treewide: add language annotations for inline code

Instruct editors to correctly highlight and evaluate inline code blocks.
This commit is contained in:
Martin Weinelt
2026-03-12 12:58:45 +01:00
parent 405f2180d4
commit 06cc71c76e
8 changed files with 563 additions and 518 deletions
+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}
''; '';
} }
+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"
) )
''; '';
} }