diff options
Diffstat (limited to 'modules/private/mail/postfix.nix')
-rw-r--r-- | modules/private/mail/postfix.nix | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix new file mode 100644 index 0000000..53bf650 --- /dev/null +++ b/modules/private/mail/postfix.nix | |||
@@ -0,0 +1,227 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | ||
2 | { | ||
3 | config.secrets.keys = [ | ||
4 | { | ||
5 | dest = "postfix/mysql_alias_maps"; | ||
6 | user = config.services.postfix.user; | ||
7 | group = config.services.postfix.group; | ||
8 | permissions = "0440"; | ||
9 | text = '' | ||
10 | # We need to specify that option to trigger ssl connection | ||
11 | tls_ciphers = TLSv1.2 | ||
12 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
13 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
14 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
15 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
16 | query = SELECT DISTINCT destination | ||
17 | FROM forwardings_merge | ||
18 | WHERE | ||
19 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
20 | AND active = 1 | ||
21 | AND '%s' NOT IN | ||
22 | ( | ||
23 | SELECT source | ||
24 | FROM forwardings_blacklisted | ||
25 | WHERE source = '%s' | ||
26 | ) UNION | ||
27 | SELECT 'devnull@immae.eu' | ||
28 | FROM forwardings_blacklisted | ||
29 | WHERE source = '%s' | ||
30 | ''; | ||
31 | } | ||
32 | { | ||
33 | dest = "postfix/mysql_mailbox_maps"; | ||
34 | user = config.services.postfix.user; | ||
35 | group = config.services.postfix.group; | ||
36 | permissions = "0440"; | ||
37 | text = '' | ||
38 | # We need to specify that option to trigger ssl connection | ||
39 | tls_ciphers = TLSv1.2 | ||
40 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
41 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
42 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
43 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
44 | result_format = /%d/%u | ||
45 | query = SELECT DISTINCT '%s' | ||
46 | FROM mailboxes | ||
47 | WHERE active = 1 | ||
48 | AND ( | ||
49 | (domain = '%d' AND user = '%u' AND regex = 0) | ||
50 | OR ( | ||
51 | regex = 1 | ||
52 | AND '%d' REGEXP CONCAT('^',domain,'$') | ||
53 | AND '%u' REGEXP CONCAT('^',user,'$') | ||
54 | ) | ||
55 | ) | ||
56 | LIMIT 1 | ||
57 | ''; | ||
58 | } | ||
59 | { | ||
60 | dest = "postfix/mysql_sender_login_maps"; | ||
61 | user = config.services.postfix.user; | ||
62 | group = config.services.postfix.group; | ||
63 | permissions = "0440"; | ||
64 | text = '' | ||
65 | # We need to specify that option to trigger ssl connection | ||
66 | tls_ciphers = TLSv1.2 | ||
67 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
68 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
69 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
70 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
71 | query = SELECT DISTINCT destination | ||
72 | FROM forwardings_merge | ||
73 | WHERE | ||
74 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
75 | AND active = 1 | ||
76 | ''; | ||
77 | } | ||
78 | ]; | ||
79 | |||
80 | config.networking.firewall.allowedTCPPorts = [ 25 587 ]; | ||
81 | |||
82 | config.nixpkgs.overlays = [ (self: super: { | ||
83 | postfix = super.postfix.override { withMySQL = true; }; | ||
84 | }) ]; | ||
85 | config.users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; | ||
86 | config.services.filesWatcher.postfix = { | ||
87 | restart = true; | ||
88 | paths = [ | ||
89 | config.secrets.fullPaths."postfix/mysql_alias_maps" | ||
90 | config.secrets.fullPaths."postfix/mysql_mailbox_maps" | ||
91 | config.secrets.fullPaths."postfix/mysql_sender_login_maps" | ||
92 | ]; | ||
93 | }; | ||
94 | config.services.postfix = { | ||
95 | mapFiles = let | ||
96 | name = n: i: "relay_${n}_${toString i}"; | ||
97 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( | ||
98 | if m.type == "hash" | ||
99 | then pkgs.writeText (name n i) m.content | ||
100 | else null | ||
101 | ); | ||
102 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; | ||
103 | in | ||
104 | lib.attrsets.filterAttrs (k: v: v != null) ( | ||
105 | lib.attrsets.listToAttrs (lib.flatten ( | ||
106 | lib.attrsets.mapAttrsToList pairs myconfig.env.mail.postfix.backup_domains | ||
107 | )) | ||
108 | ); | ||
109 | config = { | ||
110 | ### postfix module overrides | ||
111 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | ||
112 | smtp_tls_CAfile = lib.mkForce ""; | ||
113 | smtp_tls_cert_file = lib.mkForce ""; | ||
114 | smtp_tls_key_file = lib.mkForce ""; | ||
115 | |||
116 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" | ||
117 | alias_database = "\$alias_maps"; | ||
118 | |||
119 | ### Virtual mailboxes config | ||
120 | virtual_alias_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"; | ||
121 | virtual_mailbox_domains = myconfig.env.mail.postfix.additional_mailbox_domains | ||
122 | ++ lib.remove "localhost.immae.eu" (lib.remove null (lib.flatten (map | ||
123 | (zone: map | ||
124 | (e: if e.receive | ||
125 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" | ||
126 | else null | ||
127 | ) | ||
128 | (zone.withEmail or []) | ||
129 | ) | ||
130 | myconfig.env.dns.masterZones | ||
131 | ))); | ||
132 | virtual_mailbox_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"; | ||
133 | dovecot_destination_recipient_limit = "1"; | ||
134 | virtual_transport = "dovecot"; | ||
135 | |||
136 | ### Relay domains | ||
137 | relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) myconfig.env.mail.postfix.backup_domains); | ||
138 | relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: | ||
139 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps | ||
140 | ) myconfig.env.mail.postfix.backup_domains); | ||
141 | |||
142 | ### Additional smtpd configuration | ||
143 | smtpd_tls_received_header = "yes"; | ||
144 | smtpd_tls_loglevel = "1"; | ||
145 | |||
146 | ### Email sending configuration | ||
147 | smtp_tls_security_level = "may"; | ||
148 | smtp_tls_loglevel = "1"; | ||
149 | |||
150 | ### Force ip bind for smtp | ||
151 | smtp_bind_address = myconfig.env.servers.eldiron.ips.main.ip4; | ||
152 | smtp_bind_address6 = builtins.head myconfig.env.servers.eldiron.ips.main.ip6; | ||
153 | |||
154 | # #Unneeded if postfix can only send e-mail from "self" domains | ||
155 | # #smtp_sasl_auth_enable = "yes"; | ||
156 | # #smtp_sasl_password_maps = "hash:/etc/postfix/relay_creds"; | ||
157 | # #smtp_sasl_security_options = "noanonymous"; | ||
158 | # #smtp_sender_dependent_authentication = "yes"; | ||
159 | # #sender_dependent_relayhost_maps = "hash:/etc/postfix/sender_relay"; | ||
160 | |||
161 | ### opendkim, opendmarc, openarc milters | ||
162 | non_smtpd_milters = [ | ||
163 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | ||
164 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | ||
165 | "unix:${config.myServices.mail.milters.sockets.openarc}" | ||
166 | ]; | ||
167 | smtpd_milters = [ | ||
168 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | ||
169 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | ||
170 | "unix:${config.myServices.mail.milters.sockets.openarc}" | ||
171 | ]; | ||
172 | }; | ||
173 | enable = true; | ||
174 | enableSmtp = true; | ||
175 | enableSubmission = true; | ||
176 | submissionOptions = { | ||
177 | smtpd_tls_security_level = "encrypt"; | ||
178 | smtpd_sasl_auth_enable = "yes"; | ||
179 | smtpd_tls_auth_only = "yes"; | ||
180 | smtpd_sasl_tls_security_options = "noanonymous"; | ||
181 | smtpd_sasl_type = "dovecot"; | ||
182 | smtpd_sasl_path = "private/auth"; | ||
183 | smtpd_reject_unlisted_recipient = "no"; | ||
184 | smtpd_client_restrictions = "permit_sasl_authenticated,reject"; | ||
185 | # Refuse to send e-mails with a From that is not handled | ||
186 | smtpd_sender_restrictions = | ||
187 | "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; | ||
188 | smtpd_sender_login_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"; | ||
189 | smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; | ||
190 | milter_macro_daemon_name = "ORIGINATING"; | ||
191 | smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; | ||
192 | }; | ||
193 | destination = ["localhost"]; | ||
194 | # This needs to reverse DNS | ||
195 | hostname = "eldiron.immae.eu"; | ||
196 | setSendmail = true; | ||
197 | sslCert = "/var/lib/acme/mail/fullchain.pem"; | ||
198 | sslKey = "/var/lib/acme/mail/key.pem"; | ||
199 | recipientDelimiter = "+"; | ||
200 | masterConfig = { | ||
201 | dovecot = { | ||
202 | type = "unix"; | ||
203 | privileged = true; | ||
204 | chroot = false; | ||
205 | command = "pipe"; | ||
206 | args = let | ||
207 | # rspamd could be used as a milter, but then it cannot apply | ||
208 | # its checks "per user" (milter is not yet dispatched to | ||
209 | # users), so we wrap dovecot-lda inside rspamc per recipient | ||
210 | # here. | ||
211 | dovecot_exe = "${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f \${sender} -a \${original_recipient} -d \${user}@\${nexthop}"; | ||
212 | in [ | ||
213 | "flags=DRhu" "user=vhost:vhost" | ||
214 | "argv=${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d \${user}@\${nexthop} --mime --exec {${dovecot_exe}}" | ||
215 | ]; | ||
216 | }; | ||
217 | }; | ||
218 | }; | ||
219 | config.security.acme.certs."mail" = { | ||
220 | postRun = '' | ||
221 | systemctl restart postfix.service | ||
222 | ''; | ||
223 | extraDomains = { | ||
224 | "smtp.immae.eu" = null; | ||
225 | }; | ||
226 | }; | ||
227 | } | ||