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:
@@ -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
@@ -1524,6 +1524,9 @@ in
|
||||
};
|
||||
|
||||
imports = [
|
||||
./mail-server
|
||||
|
||||
# NixOS 25.05
|
||||
(mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] ''
|
||||
This option is not needed for fts-flatcurve
|
||||
'')
|
||||
@@ -1549,19 +1552,6 @@ in
|
||||
(mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] ''
|
||||
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" ] ''
|
||||
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.
|
||||
@@ -1575,13 +1565,8 @@ in
|
||||
(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.
|
||||
'')
|
||||
(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" ])
|
||||
|
||||
# NixOS 25.11
|
||||
(mkRenamedOptionModule [ "mailserver" "dmarcReporting" "domain" ] [ "mailserver" "systemDomain" ])
|
||||
(mkRenamedOptionModule
|
||||
[ "mailserver" "dmarcReporting" "organizationName" ]
|
||||
@@ -1597,6 +1582,7 @@ in
|
||||
The name in the `FROM` field for DMARC report now uses the `mailserver.systemName`.
|
||||
'')
|
||||
|
||||
# NixOS 26.05
|
||||
(mkRemovedOptionModule [ "mailserver" "certificateDomains" ] ''
|
||||
Configure `security.acme.certs.''${config.mailserver.fqdn}.extraDomains` instead.
|
||||
'')
|
||||
@@ -1607,5 +1593,13 @@ in
|
||||
(mkRenamedOptionModule [ "mailserver" "acmeCertificateName" ] [ "mailserver" "x509" "useACMEHost" ])
|
||||
(mkRenamedOptionModule [ "mailserver" "certificateFile" ] [ "mailserver" "x509" "certificateFile" ])
|
||||
(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
@@ -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}
|
||||
'';
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 <<EOF > ${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 <<EOF > ${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 <<EOF > ${userdbFile}
|
||||
${lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList (
|
||||
name: value:
|
||||
"${name}:::::::"
|
||||
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
|
||||
) cfg.loginAccounts
|
||||
)}
|
||||
EOF
|
||||
'';
|
||||
cat <<EOF > ${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
|
||||
|
||||
+41
-38
@@ -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 {
|
||||
|
||||
+90
-84
@@ -144,111 +144,117 @@
|
||||
password user2
|
||||
'';
|
||||
};
|
||||
"root/virus-email".text = ''
|
||||
From: User2 <user@example2.com>
|
||||
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 <user1@example.com>
|
||||
X-Mailer: Apple Mail (2.3445.6.18)
|
||||
"root/virus-email".text =
|
||||
# mail
|
||||
''
|
||||
From: User2 <user@example2.com>
|
||||
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 <user1@example.com>
|
||||
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 <user@example2.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user@example2.com>
|
||||
To: User1 <user1@example.com>
|
||||
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")
|
||||
'';
|
||||
}
|
||||
|
||||
+322
-297
@@ -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 <user2@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user2@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user@example2.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user@example2.com>
|
||||
To: User1 <user1@example.com>
|
||||
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: <asdfghjkl42@host.local.network>
|
||||
From: Postmaster <postmaster@example.com>
|
||||
To: Chuck <chuck@example.com>
|
||||
Cc:
|
||||
Bcc:
|
||||
Subject: This is a test Email from postmaster@example.com to chuck
|
||||
Reply-To:
|
||||
XOXO User1
|
||||
'';
|
||||
"root/email3".text =
|
||||
# mail
|
||||
''
|
||||
Message-ID: <asdfghjkl42@host.local.network>
|
||||
From: Postmaster <postmaster@example.com>
|
||||
To: Chuck <chuck@example.com>
|
||||
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: <sdfsdf@host.local.network>
|
||||
From: Single Alias <single-alias@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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: <sdfsdf@host.local.network>
|
||||
From: Single Alias <single-alias@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user2@example.com>
|
||||
To: Multi Alias <multi-alias@example.com>
|
||||
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 <user2@example.com>
|
||||
To: Multi Alias <multi-alias@example.com>
|
||||
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 <user2@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user2@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user2@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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 <user2@example.com>
|
||||
To: User1 <user1@example.com>
|
||||
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"
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ in
|
||||
nodes,
|
||||
...
|
||||
}:
|
||||
# python
|
||||
''
|
||||
machine.start()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
+29
-26
@@ -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
|
||||
|
||||
+23
-21
@@ -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"
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user