aboutsummaryrefslogtreecommitdiff
path: root/systems/eldiron/mail
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2023-10-04 01:35:06 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2023-10-04 02:11:48 +0200
commit1a64deeb894dc95e2645a75771732c6cc53a79ad (patch)
tree1b9df4838f894577a09b9b260151756272efeb53 /systems/eldiron/mail
parentfa25ffd4583cc362075cd5e1b4130f33306103f0 (diff)
downloadNix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.gz
Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.tar.zst
Nix-1a64deeb894dc95e2645a75771732c6cc53a79ad.zip
Squash changes containing private information
There were a lot of changes since the previous commit, but a lot of them contained personnal information about users. All thos changes got stashed into a single commit (history is kept in a different place) and private information was moved in a separate private repository
Diffstat (limited to 'systems/eldiron/mail')
-rw-r--r--systems/eldiron/mail/default.nix44
-rw-r--r--systems/eldiron/mail/dovecot.nix348
-rw-r--r--systems/eldiron/mail/postfix.nix497
-rw-r--r--systems/eldiron/mail/rspamd.nix88
-rwxr-xr-xsystems/eldiron/mail/scan_reported_mails21
-rwxr-xr-xsystems/eldiron/mail/sieve_bin/imapsieve_copy8
-rw-r--r--systems/eldiron/mail/sieve_scripts/backup.sieve7
-rw-r--r--systems/eldiron/mail/sieve_scripts/report_ham.sieve11
-rw-r--r--systems/eldiron/mail/sieve_scripts/report_spam.sieve3
-rw-r--r--systems/eldiron/mail/sympa.nix232
10 files changed, 1259 insertions, 0 deletions
diff --git a/systems/eldiron/mail/default.nix b/systems/eldiron/mail/default.nix
new file mode 100644
index 0000000..4e13f6a
--- /dev/null
+++ b/systems/eldiron/mail/default.nix
@@ -0,0 +1,44 @@
1{ lib, pkgs, config, ... }:
2{
3 imports = [
4 ./postfix.nix
5 ./dovecot.nix
6 ./rspamd.nix
7 ./sympa.nix
8 ];
9
10 options.myServices.mail.enable = lib.mkEnableOption "enable Mail services";
11 config = lib.mkIf config.myServices.mail.enable {
12 myServices.mail.milters.enable = true;
13 security.acme.certs."mail" = {
14 postRun = lib.mkBefore ''
15 cp -f fullchain.pem /etc/dovecot/fullchain.pem
16 chown :dovecot2 /etc/dovecot/fullchain.pem
17 chmod a+r /etc/dovecot/fullchain.pem
18 '';
19 domain = config.hostEnv.fqdn;
20 extraDomainNames = let
21 zonesWithMx = builtins.attrNames (lib.filterAttrs (n: v: v.hasEmail) config.myServices.dns.zones);
22 mxs = map (n: "${config.hostEnv.mx.subdomain}.${n}") zonesWithMx;
23 in mxs;
24 };
25 # This is for clients that don’t support elliptic curves (e.g.
26 # printer)
27 security.acme.certs."mail-rsa" = {
28 postRun = lib.mkBefore ''
29 cp -f fullchain.pem /etc/dovecot/fullchain-rsa.pem
30 chown :dovecot2 /etc/dovecot/fullchain-rsa.pem
31 chmod a+r /etc/dovecot/fullchain-rsa.pem
32 '';
33 domain = config.hostEnv.fqdn;
34 keyType = "rsa4096";
35 extraDomainNames = let
36 zonesWithMx = builtins.attrNames (lib.filterAttrs (n: v: v.hasEmail) config.myServices.dns.zones);
37 mxs = map (n: "${config.hostEnv.mx.subdomain}.${n}") zonesWithMx;
38 in mxs;
39 };
40 systemd.slices.mail = {
41 description = "Mail slice";
42 };
43 };
44}
diff --git a/systems/eldiron/mail/dovecot.nix b/systems/eldiron/mail/dovecot.nix
new file mode 100644
index 0000000..a1282e3
--- /dev/null
+++ b/systems/eldiron/mail/dovecot.nix
@@ -0,0 +1,348 @@
1{ lib, pkgs, config, ... }:
2let
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 '';
13in
14{
15 config = lib.mkIf config.myServices.mail.enable {
16 myServices.dns.zones."immae.eu".subdomains =
17 with config.myServices.dns.helpers;
18 {
19 imap = ips servers.eldiron.ips.main;
20 pop3 = ips servers.eldiron.ips.main;
21 };
22
23 myServices.chatonsProperties.services.email = {
24 file.datetime = "2022-08-22T01:00:00";
25 service = {
26 name = "E-mail account";
27 description = "Compte e-mail avec configuration imap et smtp/pop3";
28 logo = "https://www.dovecot.org/wp-content/uploads/2021/09/favicon.ico";
29 website = "https://mail.immae.eu/";
30 status.level = "OK";
31 status.description = "OK";
32 registration."" = ["MEMBER" "CLIENT"];
33 registration.load = "OPEN";
34 install.type = "PACKAGE";
35 };
36 software = {
37 name = "Dovecot";
38 website = "https://www.dovecot.org/";
39 license.url = "https://github.com/dovecot/core/blob/main/COPYING";
40 license.name = "MIT and LGPLv2.1 Licenses";
41 version = pkgs.dovecot.version;
42 source.url = "https://github.com/dovecot/core";
43 modules = ["roundcube" "rainloop"] ++ map (a: a.pname) config.services.dovecot2.modules;
44 };
45 };
46 systemd.services.dovecot2.serviceConfig.Slice = "mail.slice";
47 secrets.keys."dovecot/ldap" = {
48 user = config.services.dovecot2.user;
49 group = config.services.dovecot2.group;
50 permissions = "0400";
51 text = ''
52 hosts = ${config.myEnv.mail.dovecot.ldap.host}
53 tls = yes
54
55 dn = ${config.myEnv.mail.dovecot.ldap.dn}
56 dnpass = ${config.myEnv.mail.dovecot.ldap.password}
57
58 auth_bind = yes
59
60 ldap_version = 3
61
62 base = ${config.myEnv.mail.dovecot.ldap.base}
63 scope = subtree
64
65 pass_filter = ${config.myEnv.mail.dovecot.ldap.filter}
66 pass_attrs = ${config.myEnv.mail.dovecot.ldap.pass_attrs}
67
68 user_attrs = ${config.myEnv.mail.dovecot.ldap.user_attrs}
69 user_filter = ${config.myEnv.mail.dovecot.ldap.filter}
70 iterate_attrs = ${config.myEnv.mail.dovecot.ldap.iterate_attrs}
71 iterate_filter = ${config.myEnv.mail.dovecot.ldap.iterate_filter}
72 '';
73 };
74
75 users.users.vhost = {
76 group = "vhost";
77 uid = config.ids.uids.vhost;
78 };
79 users.groups.vhost.gid = config.ids.gids.vhost;
80 users.users."${config.services.dovecot2.user}".extraGroups = [ "acme" ];
81
82 nixpkgs.overlays = [
83 (self: super: {
84 dovecot = super.dovecot.override { openldap = self.openldap_libressl_cyrus; };
85 })
86 ];
87
88 # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous.......
89 services.dovecot2 = {
90 enable = true;
91 enablePAM = false;
92 enablePop3 = true;
93 enableImap = true;
94 enableLmtp = true;
95 protocols = [ "sieve" ];
96 modules = [
97 pkgs.dovecot_pigeonhole
98 pkgs.dovecot_fts_xapian
99 ];
100 mailUser = "vhost";
101 mailGroup = "vhost";
102 createMailUser = false;
103 mailboxes = {
104 Trash = { auto = "subscribe"; specialUse = "Trash"; };
105 Junk = { auto = "subscribe"; specialUse = "Junk"; };
106 Sent = { auto = "subscribe"; specialUse = "Sent"; };
107 Drafts = { auto = "subscribe"; specialUse = "Drafts"; };
108 };
109 mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap";
110 sslServerCert = "/etc/dovecot/fullchain.pem";
111 sslServerKey = "/var/lib/acme/mail/key.pem";
112 sslCACert = "/etc/dovecot/fullchain.pem";
113 extraConfig = builtins.concatStringsSep "\n" [
114 # For printer which doesn’t support elliptic curve
115 ''
116 ssl_alt_cert = </etc/dovecot/fullchain-rsa.pem
117 ssl_alt_key = </var/lib/acme/mail-rsa/key.pem
118 ''
119
120 ''
121 postmaster_address = postmaster@immae.eu
122 mail_attribute_dict = file:%h/dovecot-attributes
123 imap_idle_notify_interval = 20 mins
124 namespace inbox {
125 type = private
126 separator = /
127 inbox = yes
128 list = yes
129 }
130 ''
131
132 # ACL
133 ''
134 mail_plugins = $mail_plugins acl
135 plugin {
136 acl = vfile:${pkgs.writeText "dovecot-acl" ''
137 Backup/* owner lrp
138 ''}
139 acl_globals_only = yes
140 }
141 ''
142
143 # Full text search
144 ''
145 # needs to be bigger than any mailbox size
146 default_vsz_limit = 2GB
147 mail_plugins = $mail_plugins fts fts_xapian
148 plugin {
149 plugin = fts fts_xapian
150 fts = xapian
151 fts_xapian = partial=2 full=20
152 fts_autoindex = yes
153 fts_autoindex_exclude = \Junk
154 fts_autoindex_exclude2 = \Trash
155 fts_autoindex_exclude3 = Virtual/*
156 }
157 ''
158
159 # Antispam
160 # https://docs.iredmail.org/dovecot.imapsieve.html
161 ''
162 # imap_sieve plugin added below
163
164 plugin {
165 sieve_plugins = sieve_imapsieve sieve_extprograms
166 imapsieve_url = sieve://127.0.0.1:4190
167
168 sieve_before = file:${./sieve_scripts}/backup.sieve;bindir=/var/lib/vhost/.sieve_bin
169
170 # From elsewhere to Junk folder
171 imapsieve_mailbox1_name = Junk
172 imapsieve_mailbox1_causes = COPY APPEND
173 imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin
174
175 # From Junk folder to elsewhere
176 imapsieve_mailbox2_name = *
177 imapsieve_mailbox2_from = Junk
178 imapsieve_mailbox2_causes = COPY
179 imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin
180
181 # From anywhere to NoJunk folder
182 imapsieve_mailbox3_name = NoJunk
183 imapsieve_mailbox3_causes = COPY APPEND
184 imapsieve_mailbox3_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin
185
186 sieve_pipe_bin_dir = ${sieve_bin}
187
188 sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
189 }
190 ''
191 # Services to listen
192 ''
193 service imap-login {
194 inet_listener imap {
195 }
196 inet_listener imaps {
197 }
198 }
199 service pop3-login {
200 inet_listener pop3 {
201 }
202 inet_listener pop3s {
203 }
204 }
205 service imap {
206 }
207 service pop3 {
208 }
209 service auth {
210 unix_listener auth-userdb {
211 }
212 unix_listener ${config.services.postfix.config.queue_directory}/private/auth {
213 mode = 0666
214 }
215 }
216 service auth-worker {
217 }
218 service dict {
219 unix_listener dict {
220 }
221 }
222 service stats {
223 unix_listener stats-reader {
224 user = vhost
225 group = vhost
226 mode = 0660
227 }
228 unix_listener stats-writer {
229 user = vhost
230 group = vhost
231 mode = 0660
232 }
233 }
234 ''
235
236 # Authentification
237 ''
238 first_valid_uid = ${toString config.ids.uids.vhost}
239 disable_plaintext_auth = yes
240 passdb {
241 driver = ldap
242 args = ${config.secrets.fullPaths."dovecot/ldap"}
243 }
244 userdb {
245 driver = ldap
246 args = ${config.secrets.fullPaths."dovecot/ldap"}
247 }
248 ''
249
250 # Zlib
251 ''
252 mail_plugins = $mail_plugins zlib
253 plugin {
254 zlib_save_level = 6
255 zlib_save = gz
256 }
257 ''
258
259 # Sieve
260 ''
261 plugin {
262 sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve
263 }
264 service managesieve-login {
265 }
266 service managesieve {
267 }
268 ''
269
270 # Virtual mailboxes
271 ''
272 mail_plugins = $mail_plugins virtual
273 namespace Virtual {
274 prefix = Virtual/
275 location = virtual:~/Virtual
276 }
277 ''
278
279 # Protocol specific configuration
280 # Needs to come last if there are mail_plugins entries
281 ''
282 protocol imap {
283 mail_plugins = $mail_plugins imap_sieve imap_acl
284 }
285 protocol lda {
286 mail_plugins = $mail_plugins sieve
287 }
288 ''
289 ];
290 };
291 networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ];
292 system.activationScripts.dovecot = {
293 deps = [ "users" ];
294 text =''
295 install -m 0755 -o vhost -g vhost -d /var/lib/vhost
296 '';
297 };
298
299 services.cron.systemCronJobs = let
300 cron_script = pkgs.writeScriptBin "cleanup-imap-folders" ''
301 ${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"
302 ${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"
303 ${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"
304 '';
305 in
306 [
307 "0 2 * * * root ${cron_script}/bin/cleanup-imap-folders"
308 ];
309 security.acme.certs."mail-rsa" = {
310 postRun = ''
311 systemctl restart dovecot2.service
312 '';
313 extraDomainNames = [ "imap.immae.eu" "pop3.immae.eu" ];
314 };
315 security.acme.certs."mail" = {
316 postRun = ''
317 systemctl restart dovecot2.service
318 '';
319 extraDomainNames = [ "imap.immae.eu" "pop3.immae.eu" ];
320 };
321 myServices.monitoring.fromMasterActivatedPlugins = [ "imap" "tcp" ];
322 myServices.monitoring.fromMasterObjects.service = [
323 {
324 service_description = "imap connection works";
325 host_name = config.hostEnv.fqdn;
326 use = "external-service";
327 check_command = "check_imap_connection";
328
329 servicegroups = "webstatus-remote-services,webstatus-email";
330 _webstatus_name = "IMAP";
331 _webstatus_url = "imap.immae.eu";
332 }
333
334 {
335 service_description = "imap SSL is up to date";
336 host_name = config.hostEnv.fqdn;
337 use = "external-service";
338 check_command = ["check_tcp_ssl" "993"];
339
340 servicegroups = "webstatus-ssl";
341 _webstatus_name = "IMAP";
342 _webstatus_url = "imap.immae.eu";
343 }
344
345 ];
346 };
347}
348
diff --git a/systems/eldiron/mail/postfix.nix b/systems/eldiron/mail/postfix.nix
new file mode 100644
index 0000000..f95ee1b
--- /dev/null
+++ b/systems/eldiron/mail/postfix.nix
@@ -0,0 +1,497 @@
1{ lib, pkgs, config, options, ... }:
2let
3 getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) (lib.filterAttrs (n: v: v.receive) p.emailPolicies);
4 bydomain = builtins.mapAttrs (n: getDomains) config.myServices.dns.zones;
5 receiving_domains = lib.flatten (builtins.attrValues bydomain);
6in
7{
8 options.services.postfix.submissionOptions' = options.services.postfix.submissionOptions // {
9 type = with lib.types; attrsOf (either str (listOf str));
10 apply = builtins.mapAttrs (n: v: if builtins.isList v then builtins.concatStringsSep "," v else v);
11 };
12 config = lib.mkIf config.myServices.mail.enable {
13 myServices.dns.zones."immae.eu" = with config.myServices.dns.helpers; lib.mkMerge [
14 mailMX
15 (mailCommon "immae.eu")
16 mailSend
17 {
18 # Virtual forwards and mailboxes for real users
19 emailPolicies."mail".receive = true;
20 # multi-domain generic mails:
21 # hostmaster, cron, httpd, naemon, postmaster
22 # system virtual mailboxes:
23 # devnull, printer, testconnect
24 emailPolicies."".receive = true;
25 subdomains.mail = lib.mkMerge [ (mailCommon "immae.eu") mailSend ];
26 subdomains.smtp = ips servers.eldiron.ips.main;
27
28 # DMARC reports
29 subdomains._dmarc.subdomains._report.subdomains = let
30 getDomains = p: lib.mapAttrsToList (n: v: v.fqdn) p.emailPolicies;
31 bydomain = builtins.mapAttrs (n: getDomains) config.myServices.dns.zones;
32 hostsWithMail = lib.flatten (builtins.attrValues bydomain);
33 nvpairs = builtins.map (e: { name = e; value = { TXT = [ "v=DMARC1;" ]; }; }) hostsWithMail;
34 in
35 builtins.listToAttrs nvpairs;
36 }
37 ];
38
39 myServices.chatonsProperties.hostings.mx-backup = {
40 file.datetime = "2022-08-22T01:00:00";
41 hosting = {
42 name = "MX Backup";
43 description = "Serveur e-mail secondaire";
44 logo = "https://www.postfix.org/favicon.ico";
45 website = "https://mail.immae.eu/";
46 status.level = "OK";
47 status.description = "OK";
48 registration.load = "OPEN";
49 install.type = "PACKAGE";
50 };
51 software = {
52 name = "Postfix";
53 website = "http://www.postfix.org/";
54 license.url = "http://postfix.mirrors.ovh.net/postfix-release/LICENSE";
55 license.name = "Eclipse Public license (EPL 2.0) and IBM Public License (IPL 1.0)";
56 version = pkgs.postfix.version;
57 source.url = "http://www.postfix.org/download.html";
58 };
59 };
60 secrets.keys = {
61 "postfix/mysql_alias_maps" = {
62 user = config.services.postfix.user;
63 group = config.services.postfix.group;
64 permissions = "0440";
65 text = ''
66 # We need to specify that option to trigger ssl connection
67 tls_ciphers = TLSv1.2
68 user = ${config.myEnv.mail.postfix.mysql.user}
69 password = ${config.myEnv.mail.postfix.mysql.password}
70 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
71 dbname = ${config.myEnv.mail.postfix.mysql.database}
72 query = SELECT DISTINCT destination
73 FROM forwardings
74 WHERE
75 ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s'))
76 AND active = 1
77 AND '%s' NOT IN
78 (
79 SELECT source
80 FROM forwardings_blacklisted
81 WHERE source = '%s'
82 ) UNION
83 SELECT 'devnull@immae.eu'
84 FROM forwardings_blacklisted
85 WHERE source = '%s'
86 '';
87 };
88 "postfix/ldap_mailboxes" = {
89 user = config.services.postfix.user;
90 group = config.services.postfix.group;
91 permissions = "0440";
92 text = ''
93 server_host = ldaps://${config.myEnv.mail.dovecot.ldap.host}:636
94 search_base = ${config.myEnv.mail.dovecot.ldap.base}
95 query_filter = ${config.myEnv.mail.dovecot.ldap.postfix_mailbox_filter}
96 bind_dn = ${config.myEnv.mail.dovecot.ldap.dn}
97 bind_pw = ${config.myEnv.mail.dovecot.ldap.password}
98 result_attribute = immaePostfixAddress
99 result_format = dummy
100 version = 3
101 '';
102 };
103 "postfix/mysql_sender_login_maps" = {
104 user = config.services.postfix.user;
105 group = config.services.postfix.group;
106 permissions = "0440";
107 text = ''
108 # We need to specify that option to trigger ssl connection
109 tls_ciphers = TLSv1.2
110 user = ${config.myEnv.mail.postfix.mysql.user}
111 password = ${config.myEnv.mail.postfix.mysql.password}
112 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
113 dbname = ${config.myEnv.mail.postfix.mysql.database}
114 query = SELECT DISTINCT destination
115 FROM forwardings
116 WHERE
117 (
118 (regex = 1 AND CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d') REGEXP CONCAT('^',source,'$') )
119 OR
120 (regex = 0 AND source = CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d'))
121 )
122 AND active = 1
123 UNION SELECT CONCAT(SUBSTRING_INDEX('%u', '+', 1), '@%d') AS destination
124 '';
125 };
126 "postfix/mysql_sender_relays_maps" = {
127 user = config.services.postfix.user;
128 group = config.services.postfix.group;
129 permissions = "0440";
130 text = ''
131 # We need to specify that option to trigger ssl connection
132 tls_ciphers = TLSv1.2
133 user = ${config.myEnv.mail.postfix.mysql.user}
134 password = ${config.myEnv.mail.postfix.mysql.password}
135 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
136 dbname = ${config.myEnv.mail.postfix.mysql.database}
137 # INSERT INTO sender_relays
138 # (`from`, owner, relay, login, password, regex, active)
139 # VALUES
140 # ( 'sender@otherhost.org'
141 # , 'me@mail.immae.eu'
142 # , '[otherhost.org]:587'
143 # , 'otherhostlogin'
144 # , AES_ENCRYPT('otherhostpassword', '${config.myEnv.mail.postfix.mysql.password_encrypt}')
145 # , '0'
146 # , '1');
147
148 query = SELECT DISTINCT `owner`
149 FROM sender_relays
150 WHERE
151 ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s'))
152 AND active = 1
153 '';
154 };
155 "postfix/mysql_sender_relays_hosts" = {
156 user = config.services.postfix.user;
157 group = config.services.postfix.group;
158 permissions = "0440";
159 text = ''
160 # We need to specify that option to trigger ssl connection
161 tls_ciphers = TLSv1.2
162 user = ${config.myEnv.mail.postfix.mysql.user}
163 password = ${config.myEnv.mail.postfix.mysql.password}
164 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
165 dbname = ${config.myEnv.mail.postfix.mysql.database}
166
167 query = SELECT DISTINCT relay
168 FROM sender_relays
169 WHERE
170 ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s'))
171 AND active = 1
172 '';
173 };
174 "postfix/mysql_sender_relays_creds" = {
175 user = config.services.postfix.user;
176 group = config.services.postfix.group;
177 permissions = "0440";
178 text = ''
179 # We need to specify that option to trigger ssl connection
180 tls_ciphers = TLSv1.2
181 user = ${config.myEnv.mail.postfix.mysql.user}
182 password = ${config.myEnv.mail.postfix.mysql.password}
183 hosts = unix:${config.myEnv.mail.postfix.mysql.socket}
184 dbname = ${config.myEnv.mail.postfix.mysql.database}
185
186 query = SELECT DISTINCT CONCAT(`login`, ':', AES_DECRYPT(`password`, '${config.myEnv.mail.postfix.mysql.password_encrypt}'))
187 FROM sender_relays
188 WHERE
189 ((regex = 1 AND '%s' REGEXP CONCAT('^',`from`,'$') ) OR (regex = 0 AND `from` = '%s'))
190 AND active = 1
191 '';
192 };
193 "postfix/ldap_ejabberd_users_immae_fr" = {
194 user = config.services.postfix.user;
195 group = config.services.postfix.group;
196 permissions = "0440";
197 text = ''
198 server_host = ldaps://${config.myEnv.jabber.ldap.host}:636
199 search_base = ${config.myEnv.jabber.ldap.base}
200 query_filter = ${config.myEnv.jabber.postfix_user_filter}
201 domain = immae.fr
202 bind_dn = ${config.myEnv.jabber.ldap.dn}
203 bind_pw = ${config.myEnv.jabber.ldap.password}
204 result_attribute = immaeXmppUid
205 result_format = ejabberd@localhost
206 version = 3
207 '';
208 };
209 };
210
211 networking.firewall.allowedTCPPorts = [ 25 465 587 ];
212
213 users.users.postfixscripts = {
214 group = "keys";
215 uid = config.ids.uids.postfixscripts;
216 description = "Postfix scripts user";
217 };
218 users.users."${config.services.postfix.user}".extraGroups = [ "keys" ];
219 services.filesWatcher.postfix = {
220 restart = true;
221 paths = [
222 config.secrets.fullPaths."postfix/mysql_alias_maps"
223 config.secrets.fullPaths."postfix/ldap_mailboxes"
224 config.secrets.fullPaths."postfix/mysql_sender_login_maps"
225 config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"
226 ];
227 };
228 services.postfix = {
229 extraAliases = let
230 testmail = pkgs.writeScript "testmail" ''
231 #! ${pkgs.stdenv.shell}
232 ${pkgs.coreutils}/bin/touch \
233 "/var/lib/naemon/checks/email/$(${pkgs.procmail}/bin/formail -x To: | ${pkgs.coreutils}/bin/tr -d ' <>')"
234 '';
235 in
236 ''testmail: "|${testmail}"'';
237 mapFiles = let
238 virtual_map = {
239 virtual = let
240 cfg = config.myEnv.monitoring.email_check.eldiron;
241 address = "${cfg.mail_address}@${cfg.mail_domain}";
242 aliases = config.myEnv.mail.postfix.common_aliases;
243 admins = builtins.concatStringsSep "," config.myEnv.mail.postfix.admins;
244 in pkgs.writeText "postfix-virtual" (
245 builtins.concatStringsSep "\n" (
246 [ "${address} testmail@localhost"
247 ] ++
248 map (a: "${a} ${admins}") config.myEnv.mail.postfix.other_aliases
249 ++ lib.lists.flatten (
250 map (domain:
251 map (alias: "${alias}@${domain} ${admins}") aliases
252 ) receiving_domains
253 )
254 ));
255 };
256 sasl_access = {
257 host_sender_login = with lib.attrsets; let
258 addresses = zipAttrs (lib.flatten (mapAttrsToList
259 (n: v: (map (e: { "${e}" = "${n}@immae.eu"; }) v.emails)) config.myEnv.servers));
260 aliases = config.myEnv.mail.postfix.common_aliases;
261 joined = builtins.concatStringsSep ",";
262 admins = joined config.myEnv.mail.postfix.admins;
263 in pkgs.writeText "host-sender-login"
264 (builtins.concatStringsSep "\n" (
265 mapAttrsToList (n: v: "${n} ${joined v}") addresses
266 ++ lib.lists.flatten (
267 map (domain:
268 map (alias: "${alias}@${domain} ${admins}") aliases
269 ) receiving_domains
270 )
271 ++ map (a: "${a} ${admins}") config.myEnv.mail.postfix.other_aliases
272 ));
273 };
274 in
275 virtual_map // sasl_access;
276 config = {
277 ### postfix module overrides
278 readme_directory = "${pkgs.postfix}/share/postfix/doc";
279 smtp_tls_CAfile = lib.mkForce "";
280 smtp_tls_cert_file = lib.mkForce "";
281 smtp_tls_key_file = lib.mkForce "";
282
283 message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited"
284 mailbox_size_limit = "1073741825"; # Workaround, local delivered mails should all go through scripts
285 alias_database = "\$alias_maps";
286
287 ### Aliases scripts user
288 default_privs = "postfixscripts";
289
290 ### Virtual mailboxes config
291 virtual_alias_maps = [
292 "hash:/etc/postfix/virtual"
293 "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"
294 "ldap:${config.secrets.fullPaths."postfix/ldap_ejabberd_users_immae_fr"}"
295 ];
296 virtual_mailbox_domains = receiving_domains;
297 virtual_mailbox_maps = [
298 "ldap:${config.secrets.fullPaths."postfix/ldap_mailboxes"}"
299 ];
300 dovecot_destination_recipient_limit = "1";
301 virtual_transport = "dovecot";
302
303 ### Relay domains
304 smtpd_relay_restrictions = [
305 "defer_unauth_destination"
306 ];
307
308 ### Additional smtpd configuration
309 smtpd_tls_received_header = "yes";
310 smtpd_tls_loglevel = "1";
311
312 ### Email sending configuration
313 smtp_tls_security_level = "may";
314 smtp_tls_loglevel = "1";
315
316 ### Force ip bind for smtp
317 smtp_bind_address = builtins.head config.hostEnv.ips.main.ip4;
318 smtp_bind_address6 = builtins.head config.hostEnv.ips.main.ip6;
319
320 # Use some relays when authorized senders are not myself
321 smtp_sasl_mechanism_filter = [
322 "plain"
323 "login"
324 ]; # GSSAPI Not correctly supported by postfix
325 smtp_sasl_auth_enable = "yes";
326 smtp_sasl_password_maps = [
327 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_creds"}"
328 ];
329 smtp_sasl_security_options = "noanonymous";
330 smtp_sender_dependent_authentication = "yes";
331 sender_dependent_relayhost_maps = [
332 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_hosts"}"
333 ];
334
335 ### opendkim, opendmarc, openarc milters
336 non_smtpd_milters = [
337 "unix:${config.myServices.mail.milters.sockets.opendkim}"
338 ];
339 smtpd_milters = [
340 "unix:${config.myServices.mail.milters.sockets.opendkim}"
341 "unix:${config.myServices.mail.milters.sockets.openarc}"
342 "unix:${config.myServices.mail.milters.sockets.opendmarc}"
343 ];
344
345 smtp_use_tls = true;
346 smtpd_use_tls = true;
347 smtpd_tls_chain_files = [
348 "/var/lib/acme/mail/full.pem"
349 "/var/lib/acme/mail-rsa/full.pem"
350 ];
351
352 maximal_queue_lifetime = "6w";
353 bounce_queue_lifetime = "6w";
354 };
355 enable = true;
356 enableSmtp = true;
357 enableSubmission = true;
358 submissionOptions = config.services.postfix.submissionOptions';
359 submissionOptions' = {
360 # Don’t use "long form", only commas (cf
361 # http://www.postfix.org/master.5.html long form is not handled
362 # well by the submission function)
363 smtpd_tls_security_level = "encrypt";
364 smtpd_sasl_auth_enable = "yes";
365 smtpd_tls_auth_only = "yes";
366 smtpd_sasl_tls_security_options = "noanonymous";
367 smtpd_sasl_type = "dovecot";
368 smtpd_sasl_path = "private/auth";
369 smtpd_reject_unlisted_recipient = "no";
370 smtpd_client_restrictions = [
371 "permit_sasl_authenticated"
372 "reject"
373 ];
374 smtpd_relay_restrictions = [
375 "permit_sasl_authenticated"
376 "reject"
377 ];
378 # Refuse to send e-mails with a From that is not handled
379 smtpd_sender_restrictions = [
380 "reject_sender_login_mismatch"
381 "reject_unlisted_sender"
382 "permit_sasl_authenticated,reject"
383 ];
384 smtpd_sender_login_maps = [
385 "hash:/etc/postfix/host_sender_login"
386 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_relays_maps"}"
387 "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"
388 ];
389 smtpd_recipient_restrictions = [
390 "permit_sasl_authenticated"
391 "reject"
392 ];
393 milter_macro_daemon_name = "ORIGINATING";
394 smtpd_milters = [
395 # FIXME: put it back when opensmtpd is upgraded and able to
396 # rewrite the from header
397 #"unix:/run/milter_verify_from/verify_from.sock"
398 "unix:${config.myServices.mail.milters.sockets.opendkim}"
399 ];
400 };
401 destination = ["localhost"];
402 # This needs to reverse DNS
403 hostname = config.hostEnv.fqdn;
404 setSendmail = true;
405 recipientDelimiter = "+";
406 masterConfig = {
407 submissions = {
408 type = "inet";
409 private = false;
410 command = "smtpd";
411 args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let
412 mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
413 in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions)
414 );
415 };
416 dovecot = {
417 type = "unix";
418 privileged = true;
419 chroot = false;
420 command = "pipe";
421 args = let
422 # rspamd could be used as a milter, but then it cannot apply
423 # its checks "per user" (milter is not yet dispatched to
424 # users), so we wrap dovecot-lda inside rspamc per recipient
425 # here.
426 rspamc_dovecot = pkgs.writeScriptBin "rspamc_dovecot" ''
427 #! ${pkgs.stdenv.shell}
428 set -o pipefail
429 sender="$1"
430 original_recipient="$2"
431 user="$3"
432
433 ${pkgs.coreutils}/bin/cat - | \
434 ${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d "$user" --mime | \
435 ${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f "$sender" -a "$original_recipient" -d "$user"
436 if echo ''${PIPESTATUS[@]} | ${pkgs.gnugrep}/bin/grep -qE '^[0 ]+$'; then
437 exit 0
438 else
439 # src/global/sys_exits.h to retry
440 exit 75
441 fi
442 '';
443 in [
444 "flags=ODRhu" "user=vhost:vhost"
445 "argv=${rspamc_dovecot}/bin/rspamc_dovecot \${sender} \${original_recipient} \${user}@\${nexthop}"
446 ];
447 };
448 };
449 };
450 security.acme.certs."mail" = {
451 postRun = ''
452 systemctl restart postfix.service
453 '';
454 extraDomainNames = [ "smtp.immae.eu" ];
455 };
456 security.acme.certs."mail-rsa" = {
457 postRun = ''
458 systemctl restart postfix.service
459 '';
460 extraDomainNames = [ "smtp.immae.eu" ];
461 };
462 system.activationScripts.testmail = {
463 deps = [ "users" ];
464 text = let
465 allCfg = config.myEnv.monitoring.email_check;
466 cfg = allCfg.eldiron;
467 reverseTargets = builtins.attrNames (lib.attrsets.filterAttrs (k: v: builtins.elem "eldiron" v.targets) allCfg);
468 to_email = cfg': host':
469 let sep = if lib.strings.hasInfix "+" cfg'.mail_address then "_" else "+";
470 in "${cfg'.mail_address}${sep}${host'}@${cfg'.mail_domain}";
471 mails_to_receive = builtins.concatStringsSep " " (map (to_email cfg) reverseTargets);
472 in ''
473 install -m 0555 -o postfixscripts -g keys -d /var/lib/naemon/checks/email
474 for f in ${mails_to_receive}; do
475 if [ ! -f /var/lib/naemon/checks/email/$f ]; then
476 install -m 0644 -o postfixscripts -g keys /dev/null -T /var/lib/naemon/checks/email/$f
477 touch -m -d @0 /var/lib/naemon/checks/email/$f
478 fi
479 done
480 '';
481 };
482 systemd.services.postfix.serviceConfig.Slice = "mail.slice";
483
484 myServices.monitoring.fromMasterObjects.service = [
485 {
486 service_description = "postfix SSL is up to date";
487 host_name = config.hostEnv.fqdn;
488 use = "external-service";
489 check_command = "check_smtp";
490
491 servicegroups = "webstatus-ssl";
492 _webstatus_name = "SMTP";
493 _webstatus_url = "smtp.immae.eu";
494 }
495 ];
496 };
497}
diff --git a/systems/eldiron/mail/rspamd.nix b/systems/eldiron/mail/rspamd.nix
new file mode 100644
index 0000000..a300cc5
--- /dev/null
+++ b/systems/eldiron/mail/rspamd.nix
@@ -0,0 +1,88 @@
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 systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "vhost" ];
30 services.rspamd = {
31 enable = true;
32 debug = false;
33 overrides = {
34 "actions.conf".text = ''
35 reject = null;
36 add_header = 6;
37 greylist = null;
38 '';
39 "milter_headers.conf".text = ''
40 extended_spam_headers = true;
41 '';
42 };
43 locals = {
44 "redis.conf".text = ''
45 servers = "${config.myEnv.mail.rspamd.redis.socket}";
46 db = "${config.myEnv.mail.rspamd.redis.db}";
47 '';
48 "classifier-bayes.conf".text = ''
49 users_enabled = true;
50 backend = "redis";
51 servers = "${config.myEnv.mail.rspamd.redis.socket}";
52 database = "${config.myEnv.mail.rspamd.redis.db}";
53 autolearn = true;
54 cache {
55 backend = "redis";
56 }
57 new_schema = true;
58 statfile {
59 BAYES_HAM {
60 spam = false;
61 }
62 BAYES_SPAM {
63 spam = true;
64 }
65 }
66 '';
67 };
68 workers = {
69 controller = {
70 extraConfig = ''
71 enable_password = "${config.myEnv.mail.rspamd.write_password_hashed}";
72 password = "${config.myEnv.mail.rspamd.read_password_hashed}";
73 '';
74 bindSockets = [ {
75 socket = config.myServices.mail.rspamd.sockets.worker-controller;
76 mode = "0660";
77 owner = config.services.rspamd.user;
78 group = "vhost";
79 } ];
80 };
81 };
82 postfix = {
83 enable = true;
84 config = {};
85 };
86 };
87 };
88}
diff --git a/systems/eldiron/mail/scan_reported_mails b/systems/eldiron/mail/scan_reported_mails
new file mode 100755
index 0000000..fe9f4d6
--- /dev/null
+++ b/systems/eldiron/mail/scan_reported_mails
@@ -0,0 +1,21 @@
1#!/usr/bin/env bash
2
3( flock -n 9 || exit 1
4shopt -s nullglob
5for 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
20done
21) 9>/var/lib/vhost/scan_reported_mails.lock
diff --git a/systems/eldiron/mail/sieve_bin/imapsieve_copy b/systems/eldiron/mail/sieve_bin/imapsieve_copy
new file mode 100755
index 0000000..2ca1f23
--- /dev/null
+++ b/systems/eldiron/mail/sieve_bin/imapsieve_copy
@@ -0,0 +1,8 @@
1#!/usr/bin/env bash
2# Inspired from https://docs.iredmail.org/dovecot.imapsieve.html
3
4MSG_TYPE="$1"
5OUTPUT_DIR="/var/lib/vhost/.rspamd/${USER}/pending/${MSG_TYPE}"
6FILE="${OUTPUT_DIR}/$(date +%Y%m%d%H%M%S)-${RANDOM}${RANDOM}.eml"
7mkdir -p "${OUTPUT_DIR}"
8cat > ${FILE} < /dev/stdin
diff --git a/systems/eldiron/mail/sieve_scripts/backup.sieve b/systems/eldiron/mail/sieve_scripts/backup.sieve
new file mode 100644
index 0000000..3014c0a
--- /dev/null
+++ b/systems/eldiron/mail/sieve_scripts/backup.sieve
@@ -0,0 +1,7 @@
1# vim: filetype=sieve
2require ["copy","mailbox","fileinto","regex"];
3if header :is "X-Spam" "Yes" {
4 fileinto :create :copy "Backup/Spam";
5} else {
6 fileinto :create :copy "Backup/Ham";
7}
diff --git a/systems/eldiron/mail/sieve_scripts/report_ham.sieve b/systems/eldiron/mail/sieve_scripts/report_ham.sieve
new file mode 100644
index 0000000..f9b8481
--- /dev/null
+++ b/systems/eldiron/mail/sieve_scripts/report_ham.sieve
@@ -0,0 +1,11 @@
1require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
2
3if environment :matches "imap.mailbox" "*" {
4 set "mailbox" "${1}";
5}
6
7if string "${mailbox}" "Trash" {
8 stop;
9}
10
11pipe :copy "imapsieve_copy" [ "ham" ];
diff --git a/systems/eldiron/mail/sieve_scripts/report_spam.sieve b/systems/eldiron/mail/sieve_scripts/report_spam.sieve
new file mode 100644
index 0000000..9a1f794
--- /dev/null
+++ b/systems/eldiron/mail/sieve_scripts/report_spam.sieve
@@ -0,0 +1,3 @@
1require ["vnd.dovecot.pipe", "copy", "imapsieve" ];
2
3pipe :copy "imapsieve_copy" [ "spam" ];
diff --git a/systems/eldiron/mail/sympa.nix b/systems/eldiron/mail/sympa.nix
new file mode 100644
index 0000000..8e801dd
--- /dev/null
+++ b/systems/eldiron/mail/sympa.nix
@@ -0,0 +1,232 @@
1{ lib, pkgs, config, ... }:
2let
3 domain = "lists.immae.eu";
4 sympaConfig = config.myEnv.mail.sympa;
5in
6{
7 config = lib.mkIf config.myServices.mail.enable {
8 myServices.dns.zones."immae.eu".emailPolicies."lists".receive = true;
9 myServices.dns.zones."immae.eu".subdomains.lists =
10 with config.myServices.dns.helpers; lib.mkMerge [
11 (ips servers.eldiron.ips.main)
12 (mailCommon "immae.eu")
13 mailSend
14 ];
15
16 myServices.chatonsProperties.services.sympa = {
17 file.datetime = "2022-08-22T00:50:00";
18 service = {
19 name = "Sympa";
20 description = "Mailing lists service";
21 website = "https://mail.immae.eu/sympa";
22 logo = "https://mail.immae.eu/static-sympa/icons/favicon_sympa.png";
23 status.level = "OK";
24 status.description = "OK";
25 registration."" = ["MEMBER" "CLIENT"];
26 registration.load = "OPEN";
27 install.type = "PACKAGE";
28 };
29 software = {
30 name = "Sympa";
31 website = "https://www.sympa.org/";
32 license.url = "https://github.com/sympa-community/sympa/blob/sympa-6.2/COPYING";
33 license.name = "GNU General Public License v2.0";
34 version = pkgs.sympa.version;
35 source.url = "https://github.com/sympa-community/sympa/";
36 };
37 };
38 myServices.databases.postgresql.authorizedHosts = {
39 backup-2 = [
40 {
41 username = "sympa";
42 database = "sympa";
43 ip4 = config.myEnv.servers.backup-2.ips.main.ip4;
44 ip6 = map (v: "${v}/128") config.myEnv.servers.backup-2.ips.main.ip6;
45 }
46 ];
47 };
48 services.websites.env.tools.vhostConfs.mail = {
49 extraConfig = lib.mkAfter [
50 ''
51 Alias /static-sympa/ /var/lib/sympa/static_content/
52 <Directory /var/lib/sympa/static_content/>
53 Require all granted
54 AllowOverride none
55 </Directory>
56 <Location /sympa>
57 SetHandler "proxy:unix:/run/sympa/wwsympa.socket|fcgi://"
58 Require all granted
59 </Location>
60 ''
61 ];
62 };
63
64 secrets.keys = {
65 "sympa/db_password" = {
66 permissions = "0400";
67 group = "sympa";
68 user = "sympa";
69 text = sympaConfig.postgresql.password;
70 };
71 }
72 // lib.mapAttrs' (n: v: lib.nameValuePair "sympa/data_sources/${n}.incl" {
73 permissions = "0400"; group = "sympa"; user = "sympa"; text = v;
74 }) sympaConfig.data_sources
75 // lib.mapAttrs' (n: v: lib.nameValuePair "sympa/scenari/${n}" {
76 permissions = "0400"; group = "sympa"; user = "sympa"; text = v;
77 }) sympaConfig.scenari;
78 users.users.sympa.extraGroups = [ "keys" ];
79 systemd.slices.mail-sympa = {
80 description = "Sympa slice";
81 };
82
83 systemd.services.sympa.serviceConfig.SupplementaryGroups = [ "keys" ];
84 systemd.services.sympa-archive.serviceConfig.SupplementaryGroups = [ "keys" ];
85 systemd.services.sympa-bounce.serviceConfig.SupplementaryGroups = [ "keys" ];
86 systemd.services.sympa-bulk.serviceConfig.SupplementaryGroups = [ "keys" ];
87 systemd.services.sympa-task.serviceConfig.SupplementaryGroups = [ "keys" ];
88
89 systemd.services.sympa.serviceConfig.Slice = "mail-sympa.slice";
90 systemd.services.sympa-archive.serviceConfig.Slice = "mail-sympa.slice";
91 systemd.services.sympa-bounce.serviceConfig.Slice = "mail-sympa.slice";
92 systemd.services.sympa-bulk.serviceConfig.Slice = "mail-sympa.slice";
93 systemd.services.sympa-task.serviceConfig.Slice = "mail-sympa.slice";
94
95 # https://github.com/NixOS/nixpkgs/pull/84202
96 systemd.services.sympa.serviceConfig.ProtectKernelModules = lib.mkForce false;
97 systemd.services.sympa-archive.serviceConfig.ProtectKernelModules = lib.mkForce false;
98 systemd.services.sympa-bounce.serviceConfig.ProtectKernelModules = lib.mkForce false;
99 systemd.services.sympa-bulk.serviceConfig.ProtectKernelModules = lib.mkForce false;
100 systemd.services.sympa-task.serviceConfig.ProtectKernelModules = lib.mkForce false;
101 systemd.services.sympa.serviceConfig.ProtectKernelTunables = lib.mkForce false;
102 systemd.services.sympa-archive.serviceConfig.ProtectKernelTunables = lib.mkForce false;
103 systemd.services.sympa-bounce.serviceConfig.ProtectKernelTunables = lib.mkForce false;
104 systemd.services.sympa-bulk.serviceConfig.ProtectKernelTunables = lib.mkForce false;
105 systemd.services.sympa-task.serviceConfig.ProtectKernelTunables = lib.mkForce false;
106
107 systemd.services.wwsympa = {
108 wantedBy = [ "multi-user.target" ];
109 after = [ "sympa.service" ];
110 serviceConfig = {
111 Slice = "mail-sympa.slice";
112 Type = "forking";
113 PIDFile = "/run/sympa/wwsympa.pid";
114 Restart = "always";
115 ExecStart = ''${pkgs.spawn_fcgi}/bin/spawn-fcgi \
116 -u sympa \
117 -g sympa \
118 -U wwwrun \
119 -M 0600 \
120 -F 2 \
121 -P /run/sympa/wwsympa.pid \
122 -s /run/sympa/wwsympa.socket \
123 -- ${pkgs.sympa}/lib/sympa/cgi/wwsympa.fcgi
124 '';
125 StateDirectory = "sympa";
126 ProtectHome = true;
127 ProtectSystem = "full";
128 ProtectControlGroups = true;
129 };
130 };
131
132 services.postfix = {
133 mapFiles = {
134 # Update relay list when changing one of those
135 sympa_virtual = pkgs.writeText "virtual.sympa" ''
136 sympa-request@${domain} postmaster@immae.eu
137 sympa-owner@${domain} postmaster@immae.eu
138 '';
139 sympa_transport = pkgs.writeText "transport.sympa" ''
140 ${domain} error:User unknown in recipient table
141 sympa@${domain} sympa:sympa@${domain}
142 listmaster@${domain} sympa:listmaster@${domain}
143 bounce@${domain} sympabounce:sympa@${domain}
144 abuse-feedback-report@${domain} sympabounce:sympa@${domain}
145 '';
146 };
147 config = {
148 transport_maps = lib.mkAfter [
149 "hash:/etc/postfix/sympa_transport"
150 "hash:/var/lib/sympa/sympa_transport"
151 ];
152 virtual_alias_maps = lib.mkAfter [
153 "hash:/etc/postfix/sympa_virtual"
154 ];
155 virtual_mailbox_maps = lib.mkAfter [
156 "hash:/etc/postfix/sympa_transport"
157 "hash:/var/lib/sympa/sympa_transport"
158 "hash:/etc/postfix/sympa_virtual"
159 ];
160 };
161 masterConfig = {
162 sympa = {
163 type = "unix";
164 privileged = true;
165 chroot = false;
166 command = "pipe";
167 args = [
168 "flags=hqRu"
169 "user=sympa"
170 "argv=${pkgs.sympa}/libexec/queue"
171 "\${nexthop}"
172 ];
173 };
174 sympabounce = {
175 type = "unix";
176 privileged = true;
177 chroot = false;
178 command = "pipe";
179 args = [
180 "flags=hqRu"
181 "user=sympa"
182 "argv=${pkgs.sympa}/libexec/bouncequeue"
183 "\${nexthop}"
184 ];
185 };
186 };
187 };
188 services.sympa = {
189 enable = true;
190 listMasters = sympaConfig.listmasters;
191 mainDomain = domain;
192 domains = {
193 "${domain}" = {
194 webHost = "mail.immae.eu";
195 webLocation = "/sympa";
196 };
197 };
198
199 database = {
200 type = "PostgreSQL";
201 user = sympaConfig.postgresql.user;
202 host = sympaConfig.postgresql.socket;
203 name = sympaConfig.postgresql.database;
204 passwordFile = config.secrets.fullPaths."sympa/db_password";
205 createLocally = false;
206 };
207 settings = {
208 sendmail = "/run/wrappers/bin/sendmail";
209 log_smtp = "on";
210 sendmail_aliases = "/var/lib/sympa/sympa_transport";
211 aliases_program = "${pkgs.postfix}/bin/postmap";
212 create_list = "listmaster";
213 };
214 settingsFile = {
215 "virtual.sympa".enable = false;
216 "transport.sympa".enable = false;
217 } // lib.mapAttrs' (n: v: lib.nameValuePair
218 "etc/${domain}/data_sources/${n}.incl"
219 { source = config.secrets.fullPaths."sympa/data_sources/${n}.incl"; }) sympaConfig.data_sources
220 // lib.mapAttrs' (n: v: lib.nameValuePair
221 "etc/${domain}/scenari/${n}"
222 { source = config.secrets.fullPaths."sympa/scenari/${n}"; }) sympaConfig.scenari;
223 web = {
224 server = "none";
225 };
226
227 mta = {
228 type = "none";
229 };
230 };
231 };
232}