aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2019-06-23 21:04:55 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2019-06-29 15:14:43 +0200
commita929614f94d11a4f397e72e74f38b3212c24cdee (patch)
treea81b3cee45586d685c1b7c6e5c39372f203aa00d
parent53fa9f9e7d87835d6137a029fe80b3195e635797 (diff)
downloadNix-a929614f94d11a4f397e72e74f38b3212c24cdee.tar.gz
Nix-a929614f94d11a4f397e72e74f38b3212c24cdee.tar.zst
Nix-a929614f94d11a4f397e72e74f38b3212c24cdee.zip
Configure mail (dovecot, postfix, spam checks)
-rw-r--r--modules/myids.nix6
-rw-r--r--modules/private/default.nix7
-rw-r--r--modules/private/dns.nix2
-rw-r--r--modules/private/mail/default.nix12
-rw-r--r--modules/private/mail/dovecot.nix255
-rw-r--r--modules/private/mail/milters.nix (renamed from modules/private/mail.nix)26
-rw-r--r--modules/private/mail/postfix.nix227
-rw-r--r--modules/private/mail/rspamd.nix84
-rwxr-xr-xmodules/private/mail/scan_reported_mails21
-rwxr-xr-xmodules/private/mail/sieve_bin/imapsieve_copy8
-rw-r--r--modules/private/mail/sieve_scripts/report_ham.sieve11
-rw-r--r--modules/private/mail/sieve_scripts/report_spam.sieve3
-rw-r--r--modules/private/websites/tools/tools/roundcubemail.nix12
-rw-r--r--pkgs/default.nix6
-rw-r--r--pkgs/dovecot/plugins/deleted_to_trash/default.nix21
-rw-r--r--pkgs/dovecot/plugins/deleted_to_trash/dovecot-deleted_to_trash.json15
-rw-r--r--pkgs/dovecot/plugins/deleted_to_trash/fix_mbox.patch12
-rw-r--r--pkgs/dovecot/plugins/fts_xapian/default.nix14
-rw-r--r--pkgs/dovecot/plugins/fts_xapian/fts-xapian.json15
19 files changed, 737 insertions, 20 deletions
diff --git a/modules/myids.nix b/modules/myids.nix
index 7ec9c0e..e949ca7 100644
--- a/modules/myids.nix
+++ b/modules/myids.nix
@@ -3,7 +3,8 @@
3 # Check that there is no clash with nixos/modules/misc/ids.nix 3 # Check that there is no clash with nixos/modules/misc/ids.nix
4 config = { 4 config = {
5 ids.uids = { 5 ids.uids = {
6 opendarc = 391; 6 vhost = 390;
7 openarc = 391;
7 opendmarc = 392; 8 opendmarc = 392;
8 peertube = 394; 9 peertube = 394;
9 redis = 395; 10 redis = 395;
@@ -13,7 +14,8 @@
13 mastodon = 399; 14 mastodon = 399;
14 }; 15 };
15 ids.gids = { 16 ids.gids = {
16 opendarc = 392; 17 vhost = 390;
18 openarc = 391;
17 opendmarc = 392; 19 opendmarc = 392;
18 peertube = 394; 20 peertube = 394;
19 redis = 395; 21 redis = 395;
diff --git a/modules/private/default.nix b/modules/private/default.nix
index 894efb7..026e69d 100644
--- a/modules/private/default.nix
+++ b/modules/private/default.nix
@@ -47,6 +47,12 @@ set = {
47 peertubeTool = ./websites/tools/peertube; 47 peertubeTool = ./websites/tools/peertube;
48 toolsTool = ./websites/tools/tools; 48 toolsTool = ./websites/tools/tools;
49 49
50 mail = ./mail;
51 mailMilters = ./mail/milters.nix;
52 mailPostfix = ./mail/postfix.nix;
53 mailDovecot = ./mail/dovecot.nix;
54 mailRspamd = ./mail/rspamd.nix;
55
50 buildbot = ./buildbot; 56 buildbot = ./buildbot;
51 certificates = ./certificates.nix; 57 certificates = ./certificates.nix;
52 gitolite = ./gitolite; 58 gitolite = ./gitolite;
@@ -55,7 +61,6 @@ set = {
55 tasks = ./tasks; 61 tasks = ./tasks;
56 dns = ./dns.nix; 62 dns = ./dns.nix;
57 ftp = ./ftp.nix; 63 ftp = ./ftp.nix;
58 mail = ./mail.nix;
59 mpd = ./mpd.nix; 64 mpd = ./mpd.nix;
60 ssh = ./ssh; 65 ssh = ./ssh;
61 66
diff --git a/modules/private/dns.nix b/modules/private/dns.nix
index f12f982..6647c14 100644
--- a/modules/private/dns.nix
+++ b/modules/private/dns.nix
@@ -106,7 +106,7 @@
106 '' 106 ''
107 ; ------------------ mail: ${n} --------------------------- 107 ; ------------------ mail: ${n} ---------------------------
108 ${n} IN MX 10 mail.${conf.name}. 108 ${n} IN MX 10 mail.${conf.name}.
109 ;${n} IN MX 50 mx-1.${conf.name}. 109 ${n} IN MX 50 mx-1.${conf.name}.
110 110
111 ; https://tools.ietf.org/html/rfc6186 111 ; https://tools.ietf.org/html/rfc6186
112 _submission._tcp${suffix} SRV 0 1 587 smtp.immae.eu. 112 _submission._tcp${suffix} SRV 0 1 587 smtp.immae.eu.
diff --git a/modules/private/mail/default.nix b/modules/private/mail/default.nix
new file mode 100644
index 0000000..ad2c684
--- /dev/null
+++ b/modules/private/mail/default.nix
@@ -0,0 +1,12 @@
1{ lib, pkgs, config, myconfig, ... }:
2{
3 config.security.acme.certs."mail" = config.services.myCertificates.certConfig // {
4 domain = "eldiron.immae.eu";
5 extraDomains = let
6 zonesWithMx = builtins.filter (zone:
7 lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0
8 ) myconfig.env.dns.masterZones;
9 mxs = map (zone: "mx-1.${zone.name}") zonesWithMx;
10 in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs);
11 };
12}
diff --git a/modules/private/mail/dovecot.nix b/modules/private/mail/dovecot.nix
new file mode 100644
index 0000000..d757f59
--- /dev/null
+++ b/modules/private/mail/dovecot.nix
@@ -0,0 +1,255 @@
1{ lib, pkgs, config, myconfig, ... }:
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.secrets.keys = [
16 {
17 dest = "dovecot/ldap";
18 user = config.services.dovecot2.user;
19 group = config.services.dovecot2.group;
20 permissions = "0400";
21 text = ''
22 hosts = ${myconfig.env.mail.dovecot.ldap.host}
23 tls = yes
24
25 dn = ${myconfig.env.mail.dovecot.ldap.dn}
26 dnpass = ${myconfig.env.mail.dovecot.ldap.password}
27
28 auth_bind = yes
29
30 ldap_version = 3
31
32 base = ${myconfig.env.mail.dovecot.ldap.base}
33 scope = subtree
34
35 user_filter = ${myconfig.env.mail.dovecot.ldap.filter}
36 pass_filter = ${myconfig.env.mail.dovecot.ldap.filter}
37
38 user_attrs = ${myconfig.env.mail.dovecot.ldap.user_attrs}
39 pass_attrs = ${myconfig.env.mail.dovecot.ldap.pass_attrs}
40 '';
41 }
42 ];
43
44 config.users.users.vhost = {
45 group = "vhost";
46 uid = config.ids.uids.vhost;
47 };
48 config.users.groups.vhost.gid = config.ids.gids.vhost;
49
50 # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous.......
51 config.services.dovecot2 = {
52 enable = true;
53 enablePAM = false;
54 enablePop3 = true;
55 enableImap = true;
56 enableLmtp = true;
57 protocols = [ "sieve" ];
58 modules = [
59 pkgs.dovecot_pigeonhole
60 pkgs.dovecot_deleted-to-trash
61 pkgs.dovecot_fts-xapian
62 ];
63 mailUser = "vhost";
64 mailGroup = "vhost";
65 createMailUser = false;
66 mailboxes = [
67 { name = "Trash"; auto = "subscribe"; specialUse = "Trash"; }
68 { name = "Junk"; auto = "subscribe"; specialUse = "Junk"; }
69 { name = "Sent"; auto = "subscribe"; specialUse = "Sent"; }
70 { name = "Drafts"; auto = "subscribe"; specialUse = "Drafts"; }
71 ];
72 mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap";
73 sslServerCert = "/var/lib/acme/mail/fullchain.pem";
74 sslServerKey = "/var/lib/acme/mail/key.pem";
75 sslCACert = "/var/lib/acme/mail/fullchain.pem";
76 extraConfig = builtins.concatStringsSep "\n" [
77 ''
78 postmaster_address = postmaster@immae.eu
79 mail_attribute_dict = file:%h/dovecot-attributes
80 imap_idle_notify_interval = 20 mins
81 namespace inbox {
82 type = private
83 separator = /
84 inbox = yes
85 list = yes
86 }
87 ''
88
89 # Full text search
90 ''
91 # needs to be bigger than any mailbox size
92 default_vsz_limit = 2GB
93 mail_plugins = $mail_plugins fts fts_xapian
94 plugin {
95 plugin = fts fts_xapian
96 fts = xapian
97 fts_xapian = partial=2 full=20
98 fts_autoindex = yes
99 fts_autoindex_exclude = \Junk
100 fts_autoindex_exclude2 = \Trash
101 fts_autoindex_exclude3 = Virtual/*
102 }
103 ''
104
105 # Antispam
106 # https://docs.iredmail.org/dovecot.imapsieve.html
107 ''
108 # imap_sieve plugin added below
109
110 plugin {
111 sieve_plugins = sieve_imapsieve sieve_extprograms
112 imapsieve_url = sieve://127.0.0.1:4190
113
114 # From elsewhere to Junk folder
115 imapsieve_mailbox1_name = Junk
116 imapsieve_mailbox1_causes = COPY APPEND
117 imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin
118
119 # From Junk folder to elsewhere
120 imapsieve_mailbox2_name = *
121 imapsieve_mailbox2_from = Junk
122 imapsieve_mailbox2_causes = COPY
123 imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin
124
125 sieve_pipe_bin_dir = ${sieve_bin}
126
127 sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
128 }
129 ''
130 # Services to listen
131 ''
132 service imap-login {
133 inet_listener imap {
134 }
135 inet_listener imaps {
136 }
137 }
138 service pop3-login {
139 inet_listener pop3 {
140 }
141 inet_listener pop3s {
142 }
143 }
144 service imap {
145 }
146 service pop3 {
147 }
148 service auth {
149 unix_listener auth-userdb {
150 }
151 unix_listener ${config.services.postfix.config.queue_directory}/private/auth {
152 mode = 0666
153 }
154 }
155 service auth-worker {
156 }
157 service dict {
158 unix_listener dict {
159 }
160 }
161 service stats {
162 unix_listener stats-reader {
163 user = vhost
164 group = vhost
165 mode = 0660
166 }
167 unix_listener stats-writer {
168 user = vhost
169 group = vhost
170 mode = 0660
171 }
172 }
173 ''
174
175 # Authentification
176 ''
177 first_valid_uid = ${toString config.ids.uids.vhost}
178 disable_plaintext_auth = yes
179 passdb {
180 driver = ldap
181 args = ${config.secrets.fullPaths."dovecot/ldap"}
182 }
183 userdb {
184 driver = static
185 args = user=%u uid=vhost gid=vhost home=/var/lib/vhost/%d/%n/ mail=mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap
186 }
187 ''
188
189 # Zlib
190 ''
191 mail_plugins = $mail_plugins zlib
192 plugin {
193 zlib_save_level = 6
194 zlib_save = gz
195 }
196 ''
197
198 # Sieve
199 ''
200 plugin {
201 sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve
202 }
203 service managesieve-login {
204 }
205 service managesieve {
206 }
207 ''
208
209 # Deleted to trash
210 ''
211 plugin {
212 deleted_to_trash_folder = Trash
213 }
214 ''
215
216 # Virtual mailboxes
217 ''
218 mail_plugins = $mail_plugins virtual
219 namespace Virtual {
220 prefix = Virtual/
221 location = virtual:~/Virtual
222 }
223 ''
224
225 # Protocol specific configuration
226 # Needs to come last if there are mail_plugins entries
227 ''
228 protocol imap {
229 mail_plugins = $mail_plugins deleted_to_trash imap_sieve
230 }
231 protocol lda {
232 mail_plugins = $mail_plugins sieve
233 }
234 ''
235 ];
236 };
237 config.networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ];
238 config.system.activationScripts.dovecot = {
239 deps = [ "users" ];
240 text =''
241 install -m 0755 -o vhost -g vhost -d /var/lib/vhost
242 '';
243 };
244
245 config.security.acme.certs."mail" = {
246 postRun = ''
247 systemctl restart dovecot2.service
248 '';
249 extraDomains = {
250 "imap.immae.eu" = null;
251 "pop3.immae.eu" = null;
252 };
253 };
254}
255
diff --git a/modules/private/mail.nix b/modules/private/mail/milters.nix
index eb869ba..c4bd990 100644
--- a/modules/private/mail.nix
+++ b/modules/private/mail/milters.nix
@@ -1,16 +1,17 @@
1{ lib, pkgs, config, myconfig, ... }: 1{ lib, pkgs, config, myconfig, ... }:
2{ 2{
3 config.users.users.nullmailer.uid = config.ids.uids.nullmailer; 3 options.myServices.mail.milters.sockets = lib.mkOption {
4 config.users.groups.nullmailer.gid = config.ids.gids.nullmailer; 4 type = lib.types.attrsOf lib.types.path;
5 5 default = {
6 config.services.nullmailer = { 6 opendkim = "/run/opendkim/opendkim.sock";
7 enable = true; 7 opendmarc = "/run/opendmarc/opendmarc.sock";
8 config = { 8 openarc = "/run/openarc/openarc.sock";
9 me = myconfig.env.mail.host;
10 remotes = "${myconfig.env.mail.relay} smtp";
11 }; 9 };
10 readOnly = true;
11 description = ''
12 milters sockets
13 '';
12 }; 14 };
13
14 config.secrets.keys = [ 15 config.secrets.keys = [
15 { 16 {
16 dest = "opendkim/eldiron.private"; 17 dest = "opendkim/eldiron.private";
@@ -38,6 +39,7 @@
38 config.users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ]; 39 config.users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ];
39 config.services.opendkim = { 40 config.services.opendkim = {
40 enable = true; 41 enable = true;
42 socket = "local:${config.myServices.mail.milters.sockets.opendkim}";
41 domains = builtins.concatStringsSep "," (lib.flatten (map 43 domains = builtins.concatStringsSep "," (lib.flatten (map
42 (zone: map 44 (zone: map
43 (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}") 45 (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}")
@@ -51,6 +53,7 @@
51 SubDomains yes 53 SubDomains yes
52 UMask 002 54 UMask 002
53 ''; 55 '';
56 group = config.services.postfix.group;
54 }; 57 };
55 config.systemd.services.opendkim.preStart = lib.mkBefore '' 58 config.systemd.services.opendkim.preStart = lib.mkBefore ''
56 # Skip the prestart script as keys are handled in secrets 59 # Skip the prestart script as keys are handled in secrets
@@ -66,6 +69,7 @@
66 config.users.users."${config.services.opendmarc.user}".extraGroups = [ "keys" ]; 69 config.users.users."${config.services.opendmarc.user}".extraGroups = [ "keys" ];
67 config.services.opendmarc = { 70 config.services.opendmarc = {
68 enable = true; 71 enable = true;
72 socket = "local:${config.myServices.mail.milters.sockets.opendmarc}";
69 configFile = pkgs.writeText "opendmarc.conf" '' 73 configFile = pkgs.writeText "opendmarc.conf" ''
70 AuthservID HOSTNAME 74 AuthservID HOSTNAME
71 FailureReports false 75 FailureReports false
@@ -79,6 +83,7 @@
79 TrustedAuthservIDs HOSTNAME, immae.eu, nef2.ens.fr 83 TrustedAuthservIDs HOSTNAME, immae.eu, nef2.ens.fr
80 UMask 002 84 UMask 002
81 ''; 85 '';
86 group = config.services.postfix.group;
82 }; 87 };
83 config.services.filesWatcher.opendmarc = { 88 config.services.filesWatcher.opendmarc = {
84 restart = true; 89 restart = true;
@@ -90,7 +95,8 @@
90 config.services.openarc = { 95 config.services.openarc = {
91 enable = true; 96 enable = true;
92 user = "opendkim"; 97 user = "opendkim";
93 group = "opendkim"; 98 socket = "local:${config.myServices.mail.milters.sockets.openarc}";
99 group = config.services.postfix.group;
94 configFile = pkgs.writeText "openarc.conf" '' 100 configFile = pkgs.writeText "openarc.conf" ''
95 AuthservID mail.immae.eu 101 AuthservID mail.immae.eu
96 Domain mail.immae.eu 102 Domain mail.immae.eu
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}
diff --git a/modules/private/mail/rspamd.nix b/modules/private/mail/rspamd.nix
new file mode 100644
index 0000000..3a7a67c
--- /dev/null
+++ b/modules/private/mail/rspamd.nix
@@ -0,0 +1,84 @@
1{ lib, pkgs, config, myconfig, ... }:
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.services.cron.systemCronJobs = let
14 cron_script = pkgs.runCommand "cron_script" {
15 buildInputs = [ pkgs.makeWrapper ];
16 } ''
17 mkdir -p $out
18 cp ${./scan_reported_mails} $out/scan_reported_mails
19 patchShebangs $out
20 for i in $out/*; do
21 wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils pkgs.rspamd pkgs.flock ]}
22 done
23 '';
24 in
25 [ "*/20 * * * * vhost ${cron_script}/scan_reported_mails" ];
26
27 config.services.rspamd = {
28 enable = true;
29 debug = true;
30 overrides = {
31 "actions.conf".text = ''
32 reject = null;
33 add_header = 6;
34 greylist = null;
35 '';
36 "milter_headers.conf".text = ''
37 extended_spam_headers = true;
38 '';
39 };
40 locals = {
41 "redis.conf".text = ''
42 servers = "${myconfig.env.mail.rspamd.redis.socket}";
43 db = "${myconfig.env.mail.rspamd.redis.db}";
44 '';
45 "classifier-bayes.conf".text = ''
46 users_enabled = true;
47 backend = "redis";
48 servers = "${myconfig.env.mail.rspamd.redis.socket}";
49 database = "${myconfig.env.mail.rspamd.redis.db}";
50 autolearn = true;
51 cache {
52 backend = "redis";
53 }
54 new_schema = true;
55 statfile {
56 BAYES_HAM {
57 spam = false;
58 }
59 BAYES_SPAM {
60 spam = true;
61 }
62 }
63 '';
64 };
65 workers = {
66 controller = {
67 extraConfig = ''
68 enable_password = "${myconfig.env.mail.rspamd.write_password_hashed}";
69 password = "${myconfig.env.mail.rspamd.read_password_hashed}";
70 '';
71 bindSockets = [ {
72 socket = config.myServices.mail.rspamd.sockets.worker-controller;
73 mode = "0660";
74 owner = config.services.rspamd.user;
75 group = "vhost";
76 } ];
77 };
78 };
79 postfix = {
80 enable = true;
81 config = {};
82 };
83 };
84}
diff --git a/modules/private/mail/scan_reported_mails b/modules/private/mail/scan_reported_mails
new file mode 100755
index 0000000..fe9f4d6
--- /dev/null
+++ b/modules/private/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/modules/private/mail/sieve_bin/imapsieve_copy b/modules/private/mail/sieve_bin/imapsieve_copy
new file mode 100755
index 0000000..2ca1f23
--- /dev/null
+++ b/modules/private/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/modules/private/mail/sieve_scripts/report_ham.sieve b/modules/private/mail/sieve_scripts/report_ham.sieve
new file mode 100644
index 0000000..f9b8481
--- /dev/null
+++ b/modules/private/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/modules/private/mail/sieve_scripts/report_spam.sieve b/modules/private/mail/sieve_scripts/report_spam.sieve
new file mode 100644
index 0000000..9a1f794
--- /dev/null
+++ b/modules/private/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/modules/private/websites/tools/tools/roundcubemail.nix b/modules/private/websites/tools/tools/roundcubemail.nix
index 6d87cdc..8bb60d6 100644
--- a/modules/private/websites/tools/tools/roundcubemail.nix
+++ b/modules/private/websites/tools/tools/roundcubemail.nix
@@ -17,7 +17,6 @@ rec {
17 text = '' 17 text = ''
18 <?php 18 <?php
19 $config['db_dsnw'] = '${env.psql_url}'; 19 $config['db_dsnw'] = '${env.psql_url}';
20 // This is used as default @domain, don't use "imap.immae.eu" here!
21 $config['default_host'] = 'ssl://imap.immae.eu'; 20 $config['default_host'] = 'ssl://imap.immae.eu';
22 $config['username_domain'] = array( 21 $config['username_domain'] = array(
23 "imap.immae.eu" => "mail.immae.eu" 22 "imap.immae.eu" => "mail.immae.eu"
@@ -46,6 +45,7 @@ rec {
46 'identicon', 45 'identicon',
47 'identity_select', 46 'identity_select',
48 'jqueryui', 47 'jqueryui',
48 'markasjunk',
49 'managesieve', 49 'managesieve',
50 'newmail_notifier', 50 'newmail_notifier',
51 'vcard_attachments', 51 'vcard_attachments',
@@ -60,11 +60,11 @@ rec {
60 60
61 $config['language'] = 'fr_FR'; 61 $config['language'] = 'fr_FR';
62 62
63 $config['drafts_mbox'] = 'Mail/Drafts'; 63 $config['drafts_mbox'] = 'Drafts';
64 $config['junk_mbox'] = 'Mail/Spam'; 64 $config['junk_mbox'] = 'Junk';
65 $config['sent_mbox'] = 'Mail/sent'; 65 $config['sent_mbox'] = 'Sent';
66 $config['trash_mbox'] = '''; 66 $config['trash_mbox'] = 'Trash';
67 $config['default_folders'] = array('INBOX', 'Mail/Drafts', 'Mail/sent', 'Mail/Spam', '''); 67 $config['default_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
68 $config['draft_autosave'] = 60; 68 $config['draft_autosave'] = 60;
69 $config['enable_installer'] = false; 69 $config['enable_installer'] = false;
70 $config['log_driver'] = 'file'; 70 $config['log_driver'] = 'file';
diff --git a/pkgs/default.nix b/pkgs/default.nix
index 74f9d18..ff9d477 100644
--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -50,4 +50,10 @@ rec {
50 python = python3; 50 python = python3;
51 inherit mylibs; 51 inherit mylibs;
52 }; 52 };
53 dovecot_deleted-to-trash = callPackage ./dovecot/plugins/deleted_to_trash {
54 inherit mylibs;
55 };
56 dovecot_fts-xapian = callPackage ./dovecot/plugins/fts_xapian {
57 inherit mylibs;
58 };
53} 59}
diff --git a/pkgs/dovecot/plugins/deleted_to_trash/default.nix b/pkgs/dovecot/plugins/deleted_to_trash/default.nix
new file mode 100644
index 0000000..db1afb5
--- /dev/null
+++ b/pkgs/dovecot/plugins/deleted_to_trash/default.nix
@@ -0,0 +1,21 @@
1{ stdenv, fetchurl, dovecot, mylibs, fetchpatch }:
2
3stdenv.mkDerivation (mylibs.fetchedGithub ./dovecot-deleted_to_trash.json // rec {
4 buildInputs = [ dovecot ];
5 patches = [
6 (fetchpatch {
7 name = "fix-dovecot-2.3.diff";
8 url = "https://github.com/lexbrugman/dovecot_deleted_to_trash/commit/c52a3799a96104a603ade33404ef6aa1db647b2f.diff";
9 sha256 = "0pld3rdcjp9df2qxbp807k6v4f48lyk0xy5q508ypa57d559y6dq";
10 })
11 ./fix_mbox.patch
12 ];
13 preConfigure = ''
14 substituteInPlace Makefile --replace \
15 "/usr/include/dovecot" \
16 "${dovecot}/include/dovecot"
17 substituteInPlace Makefile --replace \
18 "/usr/lib/dovecot/modules" \
19 "$out/lib/dovecot"
20 '';
21})
diff --git a/pkgs/dovecot/plugins/deleted_to_trash/dovecot-deleted_to_trash.json b/pkgs/dovecot/plugins/deleted_to_trash/dovecot-deleted_to_trash.json
new file mode 100644
index 0000000..2987a02
--- /dev/null
+++ b/pkgs/dovecot/plugins/deleted_to_trash/dovecot-deleted_to_trash.json
@@ -0,0 +1,15 @@
1{
2 "tag": "81b0754-master",
3 "meta": {
4 "name": "dovecot-deleted_to_trash",
5 "url": "https://github.com/lexbrugman/dovecot_deleted_to_trash",
6 "branch": "master"
7 },
8 "github": {
9 "owner": "lexbrugman",
10 "repo": "dovecot_deleted_to_trash",
11 "rev": "81b07549accfc36467bf8527a53c295c7a02dbb9",
12 "sha256": "1b3k31g898s4fa0a9l4kvjsdyds772waaay84sjdxv09jw6mqs0f",
13 "fetchSubmodules": true
14 }
15}
diff --git a/pkgs/dovecot/plugins/deleted_to_trash/fix_mbox.patch b/pkgs/dovecot/plugins/deleted_to_trash/fix_mbox.patch
new file mode 100644
index 0000000..0060fb4
--- /dev/null
+++ b/pkgs/dovecot/plugins/deleted_to_trash/fix_mbox.patch
@@ -0,0 +1,12 @@
1diff --git a/src/deleted-to-trash-plugin.c b/src/deleted-to-trash-plugin.c
2index bb4cc78..66bad53 100644
3--- a/src/deleted-to-trash-plugin.c
4+++ b/src/deleted-to-trash-plugin.c
5@@ -82,6 +82,7 @@ static struct mailbox *mailbox_open_or_create(struct mailbox_list *list, const c
6 *error_r = mail_storage_get_last_error(mailbox_get_storage(box), &error);
7 if (error != MAIL_ERROR_NOTFOUND)
8 {
9+ i_error("%s", *error_r);
10 mailbox_free(&box);
11 return NULL;
12 }
diff --git a/pkgs/dovecot/plugins/fts_xapian/default.nix b/pkgs/dovecot/plugins/fts_xapian/default.nix
new file mode 100644
index 0000000..350a3ff
--- /dev/null
+++ b/pkgs/dovecot/plugins/fts_xapian/default.nix
@@ -0,0 +1,14 @@
1{ stdenv, autoconf, automake, pkg-config, dovecot, libtool, xapian, icu, mylibs }:
2
3stdenv.mkDerivation (mylibs.fetchedGithub ./fts-xapian.json // rec {
4 buildInputs = [ dovecot autoconf automake libtool pkg-config xapian icu ];
5 preConfigure = ''
6 export PANDOC=false
7 autoreconf -vi
8 '';
9 configureFlags = [
10 "--with-dovecot=${dovecot}/lib/dovecot"
11 "--without-dovecot-install-dirs"
12 "--with-moduledir=$(out)/lib/dovecot"
13 ];
14})
diff --git a/pkgs/dovecot/plugins/fts_xapian/fts-xapian.json b/pkgs/dovecot/plugins/fts_xapian/fts-xapian.json
new file mode 100644
index 0000000..a786776
--- /dev/null
+++ b/pkgs/dovecot/plugins/fts_xapian/fts-xapian.json
@@ -0,0 +1,15 @@
1{
2 "tag": "9a94b4a-master",
3 "meta": {
4 "name": "fts-xapian",
5 "url": "https://github.com/grosjo/fts-xapian",
6 "branch": "master"
7 },
8 "github": {
9 "owner": "grosjo",
10 "repo": "fts-xapian",
11 "rev": "9a94b4aeaac3988786ad72a716127c306b05c9d6",
12 "sha256": "12xv5fnqahs0cy26ja2jwk6dg95626amblisf2wcx3nqzkcf4w1y",
13 "fetchSubmodules": true
14 }
15}