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 | 428 | ||||
-rw-r--r-- | modules/private/mail/milters.nix | 208 | ||||
-rw-r--r-- | modules/private/mail/postfix.nix | 488 | ||||
-rw-r--r-- | modules/private/mail/rspamd.nix | 132 |
5 files changed, 658 insertions, 640 deletions
diff --git a/modules/private/mail/default.nix b/modules/private/mail/default.nix index ac8ad8c..d3b2a25 100644 --- a/modules/private/mail/default.nix +++ b/modules/private/mail/default.nix | |||
@@ -1,21 +1,31 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | 1 | { lib, pkgs, config, myconfig, ... }: |
2 | { | 2 | { |
3 | config.security.acme.certs."mail" = config.services.myCertificates.certConfig // { | 3 | imports = [ |
4 | domain = "eldiron.immae.eu"; | 4 | ./milters.nix |
5 | extraDomains = let | 5 | ./postfix.nix |
6 | zonesWithMx = builtins.filter (zone: | 6 | ./dovecot.nix |
7 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 | 7 | ./rspamd.nix |
8 | ) myconfig.env.dns.masterZones; | 8 | ]; |
9 | mxs = map (zone: "mx-1.${zone.name}") zonesWithMx; | 9 | options.myServices.mail.enable = lib.mkEnableOption "enable Mail services"; |
10 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | 10 | |
11 | }; | 11 | config = lib.mkIf config.myServices.mail.enable { |
12 | config.services.backup.profiles = { | 12 | security.acme.certs."mail" = config.myServices.certificates.certConfig // { |
13 | mail = { | 13 | domain = "eldiron.immae.eu"; |
14 | rootDir = "/var/lib"; | 14 | extraDomains = let |
15 | excludeFile = lib.mkAfter '' | 15 | zonesWithMx = builtins.filter (zone: |
16 | + /var/lib/vhost | 16 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 |
17 | - /var/lib | 17 | ) myconfig.env.dns.masterZones; |
18 | ''; | 18 | mxs = map (zone: "mx-1.${zone.name}") zonesWithMx; |
19 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | ||
20 | }; | ||
21 | services.backup.profiles = { | ||
22 | mail = { | ||
23 | rootDir = "/var/lib"; | ||
24 | excludeFile = lib.mkAfter '' | ||
25 | + /var/lib/vhost | ||
26 | - /var/lib | ||
27 | ''; | ||
28 | }; | ||
19 | }; | 29 | }; |
20 | }; | 30 | }; |
21 | } | 31 | } |
diff --git a/modules/private/mail/dovecot.nix b/modules/private/mail/dovecot.nix index 0d13a7b..dc75e0f 100644 --- a/modules/private/mail/dovecot.nix +++ b/modules/private/mail/dovecot.nix | |||
@@ -12,239 +12,241 @@ let | |||
12 | ''; | 12 | ''; |
13 | in | 13 | in |
14 | { | 14 | { |
15 | config.services.backup.profiles.mail.excludeFile = '' | 15 | config = lib.mkIf config.myServices.mail.enable { |
16 | + /var/lib/dhparams | 16 | services.backup.profiles.mail.excludeFile = '' |
17 | + /var/lib/dovecot | 17 | + /var/lib/dhparams |
18 | ''; | 18 | + /var/lib/dovecot |
19 | config.secrets.keys = [ | 19 | ''; |
20 | { | 20 | secrets.keys = [ |
21 | dest = "dovecot/ldap"; | 21 | { |
22 | user = config.services.dovecot2.user; | 22 | dest = "dovecot/ldap"; |
23 | group = config.services.dovecot2.group; | 23 | user = config.services.dovecot2.user; |
24 | permissions = "0400"; | 24 | group = config.services.dovecot2.group; |
25 | text = '' | 25 | permissions = "0400"; |
26 | hosts = ${myconfig.env.mail.dovecot.ldap.host} | 26 | text = '' |
27 | tls = yes | 27 | hosts = ${myconfig.env.mail.dovecot.ldap.host} |
28 | 28 | tls = yes | |
29 | dn = ${myconfig.env.mail.dovecot.ldap.dn} | ||
30 | dnpass = ${myconfig.env.mail.dovecot.ldap.password} | ||
31 | 29 | ||
32 | auth_bind = yes | 30 | dn = ${myconfig.env.mail.dovecot.ldap.dn} |
31 | dnpass = ${myconfig.env.mail.dovecot.ldap.password} | ||
33 | 32 | ||
34 | ldap_version = 3 | 33 | auth_bind = yes |
35 | 34 | ||
36 | base = ${myconfig.env.mail.dovecot.ldap.base} | 35 | ldap_version = 3 |
37 | scope = subtree | ||
38 | 36 | ||
39 | user_filter = ${myconfig.env.mail.dovecot.ldap.filter} | 37 | base = ${myconfig.env.mail.dovecot.ldap.base} |
40 | pass_filter = ${myconfig.env.mail.dovecot.ldap.filter} | 38 | scope = subtree |
41 | 39 | ||
42 | user_attrs = ${myconfig.env.mail.dovecot.ldap.user_attrs} | 40 | user_filter = ${myconfig.env.mail.dovecot.ldap.filter} |
43 | pass_attrs = ${myconfig.env.mail.dovecot.ldap.pass_attrs} | 41 | pass_filter = ${myconfig.env.mail.dovecot.ldap.filter} |
44 | ''; | ||
45 | } | ||
46 | ]; | ||
47 | 42 | ||
48 | config.users.users.vhost = { | 43 | user_attrs = ${myconfig.env.mail.dovecot.ldap.user_attrs} |
49 | group = "vhost"; | 44 | pass_attrs = ${myconfig.env.mail.dovecot.ldap.pass_attrs} |
50 | uid = config.ids.uids.vhost; | 45 | ''; |
51 | }; | 46 | } |
52 | config.users.groups.vhost.gid = config.ids.gids.vhost; | ||
53 | |||
54 | # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous....... | ||
55 | config.services.dovecot2 = { | ||
56 | enable = true; | ||
57 | enablePAM = false; | ||
58 | enablePop3 = true; | ||
59 | enableImap = true; | ||
60 | enableLmtp = true; | ||
61 | protocols = [ "sieve" ]; | ||
62 | modules = [ | ||
63 | pkgs.dovecot_pigeonhole | ||
64 | pkgs.dovecot_fts-xapian | ||
65 | ]; | ||
66 | mailUser = "vhost"; | ||
67 | mailGroup = "vhost"; | ||
68 | createMailUser = false; | ||
69 | mailboxes = [ | ||
70 | { name = "Trash"; auto = "subscribe"; specialUse = "Trash"; } | ||
71 | { name = "Junk"; auto = "subscribe"; specialUse = "Junk"; } | ||
72 | { name = "Sent"; auto = "subscribe"; specialUse = "Sent"; } | ||
73 | { name = "Drafts"; auto = "subscribe"; specialUse = "Drafts"; } | ||
74 | ]; | 47 | ]; |
75 | mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap"; | 48 | |
76 | sslServerCert = "/var/lib/acme/mail/fullchain.pem"; | 49 | users.users.vhost = { |
77 | sslServerKey = "/var/lib/acme/mail/key.pem"; | 50 | group = "vhost"; |
78 | sslCACert = "/var/lib/acme/mail/fullchain.pem"; | 51 | uid = config.ids.uids.vhost; |
79 | extraConfig = builtins.concatStringsSep "\n" [ | 52 | }; |
80 | '' | 53 | users.groups.vhost.gid = config.ids.gids.vhost; |
81 | postmaster_address = postmaster@immae.eu | 54 | |
82 | mail_attribute_dict = file:%h/dovecot-attributes | 55 | # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous....... |
83 | imap_idle_notify_interval = 20 mins | 56 | services.dovecot2 = { |
84 | namespace inbox { | 57 | enable = true; |
85 | type = private | 58 | enablePAM = false; |
86 | separator = / | 59 | enablePop3 = true; |
87 | inbox = yes | 60 | enableImap = true; |
88 | list = yes | 61 | enableLmtp = true; |
89 | } | 62 | protocols = [ "sieve" ]; |
90 | '' | 63 | modules = [ |
91 | 64 | pkgs.dovecot_pigeonhole | |
92 | # Full text search | 65 | pkgs.dovecot_fts-xapian |
93 | '' | 66 | ]; |
94 | # needs to be bigger than any mailbox size | 67 | mailUser = "vhost"; |
95 | default_vsz_limit = 2GB | 68 | mailGroup = "vhost"; |
96 | mail_plugins = $mail_plugins fts fts_xapian | 69 | createMailUser = false; |
70 | mailboxes = [ | ||
71 | { name = "Trash"; auto = "subscribe"; specialUse = "Trash"; } | ||
72 | { name = "Junk"; auto = "subscribe"; specialUse = "Junk"; } | ||
73 | { name = "Sent"; auto = "subscribe"; specialUse = "Sent"; } | ||
74 | { name = "Drafts"; auto = "subscribe"; specialUse = "Drafts"; } | ||
75 | ]; | ||
76 | mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap"; | ||
77 | sslServerCert = "/var/lib/acme/mail/fullchain.pem"; | ||
78 | sslServerKey = "/var/lib/acme/mail/key.pem"; | ||
79 | sslCACert = "/var/lib/acme/mail/fullchain.pem"; | ||
80 | extraConfig = builtins.concatStringsSep "\n" [ | ||
81 | '' | ||
82 | postmaster_address = postmaster@immae.eu | ||
83 | mail_attribute_dict = file:%h/dovecot-attributes | ||
84 | imap_idle_notify_interval = 20 mins | ||
85 | namespace inbox { | ||
86 | type = private | ||
87 | separator = / | ||
88 | inbox = yes | ||
89 | list = yes | ||
90 | } | ||
91 | '' | ||
92 | |||
93 | # Full text search | ||
94 | '' | ||
95 | # needs to be bigger than any mailbox size | ||
96 | default_vsz_limit = 2GB | ||
97 | mail_plugins = $mail_plugins fts fts_xapian | ||
98 | plugin { | ||
99 | plugin = fts fts_xapian | ||
100 | fts = xapian | ||
101 | fts_xapian = partial=2 full=20 | ||
102 | fts_autoindex = yes | ||
103 | fts_autoindex_exclude = \Junk | ||
104 | fts_autoindex_exclude2 = \Trash | ||
105 | fts_autoindex_exclude3 = Virtual/* | ||
106 | } | ||
107 | '' | ||
108 | |||
109 | # Antispam | ||
110 | # https://docs.iredmail.org/dovecot.imapsieve.html | ||
111 | '' | ||
112 | # imap_sieve plugin added below | ||
113 | |||
97 | plugin { | 114 | plugin { |
98 | plugin = fts fts_xapian | 115 | sieve_plugins = sieve_imapsieve sieve_extprograms |
99 | fts = xapian | 116 | imapsieve_url = sieve://127.0.0.1:4190 |
100 | fts_xapian = partial=2 full=20 | 117 | |
101 | fts_autoindex = yes | 118 | # From elsewhere to Junk folder |
102 | fts_autoindex_exclude = \Junk | 119 | imapsieve_mailbox1_name = Junk |
103 | fts_autoindex_exclude2 = \Trash | 120 | imapsieve_mailbox1_causes = COPY APPEND |
104 | fts_autoindex_exclude3 = Virtual/* | 121 | imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin |
105 | } | 122 | |
106 | '' | 123 | # From Junk folder to elsewhere |
107 | 124 | imapsieve_mailbox2_name = * | |
108 | # Antispam | 125 | imapsieve_mailbox2_from = Junk |
109 | # https://docs.iredmail.org/dovecot.imapsieve.html | 126 | imapsieve_mailbox2_causes = COPY |
110 | '' | 127 | imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin |
111 | # imap_sieve plugin added below | 128 | |
112 | 129 | sieve_pipe_bin_dir = ${sieve_bin} | |
113 | plugin { | 130 | |
114 | sieve_plugins = sieve_imapsieve sieve_extprograms | 131 | sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment |
115 | imapsieve_url = sieve://127.0.0.1:4190 | ||
116 | |||
117 | # From elsewhere to Junk folder | ||
118 | imapsieve_mailbox1_name = Junk | ||
119 | imapsieve_mailbox1_causes = COPY APPEND | ||
120 | imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
121 | |||
122 | # From Junk folder to elsewhere | ||
123 | imapsieve_mailbox2_name = * | ||
124 | imapsieve_mailbox2_from = Junk | ||
125 | imapsieve_mailbox2_causes = COPY | ||
126 | imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
127 | |||
128 | sieve_pipe_bin_dir = ${sieve_bin} | ||
129 | |||
130 | sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment | ||
131 | } | ||
132 | '' | ||
133 | # Services to listen | ||
134 | '' | ||
135 | service imap-login { | ||
136 | inet_listener imap { | ||
137 | } | 132 | } |
138 | inet_listener imaps { | 133 | '' |
134 | # Services to listen | ||
135 | '' | ||
136 | service imap-login { | ||
137 | inet_listener imap { | ||
138 | } | ||
139 | inet_listener imaps { | ||
140 | } | ||
139 | } | 141 | } |
140 | } | 142 | service pop3-login { |
141 | service pop3-login { | 143 | inet_listener pop3 { |
142 | inet_listener pop3 { | 144 | } |
145 | inet_listener pop3s { | ||
146 | } | ||
143 | } | 147 | } |
144 | inet_listener pop3s { | 148 | service imap { |
145 | } | 149 | } |
146 | } | 150 | service pop3 { |
147 | service imap { | ||
148 | } | ||
149 | service pop3 { | ||
150 | } | ||
151 | service auth { | ||
152 | unix_listener auth-userdb { | ||
153 | } | 151 | } |
154 | unix_listener ${config.services.postfix.config.queue_directory}/private/auth { | 152 | service auth { |
155 | mode = 0666 | 153 | unix_listener auth-userdb { |
154 | } | ||
155 | unix_listener ${config.services.postfix.config.queue_directory}/private/auth { | ||
156 | mode = 0666 | ||
157 | } | ||
156 | } | 158 | } |
157 | } | 159 | service auth-worker { |
158 | service auth-worker { | ||
159 | } | ||
160 | service dict { | ||
161 | unix_listener dict { | ||
162 | } | 160 | } |
163 | } | 161 | service dict { |
164 | service stats { | 162 | unix_listener dict { |
165 | unix_listener stats-reader { | 163 | } |
166 | user = vhost | ||
167 | group = vhost | ||
168 | mode = 0660 | ||
169 | } | 164 | } |
170 | unix_listener stats-writer { | 165 | service stats { |
171 | user = vhost | 166 | unix_listener stats-reader { |
172 | group = vhost | 167 | user = vhost |
173 | mode = 0660 | 168 | group = vhost |
169 | mode = 0660 | ||
170 | } | ||
171 | unix_listener stats-writer { | ||
172 | user = vhost | ||
173 | group = vhost | ||
174 | mode = 0660 | ||
175 | } | ||
174 | } | 176 | } |
175 | } | 177 | '' |
176 | '' | 178 | |
177 | 179 | # Authentification | |
178 | # Authentification | 180 | '' |
179 | '' | 181 | first_valid_uid = ${toString config.ids.uids.vhost} |
180 | first_valid_uid = ${toString config.ids.uids.vhost} | 182 | disable_plaintext_auth = yes |
181 | disable_plaintext_auth = yes | 183 | passdb { |
182 | passdb { | 184 | driver = ldap |
183 | driver = ldap | 185 | args = ${config.secrets.fullPaths."dovecot/ldap"} |
184 | args = ${config.secrets.fullPaths."dovecot/ldap"} | 186 | } |
185 | } | 187 | userdb { |
186 | userdb { | 188 | driver = static |
187 | driver = static | 189 | args = user=%u uid=vhost gid=vhost home=/var/lib/vhost/%d/%n/ mail=mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap |
188 | args = user=%u uid=vhost gid=vhost home=/var/lib/vhost/%d/%n/ mail=mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap | 190 | } |
189 | } | 191 | '' |
190 | '' | ||
191 | |||
192 | # Zlib | ||
193 | '' | ||
194 | mail_plugins = $mail_plugins zlib | ||
195 | plugin { | ||
196 | zlib_save_level = 6 | ||
197 | zlib_save = gz | ||
198 | } | ||
199 | '' | ||
200 | 192 | ||
201 | # Sieve | 193 | # Zlib |
202 | '' | 194 | '' |
203 | plugin { | 195 | mail_plugins = $mail_plugins zlib |
204 | sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve | 196 | plugin { |
205 | } | 197 | zlib_save_level = 6 |
206 | service managesieve-login { | 198 | zlib_save = gz |
207 | } | 199 | } |
208 | service managesieve { | 200 | '' |
209 | } | ||
210 | '' | ||
211 | |||
212 | # Virtual mailboxes | ||
213 | '' | ||
214 | mail_plugins = $mail_plugins virtual | ||
215 | namespace Virtual { | ||
216 | prefix = Virtual/ | ||
217 | location = virtual:~/Virtual | ||
218 | } | ||
219 | '' | ||
220 | 201 | ||
221 | # Protocol specific configuration | 202 | # Sieve |
222 | # Needs to come last if there are mail_plugins entries | 203 | '' |
223 | '' | 204 | plugin { |
224 | protocol imap { | 205 | sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve |
225 | mail_plugins = $mail_plugins imap_sieve | 206 | } |
226 | } | 207 | service managesieve-login { |
227 | protocol lda { | 208 | } |
228 | mail_plugins = $mail_plugins sieve | 209 | service managesieve { |
229 | } | 210 | } |
230 | '' | 211 | '' |
231 | ]; | 212 | |
232 | }; | 213 | # Virtual mailboxes |
233 | config.networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ]; | 214 | '' |
234 | config.system.activationScripts.dovecot = { | 215 | mail_plugins = $mail_plugins virtual |
235 | deps = [ "users" ]; | 216 | namespace Virtual { |
236 | text ='' | 217 | prefix = Virtual/ |
237 | install -m 0755 -o vhost -g vhost -d /var/lib/vhost | 218 | location = virtual:~/Virtual |
238 | ''; | 219 | } |
239 | }; | 220 | '' |
240 | 221 | ||
241 | config.security.acme.certs."mail" = { | 222 | # Protocol specific configuration |
242 | postRun = '' | 223 | # Needs to come last if there are mail_plugins entries |
243 | systemctl restart dovecot2.service | 224 | '' |
244 | ''; | 225 | protocol imap { |
245 | extraDomains = { | 226 | mail_plugins = $mail_plugins imap_sieve |
246 | "imap.immae.eu" = null; | 227 | } |
247 | "pop3.immae.eu" = null; | 228 | protocol lda { |
229 | mail_plugins = $mail_plugins sieve | ||
230 | } | ||
231 | '' | ||
232 | ]; | ||
233 | }; | ||
234 | networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ]; | ||
235 | system.activationScripts.dovecot = { | ||
236 | deps = [ "users" ]; | ||
237 | text ='' | ||
238 | install -m 0755 -o vhost -g vhost -d /var/lib/vhost | ||
239 | ''; | ||
240 | }; | ||
241 | |||
242 | security.acme.certs."mail" = { | ||
243 | postRun = '' | ||
244 | systemctl restart dovecot2.service | ||
245 | ''; | ||
246 | extraDomains = { | ||
247 | "imap.immae.eu" = null; | ||
248 | "pop3.immae.eu" = null; | ||
249 | }; | ||
248 | }; | 250 | }; |
249 | }; | 251 | }; |
250 | } | 252 | } |
diff --git a/modules/private/mail/milters.nix b/modules/private/mail/milters.nix index c4bd990..123af4a 100644 --- a/modules/private/mail/milters.nix +++ b/modules/private/mail/milters.nix | |||
@@ -12,112 +12,114 @@ | |||
12 | milters sockets | 12 | milters sockets |
13 | ''; | 13 | ''; |
14 | }; | 14 | }; |
15 | config.secrets.keys = [ | 15 | config = lib.mkIf config.myServices.mail.enable { |
16 | { | 16 | secrets.keys = [ |
17 | dest = "opendkim/eldiron.private"; | 17 | { |
18 | user = config.services.opendkim.user; | 18 | dest = "opendkim/eldiron.private"; |
19 | group = config.services.opendkim.group; | 19 | user = config.services.opendkim.user; |
20 | permissions = "0400"; | 20 | group = config.services.opendkim.group; |
21 | text = myconfig.env.mail.dkim.eldiron.private; | 21 | permissions = "0400"; |
22 | } | 22 | text = myconfig.env.mail.dkim.eldiron.private; |
23 | { | 23 | } |
24 | dest = "opendkim/eldiron.txt"; | 24 | { |
25 | user = config.services.opendkim.user; | 25 | dest = "opendkim/eldiron.txt"; |
26 | group = config.services.opendkim.group; | 26 | user = config.services.opendkim.user; |
27 | permissions = "0444"; | 27 | group = config.services.opendkim.group; |
28 | text = '' | 28 | permissions = "0444"; |
29 | eldiron._domainkey IN TXT ${myconfig.env.mail.dkim.eldiron.public}''; | 29 | text = '' |
30 | } | 30 | eldiron._domainkey IN TXT ${myconfig.env.mail.dkim.eldiron.public}''; |
31 | { | 31 | } |
32 | dest = "opendmarc/ignore.hosts"; | 32 | { |
33 | user = config.services.opendmarc.user; | 33 | dest = "opendmarc/ignore.hosts"; |
34 | group = config.services.opendmarc.group; | 34 | user = config.services.opendmarc.user; |
35 | permissions = "0400"; | 35 | group = config.services.opendmarc.group; |
36 | text = myconfig.env.mail.dmarc.ignore_hosts; | 36 | permissions = "0400"; |
37 | } | 37 | text = myconfig.env.mail.dmarc.ignore_hosts; |
38 | ]; | 38 | } |
39 | config.users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ]; | ||
40 | config.services.opendkim = { | ||
41 | enable = true; | ||
42 | socket = "local:${config.myServices.mail.milters.sockets.opendkim}"; | ||
43 | domains = builtins.concatStringsSep "," (lib.flatten (map | ||
44 | (zone: map | ||
45 | (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}") | ||
46 | (zone.withEmail or []) | ||
47 | ) | ||
48 | myconfig.env.dns.masterZones | ||
49 | )); | ||
50 | keyPath = "${config.secrets.location}/opendkim"; | ||
51 | selector = "eldiron"; | ||
52 | configFile = pkgs.writeText "opendkim.conf" '' | ||
53 | SubDomains yes | ||
54 | UMask 002 | ||
55 | ''; | ||
56 | group = config.services.postfix.group; | ||
57 | }; | ||
58 | config.systemd.services.opendkim.preStart = lib.mkBefore '' | ||
59 | # Skip the prestart script as keys are handled in secrets | ||
60 | exit 0 | ||
61 | ''; | ||
62 | config.services.filesWatcher.opendkim = { | ||
63 | restart = true; | ||
64 | paths = [ | ||
65 | config.secrets.fullPaths."opendkim/eldiron.private" | ||
66 | ]; | 39 | ]; |
67 | }; | 40 | users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ]; |
68 | 41 | services.opendkim = { | |
69 | config.users.users."${config.services.opendmarc.user}".extraGroups = [ "keys" ]; | 42 | enable = true; |
70 | config.services.opendmarc = { | 43 | socket = "local:${config.myServices.mail.milters.sockets.opendkim}"; |
71 | enable = true; | 44 | domains = builtins.concatStringsSep "," (lib.flatten (map |
72 | socket = "local:${config.myServices.mail.milters.sockets.opendmarc}"; | 45 | (zone: map |
73 | configFile = pkgs.writeText "opendmarc.conf" '' | 46 | (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}") |
74 | AuthservID HOSTNAME | 47 | (zone.withEmail or []) |
75 | FailureReports false | 48 | ) |
76 | FailureReportsBcc postmaster@localhost.immae.eu | 49 | myconfig.env.dns.masterZones |
77 | FailureReportsOnNone true | 50 | )); |
78 | FailureReportsSentBy postmaster@immae.eu | 51 | keyPath = "${config.secrets.location}/opendkim"; |
79 | IgnoreAuthenticatedClients true | 52 | selector = "eldiron"; |
80 | IgnoreHosts ${config.secrets.fullPaths."opendmarc/ignore.hosts"} | 53 | configFile = pkgs.writeText "opendkim.conf" '' |
81 | SoftwareHeader true | 54 | SubDomains yes |
82 | SPFSelfValidate true | 55 | UMask 002 |
83 | TrustedAuthservIDs HOSTNAME, immae.eu, nef2.ens.fr | 56 | ''; |
84 | UMask 002 | 57 | group = config.services.postfix.group; |
58 | }; | ||
59 | systemd.services.opendkim.preStart = lib.mkBefore '' | ||
60 | # Skip the prestart script as keys are handled in secrets | ||
61 | exit 0 | ||
85 | ''; | 62 | ''; |
86 | group = config.services.postfix.group; | 63 | services.filesWatcher.opendkim = { |
87 | }; | 64 | restart = true; |
88 | config.services.filesWatcher.opendmarc = { | 65 | paths = [ |
89 | restart = true; | 66 | config.secrets.fullPaths."opendkim/eldiron.private" |
90 | paths = [ | 67 | ]; |
91 | config.secrets.fullPaths."opendmarc/ignore.hosts" | 68 | }; |
92 | ]; | 69 | |
93 | }; | 70 | users.users."${config.services.opendmarc.user}".extraGroups = [ "keys" ]; |
71 | services.opendmarc = { | ||
72 | enable = true; | ||
73 | socket = "local:${config.myServices.mail.milters.sockets.opendmarc}"; | ||
74 | configFile = pkgs.writeText "opendmarc.conf" '' | ||
75 | AuthservID HOSTNAME | ||
76 | FailureReports false | ||
77 | FailureReportsBcc postmaster@localhost.immae.eu | ||
78 | FailureReportsOnNone true | ||
79 | FailureReportsSentBy postmaster@immae.eu | ||
80 | IgnoreAuthenticatedClients true | ||
81 | IgnoreHosts ${config.secrets.fullPaths."opendmarc/ignore.hosts"} | ||
82 | SoftwareHeader true | ||
83 | SPFSelfValidate true | ||
84 | TrustedAuthservIDs HOSTNAME, immae.eu, nef2.ens.fr | ||
85 | UMask 002 | ||
86 | ''; | ||
87 | group = config.services.postfix.group; | ||
88 | }; | ||
89 | services.filesWatcher.opendmarc = { | ||
90 | restart = true; | ||
91 | paths = [ | ||
92 | config.secrets.fullPaths."opendmarc/ignore.hosts" | ||
93 | ]; | ||
94 | }; | ||
94 | 95 | ||
95 | config.services.openarc = { | 96 | services.openarc = { |
96 | enable = true; | 97 | enable = true; |
97 | user = "opendkim"; | 98 | user = "opendkim"; |
98 | socket = "local:${config.myServices.mail.milters.sockets.openarc}"; | 99 | socket = "local:${config.myServices.mail.milters.sockets.openarc}"; |
99 | group = config.services.postfix.group; | 100 | group = config.services.postfix.group; |
100 | configFile = pkgs.writeText "openarc.conf" '' | 101 | configFile = pkgs.writeText "openarc.conf" '' |
101 | AuthservID mail.immae.eu | 102 | AuthservID mail.immae.eu |
102 | Domain mail.immae.eu | 103 | Domain mail.immae.eu |
103 | KeyFile ${config.secrets.fullPaths."opendkim/eldiron.private"} | 104 | KeyFile ${config.secrets.fullPaths."opendkim/eldiron.private"} |
104 | Mode sv | 105 | Mode sv |
105 | Selector eldiron | 106 | Selector eldiron |
106 | SoftwareHeader yes | 107 | SoftwareHeader yes |
107 | Syslog Yes | 108 | Syslog Yes |
109 | ''; | ||
110 | }; | ||
111 | systemd.services.openarc.postStart = lib.optionalString | ||
112 | (lib.strings.hasPrefix "local:" config.services.openarc.socket) '' | ||
113 | while [ ! -S ${lib.strings.removePrefix "local:" config.services.openarc.socket} ]; do | ||
114 | sleep 0.5 | ||
115 | done | ||
116 | chmod g+w ${lib.strings.removePrefix "local:" config.services.openarc.socket} | ||
108 | ''; | 117 | ''; |
109 | }; | 118 | services.filesWatcher.openarc = { |
110 | config.systemd.services.openarc.postStart = lib.optionalString | 119 | restart = true; |
111 | (lib.strings.hasPrefix "local:" config.services.openarc.socket) '' | 120 | paths = [ |
112 | while [ ! -S ${lib.strings.removePrefix "local:" config.services.openarc.socket} ]; do | 121 | config.secrets.fullPaths."opendkim/eldiron.private" |
113 | sleep 0.5 | 122 | ]; |
114 | done | 123 | }; |
115 | chmod g+w ${lib.strings.removePrefix "local:" config.services.openarc.socket} | ||
116 | ''; | ||
117 | config.services.filesWatcher.openarc = { | ||
118 | restart = true; | ||
119 | paths = [ | ||
120 | config.secrets.fullPaths."opendkim/eldiron.private" | ||
121 | ]; | ||
122 | }; | 124 | }; |
123 | } | 125 | } |
diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix index edfd196..9fdc7bd 100644 --- a/modules/private/mail/postfix.nix +++ b/modules/private/mail/postfix.nix | |||
@@ -1,267 +1,269 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | 1 | { lib, pkgs, config, myconfig, ... }: |
2 | { | 2 | { |
3 | config.services.backup.profiles.mail.excludeFile = '' | 3 | config = lib.mkIf config.myServices.mail.enable { |
4 | + /var/lib/postfix | 4 | services.backup.profiles.mail.excludeFile = '' |
5 | ''; | 5 | + /var/lib/postfix |
6 | config.secrets.keys = [ | 6 | ''; |
7 | { | 7 | secrets.keys = [ |
8 | dest = "postfix/mysql_alias_maps"; | 8 | { |
9 | user = config.services.postfix.user; | 9 | dest = "postfix/mysql_alias_maps"; |
10 | group = config.services.postfix.group; | 10 | user = config.services.postfix.user; |
11 | permissions = "0440"; | 11 | group = config.services.postfix.group; |
12 | text = '' | 12 | permissions = "0440"; |
13 | # We need to specify that option to trigger ssl connection | 13 | text = '' |
14 | tls_ciphers = TLSv1.2 | 14 | # We need to specify that option to trigger ssl connection |
15 | user = ${myconfig.env.mail.postfix.mysql.user} | 15 | tls_ciphers = TLSv1.2 |
16 | password = ${myconfig.env.mail.postfix.mysql.password} | 16 | user = ${myconfig.env.mail.postfix.mysql.user} |
17 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | 17 | password = ${myconfig.env.mail.postfix.mysql.password} |
18 | dbname = ${myconfig.env.mail.postfix.mysql.database} | 18 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} |
19 | query = SELECT DISTINCT destination | 19 | dbname = ${myconfig.env.mail.postfix.mysql.database} |
20 | FROM forwardings_merge | 20 | query = SELECT DISTINCT destination |
21 | WHERE | 21 | FROM forwardings_merge |
22 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | 22 | WHERE |
23 | AND active = 1 | 23 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) |
24 | AND '%s' NOT IN | 24 | AND active = 1 |
25 | ( | 25 | AND '%s' NOT IN |
26 | SELECT source | 26 | ( |
27 | SELECT source | ||
28 | FROM forwardings_blacklisted | ||
29 | WHERE source = '%s' | ||
30 | ) UNION | ||
31 | SELECT 'devnull@immae.eu' | ||
27 | FROM forwardings_blacklisted | 32 | FROM forwardings_blacklisted |
28 | WHERE source = '%s' | 33 | WHERE source = '%s' |
29 | ) UNION | 34 | ''; |
30 | SELECT 'devnull@immae.eu' | 35 | } |
31 | FROM forwardings_blacklisted | 36 | { |
32 | WHERE source = '%s' | 37 | dest = "postfix/mysql_mailbox_maps"; |
33 | ''; | 38 | user = config.services.postfix.user; |
34 | } | 39 | group = config.services.postfix.group; |
35 | { | 40 | permissions = "0440"; |
36 | dest = "postfix/mysql_mailbox_maps"; | 41 | text = '' |
37 | user = config.services.postfix.user; | 42 | # We need to specify that option to trigger ssl connection |
38 | group = config.services.postfix.group; | 43 | tls_ciphers = TLSv1.2 |
39 | permissions = "0440"; | 44 | user = ${myconfig.env.mail.postfix.mysql.user} |
40 | text = '' | 45 | password = ${myconfig.env.mail.postfix.mysql.password} |
41 | # We need to specify that option to trigger ssl connection | 46 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} |
42 | tls_ciphers = TLSv1.2 | 47 | dbname = ${myconfig.env.mail.postfix.mysql.database} |
43 | user = ${myconfig.env.mail.postfix.mysql.user} | 48 | result_format = /%d/%u |
44 | password = ${myconfig.env.mail.postfix.mysql.password} | 49 | query = SELECT DISTINCT '%s' |
45 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | 50 | FROM mailboxes |
46 | dbname = ${myconfig.env.mail.postfix.mysql.database} | 51 | WHERE active = 1 |
47 | result_format = /%d/%u | 52 | AND ( |
48 | query = SELECT DISTINCT '%s' | 53 | (domain = '%d' AND user = '%u' AND regex = 0) |
49 | FROM mailboxes | 54 | OR ( |
50 | WHERE active = 1 | 55 | regex = 1 |
51 | AND ( | 56 | AND '%d' REGEXP CONCAT('^',domain,'$') |
52 | (domain = '%d' AND user = '%u' AND regex = 0) | 57 | AND '%u' REGEXP CONCAT('^',user,'$') |
53 | OR ( | 58 | ) |
54 | regex = 1 | ||
55 | AND '%d' REGEXP CONCAT('^',domain,'$') | ||
56 | AND '%u' REGEXP CONCAT('^',user,'$') | ||
57 | ) | 59 | ) |
58 | ) | 60 | LIMIT 1 |
59 | LIMIT 1 | ||
60 | ''; | ||
61 | } | ||
62 | { | ||
63 | dest = "postfix/mysql_sender_login_maps"; | ||
64 | user = config.services.postfix.user; | ||
65 | group = config.services.postfix.group; | ||
66 | permissions = "0440"; | ||
67 | text = '' | ||
68 | # We need to specify that option to trigger ssl connection | ||
69 | tls_ciphers = TLSv1.2 | ||
70 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
71 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
72 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
73 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
74 | query = SELECT DISTINCT destination | ||
75 | FROM forwardings_merge | ||
76 | WHERE | ||
77 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
78 | AND active = 1 | ||
79 | UNION SELECT '%s' AS destination | ||
80 | ''; | 61 | ''; |
81 | } | 62 | } |
82 | ]; | 63 | { |
64 | dest = "postfix/mysql_sender_login_maps"; | ||
65 | user = config.services.postfix.user; | ||
66 | group = config.services.postfix.group; | ||
67 | permissions = "0440"; | ||
68 | text = '' | ||
69 | # We need to specify that option to trigger ssl connection | ||
70 | tls_ciphers = TLSv1.2 | ||
71 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
72 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
73 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
74 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
75 | query = SELECT DISTINCT destination | ||
76 | FROM forwardings_merge | ||
77 | WHERE | ||
78 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
79 | AND active = 1 | ||
80 | UNION SELECT '%s' AS destination | ||
81 | ''; | ||
82 | } | ||
83 | ]; | ||
83 | 84 | ||
84 | config.networking.firewall.allowedTCPPorts = [ 25 465 587 ]; | 85 | networking.firewall.allowedTCPPorts = [ 25 465 587 ]; |
85 | 86 | ||
86 | config.nixpkgs.overlays = [ (self: super: { | 87 | nixpkgs.overlays = [ (self: super: { |
87 | postfix = super.postfix.override { withMySQL = true; }; | 88 | postfix = super.postfix.override { withMySQL = true; }; |
88 | }) ]; | 89 | }) ]; |
89 | config.users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; | 90 | users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; |
90 | config.services.filesWatcher.postfix = { | 91 | services.filesWatcher.postfix = { |
91 | restart = true; | 92 | restart = true; |
92 | paths = [ | 93 | paths = [ |
93 | config.secrets.fullPaths."postfix/mysql_alias_maps" | 94 | config.secrets.fullPaths."postfix/mysql_alias_maps" |
94 | config.secrets.fullPaths."postfix/mysql_mailbox_maps" | 95 | config.secrets.fullPaths."postfix/mysql_mailbox_maps" |
95 | config.secrets.fullPaths."postfix/mysql_sender_login_maps" | 96 | config.secrets.fullPaths."postfix/mysql_sender_login_maps" |
96 | ]; | 97 | ]; |
97 | }; | 98 | }; |
98 | config.services.postfix = { | 99 | services.postfix = { |
99 | mapFiles = let | 100 | mapFiles = let |
100 | recipient_maps = let | 101 | recipient_maps = let |
101 | name = n: i: "relay_${n}_${toString i}"; | 102 | name = n: i: "relay_${n}_${toString i}"; |
102 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( | 103 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( |
103 | if m.type == "hash" | 104 | if m.type == "hash" |
104 | then pkgs.writeText (name n i) m.content | 105 | then pkgs.writeText (name n i) m.content |
105 | else null | ||
106 | ); | ||
107 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; | ||
108 | in lib.attrsets.filterAttrs (k: v: v != null) ( | ||
109 | lib.attrsets.listToAttrs (lib.flatten ( | ||
110 | lib.attrsets.mapAttrsToList pairs myconfig.env.mail.postfix.backup_domains | ||
111 | )) | ||
112 | ); | ||
113 | relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( | ||
114 | lib.attrsets.mapAttrs' (n: v: | ||
115 | lib.attrsets.nameValuePair "recipient_access_${n}" ( | ||
116 | if lib.attrsets.hasAttr "relay_restrictions" v | ||
117 | then pkgs.writeText "recipient_access_${n}" v.relay_restrictions | ||
118 | else null | 106 | else null |
119 | ) | 107 | ); |
120 | ) myconfig.env.mail.postfix.backup_domains | 108 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; |
121 | ); | 109 | in lib.attrsets.filterAttrs (k: v: v != null) ( |
122 | in | 110 | lib.attrsets.listToAttrs (lib.flatten ( |
123 | recipient_maps // relay_restrictions; | 111 | lib.attrsets.mapAttrsToList pairs myconfig.env.mail.postfix.backup_domains |
124 | config = { | 112 | )) |
125 | ### postfix module overrides | 113 | ); |
126 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | 114 | relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( |
127 | smtp_tls_CAfile = lib.mkForce ""; | 115 | lib.attrsets.mapAttrs' (n: v: |
128 | smtp_tls_cert_file = lib.mkForce ""; | 116 | lib.attrsets.nameValuePair "recipient_access_${n}" ( |
129 | smtp_tls_key_file = lib.mkForce ""; | 117 | if lib.attrsets.hasAttr "relay_restrictions" v |
118 | then pkgs.writeText "recipient_access_${n}" v.relay_restrictions | ||
119 | else null | ||
120 | ) | ||
121 | ) myconfig.env.mail.postfix.backup_domains | ||
122 | ); | ||
123 | in | ||
124 | recipient_maps // relay_restrictions; | ||
125 | config = { | ||
126 | ### postfix module overrides | ||
127 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | ||
128 | smtp_tls_CAfile = lib.mkForce ""; | ||
129 | smtp_tls_cert_file = lib.mkForce ""; | ||
130 | smtp_tls_key_file = lib.mkForce ""; | ||
130 | 131 | ||
131 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" | 132 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" |
132 | alias_database = "\$alias_maps"; | 133 | alias_database = "\$alias_maps"; |
133 | 134 | ||
134 | ### Virtual mailboxes config | 135 | ### Virtual mailboxes config |
135 | virtual_alias_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"; | 136 | virtual_alias_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"; |
136 | virtual_mailbox_domains = myconfig.env.mail.postfix.additional_mailbox_domains | 137 | virtual_mailbox_domains = myconfig.env.mail.postfix.additional_mailbox_domains |
137 | ++ lib.remove "localhost.immae.eu" (lib.remove null (lib.flatten (map | 138 | ++ lib.remove "localhost.immae.eu" (lib.remove null (lib.flatten (map |
138 | (zone: map | 139 | (zone: map |
139 | (e: if e.receive | 140 | (e: if e.receive |
140 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" | 141 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" |
141 | else null | 142 | else null |
143 | ) | ||
144 | (zone.withEmail or []) | ||
142 | ) | 145 | ) |
143 | (zone.withEmail or []) | 146 | myconfig.env.dns.masterZones |
144 | ) | 147 | ))); |
145 | myconfig.env.dns.masterZones | 148 | virtual_mailbox_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"; |
146 | ))); | 149 | dovecot_destination_recipient_limit = "1"; |
147 | virtual_mailbox_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"; | 150 | virtual_transport = "dovecot"; |
148 | dovecot_destination_recipient_limit = "1"; | ||
149 | virtual_transport = "dovecot"; | ||
150 | 151 | ||
151 | ### Relay domains | 152 | ### Relay domains |
152 | relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) myconfig.env.mail.postfix.backup_domains); | 153 | relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) myconfig.env.mail.postfix.backup_domains); |
153 | relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: | 154 | relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: |
154 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps | 155 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps |
155 | ) myconfig.env.mail.postfix.backup_domains); | 156 | ) myconfig.env.mail.postfix.backup_domains); |
156 | smtpd_relay_restrictions = [ | 157 | smtpd_relay_restrictions = [ |
157 | "permit_mynetworks" | 158 | "permit_mynetworks" |
158 | "permit_sasl_authenticated" | 159 | "permit_sasl_authenticated" |
159 | "defer_unauth_destination" | 160 | "defer_unauth_destination" |
160 | ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: | 161 | ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: |
161 | if lib.attrsets.hasAttr "relay_restrictions" v | 162 | if lib.attrsets.hasAttr "relay_restrictions" v |
162 | then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] | 163 | then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] |
163 | else [] | 164 | else [] |
164 | ) myconfig.env.mail.postfix.backup_domains); | 165 | ) myconfig.env.mail.postfix.backup_domains); |
165 | 166 | ||
166 | ### Additional smtpd configuration | 167 | ### Additional smtpd configuration |
167 | smtpd_tls_received_header = "yes"; | 168 | smtpd_tls_received_header = "yes"; |
168 | smtpd_tls_loglevel = "1"; | 169 | smtpd_tls_loglevel = "1"; |
169 | 170 | ||
170 | ### Email sending configuration | 171 | ### Email sending configuration |
171 | smtp_tls_security_level = "may"; | 172 | smtp_tls_security_level = "may"; |
172 | smtp_tls_loglevel = "1"; | 173 | smtp_tls_loglevel = "1"; |
173 | 174 | ||
174 | ### Force ip bind for smtp | 175 | ### Force ip bind for smtp |
175 | smtp_bind_address = myconfig.env.servers.eldiron.ips.main.ip4; | 176 | smtp_bind_address = myconfig.env.servers.eldiron.ips.main.ip4; |
176 | smtp_bind_address6 = builtins.head myconfig.env.servers.eldiron.ips.main.ip6; | 177 | smtp_bind_address6 = builtins.head myconfig.env.servers.eldiron.ips.main.ip6; |
177 | 178 | ||
178 | # #Unneeded if postfix can only send e-mail from "self" domains | 179 | # #Unneeded if postfix can only send e-mail from "self" domains |
179 | # #smtp_sasl_auth_enable = "yes"; | 180 | # #smtp_sasl_auth_enable = "yes"; |
180 | # #smtp_sasl_password_maps = "hash:/etc/postfix/relay_creds"; | 181 | # #smtp_sasl_password_maps = "hash:/etc/postfix/relay_creds"; |
181 | # #smtp_sasl_security_options = "noanonymous"; | 182 | # #smtp_sasl_security_options = "noanonymous"; |
182 | # #smtp_sender_dependent_authentication = "yes"; | 183 | # #smtp_sender_dependent_authentication = "yes"; |
183 | # #sender_dependent_relayhost_maps = "hash:/etc/postfix/sender_relay"; | 184 | # #sender_dependent_relayhost_maps = "hash:/etc/postfix/sender_relay"; |
184 | 185 | ||
185 | ### opendkim, opendmarc, openarc milters | 186 | ### opendkim, opendmarc, openarc milters |
186 | non_smtpd_milters = [ | 187 | non_smtpd_milters = [ |
187 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | 188 | "unix:${config.myServices.mail.milters.sockets.opendkim}" |
188 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | 189 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" |
189 | "unix:${config.myServices.mail.milters.sockets.openarc}" | 190 | "unix:${config.myServices.mail.milters.sockets.openarc}" |
190 | ]; | 191 | ]; |
191 | smtpd_milters = [ | 192 | smtpd_milters = [ |
192 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | 193 | "unix:${config.myServices.mail.milters.sockets.opendkim}" |
193 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | 194 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" |
194 | "unix:${config.myServices.mail.milters.sockets.openarc}" | 195 | "unix:${config.myServices.mail.milters.sockets.openarc}" |
195 | ]; | ||
196 | }; | ||
197 | enable = true; | ||
198 | enableSmtp = true; | ||
199 | enableSubmission = true; | ||
200 | submissionOptions = { | ||
201 | smtpd_tls_security_level = "encrypt"; | ||
202 | smtpd_sasl_auth_enable = "yes"; | ||
203 | smtpd_tls_auth_only = "yes"; | ||
204 | smtpd_sasl_tls_security_options = "noanonymous"; | ||
205 | smtpd_sasl_type = "dovecot"; | ||
206 | smtpd_sasl_path = "private/auth"; | ||
207 | smtpd_reject_unlisted_recipient = "no"; | ||
208 | smtpd_client_restrictions = "permit_sasl_authenticated,reject"; | ||
209 | # Refuse to send e-mails with a From that is not handled | ||
210 | smtpd_sender_restrictions = | ||
211 | "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; | ||
212 | smtpd_sender_login_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"; | ||
213 | smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; | ||
214 | milter_macro_daemon_name = "ORIGINATING"; | ||
215 | smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; | ||
216 | }; | ||
217 | # FIXME: Mail adressed to localhost.immae.eu will still have mx-1 as | ||
218 | # prioritized MX, which provokes "mail for localhost.immae.eu loops | ||
219 | # back to myself" errors. This transport entry forces to push | ||
220 | # e-mails to its right destination. | ||
221 | transport = '' | ||
222 | localhost.immae.eu smtp:[immae.eu]:25 | ||
223 | ''; | ||
224 | destination = ["localhost"]; | ||
225 | # This needs to reverse DNS | ||
226 | hostname = "eldiron.immae.eu"; | ||
227 | setSendmail = true; | ||
228 | sslCert = "/var/lib/acme/mail/fullchain.pem"; | ||
229 | sslKey = "/var/lib/acme/mail/key.pem"; | ||
230 | recipientDelimiter = "+"; | ||
231 | masterConfig = { | ||
232 | submissions = { | ||
233 | type = "inet"; | ||
234 | private = false; | ||
235 | command = "smtpd"; | ||
236 | args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let | ||
237 | mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ]; | ||
238 | in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions) | ||
239 | ); | ||
240 | }; | ||
241 | dovecot = { | ||
242 | type = "unix"; | ||
243 | privileged = true; | ||
244 | chroot = false; | ||
245 | command = "pipe"; | ||
246 | args = let | ||
247 | # rspamd could be used as a milter, but then it cannot apply | ||
248 | # its checks "per user" (milter is not yet dispatched to | ||
249 | # users), so we wrap dovecot-lda inside rspamc per recipient | ||
250 | # here. | ||
251 | dovecot_exe = "${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f \${sender} -a \${original_recipient} -d \${user}@\${nexthop}"; | ||
252 | in [ | ||
253 | "flags=DRhu" "user=vhost:vhost" | ||
254 | "argv=${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d \${user}@\${nexthop} --mime --exec {${dovecot_exe}}" | ||
255 | ]; | 196 | ]; |
256 | }; | 197 | }; |
198 | enable = true; | ||
199 | enableSmtp = true; | ||
200 | enableSubmission = true; | ||
201 | submissionOptions = { | ||
202 | smtpd_tls_security_level = "encrypt"; | ||
203 | smtpd_sasl_auth_enable = "yes"; | ||
204 | smtpd_tls_auth_only = "yes"; | ||
205 | smtpd_sasl_tls_security_options = "noanonymous"; | ||
206 | smtpd_sasl_type = "dovecot"; | ||
207 | smtpd_sasl_path = "private/auth"; | ||
208 | smtpd_reject_unlisted_recipient = "no"; | ||
209 | smtpd_client_restrictions = "permit_sasl_authenticated,reject"; | ||
210 | # Refuse to send e-mails with a From that is not handled | ||
211 | smtpd_sender_restrictions = | ||
212 | "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; | ||
213 | smtpd_sender_login_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"; | ||
214 | smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; | ||
215 | milter_macro_daemon_name = "ORIGINATING"; | ||
216 | smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; | ||
217 | }; | ||
218 | # FIXME: Mail adressed to localhost.immae.eu will still have mx-1 as | ||
219 | # prioritized MX, which provokes "mail for localhost.immae.eu loops | ||
220 | # back to myself" errors. This transport entry forces to push | ||
221 | # e-mails to its right destination. | ||
222 | transport = '' | ||
223 | localhost.immae.eu smtp:[immae.eu]:25 | ||
224 | ''; | ||
225 | destination = ["localhost"]; | ||
226 | # This needs to reverse DNS | ||
227 | hostname = "eldiron.immae.eu"; | ||
228 | setSendmail = true; | ||
229 | sslCert = "/var/lib/acme/mail/fullchain.pem"; | ||
230 | sslKey = "/var/lib/acme/mail/key.pem"; | ||
231 | recipientDelimiter = "+"; | ||
232 | masterConfig = { | ||
233 | submissions = { | ||
234 | type = "inet"; | ||
235 | private = false; | ||
236 | command = "smtpd"; | ||
237 | args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let | ||
238 | mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ]; | ||
239 | in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions) | ||
240 | ); | ||
241 | }; | ||
242 | dovecot = { | ||
243 | type = "unix"; | ||
244 | privileged = true; | ||
245 | chroot = false; | ||
246 | command = "pipe"; | ||
247 | args = let | ||
248 | # rspamd could be used as a milter, but then it cannot apply | ||
249 | # its checks "per user" (milter is not yet dispatched to | ||
250 | # users), so we wrap dovecot-lda inside rspamc per recipient | ||
251 | # here. | ||
252 | dovecot_exe = "${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f \${sender} -a \${original_recipient} -d \${user}@\${nexthop}"; | ||
253 | in [ | ||
254 | "flags=DRhu" "user=vhost:vhost" | ||
255 | "argv=${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d \${user}@\${nexthop} --mime --exec {${dovecot_exe}}" | ||
256 | ]; | ||
257 | }; | ||
258 | }; | ||
257 | }; | 259 | }; |
258 | }; | 260 | security.acme.certs."mail" = { |
259 | config.security.acme.certs."mail" = { | 261 | postRun = '' |
260 | postRun = '' | 262 | systemctl restart postfix.service |
261 | systemctl restart postfix.service | 263 | ''; |
262 | ''; | 264 | extraDomains = { |
263 | extraDomains = { | 265 | "smtp.immae.eu" = null; |
264 | "smtp.immae.eu" = null; | 266 | }; |
265 | }; | 267 | }; |
266 | }; | 268 | }; |
267 | } | 269 | } |
diff --git a/modules/private/mail/rspamd.nix b/modules/private/mail/rspamd.nix index af3541f..5e0a239 100644 --- a/modules/private/mail/rspamd.nix +++ b/modules/private/mail/rspamd.nix | |||
@@ -10,78 +10,80 @@ | |||
10 | rspamd sockets | 10 | rspamd sockets |
11 | ''; | 11 | ''; |
12 | }; | 12 | }; |
13 | config.services.backup.profiles.mail.excludeFile = '' | 13 | config = lib.mkIf config.myServices.mail.enable { |
14 | + /var/lib/rspamd | 14 | services.backup.profiles.mail.excludeFile = '' |
15 | ''; | 15 | + /var/lib/rspamd |
16 | config.services.cron.systemCronJobs = let | ||
17 | cron_script = pkgs.runCommand "cron_script" { | ||
18 | buildInputs = [ pkgs.makeWrapper ]; | ||
19 | } '' | ||
20 | mkdir -p $out | ||
21 | cp ${./scan_reported_mails} $out/scan_reported_mails | ||
22 | patchShebangs $out | ||
23 | for i in $out/*; do | ||
24 | wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils pkgs.rspamd pkgs.flock ]} | ||
25 | done | ||
26 | ''; | 16 | ''; |
27 | in | 17 | services.cron.systemCronJobs = let |
28 | [ "*/20 * * * * vhost ${cron_script}/scan_reported_mails" ]; | 18 | cron_script = pkgs.runCommand "cron_script" { |
29 | 19 | buildInputs = [ pkgs.makeWrapper ]; | |
30 | config.services.rspamd = { | 20 | } '' |
31 | enable = true; | 21 | mkdir -p $out |
32 | debug = true; | 22 | cp ${./scan_reported_mails} $out/scan_reported_mails |
33 | overrides = { | 23 | patchShebangs $out |
34 | "actions.conf".text = '' | 24 | for i in $out/*; do |
35 | reject = null; | 25 | wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils pkgs.rspamd pkgs.flock ]} |
36 | add_header = 6; | 26 | done |
37 | greylist = null; | ||
38 | ''; | 27 | ''; |
39 | "milter_headers.conf".text = '' | 28 | in |
40 | extended_spam_headers = true; | 29 | [ "*/20 * * * * vhost ${cron_script}/scan_reported_mails" ]; |
41 | ''; | 30 | |
42 | }; | 31 | services.rspamd = { |
43 | locals = { | 32 | enable = true; |
44 | "redis.conf".text = '' | 33 | debug = true; |
45 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; | 34 | overrides = { |
46 | db = "${myconfig.env.mail.rspamd.redis.db}"; | 35 | "actions.conf".text = '' |
36 | reject = null; | ||
37 | add_header = 6; | ||
38 | greylist = null; | ||
39 | ''; | ||
40 | "milter_headers.conf".text = '' | ||
41 | extended_spam_headers = true; | ||
47 | ''; | 42 | ''; |
48 | "classifier-bayes.conf".text = '' | 43 | }; |
49 | users_enabled = true; | 44 | locals = { |
50 | backend = "redis"; | 45 | "redis.conf".text = '' |
51 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; | 46 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; |
52 | database = "${myconfig.env.mail.rspamd.redis.db}"; | 47 | db = "${myconfig.env.mail.rspamd.redis.db}"; |
53 | autolearn = true; | 48 | ''; |
54 | cache { | 49 | "classifier-bayes.conf".text = '' |
50 | users_enabled = true; | ||
55 | backend = "redis"; | 51 | backend = "redis"; |
56 | } | 52 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; |
57 | new_schema = true; | 53 | database = "${myconfig.env.mail.rspamd.redis.db}"; |
58 | statfile { | 54 | autolearn = true; |
59 | BAYES_HAM { | 55 | cache { |
60 | spam = false; | 56 | backend = "redis"; |
61 | } | 57 | } |
62 | BAYES_SPAM { | 58 | new_schema = true; |
63 | spam = true; | 59 | statfile { |
60 | BAYES_HAM { | ||
61 | spam = false; | ||
62 | } | ||
63 | BAYES_SPAM { | ||
64 | spam = true; | ||
65 | } | ||
64 | } | 66 | } |
65 | } | 67 | ''; |
66 | ''; | 68 | }; |
67 | }; | 69 | workers = { |
68 | workers = { | 70 | controller = { |
69 | controller = { | 71 | extraConfig = '' |
70 | extraConfig = '' | 72 | enable_password = "${myconfig.env.mail.rspamd.write_password_hashed}"; |
71 | enable_password = "${myconfig.env.mail.rspamd.write_password_hashed}"; | 73 | password = "${myconfig.env.mail.rspamd.read_password_hashed}"; |
72 | password = "${myconfig.env.mail.rspamd.read_password_hashed}"; | 74 | ''; |
73 | ''; | 75 | bindSockets = [ { |
74 | bindSockets = [ { | 76 | socket = config.myServices.mail.rspamd.sockets.worker-controller; |
75 | socket = config.myServices.mail.rspamd.sockets.worker-controller; | 77 | mode = "0660"; |
76 | mode = "0660"; | 78 | owner = config.services.rspamd.user; |
77 | owner = config.services.rspamd.user; | 79 | group = "vhost"; |
78 | group = "vhost"; | 80 | } ]; |
79 | } ]; | 81 | }; |
82 | }; | ||
83 | postfix = { | ||
84 | enable = true; | ||
85 | config = {}; | ||
80 | }; | 86 | }; |
81 | }; | ||
82 | postfix = { | ||
83 | enable = true; | ||
84 | config = {}; | ||
85 | }; | 87 | }; |
86 | }; | 88 | }; |
87 | } | 89 | } |