Merge branch 'qol-changes' into 'master'

treewide: inline language instructions, reorganize imports

See merge request simple-nixos-mailserver/nixos-mailserver!492
This commit is contained in:
Martin Weinelt
2026-03-12 12:51:08 +00:00
11 changed files with 606 additions and 538 deletions
+12
View File
@@ -0,0 +1,12 @@
# Ignore non-functional treewide changes by configuring
#
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
#
# or used temporarily with --ignore-revs-file=
#
# nixfmt
1a7f3d718c5a6406b7d5b54f10f5c9c69ed90ef9
# language hints
06cc71c76eb52dc747704a317ac5e175ebdd2ba8
+14 -20
View File
@@ -1524,6 +1524,9 @@ in
}; };
imports = [ imports = [
./mail-server
# NixOS 25.05
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] '' (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] ''
This option is not needed for fts-flatcurve This option is not needed for fts-flatcurve
'') '')
@@ -1549,19 +1552,6 @@ in
(mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] '' (mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] ''
Use `system.autoUpgrade` instead. Use `system.autoUpgrade` instead.
'') '')
./mail-server/assertions.nix
./mail-server/borgbackup.nix
./mail-server/rsnapshot.nix
./mail-server/clamav.nix
./mail-server/monit.nix
./mail-server/users.nix
./mail-server/environment.nix
./mail-server/networking.nix
./mail-server/systemd.nix
./mail-server/dovecot.nix
./mail-server/postfix.nix
./mail-server/rspamd.nix
./mail-server/kresd.nix
(mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] '' (mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] ''
SPF checking has been migrated to Rspamd, which makes this config redundant. Please look into the rspamd config to migrate your settings. SPF checking has been migrated to Rspamd, which makes this config redundant. Please look into the rspamd config to migrate your settings.
It may be that they are redundant and are already configured in rspamd like for skip_addresses. It may be that they are redundant and are already configured in rspamd like for skip_addresses.
@@ -1575,13 +1565,8 @@ in
(mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] '' (mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] ''
The workaround for the SMTP Smuggling attack is default enabled in Postfix >3.9. Use `services.postfix.config.smtpd_forbid_bare_newline` if you need to deviate from its default. The workaround for the SMTP Smuggling attack is default enabled in Postfix >3.9. Use `services.postfix.config.smtpd_forbid_bare_newline` if you need to deviate from its default.
'') '')
(mkRenamedOptionModule [ "mailserver" "dkimSigning" ] [ "mailserver" "dkim" "enable" ])
(mkRenamedOptionModule [ "mailserver" "dkimKeyDirectory" ] [ "mailserver" "dkim" "keyDirectory" ]) # NixOS 25.11
(mkRenamedOptionModule
[ "mailserver" "dkimSelector" ]
[ "mailserver" "dkim" "defaults" "selector" ]
)
(mkRenamedOptionModule [ "mailserver" "dkimKeyType" ] [ "mailserver" "dkim" "defaults" "keyType" ])
(mkRenamedOptionModule [ "mailserver" "dmarcReporting" "domain" ] [ "mailserver" "systemDomain" ]) (mkRenamedOptionModule [ "mailserver" "dmarcReporting" "domain" ] [ "mailserver" "systemDomain" ])
(mkRenamedOptionModule (mkRenamedOptionModule
[ "mailserver" "dmarcReporting" "organizationName" ] [ "mailserver" "dmarcReporting" "organizationName" ]
@@ -1597,6 +1582,7 @@ in
The name in the `FROM` field for DMARC report now uses the `mailserver.systemName`. The name in the `FROM` field for DMARC report now uses the `mailserver.systemName`.
'') '')
# NixOS 26.05
(mkRemovedOptionModule [ "mailserver" "certificateDomains" ] '' (mkRemovedOptionModule [ "mailserver" "certificateDomains" ] ''
Configure `security.acme.certs.''${config.mailserver.fqdn}.extraDomains` instead. Configure `security.acme.certs.''${config.mailserver.fqdn}.extraDomains` instead.
'') '')
@@ -1607,5 +1593,13 @@ in
(mkRenamedOptionModule [ "mailserver" "acmeCertificateName" ] [ "mailserver" "x509" "useACMEHost" ]) (mkRenamedOptionModule [ "mailserver" "acmeCertificateName" ] [ "mailserver" "x509" "useACMEHost" ])
(mkRenamedOptionModule [ "mailserver" "certificateFile" ] [ "mailserver" "x509" "certificateFile" ]) (mkRenamedOptionModule [ "mailserver" "certificateFile" ] [ "mailserver" "x509" "certificateFile" ])
(mkRenamedOptionModule [ "mailserver" "keyFile" ] [ "mailserver" "x509" "privateKeyFile" ]) (mkRenamedOptionModule [ "mailserver" "keyFile" ] [ "mailserver" "x509" "privateKeyFile" ])
(mkRenamedOptionModule [ "mailserver" "dkimSigning" ] [ "mailserver" "dkim" "enable" ])
(mkRenamedOptionModule [ "mailserver" "dkimKeyDirectory" ] [ "mailserver" "dkim" "keyDirectory" ])
(mkRenamedOptionModule
[ "mailserver" "dkimSelector" ]
[ "mailserver" "dkim" "defaults" "selector" ]
)
(mkRenamedOptionModule [ "mailserver" "dkimKeyType" ] [ "mailserver" "dkim" "defaults" "keyType" ])
]; ];
} }
+16 -14
View File
@@ -69,21 +69,23 @@ rec {
passwordFile, passwordFile,
destination, destination,
}: }:
pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' pkgs.writeScript "append-ldap-bind-pwd-in-${name}"
#!${pkgs.stdenv.shell} # bash
set -euo pipefail ''
#!${pkgs.stdenv.shell}
set -euo pipefail
baseDir=$(dirname ${destination}) baseDir=$(dirname ${destination})
if (! test -d "$baseDir"); then if (! test -d "$baseDir"); then
mkdir -p $baseDir mkdir -p $baseDir
chmod 755 $baseDir chmod 755 $baseDir
fi fi
cat ${file} > ${destination} cat ${file} > ${destination}
echo -n '${prefix}' >> ${destination} echo -n '${prefix}' >> ${destination}
cat ${passwordFile} | tr -d '\n' >> ${destination} cat ${passwordFile} | tr -d '\n' >> ${destination}
echo -n '${suffix}' >> ${destination} echo -n '${suffix}' >> ${destination}
chmod 600 ${destination} chmod 600 ${destination}
''; '';
} }
+17
View File
@@ -0,0 +1,17 @@
{
imports = [
./assertions.nix
./borgbackup.nix
./rsnapshot.nix
./clamav.nix
./monit.nix
./users.nix
./environment.nix
./networking.nix
./systemd.nix
./dovecot.nix
./postfix.nix
./rspamd.nix
./kresd.nix
];
}
+41 -38
View File
@@ -96,50 +96,53 @@ let
destination = ldapConfFile; destination = ldapConfFile;
}; };
genPasswdScript = pkgs.writeScript "generate-password-file" '' genPasswdScript =
#!${pkgs.stdenv.shell} pkgs.writeScript "generate-password-file"
# bash
''
#!${pkgs.stdenv.shell}
set -euo pipefail set -euo pipefail
if (! test -d "${passwdDir}"); then if (! test -d "${passwdDir}"); then
mkdir "${passwdDir}" mkdir "${passwdDir}"
chmod 755 "${passwdDir}" chmod 755 "${passwdDir}"
fi fi
# Prevent world-readable password files, even temporarily. # Prevent world-readable password files, even temporarily.
umask 077 umask 077
for f in ${ for f in ${
builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts) builtins.toString (lib.mapAttrsToList (name: _: passwordFiles."${name}") cfg.loginAccounts)
}; do }; do
if [ ! -f "$f" ]; then if [ ! -f "$f" ]; then
echo "Expected password hash file $f does not exist!" echo "Expected password hash file $f does not exist!"
exit 1 exit 1
fi fi
done done
cat <<EOF > ${passwdFile} cat <<EOF > ${passwdFile}
${lib.concatStringsSep "\n" ( ${lib.concatStringsSep "\n" (
lib.mapAttrsToList ( lib.mapAttrsToList (
name: _: name: _:
if lib.elem name accountsWithPlaintextPasswordFiles then if lib.elem name accountsWithPlaintextPasswordFiles then
"${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::" "${name}:${"$(sed -n '1{p;p;q}' ${passwordFiles."${name}"} | ${lib.getExe' pkgs.dovecot "doveadm"} pw)"}::::::"
else else
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
) cfg.loginAccounts ) cfg.loginAccounts
)} )}
EOF EOF
cat <<EOF > ${userdbFile} cat <<EOF > ${userdbFile}
${lib.concatStringsSep "\n" ( ${lib.concatStringsSep "\n" (
lib.mapAttrsToList ( lib.mapAttrsToList (
name: value: name: value:
"${name}:::::::" "${name}:::::::"
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}" + lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
) cfg.loginAccounts ) cfg.loginAccounts
)} )}
EOF EOF
''; '';
junkMailboxes = builtins.attrNames ( junkMailboxes = builtins.attrNames (
lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes lib.filterAttrs (_: v: v ? "specialUse" && v.specialUse == "Junk") cfg.mailboxes
+41 -38
View File
@@ -43,48 +43,51 @@ let
group = vmailGroupName; group = vmailGroupName;
}; };
virtualMailUsersActivationScript = pkgs.writeScript "activate-virtual-mail-users" '' virtualMailUsersActivationScript =
#!${pkgs.stdenv.shell} pkgs.writeScript "activate-virtual-mail-users"
# bash
''
#!${pkgs.stdenv.shell}
set -euo pipefail set -euo pipefail
# Prevent world-readable paths, even temporarily. # Prevent world-readable paths, even temporarily.
umask 007 umask 007
# Create directory to store user sieve scripts if it doesn't exist # Create directory to store user sieve scripts if it doesn't exist
if (! test -d "${sieveDirectory}"); then if (! test -d "${sieveDirectory}"); then
mkdir "${sieveDirectory}" mkdir "${sieveDirectory}"
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}" chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}"
chmod 770 "${sieveDirectory}" chmod 770 "${sieveDirectory}"
fi fi
# Copy user's sieve script to the correct location (if it exists). If it # Copy user's sieve script to the correct location (if it exists). If it
# is null, remove the file. # is null, remove the file.
${lib.concatMapStringsSep "\n" ( ${lib.concatMapStringsSep "\n" (
{ name, sieveScript }: { name, sieveScript }:
if lib.isString sieveScript then if lib.isString sieveScript then
'' ''
if (! test -d "${sieveDirectory}/${name}"); then if (! test -d "${sieveDirectory}/${name}"); then
mkdir -p "${sieveDirectory}/${name}" mkdir -p "${sieveDirectory}/${name}"
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}" chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}"
chmod 770 "${sieveDirectory}/${name}" chmod 770 "${sieveDirectory}/${name}"
fi fi
cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve" cat << 'EOF' > "${sieveDirectory}/${name}/default.sieve"
${sieveScript} ${sieveScript}
EOF EOF
chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve" chown "${vmailUserName}:${vmailGroupName}" "${sieveDirectory}/${name}/default.sieve"
'' ''
else else
'' ''
if (test -f "${sieveDirectory}/${name}/default.sieve"); then if (test -f "${sieveDirectory}/${name}/default.sieve"); then
rm "${sieveDirectory}/${name}/default.sieve" rm "${sieveDirectory}/${name}/default.sieve"
fi fi
if (test -f "${sieveDirectory}/${name}.svbin"); then if (test -f "${sieveDirectory}/${name}.svbin"); then
rm "${sieveDirectory}/${name}/default.svbin" rm "${sieveDirectory}/${name}/default.svbin"
fi fi
'' ''
) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))} ) (map (user: { inherit (user) name sieveScript; }) (lib.attrValues loginAccounts))}
''; '';
in in
{ {
config = lib.mkIf enable { config = lib.mkIf enable {
+90 -84
View File
@@ -144,111 +144,117 @@
password user2 password user2
''; '';
}; };
"root/virus-email".text = '' "root/virus-email".text =
From: User2 <user@example2.com> # mail
Content-Type: multipart/mixed; ''
boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607" From: User2 <user@example2.com>
Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\)) Content-Type: multipart/mixed;
Subject: Testy McTest boundary="Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607"
Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com> Mime-Version: 1.0 (Mac OS X Mail 11.3 \(3445.6.18\))
Date: Sat, 12 May 2018 14:15:44 +0200 Subject: Testy McTest
To: User1 <user1@example.com> Message-Id: <94550DD9-1FF1-4ED1-9F09-8812FF2E59AA@example.com>
X-Mailer: Apple Mail (2.3445.6.18) Date: Sat, 12 May 2018 14:15:44 +0200
To: User1 <user1@example.com>
X-Mailer: Apple Mail (2.3445.6.18)
--Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
Content-Type: text/plain; Content-Type: text/plain;
charset=us-ascii charset=us-ascii
Hello Hello
I have attached a dangerous virus. I have attached a dangerous virus.
Mfg. Mfg.
User2 User2
--Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607 --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607
Content-Disposition: attachment; Content-Disposition: attachment;
filename=eicar.com.txt filename=eicar.com.txt
Content-Type: text/plain; Content-Type: text/plain;
x-unix-mode=0644; x-unix-mode=0644;
name="eicar.com.txt" name="eicar.com.txt"
Content-Transfer-Encoding: 7bit Content-Transfer-Encoding: 7bit
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H* X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
--Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607-- --Apple-Mail=_2689C63E-FD18-4E4D-8822-54797BDA9607--
''; '';
"root/safe-email".text = '' "root/safe-email".text =
From: User <user@example2.com> # mail
To: User1 <user1@example.com> ''
Cc: From: User <user@example2.com>
Bcc: To: User1 <user1@example.com>
Subject: This is a test Email from user@example2.com to user1 Cc:
Reply-To: 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 = '' testScript =
start_all() # python
''
start_all()
server.wait_for_unit("multi-user.target") server.wait_for_unit("multi-user.target")
client.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. # TODO put this blocking into the systemd units? I am not sure if rspamd already waits for the clamd socket.
server.wait_until_succeeds( server.wait_until_succeeds(
"set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
) )
server.wait_until_succeeds( server.wait_until_succeeds(
"set +e; timeout 1 nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]" "set +e; timeout 1 nc -U /run/clamav/clamd.ctl < /dev/null; [ $? -eq 124 ]"
) )
client.execute("cp -p /etc/root/.* ~/") client.execute("cp -p /etc/root/.* ~/")
client.succeed("mkdir -p ~/mail") client.succeed("mkdir -p ~/mail")
client.succeed("ls -la ~/ >&2") client.succeed("ls -la ~/ >&2")
client.succeed("cat ~/.fetchmailrc >&2") client.succeed("cat ~/.fetchmailrc >&2")
client.succeed("cat ~/.procmailrc >&2") client.succeed("cat ~/.procmailrc >&2")
client.succeed("cat ~/.msmtprc >&2") client.succeed("cat ~/.msmtprc >&2")
# fetchmail returns EXIT_CODE 1 when no new mail # fetchmail returns EXIT_CODE 1 when no new mail
client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2")
# Verify that mail can be sent and received before testing virus scanner # Verify that mail can be sent and received before testing virus scanner
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
client.succeed("msmtp -a user2 user1@example.com < /etc/root/safe-email >&2") client.succeed("msmtp -a user2 user1@example.com < /etc/root/safe-email >&2")
# give the mail server some time to process the mail # give the mail server some time to process the mail
server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v >&2") client.succeed("fetchmail --nosslcertck -v >&2")
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
with subtest("virus scan file"): with subtest("virus scan file"):
server.succeed( server.succeed(
'set +o pipefail; clamdscan $(readlink -f /etc/root/eicar.com.txt) | grep "Txt\\.Malware\\.Agent-1787597 FOUND" >&2' 'set +o pipefail; clamdscan $(readlink -f /etc/root/eicar.com.txt) | grep "Txt\\.Malware\\.Agent-1787597 FOUND" >&2'
) )
with subtest("virus scan email"): with subtest("virus scan email"):
client.succeed( 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' '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") server.succeed("journalctl -u rspamd | grep -i eicar")
# give the mail server some time to process the mail # give the mail server some time to process the mail
server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
with subtest("no warnings or errors"): with subtest("no warnings or errors"):
server.fail("journalctl -u postfix | grep -i error >&2") server.fail("journalctl -u postfix | grep -i error >&2")
server.fail("journalctl -u postfix | grep -i warning >&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 error >&2")
server.fail("journalctl -u dovecot | grep -i warning >&2") server.fail("journalctl -u dovecot | grep -i warning >&2")
''; '';
} }
+322 -297
View File
@@ -121,80 +121,89 @@
echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2 echo grep '^Message-ID:.*@mail.example.com>$' "$@" >&2
exec grep '^Message-ID:.*@mail.example.com>$' "$@" exec grep '^Message-ID:.*@mail.example.com>$' "$@"
''; '';
test-imap-spam = pkgs.writeScriptBin "imap-mark-spam" '' test-imap-spam =
#!${pkgs.python3.interpreter} pkgs.writeScriptBin "imap-mark-spam"
import imaplib # python
''
#!${pkgs.python3.interpreter}
import imaplib
with imaplib.IMAP4_SSL('${serverIP}') as imap: with imaplib.IMAP4_SSL('${serverIP}') as imap:
imap.login('user1@example.com', 'user1') imap.login('user1@example.com', 'user1')
imap.select() imap.select()
status, [response] = imap.search(None, 'ALL') status, [response] = imap.search(None, 'ALL')
msg_ids = response.decode("utf-8").split(' ') msg_ids = response.decode("utf-8").split(' ')
print(msg_ids) print(msg_ids)
assert status == 'OK' assert status == 'OK'
assert len(msg_ids) == 1 assert len(msg_ids) == 1
imap.copy(','.join(msg_ids), 'Junk') imap.copy(','.join(msg_ids), 'Junk')
for num in msg_ids: for num in msg_ids:
imap.store(num, '+FLAGS', '\\Deleted') imap.store(num, '+FLAGS', '\\Deleted')
imap.expunge() imap.expunge()
imap.select('Junk') imap.select('Junk')
status, [response] = imap.search(None, 'ALL') status, [response] = imap.search(None, 'ALL')
msg_ids = response.decode("utf-8").split(' ') msg_ids = response.decode("utf-8").split(' ')
print(msg_ids) print(msg_ids)
assert status == 'OK' assert status == 'OK'
assert len(msg_ids) == 1 assert len(msg_ids) == 1
imap.close() imap.close()
''; '';
test-imap-ham = pkgs.writeScriptBin "imap-mark-ham" '' test-imap-ham =
#!${pkgs.python3.interpreter} pkgs.writeScriptBin "imap-mark-ham"
import imaplib # python
''
#!${pkgs.python3.interpreter}
import imaplib
with imaplib.IMAP4_SSL('${serverIP}') as imap: with imaplib.IMAP4_SSL('${serverIP}') as imap:
imap.login('user1@example.com', 'user1') imap.login('user1@example.com', 'user1')
imap.select('Junk') imap.select('Junk')
status, [response] = imap.search(None, 'ALL') status, [response] = imap.search(None, 'ALL')
msg_ids = response.decode("utf-8").split(' ') msg_ids = response.decode("utf-8").split(' ')
print(msg_ids) print(msg_ids)
assert status == 'OK' assert status == 'OK'
assert len(msg_ids) == 1 assert len(msg_ids) == 1
imap.copy(','.join(msg_ids), 'INBOX') imap.copy(','.join(msg_ids), 'INBOX')
for num in msg_ids: for num in msg_ids:
imap.store(num, '+FLAGS', '\\Deleted') imap.store(num, '+FLAGS', '\\Deleted')
imap.expunge() imap.expunge()
imap.select('INBOX') imap.select('INBOX')
status, [response] = imap.search(None, 'ALL') status, [response] = imap.search(None, 'ALL')
msg_ids = response.decode("utf-8").split(' ') msg_ids = response.decode("utf-8").split(' ')
print(msg_ids) print(msg_ids)
assert status == 'OK' assert status == 'OK'
assert len(msg_ids) == 1 assert len(msg_ids) == 1
imap.close() imap.close()
''; '';
search = pkgs.writeScriptBin "search" '' search =
#!${pkgs.python3.interpreter} pkgs.writeScriptBin "search"
import imaplib # python
import sys ''
#!${pkgs.python3.interpreter}
import imaplib
import sys
[_, mailbox, needle] = sys.argv [_, mailbox, needle] = sys.argv
with imaplib.IMAP4_SSL('${serverIP}') as imap: with imaplib.IMAP4_SSL('${serverIP}') as imap:
imap.login('user1@example.com', 'user1') imap.login('user1@example.com', 'user1')
imap.select(mailbox) imap.select(mailbox)
status, [response] = imap.search(None, 'BODY', repr(needle)) status, [response] = imap.search(None, 'BODY', repr(needle))
msg_ids = [ i for i in response.decode("utf-8").split(' ') if i ] msg_ids = [ i for i in response.decode("utf-8").split(' ') if i ]
print(msg_ids) print(msg_ids)
assert status == 'OK' assert status == 'OK'
assert len(msg_ids) == 1 assert len(msg_ids) == 1
status, response = imap.fetch(msg_ids[0], '(RFC822)') status, response = imap.fetch(msg_ids[0], '(RFC822)')
assert status == "OK" assert status == "OK"
assert needle in repr(response) assert needle in repr(response)
imap.close() imap.close()
''; '';
in in
{ {
imports = [ imports = [
@@ -269,282 +278,298 @@
password user1 password user1
''; '';
}; };
"root/email1".text = '' "root/email1".text =
Message-ID: <12345qwerty@host.local.network> # mail
From: User2 <user2@example.com> ''
To: User1 <user1@example.com> Message-ID: <12345qwerty@host.local.network>
Cc: From: User2 <user2@example.com>
Bcc: To: User1 <user1@example.com>
Subject: This is a test Email from user2 to user1 Cc:
Reply-To: Bcc:
Subject: This is a test Email from user2 to user1
Reply-To:
Hello User1, Hello User1,
how are you doing today? how are you doing today?
''; '';
"root/email2".text = '' "root/email2".text =
Message-ID: <232323abc@host.local.network> # mail
From: User <user@example2.com> ''
To: User1 <user1@example.com> Message-ID: <232323abc@host.local.network>
Cc: From: User <user@example2.com>
Bcc: To: User1 <user1@example.com>
Subject: This is a test Email from user@example2.com to user1 Cc:
Reply-To: 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
''; '';
"root/email3".text = '' "root/email3".text =
Message-ID: <asdfghjkl42@host.local.network> # mail
From: Postmaster <postmaster@example.com> ''
To: Chuck <chuck@example.com> Message-ID: <asdfghjkl42@host.local.network>
Cc: From: Postmaster <postmaster@example.com>
Bcc: To: Chuck <chuck@example.com>
Subject: This is a test Email from postmaster@example.com to chuck Cc:
Reply-To: 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 I think I may have misconfigured the mail server
XOXO Postmaster XOXO Postmaster
''; '';
"root/email4".text = '' "root/email4".text =
Message-ID: <sdfsdf@host.local.network> # mail
From: Single Alias <single-alias@example.com> ''
To: User1 <user1@example.com> Message-ID: <sdfsdf@host.local.network>
Cc: From: Single Alias <single-alias@example.com>
Bcc: To: User1 <user1@example.com>
Subject: This is a test Email from single-alias@example.com to user1 Cc:
Reply-To: 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 XOXO User1 aka Single Alias
''; '';
"root/email5".text = '' "root/email5".text =
Message-ID: <789asdf@host.local.network> # mail
From: User2 <user2@example.com> ''
To: Multi Alias <multi-alias@example.com> Message-ID: <789asdf@host.local.network>
Cc: From: User2 <user2@example.com>
Bcc: To: Multi Alias <multi-alias@example.com>
Subject: This is a test Email from user2@example.com to multi-alias Cc:
Reply-To: 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 XOXO User1
''; '';
"root/email6".text = '' "root/email6".text =
Message-ID: <123457qwerty@host.local.network> # mail
From: User2 <user2@example.com> ''
To: User1 <user1@example.com> Message-ID: <123457qwerty@host.local.network>
Cc: From: User2 <user2@example.com>
Bcc: To: User1 <user1@example.com>
Subject: This is a test Email from user2 to user1 Cc:
Reply-To: Bcc:
Subject: This is a test Email from user2 to user1
Reply-To:
Hello User1, Hello User1,
this email contains the needle: this email contains the needle:
576a4565b70f5a4c1a0925cabdb587a6 576a4565b70f5a4c1a0925cabdb587a6
''; '';
"root/email7".text = '' "root/email7".text =
Message-ID: <1234578qwerty@host.local.network> # mail
From: User2 <user2@example.com> ''
To: User1 <user1@example.com> Message-ID: <1234578qwerty@host.local.network>
Cc: From: User2 <user2@example.com>
Bcc: To: User1 <user1@example.com>
Subject: This is a test Email from user2 to user1 Cc:
Reply-To: 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 = '' testScript =
start_all() # python
''
start_all()
server.wait_for_unit("multi-user.target") server.wait_for_unit("multi-user.target")
client.wait_for_unit("multi-user.target") client.wait_for_unit("multi-user.target")
# TODO put this blocking into the systemd units? # TODO put this blocking into the systemd units?
server.wait_until_succeeds( server.wait_until_succeeds(
"set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" "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("rspamadm dkim_keygen > /run/rspamd/dkim-test.key")
server.succeed("chown rspamd: /run/rspamd/dkim-test.key") server.succeed("chown rspamd: /run/rspamd/dkim-test.key")
client.execute("cp -p /etc/root/.* ~/") client.execute("cp -p /etc/root/.* ~/")
client.succeed("mkdir -p ~/mail") client.succeed("mkdir -p ~/mail")
client.succeed("ls -la ~/ >&2") client.succeed("ls -la ~/ >&2")
client.succeed("cat ~/.fetchmailrc >&2") client.succeed("cat ~/.fetchmailrc >&2")
client.succeed("cat ~/.procmailrc >&2") client.succeed("cat ~/.procmailrc >&2")
client.succeed("cat ~/.msmtprc >&2") client.succeed("cat ~/.msmtprc >&2")
with subtest("imap retrieving mail"): with subtest("imap retrieving mail"):
# fetchmail returns EXIT_CODE 1 when no new mail # fetchmail returns EXIT_CODE 1 when no new mail
client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2") client.succeed("fetchmail --nosslcertck -v || [ $? -eq 1 ] >&2")
with subtest("submission port send mail"): with subtest("submission port send mail"):
# send email from user2 to user1 # send email from user2 to user1
client.succeed( client.succeed(
"msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" "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 # give the mail server some time to process the mail
server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
with subtest("imap retrieving mail 2"): with subtest("imap retrieving mail 2"):
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v >&2") client.succeed("fetchmail --nosslcertck -v >&2")
with subtest("remove sensitive information on submission port"): with subtest("remove sensitive information on submission port"):
client.succeed("cat ~/mail/* >&2") client.succeed("cat ~/mail/* >&2")
## make sure our IP is _not_ in the email header ## make sure our IP is _not_ in the email header
client.fail("grep-ip ~/mail/*") client.fail("grep-ip ~/mail/*")
client.succeed("check-mail-id ~/mail/*") client.succeed("check-mail-id ~/mail/*")
with subtest("have correct fqdn as sender"): with subtest("have correct fqdn as sender"):
client.succeed("grep 'Received: from mail.example.com' ~/mail/*") client.succeed("grep 'Received: from mail.example.com' ~/mail/*")
with subtest("dkim has user-specified size"): with subtest("dkim has user-specified size"):
server.succeed( server.succeed(
"openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'" "openssl rsa -in /var/dkim/example2.com.dkim-rsa.key -text -noout | grep 'Private-Key: (1535 bit'"
) )
with subtest("dkim signing, multiple domains"): with subtest("dkim signing, multiple domains"):
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# send email from user2 to user1 # send email from user2 to user1
client.succeed( client.succeed(
"msmtp -a test2 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email2 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v") client.succeed("fetchmail --nosslcertck -v")
client.succeed("cat ~/mail/* >&2") client.succeed("cat ~/mail/* >&2")
# make sure it is dkim signed # make sure it is dkim signed
client.succeed("grep 's=dkim-rsa' ~/mail/*") client.succeed("grep 's=dkim-rsa' ~/mail/*")
client.succeed("grep 's=dkim-ed25519' ~/mail/*") client.succeed("grep 's=dkim-ed25519' ~/mail/*")
client.succeed("grep 's=dkim-file' ~/mail/*") client.succeed("grep 's=dkim-file' ~/mail/*")
with subtest("aliases"): with subtest("aliases"):
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# send email from chuck to postmaster # send email from chuck to postmaster
client.succeed( client.succeed(
"msmtp -a test3 --tls=on --tls-certcheck=off --auth=on postmaster@example.com < /etc/root/email2 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v") client.succeed("fetchmail --nosslcertck -v")
with subtest("domain catch-all"): with subtest("domain catch-all"):
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# send email from chuck to non-existent account # send email from chuck to non-existent account
client.succeed( client.succeed(
"msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lol@example.com < /etc/root/email2 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v") client.succeed("fetchmail --nosslcertck -v")
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# send email from user1 to chuck # send email from user1 to chuck
client.succeed( client.succeed(
"msmtp -a test4 --tls=on --tls-certcheck=off --auth=on chuck@example.com < /etc/root/email2 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 1 when no new mail # fetchmail returns EXIT_CODE 1 when no new mail
# if this succeeds, it means that user1 received the mail that was intended for chuck. # if this succeeds, it means that user1 received the mail that was intended for chuck.
client.fail("fetchmail --nosslcertck -v") client.fail("fetchmail --nosslcertck -v")
with subtest("extraVirtualAliases"): with subtest("extraVirtualAliases"):
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# send email from single-alias to user1 # send email from single-alias to user1
client.succeed( client.succeed(
"msmtp -a test5 --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email4 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v") client.succeed("fetchmail --nosslcertck -v")
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
# send email from user1 to multi-alias (user{1,2}@example.com) # send email from user1 to multi-alias (user{1,2}@example.com)
client.succeed( client.succeed(
"msmtp -a test --tls=on --tls-certcheck=off --auth=on multi-alias@example.com < /etc/root/email5 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.succeed("fetchmail --nosslcertck -v") client.succeed("fetchmail --nosslcertck -v")
with subtest("quota"): with subtest("quota"):
client.execute("rm ~/mail/*") client.execute("rm ~/mail/*")
client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc") client.execute("mv ~/.fetchmailRcLowQuota ~/.fetchmailrc")
client.succeed( client.succeed(
"msmtp -a test3 --tls=on --tls-certcheck=off --auth=on lowquota@example.com < /etc/root/email2 >&2" "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" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# fetchmail returns EXIT_CODE 0 when it retrieves mail # fetchmail returns EXIT_CODE 0 when it retrieves mail
client.fail("fetchmail --nosslcertck -v") client.fail("fetchmail --nosslcertck -v")
with subtest("imap sieve junk trainer"): with subtest("imap sieve junk trainer"):
# send email from user2 to user1 # send email from user2 to user1
client.succeed( client.succeed(
"msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email1 >&2" "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 # give the mail server some time to process the mail
server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
client.succeed("imap-mark-spam >&2") client.succeed("imap-mark-spam >&2")
server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2") server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-spam.sh >&2")
client.succeed("imap-mark-ham >&2") client.succeed("imap-mark-ham >&2")
server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2") server.wait_until_succeeds("journalctl -u dovecot | grep -i rspamd-learn-ham.sh >&2")
with subtest("full text search and indexation"): with subtest("full text search and indexation"):
# send 2 email from user2 to user1 # send 2 email from user2 to user1
client.succeed( client.succeed(
"msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email6 >&2" "msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email6 >&2"
) )
client.succeed( client.succeed(
"msmtp -a test --tls=on --tls-certcheck=off --auth=on user1@example.com < /etc/root/email7 >&2" "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 # give the mail server some time to process the mail
server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') server.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]')
# should find exactly one email containing this # should find exactly one email containing this
client.succeed("search INBOX 576a4565b70f5a4c1a0925cabdb587a6 >&2") client.succeed("search INBOX 576a4565b70f5a4c1a0925cabdb587a6 >&2")
# should fail because this folder is not indexed # should fail because this folder is not indexed
client.fail("search Junk a >&2") client.fail("search Junk a >&2")
# check that search really goes through the indexer # check that search really goes through the indexer
server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2") server.succeed("journalctl -u dovecot | grep 'fts-flatcurve(INBOX): Query ' >&2")
# check that Junk is not indexed # check that Junk is not indexed
server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2") server.fail("journalctl -u dovecot | grep 'fts-flatcurve(JUNK): Indexing ' >&2")
with subtest("dmarc reporting"): with subtest("dmarc reporting"):
server.systemctl("start rspamd-dmarc-reporter.service") server.systemctl("start rspamd-dmarc-reporter.service")
with subtest("no warnings or errors"): with subtest("no warnings or errors"):
server.fail("journalctl -u postfix | grep -i error >&2") server.fail("journalctl -u postfix | grep -i error >&2")
server.fail("journalctl -u postfix | grep -i warning >&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") 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 # harmless ? https://dovecot.org/pipermail/dovecot/2020-August/119575.html
server.fail( server.fail(
"journalctl -u dovecot | \ "journalctl -u dovecot | \
grep -v 'Expunged message reappeared, giving a new UID' | \ grep -v 'Expunged message reappeared, giving a new UID' | \
grep -v 'Time moved forwards' | \ grep -v 'Time moved forwards' | \
grep -i warning >&2" grep -i warning >&2"
) )
''; '';
} }
+1
View File
@@ -122,6 +122,7 @@ in
nodes, nodes,
... ...
}: }:
# python
'' ''
machine.start() machine.start()
machine.wait_for_unit("multi-user.target") machine.wait_for_unit("multi-user.target")
+29 -26
View File
@@ -53,36 +53,38 @@ in
}; };
}; };
}; };
declarativeContents."dc=example" = '' declarativeContents."dc=example" =
dn: dc=example #ldif
objectClass: domain ''
dc: example dn: dc=example
objectClass: domain
dc: example
dn: cn=mail,dc=example dn: cn=mail,dc=example
objectClass: organizationalRole objectClass: organizationalRole
objectClass: simpleSecurityObject objectClass: simpleSecurityObject
objectClass: top objectClass: top
cn: mail cn: mail
userPassword: ${bindPassword} userPassword: ${bindPassword}
dn: ou=users,dc=example dn: ou=users,dc=example
objectClass: organizationalUnit objectClass: organizationalUnit
ou: users ou: users
dn: cn=alice,ou=users,dc=example dn: cn=alice,ou=users,dc=example
objectClass: inetOrgPerson objectClass: inetOrgPerson
cn: alice cn: alice
sn: Foo sn: Foo
mail: alice@example.com mail: alice@example.com
userPassword: ${alicePassword} userPassword: ${alicePassword}
dn: cn=bob,ou=users,dc=example dn: cn=bob,ou=users,dc=example
objectClass: inetOrgPerson objectClass: inetOrgPerson
cn: bob cn: bob
sn: Bar sn: Bar
mail: bob@example.com mail: bob@example.com
userPassword: ${bobPassword} userPassword: ${bobPassword}
''; '';
}; };
mailserver = { mailserver = {
@@ -121,6 +123,7 @@ in
nodes, nodes,
... ...
}: }:
# python
'' ''
import sys import sys
import re import re
+23 -21
View File
@@ -90,29 +90,31 @@ in
]; ];
}; };
}; };
testScript = '' testScript =
start_all() # python
''
start_all()
for domain in [domain1, domain2]: for domain in [domain1, domain2]:
domain.wait_for_unit("multi-user.target") domain.wait_for_unit("multi-user.target")
domain.wait_for_unit("dovecot.service") domain.wait_for_unit("dovecot.service")
# TODO put this blocking into the systemd units? # TODO put this blocking into the systemd units?
domain1.wait_until_succeeds( domain1.wait_until_succeeds(
"set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" "set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
) )
domain2.wait_until_succeeds( domain2.wait_until_succeeds(
"set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]" "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 # user@domain1.com sends a mail to user@domain2.com via explicit TLS
client.succeed( 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" "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 # Send a mail to the address forwarded via implicit TLS and check it is in the recipient mailbox
client.succeed( 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" "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"
) )
''; '';
} }