Add multiple.nix test
This test is used to test feature requiring several mail domains, such as the `forwards` option.
This commit is contained in:
parent
24600377af
commit
c813f1205f
|
@ -0,0 +1,187 @@
|
||||||
|
import smtplib, sys
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import imaplib
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import email
|
||||||
|
import time
|
||||||
|
|
||||||
|
RETRY = 100
|
||||||
|
|
||||||
|
def _send_mail(smtp_host, smtp_port, from_addr, from_pwd, to_addr, subject, starttls):
|
||||||
|
print("Sending mail with subject '{}'".format(subject))
|
||||||
|
message = "\n".join([
|
||||||
|
"From: {from_addr}",
|
||||||
|
"To: {to_addr}",
|
||||||
|
"Subject: {subject}",
|
||||||
|
"",
|
||||||
|
"This validates our mail server can send to Gmail :/"]).format(
|
||||||
|
from_addr=from_addr,
|
||||||
|
to_addr=to_addr,
|
||||||
|
subject=subject)
|
||||||
|
|
||||||
|
|
||||||
|
retry = RETRY
|
||||||
|
while True:
|
||||||
|
with smtplib.SMTP(smtp_host, port=smtp_port) as smtp:
|
||||||
|
if starttls:
|
||||||
|
smtp.starttls()
|
||||||
|
if from_pwd is not None:
|
||||||
|
smtp.login(from_addr, from_pwd)
|
||||||
|
try:
|
||||||
|
smtp.sendmail(from_addr, [to_addr], message)
|
||||||
|
return
|
||||||
|
except smtplib.SMTPResponseException as e:
|
||||||
|
# This is a service unavailable error
|
||||||
|
# In this situation, we want to retry.
|
||||||
|
if e.smtp_code == 451:
|
||||||
|
if retry > 0:
|
||||||
|
retry = retry - 1
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print("Error while sending mail: %s" % e)
|
||||||
|
exit(5)
|
||||||
|
except Exception as e:
|
||||||
|
print("Error while sending mail: %s" % e)
|
||||||
|
exit(4)
|
||||||
|
|
||||||
|
def _read_mail(
|
||||||
|
imap_host,
|
||||||
|
imap_port,
|
||||||
|
imap_username,
|
||||||
|
to_pwd,
|
||||||
|
subject,
|
||||||
|
ignore_dkim_spf,
|
||||||
|
show_body=False,
|
||||||
|
delete=True):
|
||||||
|
print("Reading mail from %s" % imap_username)
|
||||||
|
|
||||||
|
message = None
|
||||||
|
|
||||||
|
obj = imaplib.IMAP4_SSL(imap_host, imap_port)
|
||||||
|
obj.login(imap_username, to_pwd)
|
||||||
|
obj.select()
|
||||||
|
|
||||||
|
today = datetime.today()
|
||||||
|
cutoff = today - timedelta(days=1)
|
||||||
|
dt = cutoff.strftime('%d-%b-%Y')
|
||||||
|
for _ in range(0, RETRY):
|
||||||
|
print("Retrying")
|
||||||
|
obj.select()
|
||||||
|
typ, data = obj.search(None, '(SINCE %s) (SUBJECT "%s")'%(dt, subject))
|
||||||
|
if data == [b'']:
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
uids = data[0].decode("utf-8").split(" ")
|
||||||
|
if len(uids) != 1:
|
||||||
|
print("Warning: %d messages have been found with subject containing %s " % (len(uids), subject))
|
||||||
|
|
||||||
|
# FIXME: we only consider the first matching message...
|
||||||
|
uid = uids[0]
|
||||||
|
_, raw = obj.fetch(uid, '(RFC822)')
|
||||||
|
if delete:
|
||||||
|
obj.store(uid, '+FLAGS', '\\Deleted')
|
||||||
|
obj.expunge()
|
||||||
|
message = email.message_from_bytes(raw[0][1])
|
||||||
|
print("Message with subject '%s' has been found" % message['subject'])
|
||||||
|
if show_body:
|
||||||
|
for m in message.get_payload():
|
||||||
|
if m.get_content_type() == 'text/plain':
|
||||||
|
print("Body:\n%s" % m.get_payload(decode=True).decode('utf-8'))
|
||||||
|
break
|
||||||
|
|
||||||
|
if message is None:
|
||||||
|
print("Error: no message with subject '%s' has been found in INBOX of %s" % (subject, imap_username))
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if ignore_dkim_spf:
|
||||||
|
return
|
||||||
|
|
||||||
|
# gmail set this standardized header
|
||||||
|
if 'ARC-Authentication-Results' in message:
|
||||||
|
if "dkim=pass" in message['ARC-Authentication-Results']:
|
||||||
|
print("DKIM ok")
|
||||||
|
else:
|
||||||
|
print("Error: no DKIM validation found in message:")
|
||||||
|
print(message.as_string())
|
||||||
|
exit(2)
|
||||||
|
if "spf=pass" in message['ARC-Authentication-Results']:
|
||||||
|
print("SPF ok")
|
||||||
|
else:
|
||||||
|
print("Error: no SPF validation found in message:")
|
||||||
|
print(message.as_string())
|
||||||
|
exit(3)
|
||||||
|
else:
|
||||||
|
print("DKIM and SPF verification failed")
|
||||||
|
exit(4)
|
||||||
|
|
||||||
|
def send_and_read(args):
|
||||||
|
src_pwd = None
|
||||||
|
if args.src_password_file is not None:
|
||||||
|
src_pwd = args.src_password_file.readline().rstrip()
|
||||||
|
dst_pwd = args.dst_password_file.readline().rstrip()
|
||||||
|
|
||||||
|
if args.imap_username != '':
|
||||||
|
imap_username = args.imap_username
|
||||||
|
else:
|
||||||
|
imap_username = args.to_addr
|
||||||
|
|
||||||
|
subject = "{}".format(uuid.uuid4())
|
||||||
|
|
||||||
|
_send_mail(smtp_host=args.smtp_host,
|
||||||
|
smtp_port=args.smtp_port,
|
||||||
|
from_addr=args.from_addr,
|
||||||
|
from_pwd=src_pwd,
|
||||||
|
to_addr=args.to_addr,
|
||||||
|
subject=subject,
|
||||||
|
starttls=args.smtp_starttls)
|
||||||
|
|
||||||
|
_read_mail(imap_host=args.imap_host,
|
||||||
|
imap_port=args.imap_port,
|
||||||
|
imap_username=imap_username,
|
||||||
|
to_pwd=dst_pwd,
|
||||||
|
subject=subject,
|
||||||
|
ignore_dkim_spf=args.ignore_dkim_spf)
|
||||||
|
|
||||||
|
def read(args):
|
||||||
|
_read_mail(imap_host=args.imap_host,
|
||||||
|
imap_port=args.imap_port,
|
||||||
|
to_addr=args.imap_username,
|
||||||
|
to_pwd=args.imap_password,
|
||||||
|
subject=args.subject,
|
||||||
|
ignore_dkim_spf=args.ignore_dkim_spf,
|
||||||
|
show_body=args.show_body,
|
||||||
|
delete=False)
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
|
parser_send_and_read = subparsers.add_parser('send-and-read', description="Send a email with a subject containing a random UUID and then try to read this email from the recipient INBOX.")
|
||||||
|
parser_send_and_read.add_argument('--smtp-host', type=str)
|
||||||
|
parser_send_and_read.add_argument('--smtp-port', type=str, default=25)
|
||||||
|
parser_send_and_read.add_argument('--smtp-starttls', action='store_true')
|
||||||
|
parser_send_and_read.add_argument('--from-addr', type=str)
|
||||||
|
parser_send_and_read.add_argument('--imap-host', required=True, type=str)
|
||||||
|
parser_send_and_read.add_argument('--imap-port', type=str, default=993)
|
||||||
|
parser_send_and_read.add_argument('--to-addr', type=str, required=True)
|
||||||
|
parser_send_and_read.add_argument('--imap-username', type=str, default='', help="username used for imap login. If not specified, the to-addr value is used")
|
||||||
|
parser_send_and_read.add_argument('--src-password-file', type=argparse.FileType('r'))
|
||||||
|
parser_send_and_read.add_argument('--dst-password-file', required=True, type=argparse.FileType('r'))
|
||||||
|
parser_send_and_read.add_argument('--ignore-dkim-spf', action='store_true', help="to ignore the dkim and spf verification on the read mail")
|
||||||
|
parser_send_and_read.set_defaults(func=send_and_read)
|
||||||
|
|
||||||
|
parser_read = subparsers.add_parser('read', description="Search for an email with a subject containing 'subject' in the INBOX.")
|
||||||
|
parser_read.add_argument('--imap-host', type=str, default="localhost")
|
||||||
|
parser_read.add_argument('--imap-port', type=str, default=993)
|
||||||
|
parser_read.add_argument('--imap-username', required=True, type=str)
|
||||||
|
parser_read.add_argument('--imap-password', required=True, type=str)
|
||||||
|
parser_read.add_argument('--ignore-dkim-spf', action='store_true', help="to ignore the dkim and spf verification on the read mail")
|
||||||
|
parser_read.add_argument('--show-body', action='store_true', help="print mail text/plain payload")
|
||||||
|
parser_read.add_argument('subject', type=str)
|
||||||
|
parser_read.set_defaults(func=read)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.func(args)
|
|
@ -35,6 +35,7 @@ let
|
||||||
"intern"
|
"intern"
|
||||||
"extern"
|
"extern"
|
||||||
"clamav"
|
"clamav"
|
||||||
|
"multiple"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Generate an attribute set containing one test per releases
|
# Generate an attribute set containing one test per releases
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# This tests is used to test features requiring several mail domains.
|
||||||
|
|
||||||
|
{ pkgs ? import <nixpkgs> {}}:
|
||||||
|
|
||||||
|
let
|
||||||
|
hashPassword = password: pkgs.runCommand
|
||||||
|
"password-${password}-hashed"
|
||||||
|
{ buildInputs = [ pkgs.mkpasswd ]; }
|
||||||
|
''
|
||||||
|
mkpasswd -m sha-512 ${password} > $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
password = pkgs.writeText "password" "password";
|
||||||
|
|
||||||
|
domainGenerator = domain: { config, pkgs, ... }: {
|
||||||
|
imports = [../default.nix];
|
||||||
|
virtualisation.memorySize = 1024;
|
||||||
|
mailserver = {
|
||||||
|
enable = true;
|
||||||
|
fqdn = "mail.${domain}";
|
||||||
|
domains = [ domain ];
|
||||||
|
localDnsResolver = false;
|
||||||
|
loginAccounts = {
|
||||||
|
"user@${domain}" = {
|
||||||
|
hashedPasswordFile = hashPassword "password";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
enableImap = true;
|
||||||
|
enableImapSsl = true;
|
||||||
|
};
|
||||||
|
services.dnsmasq = {
|
||||||
|
enable = true;
|
||||||
|
extraConfig = ''
|
||||||
|
mx-host=domain1.com,domain1,10
|
||||||
|
mx-host=domain2.com,domain2,10
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
pkgs.nixosTest {
|
||||||
|
name = "multiple";
|
||||||
|
nodes = {
|
||||||
|
domain1 = {...}: {
|
||||||
|
imports = [
|
||||||
|
../default.nix
|
||||||
|
(domainGenerator "domain1.com")
|
||||||
|
];
|
||||||
|
mailserver.forwards = {
|
||||||
|
"non-local@domain1.com" = ["user@domain2.com" "user@domain1.com"];
|
||||||
|
"non@domain1.com" = ["user@domain2.com" "user@domain1.com"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
domain2 = domainGenerator "domain2.com";
|
||||||
|
client = { config, pkgs, ... }: {
|
||||||
|
environment.systemPackages = [
|
||||||
|
(pkgs.writeScriptBin "mail-check" ''
|
||||||
|
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
||||||
|
'')];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
domain1.wait_for_unit("multi-user.target")
|
||||||
|
domain2.wait_for_unit("multi-user.target")
|
||||||
|
|
||||||
|
# TODO put this blocking into the systemd units?
|
||||||
|
domain1.wait_until_succeeds(
|
||||||
|
"timeout 1 ${pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
|
||||||
|
)
|
||||||
|
domain2.wait_until_succeeds(
|
||||||
|
"timeout 1 ${pkgs.netcat}/bin/nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
|
||||||
|
)
|
||||||
|
|
||||||
|
# user@domain1.com sends a mail to user@domain2.com
|
||||||
|
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 and check it is in the recipient mailbox
|
||||||
|
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 non-local@domain1.com --imap-username user@domain2.com --src-password-file ${password} --dst-password-file ${password} --ignore-dkim-spf"
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in New Issue