diff --git a/mail-server/common.nix b/mail-server/common.nix index d3de0d9..3ca5c57 100644 --- a/mail-server/common.nix +++ b/mail-server/common.nix @@ -69,21 +69,23 @@ rec { passwordFile, destination, }: - pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' - #!${pkgs.stdenv.shell} - set -euo pipefail + pkgs.writeScript "append-ldap-bind-pwd-in-${name}" + # bash + '' + #!${pkgs.stdenv.shell} + set -euo pipefail - baseDir=$(dirname ${destination}) - if (! test -d "$baseDir"); then - mkdir -p $baseDir - chmod 755 $baseDir - fi + baseDir=$(dirname ${destination}) + if (! test -d "$baseDir"); then + mkdir -p $baseDir + chmod 755 $baseDir + fi - cat ${file} > ${destination} - echo -n '${prefix}' >> ${destination} - cat ${passwordFile} | tr -d '\n' >> ${destination} - echo -n '${suffix}' >> ${destination} - chmod 600 ${destination} - ''; + cat ${file} > ${destination} + echo -n '${prefix}' >> ${destination} + cat ${passwordFile} | tr -d '\n' >> ${destination} + echo -n '${suffix}' >> ${destination} + chmod 600 ${destination} + ''; } diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix index c757005..2780e66 100644 --- a/mail-server/dovecot.nix +++ b/mail-server/dovecot.nix @@ -96,50 +96,53 @@ let destination = ldapConfFile; }; - genPasswdScript = pkgs.writeScript "generate-password-file" '' - #!${pkgs.stdenv.shell} + genPasswdScript = + pkgs.writeScript "generate-password-file" + # bash + '' + #!${pkgs.stdenv.shell} - set -euo pipefail + set -euo pipefail - if (! test -d "${passwdDir}"); then - mkdir "${passwdDir}" - chmod 755 "${passwdDir}" - fi + if (! test -d "${passwdDir}"); then + mkdir "${passwdDir}" + chmod 755 "${passwdDir}" + fi - # Prevent world-readable password files, even temporarily. - umask 077 + # Prevent world-readable password files, even temporarily. + umask 077 - for f in ${ - builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts) - }; do - if [ ! -f "$f" ]; then - echo "Expected password hash file $f does not exist!" - exit 1 - fi - done + for f in ${ + builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts) + }; do + if [ ! -f "$f" ]; then + echo "Expected password hash file $f does not exist!" + exit 1 + fi + done - cat < ${passwdFile} - ${lib.concatStringsSep "\n" ( - lib.mapAttrsToList ( - name: _: - if lib.elem name accountsWithPlaintextPasswordFiles then - "${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::" - else - "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" - ) cfg.loginAccounts - )} - EOF + cat < ${passwdFile} + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList ( + name: _: + if lib.elem name accountsWithPlaintextPasswordFiles then + "${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::" + else + "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" + ) cfg.loginAccounts + )} + EOF - cat < ${userdbFile} - ${lib.concatStringsSep "\n" ( - lib.mapAttrsToList ( - name: value: - "${name}:::::::" - + lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}" - ) cfg.loginAccounts - )} - EOF - ''; + cat < ${userdbFile} + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList ( + name: value: + "${name}:::::::" + + lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}" + ) cfg.loginAccounts + )} + EOF + ''; junkMailboxes = builtins.attrNames ( lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes diff --git a/mail-server/users.nix b/mail-server/users.nix index 93a543d..9863066 100644 --- a/mail-server/users.nix +++ b/mail-server/users.nix @@ -43,48 +43,51 @@ let group = vmailGroupName; }; - virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" '' - #!${pkgs.stdenv.shell} + virtualMailUsersActivationScript = + pkgs.writeScript "activate-virtual-mail-users" + # bash + '' + #!${pkgs.stdenv.shell} - set -euo pipefail + set -euo pipefail - # Prevent world-readable paths, even temporarily. - umask 007 + # Prevent world-readable paths, even temporarily. + umask 007 - # Create directory to store user sieve scripts if it doesn't exist - if (! test -d "${sieveDirectory}"); then - mkdir "${sieveDirectory}" - chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}" - chmod 770 "${sieveDirectory}" - fi + # Create directory to store user sieve scripts if it doesn't exist + if (! test -d "${sieveDirectory}"); then + mkdir "${sieveDirectory}" + chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}" + chmod 770 "${sieveDirectory}" + fi - # Copy user's sieve script to the correct location (if it exists). If it - # is null, remove the file. - ${lib.concatMapStringsSep "\n" ( - { name, sieveScript }: - if lib.isString sieveScript then - '' - if (! test -d "${sieveDirectory}/${name}"); then - mkdir -p "${sieveDirectory}/${name}" - chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}" - chmod 770 "${sieveDirectory}/${name}" - fi - cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve" - ${sieveScript} - EOF - chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve" - '' - else - '' - if (test -f "${sieveDirectory}/${name}/default.sieve"); then - rm "${sieveDirectory}/${name}/default.sieve" - fi - if (test -f "${sieveDirectory}/${name}.svbin"); then - rm "${sieveDirectory}/${name}/default.svbin" - fi - '' - ) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))} - ''; + # Copy user's sieve script to the correct location (if it exists). If it + # is null, remove the file. + ${lib.concatMapStringsSep "\n" ( + { name, sieveScript }: + if lib.isString sieveScript then + '' + if (! test -d "${sieveDirectory}/${name}"); then + mkdir -p "${sieveDirectory}/${name}" + chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}" + chmod 770 "${sieveDirectory}/${name}" + fi + cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve" + ${sieveScript} + EOF + chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve" + '' + else + '' + if (test -f "${sieveDirectory}/${name}/default.sieve"); then + rm "${sieveDirectory}/${name}/default.sieve" + fi + if (test -f "${sieveDirectory}/${name}.svbin"); then + rm "${sieveDirectory}/${name}/default.svbin" + fi + '' + ) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))} + ''; in { config = lib.mkIf enable { diff --git a/tests/clamav.nix b/tests/clamav.nix index e0cfb43..900dd32 100644 --- a/tests/clamav.nix +++ b/tests/clamav.nix @@ -144,111 +144,117 @@ password user2 ''; }; - "root/virus-email".text = '' - From: User2 - Content-Type: multipart/mixed; - boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607" - Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\)) - Subject: Testy McTest - Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com> - Date: Sat, 12 May 2018 14:15:44 +0200 - To: User1 - X-Mailer: Apple Mail (2.3445.6.18) + "root/virus-email".text = + # mail + '' + From: User2 + Content-Type: multipart/mixed; + boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607" + Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\)) + Subject: Testy McTest + Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com> + Date: Sat, 12 May 2018 14:15:44 +0200 + To: User1 + X-Mailer: Apple Mail (2.3445.6.18) - --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 - Content-Transfer-Encoding: 7bit - Content-Type: text/plain; - charset=us-ascii + --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 + Content-Transfer-Encoding: 7bit + Content-Type: text/plain; + charset=us-ascii - Hello + Hello - I have attached a dangerous virus. + I have attached a dangerous virus. - Mfg. - User2 + Mfg. + User2 - --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 - Content-Disposition: attachment; - filename=eicar.com.txt - Content-Type: text/plain; - x-unix-mode=0644; - name="eicar.com.txt" - Content-Transfer-Encoding: 7bit + --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 + Content-Disposition: attachment; + filename=eicar.com.txt + Content-Type: text/plain; + x-unix-mode=0644; + name="eicar.com.txt" + Content-Transfer-Encoding: 7bit - X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* - --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607-- - ''; - "root/safe-email".text = '' - From: User - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user@example2.com to user1 - Reply-To: + X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* + --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607-- + ''; + "root/safe-email".text = + # mail + '' + From: User + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user@example2.com to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? + how are you doing today? - XOXO User1 - ''; + XOXO User1 + ''; }; }; }; - testScript = '' - start_all() + testScript = + # python + '' + start_all() - server.wait_for_unit("multi-user.target") - client.wait_for_unit("multi-user.target") + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") - # TODO put this blocking into the systemd units? I am not sure if rspamd already waits for the clamd socket. - server.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) - server.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]" - ) + # TODO put this blocking into the systemd units? I am not sure if rspamd already waits for the clamd socket. + server.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) + server.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]" + ) - client.execute("cp -p /etc/root/.* ~/") - client.succeed("mkdir -p ~/mail") - client.succeed("ls -la ~/ >&2") - client.succeed("cat ~/.fetchmailrc >&2") - client.succeed("cat ~/.procmailrc >&2") - client.succeed("cat ~/.msmtprc >&2") + client.execute("cp -p /etc/root/.* ~/") + client.succeed("mkdir -p ~/mail") + client.succeed("ls -la ~/ >&2") + client.succeed("cat ~/.fetchmailrc >&2") + client.succeed("cat ~/.procmailrc >&2") + client.succeed("cat ~/.msmtprc >&2") - # fetchmail returns EXIT_CODE 1 when no new mail - client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") + # fetchmail returns EXIT_CODE 1 when no new mail + client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") - # Verify that mail can be sent and received before testing virus scanner - client.execute("rm ~/mail/*") - client.succeed("msmtp -a user2 user1@example.com < /etc/root/safe-email >&2") - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - client.execute("rm ~/mail/*") - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v >&2") - client.execute("rm ~/mail/*") + # Verify that mail can be sent and received before testing virus scanner + client.execute("rm ~/mail/*") + client.succeed("msmtp -a user2 user1@example.com < /etc/root/safe-email >&2") + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + client.execute("rm ~/mail/*") + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v >&2") + client.execute("rm ~/mail/*") - with subtest("virus scan file"): - server.succeed( - 'set +o pipefail; clamdscan $(readlink -f /etc/root/eicar.com.txt) | grep "Txt\\.Malware\\.Agent-1787597 FOUND" >&2' - ) + with subtest("virus scan file"): + server.succeed( + 'set +o pipefail; clamdscan $(readlink -f /etc/root/eicar.com.txt) | grep "Txt\\.Malware\\.Agent-1787597 FOUND" >&2' + ) - with subtest("virus scan email"): - client.succeed( - 'set +o pipefail; msmtp -a user2 user1@example.com < /etc/root/virus-email 2>&1 | tee /dev/stderr | grep "server message: 554 5\\.7\\.1" >&2' - ) - server.succeed("journalctl -u rspamd | grep -i eicar") - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("virus scan email"): + client.succeed( + 'set +o pipefail; msmtp -a user2 user1@example.com < /etc/root/virus-email 2>&1 | tee /dev/stderr | grep "server message: 554 5\\.7\\.1" >&2' + ) + server.succeed("journalctl -u rspamd | grep -i eicar") + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - with subtest("no warnings or errors"): - server.fail("journalctl -u postfix | grep -i error >&2") - server.fail("journalctl -u postfix | grep -i warning >&2") - server.fail("journalctl -u dovecot | grep -i error >&2") - server.fail("journalctl -u dovecot | grep -i warning >&2") - ''; + with subtest("no warnings or errors"): + server.fail("journalctl -u postfix | grep -i error >&2") + server.fail("journalctl -u postfix | grep -i warning >&2") + server.fail("journalctl -u dovecot | grep -i error >&2") + server.fail("journalctl -u dovecot | grep -i warning >&2") + ''; } diff --git a/tests/external.nix b/tests/external.nix index e4ff571..fd2c267 100644 --- a/tests/external.nix +++ b/tests/external.nix @@ -121,80 +121,89 @@ echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2 exec grep '^Message-ID:.*@mail.example.com>$' "$@" ''; - test-imap-spam = pkgs.writeScriptBin "imap-mark-spam" '' - #!${pkgs.python3.interpreter} - import imaplib + test-imap-spam = + pkgs.writeScriptBin "imap-mark-spam" + # python + '' + #!${pkgs.python3.interpreter} + import imaplib - with imaplib.IMAP4_SSL('${serverIP}') as imap: - imap.login('user1@example.com', 'user1') - imap.select() - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + with imaplib.IMAP4_SSL('${serverIP}') as imap: + imap.login('user1@example.com', 'user1') + imap.select() + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.copy(','.join(msg_ids), 'Junk') - for num in msg_ids: - imap.store(num, '+FLAGS', '\\Deleted') - imap.expunge() + imap.copy(','.join(msg_ids), 'Junk') + for num in msg_ids: + imap.store(num, '+FLAGS', '\\Deleted') + imap.expunge() - imap.select('Junk') - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + imap.select('Junk') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.close() - ''; - test-imap-ham = pkgs.writeScriptBin "imap-mark-ham" '' - #!${pkgs.python3.interpreter} - import imaplib + imap.close() + ''; + test-imap-ham = + pkgs.writeScriptBin "imap-mark-ham" + # python + '' + #!${pkgs.python3.interpreter} + import imaplib - with imaplib.IMAP4_SSL('${serverIP}') as imap: - imap.login('user1@example.com', 'user1') - imap.select('Junk') - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + with imaplib.IMAP4_SSL('${serverIP}') as imap: + imap.login('user1@example.com', 'user1') + imap.select('Junk') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.copy(','.join(msg_ids), 'INBOX') - for num in msg_ids: - imap.store(num, '+FLAGS', '\\Deleted') - imap.expunge() + imap.copy(','.join(msg_ids), 'INBOX') + for num in msg_ids: + imap.store(num, '+FLAGS', '\\Deleted') + imap.expunge() - imap.select('INBOX') - status, [response] = imap.search(None, 'ALL') - msg_ids = response.decode("utf-8").split(' ') - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 + imap.select('INBOX') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 - imap.close() - ''; - search = pkgs.writeScriptBin "search" '' - #!${pkgs.python3.interpreter} - import imaplib - import sys + imap.close() + ''; + search = + pkgs.writeScriptBin "search" + # python + '' + #!${pkgs.python3.interpreter} + import imaplib + import sys - [_, mailbox, needle] = sys.argv + [_, mailbox, needle] = sys.argv - with imaplib.IMAP4_SSL('${serverIP}') as imap: - imap.login('user1@example.com', 'user1') - imap.select(mailbox) - status, [response] = imap.search(None, 'BODY', repr(needle)) - msg_ids = [ i for i in response.decode("utf-8").split(' ') if i ] - print(msg_ids) - assert status == 'OK' - assert len(msg_ids) == 1 - status, response = imap.fetch(msg_ids[0], '(RFC822)') - assert status == "OK" - assert needle in repr(response) - imap.close() - ''; + with imaplib.IMAP4_SSL('${serverIP}') as imap: + imap.login('user1@example.com', 'user1') + imap.select(mailbox) + status, [response] = imap.search(None, 'BODY', repr(needle)) + msg_ids = [ i for i in response.decode("utf-8").split(' ') if i ] + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 + status, response = imap.fetch(msg_ids[0], '(RFC822)') + assert status == "OK" + assert needle in repr(response) + imap.close() + ''; in { imports = [ @@ -269,282 +278,298 @@ password user1 ''; }; - "root/email1".text = '' - Message-ID: <12345qwerty@host.local.network> - From: User2 - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user2 to user1 - Reply-To: + "root/email1".text = + # mail + '' + Message-ID: <12345qwerty@host.local.network> + From: User2 + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user2 to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? - ''; - "root/email2".text = '' - Message-ID: <232323abc@host.local.network> - From: User - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user@example2.com to user1 - Reply-To: + how are you doing today? + ''; + "root/email2".text = + # mail + '' + Message-ID: <232323abc@host.local.network> + From: User + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user@example2.com to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? + how are you doing today? - XOXO User1 - ''; - "root/email3".text = '' - Message-ID: - From: Postmaster - To: Chuck - Cc: - Bcc: - Subject: This is a test Email from postmaster@example.com to chuck - Reply-To: + XOXO User1 + ''; + "root/email3".text = + # mail + '' + Message-ID: + From: Postmaster + To: Chuck + Cc: + Bcc: + Subject: This is a test Email from postmaster@example.com to chuck + Reply-To: - Hello Chuck, + Hello Chuck, - I think I may have misconfigured the mail server - XOXO Postmaster - ''; - "root/email4".text = '' - Message-ID: - From: Single Alias - To: User1 - Cc: - Bcc: - Subject: This is a test Email from single-alias@example.com to user1 - Reply-To: + I think I may have misconfigured the mail server + XOXO Postmaster + ''; + "root/email4".text = + # mail + '' + Message-ID: + From: Single Alias + To: User1 + Cc: + Bcc: + Subject: This is a test Email from single-alias@example.com to user1 + Reply-To: - Hello User1, + Hello User1, - how are you doing today? + how are you doing today? - XOXO User1 aka Single Alias - ''; - "root/email5".text = '' - Message-ID: <789asdf@host.local.network> - From: User2 - To: Multi Alias - Cc: - Bcc: - Subject: This is a test Email from user2@example.com to multi-alias - Reply-To: + XOXO User1 aka Single Alias + ''; + "root/email5".text = + # mail + '' + Message-ID: <789asdf@host.local.network> + From: User2 + To: Multi Alias + Cc: + Bcc: + Subject: This is a test Email from user2@example.com to multi-alias + Reply-To: - Hello Multi Alias, + Hello Multi Alias, - how are we doing today? + how are we doing today? - XOXO User1 - ''; - "root/email6".text = '' - Message-ID: <123457qwerty@host.local.network> - From: User2 - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user2 to user1 - Reply-To: + XOXO User1 + ''; + "root/email6".text = + # mail + '' + Message-ID: <123457qwerty@host.local.network> + From: User2 + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user2 to user1 + Reply-To: - Hello User1, + Hello User1, - this email contains the needle: - 576a4565b70f5a4c1a0925cabdb587a6 - ''; - "root/email7".text = '' - Message-ID: <1234578qwerty@host.local.network> - From: User2 - To: User1 - Cc: - Bcc: - Subject: This is a test Email from user2 to user1 - Reply-To: + this email contains the needle: + 576a4565b70f5a4c1a0925cabdb587a6 + ''; + "root/email7".text = + # mail + '' + Message-ID: <1234578qwerty@host.local.network> + From: User2 + To: User1 + Cc: + Bcc: + Subject: This is a test Email from user2 to user1 + Reply-To: - Hello User1, + Hello User1, - this email does not contain the needle :( - ''; + this email does not contain the needle :( + ''; }; }; }; - testScript = '' - start_all() + testScript = + # python + '' + start_all() - server.wait_for_unit("multi-user.target") - client.wait_for_unit("multi-user.target") + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") - # TODO put this blocking into the systemd units? - server.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) + # TODO put this blocking into the systemd units? + server.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) - server.succeed("rspamadm dkim_keygen > /run/rspamd/dkim-test.key") - server.succeed("chown rspamd: /run/rspamd/dkim-test.key") + server.succeed("rspamadm dkim_keygen > /run/rspamd/dkim-test.key") + server.succeed("chown rspamd: /run/rspamd/dkim-test.key") - client.execute("cp -p /etc/root/.* ~/") - client.succeed("mkdir -p ~/mail") - client.succeed("ls -la ~/ >&2") - client.succeed("cat ~/.fetchmailrc >&2") - client.succeed("cat ~/.procmailrc >&2") - client.succeed("cat ~/.msmtprc >&2") + client.execute("cp -p /etc/root/.* ~/") + client.succeed("mkdir -p ~/mail") + client.succeed("ls -la ~/ >&2") + client.succeed("cat ~/.fetchmailrc >&2") + client.succeed("cat ~/.procmailrc >&2") + client.succeed("cat ~/.msmtprc >&2") - with subtest("imap retrieving mail"): - # fetchmail returns EXIT_CODE 1 when no new mail - client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") + with subtest("imap retrieving mail"): + # fetchmail returns EXIT_CODE 1 when no new mail + client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") - with subtest("submission port send mail"): - # send email from user2 to user1 - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" - ) - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("submission port send mail"): + # send email from user2 to user1 + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" + ) + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - with subtest("imap retrieving mail 2"): - client.execute("rm ~/mail/*") - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v >&2") + with subtest("imap retrieving mail 2"): + client.execute("rm ~/mail/*") + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v >&2") - with subtest("remove sensitive information on submission port"): - client.succeed("cat ~/mail/* >&2") - ## make sure our IP is _not_ in the email header - client.fail("grep-ip ~/mail/*") - client.succeed("check-mail-id ~/mail/*") + with subtest("remove sensitive information on submission port"): + client.succeed("cat ~/mail/* >&2") + ## make sure our IP is _not_ in the email header + client.fail("grep-ip ~/mail/*") + client.succeed("check-mail-id ~/mail/*") - with subtest("have correct fqdn as sender"): - client.succeed("grep 'Received: from mail.example.com' ~/mail/*") + with subtest("have correct fqdn as sender"): + client.succeed("grep 'Received: from mail.example.com' ~/mail/*") - with subtest("dkim has user-specified size"): - server.succeed( - "openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'" - ) + with subtest("dkim has user-specified size"): + server.succeed( + "openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'" + ) - with subtest("dkim signing, multiple domains"): - client.execute("rm ~/mail/*") - # send email from user2 to user1 - client.succeed( - "msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") - client.succeed("cat ~/mail/* >&2") - # make sure it is dkim signed - client.succeed("grep 's=dkim-rsa' ~/mail/*") - client.succeed("grep 's=dkim-ed25519' ~/mail/*") - client.succeed("grep 's=dkim-file' ~/mail/*") + with subtest("dkim signing, multiple domains"): + client.execute("rm ~/mail/*") + # send email from user2 to user1 + client.succeed( + "msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") + client.succeed("cat ~/mail/* >&2") + # make sure it is dkim signed + client.succeed("grep 's=dkim-rsa' ~/mail/*") + client.succeed("grep 's=dkim-ed25519' ~/mail/*") + client.succeed("grep 's=dkim-file' ~/mail/*") - with subtest("aliases"): - client.execute("rm ~/mail/*") - # send email from chuck to postmaster - client.succeed( - "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on postmaster@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + with subtest("aliases"): + client.execute("rm ~/mail/*") + # send email from chuck to postmaster + client.succeed( + "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on postmaster@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - with subtest("domain catch-all"): - client.execute("rm ~/mail/*") - # send email from chuck to non-existent account - client.succeed( - "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lol@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + with subtest("domain catch-all"): + client.execute("rm ~/mail/*") + # send email from chuck to non-existent account + client.succeed( + "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lol@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - client.execute("rm ~/mail/*") - # send email from user1 to chuck - client.succeed( - "msmtp -a test4 --tls=on --tls-certcheck=off --auth=on chuck@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 1 when no new mail - # if this succeeds, it means that user1 received the mail that was intended for chuck. - client.fail("fetchmail --nosslcertck -v") + client.execute("rm ~/mail/*") + # send email from user1 to chuck + client.succeed( + "msmtp -a test4 --tls=on --tls-certcheck=off --auth=on chuck@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 1 when no new mail + # if this succeeds, it means that user1 received the mail that was intended for chuck. + client.fail("fetchmail --nosslcertck -v") - with subtest("extraVirtualAliases"): - client.execute("rm ~/mail/*") - # send email from single-alias to user1 - client.succeed( - "msmtp -a test5 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email4 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + with subtest("extraVirtualAliases"): + client.execute("rm ~/mail/*") + # send email from single-alias to user1 + client.succeed( + "msmtp -a test5 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email4 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - client.execute("rm ~/mail/*") - # send email from user1 to multi-alias (user{1,2}@example.com) - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on multi-alias@example.com < /etc/root/email5 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.succeed("fetchmail --nosslcertck -v") + client.execute("rm ~/mail/*") + # send email from user1 to multi-alias (user{1,2}@example.com) + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on multi-alias@example.com < /etc/root/email5 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("fetchmail --nosslcertck -v") - with subtest("quota"): - client.execute("rm ~/mail/*") - client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc") + with subtest("quota"): + client.execute("rm ~/mail/*") + client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc") - client.succeed( - "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota@example.com < /etc/root/email2 >&2" - ) - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # fetchmail returns EXIT_CODE 0 when it retrieves mail - client.fail("fetchmail --nosslcertck -v") + client.succeed( + "msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota@example.com < /etc/root/email2 >&2" + ) + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.fail("fetchmail --nosslcertck -v") - with subtest("imap sieve junk trainer"): - # send email from user2 to user1 - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" - ) - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("imap sieve junk trainer"): + # send email from user2 to user1 + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" + ) + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - client.succeed("imap-mark-spam >&2") - server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2") - client.succeed("imap-mark-ham >&2") - server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2") + client.succeed("imap-mark-spam >&2") + server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2") + client.succeed("imap-mark-ham >&2") + server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2") - with subtest("full text search and indexation"): - # send 2 email from user2 to user1 - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email6 >&2" - ) - client.succeed( - "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email7 >&2" - ) - # give the mail server some time to process the mail - server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') + with subtest("full text search and indexation"): + # send 2 email from user2 to user1 + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email6 >&2" + ) + client.succeed( + "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email7 >&2" + ) + # give the mail server some time to process the mail + server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') - # should find exactly one email containing this - client.succeed("search INBOX 576a4565b70f5a4c1a0925cabdb587a6 >&2") - # should fail because this folder is not indexed - client.fail("search Junk a >&2") - # check that search really goes through the indexer - server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2") - # check that Junk is not indexed - server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2") + # should find exactly one email containing this + client.succeed("search INBOX 576a4565b70f5a4c1a0925cabdb587a6 >&2") + # should fail because this folder is not indexed + client.fail("search Junk a >&2") + # check that search really goes through the indexer + server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2") + # check that Junk is not indexed + server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2") - with subtest("dmarc reporting"): - server.systemctl("start rspamd-dmarc-reporter.service") + with subtest("dmarc reporting"): + server.systemctl("start rspamd-dmarc-reporter.service") - with subtest("no warnings or errors"): - server.fail("journalctl -u postfix | grep -i error >&2") - server.fail("journalctl -u postfix | grep -i warning >&2") - server.fail("journalctl -u dovecot | grep -v 'imap-login: Debug: SSL error: Connection closed' | grep -i error >&2") - # harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html - server.fail( - "journalctl -u dovecot | \ - grep -v 'Expunged message reappeared, giving a new UID' | \ - grep -v 'Time moved forwards' | \ - grep -i warning >&2" - ) - ''; + with subtest("no warnings or errors"): + server.fail("journalctl -u postfix | grep -i error >&2") + server.fail("journalctl -u postfix | grep -i warning >&2") + server.fail("journalctl -u dovecot | grep -v 'imap-login: Debug: SSL error: Connection closed' | grep -i error >&2") + # harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html + server.fail( + "journalctl -u dovecot | \ + grep -v 'Expunged message reappeared, giving a new UID' | \ + grep -v 'Time moved forwards' | \ + grep -i warning >&2" + ) + ''; } diff --git a/tests/internal.nix b/tests/internal.nix index 36bc4b3..a3cbfb8 100644 --- a/tests/internal.nix +++ b/tests/internal.nix @@ -122,6 +122,7 @@ in nodes, ... }: + # python '' machine.start() machine.wait_for_unit("multi-user.target") diff --git a/tests/ldap.nix b/tests/ldap.nix index c150567..d8f5af3 100644 --- a/tests/ldap.nix +++ b/tests/ldap.nix @@ -53,36 +53,38 @@ in }; }; }; - declarativeContents."dc=example" = '' - dn: dc=example - objectClass: domain - dc: example + declarativeContents."dc=example" = + #ldif + '' + dn: dc=example + objectClass: domain + dc: example - dn: cn=mail,dc=example - objectClass: organizationalRole - objectClass: simpleSecurityObject - objectClass: top - cn: mail - userPassword: ${bindPassword} + dn: cn=mail,dc=example + objectClass: organizationalRole + objectClass: simpleSecurityObject + objectClass: top + cn: mail + userPassword: ${bindPassword} - dn: ou=users,dc=example - objectClass: organizationalUnit - ou: users + dn: ou=users,dc=example + objectClass: organizationalUnit + ou: users - dn: cn=alice,ou=users,dc=example - objectClass: inetOrgPerson - cn: alice - sn: Foo - mail: alice@example.com - userPassword: ${alicePassword} + dn: cn=alice,ou=users,dc=example + objectClass: inetOrgPerson + cn: alice + sn: Foo + mail: alice@example.com + userPassword: ${alicePassword} - dn: cn=bob,ou=users,dc=example - objectClass: inetOrgPerson - cn: bob - sn: Bar - mail: bob@example.com - userPassword: ${bobPassword} - ''; + dn: cn=bob,ou=users,dc=example + objectClass: inetOrgPerson + cn: bob + sn: Bar + mail: bob@example.com + userPassword: ${bobPassword} + ''; }; mailserver = { @@ -121,6 +123,7 @@ in nodes, ... }: + # python '' import sys import re diff --git a/tests/multiple.nix b/tests/multiple.nix index 19b7aee..3f9b5db 100644 --- a/tests/multiple.nix +++ b/tests/multiple.nix @@ -90,29 +90,31 @@ in ]; }; }; - testScript = '' - start_all() + testScript = + # python + '' + start_all() - for domain in [domain1, domain2]: - domain.wait_for_unit("multi-user.target") - domain.wait_for_unit("dovecot.service") + for domain in [domain1, domain2]: + domain.wait_for_unit("multi-user.target") + domain.wait_for_unit("dovecot.service") - # TODO put this blocking into the systemd units? - domain1.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) - domain2.wait_until_succeeds( - "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" - ) + # TODO put this blocking into the systemd units? + domain1.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) + domain2.wait_until_succeeds( + "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" + ) - # user@domain1.com sends a mail to user@domain2.com via explicit TLS - client.succeed( - "mail-check send-and-read --smtp-port 587 --smtp-starttls --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" - ) + # user@domain1.com sends a mail to user@domain2.com via explicit TLS + client.succeed( + "mail-check send-and-read --smtp-port 587 --smtp-starttls --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" + ) - # Send a mail to the address forwarded via implicit TLS and check it is in the recipient mailbox - client.succeed( - "mail-check send-and-read --smtp-port 465 --smtp-ssl --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr non-local@domain1.com --imap-username user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" - ) - ''; + # Send a mail to the address forwarded via implicit TLS and check it is in the recipient mailbox + client.succeed( + "mail-check send-and-read --smtp-port 465 --smtp-ssl --smtp-host domain1 --from-addr user@domain1.com --imap-host domain2 --to-addr non-local@domain1.com --imap-username user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf" + ) + ''; }