diff --git a/README.md b/README.md new file mode 100644 index 0000000..64ffdfd --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +hamburg.freifunk.net Mailserver +=============================== + +Initiales Setup +----- +1. System starten +2. URL anpassen und aufrufen: http://127.0.0.1/setup.php?lostpw=1 +3. Neues Setup-Passwort vergeben und den Hash generieren. +4. Hash in der Datei variables.nix ersetzen und das System neu bauen und starten. +5. URL anpassen und aufrufen: http://127.0.0.1/setup.php +6. Admin-Account über die Website anlegen +7. URL anpassen und aufrufen: http://127.0.0.1/ +8. Mail konfigurieren. + + +Development +----- +Starten des Systems: + QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:8080-:80,hostfwd=tcp:127.0.0.1:2525-:25" nixos-shell +Zugriff dann per SSH über 127.0.0.1:2222 und HTTP über 127.0.0.1:8080. diff --git a/configuration.nix b/configuration.nix index e47ae77..cb1fd62 100644 --- a/configuration.nix +++ b/configuration.nix @@ -8,8 +8,51 @@ imports = [ # Include the results of the hardware scan. ./hardware-configuration.nix + ./variables.nix + ./mailserver.nix ]; + # Use the staging environment of Let's encrypt and accept their certificates as well... + security.acme.production = false; + security.pki.certificates = [ + '' + -----BEGIN CERTIFICATE----- + MIIFATCCAumgAwIBAgIRAKc9ZKBASymy5TLOEp57N98wDQYJKoZIhvcNAQELBQAw + GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDMyMzIyNTM0NloXDTM2 + MDMyMzIyNTM0NlowGjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+pYHvQw5iU3v2b3iNuYNKYgsWD6KU7aJ + diddtZQxSWYzUI3U0I1UsRPTxnhTifs/M9NW4ZlV13ZfB7APwC8oqKOIiwo7IwlP + xg0VKgyz+kT8RJfYr66PPIYP0fpTeu42LpMJ+CKo9sbpgVNDZN2z/qiXrRNX/VtG + TkPV7a44fZ5bHHVruAxvDnylpQxJobtCBWlJSsbIRGFHMc2z88eUz9NmIOWUKGGj + EmP76x8OfRHpIpuxRSCjn0+i9+hR2siIOpcMOGd+40uVJxbRRP5ZXnUFa2fF5FWd + O0u0RPI8HON0ovhrwPJY+4eWKkQzyC611oLPYGQ4EbifRsTsCxUZqyUuStGyp8oa + aoSKfF6X0+KzGgwwnrjRTUpIl19A92KR0Noo6h622OX+4sZiO/JQdkuX5w/HupK0 + A0M0WSMCvU6GOhjGotmh2VTEJwHHY4+TUk0iQYRtv1crONklyZoAQPD76hCrC8Cr + IbgsZLfTMC8TWUoMbyUDgvgYkHKMoPm0VGVVuwpRKJxv7+2wXO+pivrrUl2Q9fPe + Kk055nJLMV9yPUdig8othUKrRfSxli946AEV1eEOhxddfEwBE3Lt2xn0hhiIedbb + Ftf/5kEWFZkXyUmMJK8Ra76Kus2ABueUVEcZ48hrRr1Hf1N9n59VbTUaXgeiZA50 + qXf2bymE6F8CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB + Af8wHQYDVR0OBBYEFMEmdKSKRKDm+iAo2FwjmkWIGHngMA0GCSqGSIb3DQEBCwUA + A4ICAQBCPw74M9X/Xx04K1VAES3ypgQYH5bf9FXVDrwhRFSVckria/7dMzoF5wln + uq9NGsjkkkDg17AohcQdr8alH4LvPdxpKr3BjpvEcmbqF8xH+MbbeUEnmbSfLI8H + sefuhXF9AF/9iYvpVNC8FmJ0OhiVv13VgMQw0CRKkbtjZBf8xaEhq/YqxWVsgOjm + dm5CAQ2X0aX7502x8wYRgMnZhA5goC1zVWBVAi8yhhmlhhoDUfg17cXkmaJC5pDd + oenZ9NVhW8eDb03MFCrWNvIh89DDeCGWuWfDltDq0n3owyL0IeSn7RfpSclpxVmV + /53jkYjwIgxIG7Gsv0LKMbsf6QdBcTjhvfZyMIpBRkTe3zuHd2feKzY9lEkbRvRQ + zbh4Ps5YBnG6CKJPTbe2hfi3nhnw/MyEmF3zb0hzvLWNrR9XW3ibb2oL3424XOwc + VjrTSCLzO9Rv6s5wi03qoWvKAQQAElqTYRHhynJ3w6wuvKYF5zcZF3MDnrVGLbh1 + Q9ePRFBCiXOQ6wPLoUhrrbZ8LpFUFYDXHMtYM7P9sc9IAWoONXREJaO08zgFtMp4 + 8iyIYUyQAbsvx8oD2M8kRvrIRSrRJSl6L957b4AFiLIQ/GgV2curs0jje7Edx34c + idWw1VrejtwclobqNMVtG3EiPUIpJGpbMcJgbiLSmKkrvQtGng== + -----END CERTIFICATE----- + '' + ]; + + # Configuration options for the mailserver + variables = { + mailAdmin = "postmaster@mail2.hamburg.freifunk.net"; + useSSL = true; + }; # Use the GRUB 2 boot loader. boot.loader.grub.enable = true; boot.loader.grub.version = 2; diff --git a/dovecot.nix b/dovecot.nix new file mode 100644 index 0000000..745bfa9 --- /dev/null +++ b/dovecot.nix @@ -0,0 +1,136 @@ +{ config, lib, pkgs, ... }: + +let + dovecotSQL = pkgs.writeText "dovecot-sql.conf" '' + driver = sqlite + connect = ${config.variables.pfadminDataDir}/postfixadmin.db + password_query = SELECT username AS user, password FROM mailbox WHERE username = '%Lu' AND active='1' + user_query = SELECT username AS user FROM mailbox WHERE username = '%Lu' AND active='1' + ''; + dovecotConf = pkgs.writeText "dovecot.conf" '' + default_internal_user = dovecot2 + default_internal_group = dovecot2 + protocols = imap lmtp pop3 sieve + + ${lib.optionalString (config.variables.useSSL) '' + ssl = yes + ssl_cert = &2 + exit 1 + fi + rspamd=${pkgs.rspamd}/bin/rspamadm + mkdir -p /var/lib/rspamd/dkim + $rspamd dkim_keygen -b 2048 -d "$1" -s dkim | ${pkgs.gawk}/bin/awk '/^-/ {KEY= ! KEY; print; next} KEY {print} !KEY {print > "/dev/stderr"}' >/var/lib/rspamd/dkim/"$1".dkim.key 2>/var/lib/rspamd/dkim/"$1".dkim.dns + ls -l /var/lib/rspamd/dkim/"$1".dkim.key /var/lib/rspamd/dkim/"$1".dkim.dns + '') ]; + services.rspamd = { + enable = true; + # Just shove our own configuration up rspamd's rear end with high prio as the default configuration structure is a mess + extraConfig = '' + .include(try=true,priority=10,duplicate=merge) "${rspamdExtraConfig}" + ''; + workers = { + controller = { + enable = true; + extraConfig = '' + secure_ip = [::1] + ''; + bindSockets = [ + "[::1]:11334" + { mode = "0666"; owner = config.variables.vmailUser; socket = "/run/rspamd/worker-controller.socket"; } + ]; + }; + rspamd_proxy = { + enable = true; + type = "rspamd_proxy"; + count = 5; # TODO: match with postfix limits + extraConfig = '' + upstream "local" { + self_scan = yes; # Enable self-scan + } + ''; + bindSockets = [ + { socket = config.variables.rspamdMilterSocket; mode = "0600"; owner = config.services.postfix.user; group = config.services.rspamd.group; } + ]; + }; + }; + }; +} diff --git a/sieve-after.nix b/sieve-after.nix new file mode 100644 index 0000000..c8add5a --- /dev/null +++ b/sieve-after.nix @@ -0,0 +1,20 @@ +{ stdenv, dovecot_pigeonhole}: + +stdenv.mkDerivation rec { + name = "sieve-after"; + + src = ./sieve-after; + + phases = [ "copyPhase" "compilePhase" ]; + + copyPhase = '' + cd $src + mkdir $out + cp -Rv $src/. $out/ + find $out -type d -exec chmod -c 0755 {} \; + set +x + ''; + compilePhase = '' + find $out -iname '*.sieve' -print0 | xargs -t -0 -n1 ${dovecot_pigeonhole}/bin/sievec -c /dev/null + ''; +} diff --git a/sieve-after/junk-folder.sieve b/sieve-after/junk-folder.sieve new file mode 100644 index 0000000..d81b5de --- /dev/null +++ b/sieve-after/junk-folder.sieve @@ -0,0 +1,6 @@ +require ["fileinto","mailbox"]; + +if header :contains "X-Spam" "Yes" { + fileinto :create "Junk"; + stop; +} diff --git a/sieve-pipe-bin-dir.nix b/sieve-pipe-bin-dir.nix new file mode 100644 index 0000000..fdf2e3d --- /dev/null +++ b/sieve-pipe-bin-dir.nix @@ -0,0 +1,14 @@ +{ stdenv}: + +stdenv.mkDerivation rec { + name = "sieve-pipe-bin-dir"; + + src = ./sieve-pipe-bin-dir; + + phases = [ "copyPhase" "fixupPhase" ]; + + copyPhase = '' + mkdir $out + cp -Rv $src/. $out/ + ''; +} diff --git a/sieve-pipe-bin-dir/learn-ham.sh b/sieve-pipe-bin-dir/learn-ham.sh new file mode 100755 index 0000000..6e4c0b1 --- /dev/null +++ b/sieve-pipe-bin-dir/learn-ham.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +exec /run/current-system/sw/bin/rspamc -h /run/rspamd/worker-controller.socket learn_ham diff --git a/sieve-pipe-bin-dir/learn-spam.sh b/sieve-pipe-bin-dir/learn-spam.sh new file mode 100755 index 0000000..18f0dba --- /dev/null +++ b/sieve-pipe-bin-dir/learn-spam.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +exec /run/current-system/sw/bin/rspamc -h /run/rspamd/worker-controller.socket learn_spam diff --git a/sieve-report-spam-ham.nix b/sieve-report-spam-ham.nix new file mode 100644 index 0000000..d8e7e93 --- /dev/null +++ b/sieve-report-spam-ham.nix @@ -0,0 +1,30 @@ +{ stdenv, dovecot_pigeonhole}: + +stdenv.mkDerivation rec { + name = "sieve-report-spam-ham"; + + src = ./sieve-report-spam-ham; + + phases = [ "copyPhase" "compilePhase" ]; + + copyPhase = '' + mkdir $out + cp -Rv $src/. $out/ + find $out -type d -exec chmod -c 0755 {} \; + set +x + ''; + + # Yeah, need a specific dovecot.conf to enable the necessary plugins... + # taking the one used by the dovecot that actually executes the sieve scripts should + # work as well, but passing it through isn't worth my time. + compilePhase = '' + dc=$(pwd)/dovecot.conf + cat > $dc <<-EOF + plugin { + sieve_plugins = sieve_imapsieve sieve_extprograms + sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment + } + EOF + find $out -iname '*.sieve' -print0 | xargs -t -0 -n1 ${dovecot_pigeonhole}/bin/sievec -c $dc + ''; +} diff --git a/sieve-report-spam-ham/report-ham.sieve b/sieve-report-spam-ham/report-ham.sieve new file mode 100644 index 0000000..6217a90 --- /dev/null +++ b/sieve-report-spam-ham/report-ham.sieve @@ -0,0 +1,15 @@ +require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + +if environment :matches "imap.mailbox" "*" { + set "mailbox" "${1}"; +} + +if string "${mailbox}" "Trash" { + stop; +} + +if environment :matches "imap.user" "*" { + set "username" "${1}"; +} + +pipe :copy "learn-ham.sh" [ "${username}" ]; diff --git a/sieve-report-spam-ham/report-spam.sieve b/sieve-report-spam-ham/report-spam.sieve new file mode 100644 index 0000000..9d4c74b --- /dev/null +++ b/sieve-report-spam-ham/report-spam.sieve @@ -0,0 +1,7 @@ +require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; + +if environment :matches "imap.user" "*" { + set "username" "${1}"; +} + +pipe :copy "learn-spam.sh" [ "${username}" ]; diff --git a/variables.nix b/variables.nix new file mode 100644 index 0000000..db4a1dd --- /dev/null +++ b/variables.nix @@ -0,0 +1,65 @@ +{ config, lib, pkgs, ... }: + +{ + options = { + variables = lib.mkOption { + type = lib.types.attrs; + default = { }; + }; + }; + config.variables = { + dovecotGroup = "dovecot2"; + dovecotUser = "dovecot2"; + dovecotAuthSocket = "/run/dovecot2/dovecot-auth"; + dovecotLmtpSocket = "/run/dovecot2/dovecot-lmtp"; + rspamdMilterSocket = "/run/rspamd/milter"; + myFQDN = "${config.networking.hostName}.${config.networking.domain}"; + pfadminDataDir = "/var/lib/postfixadmin"; + pfaGroup = "pfadmin"; + pfaPhpfpmHostPort = "127.0.0.1:9000"; + pfaUser = "pfadmin"; + pfaDomain = "pfa.${config.variables.myFQDN}"; + roundcubeFQDN = config.variables.myFQDN; + roundcubeDataDir = "/var/lib/roundcube"; + roundcubePhpfpmHostPort = "127.0.0.1:9001"; + roundcubeUser = "roundcube"; + useSSL = false; + vmailBaseDir = "/var/vmail"; + vmailGID = 10000; + vmailGroup = "vmail"; + vmailUID = 10000; + vmailUser = "vmail"; + postfixadminpkgCacheDir = "/var/cache/postfixadmin"; + postfixadminpkg = (pkgs.callPackage ./pkg-postfixadmin.nix { + config = (pkgs.writeText "postfixadmin-config.local.php" '' + '${config.variables.mailAdmin}', + 'hostmaster' => '${config.variables.mailAdmin}', + 'postmaster' => '${config.variables.mailAdmin}', + 'webmaster' => '${config.variables.mailAdmin}' + ); + $CONF['footer_text'] = ""; + $CONF['footer_link'] = ""; + ?> + ''); + cacheDir = config.variables.postfixadminpkgCacheDir; + } ); + }; +}