import os
import re
import time
import uuid

import defusedxml.ElementTree as ElementTree

import testinfra.utils.ansible_runner


testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-*')


def test_installed_packages(host):
    """
    Tests if the necessary packages have been installed.
    """

    assert host.package('rsync').is_installed
    assert host.package('dovecot-imapd').is_installed
    assert host.package('dovecot-ldap').is_installed
    assert host.package('dovecot-sieve').is_installed
    assert host.package('dovecot-managesieved').is_installed
    assert host.package('postfix').is_installed
    assert host.package('postfix-ldap').is_installed
    assert host.package('swaks').is_installed
    assert host.package('clamav-milter').is_installed


def test_removed_packages(host):
    """
    Tests if certain packages have been removed from the system.
    """
    assert not host.package('exim4').is_installed


def test_postfix_user(host):
    """
    Tests if Postfix user has been added to correct group for traversing the TLS
    private key directory.
    """

    assert "ssl-cert" in host.user('postfix').groups


def test_dovecot_user(host):
    """
    Tests if Dovecot user has been added to correct group for traversing the TLS
    private key directory.
    """

    assert "ssl-cert" in host.user('dovecot').groups


def test_clamav_milter_configuration(host):
    """
    Tests if ClamAV Milter configuration has been deployed correctly.
    """

    config = host.file('/etc/clamav/clamav-milter.conf')

    assert config.is_file
    assert config.user == 'root'
    assert config.group == 'root'
    assert config.mode == 0o644


def test_clamav_milter(host):
    """
    Tests if ClamAV milter is blocking viruses.
    """

    server_did_not_accept_mail = 26

    eicar = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'

    send_mail = host.run("swaks --to john.doe@domain1 --server localhost --attach %s", eicar)

    assert send_mail.rc == server_did_not_accept_mail
    assert 'Your message has been rejected due to a possible virus' in send_mail.stdout


def test_postfix_chroot_directories(host):
    """
    Tests if Postfix chroot directories have been set-up with correct
    permissions.
    """

    directory = host.file('/var/spool/postfix/var')
    assert directory.is_directory
    assert directory.user == 'root'
    assert directory.group == 'root'
    assert directory.mode == 0o755

    directory = host.file('/var/spool/postfix/var/run')
    assert directory.is_directory
    assert directory.user == 'root'
    assert directory.group == 'root'
    assert directory.mode == 0o755

    directory = host.file('/var/spool/postfix/var/run/clamav')
    assert directory.is_directory
    assert directory.user == 'clamav'
    assert directory.group == 'clamav'
    assert directory.mode == 0o755


def test_ldap_tls_truststore_file(host):
    """
    Tests if the LDAP TLS truststore file has been deployed correctly.
    """

    tls_file = host.file('/etc/ssl/certs/mail_ldap_tls_truststore.pem')
    assert tls_file.is_file
    assert tls_file.user == 'root'
    assert tls_file.group == 'root'
    assert tls_file.mode == 0o644
    assert tls_file.content_string == open("tests/data/x509/ca/chain-full.cert.pem", "r").read().rstrip()

    tls_file = host.file('/var/spool/postfix/etc/ssl/certs/mail_ldap_tls_truststore.pem')
    assert tls_file.is_file
    assert tls_file.user == 'root'
    assert tls_file.group == 'root'
    assert tls_file.mode == 0o644
    assert tls_file.content_string == open("tests/data/x509/ca/chain-full.cert.pem", "r").read().rstrip()


def test_mailname_file(host):
    """
    Tests the system mail name file permissions.
    """

    mailname = host.file('/etc/mailname')

    assert mailname.is_file
    assert mailname.user == 'root'
    assert mailname.group == 'root'
    assert mailname.mode == 0o644


def test_postfix_ldap_configuration_files(host):
    """
    Tests if Postfix LDAP configuration files have been deployed correctly.
    """

    for config_file_path in ['/etc/postfix/ldap-virtual-alias-maps.cf',
                             '/etc/postfix/ldap-virtual-mailbox-domains.cf',
                             '/etc/postfix/ldap-virtual-mailbox-maps.cf']:

        config = host.file(config_file_path)
        assert config.is_file
        assert config.user == 'root'
        assert config.group == 'postfix'
        assert config.mode == 0o640


