diff options
Diffstat (limited to 'modules/private/mail')
-rw-r--r-- | modules/private/mail/default.nix | 42 | ||||
-rw-r--r-- | modules/private/mail/dovecot.nix | 292 | ||||
-rwxr-xr-x | modules/private/mail/filter-rewrite-from.py | 68 | ||||
-rw-r--r-- | modules/private/mail/milters.nix | 88 | ||||
-rw-r--r-- | modules/private/mail/opensmtpd.nix | 57 | ||||
-rw-r--r-- | modules/private/mail/postfix.nix | 471 | ||||
-rw-r--r-- | modules/private/mail/relay.nix | 235 | ||||
-rw-r--r-- | modules/private/mail/rspamd.nix | 87 | ||||
-rwxr-xr-x | modules/private/mail/scan_reported_mails | 21 | ||||
-rwxr-xr-x | modules/private/mail/sieve_bin/imapsieve_copy | 8 | ||||
-rw-r--r-- | modules/private/mail/sieve_scripts/backup.sieve | 7 | ||||
-rw-r--r-- | modules/private/mail/sieve_scripts/report_ham.sieve | 11 | ||||
-rw-r--r-- | modules/private/mail/sieve_scripts/report_spam.sieve | 3 | ||||
-rw-r--r-- | modules/private/mail/sympa.nix | 213 | ||||
-rwxr-xr-x | modules/private/mail/verify_from.py | 60 |
15 files changed, 0 insertions, 1663 deletions
diff --git a/modules/private/mail/default.nix b/modules/private/mail/default.nix deleted file mode 100644 index 2d405c6..0000000 --- a/modules/private/mail/default.nix +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | { | ||
3 | imports = [ | ||
4 | ./milters.nix | ||
5 | ./postfix.nix | ||
6 | ./dovecot.nix | ||
7 | ./relay.nix | ||
8 | ./rspamd.nix | ||
9 | ./opensmtpd.nix | ||
10 | ./sympa.nix | ||
11 | ]; | ||
12 | options.myServices.mail.enable = lib.mkEnableOption "enable Mail services"; | ||
13 | options.myServices.mailRelay.enable = lib.mkEnableOption "enable Mail relay services"; | ||
14 | options.myServices.mailBackup.enable = lib.mkEnableOption "enable MX backup services"; | ||
15 | |||
16 | config = lib.mkIf config.myServices.mail.enable { | ||
17 | security.acme.certs."mail" = config.myServices.certificates.certConfig // { | ||
18 | domain = config.hostEnv.fqdn; | ||
19 | extraDomains = let | ||
20 | zonesWithMx = builtins.filter (zone: | ||
21 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 | ||
22 | ) config.myEnv.dns.masterZones; | ||
23 | mxs = map (zone: "${config.hostEnv.mx.subdomain}.${zone.name}") zonesWithMx; | ||
24 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | ||
25 | }; | ||
26 | # This is for clients that don’t support elliptic curves (e.g. | ||
27 | # printer) | ||
28 | security.acme.certs."mail-rsa" = config.myServices.certificates.certConfig // { | ||
29 | domain = config.hostEnv.fqdn; | ||
30 | keyType = "rsa4096"; | ||
31 | extraDomains = let | ||
32 | zonesWithMx = builtins.filter (zone: | ||
33 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 | ||
34 | ) config.myEnv.dns.masterZones; | ||
35 | mxs = map (zone: "${config.hostEnv.mx.subdomain}.${zone.name}") zonesWithMx; | ||
36 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | ||
37 | }; | ||
38 | systemd.slices.mail = { | ||
39 | description = "Mail slice"; | ||
40 | }; | ||
41 | }; | ||
42 | } | ||
diff --git a/modules/private/mail/dovecot.nix b/modules/private/mail/dovecot.nix deleted file mode 100644 index b6fdc02..0000000 --- a/modules/private/mail/dovecot.nix +++ /dev/null | |||
@@ -1,292 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | let | ||
3 | sieve_bin = pkgs.runCommand "sieve_bin" { | ||
4 | buildInputs = [ pkgs.makeWrapper ]; | ||
5 | } '' | ||
6 | cp -a ${./sieve_bin} $out | ||
7 | chmod -R u+w $out | ||
8 | patchShebangs $out | ||
9 | for i in $out/*; do | ||
10 | wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils ]} | ||
11 | done | ||
12 | ''; | ||
13 | in | ||
14 | { | ||
15 | config = lib.mkIf config.myServices.mail.enable { | ||
16 | systemd.services.dovecot2.serviceConfig.Slice = "mail.slice"; | ||
17 | secrets.keys."dovecot/ldap" = { | ||
18 | user = config.services.dovecot2.user; | ||
19 | group = config.services.dovecot2.group; | ||
20 | permissions = "0400"; | ||
21 | text = '' | ||
22 | hosts = ${config.myEnv.mail.dovecot.ldap.host} | ||
23 | tls = yes | ||
24 | |||
25 | dn = ${config.myEnv.mail.dovecot.ldap.dn} | ||
26 | dnpass = ${config.myEnv.mail.dovecot.ldap.password} | ||
27 | |||
28 | auth_bind = yes | ||
29 | |||
30 | ldap_version = 3 | ||
31 | |||
32 | base = ${config.myEnv.mail.dovecot.ldap.base} | ||
33 | scope = subtree | ||
34 | |||
35 | pass_filter = ${config.myEnv.mail.dovecot.ldap.filter} | ||
36 | pass_attrs = ${config.myEnv.mail.dovecot.ldap.pass_attrs} | ||
37 | |||
38 | user_attrs = ${config.myEnv.mail.dovecot.ldap.user_attrs} | ||
39 | user_filter = ${config.myEnv.mail.dovecot.ldap.filter} | ||
40 | iterate_attrs = ${config.myEnv.mail.dovecot.ldap.iterate_attrs} | ||
41 | iterate_filter = ${config.myEnv.mail.dovecot.ldap.iterate_filter} | ||
42 | ''; | ||
43 | }; | ||
44 | |||
45 | users.users.vhost = { | ||
46 | group = "vhost"; | ||
47 | uid = config.ids.uids.vhost; | ||
48 | }; | ||
49 | users.groups.vhost.gid = config.ids.gids.vhost; | ||
50 | |||
51 | # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous....... | ||
52 | services.dovecot2 = { | ||
53 | enable = true; | ||
54 | enablePAM = false; | ||
55 | enablePop3 = true; | ||
56 | enableImap = true; | ||
57 | enableLmtp = true; | ||
58 | protocols = [ "sieve" ]; | ||
59 | modules = [ | ||
60 | pkgs.dovecot_pigeonhole | ||
61 | pkgs.dovecot_fts-xapian | ||
62 | ]; | ||
63 | mailUser = "vhost"; | ||
64 | mailGroup = "vhost"; | ||
65 | createMailUser = false; | ||
66 | mailboxes = { | ||
67 | Trash = { auto = "subscribe"; specialUse = "Trash"; }; | ||
68 | Junk = { auto = "subscribe"; specialUse = "Junk"; }; | ||
69 | Sent = { auto = "subscribe"; specialUse = "Sent"; }; | ||
70 | Drafts = { auto = "subscribe"; specialUse = "Drafts"; }; | ||
71 | }; | ||
72 | mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap"; | ||
73 | sslServerCert = "/var/lib/acme/mail/fullchain.pem"; | ||
74 | sslServerKey = "/var/lib/acme/mail/key.pem"; | ||
75 | sslCACert = "/var/lib/acme/mail/fullchain.pem"; | ||
76 | extraConfig = builtins.concatStringsSep "\n" [ | ||
77 | # For printer which doesn’t support elliptic curve | ||
78 | '' | ||
79 | ssl_alt_cert = </var/lib/acme/mail-rsa/fullchain.pem | ||
80 | ssl_alt_key = </var/lib/acme/mail-rsa/key.pem | ||
81 | '' | ||
82 | |||
83 | '' | ||
84 | postmaster_address = postmaster@immae.eu | ||
85 | mail_attribute_dict = file:%h/dovecot-attributes | ||
86 | imap_idle_notify_interval = 20 mins | ||
87 | namespace inbox { | ||
88 | type = private | ||
89 | separator = / | ||
90 | inbox = yes | ||
91 | list = yes | ||
92 | } | ||
93 | '' | ||
94 | |||
95 | # ACL | ||
96 | '' | ||
97 | mail_plugins = $mail_plugins acl | ||
98 | plugin { | ||
99 | acl = vfile:${pkgs.writeText "dovecot-acl" '' | ||
100 | Backup/* owner lrp | ||
101 | ''} | ||
102 | acl_globals_only = yes | ||
103 | } | ||
104 | '' | ||
105 | |||
106 | # Full text search | ||
107 | '' | ||
108 | # needs to be bigger than any mailbox size | ||
109 | default_vsz_limit = 2GB | ||
110 | mail_plugins = $mail_plugins fts fts_xapian | ||
111 | plugin { | ||
112 | plugin = fts fts_xapian | ||
113 | fts = xapian | ||
114 | fts_xapian = partial=2 full=20 | ||
115 | fts_autoindex = yes | ||
116 | fts_autoindex_exclude = \Junk | ||
117 | fts_autoindex_exclude2 = \Trash | ||
118 | fts_autoindex_exclude3 = Virtual/* | ||
119 | } | ||
120 | '' | ||
121 | |||
122 | # Antispam | ||
123 | # https://docs.iredmail.org/dovecot.imapsieve.html | ||
124 | '' | ||
125 | # imap_sieve plugin added below | ||
126 | |||
127 | plugin { | ||
128 | sieve_plugins = sieve_imapsieve sieve_extprograms | ||
129 | imapsieve_url = sieve://127.0.0.1:4190 | ||
130 | |||
131 | sieve_before = file:${./sieve_scripts}/backup.sieve;bindir=/var/lib/vhost/.sieve_bin | ||
132 | |||
133 | # From elsewhere to Junk folder | ||
134 | imapsieve_mailbox1_name = Junk | ||
135 | imapsieve_mailbox1_causes = COPY APPEND | ||
136 | imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
137 | |||
138 | # From Junk folder to elsewhere | ||
139 | imapsieve_mailbox2_name = * | ||
140 | imapsieve_mailbox2_from = Junk | ||
141 | imapsieve_mailbox2_causes = COPY | ||
142 | imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
143 | |||
144 | # From anywhere to NoJunk folder | ||
145 | imapsieve_mailbox3_name = NoJunk | ||
146 | imapsieve_mailbox3_causes = COPY APPEND | ||
147 | imapsieve_mailbox3_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
148 | |||
149 | sieve_pipe_bin_dir = ${sieve_bin} | ||
150 | |||
151 | sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment | ||
152 | } | ||
153 | '' | ||
154 | # Services to listen | ||
155 | '' | ||
156 | service imap-login { | ||
157 | inet_listener imap { | ||
158 | } | ||
159 | inet_listener imaps { | ||
160 | } | ||
161 | } | ||
162 | service pop3-login { | ||
163 | inet_listener pop3 { | ||
164 | } | ||
165 | inet_listener pop3s { | ||
166 | } | ||
167 | } | ||
168 | service imap { | ||
169 | } | ||
170 | service pop3 { | ||
171 | } | ||
172 | service auth { | ||
173 | unix_listener auth-userdb { | ||
174 | } | ||
175 | unix_listener ${config.services.postfix.config.queue_directory}/private/auth { | ||
176 | mode = 0666 | ||
177 | } | ||
178 | } | ||
179 | service auth-worker { | ||
180 | } | ||
181 | service dict { | ||
182 | unix_listener dict { | ||
183 | } | ||
184 | } | ||
185 | service stats { | ||
186 | unix_listener stats-reader { | ||
187 | user = vhost | ||
188 | group = vhost | ||
189 | mode = 0660 | ||
190 | } | ||
191 | unix_listener stats-writer { | ||
192 | user = vhost | ||
193 | group = vhost | ||
194 | mode = 0660 | ||
195 | } | ||
196 | } | ||
197 | '' | ||
198 | |||
199 | # Authentification | ||
200 | '' | ||
201 | first_valid_uid = ${toString config.ids.uids.vhost} | ||
202 | disable_plaintext_auth = yes | ||
203 | passdb { | ||
204 | driver = ldap | ||
205 | args = ${config.secrets.fullPaths."dovecot/ldap"} | ||
206 | } | ||
207 | userdb { | ||
208 | driver = ldap | ||
209 | args = ${config.secrets.fullPaths."dovecot/ldap"} | ||
210 | } | ||
211 | '' | ||
212 | |||
213 | # Zlib | ||
214 | '' | ||
215 | mail_plugins = $mail_plugins zlib | ||
216 | plugin { | ||
217 | zlib_save_level = 6 | ||
218 | zlib_save = gz | ||
219 | } | ||
220 | '' | ||
221 | |||
222 | # Sieve | ||
223 | '' | ||
224 | plugin { | ||
225 | sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve | ||
226 | } | ||
227 | service managesieve-login { | ||
228 | } | ||
229 | service managesieve { | ||
230 | } | ||
231 | '' | ||
232 | |||
233 | # Virtual mailboxes | ||
234 | '' | ||
235 | mail_plugins = $mail_plugins virtual | ||
236 | namespace Virtual { | ||
237 | prefix = Virtual/ | ||
238 | location = virtual:~/Virtual | ||
239 | } | ||
240 | '' | ||
241 | |||
242 | # Protocol specific configuration | ||
243 | # Needs to come last if there are mail_plugins entries | ||
244 | '' | ||
245 | protocol imap { | ||
246 | mail_plugins = $mail_plugins imap_sieve imap_acl | ||
247 | } | ||
248 | protocol lda { | ||
249 | mail_plugins = $mail_plugins sieve | ||
250 | } | ||
251 | '' | ||
252 | ]; | ||
253 | }; | ||
254 | networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ]; | ||
255 | system.activationScripts.dovecot = { | ||
256 | deps = [ "users" ]; | ||
257 | text ='' | ||
258 | install -m 0755 -o vhost -g vhost -d /var/lib/vhost | ||
259 | ''; | ||
260 | }; | ||
261 | |||
262 | services.cron.systemCronJobs = let | ||
263 | cron_script = pkgs.writeScriptBin "cleanup-imap-folders" '' | ||
264 | ${pkgs.dovecot}/bin/doveadm expunge -A MAILBOX "Backup/*" NOT FLAGGED BEFORE 8w 2>&1 > /dev/null | grep -v "Mailbox doesn't exist:" | grep -v "Info: Opening DB" | ||
265 | ${pkgs.dovecot}/bin/doveadm expunge -A MAILBOX Junk SEEN NOT FLAGGED BEFORE 4w 2>&1 > /dev/null | grep -v "Mailbox doesn't exist:" | grep -v "Info: Opening DB" | ||
266 | ${pkgs.dovecot}/bin/doveadm expunge -A MAILBOX Trash NOT FLAGGED BEFORE 4w 2>&1 > /dev/null | grep -v "Mailbox doesn't exist:" | grep -v "Info: Opening DB" | ||
267 | ''; | ||
268 | in | ||
269 | [ | ||
270 | "0 2 * * * root ${cron_script}/bin/cleanup-imap-folders" | ||
271 | ]; | ||
272 | security.acme.certs."mail-rsa" = { | ||
273 | postRun = '' | ||
274 | systemctl restart dovecot2.service | ||
275 | ''; | ||
276 | extraDomains = { | ||
277 | "imap.immae.eu" = null; | ||
278 | "pop3.immae.eu" = null; | ||
279 | }; | ||
280 | }; | ||
281 | security.acme.certs."mail" = { | ||
282 | postRun = '' | ||
283 | systemctl restart dovecot2.service | ||
284 | ''; | ||
285 | extraDomains = { | ||
286 | "imap.immae.eu" = null; | ||
287 | "pop3.immae.eu" = null; | ||
288 | }; | ||
289 | }; | ||
290 | }; | ||
291 | } | ||
292 | |||
diff --git a/modules/private/mail/filter-rewrite-from.py b/modules/private/mail/filter-rewrite-from.py deleted file mode 100755 index aad9c69..0000000 --- a/modules/private/mail/filter-rewrite-from.py +++ /dev/null | |||
@@ -1,68 +0,0 @@ | |||
1 | #! /usr/bin/env python3 | ||
2 | import sys | ||
3 | |||
4 | sys.stdin.reconfigure(encoding='utf-8') | ||
5 | sys.stdout.reconfigure(encoding='utf-8') | ||
6 | stdin = sys.stdin | ||
7 | stdout = sys.stdout | ||
8 | |||
9 | mailaddr = sys.argv[1] | ||
10 | inheader = {} | ||
11 | |||
12 | # Change to actual file for logging | ||
13 | logfile = open("/dev/null", "a") | ||
14 | |||
15 | def log(l, i): | ||
16 | logfile.write("{} {}\n".format(i, l)) | ||
17 | logfile.flush() | ||
18 | |||
19 | def send(l): | ||
20 | log(l, ">") | ||
21 | stdout.write("{}\n".format(l)) | ||
22 | stdout.flush() | ||
23 | |||
24 | def token_and_sid(version, sid, token): | ||
25 | if version < "0.5": | ||
26 | return "{}|{}".format(token, sid) | ||
27 | else: | ||
28 | return "{}|{}".format(sid, token) | ||
29 | |||
30 | log("started", "l") | ||
31 | while True: | ||
32 | line = stdin.readline().strip() | ||
33 | log(line, "<") | ||
34 | if not line: | ||
35 | log("finished", "l") | ||
36 | break | ||
37 | splitted = line.split("|") | ||
38 | if line == "config|ready": | ||
39 | log("in config ready", "l") | ||
40 | send("register|filter|smtp-in|mail-from") | ||
41 | send("register|filter|smtp-in|data-line") | ||
42 | send("register|ready") | ||
43 | if splitted[0] != "filter": | ||
44 | continue | ||
45 | if len(splitted) < 7: | ||
46 | send("invalid filter command: expected >6 fields!") | ||
47 | sys.exit(1) | ||
48 | version = splitted[1] | ||
49 | action = splitted[4] | ||
50 | sid = splitted[5] | ||
51 | token = splitted[6] | ||
52 | token_sid = token_and_sid(version, sid, token) | ||
53 | rest = "|".join(splitted[7:]) | ||
54 | if action == "mail-from": | ||
55 | inheader[sid] = True | ||
56 | send("filter-result|{}|rewrite|<{}>".format(token_sid, mailaddr)) | ||
57 | continue | ||
58 | if action == "data-line": | ||
59 | if rest == "" and inheader.get(sid, False): | ||
60 | inheader[sid] = False | ||
61 | if rest == "." and not inheader.get(sid): | ||
62 | del(inheader[sid]) | ||
63 | if inheader.get(sid, False) and rest.upper().startswith("FROM:"): | ||
64 | send("filter-dataline|{}|From: {}".format(token_sid, mailaddr)) | ||
65 | else: | ||
66 | send("filter-dataline|{}|{}".format(token_sid, rest)) | ||
67 | continue | ||
68 | send("filter-result|{}|proceed".format(token_sid)) | ||
diff --git a/modules/private/mail/milters.nix b/modules/private/mail/milters.nix deleted file mode 100644 index 4b93a7a..0000000 --- a/modules/private/mail/milters.nix +++ /dev/null | |||
@@ -1,88 +0,0 @@ | |||
1 | { lib, pkgs, config, name, ... }: | ||
2 | { | ||
3 | imports = | ||
4 | builtins.attrValues (import ../../../lib/flake-compat.nix ../../../flakes/private/openarc).nixosModules | ||
5 | ++ builtins.attrValues (import ../../../lib/flake-compat.nix ../../../flakes/private/opendmarc).nixosModules; | ||
6 | |||
7 | options.myServices.mail.milters.sockets = lib.mkOption { | ||
8 | type = lib.types.attrsOf lib.types.path; | ||
9 | default = { | ||
10 | opendkim = "/run/opendkim/opendkim.sock"; | ||
11 | opendmarc = config.services.opendmarc.socket; | ||
12 | openarc = config.services.openarc.socket; | ||
13 | }; | ||
14 | readOnly = true; | ||
15 | description = '' | ||
16 | milters sockets | ||
17 | ''; | ||
18 | }; | ||
19 | config = lib.mkIf (config.myServices.mail.enable || config.myServices.mailBackup.enable) { | ||
20 | secrets.keys = { | ||
21 | "opendkim" = { | ||
22 | isDir = true; | ||
23 | user = config.services.opendkim.user; | ||
24 | group = config.services.opendkim.group; | ||
25 | permissions = "0550"; | ||
26 | }; | ||
27 | "opendkim/eldiron.private" = { | ||
28 | user = config.services.opendkim.user; | ||
29 | group = config.services.opendkim.group; | ||
30 | permissions = "0400"; | ||
31 | text = config.myEnv.mail.dkim.eldiron.private; | ||
32 | }; | ||
33 | "opendkim/eldiron.txt" = { | ||
34 | user = config.services.opendkim.user; | ||
35 | group = config.services.opendkim.group; | ||
36 | permissions = "0444"; | ||
37 | text = '' | ||
38 | eldiron._domainkey IN TXT ${config.myEnv.mail.dkim.eldiron.public}''; | ||
39 | }; | ||
40 | }; | ||
41 | users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ]; | ||
42 | services.opendkim = { | ||
43 | enable = true; | ||
44 | socket = "local:${config.myServices.mail.milters.sockets.opendkim}"; | ||
45 | domains = builtins.concatStringsSep "," (lib.flatten (map | ||
46 | (zone: map | ||
47 | (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}") | ||
48 | (zone.withEmail or []) | ||
49 | ) | ||
50 | config.myEnv.dns.masterZones | ||
51 | )); | ||
52 | keyPath = config.secrets.fullPaths."opendkim"; | ||
53 | selector = "eldiron"; | ||
54 | configFile = pkgs.writeText "opendkim.conf" '' | ||
55 | SubDomains yes | ||
56 | UMask 002 | ||
57 | AlwaysAddARHeader yes | ||
58 | ''; | ||
59 | group = config.services.postfix.group; | ||
60 | }; | ||
61 | systemd.services.opendkim.serviceConfig.Slice = "mail.slice"; | ||
62 | systemd.services.opendkim.preStart = lib.mkBefore '' | ||
63 | # Skip the prestart script as keys are handled in secrets | ||
64 | exit 0 | ||
65 | ''; | ||
66 | services.filesWatcher.opendkim = { | ||
67 | restart = true; | ||
68 | paths = [ | ||
69 | config.secrets.fullPaths."opendkim/eldiron.private" | ||
70 | ]; | ||
71 | }; | ||
72 | |||
73 | systemd.services.milter_verify_from = { | ||
74 | description = "Verify from milter"; | ||
75 | after = [ "network.target" ]; | ||
76 | wantedBy = [ "multi-user.target" ]; | ||
77 | |||
78 | serviceConfig = { | ||
79 | Slice = "mail.slice"; | ||
80 | User = "postfix"; | ||
81 | Group = "postfix"; | ||
82 | ExecStart = let python = pkgs.python3.withPackages (p: [ p.pymilter ]); | ||
83 | in "${python}/bin/python ${./verify_from.py} -s /run/milter_verify_from/verify_from.sock"; | ||
84 | RuntimeDirectory = "milter_verify_from"; | ||
85 | }; | ||
86 | }; | ||
87 | }; | ||
88 | } | ||
diff --git a/modules/private/mail/opensmtpd.nix b/modules/private/mail/opensmtpd.nix deleted file mode 100644 index e05bba9..0000000 --- a/modules/private/mail/opensmtpd.nix +++ /dev/null | |||
@@ -1,57 +0,0 @@ | |||
1 | { lib, pkgs, config, name, ... }: | ||
2 | { | ||
3 | config = lib.mkIf config.myServices.mailRelay.enable { | ||
4 | secrets.keys."opensmtpd/creds" = { | ||
5 | user = "smtpd"; | ||
6 | group = "smtpd"; | ||
7 | permissions = "0400"; | ||
8 | text = '' | ||
9 | eldiron ${name}:${config.hostEnv.ldap.password} | ||
10 | ''; | ||
11 | }; | ||
12 | users.users.smtpd.extraGroups = [ "keys" ]; | ||
13 | services.opensmtpd = { | ||
14 | enable = true; | ||
15 | serverConfiguration = let | ||
16 | filter-rewrite-from = pkgs.runCommand "filter-rewrite-from.py" { | ||
17 | buildInputs = [ pkgs.python3 ]; | ||
18 | } '' | ||
19 | cp ${./filter-rewrite-from.py} $out | ||
20 | patchShebangs $out | ||
21 | ''; | ||
22 | in '' | ||
23 | table creds \ | ||
24 | "${config.secrets.fullPaths."opensmtpd/creds"}" | ||
25 | # FIXME: filtering requires 6.6, uncomment following lines when | ||
26 | # upgrading | ||
27 | # filter "fixfrom" \ | ||
28 | # proc-exec "${filter-rewrite-from} ${name}@immae.eu" | ||
29 | # listen on socket filter "fixfrom" | ||
30 | action "relay-rewrite-from" relay \ | ||
31 | helo ${config.hostEnv.fqdn} \ | ||
32 | host smtp+tls://eldiron@eldiron.immae.eu:587 \ | ||
33 | auth <creds> \ | ||
34 | mail-from ${name}@immae.eu | ||
35 | action "relay" relay \ | ||
36 | helo ${config.hostEnv.fqdn} \ | ||
37 | host smtp+tls://eldiron@eldiron.immae.eu:587 \ | ||
38 | auth <creds> | ||
39 | match for any !mail-from "@immae.eu" action "relay-rewrite-from" | ||
40 | match for any mail-from "@immae.eu" action "relay" | ||
41 | ''; | ||
42 | }; | ||
43 | environment.systemPackages = [ config.services.opensmtpd.package ]; | ||
44 | services.mail.sendmailSetuidWrapper = { | ||
45 | program = "sendmail"; | ||
46 | source = "${config.services.opensmtpd.package}/bin/smtpctl"; | ||
47 | setuid = false; | ||
48 | setgid = false; | ||
49 | }; | ||
50 | security.wrappers.mailq = { | ||
51 | program = "mailq"; | ||
52 | source = "${config.services.opensmtpd.package}/bin/smtpctl"; | ||
53 | setuid = false; | ||
54 | setgid = false; | ||
55 | }; | ||
56 | }; | ||
57 | } | ||
diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix deleted file mode 100644 index ae98a8a..0000000 --- a/modules/private/mail/postfix.nix +++ /dev/null | |||
@@ -1,471 +0,0 @@ | |||
1 | { lib, pkgs, config, nodes, ... }: | ||
2 | { | ||
3 | config = lib.mkIf config.myServices.mail.enable { | ||
4 | secrets.keys = { | ||
5 | "postfix/mysql_alias_maps" = { | ||
6 | user = config.services.postfix.user; | ||
7 | group = config.services.postfix.group; | ||
8 | permissions = "0440"; | ||
9 | text = '' | ||
10 | # We need to specify that option to trigger ssl connection | ||
11 | tls_ciphers = TLSv1.2 | ||
12 | user = ${config.myEnv.mail.postfix.mysql.user} | ||
13 | password = ${config.myEnv.mail.postfix.mysql.password} | ||
14 | hosts = unix:${config.myEnv.mail.postfix.mysql.socket} | ||
15 | dbname = ${config.myEnv.mail.postfix.mysql.database} | ||
16 | query = SELECT DISTINCT destination | ||
17 | FROM forwardings | ||
18 | WHERE | ||
19 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
20 | AND active = 1 | ||
21 | AND '%s' NOT IN | ||
22 | ( | ||
23 | SELECT source | ||
24 | FROM forwardings_blacklisted | ||
25 | WHERE source = '%s' | ||
26 | ) UNION | ||
27 | SELECT 'devnull@immae.eu' | ||
28 | FROM forwardings_blacklisted | ||
29 | WHERE source = '%s' | ||
30 | ''; | ||
31 | }; | ||
32 | "postfix/ldap_mailboxes" = { | ||
33 | user = config.services.postfix.user; | ||
34 | group = config.services.postfix.group; | ||
35 | permissions = "0440"; | ||
36 | text = '' | ||
37 | server_host = ldaps://${config.myEnv.mail.dovecot.ldap.host}:636 | ||
38 | search_base = ${config.myEnv.mail.dovecot.ldap.base} | ||
39 | query_filter = ${config.myEnv.mail.dovecot.ldap.postfix_mailbox_filter} | ||
40 | bind_dn = ${config.myEnv.mail.dovecot.ldap.dn} | ||
41 | bind_pw = ${config.myEnv.mail.dovecot.ldap.password} | ||
42 | result_attribute = immaePostfixAddress | ||
43 | result_format = dummy | ||
44 | version = 3 | ||
45 | ''; | ||
46 | }; | ||
47 | "postfix/mysql_sender_login_maps" = { | ||
48 | user = config.services.postfix.user; | ||
49 | group = config.services.postfix.group; | ||
50 | permissions = "0440"; | ||
51 | text = '' | ||
52 | # We need to specify that option to trigger ssl connection | ||
53 | tls_ciphers = TLSv1.2 | ||
54 | user = ${config.myEnv.mail.postfix.mysql.user} | ||
55 | password = ${config.myEnv.mail.postfix.mysql.password} | ||
56 | hosts = unix:${config.myEnv.mail.postfix.mysql.socket} | ||
57 | dbname = ${config.myEnv.mail.postfix.mysql.database} | ||
58 | query = SELECT DISTINCT destination | ||
59 | FROM forwardings | ||
60 | WHERE | ||
61 | ( | ||
62 | (regex = 1 AND CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d') REGEXP CONCAT('^',source,'$') ) | ||
63 | OR | ||
64 | (regex = 0 AND source = CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d')) | ||
65 | ) | ||
66 | AND active = 1 | ||
67 | UNION SELECT CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d') AS destination | ||
68 | ''; | ||
69 | }; | ||
70 | "postfix/mysql_sender_relays_maps" = { | ||
71 | user = config.services.postfix.user; | ||
72 | group = config.services.postfix.group; | ||
73 | permissions = "0440"; | ||
74 | text = '' | ||
75 | # We need to specify that option to trigger ssl connection | ||
76 | tls_ciphers = TLSv1.2 | ||
77 | user = ${config.myEnv.mail.postfix.mysql.user} | ||
78 | password = ${config.myEnv.mail.postfix.mysql.password} | ||
79 | hosts = unix:${config.myEnv.mail.postfix.mysql.socket} | ||
80 | dbname = ${config.myEnv.mail.postfix.mysql.database} | ||
81 | # INSERT INTO sender_relays | ||
82 | # (`from`, owner, relay, login, password, regex, active) | ||
83 | # VALUES | ||
84 | # ( 'sender@otherhost.org' | ||
85 | # , 'me@mail.immae.eu' | ||
86 | # , '[otherhost.org]:587' | ||
87 | # , 'otherhostlogin' | ||
88 | # , AES_ENCRYPT('otherhostpassword', '${config.myEnv.mail.postfix.mysql.password_encrypt}') | ||
89 | # , '0' | ||
90 | # , '1'); | ||
91 | |||
92 | query = SELECT DISTINCT `owner` | ||
93 | FROM sender_relays | ||
94 | WHERE | ||
95 | ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s')) | ||
96 | AND active = 1 | ||
97 | ''; | ||
98 | }; | ||
99 | "postfix/mysql_sender_relays_hosts" = { | ||
100 | user = config.services.postfix.user; | ||
101 | group = config.services.postfix.group; | ||
102 | permissions = "0440"; | ||
103 | text = '' | ||
104 | # We need to specify that option to trigger ssl connection | ||
105 | tls_ciphers = TLSv1.2 | ||
106 | user = ${config.myEnv.mail.postfix.mysql.user} | ||
107 | password = ${config.myEnv.mail.postfix.mysql.password} | ||
108 | hosts = unix:${config.myEnv.mail.postfix.mysql.socket} | ||
109 | dbname = ${config.myEnv.mail.postfix.mysql.database} | ||
110 | |||
111 | query = SELECT DISTINCT relay | ||
112 | FROM sender_relays | ||
113 | WHERE | ||
114 | ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s')) | ||
115 | AND active = 1 | ||
116 | ''; | ||
117 | }; | ||
118 | "postfix/mysql_sender_relays_creds" = { | ||
119 | user = config.services.postfix.user; | ||
120 | group = config.services.postfix.group; | ||
121 | permissions = "0440"; | ||
122 | text = '' | ||
123 | # We need to specify that option to trigger ssl connection | ||
124 | tls_ciphers = TLSv1.2 | ||
125 | user = ${config.myEnv.mail.postfix.mysql.user} | ||
126 | password = ${config.myEnv.mail.postfix.mysql.password} | ||
127 | hosts = unix:${config.myEnv.mail.postfix.mysql.socket} | ||
128 | dbname = ${config.myEnv.mail.postfix.mysql.database} | ||
129 | |||
130 | query = SELECT DISTINCT CONCAT(`login`, ':', AES_DECRYPT(`password`, '${config.myEnv.mail.postfix.mysql.password_encrypt}')) | ||
131 | FROM sender_relays | ||
132 | WHERE | ||
133 | ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s')) | ||
134 | AND active = 1 | ||
135 | ''; | ||
136 | }; | ||
137 | "postfix/ldap_ejabberd_users_immae_fr" = { | ||
138 | user = config.services.postfix.user; | ||
139 | group = config.services.postfix.group; | ||
140 | permissions = "0440"; | ||
141 | text = '' | ||
142 | server_host = ldaps://${config.myEnv.jabber.ldap.host}:636 | ||
143 | search_base = ${config.myEnv.jabber.ldap.base} | ||
144 | query_filter = ${config.myEnv.jabber.postfix_user_filter} | ||
145 | domain = immae.fr | ||
146 | bind_dn = ${config.myEnv.jabber.ldap.dn} | ||
147 | bind_pw = ${config.myEnv.jabber.ldap.password} | ||
148 | result_attribute = immaeXmppUid | ||
149 | result_format = ejabberd@localhost | ||
150 | version = 3 | ||
151 | ''; | ||
152 | }; | ||
153 | } // lib.mapAttrs' (name: v: lib.nameValuePair "postfix/scripts/${name}-env" { | ||
154 | user = "postfixscripts"; | ||
155 | group = "root"; | ||
156 | permissions = "0400"; | ||
157 | text = builtins.toJSON v.env; | ||
158 | }) config.myEnv.mail.scripts; | ||
159 | |||
160 | networking.firewall.allowedTCPPorts = [ 25 465 587 ]; | ||
161 | |||
162 | users.users.postfixscripts = { | ||
163 | group = "keys"; | ||
164 | uid = config.ids.uids.postfixscripts; | ||
165 | description = "Postfix scripts user"; | ||
166 | }; | ||
167 | users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; | ||
168 | services.filesWatcher.postfix = { | ||
169 | restart = true; | ||
170 | paths = [ | ||
171 | config.secrets.fullPaths."postfix/mysql_alias_maps" | ||
172 | config.secrets.fullPaths."postfix/ldap_mailboxes" | ||
173 | config.secrets.fullPaths."postfix/mysql_sender_login_maps" | ||
174 | config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr" | ||
175 | ]; | ||
176 | }; | ||
177 | services.postfix = { | ||
178 | extraAliases = let | ||
179 | toScript = name: script: pkgs.writeScript name '' | ||
180 | #! ${pkgs.stdenv.shell} | ||
181 | mail=$(${pkgs.coreutils}/bin/cat -) | ||
182 | output=$(echo "$mail" | ${script} 2>&1) | ||
183 | ret=$? | ||
184 | |||
185 | if [ "$ret" != "0" ]; then | ||
186 | echo "$mail" \ | ||
187 | | ${pkgs.procmail}/bin/formail -i "X-Return-Code: $ret" \ | ||
188 | | /run/wrappers/bin/sendmail -i scripts_error+${name}@mail.immae.eu | ||
189 | |||
190 | messageId=$(echo "$mail" | ${pkgs.procmail}/bin/formail -x "Message-Id:") | ||
191 | repeat=$(echo "$mail" | ${pkgs.procmail}/bin/formail -X "From:" -X "Received:") | ||
192 | |||
193 | ${pkgs.coreutils}/bin/cat <<EOF | /run/wrappers/bin/sendmail -i scripts_error+${name}@mail.immae.eu | ||
194 | $repeat | ||
195 | To: scripts_error+${name}@mail.immae.eu | ||
196 | Subject: Log from script error | ||
197 | Content-Type: text/plain; charset="UTF-8" | ||
198 | Content-Transfer-Encoding: 8bit | ||
199 | References:$messageId | ||
200 | MIME-Version: 1.0 | ||
201 | X-Return-Code: $ret | ||
202 | |||
203 | Error code: $ret | ||
204 | Output of message: | ||
205 | -------------- | ||
206 | $output | ||
207 | -------------- | ||
208 | EOF | ||
209 | fi | ||
210 | ''; | ||
211 | scripts = lib.attrsets.mapAttrs (n: v: | ||
212 | toScript n (pkgs.callPackage (builtins.fetchGit { url = v.src.url; ref = "master"; rev = v.src.rev; }) { scriptEnv = config.secrets.fullPaths."postfix/scripts/${n}-env"; }) | ||
213 | ) config.myEnv.mail.scripts // { | ||
214 | testmail = pkgs.writeScript "testmail" '' | ||
215 | #! ${pkgs.stdenv.shell} | ||
216 | ${pkgs.coreutils}/bin/touch \ | ||
217 | "/var/lib/naemon/checks/email/$(${pkgs.procmail}/bin/formail -x To: | ${pkgs.coreutils}/bin/tr -d ' <>')" | ||
218 | ''; | ||
219 | }; | ||
220 | in builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: ''${n}: "|${v}"'') scripts); | ||
221 | mapFiles = let | ||
222 | recipient_maps = let | ||
223 | name = n: i: "relay_${n}_${toString i}"; | ||
224 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( | ||
225 | if m.type == "hash" | ||
226 | then pkgs.writeText (name n i) m.content | ||
227 | else null | ||
228 | ); | ||
229 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; | ||
230 | in lib.attrsets.filterAttrs (k: v: v != null) ( | ||
231 | lib.attrsets.listToAttrs (lib.flatten ( | ||
232 | lib.attrsets.mapAttrsToList pairs config.myEnv.mail.postfix.backup_domains | ||
233 | )) | ||
234 | ); | ||
235 | relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( | ||
236 | lib.attrsets.mapAttrs' (n: v: | ||
237 | lib.attrsets.nameValuePair "recipient_access_${n}" ( | ||
238 | if lib.attrsets.hasAttr "relay_restrictions" v | ||
239 | then pkgs.writeText "recipient_access_${n}" v.relay_restrictions | ||
240 | else null | ||
241 | ) | ||
242 | ) config.myEnv.mail.postfix.backup_domains | ||
243 | ); | ||
244 | virtual_map = { | ||
245 | virtual = let | ||
246 | cfg = config.myEnv.monitoring.email_check.eldiron; | ||
247 | address = "${cfg.mail_address}@${cfg.mail_domain}"; | ||
248 | in pkgs.writeText "postfix-virtual" ( | ||
249 | builtins.concatStringsSep "\n" ( | ||
250 | ["${address} testmail@localhost"] ++ | ||
251 | lib.attrsets.mapAttrsToList ( | ||
252 | n: v: lib.optionalString v.external '' | ||
253 | script_${n}@mail.immae.eu ${n}@localhost, scripts@mail.immae.eu | ||
254 | '' | ||
255 | ) config.myEnv.mail.scripts | ||
256 | ) | ||
257 | ); | ||
258 | }; | ||
259 | sasl_access = { | ||
260 | host_sender_login = with lib.attrsets; let | ||
261 | addresses = zipAttrs (lib.flatten (mapAttrsToList | ||
262 | (n: v: (map (e: { "${e}" = "${n}@immae.eu"; }) v.emails)) config.myEnv.servers)); | ||
263 | joined = builtins.concatStringsSep ","; | ||
264 | in pkgs.writeText "host-sender-login" | ||
265 | (builtins.concatStringsSep "\n" (mapAttrsToList (n: v: "${n} ${joined v}") addresses)); | ||
266 | }; | ||
267 | in | ||
268 | recipient_maps // relay_restrictions // virtual_map // sasl_access; | ||
269 | config = { | ||
270 | ### postfix module overrides | ||
271 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | ||
272 | smtp_tls_CAfile = lib.mkForce ""; | ||
273 | smtp_tls_cert_file = lib.mkForce ""; | ||
274 | smtp_tls_key_file = lib.mkForce ""; | ||
275 | |||
276 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" | ||
277 | mailbox_size_limit = "1073741825"; # Workaround, local delivered mails should all go through scripts | ||
278 | alias_database = "\$alias_maps"; | ||
279 | |||
280 | ### Aliases scripts user | ||
281 | default_privs = "postfixscripts"; | ||
282 | |||
283 | ### Virtual mailboxes config | ||
284 | virtual_alias_maps = [ | ||
285 | "hash:/etc/postfix/virtual" | ||
286 | "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}" | ||
287 | "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}" | ||
288 | ]; | ||
289 | virtual_mailbox_domains = config.myEnv.mail.postfix.additional_mailbox_domains | ||
290 | ++ lib.remove null (lib.flatten (map | ||
291 | (zone: map | ||
292 | (e: if e.receive | ||
293 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" | ||
294 | else null | ||
295 | ) | ||
296 | (zone.withEmail or []) | ||
297 | ) | ||
298 | config.myEnv.dns.masterZones | ||
299 | )); | ||
300 | virtual_mailbox_maps = [ | ||
301 | "ldap:${config.secrets.fullPaths."postfix/ldap_mailboxes"}" | ||
302 | ]; | ||
303 | dovecot_destination_recipient_limit = "1"; | ||
304 | virtual_transport = "dovecot"; | ||
305 | |||
306 | ### Relay domains | ||
307 | relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) config.myEnv.mail.postfix.backup_domains); | ||
308 | relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: | ||
309 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps | ||
310 | ) config.myEnv.mail.postfix.backup_domains); | ||
311 | smtpd_relay_restrictions = [ | ||
312 | "defer_unauth_destination" | ||
313 | ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: | ||
314 | if lib.attrsets.hasAttr "relay_restrictions" v | ||
315 | then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] | ||
316 | else [] | ||
317 | ) config.myEnv.mail.postfix.backup_domains); | ||
318 | |||
319 | ### Additional smtpd configuration | ||
320 | smtpd_tls_received_header = "yes"; | ||
321 | smtpd_tls_loglevel = "1"; | ||
322 | |||
323 | ### Email sending configuration | ||
324 | smtp_tls_security_level = "may"; | ||
325 | smtp_tls_loglevel = "1"; | ||
326 | |||
327 | ### Force ip bind for smtp | ||
328 | smtp_bind_address = config.hostEnv.ips.main.ip4; | ||
329 | smtp_bind_address6 = builtins.head config.hostEnv.ips.main.ip6; | ||
330 | |||
331 | # Use some relays when authorized senders are not myself | ||
332 | smtp_sasl_mechanism_filter = "plain,login"; # GSSAPI Not correctly supported by postfix | ||
333 | smtp_sasl_auth_enable = "yes"; | ||
334 | smtp_sasl_password_maps = | ||
335 | "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_creds"}"; | ||
336 | smtp_sasl_security_options = "noanonymous"; | ||
337 | smtp_sender_dependent_authentication = "yes"; | ||
338 | sender_dependent_relayhost_maps = | ||
339 | "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_hosts"}"; | ||
340 | |||
341 | ### opendkim, opendmarc, openarc milters | ||
342 | non_smtpd_milters = [ | ||
343 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | ||
344 | ]; | ||
345 | smtpd_milters = [ | ||
346 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | ||
347 | "unix:${config.myServices.mail.milters.sockets.openarc}" | ||
348 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | ||
349 | ]; | ||
350 | |||
351 | smtp_use_tls = true; | ||
352 | smtpd_use_tls = true; | ||
353 | smtpd_tls_chain_files = builtins.concatStringsSep "," [ "/var/lib/acme/mail/full.pem" "/var/lib/acme/mail-rsa/full.pem" ]; | ||
354 | |||
355 | maximal_queue_lifetime = "6w"; | ||
356 | bounce_queue_lifetime = "6w"; | ||
357 | }; | ||
358 | enable = true; | ||
359 | enableSmtp = true; | ||
360 | enableSubmission = true; | ||
361 | submissionOptions = { | ||
362 | # Don’t use "long form", only commas (cf | ||
363 | # http://www.postfix.org/master.5.html long form is not handled | ||
364 | # well by the submission function) | ||
365 | smtpd_tls_security_level = "encrypt"; | ||
366 | smtpd_sasl_auth_enable = "yes"; | ||
367 | smtpd_tls_auth_only = "yes"; | ||
368 | smtpd_sasl_tls_security_options = "noanonymous"; | ||
369 | smtpd_sasl_type = "dovecot"; | ||
370 | smtpd_sasl_path = "private/auth"; | ||
371 | smtpd_reject_unlisted_recipient = "no"; | ||
372 | smtpd_client_restrictions = "permit_sasl_authenticated,reject"; | ||
373 | smtpd_relay_restrictions = "permit_sasl_authenticated,reject"; | ||
374 | # Refuse to send e-mails with a From that is not handled | ||
375 | smtpd_sender_restrictions = | ||
376 | "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; | ||
377 | smtpd_sender_login_maps = builtins.concatStringsSep "," [ | ||
378 | "hash:/etc/postfix/host_sender_login" | ||
379 | "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_maps"}" | ||
380 | "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}" | ||
381 | ]; | ||
382 | smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; | ||
383 | milter_macro_daemon_name = "ORIGINATING"; | ||
384 | smtpd_milters = builtins.concatStringsSep "," [ | ||
385 | # FIXME: put it back when opensmtpd is upgraded and able to | ||
386 | # rewrite the from header | ||
387 | #"unix:/run/milter_verify_from/verify_from.sock" | ||
388 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | ||
389 | ]; | ||
390 | }; | ||
391 | destination = ["localhost"]; | ||
392 | # This needs to reverse DNS | ||
393 | hostname = config.hostEnv.fqdn; | ||
394 | setSendmail = true; | ||
395 | recipientDelimiter = "+"; | ||
396 | masterConfig = { | ||
397 | submissions = { | ||
398 | type = "inet"; | ||
399 | private = false; | ||
400 | command = "smtpd"; | ||
401 | args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let | ||
402 | mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ]; | ||
403 | in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions) | ||
404 | ); | ||
405 | }; | ||
406 | dovecot = { | ||
407 | type = "unix"; | ||
408 | privileged = true; | ||
409 | chroot = false; | ||
410 | command = "pipe"; | ||
411 | args = let | ||
412 | # rspamd could be used as a milter, but then it cannot apply | ||
413 | # its checks "per user" (milter is not yet dispatched to | ||
414 | # users), so we wrap dovecot-lda inside rspamc per recipient | ||
415 | # here. | ||
416 | rspamc_dovecot = pkgs.writeScriptBin "rspamc_dovecot" '' | ||
417 | #! ${pkgs.stdenv.shell} | ||
418 | sender="$1" | ||
419 | original_recipient="$2" | ||
420 | user="$3" | ||
421 | |||
422 | ${pkgs.coreutils}/bin/cat - | \ | ||
423 | (${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d "$user" --mime || true) | \ | ||
424 | ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f "$sender" -a "$original_recipient" -d "$user" | ||
425 | ''; | ||
426 | in [ | ||
427 | "flags=ODRhu" "user=vhost:vhost" | ||
428 | "argv=${rspamc_dovecot}/bin/rspamc_dovecot \${sender} \${original_recipient} \${user}@\${nexthop}" | ||
429 | ]; | ||
430 | }; | ||
431 | }; | ||
432 | }; | ||
433 | security.acme.certs."mail" = { | ||
434 | postRun = '' | ||
435 | systemctl restart postfix.service | ||
436 | ''; | ||
437 | extraDomains = { | ||
438 | "smtp.immae.eu" = null; | ||
439 | }; | ||
440 | }; | ||
441 | security.acme.certs."mail-rsa" = { | ||
442 | postRun = '' | ||
443 | systemctl restart postfix.service | ||
444 | ''; | ||
445 | extraDomains = { | ||
446 | "smtp.immae.eu" = null; | ||
447 | }; | ||
448 | }; | ||
449 | system.activationScripts.testmail = { | ||
450 | deps = [ "users" ]; | ||
451 | text = let | ||
452 | allCfg = config.myEnv.monitoring.email_check; | ||
453 | cfg = allCfg.eldiron; | ||
454 | reverseTargets = builtins.attrNames (lib.attrsets.filterAttrs (k: v: builtins.elem "eldiron" v.targets) allCfg); | ||
455 | to_email = cfg': host': | ||
456 | let sep = if lib.strings.hasInfix "+" cfg'.mail_address then "_" else "+"; | ||
457 | in "${cfg'.mail_address}${sep}${host'}@${cfg'.mail_domain}"; | ||
458 | mails_to_receive = builtins.concatStringsSep " " (map (to_email cfg) reverseTargets); | ||
459 | in '' | ||
460 | install -m 0555 -o postfixscripts -g keys -d /var/lib/naemon/checks/email | ||
461 | for f in ${mails_to_receive}; do | ||
462 | if [ ! -f /var/lib/naemon/checks/email/$f ]; then | ||
463 | install -m 0644 -o postfixscripts -g keys /dev/null -T /var/lib/naemon/checks/email/$f | ||
464 | touch -m -d @0 /var/lib/naemon/checks/email/$f | ||
465 | fi | ||
466 | done | ||
467 | ''; | ||
468 | }; | ||
469 | systemd.services.postfix.serviceConfig.Slice = "mail.slice"; | ||
470 | }; | ||
471 | } | ||
diff --git a/modules/private/mail/relay.nix b/modules/private/mail/relay.nix deleted file mode 100644 index 668d365..0000000 --- a/modules/private/mail/relay.nix +++ /dev/null | |||
@@ -1,235 +0,0 @@ | |||
1 | { lib, pkgs, config, nodes, name, ... }: | ||
2 | { | ||
3 | config = lib.mkIf config.myServices.mailBackup.enable { | ||
4 | security.acme.certs."mail" = config.myServices.certificates.certConfig // { | ||
5 | postRun = '' | ||
6 | systemctl restart postfix.service | ||
7 | ''; | ||
8 | domain = config.hostEnv.fqdn; | ||
9 | extraDomains = let | ||
10 | zonesWithMx = builtins.filter (zone: | ||
11 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 | ||
12 | ) config.myEnv.dns.masterZones; | ||
13 | mxs = map (zone: "${config.myEnv.servers."${name}".mx.subdomain}.${zone.name}") zonesWithMx; | ||
14 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | ||
15 | }; | ||
16 | secrets.keys = { | ||
17 | "postfix/mysql_alias_maps" = { | ||
18 | user = config.services.postfix.user; | ||
19 | group = config.services.postfix.group; | ||
20 | permissions = "0440"; | ||
21 | text = '' | ||
22 | # We need to specify that option to trigger ssl connection | ||
23 | tls_ciphers = TLSv1.2 | ||
24 | user = ${config.myEnv.mail.postfix.mysql.user} | ||
25 | password = ${config.myEnv.mail.postfix.mysql.password} | ||
26 | hosts = ${config.myEnv.mail.postfix.mysql.remoteHost} | ||
27 | dbname = ${config.myEnv.mail.postfix.mysql.database} | ||
28 | query = SELECT DISTINCT 1 | ||
29 | FROM forwardings | ||
30 | WHERE | ||
31 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
32 | AND active = 1 | ||
33 | AND '%s' NOT IN | ||
34 | ( | ||
35 | SELECT source | ||
36 | FROM forwardings_blacklisted | ||
37 | WHERE source = '%s' | ||
38 | ) UNION | ||
39 | SELECT 'devnull@immae.eu' | ||
40 | FROM forwardings_blacklisted | ||
41 | WHERE source = '%s' | ||
42 | ''; | ||
43 | }; | ||
44 | "postfix/ldap_mailboxes" = { | ||
45 | user = config.services.postfix.user; | ||
46 | group = config.services.postfix.group; | ||
47 | permissions = "0440"; | ||
48 | text = '' | ||
49 | server_host = ldaps://${config.myEnv.mail.dovecot.ldap.host}:636 | ||
50 | search_base = ${config.myEnv.mail.dovecot.ldap.base} | ||
51 | query_filter = ${config.myEnv.mail.dovecot.ldap.postfix_mailbox_filter} | ||
52 | bind_dn = ${config.myEnv.mail.dovecot.ldap.dn} | ||
53 | bind_pw = ${config.myEnv.mail.dovecot.ldap.password} | ||
54 | result_attribute = immaePostfixAddress | ||
55 | result_format = dummy | ||
56 | version = 3 | ||
57 | ''; | ||
58 | }; | ||
59 | "postfix/sympa_mailbox_maps" = { | ||
60 | user = config.services.postfix.user; | ||
61 | group = config.services.postfix.group; | ||
62 | permissions = "0440"; | ||
63 | text = '' | ||
64 | hosts = ${config.myEnv.mail.sympa.postgresql.host} | ||
65 | user = ${config.myEnv.mail.sympa.postgresql.user} | ||
66 | password = ${config.myEnv.mail.sympa.postgresql.password} | ||
67 | dbname = ${config.myEnv.mail.sympa.postgresql.database} | ||
68 | query = SELECT DISTINCT 1 FROM list_table WHERE '%s' IN ( | ||
69 | CONCAT(name_list, '@', robot_list), | ||
70 | CONCAT(name_list, '-request@', robot_list), | ||
71 | CONCAT(name_list, '-editor@', robot_list), | ||
72 | CONCAT(name_list, '-unsubscribe@', robot_list), | ||
73 | CONCAT(name_list, '-owner@', robot_list), | ||
74 | CONCAT('sympa-request@', robot_list), | ||
75 | CONCAT('sympa-owner@', robot_list), | ||
76 | CONCAT('sympa@', robot_list), | ||
77 | CONCAT('listmaster@', robot_list), | ||
78 | CONCAT('bounce@', robot_list), | ||
79 | CONCAT('abuse-feedback-report@', robot_list) | ||
80 | ) | ||
81 | ''; | ||
82 | }; | ||
83 | "postfix/ldap_ejabberd_users_immae_fr" = { | ||
84 | user = config.services.postfix.user; | ||
85 | group = config.services.postfix.group; | ||
86 | permissions = "0440"; | ||
87 | text = '' | ||
88 | server_host = ldaps://${config.myEnv.jabber.ldap.host}:636 | ||
89 | search_base = ${config.myEnv.jabber.ldap.base} | ||
90 | query_filter = ${config.myEnv.jabber.postfix_user_filter} | ||
91 | domain = immae.fr | ||
92 | bind_dn = ${config.myEnv.jabber.ldap.dn} | ||
93 | bind_pw = ${config.myEnv.jabber.ldap.password} | ||
94 | result_attribute = immaeXmppUid | ||
95 | result_format = ejabberd@localhost | ||
96 | version = 3 | ||
97 | ''; | ||
98 | }; | ||
99 | }; | ||
100 | |||
101 | networking.firewall.allowedTCPPorts = [ 25 ]; | ||
102 | |||
103 | users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; | ||
104 | services.filesWatcher.postfix = { | ||
105 | restart = true; | ||
106 | paths = [ | ||
107 | config.secrets.fullPaths."postfix/mysql_alias_maps" | ||
108 | config.secrets.fullPaths."postfix/sympa_mailbox_maps" | ||
109 | config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr" | ||
110 | config.secrets.fullPaths."postfix/ldap_mailboxes" | ||
111 | ]; | ||
112 | }; | ||
113 | services.postfix = { | ||
114 | mapFiles = let | ||
115 | recipient_maps = let | ||
116 | name = n: i: "relay_${n}_${toString i}"; | ||
117 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( | ||
118 | if m.type == "hash" | ||
119 | then pkgs.writeText (name n i) m.content | ||
120 | else null | ||
121 | ); | ||
122 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; | ||
123 | in lib.attrsets.filterAttrs (k: v: v != null) ( | ||
124 | lib.attrsets.listToAttrs (lib.flatten ( | ||
125 | lib.attrsets.mapAttrsToList pairs config.myEnv.mail.postfix.backup_domains | ||
126 | )) | ||
127 | ); | ||
128 | relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( | ||
129 | lib.attrsets.mapAttrs' (n: v: | ||
130 | lib.attrsets.nameValuePair "recipient_access_${n}" ( | ||
131 | if lib.attrsets.hasAttr "relay_restrictions" v | ||
132 | then pkgs.writeText "recipient_access_${n}" v.relay_restrictions | ||
133 | else null | ||
134 | ) | ||
135 | ) config.myEnv.mail.postfix.backup_domains | ||
136 | ); | ||
137 | virtual_map = { | ||
138 | virtual = let | ||
139 | cfg = config.myEnv.monitoring.email_check.eldiron; | ||
140 | address = "${cfg.mail_address}@${cfg.mail_domain}"; | ||
141 | in pkgs.writeText "postfix-virtual" ( | ||
142 | builtins.concatStringsSep "\n" ( | ||
143 | ["${address} 1"] ++ | ||
144 | lib.attrsets.mapAttrsToList ( | ||
145 | n: v: lib.optionalString v.external '' | ||
146 | script_${n}@mail.immae.eu 1 | ||
147 | '' | ||
148 | ) config.myEnv.mail.scripts | ||
149 | ) | ||
150 | ); | ||
151 | }; | ||
152 | in | ||
153 | recipient_maps // relay_restrictions // virtual_map; | ||
154 | config = { | ||
155 | ### postfix module overrides | ||
156 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | ||
157 | smtp_tls_CAfile = lib.mkForce ""; | ||
158 | smtp_tls_cert_file = lib.mkForce ""; | ||
159 | smtp_tls_key_file = lib.mkForce ""; | ||
160 | |||
161 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" | ||
162 | mailbox_size_limit = "1073741825"; # Workaround, local delivered mails should all go through scripts | ||
163 | alias_database = "\$alias_maps"; | ||
164 | |||
165 | ### Relay domains | ||
166 | relay_domains = let | ||
167 | backups = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) config.myEnv.mail.postfix.backup_domains); | ||
168 | virtual_domains = config.myEnv.mail.postfix.additional_mailbox_domains | ||
169 | ++ lib.remove null (lib.flatten (map | ||
170 | (zone: map | ||
171 | (e: if e.receive | ||
172 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" | ||
173 | else null | ||
174 | ) | ||
175 | (zone.withEmail or []) | ||
176 | ) | ||
177 | config.myEnv.dns.masterZones | ||
178 | )); | ||
179 | in | ||
180 | backups ++ virtual_domains; | ||
181 | relay_recipient_maps = let | ||
182 | backup_recipients = lib.flatten (lib.attrsets.mapAttrsToList (n: v: | ||
183 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps | ||
184 | ) config.myEnv.mail.postfix.backup_domains); | ||
185 | virtual_alias_maps = [ | ||
186 | "hash:/etc/postfix/virtual" | ||
187 | "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}" | ||
188 | "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}" | ||
189 | ]; | ||
190 | virtual_mailbox_maps = [ | ||
191 | "ldap:${config.secrets.fullPaths."postfix/ldap_mailboxes"}" | ||
192 | "pgsql:${config.secrets.fullPaths."postfix/sympa_mailbox_maps"}" | ||
193 | ]; | ||
194 | in | ||
195 | backup_recipients ++ virtual_alias_maps ++ virtual_mailbox_maps; | ||
196 | smtpd_relay_restrictions = [ | ||
197 | "defer_unauth_destination" | ||
198 | ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: | ||
199 | if lib.attrsets.hasAttr "relay_restrictions" v | ||
200 | then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] | ||
201 | else [] | ||
202 | ) config.myEnv.mail.postfix.backup_domains); | ||
203 | |||
204 | ### Additional smtpd configuration | ||
205 | smtpd_tls_received_header = "yes"; | ||
206 | smtpd_tls_loglevel = "1"; | ||
207 | |||
208 | ### Email sending configuration | ||
209 | smtp_tls_security_level = "may"; | ||
210 | smtp_tls_loglevel = "1"; | ||
211 | |||
212 | ### Force ip bind for smtp | ||
213 | smtp_bind_address = config.myEnv.servers."${name}".ips.main.ip4; | ||
214 | smtp_bind_address6 = builtins.head config.myEnv.servers."${name}".ips.main.ip6; | ||
215 | |||
216 | smtpd_milters = [ | ||
217 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | ||
218 | "unix:${config.myServices.mail.milters.sockets.openarc}" | ||
219 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | ||
220 | ]; | ||
221 | }; | ||
222 | enable = true; | ||
223 | enableSmtp = true; | ||
224 | enableSubmission = false; | ||
225 | destination = ["localhost"]; | ||
226 | # This needs to reverse DNS | ||
227 | hostname = config.hostEnv.fqdn; | ||
228 | setSendmail = false; | ||
229 | sslCert = "/var/lib/acme/mail/fullchain.pem"; | ||
230 | sslKey = "/var/lib/acme/mail/key.pem"; | ||
231 | recipientDelimiter = "+"; | ||
232 | }; | ||
233 | }; | ||
234 | } | ||
235 | |||
diff --git a/modules/private/mail/rspamd.nix b/modules/private/mail/rspamd.nix deleted file mode 100644 index 05f1300..0000000 --- a/modules/private/mail/rspamd.nix +++ /dev/null | |||
@@ -1,87 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | { | ||
3 | options.myServices.mail.rspamd.sockets = lib.mkOption { | ||
4 | type = lib.types.attrsOf lib.types.path; | ||
5 | default = { | ||
6 | worker-controller = "/run/rspamd/worker-controller.sock"; | ||
7 | }; | ||
8 | readOnly = true; | ||
9 | description = '' | ||
10 | rspamd sockets | ||
11 | ''; | ||
12 | }; | ||
13 | config = lib.mkIf config.myServices.mail.enable { | ||
14 | services.cron.systemCronJobs = let | ||
15 | cron_script = pkgs.runCommand "cron_script" { | ||
16 | buildInputs = [ pkgs.makeWrapper ]; | ||
17 | } '' | ||
18 | mkdir -p $out | ||
19 | cp ${./scan_reported_mails} $out/scan_reported_mails | ||
20 | patchShebangs $out | ||
21 | for i in $out/*; do | ||
22 | wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils pkgs.rspamd pkgs.flock ]} | ||
23 | done | ||
24 | ''; | ||
25 | in | ||
26 | [ "*/20 * * * * vhost ${cron_script}/scan_reported_mails" ]; | ||
27 | |||
28 | systemd.services.rspamd.serviceConfig.Slice = "mail.slice"; | ||
29 | services.rspamd = { | ||
30 | enable = true; | ||
31 | debug = false; | ||
32 | overrides = { | ||
33 | "actions.conf".text = '' | ||
34 | reject = null; | ||
35 | add_header = 6; | ||
36 | greylist = null; | ||
37 | ''; | ||
38 | "milter_headers.conf".text = '' | ||
39 | extended_spam_headers = true; | ||
40 | ''; | ||
41 | }; | ||
42 | locals = { | ||
43 | "redis.conf".text = '' | ||
44 | servers = "${config.myEnv.mail.rspamd.redis.socket}"; | ||
45 | db = "${config.myEnv.mail.rspamd.redis.db}"; | ||
46 | ''; | ||
47 | "classifier-bayes.conf".text = '' | ||
48 | users_enabled = true; | ||
49 | backend = "redis"; | ||
50 | servers = "${config.myEnv.mail.rspamd.redis.socket}"; | ||
51 | database = "${config.myEnv.mail.rspamd.redis.db}"; | ||
52 | autolearn = true; | ||
53 | cache { | ||
54 | backend = "redis"; | ||
55 | } | ||
56 | new_schema = true; | ||
57 | statfile { | ||
58 | BAYES_HAM { | ||
59 | spam = false; | ||
60 | } | ||
61 | BAYES_SPAM { | ||
62 | spam = true; | ||
63 | } | ||
64 | } | ||
65 | ''; | ||
66 | }; | ||
67 | workers = { | ||
68 | controller = { | ||
69 | extraConfig = '' | ||
70 | enable_password = "${config.myEnv.mail.rspamd.write_password_hashed}"; | ||
71 | password = "${config.myEnv.mail.rspamd.read_password_hashed}"; | ||
72 | ''; | ||
73 | bindSockets = [ { | ||
74 | socket = config.myServices.mail.rspamd.sockets.worker-controller; | ||
75 | mode = "0660"; | ||
76 | owner = config.services.rspamd.user; | ||
77 | group = "vhost"; | ||
78 | } ]; | ||
79 | }; | ||
80 | }; | ||
81 | postfix = { | ||
82 | enable = true; | ||
83 | config = {}; | ||
84 | }; | ||
85 | }; | ||
86 | }; | ||
87 | } | ||
diff --git a/modules/private/mail/scan_reported_mails b/modules/private/mail/scan_reported_mails deleted file mode 100755 index fe9f4d6..0000000 --- a/modules/private/mail/scan_reported_mails +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | |||
3 | ( flock -n 9 || exit 1 | ||
4 | shopt -s nullglob | ||
5 | for spool in /var/lib/vhost/.rspamd/*/pending; do | ||
6 | rspamd_folder=$(dirname $spool) | ||
7 | mail_user=$(basename $rspamd_folder) | ||
8 | mv $rspamd_folder/pending $rspamd_folder/processing | ||
9 | |||
10 | for mtype in ham spam; do | ||
11 | if [ -d $rspamd_folder/processing/$mtype ]; then | ||
12 | output="$(rspamc -h /run/rspamd/worker-controller.sock -c bayes -d $mail_user learn_$mtype $rspamd_folder/processing/$mtype/*)" | ||
13 | echo "[$mtype: $mail_user]" ${output} >> /var/lib/vhost/.rspamd/rspamd.log | ||
14 | mkdir -p $rspamd_folder/processed/$mtype | ||
15 | cp $rspamd_folder/processing/$mtype/* $rspamd_folder/processed/$mtype/ | ||
16 | fi | ||
17 | done | ||
18 | |||
19 | rm -rf $rspamd_folder/processing | ||
20 | done | ||
21 | ) 9>/var/lib/vhost/scan_reported_mails.lock | ||
diff --git a/modules/private/mail/sieve_bin/imapsieve_copy b/modules/private/mail/sieve_bin/imapsieve_copy deleted file mode 100755 index 2ca1f23..0000000 --- a/modules/private/mail/sieve_bin/imapsieve_copy +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | #!/usr/bin/env bash | ||
2 | # Inspired from https://docs.iredmail.org/dovecot.imapsieve.html | ||
3 | |||
4 | MSG_TYPE="$1" | ||
5 | OUTPUT_DIR="/var/lib/vhost/.rspamd/${USER}/pending/${MSG_TYPE}" | ||
6 | FILE="${OUTPUT_DIR}/$(date +%Y%m%d%H%M%S)-${RANDOM}${RANDOM}.eml" | ||
7 | mkdir -p "${OUTPUT_DIR}" | ||
8 | cat > ${FILE} < /dev/stdin | ||
diff --git a/modules/private/mail/sieve_scripts/backup.sieve b/modules/private/mail/sieve_scripts/backup.sieve deleted file mode 100644 index 3014c0a..0000000 --- a/modules/private/mail/sieve_scripts/backup.sieve +++ /dev/null | |||
@@ -1,7 +0,0 @@ | |||
1 | # vim: filetype=sieve | ||
2 | require ["copy","mailbox","fileinto","regex"]; | ||
3 | if header :is "X-Spam" "Yes" { | ||
4 | fileinto :create :copy "Backup/Spam"; | ||
5 | } else { | ||
6 | fileinto :create :copy "Backup/Ham"; | ||
7 | } | ||
diff --git a/modules/private/mail/sieve_scripts/report_ham.sieve b/modules/private/mail/sieve_scripts/report_ham.sieve deleted file mode 100644 index f9b8481..0000000 --- a/modules/private/mail/sieve_scripts/report_ham.sieve +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; | ||
2 | |||
3 | if environment :matches "imap.mailbox" "*" { | ||
4 | set "mailbox" "${1}"; | ||
5 | } | ||
6 | |||
7 | if string "${mailbox}" "Trash" { | ||
8 | stop; | ||
9 | } | ||
10 | |||
11 | pipe :copy "imapsieve_copy" [ "ham" ]; | ||
diff --git a/modules/private/mail/sieve_scripts/report_spam.sieve b/modules/private/mail/sieve_scripts/report_spam.sieve deleted file mode 100644 index 9a1f794..0000000 --- a/modules/private/mail/sieve_scripts/report_spam.sieve +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | require ["vnd.dovecot.pipe", "copy", "imapsieve" ]; | ||
2 | |||
3 | pipe :copy "imapsieve_copy" [ "spam" ]; | ||
diff --git a/modules/private/mail/sympa.nix b/modules/private/mail/sympa.nix deleted file mode 100644 index 0626ac0..0000000 --- a/modules/private/mail/sympa.nix +++ /dev/null | |||
@@ -1,213 +0,0 @@ | |||
1 | { lib, pkgs, config, ... }: | ||
2 | let | ||
3 | domain = "lists.immae.eu"; | ||
4 | sympaConfig = config.myEnv.mail.sympa; | ||
5 | in | ||
6 | { | ||
7 | config = lib.mkIf config.myServices.mail.enable { | ||
8 | myServices.databases.postgresql.authorizedHosts = { | ||
9 | backup-2 = [ | ||
10 | { | ||
11 | username = "sympa"; | ||
12 | database = "sympa"; | ||
13 | ip4 = [config.myEnv.servers.backup-2.ips.main.ip4]; | ||
14 | ip6 = config.myEnv.servers.backup-2.ips.main.ip6; | ||
15 | } | ||
16 | ]; | ||
17 | }; | ||
18 | services.websites.env.tools.vhostConfs.mail = { | ||
19 | extraConfig = lib.mkAfter [ | ||
20 | '' | ||
21 | Alias /static-sympa/ /var/lib/sympa/static_content/ | ||
22 | <Directory /var/lib/sympa/static_content/> | ||
23 | Require all granted | ||
24 | AllowOverride none | ||
25 | </Directory> | ||
26 | <Location /sympa> | ||
27 | SetHandler "proxy:unix:/run/sympa/wwsympa.socket|fcgi://" | ||
28 | Require all granted | ||
29 | </Location> | ||
30 | '' | ||
31 | ]; | ||
32 | }; | ||
33 | |||
34 | secrets.keys = { | ||
35 | "sympa/db_password" = { | ||
36 | permissions = "0400"; | ||
37 | group = "sympa"; | ||
38 | user = "sympa"; | ||
39 | text = sympaConfig.postgresql.password; | ||
40 | }; | ||
41 | } | ||
42 | // lib.mapAttrs' (n: v: lib.nameValuePair "sympa/data_sources/${n}.incl" { | ||
43 | permissions = "0400"; group = "sympa"; user = "sympa"; text = v; | ||
44 | }) sympaConfig.data_sources | ||
45 | // lib.mapAttrs' (n: v: lib.nameValuePair "sympa/scenari/${n}" { | ||
46 | permissions = "0400"; group = "sympa"; user = "sympa"; text = v; | ||
47 | }) sympaConfig.scenari; | ||
48 | users.users.sympa.extraGroups = [ "keys" ]; | ||
49 | systemd.slices.mail-sympa = { | ||
50 | description = "Sympa slice"; | ||
51 | }; | ||
52 | |||
53 | systemd.services.sympa.serviceConfig.SupplementaryGroups = [ "keys" ]; | ||
54 | systemd.services.sympa-archive.serviceConfig.SupplementaryGroups = [ "keys" ]; | ||
55 | systemd.services.sympa-bounce.serviceConfig.SupplementaryGroups = [ "keys" ]; | ||
56 | systemd.services.sympa-bulk.serviceConfig.SupplementaryGroups = [ "keys" ]; | ||
57 | systemd.services.sympa-task.serviceConfig.SupplementaryGroups = [ "keys" ]; | ||
58 | |||
59 | systemd.services.sympa.serviceConfig.Slice = "mail-sympa.slice"; | ||
60 | systemd.services.sympa-archive.serviceConfig.Slice = "mail-sympa.slice"; | ||
61 | systemd.services.sympa-bounce.serviceConfig.Slice = "mail-sympa.slice"; | ||
62 | systemd.services.sympa-bulk.serviceConfig.Slice = "mail-sympa.slice"; | ||
63 | systemd.services.sympa-task.serviceConfig.Slice = "mail-sympa.slice"; | ||
64 | |||
65 | # https://github.com/NixOS/nixpkgs/pull/84202 | ||
66 | systemd.services.sympa.serviceConfig.ProtectKernelModules = lib.mkForce false; | ||
67 | systemd.services.sympa-archive.serviceConfig.ProtectKernelModules = lib.mkForce false; | ||
68 | systemd.services.sympa-bounce.serviceConfig.ProtectKernelModules = lib.mkForce false; | ||
69 | systemd.services.sympa-bulk.serviceConfig.ProtectKernelModules = lib.mkForce false; | ||
70 | systemd.services.sympa-task.serviceConfig.ProtectKernelModules = lib.mkForce false; | ||
71 | systemd.services.sympa.serviceConfig.ProtectKernelTunables = lib.mkForce false; | ||
72 | systemd.services.sympa-archive.serviceConfig.ProtectKernelTunables = lib.mkForce false; | ||
73 | systemd.services.sympa-bounce.serviceConfig.ProtectKernelTunables = lib.mkForce false; | ||
74 | systemd.services.sympa-bulk.serviceConfig.ProtectKernelTunables = lib.mkForce false; | ||
75 | systemd.services.sympa-task.serviceConfig.ProtectKernelTunables = lib.mkForce false; | ||
76 | |||
77 | systemd.services.wwsympa = { | ||
78 | wantedBy = [ "multi-user.target" ]; | ||
79 | after = [ "sympa.service" ]; | ||
80 | serviceConfig = { | ||
81 | Slice = "mail-sympa.slice"; | ||
82 | Type = "forking"; | ||
83 | PIDFile = "/run/sympa/wwsympa.pid"; | ||
84 | Restart = "always"; | ||
85 | ExecStart = ''${pkgs.spawn_fcgi}/bin/spawn-fcgi \ | ||
86 | -u sympa \ | ||
87 | -g sympa \ | ||
88 | -U wwwrun \ | ||
89 | -M 0600 \ | ||
90 | -F 2 \ | ||
91 | -P /run/sympa/wwsympa.pid \ | ||
92 | -s /run/sympa/wwsympa.socket \ | ||
93 | -- ${pkgs.sympa}/lib/sympa/cgi/wwsympa.fcgi | ||
94 | ''; | ||
95 | StateDirectory = "sympa"; | ||
96 | ProtectHome = true; | ||
97 | ProtectSystem = "full"; | ||
98 | ProtectControlGroups = true; | ||
99 | }; | ||
100 | }; | ||
101 | |||
102 | services.postfix = { | ||
103 | mapFiles = { | ||
104 | # Update relay list when changing one of those | ||
105 | sympa_virtual = pkgs.writeText "virtual.sympa" '' | ||
106 | sympa-request@${domain} postmaster@immae.eu | ||
107 | sympa-owner@${domain} postmaster@immae.eu | ||
108 | |||
109 | sympa-request@cip-ca.fr postmaster@immae.eu | ||
110 | sympa-owner@cip-ca.fr postmaster@immae.eu | ||
111 | ''; | ||
112 | sympa_transport = pkgs.writeText "transport.sympa" '' | ||
113 | ${domain} error:User unknown in recipient table | ||
114 | sympa@${domain} sympa:sympa@${domain} | ||
115 | listmaster@${domain} sympa:listmaster@${domain} | ||
116 | bounce@${domain} sympabounce:sympa@${domain} | ||
117 | abuse-feedback-report@${domain} sympabounce:sympa@${domain} | ||
118 | |||
119 | sympa@cip-ca.fr sympa:sympa@cip-ca.fr | ||
120 | listmaster@cip-ca.fr sympa:listmaster@cip-ca.fr | ||
121 | bounce@cip-ca.fr sympabounce:sympa@cip-ca.fr | ||
122 | abuse-feedback-report@cip-ca.fr sympabounce:sympa@cip-ca.fr | ||
123 | ''; | ||
124 | }; | ||
125 | config = { | ||
126 | transport_maps = lib.mkAfter [ | ||
127 | "hash:/etc/postfix/sympa_transport" | ||
128 | "hash:/var/lib/sympa/sympa_transport" | ||
129 | ]; | ||
130 | virtual_alias_maps = lib.mkAfter [ | ||
131 | "hash:/etc/postfix/sympa_virtual" | ||
132 | ]; | ||
133 | virtual_mailbox_maps = lib.mkAfter [ | ||
134 | "hash:/etc/postfix/sympa_transport" | ||
135 | "hash:/var/lib/sympa/sympa_transport" | ||
136 | "hash:/etc/postfix/sympa_virtual" | ||
137 | ]; | ||
138 | }; | ||
139 | masterConfig = { | ||
140 | sympa = { | ||
141 | type = "unix"; | ||
142 | privileged = true; | ||
143 | chroot = false; | ||
144 | command = "pipe"; | ||
145 | args = [ | ||
146 | "flags=hqRu" | ||
147 | "user=sympa" | ||
148 | "argv=${pkgs.sympa}/libexec/queue" | ||
149 | "\${nexthop}" | ||
150 | ]; | ||
151 | }; | ||
152 | sympabounce = { | ||
153 | type = "unix"; | ||
154 | privileged = true; | ||
155 | chroot = false; | ||
156 | command = "pipe"; | ||
157 | args = [ | ||
158 | "flags=hqRu" | ||
159 | "user=sympa" | ||
160 | "argv=${pkgs.sympa}/libexec/bouncequeue" | ||
161 | "\${nexthop}" | ||
162 | ]; | ||
163 | }; | ||
164 | }; | ||
165 | }; | ||
166 | services.sympa = { | ||
167 | enable = true; | ||
168 | listMasters = sympaConfig.listmasters; | ||
169 | mainDomain = domain; | ||
170 | domains = { | ||
171 | "${domain}" = { | ||
172 | webHost = "mail.immae.eu"; | ||
173 | webLocation = "/sympa"; | ||
174 | }; | ||
175 | "cip-ca.fr" = { | ||
176 | webHost = "mail.cip-ca.fr"; | ||
177 | webLocation = "/sympa"; | ||
178 | }; | ||
179 | }; | ||
180 | |||
181 | database = { | ||
182 | type = "PostgreSQL"; | ||
183 | user = sympaConfig.postgresql.user; | ||
184 | host = sympaConfig.postgresql.socket; | ||
185 | name = sympaConfig.postgresql.database; | ||
186 | passwordFile = config.secrets.fullPaths."sympa/db_password"; | ||
187 | createLocally = false; | ||
188 | }; | ||
189 | settings = { | ||
190 | sendmail = "/run/wrappers/bin/sendmail"; | ||
191 | log_smtp = "on"; | ||
192 | sendmail_aliases = "/var/lib/sympa/sympa_transport"; | ||
193 | aliases_program = "${pkgs.postfix}/bin/postmap"; | ||
194 | }; | ||
195 | settingsFile = { | ||
196 | "virtual.sympa".enable = false; | ||
197 | "transport.sympa".enable = false; | ||
198 | } // lib.mapAttrs' (n: v: lib.nameValuePair | ||
199 | "etc/${domain}/data_sources/${n}.incl" | ||
200 | { source = config.secrets.fullPaths."sympa/data_sources/${n}.incl"; }) sympaConfig.data_sources | ||
201 | // lib.mapAttrs' (n: v: lib.nameValuePair | ||
202 | "etc/${domain}/scenari/${n}" | ||
203 | { source = config.secrets.fullPaths."sympa/scenari/${n}"; }) sympaConfig.scenari; | ||
204 | web = { | ||
205 | server = "none"; | ||
206 | }; | ||
207 | |||
208 | mta = { | ||
209 | type = "none"; | ||
210 | }; | ||
211 | }; | ||
212 | }; | ||
213 | } | ||
diff --git a/modules/private/mail/verify_from.py b/modules/private/mail/verify_from.py deleted file mode 100755 index b75001e..0000000 --- a/modules/private/mail/verify_from.py +++ /dev/null | |||
@@ -1,60 +0,0 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | import Milter | ||
3 | import argparse | ||
4 | from email.header import decode_header | ||
5 | from email.utils import parseaddr | ||
6 | |||
7 | class CheckMilter(Milter.Base): | ||
8 | def __init__(self): | ||
9 | self.envelope_from = None | ||
10 | self.header_from = None | ||
11 | |||
12 | @Milter.noreply | ||
13 | def connect(self, IPname, family, hostaddr): | ||
14 | return Milter.CONTINUE | ||
15 | |||
16 | def hello(self, heloname): | ||
17 | return Milter.CONTINUE | ||
18 | |||
19 | def envfrom(self, mailfrom, *args): | ||
20 | self.envelope_from = parseaddr(mailfrom)[1] | ||
21 | return Milter.CONTINUE | ||
22 | |||
23 | @Milter.noreply | ||
24 | def envrcpt(self, to, *str): | ||
25 | return Milter.CONTINUE | ||
26 | |||
27 | @Milter.noreply | ||
28 | def header(self, name, hval): | ||
29 | if name.lower() == "from": | ||
30 | self.header_from = parseaddr(decode_header(hval)[-1][0])[1] | ||
31 | return Milter.CONTINUE | ||
32 | |||
33 | def eoh(self): | ||
34 | if self.header_from is not None and self.header_from != "" and self.header_from != self.envelope_from: | ||
35 | self.setreply("553", xcode="5.7.1", msg="<%s>: From header rejected: not matching envelope From %s" | ||
36 | % (self.header_from, self.envelope_from)) | ||
37 | return Milter.REJECT | ||
38 | |||
39 | return Milter.CONTINUE | ||
40 | |||
41 | @Milter.noreply | ||
42 | def body(self, chunk): | ||
43 | return Milter.CONTINUE | ||
44 | |||
45 | def eom(self): | ||
46 | return Milter.ACCEPT | ||
47 | |||
48 | def close(self): | ||
49 | return Milter.CONTINUE | ||
50 | |||
51 | def abort(self): | ||
52 | return Milter.CONTINUE | ||
53 | |||
54 | if __name__ == "__main__": | ||
55 | parser = argparse.ArgumentParser() | ||
56 | parser.add_argument("--socket", "-s", type=str, help="socket to listen to") | ||
57 | config = parser.parse_args() | ||
58 | |||
59 | Milter.factory = CheckMilter | ||
60 | Milter.runmilter("check_from", config.socket, timeout=300) | ||