Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 380fb36f297e63d428cdc3305ce862e2412fe5bd
Timestamp: Tue, 23 Jul 2024 08:05:59 +0000 (6 months ago)

+255 -60 +/-11 browse
implement basic postfix based mail server for ayllu-mail
1diff --git a/containers/multiuser-mail/Containerfile b/containers/multiuser-mail/Containerfile
2index d6a8692..5c21597 100644
3--- a/containers/multiuser-mail/Containerfile
4+++ b/containers/multiuser-mail/Containerfile
5 @@ -12,12 +12,17 @@ RUN cargo install --locked spf-milter@"$SPF_MILTER"
6
7 RUN mkdir /build && mv -v /root/.cargo/bin/* /build
8
9- ARG BASE_IMAGE
10- FROM $BASE_IMAGE
11+ ARG MULTIUSER_IMAGE
12+ FROM $MULTIUSER_IMAGE
13
14 USER root
15
16- RUN apk add --no-cache mutt postfix
17+ RUN apk add --no-cache neomutt postfix
18+
19+ # un-privilaged user to run various milter software
20+ RUN adduser -D -s /bin/sh -H milter
21+
22+ RUN addgroup postfix milter
23
24 COPY --from=build --chown=0:0 /build/dkimdo /usr/bin/
25 COPY --from=build --chown=0:0 /build/dkim-milter /usr/bin/
26 @@ -26,3 +31,4 @@ COPY --from=build --chown=0:0 /build/spf-milter /usr/bin/
27 COPY containers/multiuser-mail/templates /etc/templates/
28 COPY containers/multiuser-mail/service /etc/service
29 COPY containers/multiuser-mail/cron.d /etc/cron.d/
30+ COPY containers/multiuser-mail/init/ /etc/ayllu-init/
31 diff --git a/containers/multiuser-mail/init/ayllu-mail.sh b/containers/multiuser-mail/init/ayllu-mail.sh
32new file mode 100755
33index 0000000..1a24852
34--- /dev/null
35+++ b/containers/multiuser-mail/init/ayllu-mail.sh
36 @@ -0,0 +1 @@
37+ #!/bin/sh
38 diff --git a/containers/multiuser-mail/init/dkim-milter.sh b/containers/multiuser-mail/init/dkim-milter.sh
39new file mode 100755
40index 0000000..91edb41
41--- /dev/null
42+++ b/containers/multiuser-mail/init/dkim-milter.sh
43 @@ -0,0 +1,20 @@
44+ #!/bin/sh
45+ set -ex
46+
47+ TEMPLATE_PATH="/etc/templates/dkim-milter/dkim-milter.conf"
48+ CONFIG_PATH="/etc/dkim-milter/dkim-milter.conf"
49+ SIGNING_KEY="/etc/dkim-milter/ed25519.key"
50+
51+ mkdir -p /etc/dkim-milter
52+
53+ if [ ! -f "$SIGNING_KEY" ] ; then
54+ echo "DKIM signing key not detected, generating it now"
55+ dkimdo genkey -O "$SIGNING_KEY" ed25519
56+ dkimdo keyinfo "$SIGNING_KEY"
57+ chown milter:milter "$SIGNING_KEY"
58+ fi
59+
60+ envsubst < "$TEMPLATE_PATH" > "$CONFIG_PATH"
61+
62+ echo "ed25519 <$SIGNING_KEY" > /etc/dkim-milter/signing-keys
63+ echo ".$AYLLU_MAIL_HOSTNAME $AYLLU_MAIL_HOSTNAME ed25519 ed25519" > /etc/dkim-milter/signing-senders
64 diff --git a/containers/multiuser-mail/init/mail.sh b/containers/multiuser-mail/init/mail.sh
65deleted file mode 100755
66index d37118b..0000000
67--- a/containers/multiuser-mail/init/mail.sh
68+++ /dev/null
69 @@ -1,2 +0,0 @@
70- #!/bin/sh
71- set -e
72 diff --git a/containers/multiuser-mail/init/postfix.sh b/containers/multiuser-mail/init/postfix.sh
73new file mode 100755
74index 0000000..795e55e
75--- /dev/null
76+++ b/containers/multiuser-mail/init/postfix.sh
77 @@ -0,0 +1,55 @@
78+ #!/bin/sh
79+
80+ AYLLU_MAIL="/usr/bin/ayllu-mail"
81+ AYLLU_CONFIG="${AYLLU_CONFIG-/etc/ayllu/config.toml}"
82+ AYLLU_DB_PATH="${AYLLU_DB_PATH-/home/ayllu/.local/share/ayllu/mail.db}"
83+
84+ # FIXME: Mailpot's master-cf generation seems to be broken but it may also be
85+ # due to my own ignorance so manually specifying it for now.
86+
87+ AYLLU_SMTP_TLS_SECURITY_LEVEL="${AYLLU_SMTP_TLS_SECURITY_LEVEL:-none}"
88+
89+ [ -n "${AYLLU_ROOT_MAIL_USER}" ] && {
90+ echo "# AYLLU: DO NOT EDIT" > /etc/postfix/aliases
91+ AYLLU_ROOT_MAIL_USER="$(echo "$AYLLU_ROOT_MAIL_USER" | tr '[:upper:]' '[:lower:]')"
92+ AYLLU_ROOT_MAIL_USER="$AYLLU_ROOT_MAIL_USER" envsubst < /etc/templates/postfix/aliases >> /etc/postfix/aliases
93+ newaliases
94+ }
95+
96+ # hide sender's IP address / User Agent
97+ # See https://wiki.archlinux.org/title/Postfix#Hide_the_sender's_IP_and_user_agent_in_the_Received_header
98+ cp /etc/templates/postfix/smtp_header_checks /etc/postfix/
99+ postconf -e smtp_header_checks="regexp:/etc/postfix/smtp_header_checks"
100+ postconf -e smtpd_helo_required=yes
101+
102+ # attachments are entirely disallowed
103+ cp /etc/templates/postfix/mime_header_checks /etc/postfix/
104+ postconf -e mime_header_checks="regexp:/etc/postfix/mime_header_checks"
105+
106+ postconf -e smtp_tls_security_level="$AYLLU_SMTP_TLS_SECURITY_LEVEL"
107+ postconf -e maillog_file="/dev/stdout"
108+
109+ AYLLU_MAIL_HOSTNAME="${AYLLU_MAIL_HOSTNAME:-localhost}"
110+ postconf -e myhostname="${AYLLU_MAIL_HOSTNAME}"
111+
112+ # disallow relay from anywhere but localhost
113+ postconf -e inet_interfaces="loopback-only"
114+ postconf -e mynetworks="127.0.0.0/8"
115+ postconf -e local_transport="local"
116+ postconf -e transport_maps="lmdb:/etc/postfix/transport"
117+
118+ # SPF
119+ postconf -e smtpd_milters="unix:/run/spf-milter/spf-milter.sock"
120+ postconf -e policyd-spf_time_limit="3600"
121+
122+ postconf -e smtpd_recipient_restrictions="permit_mynetworks,reject_unauth_destination,check_policy_service unix:private/policyd-spf"
123+
124+ # setup master.cf
125+ AYLLU_CONFIG="$AYLLU_CONFIG" AYLLU_DB_PATH="$AYLLU_DB_PATH" envsubst \
126+ < /etc/templates/postfix/master.cf > /etc/postfix/master.cf
127+
128+ su ayllu -c "$AYLLU_MAIL --config $AYLLU_CONFIG --database $AYLLU_DB_PATH postfix maps" |tee /etc/postfix/transport
129+
130+ chown -R ayllu:ayllu /home/ayllu/.local/share/ayllu
131+
132+ postmap /etc/postfix/transport
133 diff --git a/containers/multiuser-mail/init/spf-milter.sh b/containers/multiuser-mail/init/spf-milter.sh
134new file mode 100755
135index 0000000..8d2403c
136--- /dev/null
137+++ b/containers/multiuser-mail/init/spf-milter.sh
138 @@ -0,0 +1,7 @@
139+ #!/bin/sh
140+ set -e
141+
142+ TEMPLATE_PATH="/etc/templates/spf-milter/spf-milter.conf"
143+ CONFIG_PATH="/etc/spf-milter.conf"
144+
145+ envsubst < "$TEMPLATE_PATH" > "$CONFIG_PATH"
146 diff --git a/containers/multiuser-mail/service/dkim-milter/run b/containers/multiuser-mail/service/dkim-milter/run
147new file mode 100755
148index 0000000..f09b180
149--- /dev/null
150+++ b/containers/multiuser-mail/service/dkim-milter/run
151 @@ -0,0 +1,10 @@
152+ #!/bin/sh
153+ set -e
154+
155+ RUN_DIR="/run/dkim-milter"
156+
157+ mkdir -p "$RUN_DIR"
158+ chown milter:milter "$RUN_DIR"
159+
160+ umask 0007
161+ exec su milter -c /usr/bin/dkim-milter
162 diff --git a/containers/multiuser-mail/service/postfix/run b/containers/multiuser-mail/service/postfix/run
163index 218c9ea..6fd52e1 100755
164--- a/containers/multiuser-mail/service/postfix/run
165+++ b/containers/multiuser-mail/service/postfix/run
166 @@ -1,59 +1,4 @@
167 #!/bin/sh
168 set -e
169
170- AYLLU_MAIL="/usr/bin/ayllu-mail"
171- AYLLU_CONFIG="${AYLLU_CONFIG-/etc/ayllu/config.toml}"
172- AYLLU_DB_PATH="${AYLLU_DB_PATH-/home/ayllu/.local/share/ayllu/mail.db}"
173-
174- # FIXME: Mailpot's master-cf generation seems to be broken but it may also be
175- # due to my own ignorance so manually specifying it for now.
176-
177- AYLLU_SMTP_TLS_SECURITY_LEVEL="${AYLLU_SMTP_TLS_SECURITY_LEVEL:-none}"
178-
179- [ -n "${AYLLU_ROOT_MAIL_USER}" ] && {
180- echo "# AYLLU: DO NOT EDIT" > /etc/postfix/aliases
181- AYLLU_ROOT_MAIL_USER="$(echo "$AYLLU_ROOT_MAIL_USER" | tr '[:upper:]' '[:lower:]')"
182- AYLLU_ROOT_MAIL_USER="$AYLLU_ROOT_MAIL_USER" envsubst < /etc/templates/postfix/aliases >> /etc/postfix/aliases
183- newaliases
184- }
185-
186- # hide sender's IP address / User Agent
187- # See https://wiki.archlinux.org/title/Postfix#Hide_the_sender's_IP_and_user_agent_in_the_Received_header
188- cp /etc/templates/postfix/smtp_header_checks /etc/postfix/
189- postconf -e smtp_header_checks="regexp:/etc/postfix/smtp_header_checks"
190- postconf -e smtpd_helo_required=yes
191-
192- # attachments are entirely disallowed
193- cp /etc/templates/postfix/mime_header_checks /etc/postfix/
194- postconf -e mime_header_checks="regexp:/etc/postfix/mime_header_checks"
195-
196- postconf -e smtp_tls_security_level="$AYLLU_SMTP_TLS_SECURITY_LEVEL"
197- postconf -e maillog_file="/dev/stdout"
198-
199- AYLLU_MAIL_HOSTNAME="${AYLLU_MAIL_HOSTNAME:-localhost}"
200- postconf -e myhostname="${AYLLU_MAIL_HOSTNAME}"
201-
202- # disallow relay from anywhere but localhost
203- postconf -e inet_interfaces="loopback-only"
204- postconf -e mynetworks="127.0.0.0/8"
205- postconf -e local_transport="local"
206- postconf -e transport_maps="lmdb:/etc/postfix/transport"
207-
208- # DKIM
209- postconf -e non_smtpd_milters="unix:/run/opendkim/opendkim.sock"
210- postconf -e smtpd_milters="unix:/run/opendkim/opendkim.sock"
211-
212- # SPF
213- postconf -e policyd-spf_time_limit="3600"
214- postconf -e smtpd_recipient_restrictions="permit_mynetworks,reject_unauth_destination,check_policy_service unix:private/policyd-spf"
215-
216- # setup master.cf
217- AYLLU_CONFIG="$AYLLU_CONFIG" AYLLU_DB_PATH="$AYLLU_DB_PATH" envsubst \
218- < /etc/templates/postfix/master.cf > /etc/postfix/master.cf
219-
220- "$AYLLU_MAIL" --config "$AYLLU_CONFIG" --database "$AYLLU_DB_PATH" \
221- postfix maps > /etc/postfix/transport
222-
223- postmap /etc/postfix/transport
224-
225 exec postfix -c /etc/postfix start-fg
226 diff --git a/containers/multiuser-mail/service/spf-milter/run b/containers/multiuser-mail/service/spf-milter/run
227new file mode 100755
228index 0000000..ddef30d
229--- /dev/null
230+++ b/containers/multiuser-mail/service/spf-milter/run
231 @@ -0,0 +1,10 @@
232+ #!/bin/sh
233+ set -e
234+
235+ RUN_DIR="/run/spf-milter"
236+
237+ mkdir -p "$RUN_DIR"
238+ chown milter:milter "$RUN_DIR"
239+
240+ umask 0007
241+ exec su milter -c /usr/bin/spf-milter
242 diff --git a/containers/multiuser-mail/templates/dkim-milter/dkim-milter.conf b/containers/multiuser-mail/templates/dkim-milter/dkim-milter.conf
243new file mode 100644
244index 0000000..e81678c
245--- /dev/null
246+++ b/containers/multiuser-mail/templates/dkim-milter/dkim-milter.conf
247 @@ -0,0 +1,135 @@
248+ # DKIM Milter sample configuration file
249+ # See the manual page dkim-milter.conf(5) for reference documentation.
250+
251+ #
252+ # General
253+ #
254+
255+ # Start the milter listening on port 3000:
256+ # socket = inet:localhost:3000
257+ socket = unix:/run/dkim-milter/dkim-milter.sock
258+
259+ # Whether to only "sign", only "verify", or make this decision "auto"matically:
260+ mode = auto
261+ #mode = sign
262+
263+ # Read signing keys and signing senders from the following files:
264+ signing_senders = /etc/dkim-milter/signing-senders
265+ # signing_keys = <sample-conf/signing-keys
266+ signing_keys = /etc/dkim-milter/signing-keys
267+ #signing_keys = sqlite://mail-config.db
268+ #signing_keys = sqlite://mail-config.db#dkim_signing_keys
269+
270+ # Read connection-specific configuration overrides from this file:
271+ # connection_overrides = <sample-conf/connection-overrides
272+ #
273+ # # Read recipient-specific configuration overrides from this file:
274+ # recipient_overrides = <sample-conf/recipient-overrides
275+
276+ # Treat message transactions from these networks as eligible for signing:
277+ # trusted_networks = loopback
278+ # #trusted_networks = 12.3.4.56/28, 2001:1600:2:3::4cde
279+ #
280+ # # Whether to treat messages from authenticated senders as eligible for signing:
281+ # trust_authenticated_senders = yes
282+ #
283+ # # Use this authserv-id in generated Authentication-Results headers:
284+ # authserv_id = mail.example.com
285+ #
286+ # # Whether to delete forged Authentication-Results headers
287+ # # ("forged" means *incoming* authserv-id equals *our* authserv-id).
288+ # # Important: If you use an earlier milter that adds such headers and takes care
289+ # # of deletion itself (eg, SPF Milter), you must disable this setting, else those
290+ # # legitimate headers will be deleted by DKIM Milter.
291+ # delete_incoming_authentication_results = yes
292+ #
293+ # # Whether to only accept signing senders (in Sender or From header) that match
294+ # # the envelope sender (in MAIL FROM) for signing.
295+ # require_envelope_sender_match = no
296+
297+ # Log destination (syslog, stderr) and log level (error, warn, info, debug).
298+ log_destination = stderr
299+ log_level = info
300+
301+ # Maximum time to allow when querying for DKIM public key records.
302+ lookup_timeout = 10s
303+
304+ # Whether to operate without applying changes to messages or rejecting messages.
305+ dry_run = no
306+
307+ #
308+ # Signing
309+ #
310+
311+ # When signing, include the following headers in the signature. Value "default"
312+ # selects the default set of headers plus additional colon-separated headers
313+ # after a semicolon. Value "all" selects all headers present.
314+ sign_headers = default
315+ #sign_headers = default; Message-ID
316+ #sign_headers = all
317+ #sign_headers = From:To:Cc:Date:Subject
318+
319+ # Value "default" in parameter sign_headers refers to this set of headers:
320+ default_signed_headers = From:Reply-To:Subject:Date:To:Cc:Resent-Date:Resent-From:Resent-To:Resent-Cc:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive
321+
322+ # When value "all" is used in parameter sign_headers, exclude these headers:
323+ default_unsigned_headers = Return-Path:Received:Comments:Keywords
324+
325+ # When signing, oversign these headers, that is include them in h= once more
326+ # than actually present. Value "signed" oversigns all headers included in the
327+ # h= tag, value "signed-extended" additionally oversigns all headers in the
328+ # default set even if not present in the message.
329+ oversign_headers =
330+ #oversign_headers = From:To
331+ #oversign_headers = signed
332+ #oversign_headers = signed-extended
333+
334+ # When signing, canonicalize using the following algorithm:
335+ canonicalization = relaxed/simple
336+
337+ # When signing, set the valid duration in the x= tag to this value:
338+ expiration = 5d
339+ #expiration = never
340+
341+ # Whether to record the length of the signed body in the l= tag:
342+ limit_body_length = no
343+
344+ # Whether to record the original headers in the z= tag:
345+ copy_headers = no
346+
347+ # Whether to include tag r=y in signatures (RFC 6651, DKIM Failure Reporting):
348+ request_reports = no
349+
350+ #
351+ # Verification
352+ #
353+
354+ # Whether to accept expired signatures.
355+ allow_expired = no
356+
357+ # Whether to accept signatures with a timestamp in the future.
358+ allow_timestamp_in_future = no
359+
360+ # Whether to accept signatures using the SHA-1 hash algorithm.
361+ # (This setting is only effective if DKIM Milter was compiled with feature
362+ # "pre-rfc8301".)
363+ allow_sha1 = no
364+
365+ # Minimum acceptable RSA public key size.
366+ min_rsa_key_bits = 1024
367+
368+ # When verifying, require these headers to be signed.
369+ required_signed_headers = From*
370+ #required_signed_headers = From:To:Subject
371+
372+ # When verifying, whether to accept messages whose body is only partially
373+ # included in a signature through an l= tag limit.
374+ forbid_unsigned_content = no
375+
376+ # The set of signature verification results to reject with an SMTP error reply:
377+ # "missing": reject messages without DKIM signature
378+ # "no-pass": reject messages without a passing DKIM signature
379+ # "author-mismatch": reject messages that don’t have a passing DKIM signature
380+ # where d= matches the From header domain
381+ reject_failures =
382+ #reject_failures = missing, no-pass, author-mismatch
383 diff --git a/containers/multiuser-mail/templates/spf-milter/spf-milter.conf b/containers/multiuser-mail/templates/spf-milter/spf-milter.conf
384new file mode 100644
385index 0000000..d97eb8c
386--- /dev/null
387+++ b/containers/multiuser-mail/templates/spf-milter/spf-milter.conf
388 @@ -0,0 +1,8 @@
389+ # spf-milter.conf
390+
391+ socket = unix:/run/spf-milter/spf-milter.sock
392+ log_level = debug
393+ log_destination = stderr
394+
395+ # Also verify HELO before MAIL FROM:
396+ verify_helo = yes