diff --git a/config/services/dovecot.nix b/config/services/dovecot.nix new file mode 100644 index 00000000..d260475a --- /dev/null +++ b/config/services/dovecot.nix @@ -0,0 +1,118 @@ +{ pkgs, ... }: +let + listenIPs = (import ../../utils/getInternalIP.nix config).listenIP; + sieves = import ../../packages/sieves.nix pkgs; + +in +{ + services.dovecot2 = { + enable = true; + enableImap = true; + enableLmtp = true; + enablePop3 = true; + enableQuota = true; + mailGroup = "vmail"; + mailUser = "vmail"; + mailLocation = "maildir:/var/vmail/%h/%n"; + mailPlugins = { + globally.enable = [ + "old_stats" + ]; + perProtocol = { + imap.enable = [ + "imap_sieve" + ]; + lda.enable = [ + "sieve" + ]; + lmtp.enable = [ + "lmtp_sieve" + ]; + }; + }; + mailboxes = { + Drafts = { + specialUse = "Drafts"; + auto = "subscribe"; + }; + Junk = { + specialUse = "Junk"; + auto = "subscribe"; + }; + Trash = { + specialUse = "Trash"; + auto = "subscribe"; + }; + Sent = { + specialUse = "Sent"; + auto = "subscribe"; + }; + "Sent Messages" = { + specialUse = "Sent"; + }; + "virtual/All" = { + specialUse = "All"; + auto = "subscribe"; + }; + }; + sslServerCert = "/var/lib/acme/chir.rs/cert.pem"; + sslServerKey = "/var/lib/acme/chir.rs/key.pem"; + extraConfig = '' + service old-stats { + unix_listener old-stats { + user = dovecot-exporter + group = dovecot-exporter + mode = 0660 + } + fifo_listener old-stats-mail { + mode = 0660 + user = dovecot + group = dovecot + } + fifo_listener old-stats-user { + mode = 0660 + user = dovecot + group = dovecot + } + } + plugin { + old_stats_refresh = 30 secs + old_stats_track_cmds = yes + } + plugin { + sieve_plugins = sieve_imapsieve sieve_extprograms + # From elsewhere to Spam folder or flag changed in Spam folder + imapsieve_mailbox1_name = Junk + imapsieve_mailbox1_causes = COPY FLAG + imapsieve_mailbox1_before = file:${sieves.report-spam}/report-spam.sieve + + # From Spam folder to elsewhere + imapsieve_mailbox2_name = * + imapsieve_mailbox2_from = Junk + imapsieve_mailbox2_causes = COPY + imapsieve_mailbox2_before = file:${sieves.report-ham}/report-ham.sieve + + sieve_pipe_bin_dir = /nix/store + + sieve_global_extensions = +vnd.dovecot.pipe + sieve = ${sieves.default}/default.sieve + } + disable_plaintext_auth = yes + auth_mechanisms = plain login + + ''; + user = "dovecot"; + }; + services.prometheus.exporters.dovecot = { + enable = true; + listenAddress = listenIP; + }; + sops.secrets."services/dovecot/rspamd_password" = { owner = "dovecot"; }; + services.postgresql.ensureUsers = [{ + name = "dovecot"; + ensurePermissions = { + "DATABASE \"postfix\"" = "CONNECT"; + "ALL TABLES IN DATABASE \"postfix\"" = "SELECT"; # can't select more granular permissions + }; + }]; +} diff --git a/packages/sievec.nix b/packages/sievec.nix new file mode 100644 index 00000000..f5d98f90 --- /dev/null +++ b/packages/sievec.nix @@ -0,0 +1,24 @@ +{ writeText, stdenv, dovecot_pigeonhole, ... } @ pkgs: { name, src, exts }: +let dummyConfig = writeText "dovecot.cfg" '' + plugin { + sieve_plugins = sieve_imapsieve sieve_extprograms + sieve_global_extensions = +vnd.dovecot.pipe + } +''; +in +stdenv.mkDerivation { + inherit name; + inherit src; + + phases = [ "copyPhase" "compilePhase" ]; + + copyPhase = '' + mkdir $out + cp $src $out/${name}.sieve + chmod 0755 $out/${name}.sieve + set +x + ''; + compilePhase = '' + ${dovecot_pigeonhole}/bin/sievec -c ${dummyConfig} $out/${name}.sieve $out/${name}.svbin -x "${toString exts}" + ''; +} diff --git a/packages/sieves.nix b/packages/sieves.nix new file mode 100644 index 00000000..96f2bc93 --- /dev/null +++ b/packages/sieves.nix @@ -0,0 +1,79 @@ +{ lib, writeText, writeScript, zsh, coreutils-full, rspamd, ... } @ pkgs: +let + sievec = import ./sievec.nix pkgs; + rspamd_host = "fd00:e621:e621:2::2"; + sa-learn-ham-script = writeScript "sa-learn-ham" '' + #!${zsh}/bin/zsh + + tmpfile=$(${coreutils-full}/bin/mktemp /tmp/spam.XXXXXX) + ${coreutils-full}/bin/cat > $tmpfile + password=$(${coreutils-full}/bin/cat /run/secrets/services/dovecot/rspamd_password) + + ${coreutils-full}/bin/cat $tmpfile | ${rspamd}/bin/rspamc -P $password -h ${rspamd_host} learn_ham + ${coreutils-full}/bin/cat $tmpfile | ${rspamd}/bin/rspamc -P $password -h ${rspamd_host} -w 5 -f 11 fuzzy_del + + ${coreutils-full}/bin/rm $tmpfile + ''; + + sa-learn-spam-script = writeScript "sa-learn-spam" '' + #!${zsh}/bin/zsh + + tmpfile=$(${coreutils-full}/bin/mktemp /tmp/spam.XXXXXX) + ${coreutils-full}/bin/cat > $tmpfile + password=$(${coreutils-full}/bin/cat /run/secrets/services/dovecot/rspamd_password) + + ${coreutils-full}/bin/cat $tmpfile | ${rspamd}/bin/rspamc -P $password -h ${rspamd_host} learn_spam + ${coreutils-full}/bin/cat $tmpfile | ${rspamd}/bin/rspamc -P $password -h ${rspamd_host} -w 5 -f 11 fuzzy_add + + ${coreutils-full}/bin/rm $tmpfile + ''; + removeStore = location: lib.removePrefix "/nix/store/" "${location}"; +in +{ + default = sievec { + name = "default"; + src = writeText "default.sieve" '' + require "fileinto"; + if header :contains "X-Spam" "YES" { + fileinto "Junk"; + } + ''; + exts = [ "fileinto" ]; + }; + report-ham = sievec { + name = "report-ham"; + src = writeText "report-ham.sieve" '' + require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + + if environment :matches "imap.mailbox" "*" { + set "mailbox" "$${1}"; + } + + if string "$${mailbox}" [ "Trash", "train_ham", "train_prob", "train_spam" ] { + stop; + } + + pipe :copy "${removeStore sa-learn-ham-script}"; + ''; + exts = [ "vnd.dovecot.pipe" "copy" "imapsieve" "environment" "variables" ]; + }; + report-spam = sievec { + name = "report-spam"; + src = writeText "report-spam.sieve" '' + require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "imap4flags"]; + + if environment :is "imap.cause" "COPY" { + pipe :copy "${removeStore sa-learn-spam-script}"; + } + + # Catch replied or forwarded spam + elsif anyof (allof (hasflag "\\Answered", + environment :contains "imap.changedflags" "\\Answered"), + allof (hasflag "$Forwarded", + environment :contains "imap.changedflags" "$Forwarded")) { + pipe :copy "${removeStore sa-learn-spam-script}"; + } + ''; + exts = [ "vnd.dovecot.pipe" "copy" "imapsieve" "environment" "imap4flags" ]; + }; +} diff --git a/secrets/nixos-8gb-fsn1-1/secrets.yaml b/secrets/nixos-8gb-fsn1-1/secrets.yaml index cafcd040..60d5a267 100644 --- a/secrets/nixos-8gb-fsn1-1/secrets.yaml +++ b/secrets/nixos-8gb-fsn1-1/secrets.yaml @@ -16,6 +16,8 @@ services: postfixadmin: dbpassword: ENC[AES256_GCM,data:37kEiKKgJVRIzzWXY3o/Wpk5UQ==,iv:LAJXgMr/rTMkds1r1kEWzhwNb6aPzsAhqUEeoLyBDbo=,tag:TI7oYxdpNzw2dXA4hLmVnQ==,type:str] setupPassword: ENC[AES256_GCM,data:2BiQLOZZ6zCh4F+DkeNpMGLeXoxmMtDkuAU4XGBNvso+f4jupowalLkhTG/kA8yUL6BWOxwtJGMEp5wO,iv:0guj3/elSzoOe/00wgi5Z4R4lVfWeWt8mUDao3RXK6I=,tag:1lFUqfeSGV04mfxaVrmSMg==,type:str] + dovecot: + rspamd_password: ENC[AES256_GCM,data:PYVfbmSR8Uq3gQGkXVhj6Pt4QIo=,iv:jG7BudJT6+RAprllh5dGnSiqr4hS/GtyZesc77bd8eY=,tag:GBRgqg74cp1CPhDOu8IyKw==,type:str] email: darkkirb@darkkirb.de: ENC[AES256_GCM,data:DgVyvDHsviJuGqM+YP4jjytnzJE=,iv:KhEJz2+Nl9sxjRe0FmHOXi64QtsxDZhagnYt08sqU4E=,tag:vrhBg9qWBiSLPop/5jyIwA==,type:str] lotte@chir.rs: ENC[AES256_GCM,data:bkzYVXizG/inJ/MS57G2pEiUkA==,iv:jviAx1B83wPhc128msfSs7oYwRQH+j7PU0aAmNbwi88=,tag:ylYl5k9R5BdLGAXOXVeLZg==,type:str] @@ -44,8 +46,8 @@ sops: Ync4ejJHR0RXTkpqVzFRQXhEVlFVZjgKPo209jJf8Lwn1j3VmLC+j0633zdbt2yf bPwO7dlKYGbGeGObprNtBXBS2cUXHeuQ45vRpTtg1cpxYK+TfNH8vQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2022-01-20T08:56:25Z" - mac: ENC[AES256_GCM,data:o7Ssj3mTwoYQN4TeveWxqn9oP/d7ZBw0I/KRfthhFv0ry+avBuIAr4ocgSKC6oCwnp3rNCr3pEsN+wN4kpidQ213ytvP8tSegn936i8WiU2ozD/jBHlRtRQg1abDau8rOps6W65dMBrqoGUDRVdrIsfp63K3myT8pv74u9dkYew=,iv:LU3chaUBP6FOhdR0Ua3YF+OsyEb2utmlnGxacZIcqc4=,tag:WgWXaduAXEbXj3czGHM+CA==,type:str] + lastmodified: "2022-01-20T15:46:48Z" + mac: ENC[AES256_GCM,data:jsQx0yZmcssxTXLzxx10yrSLfYqfa75LIQFRKWmHkTrd/a2w/ZZFfcorqjoNARtsx+F8Das++ZHpenEdHyxyOSPif5JCIsjrBD6RtJyaKVmIuDirhqUhMQNB2ZDLQifIIsA2qCzhJPThP/+ZJygnhUYiOKg86fangLpkAIxF7DM=,iv:R77YqOSBBMEZbwiPAhnDScYCA+DrTVsZfjNYw9S0iMU=,tag:3va+Z/r39RrD9iQ2BIApNw==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.7.1