├── .clang-format ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── docker.yml │ ├── gotests.yml │ └── govulncheck.yml ├── .gitignore ├── .mkdocs.yml ├── LICENSE ├── Makefile ├── README.md ├── chasquid.go ├── cmd ├── chasquid-util │ ├── chasquid-util.go │ ├── dkim.go │ ├── test.sh │ ├── test_alias_resolve.cmy │ ├── test_bad_args.cmy │ ├── test_dkim.cmy │ ├── test_domaininfo_remove.cmy │ ├── test_general.cmy │ ├── test_openssl_genpkey_ed25519.pem │ ├── test_openssl_genpkey_rsa.pem │ └── test_users.cmy ├── dovecot-auth-cli │ ├── dovecot-auth-cli.go │ ├── test.sh │ ├── test_auth_bad_proto.cmy │ ├── test_auth_error.cmy │ ├── test_auth_no.cmy │ ├── test_auth_yes.cmy │ ├── test_exists_bad_proto.cmy │ ├── test_exists_error.cmy │ ├── test_exists_notfound.cmy │ ├── test_exists_yes.cmy │ ├── test_missing_socket.cmy │ └── test_wrong_command.cmy ├── mda-lmtp │ ├── .gitignore │ ├── mda-lmtp.go │ ├── test-email │ ├── test.sh │ ├── test_puny_ascii.cmy │ ├── test_puny_invalid.cmy │ ├── test_puny_utf8.cmy │ ├── test_tcp_null.cmy │ ├── test_tcp_success.cmy │ ├── test_unix_failure.cmy │ ├── test_unix_success.cmy │ └── test_utf8.cmy └── smtp-check │ └── smtp-check.go ├── dnsoverride.go ├── docker ├── Dockerfile ├── README.md ├── add-user.sh ├── chasquid.conf ├── dovecot.conf └── entrypoint.sh ├── docs ├── aliases.md ├── clients.md ├── contributing.md ├── dkim.md ├── docker.md ├── dovecot.md ├── flow.md ├── haproxy.md ├── hooks.md ├── howto.md ├── index.md ├── install.md ├── knownissues.md ├── man │ ├── chasquid-util.1 │ ├── chasquid-util.1.md │ ├── chasquid-util.1.pod │ ├── chasquid.1 │ ├── chasquid.1.md │ ├── chasquid.1.pod │ ├── chasquid.conf.5 │ ├── chasquid.conf.5.md │ ├── chasquid.conf.5.pod │ ├── generate.sh │ ├── index.md │ ├── mda-lmtp.1 │ ├── mda-lmtp.1.md │ ├── mda-lmtp.1.pod │ ├── smtp-check.1 │ ├── smtp-check.1.md │ └── smtp-check.1.pod ├── monitoring.md ├── relnotes.md ├── sec-levels.md ├── security.md └── tests.md ├── etc ├── chasquid │ ├── README │ ├── certs │ ├── chasquid.conf │ ├── domains │ │ └── .gitignore │ └── hooks │ │ └── post-data ├── fail2ban │ └── filter.d │ │ └── chasquid.conf └── systemd │ └── system │ └── chasquid.service ├── go.mod ├── go.sum ├── internal ├── aliases │ ├── aliases.go │ ├── aliases_test.go │ └── testdata │ │ ├── empty-hook.sh │ │ ├── erroring-hook.sh │ │ ├── fuzz │ │ └── FuzzReader │ │ │ ├── 1c24d2215db69748c6fd16797673ad11ebc7e6167fe1bc1f54c6959ec10407b6 │ │ │ ├── 4adaceaa32e2b32c00322948769d62c2dd42e1d9f4950d3c5b411c710e6d4a86 │ │ │ ├── 8234d8c5719f30e50525290db70743bf97d940e60591cf4a638c72158d35504a │ │ │ ├── c9c80ba9f513841cb081fe9bb7439d36f9f7a06bb999d4c39441991ccc878a9e │ │ │ └── d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d │ │ ├── invalid-hook.sh │ │ └── normal-hook.sh ├── auth │ ├── auth.go │ ├── auth_test.go │ └── testdata │ │ └── fuzz │ │ └── FuzzDecodeResponse │ │ ├── 0274b170c6fe2654ca5418a914b804e9c7cc5d8e5c2a7c5fcf5c29540ec5ae52 │ │ ├── 24d35771ef1fe0645d90b061e13a777faea328736483ec2833b63950d26b5399 │ │ ├── 2e5d0b26626f2d2dd6fb423e1e1cc432277ae9877c622fe6ca067e247bc11c9d │ │ ├── 2ef1aee5347414c139270ebb6ea63d2223a8c0c7c8ec30a2ca7152f4c18f1c74 │ │ ├── 4b9259040da90f06aa2b593ee20fdffefeda813c59430050f15965bd9471235e │ │ ├── 6c2c0b4f81a675d91d1291bfdcddb7c9d43cf6264dd7763cfed31a3946854e27 │ │ ├── 6e05782952b68c7ccd94160ad6ea45e7f766397850b08e78f89407a94350825c │ │ ├── c2ae184876dd0fe9acfc8a5e2f2174a968b889b01e0f5c9a61fa27d7361f0091 │ │ ├── d9aa9c617d1f5b3021aca758b9d896d136e3b16ed53233d02abffd02aa73ffa4 │ │ └── de05c7993312bab83e8114e9d9ced331c49822dc55c1a353f1cc9718a28226e7 ├── config │ ├── config.go │ ├── config.pb.go │ ├── config.proto │ └── config_test.go ├── courier │ ├── courier.go │ ├── fakeserver_test.go │ ├── mda.go │ ├── mda_test.go │ ├── smtp.go │ └── smtp_test.go ├── dkim │ ├── canonicalize.go │ ├── canonicalize_test.go │ ├── context.go │ ├── context_test.go │ ├── dns.go │ ├── dns_test.go │ ├── file_test.go │ ├── header.go │ ├── header_test.go │ ├── message.go │ ├── message_test.go │ ├── sign.go │ ├── sign_test.go │ ├── testdata │ │ ├── .gitignore │ │ ├── 01-rfc8463.dns │ │ ├── 01-rfc8463.error │ │ ├── 01-rfc8463.msg │ │ ├── 01-rfc8463.result │ │ ├── 02-too_many_headers.dns │ │ ├── 02-too_many_headers.error │ │ ├── 02-too_many_headers.msg │ │ ├── 02-too_many_headers.readme │ │ ├── 02-too_many_headers.result │ │ ├── 03-bad_message.error │ │ ├── 03-bad_message.msg │ │ ├── 04-bad_dkim_signature_header.msg │ │ ├── 04-bad_dkim_signature_header.readme │ │ ├── 04-bad_dkim_signature_header.result │ │ ├── 05-dns_temp_error.dns │ │ ├── 05-dns_temp_error.msg │ │ ├── 05-dns_temp_error.result │ │ ├── 06-dns_perm_error.dns │ │ ├── 06-dns_perm_error.msg │ │ ├── 06-dns_perm_error.result │ │ ├── 07-algo_mismatch.dns │ │ ├── 07-algo_mismatch.msg │ │ ├── 07-algo_mismatch.readme │ │ ├── 07-algo_mismatch.result │ │ ├── 08-our_signature.dns │ │ ├── 08-our_signature.msg │ │ ├── 08-our_signature.result │ │ ├── 09-limited_body.dns │ │ ├── 09-limited_body.msg │ │ ├── 09-limited_body.readme │ │ ├── 09-limited_body.result │ │ ├── 10-strict_domain_check_pass.dns │ │ ├── 10-strict_domain_check_pass.msg │ │ ├── 10-strict_domain_check_pass.result │ │ ├── 11-strict_domain_check_fail.dns │ │ ├── 11-strict_domain_check_fail.msg │ │ ├── 11-strict_domain_check_fail.readme │ │ └── 11-strict_domain_check_fail.result │ ├── verify.go │ └── verify_test.go ├── domaininfo │ ├── domaininfo.go │ ├── domaininfo.pb.go │ ├── domaininfo.proto │ └── domaininfo_test.go ├── dovecot │ ├── dovecot.go │ └── dovecot_test.go ├── envelope │ ├── envelope.go │ └── envelope_test.go ├── expvarom │ ├── expvarom.go │ └── expvarom_test.go ├── haproxy │ ├── haproxy.go │ └── haproxy_test.go ├── localrpc │ ├── client_test.go │ ├── e2e_test.go │ ├── localrpc.go │ └── server_test.go ├── maillog │ ├── maillog.go │ └── maillog_test.go ├── nettrace │ ├── bench_test.go │ ├── context.go │ ├── context_test.go │ ├── evtring.go │ ├── histogram.go │ ├── histogram_test.go │ ├── http.go │ ├── http_test.go │ ├── templates │ │ ├── _latency.html.tmpl │ │ ├── _recursive.html.tmpl │ │ ├── _single.html.tmpl │ │ ├── index.html.tmpl │ │ └── style.css │ ├── test_server.go │ ├── trace.go │ └── trace_test.go ├── normalize │ ├── normalize.go │ ├── normalize_test.go │ └── testdata │ │ └── fuzz │ │ ├── FuzzAddr │ │ ├── 31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1 │ │ ├── 7aba1e0ef80990ccac3731800dbb0267c4c8b7156d4da3b8a5f1b57a570adfb8 │ │ ├── ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32 │ │ ├── d8637022b61fb5c4df4e153063564accd6331debaafdd594405c320a5e9f2e70 │ │ └── dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19 │ │ ├── FuzzDomain │ │ ├── 263da65bb5a59369f294d26a64a36a989a9a36ed5c60950b123e395bedbe881c │ │ ├── 31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1 │ │ ├── 6d603c8b9fbe8b9aa021dbde499ec1b3a00922b9338c68b2984cd314c3d5e633 │ │ ├── ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32 │ │ └── dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19 │ │ ├── FuzzDomainToUnicode │ │ ├── 31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1 │ │ ├── 7aba1e0ef80990ccac3731800dbb0267c4c8b7156d4da3b8a5f1b57a570adfb8 │ │ ├── ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32 │ │ ├── d8637022b61fb5c4df4e153063564accd6331debaafdd594405c320a5e9f2e70 │ │ └── dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19 │ │ └── FuzzUser │ │ ├── 263da65bb5a59369f294d26a64a36a989a9a36ed5c60950b123e395bedbe881c │ │ ├── 31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1 │ │ ├── 6d603c8b9fbe8b9aa021dbde499ec1b3a00922b9338c68b2984cd314c3d5e633 │ │ ├── ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32 │ │ └── dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19 ├── protoio │ ├── protoio.go │ ├── protoio_test.go │ └── testpb │ │ ├── dummy.go │ │ ├── testpb.pb.go │ │ └── testpb.proto ├── queue │ ├── dsn.go │ ├── dsn_test.go │ ├── queue.go │ ├── queue.pb.go │ ├── queue.proto │ └── queue_test.go ├── safeio │ ├── safeio.go │ └── safeio_test.go ├── set │ ├── set.go │ └── set_test.go ├── smtp │ ├── smtp.go │ └── smtp_test.go ├── smtpsrv │ ├── conn.go │ ├── conn_test.go │ ├── dotreader.go │ ├── dotreader_test.go │ ├── fuzz_test.go │ ├── server.go │ ├── server_test.go │ └── testdata │ │ └── fuzz │ │ └── FuzzConnection │ │ ├── 3d7e992212e817da7afdb7a4e769ceec1d4047a2e630bec4b35ecd4d55560424 │ │ ├── 68d8c7b5f149996ffd46ad9a15852165d8c1cbd6c03cceb9382e5add16415c94 │ │ ├── 79e51b30c215fb19a29855deebf2ed8299b35ca6f14db9681ee504e216c44a7f │ │ ├── 83ab02fccf91c1b9c0c972de745dc2a45d23dc3236f9027e605c3e017d8898fe │ │ ├── a24124ade554d7a25de538f2cbbced6245ba60e90d221e51590456e222c80359 │ │ ├── b896b41db27f6e36e4e727ac4f7b3d02fad34d217855c0d433ea3a325951b3bf │ │ ├── bf15e6fb937795251090940ac60a37705b36a13e71a9557e7aaf0618ea2cf661 │ │ ├── d1b1ccbbb380c53282cc2689c4bd9ff0d03a03698e9be55371739ef95d7dd671 │ │ ├── dc70e53325976a3a1067feb0b0c956c5a9abec1c867f8198808ccff83f594ded │ │ ├── e7682fde78ce0d78ddc7a818f151b6f04466a2c122197a2e4e8048d194ed72c2 │ │ └── fd41d0c11b1bb7f89825934b2ec51db1df166e34b4610e8089549eedf2e3635c ├── sts │ ├── sts.go │ └── sts_test.go ├── testlib │ ├── testlib.go │ └── testlib_test.go ├── tlsconst │ ├── ciphers.go │ ├── generate-ciphers.py │ ├── tlsconst.go │ └── tlsconst_test.go ├── trace │ └── trace.go └── userdb │ ├── userdb.go │ ├── userdb.pb.go │ ├── userdb.proto │ └── userdb_test.go ├── monitoring.go └── test ├── .gitignore ├── Dockerfile ├── README.md ├── cover.sh ├── run.sh ├── stress-01-load ├── config │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── aliases └── run.sh ├── stress-02-connections ├── config │ └── chasquid.conf └── run.sh ├── stress.sh ├── t-01-simple_local ├── config │ ├── certs │ │ ├── noprivkey │ │ │ └── fullchain.pem │ │ ├── not_a_dir │ │ └── symlink │ └── chasquid.conf ├── content ├── hosts ├── msmtprc └── run.sh ├── t-02-exim ├── .gitignore ├── config │ ├── chasquid.conf │ └── exim4.in ├── content ├── get-exim4-debian.sh ├── hosts ├── run.sh ├── smtpc.conf └── zones ├── t-03-queue_persistency ├── addtoqueue.go ├── config │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── .gitignore ├── content └── run.sh ├── t-04-aliases ├── .gitignore ├── alias-resolve-hook ├── chasquid-util.sh ├── config │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── aliases ├── content ├── hosts ├── run.sh ├── smtpc.conf └── test_chasquid_util.cmy ├── t-05-null_address ├── config │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── aliases ├── content ├── expected_dsr ├── hosts ├── run.sh ├── sendmail.cmy └── smtpc.conf ├── t-06-idna ├── .gitignore ├── A │ └── chasquid.conf ├── B │ └── chasquid.conf ├── from_A_to_B ├── from_B_to_A ├── run.sh └── zones ├── t-07-smtputf8 ├── config │ └── chasquid.conf ├── content └── run.sh ├── t-09-loop ├── A │ ├── chasquid.conf │ └── domains │ │ └── srv-A │ │ └── aliases ├── B │ ├── chasquid.conf │ └── domains │ │ └── srv-B │ │ └── aliases ├── content ├── hosts ├── run.sh ├── smtpc.conf └── zones ├── t-10-hooks ├── .gitignore ├── config │ ├── chasquid.conf │ └── hooks │ │ ├── post-data.bad1 │ │ ├── post-data.bad2 │ │ ├── post-data.bad3 │ │ ├── post-data.bad4 │ │ └── post-data.good ├── content ├── hosts ├── run.sh └── smtpc.conf ├── t-11-dovecot ├── config │ ├── chasquid.conf │ ├── domains │ │ └── srv │ │ │ └── .keep │ ├── dovecot.conf.in │ └── passwd ├── content ├── hosts ├── run.sh └── smtpc.conf ├── t-12-minor_dialogs ├── auth_multi_dialog.cmy ├── auth_not_tls.cmy ├── auth_too_many_failures.cmy ├── bad_data.cmy ├── bad_data_dot.cmy ├── bad_data_dot_2.cmy ├── bad_data_dot_on_message_too_big.cmy ├── bad_mail_from.cmy ├── bad_rcpt_to.cmy ├── config │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── aliases ├── data_dot_stuffing.cmy ├── data_dot_stuffing.cmy.verify ├── empty_helo.cmy ├── helo.cmy ├── line_too_long.cmy ├── message_too_big.cmy ├── run.sh ├── sendmail.cmy ├── sendmail.cmy.verify ├── unknown_command.cmy └── wrong_proto.cmy ├── t-13-reload ├── .gitignore ├── config │ └── chasquid.conf ├── content ├── hosts ├── run.sh └── smtpc.conf ├── t-14-tls_tracking ├── A │ ├── chasquid.conf │ └── domains │ │ └── srv-A │ │ └── .keep ├── B │ ├── chasquid.conf │ └── domains │ │ └── srv-B │ │ └── .keep ├── config │ └── chasquid.conf ├── content ├── hosts ├── run.sh ├── smtpc.conf └── zones ├── t-16-spf ├── A │ └── chasquid.conf ├── B │ └── chasquid.conf ├── config │ └── chasquid.conf ├── content ├── expected_dsn ├── hosts ├── run.sh ├── smtpc.conf ├── zones.t0 └── zones.t1 ├── t-17-maillog ├── .gitignore ├── config │ └── chasquid.conf.in ├── content ├── hosts ├── run.sh └── smtpc.conf ├── t-18-haproxy ├── config │ └── chasquid.conf ├── content ├── haproxy.cfg ├── hosts ├── msmtprc └── run.sh ├── t-20-bad_configs ├── .gitignore ├── c-01-empty │ └── .expected-error ├── c-02-all_dirs_missing │ ├── .expected-error │ └── chasquid.conf ├── c-03-no_certs │ ├── .expected-error │ ├── certs │ │ └── testserver │ │ │ └── .keep │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-04-no_cert_dirs │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-05-no_addrs │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-06-bad_maillog │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-07-bad_domain_info │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-08-bad_sts_cache │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-09-bad_queue_dir │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-10-empty_listening_addr │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-11-bad_dkim_key │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── dkim__selector.pem ├── c-12-bad_users │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── users ├── c-13-bad_aliases │ ├── .expected-error │ ├── chasquid.conf │ └── domains │ │ └── testserver │ │ └── aliases ├── data-c-07-bad_domain_info │ └── domaininfo ├── data-c-08-bad_sts_cache │ └── sts-cache ├── data-c-09-bad_queue_dir │ └── queue └── run.sh ├── t-21-dkim ├── .gitignore ├── A │ ├── chasquid.conf │ └── s1._domainkey.srv-a.pem ├── B │ └── chasquid.conf ├── from_A_to_B ├── from_A_to_B.expected ├── from_B_to_A ├── from_B_to_A.expected ├── run.sh └── zones ├── t-22-forward_via ├── .gitignore ├── content ├── expected-chain-1 ├── expected-external-user333@kiwi ├── expected-primary-user111@dodo ├── external │ ├── chasquid.conf │ └── domains │ │ └── kiwi │ │ └── aliases ├── primary │ ├── chasquid.conf │ └── domains │ │ └── dodo │ │ └── aliases ├── run.sh ├── secondary │ ├── chasquid.conf │ └── domains │ │ └── dodo │ │ └── aliases ├── smtpc-secondary.conf └── zones └── util ├── chamuyero ├── check-hostaliases ├── conngen └── conngen.go ├── coverhtml └── coverhtml.go ├── docker_entrypoint.sh ├── exitcode ├── fexp └── fexp.go ├── generate_cert └── generate_cert.go ├── lib.sh ├── loadgen └── loadgen.go ├── mail_diff ├── minidns └── minidns.go ├── smtpc └── smtpc.go ├── test-mda └── writemailto /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Proto 2 | BasedOnStyle: Google 3 | IndentWidth: 8 4 | UseTab: AlignWithSpaces 5 | -------------------------------------------------------------------------------- /.github/workflows/gotests.yml: -------------------------------------------------------------------------------- 1 | name: "gotests" 2 | 3 | on: 4 | push: 5 | branches: [ "main", "next" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "main", "next" ] 9 | schedule: 10 | - cron: '29 21 * * 6' 11 | 12 | jobs: 13 | oldest_supported: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version-file: 'go.mod' 21 | - name: normal tests 22 | run: go test ./... 23 | - name: race tests 24 | run: go test -race ./... 25 | 26 | latest: 27 | runs-on: ubuntu-latest 28 | timeout-minutes: 5 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/setup-go@v5 32 | with: 33 | go-version: "1.x" 34 | check-latest: true 35 | - name: normal tests 36 | run: go test ./... 37 | - name: race tests 38 | run: go test -race ./... 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/govulncheck.yml: -------------------------------------------------------------------------------- 1 | name: "govulncheck" 2 | 3 | on: 4 | push: 5 | branches: [ "main", "next" ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ "main", "next" ] 9 | schedule: 10 | - cron: '29 21 * * 6' 11 | 12 | jobs: 13 | govulncheck: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 5 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: ">=1.19.2" 21 | check-latest: true 22 | - name: install govulncheck 23 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 24 | - name: run govulncheck 25 | run: govulncheck ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore anything beginning with a dot: these are usually temporary or 3 | # unimportant. 4 | .* 5 | 6 | # Exceptions to the rules above: files we care about that would otherwise be 7 | # excluded. 8 | !.gitignore 9 | !.clang-format 10 | !.github/ 11 | 12 | # The binaries. 13 | /chasquid 14 | /chasquid-util 15 | /smtp-check 16 | /mda-lmtp 17 | /dovecot-auth-cli 18 | cmd/chasquid-util/chasquid-util 19 | cmd/smtp-check/smtp-check 20 | cmd/mda-lmtp/mda-lmtp 21 | cmd/dovecot-auth-cli/dovecot-auth-cli 22 | 23 | # Test util binaries. 24 | test/util/conngen/conngen 25 | test/util/coverhtml/coverhtml 26 | test/util/fexp/fexp 27 | test/util/generate_cert/generate_cert 28 | test/util/gocovcat/gocovcat 29 | test/util/loadgen/loadgen 30 | test/util/minidns/minidns 31 | test/util/smtpc/smtpc 32 | 33 | # Test binary, generated during coverage tests. 34 | chasquid.test 35 | 36 | # chamuyero logs 37 | *.cmy.log 38 | 39 | # Exclude any .pem files, to prevent accidentally including test keys and 40 | # certificates. 41 | *.pem 42 | -------------------------------------------------------------------------------- /.mkdocs.yml: -------------------------------------------------------------------------------- 1 | # mkdocs configuration 2 | # 3 | # To test changes locally, run: 4 | # mkdocs serve -f .mkdocs.yml 5 | 6 | site_name: chasquid documentation 7 | 8 | # Point the repo to github to make it easier for users to do edits, even if 9 | # it's not the canonical location. 10 | repo_url: https://github.com/albertito/chasquid 11 | 12 | markdown_extensions: 13 | - codehilite: 14 | guess_lang: false 15 | - attr_list 16 | - admonition 17 | 18 | theme: readthedocs 19 | 20 | exclude_docs: | 21 | man/*.1 22 | man/*.5 23 | 24 | nav: 25 | - Home: index.md 26 | - How-to: howto.md 27 | - Install: install.md 28 | - Manpages: man/index.md 29 | - All: 30 | - aliases.md 31 | - hooks.md 32 | - clients.md 33 | - dovecot.md 34 | - dkim.md 35 | - haproxy.md 36 | - docker.md 37 | - flow.md 38 | - monitoring.md 39 | - sec-levels.md 40 | - tests.md 41 | - relnotes.md 42 | - knownissues.md 43 | - contributing.md 44 | 45 | not_in_nav: | 46 | security.md 47 | man/* 48 | 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | ifndef VERSION 3 | VERSION = `git describe --always --long --dirty --tags` 4 | endif 5 | 6 | # https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal 7 | ifndef SOURCE_DATE_EPOCH 8 | SOURCE_DATE_EPOCH = `git log -1 --format=%ct` 9 | endif 10 | 11 | 12 | default: chasquid 13 | 14 | all: chasquid chasquid-util smtp-check mda-lmtp dovecot-auth-cli 15 | 16 | 17 | chasquid: 18 | go build -ldflags="\ 19 | -X main.version=${VERSION} \ 20 | -X main.sourceDateTs=${SOURCE_DATE_EPOCH} \ 21 | " ${GOFLAGS} 22 | 23 | 24 | chasquid-util: 25 | go build ${GOFLAGS} ./cmd/chasquid-util/ 26 | 27 | smtp-check: 28 | go build ${GOFLAGS} ./cmd/smtp-check/ 29 | 30 | mda-lmtp: 31 | go build ${GOFLAGS} ./cmd/mda-lmtp/ 32 | 33 | dovecot-auth-cli: 34 | go build ${GOFLAGS} ./cmd/dovecot-auth-cli/ 35 | 36 | test: 37 | go test ${GOFLAGS} ./... 38 | setsid -w ./test/run.sh 39 | setsid -w ./test/stress.sh 40 | setsid -w ./cmd/chasquid-util/test.sh 41 | setsid -w ./cmd/mda-lmtp/test.sh 42 | setsid -w ./cmd/dovecot-auth-cli/test.sh 43 | 44 | 45 | install-binaries: chasquid chasquid-util smtp-check mda-lmtp 46 | mkdir -p /usr/local/bin/ 47 | cp -a chasquid chasquid-util smtp-check mda-lmtp /usr/local/bin/ 48 | 49 | install-config-skeleton: 50 | if ! [ -d /etc/chasquid ] ; then cp -arv etc / ; fi 51 | 52 | if ! [ -d /var/lib/chasquid ]; then \ 53 | mkdir -v /var/lib/chasquid; \ 54 | chmod -v 0700 /var/lib/chasquid ; \ 55 | chown -v mail:mail /var/lib/chasquid ; \ 56 | fi 57 | 58 | fmt: 59 | go vet ./... 60 | gofmt -s -w . 61 | clang-format -i $(shell find . -iname '*.proto') 62 | 63 | .PHONY: chasquid test \ 64 | chasquid-util smtp-check mda-lmtp dovecot-auth-cli \ 65 | fmt 66 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../../test/util/lib.sh" 5 | 6 | init 7 | 8 | if [ "${GOCOVERDIR}" != "" ]; then 9 | GOFLAGS="-cover -covermode=count -o chasquid-util $GOFLAGS" 10 | fi 11 | 12 | # shellcheck disable=SC2086 13 | go build $GOFLAGS -tags="$GOTAGS" . 14 | 15 | function r() { 16 | ./chasquid-util -C=.config "$@" 17 | } 18 | 19 | function check_userdb() { 20 | if ! r check-userdb domain > /dev/null; then 21 | echo check-userdb failed 22 | exit 1 23 | fi 24 | } 25 | 26 | 27 | rm -rf .config/ 28 | mkdir -p .config/ .data/domaininfo 29 | echo 'data_dir: ".data"' >> .config/chasquid.conf 30 | 31 | if ! r print-config > /dev/null; then 32 | fail print-config 33 | fi 34 | 35 | # We intentionally run this when the domain directory doesn't exist, as we 36 | # want to confirm it creates it. 37 | if ! r user-add interactive@domain --password=passwd > /dev/null; then 38 | fail user-add 39 | fi 40 | 41 | # Interactive authentication. 42 | # Need to wrap the execution under "script" since the interaction requires an 43 | # actual TTY, and that's a fairly portable way to do that. 44 | if hash script 2>/dev/null; then 45 | if ! (echo passwd; echo passwd ) \ 46 | | script \ 47 | -qfec "./chasquid-util -C=.config authenticate interactive@domain" \ 48 | ".script-out" \ 49 | | grep -q "Authentication succeeded"; 50 | then 51 | fail interactive authentication 52 | fi 53 | fi 54 | 55 | C=$(r print-config | grep hostname) 56 | if ! ( echo "$C" | grep -E -q "hostname:.*\"$HOSTNAME\"" ); then 57 | echo print-config failed 58 | echo output: "$C" 59 | exit 1 60 | fi 61 | 62 | rm -rf .keys/ 63 | mkdir .keys/ 64 | 65 | # Run all the chamuyero tests. 66 | for i in *.cmy; do 67 | if ! chamuyero "$i" > "$i.log" 2>&1 ; then 68 | echo "# Test $i failed, log follows" 69 | cat "$i.log" 70 | exit 1 71 | fi 72 | done 73 | 74 | success 75 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test_alias_resolve.cmy: -------------------------------------------------------------------------------- 1 | # Test success. 2 | server unix_listen .data/localrpc-v1 3 | c = ./chasquid-util -C=.config aliases-resolve test@test.com 4 | 5 | server <- AliasResolve Address=test%40test.com 6 | server -> 200 %28email%29=r1%40r1.com&%28pipe%29=cmd%20args 7 | 8 | c <- (email) r1@r1.com 9 | c <- (pipe) cmd args 10 | c wait 0 11 | 12 | 13 | # Test error. 14 | server unix_listen .data/localrpc-v1 15 | c = ./chasquid-util -C=.config aliases-resolve test@test.com 16 | 17 | server <- AliasResolve Address=test%40test.com 18 | server -> 500 This is a test error 19 | 20 | c <- Error resolving: This is a test error 21 | c wait 1 22 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test_bad_args.cmy: -------------------------------------------------------------------------------- 1 | # Unknown argument. 2 | c = ./chasquid-util --config_dir=.config blahrarghar 3 | c <- Unknown argument "blahrarghar" 4 | c wait 1 5 | 6 | c = ./chasquid-util --config_dir=.nonono check-userdb 7 | c <- Error: file ".nonono/domains//users" does not exist 8 | c wait 1 9 | 10 | c = ./chasquid-util --config_dir=.nonono print-config 11 | c <- Error loading config: failed to read config at ".nonono/chasquid.conf": open .nonono/chasquid.conf: no such file or directory 12 | c wait 1 13 | 14 | c = ./chasquid-util --config_dir=.nonono aliases-resolve email@addr 15 | c <- Error loading config: failed to read config at ".nonono/chasquid.conf": open .nonono/chasquid.conf: no such file or directory 16 | c wait 1 17 | 18 | c = ./chasquid-util --config_dir=.nonono domaininfo-remove domain 19 | c <- Error loading config: failed to read config at ".nonono/chasquid.conf": open .nonono/chasquid.conf: no such file or directory 20 | c wait 1 21 | 22 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test_domaininfo_remove.cmy: -------------------------------------------------------------------------------- 1 | # Test success. 2 | server unix_listen .data/localrpc-v1 3 | c = ./chasquid-util -C=.config domaininfo-remove domain.com 4 | 5 | server <- DomaininfoClear Domain=domain.com 6 | server -> 200 7 | 8 | c wait 0 9 | 10 | 11 | # Test error. 12 | server unix_listen .data/localrpc-v1 13 | c = ./chasquid-util -C=.config domaininfo-remove domain.com 14 | 15 | server <- DomaininfoClear Domain=domain.com 16 | server -> 500 This is a test error 17 | 18 | c <- Error removing domaininfo entry: This is a test error 19 | c wait 1 20 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test_general.cmy: -------------------------------------------------------------------------------- 1 | # --help 2 | c = ./chasquid-util --config_dir=.config --help 3 | c <- 4 | c <- Usage: 5 | c wait 0 6 | 7 | # print-config 8 | c = ./chasquid-util -C=.config print-config 9 | c <~ hostname: +".*" 10 | c <~ max_data_size_mb: +50 11 | c <~ smtp_address: +"systemd" 12 | c <~ submission_address: +"systemd" 13 | c <~ submission_over_tls_address: +"systemd" 14 | c <~ mail_delivery_agent_bin: +"maildrop" 15 | c <~ mail_delivery_agent_args: +"-f" 16 | c <~ mail_delivery_agent_args: +"%from%" 17 | c <~ mail_delivery_agent_args: +"-d" 18 | c <~ mail_delivery_agent_args: +"%to_user%" 19 | c <~ data_dir: +".data" 20 | c <~ suffix_separators: +"\+" 21 | c <~ drop_characters: +"\." 22 | c <~ mail_log_path: +"" 23 | c wait 0 24 | 25 | # Deprecated --configdir. 26 | c = ./chasquid-util --configdir=.config print-config 27 | c <- Option --configdir is deprecated, use --config_dir instead 28 | c <~ hostname: +".*" 29 | c wait 0 30 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test_openssl_genpkey_ed25519.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEIBul+k51unaApEcZBmt1i65n09asM/howsN4B1AjNY5V 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /cmd/chasquid-util/test_openssl_genpkey_rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCJ5laGXt2fEcbK 3 | 5xlLb53ITE9DK2P9pjjpFcnjJPfNZjyCHliRVnYvXH77tczPNKD/kVHluQphX1dr 4 | 6M7piWGuJgqje21ZKbFTSEMpsbt2kP1Ovu2MJyYqoTUhd62XoNeVCXstF2aR4g0P 5 | iC1/0DXM48tHf/6S+y7W7ZxSwQVWzfoWptN7gFgufZFCVPghChxPl5US+gdzlqkq 6 | 5M4oFhOGFZFpJkb1he+x13VSOeBHZeaLO7l7+GPZ/db72uZFHLW6SZSvK4xQcxp7 7 | ZXbtbLW+seJigxQDWRAlj4dMbmQyS5q3E26L3KOZ2qtBS8IhTYVrmPElhBMsjPhT 8 | T/Pi1K1HAgMBAAECggEAJRKywk8wv7oUuqnkh/5K6fVx/bnlmOSeOjOsYg+nOyY4 9 | MDceUnxvK45vaRZYKICao/qajOrxWno6U310Wx6fDyWVCJx/KlBmJuCvhb8NifOy 10 | 1f/IdzxzK1TJpuS426HXM28oGVhIMAIYxssyiEEepaW8Gc3UUAmNbyTUOP9BgzNZ 11 | 8qH5PA5MTTSiC1ql96b5otKPTlizxT13d3MYeSBN4b31Kb/AYRNSZlyOSBFCwcqf 12 | qeZEV4cwILX+58PYwfGGRYQWbCT62ZOs5AWiPt/cH9bZg7Gk1GqNx8HKFYaq+QHq 13 | hzXkiAjDZrANuK+xeQERuAWViagtX/qtNsQJwAJP6QKBgQDAJxGCYXxv//eM09uU 14 | DBz3jrAvROPylrX+eifoleWtdHnBHXcn9G3uNwOSpVS36PcspeH44w2B/WpzDsWn 15 | HjVWP2UmeWvPMZsY81Kxd4KINB/l+z03ctYuus80UJmYH70bkJ2uxLWioU1e/Edf 16 | ruMGx16ZdBVOCWJ7BtrUc41dswKBgQC3uGZ9QdVoEMDB7dFKl5foYqHE51p4ruMv 17 | Rpb5peFQJIdbbCUSaNN9swtDemktf0OnPyGMNLogGBZ/fhf8N2QX5+OwvQeh01Mu 18 | vPCFUZ4sNXv7lPPCwj23SmoMd1Z/RdksAlF8kHVBOsHrNurPUqkbhKLChuiAAKDC 19 | S0qdoAKwHQKBgQCsqe6X5BW3ZqEBkNX8wK2+3h7/Or5CHJ9JHmeCHkAWj1Vg7KNH 20 | 6eJmblTtj1cDM3n4Ss81oIFgz2C6JwoA06pF6A1ydyUjN4YQ84TZJ3TKA1yuggZO 21 | Lwi7UO4kKlD6W3rIrDik9OnqS1uFANj55+LlEn21EpSaXOB7gHte8L6U9QKBgEy8 22 | I2qbzbPak3gsiacbLCKu15xzeTFA8rjzRend4/7iUvrXb6CB0hwFZWX4wedz6WD4 23 | mF2ERF1VUkhL9V6uEAuAGnTeb0qjBnJWDivRDDyw1ikdbLbjBH4DAcpVKfacyPl9 24 | umVJvP/St94zoN2ZS/KncofHa2LTYFHmurKde6HtAoGBAIGZHOxJF856GJlq3otA 25 | 9wGGkNpmlVhHdYYvRKCMRr1FcduCrWFrr5zZT/fb6eHSoCtYjsiqRB/j6STgnBiX 26 | 2jSsPRadUrpyZOkINTl16vC6Bnv4plfP3VIBQAIoD9ViP0v9w8VrQyIGXWAeSHcu 27 | eXZyxHh81OEU8M2hWKZf54UI 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/dovecot-auth-cli.go: -------------------------------------------------------------------------------- 1 | // CLI used for testing the dovecot authentication package. 2 | // 3 | // NOT for production use. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | 12 | "blitiri.com.ar/go/chasquid/internal/dovecot" 13 | ) 14 | 15 | const help = ` 16 | Usage: 17 | dovecot-auth-cli exists user@domain 18 | dovecot-auth-cli auth user@domain password 19 | 20 | Example: 21 | dovecot-auth-cli /var/run/dovecot/auth-chasquid exists user@domain 22 | dovecot-auth-cli /var/run/dovecot/auth-chasquid auth user@domain password 23 | 24 | ` 25 | 26 | func main() { 27 | flag.Parse() 28 | 29 | if len(flag.Args()) < 3 { 30 | fmt.Fprint(os.Stderr, help) 31 | fmt.Print("no: invalid arguments\n") 32 | return 33 | } 34 | 35 | a := dovecot.NewAuth(flag.Arg(0)+"-userdb", flag.Arg(0)+"-client") 36 | 37 | var ok bool 38 | var err error 39 | 40 | switch flag.Arg(1) { 41 | case "exists": 42 | ok, err = a.Exists(flag.Arg(2)) 43 | case "auth": 44 | ok, err = a.Authenticate(flag.Arg(2), flag.Arg(3)) 45 | default: 46 | err = fmt.Errorf("unknown subcommand %q", flag.Arg(1)) 47 | } 48 | 49 | if ok { 50 | fmt.Print("yes\n") 51 | return 52 | } 53 | 54 | fmt.Printf("no: %v\n", err) 55 | } 56 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../../test/util/lib.sh" 5 | 6 | init 7 | 8 | # Build the binary once, so we can use it and launch it in chamuyero scripts. 9 | # Otherwise, we not only spend time rebuilding it over and over, but also "go 10 | # run" masks the exit code, which is something we care about. 11 | if [ "${GOCOVERDIR}" != "" ]; then 12 | GOFLAGS="-cover -covermode=count -o dovecot-auth-cli $GOFLAGS" 13 | fi 14 | 15 | # shellcheck disable=SC2086 16 | go build $GOFLAGS -tags="$GOTAGS" . 17 | 18 | if ! ./dovecot-auth-cli lalala 2>&1 | grep -q "invalid arguments"; then 19 | echo "cli worked with invalid arguments" 20 | exit 1 21 | fi 22 | 23 | for i in *.cmy; do 24 | if ! chamuyero "$i" > "$i.log" 2>&1 ; then 25 | echo "# Test $i failed, log follows" 26 | cat "$i.log" 27 | exit 1 28 | fi 29 | done 30 | 31 | success 32 | exit 0 33 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_auth_bad_proto.cmy: -------------------------------------------------------------------------------- 1 | 2 | # Break the handshake early. 3 | client unix_listen .dovecot-client 4 | c = ./dovecot-auth-cli .dovecot auth username password 5 | 6 | client <- VERSION 1 1 7 | client <~ CPID 8 | 9 | # We are supposed to send the handshake here. 10 | client close 11 | 12 | c <- no: error receiving handshake: EOF 13 | c wait 0 14 | 15 | 16 | # Break before sending the final response. 17 | client unix_listen .dovecot-client 18 | c = ./dovecot-auth-cli .dovecot auth username password 19 | 20 | client -> VERSION 1 1 21 | client -> SPID 12345 22 | client -> CUID 12345 23 | client -> COOKIE lovelycookie 24 | client -> MECH PLAIN 25 | client -> MECH LOGIN 26 | client -> DONE 27 | 28 | client <- VERSION 1 1 29 | client <~ CPID 30 | 31 | client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ= 32 | 33 | # We're supposed to send the OK/FAIL here. 34 | client close 35 | 36 | c <- no: error receiving response: EOF 37 | c wait 0 38 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_auth_error.cmy: -------------------------------------------------------------------------------- 1 | 2 | client unix_listen .dovecot-client 3 | 4 | c = ./dovecot-auth-cli .dovecot auth username password 5 | 6 | client -> VERSION 1 1 7 | client -> SPID 12345 8 | client -> CUID 12345 9 | client -> COOKIE lovelycookie 10 | client -> MECH PLAIN 11 | client -> MECH LOGIN 12 | client -> DONE 13 | 14 | client <- VERSION 1 1 15 | client <~ CPID 16 | 17 | client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ= 18 | client -> OTHER 19 | 20 | c <~ no: invalid response 21 | c wait 0 22 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_auth_no.cmy: -------------------------------------------------------------------------------- 1 | 2 | client unix_listen .dovecot-client 3 | 4 | c = ./dovecot-auth-cli .dovecot auth username password 5 | 6 | client -> VERSION 1 1 7 | client -> SPID 12345 8 | client -> CUID 12345 9 | client -> COOKIE lovelycookie 10 | client -> MECH PLAIN 11 | client -> MECH LOGIN 12 | client -> DONE 13 | 14 | client <- VERSION 1 1 15 | client <~ CPID 16 | 17 | client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ= 18 | client -> FAIL 1 19 | 20 | c <- no: 21 | c wait 0 22 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_auth_yes.cmy: -------------------------------------------------------------------------------- 1 | 2 | client unix_listen .dovecot-client 3 | 4 | c = ./dovecot-auth-cli .dovecot auth username password 5 | 6 | client -> VERSION 1 1 7 | client -> SPID 12345 8 | client -> CUID 12345 9 | client -> COOKIE lovelycookie 10 | client -> MECH PLAIN 11 | client -> MECH LOGIN 12 | client -> DONE 13 | 14 | client <- VERSION 1 1 15 | client <~ CPID 16 | 17 | client <- AUTH 1 PLAIN service=smtp secured no-penalty nologin resp=dXNlcm5hbWUAdXNlcm5hbWUAcGFzc3dvcmQ= 18 | client -> OK 1 19 | 20 | c <- yes 21 | c wait 0 22 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_exists_bad_proto.cmy: -------------------------------------------------------------------------------- 1 | 2 | # Invalid version 3 | userdb unix_listen .dovecot-userdb 4 | c = ./dovecot-auth-cli .dovecot exists username 5 | 6 | userdb -> VERSION 0 7 | c <~ no: error receiving version 8 | c wait 0 9 | 10 | 11 | # No SPID (send "NOSPID" instead 12 | userdb unix_listen .dovecot-userdb 13 | c = ./dovecot-auth-cli .dovecot exists username 14 | 15 | userdb -> VERSION 1 1 16 | userdb -> NOSPID 17 | c <~ no: error receiving SPID: 18 | c wait 0 19 | 20 | # Break before sending the final response. 21 | userdb unix_listen .dovecot-userdb 22 | c = ./dovecot-auth-cli .dovecot exists username 23 | 24 | userdb -> VERSION 1 1 25 | userdb -> SPID 12345 26 | userdb <- VERSION 1 1 27 | userdb <- USER 1 username service=smtp 28 | 29 | userdb close 30 | 31 | c <- no: error receiving response: EOF 32 | c wait 0 33 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_exists_error.cmy: -------------------------------------------------------------------------------- 1 | 2 | userdb unix_listen .dovecot-userdb 3 | 4 | c = ./dovecot-auth-cli .dovecot exists username 5 | 6 | userdb -> VERSION 1 1 7 | userdb -> SPID 12345 8 | 9 | userdb <- VERSION 1 1 10 | userdb <- USER 1 username service=smtp 11 | 12 | userdb -> OTHER 13 | 14 | c <~ no: invalid response: 15 | c wait 0 16 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_exists_notfound.cmy: -------------------------------------------------------------------------------- 1 | 2 | userdb unix_listen .dovecot-userdb 3 | 4 | c = ./dovecot-auth-cli .dovecot exists username 5 | 6 | userdb -> VERSION 1 1 7 | userdb -> SPID 12345 8 | 9 | userdb <- VERSION 1 1 10 | userdb <- USER 1 username service=smtp 11 | 12 | userdb -> NOTFOUND 1 13 | 14 | c <- no: 15 | c wait 0 16 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_exists_yes.cmy: -------------------------------------------------------------------------------- 1 | 2 | userdb unix_listen .dovecot-userdb 3 | 4 | c = ./dovecot-auth-cli .dovecot exists username 5 | 6 | userdb -> VERSION 1 1 7 | userdb -> SPID 12345 8 | 9 | userdb <- VERSION 1 1 10 | userdb <- USER 1 username service=smtp 11 | 12 | userdb -> USER 1 username system_groups_user=blah uid=10 gid=10 13 | 14 | c <- yes 15 | c wait 0 16 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_missing_socket.cmy: -------------------------------------------------------------------------------- 1 | 2 | c = ./dovecot-auth-cli .missingsocket exists username 3 | c <~ no: dial unix .missingsocket-userdb 4 | c wait 0 5 | 6 | c = ./dovecot-auth-cli .missingsocket auth username password 7 | c <~ no: dial unix .missingsocket-client 8 | c wait 0 9 | -------------------------------------------------------------------------------- /cmd/dovecot-auth-cli/test_wrong_command.cmy: -------------------------------------------------------------------------------- 1 | 2 | c = ./dovecot-auth-cli .missingsocket something else 3 | c <- no: unknown subcommand "something" 4 | c wait 0 5 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/.gitignore: -------------------------------------------------------------------------------- 1 | mda-lmtp 2 | *.log 3 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test-email: -------------------------------------------------------------------------------- 1 | Subject: test 2 | 3 | This is a test. 4 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../../test/util/lib.sh" 5 | 6 | init 7 | 8 | # Build the binary once, so we can use it and launch it in chamuyero scripts. 9 | # Otherwise, we not only spend time rebuilding it over and over, but also "go 10 | # run" masks the exit code, which is something we care about. 11 | go build 12 | 13 | for i in *.cmy; do 14 | if ! chamuyero "$i" > "$i.log" 2>&1 ; then 15 | echo "# Test $i failed, log follows" 16 | cat "$i.log" 17 | exit 1 18 | fi 19 | done 20 | 21 | success 22 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_puny_ascii.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc unix_listen .test-sock 3 | 4 | mda |= ./mda-lmtp --addr=.test-sock --addr_network=unix \ 5 | -to_puny -f from -d to < test-email 6 | 7 | nc -> 220 Hola desde expect 8 | 9 | nc <~ LHLO .* 10 | nc -> 250-Bienvenido! 11 | nc -> 250 Contame... 12 | 13 | nc <- MAIL FROM: 14 | nc -> 250 Aja 15 | 16 | nc <- RCPT TO: 17 | nc -> 250 Aja 18 | 19 | nc <- DATA 20 | nc -> 354 Dale 21 | 22 | nc <- Subject: test 23 | nc <- 24 | nc <- This is a test. 25 | nc <- . 26 | 27 | nc -> 250 Recibido 28 | 29 | nc <- QUIT 30 | nc -> 221 Chauchas 31 | 32 | mda wait 0 33 | 34 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_puny_invalid.cmy: -------------------------------------------------------------------------------- 1 | mda = ./mda-lmtp --addr=.test-sock --addr_network=unix \ 2 | -to_puny -f fröm -d xn--t < test-email 3 | mda <- cannot puny-encode recipient: idna: invalid label "t" 4 | mda wait 2 5 | 6 | mda = ./mda-lmtp --addr=.test-sock --addr_network=unix \ 7 | -to_puny -f xn--f -d to < test-email 8 | mda <- cannot puny-encode from: idna: invalid label "f" 9 | mda wait 2 10 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_puny_utf8.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc unix_listen .test-sock 3 | 4 | mda |= ./mda-lmtp --addr=.test-sock --addr_network=unix \ 5 | -to_puny -f fröm -d þo < test-email 6 | 7 | nc -> 220 Hola desde expect 8 | 9 | nc <~ LHLO .* 10 | nc -> 250-Bienvenido! 11 | nc -> 250 Contame... 12 | 13 | nc <- MAIL FROM: 14 | nc -> 250 Aja 15 | 16 | nc <- RCPT TO: 17 | nc -> 250 Aja 18 | 19 | nc <- DATA 20 | nc -> 354 Dale 21 | 22 | nc <- Subject: test 23 | nc <- 24 | nc <- This is a test. 25 | nc <- . 26 | 27 | nc -> 250 Recibido 28 | 29 | nc <- QUIT 30 | nc -> 221 Chauchas 31 | 32 | mda wait 0 33 | 34 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_tcp_null.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc tcp_listen localhost:14932 3 | 4 | mda |= ./mda-lmtp --addr=localhost:14932 -f "<>" -d "<>" < test-email 5 | 6 | nc -> 220 Hola desde expect 7 | 8 | nc <~ LHLO .* 9 | nc -> 250-Bienvenido! 10 | nc -> 250 Contame... 11 | 12 | nc <- MAIL FROM:<> 13 | nc -> 250 Aja 14 | 15 | nc <- RCPT TO:<> 16 | nc -> 250 Aja 17 | 18 | nc <- DATA 19 | nc -> 354 Dale 20 | 21 | nc <- Subject: test 22 | nc <- 23 | nc <- This is a test. 24 | nc <- . 25 | 26 | nc -> 250 Recibido 27 | 28 | nc <- QUIT 29 | nc -> 221 Chauchas 30 | 31 | mda wait 0 32 | 33 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_tcp_success.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc tcp_listen localhost:14932 3 | 4 | mda |= ./mda-lmtp --addr=localhost:14932 -f from -d to < test-email 5 | 6 | nc -> 220 Hola desde expect 7 | 8 | nc <~ LHLO .* 9 | nc -> 250-Bienvenido! 10 | nc -> 250 Contame... 11 | 12 | nc <- MAIL FROM: 13 | nc -> 250 Aja 14 | 15 | nc <- RCPT TO: 16 | nc -> 250 Aja 17 | 18 | nc <- DATA 19 | nc -> 354 Dale 20 | 21 | nc <- Subject: test 22 | nc <- 23 | nc <- This is a test. 24 | nc <- . 25 | 26 | nc -> 250 Recibido 27 | 28 | nc <- QUIT 29 | nc -> 221 Chauchas 30 | 31 | mda wait 0 32 | 33 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_unix_failure.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc unix_listen .test-sock 3 | 4 | mda = ./mda-lmtp --addr=.test-sock --addr_network=unix \ 5 | -f from -d to < test-email 6 | 7 | nc -> 220 Hola desde expect 8 | 9 | nc <~ LHLO .* 10 | nc -> 250-Bienvenido! 11 | nc -> 250 Contame... 12 | 13 | nc <- MAIL FROM: 14 | nc -> 250 Aja 15 | 16 | nc <- RCPT TO: 17 | nc -> 250 Aja 18 | 19 | nc <- DATA 20 | nc -> 354 Dale 21 | 22 | nc <- Subject: test 23 | nc <- 24 | nc <- This is a test. 25 | nc <- . 26 | 27 | nc -> 452 Nananana 28 | 29 | mda <- Delivery failed remotely: 452 Nananana 30 | mda wait 75 31 | 32 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_unix_success.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc unix_listen .test-sock 3 | 4 | mda |= ./mda-lmtp --addr=.test-sock --addr_network=unix \ 5 | -f from -d to < test-email 6 | 7 | nc -> 220 Hola desde expect 8 | 9 | nc <~ LHLO .* 10 | nc -> 250-Bienvenido! 11 | nc -> 250 Contame... 12 | 13 | nc <- MAIL FROM: 14 | nc -> 250 Aja 15 | 16 | nc <- RCPT TO: 17 | nc -> 250 Aja 18 | 19 | nc <- DATA 20 | nc -> 354 Dale 21 | 22 | nc <- Subject: test 23 | nc <- 24 | nc <- This is a test. 25 | nc <- . 26 | 27 | nc -> 250 Recibido 28 | 29 | nc <- QUIT 30 | nc -> 221 Chauchas 31 | 32 | mda wait 0 33 | 34 | -------------------------------------------------------------------------------- /cmd/mda-lmtp/test_utf8.cmy: -------------------------------------------------------------------------------- 1 | 2 | nc unix_listen .test-sock 3 | 4 | mda |= ./mda-lmtp --addr=.test-sock --addr_network=unix \ 5 | -f fröm -d þo < test-email 6 | 7 | nc -> 220 Hola desde expect 8 | 9 | nc <~ LHLO .* 10 | nc -> 250-Bienvenido! 11 | nc -> 250 Contame... 12 | 13 | nc <- MAIL FROM: 14 | nc -> 250 Aja 15 | 16 | nc <- RCPT TO:<þo> 17 | nc -> 250 Aja 18 | 19 | nc <- DATA 20 | nc -> 354 Dale 21 | 22 | nc <- Subject: test 23 | nc <- 24 | nc <- This is a test. 25 | nc <- . 26 | 27 | nc -> 250 Recibido 28 | 29 | nc <- QUIT 30 | nc -> 221 Chauchas 31 | 32 | mda wait 0 33 | 34 | -------------------------------------------------------------------------------- /dnsoverride.go: -------------------------------------------------------------------------------- 1 | // Support for overriding DNS lookups, for testing purposes. 2 | // This is only used in tests, when the "dnsoverride" tag is active. 3 | // It requires Go >= 1.8. 4 | // 5 | //go:build dnsoverride 6 | // +build dnsoverride 7 | 8 | package main 9 | 10 | import ( 11 | "context" 12 | "flag" 13 | "net" 14 | "time" 15 | ) 16 | 17 | var ( 18 | dnsAddr = flag.String("testing__dns_addr", "127.0.0.1:9053", 19 | "DNS server address to use, for testing purposes only") 20 | ) 21 | 22 | var dialer = &net.Dialer{ 23 | // We're going to talk to localhost, so have a short timeout so we fail 24 | // fast. Otherwise the callers might hang indefinitely when trying to 25 | // dial the DNS server. 26 | Timeout: 2 * time.Second, 27 | } 28 | 29 | func dial(ctx context.Context, network, address string) (net.Conn, error) { 30 | return dialer.DialContext(ctx, network, *dnsAddr) 31 | } 32 | 33 | func init() { 34 | // Override the resolver to talk with our local server for testing. 35 | net.DefaultResolver.PreferGo = true 36 | net.DefaultResolver.Dial = dial 37 | } 38 | -------------------------------------------------------------------------------- /docker/add-user.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Creates a user. If it exists, updates the password. 4 | # 5 | # Note this is not robust, it's only for convenience on extremely simple 6 | # setups. 7 | 8 | set -e 9 | 10 | if test -z "${EMAIL:-}"; then 11 | read -r -p "Email (full user@domain format): " EMAIL 12 | fi 13 | 14 | if ! echo "${EMAIL}" | grep -q @; then 15 | echo "Error: email should have '@'." 16 | exit 1 17 | fi 18 | 19 | if test -z "${PASSWORD:-}"; then 20 | read -r -p "Password: " -s PASSWORD 21 | echo 22 | fi 23 | 24 | DOMAIN=$(echo echo "${EMAIL}" | cut -d '@' -f 2) 25 | 26 | 27 | # If the domain doesn't exist in chasquid's config, create it. 28 | mkdir -p "/data/chasquid/domains/${DOMAIN}/" 29 | 30 | 31 | # Encrypt password. 32 | ENCPASS=$(doveadm pw -u "${EMAIL}" -p "${PASSWORD}") 33 | 34 | # Edit dovecot users: remove user if it exits. 35 | mkdir -p /data/dovecot 36 | touch /data/dovecot/users 37 | sed --in-place=.old "/^${EMAIL}:/d" /data/dovecot/users 38 | 39 | # Edit dovecot users: add user. 40 | echo "${EMAIL}:${ENCPASS}::::" >> /data/dovecot/users 41 | 42 | echo "${EMAIL} added to /data/dovecot/users" 43 | 44 | -------------------------------------------------------------------------------- /docker/chasquid.conf: -------------------------------------------------------------------------------- 1 | 2 | # Listening addresses. 3 | smtp_address: ":25" 4 | submission_address: ":587" 5 | submission_over_tls_address: ":465" 6 | 7 | # Monitoring HTTP server only bound to localhost, just in case. 8 | monitoring_address: "127.0.0.1:1099" 9 | 10 | # Auth against dovecot. 11 | dovecot_auth: true 12 | 13 | # Use mda-lmtp to talk to dovecot. 14 | mail_delivery_agent_bin: "/usr/bin/mda-lmtp" 15 | mail_delivery_agent_args: "--addr" 16 | mail_delivery_agent_args: "/run/dovecot/lmtp" 17 | mail_delivery_agent_args: "-f" 18 | mail_delivery_agent_args: "%from%" 19 | mail_delivery_agent_args: "-d" 20 | mail_delivery_agent_args: "%to%" 21 | 22 | # Store data in the container volume. 23 | data_dir: "/data/chasquid/data" 24 | 25 | # Mail log to the container volume. 26 | mail_log_path: "/data/chasquid/mail.log" 27 | -------------------------------------------------------------------------------- /docs/clients.md: -------------------------------------------------------------------------------- 1 | 2 | # Clients 3 | 4 | chasquid supports most SMTP clients, but requires them to have some features: 5 | 6 | - Support TLS (either 7 | [STARTTLS](https://datatracker.ietf.org/doc/html/rfc3207) or 8 | [implicit TLS](https://datatracker.ietf.org/doc/html/rfc8314#section-3.3)) 9 | - Support the 10 | [PLAIN authentication method](https://datatracker.ietf.org/doc/html/rfc4954#section-4). 11 | 12 | All modern clients should support both, and thus have no problems talking to 13 | chasquid. 14 | 15 | 16 | ## Configuration examples 17 | 18 | ### [msmtp](https://marlam.de/msmtp/) 19 | 20 | This example is useful as either per-user `~/.msmtprc` or system-wide 21 | `/etc/msmtprc`: 22 | 23 | ``` 24 | account default 25 | tls on 26 | auth on 27 | 28 | # Use the SMTP submission port. Many providers block communications to the 29 | # default port 25, but the submission port 587 tends to work just fine. 30 | port 587 31 | 32 | # Server hostname. 33 | host SERVER 34 | 35 | # Your username (including the domain). 36 | user USER@DOMAIN 37 | 38 | # Your password. 39 | password SECRET 40 | ``` 41 | 42 | Replace the `SERVER`, `USER@DOMAIN` and `SECRET` strings with the appropriate 43 | values. 44 | 45 | 46 | ## Problematic clients 47 | 48 | These clients are known to have issues talking to chasquid: 49 | 50 | - [ssmtp](https://packages.debian.org/source/unstable/ssmtp): does not 51 | support the PLAIN authentication method. It is also unmaintained. 52 | Please use [msmtp](https://marlam.de/msmtp/) instead. 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/docker.md: -------------------------------------------------------------------------------- 1 | ../docker/README.md -------------------------------------------------------------------------------- /docs/haproxy.md: -------------------------------------------------------------------------------- 1 | 2 | # HAProxy integration 3 | 4 | As of version 1.6, [chasquid] supports being deployed behind a [HAProxy] 5 | instance. 6 | 7 | **This is EXPERIMENTAL for now, and can change in backwards-incompatible 8 | ways.** 9 | 10 | 11 | ## Configuring HAProxy 12 | 13 | In the backend server line, set the [send-proxy] parameter to turn on the use 14 | of the PROXY protocol against chasquid. 15 | 16 | You need to set this for each of the ports that are forwarded. 17 | 18 | Only PROXY protocol v1 is supported for now. 19 | 20 | 21 | ## Configuring chasquid 22 | 23 | Add the following line to `/etc/chasquid/chasquid.conf`: 24 | 25 | ``` 26 | haproxy_incoming: true 27 | ``` 28 | 29 | That turns HAProxy support on for all incoming SMTP connections. 30 | 31 | 32 | [chasquid]: https://blitiri.com.ar/p/chasquid 33 | [HAProxy]: https://www.haproxy.org/ 34 | [send-proxy]: http://cbonte.github.io/haproxy-dconv/2.0/configuration.html#5.2-send-proxy 35 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | # chasquid 3 | 4 | [chasquid](https://blitiri.com.ar/p/chasquid) is an SMTP (email) server with a 5 | focus on simplicity, security, and ease of operation. 6 | 7 | This is the documentation index. 8 | 9 | Please see the [project page](https://blitiri.com.ar/p/chasquid) for more 10 | details. 11 | 12 | 13 | ## Contact 14 | 15 | If you have any questions, comments or patches please send them to the [mailing 16 | list](https://groups.google.com/forum/#!forum/chasquid), 17 | chasquid@googlegroups.com. 18 | 19 | To subscribe, send an email to chasquid+subscribe@googlegroups.com. 20 | 21 | You can also reach out via IRC, `#chasquid` on 22 | [OFTC](https://oftc.net/). 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/knownissues.md: -------------------------------------------------------------------------------- 1 | 2 | # Known issues 3 | 4 | This file contains a list of the most common known issues, and the release 5 | range affected. It can be useful for people running older versions, to 6 | identify problems (and workarounds) faster. 7 | 8 | Entries are eventually be purged once their affected versions become uncommon, 9 | to prevent confusion. 10 | 11 | 12 | ## Dovecot auth occasionally not functional after a reboot (0.04 to 1.6) 13 | 14 | After a reboot, if chasquid starts *before* dovecot, it's possible that 15 | chasquid fails to autodetect the dovecot addresses, and the dovecot 16 | authentication will not be functional until chasquid is restarted. 17 | 18 | This condition can be identified by seeing 19 | `Dovecot autodetection failed, no dovecot fallback` in the chasquid logs, at 20 | start-up time. 21 | 22 | As a workaround, you can create the following systemd dropin file at 23 | `/etc/systemd/system/chasquid.service.d/after-dovecot.conf`, to make chasquid 24 | be started *after* dovecot: 25 | 26 | ``` 27 | [Unit] 28 | After=dovecot.service 29 | ``` 30 | 31 | The issue is fixed in 1.7. 32 | 33 | 34 | ## `dkimsign` causes parsing errors in post-data hook (0.07 to 1.5) 35 | 36 | The default post-data hook in versions 0.07 to 1.5 has a bug where if the 37 | `dkimsign` command exists, unwanted output will be emitted and cause the 38 | post-data hook invocation to fail. 39 | 40 | The problem can be identified by the following error in the logs: 41 | 42 | ``` 43 | Hook.Post-DATA 1.2.3.4:5678: error: error parsing post-data output: \"/usr/bin/dkimsign\\n... 44 | ``` 45 | 46 | As a workaround, you can edit the hook and make the change 47 | [seen here](https://blitiri.com.ar/git/r/chasquid/c/b6248f3089d7df93035bbbc0c11edf50709d5eb0/). 48 | 49 | The issue is fixed in 1.6. 50 | -------------------------------------------------------------------------------- /docs/man/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Convert pod files to manual pages, using pod2man. 4 | # 5 | # Assumes files are named like: 6 | # .
.pod 7 | 8 | set -e 9 | 10 | for IN in *.pod; do 11 | OUT=$(basename "$IN" .pod) 12 | SECTION=${OUT##*.} 13 | NAME=${OUT%.*} 14 | 15 | # If it has not changed in git, set the mtime to the last commit that 16 | # touched the file. 17 | CHANGED=$( git status --porcelain -- "$IN" | wc -l ) 18 | if [ "$CHANGED" -eq 0 ]; then 19 | GIT_MTIME=$( git log --pretty=%at -n1 -- "$IN" ) 20 | touch -d "@$GIT_MTIME" "$IN" 21 | fi 22 | 23 | podchecker "$IN" 24 | pod2man --section="$SECTION" --name="$NAME" \ 25 | --release "" --center "" \ 26 | "$IN" "$OUT" 27 | pod2markdown "$IN" \ 28 | | sed 's@\([a-z.-]\+\)(\([1-9]\))@[\1(\2)](\1.\2.md)@g' \ 29 | > "$OUT.md" 30 | done 31 | -------------------------------------------------------------------------------- /docs/man/index.md: -------------------------------------------------------------------------------- 1 | # Manual pages 2 | 3 | From the latest Debian package: 4 | 5 | - [chasquid(1)](https://manpages.debian.org/unstable/chasquid/chasquid.1): 6 | the main binary. 7 | - [chasquid.conf(5)](https://manpages.debian.org/unstable/chasquid/chasquid.conf.5): 8 | the configuration file and structure. 9 | - [chasquid-util(1)](https://manpages.debian.org/unstable/chasquid/chasquid-util.1): 10 | the command-line utility helper. 11 | - [mda-lmtp(1)](https://manpages.debian.org/unstable/chasquid/mda-lmtp.1): 12 | helper to integrate with LMTP delivery. 13 | - [smtp-check(1)](https://manpages.debian.org/unstable/chasquid/smtp-check.1): 14 | SMTP setup checker. 15 | 16 | From the current branch (more likely to change, but useful when doing 17 | development or running experimental versions): 18 | 19 | - [chasquid(1)](chasquid.1.md): the main binary. 20 | - [chasquid.conf(5)](chasquid.conf.5.md): the configuration file and 21 | structure. 22 | - [chasquid-util(1)](chasquid-util.1.md): the command-line utility helper. 23 | - [mda-lmtp(1)](mda-lmtp.1.md): helper to integrate with LMTP delivery. 24 | - [smtp-check(1)](smtp-check.1.md): SMTP setup checker. 25 | 26 | -------------------------------------------------------------------------------- /docs/man/mda-lmtp.1.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | mda-lmtp - MDA that uses LMTP to do the mail delivery 4 | 5 | # SYNOPSIS 6 | 7 | mda-lmtp 8 | \[**-addr\_network** _net_\] 9 | **-addr** _addr_ 10 | **-d** _recipient_ 11 | **-f** _from_ 12 | 13 | # DESCRIPTION 14 | 15 | mda-lmtp is a very basic MDA that uses LMTP to do the mail delivery. 16 | 17 | It takes command line arguments similar to maildrop or procmail, reads an 18 | email via standard input, and sends it over the given LMTP server. Supports 19 | connecting to LMTP servers over UNIX sockets and TCP. 20 | 21 | It can be used when your mail server does not support LMTP directly. 22 | 23 | # EXAMPLE 24 | 25 | **mda-lmtp** _--addr=localhost:1234_ _-f juan@casa_ _-d jose_ < email 26 | 27 | # OPTIONS 28 | 29 | - **-addr** _address_ 30 | 31 | LMTP server address to connect to. 32 | 33 | - **-addr\_network** _network_ 34 | 35 | Network of the LMTP address (e.g. _unix_ or _tcp_). If not present, it will 36 | be autodetected from the address itself. 37 | 38 | - **-d** _recipient_ 39 | 40 | Recipient. 41 | 42 | - **-f** _from_ 43 | 44 | Whom the message is from. 45 | 46 | # RETURN VALUES 47 | 48 | - **0** 49 | 50 | success 51 | 52 | - **75** 53 | 54 | temporary failure 55 | 56 | - _other_ 57 | 58 | permanent failures (usually indicate misconfiguration) 59 | 60 | # SEE ALSO 61 | 62 | [chasquid(1)](chasquid.1.md) 63 | -------------------------------------------------------------------------------- /docs/man/mda-lmtp.1.pod: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | mda-lmtp - MDA that uses LMTP to do the mail delivery 5 | 6 | =head1 SYNOPSIS 7 | 8 | mda-lmtp 9 | [B<-addr_network> I] 10 | B<-addr> I 11 | B<-d> I 12 | B<-f> I 13 | 14 | =head1 DESCRIPTION 15 | 16 | mda-lmtp is a very basic MDA that uses LMTP to do the mail delivery. 17 | 18 | It takes command line arguments similar to maildrop or procmail, reads an 19 | email via standard input, and sends it over the given LMTP server. Supports 20 | connecting to LMTP servers over UNIX sockets and TCP. 21 | 22 | It can be used when your mail server does not support LMTP directly. 23 | 24 | =head1 EXAMPLE 25 | 26 | B I<--addr=localhost:1234> I<-f juan@casa> I<-d jose> < email 27 | 28 | 29 | =head1 OPTIONS 30 | 31 | =over 8 32 | 33 | =item B<-addr> I
34 | 35 | LMTP server address to connect to. 36 | 37 | =item B<-addr_network> I 38 | 39 | Network of the LMTP address (e.g. I or I). If not present, it will 40 | be autodetected from the address itself. 41 | 42 | =item B<-d> I 43 | 44 | Recipient. 45 | 46 | =item B<-f> I 47 | 48 | Whom the message is from. 49 | 50 | =back 51 | 52 | 53 | =head1 RETURN VALUES 54 | 55 | =over 8 56 | 57 | =item B<0> 58 | 59 | success 60 | 61 | =item B<75> 62 | 63 | temporary failure 64 | 65 | =item I 66 | 67 | permanent failures (usually indicate misconfiguration) 68 | 69 | =back 70 | 71 | =head1 SEE ALSO 72 | 73 | chasquid(1) 74 | -------------------------------------------------------------------------------- /docs/man/smtp-check.1.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | smtp-check - SMTP setup checker 4 | 5 | # SYNOPSIS 6 | 7 | **smtp-check** \[-port _port_\] \[-localname _domain_\] \[-skip\_tls\_check\] _domain_ 8 | 9 | # DESCRIPTION 10 | 11 | smtp-check is a command-line too for checking SMTP setups (DNS records, TLS 12 | certificates, SPF, etc.). 13 | 14 | # OPTIONS 15 | 16 | - **-port** _port_: 17 | 18 | Port to use for connecting to the MX servers. 19 | 20 | - **-localname** _domain_: 21 | 22 | Local name to use for the EHLO command. 23 | 24 | - **-skip\_tls\_check**: 25 | 26 | Skip TLS check (useful if connections are blocked). 27 | 28 | # SEE ALSO 29 | 30 | [chasquid(1)](chasquid.1.md) 31 | -------------------------------------------------------------------------------- /docs/man/smtp-check.1.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | smtp-check - SMTP setup checker 4 | 5 | =head1 SYNOPSIS 6 | 7 | B [-port I] [-localname I] [-skip_tls_check] I 8 | 9 | =head1 DESCRIPTION 10 | 11 | smtp-check is a command-line too for checking SMTP setups (DNS records, TLS 12 | certificates, SPF, etc.). 13 | 14 | =head1 OPTIONS 15 | 16 | =over 8 17 | 18 | =item B<-port> I: 19 | 20 | Port to use for connecting to the MX servers. 21 | 22 | =item B<-localname> I: 23 | 24 | Local name to use for the EHLO command. 25 | 26 | =item B<-skip_tls_check>: 27 | 28 | Skip TLS check (useful if connections are blocked). 29 | 30 | =back 31 | 32 | =head1 SEE ALSO 33 | 34 | chasquid(1) 35 | 36 | -------------------------------------------------------------------------------- /docs/security.md: -------------------------------------------------------------------------------- 1 | # Reporting a security issue 2 | 3 | To privately report a suspected security issue, you can email 4 | albertito@blitiri.com.ar, or do it via [GitHub's Security tab]. 5 | 6 | Thank you! 7 | 8 | 9 | [GitHub's Security tab]: https://github.com/albertito/chasquid/security 10 | -------------------------------------------------------------------------------- /docs/tests.md: -------------------------------------------------------------------------------- 1 | ../test/README.md -------------------------------------------------------------------------------- /etc/chasquid/README: -------------------------------------------------------------------------------- 1 | 2 | This directory contains chasquid's configuration. 3 | 4 | - chasquid.conf Main config file. 5 | 6 | - domains/ Domains' data. 7 | - example.com/ 8 | - users User and password database for the domain. 9 | - aliases Aliases for the domain. 10 | ... 11 | 12 | - certs/ Certificates to use, one dir per pair. 13 | - example.com/ 14 | - fullchain.pem Certificate (full chain). 15 | - privkey.pem Private key. 16 | ... 17 | 18 | 19 | Note the certs/ directory matches certbot's structure, so if you use it you 20 | can just symlink to /etc/letsencrypt/live. 21 | 22 | You need at least one certificate, or the server will refuse to start. 23 | Ideally there should be a certificate for each DNS name pointing to you. 24 | 25 | Make sure the user you use to run chasquid under ("mail" in the example 26 | systemd files) can access the certificates and private keys. 27 | 28 | 29 | The user databases can be created and edited with the chasquid-util tool. 30 | 31 | -------------------------------------------------------------------------------- /etc/chasquid/certs: -------------------------------------------------------------------------------- 1 | /etc/letsencrypt/live/ -------------------------------------------------------------------------------- /etc/chasquid/domains/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/etc/chasquid/domains/.gitignore -------------------------------------------------------------------------------- /etc/fail2ban/filter.d/chasquid.conf: -------------------------------------------------------------------------------- 1 | # fail2ban filter config for chasquid SMTP server. 2 | [INCLUDES] 3 | before = common.conf 4 | 5 | [Definition] 6 | # Identify addresses that triggered an SMTP connection error. 7 | failregex = SMTP.Conn :\d+: error: 8 | 9 | [Init] 10 | journalmatch = _SYSTEMD_UNIT=chasquid.service 11 | 12 | -------------------------------------------------------------------------------- /etc/systemd/system/chasquid.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=chasquid mail daemon (service) 3 | 4 | [Service] 5 | ExecStart=/usr/local/bin/chasquid \ 6 | 7 | # -v=3 \ 8 | # --log_dir=/var/log/chasquid/ \ 9 | # --alsologtostderr \ 10 | 11 | Type=simple 12 | Restart=always 13 | 14 | User=mail 15 | Group=mail 16 | 17 | # Let chasquid listen on ports < 1024. 18 | AmbientCapabilities=CAP_NET_BIND_SERVICE 19 | 20 | # Simple security measures just in case. 21 | ProtectSystem=full 22 | 23 | [Install] 24 | WantedBy=multi-user.target 25 | 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module blitiri.com.ar/go/chasquid 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | blitiri.com.ar/go/log v1.1.0 7 | blitiri.com.ar/go/spf v1.5.1 8 | blitiri.com.ar/go/systemd v1.1.0 9 | github.com/google/go-cmp v0.6.0 10 | golang.org/x/crypto v0.36.0 11 | golang.org/x/net v0.38.0 12 | golang.org/x/term v0.30.0 13 | golang.org/x/text v0.23.0 14 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d 15 | google.golang.org/protobuf v1.36.6 16 | ) 17 | 18 | require golang.org/x/sys v0.31.0 // indirect 19 | -------------------------------------------------------------------------------- /internal/aliases/testdata/empty-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Hook that doesn't return any output. 4 | exit 0 5 | -------------------------------------------------------------------------------- /internal/aliases/testdata/erroring-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Hook that always returns error. 4 | # This could be replaced by /bin/false, but that doesn't work on freebsd. 5 | exit 1 6 | -------------------------------------------------------------------------------- /internal/aliases/testdata/fuzz/FuzzReader/1c24d2215db69748c6fd16797673ad11ebc7e6167fe1bc1f54c6959ec10407b6: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("# First some valid cases.\na: b\nc: d@e, f,\nx: | command\n\n# The following is invalid, should be ignored.\na@dom: x@dom\n\n# Overrides.\no1: a\no1: b\n\n# Finally one to make the file NOT end in \\n:\ny: z\n") -------------------------------------------------------------------------------- /internal/aliases/testdata/fuzz/FuzzReader/4adaceaa32e2b32c00322948769d62c2dd42e1d9f4950d3c5b411c710e6d4a86: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\nfail: | false\n\n") -------------------------------------------------------------------------------- /internal/aliases/testdata/fuzz/FuzzReader/8234d8c5719f30e50525290db70743bf97d940e60591cf4a638c72158d35504a: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\naliasA: aliasB@srv-B\n") -------------------------------------------------------------------------------- /internal/aliases/testdata/fuzz/FuzzReader/c9c80ba9f513841cb081fe9bb7439d36f9f7a06bb999d4c39441991ccc878a9e: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\n# Easy aliases.\npepe: jose\njoan: juan\n\n# UTF-8 aliases.\npitanga: ñangapirí\nañil: azul, índigo\n\n# Pipe aliases.\ntubo: | writemailto ../.data/pipe_alias_worked\n\n") -------------------------------------------------------------------------------- /internal/aliases/testdata/fuzz/FuzzReader/d40a98862ed393eb712e47a91bcef18e6f24cf368bb4bd248c7a7101ef8e178d: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("") -------------------------------------------------------------------------------- /internal/aliases/testdata/invalid-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is an invalid right-side line: the pipe is missing a command. 4 | echo "|" 5 | -------------------------------------------------------------------------------- /internal/aliases/testdata/normal-hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "p@q, x@y" 4 | -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/0274b170c6fe2654ca5418a914b804e9c7cc5d8e5c2a7c5fcf5c29540ec5ae52: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("@\xed\x88̥̥̥̥̥̥̄̈́̈́̈́̈́̈́̈́̈́̈́ͥ̈́̈́ͥ̓\x00\x00") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/24d35771ef1fe0645d90b061e13a777faea328736483ec2833b63950d26b5399: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("AHVAZABwYXNz") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/2e5d0b26626f2d2dd6fb423e1e1cc432277ae9877c622fe6ca067e247bc11c9d: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("dUBkAHVAZABwYXNz") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/2ef1aee5347414c139270ebb6ea63d2223a8c0c7c8ec30a2ca7152f4c18f1c74: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("\xeb̥̥̥̥̥̥̥̈́ͥ̈́̈́̈́̈́̈́̈́̈́̈́̈́ͥ̓@\x00\x00") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/4b9259040da90f06aa2b593ee20fdffefeda813c59430050f15965bd9471235e: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("this is not base64 encoded") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/6c2c0b4f81a675d91d1291bfdcddb7c9d43cf6264dd7763cfed31a3946854e27: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("\xeb̥̥̥̥̥̥ͯ̈́̈́̈́̈́̈́ͯ̈́̈́̈́̈́̈́̈́@\x00\x00") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/6e05782952b68c7ccd94160ad6ea45e7f766397850b08e78f89407a94350825c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("\xeb̥̥̥̥̥̥̈́̈́̈́̈́̈́ͯ̈́̈́̈́̈́̈́̈́̈́@\x00\x00") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/c2ae184876dd0fe9acfc8a5e2f2174a968b889b01e0f5c9a61fa27d7361f0091: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("dUBkAABwYXNz") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/d9aa9c617d1f5b3021aca758b9d896d136e3b16ed53233d02abffd02aa73ffa4: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("w7FhY2FAw7FlcXVlAABjbGF2YXLDqQ==") -------------------------------------------------------------------------------- /internal/auth/testdata/fuzz/FuzzDecodeResponse/de05c7993312bab83e8114e9d9ced331c49822dc55c1a353f1cc9718a28226e7: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("dUBkAABwYXNz/w==") -------------------------------------------------------------------------------- /internal/courier/courier.go: -------------------------------------------------------------------------------- 1 | // Package courier implements various couriers for delivering messages. 2 | package courier 3 | 4 | // Courier delivers mail to a single recipient. 5 | // It is implemented by different couriers, for both local and remote 6 | // recipients. 7 | type Courier interface { 8 | // Deliver mail to a recipient. Return the error (if any), and whether it 9 | // is permanent (true) or transient (false). 10 | Deliver(from string, to string, data []byte) (error, bool) 11 | 12 | // Forward mail using the given servers. 13 | // Return the error (if any), and whether it is permanent (true) or 14 | // transient (false). 15 | Forward(from string, to string, data []byte, servers []string) (error, bool) 16 | } 17 | -------------------------------------------------------------------------------- /internal/dkim/context.go: -------------------------------------------------------------------------------- 1 | package dkim 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | type contextKey string 9 | 10 | const traceKey contextKey = "trace" 11 | 12 | func trace(ctx context.Context, f string, args ...interface{}) { 13 | traceFunc, ok := ctx.Value(traceKey).(TraceFunc) 14 | if !ok { 15 | return 16 | } 17 | traceFunc(f, args...) 18 | } 19 | 20 | type TraceFunc func(f string, a ...interface{}) 21 | 22 | func WithTraceFunc(ctx context.Context, trace TraceFunc) context.Context { 23 | return context.WithValue(ctx, traceKey, trace) 24 | } 25 | 26 | const lookupTXTKey contextKey = "lookupTXT" 27 | 28 | func lookupTXT(ctx context.Context, domain string) ([]string, error) { 29 | lookupTXTFunc, ok := ctx.Value(lookupTXTKey).(lookupTXTFunc) 30 | if !ok { 31 | return net.LookupTXT(domain) 32 | } 33 | return lookupTXTFunc(ctx, domain) 34 | } 35 | 36 | type lookupTXTFunc func(ctx context.Context, domain string) ([]string, error) 37 | 38 | func WithLookupTXTFunc(ctx context.Context, lookupTXT lookupTXTFunc) context.Context { 39 | return context.WithValue(ctx, lookupTXTKey, lookupTXT) 40 | } 41 | 42 | const maxHeadersKey contextKey = "maxHeaders" 43 | 44 | func WithMaxHeaders(ctx context.Context, maxHeaders int) context.Context { 45 | return context.WithValue(ctx, maxHeadersKey, maxHeaders) 46 | } 47 | 48 | func maxHeaders(ctx context.Context) int { 49 | maxHeaders, ok := ctx.Value(maxHeadersKey).(int) 50 | if !ok { 51 | // By default, cap the number of headers to 5 (arbitrarily chosen, may 52 | // be adjusted in the future). 53 | return 5 54 | } 55 | return maxHeaders 56 | } 57 | -------------------------------------------------------------------------------- /internal/dkim/context_test.go: -------------------------------------------------------------------------------- 1 | package dkim 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "testing" 8 | ) 9 | 10 | func TestTraceNoCtx(t *testing.T) { 11 | // Call trace() on a context without a trace function, to check it doesn't 12 | // panic. 13 | ctx := context.Background() 14 | trace(ctx, "test") 15 | } 16 | 17 | func TestTrace(t *testing.T) { 18 | s := "" 19 | traceF := func(f string, a ...interface{}) { 20 | s = fmt.Sprintf(f, a...) 21 | } 22 | ctx := WithTraceFunc(context.Background(), traceF) 23 | trace(ctx, "test %d", 1) 24 | if s != "test 1" { 25 | t.Errorf("trace function not called") 26 | } 27 | } 28 | 29 | func TestLookupTXTNoCtx(t *testing.T) { 30 | // Call lookupTXT() on a context without an override, to check it calls 31 | // the real function. 32 | // We just check there is a reasonable error. 33 | // We don't specifically check that it's NXDOMAIN because if we don't have 34 | // internet access, the error may be different. 35 | ctx := context.Background() 36 | _, err := lookupTXT(ctx, "does.not.exist.example.com") 37 | if _, ok := err.(*net.DNSError); !ok { 38 | t.Fatalf("expected *net.DNSError, got %T", err) 39 | } 40 | } 41 | 42 | func TestLookupTXT(t *testing.T) { 43 | called := false 44 | lookupTXTF := func(ctx context.Context, name string) ([]string, error) { 45 | called = true 46 | return nil, nil 47 | } 48 | ctx := WithLookupTXTFunc(context.Background(), lookupTXTF) 49 | lookupTXT(ctx, "example.com") 50 | if !called { 51 | t.Errorf("lookupTXT function not called") 52 | } 53 | } 54 | 55 | func TestMaxHeaders(t *testing.T) { 56 | // First without an override, check we return the default. 57 | ctx := context.Background() 58 | if m := maxHeaders(ctx); m != 5 { 59 | t.Errorf("expected 5, got %d", m) 60 | } 61 | 62 | // Now with an override. 63 | ctx = WithMaxHeaders(ctx, 10) 64 | if m := maxHeaders(ctx); m != 10 { 65 | t.Errorf("expected 10, got %d", m) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/dkim/testdata/.gitignore: -------------------------------------------------------------------------------- 1 | *.got 2 | 3 | # Ignore private test cases, to reduce the chances of accidental leaks. 4 | private 5 | -------------------------------------------------------------------------------- /internal/dkim/testdata/01-rfc8463.dns: -------------------------------------------------------------------------------- 1 | brisbane._domainkey.football.example.com: \ 2 | v=DKIM1; k=ed25519; \ 3 | p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo= 4 | 5 | test._domainkey.football.example.com: \ 6 | v=DKIM1; k=rsa; \ 7 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 8 | 9 | -------------------------------------------------------------------------------- /internal/dkim/testdata/01-rfc8463.error: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/dkim/testdata/01-rfc8463.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=football.example.com; i=@football.example.com; 3 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 4 | subject : date : message-id : from : subject : date; 5 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 6 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 7 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 8 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 9 | d=football.example.com; i=@football.example.com; 10 | q=dns/txt; s=test; t=1528637909; h=from : to : subject : 11 | date : message-id : from : subject : date; 12 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 13 | b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 14 | DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz 15 | dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= 16 | From: Joe SixPack 17 | To: Suzie Q 18 | Subject: Is dinner ready? 19 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 20 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 21 | 22 | Hi. 23 | 24 | We lost the game. Are you hungry yet? 25 | 26 | Joe. 27 | 28 | -------------------------------------------------------------------------------- /internal/dkim/testdata/01-rfc8463.result: -------------------------------------------------------------------------------- 1 | { 2 | "Found": 2, 3 | "Valid": 2, 4 | "Results": [ 5 | { 6 | "Error": "", 7 | "SignatureHeader": " v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=brisbane; t=1528637909; h=from : to :\r\n subject : date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus\r\n Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 8 | "Domain": "football.example.com", 9 | "Selector": "brisbane", 10 | "B": "/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11BusFa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 11 | "State": "SUCCESS" 12 | }, 13 | { 14 | "Error": "", 15 | "SignatureHeader": " v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=test; t=1528637909; h=from : to : subject :\r\n date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3\r\n DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz\r\n dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 16 | "Domain": "football.example.com", 17 | "Selector": "test", 18 | "B": "F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2JzdA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 19 | "State": "SUCCESS" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /internal/dkim/testdata/02-too_many_headers.dns: -------------------------------------------------------------------------------- 1 | 01-rfc8463.dns -------------------------------------------------------------------------------- /internal/dkim/testdata/02-too_many_headers.error: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/dkim/testdata/02-too_many_headers.readme: -------------------------------------------------------------------------------- 1 | Check that we don't process more than 5 headers. 2 | 3 | The message contains 7 headers, but only the first 5 should be validated (and 4 | appear as valid). 5 | 6 | -------------------------------------------------------------------------------- /internal/dkim/testdata/03-bad_message.error: -------------------------------------------------------------------------------- 1 | invalid header: bad continuation -------------------------------------------------------------------------------- /internal/dkim/testdata/03-bad_message.msg: -------------------------------------------------------------------------------- 1 | This is not a valid message. 2 | -------------------------------------------------------------------------------- /internal/dkim/testdata/04-bad_dkim_signature_header.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=8; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=football.example.com; i=@football.example.com; 3 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 4 | subject : date : message-id : from : subject : date; 5 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 6 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 7 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 8 | From: Joe SixPack 9 | To: Suzie Q 10 | Subject: Is dinner ready? 11 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 12 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 13 | 14 | Hi. 15 | 16 | We lost the game. Are you hungry yet? 17 | 18 | Joe. 19 | 20 | -------------------------------------------------------------------------------- /internal/dkim/testdata/04-bad_dkim_signature_header.readme: -------------------------------------------------------------------------------- 1 | Check that we reject invalid DKIM signature headers. 2 | 3 | In this case, we force this by taking an otherwise valid header, but using v=8 4 | instead of v=1. 5 | -------------------------------------------------------------------------------- /internal/dkim/testdata/04-bad_dkim_signature_header.result: -------------------------------------------------------------------------------- 1 | { 2 | "Found": 1, 3 | "Valid": 0, 4 | "Results": [ 5 | { 6 | "Error": "invalid version", 7 | "SignatureHeader": " v=8; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=brisbane; t=1528637909; h=from : to :\r\n subject : date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus\r\n Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 8 | "Domain": "", 9 | "Selector": "", 10 | "B": "", 11 | "State": "PERMFAIL" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /internal/dkim/testdata/05-dns_temp_error.dns: -------------------------------------------------------------------------------- 1 | brisbane._domainkey.football.example.com: TEMPERROR 2 | 3 | test._domainkey.football.example.com: \ 4 | v=DKIM1; k=rsa; \ 5 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 6 | 7 | -------------------------------------------------------------------------------- /internal/dkim/testdata/05-dns_temp_error.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=football.example.com; i=@football.example.com; 3 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 4 | subject : date : message-id : from : subject : date; 5 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 6 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 7 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 8 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 9 | d=football.example.com; i=@football.example.com; 10 | q=dns/txt; s=test; t=1528637909; h=from : to : subject : 11 | date : message-id : from : subject : date; 12 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 13 | b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 14 | DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz 15 | dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= 16 | From: Joe SixPack 17 | To: Suzie Q 18 | Subject: Is dinner ready? 19 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 20 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 21 | 22 | Hi. 23 | 24 | We lost the game. Are you hungry yet? 25 | 26 | Joe. 27 | 28 | -------------------------------------------------------------------------------- /internal/dkim/testdata/05-dns_temp_error.result: -------------------------------------------------------------------------------- 1 | { 2 | "Found": 2, 3 | "Valid": 1, 4 | "Results": [ 5 | { 6 | "Error": "lookup : temporary error (for testing)", 7 | "SignatureHeader": " v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=brisbane; t=1528637909; h=from : to :\r\n subject : date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus\r\n Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 8 | "Domain": "football.example.com", 9 | "Selector": "brisbane", 10 | "B": "/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11BusFa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 11 | "State": "TEMPFAIL" 12 | }, 13 | { 14 | "Error": "", 15 | "SignatureHeader": " v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=test; t=1528637909; h=from : to : subject :\r\n date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3\r\n DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz\r\n dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 16 | "Domain": "football.example.com", 17 | "Selector": "test", 18 | "B": "F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2JzdA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 19 | "State": "SUCCESS" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /internal/dkim/testdata/06-dns_perm_error.dns: -------------------------------------------------------------------------------- 1 | brisbane._domainkey.football.example.com: PERMERROR 2 | 3 | test._domainkey.football.example.com: \ 4 | v=DKIM1; k=rsa; \ 5 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 6 | 7 | -------------------------------------------------------------------------------- /internal/dkim/testdata/06-dns_perm_error.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=football.example.com; i=@football.example.com; 3 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 4 | subject : date : message-id : from : subject : date; 5 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 6 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 7 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 8 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 9 | d=football.example.com; i=@football.example.com; 10 | q=dns/txt; s=test; t=1528637909; h=from : to : subject : 11 | date : message-id : from : subject : date; 12 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 13 | b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 14 | DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz 15 | dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= 16 | From: Joe SixPack 17 | To: Suzie Q 18 | Subject: Is dinner ready? 19 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 20 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 21 | 22 | Hi. 23 | 24 | We lost the game. Are you hungry yet? 25 | 26 | Joe. 27 | 28 | -------------------------------------------------------------------------------- /internal/dkim/testdata/06-dns_perm_error.result: -------------------------------------------------------------------------------- 1 | { 2 | "Found": 2, 3 | "Valid": 1, 4 | "Results": [ 5 | { 6 | "Error": "lookup : permanent error (for testing)", 7 | "SignatureHeader": " v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=brisbane; t=1528637909; h=from : to :\r\n subject : date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus\r\n Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 8 | "Domain": "football.example.com", 9 | "Selector": "brisbane", 10 | "B": "/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11BusFa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 11 | "State": "PERMFAIL" 12 | }, 13 | { 14 | "Error": "", 15 | "SignatureHeader": " v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=test; t=1528637909; h=from : to : subject :\r\n date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3\r\n DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz\r\n dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 16 | "Domain": "football.example.com", 17 | "Selector": "test", 18 | "B": "F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2JzdA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 19 | "State": "SUCCESS" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /internal/dkim/testdata/07-algo_mismatch.dns: -------------------------------------------------------------------------------- 1 | brisbane._domainkey.football.example.com: \ 2 | v=DKIM1; k=rsa; \ 3 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 4 | 5 | brisbane._domainkey.football.example.com: \ 6 | v=DKIM1; k=ed25519; \ 7 | p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo= 8 | 9 | test._domainkey.football.example.com: \ 10 | v=DKIM1; k=rsa; \ 11 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 12 | 13 | -------------------------------------------------------------------------------- /internal/dkim/testdata/07-algo_mismatch.msg: -------------------------------------------------------------------------------- 1 | 01-rfc8463.msg -------------------------------------------------------------------------------- /internal/dkim/testdata/07-algo_mismatch.readme: -------------------------------------------------------------------------------- 1 | In this test, one of the selectors has two valid TXT records with different 2 | key types. 3 | 4 | Only one of them is valid. 5 | -------------------------------------------------------------------------------- /internal/dkim/testdata/07-algo_mismatch.result: -------------------------------------------------------------------------------- 1 | 01-rfc8463.result -------------------------------------------------------------------------------- /internal/dkim/testdata/08-our_signature.dns: -------------------------------------------------------------------------------- 1 | selector._domainkey.example.com: \ 2 | v=DKIM1; k=ed25519; p=SvoPT692bVrQBT8UNxt6SF538O3snA4fE3/i/glCxwQ= 3 | 4 | brisbane._domainkey.football.example.com: \ 5 | v=DKIM1; k=ed25519; \ 6 | p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo= 7 | 8 | test._domainkey.football.example.com: \ 9 | v=DKIM1; k=rsa; \ 10 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 11 | 12 | -------------------------------------------------------------------------------- /internal/dkim/testdata/08-our_signature.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=example.com; s=selector; t=1709341950; 3 | h=from:subject:date:to:message-id:from:subject:date:to:cc:in-reply-to:message-id; 4 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 5 | b=Vut85AtCKBtJOWSgGA8uyVCLttKitiUcKI3xD+45B2HQi2uc4fWcPbSGW6djkcgJhu0zRexvE/YvnVkIDVoOAg==; 6 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 7 | d=football.example.com; i=@football.example.com; 8 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 9 | subject : date : message-id : from : subject : date; 10 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 11 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 12 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 13 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 14 | d=football.example.com; i=@football.example.com; 15 | q=dns/txt; s=test; t=1528637909; h=from : to : subject : 16 | date : message-id : from : subject : date; 17 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 18 | b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 19 | DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz 20 | dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= 21 | From: Joe SixPack 22 | To: Suzie Q 23 | Subject: Is dinner ready? 24 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 25 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 26 | 27 | Hi. 28 | 29 | We lost the game. Are you hungry yet? 30 | 31 | Joe. 32 | 33 | -------------------------------------------------------------------------------- /internal/dkim/testdata/09-limited_body.dns: -------------------------------------------------------------------------------- 1 | 08-our_signature.dns -------------------------------------------------------------------------------- /internal/dkim/testdata/09-limited_body.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=example.com; s=selector; t=1709368031; 3 | h=from:subject:date:to:message-id:from:subject:date:to:cc:in-reply-to:message-id; 4 | l=17; bh=2Lb+x7ZAi8ljletRVg9Cn+VSkE36HadUTTOwsYyzZJg=; 5 | b=2wsAeUZad5CdbyqNEuUswkD/PJb+trZ8ICldEFX/FpmfdVOtAsCR0flp0EhT7GUTY9b6Q2JvkBICSyvYyojnBQ==; 6 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 7 | d=football.example.com; i=@football.example.com; 8 | q=dns/txt; s=brisbane; t=1528637909; h=from : to : 9 | subject : date : message-id : from : subject : date; 10 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 11 | b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus 12 | Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw== 13 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 14 | d=football.example.com; i=@football.example.com; 15 | q=dns/txt; s=test; t=1528637909; h=from : to : subject : 16 | date : message-id : from : subject : date; 17 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 18 | b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3 19 | DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz 20 | dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8= 21 | From: Joe SixPack 22 | To: Suzie Q 23 | Subject: Is dinner ready? 24 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 25 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 26 | 27 | Hi. 28 | 29 | We lost the game. Are you hungry yet? 30 | 31 | Joe. 32 | 33 | -------------------------------------------------------------------------------- /internal/dkim/testdata/09-limited_body.readme: -------------------------------------------------------------------------------- 1 | This test a DKIM signature that uses an l= tag. 2 | 3 | It was constructed using an ad-hoc modified version of the signer. 4 | -------------------------------------------------------------------------------- /internal/dkim/testdata/10-strict_domain_check_pass.dns: -------------------------------------------------------------------------------- 1 | brisbane._domainkey.football.example.com: \ 2 | v=DKIM1; k=ed25519; t=s; \ 3 | p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo= 4 | 5 | test._domainkey.football.example.com: \ 6 | v=DKIM1; k=rsa; t=s; \ 7 | p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB 8 | 9 | -------------------------------------------------------------------------------- /internal/dkim/testdata/10-strict_domain_check_pass.msg: -------------------------------------------------------------------------------- 1 | 01-rfc8463.msg -------------------------------------------------------------------------------- /internal/dkim/testdata/10-strict_domain_check_pass.result: -------------------------------------------------------------------------------- 1 | { 2 | "Found": 2, 3 | "Valid": 2, 4 | "Results": [ 5 | { 6 | "Error": "", 7 | "SignatureHeader": " v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=brisbane; t=1528637909; h=from : to :\r\n subject : date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus\r\n Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 8 | "Domain": "football.example.com", 9 | "Selector": "brisbane", 10 | "B": "/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11BusFa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==", 11 | "State": "SUCCESS" 12 | }, 13 | { 14 | "Error": "", 15 | "SignatureHeader": " v=1; a=rsa-sha256; c=relaxed/relaxed;\r\n d=football.example.com; i=@football.example.com;\r\n q=dns/txt; s=test; t=1528637909; h=from : to : subject :\r\n date : message-id : from : subject : date;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3\r\n DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2Jz\r\n dA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 16 | "Domain": "football.example.com", 17 | "Selector": "test", 18 | "B": "F45dVWDfMbQDGHJFlXUNB2HKfbCeLRyhDXgFpEL8GwpsRe0IeIixNTe3DhCVlUrSjV4BwcVcOF6+FF3Zo9Rpo1tFOeS9mPYQTnGdaSGsgeefOsk2JzdA+L10TeYt9BgDfQNZtKdN1WO//KgIqXP7OdEFE4LjFYNcUxZQ4FADY+8=", 19 | "State": "SUCCESS" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /internal/dkim/testdata/11-strict_domain_check_fail.dns: -------------------------------------------------------------------------------- 1 | selector._domainkey.example.com: \ 2 | v=DKIM1; k=ed25519; t=s; p=SvoPT692bVrQBT8UNxt6SF538O3snA4fE3/i/glCxwQ= 3 | -------------------------------------------------------------------------------- /internal/dkim/testdata/11-strict_domain_check_fail.msg: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=example.com; s=selector; t=1709466347; 3 | i=test@sub.example.com; 4 | h=from:subject:date:to:message-id:from:subject:date:to:cc:message-id; 5 | bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; 6 | b=NDV3SShyaF7fXYoOx9GnBQjFIfsr5bTJUtAwRTk2sTq+5wl/r0uTN1zaSfUWuxYnMIMoSq 7 | b/xGMFTFmpSbNeCg==; 8 | From: Joe SixPack 9 | To: Suzie Q 10 | Subject: Is dinner ready? 11 | Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) 12 | Message-ID: <20030712040037.46341.5F8J@football.example.com> 13 | 14 | Hi. 15 | 16 | We lost the game. Are you hungry yet? 17 | 18 | Joe. 19 | 20 | -------------------------------------------------------------------------------- /internal/dkim/testdata/11-strict_domain_check_fail.readme: -------------------------------------------------------------------------------- 1 | Strict domain check is enabled, but fails. 2 | 3 | This test has a DNS key with t=s, but the DKIM signature's i= is different 4 | than d= (but it is a subdomain, which is enforced at parsing time as per RFC). 5 | 6 | It was constructed using an ad-hoc modified version of the signer. 7 | -------------------------------------------------------------------------------- /internal/dkim/testdata/11-strict_domain_check_fail.result: -------------------------------------------------------------------------------- 1 | { 2 | "Found": 1, 3 | "Valid": 0, 4 | "Results": [ 5 | { 6 | "Error": "verification failed", 7 | "SignatureHeader": " v=1; a=ed25519-sha256; c=relaxed/relaxed;\r\n d=example.com; s=selector; t=1709466347;\r\n i=test@sub.example.com;\r\n h=from:subject:date:to:message-id:from:subject:date:to:cc:message-id;\r\n bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;\r\n b=NDV3SShyaF7fXYoOx9GnBQjFIfsr5bTJUtAwRTk2sTq+5wl/r0uTN1zaSfUWuxYnMIMoSq\r\n b/xGMFTFmpSbNeCg==;", 8 | "Domain": "example.com", 9 | "Selector": "selector", 10 | "B": "NDV3SShyaF7fXYoOx9GnBQjFIfsr5bTJUtAwRTk2sTq+5wl/r0uTN1zaSfUWuxYnMIMoSqb/xGMFTFmpSbNeCg==", 11 | "State": "PERMFAIL" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /internal/domaininfo/domaininfo.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package domaininfo; 5 | option go_package = "blitiri.com.ar/go/chasquid/internal/domaininfo"; 6 | 7 | enum SecLevel { 8 | // Does not do TLS. 9 | PLAIN = 0; 10 | 11 | // TLS client connection (no certificate validation). 12 | TLS_CLIENT = 1; 13 | 14 | // TLS, but with invalid certificates. 15 | TLS_INSECURE = 2; 16 | 17 | // TLS, with valid certificates. 18 | TLS_SECURE = 3; 19 | } 20 | 21 | message Domain { 22 | string name = 1; 23 | 24 | // Security level for mail coming from this domain (they send to us). 25 | SecLevel incoming_sec_level = 2; 26 | 27 | // Security level for mail going to this domain (we send to them). 28 | SecLevel outgoing_sec_level = 3; 29 | } 30 | -------------------------------------------------------------------------------- /internal/envelope/envelope.go: -------------------------------------------------------------------------------- 1 | // Package envelope implements functions related to handling email envelopes 2 | // (basically tuples of (from, to, data). 3 | package envelope 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | 9 | "blitiri.com.ar/go/chasquid/internal/set" 10 | ) 11 | 12 | // Split an user@domain address into user and domain. 13 | func Split(addr string) (string, string) { 14 | ps := strings.SplitN(addr, "@", 2) 15 | if len(ps) != 2 { 16 | return addr, "" 17 | } 18 | 19 | return ps[0], ps[1] 20 | } 21 | 22 | // UserOf user@domain returns user. 23 | func UserOf(addr string) string { 24 | user, _ := Split(addr) 25 | return user 26 | } 27 | 28 | // DomainOf user@domain returns domain. 29 | func DomainOf(addr string) string { 30 | _, domain := Split(addr) 31 | return domain 32 | } 33 | 34 | // DomainIn checks that the domain of the address is on the given set. 35 | func DomainIn(addr string, locals *set.String) bool { 36 | domain := DomainOf(addr) 37 | if domain == "" { 38 | return true 39 | } 40 | 41 | return locals.Has(domain) 42 | } 43 | 44 | // AddHeader adds (prepends) a MIME header to the message. 45 | func AddHeader(data []byte, k, v string) []byte { 46 | if len(v) > 0 { 47 | // If the value contains newlines, indent them properly. 48 | if v[len(v)-1] == '\n' { 49 | v = v[:len(v)-1] 50 | } 51 | v = strings.Replace(v, "\n", "\n\t", -1) 52 | } 53 | 54 | header := []byte(fmt.Sprintf("%s: %s\n", k, v)) 55 | return append(header, data...) 56 | } 57 | -------------------------------------------------------------------------------- /internal/nettrace/bench_test.go: -------------------------------------------------------------------------------- 1 | package nettrace 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Our benchmark loop is similar to the one from golang.org/x/net/trace, so we 8 | // can compare results. 9 | func runBench(b *testing.B, events int) { 10 | nTraces := (b.N + events + 1) / events 11 | 12 | for i := 0; i < nTraces; i++ { 13 | tr := New("bench", "test") 14 | for j := 0; j < events; j++ { 15 | tr.Printf("%d", j) 16 | } 17 | tr.Finish() 18 | } 19 | } 20 | 21 | func BenchmarkTrace_2(b *testing.B) { 22 | runBench(b, 2) 23 | } 24 | 25 | func BenchmarkTrace_10(b *testing.B) { 26 | runBench(b, 10) 27 | } 28 | 29 | func BenchmarkTrace_100(b *testing.B) { 30 | runBench(b, 100) 31 | } 32 | 33 | func BenchmarkTrace_1000(b *testing.B) { 34 | runBench(b, 1000) 35 | } 36 | 37 | func BenchmarkTrace_10000(b *testing.B) { 38 | runBench(b, 10000) 39 | } 40 | 41 | func BenchmarkNewAndFinish(b *testing.B) { 42 | for i := 0; i < b.N; i++ { 43 | tr := New("bench", "test") 44 | tr.Finish() 45 | } 46 | } 47 | 48 | func BenchmarkPrintf(b *testing.B) { 49 | tr := New("bench", "test") 50 | defer tr.Finish() 51 | b.ResetTimer() 52 | for i := 0; i < b.N; i++ { 53 | // Keep this without any formatting, so we measure our code instead of 54 | // the performance of fmt.Sprintf. 55 | tr.Printf("this is printf") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/nettrace/context.go: -------------------------------------------------------------------------------- 1 | package nettrace 2 | 3 | import "context" 4 | 5 | type ctxKeyT string 6 | 7 | const ctxKey ctxKeyT = "blitiri.com.ar/go/srv/nettrace" 8 | 9 | // NewContext returns a new context with the given trace attached. 10 | func NewContext(ctx context.Context, tr Trace) context.Context { 11 | return context.WithValue(ctx, ctxKey, tr) 12 | } 13 | 14 | // FromContext returns the trace attached to the given context (if any). 15 | func FromContext(ctx context.Context) (Trace, bool) { 16 | tr, ok := ctx.Value(ctxKey).(Trace) 17 | return tr, ok 18 | } 19 | 20 | // FromContextOrNew returns the trace attached to the given context, or a new 21 | // trace if there is none. 22 | func FromContextOrNew(ctx context.Context, family, title string) (Trace, context.Context) { 23 | tr, ok := FromContext(ctx) 24 | if ok { 25 | return tr, ctx 26 | } 27 | 28 | tr = New(family, title) 29 | return tr, NewContext(ctx, tr) 30 | } 31 | 32 | // ChildFromContext returns a new trace that is a child of the one attached to 33 | // the context (if any). 34 | func ChildFromContext(ctx context.Context, family, title string) Trace { 35 | parent, ok := FromContext(ctx) 36 | if ok { 37 | return parent.NewChild(family, title) 38 | } 39 | return New(family, title) 40 | } 41 | -------------------------------------------------------------------------------- /internal/nettrace/context_test.go: -------------------------------------------------------------------------------- 1 | package nettrace 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestContext(t *testing.T) { 9 | tr := New("TestContext", "trace") 10 | defer tr.Finish() 11 | 12 | // Attach the trace to a new context. 13 | ctx := NewContext(context.Background(), tr) 14 | 15 | // Get the trace back from the context. 16 | { 17 | tr2, ok := FromContext(ctx) 18 | if !ok { 19 | t.Errorf("Context with trace returned not found") 20 | } 21 | if tr != tr2 { 22 | t.Errorf("Trace from context is different: %v != %v", tr, tr2) 23 | } 24 | } 25 | 26 | // Create a child trace from the context. 27 | { 28 | tr3 := ChildFromContext(ctx, "TestContext", "child") 29 | if p := tr3.(*trace).Parent; p != tr { 30 | t.Errorf("Child doesn't have the right parent: %v != %v", p, tr) 31 | } 32 | tr3.Finish() 33 | } 34 | 35 | // FromContextOrNew returns the one from the context. 36 | { 37 | tr4, ctx4 := FromContextOrNew(ctx, "TestContext", "from-ctx") 38 | if ctx4 != ctx { 39 | t.Errorf("Got new context: %v != %v", ctx4, ctx) 40 | } 41 | if tr4 != tr { 42 | t.Errorf("Context with trace returned new trace: %v != %v", tr4, tr) 43 | } 44 | } 45 | 46 | // FromContextOrNew needs to create a new one. 47 | { 48 | tr5, ctx5 := FromContextOrNew( 49 | context.Background(), "TestContext", "tr5") 50 | if tr, _ := FromContext(ctx5); tr != tr5 { 51 | t.Errorf("Context with trace returned the wrong trace: %v != %v", 52 | tr, tr5) 53 | } 54 | tr5.Finish() 55 | } 56 | 57 | // Child from a context that has no trace attached. 58 | { 59 | tr6 := ChildFromContext( 60 | context.Background(), "TestContext", "child") 61 | tr6.Finish() 62 | if p := tr6.(*trace).Parent; p != nil { 63 | t.Errorf("Expected orphan trace, it has a parent: %v", p) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /internal/nettrace/evtring.go: -------------------------------------------------------------------------------- 1 | package nettrace 2 | 3 | import "time" 4 | 5 | type evtRing struct { 6 | evts []event 7 | max int 8 | pos int // Points to the latest element. 9 | firstDrop time.Time 10 | } 11 | 12 | func newEvtRing(n int) *evtRing { 13 | return &evtRing{ 14 | max: n, 15 | pos: -1, 16 | } 17 | } 18 | 19 | func (r *evtRing) Add(e *event) { 20 | if len(r.evts) < r.max { 21 | r.evts = append(r.evts, *e) 22 | r.pos++ 23 | return 24 | } 25 | 26 | r.pos = (r.pos + 1) % r.max 27 | 28 | // Record the first drop as the time of the first dropped message. 29 | if r.firstDrop.IsZero() { 30 | r.firstDrop = r.evts[r.pos].When 31 | } 32 | 33 | r.evts[r.pos] = *e 34 | } 35 | 36 | func (r *evtRing) Do(f func(e *event)) { 37 | for i := 0; i < len(r.evts); i++ { 38 | // Go from older to newer by starting at (r.pos+1). 39 | pos := (r.pos + 1 + i) % len(r.evts) 40 | f(&r.evts[pos]) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /internal/nettrace/histogram.go: -------------------------------------------------------------------------------- 1 | package nettrace 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type histogram struct { 8 | count [nBuckets]uint64 9 | 10 | totalQ uint64 11 | totalT time.Duration 12 | min time.Duration 13 | max time.Duration 14 | } 15 | 16 | func (h *histogram) Add(bucket int, latency time.Duration) { 17 | if h.totalQ == 0 || h.min > latency { 18 | h.min = latency 19 | } 20 | if h.max < latency { 21 | h.max = latency 22 | } 23 | 24 | h.count[bucket]++ 25 | h.totalQ++ 26 | h.totalT += latency 27 | } 28 | 29 | type histSnapshot struct { 30 | Counts map[time.Duration]line 31 | Count uint64 32 | Avg, Min, Max time.Duration 33 | } 34 | 35 | type line struct { 36 | Start time.Duration 37 | BucketIdx int 38 | Count uint64 39 | Percent float32 40 | CumPct float32 41 | } 42 | 43 | func (h *histogram) Snapshot() *histSnapshot { 44 | s := &histSnapshot{ 45 | Counts: map[time.Duration]line{}, 46 | Count: h.totalQ, 47 | Min: h.min, 48 | Max: h.max, 49 | } 50 | 51 | if h.totalQ > 0 { 52 | s.Avg = time.Duration(uint64(h.totalT) / h.totalQ) 53 | } 54 | 55 | var cumCount uint64 56 | for i := 0; i < nBuckets; i++ { 57 | cumCount += h.count[i] 58 | l := line{ 59 | Start: buckets[i], 60 | BucketIdx: i, 61 | Count: h.count[i], 62 | } 63 | if h.totalQ > 0 { 64 | l.Percent = float32(h.count[i]) / float32(h.totalQ) * 100 65 | l.CumPct = float32(cumCount) / float32(h.totalQ) * 100 66 | } 67 | s.Counts[buckets[i]] = l 68 | } 69 | 70 | return s 71 | } 72 | -------------------------------------------------------------------------------- /internal/nettrace/histogram_test.go: -------------------------------------------------------------------------------- 1 | package nettrace 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestHistogramBasic(t *testing.T) { 9 | h := histogram{} 10 | h.Add(1, 1*time.Millisecond) 11 | 12 | snap := h.Snapshot() 13 | if snap.Count != 1 || 14 | snap.Min != 1*time.Millisecond || 15 | snap.Max != 1*time.Millisecond || 16 | snap.Avg != 1*time.Millisecond { 17 | t.Errorf("expected snapshot with only 1 sample, got %v", snap) 18 | } 19 | } 20 | 21 | func TestHistogramEmpty(t *testing.T) { 22 | h := histogram{} 23 | snap := h.Snapshot() 24 | 25 | if len(snap.Counts) != nBuckets || snap.Count != 0 || 26 | snap.Avg != 0 || snap.Min != 0 || snap.Max != 0 { 27 | t.Errorf("expected zero snapshot, got %v", snap) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/nettrace/templates/_latency.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
Count: {{.Latencies.Count}}Avg: {{.Latencies.Avg | roundDuration}}Min: {{.Latencies.Min | roundDuration}}Max: {{.Latencies.Max | roundDuration}}
7 |

8 | 9 | 10 | 11 | {{range .Latencies.Counts}} 12 | 13 | 18 | 19 | 20 | 23 | 24 | {{end}} 25 |
BucketCount%Cumulative
14 | 16 | ≥{{.Start}} 17 | {{.Count}}{{.Percent | printf "%5.2f"}}% 21 | {{.Percent | printf "%.2f"}}% 22 | {{.CumPct | printf "%5.2f"}}%
26 | -------------------------------------------------------------------------------- /internal/nettrace/templates/_recursive.html.tmpl: -------------------------------------------------------------------------------- 1 | {{if .Trace.Parent}} 2 | 3 | Parent: {{.Trace.Parent.Family}} - {{.Trace.Parent.Title}} 4 |

5 | {{end}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{$prev := .Trace.Start}} 21 | {{range .AllEvents}} 22 | 23 | 26 | 27 | 28 | 29 | 32 | 46 | 47 | {{$prev = .Event.When}} 48 | {{end}} 49 | 50 | 51 |
TraceTimestampElapsed (s)Message
25 | {{shorttitle .Trace}}{{.Event.When.Format "15:04:05.000000"}}{{(.Event.When.Sub $prev) | stripZeros}} 31 |
{{traceemoji .Trace.ID}}
33 | {{- depthspan .Depth -}} 34 | {{- if .Event.Type.IsLog -}} 35 | {{.Event.Msg}} 36 | {{- else if .Event.Type.IsChild -}} 37 | new child: {{.Event.Ref.Family}} - {{.Event.Ref.Title}} 38 | {{- else if .Event.Type.IsLink -}} 39 | {{.Event.Msg}} 40 | {{- else if .Event.Type.IsDrop -}} 41 | [ events dropped ] 42 | {{- else -}} 43 | [ unknown event type ] 44 | {{- end -}} 45 |
52 | -------------------------------------------------------------------------------- /internal/nettrace/templates/_single.html.tmpl: -------------------------------------------------------------------------------- 1 | 2 | {{.Start.Format "2006-01-02 15:04:05.000000"}} 3 | {{.Duration | roundSeconds}} 4 | {{.Title}} 5 | {{if .Parent}}(parent: 6 | {{.Parent.Family}} - {{.Parent.Title}}) 7 | {{end}} 8 | 9 | 10 | 11 | {{$prev := .Start}} 12 | {{range .Events}} 13 | 14 | {{.When.Format "15:04:05.000000"}} 15 | {{(.When.Sub $prev) | stripZeros}} 16 | 17 | {{- if .Type.IsLog -}} 18 | {{.Msg}} 19 | {{- else if .Type.IsChild -}} 20 | new child {{.Ref.Family}} {{.Ref.Title}} 21 | {{- else if .Type.IsLink -}} 22 | {{.Msg}} 23 | {{- else if .Type.IsDrop -}} 24 | [ events dropped ] 25 | {{- else -}} 26 | [ unknown event type ] 27 | {{- end -}} 28 | 29 | 30 | {{$prev = .When}} 31 | {{end}} 32 | 33 | 34 |   35 | 36 | -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzAddr/31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ÑAndÚ") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzAddr/7aba1e0ef80990ccac3731800dbb0267c4c8b7156d4da3b8a5f1b57a570adfb8: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("henryⅣ@throne") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzAddr/ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ñandú") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzAddr/d8637022b61fb5c4df4e153063564accd6331debaafdd594405c320a5e9f2e70: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("pé@léa") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzAddr/dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("Pingüino") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomain/263da65bb5a59369f294d26a64a36a989a9a36ed5c60950b123e395bedbe881c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("henryⅣthrone") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomain/31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ÑAndÚ") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomain/6d603c8b9fbe8b9aa021dbde499ec1b3a00922b9338c68b2984cd314c3d5e633: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("péléa") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomain/ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ñandú") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomain/dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("Pingüino") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomainToUnicode/31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ÑAndÚ") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomainToUnicode/7aba1e0ef80990ccac3731800dbb0267c4c8b7156d4da3b8a5f1b57a570adfb8: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("henryⅣ@throne") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomainToUnicode/ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ñandú") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomainToUnicode/d8637022b61fb5c4df4e153063564accd6331debaafdd594405c320a5e9f2e70: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("pé@léa") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzDomainToUnicode/dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("Pingüino") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzUser/263da65bb5a59369f294d26a64a36a989a9a36ed5c60950b123e395bedbe881c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("henryⅣthrone") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzUser/31400a53be6363c91bf6585789663189fa30b16181c1d18f19708acccc85f4a1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ÑAndÚ") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzUser/6d603c8b9fbe8b9aa021dbde499ec1b3a00922b9338c68b2984cd314c3d5e633: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("péléa") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzUser/ccde73fe7b7352806a87cece8eb81867bdeb177019b69a4bb3c7bb5a277b9c32: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("ñandú") -------------------------------------------------------------------------------- /internal/normalize/testdata/fuzz/FuzzUser/dc0204d8e2ab058a763873d2a5fede806e95235771ecdd96b56c906886822c19: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | string("Pingüino") -------------------------------------------------------------------------------- /internal/protoio/testpb/dummy.go: -------------------------------------------------------------------------------- 1 | package testpb 2 | 3 | //go:generate protoc --go_out=. --go_opt=paths=source_relative testpb.proto 4 | -------------------------------------------------------------------------------- /internal/protoio/testpb/testpb.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package testpb; 5 | option go_package = "blitiri.com.ar/go/chasquid/internal/protoio/testpb"; 6 | 7 | message M { 8 | string content = 1; 9 | } 10 | -------------------------------------------------------------------------------- /internal/queue/queue.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package queue; 5 | option go_package = "blitiri.com.ar/go/chasquid/internal/queue"; 6 | 7 | message Message { 8 | // Message ID. Uniquely identifies this message, it is used for 9 | // auditing and troubleshooting. 10 | string ID = 1; 11 | 12 | // The envelope for this message. 13 | string from = 2; 14 | repeated string To = 3; 15 | repeated Recipient rcpt = 4; 16 | bytes data = 5; 17 | 18 | // Creation timestamp. 19 | Timestamp created_at_ts = 6; 20 | } 21 | 22 | message Recipient { 23 | // Address to send the message to. 24 | // This is the final one, after expanding aliases. 25 | string address = 1; 26 | 27 | enum Type { 28 | EMAIL = 0; 29 | PIPE = 1; 30 | FORWARD = 2; 31 | } 32 | Type type = 2; 33 | 34 | enum Status { 35 | PENDING = 0; 36 | SENT = 1; 37 | FAILED = 2; 38 | } 39 | Status status = 3; 40 | 41 | string last_failure_message = 4; 42 | 43 | // Address that this recipient was originally intended to. 44 | // This is before expanding aliases and only used in very particular 45 | // cases. 46 | string original_address = 5; 47 | 48 | // The list of servers to use, for recipients of type == FORWARD. 49 | repeated string via = 6; 50 | } 51 | 52 | // Timestamp representation, for convenience. 53 | // We used to use the well-known type, but the dependency makes packaging much 54 | // more convoluted and adds very little value, so we now just include it here. 55 | message Timestamp { 56 | // Represents seconds of UTC time since Unix epoch. 57 | int64 seconds = 1; 58 | 59 | // Non-negative fractions of a second at nanosecond resolution. 60 | int32 nanos = 2; 61 | } 62 | -------------------------------------------------------------------------------- /internal/set/set.go: -------------------------------------------------------------------------------- 1 | // Package set implement sets for various types. Well, only string for now :) 2 | package set 3 | 4 | // String set. 5 | type String struct { 6 | m map[string]struct{} 7 | } 8 | 9 | // NewString returns a new string set, with the given values in it. 10 | func NewString(values ...string) *String { 11 | s := &String{} 12 | s.Add(values...) 13 | return s 14 | } 15 | 16 | // Add values to the string set. 17 | func (s *String) Add(values ...string) { 18 | if s.m == nil { 19 | s.m = map[string]struct{}{} 20 | } 21 | 22 | for _, v := range values { 23 | s.m[v] = struct{}{} 24 | } 25 | } 26 | 27 | // Has checks if the set has the given value. 28 | func (s *String) Has(value string) bool { 29 | // We explicitly allow s to be nil *in this function* to simplify callers' 30 | // code. Note that Add will not tolerate it, and will panic. 31 | if s == nil || s.m == nil { 32 | return false 33 | } 34 | _, ok := s.m[value] 35 | return ok 36 | } 37 | -------------------------------------------------------------------------------- /internal/set/set_test.go: -------------------------------------------------------------------------------- 1 | package set 2 | 3 | import "testing" 4 | 5 | func TestString(t *testing.T) { 6 | s1 := &String{} 7 | 8 | // Test that Has works on a new set. 9 | if s1.Has("x") { 10 | t.Error("'x' is in the empty set") 11 | } 12 | 13 | s1.Add("a") 14 | s1.Add("b", "ccc") 15 | 16 | expectStrings(s1, []string{"a", "b", "ccc"}, []string{"not-in"}, t) 17 | 18 | s2 := NewString("a", "b", "c") 19 | expectStrings(s2, []string{"a", "b", "c"}, []string{"not-in"}, t) 20 | 21 | // Test that Has works (and not panics) on a nil set. 22 | var s3 *String 23 | if s3.Has("x") { 24 | t.Error("'x' is in the nil set") 25 | } 26 | } 27 | 28 | func expectStrings(s *String, in []string, notIn []string, t *testing.T) { 29 | for _, str := range in { 30 | if !s.Has(str) { 31 | t.Errorf("String %q not in set, it should be", str) 32 | } 33 | } 34 | 35 | for _, str := range notIn { 36 | if s.Has(str) { 37 | t.Errorf("String %q is in the set, should not be", str) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/3d7e992212e817da7afdb7a4e769ceec1d4047a2e630bec4b35ecd4d55560424: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("HELO localhost\nMAIL FROM:\nRCPT LALA: <>\nRCPT TO:\nRCPT TO:\nRCPT TO:\nRCPT TO:\nRCPT TO:\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/68d8c7b5f149996ffd46ad9a15852165d8c1cbd6c03cceb9382e5add16415c94: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("DATA\nHELO localhost\nDATA\nMAIL FROM:\nRCPT TO: user@testserver\nDATA\nFrom: Mailer daemon \nSubject: I've come to haunt you\nBad header\n\nMuahahahaha\n\n\n.\nQUIT\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/79e51b30c215fb19a29855deebf2ed8299b35ca6f14db9681ee504e216c44a7f: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("EHLO localhost\nMAIL FROM: <>\nRCPT TO: user@testserver\nDATA\nFrom: Mailer daemon \nSubject: I've come to haunt you\nMessage-ID: \n\nÑañañañaña!\n\n\n.\nQUIT\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/83ab02fccf91c1b9c0c972de745dc2a45d23dc3236f9027e605c3e017d8898fe: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("HELO localhost\nMAIL LALA: <>\nMAIL FROM:\nMAIL FROM:\nMAIL FROM:\nMAIL FROM:\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/a24124ade554d7a25de538f2cbbced6245ba60e90d221e51590456e222c80359: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("HELO\nEHLO\nHELO localhost\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/b896b41db27f6e36e4e727ac4f7b3d02fad34d217855c0d433ea3a325951b3bf: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("HELO localhost\nQUIT\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/bf15e6fb937795251090940ac60a37705b36a13e71a9557e7aaf0618ea2cf661: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("EHLO localhost\nMAIL FROM: <>\nRCPT TO: user@testserver\nDATA\nFrom: Mailer daemon \nSubject: I've come to haunt you\n\nMuahahahaha\n\n\n.\nQUIT\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/d1b1ccbbb380c53282cc2689c4bd9ff0d03a03698e9be55371739ef95d7dd671: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("EHLO localhost\nAUTH PLAIN something\nAUTH PLAIN something\nAUTH PLAIN something\nAUTH PLAIN something\nAUTH PLAIN something\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/dc70e53325976a3a1067feb0b0c956c5a9abec1c867f8198808ccff83f594ded: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("EHLO localhost\nWHATISTHIS\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/e7682fde78ce0d78ddc7a818f151b6f04466a2c122197a2e4e8048d194ed72c2: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | []byte("EHLO localhost\nAUTH PLAIN\n") 4 | -------------------------------------------------------------------------------- /internal/smtpsrv/testdata/fuzz/FuzzConnection/fd41d0c11b1bb7f89825934b2ec51db1df166e34b4610e8089549eedf2e3635c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(2) 3 | []byte("EHLO localhost\nAUTH SOMETHINGELSE\nAUTH PLAIN\ndXNlckB0ZXN0c2VydmVyAHlalala==\nAUTH PLAIN\ndXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgB3cm9uZ3Bhc3N3b3Jk\nAUTH PLAIN\ndXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgBzZWNyZXRwYXNzd29yZA==\nAUTH PLAIN\n") 4 | -------------------------------------------------------------------------------- /internal/tlsconst/generate-ciphers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # This hacky script generates a go file with a map of version -> name for the 4 | # entries in the TLS Cipher Suite Registry. 5 | 6 | import csv 7 | import urllib.request 8 | import sys 9 | 10 | # Where to get the TLS parameters from. 11 | # See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml. 12 | URL = "https://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv" 13 | 14 | 15 | def getCiphers(): 16 | req = urllib.request.urlopen(URL) 17 | data = req.read().decode("utf-8") 18 | 19 | ciphers = [] 20 | reader = csv.DictReader(data.splitlines()) 21 | for row in reader: 22 | desc = row["Description"] 23 | rawval = row["Value"] 24 | 25 | # Just plain TLS values for now, to keep it simple. 26 | if "-" in rawval or not desc.startswith("TLS"): 27 | continue 28 | 29 | rv1, rv2 = rawval.split(",") 30 | rv1, rv2 = int(rv1, 16), int(rv2, 16) 31 | 32 | val = "0x%02x%02x" % (rv1, rv2) 33 | ciphers.append((val, desc)) 34 | 35 | return ciphers 36 | 37 | 38 | ciphers = getCiphers() 39 | 40 | out = open(sys.argv[1], "w") 41 | out.write( 42 | """\ 43 | package tlsconst 44 | 45 | // AUTOGENERATED - DO NOT EDIT 46 | // 47 | // This file was autogenerated by generate-ciphers.py. 48 | 49 | var cipherSuiteName = map[uint16]string{ 50 | """ 51 | ) 52 | 53 | for ver, desc in ciphers: 54 | out.write('\t%s: "%s",\n' % (ver, desc)) 55 | 56 | out.write("}\n") 57 | -------------------------------------------------------------------------------- /internal/tlsconst/tlsconst.go: -------------------------------------------------------------------------------- 1 | // Package tlsconst contains TLS constants for human consumption. 2 | package tlsconst 3 | 4 | // Most of the constants get automatically generated from IANA's assignments. 5 | //go:generate ./generate-ciphers.py ciphers.go 6 | 7 | import "fmt" 8 | 9 | var versionName = map[uint16]string{ 10 | 0x0300: "SSL-3.0", 11 | 0x0301: "TLS-1.0", 12 | 0x0302: "TLS-1.1", 13 | 0x0303: "TLS-1.2", 14 | 0x0304: "TLS-1.3", 15 | } 16 | 17 | // VersionName returns a human-readable TLS version name. 18 | func VersionName(v uint16) string { 19 | name, ok := versionName[v] 20 | if !ok { 21 | return fmt.Sprintf("TLS-%#04x", v) 22 | } 23 | return name 24 | } 25 | 26 | // CipherSuiteName returns a human-readable TLS cipher suite name. 27 | func CipherSuiteName(s uint16) string { 28 | name, ok := cipherSuiteName[s] 29 | if !ok { 30 | return fmt.Sprintf("TLS_UNKNOWN_CIPHER_SUITE-%#04x", s) 31 | } 32 | return name 33 | } 34 | -------------------------------------------------------------------------------- /internal/tlsconst/tlsconst_test.go: -------------------------------------------------------------------------------- 1 | package tlsconst 2 | 3 | import "testing" 4 | 5 | func TestVersionName(t *testing.T) { 6 | cases := []struct { 7 | ver uint16 8 | expected string 9 | }{ 10 | {0x0302, "TLS-1.1"}, 11 | {0x1234, "TLS-0x1234"}, 12 | } 13 | for _, c := range cases { 14 | got := VersionName(c.ver) 15 | if got != c.expected { 16 | t.Errorf("VersionName(%x) = %q, expected %q", 17 | c.ver, got, c.expected) 18 | } 19 | } 20 | } 21 | 22 | func TestCipherSuiteName(t *testing.T) { 23 | cases := []struct { 24 | suite uint16 25 | expected string 26 | }{ 27 | {0xc073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384"}, 28 | {0x1234, "TLS_UNKNOWN_CIPHER_SUITE-0x1234"}, 29 | } 30 | for _, c := range cases { 31 | got := CipherSuiteName(c.suite) 32 | if got != c.expected { 33 | t.Errorf("CipherSuiteName(%x) = %q, expected %q", 34 | c.suite, got, c.expected) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /internal/userdb/userdb.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | package userdb; 5 | option go_package = "blitiri.com.ar/go/chasquid/internal/userdb"; 6 | 7 | message ProtoDB { 8 | map users = 1; 9 | } 10 | 11 | message Password { 12 | oneof scheme { 13 | Scrypt scrypt = 2; 14 | Plain plain = 3; 15 | Denied denied = 4; 16 | } 17 | } 18 | 19 | message Scrypt { 20 | uint64 logN = 1; 21 | int32 r = 2; 22 | int32 p = 3; 23 | int32 keyLen = 4; 24 | bytes salt = 5; 25 | bytes encrypted = 6; 26 | } 27 | 28 | message Plain { 29 | bytes password = 1; 30 | } 31 | 32 | message Denied { } 33 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore the user databases - we create them each time. 3 | t-*/config/**/users 4 | t-*/?/**/users 5 | stress-*/config/**/users 6 | stress-*/?/**/users 7 | 8 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/util/lib.sh" 5 | 6 | init 7 | 8 | FAILED=0 9 | 10 | for i in t-*; do 11 | echo "$i" ... 12 | setsid -w "$i/run.sh" 13 | FAILED=$(( FAILED + $? )) 14 | echo 15 | done 16 | 17 | exit $FAILED 18 | -------------------------------------------------------------------------------- /test/stress-01-load/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | hostname: "testserver" 2 | 3 | smtp_address: ":1025" 4 | submission_address: ":1587" 5 | submission_over_tls_address: ":1465" 6 | monitoring_address: ":1099" 7 | 8 | mail_delivery_agent_bin: "test-mda" 9 | mail_delivery_agent_args: "%to%" 10 | 11 | data_dir: "../.data" 12 | mail_log_path: "../.logs/mail_log" 13 | 14 | suffix_separators: "+-" 15 | drop_characters: "._" 16 | -------------------------------------------------------------------------------- /test/stress-01-load/config/domains/testserver/aliases: -------------------------------------------------------------------------------- 1 | 2 | null: | true 3 | fail: | false 4 | 5 | -------------------------------------------------------------------------------- /test/stress-01-load/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | 8 | generate_certs_for testserver 9 | add_user user@testserver secretpassword 10 | 11 | # Note we run the server with minimal logging, to avoid generating very large 12 | # log files, which are not very useful anyway. 13 | mkdir -p .logs 14 | chasquid -v=-1 --logfile=.logs/chasquid.log --config_dir=config & 15 | wait_until_ready 1025 16 | 17 | echo "Peak RAM: $(chasquid_ram_peak)" 18 | 19 | if ! loadgen -logtime -addr=localhost:1025 -run_for=3s -noop; then 20 | fail "loadgen -noop error" 21 | fi 22 | 23 | echo "Peak RAM: $(chasquid_ram_peak)" 24 | 25 | if ! loadgen -logtime -addr=localhost:1025 -run_for=3s; then 26 | fail "loadgen error" 27 | fi 28 | 29 | echo "Peak RAM: $(chasquid_ram_peak)" 30 | 31 | success 32 | -------------------------------------------------------------------------------- /test/stress-02-connections/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | hostname: "testserver" 2 | 3 | smtp_address: ":1025" 4 | submission_address: ":1587" 5 | submission_over_tls_address: ":1465" 6 | monitoring_address: ":1099" 7 | 8 | mail_delivery_agent_bin: "test-mda" 9 | mail_delivery_agent_args: "%to%" 10 | 11 | data_dir: "../.data" 12 | mail_log_path: "../.logs/mail_log" 13 | 14 | suffix_separators: "+-" 15 | drop_characters: "._" 16 | -------------------------------------------------------------------------------- /test/stress-02-connections/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | 8 | generate_certs_for testserver 9 | add_user user@testserver secretpassword 10 | 11 | # Note we run the server with minimal logging, to avoid generating very large 12 | # log files, which are not very useful anyway. 13 | mkdir -p .logs 14 | chasquid -v=-1 --logfile=.logs/chasquid.log --config_dir=config & 15 | wait_until_ready 1025 16 | 17 | echo "Peak RAM: $(chasquid_ram_peak)" 18 | 19 | # Set connection count to (max open files) - (leeway). 20 | # We set the leeway to account for file descriptors opened by the runtime and 21 | # listeners; 20 should be enough for now. 22 | # Cap it to 2000, as otherwise it can be problematic due to port availability. 23 | COUNT=$(( $(ulimit -n) - 20 )) 24 | if [ $COUNT -gt 2000 ]; then 25 | COUNT=2000 26 | fi 27 | 28 | if ! conngen -logtime -addr=localhost:1025 -count=$COUNT; then 29 | tail -n 1 .logs/chasquid.log 30 | fail "conngen error" 31 | fi 32 | 33 | echo "Peak RAM: $(chasquid_ram_peak)" 34 | 35 | success 36 | -------------------------------------------------------------------------------- /test/stress.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/util/lib.sh" 5 | 6 | init 7 | 8 | FAILED=0 9 | 10 | for i in stress-*; do 11 | echo "$i ..." 12 | setsid -w "$i/run.sh" 13 | FAILED=$(( FAILED + $? )) 14 | echo 15 | done 16 | 17 | exit $FAILED 18 | -------------------------------------------------------------------------------- /test/t-01-simple_local/config/certs/noprivkey/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC/TCCAeWgAwIBAgIQeSGdISDwzlRobkplbaT4uTANBgkqhkiG9w0BAQsFADAS 3 | MRAwDgYDVQQKEwdBY21lIENvMB4XDTE4MDYwMzIyNDMwMloXDTE4MDYwMzIzNDMw 4 | MlowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBAKYuJT9DPp7qwDKoNuyhMPgA1456ApSIE+w55N0XyDvIKBTTq0xvRMU/ 6 | 1QgL6RvQCOYBh/lf8OF9lSp9IyINFD/H/VRXOOdxLimPOgvu+pZTgOOG9drgivwW 7 | 7WdMBIKt+XYhbI0sNgeN2mvkeD1x9Hx0qxRO9n7nurXYYr5ZPCIhlE7NTVbtKxCC 8 | qnvJK+nPx/0gMLkhp+38Ishtbr/yUC+KLOtk1Ykt6S8IhiEGbVFSiqZv8KCquTg7 9 | S8e40q9YJkwng6MiHaXoZv4g1QRT1jUZE/8h3VSAfvcRYtWbPQ+R8zIFNfbI8WJA 10 | KwrBu34siI5gtzB3GI416DN8YF0l+ncCAwEAAaNPME0wDgYDVR0PAQH/BAQDAgKk 11 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wFQYDVR0RBA4w 12 | DIIKdGVzdHNlcnZlcjANBgkqhkiG9w0BAQsFAAOCAQEAno2ANy1TPvoqfDebpXJX 13 | FqrrF/MmY24PLrnt2VU7dkKatjSaddSL90wTUi/m7gEH8RS3iW6EGRavw30dUrmJ 14 | J3lGfDIdm69hemcqcI1jWU0B8HigmOUhKpw/9SnQGV90IBkpv1hNrkdmqhn3a2I0 15 | IPqDshoF1qg3ECmsfnhja5Os5G2Iaxshda5gEk0dZE6epJHwFnJynHw7n3FDTtlQ 16 | 1cVvwsamG4mAtey7tPFvG955wZutFgmwoapICvKHKH2ny8dzJCAHkR8RloHLE5ZF 17 | HXnhAkgIUX07V314nlUEhrxn28Lhyb92hanc8oExBoJ8OVRtxt2X7y93LY29K0po 18 | uQ== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/t-01-simple_local/config/certs/not_a_dir: -------------------------------------------------------------------------------- 1 | A simple file, to make sure chasquid does not get confused with them in the 2 | "certs" directory. 3 | -------------------------------------------------------------------------------- /test/t-01-simple_local/config/certs/symlink: -------------------------------------------------------------------------------- 1 | testserver/ -------------------------------------------------------------------------------- /test/t-01-simple_local/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-01-simple_local/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-01-simple_local/hosts: -------------------------------------------------------------------------------- 1 | testserver localhost 2 | -------------------------------------------------------------------------------- /test/t-01-simple_local/msmtprc: -------------------------------------------------------------------------------- 1 | account default 2 | 3 | host testserver 4 | port 1587 5 | 6 | tls on 7 | tls_trust_file config/certs/testserver/fullchain.pem 8 | 9 | from user@testserver 10 | 11 | auth on 12 | user user@testserver 13 | password secretpassword 14 | 15 | account smtpport : default 16 | port 1025 17 | 18 | account subm_tls : default 19 | port 1465 20 | tls_starttls off 21 | 22 | account baduser : default 23 | user unknownuser@testserver 24 | password secretpassword 25 | 26 | account badpasswd : default 27 | user user@testserver 28 | password badsecretpassword 29 | -------------------------------------------------------------------------------- /test/t-01-simple_local/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | check_hostaliases 8 | 9 | mkdir -p .logs 10 | 11 | if ! chasquid --version > /dev/null; then 12 | fail "chasquid --version failed" 13 | fi 14 | 15 | generate_certs_for testserver 16 | chasquid-util-user-add user@testserver secretpassword 17 | chasquid-util-user-add someone@testserver secretpassword 18 | 19 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config & 20 | wait_until_ready 1025 21 | 22 | run_msmtp someone@testserver < content 23 | 24 | wait_for_file .mail/someone@testserver 25 | 26 | mail_diff content .mail/someone@testserver 27 | 28 | # At least for now, we allow AUTH over the SMTP port to avoid unnecessary 29 | # complexity, so we expect it to work. 30 | if ! run_msmtp -a smtpport someone@testserver < content 2> /dev/null; then 31 | fail "failed auth on the SMTP port" 32 | fi 33 | 34 | # Check deliver over the submission-over-TLS port. 35 | if ! run_msmtp -a subm_tls someone@testserver < content 2> /dev/null; then 36 | fail "failed submission over TLS" 37 | fi 38 | 39 | if run_msmtp nobody@testserver < content 2> /dev/null; then 40 | fail "successfully sent an email to a non-existent user" 41 | fi 42 | 43 | if run_msmtp -a baduser someone@testserver < content 2> /dev/null; then 44 | fail "successfully sent an email with a bad password" 45 | fi 46 | 47 | if run_msmtp -a badpasswd someone@testserver < content 2> /dev/null; then 48 | fail "successfully sent an email with a bad password" 49 | fi 50 | 51 | success 52 | -------------------------------------------------------------------------------- /test/t-02-exim/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Packages, so fetches via get-exim4-* don't add cruft. 3 | *.deb 4 | -------------------------------------------------------------------------------- /test/t-02-exim/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-02-exim/config/exim4.in: -------------------------------------------------------------------------------- 1 | CONFDIR = ${EXIMDIR} 2 | 3 | spool_directory = CONFDIR/spool 4 | exim_path = CONFDIR/exim4 5 | 6 | # No need to keep anything on the environment. 7 | # This is the default, but exim emits a warning if it's not set 8 | # (https://www.exim.org/static/doc/CVE-2016-1531.txt). 9 | keep_environment = 10 | 11 | # Disable TLS for now. 12 | tls_advertise_hosts = 13 | 14 | # Run as the current user. 15 | exim_group = ${USER} 16 | exim_user = ${USER} 17 | 18 | # Listen on a non-privileged port. 19 | daemon_smtp_port = 2025 20 | 21 | # ACLs to let anyone send mail (for testing, obviously). 22 | acl_smtp_rcpt = acl_check_rcpt 23 | acl_smtp_data = acl_check_data 24 | 25 | begin acl 26 | acl_check_rcpt: 27 | accept 28 | 29 | acl_check_data: 30 | accept 31 | 32 | 33 | # Rewrite envelope-from to server@srv-exim. 34 | # This is so when we redirect, we don't use user@srv-chasquid in the 35 | # envelope-from (we're not authorized to send mail on behalf of 36 | # @srv-chasquid). 37 | begin rewrite 38 | user@srv-chasquid server@srv-exim F 39 | 40 | # Forward all incoming email to chasquid (running on :1025 in this test). 41 | begin routers 42 | 43 | rewritedst: 44 | driver = redirect 45 | data = someone@srv-chasquid 46 | 47 | forwardall: 48 | driver = accept 49 | transport = tochasquid 50 | 51 | begin transports 52 | 53 | tochasquid: 54 | driver = smtp 55 | 56 | # exim4 will by default detect and special-case deliveries to localhost; 57 | # this avoids that behaviour and tells it to go ahead anyway. 58 | allow_localhost 59 | hosts_override 60 | 61 | # chasquid will be listening on localhost:1025 62 | hosts = localhost 63 | port = 1025 64 | 65 | # Add headers to help debug failures. 66 | delivery_date_add 67 | envelope_to_add 68 | return_path_add 69 | -------------------------------------------------------------------------------- /test/t-02-exim/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-02-exim/get-exim4-debian.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script downloads the exim4 binary from Debian's package. 4 | # It assumes "apt" is functional, which means it's not very portable, but 5 | # given the nature of these tests that's acceptable for now. 6 | 7 | set -e 8 | . "$(dirname "$0")/../util/lib.sh" 9 | 10 | init 11 | 12 | # Download and extract the package in .exim-bin 13 | apt download exim4-daemon-light 14 | dpkg -x exim4-daemon-light_*.deb "$PWD/.exim-bin/" 15 | 16 | # Create a symlink to .exim4, which is the directory we will use to store 17 | # configuration, spool, etc. 18 | # The configuration template will look for it here. 19 | mkdir -p .exim4 20 | ln -sf "$PWD/.exim-bin/usr/sbin/exim4" .exim4/ 21 | 22 | # Remove the setuid bit, if there is one - we don't need it and may cause 23 | # confusion and/or security troubles. 24 | chmod -s .exim-bin/usr/sbin/exim4 25 | 26 | success 27 | 28 | -------------------------------------------------------------------------------- /test/t-02-exim/hosts: -------------------------------------------------------------------------------- 1 | srv-chasquid localhost 2 | srv-exim localhost 3 | -------------------------------------------------------------------------------- /test/t-02-exim/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/srv-chasquid/fullchain.pem 3 | user user@srv-chasquid 4 | password secretpassword 5 | -------------------------------------------------------------------------------- /test/t-02-exim/zones: -------------------------------------------------------------------------------- 1 | # srv-chasquid and srv-exim to localhost. 2 | # Neither have an MX record, but that's okay. 3 | srv-chasquid A 127.0.0.1 4 | srv-chasquid AAAA ::1 5 | srv-exim A 127.0.0.1 6 | srv-exim AAAA ::1 7 | -------------------------------------------------------------------------------- /test/t-03-queue_persistency/addtoqueue.go: -------------------------------------------------------------------------------- 1 | // addtoqueue is a test helper which adds a queue item directly to the queue 2 | // directory, behind chasquid's back. 3 | // 4 | // Note that chasquid does NOT support this, we do it before starting up the 5 | // daemon for testing purposes only. 6 | // 7 | //go:build ignore 8 | // +build ignore 9 | 10 | package main 11 | 12 | import ( 13 | "flag" 14 | "fmt" 15 | "io" 16 | "os" 17 | "time" 18 | 19 | "blitiri.com.ar/go/chasquid/internal/queue" 20 | ) 21 | 22 | var ( 23 | queueDir = flag.String("queue_dir", ".queue", "queue directory") 24 | id = flag.String("id", "mid1234", "Message ID") 25 | from = flag.String("from", "from", "Mail from") 26 | rcpt = flag.String("rcpt", "rcpt", "Rcpt to") 27 | ) 28 | 29 | func main() { 30 | flag.Parse() 31 | 32 | data, err := io.ReadAll(os.Stdin) 33 | if err != nil { 34 | fmt.Printf("error reading data: %v\n", err) 35 | os.Exit(1) 36 | } 37 | 38 | item := &queue.Item{ 39 | Message: queue.Message{ 40 | ID: *id, 41 | From: *from, 42 | To: []string{*rcpt}, 43 | Rcpt: []*queue.Recipient{ 44 | { 45 | Address: *rcpt, 46 | Type: queue.Recipient_EMAIL, 47 | Status: queue.Recipient_PENDING, 48 | }, 49 | }, 50 | Data: data, 51 | }, 52 | CreatedAt: time.Now(), 53 | } 54 | 55 | os.MkdirAll(*queueDir, 0700) 56 | err = item.WriteTo(*queueDir) 57 | if err != nil { 58 | fmt.Printf("error writing item: %v\n", err) 59 | os.Exit(1) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/t-03-queue_persistency/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-03-queue_persistency/config/domains/testserver/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/test/t-03-queue_persistency/config/domains/testserver/.gitignore -------------------------------------------------------------------------------- /test/t-03-queue_persistency/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-03-queue_persistency/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | 8 | # Add an item to the queue before starting chasquid. 9 | go run addtoqueue.go --queue_dir=.data/queue \ 10 | --from someone@testserver \ 11 | --rcpt someone@testserver \ 12 | < content 13 | 14 | generate_certs_for testserver 15 | 16 | mkdir -p .logs 17 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config & 18 | wait_until_ready 1025 19 | 20 | # Check that the item in the queue was delivered. 21 | wait_for_file .mail/someone@testserver 22 | 23 | mail_diff content .mail/someone@testserver 24 | 25 | success 26 | -------------------------------------------------------------------------------- /test/t-04-aliases/.gitignore: -------------------------------------------------------------------------------- 1 | config/hooks/alias-resolve 2 | -------------------------------------------------------------------------------- /test/t-04-aliases/alias-resolve-hook: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case "$1" in 4 | "vicuña@testserver") 5 | # Test one naked, one full. These exist in the static aliases file. 6 | echo pepe, joan@testserver 7 | ;; 8 | "vic.uña+abc@testserver") 9 | echo uña 10 | ;; 11 | "ñandú@testserver") 12 | echo "| writemailto ../.data/pipe_alias_worked" 13 | ;; 14 | "roto@testserver") 15 | exit 1 16 | ;; 17 | esac 18 | -------------------------------------------------------------------------------- /test/t-04-aliases/chasquid-util.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Wrapper so chamuyero scripts can invoke chasquid-util for testing. 3 | 4 | # Run from the config directory because data_dir is relative. 5 | cd config || exit 1 6 | ../../../cmd/chasquid-util/chasquid-util -C=. "$@" 7 | -------------------------------------------------------------------------------- /test/t-04-aliases/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | 12 | suffix_separators: "+-" 13 | drop_characters: "._" 14 | -------------------------------------------------------------------------------- /test/t-04-aliases/config/domains/testserver/aliases: -------------------------------------------------------------------------------- 1 | 2 | # Easy aliases. 3 | pepe: jose 4 | joan: juan 5 | 6 | # UTF-8 aliases. 7 | pitanga: ñangapirí 8 | añil: azul, índigo 9 | 10 | # Pipe aliases. 11 | tubo: | writemailto ../.data/pipe_alias_worked 12 | 13 | -------------------------------------------------------------------------------- /test/t-04-aliases/content: -------------------------------------------------------------------------------- 1 | From: user@testserver 2 | Subject: Prueba desde el test 3 | 4 | Crece desde el test el futuro 5 | Crece desde el test 6 | -------------------------------------------------------------------------------- /test/t-04-aliases/hosts: -------------------------------------------------------------------------------- 1 | testserver localhost 2 | -------------------------------------------------------------------------------- /test/t-04-aliases/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/testserver/fullchain.pem 3 | user user@testserver 4 | password secretpassword 5 | -------------------------------------------------------------------------------- /test/t-04-aliases/test_chasquid_util.cmy: -------------------------------------------------------------------------------- 1 | # Resolve an unknown user. 2 | c = ./chasquid-util.sh aliases-resolve anunknownuser@blah 3 | c <- (email) anunknownuser@blah 4 | c wait 0 5 | 6 | # Resolve a known alias. 7 | c = ./chasquid-util.sh aliases-resolve a.ñi_l-blah@testserver 8 | c <- (email) azul@testserver 9 | c <- (email) índigo@testserver 10 | c wait 0 11 | 12 | # Resolve a pipe alias. 13 | c = ./chasquid-util.sh aliases-resolve tubo@testserver 14 | c <- (pipe) writemailto ../.data/pipe_alias_worked 15 | c wait 0 16 | 17 | # Resolve aliases that are exposed via the hook. 18 | c = ./chasquid-util.sh aliases-resolve vicuña@testserver 19 | c <- (email) jose@testserver 20 | c <- (email) juan@testserver 21 | c wait 0 22 | 23 | # The hook for this alias exits with error. 24 | c = ./chasquid-util.sh aliases-resolve roto@testserver 25 | c <- Error resolving: exit status 1 26 | c wait 1 27 | 28 | -------------------------------------------------------------------------------- /test/t-05-null_address/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | hostname: "testserver" 2 | 3 | smtp_address: ":1025" 4 | submission_address: ":1587" 5 | submission_over_tls_address: ":1465" 6 | monitoring_address: ":1099" 7 | 8 | mail_delivery_agent_bin: "test-mda" 9 | mail_delivery_agent_args: "%to%" 10 | 11 | data_dir: "../.data" 12 | mail_log_path: "../.logs/mail_log" 13 | 14 | suffix_separators: "+-" 15 | drop_characters: "._" 16 | -------------------------------------------------------------------------------- /test/t-05-null_address/config/domains/testserver/aliases: -------------------------------------------------------------------------------- 1 | 2 | fail: | false 3 | 4 | -------------------------------------------------------------------------------- /test/t-05-null_address/content: -------------------------------------------------------------------------------- 1 | From: Mailer daemon 2 | Subject: I've come to haunt you 3 | Message-ID: 4 | 5 | Ñañañañaña! 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/t-05-null_address/expected_dsr: -------------------------------------------------------------------------------- 1 | From user@testserver 2 | From: Mail Delivery System 3 | To: 4 | Subject: Mail delivery failed: returning message to sender 5 | Message-ID: * 6 | Date: * 7 | In-Reply-To: 8 | References: 9 | X-Failed-Recipients: fail@testserver, 10 | Auto-Submitted: auto-replied 11 | MIME-Version: 1.0 12 | Content-Type: multipart/report; report-type=delivery-status; 13 | boundary="???????????" 14 | 15 | 16 | --??????????? 17 | Content-Type: text/plain; charset="utf-8" 18 | Content-Disposition: inline 19 | Content-Description: Notification 20 | Content-Transfer-Encoding: 8bit 21 | 22 | Delivery of your message to the following recipient(s) failed permanently: 23 | 24 | - fail@testserver 25 | 26 | 27 | Technical details: 28 | - "false" (PIPE) failed permanently with error: 29 | exit status 1 30 | 31 | 32 | --??????????? 33 | Content-Type: message/global-delivery-status 34 | Content-Description: Delivery Report 35 | Content-Transfer-Encoding: 8bit 36 | 37 | Reporting-MTA: dns; testserver 38 | 39 | Original-Recipient: utf-8; fail@testserver 40 | Final-Recipient: utf-8; false 41 | Action: failed 42 | Status: 5.0.0 43 | Diagnostic-Code: smtp; exit status 1 44 | 45 | 46 | 47 | --??????????? 48 | Content-Type: message/rfc822 49 | Content-Description: Undelivered Message 50 | Content-Transfer-Encoding: 8bit 51 | 52 | Received: from localhost 53 | by testserver (chasquid) with ESMTPSA 54 | tls * 55 | (over * 56 | ; * 57 | From: Mailer daemon 58 | Subject: I've come to haunt you 59 | Message-Id: 60 | 61 | Ñañañañaña! 62 | 63 | 64 | 65 | 66 | --???????????-- 67 | -------------------------------------------------------------------------------- /test/t-05-null_address/hosts: -------------------------------------------------------------------------------- 1 | testserver localhost 2 | -------------------------------------------------------------------------------- /test/t-05-null_address/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | check_hostaliases 8 | 9 | generate_certs_for testserver 10 | add_user user@testserver secretpassword 11 | 12 | mkdir -p .logs 13 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config & 14 | wait_until_ready 1025 15 | 16 | 17 | # Send mail with an empty address (directly, unauthenticated). 18 | chamuyero sendmail.cmy > .logs/chamuyero 2>&1 19 | wait_for_file .mail/user@testserver 20 | mail_diff content .mail/user@testserver 21 | rm -f .mail/user@testserver 22 | 23 | 24 | # Test that we get mail back for a failed delivery 25 | smtpc fail@testserver < content 26 | wait_for_file .mail/user@testserver 27 | mail_diff expected_dsr .mail/user@testserver 28 | 29 | 30 | success 31 | -------------------------------------------------------------------------------- /test/t-05-null_address/sendmail.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> From: Mailer daemon 14 | c -> Subject: I've come to haunt you 15 | c -> Message-ID: 16 | c -> 17 | c -> Ñañañañaña! 18 | c -> 19 | c -> 20 | c -> . 21 | c <~ 250 22 | c -> QUIT 23 | c <~ 221 24 | 25 | -------------------------------------------------------------------------------- /test/t-05-null_address/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/testserver/fullchain.pem 3 | user user@testserver 4 | password secretpassword 5 | -------------------------------------------------------------------------------- /test/t-06-idna/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the configuration domain directories. 2 | ?/domains 3 | -------------------------------------------------------------------------------- /test/t-06-idna/A/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-A" 10 | mail_log_path: "../.logs/mail_log-A" 11 | -------------------------------------------------------------------------------- /test/t-06-idna/B/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":2025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-B" 10 | mail_log_path: "../.logs/mail_log-B" 11 | -------------------------------------------------------------------------------- /test/t-06-idna/from_A_to_B: -------------------------------------------------------------------------------- 1 | From: ñangapirí@srv-ñ 2 | To: pingüino@srv-ü 3 | Subject: Hola amigo pingüino! 4 | 5 | Que tal va la vida? 6 | -------------------------------------------------------------------------------- /test/t-06-idna/from_B_to_A: -------------------------------------------------------------------------------- 1 | From: pingüino@srv-ü 2 | To: ñangapirí@srv-ñ 3 | Subject: Feliz primavera! 4 | 5 | Espero que florezcas feliz! 6 | -------------------------------------------------------------------------------- /test/t-06-idna/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | check_hostaliases 8 | 9 | rm -rf .data-A .data-B .mail 10 | 11 | # Build with the DNS override, so we can fake DNS records. 12 | export GOTAGS="dnsoverride" 13 | 14 | # Launch minidns in the background using our configuration. 15 | minidns_bg --addr=":9053" -zones=zones >> .minidns.log 2>&1 16 | 17 | # Two servers: 18 | # A - listens on :1025, hosts srv-ñ 19 | # B - listens on :2015, hosts srv-ü 20 | 21 | CONFDIR=A generate_certs_for srv-ñ 22 | CONFDIR=A add_user ñangapirí@srv-ñ antaño 23 | CONFDIR=A add_user nadaa@nadaA nadaA 24 | 25 | CONFDIR=B generate_certs_for srv-ü 26 | CONFDIR=B add_user pingüino@srv-ü velóz 27 | CONFDIR=B add_user nadab@nadaB nadaB 28 | 29 | mkdir -p .logs-A .logs-B 30 | 31 | chasquid -v=2 --logfile=.logs-A/chasquid.log --config_dir=A \ 32 | --testing__dns_addr=127.0.0.1:9053 \ 33 | --testing__outgoing_smtp_port=2025 & 34 | chasquid -v=2 --logfile=.logs-B/chasquid.log --config_dir=B \ 35 | --testing__dns_addr=127.0.0.1:9053 \ 36 | --testing__outgoing_smtp_port=1025 & 37 | 38 | wait_until_ready 1465 39 | wait_until_ready 2465 40 | wait_until_ready 9053 41 | 42 | # Send from A to B. 43 | smtpc --addr=localhost:1465 --user=nadaA@nadaA --password=nadaA \ 44 | --server_cert=A/certs/srv-ñ/fullchain.pem \ 45 | pingüino@srv-ü < from_A_to_B 46 | 47 | wait_for_file .mail/pingüino@srv-ü 48 | mail_diff from_A_to_B .mail/pingüino@srv-ü 49 | 50 | # Send from B to A. 51 | smtpc --addr=localhost:2465 --user=nadaB@nadaB --password=nadaB \ 52 | --server_cert=B/certs/srv-ü/fullchain.pem \ 53 | ñangapirí@srv-ñ < from_B_to_A 54 | 55 | wait_for_file .mail/ñangapirí@srv-ñ 56 | mail_diff from_B_to_A .mail/ñangapirí@srv-ñ 57 | 58 | success 59 | -------------------------------------------------------------------------------- /test/t-06-idna/zones: -------------------------------------------------------------------------------- 1 | xn--srv--3ra A 127.0.0.1 2 | xn--srv--3ra AAAA ::1 3 | xn--srv--jqa A 127.0.0.1 4 | xn--srv--jqa AAAA ::1 5 | -------------------------------------------------------------------------------- /test/t-07-smtputf8/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-07-smtputf8/content: -------------------------------------------------------------------------------- 1 | From: ñandú@ñoÑos 2 | To: Ñangapirí@Ñoños 3 | Subject: Arañando el test 4 | 5 | Crece desde el test el futuro 6 | Crece desde el test 7 | -------------------------------------------------------------------------------- /test/t-07-smtputf8/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test UTF8 support, including usernames and domains. 4 | # Also test normalization: the destinations will have non-matching 5 | # capitalizations. 6 | 7 | set -e 8 | . "$(dirname "$0")/../util/lib.sh" 9 | 10 | init 11 | 12 | generate_certs_for ñoños 13 | 14 | # Intentionally have a config directory for upper case; this should be 15 | # normalized to lowercase internally (and match the cert accordingly). 16 | add_user ñandú@ñoñOS araño 17 | add_user ñangapirí@ñoñOS antaño 18 | 19 | mkdir -p .logs 20 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config & 21 | wait_until_ready 1465 22 | 23 | # Use a mix of upper and lower case in the from, to, and username, to check 24 | # normalization is well handled end-to-end. 25 | smtpc --addr=localhost:1465 \ 26 | --server_cert=config/certs/ñoños/fullchain.pem \ 27 | --user=ñanDÚ@ñoños --password=araño \ 28 | Ñangapirí@Ñoños < content 29 | 30 | # The MDA should see the normalized users and domains, in lower case. 31 | wait_for_file .mail/ñangapirí@ñoños 32 | mail_diff content .mail/ñangapirí@ñoños 33 | 34 | success 35 | -------------------------------------------------------------------------------- /test/t-09-loop/A/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-A" 10 | mail_log_path: "../.logs/mail_log-A" 11 | -------------------------------------------------------------------------------- /test/t-09-loop/A/domains/srv-A/aliases: -------------------------------------------------------------------------------- 1 | 2 | aliasA: aliasB@srv-B 3 | -------------------------------------------------------------------------------- /test/t-09-loop/B/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":2025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-B" 10 | mail_log_path: "../.logs/mail_log-B" 11 | -------------------------------------------------------------------------------- /test/t-09-loop/B/domains/srv-B/aliases: -------------------------------------------------------------------------------- 1 | aliasB: aliasA@srv-A 2 | -------------------------------------------------------------------------------- /test/t-09-loop/content: -------------------------------------------------------------------------------- 1 | From: userA@srv-A 2 | To: aliasB@srv-B 3 | Subject: Los espejos 4 | 5 | Yo que sentí el horror de los espejos 6 | no sólo ante el cristal impenetrable 7 | donde acaba y empieza, inhabitable, 8 | un imposible espacio de reflejos 9 | 10 | -------------------------------------------------------------------------------- /test/t-09-loop/hosts: -------------------------------------------------------------------------------- 1 | srv-A localhost 2 | srv-B localhost 3 | -------------------------------------------------------------------------------- /test/t-09-loop/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert A/certs/srv-A/fullchain.pem 3 | user userA@srv-A 4 | password userA 5 | -------------------------------------------------------------------------------- /test/t-09-loop/zones: -------------------------------------------------------------------------------- 1 | srv-a A 127.0.0.1 2 | srv-a AAAA ::1 3 | srv-b A 127.0.0.1 4 | srv-b AAAA ::1 5 | -------------------------------------------------------------------------------- /test/t-10-hooks/.gitignore: -------------------------------------------------------------------------------- 1 | config/hooks/post-data 2 | -------------------------------------------------------------------------------- /test/t-10-hooks/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-10-hooks/config/hooks/post-data.bad1: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $0 > ../.data/post-data.out 4 | echo "This is not a header" 5 | 6 | -------------------------------------------------------------------------------- /test/t-10-hooks/config/hooks/post-data.bad2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $0 > ../.data/post-data.out 4 | 5 | echo "X-Post-DATA: This starts like a header" 6 | echo 7 | echo "But then is not" 8 | 9 | -------------------------------------------------------------------------------- /test/t-10-hooks/config/hooks/post-data.bad3: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $0 > ../.data/post-data.out 4 | 5 | # Just a newline is quite problematic, as it would break the headers. 6 | echo 7 | 8 | -------------------------------------------------------------------------------- /test/t-10-hooks/config/hooks/post-data.bad4: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo $0 > ../.data/post-data.out 4 | 5 | echo -n "X-Post-DATA: valid header with no newline at the end" 6 | -------------------------------------------------------------------------------- /test/t-10-hooks/config/hooks/post-data.good: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | env > ../.data/post-data.out 4 | echo >> ../.data/post-data.out 5 | 6 | cat >> ../.data/post-data.out 7 | 8 | if [ "$RCPT_TO" == "blockme@testserver" ]; then 9 | echo "¡No pasarán!" 10 | exit 1 11 | fi 12 | 13 | if [ "$RCPT_TO" == "permanent@testserver" ]; then 14 | echo "Nos hacemos la permanente" 15 | exit 20 # permanent 16 | fi 17 | 18 | echo "X-Post-Data: success" 19 | echo "X-Post-Data-Multiline: multiline" 20 | echo " header for testing." 21 | 22 | -------------------------------------------------------------------------------- /test/t-10-hooks/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-10-hooks/hosts: -------------------------------------------------------------------------------- 1 | testserver localhost 2 | -------------------------------------------------------------------------------- /test/t-10-hooks/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/testserver/fullchain.pem 3 | user user@testserver 4 | password secretpassword 5 | -------------------------------------------------------------------------------- /test/t-11-dovecot/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | 12 | dovecot_auth: true 13 | dovecot_userdb_path: "/tmp/chasquid-dovecot-test/run/auth-userdb" 14 | dovecot_client_path: "/tmp/chasquid-dovecot-test/run/auth-client" 15 | -------------------------------------------------------------------------------- /test/t-11-dovecot/config/domains/srv/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/test/t-11-dovecot/config/domains/srv/.keep -------------------------------------------------------------------------------- /test/t-11-dovecot/config/dovecot.conf.in: -------------------------------------------------------------------------------- 1 | base_dir = $ROOT/run/ 2 | state_dir = $ROOT/lib/ 3 | log_path = $ROOT/dovecot.log 4 | ssl = no 5 | 6 | default_internal_user = $USER 7 | default_internal_group = $USER 8 | default_login_user = $USER 9 | 10 | # Before auth checks, rename "u@d" to "u-x". This exercises that chasquid 11 | # handles well the case where the returned user information does not match the 12 | # requested user. 13 | # We drop the domain, to exercise "naked" auth handling. 14 | auth_username_format = "%n-x" 15 | 16 | # Disable authentication penalty, since we intentionally make failed requests 17 | # and it just slows down tests. 18 | auth_failure_delay = 0 19 | 20 | passdb { 21 | driver = passwd-file 22 | args = $ROOT/passwd 23 | } 24 | 25 | userdb { 26 | driver = passwd-file 27 | args = $ROOT/passwd 28 | } 29 | 30 | service auth { 31 | unix_listener auth { 32 | mode = 0666 33 | } 34 | } 35 | 36 | # Dovecot refuses to start without protocols, so we need to give it one. 37 | protocols = imap 38 | 39 | service imap-login { 40 | chroot = 41 | inet_listener imap { 42 | address = 127.0.0.1 43 | port = 0 44 | } 45 | } 46 | 47 | service anvil { 48 | chroot = 49 | } 50 | 51 | # Turn on debugging information, to help troubleshooting issues. 52 | auth_verbose = yes 53 | auth_debug = yes 54 | auth_debug_passwords = yes 55 | auth_verbose_passwords = yes 56 | mail_debug = yes 57 | -------------------------------------------------------------------------------- /test/t-11-dovecot/config/passwd: -------------------------------------------------------------------------------- 1 | user-x:{plain}password:1000:1000::/home/user 2 | naked-x:{plain}gun:1001:1001::/home/naked 3 | -------------------------------------------------------------------------------- /test/t-11-dovecot/content: -------------------------------------------------------------------------------- 1 | From: user@srv 2 | Subject: Prueba desde el test 3 | 4 | Crece desde el test el futuro 5 | Crece desde el test 6 | -------------------------------------------------------------------------------- /test/t-11-dovecot/hosts: -------------------------------------------------------------------------------- 1 | srv localhost 2 | -------------------------------------------------------------------------------- /test/t-11-dovecot/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/srv/fullchain.pem 3 | user user@srv 4 | password password 5 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/auth_multi_dialog.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tls_connect localhost:1465 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | 8 | c -> AUTH SOMETHINGELSE 9 | c <~ 534 10 | 11 | c -> AUTH PLAIN 12 | c <~ 334 13 | c -> dXNlckB0ZXN0c2VydmVyAHlalala== 14 | c <~ 501 5.5.2 Error decoding AUTH response 15 | 16 | # Reconnect to avoid getting rejected due to too many errors. 17 | c close 18 | c tls_connect localhost:1465 19 | c <~ 220 20 | c -> EHLO localhost 21 | c <... 250 HELP 22 | 23 | c -> AUTH PLAIN 24 | c <~ 334 25 | c -> dXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgB3cm9uZ3Bhc3N3b3Jk 26 | c <~ 535 5.7.8 Incorrect user or password 27 | 28 | c -> AUTH PLAIN 29 | c <~ 334 30 | c -> dXNlckB0ZXN0c2VydmVyAHVzZXJAdGVzdHNlcnZlcgBzZWNyZXRwYXNzd29yZA== 31 | c <~ 235 2.7.0 Authentication successful 32 | 33 | c -> AUTH PLAIN 34 | c <~ 503 5.5.1 You are already wearing that! 35 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/auth_not_tls.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> AUTH PLAIN 8 | c <- 503 5.7.10 You feel vulnerable 9 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/auth_too_many_failures.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tls_connect localhost:1465 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | 8 | c -> AUTH PLAIN something 9 | c <~ 501 10 | c -> AUTH PLAIN something 11 | c <~ 501 12 | c -> AUTH PLAIN something 13 | c <~ 421 4.5.0 Too many errors, bye 14 | 15 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/bad_data.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | 6 | c -> DATA 7 | c <- 503 5.5.1 Invisible customers are not welcome! 8 | 9 | c -> HELO localhost 10 | c <~ 250 11 | c -> DATA 12 | c <- 503 5.5.1 Sender not yet given 13 | 14 | # Reconnect to avoid getting rejected due to too many errors. 15 | c close 16 | c tcp_connect localhost:1025 17 | c <~ 220 18 | c -> HELO localhost 19 | c <~ 250 20 | 21 | c -> MAIL FROM: 22 | c <~ 250 23 | c -> RCPT TO: user@testserver 24 | c <~ 250 25 | c -> DATA 26 | c <~ 354 27 | c -> From: Mailer daemon 28 | c -> Subject: I've come to haunt you 29 | c -> Bad header 30 | c -> 31 | c -> Muahahahaha 32 | c -> 33 | c -> 34 | c -> . 35 | c <~ 554 5.6.0 Error parsing message 36 | 37 | c -> QUIT 38 | c <~ 221 39 | 40 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/bad_data_dot.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> From: Mailer daemon 14 | c -> Subject: I've come to haunt you 15 | c -> 16 | c -> Muahahahaha 17 | c -> 18 | 19 | # An MTA must not accept isolated line breaks, otherwise it may fall victim to 20 | # an SMTP smuggling attack. See readUntilDot for more details. 21 | # This test triggers that condition with an invalid dot-ending, so we verify 22 | # the server returns an error in this case. 23 | c ~> '.\n' 24 | 25 | c -> That was a bad line ending, this is a good one. 26 | c ~> 'xxx\r\n.\r\n' 27 | 28 | c <- 521 5.5.2 Error reading DATA: invalid line ending 29 | 30 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/bad_data_dot_2.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> From: Mailer daemon 14 | c -> Subject: I've come to haunt you 15 | c -> 16 | c -> Muahahahaha 17 | c -> 18 | 19 | # An MTA must not accept isolated line breaks, otherwise it may fall victim to 20 | # an SMTP smuggling attack. See readUntilDot for more details. 21 | # This test triggers that condition with an invalid dot-ending, so we verify 22 | # the server returns an error in this case. 23 | c ~> 'xxx\n.\n' 24 | 25 | c -> That was a bad line ending, this is a good one. 26 | c ~> '\r\n.\r\n' 27 | 28 | c <- 521 5.5.2 Error reading DATA: invalid line ending 29 | 30 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/bad_data_dot_on_message_too_big.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> Subject: Message too big 14 | c -> 15 | 16 | # Max message size is 1 MiB. Note this includes line endings but converted to 17 | # \n (as per textproto.DotReader), and excluding the final ".". 18 | # We already sent (in the header) 26. 19 | # Send lines of len 900 to stay under the limit. 20 | # (1024 * 1024 - 26) - (900 * 1165) = 50 21 | c ~> ('a' * 899 + '\r\n') * 1165 22 | 23 | # We have 50 characters left before the message is too big. 24 | c ~> 'b' * 55 + '\r\n' 25 | 26 | # At this point the message is too big. The remainder data should be 27 | # discarded. 28 | # We use a "bad ." to try to do an SMTP smuggling attack. 29 | c ~> '.\n' 30 | c -> HELP 31 | c -> HELP 32 | 33 | # And now the "good .". 34 | c -> . 35 | 36 | c <- 521 5.5.2 Error reading DATA: invalid line ending 37 | 38 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/bad_mail_from.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> HELO localhost 6 | c <~ 250 7 | c -> MAIL LALA: <> 8 | c <- 500 5.5.2 Unknown command 9 | 10 | c -> MAIL FROM: 11 | c <~ 500 12 | 13 | # Reconnect to avoid getting rejected due to too many errors. 14 | c close 15 | c tcp_connect localhost:1025 16 | c <~ 220 17 | c -> HELO localhost 18 | c <~ 250 19 | 20 | c -> MAIL FROM: 21 | c <~ 501 22 | 23 | c -> MAIL FROM: 24 | c <- 501 5.1.8 Malformed sender domain (IDNA conversion failed) 25 | 26 | # Reconnect to avoid getting rejected due to too many errors. 27 | c close 28 | c tcp_connect localhost:1025 29 | c <~ 220 30 | c -> HELO localhost 31 | c <~ 250 32 | 33 | c -> MAIL FROM: 34 | c <- 501 5.1.7 Sender address too long 35 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/bad_rcpt_to.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> HELO localhost 6 | c <~ 250 7 | c -> MAIL FROM: 8 | c <~ 250 9 | 10 | c -> RCPT LALA: <> 11 | c <- 500 5.5.2 Unknown command 12 | 13 | c -> RCPT TO: 14 | c <~ 500 15 | 16 | # Reconnect to avoid getting rejected due to too many errors. 17 | c close 18 | c tcp_connect localhost:1025 19 | c <~ 220 20 | c -> HELO localhost 21 | c <~ 250 22 | c -> MAIL FROM: 23 | c <~ 250 24 | 25 | c -> RCPT TO: 26 | c <~ 501 27 | 28 | c -> RCPT TO: 29 | c <- 501 5.1.2 Malformed destination domain (IDNA conversion failed) 30 | 31 | # Reconnect to avoid getting rejected due to too many errors. 32 | c close 33 | c tcp_connect localhost:1025 34 | c <~ 220 35 | c -> HELO localhost 36 | c <~ 250 37 | c -> MAIL FROM: 38 | c <~ 250 39 | 40 | c -> RCPT TO: 41 | c <- 550 5.1.3 Destination address is invalid 42 | 43 | c -> RCPT TO: 44 | c <- 501 5.1.3 Destination address too long 45 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | hostname: "testserver" 2 | 3 | smtp_address: ":1025" 4 | submission_address: ":1587" 5 | submission_over_tls_address: ":1465" 6 | monitoring_address: ":1099" 7 | 8 | mail_delivery_agent_bin: "test-mda" 9 | mail_delivery_agent_args: "%to%" 10 | 11 | data_dir: "../.data" 12 | mail_log_path: "../.logs/mail_log" 13 | 14 | suffix_separators: "+-" 15 | drop_characters: "._" 16 | 17 | # Small max data size so we can reach it more easily in the tests. 18 | max_data_size_mb: 1 19 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/config/domains/testserver/aliases: -------------------------------------------------------------------------------- 1 | 2 | fail: | false 3 | 4 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/data_dot_stuffing.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> .From: Mailer daemon 14 | c -> Subject: I've come to haunt you 15 | c -> 16 | c -> .Muahahahaha 17 | c -> 18 | c -> ..x 19 | c -> 20 | c -> .. 21 | c -> 22 | c -> .This is stuffy. 23 | c -> 24 | c -> . 25 | c <~ 250 26 | c -> QUIT 27 | c <~ 221 28 | 29 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/data_dot_stuffing.cmy.verify: -------------------------------------------------------------------------------- 1 | From: Mailer daemon 2 | Subject: I've come to haunt you 3 | 4 | Muahahahaha 5 | 6 | .x 7 | 8 | . 9 | 10 | This is stuffy. 11 | 12 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/empty_helo.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> HELO 6 | c <~ 501 7 | c -> EHLO 8 | c <~ 501 9 | c -> HELO localhost 10 | c <~ 250 11 | 12 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/helo.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> HELO localhost 6 | c <~ 250 7 | c -> QUIT 8 | c <~ 221 9 | 10 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/line_too_long.cmy: -------------------------------------------------------------------------------- 1 | c tcp_connect localhost:1025 2 | c <~ 220 3 | 4 | c -> HELO aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1aaaaaaaaa1 5 | c <~ 554 6 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/message_too_big.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> Subject: Message too big 14 | c -> 15 | 16 | # Max message size is 1 MiB. Note this includes line endings but converted to 17 | # \n (as per textproto.DotReader), and excluding the final ".". 18 | # We already sent (in the header) 26. 19 | # Send lines of len 900 to stay under the limit. 20 | # (1024 * 1024 - 26) - (900 * 1166) = -850 21 | c ~> ('a' * 899 + '\r\n') * 1166 22 | 23 | c -> . 24 | 25 | c <~ 552 5.3.4 Message too big 26 | c -> QUIT 27 | c <~ 221 28 | 29 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | 8 | generate_certs_for testserver 9 | add_user user@testserver secretpassword 10 | 11 | mkdir -p .logs .mbox 12 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config & 13 | wait_until_ready 1025 14 | 15 | FAILED=0 16 | for i in *.cmy; do 17 | if ! chamuyero "$i" > ".logs/$i.log" 2>&1 ; then 18 | echo "test $i failed, see .logs/$i.log" 19 | echo 20 | echo "last lines of the log:" 21 | tail -n 10 ".logs/$i.log" | sed 's/^/ /g' 22 | echo 23 | FAILED=1 24 | continue 25 | fi 26 | 27 | # Some tests do email delivery, this allows us to verify the results. 28 | if [ -f "$i.verify" ]; then 29 | wait_for_file .mail/user@testserver 30 | cp .mail/user@testserver ".mbox/$i.mbox" 31 | if ! mail_diff "$i.verify" .mail/user@testserver \ 32 | > ".mbox/$i.diff" ; 33 | then 34 | echo "test $i failed, because it had a mail diff" 35 | echo 36 | echo "mail diff:" 37 | sed 's/^/ /g' ".mbox/$i.diff" 38 | echo 39 | FAILED=1 40 | fi 41 | fi 42 | done 43 | 44 | if [ $FAILED == 1 ]; then 45 | fail "got at least one error" 46 | fi 47 | success 48 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/sendmail.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> MAIL FROM: <> 8 | c <~ 250 9 | c -> RCPT TO: user@testserver 10 | c <~ 250 11 | c -> DATA 12 | c <~ 354 13 | c -> From: Mailer daemon 14 | c -> Subject: I've come to haunt you 15 | c -> 16 | c -> Muahahahaha 17 | c -> 18 | c -> 19 | c -> . 20 | c <~ 250 21 | c -> QUIT 22 | c <~ 221 23 | 24 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/sendmail.cmy.verify: -------------------------------------------------------------------------------- 1 | From: Mailer daemon 2 | Subject: I've come to haunt you 3 | 4 | Muahahahaha 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/unknown_command.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | 4 | c <~ 220 5 | c -> EHLO localhost 6 | c <... 250 HELP 7 | c -> WHATISTHIS 8 | c <- 500 5.5.1 Unknown command 9 | -------------------------------------------------------------------------------- /test/t-12-minor_dialogs/wrong_proto.cmy: -------------------------------------------------------------------------------- 1 | 2 | c tcp_connect localhost:1025 3 | c <~ 220 4 | c -> GET /evil HTTP/1.1 5 | c <- 502 5.7.0 You hear someone cursing shoplifters 6 | 7 | c tcp_connect localhost:1025 8 | c <~ 220 9 | c -> POST /evil HTTP/1.1 10 | c <- 502 5.7.0 You hear someone cursing shoplifters 11 | 12 | c tcp_connect localhost:1025 13 | c <~ 220 14 | c -> CONNECT www.evil.com:80 HTTP/1.1 15 | c <- 502 5.7.0 You hear someone cursing shoplifters 16 | -------------------------------------------------------------------------------- /test/t-13-reload/.gitignore: -------------------------------------------------------------------------------- 1 | config/domains/testserver/aliases 2 | -------------------------------------------------------------------------------- /test/t-13-reload/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-13-reload/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-13-reload/hosts: -------------------------------------------------------------------------------- 1 | testserver localhost 2 | -------------------------------------------------------------------------------- /test/t-13-reload/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/testserver/fullchain.pem 3 | user someone@testserver 4 | password password222 5 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/A/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-A" 10 | mail_log_path: "../.logs-A/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/A/domains/srv-A/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/test/t-14-tls_tracking/A/domains/srv-A/.keep -------------------------------------------------------------------------------- /test/t-14-tls_tracking/B/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":2025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-B" 10 | mail_log_path: "../.logs-B/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/B/domains/srv-B/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/test/t-14-tls_tracking/B/domains/srv-B/.keep -------------------------------------------------------------------------------- /test/t-14-tls_tracking/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/hosts: -------------------------------------------------------------------------------- 1 | srv-A localhost 2 | srv-B localhost 3 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert A/certs/srv-A/fullchain.pem 3 | user userA@srv-A 4 | password userA 5 | -------------------------------------------------------------------------------- /test/t-14-tls_tracking/zones: -------------------------------------------------------------------------------- 1 | # srv-a zone 2 | srv-a A 127.0.0.1 3 | srv-a AAAA ::1 4 | srv-a MX 10 srv-a 5 | srv-a TXT v=spf1 a 6 | 7 | # srv-b zone 8 | srv-b A 127.0.0.1 9 | srv-b AAAA ::1 10 | srv-b MX 10 srv-b 11 | srv-b TXT v=spf1 a 12 | -------------------------------------------------------------------------------- /test/t-16-spf/A/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-A" 10 | mail_log_path: "../.logs-A/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-16-spf/B/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":2025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-B" 10 | mail_log_path: "../.logs-B/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-16-spf/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-16-spf/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-16-spf/expected_dsn: -------------------------------------------------------------------------------- 1 | From usera@srv-a 2 | From: Mail Delivery System 3 | To: 4 | Subject: Mail delivery failed: returning message to sender 5 | Message-ID: config/chasquid.conf 18 | 19 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config \ 20 | > .logs/stdout 2> .logs/stderr & 21 | wait_until_ready 1025 22 | 23 | smtpc someone@testserver < content 24 | wait_for_file .mail/someone@testserver 25 | mail_diff content .mail/someone@testserver 26 | 27 | pkill -s 0 chasquid 28 | sleep 0.2 29 | } 30 | 31 | export MAIL_LOG_PATH="../.logs/mail_log" 32 | send_one 33 | if ! grep -q "from=user@testserver all done" .logs/mail_log; then 34 | fail "entries not found in .logs/mail_log" 35 | fi 36 | 37 | export MAIL_LOG_PATH="" 38 | send_one 39 | if ! grep -q "from=user@testserver all done" .logs/stdout; then 40 | fail "entries not found in .logs/stdout" 41 | fi 42 | 43 | export MAIL_LOG_PATH="" 44 | send_one 45 | if ! grep -q "from=user@testserver all done" .logs/stderr; then 46 | fail "entries not found in .logs/stderr" 47 | fi 48 | 49 | success 50 | -------------------------------------------------------------------------------- /test/t-17-maillog/smtpc.conf: -------------------------------------------------------------------------------- 1 | addr localhost:1465 2 | server_cert config/certs/testserver/fullchain.pem 3 | user user@testserver 4 | password secretpassword 5 | -------------------------------------------------------------------------------- /test/t-18-haproxy/config/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":2025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data" 10 | mail_log_path: "../.logs/mail_log" 11 | 12 | haproxy_incoming: true 13 | -------------------------------------------------------------------------------- /test/t-18-haproxy/content: -------------------------------------------------------------------------------- 1 | Subject: Prueba desde el test 2 | 3 | Crece desde el test el futuro 4 | Crece desde el test 5 | -------------------------------------------------------------------------------- /test/t-18-haproxy/haproxy.cfg: -------------------------------------------------------------------------------- 1 | listen smtp-in 2 | mode tcp 3 | bind *:1025 4 | server srv1 localhost:2025 send-proxy 5 | timeout connect 10s 6 | timeout client 10s 7 | timeout server 10s 8 | -------------------------------------------------------------------------------- /test/t-18-haproxy/hosts: -------------------------------------------------------------------------------- 1 | testserver localhost 2 | -------------------------------------------------------------------------------- /test/t-18-haproxy/msmtprc: -------------------------------------------------------------------------------- 1 | account default 2 | 3 | host testserver 4 | port 1025 5 | 6 | tls on 7 | tls_trust_file config/certs/testserver/fullchain.pem 8 | 9 | from user@testserver 10 | 11 | auth on 12 | user user@testserver 13 | password secretpassword 14 | 15 | -------------------------------------------------------------------------------- /test/t-18-haproxy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | . "$(dirname "$0")/../util/lib.sh" 5 | 6 | init 7 | check_hostaliases 8 | 9 | mkdir -p .logs 10 | 11 | if ! haproxy -v > /dev/null; then 12 | skip "haproxy binary not found" 13 | fi 14 | 15 | # Set a 2m timeout: if there are issues with haproxy, the wait tends to hang 16 | # indefinitely, so an explicit timeout helps with test automation. 17 | timeout 2m 18 | 19 | # Launch haproxy in the background, checking config first to fail fast in that 20 | # case. 21 | haproxy -f haproxy.cfg -c 22 | haproxy -f haproxy.cfg -d > .logs/haproxy.log 2>&1 & 23 | 24 | generate_certs_for testserver 25 | add_user user@testserver secretpassword 26 | add_user someone@testserver secretpassword 27 | 28 | chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config & 29 | 30 | wait_until_ready 1025 # haproxy 31 | wait_until_ready 2025 # chasquid 32 | 33 | run_msmtp someone@testserver < content 34 | 35 | wait_for_file .mail/someone@testserver 36 | 37 | mail_diff content .mail/someone@testserver 38 | 39 | success 40 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/.gitignore: -------------------------------------------------------------------------------- 1 | !.expected-error 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-01-empty/.expected-error: -------------------------------------------------------------------------------- 1 | open c-01-empty/chasquid.conf: no such file or directory 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-02-all_dirs_missing/.expected-error: -------------------------------------------------------------------------------- 1 | open certs/: no such file or directory 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-02-all_dirs_missing/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-03-no_certs/.expected-error: -------------------------------------------------------------------------------- 1 | At least one valid certificate is needed 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-03-no_certs/certs/testserver/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/test/t-20-bad_configs/c-03-no_certs/certs/testserver/.keep -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-03-no_certs/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-03-no_certs/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-04-no_cert_dirs/.expected-error: -------------------------------------------------------------------------------- 1 | No entries found in "certs/" 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-04-no_cert_dirs/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-04-no_cert_dirs/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-05-no_addrs/.expected-error: -------------------------------------------------------------------------------- 1 | No address to listen on 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-05-no_addrs/chasquid.conf: -------------------------------------------------------------------------------- 1 | mail_delivery_agent_bin: "test-mda" 2 | mail_delivery_agent_args: "%to%" 3 | 4 | data_dir: "../.data" 5 | mail_log_path: "../.logs/mail_log" 6 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-05-no_addrs/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-06-bad_maillog/.expected-error: -------------------------------------------------------------------------------- 1 | Error opening mail log: open /sys/bad-dir/mail_log: no such file or directory 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-06-bad_maillog/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | 10 | # This is expected to be invalid, and impossible to mkdir. 11 | mail_log_path: "/sys/bad-dir/mail_log" 12 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-06-bad_maillog/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-07-bad_domain_info/.expected-error: -------------------------------------------------------------------------------- 1 | Error opening domain info database: mkdir ../data-c-07-bad_domain_info/domaininfo: not a directory 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-07-bad_domain_info/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../data-c-07-bad_domain_info" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-07-bad_domain_info/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-08-bad_sts_cache/.expected-error: -------------------------------------------------------------------------------- 1 | Failed to initialize STS cache: mkdir ../data-c-08-bad_sts_cache/sts-cache: not a directory 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-08-bad_sts_cache/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../data-c-08-bad_sts_cache" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-08-bad_sts_cache/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-09-bad_queue_dir/.expected-error: -------------------------------------------------------------------------------- 1 | Error initializing queue: mkdir ../data-c-09-bad_queue_dir/queue: not a directory 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-09-bad_queue_dir/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../data-c-09-bad_queue_dir" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-09-bad_queue_dir/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-10-empty_listening_addr/.expected-error: -------------------------------------------------------------------------------- 1 | Invalid empty listening address for submission 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-10-empty_listening_addr/chasquid.conf: -------------------------------------------------------------------------------- 1 | mail_delivery_agent_bin: "test-mda" 2 | mail_delivery_agent_args: "%to%" 3 | data_dir: "../.data" 4 | mail_log_path: "../.logs/mail_log" 5 | 6 | submission_address: "" 7 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-10-empty_listening_addr/domains/testserver/users: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albertito/chasquid/9999a690862eb57301a4e2a3aa5811b6a36800aa/test/t-20-bad_configs/c-10-empty_listening_addr/domains/testserver/users -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-11-bad_dkim_key/.expected-error: -------------------------------------------------------------------------------- 1 | DKIM loading error: error decoding PEM block 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-11-bad_dkim_key/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-11-bad_dkim_key/domains/testserver/dkim__selector.pem: -------------------------------------------------------------------------------- 1 | Bad key 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-12-bad_users/.expected-error: -------------------------------------------------------------------------------- 1 | users file error: open domains/testserver/users: permission denied 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-12-bad_users/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-12-bad_users/domains/testserver/users: -------------------------------------------------------------------------------- 1 | users: { 2 | key: "someone" 3 | value: { 4 | scrypt: { 5 | logN: 14 6 | r: 8 7 | p: 1 8 | keyLen: 32 9 | salt: "J\x01\xed7]\x02\n\xe9;z[\x8d˱\x10\xc1" 10 | encrypted: "\xa50宴\xcbb\xc1!r]K\xd1yI\xa2\x99\x8d\xdaQx\x8e69\xac\xf4$\x01\x11\x03\x8d\x10" 11 | } 12 | } 13 | } 14 | users: { 15 | key: "user" 16 | value: { 17 | scrypt: { 18 | logN: 14 19 | r: 8 20 | p: 1 21 | keyLen: 32 22 | salt: "\n\xc6\x1c\x8f\xb2\x0c\x15p\x8d\xa1\xc3\x05U6\xdb\xc4" 23 | encrypted: "\xc3\xe6B2\x84W\x1a\nq{\x07\xe0\x9c\x854\n\xac\xbc\xb7\x9c\x86Kyk\x8dj\x16\x1a\x8c$*N" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-13-bad_aliases/.expected-error: -------------------------------------------------------------------------------- 1 | aliases file error: open domains/testserver/aliases: permission denied 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-13-bad_aliases/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | 5 | mail_delivery_agent_bin: "test-mda" 6 | mail_delivery_agent_args: "%to%" 7 | 8 | data_dir: "../.data" 9 | mail_log_path: "../.logs/mail_log" 10 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/c-13-bad_aliases/domains/testserver/aliases: -------------------------------------------------------------------------------- 1 | a: b 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/data-c-07-bad_domain_info/domaininfo: -------------------------------------------------------------------------------- 1 | This is a file, not a directory as expected 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/data-c-08-bad_sts_cache/sts-cache: -------------------------------------------------------------------------------- 1 | This is a file, not a directory as expected 2 | -------------------------------------------------------------------------------- /test/t-20-bad_configs/data-c-09-bad_queue_dir/queue: -------------------------------------------------------------------------------- 1 | This is a file, not a directory as expected 2 | -------------------------------------------------------------------------------- /test/t-21-dkim/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the configuration domain directories. 2 | ?/domains 3 | -------------------------------------------------------------------------------- /test/t-21-dkim/A/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-A" 10 | mail_log_path: "../.logs-A/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-21-dkim/A/s1._domainkey.srv-a.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEID6bjSoiW6g6NJA67RNl0SZ7zpylVOq9w/VGAXF5whnS 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /test/t-21-dkim/B/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: ":2025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "%to%" 8 | 9 | data_dir: "../.data-B" 10 | mail_log_path: "../.logs-B/mail_log" 11 | -------------------------------------------------------------------------------- /test/t-21-dkim/from_A_to_B: -------------------------------------------------------------------------------- 1 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 2 | d=srv-a; s=s1; t=1709494311; 3 | h=from:subject:to:from:subject:date:to:cc:message-id; 4 | bh=0MIF2K4/fGA4bxV9yOwV0PQSZ3Glv67jLvQ8NwgjcKQ=; 5 | b=JkROrF9he5gqMhWcU47h6koleiwkz0IWcRV467KuzsMdTeWPMUVB+JDu+6HElBofdzNsz5 6 | Ptug637opt4UaAAg==; 7 | From: user-a@srv-a 8 | To: user-b@srv-b 9 | Subject: Hola amigo pingüino! 10 | 11 | Que tal va la vida? 12 | -------------------------------------------------------------------------------- /test/t-21-dkim/from_A_to_B.expected: -------------------------------------------------------------------------------- 1 | Authentication-Results: srv-b 2 | ;spf=none (no DNS record found) 3 | ;dkim=pass header.b=JkROrF9he5gq header.d=srv-a 4 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 5 | d=srv-a; s=s1; t=1709494311; 6 | h=from:subject:to:from:subject:date:to:cc:message-id; 7 | bh=0MIF2K4/fGA4bxV9yOwV0PQSZ3Glv67jLvQ8NwgjcKQ=; 8 | b=JkROrF9he5gqMhWcU47h6koleiwkz0IWcRV467KuzsMdTeWPMUVB+JDu+6HElBofdzNsz5 9 | Ptug637opt4UaAAg==; 10 | From: user-a@srv-a 11 | To: user-b@srv-b 12 | Subject: Hola amigo pingüino! 13 | 14 | Que tal va la vida? 15 | -------------------------------------------------------------------------------- /test/t-21-dkim/from_B_to_A: -------------------------------------------------------------------------------- 1 | From: user-b@srv-b 2 | To: user-a@srv-a 3 | Subject: Feliz primavera! 4 | 5 | Espero que florezcas feliz! 6 | -------------------------------------------------------------------------------- /test/t-21-dkim/from_B_to_A.expected: -------------------------------------------------------------------------------- 1 | From user-a@srv-a 2 | Authentication-Results: srv-a 3 | ;spf=none (no DNS record found) 4 | ;dkim=pass header.b=* 5 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; 6 | d=srv-b; s=sel77; * 7 | h=from:subject:to:from:subject:date:to:cc:message-id; 8 | bh=* 9 | b=* 10 | * 11 | * 12 | * 13 | * 14 | * 15 | * 16 | * 17 | From: user-b@srv-b 18 | To: user-a@srv-a 19 | Subject: Feliz primavera! 20 | 21 | Espero que florezcas feliz! 22 | -------------------------------------------------------------------------------- /test/t-21-dkim/zones: -------------------------------------------------------------------------------- 1 | srv-a A 127.0.0.1 2 | srv-a AAAA ::1 3 | srv-b A 127.0.0.1 4 | srv-b AAAA ::1 5 | 6 | s1._domainkey.srv-a TXT v=DKIM1; k=ed25519; p=SvoPT692bVrQBT8UNxt6SF538O3snA4fE3/i/glCxwQ= 7 | -------------------------------------------------------------------------------- /test/t-22-forward_via/.gitignore: -------------------------------------------------------------------------------- 1 | primary/**/users 2 | secondary/**/users 3 | external/**/users 4 | -------------------------------------------------------------------------------- /test/t-22-forward_via/content: -------------------------------------------------------------------------------- 1 | Subject: Los espejos 2 | 3 | Yo que sentí el horror de los espejos 4 | no sólo ante el cristal impenetrable 5 | donde acaba y empieza, inhabitable, 6 | un imposible espacio de reflejos 7 | 8 | -------------------------------------------------------------------------------- /test/t-22-forward_via/expected-chain-1: -------------------------------------------------------------------------------- 1 | Authentication-Results: primary 2 | ;spf=pass (matched mx) 3 | ;dkim=pass header.b=???????????? header.d=dodo 4 | Received-SPF: pass (matched mx) 5 | Received: from * 6 | by primary (chasquid) with ESMTPS 7 | tls TLS_* 8 | (over SMTP, TLS-1.3, envelope from "chain-1-4+fwd_from=chain-1-3+fwd_from=user222=dodo=kiwi@dodo") 9 | ; * 10 | Authentication-Results: secondary 11 | ;spf=pass (matched mx) 12 | ;dkim=pass header.b=???????????? header.d=dodo 13 | Received-SPF: pass (matched mx) 14 | Received: from * 15 | by secondary (chasquid) with ESMTPS 16 | tls TLS_* 17 | (over SMTP, TLS-1.3, envelope from "chain-1-3+fwd_from=user222=dodo@kiwi") 18 | ; * 19 | Authentication-Results: external 20 | ;spf=pass (matched mx) 21 | ;dkim=pass header.b=???????????? header.d=dodo 22 | Received-SPF: pass (matched mx) 23 | Received: from * 24 | by external (chasquid) with ESMTPS 25 | tls TLS_* 26 | (over SMTP, TLS-1.3, envelope from "user222@dodo") 27 | ; * 28 | Authentication-Results: primary 29 | ;spf=pass (matched mx) 30 | ;dkim=pass header.b=???????????? header.d=dodo 31 | Received-SPF: pass (matched mx) 32 | Received: from * 33 | by primary (chasquid) with ESMTPS 34 | tls TLS_* 35 | (over SMTP, TLS-1.3, envelope from "user222@dodo") 36 | ; * 37 | Received: from localhost 38 | by secondary (chasquid) with ESMTPSA 39 | tls TLS_* 40 | (over submission+TLS, TLS-1.3, envelope from "user222@dodo") 41 | ; * 42 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 43 | d=dodo; s=sel-secondary-1; t=* 44 | h=subject:from:subject:date:to:cc:message-id; 45 | bh=* 46 | b=* 47 | * 48 | Subject: Los espejos 49 | 50 | Yo que sentí el horror de los espejos 51 | no sólo ante el cristal impenetrable 52 | donde acaba y empieza, inhabitable, 53 | un imposible espacio de reflejos 54 | 55 | -------------------------------------------------------------------------------- /test/t-22-forward_via/expected-external-user333@kiwi: -------------------------------------------------------------------------------- 1 | Authentication-Results: external 2 | ;spf=pass (matched mx) 3 | ;dkim=pass header.b=???????????? header.d=dodo 4 | Received-SPF: pass (matched mx) 5 | Received: from * 6 | by external (chasquid) with ESMTPS 7 | tls TLS_* 8 | (over SMTP, TLS-1.3, envelope from "user222@dodo") 9 | ; * 10 | Received: from localhost 11 | by secondary (chasquid) with ESMTPSA 12 | tls TLS_* 13 | (over submission+TLS, TLS-1.3, envelope from "user222@dodo") 14 | ; * 15 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 16 | d=dodo; s=sel-secondary-1; t=* 17 | h=subject:from:subject:date:to:cc:message-id; 18 | bh=* 19 | b=* 20 | * 21 | Subject: Los espejos 22 | 23 | Yo que sentí el horror de los espejos 24 | no sólo ante el cristal impenetrable 25 | donde acaba y empieza, inhabitable, 26 | un imposible espacio de reflejos 27 | 28 | -------------------------------------------------------------------------------- /test/t-22-forward_via/expected-primary-user111@dodo: -------------------------------------------------------------------------------- 1 | Authentication-Results: primary 2 | ;spf=pass (matched mx) 3 | ;dkim=pass header.b=???????????? header.d=dodo 4 | Received-SPF: pass (matched mx) 5 | Received: from * 6 | by primary (chasquid) with ESMTPS 7 | tls TLS_* 8 | (over SMTP, TLS-1.3, envelope from "user222@dodo") 9 | ; * 10 | Received: from localhost 11 | by secondary (chasquid) with ESMTPSA 12 | tls TLS_* 13 | (over submission+TLS, TLS-1.3, envelope from "user222@dodo") 14 | ; * 15 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; 16 | d=dodo; s=sel-secondary-1; t=* 17 | h=subject:from:subject:date:to:cc:message-id; 18 | bh=* 19 | b=* 20 | * 21 | Subject: Los espejos 22 | 23 | Yo que sentí el horror de los espejos 24 | no sólo ante el cristal impenetrable 25 | donde acaba y empieza, inhabitable, 26 | un imposible espacio de reflejos 27 | 28 | -------------------------------------------------------------------------------- /test/t-22-forward_via/external/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: "127.0.0.20:1025" 2 | submission_address: ":3587" 3 | submission_over_tls_address: ":3465" 4 | monitoring_address: ":3099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "external:%to%" 8 | 9 | data_dir: "../.data-external" 10 | mail_log_path: "../.logs/external-mail_log" 11 | -------------------------------------------------------------------------------- /test/t-22-forward_via/external/domains/kiwi/aliases: -------------------------------------------------------------------------------- 1 | # Part 3 of chain-1 (see run.sh for the full structure). 2 | chain-1-3: chain-1-4@dodo via secondary 3 | -------------------------------------------------------------------------------- /test/t-22-forward_via/primary/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: "127.0.0.10:1025" 2 | submission_address: ":1587" 3 | submission_over_tls_address: ":1465" 4 | monitoring_address: ":1099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "primary:%to%" 8 | 9 | data_dir: "../.data-primary" 10 | mail_log_path: "../.logs/primary-mail_log" 11 | -------------------------------------------------------------------------------- /test/t-22-forward_via/primary/domains/dodo/aliases: -------------------------------------------------------------------------------- 1 | # Part 2 of chain-1 (see run.sh for the full structure). 2 | chain-1-2: chain-1-3@kiwi 3 | 4 | # Part 5 of chain-1 (see run.sh for the full structure). 5 | chain-1-5: user111@dodo 6 | -------------------------------------------------------------------------------- /test/t-22-forward_via/secondary/chasquid.conf: -------------------------------------------------------------------------------- 1 | smtp_address: "127.0.0.11:1025" 2 | submission_address: ":2587" 3 | submission_over_tls_address: ":2465" 4 | monitoring_address: ":2099" 5 | 6 | mail_delivery_agent_bin: "test-mda" 7 | mail_delivery_agent_args: "secondary:%to%" 8 | 9 | data_dir: "../.data-secondary" 10 | mail_log_path: "../.logs/secondary-mail_log" 11 | -------------------------------------------------------------------------------- /test/t-22-forward_via/secondary/domains/dodo/aliases: -------------------------------------------------------------------------------- 1 | # Part 1 of chain-1 (see run.sh for the full structure). 2 | chain-1-1: chain-1-2@dodo via primary 3 | 4 | # Part 4 chain-1 (see run.sh for the full structure). 5 | chain-1-4: chain-1-5@dodo via primary 6 | 7 | # Forward all email to the primary server. 8 | *: * via primary 9 | -------------------------------------------------------------------------------- /test/t-22-forward_via/smtpc-secondary.conf: -------------------------------------------------------------------------------- 1 | addr localhost:2465 2 | server_cert secondary/certs/secondary/fullchain.pem 3 | user user222@dodo 4 | password user222 5 | -------------------------------------------------------------------------------- /test/t-22-forward_via/zones: -------------------------------------------------------------------------------- 1 | primary A 127.0.0.10 2 | secondary A 127.0.0.11 3 | external A 127.0.0.20 4 | 5 | dodo MX 10 primary 6 | dodo MX 20 secondary 7 | 8 | # We need to use mx/8 because the source address will usually be 127.0.0.1, 9 | # not 127.0.0.10 or 127.0.0.11. 10 | # TODO: Once we support specifying a sender IP address, we should use that 11 | # and remove the /8. 12 | dodo TXT v=spf1 mx/8 -all 13 | 14 | kiwi MX 10 external 15 | kiwi TXT v=spf1 mx/8 -all 16 | -------------------------------------------------------------------------------- /test/util/check-hostaliases: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import tempfile 5 | import os 6 | import socket 7 | import subprocess 8 | import sys 9 | 10 | parser = argparse.ArgumentParser( 11 | description="Check that $HOSTALIASES is working" 12 | ) 13 | parser.add_argument( 14 | "--child", 15 | action="store_true", 16 | help="run in child mode, for internal use only", 17 | ) 18 | parser.add_argument( 19 | "-v", 20 | action="store_true", 21 | help="verbose mode", 22 | ) 23 | args = parser.parse_args() 24 | 25 | 26 | def dprint(*a): 27 | if args.v: 28 | print(*a) 29 | 30 | 31 | if args.child: 32 | dprint("child mode, getting hosts") 33 | lo = socket.gethostbyname("localhost") 34 | ts = socket.gethostbyname("testserver") 35 | dprint(ts, lo, ts == lo) 36 | if ts != lo: 37 | sys.exit(1) 38 | else: 39 | dprint("## parent mode") 40 | # Create the hostaliases file. 41 | fd = tempfile.NamedTemporaryFile(mode="w+") 42 | fd.write("testserver localhost\n") 43 | fd.flush() 44 | 45 | # Re-execute ourselves with --child, to do the check. 46 | env = dict(os.environ) 47 | env["HOSTALIASES"] = fd.name 48 | cargs = [sys.argv[0], "--child"] 49 | if args.v: 50 | cargs.append("-v") 51 | cmd = subprocess.run( 52 | cargs, 53 | env=env, 54 | text=True, 55 | encoding="ascii", 56 | stdout=subprocess.PIPE, 57 | stderr=subprocess.STDOUT, 58 | ) 59 | dprint("## child output:") 60 | dprint(cmd.stdout) 61 | dprint("## child returned", cmd.returncode) 62 | sys.exit(cmd.returncode) 63 | -------------------------------------------------------------------------------- /test/util/docker_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script that is used as a Docker entrypoint. 4 | # 5 | # It starts minidns with a zone resolving "localhost", and overrides 6 | # /etc/resolv.conf to use it. Then launches docker CMD. 7 | # 8 | # This is used for more hermetic Docker test environments. 9 | 10 | set -e 11 | . "$(dirname "$0")/../util/lib.sh" 12 | 13 | init 14 | 15 | # Go to the root of the repository. 16 | cd ../.. 17 | 18 | # Undo the EXIT trap, so minidns continues to run in the background. 19 | trap - EXIT 20 | 21 | set -v 22 | 23 | # The DNS server resolves only "localhost"; tests will rely on this, as we 24 | # $HOSTALIASES to point our test hostnames to localhost, so it needs to 25 | # resolve. 26 | echo " 27 | localhost A 127.0.0.1 28 | localhost AAAA ::1 29 | " > /tmp/zones 30 | 31 | start-stop-daemon --start --background \ 32 | --exec /tmp/minidns \ 33 | -- --zones=/tmp/zones 34 | 35 | echo "nameserver 127.0.0.1" > /etc/resolv.conf 36 | echo "nameserver ::1" >> /etc/resolv.conf 37 | 38 | # Wait until the minidns resolver comes up. 39 | wait_until_ready 53 40 | 41 | # Disable the Go proxy, since now there is no external network access. 42 | # Modules should be already be made available in the environment. 43 | export GOPROXY=off 44 | 45 | # Launch arguments, which come from docker CMD, as "chasquid" user. 46 | # Running tests as root makes some integration tests more difficult, as for 47 | # example Exim has hard-coded protections against running as root. 48 | sudo -u "chasquid" -g "chasquid" \ 49 | --set-home \ 50 | --preserve-env PATH="$PATH" \ 51 | -- "$@" 52 | -------------------------------------------------------------------------------- /test/util/exitcode: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exit $1 4 | 5 | -------------------------------------------------------------------------------- /test/util/test-mda: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir -p ${MDA_DIR} 6 | 7 | # TODO: use flock to lock the file, to prevent atomic writes. 8 | cat >> ${MDA_DIR}/.tmp-${1} 9 | X=$? 10 | if [ -e ${MDA_DIR}/.tmp-${1} ]; then 11 | mv ${MDA_DIR}/.tmp-${1} ${MDA_DIR}/${1} 12 | fi 13 | exit $X 14 | -------------------------------------------------------------------------------- /test/util/writemailto: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "From writemailto" > "$1" 4 | exec cat >> "$1" 5 | --------------------------------------------------------------------------------