An SMTP client and server library for Zig implementing RFC 5321.
0

Configure Feed

Select the types of activity you want to include in your feed.

zig-smtp / nix / interop-test.nix
10 kB 308 lines
1# SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us> 2# SPDX-License-Identifier: MIT 3 4# NixOS VM test exercising zsmtp against third-party implementations: 5# - zsmtp client -> Postfix (25/465) and Exim (2625/2626): plaintext, 6# STARTTLS, and implicit TLS against each, verified by checking local 7# delivery to alice's mailbox 8# - swaks -> zsmtp server: plaintext and STARTTLS, verified by checking 9# the received message in the server's journal 10 11{ 12 testers, 13 callPackage, 14 runCommand, 15 openssl, 16}: 17let 18 zsmtp = callPackage ./package.nix { }; 19 20 snakeoil = 21 runCommand "zsmtp-test-cert" 22 { 23 nativeBuildInputs = [ openssl ]; 24 } 25 '' 26 mkdir -p $out 27 openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ 28 -keyout $out/key.pem -out $out/cert.pem -days 36500 -nodes \ 29 -subj "/CN=localhost" \ 30 -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" 31 ''; 32in 33testers.runNixOSTest { 34 name = "zsmtp-interop"; 35 36 nodes.machine = 37 { pkgs, ... }: 38 { 39 environment.systemPackages = [ 40 zsmtp 41 pkgs.swaks 42 ]; 43 44 users.users.alice.isNormalUser = true; 45 46 services.postfix = { 47 enable = true; 48 # Implicit-TLS smtpd on port 465; allow loopback without SASL. 49 enableSubmissions = true; 50 submissionsOptions = { 51 smtpd_client_restrictions = "permit_mynetworks,reject"; 52 }; 53 settings.main = { 54 mydestination = [ 55 "localhost" 56 "$myhostname" 57 ]; 58 smtpd_tls_security_level = "may"; 59 smtpd_tls_chain_files = [ 60 "${snakeoil}/key.pem" 61 "${snakeoil}/cert.pem" 62 ]; 63 }; 64 }; 65 66 # Exim: plaintext + STARTTLS on 2625, implicit TLS on 2626, delivering 67 # to /var/spool/exim-mail/<user>. 68 services.exim = { 69 enable = true; 70 config = '' 71 primary_hostname = machine.test 72 qualify_domain = localhost 73 local_interfaces = 127.0.0.1 74 daemon_smtp_ports = 2625 : 2626 75 tls_on_connect_ports = 2626 76 tls_advertise_hosts = * 77 tls_certificate = ${snakeoil}/cert.pem 78 tls_privatekey = ${snakeoil}/key.pem 79 acl_smtp_rcpt = acl_rcpt 80 81 begin acl 82 83 acl_rcpt: 84 accept 85 86 begin routers 87 88 local_users: 89 driver = accept 90 local_parts = alice : bob 91 transport = local_delivery 92 93 begin transports 94 95 local_delivery: 96 driver = appendfile 97 file = /var/spool/exim-mail/$local_part_data 98 user = exim 99 delivery_date_add 100 envelope_to_add 101 return_path_add 102 103 begin authenticators 104 105 plain_server: 106 driver = plaintext 107 public_name = PLAIN 108 server_prompts = : 109 server_condition = ''${if and{{eq{$auth2}{alice}}{eq{$auth3}{secret}}}} 110 server_set_id = $auth2 111 112 login_server: 113 driver = plaintext 114 public_name = LOGIN 115 server_prompts = Username:: : Password:: 116 server_condition = ''${if and{{eq{$auth1}{alice}}{eq{$auth2}{secret}}}} 117 server_set_id = $auth1 118 ''; 119 }; 120 121 systemd.tmpfiles.rules = [ 122 "d /var/spool/exim-mail 0755 exim exim -" 123 ]; 124 125 systemd.services.zsmtp-server = { 126 description = "zsmtp debug server (plaintext)"; 127 wantedBy = [ "multi-user.target" ]; 128 serviceConfig = { 129 ExecStart = "${zsmtp}/bin/zsmtp serve 2525"; 130 DynamicUser = true; 131 }; 132 }; 133 134 systemd.services.zsmtp-server-tls = { 135 description = "zsmtp debug server (STARTTLS)"; 136 wantedBy = [ "multi-user.target" ]; 137 serviceConfig = { 138 ExecStart = "${zsmtp}/bin/zsmtp serve --tls-cert ${snakeoil}/cert.pem --tls-key ${snakeoil}/key.pem 2526"; 139 DynamicUser = true; 140 }; 141 }; 142 143 systemd.services.zsmtp-server-tlsc = { 144 description = "zsmtp debug server (implicit TLS)"; 145 wantedBy = [ "multi-user.target" ]; 146 serviceConfig = { 147 ExecStart = "${zsmtp}/bin/zsmtp serve --tls-cert ${snakeoil}/cert.pem --tls-key ${snakeoil}/key.pem --implicit-tls 2528"; 148 DynamicUser = true; 149 }; 150 }; 151 152 systemd.services.zsmtp-server-auth = { 153 description = "zsmtp debug server (authentication required)"; 154 wantedBy = [ "multi-user.target" ]; 155 serviceConfig = { 156 ExecStart = "${zsmtp}/bin/zsmtp serve --auth alice:secret 2527"; 157 DynamicUser = true; 158 }; 159 }; 160 }; 161 162 testScript = '' 163 machine.wait_for_unit("postfix.service") 164 machine.wait_for_open_port(25) 165 machine.wait_for_open_port(465) 166 machine.wait_for_unit("exim.service") 167 machine.wait_for_open_port(2625) 168 machine.wait_for_open_port(2626) 169 machine.wait_for_unit("zsmtp-server.service") 170 machine.wait_for_unit("zsmtp-server-tls.service") 171 machine.wait_for_unit("zsmtp-server-tlsc.service") 172 machine.wait_for_unit("zsmtp-server-auth.service") 173 machine.wait_for_open_port(2525) 174 machine.wait_for_open_port(2526) 175 machine.wait_for_open_port(2527) 176 machine.wait_for_open_port(2528) 177 178 179 def deliver(flags, port, needle, mailbox): 180 machine.succeed( 181 f"printf 'Subject: interop\\r\\n\\r\\n{needle}\\r\\n'" 182 f" | zsmtp send {flags} 127.0.0.1 {port}" 183 " bob@example.com alice@localhost" 184 ) 185 machine.wait_until_succeeds(f"grep -r '{needle}' {mailbox}", timeout=60) 186 187 188 servers = { 189 "postfix": (25, 465, "/var/spool/mail/alice/"), 190 "exim": (2625, 2626, "/var/spool/exim-mail/alice"), 191 } 192 193 for name, (port, tls_port, mailbox) in servers.items(): 194 with subtest(f"zsmtp client to {name}, plaintext"): 195 deliver("", port, f"zsmtp to {name} plain", mailbox) 196 197 with subtest(f"zsmtp client to {name}, STARTTLS"): 198 deliver( 199 "--starttls --insecure", port, f"zsmtp to {name} starttls", mailbox 200 ) 201 202 with subtest(f"zsmtp client to {name}, implicit TLS"): 203 deliver("--tls --insecure", tls_port, f"zsmtp to {name} smtps", mailbox) 204 205 for name, (port, tls_port, mailbox) in servers.items(): 206 with subtest(f"zsmtp client to {name}, CHUNKING"): 207 deliver("--chunking", port, f"zsmtp to {name} chunked", mailbox) 208 209 with subtest("zsmtp client to postfix, SMTPUTF8"): 210 machine.succeed( 211 "printf 'Subject: interop\\r\\n\\r\\nzsmtp to postfix utf8\\r\\n'" 212 " | zsmtp send --smtputf8 127.0.0.1 25" 213 " 'böb@example.com' alice@localhost" 214 ) 215 machine.wait_until_succeeds( 216 "grep -r 'zsmtp to postfix utf8' /var/spool/mail/alice/", timeout=60 217 ) 218 219 with subtest("zsmtp client to exim, AUTH PLAIN"): 220 deliver( 221 "--user alice --password secret --auth-method plain", 222 2625, 223 "zsmtp to exim auth plain", 224 "/var/spool/exim-mail/alice", 225 ) 226 227 with subtest("zsmtp client to exim, AUTH LOGIN"): 228 deliver( 229 "--user alice --password secret --auth-method login", 230 2625, 231 "zsmtp to exim auth login", 232 "/var/spool/exim-mail/alice", 233 ) 234 235 with subtest("zsmtp client to exim, wrong password is rejected"): 236 machine.fail( 237 "printf 'Subject: interop\\r\\n\\r\\nnope\\r\\n'" 238 " | zsmtp send --user alice --password wrong 127.0.0.1 2625" 239 " bob@example.com alice@localhost" 240 ) 241 242 with subtest("swaks to zsmtp server, AUTH PLAIN"): 243 machine.succeed( 244 "swaks --server 127.0.0.1:2527 --auth PLAIN --auth-user alice" 245 " --auth-password secret --from bob@example.com" 246 " --to alice@example.net --body 'swaks to zsmtp auth plain'" 247 ) 248 machine.wait_until_succeeds( 249 "journalctl -u zsmtp-server-auth | grep 'swaks to zsmtp auth plain'", 250 timeout=60, 251 ) 252 253 with subtest("swaks to zsmtp server, AUTH LOGIN"): 254 machine.succeed( 255 "swaks --server 127.0.0.1:2527 --auth LOGIN --auth-user alice" 256 " --auth-password secret --from bob@example.com" 257 " --to alice@example.net --body 'swaks to zsmtp auth login'" 258 ) 259 machine.wait_until_succeeds( 260 "journalctl -u zsmtp-server-auth | grep 'swaks to zsmtp auth login'", 261 timeout=60, 262 ) 263 264 with subtest("swaks to zsmtp server, wrong password is rejected"): 265 machine.fail( 266 "swaks --server 127.0.0.1:2527 --auth PLAIN --auth-user alice" 267 " --auth-password wrong --from bob@example.com" 268 " --to alice@example.net --body nope" 269 ) 270 271 with subtest("unauthenticated mail to auth-required server is rejected"): 272 machine.fail( 273 "printf 'Subject: interop\\r\\n\\r\\nnope\\r\\n'" 274 " | zsmtp send 127.0.0.1 2527 bob@example.com alice@example.net" 275 ) 276 277 with subtest("swaks to zsmtp server, plaintext"): 278 machine.succeed( 279 "swaks --server 127.0.0.1:2525 --from bob@example.com" 280 " --to alice@example.net --header 'Subject: swaks plain'" 281 " --body 'swaks to zsmtp plain'" 282 ) 283 machine.wait_until_succeeds( 284 "journalctl -u zsmtp-server | grep 'swaks to zsmtp plain'", timeout=60 285 ) 286 287 with subtest("swaks to zsmtp server, implicit TLS"): 288 machine.succeed( 289 "swaks --tlsc --server 127.0.0.1:2528 --from bob@example.com" 290 " --to alice@example.net --header 'Subject: swaks tlsc'" 291 " --body 'swaks to zsmtp implicit tls'" 292 ) 293 machine.wait_until_succeeds( 294 "journalctl -u zsmtp-server-tlsc | grep 'swaks to zsmtp implicit tls'", 295 timeout=60, 296 ) 297 298 with subtest("swaks to zsmtp server, STARTTLS"): 299 machine.succeed( 300 "swaks --tls --server 127.0.0.1:2526 --from bob@example.com" 301 " --to alice@example.net --header 'Subject: swaks starttls'" 302 " --body 'swaks to zsmtp starttls'" 303 ) 304 machine.wait_until_succeeds( 305 "journalctl -u zsmtp-server-tls | grep 'swaks to zsmtp starttls'", timeout=60 306 ) 307 ''; 308}