# SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie # SPDX-License-Identifier: MIT # NixOS VM test exercising zsmtp against third-party implementations: # - zsmtp client -> Postfix: plaintext, STARTTLS (port 25), implicit TLS # (port 465), verified by checking local delivery to alice's spool # - swaks -> zsmtp server: plaintext and STARTTLS, verified by checking # the received message in the server's journal { testers, callPackage, runCommand, openssl, }: let zsmtp = callPackage ./package.nix { }; snakeoil = runCommand "zsmtp-test-cert" { nativeBuildInputs = [ openssl ]; } '' mkdir -p $out openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ -keyout $out/key.pem -out $out/cert.pem -days 36500 -nodes \ -subj "/CN=localhost" \ -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" ''; in testers.runNixOSTest { name = "zsmtp-interop"; nodes.machine = { pkgs, ... }: { environment.systemPackages = [ zsmtp pkgs.swaks ]; users.users.alice.isNormalUser = true; services.postfix = { enable = true; # Implicit-TLS smtpd on port 465; allow loopback without SASL. enableSubmissions = true; submissionsOptions = { smtpd_client_restrictions = "permit_mynetworks,reject"; }; settings.main = { mydestination = [ "localhost" "$myhostname" ]; smtpd_tls_security_level = "may"; smtpd_tls_chain_files = [ "${snakeoil}/key.pem" "${snakeoil}/cert.pem" ]; }; }; systemd.services.zsmtp-server = { description = "zsmtp debug server (plaintext)"; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${zsmtp}/bin/zsmtp serve 2525"; DynamicUser = true; }; }; systemd.services.zsmtp-server-tls = { description = "zsmtp debug server (STARTTLS)"; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${zsmtp}/bin/zsmtp serve --tls-cert ${snakeoil}/cert.pem --tls-key ${snakeoil}/key.pem 2526"; DynamicUser = true; }; }; }; testScript = '' machine.wait_for_unit("postfix.service") machine.wait_for_open_port(25) machine.wait_for_open_port(465) machine.wait_for_unit("zsmtp-server.service") machine.wait_for_unit("zsmtp-server-tls.service") machine.wait_for_open_port(2525) machine.wait_for_open_port(2526) with subtest("zsmtp client to postfix, plaintext"): machine.succeed( "printf 'Subject: interop\\r\\n\\r\\nzsmtp to postfix plain\\r\\n'" " | zsmtp send 127.0.0.1 25 bob@example.com alice@localhost" ) machine.wait_until_succeeds( "grep -r 'zsmtp to postfix plain' /var/spool/mail/alice/", timeout=60 ) with subtest("zsmtp client to postfix, STARTTLS"): machine.succeed( "printf 'Subject: interop\\r\\n\\r\\nzsmtp to postfix starttls\\r\\n'" " | zsmtp send --starttls --insecure 127.0.0.1 25" " bob@example.com alice@localhost" ) machine.wait_until_succeeds( "grep -r 'zsmtp to postfix starttls' /var/spool/mail/alice/", timeout=60 ) with subtest("zsmtp client to postfix, implicit TLS"): machine.succeed( "printf 'Subject: interop\\r\\n\\r\\nzsmtp to postfix smtps\\r\\n'" " | zsmtp send --tls --insecure 127.0.0.1 465" " bob@example.com alice@localhost" ) machine.wait_until_succeeds( "grep -r 'zsmtp to postfix smtps' /var/spool/mail/alice/", timeout=60 ) with subtest("swaks to zsmtp server, plaintext"): machine.succeed( "swaks --server 127.0.0.1:2525 --from bob@example.com" " --to alice@example.net --header 'Subject: swaks plain'" " --body 'swaks to zsmtp plain'" ) machine.wait_until_succeeds( "journalctl -u zsmtp-server | grep 'swaks to zsmtp plain'", timeout=60 ) with subtest("swaks to zsmtp server, STARTTLS"): machine.succeed( "swaks --tls --server 127.0.0.1:2526 --from bob@example.com" " --to alice@example.net --header 'Subject: swaks starttls'" " --body 'swaks to zsmtp starttls'" ) machine.wait_until_succeeds( "journalctl -u zsmtp-server-tls | grep 'swaks to zsmtp starttls'", timeout=60 ) ''; }