def test_postfix_ldap_configuration(host):
    """
    Tests if LDAP configuration can be used to fetch correct query results.
    """

    with host.sudo():

        # Test for valid domains.
        command = host.run("postmap -q domain1 ldap:/etc/postfix/ldap-virtual-mailbox-domains.cf")
        assert command.rc == 0
        assert command.stdout == "domain1\n"

        command = host.run("postmap -q domain2 ldap:/etc/postfix/ldap-virtual-mailbox-domains.cf")
        assert command.rc == 0
        assert command.stdout == "domain2\n"

        # Test for invalid domains.
        command = host.run("postmap -q domain3 ldap:/etc/postfix/ldap-virtual-mailbox-domains.cf")
        assert command.rc == 1
        assert command.stdout == ""

        # Test for valid mail addresses.
        command = host.run("postmap -q 'john.doe@domain1' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
        assert command.rc == 0
        assert command.stdout == 'john.doe@domain1\n'

        command = host.run("postmap -q 'jane.doe@domain2' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
        assert command.rc == 0
        assert command.stdout == 'jane.doe@domain2\n'

        # Test for invalid mail addresses.
        command = host.run("postmap -q 'jane.doe@domain1' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
        assert command.rc == 1
        assert command.stdout == ''

        command = host.run("postmap -q 'john.doe@domain2' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
        assert command.rc == 1
        assert command.stdout == ''

        # Test for valid mail address that's not allowed by LDAP group membership.
        command = host.run("postmap -q 'nomail@domain1' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
        assert command.rc == 1
        assert command.stdout == ''

        # Test for valid mail aliases.
        command = host.run("postmap -q postmaster@domain1 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
        assert command.rc == 0
        assert command.stdout == "john.doe@domain1\n"

        command = host.run("postmap -q webmaster@domain2 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
        assert command.rc == 0
        assert command.stdout == "jane.doe@domain2\n"

        # Test for invalid mail aliases.
        command = host.run("postmap -q postmaster@domain2 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
        assert command.rc == 1
        assert command.stdout == ""

        command = host.run("postmap -q webmaster@domain1 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
        assert command.rc == 1
        assert command.stdout == ""


def test_postfix_main_cf_file(host):
    """
    Tests Postfix main configuration file permissions.
    """

    config = host.file('/etc/postfix/main.cf')
    assert config.is_file
    assert config.user == 'root'
    assert config.group == 'root'
    assert config.mode == 0o644


def test_postfix_main_configuration_is_valid(host):
    """
    Tests if Postfix main configuration is valid and not producing any
    warnings.
    """

    postconf = host.run('/usr/sbin/postconf')

    # Clean-up the SSH warning from the beginning of stderr if
    # present.
    stderr = re.sub("^Warning: Permanently added.*?\r\n", "", postconf.stderr)

    assert postconf.rc == 0
    assert stderr == ""


def test_postfix_delivery_to_dovecot(host):
    """
    Tests if mail received by Postfix is properly delivered to Dovecot.
    """

    hostname = host.run('hostname').stdout.strip()

    message_id = "%s@localhost" % str(uuid.uuid4())

    # Virtual account.
    send = host.run('swaks --header %s --suppress-data --to john.doe@domain1 --server %s', "Message-Id: <%s>" % message_id, hostname)
    assert send.rc == 0

    # Wait for a little while for message to be processed.
    time.sleep(5)

    with host.sudo():
        mail_log = host.file('/var/log/mail.log')
        pattern = r"dovecot: lda\(john.doe@domain1\)<\d+><.+?>: msgid=<%s>: saved mail to INBOX" % message_id
        assert re.search(pattern, mail_log.content_string) is not None


def test_dovecot_system_authentication_is_disabled(host):
    """
    Tests if Dovecot system-based authentication has been disabled.
    """

    config = host.file("/etc/dovecot/conf.d/10-auth.conf")

    assert "!include auth-system.conf.ext" not in config.content_string


def test_dovecot_overrides_configuration_file(host):
    """
    Tests if Dovecot configuration file with overrides has been deployed and has
    correct permissions.
    """

    config = host.file("/etc/dovecot/conf.d/99-local.conf")

    assert config.is_file
    assert config.user == 'root'
    assert config.group == 'root'
    assert config.mode == 0o644


def test_dovecot_imap_ldap_configuration(host):
    """
    Tests if Dovecot LDAP configuration is correct.
    """

    with host.sudo():

        user_does_not_exist = 67

        # Test for valid mail addresses.
        command = host.run("doveadm user john.doe@domain1")
        assert command.rc == 0

        command = host.run("doveadm user jane.doe@domain2")
        assert command.rc == 0

        # Test for invalid mail addresses.
        command = host.run("doveadm user john.doe@domain2")
        assert command.rc == user_does_not_exist

        command = host.run("doveadm user jane.doe@domain1")
        assert command.rc == user_does_not_exist

        # Test for mail addresses present in LDAP, but entry not in mail group.
        command = host.run("doveadm user nomail@domain1")
        assert command.rc == user_does_not_exist


def test_postfix_master_file(host):
    """
    Tests permissions for Postfix master.cf configuration file.
    """

    config = host.file('/etc/postfix/master.cf')

    assert config.is_file
    assert config.user == 'root'
    assert config.group == 'root'
    assert config.mode == 0o644


def test_services(host):
    """
    Tests if all the mail-related servieces are up and running.
    """

    for service_name in ["clamav-daemon",
                         "clamav-freshclam",
                         "clamav-milter",
                         "postfix",
                         "dovecot"]:

        service = host.service(service_name)
        assert service.is_running
        assert service.is_enabled


def test_clamav_database_presence(host):
    """
    Tests if ClamAV database is present.
    """

    for database_file in ["/var/lib/clamav/bytecode",
                          "/var/lib/clamav/daily",
                          "/var/lib/clamav/main"]:

        database_cvd = host.file(database_file + '.cvd')
        database_cld = host.file(database_file + '.cld')

        assert database_cvd.is_file or database_cld.is_file


def test_firewall_configuration_file(host):
    """
    Tests if firewall configuration file has been deployed correctly.
    """

    with host.sudo():

        config = host.file('/etc/ferm/conf.d/20-mail.conf')

        assert config.is_file
        assert config.user == 'root'
        assert config.group == 'root'
        assert config.mode == 0o640


def test_smtp_server_dh_parameter_file(host):
    """
    Tests if the Diffie-Hellman parameter file has been generated
    correctly.
    """

    hostname = host.run('hostname').stdout.strip()
    dhparam_file_path = '/etc/ssl/private/%s_smtp.dh.pem' % hostname

    with host.sudo():
        dhparam_file = host.file(dhparam_file_path)
        assert dhparam_file.is_file
        assert dhparam_file.user == 'root'
        assert dhparam_file.group == 'root'
        assert dhparam_file.mode == 0o640

        dhparam_info = host.run("openssl dhparam -noout -text -in %s", dhparam_file_path)

        assert "DH Parameters: (2048 bit)" in dhparam_info.stdout


def test_smtp_server_uses_correct_dh_parameters(host):
    """
    Tests if the SMTP server uses the generated Diffie-Hellman parameter.
    """

    hostname = host.run('hostname').stdout.strip()

    with host.sudo():
        expected_dhparam = host.file('/etc/ssl/private/%s_smtp.dh.pem' % hostname).content_string.rstrip()

    connection = host.run("gnutls-cli --no-ca-verification --starttls-proto=smtp --port 25 "
                          "--priority 'NONE:+VERS-TLS1.2:+CTYPE-X509:+COMP-NULL:+SIGN-RSA-SHA384:+DHE-RSA:+SHA384:+AEAD:+AES-256-GCM' --verbose localhost")

    output = connection.stdout
    begin_marker = "-----BEGIN DH PARAMETERS-----"
    end_marker = "-----END DH PARAMETERS-----"
    used_dhparam = output[output.find(begin_marker):output.find(end_marker) + len(end_marker)]

    assert used_dhparam == expected_dhparam


def test_imap_server_dh_parameter_file(host):
    """
    Tests if the Diffie-Hellman parameter file has been generated
    correctly.
    """

    hostname = host.run('hostname').stdout.strip()
    dhparam_file_path = '/etc/ssl/private/%s_imap.dh.pem' % hostname

    with host.sudo():
        dhparam_file = host.file(dhparam_file_path)
        assert dhparam_file.is_file
        assert dhparam_file.user == 'root'
        assert dhparam_file.group == 'root'
        assert dhparam_file.mode == 0o640

        dhparam_info = host.run("openssl dhparam -noout -text -in %s", dhparam_file_path)

        assert "DH Parameters: (2048 bit)" in dhparam_info.stdout


def test_imap_server_uses_correct_dh_parameters(host):
    """
    Tests if the IMAP server uses correct Diffie-Hellman parameters.
    """

    connection = host.run("gnutls-cli --no-ca-verification --starttls-proto=imap --port 143 "
                          "--priority 'NONE:+VERS-TLS1.2:+CTYPE-X509:+COMP-NULL:+SIGN-RSA-SHA384:+DHE-RSA:+SHA384:+AEAD:+AES-256-GCM' --verbose localhost")

    assert " - Using prime: 2048 bits" in connection.stdout


def test_imap_and_smtp_tls_files(host):
    """
    Tests if IMAP and SMTP TLS private keys and certificates have been
    deployed correctly.
    """

    hostname = host.run('hostname').stdout.strip()

    with host.sudo():

        tls_file = host.file('/etc/ssl/private/%s_smtp.key' % hostname)
        assert tls_file.is_file
        assert tls_file.user == 'root'
        assert tls_file.group == 'root'
        assert tls_file.mode == 0o640
        assert tls_file.content_string == open("tests/data/x509/server/%s_smtp.key.pem" % hostname, "r").read().rstrip()

        tls_file = host.file('/etc/ssl/certs/%s_smtp.pem' % hostname)
        assert tls_file.is_file
        assert tls_file.user == 'root'
        assert tls_file.group == 'root'
        assert tls_file.mode == 0o644
        assert tls_file.content_string == open("tests/data/x509/server/%s_smtp.cert.pem" % hostname, "r").read().rstrip()

        tls_file = host.file('/etc/ssl/private/%s_imap.key' % hostname)
        assert tls_file.is_file
        assert tls_file.user == 'root'
        assert tls_file.group == 'root'
        assert tls_file.mode == 0o640
        assert tls_file.content_string == open("tests/data/x509/server/%s_imap.key.pem" % hostname, "r").read().rstrip()

        tls_file = host.file('/etc/ssl/certs/%s_imap.pem' % hostname)
        assert tls_file.is_file
        assert tls_file.user == 'root'
        assert tls_file.group == 'root'
        assert tls_file.mode == 0o644
        assert tls_file.content_string == open("tests/data/x509/server/%s_imap.cert.pem" % hostname, "r").read().rstrip()


def test_imap_tls_connectivity(host):
    """
    Tests connectivity over STARTTLS/TLS towards IMAP server.
    """

    hostname = host.run('hostname').stdout.strip()
    fqdn = hostname[:hostname.rfind("-")]

    starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -connect %s:143', fqdn)
    assert starttls.rc == 0
    assert '* BYE Logging out' in starttls.stdout

    tls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -connect %s:993', fqdn)
    assert tls.rc == 0
    assert '* BYE Logging out' in starttls.stdout


def test_smtp_tls_connectivity(host):
    """
    Tests connectivity over default/submission port towards SMTP
    server.
    """

    hostname = host.run('hostname').stdout.strip()
    fqdn = hostname[:hostname.rfind("-")]

    starttls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect %s:25', fqdn)
    assert starttls.rc == 0
    assert '221 2.0.0 Bye' in starttls.stdout

    tls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect %s:587', fqdn)
    assert tls.rc == 0
    assert '221 2.0.0 Bye' in starttls.stdout


def test_postfix_local_delivery(host):
    """
    Tests if mail received by Postfix is properly delivered to local
    account's mail spool.
    """

    message_id = "%s@localhost" % str(uuid.uuid4())

    send = host.run('swaks --header %s --suppress-data --to localuser@localhost --server localhost', "Message-Id: <%s>" % message_id)
    assert send.rc == 0

    # Wait for a little while for message to be processed.
    time.sleep(5)

    with host.sudo():
        mail_log = host.file('/var/log/mail.log')

        pattern = r"postfix/cleanup\[\d+\]: (?P<queue_id>[^:]+): message-id=<%s>" % message_id
        match_result = re.search(pattern, mail_log.content_string)
        assert match_result is not None

        queue_id = match_result.group('queue_id')
        pattern = r"postfix/local\[\d+\]: %s: to=<localuser@localhost>, relay=local,.*, status=sent \(delivered to mailbox\)" % queue_id
        match_result = re.search(pattern, mail_log.content_string)
        assert match_result is not None, queue_id


def test_postfix_sends_mails_without_tls_when_unavailable(host):
    """
    Tests if Postfix sends out mails even if STARTTLS is not available
    on remote server.
    """

    # Verify that the remote host refuses to accept mails over TLS first.
    send = host.run('swaks --tls --suppress-data --to root@smtp-server-refusing-tls --server smtp-server-refusing-tls')
    assert send.rc == 29
    assert "Host did not advertise STARTTLS" in send.stderr

    message_id = "%s@localhost" % str(uuid.uuid4())
    send = host.run('swaks --header %s --suppress-data --to root@smtp-server-refusing-tls --server localhost', "Message-Id: <%s>" % message_id)
    assert send.rc == 0

    # Wait for a little while for message to be processed.
    time.sleep(5)

    with host.sudo():
        mail_log = host.file('/var/log/mail.log')

        pattern = r"postfix/cleanup\[\d+\]: (?P<queue_id>[^:]+): message-id=<%s>" % message_id
        match_result = re.search(pattern, mail_log.content_string)
        assert match_result is not None

        queue_id = match_result.group('queue_id')
        pattern = r"postfix/smtp\[\d+\]: %s: to=<root@smtp-server-refusing-tls>, relay=smtp-server-refusing-tls.*, status=sent" % queue_id
        match_result = re.search(pattern, mail_log.content_string)
        assert match_result is not None, queue_id


def test_postfix_sends_mails_over_tls_when_available(host):
    """
    Tests if Postfix sends out mails using STARTTLS if available on
    remote server (oportunistic encryption).
    """

    # Verify that the remote host refuses to accept mails without TLS first.
    send = host.run('swaks --suppress-data --to root@smtp-server-requiring-tls --server smtp-server-requiring-tls')
    assert send.rc == 23
    assert "Must issue a STARTTLS command first" in send.stdout

    message_id = "%s@localhost" % str(uuid.uuid4())
    send = host.run('swaks --tls --header %s --suppress-data --to root@smtp-server-requiring-tls --server localhost', "Message-Id: <%s>" % message_id)
    assert send.rc == 0

    # Wait for a little while for message to be processed.
    time.sleep(5)

    with host.sudo():
        mail_log = host.file('/var/log/mail.log')

        pattern = r"postfix/cleanup\[\d+\]: (?P<queue_id>[^:]+): message-id=<%s>" % message_id
        match_result = re.search(pattern, mail_log.content_string)
        assert match_result is not None

        queue_id = match_result.group('queue_id')
        pattern = r"postfix/smtp\[\d+\]: %s: to=<root@smtp-server-requiring-tls>, relay=smtp-server-requiring-tls.*, status=sent" % queue_id
        match_result = re.search(pattern, mail_log.content_string)
        assert match_result is not None, queue_id


def test_certificate_validity_check_configuration(host):
    """
    Tests if certificate validity check configuration file has been deployed
    correctly.
    """

    hostname = host.run('hostname').stdout.strip()

    config = host.file('/etc/check_certificate/%s_smtp.conf' % hostname)
    assert config.is_file
    assert config.user == 'root'
    assert config.group == 'root'
    assert config.mode == 0o644
    assert config.content_string == "/etc/ssl/certs/%s_smtp.pem" % hostname

    config = host.file('/etc/check_certificate/%s_imap.conf' % hostname)
    assert config.is_file
    assert config.user == 'root'
    assert config.group == 'root'
    assert config.mode == 0o644
    assert config.content_string == "/etc/ssl/certs/%s_imap.pem" % hostname


def test_smtp_default_port_tls_version_and_ciphers(host):
    """
    Tests TLS configuration for SMTP default port (needs to be less
    restrictive for interoperability purposes).
    """

    expected_tls_versions = ["TLSv1.0", "TLSv1.1", "TLSv1.2", "TLSv1.3"]
    expected_tls_ciphers = [
        'TLS_AKE_WITH_AES_128_GCM_SHA256',
        'TLS_AKE_WITH_AES_256_GCM_SHA384',
        'TLS_AKE_WITH_CHACHA20_POLY1305_SHA256',
        'TLS_DHE_RSA_WITH_AES_128_CBC_SHA',
        'TLS_DHE_RSA_WITH_AES_128_CBC_SHA256',
        'TLS_DHE_RSA_WITH_AES_128_CCM',
        'TLS_DHE_RSA_WITH_AES_128_CCM_8',
        'TLS_DHE_RSA_WITH_AES_128_GCM_SHA256',
        'TLS_DHE_RSA_WITH_AES_256_CBC_SHA',
        'TLS_DHE_RSA_WITH_AES_256_CBC_SHA256',
        'TLS_DHE_RSA_WITH_AES_256_CCM',
        'TLS_DHE_RSA_WITH_AES_256_CCM_8',
        'TLS_DHE_RSA_WITH_AES_256_GCM_SHA384',
        'TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256',
        'TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384',
        'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA',
        'TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256',
        'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA',
        'TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256',
        'TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256',
        'TLS_DH_anon_WITH_AES_128_CBC_SHA',
        'TLS_DH_anon_WITH_AES_128_CBC_SHA256',
        'TLS_DH_anon_WITH_AES_128_GCM_SHA256',
        'TLS_DH_anon_WITH_AES_256_CBC_SHA',
        'TLS_DH_anon_WITH_AES_256_CBC_SHA256',
        'TLS_DH_anon_WITH_AES_256_GCM_SHA384',
        'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA',
        'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256',
        'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA',
        'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256',
        'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA',
        'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256',
        'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
        'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA',
        'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384',
        'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
        'TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256',
        'TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384',
        'TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256',
        'TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384',
        'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256',
        'TLS_ECDH_anon_WITH_AES_128_CBC_SHA',
        'TLS_ECDH_anon_WITH_AES_256_CBC_SHA',
        'TLS_RSA_WITH_AES_128_CBC_SHA',
        'TLS_RSA_WITH_AES_128_CBC_SHA256',
        'TLS_RSA_WITH_AES_128_CCM',
        'TLS_RSA_WITH_AES_128_CCM_8',
        'TLS_RSA_WITH_AES_128_GCM_SHA256',
        'TLS_RSA_WITH_AES_256_CBC_SHA',
        'TLS_RSA_WITH_AES_256_CBC_SHA256',
        'TLS_RSA_WITH_AES_256_CCM',
        'TLS_RSA_WITH_AES_256_CCM_8',
        'TLS_RSA_WITH_AES_256_GCM_SHA384',
        'TLS_RSA_WITH_ARIA_128_GCM_SHA256',
        'TLS_RSA_WITH_ARIA_256_GCM_SHA384',
        'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA',
        'TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256',
        'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA',
        'TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256',
    ]

    # Run the nmap scanner against the server, and fetch the results.
    nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 25 localhost -oX /tmp/report.xml")
    assert nmap.rc == 0
    report_content = host.file('/tmp/report.xml').content_string

    report_root = ElementTree.fromstring(report_content)

    tls_versions = []
    tls_ciphers = set()

    for child in report_root.findall("./host/ports/port/script/table"):
        tls_versions.append(child.attrib['key'])

    for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"):
        tls_ciphers.add(child.text)

    tls_versions.sort()
    tls_ciphers = sorted(list(tls_ciphers))

    assert tls_versions == expected_tls_versions
    assert tls_ciphers == expected_tls_ciphers


def test_dovecot_warnings(host):
    """
    Tests if Dovecot is reporting any warnings.
    """

    with host.sudo():

        # Use invocation ID to get service logs since last restart.
        invocation_id = host.run("systemctl show -p InvocationID --value dovecot").stdout.strip()
        invocation_logs = host.run("journalctl INVOCATION_ID=%s + _SYSTEMD_INVOCATION_ID=%s", invocation_id, invocation_id)

        assert "doveconf: Warning" not in invocation_logs.stdout
