From 4a0497043cc0715c9a62c652e9bff62df4aaf31e Mon Sep 17 00:00:00 2001 From: Gilles Chehade Date: Thu, 21 May 2020 21:06:26 +0200 Subject: tmp move --- smtpd/Makefile | 10 - smtpd/aliases.5 | 102 - smtpd/aliases.c | 234 --- smtpd/bounce.c | 820 -------- smtpd/ca.c | 777 -------- smtpd/cert.c | 416 ---- smtpd/compress_backend.c | 72 - smtpd/compress_gzip.c | 186 -- smtpd/config.c | 350 ---- smtpd/control.c | 817 -------- smtpd/crypto.c | 400 ---- smtpd/dict.c | 269 --- smtpd/dict.h | 48 - smtpd/dns.c | 379 ---- smtpd/enqueue.c | 932 --------- smtpd/envelope.c | 786 -------- smtpd/esc.c | 116 -- smtpd/expand.c | 332 ---- smtpd/filter.c | 868 --------- smtpd/forward.5 | 83 - smtpd/forward.c | 104 - smtpd/iobuf.c | 462 ----- smtpd/iobuf.h | 67 - smtpd/ioev.c | 1064 ----------- smtpd/ioev.h | 70 - smtpd/libressl.c | 213 --- smtpd/limit.c | 124 -- smtpd/lka.c | 914 --------- smtpd/lka_filter.c | 1746 ----------------- smtpd/lka_session.c | 556 ------ smtpd/log.c | 220 --- smtpd/log.h | 52 - smtpd/mail.lmtp.8 | 55 - smtpd/mail.lmtp.c | 332 ---- smtpd/mail.maildir.8 | 45 - smtpd/mail.maildir.c | 284 --- smtpd/mail.mboxfile.8 | 34 - smtpd/mail.mboxfile.c | 109 -- smtpd/mail.mda.8 | 35 - smtpd/mail.mda.c | 70 - smtpd/mail/Makefile | 20 - smtpd/mailaddr.c | 135 -- smtpd/makemap.8 | 174 -- smtpd/makemap.c | 521 ----- smtpd/mda.c | 919 --------- smtpd/mda_mbox.c | 94 - smtpd/mda_unpriv.c | 110 -- smtpd/mda_variables.c | 374 ---- smtpd/mproc.c | 676 ------- smtpd/mta.c | 2647 -------------------------- smtpd/mta_session.c | 2004 ------------------- smtpd/newaliases.8 | 86 - smtpd/parse.y | 3598 ----------------------------------- smtpd/parser.c | 341 ---- smtpd/parser.h | 43 - smtpd/pony.c | 212 --- smtpd/proxy.c | 387 ---- smtpd/queue.c | 750 -------- smtpd/queue_backend.c | 806 -------- smtpd/queue_fs.c | 695 ------- smtpd/queue_null.c | 120 -- smtpd/queue_proc.c | 337 ---- smtpd/queue_ram.c | 336 ---- smtpd/report_smtp.c | 335 ---- smtpd/resolver.c | 462 ----- smtpd/rfc5322.c | 266 --- smtpd/rfc5322.h | 41 - smtpd/ruleset.c | 265 --- smtpd/runq.c | 183 -- smtpd/scheduler.c | 618 ------ smtpd/scheduler_backend.c | 82 - smtpd/scheduler_null.c | 164 -- smtpd/scheduler_proc.c | 446 ----- smtpd/scheduler_ramqueue.c | 1204 ------------ smtpd/sendmail.8 | 86 - smtpd/smtp.1 | 96 - smtpd/smtp.c | 387 ---- smtpd/smtp.h | 95 - smtpd/smtp/Makefile | 24 - smtpd/smtp_client.c | 923 --------- smtpd/smtp_session.c | 3223 ------------------------------- smtpd/smtpc.c | 465 ----- smtpd/smtpctl.8 | 336 ---- smtpd/smtpctl.c | 1469 -------------- smtpd/smtpctl/Makefile | 56 - smtpd/smtpd-api.h | 290 --- smtpd/smtpd-defines.h | 68 - smtpd/smtpd-filters.7 | 653 ------- smtpd/smtpd.8 | 167 -- smtpd/smtpd.c | 2328 ---------------------- smtpd/smtpd.conf | 19 - smtpd/smtpd.conf.5 | 1240 ------------ smtpd/smtpd.h | 1784 ----------------- smtpd/smtpd/Makefile | 102 - smtpd/spfwalk.c | 391 ---- smtpd/srs.c | 379 ---- smtpd/ssl.c | 458 ----- smtpd/ssl.h | 71 - smtpd/ssl_smtpd.c | 105 - smtpd/ssl_verify.c | 297 --- smtpd/stat_backend.c | 124 -- smtpd/stat_ramstat.c | 162 -- smtpd/table.5 | 258 --- smtpd/table.c | 709 ------- smtpd/table_db.c | 282 --- smtpd/table_getpwnam.c | 120 -- smtpd/table_proc.c | 283 --- smtpd/table_static.c | 398 ---- smtpd/to.c | 880 --------- smtpd/tree.c | 259 --- smtpd/tree.h | 48 - smtpd/unpack_dns.c | 300 --- smtpd/unpack_dns.h | 96 - smtpd/util.c | 870 --------- smtpd/waitq.c | 104 - usr.sbin/smtpd/Makefile | 10 + usr.sbin/smtpd/aliases.5 | 102 + usr.sbin/smtpd/aliases.c | 234 +++ usr.sbin/smtpd/bounce.c | 820 ++++++++ usr.sbin/smtpd/ca.c | 777 ++++++++ usr.sbin/smtpd/cert.c | 416 ++++ usr.sbin/smtpd/compress_backend.c | 72 + usr.sbin/smtpd/compress_gzip.c | 186 ++ usr.sbin/smtpd/config.c | 350 ++++ usr.sbin/smtpd/control.c | 817 ++++++++ usr.sbin/smtpd/crypto.c | 400 ++++ usr.sbin/smtpd/dict.c | 269 +++ usr.sbin/smtpd/dict.h | 48 + usr.sbin/smtpd/dns.c | 379 ++++ usr.sbin/smtpd/enqueue.c | 932 +++++++++ usr.sbin/smtpd/envelope.c | 786 ++++++++ usr.sbin/smtpd/esc.c | 116 ++ usr.sbin/smtpd/expand.c | 332 ++++ usr.sbin/smtpd/filter.c | 868 +++++++++ usr.sbin/smtpd/forward.5 | 83 + usr.sbin/smtpd/forward.c | 104 + usr.sbin/smtpd/iobuf.c | 462 +++++ usr.sbin/smtpd/iobuf.h | 67 + usr.sbin/smtpd/ioev.c | 1064 +++++++++++ usr.sbin/smtpd/ioev.h | 70 + usr.sbin/smtpd/libressl.c | 213 +++ usr.sbin/smtpd/limit.c | 124 ++ usr.sbin/smtpd/lka.c | 914 +++++++++ usr.sbin/smtpd/lka_filter.c | 1746 +++++++++++++++++ usr.sbin/smtpd/lka_session.c | 556 ++++++ usr.sbin/smtpd/log.c | 220 +++ usr.sbin/smtpd/log.h | 52 + usr.sbin/smtpd/mail.lmtp.8 | 55 + usr.sbin/smtpd/mail.lmtp.c | 332 ++++ usr.sbin/smtpd/mail.maildir.8 | 45 + usr.sbin/smtpd/mail.maildir.c | 284 +++ usr.sbin/smtpd/mail.mboxfile.8 | 34 + usr.sbin/smtpd/mail.mboxfile.c | 109 ++ usr.sbin/smtpd/mail.mda.8 | 35 + usr.sbin/smtpd/mail.mda.c | 70 + usr.sbin/smtpd/mail/Makefile | 20 + usr.sbin/smtpd/mailaddr.c | 135 ++ usr.sbin/smtpd/makemap.8 | 174 ++ usr.sbin/smtpd/makemap.c | 521 +++++ usr.sbin/smtpd/mda.c | 919 +++++++++ usr.sbin/smtpd/mda_mbox.c | 94 + usr.sbin/smtpd/mda_unpriv.c | 110 ++ usr.sbin/smtpd/mda_variables.c | 374 ++++ usr.sbin/smtpd/mproc.c | 676 +++++++ usr.sbin/smtpd/mta.c | 2647 ++++++++++++++++++++++++++ usr.sbin/smtpd/mta_session.c | 2004 +++++++++++++++++++ usr.sbin/smtpd/newaliases.8 | 86 + usr.sbin/smtpd/parse.y | 3598 +++++++++++++++++++++++++++++++++++ usr.sbin/smtpd/parser.c | 341 ++++ usr.sbin/smtpd/parser.h | 43 + usr.sbin/smtpd/pony.c | 212 +++ usr.sbin/smtpd/proxy.c | 387 ++++ usr.sbin/smtpd/queue.c | 750 ++++++++ usr.sbin/smtpd/queue_backend.c | 806 ++++++++ usr.sbin/smtpd/queue_fs.c | 695 +++++++ usr.sbin/smtpd/queue_null.c | 120 ++ usr.sbin/smtpd/queue_proc.c | 337 ++++ usr.sbin/smtpd/queue_ram.c | 336 ++++ usr.sbin/smtpd/report_smtp.c | 335 ++++ usr.sbin/smtpd/resolver.c | 462 +++++ usr.sbin/smtpd/rfc5322.c | 266 +++ usr.sbin/smtpd/rfc5322.h | 41 + usr.sbin/smtpd/ruleset.c | 265 +++ usr.sbin/smtpd/runq.c | 183 ++ usr.sbin/smtpd/scheduler.c | 618 ++++++ usr.sbin/smtpd/scheduler_backend.c | 82 + usr.sbin/smtpd/scheduler_null.c | 164 ++ usr.sbin/smtpd/scheduler_proc.c | 446 +++++ usr.sbin/smtpd/scheduler_ramqueue.c | 1204 ++++++++++++ usr.sbin/smtpd/sendmail.8 | 86 + usr.sbin/smtpd/smtp.1 | 96 + usr.sbin/smtpd/smtp.c | 387 ++++ usr.sbin/smtpd/smtp.h | 95 + usr.sbin/smtpd/smtp/Makefile | 24 + usr.sbin/smtpd/smtp_client.c | 923 +++++++++ usr.sbin/smtpd/smtp_session.c | 3223 +++++++++++++++++++++++++++++++ usr.sbin/smtpd/smtpc.c | 465 +++++ usr.sbin/smtpd/smtpctl.8 | 336 ++++ usr.sbin/smtpd/smtpctl.c | 1469 ++++++++++++++ usr.sbin/smtpd/smtpctl/Makefile | 56 + usr.sbin/smtpd/smtpd-api.h | 290 +++ usr.sbin/smtpd/smtpd-defines.h | 68 + usr.sbin/smtpd/smtpd-filters.7 | 653 +++++++ usr.sbin/smtpd/smtpd.8 | 167 ++ usr.sbin/smtpd/smtpd.c | 2328 ++++++++++++++++++++++ usr.sbin/smtpd/smtpd.conf | 19 + usr.sbin/smtpd/smtpd.conf.5 | 1240 ++++++++++++ usr.sbin/smtpd/smtpd.h | 1784 +++++++++++++++++ usr.sbin/smtpd/smtpd/Makefile | 102 + usr.sbin/smtpd/spfwalk.c | 391 ++++ usr.sbin/smtpd/srs.c | 379 ++++ usr.sbin/smtpd/ssl.c | 458 +++++ usr.sbin/smtpd/ssl.h | 71 + usr.sbin/smtpd/ssl_smtpd.c | 105 + usr.sbin/smtpd/ssl_verify.c | 297 +++ usr.sbin/smtpd/stat_backend.c | 124 ++ usr.sbin/smtpd/stat_ramstat.c | 162 ++ usr.sbin/smtpd/table.5 | 258 +++ usr.sbin/smtpd/table.c | 709 +++++++ usr.sbin/smtpd/table_db.c | 282 +++ usr.sbin/smtpd/table_getpwnam.c | 120 ++ usr.sbin/smtpd/table_proc.c | 283 +++ usr.sbin/smtpd/table_static.c | 398 ++++ usr.sbin/smtpd/to.c | 880 +++++++++ usr.sbin/smtpd/tree.c | 259 +++ usr.sbin/smtpd/tree.h | 48 + usr.sbin/smtpd/unpack_dns.c | 300 +++ usr.sbin/smtpd/unpack_dns.h | 96 + usr.sbin/smtpd/util.c | 870 +++++++++ usr.sbin/smtpd/waitq.c | 104 + 230 files changed, 54409 insertions(+), 54409 deletions(-) delete mode 100644 smtpd/Makefile delete mode 100644 smtpd/aliases.5 delete mode 100644 smtpd/aliases.c delete mode 100644 smtpd/bounce.c delete mode 100644 smtpd/ca.c delete mode 100644 smtpd/cert.c delete mode 100644 smtpd/compress_backend.c delete mode 100644 smtpd/compress_gzip.c delete mode 100644 smtpd/config.c delete mode 100644 smtpd/control.c delete mode 100644 smtpd/crypto.c delete mode 100644 smtpd/dict.c delete mode 100644 smtpd/dict.h delete mode 100644 smtpd/dns.c delete mode 100644 smtpd/enqueue.c delete mode 100644 smtpd/envelope.c delete mode 100644 smtpd/esc.c delete mode 100644 smtpd/expand.c delete mode 100644 smtpd/filter.c delete mode 100644 smtpd/forward.5 delete mode 100644 smtpd/forward.c delete mode 100644 smtpd/iobuf.c delete mode 100644 smtpd/iobuf.h delete mode 100644 smtpd/ioev.c delete mode 100644 smtpd/ioev.h delete mode 100644 smtpd/libressl.c delete mode 100644 smtpd/limit.c delete mode 100644 smtpd/lka.c delete mode 100644 smtpd/lka_filter.c delete mode 100644 smtpd/lka_session.c delete mode 100644 smtpd/log.c delete mode 100644 smtpd/log.h delete mode 100644 smtpd/mail.lmtp.8 delete mode 100644 smtpd/mail.lmtp.c delete mode 100644 smtpd/mail.maildir.8 delete mode 100644 smtpd/mail.maildir.c delete mode 100644 smtpd/mail.mboxfile.8 delete mode 100644 smtpd/mail.mboxfile.c delete mode 100644 smtpd/mail.mda.8 delete mode 100644 smtpd/mail.mda.c delete mode 100644 smtpd/mail/Makefile delete mode 100644 smtpd/mailaddr.c delete mode 100644 smtpd/makemap.8 delete mode 100644 smtpd/makemap.c delete mode 100644 smtpd/mda.c delete mode 100644 smtpd/mda_mbox.c delete mode 100644 smtpd/mda_unpriv.c delete mode 100644 smtpd/mda_variables.c delete mode 100644 smtpd/mproc.c delete mode 100644 smtpd/mta.c delete mode 100644 smtpd/mta_session.c delete mode 100644 smtpd/newaliases.8 delete mode 100644 smtpd/parse.y delete mode 100644 smtpd/parser.c delete mode 100644 smtpd/parser.h delete mode 100644 smtpd/pony.c delete mode 100644 smtpd/proxy.c delete mode 100644 smtpd/queue.c delete mode 100644 smtpd/queue_backend.c delete mode 100644 smtpd/queue_fs.c delete mode 100644 smtpd/queue_null.c delete mode 100644 smtpd/queue_proc.c delete mode 100644 smtpd/queue_ram.c delete mode 100644 smtpd/report_smtp.c delete mode 100644 smtpd/resolver.c delete mode 100644 smtpd/rfc5322.c delete mode 100644 smtpd/rfc5322.h delete mode 100644 smtpd/ruleset.c delete mode 100644 smtpd/runq.c delete mode 100644 smtpd/scheduler.c delete mode 100644 smtpd/scheduler_backend.c delete mode 100644 smtpd/scheduler_null.c delete mode 100644 smtpd/scheduler_proc.c delete mode 100644 smtpd/scheduler_ramqueue.c delete mode 100644 smtpd/sendmail.8 delete mode 100644 smtpd/smtp.1 delete mode 100644 smtpd/smtp.c delete mode 100644 smtpd/smtp.h delete mode 100644 smtpd/smtp/Makefile delete mode 100644 smtpd/smtp_client.c delete mode 100644 smtpd/smtp_session.c delete mode 100644 smtpd/smtpc.c delete mode 100644 smtpd/smtpctl.8 delete mode 100644 smtpd/smtpctl.c delete mode 100644 smtpd/smtpctl/Makefile delete mode 100644 smtpd/smtpd-api.h delete mode 100644 smtpd/smtpd-defines.h delete mode 100644 smtpd/smtpd-filters.7 delete mode 100644 smtpd/smtpd.8 delete mode 100644 smtpd/smtpd.c delete mode 100644 smtpd/smtpd.conf delete mode 100644 smtpd/smtpd.conf.5 delete mode 100644 smtpd/smtpd.h delete mode 100644 smtpd/smtpd/Makefile delete mode 100644 smtpd/spfwalk.c delete mode 100644 smtpd/srs.c delete mode 100644 smtpd/ssl.c delete mode 100644 smtpd/ssl.h delete mode 100644 smtpd/ssl_smtpd.c delete mode 100644 smtpd/ssl_verify.c delete mode 100644 smtpd/stat_backend.c delete mode 100644 smtpd/stat_ramstat.c delete mode 100644 smtpd/table.5 delete mode 100644 smtpd/table.c delete mode 100644 smtpd/table_db.c delete mode 100644 smtpd/table_getpwnam.c delete mode 100644 smtpd/table_proc.c delete mode 100644 smtpd/table_static.c delete mode 100644 smtpd/to.c delete mode 100644 smtpd/tree.c delete mode 100644 smtpd/tree.h delete mode 100644 smtpd/unpack_dns.c delete mode 100644 smtpd/unpack_dns.h delete mode 100644 smtpd/util.c delete mode 100644 smtpd/waitq.c create mode 100644 usr.sbin/smtpd/Makefile create mode 100644 usr.sbin/smtpd/aliases.5 create mode 100644 usr.sbin/smtpd/aliases.c create mode 100644 usr.sbin/smtpd/bounce.c create mode 100644 usr.sbin/smtpd/ca.c create mode 100644 usr.sbin/smtpd/cert.c create mode 100644 usr.sbin/smtpd/compress_backend.c create mode 100644 usr.sbin/smtpd/compress_gzip.c create mode 100644 usr.sbin/smtpd/config.c create mode 100644 usr.sbin/smtpd/control.c create mode 100644 usr.sbin/smtpd/crypto.c create mode 100644 usr.sbin/smtpd/dict.c create mode 100644 usr.sbin/smtpd/dict.h create mode 100644 usr.sbin/smtpd/dns.c create mode 100644 usr.sbin/smtpd/enqueue.c create mode 100644 usr.sbin/smtpd/envelope.c create mode 100644 usr.sbin/smtpd/esc.c create mode 100644 usr.sbin/smtpd/expand.c create mode 100644 usr.sbin/smtpd/filter.c create mode 100644 usr.sbin/smtpd/forward.5 create mode 100644 usr.sbin/smtpd/forward.c create mode 100644 usr.sbin/smtpd/iobuf.c create mode 100644 usr.sbin/smtpd/iobuf.h create mode 100644 usr.sbin/smtpd/ioev.c create mode 100644 usr.sbin/smtpd/ioev.h create mode 100644 usr.sbin/smtpd/libressl.c create mode 100644 usr.sbin/smtpd/limit.c create mode 100644 usr.sbin/smtpd/lka.c create mode 100644 usr.sbin/smtpd/lka_filter.c create mode 100644 usr.sbin/smtpd/lka_session.c create mode 100644 usr.sbin/smtpd/log.c create mode 100644 usr.sbin/smtpd/log.h create mode 100644 usr.sbin/smtpd/mail.lmtp.8 create mode 100644 usr.sbin/smtpd/mail.lmtp.c create mode 100644 usr.sbin/smtpd/mail.maildir.8 create mode 100644 usr.sbin/smtpd/mail.maildir.c create mode 100644 usr.sbin/smtpd/mail.mboxfile.8 create mode 100644 usr.sbin/smtpd/mail.mboxfile.c create mode 100644 usr.sbin/smtpd/mail.mda.8 create mode 100644 usr.sbin/smtpd/mail.mda.c create mode 100644 usr.sbin/smtpd/mail/Makefile create mode 100644 usr.sbin/smtpd/mailaddr.c create mode 100644 usr.sbin/smtpd/makemap.8 create mode 100644 usr.sbin/smtpd/makemap.c create mode 100644 usr.sbin/smtpd/mda.c create mode 100644 usr.sbin/smtpd/mda_mbox.c create mode 100644 usr.sbin/smtpd/mda_unpriv.c create mode 100644 usr.sbin/smtpd/mda_variables.c create mode 100644 usr.sbin/smtpd/mproc.c create mode 100644 usr.sbin/smtpd/mta.c create mode 100644 usr.sbin/smtpd/mta_session.c create mode 100644 usr.sbin/smtpd/newaliases.8 create mode 100644 usr.sbin/smtpd/parse.y create mode 100644 usr.sbin/smtpd/parser.c create mode 100644 usr.sbin/smtpd/parser.h create mode 100644 usr.sbin/smtpd/pony.c create mode 100644 usr.sbin/smtpd/proxy.c create mode 100644 usr.sbin/smtpd/queue.c create mode 100644 usr.sbin/smtpd/queue_backend.c create mode 100644 usr.sbin/smtpd/queue_fs.c create mode 100644 usr.sbin/smtpd/queue_null.c create mode 100644 usr.sbin/smtpd/queue_proc.c create mode 100644 usr.sbin/smtpd/queue_ram.c create mode 100644 usr.sbin/smtpd/report_smtp.c create mode 100644 usr.sbin/smtpd/resolver.c create mode 100644 usr.sbin/smtpd/rfc5322.c create mode 100644 usr.sbin/smtpd/rfc5322.h create mode 100644 usr.sbin/smtpd/ruleset.c create mode 100644 usr.sbin/smtpd/runq.c create mode 100644 usr.sbin/smtpd/scheduler.c create mode 100644 usr.sbin/smtpd/scheduler_backend.c create mode 100644 usr.sbin/smtpd/scheduler_null.c create mode 100644 usr.sbin/smtpd/scheduler_proc.c create mode 100644 usr.sbin/smtpd/scheduler_ramqueue.c create mode 100644 usr.sbin/smtpd/sendmail.8 create mode 100644 usr.sbin/smtpd/smtp.1 create mode 100644 usr.sbin/smtpd/smtp.c create mode 100644 usr.sbin/smtpd/smtp.h create mode 100644 usr.sbin/smtpd/smtp/Makefile create mode 100644 usr.sbin/smtpd/smtp_client.c create mode 100644 usr.sbin/smtpd/smtp_session.c create mode 100644 usr.sbin/smtpd/smtpc.c create mode 100644 usr.sbin/smtpd/smtpctl.8 create mode 100644 usr.sbin/smtpd/smtpctl.c create mode 100644 usr.sbin/smtpd/smtpctl/Makefile create mode 100644 usr.sbin/smtpd/smtpd-api.h create mode 100644 usr.sbin/smtpd/smtpd-defines.h create mode 100644 usr.sbin/smtpd/smtpd-filters.7 create mode 100644 usr.sbin/smtpd/smtpd.8 create mode 100644 usr.sbin/smtpd/smtpd.c create mode 100644 usr.sbin/smtpd/smtpd.conf create mode 100644 usr.sbin/smtpd/smtpd.conf.5 create mode 100644 usr.sbin/smtpd/smtpd.h create mode 100644 usr.sbin/smtpd/smtpd/Makefile create mode 100644 usr.sbin/smtpd/spfwalk.c create mode 100644 usr.sbin/smtpd/srs.c create mode 100644 usr.sbin/smtpd/ssl.c create mode 100644 usr.sbin/smtpd/ssl.h create mode 100644 usr.sbin/smtpd/ssl_smtpd.c create mode 100644 usr.sbin/smtpd/ssl_verify.c create mode 100644 usr.sbin/smtpd/stat_backend.c create mode 100644 usr.sbin/smtpd/stat_ramstat.c create mode 100644 usr.sbin/smtpd/table.5 create mode 100644 usr.sbin/smtpd/table.c create mode 100644 usr.sbin/smtpd/table_db.c create mode 100644 usr.sbin/smtpd/table_getpwnam.c create mode 100644 usr.sbin/smtpd/table_proc.c create mode 100644 usr.sbin/smtpd/table_static.c create mode 100644 usr.sbin/smtpd/to.c create mode 100644 usr.sbin/smtpd/tree.c create mode 100644 usr.sbin/smtpd/tree.h create mode 100644 usr.sbin/smtpd/unpack_dns.c create mode 100644 usr.sbin/smtpd/unpack_dns.h create mode 100644 usr.sbin/smtpd/util.c create mode 100644 usr.sbin/smtpd/waitq.c diff --git a/smtpd/Makefile b/smtpd/Makefile deleted file mode 100644 index a3dbc9d1..00000000 --- a/smtpd/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# $OpenBSD: Makefile,v 1.18 2018/05/24 11:38:24 gilles Exp $ - -.include - -SUBDIR = smtpd -SUBDIR+= smtpctl -SUBDIR+= smtp -SUBDIR+= mail - -.include diff --git a/smtpd/aliases.5 b/smtpd/aliases.5 deleted file mode 100644 index 7c250c81..00000000 --- a/smtpd/aliases.5 +++ /dev/null @@ -1,102 +0,0 @@ -.\" $OpenBSD: aliases.5,v 1.16 2020/04/23 21:28:10 jmc Exp $ -.\" -.\" Copyright (c) 2012 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: April 23 2020 $ -.Dt ALIASES 5 -.Os -.Sh NAME -.Nm aliases -.Nd aliases file for smtpd -.Sh DESCRIPTION -This manual page describes the format of the -.Nm -file, as used by -.Xr smtpd 8 . -An alias in its simplest form is used to assign an arbitrary name -to an email address, or a group of email addresses. -This provides a convenient way to send mail. -For example an alias could refer to all users of a group: -email to that alias would be sent to all members of the group. -Much more complex aliases can be defined however: -an alias can refer to other aliases, -be used to send mail to a file instead of another person, -or to execute various commands. -.Pp -Within the file, -.Ql # -is a comment delimiter; anything placed after it is discarded. -The file consists of key/value mappings of the form: -.Bd -filled -offset indent -key: value1, value2, value3, ... -.Ed -.Pp -.Em key -is always folded to lowercase before alias lookups to ensure that -there can be no ambiguity. -The key is expanded to the corresponding values, -which consist of one or more of the following: -.Bl -tag -width Ds -.It Em user -A user on the host machine. -The user must have a valid entry in the -.Xr passwd 5 -database file. -.It Ar /path/to/file -Append messages to -.Ar file , -specified by its absolute pathname. -.It | Ns Ar command -Pipe the message to -.Ar command -on its standard input. -The command is run under the privileges of the daemon's unprivileged account. -.It : Ns Ar include : Ns Ar /path/to/file -Include any definitions in -.Ar file -as alias entries. -The format of the file is identical to this one. -.It Ar user-part@domain-part -An email address in RFC 5322 format. -If an address extension is appended to the user-part, -it is first compared for an exact match. -It is then stripped so that an address such as user+ext@example.com -will only use the part that precedes -.Sq + -as a -.Em key . -.It Ar error : Ns Ar code message -A status code and message to return. -The code must be 3 digits, -starting 4XX (TempFail) or 5XX (PermFail). -The message must be present and can be freely chosen. -.El -.Sh FILES -.Bl -tag -width "/etc/mail/aliasesXXX" -compact -.It Pa /etc/mail/aliases -Default -.Nm -file. -.El -.Sh SEE ALSO -.Xr smtpd.conf 5 , -.Xr makemap 8 , -.Xr newaliases 8 , -.Xr smtpd 8 -.Sh HISTORY -The -.Nm -file format appeared in -.Bx 4.0 . diff --git a/smtpd/aliases.c b/smtpd/aliases.c deleted file mode 100644 index 0f8a5c1e..00000000 --- a/smtpd/aliases.c +++ /dev/null @@ -1,234 +0,0 @@ -/* $OpenBSD: aliases.c,v 1.78 2020/04/28 21:46:43 eric Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UTIL_H -#include -#endif -#ifdef HAVE_LIBUTIL_H -#include -#endif - -#include "smtpd.h" -#include "log.h" - -static int aliases_expand_include(struct expand *, const char *); - -int -aliases_get(struct expand *expand, const char *username) -{ - struct expandnode *xn; - char buf[SMTPD_MAXLOCALPARTSIZE]; - size_t nbaliases; - int ret; - union lookup lk; - struct dispatcher *dsp; - struct table *mapping = NULL; - char *pbuf; - - dsp = dict_xget(env->sc_dispatchers, expand->rule->dispatcher); - mapping = table_find(env, dsp->u.local.table_alias); - - xlowercase(buf, username, sizeof(buf)); - - /* first, check if entry has a user-part tag */ - pbuf = strchr(buf, *env->sc_subaddressing_delim); - if (pbuf) { - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret < 0) - return (-1); - if (ret) - goto expand; - *pbuf = '\0'; - } - - /* no user-part tag, try looking up user */ - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret <= 0) - return ret; - -expand: - /* foreach node in table_alias expandtree, we merge */ - nbaliases = 0; - RB_FOREACH(xn, expandtree, &lk.expand->tree) { - if (xn->type == EXPAND_INCLUDE) - nbaliases += aliases_expand_include(expand, - xn->u.buffer); - else { - expand_insert(expand, xn); - nbaliases++; - } - } - - expand_free(lk.expand); - - log_debug("debug: aliases_get: returned %zd aliases", nbaliases); - return nbaliases; -} - -int -aliases_virtual_get(struct expand *expand, const struct mailaddr *maddr) -{ - struct expandnode *xn; - union lookup lk; - char buf[LINE_MAX]; - char user[LINE_MAX]; - char tag[LINE_MAX]; - char domain[LINE_MAX]; - char *pbuf; - int nbaliases; - int ret; - struct dispatcher *dsp; - struct table *mapping = NULL; - - dsp = dict_xget(env->sc_dispatchers, expand->rule->dispatcher); - mapping = table_find(env, dsp->u.local.table_virtual); - - if (!bsnprintf(user, sizeof(user), "%s", maddr->user)) - return 0; - if (!bsnprintf(domain, sizeof(domain), "%s", maddr->domain)) - return 0; - xlowercase(user, user, sizeof(user)); - xlowercase(domain, domain, sizeof(domain)); - - memset(tag, '\0', sizeof tag); - pbuf = strchr(user, *env->sc_subaddressing_delim); - if (pbuf) { - if (!bsnprintf(tag, sizeof(tag), "%s", pbuf + 1)) - return 0; - xlowercase(tag, tag, sizeof(tag)); - *pbuf = '\0'; - } - - /* first, check if entry has a user-part tag */ - if (tag[0]) { - if (!bsnprintf(buf, sizeof(buf), "%s%c%s@%s", - user, *env->sc_subaddressing_delim, tag, domain)) - return 0; - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret < 0) - return (-1); - if (ret) - goto expand; - } - - /* then, check if entry exists without user-part tag */ - if (!bsnprintf(buf, sizeof(buf), "%s@%s", user, domain)) - return 0; - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret < 0) - return (-1); - if (ret) - goto expand; - - if (tag[0]) { - /* Failed ? We lookup for username + user-part tag */ - if (!bsnprintf(buf, sizeof(buf), "%s%c%s", - user, *env->sc_subaddressing_delim, tag)) - return 0; - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret < 0) - return (-1); - if (ret) - goto expand; - } - - /* Failed ? We lookup for username only */ - if (!bsnprintf(buf, sizeof(buf), "%s", user)) - return 0; - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret < 0) - return (-1); - if (ret) - goto expand; - - /* Do not try catch-all entries if there is no domain */ - if (domain[0] == '\0') - return 0; - - if (!bsnprintf(buf, sizeof(buf), "@%s", domain)) - return 0; - /* Failed ? We lookup for catch all for virtual domain */ - ret = table_lookup(mapping, K_ALIAS, buf, &lk); - if (ret < 0) - return (-1); - if (ret) - goto expand; - - /* Failed ? We lookup for a *global* catch all */ - ret = table_lookup(mapping, K_ALIAS, "@", &lk); - if (ret <= 0) - return (ret); - -expand: - /* foreach node in table_virtual expand, we merge */ - nbaliases = 0; - RB_FOREACH(xn, expandtree, &lk.expand->tree) { - if (xn->type == EXPAND_INCLUDE) - nbaliases += aliases_expand_include(expand, - xn->u.buffer); - else { - expand_insert(expand, xn); - nbaliases++; - } - } - - expand_free(lk.expand); - - log_debug("debug: aliases_virtual_get: '%s' resolved to %d nodes", - buf, nbaliases); - - return nbaliases; -} - -static int -aliases_expand_include(struct expand *expand, const char *filename) -{ - FILE *fp; - char *line; - size_t len, lineno = 0; - char delim[3] = { '\\', '#', '\0' }; - - fp = fopen(filename, "r"); - if (fp == NULL) { - log_warn("warn: failed to open include file \"%s\".", filename); - return 0; - } - - while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) { - expand_line(expand, line, 0); - free(line); - } - - fclose(fp); - return 1; -} diff --git a/smtpd/bounce.c b/smtpd/bounce.c deleted file mode 100644 index 4a4a0992..00000000 --- a/smtpd/bounce.c +++ /dev/null @@ -1,820 +0,0 @@ -/* $OpenBSD: bounce.c,v 1.82 2020/04/24 11:34:07 eric Exp $ */ - -/* - * Copyright (c) 2009 Gilles Chehade - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define BOUNCE_MAXRUN 2 -#define BOUNCE_HIWAT 65535 - -enum { - BOUNCE_EHLO, - BOUNCE_MAIL, - BOUNCE_RCPT, - BOUNCE_DATA, - BOUNCE_DATA_NOTICE, - BOUNCE_DATA_MESSAGE, - BOUNCE_DATA_END, - BOUNCE_QUIT, - BOUNCE_CLOSE, -}; - -struct bounce_envelope { - TAILQ_ENTRY(bounce_envelope) entry; - uint64_t id; - struct mailaddr dest; - char *report; - uint8_t esc_class; - uint8_t esc_code; -}; - -struct bounce_message { - SPLAY_ENTRY(bounce_message) sp_entry; - TAILQ_ENTRY(bounce_message) entry; - uint32_t msgid; - struct delivery_bounce bounce; - char *smtpname; - char *to; - time_t timeout; - TAILQ_HEAD(, bounce_envelope) envelopes; -}; - -struct bounce_session { - char *smtpname; - struct bounce_message *msg; - FILE *msgfp; - int state; - struct io *io; - uint64_t boundary; -}; - -SPLAY_HEAD(bounce_message_tree, bounce_message); -static int bounce_message_cmp(const struct bounce_message *, - const struct bounce_message *); -SPLAY_PROTOTYPE(bounce_message_tree, bounce_message, sp_entry, - bounce_message_cmp); - -static void bounce_drain(void); -static void bounce_send(struct bounce_session *, const char *, ...); -static int bounce_next_message(struct bounce_session *); -static int bounce_next(struct bounce_session *); -static void bounce_delivery(struct bounce_message *, int, const char *); -static void bounce_status(struct bounce_session *, const char *, ...); -static void bounce_io(struct io *, int, void *); -static void bounce_timeout(int, short, void *); -static void bounce_free(struct bounce_session *); -static const char *action_str(const struct delivery_bounce *); - -static struct tree wait_fd; -static struct bounce_message_tree messages; -static TAILQ_HEAD(, bounce_message) pending; - -static int nmessage = 0; -static int running = 0; -static struct event ev_timer; - -static void -bounce_init(void) -{ - static int init = 0; - - if (init == 0) { - TAILQ_INIT(&pending); - SPLAY_INIT(&messages); - tree_init(&wait_fd); - evtimer_set(&ev_timer, bounce_timeout, NULL); - init = 1; - } -} - -void -bounce_add(uint64_t evpid) -{ - char buf[LINE_MAX], *line; - struct envelope evp; - struct bounce_message key, *msg; - struct bounce_envelope *be; - - bounce_init(); - - if (queue_envelope_load(evpid, &evp) == 0) { - m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_close(p_scheduler); - return; - } - - if (evp.type != D_BOUNCE) - errx(1, "bounce: evp:%016" PRIx64 " is not of type D_BOUNCE!", - evp.id); - - key.msgid = evpid_to_msgid(evpid); - key.bounce = evp.agent.bounce; - key.smtpname = evp.smtpname; - - switch (evp.esc_class) { - case ESC_STATUS_OK: - key.bounce.type = B_DELIVERED; - break; - case ESC_STATUS_TEMPFAIL: - key.bounce.type = B_DELAYED; - break; - default: - key.bounce.type = B_FAILED; - } - - key.bounce.dsn_ret = evp.dsn_ret; - key.bounce.ttl = evp.ttl; - msg = SPLAY_FIND(bounce_message_tree, &messages, &key); - if (msg == NULL) { - msg = xcalloc(1, sizeof(*msg)); - msg->msgid = key.msgid; - msg->bounce = key.bounce; - - TAILQ_INIT(&msg->envelopes); - - msg->smtpname = xstrdup(evp.smtpname); - (void)snprintf(buf, sizeof(buf), "%s@%s", evp.sender.user, - evp.sender.domain); - msg->to = xstrdup(buf); - nmessage += 1; - SPLAY_INSERT(bounce_message_tree, &messages, msg); - log_debug("debug: bounce: new message %08" PRIx32, - msg->msgid); - stat_increment("bounce.message", 1); - } else - TAILQ_REMOVE(&pending, msg, entry); - - line = evp.errorline; - if (strlen(line) > 4 && (*line == '1' || *line == '6')) - line += 4; - (void)snprintf(buf, sizeof(buf), "%s@%s: %s", evp.dest.user, - evp.dest.domain, line); - - be = xmalloc(sizeof *be); - be->id = evpid; - be->report = xstrdup(buf); - (void)strlcpy(be->dest.user, evp.dest.user, sizeof(be->dest.user)); - (void)strlcpy(be->dest.domain, evp.dest.domain, - sizeof(be->dest.domain)); - be->esc_class = evp.esc_class; - be->esc_code = evp.esc_code; - TAILQ_INSERT_TAIL(&msg->envelopes, be, entry); - log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, be->report); - - msg->timeout = time(NULL) + 1; - TAILQ_INSERT_TAIL(&pending, msg, entry); - - stat_increment("bounce.envelope", 1); - bounce_drain(); -} - -void -bounce_fd(int fd) -{ - struct bounce_session *s; - struct bounce_message *msg; - - log_debug("debug: bounce: got enqueue socket %d", fd); - - if (fd == -1 || TAILQ_EMPTY(&pending)) { - log_debug("debug: bounce: cancelling"); - if (fd != -1) - close(fd); - running -= 1; - bounce_drain(); - return; - } - - msg = TAILQ_FIRST(&pending); - - s = xcalloc(1, sizeof(*s)); - s->smtpname = xstrdup(msg->smtpname); - s->state = BOUNCE_EHLO; - s->io = io_new(); - io_set_callback(s->io, bounce_io, s); - io_set_fd(s->io, fd); - io_set_timeout(s->io, 30000); - io_set_read(s->io); - s->boundary = generate_uid(); - - log_debug("debug: bounce: new session %p", s); - stat_increment("bounce.session", 1); -} - -static void -bounce_timeout(int fd, short ev, void *arg) -{ - log_debug("debug: bounce: timeout"); - - bounce_drain(); -} - -static void -bounce_drain() -{ - struct bounce_message *msg; - struct timeval tv; - time_t t; - - log_debug("debug: bounce: drain: nmessage=%d running=%d", - nmessage, running); - - while (1) { - if (running >= BOUNCE_MAXRUN) { - log_debug("debug: bounce: max session reached"); - return; - } - - if (nmessage == 0) { - log_debug("debug: bounce: no more messages"); - return; - } - - if (running >= nmessage) { - log_debug("debug: bounce: enough sessions running"); - return; - } - - if ((msg = TAILQ_FIRST(&pending)) == NULL) { - log_debug("debug: bounce: no more pending messages"); - return; - } - - t = time(NULL); - if (msg->timeout > t) { - log_debug("debug: bounce: next message not ready yet"); - if (!evtimer_pending(&ev_timer, NULL)) { - log_debug("debug: bounce: setting timer"); - tv.tv_sec = msg->timeout - t; - tv.tv_usec = 0; - evtimer_add(&ev_timer, &tv); - } - return; - } - - log_debug("debug: bounce: requesting new enqueue socket..."); - m_compose(p_pony, IMSG_QUEUE_SMTP_SESSION, 0, 0, -1, NULL, 0); - - running += 1; - } -} - -static void -bounce_send(struct bounce_session *s, const char *fmt, ...) -{ - va_list ap; - char *p; - int len; - - va_start(ap, fmt); - if ((len = vasprintf(&p, fmt, ap)) == -1) - fatal("bounce: vasprintf"); - va_end(ap); - - log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", s, p); - - io_xprintf(s->io, "%s\r\n", p); - - free(p); -} - -static const char * -bounce_duration(long long int d) -{ - static char buf[32]; - - if (d < 60) { - (void)snprintf(buf, sizeof buf, "%lld second%s", d, - (d == 1) ? "" : "s"); - } else if (d < 3600) { - d = d / 60; - (void)snprintf(buf, sizeof buf, "%lld minute%s", d, - (d == 1) ? "" : "s"); - } - else if (d < 3600 * 24) { - d = d / 3600; - (void)snprintf(buf, sizeof buf, "%lld hour%s", d, - (d == 1) ? "" : "s"); - } - else { - d = d / (3600 * 24); - (void)snprintf(buf, sizeof buf, "%lld day%s", d, - (d == 1) ? "" : "s"); - } - return (buf); -} - -#define NOTICE_INTRO \ - " Hi!\r\n\r\n" \ - " This is the MAILER-DAEMON, please DO NOT REPLY to this email.\r\n" - -const char *notice_error = - " An error has occurred while attempting to deliver a message for\r\n" - " the following list of recipients:\r\n\r\n"; - -const char *notice_warning = - " A message is delayed for more than %s for the following\r\n" - " list of recipients:\r\n\r\n"; - -const char *notice_warning2 = - " Please note that this is only a temporary failure report.\r\n" - " The message is kept in the queue for up to %s.\r\n" - " You DO NOT NEED to re-send the message to these recipients.\r\n\r\n"; - -const char *notice_success = - " Your message was successfully delivered to these recipients.\r\n\r\n"; - -const char *notice_relay = - " Your message was relayed to these recipients.\r\n\r\n"; - -static int -bounce_next_message(struct bounce_session *s) -{ - struct bounce_message *msg; - char buf[LINE_MAX]; - int fd; - time_t now; - - again: - - now = time(NULL); - - TAILQ_FOREACH(msg, &pending, entry) { - if (msg->timeout > now) - continue; - if (strcmp(msg->smtpname, s->smtpname)) - continue; - break; - } - if (msg == NULL) - return (0); - - TAILQ_REMOVE(&pending, msg, entry); - SPLAY_REMOVE(bounce_message_tree, &messages, msg); - - if ((fd = queue_message_fd_r(msg->msgid)) == -1) { - bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, - "Could not open message fd"); - goto again; - } - - if ((s->msgfp = fdopen(fd, "r")) == NULL) { - (void)snprintf(buf, sizeof(buf), "fdopen: %s", strerror(errno)); - log_warn("warn: bounce: fdopen"); - close(fd); - bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, buf); - goto again; - } - - s->msg = msg; - return (1); -} - -static int -bounce_next(struct bounce_session *s) -{ - struct bounce_envelope *evp; - char *line = NULL; - size_t n, sz = 0; - ssize_t len; - - switch (s->state) { - case BOUNCE_EHLO: - bounce_send(s, "EHLO %s", s->smtpname); - s->state = BOUNCE_MAIL; - break; - - case BOUNCE_MAIL: - case BOUNCE_DATA_END: - log_debug("debug: bounce: %p: getting next message...", s); - if (bounce_next_message(s) == 0) { - log_debug("debug: bounce: %p: no more messages", s); - bounce_send(s, "QUIT"); - s->state = BOUNCE_CLOSE; - break; - } - log_debug("debug: bounce: %p: found message %08"PRIx32, - s, s->msg->msgid); - bounce_send(s, "MAIL FROM: <>"); - s->state = BOUNCE_RCPT; - break; - - case BOUNCE_RCPT: - bounce_send(s, "RCPT TO: <%s>", s->msg->to); - s->state = BOUNCE_DATA; - break; - - case BOUNCE_DATA: - bounce_send(s, "DATA"); - s->state = BOUNCE_DATA_NOTICE; - break; - - case BOUNCE_DATA_NOTICE: - /* Construct an appropriate notice. */ - - io_xprintf(s->io, - "Subject: Delivery status notification: %s\r\n" - "From: Mailer Daemon \r\n" - "To: %s\r\n" - "Date: %s\r\n" - "MIME-Version: 1.0\r\n" - "Content-Type: multipart/mixed;" - "boundary=\"%16" PRIu64 "/%s\"\r\n" - "\r\n" - "This is a MIME-encapsulated message.\r\n" - "\r\n", - action_str(&s->msg->bounce), - s->smtpname, - s->msg->to, - time_to_text(time(NULL)), - s->boundary, - s->smtpname); - - io_xprintf(s->io, - "--%16" PRIu64 "/%s\r\n" - "Content-Description: Notification\r\n" - "Content-Type: text/plain; charset=us-ascii\r\n" - "\r\n" - NOTICE_INTRO - "\r\n", - s->boundary, s->smtpname); - - switch (s->msg->bounce.type) { - case B_FAILED: - io_xprint(s->io, notice_error); - break; - case B_DELAYED: - io_xprintf(s->io, notice_warning, - bounce_duration(s->msg->bounce.delay)); - break; - case B_DELIVERED: - io_xprint(s->io, s->msg->bounce.mta_without_dsn ? - notice_relay : notice_success); - break; - default: - log_warn("warn: bounce: unknown bounce_type"); - } - - TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { - io_xprint(s->io, evp->report); - io_xprint(s->io, "\r\n"); - } - io_xprint(s->io, "\r\n"); - - if (s->msg->bounce.type == B_DELAYED) - io_xprintf(s->io, notice_warning2, - bounce_duration(s->msg->bounce.ttl)); - - io_xprintf(s->io, - " Below is a copy of the original message:\r\n" - "\r\n"); - - io_xprintf(s->io, - "--%16" PRIu64 "/%s\r\n" - "Content-Description: Delivery Report\r\n" - "Content-Type: message/delivery-status\r\n" - "\r\n", - s->boundary, s->smtpname); - - io_xprintf(s->io, - "Reporting-MTA: dns; %s\r\n" - "\r\n", - s->smtpname); - - TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { - io_xprintf(s->io, - "Final-Recipient: rfc822; %s@%s\r\n" - "Action: %s\r\n" - "Status: %s\r\n" - "\r\n", - evp->dest.user, - evp->dest.domain, - action_str(&s->msg->bounce), - esc_code(evp->esc_class, evp->esc_code)); - } - - log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]", - s, io_queued(s->io)); - - s->state = BOUNCE_DATA_MESSAGE; - break; - - case BOUNCE_DATA_MESSAGE: - io_xprintf(s->io, - "--%16" PRIu64 "/%s\r\n" - "Content-Description: Message headers\r\n" - "Content-Type: text/rfc822-headers\r\n" - "\r\n", - s->boundary, s->smtpname); - - n = io_queued(s->io); - while (io_queued(s->io) < BOUNCE_HIWAT) { - if ((len = getline(&line, &sz, s->msgfp)) == -1) - break; - if (len == 1 && line[0] == '\n' && /* end of headers */ - s->msg->bounce.type == B_DELIVERED && - s->msg->bounce.dsn_ret == DSN_RETHDRS) { - free(line); - fclose(s->msgfp); - s->msgfp = NULL; - io_xprintf(s->io, - "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, - s->smtpname); - bounce_send(s, "."); - s->state = BOUNCE_DATA_END; - return (0); - } - line[len - 1] = '\0'; - io_xprintf(s->io, "%s%s\r\n", - (len == 2 && line[0] == '.') ? "." : "", line); - } - free(line); - - if (ferror(s->msgfp)) { - fclose(s->msgfp); - s->msgfp = NULL; - bounce_delivery(s->msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, - "Error reading message"); - s->msg = NULL; - return (-1); - } - - io_xprintf(s->io, - "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, s->smtpname); - - log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]", - s, io_queued(s->io) - n); - - if (feof(s->msgfp)) { - fclose(s->msgfp); - s->msgfp = NULL; - bounce_send(s, "."); - s->state = BOUNCE_DATA_END; - } - break; - - case BOUNCE_QUIT: - bounce_send(s, "QUIT"); - s->state = BOUNCE_CLOSE; - break; - - default: - fatalx("bounce: bad state"); - } - - return (0); -} - - -static void -bounce_delivery(struct bounce_message *msg, int delivery, const char *status) -{ - struct bounce_envelope *be; - struct envelope evp; - size_t n; - const char *f; - - n = 0; - while ((be = TAILQ_FIRST(&msg->envelopes))) { - if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) { - if (queue_envelope_load(be->id, &evp) == 0) { - fatalx("could not reload envelope!"); - } - evp.retry++; - evp.lasttry = msg->timeout; - envelope_set_errormsg(&evp, "%s", status); - queue_envelope_update(&evp); - m_create(p_scheduler, delivery, 0, 0, -1); - m_add_envelope(p_scheduler, &evp); - m_close(p_scheduler); - } else { - m_create(p_scheduler, delivery, 0, 0, -1); - m_add_evpid(p_scheduler, be->id); - m_close(p_scheduler); - queue_envelope_delete(be->id); - } - TAILQ_REMOVE(&msg->envelopes, be, entry); - free(be->report); - free(be); - n += 1; - } - - - if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) - f = "TempFail"; - else if (delivery == IMSG_QUEUE_DELIVERY_PERMFAIL) - f = "PermFail"; - else - f = NULL; - - if (f) - log_warnx("warn: %s injecting failure report on message %08" - PRIx32 " to <%s> for %zu envelope%s: %s", - f, msg->msgid, msg->to, n, n > 1 ? "s":"", status); - - nmessage -= 1; - stat_decrement("bounce.message", 1); - stat_decrement("bounce.envelope", n); - free(msg->smtpname); - free(msg->to); - free(msg); -} - -static void -bounce_status(struct bounce_session *s, const char *fmt, ...) -{ - va_list ap; - char *status; - int len, delivery; - - /* Ignore if there is no message */ - if (s->msg == NULL) - return; - - va_start(ap, fmt); - if ((len = vasprintf(&status, fmt, ap)) == -1) - fatal("bounce: vasprintf"); - va_end(ap); - - if (*status == '2') - delivery = IMSG_QUEUE_DELIVERY_OK; - else if (*status == '5' || *status == '6') - delivery = IMSG_QUEUE_DELIVERY_PERMFAIL; - else - delivery = IMSG_QUEUE_DELIVERY_TEMPFAIL; - - bounce_delivery(s->msg, delivery, status); - s->msg = NULL; - if (s->msgfp) - fclose(s->msgfp); - - free(status); -} - -static void -bounce_free(struct bounce_session *s) -{ - log_debug("debug: bounce: %p: deleting session", s); - - io_free(s->io); - - free(s->smtpname); - free(s); - - running -= 1; - stat_decrement("bounce.session", 1); - bounce_drain(); -} - -static void -bounce_io(struct io *io, int evt, void *arg) -{ - struct bounce_session *s = arg; - const char *error; - char *line, *msg; - int cont; - size_t len; - - log_trace(TRACE_IO, "bounce: %p: %s %s", s, io_strevent(evt), - io_strio(io)); - - switch (evt) { - case IO_DATAIN: - nextline: - line = io_getline(s->io, &len); - if (line == NULL && io_datalen(s->io) >= LINE_MAX) { - bounce_status(s, "Input too long"); - bounce_free(s); - return; - } - - if (line == NULL) - break; - - /* Strip trailing '\r' */ - if (len && line[len - 1] == '\r') - line[--len] = '\0'; - - log_trace(TRACE_BOUNCE, "bounce: %p: <<< %s", s, line); - - if ((error = parse_smtp_response(line, len, &msg, &cont))) { - bounce_status(s, "Bad response: %s", error); - bounce_free(s); - return; - } - if (cont) - goto nextline; - - if (s->state == BOUNCE_CLOSE) { - bounce_free(s); - return; - } - - if (line[0] != '2' && line[0] != '3') { /* fail */ - bounce_status(s, "%s", line); - s->state = BOUNCE_QUIT; - } else if (s->state == BOUNCE_DATA_END) { /* accepted */ - bounce_status(s, "%s", line); - } - - if (bounce_next(s) == -1) { - bounce_free(s); - return; - } - - io_set_write(io); - break; - - case IO_LOWAT: - if (s->state == BOUNCE_DATA_MESSAGE) - if (bounce_next(s) == -1) { - bounce_free(s); - return; - } - if (io_queued(s->io) == 0) - io_set_read(io); - break; - - default: - bounce_status(s, "442 i/o error %d", evt); - bounce_free(s); - break; - } -} - -static int -bounce_message_cmp(const struct bounce_message *a, - const struct bounce_message *b) -{ - int r; - - if (a->msgid < b->msgid) - return (-1); - if (a->msgid > b->msgid) - return (1); - if ((r = strcmp(a->smtpname, b->smtpname))) - return (r); - - return memcmp(&a->bounce, &b->bounce, sizeof (a->bounce)); -} - -static const char * -action_str(const struct delivery_bounce *b) -{ - switch (b->type) { - case B_FAILED: - return ("failed"); - case B_DELAYED: - return ("delayed"); - case B_DELIVERED: - if (b->mta_without_dsn) - return ("relayed"); - - return ("delivered"); - default: - log_warn("warn: bounce: unknown bounce_type"); - return (""); - } -} - -SPLAY_GENERATE(bounce_message_tree, bounce_message, sp_entry, - bounce_message_cmp); diff --git a/smtpd/ca.c b/smtpd/ca.c deleted file mode 100644 index a27db87a..00000000 --- a/smtpd/ca.c +++ /dev/null @@ -1,777 +0,0 @@ -/* $OpenBSD: ca.c,v 1.36 2019/09/21 07:46:53 semarie Exp $ */ - -/* - * Copyright (c) 2014 Reyk Floeter - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - -static int ca_verify_cb(int, X509_STORE_CTX *); - -static int rsae_send_imsg(int, const unsigned char *, unsigned char *, - RSA *, int, unsigned int); -static int rsae_pub_enc(int, const unsigned char *, unsigned char *, - RSA *, int); -static int rsae_pub_dec(int,const unsigned char *, unsigned char *, - RSA *, int); -static int rsae_priv_enc(int, const unsigned char *, unsigned char *, - RSA *, int); -static int rsae_priv_dec(int, const unsigned char *, unsigned char *, - RSA *, int); -static int rsae_mod_exp(BIGNUM *, const BIGNUM *, RSA *, BN_CTX *); -static int rsae_bn_mod_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, - const BIGNUM *, BN_CTX *, BN_MONT_CTX *); -static int rsae_init(RSA *); -static int rsae_finish(RSA *); -static int rsae_keygen(RSA *, int, BIGNUM *, BN_GENCB *); - -#if defined(SUPPORT_ECDSA) -static ECDSA_SIG *ecdsae_do_sign(const unsigned char *, int, const BIGNUM *, - const BIGNUM *, EC_KEY *); -static int ecdsae_sign_setup(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); -static int ecdsae_do_verify(const unsigned char *, int, const ECDSA_SIG *, - EC_KEY *); -#endif - -static uint64_t reqid = 0; - -static void -ca_shutdown(void) -{ - log_debug("debug: ca agent exiting"); - _exit(0); -} - -int -ca(void) -{ - struct passwd *pw; - - purge_config(PURGE_LISTENERS|PURGE_TABLES|PURGE_RULES|PURGE_DISPATCHERS); - - if ((pw = getpwnam(SMTPD_USER)) == NULL) - fatalx("unknown user " SMTPD_USER); - - if (chroot(PATH_CHROOT) == -1) - fatal("ca: chroot"); - if (chdir("/") == -1) - fatal("ca: chdir(\"/\")"); - - config_process(PROC_CA); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("ca: cannot drop privileges"); - - imsg_callback = ca_imsg; - event_init(); - - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - config_peer(PROC_CONTROL); - config_peer(PROC_PARENT); - config_peer(PROC_PONY); - - /* Ignore them until we get our config */ - mproc_disable(p_pony); - -#if HAVE_PLEDGE - if (pledge("stdio", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} - -void -ca_init(void) -{ - BIO *in = NULL; - EVP_PKEY *pkey = NULL; - struct pki *pki; - const char *k; - void *iter_dict; - - log_debug("debug: init private ssl-tree"); - iter_dict = NULL; - while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { - if (pki->pki_key == NULL) - continue; - - if ((in = BIO_new_mem_buf(pki->pki_key, - pki->pki_key_len)) == NULL) - fatalx("ca_launch: key"); - - if ((pkey = PEM_read_bio_PrivateKey(in, - NULL, NULL, NULL)) == NULL) - fatalx("ca_launch: PEM"); - BIO_free(in); - - pki->pki_pkey = pkey; - - freezero(pki->pki_key, pki->pki_key_len); - pki->pki_key = NULL; - } -} - -static int -ca_verify_cb(int ok, X509_STORE_CTX *ctx) -{ - switch (X509_STORE_CTX_get_error(ctx)) { - case X509_V_OK: - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - break; - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - break; - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - break; - case X509_V_ERR_NO_EXPLICIT_POLICY: - break; - } - return ok; -} - -int -ca_X509_verify(void *certificate, void *chain, const char *CAfile, - const char *CRLfile, const char **errstr) -{ - X509_STORE *store = NULL; - X509_STORE_CTX *xsc = NULL; - int ret = 0; - long error = 0; - - if ((store = X509_STORE_new()) == NULL) - goto end; - - if (!X509_STORE_load_locations(store, CAfile, NULL)) { - log_warn("warn: unable to load CA file %s", CAfile); - goto end; - } - X509_STORE_set_default_paths(store); - - if ((xsc = X509_STORE_CTX_new()) == NULL) - goto end; - - if (X509_STORE_CTX_init(xsc, store, certificate, chain) != 1) - goto end; - - X509_STORE_CTX_set_verify_cb(xsc, ca_verify_cb); - - ret = X509_verify_cert(xsc); - -end: - *errstr = NULL; - if (ret != 1) { - if (xsc) { - error = X509_STORE_CTX_get_error(xsc); - *errstr = X509_verify_cert_error_string(error); - } - else if (ERR_peek_last_error()) - *errstr = ERR_error_string(ERR_peek_last_error(), NULL); - } - - X509_STORE_CTX_free(xsc); - X509_STORE_free(store); - - return ret > 0 ? 1 : 0; -} - -void -ca_imsg(struct mproc *p, struct imsg *imsg) -{ - RSA *rsa = NULL; -#if defined(SUPPORT_ECDSA) - EC_KEY *ecdsa = NULL; -#endif - const void *from = NULL; - unsigned char *to = NULL; - struct msg m; - const char *pkiname; - size_t flen, tlen, padding; -#if defined(SUPPORT_ECDSA) - int buf_len; -#endif - struct pki *pki; - int ret = 0; - uint64_t id; - int v; - - if (imsg == NULL) - ca_shutdown(); - - switch (imsg->hdr.type) { - case IMSG_CONF_START: - return; - case IMSG_CONF_END: - ca_init(); - - /* Start fulfilling requests */ - mproc_enable(p_pony); - return; - - case IMSG_CTL_VERBOSE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - log_trace_verbose(v); - return; - - case IMSG_CTL_PROFILE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - profiling = v; - return; - - case IMSG_CA_RSA_PRIVENC: - case IMSG_CA_RSA_PRIVDEC: - m_msg(&m, imsg); - m_get_id(&m, &id); - m_get_string(&m, &pkiname); - m_get_data(&m, &from, &flen); - m_get_size(&m, &tlen); - m_get_size(&m, &padding); - m_end(&m); - - pki = dict_get(env->sc_pki_dict, pkiname); - if (pki == NULL || pki->pki_pkey == NULL || - (rsa = EVP_PKEY_get1_RSA(pki->pki_pkey)) == NULL) - fatalx("ca_imsg: invalid pki"); - - if ((to = calloc(1, tlen)) == NULL) - fatalx("ca_imsg: calloc"); - - switch (imsg->hdr.type) { - case IMSG_CA_RSA_PRIVENC: - ret = RSA_private_encrypt(flen, from, to, rsa, - padding); - break; - case IMSG_CA_RSA_PRIVDEC: - ret = RSA_private_decrypt(flen, from, to, rsa, - padding); - break; - } - - m_create(p, imsg->hdr.type, 0, 0, -1); - m_add_id(p, id); - m_add_int(p, ret); - if (ret > 0) - m_add_data(p, to, (size_t)ret); - m_close(p); - - free(to); - RSA_free(rsa); - return; - -#if defined(SUPPORT_ECDSA) - case IMSG_CA_ECDSA_SIGN: - m_msg(&m, imsg); - m_get_id(&m, &id); - m_get_string(&m, &pkiname); - m_get_data(&m, &from, &flen); - m_end(&m); - - pki = dict_get(env->sc_pki_dict, pkiname); - if (pki == NULL || pki->pki_pkey == NULL || - (ecdsa = EVP_PKEY_get1_EC_KEY(pki->pki_pkey)) == NULL) - fatalx("ca_imsg: invalid pki"); - - buf_len = ECDSA_size(ecdsa); - if ((to = calloc(1, buf_len)) == NULL) - fatalx("ca_imsg: calloc"); - ret = ECDSA_sign(0, from, flen, to, &buf_len, ecdsa); - m_create(p, imsg->hdr.type, 0, 0, -1); - m_add_id(p, id); - m_add_int(p, ret); - if (ret > 0) - m_add_data(p, to, (size_t)buf_len); - m_close(p); - free(to); - EC_KEY_free(ecdsa); - return; -#endif - } - errx(1, "ca_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -/* - * RSA privsep engine (called from unprivileged processes) - */ - -const RSA_METHOD *rsa_default = NULL; - -static RSA_METHOD *rsae_method = NULL; - -static int -rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, - RSA *rsa, int padding, unsigned int cmd) -{ - int ret = 0; - struct imsgbuf *ibuf; - struct imsg imsg; - int n, done = 0; - const void *toptr; - char *pkiname; - size_t tlen; - struct msg m; - uint64_t id; - - if ((pkiname = RSA_get_ex_data(rsa, 0)) == NULL) - return (0); - - /* - * Send a synchronous imsg because we cannot defer the RSA - * operation in OpenSSL's engine layer. - */ - m_create(p_ca, cmd, 0, 0, -1); - reqid++; - m_add_id(p_ca, reqid); - m_add_string(p_ca, pkiname); - m_add_data(p_ca, (const void *)from, (size_t)flen); - m_add_size(p_ca, (size_t)RSA_size(rsa)); - m_add_size(p_ca, (size_t)padding); - m_flush(p_ca); - - ibuf = &p_ca->imsgbuf; - - while (!done) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatalx("imsg_read"); - if (n == 0) - fatalx("pipe closed"); - - while (!done) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatalx("imsg_get error"); - if (n == 0) - break; - - log_imsg(PROC_PONY, PROC_CA, &imsg); - - switch (imsg.hdr.type) { - case IMSG_CA_RSA_PRIVENC: - case IMSG_CA_RSA_PRIVDEC: - break; - default: - /* Another imsg is queued up in the buffer */ - pony_imsg(p_ca, &imsg); - imsg_free(&imsg); - continue; - } - - m_msg(&m, &imsg); - m_get_id(&m, &id); - if (id != reqid) - fatalx("invalid response id"); - m_get_int(&m, &ret); - if (ret > 0) - m_get_data(&m, &toptr, &tlen); - m_end(&m); - - if (ret > 0) - memcpy(to, toptr, tlen); - done = 1; - - imsg_free(&imsg); - } - } - mproc_event_add(p_ca); - - return (ret); -} - -static int -rsae_pub_enc(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, - int padding) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (RSA_meth_get_pub_enc(rsa_default)(flen, from, to, rsa, padding)); -} - -static int -rsae_pub_dec(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, - int padding) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (RSA_meth_get_pub_dec(rsa_default)(flen, from, to, rsa, padding)); -} - -static int -rsae_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, - int padding) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (RSA_get_ex_data(rsa, 0) != NULL) - return (rsae_send_imsg(flen, from, to, rsa, padding, - IMSG_CA_RSA_PRIVENC)); - return (RSA_meth_get_priv_enc(rsa_default)(flen, from, to, rsa, padding)); -} - -static int -rsae_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, - int padding) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (RSA_get_ex_data(rsa, 0) != NULL) - return (rsae_send_imsg(flen, from, to, rsa, padding, - IMSG_CA_RSA_PRIVDEC)); - - return (RSA_meth_get_priv_dec(rsa_default)(flen, from, to, rsa, padding)); -} - -static int -rsae_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (RSA_meth_get_mod_exp(rsa_default)(r0, I, rsa, ctx)); -} - -static int -rsae_bn_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (RSA_meth_get_bn_mod_exp(rsa_default)(r, a, p, m, ctx, m_ctx)); -} - -static int -rsae_init(RSA *rsa) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (RSA_meth_get_init(rsa_default) == NULL) - return (1); - return (RSA_meth_get_init(rsa_default)(rsa)); -} - -static int -rsae_finish(RSA *rsa) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (RSA_meth_get_finish(rsa_default) == NULL) - return (1); - return (RSA_meth_get_finish(rsa_default)(rsa)); -} - -static int -rsae_keygen(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (RSA_meth_get_keygen(rsa_default)(rsa, bits, e, cb)); -} - - -#if defined(SUPPORT_ECDSA) -/* - * ECDSA privsep engine (called from unprivileged processes) - */ - -const ECDSA_METHOD *ecdsa_default = NULL; - -static ECDSA_METHOD *ecdsae_method = NULL; - -ECDSA_METHOD * -ECDSA_METHOD_new_temporary(const char *name, int); - -ECDSA_METHOD * -ECDSA_METHOD_new_temporary(const char *name, int flags) -{ - ECDSA_METHOD *ecdsa; - - if ((ecdsa = calloc(1, sizeof (*ecdsa))) == NULL) - return NULL; - - if ((ecdsa->name = strdup(name)) == NULL) { - free(ecdsa); - return NULL; - } - - ecdsa->flags = flags; - return ecdsa; -} - -static ECDSA_SIG * -ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, - const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) -{ - int ret = 0; - struct imsgbuf *ibuf; - struct imsg imsg; - int n, done = 0; - const void *toptr; - char *pkiname; - size_t tlen; - struct msg m; - uint64_t id; - ECDSA_SIG *sig = NULL; - - if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL) - return (0); - - /* - * Send a synchronous imsg because we cannot defer the ECDSA - * operation in OpenSSL's engine layer. - */ - m_create(p_ca, IMSG_CA_ECDSA_SIGN, 0, 0, -1); - reqid++; - m_add_id(p_ca, reqid); - m_add_string(p_ca, pkiname); - m_add_data(p_ca, (const void *)dgst, (size_t)dgst_len); - m_flush(p_ca); - - ibuf = &p_ca->imsgbuf; - - while (!done) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatalx("imsg_read"); - if (n == 0) - fatalx("pipe closed"); - while (!done) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - fatalx("imsg_get error"); - if (n == 0) - break; - - log_imsg(PROC_PONY, PROC_CA, &imsg); - - switch (imsg.hdr.type) { - case IMSG_CA_ECDSA_SIGN: - break; - default: - /* Another imsg is queued up in the buffer */ - pony_imsg(p_ca, &imsg); - imsg_free(&imsg); - continue; - } - - m_msg(&m, &imsg); - m_get_id(&m, &id); - if (id != reqid) - fatalx("invalid response id"); - m_get_int(&m, &ret); - if (ret > 0) - m_get_data(&m, &toptr, &tlen); - m_end(&m); - done = 1; - - if (ret > 0) - d2i_ECDSA_SIG(&sig, (const unsigned char **)&toptr, tlen); - imsg_free(&imsg); - } - } - mproc_event_add(p_ca); - - return (sig); -} - -ECDSA_SIG * -ecdsae_do_sign(const unsigned char *dgst, int dgst_len, - const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - if (ECDSA_get_ex_data(eckey, 0) != NULL) - return (ecdsae_send_enc_imsg(dgst, dgst_len, inv, rp, eckey)); - return (ecdsa_default->ecdsa_do_sign(dgst, dgst_len, inv, rp, eckey)); -} - -int -ecdsae_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, - BIGNUM **r) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (ecdsa_default->ecdsa_sign_setup(eckey, ctx, kinv, r)); -} - -int -ecdsae_do_verify(const unsigned char *dgst, int dgst_len, - const ECDSA_SIG *sig, EC_KEY *eckey) -{ - log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); - return (ecdsa_default->ecdsa_do_verify(dgst, dgst_len, sig, eckey)); -} -#endif - -static void -rsa_engine_init(void) -{ - ENGINE *e; - const char *errstr, *name; - - if ((rsae_method = RSA_meth_new("RSA privsep engine", 0)) == NULL) { - errstr = "RSA_meth_new"; - goto fail; - } - - RSA_meth_set_pub_enc(rsae_method, rsae_pub_enc); - RSA_meth_set_pub_dec(rsae_method, rsae_pub_dec); - RSA_meth_set_priv_enc(rsae_method, rsae_priv_enc); - RSA_meth_set_priv_dec(rsae_method, rsae_priv_dec); - RSA_meth_set_mod_exp(rsae_method, rsae_mod_exp); - RSA_meth_set_bn_mod_exp(rsae_method, rsae_bn_mod_exp); - RSA_meth_set_init(rsae_method, rsae_init); - RSA_meth_set_finish(rsae_method, rsae_finish); - RSA_meth_set_keygen(rsae_method, rsae_keygen); - - if ((e = ENGINE_get_default_RSA()) == NULL) { - if ((e = ENGINE_new()) == NULL) { - errstr = "ENGINE_new"; - goto fail; - } - if (!ENGINE_set_name(e, RSA_meth_get0_name(rsae_method))) { - errstr = "ENGINE_set_name"; - goto fail; - } - if ((rsa_default = RSA_get_default_method()) == NULL) { - errstr = "RSA_get_default_method"; - goto fail; - } - } else if ((rsa_default = ENGINE_get_RSA(e)) == NULL) { - errstr = "ENGINE_get_RSA"; - goto fail; - } - - if ((name = ENGINE_get_name(e)) == NULL) - name = "unknown RSA engine"; - - log_debug("debug: %s: using %s", __func__, name); - - if (RSA_meth_get_mod_exp(rsa_default) == NULL) - RSA_meth_set_mod_exp(rsae_method, NULL); - if (RSA_meth_get_bn_mod_exp(rsa_default) == NULL) - RSA_meth_set_bn_mod_exp(rsae_method, NULL); - if (RSA_meth_get_keygen(rsa_default) == NULL) - RSA_meth_set_keygen(rsae_method, NULL); - RSA_meth_set_flags(rsae_method, - RSA_meth_get_flags(rsa_default) | RSA_METHOD_FLAG_NO_CHECK); - RSA_meth_set0_app_data(rsae_method, - RSA_meth_get0_app_data(rsa_default)); - - if (!ENGINE_set_RSA(e, rsae_method)) { - errstr = "ENGINE_set_RSA"; - goto fail; - } - if (!ENGINE_set_default_RSA(e)) { - errstr = "ENGINE_set_default_RSA"; - goto fail; - } - - return; - - fail: - ssl_error(errstr); - fatalx("%s", errstr); -} - -#if defined(SUPPORT_ECDSA) -static void -ecdsa_engine_init(void) -{ - ENGINE *e; - const char *errstr, *name; - - if ((ecdsae_method = ECDSA_METHOD_new_temporary("ECDSA privsep engine", 0)) == NULL) { - errstr = "ECDSA_METHOD_new_temporary"; - goto fail; - } - - ecdsae_method->ecdsa_do_sign = ecdsae_do_sign; - ecdsae_method->ecdsa_sign_setup = ecdsae_sign_setup; - ecdsae_method->ecdsa_do_verify = ecdsae_do_verify; - - if ((e = ENGINE_get_default_ECDSA()) == NULL) { - if ((e = ENGINE_new()) == NULL) { - errstr = "ENGINE_new"; - goto fail; - } - if (!ENGINE_set_name(e, ecdsae_method->name)) { - errstr = "ENGINE_set_name"; - goto fail; - } - if ((ecdsa_default = ECDSA_get_default_method()) == NULL) { - errstr = "ECDSA_get_default_method"; - goto fail; - } - } else if ((ecdsa_default = ENGINE_get_ECDSA(e)) == NULL) { - errstr = "ENGINE_get_ECDSA"; - goto fail; - } - - if ((name = ENGINE_get_name(e)) == NULL) - name = "unknown ECDSA engine"; - - log_debug("debug: %s: using %s", __func__, name); - - if (!ENGINE_set_ECDSA(e, ecdsae_method)) { - errstr = "ENGINE_set_ECDSA"; - goto fail; - } - if (!ENGINE_set_default_ECDSA(e)) { - errstr = "ENGINE_set_default_ECDSA"; - goto fail; - } - - return; - - fail: - ssl_error(errstr); - fatalx("%s", errstr); -} -#endif - -void -ca_engine_init(void) -{ - rsa_engine_init(); -#if defined(SUPPORT_ECDSA) - ecdsa_engine_init(); -#endif -} diff --git a/smtpd/cert.c b/smtpd/cert.c deleted file mode 100644 index 79b1df91..00000000 --- a/smtpd/cert.c +++ /dev/null @@ -1,416 +0,0 @@ -/* $OpenBSD: cert.c,v 1.2 2018/12/11 07:25:57 eric Exp $ */ - -/* - * Copyright (c) 2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "smtpd.h" -#include "ssl.h" - -#define p_cert p_lka - -struct request { - SPLAY_ENTRY(request) entry; - uint32_t id; - void (*cb_get_certificate)(void *, int, const char *, - const void *, size_t); - void (*cb_verify)(void *, int); - void *arg; -}; - -#define MAX_CERTS 16 -#define MAX_CERT_LEN (MAX_IMSGSIZE - (IMSG_HEADER_SIZE + sizeof(size_t))) - -struct session { - SPLAY_ENTRY(session) entry; - uint32_t id; - struct mproc *proc; - char *cert[MAX_CERTS]; - size_t cert_len[MAX_CERTS]; - int cert_count; -}; - -SPLAY_HEAD(cert_reqtree, request); -SPLAY_HEAD(cert_sestree, session); - -static int request_cmp(struct request *, struct request *); -static int session_cmp(struct session *, struct session *); -SPLAY_PROTOTYPE(cert_reqtree, request, entry, request_cmp); -SPLAY_PROTOTYPE(cert_sestree, session, entry, session_cmp); - -static void cert_do_verify(struct session *, const char *, int); -static int cert_X509_verify(struct session *, const char *, const char *); - -static struct cert_reqtree reqs = SPLAY_INITIALIZER(&reqs); -static struct cert_sestree sess = SPLAY_INITIALIZER(&sess); - -int -cert_init(const char *name, int fallback, void (*cb)(void *, int, - const char *, const void *, size_t), void *arg) -{ - struct request *req; - - req = calloc(1, sizeof(*req)); - if (req == NULL) { - cb(arg, CA_FAIL, NULL, NULL, 0); - return 0; - } - while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) - req->id = arc4random(); - req->cb_get_certificate = cb; - req->arg = arg; - SPLAY_INSERT(cert_reqtree, &reqs, req); - - m_create(p_cert, IMSG_CERT_INIT, req->id, 0, -1); - m_add_string(p_cert, name); - m_add_int(p_cert, fallback); - m_close(p_cert); - - return 1; -} - -int -cert_verify(const void *ssl, const char *name, int fallback, - void (*cb)(void *, int), void *arg) -{ - struct request *req; - X509 *x; - STACK_OF(X509) *xchain; - unsigned char *cert_der[MAX_CERTS]; - int cert_len[MAX_CERTS]; - int i, cert_count, ret; - - x = SSL_get_peer_certificate(ssl); - if (x == NULL) { - cb(arg, CERT_NOCERT); - return 0; - } - - ret = 0; - memset(cert_der, 0, sizeof(cert_der)); - - req = calloc(1, sizeof(*req)); - if (req == NULL) - goto end; - while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) - req->id = arc4random(); - req->cb_verify = cb; - req->arg = arg; - SPLAY_INSERT(cert_reqtree, &reqs, req); - - cert_count = 1; - if ((xchain = SSL_get_peer_cert_chain(ssl))) { - cert_count += sk_X509_num(xchain); - if (cert_count > MAX_CERTS) { - log_warnx("warn: certificate chain too long"); - goto end; - } - } - - for (i = 0; i < cert_count; ++i) { - if (i != 0) { - if ((x = sk_X509_value(xchain, i - 1)) == NULL) { - log_warnx("warn: failed to retrieve certificate"); - goto end; - } - } - - cert_len[i] = i2d_X509(x, &cert_der[i]); - if (i == 0) - X509_free(x); - - if (cert_len[i] < 0) { - log_warnx("warn: failed to encode certificate"); - goto end; - } - - log_debug("debug: certificate %i: len=%d", i, cert_len[i]); - if (cert_len[i] > (int)MAX_CERT_LEN) { - log_warnx("warn: certificate too long"); - goto end; - } - } - - /* Send the cert chain, one cert at a time */ - for (i = 0; i < cert_count; ++i) { - m_create(p_cert, IMSG_CERT_CERTIFICATE, req->id, 0, -1); - m_add_data(p_cert, cert_der[i], cert_len[i]); - m_close(p_cert); - } - - /* Tell lookup process that it can start verifying, we're done */ - m_create(p_cert, IMSG_CERT_VERIFY, req->id, 0, -1); - m_add_string(p_cert, name); - m_add_int(p_cert, fallback); - m_close(p_cert); - - ret = 1; - - end: - for (i = 0; i < MAX_CERTS; ++i) - free(cert_der[i]); - - if (ret == 0) { - if (req) - SPLAY_REMOVE(cert_reqtree, &reqs, req); - free(req); - cb(arg, CERT_ERROR); - } - - return ret; -} - - -void -cert_dispatch_request(struct mproc *proc, struct imsg *imsg) -{ - struct pki *pki; - struct session key, *s; - const char *name; - const void *data; - size_t datalen; - struct msg m; - uint32_t reqid; - char buf[LINE_MAX]; - int fallback; - - reqid = imsg->hdr.peerid; - m_msg(&m, imsg); - - switch (imsg->hdr.type) { - - case IMSG_CERT_INIT: - m_get_string(&m, &name); - m_get_int(&m, &fallback); - m_end(&m); - - xlowercase(buf, name, sizeof(buf)); - log_debug("debug: looking up pki \"%s\"", buf); - pki = dict_get(env->sc_pki_dict, buf); - if (pki == NULL && fallback) - pki = dict_get(env->sc_pki_dict, "*"); - - m_create(proc, IMSG_CERT_INIT, reqid, 0, -1); - if (pki) { - m_add_int(proc, CA_OK); - m_add_string(proc, pki->pki_name); - m_add_data(proc, pki->pki_cert, pki->pki_cert_len); - } else { - m_add_int(proc, CA_FAIL); - m_add_string(proc, NULL); - m_add_data(proc, NULL, 0); - } - m_close(proc); - return; - - case IMSG_CERT_CERTIFICATE: - m_get_data(&m, &data, &datalen); - m_end(&m); - - key.id = reqid; - key.proc = proc; - s = SPLAY_FIND(cert_sestree, &sess, &key); - if (s == NULL) { - s = calloc(1, sizeof(*s)); - s->proc = proc; - s->id = reqid; - SPLAY_INSERT(cert_sestree, &sess, s); - } - - if (s->cert_count == MAX_CERTS) - fatalx("%s: certificate chain too long", __func__); - - s->cert[s->cert_count] = xmemdup(data, datalen); - s->cert_len[s->cert_count] = datalen; - s->cert_count++; - return; - - case IMSG_CERT_VERIFY: - m_get_string(&m, &name); - m_get_int(&m, &fallback); - m_end(&m); - - key.id = reqid; - key.proc = proc; - s = SPLAY_FIND(cert_sestree, &sess, &key); - if (s == NULL) - fatalx("%s: no certificate", __func__); - - SPLAY_REMOVE(cert_sestree, &sess, s); - cert_do_verify(s, name, fallback); - return; - - default: - fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); - } -} - -void -cert_dispatch_result(struct mproc *proc, struct imsg *imsg) -{ - struct request key, *req; - struct msg m; - const void *cert; - const char *name; - size_t cert_len; - int res; - - key.id = imsg->hdr.peerid; - req = SPLAY_FIND(cert_reqtree, &reqs, &key); - if (req == NULL) - fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); - - m_msg(&m, imsg); - - switch (imsg->hdr.type) { - - case IMSG_CERT_INIT: - m_get_int(&m, &res); - m_get_string(&m, &name); - m_get_data(&m, &cert, &cert_len); - m_end(&m); - SPLAY_REMOVE(cert_reqtree, &reqs, req); - req->cb_get_certificate(req->arg, res, name, cert, cert_len); - free(req); - break; - - case IMSG_CERT_VERIFY: - m_get_int(&m, &res); - m_end(&m); - SPLAY_REMOVE(cert_reqtree, &reqs, req); - req->cb_verify(req->arg, res); - free(req); - break; - } -} - -static void -cert_do_verify(struct session *s, const char *name, int fallback) -{ - struct ca *ca; - const char *cafile; - int i, res; - - ca = dict_get(env->sc_ca_dict, name); - if (ca == NULL) - if (fallback) - ca = dict_get(env->sc_ca_dict, "*"); - cafile = ca ? ca->ca_cert_file : CA_FILE; - - if (ca == NULL && !fallback) - res = CERT_NOCA; - else if (!cert_X509_verify(s, cafile, NULL)) - res = CERT_INVALID; - else - res = CERT_OK; - - for (i = 0; i < s->cert_count; ++i) - free(s->cert[i]); - - m_create(s->proc, IMSG_CERT_VERIFY, s->id, 0, -1); - m_add_int(s->proc, res); - m_close(s->proc); - - free(s); -} - -static int -cert_X509_verify(struct session *s, const char *CAfile, - const char *CRLfile) -{ - X509 *x509; - X509 *x509_tmp; - STACK_OF(X509) *x509_chain; - const unsigned char *d2i; - int i, ret = 0; - const char *errstr; - - x509 = NULL; - x509_tmp = NULL; - x509_chain = NULL; - - d2i = s->cert[0]; - if (d2i_X509(&x509, &d2i, s->cert_len[0]) == NULL) { - x509 = NULL; - goto end; - } - - if (s->cert_count > 1) { - x509_chain = sk_X509_new_null(); - for (i = 1; i < s->cert_count; ++i) { - d2i = s->cert[i]; - if (d2i_X509(&x509_tmp, &d2i, s->cert_len[i]) == NULL) - goto end; - sk_X509_insert(x509_chain, x509_tmp, i); - x509_tmp = NULL; - } - } - if (!ca_X509_verify(x509, x509_chain, CAfile, NULL, &errstr)) - log_debug("debug: X509 verify: %s", errstr); - else - ret = 1; - -end: - X509_free(x509); - X509_free(x509_tmp); - if (x509_chain) - sk_X509_pop_free(x509_chain, X509_free); - - return ret; -} - -static int -request_cmp(struct request *a, struct request *b) -{ - if (a->id < b->id) - return -1; - if (a->id > b->id) - return 1; - return 0; -} - -SPLAY_GENERATE(cert_reqtree, request, entry, request_cmp); - -static int -session_cmp(struct session *a, struct session *b) -{ - if (a->id < b->id) - return -1; - if (a->id > b->id) - return 1; - if (a->proc < b->proc) - return -1; - if (a->proc > b->proc) - return 1; - return 0; -} - -SPLAY_GENERATE(cert_sestree, session, entry, session_cmp); diff --git a/smtpd/compress_backend.c b/smtpd/compress_backend.c deleted file mode 100644 index 1b974662..00000000 --- a/smtpd/compress_backend.c +++ /dev/null @@ -1,72 +0,0 @@ -/* $OpenBSD: compress_backend.c,v 1.9 2015/01/20 17:37:54 deraadt Exp $ */ - -/* - * Copyright (c) 2012 Charles Longeau - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" - -#define BUFFER_SIZE 16364 - -extern struct compress_backend compress_gzip; - -struct compress_backend * -compress_backend_lookup(const char *name) -{ - if (!strcmp(name, "gzip")) - return &compress_gzip; - - return NULL; -} - -size_t -compress_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) -{ - return (env->sc_comp->compress_chunk(ib, ibsz, ob, obsz)); -} - -size_t -uncompress_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) -{ - return (env->sc_comp->uncompress_chunk(ib, ibsz, ob, obsz)); -} - -int -compress_file(FILE *ifile, FILE *ofile) -{ - return (env->sc_comp->compress_file(ifile, ofile)); -} - -int -uncompress_file(FILE *ifile, FILE *ofile) -{ - return (env->sc_comp->uncompress_file(ifile, ofile)); -} diff --git a/smtpd/compress_gzip.c b/smtpd/compress_gzip.c deleted file mode 100644 index dd60aeec..00000000 --- a/smtpd/compress_gzip.c +++ /dev/null @@ -1,186 +0,0 @@ -/* $OpenBSD: compress_gzip.c,v 1.10 2015/12/28 22:08:30 jung Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * Copyright (c) 2012 Charles Longeau - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "smtpd.h" -#include "log.h" - - -#define GZIP_BUFFER_SIZE 16384 - - -static size_t compress_gzip_chunk(void *, size_t, void *, size_t); -static size_t uncompress_gzip_chunk(void *, size_t, void *, size_t); -static int compress_gzip_file(FILE *, FILE *); -static int uncompress_gzip_file(FILE *, FILE *); - - -struct compress_backend compress_gzip = { - compress_gzip_chunk, - uncompress_gzip_chunk, - - compress_gzip_file, - uncompress_gzip_file, -}; - -static size_t -compress_gzip_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) -{ - z_stream *strm; - size_t ret = 0; - - if ((strm = calloc(1, sizeof *strm)) == NULL) - return 0; - - strm->zalloc = Z_NULL; - strm->zfree = Z_NULL; - strm->opaque = Z_NULL; - if (deflateInit2(strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, - (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) - goto end; - - strm->avail_in = ibsz; - strm->next_in = (unsigned char *)ib; - strm->avail_out = obsz; - strm->next_out = (unsigned char *)ob; - if (deflate(strm, Z_FINISH) != Z_STREAM_END) - goto end; - - ret = strm->total_out; - -end: - deflateEnd(strm); - free(strm); - return ret; -} - - -static size_t -uncompress_gzip_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) -{ - z_stream *strm; - size_t ret = 0; - - if ((strm = calloc(1, sizeof *strm)) == NULL) - return 0; - - strm->zalloc = Z_NULL; - strm->zfree = Z_NULL; - strm->opaque = Z_NULL; - strm->avail_in = 0; - strm->next_in = Z_NULL; - - if (inflateInit2(strm, (15+16)) != Z_OK) - goto end; - - strm->avail_in = ibsz; - strm->next_in = (unsigned char *)ib; - strm->avail_out = obsz; - strm->next_out = (unsigned char *)ob; - - if (inflate(strm, Z_FINISH) != Z_STREAM_END) - goto end; - - ret = strm->total_out; - -end: - deflateEnd(strm); - free(strm); - return ret; -} - - -static int -compress_gzip_file(FILE *in, FILE *out) -{ - gzFile gzf; - char ibuf[GZIP_BUFFER_SIZE]; - int r, w; - int ret = 0; - - if (in == NULL || out == NULL) - return (0); - - gzf = gzdopen(fileno(out), "wb"); - if (gzf == NULL) - return (0); - - while ((r = fread(ibuf, 1, GZIP_BUFFER_SIZE, in)) != 0) { - if ((w = gzwrite(gzf, ibuf, r)) != r) - goto end; - } - if (!feof(in)) - goto end; - - ret = 1; - -end: - gzclose(gzf); - return (ret); -} - - -static int -uncompress_gzip_file(FILE *in, FILE *out) -{ - gzFile gzf; - char obuf[GZIP_BUFFER_SIZE]; - int r, w; - int ret = 0; - - if (in == NULL || out == NULL) - return (0); - - gzf = gzdopen(fileno(in), "r"); - if (gzf == NULL) - return (0); - - while ((r = gzread(gzf, obuf, sizeof(obuf))) > 0) { - if ((w = fwrite(obuf, r, 1, out)) != 1) - goto end; - } - if (!gzeof(gzf)) - goto end; - - ret = 1; - -end: - gzclose(gzf); - return (ret); -} diff --git a/smtpd/config.c b/smtpd/config.c deleted file mode 100644 index 8fe983d6..00000000 --- a/smtpd/config.c +++ /dev/null @@ -1,350 +0,0 @@ -/* $OpenBSD: config.c,v 1.51 2019/12/18 10:00:39 gilles Exp $ */ - -/* - * Copyright (c) 2008 Pierre-Yves Ritschard - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - -void set_local(struct smtpd *, const char *); -void set_localaddrs(struct smtpd *, struct table *); - -struct smtpd * -config_default(void) -{ - struct smtpd *conf = NULL; - struct mta_limits *limits = NULL; - struct table *t = NULL; - char hostname[HOST_NAME_MAX+1]; - - if (getmailname(hostname, sizeof hostname) == -1) - return NULL; - - if ((conf = calloc(1, sizeof(*conf))) == NULL) - return conf; - - (void)strlcpy(conf->sc_hostname, hostname, sizeof(conf->sc_hostname)); - - conf->sc_maxsize = DEFAULT_MAX_BODY_SIZE; - conf->sc_subaddressing_delim = SUBADDRESSING_DELIMITER; - conf->sc_ttl = SMTPD_QUEUE_EXPIRY; - conf->sc_srs_ttl = SMTPD_QUEUE_EXPIRY / 86400; - - conf->sc_mta_max_deferred = 100; - conf->sc_scheduler_max_inflight = 5000; - conf->sc_scheduler_max_schedule = 10; - conf->sc_scheduler_max_evp_batch_size = 256; - conf->sc_scheduler_max_msg_batch_size = 1024; - - conf->sc_session_max_rcpt = 1000; - conf->sc_session_max_mails = 100; - - conf->sc_mda_max_session = 50; - conf->sc_mda_max_user_session = 7; - conf->sc_mda_task_hiwat = 50; - conf->sc_mda_task_lowat = 30; - conf->sc_mda_task_release = 10; - - /* Report mails delayed for more than 4 hours */ - conf->sc_bounce_warn[0] = 3600 * 4; - - conf->sc_tables_dict = calloc(1, sizeof(*conf->sc_tables_dict)); - conf->sc_rules = calloc(1, sizeof(*conf->sc_rules)); - conf->sc_dispatchers = calloc(1, sizeof(*conf->sc_dispatchers)); - conf->sc_listeners = calloc(1, sizeof(*conf->sc_listeners)); - conf->sc_ca_dict = calloc(1, sizeof(*conf->sc_ca_dict)); - conf->sc_pki_dict = calloc(1, sizeof(*conf->sc_pki_dict)); - conf->sc_ssl_dict = calloc(1, sizeof(*conf->sc_ssl_dict)); - conf->sc_limits_dict = calloc(1, sizeof(*conf->sc_limits_dict)); - conf->sc_mda_wrappers = calloc(1, sizeof(*conf->sc_mda_wrappers)); - conf->sc_filter_processes_dict = calloc(1, sizeof(*conf->sc_filter_processes_dict)); - conf->sc_dispatcher_bounce = calloc(1, sizeof(*conf->sc_dispatcher_bounce)); - conf->sc_filters_dict = calloc(1, sizeof(*conf->sc_filters_dict)); - limits = calloc(1, sizeof(*limits)); - - if (conf->sc_tables_dict == NULL || - conf->sc_rules == NULL || - conf->sc_dispatchers == NULL || - conf->sc_listeners == NULL || - conf->sc_ca_dict == NULL || - conf->sc_pki_dict == NULL || - conf->sc_ssl_dict == NULL || - conf->sc_limits_dict == NULL || - conf->sc_mda_wrappers == NULL || - conf->sc_filter_processes_dict == NULL || - conf->sc_dispatcher_bounce == NULL || - conf->sc_filters_dict == NULL || - limits == NULL) - goto error; - - dict_init(conf->sc_dispatchers); - dict_init(conf->sc_mda_wrappers); - dict_init(conf->sc_ca_dict); - dict_init(conf->sc_pki_dict); - dict_init(conf->sc_ssl_dict); - dict_init(conf->sc_tables_dict); - dict_init(conf->sc_limits_dict); - dict_init(conf->sc_filter_processes_dict); - - limit_mta_set_defaults(limits); - - dict_xset(conf->sc_limits_dict, "default", limits); - - TAILQ_INIT(conf->sc_listeners); - TAILQ_INIT(conf->sc_rules); - - - /* bounce dispatcher */ - conf->sc_dispatcher_bounce->type = DISPATCHER_BOUNCE; - - /* - * declare special "localhost", "anyhost" and "localnames" tables - */ - set_local(conf, conf->sc_hostname); - - t = table_create(conf, "static", "", NULL); - table_add(t, "*", NULL); - - hostname[strcspn(hostname, ".")] = '\0'; - if (strcmp(conf->sc_hostname, hostname) != 0) - table_add(t, hostname, NULL); - - table_create(conf, "getpwnam", "", NULL); - - return conf; - -error: - free(conf->sc_tables_dict); - free(conf->sc_rules); - free(conf->sc_dispatchers); - free(conf->sc_listeners); - free(conf->sc_ca_dict); - free(conf->sc_pki_dict); - free(conf->sc_ssl_dict); - free(conf->sc_limits_dict); - free(conf->sc_mda_wrappers); - free(conf->sc_filter_processes_dict); - free(conf->sc_dispatcher_bounce); - free(conf->sc_filters_dict); - free(limits); - free(conf); - return NULL; -} - -void -set_local(struct smtpd *conf, const char *hostname) -{ - struct table *t; - - t = table_create(conf, "static", "", NULL); - table_add(t, "localhost", NULL); - table_add(t, hostname, NULL); - - set_localaddrs(conf, t); -} - -void -set_localaddrs(struct smtpd *conf, struct table *localnames) -{ - struct ifaddrs *ifap, *p; - struct sockaddr_storage ss; - struct sockaddr_in *sain; - struct sockaddr_in6 *sin6; - struct table *t; - char buf[NI_MAXHOST + 5]; - - t = table_create(conf, "static", "", NULL); - table_add(t, "local", NULL); - table_add(t, "0.0.0.0/0", NULL); - table_add(t, "::/0", NULL); - - if (getifaddrs(&ifap) == -1) - fatal("getifaddrs"); - - t = table_create(conf, "static", "", NULL); - table_add(t, "local", NULL); - - for (p = ifap; p != NULL; p = p->ifa_next) { - if (p->ifa_addr == NULL) - continue; - switch (p->ifa_addr->sa_family) { - case AF_INET: - sain = (struct sockaddr_in *)&ss; - *sain = *(struct sockaddr_in *)p->ifa_addr; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sain->sin_len = sizeof(struct sockaddr_in); -#endif - table_add(t, ss_to_text(&ss), NULL); - table_add(localnames, ss_to_text(&ss), NULL); - (void)snprintf(buf, sizeof buf, "[%s]", ss_to_text(&ss)); - table_add(localnames, buf, NULL); - break; - - case AF_INET6: - sin6 = (struct sockaddr_in6 *)&ss; - *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sin6->sin6_len = sizeof(struct sockaddr_in6); -#endif - table_add(t, ss_to_text(&ss), NULL); - table_add(localnames, ss_to_text(&ss), NULL); - (void)snprintf(buf, sizeof buf, "[%s]", ss_to_text(&ss)); - table_add(localnames, buf, NULL); - (void)snprintf(buf, sizeof buf, "[ipv6:%s]", ss_to_text(&ss)); - table_add(localnames, buf, NULL); - break; - } - } - - freeifaddrs(ifap); -} - -void -purge_config(uint8_t what) -{ - struct dispatcher *d; - struct listener *l; - struct table *t; - struct rule *r; - struct pki *p; - const char *k; - void *iter_dict; - - if (what & PURGE_LISTENERS) { - while ((l = TAILQ_FIRST(env->sc_listeners)) != NULL) { - TAILQ_REMOVE(env->sc_listeners, l, entry); - free(l); - } - free(env->sc_listeners); - env->sc_listeners = NULL; - } - if (what & PURGE_TABLES) { - while (dict_root(env->sc_tables_dict, NULL, (void **)&t)) - table_destroy(env, t); - free(env->sc_tables_dict); - env->sc_tables_dict = NULL; - } - if (what & PURGE_RULES) { - while ((r = TAILQ_FIRST(env->sc_rules)) != NULL) { - TAILQ_REMOVE(env->sc_rules, r, r_entry); - free(r); - } - free(env->sc_rules); - env->sc_rules = NULL; - } - if (what & PURGE_DISPATCHERS) { - while (dict_poproot(env->sc_dispatchers, (void **)&d)) { - free(d); - } - free(env->sc_dispatchers); - env->sc_dispatchers = NULL; - } - if (what & PURGE_PKI) { - while (dict_poproot(env->sc_pki_dict, (void **)&p)) { - freezero(p->pki_cert, p->pki_cert_len); - freezero(p->pki_key, p->pki_key_len); - EVP_PKEY_free(p->pki_pkey); - free(p); - } - free(env->sc_pki_dict); - env->sc_pki_dict = NULL; - } else if (what & PURGE_PKI_KEYS) { - iter_dict = NULL; - while (dict_iter(env->sc_pki_dict, &iter_dict, &k, - (void **)&p)) { - freezero(p->pki_cert, p->pki_cert_len); - p->pki_cert = NULL; - freezero(p->pki_key, p->pki_key_len); - p->pki_key = NULL; - EVP_PKEY_free(p->pki_pkey); - p->pki_pkey = NULL; - } - } -} - -#ifndef CONFIG_MINIMUM - -void -config_process(enum smtp_proc_type proc) -{ - struct rlimit rl; - - smtpd_process = proc; - setproctitle("%s", proc_title(proc)); - - if (getrlimit(RLIMIT_NOFILE, &rl) == -1) - fatal("fdlimit: getrlimit"); - rl.rlim_cur = rl.rlim_max; - if (setrlimit(RLIMIT_NOFILE, &rl) == -1) - if (errno != EINVAL) - fatal("fdlimit: setrlimit"); -} - -void -config_peer(enum smtp_proc_type proc) -{ - struct mproc *p; - - if (proc == smtpd_process) - fatal("config_peers: cannot peer with oneself"); - - if (proc == PROC_CONTROL) - p = p_control; - else if (proc == PROC_LKA) - p = p_lka; - else if (proc == PROC_PARENT) - p = p_parent; - else if (proc == PROC_QUEUE) - p = p_queue; - else if (proc == PROC_SCHEDULER) - p = p_scheduler; - else if (proc == PROC_PONY) - p = p_pony; - else if (proc == PROC_CA) - p = p_ca; - else - fatalx("bad peer"); - - mproc_enable(p); -} - -#else - -void config_process(enum smtp_proc_type proc) {} -void config_peer(enum smtp_proc_type proc) {} - -#endif diff --git a/smtpd/control.c b/smtpd/control.c deleted file mode 100644 index 0e35bbd1..00000000 --- a/smtpd/control.c +++ /dev/null @@ -1,817 +0,0 @@ -/* $OpenBSD: control.c,v 1.123 2018/05/31 21:06:12 gilles Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define CONTROL_BACKLOG 5 - -struct ctl_conn { - uint32_t id; - uint8_t flags; -#define CTL_CONN_NOTIFY 0x01 - struct mproc mproc; - uid_t euid; - gid_t egid; -}; - -struct { - struct event ev; - int fd; -} control_state; - -static void control_imsg(struct mproc *, struct imsg *); -static void control_shutdown(void); -static void control_listen(void); -static void control_accept(int, short, void *); -static void control_close(struct ctl_conn *); -static void control_dispatch_ext(struct mproc *, struct imsg *); -static void control_digest_update(const char *, size_t, int); -static void control_broadcast_verbose(int, int); - -static struct stat_backend *stat_backend = NULL; -extern const char *backend_stat; - -static uint64_t connid = 0; -static struct tree ctl_conns; -static struct tree ctl_count; -static struct stat_digest digest; - -#define CONTROL_FD_RESERVE 5 -#define CONTROL_MAXCONN_PER_CLIENT 32 - -static void -control_imsg(struct mproc *p, struct imsg *imsg) -{ - struct ctl_conn *c; - struct stat_value val; - struct msg m; - const char *key; - const void *data; - size_t sz; - - if (imsg == NULL) { - if (p->proc != PROC_CLIENT) - control_shutdown(); - return; - } - - switch (imsg->hdr.type) { - case IMSG_CTL_OK: - case IMSG_CTL_FAIL: - case IMSG_CTL_LIST_MESSAGES: - case IMSG_CTL_LIST_ENVELOPES: - case IMSG_CTL_DISCOVER_EVPID: - case IMSG_CTL_DISCOVER_MSGID: - case IMSG_CTL_MTA_SHOW_HOSTS: - case IMSG_CTL_MTA_SHOW_RELAYS: - case IMSG_CTL_MTA_SHOW_ROUTES: - case IMSG_CTL_MTA_SHOW_HOSTSTATS: - case IMSG_CTL_MTA_SHOW_BLOCK: - c = tree_get(&ctl_conns, imsg->hdr.peerid); - if (c == NULL) - return; - imsg->hdr.peerid = 0; - m_forward(&c->mproc, imsg); - return; - - case IMSG_CTL_SMTP_SESSION: - c = tree_get(&ctl_conns, imsg->hdr.peerid); - if (c == NULL) - return; - m_compose(&c->mproc, IMSG_CTL_OK, 0, 0, imsg->fd, NULL, 0); - return; - - case IMSG_STAT_INCREMENT: - m_msg(&m, imsg); - m_get_string(&m, &key); - m_get_data(&m, &data, &sz); - m_end(&m); - if (sz != sizeof(val)) - fatalx("control: IMSG_STAT_INCREMENT size mismatch"); - memmove(&val, data, sz); - if (stat_backend) - stat_backend->increment(key, val.u.counter); - control_digest_update(key, val.u.counter, 1); - return; - - case IMSG_STAT_DECREMENT: - m_msg(&m, imsg); - m_get_string(&m, &key); - m_get_data(&m, &data, &sz); - m_end(&m); - if (sz != sizeof(val)) - fatalx("control: IMSG_STAT_DECREMENT size mismatch"); - memmove(&val, data, sz); - if (stat_backend) - stat_backend->decrement(key, val.u.counter); - control_digest_update(key, val.u.counter, 0); - return; - - case IMSG_STAT_SET: - m_msg(&m, imsg); - m_get_string(&m, &key); - m_get_data(&m, &data, &sz); - m_end(&m); - if (sz != sizeof(val)) - fatalx("control: IMSG_STAT_SET size mismatch"); - memmove(&val, data, sz); - if (stat_backend) - stat_backend->set(key, &val); - return; - } - - errx(1, "control_imsg: unexpected %s imsg", - imsg_to_str(imsg->hdr.type)); -} - -int -control_create_socket(void) -{ - struct sockaddr_un s_un; - int fd; - mode_t old_umask; - - if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) - fatal("control: socket"); - - memset(&s_un, 0, sizeof(s_un)); - s_un.sun_family = AF_UNIX; - if (strlcpy(s_un.sun_path, SMTPD_SOCKET, - sizeof(s_un.sun_path)) >= sizeof(s_un.sun_path)) - fatal("control: socket name too long"); - - if (connect(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) - fatalx("control socket already listening"); - - if (unlink(SMTPD_SOCKET) == -1) - if (errno != ENOENT) - fatal("control: cannot unlink socket"); - - old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); - if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { - (void)umask(old_umask); - fatal("control: bind"); - } - (void)umask(old_umask); - - if (chmod(SMTPD_SOCKET, - S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) == -1) { - (void)unlink(SMTPD_SOCKET); - fatal("control: chmod"); - } - - io_set_nonblocking(fd); - control_state.fd = fd; - - return fd; -} - -int -control(void) -{ - struct passwd *pw; - - purge_config(PURGE_EVERYTHING); - - if ((pw = getpwnam(SMTPD_USER)) == NULL) - fatalx("unknown user " SMTPD_USER); - - stat_backend = env->sc_stat; - stat_backend->init(); - - if (chroot(PATH_CHROOT) == -1) - fatal("control: chroot"); - if (chdir("/") == -1) - fatal("control: chdir(\"/\")"); - - config_process(PROC_CONTROL); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("control: cannot drop privileges"); - - imsg_callback = control_imsg; - event_init(); - - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - tree_init(&ctl_conns); - tree_init(&ctl_count); - - memset(&digest, 0, sizeof digest); - digest.startup = time(NULL); - - config_peer(PROC_SCHEDULER); - config_peer(PROC_QUEUE); - config_peer(PROC_PARENT); - config_peer(PROC_LKA); - config_peer(PROC_PONY); - config_peer(PROC_CA); - - control_listen(); - -#if HAVE_PLEDGE - if (pledge("stdio unix recvfd sendfd", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} - -static void -control_shutdown(void) -{ - log_debug("debug: control agent exiting"); - _exit(0); -} - -static void -control_listen(void) -{ - if (listen(control_state.fd, CONTROL_BACKLOG) == -1) - fatal("control_listen"); - - event_set(&control_state.ev, control_state.fd, EV_READ|EV_PERSIST, - control_accept, NULL); - event_add(&control_state.ev, NULL); -} - -/* ARGSUSED */ -static void -control_accept(int listenfd, short event, void *arg) -{ - int connfd; - socklen_t len; - struct sockaddr_un s_un; - struct ctl_conn *c; - size_t *count; - uid_t euid; - gid_t egid; - -#if defined(HAVE_GETDTABLESIZE) && defined(HAVE_GETDTABLECOUNT) - if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE) - goto pause; -#else - if (available_fds(CONTROL_FD_RESERVE)) - goto pause; -#endif - - len = sizeof(s_un); - if ((connfd = accept(listenfd, (struct sockaddr *)&s_un, &len)) == -1) { - if (errno == ENFILE || errno == EMFILE) - goto pause; - if (errno == EINTR || errno == EWOULDBLOCK || - errno == ECONNABORTED) - return; - fatal("control_accept: accept"); - } - - io_set_nonblocking(connfd); - - if (getpeereid(connfd, &euid, &egid) == -1) - fatal("getpeereid"); - - count = tree_get(&ctl_count, euid); - if (count == NULL) { - count = xcalloc(1, sizeof *count); - tree_xset(&ctl_count, euid, count); - } - - if (*count == CONTROL_MAXCONN_PER_CLIENT) { - close(connfd); - log_warnx("warn: too many connections to control socket " - "from user with uid %lu", (unsigned long int)euid); - return; - } - (*count)++; - - do { - ++connid; - } while (tree_get(&ctl_conns, connid)); - - c = xcalloc(1, sizeof(*c)); - c->euid = euid; - c->egid = egid; - c->id = connid; - c->mproc.proc = PROC_CLIENT; - c->mproc.handler = control_dispatch_ext; - c->mproc.data = c; - if ((c->mproc.name = strdup(proc_title(c->mproc.proc))) == NULL) - fatal("strdup"); - mproc_init(&c->mproc, connfd); - mproc_enable(&c->mproc); - tree_xset(&ctl_conns, c->id, c); - - stat_backend->increment("control.session", 1); - return; - -pause: - log_warnx("warn: ctl client limit hit, disabling new connections"); - event_del(&control_state.ev); -} - -static void -control_close(struct ctl_conn *c) -{ - size_t *count; - - count = tree_xget(&ctl_count, c->euid); - (*count)--; - if (*count == 0) { - tree_xpop(&ctl_count, c->euid); - free(count); - } - tree_xpop(&ctl_conns, c->id); - mproc_clear(&c->mproc); - free(c); - - stat_backend->decrement("control.session", 1); - -#if defined(HAVE_GETDTABLESIZE) && defined(HAVE_GETDTABLECOUNT) - if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE) - return; -#else - if (available_fds(CONTROL_FD_RESERVE)) - return; -#endif - - if (!event_pending(&control_state.ev, EV_READ, NULL)) { - log_warnx("warn: re-enabling ctl connections"); - event_add(&control_state.ev, NULL); - } -} - -static void -control_digest_update(const char *key, size_t value, int incr) -{ - size_t *p; - - p = NULL; - - if (!strcmp(key, "smtp.session")) { - if (incr) - p = &digest.clt_connect; - else - digest.clt_disconnect += value; - } - else if (!strcmp(key, "scheduler.envelope")) { - if (incr) - p = &digest.evp_enqueued; - else - digest.evp_dequeued += value; - } - else if (!strcmp(key, "scheduler.envelope.expired")) - p = &digest.evp_expired; - else if (!strcmp(key, "scheduler.envelope.removed")) - p = &digest.evp_removed; - else if (!strcmp(key, "scheduler.delivery.ok")) - p = &digest.dlv_ok; - else if (!strcmp(key, "scheduler.delivery.permfail")) - p = &digest.dlv_permfail; - else if (!strcmp(key, "scheduler.delivery.tempfail")) - p = &digest.dlv_tempfail; - else if (!strcmp(key, "scheduler.delivery.loop")) - p = &digest.dlv_loop; - - else if (!strcmp(key, "queue.bounce")) - p = &digest.evp_bounce; - - if (p) { - if (incr) - *p = *p + value; - else - *p = *p - value; - } -} - -/* ARGSUSED */ -static void -control_dispatch_ext(struct mproc *p, struct imsg *imsg) -{ - struct sockaddr_storage ss; - struct ctl_conn *c; - int v; - struct stat_kv *kvp; - char *key; - struct stat_value val; - size_t len; - uint64_t evpid; - uint32_t msgid; - - c = p->data; - - if (imsg == NULL) { - control_close(c); - return; - } - - if (imsg->hdr.peerid != IMSG_VERSION) { - m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0); - return; - } - - switch (imsg->hdr.type) { - case IMSG_CTL_SMTP_SESSION: - if (env->sc_flags & SMTPD_SMTP_PAUSED) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - m_compose(p_pony, IMSG_CTL_SMTP_SESSION, c->id, 0, -1, - &c->euid, sizeof(c->euid)); - return; - - case IMSG_CTL_GET_DIGEST: - if (c->euid) - goto badcred; - digest.timestamp = time(NULL); - m_compose(p, IMSG_CTL_GET_DIGEST, 0, 0, -1, &digest, sizeof digest); - return; - - case IMSG_CTL_GET_STATS: - if (c->euid) - goto badcred; - kvp = imsg->data; - if (!stat_backend->iter(&kvp->iter, &key, &val)) - kvp->iter = NULL; - else { - (void)strlcpy(kvp->key, key, sizeof kvp->key); - kvp->val = val; - } - m_compose(p, IMSG_CTL_GET_STATS, 0, 0, -1, kvp, sizeof *kvp); - return; - - case IMSG_CTL_VERBOSE: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) - goto badcred; - - memcpy(&v, imsg->data, sizeof(v)); - log_trace_verbose(v); - - control_broadcast_verbose(IMSG_CTL_VERBOSE, v); - - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_TRACE_ENABLE: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) - goto badcred; - - memcpy(&v, imsg->data, sizeof(v)); - tracing |= v; - log_trace_verbose(tracing); - - control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing); - - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_TRACE_DISABLE: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) - goto badcred; - - memcpy(&v, imsg->data, sizeof(v)); - tracing &= ~v; - log_trace_verbose(tracing); - - control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing); - - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_PROFILE_ENABLE: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) - goto badcred; - - memcpy(&v, imsg->data, sizeof(v)); - profiling |= v; - - control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); - - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_PROFILE_DISABLE: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) - goto badcred; - - memcpy(&v, imsg->data, sizeof(v)); - profiling &= ~v; - - control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); - - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_PAUSE_EVP: - if (c->euid) - goto badcred; - - imsg->hdr.peerid = c->id; - m_forward(p_scheduler, imsg); - return; - - case IMSG_CTL_PAUSE_MDA: - if (c->euid) - goto badcred; - - if (env->sc_flags & SMTPD_MDA_PAUSED) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - log_info("info: mda paused"); - env->sc_flags |= SMTPD_MDA_PAUSED; - m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_PAUSE_MTA: - if (c->euid) - goto badcred; - - if (env->sc_flags & SMTPD_MTA_PAUSED) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - log_info("info: mta paused"); - env->sc_flags |= SMTPD_MTA_PAUSED; - m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_PAUSE_SMTP: - if (c->euid) - goto badcred; - - if (env->sc_flags & SMTPD_SMTP_PAUSED) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - log_info("info: smtp paused"); - env->sc_flags |= SMTPD_SMTP_PAUSED; - m_compose(p_pony, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_RESUME_EVP: - if (c->euid) - goto badcred; - - imsg->hdr.peerid = c->id; - m_forward(p_scheduler, imsg); - return; - - case IMSG_CTL_RESUME_MDA: - if (c->euid) - goto badcred; - - if (!(env->sc_flags & SMTPD_MDA_PAUSED)) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - log_info("info: mda resumed"); - env->sc_flags &= ~SMTPD_MDA_PAUSED; - m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_RESUME_MTA: - if (c->euid) - goto badcred; - - if (!(env->sc_flags & SMTPD_MTA_PAUSED)) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - log_info("info: mta resumed"); - env->sc_flags &= ~SMTPD_MTA_PAUSED; - m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_RESUME_SMTP: - if (c->euid) - goto badcred; - - if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) { - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); - return; - } - log_info("info: smtp resumed"); - env->sc_flags &= ~SMTPD_SMTP_PAUSED; - m_forward(p_pony, imsg); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_RESUME_ROUTE: - if (c->euid) - goto badcred; - - m_forward(p_pony, imsg); - m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); - return; - - case IMSG_CTL_LIST_MESSAGES: - if (c->euid) - goto badcred; - m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1, - imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); - return; - - case IMSG_CTL_LIST_ENVELOPES: - if (c->euid) - goto badcred; - m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1, - imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); - return; - - case IMSG_CTL_MTA_SHOW_HOSTS: - case IMSG_CTL_MTA_SHOW_RELAYS: - case IMSG_CTL_MTA_SHOW_ROUTES: - case IMSG_CTL_MTA_SHOW_HOSTSTATS: - case IMSG_CTL_MTA_SHOW_BLOCK: - if (c->euid) - goto badcred; - - imsg->hdr.peerid = c->id; - m_forward(p_pony, imsg); - return; - - case IMSG_CTL_SHOW_STATUS: - if (c->euid) - goto badcred; - - m_compose(p, IMSG_CTL_SHOW_STATUS, 0, 0, -1, &env->sc_flags, - sizeof(env->sc_flags)); - return; - - case IMSG_CTL_MTA_BLOCK: - case IMSG_CTL_MTA_UNBLOCK: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE <= sizeof(ss)) - goto invalid; - memmove(&ss, imsg->data, sizeof(ss)); - m_create(p_pony, imsg->hdr.type, c->id, 0, -1); - m_add_sockaddr(p_pony, (struct sockaddr *)&ss); - m_add_string(p_pony, (char *)imsg->data + sizeof(ss)); - m_close(p_pony); - return; - - case IMSG_CTL_SCHEDULE: - if (c->euid) - goto badcred; - - imsg->hdr.peerid = c->id; - m_forward(p_scheduler, imsg); - return; - - case IMSG_CTL_REMOVE: - if (c->euid) - goto badcred; - - imsg->hdr.peerid = c->id; - m_forward(p_scheduler, imsg); - return; - - case IMSG_CTL_UPDATE_TABLE: - if (c->euid) - goto badcred; - - /* table name too long */ - len = strlen(imsg->data); - if (len >= LINE_MAX) - goto invalid; - - imsg->hdr.peerid = c->id; - m_forward(p_lka, imsg); - return; - - case IMSG_CTL_DISCOVER_EVPID: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof evpid) - goto invalid; - - memmove(&evpid, imsg->data, sizeof evpid); - m_create(p_queue, imsg->hdr.type, c->id, 0, -1); - m_add_evpid(p_queue, evpid); - m_close(p_queue); - return; - - case IMSG_CTL_DISCOVER_MSGID: - if (c->euid) - goto badcred; - - if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof msgid) - goto invalid; - - memmove(&msgid, imsg->data, sizeof msgid); - m_create(p_queue, imsg->hdr.type, c->id, 0, -1); - m_add_msgid(p_queue, msgid); - m_close(p_queue); - return; - - default: - log_debug("debug: control_dispatch_ext: " - "error handling %s imsg", - imsg_to_str(imsg->hdr.type)); - return; - } -badcred: -invalid: - m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); -} - -static void -control_broadcast_verbose(int msg, int v) -{ - m_create(p_lka, msg, 0, 0, -1); - m_add_int(p_lka, v); - m_close(p_lka); - - m_create(p_pony, msg, 0, 0, -1); - m_add_int(p_pony, v); - m_close(p_pony); - - m_create(p_queue, msg, 0, 0, -1); - m_add_int(p_queue, v); - m_close(p_queue); - - m_create(p_ca, msg, 0, 0, -1); - m_add_int(p_ca, v); - m_close(p_ca); - - m_create(p_scheduler, msg, 0, 0, -1); - m_add_int(p_scheduler, v); - m_close(p_scheduler); - - m_create(p_parent, msg, 0, 0, -1); - m_add_int(p_parent, v); - m_close(p_parent); -} diff --git a/smtpd/crypto.c b/smtpd/crypto.c deleted file mode 100644 index 20a422cd..00000000 --- a/smtpd/crypto.c +++ /dev/null @@ -1,400 +0,0 @@ -/* $OpenBSD: crypto.c,v 1.8 2019/06/28 13:32:50 deraadt Exp $ */ - -/* - * Copyright (c) 2013 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include - -#include -#include - -#include - - -#define CRYPTO_BUFFER_SIZE 16384 - -#define GCM_TAG_SIZE 16 -#define IV_SIZE 12 -#define KEY_SIZE 32 - -/* bump if we ever switch from aes-256-gcm to anything else */ -#define API_VERSION 1 - - -int crypto_setup(const char *, size_t); -int crypto_encrypt_file(FILE *, FILE *); -int crypto_decrypt_file(FILE *, FILE *); -size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t); -size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t); - -static struct crypto_ctx { - unsigned char key[KEY_SIZE]; -} cp; - -int -crypto_setup(const char *key, size_t len) -{ - if (len != KEY_SIZE) - return 0; - - memset(&cp, 0, sizeof cp); - - /* openssl rand -hex 16 */ - memcpy(cp.key, key, sizeof cp.key); - - return 1; -} - -int -crypto_encrypt_file(FILE * in, FILE * out) -{ - EVP_CIPHER_CTX *ctx; - uint8_t ibuf[CRYPTO_BUFFER_SIZE]; - uint8_t obuf[CRYPTO_BUFFER_SIZE]; - uint8_t iv[IV_SIZE]; - uint8_t tag[GCM_TAG_SIZE]; - uint8_t version = API_VERSION; - size_t r, w; - int len; - int ret = 0; - struct stat sb; - - /* XXX - Do NOT encrypt files bigger than 64GB */ - if (fstat(fileno(in), &sb) == -1) - return 0; - if (sb.st_size >= 0x1000000000LL) - return 0; - - /* prepend version byte*/ - if ((w = fwrite(&version, 1, sizeof version, out)) != sizeof version) - return 0; - - /* generate and prepend IV */ - memset(iv, 0, sizeof iv); - arc4random_buf(iv, sizeof iv); - if ((w = fwrite(iv, 1, sizeof iv, out)) != sizeof iv) - return 0; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) - return 0; - - EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); - - /* encrypt until end of file */ - while ((r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in)) != 0) { - if (!EVP_EncryptUpdate(ctx, obuf, &len, ibuf, r)) - goto end; - if (len && (w = fwrite(obuf, len, 1, out)) != 1) - goto end; - } - if (!feof(in)) - goto end; - - /* finalize and write last chunk if any */ - if (!EVP_EncryptFinal_ex(ctx, obuf, &len)) - goto end; - if (len && (w = fwrite(obuf, len, 1, out)) != 1) - goto end; - - /* get and append tag */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag); - if ((w = fwrite(tag, sizeof tag, 1, out)) != 1) - goto end; - - fflush(out); - ret = 1; - -end: - EVP_CIPHER_CTX_free(ctx); - return ret; -} - -int -crypto_decrypt_file(FILE * in, FILE * out) -{ - EVP_CIPHER_CTX *ctx; - uint8_t ibuf[CRYPTO_BUFFER_SIZE]; - uint8_t obuf[CRYPTO_BUFFER_SIZE]; - uint8_t iv[IV_SIZE]; - uint8_t tag[GCM_TAG_SIZE]; - uint8_t version; - size_t r, w; - off_t sz; - int len; - int ret = 0; - struct stat sb; - - /* input file too small to be an encrypted file */ - if (fstat(fileno(in), &sb) == -1) - return 0; - if (sb.st_size <= (off_t) (sizeof version + sizeof tag + sizeof iv)) - return 0; - sz = sb.st_size; - - /* extract tag */ - if (fseek(in, -sizeof(tag), SEEK_END) == -1) - return 0; - if ((r = fread(tag, 1, sizeof tag, in)) != sizeof tag) - return 0; - - if (fseek(in, 0, SEEK_SET) == -1) - return 0; - - /* extract version */ - if ((r = fread(&version, 1, sizeof version, in)) != sizeof version) - return 0; - if (version != API_VERSION) - return 0; - - /* extract IV */ - memset(iv, 0, sizeof iv); - if ((r = fread(iv, 1, sizeof iv, in)) != sizeof iv) - return 0; - - /* real ciphertext length */ - sz -= sizeof version; - sz -= sizeof iv; - sz -= sizeof tag; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) - return 0; - - EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); - - /* set expected tag */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag); - - /* decrypt until end of ciphertext */ - while (sz) { - if (sz > CRYPTO_BUFFER_SIZE) - r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in); - else - r = fread(ibuf, 1, sz, in); - if (!r) - break; - if (!EVP_DecryptUpdate(ctx, obuf, &len, ibuf, r)) - goto end; - if (len && (w = fwrite(obuf, len, 1, out)) != 1) - goto end; - sz -= r; - } - if (ferror(in)) - goto end; - - /* finalize, write last chunk if any and perform authentication check */ - if (!EVP_DecryptFinal_ex(ctx, obuf, &len)) - goto end; - if (len && (w = fwrite(obuf, len, 1, out)) != 1) - goto end; - - fflush(out); - ret = 1; - -end: - EVP_CIPHER_CTX_free(ctx); - return ret; -} - -size_t -crypto_encrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen) -{ - EVP_CIPHER_CTX *ctx; - uint8_t iv[IV_SIZE]; - uint8_t tag[GCM_TAG_SIZE]; - uint8_t version = API_VERSION; - off_t sz; - int olen; - int len = 0; - int ret = 0; - - /* output buffer does not have enough room */ - if (outlen < inlen + sizeof version + sizeof tag + sizeof iv) - return 0; - - /* input should not exceed 64GB */ - sz = inlen; - if (sz >= 0x1000000000LL) - return 0; - - /* prepend version */ - *out = version; - len++; - - /* generate IV */ - memset(iv, 0, sizeof iv); - arc4random_buf(iv, sizeof iv); - memcpy(out + len, iv, sizeof iv); - len += sizeof iv; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) - return 0; - - EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); - - /* encrypt buffer */ - if (!EVP_EncryptUpdate(ctx, out + len, &olen, in, inlen)) - goto end; - len += olen; - - /* finalize and write last chunk if any */ - if (!EVP_EncryptFinal_ex(ctx, out + len, &olen)) - goto end; - len += olen; - - /* get and append tag */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag); - memcpy(out + len, tag, sizeof tag); - ret = len + sizeof tag; - -end: - EVP_CIPHER_CTX_free(ctx); - return ret; -} - -size_t -crypto_decrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen) -{ - EVP_CIPHER_CTX *ctx; - uint8_t iv[IV_SIZE]; - uint8_t tag[GCM_TAG_SIZE]; - int olen; - int len = 0; - int ret = 0; - - /* out does not have enough room */ - if (outlen < inlen - sizeof tag + sizeof iv) - return 0; - - /* extract tag */ - memcpy(tag, in + inlen - sizeof tag, sizeof tag); - inlen -= sizeof tag; - - /* check version */ - if (*in != API_VERSION) - return 0; - in++; - inlen--; - - /* extract IV */ - memset(iv, 0, sizeof iv); - memcpy(iv, in, sizeof iv); - inlen -= sizeof iv; - in += sizeof iv; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) - return 0; - - EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); - - /* set expected tag */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag); - - /* decrypt buffer */ - if (!EVP_DecryptUpdate(ctx, out, &olen, in, inlen)) - goto end; - len += olen; - - /* finalize, write last chunk if any and perform authentication check */ - if (!EVP_DecryptFinal_ex(ctx, out + len, &olen)) - goto end; - ret = len + olen; - -end: - EVP_CIPHER_CTX_free(ctx); - return ret; -} - -#if 0 -int -main(int argc, char *argv[]) -{ - if (argc != 3) { - printf("usage: crypto \n"); - return 1; - } - - if (!crypto_setup(argv[1], strlen(argv[1]))) { - printf("crypto_setup failed\n"); - return 1; - } - - { - char encbuffer[4096]; - size_t enclen; - char decbuffer[4096]; - size_t declen; - - printf("encrypt/decrypt buffer: "); - enclen = crypto_encrypt_buffer(argv[2], strlen(argv[2]), - encbuffer, sizeof encbuffer); - - /* uncomment below to provoke integrity check failure */ - /* - * encbuffer[13] = 0x42; - * encbuffer[14] = 0x42; - * encbuffer[15] = 0x42; - * encbuffer[16] = 0x42; - */ - - declen = crypto_decrypt_buffer(encbuffer, enclen, - decbuffer, sizeof decbuffer); - if (declen != 0 && !strncmp(argv[2], decbuffer, declen)) - printf("ok\n"); - else - printf("nope\n"); - } - - { - FILE *fpin; - FILE *fpout; - printf("encrypt/decrypt file: "); - - fpin = fopen("/etc/passwd", "r"); - fpout = fopen("/tmp/passwd.enc", "w"); - if (!crypto_encrypt_file(fpin, fpout)) { - printf("encryption failed\n"); - return 1; - } - fclose(fpin); - fclose(fpout); - - /* uncomment below to provoke integrity check failure */ - /* - * fpin = fopen("/tmp/passwd.enc", "a"); - * fprintf(fpin, "borken"); - * fclose(fpin); - */ - fpin = fopen("/tmp/passwd.enc", "r"); - fpout = fopen("/tmp/passwd.dec", "w"); - if (!crypto_decrypt_file(fpin, fpout)) - printf("nope\n"); - else - printf("ok\n"); - fclose(fpin); - fclose(fpout); - } - - - return 0; -} -#endif diff --git a/smtpd/dict.c b/smtpd/dict.c deleted file mode 100644 index e660f0a5..00000000 --- a/smtpd/dict.c +++ /dev/null @@ -1,269 +0,0 @@ -/* $OpenBSD: dict.c,v 1.6 2018/12/23 16:06:24 gilles Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include - -#include -#include -#include -#include - -#include "dict.h" - -struct dictentry { - SPLAY_ENTRY(dictentry) entry; - const char *key; - void *data; -}; - -static int dictentry_cmp(struct dictentry *, struct dictentry *); - -SPLAY_PROTOTYPE(_dict, dictentry, entry, dictentry_cmp); - -int -dict_check(struct dict *d, const char *k) -{ - struct dictentry key; - - key.key = k; - return (SPLAY_FIND(_dict, &d->dict, &key) != NULL); -} - -static inline struct dictentry * -dict_alloc(const char *k, void *data) -{ - struct dictentry *e; - size_t s = strlen(k) + 1; - void *t; - - if ((e = malloc(sizeof(*e) + s)) == NULL) - return NULL; - - e->key = t = (char*)(e) + sizeof(*e); - e->data = data; - memmove(t, k, s); - - return (e); -} - -void * -dict_set(struct dict *d, const char *k, void *data) -{ - struct dictentry *entry, key; - char *old; - - key.key = k; - if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) { - if ((entry = dict_alloc(k, data)) == NULL) - err(1, "dict_set: malloc"); - SPLAY_INSERT(_dict, &d->dict, entry); - old = NULL; - d->count += 1; - } else { - old = entry->data; - entry->data = data; - } - - return (old); -} - -void -dict_xset(struct dict *d, const char * k, void *data) -{ - struct dictentry *entry; - - if ((entry = dict_alloc(k, data)) == NULL) - err(1, "dict_xset: malloc"); - if (SPLAY_INSERT(_dict, &d->dict, entry)) - errx(1, "dict_xset(%p, %s)", d, k); - d->count += 1; -} - -void * -dict_get(struct dict *d, const char *k) -{ - struct dictentry key, *entry; - - key.key = k; - if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) - return (NULL); - - return (entry->data); -} - -void * -dict_xget(struct dict *d, const char *k) -{ - struct dictentry key, *entry; - - key.key = k; - if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) - errx(1, "dict_xget(%p, %s)", d, k); - - return (entry->data); -} - -void * -dict_pop(struct dict *d, const char *k) -{ - struct dictentry key, *entry; - void *data; - - key.key = k; - if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) - return (NULL); - - data = entry->data; - SPLAY_REMOVE(_dict, &d->dict, entry); - free(entry); - d->count -= 1; - - return (data); -} - -void * -dict_xpop(struct dict *d, const char *k) -{ - struct dictentry key, *entry; - void *data; - - key.key = k; - if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) - errx(1, "dict_xpop(%p, %s)", d, k); - - data = entry->data; - SPLAY_REMOVE(_dict, &d->dict, entry); - free(entry); - d->count -= 1; - - return (data); -} - -int -dict_poproot(struct dict *d, void **data) -{ - struct dictentry *entry; - - entry = SPLAY_ROOT(&d->dict); - if (entry == NULL) - return (0); - if (data) - *data = entry->data; - SPLAY_REMOVE(_dict, &d->dict, entry); - free(entry); - d->count -= 1; - - return (1); -} - -int -dict_root(struct dict *d, const char **k, void **data) -{ - struct dictentry *entry; - - entry = SPLAY_ROOT(&d->dict); - if (entry == NULL) - return (0); - if (k) - *k = entry->key; - if (data) - *data = entry->data; - return (1); -} - -int -dict_iter(struct dict *d, void **hdl, const char **k, void **data) -{ - struct dictentry *curr = *hdl; - - if (curr == NULL) - curr = SPLAY_MIN(_dict, &d->dict); - else - curr = SPLAY_NEXT(_dict, &d->dict, curr); - - if (curr) { - *hdl = curr; - if (k) - *k = curr->key; - if (data) - *data = curr->data; - return (1); - } - - return (0); -} - -int -dict_iterfrom(struct dict *d, void **hdl, const char *kfrom, const char **k, - void **data) -{ - struct dictentry *curr = *hdl, key; - - if (curr == NULL) { - if (kfrom == NULL) - curr = SPLAY_MIN(_dict, &d->dict); - else { - key.key = kfrom; - curr = SPLAY_FIND(_dict, &d->dict, &key); - if (curr == NULL) { - SPLAY_INSERT(_dict, &d->dict, &key); - curr = SPLAY_NEXT(_dict, &d->dict, &key); - SPLAY_REMOVE(_dict, &d->dict, &key); - } - } - } else - curr = SPLAY_NEXT(_dict, &d->dict, curr); - - if (curr) { - *hdl = curr; - if (k) - *k = curr->key; - if (data) - *data = curr->data; - return (1); - } - - return (0); -} - -void -dict_merge(struct dict *dst, struct dict *src) -{ - struct dictentry *entry; - - while (!SPLAY_EMPTY(&src->dict)) { - entry = SPLAY_ROOT(&src->dict); - SPLAY_REMOVE(_dict, &src->dict, entry); - if (SPLAY_INSERT(_dict, &dst->dict, entry)) - errx(1, "dict_merge: duplicate"); - } - dst->count += src->count; - src->count = 0; -} - -static int -dictentry_cmp(struct dictentry *a, struct dictentry *b) -{ - return strcmp(a->key, b->key); -} - -SPLAY_GENERATE(_dict, dictentry, entry, dictentry_cmp); diff --git a/smtpd/dict.h b/smtpd/dict.h deleted file mode 100644 index c5d47e1a..00000000 --- a/smtpd/dict.h +++ /dev/null @@ -1,48 +0,0 @@ -/* $OpenBSD: dict.h,v 1.1 2018/12/23 16:06:24 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2011 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _DICT_H_ -#define _DICT_H_ - -SPLAY_HEAD(_dict, dictentry); - -struct dict { - struct _dict dict; - size_t count; -}; - - -/* dict.c */ -#define dict_init(d) do { SPLAY_INIT(&((d)->dict)); (d)->count = 0; } while(0) -#define dict_empty(d) SPLAY_EMPTY(&((d)->dict)) -#define dict_count(d) ((d)->count) -int dict_check(struct dict *, const char *); -void *dict_set(struct dict *, const char *, void *); -void dict_xset(struct dict *, const char *, void *); -void *dict_get(struct dict *, const char *); -void *dict_xget(struct dict *, const char *); -void *dict_pop(struct dict *, const char *); -void *dict_xpop(struct dict *, const char *); -int dict_poproot(struct dict *, void **); -int dict_root(struct dict *, const char **, void **); -int dict_iter(struct dict *, void **, const char **, void **); -int dict_iterfrom(struct dict *, void **, const char *, const char **, void **); -void dict_merge(struct dict *, struct dict *); - -#endif diff --git a/smtpd/dns.c b/smtpd/dns.c deleted file mode 100644 index a3107e89..00000000 --- a/smtpd/dns.c +++ /dev/null @@ -1,379 +0,0 @@ -/* $OpenBSD: dns.c,v 1.89 2019/09/18 11:26:30 eric Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2011-2014 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#ifdef HAVE_ARPA_NAMESER_COMPAT_H -#include -#endif -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" -#include "unpack_dns.h" - -/* On OpenBSD, this function is not needed because we don't free addrinfo */ -#if defined(NOOP_ASR_FREEADDRINFO) -#define asr_freeaddrinfo(x) do { } while(0); -#endif - -struct dns_lookup { - struct dns_session *session; - char *host; - int preference; -}; - -struct dns_session { - struct mproc *p; - uint64_t reqid; - int type; - char name[HOST_NAME_MAX+1]; - size_t mxfound; - int error; - int refcount; -}; - -static void dns_lookup_host(struct dns_session *, const char *, int); -static void dns_dispatch_host(struct asr_result *, void *); -static void dns_dispatch_mx(struct asr_result *, void *); -static void dns_dispatch_mx_preference(struct asr_result *, void *); - -static int -domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl) -{ - struct addrinfo hints, *res; - socklen_t sl2; - size_t l; - char buf[SMTPD_MAXDOMAINPARTSIZE]; - int i6, error; - - if (*s != '[') - return (0); - - i6 = (strncasecmp("[IPv6:", s, 6) == 0); - s += i6 ? 6 : 1; - - l = strlcpy(buf, s, sizeof(buf)); - if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']') - return (0); - - buf[l - 1] = '\0'; - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_NUMERICHOST; - hints.ai_socktype = SOCK_STREAM; - if (i6) - hints.ai_family = AF_INET6; - - res = NULL; - if ((error = getaddrinfo(buf, NULL, &hints, &res))) { - log_warnx("getaddrinfo: %s", gai_strerror(error)); - } - - if (!res) - return (0); - - if (sa && sl) { - sl2 = *sl; - if (sl2 > res->ai_addrlen) - sl2 = res->ai_addrlen; - memmove(sa, res->ai_addr, sl2); - *sl = res->ai_addrlen; - } - - freeaddrinfo(res); - return (1); -} - -void -dns_imsg(struct mproc *p, struct imsg *imsg) -{ - struct sockaddr_storage ss; - struct dns_session *s; - struct sockaddr *sa; - struct asr_query *as; - struct msg m; - const char *domain, *mx, *host; - socklen_t sl; - - s = xcalloc(1, sizeof *s); - s->type = imsg->hdr.type; - s->p = p; - - m_msg(&m, imsg); - m_get_id(&m, &s->reqid); - - switch (s->type) { - - case IMSG_MTA_DNS_HOST: - m_get_string(&m, &host); - m_end(&m); - dns_lookup_host(s, host, -1); - return; - - case IMSG_MTA_DNS_MX: - m_get_string(&m, &domain); - m_end(&m); - (void)strlcpy(s->name, domain, sizeof(s->name)); - - sa = (struct sockaddr *)&ss; - sl = sizeof(ss); - - if (domainname_is_addr(domain, sa, &sl)) { - m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_string(s->p, sockaddr_to_text(sa)); - m_add_sockaddr(s->p, sa); - m_add_int(s->p, -1); - m_close(s->p); - - m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_int(s->p, DNS_OK); - m_close(s->p); - free(s); - return; - } - - as = res_query_async(s->name, C_IN, T_MX, NULL); - if (as == NULL) { - log_warn("warn: res_query_async: %s", s->name); - m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_int(s->p, DNS_EINVAL); - m_close(s->p); - free(s); - return; - } - - event_asr_run(as, dns_dispatch_mx, s); - return; - - case IMSG_MTA_DNS_MX_PREFERENCE: - m_get_string(&m, &domain); - m_get_string(&m, &mx); - m_end(&m); - (void)strlcpy(s->name, mx, sizeof(s->name)); - - as = res_query_async(domain, C_IN, T_MX, NULL); - if (as == NULL) { - m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_int(s->p, DNS_ENOTFOUND); - m_close(s->p); - free(s); - return; - } - - event_asr_run(as, dns_dispatch_mx_preference, s); - return; - - default: - log_warnx("warn: bad dns request %d", s->type); - fatal(NULL); - } -} - -static void -dns_dispatch_host(struct asr_result *ar, void *arg) -{ - struct dns_session *s; - struct dns_lookup *lookup = arg; - struct addrinfo *ai; - - s = lookup->session; - - for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { - s->mxfound++; - m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_string(s->p, lookup->host); - m_add_sockaddr(s->p, ai->ai_addr); - m_add_int(s->p, lookup->preference); - m_close(s->p); - } - free(lookup->host); - free(lookup); - if (ar->ar_addrinfo) - asr_freeaddrinfo(ar->ar_addrinfo); - - if (ar->ar_gai_errno) - s->error = ar->ar_gai_errno; - - if (--s->refcount) - return; - - m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND); - m_close(s->p); - free(s); -} - -static void -dns_dispatch_mx(struct asr_result *ar, void *arg) -{ - struct dns_session *s = arg; - struct unpack pack; - struct dns_header h; - struct dns_query q; - struct dns_rr rr; - char buf[512]; - size_t found; - - if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA && - ar->ar_h_errno != NOTIMP) { - - m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); - m_add_id(s->p, s->reqid); - if (ar->ar_rcode == NXDOMAIN) - m_add_int(s->p, DNS_ENONAME); - else if (ar->ar_h_errno == NO_RECOVERY) - m_add_int(s->p, DNS_EINVAL); - else - m_add_int(s->p, DNS_RETRY); - m_close(s->p); - free(s); - free(ar->ar_data); - return; - } - - unpack_init(&pack, ar->ar_data, ar->ar_datalen); - unpack_header(&pack, &h); - unpack_query(&pack, &q); - - found = 0; - for (; h.ancount; h.ancount--) { - unpack_rr(&pack, &rr); - if (rr.rr_type != T_MX) - continue; - print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); - buf[strlen(buf) - 1] = '\0'; - dns_lookup_host(s, buf, rr.rr.mx.preference); - found++; - } - free(ar->ar_data); - - /* fallback to host if no MX is found. */ - if (found == 0) - dns_lookup_host(s, s->name, 0); -} - -static void -dns_dispatch_mx_preference(struct asr_result *ar, void *arg) -{ - struct dns_session *s = arg; - struct unpack pack; - struct dns_header h; - struct dns_query q; - struct dns_rr rr; - char buf[512]; - int error; - - if (ar->ar_h_errno) { - if (ar->ar_rcode == NXDOMAIN) - error = DNS_ENONAME; - else if (ar->ar_h_errno == NO_RECOVERY - || ar->ar_h_errno == NO_DATA) - error = DNS_EINVAL; - else - error = DNS_RETRY; - } - else { - error = DNS_ENOTFOUND; - unpack_init(&pack, ar->ar_data, ar->ar_datalen); - unpack_header(&pack, &h); - unpack_query(&pack, &q); - for (; h.ancount; h.ancount--) { - unpack_rr(&pack, &rr); - if (rr.rr_type != T_MX) - continue; - print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); - buf[strlen(buf) - 1] = '\0'; - if (!strcasecmp(s->name, buf)) { - error = DNS_OK; - break; - } - } - } - - free(ar->ar_data); - - m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); - m_add_id(s->p, s->reqid); - m_add_int(s->p, error); - if (error == DNS_OK) - m_add_int(s->p, rr.rr.mx.preference); - m_close(s->p); - free(s); -} - -static void -dns_lookup_host(struct dns_session *s, const char *host, int preference) -{ - struct dns_lookup *lookup; - struct addrinfo hints; - char hostcopy[HOST_NAME_MAX+1]; - char *p; - void *as; - - lookup = xcalloc(1, sizeof *lookup); - lookup->preference = preference; - lookup->host = xstrdup(host); - lookup->session = s; - s->refcount++; - - if (*host == '[') { - if (strncasecmp("[IPv6:", host, 6) == 0) - host += 6; - else - host += 1; - (void)strlcpy(hostcopy, host, sizeof hostcopy); - p = strchr(hostcopy, ']'); - if (p) - *p = 0; - host = hostcopy; - } - - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_ADDRCONFIG; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - as = getaddrinfo_async(host, NULL, &hints, NULL); - event_asr_run(as, dns_dispatch_host, lookup); -} diff --git a/smtpd/enqueue.c b/smtpd/enqueue.c deleted file mode 100644 index 0ef694b5..00000000 --- a/smtpd/enqueue.c +++ /dev/null @@ -1,932 +0,0 @@ -/* $OpenBSD: enqueue.c,v 1.118 2020/03/18 20:17:14 eric Exp $ */ - -/* - * Copyright (c) 2005 Henning Brauer - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" - -extern struct imsgbuf *ibuf; - -void usage(void); -static void build_from(char *, struct passwd *); -static int parse_message(FILE *, int, int, FILE *); -static void parse_addr(char *, size_t, int); -static void parse_addr_terminal(int); -static char *qualify_addr(char *); -static void rcpt_add(char *); -static int open_connection(void); -static int get_responses(FILE *, int); -static int send_line(FILE *, int, char *, ...); -static int enqueue_offline(int, char *[], FILE *, FILE *); -static int savedeadletter(struct passwd *, FILE *); - -extern int srv_connected(void); - -enum headerfields { - HDR_NONE, - HDR_FROM, - HDR_TO, - HDR_CC, - HDR_BCC, - HDR_SUBJECT, - HDR_DATE, - HDR_MSGID, - HDR_MIME_VERSION, - HDR_CONTENT_TYPE, - HDR_CONTENT_DISPOSITION, - HDR_CONTENT_TRANSFER_ENCODING, - HDR_USER_AGENT -}; - -struct { - char *word; - enum headerfields type; -} keywords[] = { - { "From:", HDR_FROM }, - { "To:", HDR_TO }, - { "Cc:", HDR_CC }, - { "Bcc:", HDR_BCC }, - { "Subject:", HDR_SUBJECT }, - { "Date:", HDR_DATE }, - { "Message-Id:", HDR_MSGID }, - { "MIME-Version:", HDR_MIME_VERSION }, - { "Content-Type:", HDR_CONTENT_TYPE }, - { "Content-Disposition:", HDR_CONTENT_DISPOSITION }, - { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, - { "User-Agent:", HDR_USER_AGENT }, -}; - -#define LINESPLIT 990 -#define SMTP_LINELEN 1000 -#define TIMEOUTMSG "Timeout\n" - -#define WSP(c) (c == ' ' || c == '\t') - -int verbose = 0; -static char host[HOST_NAME_MAX+1]; -char *user = NULL; -time_t timestamp; - -struct { - int fd; - char *from; - char *fromname; - char **rcpts; - char *dsn_notify; - char *dsn_ret; - char *dsn_envid; - int rcpt_cnt; - int need_linesplit; - int saw_date; - int saw_msgid; - int saw_from; - int saw_mime_version; - int saw_content_type; - int saw_content_disposition; - int saw_content_transfer_encoding; - int saw_user_agent; - int noheader; -} msg; - -struct { - uint quote; - uint comment; - uint esc; - uint brackets; - size_t wpos; - char buf[SMTP_LINELEN]; -} pstate; - -#define QP_TEST_WRAP(fp, buf, linelen, size) do { \ - if (((linelen) += (size)) + 1 > 76) { \ - fprintf((fp), "=\r\n"); \ - if (buf[0] == '.') \ - fprintf((fp), "."); \ - (linelen) = (size); \ - } \ -} while (0) - -/* RFC 2045 section 6.7 */ -static void -qp_encoded_write(FILE *fp, char *buf) -{ - size_t linelen = 0; - - for (;buf[0] != '\0' && buf[0] != '\n'; buf++) { - /* - * Point 3: Any TAB (HT) or SPACE characters on an encoded line - * MUST thus be followed on that line by a printable character. - * - * Ergo, only encode if the next character is EOL. - */ - if (buf[0] == ' ' || buf[0] == '\t') { - if (buf[1] == '\n') { - QP_TEST_WRAP(fp, buf, linelen, 3); - fprintf(fp, "=%2X", *buf & 0xff); - } else { - QP_TEST_WRAP(fp, buf, linelen, 1); - fprintf(fp, "%c", *buf & 0xff); - } - /* - * Point 1, with exclusion of point 2, skip EBCDIC NOTE. - * Do this after whitespace check, else they would match here. - */ - } else if (!((buf[0] >= 33 && buf[0] <= 60) || - (buf[0] >= 62 && buf[0] <= 126))) { - QP_TEST_WRAP(fp, buf, linelen, 3); - fprintf(fp, "=%2X", *buf & 0xff); - /* Point 2: 33 through 60 inclusive, and 62 through 126 */ - } else { - QP_TEST_WRAP(fp, buf, linelen, 1); - fprintf(fp, "%c", *buf); - } - } - fprintf(fp, "\r\n"); -} - -int -enqueue(int argc, char *argv[], FILE *ofp) -{ - int i, ch, tflag = 0; - char *fake_from = NULL, *buf = NULL; - struct passwd *pw; - FILE *fp = NULL, *fout; - size_t sz = 0, envid_sz = 0; - ssize_t len; - char *line; - int inheaders = 1; - int save_argc; - char **save_argv; - int no_getlogin = 0; - - memset(&msg, 0, sizeof(msg)); - time(×tamp); - - save_argc = argc; - save_argv = argv; - - while ((ch = getopt(argc, argv, - "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) { - switch (ch) { - case 'f': - fake_from = optarg; - break; - case 'F': - msg.fromname = optarg; - break; - case 'N': - msg.dsn_notify = optarg; - break; - case 'r': - fake_from = optarg; - break; - case 'R': - msg.dsn_ret = optarg; - break; - case 'S': - no_getlogin = 1; - break; - case 't': - tflag = 1; - break; - case 'v': - verbose = 1; - break; - case 'V': - msg.dsn_envid = optarg; - break; - /* all remaining: ignored, sendmail compat */ - case 'A': - case 'B': - case 'b': - case 'E': - case 'e': - case 'i': - case 'L': - case 'm': - case 'o': - case 'p': - case 'x': - break; - case 'q': - /* XXX: implement "process all now" */ - return (EX_SOFTWARE); - default: - usage(); - } - } - - argc -= optind; - argv += optind; - - if (getmailname(host, sizeof(host)) == -1) - errx(EX_NOHOST, "getmailname"); - if (no_getlogin) { - if ((pw = getpwuid(getuid())) == NULL) - user = "anonymous"; - if (pw != NULL) - user = xstrdup(pw->pw_name); - } - else { - uid_t ruid = getuid(); - - if ((user = getlogin()) != NULL && *user != '\0') { - if ((pw = getpwnam(user)) == NULL || - (ruid != 0 && ruid != pw->pw_uid)) - pw = getpwuid(ruid); - } else if ((pw = getpwuid(ruid)) == NULL) { - user = "anonymous"; - } - user = xstrdup(pw ? pw->pw_name : user); - } - - build_from(fake_from, pw); - - while (argc > 0) { - rcpt_add(argv[0]); - argv++; - argc--; - } - - if ((fp = tmpfile()) == NULL) - err(EX_UNAVAILABLE, "tmpfile"); - - msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp); - - if (msg.rcpt_cnt == 0) - errx(EX_SOFTWARE, "no recipients"); - - /* init session */ - rewind(fp); - - /* check if working in offline mode */ - /* If the server is not running, enqueue the message offline */ - - if (!srv_connected()) { -#if HAVE_PLEDGE - if (pledge("stdio", NULL) == -1) - err(1, "pledge"); -#endif - return (enqueue_offline(save_argc, save_argv, fp, ofp)); - } - - if ((msg.fd = open_connection()) == -1) - errx(EX_UNAVAILABLE, "server too busy"); - -#if HAVE_PLEDGE - if (pledge("stdio wpath cpath", NULL) == -1) - err(1, "pledge"); -#endif - - fout = fdopen(msg.fd, "a+"); - if (fout == NULL) - err(EX_UNAVAILABLE, "fdopen"); - - /* - * We need to call get_responses after every command because we don't - * support PIPELINING on the server-side yet. - */ - - /* banner */ - if (!get_responses(fout, 1)) - goto fail; - - if (!send_line(fout, verbose, "EHLO localhost\r\n")) - goto fail; - if (!get_responses(fout, 1)) - goto fail; - - if (msg.dsn_envid != NULL) - envid_sz = strlen(msg.dsn_envid); - - if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n", - msg.from, - msg.dsn_ret ? "RET=" : "", - msg.dsn_ret ? msg.dsn_ret : "", - envid_sz ? "ENVID=" : "", - envid_sz ? msg.dsn_envid : "")) - goto fail; - if (!get_responses(fout, 1)) - goto fail; - - for (i = 0; i < msg.rcpt_cnt; i++) { - if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n", - msg.rcpts[i], - msg.dsn_notify ? "NOTIFY=" : "", - msg.dsn_notify ? msg.dsn_notify : "")) - goto fail; - if (!get_responses(fout, 1)) - goto fail; - } - - if (!send_line(fout, verbose, "DATA\r\n")) - goto fail; - if (!get_responses(fout, 1)) - goto fail; - - /* add From */ - if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n", - msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", - msg.from)) - goto fail; - - /* add Date */ - if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n", - time_to_text(timestamp))) - goto fail; - - if (msg.need_linesplit) { - /* we will always need to mime encode for long lines */ - if (!msg.saw_mime_version && !send_line(fout, 0, - "MIME-Version: 1.0\r\n")) - goto fail; - if (!msg.saw_content_type && !send_line(fout, 0, - "Content-Type: text/plain; charset=unknown-8bit\r\n")) - goto fail; - if (!msg.saw_content_disposition && !send_line(fout, 0, - "Content-Disposition: inline\r\n")) - goto fail; - if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, - "Content-Transfer-Encoding: quoted-printable\r\n")) - goto fail; - } - - /* add separating newline */ - if (msg.noheader) { - if (!send_line(fout, 0, "\r\n")) - goto fail; - inheaders = 0; - } - - for (;;) { - if ((len = getline(&buf, &sz, fp)) == -1) { - if (feof(fp)) - break; - else - err(EX_UNAVAILABLE, "getline"); - } - - /* newlines have been normalized on first parsing */ - if (buf[len-1] != '\n') - errx(EX_SOFTWARE, "expect EOL"); - len--; - - if (buf[0] == '.') { - if (fputc('.', fout) == EOF) - goto fail; - } - - line = buf; - - if (inheaders) { - if (strncasecmp("from ", line, 5) == 0) - continue; - if (strncasecmp("return-path: ", line, 13) == 0) - continue; - } - - if (msg.saw_content_transfer_encoding || msg.noheader || - inheaders || !msg.need_linesplit) { - if (!send_line(fout, 0, "%.*s\r\n", (int)len, line)) - goto fail; - if (inheaders && buf[0] == '\n') - inheaders = 0; - continue; - } - - /* we don't have a content transfer encoding, use our default */ - qp_encoded_write(fout, line); - } - free(buf); - if (!send_line(fout, verbose, ".\r\n")) - goto fail; - if (!get_responses(fout, 1)) - goto fail; - - if (!send_line(fout, verbose, "QUIT\r\n")) - goto fail; - if (!get_responses(fout, 1)) - goto fail; - - fclose(fp); - fclose(fout); - - exit(EX_OK); - -fail: - if (pw) - savedeadletter(pw, fp); - exit(EX_SOFTWARE); -} - -static int -get_responses(FILE *fin, int n) -{ - char *buf = NULL; - size_t sz = 0; - ssize_t len; - int e, ret = 0; - - fflush(fin); - if ((e = ferror(fin))) { - warnx("ferror: %d", e); - goto err; - } - - while (n) { - if ((len = getline(&buf, &sz, fin)) == -1) { - if (ferror(fin)) { - warn("getline"); - goto err; - } else if (feof(fin)) - break; - else - err(EX_UNAVAILABLE, "getline"); - } - - /* account for \r\n linebreaks */ - if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') - buf[--len - 1] = '\n'; - - if (len < 4) { - warnx("bad response"); - goto err; - } - - if (verbose) - printf("<<< %.*s", (int)len, buf); - - if (buf[3] == '-') - continue; - if (buf[0] != '2' && buf[0] != '3') { - warnx("command failed: %.*s", (int)len, buf); - goto err; - } - n--; - } - - ret = 1; -err: - free(buf); - return ret; -} - -static int -send_line(FILE *fp, int v, char *fmt, ...) -{ - int ret = 0; - va_list ap; - - va_start(ap, fmt); - if (vfprintf(fp, fmt, ap) >= 0) - ret = 1; - va_end(ap); - - if (ret && v) { - printf(">>> "); - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - } - - return (ret); -} - -static void -build_from(char *fake_from, struct passwd *pw) -{ - char *p; - - if (fake_from == NULL) - msg.from = qualify_addr(user); - else { - if (fake_from[0] == '<') { - if (fake_from[strlen(fake_from) - 1] != '>') - errx(1, "leading < but no trailing >"); - fake_from[strlen(fake_from) - 1] = 0; - p = xstrdup(fake_from + 1); - - msg.from = qualify_addr(p); - free(p); - } else - msg.from = qualify_addr(fake_from); - } - - if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { - int len, apos; - - len = strcspn(pw->pw_gecos, ","); - if ((p = memchr(pw->pw_gecos, '&', len))) { - apos = p - pw->pw_gecos; - if (asprintf(&msg.fromname, "%.*s%s%.*s", - apos, pw->pw_gecos, - pw->pw_name, - len - apos - 1, p + 1) == -1) - err(1, "asprintf"); - msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]); - } else { - if (asprintf(&msg.fromname, "%.*s", len, - pw->pw_gecos) == -1) - err(1, "asprintf"); - } - } -} - -static int -parse_message(FILE *fin, int get_from, int tflag, FILE *fout) -{ - char *buf = NULL; - size_t sz = 0; - ssize_t len; - uint i, cur = HDR_NONE; - uint header_seen = 0, header_done = 0; - - memset(&pstate, 0, sizeof(pstate)); - for (;;) { - if ((len = getline(&buf, &sz, fin)) == -1) { - if (feof(fin)) - break; - else - err(EX_UNAVAILABLE, "getline"); - } - - /* account for \r\n linebreaks */ - if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') - buf[--len - 1] = '\n'; - - if (len == 1 && buf[0] == '\n') /* end of header */ - header_done = 1; - - if (!WSP(buf[0])) { /* whitespace -> continuation */ - if (cur == HDR_FROM) - parse_addr_terminal(1); - if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) - parse_addr_terminal(0); - cur = HDR_NONE; - } - - /* not really exact, if we are still in headers */ - if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) - msg.need_linesplit = 1; - - for (i = 0; !header_done && cur == HDR_NONE && - i < nitems(keywords); i++) - if ((size_t)len > strlen(keywords[i].word) && - !strncasecmp(buf, keywords[i].word, - strlen(keywords[i].word))) - cur = keywords[i].type; - - if (cur != HDR_NONE) - header_seen = 1; - - if (cur != HDR_BCC) { - if (!send_line(fout, 0, "%.*s", (int)len, buf)) - err(1, "write error"); - if (buf[len - 1] != '\n') { - if (fputc('\n', fout) == EOF) - err(1, "write error"); - } - } - - /* - * using From: as envelope sender is not sendmail compatible, - * but I really want it that way - maybe needs a knob - */ - if (cur == HDR_FROM) { - msg.saw_from++; - if (get_from) - parse_addr(buf, len, 1); - } - - if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) - parse_addr(buf, len, 0); - - if (cur == HDR_DATE) - msg.saw_date++; - if (cur == HDR_MSGID) - msg.saw_msgid++; - if (cur == HDR_MIME_VERSION) - msg.saw_mime_version = 1; - if (cur == HDR_CONTENT_TYPE) - msg.saw_content_type = 1; - if (cur == HDR_CONTENT_DISPOSITION) - msg.saw_content_disposition = 1; - if (cur == HDR_CONTENT_TRANSFER_ENCODING) - msg.saw_content_transfer_encoding = 1; - if (cur == HDR_USER_AGENT) - msg.saw_user_agent = 1; - } - - free(buf); - return (!header_seen); -} - -static void -parse_addr(char *s, size_t len, int is_from) -{ - size_t pos = 0; - int terminal = 0; - - /* unless this is a continuation... */ - if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { - /* ... skip over everything before the ':' */ - for (; pos < len && s[pos] != ':'; pos++) - ; /* nothing */ - /* ... and check & reset parser state */ - parse_addr_terminal(is_from); - } - - /* skip over ':' ',' ';' and whitespace */ - for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || - s[pos] == ',' || s[pos] == ';'); pos++) - ; /* nothing */ - - for (; pos < len; pos++) { - if (!pstate.esc && !pstate.quote && s[pos] == '(') - pstate.comment++; - if (!pstate.comment && !pstate.esc && s[pos] == '"') - pstate.quote = !pstate.quote; - - if (!pstate.comment && !pstate.quote && !pstate.esc) { - if (s[pos] == ':') { /* group */ - for (pos++; pos < len && WSP(s[pos]); pos++) - ; /* nothing */ - pstate.wpos = 0; - } - if (s[pos] == '\n' || s[pos] == '\r') - break; - if (s[pos] == ',' || s[pos] == ';') { - terminal = 1; - break; - } - if (s[pos] == '<') { - pstate.brackets = 1; - pstate.wpos = 0; - } - if (pstate.brackets && s[pos] == '>') - terminal = 1; - } - - if (!pstate.comment && !terminal && (!(!(pstate.quote || - pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { - if (pstate.wpos >= sizeof(pstate.buf)) - errx(1, "address exceeds buffer size"); - pstate.buf[pstate.wpos++] = s[pos]; - } - - if (!pstate.quote && pstate.comment && s[pos] == ')') - pstate.comment--; - - if (!pstate.esc && !pstate.comment && s[pos] == '\\') - pstate.esc = 1; - else - pstate.esc = 0; - } - - if (terminal) - parse_addr_terminal(is_from); - - for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) - ; /* nothing */ - - if (pos < len) - parse_addr(s + pos, len - pos, is_from); -} - -static void -parse_addr_terminal(int is_from) -{ - if (pstate.comment || pstate.quote || pstate.esc) - errx(1, "syntax error in address"); - if (pstate.wpos) { - if (pstate.wpos >= sizeof(pstate.buf)) - errx(1, "address exceeds buffer size"); - pstate.buf[pstate.wpos] = '\0'; - if (is_from) - msg.from = qualify_addr(pstate.buf); - else - rcpt_add(pstate.buf); - pstate.wpos = 0; - } -} - -static char * -qualify_addr(char *in) -{ - char *out; - - if (strlen(in) > 0 && strchr(in, '@') == NULL) { - if (asprintf(&out, "%s@%s", in, host) == -1) - err(1, "qualify asprintf"); - } else - out = xstrdup(in); - - return (out); -} - -static void -rcpt_add(char *addr) -{ - void *nrcpts; - char *p; - int n; - - n = 1; - p = addr; - while ((p = strchr(p, ',')) != NULL) { - n++; - p++; - } - - if ((nrcpts = reallocarray(msg.rcpts, - msg.rcpt_cnt + n, sizeof(char *))) == NULL) - err(1, "rcpt_add realloc"); - msg.rcpts = nrcpts; - - while (n--) { - if ((p = strchr(addr, ',')) != NULL) - *p++ = '\0'; - msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); - if (p == NULL) - break; - addr = p; - } -} - -static int -open_connection(void) -{ - struct imsg imsg; - int fd; - int n; - - imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0); - - while (ibuf->w.queued) - if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) - err(1, "write error"); - - while (1) { - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - errx(1, "imsg_read error"); - if (n == 0) - errx(1, "pipe closed"); - - if ((n = imsg_get(ibuf, &imsg)) == -1) - errx(1, "imsg_get error"); - if (n == 0) - continue; - - switch (imsg.hdr.type) { - case IMSG_CTL_OK: - break; - case IMSG_CTL_FAIL: - errx(1, "server disallowed submission request"); - default: - errx(1, "unexpected imsg reply type"); - } - - fd = imsg.fd; - imsg_free(&imsg); - - break; - } - - return fd; -} - -static int -enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile) -{ - int i, ch; - - for (i = 1; i < argc; i++) { - if (strchr(argv[i], '|') != NULL) { - warnx("%s contains illegal character", argv[i]); - ftruncate(fileno(ofile), 0); - exit(EX_SOFTWARE); - } - if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0) - goto write_error; - } - - if (fputc('\n', ofile) == EOF) - goto write_error; - - while ((ch = fgetc(ifile)) != EOF) { - if (fputc(ch, ofile) == EOF) - goto write_error; - } - - if (ferror(ifile)) { - warn("read error"); - ftruncate(fileno(ofile), 0); - exit(EX_UNAVAILABLE); - } - - if (fclose(ofile) == EOF) - goto write_error; - - return (EX_TEMPFAIL); -write_error: - warn("write error"); - ftruncate(fileno(ofile), 0); - exit(EX_UNAVAILABLE); -} - -static int -savedeadletter(struct passwd *pw, FILE *in) -{ - char buffer[PATH_MAX]; - FILE *fp; - char *buf = NULL; - size_t sz = 0; - ssize_t len; - - (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir); - - if (fseek(in, 0, SEEK_SET) != 0) - return 0; - - if ((fp = fopen(buffer, "w")) == NULL) - return 0; - - /* add From */ - if (!msg.saw_from) - fprintf(fp, "From: %s%s<%s>\n", - msg.fromname ? msg.fromname : "", - msg.fromname ? " " : "", - msg.from); - - /* add Date */ - if (!msg.saw_date) - fprintf(fp, "Date: %s\n", time_to_text(timestamp)); - - if (msg.need_linesplit) { - /* we will always need to mime encode for long lines */ - if (!msg.saw_mime_version) - fprintf(fp, "MIME-Version: 1.0\n"); - if (!msg.saw_content_type) - fprintf(fp, "Content-Type: text/plain; " - "charset=unknown-8bit\n"); - if (!msg.saw_content_disposition) - fprintf(fp, "Content-Disposition: inline\n"); - if (!msg.saw_content_transfer_encoding) - fprintf(fp, "Content-Transfer-Encoding: " - "quoted-printable\n"); - } - - /* add separating newline */ - if (msg.noheader) - fprintf(fp, "\n"); - - while ((len = getline(&buf, &sz, in)) != -1) { - if (buf[len - 1] == '\n') - buf[len - 1] = '\0'; - fprintf(fp, "%s\n", buf); - } - - free(buf); - fprintf(fp, "\n"); - fclose(fp); - return 1; -} diff --git a/smtpd/envelope.c b/smtpd/envelope.c deleted file mode 100644 index 35d98b79..00000000 --- a/smtpd/envelope.c +++ /dev/null @@ -1,786 +0,0 @@ -/* $OpenBSD: envelope.c,v 1.47 2019/11/25 14:18:32 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2011-2013 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static int envelope_ascii_load(struct envelope *, struct dict *); -static void envelope_ascii_dump(const struct envelope *, char **, size_t *, - const char *); - -void -envelope_set_errormsg(struct envelope *e, char *fmt, ...) -{ - int ret; - va_list ap; - - va_start(ap, fmt); - ret = vsnprintf(e->errorline, sizeof(e->errorline), fmt, ap); - va_end(ap); - - /* this should not happen */ - if (ret < 0) - err(1, "vsnprintf"); - - if ((size_t)ret >= sizeof(e->errorline)) - (void)strlcpy(e->errorline + (sizeof(e->errorline) - 4), - "...", 4); -} - -void -envelope_set_esc_class(struct envelope *e, enum enhanced_status_class class) -{ - e->esc_class = class; -} - -void -envelope_set_esc_code(struct envelope *e, enum enhanced_status_code code) -{ - e->esc_code = code; -} - -static int -envelope_buffer_to_dict(struct dict *d, const char *ibuf, size_t buflen) -{ - static char lbuf[sizeof(struct envelope)]; - size_t len; - char *buf, *field, *nextline; - - memset(lbuf, 0, sizeof lbuf); - if (strlcpy(lbuf, ibuf, sizeof lbuf) >= sizeof lbuf) - goto err; - buf = lbuf; - - while (buflen > 0) { - len = strcspn(buf, "\n"); - buf[len] = '\0'; - nextline = buf + len + 1; - buflen -= (nextline - buf); - - field = buf; - while (*buf && (isalnum((unsigned char)*buf) || *buf == '-')) - buf++; - if (!*buf) - goto err; - - /* skip whitespaces before separator */ - while (*buf && isspace((unsigned char)*buf)) - *buf++ = 0; - - /* we *want* ':' */ - if (*buf != ':') - goto err; - *buf++ = 0; - - /* skip whitespaces after separator */ - while (*buf && isspace((unsigned char)*buf)) - *buf++ = 0; - dict_set(d, field, buf); - buf = nextline; - } - - return (1); - -err: - return (0); -} - -int -envelope_load_buffer(struct envelope *ep, const char *ibuf, size_t buflen) -{ - struct dict d; - const char *val, *errstr; - long long version; - int ret = 0; - - dict_init(&d); - if (!envelope_buffer_to_dict(&d, ibuf, buflen)) { - log_debug("debug: cannot parse envelope to dict"); - goto end; - } - - val = dict_get(&d, "version"); - if (val == NULL) { - log_debug("debug: envelope version not found"); - goto end; - } - version = strtonum(val, 1, 64, &errstr); - if (errstr) { - log_debug("debug: cannot parse envelope version: %s", val); - goto end; - } - - if (version != SMTPD_ENVELOPE_VERSION) { - log_debug("debug: bad envelope version %lld", version); - goto end; - } - - memset(ep, 0, sizeof *ep); - ret = envelope_ascii_load(ep, &d); - if (ret) - ep->version = SMTPD_ENVELOPE_VERSION; -end: - while (dict_poproot(&d, NULL)) - ; - return (ret); -} - -int -envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) -{ - char *p = dest; - - envelope_ascii_dump(ep, &dest, &len, "version"); - envelope_ascii_dump(ep, &dest, &len, "dispatcher"); - envelope_ascii_dump(ep, &dest, &len, "tag"); - envelope_ascii_dump(ep, &dest, &len, "type"); - envelope_ascii_dump(ep, &dest, &len, "smtpname"); - envelope_ascii_dump(ep, &dest, &len, "helo"); - envelope_ascii_dump(ep, &dest, &len, "hostname"); - envelope_ascii_dump(ep, &dest, &len, "username"); - envelope_ascii_dump(ep, &dest, &len, "errorline"); - envelope_ascii_dump(ep, &dest, &len, "sockaddr"); - envelope_ascii_dump(ep, &dest, &len, "sender"); - envelope_ascii_dump(ep, &dest, &len, "rcpt"); - envelope_ascii_dump(ep, &dest, &len, "dest"); - envelope_ascii_dump(ep, &dest, &len, "ctime"); - envelope_ascii_dump(ep, &dest, &len, "last-try"); - envelope_ascii_dump(ep, &dest, &len, "last-bounce"); - envelope_ascii_dump(ep, &dest, &len, "ttl"); - envelope_ascii_dump(ep, &dest, &len, "retry"); - envelope_ascii_dump(ep, &dest, &len, "flags"); - envelope_ascii_dump(ep, &dest, &len, "dsn-notify"); - envelope_ascii_dump(ep, &dest, &len, "dsn-ret"); - envelope_ascii_dump(ep, &dest, &len, "dsn-envid"); - envelope_ascii_dump(ep, &dest, &len, "dsn-orcpt"); - envelope_ascii_dump(ep, &dest, &len, "esc-class"); - envelope_ascii_dump(ep, &dest, &len, "esc-code"); - - switch (ep->type) { - case D_MDA: - envelope_ascii_dump(ep, &dest, &len, "mda-exec"); - envelope_ascii_dump(ep, &dest, &len, "mda-subaddress"); - envelope_ascii_dump(ep, &dest, &len, "mda-user"); - break; - case D_MTA: - break; - case D_BOUNCE: - envelope_ascii_dump(ep, &dest, &len, "bounce-ttl"); - envelope_ascii_dump(ep, &dest, &len, "bounce-delay"); - envelope_ascii_dump(ep, &dest, &len, "bounce-type"); - break; - default: - return (0); - } - - if (dest == NULL) - return (0); - - return (dest - p); -} - -static int -ascii_load_uint8(uint8_t *dest, char *buf) -{ - const char *errstr; - - *dest = strtonum(buf, 0, 0xff, &errstr); - if (errstr) - return 0; - return 1; -} - -static int -ascii_load_uint16(uint16_t *dest, char *buf) -{ - const char *errstr; - - *dest = strtonum(buf, 0, 0xffff, &errstr); - if (errstr) - return 0; - return 1; -} - -static int -ascii_load_uint32(uint32_t *dest, char *buf) -{ - const char *errstr; - - *dest = strtonum(buf, 0, 0xffffffff, &errstr); - if (errstr) - return 0; - return 1; -} - -static int -ascii_load_time(time_t *dest, char *buf) -{ - const char *errstr; - - *dest = strtonum(buf, 0, LLONG_MAX, &errstr); - if (errstr) - return 0; - return 1; -} - -static int -ascii_load_type(enum delivery_type *dest, char *buf) -{ - if (strcasecmp(buf, "mda") == 0) - *dest = D_MDA; - else if (strcasecmp(buf, "mta") == 0) - *dest = D_MTA; - else if (strcasecmp(buf, "bounce") == 0) - *dest = D_BOUNCE; - else - return 0; - return 1; -} - -static int -ascii_load_string(char *dest, char *buf, size_t len) -{ - if (strlcpy(dest, buf, len) >= len) - return 0; - return 1; -} - -static int -ascii_load_sockaddr(struct sockaddr_storage *ss, char *buf) -{ - struct sockaddr_in6 ssin6; - struct sockaddr_in ssin; - - memset(&ssin, 0, sizeof ssin); - memset(&ssin6, 0, sizeof ssin6); - - if (!strcmp("local", buf)) { - ss->ss_family = AF_LOCAL; - } - else if (strncasecmp("IPv6:", buf, 5) == 0) { - /* XXX - remove this after 6.6 release */ - if (inet_pton(AF_INET6, buf + 5, &ssin6.sin6_addr) != 1) - return 0; - ssin6.sin6_family = AF_INET6; - memcpy(ss, &ssin6, sizeof(ssin6)); -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN - ss->ss_len = sizeof(struct sockaddr_in6); -#endif - } - else if (buf[0] == '[' && buf[strlen(buf)-1] == ']') { - buf[strlen(buf)-1] = '\0'; - if (inet_pton(AF_INET6, buf+1, &ssin6.sin6_addr) != 1) - return 0; - ssin6.sin6_family = AF_INET6; - memcpy(ss, &ssin6, sizeof(ssin6)); -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN - ss->ss_len = sizeof(struct sockaddr_in6); -#endif - } - else { - if (inet_pton(AF_INET, buf, &ssin.sin_addr) != 1) - return 0; - ssin.sin_family = AF_INET; - memcpy(ss, &ssin, sizeof(ssin)); -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN - ss->ss_len = sizeof(struct sockaddr_in); -#endif - } - return 1; -} - -static int -ascii_load_mailaddr(struct mailaddr *dest, char *buf) -{ - if (!text_to_mailaddr(dest, buf)) - return 0; - return 1; -} - -static int -ascii_load_flags(enum envelope_flags *dest, char *buf) -{ - char *flag; - - while ((flag = strsep(&buf, " ,|")) != NULL) { - if (strcasecmp(flag, "authenticated") == 0) - *dest |= EF_AUTHENTICATED; - else if (strcasecmp(flag, "enqueued") == 0) - ; - else if (strcasecmp(flag, "bounce") == 0) - *dest |= EF_BOUNCE; - else if (strcasecmp(flag, "internal") == 0) - *dest |= EF_INTERNAL; - else - return 0; - } - return 1; -} - -static int -ascii_load_bounce_type(enum bounce_type *dest, char *buf) -{ - if (strcasecmp(buf, "error") == 0 || strcasecmp(buf, "failed") == 0) - *dest = B_FAILED; - else if (strcasecmp(buf, "warn") == 0 || - strcasecmp(buf, "delayed") == 0) - *dest = B_DELAYED; - else if (strcasecmp(buf, "dsn") == 0 || - strcasecmp(buf, "delivered") == 0) - *dest = B_DELIVERED; - else - return 0; - return 1; -} - -static int -ascii_load_dsn_ret(enum dsn_ret *ret, char *buf) -{ - if (strcasecmp(buf, "HDRS") == 0) - *ret = DSN_RETHDRS; - else if (strcasecmp(buf, "FULL") == 0) - *ret = DSN_RETFULL; - else - return 0; - return 1; -} - -static int -ascii_load_field(const char *field, struct envelope *ep, char *buf) -{ - if (strcasecmp("dispatcher", field) == 0) - return ascii_load_string(ep->dispatcher, buf, - sizeof ep->dispatcher); - - if (strcasecmp("bounce-delay", field) == 0) - return ascii_load_time(&ep->agent.bounce.delay, buf); - - if (strcasecmp("bounce-ttl", field) == 0) - return ascii_load_time(&ep->agent.bounce.ttl, buf); - - if (strcasecmp("bounce-type", field) == 0) - return ascii_load_bounce_type(&ep->agent.bounce.type, buf); - - if (strcasecmp("ctime", field) == 0) - return ascii_load_time(&ep->creation, buf); - - if (strcasecmp("dest", field) == 0) - return ascii_load_mailaddr(&ep->dest, buf); - - if (strcasecmp("username", field) == 0) - return ascii_load_string(ep->username, buf, sizeof(ep->username)); - - if (strcasecmp("errorline", field) == 0) - return ascii_load_string(ep->errorline, buf, - sizeof ep->errorline); - - if (strcasecmp("ttl", field) == 0) - return ascii_load_time(&ep->ttl, buf); - - if (strcasecmp("flags", field) == 0) - return ascii_load_flags(&ep->flags, buf); - - if (strcasecmp("helo", field) == 0) - return ascii_load_string(ep->helo, buf, sizeof ep->helo); - - if (strcasecmp("hostname", field) == 0) - return ascii_load_string(ep->hostname, buf, - sizeof ep->hostname); - - if (strcasecmp("last-bounce", field) == 0) - return ascii_load_time(&ep->lastbounce, buf); - - if (strcasecmp("last-try", field) == 0) - return ascii_load_time(&ep->lasttry, buf); - - if (strcasecmp("retry", field) == 0) - return ascii_load_uint16(&ep->retry, buf); - - if (strcasecmp("rcpt", field) == 0) - return ascii_load_mailaddr(&ep->rcpt, buf); - - if (strcasecmp("mda-exec", field) == 0) - return ascii_load_string(ep->mda_exec, buf, sizeof(ep->mda_exec)); - - if (strcasecmp("mda-subaddress", field) == 0) - return ascii_load_string(ep->mda_subaddress, buf, sizeof(ep->mda_subaddress)); - - if (strcasecmp("mda-user", field) == 0) - return ascii_load_string(ep->mda_user, buf, sizeof(ep->mda_user)); - - if (strcasecmp("sender", field) == 0) - return ascii_load_mailaddr(&ep->sender, buf); - - if (strcasecmp("smtpname", field) == 0) - return ascii_load_string(ep->smtpname, buf, - sizeof(ep->smtpname)); - - if (strcasecmp("sockaddr", field) == 0) - return ascii_load_sockaddr(&ep->ss, buf); - - if (strcasecmp("tag", field) == 0) - return ascii_load_string(ep->tag, buf, sizeof ep->tag); - - if (strcasecmp("type", field) == 0) - return ascii_load_type(&ep->type, buf); - - if (strcasecmp("version", field) == 0) - return ascii_load_uint32(&ep->version, buf); - - if (strcasecmp("dsn-notify", field) == 0) - return ascii_load_uint8(&ep->dsn_notify, buf); - - if (strcasecmp("dsn-orcpt", field) == 0) - return ascii_load_mailaddr(&ep->dsn_orcpt, buf); - - if (strcasecmp("dsn-ret", field) == 0) - return ascii_load_dsn_ret(&ep->dsn_ret, buf); - - if (strcasecmp("dsn-envid", field) == 0) - return ascii_load_string(ep->dsn_envid, buf, - sizeof(ep->dsn_envid)); - - if (strcasecmp("esc-class", field) == 0) - return ascii_load_uint8(&ep->esc_class, buf); - - if (strcasecmp("esc-code", field) == 0) - return ascii_load_uint8(&ep->esc_code, buf); - - return (0); -} - -static int -envelope_ascii_load(struct envelope *ep, struct dict *d) -{ - const char *field; - char *value; - void *hdl; - - hdl = NULL; - while (dict_iter(d, &hdl, &field, (void **)&value)) - if (!ascii_load_field(field, ep, value)) - goto err; - - return (1); - -err: - log_warnx("envelope: invalid field \"%s\"", field); - return (0); -} - - -static int -ascii_dump_uint8(uint8_t src, char *dest, size_t len) -{ - return bsnprintf(dest, len, "%d", src); -} - -static int -ascii_dump_uint16(uint16_t src, char *dest, size_t len) -{ - return bsnprintf(dest, len, "%d", src); -} - -static int -ascii_dump_uint32(uint32_t src, char *dest, size_t len) -{ - return bsnprintf(dest, len, "%d", src); -} - -static int -ascii_dump_time(time_t src, char *dest, size_t len) -{ - return bsnprintf(dest, len, "%lld", (long long) src); -} - -static int -ascii_dump_string(const char *src, char *dest, size_t len) -{ - return bsnprintf(dest, len, "%s", src); -} - -static int -ascii_dump_type(enum delivery_type type, char *dest, size_t len) -{ - char *p = NULL; - - switch (type) { - case D_MDA: - p = "mda"; - break; - case D_MTA: - p = "mta"; - break; - case D_BOUNCE: - p = "bounce"; - break; - default: - return 0; - } - - return bsnprintf(dest, len, "%s", p); -} - -static int -ascii_dump_mailaddr(const struct mailaddr *addr, char *dest, size_t len) -{ - return bsnprintf(dest, len, "%s@%s", - addr->user, addr->domain); -} - -static int -ascii_dump_flags(enum envelope_flags flags, char *buf, size_t len) -{ - size_t cpylen = 0; - - buf[0] = '\0'; - if (flags) { - if (flags & EF_AUTHENTICATED) - cpylen = strlcat(buf, "authenticated", len); - if (flags & EF_BOUNCE) { - if (buf[0] != '\0') - (void)strlcat(buf, " ", len); - cpylen = strlcat(buf, "bounce", len); - } - if (flags & EF_INTERNAL) { - if (buf[0] != '\0') - (void)strlcat(buf, " ", len); - cpylen = strlcat(buf, "internal", len); - } - } - - return cpylen < len ? 1 : 0; -} - -static int -ascii_dump_bounce_type(enum bounce_type type, char *dest, size_t len) -{ - char *p = NULL; - - switch (type) { - case B_FAILED: - p = "failed"; - break; - case B_DELAYED: - p = "delayed"; - break; - case B_DELIVERED: - p = "delivered"; - break; - default: - return 0; - } - return bsnprintf(dest, len, "%s", p); -} - - -static int -ascii_dump_dsn_ret(enum dsn_ret flag, char *dest, size_t len) -{ - size_t cpylen = 0; - - dest[0] = '\0'; - if (flag == DSN_RETFULL) - cpylen = strlcat(dest, "FULL", len); - else if (flag == DSN_RETHDRS) - cpylen = strlcat(dest, "HDRS", len); - - return cpylen < len ? 1 : 0; -} - -static int -ascii_dump_field(const char *field, const struct envelope *ep, - char *buf, size_t len) -{ - if (strcasecmp(field, "dispatcher") == 0) - return ascii_dump_string(ep->dispatcher, buf, len); - - if (strcasecmp(field, "bounce-delay") == 0) { - if (ep->agent.bounce.type != B_DELAYED) - return (1); - return ascii_dump_time(ep->agent.bounce.delay, buf, len); - } - - if (strcasecmp(field, "bounce-ttl") == 0) { - if (ep->agent.bounce.type != B_DELAYED) - return (1); - return ascii_dump_time(ep->agent.bounce.ttl, buf, len); - } - - if (strcasecmp(field, "bounce-type") == 0) - return ascii_dump_bounce_type(ep->agent.bounce.type, buf, len); - - if (strcasecmp(field, "ctime") == 0) - return ascii_dump_time(ep->creation, buf, len); - - if (strcasecmp(field, "dest") == 0) - return ascii_dump_mailaddr(&ep->dest, buf, len); - - if (strcasecmp(field, "username") == 0) { - if (ep->username[0]) - return ascii_dump_string(ep->username, buf, len); - return 1; - } - - if (strcasecmp(field, "errorline") == 0) - return ascii_dump_string(ep->errorline, buf, len); - - if (strcasecmp(field, "ttl") == 0) - return ascii_dump_time(ep->ttl, buf, len); - - if (strcasecmp(field, "flags") == 0) - return ascii_dump_flags(ep->flags, buf, len); - - if (strcasecmp(field, "helo") == 0) - return ascii_dump_string(ep->helo, buf, len); - - if (strcasecmp(field, "hostname") == 0) - return ascii_dump_string(ep->hostname, buf, len); - - if (strcasecmp(field, "last-bounce") == 0) - return ascii_dump_time(ep->lastbounce, buf, len); - - if (strcasecmp(field, "last-try") == 0) - return ascii_dump_time(ep->lasttry, buf, len); - - if (strcasecmp(field, "retry") == 0) - return ascii_dump_uint16(ep->retry, buf, len); - - if (strcasecmp(field, "rcpt") == 0) - return ascii_dump_mailaddr(&ep->rcpt, buf, len); - - if (strcasecmp(field, "mda-exec") == 0) { - if (ep->mda_exec[0]) - return ascii_dump_string(ep->mda_exec, buf, len); - return 1; - } - - if (strcasecmp(field, "mda-subaddress") == 0) { - if (ep->mda_subaddress[0]) - return ascii_dump_string(ep->mda_subaddress, buf, len); - return 1; - } - - if (strcasecmp(field, "mda-user") == 0) { - if (ep->mda_user[0]) - return ascii_dump_string(ep->mda_user, buf, len); - return 1; - } - - if (strcasecmp(field, "sender") == 0) - return ascii_dump_mailaddr(&ep->sender, buf, len); - - if (strcasecmp(field, "smtpname") == 0) - return ascii_dump_string(ep->smtpname, buf, len); - - if (strcasecmp(field, "sockaddr") == 0) - return ascii_dump_string(ss_to_text(&ep->ss), buf, len); - - if (strcasecmp(field, "tag") == 0) - return ascii_dump_string(ep->tag, buf, len); - - if (strcasecmp(field, "type") == 0) - return ascii_dump_type(ep->type, buf, len); - - if (strcasecmp(field, "version") == 0) - return ascii_dump_uint32(SMTPD_ENVELOPE_VERSION, buf, len); - - if (strcasecmp(field, "dsn-notify") == 0) - return ascii_dump_uint8(ep->dsn_notify, buf, len); - - if (strcasecmp(field, "dsn-ret") == 0) - return ascii_dump_dsn_ret(ep->dsn_ret, buf, len); - - if (strcasecmp(field, "dsn-orcpt") == 0) { - if (ep->dsn_orcpt.user[0] && ep->dsn_orcpt.domain[0]) - return ascii_dump_mailaddr(&ep->dsn_orcpt, buf, len); - return 1; - } - - if (strcasecmp(field, "dsn-envid") == 0) - return ascii_dump_string(ep->dsn_envid, buf, len); - - if (strcasecmp(field, "esc-class") == 0) { - if (ep->esc_class) - return ascii_dump_uint8(ep->esc_class, buf, len); - return 1; - } - - if (strcasecmp(field, "esc-code") == 0) { - /* this is not a pasto, we dump esc_code if esc_class is !0 */ - if (ep->esc_class) - return ascii_dump_uint8(ep->esc_code, buf, len); - return 1; - } - - return (0); -} - -static void -envelope_ascii_dump(const struct envelope *ep, char **dest, size_t *len, - const char *field) -{ - char buf[8192]; - int l; - - if (*dest == NULL) - return; - - memset(buf, 0, sizeof buf); - if (!ascii_dump_field(field, ep, buf, sizeof buf)) - goto err; - if (buf[0] == '\0') - return; - - l = snprintf(*dest, *len, "%s: %s\n", field, buf); - if (l < 0 || (size_t) l >= *len) - goto err; - *dest += l; - *len -= l; - - return; -err: - *dest = NULL; -} diff --git a/smtpd/esc.c b/smtpd/esc.c deleted file mode 100644 index 64a44c79..00000000 --- a/smtpd/esc.c +++ /dev/null @@ -1,116 +0,0 @@ -/* $OpenBSD: esc.c,v 1.5 2016/09/03 22:16:39 gilles Exp $ */ - -/* - * Copyright (c) 2014 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include - -#include "smtpd-defines.h" -#include "smtpd-api.h" - -static struct escode { - enum enhanced_status_code code; - const char *description; -} esc[] = { - /* 0.0 */ - { ESC_OTHER_STATUS, "Other/Undefined" }, - - /* 1.x */ - { ESC_OTHER_ADDRESS_STATUS, "Other/Undefined address status" }, - { ESC_BAD_DESTINATION_MAILBOX_ADDRESS, "Bad destination mailbox address" }, - { ESC_BAD_DESTINATION_SYSTEM_ADDRESS, "Bad destination system address" }, - { ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX, "Bad destination mailbox address syntax" }, - { ESC_DESTINATION_MAILBOX_ADDRESS_AMBIGUOUS, "Destination mailbox address ambiguous" }, - { ESC_DESTINATION_ADDRESS_VALID, "Destination address valid" }, - { ESC_DESTINATION_MAILBOX_HAS_MOVED, "Destination mailbox has moved, No forwarding address" }, - { ESC_BAD_SENDER_MAILBOX_ADDRESS_SYNTAX, "Bad sender's mailbox address syntax" }, - { ESC_BAD_SENDER_SYSTEM_ADDRESS, "Bad sender's system address syntax" }, - - /* 2.x */ - { ESC_OTHER_MAILBOX_STATUS, "Other/Undefined mailbox status" }, - { ESC_MAILBOX_DISABLED, "Mailbox disabled, not accepting messages" }, - { ESC_MAILBOX_FULL, "Mailbox full" }, - { ESC_MESSAGE_LENGTH_TOO_LARGE, "Message length exceeds administrative limit" }, - { ESC_MAILING_LIST_EXPANSION_PROBLEM, "Mailing list expansion problem" }, - - /* 3.x */ - { ESC_OTHER_MAIL_SYSTEM_STATUS, "Other/Undefined mail system status" }, - { ESC_MAIL_SYSTEM_FULL, "Mail system full" }, - { ESC_SYSTEM_NOT_ACCEPTING_MESSAGES, "System not accepting network messages" }, - { ESC_SYSTEM_NOT_CAPABLE_OF_SELECTED_FEATURES, "System not capable of selected features" }, - { ESC_MESSAGE_TOO_BIG_FOR_SYSTEM, "Message too big for system" }, - { ESC_SYSTEM_INCORRECTLY_CONFIGURED, "System incorrectly configured" }, - - /* 4.x */ - { ESC_OTHER_NETWORK_ROUTING_STATUS, "Other/Undefined network or routing status" }, - { ESC_NO_ANSWER_FROM_HOST, "No answer from host" }, - { ESC_BAD_CONNECTION, "Bad connection" }, - { ESC_DIRECTORY_SERVER_FAILURE, "Directory server failure" }, - { ESC_UNABLE_TO_ROUTE, "Unable to route" }, - { ESC_MAIL_SYSTEM_CONGESTION, "Mail system congestion" }, - { ESC_ROUTING_LOOP_DETECTED, "Routing loop detected" }, - { ESC_DELIVERY_TIME_EXPIRED, "Delivery time expired" }, - - /* 5.x */ - { ESC_INVALID_RECIPIENT, "Invalid recipient" }, - { ESC_INVALID_COMMAND, "Invalid command" }, - { ESC_SYNTAX_ERROR, "Syntax error" }, - { ESC_TOO_MANY_RECIPIENTS, "Too many recipients" }, - { ESC_INVALID_COMMAND_ARGUMENTS, "Invalid command arguments" }, - { ESC_WRONG_PROTOCOL_VERSION, "Wrong protocol version" }, - - /* 6.x */ - { ESC_OTHER_MEDIA_ERROR, "Other/Undefined media error" }, - { ESC_MEDIA_NOT_SUPPORTED, "Media not supported" }, - { ESC_CONVERSION_REQUIRED_AND_PROHIBITED, "Conversion required and prohibited" }, - { ESC_CONVERSION_REQUIRED_BUT_NOT_SUPPORTED, "Conversion required but not supported" }, - { ESC_CONVERSION_WITH_LOSS_PERFORMED, "Conversion with loss performed" }, - { ESC_CONVERSION_FAILED, "Conversion failed" }, - - /* 7.x */ - { ESC_OTHER_SECURITY_STATUS, "Other/Undefined security status" }, - { ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED, "Delivery not authorized, message refused" }, - { ESC_MAILING_LIST_EXPANSION_PROHIBITED, "Mailing list expansion prohibited" }, - { ESC_SECURITY_CONVERSION_REQUIRED_NOT_POSSIBLE,"Security conversion required but not possible" }, - { ESC_SECURITY_FEATURES_NOT_SUPPORTED, "Security features not supported" }, - { ESC_CRYPTOGRAPHIC_FAILURE, "Cryptographic failure" }, - { ESC_CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED, "Cryptographic algorithm not supported" }, - { ESC_MESSAGE_TOO_BIG_FOR_SYSTEM, "Message integrity failure" }, -}; - -const char * -esc_code(enum enhanced_status_class class, enum enhanced_status_code code) -{ - static char buffer[6]; - - (void)snprintf(buffer, sizeof buffer, "%d.%d.%d", class, code / 10, code % 10); - return buffer; - -} - -const char * -esc_description(enum enhanced_status_code code) -{ - uint32_t i; - - for (i = 0; i < nitems(esc); ++i) - if (code == esc[i].code) - return esc[i].description; - return "Other/Undefined"; -} diff --git a/smtpd/expand.c b/smtpd/expand.c deleted file mode 100644 index a4306fc0..00000000 --- a/smtpd/expand.c +++ /dev/null @@ -1,332 +0,0 @@ -/* $OpenBSD: expand.c,v 1.31 2018/05/31 21:06:12 gilles Exp $ */ - -/* - * Copyright (c) 2009 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UTIL_H -#include -#endif -#ifdef HAVE_LIBUTIL_H -#include -#endif - -#include "smtpd.h" -#include "log.h" - -static const char *expandnode_info(struct expandnode *); - -struct expandnode * -expand_lookup(struct expand *expand, struct expandnode *key) -{ - return RB_FIND(expandtree, &expand->tree, key); -} - -int -expand_to_text(struct expand *expand, char *buf, size_t sz) -{ - struct expandnode *xn; - - buf[0] = '\0'; - - RB_FOREACH(xn, expandtree, &expand->tree) { - if (buf[0]) - (void)strlcat(buf, ", ", sz); - if (strlcat(buf, expandnode_to_text(xn), sz) >= sz) - return 0; - } - - return 1; -} - -void -expand_insert(struct expand *expand, struct expandnode *node) -{ - struct expandnode *xn; - - node->rule = expand->rule; - node->parent = expand->parent; - - log_trace(TRACE_EXPAND, "expand: %p: expand_insert() called for %s", - expand, expandnode_info(node)); - if (node->type == EXPAND_USERNAME && - expand->parent && - expand->parent->type == EXPAND_USERNAME && - !strcmp(expand->parent->u.user, node->u.user)) { - log_trace(TRACE_EXPAND, "expand: %p: setting sameuser = 1", - expand); - node->sameuser = 1; - } - - if (expand_lookup(expand, node)) { - log_trace(TRACE_EXPAND, "expand: %p: node found, discarding", - expand); - return; - } - - xn = xmemdup(node, sizeof *xn); - xn->rule = expand->rule; - xn->parent = expand->parent; - if (xn->parent) - xn->depth = xn->parent->depth + 1; - else - xn->depth = 0; - RB_INSERT(expandtree, &expand->tree, xn); - if (expand->queue) - TAILQ_INSERT_TAIL(expand->queue, xn, tq_entry); - expand->nb_nodes++; - log_trace(TRACE_EXPAND, "expand: %p: inserted node %p", expand, xn); -} - -void -expand_clear(struct expand *expand) -{ - struct expandnode *xn; - - log_trace(TRACE_EXPAND, "expand: %p: clearing expand tree", expand); - if (expand->queue) - while ((xn = TAILQ_FIRST(expand->queue))) - TAILQ_REMOVE(expand->queue, xn, tq_entry); - - while ((xn = RB_ROOT(&expand->tree)) != NULL) { - RB_REMOVE(expandtree, &expand->tree, xn); - free(xn); - } -} - -void -expand_free(struct expand *expand) -{ - expand_clear(expand); - - log_trace(TRACE_EXPAND, "expand: %p: freeing expand tree", expand); - free(expand); -} - -int -expand_cmp(struct expandnode *e1, struct expandnode *e2) -{ - struct expandnode *p1, *p2; - int r; - - if (e1->type < e2->type) - return -1; - if (e1->type > e2->type) - return 1; - if (e1->sameuser < e2->sameuser) - return -1; - if (e1->sameuser > e2->sameuser) - return 1; - if (e1->realuser < e2->realuser) - return -1; - if (e1->realuser > e2->realuser) - return 1; - - r = memcmp(&e1->u, &e2->u, sizeof(e1->u)); - if (r) - return (r); - - if (e1->parent == e2->parent) - return (0); - - if (e1->parent == NULL) - return (-1); - if (e2->parent == NULL) - return (1); - - /* - * The same node can be expanded in for different dest context. - * Wen need to distinguish between those. - */ - for(p1 = e1->parent; p1->type != EXPAND_ADDRESS; p1 = p1->parent) - ; - for(p2 = e2->parent; p2->type != EXPAND_ADDRESS; p2 = p2->parent) - ; - if (p1 < p2) - return (-1); - if (p1 > p2) - return (1); - - if (e1->type != EXPAND_FILENAME && e1->type != EXPAND_FILTER) - return (0); - - /* - * For external delivery, we need to distinguish between users. - * If we can't find a username, we assume it is _smtpd. - */ - for(p1 = e1->parent; p1 && p1->type != EXPAND_USERNAME; p1 = p1->parent) - ; - for(p2 = e2->parent; p2 && p2->type != EXPAND_USERNAME; p2 = p2->parent) - ; - if (p1 < p2) - return (-1); - if (p1 > p2) - return (1); - - return (0); -} - -static int -expand_line_split(char **line, char **ret) -{ - static char buffer[LINE_MAX]; - int esc, dq, sq; - size_t i; - char *s; - - memset(buffer, 0, sizeof buffer); - esc = dq = sq = 0; - i = 0; - for (s = *line; (*s) && (i < sizeof(buffer)); ++s) { - if (esc) { - buffer[i++] = *s; - esc = 0; - continue; - } - if (*s == '\\') { - esc = 1; - continue; - } - if (*s == ',' && !dq && !sq) { - *ret = buffer; - *line = s+1; - return (1); - } - - buffer[i++] = *s; - esc = 0; - - if (*s == '"' && !sq) - dq ^= 1; - if (*s == '\'' && !dq) - sq ^= 1; - } - - if (esc || dq || sq || i == sizeof(buffer)) - return (-1); - - *ret = buffer; - *line = s; - return (i ? 1 : 0); -} - -int -expand_line(struct expand *expand, const char *s, int do_includes) -{ - struct expandnode xn; - char buffer[LINE_MAX]; - char *p, *subrcpt; - int ret; - - memset(buffer, 0, sizeof buffer); - if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) - return 0; - - p = buffer; - while ((ret = expand_line_split(&p, &subrcpt)) > 0) { - subrcpt = strip(subrcpt); - if (subrcpt[0] == '\0') - continue; - if (!text_to_expandnode(&xn, subrcpt)) - return 0; - if (!do_includes) - if (xn.type == EXPAND_INCLUDE) - continue; - expand_insert(expand, &xn); - } - - if (ret >= 0) - return 1; - - /* expand_line_split() returned < 0 */ - return 0; -} - -static const char * -expandnode_info(struct expandnode *e) -{ - static char buffer[1024]; - const char *type = NULL; - const char *value = NULL; - char tmp[64]; - - switch (e->type) { - case EXPAND_FILTER: - type = "filter"; - break; - case EXPAND_FILENAME: - type = "filename"; - break; - case EXPAND_INCLUDE: - type = "include"; - break; - case EXPAND_USERNAME: - type = "username"; - break; - case EXPAND_ADDRESS: - type = "address"; - break; - case EXPAND_ERROR: - type = "error"; - break; - case EXPAND_INVALID: - default: - return NULL; - } - - if ((value = expandnode_to_text(e)) == NULL) - return NULL; - - (void)strlcpy(buffer, type, sizeof buffer); - (void)strlcat(buffer, ":", sizeof buffer); - if (strlcat(buffer, value, sizeof buffer) >= sizeof buffer) - return NULL; - - (void)snprintf(tmp, sizeof(tmp), "[parent=%p", e->parent); - if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) - return NULL; - - (void)snprintf(tmp, sizeof(tmp), ", rule=%p", e->rule); - if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) - return NULL; - - if (e->rule) { - (void)snprintf(tmp, sizeof(tmp), ", dispatcher=%p", e->rule->dispatcher); - if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) - return NULL; - } - - if (strlcat(buffer, "]", sizeof buffer) >= sizeof buffer) - return NULL; - - return buffer; -} - -RB_GENERATE(expandtree, expandnode, entry, expand_cmp); diff --git a/smtpd/filter.c b/smtpd/filter.c deleted file mode 100644 index 614486b7..00000000 --- a/smtpd/filter.c +++ /dev/null @@ -1,868 +0,0 @@ -/* $OpenBSD: filter.c,v 1.25 2017/01/09 09:53:23 reyk Exp $ */ - -/* - * Copyright (c) 2011 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -enum { - QUERY_READY, - QUERY_RUNNING, - QUERY_DONE -}; - - -struct filter_proc { - TAILQ_ENTRY(filter_proc) entry; - struct mproc mproc; - int hooks; - int flags; - int ready; -}; - -struct filter { - TAILQ_ENTRY(filter) entry; - struct filter_proc *proc; -}; -TAILQ_HEAD(filter_lst, filter); - -TAILQ_HEAD(filter_query_lst, filter_query); -struct filter_session { - uint64_t id; - int terminate; - struct filter_lst *filters; - struct filter *fcurr; - - int error; - struct io *iev; - size_t idatalen; - FILE *ofile; - - struct filter_query *eom; -}; - -struct filter_query { - uint64_t qid; - int type; - struct filter_session *session; - - int state; - struct filter *current; - - /* current data */ - union { - struct { - struct sockaddr_storage local; - struct sockaddr_storage remote; - char hostname[HOST_NAME_MAX+1]; - } connect; - char line[LINE_MAX]; - struct mailaddr maddr; - size_t datalen; - } u; - - /* current response */ - struct { - int status; - int code; - char *response; - } smtp; -}; - -static void filter_imsg(struct mproc *, struct imsg *); -static void filter_post_event(uint64_t, int, struct filter *, struct filter *); -static struct filter_query *filter_query(struct filter_session *, int); -static void filter_drain_query(struct filter_query *); -static void filter_run_query(struct filter *, struct filter_query *); -static void filter_end_query(struct filter_query *); -static void filter_set_sink(struct filter_session *, int); -static int filter_tx(struct filter_session *, int); -static void filter_tx_io(struct io *, int, void *); - -static TAILQ_HEAD(, filter_proc) procs; -struct dict chains; - -static const char * filter_session_to_text(struct filter_session *); -static const char * filter_query_to_text(struct filter_query *); -static const char * filter_to_text(struct filter *); -static const char * filter_proc_to_text(struct filter_proc *); -static const char * query_to_str(int); -static const char * event_to_str(int); -static const char * status_to_str(int); -static const char * filterimsg_to_str(int); - -struct tree sessions; -struct tree queries; - -static void -filter_add_arg(struct filter_conf *filter, char *arg) -{ - if (filter->argc == MAX_FILTER_ARGS) { - log_warnx("warn: filter \"%s\" is full", filter->name); - fatalx("exiting"); - } - filter->argv[filter->argc++] = arg; -} - -static void -filter_extend_chain(struct filter_lst *chain, const char *name) -{ - struct filter *n; - struct filter_lst *fchain; - struct filter_conf *fconf; - int i; - - fconf = dict_xget(&env->sc_filters, name); - if (fconf->chain) { - log_debug("filter: extending with \"%s\"", name); - for (i = 0; i < fconf->argc; i++) - filter_extend_chain(chain, fconf->argv[i]); - } - else { - log_debug("filter: adding filter \"%s\"", name); - n = xcalloc(1, sizeof(*n), "filter_extend_chain"); - fchain = dict_get(&chains, name); - n->proc = TAILQ_FIRST(fchain)->proc; - TAILQ_INSERT_TAIL(chain, n, entry); - } -} - -void -filter_postfork(void) -{ - static int prepare = 0; - struct filter_conf *filter; - void *iter; - struct filter_proc *proc; - struct filter_lst *fchain; - struct filter *f; - struct mproc *p; - int done, i; - - if (prepare) - return; - prepare = 1; - - TAILQ_INIT(&procs); - dict_init(&chains); - - log_debug("filter: building simple chains..."); - - /* create all filter proc and associated chains */ - iter = NULL; - while (dict_iter(&env->sc_filters, &iter, NULL, (void **)&filter)) { - if (filter->chain) - continue; - - log_debug("filter: building simple chain \"%s\"", filter->name); - proc = xcalloc(1, sizeof(*proc), "filter_postfork"); - p = &proc->mproc; - p->handler = filter_imsg; - p->proc = PROC_FILTER; - p->name = xstrdup(filter->name, "filter_postfork"); - p->data = proc; - if (tracing & TRACE_DEBUG) - filter_add_arg(filter, "-v"); - if (foreground_log) - filter_add_arg(filter, "-d"); - if (mproc_fork(p, filter->path, filter->argv) < 0) - fatalx("filter_postfork"); - - log_debug("filter: registering proc \"%s\"", filter->name); - f = xcalloc(1, sizeof(*f), "filter_postfork"); - f->proc = proc; - - TAILQ_INSERT_TAIL(&procs, proc, entry); - fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); - TAILQ_INIT(fchain); - TAILQ_INSERT_TAIL(fchain, f, entry); - dict_xset(&chains, filter->name, fchain); - filter->done = 1; - } - - log_debug("filter: building complex chains..."); - - /* resolve all chains */ - done = 0; - while (!done) { - done = 1; - iter = NULL; - while (dict_iter(&env->sc_filters, &iter, NULL, - (void **)&filter)) { - if (filter->done) - continue; - done = 0; - filter->done = 1; - for (i = 0; i < filter->argc; i++) { - if (!dict_get(&chains, filter->argv[i])) { - filter->done = 0; - break; - } - } - if (filter->done == 0) - continue; - fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); - TAILQ_INIT(fchain); - log_debug("filter: building chain \"%s\"...", - filter->name); - for (i = 0; i < filter->argc; i++) - filter_extend_chain(fchain, filter->argv[i]); - log_debug("filter: done building chain \"%s\"", - filter->name); - dict_xset(&chains, filter->name, fchain); - } - } - log_debug("filter: done building complex chains"); - - fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); - TAILQ_INIT(fchain); - dict_xset(&chains, "", fchain); -} - -void -filter_configure(void) -{ - static int init = 0; - struct filter_proc *p; - - if (init) - return; - init = 1; - - tree_init(&sessions); - tree_init(&queries); - - TAILQ_FOREACH(p, &procs, entry) { - m_create(&p->mproc, IMSG_FILTER_REGISTER, 0, 0, -1); - m_add_u32(&p->mproc, FILTER_API_VERSION); - m_add_string(&p->mproc, p->mproc.name); - m_close(&p->mproc); - mproc_enable(&p->mproc); - } - - if (TAILQ_FIRST(&procs) == NULL) - smtp_configure(); -} - -void -filter_event(uint64_t id, int event) -{ - struct filter_session *s; - - if (event == EVENT_DISCONNECT) - /* On disconnect, the session is virtualy dead */ - s = tree_xpop(&sessions, id); - else - s = tree_xget(&sessions, id); - - filter_post_event(id, event, TAILQ_FIRST(s->filters), NULL); - - if (event == EVENT_DISCONNECT) { - if (s->iev) - io_free(s->iev); - if (s->ofile) - fclose(s->ofile); - free(s); - } -} - -void -filter_connect(uint64_t id, const struct sockaddr *local, - const struct sockaddr *remote, const char *host, const char *filter) -{ - struct filter_session *s; - struct filter_query *q; - - s = xcalloc(1, sizeof(*s), "filter_event"); - s->id = id; - if (filter == NULL) - filter = ""; - s->filters = dict_xget(&chains, filter); - tree_xset(&sessions, s->id, s); - - filter_event(id, EVENT_CONNECT); - q = filter_query(s, QUERY_CONNECT); - - memmove(&q->u.connect.local, local, SA_LEN(local)); - memmove(&q->u.connect.remote, remote, SA_LEN(remote)); - strlcpy(q->u.connect.hostname, host, sizeof(q->u.connect.hostname)); - - q->smtp.status = FILTER_OK; - q->smtp.code = 0; - q->smtp.response = NULL; - - filter_drain_query(q); -} - -void -filter_mailaddr(uint64_t id, int type, const struct mailaddr *maddr) -{ - struct filter_session *s; - struct filter_query *q; - - s = tree_xget(&sessions, id); - q = filter_query(s, type); - - strlcpy(q->u.maddr.user, maddr->user, sizeof(q->u.maddr.user)); - strlcpy(q->u.maddr.domain, maddr->domain, sizeof(q->u.maddr.domain)); - - filter_drain_query(q); -} - -void -filter_line(uint64_t id, int type, const char *line) -{ - struct filter_session *s; - struct filter_query *q; - - s = tree_xget(&sessions, id); - q = filter_query(s, type); - - if (line) - strlcpy(q->u.line, line, sizeof(q->u.line)); - - filter_drain_query(q); -} - -void -filter_eom(uint64_t id, int type, size_t datalen) -{ - struct filter_session *s; - struct filter_query *q; - - s = tree_xget(&sessions, id); - q = filter_query(s, type); - q->u.datalen = datalen; - - filter_drain_query(q); -} - -static void -filter_set_sink(struct filter_session *s, int sink) -{ - struct mproc *p; - - while (s->fcurr) { - if (s->fcurr->proc->hooks & HOOK_DATALINE) { - log_trace(TRACE_FILTERS, "filter: sending fd %d to %s", - sink, filter_to_text(s->fcurr)); - p = &s->fcurr->proc->mproc; - m_create(p, IMSG_FILTER_PIPE, 0, 0, sink); - m_add_id(p, s->id); - m_close(p); - return; - } - s->fcurr = TAILQ_PREV(s->fcurr, filter_lst, entry); - } - - log_trace(TRACE_FILTERS, "filter: chain input is %d", sink); - smtp_filter_fd(s->id, sink); -} - -void -filter_build_fd_chain(uint64_t id, int sink) -{ - struct filter_session *s; - int fd; - - s = tree_xget(&sessions, id); - s->fcurr = TAILQ_LAST(s->filters, filter_lst); - - fd = filter_tx(s, sink); - filter_set_sink(s, fd); -} - -void -filter_post_event(uint64_t id, int event, struct filter *f, struct filter *end) -{ - for(; f && f != end; f = TAILQ_NEXT(f, entry)) { - log_trace(TRACE_FILTERS, "filter: post-event event=%s filter=%s", - event_to_str(event), f->proc->mproc.name); - - m_create(&f->proc->mproc, IMSG_FILTER_EVENT, 0, 0, -1); - m_add_id(&f->proc->mproc, id); - m_add_int(&f->proc->mproc, event); - m_close(&f->proc->mproc); - } -} - -static struct filter_query * -filter_query(struct filter_session *s, int type) -{ - struct filter_query *q; - - q = xcalloc(1, sizeof(*q), "filter_query"); - q->qid = generate_uid(); - q->session = s; - q->type = type; - - q->state = QUERY_READY; - q->current = TAILQ_FIRST(s->filters); - - log_trace(TRACE_FILTERS, "filter: new query %s", query_to_str(type)); - - return (q); -} - -static void -filter_drain_query(struct filter_query *q) -{ - log_trace(TRACE_FILTERS, "filter: filter_drain_query %s", - filter_query_to_text(q)); - - /* - * The query must be passed through all filters that registered - * a hook, until one rejects it. - */ - while (q->state != QUERY_DONE) { - /* Walk over all filters */ - while (q->current) { - filter_run_query(q->current, q); - if (q->state == QUERY_RUNNING) { - log_trace(TRACE_FILTERS, - "filter: waiting for running query %s", - filter_query_to_text(q)); - return; - } - } - q->state = QUERY_DONE; - } - - /* Defer the response if the file is not closed yet. */ - if (q->type == QUERY_EOM && q->session->ofile && q->smtp.status == FILTER_OK) { - log_debug("filter: deferring eom query..."); - q->session->eom = q; - return; - } - - filter_end_query(q); -} - -static void -filter_run_query(struct filter *f, struct filter_query *q) -{ - log_trace(TRACE_FILTERS, - "filter: running filter %s for query %s", - filter_to_text(f), filter_query_to_text(q)); - - m_create(&f->proc->mproc, IMSG_FILTER_QUERY, 0, 0, -1); - m_add_id(&f->proc->mproc, q->session->id); - m_add_id(&f->proc->mproc, q->qid); - m_add_int(&f->proc->mproc, q->type); - - switch (q->type) { - case QUERY_CONNECT: - m_add_sockaddr(&f->proc->mproc, - (struct sockaddr *)&q->u.connect.local); - m_add_sockaddr(&f->proc->mproc, - (struct sockaddr *)&q->u.connect.remote); - m_add_string(&f->proc->mproc, q->u.connect.hostname); - break; - case QUERY_HELO: - m_add_string(&f->proc->mproc, q->u.line); - break; - case QUERY_MAIL: - case QUERY_RCPT: - m_add_mailaddr(&f->proc->mproc, &q->u.maddr); - break; - case QUERY_EOM: - m_add_u32(&f->proc->mproc, q->u.datalen); - break; - default: - break; - } - m_close(&f->proc->mproc); - - tree_xset(&queries, q->qid, q); - q->state = QUERY_RUNNING; -} - -static void -filter_end_query(struct filter_query *q) -{ - struct filter_session *s = q->session; - const char *response = q->smtp.response; - - log_trace(TRACE_FILTERS, "filter: filter_end_query %s", - filter_query_to_text(q)); - - if (q->type == QUERY_EOM && q->smtp.status == FILTER_OK) { - if (s->error || q->u.datalen != s->idatalen) { - response = "Internal error"; - q->smtp.code = 451; - q->smtp.status = FILTER_FAIL; - if (!s->error) - log_warnx("filter: datalen mismatch on session %" PRIx64 - ": %zu/%zu", s->id, s->idatalen, q->u.datalen); - } - } - - log_trace(TRACE_FILTERS, - "filter: query %016"PRIx64" done: " - "status=%s code=%d response=\"%s\"", - q->qid, - status_to_str(q->smtp.status), - q->smtp.code, - response); - - smtp_filter_response(s->id, q->type, q->smtp.status, q->smtp.code, - response); - free(q->smtp.response); - free(q); -} - -static void -filter_imsg(struct mproc *p, struct imsg *imsg) -{ - struct filter_proc *proc = p->data; - struct filter_session *s; - struct filter_query *q; - struct msg m; - const char *line; - uint64_t qid; - uint32_t datalen; - int type, status, code; - - if (imsg == NULL) { - log_warnx("warn: filter \"%s\" closed unexpectedly", p->name); - fatalx("exiting"); - } - - log_trace(TRACE_FILTERS, "filter: imsg %s from procfilter %s", - filterimsg_to_str(imsg->hdr.type), - filter_proc_to_text(proc)); - - switch (imsg->hdr.type) { - - case IMSG_FILTER_REGISTER: - if (proc->ready) { - log_warnx("warn: filter \"%s\" already registered", - proc->mproc.name); - exit(1); - } - - m_msg(&m, imsg); - m_get_int(&m, &proc->hooks); - m_get_int(&m, &proc->flags); - m_end(&m); - proc->ready = 1; - - log_debug("debug: filter \"%s\": hooks 0x%08x flags 0x%04x", - proc->mproc.name, proc->hooks, proc->flags); - - TAILQ_FOREACH(proc, &procs, entry) - if (!proc->ready) - return; - - smtp_configure(); - break; - - case IMSG_FILTER_RESPONSE: - m_msg(&m, imsg); - m_get_id(&m, &qid); - m_get_int(&m, &type); - if (type == QUERY_EOM) - m_get_u32(&m, &datalen); - m_get_int(&m, &status); - m_get_int(&m, &code); - if (m_is_eom(&m)) - line = NULL; - else - m_get_string(&m, &line); - m_end(&m); - - q = tree_xpop(&queries, qid); - if (q->type != type) { - log_warnx("warn: filter: type mismatch %d != %d", - q->type, type); - fatalx("exiting"); - } - q->smtp.status = status; - if (code) - q->smtp.code = code; - if (line) { - free(q->smtp.response); - q->smtp.response = xstrdup(line, "filter_imsg"); - } - q->state = (status == FILTER_OK) ? QUERY_READY : QUERY_DONE; - if (type == QUERY_EOM) - q->u.datalen = datalen; - - q->current = TAILQ_NEXT(q->current, entry); - filter_drain_query(q); - break; - - case IMSG_FILTER_PIPE: - m_msg(&m, imsg); - m_get_id(&m, &qid); - m_end(&m); - - s = tree_xget(&sessions, qid); - s->fcurr = TAILQ_PREV(s->fcurr, filter_lst, entry); - filter_set_sink(s, imsg->fd); - break; - - default: - log_warnx("warn: bad imsg from filter %s", p->name); - exit(1); - } -} - -static int -filter_tx(struct filter_session *s, int sink) -{ - int sp[2]; - - s->idatalen = 0; - s->eom = NULL; - s->error = 0; - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) { - log_warn("warn: filter: socketpair"); - return (-1); - } - - if ((s->ofile = fdopen(sink, "w")) == NULL) { - log_warn("warn: filter: fdopen"); - close(sp[0]); - close(sp[1]); - return (-1); - } - - io_set_nonblocking(sp[0]); - io_set_nonblocking(sp[1]); - - s->iev = io_new(); - io_set_callback(s->iev, filter_tx_io, s); - io_set_fd(s->iev, sp[0]); - io_set_read(s->iev); - - return (sp[1]); -} - -static void -filter_tx_io(struct io *io, int evt, void *arg) -{ - struct filter_session *s = arg; - size_t len, n; - char *data; - - log_trace(TRACE_FILTERS, "filter: filter_tx_io(%p, %s)", - s, io_strevent(evt)); - - switch (evt) { - case IO_DATAIN: - data = io_data(s->iev); - len = io_datalen(s->iev); - - log_trace(TRACE_FILTERS, - "filter: filter_tx_io: datain (%zu) for req %016"PRIx64"", - len, s->id); - - n = fwrite(data, 1, len, s->ofile); - if (n != len) { - log_warnx("warn: filter_tx_io: fwrite %zu/%zu", n, len); - s->error = 1; - break; - } - s->idatalen += n; - io_drop(s->iev, n); - return; - - case IO_DISCONNECTED: - log_trace(TRACE_FILTERS, - "debug: filter: tx done (%zu) for req %016"PRIx64, - s->idatalen, s->id); - break; - - default: - log_warn("warn: filter_tx_io: bad evt (%d) for req %016"PRIx64, - evt, s->id); - s->error = 1; - break; - } - - io_free(s->iev); - s->iev = NULL; - fclose(s->ofile); - s->ofile = NULL; - - /* deferred eom request */ - if (s->eom) { - log_debug("filter: running eom query..."); - filter_end_query(s->eom); - } else { - log_debug("filter: eom not received yet"); - } -} - -static const char * -filter_query_to_text(struct filter_query *q) -{ - static char buf[1024]; - char tmp[1024]; - - tmp[0] = '\0'; - - switch (q->type) { - case QUERY_CONNECT: - strlcat(tmp, "=", sizeof tmp); - strlcat(tmp, ss_to_text(&q->u.connect.local), - sizeof tmp); - strlcat(tmp, " <-> ", sizeof tmp); - strlcat(tmp, ss_to_text(&q->u.connect.remote), - sizeof tmp); - strlcat(tmp, "(", sizeof tmp); - strlcat(tmp, q->u.connect.hostname, sizeof tmp); - strlcat(tmp, ")", sizeof tmp); - break; - case QUERY_MAIL: - case QUERY_RCPT: - snprintf(tmp, sizeof tmp, "=%s@%s", - q->u.maddr.user, q->u.maddr.domain); - break; - case QUERY_HELO: - snprintf(tmp, sizeof tmp, "=%s", q->u.line); - break; - default: - break; - } - snprintf(buf, sizeof buf, "%016"PRIx64"[%s%s,%s]", - q->qid, query_to_str(q->type), tmp, - filter_session_to_text(q->session)); - - return (buf); -} - -static const char * -filter_session_to_text(struct filter_session *s) -{ - static char buf[1024]; - - if (s == NULL) - return "filter_session@NULL"; - - snprintf(buf, sizeof(buf), - "filter_session@%p[datalen=%zu,eom=%p,ofile=%p]", - s, s->idatalen, s->eom, s->ofile); - - return buf; -} - -static const char * -filter_to_text(struct filter *f) -{ - static char buf[1024]; - - snprintf(buf, sizeof buf, "filter:%s", filter_proc_to_text(f->proc)); - - return (buf); -} - -static const char * -filter_proc_to_text(struct filter_proc *proc) -{ - static char buf[1024]; - - snprintf(buf, sizeof buf, "%s[hooks=0x%08x,flags=0x%04x]", - proc->mproc.name, proc->hooks, proc->flags); - - return (buf); -} - -#define CASE(x) case x : return #x - -static const char * -filterimsg_to_str(int imsg) -{ - switch (imsg) { - CASE(IMSG_FILTER_REGISTER); - CASE(IMSG_FILTER_EVENT); - CASE(IMSG_FILTER_QUERY); - CASE(IMSG_FILTER_PIPE); - CASE(IMSG_FILTER_RESPONSE); - default: - return "IMSG_FILTER_???"; - } -} - -static const char * -query_to_str(int query) -{ - switch (query) { - CASE(QUERY_CONNECT); - CASE(QUERY_HELO); - CASE(QUERY_MAIL); - CASE(QUERY_RCPT); - CASE(QUERY_DATA); - CASE(QUERY_EOM); - CASE(QUERY_DATALINE); - default: - return "QUERY_???"; - } -} - -static const char * -event_to_str(int event) -{ - switch (event) { - CASE(EVENT_CONNECT); - CASE(EVENT_RESET); - CASE(EVENT_DISCONNECT); - CASE(EVENT_TX_BEGIN); - CASE(EVENT_TX_COMMIT); - CASE(EVENT_TX_ROLLBACK); - default: - return "EVENT_???"; - } -} - -static const char * -status_to_str(int status) -{ - switch (status) { - CASE(FILTER_OK); - CASE(FILTER_FAIL); - CASE(FILTER_CLOSE); - default: - return "FILTER_???"; - } -} diff --git a/smtpd/forward.5 b/smtpd/forward.5 deleted file mode 100644 index 5a68f229..00000000 --- a/smtpd/forward.5 +++ /dev/null @@ -1,83 +0,0 @@ -.\" $OpenBSD: forward.5,v 1.9 2015/03/13 22:41:54 eric Exp $ -.\" -.\" Copyright (c) 2012 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: March 13 2015 $ -.Dt FORWARD 5 -.Os -.Sh NAME -.Nm forward -.Nd email forwarding information file -.Sh DESCRIPTION -Users may put a -.Nm .forward -file in their home directory. -If this file exists, -.Xr smtpd 8 -forwards email to the destinations specified therein. -.Pp -A -.Nm .forward -file contains a list of expansion values, as described in -.Xr aliases 5 . -Each expansion value should be on a line by itself. -However, the -.Nm .forward -mechanism differs from the aliases mechanism in that it disallows -file inclusion -.Pq :include: -and it performs expansion under the user ID of the -.Nm .forward -file owner. -.Pp -Permissions on the -.Nm .forward -file are very strict and expansion is rejected if the file is -group or world-writable; -if the home directory is group writeable; -or if the file is not owned by the user. -.Pp -Users should avoid editing directly the -.Nm .forward -file to prevent delivery failures from occurring if a message -arrives while the file is not fully written. -The best option is to use a temporary file and use the -.Xr mv 1 -command to atomically overwrite the former -.Nm .forward . -Alternatively, setting the -.Xr sticky 8 -bit on the home directory will cause the -.Nm .forward -lookup to return a temporary failure, causing mails to be deferred. -.Sh FILES -.Bl -tag -width "~/.forwardXXX" -compact -.It Pa ~/.forward -Email forwarding information. -.El -.Sh EXAMPLES -The following file forwards mail to -.Dq user@example.com , -and pipes the same mail to -.Dq examplemda . -.Bd -literal -offset indent -# empty lines are ignored - -user@example.com # anything after # is ignored -"|/path/to/examplemda" -.Ed -.Sh SEE ALSO -.Xr aliases 5 , -.Xr smtpd 8 diff --git a/smtpd/forward.c b/smtpd/forward.c deleted file mode 100644 index 7494c6ce..00000000 --- a/smtpd/forward.c +++ /dev/null @@ -1,104 +0,0 @@ -/* $OpenBSD: forward.c,v 1.39 2015/12/28 22:08:30 jung Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UTIL_H -#include -#endif -#ifdef HAVE_LIBUTIL_H -#include -#endif -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define MAX_FORWARD_SIZE (4 * 1024) -#define MAX_EXPAND_NODES (100) - -int -forwards_get(int fd, struct expand *expand) -{ - FILE *fp = NULL; - char *line = NULL; - size_t len; - size_t lineno; - size_t save; - int ret; - struct stat sb; - - ret = -1; - if (fstat(fd, &sb) == -1) - goto end; - - /* if it's empty just pretend that no expansion took place */ - if (sb.st_size == 0) { - log_info("info: forward file is empty"); - ret = 0; - goto end; - } - - /* over MAX_FORWARD_SIZE, temporarily fail */ - if (sb.st_size >= MAX_FORWARD_SIZE) { - log_info("info: forward file exceeds max size"); - goto end; - } - - if ((fp = fdopen(fd, "r")) == NULL) { - log_warn("warn: fdopen failure in forwards_get()"); - goto end; - } - - lineno = 0; - save = expand->nb_nodes; - while ((line = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { - if (!expand_line(expand, line, 0)) { - log_info("info: parse error in forward file"); - goto end; - } - if (expand->nb_nodes > MAX_EXPAND_NODES) { - log_info("info: forward file expanded too many nodes"); - goto end; - } - free(line); - } - - ret = expand->nb_nodes > save ? 1 : 0; - -end: - free(line); - if (fp) - fclose(fp); - else - close(fd); - return ret; -} diff --git a/smtpd/iobuf.c b/smtpd/iobuf.c deleted file mode 100644 index dec10660..00000000 --- a/smtpd/iobuf.c +++ /dev/null @@ -1,462 +0,0 @@ -/* $OpenBSD: iobuf.c,v 1.13 2020/04/24 11:34:07 eric Exp $ */ -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifdef IO_TLS -#include -#include -#endif - -#include "iobuf.h" - -#define IOBUF_MAX 65536 -#define IOBUFQ_MIN 4096 - -struct ioqbuf *ioqbuf_alloc(struct iobuf *, size_t); -void iobuf_drain(struct iobuf *, size_t); - -int -iobuf_init(struct iobuf *io, size_t size, size_t max) -{ - memset(io, 0, sizeof *io); - - if (max == 0) - max = IOBUF_MAX; - - if (size == 0) - size = max; - - if (size > max) - return (-1); - - if ((io->buf = calloc(size, 1)) == NULL) - return (-1); - - io->size = size; - io->max = max; - - return (0); -} - -void -iobuf_clear(struct iobuf *io) -{ - struct ioqbuf *q; - - free(io->buf); - - while ((q = io->outq)) { - io->outq = q->next; - free(q); - } - - memset(io, 0, sizeof (*io)); -} - -void -iobuf_drain(struct iobuf *io, size_t n) -{ - struct ioqbuf *q; - size_t left = n; - - while ((q = io->outq) && left) { - if ((q->wpos - q->rpos) > left) { - q->rpos += left; - left = 0; - } else { - left -= q->wpos - q->rpos; - io->outq = q->next; - free(q); - } - } - - io->queued -= (n - left); - if (io->outq == NULL) - io->outqlast = NULL; -} - -int -iobuf_extend(struct iobuf *io, size_t n) -{ - char *t; - - if (n > io->max) - return (-1); - - if (io->max - io->size < n) - return (-1); - - t = recallocarray(io->buf, io->size, io->size + n, 1); - if (t == NULL) - return (-1); - - io->size += n; - io->buf = t; - - return (0); -} - -size_t -iobuf_left(struct iobuf *io) -{ - return io->size - io->wpos; -} - -size_t -iobuf_space(struct iobuf *io) -{ - return io->size - (io->wpos - io->rpos); -} - -size_t -iobuf_len(struct iobuf *io) -{ - return io->wpos - io->rpos; -} - -char * -iobuf_data(struct iobuf *io) -{ - return io->buf + io->rpos; -} - -void -iobuf_drop(struct iobuf *io, size_t n) -{ - if (n >= iobuf_len(io)) { - io->rpos = io->wpos = 0; - return; - } - - io->rpos += n; -} - -char * -iobuf_getline(struct iobuf *iobuf, size_t *rlen) -{ - char *buf; - size_t len, i; - - buf = iobuf_data(iobuf); - len = iobuf_len(iobuf); - - for (i = 0; i + 1 <= len; i++) - if (buf[i] == '\n') { - /* Note: the returned address points into the iobuf - * buffer. We NUL-end it for convenience, and discard - * the data from the iobuf, so that the caller doesn't - * have to do it. The data remains "valid" as long - * as the iobuf does not overwrite it, that is until - * the next call to iobuf_normalize() or iobuf_extend(). - */ - iobuf_drop(iobuf, i + 1); - buf[i] = '\0'; - if (rlen) - *rlen = i; - return (buf); - } - - return (NULL); -} - -void -iobuf_normalize(struct iobuf *io) -{ - if (io->rpos == 0) - return; - - if (io->rpos == io->wpos) { - io->rpos = io->wpos = 0; - return; - } - - memmove(io->buf, io->buf + io->rpos, io->wpos - io->rpos); - io->wpos -= io->rpos; - io->rpos = 0; -} - -ssize_t -iobuf_read(struct iobuf *io, int fd) -{ - ssize_t n; - - n = read(fd, io->buf + io->wpos, iobuf_left(io)); - if (n == -1) { - /* XXX is this really what we want? */ - if (errno == EAGAIN || errno == EINTR) - return (IOBUF_WANT_READ); - return (IOBUF_ERROR); - } - if (n == 0) - return (IOBUF_CLOSED); - - io->wpos += n; - - return (n); -} - -struct ioqbuf * -ioqbuf_alloc(struct iobuf *io, size_t len) -{ - struct ioqbuf *q; - - if (len < IOBUFQ_MIN) - len = IOBUFQ_MIN; - - if ((q = malloc(sizeof(*q) + len)) == NULL) - return (NULL); - - q->rpos = 0; - q->wpos = 0; - q->size = len; - q->next = NULL; - q->buf = (char *)(q) + sizeof(*q); - - if (io->outqlast == NULL) - io->outq = q; - else - io->outqlast->next = q; - io->outqlast = q; - - return (q); -} - -size_t -iobuf_queued(struct iobuf *io) -{ - return io->queued; -} - -void * -iobuf_reserve(struct iobuf *io, size_t len) -{ - struct ioqbuf *q; - void *r; - - if (len == 0) - return (NULL); - - if (((q = io->outqlast) == NULL) || q->size - q->wpos <= len) { - if ((q = ioqbuf_alloc(io, len)) == NULL) - return (NULL); - } - - r = q->buf + q->wpos; - q->wpos += len; - io->queued += len; - - return (r); -} - -int -iobuf_queue(struct iobuf *io, const void *data, size_t len) -{ - void *buf; - - if (len == 0) - return (0); - - if ((buf = iobuf_reserve(io, len)) == NULL) - return (-1); - - memmove(buf, data, len); - - return (len); -} - -int -iobuf_queuev(struct iobuf *io, const struct iovec *iov, int iovcnt) -{ - int i; - size_t len = 0; - char *buf; - - for (i = 0; i < iovcnt; i++) - len += iov[i].iov_len; - - if ((buf = iobuf_reserve(io, len)) == NULL) - return (-1); - - for (i = 0; i < iovcnt; i++) { - if (iov[i].iov_len == 0) - continue; - memmove(buf, iov[i].iov_base, iov[i].iov_len); - buf += iov[i].iov_len; - } - - return (0); - -} - -int -iobuf_fqueue(struct iobuf *io, const char *fmt, ...) -{ - va_list ap; - int len; - - va_start(ap, fmt); - len = iobuf_vfqueue(io, fmt, ap); - va_end(ap); - - return (len); -} - -int -iobuf_vfqueue(struct iobuf *io, const char *fmt, va_list ap) -{ - char *buf; - int len; - - len = vasprintf(&buf, fmt, ap); - - if (len == -1) - return (-1); - - len = iobuf_queue(io, buf, len); - free(buf); - - return (len); -} - -ssize_t -iobuf_write(struct iobuf *io, int fd) -{ - struct iovec iov[IOV_MAX]; - struct ioqbuf *q; - int i; - ssize_t n; - - i = 0; - for (q = io->outq; q ; q = q->next) { - if (i >= IOV_MAX) - break; - iov[i].iov_base = q->buf + q->rpos; - iov[i].iov_len = q->wpos - q->rpos; - i++; - } - - n = writev(fd, iov, i); - if (n == -1) { - if (errno == EAGAIN || errno == EINTR) - return (IOBUF_WANT_WRITE); - if (errno == EPIPE) - return (IOBUF_CLOSED); - return (IOBUF_ERROR); - } - - iobuf_drain(io, n); - - return (n); -} - -int -iobuf_flush(struct iobuf *io, int fd) -{ - ssize_t s; - - while (io->queued) - if ((s = iobuf_write(io, fd)) < 0) - return (s); - - return (0); -} - -#ifdef IO_TLS - -int -iobuf_flush_tls(struct iobuf *io, void *tls) -{ - ssize_t s; - - while (io->queued) - if ((s = iobuf_write_tls(io, tls)) < 0) - return (s); - - return (0); -} - -ssize_t -iobuf_write_tls(struct iobuf *io, void *tls) -{ - struct ioqbuf *q; - int r; - ssize_t n; - - q = io->outq; - n = SSL_write(tls, q->buf + q->rpos, q->wpos - q->rpos); - if (n <= 0) { - switch ((r = SSL_get_error(tls, n))) { - case SSL_ERROR_WANT_READ: - return (IOBUF_WANT_READ); - case SSL_ERROR_WANT_WRITE: - return (IOBUF_WANT_WRITE); - case SSL_ERROR_ZERO_RETURN: /* connection closed */ - return (IOBUF_CLOSED); - case SSL_ERROR_SYSCALL: - if (ERR_peek_last_error()) - return (IOBUF_TLSERROR); - return (IOBUF_ERROR); - default: - return (IOBUF_TLSERROR); - } - } - iobuf_drain(io, n); - - return (n); -} - -ssize_t -iobuf_read_tls(struct iobuf *io, void *tls) -{ - ssize_t n; - int r; - - n = SSL_read(tls, io->buf + io->wpos, iobuf_left(io)); - if (n < 0) { - switch ((r = SSL_get_error(tls, n))) { - case SSL_ERROR_WANT_READ: - return (IOBUF_WANT_READ); - case SSL_ERROR_WANT_WRITE: - return (IOBUF_WANT_WRITE); - case SSL_ERROR_SYSCALL: - if (ERR_peek_last_error()) - return (IOBUF_TLSERROR); - return (IOBUF_ERROR); - default: - return (IOBUF_TLSERROR); - } - } else if (n == 0) - return (IOBUF_CLOSED); - - io->wpos += n; - - return (n); -} - -#endif /* IO_TLS */ diff --git a/smtpd/iobuf.h b/smtpd/iobuf.h deleted file mode 100644 index c454d0a1..00000000 --- a/smtpd/iobuf.h +++ /dev/null @@ -1,67 +0,0 @@ -/* $OpenBSD: iobuf.h,v 1.5 2019/06/12 17:42:53 eric Exp $ */ -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -struct ioqbuf { - struct ioqbuf *next; - char *buf; - size_t size; - size_t wpos; - size_t rpos; -}; - -struct iobuf { - char *buf; - size_t max; - size_t size; - size_t wpos; - size_t rpos; - - size_t queued; - struct ioqbuf *outq; - struct ioqbuf *outqlast; -}; - -#define IOBUF_WANT_READ -1 -#define IOBUF_WANT_WRITE -2 -#define IOBUF_CLOSED -3 -#define IOBUF_ERROR -4 -#define IOBUF_TLSERROR -5 - -int iobuf_init(struct iobuf *, size_t, size_t); -void iobuf_clear(struct iobuf *); - -int iobuf_extend(struct iobuf *, size_t); -void iobuf_normalize(struct iobuf *); -void iobuf_drop(struct iobuf *, size_t); -size_t iobuf_space(struct iobuf *); -size_t iobuf_len(struct iobuf *); -size_t iobuf_left(struct iobuf *); -char *iobuf_data(struct iobuf *); -char *iobuf_getline(struct iobuf *, size_t *); -ssize_t iobuf_read(struct iobuf *, int); -ssize_t iobuf_read_tls(struct iobuf *, void *); - -size_t iobuf_queued(struct iobuf *); -void* iobuf_reserve(struct iobuf *, size_t); -int iobuf_queue(struct iobuf *, const void*, size_t); -int iobuf_queuev(struct iobuf *, const struct iovec *, int); -int iobuf_fqueue(struct iobuf *, const char *, ...); -int iobuf_vfqueue(struct iobuf *, const char *, va_list); -int iobuf_flush(struct iobuf *, int); -int iobuf_flush_tls(struct iobuf *, void *); -ssize_t iobuf_write(struct iobuf *, int); -ssize_t iobuf_write_tls(struct iobuf *, void *); diff --git a/smtpd/ioev.c b/smtpd/ioev.c deleted file mode 100644 index e0a8a096..00000000 --- a/smtpd/ioev.c +++ /dev/null @@ -1,1064 +0,0 @@ -/* $OpenBSD: ioev.c,v 1.42 2019/06/12 17:42:53 eric Exp $ */ -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ioev.h" -#include "iobuf.h" - -#ifdef IO_TLS -#include -#include -#endif - -enum { - IO_STATE_NONE, - IO_STATE_CONNECT, - IO_STATE_CONNECT_TLS, - IO_STATE_ACCEPT_TLS, - IO_STATE_UP, - - IO_STATE_MAX, -}; - -#define IO_PAUSE_IN IO_IN -#define IO_PAUSE_OUT IO_OUT -#define IO_READ 0x04 -#define IO_WRITE 0x08 -#define IO_RW (IO_READ | IO_WRITE) -#define IO_RESET 0x10 /* internal */ -#define IO_HELD 0x20 /* internal */ - -struct io { - int sock; - void *arg; - void (*cb)(struct io*, int, void *); - struct iobuf iobuf; - size_t lowat; - int timeout; - int flags; - int state; - struct event ev; - void *tls; - const char *error; /* only valid immediately on callback */ -}; - -const char* io_strflags(int); -const char* io_evstr(short); - -void _io_init(void); -void io_hold(struct io *); -void io_release(struct io *); -void io_callback(struct io*, int); -void io_dispatch(int, short, void *); -void io_dispatch_connect(int, short, void *); -size_t io_pending(struct io *); -size_t io_queued(struct io*); -void io_reset(struct io *, short, void (*)(int, short, void*)); -void io_frame_enter(const char *, struct io *, int); -void io_frame_leave(struct io *); - -#ifdef IO_TLS -void ssl_error(const char *); /* XXX external */ - -static const char* io_tls_error(void); -void io_dispatch_accept_tls(int, short, void *); -void io_dispatch_connect_tls(int, short, void *); -void io_dispatch_read_tls(int, short, void *); -void io_dispatch_write_tls(int, short, void *); -void io_reload_tls(struct io *io); -#endif - -static struct io *current = NULL; -static uint64_t frame = 0; -static int _io_debug = 0; - -#define io_debug(args...) do { if (_io_debug) printf(args); } while(0) - - -const char* -io_strio(struct io *io) -{ - static char buf[128]; - char ssl[128]; - - ssl[0] = '\0'; -#ifdef IO_TLS - if (io->tls) { - (void)snprintf(ssl, sizeof ssl, " tls=%s:%s:%d", - SSL_get_version(io->tls), - SSL_get_cipher_name(io->tls), - SSL_get_cipher_bits(io->tls, NULL)); - } -#endif - - (void)snprintf(buf, sizeof buf, - "", - io, io->sock, io->timeout, io_strflags(io->flags), ssl, - io_pending(io), io_queued(io)); - - return (buf); -} - -#define CASE(x) case x : return #x - -const char* -io_strevent(int evt) -{ - static char buf[32]; - - switch (evt) { - CASE(IO_CONNECTED); - CASE(IO_TLSREADY); - CASE(IO_DATAIN); - CASE(IO_LOWAT); - CASE(IO_DISCONNECTED); - CASE(IO_TIMEOUT); - CASE(IO_ERROR); - default: - (void)snprintf(buf, sizeof(buf), "IO_? %d", evt); - return buf; - } -} - -void -io_set_nonblocking(int fd) -{ - int flags; - - if ((flags = fcntl(fd, F_GETFL)) == -1) - err(1, "io_set_blocking:fcntl(F_GETFL)"); - - flags |= O_NONBLOCK; - - if (fcntl(fd, F_SETFL, flags) == -1) - err(1, "io_set_blocking:fcntl(F_SETFL)"); -} - -void -io_set_nolinger(int fd) -{ - struct linger l; - - memset(&l, 0, sizeof(l)); - if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) - err(1, "io_set_linger:setsockopt()"); -} - -/* - * Event framing must not rely on an io pointer to refer to the "same" io - * throughout the frame, because this is not always the case: - * - * 1) enter(addr0) -> free(addr0) -> leave(addr0) = SEGV - * 2) enter(addr0) -> free(addr0) -> malloc == addr0 -> leave(addr0) = BAD! - * - * In both case, the problem is that the io is freed in the callback, so - * the pointer becomes invalid. If that happens, the user is required to - * call io_clear, so we can adapt the frame state there. - */ -void -io_frame_enter(const char *where, struct io *io, int ev) -{ - io_debug("\n=== %" PRIu64 " ===\n" - "io_frame_enter(%s, %s, %s)\n", - frame, where, io_evstr(ev), io_strio(io)); - - if (current) - errx(1, "io_frame_enter: interleaved frames"); - - current = io; - - io_hold(io); -} - -void -io_frame_leave(struct io *io) -{ - io_debug("io_frame_leave(%" PRIu64 ")\n", frame); - - if (current && current != io) - errx(1, "io_frame_leave: io mismatch"); - - /* io has been cleared */ - if (current == NULL) - goto done; - - /* TODO: There is a possible optimization there: - * In a typical half-duplex request/response scenario, - * the io is waiting to read a request, and when done, it queues - * the response in the output buffer and goes to write mode. - * There, the write event is set and will be triggered in the next - * event frame. In most case, the write call could be done - * immediately as part of the last read frame, thus avoiding to go - * through the event loop machinery. So, as an optimisation, we - * could detect that case here and force an event dispatching. - */ - - /* Reload the io if it has not been reset already. */ - io_release(io); - current = NULL; - done: - io_debug("=== /%" PRIu64 "\n", frame); - - frame += 1; -} - -void -_io_init() -{ - static int init = 0; - - if (init) - return; - - init = 1; - _io_debug = getenv("IO_DEBUG") != NULL; -} - -struct io * -io_new(void) -{ - struct io *io; - - _io_init(); - - if ((io = calloc(1, sizeof(*io))) == NULL) - return NULL; - - io->sock = -1; - io->timeout = -1; - - if (iobuf_init(&io->iobuf, 0, 0) == -1) { - free(io); - return NULL; - } - - return io; -} - -void -io_free(struct io *io) -{ - io_debug("io_clear(%p)\n", io); - - /* the current io is virtually dead */ - if (io == current) - current = NULL; - -#ifdef IO_TLS - SSL_free(io->tls); - io->tls = NULL; -#endif - - if (event_initialized(&io->ev)) - event_del(&io->ev); - if (io->sock != -1) { - close(io->sock); - io->sock = -1; - } - - iobuf_clear(&io->iobuf); - free(io); -} - -void -io_hold(struct io *io) -{ - io_debug("io_enter(%p)\n", io); - - if (io->flags & IO_HELD) - errx(1, "io_hold: io is already held"); - - io->flags &= ~IO_RESET; - io->flags |= IO_HELD; -} - -void -io_release(struct io *io) -{ - if (!(io->flags & IO_HELD)) - errx(1, "io_release: io is not held"); - - io->flags &= ~IO_HELD; - if (!(io->flags & IO_RESET)) - io_reload(io); -} - -void -io_set_fd(struct io *io, int fd) -{ - io->sock = fd; - if (fd != -1) - io_reload(io); -} - -void -io_set_callback(struct io *io, void(*cb)(struct io *, int, void *), void *arg) -{ - io->cb = cb; - io->arg = arg; -} - -void -io_set_timeout(struct io *io, int msec) -{ - io_debug("io_set_timeout(%p, %d)\n", io, msec); - - io->timeout = msec; -} - -void -io_set_lowat(struct io *io, size_t lowat) -{ - io_debug("io_set_lowat(%p, %zu)\n", io, lowat); - - io->lowat = lowat; -} - -void -io_pause(struct io *io, int dir) -{ - io_debug("io_pause(%p, %x)\n", io, dir); - - io->flags |= dir & (IO_PAUSE_IN | IO_PAUSE_OUT); - io_reload(io); -} - -void -io_resume(struct io *io, int dir) -{ - io_debug("io_resume(%p, %x)\n", io, dir); - - io->flags &= ~(dir & (IO_PAUSE_IN | IO_PAUSE_OUT)); - io_reload(io); -} - -void -io_set_read(struct io *io) -{ - int mode; - - io_debug("io_set_read(%p)\n", io); - - mode = io->flags & IO_RW; - if (!(mode == 0 || mode == IO_WRITE)) - errx(1, "io_set_read(): full-duplex or reading"); - - io->flags &= ~IO_RW; - io->flags |= IO_READ; - io_reload(io); -} - -void -io_set_write(struct io *io) -{ - int mode; - - io_debug("io_set_write(%p)\n", io); - - mode = io->flags & IO_RW; - if (!(mode == 0 || mode == IO_READ)) - errx(1, "io_set_write(): full-duplex or writing"); - - io->flags &= ~IO_RW; - io->flags |= IO_WRITE; - io_reload(io); -} - -const char * -io_error(struct io *io) -{ - return io->error; -} - -void * -io_tls(struct io *io) -{ - return io->tls; -} - -int -io_fileno(struct io *io) -{ - return io->sock; -} - -int -io_paused(struct io *io, int what) -{ - return (io->flags & (IO_PAUSE_IN | IO_PAUSE_OUT)) == what; -} - -/* - * Buffered output functions - */ - -int -io_write(struct io *io, const void *buf, size_t len) -{ - int r; - - r = iobuf_queue(&io->iobuf, buf, len); - - io_reload(io); - - return r; -} - -int -io_writev(struct io *io, const struct iovec *iov, int iovcount) -{ - int r; - - r = iobuf_queuev(&io->iobuf, iov, iovcount); - - io_reload(io); - - return r; -} - -int -io_print(struct io *io, const char *s) -{ - return io_write(io, s, strlen(s)); -} - -int -io_printf(struct io *io, const char *fmt, ...) -{ - va_list ap; - int r; - - va_start(ap, fmt); - r = io_vprintf(io, fmt, ap); - va_end(ap); - - return r; -} - -int -io_vprintf(struct io *io, const char *fmt, va_list ap) -{ - - char *buf; - int len; - - len = vasprintf(&buf, fmt, ap); - if (len == -1) - return -1; - len = io_write(io, buf, len); - free(buf); - - return len; -} - -size_t -io_queued(struct io *io) -{ - return iobuf_queued(&io->iobuf); -} - -/* - * Buffered input functions - */ - -void * -io_data(struct io *io) -{ - return iobuf_data(&io->iobuf); -} - -size_t -io_datalen(struct io *io) -{ - return iobuf_len(&io->iobuf); -} - -char * -io_getline(struct io *io, size_t *sz) -{ - return iobuf_getline(&io->iobuf, sz); -} - -void -io_drop(struct io *io, size_t sz) -{ - return iobuf_drop(&io->iobuf, sz); -} - - -#define IO_READING(io) (((io)->flags & IO_RW) != IO_WRITE) -#define IO_WRITING(io) (((io)->flags & IO_RW) != IO_READ) - -/* - * Setup the necessary events as required by the current io state, - * honouring duplex mode and i/o pauses. - */ -void -io_reload(struct io *io) -{ - short events; - - /* io will be reloaded at release time */ - if (io->flags & IO_HELD) - return; - - iobuf_normalize(&io->iobuf); - -#ifdef IO_TLS - if (io->tls) { - io_reload_tls(io); - return; - } -#endif - - io_debug("io_reload(%p)\n", io); - - events = 0; - if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) - events = EV_READ; - if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && io_queued(io)) - events |= EV_WRITE; - - io_reset(io, events, io_dispatch); -} - -/* Set the requested event. */ -void -io_reset(struct io *io, short events, void (*dispatch)(int, short, void*)) -{ - struct timeval tv, *ptv; - - io_debug("io_reset(%p, %s, %p) -> %s\n", - io, io_evstr(events), dispatch, io_strio(io)); - - /* - * Indicate that the event has already been reset so that reload - * is not called on frame_leave. - */ - io->flags |= IO_RESET; - - if (event_initialized(&io->ev)) - event_del(&io->ev); - - /* - * The io is paused by the user, so we don't want the timeout to be - * effective. - */ - if (events == 0) - return; - - event_set(&io->ev, io->sock, events, dispatch, io); - if (io->timeout >= 0) { - tv.tv_sec = io->timeout / 1000; - tv.tv_usec = (io->timeout % 1000) * 1000; - ptv = &tv; - } else - ptv = NULL; - - event_add(&io->ev, ptv); -} - -size_t -io_pending(struct io *io) -{ - return iobuf_len(&io->iobuf); -} - -const char* -io_strflags(int flags) -{ - static char buf[64]; - - buf[0] = '\0'; - - switch (flags & IO_RW) { - case 0: - (void)strlcat(buf, "rw", sizeof buf); - break; - case IO_READ: - (void)strlcat(buf, "R", sizeof buf); - break; - case IO_WRITE: - (void)strlcat(buf, "W", sizeof buf); - break; - case IO_RW: - (void)strlcat(buf, "RW", sizeof buf); - break; - } - - if (flags & IO_PAUSE_IN) - (void)strlcat(buf, ",F_PI", sizeof buf); - if (flags & IO_PAUSE_OUT) - (void)strlcat(buf, ",F_PO", sizeof buf); - - return buf; -} - -const char* -io_evstr(short ev) -{ - static char buf[64]; - char buf2[16]; - int n; - - n = 0; - buf[0] = '\0'; - - if (ev == 0) { - (void)strlcat(buf, "", sizeof(buf)); - return buf; - } - - if (ev & EV_TIMEOUT) { - (void)strlcat(buf, "EV_TIMEOUT", sizeof(buf)); - ev &= ~EV_TIMEOUT; - n++; - } - - if (ev & EV_READ) { - if (n) - (void)strlcat(buf, "|", sizeof(buf)); - (void)strlcat(buf, "EV_READ", sizeof(buf)); - ev &= ~EV_READ; - n++; - } - - if (ev & EV_WRITE) { - if (n) - (void)strlcat(buf, "|", sizeof(buf)); - (void)strlcat(buf, "EV_WRITE", sizeof(buf)); - ev &= ~EV_WRITE; - n++; - } - - if (ev & EV_SIGNAL) { - if (n) - (void)strlcat(buf, "|", sizeof(buf)); - (void)strlcat(buf, "EV_SIGNAL", sizeof(buf)); - ev &= ~EV_SIGNAL; - n++; - } - - if (ev) { - if (n) - (void)strlcat(buf, "|", sizeof(buf)); - (void)strlcat(buf, "EV_?=0x", sizeof(buf)); - (void)snprintf(buf2, sizeof(buf2), "%hx", ev); - (void)strlcat(buf, buf2, sizeof(buf)); - } - - return buf; -} - -void -io_dispatch(int fd, short ev, void *humppa) -{ - struct io *io = humppa; - size_t w; - ssize_t n; - int saved_errno; - - io_frame_enter("io_dispatch", io, ev); - - if (ev == EV_TIMEOUT) { - io_callback(io, IO_TIMEOUT); - goto leave; - } - - if (ev & EV_WRITE && (w = io_queued(io))) { - if ((n = iobuf_write(&io->iobuf, io->sock)) < 0) { - if (n == IOBUF_WANT_WRITE) /* kqueue bug? */ - goto read; - if (n == IOBUF_CLOSED) - io_callback(io, IO_DISCONNECTED); - else { - saved_errno = errno; - io->error = strerror(errno); - errno = saved_errno; - io_callback(io, IO_ERROR); - } - goto leave; - } - if (w > io->lowat && w - n <= io->lowat) - io_callback(io, IO_LOWAT); - } - read: - - if (ev & EV_READ) { - iobuf_normalize(&io->iobuf); - if ((n = iobuf_read(&io->iobuf, io->sock)) < 0) { - if (n == IOBUF_CLOSED) - io_callback(io, IO_DISCONNECTED); - else { - saved_errno = errno; - io->error = strerror(errno); - errno = saved_errno; - io_callback(io, IO_ERROR); - } - goto leave; - } - if (n) - io_callback(io, IO_DATAIN); - } - -leave: - io_frame_leave(io); -} - -void -io_callback(struct io *io, int evt) -{ - io->cb(io, evt, io->arg); -} - -int -io_connect(struct io *io, const struct sockaddr *sa, const struct sockaddr *bsa) -{ - int sock, errno_save; - - if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) - goto fail; - - io_set_nonblocking(sock); - io_set_nolinger(sock); - - if (bsa && bind(sock, bsa, SA_LEN(bsa)) == -1) - goto fail; - - if (connect(sock, sa, SA_LEN(sa)) == -1) - if (errno != EINPROGRESS) - goto fail; - - io->sock = sock; - io_reset(io, EV_WRITE, io_dispatch_connect); - - return (sock); - - fail: - if (sock != -1) { - errno_save = errno; - close(sock); - errno = errno_save; - io->error = strerror(errno); - } - return (-1); -} - -void -io_dispatch_connect(int fd, short ev, void *humppa) -{ - struct io *io = humppa; - int r, e; - socklen_t sl; - - io_frame_enter("io_dispatch_connect", io, ev); - - if (ev == EV_TIMEOUT) { - close(fd); - io->sock = -1; - io_callback(io, IO_TIMEOUT); - } else { - sl = sizeof(e); - r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &sl); - if (r == -1) { - warn("io_dispatch_connect: getsockopt"); - e = errno; - } - if (e) { - close(fd); - io->sock = -1; - io->error = strerror(e); - io_callback(io, e == ETIMEDOUT ? IO_TIMEOUT : IO_ERROR); - } - else { - io->state = IO_STATE_UP; - io_callback(io, IO_CONNECTED); - } - } - - io_frame_leave(io); -} - -#ifdef IO_TLS - -static const char* -io_tls_error(void) -{ - static char buf[128]; - unsigned long e; - - e = ERR_peek_last_error(); - if (e) { - ERR_error_string(e, buf); - return (buf); - } - - return ("No TLS error"); -} - -int -io_start_tls(struct io *io, void *tls) -{ - int mode; - - mode = io->flags & IO_RW; - if (mode == 0 || mode == IO_RW) - errx(1, "io_start_tls(): full-duplex or unset"); - - if (io->tls) - errx(1, "io_start_tls(): TLS already started"); - io->tls = tls; - - if (SSL_set_fd(io->tls, io->sock) == 0) { - ssl_error("io_start_tls:SSL_set_fd"); - return (-1); - } - - if (mode == IO_WRITE) { - io->state = IO_STATE_CONNECT_TLS; - SSL_set_connect_state(io->tls); - io_reset(io, EV_WRITE, io_dispatch_connect_tls); - } else { - io->state = IO_STATE_ACCEPT_TLS; - SSL_set_accept_state(io->tls); - io_reset(io, EV_READ, io_dispatch_accept_tls); - } - - return (0); -} - -void -io_dispatch_accept_tls(int fd, short event, void *humppa) -{ - struct io *io = humppa; - int e, ret; - - io_frame_enter("io_dispatch_accept_tls", io, event); - - if (event == EV_TIMEOUT) { - io_callback(io, IO_TIMEOUT); - goto leave; - } - - if ((ret = SSL_accept(io->tls)) > 0) { - io->state = IO_STATE_UP; - io_callback(io, IO_TLSREADY); - goto leave; - } - - switch ((e = SSL_get_error(io->tls, ret))) { - case SSL_ERROR_WANT_READ: - io_reset(io, EV_READ, io_dispatch_accept_tls); - break; - case SSL_ERROR_WANT_WRITE: - io_reset(io, EV_WRITE, io_dispatch_accept_tls); - break; - default: - io->error = io_tls_error(); - ssl_error("io_dispatch_accept_tls:SSL_accept"); - io_callback(io, IO_ERROR); - break; - } - - leave: - io_frame_leave(io); -} - -void -io_dispatch_connect_tls(int fd, short event, void *humppa) -{ - struct io *io = humppa; - int e, ret; - - io_frame_enter("io_dispatch_connect_tls", io, event); - - if (event == EV_TIMEOUT) { - io_callback(io, IO_TIMEOUT); - goto leave; - } - - if ((ret = SSL_connect(io->tls)) > 0) { - io->state = IO_STATE_UP; - io_callback(io, IO_TLSREADY); - goto leave; - } - - switch ((e = SSL_get_error(io->tls, ret))) { - case SSL_ERROR_WANT_READ: - io_reset(io, EV_READ, io_dispatch_connect_tls); - break; - case SSL_ERROR_WANT_WRITE: - io_reset(io, EV_WRITE, io_dispatch_connect_tls); - break; - default: - io->error = io_tls_error(); - ssl_error("io_dispatch_connect_ssl:SSL_connect"); - io_callback(io, IO_TLSERROR); - break; - } - - leave: - io_frame_leave(io); -} - -void -io_dispatch_read_tls(int fd, short event, void *humppa) -{ - struct io *io = humppa; - int n, saved_errno; - - io_frame_enter("io_dispatch_read_tls", io, event); - - if (event == EV_TIMEOUT) { - io_callback(io, IO_TIMEOUT); - goto leave; - } - -again: - iobuf_normalize(&io->iobuf); - switch ((n = iobuf_read_tls(&io->iobuf, (SSL*)io->tls))) { - case IOBUF_WANT_READ: - io_reset(io, EV_READ, io_dispatch_read_tls); - break; - case IOBUF_WANT_WRITE: - io_reset(io, EV_WRITE, io_dispatch_read_tls); - break; - case IOBUF_CLOSED: - io_callback(io, IO_DISCONNECTED); - break; - case IOBUF_ERROR: - saved_errno = errno; - io->error = strerror(errno); - errno = saved_errno; - io_callback(io, IO_ERROR); - break; - case IOBUF_TLSERROR: - io->error = io_tls_error(); - ssl_error("io_dispatch_read_tls:SSL_read"); - io_callback(io, IO_ERROR); - break; - default: - io_debug("io_dispatch_read_tls(...) -> r=%d\n", n); - io_callback(io, IO_DATAIN); - if (current == io && IO_READING(io) && SSL_pending(io->tls)) - goto again; - } - - leave: - io_frame_leave(io); -} - -void -io_dispatch_write_tls(int fd, short event, void *humppa) -{ - struct io *io = humppa; - int n, saved_errno; - size_t w2, w; - - io_frame_enter("io_dispatch_write_tls", io, event); - - if (event == EV_TIMEOUT) { - io_callback(io, IO_TIMEOUT); - goto leave; - } - - w = io_queued(io); - switch ((n = iobuf_write_tls(&io->iobuf, (SSL*)io->tls))) { - case IOBUF_WANT_READ: - io_reset(io, EV_READ, io_dispatch_write_tls); - break; - case IOBUF_WANT_WRITE: - io_reset(io, EV_WRITE, io_dispatch_write_tls); - break; - case IOBUF_CLOSED: - io_callback(io, IO_DISCONNECTED); - break; - case IOBUF_ERROR: - saved_errno = errno; - io->error = strerror(errno); - errno = saved_errno; - io_callback(io, IO_ERROR); - break; - case IOBUF_TLSERROR: - io->error = io_tls_error(); - ssl_error("io_dispatch_write_tls:SSL_write"); - io_callback(io, IO_ERROR); - break; - default: - io_debug("io_dispatch_write_tls(...) -> w=%d\n", n); - w2 = io_queued(io); - if (w > io->lowat && w2 <= io->lowat) - io_callback(io, IO_LOWAT); - break; - } - - leave: - io_frame_leave(io); -} - -void -io_reload_tls(struct io *io) -{ - short ev = 0; - void (*dispatch)(int, short, void*) = NULL; - - switch (io->state) { - case IO_STATE_CONNECT_TLS: - ev = EV_WRITE; - dispatch = io_dispatch_connect_tls; - break; - case IO_STATE_ACCEPT_TLS: - ev = EV_READ; - dispatch = io_dispatch_accept_tls; - break; - case IO_STATE_UP: - ev = 0; - if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) { - ev = EV_READ; - dispatch = io_dispatch_read_tls; - } - else if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && - io_queued(io)) { - ev = EV_WRITE; - dispatch = io_dispatch_write_tls; - } - if (!ev) - return; /* paused */ - break; - default: - errx(1, "io_reload_tls(): bad state"); - } - - io_reset(io, ev, dispatch); -} - -#endif /* IO_TLS */ diff --git a/smtpd/ioev.h b/smtpd/ioev.h deleted file mode 100644 index f155a7fc..00000000 --- a/smtpd/ioev.h +++ /dev/null @@ -1,70 +0,0 @@ -/* $OpenBSD: ioev.h,v 1.18 2019/09/11 04:19:19 martijn Exp $ */ -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -enum { - IO_CONNECTED = 0, /* connection successful */ - IO_TLSREADY, /* TLS started successfully */ - IO_TLSERROR, /* XXX - needs more work */ - IO_DATAIN, /* new data in input buffer */ - IO_LOWAT, /* output queue running low */ - IO_DISCONNECTED, /* error? */ - IO_TIMEOUT, /* error? */ - IO_ERROR, /* details? */ -}; - -#define IO_IN 0x01 -#define IO_OUT 0x02 - -struct io; - -void io_set_nonblocking(int); -void io_set_nolinger(int); - -struct io *io_new(void); -void io_free(struct io *); -void io_set_read(struct io *); -void io_set_write(struct io *); -void io_set_fd(struct io *, int); -void io_set_callback(struct io *io, void(*)(struct io *, int, void *), void *); -void io_set_timeout(struct io *, int); -void io_set_lowat(struct io *, size_t); -void io_pause(struct io *, int); -void io_resume(struct io *, int); -void io_reload(struct io *); -int io_connect(struct io *, const struct sockaddr *, const struct sockaddr *); -int io_start_tls(struct io *, void *); -const char* io_strio(struct io *); -const char* io_strevent(int); -const char* io_error(struct io *); -void* io_tls(struct io *); -int io_fileno(struct io *); -int io_paused(struct io *, int); - -/* Buffered output functions */ -int io_write(struct io *, const void *, size_t); -int io_writev(struct io *, const struct iovec *, int); -int io_print(struct io *, const char *); -int io_printf(struct io *, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -int io_vprintf(struct io *, const char *, va_list); -size_t io_queued(struct io *); - -/* Buffered input functions */ -void* io_data(struct io *); -size_t io_datalen(struct io *); -char* io_getline(struct io *, size_t *); -void io_drop(struct io *, size_t); diff --git a/smtpd/libressl.c b/smtpd/libressl.c deleted file mode 100644 index 57d74389..00000000 --- a/smtpd/libressl.c +++ /dev/null @@ -1,213 +0,0 @@ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -/* - * SSL operations needed when running in a privilege separated environment. - * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . - */ - -#include "includes.h" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "ssl.h" - -#define SSL_ECDH_CURVE "prime256v1" - -/* - * Read a bio that contains our certificate in "PEM" format, - * possibly followed by a sequence of CA certificates that should be - * sent to the peer in the Certificate message. - */ -static int -ssl_ctx_use_certificate_chain_bio(SSL_CTX *ctx, BIO *in) -{ - int ret = 0; - X509 *x = NULL; - - ERR_clear_error(); /* clear error stack for SSL_CTX_use_certificate() */ - - x = PEM_read_bio_X509_AUX(in, NULL, ctx->default_passwd_callback, - ctx->default_passwd_callback_userdata); - if (x == NULL) { - SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); - goto end; - } - - ret = SSL_CTX_use_certificate(ctx, x); - - if (ERR_peek_error() != 0) - ret = 0; - /* Key/certificate mismatch doesn't imply ret==0 ... */ - if (ret) { - /* - * If we could set up our certificate, now proceed to - * the CA certificates. - */ - X509 *ca; - int r; - unsigned long err; - - if (ctx->extra_certs != NULL) { - sk_X509_pop_free(ctx->extra_certs, X509_free); - ctx->extra_certs = NULL; - } - - while ((ca = PEM_read_bio_X509(in, NULL, - ctx->default_passwd_callback, - ctx->default_passwd_callback_userdata)) != NULL) { - r = SSL_CTX_add_extra_chain_cert(ctx, ca); - if (!r) { - X509_free(ca); - ret = 0; - goto end; - } - /* - * Note that we must not free r if it was successfully - * added to the chain (while we must free the main - * certificate, since its reference count is increased - * by SSL_CTX_use_certificate). - */ - } - - /* When the while loop ends, it's usually just EOF. */ - err = ERR_peek_last_error(); - if (ERR_GET_LIB(err) == ERR_LIB_PEM && - ERR_GET_REASON(err) == PEM_R_NO_START_LINE) - ERR_clear_error(); - else - ret = 0; /* some real error */ - } - -end: - if (x != NULL) - X509_free(x); - return (ret); -} - -int -SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *buf, int len) -{ - BIO *in; - int ret = 0; - - in = BIO_new_mem_buf(buf, len); - if (in == NULL) { - SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB); - goto end; - } - - ret = ssl_ctx_use_certificate_chain_bio(ctx, in); - -end: - BIO_free(in); - return (ret); -} - -#ifndef HAVE_SSL_CTX_SET_ECDH_AUTO -void -SSL_CTX_set_ecdh_auto(SSL_CTX *ctx, int enable) -{ - int nid; - EC_KEY *ecdh; - - if (!enable) - return; - - if ((nid = OBJ_sn2nid(SSL_ECDH_CURVE)) == 0) { - ssl_error("ssl_set_ecdh_auto"); - fatal("ssl_set_ecdh_auto: unknown curve name " - SSL_ECDH_CURVE); - } - - if ((ecdh = EC_KEY_new_by_curve_name(nid)) == NULL) { - ssl_error("ssl_set_ecdh_auto"); - fatal("ssl_set_ecdh_auto: unable to create curve " - SSL_ECDH_CURVE); - } - - SSL_CTX_set_tmp_ecdh(ctx, ecdh); - SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); - EC_KEY_free(ecdh); -} -#endif - -#ifndef HAVE_SSL_CTX_SET_DH_AUTO -void -SSL_CTX_set_dh_auto(SSL_CTX *ctx, int enable) -{ - if (!enable) - return; - - /* stub until OpenSSL catches up with this ... */ - log_warnx("OpenSSL does not support SSL_CTX_set_dh_auto (yet ?)"); - return; -} -#endif diff --git a/smtpd/limit.c b/smtpd/limit.c deleted file mode 100644 index 25e7a026..00000000 --- a/smtpd/limit.c +++ /dev/null @@ -1,124 +0,0 @@ -/* $OpenBSD: limit.c,v 1.5 2016/06/15 19:59:03 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -void -limit_mta_set_defaults(struct mta_limits *limits) -{ - limits->maxconn_per_host = 10; - limits->maxconn_per_route = 5; - limits->maxconn_per_source = 100; - limits->maxconn_per_connector = 20; - limits->maxconn_per_relay = 100; - limits->maxconn_per_domain = 100; - - limits->conndelay_host = 0; - limits->conndelay_route = 5; - limits->conndelay_source = 0; - limits->conndelay_connector = 0; - limits->conndelay_relay = 2; - limits->conndelay_domain = 0; - - limits->discdelay_route = 3; - - limits->max_mail_per_session = 100; - limits->sessdelay_transaction = 0; - limits->sessdelay_keepalive = 10; - - limits->max_failures_per_session = 25; - - limits->family = AF_UNSPEC; - - limits->task_hiwat = 50; - limits->task_lowat = 30; - limits->task_release = 10; -} - -int -limit_mta_set(struct mta_limits *limits, const char *key, int64_t value) -{ - if (!strcmp(key, "max-conn-per-host")) - limits->maxconn_per_host = value; - else if (!strcmp(key, "max-conn-per-route")) - limits->maxconn_per_route = value; - else if (!strcmp(key, "max-conn-per-source")) - limits->maxconn_per_source = value; - else if (!strcmp(key, "max-conn-per-connector")) - limits->maxconn_per_connector = value; - else if (!strcmp(key, "max-conn-per-relay")) - limits->maxconn_per_relay = value; - else if (!strcmp(key, "max-conn-per-domain")) - limits->maxconn_per_domain = value; - - else if (!strcmp(key, "conn-delay-host")) - limits->conndelay_host = value; - else if (!strcmp(key, "conn-delay-route")) - limits->conndelay_route = value; - else if (!strcmp(key, "conn-delay-source")) - limits->conndelay_source = value; - else if (!strcmp(key, "conn-delay-connector")) - limits->conndelay_connector = value; - else if (!strcmp(key, "conn-delay-relay")) - limits->conndelay_relay = value; - else if (!strcmp(key, "conn-delay-domain")) - limits->conndelay_domain = value; - - else if (!strcmp(key, "reconn-delay-route")) - limits->discdelay_route = value; - - else if (!strcmp(key, "session-mail-max")) - limits->max_mail_per_session = value; - else if (!strcmp(key, "session-transaction-delay")) - limits->sessdelay_transaction = value; - else if (!strcmp(key, "session-keepalive")) - limits->sessdelay_keepalive = value; - - else if (!strcmp(key, "max-failures-per-session")) - limits->max_failures_per_session = value; - - else if (!strcmp(key, "task-hiwat")) - limits->task_hiwat = value; - else if (!strcmp(key, "task-lowat")) - limits->task_lowat = value; - else if (!strcmp(key, "task-release")) - limits->task_release = value; - - else - return (0); - - return (1); -} diff --git a/smtpd/lka.c b/smtpd/lka.c deleted file mode 100644 index 6ac21245..00000000 --- a/smtpd/lka.c +++ /dev/null @@ -1,914 +0,0 @@ -/* $OpenBSD: lka.c,v 1.243 2019/12/21 10:23:37 gilles Exp $ */ - -/* - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - -static void lka_imsg(struct mproc *, struct imsg *); -static void lka_shutdown(void); -static void lka_sig_handler(int, short, void *); -static int lka_authenticate(const char *, const char *, const char *); -static int lka_credentials(const char *, const char *, char *, size_t); -static int lka_userinfo(const char *, const char *, struct userinfo *); -static int lka_addrname(const char *, const struct sockaddr *, - struct addrname *); -static int lka_mailaddrmap(const char *, const char *, const struct mailaddr *); - -static void proc_timeout(int fd, short event, void *p); - -struct event ev_proc_ready; - -static void -lka_imsg(struct mproc *p, struct imsg *imsg) -{ - struct table *table; - int ret; - struct sockaddr_storage ss; - struct userinfo userinfo; - struct addrname addrname; - struct envelope evp; - struct mailaddr maddr; - struct msg m; - union lookup lk; - char buf[LINE_MAX]; - const char *tablename, *username, *password, *label, *procname; - uint64_t reqid; - int v; - struct timeval tv; - const char *direction; - const char *rdns; - const char *command; - const char *response; - const char *ciphers; - const char *address; - const char *domain; - const char *helomethod; - const char *heloname; - const char *filter_name; - const char *result; - struct sockaddr_storage ss_src, ss_dest; - int filter_response; - int filter_phase; - const char *filter_param; - uint32_t msgid; - uint32_t subsystems; - uint64_t evpid; - size_t msgsz; - int ok; - int fcrdns; - - if (imsg == NULL) - lka_shutdown(); - - switch (imsg->hdr.type) { - - case IMSG_GETADDRINFO: - case IMSG_GETNAMEINFO: - case IMSG_RES_QUERY: - resolver_dispatch_request(p, imsg); - return; - - case IMSG_CERT_INIT: - case IMSG_CERT_CERTIFICATE: - case IMSG_CERT_VERIFY: - cert_dispatch_request(p, imsg); - return; - - case IMSG_MTA_DNS_HOST: - case IMSG_MTA_DNS_MX: - case IMSG_MTA_DNS_MX_PREFERENCE: - dns_imsg(p, imsg); - return; - - case IMSG_SMTP_CHECK_SENDER: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_get_string(&m, &username); - m_get_mailaddr(&m, &maddr); - m_end(&m); - - ret = lka_mailaddrmap(tablename, username, &maddr); - - m_create(p, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, ret); - m_close(p); - return; - - case IMSG_SMTP_EXPAND_RCPT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_envelope(&m, &evp); - m_end(&m); - lka_session(reqid, &evp); - return; - - case IMSG_SMTP_LOOKUP_HELO: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_get_sockaddr(&m, (struct sockaddr *)&ss); - m_end(&m); - - ret = lka_addrname(tablename, (struct sockaddr*)&ss, - &addrname); - - m_create(p, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, ret); - if (ret == LKA_OK) - m_add_string(p, addrname.name); - m_close(p); - return; - - case IMSG_SMTP_AUTHENTICATE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_get_string(&m, &username); - m_get_string(&m, &password); - m_end(&m); - - if (!tablename[0]) { - m_create(p_parent, IMSG_LKA_AUTHENTICATE, - 0, 0, -1); - m_add_id(p_parent, reqid); - m_add_string(p_parent, username); - m_add_string(p_parent, password); - m_close(p_parent); - return; - } - - ret = lka_authenticate(tablename, username, password); - - m_create(p, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, ret); - m_close(p); - return; - - case IMSG_MDA_LOOKUP_USERINFO: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_get_string(&m, &username); - m_end(&m); - - ret = lka_userinfo(tablename, username, &userinfo); - - m_create(p, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, ret); - if (ret == LKA_OK) - m_add_data(p, &userinfo, sizeof(userinfo)); - m_close(p); - return; - - case IMSG_MTA_LOOKUP_CREDENTIALS: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_get_string(&m, &label); - m_end(&m); - - lka_credentials(tablename, label, buf, sizeof(buf)); - - m_create(p, IMSG_MTA_LOOKUP_CREDENTIALS, 0, 0, -1); - m_add_id(p, reqid); - m_add_string(p, buf); - m_close(p); - return; - - case IMSG_MTA_LOOKUP_SOURCE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_end(&m); - - table = table_find(env, tablename); - - m_create(p, IMSG_MTA_LOOKUP_SOURCE, 0, 0, -1); - m_add_id(p, reqid); - - if (table == NULL) { - log_warn("warn: source address table %s missing", - tablename); - m_add_int(p, LKA_TEMPFAIL); - } - else { - ret = table_fetch(table, K_SOURCE, &lk); - if (ret == -1) - m_add_int(p, LKA_TEMPFAIL); - else if (ret == 0) - m_add_int(p, LKA_PERMFAIL); - else { - m_add_int(p, LKA_OK); - m_add_sockaddr(p, - (struct sockaddr *)&lk.source.addr); - } - } - m_close(p); - return; - - case IMSG_MTA_LOOKUP_HELO: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &tablename); - m_get_sockaddr(&m, (struct sockaddr *)&ss); - m_end(&m); - - ret = lka_addrname(tablename, (struct sockaddr*)&ss, - &addrname); - - m_create(p, IMSG_MTA_LOOKUP_HELO, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, ret); - if (ret == LKA_OK) - m_add_string(p, addrname.name); - m_close(p); - return; - - case IMSG_MTA_LOOKUP_SMARTHOST: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &domain); - m_get_string(&m, &tablename); - m_end(&m); - - table = table_find(env, tablename); - - m_create(p, IMSG_MTA_LOOKUP_SMARTHOST, 0, 0, -1); - m_add_id(p, reqid); - - if (table == NULL) { - log_warn("warn: smarthost table %s missing", tablename); - m_add_int(p, LKA_TEMPFAIL); - } - else { - if (domain == NULL) - ret = table_fetch(table, K_RELAYHOST, &lk); - else - ret = table_lookup(table, K_RELAYHOST, domain, &lk); - - if (ret == -1) - m_add_int(p, LKA_TEMPFAIL); - else if (ret == 0) - m_add_int(p, LKA_PERMFAIL); - else { - m_add_int(p, LKA_OK); - m_add_string(p, lk.relayhost); - } - } - m_close(p); - return; - - case IMSG_CONF_START: - return; - - case IMSG_CONF_END: - if (tracing & TRACE_TABLES) - table_dump_all(env); - - /* fork & exec tables that need it */ - table_open_all(env); - -#if HAVE_PLEDGE - /* revoke proc & exec */ - if (pledge("stdio rpath inet dns getpw recvfd sendfd", - NULL) == -1) - err(1, "pledge"); -#endif - - /* setup proc registering task */ - evtimer_set(&ev_proc_ready, proc_timeout, &ev_proc_ready); - tv.tv_sec = 0; - tv.tv_usec = 10; - evtimer_add(&ev_proc_ready, &tv); - return; - - case IMSG_LKA_OPEN_FORWARD: - lka_session_forward_reply(imsg->data, imsg->fd); - return; - - case IMSG_LKA_AUTHENTICATE: - imsg->hdr.type = IMSG_SMTP_AUTHENTICATE; - m_forward(p_pony, imsg); - return; - - case IMSG_CTL_VERBOSE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - log_trace_verbose(v); - return; - - case IMSG_CTL_PROFILE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - profiling = v; - return; - - case IMSG_CTL_UPDATE_TABLE: - ret = 0; - table = table_find(env, imsg->data); - if (table == NULL) { - log_warnx("warn: Lookup table not found: " - "\"%s\"", (char *)imsg->data); - } else - ret = table_update(table); - - m_compose(p_control, - (ret == 1) ? IMSG_CTL_OK : IMSG_CTL_FAIL, - imsg->hdr.peerid, 0, -1, NULL, 0); - return; - - case IMSG_LKA_PROCESSOR_FORK: - m_msg(&m, imsg); - m_get_string(&m, &procname); - m_get_u32(&m, &subsystems); - m_end(&m); - - m_create(p, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, -1); - m_add_string(p, procname); - m_close(p); - - lka_proc_forked(procname, subsystems, imsg->fd); - return; - - case IMSG_LKA_PROCESSOR_ERRFD: - m_msg(&m, imsg); - m_get_string(&m, &procname); - m_end(&m); - - lka_proc_errfd(procname, imsg->fd); - shutdown(imsg->fd, SHUT_WR); - return; - - case IMSG_REPORT_SMTP_LINK_CONNECT: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &rdns); - m_get_int(&m, &fcrdns); - m_get_sockaddr(&m, (struct sockaddr *)&ss_src); - m_get_sockaddr(&m, (struct sockaddr *)&ss_dest); - m_end(&m); - - lka_report_smtp_link_connect(direction, &tv, reqid, rdns, fcrdns, &ss_src, &ss_dest); - return; - - case IMSG_REPORT_SMTP_LINK_GREETING: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &domain); - m_end(&m); - - lka_report_smtp_link_greeting(direction, reqid, &tv, domain); - return; - - case IMSG_REPORT_SMTP_LINK_DISCONNECT: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_end(&m); - - lka_report_smtp_link_disconnect(direction, &tv, reqid); - return; - - case IMSG_REPORT_SMTP_LINK_IDENTIFY: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &helomethod); - m_get_string(&m, &heloname); - m_end(&m); - - lka_report_smtp_link_identify(direction, &tv, reqid, helomethod, heloname); - return; - - case IMSG_REPORT_SMTP_LINK_TLS: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &ciphers); - m_end(&m); - - lka_report_smtp_link_tls(direction, &tv, reqid, ciphers); - return; - - case IMSG_REPORT_SMTP_LINK_AUTH: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &username); - m_get_string(&m, &result); - m_end(&m); - - lka_report_smtp_link_auth(direction, &tv, reqid, username, result); - return; - - case IMSG_REPORT_SMTP_TX_RESET: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_end(&m); - - lka_report_smtp_tx_reset(direction, &tv, reqid, msgid); - return; - - case IMSG_REPORT_SMTP_TX_BEGIN: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_end(&m); - - lka_report_smtp_tx_begin(direction, &tv, reqid, msgid); - return; - - case IMSG_REPORT_SMTP_TX_MAIL: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_get_string(&m, &address); - m_get_int(&m, &ok); - m_end(&m); - - lka_report_smtp_tx_mail(direction, &tv, reqid, msgid, address, ok); - return; - - case IMSG_REPORT_SMTP_TX_RCPT: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_get_string(&m, &address); - m_get_int(&m, &ok); - m_end(&m); - - lka_report_smtp_tx_rcpt(direction, &tv, reqid, msgid, address, ok); - return; - - case IMSG_REPORT_SMTP_TX_ENVELOPE: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_get_id(&m, &evpid); - m_end(&m); - - lka_report_smtp_tx_envelope(direction, &tv, reqid, msgid, evpid); - return; - - case IMSG_REPORT_SMTP_TX_DATA: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_get_int(&m, &ok); - m_end(&m); - - lka_report_smtp_tx_data(direction, &tv, reqid, msgid, ok); - return; - - case IMSG_REPORT_SMTP_TX_COMMIT: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_get_size(&m, &msgsz); - m_end(&m); - - lka_report_smtp_tx_commit(direction, &tv, reqid, msgid, msgsz); - return; - - case IMSG_REPORT_SMTP_TX_ROLLBACK: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_u32(&m, &msgid); - m_end(&m); - - lka_report_smtp_tx_rollback(direction, &tv, reqid, msgid); - return; - - case IMSG_REPORT_SMTP_PROTOCOL_CLIENT: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &command); - m_end(&m); - - lka_report_smtp_protocol_client(direction, &tv, reqid, command); - return; - - case IMSG_REPORT_SMTP_PROTOCOL_SERVER: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_string(&m, &response); - m_end(&m); - - lka_report_smtp_protocol_server(direction, &tv, reqid, response); - return; - - case IMSG_REPORT_SMTP_FILTER_RESPONSE: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_get_int(&m, &filter_phase); - m_get_int(&m, &filter_response); - m_get_string(&m, &filter_param); - m_end(&m); - - lka_report_smtp_filter_response(direction, &tv, reqid, - filter_phase, filter_response, filter_param); - return; - - case IMSG_REPORT_SMTP_TIMEOUT: - m_msg(&m, imsg); - m_get_string(&m, &direction); - m_get_timeval(&m, &tv); - m_get_id(&m, &reqid); - m_end(&m); - - lka_report_smtp_timeout(direction, &tv, reqid); - return; - - case IMSG_FILTER_SMTP_PROTOCOL: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &filter_phase); - m_get_string(&m, &filter_param); - m_end(&m); - - lka_filter_protocol(reqid, filter_phase, filter_param); - return; - - case IMSG_FILTER_SMTP_BEGIN: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &filter_name); - m_end(&m); - - lka_filter_begin(reqid, filter_name); - return; - - case IMSG_FILTER_SMTP_END: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - lka_filter_end(reqid); - return; - - case IMSG_FILTER_SMTP_DATA_BEGIN: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - lka_filter_data_begin(reqid); - return; - - case IMSG_FILTER_SMTP_DATA_END: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - lka_filter_data_end(reqid); - return; - - } - - errx(1, "lka_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -static void -lka_sig_handler(int sig, short event, void *p) -{ - int status; - pid_t pid; - - switch (sig) { - case SIGCHLD: - do { - pid = waitpid(-1, &status, WNOHANG); - } while (pid > 0 || (pid == -1 && errno == EINTR)); - break; - default: - fatalx("lka_sig_handler: unexpected signal"); - } -} - -void -lka_shutdown(void) -{ - log_debug("debug: lookup agent exiting"); - _exit(0); -} - -int -lka(void) -{ - struct passwd *pw; - struct event ev_sigchld; - - purge_config(PURGE_LISTENERS); - - if ((pw = getpwnam(SMTPD_USER)) == NULL) - fatalx("unknown user " SMTPD_USER); - - config_process(PROC_LKA); - - if (initgroups(pw->pw_name, pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("lka: cannot drop privileges"); - - imsg_callback = lka_imsg; - event_init(); - - signal_set(&ev_sigchld, SIGCHLD, lka_sig_handler, NULL); - signal_add(&ev_sigchld, NULL); - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - config_peer(PROC_PARENT); - config_peer(PROC_QUEUE); - config_peer(PROC_CONTROL); - config_peer(PROC_PONY); - - /* Ignore them until we get our config */ - mproc_disable(p_pony); - - lka_report_init(); - lka_filter_init(); - -#if HAVE_PLEDGE - /* proc & exec will be revoked before serving requests */ - if (pledge("stdio rpath inet dns getpw recvfd sendfd proc exec", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} - -static void -proc_timeout(int fd, short event, void *p) -{ - struct event *ev = p; - struct timeval tv; - - if (!lka_proc_ready()) - goto reset; - - lka_filter_ready(); - mproc_enable(p_pony); - return; - -reset: - tv.tv_sec = 0; - tv.tv_usec = 10; - evtimer_add(ev, &tv); -} - - -static int -lka_authenticate(const char *tablename, const char *user, const char *password) -{ - struct table *table; - union lookup lk; - - log_debug("debug: lka: authenticating for %s:%s", tablename, user); - table = table_find(env, tablename); - if (table == NULL) { - log_warnx("warn: could not find table %s needed for authentication", - tablename); - return (LKA_TEMPFAIL); - } - - switch (table_lookup(table, K_CREDENTIALS, user, &lk)) { - case -1: - log_warnx("warn: user credentials lookup fail for %s:%s", - tablename, user); - return (LKA_TEMPFAIL); - case 0: - return (LKA_PERMFAIL); - default: - if (crypt_checkpass(password, lk.creds.password) == 0) - return (LKA_OK); - return (LKA_PERMFAIL); - } -} - -static int -lka_credentials(const char *tablename, const char *label, char *dst, size_t sz) -{ - struct table *table; - union lookup lk; - char *buf; - int buflen, r; - - table = table_find(env, tablename); - if (table == NULL) { - log_warnx("warn: credentials table %s missing", tablename); - return (LKA_TEMPFAIL); - } - - dst[0] = '\0'; - - switch (table_lookup(table, K_CREDENTIALS, label, &lk)) { - case -1: - log_warnx("warn: credentials lookup fail for %s:%s", - tablename, label); - return (LKA_TEMPFAIL); - case 0: - log_warnx("warn: credentials not found for %s:%s", - tablename, label); - return (LKA_PERMFAIL); - default: - if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', - lk.creds.username, '\0', lk.creds.password)) == -1) { - log_warn("warn"); - return (LKA_TEMPFAIL); - } - - r = base64_encode((unsigned char *)buf, buflen, dst, sz); - free(buf); - - if (r == -1) { - log_warnx("warn: credentials parse error for %s:%s", - tablename, label); - return (LKA_TEMPFAIL); - } - return (LKA_OK); - } -} - -static int -lka_userinfo(const char *tablename, const char *username, struct userinfo *res) -{ - struct table *table; - union lookup lk; - - log_debug("debug: lka: userinfo %s:%s", tablename, username); - table = table_find(env, tablename); - if (table == NULL) { - log_warnx("warn: cannot find user table %s", tablename); - return (LKA_TEMPFAIL); - } - - switch (table_lookup(table, K_USERINFO, username, &lk)) { - case -1: - log_warnx("warn: failure during userinfo lookup %s:%s", - tablename, username); - return (LKA_TEMPFAIL); - case 0: - return (LKA_PERMFAIL); - default: - *res = lk.userinfo; - return (LKA_OK); - } -} - -static int -lka_addrname(const char *tablename, const struct sockaddr *sa, - struct addrname *res) -{ - struct table *table; - union lookup lk; - const char *source; - - source = sa_to_text(sa); - - log_debug("debug: lka: helo %s:%s", tablename, source); - table = table_find(env, tablename); - if (table == NULL) { - log_warnx("warn: cannot find helo table %s", tablename); - return (LKA_TEMPFAIL); - } - - switch (table_lookup(table, K_ADDRNAME, source, &lk)) { - case -1: - log_warnx("warn: failure during helo lookup %s:%s", - tablename, source); - return (LKA_TEMPFAIL); - case 0: - return (LKA_PERMFAIL); - default: - *res = lk.addrname; - return (LKA_OK); - } -} - -static int -lka_mailaddrmap(const char *tablename, const char *username, const struct mailaddr *maddr) -{ - struct table *table; - struct maddrnode *mn; - union lookup lk; - int found; - - log_debug("debug: lka: mailaddrmap %s:%s", tablename, username); - table = table_find(env, tablename); - if (table == NULL) { - log_warnx("warn: cannot find mailaddrmap table %s", tablename); - return (LKA_TEMPFAIL); - } - - switch (table_lookup(table, K_MAILADDRMAP, username, &lk)) { - case -1: - log_warnx("warn: failure during mailaddrmap lookup %s:%s", - tablename, username); - return (LKA_TEMPFAIL); - case 0: - return (LKA_PERMFAIL); - default: - found = 0; - TAILQ_FOREACH(mn, &lk.maddrmap->queue, entries) { - if (!mailaddr_match(maddr, &mn->mailaddr)) - continue; - found = 1; - break; - } - maddrmap_free(lk.maddrmap); - if (found) - return (LKA_OK); - return (LKA_PERMFAIL); - } - return (LKA_OK); -} diff --git a/smtpd/lka_filter.c b/smtpd/lka_filter.c deleted file mode 100644 index 2dc66057..00000000 --- a/smtpd/lka_filter.c +++ /dev/null @@ -1,1746 +0,0 @@ -/* $OpenBSD: lka_filter.c,v 1.62 2020/04/24 11:34:07 eric Exp $ */ - -/* - * Copyright (c) 2018 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define PROTOCOL_VERSION "0.6" - -struct filter; -struct filter_session; -static void filter_protocol_internal(struct filter_session *, uint64_t *, uint64_t, enum filter_phase, const char *); -static void filter_protocol(uint64_t, enum filter_phase, const char *); -static void filter_protocol_next(uint64_t, uint64_t, enum filter_phase); -static void filter_protocol_query(struct filter *, uint64_t, uint64_t, const char *, const char *); - -static void filter_data_internal(struct filter_session *, uint64_t, uint64_t, const char *); -static void filter_data(uint64_t, const char *); -static void filter_data_next(uint64_t, uint64_t, const char *); -static void filter_data_query(struct filter *, uint64_t, uint64_t, const char *); - -static int filter_builtins_notimpl(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_builtins_connect(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_builtins_helo(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_builtins_mail_from(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_builtins_rcpt_to(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_builtins_data(struct filter_session *, struct filter *, uint64_t, const char *); -static int filter_builtins_commit(struct filter_session *, struct filter *, uint64_t, const char *); - -static void filter_result_proceed(uint64_t); -static void filter_result_junk(uint64_t); -static void filter_result_rewrite(uint64_t, const char *); -static void filter_result_reject(uint64_t, const char *); -static void filter_result_disconnect(uint64_t, const char *); - -static void filter_session_io(struct io *, int, void *); -void lka_filter_process_response(const char *, const char *); - - -struct filter_session { - uint64_t id; - struct io *io; - - char *lastparam; - - char *filter_name; - struct sockaddr_storage ss_src; - struct sockaddr_storage ss_dest; - char *rdns; - int fcrdns; - - char *helo; - char *username; - char *mail_from; - - enum filter_phase phase; -}; - -static struct filter_exec { - enum filter_phase phase; - const char *phase_name; - int (*func)(struct filter_session *, struct filter *, uint64_t, const char *); -} filter_execs[FILTER_PHASES_COUNT] = { - { FILTER_CONNECT, "connect", filter_builtins_connect }, - { FILTER_HELO, "helo", filter_builtins_helo }, - { FILTER_EHLO, "ehlo", filter_builtins_helo }, - { FILTER_STARTTLS, "starttls", filter_builtins_notimpl }, - { FILTER_AUTH, "auth", filter_builtins_notimpl }, - { FILTER_MAIL_FROM, "mail-from", filter_builtins_mail_from }, - { FILTER_RCPT_TO, "rcpt-to", filter_builtins_rcpt_to }, - { FILTER_DATA, "data", filter_builtins_data }, - { FILTER_DATA_LINE, "data-line", filter_builtins_notimpl }, - { FILTER_RSET, "rset", filter_builtins_notimpl }, - { FILTER_QUIT, "quit", filter_builtins_notimpl }, - { FILTER_NOOP, "noop", filter_builtins_notimpl }, - { FILTER_HELP, "help", filter_builtins_notimpl }, - { FILTER_WIZ, "wiz", filter_builtins_notimpl }, - { FILTER_COMMIT, "commit", filter_builtins_commit }, -}; - -struct filter { - uint64_t id; - uint32_t phases; - const char *name; - const char *proc; - struct filter **chain; - size_t chain_size; - struct filter_config *config; -}; -static struct dict filters; - -struct filter_entry { - TAILQ_ENTRY(filter_entry) entries; - uint64_t id; - const char *name; -}; - -struct filter_chain { - TAILQ_HEAD(, filter_entry) chain[nitems(filter_execs)]; -}; - -static struct dict filter_smtp_in; - -static struct tree sessions; -static int filters_inited; - -static struct dict filter_chains; - -struct reporter_proc { - TAILQ_ENTRY(reporter_proc) entries; - const char *name; -}; -TAILQ_HEAD(reporters, reporter_proc); - -static struct dict report_smtp_in; -static struct dict report_smtp_out; - -static struct smtp_events { - const char *event; -} smtp_events[] = { - { "link-connect" }, - { "link-disconnect" }, - { "link-greeting" }, - { "link-identify" }, - { "link-tls" }, - { "link-auth" }, - - { "tx-reset" }, - { "tx-begin" }, - { "tx-mail" }, - { "tx-rcpt" }, - { "tx-envelope" }, - { "tx-data" }, - { "tx-commit" }, - { "tx-rollback" }, - - { "protocol-client" }, - { "protocol-server" }, - - { "filter-report" }, - { "filter-response" }, - - { "timeout" }, -}; - -static int processors_inited = 0; -static struct dict processors; - -struct processor_instance { - char *name; - struct io *io; - struct io *errfd; - int ready; - uint32_t subsystems; -}; - -static void processor_io(struct io *, int, void *); -static void processor_errfd(struct io *, int, void *); -void lka_filter_process_response(const char *, const char *); - -int -lka_proc_ready(void) -{ - void *iter; - struct processor_instance *pi; - - iter = NULL; - while (dict_iter(&processors, &iter, NULL, (void **)&pi)) - if (!pi->ready) - return 0; - return 1; -} - -static void -lka_proc_config(struct processor_instance *pi) -{ - io_printf(pi->io, "config|smtpd-version|%s\n", SMTPD_VERSION); - io_printf(pi->io, "config|smtp-session-timeout|%d\n", SMTPD_SESSION_TIMEOUT); - if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_IN) - io_printf(pi->io, "config|subsystem|smtp-in\n"); - if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_OUT) - io_printf(pi->io, "config|subsystem|smtp-out\n"); - io_printf(pi->io, "config|ready\n"); -} - -void -lka_proc_forked(const char *name, uint32_t subsystems, int fd) -{ - struct processor_instance *processor; - - if (!processors_inited) { - dict_init(&processors); - processors_inited = 1; - } - - processor = xcalloc(1, sizeof *processor); - processor->name = xstrdup(name); - processor->io = io_new(); - processor->subsystems = subsystems; - - io_set_nonblocking(fd); - - io_set_fd(processor->io, fd); - io_set_callback(processor->io, processor_io, processor->name); - dict_xset(&processors, name, processor); -} - -void -lka_proc_errfd(const char *name, int fd) -{ - struct processor_instance *processor; - - processor = dict_xget(&processors, name); - - io_set_nonblocking(fd); - - processor->errfd = io_new(); - io_set_fd(processor->errfd, fd); - io_set_callback(processor->errfd, processor_errfd, processor->name); - - lka_proc_config(processor); -} - -struct io * -lka_proc_get_io(const char *name) -{ - struct processor_instance *processor; - - processor = dict_xget(&processors, name); - - return processor->io; -} - -static void -processor_register(const char *name, const char *line) -{ - struct processor_instance *processor; - - processor = dict_xget(&processors, name); - - if (strcmp(line, "register|ready") == 0) { - processor->ready = 1; - return; - } - - if (strncmp(line, "register|report|", 16) == 0) { - lka_report_register_hook(name, line+16); - return; - } - - if (strncmp(line, "register|filter|", 16) == 0) { - lka_filter_register_hook(name, line+16); - return; - } - - fatalx("Invalid register line received: %s", line); -} - -static void -processor_io(struct io *io, int evt, void *arg) -{ - struct processor_instance *processor; - const char *name = arg; - char *line = NULL; - ssize_t len; - - switch (evt) { - case IO_DATAIN: - while ((line = io_getline(io, &len)) != NULL) { - if (strncmp("register|", line, 9) == 0) { - processor_register(name, line); - continue; - } - - processor = dict_xget(&processors, name); - if (!processor->ready) - fatalx("Non-register message before register|" - "ready: %s", line); - else if (strncmp(line, "filter-result|", 14) == 0 || - strncmp(line, "filter-dataline|", 16) == 0) - lka_filter_process_response(name, line); - else if (strncmp(line, "report|", 7) == 0) - lka_report_proc(name, line); - else - fatalx("Invalid filter message type: %s", line); - } - } -} - -static void -processor_errfd(struct io *io, int evt, void *arg) -{ - const char *name = arg; - char *line = NULL; - ssize_t len; - - switch (evt) { - case IO_DATAIN: - while ((line = io_getline(io, &len)) != NULL) - log_warnx("%s: %s", name, line); - } -} - -void -lka_filter_init(void) -{ - void *iter; - const char *name; - struct filter *filter; - struct filter_config *filter_config; - size_t i; - char buffer[LINE_MAX]; /* for traces */ - - dict_init(&filters); - dict_init(&filter_chains); - - /* first pass, allocate and init individual filters */ - iter = NULL; - while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { - switch (filter_config->filter_type) { - case FILTER_TYPE_BUILTIN: - filter = xcalloc(1, sizeof(*filter)); - filter->name = name; - filter->phases |= (1<phase); - filter->config = filter_config; - dict_set(&filters, name, filter); - log_trace(TRACE_FILTERS, "filters init type=builtin, name=%s, hooks=%08x", - name, filter->phases); - break; - - case FILTER_TYPE_PROC: - filter = xcalloc(1, sizeof(*filter)); - filter->name = name; - filter->proc = filter_config->proc; - filter->config = filter_config; - dict_set(&filters, name, filter); - log_trace(TRACE_FILTERS, "filters init type=proc, name=%s, proc=%s", - name, filter_config->proc); - break; - - case FILTER_TYPE_CHAIN: - break; - } - } - - /* second pass, allocate and init filter chains but don't build yet */ - iter = NULL; - while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { - switch (filter_config->filter_type) { - case FILTER_TYPE_CHAIN: - filter = xcalloc(1, sizeof(*filter)); - filter->name = name; - filter->chain = xcalloc(filter_config->chain_size, sizeof(void **)); - filter->chain_size = filter_config->chain_size; - filter->config = filter_config; - - buffer[0] = '\0'; - for (i = 0; i < filter->chain_size; ++i) { - filter->chain[i] = dict_xget(&filters, filter_config->chain[i]); - if (i) - (void)strlcat(buffer, ", ", sizeof buffer); - (void)strlcat(buffer, filter->chain[i]->name, sizeof buffer); - } - log_trace(TRACE_FILTERS, "filters init type=chain, name=%s { %s }", name, buffer); - - dict_set(&filters, name, filter); - break; - - case FILTER_TYPE_BUILTIN: - case FILTER_TYPE_PROC: - break; - } - } -} - -void -lka_filter_register_hook(const char *name, const char *hook) -{ - struct dict *subsystem; - struct filter *filter; - const char *filter_name; - void *iter; - size_t i; - - if (strncasecmp(hook, "smtp-in|", 8) == 0) { - subsystem = &filter_smtp_in; - hook += 8; - } - else - fatalx("Invalid message direction: %s", hook); - - for (i = 0; i < nitems(filter_execs); i++) - if (strcmp(hook, filter_execs[i].phase_name) == 0) - break; - if (i == nitems(filter_execs)) - fatalx("Unrecognized report name: %s", hook); - - iter = NULL; - while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) - if (filter->proc && strcmp(name, filter->proc) == 0) - filter->phases |= (1<chain[i]); - dict_set(&filter_chains, filter_name, filter_chain); - - if (filter->chain) { - for (i = 0; i < filter->chain_size; i++) { - subfilter = filter->chain[i]; - for (j = 0; j < nitems(filter_execs); ++j) { - if (subfilter->phases & (1<id = generate_uid(); - filter_entry->name = subfilter->name; - TAILQ_INSERT_TAIL(&filter_chain->chain[j], - filter_entry, entries); - } - } - } - continue; - } - - for (i = 0; i < nitems(filter_execs); ++i) { - if (filter->phases & (1<id = generate_uid(); - filter_entry->name = filter_name; - TAILQ_INSERT_TAIL(&filter_chain->chain[i], - filter_entry, entries); - } - } - } -} - -int -lka_filter_proc_in_session(uint64_t reqid, const char *proc) -{ - struct filter_session *fs; - struct filter *filter; - size_t i; - - if ((fs = tree_get(&sessions, reqid)) == NULL) - return 0; - - filter = dict_get(&filters, fs->filter_name); - if (filter == NULL || (filter->proc == NULL && filter->chain == NULL)) - return 0; - - if (filter->proc) - return strcmp(filter->proc, proc) == 0 ? 1 : 0; - - for (i = 0; i < filter->chain_size; i++) - if (filter->chain[i]->proc && - strcmp(filter->chain[i]->proc, proc) == 0) - return 1; - - return 0; -} - -void -lka_filter_begin(uint64_t reqid, const char *filter_name) -{ - struct filter_session *fs; - - if (!filters_inited) { - tree_init(&sessions); - filters_inited = 1; - } - - fs = xcalloc(1, sizeof (struct filter_session)); - fs->id = reqid; - fs->filter_name = xstrdup(filter_name); - tree_xset(&sessions, fs->id, fs); - - log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-begin", reqid); -} - -void -lka_filter_end(uint64_t reqid) -{ - struct filter_session *fs; - - fs = tree_xpop(&sessions, reqid); - free(fs->rdns); - free(fs->helo); - free(fs->mail_from); - free(fs->username); - free(fs->lastparam); - free(fs); - log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-end", reqid); -} - -void -lka_filter_data_begin(uint64_t reqid) -{ - struct filter_session *fs; - int sp[2]; - int fd = -1; - - fs = tree_xget(&sessions, reqid); - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) - goto end; - io_set_nonblocking(sp[0]); - io_set_nonblocking(sp[1]); - fd = sp[0]; - fs->io = io_new(); - io_set_fd(fs->io, sp[1]); - io_set_callback(fs->io, filter_session_io, fs); - -end: - m_create(p_pony, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, fd); - m_add_id(p_pony, reqid); - m_add_int(p_pony, fd != -1 ? 1 : 0); - m_close(p_pony); - log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-begin fd=%d", reqid, fd); -} - -void -lka_filter_data_end(uint64_t reqid) -{ - struct filter_session *fs; - - fs = tree_xget(&sessions, reqid); - if (fs->io) { - io_free(fs->io); - fs->io = NULL; - } - log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-end", reqid); -} - -static void -filter_session_io(struct io *io, int evt, void *arg) -{ - struct filter_session *fs = arg; - char *line = NULL; - ssize_t len; - - log_trace(TRACE_IO, "filter session: %p: %s %s", fs, io_strevent(evt), - io_strio(io)); - - switch (evt) { - case IO_DATAIN: - nextline: - line = io_getline(fs->io, &len); - /* No complete line received */ - if (line == NULL) - return; - - filter_data(fs->id, line); - - goto nextline; - - case IO_DISCONNECTED: - io_free(fs->io); - fs->io = NULL; - break; - } -} - -void -lka_filter_process_response(const char *name, const char *line) -{ - uint64_t reqid; - uint64_t token; - char buffer[LINE_MAX]; - char *ep = NULL; - char *kind = NULL; - char *qid = NULL; - /*char *phase = NULL;*/ - char *response = NULL; - char *parameter = NULL; - struct filter_session *fs; - - (void)strlcpy(buffer, line, sizeof buffer); - if ((ep = strchr(buffer, '|')) == NULL) - fatalx("Missing token: %s", line); - ep[0] = '\0'; - - kind = buffer; - - qid = ep+1; - if ((ep = strchr(qid, '|')) == NULL) - fatalx("Missing reqid: %s", line); - ep[0] = '\0'; - - reqid = strtoull(qid, &ep, 16); - if (qid[0] == '\0' || *ep != '\0') - fatalx("Invalid reqid: %s", line); - if (errno == ERANGE && reqid == ULLONG_MAX) - fatal("Invalid reqid: %s", line); - - qid = ep+1; - if ((ep = strchr(qid, '|')) == NULL) - fatal("Missing directive: %s", line); - ep[0] = '\0'; - - token = strtoull(qid, &ep, 16); - if (qid[0] == '\0' || *ep != '\0') - fatalx("Invalid token: %s", line); - if (errno == ERANGE && token == ULLONG_MAX) - fatal("Invalid token: %s", line); - - response = ep+1; - - /* session can legitimately disappear on a resume */ - if ((fs = tree_get(&sessions, reqid)) == NULL) - return; - - if (strcmp(kind, "filter-dataline") == 0) { - if (fs->phase != FILTER_DATA_LINE) - fatalx("filter-dataline out of dataline phase"); - filter_data_next(token, reqid, response); - return; - } - if (fs->phase == FILTER_DATA_LINE) - fatalx("filter-result in dataline phase"); - - if ((ep = strchr(response, '|'))) { - parameter = ep + 1; - ep[0] = '\0'; - } - - if (strcmp(response, "proceed") == 0) { - if (parameter != NULL) - fatalx("Unexpected parameter after proceed: %s", line); - filter_protocol_next(token, reqid, 0); - return; - } else if (strcmp(response, "junk") == 0) { - if (parameter != NULL) - fatalx("Unexpected parameter after junk: %s", line); - if (fs->phase == FILTER_COMMIT) - fatalx("filter-reponse junk after DATA"); - filter_result_junk(reqid); - return; - } else { - if (parameter == NULL) - fatalx("Missing parameter: %s", line); - - if (strcmp(response, "rewrite") == 0) - filter_result_rewrite(reqid, parameter); - else if (strcmp(response, "reject") == 0) - filter_result_reject(reqid, parameter); - else if (strcmp(response, "disconnect") == 0) - filter_result_disconnect(reqid, parameter); - else - fatalx("Invalid directive: %s", line); - } -} - -void -lka_filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) -{ - filter_protocol(reqid, phase, param); -} - -static void -filter_protocol_internal(struct filter_session *fs, uint64_t *token, uint64_t reqid, enum filter_phase phase, const char *param) -{ - struct filter_chain *filter_chain; - struct filter_entry *filter_entry; - struct filter *filter; - struct timeval tv; - const char *phase_name = filter_execs[phase].phase_name; - int resume = 1; - - if (!*token) { - fs->phase = phase; - resume = 0; - } - - /* XXX - this sanity check requires a protocol change, stub for now */ - phase = fs->phase; - if (fs->phase != phase) - fatalx("misbehaving filter"); - - /* based on token, identify the filter_entry we should apply */ - filter_chain = dict_get(&filter_chains, fs->filter_name); - filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); - if (*token) { - TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) - if (filter_entry->id == *token) - break; - if (filter_entry == NULL) - fatalx("misbehaving filter"); - filter_entry = TAILQ_NEXT(filter_entry, entries); - } - - /* no filter_entry, we either had none or reached end of chain */ - if (filter_entry == NULL) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, resume=%s, " - "action=proceed", - fs->id, phase_name, resume ? "y" : "n"); - filter_result_proceed(reqid); - return; - } - - /* process param with current filter_entry */ - *token = filter_entry->id; - filter = dict_get(&filters, filter_entry->name); - if (filter->proc) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=deferred, filter=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name); - filter_protocol_query(filter, filter_entry->id, reqid, - filter_execs[fs->phase].phase_name, param); - return; /* deferred response */ - } - - if (filter_execs[fs->phase].func(fs, filter, reqid, param)) { - if (filter->config->rewrite) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=rewrite, filter=%s, query=%s, response=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param, - filter->config->rewrite); - filter_result_rewrite(reqid, filter->config->rewrite); - return; - } - else if (filter->config->disconnect) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=disconnect, filter=%s, query=%s, response=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param, - filter->config->disconnect); - filter_result_disconnect(reqid, filter->config->disconnect); - return; - } - else if (filter->config->junk) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=junk, filter=%s, query=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param); - filter_result_junk(reqid); - return; - } else if (filter->config->report) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=report, filter=%s, query=%s response=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param, filter->config->report); - - gettimeofday(&tv, NULL); - lka_report_filter_report(fs->id, filter->name, 1, - "smtp-in", &tv, filter->config->report); - } else if (filter->config->bypass) { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=bypass, filter=%s, query=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param); - filter_result_proceed(reqid); - return; - } else { - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=reject, filter=%s, query=%s, response=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param, - filter->config->reject); - filter_result_reject(reqid, filter->config->reject); - return; - } - } - - log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " - "resume=%s, action=proceed, filter=%s, query=%s", - fs->id, phase_name, resume ? "y" : "n", - filter->name, - param); - - /* filter_entry resulted in proceed, try next filter */ - filter_protocol_internal(fs, token, reqid, phase, param); - return; -} - -static void -filter_data_internal(struct filter_session *fs, uint64_t token, uint64_t reqid, const char *line) -{ - struct filter_chain *filter_chain; - struct filter_entry *filter_entry; - struct filter *filter; - - if (!token) - fs->phase = FILTER_DATA_LINE; - if (fs->phase != FILTER_DATA_LINE) - fatalx("misbehaving filter"); - - /* based on token, identify the filter_entry we should apply */ - filter_chain = dict_get(&filter_chains, fs->filter_name); - filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); - if (token) { - TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) - if (filter_entry->id == token) - break; - if (filter_entry == NULL) - fatalx("misbehaving filter"); - filter_entry = TAILQ_NEXT(filter_entry, entries); - } - - /* no filter_entry, we either had none or reached end of chain */ - if (filter_entry == NULL) { - io_printf(fs->io, "%s\n", line); - return; - } - - /* pass data to the filter */ - filter = dict_get(&filters, filter_entry->name); - filter_data_query(filter, filter_entry->id, reqid, line); -} - -static void -filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) -{ - struct filter_session *fs; - uint64_t token = 0; - char *nparam = NULL; - - fs = tree_xget(&sessions, reqid); - - switch (phase) { - case FILTER_HELO: - case FILTER_EHLO: - free(fs->helo); - fs->helo = xstrdup(param); - break; - case FILTER_MAIL_FROM: - free(fs->mail_from); - fs->mail_from = xstrdup(param + 1); - *strchr(fs->mail_from, '>') = '\0'; - param = fs->mail_from; - - break; - case FILTER_RCPT_TO: - nparam = xstrdup(param + 1); - *strchr(nparam, '>') = '\0'; - param = nparam; - break; - case FILTER_STARTTLS: - /* TBD */ - break; - default: - break; - } - - free(fs->lastparam); - fs->lastparam = xstrdup(param); - - filter_protocol_internal(fs, &token, reqid, phase, param); - if (nparam) - free(nparam); -} - -static void -filter_protocol_next(uint64_t token, uint64_t reqid, enum filter_phase phase) -{ - struct filter_session *fs; - - /* session can legitimately disappear on a resume */ - if ((fs = tree_get(&sessions, reqid)) == NULL) - return; - - filter_protocol_internal(fs, &token, reqid, phase, fs->lastparam); -} - -static void -filter_data(uint64_t reqid, const char *line) -{ - struct filter_session *fs; - - fs = tree_xget(&sessions, reqid); - - filter_data_internal(fs, 0, reqid, line); -} - -static void -filter_data_next(uint64_t token, uint64_t reqid, const char *line) -{ - struct filter_session *fs; - - /* session can legitimately disappear on a resume */ - if ((fs = tree_get(&sessions, reqid)) == NULL) - return; - - filter_data_internal(fs, token, reqid, line); -} - -static void -filter_protocol_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *phase, const char *param) -{ - int n; - struct filter_session *fs; - struct timeval tv; - - gettimeofday(&tv, NULL); - - fs = tree_xget(&sessions, reqid); - if (strcmp(phase, "connect") == 0) - n = io_printf(lka_proc_get_io(filter->proc), - "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s|%s\n", - PROTOCOL_VERSION, - (long long int)tv.tv_sec, tv.tv_usec, - phase, reqid, token, fs->rdns, param); - else - n = io_printf(lka_proc_get_io(filter->proc), - "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s\n", - PROTOCOL_VERSION, - (long long int)tv.tv_sec, tv.tv_usec, - phase, reqid, token, param); - if (n == -1) - fatalx("failed to write to processor"); -} - -static void -filter_data_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *line) -{ - int n; - struct timeval tv; - - gettimeofday(&tv, NULL); - - n = io_printf(lka_proc_get_io(filter->proc), - "filter|%s|%lld.%06ld|smtp-in|data-line|" - "%016"PRIx64"|%016"PRIx64"|%s\n", - PROTOCOL_VERSION, - (long long int)tv.tv_sec, tv.tv_usec, - reqid, token, line); - if (n == -1) - fatalx("failed to write to processor"); -} - -static void -filter_result_proceed(uint64_t reqid) -{ - m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_pony, reqid); - m_add_int(p_pony, FILTER_PROCEED); - m_close(p_pony); -} - -static void -filter_result_junk(uint64_t reqid) -{ - m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_pony, reqid); - m_add_int(p_pony, FILTER_JUNK); - m_close(p_pony); -} - -static void -filter_result_rewrite(uint64_t reqid, const char *param) -{ - m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_pony, reqid); - m_add_int(p_pony, FILTER_REWRITE); - m_add_string(p_pony, param); - m_close(p_pony); -} - -static void -filter_result_reject(uint64_t reqid, const char *message) -{ - m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_pony, reqid); - m_add_int(p_pony, FILTER_REJECT); - m_add_string(p_pony, message); - m_close(p_pony); -} - -static void -filter_result_disconnect(uint64_t reqid, const char *message) -{ - m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_pony, reqid); - m_add_int(p_pony, FILTER_DISCONNECT); - m_add_string(p_pony, message); - m_close(p_pony); -} - - -/* below is code for builtin filters */ - -static int -filter_check_rdns_table(struct filter *filter, enum table_service kind, const char *key) -{ - int ret = 0; - - if (filter->config->rdns_table == NULL) - return 0; - - if (table_match(filter->config->rdns_table, kind, key) > 0) - ret = 1; - - return filter->config->not_rdns_table < 0 ? !ret : ret; -} - -static int -filter_check_rdns_regex(struct filter *filter, const char *key) -{ - int ret = 0; - - if (filter->config->rdns_regex == NULL) - return 0; - - if (table_match(filter->config->rdns_regex, K_REGEX, key) > 0) - ret = 1; - return filter->config->not_rdns_regex < 0 ? !ret : ret; -} - -static int -filter_check_src_table(struct filter *filter, enum table_service kind, const char *key) -{ - int ret = 0; - - if (filter->config->src_table == NULL) - return 0; - - if (table_match(filter->config->src_table, kind, key) > 0) - ret = 1; - return filter->config->not_src_table < 0 ? !ret : ret; -} - -static int -filter_check_src_regex(struct filter *filter, const char *key) -{ - int ret = 0; - - if (filter->config->src_regex == NULL) - return 0; - - if (table_match(filter->config->src_regex, K_REGEX, key) > 0) - ret = 1; - return filter->config->not_src_regex < 0 ? !ret : ret; -} - -static int -filter_check_helo_table(struct filter *filter, enum table_service kind, const char *key) -{ - int ret = 0; - - if (filter->config->helo_table == NULL) - return 0; - - if (table_match(filter->config->helo_table, kind, key) > 0) - ret = 1; - return filter->config->not_helo_table < 0 ? !ret : ret; -} - -static int -filter_check_helo_regex(struct filter *filter, const char *key) -{ - int ret = 0; - - if (filter->config->helo_regex == NULL) - return 0; - - if (table_match(filter->config->helo_regex, K_REGEX, key) > 0) - ret = 1; - return filter->config->not_helo_regex < 0 ? !ret : ret; -} - -static int -filter_check_auth(struct filter *filter, const char *username) -{ - int ret = 0; - - if (!filter->config->auth) - return 0; - - ret = username ? 1 : 0; - - return filter->config->not_auth < 0 ? !ret : ret; -} - -static int -filter_check_auth_table(struct filter *filter, enum table_service kind, const char *key) -{ - int ret = 0; - - if (filter->config->auth_table == NULL) - return 0; - - if (key && table_match(filter->config->auth_table, kind, key) > 0) - ret = 1; - - return filter->config->not_auth_table < 0 ? !ret : ret; -} - -static int -filter_check_auth_regex(struct filter *filter, const char *key) -{ - int ret = 0; - - if (filter->config->auth_regex == NULL) - return 0; - - if (key && table_match(filter->config->auth_regex, K_REGEX, key) > 0) - ret = 1; - return filter->config->not_auth_regex < 0 ? !ret : ret; -} - - -static int -filter_check_mail_from_table(struct filter *filter, enum table_service kind, const char *key) -{ - int ret = 0; - - if (filter->config->mail_from_table == NULL) - return 0; - - if (table_match(filter->config->mail_from_table, kind, key) > 0) - ret = 1; - return filter->config->not_mail_from_table < 0 ? !ret : ret; -} - -static int -filter_check_mail_from_regex(struct filter *filter, const char *key) -{ - int ret = 0; - - if (filter->config->mail_from_regex == NULL) - return 0; - - if (table_match(filter->config->mail_from_regex, K_REGEX, key) > 0) - ret = 1; - return filter->config->not_mail_from_regex < 0 ? !ret : ret; -} - -static int -filter_check_rcpt_to_table(struct filter *filter, enum table_service kind, const char *key) -{ - int ret = 0; - - if (filter->config->rcpt_to_table == NULL) - return 0; - - if (table_match(filter->config->rcpt_to_table, kind, key) > 0) - ret = 1; - return filter->config->not_rcpt_to_table < 0 ? !ret : ret; -} - -static int -filter_check_rcpt_to_regex(struct filter *filter, const char *key) -{ - int ret = 0; - - if (filter->config->rcpt_to_regex == NULL) - return 0; - - if (table_match(filter->config->rcpt_to_regex, K_REGEX, key) > 0) - ret = 1; - return filter->config->not_rcpt_to_regex < 0 ? !ret : ret; -} - -static int -filter_check_fcrdns(struct filter *filter, int fcrdns) -{ - int ret = 0; - - if (!filter->config->fcrdns) - return 0; - - ret = fcrdns == 1; - return filter->config->not_fcrdns < 0 ? !ret : ret; -} - -static int -filter_check_rdns(struct filter *filter, const char *hostname) -{ - int ret = 0; - struct netaddr netaddr; - - if (!filter->config->rdns) - return 0; - - /* this is a hack until smtp session properly deals with lack of rdns */ - ret = strcmp("", hostname); - if (ret == 0) - return filter->config->not_rdns < 0 ? !ret : ret; - - /* if text_to_netaddress succeeds, - * we don't have an rDNS so the filter should match - */ - ret = !text_to_netaddr(&netaddr, hostname); - return filter->config->not_rdns < 0 ? !ret : ret; -} - -static int -filter_builtins_notimpl(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return 0; -} - -static int -filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_t reqid) -{ - return filter_check_fcrdns(filter, fs->fcrdns) || - filter_check_rdns(filter, fs->rdns) || - filter_check_rdns_table(filter, K_DOMAIN, fs->rdns) || - filter_check_rdns_regex(filter, fs->rdns) || - filter_check_src_table(filter, K_NETADDR, ss_to_text(&fs->ss_src)) || - filter_check_src_regex(filter, ss_to_text(&fs->ss_src)) || - filter_check_helo_table(filter, K_DOMAIN, fs->helo) || - filter_check_helo_regex(filter, fs->helo) || - filter_check_auth(filter, fs->username) || - filter_check_auth_table(filter, K_STRING, fs->username) || - filter_check_auth_table(filter, K_CREDENTIALS, fs->username) || - filter_check_auth_regex(filter, fs->username) || - filter_check_mail_from_table(filter, K_MAILADDR, fs->mail_from) || - filter_check_mail_from_regex(filter, fs->mail_from); -} - -static int -filter_builtins_connect(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return filter_builtins_global(fs, filter, reqid); -} - -static int -filter_builtins_helo(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return filter_builtins_global(fs, filter, reqid); -} - -static int -filter_builtins_mail_from(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return filter_builtins_global(fs, filter, reqid); -} - -static int -filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return filter_builtins_global(fs, filter, reqid) || - filter_check_rcpt_to_table(filter, K_MAILADDR, param) || - filter_check_rcpt_to_regex(filter, param); -} - -static int -filter_builtins_data(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return filter_builtins_global(fs, filter, reqid); -} - -static int -filter_builtins_commit(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) -{ - return filter_builtins_global(fs, filter, reqid); -} - -static void -report_smtp_broadcast(uint64_t, const char *, struct timeval *, const char *, - const char *, ...) __attribute__((__format__ (printf, 5, 6))); - -void -lka_report_init(void) -{ - struct reporters *tailq; - size_t i; - - dict_init(&report_smtp_in); - dict_init(&report_smtp_out); - - for (i = 0; i < nitems(smtp_events); ++i) { - tailq = xcalloc(1, sizeof (struct reporters)); - TAILQ_INIT(tailq); - dict_xset(&report_smtp_in, smtp_events[i].event, tailq); - - tailq = xcalloc(1, sizeof (struct reporters)); - TAILQ_INIT(tailq); - dict_xset(&report_smtp_out, smtp_events[i].event, tailq); - } -} - -void -lka_report_register_hook(const char *name, const char *hook) -{ - struct dict *subsystem; - struct reporter_proc *rp; - struct reporters *tailq; - void *iter; - size_t i; - - if (strncmp(hook, "smtp-in|", 8) == 0) { - subsystem = &report_smtp_in; - hook += 8; - } - else if (strncmp(hook, "smtp-out|", 9) == 0) { - subsystem = &report_smtp_out; - hook += 9; - } - else - fatalx("Invalid message direction: %s", hook); - - if (strcmp(hook, "*") == 0) { - iter = NULL; - while (dict_iter(subsystem, &iter, NULL, (void **)&tailq)) { - rp = xcalloc(1, sizeof *rp); - rp->name = xstrdup(name); - TAILQ_INSERT_TAIL(tailq, rp, entries); - } - return; - } - - for (i = 0; i < nitems(smtp_events); i++) - if (strcmp(hook, smtp_events[i].event) == 0) - break; - if (i == nitems(smtp_events)) - fatalx("Unrecognized report name: %s", hook); - - tailq = dict_get(subsystem, hook); - rp = xcalloc(1, sizeof *rp); - rp->name = xstrdup(name); - TAILQ_INSERT_TAIL(tailq, rp, entries); -} - -static void -report_smtp_broadcast(uint64_t reqid, const char *direction, struct timeval *tv, const char *event, - const char *format, ...) -{ - va_list ap; - struct dict *d; - struct reporters *tailq; - struct reporter_proc *rp; - - if (strcmp("smtp-in", direction) == 0) - d = &report_smtp_in; - - else if (strcmp("smtp-out", direction) == 0) - d = &report_smtp_out; - - else - fatalx("unexpected direction: %s", direction); - - tailq = dict_xget(d, event); - TAILQ_FOREACH(rp, tailq, entries) { - if (!lka_filter_proc_in_session(reqid, rp->name)) - continue; - - va_start(ap, format); - if (io_printf(lka_proc_get_io(rp->name), - "report|%s|%lld.%06ld|%s|%s|%016"PRIx64"%s", - PROTOCOL_VERSION, (long long int)tv->tv_sec, tv->tv_usec, direction, - event, reqid, format[0] != '\n' ? "|" : "") == -1 || - io_vprintf(lka_proc_get_io(rp->name), format, ap) == -1) - fatalx("failed to write to processor"); - va_end(ap); - } -} - -void -lka_report_smtp_link_connect(const char *direction, struct timeval *tv, uint64_t reqid, const char *rdns, - int fcrdns, - const struct sockaddr_storage *ss_src, - const struct sockaddr_storage *ss_dest) -{ - struct filter_session *fs; - char src[NI_MAXHOST + 5]; - char dest[NI_MAXHOST + 5]; - uint16_t src_port = 0; - uint16_t dest_port = 0; - const char *fcrdns_str; - - if (ss_src->ss_family == AF_INET) - src_port = ntohs(((const struct sockaddr_in *)ss_src)->sin_port); - else if (ss_src->ss_family == AF_INET6) - src_port = ntohs(((const struct sockaddr_in6 *)ss_src)->sin6_port); - - if (ss_dest->ss_family == AF_INET) - dest_port = ntohs(((const struct sockaddr_in *)ss_dest)->sin_port); - else if (ss_dest->ss_family == AF_INET6) - dest_port = ntohs(((const struct sockaddr_in6 *)ss_dest)->sin6_port); - - if (strcmp(ss_to_text(ss_src), "local") == 0) { - (void)snprintf(src, sizeof src, "unix:%s", SMTPD_SOCKET); - (void)snprintf(dest, sizeof dest, "unix:%s", SMTPD_SOCKET); - } else { - (void)snprintf(src, sizeof src, "%s:%d", ss_to_text(ss_src), src_port); - (void)snprintf(dest, sizeof dest, "%s:%d", ss_to_text(ss_dest), dest_port); - } - - switch (fcrdns) { - case 1: - fcrdns_str = "pass"; - break; - case 0: - fcrdns_str = "fail"; - break; - default: - fcrdns_str = "error"; - break; - } - - fs = tree_xget(&sessions, reqid); - fs->rdns = xstrdup(rdns); - fs->fcrdns = fcrdns; - fs->ss_src = *ss_src; - fs->ss_dest = *ss_dest; - - report_smtp_broadcast(reqid, direction, tv, "link-connect", - "%s|%s|%s|%s\n", rdns, fcrdns_str, src, dest); -} - -void -lka_report_smtp_link_disconnect(const char *direction, struct timeval *tv, uint64_t reqid) -{ - report_smtp_broadcast(reqid, direction, tv, "link-disconnect", "\n"); -} - -void -lka_report_smtp_link_greeting(const char *direction, uint64_t reqid, - struct timeval *tv, const char *domain) -{ - report_smtp_broadcast(reqid, direction, tv, "link-greeting", "%s\n", - domain); -} - -void -lka_report_smtp_link_auth(const char *direction, struct timeval *tv, uint64_t reqid, - const char *username, const char *result) -{ - struct filter_session *fs; - - if (strcmp(result, "pass") == 0) { - fs = tree_xget(&sessions, reqid); - fs->username = xstrdup(username); - } - report_smtp_broadcast(reqid, direction, tv, "link-auth", "%s|%s\n", - username, result); -} - -void -lka_report_smtp_link_identify(const char *direction, struct timeval *tv, - uint64_t reqid, const char *method, const char *heloname) -{ - report_smtp_broadcast(reqid, direction, tv, "link-identify", "%s|%s\n", - method, heloname); -} - -void -lka_report_smtp_link_tls(const char *direction, struct timeval *tv, uint64_t reqid, const char *ciphers) -{ - report_smtp_broadcast(reqid, direction, tv, "link-tls", "%s\n", - ciphers); -} - -void -lka_report_smtp_tx_reset(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) -{ - report_smtp_broadcast(reqid, direction, tv, "tx-reset", "%08x\n", - msgid); -} - -void -lka_report_smtp_tx_begin(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) -{ - report_smtp_broadcast(reqid, direction, tv, "tx-begin", "%08x\n", - msgid); -} - -void -lka_report_smtp_tx_mail(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok) -{ - const char *result; - - switch (ok) { - case 1: - result = "ok"; - break; - case 0: - result = "permfail"; - break; - default: - result = "tempfail"; - break; - } - report_smtp_broadcast(reqid, direction, tv, "tx-mail", "%08x|%s|%s\n", - msgid, result, address); -} - -void -lka_report_smtp_tx_rcpt(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok) -{ - const char *result; - - switch (ok) { - case 1: - result = "ok"; - break; - case 0: - result = "permfail"; - break; - default: - result = "tempfail"; - break; - } - report_smtp_broadcast(reqid, direction, tv, "tx-rcpt", "%08x|%s|%s\n", - msgid, result, address); -} - -void -lka_report_smtp_tx_envelope(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, uint64_t evpid) -{ - report_smtp_broadcast(reqid, direction, tv, "tx-envelope", - "%08x|%016"PRIx64"\n", msgid, evpid); -} - -void -lka_report_smtp_tx_data(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, int ok) -{ - const char *result; - - switch (ok) { - case 1: - result = "ok"; - break; - case 0: - result = "permfail"; - break; - default: - result = "tempfail"; - break; - } - report_smtp_broadcast(reqid, direction, tv, "tx-data", "%08x|%s\n", - msgid, result); -} - -void -lka_report_smtp_tx_commit(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, size_t msgsz) -{ - report_smtp_broadcast(reqid, direction, tv, "tx-commit", "%08x|%zd\n", - msgid, msgsz); -} - -void -lka_report_smtp_tx_rollback(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) -{ - report_smtp_broadcast(reqid, direction, tv, "tx-rollback", "%08x\n", - msgid); -} - -void -lka_report_smtp_protocol_client(const char *direction, struct timeval *tv, uint64_t reqid, const char *command) -{ - report_smtp_broadcast(reqid, direction, tv, "protocol-client", "%s\n", - command); -} - -void -lka_report_smtp_protocol_server(const char *direction, struct timeval *tv, uint64_t reqid, const char *response) -{ - report_smtp_broadcast(reqid, direction, tv, "protocol-server", "%s\n", - response); -} - -void -lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint64_t reqid, - int phase, int response, const char *param) -{ - const char *phase_name; - const char *response_name; - - switch (phase) { - case FILTER_CONNECT: - phase_name = "connected"; - break; - case FILTER_HELO: - phase_name = "helo"; - break; - case FILTER_EHLO: - phase_name = "ehlo"; - break; - case FILTER_STARTTLS: - phase_name = "tls"; - break; - case FILTER_AUTH: - phase_name = "auth"; - break; - case FILTER_MAIL_FROM: - phase_name = "mail-from"; - break; - case FILTER_RCPT_TO: - phase_name = "rcpt-to"; - break; - case FILTER_DATA: - phase_name = "data"; - break; - case FILTER_DATA_LINE: - phase_name = "data-line"; - break; - case FILTER_RSET: - phase_name = "rset"; - break; - case FILTER_QUIT: - phase_name = "quit"; - break; - case FILTER_NOOP: - phase_name = "noop"; - break; - case FILTER_HELP: - phase_name = "help"; - break; - case FILTER_WIZ: - phase_name = "wiz"; - break; - case FILTER_COMMIT: - phase_name = "commit"; - break; - default: - phase_name = ""; - } - - switch (response) { - case FILTER_PROCEED: - response_name = "proceed"; - break; - case FILTER_JUNK: - response_name = "junk"; - break; - case FILTER_REWRITE: - response_name = "rewrite"; - break; - case FILTER_REJECT: - response_name = "reject"; - break; - case FILTER_DISCONNECT: - response_name = "disconnect"; - break; - default: - response_name = ""; - } - - report_smtp_broadcast(reqid, direction, tv, "filter-response", - "%s|%s%s%s\n", phase_name, response_name, param ? "|" : "", - param ? param : ""); -} - -void -lka_report_smtp_timeout(const char *direction, struct timeval *tv, uint64_t reqid) -{ - report_smtp_broadcast(reqid, direction, tv, "timeout", "\n"); -} - -void -lka_report_filter_report(uint64_t reqid, const char *name, int builtin, - const char *direction, struct timeval *tv, const char *message) -{ - report_smtp_broadcast(reqid, direction, tv, "filter-report", - "%s|%s|%s\n", builtin ? "builtin" : "proc", - name, message); -} - -void -lka_report_proc(const char *name, const char *line) -{ - char buffer[LINE_MAX]; - struct timeval tv; - char *ep, *sp, *direction; - uint64_t reqid; - - if (strlcpy(buffer, line + 7, sizeof(buffer)) >= sizeof(buffer)) - fatalx("Invalid report: line too long: %s", line); - - errno = 0; - tv.tv_sec = strtoll(buffer, &ep, 10); - if (ep[0] != '.' || errno != 0) - fatalx("Invalid report: invalid time: %s", line); - sp = ep + 1; - tv.tv_usec = strtol(sp, &ep, 10); - if (ep[0] != '|' || errno != 0) - fatalx("Invalid report: invalid time: %s", line); - if (ep - sp != 6) - fatalx("Invalid report: invalid time: %s", line); - - direction = ep + 1; - if (strncmp(direction, "smtp-in|", 8) == 0) { - direction[7] = '\0'; - direction += 7; -#if 0 - } else if (strncmp(direction, "smtp-out|", 9) == 0) { - direction[8] = '\0'; - direction += 8; -#endif - } else - fatalx("Invalid report: invalid direction: %s", line); - - reqid = strtoull(sp, &ep, 16); - if (ep[0] != '|' || errno != 0) - fatalx("Invalid report: invalid reqid: %s", line); - sp = ep + 1; - - lka_report_filter_report(reqid, name, 0, direction, &tv, sp); -} diff --git a/smtpd/lka_session.c b/smtpd/lka_session.c deleted file mode 100644 index 999e01d6..00000000 --- a/smtpd/lka_session.c +++ /dev/null @@ -1,556 +0,0 @@ -/* $OpenBSD: lka_session.c,v 1.93 2019/09/20 17:46:05 gilles Exp $ */ - -/* - * Copyright (c) 2011 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define EXPAND_DEPTH 10 - -#define F_WAITING 0x01 - -struct lka_session { - uint64_t id; /* given by smtp */ - - TAILQ_HEAD(, envelope) deliverylist; - struct expand expand; - - int flags; - int error; - const char *errormsg; - struct envelope envelope; - struct xnodes nodes; - /* waiting for fwdrq */ - struct rule *rule; - struct expandnode *node; -}; - -static void lka_expand(struct lka_session *, struct rule *, - struct expandnode *); -static void lka_submit(struct lka_session *, struct rule *, - struct expandnode *); -static void lka_resume(struct lka_session *); - -static int init; -static struct tree sessions; - -void -lka_session(uint64_t id, struct envelope *envelope) -{ - struct lka_session *lks; - struct expandnode xn; - - if (init == 0) { - init = 1; - tree_init(&sessions); - } - - lks = xcalloc(1, sizeof(*lks)); - lks->id = id; - RB_INIT(&lks->expand.tree); - TAILQ_INIT(&lks->deliverylist); - tree_xset(&sessions, lks->id, lks); - - lks->envelope = *envelope; - - TAILQ_INIT(&lks->nodes); - memset(&xn, 0, sizeof xn); - xn.type = EXPAND_ADDRESS; - xn.u.mailaddr = lks->envelope.rcpt; - lks->expand.parent = NULL; - lks->expand.rule = NULL; - lks->expand.queue = &lks->nodes; - expand_insert(&lks->expand, &xn); - lka_resume(lks); -} - -void -lka_session_forward_reply(struct forward_req *fwreq, int fd) -{ - struct lka_session *lks; - struct dispatcher *dsp; - struct rule *rule; - struct expandnode *xn; - int ret; - - lks = tree_xget(&sessions, fwreq->id); - xn = lks->node; - rule = lks->rule; - - lks->flags &= ~F_WAITING; - - switch (fwreq->status) { - case 0: - /* permanent failure while lookup ~/.forward */ - log_trace(TRACE_EXPAND, "expand: ~/.forward failed for user %s", - fwreq->user); - lks->error = LKA_PERMFAIL; - break; - case 1: - if (fd == -1) { - dsp = dict_get(env->sc_dispatchers, lks->rule->dispatcher); - if (dsp->u.local.forward_only) { - log_trace(TRACE_EXPAND, "expand: no .forward " - "for user %s on forward-only rule", fwreq->user); - lks->error = LKA_TEMPFAIL; - } - else if (dsp->u.local.expand_only) { - log_trace(TRACE_EXPAND, "expand: no .forward " - "for user %s and no default action on rule", fwreq->user); - lks->error = LKA_PERMFAIL; - } - else { - log_trace(TRACE_EXPAND, "expand: no .forward for " - "user %s, just deliver", fwreq->user); - lka_submit(lks, rule, xn); - } - } - else { - dsp = dict_get(env->sc_dispatchers, rule->dispatcher); - - /* expand for the current user and rule */ - lks->expand.rule = rule; - lks->expand.parent = xn; - - /* forwards_get() will close the descriptor no matter what */ - ret = forwards_get(fd, &lks->expand); - if (ret == -1) { - log_trace(TRACE_EXPAND, "expand: temporary " - "forward error for user %s", fwreq->user); - lks->error = LKA_TEMPFAIL; - } - else if (ret == 0) { - if (dsp->u.local.forward_only) { - log_trace(TRACE_EXPAND, "expand: empty .forward " - "for user %s on forward-only rule", fwreq->user); - lks->error = LKA_TEMPFAIL; - } - else if (dsp->u.local.expand_only) { - log_trace(TRACE_EXPAND, "expand: empty .forward " - "for user %s and no default action on rule", fwreq->user); - lks->error = LKA_PERMFAIL; - } - else { - log_trace(TRACE_EXPAND, "expand: empty .forward " - "for user %s, just deliver", fwreq->user); - lka_submit(lks, rule, xn); - } - } - } - break; - default: - /* temporary failure while looking up ~/.forward */ - lks->error = LKA_TEMPFAIL; - } - - if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) - lks->errormsg = "424 4.2.4 Mailing list expansion problem"; - if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) - lks->errormsg = "524 5.2.4 Mailing list expansion problem"; - - lka_resume(lks); -} - -static void -lka_resume(struct lka_session *lks) -{ - struct envelope *ep; - struct expandnode *xn; - - if (lks->error) - goto error; - - /* pop next node and expand it */ - while ((xn = TAILQ_FIRST(&lks->nodes))) { - TAILQ_REMOVE(&lks->nodes, xn, tq_entry); - lka_expand(lks, xn->rule, xn); - if (lks->flags & F_WAITING) - return; - if (lks->error) - goto error; - } - - /* delivery list is empty, reject */ - if (TAILQ_FIRST(&lks->deliverylist) == NULL) { - log_trace(TRACE_EXPAND, "expand: lka_done: expanded to empty " - "delivery list"); - lks->error = LKA_PERMFAIL; - lks->errormsg = "524 5.2.4 Mailing list expansion problem"; - } - error: - if (lks->error) { - m_create(p_pony, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); - m_add_id(p_pony, lks->id); - m_add_int(p_pony, lks->error); - - if (lks->errormsg) - m_add_string(p_pony, lks->errormsg); - else { - if (lks->error == LKA_PERMFAIL) - m_add_string(p_pony, "550 Invalid recipient"); - else if (lks->error == LKA_TEMPFAIL) - m_add_string(p_pony, "451 Temporary failure"); - } - - m_close(p_pony); - while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { - TAILQ_REMOVE(&lks->deliverylist, ep, entry); - free(ep); - } - } - else { - /* Process the delivery list and submit envelopes to queue */ - while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { - TAILQ_REMOVE(&lks->deliverylist, ep, entry); - m_create(p_queue, IMSG_LKA_ENVELOPE_SUBMIT, 0, 0, -1); - m_add_id(p_queue, lks->id); - m_add_envelope(p_queue, ep); - m_close(p_queue); - free(ep); - } - - m_create(p_queue, IMSG_LKA_ENVELOPE_COMMIT, 0, 0, -1); - m_add_id(p_queue, lks->id); - m_close(p_queue); - } - - expand_clear(&lks->expand); - tree_xpop(&sessions, lks->id); - free(lks); -} - -static void -lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) -{ - struct forward_req fwreq; - struct envelope ep; - struct expandnode node; - struct mailaddr maddr; - struct dispatcher *dsp; - struct table *userbase; - int r; - union lookup lk; - char *tag; - const char *srs_decoded; - - if (xn->depth >= EXPAND_DEPTH) { - log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep."); - lks->error = LKA_PERMFAIL; - lks->errormsg = "524 5.2.4 Mailing list expansion problem"; - return; - } - - switch (xn->type) { - case EXPAND_INVALID: - case EXPAND_INCLUDE: - fatalx("lka_expand: unexpected type"); - break; - - case EXPAND_ADDRESS: - - log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s " - "[depth=%d]", - xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth); - - - ep = lks->envelope; - ep.dest = xn->u.mailaddr; - if (xn->parent) /* nodes with parent are forward addresses */ - ep.flags |= EF_INTERNAL; - - /* handle SRS */ - if (env->sc_srs_key != NULL && - ep.sender.user[0] == '\0' && - (strncasecmp(ep.rcpt.user, "SRS0=", 5) == 0 || - strncasecmp(ep.rcpt.user, "SRS1=", 5) == 0)) { - srs_decoded = srs_decode(mailaddr_to_text(&ep.rcpt)); - if (srs_decoded && - text_to_mailaddr(&ep.rcpt, srs_decoded)) { - /* flag envelope internal and override rcpt */ - ep.flags |= EF_INTERNAL; - xn->u.mailaddr = ep.rcpt; - lks->envelope = ep; - } - else { - log_warn("SRS failed to decode: %s", - mailaddr_to_text(&ep.rcpt)); - } - } - - /* Pass the node through the ruleset */ - rule = ruleset_match(&ep); - if (rule == NULL || rule->reject) { - lks->error = (errno == EAGAIN) ? - LKA_TEMPFAIL : LKA_PERMFAIL; - break; - } - - dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); - if (dsp->type == DISPATCHER_REMOTE) { - lka_submit(lks, rule, xn); - } - else if (dsp->u.local.table_virtual) { - /* expand */ - lks->expand.rule = rule; - lks->expand.parent = xn; - - /* temporary replace the mailaddr with a copy where - * we eventually strip the '+'-part before lookup. - */ - maddr = xn->u.mailaddr; - xlowercase(maddr.user, xn->u.mailaddr.user, - sizeof maddr.user); - r = aliases_virtual_get(&lks->expand, &maddr); - if (r == -1) { - lks->error = LKA_TEMPFAIL; - log_trace(TRACE_EXPAND, "expand: lka_expand: " - "error in virtual alias lookup"); - } - else if (r == 0) { - lks->error = LKA_PERMFAIL; - log_trace(TRACE_EXPAND, "expand: lka_expand: " - "no aliases for virtual"); - } - if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) - lks->errormsg = "424 4.2.4 Mailing list expansion problem"; - if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) - lks->errormsg = "524 5.2.4 Mailing list expansion problem"; - } - else { - lks->expand.rule = rule; - lks->expand.parent = xn; - xn->rule = rule; - - memset(&node, 0, sizeof node); - node.type = EXPAND_USERNAME; - xlowercase(node.u.user, xn->u.mailaddr.user, - sizeof node.u.user); - expand_insert(&lks->expand, &node); - } - break; - - case EXPAND_USERNAME: - log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s " - "[depth=%d, sameuser=%d]", - xn->u.user, xn->depth, xn->sameuser); - - /* expand aliases with the given rule */ - dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); - - lks->expand.rule = rule; - lks->expand.parent = xn; - - if (!xn->sameuser && - (dsp->u.local.table_alias || dsp->u.local.table_virtual)) { - if (dsp->u.local.table_alias) - r = aliases_get(&lks->expand, xn->u.user); - if (dsp->u.local.table_virtual) - r = aliases_virtual_get(&lks->expand, &xn->u.mailaddr); - if (r == -1) { - log_trace(TRACE_EXPAND, "expand: lka_expand: " - "error in alias lookup"); - lks->error = LKA_TEMPFAIL; - if (lks->errormsg == NULL) - lks->errormsg = "424 4.2.4 Mailing list expansion problem"; - } - if (r) - break; - } - - /* gilles+hackers@ -> gilles@ */ - if ((tag = strchr(xn->u.user, *env->sc_subaddressing_delim)) != NULL) { - *tag++ = '\0'; - (void)strlcpy(xn->subaddress, tag, sizeof xn->subaddress); - } - - userbase = table_find(env, dsp->u.local.table_userbase); - r = table_lookup(userbase, K_USERINFO, xn->u.user, &lk); - if (r == -1) { - log_trace(TRACE_EXPAND, "expand: lka_expand: " - "backend error while searching user"); - lks->error = LKA_TEMPFAIL; - break; - } - if (r == 0) { - log_trace(TRACE_EXPAND, "expand: lka_expand: " - "user-part does not match system user"); - lks->error = LKA_PERMFAIL; - break; - } - xn->realuser = 1; - - if (xn->sameuser && xn->parent->forwarded) { - log_trace(TRACE_EXPAND, "expand: lka_expand: same " - "user, submitting"); - lka_submit(lks, rule, xn); - break; - } - - /* no aliases found, query forward file */ - lks->rule = rule; - lks->node = xn; - xn->forwarded = 1; - - memset(&fwreq, 0, sizeof(fwreq)); - fwreq.id = lks->id; - (void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user)); - (void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory)); - fwreq.uid = lk.userinfo.uid; - fwreq.gid = lk.userinfo.gid; - - m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1, - &fwreq, sizeof(fwreq)); - lks->flags |= F_WAITING; - break; - - case EXPAND_FILENAME: - dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); - if (dsp->u.local.forward_only) { - log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule"); - lks->error = LKA_TEMPFAIL; - break; - } - log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s " - "[depth=%d]", xn->u.buffer, xn->depth); - lka_submit(lks, rule, xn); - break; - - case EXPAND_ERROR: - dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); - if (dsp->u.local.forward_only) { - log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule"); - lks->error = LKA_TEMPFAIL; - break; - } - log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s " - "[depth=%d]", xn->u.buffer, xn->depth); - if (xn->u.buffer[0] == '4') - lks->error = LKA_TEMPFAIL; - else if (xn->u.buffer[0] == '5') - lks->error = LKA_PERMFAIL; - lks->errormsg = xn->u.buffer; - break; - - case EXPAND_FILTER: - dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); - if (dsp->u.local.forward_only) { - log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule"); - lks->error = LKA_TEMPFAIL; - break; - } - log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s " - "[depth=%d]", xn->u.buffer, xn->depth); - lka_submit(lks, rule, xn); - break; - } -} - -static struct expandnode * -lka_find_ancestor(struct expandnode *xn, enum expand_type type) -{ - while (xn && (xn->type != type)) - xn = xn->parent; - if (xn == NULL) { - log_warnx("warn: lka_find_ancestor: no ancestors of type %d", - type); - fatalx(NULL); - } - return (xn); -} - -static void -lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn) -{ - struct envelope *ep; - struct dispatcher *dsp; - const char *user; - const char *format; - - ep = xmemdup(&lks->envelope, sizeof *ep); - (void)strlcpy(ep->dispatcher, rule->dispatcher, sizeof ep->dispatcher); - - dsp = dict_xget(env->sc_dispatchers, ep->dispatcher); - - switch (dsp->type) { - case DISPATCHER_REMOTE: - if (xn->type != EXPAND_ADDRESS) - fatalx("lka_deliver: expect address"); - ep->type = D_MTA; - ep->dest = xn->u.mailaddr; - break; - - case DISPATCHER_BOUNCE: - case DISPATCHER_LOCAL: - if (xn->type != EXPAND_USERNAME && - xn->type != EXPAND_FILENAME && - xn->type != EXPAND_FILTER) - fatalx("lka_deliver: wrong type: %d", xn->type); - - ep->type = D_MDA; - ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr; - if (xn->type == EXPAND_USERNAME) { - (void)strlcpy(ep->mda_user, xn->u.user, sizeof(ep->mda_user)); - (void)strlcpy(ep->mda_subaddress, xn->subaddress, sizeof(ep->mda_subaddress)); - } - else { - user = !xn->parent->realuser ? - SMTPD_USER : - xn->parent->u.user; - (void)strlcpy(ep->mda_user, user, sizeof (ep->mda_user)); - - /* this battle needs to be fought ... */ - if (xn->type == EXPAND_FILTER && - strcmp(ep->mda_user, SMTPD_USER) == 0) - log_warnx("commands executed from aliases " - "run with %s privileges", SMTPD_USER); - - if (xn->type == EXPAND_FILENAME) - format = PATH_LIBEXEC"/mail.mboxfile -f %%{mbox.from} %s"; - else if (xn->type == EXPAND_FILTER) - format = "%s"; - (void)snprintf(ep->mda_exec, sizeof(ep->mda_exec), - format, xn->u.buffer); - } - break; - } - - TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry); -} diff --git a/smtpd/log.c b/smtpd/log.c deleted file mode 100644 index 14f681e3..00000000 --- a/smtpd/log.c +++ /dev/null @@ -1,220 +0,0 @@ -/* $OpenBSD: log.c,v 1.20 2017/03/21 12:06:56 bluhm Exp $ */ - -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include -#include - -static int debug; -static int verbose; -const char *log_procname; - -void log_init(int, int); -void log_procinit(const char *); -void log_setverbose(int); -int log_getverbose(void); -void log_warn(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_warnx(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_info(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_debug(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void logit(int, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -void vlog(int, const char *, va_list) - __attribute__((__format__ (printf, 2, 0))); -__dead void fatal(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -__dead void fatalx(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); - -void -log_init(int n_debug, int facility) -{ - extern char *__progname; - - debug = n_debug; - verbose = n_debug; - log_procinit(__progname); - - if (!debug) - openlog(__progname, LOG_PID | LOG_NDELAY, facility); - - tzset(); -} - -void -log_procinit(const char *procname) -{ - if (procname != NULL) - log_procname = procname; -} - -void -log_setverbose(int v) -{ - verbose = v; -} - -int -log_getverbose(void) -{ - return (verbose); -} - -void -logit(int pri, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vlog(pri, fmt, ap); - va_end(ap); -} - -void -vlog(int pri, const char *fmt, va_list ap) -{ - char *nfmt; - int saved_errno = errno; - - if (debug) { - /* best effort in out of mem situations */ - if (asprintf(&nfmt, "%s\n", fmt) == -1) { - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - } else { - vfprintf(stderr, nfmt, ap); - free(nfmt); - } - fflush(stderr); - } else - vsyslog(pri, fmt, ap); - - errno = saved_errno; -} - -void -log_warn(const char *emsg, ...) -{ - char *nfmt; - va_list ap; - int saved_errno = errno; - - /* best effort to even work in out of memory situations */ - if (emsg == NULL) - logit(LOG_ERR, "%s", strerror(saved_errno)); - else { - va_start(ap, emsg); - - if (asprintf(&nfmt, "%s: %s", emsg, - strerror(saved_errno)) == -1) { - /* we tried it... */ - vlog(LOG_ERR, emsg, ap); - logit(LOG_ERR, "%s", strerror(saved_errno)); - } else { - vlog(LOG_ERR, nfmt, ap); - free(nfmt); - } - va_end(ap); - } - - errno = saved_errno; -} - -void -log_warnx(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vlog(LOG_ERR, emsg, ap); - va_end(ap); -} - -void -log_info(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vlog(LOG_INFO, emsg, ap); - va_end(ap); -} - -void -log_debug(const char *emsg, ...) -{ - va_list ap; - - if (verbose > 1) { - va_start(ap, emsg); - vlog(LOG_DEBUG, emsg, ap); - va_end(ap); - } -} - -static void -vfatalc(int code, const char *emsg, va_list ap) -{ - static char s[BUFSIZ]; - const char *sep; - - if (emsg != NULL) { - (void)vsnprintf(s, sizeof(s), emsg, ap); - sep = ": "; - } else { - s[0] = '\0'; - sep = ""; - } - if (code) - logit(LOG_CRIT, "%s: %s%s%s", - log_procname, s, sep, strerror(code)); - else - logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); -} - -void -fatal(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vfatalc(errno, emsg, ap); - va_end(ap); - exit(1); -} - -void -fatalx(const char *emsg, ...) -{ - va_list ap; - - va_start(ap, emsg); - vfatalc(0, emsg, ap); - va_end(ap); - exit(1); -} diff --git a/smtpd/log.h b/smtpd/log.h deleted file mode 100644 index 81d0973c..00000000 --- a/smtpd/log.h +++ /dev/null @@ -1,52 +0,0 @@ -/* $OpenBSD: log.h,v 1.8 2018/04/26 20:57:59 eric Exp $ */ - -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef LOG_H -#define LOG_H - -#include "openbsd-compat.h" - -#include - -#include -#ifdef HAVE_SYS_CDEFS_H -#include -#endif - -void log_init(int, int); -void log_procinit(const char *); -void log_setverbose(int); -int log_getverbose(void); -void log_warn(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_warnx(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_info(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void log_debug(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -void logit(int, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -void vlog(int, const char *, va_list) - __attribute__((__format__ (printf, 2, 0))); -__dead void fatal(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); -__dead void fatalx(const char *, ...) - __attribute__((__format__ (printf, 1, 2))); - -#endif /* LOG_H */ diff --git a/smtpd/mail.lmtp.8 b/smtpd/mail.lmtp.8 deleted file mode 100644 index 98dee00d..00000000 --- a/smtpd/mail.lmtp.8 +++ /dev/null @@ -1,55 +0,0 @@ -.\" $OpenBSD: mail.lmtp.8,v 1.1 2017/02/14 15:16:34 gilles Exp $ -.\" -.\" Copyright (c) 2017 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: February 14 2017 $ -.Dt MAIL.LMTP 8 -.Os -.Sh NAME -.Nm mail.lmtp -.Nd deliver mail through LMTP -.Sh SYNOPSIS -.Nm mail.lmtp -.Op Fl d Ar destination -.Op Fl f Ar from -.Op Fl l Ar lhlo -.Ar user ... -.Sh DESCRIPTION -.Nm -reads the standard input up to an end-of-file and delivers it to -an LMTP server for each -.Ar user Ns 's -address. -The -.Ar user -must be a valid user name or email address. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl d Ar destination -Specify the destination LMTP address. -.It Fl f Ar from -Specify the sender's name or email address. -.It Fl l Ar lhlo -Specify the LHLO argument used in the LMTP session. -By default, -.Nm mail.lmtp -will default to "localhost". -.El -.Sh EXIT STATUS -.Ex -std mail.lmtp -.Sh SEE ALSO -.Xr mail 1 , -.Xr smtpd 8 diff --git a/smtpd/mail.lmtp.c b/smtpd/mail.lmtp.c deleted file mode 100644 index 90b89990..00000000 --- a/smtpd/mail.lmtp.c +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (c) 2017 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -enum phase { - PHASE_BANNER, - PHASE_HELO, - PHASE_MAILFROM, - PHASE_RCPTTO, - PHASE_DATA, - PHASE_EOM, - PHASE_QUIT -}; - -struct session { - const char *lhlo; - const char *mailfrom; - char *rcptto; - - char **rcpts; - int n_rcpts; -}; - -static int lmtp_connect(const char *); -static void lmtp_engine(int, struct session *); -static void stream_file(FILE *); - -int -main(int argc, char *argv[]) -{ - int ch; - int conn; - const char *destination = "localhost"; - struct session session; - - if (! geteuid()) - errx(EX_TEMPFAIL, "mail.lmtp: may not be executed as root"); - - session.lhlo = "localhost"; - session.mailfrom = getenv("SENDER"); - session.rcptto = NULL; - - while ((ch = getopt(argc, argv, "d:l:f:ru")) != -1) { - switch (ch) { - case 'd': - destination = optarg; - break; - case 'l': - session.lhlo = optarg; - break; - case 'f': - session.mailfrom = optarg; - break; - - case 'r': - session.rcptto = getenv("RECIPIENT"); - break; - - case 'u': - session.rcptto = getenv("USER"); - break; - - default: - break; - } - } - argc -= optind; - argv += optind; - - if (session.mailfrom == NULL) - errx(EX_TEMPFAIL, "sender must be specified with -f"); - - if (argc == 0 && session.rcptto == NULL) - errx(EX_TEMPFAIL, "no recipient was specified"); - - if (session.rcptto) { - session.rcpts = &session.rcptto; - session.n_rcpts = 1; - } - else { - session.rcpts = argv; - session.n_rcpts = argc; - } - - conn = lmtp_connect(destination); - lmtp_engine(conn, &session); - - return (0); -} - -static int -lmtp_connect_inet(const char *destination) -{ - struct addrinfo hints, *res, *res0; - char *destcopy = NULL; - const char *hostname = NULL; - const char *servname = NULL; - const char *cause = NULL; - char *p; - int n, s = -1, save_errno; - - if ((destcopy = strdup(destination)) == NULL) - err(EX_TEMPFAIL, NULL); - - servname = "25"; - hostname = destcopy; - p = destcopy; - if (*p == '[') { - if ((p = strchr(destcopy, ']')) == NULL) - errx(EX_TEMPFAIL, "inet: invalid address syntax"); - - /* remove [ and ] */ - *p = '\0'; - hostname++; - if (strncasecmp(hostname, "IPv6:", 5) == 0) - hostname += 5; - - /* extract port if any */ - switch (*(p+1)) { - case ':': - servname = p+2; - break; - case '\0': - break; - default: - errx(EX_TEMPFAIL, "inet: invalid address syntax"); - } - } - else if ((p = strchr(destcopy, ':')) != NULL) { - *p++ = '\0'; - servname = p; - } - - memset(&hints, 0, sizeof hints); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_NUMERICSERV; - n = getaddrinfo(hostname, servname, &hints, &res0); - if (n) - errx(EX_TEMPFAIL, "inet: %s", gai_strerror(n)); - - for (res = res0; res; res = res->ai_next) { - s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - if (s == -1) { - cause = "socket"; - continue; - } - - if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { - cause = "connect"; - save_errno = errno; - close(s); - errno = save_errno; - s = -1; - continue; - } - break; - } - - freeaddrinfo(res0); - if (s == -1) - errx(EX_TEMPFAIL, "%s", cause); - - free(destcopy); - return s; -} - -static int -lmtp_connect_unix(const char *destination) -{ - struct sockaddr_un addr; - int s; - - if (*destination != '/') - errx(EX_TEMPFAIL, "unix: path must be absolute"); - - if ((s = socket(PF_LOCAL, SOCK_STREAM, 0)) == -1) - err(EX_TEMPFAIL, NULL); - - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - if (strlcpy(addr.sun_path, destination, sizeof addr.sun_path) - >= sizeof addr.sun_path) - errx(EX_TEMPFAIL, "unix: socket path is too long"); - - if (connect(s, (struct sockaddr *)&addr, sizeof addr) == -1) - err(EX_TEMPFAIL, "connect"); - - return s; -} - -static int -lmtp_connect(const char *destination) -{ - if (destination[0] == '/') - return lmtp_connect_unix(destination); - return lmtp_connect_inet(destination); -} - -static void -lmtp_engine(int fd_read, struct session *session) -{ - int fd_write = 0; - FILE *file_read = 0; - FILE *file_write = 0; - char *line = NULL; - size_t linesize = 0; - ssize_t linelen; - enum phase phase = PHASE_BANNER; - - if ((fd_write = dup(fd_read)) == -1) - err(EX_TEMPFAIL, "dup"); - - if ((file_read = fdopen(fd_read, "r")) == NULL) - err(EX_TEMPFAIL, "fdopen"); - - if ((file_write = fdopen(fd_write, "w")) == NULL) - err(EX_TEMPFAIL, "fdopen"); - - do { - fflush(file_write); - - if ((linelen = getline(&line, &linesize, file_read)) == -1) { - if (ferror(file_read)) - err(EX_TEMPFAIL, "getline"); - else - errx(EX_TEMPFAIL, "unexpected EOF from LMTP server"); - } - line[strcspn(line, "\n")] = '\0'; - line[strcspn(line, "\r")] = '\0'; - - if (linelen < 4 || - !isdigit((unsigned char)line[0]) || - !isdigit((unsigned char)line[1]) || - !isdigit((unsigned char)line[2]) || - (line[3] != ' ' && line[3] != '-')) - errx(EX_TEMPFAIL, "LMTP server sent an invalid line"); - - if (line[0] != (phase == PHASE_DATA ? '3' : '2')) - errx(EX_TEMPFAIL, "LMTP server error: %s", line); - - if (line[3] == '-') - continue; - - switch (phase) { - - case PHASE_BANNER: - fprintf(file_write, "LHLO %s\r\n", session->lhlo); - phase++; - break; - - case PHASE_HELO: - fprintf(file_write, "MAIL FROM:<%s>\r\n", session->mailfrom); - phase++; - break; - - case PHASE_MAILFROM: - fprintf(file_write, "RCPT TO:<%s>\r\n", session->rcpts[session->n_rcpts - 1]); - if (session->n_rcpts - 1 == 0) { - phase++; - break; - } - session->n_rcpts--; - break; - - case PHASE_RCPTTO: - fprintf(file_write, "DATA\r\n"); - phase++; - break; - - case PHASE_DATA: - stream_file(file_write); - fprintf(file_write, ".\r\n"); - phase++; - break; - - case PHASE_EOM: - fprintf(file_write, "QUIT\r\n"); - phase++; - break; - - case PHASE_QUIT: - exit(0); - } - } while (1); -} - -static void -stream_file(FILE *conn) -{ - char *line = NULL; - size_t linesize = 0; - ssize_t linelen; - - while ((linelen = getline(&line, &linesize, stdin)) != -1) { - line[strcspn(line, "\n")] = '\0'; - if (line[0] == '.') - fprintf(conn, "."); - fprintf(conn, "%s\r\n", line); - } - free(line); - if (ferror(stdin)) - err(EX_TEMPFAIL, "getline"); -} diff --git a/smtpd/mail.maildir.8 b/smtpd/mail.maildir.8 deleted file mode 100644 index ce822698..00000000 --- a/smtpd/mail.maildir.8 +++ /dev/null @@ -1,45 +0,0 @@ -.\" $OpenBSD: mail.maildir.8,v 1.5 2018/05/30 12:37:57 jmc Exp $ -.\" -.\" Copyright (c) 2017 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: May 30 2018 $ -.Dt MAIL.MAILDIR 8 -.Os -.Sh NAME -.Nm mail.maildir -.Nd store mail in a maildir -.Sh SYNOPSIS -.Nm mail.maildir -.Op Fl j -.Op Ar pathname -.Sh DESCRIPTION -.Nm -reads the standard input up to an end-of-file and adds it to the -mail directory located in -.Ar pathname -or to the mail directory -.Pa Maildir -located in the user's home directory. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl j -Scan message for X-Spam and move to Junk folder if result is positive. -.El -.Sh EXIT STATUS -.Ex -std mail.maildir -.Sh SEE ALSO -.Xr mail 1 , -.Xr smtpd 8 diff --git a/smtpd/mail.maildir.c b/smtpd/mail.maildir.c deleted file mode 100644 index fe6adba6..00000000 --- a/smtpd/mail.maildir.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2017 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#ifndef nitems -#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAILADDR_ESCAPE "!#$%&'*/?^`{|}~" - -static int maildir_subdir(const char *, char *, size_t); -static void maildir_mkdirs(const char *); -static void maildir_engine(const char *, int); -static int mkdirs_component(const char *, mode_t); -static int mkdirs(const char *, mode_t); - -int -main(int argc, char *argv[]) -{ - int ch; - int junk = 0; - - if (! geteuid()) - errx(1, "mail.maildir: may not be executed as root"); - - while ((ch = getopt(argc, argv, "j")) != -1) { - switch (ch) { - case 'j': - junk = 1; - break; - default: - break; - } - } - argc -= optind; - argv += optind; - - if (argc > 1) - errx(1, "mail.maildir: only one maildir is allowed"); - - maildir_engine(argv[0], junk); - - return (0); -} - -static int -maildir_subdir(const char *extension, char *dest, size_t len) -{ - char *sanitized; - - if (strlcpy(dest, extension, len) >= len) - return 0; - - for (sanitized = dest; *sanitized; sanitized++) - if (strchr(MAILADDR_ESCAPE, *sanitized)) - *sanitized = ':'; - - return 1; -} - -static void -maildir_mkdirs(const char *dirname) -{ - uint i; - int ret; - char pathname[PATH_MAX]; - char *subdirs[] = { "cur", "tmp", "new" }; - - if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) { - if (errno == EINVAL || errno == ENAMETOOLONG) - err(1, NULL); - err(EX_TEMPFAIL, NULL); - } - - for (i = 0; i < nitems(subdirs); ++i) { - ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname, - subdirs[i]); - if (ret < 0 || (size_t)ret >= sizeof pathname) - errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]); - if (mkdir(pathname, 0700) == -1 && errno != EEXIST) - err(EX_TEMPFAIL, NULL); - } -} - -static void -maildir_engine(const char *dirname, int junk) -{ - char rootpath[PATH_MAX]; - char junkpath[PATH_MAX]; - char extpath[PATH_MAX]; - char subdir[PATH_MAX]; - char filename[PATH_MAX]; - char hostname[HOST_NAME_MAX+1]; - - char tmp[PATH_MAX]; - char new[PATH_MAX]; - - int ret; - - int fd; - FILE *fp; - char *line = NULL; - size_t linesize = 0; - ssize_t linelen; - struct stat sb; - char *home; - char *extension; - - int is_junk = 0; - int in_hdr = 1; - - if (dirname == NULL) { - if ((home = getenv("HOME")) == NULL) - err(1, NULL); - ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home); - if (ret < 0 || (size_t)ret >= sizeof rootpath) - errc(1, ENAMETOOLONG, "%s/Maildir", home); - dirname = rootpath; - } - maildir_mkdirs(dirname); - - if (junk) { - /* create Junk subdirectory */ - ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname); - if (ret < 0 || (size_t)ret >= sizeof junkpath) - errc(1, ENAMETOOLONG, "%s/.Junk", dirname); - maildir_mkdirs(junkpath); - } - - if ((extension = getenv("EXTENSION")) != NULL) { - if (maildir_subdir(extension, subdir, sizeof(subdir)) && - subdir[0]) { - ret = snprintf(extpath, sizeof extpath, "%s/.%s", - dirname, subdir); - if (ret < 0 || (size_t)ret >= sizeof extpath) - errc(1, ENAMETOOLONG, "%s/.%s", - dirname, subdir); - if (stat(extpath, &sb) != -1) { - dirname = extpath; - maildir_mkdirs(dirname); - } - } - } - - if (gethostname(hostname, sizeof hostname) != 0) - (void)strlcpy(hostname, "localhost", sizeof hostname); - - (void)snprintf(filename, sizeof filename, "%lld.%08x.%s", - (long long int) time(NULL), - arc4random(), - hostname); - - (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename); - - fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600); - if (fd == -1) - err(EX_TEMPFAIL, NULL); - if ((fp = fdopen(fd, "w")) == NULL) - err(EX_TEMPFAIL, NULL); - - while ((linelen = getline(&line, &linesize, stdin)) != -1) { - line[strcspn(line, "\n")] = '\0'; - if (line[0] == '\0') - in_hdr = 0; - if (junk && in_hdr && - (strcasecmp(line, "x-spam: yes") == 0 || - strcasecmp(line, "x-spam-flag: yes") == 0)) - is_junk = 1; - fprintf(fp, "%s\n", line); - } - free(line); - if (ferror(stdin)) - err(EX_TEMPFAIL, NULL); - - if (fflush(fp) == EOF || - ferror(fp) || - fsync(fd) == -1 || - fclose(fp) == EOF) - err(EX_TEMPFAIL, NULL); - - (void)snprintf(new, sizeof new, "%s/new/%s", - is_junk ? junkpath : dirname, filename); - - if (rename(tmp, new) == -1) - err(EX_TEMPFAIL, NULL); - - exit(0); -} - - -static int -mkdirs_component(const char *path, mode_t mode) -{ - struct stat sb; - - if (stat(path, &sb) == -1) { - if (errno != ENOENT) - return 0; - if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1) - return 0; - } - else if (!S_ISDIR(sb.st_mode)) { - errno = ENOTDIR; - return 0; - } - - return 1; -} - -static int -mkdirs(const char *path, mode_t mode) -{ - char buf[PATH_MAX]; - int i = 0; - int done = 0; - const char *p; - - /* absolute path required */ - if (*path != '/') { - errno = EINVAL; - return 0; - } - - /* make sure we don't exceed PATH_MAX */ - if (strlen(path) >= sizeof buf) { - errno = ENAMETOOLONG; - return 0; - } - - memset(buf, 0, sizeof buf); - for (p = path; *p; p++) { - if (*p == '/') { - if (buf[0] != '\0') - if (!mkdirs_component(buf, mode)) - return 0; - while (*p == '/') - p++; - buf[i++] = '/'; - buf[i++] = *p; - if (*p == '\0' && ++done) - break; - continue; - } - buf[i++] = *p; - } - if (!done) - if (!mkdirs_component(buf, mode)) - return 0; - - if (chmod(path, mode) == -1) - return 0; - - return 1; -} diff --git a/smtpd/mail.mboxfile.8 b/smtpd/mail.mboxfile.8 deleted file mode 100644 index 015adcb5..00000000 --- a/smtpd/mail.mboxfile.8 +++ /dev/null @@ -1,34 +0,0 @@ -.\" $OpenBSD: mail.mboxfile.8,v 1.1 2018/07/25 10:19:28 gilles Exp $ -.\" -.\" Copyright (c) 2017 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: July 25 2018 $ -.Dt MAIL.MDA 8 -.Os -.Sh NAME -.Nm mail.mboxfile -.Nd deliver mail to a file in mbox format -.Sh SYNOPSIS -.Nm mail.mboxfile -.Ar file -.Sh DESCRIPTION -.Nm -appends mail to a file in mbox format and acknowledges delivery success or failure -with its exit status. -.Sh EXIT STATUS -.Ex -std mail.mboxfile -.Sh SEE ALSO -.Xr mail 1 , -.Xr smtpd 8 diff --git a/smtpd/mail.mboxfile.c b/smtpd/mail.mboxfile.c deleted file mode 100644 index 097a8d96..00000000 --- a/smtpd/mail.mboxfile.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2018 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void mboxfile_engine(const char *sender, const char *filename); - -int -main(int argc, char *argv[]) -{ - int ch; - char *sender = ""; - - if (! geteuid()) - errx(1, "mail.mboxfile: may not be executed as root"); - - while ((ch = getopt(argc, argv, "f:")) != -1) { - switch (ch) { - case 'f': - sender = optarg; - break; - default: - break; - } - } - argc -= optind; - argv += optind; - - if (argc > 1) - errx(1, "mail.mboxfile: only one mboxfile is allowed"); - - mboxfile_engine(sender, argv[0]); - - return (0); -} - -static void -mboxfile_engine(const char *sender, const char *filename) -{ - int fd; - FILE *fp; - char *line = NULL; - size_t linesize = 0; - ssize_t linelen; - time_t now; - - time(&now); - -#ifndef O_EXLOCK -#define O_EXLOCK 0 -#endif - fd = open(filename, O_CREAT | O_APPEND | O_WRONLY | O_EXLOCK, 0600); -#ifndef HAVE_O_EXLOCK - /* XXX : do something! */ -#endif - if (fd == -1) - err(EX_TEMPFAIL, NULL); - - if ((fp = fdopen(fd, "w")) == NULL) - err(EX_TEMPFAIL, NULL); - - fprintf(fp, "From %s %s", sender, ctime(&now)); - while ((linelen = getline(&line, &linesize, stdin)) != -1) { - line[strcspn(line, "\n")] = '\0'; - if (strncmp(line, "From ", 5) == 0) - fprintf(fp, ">%s\n", line); - else - fprintf(fp, "%s\n", line); - } - fprintf(fp, "\n"); - free(line); - if (ferror(stdin)) - err(EX_TEMPFAIL, NULL); - - if (fflush(fp) == EOF || - ferror(fp) || - (fsync(fd) == -1 && errno != EINVAL) || - fclose(fp) == EOF) - err(EX_TEMPFAIL, NULL); -} diff --git a/smtpd/mail.mda.8 b/smtpd/mail.mda.8 deleted file mode 100644 index 61fed733..00000000 --- a/smtpd/mail.mda.8 +++ /dev/null @@ -1,35 +0,0 @@ -.\" $OpenBSD: mail.mda.8,v 1.1 2017/08/09 07:56:10 gilles Exp $ -.\" -.\" Copyright (c) 2017 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: August 9 2017 $ -.Dt MAIL.MDA 8 -.Os -.Sh NAME -.Nm mail.mda -.Nd deliver mail to a program -.Sh SYNOPSIS -.Nm mail.mda -.Ar program -.Sh DESCRIPTION -.Nm -executes the program and its parameters. -The program must read from the standard input up to an end-of-file -and acknowledge delivery success or failure with its exit status. -.Sh EXIT STATUS -.Ex -std mail.mda -.Sh SEE ALSO -.Xr mail 1 , -.Xr smtpd 8 diff --git a/smtpd/mail.mda.c b/smtpd/mail.mda.c deleted file mode 100644 index 23958071..00000000 --- a/smtpd/mail.mda.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2017 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int -main(int argc, char *argv[]) -{ - int ch; - int ret; - - if (! geteuid()) - errx(1, "mail.mda: may not be executed as root"); - - while ((ch = getopt(argc, argv, "")) != -1) { - switch (ch) { - default: - break; - } - } - argc -= optind; - argv += optind; - - if (argc == 0) - errx(1, "mail.mda: command required"); - - if (argc > 1) - errx(1, "mail.mda: only one command is supported"); - - /* could not obtain a shell or could not obtain wait status, - * tempfail */ - if ((ret = system(argv[0])) == -1) - errx(EX_TEMPFAIL, "%s", strerror(errno)); - - /* not exited properly but we have no details, - * tempfail */ - if (! WIFEXITED(ret)) - exit(EX_TEMPFAIL); - - exit(WEXITSTATUS(ret)); -} diff --git a/smtpd/mail/Makefile b/smtpd/mail/Makefile deleted file mode 100644 index b2bc2a26..00000000 --- a/smtpd/mail/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# $OpenBSD: Makefile,v 1.8 2018/07/25 10:19:28 gilles Exp $ -.PATH: ${.CURDIR}/.. - -PROGS= mail.lmtp mail.maildir mail.mboxfile mail.mda - -MAN= mail.lmtp.8 mail.maildir.8 mail.mboxfile.8 mail.mda.8 - -BINOWN= root -BINGRP= wheel - -BINDIR= /usr/libexec - -CFLAGS+= -fstack-protector-all -CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -CFLAGS+= -Wmissing-declarations -CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual -CFLAGS+= -Wsign-compare -CFLAGS+= -Werror-implicit-function-declaration - -.include diff --git a/smtpd/mailaddr.c b/smtpd/mailaddr.c deleted file mode 100644 index 4346e3dc..00000000 --- a/smtpd/mailaddr.c +++ /dev/null @@ -1,135 +0,0 @@ -/* $OpenBSD: mailaddr.c,v 1.3 2018/05/31 21:06:12 gilles Exp $ */ - -/* - * Copyright (c) 2015 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static int -mailaddr_line_split(char **line, char **ret) -{ - static char buffer[LINE_MAX]; - int esc, dq, sq; - size_t i; - char *s; - - memset(buffer, 0, sizeof buffer); - esc = dq = sq = 0; - i = 0; - for (s = *line; (*s) && (i < sizeof(buffer)); ++s) { - if (esc) { - buffer[i++] = *s; - esc = 0; - continue; - } - if (*s == '\\') { - esc = 1; - continue; - } - if (*s == ',' && !dq && !sq) { - *ret = buffer; - *line = s+1; - return (1); - } - - buffer[i++] = *s; - esc = 0; - - if (*s == '"' && !sq) - dq ^= 1; - if (*s == '\'' && !dq) - sq ^= 1; - } - - if (esc || dq || sq || i == sizeof(buffer)) - return (-1); - - *ret = buffer; - *line = s; - return (i ? 1 : 0); -} - -int -mailaddr_line(struct maddrmap *maddrmap, const char *s) -{ - struct maddrnode mn; - char buffer[LINE_MAX]; - char *p, *subrcpt; - int ret; - - memset(buffer, 0, sizeof buffer); - if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) - return 0; - - p = buffer; - while ((ret = mailaddr_line_split(&p, &subrcpt)) > 0) { - subrcpt = strip(subrcpt); - if (subrcpt[0] == '\0') - continue; - if (!text_to_mailaddr(&mn.mailaddr, subrcpt)) - return 0; - log_debug("subrcpt: [%s]", subrcpt); - maddrmap_insert(maddrmap, &mn); - } - - if (ret >= 0) - return 1; - /* expand_line_split() returned < 0 */ - return 0; -} - -void -maddrmap_init(struct maddrmap *maddrmap) -{ - TAILQ_INIT(&maddrmap->queue); -} - -void -maddrmap_insert(struct maddrmap *maddrmap, struct maddrnode *maddrnode) -{ - struct maddrnode *mn; - - mn = xmemdup(maddrnode, sizeof *maddrnode); - TAILQ_INSERT_TAIL(&maddrmap->queue, mn, entries); -} - -void -maddrmap_free(struct maddrmap *maddrmap) -{ - struct maddrnode *mn; - - while ((mn = TAILQ_FIRST(&maddrmap->queue))) { - TAILQ_REMOVE(&maddrmap->queue, mn, entries); - free(mn); - } - free(maddrmap); -} diff --git a/smtpd/makemap.8 b/smtpd/makemap.8 deleted file mode 100644 index 674bef6f..00000000 --- a/smtpd/makemap.8 +++ /dev/null @@ -1,174 +0,0 @@ -.\" $OpenBSD: makemap.8,v 1.30 2018/11/25 14:41:16 gilles Exp $ -.\" -.\" Copyright (c) 2009 Jacek Masiulaniec -.\" Copyright (c) 2008-2009 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: November 25 2018 $ -.Dt MAKEMAP 8 -.Os -.Sh NAME -.Nm makemap -.Nd create database maps for smtpd -.Sh SYNOPSIS -.Nm makemap -.Op Fl U -.Op Fl d Ar dbtype -.Op Fl o Ar dbfile -.Op Fl t Ar type -.Ar file -.Sh DESCRIPTION -Maps provide a generic interface for associating textual key to a value. -Such associations may be accessed through a plaintext file, database, or DNS. -The format of these file types is described below. -.Nm -itself creates the database maps used by keyed map lookups specified in -.Xr smtpd.conf 5 . -.Pp -.Nm -reads input from -.Ar file -and writes data to a file whose name is made by adding a -.Dq .db -suffix to -.Ar file . -The current line can be extended over multiple lines using a backslash -.Pq Sq \e . -Comments can be put anywhere in the file using a hash mark -.Pq Sq # , -and extend to the end of the current line. -Care should be taken when commenting out multi-line text: -the comment is effective until the end of the entire block. -In all cases, -.Nm -reads lines consisting of words separated by whitespace. -The first word of a line is the database key; -the remainder represents the mapped value. -The database key and value may optionally be separated -by the colon character. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl d Ar dbtype -Specify the format of the database. -Available formats are -.Ar hash -and -.Ar btree . -The default value is -.Ar hash . -.It Fl o Ar dbfile -Write the generated database to -.Ar dbfile . -.It Fl t Ar type -Specify the format of the resulting map file. -The default map format is suitable for storing simple, unstructured, -key-to-value string associations. -However, if the mapped value has special meaning, -as in the case of the virtual domains file, -a suitable -.Ar type -must be provided. -The available output types are: -.Bl -tag -width "aliases" -.It Cm aliases -The mapped value is a comma-separated list of mail destinations. -This format can be used for building user aliases and -user mappings for virtual domain files. -.It Cm set -There is no mapped value \(en a map of this type will only allow for -the lookup of keys. -This format can be used for building primary domain maps. -.El -.It Fl U -Instead of generating a database map from text input, -dump the contents of a database map as text -with the key and value separated with a tab. -.El -.Sh PRIMARY DOMAINS -Primary domains can be kept in tables. -To create a primary domain table, add each primary domain on a -single line by itself. -.Pp -In addition to adding an entry to the primary domain map, -one must add a filter rule that accepts mail for the domain -map, for example: -.Bd -literal -offset indent -table domains db:/etc/mail/domains.db - -action "local" mbox - -match for domain action "local" -.Ed -.Sh VIRTUAL DOMAINS -Virtual domains may also be kept in tables. -To create a virtual domain table, add each virtual domain on a -single line by itself. -.Pp -Virtual domains expect a mapping of virtual users to real users -in order to determine if a recipient is accepted or not. -The mapping format is an extension to -.Xr aliases 5 , -which allows the use of -.Dq user@domain.tld -to accept user only on the specified domain, -.Dq user -to accept the user for any of the virtual domains, -.Dq @domain.tld -to provide a catch-all for the specified domain and -.Dq @ -to provide a global catch-all for all domains. -.Xr smtpd 8 -will perform the lookups in that specific order. -.Pp -To create single virtual address, add -.Dq user@example.com user -to the users map. -To handle all mail destined to any user at example.com, add -.Dq @example.com user -to the virtual map. -.Pp -In addition to adding an entry to the virtual map, -one must add a filter rule that accepts mail for virtual domains, -for example: -.Bd -literal -offset indent -table vdomains db:/etc/mail/vdomains.db -table vusers db:/etc/mail/users.db - -action "local" mbox virtual - -match for domain action "local" -match for domain "example.org" action "local" -.Ed -.Sh FILES -.Bl -tag -width "/etc/mail/aliasesXXX" -compact -.It Pa /etc/mail/aliases -List of user mail aliases. -.It Pa /etc/mail/secrets -List of remote host credentials. -.El -.Sh EXIT STATUS -.Ex -std makemap -.Sh SEE ALSO -.Xr aliases 5 , -.Xr smtpd.conf 5 , -.Xr table 5 , -.Xr newaliases 8 , -.Xr smtpd 8 -.Sh HISTORY -The -.Nm -command first appeared in -.Ox 4.6 -as a replacement for the equivalent command shipped with sendmail. diff --git a/smtpd/makemap.c b/smtpd/makemap.c deleted file mode 100644 index 10e3f555..00000000 --- a/smtpd/makemap.c +++ /dev/null @@ -1,521 +0,0 @@ -/* $OpenBSD: makemap.c,v 1.73 2020/02/24 16:16:07 millert Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008-2009 Jacek Masiulaniec - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#ifdef HAVE_SYS_FILE_H -#include /* Needed for flock */ -#endif -#include -#include -#include -#include -#include - -#include -#ifdef HAVE_DB_H -#include -#elif defined(HAVE_DB1_DB_H) -#include -#elif defined(HAVE_DB_185_H) -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UTIL_H -#include -#endif -#ifdef HAVE_LIBUTIL_H -#include -#endif - -#include "smtpd.h" -#include "log.h" - -#define PATH_ALIASES SMTPD_CONFDIR "/aliases" - -static void usage(void); -static int parse_map(DB *, int *, char *); -static int parse_entry(DB *, int *, char *, size_t, size_t); -static int parse_mapentry(DB *, int *, char *, size_t, size_t); -static int parse_setentry(DB *, int *, char *, size_t, size_t); -static int make_plain(DBT *, char *); -static int make_aliases(DBT *, char *); -static char *conf_aliases(char *); -static int dump_db(const char *, DBTYPE); - -char *source; -static int mode; - -enum output_type { - T_PLAIN, - T_ALIASES, - T_SET -} type; - -/* - * Stub functions so that makemap compiles using minimum object files. - */ -int -fork_proc_backend(const char *backend, const char *conf, const char *procname) -{ - return (-1); -} - -int -makemap(int prog_mode, int argc, char *argv[]) -{ - struct stat sb; - char dbname[PATH_MAX]; - DB *db; - const char *opts; - char *conf, *oflag = NULL; - int ch, dbputs = 0, Uflag = 0; - DBTYPE dbtype = DB_HASH; - char *p; - gid_t gid; - int fd = -1; - - gid = getgid(); - if (setresgid(gid, gid, gid) == -1) - err(1, "setresgid"); - - if ((env = config_default()) == NULL) - err(1, NULL); - - log_init(1, LOG_MAIL); - - mode = prog_mode; - conf = CONF_FILE; - type = T_PLAIN; - opts = "b:C:d:ho:O:t:U"; - if (mode == P_NEWALIASES) - opts = "f:h"; - - while ((ch = getopt(argc, argv, opts)) != -1) { - switch (ch) { - case 'b': - if (optarg && strcmp(optarg, "i") == 0) - mode = P_NEWALIASES; - break; - case 'C': - break; /* for compatibility */ - case 'd': - if (strcmp(optarg, "hash") == 0) - dbtype = DB_HASH; - else if (strcmp(optarg, "btree") == 0) - dbtype = DB_BTREE; - else - errx(1, "unsupported DB type '%s'", optarg); - break; - case 'f': - conf = optarg; - break; - case 'o': - oflag = optarg; - break; - case 'O': - if (strncmp(optarg, "AliasFile=", 10) != 0) - break; - type = T_ALIASES; - p = strchr(optarg, '='); - source = ++p; - break; - case 't': - if (strcmp(optarg, "aliases") == 0) - type = T_ALIASES; - else if (strcmp(optarg, "set") == 0) - type = T_SET; - else - errx(1, "unsupported type '%s'", optarg); - break; - case 'U': - Uflag = 1; - break; - default: - usage(); - } - } - argc -= optind; - argv += optind; - - /* sendmail-compat makemap ... re-execute using proper interface */ - if (argc == 2) { - if (oflag) - usage(); - - p = strstr(argv[1], ".db"); - if (p == NULL || strcmp(p, ".db") != 0) { - if (!bsnprintf(dbname, sizeof dbname, "%s.db", - argv[1])) - errx(1, "database name too long"); - } - else { - if (strlcpy(dbname, argv[1], sizeof dbname) - >= sizeof dbname) - errx(1, "database name too long"); - } - - execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname, - "-", (char *)NULL); - err(1, "execl"); - } - - if (mode == P_NEWALIASES) { - if (geteuid()) - errx(1, "need root privileges"); - if (argc != 0) - usage(); - type = T_ALIASES; - if (source == NULL) - source = conf_aliases(conf); - } else { - if (argc != 1) - usage(); - source = argv[0]; - } - - if (Uflag) - return dump_db(source, dbtype); - - if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1) - err(1, "asprintf"); - - if (strcmp(source, "-") != 0) - if (stat(source, &sb) == -1) - err(1, "stat: %s", source); - - if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag)) - errx(1, "path too long"); - if ((fd = mkstemp(dbname)) == -1) - err(1, "mkstemp"); - - db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL); - if (db == NULL) { - warn("dbopen: %s", dbname); - goto bad; - } - - if (strcmp(source, "-") != 0) - if (fchmod(db->fd(db), sb.st_mode) == -1 || - fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) { - warn("couldn't carry ownership and perms to %s", - dbname); - goto bad; - } - - if (!parse_map(db, &dbputs, source)) - goto bad; - - if (db->close(db) == -1) { - warn("dbclose: %s", dbname); - goto bad; - } - - /* force to disk before renaming over an existing file */ - if (fsync(fd) == -1) { - warn("fsync: %s", dbname); - goto bad; - } - if (close(fd) == -1) { - fd = -1; - warn("close: %s", dbname); - goto bad; - } - fd = -1; - - if (rename(dbname, oflag) == -1) { - warn("rename"); - goto bad; - } - - if (mode == P_NEWALIASES) - printf("%s: %d aliases\n", source, dbputs); - else if (dbputs == 0) - warnx("warning: empty map created: %s", oflag); - - return 0; -bad: - if (fd != -1) - close(fd); - unlink(dbname); - return 1; -} - -static int -parse_map(DB *db, int *dbputs, char *filename) -{ - FILE *fp; - char *line; - size_t len; - size_t lineno = 0; - - if (strcmp(filename, "-") == 0) - fp = fdopen(0, "r"); - else - fp = fopen(filename, "r"); - if (fp == NULL) { - warn("%s", filename); - return 0; - } - - if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) { - if (errno == EWOULDBLOCK) - warnx("%s is locked", filename); - else - warn("%s: flock", filename); - fclose(fp); - return 0; - } - - while ((line = fparseln(fp, &len, &lineno, - NULL, FPARSELN_UNESCCOMM)) != NULL) { - if (!parse_entry(db, dbputs, line, len, lineno)) { - free(line); - fclose(fp); - return 0; - } - free(line); - } - - fclose(fp); - return 1; -} - -static int -parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) -{ - switch (type) { - case T_PLAIN: - case T_ALIASES: - return parse_mapentry(db, dbputs, line, len, lineno); - case T_SET: - return parse_setentry(db, dbputs, line, len, lineno); - } - return 0; -} - -static int -parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) -{ - DBT key; - DBT val; - char *keyp; - char *valp; - - keyp = line; - while (isspace((unsigned char)*keyp)) - keyp++; - if (*keyp == '\0') - return 1; - - valp = keyp; - strsep(&valp, " \t:"); - if (valp == NULL || valp == keyp) - goto bad; - while (*valp == ':' || isspace((unsigned char)*valp)) - valp++; - if (*valp == '\0') - goto bad; - - /* Check for dups. */ - key.data = keyp; - key.size = strlen(keyp) + 1; - - xlowercase(key.data, key.data, strlen(key.data) + 1); - if (db->get(db, &key, &val, 0) == 0) { - warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); - return 0; - } - - if (type == T_PLAIN) { - if (!make_plain(&val, valp)) - goto bad; - } - else if (type == T_ALIASES) { - if (!make_aliases(&val, valp)) - goto bad; - } - - if (db->put(db, &key, &val, 0) == -1) { - warn("dbput"); - return 0; - } - - (*dbputs)++; - - free(val.data); - - return 1; - -bad: - warnx("%s:%zd: invalid entry", source, lineno); - return 0; -} - -static int -parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) -{ - DBT key; - DBT val; - char *keyp; - - keyp = line; - while (isspace((unsigned char)*keyp)) - keyp++; - if (*keyp == '\0') - return 1; - - val.data = ""; - val.size = strlen(val.data) + 1; - - /* Check for dups. */ - key.data = keyp; - key.size = strlen(keyp) + 1; - xlowercase(key.data, key.data, strlen(key.data) + 1); - if (db->get(db, &key, &val, 0) == 0) { - warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); - return 0; - } - - if (db->put(db, &key, &val, 0) == -1) { - warn("dbput"); - return 0; - } - - (*dbputs)++; - - return 1; -} - -static int -make_plain(DBT *val, char *text) -{ - val->data = xstrdup(text); - val->size = strlen(text) + 1; - - return (val->size); -} - -static int -make_aliases(DBT *val, char *text) -{ - struct expandnode xn; - char *subrcpt; - char *origtext; - - val->data = NULL; - val->size = 0; - - origtext = xstrdup(text); - - while ((subrcpt = strsep(&text, ",")) != NULL) { - /* subrcpt: strip initial and trailing whitespace. */ - subrcpt = strip(subrcpt); - if (*subrcpt == '\0') - goto error; - - if (!text_to_expandnode(&xn, subrcpt)) - goto error; - } - - val->data = origtext; - val->size = strlen(origtext) + 1; - return (val->size); - -error: - free(origtext); - - return 0; -} - -static char * -conf_aliases(char *cfgpath) -{ - struct table *table; - char *path; - char *p; - - if (parse_config(env, cfgpath, 0)) - exit(1); - - table = table_find(env, "aliases"); - if (table == NULL) - return (PATH_ALIASES); - - path = xstrdup(table->t_config); - p = strstr(path, ".db"); - if (p == NULL || strcmp(p, ".db") != 0) { - return (path); - } - *p = '\0'; - return (path); -} - -static int -dump_db(const char *dbname, DBTYPE dbtype) -{ - DB *db; - DBT key, val; - char *keystr, *valstr; - int r; - - db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL); - if (db == NULL) - err(1, "dbopen: %s", dbname); - - for (r = db->seq(db, &key, &val, R_FIRST); r == 0; - r = db->seq(db, &key, &val, R_NEXT)) { - keystr = key.data; - valstr = val.data; - if (keystr[key.size - 1] == '\0') - key.size--; - if (valstr[val.size - 1] == '\0') - val.size--; - printf("%.*s\t%.*s\n", (int)key.size, keystr, - (int)val.size, valstr); - } - if (r == -1) - err(1, "db->seq: %s", dbname); - - if (db->close(db) == -1) - err(1, "dbclose: %s", dbname); - - return 0; -} - -static void -usage(void) -{ - if (mode == P_NEWALIASES) - fprintf(stderr, "usage: newaliases [-f file]\n"); - else - fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] " - "[-t type] file\n"); - exit(1); -} diff --git a/smtpd/mda.c b/smtpd/mda.c deleted file mode 100644 index 5e8fec19..00000000 --- a/smtpd/mda.c +++ /dev/null @@ -1,919 +0,0 @@ -/* $OpenBSD: mda.c,v 1.141 2019/10/03 08:50:08 gilles Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) -#include -#else -#include "bsd-vis.h" -#endif - -#include "smtpd.h" -#include "log.h" - -#define MDA_HIWAT 65536 - -struct mda_envelope { - TAILQ_ENTRY(mda_envelope) entry; - uint64_t session_id; - uint64_t id; - time_t creation; - char *sender; - char *rcpt; - char *dest; - char *user; - char *dispatcher; - char *mda_subaddress; - char *mda_exec; -}; - -#define USER_WAITINFO 0x01 -#define USER_RUNNABLE 0x02 -#define USER_ONHOLD 0x04 -#define USER_HOLDQ 0x08 - -struct mda_user { - uint64_t id; - TAILQ_ENTRY(mda_user) entry; - TAILQ_ENTRY(mda_user) entry_runnable; - char name[LOGIN_NAME_MAX]; - char usertable[PATH_MAX]; - size_t evpcount; - TAILQ_HEAD(, mda_envelope) envelopes; - int flags; - size_t running; - struct userinfo userinfo; -}; - -struct mda_session { - uint64_t id; - struct mda_user *user; - struct mda_envelope *evp; - struct io *io; - FILE *datafp; -}; - -static void mda_io(struct io *, int, void *); -static int mda_check_loop(FILE *, struct mda_envelope *); -static int mda_getlastline(int, char *, size_t); -static void mda_done(struct mda_session *); -static void mda_fail(struct mda_user *, int, const char *, - enum enhanced_status_code); -static void mda_drain(void); -static void mda_log(const struct mda_envelope *, const char *, const char *); -static void mda_queue_ok(uint64_t); -static void mda_queue_tempfail(uint64_t, const char *, - enum enhanced_status_code); -static void mda_queue_permfail(uint64_t, const char *, enum enhanced_status_code); -static void mda_queue_loop(uint64_t); -static struct mda_user *mda_user(const struct envelope *); -static void mda_user_free(struct mda_user *); -static const char *mda_user_to_text(const struct mda_user *); -static struct mda_envelope *mda_envelope(uint64_t, const struct envelope *); -static void mda_envelope_free(struct mda_envelope *); -static struct mda_session * mda_session(struct mda_user *); -static const char *mda_sysexit_to_str(int); - -static struct tree sessions; -static struct tree users; - -static TAILQ_HEAD(, mda_user) runnable; - -void -mda_imsg(struct mproc *p, struct imsg *imsg) -{ - struct mda_session *s; - struct mda_user *u; - struct mda_envelope *e; - struct envelope evp; - struct deliver deliver; - struct msg m; - const void *data; - const char *error, *parent_error, *syserror; - uint64_t reqid; - size_t sz; - char out[256], buf[LINE_MAX]; - int n; - enum lka_resp_status status; - enum mda_resp_status mda_status; - int mda_sysexit; - - switch (imsg->hdr.type) { - case IMSG_MDA_LOOKUP_USERINFO: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, (int *)&status); - if (status == LKA_OK) - m_get_data(&m, &data, &sz); - m_end(&m); - - u = tree_xget(&users, reqid); - - if (status == LKA_TEMPFAIL) - mda_fail(u, 0, - "Temporary failure in user lookup", - ESC_OTHER_ADDRESS_STATUS); - else if (status == LKA_PERMFAIL) - mda_fail(u, 1, - "Permanent failure in user lookup", - ESC_DESTINATION_MAILBOX_HAS_MOVED); - else { - if (sz != sizeof(u->userinfo)) - fatalx("mda: userinfo size mismatch"); - memmove(&u->userinfo, data, sz); - u->flags &= ~USER_WAITINFO; - u->flags |= USER_RUNNABLE; - TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); - mda_drain(); - } - return; - - case IMSG_QUEUE_DELIVER: - m_msg(&m, imsg); - m_get_envelope(&m, &evp); - m_end(&m); - - u = mda_user(&evp); - - if (u->evpcount >= env->sc_mda_task_hiwat) { - if (!(u->flags & USER_ONHOLD)) { - log_debug("debug: mda: hiwat reached for " - "user \"%s\": holding envelopes", - mda_user_to_text(u)); - u->flags |= USER_ONHOLD; - } - } - - if (u->flags & USER_ONHOLD) { - u->flags |= USER_HOLDQ; - m_create(p_queue, IMSG_MDA_DELIVERY_HOLD, - 0, 0, -1); - m_add_evpid(p_queue, evp.id); - m_add_id(p_queue, u->id); - m_close(p_queue); - return; - } - - e = mda_envelope(u->id, &evp); - TAILQ_INSERT_TAIL(&u->envelopes, e, entry); - u->evpcount += 1; - stat_increment("mda.pending", 1); - - if (!(u->flags & USER_RUNNABLE) && - !(u->flags & USER_WAITINFO)) { - u->flags |= USER_RUNNABLE; - TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); - } - - mda_drain(); - return; - - case IMSG_MDA_OPEN_MESSAGE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - s = tree_xget(&sessions, reqid); - e = s->evp; - - if (imsg->fd == -1) { - log_debug("debug: mda: cannot get message fd"); - mda_queue_tempfail(e->id, - "Cannot get message fd", - ESC_OTHER_MAIL_SYSTEM_STATUS); - mda_log(e, "TempFail", "Cannot get message fd"); - mda_done(s); - return; - } - - log_debug("debug: mda: got message fd %d " - "for session %016"PRIx64 " evpid %016"PRIx64, - imsg->fd, s->id, e->id); - - if ((s->datafp = fdopen(imsg->fd, "r")) == NULL) { - log_warn("warn: mda: fdopen"); - close(imsg->fd); - mda_queue_tempfail(e->id, "fdopen failed", - ESC_OTHER_MAIL_SYSTEM_STATUS); - mda_log(e, "TempFail", "fdopen failed"); - mda_done(s); - return; - } - - /* check delivery loop */ - if (mda_check_loop(s->datafp, e)) { - log_debug("debug: mda: loop detected"); - mda_queue_loop(e->id); - mda_log(e, "PermFail", "Loop detected"); - mda_done(s); - return; - } - - /* start queueing delivery headers */ - if (e->sender[0]) - /* - * XXX: remove existing Return-Path, - * if any - */ - n = io_printf(s->io, - "Return-Path: <%s>\n" - "Delivered-To: %s\n", - e->sender, - e->rcpt ? e->rcpt : e->dest); - else - n = io_printf(s->io, - "Delivered-To: %s\n", - e->rcpt ? e->rcpt : e->dest); - if (n == -1) { - log_warn("warn: mda: " - "fail to write delivery info"); - mda_queue_tempfail(e->id, "Out of memory", - ESC_OTHER_MAIL_SYSTEM_STATUS); - mda_log(e, "TempFail", "Out of memory"); - mda_done(s); - return; - } - - /* request parent to fork a helper process */ - memset(&deliver, 0, sizeof deliver); - (void)text_to_mailaddr(&deliver.sender, s->evp->sender); - (void)text_to_mailaddr(&deliver.rcpt, s->evp->rcpt); - (void)text_to_mailaddr(&deliver.dest, s->evp->dest); - if (s->evp->mda_exec) - (void)strlcpy(deliver.mda_exec, s->evp->mda_exec, sizeof deliver.mda_exec); - if (s->evp->mda_subaddress) - (void)strlcpy(deliver.mda_subaddress, s->evp->mda_subaddress, sizeof deliver.mda_subaddress); - (void)strlcpy(deliver.dispatcher, s->evp->dispatcher, sizeof deliver.dispatcher); - deliver.userinfo = s->user->userinfo; - - log_debug("debug: mda: querying mda fd " - "for session %016"PRIx64 " evpid %016"PRIx64, - s->id, s->evp->id); - - m_create(p_parent, IMSG_MDA_FORK, 0, 0, -1); - m_add_id(p_parent, reqid); - m_add_data(p_parent, &deliver, sizeof(deliver)); - m_close(p_parent); - return; - - case IMSG_MDA_FORK: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - s = tree_xget(&sessions, reqid); - e = s->evp; - if (imsg->fd == -1) { - log_warn("warn: mda: fail to retrieve mda fd"); - mda_queue_tempfail(e->id, "Cannot get mda fd", - ESC_OTHER_MAIL_SYSTEM_STATUS); - mda_log(e, "TempFail", "Cannot get mda fd"); - mda_done(s); - return; - } - - log_debug("debug: mda: got mda fd %d " - "for session %016"PRIx64 " evpid %016"PRIx64, - imsg->fd, s->id, s->evp->id); - - io_set_nonblocking(imsg->fd); - io_set_fd(s->io, imsg->fd); - io_set_write(s->io); - return; - - case IMSG_MDA_DONE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, (int *)&mda_status); - m_get_int(&m, (int *)&mda_sysexit); - m_get_string(&m, &parent_error); - m_end(&m); - - s = tree_xget(&sessions, reqid); - e = s->evp; - /* - * Grab last line of mda stdout/stderr if available. - */ - out[0] = '\0'; - if (imsg->fd != -1) - mda_getlastline(imsg->fd, out, sizeof(out)); - - /* - * Choose between parent's description of error and - * child's output, the latter having preference over - * the former. - */ - error = NULL; - if (mda_status == MDA_OK) { - if (s->datafp || (s->io && io_queued(s->io))) { - error = "mda exited prematurely"; - mda_status = MDA_TEMPFAIL; - } - } else - error = out[0] ? out : parent_error; - - syserror = NULL; - if (mda_sysexit) - syserror = mda_sysexit_to_str(mda_sysexit); - - /* update queue entry */ - switch (mda_status) { - case MDA_TEMPFAIL: - mda_queue_tempfail(e->id, error, - ESC_OTHER_MAIL_SYSTEM_STATUS); - (void)snprintf(buf, sizeof buf, - "Error (%s%s%s)", - syserror ? syserror : "", - syserror ? ": " : "", - error); - mda_log(e, "TempFail", buf); - break; - case MDA_PERMFAIL: - mda_queue_permfail(e->id, error, - ESC_OTHER_MAIL_SYSTEM_STATUS); - (void)snprintf(buf, sizeof buf, - "Error (%s%s%s)", - syserror ? syserror : "", - syserror ? ": " : "", - error); - mda_log(e, "PermFail", buf); - break; - case MDA_OK: - mda_queue_ok(e->id); - mda_log(e, "Ok", "Delivered"); - break; - } - mda_done(s); - return; - } - - errx(1, "mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -void -mda_postfork() -{ -} - -void -mda_postprivdrop() -{ - tree_init(&sessions); - tree_init(&users); - TAILQ_INIT(&runnable); -} - -static void -mda_io(struct io *io, int evt, void *arg) -{ - struct mda_session *s = arg; - char *ln = NULL; - size_t sz = 0; - ssize_t len; - - log_trace(TRACE_IO, "mda: %p: %s %s", s, io_strevent(evt), - io_strio(io)); - - switch (evt) { - case IO_LOWAT: - - /* done */ - done: - if (s->datafp == NULL) { - log_debug("debug: mda: all data sent for session" - " %016"PRIx64 " evpid %016"PRIx64, - s->id, s->evp->id); - io_free(io); - s->io = NULL; - return; - } - - while (io_queued(s->io) < MDA_HIWAT) { - if ((len = getline(&ln, &sz, s->datafp)) == -1) - break; - if (io_write(s->io, ln, len) == -1) { - m_create(p_parent, IMSG_MDA_KILL, - 0, 0, -1); - m_add_id(p_parent, s->id); - m_add_string(p_parent, "Out of memory"); - m_close(p_parent); - io_pause(io, IO_OUT); - free(ln); - return; - } - } - - free(ln); - ln = NULL; - if (ferror(s->datafp)) { - log_debug("debug: mda: ferror on session %016"PRIx64, - s->id); - m_create(p_parent, IMSG_MDA_KILL, 0, 0, -1); - m_add_id(p_parent, s->id); - m_add_string(p_parent, "Error reading body"); - m_close(p_parent); - io_pause(io, IO_OUT); - return; - } - - if (feof(s->datafp)) { - log_debug("debug: mda: end-of-file for session" - " %016"PRIx64 " evpid %016"PRIx64, - s->id, s->evp->id); - fclose(s->datafp); - s->datafp = NULL; - if (io_queued(s->io) == 0) - goto done; - } - return; - - case IO_TIMEOUT: - log_debug("debug: mda: timeout on session %016"PRIx64, s->id); - io_pause(io, IO_OUT); - return; - - case IO_ERROR: - log_debug("debug: mda: io error on session %016"PRIx64": %s", - s->id, io_error(io)); - io_pause(io, IO_OUT); - return; - - case IO_DISCONNECTED: - log_debug("debug: mda: io disconnected on session %016"PRIx64, - s->id); - io_pause(io, IO_OUT); - return; - - default: - log_debug("debug: mda: unexpected event on session %016"PRIx64, - s->id); - io_pause(io, IO_OUT); - return; - } -} - -static int -mda_check_loop(FILE *fp, struct mda_envelope *e) -{ - char *buf = NULL; - size_t sz = 0; - ssize_t len; - int ret = 0; - - while ((len = getline(&buf, &sz, fp)) != -1) { - if (buf[len - 1] == '\n') - buf[len - 1] = '\0'; - - if (strchr(buf, ':') == NULL && !isspace((unsigned char)*buf)) - break; - - if (strncasecmp("Delivered-To: ", buf, 14) == 0) { - if (strcasecmp(buf + 14, e->dest) == 0) { - ret = 1; - break; - } - } - } - - free(buf); - fseek(fp, SEEK_SET, 0); - return (ret); -} - -static int -mda_getlastline(int fd, char *dst, size_t dstsz) -{ - FILE *fp; - char *ln = NULL; - size_t sz = 0; - ssize_t len; - int out = 0; - - if (lseek(fd, 0, SEEK_SET) < 0) { - log_warn("warn: mda: lseek"); - close(fd); - return (-1); - } - fp = fdopen(fd, "r"); - if (fp == NULL) { - log_warn("warn: mda: fdopen"); - close(fd); - return (-1); - } - while ((len = getline(&ln, &sz, fp)) != -1) { - if (ln[len - 1] == '\n') - ln[len - 1] = '\0'; - out = 1; - } - fclose(fp); - - if (out) { - (void)strlcpy(dst, "\"", dstsz); - (void)strnvis(dst + 1, ln, dstsz - 2, VIS_SAFE | VIS_CSTYLE | VIS_NL); - (void)strlcat(dst, "\"", dstsz); - } - - free(ln); - return (0); -} - -static void -mda_fail(struct mda_user *user, int permfail, const char *error, - enum enhanced_status_code code) -{ - struct mda_envelope *e; - - while ((e = TAILQ_FIRST(&user->envelopes))) { - TAILQ_REMOVE(&user->envelopes, e, entry); - if (permfail) { - mda_log(e, "PermFail", error); - mda_queue_permfail(e->id, error, code); - } - else { - mda_log(e, "TempFail", error); - mda_queue_tempfail(e->id, error, code); - } - mda_envelope_free(e); - } - - mda_user_free(user); -} - -static void -mda_drain(void) -{ - struct mda_user *u; - - while ((u = (TAILQ_FIRST(&runnable)))) { - - TAILQ_REMOVE(&runnable, u, entry_runnable); - - if (u->evpcount == 0 && u->running == 0) { - log_debug("debug: mda: all done for user \"%s\"", - mda_user_to_text(u)); - mda_user_free(u); - continue; - } - - if (u->evpcount == 0) { - log_debug("debug: mda: no more envelope for \"%s\"", - mda_user_to_text(u)); - u->flags &= ~USER_RUNNABLE; - continue; - } - - if (u->running >= env->sc_mda_max_user_session) { - log_debug("debug: mda: " - "maximum number of session reached for user \"%s\"", - mda_user_to_text(u)); - u->flags &= ~USER_RUNNABLE; - continue; - } - - if (tree_count(&sessions) >= env->sc_mda_max_session) { - log_debug("debug: mda: " - "maximum number of session reached"); - TAILQ_INSERT_HEAD(&runnable, u, entry_runnable); - return; - } - - mda_session(u); - - if (u->evpcount == env->sc_mda_task_lowat) { - if (u->flags & USER_ONHOLD) { - log_debug("debug: mda: down to lowat for user " - "\"%s\": releasing", - mda_user_to_text(u)); - u->flags &= ~USER_ONHOLD; - } - if (u->flags & USER_HOLDQ) { - m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, - 0, 0, -1); - m_add_id(p_queue, u->id); - m_add_int(p_queue, env->sc_mda_task_release); - m_close(p_queue); - } - } - - /* re-add the user at the tail of the queue */ - TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); - } -} - -static void -mda_done(struct mda_session *s) -{ - log_debug("debug: mda: session %016" PRIx64 " done", s->id); - - tree_xpop(&sessions, s->id); - - mda_envelope_free(s->evp); - - s->user->running--; - if (!(s->user->flags & USER_RUNNABLE)) { - log_debug("debug: mda: user \"%s\" becomes runnable", - s->user->name); - TAILQ_INSERT_TAIL(&runnable, s->user, entry_runnable); - s->user->flags |= USER_RUNNABLE; - } - - if (s->datafp) - fclose(s->datafp); - if (s->io) - io_free(s->io); - - free(s); - - stat_decrement("mda.running", 1); - - mda_drain(); -} - -static void -mda_log(const struct mda_envelope *evp, const char *prefix, const char *status) -{ - char rcpt[LINE_MAX]; - - rcpt[0] = '\0'; - if (evp->rcpt) - (void)snprintf(rcpt, sizeof rcpt, "rcpt=<%s> ", evp->rcpt); - - log_info("%016"PRIx64" mda delivery evpid=%016" PRIx64 " from=<%s> to=<%s> " - "%suser=%s delay=%s result=%s stat=%s", - evp->session_id, - evp->id, - evp->sender ? evp->sender : "", - evp->dest, - rcpt, - evp->user, - duration_to_text(time(NULL) - evp->creation), - prefix, - status); -} - -static void -mda_queue_ok(uint64_t evpid) -{ - m_create(p_queue, IMSG_MDA_DELIVERY_OK, 0, 0, -1); - m_add_evpid(p_queue, evpid); - m_close(p_queue); -} - -static void -mda_queue_tempfail(uint64_t evpid, const char *reason, - enum enhanced_status_code code) -{ - m_create(p_queue, IMSG_MDA_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_evpid(p_queue, evpid); - m_add_string(p_queue, reason); - m_add_int(p_queue, (int)code); - m_close(p_queue); -} - -static void -mda_queue_permfail(uint64_t evpid, const char *reason, - enum enhanced_status_code code) -{ - m_create(p_queue, IMSG_MDA_DELIVERY_PERMFAIL, 0, 0, -1); - m_add_evpid(p_queue, evpid); - m_add_string(p_queue, reason); - m_add_int(p_queue, (int)code); - m_close(p_queue); -} - -static void -mda_queue_loop(uint64_t evpid) -{ - m_create(p_queue, IMSG_MDA_DELIVERY_LOOP, 0, 0, -1); - m_add_evpid(p_queue, evpid); - m_close(p_queue); -} - -static struct mda_user * -mda_user(const struct envelope *evp) -{ - struct dispatcher *dsp; - struct mda_user *u; - void *i; - - i = NULL; - dsp = dict_xget(env->sc_dispatchers, evp->dispatcher); - while (tree_iter(&users, &i, NULL, (void**)(&u))) { - if (!strcmp(evp->mda_user, u->name) && - !strcmp(dsp->u.local.table_userbase, u->usertable)) - return (u); - } - - u = xcalloc(1, sizeof *u); - u->id = generate_uid(); - TAILQ_INIT(&u->envelopes); - (void)strlcpy(u->name, evp->mda_user, sizeof(u->name)); - (void)strlcpy(u->usertable, dsp->u.local.table_userbase, - sizeof(u->usertable)); - - tree_xset(&users, u->id, u); - - m_create(p_lka, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1); - m_add_id(p_lka, u->id); - m_add_string(p_lka, dsp->u.local.table_userbase); - m_add_string(p_lka, evp->mda_user); - m_close(p_lka); - u->flags |= USER_WAITINFO; - - stat_increment("mda.user", 1); - - if (dsp->u.local.user) - log_debug("mda: new user %016" PRIx64 - " for \"%s\" delivering as \"%s\"", - u->id, mda_user_to_text(u), dsp->u.local.user); - else - log_debug("mda: new user %016" PRIx64 - " for \"%s\"", u->id, mda_user_to_text(u)); - - return (u); -} - -static void -mda_user_free(struct mda_user *u) -{ - tree_xpop(&users, u->id); - - if (u->flags & USER_HOLDQ) { - m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, 0, 0, -1); - m_add_id(p_queue, u->id); - m_add_int(p_queue, 0); - m_close(p_queue); - } - - free(u); - stat_decrement("mda.user", 1); -} - -static const char * -mda_user_to_text(const struct mda_user *u) -{ - static char buf[1024]; - - (void)snprintf(buf, sizeof(buf), "%s:%s", u->usertable, u->name); - - return (buf); -} - -static struct mda_envelope * -mda_envelope(uint64_t session_id, const struct envelope *evp) -{ - struct mda_envelope *e; - char buf[LINE_MAX]; - - e = xcalloc(1, sizeof *e); - e->session_id = session_id; - e->id = evp->id; - e->creation = evp->creation; - buf[0] = '\0'; - if (evp->sender.user[0] && evp->sender.domain[0]) - (void)snprintf(buf, sizeof buf, "%s@%s", - evp->sender.user, evp->sender.domain); - e->sender = xstrdup(buf); - (void)snprintf(buf, sizeof buf, "%s@%s", evp->dest.user, - evp->dest.domain); - e->dest = xstrdup(buf); - (void)snprintf(buf, sizeof buf, "%s@%s", evp->rcpt.user, - evp->rcpt.domain); - e->rcpt = xstrdup(buf); - e->user = evp->mda_user[0] ? - xstrdup(evp->mda_user) : xstrdup(evp->dest.user); - e->dispatcher = xstrdup(evp->dispatcher); - if (evp->mda_exec[0]) - e->mda_exec = xstrdup(evp->mda_exec); - if (evp->mda_subaddress[0]) - e->mda_subaddress = xstrdup(evp->mda_subaddress); - stat_increment("mda.envelope", 1); - return (e); -} - -static void -mda_envelope_free(struct mda_envelope *e) -{ - free(e->sender); - free(e->dest); - free(e->rcpt); - free(e->user); - free(e->mda_exec); - free(e); - - stat_decrement("mda.envelope", 1); -} - -static struct mda_session * -mda_session(struct mda_user * u) -{ - struct mda_session *s; - - s = xcalloc(1, sizeof *s); - s->id = generate_uid(); - s->user = u; - s->io = io_new(); - io_set_callback(s->io, mda_io, s); - - tree_xset(&sessions, s->id, s); - - s->evp = TAILQ_FIRST(&u->envelopes); - TAILQ_REMOVE(&u->envelopes, s->evp, entry); - u->evpcount--; - u->running++; - - stat_decrement("mda.pending", 1); - stat_increment("mda.running", 1); - - log_debug("debug: mda: new session %016" PRIx64 - " for user \"%s\" evpid %016" PRIx64, s->id, - mda_user_to_text(u), s->evp->id); - - m_create(p_queue, IMSG_MDA_OPEN_MESSAGE, 0, 0, -1); - m_add_id(p_queue, s->id); - m_add_msgid(p_queue, evpid_to_msgid(s->evp->id)); - m_close(p_queue); - - return (s); -} - -static const char * -mda_sysexit_to_str(int sysexit) -{ - switch (sysexit) { - case EX_USAGE: - return "command line usage error"; - case EX_DATAERR: - return "data format error"; - case EX_NOINPUT: - return "cannot open input"; - case EX_NOUSER: - return "user unknown"; - case EX_NOHOST: - return "host name unknown"; - case EX_UNAVAILABLE: - return "service unavailable"; - case EX_SOFTWARE: - return "internal software error"; - case EX_OSERR: - return "system resource problem"; - case EX_OSFILE: - return "critical OS file missing"; - case EX_CANTCREAT: - return "can't create user output file"; - case EX_IOERR: - return "input/output error"; - case EX_TEMPFAIL: - return "temporary failure"; - case EX_PROTOCOL: - return "remote error in protocol"; - case EX_NOPERM: - return "permission denied"; - case EX_CONFIG: - return "local configuration error"; - default: - break; - } - return NULL; -} - diff --git a/smtpd/mda_mbox.c b/smtpd/mda_mbox.c deleted file mode 100644 index 8918e3ee..00000000 --- a/smtpd/mda_mbox.c +++ /dev/null @@ -1,94 +0,0 @@ -/* $OpenBSD: mda_mbox.c,v 1.2 2020/02/03 15:41:22 gilles Exp $ */ - -/* - * Copyright (c) 2018 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" - - -void -mda_mbox(struct deliver *deliver) -{ - int ret; - char sender[LINE_MAX]; - char *envp[] = { - "HOME=/", - "PATH=" _PATH_DEFPATH, - "LOGNAME=root", - "USER=root", - NULL, - }; - - if (deliver->sender.user[0] == '\0' && - deliver->sender.domain[0] == '\0') - ret = snprintf(sender, sizeof sender, "MAILER-DAEMON"); - else - ret = snprintf(sender, sizeof sender, "%s@%s", - deliver->sender.user, deliver->sender.domain); - if (ret < 0 || (size_t)ret >= sizeof sender) - errx(EX_TEMPFAIL, "sender address too long"); - - execle(PATH_MAILLOCAL, PATH_MAILLOCAL, "-f", - sender, deliver->userinfo.username, (char *)NULL, envp); - perror("execl"); - _exit(EX_TEMPFAIL); -} - -void -mda_mbox_init(struct deliver *deliver) -{ - int fd; - int ret; - char buffer[LINE_MAX]; - - ret = snprintf(buffer, sizeof buffer, "%s/%s", - _PATH_MAILDIR, deliver->userinfo.username); - if (ret < 0 || (size_t)ret >= sizeof buffer) - errx(EX_TEMPFAIL, "mailbox pathname too long"); - - if ((fd = open(buffer, O_CREAT|O_EXCL, 0)) == -1) { - if (errno == EEXIST) - return; - err(EX_TEMPFAIL, "open"); - } - - if (fchown(fd, deliver->userinfo.uid, deliver->userinfo.gid) == -1) - err(EX_TEMPFAIL, "fchown"); - - if (fchmod(fd, S_IRUSR|S_IWUSR) == -1) - err(EX_TEMPFAIL, "fchown"); -} diff --git a/smtpd/mda_unpriv.c b/smtpd/mda_unpriv.c deleted file mode 100644 index 2143b9a0..00000000 --- a/smtpd/mda_unpriv.c +++ /dev/null @@ -1,110 +0,0 @@ -/* $OpenBSD: mda_unpriv.c,v 1.6 2020/02/02 22:13:48 gilles Exp $ */ - -/* - * Copyright (c) 2018 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" - - -void -mda_unpriv(struct dispatcher *dsp, struct deliver *deliver, - const char *pw_name, const char *pw_dir) -{ - int idx; - char *mda_environ[11]; - char mda_exec[LINE_MAX]; - char mda_wrapper[LINE_MAX]; - const char *mda_command; - const char *mda_command_wrap; - - if (deliver->mda_exec[0]) - mda_command = deliver->mda_exec; - else - mda_command = dsp->u.local.command; - - if (strlcpy(mda_exec, mda_command, sizeof (mda_exec)) - >= sizeof (mda_exec)) - errx(1, "mda command line too long"); - - if (mda_expand_format(mda_exec, sizeof mda_exec, deliver, - &deliver->userinfo, NULL) == -1) - errx(1, "mda command line could not be expanded"); - - mda_command = mda_exec; - - /* setup environment similar to other MTA */ - idx = 0; - xasprintf(&mda_environ[idx++], "PATH=%s", _PATH_DEFPATH); - xasprintf(&mda_environ[idx++], "DOMAIN=%s", deliver->rcpt.domain); - xasprintf(&mda_environ[idx++], "HOME=%s", pw_dir); - xasprintf(&mda_environ[idx++], "RECIPIENT=%s@%s", deliver->dest.user, deliver->dest.domain); - xasprintf(&mda_environ[idx++], "SHELL=/bin/sh"); - xasprintf(&mda_environ[idx++], "LOCAL=%s", deliver->rcpt.user); - xasprintf(&mda_environ[idx++], "LOGNAME=%s", pw_name); - xasprintf(&mda_environ[idx++], "USER=%s", pw_name); - - if (deliver->sender.user[0]) - xasprintf(&mda_environ[idx++], "SENDER=%s@%s", - deliver->sender.user, deliver->sender.domain); - else - xasprintf(&mda_environ[idx++], "SENDER="); - - if (deliver->mda_subaddress[0]) - xasprintf(&mda_environ[idx++], "EXTENSION=%s", deliver->mda_subaddress); - - mda_environ[idx++] = (char *)NULL; - - if (dsp->u.local.mda_wrapper) { - mda_command_wrap = dict_get(env->sc_mda_wrappers, - dsp->u.local.mda_wrapper); - if (mda_command_wrap == NULL) - errx(1, "could not find wrapper %s", - dsp->u.local.mda_wrapper); - - if (strlcpy(mda_wrapper, mda_command_wrap, sizeof (mda_wrapper)) - >= sizeof (mda_wrapper)) - errx(1, "mda command line too long"); - - if (mda_expand_format(mda_wrapper, sizeof mda_wrapper, deliver, - &deliver->userinfo, mda_command) == -1) - errx(1, "mda command line could not be expanded"); - mda_command = mda_wrapper; - } - execle("/bin/sh", "/bin/sh", "-c", mda_command, (char *)NULL, - mda_environ); - - perror("execle"); - _exit(1); -} - diff --git a/smtpd/mda_variables.c b/smtpd/mda_variables.c deleted file mode 100644 index b672e492..00000000 --- a/smtpd/mda_variables.c +++ /dev/null @@ -1,374 +0,0 @@ -/* $OpenBSD: mda_variables.c,v 1.6 2019/09/19 07:35:36 gilles Exp $ */ - -/* - * Copyright (c) 2011-2017 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define EXPAND_DEPTH 10 - -ssize_t mda_expand_format(char *, size_t, const struct deliver *, - const struct userinfo *, const char *); -static ssize_t mda_expand_token(char *, size_t, const char *, - const struct deliver *, const struct userinfo *, const char *); -static int mod_lowercase(char *, size_t); -static int mod_uppercase(char *, size_t); -static int mod_strip(char *, size_t); - -static struct modifiers { - char *name; - int (*f)(char *buf, size_t len); -} token_modifiers[] = { - { "lowercase", mod_lowercase }, - { "uppercase", mod_uppercase }, - { "strip", mod_strip }, - { "raw", NULL }, /* special case, must stay last */ -}; - -#define MAXTOKENLEN 128 - -static ssize_t -mda_expand_token(char *dest, size_t len, const char *token, - const struct deliver *dlv, const struct userinfo *ui, const char *mda_command) -{ - char rtoken[MAXTOKENLEN]; - char tmp[EXPAND_BUFFER]; - const char *string; - char *lbracket, *rbracket, *content, *sep, *mods; - ssize_t i; - ssize_t begoff, endoff; - const char *errstr = NULL; - int replace = 1; - int raw = 0; - - begoff = 0; - endoff = EXPAND_BUFFER; - mods = NULL; - - if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken) - return -1; - - /* token[x[:y]] -> extracts optional x and y, converts into offsets */ - if ((lbracket = strchr(rtoken, '[')) && - (rbracket = strchr(rtoken, ']'))) { - /* ] before [ ... or empty */ - if (rbracket < lbracket || rbracket - lbracket <= 1) - return -1; - - *lbracket = *rbracket = '\0'; - content = lbracket + 1; - - if ((sep = strchr(content, ':')) == NULL) - endoff = begoff = strtonum(content, -EXPAND_BUFFER, - EXPAND_BUFFER, &errstr); - else { - *sep = '\0'; - if (content != sep) - begoff = strtonum(content, -EXPAND_BUFFER, - EXPAND_BUFFER, &errstr); - if (*(++sep)) { - if (errstr == NULL) - endoff = strtonum(sep, -EXPAND_BUFFER, - EXPAND_BUFFER, &errstr); - } - } - if (errstr) - return -1; - - /* token:mod_1,mod_2,mod_n -> extract modifiers */ - mods = strchr(rbracket + 1, ':'); - } else { - if ((mods = strchr(rtoken, ':')) != NULL) - *mods++ = '\0'; - } - - /* token -> expanded token */ - if (!strcasecmp("sender", rtoken)) { - if (snprintf(tmp, sizeof tmp, "%s@%s", - dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) - return -1; - if (strcmp(tmp, "@") == 0) - (void)strlcpy(tmp, "", sizeof tmp); - string = tmp; - } - else if (!strcasecmp("rcpt", rtoken)) { - if (snprintf(tmp, sizeof tmp, "%s@%s", - dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp) - return -1; - if (strcmp(tmp, "@") == 0) - (void)strlcpy(tmp, "", sizeof tmp); - string = tmp; - } - else if (!strcasecmp("dest", rtoken)) { - if (snprintf(tmp, sizeof tmp, "%s@%s", - dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp) - return -1; - if (strcmp(tmp, "@") == 0) - (void)strlcpy(tmp, "", sizeof tmp); - string = tmp; - } - else if (!strcasecmp("sender.user", rtoken)) - string = dlv->sender.user; - else if (!strcasecmp("sender.domain", rtoken)) - string = dlv->sender.domain; - else if (!strcasecmp("user.username", rtoken)) - string = ui->username; - else if (!strcasecmp("user.directory", rtoken)) { - string = ui->directory; - replace = 0; - } - else if (!strcasecmp("rcpt.user", rtoken)) - string = dlv->rcpt.user; - else if (!strcasecmp("rcpt.domain", rtoken)) - string = dlv->rcpt.domain; - else if (!strcasecmp("dest.user", rtoken)) - string = dlv->dest.user; - else if (!strcasecmp("dest.domain", rtoken)) - string = dlv->dest.domain; - else if (!strcasecmp("mda", rtoken)) { - string = mda_command; - replace = 0; - } - else if (!strcasecmp("mbox.from", rtoken)) { - if (snprintf(tmp, sizeof tmp, "%s@%s", - dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) - return -1; - if (strcmp(tmp, "@") == 0) - (void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp); - string = tmp; - } - else - return -1; - - if (string != tmp) { - if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) - return -1; - string = tmp; - } - - /* apply modifiers */ - if (mods != NULL) { - do { - if ((sep = strchr(mods, '|')) != NULL) - *sep++ = '\0'; - for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { - if (!strcasecmp(token_modifiers[i].name, mods)) { - if (token_modifiers[i].f == NULL) { - raw = 1; - break; - } - if (!token_modifiers[i].f(tmp, sizeof tmp)) - return -1; /* modifier error */ - break; - } - } - if ((size_t)i == nitems(token_modifiers)) - return -1; /* modifier not found */ - } while ((mods = sep) != NULL); - } - - if (!raw && replace) - for (i = 0; (size_t)i < strlen(tmp); ++i) - if (strchr(MAILADDR_ESCAPE, tmp[i])) - tmp[i] = ':'; - - /* expanded string is empty */ - i = strlen(string); - if (i == 0) - return 0; - - /* begin offset beyond end of string */ - if (begoff >= i) - return -1; - - /* end offset beyond end of string, make it end of string */ - if (endoff >= i) - endoff = i - 1; - - /* negative begin offset, make it relative to end of string */ - if (begoff < 0) - begoff += i; - /* negative end offset, make it relative to end of string, - * note that end offset is inclusive. - */ - if (endoff < 0) - endoff += i - 1; - - /* check that final offsets are valid */ - if (begoff < 0 || endoff < 0 || endoff < begoff) - return -1; - endoff += 1; /* end offset is inclusive */ - - /* check that substring does not exceed destination buffer length */ - i = endoff - begoff; - if ((size_t)i + 1 >= len) - return -1; - - string += begoff; - for (; i; i--) { - *dest = *string; - dest++; - string++; - } - - return endoff - begoff; -} - - -ssize_t -mda_expand_format(char *buf, size_t len, const struct deliver *dlv, - const struct userinfo *ui, const char *mda_command) -{ - char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; - char exptok[EXPAND_BUFFER]; - ssize_t exptoklen; - char token[MAXTOKENLEN]; - size_t ret, tmpret; - - if (len < sizeof tmpbuf) { - log_warnx("mda_expand_format: tmp buffer < rule buffer"); - return -1; - } - - memset(tmpbuf, 0, sizeof tmpbuf); - pbuf = buf; - ptmp = tmpbuf; - ret = tmpret = 0; - - /* special case: ~/ only allowed expanded at the beginning */ - if (strncmp(pbuf, "~/", 2) == 0) { - tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); - if (tmpret >= sizeof tmpbuf) { - log_warnx("warn: user directory for %s too large", - ui->directory); - return 0; - } - ret += tmpret; - ptmp += tmpret; - pbuf += 2; - } - - - /* expansion loop */ - for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { - if (*pbuf == '%' && *(pbuf + 1) == '%') { - *ptmp++ = *pbuf++; - pbuf += 1; - tmpret = 1; - continue; - } - - if (*pbuf != '%' || *(pbuf + 1) != '{') { - *ptmp++ = *pbuf++; - tmpret = 1; - continue; - } - - /* %{...} otherwise fail */ - if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL) - return 0; - - /* extract token from %{token} */ - if ((size_t)(ebuf - pbuf) - 1 >= sizeof token) - return 0; - - memcpy(token, pbuf+2, ebuf-pbuf-1); - if (strchr(token, '}') == NULL) - return 0; - *strchr(token, '}') = '\0'; - - exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv, - ui, mda_command); - if (exptoklen == -1) - return -1; - - /* writing expanded token at ptmp will overflow tmpbuf */ - if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen) - return -1; - - memcpy(ptmp, exptok, exptoklen); - pbuf = ebuf + 1; - ptmp += exptoklen; - tmpret = exptoklen; - } - if (ret >= sizeof tmpbuf) - return -1; - - if ((ret = strlcpy(buf, tmpbuf, len)) >= len) - return -1; - - return ret; -} - -static int -mod_lowercase(char *buf, size_t len) -{ - char tmp[EXPAND_BUFFER]; - - if (!lowercase(tmp, buf, sizeof tmp)) - return 0; - if (strlcpy(buf, tmp, len) >= len) - return 0; - return 1; -} - -static int -mod_uppercase(char *buf, size_t len) -{ - char tmp[EXPAND_BUFFER]; - - if (!uppercase(tmp, buf, sizeof tmp)) - return 0; - if (strlcpy(buf, tmp, len) >= len) - return 0; - return 1; -} - -static int -mod_strip(char *buf, size_t len) -{ - char *tag, *at; - unsigned int i; - - /* gilles+hackers -> gilles */ - if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) { - /* gilles+hackers@poolp.org -> gilles@poolp.org */ - if ((at = strchr(tag, '@')) != NULL) { - for (i = 0; i <= strlen(at); ++i) - tag[i] = at[i]; - } else - *tag = '\0'; - } - return 1; -} diff --git a/smtpd/mproc.c b/smtpd/mproc.c deleted file mode 100644 index bde229e1..00000000 --- a/smtpd/mproc.c +++ /dev/null @@ -1,676 +0,0 @@ -/* $OpenBSD: mproc.c,v 1.36 2020/03/17 09:01:53 tobhe Exp $ */ - -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static void mproc_dispatch(int, short, void *); - -static ssize_t imsg_read_nofd(struct imsgbuf *); - -int -mproc_fork(struct mproc *p, const char *path, char *argv[]) -{ - int sp[2]; - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) - return (-1); - - io_set_nonblocking(sp[0]); - io_set_nonblocking(sp[1]); - - if ((p->pid = fork()) == -1) - goto err; - - if (p->pid == 0) { - /* child process */ - dup2(sp[0], STDIN_FILENO); - closefrom(STDERR_FILENO + 1); - execv(path, argv); - err(1, "execv: %s", path); - } - - /* parent process */ - close(sp[0]); - mproc_init(p, sp[1]); - return (0); - -err: - log_warn("warn: Failed to start process %s, instance of %s", argv[0], path); - close(sp[0]); - close(sp[1]); - return (-1); -} - -void -mproc_init(struct mproc *p, int fd) -{ - imsg_init(&p->imsgbuf, fd); -} - -void -mproc_clear(struct mproc *p) -{ - log_debug("debug: clearing p=%s, fd=%d, pid=%d", p->name, p->imsgbuf.fd, p->pid); - - event_del(&p->ev); - close(p->imsgbuf.fd); - imsg_clear(&p->imsgbuf); -} - -void -mproc_enable(struct mproc *p) -{ - if (p->enable == 0) { - log_trace(TRACE_MPROC, "mproc: %s -> %s: enabled", - proc_name(smtpd_process), - proc_name(p->proc)); - p->enable = 1; - } - mproc_event_add(p); -} - -void -mproc_disable(struct mproc *p) -{ - if (p->enable == 1) { - log_trace(TRACE_MPROC, "mproc: %s -> %s: disabled", - proc_name(smtpd_process), - proc_name(p->proc)); - p->enable = 0; - } - mproc_event_add(p); -} - -void -mproc_event_add(struct mproc *p) -{ - short events; - - if (p->enable) - events = EV_READ; - else - events = 0; - - if (p->imsgbuf.w.queued) - events |= EV_WRITE; - - if (p->events) - event_del(&p->ev); - - p->events = events; - if (events) { - event_set(&p->ev, p->imsgbuf.fd, events, mproc_dispatch, p); - event_add(&p->ev, NULL); - } -} - -static void -mproc_dispatch(int fd, short event, void *arg) -{ - struct mproc *p = arg; - struct imsg imsg; - ssize_t n; - - p->events = 0; - - if (event & EV_READ) { - - if (p->proc == PROC_CLIENT) - n = imsg_read_nofd(&p->imsgbuf); - else - n = imsg_read(&p->imsgbuf); - - switch (n) { - case -1: - if (errno == EAGAIN) - break; - log_warn("warn: %s -> %s: imsg_read", - proc_name(smtpd_process), p->name); - fatal("exiting"); - /* NOTREACHED */ - case 0: - /* this pipe is dead, so remove the event handler */ - log_debug("debug: %s -> %s: pipe closed", - proc_name(smtpd_process), p->name); - p->handler(p, NULL); - return; - default: - break; - } - } - - if (event & EV_WRITE) { - n = msgbuf_write(&p->imsgbuf.w); - if (n == 0 || (n == -1 && errno != EAGAIN)) { - /* this pipe is dead, so remove the event handler */ - log_debug("debug: %s -> %s: pipe closed", - proc_name(smtpd_process), p->name); - p->handler(p, NULL); - return; - } - } - - for (;;) { - if ((n = imsg_get(&p->imsgbuf, &imsg)) == -1) { - - if (smtpd_process == PROC_CONTROL && - p->proc == PROC_CLIENT) { - log_warnx("warn: client sent invalid imsg " - "over control socket"); - p->handler(p, NULL); - return; - } - log_warn("fatal: %s: error in imsg_get for %s", - proc_name(smtpd_process), p->name); - fatalx(NULL); - } - if (n == 0) - break; - - p->handler(p, &imsg); - - imsg_free(&imsg); - } - - mproc_event_add(p); -} - -/* This should go into libutil */ -static ssize_t -imsg_read_nofd(struct imsgbuf *ibuf) -{ - ssize_t n; - char *buf; - size_t len; - - buf = ibuf->r.buf + ibuf->r.wpos; - len = sizeof(ibuf->r.buf) - ibuf->r.wpos; - - while ((n = recv(ibuf->fd, buf, len, 0)) == -1) { - if (errno != EINTR) - return (n); - } - - ibuf->r.wpos += n; - return (n); -} - -void -m_forward(struct mproc *p, struct imsg *imsg) -{ - imsg_compose(&p->imsgbuf, imsg->hdr.type, imsg->hdr.peerid, - imsg->hdr.pid, imsg->fd, imsg->data, - imsg->hdr.len - sizeof(imsg->hdr)); - - if (imsg->hdr.type != IMSG_STAT_DECREMENT && - imsg->hdr.type != IMSG_STAT_INCREMENT) - log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s (forward)", - proc_name(smtpd_process), - proc_name(p->proc), - imsg->hdr.len - sizeof(imsg->hdr), - imsg_to_str(imsg->hdr.type)); - - mproc_event_add(p); -} - -void -m_compose(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd, - void *data, size_t len) -{ - imsg_compose(&p->imsgbuf, type, peerid, pid, fd, data, len); - - if (type != IMSG_STAT_DECREMENT && - type != IMSG_STAT_INCREMENT) - log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", - proc_name(smtpd_process), - proc_name(p->proc), - len, - imsg_to_str(type)); - - mproc_event_add(p); -} - -void -m_composev(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, - int fd, const struct iovec *iov, int n) -{ - size_t len; - int i; - - imsg_composev(&p->imsgbuf, type, peerid, pid, fd, iov, n); - - len = 0; - for (i = 0; i < n; i++) - len += iov[i].iov_len; - - if (type != IMSG_STAT_DECREMENT && - type != IMSG_STAT_INCREMENT) - log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", - proc_name(smtpd_process), - proc_name(p->proc), - len, - imsg_to_str(type)); - - mproc_event_add(p); -} - -void -m_create(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd) -{ - p->m_pos = 0; - p->m_type = type; - p->m_peerid = peerid; - p->m_pid = pid; - p->m_fd = fd; -} - -void -m_add(struct mproc *p, const void *data, size_t len) -{ - size_t alloc; - void *tmp; - - if (p->m_pos + len + IMSG_HEADER_SIZE > MAX_IMSGSIZE) { - log_warnx("warn: message too large"); - fatal(NULL); - } - - alloc = p->m_alloc ? p->m_alloc : 128; - while (p->m_pos + len > alloc) - alloc *= 2; - if (alloc != p->m_alloc) { - log_trace(TRACE_MPROC, "mproc: %s -> %s: realloc %zu -> %zu", - proc_name(smtpd_process), - proc_name(p->proc), - p->m_alloc, - alloc); - - tmp = recallocarray(p->m_buf, p->m_alloc, alloc, 1); - if (tmp == NULL) - fatal("realloc"); - p->m_alloc = alloc; - p->m_buf = tmp; - } - - memmove(p->m_buf + p->m_pos, data, len); - p->m_pos += len; -} - -void -m_close(struct mproc *p) -{ - if (imsg_compose(&p->imsgbuf, p->m_type, p->m_peerid, p->m_pid, p->m_fd, - p->m_buf, p->m_pos) == -1) - fatal("imsg_compose"); - - log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", - proc_name(smtpd_process), - proc_name(p->proc), - p->m_pos, - imsg_to_str(p->m_type)); - - mproc_event_add(p); -} - -void -m_flush(struct mproc *p) -{ - if (imsg_compose(&p->imsgbuf, p->m_type, p->m_peerid, p->m_pid, p->m_fd, - p->m_buf, p->m_pos) == -1) - fatal("imsg_compose"); - - log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s (flush)", - proc_name(smtpd_process), - proc_name(p->proc), - p->m_pos, - imsg_to_str(p->m_type)); - - p->m_pos = 0; - - if (imsg_flush(&p->imsgbuf) == -1) - fatal("imsg_flush"); -} - -static struct imsg * current; - -static void -m_error(const char *error) -{ - char buf[512]; - - (void)snprintf(buf, sizeof buf, "%s: %s: %s", - proc_name(smtpd_process), - imsg_to_str(current->hdr.type), - error); - fatalx("%s", buf); -} - -void -m_msg(struct msg *m, struct imsg *imsg) -{ - current = imsg; - m->pos = imsg->data; - m->end = m->pos + (imsg->hdr.len - sizeof(imsg->hdr)); -} - -void -m_end(struct msg *m) -{ - if (m->pos != m->end) - m_error("not at msg end"); -} - -int -m_is_eom(struct msg *m) -{ - return (m->pos == m->end); -} - -static inline void -m_get(struct msg *m, void *dst, size_t sz) -{ - if (sz > MAX_IMSGSIZE || - m->end - m->pos < (ssize_t)sz) - fatalx("msg too short"); - - memmove(dst, m->pos, sz); - m->pos += sz; -} - -void -m_add_int(struct mproc *m, int v) -{ - m_add(m, &v, sizeof(v)); -}; - -void -m_add_u32(struct mproc *m, uint32_t u32) -{ - m_add(m, &u32, sizeof(u32)); -}; - -void -m_add_size(struct mproc *m, size_t sz) -{ - m_add(m, &sz, sizeof(sz)); -}; - -void -m_add_time(struct mproc *m, time_t v) -{ - m_add(m, &v, sizeof(v)); -}; - -void -m_add_timeval(struct mproc *m, struct timeval *tv) -{ - m_add(m, tv, sizeof(*tv)); -} - - -void -m_add_string(struct mproc *m, const char *v) -{ - if (v) { - m_add(m, "s", 1); - m_add(m, v, strlen(v) + 1); - } - else - m_add(m, "\0", 1); -}; - -void -m_add_data(struct mproc *m, const void *v, size_t len) -{ - m_add_size(m, len); - m_add(m, v, len); -}; - -void -m_add_id(struct mproc *m, uint64_t v) -{ - m_add(m, &v, sizeof(v)); -} - -void -m_add_evpid(struct mproc *m, uint64_t v) -{ - m_add(m, &v, sizeof(v)); -} - -void -m_add_msgid(struct mproc *m, uint32_t v) -{ - m_add(m, &v, sizeof(v)); -} - -void -m_add_sockaddr(struct mproc *m, const struct sockaddr *sa) -{ - m_add_size(m, SA_LEN(sa)); - m_add(m, sa, SA_LEN(sa)); -} - -void -m_add_mailaddr(struct mproc *m, const struct mailaddr *maddr) -{ - m_add(m, maddr, sizeof(*maddr)); -} - -void -m_add_envelope(struct mproc *m, const struct envelope *evp) -{ - char buf[sizeof(*evp)]; - - envelope_dump_buffer(evp, buf, sizeof(buf)); - m_add_evpid(m, evp->id); - m_add_string(m, buf); -} - -void -m_add_params(struct mproc *m, struct dict *d) -{ - const char *key; - char *value; - void *iter; - - if (d == NULL) { - m_add_size(m, 0); - return; - } - m_add_size(m, dict_count(d)); - iter = NULL; - while (dict_iter(d, &iter, &key, (void **)&value)) { - m_add_string(m, key); - m_add_string(m, value); - } -} - -void -m_get_int(struct msg *m, int *i) -{ - m_get(m, i, sizeof(*i)); -} - -void -m_get_u32(struct msg *m, uint32_t *u32) -{ - m_get(m, u32, sizeof(*u32)); -} - -void -m_get_size(struct msg *m, size_t *sz) -{ - m_get(m, sz, sizeof(*sz)); -} - -void -m_get_time(struct msg *m, time_t *t) -{ - m_get(m, t, sizeof(*t)); -} - -void -m_get_timeval(struct msg *m, struct timeval *tv) -{ - m_get(m, tv, sizeof(*tv)); -} - -void -m_get_string(struct msg *m, const char **s) -{ - uint8_t *end; - char c; - - if (m->pos >= m->end) - m_error("msg too short"); - - c = *m->pos++; - if (c == '\0') { - *s = NULL; - return; - } - - if (m->pos >= m->end) - m_error("msg too short"); - end = memchr(m->pos, 0, m->end - m->pos); - if (end == NULL) - m_error("unterminated string"); - - *s = m->pos; - m->pos = end + 1; -} - -void -m_get_data(struct msg *m, const void **data, size_t *sz) -{ - m_get_size(m, sz); - - if (*sz == 0) { - *data = NULL; - return; - } - - if (m->pos + *sz > m->end) - m_error("msg too short"); - - *data = m->pos; - m->pos += *sz; -} - -void -m_get_evpid(struct msg *m, uint64_t *evpid) -{ - m_get(m, evpid, sizeof(*evpid)); -} - -void -m_get_msgid(struct msg *m, uint32_t *msgid) -{ - m_get(m, msgid, sizeof(*msgid)); -} - -void -m_get_id(struct msg *m, uint64_t *id) -{ - m_get(m, id, sizeof(*id)); -} - -void -m_get_sockaddr(struct msg *m, struct sockaddr *sa) -{ - size_t len; - - m_get_size(m, &len); - m_get(m, sa, len); -} - -void -m_get_mailaddr(struct msg *m, struct mailaddr *maddr) -{ - m_get(m, maddr, sizeof(*maddr)); -} - -void -m_get_envelope(struct msg *m, struct envelope *evp) -{ - uint64_t evpid; - const char *buf; - - m_get_evpid(m, &evpid); - m_get_string(m, &buf); - if (buf == NULL) - fatalx("empty envelope buffer"); - - if (!envelope_load_buffer(evp, buf, strlen(buf))) - fatalx("failed to retrieve envelope"); - evp->id = evpid; -} - -void -m_get_params(struct msg *m, struct dict *d) -{ - size_t c; - const char *key; - const char *value; - char *tmp; - - dict_init(d); - - m_get_size(m, &c); - - for (; c; c--) { - m_get_string(m, &key); - m_get_string(m, &value); - if ((tmp = strdup(value)) == NULL) - fatal("m_get_params"); - dict_set(d, key, tmp); - } -} - -void -m_clear_params(struct dict *d) -{ - char *value; - - while (dict_poproot(d, (void **)&value)) - free(value); -} diff --git a/smtpd/mta.c b/smtpd/mta.c deleted file mode 100644 index 922170ae..00000000 --- a/smtpd/mta.c +++ /dev/null @@ -1,2647 +0,0 @@ -/* $OpenBSD: mta.c,v 1.234 2019/12/21 10:34:07 gilles Exp $ */ - -/* - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define MAXERROR_PER_ROUTE 4 - -#define DELAY_CHECK_SOURCE 1 -#define DELAY_CHECK_SOURCE_SLOW 10 -#define DELAY_CHECK_SOURCE_FAST 0 -#define DELAY_CHECK_LIMIT 5 - -#define DELAY_QUADRATIC 1 -#define DELAY_ROUTE_BASE 15 -#define DELAY_ROUTE_MAX 3600 - -#define RELAY_ONHOLD 0x01 -#define RELAY_HOLDQ 0x02 - -static void mta_handle_envelope(struct envelope *, const char *); -static void mta_query_smarthost(struct envelope *); -static void mta_on_smarthost(struct envelope *, const char *); -static void mta_query_mx(struct mta_relay *); -static void mta_query_secret(struct mta_relay *); -static void mta_query_preference(struct mta_relay *); -static void mta_query_source(struct mta_relay *); -static void mta_on_mx(void *, void *, void *); -static void mta_on_secret(struct mta_relay *, const char *); -static void mta_on_preference(struct mta_relay *, int); -static void mta_on_source(struct mta_relay *, struct mta_source *); -static void mta_on_timeout(struct runq *, void *); -static void mta_connect(struct mta_connector *); -static void mta_route_enable(struct mta_route *); -static void mta_route_disable(struct mta_route *, int, int); -static void mta_drain(struct mta_relay *); -static void mta_delivery_flush_event(int, short, void *); -static void mta_flush(struct mta_relay *, int, const char *); -static struct mta_route *mta_find_route(struct mta_connector *, time_t, int*, - time_t*, struct mta_mx **); -static void mta_log(const struct mta_envelope *, const char *, const char *, - const char *, const char *); - -SPLAY_HEAD(mta_relay_tree, mta_relay); -static struct mta_relay *mta_relay(struct envelope *, struct relayhost *); -static void mta_relay_ref(struct mta_relay *); -static void mta_relay_unref(struct mta_relay *); -static void mta_relay_show(struct mta_relay *, struct mproc *, uint32_t, time_t); -static int mta_relay_cmp(const struct mta_relay *, const struct mta_relay *); -SPLAY_PROTOTYPE(mta_relay_tree, mta_relay, entry, mta_relay_cmp); - -SPLAY_HEAD(mta_host_tree, mta_host); -static struct mta_host *mta_host(const struct sockaddr *); -static void mta_host_ref(struct mta_host *); -static void mta_host_unref(struct mta_host *); -static int mta_host_cmp(const struct mta_host *, const struct mta_host *); -SPLAY_PROTOTYPE(mta_host_tree, mta_host, entry, mta_host_cmp); - -SPLAY_HEAD(mta_domain_tree, mta_domain); -static struct mta_domain *mta_domain(char *, int); -#if 0 -static void mta_domain_ref(struct mta_domain *); -#endif -static void mta_domain_unref(struct mta_domain *); -static int mta_domain_cmp(const struct mta_domain *, const struct mta_domain *); -SPLAY_PROTOTYPE(mta_domain_tree, mta_domain, entry, mta_domain_cmp); - -SPLAY_HEAD(mta_source_tree, mta_source); -static struct mta_source *mta_source(const struct sockaddr *); -static void mta_source_ref(struct mta_source *); -static void mta_source_unref(struct mta_source *); -static const char *mta_source_to_text(struct mta_source *); -static int mta_source_cmp(const struct mta_source *, const struct mta_source *); -SPLAY_PROTOTYPE(mta_source_tree, mta_source, entry, mta_source_cmp); - -static struct mta_connector *mta_connector(struct mta_relay *, - struct mta_source *); -static void mta_connector_free(struct mta_connector *); -static const char *mta_connector_to_text(struct mta_connector *); - -SPLAY_HEAD(mta_route_tree, mta_route); -static struct mta_route *mta_route(struct mta_source *, struct mta_host *); -static void mta_route_ref(struct mta_route *); -static void mta_route_unref(struct mta_route *); -static const char *mta_route_to_text(struct mta_route *); -static int mta_route_cmp(const struct mta_route *, const struct mta_route *); -SPLAY_PROTOTYPE(mta_route_tree, mta_route, entry, mta_route_cmp); - -struct mta_block { - SPLAY_ENTRY(mta_block) entry; - struct mta_source *source; - char *domain; -}; - -SPLAY_HEAD(mta_block_tree, mta_block); -void mta_block(struct mta_source *, char *); -void mta_unblock(struct mta_source *, char *); -int mta_is_blocked(struct mta_source *, char *); -static int mta_block_cmp(const struct mta_block *, const struct mta_block *); -SPLAY_PROTOTYPE(mta_block_tree, mta_block, entry, mta_block_cmp); - -static struct mta_relay_tree relays; -static struct mta_domain_tree domains; -static struct mta_host_tree hosts; -static struct mta_source_tree sources; -static struct mta_route_tree routes; -static struct mta_block_tree blocks; - -static struct tree wait_mx; -static struct tree wait_preference; -static struct tree wait_secret; -static struct tree wait_smarthost; -static struct tree wait_source; -static struct tree flush_evp; -static struct event ev_flush_evp; - -static struct runq *runq_relay; -static struct runq *runq_connector; -static struct runq *runq_route; -static struct runq *runq_hoststat; - -static time_t max_seen_conndelay_route; -static time_t max_seen_discdelay_route; - -#define HOSTSTAT_EXPIRE_DELAY (4 * 3600) -struct hoststat { - char name[HOST_NAME_MAX+1]; - time_t tm; - char error[LINE_MAX]; - struct tree deferred; -}; -static struct dict hoststat; - -void mta_hoststat_update(const char *, const char *); -void mta_hoststat_cache(const char *, uint64_t); -void mta_hoststat_uncache(const char *, uint64_t); -void mta_hoststat_reschedule(const char *); -static void mta_hoststat_remove_entry(struct hoststat *); - -void -mta_imsg(struct mproc *p, struct imsg *imsg) -{ - struct mta_relay *relay; - struct mta_domain *domain; - struct mta_host *host; - struct mta_route *route; - struct mta_block *block; - struct mta_mx *mx, *imx; - struct mta_source *source; - struct hoststat *hs; - struct sockaddr_storage ss; - struct envelope evp, *e; - struct msg m; - const char *secret; - const char *hostname; - const char *dom; - const char *smarthost; - uint64_t reqid; - time_t t; - char buf[LINE_MAX]; - int dnserror, preference, v, status; - void *iter; - uint64_t u64; - - switch (imsg->hdr.type) { - case IMSG_QUEUE_TRANSFER: - m_msg(&m, imsg); - m_get_envelope(&m, &evp); - m_end(&m); - mta_handle_envelope(&evp, NULL); - return; - - case IMSG_MTA_OPEN_MESSAGE: - mta_session_imsg(p, imsg); - return; - - case IMSG_MTA_LOOKUP_CREDENTIALS: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &secret); - m_end(&m); - relay = tree_xpop(&wait_secret, reqid); - mta_on_secret(relay, secret[0] ? secret : NULL); - return; - - case IMSG_MTA_LOOKUP_SOURCE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &status); - if (status == LKA_OK) - m_get_sockaddr(&m, (struct sockaddr*)&ss); - m_end(&m); - - relay = tree_xpop(&wait_source, reqid); - mta_on_source(relay, (status == LKA_OK) ? - mta_source((struct sockaddr *)&ss) : NULL); - return; - - case IMSG_MTA_LOOKUP_SMARTHOST: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &status); - smarthost = NULL; - if (status == LKA_OK) - m_get_string(&m, &smarthost); - m_end(&m); - - e = tree_xpop(&wait_smarthost, reqid); - mta_on_smarthost(e, smarthost); - return; - - case IMSG_MTA_LOOKUP_HELO: - mta_session_imsg(p, imsg); - return; - - case IMSG_MTA_DNS_HOST: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &hostname); - m_get_sockaddr(&m, (struct sockaddr*)&ss); - m_get_int(&m, &preference); - m_end(&m); - domain = tree_xget(&wait_mx, reqid); - mx = xcalloc(1, sizeof *mx); - mx->mxname = xstrdup(hostname); - mx->host = mta_host((struct sockaddr*)&ss); - mx->preference = preference; - TAILQ_FOREACH(imx, &domain->mxs, entry) { - if (imx->preference > mx->preference) { - TAILQ_INSERT_BEFORE(imx, mx, entry); - return; - } - } - TAILQ_INSERT_TAIL(&domain->mxs, mx, entry); - return; - - case IMSG_MTA_DNS_HOST_END: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &dnserror); - m_end(&m); - domain = tree_xpop(&wait_mx, reqid); - domain->mxstatus = dnserror; - if (domain->mxstatus == DNS_OK) { - log_debug("debug: MXs for domain %s:", - domain->name); - TAILQ_FOREACH(mx, &domain->mxs, entry) - log_debug(" %s preference %d", - sa_to_text(mx->host->sa), - mx->preference); - } - else { - log_debug("debug: Failed MX query for %s:", - domain->name); - } - domain->lastmxquery = time(NULL); - waitq_run(&domain->mxs, domain); - return; - - case IMSG_MTA_DNS_MX_PREFERENCE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &dnserror); - if (dnserror == 0) - m_get_int(&m, &preference); - m_end(&m); - - relay = tree_xpop(&wait_preference, reqid); - if (dnserror) { - log_warnx("warn: Couldn't find backup " - "preference for %s: error %d", - mta_relay_to_text(relay), dnserror); - preference = INT_MAX; - } - mta_on_preference(relay, preference); - return; - - case IMSG_CTL_RESUME_ROUTE: - u64 = *((uint64_t *)imsg->data); - if (u64) - log_debug("resuming route: %llu", - (unsigned long long)u64); - else - log_debug("resuming all routes"); - SPLAY_FOREACH(route, mta_route_tree, &routes) { - if (u64 && route->id != u64) - continue; - - if (route->flags & ROUTE_DISABLED) { - log_info("smtp-out: Enabling route %s per admin request", - mta_route_to_text(route)); - if (!runq_cancel(runq_route, route)) { - log_warnx("warn: route not on runq"); - fatalx("exiting"); - } - route->flags &= ~ROUTE_DISABLED; - route->flags |= ROUTE_NEW; - route->nerror = 0; - route->penalty = 0; - mta_route_unref(route); /* from mta_route_disable */ - } - - if (u64) - break; - } - return; - - case IMSG_CTL_MTA_SHOW_HOSTS: - t = time(NULL); - SPLAY_FOREACH(host, mta_host_tree, &hosts) { - (void)snprintf(buf, sizeof(buf), - "%s %s refcount=%d nconn=%zu lastconn=%s", - sockaddr_to_text(host->sa), - host->ptrname, - host->refcount, - host->nconn, - host->lastconn ? duration_to_text(t - host->lastconn) : "-"); - m_compose(p, IMSG_CTL_MTA_SHOW_HOSTS, - imsg->hdr.peerid, 0, -1, - buf, strlen(buf) + 1); - } - m_compose(p, IMSG_CTL_MTA_SHOW_HOSTS, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_CTL_MTA_SHOW_RELAYS: - t = time(NULL); - SPLAY_FOREACH(relay, mta_relay_tree, &relays) - mta_relay_show(relay, p, imsg->hdr.peerid, t); - m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_CTL_MTA_SHOW_ROUTES: - SPLAY_FOREACH(route, mta_route_tree, &routes) { - v = runq_pending(runq_route, route, &t); - (void)snprintf(buf, sizeof(buf), - "%llu. %s %c%c%c%c nconn=%zu nerror=%d penalty=%d timeout=%s", - (unsigned long long)route->id, - mta_route_to_text(route), - route->flags & ROUTE_NEW ? 'N' : '-', - route->flags & ROUTE_DISABLED ? 'D' : '-', - route->flags & ROUTE_RUNQ ? 'Q' : '-', - route->flags & ROUTE_KEEPALIVE ? 'K' : '-', - route->nconn, - route->nerror, - route->penalty, - v ? duration_to_text(t - time(NULL)) : "-"); - m_compose(p, IMSG_CTL_MTA_SHOW_ROUTES, - imsg->hdr.peerid, 0, -1, - buf, strlen(buf) + 1); - } - m_compose(p, IMSG_CTL_MTA_SHOW_ROUTES, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_CTL_MTA_SHOW_HOSTSTATS: - iter = NULL; - while (dict_iter(&hoststat, &iter, &hostname, - (void **)&hs)) { - (void)snprintf(buf, sizeof(buf), - "%s|%llu|%s", - hostname, (unsigned long long) hs->tm, - hs->error); - m_compose(p, IMSG_CTL_MTA_SHOW_HOSTSTATS, - imsg->hdr.peerid, 0, -1, - buf, strlen(buf) + 1); - } - m_compose(p, IMSG_CTL_MTA_SHOW_HOSTSTATS, - imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_CTL_MTA_BLOCK: - m_msg(&m, imsg); - m_get_sockaddr(&m, (struct sockaddr*)&ss); - m_get_string(&m, &dom); - m_end(&m); - source = mta_source((struct sockaddr*)&ss); - if (*dom != '\0') { - if (!(strlcpy(buf, dom, sizeof(buf)) - >= sizeof(buf))) - mta_block(source, buf); - } - else - mta_block(source, NULL); - mta_source_unref(source); - m_compose(p, IMSG_CTL_OK, imsg->hdr.peerid, 0, -1, NULL, 0); - return; - - case IMSG_CTL_MTA_UNBLOCK: - m_msg(&m, imsg); - m_get_sockaddr(&m, (struct sockaddr*)&ss); - m_get_string(&m, &dom); - m_end(&m); - source = mta_source((struct sockaddr*)&ss); - if (*dom != '\0') { - if (!(strlcpy(buf, dom, sizeof(buf)) - >= sizeof(buf))) - mta_unblock(source, buf); - } - else - mta_unblock(source, NULL); - mta_source_unref(source); - m_compose(p, IMSG_CTL_OK, imsg->hdr.peerid, 0, -1, NULL, 0); - return; - - case IMSG_CTL_MTA_SHOW_BLOCK: - SPLAY_FOREACH(block, mta_block_tree, &blocks) { - (void)snprintf(buf, sizeof(buf), "%s -> %s", - mta_source_to_text(block->source), - block->domain ? block->domain : "*"); - m_compose(p, IMSG_CTL_MTA_SHOW_BLOCK, - imsg->hdr.peerid, 0, -1, buf, strlen(buf) + 1); - } - m_compose(p, IMSG_CTL_MTA_SHOW_BLOCK, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - } - - errx(1, "mta_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -void -mta_postfork(void) -{ -} - -void -mta_postprivdrop(void) -{ - SPLAY_INIT(&relays); - SPLAY_INIT(&domains); - SPLAY_INIT(&hosts); - SPLAY_INIT(&sources); - SPLAY_INIT(&routes); - SPLAY_INIT(&blocks); - - tree_init(&wait_secret); - tree_init(&wait_smarthost); - tree_init(&wait_mx); - tree_init(&wait_preference); - tree_init(&wait_source); - tree_init(&flush_evp); - dict_init(&hoststat); - - evtimer_set(&ev_flush_evp, mta_delivery_flush_event, NULL); - - runq_init(&runq_relay, mta_on_timeout); - runq_init(&runq_connector, mta_on_timeout); - runq_init(&runq_route, mta_on_timeout); - runq_init(&runq_hoststat, mta_on_timeout); -} - - -/* - * Local error on the given source. - */ -void -mta_source_error(struct mta_relay *relay, struct mta_route *route, const char *e) -{ - struct mta_connector *c; - - /* - * Remember the source as broken for this connector. - */ - c = mta_connector(relay, route->src); - if (!(c->flags & CONNECTOR_ERROR_SOURCE)) - log_info("smtp-out: Error on %s: %s", - mta_route_to_text(route), e); - c->flags |= CONNECTOR_ERROR_SOURCE; -} - -void -mta_route_error(struct mta_relay *relay, struct mta_route *route) -{ -#if 0 - route->nerror += 1; - - if (route->nerror > MAXERROR_PER_ROUTE) { - log_info("smtp-out: Too many errors on %s: " - "disabling for a while", mta_route_to_text(route)); - mta_route_disable(route, 2, ROUTE_DISABLED_SMTP); - } -#endif -} - -void -mta_route_ok(struct mta_relay *relay, struct mta_route *route) -{ - struct mta_connector *c; - - if (!(route->flags & ROUTE_NEW)) - return; - - log_debug("debug: mta-routing: route %s is now valid.", - mta_route_to_text(route)); - - route->nerror = 0; - route->flags &= ~ROUTE_NEW; - - c = mta_connector(relay, route->src); - mta_connect(c); -} - -void -mta_route_down(struct mta_relay *relay, struct mta_route *route) -{ -#if 0 - mta_route_disable(route, 2, ROUTE_DISABLED_SMTP); -#endif -} - -void -mta_route_collect(struct mta_relay *relay, struct mta_route *route) -{ - struct mta_connector *c; - - log_debug("debug: mta_route_collect(%s)", - mta_route_to_text(route)); - - relay->nconn -= 1; - relay->domain->nconn -= 1; - route->nconn -= 1; - route->src->nconn -= 1; - route->dst->nconn -= 1; - route->lastdisc = time(NULL); - - /* First connection failed */ - if (route->flags & ROUTE_NEW) - mta_route_disable(route, 1, ROUTE_DISABLED_NET); - - c = mta_connector(relay, route->src); - c->nconn -= 1; - mta_connect(c); - mta_route_unref(route); /* from mta_find_route() */ - mta_relay_unref(relay); /* from mta_connect() */ -} - -struct mta_task * -mta_route_next_task(struct mta_relay *relay, struct mta_route *route) -{ - struct mta_task *task; - - if ((task = TAILQ_FIRST(&relay->tasks))) { - TAILQ_REMOVE(&relay->tasks, task, entry); - relay->ntask -= 1; - task->relay = NULL; - - /* When the number of tasks is down to lowat, query some evp */ - if (relay->ntask == (size_t)relay->limits->task_lowat) { - if (relay->state & RELAY_ONHOLD) { - log_info("smtp-out: back to lowat on %s: releasing", - mta_relay_to_text(relay)); - relay->state &= ~RELAY_ONHOLD; - } - if (relay->state & RELAY_HOLDQ) { - m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); - m_add_id(p_queue, relay->id); - m_add_int(p_queue, relay->limits->task_release); - m_close(p_queue); - } - } - else if (relay->ntask == 0 && relay->state & RELAY_HOLDQ) { - m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); - m_add_id(p_queue, relay->id); - m_add_int(p_queue, 0); - m_close(p_queue); - } - } - - return (task); -} - -static void -mta_handle_envelope(struct envelope *evp, const char *smarthost) -{ - struct mta_relay *relay; - struct mta_task *task; - struct mta_envelope *e; - struct dispatcher *dispatcher; - struct mailaddr maddr; - struct relayhost relayh; - char buf[LINE_MAX]; - - dispatcher = dict_xget(env->sc_dispatchers, evp->dispatcher); - if (dispatcher->u.remote.smarthost && smarthost == NULL) { - mta_query_smarthost(evp); - return; - } - - memset(&relayh, 0, sizeof(relayh)); - relayh.tls = RELAY_TLS_OPPORTUNISTIC; - if (smarthost && !text_to_relayhost(&relayh, smarthost)) { - log_warnx("warn: Failed to parse smarthost %s", smarthost); - m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_evpid(p_queue, evp->id); - m_add_string(p_queue, "Cannot parse smarthost"); - m_add_int(p_queue, ESC_OTHER_STATUS); - m_close(p_queue); - return; - } - - if (relayh.flags & RELAY_AUTH && dispatcher->u.remote.auth == NULL) { - log_warnx("warn: No auth table on action \"%s\" for relay %s", - evp->dispatcher, smarthost); - m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_evpid(p_queue, evp->id); - m_add_string(p_queue, "No auth table for relaying"); - m_add_int(p_queue, ESC_OTHER_STATUS); - m_close(p_queue); - return; - } - - if (dispatcher->u.remote.tls_required) { - /* Reject relay if smtp+notls:// is requested */ - if (relayh.tls == RELAY_TLS_NO) { - log_warnx("warn: TLS required for action \"%s\"", - evp->dispatcher); - m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_evpid(p_queue, evp->id); - m_add_string(p_queue, "TLS required for relaying"); - m_add_int(p_queue, ESC_OTHER_STATUS); - m_close(p_queue); - return; - } - /* Update smtp:// to smtp+tls:// */ - if (relayh.tls == RELAY_TLS_OPPORTUNISTIC) - relayh.tls = RELAY_TLS_STARTTLS; - } - - relay = mta_relay(evp, &relayh); - /* ignore if we don't know the limits yet */ - if (relay->limits && - relay->ntask >= (size_t)relay->limits->task_hiwat) { - if (!(relay->state & RELAY_ONHOLD)) { - log_info("smtp-out: hiwat reached on %s: holding envelopes", - mta_relay_to_text(relay)); - relay->state |= RELAY_ONHOLD; - } - } - - /* - * If the relay has too many pending tasks, tell the - * scheduler to hold it until further notice - */ - if (relay->state & RELAY_ONHOLD) { - relay->state |= RELAY_HOLDQ; - m_create(p_queue, IMSG_MTA_DELIVERY_HOLD, 0, 0, -1); - m_add_evpid(p_queue, evp->id); - m_add_id(p_queue, relay->id); - m_close(p_queue); - mta_relay_unref(relay); /* from here */ - return; - } - - task = NULL; - TAILQ_FOREACH(task, &relay->tasks, entry) - if (task->msgid == evpid_to_msgid(evp->id)) - break; - - if (task == NULL) { - task = xmalloc(sizeof *task); - TAILQ_INIT(&task->envelopes); - task->relay = relay; - relay->ntask += 1; - TAILQ_INSERT_TAIL(&relay->tasks, task, entry); - task->msgid = evpid_to_msgid(evp->id); - if (evp->sender.user[0] || evp->sender.domain[0]) - (void)snprintf(buf, sizeof buf, "%s@%s", - evp->sender.user, evp->sender.domain); - else - buf[0] = '\0'; - - if (dispatcher->u.remote.mail_from && evp->sender.user[0]) { - memset(&maddr, 0, sizeof (maddr)); - if (text_to_mailaddr(&maddr, - dispatcher->u.remote.mail_from)) { - (void)snprintf(buf, sizeof buf, "%s@%s", - maddr.user[0] ? maddr.user : evp->sender.user, - maddr.domain[0] ? maddr.domain : evp->sender.domain); - } - } - - task->sender = xstrdup(buf); - stat_increment("mta.task", 1); - } - - e = xcalloc(1, sizeof *e); - e->id = evp->id; - e->creation = evp->creation; - e->smtpname = xstrdup(evp->smtpname); - (void)snprintf(buf, sizeof buf, "%s@%s", - evp->dest.user, evp->dest.domain); - e->dest = xstrdup(buf); - (void)snprintf(buf, sizeof buf, "%s@%s", - evp->rcpt.user, evp->rcpt.domain); - if (strcmp(buf, e->dest)) - e->rcpt = xstrdup(buf); - e->task = task; - if (evp->dsn_orcpt.user[0] && evp->dsn_orcpt.domain[0]) { - (void)snprintf(buf, sizeof buf, "%s@%s", - evp->dsn_orcpt.user, evp->dsn_orcpt.domain); - e->dsn_orcpt = xstrdup(buf); - } - (void)strlcpy(e->dsn_envid, evp->dsn_envid, - sizeof e->dsn_envid); - e->dsn_notify = evp->dsn_notify; - e->dsn_ret = evp->dsn_ret; - - TAILQ_INSERT_TAIL(&task->envelopes, e, entry); - log_debug("debug: mta: received evp:%016" PRIx64 - " for <%s>", e->id, e->dest); - - stat_increment("mta.envelope", 1); - - mta_drain(relay); - mta_relay_unref(relay); /* from here */ -} - -static void -mta_delivery_flush_event(int fd, short event, void *arg) -{ - struct mta_envelope *e; - struct timeval tv; - - if (tree_poproot(&flush_evp, NULL, (void**)(&e))) { - - if (e->delivery == IMSG_MTA_DELIVERY_OK) { - m_create(p_queue, IMSG_MTA_DELIVERY_OK, 0, 0, -1); - m_add_evpid(p_queue, e->id); - m_add_int(p_queue, e->ext); - m_close(p_queue); - } else if (e->delivery == IMSG_MTA_DELIVERY_TEMPFAIL) { - m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_evpid(p_queue, e->id); - m_add_string(p_queue, e->status); - m_add_int(p_queue, ESC_OTHER_STATUS); - m_close(p_queue); - } - else if (e->delivery == IMSG_MTA_DELIVERY_PERMFAIL) { - m_create(p_queue, IMSG_MTA_DELIVERY_PERMFAIL, 0, 0, -1); - m_add_evpid(p_queue, e->id); - m_add_string(p_queue, e->status); - m_add_int(p_queue, ESC_OTHER_STATUS); - m_close(p_queue); - } - else if (e->delivery == IMSG_MTA_DELIVERY_LOOP) { - m_create(p_queue, IMSG_MTA_DELIVERY_LOOP, 0, 0, -1); - m_add_evpid(p_queue, e->id); - m_close(p_queue); - } - else { - log_warnx("warn: bad delivery type %d for %016" PRIx64, - e->delivery, e->id); - fatalx("aborting"); - } - - log_debug("debug: mta: flush for %016"PRIx64" (-> %s)", e->id, e->dest); - - free(e->smtpname); - free(e->dest); - free(e->rcpt); - free(e->dsn_orcpt); - free(e); - - tv.tv_sec = 0; - tv.tv_usec = 0; - evtimer_add(&ev_flush_evp, &tv); - } -} - -void -mta_delivery_log(struct mta_envelope *e, const char *source, const char *relay, - int delivery, const char *status) -{ - if (delivery == IMSG_MTA_DELIVERY_OK) - mta_log(e, "Ok", source, relay, status); - else if (delivery == IMSG_MTA_DELIVERY_TEMPFAIL) - mta_log(e, "TempFail", source, relay, status); - else if (delivery == IMSG_MTA_DELIVERY_PERMFAIL) - mta_log(e, "PermFail", source, relay, status); - else if (delivery == IMSG_MTA_DELIVERY_LOOP) - mta_log(e, "PermFail", source, relay, "Loop detected"); - else { - log_warnx("warn: bad delivery type %d for %016" PRIx64, - delivery, e->id); - fatalx("aborting"); - } - - e->delivery = delivery; - if (status) - (void)strlcpy(e->status, status, sizeof(e->status)); -} - -void -mta_delivery_notify(struct mta_envelope *e) -{ - struct timeval tv; - - tree_xset(&flush_evp, e->id, e); - if (tree_count(&flush_evp) == 1) { - tv.tv_sec = 0; - tv.tv_usec = 0; - evtimer_add(&ev_flush_evp, &tv); - } -} - -static void -mta_query_mx(struct mta_relay *relay) -{ - uint64_t id; - - if (relay->status & RELAY_WAIT_MX) - return; - - log_debug("debug: mta: querying MX for %s...", - mta_relay_to_text(relay)); - - if (waitq_wait(&relay->domain->mxs, mta_on_mx, relay)) { - id = generate_uid(); - tree_xset(&wait_mx, id, relay->domain); - if (relay->domain->as_host) - m_create(p_lka, IMSG_MTA_DNS_HOST, 0, 0, -1); - else - m_create(p_lka, IMSG_MTA_DNS_MX, 0, 0, -1); - m_add_id(p_lka, id); - m_add_string(p_lka, relay->domain->name); - m_close(p_lka); - } - relay->status |= RELAY_WAIT_MX; - mta_relay_ref(relay); -} - -static void -mta_query_limits(struct mta_relay *relay) -{ - if (relay->status & RELAY_WAIT_LIMITS) - return; - - relay->limits = dict_get(env->sc_limits_dict, relay->domain->name); - if (relay->limits == NULL) - relay->limits = dict_get(env->sc_limits_dict, "default"); - - if (max_seen_conndelay_route < relay->limits->conndelay_route) - max_seen_conndelay_route = relay->limits->conndelay_route; - if (max_seen_discdelay_route < relay->limits->discdelay_route) - max_seen_discdelay_route = relay->limits->discdelay_route; -} - -static void -mta_query_secret(struct mta_relay *relay) -{ - if (relay->status & RELAY_WAIT_SECRET) - return; - - log_debug("debug: mta: querying secret for %s...", - mta_relay_to_text(relay)); - - tree_xset(&wait_secret, relay->id, relay); - relay->status |= RELAY_WAIT_SECRET; - - m_create(p_lka, IMSG_MTA_LOOKUP_CREDENTIALS, 0, 0, -1); - m_add_id(p_lka, relay->id); - m_add_string(p_lka, relay->authtable); - m_add_string(p_lka, relay->authlabel); - m_close(p_lka); - - mta_relay_ref(relay); -} - -static void -mta_query_smarthost(struct envelope *evp0) -{ - struct dispatcher *dispatcher; - struct envelope *evp; - - evp = malloc(sizeof(*evp)); - memmove(evp, evp0, sizeof(*evp)); - - dispatcher = dict_xget(env->sc_dispatchers, evp->dispatcher); - - log_debug("debug: mta: querying smarthost for %s:%s...", - evp->dispatcher, dispatcher->u.remote.smarthost); - - tree_xset(&wait_smarthost, evp->id, evp); - - m_create(p_lka, IMSG_MTA_LOOKUP_SMARTHOST, 0, 0, -1); - m_add_id(p_lka, evp->id); - if (dispatcher->u.remote.smarthost_domain) - m_add_string(p_lka, evp->dest.domain); - else - m_add_string(p_lka, NULL); - m_add_string(p_lka, dispatcher->u.remote.smarthost); - m_close(p_lka); - - log_debug("debug: mta: querying smarthost"); -} - -static void -mta_query_preference(struct mta_relay *relay) -{ - if (relay->status & RELAY_WAIT_PREFERENCE) - return; - - log_debug("debug: mta: querying preference for %s...", - mta_relay_to_text(relay)); - - tree_xset(&wait_preference, relay->id, relay); - relay->status |= RELAY_WAIT_PREFERENCE; - - m_create(p_lka, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); - m_add_id(p_lka, relay->id); - m_add_string(p_lka, relay->domain->name); - m_add_string(p_lka, relay->backupname); - m_close(p_lka); - - mta_relay_ref(relay); -} - -static void -mta_query_source(struct mta_relay *relay) -{ - log_debug("debug: mta: querying source for %s...", - mta_relay_to_text(relay)); - - relay->sourceloop += 1; - - if (relay->sourcetable == NULL) { - /* - * This is a recursive call, but it only happens once, since - * another source will not be queried immediately. - */ - mta_relay_ref(relay); - mta_on_source(relay, mta_source(NULL)); - return; - } - - m_create(p_lka, IMSG_MTA_LOOKUP_SOURCE, 0, 0, -1); - m_add_id(p_lka, relay->id); - m_add_string(p_lka, relay->sourcetable); - m_close(p_lka); - - tree_xset(&wait_source, relay->id, relay); - relay->status |= RELAY_WAIT_SOURCE; - mta_relay_ref(relay); -} - -static void -mta_on_mx(void *tag, void *arg, void *data) -{ - struct mta_domain *domain = data; - struct mta_relay *relay = arg; - - log_debug("debug: mta: ... got mx (%p, %s, %s)", - tag, domain->name, mta_relay_to_text(relay)); - - switch (domain->mxstatus) { - case DNS_OK: - break; - case DNS_RETRY: - relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; - relay->failstr = "Temporary failure in MX lookup"; - break; - case DNS_EINVAL: - relay->fail = IMSG_MTA_DELIVERY_PERMFAIL; - relay->failstr = "Invalid domain name"; - break; - case DNS_ENONAME: - relay->fail = IMSG_MTA_DELIVERY_PERMFAIL; - relay->failstr = "Domain does not exist"; - break; - case DNS_ENOTFOUND: - relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; - if (relay->domain->as_host) - relay->failstr = "Host not found"; - else - relay->failstr = "No MX found for domain"; - break; - default: - fatalx("bad DNS lookup error code"); - break; - } - - if (domain->mxstatus) - log_info("smtp-out: Failed to resolve MX for %s: %s", - mta_relay_to_text(relay), relay->failstr); - - relay->status &= ~RELAY_WAIT_MX; - mta_drain(relay); - mta_relay_unref(relay); /* from mta_drain() */ -} - -static void -mta_on_secret(struct mta_relay *relay, const char *secret) -{ - log_debug("debug: mta: ... got secret for %s: %s", - mta_relay_to_text(relay), secret); - - if (secret) - relay->secret = strdup(secret); - - if (relay->secret == NULL) { - log_warnx("warn: Failed to retrieve secret " - "for %s", mta_relay_to_text(relay)); - relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; - relay->failstr = "Could not retrieve credentials"; - } - - relay->status &= ~RELAY_WAIT_SECRET; - mta_drain(relay); - mta_relay_unref(relay); /* from mta_query_secret() */ -} - -static void -mta_on_smarthost(struct envelope *evp, const char *smarthost) -{ - if (smarthost == NULL) { - log_warnx("warn: Failed to retrieve smarthost " - "for envelope %"PRIx64, evp->id); - m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_evpid(p_queue, evp->id); - m_add_string(p_queue, "Cannot retrieve smarthost"); - m_add_int(p_queue, ESC_OTHER_STATUS); - m_close(p_queue); - return; - } - - log_debug("debug: mta: ... got smarthost for %016"PRIx64": %s", - evp->id, smarthost); - mta_handle_envelope(evp, smarthost); - free(evp); -} - -static void -mta_on_preference(struct mta_relay *relay, int preference) -{ - log_debug("debug: mta: ... got preference for %s: %d", - mta_relay_to_text(relay), preference); - - relay->backuppref = preference; - - relay->status &= ~RELAY_WAIT_PREFERENCE; - mta_drain(relay); - mta_relay_unref(relay); /* from mta_query_preference() */ -} - -static void -mta_on_source(struct mta_relay *relay, struct mta_source *source) -{ - struct mta_connector *c; - void *iter; - int delay, errmask; - - log_debug("debug: mta: ... got source for %s: %s", - mta_relay_to_text(relay), source ? mta_source_to_text(source) : "NULL"); - - relay->lastsource = time(NULL); - delay = DELAY_CHECK_SOURCE_SLOW; - - if (source) { - c = mta_connector(relay, source); - if (c->flags & CONNECTOR_NEW) { - c->flags &= ~CONNECTOR_NEW; - delay = DELAY_CHECK_SOURCE; - } - mta_connect(c); - if ((c->flags & CONNECTOR_ERROR) == 0) - relay->sourceloop = 0; - else - delay = DELAY_CHECK_SOURCE_FAST; - mta_source_unref(source); /* from constructor */ - } - else { - log_warnx("warn: Failed to get source address for %s", - mta_relay_to_text(relay)); - } - - if (tree_count(&relay->connectors) == 0) { - relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; - relay->failstr = "Could not retrieve source address"; - } - if (tree_count(&relay->connectors) < relay->sourceloop) { - relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; - relay->failstr = "No valid route to remote MX"; - - errmask = 0; - iter = NULL; - while (tree_iter(&relay->connectors, &iter, NULL, (void **)&c)) - errmask |= c->flags; - - if (errmask & CONNECTOR_ERROR_ROUTE_SMTP) - relay->failstr = "Destination seem to reject all mails"; - else if (errmask & CONNECTOR_ERROR_ROUTE_NET) - relay->failstr = "Network error on destination MXs"; - else if (errmask & CONNECTOR_ERROR_MX) - relay->failstr = "No MX found for destination"; - else if (errmask & CONNECTOR_ERROR_FAMILY) - relay->failstr = "Address family mismatch on destination MXs"; - else if (errmask & CONNECTOR_ERROR_BLOCKED) - relay->failstr = "All routes to destination blocked"; - else - relay->failstr = "No valid route to destination"; - } - - relay->nextsource = relay->lastsource + delay; - relay->status &= ~RELAY_WAIT_SOURCE; - mta_drain(relay); - mta_relay_unref(relay); /* from mta_query_source() */ -} - -static void -mta_connect(struct mta_connector *c) -{ - struct mta_route *route; - struct mta_mx *mx; - struct mta_limits *l = c->relay->limits; - int limits; - time_t nextconn, now; - - /* toggle the block flag */ - if (mta_is_blocked(c->source, c->relay->domain->name)) - c->flags |= CONNECTOR_ERROR_BLOCKED; - else - c->flags &= ~CONNECTOR_ERROR_BLOCKED; - - again: - - log_debug("debug: mta: connecting with %s", mta_connector_to_text(c)); - - /* Do not connect if this connector has an error. */ - if (c->flags & CONNECTOR_ERROR) { - log_debug("debug: mta: connector error"); - return; - } - - if (c->flags & CONNECTOR_WAIT) { - log_debug("debug: mta: cancelling connector timeout"); - runq_cancel(runq_connector, c); - c->flags &= ~CONNECTOR_WAIT; - } - - /* No job. */ - if (c->relay->ntask == 0) { - log_debug("debug: mta: no task for connector"); - return; - } - - /* Do not create more connections than necessary */ - if ((c->relay->nconn_ready >= c->relay->ntask) || - (c->relay->nconn > 2 && c->relay->nconn >= c->relay->ntask / 2)) { - log_debug("debug: mta: enough connections already"); - return; - } - - limits = 0; - nextconn = now = time(NULL); - - if (c->relay->domain->lastconn + l->conndelay_domain > nextconn) { - log_debug("debug: mta: cannot use domain %s before %llus", - c->relay->domain->name, - (unsigned long long) c->relay->domain->lastconn + l->conndelay_domain - now); - nextconn = c->relay->domain->lastconn + l->conndelay_domain; - } - if (c->relay->domain->nconn >= l->maxconn_per_domain) { - log_debug("debug: mta: hit domain limit"); - limits |= CONNECTOR_LIMIT_DOMAIN; - } - - if (c->source->lastconn + l->conndelay_source > nextconn) { - log_debug("debug: mta: cannot use source %s before %llus", - mta_source_to_text(c->source), - (unsigned long long) c->source->lastconn + l->conndelay_source - now); - nextconn = c->source->lastconn + l->conndelay_source; - } - if (c->source->nconn >= l->maxconn_per_source) { - log_debug("debug: mta: hit source limit"); - limits |= CONNECTOR_LIMIT_SOURCE; - } - - if (c->lastconn + l->conndelay_connector > nextconn) { - log_debug("debug: mta: cannot use %s before %llus", - mta_connector_to_text(c), - (unsigned long long) c->lastconn + l->conndelay_connector - now); - nextconn = c->lastconn + l->conndelay_connector; - } - if (c->nconn >= l->maxconn_per_connector) { - log_debug("debug: mta: hit connector limit"); - limits |= CONNECTOR_LIMIT_CONN; - } - - if (c->relay->lastconn + l->conndelay_relay > nextconn) { - log_debug("debug: mta: cannot use %s before %llus", - mta_relay_to_text(c->relay), - (unsigned long long) c->relay->lastconn + l->conndelay_relay - now); - nextconn = c->relay->lastconn + l->conndelay_relay; - } - if (c->relay->nconn >= l->maxconn_per_relay) { - log_debug("debug: mta: hit relay limit"); - limits |= CONNECTOR_LIMIT_RELAY; - } - - /* We can connect now, find a route */ - if (!limits && nextconn <= now) - route = mta_find_route(c, now, &limits, &nextconn, &mx); - else - route = NULL; - - /* No route */ - if (route == NULL) { - - if (c->flags & CONNECTOR_ERROR) { - /* XXX we might want to clear this flag later */ - log_debug("debug: mta-routing: no route available for %s: errors on connector", - mta_connector_to_text(c)); - return; - } - else if (limits) { - log_debug("debug: mta-routing: no route available for %s: limits reached", - mta_connector_to_text(c)); - nextconn = now + DELAY_CHECK_LIMIT; - } - else { - log_debug("debug: mta-routing: no route available for %s: must wait a bit", - mta_connector_to_text(c)); - } - log_debug("debug: mta: retrying to connect on %s in %llus...", - mta_connector_to_text(c), - (unsigned long long) nextconn - time(NULL)); - c->flags |= CONNECTOR_WAIT; - runq_schedule_at(runq_connector, nextconn, c); - return; - } - - log_debug("debug: mta-routing: spawning new connection on %s", - mta_route_to_text(route)); - - c->nconn += 1; - c->lastconn = time(NULL); - - c->relay->nconn += 1; - c->relay->lastconn = c->lastconn; - c->relay->domain->nconn += 1; - c->relay->domain->lastconn = c->lastconn; - route->nconn += 1; - route->lastconn = c->lastconn; - route->src->nconn += 1; - route->src->lastconn = c->lastconn; - route->dst->nconn += 1; - route->dst->lastconn = c->lastconn; - - mta_session(c->relay, route, mx->mxname); /* this never fails synchronously */ - mta_relay_ref(c->relay); - - goto again; -} - -static void -mta_on_timeout(struct runq *runq, void *arg) -{ - struct mta_connector *connector = arg; - struct mta_relay *relay = arg; - struct mta_route *route = arg; - struct hoststat *hs = arg; - - if (runq == runq_relay) { - log_debug("debug: mta: ... timeout for %s", - mta_relay_to_text(relay)); - relay->status &= ~RELAY_WAIT_CONNECTOR; - mta_drain(relay); - mta_relay_unref(relay); /* from mta_drain() */ - } - else if (runq == runq_connector) { - log_debug("debug: mta: ... timeout for %s", - mta_connector_to_text(connector)); - connector->flags &= ~CONNECTOR_WAIT; - mta_connect(connector); - } - else if (runq == runq_route) { - route->flags &= ~ROUTE_RUNQ; - mta_route_enable(route); - mta_route_unref(route); - } - else if (runq == runq_hoststat) { - log_debug("debug: mta: ... timeout for hoststat %s", - hs->name); - mta_hoststat_remove_entry(hs); - free(hs); - } -} - -static void -mta_route_disable(struct mta_route *route, int penalty, int reason) -{ - unsigned long long delay; - - route->penalty += penalty; - route->lastpenalty = time(NULL); - delay = (unsigned long long)DELAY_ROUTE_BASE * route->penalty * route->penalty; - if (delay > DELAY_ROUTE_MAX) - delay = DELAY_ROUTE_MAX; -#if 0 - delay = 60; -#endif - - log_info("smtp-out: Disabling route %s for %llus", - mta_route_to_text(route), delay); - - if (route->flags & ROUTE_DISABLED) - runq_cancel(runq_route, route); - else - mta_route_ref(route); - - route->flags |= reason & ROUTE_DISABLED; - runq_schedule(runq_route, delay, route); -} - -static void -mta_route_enable(struct mta_route *route) -{ - if (route->flags & ROUTE_DISABLED) { - log_info("smtp-out: Enabling route %s", - mta_route_to_text(route)); - route->flags &= ~ROUTE_DISABLED; - route->flags |= ROUTE_NEW; - route->nerror = 0; - } - - if (route->penalty) { -#if DELAY_QUADRATIC - route->penalty -= 1; - route->lastpenalty = time(NULL); -#else - route->penalty = 0; -#endif - } -} - -static void -mta_drain(struct mta_relay *r) -{ - char buf[64]; - - log_debug("debug: mta: draining %s " - "refcount=%d, ntask=%zu, nconnector=%zu, nconn=%zu", - mta_relay_to_text(r), - r->refcount, r->ntask, tree_count(&r->connectors), r->nconn); - - /* - * All done. - */ - if (r->ntask == 0) { - log_debug("debug: mta: all done for %s", mta_relay_to_text(r)); - return; - } - - /* - * If we know that this relay is failing flush the tasks. - */ - if (r->fail) { - mta_flush(r, r->fail, r->failstr); - return; - } - - /* Query secret if needed. */ - if (r->flags & RELAY_AUTH && r->secret == NULL) - mta_query_secret(r); - - /* Query our preference if needed. */ - if (r->backupname && r->backuppref == -1) - mta_query_preference(r); - - /* Query the domain MXs if needed. */ - if (r->domain->lastmxquery == 0) - mta_query_mx(r); - - /* Query the limits if needed. */ - if (r->limits == NULL) - mta_query_limits(r); - - /* Wait until we are ready to proceed. */ - if (r->status & RELAY_WAITMASK) { - buf[0] = '\0'; - if (r->status & RELAY_WAIT_MX) - (void)strlcat(buf, " MX", sizeof buf); - if (r->status & RELAY_WAIT_PREFERENCE) - (void)strlcat(buf, " preference", sizeof buf); - if (r->status & RELAY_WAIT_SECRET) - (void)strlcat(buf, " secret", sizeof buf); - if (r->status & RELAY_WAIT_SOURCE) - (void)strlcat(buf, " source", sizeof buf); - if (r->status & RELAY_WAIT_CONNECTOR) - (void)strlcat(buf, " connector", sizeof buf); - log_debug("debug: mta: %s waiting for%s", - mta_relay_to_text(r), buf); - return; - } - - /* - * We have pending task, and it's maybe time too try a new source. - */ - if (r->nextsource <= time(NULL)) - mta_query_source(r); - else { - log_debug("debug: mta: scheduling relay %s in %llus...", - mta_relay_to_text(r), - (unsigned long long) r->nextsource - time(NULL)); - runq_schedule_at(runq_relay, r->nextsource, r); - r->status |= RELAY_WAIT_CONNECTOR; - mta_relay_ref(r); - } -} - -static void -mta_flush(struct mta_relay *relay, int fail, const char *error) -{ - struct mta_envelope *e; - struct mta_task *task; - const char *domain; - void *iter; - struct mta_connector *c; - size_t n, r; - - log_debug("debug: mta_flush(%s, %d, \"%s\")", - mta_relay_to_text(relay), fail, error); - - if (fail != IMSG_MTA_DELIVERY_TEMPFAIL && fail != IMSG_MTA_DELIVERY_PERMFAIL) - errx(1, "unexpected delivery status %d", fail); - - n = 0; - while ((task = TAILQ_FIRST(&relay->tasks))) { - TAILQ_REMOVE(&relay->tasks, task, entry); - while ((e = TAILQ_FIRST(&task->envelopes))) { - TAILQ_REMOVE(&task->envelopes, e, entry); - - /* - * host was suspended, cache envelope id in hoststat tree - * so that it can be retried when a delivery succeeds for - * that domain. - */ - domain = strchr(e->dest, '@'); - if (fail == IMSG_MTA_DELIVERY_TEMPFAIL && domain) { - r = 0; - iter = NULL; - while (tree_iter(&relay->connectors, &iter, - NULL, (void **)&c)) { - if (c->flags & CONNECTOR_ERROR_ROUTE) - r++; - } - if (tree_count(&relay->connectors) == r) - mta_hoststat_cache(domain+1, e->id); - } - - mta_delivery_log(e, NULL, relay->domain->name, fail, error); - mta_delivery_notify(e); - - n++; - } - free(task->sender); - free(task); - } - - stat_decrement("mta.task", relay->ntask); - stat_decrement("mta.envelope", n); - relay->ntask = 0; - - /* release all waiting envelopes for the relay */ - if (relay->state & RELAY_HOLDQ) { - m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); - m_add_id(p_queue, relay->id); - m_add_int(p_queue, -1); - m_close(p_queue); - } -} - -/* - * Find a route to use for this connector - */ -static struct mta_route * -mta_find_route(struct mta_connector *c, time_t now, int *limits, - time_t *nextconn, struct mta_mx **pmx) -{ - struct mta_route *route, *best; - struct mta_limits *l = c->relay->limits; - struct mta_mx *mx; - int level, limit_host, limit_route; - int family_mismatch, seen, suspended_route; - time_t tm; - - log_debug("debug: mta-routing: searching new route for %s...", - mta_connector_to_text(c)); - - tm = 0; - limit_host = 0; - limit_route = 0; - suspended_route = 0; - family_mismatch = 0; - level = -1; - best = NULL; - seen = 0; - - TAILQ_FOREACH(mx, &c->relay->domain->mxs, entry) { - /* - * New preference level - */ - if (mx->preference > level) { -#ifndef IGNORE_MX_PREFERENCE - /* - * Use the current best MX if found. - */ - if (best) - break; - - /* - * No candidate found. There are valid MXs at this - * preference level but they reached their limit, or - * we can't connect yet. - */ - if (limit_host || limit_route || tm) - break; - - /* - * If we are a backup MX, do not relay to MXs with - * a greater preference value. - */ - if (c->relay->backuppref >= 0 && - mx->preference >= c->relay->backuppref) - break; - - /* - * Start looking at MXs on this preference level. - */ -#endif - level = mx->preference; - } - - if (mx->host->flags & HOST_IGNORE) - continue; - - /* Found a possibly valid mx */ - seen++; - - if ((c->source->sa && - c->source->sa->sa_family != mx->host->sa->sa_family) || - (l->family && l->family != mx->host->sa->sa_family)) { - log_debug("debug: mta-routing: skipping host %s: AF mismatch", - mta_host_to_text(mx->host)); - family_mismatch = 1; - continue; - } - - if (mx->host->nconn >= l->maxconn_per_host) { - log_debug("debug: mta-routing: skipping host %s: too many connections", - mta_host_to_text(mx->host)); - limit_host = 1; - continue; - } - - if (mx->host->lastconn + l->conndelay_host > now) { - log_debug("debug: mta-routing: skipping host %s: cannot use before %llus", - mta_host_to_text(mx->host), - (unsigned long long) mx->host->lastconn + l->conndelay_host - now); - if (tm == 0 || mx->host->lastconn + l->conndelay_host < tm) - tm = mx->host->lastconn + l->conndelay_host; - continue; - } - - route = mta_route(c->source, mx->host); - - if (route->flags & ROUTE_DISABLED) { - log_debug("debug: mta-routing: skipping route %s: suspend", - mta_route_to_text(route)); - suspended_route |= route->flags & ROUTE_DISABLED; - mta_route_unref(route); /* from here */ - continue; - } - - if (route->nconn && (route->flags & ROUTE_NEW)) { - log_debug("debug: mta-routing: skipping route %s: not validated yet", - mta_route_to_text(route)); - limit_route = 1; - mta_route_unref(route); /* from here */ - continue; - } - - if (route->nconn >= l->maxconn_per_route) { - log_debug("debug: mta-routing: skipping route %s: too many connections", - mta_route_to_text(route)); - limit_route = 1; - mta_route_unref(route); /* from here */ - continue; - } - - if (route->lastconn + l->conndelay_route > now) { - log_debug("debug: mta-routing: skipping route %s: cannot use before %llus (delay after connect)", - mta_route_to_text(route), - (unsigned long long) route->lastconn + l->conndelay_route - now); - if (tm == 0 || route->lastconn + l->conndelay_route < tm) - tm = route->lastconn + l->conndelay_route; - mta_route_unref(route); /* from here */ - continue; - } - - if (route->lastdisc + l->discdelay_route > now) { - log_debug("debug: mta-routing: skipping route %s: cannot use before %llus (delay after disconnect)", - mta_route_to_text(route), - (unsigned long long) route->lastdisc + l->discdelay_route - now); - if (tm == 0 || route->lastdisc + l->discdelay_route < tm) - tm = route->lastdisc + l->discdelay_route; - mta_route_unref(route); /* from here */ - continue; - } - - /* Use the route with the lowest number of connections. */ - if (best && route->nconn >= best->nconn) { - log_debug("debug: mta-routing: skipping route %s: current one is better", - mta_route_to_text(route)); - mta_route_unref(route); /* from here */ - continue; - } - - if (best) - mta_route_unref(best); /* from here */ - best = route; - *pmx = mx; - log_debug("debug: mta-routing: selecting candidate route %s", - mta_route_to_text(route)); - } - - if (best) - return (best); - - /* Order is important */ - if (seen == 0) { - log_info("smtp-out: No MX found for %s", - mta_connector_to_text(c)); - c->flags |= CONNECTOR_ERROR_MX; - } - else if (limit_route) { - log_debug("debug: mta: hit route limit"); - *limits |= CONNECTOR_LIMIT_ROUTE; - } - else if (limit_host) { - log_debug("debug: mta: hit host limit"); - *limits |= CONNECTOR_LIMIT_HOST; - } - else if (tm) { - if (tm > *nextconn) - *nextconn = tm; - } - else if (family_mismatch) { - log_info("smtp-out: Address family mismatch on %s", - mta_connector_to_text(c)); - c->flags |= CONNECTOR_ERROR_FAMILY; - } - else if (suspended_route) { - log_info("smtp-out: No valid route for %s", - mta_connector_to_text(c)); - if (suspended_route & ROUTE_DISABLED_NET) - c->flags |= CONNECTOR_ERROR_ROUTE_NET; - if (suspended_route & ROUTE_DISABLED_SMTP) - c->flags |= CONNECTOR_ERROR_ROUTE_SMTP; - } - - return (NULL); -} - -static void -mta_log(const struct mta_envelope *evp, const char *prefix, const char *source, - const char *relay, const char *status) -{ - log_info("%016"PRIx64" mta delivery evpid=%016"PRIx64" " - "from=<%s> to=<%s> rcpt=<%s> source=\"%s\" " - "relay=\"%s\" delay=%s result=\"%s\" stat=\"%s\"", - evp->session, - evp->id, - evp->task->sender, - evp->dest, - evp->rcpt ? evp->rcpt : "-", - source ? source : "-", - relay, - duration_to_text(time(NULL) - evp->creation), - prefix, - status); -} - -static struct mta_relay * -mta_relay(struct envelope *e, struct relayhost *relayh) -{ - struct dispatcher *dispatcher; - struct mta_relay key, *r; - - dispatcher = dict_xget(env->sc_dispatchers, e->dispatcher); - - memset(&key, 0, sizeof key); - - key.pki_name = dispatcher->u.remote.pki; - key.ca_name = dispatcher->u.remote.ca; - key.authtable = dispatcher->u.remote.auth; - key.sourcetable = dispatcher->u.remote.source; - key.helotable = dispatcher->u.remote.helo_source; - key.heloname = dispatcher->u.remote.helo; - key.srs = dispatcher->u.remote.srs; - - if (relayh->hostname[0]) { - key.domain = mta_domain(relayh->hostname, 1); - } - else { - key.domain = mta_domain(e->dest.domain, 0); - if (dispatcher->u.remote.backup) { - key.backupname = dispatcher->u.remote.backupmx; - if (key.backupname == NULL) - key.backupname = e->smtpname; - } - } - - key.tls = relayh->tls; - key.flags |= relayh->flags; - key.port = relayh->port; - key.authlabel = relayh->authlabel; - if (!key.authlabel[0]) - key.authlabel = NULL; - - if ((key.tls == RELAY_TLS_STARTTLS || key.tls == RELAY_TLS_SMTPS) && - dispatcher->u.remote.tls_noverify == 0) - key.flags |= RELAY_TLS_VERIFY; - - if ((r = SPLAY_FIND(mta_relay_tree, &relays, &key)) == NULL) { - r = xcalloc(1, sizeof *r); - TAILQ_INIT(&r->tasks); - r->id = generate_uid(); - r->dispatcher = dispatcher; - r->tls = key.tls; - r->flags = key.flags; - r->domain = key.domain; - r->backupname = key.backupname ? - xstrdup(key.backupname) : NULL; - r->backuppref = -1; - r->port = key.port; - r->pki_name = key.pki_name ? xstrdup(key.pki_name) : NULL; - r->ca_name = key.ca_name ? xstrdup(key.ca_name) : NULL; - if (key.authtable) - r->authtable = xstrdup(key.authtable); - if (key.authlabel) - r->authlabel = xstrdup(key.authlabel); - if (key.sourcetable) - r->sourcetable = xstrdup(key.sourcetable); - if (key.helotable) - r->helotable = xstrdup(key.helotable); - if (key.heloname) - r->heloname = xstrdup(key.heloname); - r->srs = key.srs; - SPLAY_INSERT(mta_relay_tree, &relays, r); - stat_increment("mta.relay", 1); - } else { - mta_domain_unref(key.domain); /* from here */ - } - - r->refcount++; - return (r); -} - -static void -mta_relay_ref(struct mta_relay *r) -{ - r->refcount++; -} - -static void -mta_relay_unref(struct mta_relay *relay) -{ - struct mta_connector *c; - - if (--relay->refcount) - return; - - /* Make sure they are no envelopes held for this relay */ - if (relay->state & RELAY_HOLDQ) { - m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); - m_add_id(p_queue, relay->id); - m_add_int(p_queue, 0); - m_close(p_queue); - } - - log_debug("debug: mta: freeing %s", mta_relay_to_text(relay)); - SPLAY_REMOVE(mta_relay_tree, &relays, relay); - - while ((tree_poproot(&relay->connectors, NULL, (void**)&c))) - mta_connector_free(c); - - free(relay->authlabel); - free(relay->authtable); - free(relay->backupname); - free(relay->pki_name); - free(relay->ca_name); - free(relay->helotable); - free(relay->heloname); - free(relay->secret); - free(relay->sourcetable); - - mta_domain_unref(relay->domain); /* from constructor */ - free(relay); - stat_decrement("mta.relay", 1); -} - -const char * -mta_relay_to_text(struct mta_relay *relay) -{ - static char buf[1024]; - char tmp[32]; - const char *sep = ","; - - (void)snprintf(buf, sizeof buf, "[relay:%s", relay->domain->name); - - if (relay->port) { - (void)strlcat(buf, sep, sizeof buf); - (void)snprintf(tmp, sizeof tmp, "port=%d", (int)relay->port); - (void)strlcat(buf, tmp, sizeof buf); - } - - (void)strlcat(buf, sep, sizeof buf); - switch(relay->tls) { - case RELAY_TLS_OPPORTUNISTIC: - (void)strlcat(buf, "smtp", sizeof buf); - break; - case RELAY_TLS_STARTTLS: - (void)strlcat(buf, "smtp+tls", sizeof buf); - break; - case RELAY_TLS_SMTPS: - (void)strlcat(buf, "smtps", sizeof buf); - break; - case RELAY_TLS_NO: - if (relay->flags & RELAY_LMTP) - (void)strlcat(buf, "lmtp", sizeof buf); - else - (void)strlcat(buf, "smtp+notls", sizeof buf); - break; - default: - (void)strlcat(buf, "???", sizeof buf); - } - - if (relay->flags & RELAY_AUTH) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "auth=", sizeof buf); - (void)strlcat(buf, relay->authtable, sizeof buf); - (void)strlcat(buf, ":", sizeof buf); - (void)strlcat(buf, relay->authlabel, sizeof buf); - } - - if (relay->pki_name) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "pki_name=", sizeof buf); - (void)strlcat(buf, relay->pki_name, sizeof buf); - } - - if (relay->domain->as_host) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "mx", sizeof buf); - } - - if (relay->backupname) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "backup=", sizeof buf); - (void)strlcat(buf, relay->backupname, sizeof buf); - } - - if (relay->sourcetable) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "sourcetable=", sizeof buf); - (void)strlcat(buf, relay->sourcetable, sizeof buf); - } - - if (relay->helotable) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "helotable=", sizeof buf); - (void)strlcat(buf, relay->helotable, sizeof buf); - } - - if (relay->heloname) { - (void)strlcat(buf, sep, sizeof buf); - (void)strlcat(buf, "heloname=", sizeof buf); - (void)strlcat(buf, relay->heloname, sizeof buf); - } - - (void)strlcat(buf, "]", sizeof buf); - - return (buf); -} - -static void -mta_relay_show(struct mta_relay *r, struct mproc *p, uint32_t id, time_t t) -{ - struct mta_connector *c; - void *iter; - char buf[1024], flags[1024], dur[64]; - time_t to; - - flags[0] = '\0'; - -#define SHOWSTATUS(f, n) do { \ - if (r->status & (f)) { \ - if (flags[0]) \ - (void)strlcat(flags, ",", sizeof(flags)); \ - (void)strlcat(flags, (n), sizeof(flags)); \ - } \ - } while(0) - - SHOWSTATUS(RELAY_WAIT_MX, "MX"); - SHOWSTATUS(RELAY_WAIT_PREFERENCE, "preference"); - SHOWSTATUS(RELAY_WAIT_SECRET, "secret"); - SHOWSTATUS(RELAY_WAIT_LIMITS, "limits"); - SHOWSTATUS(RELAY_WAIT_SOURCE, "source"); - SHOWSTATUS(RELAY_WAIT_CONNECTOR, "connector"); -#undef SHOWSTATUS - - if (runq_pending(runq_relay, r, &to)) - (void)snprintf(dur, sizeof(dur), "%s", duration_to_text(to - t)); - else - (void)strlcpy(dur, "-", sizeof(dur)); - - (void)snprintf(buf, sizeof(buf), "%s refcount=%d ntask=%zu nconn=%zu lastconn=%s timeout=%s wait=%s%s", - mta_relay_to_text(r), - r->refcount, - r->ntask, - r->nconn, - r->lastconn ? duration_to_text(t - r->lastconn) : "-", - dur, - flags, - (r->state & RELAY_ONHOLD) ? "ONHOLD" : ""); - m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, id, 0, -1, buf, strlen(buf) + 1); - - iter = NULL; - while (tree_iter(&r->connectors, &iter, NULL, (void **)&c)) { - - if (runq_pending(runq_connector, c, &to)) - (void)snprintf(dur, sizeof(dur), "%s", duration_to_text(to - t)); - else - (void)strlcpy(dur, "-", sizeof(dur)); - - flags[0] = '\0'; - -#define SHOWFLAG(f, n) do { \ - if (c->flags & (f)) { \ - if (flags[0]) \ - (void)strlcat(flags, ",", sizeof(flags)); \ - (void)strlcat(flags, (n), sizeof(flags)); \ - } \ - } while(0) - - SHOWFLAG(CONNECTOR_NEW, "NEW"); - SHOWFLAG(CONNECTOR_WAIT, "WAIT"); - - SHOWFLAG(CONNECTOR_ERROR_FAMILY, "ERROR_FAMILY"); - SHOWFLAG(CONNECTOR_ERROR_SOURCE, "ERROR_SOURCE"); - SHOWFLAG(CONNECTOR_ERROR_MX, "ERROR_MX"); - SHOWFLAG(CONNECTOR_ERROR_ROUTE_NET, "ERROR_ROUTE_NET"); - SHOWFLAG(CONNECTOR_ERROR_ROUTE_SMTP, "ERROR_ROUTE_SMTP"); - SHOWFLAG(CONNECTOR_ERROR_BLOCKED, "ERROR_BLOCKED"); - - SHOWFLAG(CONNECTOR_LIMIT_HOST, "LIMIT_HOST"); - SHOWFLAG(CONNECTOR_LIMIT_ROUTE, "LIMIT_ROUTE"); - SHOWFLAG(CONNECTOR_LIMIT_SOURCE, "LIMIT_SOURCE"); - SHOWFLAG(CONNECTOR_LIMIT_RELAY, "LIMIT_RELAY"); - SHOWFLAG(CONNECTOR_LIMIT_CONN, "LIMIT_CONN"); - SHOWFLAG(CONNECTOR_LIMIT_DOMAIN, "LIMIT_DOMAIN"); -#undef SHOWFLAG - - (void)snprintf(buf, sizeof(buf), - " connector %s refcount=%d nconn=%zu lastconn=%s timeout=%s flags=%s", - mta_source_to_text(c->source), - c->refcount, - c->nconn, - c->lastconn ? duration_to_text(t - c->lastconn) : "-", - dur, - flags); - m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, id, 0, -1, buf, - strlen(buf) + 1); - - - } -} - -static int -mta_relay_cmp(const struct mta_relay *a, const struct mta_relay *b) -{ - int r; - - if (a->domain < b->domain) - return (-1); - if (a->domain > b->domain) - return (1); - - if (a->tls < b->tls) - return (-1); - if (a->tls > b->tls) - return (1); - - if (a->flags < b->flags) - return (-1); - if (a->flags > b->flags) - return (1); - - if (a->port < b->port) - return (-1); - if (a->port > b->port) - return (1); - - if (a->authtable == NULL && b->authtable) - return (-1); - if (a->authtable && b->authtable == NULL) - return (1); - if (a->authtable && ((r = strcmp(a->authtable, b->authtable)))) - return (r); - if (a->authlabel == NULL && b->authlabel) - return (-1); - if (a->authlabel && b->authlabel == NULL) - return (1); - if (a->authlabel && ((r = strcmp(a->authlabel, b->authlabel)))) - return (r); - if (a->sourcetable == NULL && b->sourcetable) - return (-1); - if (a->sourcetable && b->sourcetable == NULL) - return (1); - if (a->sourcetable && ((r = strcmp(a->sourcetable, b->sourcetable)))) - return (r); - if (a->helotable == NULL && b->helotable) - return (-1); - if (a->helotable && b->helotable == NULL) - return (1); - if (a->helotable && ((r = strcmp(a->helotable, b->helotable)))) - return (r); - if (a->heloname == NULL && b->heloname) - return (-1); - if (a->heloname && b->heloname == NULL) - return (1); - if (a->heloname && ((r = strcmp(a->heloname, b->heloname)))) - return (r); - - if (a->pki_name == NULL && b->pki_name) - return (-1); - if (a->pki_name && b->pki_name == NULL) - return (1); - if (a->pki_name && ((r = strcmp(a->pki_name, b->pki_name)))) - return (r); - - if (a->ca_name == NULL && b->ca_name) - return (-1); - if (a->ca_name && b->ca_name == NULL) - return (1); - if (a->ca_name && ((r = strcmp(a->ca_name, b->ca_name)))) - return (r); - - if (a->backupname == NULL && b->backupname) - return (-1); - if (a->backupname && b->backupname == NULL) - return (1); - if (a->backupname && ((r = strcmp(a->backupname, b->backupname)))) - return (r); - - if (a->srs < b->srs) - return (-1); - if (a->srs > b->srs) - return (1); - - return (0); -} - -SPLAY_GENERATE(mta_relay_tree, mta_relay, entry, mta_relay_cmp); - -static struct mta_host * -mta_host(const struct sockaddr *sa) -{ - struct mta_host key, *h; - struct sockaddr_storage ss; - - memmove(&ss, sa, SA_LEN(sa)); - key.sa = (struct sockaddr*)&ss; - h = SPLAY_FIND(mta_host_tree, &hosts, &key); - - if (h == NULL) { - h = xcalloc(1, sizeof(*h)); - h->sa = xmemdup(sa, SA_LEN(sa)); - SPLAY_INSERT(mta_host_tree, &hosts, h); - stat_increment("mta.host", 1); - } - - h->refcount++; - return (h); -} - -static void -mta_host_ref(struct mta_host *h) -{ - h->refcount++; -} - -static void -mta_host_unref(struct mta_host *h) -{ - if (--h->refcount) - return; - - SPLAY_REMOVE(mta_host_tree, &hosts, h); - free(h->sa); - free(h->ptrname); - free(h); - stat_decrement("mta.host", 1); -} - -const char * -mta_host_to_text(struct mta_host *h) -{ - static char buf[1024]; - - if (h->ptrname) - (void)snprintf(buf, sizeof buf, "%s (%s)", - sa_to_text(h->sa), h->ptrname); - else - (void)snprintf(buf, sizeof buf, "%s", sa_to_text(h->sa)); - - return (buf); -} - -static int -mta_host_cmp(const struct mta_host *a, const struct mta_host *b) -{ - if (SA_LEN(a->sa) < SA_LEN(b->sa)) - return (-1); - if (SA_LEN(a->sa) > SA_LEN(b->sa)) - return (1); - return (memcmp(a->sa, b->sa, SA_LEN(a->sa))); -} - -SPLAY_GENERATE(mta_host_tree, mta_host, entry, mta_host_cmp); - -static struct mta_domain * -mta_domain(char *name, int as_host) -{ - struct mta_domain key, *d; - - key.name = name; - key.as_host = as_host; - d = SPLAY_FIND(mta_domain_tree, &domains, &key); - - if (d == NULL) { - d = xcalloc(1, sizeof(*d)); - d->name = xstrdup(name); - d->as_host = as_host; - TAILQ_INIT(&d->mxs); - SPLAY_INSERT(mta_domain_tree, &domains, d); - stat_increment("mta.domain", 1); - } - - d->refcount++; - return (d); -} - -#if 0 -static void -mta_domain_ref(struct mta_domain *d) -{ - d->refcount++; -} -#endif - -static void -mta_domain_unref(struct mta_domain *d) -{ - struct mta_mx *mx; - - if (--d->refcount) - return; - - while ((mx = TAILQ_FIRST(&d->mxs))) { - TAILQ_REMOVE(&d->mxs, mx, entry); - mta_host_unref(mx->host); /* from IMSG_DNS_HOST */ - free(mx->mxname); - free(mx); - } - - SPLAY_REMOVE(mta_domain_tree, &domains, d); - free(d->name); - free(d); - stat_decrement("mta.domain", 1); -} - -static int -mta_domain_cmp(const struct mta_domain *a, const struct mta_domain *b) -{ - if (a->as_host < b->as_host) - return (-1); - if (a->as_host > b->as_host) - return (1); - return (strcasecmp(a->name, b->name)); -} - -SPLAY_GENERATE(mta_domain_tree, mta_domain, entry, mta_domain_cmp); - -static struct mta_source * -mta_source(const struct sockaddr *sa) -{ - struct mta_source key, *s; - struct sockaddr_storage ss; - - if (sa) { - memmove(&ss, sa, SA_LEN(sa)); - key.sa = (struct sockaddr*)&ss; - } else - key.sa = NULL; - s = SPLAY_FIND(mta_source_tree, &sources, &key); - - if (s == NULL) { - s = xcalloc(1, sizeof(*s)); - if (sa) - s->sa = xmemdup(sa, SA_LEN(sa)); - SPLAY_INSERT(mta_source_tree, &sources, s); - stat_increment("mta.source", 1); - } - - s->refcount++; - return (s); -} - -static void -mta_source_ref(struct mta_source *s) -{ - s->refcount++; -} - -static void -mta_source_unref(struct mta_source *s) -{ - if (--s->refcount) - return; - - SPLAY_REMOVE(mta_source_tree, &sources, s); - free(s->sa); - free(s); - stat_decrement("mta.source", 1); -} - -static const char * -mta_source_to_text(struct mta_source *s) -{ - static char buf[1024]; - - if (s->sa == NULL) - return "[]"; - (void)snprintf(buf, sizeof buf, "%s", sa_to_text(s->sa)); - return (buf); -} - -static int -mta_source_cmp(const struct mta_source *a, const struct mta_source *b) -{ - if (a->sa == NULL) - return ((b->sa == NULL) ? 0 : -1); - if (b->sa == NULL) - return (1); - if (SA_LEN(a->sa) < SA_LEN(b->sa)) - return (-1); - if (SA_LEN(a->sa) > SA_LEN(b->sa)) - return (1); - return (memcmp(a->sa, b->sa, SA_LEN(a->sa))); -} - -SPLAY_GENERATE(mta_source_tree, mta_source, entry, mta_source_cmp); - -static struct mta_connector * -mta_connector(struct mta_relay *relay, struct mta_source *source) -{ - struct mta_connector *c; - - c = tree_get(&relay->connectors, (uintptr_t)(source)); - if (c == NULL) { - c = xcalloc(1, sizeof(*c)); - c->relay = relay; - c->source = source; - c->flags |= CONNECTOR_NEW; - mta_source_ref(source); - tree_xset(&relay->connectors, (uintptr_t)(source), c); - stat_increment("mta.connector", 1); - log_debug("debug: mta: new %s", mta_connector_to_text(c)); - } - - return (c); -} - -static void -mta_connector_free(struct mta_connector *c) -{ - log_debug("debug: mta: freeing %s", - mta_connector_to_text(c)); - - if (c->flags & CONNECTOR_WAIT) { - log_debug("debug: mta: cancelling timeout for %s", - mta_connector_to_text(c)); - runq_cancel(runq_connector, c); - } - mta_source_unref(c->source); /* from constructor */ - free(c); - - stat_decrement("mta.connector", 1); -} - -static const char * -mta_connector_to_text(struct mta_connector *c) -{ - static char buf[1024]; - - (void)snprintf(buf, sizeof buf, "[connector:%s->%s,0x%x]", - mta_source_to_text(c->source), - mta_relay_to_text(c->relay), - c->flags); - return (buf); -} - -static struct mta_route * -mta_route(struct mta_source *src, struct mta_host *dst) -{ - struct mta_route key, *r; - static uint64_t rid = 0; - - key.src = src; - key.dst = dst; - r = SPLAY_FIND(mta_route_tree, &routes, &key); - - if (r == NULL) { - r = xcalloc(1, sizeof(*r)); - r->src = src; - r->dst = dst; - r->flags |= ROUTE_NEW; - r->id = ++rid; - SPLAY_INSERT(mta_route_tree, &routes, r); - mta_source_ref(src); - mta_host_ref(dst); - stat_increment("mta.route", 1); - } - else if (r->flags & ROUTE_RUNQ) { - log_debug("debug: mta: mta_route_ref(): cancelling runq for route %s", - mta_route_to_text(r)); - r->flags &= ~(ROUTE_RUNQ | ROUTE_KEEPALIVE); - runq_cancel(runq_route, r); - r->refcount--; /* from mta_route_unref() */ - } - - r->refcount++; - return (r); -} - -static void -mta_route_ref(struct mta_route *r) -{ - r->refcount++; -} - -static void -mta_route_unref(struct mta_route *r) -{ - time_t sched, now; - int delay; - - if (--r->refcount) - return; - - /* - * Nothing references this route, but we might want to keep it alive - * for a while. - */ - now = time(NULL); - sched = 0; - - if (r->penalty) { -#if DELAY_QUADRATIC - delay = DELAY_ROUTE_BASE * r->penalty * r->penalty; -#else - delay = 15 * 60; -#endif - if (delay > DELAY_ROUTE_MAX) - delay = DELAY_ROUTE_MAX; - sched = r->lastpenalty + delay; - log_debug("debug: mta: mta_route_unref(): keeping route %s alive for %llus (penalty %d)", - mta_route_to_text(r), (unsigned long long) sched - now, r->penalty); - } else if (!(r->flags & ROUTE_KEEPALIVE)) { - if (r->lastconn + max_seen_conndelay_route > now) - sched = r->lastconn + max_seen_conndelay_route; - if (r->lastdisc + max_seen_discdelay_route > now && - r->lastdisc + max_seen_discdelay_route < sched) - sched = r->lastdisc + max_seen_discdelay_route; - - if (sched > now) - log_debug("debug: mta: mta_route_unref(): keeping route %s alive for %llus (imposed delay)", - mta_route_to_text(r), (unsigned long long) sched - now); - } - - if (sched > now) { - r->flags |= ROUTE_RUNQ; - runq_schedule_at(runq_route, sched, r); - r->refcount++; - return; - } - - log_debug("debug: mta: mta_route_unref(): really discarding route %s", - mta_route_to_text(r)); - - SPLAY_REMOVE(mta_route_tree, &routes, r); - mta_source_unref(r->src); /* from constructor */ - mta_host_unref(r->dst); /* from constructor */ - free(r); - stat_decrement("mta.route", 1); -} - -static const char * -mta_route_to_text(struct mta_route *r) -{ - static char buf[1024]; - - (void)snprintf(buf, sizeof buf, "%s <-> %s", - mta_source_to_text(r->src), - mta_host_to_text(r->dst)); - - return (buf); -} - -static int -mta_route_cmp(const struct mta_route *a, const struct mta_route *b) -{ - if (a->src < b->src) - return (-1); - if (a->src > b->src) - return (1); - - if (a->dst < b->dst) - return (-1); - if (a->dst > b->dst) - return (1); - - return (0); -} - -SPLAY_GENERATE(mta_route_tree, mta_route, entry, mta_route_cmp); - -void -mta_block(struct mta_source *src, char *dom) -{ - struct mta_block key, *b; - - key.source = src; - key.domain = dom; - - b = SPLAY_FIND(mta_block_tree, &blocks, &key); - if (b != NULL) - return; - - b = xcalloc(1, sizeof(*b)); - if (dom) - b->domain = xstrdup(dom); - b->source = src; - mta_source_ref(src); - SPLAY_INSERT(mta_block_tree, &blocks, b); -} - -void -mta_unblock(struct mta_source *src, char *dom) -{ - struct mta_block key, *b; - - key.source = src; - key.domain = dom; - - b = SPLAY_FIND(mta_block_tree, &blocks, &key); - if (b == NULL) - return; - - SPLAY_REMOVE(mta_block_tree, &blocks, b); - - mta_source_unref(b->source); - free(b->domain); - free(b); -} - -int -mta_is_blocked(struct mta_source *src, char *dom) -{ - struct mta_block key; - - key.source = src; - key.domain = dom; - - if (SPLAY_FIND(mta_block_tree, &blocks, &key)) - return (1); - - return (0); -} - -static -int -mta_block_cmp(const struct mta_block *a, const struct mta_block *b) -{ - if (a->source < b->source) - return (-1); - if (a->source > b->source) - return (1); - if (!a->domain && b->domain) - return (-1); - if (a->domain && !b->domain) - return (1); - if (a->domain == b->domain) - return (0); - return (strcasecmp(a->domain, b->domain)); -} - -SPLAY_GENERATE(mta_block_tree, mta_block, entry, mta_block_cmp); - - - -/* hoststat errors are not critical, we do best effort */ -void -mta_hoststat_update(const char *host, const char *error) -{ - struct hoststat *hs = NULL; - char buf[HOST_NAME_MAX+1]; - - if (!lowercase(buf, host, sizeof buf)) - return; - - hs = dict_get(&hoststat, buf); - if (hs == NULL) { - if ((hs = calloc(1, sizeof *hs)) == NULL) - return; - tree_init(&hs->deferred); - runq_schedule(runq_hoststat, HOSTSTAT_EXPIRE_DELAY, hs); - } - (void)strlcpy(hs->name, buf, sizeof hs->name); - (void)strlcpy(hs->error, error, sizeof hs->error); - hs->tm = time(NULL); - dict_set(&hoststat, buf, hs); - - runq_cancel(runq_hoststat, hs); - runq_schedule(runq_hoststat, HOSTSTAT_EXPIRE_DELAY, hs); -} - -void -mta_hoststat_cache(const char *host, uint64_t evpid) -{ - struct hoststat *hs = NULL; - char buf[HOST_NAME_MAX+1]; - - if (!lowercase(buf, host, sizeof buf)) - return; - - hs = dict_get(&hoststat, buf); - if (hs == NULL) - return; - - if (tree_count(&hs->deferred) >= env->sc_mta_max_deferred) - return; - - tree_set(&hs->deferred, evpid, NULL); -} - -void -mta_hoststat_uncache(const char *host, uint64_t evpid) -{ - struct hoststat *hs = NULL; - char buf[HOST_NAME_MAX+1]; - - if (!lowercase(buf, host, sizeof buf)) - return; - - hs = dict_get(&hoststat, buf); - if (hs == NULL) - return; - - tree_pop(&hs->deferred, evpid); -} - -void -mta_hoststat_reschedule(const char *host) -{ - struct hoststat *hs = NULL; - char buf[HOST_NAME_MAX+1]; - uint64_t evpid; - - if (!lowercase(buf, host, sizeof buf)) - return; - - hs = dict_get(&hoststat, buf); - if (hs == NULL) - return; - - while (tree_poproot(&hs->deferred, &evpid, NULL)) { - m_compose(p_queue, IMSG_MTA_SCHEDULE, 0, 0, -1, - &evpid, sizeof evpid); - } -} - -static void -mta_hoststat_remove_entry(struct hoststat *hs) -{ - while (tree_poproot(&hs->deferred, NULL, NULL)) - ; - dict_pop(&hoststat, hs->name); - runq_cancel(runq_hoststat, hs); -} diff --git a/smtpd/mta_session.c b/smtpd/mta_session.c deleted file mode 100644 index 632f5be7..00000000 --- a/smtpd/mta_session.c +++ /dev/null @@ -1,2004 +0,0 @@ -/* $OpenBSD: mta_session.c,v 1.135 2020/04/24 11:34:07 eric Exp $ */ - -/* - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - -#define MAX_TRYBEFOREDISABLE 10 - -#define MTA_HIWAT 65535 - -enum mta_state { - MTA_INIT, - MTA_BANNER, - MTA_EHLO, - MTA_HELO, - MTA_LHLO, - MTA_STARTTLS, - MTA_AUTH, - MTA_AUTH_PLAIN, - MTA_AUTH_LOGIN, - MTA_AUTH_LOGIN_USER, - MTA_AUTH_LOGIN_PASS, - MTA_READY, - MTA_MAIL, - MTA_RCPT, - MTA_DATA, - MTA_BODY, - MTA_EOM, - MTA_LMTP_EOM, - MTA_RSET, - MTA_QUIT, -}; - -#define MTA_FORCE_ANYSSL 0x0001 -#define MTA_FORCE_SMTPS 0x0002 -#define MTA_FORCE_TLS 0x0004 -#define MTA_FORCE_PLAIN 0x0008 -#define MTA_WANT_SECURE 0x0010 -#define MTA_DOWNGRADE_PLAIN 0x0080 - -#define MTA_TLS 0x0100 -#define MTA_TLS_VERIFIED 0x0200 - -#define MTA_FREE 0x0400 -#define MTA_LMTP 0x0800 -#define MTA_WAIT 0x1000 -#define MTA_HANGON 0x2000 -#define MTA_RECONN 0x4000 - -#define MTA_EXT_STARTTLS 0x01 -#define MTA_EXT_PIPELINING 0x02 -#define MTA_EXT_AUTH 0x04 -#define MTA_EXT_AUTH_PLAIN 0x08 -#define MTA_EXT_AUTH_LOGIN 0x10 -#define MTA_EXT_SIZE 0x20 - -struct mta_session { - uint64_t id; - struct mta_relay *relay; - struct mta_route *route; - char *helo; - char *mxname; - - char *username; - - int flags; - - int attempt; - int use_smtps; - int use_starttls; - int use_smtp_tls; - int ready; - - struct event ev; - struct io *io; - int ext; - - size_t ext_size; - - size_t msgtried; - size_t msgcount; - size_t rcptcount; - int hangon; - - enum mta_state state; - struct mta_task *task; - struct mta_envelope *currevp; - FILE *datafp; - size_t datalen; - - size_t failures; - - char replybuf[2048]; -}; - -static void mta_session_init(void); -static void mta_start(int fd, short ev, void *arg); -static void mta_io(struct io *, int, void *); -static void mta_free(struct mta_session *); -static void mta_getnameinfo_cb(void *, int, const char *, const char *); -static void mta_on_ptr(void *, void *, void *); -static void mta_on_timeout(struct runq *, void *); -static void mta_connect(struct mta_session *); -static void mta_enter_state(struct mta_session *, int); -static void mta_flush_task(struct mta_session *, int, const char *, size_t, int); -static void mta_error(struct mta_session *, const char *, ...); -static void mta_send(struct mta_session *, char *, ...); -static ssize_t mta_queue_data(struct mta_session *); -static void mta_response(struct mta_session *, char *); -static const char * mta_strstate(int); -static void mta_cert_init(struct mta_session *); -static void mta_cert_init_cb(void *, int, const char *, const void *, size_t); -static void mta_cert_verify(struct mta_session *); -static void mta_cert_verify_cb(void *, int); -static void mta_tls_verified(struct mta_session *); -static struct mta_session *mta_tree_pop(struct tree *, uint64_t); -static const char * dsn_strret(enum dsn_ret); -static const char * dsn_strnotify(uint8_t); - -void mta_hoststat_update(const char *, const char *); -void mta_hoststat_reschedule(const char *); -void mta_hoststat_cache(const char *, uint64_t); -void mta_hoststat_uncache(const char *, uint64_t); - - -static void mta_filter_begin(struct mta_session *); -static void mta_filter_end(struct mta_session *); -static void mta_connected(struct mta_session *); -static void mta_disconnected(struct mta_session *); - -static void mta_report_link_connect(struct mta_session *, const char *, int, - const struct sockaddr_storage *, - const struct sockaddr_storage *); -static void mta_report_link_greeting(struct mta_session *, const char *); -static void mta_report_link_identify(struct mta_session *, const char *, const char *); -static void mta_report_link_tls(struct mta_session *, const char *); -static void mta_report_link_disconnect(struct mta_session *); -static void mta_report_link_auth(struct mta_session *, const char *, const char *); -static void mta_report_tx_reset(struct mta_session *, uint32_t); -static void mta_report_tx_begin(struct mta_session *, uint32_t); -static void mta_report_tx_mail(struct mta_session *, uint32_t, const char *, int); -static void mta_report_tx_rcpt(struct mta_session *, uint32_t, const char *, int); -static void mta_report_tx_envelope(struct mta_session *, uint32_t, uint64_t); -static void mta_report_tx_data(struct mta_session *, uint32_t, int); -static void mta_report_tx_commit(struct mta_session *, uint32_t, size_t); -static void mta_report_tx_rollback(struct mta_session *, uint32_t); -static void mta_report_protocol_client(struct mta_session *, const char *); -static void mta_report_protocol_server(struct mta_session *, const char *); -#if 0 -static void mta_report_filter_response(struct mta_session *, int, int, const char *); -#endif -static void mta_report_timeout(struct mta_session *); - - -static struct tree wait_helo; -static struct tree wait_ptr; -static struct tree wait_fd; -static struct tree wait_tls_init; -static struct tree wait_tls_verify; - -static struct runq *hangon; - -#define SESSION_FILTERED(s) \ - ((s)->relay->dispatcher->u.remote.filtername) - -static void -mta_session_init(void) -{ - static int init = 0; - - if (!init) { - tree_init(&wait_helo); - tree_init(&wait_ptr); - tree_init(&wait_fd); - tree_init(&wait_tls_init); - tree_init(&wait_tls_verify); - runq_init(&hangon, mta_on_timeout); - init = 1; - } -} - -void -mta_session(struct mta_relay *relay, struct mta_route *route, const char *mxname) -{ - struct mta_session *s; - struct timeval tv; - - mta_session_init(); - - s = xcalloc(1, sizeof *s); - s->id = generate_uid(); - s->relay = relay; - s->route = route; - s->mxname = xstrdup(mxname); - - mta_filter_begin(s); - - if (relay->flags & RELAY_LMTP) - s->flags |= MTA_LMTP; - switch (relay->tls) { - case RELAY_TLS_SMTPS: - s->flags |= MTA_FORCE_SMTPS; - s->flags |= MTA_WANT_SECURE; - break; - case RELAY_TLS_STARTTLS: - s->flags |= MTA_FORCE_TLS; - s->flags |= MTA_WANT_SECURE; - break; - case RELAY_TLS_OPPORTUNISTIC: - /* do not force anything, try tls then smtp */ - break; - case RELAY_TLS_NO: - s->flags |= MTA_FORCE_PLAIN; - break; - default: - fatalx("bad value for relay->tls: %d", relay->tls); - } - - log_debug("debug: mta: %p: spawned for relay %s", s, - mta_relay_to_text(relay)); - stat_increment("mta.session", 1); - - if (route->dst->ptrname || route->dst->lastptrquery) { - /* We want to delay the connection since to always notify - * the relay asynchronously. - */ - tv.tv_sec = 0; - tv.tv_usec = 0; - evtimer_set(&s->ev, mta_start, s); - evtimer_add(&s->ev, &tv); - } else if (waitq_wait(&route->dst->ptrname, mta_on_ptr, s)) { - resolver_getnameinfo(s->route->dst->sa, 0, mta_getnameinfo_cb, s); - } -} - -void -mta_session_imsg(struct mproc *p, struct imsg *imsg) -{ - struct mta_session *s; - struct msg m; - uint64_t reqid; - const char *name; - int status; - struct stat sb; - - switch (imsg->hdr.type) { - - case IMSG_MTA_OPEN_MESSAGE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - s = mta_tree_pop(&wait_fd, reqid); - if (s == NULL) { - if (imsg->fd != -1) - close(imsg->fd); - return; - } - - if (imsg->fd == -1) { - log_debug("debug: mta: failed to obtain msg fd"); - mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, - "Could not get message fd", 0, 0); - mta_enter_state(s, MTA_READY); - return; - } - - if ((s->ext & MTA_EXT_SIZE) && s->ext_size != 0) { - if (fstat(imsg->fd, &sb) == -1) { - log_debug("debug: mta: failed to stat msg fd"); - mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, - "Could not stat message fd", 0, 0); - mta_enter_state(s, MTA_READY); - close(imsg->fd); - return; - } - if (sb.st_size > (off_t)s->ext_size) { - log_debug("debug: mta: message too large for peer"); - mta_flush_task(s, IMSG_MTA_DELIVERY_PERMFAIL, - "message too large for peer", 0, 0); - mta_enter_state(s, MTA_READY); - close(imsg->fd); - return; - } - } - - s->datafp = fdopen(imsg->fd, "r"); - if (s->datafp == NULL) - fatal("mta: fdopen"); - - mta_enter_state(s, MTA_MAIL); - return; - - case IMSG_MTA_LOOKUP_HELO: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &status); - if (status == LKA_OK) - m_get_string(&m, &name); - m_end(&m); - - s = mta_tree_pop(&wait_helo, reqid); - if (s == NULL) - return; - - if (status == LKA_OK) { - s->helo = xstrdup(name); - mta_connect(s); - } else { - mta_source_error(s->relay, s->route, - "Failed to retrieve helo string"); - mta_free(s); - } - return; - - default: - errx(1, "mta_session_imsg: unexpected %s imsg", - imsg_to_str(imsg->hdr.type)); - } -} - -static struct mta_session * -mta_tree_pop(struct tree *wait, uint64_t reqid) -{ - struct mta_session *s; - - s = tree_xpop(wait, reqid); - if (s->flags & MTA_FREE) { - log_debug("debug: mta: %p: zombie session", s); - mta_free(s); - return (NULL); - } - s->flags &= ~MTA_WAIT; - - return (s); -} - -static void -mta_free(struct mta_session *s) -{ - struct mta_relay *relay; - struct mta_route *route; - - log_debug("debug: mta: %p: session done", s); - - mta_disconnected(s); - - if (s->ready) - s->relay->nconn_ready -= 1; - - if (s->flags & MTA_HANGON) { - log_debug("debug: mta: %p: cancelling hangon timer", s); - runq_cancel(hangon, s); - } - - if (s->io) - io_free(s->io); - - if (s->task) - fatalx("current task should have been deleted already"); - if (s->datafp) { - fclose(s->datafp); - s->datalen = 0; - } - free(s->helo); - - relay = s->relay; - route = s->route; - free(s->username); - free(s->mxname); - free(s); - stat_decrement("mta.session", 1); - mta_route_collect(relay, route); -} - -static void -mta_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) -{ - struct mta_session *s = arg; - struct mta_host *h; - - h = s->route->dst; - h->lastptrquery = time(NULL); - if (host) - h->ptrname = xstrdup(host); - waitq_run(&h->ptrname, h->ptrname); -} - -static void -mta_on_timeout(struct runq *runq, void *arg) -{ - struct mta_session *s = arg; - - log_debug("mta: timeout for session hangon"); - - s->flags &= ~MTA_HANGON; - s->hangon++; - - mta_enter_state(s, MTA_READY); -} - -static void -mta_on_ptr(void *tag, void *arg, void *data) -{ - struct mta_session *s = arg; - - mta_connect(s); -} - -static void -mta_start(int fd, short ev, void *arg) -{ - struct mta_session *s = arg; - - mta_connect(s); -} - -static void -mta_connect(struct mta_session *s) -{ - struct sockaddr_storage ss; - struct sockaddr *sa; - int portno; - const char *schema; - - if (s->helo == NULL) { - if (s->relay->helotable && s->route->src->sa) { - m_create(p_lka, IMSG_MTA_LOOKUP_HELO, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_string(p_lka, s->relay->helotable); - m_add_sockaddr(p_lka, s->route->src->sa); - m_close(p_lka); - tree_xset(&wait_helo, s->id, s); - s->flags |= MTA_WAIT; - return; - } - else if (s->relay->heloname) - s->helo = xstrdup(s->relay->heloname); - else - s->helo = xstrdup(env->sc_hostname); - } - - if (s->io) { - io_free(s->io); - s->io = NULL; - } - - s->use_smtps = s->use_starttls = s->use_smtp_tls = 0; - - switch (s->attempt) { - case 0: - if (s->flags & MTA_FORCE_SMTPS) - s->use_smtps = 1; /* smtps */ - else if (s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL)) - s->use_starttls = 1; /* tls, tls+smtps */ - else if (!(s->flags & MTA_FORCE_PLAIN)) - s->use_smtp_tls = 1; - break; - case 1: - if (s->flags & MTA_FORCE_ANYSSL) { - s->use_smtps = 1; /* tls+smtps */ - break; - } - else if (s->flags & MTA_DOWNGRADE_PLAIN) { - /* smtp, with tls failure */ - break; - } - default: - mta_free(s); - return; - } - portno = s->use_smtps ? 465 : 25; - - /* Override with relay-specified port */ - if (s->relay->port) - portno = s->relay->port; - - memmove(&ss, s->route->dst->sa, SA_LEN(s->route->dst->sa)); - sa = (struct sockaddr *)&ss; - - if (sa->sa_family == AF_INET) - ((struct sockaddr_in *)sa)->sin_port = htons(portno); - else if (sa->sa_family == AF_INET6) - ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); - - s->attempt += 1; - if (s->use_smtp_tls) - schema = "smtp://"; - else if (s->use_starttls) - schema = "smtp+tls://"; - else if (s->use_smtps) - schema = "smtps://"; - else if (s->flags & MTA_LMTP) - schema = "lmtp://"; - else - schema = "smtp+notls://"; - - log_info("%016"PRIx64" mta " - "connecting address=%s%s:%d host=%s", - s->id, schema, sa_to_text(s->route->dst->sa), - portno, s->route->dst->ptrname); - - mta_enter_state(s, MTA_INIT); - s->io = io_new(); - io_set_callback(s->io, mta_io, s); - io_set_timeout(s->io, 300000); - if (io_connect(s->io, sa, s->route->src->sa) == -1) { - /* - * This error is most likely a "no route", - * so there is no need to try again. - */ - log_debug("debug: mta: io_connect failed: %s", io_error(s->io)); - if (errno == EADDRNOTAVAIL) - mta_source_error(s->relay, s->route, io_error(s->io)); - else - mta_error(s, "Connection failed: %s", io_error(s->io)); - mta_free(s); - } -} - -static void -mta_enter_state(struct mta_session *s, int newstate) -{ - struct mta_envelope *e; - size_t envid_sz; - int oldstate; - ssize_t q; - char ibuf[LINE_MAX]; - char obuf[LINE_MAX]; - int offset; - const char *srs_sender; - -again: - oldstate = s->state; - - log_trace(TRACE_MTA, "mta: %p: %s -> %s", s, - mta_strstate(oldstate), - mta_strstate(newstate)); - - s->state = newstate; - - memset(s->replybuf, 0, sizeof s->replybuf); - - /* don't try this at home! */ -#define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while (0) - - switch (s->state) { - case MTA_INIT: - case MTA_BANNER: - break; - - case MTA_EHLO: - s->ext = 0; - mta_send(s, "EHLO %s", s->helo); - mta_report_link_identify(s, "EHLO", s->helo); - break; - - case MTA_HELO: - s->ext = 0; - mta_send(s, "HELO %s", s->helo); - mta_report_link_identify(s, "HELO", s->helo); - break; - - case MTA_LHLO: - s->ext = 0; - mta_send(s, "LHLO %s", s->helo); - mta_report_link_identify(s, "LHLO", s->helo); - break; - - case MTA_STARTTLS: - if (s->flags & MTA_DOWNGRADE_PLAIN) - mta_enter_state(s, MTA_AUTH); - if (s->flags & MTA_TLS) /* already started */ - mta_enter_state(s, MTA_AUTH); - else if ((s->ext & MTA_EXT_STARTTLS) == 0) { - if (s->flags & MTA_FORCE_TLS || s->flags & MTA_WANT_SECURE) { - mta_error(s, "TLS required but not supported by remote host"); - s->flags |= MTA_RECONN; - } - else - /* server doesn't support starttls, do not use it */ - mta_enter_state(s, MTA_AUTH); - } - else - mta_send(s, "STARTTLS"); - break; - - case MTA_AUTH: - if (s->relay->secret && s->flags & MTA_TLS) { - if (s->ext & MTA_EXT_AUTH) { - if (s->ext & MTA_EXT_AUTH_PLAIN) { - mta_enter_state(s, MTA_AUTH_PLAIN); - break; - } - if (s->ext & MTA_EXT_AUTH_LOGIN) { - mta_enter_state(s, MTA_AUTH_LOGIN); - break; - } - log_debug("debug: mta: %p: no supported AUTH method on session", s); - mta_error(s, "no supported AUTH method"); - } - else { - log_debug("debug: mta: %p: AUTH not advertised on session", s); - mta_error(s, "AUTH not advertised"); - } - } - else if (s->relay->secret) { - log_debug("debug: mta: %p: not using AUTH on non-TLS " - "session", s); - mta_error(s, "Refuse to AUTH over unsecure channel"); - mta_connect(s); - } else { - mta_enter_state(s, MTA_READY); - } - break; - - case MTA_AUTH_PLAIN: - memset(ibuf, 0, sizeof ibuf); - if (base64_decode(s->relay->secret, (unsigned char *)ibuf, - sizeof(ibuf)-1) == -1) { - log_debug("debug: mta: %p: credentials too large on session", s); - mta_error(s, "Credentials too large"); - break; - } - s->username = xstrdup(ibuf+1); - mta_send(s, "AUTH PLAIN %s", s->relay->secret); - break; - - case MTA_AUTH_LOGIN: - mta_send(s, "AUTH LOGIN"); - break; - - case MTA_AUTH_LOGIN_USER: - memset(ibuf, 0, sizeof ibuf); - if (base64_decode(s->relay->secret, (unsigned char *)ibuf, - sizeof(ibuf)-1) == -1) { - log_debug("debug: mta: %p: credentials too large on session", s); - mta_error(s, "Credentials too large"); - break; - } - s->username = xstrdup(ibuf+1); - - memset(obuf, 0, sizeof obuf); - base64_encode((unsigned char *)ibuf + 1, strlen(ibuf + 1), obuf, sizeof obuf); - mta_send(s, "%s", obuf); - - memset(ibuf, 0, sizeof ibuf); - memset(obuf, 0, sizeof obuf); - break; - - case MTA_AUTH_LOGIN_PASS: - memset(ibuf, 0, sizeof ibuf); - if (base64_decode(s->relay->secret, (unsigned char *)ibuf, - sizeof(ibuf)-1) == -1) { - log_debug("debug: mta: %p: credentials too large on session", s); - mta_error(s, "Credentials too large"); - break; - } - - offset = strlen(ibuf+1)+2; - memset(obuf, 0, sizeof obuf); - base64_encode((unsigned char *)ibuf + offset, strlen(ibuf + offset), obuf, sizeof obuf); - mta_send(s, "%s", obuf); - - memset(ibuf, 0, sizeof ibuf); - memset(obuf, 0, sizeof obuf); - break; - - case MTA_READY: - /* Ready to send a new mail */ - if (s->ready == 0) { - s->ready = 1; - s->relay->nconn_ready += 1; - mta_route_ok(s->relay, s->route); - } - - if (s->msgtried >= MAX_TRYBEFOREDISABLE) { - log_info("%016"PRIx64" mta host-rejects-all-mails", - s->id); - mta_route_down(s->relay, s->route); - mta_enter_state(s, MTA_QUIT); - break; - } - - if (s->msgcount >= s->relay->limits->max_mail_per_session) { - log_debug("debug: mta: " - "%p: cannot send more message to relay %s", s, - mta_relay_to_text(s->relay)); - mta_enter_state(s, MTA_QUIT); - break; - } - - /* - * When downgrading from opportunistic TLS, clear flag and - * possibly reuse the same task (forbidden in other cases). - */ - if (s->flags & MTA_DOWNGRADE_PLAIN) - s->flags &= ~MTA_DOWNGRADE_PLAIN; - else if (s->task) - fatalx("task should be NULL at this point"); - - if (s->task == NULL) - s->task = mta_route_next_task(s->relay, s->route); - if (s->task == NULL) { - log_debug("debug: mta: %p: no task for relay %s", - s, mta_relay_to_text(s->relay)); - - if (s->relay->nconn > 1 || - s->hangon >= s->relay->limits->sessdelay_keepalive) { - mta_enter_state(s, MTA_QUIT); - break; - } - - log_debug("mta: debug: last connection: hanging on for %llds", - (long long)(s->relay->limits->sessdelay_keepalive - - s->hangon)); - s->flags |= MTA_HANGON; - runq_schedule(hangon, 1, s); - break; - } - - log_debug("debug: mta: %p: handling next task for relay %s", s, - mta_relay_to_text(s->relay)); - - stat_increment("mta.task.running", 1); - - m_create(p_queue, IMSG_MTA_OPEN_MESSAGE, 0, 0, -1); - m_add_id(p_queue, s->id); - m_add_msgid(p_queue, s->task->msgid); - m_close(p_queue); - - tree_xset(&wait_fd, s->id, s); - s->flags |= MTA_WAIT; - break; - - case MTA_MAIL: - s->currevp = TAILQ_FIRST(&s->task->envelopes); - - e = s->currevp; - s->hangon = 0; - s->msgtried++; - envid_sz = strlen(e->dsn_envid); - - /* SRS-encode if requested for the relay action, AND we're not - * bouncing, AND we have an RCPT which means we are forwarded, - * AND the RCPT has a '@' just for sanity check (will always). - */ - if (env->sc_srs_key != NULL && - s->relay->srs && - strchr(s->task->sender, '@') && - e->rcpt && - strchr(e->rcpt, '@')) { - /* encode and replace task sender with new SRS-sender */ - srs_sender = srs_encode(s->task->sender, - strchr(e->rcpt, '@') + 1); - if (srs_sender) { - free(s->task->sender); - s->task->sender = xstrdup(srs_sender); - } - } - - if (s->ext & MTA_EXT_DSN) { - mta_send(s, "MAIL FROM:<%s>%s%s%s%s", - s->task->sender, - e->dsn_ret ? " RET=" : "", - e->dsn_ret ? dsn_strret(e->dsn_ret) : "", - envid_sz ? " ENVID=" : "", - envid_sz ? e->dsn_envid : ""); - } else - mta_send(s, "MAIL FROM:<%s>", s->task->sender); - break; - - case MTA_RCPT: - if (s->currevp == NULL) - s->currevp = TAILQ_FIRST(&s->task->envelopes); - - e = s->currevp; - if (s->ext & MTA_EXT_DSN) { - mta_send(s, "RCPT TO:<%s>%s%s%s%s", - e->dest, - e->dsn_notify ? " NOTIFY=" : "", - e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "", - e->dsn_orcpt ? " ORCPT=rfc822;" : "", - e->dsn_orcpt ? e->dsn_orcpt : ""); - } else - mta_send(s, "RCPT TO:<%s>", e->dest); - - mta_report_tx_envelope(s, s->task->msgid, e->id); - s->rcptcount++; - break; - - case MTA_DATA: - fseek(s->datafp, 0, SEEK_SET); - mta_send(s, "DATA"); - break; - - case MTA_BODY: - if (s->datafp == NULL) { - log_trace(TRACE_MTA, "mta: %p: end-of-file", s); - mta_enter_state(s, MTA_EOM); - break; - } - - if ((q = mta_queue_data(s)) == -1) { - s->flags |= MTA_FREE; - break; - } - if (q == 0) { - mta_enter_state(s, MTA_BODY); - break; - } - - log_trace(TRACE_MTA, "mta: %p: >>> [...%zd bytes...]", s, q); - break; - - case MTA_EOM: - mta_send(s, "."); - break; - - case MTA_LMTP_EOM: - /* LMTP reports status of each delivery, so enable read */ - io_set_read(s->io); - break; - - case MTA_RSET: - if (s->datafp) { - fclose(s->datafp); - s->datafp = NULL; - s->datalen = 0; - } - mta_send(s, "RSET"); - break; - - case MTA_QUIT: - mta_send(s, "QUIT"); - break; - - default: - fatalx("mta_enter_state: unknown state"); - } -#undef mta_enter_state -} - -/* - * Handle a response to an SMTP command - */ -static void -mta_response(struct mta_session *s, char *line) -{ - struct mta_envelope *e; - struct sockaddr_storage ss; - struct sockaddr *sa; - const char *domain; - char *pbuf; - socklen_t sa_len; - char buf[LINE_MAX]; - int delivery; - - switch (s->state) { - - case MTA_BANNER: - if (line[0] != '2') { - mta_error(s, "BANNER rejected: %s", line); - s->flags |= MTA_FREE; - return; - } - - pbuf = ""; - if (strlen(line) > 4) { - (void)strlcpy(buf, line + 4, sizeof buf); - if ((pbuf = strchr(buf, ' '))) - *pbuf = '\0'; - pbuf = valid_domainpart(buf) ? buf : ""; - } - mta_report_link_greeting(s, pbuf); - - if (s->flags & MTA_LMTP) - mta_enter_state(s, MTA_LHLO); - else - mta_enter_state(s, MTA_EHLO); - break; - - case MTA_EHLO: - if (line[0] != '2') { - /* rejected at ehlo state */ - if ((s->relay->flags & RELAY_AUTH) || - (s->flags & MTA_WANT_SECURE)) { - mta_error(s, "EHLO rejected: %s", line); - s->flags |= MTA_FREE; - return; - } - mta_enter_state(s, MTA_HELO); - return; - } - if (!(s->flags & MTA_FORCE_PLAIN)) - mta_enter_state(s, MTA_STARTTLS); - else - mta_enter_state(s, MTA_READY); - break; - - case MTA_HELO: - if (line[0] != '2') { - mta_error(s, "HELO rejected: %s", line); - s->flags |= MTA_FREE; - return; - } - mta_enter_state(s, MTA_READY); - break; - - case MTA_LHLO: - if (line[0] != '2') { - mta_error(s, "LHLO rejected: %s", line); - s->flags |= MTA_FREE; - return; - } - mta_enter_state(s, MTA_READY); - break; - - case MTA_STARTTLS: - if (line[0] != '2') { - if (!(s->flags & MTA_WANT_SECURE)) { - mta_enter_state(s, MTA_AUTH); - return; - } - /* XXX mark that the MX doesn't support STARTTLS */ - mta_error(s, "STARTTLS rejected: %s", line); - s->flags |= MTA_FREE; - return; - } - - mta_cert_init(s); - break; - - case MTA_AUTH_PLAIN: - if (line[0] != '2') { - mta_error(s, "AUTH rejected: %s", line); - mta_report_link_auth(s, s->username, "fail"); - s->flags |= MTA_FREE; - return; - } - mta_report_link_auth(s, s->username, "pass"); - mta_enter_state(s, MTA_READY); - break; - - case MTA_AUTH_LOGIN: - if (strncmp(line, "334 ", 4) != 0) { - mta_error(s, "AUTH rejected: %s", line); - mta_report_link_auth(s, s->username, "fail"); - s->flags |= MTA_FREE; - return; - } - mta_enter_state(s, MTA_AUTH_LOGIN_USER); - break; - - case MTA_AUTH_LOGIN_USER: - if (strncmp(line, "334 ", 4) != 0) { - mta_error(s, "AUTH rejected: %s", line); - mta_report_link_auth(s, s->username, "fail"); - s->flags |= MTA_FREE; - return; - } - mta_enter_state(s, MTA_AUTH_LOGIN_PASS); - break; - - case MTA_AUTH_LOGIN_PASS: - if (line[0] != '2') { - mta_error(s, "AUTH rejected: %s", line); - mta_report_link_auth(s, s->username, "fail"); - s->flags |= MTA_FREE; - return; - } - mta_report_link_auth(s, s->username, "pass"); - mta_enter_state(s, MTA_READY); - break; - - case MTA_MAIL: - if (line[0] != '2') { - if (line[0] == '5') - delivery = IMSG_MTA_DELIVERY_PERMFAIL; - else - delivery = IMSG_MTA_DELIVERY_TEMPFAIL; - - mta_flush_task(s, delivery, line, 0, 0); - mta_enter_state(s, MTA_RSET); - return; - } - mta_report_tx_begin(s, s->task->msgid); - mta_report_tx_mail(s, s->task->msgid, s->task->sender, 1); - mta_enter_state(s, MTA_RCPT); - break; - - case MTA_RCPT: - e = s->currevp; - - /* remove envelope from hosttat cache if there */ - if ((domain = strchr(e->dest, '@')) != NULL) { - domain++; - mta_hoststat_uncache(domain, e->id); - } - - s->currevp = TAILQ_NEXT(s->currevp, entry); - if (line[0] == '2') { - s->failures = 0; - /* - * this host is up, reschedule envelopes that - * were cached for reschedule. - */ - if (domain) - mta_hoststat_reschedule(domain); - } - else { - mta_report_tx_rollback(s, s->task->msgid); - mta_report_tx_reset(s, s->task->msgid); - if (line[0] == '5') - delivery = IMSG_MTA_DELIVERY_PERMFAIL; - else - delivery = IMSG_MTA_DELIVERY_TEMPFAIL; - s->failures++; - - /* remove failed envelope from task list */ - TAILQ_REMOVE(&s->task->envelopes, e, entry); - stat_decrement("mta.envelope", 1); - - /* log right away */ - (void)snprintf(buf, sizeof(buf), "%s", - mta_host_to_text(s->route->dst)); - - e->session = s->id; - /* XXX */ - /* - * getsockname() can only fail with ENOBUFS here - * best effort, don't log source ... - */ - sa_len = sizeof(ss); - sa = (struct sockaddr *)&ss; - if (getsockname(io_fileno(s->io), sa, &sa_len) == -1) - mta_delivery_log(e, NULL, buf, delivery, line); - else - mta_delivery_log(e, sa_to_text(sa), - buf, delivery, line); - - if (domain) - mta_hoststat_update(domain, e->status); - mta_delivery_notify(e); - - if (s->relay->limits->max_failures_per_session && - s->failures == s->relay->limits->max_failures_per_session) { - mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, - "Too many consecutive errors, closing connection", 0, 1); - mta_enter_state(s, MTA_QUIT); - break; - } - - /* - * if no more envelopes, flush failed queue - */ - if (TAILQ_EMPTY(&s->task->envelopes)) { - mta_flush_task(s, IMSG_MTA_DELIVERY_OK, - "No envelope", 0, 0); - mta_enter_state(s, MTA_RSET); - break; - } - } - - switch (line[0]) { - case '2': - mta_report_tx_rcpt(s, - s->task->msgid, e->dest, 1); - break; - case '4': - mta_report_tx_rcpt(s, - s->task->msgid, e->dest, -1); - break; - case '5': - mta_report_tx_rcpt(s, - s->task->msgid, e->dest, 0); - break; - } - - if (s->currevp == NULL) - mta_enter_state(s, MTA_DATA); - else - mta_enter_state(s, MTA_RCPT); - break; - - case MTA_DATA: - if (line[0] == '2' || line[0] == '3') { - mta_report_tx_data(s, s->task->msgid, 1); - mta_enter_state(s, MTA_BODY); - break; - } - - if (line[0] == '5') - delivery = IMSG_MTA_DELIVERY_PERMFAIL; - else - delivery = IMSG_MTA_DELIVERY_TEMPFAIL; - mta_report_tx_data(s, s->task->msgid, - delivery == IMSG_MTA_DELIVERY_TEMPFAIL ? -1 : 0); - mta_report_tx_rollback(s, s->task->msgid); - mta_report_tx_reset(s, s->task->msgid); - mta_flush_task(s, delivery, line, 0, 0); - mta_enter_state(s, MTA_RSET); - break; - - case MTA_LMTP_EOM: - case MTA_EOM: - if (line[0] == '2') { - delivery = IMSG_MTA_DELIVERY_OK; - s->msgtried = 0; - s->msgcount++; - } - else if (line[0] == '5') - delivery = IMSG_MTA_DELIVERY_PERMFAIL; - else - delivery = IMSG_MTA_DELIVERY_TEMPFAIL; - if (delivery != IMSG_MTA_DELIVERY_OK) { - mta_report_tx_rollback(s, s->task->msgid); - mta_report_tx_reset(s, s->task->msgid); - } - else { - mta_report_tx_commit(s, s->task->msgid, s->datalen); - mta_report_tx_reset(s, s->task->msgid); - } - mta_flush_task(s, delivery, line, (s->flags & MTA_LMTP) ? 1 : 0, 0); - if (s->task) { - s->rcptcount--; - mta_enter_state(s, MTA_LMTP_EOM); - } else { - s->rcptcount = 0; - if (s->relay->limits->sessdelay_transaction) { - log_debug("debug: mta: waiting for %llds before next transaction", - (long long int)s->relay->limits->sessdelay_transaction); - s->hangon = s->relay->limits->sessdelay_transaction -1; - s->flags |= MTA_HANGON; - runq_schedule(hangon, - s->relay->limits->sessdelay_transaction, s); - } - else - mta_enter_state(s, MTA_READY); - } - break; - - case MTA_RSET: - s->rcptcount = 0; - - if (s->task) { - mta_report_tx_rollback(s, s->task->msgid); - mta_report_tx_reset(s, s->task->msgid); - } - if (s->relay->limits->sessdelay_transaction) { - log_debug("debug: mta: waiting for %llds after reset", - (long long int)s->relay->limits->sessdelay_transaction); - s->hangon = s->relay->limits->sessdelay_transaction -1; - s->flags |= MTA_HANGON; - runq_schedule(hangon, - s->relay->limits->sessdelay_transaction, s); - } - else - mta_enter_state(s, MTA_READY); - break; - - default: - fatalx("mta_response() bad state"); - } -} - -static void -mta_io(struct io *io, int evt, void *arg) -{ - struct mta_session *s = arg; - char *line, *msg, *p; - size_t len; - const char *error; - int cont; - - log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), - io_strio(io)); - - switch (evt) { - - case IO_CONNECTED: - mta_connected(s); - - if (s->use_smtps) { - io_set_write(io); - mta_cert_init(s); - } - else { - mta_enter_state(s, MTA_BANNER); - io_set_read(io); - } - break; - - case IO_TLSREADY: - log_info("%016"PRIx64" mta tls ciphers=%s", - s->id, ssl_to_text(io_tls(s->io))); - s->flags |= MTA_TLS; - - mta_report_link_tls(s, - ssl_to_text(io_tls(s->io))); - - mta_cert_verify(s); - break; - - case IO_DATAIN: - nextline: - line = io_getline(s->io, &len); - if (line == NULL) { - if (io_datalen(s->io) >= LINE_MAX) { - mta_error(s, "Input too long"); - mta_free(s); - } - return; - } - - /* Strip trailing '\r' */ - if (len && line[len - 1] == '\r') - line[--len] = '\0'; - - log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); - mta_report_protocol_server(s, line); - - if ((error = parse_smtp_response(line, len, &msg, &cont))) { - mta_error(s, "Bad response: %s", error); - mta_free(s); - return; - } - - /* read extensions */ - if (s->state == MTA_EHLO) { - if (strcmp(msg, "STARTTLS") == 0) - s->ext |= MTA_EXT_STARTTLS; - else if (strncmp(msg, "AUTH ", 5) == 0) { - s->ext |= MTA_EXT_AUTH; - if ((p = strstr(msg, " PLAIN")) && - (*(p+6) == '\0' || *(p+6) == ' ')) - s->ext |= MTA_EXT_AUTH_PLAIN; - if ((p = strstr(msg, " LOGIN")) && - (*(p+6) == '\0' || *(p+6) == ' ')) - s->ext |= MTA_EXT_AUTH_LOGIN; - } - else if (strcmp(msg, "PIPELINING") == 0) - s->ext |= MTA_EXT_PIPELINING; - else if (strcmp(msg, "DSN") == 0) - s->ext |= MTA_EXT_DSN; - else if (strncmp(msg, "SIZE ", 5) == 0) { - s->ext_size = strtonum(msg+5, 0, UINT32_MAX, &error); - if (error == NULL) - s->ext |= MTA_EXT_SIZE; - } - } - - /* continuation reply, we parse out the repeating statuses and ESC */ - if (cont) { - if (s->replybuf[0] == '\0') - (void)strlcat(s->replybuf, line, sizeof s->replybuf); - else if (len > 4) { - p = line + 4; - if (isdigit((unsigned char)p[0]) && p[1] == '.' && - isdigit((unsigned char)p[2]) && p[3] == '.' && - isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5])) - p += 5; - (void)strlcat(s->replybuf, p, sizeof s->replybuf); - } - goto nextline; - } - - /* last line of a reply, check if we're on a continuation to parse out status and ESC. - * if we overflow reply buffer or are not on continuation, log entire last line. - */ - if (s->replybuf[0] == '\0') - (void)strlcat(s->replybuf, line, sizeof s->replybuf); - else if (len > 4) { - p = line + 4; - if (isdigit((unsigned char)p[0]) && p[1] == '.' && - isdigit((unsigned char)p[2]) && p[3] == '.' && - isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5])) - p += 5; - if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf) - (void)strlcpy(s->replybuf, line, sizeof s->replybuf); - } - - if (s->state == MTA_QUIT) { - log_info("%016"PRIx64" mta disconnected reason=quit messages=%zu", - s->id, s->msgcount); - mta_free(s); - return; - } - io_set_write(io); - mta_response(s, s->replybuf); - if (s->flags & MTA_FREE) { - mta_free(s); - return; - } - if (s->flags & MTA_RECONN) { - s->flags &= ~MTA_RECONN; - mta_connect(s); - return; - } - - if (io_datalen(s->io)) { - log_debug("debug: mta: remaining data in input buffer"); - mta_error(s, "Remote host sent too much data"); - if (s->flags & MTA_WAIT) - s->flags |= MTA_FREE; - else - mta_free(s); - } - break; - - case IO_LOWAT: - if (s->state == MTA_BODY) { - mta_enter_state(s, MTA_BODY); - if (s->flags & MTA_FREE) { - mta_free(s); - return; - } - } - - if (io_queued(s->io) == 0) - io_set_read(io); - break; - - case IO_TIMEOUT: - log_debug("debug: mta: %p: connection timeout", s); - mta_error(s, "Connection timeout"); - mta_report_timeout(s); - if (!s->ready) - mta_connect(s); - else - mta_free(s); - break; - - case IO_ERROR: - case IO_TLSERROR: - log_debug("debug: mta: %p: IO error: %s", s, io_error(io)); - - if (s->state == MTA_STARTTLS && s->use_smtp_tls) { - /* error in non-strict SSL negotiation, downgrade to plain */ - log_info("smtp-out: Error on session %016"PRIx64 - ": opportunistic TLS failed, " - "downgrading to plain", s->id); - s->flags &= ~MTA_TLS; - s->flags |= MTA_DOWNGRADE_PLAIN; - mta_connect(s); - break; - } - - mta_error(s, "IO Error: %s", io_error(io)); - mta_free(s); - break; - - case IO_DISCONNECTED: - log_debug("debug: mta: %p: disconnected in state %s", - s, mta_strstate(s->state)); - mta_error(s, "Connection closed unexpectedly"); - if (!s->ready) - mta_connect(s); - else - mta_free(s); - break; - - default: - fatalx("mta_io() bad event"); - } -} - -static void -mta_send(struct mta_session *s, char *fmt, ...) -{ - va_list ap; - char *p; - int len; - - va_start(ap, fmt); - if ((len = vasprintf(&p, fmt, ap)) == -1) - fatal("mta: vasprintf"); - va_end(ap); - - log_trace(TRACE_MTA, "mta: %p: >>> %s", s, p); - - if (strncasecmp(p, "AUTH PLAIN ", 11) == 0) - mta_report_protocol_client(s, "AUTH PLAIN ********"); - else if (s->state == MTA_AUTH_LOGIN_USER || s->state == MTA_AUTH_LOGIN_PASS) - mta_report_protocol_client(s, "********"); - else - mta_report_protocol_client(s, p); - - io_xprintf(s->io, "%s\r\n", p); - - free(p); -} - -/* - * Queue some data into the input buffer - */ -static ssize_t -mta_queue_data(struct mta_session *s) -{ - char *ln = NULL; - size_t sz = 0, q; - ssize_t len; - - q = io_queued(s->io); - - while (io_queued(s->io) < MTA_HIWAT) { - if ((len = getline(&ln, &sz, s->datafp)) == -1) - break; - if (ln[len - 1] == '\n') - ln[len - 1] = '\0'; - s->datalen += io_xprintf(s->io, "%s%s\r\n", *ln == '.' ? "." : "", ln); - } - - free(ln); - if (ferror(s->datafp)) { - mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, - "Error reading content file", 0, 0); - return (-1); - } - - if (feof(s->datafp)) { - fclose(s->datafp); - s->datafp = NULL; - } - - return (io_queued(s->io) - q); -} - -static void -mta_flush_task(struct mta_session *s, int delivery, const char *error, size_t count, - int cache) -{ - struct mta_envelope *e; - char relay[LINE_MAX]; - size_t n; - struct sockaddr_storage ss; - struct sockaddr *sa; - socklen_t sa_len; - const char *domain; - - (void)snprintf(relay, sizeof relay, "%s", mta_host_to_text(s->route->dst)); - n = 0; - while ((e = TAILQ_FIRST(&s->task->envelopes))) { - - if (count && n == count) { - stat_decrement("mta.envelope", n); - return; - } - - TAILQ_REMOVE(&s->task->envelopes, e, entry); - - /* we're about to log, associate session to envelope */ - e->session = s->id; - e->ext = s->ext; - - /* XXX */ - /* - * getsockname() can only fail with ENOBUFS here - * best effort, don't log source ... - */ - sa = (struct sockaddr *)&ss; - sa_len = sizeof(ss); - if (getsockname(io_fileno(s->io), sa, &sa_len) == -1) - mta_delivery_log(e, NULL, relay, delivery, error); - else - mta_delivery_log(e, sa_to_text(sa), - relay, delivery, error); - - mta_delivery_notify(e); - - domain = strchr(e->dest, '@'); - if (domain) { - domain++; - mta_hoststat_update(domain, error); - if (cache) - mta_hoststat_cache(domain, e->id); - } - - n++; - } - - free(s->task->sender); - free(s->task); - s->task = NULL; - - if (s->datafp) { - fclose(s->datafp); - s->datafp = NULL; - } - - stat_decrement("mta.envelope", n); - stat_decrement("mta.task.running", 1); - stat_decrement("mta.task", 1); -} - -static void -mta_error(struct mta_session *s, const char *fmt, ...) -{ - va_list ap; - char *error; - int len; - - va_start(ap, fmt); - if ((len = vasprintf(&error, fmt, ap)) == -1) - fatal("mta: vasprintf"); - va_end(ap); - - if (s->msgcount) - log_info("smtp-out: Error on session %016"PRIx64 - " after %zu message%s sent: %s", s->id, s->msgcount, - (s->msgcount > 1) ? "s" : "", error); - else - log_info("%016"PRIx64" mta error reason=%s", - s->id, error); - - /* - * If not connected yet, and the error is not local, just ignore it - * and try to reconnect. - */ - if (s->state == MTA_INIT && - (errno == ETIMEDOUT || errno == ECONNREFUSED)) { - log_debug("debug: mta: not reporting route error yet"); - free(error); - return; - } - - mta_route_error(s->relay, s->route); - - if (s->task) - mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, error, 0, 0); - - free(error); -} - -static void -mta_cert_init(struct mta_session *s) -{ - const char *name; - int fallback; - - if (s->relay->pki_name) { - name = s->relay->pki_name; - fallback = 0; - } - else { - name = s->helo; - fallback = 1; - } - - if (cert_init(name, fallback, mta_cert_init_cb, s)) { - tree_xset(&wait_tls_init, s->id, s); - s->flags |= MTA_WAIT; - } -} - -static void -mta_cert_init_cb(void *arg, int status, const char *name, const void *cert, - size_t cert_len) -{ - struct mta_session *s = arg; - void *ssl; - char *xname = NULL, *xcert = NULL; - - if (s->flags & MTA_WAIT) - mta_tree_pop(&wait_tls_init, s->id); - - if (status == CA_FAIL && s->relay->pki_name) { - log_info("%016"PRIx64" mta closing reason=ca-failure", s->id); - mta_free(s); - return; - } - - if (name) - xname = xstrdup(name); - if (cert) - xcert = xmemdup(cert, cert_len); - ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers); - free(xname); - free(xcert); - if (ssl == NULL) - fatal("mta: ssl_mta_init"); - io_start_tls(s->io, ssl); -} - -static void -mta_cert_verify(struct mta_session *s) -{ - const char *name; - int fallback; - - if (s->relay->ca_name) { - name = s->relay->ca_name; - fallback = 0; - } - else { - name = s->helo; - fallback = 1; - } - - if (cert_verify(io_tls(s->io), name, fallback, mta_cert_verify_cb, s)) { - tree_xset(&wait_tls_verify, s->id, s); - io_pause(s->io, IO_IN); - s->flags |= MTA_WAIT; - } -} - -static void -mta_cert_verify_cb(void *arg, int status) -{ - struct mta_session *s = arg; - int match, resume = 0; - X509 *cert; - - if (s->flags & MTA_WAIT) { - mta_tree_pop(&wait_tls_verify, s->id); - resume = 1; - } - - if (status == CERT_OK) { - cert = SSL_get_peer_certificate(io_tls(s->io)); - if (!cert) - status = CERT_NOCERT; - else { - match = 0; - (void)ssl_check_name(cert, s->mxname, &match); - X509_free(cert); - if (!match) { - log_info("%016"PRIx64" mta " - "ssl_check_name: no match for '%s' in cert", - s->id, s->mxname); - status = CERT_INVALID; - } - } - } - - if (status == CERT_OK) - s->flags |= MTA_TLS_VERIFIED; - else if (s->relay->flags & RELAY_TLS_VERIFY) { - errno = 0; - mta_error(s, "SSL certificate check failed"); - mta_free(s); - return; - } - - mta_tls_verified(s); - if (resume) - io_resume(s->io, IO_IN); -} - -static void -mta_tls_verified(struct mta_session *s) -{ - X509 *x; - - x = SSL_get_peer_certificate(io_tls(s->io)); - if (x) { - log_info("%016"PRIx64" mta " - "server-cert-check result=\"%s\"", - s->id, - (s->flags & MTA_TLS_VERIFIED) ? "success" : "failure"); - X509_free(x); - } - - if (s->use_smtps) { - mta_enter_state(s, MTA_BANNER); - io_set_read(s->io); - } - else - mta_enter_state(s, MTA_EHLO); -} - -static const char * -dsn_strret(enum dsn_ret ret) -{ - if (ret == DSN_RETHDRS) - return "HDRS"; - else if (ret == DSN_RETFULL) - return "FULL"; - else { - log_debug("mta: invalid ret %d", ret); - return "???"; - } -} - -static const char * -dsn_strnotify(uint8_t arg) -{ - static char buf[32]; - size_t sz; - - buf[0] = '\0'; - if (arg & DSN_SUCCESS) - (void)strlcat(buf, "SUCCESS,", sizeof(buf)); - - if (arg & DSN_FAILURE) - (void)strlcat(buf, "FAILURE,", sizeof(buf)); - - if (arg & DSN_DELAY) - (void)strlcat(buf, "DELAY,", sizeof(buf)); - - if (arg & DSN_NEVER) - (void)strlcat(buf, "NEVER,", sizeof(buf)); - - /* trim trailing comma */ - sz = strlen(buf); - if (sz) - buf[sz - 1] = '\0'; - - return (buf); -} - -#define CASE(x) case x : return #x - -static const char * -mta_strstate(int state) -{ - switch (state) { - CASE(MTA_INIT); - CASE(MTA_BANNER); - CASE(MTA_EHLO); - CASE(MTA_HELO); - CASE(MTA_STARTTLS); - CASE(MTA_AUTH); - CASE(MTA_AUTH_PLAIN); - CASE(MTA_AUTH_LOGIN); - CASE(MTA_AUTH_LOGIN_USER); - CASE(MTA_AUTH_LOGIN_PASS); - CASE(MTA_READY); - CASE(MTA_MAIL); - CASE(MTA_RCPT); - CASE(MTA_DATA); - CASE(MTA_BODY); - CASE(MTA_EOM); - CASE(MTA_LMTP_EOM); - CASE(MTA_RSET); - CASE(MTA_QUIT); - default: - return "MTA_???"; - } -} - -static void -mta_filter_begin(struct mta_session *s) -{ - if (!SESSION_FILTERED(s)) - return; - - m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_string(p_lka, s->relay->dispatcher->u.remote.filtername); - m_close(p_lka); -} - -static void -mta_filter_end(struct mta_session *s) -{ - if (!SESSION_FILTERED(s)) - return; - - m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); - m_add_id(p_lka, s->id); - m_close(p_lka); -} - -static void -mta_connected(struct mta_session *s) -{ - struct sockaddr sa_src; - struct sockaddr sa_dest; - int sa_len; - - log_info("%016"PRIx64" mta connected", s->id); - - if (getsockname(io_fileno(s->io), &sa_src, &sa_len) == -1) - bzero(&sa_src, sizeof sa_src); - if (getpeername(io_fileno(s->io), &sa_dest, &sa_len) == -1) - bzero(&sa_dest, sizeof sa_dest); - - mta_report_link_connect(s, - s->route->dst->ptrname, 1, - (struct sockaddr_storage *)&sa_src, - (struct sockaddr_storage *)&sa_dest); -} - -static void -mta_disconnected(struct mta_session *s) -{ - mta_report_link_disconnect(s); - mta_filter_end(s); -} - - -static void -mta_report_link_connect(struct mta_session *s, const char *rdns, int fcrdns, - const struct sockaddr_storage *ss_src, - const struct sockaddr_storage *ss_dest) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_connect("smtp-out", s->id, rdns, fcrdns, ss_src, ss_dest); -} - -static void -mta_report_link_greeting(struct mta_session *s, - const char *domain) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_greeting("smtp-out", s->id, domain); -} - -static void -mta_report_link_identify(struct mta_session *s, const char *method, const char *identity) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_identify("smtp-out", s->id, method, identity); -} - -static void -mta_report_link_tls(struct mta_session *s, const char *ssl) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_tls("smtp-out", s->id, ssl); -} - -static void -mta_report_link_disconnect(struct mta_session *s) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_disconnect("smtp-out", s->id); -} - -static void -mta_report_link_auth(struct mta_session *s, const char *user, const char *result) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_auth("smtp-out", s->id, user, result); -} - -static void -mta_report_tx_reset(struct mta_session *s, uint32_t msgid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_reset("smtp-out", s->id, msgid); -} - -static void -mta_report_tx_begin(struct mta_session *s, uint32_t msgid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_begin("smtp-out", s->id, msgid); -} - -static void -mta_report_tx_mail(struct mta_session *s, uint32_t msgid, const char *address, int ok) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_mail("smtp-out", s->id, msgid, address, ok); -} - -static void -mta_report_tx_rcpt(struct mta_session *s, uint32_t msgid, const char *address, int ok) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_rcpt("smtp-out", s->id, msgid, address, ok); -} - -static void -mta_report_tx_envelope(struct mta_session *s, uint32_t msgid, uint64_t evpid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_envelope("smtp-out", s->id, msgid, evpid); -} - -static void -mta_report_tx_data(struct mta_session *s, uint32_t msgid, int ok) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_data("smtp-out", s->id, msgid, ok); -} - -static void -mta_report_tx_commit(struct mta_session *s, uint32_t msgid, size_t msgsz) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_commit("smtp-out", s->id, msgid, msgsz); -} - -static void -mta_report_tx_rollback(struct mta_session *s, uint32_t msgid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_rollback("smtp-out", s->id, msgid); -} - -static void -mta_report_protocol_client(struct mta_session *s, const char *command) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_protocol_client("smtp-out", s->id, command); -} - -static void -mta_report_protocol_server(struct mta_session *s, const char *response) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_protocol_server("smtp-out", s->id, response); -} - -#if 0 -static void -mta_report_filter_response(struct mta_session *s, int phase, int response, const char *param) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_filter_response("smtp-out", s->id, phase, response, param); -} -#endif - -static void -mta_report_timeout(struct mta_session *s) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_timeout("smtp-out", s->id); -} diff --git a/smtpd/newaliases.8 b/smtpd/newaliases.8 deleted file mode 100644 index b82e4515..00000000 --- a/smtpd/newaliases.8 +++ /dev/null @@ -1,86 +0,0 @@ -.\" $OpenBSD: newaliases.8,v 1.12 2018/07/20 15:35:33 millert Exp $ -.\" -.\" Copyright (c) 2009 Jacek Masiulaniec -.\" Copyright (c) 2008-2009 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: July 20 2018 $ -.Dt NEWALIASES 8 -.Os -.Sh NAME -.Nm newaliases -.Nd rebuild mail aliases -.Sh SYNOPSIS -.Nm newaliases -.Op Fl f Ar file -.Sh DESCRIPTION -The -.Nm -utility makes changes to the mail aliases file visible to -.Xr smtpd 8 . -It should be run every time the -.Xr aliases 5 -file is changed. -The location of the alias file is defined in -.Xr smtpd.conf 5 , -and defaults to -.Pa /etc/mail/aliases . -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl f Ar file -Use -.Ar file -as the configuration file, -instead of the default -.Pa /etc/mail/smtpd.conf . -.El -.Pp -If using database (db) files, -.Nm -is equivalent to running -.Xr makemap 8 -as follows: -.Bd -literal -offset indent -# makemap -t aliases /etc/mail/aliases -.Ed -.Pp -If using plain text files, -.Nm -is equivalent to running -.Xr smtpctl 8 -as follows: -.Bd -literal -offset indent -# smtpctl update table aliases -.Ed -.Sh FILES -.Bl -tag -width "/etc/mail/aliasesXXX" -compact -.It Pa /etc/mail/aliases -List of local user mail aliases. -.It Pa /etc/mail/virtual -List of virtual host aliases. -.El -.Sh EXIT STATUS -.Ex -std newaliases -.Sh SEE ALSO -.Xr smtpd.conf 5 , -.Xr makemap 8 , -.Xr smtpctl 8 , -.Xr smtpd 8 -.Sh HISTORY -The -.Nm -command first appeared in -.Ox 4.6 -as a replacement for the equivalent command shipped with sendmail. diff --git a/smtpd/parse.y b/smtpd/parse.y deleted file mode 100644 index db5be747..00000000 --- a/smtpd/parse.y +++ /dev/null @@ -1,3598 +0,0 @@ -/* $OpenBSD: parse.y,v 1.277 2020/02/24 23:54:27 millert Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2002, 2003, 2004 Henning Brauer - * Copyright (c) 2001 Markus Friedl. All rights reserved. - * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. - * Copyright (c) 2001 Theo de Raadt. All rights reserved. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -%{ -#include "includes.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UTIL_H -#include -#endif - -#include - -#include "smtpd.h" -#include "ssl.h" -#include "log.h" - -TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); -static struct file { - TAILQ_ENTRY(file) entry; - FILE *stream; - char *name; - size_t ungetpos; - size_t ungetsize; - u_char *ungetbuf; - int eof_reached; - int lineno; - int errors; -} *file, *topfile; -struct file *pushfile(const char *, int); -int popfile(void); -int check_file_secrecy(int, const char *); -int yyparse(void); -int yylex(void); -int kw_cmp(const void *, const void *); -int lookup(char *); -int igetc(void); -int lgetc(int); -void lungetc(int); -int findeol(void); -int yyerror(const char *, ...) - __attribute__((__format__ (printf, 1, 2))) - __attribute__((__nonnull__ (1))); - -TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); -struct sym { - TAILQ_ENTRY(sym) entry; - int used; - int persist; - char *nam; - char *val; -}; -int symset(const char *, const char *, int); -char *symget(const char *); - -struct smtpd *conf = NULL; -static int errors = 0; - -struct table *table = NULL; -struct mta_limits *limits; -static struct pki *pki; -static struct ca *sca; - -struct dispatcher *dispatcher; -struct rule *rule; -struct filter_proc *processor; -struct filter_config *filter_config; -static uint32_t last_dynchain_id = 1; - -enum listen_options { - LO_FAMILY = 0x000001, - LO_PORT = 0x000002, - LO_SSL = 0x000004, - LO_FILTER = 0x000008, - LO_PKI = 0x000010, - LO_AUTH = 0x000020, - LO_TAG = 0x000040, - LO_HOSTNAME = 0x000080, - LO_HOSTNAMES = 0x000100, - LO_MASKSOURCE = 0x000200, - LO_NODSN = 0x000400, - LO_SENDERS = 0x000800, - LO_RECEIVEDAUTH = 0x001000, - LO_MASQUERADE = 0x002000, - LO_CA = 0x004000, - LO_PROXY = 0x008000, -}; - -static struct listen_opts { - char *ifx; - int family; - in_port_t port; - uint16_t ssl; - char *filtername; - char *pki; - char *ca; - uint16_t auth; - struct table *authtable; - char *tag; - char *hostname; - struct table *hostnametable; - struct table *sendertable; - uint16_t flags; - - uint32_t options; -} listen_opts; - -static void create_sock_listener(struct listen_opts *); -static void create_if_listener(struct listen_opts *); -static void config_listener(struct listener *, struct listen_opts *); -static int host_v4(struct listen_opts *); -static int host_v6(struct listen_opts *); -static int host_dns(struct listen_opts *); -static int interface(struct listen_opts *); - -int delaytonum(char *); -int is_if_in_group(const char *, const char *); - -static int config_lo_mask_source(struct listen_opts *); - -typedef struct { - union { - int64_t number; - struct table *table; - char *string; - struct host *host; - struct mailaddr *maddr; - } v; - int lineno; -} YYSTYPE; - -%} - -%token ACTION ALIAS ANY ARROW AUTH AUTH_OPTIONAL -%token BACKUP BOUNCE BYPASS -%token CA CERT CHAIN CHROOT CIPHERS COMMIT COMPRESSION CONNECT -%token DATA DATA_LINE DHE DISCONNECT DOMAIN -%token EHLO ENABLE ENCRYPTION ERROR EXPAND_ONLY -%token FCRDNS FILTER FOR FORWARD_ONLY FROM -%token GROUP -%token HELO HELO_SRC HOST HOSTNAME HOSTNAMES -%token INCLUDE INET4 INET6 -%token JUNK -%token KEY -%token LIMIT LISTEN LMTP LOCAL -%token MAIL_FROM MAILDIR MASK_SRC MASQUERADE MATCH MAX_MESSAGE_SIZE MAX_DEFERRED MBOX MDA MTA MX -%token NO_DSN NO_VERIFY NOOP -%token ON -%token PHASE PKI PORT PROC PROC_EXEC PROXY_V2 -%token QUEUE QUIT -%token RCPT_TO RDNS RECIPIENT RECEIVEDAUTH REGEX RELAY REJECT REPORT REWRITE RSET -%token SCHEDULER SENDER SENDERS SMTP SMTP_IN SMTP_OUT SMTPS SOCKET SRC SRS SUB_ADDR_DELIM -%token TABLE TAG TAGGED TLS TLS_REQUIRE TTL -%token USER USERBASE -%token VERIFY VIRTUAL -%token WARN_INTERVAL WRAPPER - -%token STRING -%token NUMBER -%type table -%type size negation -%type tables tablenew tableref -%% - -grammar : /* empty */ - | grammar '\n' - | grammar include '\n' - | grammar varset '\n' - | grammar bounce '\n' - | grammar ca '\n' - | grammar mda '\n' - | grammar mta '\n' - | grammar pki '\n' - | grammar proc '\n' - | grammar queue '\n' - | grammar scheduler '\n' - | grammar smtp '\n' - | grammar srs '\n' - | grammar listen '\n' - | grammar table '\n' - | grammar dispatcher '\n' - | grammar match '\n' - | grammar filter '\n' - | grammar error '\n' { file->errors++; } - ; - -include : INCLUDE STRING { - struct file *nfile; - - if ((nfile = pushfile($2, 0)) == NULL) { - yyerror("failed to include file %s", $2); - free($2); - YYERROR; - } - free($2); - - file = nfile; - lungetc('\n'); - } - ; - -varset : STRING '=' STRING { - char *s = $1; - while (*s++) { - if (isspace((unsigned char)*s)) { - yyerror("macro name cannot contain " - "whitespace"); - free($1); - free($3); - YYERROR; - } - } - if (symset($1, $3, 0) == -1) - fatal("cannot store variable"); - free($1); - free($3); - } - ; - -comma : ',' - | nl - | /* empty */ - ; - -optnl : '\n' optnl - | - ; - -nl : '\n' optnl - ; - -negation : '!' { $$ = 1; } - | /* empty */ { $$ = 0; } - ; - -assign : '=' | ARROW; - - -keyval : STRING assign STRING { - table_add(table, $1, $3); - free($1); - free($3); - } - ; - -keyval_list : keyval - | keyval comma keyval_list - ; - -stringel : STRING { - table_add(table, $1, NULL); - free($1); - } - ; - -string_list : stringel - | stringel comma string_list - ; - -tableval_list : string_list { } - | keyval_list { } - ; - -bounce: -BOUNCE WARN_INTERVAL { - memset(conf->sc_bounce_warn, 0, sizeof conf->sc_bounce_warn); -} bouncedelays -; - - -ca: -CA STRING { - char buf[HOST_NAME_MAX+1]; - - /* if not catchall, check that it is a valid domain */ - if (strcmp($2, "*") != 0) { - if (!res_hnok($2)) { - yyerror("not a valid domain name: %s", $2); - free($2); - YYERROR; - } - } - xlowercase(buf, $2, sizeof(buf)); - free($2); - sca = dict_get(conf->sc_ca_dict, buf); - if (sca == NULL) { - sca = xcalloc(1, sizeof *sca); - (void)strlcpy(sca->ca_name, buf, sizeof(sca->ca_name)); - dict_set(conf->sc_ca_dict, sca->ca_name, sca); - } -} ca_params -; - - -ca_params_opt: -CERT STRING { - sca->ca_cert_file = $2; -} -; - -ca_params: -ca_params_opt -; - - -mda: -MDA LIMIT limits_mda -| MDA WRAPPER STRING STRING { - if (dict_get(conf->sc_mda_wrappers, $3)) { - yyerror("mda wrapper already declared with that name: %s", $3); - YYERROR; - } - dict_set(conf->sc_mda_wrappers, $3, $4); -} -; - - -mta: -MTA MAX_DEFERRED NUMBER { - conf->sc_mta_max_deferred = $3; -} -| MTA LIMIT FOR DOMAIN STRING { - struct mta_limits *d; - - limits = dict_get(conf->sc_limits_dict, $5); - if (limits == NULL) { - limits = xcalloc(1, sizeof(*limits)); - dict_xset(conf->sc_limits_dict, $5, limits); - d = dict_xget(conf->sc_limits_dict, "default"); - memmove(limits, d, sizeof(*limits)); - } - free($5); -} limits_mta -| MTA LIMIT { - limits = dict_get(conf->sc_limits_dict, "default"); -} limits_mta -; - - -pki: -PKI STRING { - char buf[HOST_NAME_MAX+1]; - - /* if not catchall, check that it is a valid domain */ - if (strcmp($2, "*") != 0) { - if (!res_hnok($2)) { - yyerror("not a valid domain name: %s", $2); - free($2); - YYERROR; - } - } - xlowercase(buf, $2, sizeof(buf)); - free($2); - pki = dict_get(conf->sc_pki_dict, buf); - if (pki == NULL) { - pki = xcalloc(1, sizeof *pki); - (void)strlcpy(pki->pki_name, buf, sizeof(pki->pki_name)); - dict_set(conf->sc_pki_dict, pki->pki_name, pki); - } -} pki_params -; - -pki_params_opt: -CERT STRING { - pki->pki_cert_file = $2; -} -| KEY STRING { - pki->pki_key_file = $2; -} -| DHE STRING { - if (strcasecmp($2, "none") == 0) - pki->pki_dhe = 0; - else if (strcasecmp($2, "auto") == 0) - pki->pki_dhe = 1; - else if (strcasecmp($2, "legacy") == 0) - pki->pki_dhe = 2; - else { - yyerror("invalid DHE keyword: %s", $2); - free($2); - YYERROR; - } - free($2); -} -; - - -pki_params: -pki_params_opt pki_params -| /* empty */ -; - - -proc: -PROC STRING STRING { - if (dict_get(conf->sc_filter_processes_dict, $2)) { - yyerror("processor already exists with that name: %s", $2); - free($2); - free($3); - YYERROR; - } - processor = xcalloc(1, sizeof *processor); - processor->command = $3; -} proc_params { - dict_set(conf->sc_filter_processes_dict, $2, processor); - processor = NULL; -} -; - - -proc_params_opt: -USER STRING { - if (processor->user) { - yyerror("user already specified for this processor"); - free($2); - YYERROR; - } - processor->user = $2; -} -| GROUP STRING { - if (processor->group) { - yyerror("group already specified for this processor"); - free($2); - YYERROR; - } - processor->group = $2; -} -| CHROOT STRING { - if (processor->chroot) { - yyerror("chroot already specified for this processor"); - free($2); - YYERROR; - } - processor->chroot = $2; -} -; - -proc_params: -proc_params_opt proc_params -| /* empty */ -; - - -queue: -QUEUE COMPRESSION { - conf->sc_queue_flags |= QUEUE_COMPRESSION; -} -| QUEUE ENCRYPTION { - conf->sc_queue_flags |= QUEUE_ENCRYPTION; -} -| QUEUE ENCRYPTION STRING { - if (strcasecmp($3, "stdin") == 0 || strcasecmp($3, "-") == 0) { - conf->sc_queue_key = "stdin"; - free($3); - } - else - conf->sc_queue_key = $3; - conf->sc_queue_flags |= QUEUE_ENCRYPTION; -} -| QUEUE TTL STRING { - conf->sc_ttl = delaytonum($3); - if (conf->sc_ttl == -1) { - yyerror("invalid ttl delay: %s", $3); - free($3); - YYERROR; - } - free($3); -} -; - - -scheduler: -SCHEDULER LIMIT limits_scheduler -; - - -smtp: -SMTP LIMIT limits_smtp -| SMTP CIPHERS STRING { - conf->sc_tls_ciphers = $3; -} -| SMTP MAX_MESSAGE_SIZE size { - conf->sc_maxsize = $3; -} -| SMTP SUB_ADDR_DELIM STRING { - if (strlen($3) != 1) { - yyerror("subaddressing-delimiter must be one character"); - free($3); - YYERROR; - } - if (isspace((unsigned char)*$3) || !isprint((unsigned char)*$3) || *$3 == '@') { - yyerror("sub-addr-delim uses invalid character"); - free($3); - YYERROR; - } - conf->sc_subaddressing_delim = $3; -} -; - -srs: -SRS KEY STRING { - conf->sc_srs_key = $3; -} -| SRS KEY BACKUP STRING { - conf->sc_srs_key_backup = $4; -} -| SRS TTL STRING { - conf->sc_srs_ttl = delaytonum($3); - if (conf->sc_srs_ttl == -1) { - yyerror("ttl delay \"%s\" is invalid", $3); - free($3); - YYERROR; - } - - conf->sc_srs_ttl /= 86400; - if (conf->sc_srs_ttl == 0) { - yyerror("ttl delay \"%s\" is too short", $3); - free($3); - YYERROR; - } - free($3); -} -; - - -dispatcher_local_option: -USER STRING { - if (dispatcher->u.local.is_mbox) { - yyerror("user may not be specified for this dispatcher"); - YYERROR; - } - - if (dispatcher->u.local.forward_only) { - yyerror("user may not be specified for forward-only"); - YYERROR; - } - - if (dispatcher->u.local.expand_only) { - yyerror("user may not be specified for expand-only"); - YYERROR; - } - - if (dispatcher->u.local.user) { - yyerror("user already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.local.user = $2; -} -| ALIAS tables { - struct table *t = $2; - - if (dispatcher->u.local.table_alias) { - yyerror("alias mapping already specified for this dispatcher"); - YYERROR; - } - - if (dispatcher->u.local.table_virtual) { - yyerror("virtual mapping already specified for this dispatcher"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ALIAS)) { - yyerror("table \"%s\" may not be used for alias lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.local.table_alias = strdup(t->t_name); -} -| VIRTUAL tables { - struct table *t = $2; - - if (dispatcher->u.local.table_virtual) { - yyerror("virtual mapping already specified for this dispatcher"); - YYERROR; - } - - if (dispatcher->u.local.table_alias) { - yyerror("alias mapping already specified for this dispatcher"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ALIAS)) { - yyerror("table \"%s\" may not be used for virtual lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.local.table_virtual = strdup(t->t_name); -} -| USERBASE tables { - struct table *t = $2; - - if (dispatcher->u.local.table_userbase) { - yyerror("userbase mapping already specified for this dispatcher"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_USERINFO)) { - yyerror("table \"%s\" may not be used for userbase lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.local.table_userbase = strdup(t->t_name); -} -| WRAPPER STRING { - if (! dict_get(conf->sc_mda_wrappers, $2)) { - yyerror("no mda wrapper with that name: %s", $2); - YYERROR; - } - dispatcher->u.local.mda_wrapper = $2; -} -; - -dispatcher_local_options: -dispatcher_local_option dispatcher_local_options -| /* empty */ -; - -dispatcher_local: -MBOX { - dispatcher->u.local.is_mbox = 1; - asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.local -f %%{mbox.from} -- %%{user.username}"); -} dispatcher_local_options -| MAILDIR { - asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.maildir"); -} dispatcher_local_options -| MAILDIR JUNK { - asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.maildir -j"); -} dispatcher_local_options -| MAILDIR STRING { - if (strncmp($2, "~/", 2) == 0) - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.maildir \"%%{user.directory}/%s\"", $2+2); - else - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.maildir \"%s\"", $2); -} dispatcher_local_options -| MAILDIR STRING JUNK { - if (strncmp($2, "~/", 2) == 0) - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.maildir -j \"%%{user.directory}/%s\"", $2+2); - else - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.maildir -j \"%s\"", $2); -} dispatcher_local_options -| LMTP STRING { - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.lmtp -d \"%s\" -u", $2); -} dispatcher_local_options -| LMTP STRING RCPT_TO { - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.lmtp -d \"%s\" -r", $2); -} dispatcher_local_options -| MDA STRING { - asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.mda \"%s\"", $2); -} dispatcher_local_options -| FORWARD_ONLY { - dispatcher->u.local.forward_only = 1; -} dispatcher_local_options -| EXPAND_ONLY { - dispatcher->u.local.expand_only = 1; -} dispatcher_local_options - -; - -dispatcher_remote_option: -HELO STRING { - if (dispatcher->u.remote.helo) { - yyerror("helo already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.helo = $2; -} -| HELO_SRC tables { - struct table *t = $2; - - if (dispatcher->u.remote.helo_source) { - yyerror("helo-source mapping already specified for this dispatcher"); - YYERROR; - } - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { - yyerror("table \"%s\" may not be used for helo-source lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.remote.helo_source = strdup(t->t_name); -} -| PKI STRING { - if (dispatcher->u.remote.pki) { - yyerror("pki already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.pki = $2; -} -| CA STRING { - if (dispatcher->u.remote.ca) { - yyerror("ca already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.ca = $2; -} -| SRC tables { - struct table *t = $2; - - if (dispatcher->u.remote.source) { - yyerror("source mapping already specified for this dispatcher"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_SOURCE)) { - yyerror("table \"%s\" may not be used for source lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.remote.source = strdup(t->t_name); -} -| MAIL_FROM STRING { - if (dispatcher->u.remote.mail_from) { - yyerror("mail-from already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.mail_from = $2; -} -| BACKUP MX STRING { - if (dispatcher->u.remote.backup) { - yyerror("backup already specified for this dispatcher"); - YYERROR; - } - if (dispatcher->u.remote.smarthost) { - yyerror("backup and host are mutually exclusive"); - YYERROR; - } - - dispatcher->u.remote.backup = 1; - dispatcher->u.remote.backupmx = $3; -} -| BACKUP { - if (dispatcher->u.remote.backup) { - yyerror("backup already specified for this dispatcher"); - YYERROR; - } - if (dispatcher->u.remote.smarthost) { - yyerror("backup and host are mutually exclusive"); - YYERROR; - } - - dispatcher->u.remote.backup = 1; -} -| HOST tables { - struct table *t = $2; - - if (dispatcher->u.remote.smarthost) { - yyerror("host mapping already specified for this dispatcher"); - YYERROR; - } - if (dispatcher->u.remote.backup) { - yyerror("backup and host are mutually exclusive"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_RELAYHOST)) { - yyerror("table \"%s\" may not be used for host lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.remote.smarthost = strdup(t->t_name); -} -| DOMAIN tables { - struct table *t = $2; - - if (dispatcher->u.remote.smarthost) { - yyerror("host mapping already specified for this dispatcher"); - YYERROR; - } - if (dispatcher->u.remote.backup) { - yyerror("backup and domain are mutually exclusive"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_RELAYHOST)) { - yyerror("table \"%s\" may not be used for host lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.remote.smarthost = strdup(t->t_name); - dispatcher->u.remote.smarthost_domain = 1; -} -| TLS { - if (dispatcher->u.remote.tls_required == 1) { - yyerror("tls already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.tls_required = 1; -} -| TLS NO_VERIFY { - if (dispatcher->u.remote.tls_required == 1) { - yyerror("tls already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.tls_required = 1; - dispatcher->u.remote.tls_noverify = 1; -} -| AUTH tables { - struct table *t = $2; - - if (dispatcher->u.remote.smarthost == NULL) { - yyerror("auth may not be specified without host on a dispatcher"); - YYERROR; - } - - if (dispatcher->u.remote.auth) { - yyerror("auth mapping already specified for this dispatcher"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_CREDENTIALS)) { - yyerror("table \"%s\" may not be used for auth lookups", - t->t_name); - YYERROR; - } - - dispatcher->u.remote.auth = strdup(t->t_name); -} -| FILTER STRING { - struct filter_config *fc; - - if (dispatcher->u.remote.filtername) { - yyerror("filter already specified for this dispatcher"); - YYERROR; - } - - if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { - yyerror("no filter exist with that name: %s", $2); - free($2); - YYERROR; - } - fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_OUT; - dispatcher->u.remote.filtername = $2; -} -| FILTER { - char buffer[128]; - char *filtername; - - if (dispatcher->u.remote.filtername) { - yyerror("filter already specified for this dispatcher"); - YYERROR; - } - - do { - (void)snprintf(buffer, sizeof buffer, "", last_dynchain_id++); - } while (dict_check(conf->sc_filters_dict, buffer)); - - filtername = xstrdup(buffer); - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->filter_type = FILTER_TYPE_CHAIN; - filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_OUT; - dict_init(&filter_config->chain_procs); - dispatcher->u.remote.filtername = filtername; -} '{' filter_list '}' { - dict_set(conf->sc_filters_dict, dispatcher->u.remote.filtername, filter_config); - filter_config = NULL; -} -| SRS { - if (conf->sc_srs_key == NULL) { - yyerror("an srs key is required for srs to be specified in an action"); - YYERROR; - } - if (dispatcher->u.remote.srs == 1) { - yyerror("srs already specified for this dispatcher"); - YYERROR; - } - - dispatcher->u.remote.srs = 1; -} -; - -dispatcher_remote_options: -dispatcher_remote_option dispatcher_remote_options -| /* empty */ -; - -dispatcher_remote : -RELAY dispatcher_remote_options -; - -dispatcher_type: -dispatcher_local { - dispatcher->type = DISPATCHER_LOCAL; -} -| dispatcher_remote { - dispatcher->type = DISPATCHER_REMOTE; -} -; - -dispatcher_option: -TTL STRING { - if (dispatcher->ttl) { - yyerror("ttl already specified for this dispatcher"); - YYERROR; - } - - dispatcher->ttl = delaytonum($2); - if (dispatcher->ttl == -1) { - yyerror("ttl delay \"%s\" is invalid", $2); - free($2); - YYERROR; - } - free($2); -} -; - -dispatcher_options: -dispatcher_option dispatcher_options -| /* empty */ -; - -dispatcher: -ACTION STRING { - if (dict_get(conf->sc_dispatchers, $2)) { - yyerror("dispatcher already declared with that name: %s", $2); - YYERROR; - } - dispatcher = xcalloc(1, sizeof *dispatcher); -} dispatcher_type dispatcher_options { - if (dispatcher->type == DISPATCHER_LOCAL) - if (dispatcher->u.local.table_userbase == NULL) - dispatcher->u.local.table_userbase = ""; - dict_set(conf->sc_dispatchers, $2, dispatcher); - dispatcher = NULL; -} -; - -match_option: -negation TAG tables { - struct table *t = $3; - - if (rule->flag_tag) { - yyerror("tag already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING)) { - yyerror("table \"%s\" may not be used for tag lookups", - t->t_name); - YYERROR; - } - - rule->flag_tag = $1 ? -1 : 1; - rule->table_tag = strdup(t->t_name); -} -| -negation TAG REGEX tables { - struct table *t = $4; - - if (rule->flag_tag) { - yyerror("tag already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for tag lookups", - t->t_name); - YYERROR; - } - - rule->flag_tag = $1 ? -1 : 1; - rule->flag_tag_regex = 1; - rule->table_tag = strdup(t->t_name); -} - -| negation HELO tables { - struct table *t = $3; - - if (rule->flag_smtp_helo) { - yyerror("helo already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { - yyerror("table \"%s\" may not be used for helo lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_helo = $1 ? -1 : 1; - rule->table_smtp_helo = strdup(t->t_name); -} -| negation HELO REGEX tables { - struct table *t = $4; - - if (rule->flag_smtp_helo) { - yyerror("helo already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for helo lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_helo = $1 ? -1 : 1; - rule->flag_smtp_helo_regex = 1; - rule->table_smtp_helo = strdup(t->t_name); -} -| negation TLS { - if (rule->flag_smtp_starttls) { - yyerror("tls already specified for this rule"); - YYERROR; - } - rule->flag_smtp_starttls = $1 ? -1 : 1; -} -| negation AUTH { - if (rule->flag_smtp_auth) { - yyerror("auth already specified for this rule"); - YYERROR; - } - rule->flag_smtp_auth = $1 ? -1 : 1; -} -| negation AUTH tables { - struct table *t = $3; - - if (rule->flag_smtp_auth) { - yyerror("auth already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING|K_CREDENTIALS)) { - yyerror("table \"%s\" may not be used for auth lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_auth = $1 ? -1 : 1; - rule->table_smtp_auth = strdup(t->t_name); -} -| negation AUTH REGEX tables { - struct table *t = $4; - - if (rule->flag_smtp_auth) { - yyerror("auth already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for auth lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_auth = $1 ? -1 : 1; - rule->flag_smtp_auth_regex = 1; - rule->table_smtp_auth = strdup(t->t_name); -} -| negation MAIL_FROM tables { - struct table *t = $3; - - if (rule->flag_smtp_mail_from) { - yyerror("mail-from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { - yyerror("table \"%s\" may not be used for mail-from lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_mail_from = $1 ? -1 : 1; - rule->table_smtp_mail_from = strdup(t->t_name); -} -| negation MAIL_FROM REGEX tables { - struct table *t = $4; - - if (rule->flag_smtp_mail_from) { - yyerror("mail-from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for mail-from lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_mail_from = $1 ? -1 : 1; - rule->flag_smtp_mail_from_regex = 1; - rule->table_smtp_mail_from = strdup(t->t_name); -} -| negation RCPT_TO tables { - struct table *t = $3; - - if (rule->flag_smtp_rcpt_to) { - yyerror("rcpt-to already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { - yyerror("table \"%s\" may not be used for rcpt-to lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_rcpt_to = $1 ? -1 : 1; - rule->table_smtp_rcpt_to = strdup(t->t_name); -} -| negation RCPT_TO REGEX tables { - struct table *t = $4; - - if (rule->flag_smtp_rcpt_to) { - yyerror("rcpt-to already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for rcpt-to lookups", - t->t_name); - YYERROR; - } - - rule->flag_smtp_rcpt_to = $1 ? -1 : 1; - rule->flag_smtp_rcpt_to_regex = 1; - rule->table_smtp_rcpt_to = strdup(t->t_name); -} - -| negation FROM SOCKET { - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - rule->flag_from = $1 ? -1 : 1; - rule->flag_from_socket = 1; -} -| negation FROM LOCAL { - struct table *t = table_find(conf, ""); - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - rule->flag_from = $1 ? -1 : 1; - rule->table_from = strdup(t->t_name); -} -| negation FROM ANY { - struct table *t = table_find(conf, ""); - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - rule->flag_from = $1 ? -1 : 1; - rule->table_from = strdup(t->t_name); -} -| negation FROM SRC tables { - struct table *t = $4; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_NETADDR)) { - yyerror("table \"%s\" may not be used for from lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = $1 ? -1 : 1; - rule->table_from = strdup(t->t_name); -} -| negation FROM SRC REGEX tables { - struct table *t = $5; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for from lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = $1 ? -1 : 1; - rule->flag_from_regex = 1; - rule->table_from = strdup(t->t_name); -} -| negation FROM RDNS { - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - rule->flag_from = $1 ? -1 : 1; - rule->flag_from_rdns = 1; -} -| negation FROM RDNS tables { - struct table *t = $4; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { - yyerror("table \"%s\" may not be used for rdns lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = $1 ? -1 : 1; - rule->flag_from_rdns = 1; - rule->table_from = strdup(t->t_name); -} -| negation FROM RDNS REGEX tables { - struct table *t = $5; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { - yyerror("table \"%s\" may not be used for rdns lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = $1 ? -1 : 1; - rule->flag_from_regex = 1; - rule->flag_from_rdns = 1; - rule->table_from = strdup(t->t_name); -} - -| negation FROM AUTH { - struct table *anyhost = table_find(conf, ""); - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - rule->flag_from = 1; - rule->table_from = strdup(anyhost->t_name); - rule->flag_smtp_auth = $1 ? -1 : 1; -} -| negation FROM AUTH tables { - struct table *anyhost = table_find(conf, ""); - struct table *t = $4; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING|K_CREDENTIALS)) { - yyerror("table \"%s\" may not be used for from lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = 1; - rule->table_from = strdup(anyhost->t_name); - rule->flag_smtp_auth = $1 ? -1 : 1; - rule->table_smtp_auth = strdup(t->t_name); -} -| negation FROM AUTH REGEX tables { - struct table *anyhost = table_find(conf, ""); - struct table *t = $5; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for from lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = 1; - rule->table_from = strdup(anyhost->t_name); - rule->flag_smtp_auth = $1 ? -1 : 1; - rule->flag_smtp_auth_regex = 1; - rule->table_smtp_auth = strdup(t->t_name); -} - -| negation FROM MAIL_FROM tables { - struct table *anyhost = table_find(conf, ""); - struct table *t = $4; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { - yyerror("table \"%s\" may not be used for from lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = 1; - rule->table_from = strdup(anyhost->t_name); - rule->flag_smtp_mail_from = $1 ? -1 : 1; - rule->table_smtp_mail_from = strdup(t->t_name); -} -| negation FROM MAIL_FROM REGEX tables { - struct table *anyhost = table_find(conf, ""); - struct table *t = $5; - - if (rule->flag_from) { - yyerror("from already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for from lookups", - t->t_name); - YYERROR; - } - - rule->flag_from = 1; - rule->table_from = strdup(anyhost->t_name); - rule->flag_smtp_mail_from = $1 ? -1 : 1; - rule->flag_smtp_mail_from_regex = 1; - rule->table_smtp_mail_from = strdup(t->t_name); -} - -| negation FOR LOCAL { - struct table *t = table_find(conf, ""); - - if (rule->flag_for) { - yyerror("for already specified for this rule"); - YYERROR; - } - rule->flag_for = $1 ? -1 : 1; - rule->table_for = strdup(t->t_name); -} -| negation FOR ANY { - struct table *t = table_find(conf, ""); - - if (rule->flag_for) { - yyerror("for already specified for this rule"); - YYERROR; - } - rule->flag_for = $1 ? -1 : 1; - rule->table_for = strdup(t->t_name); -} -| negation FOR DOMAIN tables { - struct table *t = $4; - - if (rule->flag_for) { - yyerror("for already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { - yyerror("table \"%s\" may not be used for 'for' lookups", - t->t_name); - YYERROR; - } - - rule->flag_for = $1 ? -1 : 1; - rule->table_for = strdup(t->t_name); -} -| negation FOR DOMAIN REGEX tables { - struct table *t = $5; - - if (rule->flag_for) { - yyerror("for already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for 'for' lookups", - t->t_name); - YYERROR; - } - - rule->flag_for = $1 ? -1 : 1; - rule->flag_for_regex = 1; - rule->table_for = strdup(t->t_name); -} -| negation FOR RCPT_TO tables { - struct table *anyhost = table_find(conf, ""); - struct table *t = $4; - - if (rule->flag_for) { - yyerror("for already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { - yyerror("table \"%s\" may not be used for for lookups", - t->t_name); - YYERROR; - } - - rule->flag_for = 1; - rule->table_for = strdup(anyhost->t_name); - rule->flag_smtp_rcpt_to = $1 ? -1 : 1; - rule->table_smtp_rcpt_to = strdup(t->t_name); -} -| negation FOR RCPT_TO REGEX tables { - struct table *anyhost = table_find(conf, ""); - struct table *t = $5; - - if (rule->flag_for) { - yyerror("for already specified for this rule"); - YYERROR; - } - - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { - yyerror("table \"%s\" may not be used for for lookups", - t->t_name); - YYERROR; - } - - rule->flag_for = 1; - rule->table_for = strdup(anyhost->t_name); - rule->flag_smtp_rcpt_to = $1 ? -1 : 1; - rule->flag_smtp_rcpt_to_regex = 1; - rule->table_smtp_rcpt_to = strdup(t->t_name); -} -; - -match_options: -match_option match_options -| /* empty */ -; - -match_dispatcher: -STRING { - if (dict_get(conf->sc_dispatchers, $1) == NULL) { - yyerror("no such dispatcher: %s", $1); - YYERROR; - } - rule->dispatcher = $1; -} -; - -action: -REJECT { - rule->reject = 1; -} -| ACTION match_dispatcher -; - -match: -MATCH { - rule = xcalloc(1, sizeof *rule); -} match_options action { - if (!rule->flag_from) { - rule->table_from = strdup(""); - rule->flag_from = 1; - } - if (!rule->flag_for) { - rule->table_for = strdup(""); - rule->flag_for = 1; - } - TAILQ_INSERT_TAIL(conf->sc_rules, rule, r_entry); - rule = NULL; -} -; - -filter_action_builtin: -filter_action_builtin_nojunk -| JUNK { - filter_config->junk = 1; -} -| BYPASS { - filter_config->bypass = 1; -} -; - -filter_action_builtin_nojunk: -REJECT STRING { - filter_config->reject = $2; -} -| DISCONNECT STRING { - filter_config->disconnect = $2; -} -| REWRITE STRING { - filter_config->rewrite = $2; -} -| REPORT STRING { - filter_config->report = $2; -} -; - -filter_phase_check_fcrdns: -negation FCRDNS { - filter_config->not_fcrdns = $1 ? -1 : 1; - filter_config->fcrdns = 1; -} -; - -filter_phase_check_rdns: -negation RDNS { - filter_config->not_rdns = $1 ? -1 : 1; - filter_config->rdns = 1; -} -; - -filter_phase_check_rdns_table: -negation RDNS tables { - filter_config->not_rdns_table = $1 ? -1 : 1; - filter_config->rdns_table = $3; -} -; -filter_phase_check_rdns_regex: -negation RDNS REGEX tables { - filter_config->not_rdns_regex = $1 ? -1 : 1; - filter_config->rdns_regex = $4; -} -; - -filter_phase_check_src_table: -negation SRC tables { - filter_config->not_src_table = $1 ? -1 : 1; - filter_config->src_table = $3; -} -; -filter_phase_check_src_regex: -negation SRC REGEX tables { - filter_config->not_src_regex = $1 ? -1 : 1; - filter_config->src_regex = $4; -} -; - -filter_phase_check_helo_table: -negation HELO tables { - filter_config->not_helo_table = $1 ? -1 : 1; - filter_config->helo_table = $3; -} -; -filter_phase_check_helo_regex: -negation HELO REGEX tables { - filter_config->not_helo_regex = $1 ? -1 : 1; - filter_config->helo_regex = $4; -} -; - -filter_phase_check_auth: -negation AUTH { - filter_config->not_auth = $1 ? -1 : 1; - filter_config->auth = 1; -} -; -filter_phase_check_auth_table: -negation AUTH tables { - filter_config->not_auth_table = $1 ? -1 : 1; - filter_config->auth_table = $3; -} -; -filter_phase_check_auth_regex: -negation AUTH REGEX tables { - filter_config->not_auth_regex = $1 ? -1 : 1; - filter_config->auth_regex = $4; -} -; - -filter_phase_check_mail_from_table: -negation MAIL_FROM tables { - filter_config->not_mail_from_table = $1 ? -1 : 1; - filter_config->mail_from_table = $3; -} -; -filter_phase_check_mail_from_regex: -negation MAIL_FROM REGEX tables { - filter_config->not_mail_from_regex = $1 ? -1 : 1; - filter_config->mail_from_regex = $4; -} -; - -filter_phase_check_rcpt_to_table: -negation RCPT_TO tables { - filter_config->not_rcpt_to_table = $1 ? -1 : 1; - filter_config->rcpt_to_table = $3; -} -; -filter_phase_check_rcpt_to_regex: -negation RCPT_TO REGEX tables { - filter_config->not_rcpt_to_regex = $1 ? -1 : 1; - filter_config->rcpt_to_regex = $4; -} -; - -filter_phase_global_options: -filter_phase_check_fcrdns | -filter_phase_check_rdns | -filter_phase_check_rdns_regex | -filter_phase_check_rdns_table | -filter_phase_check_src_regex | -filter_phase_check_src_table; - -filter_phase_connect_options: -filter_phase_global_options; - -filter_phase_helo_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_global_options; - -filter_phase_auth_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_check_auth | -filter_phase_check_auth_table | -filter_phase_check_auth_regex | -filter_phase_global_options; - -filter_phase_mail_from_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_check_auth | -filter_phase_check_auth_table | -filter_phase_check_auth_regex | -filter_phase_check_mail_from_table | -filter_phase_check_mail_from_regex | -filter_phase_global_options; - -filter_phase_rcpt_to_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_check_auth | -filter_phase_check_auth_table | -filter_phase_check_auth_regex | -filter_phase_check_mail_from_table | -filter_phase_check_mail_from_regex | -filter_phase_check_rcpt_to_table | -filter_phase_check_rcpt_to_regex | -filter_phase_global_options; - -filter_phase_data_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_check_auth | -filter_phase_check_auth_table | -filter_phase_check_auth_regex | -filter_phase_check_mail_from_table | -filter_phase_check_mail_from_regex | -filter_phase_global_options; - -/* -filter_phase_quit_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_global_options; - -filter_phase_rset_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_global_options; - -filter_phase_noop_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_global_options; -*/ - -filter_phase_commit_options: -filter_phase_check_helo_table | -filter_phase_check_helo_regex | -filter_phase_check_auth | -filter_phase_check_auth_table | -filter_phase_check_auth_regex | -filter_phase_check_mail_from_table | -filter_phase_check_mail_from_regex | -filter_phase_global_options; - - -filter_phase_connect: -CONNECT { - filter_config->phase = FILTER_CONNECT; -} MATCH filter_phase_connect_options filter_action_builtin -; - - -filter_phase_helo: -HELO { - filter_config->phase = FILTER_HELO; -} MATCH filter_phase_helo_options filter_action_builtin -; - -filter_phase_ehlo: -EHLO { - filter_config->phase = FILTER_EHLO; -} MATCH filter_phase_helo_options filter_action_builtin -; - -filter_phase_auth: -AUTH { -} MATCH filter_phase_auth_options filter_action_builtin -; - -filter_phase_mail_from: -MAIL_FROM { - filter_config->phase = FILTER_MAIL_FROM; -} MATCH filter_phase_mail_from_options filter_action_builtin -; - -filter_phase_rcpt_to: -RCPT_TO { - filter_config->phase = FILTER_RCPT_TO; -} MATCH filter_phase_rcpt_to_options filter_action_builtin -; - -filter_phase_data: -DATA { - filter_config->phase = FILTER_DATA; -} MATCH filter_phase_data_options filter_action_builtin -; - -/* -filter_phase_data_line: -DATA_LINE { - filter_config->phase = FILTER_DATA_LINE; -} MATCH filter_action_builtin -; - -filter_phase_quit: -QUIT { - filter_config->phase = FILTER_QUIT; -} filter_phase_quit_options filter_action_builtin -; - -filter_phase_rset: -RSET { - filter_config->phase = FILTER_RSET; -} MATCH filter_phase_rset_options filter_action_builtin -; - -filter_phase_noop: -NOOP { - filter_config->phase = FILTER_NOOP; -} MATCH filter_phase_noop_options filter_action_builtin -; -*/ - -filter_phase_commit: -COMMIT { - filter_config->phase = FILTER_COMMIT; -} MATCH filter_phase_commit_options filter_action_builtin_nojunk -; - - - -filter_phase: -filter_phase_connect -| filter_phase_helo -| filter_phase_ehlo -| filter_phase_auth -| filter_phase_mail_from -| filter_phase_rcpt_to -| filter_phase_data -/*| filter_phase_data_line*/ -/*| filter_phase_quit*/ -/*| filter_phase_noop*/ -/*| filter_phase_rset*/ -| filter_phase_commit -; - - -filterel: -STRING { - struct filter_config *fr; - struct filter_proc *fp; - size_t i; - - if ((fr = dict_get(conf->sc_filters_dict, $1)) == NULL) { - yyerror("no filter exist with that name: %s", $1); - free($1); - YYERROR; - } - if (fr->filter_type == FILTER_TYPE_CHAIN) { - yyerror("no filter chain allowed within a filter chain: %s", $1); - free($1); - YYERROR; - } - - for (i = 0; i < filter_config->chain_size; i++) { - if (strcmp(filter_config->chain[i], $1) == 0) { - yyerror("no filter allowed twice within a filter chain: %s", $1); - free($1); - YYERROR; - } - } - - if (fr->proc) { - if ((fp = dict_get(&filter_config->chain_procs, fr->proc))) { - yyerror("no proc allowed twice within a filter chain: %s", fr->proc); - free($1); - YYERROR; - } - dict_set(&filter_config->chain_procs, fr->proc, NULL); - } - - fr->filter_subsystem |= filter_config->filter_subsystem; - filter_config->chain_size += 1; - filter_config->chain = reallocarray(filter_config->chain, filter_config->chain_size, sizeof(char *)); - if (filter_config->chain == NULL) - err(1, NULL); - filter_config->chain[filter_config->chain_size - 1] = $1; -} -; - -filter_list: -filterel -| filterel comma filter_list -; - -filter: -FILTER STRING PROC STRING { - struct filter_proc *fp; - - if (dict_get(conf->sc_filters_dict, $2)) { - yyerror("filter already exists with that name: %s", $2); - free($2); - free($4); - YYERROR; - } - if ((fp = dict_get(conf->sc_filter_processes_dict, $4)) == NULL) { - yyerror("no processor exist with that name: %s", $4); - free($4); - YYERROR; - } - - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->filter_type = FILTER_TYPE_PROC; - filter_config->name = $2; - filter_config->proc = $4; - dict_set(conf->sc_filters_dict, $2, filter_config); - filter_config = NULL; -} -| -FILTER STRING PROC_EXEC STRING { - if (dict_get(conf->sc_filters_dict, $2)) { - yyerror("filter already exists with that name: %s", $2); - free($2); - free($4); - YYERROR; - } - - processor = xcalloc(1, sizeof *processor); - processor->command = $4; - - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->filter_type = FILTER_TYPE_PROC; - filter_config->name = $2; - filter_config->proc = xstrdup($2); - dict_set(conf->sc_filters_dict, $2, filter_config); -} proc_params { - dict_set(conf->sc_filter_processes_dict, filter_config->proc, processor); - processor = NULL; - filter_config = NULL; -} -| -FILTER STRING PHASE { - if (dict_get(conf->sc_filters_dict, $2)) { - yyerror("filter already exists with that name: %s", $2); - free($2); - YYERROR; - } - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->name = $2; - filter_config->filter_type = FILTER_TYPE_BUILTIN; - dict_set(conf->sc_filters_dict, $2, filter_config); -} filter_phase { - filter_config = NULL; -} -| -FILTER STRING CHAIN { - if (dict_get(conf->sc_filters_dict, $2)) { - yyerror("filter already exists with that name: %s", $2); - free($2); - YYERROR; - } - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->filter_type = FILTER_TYPE_CHAIN; - dict_init(&filter_config->chain_procs); -} '{' filter_list '}' { - dict_set(conf->sc_filters_dict, $2, filter_config); - filter_config = NULL; -} -; - -size : NUMBER { - if ($1 < 0) { - yyerror("invalid size: %" PRId64, $1); - YYERROR; - } - $$ = $1; - } - | STRING { - long long result; - - if (scan_scaled($1, &result) == -1 || result < 0) { - yyerror("invalid size: %s", $1); - free($1); - YYERROR; - } - free($1); - $$ = result; - } - ; - -bouncedelay : STRING { - time_t d; - int i; - - d = delaytonum($1); - if (d < 0) { - yyerror("invalid bounce delay: %s", $1); - free($1); - YYERROR; - } - free($1); - for (i = 0; i < MAX_BOUNCE_WARN; i++) { - if (conf->sc_bounce_warn[i] != 0) - continue; - conf->sc_bounce_warn[i] = d; - break; - } - } - ; - -bouncedelays : bouncedelays ',' bouncedelay - | bouncedelay - ; - -opt_limit_mda : STRING NUMBER { - if (!strcmp($1, "max-session")) { - conf->sc_mda_max_session = $2; - } - else if (!strcmp($1, "max-session-per-user")) { - conf->sc_mda_max_user_session = $2; - } - else if (!strcmp($1, "task-lowat")) { - conf->sc_mda_task_lowat = $2; - } - else if (!strcmp($1, "task-hiwat")) { - conf->sc_mda_task_hiwat = $2; - } - else if (!strcmp($1, "task-release")) { - conf->sc_mda_task_release = $2; - } - else { - yyerror("invalid scheduler limit keyword: %s", $1); - free($1); - YYERROR; - } - free($1); - } - ; - -limits_smtp : opt_limit_smtp limits_smtp - | /* empty */ - ; - -opt_limit_smtp : STRING NUMBER { - if (!strcmp($1, "max-rcpt")) { - conf->sc_session_max_rcpt = $2; - } - else if (!strcmp($1, "max-mails")) { - conf->sc_session_max_mails = $2; - } - else { - yyerror("invalid session limit keyword: %s", $1); - free($1); - YYERROR; - } - free($1); - } - ; - -limits_mda : opt_limit_mda limits_mda - | /* empty */ - ; - -opt_limit_mta : INET4 { - limits->family = AF_INET; - } - | INET6 { - limits->family = AF_INET6; - } - | STRING NUMBER { - if (!limit_mta_set(limits, $1, $2)) { - yyerror("invalid mta limit keyword: %s", $1); - free($1); - YYERROR; - } - free($1); - } - ; - -limits_mta : opt_limit_mta limits_mta - | /* empty */ - ; - -opt_limit_scheduler : STRING NUMBER { - if (!strcmp($1, "max-inflight")) { - conf->sc_scheduler_max_inflight = $2; - } - else if (!strcmp($1, "max-evp-batch-size")) { - conf->sc_scheduler_max_evp_batch_size = $2; - } - else if (!strcmp($1, "max-msg-batch-size")) { - conf->sc_scheduler_max_msg_batch_size = $2; - } - else if (!strcmp($1, "max-schedule")) { - conf->sc_scheduler_max_schedule = $2; - } - else { - yyerror("invalid scheduler limit keyword: %s", $1); - free($1); - YYERROR; - } - free($1); - } - ; - -limits_scheduler: opt_limit_scheduler limits_scheduler - | /* empty */ - ; - - -opt_sock_listen : FILTER STRING { - struct filter_config *fc; - - if (listen_opts.options & LO_FILTER) { - yyerror("filter already specified"); - free($2); - YYERROR; - } - if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { - yyerror("no filter exist with that name: %s", $2); - free($2); - YYERROR; - } - fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; - listen_opts.options |= LO_FILTER; - listen_opts.filtername = $2; - } - | FILTER { - char buffer[128]; - - if (listen_opts.options & LO_FILTER) { - yyerror("filter already specified"); - YYERROR; - } - - do { - (void)snprintf(buffer, sizeof buffer, "", last_dynchain_id++); - } while (dict_check(conf->sc_filters_dict, buffer)); - - listen_opts.options |= LO_FILTER; - listen_opts.filtername = xstrdup(buffer); - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->filter_type = FILTER_TYPE_CHAIN; - filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; - dict_init(&filter_config->chain_procs); - } '{' filter_list '}' { - dict_set(conf->sc_filters_dict, listen_opts.filtername, filter_config); - filter_config = NULL; - } - | MASK_SRC { - if (config_lo_mask_source(&listen_opts)) { - YYERROR; - } - } - | TAG STRING { - if (listen_opts.options & LO_TAG) { - yyerror("tag already specified"); - YYERROR; - } - listen_opts.options |= LO_TAG; - - if (strlen($2) >= SMTPD_TAG_SIZE) { - yyerror("tag name too long"); - free($2); - YYERROR; - } - listen_opts.tag = $2; - } - ; - -opt_if_listen : INET4 { - if (listen_opts.options & LO_FAMILY) { - yyerror("address family already specified"); - YYERROR; - } - listen_opts.options |= LO_FAMILY; - listen_opts.family = AF_INET; - } - | INET6 { - if (listen_opts.options & LO_FAMILY) { - yyerror("address family already specified"); - YYERROR; - } - listen_opts.options |= LO_FAMILY; - listen_opts.family = AF_INET6; - } - | PORT STRING { - struct servent *servent; - - if (listen_opts.options & LO_PORT) { - yyerror("port already specified"); - YYERROR; - } - listen_opts.options |= LO_PORT; - - servent = getservbyname($2, "tcp"); - if (servent == NULL) { - yyerror("invalid port: %s", $2); - free($2); - YYERROR; - } - free($2); - listen_opts.port = ntohs(servent->s_port); - } - | PORT SMTP { - struct servent *servent; - - if (listen_opts.options & LO_PORT) { - yyerror("port already specified"); - YYERROR; - } - listen_opts.options |= LO_PORT; - - servent = getservbyname("smtp", "tcp"); - if (servent == NULL) { - yyerror("invalid port: smtp"); - YYERROR; - } - listen_opts.port = ntohs(servent->s_port); - } - | PORT SMTPS { - struct servent *servent; - - if (listen_opts.options & LO_PORT) { - yyerror("port already specified"); - YYERROR; - } - listen_opts.options |= LO_PORT; - - servent = getservbyname("smtps", "tcp"); - if (servent == NULL) { - yyerror("invalid port: smtps"); - YYERROR; - } - listen_opts.port = ntohs(servent->s_port); - } - | PORT NUMBER { - if (listen_opts.options & LO_PORT) { - yyerror("port already specified"); - YYERROR; - } - listen_opts.options |= LO_PORT; - - if ($2 <= 0 || $2 > (int)USHRT_MAX) { - yyerror("invalid port: %" PRId64, $2); - YYERROR; - } - listen_opts.port = $2; - } - | FILTER STRING { - struct filter_config *fc; - - if (listen_opts.options & LO_FILTER) { - yyerror("filter already specified"); - YYERROR; - } - if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { - yyerror("no filter exist with that name: %s", $2); - free($2); - YYERROR; - } - fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; - listen_opts.options |= LO_FILTER; - listen_opts.filtername = $2; - } - | FILTER { - char buffer[128]; - - if (listen_opts.options & LO_FILTER) { - yyerror("filter already specified"); - YYERROR; - } - - do { - (void)snprintf(buffer, sizeof buffer, "", last_dynchain_id++); - } while (dict_check(conf->sc_filters_dict, buffer)); - - listen_opts.options |= LO_FILTER; - listen_opts.filtername = xstrdup(buffer); - filter_config = xcalloc(1, sizeof *filter_config); - filter_config->filter_type = FILTER_TYPE_CHAIN; - filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; - dict_init(&filter_config->chain_procs); - } '{' filter_list '}' { - dict_set(conf->sc_filters_dict, listen_opts.filtername, filter_config); - filter_config = NULL; - } - | SMTPS { - if (listen_opts.options & LO_SSL) { - yyerror("TLS mode already specified"); - YYERROR; - } - listen_opts.options |= LO_SSL; - listen_opts.ssl = F_SMTPS; - } - | SMTPS VERIFY { - if (listen_opts.options & LO_SSL) { - yyerror("TLS mode already specified"); - YYERROR; - } - listen_opts.options |= LO_SSL; - listen_opts.ssl = F_SMTPS|F_TLS_VERIFY; - } - | TLS { - if (listen_opts.options & LO_SSL) { - yyerror("TLS mode already specified"); - YYERROR; - } - listen_opts.options |= LO_SSL; - listen_opts.ssl = F_STARTTLS; - } - | TLS_REQUIRE { - if (listen_opts.options & LO_SSL) { - yyerror("TLS mode already specified"); - YYERROR; - } - listen_opts.options |= LO_SSL; - listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE; - } - | TLS_REQUIRE VERIFY { - if (listen_opts.options & LO_SSL) { - yyerror("TLS mode already specified"); - YYERROR; - } - listen_opts.options |= LO_SSL; - listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE|F_TLS_VERIFY; - } - | PKI STRING { - if (listen_opts.options & LO_PKI) { - yyerror("pki already specified"); - YYERROR; - } - listen_opts.options |= LO_PKI; - listen_opts.pki = $2; - } - | CA STRING { - if (listen_opts.options & LO_CA) { - yyerror("ca already specified"); - YYERROR; - } - listen_opts.options |= LO_CA; - listen_opts.ca = $2; - } - | AUTH { - if (listen_opts.options & LO_AUTH) { - yyerror("auth already specified"); - YYERROR; - } - listen_opts.options |= LO_AUTH; - listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; - } - | AUTH_OPTIONAL { - if (listen_opts.options & LO_AUTH) { - yyerror("auth already specified"); - YYERROR; - } - listen_opts.options |= LO_AUTH; - listen_opts.auth = F_AUTH; - } - | AUTH tables { - if (listen_opts.options & LO_AUTH) { - yyerror("auth already specified"); - YYERROR; - } - listen_opts.options |= LO_AUTH; - listen_opts.authtable = $2; - listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; - } - | AUTH_OPTIONAL tables { - if (listen_opts.options & LO_AUTH) { - yyerror("auth already specified"); - YYERROR; - } - listen_opts.options |= LO_AUTH; - listen_opts.authtable = $2; - listen_opts.auth = F_AUTH; - } - | TAG STRING { - if (listen_opts.options & LO_TAG) { - yyerror("tag already specified"); - YYERROR; - } - listen_opts.options |= LO_TAG; - - if (strlen($2) >= SMTPD_TAG_SIZE) { - yyerror("tag name too long"); - free($2); - YYERROR; - } - listen_opts.tag = $2; - } - | HOSTNAME STRING { - if (listen_opts.options & LO_HOSTNAME) { - yyerror("hostname already specified"); - YYERROR; - } - listen_opts.options |= LO_HOSTNAME; - - listen_opts.hostname = $2; - } - | HOSTNAMES tables { - struct table *t = $2; - - if (listen_opts.options & LO_HOSTNAMES) { - yyerror("hostnames already specified"); - YYERROR; - } - listen_opts.options |= LO_HOSTNAMES; - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { - yyerror("invalid use of table \"%s\" as " - "HOSTNAMES parameter", t->t_name); - YYERROR; - } - listen_opts.hostnametable = t; - } - | MASK_SRC { - if (config_lo_mask_source(&listen_opts)) { - YYERROR; - } - } - | RECEIVEDAUTH { - if (listen_opts.options & LO_RECEIVEDAUTH) { - yyerror("received-auth already specified"); - YYERROR; - } - listen_opts.options |= LO_RECEIVEDAUTH; - listen_opts.flags |= F_RECEIVEDAUTH; - } - | NO_DSN { - if (listen_opts.options & LO_NODSN) { - yyerror("no-dsn already specified"); - YYERROR; - } - listen_opts.options |= LO_NODSN; - listen_opts.flags &= ~F_EXT_DSN; - } - | PROXY_V2 { - if (listen_opts.options & LO_PROXY) { - yyerror("proxy-v2 already specified"); - YYERROR; - } - listen_opts.options |= LO_PROXY; - listen_opts.flags |= F_PROXY; - } - | SENDERS tables { - struct table *t = $2; - - if (listen_opts.options & LO_SENDERS) { - yyerror("senders already specified"); - YYERROR; - } - listen_opts.options |= LO_SENDERS; - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_MAILADDRMAP)) { - yyerror("invalid use of table \"%s\" as " - "SENDERS parameter", t->t_name); - YYERROR; - } - listen_opts.sendertable = t; - } - | SENDERS tables MASQUERADE { - struct table *t = $2; - - if (listen_opts.options & LO_SENDERS) { - yyerror("senders already specified"); - YYERROR; - } - listen_opts.options |= LO_SENDERS|LO_MASQUERADE; - - if (!table_check_use(t, T_DYNAMIC|T_HASH, K_MAILADDRMAP)) { - yyerror("invalid use of table \"%s\" as " - "SENDERS parameter", t->t_name); - YYERROR; - } - listen_opts.sendertable = t; - } - ; - -listener_type : socket_listener - | if_listener - ; - -socket_listener : SOCKET sock_listen { - if (conf->sc_sock_listener) { - yyerror("socket listener already configured"); - YYERROR; - } - create_sock_listener(&listen_opts); - } - ; - -if_listener : STRING if_listen { - listen_opts.ifx = $1; - create_if_listener(&listen_opts); - } - ; - -sock_listen : opt_sock_listen sock_listen - | /* empty */ - ; - -if_listen : opt_if_listen if_listen - | /* empty */ - ; - - -listen : LISTEN { - memset(&listen_opts, 0, sizeof listen_opts); - listen_opts.family = AF_UNSPEC; - listen_opts.flags |= F_EXT_DSN; - } ON listener_type - ; - -table : TABLE STRING STRING { - char *p, *backend, *config; - - p = $3; - if (*p == '/') { - backend = "static"; - config = $3; - } - else { - backend = $3; - config = NULL; - for (p = $3; *p && *p != ':'; p++) - ; - if (*p == ':') { - *p = '\0'; - backend = $3; - config = p+1; - } - } - if (config != NULL && *config != '/') { - yyerror("invalid backend parameter for table: %s", - $2); - free($2); - free($3); - YYERROR; - } - table = table_create(conf, backend, $2, config); - if (!table_config(table)) { - yyerror("invalid configuration file %s for table %s", - config, table->t_name); - free($2); - free($3); - YYERROR; - } - table = NULL; - free($2); - free($3); - } - | TABLE STRING { - table = table_create(conf, "static", $2, NULL); - free($2); - } '{' tableval_list '}' { - table = NULL; - } - ; - -tablenew : STRING { - struct table *t; - - t = table_create(conf, "static", NULL, NULL); - table_add(t, $1, NULL); - free($1); - $$ = t; - } - | '{' { - table = table_create(conf, "static", NULL, NULL); - } tableval_list '}' { - $$ = table; - table = NULL; - } - ; - -tableref : '<' STRING '>' { - struct table *t; - - if ((t = table_find(conf, $2)) == NULL) { - yyerror("no such table: %s", $2); - free($2); - YYERROR; - } - free($2); - $$ = t; - } - ; - -tables : tablenew { $$ = $1; } - | tableref { $$ = $1; } - ; - - -%% - -struct keywords { - const char *k_name; - int k_val; -}; - -int -yyerror(const char *fmt, ...) -{ - va_list ap; - char *msg; - - file->errors++; - va_start(ap, fmt); - if (vasprintf(&msg, fmt, ap) == -1) - fatalx("yyerror vasprintf"); - va_end(ap); - logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); - free(msg); - return (0); -} - -int -kw_cmp(const void *k, const void *e) -{ - return (strcmp(k, ((const struct keywords *)e)->k_name)); -} - -int -lookup(char *s) -{ - /* this has to be sorted always */ - static const struct keywords keywords[] = { - { "action", ACTION }, - { "alias", ALIAS }, - { "any", ANY }, - { "auth", AUTH }, - { "auth-optional", AUTH_OPTIONAL }, - { "backup", BACKUP }, - { "bounce", BOUNCE }, - { "bypass", BYPASS }, - { "ca", CA }, - { "cert", CERT }, - { "chain", CHAIN }, - { "chroot", CHROOT }, - { "ciphers", CIPHERS }, - { "commit", COMMIT }, - { "compression", COMPRESSION }, - { "connect", CONNECT }, - { "data", DATA }, - { "data-line", DATA_LINE }, - { "dhe", DHE }, - { "disconnect", DISCONNECT }, - { "domain", DOMAIN }, - { "ehlo", EHLO }, - { "encryption", ENCRYPTION }, - { "expand-only", EXPAND_ONLY }, - { "fcrdns", FCRDNS }, - { "filter", FILTER }, - { "for", FOR }, - { "forward-only", FORWARD_ONLY }, - { "from", FROM }, - { "group", GROUP }, - { "helo", HELO }, - { "helo-src", HELO_SRC }, - { "host", HOST }, - { "hostname", HOSTNAME }, - { "hostnames", HOSTNAMES }, - { "include", INCLUDE }, - { "inet4", INET4 }, - { "inet6", INET6 }, - { "junk", JUNK }, - { "key", KEY }, - { "limit", LIMIT }, - { "listen", LISTEN }, - { "lmtp", LMTP }, - { "local", LOCAL }, - { "mail-from", MAIL_FROM }, - { "maildir", MAILDIR }, - { "mask-src", MASK_SRC }, - { "masquerade", MASQUERADE }, - { "match", MATCH }, - { "max-deferred", MAX_DEFERRED }, - { "max-message-size", MAX_MESSAGE_SIZE }, - { "mbox", MBOX }, - { "mda", MDA }, - { "mta", MTA }, - { "mx", MX }, - { "no-dsn", NO_DSN }, - { "no-verify", NO_VERIFY }, - { "noop", NOOP }, - { "on", ON }, - { "phase", PHASE }, - { "pki", PKI }, - { "port", PORT }, - { "proc", PROC }, - { "proc-exec", PROC_EXEC }, - { "proxy-v2", PROXY_V2 }, - { "queue", QUEUE }, - { "quit", QUIT }, - { "rcpt-to", RCPT_TO }, - { "rdns", RDNS }, - { "received-auth", RECEIVEDAUTH }, - { "recipient", RECIPIENT }, - { "regex", REGEX }, - { "reject", REJECT }, - { "relay", RELAY }, - { "report", REPORT }, - { "rewrite", REWRITE }, - { "rset", RSET }, - { "scheduler", SCHEDULER }, - { "senders", SENDERS }, - { "smtp", SMTP }, - { "smtp-in", SMTP_IN }, - { "smtp-out", SMTP_OUT }, - { "smtps", SMTPS }, - { "socket", SOCKET }, - { "src", SRC }, - { "srs", SRS }, - { "sub-addr-delim", SUB_ADDR_DELIM }, - { "table", TABLE }, - { "tag", TAG }, - { "tagged", TAGGED }, - { "tls", TLS }, - { "tls-require", TLS_REQUIRE }, - { "ttl", TTL }, - { "user", USER }, - { "userbase", USERBASE }, - { "verify", VERIFY }, - { "virtual", VIRTUAL }, - { "warn-interval", WARN_INTERVAL }, - { "wrapper", WRAPPER }, - }; - const struct keywords *p; - - p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), - sizeof(keywords[0]), kw_cmp); - - if (p) - return (p->k_val); - else - return (STRING); -} - -#define START_EXPAND 1 -#define DONE_EXPAND 2 - -static int expanding; - -int -igetc(void) -{ - int c; - - while (1) { - if (file->ungetpos > 0) - c = file->ungetbuf[--file->ungetpos]; - else - c = getc(file->stream); - - if (c == START_EXPAND) - expanding = 1; - else if (c == DONE_EXPAND) - expanding = 0; - else - break; - } - return (c); -} - -int -lgetc(int quotec) -{ - int c, next; - - if (quotec) { - if ((c = igetc()) == EOF) { - yyerror("reached end of file while parsing " - "quoted string"); - if (file == topfile || popfile() == EOF) - return (EOF); - return (quotec); - } - return (c); - } - - while ((c = igetc()) == '\\') { - next = igetc(); - if (next != '\n') { - c = next; - break; - } - yylval.lineno = file->lineno; - file->lineno++; - } - - if (c == EOF) { - /* - * Fake EOL when hit EOF for the first time. This gets line - * count right if last line in included file is syntactically - * invalid and has no newline. - */ - if (file->eof_reached == 0) { - file->eof_reached = 1; - return ('\n'); - } - while (c == EOF) { - if (file == topfile || popfile() == EOF) - return (EOF); - c = igetc(); - } - } - return (c); -} - -void -lungetc(int c) -{ - if (c == EOF) - return; - - if (file->ungetpos >= file->ungetsize) { - void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); - if (p == NULL) - err(1, "%s", __func__); - file->ungetbuf = p; - file->ungetsize *= 2; - } - file->ungetbuf[file->ungetpos++] = c; -} - -int -findeol(void) -{ - int c; - - /* skip to either EOF or the first real EOL */ - while (1) { - c = lgetc(0); - if (c == '\n') { - file->lineno++; - break; - } - if (c == EOF) - break; - } - return (ERROR); -} - -int -yylex(void) -{ - unsigned char buf[8096]; - unsigned char *p, *val; - int quotec, next, c; - int token; - -top: - p = buf; - while ((c = lgetc(0)) == ' ' || c == '\t') - ; /* nothing */ - - yylval.lineno = file->lineno; - if (c == '#') - while ((c = lgetc(0)) != '\n' && c != EOF) - ; /* nothing */ - if (c == '$' && !expanding) { - while (1) { - if ((c = lgetc(0)) == EOF) - return (0); - - if (p + 1 >= buf + sizeof(buf) - 1) { - yyerror("string too long"); - return (findeol()); - } - if (isalnum(c) || c == '_') { - *p++ = c; - continue; - } - *p = '\0'; - lungetc(c); - break; - } - val = symget(buf); - if (val == NULL) { - yyerror("macro '%s' not defined", buf); - return (findeol()); - } - p = val + strlen(val) - 1; - lungetc(DONE_EXPAND); - while (p >= val) { - lungetc(*p); - p--; - } - lungetc(START_EXPAND); - goto top; - } - - switch (c) { - case '\'': - case '"': - quotec = c; - while (1) { - if ((c = lgetc(quotec)) == EOF) - return (0); - if (c == '\n') { - file->lineno++; - continue; - } else if (c == '\\') { - if ((next = lgetc(quotec)) == EOF) - return (0); - if (next == quotec || next == ' ' || - next == '\t') - c = next; - else if (next == '\n') { - file->lineno++; - continue; - } else - lungetc(next); - } else if (c == quotec) { - *p = '\0'; - break; - } else if (c == '\0') { - yyerror("syntax error"); - return (findeol()); - } - if (p + 1 >= buf + sizeof(buf) - 1) { - yyerror("string too long"); - return (findeol()); - } - *p++ = c; - } - yylval.v.string = strdup(buf); - if (yylval.v.string == NULL) - err(1, "%s", __func__); - return (STRING); - } - -#define allowed_to_end_number(x) \ - (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') - - if (c == '-' || isdigit(c)) { - do { - *p++ = c; - if ((size_t)(p-buf) >= sizeof(buf)) { - yyerror("string too long"); - return (findeol()); - } - } while ((c = lgetc(0)) != EOF && isdigit(c)); - lungetc(c); - if (p == buf + 1 && buf[0] == '-') - goto nodigits; - if (c == EOF || allowed_to_end_number(c)) { - const char *errstr = NULL; - - *p = '\0'; - yylval.v.number = strtonum(buf, LLONG_MIN, - LLONG_MAX, &errstr); - if (errstr) { - yyerror("\"%s\" invalid number: %s", - buf, errstr); - return (findeol()); - } - return (NUMBER); - } else { -nodigits: - while (p > buf + 1) - lungetc(*--p); - c = *--p; - if (c == '-') - return (c); - } - } - - if (c == '=') { - if ((c = lgetc(0)) != EOF && c == '>') - return (ARROW); - lungetc(c); - c = '='; - } - -#define allowed_in_string(x) \ - (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ - x != '{' && x != '}' && x != '<' && x != '>' && \ - x != '!' && x != '=' && x != '#' && \ - x != ',')) - - if (isalnum(c) || c == ':' || c == '_') { - do { - *p++ = c; - if ((size_t)(p-buf) >= sizeof(buf)) { - yyerror("string too long"); - return (findeol()); - } - } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); - lungetc(c); - *p = '\0'; - if ((token = lookup(buf)) == STRING) - if ((yylval.v.string = strdup(buf)) == NULL) - err(1, "%s", __func__); - return (token); - } - if (c == '\n') { - yylval.lineno = file->lineno; - file->lineno++; - } - if (c == EOF) - return (0); - return (c); -} - -int -check_file_secrecy(int fd, const char *fname) -{ - struct stat st; - - if (fstat(fd, &st)) { - log_warn("warn: cannot stat %s", fname); - return (-1); - } - if (st.st_uid != 0 && st.st_uid != getuid()) { - log_warnx("warn: %s: owner not root or current user", fname); - return (-1); - } - if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { - log_warnx("warn: %s: group/world readable/writeable", fname); - return (-1); - } - return (0); -} - -struct file * -pushfile(const char *name, int secret) -{ - struct file *nfile; - - if ((nfile = calloc(1, sizeof(struct file))) == NULL) { - log_warn("%s", __func__); - return (NULL); - } - if ((nfile->name = strdup(name)) == NULL) { - log_warn("%s", __func__); - free(nfile); - return (NULL); - } - if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { - log_warn("%s: %s", __func__, nfile->name); - free(nfile->name); - free(nfile); - return (NULL); - } else if (secret && - check_file_secrecy(fileno(nfile->stream), nfile->name)) { - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return (NULL); - } - nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; - nfile->ungetsize = 16; - nfile->ungetbuf = malloc(nfile->ungetsize); - if (nfile->ungetbuf == NULL) { - log_warn("%s", __func__); - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return (NULL); - } - TAILQ_INSERT_TAIL(&files, nfile, entry); - return (nfile); -} - -int -popfile(void) -{ - struct file *prev; - - if ((prev = TAILQ_PREV(file, files, entry)) != NULL) - prev->errors += file->errors; - - TAILQ_REMOVE(&files, file, entry); - fclose(file->stream); - free(file->name); - free(file->ungetbuf); - free(file); - file = prev; - return (file ? 0 : EOF); -} - -int -parse_config(struct smtpd *x_conf, const char *filename, int opts) -{ - struct sym *sym, *next; - - conf = x_conf; - errors = 0; - - if ((file = pushfile(filename, 0)) == NULL) { - purge_config(PURGE_EVERYTHING); - return (-1); - } - topfile = file; - - /* - * parse configuration - */ - setservent(1); - yyparse(); - errors = file->errors; - popfile(); - endservent(); - - /* If the socket listener was not configured, create a default one. */ - if (!conf->sc_sock_listener) { - memset(&listen_opts, 0, sizeof listen_opts); - create_sock_listener(&listen_opts); - } - - /* Free macros and check which have not been used. */ - TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { - if ((conf->sc_opts & SMTPD_OPT_VERBOSE) && !sym->used) - fprintf(stderr, "warning: macro '%s' not " - "used\n", sym->nam); - if (!sym->persist) { - free(sym->nam); - free(sym->val); - TAILQ_REMOVE(&symhead, sym, entry); - free(sym); - } - } - - if (TAILQ_EMPTY(conf->sc_rules)) { - log_warnx("warn: no rules, nothing to do"); - errors++; - } - - if (errors) { - purge_config(PURGE_EVERYTHING); - return (-1); - } - - return (0); -} - -int -symset(const char *nam, const char *val, int persist) -{ - struct sym *sym; - - TAILQ_FOREACH(sym, &symhead, entry) { - if (strcmp(nam, sym->nam) == 0) - break; - } - - if (sym != NULL) { - if (sym->persist == 1) - return (0); - else { - free(sym->nam); - free(sym->val); - TAILQ_REMOVE(&symhead, sym, entry); - free(sym); - } - } - if ((sym = calloc(1, sizeof(*sym))) == NULL) - return (-1); - - sym->nam = strdup(nam); - if (sym->nam == NULL) { - free(sym); - return (-1); - } - sym->val = strdup(val); - if (sym->val == NULL) { - free(sym->nam); - free(sym); - return (-1); - } - sym->used = 0; - sym->persist = persist; - TAILQ_INSERT_TAIL(&symhead, sym, entry); - return (0); -} - -int -cmdline_symset(char *s) -{ - char *sym, *val; - int ret; - - if ((val = strrchr(s, '=')) == NULL) - return (-1); - sym = strndup(s, val - s); - if (sym == NULL) - errx(1, "%s: strndup", __func__); - ret = symset(sym, val + 1, 1); - free(sym); - - return (ret); -} - -char * -symget(const char *nam) -{ - struct sym *sym; - - TAILQ_FOREACH(sym, &symhead, entry) { - if (strcmp(nam, sym->nam) == 0) { - sym->used = 1; - return (sym->val); - } - } - return (NULL); -} - -static void -create_sock_listener(struct listen_opts *lo) -{ - struct listener *l = xcalloc(1, sizeof(*l)); - lo->hostname = conf->sc_hostname; - l->ss.ss_family = AF_LOCAL; -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN - l->ss.ss_len = sizeof(struct sockaddr *); -#endif - l->local = 1; - conf->sc_sock_listener = l; - config_listener(l, lo); -} - -static void -create_if_listener(struct listen_opts *lo) -{ - uint16_t flags; - - if (lo->port != 0 && lo->ssl == F_SSL) - errx(1, "invalid listen option: tls/smtps on same port"); - - if (lo->auth != 0 && !lo->ssl) - errx(1, "invalid listen option: auth requires tls/smtps"); - - if (lo->pki && !lo->ssl) - errx(1, "invalid listen option: pki requires tls/smtps"); - - flags = lo->flags; - - if (lo->port) { - lo->flags = lo->ssl|lo->auth|flags; - lo->port = htons(lo->port); - } - else { - if (lo->ssl & F_SMTPS) { - lo->port = htons(465); - lo->flags = F_SMTPS|lo->auth|flags; - } - - if (!lo->ssl || (lo->ssl & F_STARTTLS)) { - lo->port = htons(25); - lo->flags = lo->auth|flags; - if (lo->ssl & F_STARTTLS) - lo->flags |= F_STARTTLS; - } - } - - if (interface(lo)) - return; - if (host_v4(lo)) - return; - if (host_v6(lo)) - return; - if (host_dns(lo)) - return; - - errx(1, "invalid virtual ip or interface: %s", lo->ifx); -} - -static void -config_listener(struct listener *h, struct listen_opts *lo) -{ - h->fd = -1; - h->port = lo->port; - h->flags = lo->flags; - - if (lo->hostname == NULL) - lo->hostname = conf->sc_hostname; - - if (lo->options & LO_FILTER) { - h->flags |= F_FILTERED; - (void)strlcpy(h->filter_name, - lo->filtername, - sizeof(h->filter_name)); - } - - h->pki_name[0] = '\0'; - - if (lo->authtable != NULL) - (void)strlcpy(h->authtable, lo->authtable->t_name, sizeof(h->authtable)); - if (lo->pki != NULL) { - if (!lowercase(h->pki_name, lo->pki, sizeof(h->pki_name))) { - log_warnx("pki name too long: %s", lo->pki); - fatalx(NULL); - } - if (dict_get(conf->sc_pki_dict, h->pki_name) == NULL) { - log_warnx("pki name not found: %s", lo->pki); - fatalx(NULL); - } - } - - if (lo->ca != NULL) { - if (!lowercase(h->ca_name, lo->ca, sizeof(h->ca_name))) { - log_warnx("ca name too long: %s", lo->ca); - fatalx(NULL); - } - if (dict_get(conf->sc_ca_dict, h->ca_name) == NULL) { - log_warnx("ca name not found: %s", lo->ca); - fatalx(NULL); - } - } - if (lo->tag != NULL) - (void)strlcpy(h->tag, lo->tag, sizeof(h->tag)); - - (void)strlcpy(h->hostname, lo->hostname, sizeof(h->hostname)); - if (lo->hostnametable) - (void)strlcpy(h->hostnametable, lo->hostnametable->t_name, sizeof(h->hostnametable)); - if (lo->sendertable) { - (void)strlcpy(h->sendertable, lo->sendertable->t_name, sizeof(h->sendertable)); - if (lo->options & LO_MASQUERADE) - h->flags |= F_MASQUERADE; - } - - if (lo->ssl & F_TLS_VERIFY) - h->flags |= F_TLS_VERIFY; - - if (lo->ssl & F_STARTTLS_REQUIRE) - h->flags |= F_STARTTLS_REQUIRE; - - if (h != conf->sc_sock_listener) - TAILQ_INSERT_TAIL(conf->sc_listeners, h, entry); -} - -static int -host_v4(struct listen_opts *lo) -{ - struct in_addr ina; - struct sockaddr_in *sain; - struct listener *h; - - if (lo->family != AF_UNSPEC && lo->family != AF_INET) - return (0); - - memset(&ina, 0, sizeof(ina)); - if (inet_pton(AF_INET, lo->ifx, &ina) != 1) - return (0); - - h = xcalloc(1, sizeof(*h)); - sain = (struct sockaddr_in *)&h->ss; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sain->sin_len = sizeof(struct sockaddr_in); -#endif - sain->sin_family = AF_INET; - sain->sin_addr.s_addr = ina.s_addr; - sain->sin_port = lo->port; - - if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) - h->local = 1; - config_listener(h, lo); - - return (1); -} - -static int -host_v6(struct listen_opts *lo) -{ - struct in6_addr ina6; - struct sockaddr_in6 *sin6; - struct listener *h; - - if (lo->family != AF_UNSPEC && lo->family != AF_INET6) - return (0); - - memset(&ina6, 0, sizeof(ina6)); - if (inet_pton(AF_INET6, lo->ifx, &ina6) != 1) - return (0); - - h = xcalloc(1, sizeof(*h)); - sin6 = (struct sockaddr_in6 *)&h->ss; -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sin6->sin6_len = sizeof(struct sockaddr_in6); -#endif - sin6->sin6_family = AF_INET6; - sin6->sin6_port = lo->port; - memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6)); - - if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) - h->local = 1; - config_listener(h, lo); - - return (1); -} - -static int -host_dns(struct listen_opts *lo) -{ - struct addrinfo hints, *res0, *res; - int error, cnt = 0; - struct sockaddr_in *sain; - struct sockaddr_in6 *sin6; - struct listener *h; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = lo->family; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG; - error = getaddrinfo(lo->ifx, NULL, &hints, &res0); - if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) - return (0); - if (error) { - log_warnx("warn: host_dns: could not parse \"%s\": %s", lo->ifx, - gai_strerror(error)); - return (-1); - } - - for (res = res0; res; res = res->ai_next) { - if (res->ai_family != AF_INET && - res->ai_family != AF_INET6) - continue; - h = xcalloc(1, sizeof(*h)); - - h->ss.ss_family = res->ai_family; - if (res->ai_family == AF_INET) { - sain = (struct sockaddr_in *)&h->ss; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sain->sin_len = sizeof(struct sockaddr_in); -#endif - sain->sin_addr.s_addr = ((struct sockaddr_in *) - res->ai_addr)->sin_addr.s_addr; - sain->sin_port = lo->port; - if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) - h->local = 1; - } else { - sin6 = (struct sockaddr_in6 *)&h->ss; -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sin6->sin6_len = sizeof(struct sockaddr_in6); -#endif - memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) - res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); - sin6->sin6_port = lo->port; - if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) - h->local = 1; - } - - config_listener(h, lo); - - cnt++; - } - - freeaddrinfo(res0); - return (cnt); -} - -static int -interface(struct listen_opts *lo) -{ - struct ifaddrs *ifap, *p; - struct sockaddr_in *sain; - struct sockaddr_in6 *sin6; - struct listener *h; - int ret = 0; - - if (getifaddrs(&ifap) == -1) - fatal("getifaddrs"); - - for (p = ifap; p != NULL; p = p->ifa_next) { - if (p->ifa_addr == NULL) - continue; - if (strcmp(p->ifa_name, lo->ifx) != 0 && - !is_if_in_group(p->ifa_name, lo->ifx)) - continue; - if (lo->family != AF_UNSPEC && lo->family != p->ifa_addr->sa_family) - continue; - - h = xcalloc(1, sizeof(*h)); - - switch (p->ifa_addr->sa_family) { - case AF_INET: - sain = (struct sockaddr_in *)&h->ss; - *sain = *(struct sockaddr_in *)p->ifa_addr; -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sain->sin_len = sizeof(struct sockaddr_in); -#endif - sain->sin_port = lo->port; - if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) - h->local = 1; - break; - - case AF_INET6: - sin6 = (struct sockaddr_in6 *)&h->ss; - *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sin6->sin6_len = sizeof(struct sockaddr_in6); -#endif - sin6->sin6_port = lo->port; - if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) - h->local = 1; - break; - - default: - free(h); - continue; - } - - config_listener(h, lo); - ret = 1; - } - - freeifaddrs(ifap); - - return ret; -} - -int -delaytonum(char *str) -{ - unsigned int factor; - size_t len; - const char *errstr = NULL; - int delay; - - /* we need at least 1 digit and 1 unit */ - len = strlen(str); - if (len < 2) - goto bad; - - switch(str[len - 1]) { - - case 's': - factor = 1; - break; - - case 'm': - factor = 60; - break; - - case 'h': - factor = 60 * 60; - break; - - case 'd': - factor = 24 * 60 * 60; - break; - - default: - goto bad; - } - - str[len - 1] = '\0'; - delay = strtonum(str, 1, INT_MAX / factor, &errstr); - if (errstr) - goto bad; - - return (delay * factor); - -bad: - return (-1); -} - -int -is_if_in_group(const char *ifname, const char *groupname) -{ -#ifdef HAVE_STRUCT_IFGROUPREQ - unsigned int len; - struct ifgroupreq ifgr; - struct ifg_req *ifg; - int s; - int ret = 0; - - if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) - err(1, "socket"); - - memset(&ifgr, 0, sizeof(ifgr)); - if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ) - errx(1, "interface name too large"); - - if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) { - if (errno == EINVAL || errno == ENOTTY) - goto end; - err(1, "SIOCGIFGROUP"); - } - - len = ifgr.ifgr_len; - ifgr.ifgr_groups = xcalloc(len/sizeof(struct ifg_req), - sizeof(struct ifg_req)); - if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) - err(1, "SIOCGIFGROUP"); - - ifg = ifgr.ifgr_groups; - for (; ifg && len >= sizeof(struct ifg_req); ifg++) { - len -= sizeof(struct ifg_req); - if (strcmp(ifg->ifgrq_group, groupname) == 0) { - ret = 1; - break; - } - } - free(ifgr.ifgr_groups); - -end: - close(s); - return ret; -#else - return (0); -#endif -} - -static int -config_lo_mask_source(struct listen_opts *lo) { - if (lo->options & LO_MASKSOURCE) { - yyerror("mask-source already specified"); - return -1; - } - lo->options |= LO_MASKSOURCE; - lo->flags |= F_MASK_SOURCE; - - return 0; -} - diff --git a/smtpd/parser.c b/smtpd/parser.c deleted file mode 100644 index 4c2321ec..00000000 --- a/smtpd/parser.c +++ /dev/null @@ -1,341 +0,0 @@ -/* $OpenBSD: parser.c,v 1.42 2020/01/06 11:02:38 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "parser.h" - -uint64_t text_to_evpid(const char *); -uint32_t text_to_msgid(const char *); - -struct node { - int type; - const char *token; - struct node *parent; - TAILQ_ENTRY(node) entry; - TAILQ_HEAD(, node) children; - int (*cmd)(int, struct parameter*); -}; - -static struct node *root; - -static int text_to_sockaddr(struct sockaddr *, int, const char *); - -#define ARGVMAX 64 - -int -cmd_install(const char *pattern, int (*cmd)(int, struct parameter*)) -{ - struct node *node, *tmp; - char *s, *str, *argv[ARGVMAX], **ap; - int i, n; - - /* Tokenize */ - str = s = strdup(pattern); - if (str == NULL) - err(1, "strdup"); - n = 0; - for (ap = argv; n < ARGVMAX && (*ap = strsep(&str, " \t")) != NULL;) { - if (**ap != '\0') { - ap++; - n++; - } - } - *ap = NULL; - - if (root == NULL) { - root = calloc(1, sizeof (*root)); - TAILQ_INIT(&root->children); - } - node = root; - - for (i = 0; i < n; i++) { - TAILQ_FOREACH(tmp, &node->children, entry) { - if (!strcmp(tmp->token, argv[i])) { - node = tmp; - break; - } - } - if (tmp == NULL) { - tmp = calloc(1, sizeof (*tmp)); - TAILQ_INIT(&tmp->children); - if (!strcmp(argv[i], "")) - tmp->type = P_STR; - else if (!strcmp(argv[i], "")) - tmp->type = P_INT; - else if (!strcmp(argv[i], "")) - tmp->type = P_MSGID; - else if (!strcmp(argv[i], "")) - tmp->type = P_EVPID; - else if (!strcmp(argv[i], "")) - tmp->type = P_ROUTEID; - else if (!strcmp(argv[i], "")) - tmp->type = P_ADDR; - else - tmp->type = P_TOKEN; - tmp->token = strdup(argv[i]); - tmp->parent = node; - TAILQ_INSERT_TAIL(&node->children, tmp, entry); - node = tmp; - } - } - - if (node->cmd) - errx(1, "duplicate pattern: %s", pattern); - node->cmd = cmd; - - free(s); - return (n); -} - -static int -cmd_check(const char *str, struct node *node, struct parameter *res) -{ - const char *e; - - switch (node->type) { - case P_TOKEN: - if (!strcmp(str, node->token)) - return (1); - return (0); - - case P_STR: - res->u.u_str = str; - return (1); - - case P_INT: - res->u.u_int = strtonum(str, INT_MIN, INT_MAX, &e); - if (e) - return (0); - return (1); - - case P_MSGID: - if (strlen(str) != 8) - return (0); - res->u.u_msgid = text_to_msgid(str); - if (res->u.u_msgid == 0) - return (0); - return (1); - - case P_EVPID: - if (strlen(str) != 16) - return (0); - res->u.u_evpid = text_to_evpid(str); - if (res->u.u_evpid == 0) - return (0); - return (1); - - case P_ROUTEID: - res->u.u_routeid = strtonum(str, 1, LLONG_MAX, &e); - if (e) - return (0); - return (1); - - case P_ADDR: - if (text_to_sockaddr((struct sockaddr *)&res->u.u_ss, PF_UNSPEC, str) == 0) - return (1); - return (0); - - default: - errx(1, "bad token type: %d", node->type); - return (0); - } -} - -int -cmd_run(int argc, char **argv) -{ - struct parameter param[ARGVMAX]; - struct node *node, *tmp, *stack[ARGVMAX], *best; - int i, j, np; - - node = root; - np = 0; - - for (i = 0; i < argc; i++) { - TAILQ_FOREACH(tmp, &node->children, entry) { - if (cmd_check(argv[i], tmp, ¶m[np])) { - stack[i] = tmp; - node = tmp; - param[np].type = node->type; - if (node->type != P_TOKEN) - np++; - break; - } - } - if (tmp == NULL) { - best = NULL; - TAILQ_FOREACH(tmp, &node->children, entry) { - if (tmp->type != P_TOKEN) - continue; - if (strstr(tmp->token, argv[i]) != tmp->token) - continue; - if (best) - goto fail; - best = tmp; - } - if (best == NULL) - goto fail; - stack[i] = best; - node = best; - param[np].type = node->type; - if (node->type != P_TOKEN) - np++; - } - } - - if (node->cmd == NULL) - goto fail; - - return (node->cmd(np, np ? param : NULL)); - -fail: - if (TAILQ_FIRST(&node->children) == NULL) { - fprintf(stderr, "invalid command\n"); - return (-1); - } - - fprintf(stderr, "possibilities are:\n"); - TAILQ_FOREACH(tmp, &node->children, entry) { - for (j = 0; j < i; j++) - fprintf(stderr, "%s%s", j?" ":"", stack[j]->token); - fprintf(stderr, "%s%s\n", i?" ":"", tmp->token); - } - - return (-1); -} - -int -cmd_show_params(int argc, struct parameter *argv) -{ - int i; - - for (i = 0; i < argc; i++) { - switch(argv[i].type) { - case P_STR: - printf(" str:\"%s\"", argv[i].u.u_str); - break; - case P_INT: - printf(" int:%d", argv[i].u.u_int); - break; - case P_MSGID: - printf(" msgid:%08"PRIx32, argv[i].u.u_msgid); - break; - case P_EVPID: - printf(" evpid:%016"PRIx64, argv[i].u.u_evpid); - break; - case P_ROUTEID: - printf(" routeid:%016"PRIx64, argv[i].u.u_routeid); - break; - default: - printf(" ???:%d", argv[i].type); - } - } - printf ("\n"); - return (1); -} - -static int -text_to_sockaddr(struct sockaddr *sa, int family, const char *str) -{ - struct in_addr ina; - struct in6_addr in6a; - struct sockaddr_in *in; - struct sockaddr_in6 *in6; - char *cp, *str2; - const char *errstr; - - switch (family) { - case PF_UNSPEC: - if (text_to_sockaddr(sa, PF_INET, str) == 0) - return (0); - return text_to_sockaddr(sa, PF_INET6, str); - - case PF_INET: - if (inet_pton(PF_INET, str, &ina) != 1) - return (-1); - - in = (struct sockaddr_in *)sa; - memset(in, 0, sizeof *in); -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - in->sin_len = sizeof(struct sockaddr_in); -#endif - in->sin_family = PF_INET; - in->sin_addr.s_addr = ina.s_addr; - return (0); - - case PF_INET6: - cp = strchr(str, SCOPE_DELIMITER); - if (cp) { - str2 = strdup(str); - if (str2 == NULL) - return (-1); - str2[cp - str] = '\0'; - if (inet_pton(PF_INET6, str2, &in6a) != 1) { - free(str2); - return (-1); - } - cp++; - free(str2); - } else if (inet_pton(PF_INET6, str, &in6a) != 1) - return (-1); - - in6 = (struct sockaddr_in6 *)sa; - memset(in6, 0, sizeof *in6); -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - in6->sin6_len = sizeof(struct sockaddr_in6); -#endif - in6->sin6_family = PF_INET6; - in6->sin6_addr = in6a; - - if (cp == NULL) - return (0); - - if (IN6_IS_ADDR_LINKLOCAL(&in6a) || - IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || - IN6_IS_ADDR_MC_NODELOCAL(&in6a)) - if ((in6->sin6_scope_id = if_nametoindex(cp))) - return (0); - - in6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); - if (errstr) - return (-1); - return (0); - - default: - break; - } - - return (-1); -} diff --git a/smtpd/parser.h b/smtpd/parser.h deleted file mode 100644 index f0114e9e..00000000 --- a/smtpd/parser.h +++ /dev/null @@ -1,43 +0,0 @@ -/* $OpenBSD: parser.h,v 1.29 2014/02/04 15:22:39 eric Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -enum { - P_TOKEN, - P_STR, - P_INT, - P_MSGID, - P_EVPID, - P_ROUTEID, - P_ADDR, -}; - -struct parameter { - int type; - union { - const char *u_str; - int u_int; - uint32_t u_msgid; - uint64_t u_evpid; - uint64_t u_routeid; - struct sockaddr_storage u_ss; - } u; -}; - -int cmd_install(const char *, int (*)(int, struct parameter *)); -int cmd_run(int, char **); -int cmd_show_params(int argc, struct parameter *argv); diff --git a/smtpd/pony.c b/smtpd/pony.c deleted file mode 100644 index 1865b339..00000000 --- a/smtpd/pony.c +++ /dev/null @@ -1,212 +0,0 @@ -/* $OpenBSD: pony.c,v 1.27 2019/06/13 11:45:35 eric Exp $ */ - -/* - * Copyright (c) 2014 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -void mda_imsg(struct mproc *, struct imsg *); -void mta_imsg(struct mproc *, struct imsg *); -void smtp_imsg(struct mproc *, struct imsg *); - -static void pony_shutdown(void); - -void -pony_imsg(struct mproc *p, struct imsg *imsg) -{ - struct msg m; - int v; - - if (imsg == NULL) - pony_shutdown(); - - switch (imsg->hdr.type) { - - case IMSG_GETADDRINFO: - case IMSG_GETADDRINFO_END: - case IMSG_GETNAMEINFO: - case IMSG_RES_QUERY: - resolver_dispatch_result(p, imsg); - return; - - case IMSG_CERT_INIT: - case IMSG_CERT_VERIFY: - cert_dispatch_result(p, imsg); - return; - - case IMSG_CONF_START: - return; - case IMSG_CONF_END: - smtp_configure(); - return; - case IMSG_CTL_VERBOSE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - log_trace_verbose(v); - return; - case IMSG_CTL_PROFILE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - profiling = v; - return; - - /* smtp imsg */ - case IMSG_SMTP_CHECK_SENDER: - case IMSG_SMTP_EXPAND_RCPT: - case IMSG_SMTP_LOOKUP_HELO: - case IMSG_SMTP_AUTHENTICATE: - case IMSG_SMTP_MESSAGE_COMMIT: - case IMSG_SMTP_MESSAGE_CREATE: - case IMSG_SMTP_MESSAGE_OPEN: - case IMSG_FILTER_SMTP_PROTOCOL: - case IMSG_FILTER_SMTP_DATA_BEGIN: - case IMSG_QUEUE_ENVELOPE_SUBMIT: - case IMSG_QUEUE_ENVELOPE_COMMIT: - case IMSG_QUEUE_SMTP_SESSION: - case IMSG_CTL_SMTP_SESSION: - case IMSG_CTL_PAUSE_SMTP: - case IMSG_CTL_RESUME_SMTP: - smtp_imsg(p, imsg); - return; - - /* mta imsg */ - case IMSG_QUEUE_TRANSFER: - case IMSG_MTA_OPEN_MESSAGE: - case IMSG_MTA_LOOKUP_CREDENTIALS: - case IMSG_MTA_LOOKUP_SMARTHOST: - case IMSG_MTA_LOOKUP_SOURCE: - case IMSG_MTA_LOOKUP_HELO: - case IMSG_MTA_DNS_HOST: - case IMSG_MTA_DNS_HOST_END: - case IMSG_MTA_DNS_MX_PREFERENCE: - case IMSG_CTL_RESUME_ROUTE: - case IMSG_CTL_MTA_SHOW_HOSTS: - case IMSG_CTL_MTA_SHOW_RELAYS: - case IMSG_CTL_MTA_SHOW_ROUTES: - case IMSG_CTL_MTA_SHOW_HOSTSTATS: - case IMSG_CTL_MTA_BLOCK: - case IMSG_CTL_MTA_UNBLOCK: - case IMSG_CTL_MTA_SHOW_BLOCK: - mta_imsg(p, imsg); - return; - - /* mda imsg */ - case IMSG_MDA_LOOKUP_USERINFO: - case IMSG_QUEUE_DELIVER: - case IMSG_MDA_OPEN_MESSAGE: - case IMSG_MDA_FORK: - case IMSG_MDA_DONE: - mda_imsg(p, imsg); - return; - default: - break; - } - - errx(1, "session_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -static void -pony_shutdown(void) -{ - log_debug("debug: pony agent exiting"); - _exit(0); -} - -int -pony(void) -{ - struct passwd *pw; - - mda_postfork(); - mta_postfork(); - smtp_postfork(); - - /* do not purge listeners and pki, they are purged - * in smtp_configure() - */ - purge_config(PURGE_TABLES|PURGE_RULES); - - if ((pw = getpwnam(SMTPD_USER)) == NULL) - fatalx("unknown user " SMTPD_USER); - - if (chroot(PATH_CHROOT) == -1) - fatal("pony: chroot"); - if (chdir("/") == -1) - fatal("pony: chdir(\"/\")"); - - config_process(PROC_PONY); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("pony: cannot drop privileges"); - - imsg_callback = pony_imsg; - event_init(); - - mda_postprivdrop(); - mta_postprivdrop(); - smtp_postprivdrop(); - - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - config_peer(PROC_PARENT); - config_peer(PROC_QUEUE); - config_peer(PROC_LKA); - config_peer(PROC_CONTROL); - config_peer(PROC_CA); - - ca_engine_init(); - -#if HAVE_PLEDGE - if (pledge("stdio inet unix recvfd sendfd", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} diff --git a/smtpd/proxy.c b/smtpd/proxy.c deleted file mode 100644 index fdaf4f27..00000000 --- a/smtpd/proxy.c +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (c) 2017 Antoine Kaufmann - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "smtpd.h" - - -/* - * The PROXYv2 protocol is described here: - * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt - */ - -#define PROXY_CLOCAL 0x0 -#define PROXY_CPROXY 0x1 -#define PROXY_AF_UNSPEC 0x0 -#define PROXY_AF_INET 0x1 -#define PROXY_AF_INET6 0x2 -#define PROXY_AF_UNIX 0x3 -#define PROXY_TF_UNSPEC 0x0 -#define PROXY_TF_STREAM 0x1 -#define PROXY_TF_DGRAM 0x2 - -#define PROXY_SESSION_TIMEOUT 300 - -static const uint8_t pv2_signature[] = { - 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, - 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A -}; - -struct proxy_hdr_v2 { - uint8_t sig[12]; - uint8_t ver_cmd; - uint8_t fam; - uint16_t len; -} __attribute__((packed)); - - -struct proxy_addr_ipv4 { - uint32_t src_addr; - uint32_t dst_addr; - uint16_t src_port; - uint16_t dst_port; -} __attribute__((packed)); - -struct proxy_addr_ipv6 { - uint8_t src_addr[16]; - uint8_t dst_addr[16]; - uint16_t src_port; - uint16_t dst_port; -} __attribute__((packed)); - -struct proxy_addr_unix { - uint8_t src_addr[108]; - uint8_t dst_addr[108]; -} __attribute__((packed)); - -union proxy_addr { - struct proxy_addr_ipv4 ipv4; - struct proxy_addr_ipv6 ipv6; - struct proxy_addr_unix un; -} __attribute__((packed)); - -struct proxy_session { - struct listener *l; - struct io *io; - - uint64_t id; - int fd; - uint16_t header_len; - uint16_t header_total; - uint16_t addr_len; - uint16_t addr_total; - - struct sockaddr_storage ss; - struct proxy_hdr_v2 hdr; - union proxy_addr addr; - - void (*cb_accepted)(struct listener *, int, - const struct sockaddr_storage *, struct io *); - void (*cb_dropped)(struct listener *, int, - const struct sockaddr_storage *); -}; - -static void proxy_io(struct io *, int, void *); -static void proxy_error(struct proxy_session *, const char *, const char *); -static int proxy_header_validate(struct proxy_session *); -static int proxy_translate_ss(struct proxy_session *); - -int -proxy_session(struct listener *listener, int sock, - const struct sockaddr_storage *ss, - void (*accepted)(struct listener *, int, - const struct sockaddr_storage *, struct io *), - void (*dropped)(struct listener *, int, - const struct sockaddr_storage *)); - -int -proxy_session(struct listener *listener, int sock, - const struct sockaddr_storage *ss, - void (*accepted)(struct listener *, int, - const struct sockaddr_storage *, struct io *), - void (*dropped)(struct listener *, int, - const struct sockaddr_storage *)) -{ - struct proxy_session *s; - - if ((s = calloc(1, sizeof(*s))) == NULL) - return (-1); - - if ((s->io = io_new()) == NULL) { - free(s); - return (-1); - } - - s->id = generate_uid(); - s->l = listener; - s->fd = sock; - s->header_len = 0; - s->addr_len = 0; - s->ss = *ss; - s->cb_accepted = accepted; - s->cb_dropped = dropped; - - io_set_callback(s->io, proxy_io, s); - io_set_fd(s->io, sock); - io_set_timeout(s->io, PROXY_SESSION_TIMEOUT * 1000); - io_set_read(s->io); - - log_info("%016"PRIx64" smtp event=proxy address=%s", - s->id, ss_to_text(&s->ss)); - - return 0; -} - -static void -proxy_io(struct io *io, int evt, void *arg) -{ - struct proxy_session *s = arg; - struct proxy_hdr_v2 *h = &s->hdr; - uint8_t *buf; - size_t len, off; - - switch (evt) { - - case IO_DATAIN: - buf = io_data(io); - len = io_datalen(io); - - if (s->header_len < sizeof(s->hdr)) { - /* header is incomplete */ - off = sizeof(s->hdr) - s->header_len; - off = (len < off ? len : off); - memcpy((uint8_t *) &s->hdr + s->header_len, buf, off); - - s->header_len += off; - buf += off; - len -= off; - io_drop(s->io, off); - - if (s->header_len < sizeof(s->hdr)) { - /* header is still not complete */ - return; - } - - if (proxy_header_validate(s) != 0) - return; - } - - if (s->addr_len < s->addr_total) { - /* address is incomplete */ - off = s->addr_total - s->addr_len; - off = (len < off ? len : off); - memcpy((uint8_t *) &s->addr + s->addr_len, buf, off); - - s->header_len += off; - s->addr_len += off; - buf += off; - len -= off; - io_drop(s->io, off); - - if (s->addr_len < s->addr_total) { - /* address is still not complete */ - return; - } - } - - if (s->header_len < s->header_total) { - /* additional parameters not complete */ - /* these are ignored for now, but we still need to drop - * the bytes from the buffer */ - off = s->header_total - s->header_len; - off = (len < off ? len : off); - - s->header_len += off; - io_drop(s->io, off); - - if (s->header_len < s->header_total) - /* not complete yet */ - return; - } - - switch(h->ver_cmd & 0xF) { - case PROXY_CLOCAL: - /* local address, no need to modify ss */ - break; - - case PROXY_CPROXY: - if (proxy_translate_ss(s) != 0) - return; - break; - - default: - proxy_error(s, "protocol error", "unknown command"); - return; - } - - s->cb_accepted(s->l, s->fd, &s->ss, s->io); - /* we passed off s->io, so it does not need to be freed here */ - free(s); - break; - - case IO_TIMEOUT: - proxy_error(s, "timeout", NULL); - break; - - case IO_DISCONNECTED: - proxy_error(s, "disconnected", NULL); - break; - - case IO_ERROR: - proxy_error(s, "IO error", io_error(io)); - break; - - default: - fatalx("proxy_io()"); - } - -} - -static void -proxy_error(struct proxy_session *s, const char *reason, const char *extra) -{ - if (extra) - log_info("proxy %p event=closed address=%s reason=\"%s (%s)\"", - s, ss_to_text(&s->ss), reason, extra); - else - log_info("proxy %p event=closed address=%s reason=\"%s\"", - s, ss_to_text(&s->ss), reason); - - s->cb_dropped(s->l, s->fd, &s->ss); - io_free(s->io); - free(s); -} - -static int -proxy_header_validate(struct proxy_session *s) -{ - struct proxy_hdr_v2 *h = &s->hdr; - - if (memcmp(h->sig, pv2_signature, - sizeof(pv2_signature)) != 0) { - proxy_error(s, "protocol error", "invalid signature"); - return (-1); - } - - if ((h->ver_cmd >> 4) != 2) { - proxy_error(s, "protocol error", "invalid version"); - return (-1); - } - - switch (h->fam) { - case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC): - s->addr_total = 0; - break; - - case (PROXY_AF_INET << 4 | PROXY_TF_STREAM): - s->addr_total = sizeof(s->addr.ipv4); - break; - - case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM): - s->addr_total = sizeof(s->addr.ipv6); - break; - - case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM): - s->addr_total = sizeof(s->addr.un); - break; - - default: - proxy_error(s, "protocol error", "unsupported address family"); - return (-1); - } - - s->header_total = ntohs(h->len); - if (s->header_total > UINT16_MAX - sizeof(struct proxy_hdr_v2)) { - proxy_error(s, "protocol error", "header too long"); - return (-1); - } - s->header_total += sizeof(struct proxy_hdr_v2); - - if (s->header_total < sizeof(struct proxy_hdr_v2) + s->addr_total) { - proxy_error(s, "protocol error", "address info too short"); - return (-1); - } - - return 0; -} - -static int -proxy_translate_ss(struct proxy_session *s) -{ - struct sockaddr_in *sin = (struct sockaddr_in *) &s->ss; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &s->ss; - struct sockaddr_un *sun = (struct sockaddr_un *) &s->ss; - size_t sun_len; - - switch (s->hdr.fam) { - case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC): - /* unspec: only supported for local */ - proxy_error(s, "address translation", "UNSPEC family not " - "supported for PROXYing"); - return (-1); - - case (PROXY_AF_INET << 4 | PROXY_TF_STREAM): - memset(&s->ss, 0, sizeof(s->ss)); - sin->sin_family = AF_INET; - sin->sin_port = s->addr.ipv4.src_port; - sin->sin_addr.s_addr = s->addr.ipv4.src_addr; - break; - - case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM): - memset(&s->ss, 0, sizeof(s->ss)); - sin6->sin6_family = AF_INET6; - sin6->sin6_port = s->addr.ipv6.src_port; - memcpy(sin6->sin6_addr.s6_addr, s->addr.ipv6.src_addr, - sizeof(s->addr.ipv6.src_addr)); - break; - - case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM): - memset(&s->ss, 0, sizeof(s->ss)); - sun_len = strnlen(s->addr.un.src_addr, - sizeof(s->addr.un.src_addr)); - if (sun_len > sizeof(sun->sun_path)) { - proxy_error(s, "address translation", "Unix socket path" - " longer than supported"); - return (-1); - } - sun->sun_family = AF_UNIX; - memcpy(sun->sun_path, s->addr.un.src_addr, sun_len); - break; - - default: - fatalx("proxy_translate_ss()"); - } - - return 0; -} diff --git a/smtpd/queue.c b/smtpd/queue.c deleted file mode 100644 index 434e3647..00000000 --- a/smtpd/queue.c +++ /dev/null @@ -1,750 +0,0 @@ -/* $OpenBSD: queue.c,v 1.190 2020/04/22 11:35:34 eric Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static void queue_imsg(struct mproc *, struct imsg *); -static void queue_timeout(int, short, void *); -static void queue_bounce(struct envelope *, struct delivery_bounce *); -static void queue_shutdown(void); -static void queue_log(const struct envelope *, const char *, const char *); -static void queue_msgid_walk(int, short, void *); - - -static void -queue_imsg(struct mproc *p, struct imsg *imsg) -{ - struct delivery_bounce bounce; - struct msg_walkinfo *wi; - struct timeval tv; - struct bounce_req_msg *req_bounce; - struct envelope evp; - struct msg m; - const char *reason; - uint64_t reqid, evpid, holdq; - uint32_t msgid; - time_t nexttry; - size_t n_evp; - int fd, mta_ext, ret, v, flags, code; - - if (imsg == NULL) - queue_shutdown(); - - memset(&bounce, 0, sizeof(struct delivery_bounce)); - - switch (imsg->hdr.type) { - case IMSG_SMTP_MESSAGE_CREATE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - - ret = queue_message_create(&msgid); - - m_create(p, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); - m_add_id(p, reqid); - if (ret == 0) - m_add_int(p, 0); - else { - m_add_int(p, 1); - m_add_msgid(p, msgid); - } - m_close(p); - return; - - case IMSG_SMTP_MESSAGE_ROLLBACK: - m_msg(&m, imsg); - m_get_msgid(&m, &msgid); - m_end(&m); - - queue_message_delete(msgid); - - m_create(p_scheduler, IMSG_QUEUE_MESSAGE_ROLLBACK, - 0, 0, -1); - m_add_msgid(p_scheduler, msgid); - m_close(p_scheduler); - return; - - case IMSG_SMTP_MESSAGE_COMMIT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_msgid(&m, &msgid); - m_end(&m); - - ret = queue_message_commit(msgid); - - m_create(p, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, (ret == 0) ? 0 : 1); - m_close(p); - - if (ret) { - m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, - 0, 0, -1); - m_add_msgid(p_scheduler, msgid); - m_close(p_scheduler); - } - return; - - case IMSG_SMTP_MESSAGE_OPEN: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_msgid(&m, &msgid); - m_end(&m); - - fd = queue_message_fd_rw(msgid); - - m_create(p, IMSG_SMTP_MESSAGE_OPEN, 0, 0, fd); - m_add_id(p, reqid); - m_add_int(p, (fd == -1) ? 0 : 1); - m_close(p); - return; - - case IMSG_QUEUE_SMTP_SESSION: - bounce_fd(imsg->fd); - return; - - case IMSG_LKA_ENVELOPE_SUBMIT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_envelope(&m, &evp); - m_end(&m); - - if (evp.id == 0) - log_warnx("warn: imsg_queue_submit_envelope: evpid=0"); - if (evpid_to_msgid(evp.id) == 0) - log_warnx("warn: imsg_queue_submit_envelope: msgid=0, " - "evpid=%016"PRIx64, evp.id); - ret = queue_envelope_create(&evp); - m_create(p_pony, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); - m_add_id(p_pony, reqid); - if (ret == 0) - m_add_int(p_pony, 0); - else { - m_add_int(p_pony, 1); - m_add_evpid(p_pony, evp.id); - } - m_close(p_pony); - if (ret) { - m_create(p_scheduler, - IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); - m_add_envelope(p_scheduler, &evp); - m_close(p_scheduler); - } - return; - - case IMSG_LKA_ENVELOPE_COMMIT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_end(&m); - m_create(p_pony, IMSG_QUEUE_ENVELOPE_COMMIT, 0, 0, -1); - m_add_id(p_pony, reqid); - m_add_int(p_pony, 1); - m_close(p_pony); - return; - - case IMSG_SCHED_ENVELOPE_REMOVE: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_ACK, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_close(p_scheduler); - - /* already removed by scheduler */ - if (queue_envelope_load(evpid, &evp) == 0) - return; - - queue_log(&evp, "Remove", "Removed by administrator"); - queue_envelope_delete(evpid); - return; - - case IMSG_SCHED_ENVELOPE_EXPIRE: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_ACK, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_close(p_scheduler); - - /* already removed by scheduler*/ - if (queue_envelope_load(evpid, &evp) == 0) - return; - - bounce.type = B_FAILED; - envelope_set_errormsg(&evp, "Envelope expired"); - envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); - envelope_set_esc_code(&evp, ESC_DELIVERY_TIME_EXPIRED); - queue_bounce(&evp, &bounce); - queue_log(&evp, "Expire", "Envelope expired"); - queue_envelope_delete(evpid); - return; - - case IMSG_SCHED_ENVELOPE_BOUNCE: - CHECK_IMSG_DATA_SIZE(imsg, sizeof *req_bounce); - req_bounce = imsg->data; - evpid = req_bounce->evpid; - - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: bounce: failed to load envelope"); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_add_u32(p_scheduler, 0); /* not in-flight */ - m_close(p_scheduler); - return; - } - queue_bounce(&evp, &req_bounce->bounce); - evp.lastbounce = req_bounce->timestamp; - if (!queue_envelope_update(&evp)) - log_warnx("warn: could not update envelope %016"PRIx64, evpid); - return; - - case IMSG_SCHED_ENVELOPE_DELIVER: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: deliver: failed to load envelope"); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_add_u32(p_scheduler, 1); /* in-flight */ - m_close(p_scheduler); - return; - } - evp.lasttry = time(NULL); - m_create(p_pony, IMSG_QUEUE_DELIVER, 0, 0, -1); - m_add_envelope(p_pony, &evp); - m_close(p_pony); - return; - - case IMSG_SCHED_ENVELOPE_INJECT: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - bounce_add(evpid); - return; - - case IMSG_SCHED_ENVELOPE_TRANSFER: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: failed to load envelope"); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_add_u32(p_scheduler, 1); /* in-flight */ - m_close(p_scheduler); - return; - } - evp.lasttry = time(NULL); - m_create(p_pony, IMSG_QUEUE_TRANSFER, 0, 0, -1); - m_add_envelope(p_pony, &evp); - m_close(p_pony); - return; - - case IMSG_CTL_LIST_ENVELOPES: - if (imsg->hdr.len == sizeof imsg->hdr) { - m_forward(p_control, imsg); - return; - } - - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_get_int(&m, &flags); - m_get_time(&m, &nexttry); - m_end(&m); - - if (queue_envelope_load(evpid, &evp) == 0) - return; /* Envelope is gone, drop it */ - - /* - * XXX consistency: The envelope might already be on - * its way back to the scheduler. We need to detect - * this properly and report that state. - */ - if (flags & EF_INFLIGHT) { - /* - * Not exactly correct but pretty close: The - * value is not recorded on the envelope unless - * a tempfail occurs. - */ - evp.lasttry = nexttry; - } - - m_create(p_control, IMSG_CTL_LIST_ENVELOPES, - imsg->hdr.peerid, 0, -1); - m_add_int(p_control, flags); - m_add_time(p_control, nexttry); - m_add_envelope(p_control, &evp); - m_close(p_control); - return; - - case IMSG_MDA_OPEN_MESSAGE: - case IMSG_MTA_OPEN_MESSAGE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_msgid(&m, &msgid); - m_end(&m); - fd = queue_message_fd_r(msgid); - m_create(p, imsg->hdr.type, 0, 0, fd); - m_add_id(p, reqid); - m_close(p); - return; - - case IMSG_MDA_DELIVERY_OK: - case IMSG_MTA_DELIVERY_OK: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK) - m_get_int(&m, &mta_ext); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warn("queue: dsn: failed to load envelope"); - return; - } - if (evp.dsn_notify & DSN_SUCCESS) { - bounce.type = B_DELIVERED; - bounce.dsn_ret = evp.dsn_ret; - envelope_set_esc_class(&evp, ESC_STATUS_OK); - if (imsg->hdr.type == IMSG_MDA_DELIVERY_OK) - queue_bounce(&evp, &bounce); - else if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK && - (mta_ext & MTA_EXT_DSN) == 0) { - bounce.mta_without_dsn = 1; - queue_bounce(&evp, &bounce); - } - } - queue_envelope_delete(evpid); - m_create(p_scheduler, IMSG_QUEUE_DELIVERY_OK, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_close(p_scheduler); - return; - - case IMSG_MDA_DELIVERY_TEMPFAIL: - case IMSG_MTA_DELIVERY_TEMPFAIL: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_get_string(&m, &reason); - m_get_int(&m, &code); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: tempfail: failed to load envelope"); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_add_u32(p_scheduler, 1); /* in-flight */ - m_close(p_scheduler); - return; - } - envelope_set_errormsg(&evp, "%s", reason); - envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); - envelope_set_esc_code(&evp, code); - evp.retry++; - if (!queue_envelope_update(&evp)) - log_warnx("warn: could not update envelope %016"PRIx64, evpid); - m_create(p_scheduler, IMSG_QUEUE_DELIVERY_TEMPFAIL, 0, 0, -1); - m_add_envelope(p_scheduler, &evp); - m_close(p_scheduler); - return; - - case IMSG_MDA_DELIVERY_PERMFAIL: - case IMSG_MTA_DELIVERY_PERMFAIL: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_get_string(&m, &reason); - m_get_int(&m, &code); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: permfail: failed to load envelope"); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_add_u32(p_scheduler, 1); /* in-flight */ - m_close(p_scheduler); - return; - } - bounce.type = B_FAILED; - envelope_set_errormsg(&evp, "%s", reason); - envelope_set_esc_class(&evp, ESC_STATUS_PERMFAIL); - envelope_set_esc_code(&evp, code); - queue_bounce(&evp, &bounce); - queue_envelope_delete(evpid); - m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_close(p_scheduler); - return; - - case IMSG_MDA_DELIVERY_LOOP: - case IMSG_MTA_DELIVERY_LOOP: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: loop: failed to load envelope"); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_scheduler, evpid); - m_add_u32(p_scheduler, 1); /* in-flight */ - m_close(p_scheduler); - return; - } - envelope_set_errormsg(&evp, "%s", "Loop detected"); - envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); - envelope_set_esc_code(&evp, ESC_ROUTING_LOOP_DETECTED); - bounce.type = B_FAILED; - queue_bounce(&evp, &bounce); - queue_envelope_delete(evp.id); - m_create(p_scheduler, IMSG_QUEUE_DELIVERY_LOOP, 0, 0, -1); - m_add_evpid(p_scheduler, evp.id); - m_close(p_scheduler); - return; - - case IMSG_MTA_DELIVERY_HOLD: - case IMSG_MDA_DELIVERY_HOLD: - imsg->hdr.type = IMSG_QUEUE_HOLDQ_HOLD; - m_forward(p_scheduler, imsg); - return; - - case IMSG_MTA_SCHEDULE: - imsg->hdr.type = IMSG_QUEUE_ENVELOPE_SCHEDULE; - m_forward(p_scheduler, imsg); - return; - - case IMSG_MTA_HOLDQ_RELEASE: - case IMSG_MDA_HOLDQ_RELEASE: - m_msg(&m, imsg); - m_get_id(&m, &holdq); - m_get_int(&m, &v); - m_end(&m); - m_create(p_scheduler, IMSG_QUEUE_HOLDQ_RELEASE, 0, 0, -1); - if (imsg->hdr.type == IMSG_MTA_HOLDQ_RELEASE) - m_add_int(p_scheduler, D_MTA); - else - m_add_int(p_scheduler, D_MDA); - m_add_id(p_scheduler, holdq); - m_add_int(p_scheduler, v); - m_close(p_scheduler); - return; - - case IMSG_CTL_PAUSE_MDA: - case IMSG_CTL_PAUSE_MTA: - case IMSG_CTL_RESUME_MDA: - case IMSG_CTL_RESUME_MTA: - m_forward(p_scheduler, imsg); - return; - - case IMSG_CTL_VERBOSE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - log_trace_verbose(v); - return; - - case IMSG_CTL_PROFILE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - profiling = v; - return; - - case IMSG_CTL_DISCOVER_EVPID: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - if (queue_envelope_load(evpid, &evp) == 0) { - log_warnx("queue: discover: failed to load " - "envelope %016" PRIx64, evpid); - n_evp = 0; - m_compose(p_control, imsg->hdr.type, - imsg->hdr.peerid, 0, -1, - &n_evp, sizeof n_evp); - return; - } - - m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, - 0, 0, -1); - m_add_envelope(p_scheduler, &evp); - m_close(p_scheduler); - - m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, - 0, 0, -1); - m_add_msgid(p_scheduler, evpid_to_msgid(evpid)); - m_close(p_scheduler); - n_evp = 1; - m_compose(p_control, imsg->hdr.type, imsg->hdr.peerid, - 0, -1, &n_evp, sizeof n_evp); - return; - - case IMSG_CTL_DISCOVER_MSGID: - m_msg(&m, imsg); - m_get_msgid(&m, &msgid); - m_end(&m); - /* handle concurrent walk requests */ - wi = xcalloc(1, sizeof *wi); - wi->msgid = msgid; - wi->peerid = imsg->hdr.peerid; - evtimer_set(&wi->ev, queue_msgid_walk, wi); - tv.tv_sec = 0; - tv.tv_usec = 10; - evtimer_add(&wi->ev, &tv); - return; - } - - errx(1, "queue_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -static void -queue_msgid_walk(int fd, short event, void *arg) -{ - struct envelope evp; - struct timeval tv; - struct msg_walkinfo *wi = arg; - int r; - - r = queue_message_walk(&evp, wi->msgid, &wi->done, &wi->data); - if (r == -1) { - if (wi->n_evp) { - m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, - 0, 0, -1); - m_add_msgid(p_scheduler, wi->msgid); - m_close(p_scheduler); - } - - m_compose(p_control, IMSG_CTL_DISCOVER_MSGID, wi->peerid, 0, -1, - &wi->n_evp, sizeof wi->n_evp); - evtimer_del(&wi->ev); - free(wi); - return; - } - - if (r) { - m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, 0, 0, -1); - m_add_envelope(p_scheduler, &evp); - m_close(p_scheduler); - wi->n_evp += 1; - } - - tv.tv_sec = 0; - tv.tv_usec = 10; - evtimer_set(&wi->ev, queue_msgid_walk, wi); - evtimer_add(&wi->ev, &tv); -} - -static void -queue_bounce(struct envelope *e, struct delivery_bounce *d) -{ - struct envelope b; - - b = *e; - b.type = D_BOUNCE; - b.agent.bounce = *d; - b.retry = 0; - b.lasttry = 0; - b.creation = time(NULL); - b.ttl = 3600 * 24 * 7; - - if (e->dsn_notify & DSN_NEVER) - return; - - if (b.id == 0) - log_warnx("warn: queue_bounce: evpid=0"); - if (evpid_to_msgid(b.id) == 0) - log_warnx("warn: queue_bounce: msgid=0, evpid=%016"PRIx64, - b.id); - if (e->type == D_BOUNCE) { - log_warnx("warn: queue: double bounce!"); - } else if (e->sender.user[0] == '\0') { - log_warnx("warn: queue: no return path!"); - } else if (!queue_envelope_create(&b)) { - log_warnx("warn: queue: cannot bounce!"); - } else { - log_debug("debug: queue: bouncing evp:%016" PRIx64 - " as evp:%016" PRIx64, e->id, b.id); - - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); - m_add_envelope(p_scheduler, &b); - m_close(p_scheduler); - - m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1); - m_add_msgid(p_scheduler, evpid_to_msgid(b.id)); - m_close(p_scheduler); - - stat_increment("queue.bounce", 1); - } -} - -static void -queue_shutdown(void) -{ - log_debug("debug: queue agent exiting"); - queue_close(); - _exit(0); -} - -int -queue(void) -{ - struct passwd *pw; - struct timeval tv; - struct event ev_qload; - - purge_config(PURGE_EVERYTHING & ~PURGE_DISPATCHERS); - - if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL) - if ((pw = getpwnam(SMTPD_USER)) == NULL) - fatalx("unknown user " SMTPD_USER); - - env->sc_queue_flags |= QUEUE_EVPCACHE; - env->sc_queue_evpcache_size = 1024; - - if (chroot(PATH_SPOOL) == -1) - fatal("queue: chroot"); - if (chdir("/") == -1) - fatal("queue: chdir(\"/\")"); - - config_process(PROC_QUEUE); - - if (env->sc_queue_flags & QUEUE_COMPRESSION) - log_info("queue: queue compression enabled"); - - if (env->sc_queue_key) { - if (!crypto_setup(env->sc_queue_key, strlen(env->sc_queue_key))) - fatalx("crypto_setup: invalid key for queue encryption"); - log_info("queue: queue encryption enabled"); - } - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("queue: cannot drop privileges"); - - imsg_callback = queue_imsg; - event_init(); - - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - config_peer(PROC_PARENT); - config_peer(PROC_CONTROL); - config_peer(PROC_LKA); - config_peer(PROC_SCHEDULER); - config_peer(PROC_PONY); - - /* setup queue loading task */ - evtimer_set(&ev_qload, queue_timeout, &ev_qload); - tv.tv_sec = 0; - tv.tv_usec = 10; - evtimer_add(&ev_qload, &tv); - -#if HAVE_PLEDGE - if (pledge("stdio rpath wpath cpath flock recvfd sendfd", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} - -static void -queue_timeout(int fd, short event, void *p) -{ - static uint32_t msgid = 0; - struct envelope evp; - struct event *ev = p; - struct timeval tv; - int r; - - r = queue_envelope_walk(&evp); - if (r == -1) { - if (msgid) { - m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, - 0, 0, -1); - m_add_msgid(p_scheduler, msgid); - m_close(p_scheduler); - } - log_debug("debug: queue: done loading queue into scheduler"); - return; - } - - if (r) { - if (msgid && evpid_to_msgid(evp.id) != msgid) { - m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, - 0, 0, -1); - m_add_msgid(p_scheduler, msgid); - m_close(p_scheduler); - } - msgid = evpid_to_msgid(evp.id); - m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); - m_add_envelope(p_scheduler, &evp); - m_close(p_scheduler); - } - - tv.tv_sec = 0; - tv.tv_usec = 10; - evtimer_add(ev, &tv); -} - -static void -queue_log(const struct envelope *e, const char *prefix, const char *status) -{ - char rcpt[LINE_MAX]; - - (void)strlcpy(rcpt, "-", sizeof rcpt); - if (strcmp(e->rcpt.user, e->dest.user) || - strcmp(e->rcpt.domain, e->dest.domain)) - (void)snprintf(rcpt, sizeof rcpt, "%s@%s", - e->rcpt.user, e->rcpt.domain); - - log_info("%s: %s for %016" PRIx64 ": from=<%s@%s>, to=<%s@%s>, " - "rcpt=<%s>, delay=%s, stat=%s", - e->type == D_MDA ? "delivery" : "relay", - prefix, - e->id, e->sender.user, e->sender.domain, - e->dest.user, e->dest.domain, - rcpt, - duration_to_text(time(NULL) - e->creation), - status); -} diff --git a/smtpd/queue_backend.c b/smtpd/queue_backend.c deleted file mode 100644 index fa945f47..00000000 --- a/smtpd/queue_backend.c +++ /dev/null @@ -1,806 +0,0 @@ -/* $OpenBSD: queue_backend.c,v 1.66 2020/04/22 11:35:34 eric Exp $ */ - -/* - * Copyright (c) 2011 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static const char* envelope_validate(struct envelope *); - -extern struct queue_backend queue_backend_fs; -extern struct queue_backend queue_backend_null; -extern struct queue_backend queue_backend_proc; -extern struct queue_backend queue_backend_ram; - -static void queue_envelope_cache_add(struct envelope *); -static void queue_envelope_cache_update(struct envelope *); -static void queue_envelope_cache_del(uint64_t evpid); - -TAILQ_HEAD(evplst, envelope); - -static struct tree evpcache_tree; -static struct evplst evpcache_list; -static struct queue_backend *backend; - -static int (*handler_close)(void); -static int (*handler_message_create)(uint32_t *); -static int (*handler_message_commit)(uint32_t, const char*); -static int (*handler_message_delete)(uint32_t); -static int (*handler_message_fd_r)(uint32_t); -static int (*handler_envelope_create)(uint32_t, const char *, size_t, uint64_t *); -static int (*handler_envelope_delete)(uint64_t); -static int (*handler_envelope_update)(uint64_t, const char *, size_t); -static int (*handler_envelope_load)(uint64_t, char *, size_t); -static int (*handler_envelope_walk)(uint64_t *, char *, size_t); -static int (*handler_message_walk)(uint64_t *, char *, size_t, - uint32_t, int *, void **); - -#ifdef QUEUE_PROFILING - -static struct { - struct timespec t0; - const char *name; -} profile; - -static inline void profile_enter(const char *name) -{ - if ((profiling & PROFILE_QUEUE) == 0) - return; - - profile.name = name; - clock_gettime(CLOCK_MONOTONIC, &profile.t0); -} - -static inline void profile_leave(void) -{ - struct timespec t1, dt; - - if ((profiling & PROFILE_QUEUE) == 0) - return; - - clock_gettime(CLOCK_MONOTONIC, &t1); - timespecsub(&t1, &profile.t0, &dt); - log_debug("profile-queue: %s %lld.%09ld", profile.name, - (long long)dt.tv_sec, dt.tv_nsec); -} -#else -#define profile_enter(x) do {} while (0) -#define profile_leave() do {} while (0) -#endif - -static int -queue_message_path(uint32_t msgid, char *buf, size_t len) -{ - return bsnprintf(buf, len, "%s/%08"PRIx32, PATH_TEMPORARY, msgid); -} - -int -queue_init(const char *name, int server) -{ - struct passwd *pwq; - struct group *gr; - int r; - - pwq = getpwnam(SMTPD_QUEUE_USER); - if (pwq == NULL) - errx(1, "unknown user %s", SMTPD_QUEUE_USER); - - gr = getgrnam(SMTPD_QUEUE_GROUP); - if (gr == NULL) - errx(1, "unknown group %s", SMTPD_QUEUE_GROUP); - - tree_init(&evpcache_tree); - TAILQ_INIT(&evpcache_list); - - if (!strcmp(name, "fs")) - backend = &queue_backend_fs; - else if (!strcmp(name, "null")) - backend = &queue_backend_null; - else if (!strcmp(name, "ram")) - backend = &queue_backend_ram; - else - backend = &queue_backend_proc; - - if (server) { - if (ckdir(PATH_SPOOL, 0711, 0, 0, 1) == 0) - errx(1, "error in spool directory setup"); - if (ckdir(PATH_SPOOL PATH_OFFLINE, 0770, 0, gr->gr_gid, 1) == 0) - errx(1, "error in offline directory setup"); - if (ckdir(PATH_SPOOL PATH_PURGE, 0700, pwq->pw_uid, 0, 1) == 0) - errx(1, "error in purge directory setup"); - - mvpurge(PATH_SPOOL PATH_TEMPORARY, PATH_SPOOL PATH_PURGE); - - if (ckdir(PATH_SPOOL PATH_TEMPORARY, 0700, pwq->pw_uid, 0, 1) == 0) - errx(1, "error in purge directory setup"); - } - - r = backend->init(pwq, server, name); - - log_trace(TRACE_QUEUE, "queue-backend: queue_init(%d) -> %d", server, r); - - return (r); -} - -int -queue_close(void) -{ - if (handler_close) - return (handler_close()); - - return (1); -} - -int -queue_message_create(uint32_t *msgid) -{ - int r; - - profile_enter("queue_message_create"); - r = handler_message_create(msgid); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_message_create() -> %d (%08"PRIx32")", - r, *msgid); - - return (r); -} - -int -queue_message_delete(uint32_t msgid) -{ - char msgpath[PATH_MAX]; - uint64_t evpid; - void *iter; - int r; - - profile_enter("queue_message_delete"); - r = handler_message_delete(msgid); - profile_leave(); - - /* in case the message is incoming */ - queue_message_path(msgid, msgpath, sizeof(msgpath)); - unlink(msgpath); - - /* remove remaining envelopes from the cache if any (on rollback) */ - evpid = msgid_to_evpid(msgid); - for (;;) { - iter = NULL; - if (!tree_iterfrom(&evpcache_tree, &iter, evpid, &evpid, NULL)) - break; - if (evpid_to_msgid(evpid) != msgid) - break; - queue_envelope_cache_del(evpid); - } - - log_trace(TRACE_QUEUE, - "queue-backend: queue_message_delete(%08"PRIx32") -> %d", msgid, r); - - return (r); -} - -int -queue_message_commit(uint32_t msgid) -{ - int r; - char msgpath[PATH_MAX]; - char tmppath[PATH_MAX]; - FILE *ifp = NULL; - FILE *ofp = NULL; - - profile_enter("queue_message_commit"); - - queue_message_path(msgid, msgpath, sizeof(msgpath)); - - if (env->sc_queue_flags & QUEUE_COMPRESSION) { - bsnprintf(tmppath, sizeof tmppath, "%s.comp", msgpath); - ifp = fopen(msgpath, "r"); - ofp = fopen(tmppath, "w+"); - if (ifp == NULL || ofp == NULL) - goto err; - if (!compress_file(ifp, ofp)) - goto err; - fclose(ifp); - fclose(ofp); - ifp = NULL; - ofp = NULL; - - if (rename(tmppath, msgpath) == -1) { - if (errno == ENOSPC) - return (0); - unlink(tmppath); - log_warn("rename"); - return (0); - } - } - - if (env->sc_queue_flags & QUEUE_ENCRYPTION) { - bsnprintf(tmppath, sizeof tmppath, "%s.enc", msgpath); - ifp = fopen(msgpath, "r"); - ofp = fopen(tmppath, "w+"); - if (ifp == NULL || ofp == NULL) - goto err; - if (!crypto_encrypt_file(ifp, ofp)) - goto err; - fclose(ifp); - fclose(ofp); - ifp = NULL; - ofp = NULL; - - if (rename(tmppath, msgpath) == -1) { - if (errno == ENOSPC) - return (0); - unlink(tmppath); - log_warn("rename"); - return (0); - } - } - - r = handler_message_commit(msgid, msgpath); - profile_leave(); - - /* in case it's not done by the backend */ - unlink(msgpath); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_message_commit(%08"PRIx32") -> %d", - msgid, r); - - return (r); - -err: - if (ifp) - fclose(ifp); - if (ofp) - fclose(ofp); - return 0; -} - -int -queue_message_fd_r(uint32_t msgid) -{ - int fdin = -1, fdout = -1, fd = -1; - FILE *ifp = NULL; - FILE *ofp = NULL; - - profile_enter("queue_message_fd_r"); - fdin = handler_message_fd_r(msgid); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_message_fd_r(%08"PRIx32") -> %d", msgid, fdin); - - if (fdin == -1) - return (-1); - - if (env->sc_queue_flags & QUEUE_ENCRYPTION) { - if ((fdout = mktmpfile()) == -1) - goto err; - if ((fd = dup(fdout)) == -1) - goto err; - if ((ifp = fdopen(fdin, "r")) == NULL) - goto err; - fdin = fd; - fd = -1; - if ((ofp = fdopen(fdout, "w+")) == NULL) - goto err; - - if (!crypto_decrypt_file(ifp, ofp)) - goto err; - - fclose(ifp); - ifp = NULL; - fclose(ofp); - ofp = NULL; - lseek(fdin, SEEK_SET, 0); - } - - if (env->sc_queue_flags & QUEUE_COMPRESSION) { - if ((fdout = mktmpfile()) == -1) - goto err; - if ((fd = dup(fdout)) == -1) - goto err; - if ((ifp = fdopen(fdin, "r")) == NULL) - goto err; - fdin = fd; - fd = -1; - if ((ofp = fdopen(fdout, "w+")) == NULL) - goto err; - - if (!uncompress_file(ifp, ofp)) - goto err; - - fclose(ifp); - ifp = NULL; - fclose(ofp); - ofp = NULL; - lseek(fdin, SEEK_SET, 0); - } - - return (fdin); - -err: - if (fd != -1) - close(fd); - if (fdin != -1) - close(fdin); - if (fdout != -1) - close(fdout); - if (ifp) - fclose(ifp); - if (ofp) - fclose(ofp); - return -1; -} - -int -queue_message_fd_rw(uint32_t msgid) -{ - char buf[PATH_MAX]; - - queue_message_path(msgid, buf, sizeof(buf)); - - return open(buf, O_RDWR | O_CREAT | O_EXCL, 0600); -} - -static int -queue_envelope_dump_buffer(struct envelope *ep, char *evpbuf, size_t evpbufsize) -{ - char *evp; - size_t evplen; - size_t complen; - char compbuf[sizeof(struct envelope)]; - size_t enclen; - char encbuf[sizeof(struct envelope)]; - - evp = evpbuf; - evplen = envelope_dump_buffer(ep, evpbuf, evpbufsize); - if (evplen == 0) - return (0); - - if (env->sc_queue_flags & QUEUE_COMPRESSION) { - complen = compress_chunk(evp, evplen, compbuf, sizeof compbuf); - if (complen == 0) - return (0); - evp = compbuf; - evplen = complen; - } - - if (env->sc_queue_flags & QUEUE_ENCRYPTION) { - enclen = crypto_encrypt_buffer(evp, evplen, encbuf, sizeof encbuf); - if (enclen == 0) - return (0); - evp = encbuf; - evplen = enclen; - } - - memmove(evpbuf, evp, evplen); - - return (evplen); -} - -static int -queue_envelope_load_buffer(struct envelope *ep, char *evpbuf, size_t evpbufsize) -{ - char *evp; - size_t evplen; - char compbuf[sizeof(struct envelope)]; - size_t complen; - char encbuf[sizeof(struct envelope)]; - size_t enclen; - - evp = evpbuf; - evplen = evpbufsize; - - if (env->sc_queue_flags & QUEUE_ENCRYPTION) { - enclen = crypto_decrypt_buffer(evp, evplen, encbuf, sizeof encbuf); - if (enclen == 0) - return (0); - evp = encbuf; - evplen = enclen; - } - - if (env->sc_queue_flags & QUEUE_COMPRESSION) { - complen = uncompress_chunk(evp, evplen, compbuf, sizeof compbuf); - if (complen == 0) - return (0); - evp = compbuf; - evplen = complen; - } - - return (envelope_load_buffer(ep, evp, evplen)); -} - -static void -queue_envelope_cache_add(struct envelope *e) -{ - struct envelope *cached; - - while (tree_count(&evpcache_tree) >= env->sc_queue_evpcache_size) - queue_envelope_cache_del(TAILQ_LAST(&evpcache_list, evplst)->id); - - cached = xcalloc(1, sizeof *cached); - *cached = *e; - TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); - tree_xset(&evpcache_tree, e->id, cached); - stat_increment("queue.evpcache.size", 1); -} - -static void -queue_envelope_cache_update(struct envelope *e) -{ - struct envelope *cached; - - if ((cached = tree_get(&evpcache_tree, e->id)) == NULL) { - queue_envelope_cache_add(e); - stat_increment("queue.evpcache.update.missed", 1); - } else { - TAILQ_REMOVE(&evpcache_list, cached, entry); - *cached = *e; - TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); - stat_increment("queue.evpcache.update.hit", 1); - } -} - -static void -queue_envelope_cache_del(uint64_t evpid) -{ - struct envelope *cached; - - if ((cached = tree_pop(&evpcache_tree, evpid)) == NULL) - return; - - TAILQ_REMOVE(&evpcache_list, cached, entry); - free(cached); - stat_decrement("queue.evpcache.size", 1); -} - -int -queue_envelope_create(struct envelope *ep) -{ - int r; - char evpbuf[sizeof(struct envelope)]; - size_t evplen; - uint64_t evpid; - uint32_t msgid; - - ep->creation = time(NULL); - evplen = queue_envelope_dump_buffer(ep, evpbuf, sizeof evpbuf); - if (evplen == 0) - return (0); - - evpid = ep->id; - msgid = evpid_to_msgid(evpid); - - profile_enter("queue_envelope_create"); - r = handler_envelope_create(msgid, evpbuf, evplen, &ep->id); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_envelope_create(%016"PRIx64", %zu) -> %d (%016"PRIx64")", - evpid, evplen, r, ep->id); - - if (!r) { - ep->creation = 0; - ep->id = 0; - } - - if (r && env->sc_queue_flags & QUEUE_EVPCACHE) - queue_envelope_cache_add(ep); - - return (r); -} - -int -queue_envelope_delete(uint64_t evpid) -{ - int r; - - if (env->sc_queue_flags & QUEUE_EVPCACHE) - queue_envelope_cache_del(evpid); - - profile_enter("queue_envelope_delete"); - r = handler_envelope_delete(evpid); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_envelope_delete(%016"PRIx64") -> %d", - evpid, r); - - return (r); -} - -int -queue_envelope_load(uint64_t evpid, struct envelope *ep) -{ - const char *e; - char evpbuf[sizeof(struct envelope)]; - size_t evplen; - struct envelope *cached; - - if ((env->sc_queue_flags & QUEUE_EVPCACHE) && - (cached = tree_get(&evpcache_tree, evpid))) { - *ep = *cached; - stat_increment("queue.evpcache.load.hit", 1); - return (1); - } - - ep->id = evpid; - profile_enter("queue_envelope_load"); - evplen = handler_envelope_load(ep->id, evpbuf, sizeof evpbuf); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_envelope_load(%016"PRIx64") -> %zu", - evpid, evplen); - - if (evplen == 0) - return (0); - - if (queue_envelope_load_buffer(ep, evpbuf, evplen)) { - if ((e = envelope_validate(ep)) == NULL) { - ep->id = evpid; - if (env->sc_queue_flags & QUEUE_EVPCACHE) { - queue_envelope_cache_add(ep); - stat_increment("queue.evpcache.load.missed", 1); - } - return (1); - } - log_warnx("warn: invalid envelope %016" PRIx64 ": %s", - evpid, e); - } - return (0); -} - -int -queue_envelope_update(struct envelope *ep) -{ - char evpbuf[sizeof(struct envelope)]; - size_t evplen; - int r; - - evplen = queue_envelope_dump_buffer(ep, evpbuf, sizeof evpbuf); - if (evplen == 0) - return (0); - - profile_enter("queue_envelope_update"); - r = handler_envelope_update(ep->id, evpbuf, evplen); - profile_leave(); - - if (r && env->sc_queue_flags & QUEUE_EVPCACHE) - queue_envelope_cache_update(ep); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_envelope_update(%016"PRIx64") -> %d", - ep->id, r); - - return (r); -} - -int -queue_message_walk(struct envelope *ep, uint32_t msgid, int *done, void **data) -{ - char evpbuf[sizeof(struct envelope)]; - uint64_t evpid; - int r; - const char *e; - - profile_enter("queue_message_walk"); - r = handler_message_walk(&evpid, evpbuf, sizeof evpbuf, - msgid, done, data); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_message_walk() -> %d (%016"PRIx64")", - r, evpid); - - if (r == -1) - return (r); - - if (r && queue_envelope_load_buffer(ep, evpbuf, (size_t)r)) { - if ((e = envelope_validate(ep)) == NULL) { - ep->id = evpid; - /* - * do not cache the envelope here, while discovering - * envelopes one could re-run discover on already - * scheduled envelopes which leads to triggering of - * strict checks in caching. Envelopes could anyway - * be loaded from backend if it isn't cached. - */ - return (1); - } - log_warnx("warn: invalid envelope %016" PRIx64 ": %s", - evpid, e); - } - return (0); -} - -int -queue_envelope_walk(struct envelope *ep) -{ - const char *e; - uint64_t evpid; - char evpbuf[sizeof(struct envelope)]; - int r; - - profile_enter("queue_envelope_walk"); - r = handler_envelope_walk(&evpid, evpbuf, sizeof evpbuf); - profile_leave(); - - log_trace(TRACE_QUEUE, - "queue-backend: queue_envelope_walk() -> %d (%016"PRIx64")", - r, evpid); - - if (r == -1) - return (r); - - if (r && queue_envelope_load_buffer(ep, evpbuf, (size_t)r)) { - if ((e = envelope_validate(ep)) == NULL) { - ep->id = evpid; - if (env->sc_queue_flags & QUEUE_EVPCACHE) - queue_envelope_cache_add(ep); - return (1); - } - log_warnx("warn: invalid envelope %016" PRIx64 ": %s", - evpid, e); - } - return (0); -} - -uint32_t -queue_generate_msgid(void) -{ - uint32_t msgid; - - while ((msgid = arc4random()) == 0) - ; - - return msgid; -} - -uint64_t -queue_generate_evpid(uint32_t msgid) -{ - uint32_t rnd; - uint64_t evpid; - - while ((rnd = arc4random()) == 0) - ; - - evpid = msgid; - evpid <<= 32; - evpid |= rnd; - - return evpid; -} - -static const char* -envelope_validate(struct envelope *ep) -{ - if (ep->version != SMTPD_ENVELOPE_VERSION) - return "version mismatch"; - - if (memchr(ep->helo, '\0', sizeof(ep->helo)) == NULL) - return "invalid helo"; - if (ep->helo[0] == '\0') - return "empty helo"; - - if (memchr(ep->hostname, '\0', sizeof(ep->hostname)) == NULL) - return "invalid hostname"; - if (ep->hostname[0] == '\0') - return "empty hostname"; - - if (memchr(ep->errorline, '\0', sizeof(ep->errorline)) == NULL) - return "invalid error line"; - - if (dict_get(env->sc_dispatchers, ep->dispatcher) == NULL) - return "unknown dispatcher"; - - return NULL; -} - -void -queue_api_on_close(int(*cb)(void)) -{ - handler_close = cb; -} - -void -queue_api_on_message_create(int(*cb)(uint32_t *)) -{ - handler_message_create = cb; -} - -void -queue_api_on_message_commit(int(*cb)(uint32_t, const char *)) -{ - handler_message_commit = cb; -} - -void -queue_api_on_message_delete(int(*cb)(uint32_t)) -{ - handler_message_delete = cb; -} - -void -queue_api_on_message_fd_r(int(*cb)(uint32_t)) -{ - handler_message_fd_r = cb; -} - -void -queue_api_on_envelope_create(int(*cb)(uint32_t, const char *, size_t, uint64_t *)) -{ - handler_envelope_create = cb; -} - -void -queue_api_on_envelope_delete(int(*cb)(uint64_t)) -{ - handler_envelope_delete = cb; -} - -void -queue_api_on_envelope_update(int(*cb)(uint64_t, const char *, size_t)) -{ - handler_envelope_update = cb; -} - -void -queue_api_on_envelope_load(int(*cb)(uint64_t, char *, size_t)) -{ - handler_envelope_load = cb; -} - -void -queue_api_on_envelope_walk(int(*cb)(uint64_t *, char *, size_t)) -{ - handler_envelope_walk = cb; -} - -void -queue_api_on_message_walk(int(*cb)(uint64_t *, char *, size_t, - uint32_t, int *, void **)) -{ - handler_message_walk = cb; -} diff --git a/smtpd/queue_fs.c b/smtpd/queue_fs.c deleted file mode 100644 index 097ba1e2..00000000 --- a/smtpd/queue_fs.c +++ /dev/null @@ -1,695 +0,0 @@ -/* $OpenBSD: queue_fs.c,v 1.20 2020/02/25 17:03:13 millert Exp $ */ - -/* - * Copyright (c) 2011 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#if HAVE_SYS_MOUNT_H -#include -#endif -#include -#include -#include -#include -#ifdef HAVE_SYS_STATFS_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define PATH_QUEUE "/queue" -#define PATH_INCOMING "/incoming" -#define PATH_EVPTMP PATH_INCOMING "/envelope.tmp" -#define PATH_MESSAGE "/message" - -/* percentage of remaining space / inodes required to accept new messages */ -#define MINSPACE 5 -#define MININODES 5 - -struct qwalk { - FTS *fts; - int depth; -}; - -static int fsqueue_check_space(void); -static void fsqueue_envelope_path(uint64_t, char *, size_t); -static void fsqueue_envelope_incoming_path(uint64_t, char *, size_t); -static int fsqueue_envelope_dump(char *, const char *, size_t, int, int); -static void fsqueue_message_path(uint32_t, char *, size_t); -static void fsqueue_message_incoming_path(uint32_t, char *, size_t); -static void *fsqueue_qwalk_new(void); -static int fsqueue_qwalk(void *, uint64_t *); -static void fsqueue_qwalk_close(void *); - -struct tree evpcount; -static struct timespec startup; - -#define REF (int*)0xf00 - -static int -queue_fs_message_create(uint32_t *msgid) -{ - char rootdir[PATH_MAX]; - struct stat sb; - - if (!fsqueue_check_space()) - return 0; - -again: - *msgid = queue_generate_msgid(); - - /* prevent possible collision later when moving to Q_QUEUE */ - fsqueue_message_path(*msgid, rootdir, sizeof(rootdir)); - if (stat(rootdir, &sb) != -1) - goto again; - - /* we hit an unexpected error, temporarily fail */ - if (errno != ENOENT) { - *msgid = 0; - return 0; - } - - fsqueue_message_incoming_path(*msgid, rootdir, sizeof(rootdir)); - if (mkdir(rootdir, 0700) == -1) { - if (errno == EEXIST) - goto again; - - if (errno == ENOSPC) { - *msgid = 0; - return 0; - } - - log_warn("warn: queue-fs: mkdir"); - *msgid = 0; - return 0; - } - - return (1); -} - -static int -queue_fs_message_commit(uint32_t msgid, const char *path) -{ - char incomingdir[PATH_MAX]; - char queuedir[PATH_MAX]; - char msgdir[PATH_MAX]; - char msgpath[PATH_MAX]; - - /* before-first, move the message content in the incoming directory */ - fsqueue_message_incoming_path(msgid, msgpath, sizeof(msgpath)); - if (strlcat(msgpath, PATH_MESSAGE, sizeof(msgpath)) - >= sizeof(msgpath)) - return (0); - if (rename(path, msgpath) == -1) - return (0); - - fsqueue_message_incoming_path(msgid, incomingdir, sizeof(incomingdir)); - fsqueue_message_path(msgid, msgdir, sizeof(msgdir)); - if (strlcpy(queuedir, msgdir, sizeof(queuedir)) - >= sizeof(queuedir)) - return (0); - - /* first attempt to rename */ - if (rename(incomingdir, msgdir) == 0) - return 1; - if (errno == ENOSPC) - return 0; - if (errno != ENOENT) { - log_warn("warn: queue-fs: rename"); - return 0; - } - - /* create the bucket */ - *strrchr(queuedir, '/') = '\0'; - if (mkdir(queuedir, 0700) == -1) { - if (errno == ENOSPC) - return 0; - if (errno != EEXIST) { - log_warn("warn: queue-fs: mkdir"); - return 0; - } - } - - /* rename */ - if (rename(incomingdir, msgdir) == -1) { - if (errno == ENOSPC) - return 0; - log_warn("warn: queue-fs: rename"); - return 0; - } - - return 1; -} - -static int -queue_fs_message_fd_r(uint32_t msgid) -{ - int fd; - char path[PATH_MAX]; - - fsqueue_message_path(msgid, path, sizeof(path)); - if (strlcat(path, PATH_MESSAGE, sizeof(path)) - >= sizeof(path)) - return -1; - - if ((fd = open(path, O_RDONLY)) == -1) { - log_warn("warn: queue-fs: open"); - return -1; - } - - return fd; -} - -static int -queue_fs_message_delete(uint32_t msgid) -{ - char path[PATH_MAX]; - struct stat sb; - - fsqueue_message_incoming_path(msgid, path, sizeof(path)); - if (stat(path, &sb) == -1) - fsqueue_message_path(msgid, path, sizeof(path)); - - if (rmtree(path, 0) == -1) - log_warn("warn: queue-fs: rmtree"); - - tree_pop(&evpcount, msgid); - - return 1; -} - -static int -queue_fs_envelope_create(uint32_t msgid, const char *buf, size_t len, - uint64_t *evpid) -{ - char path[PATH_MAX]; - int queued = 0, i, r = 0, *n; - struct stat sb; - - if (msgid == 0) { - log_warnx("warn: queue-fs: msgid=0, evpid=%016"PRIx64, *evpid); - goto done; - } - - fsqueue_message_incoming_path(msgid, path, sizeof(path)); - if (stat(path, &sb) == -1) - queued = 1; - - for (i = 0; i < 20; i ++) { - *evpid = queue_generate_evpid(msgid); - if (queued) - fsqueue_envelope_path(*evpid, path, sizeof(path)); - else - fsqueue_envelope_incoming_path(*evpid, path, - sizeof(path)); - - if ((r = fsqueue_envelope_dump(path, buf, len, 0, 0)) != 0) - goto done; - } - r = 0; - log_warnx("warn: queue-fs: could not allocate evpid"); - -done: - if (r) { - n = tree_pop(&evpcount, msgid); - if (n == NULL) - n = REF; - n += 1; - tree_xset(&evpcount, msgid, n); - } - return (r); -} - -static int -queue_fs_envelope_load(uint64_t evpid, char *buf, size_t len) -{ - char pathname[PATH_MAX]; - FILE *fp; - size_t r; - - fsqueue_envelope_path(evpid, pathname, sizeof(pathname)); - - fp = fopen(pathname, "r"); - if (fp == NULL) { - if (errno != ENOENT && errno != ENFILE) - log_warn("warn: queue-fs: fopen"); - return 0; - } - - r = fread(buf, 1, len, fp); - if (r) { - if (r == len) { - log_warn("warn: queue-fs: too large"); - r = 0; - } - else - buf[r] = '\0'; - } - fclose(fp); - - return (r); -} - -static int -queue_fs_envelope_update(uint64_t evpid, const char *buf, size_t len) -{ - char dest[PATH_MAX]; - - fsqueue_envelope_path(evpid, dest, sizeof(dest)); - - return (fsqueue_envelope_dump(dest, buf, len, 1, 1)); -} - -static int -queue_fs_envelope_delete(uint64_t evpid) -{ - char pathname[PATH_MAX]; - uint32_t msgid; - int *n; - - fsqueue_envelope_path(evpid, pathname, sizeof(pathname)); - if (unlink(pathname) == -1) - if (errno != ENOENT) - return 0; - - msgid = evpid_to_msgid(evpid); - n = tree_pop(&evpcount, msgid); - n -= 1; - - if (n - REF == 0) - queue_fs_message_delete(msgid); - else - tree_xset(&evpcount, msgid, n); - - return (1); -} - -static int -queue_fs_message_walk(uint64_t *evpid, char *buf, size_t len, - uint32_t msgid, int *done, void **data) -{ - struct dirent *dp; - DIR *dir = *data; - char path[PATH_MAX]; - char msgid_str[9]; - char *tmp; - int r, *n; - - if (*done) - return (-1); - - if (!bsnprintf(path, sizeof path, "%s/%02x/%08x", - PATH_QUEUE, (msgid & 0xff000000) >> 24, msgid)) - fatalx("queue_fs_message_walk: path does not fit buffer"); - - if (dir == NULL) { - if ((dir = opendir(path)) == NULL) { - log_warn("warn: queue_fs: opendir: %s", path); - *done = 1; - return (-1); - } - - *data = dir; - } - - (void)snprintf(msgid_str, sizeof msgid_str, "%08" PRIx32, msgid); - while ((dp = readdir(dir)) != NULL) { -#if defined(HAVE_STRUCT_DIR_D_TYPE) - if (dp->d_type != DT_REG) - continue; -#endif - - /* ignore files other than envelopes */ - if (strlen(dp->d_name) != 16 || - strncmp(dp->d_name, msgid_str, 8)) - continue; - - tmp = NULL; - *evpid = strtoull(dp->d_name, &tmp, 16); - if (tmp && *tmp != '\0') { - log_debug("debug: fsqueue: bogus file %s", dp->d_name); - continue; - } - - memset(buf, 0, len); - r = queue_fs_envelope_load(*evpid, buf, len); - if (r) { - n = tree_pop(&evpcount, msgid); - if (n == NULL) - n = REF; - - n += 1; - tree_xset(&evpcount, msgid, n); - } - - return (r); - } - - (void)closedir(dir); - *done = 1; - return (-1); -} - -static int -queue_fs_envelope_walk(uint64_t *evpid, char *buf, size_t len) -{ - static int done = 0; - static void *hdl = NULL; - int r, *n; - uint32_t msgid; - - if (done) - return (-1); - - if (hdl == NULL) - hdl = fsqueue_qwalk_new(); - - if (fsqueue_qwalk(hdl, evpid)) { - memset(buf, 0, len); - r = queue_fs_envelope_load(*evpid, buf, len); - if (r) { - msgid = evpid_to_msgid(*evpid); - n = tree_pop(&evpcount, msgid); - if (n == NULL) - n = REF; - n += 1; - tree_xset(&evpcount, msgid, n); - } - return (r); - } - - fsqueue_qwalk_close(hdl); - done = 1; - return (-1); -} - -static int -fsqueue_check_space(void) -{ -#ifdef __OpenBSD__ - struct statfs buf; - uint64_t used; - uint64_t total; - - if (statfs(PATH_QUEUE, &buf) == -1) { - log_warn("warn: queue-fs: statfs"); - return 0; - } - - /* - * f_bfree and f_ffree is not set on all filesystems. - * They could be signed or unsigned integers. - * Some systems will set them to 0, others will set them to -1. - */ - if (buf.f_bfree == 0 || buf.f_ffree == 0 || - (int64_t)buf.f_bfree == -1 || (int64_t)buf.f_ffree == -1) - return 1; - - used = buf.f_blocks - buf.f_bfree; - total = buf.f_bavail + used; - if (total != 0) - used = (float)used / (float)total * 100; - else - used = 100; - if (100 - used < MINSPACE) { - log_warnx("warn: not enough disk space: %llu%% left", - (unsigned long long) 100 - used); - log_warnx("warn: temporarily rejecting messages"); - return 0; - } - - used = buf.f_files - buf.f_ffree; - total = buf.f_favail + used; - if (total != 0) - used = (float)used / (float)total * 100; - else - used = 100; - if (100 - used < MININODES) { - log_warnx("warn: not enough inodes: %llu%% left", - (unsigned long long) 100 - used); - log_warnx("warn: temporarily rejecting messages"); - return 0; - } -#endif - return 1; -} - -static void -fsqueue_envelope_path(uint64_t evpid, char *buf, size_t len) -{ - if (!bsnprintf(buf, len, "%s/%02x/%08x/%016" PRIx64, - PATH_QUEUE, - (evpid_to_msgid(evpid) & 0xff000000) >> 24, - evpid_to_msgid(evpid), - evpid)) - fatalx("fsqueue_envelope_path: path does not fit buffer"); -} - -static void -fsqueue_envelope_incoming_path(uint64_t evpid, char *buf, size_t len) -{ - if (!bsnprintf(buf, len, "%s/%08x/%016" PRIx64, - PATH_INCOMING, - evpid_to_msgid(evpid), - evpid)) - fatalx("fsqueue_envelope_incoming_path: path does not fit buffer"); -} - -static int -fsqueue_envelope_dump(char *dest, const char *evpbuf, size_t evplen, - int do_atomic, int do_sync) -{ - const char *path = do_atomic ? PATH_EVPTMP : dest; - FILE *fp = NULL; - int fd; - size_t w; - - if ((fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600)) == -1) { - log_warn("warn: queue-fs: open"); - goto tempfail; - } - - if ((fp = fdopen(fd, "w")) == NULL) { - log_warn("warn: queue-fs: fdopen"); - goto tempfail; - } - - w = fwrite(evpbuf, 1, evplen, fp); - if (w < evplen) { - log_warn("warn: queue-fs: short write"); - goto tempfail; - } - if (fflush(fp)) { - log_warn("warn: queue-fs: fflush"); - goto tempfail; - } - if (do_sync && fsync(fileno(fp))) { - log_warn("warn: queue-fs: fsync"); - goto tempfail; - } - if (fclose(fp) != 0) { - log_warn("warn: queue-fs: fclose"); - fp = NULL; - goto tempfail; - } - fp = NULL; - fd = -1; - - if (do_atomic && rename(path, dest) == -1) { - log_warn("warn: queue-fs: rename"); - goto tempfail; - } - return (1); - -tempfail: - if (fp) - fclose(fp); - else if (fd != -1) - close(fd); - if (unlink(path) == -1) - log_warn("warn: queue-fs: unlink"); - return (0); -} - -static void -fsqueue_message_path(uint32_t msgid, char *buf, size_t len) -{ - if (!bsnprintf(buf, len, "%s/%02x/%08x", - PATH_QUEUE, - (msgid & 0xff000000) >> 24, - msgid)) - fatalx("fsqueue_message_path: path does not fit buffer"); -} - -static void -fsqueue_message_incoming_path(uint32_t msgid, char *buf, size_t len) -{ - if (!bsnprintf(buf, len, "%s/%08x", - PATH_INCOMING, - msgid)) - fatalx("fsqueue_message_incoming_path: path does not fit buffer"); -} - -static void * -fsqueue_qwalk_new(void) -{ - char path[PATH_MAX]; - char * const path_argv[] = { path, NULL }; - struct qwalk *q; - - q = xcalloc(1, sizeof(*q)); - (void)strlcpy(path, PATH_QUEUE, sizeof(path)); - q->fts = fts_open(path_argv, - FTS_PHYSICAL | FTS_NOCHDIR, NULL); - - if (q->fts == NULL) - err(1, "fsqueue_qwalk_new: fts_open: %s", path); - - return (q); -} - -static void -fsqueue_qwalk_close(void *hdl) -{ - struct qwalk *q = hdl; - - fts_close(q->fts); - - free(q); -} - -static int -fsqueue_qwalk(void *hdl, uint64_t *evpid) -{ - struct qwalk *q = hdl; - FTSENT *e; - char *tmp; - - while ((e = fts_read(q->fts)) != NULL) { - switch (e->fts_info) { - case FTS_D: - q->depth += 1; - if (q->depth == 2 && e->fts_namelen != 2) { - log_debug("debug: fsqueue: bogus directory %s", - e->fts_path); - fts_set(q->fts, e, FTS_SKIP); - break; - } - if (q->depth == 3 && e->fts_namelen != 8) { - log_debug("debug: fsqueue: bogus directory %s", - e->fts_path); - fts_set(q->fts, e, FTS_SKIP); - break; - } - break; - - case FTS_DP: - case FTS_DNR: - q->depth -= 1; - break; - - case FTS_F: - if (q->depth != 3) - break; - if (e->fts_namelen != 16) - break; -#if HAVE_STRUCT_STAT_ST_MTIM - if (timespeccmp(&e->fts_statp->st_mtim, &startup, >)) -#endif -#if HAVE_STRUCT_STAT_ST_MTIMSPEC - if (timespeccmp(&e->fts_statp->st_mtimspec, &startup, >)) -#endif - break; - tmp = NULL; - *evpid = strtoull(e->fts_name, &tmp, 16); - if (tmp && *tmp != '\0') { - log_debug("debug: fsqueue: bogus file %s", - e->fts_path); - break; - } - return (1); - default: - break; - } - } - - return (0); -} - -static int -queue_fs_init(struct passwd *pw, int server, const char *conf) -{ - unsigned int n; - char *paths[] = { PATH_QUEUE, PATH_INCOMING }; - char path[PATH_MAX]; - int ret; - - /* remove incoming/ if it exists */ - if (server) - mvpurge(PATH_SPOOL PATH_INCOMING, PATH_SPOOL PATH_PURGE); - - fsqueue_envelope_path(0, path, sizeof(path)); - - ret = 1; - for (n = 0; n < nitems(paths); n++) { - (void)strlcpy(path, PATH_SPOOL, sizeof(path)); - if (strlcat(path, paths[n], sizeof(path)) >= sizeof(path)) - errx(1, "path too long %s%s", PATH_SPOOL, paths[n]); - if (ckdir(path, 0700, pw->pw_uid, 0, server) == 0) - ret = 0; - } - - if (clock_gettime(CLOCK_REALTIME, &startup)) - err(1, "clock_gettime"); - - tree_init(&evpcount); - - queue_api_on_message_create(queue_fs_message_create); - queue_api_on_message_commit(queue_fs_message_commit); - queue_api_on_message_delete(queue_fs_message_delete); - queue_api_on_message_fd_r(queue_fs_message_fd_r); - queue_api_on_envelope_create(queue_fs_envelope_create); - queue_api_on_envelope_delete(queue_fs_envelope_delete); - queue_api_on_envelope_update(queue_fs_envelope_update); - queue_api_on_envelope_load(queue_fs_envelope_load); - queue_api_on_envelope_walk(queue_fs_envelope_walk); - queue_api_on_message_walk(queue_fs_message_walk); - - return (ret); -} - -struct queue_backend queue_backend_fs = { - queue_fs_init, -}; diff --git a/smtpd/queue_null.c b/smtpd/queue_null.c deleted file mode 100644 index 1e608be8..00000000 --- a/smtpd/queue_null.c +++ /dev/null @@ -1,120 +0,0 @@ -/* $OpenBSD: queue_null.c,v 1.8 2018/12/30 23:09:58 guenther Exp $ */ - -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static int -queue_null_message_create(uint32_t *msgid) -{ - *msgid = queue_generate_msgid(); - return (1); -} - -static int -queue_null_message_commit(uint32_t msgid, const char *path) -{ - return (1); -} - -static int -queue_null_message_delete(uint32_t msgid) -{ - return (1); -} - -static int -queue_null_message_fd_r(uint32_t msgid) -{ - return (-1); -} - -static int -queue_null_envelope_create(uint32_t msgid, const char *buf, size_t len, - uint64_t *evpid) -{ - *evpid = queue_generate_evpid(msgid); - return (1); -} - -static int -queue_null_envelope_delete(uint64_t evpid) -{ - return (1); -} - -static int -queue_null_envelope_update(uint64_t evpid, const char *buf, size_t len) -{ - return (1); -} - -static int -queue_null_envelope_load(uint64_t evpid, char *buf, size_t len) -{ - return (0); -} - -static int -queue_null_envelope_walk(uint64_t *evpid, char *buf, size_t len) -{ - return (-1); -} - -static int -queue_null_init(struct passwd *pw, int server, const char *conf) -{ - queue_api_on_message_create(queue_null_message_create); - queue_api_on_message_commit(queue_null_message_commit); - queue_api_on_message_delete(queue_null_message_delete); - queue_api_on_message_fd_r(queue_null_message_fd_r); - queue_api_on_envelope_create(queue_null_envelope_create); - queue_api_on_envelope_delete(queue_null_envelope_delete); - queue_api_on_envelope_update(queue_null_envelope_update); - queue_api_on_envelope_load(queue_null_envelope_load); - queue_api_on_envelope_walk(queue_null_envelope_walk); - - return (1); -} - -struct queue_backend queue_backend_null = { - queue_null_init, -}; diff --git a/smtpd/queue_proc.c b/smtpd/queue_proc.c deleted file mode 100644 index d6e0f409..00000000 --- a/smtpd/queue_proc.c +++ /dev/null @@ -1,337 +0,0 @@ -/* $OpenBSD: queue_proc.c,v 1.8 2018/12/30 23:09:58 guenther Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static struct imsgbuf ibuf; -static struct imsg imsg; -static size_t rlen; -static char *rdata; - -static void -queue_proc_call(void) -{ - ssize_t n; - - if (imsg_flush(&ibuf) == -1) { - log_warn("warn: queue-proc: imsg_flush"); - fatalx("queue-proc: exiting"); - } - - while (1) { - if ((n = imsg_get(&ibuf, &imsg)) == -1) { - log_warn("warn: queue-proc: imsg_get"); - break; - } - if (n) { - rlen = imsg.hdr.len - IMSG_HEADER_SIZE; - rdata = imsg.data; - - if (imsg.hdr.type != PROC_QUEUE_OK) { - log_warnx("warn: queue-proc: bad response"); - break; - } - return; - } - - if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) { - log_warn("warn: queue-proc: imsg_read"); - break; - } - - if (n == 0) { - log_warnx("warn: queue-proc: pipe closed"); - break; - } - } - - fatalx("queue-proc: exiting"); -} - -static void -queue_proc_read(void *dst, size_t len) -{ - if (len > rlen) { - log_warnx("warn: queue-proc: bad msg len"); - fatalx("queue-proc: exiting"); - } - - memmove(dst, rdata, len); - rlen -= len; - rdata += len; -} - -static void -queue_proc_end(void) -{ - if (rlen) { - log_warnx("warn: queue-proc: bogus data"); - fatalx("queue-proc: exiting"); - } - imsg_free(&imsg); -} - -/* - * API - */ - -static int -queue_proc_close(void) -{ - int r; - - imsg_compose(&ibuf, PROC_QUEUE_CLOSE, 0, 0, -1, NULL, 0); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_message_create(uint32_t *msgid) -{ - int r; - - imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_CREATE, 0, 0, -1, NULL, 0); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - if (r == 1) - queue_proc_read(msgid, sizeof(*msgid)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_message_commit(uint32_t msgid, const char *path) -{ - int r, fd; - - fd = open(path, O_RDONLY); - if (fd == -1) { - log_warn("queue-proc: open: %s", path); - return (0); - } - - imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_COMMIT, 0, 0, fd, &msgid, - sizeof(msgid)); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_message_delete(uint32_t msgid) -{ - int r; - - imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_DELETE, 0, 0, -1, &msgid, - sizeof(msgid)); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_message_fd_r(uint32_t msgid) -{ - imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_FD_R, 0, 0, -1, &msgid, - sizeof(msgid)); - - queue_proc_call(); - queue_proc_end(); - - return (imsg.fd); -} - -static int -queue_proc_envelope_create(uint32_t msgid, const char *buf, size_t len, - uint64_t *evpid) -{ - struct ibuf *b; - int r; - - msgid = evpid_to_msgid(*evpid); - b = imsg_create(&ibuf, PROC_QUEUE_ENVELOPE_CREATE, 0, 0, - sizeof(msgid) + len); - if (imsg_add(b, &msgid, sizeof(msgid)) == -1 || - imsg_add(b, buf, len) == -1) - return (0); - imsg_close(&ibuf, b); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - if (r == 1) - queue_proc_read(evpid, sizeof(*evpid)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_envelope_delete(uint64_t evpid) -{ - int r; - - imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_DELETE, 0, 0, -1, &evpid, - sizeof(evpid)); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_envelope_update(uint64_t evpid, const char *buf, size_t len) -{ - struct ibuf *b; - int r; - - b = imsg_create(&ibuf, PROC_QUEUE_ENVELOPE_UPDATE, 0, 0, - len + sizeof(evpid)); - if (imsg_add(b, &evpid, sizeof(evpid)) == -1 || - imsg_add(b, buf, len) == -1) - return (0); - imsg_close(&ibuf, b); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_envelope_load(uint64_t evpid, char *buf, size_t len) -{ - int r; - - imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_LOAD, 0, 0, -1, &evpid, - sizeof(evpid)); - - queue_proc_call(); - - if (rlen > len) { - log_warnx("warn: queue-proc: buf too small"); - fatalx("queue-proc: exiting"); - } - - r = rlen; - queue_proc_read(buf, rlen); - queue_proc_end(); - - return (r); -} - -static int -queue_proc_envelope_walk(uint64_t *evpid, char *buf, size_t len) -{ - int r; - - imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_WALK, 0, 0, -1, NULL, 0); - - queue_proc_call(); - queue_proc_read(&r, sizeof(r)); - - if (r > 0) { - queue_proc_read(evpid, sizeof(*evpid)); - if (rlen > len) { - log_warnx("warn: queue-proc: buf too small"); - fatalx("queue-proc: exiting"); - } - if (r != (int)rlen) { - log_warnx("warn: queue-proc: len mismatch"); - fatalx("queue-proc: exiting"); - } - queue_proc_read(buf, rlen); - } - queue_proc_end(); - - return (r); -} - -static int -queue_proc_init(struct passwd *pw, int server, const char *conf) -{ - uint32_t version; - int fd; - - fd = fork_proc_backend("queue", conf, "queue-proc"); - if (fd == -1) - fatalx("queue-proc: exiting"); - - imsg_init(&ibuf, fd); - - version = PROC_QUEUE_API_VERSION; - imsg_compose(&ibuf, PROC_QUEUE_INIT, 0, 0, -1, - &version, sizeof(version)); - - queue_api_on_close(queue_proc_close); - queue_api_on_message_create(queue_proc_message_create); - queue_api_on_message_commit(queue_proc_message_commit); - queue_api_on_message_delete(queue_proc_message_delete); - queue_api_on_message_fd_r(queue_proc_message_fd_r); - queue_api_on_envelope_create(queue_proc_envelope_create); - queue_api_on_envelope_delete(queue_proc_envelope_delete); - queue_api_on_envelope_update(queue_proc_envelope_update); - queue_api_on_envelope_load(queue_proc_envelope_load); - queue_api_on_envelope_walk(queue_proc_envelope_walk); - - queue_proc_call(); - queue_proc_end(); - - return (1); -} - -struct queue_backend queue_backend_proc = { - queue_proc_init, -}; diff --git a/smtpd/queue_ram.c b/smtpd/queue_ram.c deleted file mode 100644 index 50ce17e1..00000000 --- a/smtpd/queue_ram.c +++ /dev/null @@ -1,336 +0,0 @@ -/* $OpenBSD: queue_ram.c,v 1.9 2018/12/30 23:09:58 guenther Exp $ */ - -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -struct qr_envelope { - char *buf; - size_t len; -}; - -struct qr_message { - char *buf; - size_t len; - struct tree envelopes; -}; - -static struct tree messages; - -static struct qr_message * -get_message(uint32_t msgid) -{ - struct qr_message *msg; - - msg = tree_get(&messages, msgid); - if (msg == NULL) - log_warn("warn: queue-ram: message not found"); - - return (msg); -} - -static int -queue_ram_message_create(uint32_t *msgid) -{ - struct qr_message *msg; - - msg = calloc(1, sizeof(*msg)); - if (msg == NULL) { - log_warn("warn: queue-ram: calloc"); - return (0); - } - tree_init(&msg->envelopes); - - do { - *msgid = queue_generate_msgid(); - } while (tree_check(&messages, *msgid)); - - tree_xset(&messages, *msgid, msg); - - return (1); -} - -static int -queue_ram_message_commit(uint32_t msgid, const char *path) -{ - struct qr_message *msg; - struct stat sb; - size_t n; - FILE *f; - int ret; - - if ((msg = tree_get(&messages, msgid)) == NULL) { - log_warnx("warn: queue-ram: msgid not found"); - return (0); - } - - f = fopen(path, "rb"); - if (f == NULL) { - log_warn("warn: queue-ram: fopen: %s", path); - return (0); - } - if (fstat(fileno(f), &sb) == -1) { - log_warn("warn: queue-ram: fstat"); - fclose(f); - return (0); - } - - msg->len = sb.st_size; - msg->buf = malloc(msg->len); - if (msg->buf == NULL) { - log_warn("warn: queue-ram: malloc"); - fclose(f); - return (0); - } - - ret = 0; - n = fread(msg->buf, 1, msg->len, f); - if (ferror(f)) - log_warn("warn: queue-ram: fread"); - else if ((off_t)n != sb.st_size) - log_warnx("warn: queue-ram: bad read"); - else { - ret = 1; - stat_increment("queue.ram.message.size", msg->len); - } - fclose(f); - - return (ret); -} - -static int -queue_ram_message_delete(uint32_t msgid) -{ - struct qr_message *msg; - struct qr_envelope *evp; - uint64_t evpid; - - if ((msg = tree_pop(&messages, msgid)) == NULL) { - log_warnx("warn: queue-ram: not found"); - return (0); - } - while (tree_poproot(&messages, &evpid, (void**)&evp)) { - stat_decrement("queue.ram.envelope.size", evp->len); - free(evp->buf); - free(evp); - } - stat_decrement("queue.ram.message.size", msg->len); - free(msg->buf); - free(msg); - return (0); -} - -static int -queue_ram_message_fd_r(uint32_t msgid) -{ - struct qr_message *msg; - size_t n; - FILE *f; - int fd, fd2; - - if ((msg = tree_get(&messages, msgid)) == NULL) { - log_warnx("warn: queue-ram: not found"); - return (-1); - } - - fd = mktmpfile(); - if (fd == -1) { - log_warn("warn: queue-ram: mktmpfile"); - return (-1); - } - - fd2 = dup(fd); - if (fd2 == -1) { - log_warn("warn: queue-ram: dup"); - close(fd); - return (-1); - } - f = fdopen(fd2, "w"); - if (f == NULL) { - log_warn("warn: queue-ram: fdopen"); - close(fd); - close(fd2); - return (-1); - } - n = fwrite(msg->buf, 1, msg->len, f); - if (n != msg->len) { - log_warn("warn: queue-ram: write"); - close(fd); - fclose(f); - return (-1); - } - fclose(f); - lseek(fd, 0, SEEK_SET); - return (fd); -} - -static int -queue_ram_envelope_create(uint32_t msgid, const char *buf, size_t len, - uint64_t *evpid) -{ - struct qr_envelope *evp; - struct qr_message *msg; - - if ((msg = get_message(msgid)) == NULL) - return (0); - - do { - *evpid = queue_generate_evpid(msgid); - } while (tree_check(&msg->envelopes, *evpid)); - evp = calloc(1, sizeof *evp); - if (evp == NULL) { - log_warn("warn: queue-ram: calloc"); - return (0); - } - evp->len = len; - evp->buf = malloc(len); - if (evp->buf == NULL) { - log_warn("warn: queue-ram: malloc"); - free(evp); - return (0); - } - memmove(evp->buf, buf, len); - tree_xset(&msg->envelopes, *evpid, evp); - stat_increment("queue.ram.envelope.size", len); - return (1); -} - -static int -queue_ram_envelope_delete(uint64_t evpid) -{ - struct qr_envelope *evp; - struct qr_message *msg; - - if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) - return (0); - - if ((evp = tree_pop(&msg->envelopes, evpid)) == NULL) { - log_warnx("warn: queue-ram: not found"); - return (0); - } - stat_decrement("queue.ram.envelope.size", evp->len); - free(evp->buf); - free(evp); - if (tree_empty(&msg->envelopes)) { - tree_xpop(&messages, evpid_to_msgid(evpid)); - stat_decrement("queue.ram.message.size", msg->len); - free(msg->buf); - free(msg); - } - return (1); -} - -static int -queue_ram_envelope_update(uint64_t evpid, const char *buf, size_t len) -{ - struct qr_envelope *evp; - struct qr_message *msg; - void *tmp; - - if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) - return (0); - - if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { - log_warn("warn: queue-ram: not found"); - return (0); - } - tmp = malloc(len); - if (tmp == NULL) { - log_warn("warn: queue-ram: malloc"); - return (0); - } - memmove(tmp, buf, len); - free(evp->buf); - evp->len = len; - evp->buf = tmp; - stat_decrement("queue.ram.envelope.size", evp->len); - stat_increment("queue.ram.envelope.size", len); - return (1); -} - -static int -queue_ram_envelope_load(uint64_t evpid, char *buf, size_t len) -{ - struct qr_envelope *evp; - struct qr_message *msg; - - if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) - return (0); - - if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { - log_warn("warn: queue-ram: not found"); - return (0); - } - if (len < evp->len) { - log_warnx("warn: queue-ram: buffer too small"); - return (0); - } - memmove(buf, evp->buf, evp->len); - return (evp->len); -} - -static int -queue_ram_envelope_walk(uint64_t *evpid, char *buf, size_t len) -{ - return (-1); -} - -static int -queue_ram_init(struct passwd *pw, int server, const char * conf) -{ - tree_init(&messages); - - queue_api_on_message_create(queue_ram_message_create); - queue_api_on_message_commit(queue_ram_message_commit); - queue_api_on_message_delete(queue_ram_message_delete); - queue_api_on_message_fd_r(queue_ram_message_fd_r); - queue_api_on_envelope_create(queue_ram_envelope_create); - queue_api_on_envelope_delete(queue_ram_envelope_delete); - queue_api_on_envelope_update(queue_ram_envelope_update); - queue_api_on_envelope_load(queue_ram_envelope_load); - queue_api_on_envelope_walk(queue_ram_envelope_walk); - - return (1); -} - -struct queue_backend queue_backend_ram = { - queue_ram_init, -}; diff --git a/smtpd/report_smtp.c b/smtpd/report_smtp.c deleted file mode 100644 index 7802eaae..00000000 --- a/smtpd/report_smtp.c +++ /dev/null @@ -1,335 +0,0 @@ -/* $OpenBSD: report_smtp.c,v 1.11 2020/01/07 23:03:37 gilles Exp $ */ - -/* - * Copyright (c) 2018 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) -#include -#else -#include "bsd-vis.h" -#endif - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" -#include "rfc5322.h" - -void -report_smtp_link_connect(const char *direction, uint64_t qid, const char *rdns, int fcrdns, - const struct sockaddr_storage *ss_src, - const struct sockaddr_storage *ss_dest) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_LINK_CONNECT, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, rdns); - m_add_int(p_lka, fcrdns); - m_add_sockaddr(p_lka, (const struct sockaddr *)ss_src); - m_add_sockaddr(p_lka, (const struct sockaddr *)ss_dest); - m_close(p_lka); -} - -void -report_smtp_link_greeting(const char *direction, uint64_t qid, - const char *domain) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_LINK_GREETING, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, domain); - m_close(p_lka); -} - -void -report_smtp_link_identify(const char *direction, uint64_t qid, const char *method, const char *identity) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_LINK_IDENTIFY, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, method); - m_add_string(p_lka, identity); - m_close(p_lka); -} - -void -report_smtp_link_tls(const char *direction, uint64_t qid, const char *ssl) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_LINK_TLS, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, ssl); - m_close(p_lka); -} - -void -report_smtp_link_disconnect(const char *direction, uint64_t qid) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_LINK_DISCONNECT, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_close(p_lka); -} - -void -report_smtp_link_auth(const char *direction, uint64_t qid, const char *user, const char *result) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_LINK_AUTH, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, user); - m_add_string(p_lka, result); - m_close(p_lka); -} - -void -report_smtp_tx_reset(const char *direction, uint64_t qid, uint32_t msgid) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_RESET, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_close(p_lka); -} - -void -report_smtp_tx_begin(const char *direction, uint64_t qid, uint32_t msgid) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_BEGIN, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_close(p_lka); -} - -void -report_smtp_tx_mail(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_MAIL, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_add_string(p_lka, address); - m_add_int(p_lka, ok); - m_close(p_lka); -} - -void -report_smtp_tx_rcpt(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_RCPT, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_add_string(p_lka, address); - m_add_int(p_lka, ok); - m_close(p_lka); -} - -void -report_smtp_tx_envelope(const char *direction, uint64_t qid, uint32_t msgid, uint64_t evpid) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_ENVELOPE, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_add_id(p_lka, evpid); - m_close(p_lka); -} - -void -report_smtp_tx_data(const char *direction, uint64_t qid, uint32_t msgid, int ok) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_DATA, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_add_int(p_lka, ok); - m_close(p_lka); -} - -void -report_smtp_tx_commit(const char *direction, uint64_t qid, uint32_t msgid, size_t msgsz) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_COMMIT, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_add_size(p_lka, msgsz); - m_close(p_lka); -} - -void -report_smtp_tx_rollback(const char *direction, uint64_t qid, uint32_t msgid) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TX_ROLLBACK, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_u32(p_lka, msgid); - m_close(p_lka); -} - -void -report_smtp_protocol_client(const char *direction, uint64_t qid, const char *command) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_PROTOCOL_CLIENT, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, command); - m_close(p_lka); -} - -void -report_smtp_protocol_server(const char *direction, uint64_t qid, const char *response) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_PROTOCOL_SERVER, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_string(p_lka, response); - m_close(p_lka); -} - -void -report_smtp_filter_response(const char *direction, uint64_t qid, int phase, int response, const char *param) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_FILTER_RESPONSE, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_add_int(p_lka, phase); - m_add_int(p_lka, response); - m_add_string(p_lka, param); - m_close(p_lka); -} - -void -report_smtp_timeout(const char *direction, uint64_t qid) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - - m_create(p_lka, IMSG_REPORT_SMTP_TIMEOUT, 0, 0, -1); - m_add_string(p_lka, direction); - m_add_timeval(p_lka, &tv); - m_add_id(p_lka, qid); - m_close(p_lka); -} diff --git a/smtpd/resolver.c b/smtpd/resolver.c deleted file mode 100644 index f0f0f8ea..00000000 --- a/smtpd/resolver.c +++ /dev/null @@ -1,462 +0,0 @@ -/* $OpenBSD: resolver.c,v 1.5 2019/06/13 11:45:35 eric Exp $ */ - -/* - * Copyright (c) 2017-2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define p_resolver p_lka - -struct request { - SPLAY_ENTRY(request) entry; - uint32_t id; - void (*cb_ai)(void *, int, struct addrinfo *); - void (*cb_ni)(void *, int, const char *, const char *); - void (*cb_res)(void *, int, int, int, const void *, int); - void *arg; - struct addrinfo *ai; -}; - -struct session { - uint32_t reqid; - struct mproc *proc; - char *host; - char *serv; -}; - -SPLAY_HEAD(reqtree, request); - -static void resolver_init(void); -static void resolver_getaddrinfo_cb(struct asr_result *, void *); -static void resolver_getnameinfo_cb(struct asr_result *, void *); -static void resolver_res_query_cb(struct asr_result *, void *); - -static int request_cmp(struct request *, struct request *); -SPLAY_PROTOTYPE(reqtree, request, entry, request_cmp); - -/* musl work-around */ -void portable_freeaddrinfo(struct addrinfo *); - -static struct reqtree reqs; - -void -resolver_getaddrinfo(const char *hostname, const char *servname, - const struct addrinfo *hints, void (*cb)(void *, int, struct addrinfo *), - void *arg) -{ - struct request *req; - - resolver_init(); - - req = calloc(1, sizeof(*req)); - if (req == NULL) { - cb(arg, EAI_MEMORY, NULL); - return; - } - - while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) - req->id = arc4random(); - req->cb_ai = cb; - req->arg = arg; - - SPLAY_INSERT(reqtree, &reqs, req); - - m_create(p_resolver, IMSG_GETADDRINFO, req->id, 0, -1); - m_add_int(p_resolver, hints ? hints->ai_flags : 0); - m_add_int(p_resolver, hints ? hints->ai_family : 0); - m_add_int(p_resolver, hints ? hints->ai_socktype : 0); - m_add_int(p_resolver, hints ? hints->ai_protocol : 0); - m_add_string(p_resolver, hostname); - m_add_string(p_resolver, servname); - m_close(p_resolver); -} - -void -resolver_getnameinfo(const struct sockaddr *sa, int flags, - void(*cb)(void *, int, const char *, const char *), void *arg) -{ - struct request *req; - - resolver_init(); - - req = calloc(1, sizeof(*req)); - if (req == NULL) { - cb(arg, EAI_MEMORY, NULL, NULL); - return; - } - - while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) - req->id = arc4random(); - req->cb_ni = cb; - req->arg = arg; - - SPLAY_INSERT(reqtree, &reqs, req); - - m_create(p_resolver, IMSG_GETNAMEINFO, req->id, 0, -1); - m_add_sockaddr(p_resolver, sa); - m_add_int(p_resolver, flags); - m_close(p_resolver); -} - -void -resolver_res_query(const char *dname, int class, int type, - void (*cb)(void *, int, int, int, const void *, int), void *arg) -{ - struct request *req; - - resolver_init(); - - req = calloc(1, sizeof(*req)); - if (req == NULL) { - cb(arg, NETDB_INTERNAL, 0, 0, NULL, 0); - return; - } - - while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) - req->id = arc4random(); - req->cb_res = cb; - req->arg = arg; - - SPLAY_INSERT(reqtree, &reqs, req); - - m_create(p_resolver, IMSG_RES_QUERY, req->id, 0, -1); - m_add_string(p_resolver, dname); - m_add_int(p_resolver, class); - m_add_int(p_resolver, type); - m_close(p_resolver); -} - -void -resolver_dispatch_request(struct mproc *proc, struct imsg *imsg) -{ - const char *hostname, *servname, *dname; - struct session *s; - struct asr_query *q; - struct addrinfo hints; - struct sockaddr_storage ss; - struct sockaddr *sa; - struct msg m; - uint32_t reqid; - int class, type, flags, save_errno; - - reqid = imsg->hdr.peerid; - m_msg(&m, imsg); - - switch (imsg->hdr.type) { - - case IMSG_GETADDRINFO: - servname = NULL; - memset(&hints, 0 , sizeof(hints)); - m_get_int(&m, &hints.ai_flags); - m_get_int(&m, &hints.ai_family); - m_get_int(&m, &hints.ai_socktype); - m_get_int(&m, &hints.ai_protocol); - m_get_string(&m, &hostname); - m_get_string(&m, &servname); - m_end(&m); - - s = NULL; - q = NULL; - if ((s = calloc(1, sizeof(*s))) && - (q = getaddrinfo_async(hostname, servname, &hints, NULL)) && - (event_asr_run(q, resolver_getaddrinfo_cb, s))) { - s->reqid = reqid; - s->proc = proc; - break; - } - save_errno = errno; - - if (q) - asr_abort(q); - if (s) - free(s); - - m_create(proc, IMSG_GETADDRINFO_END, reqid, 0, -1); - m_add_int(proc, EAI_SYSTEM); - m_add_int(proc, save_errno); - m_close(proc); - break; - - case IMSG_GETNAMEINFO: - sa = (struct sockaddr*)&ss; - m_get_sockaddr(&m, sa); - m_get_int(&m, &flags); - m_end(&m); - - s = NULL; - q = NULL; - if ((s = calloc(1, sizeof(*s))) && - (s->host = malloc(NI_MAXHOST)) && - (s->serv = malloc(NI_MAXSERV)) && - (q = getnameinfo_async(sa, SA_LEN(sa), s->host, NI_MAXHOST, - s->serv, NI_MAXSERV, flags, NULL)) && - (event_asr_run(q, resolver_getnameinfo_cb, s))) { - s->reqid = reqid; - s->proc = proc; - break; - } - save_errno = errno; - - if (q) - asr_abort(q); - if (s) { - free(s->host); - free(s->serv); - free(s); - } - - m_create(proc, IMSG_GETNAMEINFO, reqid, 0, -1); - m_add_int(proc, EAI_SYSTEM); - m_add_int(proc, save_errno); - m_add_string(proc, NULL); - m_add_string(proc, NULL); - m_close(proc); - break; - - case IMSG_RES_QUERY: - m_get_string(&m, &dname); - m_get_int(&m, &class); - m_get_int(&m, &type); - m_end(&m); - - s = NULL; - q = NULL; - if ((s = calloc(1, sizeof(*s))) && - (q = res_query_async(dname, class, type, NULL)) && - (event_asr_run(q, resolver_res_query_cb, s))) { - s->reqid = reqid; - s->proc = proc; - break; - } - save_errno = errno; - - if (q) - asr_abort(q); - if (s) - free(s); - - m_create(proc, IMSG_RES_QUERY, reqid, 0, -1); - m_add_int(proc, NETDB_INTERNAL); - m_add_int(proc, save_errno); - m_add_int(proc, 0); - m_add_int(proc, 0); - m_add_data(proc, NULL, 0); - m_close(proc); - break; - - default: - fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); - } -} - -void -resolver_dispatch_result(struct mproc *proc, struct imsg *imsg) -{ - struct request key, *req; - struct sockaddr_storage ss; - struct addrinfo *ai; - struct msg m; - const char *cname, *host, *serv; - const void *data; - size_t datalen; - int gai_errno, herrno, rcode, count; - - key.id = imsg->hdr.peerid; - req = SPLAY_FIND(reqtree, &reqs, &key); - if (req == NULL) - fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); - - m_msg(&m, imsg); - - switch (imsg->hdr.type) { - - case IMSG_GETADDRINFO: - ai = calloc(1, sizeof(*ai)); - if (ai == NULL) { - log_warn("%s: calloc", __func__); - break; - } - m_get_int(&m, &ai->ai_flags); - m_get_int(&m, &ai->ai_family); - m_get_int(&m, &ai->ai_socktype); - m_get_int(&m, &ai->ai_protocol); - m_get_sockaddr(&m, (struct sockaddr *)&ss); - m_get_string(&m, &cname); - m_end(&m); - - ai->ai_addr = malloc(SS_LEN(&ss)); - if (ai->ai_addr == NULL) { - log_warn("%s: malloc", __func__); - free(ai); - break; - } - - memmove(ai->ai_addr, &ss, SS_LEN(&ss)); - - if (cname) { - ai->ai_canonname = strdup(cname); - if (ai->ai_canonname == NULL) { - log_warn("%s: strdup", __func__); - free(ai->ai_addr); - free(ai); - break; - } - } - - ai->ai_next = req->ai; - req->ai = ai; - break; - - case IMSG_GETADDRINFO_END: - m_get_int(&m, &gai_errno); - m_get_int(&m, &errno); - m_end(&m); - - SPLAY_REMOVE(reqtree, &reqs, req); - req->cb_ai(req->arg, gai_errno, req->ai); - free(req); - break; - - case IMSG_GETNAMEINFO: - m_get_int(&m, &gai_errno); - m_get_int(&m, &errno); - m_get_string(&m, &host); - m_get_string(&m, &serv); - m_end(&m); - - SPLAY_REMOVE(reqtree, &reqs, req); - req->cb_ni(req->arg, gai_errno, host, serv); - free(req); - break; - - case IMSG_RES_QUERY: - m_get_int(&m, &herrno); - m_get_int(&m, &errno); - m_get_int(&m, &rcode); - m_get_int(&m, &count); - m_get_data(&m, &data, &datalen); - m_end(&m); - - SPLAY_REMOVE(reqtree, &reqs, req); - req->cb_res(req->arg, herrno, rcode, count, data, datalen); - free(req); - break; - } -} - -static void -resolver_init(void) -{ - static int init = 0; - - if (init == 0) { - SPLAY_INIT(&reqs); - init = 1; - } -} - -static void -resolver_getaddrinfo_cb(struct asr_result *ar, void *arg) -{ - struct session *s = arg; - struct addrinfo *ai; - - for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { - m_create(s->proc, IMSG_GETADDRINFO, s->reqid, 0, -1); - m_add_int(s->proc, ai->ai_flags); - m_add_int(s->proc, ai->ai_family); - m_add_int(s->proc, ai->ai_socktype); - m_add_int(s->proc, ai->ai_protocol); - m_add_sockaddr(s->proc, ai->ai_addr); - m_add_string(s->proc, ai->ai_canonname); - m_close(s->proc); - } - - m_create(s->proc, IMSG_GETADDRINFO_END, s->reqid, 0, -1); - m_add_int(s->proc, ar->ar_gai_errno); - m_add_int(s->proc, ar->ar_errno); - m_close(s->proc); - - if (ar->ar_addrinfo) - portable_freeaddrinfo(ar->ar_addrinfo); - free(s); -} - -static void -resolver_getnameinfo_cb(struct asr_result *ar, void *arg) -{ - struct session *s = arg; - - m_create(s->proc, IMSG_GETNAMEINFO, s->reqid, 0, -1); - m_add_int(s->proc, ar->ar_gai_errno); - m_add_int(s->proc, ar->ar_errno); - m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->host); - m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->serv); - m_close(s->proc); - - free(s->host); - free(s->serv); - free(s); -} - -static void -resolver_res_query_cb(struct asr_result *ar, void *arg) -{ - struct session *s = arg; - - m_create(s->proc, IMSG_RES_QUERY, s->reqid, 0, -1); - m_add_int(s->proc, ar->ar_h_errno); - m_add_int(s->proc, ar->ar_errno); - m_add_int(s->proc, ar->ar_rcode); - m_add_int(s->proc, ar->ar_count); - m_add_data(s->proc, ar->ar_data, ar->ar_datalen); - m_close(s->proc); - - free(ar->ar_data); - free(s); -} - -static int -request_cmp(struct request *a, struct request *b) -{ - if (a->id < b->id) - return -1; - if (a->id > b->id) - return 1; - return 0; -} - -SPLAY_GENERATE(reqtree, request, entry, request_cmp); diff --git a/smtpd/rfc5322.c b/smtpd/rfc5322.c deleted file mode 100644 index 0af66772..00000000 --- a/smtpd/rfc5322.c +++ /dev/null @@ -1,266 +0,0 @@ -/* $OpenBSD: rfc5322.c,v 1.2 2018/10/24 18:59:29 gilles Exp $ */ - -/* - * Copyright (c) 2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include "rfc5322.h" - -struct buf { - char *buf; - size_t bufsz; - size_t buflen; - size_t bufmax; -}; - -static int buf_alloc(struct buf *, size_t); -static int buf_grow(struct buf *, size_t); -static int buf_cat(struct buf *, const char *); - -struct rfc5322_parser { - const char *line; - int state; /* last parser state */ - int next; /* parser needs data */ - int unfold; - const char *currhdr; - struct buf hdr; - struct buf val; -}; - -struct rfc5322_parser * -rfc5322_parser_new(void) -{ - struct rfc5322_parser *parser; - - parser = calloc(1, sizeof(*parser)); - if (parser == NULL) - return NULL; - - rfc5322_clear(parser); - parser->hdr.bufmax = 1024; - parser->val.bufmax = 65536; - - return parser; -} - -void -rfc5322_free(struct rfc5322_parser *parser) -{ - free(parser->hdr.buf); - free(parser->val.buf); - free(parser); -} - -void -rfc5322_clear(struct rfc5322_parser *parser) -{ - parser->line = NULL; - parser->state = RFC5322_NONE; - parser->next = 0; - parser->hdr.buflen = 0; - parser->val.buflen = 0; -} - -int -rfc5322_push(struct rfc5322_parser *parser, const char *line) -{ - if (parser->line) { - errno = EALREADY; - return -1; - } - - parser->line = line; - parser->next = 0; - - return 0; -} - -int -rfc5322_unfold_header(struct rfc5322_parser *parser) -{ - if (parser->unfold) { - errno = EALREADY; - return -1; - } - - if (parser->currhdr == NULL) { - errno = EOPNOTSUPP; - return -1; - } - - if (buf_cat(&parser->val, parser->currhdr) == -1) - return -1; - - parser->currhdr = NULL; - parser->unfold = 1; - - return 0; -} - -static int -_rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) -{ - size_t len; - const char *pos, *line; - - line = parser->line; - - switch(parser->state) { - - case RFC5322_HEADER_START: - case RFC5322_HEADER_CONT: - res->hdr = parser->hdr.buf; - - if (line && (line[0] == ' ' || line[0] == '\t')) { - parser->line = NULL; - parser->next = 1; - if (parser->unfold) { - if (buf_cat(&parser->val, "\n") == -1 || - buf_cat(&parser->val, line) == -1) - return -1; - } - res->value = line; - return RFC5322_HEADER_CONT; - } - - if (parser->unfold) { - parser->val.buflen = 0; - parser->unfold = 0; - res->value = parser->val.buf; - } - return RFC5322_HEADER_END; - - case RFC5322_NONE: - case RFC5322_HEADER_END: - if (line && (pos = strchr(line, ':'))) { - len = pos - line; - if (buf_grow(&parser->hdr, len + 1) == -1) - return -1; - (void)memcpy(parser->hdr.buf, line, len); - parser->hdr.buf[len] = '\0'; - parser->hdr.buflen = len + 1; - parser->line = NULL; - parser->next = 1; - parser->currhdr = pos + 1; - res->hdr = parser->hdr.buf; - res->value = pos + 1; - return RFC5322_HEADER_START; - } - - return RFC5322_END_OF_HEADERS; - - case RFC5322_END_OF_HEADERS: - if (line == NULL) - return RFC5322_END_OF_MESSAGE; - - if (line[0] == '\0') { - parser->line = NULL; - parser->next = 1; - res->value = line; - return RFC5322_BODY_START; - } - - errno = EINVAL; - return -1; - - case RFC5322_BODY_START: - case RFC5322_BODY: - if (line == NULL) - return RFC5322_END_OF_MESSAGE; - - parser->line = NULL; - parser->next = 1; - res->value = line; - return RFC5322_BODY; - - case RFC5322_END_OF_MESSAGE: - errno = ENOMSG; - return -1; - - default: - errno = EINVAL; - return -1; - } -} - -int -rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) -{ - memset(res, 0, sizeof(*res)); - - if (parser->next) - return RFC5322_NONE; - - return (parser->state = _rfc5322_next(parser, res)); -} - -static int -buf_alloc(struct buf *b, size_t need) -{ - char *buf; - size_t alloc; - - if (b->buf && b->bufsz >= need) - return 0; - - if (need >= b->bufmax) { - errno = ERANGE; - return -1; - } - -#define N 256 - alloc = N * (need / N) + ((need % N) ? N : 0); -#undef N - buf = reallocarray(b->buf, alloc, 1); - if (buf == NULL) - return -1; - - b->buf = buf; - b->bufsz = alloc; - - return 0; -} - -static int -buf_grow(struct buf *b, size_t sz) -{ - if (SIZE_T_MAX - b->buflen <= sz) { - errno = ERANGE; - return -1; - } - - return buf_alloc(b, b->buflen + sz); -} - -static int -buf_cat(struct buf *b, const char *s) -{ - size_t len = strlen(s); - - if (buf_grow(b, len + 1) == -1) - return -1; - - (void)memmove(b->buf + b->buflen, s, len + 1); - b->buflen += len; - return 0; -} diff --git a/smtpd/rfc5322.h b/smtpd/rfc5322.h deleted file mode 100644 index 0979bd4c..00000000 --- a/smtpd/rfc5322.h +++ /dev/null @@ -1,41 +0,0 @@ -/* $OpenBSD: rfc5322.h,v 1.1 2018/08/23 10:07:06 eric Exp $ */ - -/* - * Copyright (c) 2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -struct rfc5322_result { - const char *hdr; - const char *value; -}; - -#define RFC5322_ERR -1 -#define RFC5322_NONE 0 -#define RFC5322_HEADER_START 1 -#define RFC5322_HEADER_CONT 2 -#define RFC5322_HEADER_END 3 -#define RFC5322_END_OF_HEADERS 4 -#define RFC5322_BODY_START 5 -#define RFC5322_BODY 6 -#define RFC5322_END_OF_MESSAGE 7 - -struct rfc5322_parser; - -struct rfc5322_parser *rfc5322_parser_new(void); -void rfc5322_free(struct rfc5322_parser *); -void rfc5322_clear(struct rfc5322_parser *); -int rfc5322_push(struct rfc5322_parser *, const char *); -int rfc5322_next(struct rfc5322_parser *, struct rfc5322_result *); -int rfc5322_unfold_header(struct rfc5322_parser *); diff --git a/smtpd/ruleset.c b/smtpd/ruleset.c deleted file mode 100644 index 719a2913..00000000 --- a/smtpd/ruleset.c +++ /dev/null @@ -1,265 +0,0 @@ -/* $OpenBSD: ruleset.c,v 1.47 2019/11/25 14:18:33 gilles Exp $ */ - -/* - * Copyright (c) 2009 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -#define MATCH_RESULT(r, neg) ((r) == -1 ? -1 : ((neg) < 0 ? !(r) : (r))) - -static int -ruleset_match_tag(struct rule *r, const struct envelope *evp) -{ - int ret; - struct table *table; - enum table_service service = K_STRING; - - if (!r->flag_tag) - return 1; - - if (r->flag_tag_regex) - service = K_REGEX; - - table = table_find(env, r->table_tag); - ret = table_match(table, service, evp->tag); - - return MATCH_RESULT(ret, r->flag_tag); -} - -static int -ruleset_match_from(struct rule *r, const struct envelope *evp) -{ - int ret; - int has_rdns; - const char *key; - struct table *table; - enum table_service service = K_NETADDR; - - if (!r->flag_from) - return 1; - - if (evp->flags & EF_INTERNAL) { - /* if expanded from an empty table_from, skip rule - * if no table - */ - if (r->table_from == NULL) - return 0; - key = "local"; - } - else if (r->flag_from_rdns) { - has_rdns = strcmp(evp->hostname, "") != 0; - if (r->table_from == NULL) - return MATCH_RESULT(has_rdns, r->flag_from); - if (!has_rdns) - return 0; - key = evp->hostname; - } - else { - key = ss_to_text(&evp->ss); - if (r->flag_from_socket) { - if (strcmp(key, "local") == 0) - return MATCH_RESULT(1, r->flag_from); - else - return r->flag_from < 0 ? 1 : 0; - } - } - if (r->flag_from_regex) - service = K_REGEX; - - table = table_find(env, r->table_from); - ret = table_match(table, service, key); - - return MATCH_RESULT(ret, r->flag_from); -} - -static int -ruleset_match_to(struct rule *r, const struct envelope *evp) -{ - int ret; - struct table *table; - enum table_service service = K_DOMAIN; - - if (!r->flag_for) - return 1; - - if (r->flag_for_regex) - service = K_REGEX; - - table = table_find(env, r->table_for); - ret = table_match(table, service, evp->dest.domain); - - return MATCH_RESULT(ret, r->flag_for); -} - -static int -ruleset_match_smtp_helo(struct rule *r, const struct envelope *evp) -{ - int ret; - struct table *table; - enum table_service service = K_DOMAIN; - - if (!r->flag_smtp_helo) - return 1; - - if (r->flag_smtp_helo_regex) - service = K_REGEX; - - table = table_find(env, r->table_smtp_helo); - ret = table_match(table, service, evp->helo); - - return MATCH_RESULT(ret, r->flag_smtp_helo); -} - -static int -ruleset_match_smtp_starttls(struct rule *r, const struct envelope *evp) -{ - if (!r->flag_smtp_starttls) - return 1; - - /* XXX - not until TLS flag is added to envelope */ - return -1; -} - -static int -ruleset_match_smtp_auth(struct rule *r, const struct envelope *evp) -{ - int ret; - struct table *table; - enum table_service service; - - if (!r->flag_smtp_auth) - return 1; - - if (!(evp->flags & EF_AUTHENTICATED)) - ret = 0; - else if (r->table_smtp_auth) { - - if (r->flag_smtp_auth_regex) - service = K_REGEX; - else - service = strchr(evp->username, '@') ? - K_MAILADDR : K_STRING; - table = table_find(env, r->table_smtp_auth); - ret = table_match(table, service, evp->username); - } - else - ret = 1; - - return MATCH_RESULT(ret, r->flag_smtp_auth); -} - -static int -ruleset_match_smtp_mail_from(struct rule *r, const struct envelope *evp) -{ - int ret; - const char *key; - struct table *table; - enum table_service service = K_MAILADDR; - - if (!r->flag_smtp_mail_from) - return 1; - - if (r->flag_smtp_mail_from_regex) - service = K_REGEX; - - if ((key = mailaddr_to_text(&evp->sender)) == NULL) - return -1; - - table = table_find(env, r->table_smtp_mail_from); - ret = table_match(table, service, key); - - return MATCH_RESULT(ret, r->flag_smtp_mail_from); -} - -static int -ruleset_match_smtp_rcpt_to(struct rule *r, const struct envelope *evp) -{ - int ret; - const char *key; - struct table *table; - enum table_service service = K_MAILADDR; - - if (!r->flag_smtp_rcpt_to) - return 1; - - if (r->flag_smtp_rcpt_to_regex) - service = K_REGEX; - - if ((key = mailaddr_to_text(&evp->dest)) == NULL) - return -1; - - table = table_find(env, r->table_smtp_rcpt_to); - ret = table_match(table, service, key); - - return MATCH_RESULT(ret, r->flag_smtp_rcpt_to); -} - -struct rule * -ruleset_match(const struct envelope *evp) -{ - struct rule *r; - int i = 0; - -#define MATCH_EVAL(x) \ - switch ((x)) { \ - case -1: goto tempfail; \ - case 0: continue; \ - default: break; \ - } - TAILQ_FOREACH(r, env->sc_rules, r_entry) { - ++i; - MATCH_EVAL(ruleset_match_tag(r, evp)); - MATCH_EVAL(ruleset_match_from(r, evp)); - MATCH_EVAL(ruleset_match_to(r, evp)); - MATCH_EVAL(ruleset_match_smtp_helo(r, evp)); - MATCH_EVAL(ruleset_match_smtp_auth(r, evp)); - MATCH_EVAL(ruleset_match_smtp_starttls(r, evp)); - MATCH_EVAL(ruleset_match_smtp_mail_from(r, evp)); - MATCH_EVAL(ruleset_match_smtp_rcpt_to(r, evp)); - goto matched; - } -#undef MATCH_EVAL - - errno = 0; - log_trace(TRACE_RULES, "no rule matched"); - return (NULL); - -tempfail: - errno = EAGAIN; - log_trace(TRACE_RULES, "temporary failure in processing of a rule"); - return (NULL); - -matched: - log_trace(TRACE_RULES, "rule #%d matched: %s", i, rule_to_text(r)); - return r; -} diff --git a/smtpd/runq.c b/smtpd/runq.c deleted file mode 100644 index 786d36fb..00000000 --- a/smtpd/runq.c +++ /dev/null @@ -1,183 +0,0 @@ -/* $OpenBSD: runq.c,v 1.3 2019/06/14 19:55:25 eric Exp $ */ - -/* - * Copyright (c) 2013,2019 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "smtpd.h" - -struct job { - TAILQ_ENTRY(job) entry; - time_t when; - void *arg; -}; - -struct runq { - TAILQ_HEAD(, job) jobs; - void (*cb)(struct runq *, void *); - struct event ev; -}; - -static void runq_timeout(int, short, void *); - -static struct runq *active; - -static void -runq_reset(struct runq *runq) -{ - struct timeval tv; - struct job *job; - time_t now; - - job = TAILQ_FIRST(&runq->jobs); - if (job == NULL) - return; - - now = time(NULL); - if (job->when <= now) - tv.tv_sec = 0; - else - tv.tv_sec = job->when - now; - tv.tv_usec = 0; - evtimer_add(&runq->ev, &tv); -} - -static void -runq_timeout(int fd, short ev, void *arg) -{ - struct runq *runq = arg; - struct job *job; - time_t now; - - active = runq; - now = time(NULL); - - while((job = TAILQ_FIRST(&runq->jobs))) { - if (job->when > now) - break; - TAILQ_REMOVE(&runq->jobs, job, entry); - runq->cb(runq, job->arg); - free(job); - } - - active = NULL; - runq_reset(runq); -} - -int -runq_init(struct runq **runqp, void (*cb)(struct runq *, void *)) -{ - struct runq *runq; - - runq = malloc(sizeof(*runq)); - if (runq == NULL) - return (0); - - runq->cb = cb; - TAILQ_INIT(&runq->jobs); - evtimer_set(&runq->ev, runq_timeout, runq); - - *runqp = runq; - - return (1); -} - -int -runq_schedule(struct runq *runq, time_t delay, void *arg) -{ - time_t t; - - time(&t); - return runq_schedule_at(runq, t + delay, arg); -} - -int -runq_schedule_at(struct runq *runq, time_t when, void *arg) -{ - struct job *job, *tmpjob; - - job = malloc(sizeof(*job)); - if (job == NULL) - return (0); - - job->arg = arg; - job->when = when; - - TAILQ_FOREACH(tmpjob, &runq->jobs, entry) { - if (tmpjob->when > job->when) { - TAILQ_INSERT_BEFORE(tmpjob, job, entry); - goto done; - } - } - TAILQ_INSERT_TAIL(&runq->jobs, job, entry); - - done: - if (runq != active && job == TAILQ_FIRST(&runq->jobs)) { - evtimer_del(&runq->ev); - runq_reset(runq); - } - return (1); -} - -int -runq_cancel(struct runq *runq, void *arg) -{ - struct job *job, *first; - - first = TAILQ_FIRST(&runq->jobs); - TAILQ_FOREACH(job, &runq->jobs, entry) { - if (job->arg == arg) { - TAILQ_REMOVE(&runq->jobs, job, entry); - free(job); - if (runq != active && job == first) { - evtimer_del(&runq->ev); - runq_reset(runq); - } - return (1); - } - } - - return (0); -} - -int -runq_pending(struct runq *runq, void *arg, time_t *when) -{ - struct job *job; - - TAILQ_FOREACH(job, &runq->jobs, entry) { - if (job->arg == arg) { - if (when) - *when = job->when; - return (1); - } - } - - return (0); -} diff --git a/smtpd/scheduler.c b/smtpd/scheduler.c deleted file mode 100644 index ea70a83d..00000000 --- a/smtpd/scheduler.c +++ /dev/null @@ -1,618 +0,0 @@ -/* $OpenBSD: scheduler.c,v 1.60 2018/12/30 23:09:58 guenther Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008-2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static void scheduler_imsg(struct mproc *, struct imsg *); -static void scheduler_shutdown(void); -static void scheduler_reset_events(void); -static void scheduler_timeout(int, short, void *); - -static struct scheduler_backend *backend = NULL; -static struct event ev; -static size_t ninflight = 0; -static int *types; -static uint64_t *evpids; -static uint32_t *msgids; -static struct evpstate *state; - -extern const char *backend_scheduler; - -void -scheduler_imsg(struct mproc *p, struct imsg *imsg) -{ - struct bounce_req_msg req; - struct envelope evp; - struct scheduler_info si; - struct msg m; - uint64_t evpid, id, holdq; - uint32_t msgid; - uint32_t inflight; - size_t n, i; - time_t timestamp; - int v, r, type; - - if (imsg == NULL) - scheduler_shutdown(); - - switch (imsg->hdr.type) { - - case IMSG_QUEUE_ENVELOPE_SUBMIT: - m_msg(&m, imsg); - m_get_envelope(&m, &evp); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: inserting evp:%016" PRIx64, evp.id); - scheduler_info(&si, &evp); - stat_increment("scheduler.envelope.incoming", 1); - backend->insert(&si); - return; - - case IMSG_QUEUE_MESSAGE_COMMIT: - m_msg(&m, imsg); - m_get_msgid(&m, &msgid); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: committing msg:%08" PRIx32, msgid); - n = backend->commit(msgid); - stat_decrement("scheduler.envelope.incoming", n); - stat_increment("scheduler.envelope", n); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_DISCOVER_EVPID: - m_msg(&m, imsg); - m_get_envelope(&m, &evp); - m_end(&m); - r = backend->query(evp.id); - if (r) { - log_debug("debug: scheduler: evp:%016" PRIx64 - " already scheduled", evp.id); - return; - } - log_trace(TRACE_SCHEDULER, - "scheduler: discovering evp:%016" PRIx64, evp.id); - scheduler_info(&si, &evp); - stat_increment("scheduler.envelope.incoming", 1); - backend->insert(&si); - return; - - case IMSG_QUEUE_DISCOVER_MSGID: - m_msg(&m, imsg); - m_get_msgid(&m, &msgid); - m_end(&m); - r = backend->query(msgid); - if (r) { - log_debug("debug: scheduler: msgid:%08" PRIx32 - " already scheduled", msgid); - return; - } - log_trace(TRACE_SCHEDULER, - "scheduler: committing msg:%08" PRIx32, msgid); - n = backend->commit(msgid); - stat_decrement("scheduler.envelope.incoming", n); - stat_increment("scheduler.envelope", n); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_MESSAGE_ROLLBACK: - m_msg(&m, imsg); - m_get_msgid(&m, &msgid); - m_end(&m); - log_trace(TRACE_SCHEDULER, "scheduler: aborting msg:%08" PRIx32, - msgid); - n = backend->rollback(msgid); - stat_decrement("scheduler.envelope.incoming", n); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_ENVELOPE_REMOVE: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_get_u32(&m, &inflight); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: queue requested removal of evp:%016" PRIx64, - evpid); - stat_decrement("scheduler.envelope", 1); - if (!inflight) - backend->remove(evpid); - else { - backend->delete(evpid); - ninflight -= 1; - stat_decrement("scheduler.envelope.inflight", 1); - } - - scheduler_reset_events(); - return; - - case IMSG_QUEUE_ENVELOPE_ACK: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: queue ack removal of evp:%016" PRIx64, - evpid); - ninflight -= 1; - stat_decrement("scheduler.envelope.inflight", 1); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_DELIVERY_OK: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: deleting evp:%016" PRIx64 " (ok)", evpid); - backend->delete(evpid); - ninflight -= 1; - stat_increment("scheduler.delivery.ok", 1); - stat_decrement("scheduler.envelope.inflight", 1); - stat_decrement("scheduler.envelope", 1); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_DELIVERY_TEMPFAIL: - m_msg(&m, imsg); - m_get_envelope(&m, &evp); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: updating evp:%016" PRIx64, evp.id); - scheduler_info(&si, &evp); - backend->update(&si); - ninflight -= 1; - stat_increment("scheduler.delivery.tempfail", 1); - stat_decrement("scheduler.envelope.inflight", 1); - - for (i = 0; i < MAX_BOUNCE_WARN; i++) { - if (env->sc_bounce_warn[i] == 0) - break; - timestamp = si.creation + env->sc_bounce_warn[i]; - if (si.nexttry >= timestamp && - si.lastbounce < timestamp) { - req.evpid = evp.id; - req.timestamp = timestamp; - req.bounce.type = B_DELAYED; - req.bounce.delay = env->sc_bounce_warn[i]; - req.bounce.ttl = si.ttl; - m_compose(p, IMSG_SCHED_ENVELOPE_BOUNCE, 0, 0, -1, - &req, sizeof req); - break; - } - } - scheduler_reset_events(); - return; - - case IMSG_QUEUE_DELIVERY_PERMFAIL: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: deleting evp:%016" PRIx64 " (fail)", evpid); - backend->delete(evpid); - ninflight -= 1; - stat_increment("scheduler.delivery.permfail", 1); - stat_decrement("scheduler.envelope.inflight", 1); - stat_decrement("scheduler.envelope", 1); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_DELIVERY_LOOP: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: deleting evp:%016" PRIx64 " (loop)", evpid); - backend->delete(evpid); - ninflight -= 1; - stat_increment("scheduler.delivery.loop", 1); - stat_decrement("scheduler.envelope.inflight", 1); - stat_decrement("scheduler.envelope", 1); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_HOLDQ_HOLD: - m_msg(&m, imsg); - m_get_evpid(&m, &evpid); - m_get_id(&m, &holdq); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: holding evp:%016" PRIx64 " on %016" PRIx64, - evpid, holdq); - backend->hold(evpid, holdq); - ninflight -= 1; - stat_decrement("scheduler.envelope.inflight", 1); - scheduler_reset_events(); - return; - - case IMSG_QUEUE_HOLDQ_RELEASE: - m_msg(&m, imsg); - m_get_int(&m, &type); - m_get_id(&m, &holdq); - m_get_int(&m, &r); - m_end(&m); - log_trace(TRACE_SCHEDULER, - "scheduler: releasing %d on holdq (%d, %016" PRIx64 ")", - r, type, holdq); - backend->release(type, holdq, r); - scheduler_reset_events(); - return; - - case IMSG_CTL_PAUSE_MDA: - log_trace(TRACE_SCHEDULER, "scheduler: pausing mda"); - env->sc_flags |= SMTPD_MDA_PAUSED; - return; - - case IMSG_CTL_RESUME_MDA: - log_trace(TRACE_SCHEDULER, "scheduler: resuming mda"); - env->sc_flags &= ~SMTPD_MDA_PAUSED; - scheduler_reset_events(); - return; - - case IMSG_CTL_PAUSE_MTA: - log_trace(TRACE_SCHEDULER, "scheduler: pausing mta"); - env->sc_flags |= SMTPD_MTA_PAUSED; - return; - - case IMSG_CTL_RESUME_MTA: - log_trace(TRACE_SCHEDULER, "scheduler: resuming mta"); - env->sc_flags &= ~SMTPD_MTA_PAUSED; - scheduler_reset_events(); - return; - - case IMSG_CTL_VERBOSE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - log_setverbose(v); - return; - - case IMSG_CTL_PROFILE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - profiling = v; - return; - - case IMSG_CTL_LIST_MESSAGES: - msgid = *(uint32_t *)(imsg->data); - n = backend->messages(msgid, msgids, env->sc_scheduler_max_msg_batch_size); - m_compose(p, IMSG_CTL_LIST_MESSAGES, imsg->hdr.peerid, 0, -1, - msgids, n * sizeof (*msgids)); - return; - - case IMSG_CTL_LIST_ENVELOPES: - id = *(uint64_t *)(imsg->data); - n = backend->envelopes(id, state, env->sc_scheduler_max_evp_batch_size); - for (i = 0; i < n; i++) { - m_create(p_queue, IMSG_CTL_LIST_ENVELOPES, - imsg->hdr.peerid, 0, -1); - m_add_evpid(p_queue, state[i].evpid); - m_add_int(p_queue, state[i].flags); - m_add_time(p_queue, state[i].time); - m_close(p_queue); - } - m_compose(p_queue, IMSG_CTL_LIST_ENVELOPES, - imsg->hdr.peerid, 0, -1, NULL, 0); - return; - - case IMSG_CTL_SCHEDULE: - id = *(uint64_t *)(imsg->data); - if (id <= 0xffffffffL) - log_debug("debug: scheduler: " - "scheduling msg:%08" PRIx64, id); - else - log_debug("debug: scheduler: " - "scheduling evp:%016" PRIx64, id); - r = backend->schedule(id); - scheduler_reset_events(); - m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_QUEUE_ENVELOPE_SCHEDULE: - id = *(uint64_t *)(imsg->data); - backend->schedule(id); - scheduler_reset_events(); - return; - - case IMSG_CTL_REMOVE: - id = *(uint64_t *)(imsg->data); - if (id <= 0xffffffffL) - log_debug("debug: scheduler: " - "removing msg:%08" PRIx64, id); - else - log_debug("debug: scheduler: " - "removing evp:%016" PRIx64, id); - r = backend->remove(id); - scheduler_reset_events(); - m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_CTL_PAUSE_EVP: - id = *(uint64_t *)(imsg->data); - if (id <= 0xffffffffL) - log_debug("debug: scheduler: " - "suspending msg:%08" PRIx64, id); - else - log_debug("debug: scheduler: " - "suspending evp:%016" PRIx64, id); - r = backend->suspend(id); - scheduler_reset_events(); - m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - - case IMSG_CTL_RESUME_EVP: - id = *(uint64_t *)(imsg->data); - if (id <= 0xffffffffL) - log_debug("debug: scheduler: " - "resuming msg:%08" PRIx64, id); - else - log_debug("debug: scheduler: " - "resuming evp:%016" PRIx64, id); - r = backend->resume(id); - scheduler_reset_events(); - m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, - 0, -1, NULL, 0); - return; - } - - errx(1, "scheduler_imsg: unexpected %s imsg", - imsg_to_str(imsg->hdr.type)); -} - -static void -scheduler_shutdown(void) -{ - log_debug("debug: scheduler agent exiting"); - _exit(0); -} - -static void -scheduler_reset_events(void) -{ - struct timeval tv; - - evtimer_del(&ev); - tv.tv_sec = 0; - tv.tv_usec = 0; - evtimer_add(&ev, &tv); -} - -int -scheduler(void) -{ - struct passwd *pw; - - backend = scheduler_backend_lookup(backend_scheduler); - if (backend == NULL) - errx(1, "cannot find scheduler backend \"%s\"", - backend_scheduler); - - purge_config(PURGE_EVERYTHING & ~PURGE_DISPATCHERS); - - if ((pw = getpwnam(SMTPD_USER)) == NULL) - fatalx("unknown user " SMTPD_USER); - - config_process(PROC_SCHEDULER); - - backend->init(backend_scheduler); - - if (chroot(PATH_CHROOT) == -1) - fatal("scheduler: chroot"); - if (chdir("/") == -1) - fatal("scheduler: chdir(\"/\")"); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - fatal("scheduler: cannot drop privileges"); - - evpids = xcalloc(env->sc_scheduler_max_schedule, sizeof *evpids); - types = xcalloc(env->sc_scheduler_max_schedule, sizeof *types); - msgids = xcalloc(env->sc_scheduler_max_msg_batch_size, sizeof *msgids); - state = xcalloc(env->sc_scheduler_max_evp_batch_size, sizeof *state); - - imsg_callback = scheduler_imsg; - event_init(); - - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - signal(SIGHUP, SIG_IGN); - - config_peer(PROC_CONTROL); - config_peer(PROC_QUEUE); - - evtimer_set(&ev, scheduler_timeout, NULL); - scheduler_reset_events(); - -#if HAVE_PLEDGE - if (pledge("stdio", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} - -static void -scheduler_timeout(int fd, short event, void *p) -{ - struct timeval tv; - size_t i; - size_t d_inflight; - size_t d_envelope; - size_t d_removed; - size_t d_expired; - size_t d_updated; - size_t count; - int mask, r, delay; - - tv.tv_sec = 0; - tv.tv_usec = 0; - - mask = SCHED_UPDATE; - - if (ninflight < env->sc_scheduler_max_inflight) { - mask |= SCHED_EXPIRE | SCHED_REMOVE | SCHED_BOUNCE; - if (!(env->sc_flags & SMTPD_MDA_PAUSED)) - mask |= SCHED_MDA; - if (!(env->sc_flags & SMTPD_MTA_PAUSED)) - mask |= SCHED_MTA; - } - - count = env->sc_scheduler_max_schedule; - - log_trace(TRACE_SCHEDULER, "scheduler: getting batch: mask=0x%x, count=%zu", mask, count); - - r = backend->batch(mask, &delay, &count, evpids, types); - - log_trace(TRACE_SCHEDULER, "scheduler: got r=%i, delay=%i, count=%zu", r, delay, count); - - if (r < 0) - fatalx("scheduler: error in batch handler"); - - if (r == 0) { - - if (delay < -1) - fatalx("scheduler: invalid delay %d", delay); - - if (delay == -1) { - log_trace(TRACE_SCHEDULER, "scheduler: sleeping"); - return; - } - - tv.tv_sec = delay; - tv.tv_usec = 0; - log_trace(TRACE_SCHEDULER, - "scheduler: waiting for %s", duration_to_text(tv.tv_sec)); - evtimer_add(&ev, &tv); - return; - } - - d_inflight = 0; - d_envelope = 0; - d_removed = 0; - d_expired = 0; - d_updated = 0; - - for (i = 0; i < count; i++) { - switch(types[i]) { - case SCHED_REMOVE: - log_debug("debug: scheduler: evp:%016" PRIx64 - " removed", evpids[i]); - m_create(p_queue, IMSG_SCHED_ENVELOPE_REMOVE, 0, 0, -1); - m_add_evpid(p_queue, evpids[i]); - m_close(p_queue); - d_envelope += 1; - d_removed += 1; - d_inflight += 1; - break; - - case SCHED_EXPIRE: - log_debug("debug: scheduler: evp:%016" PRIx64 - " expired", evpids[i]); - m_create(p_queue, IMSG_SCHED_ENVELOPE_EXPIRE, 0, 0, -1); - m_add_evpid(p_queue, evpids[i]); - m_close(p_queue); - d_envelope += 1; - d_expired += 1; - d_inflight += 1; - break; - - case SCHED_UPDATE: - log_debug("debug: scheduler: evp:%016" PRIx64 - " scheduled (update)", evpids[i]); - d_updated += 1; - break; - - case SCHED_BOUNCE: - log_debug("debug: scheduler: evp:%016" PRIx64 - " scheduled (bounce)", evpids[i]); - m_create(p_queue, IMSG_SCHED_ENVELOPE_INJECT, 0, 0, -1); - m_add_evpid(p_queue, evpids[i]); - m_close(p_queue); - d_inflight += 1; - break; - - case SCHED_MDA: - log_debug("debug: scheduler: evp:%016" PRIx64 - " scheduled (mda)", evpids[i]); - m_create(p_queue, IMSG_SCHED_ENVELOPE_DELIVER, 0, 0, -1); - m_add_evpid(p_queue, evpids[i]); - m_close(p_queue); - d_inflight += 1; - break; - - case SCHED_MTA: - log_debug("debug: scheduler: evp:%016" PRIx64 - " scheduled (mta)", evpids[i]); - m_create(p_queue, IMSG_SCHED_ENVELOPE_TRANSFER, 0, 0, -1); - m_add_evpid(p_queue, evpids[i]); - m_close(p_queue); - d_inflight += 1; - break; - } - } - - stat_decrement("scheduler.envelope", d_envelope); - stat_increment("scheduler.envelope.inflight", d_inflight); - stat_increment("scheduler.envelope.expired", d_expired); - stat_increment("scheduler.envelope.removed", d_removed); - stat_increment("scheduler.envelope.updated", d_updated); - - ninflight += d_inflight; - - tv.tv_sec = 0; - tv.tv_usec = 0; - evtimer_add(&ev, &tv); -} diff --git a/smtpd/scheduler_backend.c b/smtpd/scheduler_backend.c deleted file mode 100644 index ad2b4cab..00000000 --- a/smtpd/scheduler_backend.c +++ /dev/null @@ -1,82 +0,0 @@ -/* $OpenBSD: scheduler_backend.c,v 1.16 2018/05/24 11:38:24 gilles Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -extern struct scheduler_backend scheduler_backend_null; -extern struct scheduler_backend scheduler_backend_proc; -extern struct scheduler_backend scheduler_backend_ramqueue; - -struct scheduler_backend * -scheduler_backend_lookup(const char *name) -{ - if (!strcmp(name, "null")) - return &scheduler_backend_null; - if (!strcmp(name, "ramqueue")) - return &scheduler_backend_ramqueue; - - return &scheduler_backend_proc; -} - -void -scheduler_info(struct scheduler_info *sched, struct envelope *evp) -{ - struct dispatcher *disp; - - disp = evp->type == D_BOUNCE ? - env->sc_dispatcher_bounce : - dict_xget(env->sc_dispatchers, evp->dispatcher); - - switch (disp->type) { - case DISPATCHER_LOCAL: - sched->type = D_MDA; - break; - case DISPATCHER_REMOTE: - sched->type = D_MTA; - break; - case DISPATCHER_BOUNCE: - sched->type = D_BOUNCE; - break; - } - sched->ttl = disp->ttl ? disp->ttl : env->sc_ttl; - - sched->evpid = evp->id; - sched->creation = evp->creation; - sched->retry = evp->retry; - sched->lasttry = evp->lasttry; - sched->lastbounce = evp->lastbounce; - sched->nexttry = 0; -} diff --git a/smtpd/scheduler_null.c b/smtpd/scheduler_null.c deleted file mode 100644 index 40db6205..00000000 --- a/smtpd/scheduler_null.c +++ /dev/null @@ -1,164 +0,0 @@ -/* $OpenBSD: scheduler_null.c,v 1.9 2015/01/20 17:37:54 deraadt Exp $ */ - -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" - -static int scheduler_null_init(const char *); -static int scheduler_null_insert(struct scheduler_info *); -static size_t scheduler_null_commit(uint32_t); -static size_t scheduler_null_rollback(uint32_t); -static int scheduler_null_update(struct scheduler_info *); -static int scheduler_null_delete(uint64_t); -static int scheduler_null_hold(uint64_t, uint64_t); -static int scheduler_null_release(int, uint64_t, int); -static int scheduler_null_batch(int, int*, size_t*, uint64_t*, int*); -static size_t scheduler_null_messages(uint32_t, uint32_t *, size_t); -static size_t scheduler_null_envelopes(uint64_t, struct evpstate *, size_t); -static int scheduler_null_schedule(uint64_t); -static int scheduler_null_remove(uint64_t); -static int scheduler_null_suspend(uint64_t); -static int scheduler_null_resume(uint64_t); - -struct scheduler_backend scheduler_backend_null = { - scheduler_null_init, - - scheduler_null_insert, - scheduler_null_commit, - scheduler_null_rollback, - - scheduler_null_update, - scheduler_null_delete, - scheduler_null_hold, - scheduler_null_release, - - scheduler_null_batch, - - scheduler_null_messages, - scheduler_null_envelopes, - scheduler_null_schedule, - scheduler_null_remove, - scheduler_null_suspend, - scheduler_null_resume, -}; - -static int -scheduler_null_init(const char *arg) -{ - return (1); -} - -static int -scheduler_null_insert(struct scheduler_info *si) -{ - return (0); -} - -static size_t -scheduler_null_commit(uint32_t msgid) -{ - return (0); -} - -static size_t -scheduler_null_rollback(uint32_t msgid) -{ - return (0); -} - -static int -scheduler_null_update(struct scheduler_info *si) -{ - return (0); -} - -static int -scheduler_null_delete(uint64_t evpid) -{ - return (0); -} - -static int -scheduler_null_hold(uint64_t evpid, uint64_t holdq) -{ - return (0); -} - -static int -scheduler_null_release(int type, uint64_t holdq, int n) -{ - return (0); -} - -static int -scheduler_null_batch(int typemask, int *delay, size_t *count, uint64_t *evpids, int *types) -{ - *delay = 0; - - return (0); -} - -static int -scheduler_null_schedule(uint64_t evpid) -{ - return (0); -} - -static int -scheduler_null_remove(uint64_t evpid) -{ - return (0); -} - -static int -scheduler_null_suspend(uint64_t evpid) -{ - return (0); -} - -static int -scheduler_null_resume(uint64_t evpid) -{ - return (0); -} - -static size_t -scheduler_null_messages(uint32_t from, uint32_t *dst, size_t size) -{ - return (0); -} - -static size_t -scheduler_null_envelopes(uint64_t from, struct evpstate *dst, size_t size) -{ - return (0); -} diff --git a/smtpd/scheduler_proc.c b/smtpd/scheduler_proc.c deleted file mode 100644 index 5f4e8b70..00000000 --- a/smtpd/scheduler_proc.c +++ /dev/null @@ -1,446 +0,0 @@ -/* $OpenBSD: scheduler_proc.c,v 1.8 2015/12/05 13:14:21 claudio Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static struct imsgbuf ibuf; -static struct imsg imsg; -static size_t rlen; -static char *rdata; - -static void -scheduler_proc_call(void) -{ - ssize_t n; - - if (imsg_flush(&ibuf) == -1) { - log_warn("warn: scheduler-proc: imsg_flush"); - fatalx("scheduler-proc: exiting"); - } - - while (1) { - if ((n = imsg_get(&ibuf, &imsg)) == -1) { - log_warn("warn: scheduler-proc: imsg_get"); - break; - } - if (n) { - rlen = imsg.hdr.len - IMSG_HEADER_SIZE; - rdata = imsg.data; - - if (imsg.hdr.type != PROC_SCHEDULER_OK) { - log_warnx("warn: scheduler-proc: bad response"); - break; - } - return; - } - - if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) { - log_warn("warn: scheduler-proc: imsg_read"); - break; - } - - if (n == 0) { - log_warnx("warn: scheduler-proc: pipe closed"); - break; - } - } - - fatalx("scheduler-proc: exiting"); -} - -static void -scheduler_proc_read(void *dst, size_t len) -{ - if (len > rlen) { - log_warnx("warn: scheduler-proc: bad msg len"); - fatalx("scheduler-proc: exiting"); - } - - memmove(dst, rdata, len); - rlen -= len; - rdata += len; -} - -static void -scheduler_proc_end(void) -{ - if (rlen) { - log_warnx("warn: scheduler-proc: bogus data"); - fatalx("scheduler-proc: exiting"); - } - imsg_free(&imsg); -} - -/* - * API - */ - -static int -scheduler_proc_init(const char *conf) -{ - int fd, r; - uint32_t version; - - fd = fork_proc_backend("scheduler", conf, "scheduler-proc"); - if (fd == -1) - fatalx("scheduler-proc: exiting"); - - imsg_init(&ibuf, fd); - - version = PROC_SCHEDULER_API_VERSION; - imsg_compose(&ibuf, PROC_SCHEDULER_INIT, 0, 0, -1, - &version, sizeof(version)); - scheduler_proc_call(); - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (1); -} - -static int -scheduler_proc_insert(struct scheduler_info *si) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_INSERT"); - - imsg_compose(&ibuf, PROC_SCHEDULER_INSERT, 0, 0, -1, si, sizeof(*si)); - - scheduler_proc_call(); - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static size_t -scheduler_proc_commit(uint32_t msgid) -{ - size_t s; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_COMMIT"); - - imsg_compose(&ibuf, PROC_SCHEDULER_COMMIT, 0, 0, -1, - &msgid, sizeof(msgid)); - - scheduler_proc_call(); - scheduler_proc_read(&s, sizeof(s)); - scheduler_proc_end(); - - return (s); -} - -static size_t -scheduler_proc_rollback(uint32_t msgid) -{ - size_t s; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_ROLLBACK"); - - imsg_compose(&ibuf, PROC_SCHEDULER_ROLLBACK, 0, 0, -1, - &msgid, sizeof(msgid)); - - scheduler_proc_call(); - scheduler_proc_read(&s, sizeof(s)); - scheduler_proc_end(); - - return (s); -} - -static int -scheduler_proc_update(struct scheduler_info *si) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_UPDATE"); - - imsg_compose(&ibuf, PROC_SCHEDULER_UPDATE, 0, 0, -1, si, sizeof(*si)); - - scheduler_proc_call(); - scheduler_proc_read(&r, sizeof(r)); - if (r == 1) - scheduler_proc_read(si, sizeof(*si)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_delete(uint64_t evpid) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_DELETE"); - - imsg_compose(&ibuf, PROC_SCHEDULER_DELETE, 0, 0, -1, - &evpid, sizeof(evpid)); - - scheduler_proc_call(); - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_hold(uint64_t evpid, uint64_t holdq) -{ - struct ibuf *buf; - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_HOLD"); - - buf = imsg_create(&ibuf, PROC_SCHEDULER_HOLD, 0, 0, - sizeof(evpid) + sizeof(holdq)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &evpid, sizeof(evpid)) == -1) - return (-1); - if (imsg_add(buf, &holdq, sizeof(holdq)) == -1) - return (-1); - imsg_close(&ibuf, buf); - - scheduler_proc_call(); - - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_release(int type, uint64_t holdq, int n) -{ - struct ibuf *buf; - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_RELEASE"); - - buf = imsg_create(&ibuf, PROC_SCHEDULER_RELEASE, 0, 0, - sizeof(holdq) + sizeof(n)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &type, sizeof(type)) == -1) - return (-1); - if (imsg_add(buf, &holdq, sizeof(holdq)) == -1) - return (-1); - if (imsg_add(buf, &n, sizeof(n)) == -1) - return (-1); - imsg_close(&ibuf, buf); - - scheduler_proc_call(); - - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_batch(int typemask, int *delay, size_t *count, uint64_t *evpids, int *types) -{ - struct ibuf *buf; - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_BATCH"); - - buf = imsg_create(&ibuf, PROC_SCHEDULER_BATCH, 0, 0, - sizeof(typemask) + sizeof(*count)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &typemask, sizeof(typemask)) == -1) - return (-1); - if (imsg_add(buf, count, sizeof(*count)) == -1) - return (-1); - imsg_close(&ibuf, buf); - - scheduler_proc_call(); - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_read(delay, sizeof(*delay)); - scheduler_proc_read(count, sizeof(*count)); - if (r > 0) { - scheduler_proc_read(evpids, sizeof(*evpids) * (*count)); - scheduler_proc_read(types, sizeof(*types) * (*count)); - } - scheduler_proc_end(); - - return (r); -} - -static size_t -scheduler_proc_messages(uint32_t from, uint32_t *dst, size_t size) -{ - struct ibuf *buf; - size_t s; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_MESSAGES"); - - buf = imsg_create(&ibuf, PROC_SCHEDULER_MESSAGES, 0, 0, - sizeof(from) + sizeof(size)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &from, sizeof(from)) == -1) - return (-1); - if (imsg_add(buf, &size, sizeof(size)) == -1) - return (-1); - imsg_close(&ibuf, buf); - - scheduler_proc_call(); - - s = rlen / sizeof(*dst); - scheduler_proc_read(dst, s * sizeof(*dst)); - scheduler_proc_end(); - - return (s); -} - -static size_t -scheduler_proc_envelopes(uint64_t from, struct evpstate *dst, size_t size) -{ - struct ibuf *buf; - size_t s; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_ENVELOPES"); - - buf = imsg_create(&ibuf, PROC_SCHEDULER_ENVELOPES, 0, 0, - sizeof(from) + sizeof(size)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &from, sizeof(from)) == -1) - return (-1); - if (imsg_add(buf, &size, sizeof(size)) == -1) - return (-1); - imsg_close(&ibuf, buf); - - scheduler_proc_call(); - - s = rlen / sizeof(*dst); - scheduler_proc_read(dst, s * sizeof(*dst)); - scheduler_proc_end(); - - return (s); -} - -static int -scheduler_proc_schedule(uint64_t evpid) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_SCHEDULE"); - - imsg_compose(&ibuf, PROC_SCHEDULER_SCHEDULE, 0, 0, -1, - &evpid, sizeof(evpid)); - - scheduler_proc_call(); - - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_remove(uint64_t evpid) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_REMOVE"); - - imsg_compose(&ibuf, PROC_SCHEDULER_REMOVE, 0, 0, -1, - &evpid, sizeof(evpid)); - - scheduler_proc_call(); - - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_suspend(uint64_t evpid) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_SUSPEND"); - - imsg_compose(&ibuf, PROC_SCHEDULER_SUSPEND, 0, 0, -1, - &evpid, sizeof(evpid)); - - scheduler_proc_call(); - - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -static int -scheduler_proc_resume(uint64_t evpid) -{ - int r; - - log_debug("debug: scheduler-proc: PROC_SCHEDULER_RESUME"); - - imsg_compose(&ibuf, PROC_SCHEDULER_RESUME, 0, 0, -1, - &evpid, sizeof(evpid)); - - scheduler_proc_call(); - - scheduler_proc_read(&r, sizeof(r)); - scheduler_proc_end(); - - return (r); -} - -struct scheduler_backend scheduler_backend_proc = { - scheduler_proc_init, - scheduler_proc_insert, - scheduler_proc_commit, - scheduler_proc_rollback, - scheduler_proc_update, - scheduler_proc_delete, - scheduler_proc_hold, - scheduler_proc_release, - scheduler_proc_batch, - scheduler_proc_messages, - scheduler_proc_envelopes, - scheduler_proc_schedule, - scheduler_proc_remove, - scheduler_proc_suspend, - scheduler_proc_resume, -}; diff --git a/smtpd/scheduler_ramqueue.c b/smtpd/scheduler_ramqueue.c deleted file mode 100644 index 0c04fc0b..00000000 --- a/smtpd/scheduler_ramqueue.c +++ /dev/null @@ -1,1204 +0,0 @@ -/* $OpenBSD: scheduler_ramqueue.c,v 1.45 2018/05/31 21:06:12 gilles Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -TAILQ_HEAD(evplist, rq_envelope); - -struct rq_message { - uint32_t msgid; - struct tree envelopes; -}; - -struct rq_envelope { - TAILQ_ENTRY(rq_envelope) entry; - SPLAY_ENTRY(rq_envelope) t_entry; - - uint64_t evpid; - uint64_t holdq; - enum delivery_type type; - -#define RQ_EVPSTATE_PENDING 0 -#define RQ_EVPSTATE_SCHEDULED 1 -#define RQ_EVPSTATE_INFLIGHT 2 -#define RQ_EVPSTATE_HELD 3 - uint8_t state; - -#define RQ_ENVELOPE_EXPIRED 0x01 -#define RQ_ENVELOPE_REMOVED 0x02 -#define RQ_ENVELOPE_SUSPEND 0x04 -#define RQ_ENVELOPE_UPDATE 0x08 -#define RQ_ENVELOPE_OVERFLOW 0x10 - uint8_t flags; - - time_t ctime; - time_t sched; - time_t expire; - - struct rq_message *message; - - time_t t_inflight; - time_t t_scheduled; -}; - -struct rq_holdq { - struct evplist q; - size_t count; -}; - -struct rq_queue { - size_t evpcount; - struct tree messages; - SPLAY_HEAD(prioqtree, rq_envelope) q_priotree; - - struct evplist q_pending; - struct evplist q_inflight; - - struct evplist q_mta; - struct evplist q_mda; - struct evplist q_bounce; - struct evplist q_update; - struct evplist q_expired; - struct evplist q_removed; -}; - -static int rq_envelope_cmp(struct rq_envelope *, struct rq_envelope *); - -SPLAY_PROTOTYPE(prioqtree, rq_envelope, t_entry, rq_envelope_cmp); -static int scheduler_ram_init(const char *); -static int scheduler_ram_insert(struct scheduler_info *); -static size_t scheduler_ram_commit(uint32_t); -static size_t scheduler_ram_rollback(uint32_t); -static int scheduler_ram_update(struct scheduler_info *); -static int scheduler_ram_delete(uint64_t); -static int scheduler_ram_hold(uint64_t, uint64_t); -static int scheduler_ram_release(int, uint64_t, int); -static int scheduler_ram_batch(int, int *, size_t *, uint64_t *, int *); -static size_t scheduler_ram_messages(uint32_t, uint32_t *, size_t); -static size_t scheduler_ram_envelopes(uint64_t, struct evpstate *, size_t); -static int scheduler_ram_schedule(uint64_t); -static int scheduler_ram_remove(uint64_t); -static int scheduler_ram_suspend(uint64_t); -static int scheduler_ram_resume(uint64_t); -static int scheduler_ram_query(uint64_t); - -static void sorted_insert(struct rq_queue *, struct rq_envelope *); - -static void rq_queue_init(struct rq_queue *); -static void rq_queue_merge(struct rq_queue *, struct rq_queue *); -static void rq_queue_dump(struct rq_queue *, const char *); -static void rq_queue_schedule(struct rq_queue *rq); -static struct evplist *rq_envelope_list(struct rq_queue *, struct rq_envelope *); -static void rq_envelope_schedule(struct rq_queue *, struct rq_envelope *); -static int rq_envelope_remove(struct rq_queue *, struct rq_envelope *); -static int rq_envelope_suspend(struct rq_queue *, struct rq_envelope *); -static int rq_envelope_resume(struct rq_queue *, struct rq_envelope *); -static void rq_envelope_delete(struct rq_queue *, struct rq_envelope *); -static const char *rq_envelope_to_text(struct rq_envelope *); - -struct scheduler_backend scheduler_backend_ramqueue = { - scheduler_ram_init, - - scheduler_ram_insert, - scheduler_ram_commit, - scheduler_ram_rollback, - - scheduler_ram_update, - scheduler_ram_delete, - scheduler_ram_hold, - scheduler_ram_release, - - scheduler_ram_batch, - - scheduler_ram_messages, - scheduler_ram_envelopes, - scheduler_ram_schedule, - scheduler_ram_remove, - scheduler_ram_suspend, - scheduler_ram_resume, - scheduler_ram_query, -}; - -static struct rq_queue ramqueue; -static struct tree updates; -static struct tree holdqs[3]; /* delivery type */ - -static time_t currtime; - -#define BACKOFF_TRANSFER 400 -#define BACKOFF_DELIVERY 10 -#define BACKOFF_OVERFLOW 3 - -static time_t -scheduler_backoff(time_t t0, time_t base, uint32_t step) -{ - return (t0 + base * step * step); -} - -static time_t -scheduler_next(time_t t0, time_t base, uint32_t step) -{ - time_t t; - - /* XXX be more efficient */ - while ((t = scheduler_backoff(t0, base, step)) <= currtime) - step++; - - return (t); -} - -static int -scheduler_ram_init(const char *arg) -{ - rq_queue_init(&ramqueue); - tree_init(&updates); - tree_init(&holdqs[D_MDA]); - tree_init(&holdqs[D_MTA]); - tree_init(&holdqs[D_BOUNCE]); - - return (1); -} - -static int -scheduler_ram_insert(struct scheduler_info *si) -{ - struct rq_queue *update; - struct rq_message *message; - struct rq_envelope *envelope; - uint32_t msgid; - - currtime = time(NULL); - - msgid = evpid_to_msgid(si->evpid); - - /* find/prepare a ramqueue update */ - if ((update = tree_get(&updates, msgid)) == NULL) { - update = xcalloc(1, sizeof *update); - stat_increment("scheduler.ramqueue.update", 1); - rq_queue_init(update); - tree_xset(&updates, msgid, update); - } - - /* find/prepare the msgtree message in ramqueue update */ - if ((message = tree_get(&update->messages, msgid)) == NULL) { - message = xcalloc(1, sizeof *message); - message->msgid = msgid; - tree_init(&message->envelopes); - tree_xset(&update->messages, msgid, message); - stat_increment("scheduler.ramqueue.message", 1); - } - - /* create envelope in ramqueue message */ - envelope = xcalloc(1, sizeof *envelope); - envelope->evpid = si->evpid; - envelope->type = si->type; - envelope->message = message; - envelope->ctime = si->creation; - envelope->expire = si->creation + si->ttl; - envelope->sched = scheduler_backoff(si->creation, - (si->type == D_MTA) ? BACKOFF_TRANSFER : BACKOFF_DELIVERY, si->retry); - tree_xset(&message->envelopes, envelope->evpid, envelope); - - update->evpcount++; - stat_increment("scheduler.ramqueue.envelope", 1); - - envelope->state = RQ_EVPSTATE_PENDING; - TAILQ_INSERT_TAIL(&update->q_pending, envelope, entry); - - si->nexttry = envelope->sched; - - return (1); -} - -static size_t -scheduler_ram_commit(uint32_t msgid) -{ - struct rq_queue *update; - size_t r; - - currtime = time(NULL); - - update = tree_xpop(&updates, msgid); - r = update->evpcount; - - if (tracing & TRACE_SCHEDULER) - rq_queue_dump(update, "update to commit"); - - rq_queue_merge(&ramqueue, update); - - if (tracing & TRACE_SCHEDULER) - rq_queue_dump(&ramqueue, "resulting queue"); - - rq_queue_schedule(&ramqueue); - - free(update); - stat_decrement("scheduler.ramqueue.update", 1); - - return (r); -} - -static size_t -scheduler_ram_rollback(uint32_t msgid) -{ - struct rq_queue *update; - struct rq_envelope *evp; - size_t r; - - currtime = time(NULL); - - if ((update = tree_pop(&updates, msgid)) == NULL) - return (0); - r = update->evpcount; - - while ((evp = TAILQ_FIRST(&update->q_pending))) { - TAILQ_REMOVE(&update->q_pending, evp, entry); - rq_envelope_delete(update, evp); - } - - free(update); - stat_decrement("scheduler.ramqueue.update", 1); - - return (r); -} - -static int -scheduler_ram_update(struct scheduler_info *si) -{ - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - - currtime = time(NULL); - - msgid = evpid_to_msgid(si->evpid); - msg = tree_xget(&ramqueue.messages, msgid); - evp = tree_xget(&msg->envelopes, si->evpid); - - /* it *must* be in-flight */ - if (evp->state != RQ_EVPSTATE_INFLIGHT) - errx(1, "evp:%016" PRIx64 " not in-flight", si->evpid); - - TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); - - /* - * If the envelope was removed while inflight, schedule it for - * removal immediately. - */ - if (evp->flags & RQ_ENVELOPE_REMOVED) { - TAILQ_INSERT_TAIL(&ramqueue.q_removed, evp, entry); - evp->state = RQ_EVPSTATE_SCHEDULED; - evp->t_scheduled = currtime; - return (1); - } - - evp->sched = scheduler_next(evp->ctime, - (si->type == D_MTA) ? BACKOFF_TRANSFER : BACKOFF_DELIVERY, si->retry); - - evp->state = RQ_EVPSTATE_PENDING; - if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) - sorted_insert(&ramqueue, evp); - - si->nexttry = evp->sched; - - return (1); -} - -static int -scheduler_ram_delete(uint64_t evpid) -{ - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - - currtime = time(NULL); - - msgid = evpid_to_msgid(evpid); - msg = tree_xget(&ramqueue.messages, msgid); - evp = tree_xget(&msg->envelopes, evpid); - - /* it *must* be in-flight */ - if (evp->state != RQ_EVPSTATE_INFLIGHT) - errx(1, "evp:%016" PRIx64 " not in-flight", evpid); - - TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); - - rq_envelope_delete(&ramqueue, evp); - - return (1); -} - -#define HOLDQ_MAXSIZE 1000 - -static int -scheduler_ram_hold(uint64_t evpid, uint64_t holdq) -{ - struct rq_holdq *hq; - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - - currtime = time(NULL); - - msgid = evpid_to_msgid(evpid); - msg = tree_xget(&ramqueue.messages, msgid); - evp = tree_xget(&msg->envelopes, evpid); - - /* it *must* be in-flight */ - if (evp->state != RQ_EVPSTATE_INFLIGHT) - errx(1, "evp:%016" PRIx64 " not in-flight", evpid); - - TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); - - /* If the envelope is suspended, just mark it as pending */ - if (evp->flags & RQ_ENVELOPE_SUSPEND) { - evp->state = RQ_EVPSTATE_PENDING; - return (0); - } - - hq = tree_get(&holdqs[evp->type], holdq); - if (hq == NULL) { - hq = xcalloc(1, sizeof(*hq)); - TAILQ_INIT(&hq->q); - tree_xset(&holdqs[evp->type], holdq, hq); - stat_increment("scheduler.ramqueue.holdq", 1); - } - - /* If the holdq is full, just "tempfail" the envelope */ - if (hq->count >= HOLDQ_MAXSIZE) { - evp->state = RQ_EVPSTATE_PENDING; - evp->flags |= RQ_ENVELOPE_UPDATE; - evp->flags |= RQ_ENVELOPE_OVERFLOW; - sorted_insert(&ramqueue, evp); - stat_increment("scheduler.ramqueue.hold-overflow", 1); - return (0); - } - - evp->state = RQ_EVPSTATE_HELD; - evp->holdq = holdq; - /* This is an optimization: upon release, the envelopes will be - * inserted in the pending queue from the first element to the last. - * Since elements already in the queue were received first, they - * were scheduled first, so they will be reinserted before the - * current element. - */ - TAILQ_INSERT_HEAD(&hq->q, evp, entry); - hq->count += 1; - stat_increment("scheduler.ramqueue.hold", 1); - - return (1); -} - -static int -scheduler_ram_release(int type, uint64_t holdq, int n) -{ - struct rq_holdq *hq; - struct rq_envelope *evp; - int i, update; - - currtime = time(NULL); - - hq = tree_get(&holdqs[type], holdq); - if (hq == NULL) - return (0); - - if (n == -1) { - n = 0; - update = 1; - } - else - update = 0; - - for (i = 0; n == 0 || i < n; i++) { - evp = TAILQ_FIRST(&hq->q); - if (evp == NULL) - break; - - TAILQ_REMOVE(&hq->q, evp, entry); - hq->count -= 1; - evp->holdq = 0; - - /* When released, all envelopes are put in the pending queue - * and will be rescheduled immediately. As an optimization, - * we could just schedule them directly. - */ - evp->state = RQ_EVPSTATE_PENDING; - if (update) - evp->flags |= RQ_ENVELOPE_UPDATE; - sorted_insert(&ramqueue, evp); - } - - if (TAILQ_EMPTY(&hq->q)) { - tree_xpop(&holdqs[type], holdq); - free(hq); - stat_decrement("scheduler.ramqueue.holdq", 1); - } - stat_decrement("scheduler.ramqueue.hold", i); - - return (i); -} - -static int -scheduler_ram_batch(int mask, int *delay, size_t *count, uint64_t *evpids, int *types) -{ - struct rq_envelope *evp; - size_t i, n; - time_t t; - - currtime = time(NULL); - - rq_queue_schedule(&ramqueue); - if (tracing & TRACE_SCHEDULER) - rq_queue_dump(&ramqueue, "scheduler_ram_batch()"); - - i = 0; - n = 0; - - for (;;) { - - if (mask & SCHED_REMOVE && (evp = TAILQ_FIRST(&ramqueue.q_removed))) { - TAILQ_REMOVE(&ramqueue.q_removed, evp, entry); - types[i] = SCHED_REMOVE; - evpids[i] = evp->evpid; - rq_envelope_delete(&ramqueue, evp); - - if (++i == *count) - break; - } - - if (mask & SCHED_EXPIRE && (evp = TAILQ_FIRST(&ramqueue.q_expired))) { - TAILQ_REMOVE(&ramqueue.q_expired, evp, entry); - types[i] = SCHED_EXPIRE; - evpids[i] = evp->evpid; - rq_envelope_delete(&ramqueue, evp); - - if (++i == *count) - break; - } - - if (mask & SCHED_UPDATE && (evp = TAILQ_FIRST(&ramqueue.q_update))) { - TAILQ_REMOVE(&ramqueue.q_update, evp, entry); - types[i] = SCHED_UPDATE; - evpids[i] = evp->evpid; - - if (evp->flags & RQ_ENVELOPE_OVERFLOW) - t = BACKOFF_OVERFLOW; - else if (evp->type == D_MTA) - t = BACKOFF_TRANSFER; - else - t = BACKOFF_DELIVERY; - - evp->sched = scheduler_next(evp->ctime, t, 0); - evp->flags &= ~(RQ_ENVELOPE_UPDATE|RQ_ENVELOPE_OVERFLOW); - evp->state = RQ_EVPSTATE_PENDING; - if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) - sorted_insert(&ramqueue, evp); - - if (++i == *count) - break; - } - - if (mask & SCHED_BOUNCE && (evp = TAILQ_FIRST(&ramqueue.q_bounce))) { - TAILQ_REMOVE(&ramqueue.q_bounce, evp, entry); - types[i] = SCHED_BOUNCE; - evpids[i] = evp->evpid; - - TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); - evp->state = RQ_EVPSTATE_INFLIGHT; - evp->t_inflight = currtime; - - if (++i == *count) - break; - } - - if (mask & SCHED_MDA && (evp = TAILQ_FIRST(&ramqueue.q_mda))) { - TAILQ_REMOVE(&ramqueue.q_mda, evp, entry); - types[i] = SCHED_MDA; - evpids[i] = evp->evpid; - - TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); - evp->state = RQ_EVPSTATE_INFLIGHT; - evp->t_inflight = currtime; - - if (++i == *count) - break; - } - - if (mask & SCHED_MTA && (evp = TAILQ_FIRST(&ramqueue.q_mta))) { - TAILQ_REMOVE(&ramqueue.q_mta, evp, entry); - types[i] = SCHED_MTA; - evpids[i] = evp->evpid; - - TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); - evp->state = RQ_EVPSTATE_INFLIGHT; - evp->t_inflight = currtime; - - if (++i == *count) - break; - } - - /* nothing seen this round */ - if (i == n) - break; - - n = i; - } - - if (i) { - *count = i; - return (1); - } - - if ((evp = TAILQ_FIRST(&ramqueue.q_pending))) { - if (evp->sched < evp->expire) - t = evp->sched; - else - t = evp->expire; - *delay = (t < currtime) ? 0 : (t - currtime); - } - else - *delay = -1; - - return (0); -} - -static size_t -scheduler_ram_messages(uint32_t from, uint32_t *dst, size_t size) -{ - uint64_t id; - size_t n; - void *i; - - for (n = 0, i = NULL; n < size; n++) { - if (tree_iterfrom(&ramqueue.messages, &i, from, &id, NULL) == 0) - break; - dst[n] = id; - } - - return (n); -} - -static size_t -scheduler_ram_envelopes(uint64_t from, struct evpstate *dst, size_t size) -{ - struct rq_message *msg; - struct rq_envelope *evp; - void *i; - size_t n; - - if ((msg = tree_get(&ramqueue.messages, evpid_to_msgid(from))) == NULL) - return (0); - - for (n = 0, i = NULL; n < size; ) { - - if (tree_iterfrom(&msg->envelopes, &i, from, NULL, - (void**)&evp) == 0) - break; - - if (evp->flags & (RQ_ENVELOPE_REMOVED | RQ_ENVELOPE_EXPIRED)) - continue; - - dst[n].evpid = evp->evpid; - dst[n].flags = 0; - dst[n].retry = 0; - dst[n].time = 0; - - if (evp->state == RQ_EVPSTATE_PENDING) { - dst[n].time = evp->sched; - dst[n].flags = EF_PENDING; - } - else if (evp->state == RQ_EVPSTATE_SCHEDULED) { - dst[n].time = evp->t_scheduled; - dst[n].flags = EF_PENDING; - } - else if (evp->state == RQ_EVPSTATE_INFLIGHT) { - dst[n].time = evp->t_inflight; - dst[n].flags = EF_INFLIGHT; - } - else if (evp->state == RQ_EVPSTATE_HELD) { - /* same as scheduled */ - dst[n].time = evp->t_scheduled; - dst[n].flags = EF_PENDING; - dst[n].flags |= EF_HOLD; - } - if (evp->flags & RQ_ENVELOPE_SUSPEND) - dst[n].flags |= EF_SUSPEND; - - n++; - } - - return (n); -} - -static int -scheduler_ram_schedule(uint64_t evpid) -{ - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - void *i; - int r; - - currtime = time(NULL); - - if (evpid > 0xffffffff) { - msgid = evpid_to_msgid(evpid); - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) - return (0); - if (evp->state == RQ_EVPSTATE_INFLIGHT) - return (0); - rq_envelope_schedule(&ramqueue, evp); - return (1); - } - else { - msgid = evpid; - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - i = NULL; - r = 0; - while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) { - if (evp->state == RQ_EVPSTATE_INFLIGHT) - continue; - rq_envelope_schedule(&ramqueue, evp); - r++; - } - return (r); - } -} - -static int -scheduler_ram_remove(uint64_t evpid) -{ - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - void *i; - int r; - - currtime = time(NULL); - - if (evpid > 0xffffffff) { - msgid = evpid_to_msgid(evpid); - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) - return (0); - if (rq_envelope_remove(&ramqueue, evp)) - return (1); - return (0); - } - else { - msgid = evpid; - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - i = NULL; - r = 0; - while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) - if (rq_envelope_remove(&ramqueue, evp)) - r++; - return (r); - } -} - -static int -scheduler_ram_suspend(uint64_t evpid) -{ - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - void *i; - int r; - - currtime = time(NULL); - - if (evpid > 0xffffffff) { - msgid = evpid_to_msgid(evpid); - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) - return (0); - if (rq_envelope_suspend(&ramqueue, evp)) - return (1); - return (0); - } - else { - msgid = evpid; - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - i = NULL; - r = 0; - while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) - if (rq_envelope_suspend(&ramqueue, evp)) - r++; - return (r); - } -} - -static int -scheduler_ram_resume(uint64_t evpid) -{ - struct rq_message *msg; - struct rq_envelope *evp; - uint32_t msgid; - void *i; - int r; - - currtime = time(NULL); - - if (evpid > 0xffffffff) { - msgid = evpid_to_msgid(evpid); - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) - return (0); - if (rq_envelope_resume(&ramqueue, evp)) - return (1); - return (0); - } - else { - msgid = evpid; - if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) - return (0); - i = NULL; - r = 0; - while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) - if (rq_envelope_resume(&ramqueue, evp)) - r++; - return (r); - } -} - -static int -scheduler_ram_query(uint64_t evpid) -{ - uint32_t msgid; - - if (evpid > 0xffffffff) - msgid = evpid_to_msgid(evpid); - else - msgid = evpid; - - if (tree_get(&ramqueue.messages, msgid) == NULL) - return (0); - - return (1); -} - -static void -sorted_insert(struct rq_queue *rq, struct rq_envelope *evp) -{ - struct rq_envelope *evp2; - - SPLAY_INSERT(prioqtree, &rq->q_priotree, evp); - evp2 = SPLAY_NEXT(prioqtree, &rq->q_priotree, evp); - if (evp2) - TAILQ_INSERT_BEFORE(evp2, evp, entry); - else - TAILQ_INSERT_TAIL(&rq->q_pending, evp, entry); -} - -static void -rq_queue_init(struct rq_queue *rq) -{ - memset(rq, 0, sizeof *rq); - tree_init(&rq->messages); - TAILQ_INIT(&rq->q_pending); - TAILQ_INIT(&rq->q_inflight); - TAILQ_INIT(&rq->q_mta); - TAILQ_INIT(&rq->q_mda); - TAILQ_INIT(&rq->q_bounce); - TAILQ_INIT(&rq->q_update); - TAILQ_INIT(&rq->q_expired); - TAILQ_INIT(&rq->q_removed); - SPLAY_INIT(&rq->q_priotree); -} - -static void -rq_queue_merge(struct rq_queue *rq, struct rq_queue *update) -{ - struct rq_message *message, *tomessage; - struct rq_envelope *envelope; - uint64_t id; - void *i; - - while (tree_poproot(&update->messages, &id, (void*)&message)) { - if ((tomessage = tree_get(&rq->messages, id)) == NULL) { - /* message does not exist. re-use structure */ - tree_xset(&rq->messages, id, message); - continue; - } - /* need to re-link all envelopes before merging them */ - i = NULL; - while ((tree_iter(&message->envelopes, &i, &id, - (void*)&envelope))) - envelope->message = tomessage; - tree_merge(&tomessage->envelopes, &message->envelopes); - free(message); - stat_decrement("scheduler.ramqueue.message", 1); - } - - /* Sorted insert in the pending queue */ - while ((envelope = TAILQ_FIRST(&update->q_pending))) { - TAILQ_REMOVE(&update->q_pending, envelope, entry); - sorted_insert(rq, envelope); - } - - rq->evpcount += update->evpcount; -} - -#define SCHEDULEMAX 1024 - -static void -rq_queue_schedule(struct rq_queue *rq) -{ - struct rq_envelope *evp; - size_t n; - - n = 0; - while ((evp = TAILQ_FIRST(&rq->q_pending))) { - if (evp->sched > currtime && evp->expire > currtime) - break; - - if (n == SCHEDULEMAX) - break; - - if (evp->state != RQ_EVPSTATE_PENDING) - errx(1, "evp:%016" PRIx64 " flags=0x%x", evp->evpid, - evp->flags); - - if (evp->expire <= currtime) { - TAILQ_REMOVE(&rq->q_pending, evp, entry); - SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); - TAILQ_INSERT_TAIL(&rq->q_expired, evp, entry); - evp->state = RQ_EVPSTATE_SCHEDULED; - evp->flags |= RQ_ENVELOPE_EXPIRED; - evp->t_scheduled = currtime; - continue; - } - rq_envelope_schedule(rq, evp); - n += 1; - } -} - -static struct evplist * -rq_envelope_list(struct rq_queue *rq, struct rq_envelope *evp) -{ - switch (evp->state) { - case RQ_EVPSTATE_PENDING: - return &rq->q_pending; - - case RQ_EVPSTATE_SCHEDULED: - if (evp->flags & RQ_ENVELOPE_EXPIRED) - return &rq->q_expired; - if (evp->flags & RQ_ENVELOPE_REMOVED) - return &rq->q_removed; - if (evp->flags & RQ_ENVELOPE_UPDATE) - return &rq->q_update; - if (evp->type == D_MTA) - return &rq->q_mta; - if (evp->type == D_MDA) - return &rq->q_mda; - if (evp->type == D_BOUNCE) - return &rq->q_bounce; - errx(1, "%016" PRIx64 " bad evp type %d", evp->evpid, evp->type); - - case RQ_EVPSTATE_INFLIGHT: - return &rq->q_inflight; - - case RQ_EVPSTATE_HELD: - return (NULL); - } - - errx(1, "%016" PRIx64 " bad state %d", evp->evpid, evp->state); - return (NULL); -} - -static void -rq_envelope_schedule(struct rq_queue *rq, struct rq_envelope *evp) -{ - struct rq_holdq *hq; - struct evplist *q = NULL; - - switch (evp->type) { - case D_MTA: - q = &rq->q_mta; - break; - case D_MDA: - q = &rq->q_mda; - break; - case D_BOUNCE: - q = &rq->q_bounce; - break; - } - - if (evp->flags & RQ_ENVELOPE_UPDATE) - q = &rq->q_update; - - if (evp->state == RQ_EVPSTATE_HELD) { - hq = tree_xget(&holdqs[evp->type], evp->holdq); - TAILQ_REMOVE(&hq->q, evp, entry); - hq->count -= 1; - if (TAILQ_EMPTY(&hq->q)) { - tree_xpop(&holdqs[evp->type], evp->holdq); - free(hq); - } - evp->holdq = 0; - stat_decrement("scheduler.ramqueue.hold", 1); - } - else if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) { - TAILQ_REMOVE(&rq->q_pending, evp, entry); - SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); - } - - TAILQ_INSERT_TAIL(q, evp, entry); - evp->state = RQ_EVPSTATE_SCHEDULED; - evp->t_scheduled = currtime; -} - -static int -rq_envelope_remove(struct rq_queue *rq, struct rq_envelope *evp) -{ - struct rq_holdq *hq; - struct evplist *evl; - - if (evp->flags & (RQ_ENVELOPE_REMOVED | RQ_ENVELOPE_EXPIRED)) - return (0); - /* - * If envelope is inflight, mark it envelope for removal. - */ - if (evp->state == RQ_EVPSTATE_INFLIGHT) { - evp->flags |= RQ_ENVELOPE_REMOVED; - return (1); - } - - if (evp->state == RQ_EVPSTATE_HELD) { - hq = tree_xget(&holdqs[evp->type], evp->holdq); - TAILQ_REMOVE(&hq->q, evp, entry); - hq->count -= 1; - if (TAILQ_EMPTY(&hq->q)) { - tree_xpop(&holdqs[evp->type], evp->holdq); - free(hq); - } - evp->holdq = 0; - stat_decrement("scheduler.ramqueue.hold", 1); - } - else if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) { - evl = rq_envelope_list(rq, evp); - TAILQ_REMOVE(evl, evp, entry); - if (evl == &rq->q_pending) - SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); - } - - TAILQ_INSERT_TAIL(&rq->q_removed, evp, entry); - evp->state = RQ_EVPSTATE_SCHEDULED; - evp->flags |= RQ_ENVELOPE_REMOVED; - evp->t_scheduled = currtime; - - return (1); -} - -static int -rq_envelope_suspend(struct rq_queue *rq, struct rq_envelope *evp) -{ - struct rq_holdq *hq; - struct evplist *evl; - - if (evp->flags & RQ_ENVELOPE_SUSPEND) - return (0); - - if (evp->state == RQ_EVPSTATE_HELD) { - hq = tree_xget(&holdqs[evp->type], evp->holdq); - TAILQ_REMOVE(&hq->q, evp, entry); - hq->count -= 1; - if (TAILQ_EMPTY(&hq->q)) { - tree_xpop(&holdqs[evp->type], evp->holdq); - free(hq); - } - evp->holdq = 0; - evp->state = RQ_EVPSTATE_PENDING; - stat_decrement("scheduler.ramqueue.hold", 1); - } - else if (evp->state != RQ_EVPSTATE_INFLIGHT) { - evl = rq_envelope_list(rq, evp); - TAILQ_REMOVE(evl, evp, entry); - if (evl == &rq->q_pending) - SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); - } - - evp->flags |= RQ_ENVELOPE_SUSPEND; - - return (1); -} - -static int -rq_envelope_resume(struct rq_queue *rq, struct rq_envelope *evp) -{ - struct evplist *evl; - - if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) - return (0); - - if (evp->state != RQ_EVPSTATE_INFLIGHT) { - evl = rq_envelope_list(rq, evp); - if (evl == &rq->q_pending) - sorted_insert(rq, evp); - else - TAILQ_INSERT_TAIL(evl, evp, entry); - } - - evp->flags &= ~RQ_ENVELOPE_SUSPEND; - - return (1); -} - -static void -rq_envelope_delete(struct rq_queue *rq, struct rq_envelope *evp) -{ - tree_xpop(&evp->message->envelopes, evp->evpid); - if (tree_empty(&evp->message->envelopes)) { - tree_xpop(&rq->messages, evp->message->msgid); - free(evp->message); - stat_decrement("scheduler.ramqueue.message", 1); - } - - free(evp); - rq->evpcount--; - stat_decrement("scheduler.ramqueue.envelope", 1); -} - -static const char * -rq_envelope_to_text(struct rq_envelope *e) -{ - static char buf[256]; - char t[64]; - - (void)snprintf(buf, sizeof buf, "evp:%016" PRIx64 " [", e->evpid); - - if (e->type == D_BOUNCE) - (void)strlcat(buf, "bounce", sizeof buf); - else if (e->type == D_MDA) - (void)strlcat(buf, "mda", sizeof buf); - else if (e->type == D_MTA) - (void)strlcat(buf, "mta", sizeof buf); - - (void)snprintf(t, sizeof t, ",expire=%s", - duration_to_text(e->expire - currtime)); - (void)strlcat(buf, t, sizeof buf); - - - switch (e->state) { - case RQ_EVPSTATE_PENDING: - (void)snprintf(t, sizeof t, ",pending=%s", - duration_to_text(e->sched - currtime)); - (void)strlcat(buf, t, sizeof buf); - break; - - case RQ_EVPSTATE_SCHEDULED: - (void)snprintf(t, sizeof t, ",scheduled=%s", - duration_to_text(currtime - e->t_scheduled)); - (void)strlcat(buf, t, sizeof buf); - break; - - case RQ_EVPSTATE_INFLIGHT: - (void)snprintf(t, sizeof t, ",inflight=%s", - duration_to_text(currtime - e->t_inflight)); - (void)strlcat(buf, t, sizeof buf); - break; - - case RQ_EVPSTATE_HELD: - (void)snprintf(t, sizeof t, ",held=%s", - duration_to_text(currtime - e->t_inflight)); - (void)strlcat(buf, t, sizeof buf); - break; - default: - errx(1, "%016" PRIx64 " bad state %d", e->evpid, e->state); - } - - if (e->flags & RQ_ENVELOPE_REMOVED) - (void)strlcat(buf, ",removed", sizeof buf); - if (e->flags & RQ_ENVELOPE_EXPIRED) - (void)strlcat(buf, ",expired", sizeof buf); - if (e->flags & RQ_ENVELOPE_SUSPEND) - (void)strlcat(buf, ",suspended", sizeof buf); - - (void)strlcat(buf, "]", sizeof buf); - - return (buf); -} - -static void -rq_queue_dump(struct rq_queue *rq, const char * name) -{ - struct rq_message *message; - struct rq_envelope *envelope; - void *i, *j; - uint64_t id; - - log_debug("debug: /--- ramqueue: %s", name); - - i = NULL; - while ((tree_iter(&rq->messages, &i, &id, (void*)&message))) { - log_debug("debug: | msg:%08" PRIx32, message->msgid); - j = NULL; - while ((tree_iter(&message->envelopes, &j, &id, - (void*)&envelope))) - log_debug("debug: | %s", - rq_envelope_to_text(envelope)); - } - log_debug("debug: \\---"); -} - -static int -rq_envelope_cmp(struct rq_envelope *e1, struct rq_envelope *e2) -{ - time_t ref1, ref2; - - ref1 = (e1->sched < e1->expire) ? e1->sched : e1->expire; - ref2 = (e2->sched < e2->expire) ? e2->sched : e2->expire; - if (ref1 != ref2) - return (ref1 < ref2) ? -1 : 1; - - if (e1->evpid != e2->evpid) - return (e1->evpid < e2->evpid) ? -1 : 1; - - return 0; -} - -SPLAY_GENERATE(prioqtree, rq_envelope, t_entry, rq_envelope_cmp); diff --git a/smtpd/sendmail.8 b/smtpd/sendmail.8 deleted file mode 100644 index 1696a861..00000000 --- a/smtpd/sendmail.8 +++ /dev/null @@ -1,86 +0,0 @@ -.\" $OpenBSD: sendmail.8,v 1.4 2015/10/23 15:48:16 jung Exp $ -.\" -.\" Copyright (C) 2013 Ryan Kavanagh -.\" All rights reserved. -.\" -.\" Permission to use, copy, modify, and/or distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.Dd $Mdocdate: October 23 2015 $ -.Dt SENDMAIL 8 -.Os -.Sh NAME -.Nm sendmail -.Nd a mail enqueuer for -.Xr smtpd 8 -.Sh SYNOPSIS -.Nm -.Op Fl tv -.Op Fl F Ar name -.Op Fl f Ar from -.Ar to ... -.Sh DESCRIPTION -The -.Nm -utility is a local enqueuer for the -.Xr smtpd 8 -daemon, -compatible with -.Xr mailwrapper 8 . -The message is read on standard input (stdin) until -.Nm -encounters an end-of-file. -The -.Nm -enqueuer is not intended to be used directly to send mail, -but rather via a frontend known as a mail user agent. -.Pp -Unless the optional -.Fl t -flag is specified, -one or more recipients must be specified on the command line. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl F Ar name -Set the sender's full name. -.It Fl f Ar from -Set the sender's address. -.It Fl t -Read the message's To:, Cc:, and Bcc: fields for recipients. -The Bcc: field will be deleted before sending. -.It Fl v -Enable verbose output. -.El -.Pp -To maintain compatibility with Sendmail, Inc.'s implementation of -.Nm , -various other flags are accepted, -but have no effect. -.Sh EXIT STATUS -.Ex -std -.Sh SEE ALSO -.Xr smtpctl 8 , -.Xr smtpd 8 -.Sh AUTHORS -.Sy OpenSMTPD -is primarily developed by Gilles Chehade, -Eric Faurot, -and Charles Longeau, -with contributions from various -.Ox -hackers. -It is distributed under the ISC license. -.Pp -This manpage was written by -.An Ryan Kavanagh -.Aq Mt rak@debian.org -for the Debian project and is distributed under the ISC license. diff --git a/smtpd/smtp.1 b/smtpd/smtp.1 deleted file mode 100644 index 3cc03844..00000000 --- a/smtpd/smtp.1 +++ /dev/null @@ -1,96 +0,0 @@ -.\" $OpenBSD: smtp.1,v 1.7 2018/07/04 08:23:43 jmc Exp $ -.\" -.\" Copyright (c) 2018, Eric Faurot -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: July 4 2018 $ -.Dt SMTP 1 -.Os -.Sh NAME -.Nm smtp -.Nd Simple Mail Transfer Protocol client -.Sh SYNOPSIS -.Nm -.Op Fl Chnv -.Op Fl F Ar from -.Op Fl H Ar helo -.Op Fl s Ar server -.Op Ar recipient ... -.Sh DESCRIPTION -The -.Nm -utility is a Simple Mail Transfer Protocol -.Pq SMTP -client which can be used to run an SMTP transaction against an SMTP server. -.Pp -By default, -.Nm -reads the mail content from the standard input, establishes an SMTP session, -and runs an SMTP transaction for all the specified recipients. -The content is sent unaltered as mail data. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl C -Do not require server certificate to be valid. -.It Fl F Ar from -Set the return-path (MAIL FROM) for the SMTP transaction. -Default to the current username. -.It Fl H Ar helo -Define the hostname to advertise (HELO) when establishing the SMTP session. -.It Fl h -Display version and usage. -.It Fl n -Do not actually execute a transaction, -just try to establish an SMTP session and quit. -When this option is given, no message is read from the standard input. -.It Fl s Ar server -Specify the server to connect to and connection parameters. -The format is -.Sm off -.Op Ar proto No :// Op Ar user : pass No @ -.Ar host Op : Ar port . -.Sm on -The following protocols are available: -.Pp -.Bl -tag -width "smtp+notls" -compact -.It smtp -Normal SMTP session with opportunistic STARTTLS. -.It smtp+tls -Normal SMTP session with mandatory STARTTLS. -.It smtp+notls -Plain text SMTP session without TLS. -.It lmtp -LMTP session with opportunistic STARTTLS. -.It lmtp+tls -LMTP session with mandatory STARTTLS. -.It lmtp+notls -Plain text LMTP session without TLS. -.It smtps -SMTP session with forced TLS on connection. -.El -.Pp -Defaults to -.Dq smtp://localhost:25 . -.It Fl v -Be more verbose. -This option can be specified multiple times. -.El -.Sh SEE ALSO -.Xr smtpd 8 -.Sh HISTORY -The -.Nm -program first appeared in -.Ox 6.4 . diff --git a/smtpd/smtp.c b/smtpd/smtp.c deleted file mode 100644 index 602fd0d6..00000000 --- a/smtpd/smtp.c +++ /dev/null @@ -1,387 +0,0 @@ -/* $OpenBSD: smtp.c,v 1.166 2019/08/10 16:07:01 gilles Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2009 Jacek Masiulaniec - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - -static void smtp_setup_events(void); -static void smtp_pause(void); -static void smtp_resume(void); -static void smtp_accept(int, short, void *); -static void smtp_dropped(struct listener *, int, const struct sockaddr_storage *); -static int smtp_enqueue(void); -static int smtp_can_accept(void); -static void smtp_setup_listeners(void); -static int smtp_sni_callback(SSL *, int *, void *); - -int -proxy_session(struct listener *listener, int sock, - const struct sockaddr_storage *ss, - void (*accepted)(struct listener *, int, - const struct sockaddr_storage *, struct io *), - void (*dropped)(struct listener *, int, - const struct sockaddr_storage *)); - -static void smtp_accepted(struct listener *, int, const struct sockaddr_storage *, struct io *); - - -#define SMTP_FD_RESERVE 5 -#define getdtablecount() 0 - -static size_t sessions; -static size_t maxsessions; - -void -smtp_imsg(struct mproc *p, struct imsg *imsg) -{ - switch (imsg->hdr.type) { - case IMSG_SMTP_CHECK_SENDER: - case IMSG_SMTP_EXPAND_RCPT: - case IMSG_SMTP_LOOKUP_HELO: - case IMSG_SMTP_AUTHENTICATE: - case IMSG_FILTER_SMTP_PROTOCOL: - case IMSG_FILTER_SMTP_DATA_BEGIN: - smtp_session_imsg(p, imsg); - return; - - case IMSG_SMTP_MESSAGE_COMMIT: - case IMSG_SMTP_MESSAGE_CREATE: - case IMSG_SMTP_MESSAGE_OPEN: - case IMSG_QUEUE_ENVELOPE_SUBMIT: - case IMSG_QUEUE_ENVELOPE_COMMIT: - smtp_session_imsg(p, imsg); - return; - - case IMSG_QUEUE_SMTP_SESSION: - m_compose(p, IMSG_QUEUE_SMTP_SESSION, 0, 0, smtp_enqueue(), - imsg->data, imsg->hdr.len - sizeof imsg->hdr); - return; - - case IMSG_CTL_SMTP_SESSION: - m_compose(p, IMSG_CTL_SMTP_SESSION, imsg->hdr.peerid, 0, - smtp_enqueue(), NULL, 0); - return; - - case IMSG_CTL_PAUSE_SMTP: - log_debug("debug: smtp: pausing listening sockets"); - smtp_pause(); - env->sc_flags |= SMTPD_SMTP_PAUSED; - return; - - case IMSG_CTL_RESUME_SMTP: - log_debug("debug: smtp: resuming listening sockets"); - env->sc_flags &= ~SMTPD_SMTP_PAUSED; - smtp_resume(); - return; - } - - errx(1, "smtp_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); -} - -void -smtp_postfork(void) -{ - smtp_setup_listeners(); -} - -void -smtp_postprivdrop(void) -{ -} - -void -smtp_configure(void) -{ - smtp_setup_events(); -} - -static void -smtp_setup_listeners(void) -{ - struct listener *l; - int opt; - - TAILQ_FOREACH(l, env->sc_listeners, entry) { - if ((l->fd = socket(l->ss.ss_family, SOCK_STREAM, 0)) == -1) { - if (errno == EAFNOSUPPORT) { - log_warn("smtpd: socket"); - continue; - } - fatal("smtpd: socket"); - } - opt = 1; -#ifdef SO_REUSEADDR - if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt, - sizeof(opt)) == -1) - fatal("smtpd: setsockopt"); -#else - if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEPORT, &opt, - sizeof(opt)) < 0) - fatal("smtpd: setsockopt"); -#endif -#ifdef IPV6_V6ONLY - /* - * If using IPv6, bind only to IPv6 if possible. - * This avoids ambiguities with IPv4-mapped IPv6 addresses. - */ - if (l->ss.ss_family == AF_INET6) - if (setsockopt(l->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, - sizeof(opt)) < 0) - fatal("smtpd: setsockopt"); -#endif - if (bind(l->fd, (struct sockaddr *)&l->ss, SS_LEN(&l->ss)) == -1) - fatal("smtpd: bind"); - } -} - -static void -smtp_setup_events(void) -{ - struct listener *l; - struct pki *pki; - SSL_CTX *ssl_ctx; - void *iter; - const char *k; - - TAILQ_FOREACH(l, env->sc_listeners, entry) { - log_debug("debug: smtp: listen on %s port %d flags 0x%01x" - " pki \"%s\"" - " ca \"%s\"", ss_to_text(&l->ss), ntohs(l->port), - l->flags, l->pki_name, l->ca_name); - - io_set_nonblocking(l->fd); - if (listen(l->fd, SMTPD_BACKLOG) == -1) - fatal("listen"); - event_set(&l->ev, l->fd, EV_READ|EV_PERSIST, smtp_accept, l); - - if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) - event_add(&l->ev, NULL); - } - - iter = NULL; - while (dict_iter(env->sc_pki_dict, &iter, &k, (void **)&pki)) { - if (!ssl_setup((SSL_CTX **)&ssl_ctx, pki, smtp_sni_callback, - env->sc_tls_ciphers)) - fatal("smtp_setup_events: ssl_setup failure"); - dict_xset(env->sc_ssl_dict, k, ssl_ctx); - } - - purge_config(PURGE_PKI_KEYS); - - maxsessions = (getdtablesize() - getdtablecount()) / 2 - SMTP_FD_RESERVE; - log_debug("debug: smtp: will accept at most %zu clients", maxsessions); -} - -static void -smtp_pause(void) -{ - struct listener *l; - - if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED)) - return; - - TAILQ_FOREACH(l, env->sc_listeners, entry) - event_del(&l->ev); -} - -static void -smtp_resume(void) -{ - struct listener *l; - - if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED)) - return; - - TAILQ_FOREACH(l, env->sc_listeners, entry) - event_add(&l->ev, NULL); -} - -static int -smtp_enqueue(void) -{ - struct listener *listener = env->sc_sock_listener; - int fd[2]; - - /* - * Some enqueue requests buffered in IMSG may still arrive even after - * call to smtp_pause() because enqueue listener is not a real socket - * and thus cannot be paused properly. - */ - if (env->sc_flags & SMTPD_SMTP_PAUSED) - return (-1); - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd)) - return (-1); - - if ((smtp_session(listener, fd[0], &listener->ss, env->sc_hostname, NULL)) == -1) { - close(fd[0]); - close(fd[1]); - return (-1); - } - - sessions++; - stat_increment("smtp.session", 1); - stat_increment("smtp.session.local", 1); - - return (fd[1]); -} - -static void -smtp_accept(int fd, short event, void *p) -{ - struct listener *listener = p; - struct sockaddr_storage ss; - socklen_t len; - int sock; - - if (env->sc_flags & SMTPD_SMTP_PAUSED) - fatalx("smtp_session: unexpected client"); - - if (!smtp_can_accept()) { - log_warnx("warn: Disabling incoming SMTP connections: " - "Client limit reached"); - goto pause; - } - - len = sizeof(ss); - if ((sock = accept(fd, (struct sockaddr *)&ss, &len)) == -1) { - if (errno == ENFILE || errno == EMFILE) { - log_warn("warn: Disabling incoming SMTP connections"); - goto pause; - } - if (errno == EINTR || errno == EWOULDBLOCK || - errno == ECONNABORTED) - return; - fatal("smtp_accept"); - } - - if (listener->flags & F_PROXY) { - io_set_nonblocking(sock); - if (proxy_session(listener, sock, &ss, - smtp_accepted, smtp_dropped) == -1) { - close(sock); - return; - } - return; - } - - smtp_accepted(listener, sock, &ss, NULL); - return; - -pause: - smtp_pause(); - env->sc_flags |= SMTPD_SMTP_DISABLED; - return; -} - -static int -smtp_can_accept(void) -{ - if (sessions + 1 == maxsessions) - return 0; - return (getdtablesize() - getdtablecount() - SMTP_FD_RESERVE >= 2); -} - -void -smtp_collect(void) -{ - sessions--; - stat_decrement("smtp.session", 1); - - if (!smtp_can_accept()) - return; - - if (env->sc_flags & SMTPD_SMTP_DISABLED) { - log_warnx("warn: smtp: " - "fd exhaustion over, re-enabling incoming connections"); - env->sc_flags &= ~SMTPD_SMTP_DISABLED; - smtp_resume(); - } -} - -static int -smtp_sni_callback(SSL *ssl, int *ad, void *arg) -{ - const char *sn; - void *ssl_ctx; - - sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (sn == NULL) - return SSL_TLSEXT_ERR_NOACK; - ssl_ctx = dict_get(env->sc_ssl_dict, sn); - if (ssl_ctx == NULL) - return SSL_TLSEXT_ERR_NOACK; - SSL_set_SSL_CTX(ssl, ssl_ctx); - return SSL_TLSEXT_ERR_OK; -} - -static void -smtp_accepted(struct listener *listener, int sock, const struct sockaddr_storage *ss, struct io *io) -{ - int ret; - - ret = smtp_session(listener, sock, ss, NULL, io); - if (ret == -1) { - log_warn("warn: Failed to create SMTP session"); - close(sock); - return; - } - io_set_nonblocking(sock); - - sessions++; - stat_increment("smtp.session", 1); - if (listener->ss.ss_family == AF_LOCAL) - stat_increment("smtp.session.local", 1); - if (listener->ss.ss_family == AF_INET) - stat_increment("smtp.session.inet4", 1); - if (listener->ss.ss_family == AF_INET6) - stat_increment("smtp.session.inet6", 1); -} - -static void -smtp_dropped(struct listener *listener, int sock, const struct sockaddr_storage *ss) -{ - close(sock); - sessions--; -} diff --git a/smtpd/smtp.h b/smtpd/smtp.h deleted file mode 100644 index dc91d878..00000000 --- a/smtpd/smtp.h +++ /dev/null @@ -1,95 +0,0 @@ -/* $OpenBSD: smtp.h,v 1.3 2019/09/02 20:05:21 eric Exp $ */ - -/* - * Copyright (c) 2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define TLS_NO 0 -#define TLS_YES 1 -#define TLS_FORCE 2 -#define TLS_SMTPS 3 - -#define CERT_OK 0 -#define CERT_UNKNOWN 1 -#define CERT_INVALID 2 - -#define FAIL_INTERNAL 1 /* malloc, etc. (check errno) */ -#define FAIL_CONN 2 /* disconnect, timeout... (check errno) */ -#define FAIL_PROTO 3 /* protocol violation */ -#define FAIL_IMPL 4 /* server lacks a required feature */ -#define FAIL_RESP 5 /* rejected command */ - -struct smtp_params { - - /* Client options */ - size_t linemax; /* max input line size */ - size_t ibufmax; /* max input buffer size */ - size_t obufmax; /* max output buffer size */ - - /* Connection settings */ - const struct sockaddr *dst; /* address to connect to */ - const struct sockaddr *src; /* address to bind to */ - int timeout; /* timeout in seconds */ - - /* TLS options */ - int tls_req; /* requested TLS mode */ - int tls_verify; /* need valid server certificate */ - - /* SMTP options */ - int lmtp; /* use LMTP protocol */ - const char *helo; /* string to use with HELO */ - const char *auth_user; /* for AUTH */ - const char *auth_pass; /* for AUTH */ -}; - -struct smtp_rcpt { - const char *to; - const char *dsn_notify; - const char *dsn_orcpt; - int done; -}; - -struct smtp_mail { - const char *from; - const char *dsn_ret; - const char *dsn_envid; - struct smtp_rcpt *rcpt; - int rcptcount; - FILE *fp; -}; - -struct smtp_status { - struct smtp_rcpt *rcpt; - const char *cmd; - const char *status; -}; - -struct smtp_client; - -/* smtp_client.c */ -struct smtp_client *smtp_connect(const struct smtp_params *, void *); -void smtp_cert_verified(struct smtp_client *, int); -void smtp_set_tls(struct smtp_client *, void *); -void smtp_quit(struct smtp_client *); -void smtp_sendmail(struct smtp_client *, struct smtp_mail *); - -/* callbacks */ -void smtp_verify_server_cert(void *, struct smtp_client *, void *); -void smtp_require_tls(void *, struct smtp_client *); -void smtp_ready(void *, struct smtp_client *); -void smtp_failed(void *, struct smtp_client *, int, const char *); -void smtp_closed(void *, struct smtp_client *); -void smtp_status(void *, struct smtp_client *, struct smtp_status *); -void smtp_done(void *, struct smtp_client *, struct smtp_mail *); diff --git a/smtpd/smtp/Makefile b/smtpd/smtp/Makefile deleted file mode 100644 index 380e3ad6..00000000 --- a/smtpd/smtp/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -# $OpenBSD: Makefile,v 1.1 2018/04/26 13:57:13 eric Exp $ - -.PATH: ${.CURDIR}/.. - -PROG= smtp - -BINDIR= /usr/bin -MAN= smtp.1 - -SRCS+= iobuf.c -SRCS+= ioev.c -SRCS+= log.c -SRCS+= smtp_client.c -SRCS+= smtpc.c -SRCS+= ssl.c -SRCS+= ssl_smtpd.c -SRCS+= ssl_verify.c - -CPPFLAGS+= -DIO_TLS - -LDADD+= -levent -lutil -lssl -lcrypto -lm -lz -DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} - -.include diff --git a/smtpd/smtp_client.c b/smtpd/smtp_client.c deleted file mode 100644 index 8e146e1b..00000000 --- a/smtpd/smtp_client.c +++ /dev/null @@ -1,923 +0,0 @@ -/* $OpenBSD: smtp_client.c,v 1.14 2020/04/24 11:34:07 eric Exp $ */ - -/* - * Copyright (c) 2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "ioev.h" -#include "smtp.h" - -#define TRACE_SMTPCLT 2 -#define TRACE_IO 3 - -enum { - STATE_INIT = 0, - STATE_BANNER, - STATE_EHLO, - STATE_HELO, - STATE_LHLO, - STATE_STARTTLS, - STATE_AUTH, - STATE_AUTH_PLAIN, - STATE_AUTH_LOGIN, - STATE_AUTH_LOGIN_USER, - STATE_AUTH_LOGIN_PASS, - STATE_READY, - STATE_MAIL, - STATE_RCPT, - STATE_DATA, - STATE_BODY, - STATE_EOM, - STATE_RSET, - STATE_QUIT, - - STATE_LAST -}; - -#define base64_encode __b64_ntop -#define base64_decode __b64_pton - -#define FLAG_TLS 0x01 -#define FLAG_TLS_VERIFIED 0x02 - -#define SMTP_EXT_STARTTLS 0x01 -#define SMTP_EXT_PIPELINING 0x02 -#define SMTP_EXT_AUTH 0x04 -#define SMTP_EXT_AUTH_PLAIN 0x08 -#define SMTP_EXT_AUTH_LOGIN 0x10 -#define SMTP_EXT_DSN 0x20 -#define SMTP_EXT_SIZE 0x40 - -struct smtp_client { - void *tag; - struct smtp_params params; - - int state; - int flags; - int ext; - size_t ext_size; - - struct io *io; - char *reply; - size_t replysz; - - struct smtp_mail *mail; - int rcptidx; - int rcptok; -}; - -void log_trace_verbose(int); -void log_trace(int, const char *, ...) - __attribute__((format (printf, 2, 3))); - -static void smtp_client_io(struct io *, int, void *); -static void smtp_client_free(struct smtp_client *); -static void smtp_client_state(struct smtp_client *, int); -static void smtp_client_abort(struct smtp_client *, int, const char *); -static void smtp_client_cancel(struct smtp_client *, int, const char *); -static void smtp_client_sendcmd(struct smtp_client *, char *, ...); -static void smtp_client_sendbody(struct smtp_client *); -static int smtp_client_readline(struct smtp_client *); -static int smtp_client_replycat(struct smtp_client *, const char *); -static void smtp_client_response(struct smtp_client *, const char *); -static void smtp_client_mail_abort(struct smtp_client *); -static void smtp_client_mail_status(struct smtp_client *, const char *); -static void smtp_client_rcpt_status(struct smtp_client *, struct smtp_rcpt *, const char *); - -static const char *strstate[STATE_LAST] = { - "INIT", - "BANNER", - "EHLO", - "HELO", - "LHLO", - "STARTTLS", - "AUTH", - "AUTH_PLAIN", - "AUTH_LOGIN", - "AUTH_LOGIN_USER", - "AUTH_LOGIN_PASS", - "READY", - "MAIL", - "RCPT", - "DATA", - "BODY", - "EOM", - "RSET", - "QUIT", -}; - -struct smtp_client * -smtp_connect(const struct smtp_params *params, void *tag) -{ - struct smtp_client *proto; - - proto = calloc(1, sizeof *proto); - if (proto == NULL) - return NULL; - - memmove(&proto->params, params, sizeof(*params)); - proto->tag = tag; - proto->io = io_new(); - if (proto->io == NULL) { - free(proto); - return NULL; - } - io_set_callback(proto->io, smtp_client_io, proto); - io_set_timeout(proto->io, proto->params.timeout); - - if (io_connect(proto->io, proto->params.dst, proto->params.src) == -1) { - smtp_client_abort(proto, FAIL_CONN, io_error(proto->io)); - return NULL; - } - - return proto; -} - -void -smtp_cert_verified(struct smtp_client *proto, int verified) -{ - if (verified == CERT_OK) - proto->flags |= FLAG_TLS_VERIFIED; - - else if (proto->params.tls_verify) { - errno = EAUTH; - smtp_client_abort(proto, FAIL_CONN, - "Invalid server certificate"); - return; - } - - io_resume(proto->io, IO_IN); - - if (proto->state == STATE_INIT) - smtp_client_state(proto, STATE_BANNER); - else { - /* Clear extensions before re-issueing an EHLO command. */ - proto->ext = 0; - smtp_client_state(proto, STATE_EHLO); - } -} - -void -smtp_set_tls(struct smtp_client *proto, void *ctx) -{ - io_start_tls(proto->io, ctx); -} - -void -smtp_quit(struct smtp_client *proto) -{ - if (proto->state != STATE_READY) - fatalx("connection is not ready"); - - smtp_client_state(proto, STATE_QUIT); -} - -void -smtp_sendmail(struct smtp_client *proto, struct smtp_mail *mail) -{ - if (proto->state != STATE_READY) - fatalx("connection is not ready"); - - proto->mail = mail; - smtp_client_state(proto, STATE_MAIL); -} - -static void -smtp_client_free(struct smtp_client *proto) -{ - if (proto->mail) - fatalx("current task should have been deleted already"); - - smtp_closed(proto->tag, proto); - - if (proto->io) - io_free(proto->io); - - free(proto->reply); - free(proto); -} - -/* - * End the session immediatly. - */ -static void -smtp_client_abort(struct smtp_client *proto, int err, const char *reason) -{ - smtp_failed(proto->tag, proto, err, reason); - - if (proto->mail) - smtp_client_mail_abort(proto); - - smtp_client_free(proto); -} - -/* - * Properly close the session. - */ -static void -smtp_client_cancel(struct smtp_client *proto, int err, const char *reason) -{ - if (proto->mail) - fatal("not supposed to have a mail"); - - smtp_failed(proto->tag, proto, err, reason); - - smtp_client_state(proto, STATE_QUIT); -} - -static void -smtp_client_state(struct smtp_client *proto, int newstate) -{ - struct smtp_rcpt *rcpt; - char ibuf[LINE_MAX], obuf[LINE_MAX]; - size_t n; - int oldstate; - - if (proto->reply) - proto->reply[0] = '\0'; - - again: - oldstate = proto->state; - proto->state = newstate; - - log_trace(TRACE_SMTPCLT, "%p: %s -> %s", proto, - strstate[oldstate], - strstate[newstate]); - - /* don't try this at home! */ -#define smtp_client_state(_s, _st) do { newstate = _st; goto again; } while (0) - - switch (proto->state) { - case STATE_BANNER: - io_set_read(proto->io); - break; - - case STATE_EHLO: - smtp_client_sendcmd(proto, "EHLO %s", proto->params.helo); - break; - - case STATE_HELO: - smtp_client_sendcmd(proto, "HELO %s", proto->params.helo); - break; - - case STATE_LHLO: - smtp_client_sendcmd(proto, "LHLO %s", proto->params.helo); - break; - - case STATE_STARTTLS: - if (proto->params.tls_req == TLS_NO || proto->flags & FLAG_TLS) - smtp_client_state(proto, STATE_AUTH); - else if (proto->ext & SMTP_EXT_STARTTLS) - smtp_client_sendcmd(proto, "STARTTLS"); - else if (proto->params.tls_req == TLS_FORCE) - smtp_client_cancel(proto, FAIL_IMPL, - "TLS not supported by remote host"); - else - smtp_client_state(proto, STATE_AUTH); - break; - - case STATE_AUTH: - if (!proto->params.auth_user) - smtp_client_state(proto, STATE_READY); - else if ((proto->flags & FLAG_TLS) == 0) - smtp_client_cancel(proto, FAIL_IMPL, - "Authentication requires TLS"); - else if ((proto->ext & SMTP_EXT_AUTH) == 0) - smtp_client_cancel(proto, FAIL_IMPL, - "AUTH not supported by remote host"); - else if (proto->ext & SMTP_EXT_AUTH_PLAIN) - smtp_client_state(proto, STATE_AUTH_PLAIN); - else if (proto->ext & SMTP_EXT_AUTH_LOGIN) - smtp_client_state(proto, STATE_AUTH_LOGIN); - else - smtp_client_cancel(proto, FAIL_IMPL, - "No supported AUTH method"); - break; - - case STATE_AUTH_PLAIN: - (void)strlcpy(ibuf, "-", sizeof(ibuf)); - (void)strlcat(ibuf, proto->params.auth_user, sizeof(ibuf)); - if (strlcat(ibuf, ":", sizeof(ibuf)) >= sizeof(ibuf)) { - errno = EMSGSIZE; - smtp_client_cancel(proto, FAIL_INTERNAL, - "credentials too large"); - break; - } - n = strlcat(ibuf, proto->params.auth_pass, sizeof(ibuf)); - if (n >= sizeof(ibuf)) { - errno = EMSGSIZE; - smtp_client_cancel(proto, FAIL_INTERNAL, - "credentials too large"); - break; - } - *strchr(ibuf, ':') = '\0'; - ibuf[0] = '\0'; - if (base64_encode(ibuf, n, obuf, sizeof(obuf)) == -1) { - errno = EMSGSIZE; - smtp_client_cancel(proto, FAIL_INTERNAL, - "credentials too large"); - break; - } - smtp_client_sendcmd(proto, "AUTH PLAIN %s", obuf); - explicit_bzero(ibuf, sizeof ibuf); - explicit_bzero(obuf, sizeof obuf); - break; - - case STATE_AUTH_LOGIN: - smtp_client_sendcmd(proto, "AUTH LOGIN"); - break; - - case STATE_AUTH_LOGIN_USER: - if (base64_encode(proto->params.auth_user, - strlen(proto->params.auth_user), obuf, - sizeof(obuf)) == -1) { - errno = EMSGSIZE; - smtp_client_cancel(proto, FAIL_INTERNAL, - "credentials too large"); - break; - } - smtp_client_sendcmd(proto, "%s", obuf); - explicit_bzero(obuf, sizeof obuf); - break; - - case STATE_AUTH_LOGIN_PASS: - if (base64_encode(proto->params.auth_pass, - strlen(proto->params.auth_pass), obuf, - sizeof(obuf)) == -1) { - errno = EMSGSIZE; - smtp_client_cancel(proto, FAIL_INTERNAL, - "credentials too large"); - break; - } - smtp_client_sendcmd(proto, "%s", obuf); - explicit_bzero(obuf, sizeof obuf); - break; - - case STATE_READY: - smtp_ready(proto->tag, proto); - break; - - case STATE_MAIL: - if (proto->ext & SMTP_EXT_DSN) - smtp_client_sendcmd(proto, "MAIL FROM:<%s>%s%s%s%s", - proto->mail->from, - proto->mail->dsn_ret ? " RET=" : "", - proto->mail->dsn_ret ? proto->mail->dsn_ret : "", - proto->mail->dsn_envid ? " ENVID=" : "", - proto->mail->dsn_envid ? proto->mail->dsn_envid : ""); - else - smtp_client_sendcmd(proto, "MAIL FROM:<%s>", - proto->mail->from); - break; - - case STATE_RCPT: - if (proto->rcptidx == proto->mail->rcptcount) { - smtp_client_state(proto, STATE_DATA); - break; - } - rcpt = &proto->mail->rcpt[proto->rcptidx]; - if (proto->ext & SMTP_EXT_DSN) - smtp_client_sendcmd(proto, "RCPT TO:<%s>%s%s%s%s", - rcpt->to, - rcpt->dsn_notify ? " NOTIFY=" : "", - rcpt->dsn_notify ? rcpt->dsn_notify : "", - rcpt->dsn_orcpt ? " ORCPT=" : "", - rcpt->dsn_orcpt ? rcpt->dsn_orcpt : ""); - else - smtp_client_sendcmd(proto, "RCPT TO:<%s>", rcpt->to); - break; - - case STATE_DATA: - if (proto->rcptok == 0) { - smtp_client_mail_abort(proto); - smtp_client_state(proto, STATE_RSET); - } - else - smtp_client_sendcmd(proto, "DATA"); - break; - - case STATE_BODY: - fseek(proto->mail->fp, 0, SEEK_SET); - smtp_client_sendbody(proto); - break; - - case STATE_EOM: - smtp_client_sendcmd(proto, "."); - break; - - case STATE_RSET: - smtp_client_sendcmd(proto, "RSET"); - break; - - case STATE_QUIT: - smtp_client_sendcmd(proto, "QUIT"); - break; - - default: - fatalx("%s: bad state %d", __func__, proto->state); - } -#undef smtp_client_state -} - -/* - * Handle a response to an SMTP command - */ -static void -smtp_client_response(struct smtp_client *proto, const char *line) -{ - struct smtp_rcpt *rcpt; - int i, seen; - - switch (proto->state) { - case STATE_BANNER: - if (line[0] != '2') - smtp_client_abort(proto, FAIL_RESP, line); - else if (proto->params.lmtp) - smtp_client_state(proto, STATE_LHLO); - else - smtp_client_state(proto, STATE_EHLO); - break; - - case STATE_EHLO: - if (line[0] != '2') { - /* - * Either rejected or not implemented. If we want to - * use EHLO extensions, report an SMTP error. - * Otherwise, fallback to using HELO. - */ - if ((proto->params.tls_req == TLS_FORCE) || - (proto->params.auth_user)) - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_HELO); - break; - } - smtp_client_state(proto, STATE_STARTTLS); - break; - - case STATE_HELO: - if (line[0] != '2') - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_READY); - break; - - case STATE_LHLO: - if (line[0] != '2') - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_READY); - break; - - case STATE_STARTTLS: - if (line[0] != '2') { - if ((proto->params.tls_req == TLS_FORCE) || - (proto->params.auth_user)) { - smtp_client_cancel(proto, FAIL_RESP, line); - break; - } - smtp_client_state(proto, STATE_AUTH); - } - else - smtp_require_tls(proto->tag, proto); - break; - - case STATE_AUTH_PLAIN: - if (line[0] != '2') - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_READY); - break; - - case STATE_AUTH_LOGIN: - if (strncmp(line, "334 ", 4)) - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_AUTH_LOGIN_USER); - break; - - case STATE_AUTH_LOGIN_USER: - if (strncmp(line, "334 ", 4)) - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_AUTH_LOGIN_PASS); - break; - - case STATE_AUTH_LOGIN_PASS: - if (line[0] != '2') - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_READY); - break; - - case STATE_MAIL: - if (line[0] != '2') { - smtp_client_mail_status(proto, line); - smtp_client_state(proto, STATE_RSET); - } - else - smtp_client_state(proto, STATE_RCPT); - break; - - case STATE_RCPT: - rcpt = &proto->mail->rcpt[proto->rcptidx++]; - if (line[0] != '2') - smtp_client_rcpt_status(proto, rcpt, line); - else { - proto->rcptok++; - smtp_client_state(proto, STATE_RCPT); - } - break; - - case STATE_DATA: - if (line[0] != '2' && line[0] != '3') { - smtp_client_mail_status(proto, line); - smtp_client_state(proto, STATE_RSET); - } - else - smtp_client_state(proto, STATE_BODY); - break; - - case STATE_EOM: - if (proto->params.lmtp) { - /* - * LMTP reports a status of each accepted RCPT. - * Report status for the first pending RCPT and read - * more lines if another rcpt needs a status. - */ - for (i = 0, seen = 0; i < proto->mail->rcptcount; i++) { - rcpt = &proto->mail->rcpt[i]; - if (rcpt->done) - continue; - if (seen) { - io_set_read(proto->io); - return; - } - smtp_client_rcpt_status(proto, - &proto->mail->rcpt[i], line); - seen = 1; - } - } - smtp_client_mail_status(proto, line); - smtp_client_state(proto, STATE_READY); - break; - - case STATE_RSET: - if (line[0] != '2') - smtp_client_cancel(proto, FAIL_RESP, line); - else - smtp_client_state(proto, STATE_READY); - break; - - case STATE_QUIT: - smtp_client_free(proto); - break; - - default: - fatalx("%s: bad state %d", __func__, proto->state); - } -} - -static void -smtp_client_io(struct io *io, int evt, void *arg) -{ - struct smtp_client *proto = arg; - - log_trace(TRACE_IO, "%p: %s %s", proto, io_strevent(evt), io_strio(io)); - - switch (evt) { - case IO_CONNECTED: - if (proto->params.tls_req == TLS_SMTPS) { - io_set_write(io); - smtp_require_tls(proto->tag, proto); - } - else - smtp_client_state(proto, STATE_BANNER); - break; - - case IO_TLSREADY: - proto->flags |= FLAG_TLS; - io_pause(proto->io, IO_IN); - smtp_verify_server_cert(proto->tag, proto, io_tls(proto->io)); - break; - - case IO_DATAIN: - while (smtp_client_readline(proto)) - ; - break; - - case IO_LOWAT: - if (proto->state == STATE_BODY) - smtp_client_sendbody(proto); - else - io_set_read(io); - break; - - case IO_TIMEOUT: - errno = ETIMEDOUT; - smtp_client_abort(proto, FAIL_CONN, "Connection timeout"); - break; - - case IO_ERROR: - smtp_client_abort(proto, FAIL_CONN, io_error(io)); - break; - - case IO_TLSERROR: - smtp_client_abort(proto, FAIL_CONN, io_error(io)); - break; - - case IO_DISCONNECTED: - smtp_client_abort(proto, FAIL_CONN, io_error(io)); - break; - - default: - fatalx("%s: bad event %d", __func__, evt); - } -} - -/* - * return 1 if a new line is expected. - */ -static int -smtp_client_readline(struct smtp_client *proto) -{ - const char *e; - size_t len; - char *line, *msg, *p; - int cont; - - line = io_getline(proto->io, &len); - if (line == NULL) { - if (io_datalen(proto->io) >= proto->params.linemax) - smtp_client_abort(proto, FAIL_PROTO, "Line too long"); - return 0; - } - - /* Strip trailing '\r' */ - if (len && line[len - 1] == '\r') - line[--len] = '\0'; - - log_trace(TRACE_SMTPCLT, "%p: <<< %s", proto, line); - - /* Validate SMTP */ - if (len > 3) { - msg = line + 4; - cont = (line[3] == '-'); - } else if (len == 3) { - msg = line + 3; - cont = 0; - } else { - smtp_client_abort(proto, FAIL_PROTO, "Response too short"); - return 0; - } - - /* Validate reply code. */ - if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) || - !isdigit((unsigned char)line[2])) { - smtp_client_abort(proto, FAIL_PROTO, "Invalid reply code"); - return 0; - } - - /* Validate reply message. */ - for (p = msg; *p; p++) - if (!isprint((unsigned char)*p)) { - smtp_client_abort(proto, FAIL_PROTO, - "Non-printable characters in response"); - return 0; - } - - /* Read extensions. */ - if (proto->state == STATE_EHLO) { - if (strcmp(msg, "STARTTLS") == 0) - proto->ext |= SMTP_EXT_STARTTLS; - else if (strncmp(msg, "AUTH ", 5) == 0) { - proto->ext |= SMTP_EXT_AUTH; - if ((p = strstr(msg, " PLAIN")) && - (*(p+6) == '\0' || *(p+6) == ' ')) - proto->ext |= SMTP_EXT_AUTH_PLAIN; - if ((p = strstr(msg, " LOGIN")) && - (*(p+6) == '\0' || *(p+6) == ' ')) - proto->ext |= SMTP_EXT_AUTH_LOGIN; - } - else if (strcmp(msg, "PIPELINING") == 0) - proto->ext |= SMTP_EXT_PIPELINING; - else if (strcmp(msg, "DSN") == 0) - proto->ext |= SMTP_EXT_DSN; - else if (strncmp(msg, "SIZE ", 5) == 0) { - proto->ext_size = strtonum(msg + 5, 0, SIZE_T_MAX, &e); - if (e == NULL) - proto->ext |= SMTP_EXT_SIZE; - } - } - - if (smtp_client_replycat(proto, line) == -1) { - smtp_client_abort(proto, FAIL_INTERNAL, NULL); - return 0; - } - - if (cont) - return 1; - - if (io_datalen(proto->io)) { - /* - * There should be no pending data after a response is read, - * except for the multiple status lines after a LMTP message. - * It can also happen with pipelineing, but we don't do that - * for now. - */ - if (!(proto->params.lmtp && proto->state == STATE_EOM)) { - smtp_client_abort(proto, FAIL_PROTO, "Trailing data"); - return 0; - } - } - - io_set_write(proto->io); - smtp_client_response(proto, proto->reply); - return 0; -} - -/* - * Concatenate the given response line. - */ -static int -smtp_client_replycat(struct smtp_client *proto, const char *line) -{ - size_t len; - char *tmp; - int first; - - if (proto->reply && proto->reply[0]) { - /* - * If the line is the continuation of an multi-line response, - * skip the status and ESC parts. First, skip the status, then - * skip the separator amd ESC if found. - */ - first = 0; - line += 3; - if (line[0]) { - line += 1; - if (isdigit((unsigned char)line[0]) && line[1] == '.' && - isdigit((unsigned char)line[2]) && line[3] == '.' && - isdigit((unsigned char)line[4]) && - isspace((unsigned char)line[5])) - line += 5; - } - } else - first = 1; - - if (proto->reply) { - len = strlcat(proto->reply, line, proto->replysz); - if (len < proto->replysz) - return 0; - } - else - len = strlen(line); - - if (len > proto->params.ibufmax) { - errno = EMSGSIZE; - return -1; - } - - /* Allocate by multiples of 2^8 */ - len += (len % 256) ? (256 - (len % 256)) : 0; - - tmp = realloc(proto->reply, len); - if (tmp == NULL) - return -1; - if (proto->reply == NULL) - tmp[0] = '\0'; - - proto->reply = tmp; - proto->replysz = len; - (void)strlcat(proto->reply, line, proto->replysz); - - /* Replace the separator with a space for the first line. */ - if (first && proto->reply[3]) - proto->reply[3] = ' '; - - return 0; -} - -static void -smtp_client_sendbody(struct smtp_client *proto) -{ - ssize_t len; - size_t sz = 0, total, w; - char *ln = NULL; - int n; - - total = io_queued(proto->io); - w = 0; - - while (total < proto->params.obufmax) { - if ((len = getline(&ln, &sz, proto->mail->fp)) == -1) - break; - if (ln[len - 1] == '\n') - ln[len - 1] = '\0'; - n = io_printf(proto->io, "%s%s\r\n", *ln == '.'?".":"", ln); - if (n == -1) { - free(ln); - smtp_client_abort(proto, FAIL_INTERNAL, NULL); - return; - } - total += n; - w += n; - } - free(ln); - - if (ferror(proto->mail->fp)) { - smtp_client_abort(proto, FAIL_INTERNAL, "Cannot read message"); - return; - } - - log_trace(TRACE_SMTPCLT, "%p: >>> [...%zd bytes...]", proto, w); - - if (feof(proto->mail->fp)) - smtp_client_state(proto, STATE_EOM); -} - -static void -smtp_client_sendcmd(struct smtp_client *proto, char *fmt, ...) -{ - va_list ap; - char *p; - int len; - - va_start(ap, fmt); - len = vasprintf(&p, fmt, ap); - va_end(ap); - - if (len == -1) { - smtp_client_abort(proto, FAIL_INTERNAL, NULL); - return; - } - - log_trace(TRACE_SMTPCLT, "mta: %p: >>> %s", proto, p); - - len = io_printf(proto->io, "%s\r\n", p); - free(p); - - if (len == -1) - smtp_client_abort(proto, FAIL_INTERNAL, NULL); -} - -static void -smtp_client_mail_status(struct smtp_client *proto, const char *status) -{ - int i; - - for (i = 0; i < proto->mail->rcptcount; i++) - smtp_client_rcpt_status(proto, &proto->mail->rcpt[i], status); - - smtp_done(proto->tag, proto, proto->mail); - proto->mail = NULL; -} - -static void -smtp_client_mail_abort(struct smtp_client *proto) -{ - smtp_done(proto->tag, proto, proto->mail); - proto->mail = NULL; -} - -static void -smtp_client_rcpt_status(struct smtp_client *proto, struct smtp_rcpt *rcpt, const char *line) -{ - struct smtp_status status; - - if (rcpt->done) - return; - - rcpt->done = 1; - status.rcpt = rcpt; - status.cmd = strstate[proto->state]; - status.status = line; - smtp_status(proto->tag, proto, &status); -} diff --git a/smtpd/smtp_session.c b/smtpd/smtp_session.c deleted file mode 100644 index aefce155..00000000 --- a/smtpd/smtp_session.c +++ /dev/null @@ -1,3223 +0,0 @@ -/* $OpenBSD: smtp_session.c,v 1.426 2020/04/24 11:34:07 eric Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008-2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) -#include -#else -#include "bsd-vis.h" -#endif - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" -#include "rfc5322.h" - -#define SMTP_LINE_MAX 65535 -#define DATA_HIWAT 65535 -#define APPEND_DOMAIN_BUFFER_SIZE SMTP_LINE_MAX - -enum smtp_state { - STATE_NEW = 0, - STATE_CONNECTED, - STATE_TLS, - STATE_HELO, - STATE_AUTH_INIT, - STATE_AUTH_USERNAME, - STATE_AUTH_PASSWORD, - STATE_AUTH_FINALIZE, - STATE_BODY, - STATE_QUIT, -}; - -enum session_flags { - SF_EHLO = 0x0001, - SF_8BITMIME = 0x0002, - SF_SECURE = 0x0004, - SF_AUTHENTICATED = 0x0008, - SF_BOUNCE = 0x0010, - SF_VERIFIED = 0x0020, - SF_BADINPUT = 0x0080, -}; - -enum { - TX_OK = 0, - TX_ERROR_ENVELOPE, - TX_ERROR_SIZE, - TX_ERROR_IO, - TX_ERROR_LOOP, - TX_ERROR_MALFORMED, - TX_ERROR_RESOURCES, - TX_ERROR_INTERNAL, -}; - -enum smtp_command { - CMD_HELO = 0, - CMD_EHLO, - CMD_STARTTLS, - CMD_AUTH, - CMD_MAIL_FROM, - CMD_RCPT_TO, - CMD_DATA, - CMD_RSET, - CMD_QUIT, - CMD_HELP, - CMD_WIZ, - CMD_NOOP, - CMD_COMMIT, -}; - -struct smtp_rcpt { - TAILQ_ENTRY(smtp_rcpt) entry; - uint64_t evpid; - struct mailaddr maddr; - size_t destcount; -}; - -struct smtp_tx { - struct smtp_session *session; - uint32_t msgid; - - struct envelope evp; - size_t rcptcount; - size_t destcount; - TAILQ_HEAD(, smtp_rcpt) rcpts; - - time_t time; - int error; - size_t datain; - size_t odatalen; - FILE *ofile; - struct io *filter; - struct rfc5322_parser *parser; - int rcvcount; - int has_date; - int has_message_id; - - uint8_t junk; -}; - -struct smtp_session { - uint64_t id; - struct io *io; - struct listener *listener; - void *ssl_ctx; - struct sockaddr_storage ss; - char rdns[HOST_NAME_MAX+1]; - char smtpname[HOST_NAME_MAX+1]; - int fcrdns; - - int flags; - enum smtp_state state; - - uint8_t banner_sent; - char helo[LINE_MAX]; - char cmd[LINE_MAX]; - char username[SMTPD_MAXMAILADDRSIZE]; - - size_t mailcount; - struct event pause; - - struct smtp_tx *tx; - - enum smtp_command last_cmd; - enum filter_phase filter_phase; - const char *filter_param; - - uint8_t junk; -}; - -#define ADVERTISE_TLS(s) \ - ((s)->listener->flags & F_STARTTLS && !((s)->flags & SF_SECURE)) - -#define ADVERTISE_AUTH(s) \ - ((s)->listener->flags & F_AUTH && (s)->flags & SF_SECURE && \ - !((s)->flags & SF_AUTHENTICATED)) - -#define ADVERTISE_EXT_DSN(s) \ - ((s)->listener->flags & F_EXT_DSN) - -#define SESSION_FILTERED(s) \ - ((s)->listener->flags & F_FILTERED) - -#define SESSION_DATA_FILTERED(s) \ - ((s)->listener->flags & F_FILTERED) - - -static int smtp_mailaddr(struct mailaddr *, char *, int, char **, const char *); -static void smtp_session_init(void); -static void smtp_lookup_servername(struct smtp_session *); -static void smtp_getnameinfo_cb(void *, int, const char *, const char *); -static void smtp_getaddrinfo_cb(void *, int, struct addrinfo *); -static void smtp_connected(struct smtp_session *); -static void smtp_send_banner(struct smtp_session *); -static void smtp_tls_verified(struct smtp_session *); -static void smtp_io(struct io *, int, void *); -static void smtp_enter_state(struct smtp_session *, int); -static void smtp_reply(struct smtp_session *, char *, ...); -static void smtp_command(struct smtp_session *, char *); -static void smtp_rfc4954_auth_plain(struct smtp_session *, char *); -static void smtp_rfc4954_auth_login(struct smtp_session *, char *); -static void smtp_free(struct smtp_session *, const char *); -static const char *smtp_strstate(int); -static void smtp_cert_init(struct smtp_session *); -static void smtp_cert_init_cb(void *, int, const char *, const void *, size_t); -static void smtp_cert_verify(struct smtp_session *); -static void smtp_cert_verify_cb(void *, int); -static void smtp_auth_failure_pause(struct smtp_session *); -static void smtp_auth_failure_resume(int, short, void *); - -static int smtp_tx(struct smtp_session *); -static void smtp_tx_free(struct smtp_tx *); -static void smtp_tx_create_message(struct smtp_tx *); -static void smtp_tx_mail_from(struct smtp_tx *, const char *); -static void smtp_tx_rcpt_to(struct smtp_tx *, const char *); -static void smtp_tx_open_message(struct smtp_tx *); -static void smtp_tx_commit(struct smtp_tx *); -static void smtp_tx_rollback(struct smtp_tx *); -static int smtp_tx_dataline(struct smtp_tx *, const char *); -static int smtp_tx_filtered_dataline(struct smtp_tx *, const char *); -static void smtp_tx_eom(struct smtp_tx *); -static void smtp_filter_fd(struct smtp_tx *, int); -static int smtp_message_fd(struct smtp_tx *, int); -static void smtp_message_begin(struct smtp_tx *); -static void smtp_message_end(struct smtp_tx *); -static int smtp_filter_printf(struct smtp_tx *, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -static int smtp_message_printf(struct smtp_tx *, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); - -static int smtp_check_rset(struct smtp_session *, const char *); -static int smtp_check_helo(struct smtp_session *, const char *); -static int smtp_check_ehlo(struct smtp_session *, const char *); -static int smtp_check_auth(struct smtp_session *s, const char *); -static int smtp_check_starttls(struct smtp_session *, const char *); -static int smtp_check_mail_from(struct smtp_session *, const char *); -static int smtp_check_rcpt_to(struct smtp_session *, const char *); -static int smtp_check_data(struct smtp_session *, const char *); -static int smtp_check_noparam(struct smtp_session *, const char *); - -static void smtp_filter_phase(enum filter_phase, struct smtp_session *, const char *); - -static void smtp_proceed_connected(struct smtp_session *); -static void smtp_proceed_rset(struct smtp_session *, const char *); -static void smtp_proceed_helo(struct smtp_session *, const char *); -static void smtp_proceed_ehlo(struct smtp_session *, const char *); -static void smtp_proceed_auth(struct smtp_session *, const char *); -static void smtp_proceed_starttls(struct smtp_session *, const char *); -static void smtp_proceed_mail_from(struct smtp_session *, const char *); -static void smtp_proceed_rcpt_to(struct smtp_session *, const char *); -static void smtp_proceed_data(struct smtp_session *, const char *); -static void smtp_proceed_noop(struct smtp_session *, const char *); -static void smtp_proceed_help(struct smtp_session *, const char *); -static void smtp_proceed_wiz(struct smtp_session *, const char *); -static void smtp_proceed_quit(struct smtp_session *, const char *); -static void smtp_proceed_commit(struct smtp_session *, const char *); -static void smtp_proceed_rollback(struct smtp_session *, const char *); - -static void smtp_filter_begin(struct smtp_session *); -static void smtp_filter_end(struct smtp_session *); -static void smtp_filter_data_begin(struct smtp_session *); -static void smtp_filter_data_end(struct smtp_session *); - -static void smtp_report_link_connect(struct smtp_session *, const char *, int, - const struct sockaddr_storage *, - const struct sockaddr_storage *); -static void smtp_report_link_greeting(struct smtp_session *, const char *); -static void smtp_report_link_identify(struct smtp_session *, const char *, const char *); -static void smtp_report_link_tls(struct smtp_session *, const char *); -static void smtp_report_link_disconnect(struct smtp_session *); -static void smtp_report_link_auth(struct smtp_session *, const char *, const char *); -static void smtp_report_tx_reset(struct smtp_session *, uint32_t); -static void smtp_report_tx_begin(struct smtp_session *, uint32_t); -static void smtp_report_tx_mail(struct smtp_session *, uint32_t, const char *, int); -static void smtp_report_tx_rcpt(struct smtp_session *, uint32_t, const char *, int); -static void smtp_report_tx_envelope(struct smtp_session *, uint32_t, uint64_t); -static void smtp_report_tx_data(struct smtp_session *, uint32_t, int); -static void smtp_report_tx_commit(struct smtp_session *, uint32_t, size_t); -static void smtp_report_tx_rollback(struct smtp_session *, uint32_t); -static void smtp_report_protocol_client(struct smtp_session *, const char *); -static void smtp_report_protocol_server(struct smtp_session *, const char *); -static void smtp_report_filter_response(struct smtp_session *, int, int, const char *); -static void smtp_report_timeout(struct smtp_session *); - - -/* musl work-around */ -void portable_freeaddrinfo(struct addrinfo *); - - -static struct { - int code; - enum filter_phase filter_phase; - const char *cmd; - - int (*check)(struct smtp_session *, const char *); - void (*proceed)(struct smtp_session *, const char *); -} commands[] = { - { CMD_HELO, FILTER_HELO, "HELO", smtp_check_helo, smtp_proceed_helo }, - { CMD_EHLO, FILTER_EHLO, "EHLO", smtp_check_ehlo, smtp_proceed_ehlo }, - { CMD_STARTTLS, FILTER_STARTTLS, "STARTTLS", smtp_check_starttls, smtp_proceed_starttls }, - { CMD_AUTH, FILTER_AUTH, "AUTH", smtp_check_auth, smtp_proceed_auth }, - { CMD_MAIL_FROM, FILTER_MAIL_FROM, "MAIL FROM", smtp_check_mail_from, smtp_proceed_mail_from }, - { CMD_RCPT_TO, FILTER_RCPT_TO, "RCPT TO", smtp_check_rcpt_to, smtp_proceed_rcpt_to }, - { CMD_DATA, FILTER_DATA, "DATA", smtp_check_data, smtp_proceed_data }, - { CMD_RSET, FILTER_RSET, "RSET", smtp_check_rset, smtp_proceed_rset }, - { CMD_QUIT, FILTER_QUIT, "QUIT", smtp_check_noparam, smtp_proceed_quit }, - { CMD_NOOP, FILTER_NOOP, "NOOP", smtp_check_noparam, smtp_proceed_noop }, - { CMD_HELP, FILTER_HELP, "HELP", smtp_check_noparam, smtp_proceed_help }, - { CMD_WIZ, FILTER_WIZ, "WIZ", smtp_check_noparam, smtp_proceed_wiz }, - { CMD_COMMIT, FILTER_COMMIT, ".", smtp_check_noparam, smtp_proceed_commit }, - { -1, 0, NULL, NULL }, -}; - -static struct tree wait_lka_helo; -static struct tree wait_lka_mail; -static struct tree wait_lka_rcpt; -static struct tree wait_parent_auth; -static struct tree wait_queue_msg; -static struct tree wait_queue_fd; -static struct tree wait_queue_commit; -static struct tree wait_ssl_init; -static struct tree wait_ssl_verify; -static struct tree wait_filters; -static struct tree wait_filter_fd; - -static void -header_append_domain_buffer(char *buffer, char *domain, size_t len) -{ - size_t i; - int escape, quote, comment, bracket; - int has_domain, has_bracket, has_group; - int pos_bracket, pos_component, pos_insert; - char copy[APPEND_DOMAIN_BUFFER_SIZE]; - - escape = quote = comment = bracket = 0; - has_domain = has_bracket = has_group = 0; - pos_bracket = pos_insert = pos_component = 0; - for (i = 0; buffer[i]; ++i) { - if (buffer[i] == '(' && !escape && !quote) - comment++; - if (buffer[i] == '"' && !escape && !comment) - quote = !quote; - if (buffer[i] == ')' && !escape && !quote && comment) - comment--; - if (buffer[i] == '\\' && !escape && !comment && !quote) - escape = 1; - else - escape = 0; - if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { - bracket++; - has_bracket = 1; - } - if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { - bracket--; - pos_bracket = i; - } - if (buffer[i] == '@' && !escape && !comment && !quote) - has_domain = 1; - if (buffer[i] == ':' && !escape && !comment && !quote) - has_group = 1; - - /* update insert point if not in comment and not on a whitespace */ - if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) - pos_component = i; - } - - /* parse error, do not attempt to modify */ - if (escape || quote || comment || bracket) - return; - - /* domain already present, no need to modify */ - if (has_domain) - return; - - /* address is group, skip */ - if (has_group) - return; - - /* there's an address between brackets, just append domain */ - if (has_bracket) { - pos_bracket--; - while (isspace((unsigned char)buffer[pos_bracket])) - pos_bracket--; - if (buffer[pos_bracket] == '<') - return; - pos_insert = pos_bracket + 1; - } - else { - /* otherwise append address to last component */ - pos_insert = pos_component + 1; - - /* empty address */ - if (buffer[pos_component] == '\0' || - isspace((unsigned char)buffer[pos_component])) - return; - } - - if (snprintf(copy, sizeof copy, "%.*s@%s%s", - (int)pos_insert, buffer, - domain, - buffer+pos_insert) >= (int)sizeof copy) - return; - - memcpy(buffer, copy, len); -} - -static void -header_address_rewrite_buffer(char *buffer, const char *address, size_t len) -{ - size_t i; - int address_len; - int escape, quote, comment, bracket; - int has_bracket, has_group; - int pos_bracket_beg, pos_bracket_end, pos_component_beg, pos_component_end; - int insert_beg, insert_end; - char copy[APPEND_DOMAIN_BUFFER_SIZE]; - - escape = quote = comment = bracket = 0; - has_bracket = has_group = 0; - pos_bracket_beg = pos_bracket_end = pos_component_beg = pos_component_end = 0; - for (i = 0; buffer[i]; ++i) { - if (buffer[i] == '(' && !escape && !quote) - comment++; - if (buffer[i] == '"' && !escape && !comment) - quote = !quote; - if (buffer[i] == ')' && !escape && !quote && comment) - comment--; - if (buffer[i] == '\\' && !escape && !comment && !quote) - escape = 1; - else - escape = 0; - if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { - bracket++; - has_bracket = 1; - pos_bracket_beg = i+1; - } - if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { - bracket--; - pos_bracket_end = i; - } - if (buffer[i] == ':' && !escape && !comment && !quote) - has_group = 1; - - /* update insert point if not in comment and not on a whitespace */ - if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) - pos_component_end = i; - } - - /* parse error, do not attempt to modify */ - if (escape || quote || comment || bracket) - return; - - /* address is group, skip */ - if (has_group) - return; - - /* there's an address between brackets, just replace everything brackets */ - if (has_bracket) { - insert_beg = pos_bracket_beg; - insert_end = pos_bracket_end; - } - else { - if (pos_component_end == 0) - pos_component_beg = 0; - else { - for (pos_component_beg = pos_component_end; pos_component_beg >= 0; --pos_component_beg) - if (buffer[pos_component_beg] == ')' || isspace((unsigned char)buffer[pos_component_beg])) - break; - pos_component_beg += 1; - pos_component_end += 1; - } - insert_beg = pos_component_beg; - insert_end = pos_component_end; - } - - /* check that masquerade won' t overflow */ - address_len = strlen(address); - if (strlen(buffer) - (insert_end - insert_beg) + address_len >= len) - return; - - (void)strlcpy(copy, buffer, sizeof copy); - (void)strlcpy(copy+insert_beg, address, sizeof (copy) - insert_beg); - (void)strlcat(copy, buffer+insert_end, sizeof (copy)); - memcpy(buffer, copy, len); -} - -static void -header_domain_append_callback(struct smtp_tx *tx, const char *hdr, - const char *val) -{ - size_t i, j, linelen; - int escape, quote, comment, skip; - char buffer[APPEND_DOMAIN_BUFFER_SIZE]; - const char *line, *end; - - if (smtp_message_printf(tx, "%s:", hdr) == -1) - return; - - j = 0; - escape = quote = comment = skip = 0; - memset(buffer, 0, sizeof buffer); - - for (line = val; line; line = end) { - end = strchr(line, '\n'); - if (end) { - linelen = end - line; - end++; - } - else - linelen = strlen(line); - - for (i = 0; i < linelen; ++i) { - if (line[i] == '(' && !escape && !quote) - comment++; - if (line[i] == '"' && !escape && !comment) - quote = !quote; - if (line[i] == ')' && !escape && !quote && comment) - comment--; - if (line[i] == '\\' && !escape && !comment && !quote) - escape = 1; - else - escape = 0; - - /* found a separator, buffer contains a full address */ - if (line[i] == ',' && !escape && !quote && !comment) { - if (!skip && j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { - header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); - if (tx->session->flags & SF_AUTHENTICATED && - tx->session->listener->sendertable[0] && - tx->session->listener->flags & F_MASQUERADE && - !(strcasecmp(hdr, "From"))) - header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), - sizeof buffer); - } - if (smtp_message_printf(tx, "%s,", buffer) == -1) - return; - j = 0; - skip = 0; - memset(buffer, 0, sizeof buffer); - } - else { - if (skip) { - if (smtp_message_printf(tx, "%c", line[i]) == -1) - return; - } - else { - buffer[j++] = line[i]; - if (j == sizeof (buffer) - 1) { - if (smtp_message_printf(tx, "%s", buffer) == -1) - return; - skip = 1; - j = 0; - memset(buffer, 0, sizeof buffer); - } - } - } - } - if (skip) { - if (smtp_message_printf(tx, "\n") == -1) - return; - } - else { - buffer[j++] = '\n'; - if (j == sizeof (buffer) - 1) { - if (smtp_message_printf(tx, "%s", buffer) == -1) - return; - skip = 1; - j = 0; - memset(buffer, 0, sizeof buffer); - } - } - } - - /* end of header, if buffer is not empty we'll process it */ - if (buffer[0]) { - if (j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { - header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); - if (tx->session->flags & SF_AUTHENTICATED && - tx->session->listener->sendertable[0] && - tx->session->listener->flags & F_MASQUERADE && - !(strcasecmp(hdr, "From"))) - header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), - sizeof buffer); - } - smtp_message_printf(tx, "%s", buffer); - } -} - -static void -smtp_session_init(void) -{ - static int init = 0; - - if (!init) { - tree_init(&wait_lka_helo); - tree_init(&wait_lka_mail); - tree_init(&wait_lka_rcpt); - tree_init(&wait_parent_auth); - tree_init(&wait_queue_msg); - tree_init(&wait_queue_fd); - tree_init(&wait_queue_commit); - tree_init(&wait_ssl_init); - tree_init(&wait_ssl_verify); - tree_init(&wait_filters); - tree_init(&wait_filter_fd); - init = 1; - } -} - -int -smtp_session(struct listener *listener, int sock, - const struct sockaddr_storage *ss, const char *hostname, struct io *io) -{ - struct smtp_session *s; - - smtp_session_init(); - - if ((s = calloc(1, sizeof(*s))) == NULL) - return (-1); - - s->id = generate_uid(); - s->listener = listener; - memmove(&s->ss, ss, sizeof(*ss)); - - if (io != NULL) - s->io = io; - else - s->io = io_new(); - - io_set_callback(s->io, smtp_io, s); - io_set_fd(s->io, sock); - io_set_timeout(s->io, SMTPD_SESSION_TIMEOUT * 1000); - io_set_write(s->io); - s->state = STATE_NEW; - - (void)strlcpy(s->smtpname, listener->hostname, sizeof(s->smtpname)); - - log_trace(TRACE_SMTP, "smtp: %p: connected to listener %p " - "[hostname=%s, port=%d, tag=%s]", s, listener, - listener->hostname, ntohs(listener->port), listener->tag); - - /* For local enqueueing, the hostname is already set */ - if (hostname) { - s->flags |= SF_AUTHENTICATED; - /* A bit of a hack */ - if (!strcmp(hostname, "localhost")) - s->flags |= SF_BOUNCE; - (void)strlcpy(s->rdns, hostname, sizeof(s->rdns)); - s->fcrdns = 1; - smtp_lookup_servername(s); - } else { - resolver_getnameinfo((struct sockaddr *)&s->ss, NI_NAMEREQD, - smtp_getnameinfo_cb, s); - } - - /* session may have been freed by now */ - - return (0); -} - -static void -smtp_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) -{ - struct smtp_session *s = arg; - struct addrinfo hints; - - if (gaierrno) { - (void)strlcpy(s->rdns, "", sizeof(s->rdns)); - - if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) - s->fcrdns = 0; - else { - log_warnx("getnameinfo: %s: %s", ss_to_text(&s->ss), - gai_strerror(gaierrno)); - s->fcrdns = -1; - } - - smtp_lookup_servername(s); - return; - } - - (void)strlcpy(s->rdns, host, sizeof(s->rdns)); - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = s->ss.ss_family; - hints.ai_socktype = SOCK_STREAM; - resolver_getaddrinfo(s->rdns, NULL, &hints, smtp_getaddrinfo_cb, s); -} - -static void -smtp_getaddrinfo_cb(void *arg, int gaierrno, struct addrinfo *ai0) -{ - struct smtp_session *s = arg; - struct addrinfo *ai; - char fwd[64], rev[64]; - - if (gaierrno) { - if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) - s->fcrdns = 0; - else { - log_warnx("getaddrinfo: %s: %s", s->rdns, - gai_strerror(gaierrno)); - s->fcrdns = -1; - } - } - else { - strlcpy(rev, ss_to_text(&s->ss), sizeof(rev)); - for (ai = ai0; ai; ai = ai->ai_next) { - strlcpy(fwd, sa_to_text(ai->ai_addr), sizeof(fwd)); - if (!strcmp(fwd, rev)) { - s->fcrdns = 1; - break; - } - } - portable_freeaddrinfo(ai0); - } - - smtp_lookup_servername(s); -} - -void -smtp_session_imsg(struct mproc *p, struct imsg *imsg) -{ - struct smtp_session *s; - struct smtp_rcpt *rcpt; - char user[SMTPD_MAXMAILADDRSIZE]; - char tmp[SMTP_LINE_MAX]; - struct msg m; - const char *line, *helo; - uint64_t reqid, evpid; - uint32_t msgid; - int status, success; - int filter_response; - const char *filter_param; - uint8_t i; - - switch (imsg->hdr.type) { - - case IMSG_SMTP_CHECK_SENDER: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &status); - m_end(&m); - s = tree_xpop(&wait_lka_mail, reqid); - switch (status) { - case LKA_OK: - smtp_tx_create_message(s->tx); - break; - - case LKA_PERMFAIL: - smtp_tx_free(s->tx); - smtp_reply(s, "%d %s", 530, "Sender rejected"); - break; - case LKA_TEMPFAIL: - smtp_tx_free(s->tx); - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - break; - } - return; - - case IMSG_SMTP_EXPAND_RCPT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &status); - m_get_string(&m, &line); - m_end(&m); - s = tree_xpop(&wait_lka_rcpt, reqid); - - tmp[0] = '\0'; - if (s->tx->evp.rcpt.user[0]) { - (void)strlcpy(tmp, s->tx->evp.rcpt.user, sizeof tmp); - if (s->tx->evp.rcpt.domain[0]) { - (void)strlcat(tmp, "@", sizeof tmp); - (void)strlcat(tmp, s->tx->evp.rcpt.domain, - sizeof tmp); - } - } - - switch (status) { - case LKA_OK: - fatalx("unexpected ok"); - case LKA_PERMFAIL: - smtp_reply(s, "%s: <%s>", line, tmp); - break; - case LKA_TEMPFAIL: - smtp_reply(s, "%s: <%s>", line, tmp); - break; - } - return; - - case IMSG_SMTP_LOOKUP_HELO: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - s = tree_xpop(&wait_lka_helo, reqid); - m_get_int(&m, &status); - if (status == LKA_OK) { - m_get_string(&m, &helo); - (void)strlcpy(s->smtpname, helo, sizeof(s->smtpname)); - } - m_end(&m); - smtp_connected(s); - return; - - case IMSG_SMTP_MESSAGE_CREATE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - s = tree_xpop(&wait_queue_msg, reqid); - if (success) { - m_get_msgid(&m, &msgid); - s->tx->msgid = msgid; - s->tx->evp.id = msgid_to_evpid(msgid); - s->tx->rcptcount = 0; - smtp_reply(s, "250 %s Ok", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); - } else { - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_tx_free(s->tx); - smtp_enter_state(s, STATE_QUIT); - } - m_end(&m); - return; - - case IMSG_SMTP_MESSAGE_OPEN: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - m_end(&m); - - s = tree_xpop(&wait_queue_fd, reqid); - if (!success || imsg->fd == -1) { - if (imsg->fd != -1) - close(imsg->fd); - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_enter_state(s, STATE_QUIT); - return; - } - - log_debug("smtp: %p: fd %d from queue", s, imsg->fd); - - if (smtp_message_fd(s->tx, imsg->fd)) { - if (!SESSION_DATA_FILTERED(s)) - smtp_message_begin(s->tx); - else - smtp_filter_data_begin(s); - } - return; - - case IMSG_FILTER_SMTP_DATA_BEGIN: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - m_end(&m); - - s = tree_xpop(&wait_filter_fd, reqid); - if (!success || imsg->fd == -1) { - if (imsg->fd != -1) - close(imsg->fd); - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_enter_state(s, STATE_QUIT); - return; - } - - log_debug("smtp: %p: fd %d from lka", s, imsg->fd); - - smtp_filter_fd(s->tx, imsg->fd); - smtp_message_begin(s->tx); - return; - - case IMSG_QUEUE_ENVELOPE_SUBMIT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - s = tree_xget(&wait_lka_rcpt, reqid); - if (success) { - m_get_evpid(&m, &evpid); - s->tx->evp.id = evpid; - s->tx->destcount++; - smtp_report_tx_envelope(s, s->tx->msgid, evpid); - } - else - s->tx->error = TX_ERROR_ENVELOPE; - m_end(&m); - return; - - case IMSG_QUEUE_ENVELOPE_COMMIT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - m_end(&m); - if (!success) - fatalx("commit evp failed: not supposed to happen"); - s = tree_xpop(&wait_lka_rcpt, reqid); - if (s->tx->error) { - /* - * If an envelope failed, we can't cancel the last - * RCPT only so we must cancel the whole transaction - * and close the connection. - */ - smtp_reply(s, "421 %s Temporary failure", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_enter_state(s, STATE_QUIT); - } - else { - rcpt = xcalloc(1, sizeof(*rcpt)); - rcpt->evpid = s->tx->evp.id; - rcpt->destcount = s->tx->destcount; - rcpt->maddr = s->tx->evp.rcpt; - TAILQ_INSERT_TAIL(&s->tx->rcpts, rcpt, entry); - - s->tx->destcount = 0; - s->tx->rcptcount++; - smtp_reply(s, "250 %s %s: Recipient ok", - esc_code(ESC_STATUS_OK, ESC_DESTINATION_ADDRESS_VALID), - esc_description(ESC_DESTINATION_ADDRESS_VALID)); - } - return; - - case IMSG_SMTP_MESSAGE_COMMIT: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - m_end(&m); - s = tree_xpop(&wait_queue_commit, reqid); - if (!success) { - smtp_reply(s, "421 %s Temporary failure", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_tx_free(s->tx); - smtp_enter_state(s, STATE_QUIT); - return; - } - - smtp_reply(s, "250 %s %08x Message accepted for delivery", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS), - s->tx->msgid); - smtp_report_tx_commit(s, s->tx->msgid, s->tx->odatalen); - smtp_report_tx_reset(s, s->tx->msgid); - - log_info("%016"PRIx64" smtp message " - "msgid=%08x size=%zu nrcpt=%zu proto=%s", - s->id, - s->tx->msgid, - s->tx->odatalen, - s->tx->rcptcount, - s->flags & SF_EHLO ? "ESMTP" : "SMTP"); - TAILQ_FOREACH(rcpt, &s->tx->rcpts, entry) { - log_info("%016"PRIx64" smtp envelope " - "evpid=%016"PRIx64" from=<%s%s%s> to=<%s%s%s>", - s->id, - rcpt->evpid, - s->tx->evp.sender.user, - s->tx->evp.sender.user[0] == '\0' ? "" : "@", - s->tx->evp.sender.domain, - rcpt->maddr.user, - rcpt->maddr.user[0] == '\0' ? "" : "@", - rcpt->maddr.domain); - } - smtp_tx_free(s->tx); - s->mailcount++; - smtp_enter_state(s, STATE_HELO); - return; - - case IMSG_SMTP_AUTHENTICATE: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &success); - m_end(&m); - - s = tree_xpop(&wait_parent_auth, reqid); - strnvis(user, s->username, sizeof user, VIS_WHITE | VIS_SAFE); - if (success == LKA_OK) { - log_info("%016"PRIx64" smtp " - "authentication user=%s " - "result=ok", - s->id, user); - s->flags |= SF_AUTHENTICATED; - smtp_report_link_auth(s, user, "pass"); - smtp_reply(s, "235 %s Authentication succeeded", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); - } - else if (success == LKA_PERMFAIL) { - log_info("%016"PRIx64" smtp " - "authentication user=%s " - "result=permfail", - s->id, user); - smtp_report_link_auth(s, user, "fail"); - smtp_auth_failure_pause(s); - return; - } - else if (success == LKA_TEMPFAIL) { - log_info("%016"PRIx64" smtp " - "authentication user=%s " - "result=tempfail", - s->id, user); - smtp_report_link_auth(s, user, "error"); - smtp_reply(s, "421 %s Temporary failure", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - } - else - fatalx("bad lka response"); - - smtp_enter_state(s, STATE_HELO); - return; - - case IMSG_FILTER_SMTP_PROTOCOL: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_int(&m, &filter_response); - if (filter_response != FILTER_PROCEED && - filter_response != FILTER_JUNK) - m_get_string(&m, &filter_param); - else - filter_param = NULL; - m_end(&m); - - s = tree_xpop(&wait_filters, reqid); - - switch (filter_response) { - case FILTER_REJECT: - case FILTER_DISCONNECT: - if (!valid_smtp_response(filter_param) || - (filter_param[0] != '4' && filter_param[0] != '5')) - filter_param = "421 Internal server error"; - if (!strncmp(filter_param, "421", 3)) - filter_response = FILTER_DISCONNECT; - - smtp_report_filter_response(s, s->filter_phase, - filter_response, filter_param); - - smtp_reply(s, "%s", filter_param); - - if (filter_response == FILTER_DISCONNECT) - smtp_enter_state(s, STATE_QUIT); - else if (s->filter_phase == FILTER_COMMIT) - smtp_proceed_rollback(s, NULL); - break; - - - case FILTER_JUNK: - if (s->tx) - s->tx->junk = 1; - else - s->junk = 1; - /* fallthrough */ - - case FILTER_PROCEED: - filter_param = s->filter_param; - /* fallthrough */ - - case FILTER_REWRITE: - smtp_report_filter_response(s, s->filter_phase, - filter_response, - filter_param == s->filter_param ? NULL : filter_param); - if (s->filter_phase == FILTER_CONNECT) { - smtp_proceed_connected(s); - return; - } - for (i = 0; i < nitems(commands); ++i) - if (commands[i].filter_phase == s->filter_phase) { - if (filter_response == FILTER_REWRITE) - if (!commands[i].check(s, filter_param)) - break; - commands[i].proceed(s, filter_param); - break; - } - break; - } - return; - } - - log_warnx("smtp_session_imsg: unexpected %s imsg", - imsg_to_str(imsg->hdr.type)); - fatalx(NULL); -} - -static void -smtp_tls_verified(struct smtp_session *s) -{ - X509 *x; - - x = SSL_get_peer_certificate(io_tls(s->io)); - if (x) { - log_info("%016"PRIx64" smtp " - "client-cert-check result=\"%s\"", - s->id, - (s->flags & SF_VERIFIED) ? "success" : "failure"); - X509_free(x); - } - - if (s->listener->flags & F_SMTPS) { - stat_increment("smtp.smtps", 1); - io_set_write(s->io); - smtp_send_banner(s); - } - else { - stat_increment("smtp.tls", 1); - smtp_enter_state(s, STATE_HELO); - } -} - -static void -smtp_io(struct io *io, int evt, void *arg) -{ - struct smtp_session *s = arg; - char *line; - size_t len; - int eom; - - log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), - io_strio(io)); - - switch (evt) { - - case IO_TLSREADY: - log_info("%016"PRIx64" smtp tls ciphers=%s", - s->id, ssl_to_text(io_tls(s->io))); - - smtp_report_link_tls(s, ssl_to_text(io_tls(s->io))); - - s->flags |= SF_SECURE; - s->helo[0] = '\0'; - - smtp_cert_verify(s); - break; - - case IO_DATAIN: - nextline: - line = io_getline(s->io, &len); - if ((line == NULL && io_datalen(s->io) >= SMTP_LINE_MAX) || - (line && len >= SMTP_LINE_MAX)) { - s->flags |= SF_BADINPUT; - smtp_reply(s, "500 %s Line too long", - esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); - smtp_enter_state(s, STATE_QUIT); - io_set_write(io); - return; - } - - /* No complete line received */ - if (line == NULL) - return; - - /* Strip trailing '\r' */ - if (len && line[len - 1] == '\r') - line[--len] = '\0'; - - /* Message body */ - eom = 0; - if (s->state == STATE_BODY) { - if (strcmp(line, ".")) { - s->tx->datain += strlen(line) + 1; - if (s->tx->datain > env->sc_maxsize) - s->tx->error = TX_ERROR_SIZE; - } - eom = (s->tx->filter == NULL) ? - smtp_tx_dataline(s->tx, line) : - smtp_tx_filtered_dataline(s->tx, line); - if (eom == 0) - goto nextline; - } - - /* Pipelining not supported */ - if (io_datalen(s->io)) { - s->flags |= SF_BADINPUT; - smtp_reply(s, "500 %s %s: Pipelining not supported", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - smtp_enter_state(s, STATE_QUIT); - io_set_write(io); - return; - } - - if (eom) { - io_set_write(io); - if (s->tx->filter == NULL) - smtp_tx_eom(s->tx); - return; - } - - /* Must be a command */ - if (strlcpy(s->cmd, line, sizeof(s->cmd)) >= sizeof(s->cmd)) { - s->flags |= SF_BADINPUT; - smtp_reply(s, "500 %s Command line too long", - esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); - smtp_enter_state(s, STATE_QUIT); - io_set_write(io); - return; - } - io_set_write(io); - smtp_command(s, line); - break; - - case IO_LOWAT: - if (s->state == STATE_QUIT) { - log_info("%016"PRIx64" smtp disconnected " - "reason=quit", - s->id); - smtp_free(s, "done"); - break; - } - - /* Wait for the client to start tls */ - if (s->state == STATE_TLS) { - smtp_cert_init(s); - break; - } - - io_set_read(io); - break; - - case IO_TIMEOUT: - log_info("%016"PRIx64" smtp disconnected " - "reason=timeout", - s->id); - smtp_report_timeout(s); - smtp_free(s, "timeout"); - break; - - case IO_DISCONNECTED: - log_info("%016"PRIx64" smtp disconnected " - "reason=disconnect", - s->id); - smtp_free(s, "disconnected"); - break; - - case IO_ERROR: - log_info("%016"PRIx64" smtp disconnected " - "reason=\"io-error: %s\"", - s->id, io_error(io)); - smtp_free(s, "IO error"); - break; - - default: - fatalx("smtp_io()"); - } -} - -static void -smtp_command(struct smtp_session *s, char *line) -{ - char *args; - int cmd, i; - - log_trace(TRACE_SMTP, "smtp: %p: <<< %s", s, line); - - /* - * These states are special. - */ - if (s->state == STATE_AUTH_INIT) { - smtp_report_protocol_client(s, "********"); - smtp_rfc4954_auth_plain(s, line); - return; - } - if (s->state == STATE_AUTH_USERNAME || s->state == STATE_AUTH_PASSWORD) { - smtp_report_protocol_client(s, "********"); - smtp_rfc4954_auth_login(s, line); - return; - } - - if (s->state == STATE_HELO && strncasecmp(line, "AUTH PLAIN ", 11) == 0) - smtp_report_protocol_client(s, "AUTH PLAIN ********"); - else - smtp_report_protocol_client(s, line); - - - /* - * Unlike other commands, "mail from" and "rcpt to" contain a - * space in the command name. - */ - if (strncasecmp("mail from:", line, 10) == 0 || - strncasecmp("rcpt to:", line, 8) == 0) - args = strchr(line, ':'); - else - args = strchr(line, ' '); - - if (args) { - *args++ = '\0'; - while (isspace((unsigned char)*args)) - args++; - } - - cmd = -1; - for (i = 0; commands[i].code != -1; i++) - if (!strcasecmp(line, commands[i].cmd)) { - cmd = commands[i].code; - break; - } - - s->last_cmd = cmd; - switch (cmd) { - /* - * INIT - */ - case CMD_HELO: - if (!smtp_check_helo(s, args)) - break; - smtp_filter_phase(FILTER_HELO, s, args); - break; - - case CMD_EHLO: - if (!smtp_check_ehlo(s, args)) - break; - smtp_filter_phase(FILTER_EHLO, s, args); - break; - - /* - * SETUP - */ - case CMD_STARTTLS: - if (!smtp_check_starttls(s, args)) - break; - - smtp_filter_phase(FILTER_STARTTLS, s, NULL); - break; - - case CMD_AUTH: - if (!smtp_check_auth(s, args)) - break; - smtp_filter_phase(FILTER_AUTH, s, args); - break; - - case CMD_MAIL_FROM: - if (!smtp_check_mail_from(s, args)) - break; - smtp_filter_phase(FILTER_MAIL_FROM, s, args); - break; - - /* - * TRANSACTION - */ - case CMD_RCPT_TO: - if (!smtp_check_rcpt_to(s, args)) - break; - smtp_filter_phase(FILTER_RCPT_TO, s, args); - break; - - case CMD_RSET: - if (!smtp_check_rset(s, args)) - break; - smtp_filter_phase(FILTER_RSET, s, NULL); - break; - - case CMD_DATA: - if (!smtp_check_data(s, args)) - break; - smtp_filter_phase(FILTER_DATA, s, NULL); - break; - - /* - * ANY - */ - case CMD_QUIT: - if (!smtp_check_noparam(s, args)) - break; - smtp_filter_phase(FILTER_QUIT, s, NULL); - break; - - case CMD_NOOP: - if (!smtp_check_noparam(s, args)) - break; - smtp_filter_phase(FILTER_NOOP, s, NULL); - break; - - case CMD_HELP: - if (!smtp_check_noparam(s, args)) - break; - smtp_proceed_help(s, NULL); - break; - - case CMD_WIZ: - if (!smtp_check_noparam(s, args)) - break; - smtp_proceed_wiz(s, NULL); - break; - - default: - smtp_reply(s, "500 %s %s: Command unrecognized", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - break; - } -} - -static int -smtp_check_rset(struct smtp_session *s, const char *args) -{ - if (!smtp_check_noparam(s, args)) - return 0; - - if (s->helo[0] == '\0') { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - return 1; -} - -static int -smtp_check_helo(struct smtp_session *s, const char *args) -{ - if (!s->banner_sent) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->helo[0]) { - smtp_reply(s, "503 %s %s: Already identified", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (args == NULL) { - smtp_reply(s, "501 %s %s: HELO requires domain name", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (!valid_domainpart(args)) { - smtp_reply(s, "501 %s %s: Invalid domain name", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); - return 0; - } - - return 1; -} - -static int -smtp_check_ehlo(struct smtp_session *s, const char *args) -{ - if (!s->banner_sent) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->helo[0]) { - smtp_reply(s, "503 %s %s: Already identified", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (args == NULL) { - smtp_reply(s, "501 %s %s: EHLO requires domain name", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (!valid_domainpart(args)) { - smtp_reply(s, "501 %s %s: Invalid domain name", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); - return 0; - } - - return 1; -} - -static int -smtp_check_auth(struct smtp_session *s, const char *args) -{ - if (s->helo[0] == '\0' || s->tx) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->flags & SF_AUTHENTICATED) { - smtp_reply(s, "503 %s %s: Already authenticated", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (!ADVERTISE_AUTH(s)) { - smtp_reply(s, "503 %s %s: Command not supported", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (args == NULL) { - smtp_reply(s, "501 %s %s: No parameters given", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); - return 0; - } - - return 1; -} - -static int -smtp_check_starttls(struct smtp_session *s, const char *args) -{ - if (s->helo[0] == '\0' || s->tx) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (!(s->listener->flags & F_STARTTLS)) { - smtp_reply(s, "503 %s %s: Command not supported", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->flags & SF_SECURE) { - smtp_reply(s, "503 %s %s: Channel already secured", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (args != NULL) { - smtp_reply(s, "501 %s %s: No parameters allowed", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); - return 0; - } - - return 1; -} - -static int -smtp_check_mail_from(struct smtp_session *s, const char *args) -{ - char *copy; - char tmp[SMTP_LINE_MAX]; - struct mailaddr sender; - - (void)strlcpy(tmp, args, sizeof tmp); - copy = tmp; - - if (s->helo[0] == '\0' || s->tx) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->listener->flags & F_STARTTLS_REQUIRE && - !(s->flags & SF_SECURE)) { - smtp_reply(s, - "530 %s %s: Must issue a STARTTLS command first", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->listener->flags & F_AUTH_REQUIRE && - !(s->flags & SF_AUTHENTICATED)) { - smtp_reply(s, - "530 %s %s: Must issue an AUTH command first", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->mailcount >= env->sc_session_max_mails) { - /* we can pretend we had too many recipients */ - smtp_reply(s, "452 %s %s: Too many messages sent", - esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), - esc_description(ESC_TOO_MANY_RECIPIENTS)); - return 0; - } - - if (smtp_mailaddr(&sender, copy, 1, ©, - s->smtpname) == 0) { - smtp_reply(s, "553 %s Sender address syntax error", - esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); - return 0; - } - - return 1; -} - -static int -smtp_check_rcpt_to(struct smtp_session *s, const char *args) -{ - char *copy; - char tmp[SMTP_LINE_MAX]; - - (void)strlcpy(tmp, args, sizeof tmp); - copy = tmp; - - if (s->tx == NULL) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->tx->rcptcount >= env->sc_session_max_rcpt) { - smtp_reply(s->tx->session, "451 %s %s: Too many recipients", - esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), - esc_description(ESC_TOO_MANY_RECIPIENTS)); - return 0; - } - - if (smtp_mailaddr(&s->tx->evp.rcpt, copy, 0, ©, - s->tx->session->smtpname) == 0) { - smtp_reply(s->tx->session, - "501 %s Recipient address syntax error", - esc_code(ESC_STATUS_PERMFAIL, - ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); - return 0; - } - - return 1; -} - -static int -smtp_check_data(struct smtp_session *s, const char *args) -{ - if (!smtp_check_noparam(s, args)) - return 0; - - if (s->tx == NULL) { - smtp_reply(s, "503 %s %s: Command not allowed at this point.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); - return 0; - } - - if (s->tx->rcptcount == 0) { - smtp_reply(s, "503 %s %s: No recipient specified", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); - return 0; - } - - return 1; -} - -static int -smtp_check_noparam(struct smtp_session *s, const char *args) -{ - if (args != NULL) { - smtp_reply(s, "500 %s %s: command does not accept arguments.", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); - return 0; - } - return 1; -} - -static void -smtp_query_filters(enum filter_phase phase, struct smtp_session *s, const char *args) -{ - m_create(p_lka, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_int(p_lka, phase); - m_add_string(p_lka, args); - m_close(p_lka); - tree_xset(&wait_filters, s->id, s); -} - -static void -smtp_filter_begin(struct smtp_session *s) -{ - if (!SESSION_FILTERED(s)) - return; - - m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_string(p_lka, s->listener->filter_name); - m_close(p_lka); -} - -static void -smtp_filter_end(struct smtp_session *s) -{ - if (!SESSION_FILTERED(s)) - return; - - m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); - m_add_id(p_lka, s->id); - m_close(p_lka); -} - -static void -smtp_filter_data_begin(struct smtp_session *s) -{ - if (!SESSION_FILTERED(s)) - return; - - m_create(p_lka, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, -1); - m_add_id(p_lka, s->id); - m_close(p_lka); - tree_xset(&wait_filter_fd, s->id, s); -} - -static void -smtp_filter_data_end(struct smtp_session *s) -{ - if (!SESSION_FILTERED(s)) - return; - - if (s->tx->filter == NULL) - return; - - io_free(s->tx->filter); - s->tx->filter = NULL; - - m_create(p_lka, IMSG_FILTER_SMTP_DATA_END, 0, 0, -1); - m_add_id(p_lka, s->id); - m_close(p_lka); -} - -static void -smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *param) -{ - uint8_t i; - - s->filter_phase = phase; - s->filter_param = param; - - if (SESSION_FILTERED(s)) { - smtp_query_filters(phase, s, param ? param : ""); - return; - } - - if (s->filter_phase == FILTER_CONNECT) { - smtp_proceed_connected(s); - return; - } - - for (i = 0; i < nitems(commands); ++i) - if (commands[i].filter_phase == s->filter_phase) { - commands[i].proceed(s, param); - break; - } -} - -static void -smtp_proceed_rset(struct smtp_session *s, const char *args) -{ - smtp_reply(s, "250 %s Reset state", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); - - if (s->tx) { - if (s->tx->msgid) - smtp_tx_rollback(s->tx); - smtp_tx_free(s->tx); - } -} - -static void -smtp_proceed_helo(struct smtp_session *s, const char *args) -{ - (void)strlcpy(s->helo, args, sizeof(s->helo)); - s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; - - smtp_report_link_identify(s, "HELO", s->helo); - - smtp_enter_state(s, STATE_HELO); - - smtp_reply(s, "250 %s Hello %s %s%s%s, pleased to meet you", - s->smtpname, - s->helo, - s->ss.ss_family == AF_INET6 ? "" : "[", - ss_to_text(&s->ss), - s->ss.ss_family == AF_INET6 ? "" : "]"); -} - -static void -smtp_proceed_ehlo(struct smtp_session *s, const char *args) -{ - (void)strlcpy(s->helo, args, sizeof(s->helo)); - s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; - s->flags |= SF_EHLO; - s->flags |= SF_8BITMIME; - - smtp_report_link_identify(s, "EHLO", s->helo); - - smtp_enter_state(s, STATE_HELO); - smtp_reply(s, "250-%s Hello %s %s%s%s, pleased to meet you", - s->smtpname, - s->helo, - s->ss.ss_family == AF_INET6 ? "" : "[", - ss_to_text(&s->ss), - s->ss.ss_family == AF_INET6 ? "" : "]"); - - smtp_reply(s, "250-8BITMIME"); - smtp_reply(s, "250-ENHANCEDSTATUSCODES"); - smtp_reply(s, "250-SIZE %zu", env->sc_maxsize); - if (ADVERTISE_EXT_DSN(s)) - smtp_reply(s, "250-DSN"); - if (ADVERTISE_TLS(s)) - smtp_reply(s, "250-STARTTLS"); - if (ADVERTISE_AUTH(s)) - smtp_reply(s, "250-AUTH PLAIN LOGIN"); - smtp_reply(s, "250 HELP"); -} - -static void -smtp_proceed_auth(struct smtp_session *s, const char *args) -{ - char tmp[SMTP_LINE_MAX]; - char *eom, *method; - - (void)strlcpy(tmp, args, sizeof tmp); - - method = tmp; - eom = strchr(tmp, ' '); - if (eom == NULL) - eom = strchr(tmp, '\t'); - if (eom != NULL) - *eom++ = '\0'; - if (strcasecmp(method, "PLAIN") == 0) - smtp_rfc4954_auth_plain(s, eom); - else if (strcasecmp(method, "LOGIN") == 0) - smtp_rfc4954_auth_login(s, eom); - else - smtp_reply(s, "504 %s %s: AUTH method \"%s\" not supported", - esc_code(ESC_STATUS_PERMFAIL, ESC_SECURITY_FEATURES_NOT_SUPPORTED), - esc_description(ESC_SECURITY_FEATURES_NOT_SUPPORTED), - method); -} - -static void -smtp_proceed_starttls(struct smtp_session *s, const char *args) -{ - smtp_reply(s, "220 %s Ready to start TLS", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); - smtp_enter_state(s, STATE_TLS); -} - -static void -smtp_proceed_mail_from(struct smtp_session *s, const char *args) -{ - char *copy; - char tmp[SMTP_LINE_MAX]; - - (void)strlcpy(tmp, args, sizeof tmp); - copy = tmp; - - if (!smtp_tx(s)) { - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_enter_state(s, STATE_QUIT); - return; - } - - if (smtp_mailaddr(&s->tx->evp.sender, copy, 1, ©, - s->smtpname) == 0) { - smtp_reply(s, "553 %s Sender address syntax error", - esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); - smtp_tx_free(s->tx); - return; - } - - smtp_tx_mail_from(s->tx, args); -} - -static void -smtp_proceed_rcpt_to(struct smtp_session *s, const char *args) -{ - smtp_tx_rcpt_to(s->tx, args); -} - -static void -smtp_proceed_data(struct smtp_session *s, const char *args) -{ - smtp_tx_open_message(s->tx); -} - -static void -smtp_proceed_quit(struct smtp_session *s, const char *args) -{ - smtp_reply(s, "221 %s Bye", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); - smtp_enter_state(s, STATE_QUIT); -} - -static void -smtp_proceed_noop(struct smtp_session *s, const char *args) -{ - smtp_reply(s, "250 %s Ok", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); -} - -static void -smtp_proceed_help(struct smtp_session *s, const char *args) -{ - const char *code = esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS); - - smtp_reply(s, "214-%s This is " SMTPD_NAME, code); - smtp_reply(s, "214-%s To report bugs in the implementation, " - "please contact bugs@openbsd.org", code); - smtp_reply(s, "214-%s with full details", code); - smtp_reply(s, "214 %s End of HELP info", code); -} - -static void -smtp_proceed_wiz(struct smtp_session *s, const char *args) -{ - smtp_reply(s, "500 %s %s: this feature is not supported yet ;-)", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), - esc_description(ESC_INVALID_COMMAND)); -} - -static void -smtp_proceed_commit(struct smtp_session *s, const char *args) -{ - smtp_message_end(s->tx); -} - -static void -smtp_proceed_rollback(struct smtp_session *s, const char *args) -{ - struct smtp_tx *tx; - - tx = s->tx; - - fclose(tx->ofile); - tx->ofile = NULL; - - smtp_tx_rollback(tx); - smtp_tx_free(tx); - smtp_enter_state(s, STATE_HELO); -} - -static void -smtp_rfc4954_auth_plain(struct smtp_session *s, char *arg) -{ - char buf[1024], *user, *pass; - int len; - - switch (s->state) { - case STATE_HELO: - if (arg == NULL) { - smtp_enter_state(s, STATE_AUTH_INIT); - smtp_reply(s, "334 "); - return; - } - smtp_enter_state(s, STATE_AUTH_INIT); - /* FALLTHROUGH */ - - case STATE_AUTH_INIT: - /* String is not NUL terminated, leave room. */ - if ((len = base64_decode(arg, (unsigned char *)buf, - sizeof(buf) - 1)) == -1) - goto abort; - /* buf is a byte string, NUL terminate. */ - buf[len] = '\0'; - - /* - * Skip "foo" in "foo\0user\0pass", if present. - */ - user = memchr(buf, '\0', len); - if (user == NULL || user >= buf + len - 2) - goto abort; - user++; /* skip NUL */ - if (strlcpy(s->username, user, sizeof(s->username)) - >= sizeof(s->username)) - goto abort; - - pass = memchr(user, '\0', len - (user - buf)); - if (pass == NULL || pass >= buf + len - 2) - goto abort; - pass++; /* skip NUL */ - - m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_string(p_lka, s->listener->authtable); - m_add_string(p_lka, user); - m_add_string(p_lka, pass); - m_close(p_lka); - tree_xset(&wait_parent_auth, s->id, s); - return; - - default: - fatal("smtp_rfc4954_auth_plain: unknown state"); - } - -abort: - smtp_reply(s, "501 %s %s: Syntax error", - esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), - esc_description(ESC_SYNTAX_ERROR)); - smtp_enter_state(s, STATE_HELO); -} - -static void -smtp_rfc4954_auth_login(struct smtp_session *s, char *arg) -{ - char buf[LINE_MAX]; - - switch (s->state) { - case STATE_HELO: - smtp_enter_state(s, STATE_AUTH_USERNAME); - if (arg != NULL && *arg != '\0') { - smtp_rfc4954_auth_login(s, arg); - return; - } - smtp_reply(s, "334 VXNlcm5hbWU6"); - return; - - case STATE_AUTH_USERNAME: - memset(s->username, 0, sizeof(s->username)); - if (base64_decode(arg, (unsigned char *)s->username, - sizeof(s->username) - 1) == -1) - goto abort; - - smtp_enter_state(s, STATE_AUTH_PASSWORD); - smtp_reply(s, "334 UGFzc3dvcmQ6"); - return; - - case STATE_AUTH_PASSWORD: - memset(buf, 0, sizeof(buf)); - if (base64_decode(arg, (unsigned char *)buf, - sizeof(buf)-1) == -1) - goto abort; - - m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_string(p_lka, s->listener->authtable); - m_add_string(p_lka, s->username); - m_add_string(p_lka, buf); - m_close(p_lka); - tree_xset(&wait_parent_auth, s->id, s); - return; - - default: - fatal("smtp_rfc4954_auth_login: unknown state"); - } - -abort: - smtp_reply(s, "501 %s %s: Syntax error", - esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), - esc_description(ESC_SYNTAX_ERROR)); - smtp_enter_state(s, STATE_HELO); -} - -static void -smtp_lookup_servername(struct smtp_session *s) -{ - if (s->listener->hostnametable[0]) { - m_create(p_lka, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); - m_add_id(p_lka, s->id); - m_add_string(p_lka, s->listener->hostnametable); - m_add_sockaddr(p_lka, (struct sockaddr*)&s->listener->ss); - m_close(p_lka); - tree_xset(&wait_lka_helo, s->id, s); - return; - } - - smtp_connected(s); -} - -static void -smtp_connected(struct smtp_session *s) -{ - smtp_enter_state(s, STATE_CONNECTED); - - log_info("%016"PRIx64" smtp connected address=%s host=%s", - s->id, ss_to_text(&s->ss), s->rdns); - - smtp_filter_begin(s); - - smtp_report_link_connect(s, s->rdns, s->fcrdns, &s->ss, - &s->listener->ss); - - smtp_filter_phase(FILTER_CONNECT, s, ss_to_text(&s->ss)); -} - -static void -smtp_proceed_connected(struct smtp_session *s) -{ - if (s->listener->flags & F_SMTPS) - smtp_cert_init(s); - else - smtp_send_banner(s); -} - -static void -smtp_send_banner(struct smtp_session *s) -{ - smtp_reply(s, "220 %s ESMTP %s", s->smtpname, SMTPD_NAME); - s->banner_sent = 1; - smtp_report_link_greeting(s, s->smtpname); -} - -void -smtp_enter_state(struct smtp_session *s, int newstate) -{ - log_trace(TRACE_SMTP, "smtp: %p: %s -> %s", s, - smtp_strstate(s->state), - smtp_strstate(newstate)); - - s->state = newstate; -} - -static void -smtp_reply(struct smtp_session *s, char *fmt, ...) -{ - va_list ap; - int n; - char buf[LINE_MAX*2], tmp[LINE_MAX*2]; - - va_start(ap, fmt); - n = vsnprintf(buf, sizeof buf, fmt, ap); - va_end(ap); - if (n < 0) - fatalx("smtp_reply: response format error"); - if (n < 4) - fatalx("smtp_reply: response too short"); - if (n >= (int)sizeof buf) { - /* only first three bytes are used by SMTP logic, - * so if _our_ reply does not fit entirely in the - * buffer, it's ok to truncate. - */ - } - - log_trace(TRACE_SMTP, "smtp: %p: >>> %s", s, buf); - smtp_report_protocol_server(s, buf); - - switch (buf[0]) { - case '2': - if (s->tx) { - if (s->last_cmd == CMD_MAIL_FROM) { - smtp_report_tx_begin(s, s->tx->msgid); - smtp_report_tx_mail(s, s->tx->msgid, s->cmd + 10, 1); - } - else if (s->last_cmd == CMD_RCPT_TO) - smtp_report_tx_rcpt(s, s->tx->msgid, s->cmd + 8, 1); - } - break; - case '3': - if (s->tx) { - if (s->last_cmd == CMD_DATA) - smtp_report_tx_data(s, s->tx->msgid, 1); - } - break; - case '5': - case '4': - /* do not report smtp_tx_mail/smtp_tx_rcpt errors - * if they happened outside of a transaction. - */ - if (s->tx) { - if (s->last_cmd == CMD_MAIL_FROM) - smtp_report_tx_mail(s, s->tx->msgid, - s->cmd + 10, buf[0] == '4' ? -1 : 0); - else if (s->last_cmd == CMD_RCPT_TO) - smtp_report_tx_rcpt(s, - s->tx->msgid, s->cmd + 8, buf[0] == '4' ? -1 : 0); - else if (s->last_cmd == CMD_DATA && s->tx->rcptcount) - smtp_report_tx_data(s, s->tx->msgid, - buf[0] == '4' ? -1 : 0); - } - - if (s->flags & SF_BADINPUT) { - log_info("%016"PRIx64" smtp " - "bad-input result=\"%.*s\"", - s->id, n, buf); - } - else if (s->state == STATE_AUTH_INIT) { - log_info("%016"PRIx64" smtp " - "failed-command " - "command=\"AUTH PLAIN (...)\" result=\"%.*s\"", - s->id, n, buf); - } - else if (s->state == STATE_AUTH_USERNAME) { - log_info("%016"PRIx64" smtp " - "failed-command " - "command=\"AUTH LOGIN (username)\" result=\"%.*s\"", - s->id, n, buf); - } - else if (s->state == STATE_AUTH_PASSWORD) { - log_info("%016"PRIx64" smtp " - "failed-command " - "command=\"AUTH LOGIN (password)\" result=\"%.*s\"", - s->id, n, buf); - } - else { - strnvis(tmp, s->cmd, sizeof tmp, VIS_SAFE | VIS_CSTYLE); - log_info("%016"PRIx64" smtp " - "failed-command command=\"%s\" " - "result=\"%.*s\"", - s->id, tmp, n, buf); - } - break; - } - - io_xprintf(s->io, "%s\r\n", buf); -} - -static void -smtp_free(struct smtp_session *s, const char * reason) -{ - if (s->tx) { - if (s->tx->msgid) - smtp_tx_rollback(s->tx); - smtp_tx_free(s->tx); - } - - smtp_report_link_disconnect(s); - smtp_filter_end(s); - - if (s->flags & SF_SECURE && s->listener->flags & F_SMTPS) - stat_decrement("smtp.smtps", 1); - if (s->flags & SF_SECURE && s->listener->flags & F_STARTTLS) - stat_decrement("smtp.tls", 1); - - io_free(s->io); - free(s); - - smtp_collect(); -} - -static int -smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, - const char *domain) -{ - char *p, *e; - - if (line == NULL) - return (0); - - if (*line != '<') - return (0); - - e = strchr(line, '>'); - if (e == NULL) - return (0); - *e++ = '\0'; - while (*e == ' ') - e++; - *args = e; - - if (!text_to_mailaddr(maddr, line + 1)) - return (0); - - p = strchr(maddr->user, ':'); - if (p != NULL) { - p++; - memmove(maddr->user, p, strlen(p) + 1); - } - - /* accept empty return-path in MAIL FROM, required for bounces */ - if (mailfrom && maddr->user[0] == '\0' && maddr->domain[0] == '\0') - return (1); - - /* no or invalid user-part, reject */ - if (maddr->user[0] == '\0' || !valid_localpart(maddr->user)) - return (0); - - /* no domain part, local user */ - if (maddr->domain[0] == '\0') { - (void)strlcpy(maddr->domain, domain, - sizeof(maddr->domain)); - } - - if (!valid_domainpart(maddr->domain)) - return (0); - - return (1); -} - -static void -smtp_cert_init(struct smtp_session *s) -{ - const char *name; - int fallback; - - if (s->listener->pki_name[0]) { - name = s->listener->pki_name; - fallback = 0; - } - else { - name = s->smtpname; - fallback = 1; - } - - if (cert_init(name, fallback, smtp_cert_init_cb, s)) - tree_xset(&wait_ssl_init, s->id, s); -} - -static void -smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert, - size_t cert_len) -{ - struct smtp_session *s = arg; - void *ssl, *ssl_ctx; - - tree_pop(&wait_ssl_init, s->id); - - if (status == CA_FAIL) { - log_info("%016"PRIx64" smtp disconnected " - "reason=ca-failure", - s->id); - smtp_free(s, "CA failure"); - return; - } - - ssl_ctx = dict_get(env->sc_ssl_dict, name); - ssl = ssl_smtp_init(ssl_ctx, s->listener->flags & F_TLS_VERIFY); - io_set_read(s->io); - io_start_tls(s->io, ssl); -} - -static void -smtp_cert_verify(struct smtp_session *s) -{ - const char *name; - int fallback; - - if (s->listener->ca_name[0]) { - name = s->listener->ca_name; - fallback = 0; - } - else { - name = s->smtpname; - fallback = 1; - } - - if (cert_verify(io_tls(s->io), name, fallback, smtp_cert_verify_cb, s)) { - tree_xset(&wait_ssl_verify, s->id, s); - io_pause(s->io, IO_IN); - } -} - -static void -smtp_cert_verify_cb(void *arg, int status) -{ - struct smtp_session *s = arg; - const char *reason = NULL; - int resume; - - resume = tree_pop(&wait_ssl_verify, s->id) != NULL; - - switch (status) { - case CERT_OK: - reason = "cert-ok"; - s->flags |= SF_VERIFIED; - break; - case CERT_NOCA: - reason = "no-ca"; - break; - case CERT_NOCERT: - reason = "no-client-cert"; - break; - case CERT_INVALID: - reason = "cert-invalid"; - break; - default: - reason = "cert-check-failed"; - break; - } - - log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason); - - if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) { - log_info("%016"PRIx64" smtp disconnected " - " reason=%s", s->id, - reason); - smtp_free(s, "SSL certificate check failed"); - return; - } - - smtp_tls_verified(s); - if (resume) - io_resume(s->io, IO_IN); -} - -static void -smtp_auth_failure_resume(int fd, short event, void *p) -{ - struct smtp_session *s = p; - - smtp_reply(s, "535 Authentication failed"); - smtp_enter_state(s, STATE_HELO); -} - -static void -smtp_auth_failure_pause(struct smtp_session *s) -{ - struct timeval tv; - - tv.tv_sec = 0; - tv.tv_usec = arc4random_uniform(1000000); - log_trace(TRACE_SMTP, "smtp: timing-attack protection triggered, " - "will defer answer for %lu microseconds", tv.tv_usec); - evtimer_set(&s->pause, smtp_auth_failure_resume, s); - evtimer_add(&s->pause, &tv); -} - -static int -smtp_tx(struct smtp_session *s) -{ - struct smtp_tx *tx; - - tx = calloc(1, sizeof(*tx)); - if (tx == NULL) - return 0; - - TAILQ_INIT(&tx->rcpts); - - s->tx = tx; - tx->session = s; - - /* setup the envelope */ - tx->evp.ss = s->ss; - (void)strlcpy(tx->evp.tag, s->listener->tag, sizeof(tx->evp.tag)); - (void)strlcpy(tx->evp.smtpname, s->smtpname, sizeof(tx->evp.smtpname)); - (void)strlcpy(tx->evp.hostname, s->rdns, sizeof tx->evp.hostname); - (void)strlcpy(tx->evp.helo, s->helo, sizeof(tx->evp.helo)); - (void)strlcpy(tx->evp.username, s->username, sizeof(tx->evp.username)); - - if (s->flags & SF_BOUNCE) - tx->evp.flags |= EF_BOUNCE; - if (s->flags & SF_AUTHENTICATED) - tx->evp.flags |= EF_AUTHENTICATED; - - if ((tx->parser = rfc5322_parser_new()) == NULL) { - free(tx); - return 0; - } - - return 1; -} - -static void -smtp_tx_free(struct smtp_tx *tx) -{ - struct smtp_rcpt *rcpt; - - rfc5322_free(tx->parser); - - while ((rcpt = TAILQ_FIRST(&tx->rcpts))) { - TAILQ_REMOVE(&tx->rcpts, rcpt, entry); - free(rcpt); - } - - if (tx->ofile) - fclose(tx->ofile); - - tx->session->tx = NULL; - - free(tx); -} - -static void -smtp_tx_mail_from(struct smtp_tx *tx, const char *line) -{ - char *opt; - char *copy; - char tmp[SMTP_LINE_MAX]; - - (void)strlcpy(tmp, line, sizeof tmp); - copy = tmp; - - if (smtp_mailaddr(&tx->evp.sender, copy, 1, ©, - tx->session->smtpname) == 0) { - smtp_reply(tx->session, "553 %s Sender address syntax error", - esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); - smtp_tx_free(tx); - return; - } - - while ((opt = strsep(©, " "))) { - if (*opt == '\0') - continue; - - if (strncasecmp(opt, "AUTH=", 5) == 0) - log_debug("debug: smtp: AUTH in MAIL FROM command"); - else if (strncasecmp(opt, "SIZE=", 5) == 0) - log_debug("debug: smtp: SIZE in MAIL FROM command"); - else if (strcasecmp(opt, "BODY=7BIT") == 0) - /* XXX only for this transaction */ - tx->session->flags &= ~SF_8BITMIME; - else if (strcasecmp(opt, "BODY=8BITMIME") == 0) - ; - else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "RET=", 4) == 0) { - opt += 4; - if (strcasecmp(opt, "HDRS") == 0) - tx->evp.dsn_ret = DSN_RETHDRS; - else if (strcasecmp(opt, "FULL") == 0) - tx->evp.dsn_ret = DSN_RETFULL; - } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ENVID=", 6) == 0) { - opt += 6; - if (strlcpy(tx->evp.dsn_envid, opt, sizeof(tx->evp.dsn_envid)) - >= sizeof(tx->evp.dsn_envid)) { - smtp_reply(tx->session, - "503 %s %s: option too large, truncated: %s", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); - smtp_tx_free(tx); - return; - } - } else { - smtp_reply(tx->session, "503 %s %s: Unsupported option %s", - esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), - esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); - smtp_tx_free(tx); - return; - } - } - - /* only check sendertable if defined and user has authenticated */ - if (tx->session->flags & SF_AUTHENTICATED && - tx->session->listener->sendertable[0]) { - m_create(p_lka, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); - m_add_id(p_lka, tx->session->id); - m_add_string(p_lka, tx->session->listener->sendertable); - m_add_string(p_lka, tx->session->username); - m_add_mailaddr(p_lka, &tx->evp.sender); - m_close(p_lka); - tree_xset(&wait_lka_mail, tx->session->id, tx->session); - } - else - smtp_tx_create_message(tx); -} - -static void -smtp_tx_create_message(struct smtp_tx *tx) -{ - m_create(p_queue, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); - m_add_id(p_queue, tx->session->id); - m_close(p_queue); - tree_xset(&wait_queue_msg, tx->session->id, tx->session); -} - -static void -smtp_tx_rcpt_to(struct smtp_tx *tx, const char *line) -{ - char *opt, *p; - char *copy; - char tmp[SMTP_LINE_MAX]; - - (void)strlcpy(tmp, line, sizeof tmp); - copy = tmp; - - if (tx->rcptcount >= env->sc_session_max_rcpt) { - smtp_reply(tx->session, "451 %s %s: Too many recipients", - esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), - esc_description(ESC_TOO_MANY_RECIPIENTS)); - return; - } - - if (smtp_mailaddr(&tx->evp.rcpt, copy, 0, ©, - tx->session->smtpname) == 0) { - smtp_reply(tx->session, - "501 %s Recipient address syntax error", - esc_code(ESC_STATUS_PERMFAIL, - ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); - return; - } - - while ((opt = strsep(©, " "))) { - if (*opt == '\0') - continue; - - if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "NOTIFY=", 7) == 0) { - opt += 7; - while ((p = strsep(&opt, ","))) { - if (strcasecmp(p, "SUCCESS") == 0) - tx->evp.dsn_notify |= DSN_SUCCESS; - else if (strcasecmp(p, "FAILURE") == 0) - tx->evp.dsn_notify |= DSN_FAILURE; - else if (strcasecmp(p, "DELAY") == 0) - tx->evp.dsn_notify |= DSN_DELAY; - else if (strcasecmp(p, "NEVER") == 0) - tx->evp.dsn_notify |= DSN_NEVER; - } - - if (tx->evp.dsn_notify & DSN_NEVER && - tx->evp.dsn_notify & (DSN_SUCCESS | DSN_FAILURE | - DSN_DELAY)) { - smtp_reply(tx->session, - "553 NOTIFY option NEVER cannot be" - " combined with other options"); - return; - } - } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ORCPT=", 6) == 0) { - opt += 6; - - if (strncasecmp(opt, "rfc822;", 7) == 0) - opt += 7; - - if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt) || - !valid_localpart(tx->evp.dsn_orcpt.user) || - !valid_domainpart(tx->evp.dsn_orcpt.domain)) { - smtp_reply(tx->session, - "553 ORCPT address syntax error"); - return; - } - } else { - smtp_reply(tx->session, "503 Unsupported option %s", opt); - return; - } - } - - m_create(p_lka, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); - m_add_id(p_lka, tx->session->id); - m_add_envelope(p_lka, &tx->evp); - m_close(p_lka); - tree_xset(&wait_lka_rcpt, tx->session->id, tx->session); -} - -static void -smtp_tx_open_message(struct smtp_tx *tx) -{ - m_create(p_queue, IMSG_SMTP_MESSAGE_OPEN, 0, 0, -1); - m_add_id(p_queue, tx->session->id); - m_add_msgid(p_queue, tx->msgid); - m_close(p_queue); - tree_xset(&wait_queue_fd, tx->session->id, tx->session); -} - -static void -smtp_tx_commit(struct smtp_tx *tx) -{ - m_create(p_queue, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); - m_add_id(p_queue, tx->session->id); - m_add_msgid(p_queue, tx->msgid); - m_close(p_queue); - tree_xset(&wait_queue_commit, tx->session->id, tx->session); - smtp_filter_data_end(tx->session); -} - -static void -smtp_tx_rollback(struct smtp_tx *tx) -{ - m_create(p_queue, IMSG_SMTP_MESSAGE_ROLLBACK, 0, 0, -1); - m_add_msgid(p_queue, tx->msgid); - m_close(p_queue); - smtp_report_tx_rollback(tx->session, tx->msgid); - smtp_report_tx_reset(tx->session, tx->msgid); - smtp_filter_data_end(tx->session); -} - -static int -smtp_tx_dataline(struct smtp_tx *tx, const char *line) -{ - struct rfc5322_result res; - int r; - - log_trace(TRACE_SMTP, "<<< [MSG] %s", line); - - if (!strcmp(line, ".")) { - smtp_report_protocol_client(tx->session, "."); - log_trace(TRACE_SMTP, "<<< [EOM]"); - if (tx->error) - return 1; - line = NULL; - } - else { - /* ignore data line if an error is set */ - if (tx->error) - return 0; - - /* escape lines starting with a '.' */ - if (line[0] == '.') - line += 1; - } - - if (rfc5322_push(tx->parser, line) == -1) { - log_warnx("failed to push dataline"); - tx->error = TX_ERROR_INTERNAL; - return 0; - } - - for(;;) { - r = rfc5322_next(tx->parser, &res); - switch (r) { - case -1: - if (errno == ENOMEM) - tx->error = TX_ERROR_INTERNAL; - else - tx->error = TX_ERROR_MALFORMED; - return 0; - - case RFC5322_NONE: - /* Need more data */ - return 0; - - case RFC5322_HEADER_START: - /* ignore bcc */ - if (!strcasecmp("Bcc", res.hdr)) - continue; - - if (!strcasecmp("To", res.hdr) || - !strcasecmp("Cc", res.hdr) || - !strcasecmp("From", res.hdr)) { - rfc5322_unfold_header(tx->parser); - continue; - } - - if (!strcasecmp("Received", res.hdr)) { - if (++tx->rcvcount >= MAX_HOPS_COUNT) { - log_warnx("warn: loop detected"); - tx->error = TX_ERROR_LOOP; - return 0; - } - } - else if (!tx->has_date && !strcasecmp("Date", res.hdr)) - tx->has_date = 1; - else if (!tx->has_message_id && - !strcasecmp("Message-Id", res.hdr)) - tx->has_message_id = 1; - - smtp_message_printf(tx, "%s:%s\n", res.hdr, res.value); - break; - - case RFC5322_HEADER_CONT: - - if (!strcasecmp("Bcc", res.hdr) || - !strcasecmp("To", res.hdr) || - !strcasecmp("Cc", res.hdr) || - !strcasecmp("From", res.hdr)) - continue; - - smtp_message_printf(tx, "%s\n", res.value); - break; - - case RFC5322_HEADER_END: - if (!strcasecmp("To", res.hdr) || - !strcasecmp("Cc", res.hdr) || - !strcasecmp("From", res.hdr)) - header_domain_append_callback(tx, res.hdr, - res.value); - break; - - case RFC5322_END_OF_HEADERS: - if (tx->session->listener->local || - tx->session->listener->port == 587) { - - if (!tx->has_date) { - log_debug("debug: %p: adding Date", tx); - smtp_message_printf(tx, "Date: %s\n", - time_to_text(tx->time)); - } - - if (!tx->has_message_id) { - log_debug("debug: %p: adding Message-ID", tx); - smtp_message_printf(tx, - "Message-ID: <%016"PRIx64"@%s>\n", - generate_uid(), - tx->session->listener->hostname); - } - } - break; - - case RFC5322_BODY_START: - case RFC5322_BODY: - smtp_message_printf(tx, "%s\n", res.value); - break; - - case RFC5322_END_OF_MESSAGE: - return 1; - - default: - fatalx("%s", __func__); - } - } -} - -static int -smtp_tx_filtered_dataline(struct smtp_tx *tx, const char *line) -{ - if (!strcmp(line, ".")) - line = NULL; - else { - /* ignore data line if an error is set */ - if (tx->error) - return 0; - } - io_printf(tx->filter, "%s\n", line ? line : "."); - return line ? 0 : 1; -} - -static void -smtp_tx_eom(struct smtp_tx *tx) -{ - smtp_filter_phase(FILTER_COMMIT, tx->session, NULL); -} - -static int -smtp_message_fd(struct smtp_tx *tx, int fd) -{ - struct smtp_session *s; - - s = tx->session; - - log_debug("smtp: %p: message fd %d", s, fd); - - if ((tx->ofile = fdopen(fd, "w")) == NULL) { - close(fd); - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - smtp_enter_state(s, STATE_QUIT); - return 0; - } - return 1; -} - -static void -filter_session_io(struct io *io, int evt, void *arg) -{ - struct smtp_tx*tx = arg; - char*line = NULL; - ssize_t len; - - log_trace(TRACE_IO, "filter session io (smtp): %p: %s %s", tx, io_strevent(evt), - io_strio(io)); - - switch (evt) { - case IO_DATAIN: - nextline: - line = io_getline(tx->filter, &len); - /* No complete line received */ - if (line == NULL) - return; - - if (smtp_tx_dataline(tx, line)) { - smtp_tx_eom(tx); - return; - } - - goto nextline; - } -} - -static void -smtp_filter_fd(struct smtp_tx *tx, int fd) -{ - struct smtp_session *s; - - s = tx->session; - - log_debug("smtp: %p: filter fd %d", s, fd); - - tx->filter = io_new(); - io_set_fd(tx->filter, fd); - io_set_callback(tx->filter, filter_session_io, tx); -} - -static void -smtp_message_begin(struct smtp_tx *tx) -{ - struct smtp_session *s; - X509 *x; - int (*m_printf)(struct smtp_tx *, const char *, ...); - - m_printf = smtp_message_printf; - if (tx->filter) - m_printf = smtp_filter_printf; - - s = tx->session; - - log_debug("smtp: %p: message begin", s); - - smtp_reply(s, "354 Enter mail, end with \".\"" - " on a line by itself"); - - if (s->junk || (s->tx && s->tx->junk)) - m_printf(tx, "X-Spam: Yes\n"); - - m_printf(tx, "Received: "); - if (!(s->listener->flags & F_MASK_SOURCE)) { - m_printf(tx, "from %s (%s %s%s%s)", - s->helo, - s->rdns, - s->ss.ss_family == AF_INET6 ? "" : "[", - ss_to_text(&s->ss), - s->ss.ss_family == AF_INET6 ? "" : "]"); - } - m_printf(tx, "\n\tby %s (%s) with %sSMTP%s%s id %08x", - s->smtpname, - SMTPD_NAME, - s->flags & SF_EHLO ? "E" : "", - s->flags & SF_SECURE ? "S" : "", - s->flags & SF_AUTHENTICATED ? "A" : "", - tx->msgid); - - if (s->flags & SF_SECURE) { - x = SSL_get_peer_certificate(io_tls(s->io)); - m_printf(tx, " (%s:%s:%d:%s)", - SSL_get_version(io_tls(s->io)), - SSL_get_cipher_name(io_tls(s->io)), - SSL_get_cipher_bits(io_tls(s->io), NULL), - (s->flags & SF_VERIFIED) ? "YES" : (x ? "FAIL" : "NO")); - X509_free(x); - - if (s->listener->flags & F_RECEIVEDAUTH) { - m_printf(tx, " auth=%s", - s->username[0] ? "yes" : "no"); - if (s->username[0]) - m_printf(tx, " user=%s", s->username); - } - } - - if (tx->rcptcount == 1) { - m_printf(tx, "\n\tfor <%s@%s>", - tx->evp.rcpt.user, - tx->evp.rcpt.domain); - } - - m_printf(tx, ";\n\t%s\n", time_to_text(time(&tx->time))); - - smtp_enter_state(s, STATE_BODY); -} - -static void -smtp_message_end(struct smtp_tx *tx) -{ - struct smtp_session *s; - - s = tx->session; - - log_debug("debug: %p: end of message, error=%d", s, tx->error); - - fclose(tx->ofile); - tx->ofile = NULL; - - switch(tx->error) { - case TX_OK: - smtp_tx_commit(tx); - return; - - case TX_ERROR_SIZE: - smtp_reply(s, "554 %s %s: Transaction failed, message too big", - esc_code(ESC_STATUS_PERMFAIL, ESC_MESSAGE_TOO_BIG_FOR_SYSTEM), - esc_description(ESC_MESSAGE_TOO_BIG_FOR_SYSTEM)); - break; - - case TX_ERROR_LOOP: - smtp_reply(s, "500 %s %s: Loop detected", - esc_code(ESC_STATUS_PERMFAIL, ESC_ROUTING_LOOP_DETECTED), - esc_description(ESC_ROUTING_LOOP_DETECTED)); - break; - - case TX_ERROR_MALFORMED: - smtp_reply(s, "550 %s %s: Message is not RFC 2822 compliant", - esc_code(ESC_STATUS_PERMFAIL, ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED), - esc_description(ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED)); - break; - - case TX_ERROR_IO: - case TX_ERROR_RESOURCES: - smtp_reply(s, "421 %s Temporary Error", - esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); - break; - - default: - /* fatal? */ - smtp_reply(s, "421 Internal server error"); - } - - smtp_tx_rollback(tx); - smtp_tx_free(tx); - smtp_enter_state(s, STATE_HELO); -} - -static int -smtp_filter_printf(struct smtp_tx *tx, const char *fmt, ...) -{ - va_list ap; - int len; - - if (tx->error) - return -1; - - va_start(ap, fmt); - len = io_vprintf(tx->filter, fmt, ap); - va_end(ap); - - if (len < 0) { - log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); - tx->error = TX_ERROR_IO; - } - else - tx->odatalen += len; - - return len; -} - -static int -smtp_message_printf(struct smtp_tx *tx, const char *fmt, ...) -{ - va_list ap; - int len; - - if (tx->error) - return -1; - - va_start(ap, fmt); - len = vfprintf(tx->ofile, fmt, ap); - va_end(ap); - - if (len == -1) { - log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); - tx->error = TX_ERROR_IO; - } - else - tx->odatalen += len; - - return len; -} - -#define CASE(x) case x : return #x - -const char * -smtp_strstate(int state) -{ - static char buf[32]; - - switch (state) { - CASE(STATE_NEW); - CASE(STATE_CONNECTED); - CASE(STATE_TLS); - CASE(STATE_HELO); - CASE(STATE_AUTH_INIT); - CASE(STATE_AUTH_USERNAME); - CASE(STATE_AUTH_PASSWORD); - CASE(STATE_AUTH_FINALIZE); - CASE(STATE_BODY); - CASE(STATE_QUIT); - default: - (void)snprintf(buf, sizeof(buf), "STATE_??? (%d)", state); - return (buf); - } -} - - -static void -smtp_report_link_connect(struct smtp_session *s, const char *rdns, int fcrdns, - const struct sockaddr_storage *ss_src, - const struct sockaddr_storage *ss_dest) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_connect("smtp-in", s->id, rdns, fcrdns, ss_src, ss_dest); -} - -static void -smtp_report_link_greeting(struct smtp_session *s, - const char *domain) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_greeting("smtp-in", s->id, domain); -} - -static void -smtp_report_link_identify(struct smtp_session *s, const char *method, const char *identity) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_identify("smtp-in", s->id, method, identity); -} - -static void -smtp_report_link_tls(struct smtp_session *s, const char *ssl) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_tls("smtp-in", s->id, ssl); -} - -static void -smtp_report_link_disconnect(struct smtp_session *s) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_disconnect("smtp-in", s->id); -} - -static void -smtp_report_link_auth(struct smtp_session *s, const char *user, const char *result) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_link_auth("smtp-in", s->id, user, result); -} - -static void -smtp_report_tx_reset(struct smtp_session *s, uint32_t msgid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_reset("smtp-in", s->id, msgid); -} - -static void -smtp_report_tx_begin(struct smtp_session *s, uint32_t msgid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_begin("smtp-in", s->id, msgid); -} - -static void -smtp_report_tx_mail(struct smtp_session *s, uint32_t msgid, const char *address, int ok) -{ - char mailaddr[SMTPD_MAXMAILADDRSIZE]; - char *p; - - if (! SESSION_FILTERED(s)) - return; - - if ((p = strchr(address, '<')) == NULL) - return; - (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); - if ((p = strchr(mailaddr, '>')) == NULL) - return; - *p = '\0'; - - report_smtp_tx_mail("smtp-in", s->id, msgid, mailaddr, ok); -} - -static void -smtp_report_tx_rcpt(struct smtp_session *s, uint32_t msgid, const char *address, int ok) -{ - char mailaddr[SMTPD_MAXMAILADDRSIZE]; - char *p; - - if (! SESSION_FILTERED(s)) - return; - - if ((p = strchr(address, '<')) == NULL) - return; - (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); - if ((p = strchr(mailaddr, '>')) == NULL) - return; - *p = '\0'; - - report_smtp_tx_rcpt("smtp-in", s->id, msgid, mailaddr, ok); -} - -static void -smtp_report_tx_envelope(struct smtp_session *s, uint32_t msgid, uint64_t evpid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_envelope("smtp-in", s->id, msgid, evpid); -} - -static void -smtp_report_tx_data(struct smtp_session *s, uint32_t msgid, int ok) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_data("smtp-in", s->id, msgid, ok); -} - -static void -smtp_report_tx_commit(struct smtp_session *s, uint32_t msgid, size_t msgsz) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_commit("smtp-in", s->id, msgid, msgsz); -} - -static void -smtp_report_tx_rollback(struct smtp_session *s, uint32_t msgid) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_tx_rollback("smtp-in", s->id, msgid); -} - -static void -smtp_report_protocol_client(struct smtp_session *s, const char *command) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_protocol_client("smtp-in", s->id, command); -} - -static void -smtp_report_protocol_server(struct smtp_session *s, const char *response) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_protocol_server("smtp-in", s->id, response); -} - -static void -smtp_report_filter_response(struct smtp_session *s, int phase, int response, const char *param) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_filter_response("smtp-in", s->id, phase, response, param); -} - -static void -smtp_report_timeout(struct smtp_session *s) -{ - if (! SESSION_FILTERED(s)) - return; - - report_smtp_timeout("smtp-in", s->id); -} diff --git a/smtpd/smtpc.c b/smtpd/smtpc.c deleted file mode 100644 index 59479703..00000000 --- a/smtpd/smtpc.c +++ /dev/null @@ -1,465 +0,0 @@ -/* $OpenBSD: smtpc.c,v 1.10 2019/09/21 09:04:08 semarie Exp $ */ - -/* - * Copyright (c) 2018 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "smtp.h" -#include "ssl.h" -#include "log.h" - -static void parse_server(char *); -static void parse_message(FILE *); -static void resume(void); - -static int verbose = 1; -static int done = 0; -static int noaction = 0; -static struct addrinfo *res0, *ai; -static struct smtp_params params; -static struct smtp_mail mail; -static const char *servname = NULL; - -static SSL_CTX *ssl_ctx; - -static void -usage(void) -{ - extern char *__progname; - - fprintf(stderr, - "usage: %s [-Chnv] [-F from] [-H helo] [-s server] [-S name] rcpt ...\n", - __progname); - exit(1); -} - -int -main(int argc, char **argv) -{ - char hostname[256]; - int ch, i; - char *server = "localhost"; - struct passwd *pw; - - log_init(1, 0); - - if (gethostname(hostname, sizeof(hostname)) == -1) - fatal("gethostname"); - - if ((pw = getpwuid(getuid())) == NULL) - fatal("getpwuid"); - - memset(¶ms, 0, sizeof(params)); - - params.linemax = 16392; - params.ibufmax = 65536; - params.obufmax = 65536; - params.timeout = 100000; - params.helo = hostname; - - params.tls_verify = 1; - - memset(&mail, 0, sizeof(mail)); - mail.from = pw->pw_name; - - while ((ch = getopt(argc, argv, "CF:H:S:hns:v")) != -1) { - switch (ch) { - case 'C': - params.tls_verify = 0; - break; - case 'F': - mail.from = optarg; - break; - case 'H': - params.helo = optarg; - break; - case 'S': - servname = optarg; - break; - case 'h': - usage(); - break; - case 'n': - noaction = 1; - break; - case 's': - server = optarg; - break; - case 'v': - verbose++; - break; - default: - usage(); - } - } - - log_setverbose(verbose); - - argc -= optind; - argv += optind; - - if (argc) { - mail.rcpt = calloc(argc, sizeof(*mail.rcpt)); - if (mail.rcpt == NULL) - fatal("calloc"); - for (i = 0; i < argc; i++) - mail.rcpt[i].to = argv[i]; - mail.rcptcount = argc; - } - - ssl_init(); - event_init(); - - ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL); - if (!SSL_CTX_load_verify_locations(ssl_ctx, - X509_get_default_cert_file(), NULL)) - fatal("SSL_CTX_load_verify_locations"); - if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method())) - fatal("SSL_CTX_set_ssl_version"); - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL); - -#if HAVE_PLEDGE - if (pledge("stdio inet dns tmppath", NULL) == -1) - fatal("pledge"); -#endif - - if (!noaction) - parse_message(stdin); - -#if HAVE_PLEDGE - if (pledge("stdio inet dns", NULL) == -1) - fatal("pledge"); -#endif - - parse_server(server); - -#if HAVE_PLEDGE - if (pledge("stdio inet", NULL) == -1) - fatal("pledge"); -#endif - - resume(); - - log_debug("done..."); - - return 0; -} - -static void -parse_server(char *server) -{ - struct addrinfo hints; - char *scheme, *creds, *host, *port, *p, *c; - int error; - - creds = NULL; - host = NULL; - port = NULL; - scheme = server; - - p = strstr(server, "://"); - if (p) { - *p = '\0'; - p += 3; - /* check for credentials */ - c = strchr(p, '@'); - if (c) { - creds = p; - *c = '\0'; - host = c + 1; - } else - host = p; - } else { - /* Assume a simple server name */ - scheme = "smtp"; - host = server; - } - - if (host[0] == '[') { - /* IPV6 address? */ - p = strchr(host, ']'); - if (p) { - if (p[1] == ':' || p[1] == '\0') { - *p++ = '\0'; /* remove ']' */ - host++; /* skip '[' */ - if (*p == ':') - port = p + 1; - } - } - } - else { - port = strchr(host, ':'); - if (port) - *port++ = '\0'; - } - - if (port && port[0] == '\0') - port = NULL; - - if (creds) { - p = strchr(creds, ':'); - if (p == NULL) - fatalx("invalid credentials"); - *p = '\0'; - - params.auth_user = creds; - params.auth_pass = p + 1; - } - params.tls_req = TLS_YES; - - if (!strcmp(scheme, "lmtp")) { - params.lmtp = 1; - } - else if (!strcmp(scheme, "lmtp+tls")) { - params.lmtp = 1; - params.tls_req = TLS_FORCE; - } - else if (!strcmp(scheme, "lmtp+notls")) { - params.lmtp = 1; - params.tls_req = TLS_NO; - } - else if (!strcmp(scheme, "smtps")) { - params.tls_req = TLS_SMTPS; - if (port == NULL) - port = "smtps"; - } - else if (!strcmp(scheme, "smtp")) { - } - else if (!strcmp(scheme, "smtp+tls")) { - params.tls_req = TLS_FORCE; - } - else if (!strcmp(scheme, "smtp+notls")) { - params.tls_req = TLS_NO; - } - else - fatalx("invalid url scheme %s", scheme); - - if (port == NULL) - port = "smtp"; - - if (servname == NULL) - servname = host; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - error = getaddrinfo(host, port, &hints, &res0); - if (error) - fatalx("%s: %s", host, gai_strerror(error)); - ai = res0; -} - -void -parse_message(FILE *ifp) -{ - char *line = NULL; - size_t linesz = 0; - ssize_t len; - - if ((mail.fp = tmpfile()) == NULL) - fatal("tmpfile"); - - for (;;) { - if ((len = getline(&line, &linesz, ifp)) == -1) { - if (feof(ifp)) - break; - fatal("getline"); - } - - if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') - line[--len - 1] = '\n'; - - if (fwrite(line, 1, len, mail.fp) != (size_t)len) - fatal("fwrite"); - - if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF) - fatal("fputc"); - } - - fclose(ifp); - rewind(mail.fp); -} - -void -resume(void) -{ - static int started = 0; - char host[256]; - char serv[16]; - - if (done) { - event_loopexit(NULL); - return; - } - - if (ai == NULL) - fatalx("no more host"); - - getnameinfo(ai->ai_addr, SA_LEN(ai->ai_addr), - host, sizeof(host), serv, sizeof(serv), - NI_NUMERICHOST | NI_NUMERICSERV); - log_debug("trying host %s port %s...", host, serv); - - params.dst = ai->ai_addr; - if (smtp_connect(¶ms, NULL) == NULL) - fatal("smtp_connect"); - - if (started == 0) { - started = 1; - event_loop(0); - } -} - -void -log_trace(int lvl, const char *emsg, ...) -{ - va_list ap; - - if (verbose > lvl) { - va_start(ap, emsg); - vlog(LOG_DEBUG, emsg, ap); - va_end(ap); - } -} - -void -smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx) -{ - SSL *ssl = ctx; - X509 *cert; - long res; - int match; - - if ((cert = SSL_get_peer_certificate(ssl))) { - (void)ssl_check_name(cert, servname, &match); - X509_free(cert); - res = SSL_get_verify_result(ssl); - if (res == X509_V_OK) { - if (match) { - log_debug("valid certificate"); - smtp_cert_verified(proto, CERT_OK); - } - else { - log_debug("certificate does not match hostname"); - smtp_cert_verified(proto, CERT_INVALID); - } - return; - } - log_debug("certificate validation error %ld", res); - } - else - log_debug("no certificate provided"); - - smtp_cert_verified(proto, CERT_INVALID); -} - -void -smtp_require_tls(void *tag, struct smtp_client *proto) -{ - SSL *ssl = NULL; - - if ((ssl = SSL_new(ssl_ctx)) == NULL) - fatal("SSL_new"); - smtp_set_tls(proto, ssl); -} - -void -smtp_ready(void *tag, struct smtp_client *proto) -{ - log_debug("connection ready..."); - - if (done || noaction) - smtp_quit(proto); - else - smtp_sendmail(proto, &mail); -} - -void -smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail) -{ - switch (failure) { - case FAIL_INTERNAL: - log_warnx("internal error: %s", detail); - break; - case FAIL_CONN: - log_warnx("connection error: %s", detail); - break; - case FAIL_PROTO: - log_warnx("protocol error: %s", detail); - break; - case FAIL_IMPL: - log_warnx("missing feature: %s", detail); - break; - case FAIL_RESP: - log_warnx("rejected by server: %s", detail); - break; - default: - fatalx("unknown failure %d: %s", failure, detail); - } -} - -void -smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status) -{ - log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status); -} - -void -smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail) -{ - int i; - - log_debug("mail done..."); - - if (noaction) - return; - - for (i = 0; i < mail->rcptcount; i++) - if (!mail->rcpt[i].done) - return; - - done = 1; -} - -void -smtp_closed(void *tag, struct smtp_client *proto) -{ - log_debug("connection closed..."); - - ai = ai->ai_next; - if (noaction && ai == NULL) - done = 1; - - resume(); -} diff --git a/smtpd/smtpctl.8 b/smtpd/smtpctl.8 deleted file mode 100644 index 1efcff63..00000000 --- a/smtpd/smtpctl.8 +++ /dev/null @@ -1,336 +0,0 @@ -.\" $OpenBSD: smtpctl.8,v 1.64 2018/09/18 06:21:45 miko Exp $ -.\" -.\" Copyright (c) 2006 Pierre-Yves Ritschard -.\" Copyright (c) 2012 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: September 18 2018 $ -.Dt SMTPCTL 8 -.Os -.Sh NAME -.Nm smtpctl , -.Nm mailq -.Nd control the Simple Mail Transfer Protocol daemon -.Sh SYNOPSIS -.Nm -.Ar command -.Op Ar argument ... -.Nm mailq -.Sh DESCRIPTION -The -.Nm -program controls -.Xr smtpd 8 . -Commands may be abbreviated to the minimum unambiguous prefix; for example, -.Cm sh ro -for -.Cm show routes . -.Pp -The -.Nm mailq -command is provided for compatibility with other MTAs -and is simply a shortcut for -.Cm show queue . -.Pp -The following commands are available: -.Bl -tag -width Ds -.It Cm discover Ar envelope-id | message-id -Schedule a single envelope, or all envelopes with the same message ID -that were manually moved to the queue. -.It Cm encrypt Op Ar string -Encrypt the password -.Ar string -to a representation suitable for user credentials and print it to the -standard output. -If -.Ar string -is not provided, cleartext passwords are read from standard input. -.Pp -It is advised to avoid providing the password as a parameter as it will be -visible from -.Xr top 1 -and -.Xr ps 1 -output. -.It Cm log brief -Disable verbose debug logging. -.It Cm log verbose -Enable verbose debug logging. -.It Cm monitor -Display updates of some -.Xr smtpd 8 -internal counters in one second intervals. -Each line reports the increment of all counters since the last update, -except for some counters which are always absolute values. -The first line reports the current value of each counter. -The fields are: -.Pp -.Bl -bullet -compact -.It -Current number of active SMTP clients (absolute value). -.It -New SMTP clients. -.It -Disconnected clients. -.It -Current number of envelopes in the queue (absolute value). -.It -Newly enqueued envelopes. -.It -Dequeued envelopes. -.It -Successful deliveries. -.It -Temporary failures. -.It -Permanent failures. -.It -Message loops. -.It -Expired envelopes. -.It -Envelopes removed by the administrator. -.It -Generated bounces. -.El -.It Cm pause envelope Ar envelope-id | message-id | Cm all -Temporarily suspend scheduling for the envelope with the given ID, -envelopes with the given message ID, -or all envelopes. -.It Cm pause mda -Temporarily stop deliveries to local users. -.It Cm pause mta -Temporarily stop relaying and deliveries to -remote users. -.It Cm pause smtp -Temporarily stop accepting incoming sessions. -.It Cm profile Ar subsystem -Enables real-time profiling of -.Ar subsystem . -Supported subsystems are: -.Pp -.Bl -bullet -compact -.It -queue, to profile cost of queue IO -.It -imsg, to profile cost of event handlers -.El -.It Cm remove Ar envelope-id | message-id | Cm all -Remove a single envelope, -envelopes with the given message ID, -or all envelopes. -.It Cm resume envelope Ar envelope-id | message-id | Cm all -Resume scheduling for the envelope with the given ID, -envelopes with the given message ID, -or all envelopes. -.It Cm resume mda -Resume deliveries to local users. -.It Cm resume mta -Resume relaying and deliveries to remote users. -.It Cm resume route Ar route-id -Resume routing on disabled route -.Ar route-id . -.It Cm resume smtp -Resume accepting incoming sessions. -.It Cm schedule Ar envelope-id | message-id | Cm all -Mark as ready for immediate delivery -a single envelope, -envelopes with the given message ID, -or all envelopes. -.It Cm show envelope Ar envelope-id -Display envelope content for the given ID. -.It Cm show hosts -Display the list of known remote MX hosts. -For each of them, it shows the IP address, the canonical hostname, -a reference count, the number of active connections to this host, -and the elapsed time since the last connection. -.It Cm show hoststats -Display status of last delivery for domains that have been active in the -last 4 hours. -It consists of the following fields, separated by a "|": -.Pp -.Bl -bullet -compact -.It -Domain. -.It -.Ux -timestamp of last delivery. -.It -Status of last delivery. -.El -.It Cm show message Ar envelope-id -Display message content for the given ID. -.It Cm show queue -Display information concerning envelopes that are currently in the queue. -Each line of output describes a single envelope. -It consists of the following fields, separated by a "|": -.Pp -.Bl -bullet -compact -.It -Envelope ID. -.It -Address family of the client which enqueued the mail. -.It -Type of delivery: one of "mta", "mda" or "bounce". -.It -Various flags on the envelope. -.It -Sender address (return path). -.It -The original recipient address. -.It -The destination address. -.It -Time of creation. -.It -Time of expiration. -.It -Time of last delivery or relaying attempt. -.It -Number of delivery or relaying attempts. -.It -Current runstate: either "pending" or "inflight" if -.Xr smtpd 8 -is running, or "offline" otherwise. -.It -Delay in seconds before the next attempt if pending, or time elapsed -if currently running. -This field is blank if -.Xr smtpd 8 -is not running. -.It -Error string for the last failed delivery or relay attempt. -.El -.It Cm show relays -Display the list of currently active relays and associated connectors. -For each relay, it shows a number of counters and information on its -internal state on a single line. -Then comes the list of connectors -(source addresses to connect from for this relay). -.It Cm show routes -Display status of routes currently known by -.Xr smtpd 8 . -Each line consists of a route number, a source address, a destination -address, a set of flags, the number of connections on this -route, the current penalty level which determines the amount of time -the route is disabled if an error occurs, and the delay before it -gets reactivated. -The following flags are defined: -.Pp -.Bl -tag -width xx -compact -.It D -The route is currently disabled. -.It N -The route is new. -No SMTP session has been established yet. -.It Q -The route has a timeout registered to lower its penalty level and possibly -reactivate or discard it. -.El -.It Cm show stats -Displays runtime statistics concerning -.Xr smtpd 8 . -.It Cm show status -Shows if MTA, MDA and SMTP systems are currently running or paused. -.It Cm spf walk -Recursively look up SPF records for the domains read from stdin. -For example: -.Bd -literal -offset indent -# smtpctl spf walk < domains.txt -.Ed -.It Cm trace Ar subsystem -Enables real-time tracing of -.Ar subsystem . -Supported subsystems are: -.Pp -.Bl -bullet -compact -.It -imsg -.It -io -.It -smtp (incoming sessions) -.It -filters -.It -mta (outgoing sessions) -.It -bounce -.It -scheduler -.It -expand (aliases/virtual/forward expansion) -.It -lookup (user/credentials lookups) -.It -stat -.It -rules (matched by incoming sessions) -.It -mproc -.It -all -.El -.It Cm unprofile Ar subsystem -Disables real-time profiling of -.Ar subsystem . -.It Cm untrace Ar subsystem -Disables real-time tracing of -.Ar subsystem . -.It Cm update table Ar name -Updates the contents of table -.Ar name , -for tables using the -.Dq file -backend. -.El -.Pp -When -.Nm smtpd -receives a message, it generates a -.Ar message-id -for the message, and one -.Ar envelope-id -per recipient. -The -.Ar message-id -is a 32-bit random identifier that is guaranteed to be -unique on the host system. -The -.Ar envelope-id -is a 64-bit unique identifier that encodes the -.Ar message-id -in the 32 upper bits and a random envelope identifier -in the 32 lower bits. -.Pp -A command which specifies a -.Ar message-id -applies to all recipients of a message; -a command which specifies an -.Ar envelope-id -applies to a specific recipient of a message. -.Sh FILES -.Bl -tag -width "/var/run/smtpd.sockXXX" -compact -.It Pa /var/run/smtpd.sock -.Ux Ns -domain -socket used for communication with -.Xr smtpd 8 . -.El -.Sh SEE ALSO -.Xr smtpd 8 -.Sh HISTORY -The -.Nm -program first appeared in -.Ox 4.6 . diff --git a/smtpd/smtpctl.c b/smtpd/smtpctl.c deleted file mode 100644 index 7dba4224..00000000 --- a/smtpd/smtpctl.c +++ /dev/null @@ -1,1469 +0,0 @@ -/* $OpenBSD: smtpctl.c,v 1.167 2020/02/24 16:16:07 millert Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2006 Gilles Chehade - * Copyright (c) 2006 Pierre-Yves Ritschard - * Copyright (c) 2005 Claudio Jeker - * Copyright (c) 2004, 2005 Esben Norby - * Copyright (c) 2003 Henning Brauer - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -/* #include */ -/* #include */ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) -#include -#else -#include "bsd-vis.h" -#endif -#include - -#include "smtpd.h" -#include "parser.h" -#include "log.h" - -#ifndef PATH_GZCAT -#define PATH_GZCAT "/usr/bin/gzcat" -#endif -#define PATH_CAT "/bin/cat" -#define PATH_QUEUE "/queue" -#ifndef PATH_ENCRYPT -#define PATH_ENCRYPT "/usr/bin/encrypt" -#endif - -#ifndef HAVE_DB_API -#define makemap(x, y, z) 1 -#endif - - -int srv_connect(void); -int srv_connected(void); - -void usage(void); -static void show_queue_envelope(struct envelope *, int); -static void getflag(uint *, int, char *, char *, size_t); -static void display(const char *); -static int str_to_trace(const char *); -static int str_to_profile(const char *); -static void show_offline_envelope(uint64_t); -static int is_gzip_fp(FILE *); -static int is_encrypted_fp(FILE *); -static int is_encrypted_buffer(const char *); -static int is_gzip_buffer(const char *); -static FILE *offline_file(void); -static void sendmail_compat(int, char **); - -extern int spfwalk(int, struct parameter *); - -extern char *__progname; -int sendmail; -struct smtpd *env; -struct imsgbuf *ibuf; -struct imsg imsg; -char *rdata; -size_t rlen; -time_t now; - -struct queue_backend queue_backend_null; -struct queue_backend queue_backend_proc; -struct queue_backend queue_backend_ram; - -__dead void -usage(void) -{ - if (sendmail) - fprintf(stderr, "usage: %s [-tv] [-f from] [-F name] to ...\n", - __progname); - else - fprintf(stderr, "usage: %s command [argument ...]\n", - __progname); - exit(1); -} - -void stat_increment(const char *k, size_t v) -{ -} - -void stat_decrement(const char *k, size_t v) -{ -} - -int -srv_connect(void) -{ - struct sockaddr_un s_un; - int ctl_sock, saved_errno; - - /* connect to smtpd control socket */ - if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) - err(1, "socket"); - - memset(&s_un, 0, sizeof(s_un)); - s_un.sun_family = AF_UNIX; - (void)strlcpy(s_un.sun_path, SMTPD_SOCKET, sizeof(s_un.sun_path)); - if (connect(ctl_sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { - saved_errno = errno; - close(ctl_sock); - errno = saved_errno; - return (0); - } - - ibuf = xcalloc(1, sizeof(struct imsgbuf)); - imsg_init(ibuf, ctl_sock); - - return (1); -} - -int -srv_connected(void) -{ - return ibuf != NULL ? 1 : 0; -} - -FILE * -offline_file(void) -{ - char path[PATH_MAX]; - int fd; - FILE *fp; - - if (!bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL, - PATH_OFFLINE, (long long int) time(NULL))) - err(EX_UNAVAILABLE, "snprintf"); - - if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { - if (fd != -1) - unlink(path); - err(EX_UNAVAILABLE, "cannot create temporary file %s", path); - } - - if (fchmod(fd, 0600) == -1) { - unlink(path); - err(EX_SOFTWARE, "fchmod"); - } - - return fp; -} - - -static void -srv_flush(void) -{ - if (imsg_flush(ibuf) == -1) - err(1, "write error"); -} - -static void -srv_send(int msg, const void *data, size_t len) -{ - if (ibuf == NULL && !srv_connect()) - errx(1, "smtpd doesn't seem to be running"); - imsg_compose(ibuf, msg, IMSG_VERSION, 0, -1, data, len); -} - -static void -srv_recv(int type) -{ - ssize_t n; - - srv_flush(); - - while (1) { - if ((n = imsg_get(ibuf, &imsg)) == -1) - errx(1, "imsg_get error"); - if (n) { - if (imsg.hdr.type == IMSG_CTL_FAIL && - imsg.hdr.peerid != 0 && - imsg.hdr.peerid != IMSG_VERSION) - errx(1, "incompatible smtpctl and smtpd"); - if (type != -1 && type != (int)imsg.hdr.type) - errx(1, "bad message type"); - rdata = imsg.data; - rlen = imsg.hdr.len - sizeof(imsg.hdr); - break; - } - - if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - errx(1, "imsg_read error"); - if (n == 0) - errx(1, "pipe closed"); - } -} - -static void -srv_read(void *dst, size_t sz) -{ - if (sz == 0) - return; - if (rlen < sz) - errx(1, "message too short"); - if (dst) - memmove(dst, rdata, sz); - rlen -= sz; - rdata += sz; -} - -static void -srv_get_int(int *i) -{ - srv_read(i, sizeof(*i)); -} - -static void -srv_get_time(time_t *t) -{ - srv_read(t, sizeof(*t)); -} - -static void -srv_get_evpid(uint64_t *evpid) -{ - srv_read(evpid, sizeof(*evpid)); -} - -static void -srv_get_string(const char **s) -{ - const char *end; - size_t len; - - if (rlen == 0) - errx(1, "message too short"); - - rlen -= 1; - if (*rdata++ == '\0') { - *s = NULL; - return; - } - - if (rlen == 0) - errx(1, "bogus string"); - - end = memchr(rdata, 0, rlen); - if (end == NULL) - errx(1, "unterminated string"); - - len = end + 1 - rdata; - - *s = rdata; - rlen -= len; - rdata += len; -} - -static void -srv_get_envelope(struct envelope *evp) -{ - uint64_t evpid; - const char *str; - - srv_get_evpid(&evpid); - srv_get_string(&str); - - envelope_load_buffer(evp, str, strlen(str)); - evp->id = evpid; -} - -static void -srv_end(void) -{ - if (rlen) - errx(1, "bogus data"); - imsg_free(&imsg); -} - -static int -srv_check_result(int verbose_) -{ - srv_recv(-1); - srv_end(); - - switch (imsg.hdr.type) { - case IMSG_CTL_OK: - if (verbose_) - printf("command succeeded\n"); - return (0); - case IMSG_CTL_FAIL: - if (verbose_) { - if (rlen) - printf("command failed: %s\n", rdata); - else - printf("command failed\n"); - } - return (1); - default: - errx(1, "wrong message in response: %u", imsg.hdr.type); - } - return (0); -} - -static int -srv_iter_messages(uint32_t *res) -{ - static uint32_t *msgids = NULL, from = 0; - static size_t n, curr; - static int done = 0; - - if (done) - return (0); - - if (msgids == NULL) { - srv_send(IMSG_CTL_LIST_MESSAGES, &from, sizeof(from)); - srv_recv(IMSG_CTL_LIST_MESSAGES); - if (rlen == 0) { - srv_end(); - done = 1; - return (0); - } - msgids = malloc(rlen); - n = rlen / sizeof(*msgids); - srv_read(msgids, rlen); - srv_end(); - - curr = 0; - from = msgids[n - 1] + 1; - if (from == 0) - done = 1; - } - - *res = msgids[curr++]; - if (curr == n) { - free(msgids); - msgids = NULL; - } - - return (1); -} - -static int -srv_iter_envelopes(uint32_t msgid, struct envelope *evp) -{ - static uint32_t currmsgid = 0; - static uint64_t from = 0; - static int done = 0, need_send = 1, found; - int flags; - time_t nexttry; - - if (currmsgid != msgid) { - if (currmsgid != 0 && !done) - errx(1, "must finish current iteration first"); - currmsgid = msgid; - from = msgid_to_evpid(msgid); - done = 0; - found = 0; - need_send = 1; - } - - if (done) - return (0); - - again: - if (need_send) { - found = 0; - srv_send(IMSG_CTL_LIST_ENVELOPES, &from, sizeof(from)); - } - need_send = 0; - - srv_recv(IMSG_CTL_LIST_ENVELOPES); - if (rlen == 0) { - srv_end(); - if (!found || evpid_to_msgid(from) != msgid) { - done = 1; - return (0); - } - need_send = 1; - goto again; - } - - srv_get_int(&flags); - srv_get_time(&nexttry); - srv_get_envelope(evp); - srv_end(); - - evp->flags |= flags; - evp->nexttry = nexttry; - - from = evp->id + 1; - found++; - return (1); -} - -static int -srv_iter_evpids(uint32_t msgid, uint64_t *evpid, int *offset) -{ - static uint64_t *evpids = NULL, *tmp; - static int n, tmpalloc, alloc = 0; - struct envelope evp; - - if (*offset == 0) { - n = 0; - while (srv_iter_envelopes(msgid, &evp)) { - if (n == alloc) { - tmpalloc = alloc ? (alloc * 2) : 128; - tmp = recallocarray(evpids, alloc, tmpalloc, - sizeof(*evpids)); - if (tmp == NULL) - err(1, "recallocarray"); - evpids = tmp; - alloc = tmpalloc; - } - evpids[n++] = evp.id; - } - } - - if (*offset >= n) - return (0); - *evpid = evpids[*offset]; - *offset += 1; - return (1); -} - -static void -srv_foreach_envelope(struct parameter *argv, int ctl, size_t *total, size_t *ok) -{ - uint32_t msgid; - uint64_t evpid; - int i; - - *total = 0; - *ok = 0; - - if (argv == NULL) { - while (srv_iter_messages(&msgid)) { - i = 0; - while (srv_iter_evpids(msgid, &evpid, &i)) { - *total += 1; - srv_send(ctl, &evpid, sizeof(evpid)); - if (srv_check_result(0) == 0) - *ok += 1; - } - } - } else if (argv->type == P_MSGID) { - i = 0; - while (srv_iter_evpids(argv->u.u_msgid, &evpid, &i)) { - srv_send(ctl, &evpid, sizeof(evpid)); - if (srv_check_result(0) == 0) - *ok += 1; - } - } else { - *total += 1; - srv_send(ctl, &argv->u.u_evpid, sizeof(evpid)); - if (srv_check_result(0) == 0) - *ok += 1; - } -} - -static void -srv_show_cmd(int cmd, const void *data, size_t len) -{ - int done = 0; - - srv_send(cmd, data, len); - - do { - srv_recv(cmd); - if (rlen) { - printf("%s\n", rdata); - srv_read(NULL, rlen); - } - else - done = 1; - srv_end(); - } while (!done); -} - -static void -droppriv(void) -{ - struct passwd *pw; - - if (geteuid()) - return; - - if ((pw = getpwnam(SMTPD_USER)) == NULL) - errx(1, "unknown user " SMTPD_USER); - - if ((setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))) - err(1, "cannot drop privileges"); -} - -static int -do_permission_denied(int argc, struct parameter *argv) -{ - errx(1, "need root privileges"); -} - -static int -do_log_brief(int argc, struct parameter *argv) -{ - int v = 0; - - srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_log_verbose(int argc, struct parameter *argv) -{ - int v = TRACE_DEBUG; - - srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_monitor(int argc, struct parameter *argv) -{ - struct stat_digest last, digest; - size_t count; - - memset(&last, 0, sizeof(last)); - count = 0; - - while (1) { - srv_send(IMSG_CTL_GET_DIGEST, NULL, 0); - srv_recv(IMSG_CTL_GET_DIGEST); - srv_read(&digest, sizeof(digest)); - srv_end(); - - if (count % 25 == 0) { - if (count != 0) - printf("\n"); - printf("--- client --- " - "-- envelope -- " - "---- relay/delivery --- " - "------- misc -------\n" - "curr conn disc " - "curr enq deq " - "ok tmpfail prmfail loop " - "expire remove bounce\n"); - } - printf("%4zu %4zu %4zu " - "%4zu %4zu %4zu " - "%4zu %4zu %4zu %4zu " - "%4zu %4zu %4zu\n", - digest.clt_connect - digest.clt_disconnect, - digest.clt_connect - last.clt_connect, - digest.clt_disconnect - last.clt_disconnect, - - digest.evp_enqueued - digest.evp_dequeued, - digest.evp_enqueued - last.evp_enqueued, - digest.evp_dequeued - last.evp_dequeued, - - digest.dlv_ok - last.dlv_ok, - digest.dlv_tempfail - last.dlv_tempfail, - digest.dlv_permfail - last.dlv_permfail, - digest.dlv_loop - last.dlv_loop, - - digest.evp_expired - last.evp_expired, - digest.evp_removed - last.evp_removed, - digest.evp_bounce - last.evp_bounce); - - last = digest; - count++; - sleep(1); - } - - return (0); -} - -static int -do_pause_envelope(int argc, struct parameter *argv) -{ - size_t total, ok; - - srv_foreach_envelope(argv, IMSG_CTL_PAUSE_EVP, &total, &ok); - printf("%zu envelope%s paused\n", ok, (ok > 1) ? "s" : ""); - - return (0); -} - -static int -do_pause_mda(int argc, struct parameter *argv) -{ - srv_send(IMSG_CTL_PAUSE_MDA, NULL, 0); - return srv_check_result(1); -} - -static int -do_pause_mta(int argc, struct parameter *argv) -{ - srv_send(IMSG_CTL_PAUSE_MTA, NULL, 0); - return srv_check_result(1); -} - -static int -do_pause_smtp(int argc, struct parameter *argv) -{ - srv_send(IMSG_CTL_PAUSE_SMTP, NULL, 0); - return srv_check_result(1); -} - -static int -do_profile(int argc, struct parameter *argv) -{ - int v; - - v = str_to_profile(argv[0].u.u_str); - - srv_send(IMSG_CTL_PROFILE_ENABLE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_remove(int argc, struct parameter *argv) -{ - size_t total, ok; - - srv_foreach_envelope(argv, IMSG_CTL_REMOVE, &total, &ok); - printf("%zu envelope%s removed\n", ok, (ok > 1) ? "s" : ""); - - return (0); -} - -static int -do_resume_envelope(int argc, struct parameter *argv) -{ - size_t total, ok; - - srv_foreach_envelope(argv, IMSG_CTL_RESUME_EVP, &total, &ok); - printf("%zu envelope%s resumed\n", ok, (ok > 1) ? "s" : ""); - - return (0); -} - -static int -do_resume_mda(int argc, struct parameter *argv) -{ - srv_send(IMSG_CTL_RESUME_MDA, NULL, 0); - return srv_check_result(1); -} - -static int -do_resume_mta(int argc, struct parameter *argv) -{ - srv_send(IMSG_CTL_RESUME_MTA, NULL, 0); - return srv_check_result(1); -} - -static int -do_resume_route(int argc, struct parameter *argv) -{ - uint64_t v; - - if (argc == 0) - v = 0; - else - v = argv[0].u.u_routeid; - - srv_send(IMSG_CTL_RESUME_ROUTE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_resume_smtp(int argc, struct parameter *argv) -{ - srv_send(IMSG_CTL_RESUME_SMTP, NULL, 0); - return srv_check_result(1); -} - -static int -do_schedule(int argc, struct parameter *argv) -{ - size_t total, ok; - - srv_foreach_envelope(argv, IMSG_CTL_SCHEDULE, &total, &ok); - printf("%zu envelope%s scheduled\n", ok, (ok > 1) ? "s" : ""); - - return (0); -} - -static int -do_show_envelope(int argc, struct parameter *argv) -{ - char buf[PATH_MAX]; - - if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/%016" PRIx64, - PATH_SPOOL, - PATH_QUEUE, - (evpid_to_msgid(argv[0].u.u_evpid) & 0xff000000) >> 24, - evpid_to_msgid(argv[0].u.u_evpid), - argv[0].u.u_evpid)) - errx(1, "unable to retrieve envelope"); - - display(buf); - - return (0); -} - -static int -do_show_hoststats(int argc, struct parameter *argv) -{ - srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTSTATS, NULL, 0); - - return (0); -} - -static int -do_show_message(int argc, struct parameter *argv) -{ - char buf[PATH_MAX]; - uint32_t msgid; - - if (argv[0].type == P_EVPID) - msgid = evpid_to_msgid(argv[0].u.u_evpid); - else - msgid = argv[0].u.u_msgid; - - if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/message", - PATH_SPOOL, - PATH_QUEUE, - (msgid & 0xff000000) >> 24, - msgid)) - errx(1, "unable to retrieve message"); - - display(buf); - - return (0); -} - -static int -do_show_queue(int argc, struct parameter *argv) -{ - struct envelope evp; - uint32_t msgid; - FTS *fts; - FTSENT *ftse; - char *qpath[] = {"/queue", NULL}; - char *tmp; - uint64_t evpid; - - now = time(NULL); - - if (!srv_connect()) { - log_init(1, LOG_MAIL); - queue_init("fs", 0); - if (chroot(PATH_SPOOL) == -1 || chdir("/") == -1) - err(1, "%s", PATH_SPOOL); - fts = fts_open(qpath, FTS_PHYSICAL|FTS_NOCHDIR, NULL); - if (fts == NULL) - err(1, "%s/queue", PATH_SPOOL); - - while ((ftse = fts_read(fts)) != NULL) { - switch (ftse->fts_info) { - case FTS_DP: - case FTS_DNR: - break; - case FTS_F: - tmp = NULL; - evpid = strtoull(ftse->fts_name, &tmp, 16); - if (tmp && *tmp != '\0') - break; - show_offline_envelope(evpid); - } - } - - fts_close(fts); - return (0); - } - - if (argc == 0) { - msgid = 0; - while (srv_iter_messages(&msgid)) - while (srv_iter_envelopes(msgid, &evp)) - show_queue_envelope(&evp, 1); - } else if (argv[0].type == P_MSGID) { - while (srv_iter_envelopes(argv[0].u.u_msgid, &evp)) - show_queue_envelope(&evp, 1); - } - - return (0); -} - -static int -do_show_hosts(int argc, struct parameter *argv) -{ - srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTS, NULL, 0); - - return (0); -} - -static int -do_show_relays(int argc, struct parameter *argv) -{ - srv_show_cmd(IMSG_CTL_MTA_SHOW_RELAYS, NULL, 0); - - return (0); -} - -static int -do_show_routes(int argc, struct parameter *argv) -{ - srv_show_cmd(IMSG_CTL_MTA_SHOW_ROUTES, NULL, 0); - - return (0); -} - -static int -do_show_stats(int argc, struct parameter *argv) -{ - struct stat_kv kv; - time_t duration; - - memset(&kv, 0, sizeof kv); - - while (1) { - srv_send(IMSG_CTL_GET_STATS, &kv, sizeof kv); - srv_recv(IMSG_CTL_GET_STATS); - srv_read(&kv, sizeof(kv)); - srv_end(); - - if (kv.iter == NULL) - break; - - if (strcmp(kv.key, "uptime") == 0) { - duration = time(NULL) - kv.val.u.counter; - printf("uptime=%lld\n", (long long)duration); - printf("uptime.human=%s\n", - duration_to_text(duration)); - } - else { - switch (kv.val.type) { - case STAT_COUNTER: - printf("%s=%zd\n", - kv.key, kv.val.u.counter); - break; - case STAT_TIMESTAMP: - printf("%s=%" PRId64 "\n", - kv.key, (int64_t)kv.val.u.timestamp); - break; - case STAT_TIMEVAL: - printf("%s=%lld.%lld\n", - kv.key, (long long)kv.val.u.tv.tv_sec, - (long long)kv.val.u.tv.tv_usec); - break; - case STAT_TIMESPEC: - printf("%s=%lld.%06ld\n", - kv.key, - (long long)kv.val.u.ts.tv_sec * 1000000 + - kv.val.u.ts.tv_nsec / 1000000, - kv.val.u.ts.tv_nsec % 1000000); - break; - } - } - } - - return (0); -} - -static int -do_show_status(int argc, struct parameter *argv) -{ - uint32_t sc_flags; - - srv_send(IMSG_CTL_SHOW_STATUS, NULL, 0); - srv_recv(IMSG_CTL_SHOW_STATUS); - srv_read(&sc_flags, sizeof(sc_flags)); - srv_end(); - printf("MDA %s\n", - (sc_flags & SMTPD_MDA_PAUSED) ? "paused" : "running"); - printf("MTA %s\n", - (sc_flags & SMTPD_MTA_PAUSED) ? "paused" : "running"); - printf("SMTP %s\n", - (sc_flags & SMTPD_SMTP_PAUSED) ? "paused" : "running"); - return (0); -} - -static int -do_trace(int argc, struct parameter *argv) -{ - int v; - - v = str_to_trace(argv[0].u.u_str); - - srv_send(IMSG_CTL_TRACE_ENABLE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_unprofile(int argc, struct parameter *argv) -{ - int v; - - v = str_to_profile(argv[0].u.u_str); - - srv_send(IMSG_CTL_PROFILE_DISABLE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_untrace(int argc, struct parameter *argv) -{ - int v; - - v = str_to_trace(argv[0].u.u_str); - - srv_send(IMSG_CTL_TRACE_DISABLE, &v, sizeof(v)); - return srv_check_result(1); -} - -static int -do_update_table(int argc, struct parameter *argv) -{ - const char *name = argv[0].u.u_str; - - srv_send(IMSG_CTL_UPDATE_TABLE, name, strlen(name) + 1); - return srv_check_result(1); -} - -static int -do_encrypt(int argc, struct parameter *argv) -{ - const char *p = NULL; - - droppriv(); - - if (argv) - p = argv[0].u.u_str; - execl(PATH_ENCRYPT, "encrypt", "--", p, (char *)NULL); - errx(1, "execl"); -} - -static int -do_block_mta(int argc, struct parameter *argv) -{ - struct ibuf *m; - - if (ibuf == NULL && !srv_connect()) - errx(1, "smtpd doesn't seem to be running"); - m = imsg_create(ibuf, IMSG_CTL_MTA_BLOCK, IMSG_VERSION, 0, - sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1); - if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1) - errx(1, "imsg_add"); - if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1) - errx(1, "imsg_add"); - imsg_close(ibuf, m); - - return srv_check_result(1); -} - -static int -do_unblock_mta(int argc, struct parameter *argv) -{ - struct ibuf *m; - - if (ibuf == NULL && !srv_connect()) - errx(1, "smtpd doesn't seem to be running"); - - m = imsg_create(ibuf, IMSG_CTL_MTA_UNBLOCK, IMSG_VERSION, 0, - sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1); - if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1) - errx(1, "imsg_add"); - if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1) - errx(1, "imsg_add"); - imsg_close(ibuf, m); - - return srv_check_result(1); -} - -static int -do_show_mta_block(int argc, struct parameter *argv) -{ - srv_show_cmd(IMSG_CTL_MTA_SHOW_BLOCK, NULL, 0); - - return (0); -} - -static int -do_discover(int argc, struct parameter *argv) -{ - uint64_t evpid; - uint32_t msgid; - size_t n_evp; - - if (ibuf == NULL && !srv_connect()) - errx(1, "smtpd doesn't seem to be running"); - - if (argv[0].type == P_EVPID) { - evpid = argv[0].u.u_evpid; - srv_send(IMSG_CTL_DISCOVER_EVPID, &evpid, sizeof evpid); - srv_recv(IMSG_CTL_DISCOVER_EVPID); - } else { - msgid = argv[0].u.u_msgid; - srv_send(IMSG_CTL_DISCOVER_MSGID, &msgid, sizeof msgid); - srv_recv(IMSG_CTL_DISCOVER_MSGID); - } - - if (rlen == 0) { - srv_end(); - return (0); - } else { - srv_read(&n_evp, sizeof n_evp); - srv_end(); - } - - printf("%zu envelope%s discovered\n", n_evp, (n_evp != 1) ? "s" : ""); - return (0); -} - -static int -do_spf_walk(int argc, struct parameter *argv) -{ - droppriv(); - - return spfwalk(argc, argv); -} - -#define cmd_install_priv(s, f) \ - cmd_install((s), privileged ? (f) : do_permission_denied) - -int -main(int argc, char **argv) -{ - gid_t gid; - struct group *gr; - int privileged; - char *argv_mailq[] = { "show", "queue", NULL }; - -#ifndef HAVE___PROGNAME - __progname = ssh_get_progname(argv[0]); -#endif - - /* check that smtpctl was installed setgid */ - if ((gr = getgrnam(SMTPD_QUEUE_GROUP)) == NULL) - errx(1, "unknown group %s", SMTPD_QUEUE_GROUP); - else if (gr->gr_gid != getegid()) - errx(1, "this program must be setgid %s", SMTPD_QUEUE_GROUP); - - sendmail_compat(argc, argv); - privileged = geteuid() == 0; - - gid = getgid(); - if (setresgid(gid, gid, gid) == -1) - err(1, "setresgid"); - - /* Privileged commands */ - cmd_install_priv("discover ", do_discover); - cmd_install_priv("discover ", do_discover); - cmd_install_priv("pause mta from for ", do_block_mta); - cmd_install_priv("resume mta from for ", do_unblock_mta); - cmd_install_priv("show mta paused", do_show_mta_block); - cmd_install_priv("log brief", do_log_brief); - cmd_install_priv("log verbose", do_log_verbose); - cmd_install_priv("monitor", do_monitor); - cmd_install_priv("pause envelope ", do_pause_envelope); - cmd_install_priv("pause envelope ", do_pause_envelope); - cmd_install_priv("pause envelope all", do_pause_envelope); - cmd_install_priv("pause mda", do_pause_mda); - cmd_install_priv("pause mta", do_pause_mta); - cmd_install_priv("pause smtp", do_pause_smtp); - cmd_install_priv("profile ", do_profile); - cmd_install_priv("remove ", do_remove); - cmd_install_priv("remove ", do_remove); - cmd_install_priv("remove all", do_remove); - cmd_install_priv("resume envelope ", do_resume_envelope); - cmd_install_priv("resume envelope ", do_resume_envelope); - cmd_install_priv("resume envelope all", do_resume_envelope); - cmd_install_priv("resume mda", do_resume_mda); - cmd_install_priv("resume mta", do_resume_mta); - cmd_install_priv("resume route ", do_resume_route); - cmd_install_priv("resume smtp", do_resume_smtp); - cmd_install_priv("schedule ", do_schedule); - cmd_install_priv("schedule ", do_schedule); - cmd_install_priv("schedule all", do_schedule); - cmd_install_priv("show envelope ", do_show_envelope); - cmd_install_priv("show hoststats", do_show_hoststats); - cmd_install_priv("show message ", do_show_message); - cmd_install_priv("show message ", do_show_message); - cmd_install_priv("show queue", do_show_queue); - cmd_install_priv("show queue ", do_show_queue); - cmd_install_priv("show hosts", do_show_hosts); - cmd_install_priv("show relays", do_show_relays); - cmd_install_priv("show routes", do_show_routes); - cmd_install_priv("show stats", do_show_stats); - cmd_install_priv("show status", do_show_status); - cmd_install_priv("trace ", do_trace); - cmd_install_priv("unprofile ", do_unprofile); - cmd_install_priv("untrace ", do_untrace); - cmd_install_priv("update table ", do_update_table); - - /* Unprivileged commands */ - cmd_install("encrypt", do_encrypt); - cmd_install("encrypt ", do_encrypt); - cmd_install("spf walk", do_spf_walk); - - if (strcmp(__progname, "mailq") == 0) - return cmd_run(2, argv_mailq); - if (strcmp(__progname, "smtpctl") == 0) - return cmd_run(argc - 1, argv + 1); - - errx(1, "unsupported mode"); - return (0); -} - -void -sendmail_compat(int argc, char **argv) -{ - FILE *offlinefp = NULL; - gid_t gid; - int i, r; - - if (strcmp(__progname, "sendmail") == 0 || - strcmp(__progname, "send-mail") == 0) { - /* - * determine whether we are called with flags - * that should invoke makemap/newaliases. - */ - for (i = 1; i < argc; i++) - if (strncmp(argv[i], "-bi", 3) == 0) - exit(makemap(P_SENDMAIL, argc, argv)); - - if (!srv_connect()) - offlinefp = offline_file(); - - gid = getgid(); - if (setresgid(gid, gid, gid) == -1) - err(1, "setresgid"); - -#if HAVE_PLEDGE - /* we'll reduce further down the road */ - if (pledge("stdio rpath wpath cpath tmppath flock " - "dns getpw recvfd", NULL) == -1) - err(1, "pledge"); -#endif - - sendmail = 1; - exit(enqueue(argc, argv, offlinefp)); - } else if (strcmp(__progname, "makemap") == 0) - exit(makemap(P_MAKEMAP, argc, argv)); - else if (strcmp(__progname, "newaliases") == 0) { - r = makemap(P_NEWALIASES, argc, argv); - /* - * if server is available, notify of table update. - * only makes sense for static tables AND if server is up. - */ - if (srv_connect()) { - srv_send(IMSG_CTL_UPDATE_TABLE, "aliases", strlen("aliases") + 1); - srv_check_result(0); - } - exit(r); - } -} - -static void -show_queue_envelope(struct envelope *e, int online) -{ - const char *src = "?", *agent = "?"; - char status[128], runstate[128], errline[LINE_MAX]; - - status[0] = '\0'; - - getflag(&e->flags, EF_BOUNCE, "bounce", status, sizeof(status)); - getflag(&e->flags, EF_AUTHENTICATED, "auth", status, sizeof(status)); - getflag(&e->flags, EF_INTERNAL, "internal", status, sizeof(status)); - getflag(&e->flags, EF_SUSPEND, "suspend", status, sizeof(status)); - getflag(&e->flags, EF_HOLD, "hold", status, sizeof(status)); - - if (online) { - if (e->flags & EF_PENDING) - (void)snprintf(runstate, sizeof runstate, "pending|%zd", - (ssize_t)(e->nexttry - now)); - else if (e->flags & EF_INFLIGHT) - (void)snprintf(runstate, sizeof runstate, - "inflight|%zd", (ssize_t)(now - e->lasttry)); - else - (void)snprintf(runstate, sizeof runstate, "invalid|"); - e->flags &= ~(EF_PENDING|EF_INFLIGHT); - } - else - (void)strlcpy(runstate, "offline|", sizeof runstate); - - if (e->flags) - errx(1, "%016" PRIx64 ": unexpected flags 0x%04x", e->id, - e->flags); - - if (status[0]) - status[strlen(status) - 1] = '\0'; - - if (e->type == D_MDA) - agent = "mda"; - else if (e->type == D_MTA) - agent = "mta"; - else if (e->type == D_BOUNCE) - agent = "bounce"; - - if (e->ss.ss_family == AF_LOCAL) - src = "local"; - else if (e->ss.ss_family == AF_INET) - src = "inet4"; - else if (e->ss.ss_family == AF_INET6) - src = "inet6"; - - strnvis(errline, e->errorline, sizeof(errline), 0); - - printf("%016"PRIx64 - "|%s|%s|%s|%s@%s|%s@%s|%s@%s" - "|%zu|%zu|%zu|%zu|%s|%s\n", - - e->id, - - src, - agent, - status, - e->sender.user, e->sender.domain, - e->rcpt.user, e->rcpt.domain, - e->dest.user, e->dest.domain, - - (size_t) e->creation, - (size_t) (e->creation + e->ttl), - (size_t) e->lasttry, - (size_t) e->retry, - runstate, - errline); -} - -static void -getflag(uint *bitmap, int bit, char *bitstr, char *buf, size_t len) -{ - if (*bitmap & bit) { - *bitmap &= ~bit; - (void)strlcat(buf, bitstr, len); - (void)strlcat(buf, ",", len); - } -} - -static void -show_offline_envelope(uint64_t evpid) -{ - FILE *fp = NULL; - char pathname[PATH_MAX]; - size_t plen; - char *p; - size_t buflen; - char buffer[sizeof(struct envelope)]; - - struct envelope evp; - - if (!bsnprintf(pathname, sizeof pathname, - "/queue/%02x/%08x/%016"PRIx64, - (evpid_to_msgid(evpid) & 0xff000000) >> 24, - evpid_to_msgid(evpid), evpid)) - goto end; - fp = fopen(pathname, "r"); - if (fp == NULL) - goto end; - - buflen = fread(buffer, 1, sizeof (buffer) - 1, fp); - p = buffer; - plen = buflen; - buffer[buflen] = '\0'; - - if (is_encrypted_buffer(p)) { - warnx("offline encrypted queue is not supported yet"); - goto end; - } - - if (is_gzip_buffer(p)) { - warnx("offline compressed queue is not supported yet"); - goto end; - } - - if (!envelope_load_buffer(&evp, p, plen)) - goto end; - evp.id = evpid; - show_queue_envelope(&evp, 0); - -end: - if (fp) - fclose(fp); -} - -static void -display(const char *s) -{ - FILE *fp; - char *key; - int gzipped; - char *gzcat_argv0 = strrchr(PATH_GZCAT, '/') + 1; - - if ((fp = fopen(s, "r")) == NULL) - err(1, "fopen"); - - if (is_encrypted_fp(fp)) { - int i; - FILE *ofp = NULL; - - if ((ofp = tmpfile()) == NULL) - err(1, "tmpfile"); - - for (i = 0; i < 3; i++) { - key = getpass("key> "); - if (crypto_setup(key, strlen(key))) - break; - } - if (i == 3) - errx(1, "crypto-setup: invalid key"); - - if (!crypto_decrypt_file(fp, ofp)) { - printf("object is encrypted: %s\n", key); - exit(1); - } - - fclose(fp); - fp = ofp; - fseek(fp, 0, SEEK_SET); - } - gzipped = is_gzip_fp(fp); - - lseek(fileno(fp), 0, SEEK_SET); - (void)dup2(fileno(fp), STDIN_FILENO); - if (gzipped) - execl(PATH_GZCAT, gzcat_argv0, (char *)NULL); - else - execl(PATH_CAT, "cat", (char *)NULL); - err(1, "execl"); -} - -static int -str_to_trace(const char *str) -{ - if (!strcmp(str, "imsg")) - return TRACE_IMSG; - if (!strcmp(str, "io")) - return TRACE_IO; - if (!strcmp(str, "smtp")) - return TRACE_SMTP; - if (!strcmp(str, "filters")) - return TRACE_FILTERS; - if (!strcmp(str, "mta")) - return TRACE_MTA; - if (!strcmp(str, "bounce")) - return TRACE_BOUNCE; - if (!strcmp(str, "scheduler")) - return TRACE_SCHEDULER; - if (!strcmp(str, "lookup")) - return TRACE_LOOKUP; - if (!strcmp(str, "stat")) - return TRACE_STAT; - if (!strcmp(str, "rules")) - return TRACE_RULES; - if (!strcmp(str, "mproc")) - return TRACE_MPROC; - if (!strcmp(str, "expand")) - return TRACE_EXPAND; - if (!strcmp(str, "all")) - return ~TRACE_DEBUG; - errx(1, "invalid trace keyword: %s", str); - return (0); -} - -static int -str_to_profile(const char *str) -{ - if (!strcmp(str, "imsg")) - return PROFILE_IMSG; - if (!strcmp(str, "queue")) - return PROFILE_QUEUE; - errx(1, "invalid profile keyword: %s", str); - return (0); -} - -static int -is_gzip_buffer(const char *buffer) -{ - uint16_t magic; - - memcpy(&magic, buffer, sizeof magic); -#define GZIP_MAGIC 0x8b1f - return (magic == GZIP_MAGIC); -} - -static int -is_gzip_fp(FILE *fp) -{ - uint8_t magic[2]; - int ret = 0; - - if (fread(&magic, 1, sizeof magic, fp) != sizeof magic) - goto end; - - ret = is_gzip_buffer((const char *)&magic); -end: - fseek(fp, 0, SEEK_SET); - return ret; -} - - -/* XXX */ -/* - * queue supports transparent encryption. - * encrypted chunks are prefixed with an API version byte - * which we ensure is unambiguous with gzipped / plain - * objects. - */ - -static int -is_encrypted_buffer(const char *buffer) -{ - uint8_t magic; - - magic = *buffer; -#define ENCRYPTION_MAGIC 0x1 - return (magic == ENCRYPTION_MAGIC); -} - -static int -is_encrypted_fp(FILE *fp) -{ - uint8_t magic; - int ret = 0; - - if (fread(&magic, 1, sizeof magic, fp) != sizeof magic) - goto end; - - ret = is_encrypted_buffer((const char *)&magic); -end: - fseek(fp, 0, SEEK_SET); - return ret; -} diff --git a/smtpd/smtpctl/Makefile b/smtpd/smtpctl/Makefile deleted file mode 100644 index ef8148be..00000000 --- a/smtpd/smtpctl/Makefile +++ /dev/null @@ -1,56 +0,0 @@ -# $OpenBSD: Makefile,v 1.47 2018/07/03 01:34:43 mortimer Exp $ - -.PATH: ${.CURDIR}/.. - -PROG= smtpctl -BINOWN= root -BINGRP= _smtpq - -BINMODE?=2555 - -BINDIR= /usr/sbin -MAN= smtpctl.8 aliases.5 forward.5 makemap.8 newaliases.8 - -CFLAGS+= -fstack-protector-all -CFLAGS+= -I${.CURDIR}/.. -CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -CFLAGS+= -Wmissing-declarations -CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual -CFLAGS+= -Wsign-compare -CFLAGS+= -Werror-implicit-function-declaration -CFLAGS+= -DNO_IO -CFLAGS+= -DCONFIG_MINIMUM -YFLAGS= - -SRCS= enqueue.c -SRCS+= parser.c -SRCS+= log.c -SRCS+= envelope.c -SRCS+= crypto.c -SRCS+= queue_backend.c -SRCS+= queue_fs.c -SRCS+= smtpctl.c -SRCS+= util.c -SRCS+= compress_backend.c -SRCS+= compress_gzip.c -SRCS+= to.c -SRCS+= expand.c -SRCS+= tree.c -SRCS+= config.c -SRCS+= dict.c -SRCS+= aliases.c -SRCS+= limit.c -SRCS+= makemap.c -SRCS+= parse.y -SRCS+= mailaddr.c -SRCS+= table.c -SRCS+= table_static.c -SRCS+= table_db.c -SRCS+= table_getpwnam.c -SRCS+= table_proc.c -SRCS+= unpack_dns.c -SRCS+= spfwalk.c - -LDADD+= -levent -lutil -lz -lcrypto -DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBZ} ${LIBCRYPTO} -.include diff --git a/smtpd/smtpd-api.h b/smtpd/smtpd-api.h deleted file mode 100644 index f83edd05..00000000 --- a/smtpd/smtpd-api.h +++ /dev/null @@ -1,290 +0,0 @@ -/* $OpenBSD: smtpd-api.h,v 1.36 2018/12/23 16:06:24 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2011 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _SMTPD_API_H_ -#define _SMTPD_API_H_ - -#include "dict.h" -#include "tree.h" - -struct mailaddr { - char user[SMTPD_MAXLOCALPARTSIZE]; - char domain[SMTPD_MAXDOMAINPARTSIZE]; -}; - -#define PROC_QUEUE_API_VERSION 2 - -enum { - PROC_QUEUE_OK, - PROC_QUEUE_FAIL, - PROC_QUEUE_INIT, - PROC_QUEUE_CLOSE, - PROC_QUEUE_MESSAGE_CREATE, - PROC_QUEUE_MESSAGE_DELETE, - PROC_QUEUE_MESSAGE_COMMIT, - PROC_QUEUE_MESSAGE_FD_R, - PROC_QUEUE_ENVELOPE_CREATE, - PROC_QUEUE_ENVELOPE_DELETE, - PROC_QUEUE_ENVELOPE_LOAD, - PROC_QUEUE_ENVELOPE_UPDATE, - PROC_QUEUE_ENVELOPE_WALK, -}; - -#define PROC_SCHEDULER_API_VERSION 2 - -struct scheduler_info; - -enum { - PROC_SCHEDULER_OK, - PROC_SCHEDULER_FAIL, - PROC_SCHEDULER_INIT, - PROC_SCHEDULER_INSERT, - PROC_SCHEDULER_COMMIT, - PROC_SCHEDULER_ROLLBACK, - PROC_SCHEDULER_UPDATE, - PROC_SCHEDULER_DELETE, - PROC_SCHEDULER_HOLD, - PROC_SCHEDULER_RELEASE, - PROC_SCHEDULER_BATCH, - PROC_SCHEDULER_MESSAGES, - PROC_SCHEDULER_ENVELOPES, - PROC_SCHEDULER_SCHEDULE, - PROC_SCHEDULER_REMOVE, - PROC_SCHEDULER_SUSPEND, - PROC_SCHEDULER_RESUME, -}; - -enum envelope_flags { - EF_AUTHENTICATED = 0x01, - EF_BOUNCE = 0x02, - EF_INTERNAL = 0x04, /* Internal expansion forward */ - - /* runstate, not saved on disk */ - - EF_PENDING = 0x10, - EF_INFLIGHT = 0x20, - EF_SUSPEND = 0x40, - EF_HOLD = 0x80, -}; - -struct evpstate { - uint64_t evpid; - uint16_t flags; - uint16_t retry; - time_t time; -}; - -enum delivery_type { - D_MDA, - D_MTA, - D_BOUNCE, -}; - -struct scheduler_info { - uint64_t evpid; - enum delivery_type type; - uint16_t retry; - time_t creation; - time_t ttl; - time_t lasttry; - time_t lastbounce; - time_t nexttry; -}; - -#define SCHED_REMOVE 0x01 -#define SCHED_EXPIRE 0x02 -#define SCHED_UPDATE 0x04 -#define SCHED_BOUNCE 0x08 -#define SCHED_MDA 0x10 -#define SCHED_MTA 0x20 - -#define PROC_TABLE_API_VERSION 2 - -struct table_open_params { - uint32_t version; - char name[LINE_MAX]; -}; - -enum table_service { - K_NONE = 0x000, - K_ALIAS = 0x001, /* returns struct expand */ - K_DOMAIN = 0x002, /* returns struct destination */ - K_CREDENTIALS = 0x004, /* returns struct credentials */ - K_NETADDR = 0x008, /* returns struct netaddr */ - K_USERINFO = 0x010, /* returns struct userinfo */ - K_SOURCE = 0x020, /* returns struct source */ - K_MAILADDR = 0x040, /* returns struct mailaddr */ - K_ADDRNAME = 0x080, /* returns struct addrname */ - K_MAILADDRMAP = 0x100, /* returns struct maddrmap */ - K_RELAYHOST = 0x200, /* returns struct relayhost */ - K_STRING = 0x400, - K_REGEX = 0x800, -}; -#define K_ANY 0xfff - -enum { - PROC_TABLE_OK, - PROC_TABLE_FAIL, - PROC_TABLE_OPEN, - PROC_TABLE_CLOSE, - PROC_TABLE_UPDATE, - PROC_TABLE_CHECK, - PROC_TABLE_LOOKUP, - PROC_TABLE_FETCH, -}; - -enum enhanced_status_code { - /* 0.0 */ - ESC_OTHER_STATUS = 00, - - /* 1.x */ - ESC_OTHER_ADDRESS_STATUS = 10, - ESC_BAD_DESTINATION_MAILBOX_ADDRESS = 11, - ESC_BAD_DESTINATION_SYSTEM_ADDRESS = 12, - ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX = 13, - ESC_DESTINATION_MAILBOX_ADDRESS_AMBIGUOUS = 14, - ESC_DESTINATION_ADDRESS_VALID = 15, - ESC_DESTINATION_MAILBOX_HAS_MOVED = 16, - ESC_BAD_SENDER_MAILBOX_ADDRESS_SYNTAX = 17, - ESC_BAD_SENDER_SYSTEM_ADDRESS = 18, - - /* 2.x */ - ESC_OTHER_MAILBOX_STATUS = 20, - ESC_MAILBOX_DISABLED = 21, - ESC_MAILBOX_FULL = 22, - ESC_MESSAGE_LENGTH_TOO_LARGE = 23, - ESC_MAILING_LIST_EXPANSION_PROBLEM = 24, - - /* 3.x */ - ESC_OTHER_MAIL_SYSTEM_STATUS = 30, - ESC_MAIL_SYSTEM_FULL = 31, - ESC_SYSTEM_NOT_ACCEPTING_MESSAGES = 32, - ESC_SYSTEM_NOT_CAPABLE_OF_SELECTED_FEATURES = 33, - ESC_MESSAGE_TOO_BIG_FOR_SYSTEM = 34, - ESC_SYSTEM_INCORRECTLY_CONFIGURED = 35, - - /* 4.x */ - ESC_OTHER_NETWORK_ROUTING_STATUS = 40, - ESC_NO_ANSWER_FROM_HOST = 41, - ESC_BAD_CONNECTION = 42, - ESC_DIRECTORY_SERVER_FAILURE = 43, - ESC_UNABLE_TO_ROUTE = 44, - ESC_MAIL_SYSTEM_CONGESTION = 45, - ESC_ROUTING_LOOP_DETECTED = 46, - ESC_DELIVERY_TIME_EXPIRED = 47, - - /* 5.x */ - ESC_INVALID_RECIPIENT = 50, - ESC_INVALID_COMMAND = 51, - ESC_SYNTAX_ERROR = 52, - ESC_TOO_MANY_RECIPIENTS = 53, - ESC_INVALID_COMMAND_ARGUMENTS = 54, - ESC_WRONG_PROTOCOL_VERSION = 55, - - /* 6.x */ - ESC_OTHER_MEDIA_ERROR = 60, - ESC_MEDIA_NOT_SUPPORTED = 61, - ESC_CONVERSION_REQUIRED_AND_PROHIBITED = 62, - ESC_CONVERSION_REQUIRED_BUT_NOT_SUPPORTED = 63, - ESC_CONVERSION_WITH_LOSS_PERFORMED = 64, - ESC_CONVERSION_FAILED = 65, - - /* 7.x */ - ESC_OTHER_SECURITY_STATUS = 70, - ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED = 71, - ESC_MAILING_LIST_EXPANSION_PROHIBITED = 72, - ESC_SECURITY_CONVERSION_REQUIRED_NOT_POSSIBLE = 73, - ESC_SECURITY_FEATURES_NOT_SUPPORTED = 74, - ESC_CRYPTOGRAPHIC_FAILURE = 75, - ESC_CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED = 76, - ESC_MESSAGE_INTEGRITY_FAILURE = 77, -}; - -enum enhanced_status_class { - ESC_STATUS_OK = 2, - ESC_STATUS_TEMPFAIL = 4, - ESC_STATUS_PERMFAIL = 5, -}; - -static inline uint32_t -evpid_to_msgid(uint64_t evpid) -{ - return (evpid >> 32); -} - -static inline uint64_t -msgid_to_evpid(uint32_t msgid) -{ - return ((uint64_t)msgid << 32); -} - - -/* esc.c */ -const char *esc_code(enum enhanced_status_class, enum enhanced_status_code); -const char *esc_description(enum enhanced_status_code); - - -/* queue */ -void queue_api_on_close(int(*)(void)); -void queue_api_on_message_create(int(*)(uint32_t *)); -void queue_api_on_message_commit(int(*)(uint32_t, const char*)); -void queue_api_on_message_delete(int(*)(uint32_t)); -void queue_api_on_message_fd_r(int(*)(uint32_t)); -void queue_api_on_envelope_create(int(*)(uint32_t, const char *, size_t, uint64_t *)); -void queue_api_on_envelope_delete(int(*)(uint64_t)); -void queue_api_on_envelope_update(int(*)(uint64_t, const char *, size_t)); -void queue_api_on_envelope_load(int(*)(uint64_t, char *, size_t)); -void queue_api_on_envelope_walk(int(*)(uint64_t *, char *, size_t)); -void queue_api_on_message_walk(int(*)(uint64_t *, char *, size_t, - uint32_t, int *, void **)); -void queue_api_no_chroot(void); -void queue_api_set_chroot(const char *); -void queue_api_set_user(const char *); -int queue_api_dispatch(void); - -/* scheduler */ -void scheduler_api_on_init(int(*)(void)); -void scheduler_api_on_insert(int(*)(struct scheduler_info *)); -void scheduler_api_on_commit(size_t(*)(uint32_t)); -void scheduler_api_on_rollback(size_t(*)(uint32_t)); -void scheduler_api_on_update(int(*)(struct scheduler_info *)); -void scheduler_api_on_delete(int(*)(uint64_t)); -void scheduler_api_on_hold(int(*)(uint64_t, uint64_t)); -void scheduler_api_on_release(int(*)(int, uint64_t, int)); -void scheduler_api_on_batch(int(*)(int, int *, size_t *, uint64_t *, int *)); -void scheduler_api_on_messages(size_t(*)(uint32_t, uint32_t *, size_t)); -void scheduler_api_on_envelopes(size_t(*)(uint64_t, struct evpstate *, size_t)); -void scheduler_api_on_schedule(int(*)(uint64_t)); -void scheduler_api_on_remove(int(*)(uint64_t)); -void scheduler_api_on_suspend(int(*)(uint64_t)); -void scheduler_api_on_resume(int(*)(uint64_t)); -void scheduler_api_no_chroot(void); -void scheduler_api_set_chroot(const char *); -void scheduler_api_set_user(const char *); -int scheduler_api_dispatch(void); - -/* table */ -void table_api_on_update(int(*)(void)); -void table_api_on_check(int(*)(int, struct dict *, const char *)); -void table_api_on_lookup(int(*)(int, struct dict *, const char *, char *, size_t)); -void table_api_on_fetch(int(*)(int, struct dict *, char *, size_t)); -int table_api_dispatch(void); -const char *table_api_get_name(void); - -#endif diff --git a/smtpd/smtpd-defines.h b/smtpd/smtpd-defines.h deleted file mode 100644 index f22a546f..00000000 --- a/smtpd/smtpd-defines.h +++ /dev/null @@ -1,68 +0,0 @@ -/* $OpenBSD: smtpd-defines.h,v 1.12 2020/02/24 16:16:08 millert Exp $ */ - -/* - * Copyright (c) 2013 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef nitems -#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) -#endif - -#define SMTPD_TABLENAME_SIZE (64 + 1) -#define SMTPD_TAG_SIZE (32 + 1) - -/* buffer sizes for email address components */ -#define SMTPD_MAXLOCALPARTSIZE (255 + 1) -#define SMTPD_MAXDOMAINPARTSIZE (255 + 1) -#define SMTPD_MAXMAILADDRSIZE (255 + 1) - -/* buffer size for virtual username (can be email addresses) */ -#define SMTPD_VUSERNAME_SIZE (255 + 1) -#define SMTPD_SUBADDRESS_SIZE (255 + 1) - -#ifndef SMTPD_USER -#define SMTPD_USER "_smtpd" -#endif -#ifndef PATH_CHROOT -#define PATH_CHROOT "/var/empty" -#endif -#ifndef SMTPD_QUEUE_USER -#define SMTPD_QUEUE_USER "_smtpq" -#endif -#ifndef SMTPD_QUEUE_GROUP -#define SMTPD_QUEUE_GROUP "_smtpq" -#endif -#ifndef PATH_SPOOL -#define PATH_SPOOL "/var/spool/smtpd" -#endif - -#ifndef PATH_MAILLOCAL -#define PATH_MAILLOCAL PATH_LIBEXEC "/mail.local" -#endif - -#ifndef PATH_MAKEMAP -#define PATH_MAKEMAP "/usr/sbin/makemap" -#endif - -#define SUBADDRESSING_DELIMITER "+" - - -/* sendmail compat */ - -#define EX_OK 0 -#define EX_NOHOST 68 -#define EX_UNAVAILABLE 69 -#define EX_SOFTWARE 70 -#define EX_TEMPFAIL 75 diff --git a/smtpd/smtpd-filters.7 b/smtpd/smtpd-filters.7 deleted file mode 100644 index 5af7008e..00000000 --- a/smtpd/smtpd-filters.7 +++ /dev/null @@ -1,653 +0,0 @@ -.\" $OpenBSD: smtpd-filters.7,v 1.6 2020/04/25 09:44:02 eric Exp $ -.\" -.\" Copyright (c) 2008 Janne Johansson -.\" Copyright (c) 2009 Jacek Masiulaniec -.\" Copyright (c) 2012 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.\" -.Dd $Mdocdate: April 25 2020 $ -.Dt FILTERS 7 -.Os -.Sh NAME -.Nm filters -.Nd filtering API for the -.Xr smtpd 8 -daemon -.Sh DESCRIPTION -The -.Xr smtpd 8 -daemon provides a Simple Mail Transfer Protocol (SMTP) implementation -that allows an ordinary machine to become Mail eXchangers (MX). -Many features that are commonly used by MX, -such as delivery reporting or Spam filtering, -are outside the scope of SMTP and too complex to fit in -.Xr smtpd 8 . -.Pp -Because an MX needs to provide these features, -.Xr smtpd 8 -provides an API to extend its behavior through pluggable -.Nm . -.Pp -At runtime, -.Xr smtpd 8 -can report events to -.Nm -and query what it should answer to these events. -This allows the decision logic to rely on third-party programs. -.Sh DESIGN -The -.Nm -are programs that run as unique standalone processes, -they do not share -.Xr smtpd 8 -memory space. -They are executed by -.Xr smtpd 8 -at startup and expected to run in an infinite loop, -reading events and filtering requests from -.Xr stdin 4 , -writing responses to -.Xr stdout 4 -and logging to -.Xr stderr 4 . -They are not allowed to terminate. -.Pp -Because -.Nm -are standalone programs that communicate with -.Xr smtpd 8 -through -.Xr fd 4 , -they may run as different users than -.Xr smtpd 8 -and may be written in any language. -The -.Nm -must not use blocking I/O, -they must support answering asynchronously to -.Xr smtpd 8 . -.Sh REPORT AND FILTER -The API relies on two streams, -report and filter. -.Pp -The report stream is a one-way stream which allows -.Xr smtpd 8 -to inform -.Nm -in real-time about events that are occurring in the daemon. -The report events do not expect an answer from -.Nm , -it is just meant to provide them with informations. -A filter should be able to replicate the -.Xr smtpd 8 -state for a session by gathering informations coming from report events. -No decision is ever taken by the report stream. -.Pp -The filter stream is a two-way stream which allows -.Xr smtpd 8 -to query -.Nm -about what it should do with a session at a given phase. -The filter requests expects an answer from -.Nm , -.Xr smtpd 8 -will not let the session move forward until then. -A decision must always be taken by the filter stream. -.Pp -It is sometimes possible to rely on filter requests to gather information, -but because a reponse is expected by -.Xr smtpd 8 , -this is more costly than using report events. -The correct pattern for writing filters is to use the report events to -create a local state for a session, -then use filter requests to take decisions based on this state. -The only case when using filter request instead of report events is correct, -is when a decision is required for the filter request and there is no need for -more information than that of the event. -.Sh PROTOCOL -The protocol is straightforward, -it consists of a human-readable line exchanges between -.Nm -and -.Xr smtpd 8 -through -.Xr fd 4 . -.Pp -The protocol begins with a handshake. -First, -.Xr smtpd 8 -provides -.Nm -with general configuration information in the form of key-value lines: -.Bd -literal -offset indent -config|smtpd-version|6.6.1 -config|smtp-session-timeout|300 -config|subsystem|smtp-in -config|ready -.Ed -.Pp -Then, -.Nm -register the stream, -subsystem and event they want to handle: -.Bd -literal -offset indent -register|report|smtp-in|link-connect -register|ready -.Ed -.Pp -Finally, -.Xr smtpd 8 -will emit report events and filter requests, -expecting -.Nm -to react accordingly either by responding or not depending on the stream: -.Bd -literal -offset indent -report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 -report|0.5|1576147242.200225|smtp-in|link-connect|7641dfb3798eb5bf|mail.openbsd.org|pass|199.185.178.25:31205|45.77.67.80:25 -report|0.5|1576148447.982572|smtp-in|link-connect|7641dfc063102cbd|mail.openbsd.org|pass|199.185.178.25:24786|45.77.67.80:25 -.Ed -.Pp -The char -.Dq | -may only appear in the last field of a payload, -in which case it should be considered a regular char and not a separator. -Other fields have strict formatting excluding the possibility of having a -.Dq | . -.Pp -The list of subsystems and events, -as well as the format of requests and reponses, -will be documented in the sections below. -.Sh CONFIGURATION -During the initial handshake, -.Xr smtpd 8 -will emit a serie of configuration keys and values. -The list is meant to be ignored by -.Nm -that do not require it and consumed gracefully by filters that do. -.Pp -There are currently three keys: -.Bd -literal -offset indent -config|smtpd-version|6.6.1 -config|smtp-session-timeout|300 -config|subsystem|smtp-in -.Ed -.Pp -When -.Xr smtpd 8 -has sent all configuration keys it emits the following line: -.Bd -literal -offset indent -config|ready -.Ed -.Sh REPORT EVENTS -There is currently only one subsystem supported in the API: -smtp-in. -.Pp -Each report event is generated by -.Xr smtpd 8 -as a single line similar to the one below: -.Bd -literal -offset indent -report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 -.Ed -.Pp -The format consists of a protocol prefix containing the stream, -the protocol version, -the timestamp, -the subsystem, -the event and the unique session identifier separated by -.Dq | : -.Bd -literal -offset indent -report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00 -.Ed -.Pp -It is followed by a suffix containing the event-specific parameters, -also separated by -.Dq | : -.Bd -literal -offset indent -mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 -.Ed -.Pp -The list of events and event-specific parameters are provided here for smtp-in: -.Bl -tag -width Ds -.It Ic link-connect : Ar rdns fcrdns src dest -This event is generated upon connection. -.Pp -.Ar rdns -contains the reverse DNS hostname for the remote end or an empty string if none. -.Pp -.Ar fcrdns -contains the string -.Dq pass -or -.Dq fail -depending on if the remote end validates FCrDNS. -.Pp -.Ar src -holds either the IP address and port from source address, -in the format -.Dq address:port -or the path to a UNIX socket in the format -.Dq unix:/path . -.Pp -.Ar dest -holds either the IP address and port from destination address, -in the format -.Dq address:port -or the path to a UNIX socket in the format -.Dq unix:/path . -.It Ic link-greeting : Ar hostname -This event is generated upon display of the server banner. -.Pp -.Ar hostname -contains the hostname displayed in the banner. -.It Ic link-identify : Ar method identity -This event is generated upon -.Dq HELO -or -.Dq EHLO -command from the client. -.Pp -.Ar method -contains the string -.Dq HELO -or -.Dq EHLO -indicating the method used by the client. -.Pp -.Ar identity -contains the identity provided by the client. -.It Ic link-tls : Ar tls-string -This event is generated upon successful negotiation of TLS. -.Pp -.Ar tls-string -contains a colon-separated list of TLS properties including the TLS version, -the cipher suite used by the session and the cipher strenght in bits. -.It Ic link-disconnect -This event is generated upon disconnection of the client. -.It Ic link-auth : Ar username result -This event is generated upon authentication attempt of the client. -.Pp -.Ar username -contains the username used for the authentication attempt. -.Pp -.Ar result -contains the string -.Dq pass , -.Dq fail -or -.Dq error -depending on the result of the authentication attempt. -.It Ic tx-reset : Op message-id -This event is generated when a transaction is reset. -.Pp -If reset happend while in a transaction, -.Ar message-id -contains the identifier of the transaction being reset. -.It Ic tx-begin : Ar message-id -This event is generated when a transaction is initiated. -.Pp -.Ar message-id -contains the identifier for the transaction. -.It Ic tx-mail : Ar message-id Ar result address -This event is generated when client emits -.Dq MAIL FROM . -.Pp -.Ar message-id -contains the identifier for the transaction. -.Pp -.Ar result -contains -.Dq ok -if the sender was accepted, -.Dq permfail -if it was rejected -or -.Dq tempfail -if it was rejected for a transient error. -.Pp -.Ar address -contains the e-mail address of the sender. -The address is normalized and sanitized, -the protocol -.Dq < -and -.Dq > -are removed and so are parameters to -.Dq MAIL FROM . -.It Ic tx-rcpt : Ar message-id Ar result address -This event is generated when client emits -.Dq RCPT TO . -.Pp -.Ar message-id -contains the identifier for the transaction. -.Pp -.Ar result -contains -.Dq ok -if the recipient was accepted, -.Dq permfail -if it was rejected -or -.Dq tempfail -if it was rejected for a transient error. -.Pp -.Ar address -contains the e-mail address of the recipient. -The address is normalized and sanitized, -the protocol -.Dq < -and -.Dq > -are removed and so are parameters to -.Dq RCPT TO . -.It Ic tx-envelope : Ar message-id Ar envelope-id -This event is generated when an envelope is accepted. -.Pp -.Ar envelope-id -contains the unique identifier for the envelope. -.It Ic tx-data : Ar message-id Ar result -This event is generated when client has emitted -.Dq DATA . -.Pp -.Ar message-id -contains the unique identifier for the transaction. -.Pp -.Ar result -contains -.Dq ok -if server accepted to process the message, -.Dq permfail -if it has not accepted and -.Dq tempfail -if a transient error is preventing the processing of message. -.It Ic tx-commit : Ar message-id Ar message-size -This event is generated when a transaction has been accepted by the server. -.Pp -.Ar message-id -contains the unique identifier for the SMTP transaction. -.Pp -.Ar message-size -contains the size of the message submitted in the -.Dq DATA -phase of the SMTP transaction. -.It Ic tx-rollback : Ar message-id -This event is generated when a transaction has been rejected by the server. -.Pp -.Ar message-id -contains the unique identifier for the SMTP transaction. -.It Ic protocol-client : Ar command -This event is generated for every command submitted by the client. -It contains the raw command as received by the server. -.Pp -.Ar command -contains the command emitted by the client to the server. -.It Ic protocol-server : Ar response -This event is generated for every response emitted by the server. -It contains the raw response as emitted by the server. -.Pp -.Ar response -contains the response emitted by the server to the client. -.It Ic filter-report : Ar filter-kind Ar name message -This event is generated when a filter emits a report. -.Pp -.Ar filter-kind may be either -.Dq builtin -or -.Dq proc -depending on if the filter is an -.Xr smtpd 8 -builtin filter or a proc filter implementing the API. -.Pp -.Ar name -is the name of the filter that generated the report. -.Pp -.Ar message -is a filter-specific message. -.It Ic filter-response : Ar phase response Op param -This event is generated when a filter responds to a filtering request. -.Pp -.Ar phase -contains the phase name for the request. -The phases are documented in the next section. -.Pp -.Ar response -contains the response of the filter to the request, -it is either one of -.Dq proceed , -.Dq report , -.Dq reject , -.Dq disconnect , -.Dq junk or -.Dq rewrite . -.Pp -If specified, -.Ar param -is the parameter to the response. -.It Ic timeout -This event is generated when a timeout happens for a session. -.El -.Sh FILTER REQUESTS -There is currently only one subsystem supported in the API: -smtp-in. -.Pp -The filter requests allow -.Xr smtpd 8 -to query -.Nm -about what to do with a session at a particular phase. -In addition, -they allow -.Nm -to alter the content of a message by adding, -modifying, -or suppressing lines of input in a way that is similar to what program like -.Xr sed 1 -or -.Xr grep 1 -would do. -.Pp -Each filter request is generated by -.Xr smtpd 8 -as a single line similar to the one below: -.Bd -literal -offset indent -filter|0.5|1576146008.006099|smtp-in|connect|7641df9771b4ed00|1ef1c203cc576e5d|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 -.Ed -.Pp -The format consists of a protocol prefix containing the stream, -the protocol version, -the timestamp, -the subsystem, -the filtering phase, -the unique session identifier and an opaque token separated by -.Dq | -that the filter should provide in its response: -.Bd -literal -offset indent -filter|0.5|1576146008.006099|smtp-in|connect|7641df9771b4ed00|1ef1c203cc576e5d -.Ed -.Pp -It is followed by a suffix containing the phase-specific parameters to the -filter request, -also separated by -.Dq | : -.Bd -literal -offset indent -mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 -.Ed -.Pp -Unlike with report events, -.Xr smtpd 8 -expects answers from filter requests and will not allow a session to move -forward before the filter has instructed -.Xr smtpd 8 -what to do with it. -.Pp -For all phases, -excepted -.Dq data-line , -the responses must follow the same construct, -a message type -.Dq filter-result , -followed by the unique session id, -the opaque token, -a decision and optional decision-specific parameters: -.Bd -literal -offset indent -filter-result|7641df9771b4ed00|1ef1c203cc576e5d|proceed -filter-result|7641df9771b4ed00|1ef1c203cc576e5d|reject|550 nope -.Ed -.Pp -The possible decisions to a -.Dq filter-result -message will be described below. -.Pp -For the -.Dq data-line -phase, -.Nm -are fed with a stream of lines corresponding to the message to filter, -and terminated by a single dot: -.Bd -literal -offset indent -filter|0.5|1576146008.006099|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|line 1 -filter|0.5|1576146008.006103|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|line 2 -filter|0.5|1576146008.006105|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|. -.Ed -.Pp -They are expected to produce an output stream similarly terminate by a single -dot. -A filter may inject, -suppress, -modify or echo back the lines it receives. -Ultimately, -.Xr smtpd 8 -will assume that the message consists of the output from -.Nm . -.Pp -Note that filters may be chained and the lines that are input into a filter -are the lines that are output from previous filter. -.Pp -The response to -.Dq data-line -requests use their own construct. -A -.Dq filter-dataline -prefix, -followed by the unique session identifier, -the opaque token and the output line as follows: -.Bd -literal -offset indent -filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|line 1 -filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|line 2 -filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|. -.Ed -.Pp -The list of events and event-specific parameters are provided here for smtp-in: -.Bl -tag -width Ds -.It Ic connect : Ar rdns fcrdns src dest -This request is emitted after connection, -before the banner is displayed. -.It Ic helo : Ar identity -This request is emitted after the client has emitted -.Dq HELO . -.It Ic ehlo : Ar identity -This request is emitted after the client has emitted -.Dq EHLO . -.It Ic starttls : Ar tls-string -This request is emitted after the client has requested -.Dq STARTTLS . -.It Ic auth : Ar auth -This request is emitted after the client has requested -.Dq AUTH . -.It Ic mail-from : Ar address -This request is emitted after the client has requested -.Dq MAIL FROM . -.It Ic rcpt-to : Ar address -This request is emitted after the client has requested -.Dq RCPT TO . -.It Ic data -This request is emitted after the client has requested -.Dq DATA . -.It Ic data-line : Ar line -This request is emitted for each line of input in the -.Dq DATA -phase. -The lines are raw dot-escaped SMTP DATA input, -terminated with a single dot. -.It Ic commit -This request is emitted after the final single dot is received. -.El -.Pp -For every filtering phase, -excepted -.Dq data-line , -the following decisions may be taken by a filter: -.Bl -tag -width Ds -.It Ic proceed -No action is taken, -session or transaction may be passed to the next filter. -.It Ic junk -The session or transaction is marked as Spam. -.Xr smtpd 8 -will prepend a -.Dq X-Spam -header to the message. -.It Ic reject Ar error -The command is rejected with the message -.Ar error . -The message must be a valid SMTP message including status code, -5xx or 4xx. -.Pp -Messages starting with a 5xx status result in a permanent failure, -those starting with a 4xx status result in a temporary failure. -.Pp -Messages starting with a 421 status will result in a client disconnect. -.It Ic disconnect Ar error -The client is disconnected with the message -.Ar error . -The message must be a valid SMTP message including status code, -5xx or 4xx. -.Pp -Messages starting with a 5xx status result in a permanent failure, -those starting with a 4xx status result in a temporary failure. -.It Ic rewrite Ar parameter -The command parameter is rewritten. -.Pp -This decision allows a filter to perform a rewrite of client-submitted -commands before they are processed by the SMTP engine. -.Ar parameter -is expected to be a valid SMTP parameter for the command. -.It Ic report Ar parameter -Generates a report with -.Ar parameter -for this filter. -.El -.\".Sh EXAMPLES -.\"This example filter written in -.\".Xr sh 1 -.\"will echo back... -.\".Bd -literal -offset indent -.\"XXX -.\".Ed -.\".Pp -.\"This example filter will filter... -.\".Bd -literal -offset indent -.\"XXX -.\".Ed -.\".Pp -.\"Note that libraries may provide a simpler interface to -.\".Nm -.\"that does not require implementing the protocol itself. -.\".Ed -.Sh SEE ALSO -.Xr smtpd 8 -.Sh HISTORY -.Nm -first appeared in -.Ox 6.6 . diff --git a/smtpd/smtpd.8 b/smtpd/smtpd.8 deleted file mode 100644 index e3429f07..00000000 --- a/smtpd/smtpd.8 +++ /dev/null @@ -1,167 +0,0 @@ -.\" $OpenBSD: smtpd.8,v 1.32 2017/01/03 22:11:39 jmc Exp $ -.\" -.\" Copyright (c) 2012, Eric Faurot -.\" Copyright (c) 2008, Gilles Chehade -.\" Copyright (c) 2008, Pierre-Yves Ritschard -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.Dd $Mdocdate: January 3 2017 $ -.Dt SMTPD 8 -.Os -.Sh NAME -.Nm smtpd -.Nd Simple Mail Transfer Protocol daemon -.Sh SYNOPSIS -.Nm -.Op Fl dFhnv -.Op Fl D Ar macro Ns = Ns Ar value -.Op Fl f Ar file -.Op Fl P Ar system -.Op Fl T Ar trace -.Sh DESCRIPTION -.Nm -is a Simple Mail Transfer Protocol -.Pq SMTP -daemon which can be used as a machine's primary mail system. -.Nm -can listen on a network interface and handle SMTP -transactions; it can also be fed messages through the standard -.Xr sendmail 8 -interface. -It can relay messages through remote mail transfer agents or store them -locally using either the mbox or maildir format. -This implementation supports SMTP as defined by RFC 5321 as well as several -extensions. -A running -.Nm -can be controlled through -.Xr smtpctl 8 . -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl D Ar macro Ns = Ns Ar value -Define -.Ar macro -to be set to -.Ar value -on the command line. -Overrides the definition of -.Ar macro -in the configuration file. -.It Fl d -Do not daemonize. -If this option is specified, -.Nm -will run in the foreground and log to -.Em stderr . -.It Fl F -Do not daemonize. -If this option is specified, -.Nm -will run in the foreground and log to -.Xr syslogd 8 . -.It Fl f Ar file -Specify an alternative configuration file. -.It Fl h -Display version and usage. -.It Fl n -Configtest mode. -Only check the configuration file for validity. -.It Fl P Ar system -Pause a specific subsystem at startup. -Normal operation can be resumed using -.Xr smtpctl 8 . -This option can be used multiple times. -The accepted values are: -.Pp -.Bl -tag -width "smtpXXX" -compact -.It mda -Do not schedule local deliveries. -.It mta -Do not schedule remote transfers. -.It smtp -Do not listen on SMTP sockets. -.El -.It Fl T Ar trace -Enables real-time tracing at startup. -Normal operation can be resumed using -.Xr smtpctl 8 . -This option can be used multiple times. -The accepted values are: -.Pp -.Bl -bullet -compact -.It -imsg -.It -io -.It -smtp (incoming sessions) -.It -filters -.It -transfer (outgoing sessions) -.It -bounce -.It -scheduler -.It -expand (aliases/virtual/forward expansion) -.It -lookup (user/credentials lookups) -.It -stat -.It -rules (matched by incoming sessions) -.It -mproc -.It -all -.El -.It Fl v -Produce more verbose output. -.El -.Sh FILES -.Bl -tag -width "/etc/mail/smtpd.confXXX" -compact -.It Pa /etc/mail/mailname -Alternate server name to use. -.It Pa /etc/mail/smtpd.conf -Default -.Nm -configuration file. -.It Pa /var/run/smtpd.sock -.Ux Ns -domain -socket used for communication with -.Xr smtpctl 8 . -.It Pa /var/spool/smtpd/ -Spool directories for mail during processing. -.It Pa ~/.forward -User email forwarding information. -.El -.Sh SEE ALSO -.Xr forward 5 , -.Xr smtpd.conf 5 , -.Xr mailwrapper 8 , -.Xr smtpctl 8 -.Sh STANDARDS -.Rs -.%A J. Klensin -.%D October 2008 -.%R RFC 5321 -.%T Simple Mail Transfer Protocol -.Re -.Sh HISTORY -The -.Nm -program first appeared in -.Ox 4.6 . diff --git a/smtpd/smtpd.c b/smtpd/smtpd.c deleted file mode 100644 index f18b1446..00000000 --- a/smtpd/smtpd.c +++ /dev/null @@ -1,2328 +0,0 @@ -/* $OpenBSD: smtpd.c,v 1.333 2020/05/06 16:03:30 millert Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2009 Jacek Masiulaniec - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include /* Needed for flock */ -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef BSD_AUTH -#include -#endif - -#ifdef USE_PAM -#if defined(HAVE_SECURITY_PAM_APPL_H) -#include -#elif defined (HAVE_PAM_PAM_APPL_H) -#include -#endif -#endif - -#ifdef HAVE_CRYPT_H -#include /* needed for crypt() */ -#endif -#include -#include -#include -#include -#include -#include /* needed for setgroups */ -#include -#include -#include -#include -#include -#ifdef HAVE_LOGIN_CAP_H -#include -#endif -#ifdef HAVE_PATHS_H -#include -#endif -#include -#include -#include -#ifdef HAVE_SHADOW_H -#include /* needed for getspnam() */ -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UTIL_H -#include -#endif - -#include -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - -extern char *__progname; - -#define SMTPD_MAXARG 32 - -static void parent_imsg(struct mproc *, struct imsg *); -static void usage(void); -static int smtpd(void); -static void parent_shutdown(void); -static void parent_send_config(int, short, void *); -static void parent_send_config_lka(void); -static void parent_send_config_pony(void); -static void parent_send_config_ca(void); -static void parent_sig_handler(int, short, void *); -static void forkmda(struct mproc *, uint64_t, struct deliver *); -static int parent_forward_open(char *, char *, uid_t, gid_t); -static struct child *child_add(pid_t, int, const char *); -static struct mproc *start_child(int, char **, char *); -static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int); -static void setup_peers(struct mproc *, struct mproc *); -static void setup_done(struct mproc *); -static void setup_proc(void); -static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int); -static int imsg_wait(struct imsgbuf *, struct imsg *, int); - -static void offline_scan(int, short, void *); -static int offline_add(char *, uid_t, gid_t); -static void offline_done(void); -static int offline_enqueue(char *, uid_t, gid_t); - -static void purge_task(void); -static int parent_auth_user(const char *, const char *); -static void load_pki_tree(void); -static void load_pki_keys(void); - -static void fork_filter_processes(void); -static void fork_filter_process(const char *, const char *, const char *, const char *, const char *, uint32_t); - -enum child_type { - CHILD_DAEMON, - CHILD_MDA, - CHILD_PROCESSOR, - CHILD_ENQUEUE_OFFLINE, -}; - -struct child { - pid_t pid; - enum child_type type; - const char *title; - int mda_out; - uint64_t mda_id; - char *path; - char *cause; -}; - -struct offline { - TAILQ_ENTRY(offline) entry; - uid_t uid; - gid_t gid; - char *path; -}; - -#define OFFLINE_READMAX 20 -#define OFFLINE_QUEUEMAX 5 -static size_t offline_running = 0; -TAILQ_HEAD(, offline) offline_q; - -static struct event config_ev; -static struct event offline_ev; -static struct timeval offline_timeout; - -static pid_t purge_pid = -1; - -extern char **environ; -void (*imsg_callback)(struct mproc *, struct imsg *); - -enum smtp_proc_type smtpd_process; - -struct smtpd *env = NULL; - -struct mproc *p_control = NULL; -struct mproc *p_lka = NULL; -struct mproc *p_parent = NULL; -struct mproc *p_queue = NULL; -struct mproc *p_scheduler = NULL; -struct mproc *p_pony = NULL; -struct mproc *p_ca = NULL; - -const char *backend_queue = "fs"; -const char *backend_scheduler = "ramqueue"; -const char *backend_stat = "ram"; - -int profiling = 0; -int debug = 0; -int foreground = 0; -int control_socket = -1; - -struct tree children; - -/* Saved arguments to main(). */ -char **saved_argv; -int saved_argc; - -static void -parent_imsg(struct mproc *p, struct imsg *imsg) -{ - struct forward_req *fwreq; - struct filter_proc *processor; - struct deliver deliver; - struct child *c; - struct msg m; - const void *data; - const char *username, *password, *cause, *procname; - uint64_t reqid; - size_t sz; - void *i; - int fd, n, v, ret; - - if (imsg == NULL) - fatalx("process %s socket closed", p->name); - - switch (imsg->hdr.type) { - case IMSG_LKA_OPEN_FORWARD: - CHECK_IMSG_DATA_SIZE(imsg, sizeof *fwreq); - fwreq = imsg->data; - fd = parent_forward_open(fwreq->user, fwreq->directory, - fwreq->uid, fwreq->gid); - fwreq->status = 0; - if (fd == -1 && errno != ENOENT) { - if (errno == EAGAIN) - fwreq->status = -1; - } - else - fwreq->status = 1; - m_compose(p, IMSG_LKA_OPEN_FORWARD, 0, 0, fd, - fwreq, sizeof *fwreq); - return; - - case IMSG_LKA_AUTHENTICATE: - /* - * If we reached here, it means we want root to lookup - * system user. - */ - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &username); - m_get_string(&m, &password); - m_end(&m); - - ret = parent_auth_user(username, password); - - m_create(p, IMSG_LKA_AUTHENTICATE, 0, 0, -1); - m_add_id(p, reqid); - m_add_int(p, ret); - m_close(p); - return; - - case IMSG_MDA_FORK: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_data(&m, &data, &sz); - m_end(&m); - if (sz != sizeof(deliver)) - fatalx("expected deliver"); - memmove(&deliver, data, sz); - forkmda(p, reqid, &deliver); - return; - - case IMSG_MDA_KILL: - m_msg(&m, imsg); - m_get_id(&m, &reqid); - m_get_string(&m, &cause); - m_end(&m); - - i = NULL; - while ((n = tree_iter(&children, &i, NULL, (void**)&c))) - if (c->type == CHILD_MDA && - c->mda_id == reqid && - c->cause == NULL) - break; - if (!n) { - log_debug("debug: smtpd: " - "kill request: proc not found"); - return; - } - - c->cause = xstrdup(cause); - log_debug("debug: smtpd: kill requested for %u: %s", - c->pid, c->cause); - kill(c->pid, SIGTERM); - return; - - case IMSG_CTL_VERBOSE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - log_trace_verbose(v); - return; - - case IMSG_CTL_PROFILE: - m_msg(&m, imsg); - m_get_int(&m, &v); - m_end(&m); - profiling = v; - return; - - case IMSG_LKA_PROCESSOR_ERRFD: - m_msg(&m, imsg); - m_get_string(&m, &procname); - m_end(&m); - - processor = dict_xget(env->sc_filter_processes_dict, procname); - m_create(p_lka, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, processor->errfd); - m_add_string(p_lka, procname); - m_close(p_lka); - return; - } - - errx(1, "parent_imsg: unexpected %s imsg from %s", - imsg_to_str(imsg->hdr.type), proc_title(p->proc)); -} - -static void -usage(void) -{ - extern char *__progname; - - fprintf(stderr, "usage: %s [-dFhnv] [-D macro=value] " - "[-f file] [-P system] [-T trace]\n", __progname); - exit(1); -} - -static void -parent_shutdown(void) -{ - pid_t pid; - - mproc_clear(p_ca); - mproc_clear(p_pony); - mproc_clear(p_control); - mproc_clear(p_lka); - mproc_clear(p_scheduler); - mproc_clear(p_queue); - - do { - pid = waitpid(WAIT_MYPGRP, NULL, 0); - } while (pid != -1 || (pid == -1 && errno == EINTR)); - - unlink(SMTPD_SOCKET); - - log_info("Exiting"); - exit(0); -} - -static void -parent_send_config(int fd, short event, void *p) -{ - parent_send_config_lka(); - parent_send_config_pony(); - parent_send_config_ca(); - purge_config(PURGE_PKI); -} - -static void -parent_send_config_pony(void) -{ - log_debug("debug: parent_send_config: configuring pony process"); - m_compose(p_pony, IMSG_CONF_START, 0, 0, -1, NULL, 0); - m_compose(p_pony, IMSG_CONF_END, 0, 0, -1, NULL, 0); -} - -void -parent_send_config_lka() -{ - log_debug("debug: parent_send_config_ruleset: reloading"); - m_compose(p_lka, IMSG_CONF_START, 0, 0, -1, NULL, 0); - m_compose(p_lka, IMSG_CONF_END, 0, 0, -1, NULL, 0); -} - -static void -parent_send_config_ca(void) -{ - log_debug("debug: parent_send_config: configuring ca process"); - m_compose(p_ca, IMSG_CONF_START, 0, 0, -1, NULL, 0); - m_compose(p_ca, IMSG_CONF_END, 0, 0, -1, NULL, 0); -} - -static void -parent_sig_handler(int sig, short event, void *p) -{ - struct child *child; - int status, fail; - pid_t pid; - char *cause; - - switch (sig) { - case SIGTERM: - case SIGINT: - log_debug("debug: got signal %d", sig); - parent_shutdown(); - /* NOT REACHED */ - - case SIGCHLD: - do { - int len; - enum mda_resp_status mda_status; - int mda_sysexit; - - pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - continue; - - fail = 0; - if (WIFSIGNALED(status)) { - fail = 1; - len = asprintf(&cause, "terminated; signal %d", - WTERMSIG(status)); - mda_status = MDA_TEMPFAIL; - mda_sysexit = 0; - } else if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - fail = 1; - len = asprintf(&cause, - "exited abnormally"); - mda_sysexit = WEXITSTATUS(status); - if (mda_sysexit == EX_OSERR || - mda_sysexit == EX_TEMPFAIL) - mda_status = MDA_TEMPFAIL; - else - mda_status = MDA_PERMFAIL; - } else { - len = asprintf(&cause, "exited okay"); - mda_status = MDA_OK; - mda_sysexit = 0; - } - } else - /* WIFSTOPPED or WIFCONTINUED */ - continue; - - if (len == -1) - fatal("asprintf"); - - if (pid == purge_pid) - purge_pid = -1; - - child = tree_pop(&children, pid); - if (child == NULL) - goto skip; - - switch (child->type) { - case CHILD_PROCESSOR: - if (fail) { - log_warnx("warn: lost processor: %s %s", - child->title, cause); - parent_shutdown(); - } - break; - - case CHILD_DAEMON: - if (fail) - log_warnx("warn: lost child: %s %s", - child->title, cause); - break; - - case CHILD_MDA: - if (WIFSIGNALED(status) && - WTERMSIG(status) == SIGALRM) { - char *tmp; - if (asprintf(&tmp, - "terminated; timeout") != -1) { - free(cause); - cause = tmp; - } - } - else if (child->cause && - WIFSIGNALED(status) && - WTERMSIG(status) == SIGTERM) { - free(cause); - cause = child->cause; - child->cause = NULL; - } - free(child->cause); - log_debug("debug: smtpd: mda process done " - "for session %016"PRIx64 ": %s", - child->mda_id, cause); - - m_create(p_pony, IMSG_MDA_DONE, 0, 0, - child->mda_out); - m_add_id(p_pony, child->mda_id); - m_add_int(p_pony, mda_status); - m_add_int(p_pony, mda_sysexit); - m_add_string(p_pony, cause); - m_close(p_pony); - - break; - - case CHILD_ENQUEUE_OFFLINE: - if (fail) - log_warnx("warn: smtpd: " - "couldn't enqueue offline " - "message %s; smtpctl %s", - child->path, cause); - else - unlink(child->path); - free(child->path); - offline_done(); - break; - - default: - fatalx("smtpd: unexpected child type"); - } - free(child); - skip: - free(cause); - } while (pid > 0 || (pid == -1 && errno == EINTR)); - - break; - default: - fatalx("smtpd: unexpected signal"); - } -} - -int -main(int argc, char *argv[]) -{ - int c, i; - int opts, flags; - const char *conffile = CONF_FILE; - int save_argc = argc; - char **save_argv = argv; - char *rexec = NULL; - struct smtpd *conf; - -#ifndef HAVE___PROGNAME - __progname = ssh_get_progname(argv[0]); -#endif - -#ifndef HAVE_SETPROCTITLE - /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ - saved_argc = argc; - saved_argv = xcalloc(argc + 1, sizeof(*saved_argv)); - for (i = 0; i < argc; i++) - saved_argv[i] = xstrdup(argv[i]); - saved_argv[i] = NULL; - - /* Prepare for later setproctitle emulation */ - compat_init_setproctitle(argc, argv); - argv = saved_argv; - - /* this is to work around GNU getopt + portable setproctitle() fuckery */ - save_argc = saved_argc; - save_argv = saved_argv; -#endif - - if ((conf = config_default()) == NULL) - err(1, NULL); - - env = conf; - - flags = 0; - opts = 0; - debug = 0; - tracing = 0; - - log_init(1, LOG_MAIL); - - TAILQ_INIT(&offline_q); - - while ((c = getopt(argc, argv, "B:dD:hnP:f:FT:vx:")) != -1) { - switch (c) { - case 'B': - if (strstr(optarg, "queue=") == optarg) - backend_queue = strchr(optarg, '=') + 1; - else if (strstr(optarg, "scheduler=") == optarg) - backend_scheduler = strchr(optarg, '=') + 1; - else if (strstr(optarg, "stat=") == optarg) - backend_stat = strchr(optarg, '=') + 1; - else - log_warnx("warn: " - "invalid backend specifier %s", - optarg); - break; - case 'd': - foreground = 1; - foreground_log = 1; - break; - case 'D': - if (cmdline_symset(optarg) < 0) - log_warnx("warn: " - "could not parse macro definition %s", - optarg); - break; - case 'h': - log_info("version: " SMTPD_NAME " " SMTPD_VERSION); - usage(); - break; - case 'n': - debug = 2; - opts |= SMTPD_OPT_NOACTION; - break; - case 'f': - conffile = optarg; - break; - case 'F': - foreground = 1; - break; - - case 'T': - if (!strcmp(optarg, "imsg")) - tracing |= TRACE_IMSG; - else if (!strcmp(optarg, "io")) - tracing |= TRACE_IO; - else if (!strcmp(optarg, "smtp")) - tracing |= TRACE_SMTP; - else if (!strcmp(optarg, "filters")) - tracing |= TRACE_FILTERS; - else if (!strcmp(optarg, "mta") || - !strcmp(optarg, "transfer")) - tracing |= TRACE_MTA; - else if (!strcmp(optarg, "bounce") || - !strcmp(optarg, "bounces")) - tracing |= TRACE_BOUNCE; - else if (!strcmp(optarg, "scheduler")) - tracing |= TRACE_SCHEDULER; - else if (!strcmp(optarg, "lookup")) - tracing |= TRACE_LOOKUP; - else if (!strcmp(optarg, "stat") || - !strcmp(optarg, "stats")) - tracing |= TRACE_STAT; - else if (!strcmp(optarg, "rules")) - tracing |= TRACE_RULES; - else if (!strcmp(optarg, "mproc")) - tracing |= TRACE_MPROC; - else if (!strcmp(optarg, "expand")) - tracing |= TRACE_EXPAND; - else if (!strcmp(optarg, "table") || - !strcmp(optarg, "tables")) - tracing |= TRACE_TABLES; - else if (!strcmp(optarg, "queue")) - tracing |= TRACE_QUEUE; - else if (!strcmp(optarg, "all")) - tracing |= ~TRACE_DEBUG; - else if (!strcmp(optarg, "profstat")) - profiling |= PROFILE_TOSTAT; - else if (!strcmp(optarg, "profile-imsg")) - profiling |= PROFILE_IMSG; - else if (!strcmp(optarg, "profile-queue")) - profiling |= PROFILE_QUEUE; - else - log_warnx("warn: unknown trace flag \"%s\"", - optarg); - break; - case 'P': - if (!strcmp(optarg, "smtp")) - flags |= SMTPD_SMTP_PAUSED; - else if (!strcmp(optarg, "mta")) - flags |= SMTPD_MTA_PAUSED; - else if (!strcmp(optarg, "mda")) - flags |= SMTPD_MDA_PAUSED; - break; - case 'v': - tracing |= TRACE_DEBUG; - break; - case 'x': - rexec = optarg; - break; - default: - usage(); - } - } - - argv += optind; - argc -= optind; - - if (argc || *argv) - usage(); - - env->sc_opts |= opts; - - ssl_init(); - - if (parse_config(conf, conffile, opts)) - exit(1); - - if (RAND_status() != 1) - errx(1, "PRNG is not seeded"); - - if (strlcpy(env->sc_conffile, conffile, PATH_MAX) - >= PATH_MAX) - errx(1, "config file exceeds PATH_MAX"); - - if (env->sc_opts & SMTPD_OPT_NOACTION) { - if (env->sc_queue_key && - crypto_setup(env->sc_queue_key, - strlen(env->sc_queue_key)) == 0) { - fatalx("crypto_setup:" - "invalid key for queue encryption"); - } - load_pki_tree(); - load_pki_keys(); - fprintf(stderr, "configuration OK\n"); - exit(0); - } - - env->sc_flags |= flags; - - /* check for root privileges */ - if (geteuid()) - errx(1, "need root privileges"); - - log_init(foreground_log, LOG_MAIL); - log_trace_verbose(tracing); - load_pki_tree(); - load_pki_keys(); - - log_debug("debug: using \"%s\" queue backend", backend_queue); - log_debug("debug: using \"%s\" scheduler backend", backend_scheduler); - log_debug("debug: using \"%s\" stat backend", backend_stat); - - if (env->sc_hostname[0] == '\0') - errx(1, "machine does not have a hostname set"); - env->sc_uptime = time(NULL); - - if (rexec == NULL) { - smtpd_process = PROC_PARENT; - - if (env->sc_queue_flags & QUEUE_ENCRYPTION) { - if (env->sc_queue_key == NULL) { - char *password; - - password = getpass("queue key: "); - if (password == NULL) - err(1, "getpass"); - - env->sc_queue_key = strdup(password); - explicit_bzero(password, strlen(password)); - if (env->sc_queue_key == NULL) - err(1, "strdup"); - } - else { - char *buf = NULL; - size_t sz = 0; - ssize_t len; - - if (strcasecmp(env->sc_queue_key, "stdin") == 0) { - if ((len = getline(&buf, &sz, stdin)) == -1) - err(1, "getline"); - if (buf[len - 1] == '\n') - buf[len - 1] = '\0'; - env->sc_queue_key = buf; - } - } - } - - log_info("info: %s %s starting", SMTPD_NAME, SMTPD_VERSION); - - if (!foreground) - if (daemon(0, 0) == -1) - err(1, "failed to daemonize"); - - /* setup all processes */ - - p_ca = start_child(save_argc, save_argv, "ca"); - p_ca->proc = PROC_CA; - - p_control = start_child(save_argc, save_argv, "control"); - p_control->proc = PROC_CONTROL; - - p_lka = start_child(save_argc, save_argv, "lka"); - p_lka->proc = PROC_LKA; - - p_pony = start_child(save_argc, save_argv, "pony"); - p_pony->proc = PROC_PONY; - - p_queue = start_child(save_argc, save_argv, "queue"); - p_queue->proc = PROC_QUEUE; - - p_scheduler = start_child(save_argc, save_argv, "scheduler"); - p_scheduler->proc = PROC_SCHEDULER; - - setup_peers(p_control, p_ca); - setup_peers(p_control, p_lka); - setup_peers(p_control, p_pony); - setup_peers(p_control, p_queue); - setup_peers(p_control, p_scheduler); - setup_peers(p_pony, p_ca); - setup_peers(p_pony, p_lka); - setup_peers(p_pony, p_queue); - setup_peers(p_queue, p_lka); - setup_peers(p_queue, p_scheduler); - - if (env->sc_queue_key) { - if (imsg_compose(&p_queue->imsgbuf, IMSG_SETUP_KEY, 0, - 0, -1, env->sc_queue_key, strlen(env->sc_queue_key) - + 1) == -1) - fatal("imsg_compose"); - if (imsg_flush(&p_queue->imsgbuf) == -1) - fatal("imsg_flush"); - } - - setup_done(p_ca); - setup_done(p_control); - setup_done(p_lka); - setup_done(p_pony); - setup_done(p_queue); - setup_done(p_scheduler); - - log_debug("smtpd: setup done"); - - return smtpd(); - } - - if (!strcmp(rexec, "ca")) { - smtpd_process = PROC_CA; - setup_proc(); - - return ca(); - } - - else if (!strcmp(rexec, "control")) { - smtpd_process = PROC_CONTROL; - setup_proc(); - - /* the control socket ensures that only one smtpd instance is running */ - control_socket = control_create_socket(); - - env->sc_stat = stat_backend_lookup(backend_stat); - if (env->sc_stat == NULL) - errx(1, "could not find stat backend \"%s\"", backend_stat); - - return control(); - } - - else if (!strcmp(rexec, "lka")) { - smtpd_process = PROC_LKA; - setup_proc(); - - return lka(); - } - - else if (!strcmp(rexec, "pony")) { - smtpd_process = PROC_PONY; - setup_proc(); - - return pony(); - } - - else if (!strcmp(rexec, "queue")) { - smtpd_process = PROC_QUEUE; - setup_proc(); - - if (env->sc_queue_flags & QUEUE_COMPRESSION) - env->sc_comp = compress_backend_lookup("gzip"); - - if (!queue_init(backend_queue, 1)) - errx(1, "could not initialize queue backend"); - - return queue(); - } - - else if (!strcmp(rexec, "scheduler")) { - smtpd_process = PROC_SCHEDULER; - setup_proc(); - - for (i = 0; i < MAX_BOUNCE_WARN; i++) { - if (env->sc_bounce_warn[i] == 0) - break; - log_debug("debug: bounce warning after %s", - duration_to_text(env->sc_bounce_warn[i])); - } - - return scheduler(); - } - - fatalx("bad rexec: %s", rexec); - - return (1); -} - -static struct mproc * -start_child(int save_argc, char **save_argv, char *rexec) -{ - struct mproc *p; - char *argv[SMTPD_MAXARG]; - int sp[2], argc = 0; - pid_t pid; - - if (save_argc >= SMTPD_MAXARG - 2) - fatalx("too many arguments"); - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) - fatal("socketpair"); - - io_set_nonblocking(sp[0]); - io_set_nonblocking(sp[1]); - - switch (pid = fork()) { - case -1: - fatal("%s: fork", save_argv[0]); - case 0: - break; - default: - close(sp[0]); - p = calloc(1, sizeof(*p)); - if (p == NULL) - fatal("calloc"); - if((p->name = strdup(rexec)) == NULL) - fatal("strdup"); - mproc_init(p, sp[1]); - p->pid = pid; - p->handler = parent_imsg; - return p; - } - - if (sp[0] != 3) { - if (dup2(sp[0], 3) == -1) - fatal("%s: dup2", rexec); - } else if (fcntl(sp[0], F_SETFD, 0) == -1) - fatal("%s: fcntl", rexec); - - xclosefrom(4); - - for (argc = 0; argc < save_argc; argc++) - argv[argc] = save_argv[argc]; - argv[argc++] = "-x"; - argv[argc++] = rexec; - argv[argc++] = NULL; - - execvp(argv[0], argv); - fatal("%s: execvp", rexec); -} - -static void -setup_peers(struct mproc *a, struct mproc *b) -{ - int sp[2]; - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) - fatal("socketpair"); - - io_set_nonblocking(sp[0]); - io_set_nonblocking(sp[1]); - - if (imsg_compose(&a->imsgbuf, IMSG_SETUP_PEER, b->proc, b->pid, sp[0], - NULL, 0) == -1) - fatal("imsg_compose"); - if (imsg_flush(&a->imsgbuf) == -1) - fatal("imsg_flush"); - - if (imsg_compose(&b->imsgbuf, IMSG_SETUP_PEER, a->proc, a->pid, sp[1], - NULL, 0) == -1) - fatal("imsg_compose"); - if (imsg_flush(&b->imsgbuf) == -1) - fatal("imsg_flush"); -} - -static void -setup_done(struct mproc *p) -{ - struct imsg imsg; - - if (imsg_compose(&p->imsgbuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1) - fatal("imsg_compose"); - if (imsg_flush(&p->imsgbuf) == -1) - fatal("imsg_flush"); - - if (imsg_wait(&p->imsgbuf, &imsg, 10000) == -1) - fatal("imsg_wait"); - - if (imsg.hdr.type != IMSG_SETUP_DONE) - fatalx("expect IMSG_SETUP_DONE"); - - log_debug("setup_done: %s[%d] done", p->name, p->pid); - - imsg_free(&imsg); -} - -static void -setup_proc(void) -{ - struct imsgbuf *ibuf; - struct imsg imsg; - int setup = 1; - - log_procinit(proc_title(smtpd_process)); - - p_parent = calloc(1, sizeof(*p_parent)); - if (p_parent == NULL) - fatal("calloc"); - if((p_parent->name = strdup("parent")) == NULL) - fatal("strdup"); - p_parent->proc = PROC_PARENT; - p_parent->handler = imsg_dispatch; - mproc_init(p_parent, 3); - - ibuf = &p_parent->imsgbuf; - - while (setup) { - if (imsg_wait(ibuf, &imsg, 10000) == -1) - fatal("imsg_wait"); - - switch (imsg.hdr.type) { - case IMSG_SETUP_KEY: - env->sc_queue_key = strdup(imsg.data); - break; - case IMSG_SETUP_PEER: - setup_peer(imsg.hdr.peerid, imsg.hdr.pid, imsg.fd); - break; - case IMSG_SETUP_DONE: - setup = 0; - break; - default: - fatal("bad imsg %d", imsg.hdr.type); - } - imsg_free(&imsg); - } - - if (imsg_compose(ibuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1) - fatal("imsg_compose"); - - if (imsg_flush(ibuf) == -1) - fatal("imsg_flush"); - - log_debug("setup_proc: %s done", proc_title(smtpd_process)); -} - -static struct mproc * -setup_peer(enum smtp_proc_type proc, pid_t pid, int sock) -{ - struct mproc *p, **pp; - - log_debug("setup_peer: %s -> %s[%u] fd=%d", proc_title(smtpd_process), - proc_title(proc), pid, sock); - - if (sock == -1) - fatalx("peer socket not received"); - - switch (proc) { - case PROC_LKA: - pp = &p_lka; - break; - case PROC_QUEUE: - pp = &p_queue; - break; - case PROC_CONTROL: - pp = &p_control; - break; - case PROC_SCHEDULER: - pp = &p_scheduler; - break; - case PROC_PONY: - pp = &p_pony; - break; - case PROC_CA: - pp = &p_ca; - break; - default: - fatalx("unknown peer"); - } - - if (*pp) - fatalx("peer already set"); - - p = calloc(1, sizeof(*p)); - if (p == NULL) - fatal("calloc"); - if((p->name = strdup(proc_title(proc))) == NULL) - fatal("strdup"); - mproc_init(p, sock); - p->pid = pid; - p->proc = proc; - p->handler = imsg_dispatch; - - *pp = p; - - return p; -} - -static int -imsg_wait(struct imsgbuf *ibuf, struct imsg *imsg, int timeout) -{ - struct pollfd pfd[1]; - ssize_t n; - - pfd[0].fd = ibuf->fd; - pfd[0].events = POLLIN; - - while (1) { - if ((n = imsg_get(ibuf, imsg)) == -1) - return -1; - if (n) - return 1; - - n = poll(pfd, 1, timeout); - if (n == -1) - return -1; - if (n == 0) { - errno = ETIMEDOUT; - return -1; - } - - if (((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) || n == 0) - return -1; - } -} - -int -smtpd(void) { - struct event ev_sigint; - struct event ev_sigterm; - struct event ev_sigchld; - struct event ev_sighup; - struct timeval tv; - - imsg_callback = parent_imsg; - - tree_init(&children); - - child_add(p_queue->pid, CHILD_DAEMON, proc_title(PROC_QUEUE)); - child_add(p_control->pid, CHILD_DAEMON, proc_title(PROC_CONTROL)); - child_add(p_lka->pid, CHILD_DAEMON, proc_title(PROC_LKA)); - child_add(p_scheduler->pid, CHILD_DAEMON, proc_title(PROC_SCHEDULER)); - child_add(p_pony->pid, CHILD_DAEMON, proc_title(PROC_PONY)); - child_add(p_ca->pid, CHILD_DAEMON, proc_title(PROC_CA)); - - event_init(); - - signal_set(&ev_sigint, SIGINT, parent_sig_handler, NULL); - signal_set(&ev_sigterm, SIGTERM, parent_sig_handler, NULL); - signal_set(&ev_sigchld, SIGCHLD, parent_sig_handler, NULL); - signal_set(&ev_sighup, SIGHUP, parent_sig_handler, NULL); - signal_add(&ev_sigint, NULL); - signal_add(&ev_sigterm, NULL); - signal_add(&ev_sigchld, NULL); - signal_add(&ev_sighup, NULL); - signal(SIGPIPE, SIG_IGN); - - config_peer(PROC_CONTROL); - config_peer(PROC_LKA); - config_peer(PROC_QUEUE); - config_peer(PROC_CA); - config_peer(PROC_PONY); - - evtimer_set(&config_ev, parent_send_config, NULL); - memset(&tv, 0, sizeof(tv)); - evtimer_add(&config_ev, &tv); - - /* defer offline scanning for a second */ - evtimer_set(&offline_ev, offline_scan, NULL); - offline_timeout.tv_sec = 1; - offline_timeout.tv_usec = 0; - evtimer_add(&offline_ev, &offline_timeout); - - if (pidfile(NULL) < 0) - err(1, "pidfile"); - - fork_filter_processes(); - - purge_task(); - -#if HAVE_PLEDGE - if (pledge("stdio rpath wpath cpath fattr tmppath " - "getpw sendfd proc exec id inet chown unix", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - fatalx("exited event loop"); - - return (0); -} - -static void -load_pki_tree(void) -{ - struct pki *pki; - struct ca *sca; - const char *k; - void *iter_dict; - - log_debug("debug: init ssl-tree"); - iter_dict = NULL; - while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { - log_debug("info: loading pki information for %s", k); - if (pki->pki_cert_file == NULL) - fatalx("load_pki_tree: missing certificate file"); - if (pki->pki_key_file == NULL) - fatalx("load_pki_tree: missing key file"); - - if (!ssl_load_certificate(pki, pki->pki_cert_file)) - fatalx("load_pki_tree: failed to load certificate file"); - } - - log_debug("debug: init ca-tree"); - iter_dict = NULL; - while (dict_iter(env->sc_ca_dict, &iter_dict, &k, (void **)&sca)) { - log_debug("info: loading CA information for %s", k); - if (!ssl_load_cafile(sca, sca->ca_cert_file)) - fatalx("load_pki_tree: failed to load CA file"); - } -} - -void -load_pki_keys(void) -{ - struct pki *pki; - const char *k; - void *iter_dict; - - log_debug("debug: init ssl-tree"); - iter_dict = NULL; - while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { - log_debug("info: loading pki keys for %s", k); - - if (!ssl_load_keyfile(pki, pki->pki_key_file, k)) - fatalx("load_pki_keys: failed to load key file"); - } -} - -int -fork_proc_backend(const char *key, const char *conf, const char *procname) -{ - pid_t pid; - int sp[2]; - char path[PATH_MAX]; - char name[PATH_MAX]; - char *arg; - - if (strlcpy(name, conf, sizeof(name)) >= sizeof(name)) { - log_warnx("warn: %s-proc: conf too long", key); - return (0); - } - - arg = strchr(name, ':'); - if (arg) - *arg++ = '\0'; - - if (snprintf(path, sizeof(path), PATH_LIBEXEC "/%s-%s", key, name) >= - (ssize_t)sizeof(path)) { - log_warn("warn: %s-proc: exec path too long", key); - return (-1); - } - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) { - log_warn("warn: %s-proc: socketpair", key); - return (-1); - } - - if ((pid = fork()) == -1) { - log_warn("warn: %s-proc: fork", key); - close(sp[0]); - close(sp[1]); - return (-1); - } - - if (pid == 0) { - /* child process */ - dup2(sp[0], STDIN_FILENO); - closefrom(STDERR_FILENO + 1); - - if (procname == NULL) - procname = name; - - execl(path, procname, arg, (char *)NULL); - err(1, "execl: %s", path); - } - - /* parent process */ - close(sp[0]); - - return (sp[1]); -} - -struct child * -child_add(pid_t pid, int type, const char *title) -{ - struct child *child; - - if ((child = calloc(1, sizeof(*child))) == NULL) - fatal("smtpd: child_add: calloc"); - - child->pid = pid; - child->type = type; - child->title = title; - - tree_xset(&children, pid, child); - - return (child); -} - -static void -purge_task(void) -{ - struct passwd *pw; - DIR *d; - int n; - uid_t uid; - gid_t gid; - - n = 0; - if ((d = opendir(PATH_SPOOL PATH_PURGE))) { - while (readdir(d) != NULL) - n++; - closedir(d); - } else - log_warn("warn: purge_task: opendir"); - - if (n > 2) { - switch (purge_pid = fork()) { - case -1: - log_warn("warn: purge_task: fork"); - break; - case 0: - if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL) - fatalx("unknown user " SMTPD_QUEUE_USER); - if (chroot(PATH_SPOOL PATH_PURGE) == -1) - fatal("smtpd: chroot"); - if (chdir("/") == -1) - fatal("smtpd: chdir"); - uid = pw->pw_uid; - gid = pw->pw_gid; - if (setgroups(1, &gid) || - setresgid(gid, gid, gid) || - setresuid(uid, uid, uid)) - fatal("smtpd: cannot drop privileges"); - rmtree("/", 1); - _exit(0); - break; - default: - break; - } - } -} - -static void -fork_filter_processes(void) -{ - const char *name; - void *iter; - const char *fn; - struct filter_config *fc; - struct filter_config *fcs; - struct filter_proc *fp; - size_t i; - - /* For each filter chain, assign the registered subsystem to subfilters */ - iter = NULL; - while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) { - if (fc->chain) { - for (i = 0; i < fc->chain_size; ++i) { - fcs = dict_xget(env->sc_filters_dict, fc->chain[i]); - fcs->filter_subsystem |= fc->filter_subsystem; - } - } - } - - /* For each filter, assign the registered subsystem to underlying proc */ - iter = NULL; - while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) { - if (fc->proc) { - fp = dict_xget(env->sc_filter_processes_dict, fc->proc); - fp->filter_subsystem |= fc->filter_subsystem; - } - } - - iter = NULL; - while (dict_iter(env->sc_filter_processes_dict, &iter, &name, (void **)&fp)) - fork_filter_process(name, fp->command, fp->user, fp->group, fp->chroot, fp->filter_subsystem); -} - -static void -fork_filter_process(const char *name, const char *command, const char *user, const char *group, const char *chroot_path, uint32_t subsystems) -{ - pid_t pid; - struct filter_proc *processor; - char buf; - int sp[2], errfd[2]; - struct passwd *pw; - struct group *gr; - char exec[_POSIX_ARG_MAX]; - int execr; - - if (user == NULL) - user = SMTPD_USER; - if ((pw = getpwnam(user)) == NULL) - err(1, "getpwnam"); - - if (group) { - if ((gr = getgrnam(group)) == NULL) - err(1, "getgrnam"); - } - else { - if ((gr = getgrgid(pw->pw_gid)) == NULL) - err(1, "getgrgid"); - } - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) - err(1, "socketpair"); - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, errfd) == -1) - err(1, "socketpair"); - - if ((pid = fork()) == -1) - err(1, "fork"); - - /* parent passes the child fd over to lka */ - if (pid > 0) { - processor = dict_xget(env->sc_filter_processes_dict, name); - processor->errfd = errfd[1]; - child_add(pid, CHILD_PROCESSOR, name); - close(sp[0]); - close(errfd[0]); - m_create(p_lka, IMSG_LKA_PROCESSOR_FORK, 0, 0, sp[1]); - m_add_string(p_lka, name); - m_add_u32(p_lka, (uint32_t)subsystems); - m_close(p_lka); - return; - } - - close(sp[1]); - close(errfd[1]); - dup2(sp[0], STDIN_FILENO); - dup2(sp[0], STDOUT_FILENO); - dup2(errfd[0], STDERR_FILENO); - - if (chroot_path) { - if (chroot(chroot_path) != 0 || chdir("/") != 0) - err(1, "chroot: %s", chroot_path); - } - - if (setgroups(1, &gr->gr_gid) || - setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - err(1, "fork_filter_process: cannot drop privileges"); - - xclosefrom(STDERR_FILENO + 1); - - if (setsid() < 0) - err(1, "setsid"); - if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || - signal(SIGINT, SIG_DFL) == SIG_ERR || - signal(SIGTERM, SIG_DFL) == SIG_ERR || - signal(SIGCHLD, SIG_DFL) == SIG_ERR || - signal(SIGHUP, SIG_DFL) == SIG_ERR) - err(1, "signal"); - - if (command[0] == '/') - execr = snprintf(exec, sizeof(exec), "exec %s", command); - else - execr = snprintf(exec, sizeof(exec), "exec %s/%s", - PATH_LIBEXEC, command); - if (execr >= (int) sizeof(exec)) - errx(1, "%s: exec path too long", name); - - /* - * Wait for lka to acknowledge that it received the fd. - * This prevents a race condition between the filter sending an error - * message, and exiting and lka not being able to log it because of - * SIGCHLD. - * (Ab)use read to determine if the fd is installed; since stderr is - * never going to be read from we can shutdown(2) the write-end in lka. - */ - if (read(STDERR_FILENO, &buf, 1) != 0) - errx(1, "lka didn't properly close write end of error socket"); - if (system(exec) == -1) - err(1, NULL); - - /* there's no successful exit from a processor */ - _exit(1); -} - -static void -forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) -{ - char ebuf[128], sfn[32]; - struct dispatcher *dsp; - struct child *child; - pid_t pid; - int allout, pipefd[2]; - struct passwd *pw; - const char *pw_name; - uid_t pw_uid; - gid_t pw_gid; - const char *pw_dir; - - dsp = dict_xget(env->sc_dispatchers, deliver->dispatcher); - if (dsp->type != DISPATCHER_LOCAL) - fatalx("non-local dispatcher called from forkmda()"); - - log_debug("debug: smtpd: forking mda for session %016"PRIx64 - ": %s as %s", id, deliver->userinfo.username, - dsp->u.local.user ? dsp->u.local.user : deliver->userinfo.username); - - if (dsp->u.local.user) { - if ((pw = getpwnam(dsp->u.local.user)) == NULL) { - (void)snprintf(ebuf, sizeof ebuf, - "delivery user '%s' does not exist", - dsp->u.local.user); - m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); - m_add_id(p_pony, id); - m_add_int(p_pony, MDA_PERMFAIL); - m_add_int(p_pony, EX_NOUSER); - m_add_string(p_pony, ebuf); - m_close(p_pony); - return; - } - pw_name = pw->pw_name; - pw_uid = pw->pw_uid; - pw_gid = pw->pw_gid; - pw_dir = pw->pw_dir; - } - else { - pw_name = deliver->userinfo.username; - pw_uid = deliver->userinfo.uid; - pw_gid = deliver->userinfo.gid; - pw_dir = deliver->userinfo.directory; - } - - if (pw_uid == 0 && deliver->mda_exec[0]) { - pw_name = deliver->userinfo.username; - pw_uid = deliver->userinfo.uid; - pw_gid = deliver->userinfo.gid; - pw_dir = deliver->userinfo.directory; - } - - if (pw_uid == 0 && !dsp->u.local.is_mbox) { - (void)snprintf(ebuf, sizeof ebuf, "not allowed to deliver to: %s", - deliver->userinfo.username); - m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); - m_add_id(p_pony, id); - m_add_int(p_pony, MDA_PERMFAIL); - m_add_int(p_pony, EX_NOPERM); - m_add_string(p_pony, ebuf); - m_close(p_pony); - return; - } - - if (pipe(pipefd) == -1) { - (void)snprintf(ebuf, sizeof ebuf, "pipe: %s", strerror(errno)); - m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); - m_add_id(p_pony, id); - m_add_int(p_pony, MDA_TEMPFAIL); - m_add_int(p_pony, EX_OSERR); - m_add_string(p_pony, ebuf); - m_close(p_pony); - return; - } - - /* prepare file which captures stdout and stderr */ - (void)strlcpy(sfn, "/tmp/smtpd.out.XXXXXXXXXXX", sizeof(sfn)); - allout = mkstemp(sfn); - if (allout == -1) { - (void)snprintf(ebuf, sizeof ebuf, "mkstemp: %s", strerror(errno)); - m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); - m_add_id(p_pony, id); - m_add_int(p_pony, MDA_TEMPFAIL); - m_add_int(p_pony, EX_OSERR); - m_add_string(p_pony, ebuf); - m_close(p_pony); - close(pipefd[0]); - close(pipefd[1]); - return; - } - unlink(sfn); - - pid = fork(); - if (pid == -1) { - (void)snprintf(ebuf, sizeof ebuf, "fork: %s", strerror(errno)); - m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); - m_add_id(p_pony, id); - m_add_int(p_pony, MDA_TEMPFAIL); - m_add_int(p_pony, EX_OSERR); - m_add_string(p_pony, ebuf); - m_close(p_pony); - close(pipefd[0]); - close(pipefd[1]); - close(allout); - return; - } - - /* parent passes the child fd over to mda */ - if (pid > 0) { - child = child_add(pid, CHILD_MDA, NULL); - child->mda_out = allout; - child->mda_id = id; - close(pipefd[0]); - m_create(p, IMSG_MDA_FORK, 0, 0, pipefd[1]); - m_add_id(p, id); - m_close(p); - return; - } - - /* mbox helper, create mailbox before privdrop if it doesn't exist */ - if (dsp->u.local.is_mbox) - mda_mbox_init(deliver); - - if (chdir(pw_dir) == -1 && chdir("/") == -1) - err(1, "chdir"); - if (setgroups(1, &pw_gid) || - setresgid(pw_gid, pw_gid, pw_gid) || - setresuid(pw_uid, pw_uid, pw_uid)) - err(1, "forkmda: cannot drop privileges"); - if (dup2(pipefd[0], STDIN_FILENO) == -1 || - dup2(allout, STDOUT_FILENO) == -1 || - dup2(allout, STDERR_FILENO) == -1) - err(1, "forkmda: dup2"); - closefrom(STDERR_FILENO + 1); - if (setsid() < 0) - err(1, "setsid"); - if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || - signal(SIGINT, SIG_DFL) == SIG_ERR || - signal(SIGTERM, SIG_DFL) == SIG_ERR || - signal(SIGCHLD, SIG_DFL) == SIG_ERR || - signal(SIGHUP, SIG_DFL) == SIG_ERR) - err(1, "signal"); - - /* avoid hangs by setting 5m timeout */ - alarm(300); - - if (dsp->u.local.is_mbox && - dsp->u.local.mda_wrapper == NULL && - deliver->mda_exec[0] == '\0') - mda_mbox(deliver); - else - mda_unpriv(dsp, deliver, pw_name, pw_dir); -} - -static void -offline_scan(int fd, short ev, void *arg) -{ - char *path_argv[2]; - FTS *fts = arg; - FTSENT *e; - int n = 0; - - path_argv[0] = PATH_SPOOL PATH_OFFLINE; - path_argv[1] = NULL; - - if (fts == NULL) { - log_debug("debug: smtpd: scanning offline queue..."); - fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); - if (fts == NULL) { - log_warn("fts_open: %s", path_argv[0]); - return; - } - } - - while ((e = fts_read(fts)) != NULL) { - if (e->fts_info != FTS_F) - continue; - - /* offline files must be at depth 1 */ - if (e->fts_level != 1) - continue; - - /* offline file group must match parent directory group */ - if (e->fts_statp->st_gid != e->fts_parent->fts_statp->st_gid) - continue; - - if (e->fts_statp->st_size == 0) { - if (unlink(e->fts_accpath) == -1) - log_warnx("warn: smtpd: could not unlink %s", e->fts_accpath); - continue; - } - - if (offline_add(e->fts_name, e->fts_statp->st_uid, - e->fts_statp->st_gid)) { - log_warnx("warn: smtpd: " - "could not add offline message %s", e->fts_name); - continue; - } - - if ((n++) == OFFLINE_READMAX) { - evtimer_set(&offline_ev, offline_scan, fts); - offline_timeout.tv_sec = 0; - offline_timeout.tv_usec = 100000; - evtimer_add(&offline_ev, &offline_timeout); - return; - } - } - - log_debug("debug: smtpd: offline scanning done"); - fts_close(fts); -} - -static int -offline_enqueue(char *name, uid_t uid, gid_t gid) -{ - char *path; - struct stat sb; - pid_t pid; - struct child *child; - struct passwd *pw; - int pathlen; - - pathlen = asprintf(&path, "%s/%s", PATH_SPOOL PATH_OFFLINE, name); - if (pathlen == -1) { - log_warnx("warn: smtpd: asprintf"); - return (-1); - } - - if (pathlen >= PATH_MAX) { - log_warnx("warn: smtpd: pathname exceeds PATH_MAX"); - free(path); - return (-1); - } - - log_debug("debug: smtpd: enqueueing offline message %s", path); - - if ((pid = fork()) == -1) { - log_warn("warn: smtpd: fork"); - free(path); - return (-1); - } - - if (pid == 0) { - char *envp[2], *p = NULL, *tmp; - int fd; - FILE *fp; - size_t sz = 0; - ssize_t len; - arglist args; - - closefrom(STDERR_FILENO + 1); - - memset(&args, 0, sizeof(args)); - - if ((fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) == -1) { - log_warn("warn: smtpd: open: %s", path); - _exit(1); - } - - if (fstat(fd, &sb) == -1) { - log_warn("warn: smtpd: fstat: %s", path); - _exit(1); - } - - if (!S_ISREG(sb.st_mode)) { - log_warnx("warn: smtpd: file %s (uid %d) not regular", - path, sb.st_uid); - _exit(1); - } - - if (sb.st_nlink != 1) { - log_warnx("warn: smtpd: file %s is hard-link", path); - _exit(1); - } - - if (sb.st_uid != uid) { - log_warnx("warn: smtpd: file %s has bad uid %d", - path, sb.st_uid); - _exit(1); - } - - if (sb.st_gid != gid) { - log_warnx("warn: smtpd: file %s has bad gid %d", - path, sb.st_gid); - _exit(1); - } - - pw = getpwuid(sb.st_uid); - if (pw == NULL) { - log_warnx("warn: smtpd: getpwuid for uid %d failed", - sb.st_uid); - _exit(1); - } - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) - _exit(1); - - if ((fp = fdopen(fd, "r")) == NULL) - _exit(1); - - if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) - _exit(1); - - if (setsid() == -1 || - signal(SIGPIPE, SIG_DFL) == SIG_ERR || - dup2(fileno(fp), STDIN_FILENO) == -1) - _exit(1); - - if ((len = getline(&p, &sz, fp)) == -1) - _exit(1); - - if (p[len - 1] != '\n') - _exit(1); - p[len - 1] = '\0'; - - addargs(&args, "%s", "sendmail"); - addargs(&args, "%s", "-S"); - - while ((tmp = strsep(&p, "|")) != NULL) - addargs(&args, "%s", tmp); - - free(p); - if (lseek(fileno(fp), len, SEEK_SET) == -1) - _exit(1); - - envp[0] = "PATH=" _PATH_DEFPATH; - envp[1] = (char *)NULL; - environ = envp; - - execvp(PATH_SMTPCTL, args.list); - _exit(1); - } - - offline_running++; - child = child_add(pid, CHILD_ENQUEUE_OFFLINE, NULL); - child->path = path; - - return (0); -} - -static int -offline_add(char *path, uid_t uid, gid_t gid) -{ - struct offline *q; - - if (offline_running < OFFLINE_QUEUEMAX) - /* skip queue */ - return offline_enqueue(path, uid, gid); - - q = malloc(sizeof(*q) + strlen(path) + 1); - if (q == NULL) - return (-1); - q->uid = uid; - q->gid = gid; - q->path = (char *)q + sizeof(*q); - memmove(q->path, path, strlen(path) + 1); - TAILQ_INSERT_TAIL(&offline_q, q, entry); - - return (0); -} - -static void -offline_done(void) -{ - struct offline *q; - - offline_running--; - - while (offline_running < OFFLINE_QUEUEMAX) { - if ((q = TAILQ_FIRST(&offline_q)) == NULL) - break; /* all done */ - TAILQ_REMOVE(&offline_q, q, entry); - offline_enqueue(q->path, q->uid, q->gid); - free(q); - } -} - -static int -parent_forward_open(char *username, char *directory, uid_t uid, gid_t gid) -{ - char pathname[PATH_MAX]; - int fd; - struct stat sb; - - if (!bsnprintf(pathname, sizeof (pathname), "%s/.forward", - directory)) { - log_warnx("warn: smtpd: %s: pathname too large", pathname); - return -1; - } - - if (stat(directory, &sb) == -1) { - log_warn("warn: smtpd: parent_forward_open: %s", directory); - return -1; - } - if (sb.st_mode & S_ISVTX) { - log_warnx("warn: smtpd: parent_forward_open: %s is sticky", - directory); - errno = EAGAIN; - return -1; - } - - do { - fd = open(pathname, O_RDONLY|O_NOFOLLOW|O_NONBLOCK); - } while (fd == -1 && errno == EINTR); - if (fd == -1) { - if (errno == ENOENT) - return -1; - if (errno == EMFILE || errno == ENFILE || errno == EIO) { - errno = EAGAIN; - return -1; - } - if (errno == ELOOP) - log_warnx("warn: smtpd: parent_forward_open: %s: " - "cannot follow symbolic links", pathname); - else - log_warn("warn: smtpd: parent_forward_open: %s", pathname); - return -1; - } - - if (!secure_file(fd, pathname, directory, uid, 1)) { - log_warnx("warn: smtpd: %s: unsecure file", pathname); - close(fd); - return -1; - } - - return fd; -} - -void -imsg_dispatch(struct mproc *p, struct imsg *imsg) -{ - struct timespec t0, t1, dt; - int msg; - - if (imsg == NULL) { - imsg_callback(p, imsg); - return; - } - - log_imsg(smtpd_process, p->proc, imsg); - - if (profiling & PROFILE_IMSG) - clock_gettime(CLOCK_MONOTONIC, &t0); - - msg = imsg->hdr.type; - imsg_callback(p, imsg); - - if (profiling & PROFILE_IMSG) { - clock_gettime(CLOCK_MONOTONIC, &t1); - timespecsub(&t1, &t0, &dt); - - log_debug("profile-imsg: %s %s %s %d %lld.%09ld", - proc_name(smtpd_process), - proc_name(p->proc), - imsg_to_str(msg), - (int)imsg->hdr.len, - (long long)dt.tv_sec, - dt.tv_nsec); - - if (profiling & PROFILE_TOSTAT) { - char key[STAT_KEY_SIZE]; - /* can't profstat control process yet */ - if (smtpd_process == PROC_CONTROL) - return; - - if (!bsnprintf(key, sizeof key, - "profiling.imsg.%s.%s.%s", - proc_name(smtpd_process), - proc_name(p->proc), - imsg_to_str(msg))) - return; - stat_set(key, stat_timespec(&dt)); - } - } -} - -void -log_imsg(int to, int from, struct imsg *imsg) -{ - - if (to == PROC_CONTROL && imsg->hdr.type == IMSG_STAT_SET) - return; - - if (imsg->fd != -1) - log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu, fd=%d)", - proc_name(to), - proc_name(from), - imsg_to_str(imsg->hdr.type), - imsg->hdr.len - IMSG_HEADER_SIZE, - imsg->fd); - else - log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu)", - proc_name(to), - proc_name(from), - imsg_to_str(imsg->hdr.type), - imsg->hdr.len - IMSG_HEADER_SIZE); -} - -const char * -proc_title(enum smtp_proc_type proc) -{ - switch (proc) { - case PROC_PARENT: - return "[priv]"; - case PROC_LKA: - return "lookup"; - case PROC_QUEUE: - return "queue"; - case PROC_CONTROL: - return "control"; - case PROC_SCHEDULER: - return "scheduler"; - case PROC_PONY: - return "pony express"; - case PROC_CA: - return "klondike"; - case PROC_CLIENT: - return "client"; - case PROC_PROCESSOR: - return "processor"; - } - return "unknown"; -} - -const char * -proc_name(enum smtp_proc_type proc) -{ - switch (proc) { - case PROC_PARENT: - return "parent"; - case PROC_LKA: - return "lka"; - case PROC_QUEUE: - return "queue"; - case PROC_CONTROL: - return "control"; - case PROC_SCHEDULER: - return "scheduler"; - case PROC_PONY: - return "pony"; - case PROC_CA: - return "ca"; - case PROC_CLIENT: - return "client-proc"; - default: - return "unknown"; - } -} - -#define CASE(x) case x : return #x - -const char * -imsg_to_str(int type) -{ - static char buf[32]; - - switch (type) { - CASE(IMSG_NONE); - - CASE(IMSG_CTL_OK); - CASE(IMSG_CTL_FAIL); - - CASE(IMSG_CTL_GET_DIGEST); - CASE(IMSG_CTL_GET_STATS); - CASE(IMSG_CTL_LIST_MESSAGES); - CASE(IMSG_CTL_LIST_ENVELOPES); - CASE(IMSG_CTL_MTA_SHOW_HOSTS); - CASE(IMSG_CTL_MTA_SHOW_RELAYS); - CASE(IMSG_CTL_MTA_SHOW_ROUTES); - CASE(IMSG_CTL_MTA_SHOW_HOSTSTATS); - CASE(IMSG_CTL_MTA_BLOCK); - CASE(IMSG_CTL_MTA_UNBLOCK); - CASE(IMSG_CTL_MTA_SHOW_BLOCK); - CASE(IMSG_CTL_PAUSE_EVP); - CASE(IMSG_CTL_PAUSE_MDA); - CASE(IMSG_CTL_PAUSE_MTA); - CASE(IMSG_CTL_PAUSE_SMTP); - CASE(IMSG_CTL_PROFILE); - CASE(IMSG_CTL_PROFILE_DISABLE); - CASE(IMSG_CTL_PROFILE_ENABLE); - CASE(IMSG_CTL_RESUME_EVP); - CASE(IMSG_CTL_RESUME_MDA); - CASE(IMSG_CTL_RESUME_MTA); - CASE(IMSG_CTL_RESUME_SMTP); - CASE(IMSG_CTL_RESUME_ROUTE); - CASE(IMSG_CTL_REMOVE); - CASE(IMSG_CTL_SCHEDULE); - CASE(IMSG_CTL_SHOW_STATUS); - CASE(IMSG_CTL_TRACE_DISABLE); - CASE(IMSG_CTL_TRACE_ENABLE); - CASE(IMSG_CTL_UPDATE_TABLE); - CASE(IMSG_CTL_VERBOSE); - CASE(IMSG_CTL_DISCOVER_EVPID); - CASE(IMSG_CTL_DISCOVER_MSGID); - - CASE(IMSG_CTL_SMTP_SESSION); - - CASE(IMSG_GETADDRINFO); - CASE(IMSG_GETADDRINFO_END); - CASE(IMSG_GETNAMEINFO); - CASE(IMSG_RES_QUERY); - - CASE(IMSG_CERT_INIT); - CASE(IMSG_CERT_CERTIFICATE); - CASE(IMSG_CERT_VERIFY); - - CASE(IMSG_SETUP_KEY); - CASE(IMSG_SETUP_PEER); - CASE(IMSG_SETUP_DONE); - - CASE(IMSG_CONF_START); - CASE(IMSG_CONF_END); - - CASE(IMSG_STAT_INCREMENT); - CASE(IMSG_STAT_DECREMENT); - CASE(IMSG_STAT_SET); - - CASE(IMSG_LKA_AUTHENTICATE); - CASE(IMSG_LKA_OPEN_FORWARD); - CASE(IMSG_LKA_ENVELOPE_SUBMIT); - CASE(IMSG_LKA_ENVELOPE_COMMIT); - - CASE(IMSG_QUEUE_DELIVER); - CASE(IMSG_QUEUE_DELIVERY_OK); - CASE(IMSG_QUEUE_DELIVERY_TEMPFAIL); - CASE(IMSG_QUEUE_DELIVERY_PERMFAIL); - CASE(IMSG_QUEUE_DELIVERY_LOOP); - CASE(IMSG_QUEUE_DISCOVER_EVPID); - CASE(IMSG_QUEUE_DISCOVER_MSGID); - CASE(IMSG_QUEUE_ENVELOPE_ACK); - CASE(IMSG_QUEUE_ENVELOPE_COMMIT); - CASE(IMSG_QUEUE_ENVELOPE_REMOVE); - CASE(IMSG_QUEUE_ENVELOPE_SCHEDULE); - CASE(IMSG_QUEUE_ENVELOPE_SUBMIT); - CASE(IMSG_QUEUE_HOLDQ_HOLD); - CASE(IMSG_QUEUE_HOLDQ_RELEASE); - CASE(IMSG_QUEUE_MESSAGE_COMMIT); - CASE(IMSG_QUEUE_MESSAGE_ROLLBACK); - CASE(IMSG_QUEUE_SMTP_SESSION); - CASE(IMSG_QUEUE_TRANSFER); - - CASE(IMSG_MDA_DELIVERY_OK); - CASE(IMSG_MDA_DELIVERY_TEMPFAIL); - CASE(IMSG_MDA_DELIVERY_PERMFAIL); - CASE(IMSG_MDA_DELIVERY_LOOP); - CASE(IMSG_MDA_DELIVERY_HOLD); - CASE(IMSG_MDA_DONE); - CASE(IMSG_MDA_FORK); - CASE(IMSG_MDA_HOLDQ_RELEASE); - CASE(IMSG_MDA_LOOKUP_USERINFO); - CASE(IMSG_MDA_KILL); - CASE(IMSG_MDA_OPEN_MESSAGE); - - CASE(IMSG_MTA_DELIVERY_OK); - CASE(IMSG_MTA_DELIVERY_TEMPFAIL); - CASE(IMSG_MTA_DELIVERY_PERMFAIL); - CASE(IMSG_MTA_DELIVERY_LOOP); - CASE(IMSG_MTA_DELIVERY_HOLD); - CASE(IMSG_MTA_DNS_HOST); - CASE(IMSG_MTA_DNS_HOST_END); - CASE(IMSG_MTA_DNS_MX); - CASE(IMSG_MTA_DNS_MX_PREFERENCE); - CASE(IMSG_MTA_HOLDQ_RELEASE); - CASE(IMSG_MTA_LOOKUP_CREDENTIALS); - CASE(IMSG_MTA_LOOKUP_SOURCE); - CASE(IMSG_MTA_LOOKUP_HELO); - CASE(IMSG_MTA_LOOKUP_SMARTHOST); - CASE(IMSG_MTA_OPEN_MESSAGE); - CASE(IMSG_MTA_SCHEDULE); - - CASE(IMSG_SCHED_ENVELOPE_BOUNCE); - CASE(IMSG_SCHED_ENVELOPE_DELIVER); - CASE(IMSG_SCHED_ENVELOPE_EXPIRE); - CASE(IMSG_SCHED_ENVELOPE_INJECT); - CASE(IMSG_SCHED_ENVELOPE_REMOVE); - CASE(IMSG_SCHED_ENVELOPE_TRANSFER); - - CASE(IMSG_SMTP_AUTHENTICATE); - CASE(IMSG_SMTP_MESSAGE_COMMIT); - CASE(IMSG_SMTP_MESSAGE_CREATE); - CASE(IMSG_SMTP_MESSAGE_ROLLBACK); - CASE(IMSG_SMTP_MESSAGE_OPEN); - CASE(IMSG_SMTP_CHECK_SENDER); - CASE(IMSG_SMTP_EXPAND_RCPT); - CASE(IMSG_SMTP_LOOKUP_HELO); - - CASE(IMSG_SMTP_REQ_CONNECT); - CASE(IMSG_SMTP_REQ_HELO); - CASE(IMSG_SMTP_REQ_MAIL); - CASE(IMSG_SMTP_REQ_RCPT); - CASE(IMSG_SMTP_REQ_DATA); - CASE(IMSG_SMTP_REQ_EOM); - CASE(IMSG_SMTP_EVENT_RSET); - CASE(IMSG_SMTP_EVENT_COMMIT); - CASE(IMSG_SMTP_EVENT_ROLLBACK); - CASE(IMSG_SMTP_EVENT_DISCONNECT); - - CASE(IMSG_LKA_PROCESSOR_FORK); - CASE(IMSG_LKA_PROCESSOR_ERRFD); - - CASE(IMSG_REPORT_SMTP_LINK_CONNECT); - CASE(IMSG_REPORT_SMTP_LINK_DISCONNECT); - CASE(IMSG_REPORT_SMTP_LINK_TLS); - CASE(IMSG_REPORT_SMTP_LINK_GREETING); - CASE(IMSG_REPORT_SMTP_LINK_IDENTIFY); - CASE(IMSG_REPORT_SMTP_LINK_AUTH); - - CASE(IMSG_REPORT_SMTP_TX_RESET); - CASE(IMSG_REPORT_SMTP_TX_BEGIN); - CASE(IMSG_REPORT_SMTP_TX_ENVELOPE); - CASE(IMSG_REPORT_SMTP_TX_COMMIT); - CASE(IMSG_REPORT_SMTP_TX_ROLLBACK); - - CASE(IMSG_REPORT_SMTP_PROTOCOL_CLIENT); - CASE(IMSG_REPORT_SMTP_PROTOCOL_SERVER); - - CASE(IMSG_FILTER_SMTP_BEGIN); - CASE(IMSG_FILTER_SMTP_END); - CASE(IMSG_FILTER_SMTP_PROTOCOL); - CASE(IMSG_FILTER_SMTP_DATA_BEGIN); - CASE(IMSG_FILTER_SMTP_DATA_END); - - CASE(IMSG_CA_RSA_PRIVENC); - CASE(IMSG_CA_RSA_PRIVDEC); - CASE(IMSG_CA_ECDSA_SIGN); - default: - (void)snprintf(buf, sizeof(buf), "IMSG_??? (%d)", type); - - return buf; - } -} - -#ifdef BSD_AUTH -int -parent_auth_bsd(const char *username, const char *password) -{ - char user[LOGIN_NAME_MAX]; - char pass[LINE_MAX]; - int ret; - - (void)strlcpy(user, username, sizeof(user)); - (void)strlcpy(pass, password, sizeof(pass)); - - ret = auth_userokay(user, NULL, "auth-smtp", pass); - if (ret) - return LKA_OK; - return LKA_PERMFAIL; -} -#endif - -#ifdef USE_PAM -int -pam_conv_password(int num_msg, const struct pam_message **msg, - struct pam_response **respp, void *password) -{ - struct pam_response *response; - - if (num_msg != 1) - return PAM_CONV_ERR; - - response = calloc(1, sizeof(struct pam_response)); - if (response == NULL || (response->resp = strdup(password)) == NULL) { - free(response); - return PAM_BUF_ERR; - } - - *respp = response; - return PAM_SUCCESS; -} -int -parent_auth_pam(const char *username, const char *password) -{ - int rc; - pam_handle_t *pamh = NULL; - struct pam_conv conv = { pam_conv_password, (char *)password }; - - if ((rc = pam_start(USE_PAM_SERVICE, username, &conv, &pamh)) != PAM_SUCCESS) - goto end; - if ((rc = pam_authenticate(pamh, 0)) != PAM_SUCCESS) - goto end; - if ((rc = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) - goto end; - -end: - pam_end(pamh, rc); - - switch (rc) { - case PAM_SUCCESS: - return LKA_OK; - case PAM_SYSTEM_ERR: - case PAM_ABORT: - case PAM_AUTHINFO_UNAVAIL: - return LKA_TEMPFAIL; - default: - return LKA_PERMFAIL; - } -} -#endif - -#ifdef HAVE_GETSPNAM -int -parent_auth_getspnam(const char *username, const char *password) -{ - struct spwd *pw; - char *ep; - - errno = 0; - do { - pw = getspnam(username); - } while (pw == NULL && errno == EINTR); - - if (pw == NULL) { - if (errno) - return LKA_TEMPFAIL; - return LKA_PERMFAIL; - } - - if ((ep = crypt(password, pw->sp_pwdp)) == NULL) - return LKA_PERMFAIL; - - if (strcmp(pw->sp_pwdp, ep) == 0) - return LKA_OK; - - return LKA_PERMFAIL; -} -#endif - -int -parent_auth_pwd(const char *username, const char *password) -{ - struct passwd *pw; - char *ep; - - errno = 0; - do { - pw = getpwnam(username); - } while (pw == NULL && errno == EINTR); - - if (pw == NULL) { - if (errno) - return LKA_TEMPFAIL; - return LKA_PERMFAIL; - } - - if ((ep = crypt(password, pw->pw_passwd)) == NULL) - return LKA_PERMFAIL; - - if (strcmp(pw->pw_passwd, ep) == 0) - return LKA_OK; - - return LKA_PERMFAIL; -} - -int -parent_auth_user(const char *username, const char *password) -{ -#if defined(BSD_AUTH) - return (parent_auth_bsd(username, password)); -#elif defined(USE_PAM) - return (parent_auth_pam(username, password)); -#elif defined(HAVE_GETSPNAM) - return (parent_auth_getspnam(username, password)); -#else - return (parent_auth_pwd(username, password)); -#endif -} diff --git a/smtpd/smtpd.conf b/smtpd/smtpd.conf deleted file mode 100644 index a7ba6c64..00000000 --- a/smtpd/smtpd.conf +++ /dev/null @@ -1,19 +0,0 @@ -# $OpenBSD: smtpd.conf,v 1.10 2018/05/24 11:40:17 gilles Exp $ - -# This is the smtpd server system-wide configuration file. -# See smtpd.conf(5) for more information. - -table aliases file:/etc/mail/aliases - -# To accept external mail, replace with: listen on all -# -listen on localhost - -action "local" maildir alias -action "relay" relay - -# Uncomment the following to accept external mail for domain "example.org" -# -# match from any for domain "example.org" action "local" -match for local action "local" -match from local for any action "relay" diff --git a/smtpd/smtpd.conf.5 b/smtpd/smtpd.conf.5 deleted file mode 100644 index c543c662..00000000 --- a/smtpd/smtpd.conf.5 +++ /dev/null @@ -1,1240 +0,0 @@ -.\" $OpenBSD: smtpd.conf.5,v 1.250 2020/04/25 09:20:38 eric Exp $ -.\" -.\" Copyright (c) 2008 Janne Johansson -.\" Copyright (c) 2009 Jacek Masiulaniec -.\" Copyright (c) 2012 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.\" -.Dd $Mdocdate: April 25 2020 $ -.Dt SMTPD.CONF 5 -.Os -.Sh NAME -.Nm smtpd.conf -.Nd Simple Mail Transfer Protocol daemon configuration file -.Sh DESCRIPTION -.Nm -is the configuration file for the mail daemon -.Xr smtpd 8 . -.Pp -When mail arrives, -each -.Dq RCPT TO: -command generates a mail envelope. -If an envelope matches -any of a pre-designated set of criteria -(using the -.Ic match -directive), -the message is accepted for delivery. -A copy of the message, as well as its associated envelopes, -is saved in the mail queue and later dispatched -according to an associated set of actions -(using the -.Ic action -directive). -If an envelope does not match any options, -it is rejected. -The match rules are evaluated sequentially, -with the first match winning. -.Pp -The format of the configuration file is fairly flexible. -The current line can be extended over multiple lines using a backslash -.Pq Sq \e . -Comments can be put anywhere in the file using a hash mark -.Pq Sq # , -and extend to the end of the current line. -Care should be taken when commenting out multi-line text: -the comment is effective until the end of the entire block. -Argument names not beginning with a letter, digit, or underscore, -as well as reserved words -(such as -.Ic listen , -.Ic match , -and -.Cm port ) , -must be quoted. -Arguments containing whitespace should be surrounded by double quotes -.Pq \&" . -.Pp -Macros can be defined that are later expanded in context. -Macro names must start with a letter, digit, or underscore, -and may contain any of those characters, -but may not be reserved words. -Macros are not expanded inside quotes. -For example: -.Bd -literal -offset indent -lan_addr = "192.168.0.1" -listen on $lan_addr -listen on $lan_addr tls auth -.Ed -.Pp -The syntax of -.Nm -is described below. -.Bl -tag -width Ds -.It Ic action Ar name method Op Ar options -When the queue runner processes an envelope from the mail queue, -it carries out the -.Ic action -.Ar name , -selected by the -.Ic match No ... Cm action -directive when the message was received. -The -.Ic action -directive provides configuration data for delivery attempts. -Required lookups are performed at the time of each delivery attempt. -Consequently, changing an -.Ic action -directive or the files it references and restarting the -.Xr smtpd 8 -daemon causes the changes to take effect for subsequent delivery -attempts for the respective dispatcher -.Ar name , -even for messages that were already stuck in the queue -prior to the configuration changes. -.Pp -The delivery -.Ar method -parameter may be one of the following: -.Bl -tag -width Ds -.It Cm expand-only -Only accept the message if a delivery method was specified -in an aliases or -.Pa .forward -file. -.It Cm forward-only -Only accept the message if the recipient results in a remote address -after the processing of aliases or forward file. -.It Cm lmtp Ar destination Op Ar rcpt-to -Deliver the message to an LMTP server at -.Ar destination . -The location may be expressed as host:port or as a UNIX socket. -.Pp -Optionally, -.Ar rcpt-to -might be specified to use the -recipient email address (after expansion) instead of the -local user in the LMTP session as RCPT TO. -.It Cm maildir Op Ar pathname Op Cm junk -Deliver the message to the maildir in -.Ar pathname -if specified, or by default to -.Pa ~/Maildir . -.Pp -The -.Ar pathname -may contain format specifiers that are expanded before use -.Pq see Sx FORMAT SPECIFIERS . -.Pp -If the -.Cm junk -argument is provided, the message will be moved to the -.Ql Junk -folder if it contains a positive -.Ql X-Spam -header. -This folder will be created under -.Ar pathname -if it does not yet exist. -.It Cm mbox -Deliver the message to the user's mbox with -.Xr mail.local 8 . -.It Cm mda Ar command -Delegate the delivery to a -.Ar command -that receives the message on its standard input. -.Pp -The -.Ar command -may contain format specifiers that are expanded before use -.Pq see Sx FORMAT SPECIFIERS . -.It Cm relay -Relay the message to another SMTP server. -.El -.Pp -The local delivery methods support additional options: -.Bl -tag -width Ds -.It Cm alias Pf < Ar table Ns > -Use the mapping -.Ar table -for -.Xr aliases 5 -expansion. -.It Xo -.Cm ttl -.Sm off -.Ar n -.Brq Cm s | m | h | d -.Sm on -.Xc -Specify how long a message may remain in the queue. -.It Cm user Ar username -Specify the -.Ar username -for performing the delivery, to be looked up with -.Xr getpwnam 3 . -.Pp -This is used for virtual hosting where a single username -is in charge of handling delivery for all virtual users. -.Pp -This option is not usable with the -.Cm mbox -delivery method. -.It Cm userbase Pf < Ar table Ns > -Use the mapping -.Ar table -for user lookups instead of the -.Xr getpwnam 3 -function. -.Pp -The -.Cm userbase -does not apply for the -.Cm user -option. -.It Cm virtual Pf < Ar table Ns > -Use the mapping -.Ar table -for virtual expansion. -The aliasing table format is described in -.Xr table 5 . -.It Cm wrapper Ar name -Use the wrapper specified in -.Cm mda wrapper . -.El -.Pp -The relay delivery methods also support additional options: -.Bl -tag -width Ds -.It Cm backup -Operate as a backup mail exchanger delivering messages to any mail exchanger -with higher priority. -.It Cm backup mx Ar name -Operate as a backup mail exchanger delivering messages to any mail exchanger -with higher priority than mail exchanger identified as -.Ar name . -.It Cm helo Ar heloname -Advertise -.Ar heloname -as the hostname to other mail exchangers during the HELO phase. -.It Cm helo-src Pf < Ar table Ns > -Use the mapping -.Ar table -to look up a hostname matching the source address, -to advertise during the HELO phase. -.It Cm domain Pf < Ar domains Ns > -Do not perform MX lookups but look up destination domain in -.Ar domains -and use matching relay url as relay host. -.It Cm host Ar relay-url -Do not perform MX lookups but relay messages to the relay host described by -.Ar relay-url . -The format for -.Ar relay-url -is -.Sm off -.Op Ar proto No :// Op Ar label No @ -.Ar host Op : Ar port . -.Sm on -The following protocols are available: -.Pp -.Bl -tag -width "smtp+notls" -compact -.It smtp -Normal SMTP session with opportunistic STARTTLS -(the default). -.It smtp+tls -Normal SMTP session with mandatory STARTTLS. -.It smtp+notls -Plain text SMTP session without TLS. -.It lmtp -LMTP session. -.Ar port -is required. -.It smtps -SMTP session with forced TLS on connection, default port is 465. -.El -Unless noted, -.Ar port -defaults to 25. -.Pp -The -.Ar label -corresponds to an entry in a credentials table, -as documented in -.Xr table 5 . -It is used with the -.Dq smtp+tls -and -.Dq smtps -protocols for authentication. -Server certificates for those protocols are verified by default. -.It Cm srs -When relaying a mail resulting from a forward, -use the Sender Rewriting Scheme to rewrite sender address. -.It Cm tls Op Cm no-verify -Require TLS to be used when relaying, using mandatory STARTTLS by default. -When used with a smarthost, the protocol must not be -.Dq smtp+notls:// . -If -.Cm no-verify -is specified, do not require a valid certificate. -.It Cm auth Pf < Ar table Ns > -Use the mapping -.Ar table -for connecting to -.Ar relay-url -using credentials. -This option is usable only with -.Cm host -option. -The credential table format is described in -.Xr table 5 . -.It Cm mail-from Ar mailaddr -Use -.Ar mailaddr -as the MAIL FROM address within the SMTP transaction. -.It Cm src Ar sourceaddr | Pf < Ar sourceaddr Ns > -Use the string or list table -.Ar sourceaddr -for the source IP address, -which is useful on machines with multiple interfaces. -If the list contains more than one address, all of them are used -in such a way that traffic is routed as efficiently as possible. -.El -.It Ic bounce Cm warn-interval Ar delay Op , Ar delay ... -Send warning messages to the envelope sender when temporary delivery -failures cause a message to remain on the queue for longer than -.Ar delay . -Each -.Ar delay -parameter consists of a positive decimal integer and a unit -.Cm s , m , h , -or -.Cm d . -At most four -.Ar delay -parameters can be specified. -The default is -.Qq Ic bounce Cm warn-interval No 4h , -sending a single warning after four hours. -.It Ic ca Ar caname Cm cert Ar cafile -Associate the Certificate Authority (CA) certificate file -.Ar cafile -with host -.Ar caname , -and use that file as the CA certificate for that host. -.Ar caname -is the server's name, -derived from the default hostname -or set using either -.Pa /etc/mail/mailname -or using the -.Ic hostname -directive. -.It Ic filter Ar chain-name Ic chain Brq Ar filter-name Op , Ar ... -Register a chain of filters -.Ar chain-name , -consisting of the filters listed from -.Ar filter-name . -Filters part of a filter chain are executed in order of declaration for -each phase that they are registered for. -A filter chain may be used in place of a filter for any directive but -filter chains themselves. -.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions decision -Register a filter -.Ar filter-name . -A -.Ar decision -about what to do with the mail is taken at phase -.Ar phase-name -when matching -.Ar conditions . -Phases, matching conditions, and decisions are described in -.Sx MAIL FILTERING , -below. -.It Ic filter Ar filter-name Ic proc Ar proc-name -Register -.Qq proc -filter -.Ar filter-name -backed by the -.Ar proc-name -process. -.It Ic filter Ar filter-name Ic proc-exec Ar command -Register and execute -.Qq proc -filter -.Ar filter-name -from -.Ar command . -If -.Ar command -starts with a slash it is executed with an absolute path, -else it will be run from -.Dq /usr/local/libexec/smtpd/ . -.It Ic include Qq Ar pathname -Replace this directive with the content of the additional configuration -file at the absolute -.Ar pathname . -.It Ic listen on Ar interface Oo Ar family Oc Op Ar options -Listen on the -.Ar interface -for incoming connections, using the same syntax as for -.Xr ifconfig 8 . -The -.Ar interface -parameter may also be an interface group, an IP address, or a domain name. -Listening can optionally be restricted to a specific address -.Ar family , -which can be either -.Cm inet4 -or -.Cm inet6 . -.Pp -The -.Ar options -are as follows: -.Bl -tag -width Ds -.It Cm auth Op Pf < Ar authtable Ns > -Support SMTPAUTH: clients may only start SMTP transactions -after successful authentication. -Users are authenticated against either their own normal login credentials -or a credentials table -.Ar authtable , -the format of which is described in -.Xr table 5 . -.It Cm auth-optional Op Pf < Ar authtable Ns > -Support SMTPAUTH optionally: -clients need not authenticate, but may do so. -This allows a -.Ic listen on -directive to both accept incoming mail from untrusted senders -and permit outgoing mail from authenticated users -(using -.Cm match auth ) . -It can be used in situations where it is not possible to listen on a separate port -(usually the submission port, 587) -for users to authenticate. -.It Ic ca Ar caname -For secure connections, -use the CA certificate associated with -.Ar caname -(declared in a -.Ic ca -directive) -as the CA certificate when verifying client certificates. -.It Ic filter Ar name -Apply filter -.Ar name -on connections handled by this listener. -.It Cm hostname Ar hostname -Use -.Ar hostname -in the greeting banner instead of the default server name. -.It Cm hostnames Pf < Ar names Ns > -Override the server name for specific addresses. -The -.Ar names -table contains a mapping of IP addresses to hostnames. -If the address on which the connection arrives appears in the mapping, -the associated hostname is used. -.It Cm mask-src -Omit the -.Sy from -part when prepending -.Dq Received -headers. -.It Cm no-dsn -Disable the DSN (Delivery Status Notification) extension. -.It Cm pki Ar pkiname -For secure connections, -use the certificate associated with -.Ar pkiname -(declared in a -.Ic pki -directive) -to prove a mail server's identity. -.It Cm port Op Ar port -Listen on the given -.Ar port -instead of the default port 25. -.It Cm proxy-v2 -Support the PROXYv2 protocol, -rewriting appropriately source address received from proxy. -.It Cm received-auth -In -.Dq Received -headers, report whether the session was authenticated -and by which local user. -.It Cm senders Pf < Ar users Ns > Op Cm masquerade -Look up the authenticated user in the -.Ar users -mapping table to find the email addresses that user is allowed -to submit mail as. -In addition, if the -.Cm masquerade -option is provided, -the From header is rewritten -to match the sender provided in the SMTP session. -.It Cm smtps -Support SMTPS, by default on port 465. -Mutually exclusive with -.Cm tls . -.It Cm tag Ar tag -Clients connecting to the listener are tagged with the given -.Ar tag . -.It Cm tls -Support STARTTLS, by default on port 25. -Mutually exclusive with -.Cm smtps . -.It Cm tls-require Op Cm verify -Like -.Cm tls , -but force clients to establish a secure connection -before being allowed to start an SMTP transaction. -With the -.Cm verify -option, clients must also provide a valid certificate -to establish an SMTP session. -.El -.It Ic listen on Cm socket Op Ar options -Listen for incoming SMTP connections on the Unix domain socket -.Pa /var/run/smtpd.sock . -This is done by default, even if the directive is absent. -.Pp -The -.Ar options -are as follows: -.Bl -tag -width Ds -.It Ic filter Ar name -Apply filter -.Ar name -on connections handled by this listener. -.It Cm mask-src -Omit the -.Sy from -part when prepending -.Dq Received -headers. -.It Cm tag Ar tag -Clients connecting to the listener are tagged with the given -.Ar tag . -.El -.It Ic match Ar options Cm action Ar name -If at least one mail envelope matches the -.Ar options -of one -.Ic match Cm action -directive, receive the incoming message, put a copy into each -matching envelope, and atomically save the envelopes to the mail -spool for later processing by the respective dispatcher -.Ar name . -.Pp -The following matching options are supported and can all be negated: -.Bl -tag -width Ds -.It Xo -.Op Ic \&! -.Cm for any -.Xc -Specify that session may address any destination. -.It Xo -.Op Ic \&! -.Cm for local -.Xc -Specify that session may address any local domain. -This is the default, and may be omitted. -.It Xo -.Op Ic \&! -.Cm for domain -.Ar domain | Pf < Ar domain Ns > -.Xc -Specify that session may address the string or list table -.Ar domain . -.It Xo -.Op Ic \&! -.Cm for domain regex -.Ar domain | Pf < Ar domain Ns > -.Xc -Specify that session may address the regex or regex table -.Ar domain . -.It Xo -.Op Ic \&! -.Cm for rcpt-to -.Ar recipient | Pf < Ar recipient Ns > -.Xc -Specify that session may address the string or list table -.Ar recipient . -.It Xo -.Op Ic \&! -.Cm for rcpt-to regex -.Ar recipient | Pf < Ar recipient Ns > -.Xc -Specify that session may address the regex or regex table -.Ar recipient . -.It Xo -.Op Ic \&! -.Cm from any -.Xc -Specify that session may originate from any source. -.It Xo -.Op Ic \&! -.Cm from auth -.Xc -Specify that session may originate from any authenticated user, -no matter the source IP address. -.It Xo -.Op Ic \&! -.Cm from auth -.Ar user | Pf < Ar user Ns > -.Xc -Specify that session may originate from authenticated user or user list -.Ar user , -no matter the source IP address. -.It Xo -.Op Ic \&! -.Cm from auth -.Ar user | Pf < Ar user Ns > -.Xc -Specify that session may originate from authenticated regex or regex list -.Ar user , -no matter the source IP address. -.It Xo -.Op Ic \&! -.Cm from local -.Xc -Specify that session may only originate from a local IP address, -or from the local enqueuer. -This is the default, and may be omitted. -.It Xo -.Op Ic \&! -.Cm from mail-from -.Ar sender | Pf < Ar sender Ns > -.Xc -Specify that session may originate from sender or sender list -.Ar sender , -no matter the source IP address. -.It Xo -.Op Ic \&! -.Cm from mail-from regex -.Ar sender | Pf < Ar sender Ns > -.Xc -Specify that session may originate from regex or regex list -.Ar sender , -no matter the source IP address. -.It Xo -.Op Ic \&! -.Cm from rdns -.Xc -Specify that session may only originate from an IP address that -resolves to a reverse DNS. -.It Xo -.Op Ic \&! -.Cm from rdns -.Ar hostname | Pf < Ar hostname Ns > -.Xc -Specify that session may only originate from an IP address that -resolves to a reverse DNS matching string or list string -.Ar hostname . -.It Xo -.Op Ic \&! -.Cm from rdns regex -.Ar hostname | Pf < Ar hostname Ns > -.Xc -Specify that session may only originate from an IP address that -resolves to a reverse DNS matching regex or list regex -.Ar hostname . -.It Xo -.Op Ic \&! -.Cm from socket -.Xc -Specify that session may only originate from the local enqueuer. -.It Xo -.Op Ic \&! -.Cm from src -.Ar address | Pf < Ar address Ns > -.Xc -Specify that session may only originate from string or list table -.Ar address -which can be a specific address or a subnet expressed in CIDR-notation. -.It Xo -.Op Ic \&! -.Cm from src regex -.Ar address | Pf < Ar address Ns > -.Xc -Specify that session may only originate from regex or regex table -.Ar address -which can be a specific address or a subnet expressed in CIDR-notation. -.El -.Pp -In addition, the following transaction options: -.Bl -tag -width Ds -.It Xo -.Op Ic \&! -.Cm auth -.Xc -Matches transactions which have been authenticated. -.It Xo -.Op Ic \&! -.Cm auth -.Ar username | Pf < Ar username Ns > -.Xc -Matches transactions which have been authenticated for user or user list -.Ar username . -.It Xo -.Op Ic \&! -.Cm auth regex -.Ar username | Pf < Ar username Ns > -.Xc -Matches transactions which have been authenticated for regex or regex list -.Ar username . -.It Xo -.Op Ic \&! -.Cm helo -.Ar helo-name | Pf < Ar helo-name Ns > -.Xc -Specify that session's HELO / EHLO should match the string or list table -.Ar helo-name . -.It Xo -.Op Ic \&! -.Cm helo regex -.Ar helo-name | Pf < Ar helo-name Ns > -.Xc -Specify that session's HELO / EHLO should match the regex or regex table -.Ar helo-name . -.It Xo -.Op Ic \&! -.Cm mail-from -.Ar sender | Pf < Ar sender Ns > -.Xc -Specify that transactions's MAIL FROM should match the string or list table -.Ar sender . -.It Xo -.Op Ic \&! -.Cm mail-from regex -.Ar sender | Pf < Ar sender Ns > -.Xc -Specify that transactions's MAIL FROM should match the regex or regex table -.Ar sender . -.It Xo -.Op Ic \&! -.Cm rcpt-to -.Ar recipient | Pf < Ar recipient Ns > -.Xc -Specify that transaction's RCPT TO should match the string or list table -.Ar recipient . -.It Xo -.Op Ic \&! -.Cm rcpt-to regex -.Ar recipient | Pf < Ar recipient Ns > -.Xc -Specify that transaction's RCPT TO should match the regex or regex table -.Ar recipient . -.It Xo -.Op Ic \&! -.Cm tag Ar tag -.Xc -Matches transactions tagged with the given -.Ar tag . -.It Xo -.Op Ic \&! -.Cm tag regex Ar tag -.Xc -Matches transactions tagged with the given -.Ar tag -regex. -.It Xo -.Op Ic \&! -.Cm tls -.Xc -Specify that transaction should take place in a TLS channel. -.El -.It Ic match Ar options Cm reject -Reject the incoming message during the SMTP dialogue. -The same -.Ar options -are supported as for the -.Ic match Cm action -directive. -.It Ic mda Cm wrapper Ar name command -Associate -.Ar command -with the mail delivery agent wrapper named -.Ar name . -When a local delivery specifies a wrapper, the -.Ar command -associated with the wrapper will be executed instead. -The command may contain format specifiers -.Pq see Sx FORMAT SPECIFIERS . -.It Ic mta Cm max-deferred Ar number -When delivery to a given host is suspended due to temporary failures, -cache at most -.Ar number -envelopes for that host such that they can be delivered -as soon as another delivery succeeds to that host. -The default is 100. -.It Ic pki Ar pkiname Cm cert Ar certfile -Associate certificate file -.Ar certfile -with host -.Ar pkiname , -and use that file to prove the identity of the mail server to clients. -.Ar pkiname -is the server's name, -derived from the default hostname -or set using either -.Pa /etc/mail/mailname -or using the -.Ic hostname -directive. -If a fallback certificate or SNI is wanted, the -.Sq * -wildcard may be used as -.Ar pkiname . -.Pp -A certificate chain may be created by appending one or many certificates, -including a Certificate Authority certificate, -to -.Ar certfile . -The creation of certificates is documented in -.Xr starttls 8 . -.It Ic pki Ar pkiname Cm key Ar keyfile -Associate the key located in -.Ar keyfile -with host -.Ar pkiname . -.It Ic pki Ar pkiname Cm dhe Ar params -Specify the DHE parameters to use for DHE cipher suites with host -.Ar pkiname . -Valid parameter values are -.Cm none , -.Cm legacy , -and -.Cm auto . -For -.Cm legacy , -a fixed key length of 1024 bits is used, whereas for -.Cm auto , -the key length is determined automatically. -The default is -.Cm none , -which disables DHE cipher suites. -.It Ic proc Ar proc-name Ar command -Register an external process named -.Ar proc-name -from -.Ar command . -Such processes may be used to share the same instance between multiple filters. -If -.Ar command -starts with a slash it is executed with an absolute path, -else it will be run from -.Dq /usr/local/libexec/smtpd/ . -.It Ic queue Cm compression -Store queue files in a compressed format. -This may be useful to save disk space. -.It Ic queue Cm encryption Op Ar key -Encrypt queue files with -.Xr EVP_aes_256_gcm 3 . -If no -.Ar key -is specified, it is read with -.Xr getpass 3 . -If the string -.Cm stdin -or a single dash -.Pq Ql - -is given instead of a -.Ar key , -the key is read from the standard input. -.It Ic queue Cm ttl Ar delay -Set the default expiration time for temporarily undeliverable -messages, given as a positive decimal integer followed by a unit -.Cm s , m , h , -or -.Cm d . -The default is four days -.Pq 4d . -.It Ic smtp Cm ciphers Ar control -Set the -.Ar control -string for -.Xr SSL_CTX_set_cipher_list 3 . -The default is -.Qq HIGH:!aNULL:!MD5 . -.It Ic smtp limit Cm max-mails Ar count -Limit the number of messages to -.Ar count -for each session. -The default is 100. -.It Ic smtp limit Cm max-rcpt Ar count -Limit the number of recipients to -.Ar count -for each transaction. -The default is 1000. -.It Ic smtp Cm max-message-size Ar size -Reject messages larger than -.Ar size , -given as a positive number of bytes or as a string to be parsed with -.Xr scan_scaled 3 . -The default is -.Qq 35M . -.It Ic smtp Cm sub-addr-delim Ar character -When resolving the local part of a local email address, ignore the ASCII -.Ar character -and all characters following it. -The default is -.Ql + . -.It Ic srs Cm key Ar secret -Set the secret key to use for SRS, -the Sender Rewriting Scheme. -.It Ic srs Cm key backup Ar secret -Set a backup secret key to use as a fallback for SRS. -This can be used to implement SRS key rotation. -.It Ic srs Cm ttl Ar delay -Set the time-to-live delay for SRS envelopes. -After this delay, -a bounce reply to the SRS address will be discarded to limit risks of forged addresses. -The default is four days -.Pq 4d . -.It Ic table Ar name Oo Ar type : Oc Ns Ar pathname -Tables provide additional configuration information for -.Xr smtpd 8 -in the form of lists or key-value mappings. -The format of the entries depends on what the table is used for. -Refer to -.Xr table 5 -for the exhaustive documentation. -.Pp -Each table is identified by an arbitrary, unique -.Ar name . -.Pp -If the -.Ar type -is -.Cm db , -information is stored in a file created with -.Xr makemap 8 ; -if it is -.Cm file -or omitted, information is stored in a plain text file -using the format described in -.Xr table 5 . -The -.Ar pathname -to the file must be absolute. -.It Ic table Ar name Brq Ar value Op , Ar ... -Instead of using a separate file, declare a list table -containing the given static -.Ar value Ns s . -The table must contain at least one value and may declare multiple values as a -comma-separated (whitespace optional) list. -.It Ic table Ar name Brq Ar key Ns = Ns Ar value Op , Ar ... -Instead of using a separate file, declare a mapping table -containing the given static -.Ar key Ns - Ns Ar value -pairs. -The table must contain at least one key-value pair and may declare -multiple pairs as a comma-separated (whitespace optional) list. -.El -.Ss MAIL FILTERING -In a regular workflow, -.Xr smtpd 8 -may accept or reject a message based only on the content of envelopes. -Its decisions are about the handling of the message, -not about the handling of an active session. -.Pp -Filtering extends the decision making process by allowing -.Xr smtpd 8 -to stop at each phase of an SMTP session, -check that conditions are met, -then decide if a session is allowed to move forward. -.Pp -With filtering, -a session may be interrupted at any phase before an envelope is complete. -A message may also be rejected after being submitted, -regardless of whether the envelope was accepted or not. -.Pp -The following phases are currently supported: -.Bl -column mail-from -offset indent -.It connect Ta upon connection, before a banner is displayed -.It helo Ta after HELO command is submitted -.It ehlo Ta after EHLO command is submitted -.It mail-from Ta after MAIL FROM command is submitted -.It rcpt-to Ta after RCPT TO command is submitted -.It data Ta after DATA command is submitted -.It commit Ta after message is fully is submitted -.El -.Pp -At each phase, various conditions may be matched. -The fcrdns, rdns, and src data are available in all phases, -but other data must have been already submitted before they are available. -.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent -.It fcrdns Ta forward-confirmed reverse DNS is valid -.It rdns Ta session has a reverse DNS -.It rdns Pf < Ar table Ns > Ta session has a reverse DNS in table -.It src Pf < Ar table Ns > Ta source address is in table -.It helo Pf < Ar table Ns > Ta helo name is in table -.It auth Ta session is authenticated -.It auth Pf < Ar table Ns > Ta session username is in table -.It mail-from Pf < Ar table Ns > Ta sender address is in table -.It rcpt-to Pf < Ar table Ns > Ta recipient address is in table -.El -.Pp -These conditions may all be negated by prefixing them with an exclamation mark: -.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent -.It !fcrdns Ta forward-confirmed reverse DNS is invalid -.El -.Pp -Any conditions using a table may indicate that tables hold regex by -prefixing the table name with the keyword regex. -.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent -.It helo regex Pf < Ar table Ns > Ta helo name matches a regex in table -.El -.Pp -Finally, a number of decisions may be taken: -.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent -.It bypass Ta the session or transaction bypasses filters -.It disconnect Ar message Ta the session is disconnected with message -.It junk Ta the session or transaction is junked, i.e., an -.Ql X-Spam: yes -header is added to any messages -.It reject Ar message Ta the command is rejected with message -.It rewrite Ar value Ta the command parameter is rewritten with value -.El -.Pp -Decisions that involve a message require that the message be RFC valid, -meaning that they should either start with a 4xx or 5xx status code. -Descisions can be taken at any phase, -though junking can only happen before a message is committed. -.Ss FORMAT SPECIFIERS -Some configuration directives support expansion of their parameters at runtime. -Such directives (for example -.Ic action Cm maildir , -.Ic action Cm mda ) -may use format specifiers which are expanded before delivery or -relaying. -The following formats are currently supported: -.Bl -column %{user.directory} -offset indent -.It %{sender} Ta sender email address, may be empty string -.It %{sender.user} Ta user part of the sender email address, may be empty -.It %{sender.domain} Ta domain part of the sender email address, may be empty -.It %{rcpt} Ta recipient email address -.It %{rcpt.user} Ta user part of the recipient email address -.It %{rcpt.domain} Ta domain part of the recipient email address -.It %{dest} Ta recipient email address after expansion -.It %{dest.user} Ta user part after expansion -.It %{dest.domain} Ta domain part after expansion -.It %{user.username} Ta local user -.It %{user.directory} Ta home directory of the local user -.It %{mbox.from} Ta name used in mbox From separator lines -.It %{mda} Ta mda command, only available for mda wrappers -.El -.Pp -Expansion formats also support partial expansion using the optional -bracket notations with substring offset. -For example, with recipient domain -.Dq example.org : -.Bl -column %{rcpt.domain[0:-4]} -offset indent -.It %{rcpt.domain[0]} Ta expands to Dq e -.It %{rcpt.domain[1]} Ta expands to Dq x -.It %{rcpt.domain[8:]} Ta expands to Dq org -.It %{rcpt.domain[-3:]} Ta expands to Dq org -.It %{rcpt.domain[0:6]} Ta expands to Dq example -.It %{rcpt.domain[0:-4]} Ta expands to Dq example -.El -.Pp -In addition, modifiers may be applied to the token. -For example, with recipient -.Dq User+Tag@Example.org : -.Bl -column %{rcpt:lowercase|strip} -offset indent -.It %{rcpt:lowercase} Ta expands to Dq user+tag@example.org -.It %{rcpt:uppercase} Ta expands to Dq USER+TAG@EXAMPLE.ORG -.It %{rcpt:strip} Ta expands to Dq User@Example.org -.It %{rcpt:lowercase|strip} Ta expands to Dq user@example.org -.El -.Pp -For security concerns, expanded values are sanitized and potentially -dangerous characters are replaced with -.Sq \&: . -In situations where they are desirable, the -.Dq raw -modifier may be applied. -For example, with recipient -.Dq user+t?g@example.org : -.Bl -column %{rcpt:raw} -offset indent -.It %{rcpt} Ta expands to Dq user+t:g@example.org -.It %{rcpt:raw} Ta expands to Dq user+t?g@example.org -.El -.Sh FILES -.Bl -tag -width "/etc/mail/smtpd.confXXX" -compact -.It Pa /etc/mail/smtpd.conf -Default -.Xr smtpd 8 -configuration file. -.It Pa /etc/mail/mailname -If this file exists, -the first line is used as the server name. -Otherwise, the server name is derived from the local hostname returned by -.Xr gethostname 3 , -either directly if it is a fully qualified domain name, -or by retrieving the associated canonical name through -.Xr getaddrinfo 3 . -.It Pa /var/run/smtpd.sock -Unix domain socket for incoming SMTP connections. -.It Pa /var/spool/smtpd/ -Spool directories for mail during processing. -.El -.Sh EXAMPLES -The default -.Nm -file which ships with -.Ox -listens on the loopback network interface -.Pq Pa lo0 -and allows for mail from users and daemons on the local machine, -as well as permitting email to remote servers. -Some more complex configurations are given below. -.Pp -This first example is the same as the default configuration, -but all outgoing mail is forwarded to a remote SMTP server. -A secrets file is needed to specify a username and password: -.Bd -literal -offset indent -# touch /etc/mail/secrets -# chmod 640 /etc/mail/secrets -# chown root:_smtpd /etc/mail/secrets -# echo "bob username:password" > /etc/mail/secrets -.Ed -.Pp -.Nm -would look like this: -.Bd -literal -offset indent -table aliases file:/etc/mail/aliases -table secrets file:/etc/mail/secrets - -listen on lo0 - -action "local_mail" mbox alias -action "outbound" relay host smtp+tls://bob@smtp.example.com \e - auth - -match from local for local action "local_mail" -match from local for any action "outbound" -.Ed -.Pp -In this second example, -the aim is to permit mail delivery and relaying only for users that can authenticate -(using their normal login credentials). -An RSA certificate must be provided to prove the server's identity. -The mail server listens on all interfaces the default routes point to. -Mail with a local destination is sent to an external MDA. -First, the RSA certificate is created: -.Bd -literal -offset indent -# openssl genrsa \-out /etc/ssl/private/mail.example.com.key 4096 -# openssl req \-new \-x509 \-key /etc/ssl/private/mail.example.com.key \e - \-out /etc/ssl/mail.example.com.crt \-days 365 -# chmod 600 /etc/ssl/mail.example.com.crt -# chmod 600 /etc/ssl/private/mail.example.com.key -.Ed -.Pp -In the example above, -a certificate valid for one year was created. -The configuration file would look like this: -.Bd -literal -offset indent -pki mail.example.com cert "/etc/ssl/mail.example.com.crt" -pki mail.example.com key "/etc/ssl/private/mail.example.com.key" - -table aliases file:/etc/mail/aliases - -listen on lo0 -listen on egress tls pki mail.example.com auth - -action mda_with_aliases mda "/path/to/mda \-f \-" alias -action mda_without_aliases mda "/path/to/mda \-f \-" -action "outbound" relay - -match for local action mda_with_aliases -match from any for domain example.com action mda_without_aliases -match for any action "outbound" -match auth from any for any action "outbound" -.Ed -.Pp -For sites that wish to sign messages using DKIM, -the following example uses -.Sy opensmtpd-filter-dkimsign -for DKIM signing: -.Bd -literal -offset indent -table aliases file:/etc/mail/aliases - -filter "dkimsign" proc-exec "filter-dkimsign -d -s \e - -k /etc/mail/dkim/private.key" user _dkimsign group _dkimsign - -listen on socket filter "dkimsign" -listen on lo0 filter "dkimsign" - -action "local_mail" mbox alias -action "outbound" relay - -match for local action "local_mail" -match for any action "outbound" -.Ed -.Pp -Alternatively, the -.Sy opensmtpd-filter-rspamd -package may be used to provide integration with -.Sy rspamd , -a third-party daemon which provides multiple antispam features -as well as DKIM signing. -As well as configuring -.Sy rspamd -itself, -it requires use of the -.Cm proc-exec -keyword: -.Bd -literal -offset indent -filter "rspamd" proc-exec "filter-rspamd" -.Ed -.Pp -Sites that accept non-local messages may be able to cut down on the -volume of spam received by rejecting forged messages that claim -to be from the local domain. -The following example uses a list table -.Em other-relays -to specify the IP addresses of relays that may legitimately -originate mail with the owner's domain as the sender. -.Bd -literal -offset indent -table aliases file:/etc/mail/aliases -table other-relays file:/etc/mail/other-relays - -listen on lo0 -listen on egress - -action "local_mail" mbox alias -action "outbound" relay - -match for local action "local_mail" -match for any action "outbound" -match !from src mail\-from "@example.com" for any \e - reject -match from any for domain example.com action "local_mail" -.Ed -.Sh SEE ALSO -.Xr mailer.conf 5 , -.Xr table 5 , -.Xr makemap 8 , -.Xr smtpd 8 -.Sh HISTORY -.Xr smtpd 8 -first appeared in -.Ox 4.6 . diff --git a/smtpd/smtpd.h b/smtpd/smtpd.h deleted file mode 100644 index 4385f747..00000000 --- a/smtpd/smtpd.h +++ /dev/null @@ -1,1784 +0,0 @@ -/* $OpenBSD: smtpd.h,v 1.656 2020/04/08 07:30:44 eric Exp $ */ - -/* - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include - -#include "openbsd-compat.h" - -#ifndef nitems -#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) -#endif - -#include -#include -#include - -#include "smtpd-defines.h" -#include "smtpd-api.h" -#include "ioev.h" - -#define CHECK_IMSG_DATA_SIZE(imsg, expected_sz) do { \ - if ((imsg)->hdr.len - IMSG_HEADER_SIZE != (expected_sz)) \ - fatalx("smtpd: imsg %d: data size expected %zd got %zd",\ - (imsg)->hdr.type, \ - (expected_sz), (imsg)->hdr.len - IMSG_HEADER_SIZE); \ -} while (0) - -#ifndef SMTPD_CONFDIR -#define SMTPD_CONFDIR "/etc" -#endif -#define CONF_FILE SMTPD_CONFDIR "/smtpd.conf" -#define MAILNAME_FILE SMTPD_CONFDIR "/mailname" -#ifndef CA_FILE -#define CA_FILE "/etc/ssl/cert.pem" -#endif - -#define PROC_COUNT 7 - -#define MAX_HOPS_COUNT 100 -#define DEFAULT_MAX_BODY_SIZE (35*1024*1024) - -#define EXPAND_BUFFER 1024 - -#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) -#ifndef SMTPD_USER -#define SMTPD_USER "_smtpd" -#endif -#ifndef SMTPD_QUEUE_USER -#define SMTPD_QUEUE_USER "_smtpq" -#endif -#ifndef SMTPD_SOCKDIR -#define SMTPD_SOCKDIR "/var/run" -#endif -#define SMTPD_SOCKET SMTPD_SOCKDIR "/smtpd.sock" -#ifndef SMTPD_NAME -#define SMTPD_NAME "OpenSMTPD" -#endif -#define SMTPD_VERSION "6.7.0-portable" -#define SMTPD_SESSION_TIMEOUT 300 -#define SMTPD_BACKLOG 5 - -#ifndef PATH_SMTPCTL -#define PATH_SMTPCTL "/usr/sbin/smtpctl" -#endif - -#define PATH_OFFLINE "/offline" -#define PATH_PURGE "/purge" -#define PATH_TEMPORARY "/temporary" - -#ifndef PATH_LIBEXEC -#define PATH_LIBEXEC "/usr/local/libexec/smtpd" -#endif - - -/* - * RFC 5322 defines these characters as valid, some of them are - * potentially dangerous and need to be escaped. - */ -#define MAILADDR_ALLOWED "!#$%&'*/?^`{|}~+-=_" -#define MAILADDR_ESCAPE "!#$%&'*?`{|}~" - - -#define F_STARTTLS 0x01 -#define F_SMTPS 0x02 -#define F_SSL (F_STARTTLS | F_SMTPS) -#define F_AUTH 0x08 -#define F_STARTTLS_REQUIRE 0x20 -#define F_AUTH_REQUIRE 0x40 -#define F_MASK_SOURCE 0x100 -#define F_TLS_VERIFY 0x200 -#define F_EXT_DSN 0x400 -#define F_RECEIVEDAUTH 0x800 -#define F_MASQUERADE 0x1000 -#define F_FILTERED 0x2000 -#define F_PROXY 0x4000 - -#define RELAY_TLS_OPPORTUNISTIC 0 -#define RELAY_TLS_STARTTLS 1 -#define RELAY_TLS_SMTPS 2 -#define RELAY_TLS_NO 3 - -#define RELAY_AUTH 0x08 -#define RELAY_LMTP 0x80 -#define RELAY_TLS_VERIFY 0x200 - -#define MTA_EXT_DSN 0x400 - - -#define P_SENDMAIL 0 -#define P_NEWALIASES 1 -#define P_MAKEMAP 2 - -#define CERT_ERROR -1 -#define CERT_OK 0 -#define CERT_NOCA 1 -#define CERT_NOCERT 2 -#define CERT_INVALID 3 - -struct userinfo { - char username[SMTPD_VUSERNAME_SIZE]; - char directory[PATH_MAX]; - uid_t uid; - gid_t gid; -}; - -struct netaddr { - struct sockaddr_storage ss; - int bits; -}; - -struct relayhost { - uint16_t flags; - int tls; - char hostname[HOST_NAME_MAX+1]; - uint16_t port; - char authlabel[PATH_MAX]; -}; - -struct credentials { - char username[LINE_MAX]; - char password[LINE_MAX]; -}; - -struct destination { - char name[HOST_NAME_MAX+1]; -}; - -struct source { - struct sockaddr_storage addr; -}; - -struct addrname { - struct sockaddr_storage addr; - char name[HOST_NAME_MAX+1]; -}; - -union lookup { - struct expand *expand; - struct credentials creds; - struct netaddr netaddr; - struct source source; - struct destination domain; - struct userinfo userinfo; - struct mailaddr mailaddr; - struct addrname addrname; - struct maddrmap *maddrmap; - char relayhost[LINE_MAX]; -}; - -/* - * Bump IMSG_VERSION whenever a change is made to enum imsg_type. - * This will ensure that we can never use a wrong version of smtpctl with smtpd. - */ -#define IMSG_VERSION 16 - -enum imsg_type { - IMSG_NONE, - - IMSG_CTL_OK, - IMSG_CTL_FAIL, - - IMSG_CTL_GET_DIGEST, - IMSG_CTL_GET_STATS, - IMSG_CTL_LIST_MESSAGES, - IMSG_CTL_LIST_ENVELOPES, - IMSG_CTL_MTA_SHOW_HOSTS, - IMSG_CTL_MTA_SHOW_RELAYS, - IMSG_CTL_MTA_SHOW_ROUTES, - IMSG_CTL_MTA_SHOW_HOSTSTATS, - IMSG_CTL_MTA_BLOCK, - IMSG_CTL_MTA_UNBLOCK, - IMSG_CTL_MTA_SHOW_BLOCK, - IMSG_CTL_PAUSE_EVP, - IMSG_CTL_PAUSE_MDA, - IMSG_CTL_PAUSE_MTA, - IMSG_CTL_PAUSE_SMTP, - IMSG_CTL_PROFILE, - IMSG_CTL_PROFILE_DISABLE, - IMSG_CTL_PROFILE_ENABLE, - IMSG_CTL_RESUME_EVP, - IMSG_CTL_RESUME_MDA, - IMSG_CTL_RESUME_MTA, - IMSG_CTL_RESUME_SMTP, - IMSG_CTL_RESUME_ROUTE, - IMSG_CTL_REMOVE, - IMSG_CTL_SCHEDULE, - IMSG_CTL_SHOW_STATUS, - IMSG_CTL_TRACE_DISABLE, - IMSG_CTL_TRACE_ENABLE, - IMSG_CTL_UPDATE_TABLE, - IMSG_CTL_VERBOSE, - IMSG_CTL_DISCOVER_EVPID, - IMSG_CTL_DISCOVER_MSGID, - - IMSG_CTL_SMTP_SESSION, - - IMSG_GETADDRINFO, - IMSG_GETADDRINFO_END, - IMSG_GETNAMEINFO, - IMSG_RES_QUERY, - - IMSG_CERT_INIT, - IMSG_CERT_CERTIFICATE, - IMSG_CERT_VERIFY, - - IMSG_SETUP_KEY, - IMSG_SETUP_PEER, - IMSG_SETUP_DONE, - - IMSG_CONF_START, - IMSG_CONF_END, - - IMSG_STAT_INCREMENT, - IMSG_STAT_DECREMENT, - IMSG_STAT_SET, - - IMSG_LKA_AUTHENTICATE, - IMSG_LKA_OPEN_FORWARD, - IMSG_LKA_ENVELOPE_SUBMIT, - IMSG_LKA_ENVELOPE_COMMIT, - - IMSG_QUEUE_DELIVER, - IMSG_QUEUE_DELIVERY_OK, - IMSG_QUEUE_DELIVERY_TEMPFAIL, - IMSG_QUEUE_DELIVERY_PERMFAIL, - IMSG_QUEUE_DELIVERY_LOOP, - IMSG_QUEUE_DISCOVER_EVPID, - IMSG_QUEUE_DISCOVER_MSGID, - IMSG_QUEUE_ENVELOPE_ACK, - IMSG_QUEUE_ENVELOPE_COMMIT, - IMSG_QUEUE_ENVELOPE_REMOVE, - IMSG_QUEUE_ENVELOPE_SCHEDULE, - IMSG_QUEUE_ENVELOPE_SUBMIT, - IMSG_QUEUE_HOLDQ_HOLD, - IMSG_QUEUE_HOLDQ_RELEASE, - IMSG_QUEUE_MESSAGE_COMMIT, - IMSG_QUEUE_MESSAGE_ROLLBACK, - IMSG_QUEUE_SMTP_SESSION, - IMSG_QUEUE_TRANSFER, - - IMSG_MDA_DELIVERY_OK, - IMSG_MDA_DELIVERY_TEMPFAIL, - IMSG_MDA_DELIVERY_PERMFAIL, - IMSG_MDA_DELIVERY_LOOP, - IMSG_MDA_DELIVERY_HOLD, - IMSG_MDA_DONE, - IMSG_MDA_FORK, - IMSG_MDA_HOLDQ_RELEASE, - IMSG_MDA_LOOKUP_USERINFO, - IMSG_MDA_KILL, - IMSG_MDA_OPEN_MESSAGE, - - IMSG_MTA_DELIVERY_OK, - IMSG_MTA_DELIVERY_TEMPFAIL, - IMSG_MTA_DELIVERY_PERMFAIL, - IMSG_MTA_DELIVERY_LOOP, - IMSG_MTA_DELIVERY_HOLD, - IMSG_MTA_DNS_HOST, - IMSG_MTA_DNS_HOST_END, - IMSG_MTA_DNS_MX, - IMSG_MTA_DNS_MX_PREFERENCE, - IMSG_MTA_HOLDQ_RELEASE, - IMSG_MTA_LOOKUP_CREDENTIALS, - IMSG_MTA_LOOKUP_SOURCE, - IMSG_MTA_LOOKUP_HELO, - IMSG_MTA_LOOKUP_SMARTHOST, - IMSG_MTA_OPEN_MESSAGE, - IMSG_MTA_SCHEDULE, - - IMSG_SCHED_ENVELOPE_BOUNCE, - IMSG_SCHED_ENVELOPE_DELIVER, - IMSG_SCHED_ENVELOPE_EXPIRE, - IMSG_SCHED_ENVELOPE_INJECT, - IMSG_SCHED_ENVELOPE_REMOVE, - IMSG_SCHED_ENVELOPE_TRANSFER, - - IMSG_SMTP_AUTHENTICATE, - IMSG_SMTP_MESSAGE_COMMIT, - IMSG_SMTP_MESSAGE_CREATE, - IMSG_SMTP_MESSAGE_ROLLBACK, - IMSG_SMTP_MESSAGE_OPEN, - IMSG_SMTP_CHECK_SENDER, - IMSG_SMTP_EXPAND_RCPT, - IMSG_SMTP_LOOKUP_HELO, - - IMSG_SMTP_REQ_CONNECT, - IMSG_SMTP_REQ_HELO, - IMSG_SMTP_REQ_MAIL, - IMSG_SMTP_REQ_RCPT, - IMSG_SMTP_REQ_DATA, - IMSG_SMTP_REQ_EOM, - IMSG_SMTP_EVENT_RSET, - IMSG_SMTP_EVENT_COMMIT, - IMSG_SMTP_EVENT_ROLLBACK, - IMSG_SMTP_EVENT_DISCONNECT, - - IMSG_LKA_PROCESSOR_FORK, - IMSG_LKA_PROCESSOR_ERRFD, - - IMSG_REPORT_SMTP_LINK_CONNECT, - IMSG_REPORT_SMTP_LINK_DISCONNECT, - IMSG_REPORT_SMTP_LINK_GREETING, - IMSG_REPORT_SMTP_LINK_IDENTIFY, - IMSG_REPORT_SMTP_LINK_TLS, - IMSG_REPORT_SMTP_LINK_AUTH, - IMSG_REPORT_SMTP_TX_RESET, - IMSG_REPORT_SMTP_TX_BEGIN, - IMSG_REPORT_SMTP_TX_MAIL, - IMSG_REPORT_SMTP_TX_RCPT, - IMSG_REPORT_SMTP_TX_ENVELOPE, - IMSG_REPORT_SMTP_TX_DATA, - IMSG_REPORT_SMTP_TX_COMMIT, - IMSG_REPORT_SMTP_TX_ROLLBACK, - IMSG_REPORT_SMTP_PROTOCOL_CLIENT, - IMSG_REPORT_SMTP_PROTOCOL_SERVER, - IMSG_REPORT_SMTP_FILTER_RESPONSE, - IMSG_REPORT_SMTP_TIMEOUT, - - IMSG_FILTER_SMTP_BEGIN, - IMSG_FILTER_SMTP_END, - IMSG_FILTER_SMTP_PROTOCOL, - IMSG_FILTER_SMTP_DATA_BEGIN, - IMSG_FILTER_SMTP_DATA_END, - - IMSG_CA_RSA_PRIVENC, - IMSG_CA_RSA_PRIVDEC, - IMSG_CA_ECDSA_SIGN, -}; - -enum smtp_proc_type { - PROC_PARENT = 0, - PROC_LKA, - PROC_QUEUE, - PROC_CONTROL, - PROC_SCHEDULER, - PROC_PONY, - PROC_CA, - PROC_PROCESSOR, - PROC_CLIENT, -}; - -enum table_type { - T_NONE = 0, - T_DYNAMIC = 0x01, /* table with external source */ - T_LIST = 0x02, /* table holding a list */ - T_HASH = 0x04, /* table holding a hash table */ -}; - -struct table { - char t_name[LINE_MAX]; - enum table_type t_type; - char t_config[PATH_MAX]; - - void *t_handle; - struct table_backend *t_backend; -}; - -struct table_backend { - const char *name; - const unsigned int services; - int (*config)(struct table *); - int (*add)(struct table *, const char *, const char *); - void (*dump)(struct table *); - int (*open)(struct table *); - int (*update)(struct table *); - void (*close)(struct table *); - int (*lookup)(struct table *, enum table_service, const char *, char **); - int (*fetch)(struct table *, enum table_service, char **); -}; - - -enum bounce_type { - B_FAILED, - B_DELAYED, - B_DELIVERED -}; - -enum dsn_ret { - DSN_RETFULL = 1, - DSN_RETHDRS -}; - -struct delivery_bounce { - enum bounce_type type; - time_t delay; - time_t ttl; - enum dsn_ret dsn_ret; - int mta_without_dsn; -}; - -enum expand_type { - EXPAND_INVALID, - EXPAND_USERNAME, - EXPAND_FILENAME, - EXPAND_FILTER, - EXPAND_INCLUDE, - EXPAND_ADDRESS, - EXPAND_ERROR, -}; - -enum filter_phase { - FILTER_CONNECT, - FILTER_HELO, - FILTER_EHLO, - FILTER_STARTTLS, - FILTER_AUTH, - FILTER_MAIL_FROM, - FILTER_RCPT_TO, - FILTER_DATA, - FILTER_DATA_LINE, - FILTER_RSET, - FILTER_QUIT, - FILTER_NOOP, - FILTER_HELP, - FILTER_WIZ, - FILTER_COMMIT, - FILTER_PHASES_COUNT /* must be last */ -}; - -struct expandnode { - RB_ENTRY(expandnode) entry; - TAILQ_ENTRY(expandnode) tq_entry; - enum expand_type type; - int sameuser; - int realuser; - int forwarded; - struct rule *rule; - struct expandnode *parent; - unsigned int depth; - union { - /* - * user field handles both expansion user and system user - * so we MUST make it large enough to fit a mailaddr user - */ - char user[SMTPD_MAXLOCALPARTSIZE]; - char buffer[EXPAND_BUFFER]; - struct mailaddr mailaddr; - } u; - char subaddress[SMTPD_SUBADDRESS_SIZE]; -}; - -struct expand { - RB_HEAD(expandtree, expandnode) tree; - TAILQ_HEAD(xnodes, expandnode) *queue; - size_t nb_nodes; - struct rule *rule; - struct expandnode *parent; -}; - -struct maddrnode { - TAILQ_ENTRY(maddrnode) entries; - struct mailaddr mailaddr; -}; - -struct maddrmap { - TAILQ_HEAD(xmaddr, maddrnode) queue; -}; - -#define DSN_SUCCESS 0x01 -#define DSN_FAILURE 0x02 -#define DSN_DELAY 0x04 -#define DSN_NEVER 0x08 - -#define DSN_ENVID_LEN 100 - -#define SMTPD_ENVELOPE_VERSION 3 -struct envelope { - TAILQ_ENTRY(envelope) entry; - - char dispatcher[HOST_NAME_MAX+1]; - - char tag[SMTPD_TAG_SIZE]; - - uint32_t version; - uint64_t id; - enum envelope_flags flags; - - char smtpname[HOST_NAME_MAX+1]; - char helo[HOST_NAME_MAX+1]; - char hostname[HOST_NAME_MAX+1]; - char username[SMTPD_MAXMAILADDRSIZE]; - char errorline[LINE_MAX]; - struct sockaddr_storage ss; - - struct mailaddr sender; - struct mailaddr rcpt; - struct mailaddr dest; - - char mda_user[SMTPD_VUSERNAME_SIZE]; - char mda_subaddress[SMTPD_SUBADDRESS_SIZE]; - char mda_exec[LINE_MAX]; - - enum delivery_type type; - union { - struct delivery_bounce bounce; - } agent; - - uint16_t retry; - time_t creation; - time_t ttl; - time_t lasttry; - time_t nexttry; - time_t lastbounce; - - struct mailaddr dsn_orcpt; - char dsn_envid[DSN_ENVID_LEN+1]; - uint8_t dsn_notify; - enum dsn_ret dsn_ret; - - uint8_t esc_class; - uint8_t esc_code; -}; - -struct listener { - uint16_t flags; - int fd; - struct sockaddr_storage ss; - in_port_t port; - struct timeval timeout; - struct event ev; - char filter_name[PATH_MAX]; - char pki_name[PATH_MAX]; - char ca_name[PATH_MAX]; - char tag[SMTPD_TAG_SIZE]; - char authtable[LINE_MAX]; - char hostname[HOST_NAME_MAX+1]; - char hostnametable[PATH_MAX]; - char sendertable[PATH_MAX]; - - TAILQ_ENTRY(listener) entry; - - int local; /* there must be a better way */ -}; - -struct smtpd { - char sc_conffile[PATH_MAX]; - size_t sc_maxsize; - -#define SMTPD_OPT_VERBOSE 0x00000001 -#define SMTPD_OPT_NOACTION 0x00000002 - uint32_t sc_opts; - -#define SMTPD_EXITING 0x00000001 /* unused */ -#define SMTPD_MDA_PAUSED 0x00000002 -#define SMTPD_MTA_PAUSED 0x00000004 -#define SMTPD_SMTP_PAUSED 0x00000008 -#define SMTPD_MDA_BUSY 0x00000010 -#define SMTPD_MTA_BUSY 0x00000020 -#define SMTPD_BOUNCE_BUSY 0x00000040 -#define SMTPD_SMTP_DISABLED 0x00000080 - uint32_t sc_flags; - -#define QUEUE_COMPRESSION 0x00000001 -#define QUEUE_ENCRYPTION 0x00000002 -#define QUEUE_EVPCACHE 0x00000004 - uint32_t sc_queue_flags; - char *sc_queue_key; - size_t sc_queue_evpcache_size; - - size_t sc_session_max_rcpt; - size_t sc_session_max_mails; - - struct dict *sc_mda_wrappers; - size_t sc_mda_max_session; - size_t sc_mda_max_user_session; - size_t sc_mda_task_hiwat; - size_t sc_mda_task_lowat; - size_t sc_mda_task_release; - - size_t sc_mta_max_deferred; - - size_t sc_scheduler_max_inflight; - size_t sc_scheduler_max_evp_batch_size; - size_t sc_scheduler_max_msg_batch_size; - size_t sc_scheduler_max_schedule; - - struct dict *sc_filter_processes_dict; - - int sc_ttl; -#define MAX_BOUNCE_WARN 4 - time_t sc_bounce_warn[MAX_BOUNCE_WARN]; - char sc_hostname[HOST_NAME_MAX+1]; - struct stat_backend *sc_stat; - struct compress_backend *sc_comp; - - time_t sc_uptime; - - /* This is a listener for a local socket used by smtp_enqueue(). */ - struct listener *sc_sock_listener; - - TAILQ_HEAD(listenerlist, listener) *sc_listeners; - - TAILQ_HEAD(rulelist, rule) *sc_rules; - - - struct dict *sc_filters_dict; - struct dict *sc_dispatchers; - struct dispatcher *sc_dispatcher_bounce; - - struct dict *sc_ca_dict; - struct dict *sc_pki_dict; - struct dict *sc_ssl_dict; - - struct dict *sc_tables_dict; /* keyed lookup */ - - struct dict *sc_limits_dict; - - char *sc_tls_ciphers; - - char *sc_subaddressing_delim; - - char *sc_srs_key; - char *sc_srs_key_backup; - int sc_srs_ttl; -}; - -#define TRACE_DEBUG 0x0001 -#define TRACE_IMSG 0x0002 -#define TRACE_IO 0x0004 -#define TRACE_SMTP 0x0008 -#define TRACE_FILTERS 0x0010 -#define TRACE_MTA 0x0020 -#define TRACE_BOUNCE 0x0040 -#define TRACE_SCHEDULER 0x0080 -#define TRACE_LOOKUP 0x0100 -#define TRACE_STAT 0x0200 -#define TRACE_RULES 0x0400 -#define TRACE_MPROC 0x0800 -#define TRACE_EXPAND 0x1000 -#define TRACE_TABLES 0x2000 -#define TRACE_QUEUE 0x4000 - -#define PROFILE_TOSTAT 0x0001 -#define PROFILE_IMSG 0x0002 -#define PROFILE_QUEUE 0x0004 - -struct forward_req { - uint64_t id; - uint8_t status; - - char user[SMTPD_VUSERNAME_SIZE]; - uid_t uid; - gid_t gid; - char directory[PATH_MAX]; -}; - -struct deliver { - char dispatcher[EXPAND_BUFFER]; - - struct mailaddr sender; - struct mailaddr rcpt; - struct mailaddr dest; - - char mda_subaddress[SMTPD_SUBADDRESS_SIZE]; - char mda_exec[LINE_MAX]; - - struct userinfo userinfo; -}; - -struct mta_host { - SPLAY_ENTRY(mta_host) entry; - struct sockaddr *sa; - char *ptrname; - int refcount; - size_t nconn; - time_t lastconn; - time_t lastptrquery; - -#define HOST_IGNORE 0x01 - int flags; -}; - -struct mta_mx { - TAILQ_ENTRY(mta_mx) entry; - struct mta_host *host; - char *mxname; - int preference; -}; - -struct mta_domain { - SPLAY_ENTRY(mta_domain) entry; - char *name; - int as_host; - TAILQ_HEAD(, mta_mx) mxs; - int mxstatus; - int refcount; - size_t nconn; - time_t lastconn; - time_t lastmxquery; -}; - -struct mta_source { - SPLAY_ENTRY(mta_source) entry; - struct sockaddr *sa; - int refcount; - size_t nconn; - time_t lastconn; -}; - -struct mta_connector { - struct mta_source *source; - struct mta_relay *relay; - -#define CONNECTOR_ERROR_FAMILY 0x0001 -#define CONNECTOR_ERROR_SOURCE 0x0002 -#define CONNECTOR_ERROR_MX 0x0004 -#define CONNECTOR_ERROR_ROUTE_NET 0x0008 -#define CONNECTOR_ERROR_ROUTE_SMTP 0x0010 -#define CONNECTOR_ERROR_ROUTE 0x0018 -#define CONNECTOR_ERROR_BLOCKED 0x0020 -#define CONNECTOR_ERROR 0x00ff - -#define CONNECTOR_LIMIT_HOST 0x0100 -#define CONNECTOR_LIMIT_ROUTE 0x0200 -#define CONNECTOR_LIMIT_SOURCE 0x0400 -#define CONNECTOR_LIMIT_RELAY 0x0800 -#define CONNECTOR_LIMIT_CONN 0x1000 -#define CONNECTOR_LIMIT_DOMAIN 0x2000 -#define CONNECTOR_LIMIT 0xff00 - -#define CONNECTOR_NEW 0x10000 -#define CONNECTOR_WAIT 0x20000 - int flags; - - int refcount; - size_t nconn; - time_t lastconn; -}; - -struct mta_route { - SPLAY_ENTRY(mta_route) entry; - uint64_t id; - struct mta_source *src; - struct mta_host *dst; -#define ROUTE_NEW 0x01 -#define ROUTE_RUNQ 0x02 -#define ROUTE_KEEPALIVE 0x04 -#define ROUTE_DISABLED 0xf0 -#define ROUTE_DISABLED_NET 0x10 -#define ROUTE_DISABLED_SMTP 0x20 - int flags; - int nerror; - int penalty; - int refcount; - size_t nconn; - time_t lastconn; - time_t lastdisc; - time_t lastpenalty; -}; - -struct mta_limits { - size_t maxconn_per_host; - size_t maxconn_per_route; - size_t maxconn_per_source; - size_t maxconn_per_connector; - size_t maxconn_per_relay; - size_t maxconn_per_domain; - - time_t conndelay_host; - time_t conndelay_route; - time_t conndelay_source; - time_t conndelay_connector; - time_t conndelay_relay; - time_t conndelay_domain; - - time_t discdelay_route; - - size_t max_mail_per_session; - time_t sessdelay_transaction; - time_t sessdelay_keepalive; - - size_t max_failures_per_session; - - int family; - - int task_hiwat; - int task_lowat; - int task_release; -}; - -struct mta_relay { - SPLAY_ENTRY(mta_relay) entry; - uint64_t id; - - struct dispatcher *dispatcher; - struct mta_domain *domain; - struct mta_limits *limits; - int tls; - int flags; - char *backupname; - int backuppref; - char *sourcetable; - uint16_t port; - char *pki_name; - char *ca_name; - char *authtable; - char *authlabel; - char *helotable; - char *heloname; - char *secret; - int srs; - - int state; - size_t ntask; - TAILQ_HEAD(, mta_task) tasks; - - struct tree connectors; - size_t sourceloop; - time_t lastsource; - time_t nextsource; - - int fail; - char *failstr; - -#define RELAY_WAIT_MX 0x01 -#define RELAY_WAIT_PREFERENCE 0x02 -#define RELAY_WAIT_SECRET 0x04 -#define RELAY_WAIT_LIMITS 0x08 -#define RELAY_WAIT_SOURCE 0x10 -#define RELAY_WAIT_CONNECTOR 0x20 -#define RELAY_WAIT_SMARTHOST 0x40 -#define RELAY_WAITMASK 0x7f - int status; - - int refcount; - size_t nconn; - size_t nconn_ready; - time_t lastconn; -}; - -struct mta_envelope { - TAILQ_ENTRY(mta_envelope) entry; - uint64_t id; - uint64_t session; - time_t creation; - char *smtpname; - char *dest; - char *rcpt; - struct mta_task *task; - int delivery; - - int ext; - char *dsn_orcpt; - char dsn_envid[DSN_ENVID_LEN+1]; - uint8_t dsn_notify; - enum dsn_ret dsn_ret; - - char status[LINE_MAX]; -}; - -struct mta_task { - TAILQ_ENTRY(mta_task) entry; - struct mta_relay *relay; - uint32_t msgid; - TAILQ_HEAD(, mta_envelope) envelopes; - char *sender; -}; - -struct passwd; - -struct queue_backend { - int (*init)(struct passwd *, int, const char *); -}; - -struct compress_backend { - size_t (*compress_chunk)(void *, size_t, void *, size_t); - size_t (*uncompress_chunk)(void *, size_t, void *, size_t); - int (*compress_file)(FILE *, FILE *); - int (*uncompress_file)(FILE *, FILE *); -}; - -/* auth structures */ -enum auth_type { - AUTH_BSD, - AUTH_PWD, -}; - -struct auth_backend { - int (*authenticate)(char *, char *); -}; - -struct scheduler_backend { - int (*init)(const char *); - - int (*insert)(struct scheduler_info *); - size_t (*commit)(uint32_t); - size_t (*rollback)(uint32_t); - - int (*update)(struct scheduler_info *); - int (*delete)(uint64_t); - int (*hold)(uint64_t, uint64_t); - int (*release)(int, uint64_t, int); - - int (*batch)(int, int*, size_t*, uint64_t*, int*); - - size_t (*messages)(uint32_t, uint32_t *, size_t); - size_t (*envelopes)(uint64_t, struct evpstate *, size_t); - int (*schedule)(uint64_t); - int (*remove)(uint64_t); - int (*suspend)(uint64_t); - int (*resume)(uint64_t); - int (*query)(uint64_t); -}; - -enum stat_type { - STAT_COUNTER, - STAT_TIMESTAMP, - STAT_TIMEVAL, - STAT_TIMESPEC, -}; - -struct stat_value { - enum stat_type type; - union stat_v { - size_t counter; - time_t timestamp; - struct timeval tv; - struct timespec ts; - } u; -}; - -#define STAT_KEY_SIZE 1024 -struct stat_kv { - void *iter; - char key[STAT_KEY_SIZE]; - struct stat_value val; -}; - -struct stat_backend { - void (*init)(void); - void (*close)(void); - void (*increment)(const char *, size_t); - void (*decrement)(const char *, size_t); - void (*set)(const char *, const struct stat_value *); - int (*iter)(void **, char **, struct stat_value *); -}; - -struct stat_digest { - time_t startup; - time_t timestamp; - - size_t clt_connect; - size_t clt_disconnect; - - size_t evp_enqueued; - size_t evp_dequeued; - - size_t evp_expired; - size_t evp_removed; - size_t evp_bounce; - - size_t dlv_ok; - size_t dlv_permfail; - size_t dlv_tempfail; - size_t dlv_loop; -}; - - -struct mproc { - pid_t pid; - char *name; - int proc; - void (*handler)(struct mproc *, struct imsg *); - struct imsgbuf imsgbuf; - - char *m_buf; - size_t m_alloc; - size_t m_pos; - uint32_t m_type; - uint32_t m_peerid; - pid_t m_pid; - int m_fd; - - int enable; - short events; - struct event ev; - void *data; -}; - -struct msg { - const uint8_t *pos; - const uint8_t *end; -}; - -extern enum smtp_proc_type smtpd_process; - -extern int tracing; -extern int foreground_log; -extern int profiling; - -extern struct mproc *p_control; -extern struct mproc *p_parent; -extern struct mproc *p_lka; -extern struct mproc *p_queue; -extern struct mproc *p_scheduler; -extern struct mproc *p_pony; -extern struct mproc *p_ca; - -extern struct smtpd *env; -extern void (*imsg_callback)(struct mproc *, struct imsg *); - -/* inter-process structures */ - -struct bounce_req_msg { - uint64_t evpid; - time_t timestamp; - struct delivery_bounce bounce; -}; - -enum dns_error { - DNS_OK = 0, - DNS_RETRY, - DNS_EINVAL, - DNS_ENONAME, - DNS_ENOTFOUND, -}; - -enum lka_resp_status { - LKA_OK, - LKA_TEMPFAIL, - LKA_PERMFAIL -}; - -enum filter_type { - FILTER_TYPE_BUILTIN, - FILTER_TYPE_PROC, - FILTER_TYPE_CHAIN, -}; - -enum filter_subsystem { - FILTER_SUBSYSTEM_SMTP_IN = 1<<0, - FILTER_SUBSYSTEM_SMTP_OUT = 1<<1, -}; - -struct filter_proc { - const char *command; - const char *user; - const char *group; - const char *chroot; - int errfd; - enum filter_subsystem filter_subsystem; -}; - -struct filter_config { - char *name; - enum filter_subsystem filter_subsystem; - enum filter_type filter_type; - enum filter_phase phase; - char *reject; - char *disconnect; - char *rewrite; - char *report; - uint8_t junk; - uint8_t bypass; - char *proc; - - const char **chain; - size_t chain_size; - struct dict chain_procs; - - int8_t not_fcrdns; - int8_t fcrdns; - - int8_t not_rdns; - int8_t rdns; - - int8_t not_rdns_table; - struct table *rdns_table; - - int8_t not_rdns_regex; - struct table *rdns_regex; - - int8_t not_src_table; - struct table *src_table; - - int8_t not_src_regex; - struct table *src_regex; - - int8_t not_helo_table; - struct table *helo_table; - - int8_t not_helo_regex; - struct table *helo_regex; - - int8_t not_auth; - int8_t auth; - - int8_t not_auth_table; - struct table *auth_table; - - int8_t not_auth_regex; - struct table *auth_regex; - - int8_t not_mail_from_table; - struct table *mail_from_table; - - int8_t not_mail_from_regex; - struct table *mail_from_regex; - - int8_t not_rcpt_to_table; - struct table *rcpt_to_table; - - int8_t not_rcpt_to_regex; - struct table *rcpt_to_regex; - -}; - -enum filter_status { - FILTER_PROCEED, - FILTER_REWRITE, - FILTER_REJECT, - FILTER_DISCONNECT, - FILTER_JUNK, -}; - -enum ca_resp_status { - CA_OK, - CA_FAIL -}; - -enum mda_resp_status { - MDA_OK, - MDA_TEMPFAIL, - MDA_PERMFAIL -}; - -struct msg_walkinfo { - struct event ev; - uint32_t msgid; - uint32_t peerid; - size_t n_evp; - void *data; - int done; -}; - - -enum dispatcher_type { - DISPATCHER_LOCAL, - DISPATCHER_REMOTE, - DISPATCHER_BOUNCE, -}; - -struct dispatcher_local { - uint8_t is_mbox; /* only for MBOX */ - - uint8_t expand_only; - uint8_t forward_only; - - char *mda_wrapper; - char *command; - - char *table_alias; - char *table_virtual; - char *table_userbase; - - char *user; -}; - -struct dispatcher_remote { - char *helo; - char *helo_source; - - char *source; - - char *ca; - char *pki; - - char *mail_from; - - char *smarthost; - int smarthost_domain; - - char *auth; - int tls_required; - int tls_noverify; - - int backup; - char *backupmx; - - char *filtername; - - int srs; -}; - -struct dispatcher_bounce { -}; - -struct dispatcher { - enum dispatcher_type type; - union dispatcher_agent { - struct dispatcher_local local; - struct dispatcher_remote remote; - struct dispatcher_bounce bounce; - } u; - - time_t ttl; -}; - -struct rule { - TAILQ_ENTRY(rule) r_entry; - - uint8_t reject; - - int8_t flag_tag; - int8_t flag_from; - int8_t flag_for; - int8_t flag_from_rdns; - int8_t flag_from_socket; - - int8_t flag_tag_regex; - int8_t flag_from_regex; - int8_t flag_for_regex; - - int8_t flag_smtp_helo; - int8_t flag_smtp_starttls; - int8_t flag_smtp_auth; - int8_t flag_smtp_mail_from; - int8_t flag_smtp_rcpt_to; - - int8_t flag_smtp_helo_regex; - int8_t flag_smtp_starttls_regex; - int8_t flag_smtp_auth_regex; - int8_t flag_smtp_mail_from_regex; - int8_t flag_smtp_rcpt_to_regex; - - - char *table_tag; - char *table_from; - char *table_for; - - char *table_smtp_helo; - char *table_smtp_auth; - char *table_smtp_mail_from; - char *table_smtp_rcpt_to; - - char *dispatcher; -}; - - -/* aliases.c */ -int aliases_get(struct expand *, const char *); -int aliases_virtual_get(struct expand *, const struct mailaddr *); -int alias_parse(struct expandnode *, const char *); - - -/* auth.c */ -struct auth_backend *auth_backend_lookup(enum auth_type); - - -/* bounce.c */ -void bounce_add(uint64_t); -void bounce_fd(int); - - -/* ca.c */ -int ca(void); -int ca_X509_verify(void *, void *, const char *, const char *, const char **); -void ca_imsg(struct mproc *, struct imsg *); -void ca_init(void); -void ca_engine_init(void); - - -/* cert.c */ -int cert_init(const char *, int, - void (*)(void *, int, const char *, const void *, size_t), void *); -int cert_verify(const void *, const char *, int, void (*)(void *, int), void *); -void cert_dispatch_request(struct mproc *, struct imsg *); -void cert_dispatch_result(struct mproc *, struct imsg *); - - -/* compress_backend.c */ -struct compress_backend *compress_backend_lookup(const char *); -size_t compress_chunk(void *, size_t, void *, size_t); -size_t uncompress_chunk(void *, size_t, void *, size_t); -int compress_file(FILE *, FILE *); -int uncompress_file(FILE *, FILE *); - -/* config.c */ -#define PURGE_LISTENERS 0x01 -#define PURGE_TABLES 0x02 -#define PURGE_RULES 0x04 -#define PURGE_PKI 0x08 -#define PURGE_PKI_KEYS 0x10 -#define PURGE_DISPATCHERS 0x20 -#define PURGE_EVERYTHING 0xff -struct smtpd *config_default(void); -void purge_config(uint8_t); -void config_process(enum smtp_proc_type); -void config_peer(enum smtp_proc_type); - - -/* control.c */ -int control(void); -int control_create_socket(void); - - -/* crypto.c */ -int crypto_setup(const char *, size_t); -int crypto_encrypt_file(FILE *, FILE *); -int crypto_decrypt_file(FILE *, FILE *); -size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t); -size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t); - - -/* dns.c */ -void dns_imsg(struct mproc *, struct imsg *); - - -/* enqueue.c */ -int enqueue(int, char **, FILE *); - - -/* envelope.c */ -void envelope_set_errormsg(struct envelope *, char *, ...); -void envelope_set_esc_class(struct envelope *, enum enhanced_status_class); -void envelope_set_esc_code(struct envelope *, enum enhanced_status_code); -int envelope_load_buffer(struct envelope *, const char *, size_t); -int envelope_dump_buffer(const struct envelope *, char *, size_t); - - -/* expand.c */ -int expand_cmp(struct expandnode *, struct expandnode *); -void expand_insert(struct expand *, struct expandnode *); -struct expandnode *expand_lookup(struct expand *, struct expandnode *); -void expand_clear(struct expand *); -void expand_free(struct expand *); -int expand_line(struct expand *, const char *, int); -int expand_to_text(struct expand *, char *, size_t); -RB_PROTOTYPE(expandtree, expandnode, nodes, expand_cmp); - - -/* forward.c */ -int forwards_get(int, struct expand *); - - -/* limit.c */ -void limit_mta_set_defaults(struct mta_limits *); -int limit_mta_set(struct mta_limits *, const char*, int64_t); - - -/* lka.c */ -int lka(void); - - -/* lka_proc.c */ -int lka_proc_ready(void); -void lka_proc_forked(const char *, uint32_t, int); -void lka_proc_errfd(const char *, int); -struct io *lka_proc_get_io(const char *); - - -/* lka_report.c */ -void lka_report_init(void); -void lka_report_register_hook(const char *, const char *); -void lka_report_smtp_link_connect(const char *, struct timeval *, uint64_t, const char *, int, - const struct sockaddr_storage *, const struct sockaddr_storage *); -void lka_report_smtp_link_disconnect(const char *, struct timeval *, uint64_t); -void lka_report_smtp_link_greeting(const char *, uint64_t, struct timeval *, - const char *); -void lka_report_smtp_link_identify(const char *, struct timeval *, uint64_t, const char *, const char *); -void lka_report_smtp_link_tls(const char *, struct timeval *, uint64_t, const char *); -void lka_report_smtp_link_auth(const char *, struct timeval *, uint64_t, const char *, const char *); -void lka_report_smtp_tx_reset(const char *, struct timeval *, uint64_t, uint32_t); -void lka_report_smtp_tx_begin(const char *, struct timeval *, uint64_t, uint32_t); -void lka_report_smtp_tx_mail(const char *, struct timeval *, uint64_t, uint32_t, const char *, int); -void lka_report_smtp_tx_rcpt(const char *, struct timeval *, uint64_t, uint32_t, const char *, int); -void lka_report_smtp_tx_envelope(const char *, struct timeval *, uint64_t, uint32_t, uint64_t); -void lka_report_smtp_tx_commit(const char *, struct timeval *, uint64_t, uint32_t, size_t); -void lka_report_smtp_tx_data(const char *, struct timeval *, uint64_t, uint32_t, int); -void lka_report_smtp_tx_rollback(const char *, struct timeval *, uint64_t, uint32_t); -void lka_report_smtp_protocol_client(const char *, struct timeval *, uint64_t, const char *); -void lka_report_smtp_protocol_server(const char *, struct timeval *, uint64_t, const char *); -void lka_report_smtp_filter_response(const char *, struct timeval *, uint64_t, - int, int, const char *); -void lka_report_smtp_timeout(const char *, struct timeval *, uint64_t); -void lka_report_filter_report(uint64_t, const char *, int, const char *, - struct timeval *, const char *); -void lka_report_proc(const char *, const char *); - - -/* lka_filter.c */ -void lka_filter_init(void); -void lka_filter_register_hook(const char *, const char *); -void lka_filter_ready(void); -int lka_filter_proc_in_session(uint64_t, const char *); -void lka_filter_begin(uint64_t, const char *); -void lka_filter_end(uint64_t); -void lka_filter_protocol(uint64_t, enum filter_phase, const char *); -void lka_filter_data_begin(uint64_t); -void lka_filter_data_end(uint64_t); -int lka_filter_response(uint64_t, const char *, const char *); - - -/* lka_session.c */ -void lka_session(uint64_t, struct envelope *); -void lka_session_forward_reply(struct forward_req *, int); - - -/* log.c */ -void vlog(int, const char *, va_list); -void logit(int, const char *, ...) __attribute__((format (printf, 2, 3))); - - -/* mda.c */ -void mda_postfork(void); -void mda_postprivdrop(void); -void mda_imsg(struct mproc *, struct imsg *); - - -/* mda_mbox.c */ -void mda_mbox_init(struct deliver *); -void mda_mbox(struct deliver *); - - -/* mda_unpriv.c */ -void mda_unpriv(struct dispatcher *, struct deliver *, const char *, const char *); - - -/* mda_variables.c */ -ssize_t mda_expand_format(char *, size_t, const struct deliver *, - const struct userinfo *, const char *); - - -/* makemap.c */ -int makemap(int, int, char **); - - -/* mailaddr.c */ -int mailaddr_line(struct maddrmap *, const char *); -void maddrmap_init(struct maddrmap *); -void maddrmap_insert(struct maddrmap *, struct maddrnode *); -void maddrmap_free(struct maddrmap *); - - -/* mproc.c */ -int mproc_fork(struct mproc *, const char*, char **); -void mproc_init(struct mproc *, int); -void mproc_clear(struct mproc *); -void mproc_enable(struct mproc *); -void mproc_disable(struct mproc *); -void mproc_event_add(struct mproc *); -void m_compose(struct mproc *, uint32_t, uint32_t, pid_t, int, void *, size_t); -void m_composev(struct mproc *, uint32_t, uint32_t, pid_t, int, - const struct iovec *, int); -void m_forward(struct mproc *, struct imsg *); -void m_create(struct mproc *, uint32_t, uint32_t, pid_t, int); -void m_add(struct mproc *, const void *, size_t); -void m_add_int(struct mproc *, int); -void m_add_u32(struct mproc *, uint32_t); -void m_add_size(struct mproc *, size_t); -void m_add_time(struct mproc *, time_t); -void m_add_timeval(struct mproc *, struct timeval *tv); -void m_add_string(struct mproc *, const char *); -void m_add_data(struct mproc *, const void *, size_t); -void m_add_evpid(struct mproc *, uint64_t); -void m_add_msgid(struct mproc *, uint32_t); -void m_add_id(struct mproc *, uint64_t); -void m_add_sockaddr(struct mproc *, const struct sockaddr *); -void m_add_mailaddr(struct mproc *, const struct mailaddr *); -void m_add_envelope(struct mproc *, const struct envelope *); -void m_add_params(struct mproc *, struct dict *); -void m_close(struct mproc *); -void m_flush(struct mproc *); - -void m_msg(struct msg *, struct imsg *); -int m_is_eom(struct msg *); -void m_end(struct msg *); -void m_get_int(struct msg *, int *); -void m_get_size(struct msg *, size_t *); -void m_get_u32(struct msg *, uint32_t *); -void m_get_time(struct msg *, time_t *); -void m_get_timeval(struct msg *, struct timeval *); -void m_get_string(struct msg *, const char **); -void m_get_data(struct msg *, const void **, size_t *); -void m_get_evpid(struct msg *, uint64_t *); -void m_get_msgid(struct msg *, uint32_t *); -void m_get_id(struct msg *, uint64_t *); -void m_get_sockaddr(struct msg *, struct sockaddr *); -void m_get_mailaddr(struct msg *, struct mailaddr *); -void m_get_envelope(struct msg *, struct envelope *); -void m_get_params(struct msg *, struct dict *); -void m_clear_params(struct dict *); - - -/* mta.c */ -void mta_postfork(void); -void mta_postprivdrop(void); -void mta_imsg(struct mproc *, struct imsg *); -void mta_route_ok(struct mta_relay *, struct mta_route *); -void mta_route_error(struct mta_relay *, struct mta_route *); -void mta_route_down(struct mta_relay *, struct mta_route *); -void mta_route_collect(struct mta_relay *, struct mta_route *); -void mta_source_error(struct mta_relay *, struct mta_route *, const char *); -void mta_delivery_log(struct mta_envelope *, const char *, const char *, int, const char *); -void mta_delivery_notify(struct mta_envelope *); -struct mta_task *mta_route_next_task(struct mta_relay *, struct mta_route *); -const char *mta_host_to_text(struct mta_host *); -const char *mta_relay_to_text(struct mta_relay *); - - -/* mta_session.c */ -void mta_session(struct mta_relay *, struct mta_route *, const char *); -void mta_session_imsg(struct mproc *, struct imsg *); - - -/* parse.y */ -int parse_config(struct smtpd *, const char *, int); -int cmdline_symset(char *); - - -/* queue.c */ -int queue(void); - - -/* queue_backend.c */ -uint32_t queue_generate_msgid(void); -uint64_t queue_generate_evpid(uint32_t); -int queue_init(const char *, int); -int queue_close(void); -int queue_message_create(uint32_t *); -int queue_message_delete(uint32_t); -int queue_message_commit(uint32_t); -int queue_message_fd_r(uint32_t); -int queue_message_fd_rw(uint32_t); -int queue_envelope_create(struct envelope *); -int queue_envelope_delete(uint64_t); -int queue_envelope_load(uint64_t, struct envelope *); -int queue_envelope_update(struct envelope *); -int queue_envelope_walk(struct envelope *); -int queue_message_walk(struct envelope *, uint32_t, int *, void **); - - -/* report_smtp.c */ -void report_smtp_link_connect(const char *, uint64_t, const char *, int, - const struct sockaddr_storage *, const struct sockaddr_storage *); -void report_smtp_link_disconnect(const char *, uint64_t); -void report_smtp_link_greeting(const char *, uint64_t, const char *); -void report_smtp_link_identify(const char *, uint64_t, const char *, const char *); -void report_smtp_link_tls(const char *, uint64_t, const char *); -void report_smtp_link_auth(const char *, uint64_t, const char *, const char *); -void report_smtp_tx_reset(const char *, uint64_t, uint32_t); -void report_smtp_tx_begin(const char *, uint64_t, uint32_t); -void report_smtp_tx_mail(const char *, uint64_t, uint32_t, const char *, int); -void report_smtp_tx_rcpt(const char *, uint64_t, uint32_t, const char *, int); -void report_smtp_tx_envelope(const char *, uint64_t, uint32_t, uint64_t); -void report_smtp_tx_data(const char *, uint64_t, uint32_t, int); -void report_smtp_tx_commit(const char *, uint64_t, uint32_t, size_t); -void report_smtp_tx_rollback(const char *, uint64_t, uint32_t); -void report_smtp_protocol_client(const char *, uint64_t, const char *); -void report_smtp_protocol_server(const char *, uint64_t, const char *); -void report_smtp_filter_response(const char *, uint64_t, int, int, const char *); -void report_smtp_timeout(const char *, uint64_t); - - -/* ruleset.c */ -struct rule *ruleset_match(const struct envelope *); - - -/* scheduler.c */ -int scheduler(void); - - -/* scheduler_bakend.c */ -struct scheduler_backend *scheduler_backend_lookup(const char *); -void scheduler_info(struct scheduler_info *, struct envelope *); - - -/* pony.c */ -int pony(void); -void pony_imsg(struct mproc *, struct imsg *); - - -/* resolver.c */ -void resolver_getaddrinfo(const char *, const char *, const struct addrinfo *, - void(*)(void *, int, struct addrinfo*), void *); -void resolver_getnameinfo(const struct sockaddr *, int, - void(*)(void *, int, const char *, const char *), void *); -void resolver_res_query(const char *, int, int, - void (*cb)(void *, int, int, int, const void *, int), void *); -void resolver_dispatch_request(struct mproc *, struct imsg *); -void resolver_dispatch_result(struct mproc *, struct imsg *); - - -/* smtp.c */ -void smtp_postfork(void); -void smtp_postprivdrop(void); -void smtp_imsg(struct mproc *, struct imsg *); -void smtp_configure(void); -void smtp_collect(void); - - -/* smtp_session.c */ -int smtp_session(struct listener *, int, const struct sockaddr_storage *, - const char *, struct io *); -void smtp_session_imsg(struct mproc *, struct imsg *); - - -/* smtpf_session.c */ -int smtpf_session(struct listener *, int, const struct sockaddr_storage *, - const char *); -void smtpf_session_imsg(struct mproc *, struct imsg *); - - -/* smtpd.c */ -void imsg_dispatch(struct mproc *, struct imsg *); -const char *proc_name(enum smtp_proc_type); -const char *proc_title(enum smtp_proc_type); -const char *imsg_to_str(int); -void log_imsg(int, int, struct imsg *); -int fork_proc_backend(const char *, const char *, const char *); - - -/* srs.c */ -const char *srs_encode(const char *, const char *); -const char *srs_decode(const char *); - - -/* ssl_smtpd.c */ -void *ssl_mta_init(void *, char *, off_t, const char *); -void *ssl_smtp_init(void *, int); - - -/* stat_backend.c */ -struct stat_backend *stat_backend_lookup(const char *); -void stat_increment(const char *, size_t); -void stat_decrement(const char *, size_t); -void stat_set(const char *, const struct stat_value *); -struct stat_value *stat_counter(size_t); -struct stat_value *stat_timestamp(time_t); -struct stat_value *stat_timeval(struct timeval *); -struct stat_value *stat_timespec(struct timespec *); - - -/* table.c */ -struct table *table_find(struct smtpd *, const char *); -struct table *table_create(struct smtpd *, const char *, const char *, - const char *); -int table_config(struct table *); -int table_open(struct table *); -int table_update(struct table *); -void table_close(struct table *); -void table_dump(struct table *); -int table_check_use(struct table *, uint32_t, uint32_t); -int table_check_type(struct table *, uint32_t); -int table_check_service(struct table *, uint32_t); -int table_match(struct table *, enum table_service, const char *); -int table_lookup(struct table *, enum table_service, const char *, - union lookup *); -int table_fetch(struct table *, enum table_service, union lookup *); -void table_destroy(struct smtpd *, struct table *); -void table_add(struct table *, const char *, const char *); -int table_domain_match(const char *, const char *); -int table_netaddr_match(const char *, const char *); -int table_mailaddr_match(const char *, const char *); -int table_regex_match(const char *, const char *); -void table_open_all(struct smtpd *); -void table_dump_all(struct smtpd *); -void table_close_all(struct smtpd *); - - -/* to.c */ -int email_to_mailaddr(struct mailaddr *, char *); -int text_to_netaddr(struct netaddr *, const char *); -int text_to_mailaddr(struct mailaddr *, const char *); -int text_to_relayhost(struct relayhost *, const char *); -int text_to_userinfo(struct userinfo *, const char *); -int text_to_credentials(struct credentials *, const char *); -int text_to_expandnode(struct expandnode *, const char *); -uint64_t text_to_evpid(const char *); -uint32_t text_to_msgid(const char *); -const char *sa_to_text(const struct sockaddr *); -const char *ss_to_text(const struct sockaddr_storage *); -const char *time_to_text(time_t); -const char *duration_to_text(time_t); -const char *rule_to_text(struct rule *); -const char *sockaddr_to_text(struct sockaddr *); -const char *mailaddr_to_text(const struct mailaddr *); -const char *expandnode_to_text(struct expandnode *); - - -/* util.c */ -typedef struct arglist arglist; -struct arglist { - char **list; - uint num; - uint nalloc; -}; -void addargs(arglist *, char *, ...) - __attribute__((format(printf, 2, 3))); -int bsnprintf(char *, size_t, const char *, ...) - __attribute__((format (printf, 3, 4))); -int safe_fclose(FILE *); -int hostname_match(const char *, const char *); -int mailaddr_match(const struct mailaddr *, const struct mailaddr *); -int valid_localpart(const char *); -int valid_domainpart(const char *); -int valid_domainname(const char *); -int valid_smtp_response(const char *); -int secure_file(int, char *, char *, uid_t, int); -int lowercase(char *, const char *, size_t); -void xlowercase(char *, const char *, size_t); -int uppercase(char *, const char *, size_t); -uint64_t generate_uid(void); -int availdesc(void); -int ckdir(const char *, mode_t, uid_t, gid_t, int); -int rmtree(char *, int); -int mvpurge(char *, char *); -int mktmpfile(void); -const char *parse_smtp_response(char *, size_t, char **, int *); -int xasprintf(char **, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -void *xmalloc(size_t); -void *xcalloc(size_t, size_t); -char *xstrdup(const char *); -void *xmemdup(const void *, size_t); -char *strip(char *); -int io_xprint(struct io *, const char *); -int io_xprintf(struct io *, const char *, ...) - __attribute__((__format__ (printf, 2, 3))); -void log_envelope(const struct envelope *, const char *, const char *, - const char *); -int session_socket_error(int); -int getmailname(char *, size_t); -int base64_encode(unsigned char const *, size_t, char *, size_t); -int base64_decode(char const *, unsigned char *, size_t); -int base64_encode_rfc3548(unsigned char const *, size_t, - char *, size_t); -void xclosefrom(int); - -void log_trace_verbose(int); -void log_trace(int, const char *, ...) - __attribute__((format (printf, 2, 3))); - -/* waitq.c */ -int waitq_wait(void *, void (*)(void *, void *, void *), void *); -void waitq_run(void *, void *); - - -/* runq.c */ -struct runq; - -int runq_init(struct runq **, void (*)(struct runq *, void *)); -int runq_schedule(struct runq *, time_t, void *); -int runq_schedule_at(struct runq *, time_t, void *); -int runq_cancel(struct runq *, void *); -int runq_pending(struct runq *, void *, time_t *); diff --git a/smtpd/smtpd/Makefile b/smtpd/smtpd/Makefile deleted file mode 100644 index 34a4dc74..00000000 --- a/smtpd/smtpd/Makefile +++ /dev/null @@ -1,102 +0,0 @@ -# $OpenBSD: Makefile,v 1.91 2018/06/03 14:04:06 gilles Exp $ - -.PATH: ${.CURDIR}/.. - -PROG= smtpd - -SRCS= aliases.c -SRCS+= bounce.c -SRCS+= ca.c -SRCS+= cert.c -SRCS+= compress_backend.c -SRCS+= config.c -SRCS+= control.c -SRCS+= crypto.c -SRCS+= dict.c -SRCS+= dns.c -SRCS+= unpack_dns.c -SRCS+= envelope.c -SRCS+= esc.c -SRCS+= expand.c -SRCS+= forward.c -SRCS+= iobuf.c -SRCS+= ioev.c -SRCS+= limit.c -SRCS+= lka.c -SRCS+= lka_filter.c -SRCS+= lka_session.c -SRCS+= log.c -SRCS+= mailaddr.c -SRCS+= mda.c -SRCS+= mda_mbox.c -SRCS+= mda_unpriv.c -SRCS+= mda_variables.c -SRCS+= mproc.c -SRCS+= mta.c -SRCS+= mta_session.c -SRCS+= parse.y -SRCS+= pony.c -SRCS+= proxy.c -SRCS+= queue.c -SRCS+= queue_backend.c -SRCS+= report_smtp.c -SRCS+= resolver.c -SRCS+= ruleset.c -SRCS+= runq.c -SRCS+= scheduler.c -SRCS+= scheduler_backend.c -SRCS+= smtp.c -SRCS+= smtp_session.c -SRCS+= smtpd.c -SRCS+= srs.c -SRCS+= ssl.c -SRCS+= ssl_smtpd.c -SRCS+= ssl_verify.c -SRCS+= stat_backend.c -SRCS+= table.c -SRCS+= to.c -SRCS+= tree.c -SRCS+= util.c -SRCS+= waitq.c - -# RFC parsers -SRCS+= rfc5322.c - -# backends -SRCS+= compress_gzip.c - -SRCS+= table_db.c -SRCS+= table_getpwnam.c -SRCS+= table_proc.c -SRCS+= table_static.c - -SRCS+= queue_fs.c -SRCS+= queue_null.c -SRCS+= queue_proc.c -SRCS+= queue_ram.c - -SRCS+= scheduler_ramqueue.c -SRCS+= scheduler_null.c -SRCS+= scheduler_proc.c - -SRCS+= stat_ramstat.c - -MAN= sendmail.8 smtpd.8 smtpd.conf.5 table.5 -BINDIR= /usr/sbin - -LDADD+= -levent -lutil -lssl -lcrypto -lm -lz -DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} - -CFLAGS+= -fstack-protector-all -CFLAGS+= -I${.CURDIR}/.. -CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes -CFLAGS+= -Wmissing-declarations -CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual -CFLAGS+= -Wsign-compare -CFLAGS+= -Werror-implicit-function-declaration -#CFLAGS+= -Werror # during development phase (breaks some archs) -CFLAGS+= -DIO_TLS -CFLAGS+= -DQUEUE_PROFILING -YFLAGS= - -.include diff --git a/smtpd/spfwalk.c b/smtpd/spfwalk.c deleted file mode 100644 index 0832d1bc..00000000 --- a/smtpd/spfwalk.c +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (c) 2017 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include - -#ifdef HAVE_ARPA_NAMESER_COMPAT_H -#include -#endif -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd-defines.h" -#include "smtpd-api.h" -#include "unpack_dns.h" -#include "parser.h" - -struct target { - void (*dispatch)(struct dns_rr *, struct target *); - int cidr4; - int cidr6; -}; - -int spfwalk(int, struct parameter *); - -static void dispatch_txt(struct dns_rr *, struct target *); -static void dispatch_mx(struct dns_rr *, struct target *); -static void dispatch_a(struct dns_rr *, struct target *); -static void dispatch_aaaa(struct dns_rr *, struct target *); -static void lookup_record(int, const char *, struct target *); -static void dispatch_record(struct asr_result *, void *); -static ssize_t parse_txt(const char *, size_t, char *, size_t); -static int parse_target(char *, struct target *); -void *xmalloc(size_t size); - -int ip_v4 = 0; -int ip_v6 = 0; -int ip_both = 1; - -struct dict seen; - -int -spfwalk(int argc, struct parameter *argv) -{ - struct target tgt; - const char *ip_family = NULL; - char *line = NULL; - size_t linesize = 0; - ssize_t linelen; - - if (argv) - ip_family = argv[0].u.u_str; - - if (ip_family) { - if (strcmp(ip_family, "-4") == 0) { - ip_both = 0; - ip_v4 = 1; - } else if (strcmp(ip_family, "-6") == 0) { - ip_both = 0; - ip_v6 = 1; - } else - errx(1, "invalid ip_family"); - } - - dict_init(&seen); - event_init(); - - tgt.cidr4 = tgt.cidr6 = -1; - tgt.dispatch = dispatch_txt; - - while ((linelen = getline(&line, &linesize, stdin)) != -1) { - while (linelen-- > 0 && isspace((unsigned char)line[linelen])) - line[linelen] = '\0'; - - if (linelen > 0) - lookup_record(T_TXT, line, &tgt); - } - - free(line); - -#if HAVE_PLEDGE - if (pledge("dns stdio", NULL) == -1) - err(1, "pledge"); -#endif - - event_dispatch(); - - return 0; -} - -void -lookup_record(int type, const char *record, struct target *tgt) -{ - struct asr_query *as; - struct target *ntgt; - - as = res_query_async(record, C_IN, type, NULL); - if (as == NULL) - err(1, "res_query_async"); - ntgt = xmalloc(sizeof(*ntgt)); - *ntgt = *tgt; - event_asr_run(as, dispatch_record, (void *)ntgt); -} - -void -dispatch_record(struct asr_result *ar, void *arg) -{ - struct target *tgt = arg; - struct unpack pack; - struct dns_header h; - struct dns_query q; - struct dns_rr rr; - - /* best effort */ - if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA) - goto end; - - unpack_init(&pack, ar->ar_data, ar->ar_datalen); - unpack_header(&pack, &h); - unpack_query(&pack, &q); - - for (; h.ancount; h.ancount--) { - unpack_rr(&pack, &rr); - /**/ - tgt->dispatch(&rr, tgt); - } -end: - free(tgt); -} - -void -dispatch_txt(struct dns_rr *rr, struct target *tgt) -{ - char buf[4096]; - char *argv[512]; - char buf2[512]; - struct target ltgt; - struct in6_addr ina; - char **ap = argv; - char *in = buf; - char *record, *end; - ssize_t n; - - if (rr->rr_type != T_TXT) - return; - n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf)); - if (n == -1 || n == sizeof(buf)) - return; - buf[n] = '\0'; - - if (strncasecmp("v=spf1 ", buf, 7)) - return; - - while ((*ap = strsep(&in, " ")) != NULL) { - if (strcasecmp(*ap, "v=spf1") == 0) - continue; - - end = *ap + strlen(*ap)-1; - if (*end == '.') - *end = '\0'; - - if (dict_set(&seen, *ap, &seen)) - continue; - - if (**ap == '-' || **ap == '~') - continue; - - if (**ap == '+' || **ap == '?') - (*ap)++; - - ltgt.cidr4 = ltgt.cidr6 = -1; - - if (strncasecmp("ip4:", *ap, 4) == 0) { - if ((ip_v4 == 1 || ip_both == 1) && - inet_net_pton(AF_INET, *(ap) + 4, - &ina, sizeof(ina)) != -1) - printf("%s\n", *(ap) + 4); - continue; - } - if (strncasecmp("ip6:", *ap, 4) == 0) { - if ((ip_v6 == 1 || ip_both == 1) && - inet_net_pton(AF_INET6, *(ap) + 4, - &ina, sizeof(ina)) != -1) - printf("%s\n", *(ap) + 4); - continue; - } - if (strcasecmp("a", *ap) == 0) { - print_dname(rr->rr_dname, buf2, sizeof(buf2)); - buf2[strlen(buf2) - 1] = '\0'; - ltgt.dispatch = dispatch_a; - lookup_record(T_A, buf2, <gt); - ltgt.dispatch = dispatch_aaaa; - lookup_record(T_AAAA, buf2, <gt); - continue; - } - if (strncasecmp("a:", *ap, 2) == 0) { - record = *(ap) + 2; - if (parse_target(record, <gt) < 0) - continue; - ltgt.dispatch = dispatch_a; - lookup_record(T_A, record, <gt); - ltgt.dispatch = dispatch_aaaa; - lookup_record(T_AAAA, record, <gt); - continue; - } - if (strncasecmp("exists:", *ap, 7) == 0) { - ltgt.dispatch = dispatch_a; - lookup_record(T_A, *(ap) + 7, <gt); - continue; - } - if (strncasecmp("include:", *ap, 8) == 0) { - ltgt.dispatch = dispatch_txt; - lookup_record(T_TXT, *(ap) + 8, <gt); - continue; - } - if (strncasecmp("redirect=", *ap, 9) == 0) { - ltgt.dispatch = dispatch_txt; - lookup_record(T_TXT, *(ap) + 9, <gt); - continue; - } - if (strcasecmp("mx", *ap) == 0) { - print_dname(rr->rr_dname, buf2, sizeof(buf2)); - buf2[strlen(buf2) - 1] = '\0'; - ltgt.dispatch = dispatch_mx; - lookup_record(T_MX, buf2, <gt); - continue; - } - if (strncasecmp("mx:", *ap, 3) == 0) { - record = *(ap) + 3; - if (parse_target(record, <gt) < 0) - continue; - ltgt.dispatch = dispatch_mx; - lookup_record(T_MX, record, <gt); - continue; - } - } - *ap = NULL; -} - -void -dispatch_mx(struct dns_rr *rr, struct target *tgt) -{ - char buf[512]; - struct target ltgt; - - if (rr->rr_type != T_MX) - return; - - print_dname(rr->rr.mx.exchange, buf, sizeof(buf)); - buf[strlen(buf) - 1] = '\0'; - if (buf[strlen(buf) - 1] == '.') - buf[strlen(buf) - 1] = '\0'; - - ltgt = *tgt; - ltgt.dispatch = dispatch_a; - lookup_record(T_A, buf, <gt); - ltgt.dispatch = dispatch_aaaa; - lookup_record(T_AAAA, buf, <gt); -} - -void -dispatch_a(struct dns_rr *rr, struct target *tgt) -{ - char buffer[512]; - const char *ptr; - - if (rr->rr_type != T_A) - return; - - if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr, - buffer, sizeof buffer))) { - if (tgt->cidr4 >= 0) - printf("%s/%d\n", ptr, tgt->cidr4); - else - printf("%s\n", ptr); - } -} - -void -dispatch_aaaa(struct dns_rr *rr, struct target *tgt) -{ - char buffer[512]; - const char *ptr; - - if (rr->rr_type != T_AAAA) - return; - - if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6, - buffer, sizeof buffer))) { - if (tgt->cidr6 >= 0) - printf("%s/%d\n", ptr, tgt->cidr6); - else - printf("%s\n", ptr); - } -} - -ssize_t -parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz) -{ - size_t len; - ssize_t r = 0; - - while (rdatalen) { - len = *(const unsigned char *)rdata; - if (len >= rdatalen) { - errno = EINVAL; - return -1; - } - - rdata++; - rdatalen--; - - if (len == 0) - continue; - - if (len >= dstsz) { - errno = EOVERFLOW; - return -1; - } - memmove(dst, rdata, len); - dst += len; - dstsz -= len; - - rdata += len; - rdatalen -= len; - r += len; - } - - return r; -} - -int -parse_target(char *record, struct target *tgt) -{ - const char *err; - char *m4, *m6; - - m4 = record; - strsep(&m4, "/"); - if (m4 == NULL) - return 0; - - m6 = m4; - strsep(&m6, "/"); - - if (*m4) { - tgt->cidr4 = strtonum(m4, 0, 32, &err); - if (err) - return tgt->cidr4 = -1; - } - - if (m6 == NULL) - return 0; - - tgt->cidr6 = strtonum(m6, 0, 128, &err); - if (err) - return tgt->cidr6 = -1; - - return 0; -} diff --git a/smtpd/srs.c b/smtpd/srs.c deleted file mode 100644 index bb4f4d9e..00000000 --- a/smtpd/srs.c +++ /dev/null @@ -1,379 +0,0 @@ -/* $OpenBSD: srs.c,v 1.3 2019/09/29 10:03:49 gilles Exp $ */ - -/* - * Copyright (c) 2019 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "smtpd.h" -#include "log.h" - -static uint8_t base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - -static int -minrange(uint16_t tref, uint16_t t2, int drift, int mod) -{ - if (tref > drift) { - /* t2 must fall in between tref and tref - drift */ - if (t2 <= tref && t2>= tref - drift) - return 1; - } - else { - /* t2 must fall in between 0 and tref, or wrap */ - if (t2 <= tref || t2 >= mod - (drift - tref)) - return 1; - } - return 0; -} - -static int -maxrange(uint16_t tref, uint16_t t2, int drift, int mod) -{ - if (tref + drift < 1024) { - /* t2 must fall in between tref and tref + drift */ - if (t2 >= tref && t2 <= tref + drift) - return 1; - } - else { - /* t2 must fall in between tref + drift, or wrap */ - if (t2 >= tref || t2 <= (tref + drift) % 1024) - return 1; - } - return 0; -} - -static int -timestamp_check_range(uint16_t tref, uint16_t t2) -{ - if (! minrange(tref, t2, env->sc_srs_ttl, 1024) && - ! maxrange(tref, t2, 1, 1024)) - return 0; - - return 1; -} - -static const unsigned char * -srs_hash(const char *key, const char *value) -{ - SHA_CTX c; - static unsigned char md[SHA_DIGEST_LENGTH]; - - SHA1_Init(&c); - SHA1_Update(&c, key, strlen(key)); - SHA1_Update(&c, value, strlen(value)); - SHA1_Final(md, &c); - return md; -} - -static const char * -srs0_encode(const char *sender, const char *rcpt_domain) -{ - static char dest[SMTPD_MAXMAILADDRSIZE]; - char tmp[SMTPD_MAXMAILADDRSIZE]; - char md[SHA_DIGEST_LENGTH*4+1]; - struct mailaddr maddr; - uint16_t timestamp; - int ret; - - /* compute 10 bits timestamp according to spec */ - timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; - - /* parse sender into user and domain */ - if (! text_to_mailaddr(&maddr, sender)) - return sender; - - /* TT==@ */ - ret = snprintf(tmp, sizeof tmp, "%c%c=%s=%s@%s", - base32[(timestamp>>5) & 0x1F], - base32[timestamp & 0x1F], - maddr.domain, maddr.user, rcpt_domain); - if (ret == -1 || ret >= (int)sizeof tmp) - return sender; - - /* compute HHHH */ - base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, - md, sizeof md); - - /* prepend SRS0=HHHH= prefix */ - ret = snprintf(dest, sizeof dest, "SRS0=%c%c%c%c=%s", - md[0], md[1], md[2], md[3], tmp); - if (ret == -1 || ret >= (int)sizeof dest) - return sender; - - return dest; -} - -static const char * -srs1_encode_srs0(const char *sender, const char *rcpt_domain) -{ - static char dest[SMTPD_MAXMAILADDRSIZE]; - char tmp[SMTPD_MAXMAILADDRSIZE]; - char md[SHA_DIGEST_LENGTH*4+1]; - struct mailaddr maddr; - int ret; - - /* parse sender into user and domain */ - if (! text_to_mailaddr(&maddr, sender)) - return sender; - - /* ==@ */ - ret = snprintf(tmp, sizeof tmp, "%s==%s@%s", - maddr.domain, maddr.user, rcpt_domain); - if (ret == -1 || ret >= (int)sizeof tmp) - return sender; - - /* compute HHHH */ - base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, - md, sizeof md); - - /* prepend SRS1=HHHH= prefix */ - ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", - md[0], md[1], md[2], md[3], tmp); - if (ret == -1 || ret >= (int)sizeof dest) - return sender; - - return dest; -} - -static const char * -srs1_encode_srs1(const char *sender, const char *rcpt_domain) -{ - static char dest[SMTPD_MAXMAILADDRSIZE]; - char tmp[SMTPD_MAXMAILADDRSIZE]; - char md[SHA_DIGEST_LENGTH*4+1]; - struct mailaddr maddr; - int ret; - - /* parse sender into user and domain */ - if (! text_to_mailaddr(&maddr, sender)) - return sender; - - /* @ */ - ret = snprintf(tmp, sizeof tmp, "%s@%s", maddr.user, rcpt_domain); - if (ret == -1 || ret >= (int)sizeof tmp) - return sender; - - /* sanity check: there's at least room for a checksum - * with allowed delimiter =, + or - - */ - if (strlen(tmp) < 5) - return sender; - if (tmp[4] != '=' && tmp[4] != '+' && tmp[4] != '-') - return sender; - - /* compute HHHH */ - base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp + 5), SHA_DIGEST_LENGTH, - md, sizeof md); - - /* prepend SRS1=HHHH= prefix skipping previous hops' HHHH */ - ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", - md[0], md[1], md[2], md[3], tmp + 5); - if (ret == -1 || ret >= (int)sizeof dest) - return sender; - - return dest; -} - -const char * -srs_encode(const char *sender, const char *rcpt_domain) -{ - if (strncasecmp(sender, "SRS0=", 5) == 0) - return srs1_encode_srs0(sender+5, rcpt_domain); - if (strncasecmp(sender, "SRS1=", 5) == 0) - return srs1_encode_srs1(sender+5, rcpt_domain); - return srs0_encode(sender, rcpt_domain); -} - -static const char * -srs0_decode(const char *rcpt) -{ - static char dest[SMTPD_MAXMAILADDRSIZE]; - char md[SHA_DIGEST_LENGTH*4+1]; - struct mailaddr maddr; - char *p; - uint8_t *idx; - int ret; - uint16_t timestamp, srs_timestamp; - - /* sanity check: we have room for a checksum and delimiter */ - if (strlen(rcpt) < 5) - return NULL; - - /* compute checksum */ - base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, - md, sizeof md); - - /* compare prefix checksum with computed checksum */ - if (strncmp(md, rcpt, 4) != 0) { - if (env->sc_srs_key_backup == NULL) - return NULL; - base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), - SHA_DIGEST_LENGTH, md, sizeof md); - if (strncmp(md, rcpt, 4) != 0) - return NULL; - } - rcpt += 5; - - /* sanity check: we have room for a timestamp and delimiter */ - if (strlen(rcpt) < 3) - return NULL; - - /* decode timestamp */ - if ((idx = strchr(base32, rcpt[0])) == NULL) - return NULL; - srs_timestamp = ((idx - base32) << 5); - - if ((idx = strchr(base32, rcpt[1])) == NULL) - return NULL; - srs_timestamp |= (idx - base32); - rcpt += 3; - - /* compute current 10 bits timestamp */ - timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; - - /* check that SRS timestamp isn't too far from current */ - if (timestamp != srs_timestamp) - if (! timestamp_check_range(timestamp, srs_timestamp)) - return NULL; - - if (! text_to_mailaddr(&maddr, rcpt)) - return NULL; - - /* sanity check: we have at least one SRS separator */ - if ((p = strchr(maddr.user, '=')) == NULL) - return NULL; - *p++ = '\0'; - - /* maddr.user holds "domain\0user", with p pointing at user */ - ret = snprintf(dest, sizeof dest, "%s@%s", p, maddr.user); - if (ret == -1 || ret >= (int)sizeof dest) - return NULL; - - return dest; -} - -static const char * -srs1_decode(const char *rcpt) -{ - static char dest[SMTPD_MAXMAILADDRSIZE]; - char md[SHA_DIGEST_LENGTH*4+1]; - struct mailaddr maddr; - char *p; - uint8_t *idx; - int ret; - uint16_t timestamp, srs_timestamp; - - /* sanity check: we have room for a checksum and delimiter */ - if (strlen(rcpt) < 5) - return NULL; - - /* compute checksum */ - base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, - md, sizeof md); - - /* compare prefix checksum with computed checksum */ - if (strncmp(md, rcpt, 4) != 0) { - if (env->sc_srs_key_backup == NULL) - return NULL; - base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), - SHA_DIGEST_LENGTH, md, sizeof md); - if (strncmp(md, rcpt, 4) != 0) - return NULL; - } - rcpt += 5; - - if (! text_to_mailaddr(&maddr, rcpt)) - return NULL; - - /* sanity check: we have at least one SRS separator */ - if ((p = strchr(maddr.user, '=')) == NULL) - return NULL; - *p++ = '\0'; - - /* maddr.user holds "domain\0user", with p pointing at user */ - ret = snprintf(dest, sizeof dest, "SRS0%s@%s", p, maddr.user); - if (ret == -1 || ret >= (int)sizeof dest) - return NULL; - - - /* we're ready to return decoded address, but let's check if - * SRS0 timestamp is valid. - */ - - /* first, get rid of SRS0 checksum (=HHHH=), we can't check it */ - if (strlen(p) < 6) - return NULL; - p += 6; - - /* we should be pointing to a timestamp, check that we're indeed */ - if (strlen(p) < 3) - return NULL; - if (p[2] != '=' && p[2] != '+' && p[2] != '-') - return NULL; - p[2] = '\0'; - - if ((idx = strchr(base32, p[0])) == NULL) - return NULL; - srs_timestamp = ((idx - base32) << 5); - - if ((idx = strchr(base32, p[1])) == NULL) - return NULL; - srs_timestamp |= (idx - base32); - - /* compute current 10 bits timestamp */ - timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; - - /* check that SRS timestamp isn't too far from current */ - if (timestamp != srs_timestamp) - if (! timestamp_check_range(timestamp, srs_timestamp)) - return NULL; - - return dest; -} - -const char * -srs_decode(const char *rcpt) -{ - if (strncasecmp(rcpt, "SRS0=", 5) == 0) - return srs0_decode(rcpt + 5); - if (strncasecmp(rcpt, "SRS1=", 5) == 0) - return srs1_decode(rcpt + 5); - - return NULL; -} diff --git a/smtpd/ssl.c b/smtpd/ssl.c deleted file mode 100644 index a37d6fea..00000000 --- a/smtpd/ssl.c +++ /dev/null @@ -1,458 +0,0 @@ -/* $OpenBSD: ssl.c,v 1.93 2019/06/05 06:40:13 gilles Exp $ */ - -/* - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008 Reyk Floeter - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "ssl.h" - -void -ssl_init(void) -{ - static int inited = 0; - - if (inited) - return; - - SSL_library_init(); - SSL_load_error_strings(); - - OpenSSL_add_all_algorithms(); - - /* Init hardware crypto engines. */ - ENGINE_load_builtin_engines(); - ENGINE_register_all_complete(); - inited = 1; -} - -int -ssl_setup(SSL_CTX **ctxp, struct pki *pki, - int (*sni_cb)(SSL *,int *,void *), const char *ciphers) -{ - SSL_CTX *ctx; - uint8_t sid[SSL_MAX_SID_CTX_LENGTH]; - - ctx = ssl_ctx_create(pki->pki_name, pki->pki_cert, pki->pki_cert_len, ciphers); - - /* - * Set session ID context to a random value. We don't support - * persistent caching of sessions so it is OK to set a temporary - * session ID context that is valid during run time. - */ - arc4random_buf(sid, sizeof(sid)); - if (!SSL_CTX_set_session_id_context(ctx, sid, sizeof(sid))) - goto err; - - if (sni_cb) - SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb); - - SSL_CTX_set_dh_auto(ctx, 0); - - SSL_CTX_set_ecdh_auto(ctx, 1); - - *ctxp = ctx; - return 1; - -err: - SSL_CTX_free(ctx); - ssl_error("ssl_setup"); - return 0; -} - -char * -ssl_load_file(const char *name, off_t *len, mode_t perm) -{ - struct stat st; - off_t size; - char *buf = NULL; - int fd, saved_errno; - char mode[12]; - - if ((fd = open(name, O_RDONLY)) == -1) - return (NULL); - if (fstat(fd, &st) != 0) - goto fail; - if (st.st_uid != 0) { - log_warnx("warn: %s: not owned by uid 0", name); - errno = EACCES; - goto fail; - } - if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) { - strmode(perm, mode); - log_warnx("warn: %s: insecure permissions: must be at most %s", - name, &mode[1]); - errno = EACCES; - goto fail; - } - size = st.st_size; - if ((buf = calloc(1, size + 1)) == NULL) - goto fail; - if (read(fd, buf, size) != size) - goto fail; - close(fd); - - *len = size + 1; - return (buf); - -fail: - free(buf); - saved_errno = errno; - close(fd); - errno = saved_errno; - return (NULL); -} - -#if 0 -static int -ssl_password_cb(char *buf, int size, int rwflag, void *u) -{ - size_t len; - if (u == NULL) { - explicit_bzero(buf, size); - return (0); - } - if ((len = strlcpy(buf, u, size)) >= (size_t)size) - return (0); - return (len); -} -#endif - -static int -ssl_password_cb(char *buf, int size, int rwflag, void *u) -{ - int ret = 0; - size_t len; - char *pass; - - pass = getpass((const char *)u); - if (pass == NULL) - return 0; - len = strlen(pass); - if (strlcpy(buf, pass, size) >= (size_t)size) - goto end; - ret = len; -end: - if (len) - explicit_bzero(pass, len); - return ret; -} - -char * -ssl_load_key(const char *name, off_t *len, char *pass, mode_t perm, const char *pkiname) -{ - FILE *fp = NULL; - EVP_PKEY *key = NULL; - BIO *bio = NULL; - long size; - char *data, *buf, *filebuf; - struct stat st; - char mode[12]; - char prompt[2048]; - - /* Initialize SSL library once */ - ssl_init(); - - /* - * Read (possibly) encrypted key from file - */ - if ((fp = fopen(name, "r")) == NULL) - return (NULL); - if ((filebuf = malloc_conceal(BUFSIZ)) == NULL) - goto fail; - setvbuf(fp, filebuf, _IOFBF, BUFSIZ); - - if (fstat(fileno(fp), &st) != 0) - goto fail; - if (st.st_uid != 0) { - log_warnx("warn: %s: not owned by uid 0", name); - errno = EACCES; - goto fail; - } - if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) { - strmode(perm, mode); - log_warnx("warn: %s: insecure permissions: must be at most %s", - name, &mode[1]); - errno = EACCES; - goto fail; - } - - (void)snprintf(prompt, sizeof prompt, "passphrase for %s: ", pkiname); - key = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, prompt); - fclose(fp); - fp = NULL; - freezero(filebuf, BUFSIZ); - filebuf = NULL; - if (key == NULL) - goto fail; - /* - * Write unencrypted key to memory buffer - */ - if ((bio = BIO_new(BIO_s_mem())) == NULL) - goto fail; - if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) - goto fail; - if ((size = BIO_get_mem_data(bio, &data)) <= 0) - goto fail; - if ((buf = calloc_conceal(1, size + 1)) == NULL) - goto fail; - memcpy(buf, data, size); - - BIO_free_all(bio); - EVP_PKEY_free(key); - - *len = (off_t)size + 1; - return (buf); - -fail: - ssl_error("ssl_load_key"); - BIO_free_all(bio); - EVP_PKEY_free(key); - if (fp) - fclose(fp); - freezero(filebuf, BUFSIZ); - return (NULL); -} - -SSL_CTX * -ssl_ctx_create(const char *pkiname, char *cert, off_t cert_len, const char *ciphers) -{ - SSL_CTX *ctx; - size_t pkinamelen = 0; - - ctx = SSL_CTX_new(SSLv23_method()); - if (ctx == NULL) { - ssl_error("ssl_ctx_create"); - fatal("ssl_ctx_create: could not create SSL context"); - } - - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); - SSL_CTX_set_timeout(ctx, SSL_SESSION_TIMEOUT); - SSL_CTX_set_options(ctx, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TICKET); - SSL_CTX_set_options(ctx, - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION); - SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - - if (ciphers == NULL) - ciphers = SSL_CIPHERS; - if (!SSL_CTX_set_cipher_list(ctx, ciphers)) { - ssl_error("ssl_ctx_create"); - fatal("ssl_ctx_create: could not set cipher list"); - } - - if (cert != NULL) { - if (pkiname != NULL) - pkinamelen = strlen(pkiname) + 1; - if (!SSL_CTX_use_certificate_chain_mem(ctx, cert, cert_len)) { - ssl_error("ssl_ctx_create"); - fatal("ssl_ctx_create: invalid certificate chain"); - } else if (!ssl_ctx_fake_private_key(ctx, - pkiname, pkinamelen, cert, cert_len, NULL, NULL)) { - ssl_error("ssl_ctx_create"); - fatal("ssl_ctx_create: could not fake private key"); - } else if (!SSL_CTX_check_private_key(ctx)) { - ssl_error("ssl_ctx_create"); - fatal("ssl_ctx_create: invalid private key"); - } - } - - return (ctx); -} - -int -ssl_load_certificate(struct pki *p, const char *pathname) -{ - p->pki_cert = ssl_load_file(pathname, &p->pki_cert_len, 0755); - if (p->pki_cert == NULL) - return 0; - return 1; -} - -int -ssl_load_keyfile(struct pki *p, const char *pathname, const char *pkiname) -{ - char pass[1024]; - - p->pki_key = ssl_load_key(pathname, &p->pki_key_len, pass, 0740, pkiname); - if (p->pki_key == NULL) - return 0; - return 1; -} - -int -ssl_load_cafile(struct ca *c, const char *pathname) -{ - c->ca_cert = ssl_load_file(pathname, &c->ca_cert_len, 0755); - if (c->ca_cert == NULL) - return 0; - return 1; -} - -const char * -ssl_to_text(const SSL *ssl) -{ - static char buf[256]; - - (void)snprintf(buf, sizeof buf, "%s:%s:%d", - SSL_get_version(ssl), - SSL_get_cipher_name(ssl), - SSL_get_cipher_bits(ssl, NULL)); - - return (buf); -} - -void -ssl_error(const char *where) -{ - unsigned long code; - char errbuf[128]; - - for (; (code = ERR_get_error()) != 0 ;) { - ERR_error_string_n(code, errbuf, sizeof(errbuf)); - log_debug("debug: SSL library error: %s: %s", where, errbuf); - } -} - -int -ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, - X509 **x509ptr, EVP_PKEY **pkeyptr) -{ - BIO *in; - X509 *x509 = NULL; - EVP_PKEY *pkey = NULL; - RSA *rsa = NULL; - EC_KEY *eckey = NULL; - void *exdata = NULL; - - if ((in = BIO_new_mem_buf(buf, len)) == NULL) { - SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_BUF_LIB); - return (0); - } - - if ((x509 = PEM_read_bio_X509(in, NULL, - ssl_password_cb, NULL)) == NULL) { - SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_PEM_LIB); - goto fail; - } - - if ((pkey = X509_get_pubkey(x509)) == NULL) { - SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_X509_LIB); - goto fail; - } - - BIO_free(in); - in = NULL; - - if (data != NULL && datalen) { - if (((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL && - (eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) || - (exdata = malloc(datalen)) == NULL) { - SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_EVP_LIB); - goto fail; - } - - memcpy(exdata, data, datalen); - if (rsa) - RSA_set_ex_data(rsa, 0, exdata); -#if defined(SUPPORT_ECDSA) - if (eckey) - ECDSA_set_ex_data(eckey, 0, exdata); -#endif - RSA_free(rsa); /* dereference, will be cleaned up with pkey */ -#if defined(SUPPORT_ECDSA) - EC_KEY_free(eckey); /* dereference, will be cleaned up with pkey */ -#endif - } - - *x509ptr = x509; - *pkeyptr = pkey; - - return (1); - - fail: - RSA_free(rsa); - EC_KEY_free(eckey); - BIO_free(in); - EVP_PKEY_free(pkey); - X509_free(x509); - free(exdata); - - return (0); -} - -int -ssl_ctx_fake_private_key(SSL_CTX *ctx, const void *data, size_t datalen, - char *buf, off_t len, X509 **x509ptr, EVP_PKEY **pkeyptr) -{ - int ret = 0; - EVP_PKEY *pkey = NULL; - X509 *x509 = NULL; - - if (!ssl_load_pkey(data, datalen, buf, len, &x509, &pkey)) - return (0); - - /* - * Use the public key as the "private" key - the secret key - * parameters are hidden in an extra process that will be - * contacted by the RSA engine. The SSL/TLS library needs at - * least the public key parameters in the current process. - */ - ret = SSL_CTX_use_PrivateKey(ctx, pkey); - if (!ret) - SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_LIB_SSL); - - if (pkeyptr != NULL) - *pkeyptr = pkey; - else - EVP_PKEY_free(pkey); - - if (x509ptr != NULL) - *x509ptr = x509; - else - X509_free(x509); - - return (ret); -} diff --git a/smtpd/ssl.h b/smtpd/ssl.h deleted file mode 100644 index 11c80c68..00000000 --- a/smtpd/ssl.h +++ /dev/null @@ -1,71 +0,0 @@ -/* $OpenBSD: ssl.h,v 1.21 2019/09/18 11:26:30 eric Exp $ */ -/* - * Copyright (c) 2013 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define SSL_CIPHERS "HIGH:!aNULL:!MD5" -#define SSL_SESSION_TIMEOUT 300 - -struct pki { - char pki_name[HOST_NAME_MAX+1]; - - char *pki_cert_file; - char *pki_cert; - off_t pki_cert_len; - - char *pki_key_file; - char *pki_key; - off_t pki_key_len; - - EVP_PKEY *pki_pkey; - - int pki_dhe; -}; - -struct ca { - char ca_name[HOST_NAME_MAX+1]; - - char *ca_cert_file; - char *ca_cert; - off_t ca_cert_len; -}; - - -/* ssl.c */ -void ssl_init(void); -int ssl_setup(SSL_CTX **, struct pki *, - int (*)(SSL *, int *, void *), const char *); -SSL_CTX *ssl_ctx_create(const char *, char *, off_t, const char *); -int ssl_cmp(struct pki *, struct pki *); -char *ssl_load_file(const char *, off_t *, mode_t); -char *ssl_load_key(const char *, off_t *, char *, mode_t, const char *); - -const char *ssl_to_text(const SSL *); -void ssl_error(const char *); - -int ssl_load_certificate(struct pki *, const char *); -int ssl_load_keyfile(struct pki *, const char *, const char *); -int ssl_load_cafile(struct ca *, const char *); -int ssl_load_pkey(const void *, size_t, char *, off_t, - X509 **, EVP_PKEY **); -int ssl_ctx_fake_private_key(SSL_CTX *, const void *, size_t, - char *, off_t, X509 **, EVP_PKEY **); - -/* ssl_privsep.c */ -int ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); -int SSL_CTX_use_certificate_chain_mem(SSL_CTX *, void *, int); - -/* ssl_verify.c */ -int ssl_check_name(X509 *, const char *, int *); diff --git a/smtpd/ssl_smtpd.c b/smtpd/ssl_smtpd.c deleted file mode 100644 index 4e5b7e75..00000000 --- a/smtpd/ssl_smtpd.c +++ /dev/null @@ -1,105 +0,0 @@ -/* $OpenBSD: ssl_smtpd.c,v 1.13 2015/12/30 16:02:08 benno Exp $ */ - -/* - * Copyright (c) 2008 Pierre-Yves Ritschard - * Copyright (c) 2008 Reyk Floeter - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "smtpd.h" -#include "log.h" -#include "ssl.h" - - -void * -ssl_mta_init(void *pkiname, char *cert, off_t cert_len, const char *ciphers) -{ - SSL_CTX *ctx = NULL; - SSL *ssl = NULL; - - ctx = ssl_ctx_create(pkiname, cert, cert_len, ciphers); - - if ((ssl = SSL_new(ctx)) == NULL) - goto err; - if (!SSL_set_ssl_method(ssl, SSLv23_client_method())) - goto err; - - SSL_CTX_free(ctx); - return (void *)(ssl); - -err: - SSL_free(ssl); - SSL_CTX_free(ctx); - ssl_error("ssl_mta_init"); - return (NULL); -} - -/* dummy_verify */ -static int -dummy_verify(int ok, X509_STORE_CTX *store) -{ - /* - * We *want* SMTP to request an optional client certificate, however we don't want the - * verification to take place in the SMTP process. This dummy verify will allow us to - * asynchronously verify in the lookup process. - */ - return 1; -} - -void * -ssl_smtp_init(void *ssl_ctx, int verify) -{ - SSL *ssl = NULL; - - log_debug("debug: session_start_ssl: switching to SSL"); - - if (verify) - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, dummy_verify); - - if ((ssl = SSL_new(ssl_ctx)) == NULL) - goto err; - if (!SSL_set_ssl_method(ssl, SSLv23_server_method())) - goto err; - - return (void *)(ssl); - -err: - SSL_free(ssl); - ssl_error("ssl_smtp_init"); - return (NULL); -} diff --git a/smtpd/ssl_verify.c b/smtpd/ssl_verify.c deleted file mode 100644 index 2e784b97..00000000 --- a/smtpd/ssl_verify.c +++ /dev/null @@ -1,297 +0,0 @@ -/* $OpenBSD: ssl_verify.c,v 1.2 2019/11/02 03:16:45 gilles Exp $ */ -/* - * Copyright (c) 2014 Jeremie Courreges-Anglas - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/* Adapted from lib/libtls/tls_verify.c */ - -#include "includes.h" - -#include - -#include -#include - -#include -#include - -#include - -#if 0 -#include -#include "tls_internal.h" -#endif - -#include "ssl.h" -#include "log.h" - -struct tls; -#define tls_set_errorx(ctx, ...) log_warnx(__VA_ARGS__) -union tls_addr { - struct in_addr in; - struct in6_addr in6; -}; - -static int -tls_match_name(const char *cert_name, const char *name) -{ - const char *cert_domain, *domain, *next_dot; - - if (strcasecmp(cert_name, name) == 0) - return 0; - - /* Wildcard match? */ - if (cert_name[0] == '*') { - /* - * Valid wildcards: - * - "*.domain.tld" - * - "*.sub.domain.tld" - * - etc. - * Reject "*.tld". - * No attempt to prevent the use of eg. "*.co.uk". - */ - cert_domain = &cert_name[1]; - /* Disallow "*" */ - if (cert_domain[0] == '\0') - return -1; - /* Disallow "*foo" */ - if (cert_domain[0] != '.') - return -1; - /* Disallow "*.." */ - if (cert_domain[1] == '.') - return -1; - next_dot = strchr(&cert_domain[1], '.'); - /* Disallow "*.bar" */ - if (next_dot == NULL) - return -1; - /* Disallow "*.bar.." */ - if (next_dot[1] == '.') - return -1; - - domain = strchr(name, '.'); - - /* No wildcard match against a name with no host part. */ - if (name[0] == '.') - return -1; - /* No wildcard match against a name with no domain part. */ - if (domain == NULL || strlen(domain) == 1) - return -1; - - if (strcasecmp(cert_domain, domain) == 0) - return 0; - } - - return -1; -} - -/* - * See RFC 5280 section 4.2.1.6 for SubjectAltName details. - * alt_match is set to 1 if a matching alternate name is found. - * alt_exists is set to 1 if any known alternate name exists in the certificate. - */ -static int -tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name, - int *alt_match, int *alt_exists) -{ - STACK_OF(GENERAL_NAME) *altname_stack = NULL; - union tls_addr addrbuf; - int addrlen, type; - int count, i; - int rv = 0; - - *alt_match = 0; - *alt_exists = 0; - - altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, - NULL, NULL); - if (altname_stack == NULL) - return 0; - - if (inet_pton(AF_INET, name, &addrbuf) == 1) { - type = GEN_IPADD; - addrlen = 4; - } else if (inet_pton(AF_INET6, name, &addrbuf) == 1) { - type = GEN_IPADD; - addrlen = 16; - } else { - type = GEN_DNS; - addrlen = 0; - } - - count = sk_GENERAL_NAME_num(altname_stack); - for (i = 0; i < count; i++) { - GENERAL_NAME *altname; - - altname = sk_GENERAL_NAME_value(altname_stack, i); - - if (altname->type == GEN_DNS || altname->type == GEN_IPADD) - *alt_exists = 1; - - if (altname->type != type) - continue; - - if (type == GEN_DNS) { - const unsigned char *data; - int format, len; - - format = ASN1_STRING_type(altname->d.dNSName); - if (format == V_ASN1_IA5STRING) { - data = ASN1_STRING_get0_data(altname->d.dNSName); - len = ASN1_STRING_length(altname->d.dNSName); - - if (len < 0 || (size_t)len != strlen(data)) { - tls_set_errorx(ctx, - "error verifying name '%s': " - "NUL byte in subjectAltName, " - "probably a malicious certificate", - name); - rv = -1; - break; - } - - /* - * Per RFC 5280 section 4.2.1.6: - * " " is a legal domain name, but that - * dNSName must be rejected. - */ - if (strcmp(data, " ") == 0) { - tls_set_errorx(ctx, - "error verifying name '%s': " - "a dNSName of \" \" must not be " - "used", name); - rv = -1; - break; - } - - if (tls_match_name(data, name) == 0) { - *alt_match = 1; - break; - } - } else { -#ifdef DEBUG - fprintf(stdout, "%s: unhandled subjectAltName " - "dNSName encoding (%d)\n", getprogname(), - format); -#endif - } - - } else if (type == GEN_IPADD) { - const unsigned char *data; - int datalen; - - datalen = ASN1_STRING_length(altname->d.iPAddress); - data = ASN1_STRING_get0_data(altname->d.iPAddress); - - if (datalen < 0) { - tls_set_errorx(ctx, - "Unexpected negative length for an " - "IP address: %d", datalen); - rv = -1; - break; - } - - /* - * Per RFC 5280 section 4.2.1.6: - * IPv4 must use 4 octets and IPv6 must use 16 octets. - */ - if (datalen == addrlen && - memcmp(data, &addrbuf, addrlen) == 0) { - *alt_match = 1; - break; - } - } - } - - sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free); - return rv; -} - -static int -tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, - int *cn_match) -{ - X509_NAME *subject_name; - char *common_name = NULL; - union tls_addr addrbuf; - int common_name_len; - int rv = 0; - - *cn_match = 0; - - subject_name = X509_get_subject_name(cert); - if (subject_name == NULL) - goto done; - - common_name_len = X509_NAME_get_text_by_NID(subject_name, - NID_commonName, NULL, 0); - if (common_name_len < 0) - goto done; - - common_name = calloc(common_name_len + 1, 1); - if (common_name == NULL) - goto done; - - X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, - common_name_len + 1); - - /* NUL bytes in CN? */ - if (common_name_len < 0 || - (size_t)common_name_len != strlen(common_name)) { - tls_set_errorx(ctx, "error verifying name '%s': " - "NUL byte in Common Name field, " - "probably a malicious certificate", name); - rv = -1; - goto done; - } - - /* - * We don't want to attempt wildcard matching against IP addresses, - * so perform a simple comparison here. - */ - if (inet_pton(AF_INET, name, &addrbuf) == 1 || - inet_pton(AF_INET6, name, &addrbuf) == 1) { - if (strcmp(common_name, name) == 0) - *cn_match = 1; - goto done; - } - - if (tls_match_name(common_name, name) == 0) - *cn_match = 1; - - done: - free(common_name); - return rv; -} - -int -ssl_check_name(X509 *cert, const char *name, int *match) -{ - int alt_exists; - - *match = 0; - - if (tls_check_subject_altname(NULL, cert, name, match, - &alt_exists) == -1) - return -1; - - /* - * As per RFC 6125 section 6.4.4, if any known alternate name existed - * in the certificate, we do not attempt to match on the CN. - */ - if (*match || alt_exists) - return 0; - - return tls_check_common_name(NULL, cert, name, match); -} diff --git a/smtpd/stat_backend.c b/smtpd/stat_backend.c deleted file mode 100644 index 30cb299b..00000000 --- a/smtpd/stat_backend.c +++ /dev/null @@ -1,124 +0,0 @@ -/* $OpenBSD: stat_backend.c,v 1.11 2018/12/27 10:35:26 gilles Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "log.h" -#include "smtpd.h" - -extern struct stat_backend stat_backend_ramstat; - -struct stat_backend * -stat_backend_lookup(const char *name) -{ - return &stat_backend_ramstat; -} - -void -stat_increment(const char *key, size_t count) -{ - struct stat_value *value; - - if (count == 0) - return; - - value = stat_counter(count); - - m_create(p_control, IMSG_STAT_INCREMENT, 0, 0, -1); - m_add_string(p_control, key); - m_add_data(p_control, value, sizeof(*value)); - m_close(p_control); -} - -void -stat_decrement(const char *key, size_t count) -{ - struct stat_value *value; - - if (count == 0) - return; - - value = stat_counter(count); - - m_create(p_control, IMSG_STAT_DECREMENT, 0, 0, -1); - m_add_string(p_control, key); - m_add_data(p_control, value, sizeof(*value)); - m_close(p_control); -} - -void -stat_set(const char *key, const struct stat_value *value) -{ - m_create(p_control, IMSG_STAT_SET, 0, 0, -1); - m_add_string(p_control, key); - m_add_data(p_control, value, sizeof(*value)); - m_close(p_control); -} - -/* helpers */ - -struct stat_value * -stat_counter(size_t counter) -{ - static struct stat_value value; - - value.type = STAT_COUNTER; - value.u.counter = counter; - return &value; -} - -struct stat_value * -stat_timestamp(time_t timestamp) -{ - static struct stat_value value; - - value.type = STAT_TIMESTAMP; - value.u.timestamp = timestamp; - return &value; -} - -struct stat_value * -stat_timeval(struct timeval *tv) -{ - static struct stat_value value; - - value.type = STAT_TIMEVAL; - value.u.tv = *tv; - return &value; -} - -struct stat_value * -stat_timespec(struct timespec *ts) -{ - static struct stat_value value; - - value.type = STAT_TIMESPEC; - value.u.ts = *ts; - return &value; -} diff --git a/smtpd/stat_ramstat.c b/smtpd/stat_ramstat.c deleted file mode 100644 index bbf1541a..00000000 --- a/smtpd/stat_ramstat.c +++ /dev/null @@ -1,162 +0,0 @@ -/* $OpenBSD: stat_ramstat.c,v 1.11 2018/05/31 21:06:12 gilles Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - - -static void ramstat_init(void); -static void ramstat_close(void); -static void ramstat_increment(const char *, size_t); -static void ramstat_decrement(const char *, size_t); -static void ramstat_set(const char *, const struct stat_value *); -static int ramstat_iter(void **, char **, struct stat_value *); - -struct ramstat_entry { - RB_ENTRY(ramstat_entry) entry; - char key[STAT_KEY_SIZE]; - struct stat_value value; -}; -RB_HEAD(stats_tree, ramstat_entry) stats; -RB_PROTOTYPE(stats_tree, ramstat_entry, entry, ramstat_entry_cmp); - -struct stat_backend stat_backend_ramstat = { - ramstat_init, - ramstat_close, - ramstat_increment, - ramstat_decrement, - ramstat_set, - ramstat_iter -}; - -static void -ramstat_init(void) -{ - log_trace(TRACE_STAT, "ramstat: init"); - - RB_INIT(&stats); - - /* ramstat_set() should be called for each key we want - * to have displayed by smtpctl show stats at startup. - */ - ramstat_set("uptime", stat_timestamp(env->sc_uptime)); -} - -static void -ramstat_close(void) -{ - log_trace(TRACE_STAT, "ramstat: close"); -} - -static void -ramstat_increment(const char *name, size_t val) -{ - struct ramstat_entry *np, lk; - - log_trace(TRACE_STAT, "ramstat: increment: %s", name); - (void)strlcpy(lk.key, name, sizeof (lk.key)); - np = RB_FIND(stats_tree, &stats, &lk); - if (np == NULL) { - np = xcalloc(1, sizeof *np); - (void)strlcpy(np->key, name, sizeof (np->key)); - RB_INSERT(stats_tree, &stats, np); - } - log_trace(TRACE_STAT, "ramstat: %s (%p): %zd -> %zd", - name, name, np->value.u.counter, np->value.u.counter + val); - np->value.u.counter += val; -} - -static void -ramstat_decrement(const char *name, size_t val) -{ - struct ramstat_entry *np, lk; - - log_trace(TRACE_STAT, "ramstat: decrement: %s", name); - (void)strlcpy(lk.key, name, sizeof (lk.key)); - np = RB_FIND(stats_tree, &stats, &lk); - if (np == NULL) { - np = xcalloc(1, sizeof *np); - (void)strlcpy(np->key, name, sizeof (np->key)); - RB_INSERT(stats_tree, &stats, np); - } - log_trace(TRACE_STAT, "ramstat: %s (%p): %zd -> %zd", - name, name, np->value.u.counter, np->value.u.counter - val); - np->value.u.counter -= val; -} - -static void -ramstat_set(const char *name, const struct stat_value *val) -{ - struct ramstat_entry *np, lk; - - log_trace(TRACE_STAT, "ramstat: set: %s", name); - (void)strlcpy(lk.key, name, sizeof (lk.key)); - np = RB_FIND(stats_tree, &stats, &lk); - if (np == NULL) { - np = xcalloc(1, sizeof *np); - (void)strlcpy(np->key, name, sizeof (np->key)); - RB_INSERT(stats_tree, &stats, np); - } - log_trace(TRACE_STAT, "ramstat: %s: n/a -> n/a", name); - np->value = *val; -} - -static int -ramstat_iter(void **iter, char **name, struct stat_value *val) -{ - struct ramstat_entry *np; - - log_trace(TRACE_STAT, "ramstat: iter"); - if (RB_EMPTY(&stats)) - return 0; - - if (*iter == NULL) - np = RB_MIN(stats_tree, &stats); - else - np = RB_NEXT(stats_tree, &stats, *iter); - - *iter = np; - if (np == NULL) - return 0; - - *name = np->key; - *val = np->value; - return 1; -} - - -static int -ramstat_entry_cmp(struct ramstat_entry *e1, struct ramstat_entry *e2) -{ - return strcmp(e1->key, e2->key); -} - -RB_GENERATE(stats_tree, ramstat_entry, entry, ramstat_entry_cmp); diff --git a/smtpd/table.5 b/smtpd/table.5 deleted file mode 100644 index e9d4fa4b..00000000 --- a/smtpd/table.5 +++ /dev/null @@ -1,258 +0,0 @@ -.\" $OpenBSD: table.5,v 1.11 2019/08/11 13:00:57 gilles Exp $ -.\" -.\" Copyright (c) 2013 Eric Faurot -.\" Copyright (c) 2013 Gilles Chehade -.\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. -.\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.\" -.\" -.Dd $Mdocdate: August 11 2019 $ -.Dt TABLE 5 -.Os -.Sh NAME -.Nm table -.Nd format description for smtpd tables -.Sh DESCRIPTION -This manual page documents the file format for the various tables used in the -.Xr smtpd 8 -mail daemon. -.Pp -The format described here applies to tables as defined in -.Xr smtpd.conf 5 . -.Sh TABLE TYPES -There are two types of tables: lists and mappings. -A list consists of a series of values, -while a mapping consists of a series of keys and their associated values. -The following illustrates how to declare them as static tables: -.Bd -literal -offset indent -table mylist { value1, value2, value3 } -table mymapping { key1 = value1, key2 = value2, key3 = value3 } -.Ed -.Pp -When using a -.Ql file -table, a list will be written with each value on a line by itself. -Comments can be put anywhere in the file using a hash mark -.Pq Sq # , -and extend to the end of the current line. -.Bd -literal -offset indent -value1 -value2 -value3 -.Ed -.Pp -A mapping will be written with each key and value on a line, -whitespaces separating both columns: -.Bd -literal -offset indent -key1 value1 -key2 value2 -key3 value3 -.Ed -.Pp -A file table can be converted to a Berkeley database using the -.Xr makemap 8 -utility with no syntax change. -.Pp -Tables using a -.Ql file -or Berkeley DB backend will be referenced as follows: -.Bd -unfilled -offset indent -.Ic table Ar name Cm file : Ns Pa /path/to/file -.Ic table Ar name Cm db : Ns Pa /path/to/file.db -.Ed -.Ss Aliasing tables -Aliasing tables are mappings that associate a recipient to one or many -destinations. -They can be used in two contexts: primary domain aliases and virtual domain -mapping. -.Bd -unfilled -offset indent -.Ic action Ar name method Cm alias Pf < table Ns > -.Ic action Ar name method Cm virtual Pf < table Ns > -.Ed -.Pp -In a primary domain context, the key is the user part of the recipient address, -whilst the value is one or many recipients as described in -.Xr aliases 5 : -.Bd -literal -offset indent -user1 otheruser -user2 otheruser1,otheruser2 -user3 otheruser@example.com -.Ed -.Pp -In a virtual domain context, the key is either a user part, a full email -address or a catch all, following selection rules described in -.Xr smtpd.conf 5 , -and the value is one or many recipients as described in -.Xr aliases 5 : -.Bd -literal -offset indent -user1 otheruser -user2@example.org otheruser1,otheruser2 -@example.org otheruser@example.com -@ catchall@example.com -.Ed -.Pp -The following directive shares the same table format, -but with a different meaning. -Here, the user is allowed to send mail from the listed addresses: -.Bd -unfilled -offset indent -.Ic listen on Ar interface Cm auth Oo Ar ... Oc Cm senders Pf < Ar table Ns > -.Ed -.Ss Domain tables -Domain tables are simple lists of domains or hosts. -.Bd -unfilled -offset indent -.Ic match Cm for domain Pf < table Ns > Cm action Ar name -.Ic match Cm helo Pf < table Ns > Oo Ar ... Oc Cm action Ar name -.Ed -.Pp -In that context, the list of domains will be matched against the recipient -domain or against the HELO name advertised by the sending host, -respectively. -For -.Ql static , -.Ql file -and -.Xr dbopen 3 -backends, a wildcard may be used so the domain table may contain: -.Bd -literal -offset indent -example.org -*.example.org -.Ed -.Ss Credentials tables -Credentials tables are mappings of credentials. -They can be used in two contexts: -.Bd -unfilled -offset indent -.Ic listen on Ar interface Cm tls Oo Ar ... Oc Cm auth Pf < Ar table Ns > -.Ic action Ar name Cm relay host Ar relay-url Cm auth Pf < Ar table Ns > -.Ed -.Pp -In a listener context, the credentials are a mapping of username and encrypted -passwords: -.Bd -literal -offset indent -user1 $2b$10$hIJ4QfMcp.90nJwKqGbKM.MybArjHOTpEtoTV.DgLYAiThuoYmTSe -user2 $2b$10$bwSmUOBGcZGamIfRuXGTvuTo3VLbPG9k5yeKNMBtULBhksV5KdGsK -.Ed -.Pp -The passwords are to be encrypted using the -.Xr smtpctl 8 -encrypt subcommand. -.Pp -In a relay context, the credentials are a mapping of labels and -username:password pairs: -.Bd -literal -offset indent -label1 user:password -.Ed -.Pp -The label must be unique and is used as a selector for the proper credentials -when multiple credentials are valid for a single destination. -The password is not encrypted as it must be provided to the remote host. -.Ss Netaddr tables -Netaddr tables are lists of IPv4 and IPv6 network addresses. -They can only be used in the following context: -.Pp -.D1 Ic match Cm from src Pf < Ar table Ns > Cm action Ar name -.Pp -When used as a "from source", the address of a client is compared to the list -of addresses in the table until a match is found. -.Pp -A netaddr table can contain exact addresses or netmasks, and looks as follow: -.Bd -literal -offset indent -192.168.1.1 -::1 -ipv6:::1 -192.168.1.0/24 -.Ed -.Ss Userinfo tables -User info tables are used in rule context to specify an alternate user base, -mapping virtual users to local system users by UID, GID and home directory. -.Pp -.D1 Ic action Ar name method Cm userbase Pf < Ar table Ns > -.Pp -A userinfo table looks as follows: -.Bd -literal -offset indent -joe 1000:100:/home/virtual/joe -jack 1000:100:/home/virtual/jack -.Ed -.Pp -In this example, both joe and jack are virtual users mapped to the local -system user with UID 1000 and GID 100, but different home directories. -These directories may contain a -.Xr forward 5 -file. -This can be used in conjunction with an alias table -that maps an email address or the domain part to the desired virtual -username. -For example: -.Bd -literal -offset indent -joe@example.org joe -jack@example.com jack -.Ed -.Ss Source tables -Source tables are lists of IPv4 and IPv6 addresses. -They can only be used in the following context: -.Pp -.D1 Ic action Ar name Cm relay src Pf < Ar table Ns > -.Pp -Successive queries to the source table will return the elements one by one. -.Pp -A source table looks as follow: -.Bd -literal -offset indent -192.168.1.2 -192.168.1.3 -::1 -::2 -ipv6:::3 -ipv6:::4 -.Ed -.Ss Mailaddr tables -Mailaddr tables are lists of email addresses. -They can be used in the following contexts: -.Bd -unfilled -offset indent -.Ic match Cm mail\-from Pf < Ar table Ns > Cm action Ar name -.Ic match Cm rcpt\-to Pf < Ar table Ns > Cm action Ar name -.Ed -.Pp -A mailaddr entry is used to match an email address against a username, -a domain or a full email address. -A "*" wildcard may be used in part of the domain name. -.Pp -A mailaddr table looks as follow: -.Bd -literal -offset indent -user -@domain -user@domain -user@*.domain -.Ed -.Ss Addrname tables -Addrname tables are used to map IP addresses to hostnames. -They can be used in both listen context and relay context: -.Bd -unfilled -offset indent -.Ic listen on Ar interface Cm hostnames Pf < Ar table Ns > -.Ic action Ar name Cm relay helo\-src Pf < Ar table Ns > -.Ed -.Pp -In listen context, the table is used to look up the server name to advertise -depending on the local address of the socket on which a connection is accepted. -In relay context, the table is used to determine the hostname for the HELO -sequence of the SMTP protocol, depending on the local address used for the -outgoing connection. -.Pp -The format is a mapping from inet4 or inet6 addresses to hostnames: -.Bd -literal -offset indent -::1 localhost -127.0.0.1 localhost -88.190.23.165 www.opensmtpd.org -.Ed -.Sh SEE ALSO -.Xr smtpd.conf 5 , -.Xr makemap 8 , -.Xr smtpd 8 diff --git a/smtpd/table.c b/smtpd/table.c deleted file mode 100644 index 469eeee1..00000000 --- a/smtpd/table.c +++ /dev/null @@ -1,709 +0,0 @@ -/* $OpenBSD: table.c,v 1.48 2019/01/10 07:40:52 eric Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2008 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -struct table_backend *table_backend_lookup(const char *); - -extern struct table_backend table_backend_static; -#ifdef HAVE_DB_API -extern struct table_backend table_backend_db; -#endif -extern struct table_backend table_backend_getpwnam; -extern struct table_backend table_backend_proc; - -static const char * table_service_name(enum table_service); -static int table_parse_lookup(enum table_service, const char *, const char *, - union lookup *); -static int parse_sockaddr(struct sockaddr *, int, const char *); - -static unsigned int last_table_id = 0; - -static struct table_backend *backends[] = { - &table_backend_static, -#ifdef HAVE_DB_API - &table_backend_db, -#endif - &table_backend_getpwnam, - &table_backend_proc, - NULL -}; - -struct table_backend * -table_backend_lookup(const char *backend) -{ - int i; - - if (!strcmp(backend, "file")) - backend = "static"; - - for (i = 0; backends[i]; i++) - if (!strcmp(backends[i]->name, backend)) - return (backends[i]); - - return NULL; -} - -static const char * -table_service_name(enum table_service s) -{ - switch (s) { - case K_NONE: return "NONE"; - case K_ALIAS: return "ALIAS"; - case K_DOMAIN: return "DOMAIN"; - case K_CREDENTIALS: return "CREDENTIALS"; - case K_NETADDR: return "NETADDR"; - case K_USERINFO: return "USERINFO"; - case K_SOURCE: return "SOURCE"; - case K_MAILADDR: return "MAILADDR"; - case K_ADDRNAME: return "ADDRNAME"; - case K_MAILADDRMAP: return "MAILADDRMAP"; - case K_RELAYHOST: return "RELAYHOST"; - case K_STRING: return "STRING"; - case K_REGEX: return "REGEX"; - } - return "???"; -} - -struct table * -table_find(struct smtpd *conf, const char *name) -{ - return dict_get(conf->sc_tables_dict, name); -} - -int -table_match(struct table *table, enum table_service kind, const char *key) -{ - return table_lookup(table, kind, key, NULL); -} - -int -table_lookup(struct table *table, enum table_service kind, const char *key, - union lookup *lk) -{ - char lkey[1024], *buf = NULL; - int r; - - r = -1; - if (table->t_backend->lookup == NULL) - errno = ENOTSUP; - else if (!lowercase(lkey, key, sizeof lkey)) { - log_warnx("warn: lookup key too long: %s", key); - errno = EINVAL; - } - else - r = table->t_backend->lookup(table, kind, lkey, lk ? &buf : NULL); - - if (r == 1) { - log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s%s", - lk ? "lookup" : "match", - key, - table_service_name(kind), - table->t_backend->name, - table->t_name, - lk ? "\"" : "", - lk ? buf : "true", - lk ? "\"" : ""); - if (buf) - r = table_parse_lookup(kind, lkey, buf, lk); - } - else - log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s", - lk ? "lookup" : "match", - key, - table_service_name(kind), - table->t_backend->name, - table->t_name, - (r == -1) ? "error: " : (lk ? "none" : "false"), - (r == -1) ? strerror(errno) : ""); - - free(buf); - - return (r); -} - -int -table_fetch(struct table *table, enum table_service kind, union lookup *lk) -{ - char *buf = NULL; - int r; - - r = -1; - if (table->t_backend->fetch == NULL) - errno = ENOTSUP; - else - r = table->t_backend->fetch(table, kind, &buf); - - if (r == 1) { - log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> \"%s\"", - table_service_name(kind), - table->t_backend->name, - table->t_name, - buf); - r = table_parse_lookup(kind, NULL, buf, lk); - } - else - log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> %s%s", - table_service_name(kind), - table->t_backend->name, - table->t_name, - (r == -1) ? "error: " : "none", - (r == -1) ? strerror(errno) : ""); - - free(buf); - - return (r); -} - -struct table * -table_create(struct smtpd *conf, const char *backend, const char *name, - const char *config) -{ - struct table *t; - struct table_backend *tb; - char path[LINE_MAX]; - size_t n; - struct stat sb; - - if (name && table_find(conf, name)) - fatalx("table_create: table \"%s\" already defined", name); - - if ((tb = table_backend_lookup(backend)) == NULL) { - if ((size_t)snprintf(path, sizeof(path), PATH_LIBEXEC"/table-%s", - backend) >= sizeof(path)) { - fatalx("table_create: path too long \"" - PATH_LIBEXEC"/table-%s\"", backend); - } - if (stat(path, &sb) == 0) { - tb = table_backend_lookup("proc"); - (void)strlcpy(path, backend, sizeof(path)); - if (config) { - (void)strlcat(path, ":", sizeof(path)); - if (strlcat(path, config, sizeof(path)) - >= sizeof(path)) - fatalx("table_create: config file path too long"); - } - config = path; - } - } - - if (tb == NULL) - fatalx("table_create: backend \"%s\" does not exist", backend); - - t = xcalloc(1, sizeof(*t)); - t->t_backend = tb; - - if (config) { - if (strlcpy(t->t_config, config, sizeof t->t_config) - >= sizeof t->t_config) - fatalx("table_create: table config \"%s\" too large", - t->t_config); - } - - if (strcmp(tb->name, "static") != 0) - t->t_type = T_DYNAMIC; - - if (name == NULL) - (void)snprintf(t->t_name, sizeof(t->t_name), "", - last_table_id++); - else { - n = strlcpy(t->t_name, name, sizeof(t->t_name)); - if (n >= sizeof(t->t_name)) - fatalx("table_create: table name too long"); - } - - dict_set(conf->sc_tables_dict, t->t_name, t); - - return (t); -} - -void -table_destroy(struct smtpd *conf, struct table *t) -{ - dict_xpop(conf->sc_tables_dict, t->t_name); - free(t); -} - -int -table_config(struct table *t) -{ - if (t->t_backend->config == NULL) - return (1); - return (t->t_backend->config(t)); -} - -void -table_add(struct table *t, const char *key, const char *val) -{ - if (t->t_backend->add == NULL) - fatalx("table_add: cannot add to table"); - - if (t->t_backend->add(t, key, val) == 0) - log_warnx("warn: failed to add \"%s\" in table \"%s\"", key, t->t_name); -} - -void -table_dump(struct table *t) -{ - const char *type; - char buf[LINE_MAX]; - - switch(t->t_type) { - case T_NONE: - type = "NONE"; - break; - case T_DYNAMIC: - type = "DYNAMIC"; - break; - case T_LIST: - type = "LIST"; - break; - case T_HASH: - type = "HASH"; - break; - default: - type = "???"; - break; - } - - if (t->t_config[0]) - snprintf(buf, sizeof(buf), " config=\"%s\"", t->t_config); - else - buf[0] = '\0'; - - log_debug("TABLE \"%s\" backend=%s type=%s%s", t->t_name, - t->t_backend->name, type, buf); - - if (t->t_backend->dump) - t->t_backend->dump(t); -} - -int -table_check_type(struct table *t, uint32_t mask) -{ - return t->t_type & mask; -} - -int -table_check_service(struct table *t, uint32_t mask) -{ - return t->t_backend->services & mask; -} - -int -table_check_use(struct table *t, uint32_t tmask, uint32_t smask) -{ - return table_check_type(t, tmask) && table_check_service(t, smask); -} - -int -table_open(struct table *t) -{ - if (t->t_backend->open == NULL) - return (1); - return (t->t_backend->open(t)); -} - -void -table_close(struct table *t) -{ - if (t->t_backend->close) - t->t_backend->close(t); -} - -int -table_update(struct table *t) -{ - if (t->t_backend->update == NULL) - return (1); - return (t->t_backend->update(t)); -} - - -/* - * quick reminder: - * in *_match() s1 comes from session, s2 comes from table - */ - -int -table_domain_match(const char *s1, const char *s2) -{ - return hostname_match(s1, s2); -} - -int -table_mailaddr_match(const char *s1, const char *s2) -{ - struct mailaddr m1; - struct mailaddr m2; - - if (!text_to_mailaddr(&m1, s1)) - return 0; - if (!text_to_mailaddr(&m2, s2)) - return 0; - return mailaddr_match(&m1, &m2); -} - -static int table_match_mask(struct sockaddr_storage *, struct netaddr *); -static int table_inet4_match(struct sockaddr_in *, struct netaddr *); -static int table_inet6_match(struct sockaddr_in6 *, struct netaddr *); - -int -table_netaddr_match(const char *s1, const char *s2) -{ - struct netaddr n1; - struct netaddr n2; - - if (strcasecmp(s1, s2) == 0) - return 1; - if (!text_to_netaddr(&n1, s1)) - return 0; - if (!text_to_netaddr(&n2, s2)) - return 0; - if (n1.ss.ss_family != n2.ss.ss_family) - return 0; - if (SS_LEN(&n1.ss) != SS_LEN(&n2.ss)) - return 0; - return table_match_mask(&n1.ss, &n2); -} - -static int -table_match_mask(struct sockaddr_storage *ss, struct netaddr *ssmask) -{ - if (ss->ss_family == AF_INET) - return table_inet4_match((struct sockaddr_in *)ss, ssmask); - - if (ss->ss_family == AF_INET6) - return table_inet6_match((struct sockaddr_in6 *)ss, ssmask); - - return (0); -} - -static int -table_inet4_match(struct sockaddr_in *ss, struct netaddr *ssmask) -{ - in_addr_t mask; - int i; - - /* a.b.c.d/8 -> htonl(0xff000000) */ - mask = 0; - for (i = 0; i < ssmask->bits; ++i) - mask = (mask >> 1) | 0x80000000; - mask = htonl(mask); - - /* (addr & mask) == (net & mask) */ - if ((ss->sin_addr.s_addr & mask) == - (((struct sockaddr_in *)ssmask)->sin_addr.s_addr & mask)) - return 1; - - return 0; -} - -static int -table_inet6_match(struct sockaddr_in6 *ss, struct netaddr *ssmask) -{ - struct in6_addr *in; - struct in6_addr *inmask; - struct in6_addr mask; - int i; - - memset(&mask, 0, sizeof(mask)); - for (i = 0; i < ssmask->bits / 8; i++) - mask.s6_addr[i] = 0xff; - i = ssmask->bits % 8; - if (i) - mask.s6_addr[ssmask->bits / 8] = 0xff00 >> i; - - in = &ss->sin6_addr; - inmask = &((struct sockaddr_in6 *)&ssmask->ss)->sin6_addr; - - for (i = 0; i < 16; i++) { - if ((in->s6_addr[i] & mask.s6_addr[i]) != - (inmask->s6_addr[i] & mask.s6_addr[i])) - return (0); - } - - return (1); -} - -int -table_regex_match(const char *string, const char *pattern) -{ - regex_t preg; - int cflags = REG_EXTENDED|REG_NOSUB; - - if (strncmp(pattern, "(?i)", 4) == 0) { - cflags |= REG_ICASE; - pattern += 4; - } - - if (regcomp(&preg, pattern, cflags) != 0) - return (0); - - if (regexec(&preg, string, 0, NULL, 0) != 0) - return (0); - - return (1); -} - -void -table_dump_all(struct smtpd *conf) -{ - struct table *t; - void *iter; - - iter = NULL; - while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) - table_dump(t); -} - -void -table_open_all(struct smtpd *conf) -{ - struct table *t; - void *iter; - - iter = NULL; - while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) - if (!table_open(t)) - fatalx("failed to open table %s", t->t_name); -} - -void -table_close_all(struct smtpd *conf) -{ - struct table *t; - void *iter; - - iter = NULL; - while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) - table_close(t); -} - -static int -table_parse_lookup(enum table_service service, const char *key, - const char *line, union lookup *lk) -{ - char buffer[LINE_MAX], *p; - size_t len; - - len = strlen(line); - - switch (service) { - case K_ALIAS: - lk->expand = calloc(1, sizeof(*lk->expand)); - if (lk->expand == NULL) - return (-1); - if (!expand_line(lk->expand, line, 1)) { - expand_free(lk->expand); - return (-1); - } - return (1); - - case K_DOMAIN: - if (strlcpy(lk->domain.name, line, sizeof(lk->domain.name)) - >= sizeof(lk->domain.name)) - return (-1); - return (1); - - case K_CREDENTIALS: - - /* credentials are stored as user:password */ - if (len < 3) - return (-1); - - /* too big to fit in a smtp session line */ - if (len >= LINE_MAX) - return (-1); - - p = strchr(line, ':'); - if (p == NULL) { - if (strlcpy(lk->creds.username, key, sizeof (lk->creds.username)) - >= sizeof (lk->creds.username)) - return (-1); - if (strlcpy(lk->creds.password, line, sizeof(lk->creds.password)) - >= sizeof(lk->creds.password)) - return (-1); - return (1); - } - - if (p == line || p == line + len - 1) - return (-1); - - memmove(lk->creds.username, line, p - line); - lk->creds.username[p - line] = '\0'; - - if (strlcpy(lk->creds.password, p+1, sizeof(lk->creds.password)) - >= sizeof(lk->creds.password)) - return (-1); - - return (1); - - case K_NETADDR: - if (!text_to_netaddr(&lk->netaddr, line)) - return (-1); - return (1); - - case K_USERINFO: - if (!bsnprintf(buffer, sizeof(buffer), "%s:%s", key, line)) - return (-1); - if (!text_to_userinfo(&lk->userinfo, buffer)) - return (-1); - return (1); - - case K_SOURCE: - if (parse_sockaddr((struct sockaddr *)&lk->source.addr, - PF_UNSPEC, line) == -1) - return (-1); - return (1); - - case K_MAILADDR: - if (!text_to_mailaddr(&lk->mailaddr, line)) - return (-1); - return (1); - - case K_MAILADDRMAP: - lk->maddrmap = calloc(1, sizeof(*lk->maddrmap)); - if (lk->maddrmap == NULL) - return (-1); - maddrmap_init(lk->maddrmap); - if (!mailaddr_line(lk->maddrmap, line)) { - maddrmap_free(lk->maddrmap); - return (-1); - } - return (1); - - case K_ADDRNAME: - if (parse_sockaddr((struct sockaddr *)&lk->addrname.addr, - PF_UNSPEC, key) == -1) - return (-1); - if (strlcpy(lk->addrname.name, line, sizeof(lk->addrname.name)) - >= sizeof(lk->addrname.name)) - return (-1); - return (1); - - case K_RELAYHOST: - if (strlcpy(lk->relayhost, line, sizeof(lk->relayhost)) - >= sizeof(lk->relayhost)) - return (-1); - return (1); - - default: - return (-1); - } -} - -static int -parse_sockaddr(struct sockaddr *sa, int family, const char *str) -{ - struct in_addr ina; - struct in6_addr in6a; - struct sockaddr_in *sin; - struct sockaddr_in6 *sin6; - char *cp, *str2; - const char *errstr; - - switch (family) { - case PF_UNSPEC: - if (parse_sockaddr(sa, PF_INET, str) == 0) - return (0); - return parse_sockaddr(sa, PF_INET6, str); - - case PF_INET: - if (inet_pton(PF_INET, str, &ina) != 1) - return (-1); - - sin = (struct sockaddr_in *)sa; - memset(sin, 0, sizeof *sin); -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - sin->sin_len = sizeof(struct sockaddr_in); -#endif - sin->sin_family = PF_INET; - sin->sin_addr.s_addr = ina.s_addr; - return (0); - - case PF_INET6: - if (strncasecmp("ipv6:", str, 5) == 0) - str += 5; - cp = strchr(str, SCOPE_DELIMITER); - if (cp) { - str2 = strdup(str); - if (str2 == NULL) - return (-1); - str2[cp - str] = '\0'; - if (inet_pton(PF_INET6, str2, &in6a) != 1) { - free(str2); - return (-1); - } - cp++; - free(str2); - } else if (inet_pton(PF_INET6, str, &in6a) != 1) - return (-1); - - sin6 = (struct sockaddr_in6 *)sa; - memset(sin6, 0, sizeof *sin6); -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sin6->sin6_len = sizeof(struct sockaddr_in6); -#endif - sin6->sin6_family = PF_INET6; - sin6->sin6_addr = in6a; - - if (cp == NULL) - return (0); - - if (IN6_IS_ADDR_LINKLOCAL(&in6a) || - IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || - IN6_IS_ADDR_MC_NODELOCAL(&in6a)) - if ((sin6->sin6_scope_id = if_nametoindex(cp))) - return (0); - - sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); - if (errstr) - return (-1); - return (0); - - default: - break; - } - - return (-1); -} diff --git a/smtpd/table_db.c b/smtpd/table_db.c deleted file mode 100644 index f7d766dd..00000000 --- a/smtpd/table_db.c +++ /dev/null @@ -1,282 +0,0 @@ -/* $OpenBSD: table_db.c,v 1.21 2019/06/28 13:32:51 deraadt Exp $ */ - -/* - * Copyright (c) 2011 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#ifdef HAVE_DB_H -#include -#elif defined(HAVE_DB1_DB_H) -#include -#elif defined(HAVE_DB_185_H) -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - - -/* db(3) backend */ -static int table_db_config(struct table *); -static int table_db_update(struct table *); -static int table_db_open(struct table *); -static void *table_db_open2(struct table *); -static int table_db_lookup(struct table *, enum table_service, const char *, char **); -static int table_db_fetch(struct table *, enum table_service, char **); -static void table_db_close(struct table *); -static void table_db_close2(void *); - -static char *table_db_get_entry(void *, const char *, size_t *); -static char *table_db_get_entry_match(void *, const char *, size_t *, - int(*)(const char *, const char *)); - -struct table_backend table_backend_db = { - "db", - K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO|K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP, - table_db_config, - NULL, - NULL, - table_db_open, - table_db_update, - table_db_close, - table_db_lookup, - table_db_fetch, -}; - -static struct keycmp { - enum table_service service; - int (*func)(const char *, const char *); -} keycmp[] = { - { K_DOMAIN, table_domain_match }, - { K_NETADDR, table_netaddr_match }, - { K_MAILADDR, table_mailaddr_match } -}; - -struct dbhandle { - DB *db; - char pathname[PATH_MAX]; - time_t mtime; - int iter; -}; - -static int -table_db_config(struct table *table) -{ - struct dbhandle *handle; - - handle = table_db_open2(table); - if (handle == NULL) - return 0; - - table_db_close2(handle); - return 1; -} - -static int -table_db_update(struct table *table) -{ - struct dbhandle *handle; - - handle = table_db_open2(table); - if (handle == NULL) - return 0; - - table_db_close2(table->t_handle); - table->t_handle = handle; - return 1; -} - -static int -table_db_open(struct table *table) -{ - table->t_handle = table_db_open2(table); - if (table->t_handle == NULL) - return 0; - return 1; -} - -static void -table_db_close(struct table *table) -{ - table_db_close2(table->t_handle); - table->t_handle = NULL; -} - -static void * -table_db_open2(struct table *table) -{ - struct dbhandle *handle; - struct stat sb; - - handle = xcalloc(1, sizeof *handle); - if (strlcpy(handle->pathname, table->t_config, sizeof handle->pathname) - >= sizeof handle->pathname) - goto error; - - if (stat(handle->pathname, &sb) == -1) - goto error; - - handle->mtime = sb.st_mtime; - handle->db = dbopen(table->t_config, O_RDONLY, 0600, DB_HASH, NULL); - if (handle->db == NULL) - goto error; - - return handle; - -error: - if (handle->db) - handle->db->close(handle->db); - free(handle); - return NULL; -} - -static void -table_db_close2(void *hdl) -{ - struct dbhandle *handle = hdl; - handle->db->close(handle->db); - free(handle); -} - -static int -table_db_lookup(struct table *table, enum table_service service, const char *key, - char **dst) -{ - struct dbhandle *handle = table->t_handle; - char *line; - size_t len = 0; - int ret; - int (*match)(const char *, const char *) = NULL; - size_t i; - struct stat sb; - - if (stat(handle->pathname, &sb) == -1) - return -1; - - /* DB has changed, close and reopen */ - if (sb.st_mtime != handle->mtime) { - table_db_update(table); - handle = table->t_handle; - } - - for (i = 0; i < nitems(keycmp); ++i) - if (keycmp[i].service == service) - match = keycmp[i].func; - - if (match == NULL) - line = table_db_get_entry(handle, key, &len); - else - line = table_db_get_entry_match(handle, key, &len, match); - if (line == NULL) - return 0; - - ret = 1; - if (dst) - *dst = line; - else - free(line); - - return ret; -} - -static int -table_db_fetch(struct table *table, enum table_service service, char **dst) -{ - struct dbhandle *handle = table->t_handle; - DBT dbk; - DBT dbd; - int r; - - if (handle->iter == 0) - r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); - else - r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT); - handle->iter = 1; - if (!r) { - r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); - if (!r) - return 0; - } - - *dst = strdup(dbk.data); - if (*dst == NULL) - return -1; - - return 1; -} - - -static char * -table_db_get_entry_match(void *hdl, const char *key, size_t *len, - int(*func)(const char *, const char *)) -{ - struct dbhandle *handle = hdl; - DBT dbk; - DBT dbd; - int r; - char *buf = NULL; - - for (r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); !r; - r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT)) { - buf = xmemdup(dbk.data, dbk.size); - if (func(key, buf)) { - *len = dbk.size; - return buf; - } - free(buf); - } - return NULL; -} - -static char * -table_db_get_entry(void *hdl, const char *key, size_t *len) -{ - struct dbhandle *handle = hdl; - int ret; - DBT dbk; - DBT dbv; - char pkey[LINE_MAX]; - - /* workaround the stupidity of the DB interface */ - if (strlcpy(pkey, key, sizeof pkey) >= sizeof pkey) - errx(1, "table_db_get_entry: key too long"); - dbk.data = pkey; - dbk.size = strlen(pkey) + 1; - - if ((ret = handle->db->get(handle->db, &dbk, &dbv, 0)) != 0) - return NULL; - - *len = dbv.size; - - return xmemdup(dbv.data, dbv.size); -} diff --git a/smtpd/table_getpwnam.c b/smtpd/table_getpwnam.c deleted file mode 100644 index ccf889be..00000000 --- a/smtpd/table_getpwnam.c +++ /dev/null @@ -1,120 +0,0 @@ -/* $OpenBSD: table_getpwnam.c,v 1.12 2018/12/27 14:23:41 eric Exp $ */ - -/* - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - - -/* getpwnam(3) backend */ -static int table_getpwnam_config(struct table *); -static int table_getpwnam_update(struct table *); -static int table_getpwnam_open(struct table *); -static int table_getpwnam_lookup(struct table *, enum table_service, const char *, - char **); -static void table_getpwnam_close(struct table *); - -struct table_backend table_backend_getpwnam = { - "getpwnam", - K_USERINFO, - table_getpwnam_config, - NULL, - NULL, - table_getpwnam_open, - table_getpwnam_update, - table_getpwnam_close, - table_getpwnam_lookup, -}; - - -static int -table_getpwnam_config(struct table *table) -{ - if (table->t_config[0]) - return 0; - return 1; -} - -static int -table_getpwnam_update(struct table *table) -{ - return 1; -} - -static int -table_getpwnam_open(struct table *table) -{ - return 1; -} - -static void -table_getpwnam_close(struct table *table) -{ - return; -} - -static int -table_getpwnam_lookup(struct table *table, enum table_service kind, const char *key, - char **dst) -{ - struct passwd *pw; - - if (kind != K_USERINFO) - return -1; - - errno = 0; - do { - pw = getpwnam(key); - } while (pw == NULL && errno == EINTR); - - if (pw == NULL) { - if (errno) - return -1; - return 0; - } - if (dst == NULL) - return 1; - - if (asprintf(dst, "%d:%d:%s", - pw->pw_uid, - pw->pw_gid, - pw->pw_dir) == -1) { - *dst = NULL; - return -1; - } - - return (1); -} diff --git a/smtpd/table_proc.c b/smtpd/table_proc.c deleted file mode 100644 index 44589bd7..00000000 --- a/smtpd/table_proc.c +++ /dev/null @@ -1,283 +0,0 @@ -/* $OpenBSD: table_proc.c,v 1.16 2019/10/03 04:51:15 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#ifdef HAVE_PATHS_H -#include -#endif -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -struct table_proc_priv { - pid_t pid; - struct imsgbuf ibuf; -}; - -static struct imsg imsg; -static size_t rlen; -static char *rdata; - -extern char **environ; - -static void -table_proc_call(struct table_proc_priv *p) -{ - ssize_t n; - - if (imsg_flush(&p->ibuf) == -1) { - log_warn("warn: table-proc: imsg_flush"); - fatalx("table-proc: exiting"); - } - - while (1) { - if ((n = imsg_get(&p->ibuf, &imsg)) == -1) { - log_warn("warn: table-proc: imsg_get"); - break; - } - if (n) { - rlen = imsg.hdr.len - IMSG_HEADER_SIZE; - rdata = imsg.data; - - if (imsg.hdr.type != PROC_TABLE_OK) { - log_warnx("warn: table-proc: bad response"); - break; - } - return; - } - - if ((n = imsg_read(&p->ibuf)) == -1 && errno != EAGAIN) { - log_warn("warn: table-proc: imsg_read"); - break; - } - - if (n == 0) { - log_warnx("warn: table-proc: pipe closed"); - break; - } - } - - fatalx("table-proc: exiting"); -} - -static void -table_proc_read(void *dst, size_t len) -{ - if (len > rlen) { - log_warnx("warn: table-proc: bad msg len"); - fatalx("table-proc: exiting"); - } - - if (dst) - memmove(dst, rdata, len); - - rlen -= len; - rdata += len; -} - -static void -table_proc_end(void) -{ - if (rlen) { - log_warnx("warn: table-proc: bogus data"); - fatalx("table-proc: exiting"); - } - imsg_free(&imsg); -} - -/* - * API - */ - -static int -table_proc_open(struct table *table) -{ - struct table_proc_priv *priv; - struct table_open_params op; - int fd; - - priv = xcalloc(1, sizeof(*priv)); - - fd = fork_proc_backend("table", table->t_config, table->t_name); - if (fd == -1) - fatalx("table-proc: exiting"); - - imsg_init(&priv->ibuf, fd); - - memset(&op, 0, sizeof op); - op.version = PROC_TABLE_API_VERSION; - (void)strlcpy(op.name, table->t_name, sizeof op.name); - imsg_compose(&priv->ibuf, PROC_TABLE_OPEN, 0, 0, -1, &op, sizeof op); - - table_proc_call(priv); - table_proc_end(); - - table->t_handle = priv; - - return (1); -} - -static int -table_proc_update(struct table *table) -{ - struct table_proc_priv *priv = table->t_handle; - int r; - - imsg_compose(&priv->ibuf, PROC_TABLE_UPDATE, 0, 0, -1, NULL, 0); - - table_proc_call(priv); - table_proc_read(&r, sizeof(r)); - table_proc_end(); - - return (r); -} - -static void -table_proc_close(struct table *table) -{ - struct table_proc_priv *priv = table->t_handle; - - imsg_compose(&priv->ibuf, PROC_TABLE_CLOSE, 0, 0, -1, NULL, 0); - if (imsg_flush(&priv->ibuf) == -1) - fatal("imsg_flush"); - - table->t_handle = NULL; -} - -static int -imsg_add_params(struct ibuf *buf) -{ - size_t count = 0; - - if (imsg_add(buf, &count, sizeof(count)) == -1) - return (-1); - - return (0); -} - -static int -table_proc_lookup(struct table *table, enum table_service s, const char *k, char **dst) -{ - struct table_proc_priv *priv = table->t_handle; - struct ibuf *buf; - int r; - - buf = imsg_create(&priv->ibuf, - dst ? PROC_TABLE_LOOKUP : PROC_TABLE_CHECK, 0, 0, - sizeof(s) + strlen(k) + 1); - - if (buf == NULL) - return (-1); - if (imsg_add(buf, &s, sizeof(s)) == -1) - return (-1); - if (imsg_add_params(buf) == -1) - return (-1); - if (imsg_add(buf, k, strlen(k) + 1) == -1) - return (-1); - imsg_close(&priv->ibuf, buf); - - table_proc_call(priv); - table_proc_read(&r, sizeof(r)); - - if (r == 1 && dst) { - if (rlen == 0) { - log_warnx("warn: table-proc: empty response"); - fatalx("table-proc: exiting"); - } - if (rdata[rlen - 1] != '\0') { - log_warnx("warn: table-proc: not NUL-terminated"); - fatalx("table-proc: exiting"); - } - *dst = strdup(rdata); - if (*dst == NULL) - r = -1; - table_proc_read(NULL, rlen); - } - - table_proc_end(); - - return (r); -} - -static int -table_proc_fetch(struct table *table, enum table_service s, char **dst) -{ - struct table_proc_priv *priv = table->t_handle; - struct ibuf *buf; - int r; - - buf = imsg_create(&priv->ibuf, PROC_TABLE_FETCH, 0, 0, sizeof(s)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &s, sizeof(s)) == -1) - return (-1); - if (imsg_add_params(buf) == -1) - return (-1); - imsg_close(&priv->ibuf, buf); - - table_proc_call(priv); - table_proc_read(&r, sizeof(r)); - - if (r == 1) { - if (rlen == 0) { - log_warnx("warn: table-proc: empty response"); - fatalx("table-proc: exiting"); - } - if (rdata[rlen - 1] != '\0') { - log_warnx("warn: table-proc: not NUL-terminated"); - fatalx("table-proc: exiting"); - } - *dst = strdup(rdata); - if (*dst == NULL) - r = -1; - table_proc_read(NULL, rlen); - } - - table_proc_end(); - - return (r); -} - -struct table_backend table_backend_proc = { - "proc", - K_ANY, - NULL, - NULL, - NULL, - table_proc_open, - table_proc_update, - table_proc_close, - table_proc_lookup, - table_proc_fetch, -}; diff --git a/smtpd/table_static.c b/smtpd/table_static.c deleted file mode 100644 index 8f78ae11..00000000 --- a/smtpd/table_static.c +++ /dev/null @@ -1,398 +0,0 @@ -/* $OpenBSD: table_static.c,v 1.32 2018/12/28 14:21:02 eric Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -struct table_static_priv { - int type; - struct dict dict; - void *iter; -}; - -/* static backend */ -static int table_static_config(struct table *); -static int table_static_add(struct table *, const char *, const char *); -static void table_static_dump(struct table *); -static int table_static_update(struct table *); -static int table_static_open(struct table *); -static int table_static_lookup(struct table *, enum table_service, const char *, - char **); -static int table_static_fetch(struct table *, enum table_service, char **); -static void table_static_close(struct table *); - -struct table_backend table_backend_static = { - "static", - K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO| - K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP|K_RELAYHOST| - K_STRING|K_REGEX, - table_static_config, - table_static_add, - table_static_dump, - table_static_open, - table_static_update, - table_static_close, - table_static_lookup, - table_static_fetch -}; - -static struct keycmp { - enum table_service service; - int (*func)(const char *, const char *); -} keycmp[] = { - { K_DOMAIN, table_domain_match }, - { K_NETADDR, table_netaddr_match }, - { K_MAILADDR, table_mailaddr_match }, - { K_REGEX, table_regex_match }, -}; - - -static void -table_static_priv_free(struct table_static_priv *priv) -{ - void *p; - - while (dict_poproot(&priv->dict, (void **)&p)) - if (p != priv) - free(p); - free(priv); -} - -static int -table_static_priv_add(struct table_static_priv *priv, const char *key, const char *val) -{ - char lkey[1024]; - void *old, *new = NULL; - - if (!lowercase(lkey, key, sizeof lkey)) { - errno = ENAMETOOLONG; - return (-1); - } - - if (val) { - new = strdup(val); - if (new == NULL) - return (-1); - } - - /* use priv if value is null, so we can detect duplicate entries */ - old = dict_set(&priv->dict, lkey, new ? new : priv); - if (old) { - if (old != priv) - free(old); - return (1); - } - - return (0); -} - -static int -table_static_priv_load(struct table_static_priv *priv, const char *path) -{ - FILE *fp; - char *buf = NULL, *p; - int lineno = 0; - size_t sz = 0; - ssize_t flen; - char *keyp; - char *valp; - int ret = 0; - - if ((fp = fopen(path, "r")) == NULL) { - log_warn("%s: fopen", path); - return 0; - } - - while ((flen = getline(&buf, &sz, fp)) != -1) { - lineno++; - if (buf[flen - 1] == '\n') - buf[--flen] = '\0'; - - keyp = buf; - while (isspace((unsigned char)*keyp)) { - ++keyp; - --flen; - } - if (*keyp == '\0') - continue; - while (isspace((unsigned char)keyp[flen - 1])) - keyp[--flen] = '\0'; - if (*keyp == '#') { - if (priv->type == T_NONE) { - keyp++; - while (isspace((unsigned char)*keyp)) - ++keyp; - if (!strcmp(keyp, "@list")) - priv->type = T_LIST; - } - continue; - } - - if (priv->type == T_NONE) { - for (p = keyp; *p; p++) { - if (*p == ' ' || *p == '\t' || *p == ':') { - priv->type = T_HASH; - break; - } - } - if (priv->type == T_NONE) - priv->type = T_LIST; - } - - if (priv->type == T_LIST) { - table_static_priv_add(priv, keyp, NULL); - continue; - } - - /* T_HASH */ - valp = keyp; - strsep(&valp, " \t:"); - if (valp) { - while (*valp) { - if (!isspace((unsigned char)*valp) && - !(*valp == ':' && - isspace((unsigned char)*(valp + 1)))) - break; - ++valp; - } - if (*valp == '\0') - valp = NULL; - } - if (valp == NULL) { - log_warnx("%s: invalid map entry line %d", - path, lineno); - goto end; - } - - table_static_priv_add(priv, keyp, valp); - } - - if (ferror(fp)) { - log_warn("%s: getline", path); - goto end; - } - - /* Accept empty alias files; treat them as hashes */ - if (priv->type == T_NONE) - priv->type = T_HASH; - - ret = 1; -end: - free(buf); - fclose(fp); - return ret; -} - -static int -table_static_config(struct table *t) -{ - struct table_static_priv *priv, *old; - - /* already up, and no config file? ok */ - if (t->t_handle && *t->t_config == '\0') - return 1; - - /* new config */ - priv = calloc(1, sizeof(*priv)); - if (priv == NULL) - return 0; - priv->type = t->t_type; - dict_init(&priv->dict); - - if (*t->t_config) { - /* load the config file */ - if (table_static_priv_load(priv, t->t_config) == 0) { - table_static_priv_free(priv); - return 0; - } - } - - if ((old = t->t_handle)) - table_static_priv_free(old); - t->t_handle = priv; - t->t_type = priv->type; - - return 1; -} - -static int -table_static_add(struct table *table, const char *key, const char *val) -{ - struct table_static_priv *priv = table->t_handle; - int r; - - /* cannot add to a table read from a file */ - if (*table->t_config) - return 0; - - if (table->t_type == T_NONE) - table->t_type = val ? T_HASH : T_LIST; - else if (table->t_type == T_LIST && val) - return 0; - else if (table->t_type == T_HASH && val == NULL) - return 0; - - if (priv == NULL) { - if (table_static_config(table) == 0) - return 0; - priv = table->t_handle; - } - - r = table_static_priv_add(priv, key, val); - if (r == -1) - return 0; - return 1; -} - -static void -table_static_dump(struct table *table) -{ - struct table_static_priv *priv = table->t_handle; - const char *key; - char *value; - void *iter; - - iter = NULL; - while (dict_iter(&priv->dict, &iter, &key, (void**)&value)) { - if (value && (void*)value != (void*)priv) - log_debug(" \"%s\" -> \"%s\"", key, value); - else - log_debug(" \"%s\"", key); - } -} - -static int -table_static_update(struct table *table) -{ - if (table_static_config(table) == 1) { - log_info("info: Table \"%s\" successfully updated", table->t_name); - return 1; - } - - log_info("info: Failed to update table \"%s\"", table->t_name); - return 0; -} - -static int -table_static_open(struct table *table) -{ - if (table->t_handle == NULL) - return table_static_config(table); - return 1; -} - -static void -table_static_close(struct table *table) -{ - struct table_static_priv *priv = table->t_handle; - - if (priv) - table_static_priv_free(priv); - table->t_handle = NULL; -} - -static int -table_static_lookup(struct table *table, enum table_service service, const char *key, - char **dst) -{ - struct table_static_priv *priv = table->t_handle; - char *line; - int ret; - int (*match)(const char *, const char *) = NULL; - size_t i; - void *iter; - const char *k; - char *v; - - for (i = 0; i < nitems(keycmp); ++i) - if (keycmp[i].service == service) - match = keycmp[i].func; - - line = NULL; - iter = NULL; - ret = 0; - while (dict_iter(&priv->dict, &iter, &k, (void **)&v)) { - if (match) { - if (match(key, k)) { - line = v; - ret = 1; - } - } - else { - if (strcmp(key, k) == 0) { - line = v; - ret = 1; - } - } - if (ret) - break; - } - - if (dst == NULL) - return ret ? 1 : 0; - - if (ret == 0) - return 0; - - *dst = strdup(line); - if (*dst == NULL) - return -1; - - return 1; -} - -static int -table_static_fetch(struct table *t, enum table_service service, char **dst) -{ - struct table_static_priv *priv = t->t_handle; - const char *k; - - if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) { - priv->iter = NULL; - if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) - return 0; - } - - *dst = strdup(k); - if (*dst == NULL) - return -1; - - return 1; -} diff --git a/smtpd/to.c b/smtpd/to.c deleted file mode 100644 index 81a1bb54..00000000 --- a/smtpd/to.c +++ /dev/null @@ -1,880 +0,0 @@ -/* $OpenBSD: to.c,v 1.44 2019/11/12 20:21:46 gilles Exp $ */ - -/* - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * Copyright (c) 2012 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -static const char *in6addr_to_text(const struct in6_addr *); -static int alias_is_filter(struct expandnode *, const char *, size_t); -static int alias_is_username(struct expandnode *, const char *, size_t); -static int alias_is_address(struct expandnode *, const char *, size_t); -static int alias_is_filename(struct expandnode *, const char *, size_t); -static int alias_is_include(struct expandnode *, const char *, size_t); -static int alias_is_error(struct expandnode *, const char *, size_t); - -static int broken_inet_net_pton_ipv6(const char *, void *, size_t); - -const char * -sockaddr_to_text(struct sockaddr *sa) -{ - static char buf[NI_MAXHOST]; - - if (getnameinfo(sa, SA_LEN(sa), buf, sizeof(buf), NULL, 0, - NI_NUMERICHOST)) - return ("(unknown)"); - else - return (buf); -} - -static const char * -in6addr_to_text(const struct in6_addr *addr) -{ - struct sockaddr_in6 sa_in6; - uint16_t tmp16; - - memset(&sa_in6, 0, sizeof(sa_in6)); -#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN - sa_in6.sin6_len = sizeof(sa_in6); -#endif - sa_in6.sin6_family = AF_INET6; - memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); - - /* XXX thanks, KAME, for this ugliness... adopted from route/show.c */ - if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) || - IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) { - memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16)); - sa_in6.sin6_scope_id = ntohs(tmp16); - sa_in6.sin6_addr.s6_addr[2] = 0; - sa_in6.sin6_addr.s6_addr[3] = 0; - } - - return (sockaddr_to_text((struct sockaddr *)&sa_in6)); -} - -int -text_to_mailaddr(struct mailaddr *maddr, const char *email) -{ - char *username; - char *hostname; - char buffer[LINE_MAX]; - - if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer) - return 0; - - memset(maddr, 0, sizeof *maddr); - - username = buffer; - hostname = strrchr(username, '@'); - - if (hostname == NULL) { - if (strlcpy(maddr->user, username, sizeof maddr->user) - >= sizeof maddr->user) - return 0; - } - else if (username == hostname) { - *hostname++ = '\0'; - if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) - >= sizeof maddr->domain) - return 0; - } - else { - *hostname++ = '\0'; - if (strlcpy(maddr->user, username, sizeof maddr->user) - >= sizeof maddr->user) - return 0; - if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) - >= sizeof maddr->domain) - return 0; - } - - return 1; -} - -const char * -mailaddr_to_text(const struct mailaddr *maddr) -{ - static char buffer[LINE_MAX]; - - (void)strlcpy(buffer, maddr->user, sizeof buffer); - (void)strlcat(buffer, "@", sizeof buffer); - if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer) - return NULL; - - return buffer; -} - - -const char * -sa_to_text(const struct sockaddr *sa) -{ - static char buf[NI_MAXHOST + 5]; - char *p; - - buf[0] = '\0'; - p = buf; - - if (sa->sa_family == AF_LOCAL) - (void)strlcpy(buf, "local", sizeof buf); - else if (sa->sa_family == AF_INET) { - in_addr_t addr; - - addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr; - addr = ntohl(addr); - (void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d", - (addr >> 24) & 0xff, (addr >> 16) & 0xff, - (addr >> 8) & 0xff, addr & 0xff); - } - else if (sa->sa_family == AF_INET6) { - const struct sockaddr_in6 *in6; - const struct in6_addr *in6_addr; - - in6 = (const struct sockaddr_in6 *)sa; - p = buf; - in6_addr = &in6->sin6_addr; - (void)bsnprintf(p, NI_MAXHOST, "[%s]", in6addr_to_text(in6_addr)); - } - - return (buf); -} - -const char * -ss_to_text(const struct sockaddr_storage *ss) -{ - return (sa_to_text((const struct sockaddr*)ss)); -} - -const char * -time_to_text(time_t when) -{ - struct tm *lt; - static char buf[40]; - char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; - char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", - "Jul","Aug","Sep","Oct","Nov","Dec"}; - const char *tz; - long offset; - - lt = localtime(&when); - if (lt == NULL || when == 0) - fatalx("time_to_text: localtime"); - -#if HAVE_STRUCT_TM_TM_GMTOFF - offset = lt->tm_gmtoff; - tz = lt->tm_zone; -#elif defined HAVE_DECL_ALTZONE && defined HAVE_DECL_TIMEZONE - offset = lt->tm_isdst > 0 ? altzone : timezone; - tz = lt->tm_isdst > 0 ? tzname[1] : tzname[0]; -#endif - - /* We do not use strftime because it is subject to locale substitution*/ - if (!bsnprintf(buf, sizeof(buf), - "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", - day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], - lt->tm_year + 1900, - lt->tm_hour, lt->tm_min, lt->tm_sec, - offset >= 0 ? '+' : '-', - abs((int)offset / 3600), - abs((int)offset % 3600) / 60, - tz)) - fatalx("time_to_text: bsnprintf"); - - return buf; -} - -const char * -duration_to_text(time_t t) -{ - static char dst[64]; - char buf[64]; - int h, m, s; - long long d; - - if (t == 0) { - (void)strlcpy(dst, "0s", sizeof dst); - return (dst); - } - - dst[0] = '\0'; - if (t < 0) { - (void)strlcpy(dst, "-", sizeof dst); - t = -t; - } - - s = t % 60; - t /= 60; - m = t % 60; - t /= 60; - h = t % 24; - d = t / 24; - - if (d) { - (void)snprintf(buf, sizeof buf, "%lldd", d); - (void)strlcat(dst, buf, sizeof dst); - } - if (h) { - (void)snprintf(buf, sizeof buf, "%dh", h); - (void)strlcat(dst, buf, sizeof dst); - } - if (m) { - (void)snprintf(buf, sizeof buf, "%dm", m); - (void)strlcat(dst, buf, sizeof dst); - } - if (s) { - (void)snprintf(buf, sizeof buf, "%ds", s); - (void)strlcat(dst, buf, sizeof dst); - } - - return (dst); -} - -int -text_to_netaddr(struct netaddr *netaddr, const char *s) -{ - struct sockaddr_storage ss; - struct sockaddr_in ssin; - struct sockaddr_in6 ssin6; - int bits; - char buf[NI_MAXHOST]; - size_t len; - - memset(&ssin, 0, sizeof(struct sockaddr_in)); - memset(&ssin6, 0, sizeof(struct sockaddr_in6)); - - if (strncasecmp("IPv6:", s, 5) == 0) - s += 5; - - bits = inet_net_pton(AF_INET, s, &ssin.sin_addr, - sizeof(struct in_addr)); - if (bits != -1) { - ssin.sin_family = AF_INET; - memcpy(&ss, &ssin, sizeof(ssin)); -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN - ss.ss_len = sizeof(struct sockaddr_in); -#endif - } else { - if (s[0] != '[') { - if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) - return 0; - } - else { - s++; - if (strncasecmp("IPv6:", s, 5) == 0) - s += 5; - if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) - return 0; - if (buf[len-1] != ']') - return 0; - buf[len-1] = 0; - } - bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr, - sizeof(struct in6_addr)); - if (bits == -1) { - if (errno != EAFNOSUPPORT) - return 0; - bits = broken_inet_net_pton_ipv6(buf, &ssin6.sin6_addr, - sizeof(struct in6_addr)); - if (bits == -1) - return 0; - } - ssin6.sin6_family = AF_INET6; - memcpy(&ss, &ssin6, sizeof(ssin6)); -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN - ss.ss_len = sizeof(struct sockaddr_in6); -#endif - } - - netaddr->ss = ss; - netaddr->bits = bits; - return 1; -} - -int -text_to_relayhost(struct relayhost *relay, const char *s) -{ - static const struct schema { - const char *name; - int tls; - uint16_t flags; - uint16_t port; - } schemas [] = { - /* - * new schemas should be *appended* otherwise the default - * schema index needs to be updated later in this function. - */ - { "smtp://", RELAY_TLS_OPPORTUNISTIC, 0, 25 }, - { "smtp+tls://", RELAY_TLS_STARTTLS, 0, 25 }, - { "smtp+notls://", RELAY_TLS_NO, 0, 25 }, - /* need to specify an explicit port for LMTP */ - { "lmtp://", RELAY_TLS_NO, RELAY_LMTP, 0 }, - { "smtps://", RELAY_TLS_SMTPS, 0, 465 } - }; - const char *errstr = NULL; - char *p, *q; - char buffer[1024]; - char *beg, *end; - size_t i; - size_t len; - - memset(buffer, 0, sizeof buffer); - if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) - return 0; - - for (i = 0; i < nitems(schemas); ++i) - if (strncasecmp(schemas[i].name, s, - strlen(schemas[i].name)) == 0) - break; - - if (i == nitems(schemas)) { - /* there is a schema, but it's not recognized */ - if (strstr(buffer, "://")) - return 0; - - /* no schema, default to smtp:// */ - i = 0; - p = buffer; - } - else - p = buffer + strlen(schemas[i].name); - - relay->tls = schemas[i].tls; - relay->flags = schemas[i].flags; - relay->port = schemas[i].port; - - /* first, we extract the label if any */ - if ((q = strchr(p, '@')) != NULL) { - *q = 0; - if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel)) - >= sizeof (relay->authlabel)) - return 0; - p = q + 1; - } - - /* then, we extract the mail exchanger */ - beg = end = p; - if (*beg == '[') { - if ((end = strchr(beg, ']')) == NULL) - return 0; - /* skip ']', it has to be included in the relay hostname */ - ++end; - len = end - beg; - } - else { - for (end = beg; *end; ++end) - if (!isalnum((unsigned char)*end) && - *end != '_' && *end != '.' && *end != '-') - break; - len = end - beg; - } - if (len >= sizeof relay->hostname) - return 0; - for (i = 0; i < len; ++i) - relay->hostname[i] = beg[i]; - relay->hostname[i] = 0; - - /* finally, we extract the port */ - p = beg + len; - if (*p == ':') { - relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr); - if (errstr) - return 0; - } - - if (!valid_domainpart(relay->hostname)) - return 0; - if ((relay->flags & RELAY_LMTP) && (relay->port == 0)) - return 0; - if (relay->authlabel[0]) { - /* disallow auth on non-tls scheme. */ - if (relay->tls != RELAY_TLS_STARTTLS && - relay->tls != RELAY_TLS_SMTPS) - return 0; - relay->flags |= RELAY_AUTH; - } - - return 1; -} - -uint64_t -text_to_evpid(const char *s) -{ - uint64_t ulval; - char *ep; - - errno = 0; - ulval = strtoull(s, &ep, 16); - if (s[0] == '\0' || *ep != '\0') - return 0; - if (errno == ERANGE && ulval == ULLONG_MAX) - return 0; - if (ulval == 0) - return 0; - return (ulval); -} - -uint32_t -text_to_msgid(const char *s) -{ - uint64_t ulval; - char *ep; - - errno = 0; - ulval = strtoull(s, &ep, 16); - if (s[0] == '\0' || *ep != '\0') - return 0; - if (errno == ERANGE && ulval == ULLONG_MAX) - return 0; - if (ulval == 0) - return 0; - if (ulval > 0xffffffff) - return 0; - return (ulval & 0xffffffff); -} - -const char * -rule_to_text(struct rule *r) -{ - static char buf[4096]; - - memset(buf, 0, sizeof buf); - (void)strlcpy(buf, "match", sizeof buf); - if (r->flag_tag) { - if (r->flag_tag < 0) - (void)strlcat(buf, " !", sizeof buf); - (void)strlcat(buf, " tag ", sizeof buf); - (void)strlcat(buf, r->table_tag, sizeof buf); - } - - if (r->flag_from) { - if (r->flag_from < 0) - (void)strlcat(buf, " !", sizeof buf); - if (r->flag_from_socket) - (void)strlcat(buf, " from socket", sizeof buf); - else if (r->flag_from_rdns) { - (void)strlcat(buf, " from rdns", sizeof buf); - if (r->table_from) { - (void)strlcat(buf, " ", sizeof buf); - (void)strlcat(buf, r->table_from, sizeof buf); - } - } - else if (strcmp(r->table_from, "") == 0) - (void)strlcat(buf, " from any", sizeof buf); - else if (strcmp(r->table_from, "") == 0) - (void)strlcat(buf, " from local", sizeof buf); - else { - (void)strlcat(buf, " from src ", sizeof buf); - (void)strlcat(buf, r->table_from, sizeof buf); - } - } - - if (r->flag_for) { - if (r->flag_for < 0) - (void)strlcat(buf, " !", sizeof buf); - if (strcmp(r->table_for, "") == 0) - (void)strlcat(buf, " for any", sizeof buf); - else if (strcmp(r->table_for, "") == 0) - (void)strlcat(buf, " for local", sizeof buf); - else { - (void)strlcat(buf, " for domain ", sizeof buf); - (void)strlcat(buf, r->table_for, sizeof buf); - } - } - - if (r->flag_smtp_helo) { - if (r->flag_smtp_helo < 0) - (void)strlcat(buf, " !", sizeof buf); - (void)strlcat(buf, " helo ", sizeof buf); - (void)strlcat(buf, r->table_smtp_helo, sizeof buf); - } - - if (r->flag_smtp_auth) { - if (r->flag_smtp_auth < 0) - (void)strlcat(buf, " !", sizeof buf); - (void)strlcat(buf, " auth", sizeof buf); - if (r->table_smtp_auth) { - (void)strlcat(buf, " ", sizeof buf); - (void)strlcat(buf, r->table_smtp_auth, sizeof buf); - } - } - - if (r->flag_smtp_starttls) { - if (r->flag_smtp_starttls < 0) - (void)strlcat(buf, " !", sizeof buf); - (void)strlcat(buf, " tls", sizeof buf); - } - - if (r->flag_smtp_mail_from) { - if (r->flag_smtp_mail_from < 0) - (void)strlcat(buf, " !", sizeof buf); - (void)strlcat(buf, " mail-from ", sizeof buf); - (void)strlcat(buf, r->table_smtp_mail_from, sizeof buf); - } - - if (r->flag_smtp_rcpt_to) { - if (r->flag_smtp_rcpt_to < 0) - (void)strlcat(buf, " !", sizeof buf); - (void)strlcat(buf, " rcpt-to ", sizeof buf); - (void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf); - } - (void)strlcat(buf, " action ", sizeof buf); - if (r->reject) - (void)strlcat(buf, "reject", sizeof buf); - else - (void)strlcat(buf, r->dispatcher, sizeof buf); - return buf; -} - - -int -text_to_userinfo(struct userinfo *userinfo, const char *s) -{ - char buf[PATH_MAX]; - char *p; - const char *errstr; - - memset(buf, 0, sizeof buf); - p = buf; - while (*s && *s != ':') - *p++ = *s++; - if (*s++ != ':') - goto error; - - if (strlcpy(userinfo->username, buf, - sizeof userinfo->username) >= sizeof userinfo->username) - goto error; - - memset(buf, 0, sizeof buf); - p = buf; - while (*s && *s != ':') - *p++ = *s++; - if (*s++ != ':') - goto error; - userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr); - if (errstr) - goto error; - - memset(buf, 0, sizeof buf); - p = buf; - while (*s && *s != ':') - *p++ = *s++; - if (*s++ != ':') - goto error; - userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr); - if (errstr) - goto error; - - if (strlcpy(userinfo->directory, s, - sizeof userinfo->directory) >= sizeof userinfo->directory) - goto error; - - return 1; - -error: - return 0; -} - -int -text_to_credentials(struct credentials *creds, const char *s) -{ - char *p; - char buffer[LINE_MAX]; - size_t offset; - - p = strchr(s, ':'); - if (p == NULL) { - creds->username[0] = '\0'; - if (strlcpy(creds->password, s, sizeof creds->password) - >= sizeof creds->password) - return 0; - return 1; - } - - offset = p - s; - - memset(buffer, 0, sizeof buffer); - if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) - return 0; - p = buffer + offset; - *p = '\0'; - - if (strlcpy(creds->username, buffer, sizeof creds->username) - >= sizeof creds->username) - return 0; - if (strlcpy(creds->password, p+1, sizeof creds->password) - >= sizeof creds->password) - return 0; - - return 1; -} - -int -text_to_expandnode(struct expandnode *expandnode, const char *s) -{ - size_t l; - - l = strlen(s); - if (alias_is_error(expandnode, s, l) || - alias_is_include(expandnode, s, l) || - alias_is_filter(expandnode, s, l) || - alias_is_filename(expandnode, s, l) || - alias_is_address(expandnode, s, l) || - alias_is_username(expandnode, s, l)) - return (1); - - return (0); -} - -const char * -expandnode_to_text(struct expandnode *expandnode) -{ - switch (expandnode->type) { - case EXPAND_FILTER: - case EXPAND_FILENAME: - case EXPAND_INCLUDE: - case EXPAND_ERROR: - case EXPAND_USERNAME: - return expandnode->u.user; - case EXPAND_ADDRESS: - return mailaddr_to_text(&expandnode->u.mailaddr); - case EXPAND_INVALID: - break; - } - - return NULL; -} - -/******/ -static int -alias_is_filter(struct expandnode *alias, const char *line, size_t len) -{ - int v = 0; - - if (*line == '"') - v = 1; - if (*(line+v) == '|') { - if (strlcpy(alias->u.buffer, line + v + 1, - sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) - return 0; - if (v) { - v = strlen(alias->u.buffer); - if (v == 0) - return (0); - if (alias->u.buffer[v-1] != '"') - return (0); - alias->u.buffer[v-1] = '\0'; - } - alias->type = EXPAND_FILTER; - return (1); - } - return (0); -} - -static int -alias_is_username(struct expandnode *alias, const char *line, size_t len) -{ - memset(alias, 0, sizeof *alias); - - if (strlcpy(alias->u.user, line, - sizeof(alias->u.user)) >= sizeof(alias->u.user)) - return 0; - - while (*line) { - if (!isalnum((unsigned char)*line) && - *line != '_' && *line != '.' && *line != '-' && *line != '+') - return 0; - ++line; - } - - alias->type = EXPAND_USERNAME; - return 1; -} - -static int -alias_is_address(struct expandnode *alias, const char *line, size_t len) -{ - char *domain; - - memset(alias, 0, sizeof *alias); - - if (len < 3) /* x@y */ - return 0; - - domain = strchr(line, '@'); - if (domain == NULL) - return 0; - - /* @ cannot start or end an address */ - if (domain == line || domain == line + len - 1) - return 0; - - /* scan pre @ for disallowed chars */ - *domain++ = '\0'; - (void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user)); - (void)strlcpy(alias->u.mailaddr.domain, domain, - sizeof(alias->u.mailaddr.domain)); - - while (*line) { - char allowedset[] = "!#$%*/?|^{}`~&'+-=_."; - if (!isalnum((unsigned char)*line) && - strchr(allowedset, *line) == NULL) - return 0; - ++line; - } - - while (*domain) { - char allowedset[] = "-."; - if (!isalnum((unsigned char)*domain) && - strchr(allowedset, *domain) == NULL) - return 0; - ++domain; - } - - alias->type = EXPAND_ADDRESS; - return 1; -} - -static int -alias_is_filename(struct expandnode *alias, const char *line, size_t len) -{ - memset(alias, 0, sizeof *alias); - - if (*line != '/') - return 0; - - if (strlcpy(alias->u.buffer, line, - sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) - return 0; - alias->type = EXPAND_FILENAME; - return 1; -} - -static int -alias_is_include(struct expandnode *alias, const char *line, size_t len) -{ - size_t skip; - - memset(alias, 0, sizeof *alias); - - if (strncasecmp(":include:", line, 9) == 0) - skip = 9; - else if (strncasecmp("include:", line, 8) == 0) - skip = 8; - else - return 0; - - if (!alias_is_filename(alias, line + skip, len - skip)) - return 0; - - alias->type = EXPAND_INCLUDE; - return 1; -} - -static int -alias_is_error(struct expandnode *alias, const char *line, size_t len) -{ - size_t skip; - - memset(alias, 0, sizeof *alias); - - if (strncasecmp(":error:", line, 7) == 0) - skip = 7; - else if (strncasecmp("error:", line, 6) == 0) - skip = 6; - else - return 0; - - if (strlcpy(alias->u.buffer, line + skip, - sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) - return 0; - - if (strlen(alias->u.buffer) < 5) - return 0; - - /* [45][0-9]{2} [a-zA-Z0-9].* */ - if (alias->u.buffer[3] != ' ' || - !isalnum((unsigned char)alias->u.buffer[4]) || - (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') || - !isdigit((unsigned char)alias->u.buffer[1]) || - !isdigit((unsigned char)alias->u.buffer[2])) - return 0; - - alias->type = EXPAND_ERROR; - return 1; -} - -static int -broken_inet_net_pton_ipv6(const char *src, void *dst, size_t size) -{ - int ret; - int bits; - char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")]; - char *sep; - const char *errstr; - - if (strlcpy(buf, src, sizeof buf) >= sizeof buf) { - errno = EMSGSIZE; - return (-1); - } - - sep = strchr(buf, '/'); - if (sep != NULL) - *sep++ = '\0'; - - ret = inet_pton(AF_INET6, buf, dst); - if (ret != 1) - return (-1); - - if (sep == NULL) - return 128; - - bits = strtonum(sep, 0, 128, &errstr); - if (errstr) - return (-1); - - return bits; -} diff --git a/smtpd/tree.c b/smtpd/tree.c deleted file mode 100644 index 1d720a59..00000000 --- a/smtpd/tree.c +++ /dev/null @@ -1,259 +0,0 @@ -/* $OpenBSD: tree.c,v 1.6 2018/12/23 16:06:24 gilles Exp $ */ - -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include - -#include -#include -#include -#include - -#include "tree.h" - -struct treeentry { - SPLAY_ENTRY(treeentry) entry; - uint64_t id; - void *data; -}; - -static int treeentry_cmp(struct treeentry *, struct treeentry *); - -SPLAY_PROTOTYPE(_tree, treeentry, entry, treeentry_cmp); - -int -tree_check(struct tree *t, uint64_t id) -{ - struct treeentry key; - - key.id = id; - return (SPLAY_FIND(_tree, &t->tree, &key) != NULL); -} - -void * -tree_set(struct tree *t, uint64_t id, void *data) -{ - struct treeentry *entry, key; - char *old; - - key.id = id; - if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) { - if ((entry = malloc(sizeof *entry)) == NULL) - err(1, "tree_set: malloc"); - entry->id = id; - SPLAY_INSERT(_tree, &t->tree, entry); - old = NULL; - t->count += 1; - } else - old = entry->data; - - entry->data = data; - - return (old); -} - -void -tree_xset(struct tree *t, uint64_t id, void *data) -{ - struct treeentry *entry; - - if ((entry = malloc(sizeof *entry)) == NULL) - err(1, "tree_xset: malloc"); - entry->id = id; - entry->data = data; - if (SPLAY_INSERT(_tree, &t->tree, entry)) - errx(1, "tree_xset(%p, 0x%016"PRIx64 ")", t, id); - t->count += 1; -} - -void * -tree_get(struct tree *t, uint64_t id) -{ - struct treeentry key, *entry; - - key.id = id; - if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) - return (NULL); - - return (entry->data); -} - -void * -tree_xget(struct tree *t, uint64_t id) -{ - struct treeentry key, *entry; - - key.id = id; - if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) - errx(1, "tree_get(%p, 0x%016"PRIx64 ")", t, id); - - return (entry->data); -} - -void * -tree_pop(struct tree *t, uint64_t id) -{ - struct treeentry key, *entry; - void *data; - - key.id = id; - if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) - return (NULL); - - data = entry->data; - SPLAY_REMOVE(_tree, &t->tree, entry); - free(entry); - t->count -= 1; - - return (data); -} - -void * -tree_xpop(struct tree *t, uint64_t id) -{ - struct treeentry key, *entry; - void *data; - - key.id = id; - if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) - errx(1, "tree_xpop(%p, 0x%016" PRIx64 ")", t, id); - - data = entry->data; - SPLAY_REMOVE(_tree, &t->tree, entry); - free(entry); - t->count -= 1; - - return (data); -} - -int -tree_poproot(struct tree *t, uint64_t *id, void **data) -{ - struct treeentry *entry; - - entry = SPLAY_ROOT(&t->tree); - if (entry == NULL) - return (0); - if (id) - *id = entry->id; - if (data) - *data = entry->data; - SPLAY_REMOVE(_tree, &t->tree, entry); - free(entry); - t->count -= 1; - - return (1); -} - -int -tree_root(struct tree *t, uint64_t *id, void **data) -{ - struct treeentry *entry; - - entry = SPLAY_ROOT(&t->tree); - if (entry == NULL) - return (0); - if (id) - *id = entry->id; - if (data) - *data = entry->data; - return (1); -} - -int -tree_iter(struct tree *t, void **hdl, uint64_t *id, void **data) -{ - struct treeentry *curr = *hdl; - - if (curr == NULL) - curr = SPLAY_MIN(_tree, &t->tree); - else - curr = SPLAY_NEXT(_tree, &t->tree, curr); - - if (curr) { - *hdl = curr; - if (id) - *id = curr->id; - if (data) - *data = curr->data; - return (1); - } - - return (0); -} - -int -tree_iterfrom(struct tree *t, void **hdl, uint64_t k, uint64_t *id, void **data) -{ - struct treeentry *curr = *hdl, key; - - if (curr == NULL) { - if (k == 0) - curr = SPLAY_MIN(_tree, &t->tree); - else { - key.id = k; - curr = SPLAY_FIND(_tree, &t->tree, &key); - if (curr == NULL) { - SPLAY_INSERT(_tree, &t->tree, &key); - curr = SPLAY_NEXT(_tree, &t->tree, &key); - SPLAY_REMOVE(_tree, &t->tree, &key); - } - } - } else - curr = SPLAY_NEXT(_tree, &t->tree, curr); - - if (curr) { - *hdl = curr; - if (id) - *id = curr->id; - if (data) - *data = curr->data; - return (1); - } - - return (0); -} - -void -tree_merge(struct tree *dst, struct tree *src) -{ - struct treeentry *entry; - - while (!SPLAY_EMPTY(&src->tree)) { - entry = SPLAY_ROOT(&src->tree); - SPLAY_REMOVE(_tree, &src->tree, entry); - if (SPLAY_INSERT(_tree, &dst->tree, entry)) - errx(1, "tree_merge: duplicate"); - } - dst->count += src->count; - src->count = 0; -} - -static int -treeentry_cmp(struct treeentry *a, struct treeentry *b) -{ - if (a->id < b->id) - return (-1); - if (a->id > b->id) - return (1); - return (0); -} - -SPLAY_GENERATE(_tree, treeentry, entry, treeentry_cmp); diff --git a/smtpd/tree.h b/smtpd/tree.h deleted file mode 100644 index 3d719f09..00000000 --- a/smtpd/tree.h +++ /dev/null @@ -1,48 +0,0 @@ -/* $OpenBSD: tree.h,v 1.1 2018/12/23 16:06:24 gilles Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot - * Copyright (c) 2011 Gilles Chehade - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _TREE_H_ -#define _TREE_H_ - -SPLAY_HEAD(_tree, treeentry); - -struct tree { - struct _tree tree; - size_t count; -}; - - -/* tree.c */ -#define tree_init(t) do { SPLAY_INIT(&((t)->tree)); (t)->count = 0; } while(0) -#define tree_empty(t) SPLAY_EMPTY(&((t)->tree)) -#define tree_count(t) ((t)->count) -int tree_check(struct tree *, uint64_t); -void *tree_set(struct tree *, uint64_t, void *); -void tree_xset(struct tree *, uint64_t, void *); -void *tree_get(struct tree *, uint64_t); -void *tree_xget(struct tree *, uint64_t); -void *tree_pop(struct tree *, uint64_t); -void *tree_xpop(struct tree *, uint64_t); -int tree_poproot(struct tree *, uint64_t *, void **); -int tree_root(struct tree *, uint64_t *, void **); -int tree_iter(struct tree *, void **, uint64_t *, void **); -int tree_iterfrom(struct tree *, void **, uint64_t, uint64_t *, void **); -void tree_merge(struct tree *, struct tree *); - -#endif diff --git a/smtpd/unpack_dns.c b/smtpd/unpack_dns.c deleted file mode 100644 index 974d5727..00000000 --- a/smtpd/unpack_dns.c +++ /dev/null @@ -1,300 +0,0 @@ -/* $OpenBSD: unpack_dns.c,v 1.1 2018/01/06 07:57:53 sunil Exp $ */ - -/* - * Copyright (c) 2011-2014 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#ifdef HAVE_ARPA_NAMESER_COMPAT_H -#include -#endif -#include - -#include - -#include "unpack_dns.h" - -static int unpack_data(struct unpack *, void *, size_t); -static int unpack_u16(struct unpack *, uint16_t *); -static int unpack_u32(struct unpack *, uint32_t *); -static int unpack_inaddr(struct unpack *, struct in_addr *); -static int unpack_in6addr(struct unpack *, struct in6_addr *); -static int unpack_dname(struct unpack *, char *, size_t); - -void -unpack_init(struct unpack *unpack, const char *buf, size_t len) -{ - unpack->buf = buf; - unpack->len = len; - unpack->offset = 0; - unpack->err = NULL; -} - -int -unpack_header(struct unpack *p, struct dns_header *h) -{ - if (unpack_data(p, h, HFIXEDSZ) == -1) - return (-1); - - h->flags = ntohs(h->flags); - h->qdcount = ntohs(h->qdcount); - h->ancount = ntohs(h->ancount); - h->nscount = ntohs(h->nscount); - h->arcount = ntohs(h->arcount); - - return (0); -} - -int -unpack_query(struct unpack *p, struct dns_query *q) -{ - unpack_dname(p, q->q_dname, sizeof(q->q_dname)); - unpack_u16(p, &q->q_type); - unpack_u16(p, &q->q_class); - - return (p->err) ? (-1) : (0); -} - -int -unpack_rr(struct unpack *p, struct dns_rr *rr) -{ - uint16_t rdlen; - size_t save_offset; - - unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); - unpack_u16(p, &rr->rr_type); - unpack_u16(p, &rr->rr_class); - unpack_u32(p, &rr->rr_ttl); - unpack_u16(p, &rdlen); - - if (p->err) - return (-1); - - if (p->len - p->offset < rdlen) { - p->err = "too short"; - return (-1); - } - - save_offset = p->offset; - - switch (rr->rr_type) { - - case T_CNAME: - unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname)); - break; - - case T_MX: - unpack_u16(p, &rr->rr.mx.preference); - unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange)); - break; - - case T_NS: - unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname)); - break; - - case T_PTR: - unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname)); - break; - - case T_SOA: - unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname)); - unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname)); - unpack_u32(p, &rr->rr.soa.serial); - unpack_u32(p, &rr->rr.soa.refresh); - unpack_u32(p, &rr->rr.soa.retry); - unpack_u32(p, &rr->rr.soa.expire); - unpack_u32(p, &rr->rr.soa.minimum); - break; - - case T_A: - if (rr->rr_class != C_IN) - goto other; - unpack_inaddr(p, &rr->rr.in_a.addr); - break; - - case T_AAAA: - if (rr->rr_class != C_IN) - goto other; - unpack_in6addr(p, &rr->rr.in_aaaa.addr6); - break; - default: - other: - rr->rr.other.rdata = p->buf + p->offset; - rr->rr.other.rdlen = rdlen; - p->offset += rdlen; - } - - if (p->err) - return (-1); - - /* make sure that the advertised rdlen is really ok */ - if (p->offset - save_offset != rdlen) - p->err = "bad dlen"; - - return (p->err) ? (-1) : (0); -} - -ssize_t -dname_expand(const unsigned char *data, size_t len, size_t offset, - size_t *newoffset, char *dst, size_t max) -{ - size_t n, count, end, ptr, start; - ssize_t res; - - if (offset >= len) - return (-1); - - res = 0; - end = start = offset; - - for (; (n = data[offset]); ) { - if ((n & 0xc0) == 0xc0) { - if (offset + 2 > len) - return (-1); - ptr = 256 * (n & ~0xc0) + data[offset + 1]; - if (ptr >= start) - return (-1); - if (end < offset + 2) - end = offset + 2; - offset = start = ptr; - continue; - } - if (offset + n + 1 > len) - return (-1); - - /* copy n + at offset+1 */ - if (dst != NULL && max != 0) { - count = (max < n + 1) ? (max) : (n + 1); - memmove(dst, data + offset, count); - dst += count; - max -= count; - } - res += n + 1; - offset += n + 1; - if (end < offset) - end = offset; - } - if (end < offset + 1) - end = offset + 1; - - if (dst != NULL && max != 0) - dst[0] = 0; - if (newoffset) - *newoffset = end; - return (res + 1); -} - -char * -print_dname(const char *_dname, char *buf, size_t max) -{ - const unsigned char *dname = _dname; - char *res; - size_t left, n, count; - - if (_dname[0] == 0) { - (void)strlcpy(buf, ".", max); - return buf; - } - - res = buf; - left = max - 1; - for (n = 0; dname[0] && left; n += dname[0]) { - count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); - memmove(buf, dname + 1, count); - dname += dname[0] + 1; - left -= count; - buf += count; - if (left) { - left -= 1; - *buf++ = '.'; - } - } - buf[0] = 0; - - return (res); -} - -static int -unpack_data(struct unpack *p, void *data, size_t len) -{ - if (p->err) - return (-1); - - if (p->len - p->offset < len) { - p->err = "too short"; - return (-1); - } - - memmove(data, p->buf + p->offset, len); - p->offset += len; - - return (0); -} - -static int -unpack_u16(struct unpack *p, uint16_t *u16) -{ - if (unpack_data(p, u16, 2) == -1) - return (-1); - - *u16 = ntohs(*u16); - - return (0); -} - -static int -unpack_u32(struct unpack *p, uint32_t *u32) -{ - if (unpack_data(p, u32, 4) == -1) - return (-1); - - *u32 = ntohl(*u32); - - return (0); -} - -static int -unpack_inaddr(struct unpack *p, struct in_addr *a) -{ - return (unpack_data(p, a, 4)); -} - -static int -unpack_in6addr(struct unpack *p, struct in6_addr *a6) -{ - return (unpack_data(p, a6, 16)); -} - -static int -unpack_dname(struct unpack *p, char *dst, size_t max) -{ - ssize_t e; - - if (p->err) - return (-1); - - e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max); - if (e == -1) { - p->err = "bad domain name"; - return (-1); - } - if (e < 0 || e > MAXDNAME) { - p->err = "domain name too long"; - return (-1); - } - - return (0); -} diff --git a/smtpd/unpack_dns.h b/smtpd/unpack_dns.h deleted file mode 100644 index 2318a0c5..00000000 --- a/smtpd/unpack_dns.h +++ /dev/null @@ -1,96 +0,0 @@ -/* $OpenBSD: unpack_dns.h,v 1.1 2018/01/06 07:57:53 sunil Exp $ */ - -/* - * Copyright (c) 2011-2014 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include - -#include -#include - -struct unpack { - const char *buf; - size_t len; - size_t offset; - const char *err; -}; - -struct dns_header { - uint16_t id; - uint16_t flags; - uint16_t qdcount; - uint16_t ancount; - uint16_t nscount; - uint16_t arcount; -}; - -struct dns_query { - char q_dname[MAXDNAME]; - uint16_t q_type; - uint16_t q_class; -}; - -struct dns_rr { - char rr_dname[MAXDNAME]; - uint16_t rr_type; - uint16_t rr_class; - uint32_t rr_ttl; - union { - struct { - char cname[MAXDNAME]; - } cname; - struct { - uint16_t preference; - char exchange[MAXDNAME]; - } mx; - struct { - char nsname[MAXDNAME]; - } ns; - struct { - char ptrname[MAXDNAME]; - } ptr; - struct { - char mname[MAXDNAME]; - char rname[MAXDNAME]; - uint32_t serial; - uint32_t refresh; - uint32_t retry; - uint32_t expire; - uint32_t minimum; - } soa; - struct { - struct in_addr addr; - } in_a; - struct { - struct in6_addr addr6; - } in_aaaa; - struct { - uint16_t rdlen; - const void *rdata; - } other; - } rr; -}; - -void unpack_init(struct unpack *, const char *, size_t); -int unpack_header(struct unpack *, struct dns_header *); -int unpack_rr(struct unpack *, struct dns_rr *); -int unpack_query(struct unpack *, struct dns_query *); -char *print_dname(const char *, char *, size_t); -ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, - char *, size_t); - diff --git a/smtpd/util.c b/smtpd/util.c deleted file mode 100644 index b2b1458c..00000000 --- a/smtpd/util.c +++ /dev/null @@ -1,870 +0,0 @@ -/* $OpenBSD: util.c,v 1.151 2020/02/24 23:54:28 millert Exp $ */ - -/* - * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2009 Jacek Masiulaniec - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "smtpd.h" -#include "log.h" - -const char *log_in6addr(const struct in6_addr *); -const char *log_sockaddr(struct sockaddr *); -static int parse_mailname_file(char *, size_t); - -int tracing = 0; -int foreground_log = 0; - -void * -xmalloc(size_t size) -{ - void *r; - - if ((r = malloc(size)) == NULL) - fatal("malloc"); - - return (r); -} - -void * -xcalloc(size_t nmemb, size_t size) -{ - void *r; - - if ((r = calloc(nmemb, size)) == NULL) - fatal("calloc"); - - return (r); -} - -char * -xstrdup(const char *str) -{ - char *r; - - if ((r = strdup(str)) == NULL) - fatal("strdup"); - - return (r); -} - -void * -xmemdup(const void *ptr, size_t size) -{ - void *r; - - if ((r = malloc(size)) == NULL) - fatal("malloc"); - - memmove(r, ptr, size); - - return (r); -} - -int -xasprintf(char **ret, const char *format, ...) -{ - int r; - va_list ap; - - va_start(ap, format); - r = vasprintf(ret, format, ap); - va_end(ap); - if (r == -1) - fatal("vasprintf"); - - return (r); -} - - -#if !defined(NO_IO) -int -io_xprintf(struct io *io, const char *fmt, ...) -{ - va_list ap; - int len; - - va_start(ap, fmt); - len = io_vprintf(io, fmt, ap); - va_end(ap); - if (len == -1) - fatal("io_xprintf(%p, %s, ...)", io, fmt); - - return len; -} - -int -io_xprint(struct io *io, const char *str) -{ - int len; - - len = io_print(io, str); - if (len == -1) - fatal("io_xprint(%p, %s, ...)", io, str); - - return len; -} -#endif - -char * -strip(char *s) -{ - size_t l; - - while (isspace((unsigned char)*s)) - s++; - - for (l = strlen(s); l; l--) { - if (!isspace((unsigned char)s[l-1])) - break; - s[l-1] = '\0'; - } - - return (s); -} - -int -bsnprintf(char *str, size_t size, const char *format, ...) -{ - int ret; - va_list ap; - - va_start(ap, format); - ret = vsnprintf(str, size, format, ap); - va_end(ap); - if (ret < 0 || ret >= (int)size) - return 0; - - return 1; -} - - -int -ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create) -{ - char mode_str[12]; - int ret; - struct stat sb; - - if (stat(path, &sb) == -1) { - if (errno != ENOENT || create == 0) { - log_warn("stat: %s", path); - return (0); - } - - /* chmod is deferred to avoid umask effect */ - if (mkdir(path, 0) == -1) { - log_warn("mkdir: %s", path); - return (0); - } - - if (chown(path, owner, group) == -1) { - log_warn("chown: %s", path); - return (0); - } - - if (chmod(path, mode) == -1) { - log_warn("chmod: %s", path); - return (0); - } - - if (stat(path, &sb) == -1) { - log_warn("stat: %s", path); - return (0); - } - } - - ret = 1; - - /* check if it's a directory */ - if (!S_ISDIR(sb.st_mode)) { - ret = 0; - log_warnx("%s is not a directory", path); - } - - /* check that it is owned by owner/group */ - if (sb.st_uid != owner) { - ret = 0; - log_warnx("%s is not owned by uid %d", path, owner); - } - if (sb.st_gid != group) { - ret = 0; - log_warnx("%s is not owned by gid %d", path, group); - } - - /* check permission */ - if ((sb.st_mode & 07777) != mode) { - ret = 0; - strmode(mode, mode_str); - mode_str[10] = '\0'; - log_warnx("%s must be %s (%o)", path, mode_str + 1, mode); - } - - return ret; -} - -int -rmtree(char *path, int keepdir) -{ - char *path_argv[2]; - FTS *fts; - FTSENT *e; - int ret, depth; - - path_argv[0] = path; - path_argv[1] = NULL; - ret = 0; - depth = 0; - - fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); - if (fts == NULL) { - log_warn("fts_open: %s", path); - return (-1); - } - - while ((e = fts_read(fts)) != NULL) { - switch (e->fts_info) { - case FTS_D: - depth++; - break; - case FTS_DP: - case FTS_DNR: - depth--; - if (keepdir && depth == 0) - continue; - if (rmdir(e->fts_path) == -1) { - log_warn("rmdir: %s", e->fts_path); - ret = -1; - } - break; - - case FTS_F: - if (unlink(e->fts_path) == -1) { - log_warn("unlink: %s", e->fts_path); - ret = -1; - } - } - } - - fts_close(fts); - - return (ret); -} - -int -mvpurge(char *from, char *to) -{ - size_t n; - int retry; - const char *sep; - char buf[PATH_MAX]; - - if ((n = strlen(to)) == 0) - fatalx("to is empty"); - - sep = (to[n - 1] == '/') ? "" : "/"; - retry = 0; - -again: - (void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random()); - if (rename(from, buf) == -1) { - /* ENOTDIR has actually 2 meanings, and incorrect input - * could lead to an infinite loop. Consider that after - * 20 tries something is hopelessly wrong. - */ - if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) { - if ((retry++) >= 20) - return (-1); - goto again; - } - return -1; - } - - return 0; -} - - -int -mktmpfile(void) -{ - char path[PATH_MAX]; - int fd; - - if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX", - PATH_TEMPORARY)) { - log_warn("snprintf"); - fatal("exiting"); - } - - if ((fd = mkstemp(path)) == -1) { - log_warn("cannot create temporary file %s", path); - fatal("exiting"); - } - unlink(path); - return (fd); -} - - -/* Close file, signifying temporary error condition (if any) to the caller. */ -int -safe_fclose(FILE *fp) -{ - if (ferror(fp)) { - fclose(fp); - return 0; - } - if (fflush(fp)) { - fclose(fp); - if (errno == ENOSPC) - return 0; - fatal("safe_fclose: fflush"); - } - if (fsync(fileno(fp))) - fatal("safe_fclose: fsync"); - if (fclose(fp)) - fatal("safe_fclose: fclose"); - - return 1; -} - -int -hostname_match(const char *hostname, const char *pattern) -{ - while (*pattern != '\0' && *hostname != '\0') { - if (*pattern == '*') { - while (*pattern == '*') - pattern++; - while (*hostname != '\0' && - tolower((unsigned char)*hostname) != - tolower((unsigned char)*pattern)) - hostname++; - continue; - } - - if (tolower((unsigned char)*pattern) != - tolower((unsigned char)*hostname)) - return 0; - pattern++; - hostname++; - } - - return (*hostname == '\0' && *pattern == '\0'); -} - -int -mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2) -{ - struct mailaddr m1 = *maddr1; - struct mailaddr m2 = *maddr2; - char *p; - - /* catchall */ - if (m2.user[0] == '\0' && m2.domain[0] == '\0') - return 1; - - if (m2.domain[0] && !hostname_match(m1.domain, m2.domain)) - return 0; - - if (m2.user[0]) { - /* if address from table has a tag, we must respect it */ - if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) { - /* otherwise, strip tag from session address if any */ - p = strchr(m1.user, *env->sc_subaddressing_delim); - if (p) - *p = '\0'; - } - if (strcasecmp(m1.user, m2.user)) - return 0; - } - return 1; -} - -int -valid_localpart(const char *s) -{ -#define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c))) -nextatom: - if (!IS_ATEXT(*s) || *s == '\0') - return 0; - while (*(++s) != '\0') { - if (*s == '.') - break; - if (IS_ATEXT(*s)) - continue; - return 0; - } - if (*s == '.') { - s++; - goto nextatom; - } - return 1; -} - -int -valid_domainpart(const char *s) -{ - struct in_addr ina; - struct in6_addr ina6; - char *c, domain[SMTPD_MAXDOMAINPARTSIZE]; - const char *p; - size_t dlen; - - if (*s == '[') { - if (strncasecmp("[IPv6:", s, 6) == 0) - p = s + 6; - else - p = s + 1; - - if (strlcpy(domain, p, sizeof domain) >= sizeof domain) - return 0; - - c = strchr(domain, ']'); - if (!c || c[1] != '\0') - return 0; - - *c = '\0'; - - if (inet_pton(AF_INET6, domain, &ina6) == 1) - return 1; - if (inet_pton(AF_INET, domain, &ina) == 1) - return 1; - - return 0; - } - - if (*s == '\0') - return 0; - - dlen = strlen(s); - if (dlen >= sizeof domain) - return 0; - - if (s[dlen - 1] == '.') - return 0; - - return res_hnok(s); -} - -#define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c))) -#define LABELMAX 63 -#define DNAMEMAX 253 - -int -valid_domainname(const char *str) -{ - const char *label, *s; - - /* - * Expect a sequence of dot-separated labels, possibly with a trailing - * dot. The empty string is rejected, as well a single dot. - */ - for (s = str; *s; s++) { - - /* Start of a new label. */ - label = s; - while (LABELCHR(*s)) - s++; - - /* Must have at least one char and at most LABELMAX. */ - if (s == label || s - label > LABELMAX) - return 0; - - /* If last label, stop here. */ - if (*s == '\0') - break; - - /* Expect a dot as label separator or last char. */ - if (*s != '.') - return 0; - } - - /* Must have at leat one label and no more than DNAMEMAX chars. */ - if (s == str || s - str > DNAMEMAX) - return 0; - - return 1; -} - -int -valid_smtp_response(const char *s) -{ - if (strlen(s) < 5) - return 0; - - if ((s[0] < '2' || s[0] > '5') || - (s[1] < '0' || s[1] > '9') || - (s[2] < '0' || s[2] > '9') || - (s[3] != ' ')) - return 0; - - return 1; -} - -int -secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread) -{ - char buf[PATH_MAX]; - char homedir[PATH_MAX]; - struct stat st; - char *cp; - - if (realpath(path, buf) == NULL) - return 0; - - if (realpath(userdir, homedir) == NULL) - homedir[0] = '\0'; - - /* Check the open file to avoid races. */ - if (fstat(fd, &st) == -1 || - !S_ISREG(st.st_mode) || - st.st_uid != uid || - (st.st_mode & (mayread ? 022 : 066)) != 0) - return 0; - - /* For each component of the canonical path, walking upwards. */ - for (;;) { - if ((cp = dirname(buf)) == NULL) - return 0; - (void)strlcpy(buf, cp, sizeof(buf)); - - if (stat(buf, &st) == -1 || - (st.st_uid != 0 && st.st_uid != uid) || - (st.st_mode & 022) != 0) - return 0; - - /* We can stop checking after reaching homedir level. */ - if (strcmp(homedir, buf) == 0) - break; - - /* - * dirname should always complete with a "/" path, - * but we can be paranoid and check for "." too - */ - if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) - break; - } - - return 1; -} - -void -addargs(arglist *args, char *fmt, ...) -{ - va_list ap; - char *cp; - uint nalloc; - int r; - char **tmp; - - va_start(ap, fmt); - r = vasprintf(&cp, fmt, ap); - va_end(ap); - if (r == -1) - fatal("addargs: argument too long"); - - nalloc = args->nalloc; - if (args->list == NULL) { - nalloc = 32; - args->num = 0; - } else if (args->num+2 >= nalloc) - nalloc *= 2; - - tmp = reallocarray(args->list, nalloc, sizeof(char *)); - if (tmp == NULL) - fatal("addargs: reallocarray"); - args->list = tmp; - args->nalloc = nalloc; - args->list[args->num++] = cp; - args->list[args->num] = NULL; -} - -int -lowercase(char *buf, const char *s, size_t len) -{ - if (len == 0) - return 0; - - if (strlcpy(buf, s, len) >= len) - return 0; - - while (*buf != '\0') { - *buf = tolower((unsigned char)*buf); - buf++; - } - - return 1; -} - -int -uppercase(char *buf, const char *s, size_t len) -{ - if (len == 0) - return 0; - - if (strlcpy(buf, s, len) >= len) - return 0; - - while (*buf != '\0') { - *buf = toupper((unsigned char)*buf); - buf++; - } - - return 1; -} - -void -xlowercase(char *buf, const char *s, size_t len) -{ - if (len == 0) - fatalx("lowercase: len == 0"); - - if (!lowercase(buf, s, len)) - fatalx("lowercase: truncation"); -} - -uint64_t -generate_uid(void) -{ - static uint32_t id; - static uint8_t inited; - uint64_t uid; - - if (!inited) { - id = arc4random(); - inited = 1; - } - while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0) - ; - - return (uid); -} - -int -session_socket_error(int fd) -{ - int error; - socklen_t len; - - len = sizeof(error); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) - fatal("session_socket_error: getsockopt"); - - return (error); -} - -const char * -parse_smtp_response(char *line, size_t len, char **msg, int *cont) -{ - if (len >= LINE_MAX) - return "line too long"; - - if (len > 3) { - if (msg) - *msg = line + 4; - if (cont) - *cont = (line[3] == '-'); - } else if (len == 3) { - if (msg) - *msg = line + 3; - if (cont) - *cont = 0; - } else - return "line too short"; - - /* validate reply code */ - if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) || - !isdigit((unsigned char)line[2])) - return "reply code out of range"; - - return NULL; -} - -static int -parse_mailname_file(char *hostname, size_t len) -{ - FILE *fp; - char *buf = NULL; - size_t bufsz = 0; - ssize_t buflen; - - if ((fp = fopen(MAILNAME_FILE, "r")) == NULL) - return 1; - - if ((buflen = getline(&buf, &bufsz, fp)) == -1) - goto error; - - if (buf[buflen - 1] == '\n') - buf[buflen - 1] = '\0'; - - if (strlcpy(hostname, buf, len) >= len) { - fprintf(stderr, MAILNAME_FILE " entry too long"); - goto error; - } - - return 0; -error: - fclose(fp); - free(buf); - return 1; -} - -int -getmailname(char *hostname, size_t len) -{ - struct addrinfo hints, *res = NULL; - int error; - - /* Try MAILNAME_FILE first */ - if (parse_mailname_file(hostname, len) == 0) - return 0; - - /* Next, gethostname(3) */ - if (gethostname(hostname, len) == -1) { - fprintf(stderr, "getmailname: gethostname() failed\n"); - return -1; - } - - if (strchr(hostname, '.') != NULL) - return 0; - - /* Canonicalize if domain part is missing */ - memset(&hints, 0, sizeof hints); - hints.ai_family = PF_UNSPEC; - hints.ai_flags = AI_CANONNAME; - error = getaddrinfo(hostname, NULL, &hints, &res); - if (error) - return 0; /* Continue with non-canon hostname */ - - if (strlcpy(hostname, res->ai_canonname, len) >= len) { - fprintf(stderr, "hostname too long"); - freeaddrinfo(res); - return -1; - } - - freeaddrinfo(res); - return 0; -} - -int -base64_encode(unsigned char const *src, size_t srclen, - char *dest, size_t destsize) -{ - return __b64_ntop(src, srclen, dest, destsize); -} - -int -base64_decode(char const *src, unsigned char *dest, size_t destsize) -{ - return __b64_pton(src, dest, destsize); -} - -int -base64_encode_rfc3548(unsigned char const *src, size_t srclen, - char *dest, size_t destsize) -{ - size_t i; - int ret; - - if ((ret = base64_encode(src, srclen, dest, destsize)) == -1) - return -1; - - for (i = 0; i < destsize; ++i) { - if (dest[i] == '/') - dest[i] = '_'; - else if (dest[i] == '+') - dest[i] = '-'; - } - - return ret; -} - -void -log_trace(int mask, const char *emsg, ...) -{ - va_list ap; - - if (tracing & mask) { - va_start(ap, emsg); - vlog(LOG_DEBUG, emsg, ap); - va_end(ap); - } -} - -void -log_trace_verbose(int v) -{ - tracing = v; - - /* Set debug logging in log.c */ - log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log); -} - -void -xclosefrom(int lowfd) -{ -#if defined HAVE_CLOSEFROM_INT - if (closefrom(lowfd) == -1) - err(1, "closefrom"); -#else - closefrom(lowfd); -#endif -} - -void -portable_freeaddrinfo(struct addrinfo *ai) -{ - struct addrinfo *p; - - do { - p = ai; - ai = ai->ai_next; - free(p->ai_canonname); - free(p); - } while (ai); -} diff --git a/smtpd/waitq.c b/smtpd/waitq.c deleted file mode 100644 index 082a1e51..00000000 --- a/smtpd/waitq.c +++ /dev/null @@ -1,104 +0,0 @@ -/* $OpenBSD: waitq.c,v 1.6 2018/05/31 21:06:12 gilles Exp $ */ - -/* - * Copyright (c) 2012 Eric Faurot - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "includes.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "smtpd.h" - -struct waiter { - TAILQ_ENTRY(waiter) entry; - void (*cb)(void *, void *, void *); - void *arg; -}; - -struct waitq { - SPLAY_ENTRY(waitq) entry; - void *tag; - TAILQ_HEAD(, waiter) waiters; -}; - -static int waitq_cmp(struct waitq *, struct waitq *); - -SPLAY_HEAD(waitqtree, waitq); -SPLAY_PROTOTYPE(waitqtree, waitq, entry, waitq_cmp); - -static struct waitqtree waitqs = SPLAY_INITIALIZER(&waitqs); - -static int -waitq_cmp(struct waitq *a, struct waitq *b) -{ - if (a->tag < b->tag) - return (-1); - if (a->tag > b->tag) - return (1); - return (0); -} - -SPLAY_GENERATE(waitqtree, waitq, entry, waitq_cmp); - -int -waitq_wait(void *tag, void (*cb)(void *, void *, void *), void *arg) -{ - struct waitq *wq, key; - struct waiter *w; - - key.tag = tag; - wq = SPLAY_FIND(waitqtree, &waitqs, &key); - if (wq == NULL) { - wq = xmalloc(sizeof *wq); - wq->tag = tag; - TAILQ_INIT(&wq->waiters); - SPLAY_INSERT(waitqtree, &waitqs, wq); - } - - w = xmalloc(sizeof *w); - w->cb = cb; - w->arg = arg; - TAILQ_INSERT_TAIL(&wq->waiters, w, entry); - - return (w == TAILQ_FIRST(&wq->waiters)); -} - -void -waitq_run(void *tag, void *result) -{ - struct waitq *wq, key; - struct waiter *w; - - key.tag = tag; - wq = SPLAY_FIND(waitqtree, &waitqs, &key); - SPLAY_REMOVE(waitqtree, &waitqs, wq); - - while ((w = TAILQ_FIRST(&wq->waiters))) { - TAILQ_REMOVE(&wq->waiters, w, entry); - w->cb(tag, w->arg, result); - free(w); - } - free(wq); -} diff --git a/usr.sbin/smtpd/Makefile b/usr.sbin/smtpd/Makefile new file mode 100644 index 00000000..a3dbc9d1 --- /dev/null +++ b/usr.sbin/smtpd/Makefile @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile,v 1.18 2018/05/24 11:38:24 gilles Exp $ + +.include + +SUBDIR = smtpd +SUBDIR+= smtpctl +SUBDIR+= smtp +SUBDIR+= mail + +.include diff --git a/usr.sbin/smtpd/aliases.5 b/usr.sbin/smtpd/aliases.5 new file mode 100644 index 00000000..7c250c81 --- /dev/null +++ b/usr.sbin/smtpd/aliases.5 @@ -0,0 +1,102 @@ +.\" $OpenBSD: aliases.5,v 1.16 2020/04/23 21:28:10 jmc Exp $ +.\" +.\" Copyright (c) 2012 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: April 23 2020 $ +.Dt ALIASES 5 +.Os +.Sh NAME +.Nm aliases +.Nd aliases file for smtpd +.Sh DESCRIPTION +This manual page describes the format of the +.Nm +file, as used by +.Xr smtpd 8 . +An alias in its simplest form is used to assign an arbitrary name +to an email address, or a group of email addresses. +This provides a convenient way to send mail. +For example an alias could refer to all users of a group: +email to that alias would be sent to all members of the group. +Much more complex aliases can be defined however: +an alias can refer to other aliases, +be used to send mail to a file instead of another person, +or to execute various commands. +.Pp +Within the file, +.Ql # +is a comment delimiter; anything placed after it is discarded. +The file consists of key/value mappings of the form: +.Bd -filled -offset indent +key: value1, value2, value3, ... +.Ed +.Pp +.Em key +is always folded to lowercase before alias lookups to ensure that +there can be no ambiguity. +The key is expanded to the corresponding values, +which consist of one or more of the following: +.Bl -tag -width Ds +.It Em user +A user on the host machine. +The user must have a valid entry in the +.Xr passwd 5 +database file. +.It Ar /path/to/file +Append messages to +.Ar file , +specified by its absolute pathname. +.It | Ns Ar command +Pipe the message to +.Ar command +on its standard input. +The command is run under the privileges of the daemon's unprivileged account. +.It : Ns Ar include : Ns Ar /path/to/file +Include any definitions in +.Ar file +as alias entries. +The format of the file is identical to this one. +.It Ar user-part@domain-part +An email address in RFC 5322 format. +If an address extension is appended to the user-part, +it is first compared for an exact match. +It is then stripped so that an address such as user+ext@example.com +will only use the part that precedes +.Sq + +as a +.Em key . +.It Ar error : Ns Ar code message +A status code and message to return. +The code must be 3 digits, +starting 4XX (TempFail) or 5XX (PermFail). +The message must be present and can be freely chosen. +.El +.Sh FILES +.Bl -tag -width "/etc/mail/aliasesXXX" -compact +.It Pa /etc/mail/aliases +Default +.Nm +file. +.El +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr makemap 8 , +.Xr newaliases 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +file format appeared in +.Bx 4.0 . diff --git a/usr.sbin/smtpd/aliases.c b/usr.sbin/smtpd/aliases.c new file mode 100644 index 00000000..0f8a5c1e --- /dev/null +++ b/usr.sbin/smtpd/aliases.c @@ -0,0 +1,234 @@ +/* $OpenBSD: aliases.c,v 1.78 2020/04/28 21:46:43 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UTIL_H +#include +#endif +#ifdef HAVE_LIBUTIL_H +#include +#endif + +#include "smtpd.h" +#include "log.h" + +static int aliases_expand_include(struct expand *, const char *); + +int +aliases_get(struct expand *expand, const char *username) +{ + struct expandnode *xn; + char buf[SMTPD_MAXLOCALPARTSIZE]; + size_t nbaliases; + int ret; + union lookup lk; + struct dispatcher *dsp; + struct table *mapping = NULL; + char *pbuf; + + dsp = dict_xget(env->sc_dispatchers, expand->rule->dispatcher); + mapping = table_find(env, dsp->u.local.table_alias); + + xlowercase(buf, username, sizeof(buf)); + + /* first, check if entry has a user-part tag */ + pbuf = strchr(buf, *env->sc_subaddressing_delim); + if (pbuf) { + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + *pbuf = '\0'; + } + + /* no user-part tag, try looking up user */ + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret <= 0) + return ret; + +expand: + /* foreach node in table_alias expandtree, we merge */ + nbaliases = 0; + RB_FOREACH(xn, expandtree, &lk.expand->tree) { + if (xn->type == EXPAND_INCLUDE) + nbaliases += aliases_expand_include(expand, + xn->u.buffer); + else { + expand_insert(expand, xn); + nbaliases++; + } + } + + expand_free(lk.expand); + + log_debug("debug: aliases_get: returned %zd aliases", nbaliases); + return nbaliases; +} + +int +aliases_virtual_get(struct expand *expand, const struct mailaddr *maddr) +{ + struct expandnode *xn; + union lookup lk; + char buf[LINE_MAX]; + char user[LINE_MAX]; + char tag[LINE_MAX]; + char domain[LINE_MAX]; + char *pbuf; + int nbaliases; + int ret; + struct dispatcher *dsp; + struct table *mapping = NULL; + + dsp = dict_xget(env->sc_dispatchers, expand->rule->dispatcher); + mapping = table_find(env, dsp->u.local.table_virtual); + + if (!bsnprintf(user, sizeof(user), "%s", maddr->user)) + return 0; + if (!bsnprintf(domain, sizeof(domain), "%s", maddr->domain)) + return 0; + xlowercase(user, user, sizeof(user)); + xlowercase(domain, domain, sizeof(domain)); + + memset(tag, '\0', sizeof tag); + pbuf = strchr(user, *env->sc_subaddressing_delim); + if (pbuf) { + if (!bsnprintf(tag, sizeof(tag), "%s", pbuf + 1)) + return 0; + xlowercase(tag, tag, sizeof(tag)); + *pbuf = '\0'; + } + + /* first, check if entry has a user-part tag */ + if (tag[0]) { + if (!bsnprintf(buf, sizeof(buf), "%s%c%s@%s", + user, *env->sc_subaddressing_delim, tag, domain)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + } + + /* then, check if entry exists without user-part tag */ + if (!bsnprintf(buf, sizeof(buf), "%s@%s", user, domain)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + + if (tag[0]) { + /* Failed ? We lookup for username + user-part tag */ + if (!bsnprintf(buf, sizeof(buf), "%s%c%s", + user, *env->sc_subaddressing_delim, tag)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + } + + /* Failed ? We lookup for username only */ + if (!bsnprintf(buf, sizeof(buf), "%s", user)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + + /* Do not try catch-all entries if there is no domain */ + if (domain[0] == '\0') + return 0; + + if (!bsnprintf(buf, sizeof(buf), "@%s", domain)) + return 0; + /* Failed ? We lookup for catch all for virtual domain */ + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + + /* Failed ? We lookup for a *global* catch all */ + ret = table_lookup(mapping, K_ALIAS, "@", &lk); + if (ret <= 0) + return (ret); + +expand: + /* foreach node in table_virtual expand, we merge */ + nbaliases = 0; + RB_FOREACH(xn, expandtree, &lk.expand->tree) { + if (xn->type == EXPAND_INCLUDE) + nbaliases += aliases_expand_include(expand, + xn->u.buffer); + else { + expand_insert(expand, xn); + nbaliases++; + } + } + + expand_free(lk.expand); + + log_debug("debug: aliases_virtual_get: '%s' resolved to %d nodes", + buf, nbaliases); + + return nbaliases; +} + +static int +aliases_expand_include(struct expand *expand, const char *filename) +{ + FILE *fp; + char *line; + size_t len, lineno = 0; + char delim[3] = { '\\', '#', '\0' }; + + fp = fopen(filename, "r"); + if (fp == NULL) { + log_warn("warn: failed to open include file \"%s\".", filename); + return 0; + } + + while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) { + expand_line(expand, line, 0); + free(line); + } + + fclose(fp); + return 1; +} diff --git a/usr.sbin/smtpd/bounce.c b/usr.sbin/smtpd/bounce.c new file mode 100644 index 00000000..4a4a0992 --- /dev/null +++ b/usr.sbin/smtpd/bounce.c @@ -0,0 +1,820 @@ +/* $OpenBSD: bounce.c,v 1.82 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define BOUNCE_MAXRUN 2 +#define BOUNCE_HIWAT 65535 + +enum { + BOUNCE_EHLO, + BOUNCE_MAIL, + BOUNCE_RCPT, + BOUNCE_DATA, + BOUNCE_DATA_NOTICE, + BOUNCE_DATA_MESSAGE, + BOUNCE_DATA_END, + BOUNCE_QUIT, + BOUNCE_CLOSE, +}; + +struct bounce_envelope { + TAILQ_ENTRY(bounce_envelope) entry; + uint64_t id; + struct mailaddr dest; + char *report; + uint8_t esc_class; + uint8_t esc_code; +}; + +struct bounce_message { + SPLAY_ENTRY(bounce_message) sp_entry; + TAILQ_ENTRY(bounce_message) entry; + uint32_t msgid; + struct delivery_bounce bounce; + char *smtpname; + char *to; + time_t timeout; + TAILQ_HEAD(, bounce_envelope) envelopes; +}; + +struct bounce_session { + char *smtpname; + struct bounce_message *msg; + FILE *msgfp; + int state; + struct io *io; + uint64_t boundary; +}; + +SPLAY_HEAD(bounce_message_tree, bounce_message); +static int bounce_message_cmp(const struct bounce_message *, + const struct bounce_message *); +SPLAY_PROTOTYPE(bounce_message_tree, bounce_message, sp_entry, + bounce_message_cmp); + +static void bounce_drain(void); +static void bounce_send(struct bounce_session *, const char *, ...); +static int bounce_next_message(struct bounce_session *); +static int bounce_next(struct bounce_session *); +static void bounce_delivery(struct bounce_message *, int, const char *); +static void bounce_status(struct bounce_session *, const char *, ...); +static void bounce_io(struct io *, int, void *); +static void bounce_timeout(int, short, void *); +static void bounce_free(struct bounce_session *); +static const char *action_str(const struct delivery_bounce *); + +static struct tree wait_fd; +static struct bounce_message_tree messages; +static TAILQ_HEAD(, bounce_message) pending; + +static int nmessage = 0; +static int running = 0; +static struct event ev_timer; + +static void +bounce_init(void) +{ + static int init = 0; + + if (init == 0) { + TAILQ_INIT(&pending); + SPLAY_INIT(&messages); + tree_init(&wait_fd); + evtimer_set(&ev_timer, bounce_timeout, NULL); + init = 1; + } +} + +void +bounce_add(uint64_t evpid) +{ + char buf[LINE_MAX], *line; + struct envelope evp; + struct bounce_message key, *msg; + struct bounce_envelope *be; + + bounce_init(); + + if (queue_envelope_load(evpid, &evp) == 0) { + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + return; + } + + if (evp.type != D_BOUNCE) + errx(1, "bounce: evp:%016" PRIx64 " is not of type D_BOUNCE!", + evp.id); + + key.msgid = evpid_to_msgid(evpid); + key.bounce = evp.agent.bounce; + key.smtpname = evp.smtpname; + + switch (evp.esc_class) { + case ESC_STATUS_OK: + key.bounce.type = B_DELIVERED; + break; + case ESC_STATUS_TEMPFAIL: + key.bounce.type = B_DELAYED; + break; + default: + key.bounce.type = B_FAILED; + } + + key.bounce.dsn_ret = evp.dsn_ret; + key.bounce.ttl = evp.ttl; + msg = SPLAY_FIND(bounce_message_tree, &messages, &key); + if (msg == NULL) { + msg = xcalloc(1, sizeof(*msg)); + msg->msgid = key.msgid; + msg->bounce = key.bounce; + + TAILQ_INIT(&msg->envelopes); + + msg->smtpname = xstrdup(evp.smtpname); + (void)snprintf(buf, sizeof(buf), "%s@%s", evp.sender.user, + evp.sender.domain); + msg->to = xstrdup(buf); + nmessage += 1; + SPLAY_INSERT(bounce_message_tree, &messages, msg); + log_debug("debug: bounce: new message %08" PRIx32, + msg->msgid); + stat_increment("bounce.message", 1); + } else + TAILQ_REMOVE(&pending, msg, entry); + + line = evp.errorline; + if (strlen(line) > 4 && (*line == '1' || *line == '6')) + line += 4; + (void)snprintf(buf, sizeof(buf), "%s@%s: %s", evp.dest.user, + evp.dest.domain, line); + + be = xmalloc(sizeof *be); + be->id = evpid; + be->report = xstrdup(buf); + (void)strlcpy(be->dest.user, evp.dest.user, sizeof(be->dest.user)); + (void)strlcpy(be->dest.domain, evp.dest.domain, + sizeof(be->dest.domain)); + be->esc_class = evp.esc_class; + be->esc_code = evp.esc_code; + TAILQ_INSERT_TAIL(&msg->envelopes, be, entry); + log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, be->report); + + msg->timeout = time(NULL) + 1; + TAILQ_INSERT_TAIL(&pending, msg, entry); + + stat_increment("bounce.envelope", 1); + bounce_drain(); +} + +void +bounce_fd(int fd) +{ + struct bounce_session *s; + struct bounce_message *msg; + + log_debug("debug: bounce: got enqueue socket %d", fd); + + if (fd == -1 || TAILQ_EMPTY(&pending)) { + log_debug("debug: bounce: cancelling"); + if (fd != -1) + close(fd); + running -= 1; + bounce_drain(); + return; + } + + msg = TAILQ_FIRST(&pending); + + s = xcalloc(1, sizeof(*s)); + s->smtpname = xstrdup(msg->smtpname); + s->state = BOUNCE_EHLO; + s->io = io_new(); + io_set_callback(s->io, bounce_io, s); + io_set_fd(s->io, fd); + io_set_timeout(s->io, 30000); + io_set_read(s->io); + s->boundary = generate_uid(); + + log_debug("debug: bounce: new session %p", s); + stat_increment("bounce.session", 1); +} + +static void +bounce_timeout(int fd, short ev, void *arg) +{ + log_debug("debug: bounce: timeout"); + + bounce_drain(); +} + +static void +bounce_drain() +{ + struct bounce_message *msg; + struct timeval tv; + time_t t; + + log_debug("debug: bounce: drain: nmessage=%d running=%d", + nmessage, running); + + while (1) { + if (running >= BOUNCE_MAXRUN) { + log_debug("debug: bounce: max session reached"); + return; + } + + if (nmessage == 0) { + log_debug("debug: bounce: no more messages"); + return; + } + + if (running >= nmessage) { + log_debug("debug: bounce: enough sessions running"); + return; + } + + if ((msg = TAILQ_FIRST(&pending)) == NULL) { + log_debug("debug: bounce: no more pending messages"); + return; + } + + t = time(NULL); + if (msg->timeout > t) { + log_debug("debug: bounce: next message not ready yet"); + if (!evtimer_pending(&ev_timer, NULL)) { + log_debug("debug: bounce: setting timer"); + tv.tv_sec = msg->timeout - t; + tv.tv_usec = 0; + evtimer_add(&ev_timer, &tv); + } + return; + } + + log_debug("debug: bounce: requesting new enqueue socket..."); + m_compose(p_pony, IMSG_QUEUE_SMTP_SESSION, 0, 0, -1, NULL, 0); + + running += 1; + } +} + +static void +bounce_send(struct bounce_session *s, const char *fmt, ...) +{ + va_list ap; + char *p; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&p, fmt, ap)) == -1) + fatal("bounce: vasprintf"); + va_end(ap); + + log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", s, p); + + io_xprintf(s->io, "%s\r\n", p); + + free(p); +} + +static const char * +bounce_duration(long long int d) +{ + static char buf[32]; + + if (d < 60) { + (void)snprintf(buf, sizeof buf, "%lld second%s", d, + (d == 1) ? "" : "s"); + } else if (d < 3600) { + d = d / 60; + (void)snprintf(buf, sizeof buf, "%lld minute%s", d, + (d == 1) ? "" : "s"); + } + else if (d < 3600 * 24) { + d = d / 3600; + (void)snprintf(buf, sizeof buf, "%lld hour%s", d, + (d == 1) ? "" : "s"); + } + else { + d = d / (3600 * 24); + (void)snprintf(buf, sizeof buf, "%lld day%s", d, + (d == 1) ? "" : "s"); + } + return (buf); +} + +#define NOTICE_INTRO \ + " Hi!\r\n\r\n" \ + " This is the MAILER-DAEMON, please DO NOT REPLY to this email.\r\n" + +const char *notice_error = + " An error has occurred while attempting to deliver a message for\r\n" + " the following list of recipients:\r\n\r\n"; + +const char *notice_warning = + " A message is delayed for more than %s for the following\r\n" + " list of recipients:\r\n\r\n"; + +const char *notice_warning2 = + " Please note that this is only a temporary failure report.\r\n" + " The message is kept in the queue for up to %s.\r\n" + " You DO NOT NEED to re-send the message to these recipients.\r\n\r\n"; + +const char *notice_success = + " Your message was successfully delivered to these recipients.\r\n\r\n"; + +const char *notice_relay = + " Your message was relayed to these recipients.\r\n\r\n"; + +static int +bounce_next_message(struct bounce_session *s) +{ + struct bounce_message *msg; + char buf[LINE_MAX]; + int fd; + time_t now; + + again: + + now = time(NULL); + + TAILQ_FOREACH(msg, &pending, entry) { + if (msg->timeout > now) + continue; + if (strcmp(msg->smtpname, s->smtpname)) + continue; + break; + } + if (msg == NULL) + return (0); + + TAILQ_REMOVE(&pending, msg, entry); + SPLAY_REMOVE(bounce_message_tree, &messages, msg); + + if ((fd = queue_message_fd_r(msg->msgid)) == -1) { + bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, + "Could not open message fd"); + goto again; + } + + if ((s->msgfp = fdopen(fd, "r")) == NULL) { + (void)snprintf(buf, sizeof(buf), "fdopen: %s", strerror(errno)); + log_warn("warn: bounce: fdopen"); + close(fd); + bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, buf); + goto again; + } + + s->msg = msg; + return (1); +} + +static int +bounce_next(struct bounce_session *s) +{ + struct bounce_envelope *evp; + char *line = NULL; + size_t n, sz = 0; + ssize_t len; + + switch (s->state) { + case BOUNCE_EHLO: + bounce_send(s, "EHLO %s", s->smtpname); + s->state = BOUNCE_MAIL; + break; + + case BOUNCE_MAIL: + case BOUNCE_DATA_END: + log_debug("debug: bounce: %p: getting next message...", s); + if (bounce_next_message(s) == 0) { + log_debug("debug: bounce: %p: no more messages", s); + bounce_send(s, "QUIT"); + s->state = BOUNCE_CLOSE; + break; + } + log_debug("debug: bounce: %p: found message %08"PRIx32, + s, s->msg->msgid); + bounce_send(s, "MAIL FROM: <>"); + s->state = BOUNCE_RCPT; + break; + + case BOUNCE_RCPT: + bounce_send(s, "RCPT TO: <%s>", s->msg->to); + s->state = BOUNCE_DATA; + break; + + case BOUNCE_DATA: + bounce_send(s, "DATA"); + s->state = BOUNCE_DATA_NOTICE; + break; + + case BOUNCE_DATA_NOTICE: + /* Construct an appropriate notice. */ + + io_xprintf(s->io, + "Subject: Delivery status notification: %s\r\n" + "From: Mailer Daemon \r\n" + "To: %s\r\n" + "Date: %s\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: multipart/mixed;" + "boundary=\"%16" PRIu64 "/%s\"\r\n" + "\r\n" + "This is a MIME-encapsulated message.\r\n" + "\r\n", + action_str(&s->msg->bounce), + s->smtpname, + s->msg->to, + time_to_text(time(NULL)), + s->boundary, + s->smtpname); + + io_xprintf(s->io, + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Notification\r\n" + "Content-Type: text/plain; charset=us-ascii\r\n" + "\r\n" + NOTICE_INTRO + "\r\n", + s->boundary, s->smtpname); + + switch (s->msg->bounce.type) { + case B_FAILED: + io_xprint(s->io, notice_error); + break; + case B_DELAYED: + io_xprintf(s->io, notice_warning, + bounce_duration(s->msg->bounce.delay)); + break; + case B_DELIVERED: + io_xprint(s->io, s->msg->bounce.mta_without_dsn ? + notice_relay : notice_success); + break; + default: + log_warn("warn: bounce: unknown bounce_type"); + } + + TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { + io_xprint(s->io, evp->report); + io_xprint(s->io, "\r\n"); + } + io_xprint(s->io, "\r\n"); + + if (s->msg->bounce.type == B_DELAYED) + io_xprintf(s->io, notice_warning2, + bounce_duration(s->msg->bounce.ttl)); + + io_xprintf(s->io, + " Below is a copy of the original message:\r\n" + "\r\n"); + + io_xprintf(s->io, + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Delivery Report\r\n" + "Content-Type: message/delivery-status\r\n" + "\r\n", + s->boundary, s->smtpname); + + io_xprintf(s->io, + "Reporting-MTA: dns; %s\r\n" + "\r\n", + s->smtpname); + + TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { + io_xprintf(s->io, + "Final-Recipient: rfc822; %s@%s\r\n" + "Action: %s\r\n" + "Status: %s\r\n" + "\r\n", + evp->dest.user, + evp->dest.domain, + action_str(&s->msg->bounce), + esc_code(evp->esc_class, evp->esc_code)); + } + + log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]", + s, io_queued(s->io)); + + s->state = BOUNCE_DATA_MESSAGE; + break; + + case BOUNCE_DATA_MESSAGE: + io_xprintf(s->io, + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Message headers\r\n" + "Content-Type: text/rfc822-headers\r\n" + "\r\n", + s->boundary, s->smtpname); + + n = io_queued(s->io); + while (io_queued(s->io) < BOUNCE_HIWAT) { + if ((len = getline(&line, &sz, s->msgfp)) == -1) + break; + if (len == 1 && line[0] == '\n' && /* end of headers */ + s->msg->bounce.type == B_DELIVERED && + s->msg->bounce.dsn_ret == DSN_RETHDRS) { + free(line); + fclose(s->msgfp); + s->msgfp = NULL; + io_xprintf(s->io, + "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, + s->smtpname); + bounce_send(s, "."); + s->state = BOUNCE_DATA_END; + return (0); + } + line[len - 1] = '\0'; + io_xprintf(s->io, "%s%s\r\n", + (len == 2 && line[0] == '.') ? "." : "", line); + } + free(line); + + if (ferror(s->msgfp)) { + fclose(s->msgfp); + s->msgfp = NULL; + bounce_delivery(s->msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, + "Error reading message"); + s->msg = NULL; + return (-1); + } + + io_xprintf(s->io, + "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, s->smtpname); + + log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]", + s, io_queued(s->io) - n); + + if (feof(s->msgfp)) { + fclose(s->msgfp); + s->msgfp = NULL; + bounce_send(s, "."); + s->state = BOUNCE_DATA_END; + } + break; + + case BOUNCE_QUIT: + bounce_send(s, "QUIT"); + s->state = BOUNCE_CLOSE; + break; + + default: + fatalx("bounce: bad state"); + } + + return (0); +} + + +static void +bounce_delivery(struct bounce_message *msg, int delivery, const char *status) +{ + struct bounce_envelope *be; + struct envelope evp; + size_t n; + const char *f; + + n = 0; + while ((be = TAILQ_FIRST(&msg->envelopes))) { + if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) { + if (queue_envelope_load(be->id, &evp) == 0) { + fatalx("could not reload envelope!"); + } + evp.retry++; + evp.lasttry = msg->timeout; + envelope_set_errormsg(&evp, "%s", status); + queue_envelope_update(&evp); + m_create(p_scheduler, delivery, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + } else { + m_create(p_scheduler, delivery, 0, 0, -1); + m_add_evpid(p_scheduler, be->id); + m_close(p_scheduler); + queue_envelope_delete(be->id); + } + TAILQ_REMOVE(&msg->envelopes, be, entry); + free(be->report); + free(be); + n += 1; + } + + + if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) + f = "TempFail"; + else if (delivery == IMSG_QUEUE_DELIVERY_PERMFAIL) + f = "PermFail"; + else + f = NULL; + + if (f) + log_warnx("warn: %s injecting failure report on message %08" + PRIx32 " to <%s> for %zu envelope%s: %s", + f, msg->msgid, msg->to, n, n > 1 ? "s":"", status); + + nmessage -= 1; + stat_decrement("bounce.message", 1); + stat_decrement("bounce.envelope", n); + free(msg->smtpname); + free(msg->to); + free(msg); +} + +static void +bounce_status(struct bounce_session *s, const char *fmt, ...) +{ + va_list ap; + char *status; + int len, delivery; + + /* Ignore if there is no message */ + if (s->msg == NULL) + return; + + va_start(ap, fmt); + if ((len = vasprintf(&status, fmt, ap)) == -1) + fatal("bounce: vasprintf"); + va_end(ap); + + if (*status == '2') + delivery = IMSG_QUEUE_DELIVERY_OK; + else if (*status == '5' || *status == '6') + delivery = IMSG_QUEUE_DELIVERY_PERMFAIL; + else + delivery = IMSG_QUEUE_DELIVERY_TEMPFAIL; + + bounce_delivery(s->msg, delivery, status); + s->msg = NULL; + if (s->msgfp) + fclose(s->msgfp); + + free(status); +} + +static void +bounce_free(struct bounce_session *s) +{ + log_debug("debug: bounce: %p: deleting session", s); + + io_free(s->io); + + free(s->smtpname); + free(s); + + running -= 1; + stat_decrement("bounce.session", 1); + bounce_drain(); +} + +static void +bounce_io(struct io *io, int evt, void *arg) +{ + struct bounce_session *s = arg; + const char *error; + char *line, *msg; + int cont; + size_t len; + + log_trace(TRACE_IO, "bounce: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if (line == NULL && io_datalen(s->io) >= LINE_MAX) { + bounce_status(s, "Input too long"); + bounce_free(s); + return; + } + + if (line == NULL) + break; + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + log_trace(TRACE_BOUNCE, "bounce: %p: <<< %s", s, line); + + if ((error = parse_smtp_response(line, len, &msg, &cont))) { + bounce_status(s, "Bad response: %s", error); + bounce_free(s); + return; + } + if (cont) + goto nextline; + + if (s->state == BOUNCE_CLOSE) { + bounce_free(s); + return; + } + + if (line[0] != '2' && line[0] != '3') { /* fail */ + bounce_status(s, "%s", line); + s->state = BOUNCE_QUIT; + } else if (s->state == BOUNCE_DATA_END) { /* accepted */ + bounce_status(s, "%s", line); + } + + if (bounce_next(s) == -1) { + bounce_free(s); + return; + } + + io_set_write(io); + break; + + case IO_LOWAT: + if (s->state == BOUNCE_DATA_MESSAGE) + if (bounce_next(s) == -1) { + bounce_free(s); + return; + } + if (io_queued(s->io) == 0) + io_set_read(io); + break; + + default: + bounce_status(s, "442 i/o error %d", evt); + bounce_free(s); + break; + } +} + +static int +bounce_message_cmp(const struct bounce_message *a, + const struct bounce_message *b) +{ + int r; + + if (a->msgid < b->msgid) + return (-1); + if (a->msgid > b->msgid) + return (1); + if ((r = strcmp(a->smtpname, b->smtpname))) + return (r); + + return memcmp(&a->bounce, &b->bounce, sizeof (a->bounce)); +} + +static const char * +action_str(const struct delivery_bounce *b) +{ + switch (b->type) { + case B_FAILED: + return ("failed"); + case B_DELAYED: + return ("delayed"); + case B_DELIVERED: + if (b->mta_without_dsn) + return ("relayed"); + + return ("delivered"); + default: + log_warn("warn: bounce: unknown bounce_type"); + return (""); + } +} + +SPLAY_GENERATE(bounce_message_tree, bounce_message, sp_entry, + bounce_message_cmp); diff --git a/usr.sbin/smtpd/ca.c b/usr.sbin/smtpd/ca.c new file mode 100644 index 00000000..a27db87a --- /dev/null +++ b/usr.sbin/smtpd/ca.c @@ -0,0 +1,777 @@ +/* $OpenBSD: ca.c,v 1.36 2019/09/21 07:46:53 semarie Exp $ */ + +/* + * Copyright (c) 2014 Reyk Floeter + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +static int ca_verify_cb(int, X509_STORE_CTX *); + +static int rsae_send_imsg(int, const unsigned char *, unsigned char *, + RSA *, int, unsigned int); +static int rsae_pub_enc(int, const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_pub_dec(int,const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_priv_enc(int, const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_priv_dec(int, const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_mod_exp(BIGNUM *, const BIGNUM *, RSA *, BN_CTX *); +static int rsae_bn_mod_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, BN_CTX *, BN_MONT_CTX *); +static int rsae_init(RSA *); +static int rsae_finish(RSA *); +static int rsae_keygen(RSA *, int, BIGNUM *, BN_GENCB *); + +#if defined(SUPPORT_ECDSA) +static ECDSA_SIG *ecdsae_do_sign(const unsigned char *, int, const BIGNUM *, + const BIGNUM *, EC_KEY *); +static int ecdsae_sign_setup(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); +static int ecdsae_do_verify(const unsigned char *, int, const ECDSA_SIG *, + EC_KEY *); +#endif + +static uint64_t reqid = 0; + +static void +ca_shutdown(void) +{ + log_debug("debug: ca agent exiting"); + _exit(0); +} + +int +ca(void) +{ + struct passwd *pw; + + purge_config(PURGE_LISTENERS|PURGE_TABLES|PURGE_RULES|PURGE_DISPATCHERS); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + if (chroot(PATH_CHROOT) == -1) + fatal("ca: chroot"); + if (chdir("/") == -1) + fatal("ca: chdir(\"/\")"); + + config_process(PROC_CA); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("ca: cannot drop privileges"); + + imsg_callback = ca_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_CONTROL); + config_peer(PROC_PARENT); + config_peer(PROC_PONY); + + /* Ignore them until we get our config */ + mproc_disable(p_pony); + +#if HAVE_PLEDGE + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +void +ca_init(void) +{ + BIO *in = NULL; + EVP_PKEY *pkey = NULL; + struct pki *pki; + const char *k; + void *iter_dict; + + log_debug("debug: init private ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { + if (pki->pki_key == NULL) + continue; + + if ((in = BIO_new_mem_buf(pki->pki_key, + pki->pki_key_len)) == NULL) + fatalx("ca_launch: key"); + + if ((pkey = PEM_read_bio_PrivateKey(in, + NULL, NULL, NULL)) == NULL) + fatalx("ca_launch: PEM"); + BIO_free(in); + + pki->pki_pkey = pkey; + + freezero(pki->pki_key, pki->pki_key_len); + pki->pki_key = NULL; + } +} + +static int +ca_verify_cb(int ok, X509_STORE_CTX *ctx) +{ + switch (X509_STORE_CTX_get_error(ctx)) { + case X509_V_OK: + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + break; + case X509_V_ERR_NO_EXPLICIT_POLICY: + break; + } + return ok; +} + +int +ca_X509_verify(void *certificate, void *chain, const char *CAfile, + const char *CRLfile, const char **errstr) +{ + X509_STORE *store = NULL; + X509_STORE_CTX *xsc = NULL; + int ret = 0; + long error = 0; + + if ((store = X509_STORE_new()) == NULL) + goto end; + + if (!X509_STORE_load_locations(store, CAfile, NULL)) { + log_warn("warn: unable to load CA file %s", CAfile); + goto end; + } + X509_STORE_set_default_paths(store); + + if ((xsc = X509_STORE_CTX_new()) == NULL) + goto end; + + if (X509_STORE_CTX_init(xsc, store, certificate, chain) != 1) + goto end; + + X509_STORE_CTX_set_verify_cb(xsc, ca_verify_cb); + + ret = X509_verify_cert(xsc); + +end: + *errstr = NULL; + if (ret != 1) { + if (xsc) { + error = X509_STORE_CTX_get_error(xsc); + *errstr = X509_verify_cert_error_string(error); + } + else if (ERR_peek_last_error()) + *errstr = ERR_error_string(ERR_peek_last_error(), NULL); + } + + X509_STORE_CTX_free(xsc); + X509_STORE_free(store); + + return ret > 0 ? 1 : 0; +} + +void +ca_imsg(struct mproc *p, struct imsg *imsg) +{ + RSA *rsa = NULL; +#if defined(SUPPORT_ECDSA) + EC_KEY *ecdsa = NULL; +#endif + const void *from = NULL; + unsigned char *to = NULL; + struct msg m; + const char *pkiname; + size_t flen, tlen, padding; +#if defined(SUPPORT_ECDSA) + int buf_len; +#endif + struct pki *pki; + int ret = 0; + uint64_t id; + int v; + + if (imsg == NULL) + ca_shutdown(); + + switch (imsg->hdr.type) { + case IMSG_CONF_START: + return; + case IMSG_CONF_END: + ca_init(); + + /* Start fulfilling requests */ + mproc_enable(p_pony); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CA_RSA_PRIVENC: + case IMSG_CA_RSA_PRIVDEC: + m_msg(&m, imsg); + m_get_id(&m, &id); + m_get_string(&m, &pkiname); + m_get_data(&m, &from, &flen); + m_get_size(&m, &tlen); + m_get_size(&m, &padding); + m_end(&m); + + pki = dict_get(env->sc_pki_dict, pkiname); + if (pki == NULL || pki->pki_pkey == NULL || + (rsa = EVP_PKEY_get1_RSA(pki->pki_pkey)) == NULL) + fatalx("ca_imsg: invalid pki"); + + if ((to = calloc(1, tlen)) == NULL) + fatalx("ca_imsg: calloc"); + + switch (imsg->hdr.type) { + case IMSG_CA_RSA_PRIVENC: + ret = RSA_private_encrypt(flen, from, to, rsa, + padding); + break; + case IMSG_CA_RSA_PRIVDEC: + ret = RSA_private_decrypt(flen, from, to, rsa, + padding); + break; + } + + m_create(p, imsg->hdr.type, 0, 0, -1); + m_add_id(p, id); + m_add_int(p, ret); + if (ret > 0) + m_add_data(p, to, (size_t)ret); + m_close(p); + + free(to); + RSA_free(rsa); + return; + +#if defined(SUPPORT_ECDSA) + case IMSG_CA_ECDSA_SIGN: + m_msg(&m, imsg); + m_get_id(&m, &id); + m_get_string(&m, &pkiname); + m_get_data(&m, &from, &flen); + m_end(&m); + + pki = dict_get(env->sc_pki_dict, pkiname); + if (pki == NULL || pki->pki_pkey == NULL || + (ecdsa = EVP_PKEY_get1_EC_KEY(pki->pki_pkey)) == NULL) + fatalx("ca_imsg: invalid pki"); + + buf_len = ECDSA_size(ecdsa); + if ((to = calloc(1, buf_len)) == NULL) + fatalx("ca_imsg: calloc"); + ret = ECDSA_sign(0, from, flen, to, &buf_len, ecdsa); + m_create(p, imsg->hdr.type, 0, 0, -1); + m_add_id(p, id); + m_add_int(p, ret); + if (ret > 0) + m_add_data(p, to, (size_t)buf_len); + m_close(p); + free(to); + EC_KEY_free(ecdsa); + return; +#endif + } + errx(1, "ca_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +/* + * RSA privsep engine (called from unprivileged processes) + */ + +const RSA_METHOD *rsa_default = NULL; + +static RSA_METHOD *rsae_method = NULL; + +static int +rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding, unsigned int cmd) +{ + int ret = 0; + struct imsgbuf *ibuf; + struct imsg imsg; + int n, done = 0; + const void *toptr; + char *pkiname; + size_t tlen; + struct msg m; + uint64_t id; + + if ((pkiname = RSA_get_ex_data(rsa, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the RSA + * operation in OpenSSL's engine layer. + */ + m_create(p_ca, cmd, 0, 0, -1); + reqid++; + m_add_id(p_ca, reqid); + m_add_string(p_ca, pkiname); + m_add_data(p_ca, (const void *)from, (size_t)flen); + m_add_size(p_ca, (size_t)RSA_size(rsa)); + m_add_size(p_ca, (size_t)padding); + m_flush(p_ca); + + ibuf = &p_ca->imsgbuf; + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + + log_imsg(PROC_PONY, PROC_CA, &imsg); + + switch (imsg.hdr.type) { + case IMSG_CA_RSA_PRIVENC: + case IMSG_CA_RSA_PRIVDEC: + break; + default: + /* Another imsg is queued up in the buffer */ + pony_imsg(p_ca, &imsg); + imsg_free(&imsg); + continue; + } + + m_msg(&m, &imsg); + m_get_id(&m, &id); + if (id != reqid) + fatalx("invalid response id"); + m_get_int(&m, &ret); + if (ret > 0) + m_get_data(&m, &toptr, &tlen); + m_end(&m); + + if (ret > 0) + memcpy(to, toptr, tlen); + done = 1; + + imsg_free(&imsg); + } + } + mproc_event_add(p_ca); + + return (ret); +} + +static int +rsae_pub_enc(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_pub_enc(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_pub_dec(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_pub_dec(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_get_ex_data(rsa, 0) != NULL) + return (rsae_send_imsg(flen, from, to, rsa, padding, + IMSG_CA_RSA_PRIVENC)); + return (RSA_meth_get_priv_enc(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_get_ex_data(rsa, 0) != NULL) + return (rsae_send_imsg(flen, from, to, rsa, padding, + IMSG_CA_RSA_PRIVDEC)); + + return (RSA_meth_get_priv_dec(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_mod_exp(rsa_default)(r0, I, rsa, ctx)); +} + +static int +rsae_bn_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_bn_mod_exp(rsa_default)(r, a, p, m, ctx, m_ctx)); +} + +static int +rsae_init(RSA *rsa) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_meth_get_init(rsa_default) == NULL) + return (1); + return (RSA_meth_get_init(rsa_default)(rsa)); +} + +static int +rsae_finish(RSA *rsa) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_meth_get_finish(rsa_default) == NULL) + return (1); + return (RSA_meth_get_finish(rsa_default)(rsa)); +} + +static int +rsae_keygen(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_keygen(rsa_default)(rsa, bits, e, cb)); +} + + +#if defined(SUPPORT_ECDSA) +/* + * ECDSA privsep engine (called from unprivileged processes) + */ + +const ECDSA_METHOD *ecdsa_default = NULL; + +static ECDSA_METHOD *ecdsae_method = NULL; + +ECDSA_METHOD * +ECDSA_METHOD_new_temporary(const char *name, int); + +ECDSA_METHOD * +ECDSA_METHOD_new_temporary(const char *name, int flags) +{ + ECDSA_METHOD *ecdsa; + + if ((ecdsa = calloc(1, sizeof (*ecdsa))) == NULL) + return NULL; + + if ((ecdsa->name = strdup(name)) == NULL) { + free(ecdsa); + return NULL; + } + + ecdsa->flags = flags; + return ecdsa; +} + +static ECDSA_SIG * +ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + int ret = 0; + struct imsgbuf *ibuf; + struct imsg imsg; + int n, done = 0; + const void *toptr; + char *pkiname; + size_t tlen; + struct msg m; + uint64_t id; + ECDSA_SIG *sig = NULL; + + if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the ECDSA + * operation in OpenSSL's engine layer. + */ + m_create(p_ca, IMSG_CA_ECDSA_SIGN, 0, 0, -1); + reqid++; + m_add_id(p_ca, reqid); + m_add_string(p_ca, pkiname); + m_add_data(p_ca, (const void *)dgst, (size_t)dgst_len); + m_flush(p_ca); + + ibuf = &p_ca->imsgbuf; + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + + log_imsg(PROC_PONY, PROC_CA, &imsg); + + switch (imsg.hdr.type) { + case IMSG_CA_ECDSA_SIGN: + break; + default: + /* Another imsg is queued up in the buffer */ + pony_imsg(p_ca, &imsg); + imsg_free(&imsg); + continue; + } + + m_msg(&m, &imsg); + m_get_id(&m, &id); + if (id != reqid) + fatalx("invalid response id"); + m_get_int(&m, &ret); + if (ret > 0) + m_get_data(&m, &toptr, &tlen); + m_end(&m); + done = 1; + + if (ret > 0) + d2i_ECDSA_SIG(&sig, (const unsigned char **)&toptr, tlen); + imsg_free(&imsg); + } + } + mproc_event_add(p_ca); + + return (sig); +} + +ECDSA_SIG * +ecdsae_do_sign(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (ECDSA_get_ex_data(eckey, 0) != NULL) + return (ecdsae_send_enc_imsg(dgst, dgst_len, inv, rp, eckey)); + return (ecdsa_default->ecdsa_do_sign(dgst, dgst_len, inv, rp, eckey)); +} + +int +ecdsae_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, + BIGNUM **r) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (ecdsa_default->ecdsa_sign_setup(eckey, ctx, kinv, r)); +} + +int +ecdsae_do_verify(const unsigned char *dgst, int dgst_len, + const ECDSA_SIG *sig, EC_KEY *eckey) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (ecdsa_default->ecdsa_do_verify(dgst, dgst_len, sig, eckey)); +} +#endif + +static void +rsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((rsae_method = RSA_meth_new("RSA privsep engine", 0)) == NULL) { + errstr = "RSA_meth_new"; + goto fail; + } + + RSA_meth_set_pub_enc(rsae_method, rsae_pub_enc); + RSA_meth_set_pub_dec(rsae_method, rsae_pub_dec); + RSA_meth_set_priv_enc(rsae_method, rsae_priv_enc); + RSA_meth_set_priv_dec(rsae_method, rsae_priv_dec); + RSA_meth_set_mod_exp(rsae_method, rsae_mod_exp); + RSA_meth_set_bn_mod_exp(rsae_method, rsae_bn_mod_exp); + RSA_meth_set_init(rsae_method, rsae_init); + RSA_meth_set_finish(rsae_method, rsae_finish); + RSA_meth_set_keygen(rsae_method, rsae_keygen); + + if ((e = ENGINE_get_default_RSA()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, RSA_meth_get0_name(rsae_method))) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((rsa_default = RSA_get_default_method()) == NULL) { + errstr = "RSA_get_default_method"; + goto fail; + } + } else if ((rsa_default = ENGINE_get_RSA(e)) == NULL) { + errstr = "ENGINE_get_RSA"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown RSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (RSA_meth_get_mod_exp(rsa_default) == NULL) + RSA_meth_set_mod_exp(rsae_method, NULL); + if (RSA_meth_get_bn_mod_exp(rsa_default) == NULL) + RSA_meth_set_bn_mod_exp(rsae_method, NULL); + if (RSA_meth_get_keygen(rsa_default) == NULL) + RSA_meth_set_keygen(rsae_method, NULL); + RSA_meth_set_flags(rsae_method, + RSA_meth_get_flags(rsa_default) | RSA_METHOD_FLAG_NO_CHECK); + RSA_meth_set0_app_data(rsae_method, + RSA_meth_get0_app_data(rsa_default)); + + if (!ENGINE_set_RSA(e, rsae_method)) { + errstr = "ENGINE_set_RSA"; + goto fail; + } + if (!ENGINE_set_default_RSA(e)) { + errstr = "ENGINE_set_default_RSA"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} + +#if defined(SUPPORT_ECDSA) +static void +ecdsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((ecdsae_method = ECDSA_METHOD_new_temporary("ECDSA privsep engine", 0)) == NULL) { + errstr = "ECDSA_METHOD_new_temporary"; + goto fail; + } + + ecdsae_method->ecdsa_do_sign = ecdsae_do_sign; + ecdsae_method->ecdsa_sign_setup = ecdsae_sign_setup; + ecdsae_method->ecdsa_do_verify = ecdsae_do_verify; + + if ((e = ENGINE_get_default_ECDSA()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, ecdsae_method->name)) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((ecdsa_default = ECDSA_get_default_method()) == NULL) { + errstr = "ECDSA_get_default_method"; + goto fail; + } + } else if ((ecdsa_default = ENGINE_get_ECDSA(e)) == NULL) { + errstr = "ENGINE_get_ECDSA"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown ECDSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (!ENGINE_set_ECDSA(e, ecdsae_method)) { + errstr = "ENGINE_set_ECDSA"; + goto fail; + } + if (!ENGINE_set_default_ECDSA(e)) { + errstr = "ENGINE_set_default_ECDSA"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} +#endif + +void +ca_engine_init(void) +{ + rsa_engine_init(); +#if defined(SUPPORT_ECDSA) + ecdsa_engine_init(); +#endif +} diff --git a/usr.sbin/smtpd/cert.c b/usr.sbin/smtpd/cert.c new file mode 100644 index 00000000..79b1df91 --- /dev/null +++ b/usr.sbin/smtpd/cert.c @@ -0,0 +1,416 @@ +/* $OpenBSD: cert.c,v 1.2 2018/12/11 07:25:57 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "smtpd.h" +#include "ssl.h" + +#define p_cert p_lka + +struct request { + SPLAY_ENTRY(request) entry; + uint32_t id; + void (*cb_get_certificate)(void *, int, const char *, + const void *, size_t); + void (*cb_verify)(void *, int); + void *arg; +}; + +#define MAX_CERTS 16 +#define MAX_CERT_LEN (MAX_IMSGSIZE - (IMSG_HEADER_SIZE + sizeof(size_t))) + +struct session { + SPLAY_ENTRY(session) entry; + uint32_t id; + struct mproc *proc; + char *cert[MAX_CERTS]; + size_t cert_len[MAX_CERTS]; + int cert_count; +}; + +SPLAY_HEAD(cert_reqtree, request); +SPLAY_HEAD(cert_sestree, session); + +static int request_cmp(struct request *, struct request *); +static int session_cmp(struct session *, struct session *); +SPLAY_PROTOTYPE(cert_reqtree, request, entry, request_cmp); +SPLAY_PROTOTYPE(cert_sestree, session, entry, session_cmp); + +static void cert_do_verify(struct session *, const char *, int); +static int cert_X509_verify(struct session *, const char *, const char *); + +static struct cert_reqtree reqs = SPLAY_INITIALIZER(&reqs); +static struct cert_sestree sess = SPLAY_INITIALIZER(&sess); + +int +cert_init(const char *name, int fallback, void (*cb)(void *, int, + const char *, const void *, size_t), void *arg) +{ + struct request *req; + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, CA_FAIL, NULL, NULL, 0); + return 0; + } + while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_get_certificate = cb; + req->arg = arg; + SPLAY_INSERT(cert_reqtree, &reqs, req); + + m_create(p_cert, IMSG_CERT_INIT, req->id, 0, -1); + m_add_string(p_cert, name); + m_add_int(p_cert, fallback); + m_close(p_cert); + + return 1; +} + +int +cert_verify(const void *ssl, const char *name, int fallback, + void (*cb)(void *, int), void *arg) +{ + struct request *req; + X509 *x; + STACK_OF(X509) *xchain; + unsigned char *cert_der[MAX_CERTS]; + int cert_len[MAX_CERTS]; + int i, cert_count, ret; + + x = SSL_get_peer_certificate(ssl); + if (x == NULL) { + cb(arg, CERT_NOCERT); + return 0; + } + + ret = 0; + memset(cert_der, 0, sizeof(cert_der)); + + req = calloc(1, sizeof(*req)); + if (req == NULL) + goto end; + while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_verify = cb; + req->arg = arg; + SPLAY_INSERT(cert_reqtree, &reqs, req); + + cert_count = 1; + if ((xchain = SSL_get_peer_cert_chain(ssl))) { + cert_count += sk_X509_num(xchain); + if (cert_count > MAX_CERTS) { + log_warnx("warn: certificate chain too long"); + goto end; + } + } + + for (i = 0; i < cert_count; ++i) { + if (i != 0) { + if ((x = sk_X509_value(xchain, i - 1)) == NULL) { + log_warnx("warn: failed to retrieve certificate"); + goto end; + } + } + + cert_len[i] = i2d_X509(x, &cert_der[i]); + if (i == 0) + X509_free(x); + + if (cert_len[i] < 0) { + log_warnx("warn: failed to encode certificate"); + goto end; + } + + log_debug("debug: certificate %i: len=%d", i, cert_len[i]); + if (cert_len[i] > (int)MAX_CERT_LEN) { + log_warnx("warn: certificate too long"); + goto end; + } + } + + /* Send the cert chain, one cert at a time */ + for (i = 0; i < cert_count; ++i) { + m_create(p_cert, IMSG_CERT_CERTIFICATE, req->id, 0, -1); + m_add_data(p_cert, cert_der[i], cert_len[i]); + m_close(p_cert); + } + + /* Tell lookup process that it can start verifying, we're done */ + m_create(p_cert, IMSG_CERT_VERIFY, req->id, 0, -1); + m_add_string(p_cert, name); + m_add_int(p_cert, fallback); + m_close(p_cert); + + ret = 1; + + end: + for (i = 0; i < MAX_CERTS; ++i) + free(cert_der[i]); + + if (ret == 0) { + if (req) + SPLAY_REMOVE(cert_reqtree, &reqs, req); + free(req); + cb(arg, CERT_ERROR); + } + + return ret; +} + + +void +cert_dispatch_request(struct mproc *proc, struct imsg *imsg) +{ + struct pki *pki; + struct session key, *s; + const char *name; + const void *data; + size_t datalen; + struct msg m; + uint32_t reqid; + char buf[LINE_MAX]; + int fallback; + + reqid = imsg->hdr.peerid; + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_CERT_INIT: + m_get_string(&m, &name); + m_get_int(&m, &fallback); + m_end(&m); + + xlowercase(buf, name, sizeof(buf)); + log_debug("debug: looking up pki \"%s\"", buf); + pki = dict_get(env->sc_pki_dict, buf); + if (pki == NULL && fallback) + pki = dict_get(env->sc_pki_dict, "*"); + + m_create(proc, IMSG_CERT_INIT, reqid, 0, -1); + if (pki) { + m_add_int(proc, CA_OK); + m_add_string(proc, pki->pki_name); + m_add_data(proc, pki->pki_cert, pki->pki_cert_len); + } else { + m_add_int(proc, CA_FAIL); + m_add_string(proc, NULL); + m_add_data(proc, NULL, 0); + } + m_close(proc); + return; + + case IMSG_CERT_CERTIFICATE: + m_get_data(&m, &data, &datalen); + m_end(&m); + + key.id = reqid; + key.proc = proc; + s = SPLAY_FIND(cert_sestree, &sess, &key); + if (s == NULL) { + s = calloc(1, sizeof(*s)); + s->proc = proc; + s->id = reqid; + SPLAY_INSERT(cert_sestree, &sess, s); + } + + if (s->cert_count == MAX_CERTS) + fatalx("%s: certificate chain too long", __func__); + + s->cert[s->cert_count] = xmemdup(data, datalen); + s->cert_len[s->cert_count] = datalen; + s->cert_count++; + return; + + case IMSG_CERT_VERIFY: + m_get_string(&m, &name); + m_get_int(&m, &fallback); + m_end(&m); + + key.id = reqid; + key.proc = proc; + s = SPLAY_FIND(cert_sestree, &sess, &key); + if (s == NULL) + fatalx("%s: no certificate", __func__); + + SPLAY_REMOVE(cert_sestree, &sess, s); + cert_do_verify(s, name, fallback); + return; + + default: + fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); + } +} + +void +cert_dispatch_result(struct mproc *proc, struct imsg *imsg) +{ + struct request key, *req; + struct msg m; + const void *cert; + const char *name; + size_t cert_len; + int res; + + key.id = imsg->hdr.peerid; + req = SPLAY_FIND(cert_reqtree, &reqs, &key); + if (req == NULL) + fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); + + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_CERT_INIT: + m_get_int(&m, &res); + m_get_string(&m, &name); + m_get_data(&m, &cert, &cert_len); + m_end(&m); + SPLAY_REMOVE(cert_reqtree, &reqs, req); + req->cb_get_certificate(req->arg, res, name, cert, cert_len); + free(req); + break; + + case IMSG_CERT_VERIFY: + m_get_int(&m, &res); + m_end(&m); + SPLAY_REMOVE(cert_reqtree, &reqs, req); + req->cb_verify(req->arg, res); + free(req); + break; + } +} + +static void +cert_do_verify(struct session *s, const char *name, int fallback) +{ + struct ca *ca; + const char *cafile; + int i, res; + + ca = dict_get(env->sc_ca_dict, name); + if (ca == NULL) + if (fallback) + ca = dict_get(env->sc_ca_dict, "*"); + cafile = ca ? ca->ca_cert_file : CA_FILE; + + if (ca == NULL && !fallback) + res = CERT_NOCA; + else if (!cert_X509_verify(s, cafile, NULL)) + res = CERT_INVALID; + else + res = CERT_OK; + + for (i = 0; i < s->cert_count; ++i) + free(s->cert[i]); + + m_create(s->proc, IMSG_CERT_VERIFY, s->id, 0, -1); + m_add_int(s->proc, res); + m_close(s->proc); + + free(s); +} + +static int +cert_X509_verify(struct session *s, const char *CAfile, + const char *CRLfile) +{ + X509 *x509; + X509 *x509_tmp; + STACK_OF(X509) *x509_chain; + const unsigned char *d2i; + int i, ret = 0; + const char *errstr; + + x509 = NULL; + x509_tmp = NULL; + x509_chain = NULL; + + d2i = s->cert[0]; + if (d2i_X509(&x509, &d2i, s->cert_len[0]) == NULL) { + x509 = NULL; + goto end; + } + + if (s->cert_count > 1) { + x509_chain = sk_X509_new_null(); + for (i = 1; i < s->cert_count; ++i) { + d2i = s->cert[i]; + if (d2i_X509(&x509_tmp, &d2i, s->cert_len[i]) == NULL) + goto end; + sk_X509_insert(x509_chain, x509_tmp, i); + x509_tmp = NULL; + } + } + if (!ca_X509_verify(x509, x509_chain, CAfile, NULL, &errstr)) + log_debug("debug: X509 verify: %s", errstr); + else + ret = 1; + +end: + X509_free(x509); + X509_free(x509_tmp); + if (x509_chain) + sk_X509_pop_free(x509_chain, X509_free); + + return ret; +} + +static int +request_cmp(struct request *a, struct request *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return 0; +} + +SPLAY_GENERATE(cert_reqtree, request, entry, request_cmp); + +static int +session_cmp(struct session *a, struct session *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + if (a->proc < b->proc) + return -1; + if (a->proc > b->proc) + return 1; + return 0; +} + +SPLAY_GENERATE(cert_sestree, session, entry, session_cmp); diff --git a/usr.sbin/smtpd/compress_backend.c b/usr.sbin/smtpd/compress_backend.c new file mode 100644 index 00000000..1b974662 --- /dev/null +++ b/usr.sbin/smtpd/compress_backend.c @@ -0,0 +1,72 @@ +/* $OpenBSD: compress_backend.c,v 1.9 2015/01/20 17:37:54 deraadt Exp $ */ + +/* + * Copyright (c) 2012 Charles Longeau + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" + +#define BUFFER_SIZE 16364 + +extern struct compress_backend compress_gzip; + +struct compress_backend * +compress_backend_lookup(const char *name) +{ + if (!strcmp(name, "gzip")) + return &compress_gzip; + + return NULL; +} + +size_t +compress_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + return (env->sc_comp->compress_chunk(ib, ibsz, ob, obsz)); +} + +size_t +uncompress_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + return (env->sc_comp->uncompress_chunk(ib, ibsz, ob, obsz)); +} + +int +compress_file(FILE *ifile, FILE *ofile) +{ + return (env->sc_comp->compress_file(ifile, ofile)); +} + +int +uncompress_file(FILE *ifile, FILE *ofile) +{ + return (env->sc_comp->uncompress_file(ifile, ofile)); +} diff --git a/usr.sbin/smtpd/compress_gzip.c b/usr.sbin/smtpd/compress_gzip.c new file mode 100644 index 00000000..dd60aeec --- /dev/null +++ b/usr.sbin/smtpd/compress_gzip.c @@ -0,0 +1,186 @@ +/* $OpenBSD: compress_gzip.c,v 1.10 2015/12/28 22:08:30 jung Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * Copyright (c) 2012 Charles Longeau + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "smtpd.h" +#include "log.h" + + +#define GZIP_BUFFER_SIZE 16384 + + +static size_t compress_gzip_chunk(void *, size_t, void *, size_t); +static size_t uncompress_gzip_chunk(void *, size_t, void *, size_t); +static int compress_gzip_file(FILE *, FILE *); +static int uncompress_gzip_file(FILE *, FILE *); + + +struct compress_backend compress_gzip = { + compress_gzip_chunk, + uncompress_gzip_chunk, + + compress_gzip_file, + uncompress_gzip_file, +}; + +static size_t +compress_gzip_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + z_stream *strm; + size_t ret = 0; + + if ((strm = calloc(1, sizeof *strm)) == NULL) + return 0; + + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + if (deflateInit2(strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) + goto end; + + strm->avail_in = ibsz; + strm->next_in = (unsigned char *)ib; + strm->avail_out = obsz; + strm->next_out = (unsigned char *)ob; + if (deflate(strm, Z_FINISH) != Z_STREAM_END) + goto end; + + ret = strm->total_out; + +end: + deflateEnd(strm); + free(strm); + return ret; +} + + +static size_t +uncompress_gzip_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + z_stream *strm; + size_t ret = 0; + + if ((strm = calloc(1, sizeof *strm)) == NULL) + return 0; + + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = Z_NULL; + + if (inflateInit2(strm, (15+16)) != Z_OK) + goto end; + + strm->avail_in = ibsz; + strm->next_in = (unsigned char *)ib; + strm->avail_out = obsz; + strm->next_out = (unsigned char *)ob; + + if (inflate(strm, Z_FINISH) != Z_STREAM_END) + goto end; + + ret = strm->total_out; + +end: + deflateEnd(strm); + free(strm); + return ret; +} + + +static int +compress_gzip_file(FILE *in, FILE *out) +{ + gzFile gzf; + char ibuf[GZIP_BUFFER_SIZE]; + int r, w; + int ret = 0; + + if (in == NULL || out == NULL) + return (0); + + gzf = gzdopen(fileno(out), "wb"); + if (gzf == NULL) + return (0); + + while ((r = fread(ibuf, 1, GZIP_BUFFER_SIZE, in)) != 0) { + if ((w = gzwrite(gzf, ibuf, r)) != r) + goto end; + } + if (!feof(in)) + goto end; + + ret = 1; + +end: + gzclose(gzf); + return (ret); +} + + +static int +uncompress_gzip_file(FILE *in, FILE *out) +{ + gzFile gzf; + char obuf[GZIP_BUFFER_SIZE]; + int r, w; + int ret = 0; + + if (in == NULL || out == NULL) + return (0); + + gzf = gzdopen(fileno(in), "r"); + if (gzf == NULL) + return (0); + + while ((r = gzread(gzf, obuf, sizeof(obuf))) > 0) { + if ((w = fwrite(obuf, r, 1, out)) != 1) + goto end; + } + if (!gzeof(gzf)) + goto end; + + ret = 1; + +end: + gzclose(gzf); + return (ret); +} diff --git a/usr.sbin/smtpd/config.c b/usr.sbin/smtpd/config.c new file mode 100644 index 00000000..8fe983d6 --- /dev/null +++ b/usr.sbin/smtpd/config.c @@ -0,0 +1,350 @@ +/* $OpenBSD: config.c,v 1.51 2019/12/18 10:00:39 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +void set_local(struct smtpd *, const char *); +void set_localaddrs(struct smtpd *, struct table *); + +struct smtpd * +config_default(void) +{ + struct smtpd *conf = NULL; + struct mta_limits *limits = NULL; + struct table *t = NULL; + char hostname[HOST_NAME_MAX+1]; + + if (getmailname(hostname, sizeof hostname) == -1) + return NULL; + + if ((conf = calloc(1, sizeof(*conf))) == NULL) + return conf; + + (void)strlcpy(conf->sc_hostname, hostname, sizeof(conf->sc_hostname)); + + conf->sc_maxsize = DEFAULT_MAX_BODY_SIZE; + conf->sc_subaddressing_delim = SUBADDRESSING_DELIMITER; + conf->sc_ttl = SMTPD_QUEUE_EXPIRY; + conf->sc_srs_ttl = SMTPD_QUEUE_EXPIRY / 86400; + + conf->sc_mta_max_deferred = 100; + conf->sc_scheduler_max_inflight = 5000; + conf->sc_scheduler_max_schedule = 10; + conf->sc_scheduler_max_evp_batch_size = 256; + conf->sc_scheduler_max_msg_batch_size = 1024; + + conf->sc_session_max_rcpt = 1000; + conf->sc_session_max_mails = 100; + + conf->sc_mda_max_session = 50; + conf->sc_mda_max_user_session = 7; + conf->sc_mda_task_hiwat = 50; + conf->sc_mda_task_lowat = 30; + conf->sc_mda_task_release = 10; + + /* Report mails delayed for more than 4 hours */ + conf->sc_bounce_warn[0] = 3600 * 4; + + conf->sc_tables_dict = calloc(1, sizeof(*conf->sc_tables_dict)); + conf->sc_rules = calloc(1, sizeof(*conf->sc_rules)); + conf->sc_dispatchers = calloc(1, sizeof(*conf->sc_dispatchers)); + conf->sc_listeners = calloc(1, sizeof(*conf->sc_listeners)); + conf->sc_ca_dict = calloc(1, sizeof(*conf->sc_ca_dict)); + conf->sc_pki_dict = calloc(1, sizeof(*conf->sc_pki_dict)); + conf->sc_ssl_dict = calloc(1, sizeof(*conf->sc_ssl_dict)); + conf->sc_limits_dict = calloc(1, sizeof(*conf->sc_limits_dict)); + conf->sc_mda_wrappers = calloc(1, sizeof(*conf->sc_mda_wrappers)); + conf->sc_filter_processes_dict = calloc(1, sizeof(*conf->sc_filter_processes_dict)); + conf->sc_dispatcher_bounce = calloc(1, sizeof(*conf->sc_dispatcher_bounce)); + conf->sc_filters_dict = calloc(1, sizeof(*conf->sc_filters_dict)); + limits = calloc(1, sizeof(*limits)); + + if (conf->sc_tables_dict == NULL || + conf->sc_rules == NULL || + conf->sc_dispatchers == NULL || + conf->sc_listeners == NULL || + conf->sc_ca_dict == NULL || + conf->sc_pki_dict == NULL || + conf->sc_ssl_dict == NULL || + conf->sc_limits_dict == NULL || + conf->sc_mda_wrappers == NULL || + conf->sc_filter_processes_dict == NULL || + conf->sc_dispatcher_bounce == NULL || + conf->sc_filters_dict == NULL || + limits == NULL) + goto error; + + dict_init(conf->sc_dispatchers); + dict_init(conf->sc_mda_wrappers); + dict_init(conf->sc_ca_dict); + dict_init(conf->sc_pki_dict); + dict_init(conf->sc_ssl_dict); + dict_init(conf->sc_tables_dict); + dict_init(conf->sc_limits_dict); + dict_init(conf->sc_filter_processes_dict); + + limit_mta_set_defaults(limits); + + dict_xset(conf->sc_limits_dict, "default", limits); + + TAILQ_INIT(conf->sc_listeners); + TAILQ_INIT(conf->sc_rules); + + + /* bounce dispatcher */ + conf->sc_dispatcher_bounce->type = DISPATCHER_BOUNCE; + + /* + * declare special "localhost", "anyhost" and "localnames" tables + */ + set_local(conf, conf->sc_hostname); + + t = table_create(conf, "static", "", NULL); + table_add(t, "*", NULL); + + hostname[strcspn(hostname, ".")] = '\0'; + if (strcmp(conf->sc_hostname, hostname) != 0) + table_add(t, hostname, NULL); + + table_create(conf, "getpwnam", "", NULL); + + return conf; + +error: + free(conf->sc_tables_dict); + free(conf->sc_rules); + free(conf->sc_dispatchers); + free(conf->sc_listeners); + free(conf->sc_ca_dict); + free(conf->sc_pki_dict); + free(conf->sc_ssl_dict); + free(conf->sc_limits_dict); + free(conf->sc_mda_wrappers); + free(conf->sc_filter_processes_dict); + free(conf->sc_dispatcher_bounce); + free(conf->sc_filters_dict); + free(limits); + free(conf); + return NULL; +} + +void +set_local(struct smtpd *conf, const char *hostname) +{ + struct table *t; + + t = table_create(conf, "static", "", NULL); + table_add(t, "localhost", NULL); + table_add(t, hostname, NULL); + + set_localaddrs(conf, t); +} + +void +set_localaddrs(struct smtpd *conf, struct table *localnames) +{ + struct ifaddrs *ifap, *p; + struct sockaddr_storage ss; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct table *t; + char buf[NI_MAXHOST + 5]; + + t = table_create(conf, "static", "", NULL); + table_add(t, "local", NULL); + table_add(t, "0.0.0.0/0", NULL); + table_add(t, "::/0", NULL); + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + t = table_create(conf, "static", "", NULL); + table_add(t, "local", NULL); + + for (p = ifap; p != NULL; p = p->ifa_next) { + if (p->ifa_addr == NULL) + continue; + switch (p->ifa_addr->sa_family) { + case AF_INET: + sain = (struct sockaddr_in *)&ss; + *sain = *(struct sockaddr_in *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + table_add(t, ss_to_text(&ss), NULL); + table_add(localnames, ss_to_text(&ss), NULL); + (void)snprintf(buf, sizeof buf, "[%s]", ss_to_text(&ss)); + table_add(localnames, buf, NULL); + break; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&ss; + *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + table_add(t, ss_to_text(&ss), NULL); + table_add(localnames, ss_to_text(&ss), NULL); + (void)snprintf(buf, sizeof buf, "[%s]", ss_to_text(&ss)); + table_add(localnames, buf, NULL); + (void)snprintf(buf, sizeof buf, "[ipv6:%s]", ss_to_text(&ss)); + table_add(localnames, buf, NULL); + break; + } + } + + freeifaddrs(ifap); +} + +void +purge_config(uint8_t what) +{ + struct dispatcher *d; + struct listener *l; + struct table *t; + struct rule *r; + struct pki *p; + const char *k; + void *iter_dict; + + if (what & PURGE_LISTENERS) { + while ((l = TAILQ_FIRST(env->sc_listeners)) != NULL) { + TAILQ_REMOVE(env->sc_listeners, l, entry); + free(l); + } + free(env->sc_listeners); + env->sc_listeners = NULL; + } + if (what & PURGE_TABLES) { + while (dict_root(env->sc_tables_dict, NULL, (void **)&t)) + table_destroy(env, t); + free(env->sc_tables_dict); + env->sc_tables_dict = NULL; + } + if (what & PURGE_RULES) { + while ((r = TAILQ_FIRST(env->sc_rules)) != NULL) { + TAILQ_REMOVE(env->sc_rules, r, r_entry); + free(r); + } + free(env->sc_rules); + env->sc_rules = NULL; + } + if (what & PURGE_DISPATCHERS) { + while (dict_poproot(env->sc_dispatchers, (void **)&d)) { + free(d); + } + free(env->sc_dispatchers); + env->sc_dispatchers = NULL; + } + if (what & PURGE_PKI) { + while (dict_poproot(env->sc_pki_dict, (void **)&p)) { + freezero(p->pki_cert, p->pki_cert_len); + freezero(p->pki_key, p->pki_key_len); + EVP_PKEY_free(p->pki_pkey); + free(p); + } + free(env->sc_pki_dict); + env->sc_pki_dict = NULL; + } else if (what & PURGE_PKI_KEYS) { + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, + (void **)&p)) { + freezero(p->pki_cert, p->pki_cert_len); + p->pki_cert = NULL; + freezero(p->pki_key, p->pki_key_len); + p->pki_key = NULL; + EVP_PKEY_free(p->pki_pkey); + p->pki_pkey = NULL; + } + } +} + +#ifndef CONFIG_MINIMUM + +void +config_process(enum smtp_proc_type proc) +{ + struct rlimit rl; + + smtpd_process = proc; + setproctitle("%s", proc_title(proc)); + + if (getrlimit(RLIMIT_NOFILE, &rl) == -1) + fatal("fdlimit: getrlimit"); + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) + if (errno != EINVAL) + fatal("fdlimit: setrlimit"); +} + +void +config_peer(enum smtp_proc_type proc) +{ + struct mproc *p; + + if (proc == smtpd_process) + fatal("config_peers: cannot peer with oneself"); + + if (proc == PROC_CONTROL) + p = p_control; + else if (proc == PROC_LKA) + p = p_lka; + else if (proc == PROC_PARENT) + p = p_parent; + else if (proc == PROC_QUEUE) + p = p_queue; + else if (proc == PROC_SCHEDULER) + p = p_scheduler; + else if (proc == PROC_PONY) + p = p_pony; + else if (proc == PROC_CA) + p = p_ca; + else + fatalx("bad peer"); + + mproc_enable(p); +} + +#else + +void config_process(enum smtp_proc_type proc) {} +void config_peer(enum smtp_proc_type proc) {} + +#endif diff --git a/usr.sbin/smtpd/control.c b/usr.sbin/smtpd/control.c new file mode 100644 index 00000000..0e35bbd1 --- /dev/null +++ b/usr.sbin/smtpd/control.c @@ -0,0 +1,817 @@ +/* $OpenBSD: control.c,v 1.123 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_conn { + uint32_t id; + uint8_t flags; +#define CTL_CONN_NOTIFY 0x01 + struct mproc mproc; + uid_t euid; + gid_t egid; +}; + +struct { + struct event ev; + int fd; +} control_state; + +static void control_imsg(struct mproc *, struct imsg *); +static void control_shutdown(void); +static void control_listen(void); +static void control_accept(int, short, void *); +static void control_close(struct ctl_conn *); +static void control_dispatch_ext(struct mproc *, struct imsg *); +static void control_digest_update(const char *, size_t, int); +static void control_broadcast_verbose(int, int); + +static struct stat_backend *stat_backend = NULL; +extern const char *backend_stat; + +static uint64_t connid = 0; +static struct tree ctl_conns; +static struct tree ctl_count; +static struct stat_digest digest; + +#define CONTROL_FD_RESERVE 5 +#define CONTROL_MAXCONN_PER_CLIENT 32 + +static void +control_imsg(struct mproc *p, struct imsg *imsg) +{ + struct ctl_conn *c; + struct stat_value val; + struct msg m; + const char *key; + const void *data; + size_t sz; + + if (imsg == NULL) { + if (p->proc != PROC_CLIENT) + control_shutdown(); + return; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_OK: + case IMSG_CTL_FAIL: + case IMSG_CTL_LIST_MESSAGES: + case IMSG_CTL_LIST_ENVELOPES: + case IMSG_CTL_DISCOVER_EVPID: + case IMSG_CTL_DISCOVER_MSGID: + case IMSG_CTL_MTA_SHOW_HOSTS: + case IMSG_CTL_MTA_SHOW_RELAYS: + case IMSG_CTL_MTA_SHOW_ROUTES: + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + case IMSG_CTL_MTA_SHOW_BLOCK: + c = tree_get(&ctl_conns, imsg->hdr.peerid); + if (c == NULL) + return; + imsg->hdr.peerid = 0; + m_forward(&c->mproc, imsg); + return; + + case IMSG_CTL_SMTP_SESSION: + c = tree_get(&ctl_conns, imsg->hdr.peerid); + if (c == NULL) + return; + m_compose(&c->mproc, IMSG_CTL_OK, 0, 0, imsg->fd, NULL, 0); + return; + + case IMSG_STAT_INCREMENT: + m_msg(&m, imsg); + m_get_string(&m, &key); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(val)) + fatalx("control: IMSG_STAT_INCREMENT size mismatch"); + memmove(&val, data, sz); + if (stat_backend) + stat_backend->increment(key, val.u.counter); + control_digest_update(key, val.u.counter, 1); + return; + + case IMSG_STAT_DECREMENT: + m_msg(&m, imsg); + m_get_string(&m, &key); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(val)) + fatalx("control: IMSG_STAT_DECREMENT size mismatch"); + memmove(&val, data, sz); + if (stat_backend) + stat_backend->decrement(key, val.u.counter); + control_digest_update(key, val.u.counter, 0); + return; + + case IMSG_STAT_SET: + m_msg(&m, imsg); + m_get_string(&m, &key); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(val)) + fatalx("control: IMSG_STAT_SET size mismatch"); + memmove(&val, data, sz); + if (stat_backend) + stat_backend->set(key, &val); + return; + } + + errx(1, "control_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); +} + +int +control_create_socket(void) +{ + struct sockaddr_un s_un; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + fatal("control: socket"); + + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + if (strlcpy(s_un.sun_path, SMTPD_SOCKET, + sizeof(s_un.sun_path)) >= sizeof(s_un.sun_path)) + fatal("control: socket name too long"); + + if (connect(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) + fatalx("control socket already listening"); + + if (unlink(SMTPD_SOCKET) == -1) + if (errno != ENOENT) + fatal("control: cannot unlink socket"); + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + (void)umask(old_umask); + fatal("control: bind"); + } + (void)umask(old_umask); + + if (chmod(SMTPD_SOCKET, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) == -1) { + (void)unlink(SMTPD_SOCKET); + fatal("control: chmod"); + } + + io_set_nonblocking(fd); + control_state.fd = fd; + + return fd; +} + +int +control(void) +{ + struct passwd *pw; + + purge_config(PURGE_EVERYTHING); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + stat_backend = env->sc_stat; + stat_backend->init(); + + if (chroot(PATH_CHROOT) == -1) + fatal("control: chroot"); + if (chdir("/") == -1) + fatal("control: chdir(\"/\")"); + + config_process(PROC_CONTROL); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("control: cannot drop privileges"); + + imsg_callback = control_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + tree_init(&ctl_conns); + tree_init(&ctl_count); + + memset(&digest, 0, sizeof digest); + digest.startup = time(NULL); + + config_peer(PROC_SCHEDULER); + config_peer(PROC_QUEUE); + config_peer(PROC_PARENT); + config_peer(PROC_LKA); + config_peer(PROC_PONY); + config_peer(PROC_CA); + + control_listen(); + +#if HAVE_PLEDGE + if (pledge("stdio unix recvfd sendfd", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +control_shutdown(void) +{ + log_debug("debug: control agent exiting"); + _exit(0); +} + +static void +control_listen(void) +{ + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) + fatal("control_listen"); + + event_set(&control_state.ev, control_state.fd, EV_READ|EV_PERSIST, + control_accept, NULL); + event_add(&control_state.ev, NULL); +} + +/* ARGSUSED */ +static void +control_accept(int listenfd, short event, void *arg) +{ + int connfd; + socklen_t len; + struct sockaddr_un s_un; + struct ctl_conn *c; + size_t *count; + uid_t euid; + gid_t egid; + +#if defined(HAVE_GETDTABLESIZE) && defined(HAVE_GETDTABLECOUNT) + if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE) + goto pause; +#else + if (available_fds(CONTROL_FD_RESERVE)) + goto pause; +#endif + + len = sizeof(s_un); + if ((connfd = accept(listenfd, (struct sockaddr *)&s_un, &len)) == -1) { + if (errno == ENFILE || errno == EMFILE) + goto pause; + if (errno == EINTR || errno == EWOULDBLOCK || + errno == ECONNABORTED) + return; + fatal("control_accept: accept"); + } + + io_set_nonblocking(connfd); + + if (getpeereid(connfd, &euid, &egid) == -1) + fatal("getpeereid"); + + count = tree_get(&ctl_count, euid); + if (count == NULL) { + count = xcalloc(1, sizeof *count); + tree_xset(&ctl_count, euid, count); + } + + if (*count == CONTROL_MAXCONN_PER_CLIENT) { + close(connfd); + log_warnx("warn: too many connections to control socket " + "from user with uid %lu", (unsigned long int)euid); + return; + } + (*count)++; + + do { + ++connid; + } while (tree_get(&ctl_conns, connid)); + + c = xcalloc(1, sizeof(*c)); + c->euid = euid; + c->egid = egid; + c->id = connid; + c->mproc.proc = PROC_CLIENT; + c->mproc.handler = control_dispatch_ext; + c->mproc.data = c; + if ((c->mproc.name = strdup(proc_title(c->mproc.proc))) == NULL) + fatal("strdup"); + mproc_init(&c->mproc, connfd); + mproc_enable(&c->mproc); + tree_xset(&ctl_conns, c->id, c); + + stat_backend->increment("control.session", 1); + return; + +pause: + log_warnx("warn: ctl client limit hit, disabling new connections"); + event_del(&control_state.ev); +} + +static void +control_close(struct ctl_conn *c) +{ + size_t *count; + + count = tree_xget(&ctl_count, c->euid); + (*count)--; + if (*count == 0) { + tree_xpop(&ctl_count, c->euid); + free(count); + } + tree_xpop(&ctl_conns, c->id); + mproc_clear(&c->mproc); + free(c); + + stat_backend->decrement("control.session", 1); + +#if defined(HAVE_GETDTABLESIZE) && defined(HAVE_GETDTABLECOUNT) + if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE) + return; +#else + if (available_fds(CONTROL_FD_RESERVE)) + return; +#endif + + if (!event_pending(&control_state.ev, EV_READ, NULL)) { + log_warnx("warn: re-enabling ctl connections"); + event_add(&control_state.ev, NULL); + } +} + +static void +control_digest_update(const char *key, size_t value, int incr) +{ + size_t *p; + + p = NULL; + + if (!strcmp(key, "smtp.session")) { + if (incr) + p = &digest.clt_connect; + else + digest.clt_disconnect += value; + } + else if (!strcmp(key, "scheduler.envelope")) { + if (incr) + p = &digest.evp_enqueued; + else + digest.evp_dequeued += value; + } + else if (!strcmp(key, "scheduler.envelope.expired")) + p = &digest.evp_expired; + else if (!strcmp(key, "scheduler.envelope.removed")) + p = &digest.evp_removed; + else if (!strcmp(key, "scheduler.delivery.ok")) + p = &digest.dlv_ok; + else if (!strcmp(key, "scheduler.delivery.permfail")) + p = &digest.dlv_permfail; + else if (!strcmp(key, "scheduler.delivery.tempfail")) + p = &digest.dlv_tempfail; + else if (!strcmp(key, "scheduler.delivery.loop")) + p = &digest.dlv_loop; + + else if (!strcmp(key, "queue.bounce")) + p = &digest.evp_bounce; + + if (p) { + if (incr) + *p = *p + value; + else + *p = *p - value; + } +} + +/* ARGSUSED */ +static void +control_dispatch_ext(struct mproc *p, struct imsg *imsg) +{ + struct sockaddr_storage ss; + struct ctl_conn *c; + int v; + struct stat_kv *kvp; + char *key; + struct stat_value val; + size_t len; + uint64_t evpid; + uint32_t msgid; + + c = p->data; + + if (imsg == NULL) { + control_close(c); + return; + } + + if (imsg->hdr.peerid != IMSG_VERSION) { + m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0); + return; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_SMTP_SESSION: + if (env->sc_flags & SMTPD_SMTP_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + m_compose(p_pony, IMSG_CTL_SMTP_SESSION, c->id, 0, -1, + &c->euid, sizeof(c->euid)); + return; + + case IMSG_CTL_GET_DIGEST: + if (c->euid) + goto badcred; + digest.timestamp = time(NULL); + m_compose(p, IMSG_CTL_GET_DIGEST, 0, 0, -1, &digest, sizeof digest); + return; + + case IMSG_CTL_GET_STATS: + if (c->euid) + goto badcred; + kvp = imsg->data; + if (!stat_backend->iter(&kvp->iter, &key, &val)) + kvp->iter = NULL; + else { + (void)strlcpy(kvp->key, key, sizeof kvp->key); + kvp->val = val; + } + m_compose(p, IMSG_CTL_GET_STATS, 0, 0, -1, kvp, sizeof *kvp); + return; + + case IMSG_CTL_VERBOSE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + log_trace_verbose(v); + + control_broadcast_verbose(IMSG_CTL_VERBOSE, v); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_TRACE_ENABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + tracing |= v; + log_trace_verbose(tracing); + + control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_TRACE_DISABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + tracing &= ~v; + log_trace_verbose(tracing); + + control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PROFILE_ENABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + profiling |= v; + + control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PROFILE_DISABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + profiling &= ~v; + + control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_EVP: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_PAUSE_MDA: + if (c->euid) + goto badcred; + + if (env->sc_flags & SMTPD_MDA_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mda paused"); + env->sc_flags |= SMTPD_MDA_PAUSED; + m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_MTA: + if (c->euid) + goto badcred; + + if (env->sc_flags & SMTPD_MTA_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mta paused"); + env->sc_flags |= SMTPD_MTA_PAUSED; + m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_SMTP: + if (c->euid) + goto badcred; + + if (env->sc_flags & SMTPD_SMTP_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: smtp paused"); + env->sc_flags |= SMTPD_SMTP_PAUSED; + m_compose(p_pony, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_EVP: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_RESUME_MDA: + if (c->euid) + goto badcred; + + if (!(env->sc_flags & SMTPD_MDA_PAUSED)) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mda resumed"); + env->sc_flags &= ~SMTPD_MDA_PAUSED; + m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_MTA: + if (c->euid) + goto badcred; + + if (!(env->sc_flags & SMTPD_MTA_PAUSED)) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mta resumed"); + env->sc_flags &= ~SMTPD_MTA_PAUSED; + m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_SMTP: + if (c->euid) + goto badcred; + + if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: smtp resumed"); + env->sc_flags &= ~SMTPD_SMTP_PAUSED; + m_forward(p_pony, imsg); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_ROUTE: + if (c->euid) + goto badcred; + + m_forward(p_pony, imsg); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_LIST_MESSAGES: + if (c->euid) + goto badcred; + m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1, + imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); + return; + + case IMSG_CTL_LIST_ENVELOPES: + if (c->euid) + goto badcred; + m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1, + imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); + return; + + case IMSG_CTL_MTA_SHOW_HOSTS: + case IMSG_CTL_MTA_SHOW_RELAYS: + case IMSG_CTL_MTA_SHOW_ROUTES: + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + case IMSG_CTL_MTA_SHOW_BLOCK: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_pony, imsg); + return; + + case IMSG_CTL_SHOW_STATUS: + if (c->euid) + goto badcred; + + m_compose(p, IMSG_CTL_SHOW_STATUS, 0, 0, -1, &env->sc_flags, + sizeof(env->sc_flags)); + return; + + case IMSG_CTL_MTA_BLOCK: + case IMSG_CTL_MTA_UNBLOCK: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE <= sizeof(ss)) + goto invalid; + memmove(&ss, imsg->data, sizeof(ss)); + m_create(p_pony, imsg->hdr.type, c->id, 0, -1); + m_add_sockaddr(p_pony, (struct sockaddr *)&ss); + m_add_string(p_pony, (char *)imsg->data + sizeof(ss)); + m_close(p_pony); + return; + + case IMSG_CTL_SCHEDULE: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_REMOVE: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_UPDATE_TABLE: + if (c->euid) + goto badcred; + + /* table name too long */ + len = strlen(imsg->data); + if (len >= LINE_MAX) + goto invalid; + + imsg->hdr.peerid = c->id; + m_forward(p_lka, imsg); + return; + + case IMSG_CTL_DISCOVER_EVPID: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof evpid) + goto invalid; + + memmove(&evpid, imsg->data, sizeof evpid); + m_create(p_queue, imsg->hdr.type, c->id, 0, -1); + m_add_evpid(p_queue, evpid); + m_close(p_queue); + return; + + case IMSG_CTL_DISCOVER_MSGID: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof msgid) + goto invalid; + + memmove(&msgid, imsg->data, sizeof msgid); + m_create(p_queue, imsg->hdr.type, c->id, 0, -1); + m_add_msgid(p_queue, msgid); + m_close(p_queue); + return; + + default: + log_debug("debug: control_dispatch_ext: " + "error handling %s imsg", + imsg_to_str(imsg->hdr.type)); + return; + } +badcred: +invalid: + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); +} + +static void +control_broadcast_verbose(int msg, int v) +{ + m_create(p_lka, msg, 0, 0, -1); + m_add_int(p_lka, v); + m_close(p_lka); + + m_create(p_pony, msg, 0, 0, -1); + m_add_int(p_pony, v); + m_close(p_pony); + + m_create(p_queue, msg, 0, 0, -1); + m_add_int(p_queue, v); + m_close(p_queue); + + m_create(p_ca, msg, 0, 0, -1); + m_add_int(p_ca, v); + m_close(p_ca); + + m_create(p_scheduler, msg, 0, 0, -1); + m_add_int(p_scheduler, v); + m_close(p_scheduler); + + m_create(p_parent, msg, 0, 0, -1); + m_add_int(p_parent, v); + m_close(p_parent); +} diff --git a/usr.sbin/smtpd/crypto.c b/usr.sbin/smtpd/crypto.c new file mode 100644 index 00000000..20a422cd --- /dev/null +++ b/usr.sbin/smtpd/crypto.c @@ -0,0 +1,400 @@ +/* $OpenBSD: crypto.c,v 1.8 2019/06/28 13:32:50 deraadt Exp $ */ + +/* + * Copyright (c) 2013 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include +#include + +#include + + +#define CRYPTO_BUFFER_SIZE 16384 + +#define GCM_TAG_SIZE 16 +#define IV_SIZE 12 +#define KEY_SIZE 32 + +/* bump if we ever switch from aes-256-gcm to anything else */ +#define API_VERSION 1 + + +int crypto_setup(const char *, size_t); +int crypto_encrypt_file(FILE *, FILE *); +int crypto_decrypt_file(FILE *, FILE *); +size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t); +size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t); + +static struct crypto_ctx { + unsigned char key[KEY_SIZE]; +} cp; + +int +crypto_setup(const char *key, size_t len) +{ + if (len != KEY_SIZE) + return 0; + + memset(&cp, 0, sizeof cp); + + /* openssl rand -hex 16 */ + memcpy(cp.key, key, sizeof cp.key); + + return 1; +} + +int +crypto_encrypt_file(FILE * in, FILE * out) +{ + EVP_CIPHER_CTX *ctx; + uint8_t ibuf[CRYPTO_BUFFER_SIZE]; + uint8_t obuf[CRYPTO_BUFFER_SIZE]; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + uint8_t version = API_VERSION; + size_t r, w; + int len; + int ret = 0; + struct stat sb; + + /* XXX - Do NOT encrypt files bigger than 64GB */ + if (fstat(fileno(in), &sb) == -1) + return 0; + if (sb.st_size >= 0x1000000000LL) + return 0; + + /* prepend version byte*/ + if ((w = fwrite(&version, 1, sizeof version, out)) != sizeof version) + return 0; + + /* generate and prepend IV */ + memset(iv, 0, sizeof iv); + arc4random_buf(iv, sizeof iv); + if ((w = fwrite(iv, 1, sizeof iv, out)) != sizeof iv) + return 0; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* encrypt until end of file */ + while ((r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in)) != 0) { + if (!EVP_EncryptUpdate(ctx, obuf, &len, ibuf, r)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + } + if (!feof(in)) + goto end; + + /* finalize and write last chunk if any */ + if (!EVP_EncryptFinal_ex(ctx, obuf, &len)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + + /* get and append tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag); + if ((w = fwrite(tag, sizeof tag, 1, out)) != 1) + goto end; + + fflush(out); + ret = 1; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +int +crypto_decrypt_file(FILE * in, FILE * out) +{ + EVP_CIPHER_CTX *ctx; + uint8_t ibuf[CRYPTO_BUFFER_SIZE]; + uint8_t obuf[CRYPTO_BUFFER_SIZE]; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + uint8_t version; + size_t r, w; + off_t sz; + int len; + int ret = 0; + struct stat sb; + + /* input file too small to be an encrypted file */ + if (fstat(fileno(in), &sb) == -1) + return 0; + if (sb.st_size <= (off_t) (sizeof version + sizeof tag + sizeof iv)) + return 0; + sz = sb.st_size; + + /* extract tag */ + if (fseek(in, -sizeof(tag), SEEK_END) == -1) + return 0; + if ((r = fread(tag, 1, sizeof tag, in)) != sizeof tag) + return 0; + + if (fseek(in, 0, SEEK_SET) == -1) + return 0; + + /* extract version */ + if ((r = fread(&version, 1, sizeof version, in)) != sizeof version) + return 0; + if (version != API_VERSION) + return 0; + + /* extract IV */ + memset(iv, 0, sizeof iv); + if ((r = fread(iv, 1, sizeof iv, in)) != sizeof iv) + return 0; + + /* real ciphertext length */ + sz -= sizeof version; + sz -= sizeof iv; + sz -= sizeof tag; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* set expected tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag); + + /* decrypt until end of ciphertext */ + while (sz) { + if (sz > CRYPTO_BUFFER_SIZE) + r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in); + else + r = fread(ibuf, 1, sz, in); + if (!r) + break; + if (!EVP_DecryptUpdate(ctx, obuf, &len, ibuf, r)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + sz -= r; + } + if (ferror(in)) + goto end; + + /* finalize, write last chunk if any and perform authentication check */ + if (!EVP_DecryptFinal_ex(ctx, obuf, &len)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + + fflush(out); + ret = 1; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +size_t +crypto_encrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen) +{ + EVP_CIPHER_CTX *ctx; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + uint8_t version = API_VERSION; + off_t sz; + int olen; + int len = 0; + int ret = 0; + + /* output buffer does not have enough room */ + if (outlen < inlen + sizeof version + sizeof tag + sizeof iv) + return 0; + + /* input should not exceed 64GB */ + sz = inlen; + if (sz >= 0x1000000000LL) + return 0; + + /* prepend version */ + *out = version; + len++; + + /* generate IV */ + memset(iv, 0, sizeof iv); + arc4random_buf(iv, sizeof iv); + memcpy(out + len, iv, sizeof iv); + len += sizeof iv; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* encrypt buffer */ + if (!EVP_EncryptUpdate(ctx, out + len, &olen, in, inlen)) + goto end; + len += olen; + + /* finalize and write last chunk if any */ + if (!EVP_EncryptFinal_ex(ctx, out + len, &olen)) + goto end; + len += olen; + + /* get and append tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag); + memcpy(out + len, tag, sizeof tag); + ret = len + sizeof tag; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +size_t +crypto_decrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen) +{ + EVP_CIPHER_CTX *ctx; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + int olen; + int len = 0; + int ret = 0; + + /* out does not have enough room */ + if (outlen < inlen - sizeof tag + sizeof iv) + return 0; + + /* extract tag */ + memcpy(tag, in + inlen - sizeof tag, sizeof tag); + inlen -= sizeof tag; + + /* check version */ + if (*in != API_VERSION) + return 0; + in++; + inlen--; + + /* extract IV */ + memset(iv, 0, sizeof iv); + memcpy(iv, in, sizeof iv); + inlen -= sizeof iv; + in += sizeof iv; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* set expected tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag); + + /* decrypt buffer */ + if (!EVP_DecryptUpdate(ctx, out, &olen, in, inlen)) + goto end; + len += olen; + + /* finalize, write last chunk if any and perform authentication check */ + if (!EVP_DecryptFinal_ex(ctx, out + len, &olen)) + goto end; + ret = len + olen; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +#if 0 +int +main(int argc, char *argv[]) +{ + if (argc != 3) { + printf("usage: crypto \n"); + return 1; + } + + if (!crypto_setup(argv[1], strlen(argv[1]))) { + printf("crypto_setup failed\n"); + return 1; + } + + { + char encbuffer[4096]; + size_t enclen; + char decbuffer[4096]; + size_t declen; + + printf("encrypt/decrypt buffer: "); + enclen = crypto_encrypt_buffer(argv[2], strlen(argv[2]), + encbuffer, sizeof encbuffer); + + /* uncomment below to provoke integrity check failure */ + /* + * encbuffer[13] = 0x42; + * encbuffer[14] = 0x42; + * encbuffer[15] = 0x42; + * encbuffer[16] = 0x42; + */ + + declen = crypto_decrypt_buffer(encbuffer, enclen, + decbuffer, sizeof decbuffer); + if (declen != 0 && !strncmp(argv[2], decbuffer, declen)) + printf("ok\n"); + else + printf("nope\n"); + } + + { + FILE *fpin; + FILE *fpout; + printf("encrypt/decrypt file: "); + + fpin = fopen("/etc/passwd", "r"); + fpout = fopen("/tmp/passwd.enc", "w"); + if (!crypto_encrypt_file(fpin, fpout)) { + printf("encryption failed\n"); + return 1; + } + fclose(fpin); + fclose(fpout); + + /* uncomment below to provoke integrity check failure */ + /* + * fpin = fopen("/tmp/passwd.enc", "a"); + * fprintf(fpin, "borken"); + * fclose(fpin); + */ + fpin = fopen("/tmp/passwd.enc", "r"); + fpout = fopen("/tmp/passwd.dec", "w"); + if (!crypto_decrypt_file(fpin, fpout)) + printf("nope\n"); + else + printf("ok\n"); + fclose(fpin); + fclose(fpout); + } + + + return 0; +} +#endif diff --git a/usr.sbin/smtpd/dict.c b/usr.sbin/smtpd/dict.c new file mode 100644 index 00000000..e660f0a5 --- /dev/null +++ b/usr.sbin/smtpd/dict.c @@ -0,0 +1,269 @@ +/* $OpenBSD: dict.c,v 1.6 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include +#include +#include +#include + +#include "dict.h" + +struct dictentry { + SPLAY_ENTRY(dictentry) entry; + const char *key; + void *data; +}; + +static int dictentry_cmp(struct dictentry *, struct dictentry *); + +SPLAY_PROTOTYPE(_dict, dictentry, entry, dictentry_cmp); + +int +dict_check(struct dict *d, const char *k) +{ + struct dictentry key; + + key.key = k; + return (SPLAY_FIND(_dict, &d->dict, &key) != NULL); +} + +static inline struct dictentry * +dict_alloc(const char *k, void *data) +{ + struct dictentry *e; + size_t s = strlen(k) + 1; + void *t; + + if ((e = malloc(sizeof(*e) + s)) == NULL) + return NULL; + + e->key = t = (char*)(e) + sizeof(*e); + e->data = data; + memmove(t, k, s); + + return (e); +} + +void * +dict_set(struct dict *d, const char *k, void *data) +{ + struct dictentry *entry, key; + char *old; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) { + if ((entry = dict_alloc(k, data)) == NULL) + err(1, "dict_set: malloc"); + SPLAY_INSERT(_dict, &d->dict, entry); + old = NULL; + d->count += 1; + } else { + old = entry->data; + entry->data = data; + } + + return (old); +} + +void +dict_xset(struct dict *d, const char * k, void *data) +{ + struct dictentry *entry; + + if ((entry = dict_alloc(k, data)) == NULL) + err(1, "dict_xset: malloc"); + if (SPLAY_INSERT(_dict, &d->dict, entry)) + errx(1, "dict_xset(%p, %s)", d, k); + d->count += 1; +} + +void * +dict_get(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + return (NULL); + + return (entry->data); +} + +void * +dict_xget(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + errx(1, "dict_xget(%p, %s)", d, k); + + return (entry->data); +} + +void * +dict_pop(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + void *data; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + return (NULL); + + data = entry->data; + SPLAY_REMOVE(_dict, &d->dict, entry); + free(entry); + d->count -= 1; + + return (data); +} + +void * +dict_xpop(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + void *data; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + errx(1, "dict_xpop(%p, %s)", d, k); + + data = entry->data; + SPLAY_REMOVE(_dict, &d->dict, entry); + free(entry); + d->count -= 1; + + return (data); +} + +int +dict_poproot(struct dict *d, void **data) +{ + struct dictentry *entry; + + entry = SPLAY_ROOT(&d->dict); + if (entry == NULL) + return (0); + if (data) + *data = entry->data; + SPLAY_REMOVE(_dict, &d->dict, entry); + free(entry); + d->count -= 1; + + return (1); +} + +int +dict_root(struct dict *d, const char **k, void **data) +{ + struct dictentry *entry; + + entry = SPLAY_ROOT(&d->dict); + if (entry == NULL) + return (0); + if (k) + *k = entry->key; + if (data) + *data = entry->data; + return (1); +} + +int +dict_iter(struct dict *d, void **hdl, const char **k, void **data) +{ + struct dictentry *curr = *hdl; + + if (curr == NULL) + curr = SPLAY_MIN(_dict, &d->dict); + else + curr = SPLAY_NEXT(_dict, &d->dict, curr); + + if (curr) { + *hdl = curr; + if (k) + *k = curr->key; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +int +dict_iterfrom(struct dict *d, void **hdl, const char *kfrom, const char **k, + void **data) +{ + struct dictentry *curr = *hdl, key; + + if (curr == NULL) { + if (kfrom == NULL) + curr = SPLAY_MIN(_dict, &d->dict); + else { + key.key = kfrom; + curr = SPLAY_FIND(_dict, &d->dict, &key); + if (curr == NULL) { + SPLAY_INSERT(_dict, &d->dict, &key); + curr = SPLAY_NEXT(_dict, &d->dict, &key); + SPLAY_REMOVE(_dict, &d->dict, &key); + } + } + } else + curr = SPLAY_NEXT(_dict, &d->dict, curr); + + if (curr) { + *hdl = curr; + if (k) + *k = curr->key; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +void +dict_merge(struct dict *dst, struct dict *src) +{ + struct dictentry *entry; + + while (!SPLAY_EMPTY(&src->dict)) { + entry = SPLAY_ROOT(&src->dict); + SPLAY_REMOVE(_dict, &src->dict, entry); + if (SPLAY_INSERT(_dict, &dst->dict, entry)) + errx(1, "dict_merge: duplicate"); + } + dst->count += src->count; + src->count = 0; +} + +static int +dictentry_cmp(struct dictentry *a, struct dictentry *b) +{ + return strcmp(a->key, b->key); +} + +SPLAY_GENERATE(_dict, dictentry, entry, dictentry_cmp); diff --git a/usr.sbin/smtpd/dict.h b/usr.sbin/smtpd/dict.h new file mode 100644 index 00000000..c5d47e1a --- /dev/null +++ b/usr.sbin/smtpd/dict.h @@ -0,0 +1,48 @@ +/* $OpenBSD: dict.h,v 1.1 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2011 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _DICT_H_ +#define _DICT_H_ + +SPLAY_HEAD(_dict, dictentry); + +struct dict { + struct _dict dict; + size_t count; +}; + + +/* dict.c */ +#define dict_init(d) do { SPLAY_INIT(&((d)->dict)); (d)->count = 0; } while(0) +#define dict_empty(d) SPLAY_EMPTY(&((d)->dict)) +#define dict_count(d) ((d)->count) +int dict_check(struct dict *, const char *); +void *dict_set(struct dict *, const char *, void *); +void dict_xset(struct dict *, const char *, void *); +void *dict_get(struct dict *, const char *); +void *dict_xget(struct dict *, const char *); +void *dict_pop(struct dict *, const char *); +void *dict_xpop(struct dict *, const char *); +int dict_poproot(struct dict *, void **); +int dict_root(struct dict *, const char **, void **); +int dict_iter(struct dict *, void **, const char **, void **); +int dict_iterfrom(struct dict *, void **, const char *, const char **, void **); +void dict_merge(struct dict *, struct dict *); + +#endif diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c new file mode 100644 index 00000000..a3107e89 --- /dev/null +++ b/usr.sbin/smtpd/dns.c @@ -0,0 +1,379 @@ +/* $OpenBSD: dns.c,v 1.89 2019/09/18 11:26:30 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2011-2014 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" +#include "unpack_dns.h" + +/* On OpenBSD, this function is not needed because we don't free addrinfo */ +#if defined(NOOP_ASR_FREEADDRINFO) +#define asr_freeaddrinfo(x) do { } while(0); +#endif + +struct dns_lookup { + struct dns_session *session; + char *host; + int preference; +}; + +struct dns_session { + struct mproc *p; + uint64_t reqid; + int type; + char name[HOST_NAME_MAX+1]; + size_t mxfound; + int error; + int refcount; +}; + +static void dns_lookup_host(struct dns_session *, const char *, int); +static void dns_dispatch_host(struct asr_result *, void *); +static void dns_dispatch_mx(struct asr_result *, void *); +static void dns_dispatch_mx_preference(struct asr_result *, void *); + +static int +domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl) +{ + struct addrinfo hints, *res; + socklen_t sl2; + size_t l; + char buf[SMTPD_MAXDOMAINPARTSIZE]; + int i6, error; + + if (*s != '[') + return (0); + + i6 = (strncasecmp("[IPv6:", s, 6) == 0); + s += i6 ? 6 : 1; + + l = strlcpy(buf, s, sizeof(buf)); + if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']') + return (0); + + buf[l - 1] = '\0'; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_STREAM; + if (i6) + hints.ai_family = AF_INET6; + + res = NULL; + if ((error = getaddrinfo(buf, NULL, &hints, &res))) { + log_warnx("getaddrinfo: %s", gai_strerror(error)); + } + + if (!res) + return (0); + + if (sa && sl) { + sl2 = *sl; + if (sl2 > res->ai_addrlen) + sl2 = res->ai_addrlen; + memmove(sa, res->ai_addr, sl2); + *sl = res->ai_addrlen; + } + + freeaddrinfo(res); + return (1); +} + +void +dns_imsg(struct mproc *p, struct imsg *imsg) +{ + struct sockaddr_storage ss; + struct dns_session *s; + struct sockaddr *sa; + struct asr_query *as; + struct msg m; + const char *domain, *mx, *host; + socklen_t sl; + + s = xcalloc(1, sizeof *s); + s->type = imsg->hdr.type; + s->p = p; + + m_msg(&m, imsg); + m_get_id(&m, &s->reqid); + + switch (s->type) { + + case IMSG_MTA_DNS_HOST: + m_get_string(&m, &host); + m_end(&m); + dns_lookup_host(s, host, -1); + return; + + case IMSG_MTA_DNS_MX: + m_get_string(&m, &domain); + m_end(&m); + (void)strlcpy(s->name, domain, sizeof(s->name)); + + sa = (struct sockaddr *)&ss; + sl = sizeof(ss); + + if (domainname_is_addr(domain, sa, &sl)) { + m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_string(s->p, sockaddr_to_text(sa)); + m_add_sockaddr(s->p, sa); + m_add_int(s->p, -1); + m_close(s->p); + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_OK); + m_close(s->p); + free(s); + return; + } + + as = res_query_async(s->name, C_IN, T_MX, NULL); + if (as == NULL) { + log_warn("warn: res_query_async: %s", s->name); + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_EINVAL); + m_close(s->p); + free(s); + return; + } + + event_asr_run(as, dns_dispatch_mx, s); + return; + + case IMSG_MTA_DNS_MX_PREFERENCE: + m_get_string(&m, &domain); + m_get_string(&m, &mx); + m_end(&m); + (void)strlcpy(s->name, mx, sizeof(s->name)); + + as = res_query_async(domain, C_IN, T_MX, NULL); + if (as == NULL) { + m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_ENOTFOUND); + m_close(s->p); + free(s); + return; + } + + event_asr_run(as, dns_dispatch_mx_preference, s); + return; + + default: + log_warnx("warn: bad dns request %d", s->type); + fatal(NULL); + } +} + +static void +dns_dispatch_host(struct asr_result *ar, void *arg) +{ + struct dns_session *s; + struct dns_lookup *lookup = arg; + struct addrinfo *ai; + + s = lookup->session; + + for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { + s->mxfound++; + m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_string(s->p, lookup->host); + m_add_sockaddr(s->p, ai->ai_addr); + m_add_int(s->p, lookup->preference); + m_close(s->p); + } + free(lookup->host); + free(lookup); + if (ar->ar_addrinfo) + asr_freeaddrinfo(ar->ar_addrinfo); + + if (ar->ar_gai_errno) + s->error = ar->ar_gai_errno; + + if (--s->refcount) + return; + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND); + m_close(s->p); + free(s); +} + +static void +dns_dispatch_mx(struct asr_result *ar, void *arg) +{ + struct dns_session *s = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + char buf[512]; + size_t found; + + if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA && + ar->ar_h_errno != NOTIMP) { + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + if (ar->ar_rcode == NXDOMAIN) + m_add_int(s->p, DNS_ENONAME); + else if (ar->ar_h_errno == NO_RECOVERY) + m_add_int(s->p, DNS_EINVAL); + else + m_add_int(s->p, DNS_RETRY); + m_close(s->p); + free(s); + free(ar->ar_data); + return; + } + + unpack_init(&pack, ar->ar_data, ar->ar_datalen); + unpack_header(&pack, &h); + unpack_query(&pack, &q); + + found = 0; + for (; h.ancount; h.ancount--) { + unpack_rr(&pack, &rr); + if (rr.rr_type != T_MX) + continue; + print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); + buf[strlen(buf) - 1] = '\0'; + dns_lookup_host(s, buf, rr.rr.mx.preference); + found++; + } + free(ar->ar_data); + + /* fallback to host if no MX is found. */ + if (found == 0) + dns_lookup_host(s, s->name, 0); +} + +static void +dns_dispatch_mx_preference(struct asr_result *ar, void *arg) +{ + struct dns_session *s = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + char buf[512]; + int error; + + if (ar->ar_h_errno) { + if (ar->ar_rcode == NXDOMAIN) + error = DNS_ENONAME; + else if (ar->ar_h_errno == NO_RECOVERY + || ar->ar_h_errno == NO_DATA) + error = DNS_EINVAL; + else + error = DNS_RETRY; + } + else { + error = DNS_ENOTFOUND; + unpack_init(&pack, ar->ar_data, ar->ar_datalen); + unpack_header(&pack, &h); + unpack_query(&pack, &q); + for (; h.ancount; h.ancount--) { + unpack_rr(&pack, &rr); + if (rr.rr_type != T_MX) + continue; + print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); + buf[strlen(buf) - 1] = '\0'; + if (!strcasecmp(s->name, buf)) { + error = DNS_OK; + break; + } + } + } + + free(ar->ar_data); + + m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, error); + if (error == DNS_OK) + m_add_int(s->p, rr.rr.mx.preference); + m_close(s->p); + free(s); +} + +static void +dns_lookup_host(struct dns_session *s, const char *host, int preference) +{ + struct dns_lookup *lookup; + struct addrinfo hints; + char hostcopy[HOST_NAME_MAX+1]; + char *p; + void *as; + + lookup = xcalloc(1, sizeof *lookup); + lookup->preference = preference; + lookup->host = xstrdup(host); + lookup->session = s; + s->refcount++; + + if (*host == '[') { + if (strncasecmp("[IPv6:", host, 6) == 0) + host += 6; + else + host += 1; + (void)strlcpy(hostcopy, host, sizeof hostcopy); + p = strchr(hostcopy, ']'); + if (p) + *p = 0; + host = hostcopy; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + as = getaddrinfo_async(host, NULL, &hints, NULL); + event_asr_run(as, dns_dispatch_host, lookup); +} diff --git a/usr.sbin/smtpd/enqueue.c b/usr.sbin/smtpd/enqueue.c new file mode 100644 index 00000000..0ef694b5 --- /dev/null +++ b/usr.sbin/smtpd/enqueue.c @@ -0,0 +1,932 @@ +/* $OpenBSD: enqueue.c,v 1.118 2020/03/18 20:17:14 eric Exp $ */ + +/* + * Copyright (c) 2005 Henning Brauer + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" + +extern struct imsgbuf *ibuf; + +void usage(void); +static void build_from(char *, struct passwd *); +static int parse_message(FILE *, int, int, FILE *); +static void parse_addr(char *, size_t, int); +static void parse_addr_terminal(int); +static char *qualify_addr(char *); +static void rcpt_add(char *); +static int open_connection(void); +static int get_responses(FILE *, int); +static int send_line(FILE *, int, char *, ...); +static int enqueue_offline(int, char *[], FILE *, FILE *); +static int savedeadletter(struct passwd *, FILE *); + +extern int srv_connected(void); + +enum headerfields { + HDR_NONE, + HDR_FROM, + HDR_TO, + HDR_CC, + HDR_BCC, + HDR_SUBJECT, + HDR_DATE, + HDR_MSGID, + HDR_MIME_VERSION, + HDR_CONTENT_TYPE, + HDR_CONTENT_DISPOSITION, + HDR_CONTENT_TRANSFER_ENCODING, + HDR_USER_AGENT +}; + +struct { + char *word; + enum headerfields type; +} keywords[] = { + { "From:", HDR_FROM }, + { "To:", HDR_TO }, + { "Cc:", HDR_CC }, + { "Bcc:", HDR_BCC }, + { "Subject:", HDR_SUBJECT }, + { "Date:", HDR_DATE }, + { "Message-Id:", HDR_MSGID }, + { "MIME-Version:", HDR_MIME_VERSION }, + { "Content-Type:", HDR_CONTENT_TYPE }, + { "Content-Disposition:", HDR_CONTENT_DISPOSITION }, + { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, + { "User-Agent:", HDR_USER_AGENT }, +}; + +#define LINESPLIT 990 +#define SMTP_LINELEN 1000 +#define TIMEOUTMSG "Timeout\n" + +#define WSP(c) (c == ' ' || c == '\t') + +int verbose = 0; +static char host[HOST_NAME_MAX+1]; +char *user = NULL; +time_t timestamp; + +struct { + int fd; + char *from; + char *fromname; + char **rcpts; + char *dsn_notify; + char *dsn_ret; + char *dsn_envid; + int rcpt_cnt; + int need_linesplit; + int saw_date; + int saw_msgid; + int saw_from; + int saw_mime_version; + int saw_content_type; + int saw_content_disposition; + int saw_content_transfer_encoding; + int saw_user_agent; + int noheader; +} msg; + +struct { + uint quote; + uint comment; + uint esc; + uint brackets; + size_t wpos; + char buf[SMTP_LINELEN]; +} pstate; + +#define QP_TEST_WRAP(fp, buf, linelen, size) do { \ + if (((linelen) += (size)) + 1 > 76) { \ + fprintf((fp), "=\r\n"); \ + if (buf[0] == '.') \ + fprintf((fp), "."); \ + (linelen) = (size); \ + } \ +} while (0) + +/* RFC 2045 section 6.7 */ +static void +qp_encoded_write(FILE *fp, char *buf) +{ + size_t linelen = 0; + + for (;buf[0] != '\0' && buf[0] != '\n'; buf++) { + /* + * Point 3: Any TAB (HT) or SPACE characters on an encoded line + * MUST thus be followed on that line by a printable character. + * + * Ergo, only encode if the next character is EOL. + */ + if (buf[0] == ' ' || buf[0] == '\t') { + if (buf[1] == '\n') { + QP_TEST_WRAP(fp, buf, linelen, 3); + fprintf(fp, "=%2X", *buf & 0xff); + } else { + QP_TEST_WRAP(fp, buf, linelen, 1); + fprintf(fp, "%c", *buf & 0xff); + } + /* + * Point 1, with exclusion of point 2, skip EBCDIC NOTE. + * Do this after whitespace check, else they would match here. + */ + } else if (!((buf[0] >= 33 && buf[0] <= 60) || + (buf[0] >= 62 && buf[0] <= 126))) { + QP_TEST_WRAP(fp, buf, linelen, 3); + fprintf(fp, "=%2X", *buf & 0xff); + /* Point 2: 33 through 60 inclusive, and 62 through 126 */ + } else { + QP_TEST_WRAP(fp, buf, linelen, 1); + fprintf(fp, "%c", *buf); + } + } + fprintf(fp, "\r\n"); +} + +int +enqueue(int argc, char *argv[], FILE *ofp) +{ + int i, ch, tflag = 0; + char *fake_from = NULL, *buf = NULL; + struct passwd *pw; + FILE *fp = NULL, *fout; + size_t sz = 0, envid_sz = 0; + ssize_t len; + char *line; + int inheaders = 1; + int save_argc; + char **save_argv; + int no_getlogin = 0; + + memset(&msg, 0, sizeof(msg)); + time(×tamp); + + save_argc = argc; + save_argv = argv; + + while ((ch = getopt(argc, argv, + "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) { + switch (ch) { + case 'f': + fake_from = optarg; + break; + case 'F': + msg.fromname = optarg; + break; + case 'N': + msg.dsn_notify = optarg; + break; + case 'r': + fake_from = optarg; + break; + case 'R': + msg.dsn_ret = optarg; + break; + case 'S': + no_getlogin = 1; + break; + case 't': + tflag = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + msg.dsn_envid = optarg; + break; + /* all remaining: ignored, sendmail compat */ + case 'A': + case 'B': + case 'b': + case 'E': + case 'e': + case 'i': + case 'L': + case 'm': + case 'o': + case 'p': + case 'x': + break; + case 'q': + /* XXX: implement "process all now" */ + return (EX_SOFTWARE); + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (getmailname(host, sizeof(host)) == -1) + errx(EX_NOHOST, "getmailname"); + if (no_getlogin) { + if ((pw = getpwuid(getuid())) == NULL) + user = "anonymous"; + if (pw != NULL) + user = xstrdup(pw->pw_name); + } + else { + uid_t ruid = getuid(); + + if ((user = getlogin()) != NULL && *user != '\0') { + if ((pw = getpwnam(user)) == NULL || + (ruid != 0 && ruid != pw->pw_uid)) + pw = getpwuid(ruid); + } else if ((pw = getpwuid(ruid)) == NULL) { + user = "anonymous"; + } + user = xstrdup(pw ? pw->pw_name : user); + } + + build_from(fake_from, pw); + + while (argc > 0) { + rcpt_add(argv[0]); + argv++; + argc--; + } + + if ((fp = tmpfile()) == NULL) + err(EX_UNAVAILABLE, "tmpfile"); + + msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp); + + if (msg.rcpt_cnt == 0) + errx(EX_SOFTWARE, "no recipients"); + + /* init session */ + rewind(fp); + + /* check if working in offline mode */ + /* If the server is not running, enqueue the message offline */ + + if (!srv_connected()) { +#if HAVE_PLEDGE + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + return (enqueue_offline(save_argc, save_argv, fp, ofp)); + } + + if ((msg.fd = open_connection()) == -1) + errx(EX_UNAVAILABLE, "server too busy"); + +#if HAVE_PLEDGE + if (pledge("stdio wpath cpath", NULL) == -1) + err(1, "pledge"); +#endif + + fout = fdopen(msg.fd, "a+"); + if (fout == NULL) + err(EX_UNAVAILABLE, "fdopen"); + + /* + * We need to call get_responses after every command because we don't + * support PIPELINING on the server-side yet. + */ + + /* banner */ + if (!get_responses(fout, 1)) + goto fail; + + if (!send_line(fout, verbose, "EHLO localhost\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + if (msg.dsn_envid != NULL) + envid_sz = strlen(msg.dsn_envid); + + if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n", + msg.from, + msg.dsn_ret ? "RET=" : "", + msg.dsn_ret ? msg.dsn_ret : "", + envid_sz ? "ENVID=" : "", + envid_sz ? msg.dsn_envid : "")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + for (i = 0; i < msg.rcpt_cnt; i++) { + if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n", + msg.rcpts[i], + msg.dsn_notify ? "NOTIFY=" : "", + msg.dsn_notify ? msg.dsn_notify : "")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + } + + if (!send_line(fout, verbose, "DATA\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + /* add From */ + if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n", + msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", + msg.from)) + goto fail; + + /* add Date */ + if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n", + time_to_text(timestamp))) + goto fail; + + if (msg.need_linesplit) { + /* we will always need to mime encode for long lines */ + if (!msg.saw_mime_version && !send_line(fout, 0, + "MIME-Version: 1.0\r\n")) + goto fail; + if (!msg.saw_content_type && !send_line(fout, 0, + "Content-Type: text/plain; charset=unknown-8bit\r\n")) + goto fail; + if (!msg.saw_content_disposition && !send_line(fout, 0, + "Content-Disposition: inline\r\n")) + goto fail; + if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, + "Content-Transfer-Encoding: quoted-printable\r\n")) + goto fail; + } + + /* add separating newline */ + if (msg.noheader) { + if (!send_line(fout, 0, "\r\n")) + goto fail; + inheaders = 0; + } + + for (;;) { + if ((len = getline(&buf, &sz, fp)) == -1) { + if (feof(fp)) + break; + else + err(EX_UNAVAILABLE, "getline"); + } + + /* newlines have been normalized on first parsing */ + if (buf[len-1] != '\n') + errx(EX_SOFTWARE, "expect EOL"); + len--; + + if (buf[0] == '.') { + if (fputc('.', fout) == EOF) + goto fail; + } + + line = buf; + + if (inheaders) { + if (strncasecmp("from ", line, 5) == 0) + continue; + if (strncasecmp("return-path: ", line, 13) == 0) + continue; + } + + if (msg.saw_content_transfer_encoding || msg.noheader || + inheaders || !msg.need_linesplit) { + if (!send_line(fout, 0, "%.*s\r\n", (int)len, line)) + goto fail; + if (inheaders && buf[0] == '\n') + inheaders = 0; + continue; + } + + /* we don't have a content transfer encoding, use our default */ + qp_encoded_write(fout, line); + } + free(buf); + if (!send_line(fout, verbose, ".\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + if (!send_line(fout, verbose, "QUIT\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + fclose(fp); + fclose(fout); + + exit(EX_OK); + +fail: + if (pw) + savedeadletter(pw, fp); + exit(EX_SOFTWARE); +} + +static int +get_responses(FILE *fin, int n) +{ + char *buf = NULL; + size_t sz = 0; + ssize_t len; + int e, ret = 0; + + fflush(fin); + if ((e = ferror(fin))) { + warnx("ferror: %d", e); + goto err; + } + + while (n) { + if ((len = getline(&buf, &sz, fin)) == -1) { + if (ferror(fin)) { + warn("getline"); + goto err; + } else if (feof(fin)) + break; + else + err(EX_UNAVAILABLE, "getline"); + } + + /* account for \r\n linebreaks */ + if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') + buf[--len - 1] = '\n'; + + if (len < 4) { + warnx("bad response"); + goto err; + } + + if (verbose) + printf("<<< %.*s", (int)len, buf); + + if (buf[3] == '-') + continue; + if (buf[0] != '2' && buf[0] != '3') { + warnx("command failed: %.*s", (int)len, buf); + goto err; + } + n--; + } + + ret = 1; +err: + free(buf); + return ret; +} + +static int +send_line(FILE *fp, int v, char *fmt, ...) +{ + int ret = 0; + va_list ap; + + va_start(ap, fmt); + if (vfprintf(fp, fmt, ap) >= 0) + ret = 1; + va_end(ap); + + if (ret && v) { + printf(">>> "); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + return (ret); +} + +static void +build_from(char *fake_from, struct passwd *pw) +{ + char *p; + + if (fake_from == NULL) + msg.from = qualify_addr(user); + else { + if (fake_from[0] == '<') { + if (fake_from[strlen(fake_from) - 1] != '>') + errx(1, "leading < but no trailing >"); + fake_from[strlen(fake_from) - 1] = 0; + p = xstrdup(fake_from + 1); + + msg.from = qualify_addr(p); + free(p); + } else + msg.from = qualify_addr(fake_from); + } + + if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { + int len, apos; + + len = strcspn(pw->pw_gecos, ","); + if ((p = memchr(pw->pw_gecos, '&', len))) { + apos = p - pw->pw_gecos; + if (asprintf(&msg.fromname, "%.*s%s%.*s", + apos, pw->pw_gecos, + pw->pw_name, + len - apos - 1, p + 1) == -1) + err(1, "asprintf"); + msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]); + } else { + if (asprintf(&msg.fromname, "%.*s", len, + pw->pw_gecos) == -1) + err(1, "asprintf"); + } + } +} + +static int +parse_message(FILE *fin, int get_from, int tflag, FILE *fout) +{ + char *buf = NULL; + size_t sz = 0; + ssize_t len; + uint i, cur = HDR_NONE; + uint header_seen = 0, header_done = 0; + + memset(&pstate, 0, sizeof(pstate)); + for (;;) { + if ((len = getline(&buf, &sz, fin)) == -1) { + if (feof(fin)) + break; + else + err(EX_UNAVAILABLE, "getline"); + } + + /* account for \r\n linebreaks */ + if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') + buf[--len - 1] = '\n'; + + if (len == 1 && buf[0] == '\n') /* end of header */ + header_done = 1; + + if (!WSP(buf[0])) { /* whitespace -> continuation */ + if (cur == HDR_FROM) + parse_addr_terminal(1); + if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) + parse_addr_terminal(0); + cur = HDR_NONE; + } + + /* not really exact, if we are still in headers */ + if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) + msg.need_linesplit = 1; + + for (i = 0; !header_done && cur == HDR_NONE && + i < nitems(keywords); i++) + if ((size_t)len > strlen(keywords[i].word) && + !strncasecmp(buf, keywords[i].word, + strlen(keywords[i].word))) + cur = keywords[i].type; + + if (cur != HDR_NONE) + header_seen = 1; + + if (cur != HDR_BCC) { + if (!send_line(fout, 0, "%.*s", (int)len, buf)) + err(1, "write error"); + if (buf[len - 1] != '\n') { + if (fputc('\n', fout) == EOF) + err(1, "write error"); + } + } + + /* + * using From: as envelope sender is not sendmail compatible, + * but I really want it that way - maybe needs a knob + */ + if (cur == HDR_FROM) { + msg.saw_from++; + if (get_from) + parse_addr(buf, len, 1); + } + + if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) + parse_addr(buf, len, 0); + + if (cur == HDR_DATE) + msg.saw_date++; + if (cur == HDR_MSGID) + msg.saw_msgid++; + if (cur == HDR_MIME_VERSION) + msg.saw_mime_version = 1; + if (cur == HDR_CONTENT_TYPE) + msg.saw_content_type = 1; + if (cur == HDR_CONTENT_DISPOSITION) + msg.saw_content_disposition = 1; + if (cur == HDR_CONTENT_TRANSFER_ENCODING) + msg.saw_content_transfer_encoding = 1; + if (cur == HDR_USER_AGENT) + msg.saw_user_agent = 1; + } + + free(buf); + return (!header_seen); +} + +static void +parse_addr(char *s, size_t len, int is_from) +{ + size_t pos = 0; + int terminal = 0; + + /* unless this is a continuation... */ + if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { + /* ... skip over everything before the ':' */ + for (; pos < len && s[pos] != ':'; pos++) + ; /* nothing */ + /* ... and check & reset parser state */ + parse_addr_terminal(is_from); + } + + /* skip over ':' ',' ';' and whitespace */ + for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || + s[pos] == ',' || s[pos] == ';'); pos++) + ; /* nothing */ + + for (; pos < len; pos++) { + if (!pstate.esc && !pstate.quote && s[pos] == '(') + pstate.comment++; + if (!pstate.comment && !pstate.esc && s[pos] == '"') + pstate.quote = !pstate.quote; + + if (!pstate.comment && !pstate.quote && !pstate.esc) { + if (s[pos] == ':') { /* group */ + for (pos++; pos < len && WSP(s[pos]); pos++) + ; /* nothing */ + pstate.wpos = 0; + } + if (s[pos] == '\n' || s[pos] == '\r') + break; + if (s[pos] == ',' || s[pos] == ';') { + terminal = 1; + break; + } + if (s[pos] == '<') { + pstate.brackets = 1; + pstate.wpos = 0; + } + if (pstate.brackets && s[pos] == '>') + terminal = 1; + } + + if (!pstate.comment && !terminal && (!(!(pstate.quote || + pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { + if (pstate.wpos >= sizeof(pstate.buf)) + errx(1, "address exceeds buffer size"); + pstate.buf[pstate.wpos++] = s[pos]; + } + + if (!pstate.quote && pstate.comment && s[pos] == ')') + pstate.comment--; + + if (!pstate.esc && !pstate.comment && s[pos] == '\\') + pstate.esc = 1; + else + pstate.esc = 0; + } + + if (terminal) + parse_addr_terminal(is_from); + + for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) + ; /* nothing */ + + if (pos < len) + parse_addr(s + pos, len - pos, is_from); +} + +static void +parse_addr_terminal(int is_from) +{ + if (pstate.comment || pstate.quote || pstate.esc) + errx(1, "syntax error in address"); + if (pstate.wpos) { + if (pstate.wpos >= sizeof(pstate.buf)) + errx(1, "address exceeds buffer size"); + pstate.buf[pstate.wpos] = '\0'; + if (is_from) + msg.from = qualify_addr(pstate.buf); + else + rcpt_add(pstate.buf); + pstate.wpos = 0; + } +} + +static char * +qualify_addr(char *in) +{ + char *out; + + if (strlen(in) > 0 && strchr(in, '@') == NULL) { + if (asprintf(&out, "%s@%s", in, host) == -1) + err(1, "qualify asprintf"); + } else + out = xstrdup(in); + + return (out); +} + +static void +rcpt_add(char *addr) +{ + void *nrcpts; + char *p; + int n; + + n = 1; + p = addr; + while ((p = strchr(p, ',')) != NULL) { + n++; + p++; + } + + if ((nrcpts = reallocarray(msg.rcpts, + msg.rcpt_cnt + n, sizeof(char *))) == NULL) + err(1, "rcpt_add realloc"); + msg.rcpts = nrcpts; + + while (n--) { + if ((p = strchr(addr, ',')) != NULL) + *p++ = '\0'; + msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); + if (p == NULL) + break; + addr = p; + } +} + +static int +open_connection(void) +{ + struct imsg imsg; + int fd; + int n; + + imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0); + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) + err(1, "write error"); + + while (1) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + continue; + + switch (imsg.hdr.type) { + case IMSG_CTL_OK: + break; + case IMSG_CTL_FAIL: + errx(1, "server disallowed submission request"); + default: + errx(1, "unexpected imsg reply type"); + } + + fd = imsg.fd; + imsg_free(&imsg); + + break; + } + + return fd; +} + +static int +enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile) +{ + int i, ch; + + for (i = 1; i < argc; i++) { + if (strchr(argv[i], '|') != NULL) { + warnx("%s contains illegal character", argv[i]); + ftruncate(fileno(ofile), 0); + exit(EX_SOFTWARE); + } + if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0) + goto write_error; + } + + if (fputc('\n', ofile) == EOF) + goto write_error; + + while ((ch = fgetc(ifile)) != EOF) { + if (fputc(ch, ofile) == EOF) + goto write_error; + } + + if (ferror(ifile)) { + warn("read error"); + ftruncate(fileno(ofile), 0); + exit(EX_UNAVAILABLE); + } + + if (fclose(ofile) == EOF) + goto write_error; + + return (EX_TEMPFAIL); +write_error: + warn("write error"); + ftruncate(fileno(ofile), 0); + exit(EX_UNAVAILABLE); +} + +static int +savedeadletter(struct passwd *pw, FILE *in) +{ + char buffer[PATH_MAX]; + FILE *fp; + char *buf = NULL; + size_t sz = 0; + ssize_t len; + + (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir); + + if (fseek(in, 0, SEEK_SET) != 0) + return 0; + + if ((fp = fopen(buffer, "w")) == NULL) + return 0; + + /* add From */ + if (!msg.saw_from) + fprintf(fp, "From: %s%s<%s>\n", + msg.fromname ? msg.fromname : "", + msg.fromname ? " " : "", + msg.from); + + /* add Date */ + if (!msg.saw_date) + fprintf(fp, "Date: %s\n", time_to_text(timestamp)); + + if (msg.need_linesplit) { + /* we will always need to mime encode for long lines */ + if (!msg.saw_mime_version) + fprintf(fp, "MIME-Version: 1.0\n"); + if (!msg.saw_content_type) + fprintf(fp, "Content-Type: text/plain; " + "charset=unknown-8bit\n"); + if (!msg.saw_content_disposition) + fprintf(fp, "Content-Disposition: inline\n"); + if (!msg.saw_content_transfer_encoding) + fprintf(fp, "Content-Transfer-Encoding: " + "quoted-printable\n"); + } + + /* add separating newline */ + if (msg.noheader) + fprintf(fp, "\n"); + + while ((len = getline(&buf, &sz, in)) != -1) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + fprintf(fp, "%s\n", buf); + } + + free(buf); + fprintf(fp, "\n"); + fclose(fp); + return 1; +} diff --git a/usr.sbin/smtpd/envelope.c b/usr.sbin/smtpd/envelope.c new file mode 100644 index 00000000..35d98b79 --- /dev/null +++ b/usr.sbin/smtpd/envelope.c @@ -0,0 +1,786 @@ +/* $OpenBSD: envelope.c,v 1.47 2019/11/25 14:18:32 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2011-2013 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static int envelope_ascii_load(struct envelope *, struct dict *); +static void envelope_ascii_dump(const struct envelope *, char **, size_t *, + const char *); + +void +envelope_set_errormsg(struct envelope *e, char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = vsnprintf(e->errorline, sizeof(e->errorline), fmt, ap); + va_end(ap); + + /* this should not happen */ + if (ret < 0) + err(1, "vsnprintf"); + + if ((size_t)ret >= sizeof(e->errorline)) + (void)strlcpy(e->errorline + (sizeof(e->errorline) - 4), + "...", 4); +} + +void +envelope_set_esc_class(struct envelope *e, enum enhanced_status_class class) +{ + e->esc_class = class; +} + +void +envelope_set_esc_code(struct envelope *e, enum enhanced_status_code code) +{ + e->esc_code = code; +} + +static int +envelope_buffer_to_dict(struct dict *d, const char *ibuf, size_t buflen) +{ + static char lbuf[sizeof(struct envelope)]; + size_t len; + char *buf, *field, *nextline; + + memset(lbuf, 0, sizeof lbuf); + if (strlcpy(lbuf, ibuf, sizeof lbuf) >= sizeof lbuf) + goto err; + buf = lbuf; + + while (buflen > 0) { + len = strcspn(buf, "\n"); + buf[len] = '\0'; + nextline = buf + len + 1; + buflen -= (nextline - buf); + + field = buf; + while (*buf && (isalnum((unsigned char)*buf) || *buf == '-')) + buf++; + if (!*buf) + goto err; + + /* skip whitespaces before separator */ + while (*buf && isspace((unsigned char)*buf)) + *buf++ = 0; + + /* we *want* ':' */ + if (*buf != ':') + goto err; + *buf++ = 0; + + /* skip whitespaces after separator */ + while (*buf && isspace((unsigned char)*buf)) + *buf++ = 0; + dict_set(d, field, buf); + buf = nextline; + } + + return (1); + +err: + return (0); +} + +int +envelope_load_buffer(struct envelope *ep, const char *ibuf, size_t buflen) +{ + struct dict d; + const char *val, *errstr; + long long version; + int ret = 0; + + dict_init(&d); + if (!envelope_buffer_to_dict(&d, ibuf, buflen)) { + log_debug("debug: cannot parse envelope to dict"); + goto end; + } + + val = dict_get(&d, "version"); + if (val == NULL) { + log_debug("debug: envelope version not found"); + goto end; + } + version = strtonum(val, 1, 64, &errstr); + if (errstr) { + log_debug("debug: cannot parse envelope version: %s", val); + goto end; + } + + if (version != SMTPD_ENVELOPE_VERSION) { + log_debug("debug: bad envelope version %lld", version); + goto end; + } + + memset(ep, 0, sizeof *ep); + ret = envelope_ascii_load(ep, &d); + if (ret) + ep->version = SMTPD_ENVELOPE_VERSION; +end: + while (dict_poproot(&d, NULL)) + ; + return (ret); +} + +int +envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) +{ + char *p = dest; + + envelope_ascii_dump(ep, &dest, &len, "version"); + envelope_ascii_dump(ep, &dest, &len, "dispatcher"); + envelope_ascii_dump(ep, &dest, &len, "tag"); + envelope_ascii_dump(ep, &dest, &len, "type"); + envelope_ascii_dump(ep, &dest, &len, "smtpname"); + envelope_ascii_dump(ep, &dest, &len, "helo"); + envelope_ascii_dump(ep, &dest, &len, "hostname"); + envelope_ascii_dump(ep, &dest, &len, "username"); + envelope_ascii_dump(ep, &dest, &len, "errorline"); + envelope_ascii_dump(ep, &dest, &len, "sockaddr"); + envelope_ascii_dump(ep, &dest, &len, "sender"); + envelope_ascii_dump(ep, &dest, &len, "rcpt"); + envelope_ascii_dump(ep, &dest, &len, "dest"); + envelope_ascii_dump(ep, &dest, &len, "ctime"); + envelope_ascii_dump(ep, &dest, &len, "last-try"); + envelope_ascii_dump(ep, &dest, &len, "last-bounce"); + envelope_ascii_dump(ep, &dest, &len, "ttl"); + envelope_ascii_dump(ep, &dest, &len, "retry"); + envelope_ascii_dump(ep, &dest, &len, "flags"); + envelope_ascii_dump(ep, &dest, &len, "dsn-notify"); + envelope_ascii_dump(ep, &dest, &len, "dsn-ret"); + envelope_ascii_dump(ep, &dest, &len, "dsn-envid"); + envelope_ascii_dump(ep, &dest, &len, "dsn-orcpt"); + envelope_ascii_dump(ep, &dest, &len, "esc-class"); + envelope_ascii_dump(ep, &dest, &len, "esc-code"); + + switch (ep->type) { + case D_MDA: + envelope_ascii_dump(ep, &dest, &len, "mda-exec"); + envelope_ascii_dump(ep, &dest, &len, "mda-subaddress"); + envelope_ascii_dump(ep, &dest, &len, "mda-user"); + break; + case D_MTA: + break; + case D_BOUNCE: + envelope_ascii_dump(ep, &dest, &len, "bounce-ttl"); + envelope_ascii_dump(ep, &dest, &len, "bounce-delay"); + envelope_ascii_dump(ep, &dest, &len, "bounce-type"); + break; + default: + return (0); + } + + if (dest == NULL) + return (0); + + return (dest - p); +} + +static int +ascii_load_uint8(uint8_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_uint16(uint16_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xffff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_uint32(uint32_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xffffffff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_time(time_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, LLONG_MAX, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_type(enum delivery_type *dest, char *buf) +{ + if (strcasecmp(buf, "mda") == 0) + *dest = D_MDA; + else if (strcasecmp(buf, "mta") == 0) + *dest = D_MTA; + else if (strcasecmp(buf, "bounce") == 0) + *dest = D_BOUNCE; + else + return 0; + return 1; +} + +static int +ascii_load_string(char *dest, char *buf, size_t len) +{ + if (strlcpy(dest, buf, len) >= len) + return 0; + return 1; +} + +static int +ascii_load_sockaddr(struct sockaddr_storage *ss, char *buf) +{ + struct sockaddr_in6 ssin6; + struct sockaddr_in ssin; + + memset(&ssin, 0, sizeof ssin); + memset(&ssin6, 0, sizeof ssin6); + + if (!strcmp("local", buf)) { + ss->ss_family = AF_LOCAL; + } + else if (strncasecmp("IPv6:", buf, 5) == 0) { + /* XXX - remove this after 6.6 release */ + if (inet_pton(AF_INET6, buf + 5, &ssin6.sin6_addr) != 1) + return 0; + ssin6.sin6_family = AF_INET6; + memcpy(ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in6); +#endif + } + else if (buf[0] == '[' && buf[strlen(buf)-1] == ']') { + buf[strlen(buf)-1] = '\0'; + if (inet_pton(AF_INET6, buf+1, &ssin6.sin6_addr) != 1) + return 0; + ssin6.sin6_family = AF_INET6; + memcpy(ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in6); +#endif + } + else { + if (inet_pton(AF_INET, buf, &ssin.sin_addr) != 1) + return 0; + ssin.sin_family = AF_INET; + memcpy(ss, &ssin, sizeof(ssin)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in); +#endif + } + return 1; +} + +static int +ascii_load_mailaddr(struct mailaddr *dest, char *buf) +{ + if (!text_to_mailaddr(dest, buf)) + return 0; + return 1; +} + +static int +ascii_load_flags(enum envelope_flags *dest, char *buf) +{ + char *flag; + + while ((flag = strsep(&buf, " ,|")) != NULL) { + if (strcasecmp(flag, "authenticated") == 0) + *dest |= EF_AUTHENTICATED; + else if (strcasecmp(flag, "enqueued") == 0) + ; + else if (strcasecmp(flag, "bounce") == 0) + *dest |= EF_BOUNCE; + else if (strcasecmp(flag, "internal") == 0) + *dest |= EF_INTERNAL; + else + return 0; + } + return 1; +} + +static int +ascii_load_bounce_type(enum bounce_type *dest, char *buf) +{ + if (strcasecmp(buf, "error") == 0 || strcasecmp(buf, "failed") == 0) + *dest = B_FAILED; + else if (strcasecmp(buf, "warn") == 0 || + strcasecmp(buf, "delayed") == 0) + *dest = B_DELAYED; + else if (strcasecmp(buf, "dsn") == 0 || + strcasecmp(buf, "delivered") == 0) + *dest = B_DELIVERED; + else + return 0; + return 1; +} + +static int +ascii_load_dsn_ret(enum dsn_ret *ret, char *buf) +{ + if (strcasecmp(buf, "HDRS") == 0) + *ret = DSN_RETHDRS; + else if (strcasecmp(buf, "FULL") == 0) + *ret = DSN_RETFULL; + else + return 0; + return 1; +} + +static int +ascii_load_field(const char *field, struct envelope *ep, char *buf) +{ + if (strcasecmp("dispatcher", field) == 0) + return ascii_load_string(ep->dispatcher, buf, + sizeof ep->dispatcher); + + if (strcasecmp("bounce-delay", field) == 0) + return ascii_load_time(&ep->agent.bounce.delay, buf); + + if (strcasecmp("bounce-ttl", field) == 0) + return ascii_load_time(&ep->agent.bounce.ttl, buf); + + if (strcasecmp("bounce-type", field) == 0) + return ascii_load_bounce_type(&ep->agent.bounce.type, buf); + + if (strcasecmp("ctime", field) == 0) + return ascii_load_time(&ep->creation, buf); + + if (strcasecmp("dest", field) == 0) + return ascii_load_mailaddr(&ep->dest, buf); + + if (strcasecmp("username", field) == 0) + return ascii_load_string(ep->username, buf, sizeof(ep->username)); + + if (strcasecmp("errorline", field) == 0) + return ascii_load_string(ep->errorline, buf, + sizeof ep->errorline); + + if (strcasecmp("ttl", field) == 0) + return ascii_load_time(&ep->ttl, buf); + + if (strcasecmp("flags", field) == 0) + return ascii_load_flags(&ep->flags, buf); + + if (strcasecmp("helo", field) == 0) + return ascii_load_string(ep->helo, buf, sizeof ep->helo); + + if (strcasecmp("hostname", field) == 0) + return ascii_load_string(ep->hostname, buf, + sizeof ep->hostname); + + if (strcasecmp("last-bounce", field) == 0) + return ascii_load_time(&ep->lastbounce, buf); + + if (strcasecmp("last-try", field) == 0) + return ascii_load_time(&ep->lasttry, buf); + + if (strcasecmp("retry", field) == 0) + return ascii_load_uint16(&ep->retry, buf); + + if (strcasecmp("rcpt", field) == 0) + return ascii_load_mailaddr(&ep->rcpt, buf); + + if (strcasecmp("mda-exec", field) == 0) + return ascii_load_string(ep->mda_exec, buf, sizeof(ep->mda_exec)); + + if (strcasecmp("mda-subaddress", field) == 0) + return ascii_load_string(ep->mda_subaddress, buf, sizeof(ep->mda_subaddress)); + + if (strcasecmp("mda-user", field) == 0) + return ascii_load_string(ep->mda_user, buf, sizeof(ep->mda_user)); + + if (strcasecmp("sender", field) == 0) + return ascii_load_mailaddr(&ep->sender, buf); + + if (strcasecmp("smtpname", field) == 0) + return ascii_load_string(ep->smtpname, buf, + sizeof(ep->smtpname)); + + if (strcasecmp("sockaddr", field) == 0) + return ascii_load_sockaddr(&ep->ss, buf); + + if (strcasecmp("tag", field) == 0) + return ascii_load_string(ep->tag, buf, sizeof ep->tag); + + if (strcasecmp("type", field) == 0) + return ascii_load_type(&ep->type, buf); + + if (strcasecmp("version", field) == 0) + return ascii_load_uint32(&ep->version, buf); + + if (strcasecmp("dsn-notify", field) == 0) + return ascii_load_uint8(&ep->dsn_notify, buf); + + if (strcasecmp("dsn-orcpt", field) == 0) + return ascii_load_mailaddr(&ep->dsn_orcpt, buf); + + if (strcasecmp("dsn-ret", field) == 0) + return ascii_load_dsn_ret(&ep->dsn_ret, buf); + + if (strcasecmp("dsn-envid", field) == 0) + return ascii_load_string(ep->dsn_envid, buf, + sizeof(ep->dsn_envid)); + + if (strcasecmp("esc-class", field) == 0) + return ascii_load_uint8(&ep->esc_class, buf); + + if (strcasecmp("esc-code", field) == 0) + return ascii_load_uint8(&ep->esc_code, buf); + + return (0); +} + +static int +envelope_ascii_load(struct envelope *ep, struct dict *d) +{ + const char *field; + char *value; + void *hdl; + + hdl = NULL; + while (dict_iter(d, &hdl, &field, (void **)&value)) + if (!ascii_load_field(field, ep, value)) + goto err; + + return (1); + +err: + log_warnx("envelope: invalid field \"%s\"", field); + return (0); +} + + +static int +ascii_dump_uint8(uint8_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_uint16(uint16_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_uint32(uint32_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_time(time_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%lld", (long long) src); +} + +static int +ascii_dump_string(const char *src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%s", src); +} + +static int +ascii_dump_type(enum delivery_type type, char *dest, size_t len) +{ + char *p = NULL; + + switch (type) { + case D_MDA: + p = "mda"; + break; + case D_MTA: + p = "mta"; + break; + case D_BOUNCE: + p = "bounce"; + break; + default: + return 0; + } + + return bsnprintf(dest, len, "%s", p); +} + +static int +ascii_dump_mailaddr(const struct mailaddr *addr, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%s@%s", + addr->user, addr->domain); +} + +static int +ascii_dump_flags(enum envelope_flags flags, char *buf, size_t len) +{ + size_t cpylen = 0; + + buf[0] = '\0'; + if (flags) { + if (flags & EF_AUTHENTICATED) + cpylen = strlcat(buf, "authenticated", len); + if (flags & EF_BOUNCE) { + if (buf[0] != '\0') + (void)strlcat(buf, " ", len); + cpylen = strlcat(buf, "bounce", len); + } + if (flags & EF_INTERNAL) { + if (buf[0] != '\0') + (void)strlcat(buf, " ", len); + cpylen = strlcat(buf, "internal", len); + } + } + + return cpylen < len ? 1 : 0; +} + +static int +ascii_dump_bounce_type(enum bounce_type type, char *dest, size_t len) +{ + char *p = NULL; + + switch (type) { + case B_FAILED: + p = "failed"; + break; + case B_DELAYED: + p = "delayed"; + break; + case B_DELIVERED: + p = "delivered"; + break; + default: + return 0; + } + return bsnprintf(dest, len, "%s", p); +} + + +static int +ascii_dump_dsn_ret(enum dsn_ret flag, char *dest, size_t len) +{ + size_t cpylen = 0; + + dest[0] = '\0'; + if (flag == DSN_RETFULL) + cpylen = strlcat(dest, "FULL", len); + else if (flag == DSN_RETHDRS) + cpylen = strlcat(dest, "HDRS", len); + + return cpylen < len ? 1 : 0; +} + +static int +ascii_dump_field(const char *field, const struct envelope *ep, + char *buf, size_t len) +{ + if (strcasecmp(field, "dispatcher") == 0) + return ascii_dump_string(ep->dispatcher, buf, len); + + if (strcasecmp(field, "bounce-delay") == 0) { + if (ep->agent.bounce.type != B_DELAYED) + return (1); + return ascii_dump_time(ep->agent.bounce.delay, buf, len); + } + + if (strcasecmp(field, "bounce-ttl") == 0) { + if (ep->agent.bounce.type != B_DELAYED) + return (1); + return ascii_dump_time(ep->agent.bounce.ttl, buf, len); + } + + if (strcasecmp(field, "bounce-type") == 0) + return ascii_dump_bounce_type(ep->agent.bounce.type, buf, len); + + if (strcasecmp(field, "ctime") == 0) + return ascii_dump_time(ep->creation, buf, len); + + if (strcasecmp(field, "dest") == 0) + return ascii_dump_mailaddr(&ep->dest, buf, len); + + if (strcasecmp(field, "username") == 0) { + if (ep->username[0]) + return ascii_dump_string(ep->username, buf, len); + return 1; + } + + if (strcasecmp(field, "errorline") == 0) + return ascii_dump_string(ep->errorline, buf, len); + + if (strcasecmp(field, "ttl") == 0) + return ascii_dump_time(ep->ttl, buf, len); + + if (strcasecmp(field, "flags") == 0) + return ascii_dump_flags(ep->flags, buf, len); + + if (strcasecmp(field, "helo") == 0) + return ascii_dump_string(ep->helo, buf, len); + + if (strcasecmp(field, "hostname") == 0) + return ascii_dump_string(ep->hostname, buf, len); + + if (strcasecmp(field, "last-bounce") == 0) + return ascii_dump_time(ep->lastbounce, buf, len); + + if (strcasecmp(field, "last-try") == 0) + return ascii_dump_time(ep->lasttry, buf, len); + + if (strcasecmp(field, "retry") == 0) + return ascii_dump_uint16(ep->retry, buf, len); + + if (strcasecmp(field, "rcpt") == 0) + return ascii_dump_mailaddr(&ep->rcpt, buf, len); + + if (strcasecmp(field, "mda-exec") == 0) { + if (ep->mda_exec[0]) + return ascii_dump_string(ep->mda_exec, buf, len); + return 1; + } + + if (strcasecmp(field, "mda-subaddress") == 0) { + if (ep->mda_subaddress[0]) + return ascii_dump_string(ep->mda_subaddress, buf, len); + return 1; + } + + if (strcasecmp(field, "mda-user") == 0) { + if (ep->mda_user[0]) + return ascii_dump_string(ep->mda_user, buf, len); + return 1; + } + + if (strcasecmp(field, "sender") == 0) + return ascii_dump_mailaddr(&ep->sender, buf, len); + + if (strcasecmp(field, "smtpname") == 0) + return ascii_dump_string(ep->smtpname, buf, len); + + if (strcasecmp(field, "sockaddr") == 0) + return ascii_dump_string(ss_to_text(&ep->ss), buf, len); + + if (strcasecmp(field, "tag") == 0) + return ascii_dump_string(ep->tag, buf, len); + + if (strcasecmp(field, "type") == 0) + return ascii_dump_type(ep->type, buf, len); + + if (strcasecmp(field, "version") == 0) + return ascii_dump_uint32(SMTPD_ENVELOPE_VERSION, buf, len); + + if (strcasecmp(field, "dsn-notify") == 0) + return ascii_dump_uint8(ep->dsn_notify, buf, len); + + if (strcasecmp(field, "dsn-ret") == 0) + return ascii_dump_dsn_ret(ep->dsn_ret, buf, len); + + if (strcasecmp(field, "dsn-orcpt") == 0) { + if (ep->dsn_orcpt.user[0] && ep->dsn_orcpt.domain[0]) + return ascii_dump_mailaddr(&ep->dsn_orcpt, buf, len); + return 1; + } + + if (strcasecmp(field, "dsn-envid") == 0) + return ascii_dump_string(ep->dsn_envid, buf, len); + + if (strcasecmp(field, "esc-class") == 0) { + if (ep->esc_class) + return ascii_dump_uint8(ep->esc_class, buf, len); + return 1; + } + + if (strcasecmp(field, "esc-code") == 0) { + /* this is not a pasto, we dump esc_code if esc_class is !0 */ + if (ep->esc_class) + return ascii_dump_uint8(ep->esc_code, buf, len); + return 1; + } + + return (0); +} + +static void +envelope_ascii_dump(const struct envelope *ep, char **dest, size_t *len, + const char *field) +{ + char buf[8192]; + int l; + + if (*dest == NULL) + return; + + memset(buf, 0, sizeof buf); + if (!ascii_dump_field(field, ep, buf, sizeof buf)) + goto err; + if (buf[0] == '\0') + return; + + l = snprintf(*dest, *len, "%s: %s\n", field, buf); + if (l < 0 || (size_t) l >= *len) + goto err; + *dest += l; + *len -= l; + + return; +err: + *dest = NULL; +} diff --git a/usr.sbin/smtpd/esc.c b/usr.sbin/smtpd/esc.c new file mode 100644 index 00000000..64a44c79 --- /dev/null +++ b/usr.sbin/smtpd/esc.c @@ -0,0 +1,116 @@ +/* $OpenBSD: esc.c,v 1.5 2016/09/03 22:16:39 gilles Exp $ */ + +/* + * Copyright (c) 2014 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include "smtpd-defines.h" +#include "smtpd-api.h" + +static struct escode { + enum enhanced_status_code code; + const char *description; +} esc[] = { + /* 0.0 */ + { ESC_OTHER_STATUS, "Other/Undefined" }, + + /* 1.x */ + { ESC_OTHER_ADDRESS_STATUS, "Other/Undefined address status" }, + { ESC_BAD_DESTINATION_MAILBOX_ADDRESS, "Bad destination mailbox address" }, + { ESC_BAD_DESTINATION_SYSTEM_ADDRESS, "Bad destination system address" }, + { ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX, "Bad destination mailbox address syntax" }, + { ESC_DESTINATION_MAILBOX_ADDRESS_AMBIGUOUS, "Destination mailbox address ambiguous" }, + { ESC_DESTINATION_ADDRESS_VALID, "Destination address valid" }, + { ESC_DESTINATION_MAILBOX_HAS_MOVED, "Destination mailbox has moved, No forwarding address" }, + { ESC_BAD_SENDER_MAILBOX_ADDRESS_SYNTAX, "Bad sender's mailbox address syntax" }, + { ESC_BAD_SENDER_SYSTEM_ADDRESS, "Bad sender's system address syntax" }, + + /* 2.x */ + { ESC_OTHER_MAILBOX_STATUS, "Other/Undefined mailbox status" }, + { ESC_MAILBOX_DISABLED, "Mailbox disabled, not accepting messages" }, + { ESC_MAILBOX_FULL, "Mailbox full" }, + { ESC_MESSAGE_LENGTH_TOO_LARGE, "Message length exceeds administrative limit" }, + { ESC_MAILING_LIST_EXPANSION_PROBLEM, "Mailing list expansion problem" }, + + /* 3.x */ + { ESC_OTHER_MAIL_SYSTEM_STATUS, "Other/Undefined mail system status" }, + { ESC_MAIL_SYSTEM_FULL, "Mail system full" }, + { ESC_SYSTEM_NOT_ACCEPTING_MESSAGES, "System not accepting network messages" }, + { ESC_SYSTEM_NOT_CAPABLE_OF_SELECTED_FEATURES, "System not capable of selected features" }, + { ESC_MESSAGE_TOO_BIG_FOR_SYSTEM, "Message too big for system" }, + { ESC_SYSTEM_INCORRECTLY_CONFIGURED, "System incorrectly configured" }, + + /* 4.x */ + { ESC_OTHER_NETWORK_ROUTING_STATUS, "Other/Undefined network or routing status" }, + { ESC_NO_ANSWER_FROM_HOST, "No answer from host" }, + { ESC_BAD_CONNECTION, "Bad connection" }, + { ESC_DIRECTORY_SERVER_FAILURE, "Directory server failure" }, + { ESC_UNABLE_TO_ROUTE, "Unable to route" }, + { ESC_MAIL_SYSTEM_CONGESTION, "Mail system congestion" }, + { ESC_ROUTING_LOOP_DETECTED, "Routing loop detected" }, + { ESC_DELIVERY_TIME_EXPIRED, "Delivery time expired" }, + + /* 5.x */ + { ESC_INVALID_RECIPIENT, "Invalid recipient" }, + { ESC_INVALID_COMMAND, "Invalid command" }, + { ESC_SYNTAX_ERROR, "Syntax error" }, + { ESC_TOO_MANY_RECIPIENTS, "Too many recipients" }, + { ESC_INVALID_COMMAND_ARGUMENTS, "Invalid command arguments" }, + { ESC_WRONG_PROTOCOL_VERSION, "Wrong protocol version" }, + + /* 6.x */ + { ESC_OTHER_MEDIA_ERROR, "Other/Undefined media error" }, + { ESC_MEDIA_NOT_SUPPORTED, "Media not supported" }, + { ESC_CONVERSION_REQUIRED_AND_PROHIBITED, "Conversion required and prohibited" }, + { ESC_CONVERSION_REQUIRED_BUT_NOT_SUPPORTED, "Conversion required but not supported" }, + { ESC_CONVERSION_WITH_LOSS_PERFORMED, "Conversion with loss performed" }, + { ESC_CONVERSION_FAILED, "Conversion failed" }, + + /* 7.x */ + { ESC_OTHER_SECURITY_STATUS, "Other/Undefined security status" }, + { ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED, "Delivery not authorized, message refused" }, + { ESC_MAILING_LIST_EXPANSION_PROHIBITED, "Mailing list expansion prohibited" }, + { ESC_SECURITY_CONVERSION_REQUIRED_NOT_POSSIBLE,"Security conversion required but not possible" }, + { ESC_SECURITY_FEATURES_NOT_SUPPORTED, "Security features not supported" }, + { ESC_CRYPTOGRAPHIC_FAILURE, "Cryptographic failure" }, + { ESC_CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED, "Cryptographic algorithm not supported" }, + { ESC_MESSAGE_TOO_BIG_FOR_SYSTEM, "Message integrity failure" }, +}; + +const char * +esc_code(enum enhanced_status_class class, enum enhanced_status_code code) +{ + static char buffer[6]; + + (void)snprintf(buffer, sizeof buffer, "%d.%d.%d", class, code / 10, code % 10); + return buffer; + +} + +const char * +esc_description(enum enhanced_status_code code) +{ + uint32_t i; + + for (i = 0; i < nitems(esc); ++i) + if (code == esc[i].code) + return esc[i].description; + return "Other/Undefined"; +} diff --git a/usr.sbin/smtpd/expand.c b/usr.sbin/smtpd/expand.c new file mode 100644 index 00000000..a4306fc0 --- /dev/null +++ b/usr.sbin/smtpd/expand.c @@ -0,0 +1,332 @@ +/* $OpenBSD: expand.c,v 1.31 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UTIL_H +#include +#endif +#ifdef HAVE_LIBUTIL_H +#include +#endif + +#include "smtpd.h" +#include "log.h" + +static const char *expandnode_info(struct expandnode *); + +struct expandnode * +expand_lookup(struct expand *expand, struct expandnode *key) +{ + return RB_FIND(expandtree, &expand->tree, key); +} + +int +expand_to_text(struct expand *expand, char *buf, size_t sz) +{ + struct expandnode *xn; + + buf[0] = '\0'; + + RB_FOREACH(xn, expandtree, &expand->tree) { + if (buf[0]) + (void)strlcat(buf, ", ", sz); + if (strlcat(buf, expandnode_to_text(xn), sz) >= sz) + return 0; + } + + return 1; +} + +void +expand_insert(struct expand *expand, struct expandnode *node) +{ + struct expandnode *xn; + + node->rule = expand->rule; + node->parent = expand->parent; + + log_trace(TRACE_EXPAND, "expand: %p: expand_insert() called for %s", + expand, expandnode_info(node)); + if (node->type == EXPAND_USERNAME && + expand->parent && + expand->parent->type == EXPAND_USERNAME && + !strcmp(expand->parent->u.user, node->u.user)) { + log_trace(TRACE_EXPAND, "expand: %p: setting sameuser = 1", + expand); + node->sameuser = 1; + } + + if (expand_lookup(expand, node)) { + log_trace(TRACE_EXPAND, "expand: %p: node found, discarding", + expand); + return; + } + + xn = xmemdup(node, sizeof *xn); + xn->rule = expand->rule; + xn->parent = expand->parent; + if (xn->parent) + xn->depth = xn->parent->depth + 1; + else + xn->depth = 0; + RB_INSERT(expandtree, &expand->tree, xn); + if (expand->queue) + TAILQ_INSERT_TAIL(expand->queue, xn, tq_entry); + expand->nb_nodes++; + log_trace(TRACE_EXPAND, "expand: %p: inserted node %p", expand, xn); +} + +void +expand_clear(struct expand *expand) +{ + struct expandnode *xn; + + log_trace(TRACE_EXPAND, "expand: %p: clearing expand tree", expand); + if (expand->queue) + while ((xn = TAILQ_FIRST(expand->queue))) + TAILQ_REMOVE(expand->queue, xn, tq_entry); + + while ((xn = RB_ROOT(&expand->tree)) != NULL) { + RB_REMOVE(expandtree, &expand->tree, xn); + free(xn); + } +} + +void +expand_free(struct expand *expand) +{ + expand_clear(expand); + + log_trace(TRACE_EXPAND, "expand: %p: freeing expand tree", expand); + free(expand); +} + +int +expand_cmp(struct expandnode *e1, struct expandnode *e2) +{ + struct expandnode *p1, *p2; + int r; + + if (e1->type < e2->type) + return -1; + if (e1->type > e2->type) + return 1; + if (e1->sameuser < e2->sameuser) + return -1; + if (e1->sameuser > e2->sameuser) + return 1; + if (e1->realuser < e2->realuser) + return -1; + if (e1->realuser > e2->realuser) + return 1; + + r = memcmp(&e1->u, &e2->u, sizeof(e1->u)); + if (r) + return (r); + + if (e1->parent == e2->parent) + return (0); + + if (e1->parent == NULL) + return (-1); + if (e2->parent == NULL) + return (1); + + /* + * The same node can be expanded in for different dest context. + * Wen need to distinguish between those. + */ + for(p1 = e1->parent; p1->type != EXPAND_ADDRESS; p1 = p1->parent) + ; + for(p2 = e2->parent; p2->type != EXPAND_ADDRESS; p2 = p2->parent) + ; + if (p1 < p2) + return (-1); + if (p1 > p2) + return (1); + + if (e1->type != EXPAND_FILENAME && e1->type != EXPAND_FILTER) + return (0); + + /* + * For external delivery, we need to distinguish between users. + * If we can't find a username, we assume it is _smtpd. + */ + for(p1 = e1->parent; p1 && p1->type != EXPAND_USERNAME; p1 = p1->parent) + ; + for(p2 = e2->parent; p2 && p2->type != EXPAND_USERNAME; p2 = p2->parent) + ; + if (p1 < p2) + return (-1); + if (p1 > p2) + return (1); + + return (0); +} + +static int +expand_line_split(char **line, char **ret) +{ + static char buffer[LINE_MAX]; + int esc, dq, sq; + size_t i; + char *s; + + memset(buffer, 0, sizeof buffer); + esc = dq = sq = 0; + i = 0; + for (s = *line; (*s) && (i < sizeof(buffer)); ++s) { + if (esc) { + buffer[i++] = *s; + esc = 0; + continue; + } + if (*s == '\\') { + esc = 1; + continue; + } + if (*s == ',' && !dq && !sq) { + *ret = buffer; + *line = s+1; + return (1); + } + + buffer[i++] = *s; + esc = 0; + + if (*s == '"' && !sq) + dq ^= 1; + if (*s == '\'' && !dq) + sq ^= 1; + } + + if (esc || dq || sq || i == sizeof(buffer)) + return (-1); + + *ret = buffer; + *line = s; + return (i ? 1 : 0); +} + +int +expand_line(struct expand *expand, const char *s, int do_includes) +{ + struct expandnode xn; + char buffer[LINE_MAX]; + char *p, *subrcpt; + int ret; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + + p = buffer; + while ((ret = expand_line_split(&p, &subrcpt)) > 0) { + subrcpt = strip(subrcpt); + if (subrcpt[0] == '\0') + continue; + if (!text_to_expandnode(&xn, subrcpt)) + return 0; + if (!do_includes) + if (xn.type == EXPAND_INCLUDE) + continue; + expand_insert(expand, &xn); + } + + if (ret >= 0) + return 1; + + /* expand_line_split() returned < 0 */ + return 0; +} + +static const char * +expandnode_info(struct expandnode *e) +{ + static char buffer[1024]; + const char *type = NULL; + const char *value = NULL; + char tmp[64]; + + switch (e->type) { + case EXPAND_FILTER: + type = "filter"; + break; + case EXPAND_FILENAME: + type = "filename"; + break; + case EXPAND_INCLUDE: + type = "include"; + break; + case EXPAND_USERNAME: + type = "username"; + break; + case EXPAND_ADDRESS: + type = "address"; + break; + case EXPAND_ERROR: + type = "error"; + break; + case EXPAND_INVALID: + default: + return NULL; + } + + if ((value = expandnode_to_text(e)) == NULL) + return NULL; + + (void)strlcpy(buffer, type, sizeof buffer); + (void)strlcat(buffer, ":", sizeof buffer); + if (strlcat(buffer, value, sizeof buffer) >= sizeof buffer) + return NULL; + + (void)snprintf(tmp, sizeof(tmp), "[parent=%p", e->parent); + if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) + return NULL; + + (void)snprintf(tmp, sizeof(tmp), ", rule=%p", e->rule); + if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) + return NULL; + + if (e->rule) { + (void)snprintf(tmp, sizeof(tmp), ", dispatcher=%p", e->rule->dispatcher); + if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) + return NULL; + } + + if (strlcat(buffer, "]", sizeof buffer) >= sizeof buffer) + return NULL; + + return buffer; +} + +RB_GENERATE(expandtree, expandnode, entry, expand_cmp); diff --git a/usr.sbin/smtpd/filter.c b/usr.sbin/smtpd/filter.c new file mode 100644 index 00000000..614486b7 --- /dev/null +++ b/usr.sbin/smtpd/filter.c @@ -0,0 +1,868 @@ +/* $OpenBSD: filter.c,v 1.25 2017/01/09 09:53:23 reyk Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +enum { + QUERY_READY, + QUERY_RUNNING, + QUERY_DONE +}; + + +struct filter_proc { + TAILQ_ENTRY(filter_proc) entry; + struct mproc mproc; + int hooks; + int flags; + int ready; +}; + +struct filter { + TAILQ_ENTRY(filter) entry; + struct filter_proc *proc; +}; +TAILQ_HEAD(filter_lst, filter); + +TAILQ_HEAD(filter_query_lst, filter_query); +struct filter_session { + uint64_t id; + int terminate; + struct filter_lst *filters; + struct filter *fcurr; + + int error; + struct io *iev; + size_t idatalen; + FILE *ofile; + + struct filter_query *eom; +}; + +struct filter_query { + uint64_t qid; + int type; + struct filter_session *session; + + int state; + struct filter *current; + + /* current data */ + union { + struct { + struct sockaddr_storage local; + struct sockaddr_storage remote; + char hostname[HOST_NAME_MAX+1]; + } connect; + char line[LINE_MAX]; + struct mailaddr maddr; + size_t datalen; + } u; + + /* current response */ + struct { + int status; + int code; + char *response; + } smtp; +}; + +static void filter_imsg(struct mproc *, struct imsg *); +static void filter_post_event(uint64_t, int, struct filter *, struct filter *); +static struct filter_query *filter_query(struct filter_session *, int); +static void filter_drain_query(struct filter_query *); +static void filter_run_query(struct filter *, struct filter_query *); +static void filter_end_query(struct filter_query *); +static void filter_set_sink(struct filter_session *, int); +static int filter_tx(struct filter_session *, int); +static void filter_tx_io(struct io *, int, void *); + +static TAILQ_HEAD(, filter_proc) procs; +struct dict chains; + +static const char * filter_session_to_text(struct filter_session *); +static const char * filter_query_to_text(struct filter_query *); +static const char * filter_to_text(struct filter *); +static const char * filter_proc_to_text(struct filter_proc *); +static const char * query_to_str(int); +static const char * event_to_str(int); +static const char * status_to_str(int); +static const char * filterimsg_to_str(int); + +struct tree sessions; +struct tree queries; + +static void +filter_add_arg(struct filter_conf *filter, char *arg) +{ + if (filter->argc == MAX_FILTER_ARGS) { + log_warnx("warn: filter \"%s\" is full", filter->name); + fatalx("exiting"); + } + filter->argv[filter->argc++] = arg; +} + +static void +filter_extend_chain(struct filter_lst *chain, const char *name) +{ + struct filter *n; + struct filter_lst *fchain; + struct filter_conf *fconf; + int i; + + fconf = dict_xget(&env->sc_filters, name); + if (fconf->chain) { + log_debug("filter: extending with \"%s\"", name); + for (i = 0; i < fconf->argc; i++) + filter_extend_chain(chain, fconf->argv[i]); + } + else { + log_debug("filter: adding filter \"%s\"", name); + n = xcalloc(1, sizeof(*n), "filter_extend_chain"); + fchain = dict_get(&chains, name); + n->proc = TAILQ_FIRST(fchain)->proc; + TAILQ_INSERT_TAIL(chain, n, entry); + } +} + +void +filter_postfork(void) +{ + static int prepare = 0; + struct filter_conf *filter; + void *iter; + struct filter_proc *proc; + struct filter_lst *fchain; + struct filter *f; + struct mproc *p; + int done, i; + + if (prepare) + return; + prepare = 1; + + TAILQ_INIT(&procs); + dict_init(&chains); + + log_debug("filter: building simple chains..."); + + /* create all filter proc and associated chains */ + iter = NULL; + while (dict_iter(&env->sc_filters, &iter, NULL, (void **)&filter)) { + if (filter->chain) + continue; + + log_debug("filter: building simple chain \"%s\"", filter->name); + proc = xcalloc(1, sizeof(*proc), "filter_postfork"); + p = &proc->mproc; + p->handler = filter_imsg; + p->proc = PROC_FILTER; + p->name = xstrdup(filter->name, "filter_postfork"); + p->data = proc; + if (tracing & TRACE_DEBUG) + filter_add_arg(filter, "-v"); + if (foreground_log) + filter_add_arg(filter, "-d"); + if (mproc_fork(p, filter->path, filter->argv) < 0) + fatalx("filter_postfork"); + + log_debug("filter: registering proc \"%s\"", filter->name); + f = xcalloc(1, sizeof(*f), "filter_postfork"); + f->proc = proc; + + TAILQ_INSERT_TAIL(&procs, proc, entry); + fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); + TAILQ_INIT(fchain); + TAILQ_INSERT_TAIL(fchain, f, entry); + dict_xset(&chains, filter->name, fchain); + filter->done = 1; + } + + log_debug("filter: building complex chains..."); + + /* resolve all chains */ + done = 0; + while (!done) { + done = 1; + iter = NULL; + while (dict_iter(&env->sc_filters, &iter, NULL, + (void **)&filter)) { + if (filter->done) + continue; + done = 0; + filter->done = 1; + for (i = 0; i < filter->argc; i++) { + if (!dict_get(&chains, filter->argv[i])) { + filter->done = 0; + break; + } + } + if (filter->done == 0) + continue; + fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); + TAILQ_INIT(fchain); + log_debug("filter: building chain \"%s\"...", + filter->name); + for (i = 0; i < filter->argc; i++) + filter_extend_chain(fchain, filter->argv[i]); + log_debug("filter: done building chain \"%s\"", + filter->name); + dict_xset(&chains, filter->name, fchain); + } + } + log_debug("filter: done building complex chains"); + + fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); + TAILQ_INIT(fchain); + dict_xset(&chains, "", fchain); +} + +void +filter_configure(void) +{ + static int init = 0; + struct filter_proc *p; + + if (init) + return; + init = 1; + + tree_init(&sessions); + tree_init(&queries); + + TAILQ_FOREACH(p, &procs, entry) { + m_create(&p->mproc, IMSG_FILTER_REGISTER, 0, 0, -1); + m_add_u32(&p->mproc, FILTER_API_VERSION); + m_add_string(&p->mproc, p->mproc.name); + m_close(&p->mproc); + mproc_enable(&p->mproc); + } + + if (TAILQ_FIRST(&procs) == NULL) + smtp_configure(); +} + +void +filter_event(uint64_t id, int event) +{ + struct filter_session *s; + + if (event == EVENT_DISCONNECT) + /* On disconnect, the session is virtualy dead */ + s = tree_xpop(&sessions, id); + else + s = tree_xget(&sessions, id); + + filter_post_event(id, event, TAILQ_FIRST(s->filters), NULL); + + if (event == EVENT_DISCONNECT) { + if (s->iev) + io_free(s->iev); + if (s->ofile) + fclose(s->ofile); + free(s); + } +} + +void +filter_connect(uint64_t id, const struct sockaddr *local, + const struct sockaddr *remote, const char *host, const char *filter) +{ + struct filter_session *s; + struct filter_query *q; + + s = xcalloc(1, sizeof(*s), "filter_event"); + s->id = id; + if (filter == NULL) + filter = ""; + s->filters = dict_xget(&chains, filter); + tree_xset(&sessions, s->id, s); + + filter_event(id, EVENT_CONNECT); + q = filter_query(s, QUERY_CONNECT); + + memmove(&q->u.connect.local, local, SA_LEN(local)); + memmove(&q->u.connect.remote, remote, SA_LEN(remote)); + strlcpy(q->u.connect.hostname, host, sizeof(q->u.connect.hostname)); + + q->smtp.status = FILTER_OK; + q->smtp.code = 0; + q->smtp.response = NULL; + + filter_drain_query(q); +} + +void +filter_mailaddr(uint64_t id, int type, const struct mailaddr *maddr) +{ + struct filter_session *s; + struct filter_query *q; + + s = tree_xget(&sessions, id); + q = filter_query(s, type); + + strlcpy(q->u.maddr.user, maddr->user, sizeof(q->u.maddr.user)); + strlcpy(q->u.maddr.domain, maddr->domain, sizeof(q->u.maddr.domain)); + + filter_drain_query(q); +} + +void +filter_line(uint64_t id, int type, const char *line) +{ + struct filter_session *s; + struct filter_query *q; + + s = tree_xget(&sessions, id); + q = filter_query(s, type); + + if (line) + strlcpy(q->u.line, line, sizeof(q->u.line)); + + filter_drain_query(q); +} + +void +filter_eom(uint64_t id, int type, size_t datalen) +{ + struct filter_session *s; + struct filter_query *q; + + s = tree_xget(&sessions, id); + q = filter_query(s, type); + q->u.datalen = datalen; + + filter_drain_query(q); +} + +static void +filter_set_sink(struct filter_session *s, int sink) +{ + struct mproc *p; + + while (s->fcurr) { + if (s->fcurr->proc->hooks & HOOK_DATALINE) { + log_trace(TRACE_FILTERS, "filter: sending fd %d to %s", + sink, filter_to_text(s->fcurr)); + p = &s->fcurr->proc->mproc; + m_create(p, IMSG_FILTER_PIPE, 0, 0, sink); + m_add_id(p, s->id); + m_close(p); + return; + } + s->fcurr = TAILQ_PREV(s->fcurr, filter_lst, entry); + } + + log_trace(TRACE_FILTERS, "filter: chain input is %d", sink); + smtp_filter_fd(s->id, sink); +} + +void +filter_build_fd_chain(uint64_t id, int sink) +{ + struct filter_session *s; + int fd; + + s = tree_xget(&sessions, id); + s->fcurr = TAILQ_LAST(s->filters, filter_lst); + + fd = filter_tx(s, sink); + filter_set_sink(s, fd); +} + +void +filter_post_event(uint64_t id, int event, struct filter *f, struct filter *end) +{ + for(; f && f != end; f = TAILQ_NEXT(f, entry)) { + log_trace(TRACE_FILTERS, "filter: post-event event=%s filter=%s", + event_to_str(event), f->proc->mproc.name); + + m_create(&f->proc->mproc, IMSG_FILTER_EVENT, 0, 0, -1); + m_add_id(&f->proc->mproc, id); + m_add_int(&f->proc->mproc, event); + m_close(&f->proc->mproc); + } +} + +static struct filter_query * +filter_query(struct filter_session *s, int type) +{ + struct filter_query *q; + + q = xcalloc(1, sizeof(*q), "filter_query"); + q->qid = generate_uid(); + q->session = s; + q->type = type; + + q->state = QUERY_READY; + q->current = TAILQ_FIRST(s->filters); + + log_trace(TRACE_FILTERS, "filter: new query %s", query_to_str(type)); + + return (q); +} + +static void +filter_drain_query(struct filter_query *q) +{ + log_trace(TRACE_FILTERS, "filter: filter_drain_query %s", + filter_query_to_text(q)); + + /* + * The query must be passed through all filters that registered + * a hook, until one rejects it. + */ + while (q->state != QUERY_DONE) { + /* Walk over all filters */ + while (q->current) { + filter_run_query(q->current, q); + if (q->state == QUERY_RUNNING) { + log_trace(TRACE_FILTERS, + "filter: waiting for running query %s", + filter_query_to_text(q)); + return; + } + } + q->state = QUERY_DONE; + } + + /* Defer the response if the file is not closed yet. */ + if (q->type == QUERY_EOM && q->session->ofile && q->smtp.status == FILTER_OK) { + log_debug("filter: deferring eom query..."); + q->session->eom = q; + return; + } + + filter_end_query(q); +} + +static void +filter_run_query(struct filter *f, struct filter_query *q) +{ + log_trace(TRACE_FILTERS, + "filter: running filter %s for query %s", + filter_to_text(f), filter_query_to_text(q)); + + m_create(&f->proc->mproc, IMSG_FILTER_QUERY, 0, 0, -1); + m_add_id(&f->proc->mproc, q->session->id); + m_add_id(&f->proc->mproc, q->qid); + m_add_int(&f->proc->mproc, q->type); + + switch (q->type) { + case QUERY_CONNECT: + m_add_sockaddr(&f->proc->mproc, + (struct sockaddr *)&q->u.connect.local); + m_add_sockaddr(&f->proc->mproc, + (struct sockaddr *)&q->u.connect.remote); + m_add_string(&f->proc->mproc, q->u.connect.hostname); + break; + case QUERY_HELO: + m_add_string(&f->proc->mproc, q->u.line); + break; + case QUERY_MAIL: + case QUERY_RCPT: + m_add_mailaddr(&f->proc->mproc, &q->u.maddr); + break; + case QUERY_EOM: + m_add_u32(&f->proc->mproc, q->u.datalen); + break; + default: + break; + } + m_close(&f->proc->mproc); + + tree_xset(&queries, q->qid, q); + q->state = QUERY_RUNNING; +} + +static void +filter_end_query(struct filter_query *q) +{ + struct filter_session *s = q->session; + const char *response = q->smtp.response; + + log_trace(TRACE_FILTERS, "filter: filter_end_query %s", + filter_query_to_text(q)); + + if (q->type == QUERY_EOM && q->smtp.status == FILTER_OK) { + if (s->error || q->u.datalen != s->idatalen) { + response = "Internal error"; + q->smtp.code = 451; + q->smtp.status = FILTER_FAIL; + if (!s->error) + log_warnx("filter: datalen mismatch on session %" PRIx64 + ": %zu/%zu", s->id, s->idatalen, q->u.datalen); + } + } + + log_trace(TRACE_FILTERS, + "filter: query %016"PRIx64" done: " + "status=%s code=%d response=\"%s\"", + q->qid, + status_to_str(q->smtp.status), + q->smtp.code, + response); + + smtp_filter_response(s->id, q->type, q->smtp.status, q->smtp.code, + response); + free(q->smtp.response); + free(q); +} + +static void +filter_imsg(struct mproc *p, struct imsg *imsg) +{ + struct filter_proc *proc = p->data; + struct filter_session *s; + struct filter_query *q; + struct msg m; + const char *line; + uint64_t qid; + uint32_t datalen; + int type, status, code; + + if (imsg == NULL) { + log_warnx("warn: filter \"%s\" closed unexpectedly", p->name); + fatalx("exiting"); + } + + log_trace(TRACE_FILTERS, "filter: imsg %s from procfilter %s", + filterimsg_to_str(imsg->hdr.type), + filter_proc_to_text(proc)); + + switch (imsg->hdr.type) { + + case IMSG_FILTER_REGISTER: + if (proc->ready) { + log_warnx("warn: filter \"%s\" already registered", + proc->mproc.name); + exit(1); + } + + m_msg(&m, imsg); + m_get_int(&m, &proc->hooks); + m_get_int(&m, &proc->flags); + m_end(&m); + proc->ready = 1; + + log_debug("debug: filter \"%s\": hooks 0x%08x flags 0x%04x", + proc->mproc.name, proc->hooks, proc->flags); + + TAILQ_FOREACH(proc, &procs, entry) + if (!proc->ready) + return; + + smtp_configure(); + break; + + case IMSG_FILTER_RESPONSE: + m_msg(&m, imsg); + m_get_id(&m, &qid); + m_get_int(&m, &type); + if (type == QUERY_EOM) + m_get_u32(&m, &datalen); + m_get_int(&m, &status); + m_get_int(&m, &code); + if (m_is_eom(&m)) + line = NULL; + else + m_get_string(&m, &line); + m_end(&m); + + q = tree_xpop(&queries, qid); + if (q->type != type) { + log_warnx("warn: filter: type mismatch %d != %d", + q->type, type); + fatalx("exiting"); + } + q->smtp.status = status; + if (code) + q->smtp.code = code; + if (line) { + free(q->smtp.response); + q->smtp.response = xstrdup(line, "filter_imsg"); + } + q->state = (status == FILTER_OK) ? QUERY_READY : QUERY_DONE; + if (type == QUERY_EOM) + q->u.datalen = datalen; + + q->current = TAILQ_NEXT(q->current, entry); + filter_drain_query(q); + break; + + case IMSG_FILTER_PIPE: + m_msg(&m, imsg); + m_get_id(&m, &qid); + m_end(&m); + + s = tree_xget(&sessions, qid); + s->fcurr = TAILQ_PREV(s->fcurr, filter_lst, entry); + filter_set_sink(s, imsg->fd); + break; + + default: + log_warnx("warn: bad imsg from filter %s", p->name); + exit(1); + } +} + +static int +filter_tx(struct filter_session *s, int sink) +{ + int sp[2]; + + s->idatalen = 0; + s->eom = NULL; + s->error = 0; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) { + log_warn("warn: filter: socketpair"); + return (-1); + } + + if ((s->ofile = fdopen(sink, "w")) == NULL) { + log_warn("warn: filter: fdopen"); + close(sp[0]); + close(sp[1]); + return (-1); + } + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + s->iev = io_new(); + io_set_callback(s->iev, filter_tx_io, s); + io_set_fd(s->iev, sp[0]); + io_set_read(s->iev); + + return (sp[1]); +} + +static void +filter_tx_io(struct io *io, int evt, void *arg) +{ + struct filter_session *s = arg; + size_t len, n; + char *data; + + log_trace(TRACE_FILTERS, "filter: filter_tx_io(%p, %s)", + s, io_strevent(evt)); + + switch (evt) { + case IO_DATAIN: + data = io_data(s->iev); + len = io_datalen(s->iev); + + log_trace(TRACE_FILTERS, + "filter: filter_tx_io: datain (%zu) for req %016"PRIx64"", + len, s->id); + + n = fwrite(data, 1, len, s->ofile); + if (n != len) { + log_warnx("warn: filter_tx_io: fwrite %zu/%zu", n, len); + s->error = 1; + break; + } + s->idatalen += n; + io_drop(s->iev, n); + return; + + case IO_DISCONNECTED: + log_trace(TRACE_FILTERS, + "debug: filter: tx done (%zu) for req %016"PRIx64, + s->idatalen, s->id); + break; + + default: + log_warn("warn: filter_tx_io: bad evt (%d) for req %016"PRIx64, + evt, s->id); + s->error = 1; + break; + } + + io_free(s->iev); + s->iev = NULL; + fclose(s->ofile); + s->ofile = NULL; + + /* deferred eom request */ + if (s->eom) { + log_debug("filter: running eom query..."); + filter_end_query(s->eom); + } else { + log_debug("filter: eom not received yet"); + } +} + +static const char * +filter_query_to_text(struct filter_query *q) +{ + static char buf[1024]; + char tmp[1024]; + + tmp[0] = '\0'; + + switch (q->type) { + case QUERY_CONNECT: + strlcat(tmp, "=", sizeof tmp); + strlcat(tmp, ss_to_text(&q->u.connect.local), + sizeof tmp); + strlcat(tmp, " <-> ", sizeof tmp); + strlcat(tmp, ss_to_text(&q->u.connect.remote), + sizeof tmp); + strlcat(tmp, "(", sizeof tmp); + strlcat(tmp, q->u.connect.hostname, sizeof tmp); + strlcat(tmp, ")", sizeof tmp); + break; + case QUERY_MAIL: + case QUERY_RCPT: + snprintf(tmp, sizeof tmp, "=%s@%s", + q->u.maddr.user, q->u.maddr.domain); + break; + case QUERY_HELO: + snprintf(tmp, sizeof tmp, "=%s", q->u.line); + break; + default: + break; + } + snprintf(buf, sizeof buf, "%016"PRIx64"[%s%s,%s]", + q->qid, query_to_str(q->type), tmp, + filter_session_to_text(q->session)); + + return (buf); +} + +static const char * +filter_session_to_text(struct filter_session *s) +{ + static char buf[1024]; + + if (s == NULL) + return "filter_session@NULL"; + + snprintf(buf, sizeof(buf), + "filter_session@%p[datalen=%zu,eom=%p,ofile=%p]", + s, s->idatalen, s->eom, s->ofile); + + return buf; +} + +static const char * +filter_to_text(struct filter *f) +{ + static char buf[1024]; + + snprintf(buf, sizeof buf, "filter:%s", filter_proc_to_text(f->proc)); + + return (buf); +} + +static const char * +filter_proc_to_text(struct filter_proc *proc) +{ + static char buf[1024]; + + snprintf(buf, sizeof buf, "%s[hooks=0x%08x,flags=0x%04x]", + proc->mproc.name, proc->hooks, proc->flags); + + return (buf); +} + +#define CASE(x) case x : return #x + +static const char * +filterimsg_to_str(int imsg) +{ + switch (imsg) { + CASE(IMSG_FILTER_REGISTER); + CASE(IMSG_FILTER_EVENT); + CASE(IMSG_FILTER_QUERY); + CASE(IMSG_FILTER_PIPE); + CASE(IMSG_FILTER_RESPONSE); + default: + return "IMSG_FILTER_???"; + } +} + +static const char * +query_to_str(int query) +{ + switch (query) { + CASE(QUERY_CONNECT); + CASE(QUERY_HELO); + CASE(QUERY_MAIL); + CASE(QUERY_RCPT); + CASE(QUERY_DATA); + CASE(QUERY_EOM); + CASE(QUERY_DATALINE); + default: + return "QUERY_???"; + } +} + +static const char * +event_to_str(int event) +{ + switch (event) { + CASE(EVENT_CONNECT); + CASE(EVENT_RESET); + CASE(EVENT_DISCONNECT); + CASE(EVENT_TX_BEGIN); + CASE(EVENT_TX_COMMIT); + CASE(EVENT_TX_ROLLBACK); + default: + return "EVENT_???"; + } +} + +static const char * +status_to_str(int status) +{ + switch (status) { + CASE(FILTER_OK); + CASE(FILTER_FAIL); + CASE(FILTER_CLOSE); + default: + return "FILTER_???"; + } +} diff --git a/usr.sbin/smtpd/forward.5 b/usr.sbin/smtpd/forward.5 new file mode 100644 index 00000000..5a68f229 --- /dev/null +++ b/usr.sbin/smtpd/forward.5 @@ -0,0 +1,83 @@ +.\" $OpenBSD: forward.5,v 1.9 2015/03/13 22:41:54 eric Exp $ +.\" +.\" Copyright (c) 2012 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 13 2015 $ +.Dt FORWARD 5 +.Os +.Sh NAME +.Nm forward +.Nd email forwarding information file +.Sh DESCRIPTION +Users may put a +.Nm .forward +file in their home directory. +If this file exists, +.Xr smtpd 8 +forwards email to the destinations specified therein. +.Pp +A +.Nm .forward +file contains a list of expansion values, as described in +.Xr aliases 5 . +Each expansion value should be on a line by itself. +However, the +.Nm .forward +mechanism differs from the aliases mechanism in that it disallows +file inclusion +.Pq :include: +and it performs expansion under the user ID of the +.Nm .forward +file owner. +.Pp +Permissions on the +.Nm .forward +file are very strict and expansion is rejected if the file is +group or world-writable; +if the home directory is group writeable; +or if the file is not owned by the user. +.Pp +Users should avoid editing directly the +.Nm .forward +file to prevent delivery failures from occurring if a message +arrives while the file is not fully written. +The best option is to use a temporary file and use the +.Xr mv 1 +command to atomically overwrite the former +.Nm .forward . +Alternatively, setting the +.Xr sticky 8 +bit on the home directory will cause the +.Nm .forward +lookup to return a temporary failure, causing mails to be deferred. +.Sh FILES +.Bl -tag -width "~/.forwardXXX" -compact +.It Pa ~/.forward +Email forwarding information. +.El +.Sh EXAMPLES +The following file forwards mail to +.Dq user@example.com , +and pipes the same mail to +.Dq examplemda . +.Bd -literal -offset indent +# empty lines are ignored + +user@example.com # anything after # is ignored +"|/path/to/examplemda" +.Ed +.Sh SEE ALSO +.Xr aliases 5 , +.Xr smtpd 8 diff --git a/usr.sbin/smtpd/forward.c b/usr.sbin/smtpd/forward.c new file mode 100644 index 00000000..7494c6ce --- /dev/null +++ b/usr.sbin/smtpd/forward.c @@ -0,0 +1,104 @@ +/* $OpenBSD: forward.c,v 1.39 2015/12/28 22:08:30 jung Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UTIL_H +#include +#endif +#ifdef HAVE_LIBUTIL_H +#include +#endif +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define MAX_FORWARD_SIZE (4 * 1024) +#define MAX_EXPAND_NODES (100) + +int +forwards_get(int fd, struct expand *expand) +{ + FILE *fp = NULL; + char *line = NULL; + size_t len; + size_t lineno; + size_t save; + int ret; + struct stat sb; + + ret = -1; + if (fstat(fd, &sb) == -1) + goto end; + + /* if it's empty just pretend that no expansion took place */ + if (sb.st_size == 0) { + log_info("info: forward file is empty"); + ret = 0; + goto end; + } + + /* over MAX_FORWARD_SIZE, temporarily fail */ + if (sb.st_size >= MAX_FORWARD_SIZE) { + log_info("info: forward file exceeds max size"); + goto end; + } + + if ((fp = fdopen(fd, "r")) == NULL) { + log_warn("warn: fdopen failure in forwards_get()"); + goto end; + } + + lineno = 0; + save = expand->nb_nodes; + while ((line = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { + if (!expand_line(expand, line, 0)) { + log_info("info: parse error in forward file"); + goto end; + } + if (expand->nb_nodes > MAX_EXPAND_NODES) { + log_info("info: forward file expanded too many nodes"); + goto end; + } + free(line); + } + + ret = expand->nb_nodes > save ? 1 : 0; + +end: + free(line); + if (fp) + fclose(fp); + else + close(fd); + return ret; +} diff --git a/usr.sbin/smtpd/iobuf.c b/usr.sbin/smtpd/iobuf.c new file mode 100644 index 00000000..dec10660 --- /dev/null +++ b/usr.sbin/smtpd/iobuf.c @@ -0,0 +1,462 @@ +/* $OpenBSD: iobuf.c,v 1.13 2020/04/24 11:34:07 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef IO_TLS +#include +#include +#endif + +#include "iobuf.h" + +#define IOBUF_MAX 65536 +#define IOBUFQ_MIN 4096 + +struct ioqbuf *ioqbuf_alloc(struct iobuf *, size_t); +void iobuf_drain(struct iobuf *, size_t); + +int +iobuf_init(struct iobuf *io, size_t size, size_t max) +{ + memset(io, 0, sizeof *io); + + if (max == 0) + max = IOBUF_MAX; + + if (size == 0) + size = max; + + if (size > max) + return (-1); + + if ((io->buf = calloc(size, 1)) == NULL) + return (-1); + + io->size = size; + io->max = max; + + return (0); +} + +void +iobuf_clear(struct iobuf *io) +{ + struct ioqbuf *q; + + free(io->buf); + + while ((q = io->outq)) { + io->outq = q->next; + free(q); + } + + memset(io, 0, sizeof (*io)); +} + +void +iobuf_drain(struct iobuf *io, size_t n) +{ + struct ioqbuf *q; + size_t left = n; + + while ((q = io->outq) && left) { + if ((q->wpos - q->rpos) > left) { + q->rpos += left; + left = 0; + } else { + left -= q->wpos - q->rpos; + io->outq = q->next; + free(q); + } + } + + io->queued -= (n - left); + if (io->outq == NULL) + io->outqlast = NULL; +} + +int +iobuf_extend(struct iobuf *io, size_t n) +{ + char *t; + + if (n > io->max) + return (-1); + + if (io->max - io->size < n) + return (-1); + + t = recallocarray(io->buf, io->size, io->size + n, 1); + if (t == NULL) + return (-1); + + io->size += n; + io->buf = t; + + return (0); +} + +size_t +iobuf_left(struct iobuf *io) +{ + return io->size - io->wpos; +} + +size_t +iobuf_space(struct iobuf *io) +{ + return io->size - (io->wpos - io->rpos); +} + +size_t +iobuf_len(struct iobuf *io) +{ + return io->wpos - io->rpos; +} + +char * +iobuf_data(struct iobuf *io) +{ + return io->buf + io->rpos; +} + +void +iobuf_drop(struct iobuf *io, size_t n) +{ + if (n >= iobuf_len(io)) { + io->rpos = io->wpos = 0; + return; + } + + io->rpos += n; +} + +char * +iobuf_getline(struct iobuf *iobuf, size_t *rlen) +{ + char *buf; + size_t len, i; + + buf = iobuf_data(iobuf); + len = iobuf_len(iobuf); + + for (i = 0; i + 1 <= len; i++) + if (buf[i] == '\n') { + /* Note: the returned address points into the iobuf + * buffer. We NUL-end it for convenience, and discard + * the data from the iobuf, so that the caller doesn't + * have to do it. The data remains "valid" as long + * as the iobuf does not overwrite it, that is until + * the next call to iobuf_normalize() or iobuf_extend(). + */ + iobuf_drop(iobuf, i + 1); + buf[i] = '\0'; + if (rlen) + *rlen = i; + return (buf); + } + + return (NULL); +} + +void +iobuf_normalize(struct iobuf *io) +{ + if (io->rpos == 0) + return; + + if (io->rpos == io->wpos) { + io->rpos = io->wpos = 0; + return; + } + + memmove(io->buf, io->buf + io->rpos, io->wpos - io->rpos); + io->wpos -= io->rpos; + io->rpos = 0; +} + +ssize_t +iobuf_read(struct iobuf *io, int fd) +{ + ssize_t n; + + n = read(fd, io->buf + io->wpos, iobuf_left(io)); + if (n == -1) { + /* XXX is this really what we want? */ + if (errno == EAGAIN || errno == EINTR) + return (IOBUF_WANT_READ); + return (IOBUF_ERROR); + } + if (n == 0) + return (IOBUF_CLOSED); + + io->wpos += n; + + return (n); +} + +struct ioqbuf * +ioqbuf_alloc(struct iobuf *io, size_t len) +{ + struct ioqbuf *q; + + if (len < IOBUFQ_MIN) + len = IOBUFQ_MIN; + + if ((q = malloc(sizeof(*q) + len)) == NULL) + return (NULL); + + q->rpos = 0; + q->wpos = 0; + q->size = len; + q->next = NULL; + q->buf = (char *)(q) + sizeof(*q); + + if (io->outqlast == NULL) + io->outq = q; + else + io->outqlast->next = q; + io->outqlast = q; + + return (q); +} + +size_t +iobuf_queued(struct iobuf *io) +{ + return io->queued; +} + +void * +iobuf_reserve(struct iobuf *io, size_t len) +{ + struct ioqbuf *q; + void *r; + + if (len == 0) + return (NULL); + + if (((q = io->outqlast) == NULL) || q->size - q->wpos <= len) { + if ((q = ioqbuf_alloc(io, len)) == NULL) + return (NULL); + } + + r = q->buf + q->wpos; + q->wpos += len; + io->queued += len; + + return (r); +} + +int +iobuf_queue(struct iobuf *io, const void *data, size_t len) +{ + void *buf; + + if (len == 0) + return (0); + + if ((buf = iobuf_reserve(io, len)) == NULL) + return (-1); + + memmove(buf, data, len); + + return (len); +} + +int +iobuf_queuev(struct iobuf *io, const struct iovec *iov, int iovcnt) +{ + int i; + size_t len = 0; + char *buf; + + for (i = 0; i < iovcnt; i++) + len += iov[i].iov_len; + + if ((buf = iobuf_reserve(io, len)) == NULL) + return (-1); + + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) + continue; + memmove(buf, iov[i].iov_base, iov[i].iov_len); + buf += iov[i].iov_len; + } + + return (0); + +} + +int +iobuf_fqueue(struct iobuf *io, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = iobuf_vfqueue(io, fmt, ap); + va_end(ap); + + return (len); +} + +int +iobuf_vfqueue(struct iobuf *io, const char *fmt, va_list ap) +{ + char *buf; + int len; + + len = vasprintf(&buf, fmt, ap); + + if (len == -1) + return (-1); + + len = iobuf_queue(io, buf, len); + free(buf); + + return (len); +} + +ssize_t +iobuf_write(struct iobuf *io, int fd) +{ + struct iovec iov[IOV_MAX]; + struct ioqbuf *q; + int i; + ssize_t n; + + i = 0; + for (q = io->outq; q ; q = q->next) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = q->buf + q->rpos; + iov[i].iov_len = q->wpos - q->rpos; + i++; + } + + n = writev(fd, iov, i); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) + return (IOBUF_WANT_WRITE); + if (errno == EPIPE) + return (IOBUF_CLOSED); + return (IOBUF_ERROR); + } + + iobuf_drain(io, n); + + return (n); +} + +int +iobuf_flush(struct iobuf *io, int fd) +{ + ssize_t s; + + while (io->queued) + if ((s = iobuf_write(io, fd)) < 0) + return (s); + + return (0); +} + +#ifdef IO_TLS + +int +iobuf_flush_tls(struct iobuf *io, void *tls) +{ + ssize_t s; + + while (io->queued) + if ((s = iobuf_write_tls(io, tls)) < 0) + return (s); + + return (0); +} + +ssize_t +iobuf_write_tls(struct iobuf *io, void *tls) +{ + struct ioqbuf *q; + int r; + ssize_t n; + + q = io->outq; + n = SSL_write(tls, q->buf + q->rpos, q->wpos - q->rpos); + if (n <= 0) { + switch ((r = SSL_get_error(tls, n))) { + case SSL_ERROR_WANT_READ: + return (IOBUF_WANT_READ); + case SSL_ERROR_WANT_WRITE: + return (IOBUF_WANT_WRITE); + case SSL_ERROR_ZERO_RETURN: /* connection closed */ + return (IOBUF_CLOSED); + case SSL_ERROR_SYSCALL: + if (ERR_peek_last_error()) + return (IOBUF_TLSERROR); + return (IOBUF_ERROR); + default: + return (IOBUF_TLSERROR); + } + } + iobuf_drain(io, n); + + return (n); +} + +ssize_t +iobuf_read_tls(struct iobuf *io, void *tls) +{ + ssize_t n; + int r; + + n = SSL_read(tls, io->buf + io->wpos, iobuf_left(io)); + if (n < 0) { + switch ((r = SSL_get_error(tls, n))) { + case SSL_ERROR_WANT_READ: + return (IOBUF_WANT_READ); + case SSL_ERROR_WANT_WRITE: + return (IOBUF_WANT_WRITE); + case SSL_ERROR_SYSCALL: + if (ERR_peek_last_error()) + return (IOBUF_TLSERROR); + return (IOBUF_ERROR); + default: + return (IOBUF_TLSERROR); + } + } else if (n == 0) + return (IOBUF_CLOSED); + + io->wpos += n; + + return (n); +} + +#endif /* IO_TLS */ diff --git a/usr.sbin/smtpd/iobuf.h b/usr.sbin/smtpd/iobuf.h new file mode 100644 index 00000000..c454d0a1 --- /dev/null +++ b/usr.sbin/smtpd/iobuf.h @@ -0,0 +1,67 @@ +/* $OpenBSD: iobuf.h,v 1.5 2019/06/12 17:42:53 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct ioqbuf { + struct ioqbuf *next; + char *buf; + size_t size; + size_t wpos; + size_t rpos; +}; + +struct iobuf { + char *buf; + size_t max; + size_t size; + size_t wpos; + size_t rpos; + + size_t queued; + struct ioqbuf *outq; + struct ioqbuf *outqlast; +}; + +#define IOBUF_WANT_READ -1 +#define IOBUF_WANT_WRITE -2 +#define IOBUF_CLOSED -3 +#define IOBUF_ERROR -4 +#define IOBUF_TLSERROR -5 + +int iobuf_init(struct iobuf *, size_t, size_t); +void iobuf_clear(struct iobuf *); + +int iobuf_extend(struct iobuf *, size_t); +void iobuf_normalize(struct iobuf *); +void iobuf_drop(struct iobuf *, size_t); +size_t iobuf_space(struct iobuf *); +size_t iobuf_len(struct iobuf *); +size_t iobuf_left(struct iobuf *); +char *iobuf_data(struct iobuf *); +char *iobuf_getline(struct iobuf *, size_t *); +ssize_t iobuf_read(struct iobuf *, int); +ssize_t iobuf_read_tls(struct iobuf *, void *); + +size_t iobuf_queued(struct iobuf *); +void* iobuf_reserve(struct iobuf *, size_t); +int iobuf_queue(struct iobuf *, const void*, size_t); +int iobuf_queuev(struct iobuf *, const struct iovec *, int); +int iobuf_fqueue(struct iobuf *, const char *, ...); +int iobuf_vfqueue(struct iobuf *, const char *, va_list); +int iobuf_flush(struct iobuf *, int); +int iobuf_flush_tls(struct iobuf *, void *); +ssize_t iobuf_write(struct iobuf *, int); +ssize_t iobuf_write_tls(struct iobuf *, void *); diff --git a/usr.sbin/smtpd/ioev.c b/usr.sbin/smtpd/ioev.c new file mode 100644 index 00000000..e0a8a096 --- /dev/null +++ b/usr.sbin/smtpd/ioev.c @@ -0,0 +1,1064 @@ +/* $OpenBSD: ioev.c,v 1.42 2019/06/12 17:42:53 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioev.h" +#include "iobuf.h" + +#ifdef IO_TLS +#include +#include +#endif + +enum { + IO_STATE_NONE, + IO_STATE_CONNECT, + IO_STATE_CONNECT_TLS, + IO_STATE_ACCEPT_TLS, + IO_STATE_UP, + + IO_STATE_MAX, +}; + +#define IO_PAUSE_IN IO_IN +#define IO_PAUSE_OUT IO_OUT +#define IO_READ 0x04 +#define IO_WRITE 0x08 +#define IO_RW (IO_READ | IO_WRITE) +#define IO_RESET 0x10 /* internal */ +#define IO_HELD 0x20 /* internal */ + +struct io { + int sock; + void *arg; + void (*cb)(struct io*, int, void *); + struct iobuf iobuf; + size_t lowat; + int timeout; + int flags; + int state; + struct event ev; + void *tls; + const char *error; /* only valid immediately on callback */ +}; + +const char* io_strflags(int); +const char* io_evstr(short); + +void _io_init(void); +void io_hold(struct io *); +void io_release(struct io *); +void io_callback(struct io*, int); +void io_dispatch(int, short, void *); +void io_dispatch_connect(int, short, void *); +size_t io_pending(struct io *); +size_t io_queued(struct io*); +void io_reset(struct io *, short, void (*)(int, short, void*)); +void io_frame_enter(const char *, struct io *, int); +void io_frame_leave(struct io *); + +#ifdef IO_TLS +void ssl_error(const char *); /* XXX external */ + +static const char* io_tls_error(void); +void io_dispatch_accept_tls(int, short, void *); +void io_dispatch_connect_tls(int, short, void *); +void io_dispatch_read_tls(int, short, void *); +void io_dispatch_write_tls(int, short, void *); +void io_reload_tls(struct io *io); +#endif + +static struct io *current = NULL; +static uint64_t frame = 0; +static int _io_debug = 0; + +#define io_debug(args...) do { if (_io_debug) printf(args); } while(0) + + +const char* +io_strio(struct io *io) +{ + static char buf[128]; + char ssl[128]; + + ssl[0] = '\0'; +#ifdef IO_TLS + if (io->tls) { + (void)snprintf(ssl, sizeof ssl, " tls=%s:%s:%d", + SSL_get_version(io->tls), + SSL_get_cipher_name(io->tls), + SSL_get_cipher_bits(io->tls, NULL)); + } +#endif + + (void)snprintf(buf, sizeof buf, + "", + io, io->sock, io->timeout, io_strflags(io->flags), ssl, + io_pending(io), io_queued(io)); + + return (buf); +} + +#define CASE(x) case x : return #x + +const char* +io_strevent(int evt) +{ + static char buf[32]; + + switch (evt) { + CASE(IO_CONNECTED); + CASE(IO_TLSREADY); + CASE(IO_DATAIN); + CASE(IO_LOWAT); + CASE(IO_DISCONNECTED); + CASE(IO_TIMEOUT); + CASE(IO_ERROR); + default: + (void)snprintf(buf, sizeof(buf), "IO_? %d", evt); + return buf; + } +} + +void +io_set_nonblocking(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) + err(1, "io_set_blocking:fcntl(F_GETFL)"); + + flags |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) == -1) + err(1, "io_set_blocking:fcntl(F_SETFL)"); +} + +void +io_set_nolinger(int fd) +{ + struct linger l; + + memset(&l, 0, sizeof(l)); + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) + err(1, "io_set_linger:setsockopt()"); +} + +/* + * Event framing must not rely on an io pointer to refer to the "same" io + * throughout the frame, because this is not always the case: + * + * 1) enter(addr0) -> free(addr0) -> leave(addr0) = SEGV + * 2) enter(addr0) -> free(addr0) -> malloc == addr0 -> leave(addr0) = BAD! + * + * In both case, the problem is that the io is freed in the callback, so + * the pointer becomes invalid. If that happens, the user is required to + * call io_clear, so we can adapt the frame state there. + */ +void +io_frame_enter(const char *where, struct io *io, int ev) +{ + io_debug("\n=== %" PRIu64 " ===\n" + "io_frame_enter(%s, %s, %s)\n", + frame, where, io_evstr(ev), io_strio(io)); + + if (current) + errx(1, "io_frame_enter: interleaved frames"); + + current = io; + + io_hold(io); +} + +void +io_frame_leave(struct io *io) +{ + io_debug("io_frame_leave(%" PRIu64 ")\n", frame); + + if (current && current != io) + errx(1, "io_frame_leave: io mismatch"); + + /* io has been cleared */ + if (current == NULL) + goto done; + + /* TODO: There is a possible optimization there: + * In a typical half-duplex request/response scenario, + * the io is waiting to read a request, and when done, it queues + * the response in the output buffer and goes to write mode. + * There, the write event is set and will be triggered in the next + * event frame. In most case, the write call could be done + * immediately as part of the last read frame, thus avoiding to go + * through the event loop machinery. So, as an optimisation, we + * could detect that case here and force an event dispatching. + */ + + /* Reload the io if it has not been reset already. */ + io_release(io); + current = NULL; + done: + io_debug("=== /%" PRIu64 "\n", frame); + + frame += 1; +} + +void +_io_init() +{ + static int init = 0; + + if (init) + return; + + init = 1; + _io_debug = getenv("IO_DEBUG") != NULL; +} + +struct io * +io_new(void) +{ + struct io *io; + + _io_init(); + + if ((io = calloc(1, sizeof(*io))) == NULL) + return NULL; + + io->sock = -1; + io->timeout = -1; + + if (iobuf_init(&io->iobuf, 0, 0) == -1) { + free(io); + return NULL; + } + + return io; +} + +void +io_free(struct io *io) +{ + io_debug("io_clear(%p)\n", io); + + /* the current io is virtually dead */ + if (io == current) + current = NULL; + +#ifdef IO_TLS + SSL_free(io->tls); + io->tls = NULL; +#endif + + if (event_initialized(&io->ev)) + event_del(&io->ev); + if (io->sock != -1) { + close(io->sock); + io->sock = -1; + } + + iobuf_clear(&io->iobuf); + free(io); +} + +void +io_hold(struct io *io) +{ + io_debug("io_enter(%p)\n", io); + + if (io->flags & IO_HELD) + errx(1, "io_hold: io is already held"); + + io->flags &= ~IO_RESET; + io->flags |= IO_HELD; +} + +void +io_release(struct io *io) +{ + if (!(io->flags & IO_HELD)) + errx(1, "io_release: io is not held"); + + io->flags &= ~IO_HELD; + if (!(io->flags & IO_RESET)) + io_reload(io); +} + +void +io_set_fd(struct io *io, int fd) +{ + io->sock = fd; + if (fd != -1) + io_reload(io); +} + +void +io_set_callback(struct io *io, void(*cb)(struct io *, int, void *), void *arg) +{ + io->cb = cb; + io->arg = arg; +} + +void +io_set_timeout(struct io *io, int msec) +{ + io_debug("io_set_timeout(%p, %d)\n", io, msec); + + io->timeout = msec; +} + +void +io_set_lowat(struct io *io, size_t lowat) +{ + io_debug("io_set_lowat(%p, %zu)\n", io, lowat); + + io->lowat = lowat; +} + +void +io_pause(struct io *io, int dir) +{ + io_debug("io_pause(%p, %x)\n", io, dir); + + io->flags |= dir & (IO_PAUSE_IN | IO_PAUSE_OUT); + io_reload(io); +} + +void +io_resume(struct io *io, int dir) +{ + io_debug("io_resume(%p, %x)\n", io, dir); + + io->flags &= ~(dir & (IO_PAUSE_IN | IO_PAUSE_OUT)); + io_reload(io); +} + +void +io_set_read(struct io *io) +{ + int mode; + + io_debug("io_set_read(%p)\n", io); + + mode = io->flags & IO_RW; + if (!(mode == 0 || mode == IO_WRITE)) + errx(1, "io_set_read(): full-duplex or reading"); + + io->flags &= ~IO_RW; + io->flags |= IO_READ; + io_reload(io); +} + +void +io_set_write(struct io *io) +{ + int mode; + + io_debug("io_set_write(%p)\n", io); + + mode = io->flags & IO_RW; + if (!(mode == 0 || mode == IO_READ)) + errx(1, "io_set_write(): full-duplex or writing"); + + io->flags &= ~IO_RW; + io->flags |= IO_WRITE; + io_reload(io); +} + +const char * +io_error(struct io *io) +{ + return io->error; +} + +void * +io_tls(struct io *io) +{ + return io->tls; +} + +int +io_fileno(struct io *io) +{ + return io->sock; +} + +int +io_paused(struct io *io, int what) +{ + return (io->flags & (IO_PAUSE_IN | IO_PAUSE_OUT)) == what; +} + +/* + * Buffered output functions + */ + +int +io_write(struct io *io, const void *buf, size_t len) +{ + int r; + + r = iobuf_queue(&io->iobuf, buf, len); + + io_reload(io); + + return r; +} + +int +io_writev(struct io *io, const struct iovec *iov, int iovcount) +{ + int r; + + r = iobuf_queuev(&io->iobuf, iov, iovcount); + + io_reload(io); + + return r; +} + +int +io_print(struct io *io, const char *s) +{ + return io_write(io, s, strlen(s)); +} + +int +io_printf(struct io *io, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = io_vprintf(io, fmt, ap); + va_end(ap); + + return r; +} + +int +io_vprintf(struct io *io, const char *fmt, va_list ap) +{ + + char *buf; + int len; + + len = vasprintf(&buf, fmt, ap); + if (len == -1) + return -1; + len = io_write(io, buf, len); + free(buf); + + return len; +} + +size_t +io_queued(struct io *io) +{ + return iobuf_queued(&io->iobuf); +} + +/* + * Buffered input functions + */ + +void * +io_data(struct io *io) +{ + return iobuf_data(&io->iobuf); +} + +size_t +io_datalen(struct io *io) +{ + return iobuf_len(&io->iobuf); +} + +char * +io_getline(struct io *io, size_t *sz) +{ + return iobuf_getline(&io->iobuf, sz); +} + +void +io_drop(struct io *io, size_t sz) +{ + return iobuf_drop(&io->iobuf, sz); +} + + +#define IO_READING(io) (((io)->flags & IO_RW) != IO_WRITE) +#define IO_WRITING(io) (((io)->flags & IO_RW) != IO_READ) + +/* + * Setup the necessary events as required by the current io state, + * honouring duplex mode and i/o pauses. + */ +void +io_reload(struct io *io) +{ + short events; + + /* io will be reloaded at release time */ + if (io->flags & IO_HELD) + return; + + iobuf_normalize(&io->iobuf); + +#ifdef IO_TLS + if (io->tls) { + io_reload_tls(io); + return; + } +#endif + + io_debug("io_reload(%p)\n", io); + + events = 0; + if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) + events = EV_READ; + if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && io_queued(io)) + events |= EV_WRITE; + + io_reset(io, events, io_dispatch); +} + +/* Set the requested event. */ +void +io_reset(struct io *io, short events, void (*dispatch)(int, short, void*)) +{ + struct timeval tv, *ptv; + + io_debug("io_reset(%p, %s, %p) -> %s\n", + io, io_evstr(events), dispatch, io_strio(io)); + + /* + * Indicate that the event has already been reset so that reload + * is not called on frame_leave. + */ + io->flags |= IO_RESET; + + if (event_initialized(&io->ev)) + event_del(&io->ev); + + /* + * The io is paused by the user, so we don't want the timeout to be + * effective. + */ + if (events == 0) + return; + + event_set(&io->ev, io->sock, events, dispatch, io); + if (io->timeout >= 0) { + tv.tv_sec = io->timeout / 1000; + tv.tv_usec = (io->timeout % 1000) * 1000; + ptv = &tv; + } else + ptv = NULL; + + event_add(&io->ev, ptv); +} + +size_t +io_pending(struct io *io) +{ + return iobuf_len(&io->iobuf); +} + +const char* +io_strflags(int flags) +{ + static char buf[64]; + + buf[0] = '\0'; + + switch (flags & IO_RW) { + case 0: + (void)strlcat(buf, "rw", sizeof buf); + break; + case IO_READ: + (void)strlcat(buf, "R", sizeof buf); + break; + case IO_WRITE: + (void)strlcat(buf, "W", sizeof buf); + break; + case IO_RW: + (void)strlcat(buf, "RW", sizeof buf); + break; + } + + if (flags & IO_PAUSE_IN) + (void)strlcat(buf, ",F_PI", sizeof buf); + if (flags & IO_PAUSE_OUT) + (void)strlcat(buf, ",F_PO", sizeof buf); + + return buf; +} + +const char* +io_evstr(short ev) +{ + static char buf[64]; + char buf2[16]; + int n; + + n = 0; + buf[0] = '\0'; + + if (ev == 0) { + (void)strlcat(buf, "", sizeof(buf)); + return buf; + } + + if (ev & EV_TIMEOUT) { + (void)strlcat(buf, "EV_TIMEOUT", sizeof(buf)); + ev &= ~EV_TIMEOUT; + n++; + } + + if (ev & EV_READ) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_READ", sizeof(buf)); + ev &= ~EV_READ; + n++; + } + + if (ev & EV_WRITE) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_WRITE", sizeof(buf)); + ev &= ~EV_WRITE; + n++; + } + + if (ev & EV_SIGNAL) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_SIGNAL", sizeof(buf)); + ev &= ~EV_SIGNAL; + n++; + } + + if (ev) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_?=0x", sizeof(buf)); + (void)snprintf(buf2, sizeof(buf2), "%hx", ev); + (void)strlcat(buf, buf2, sizeof(buf)); + } + + return buf; +} + +void +io_dispatch(int fd, short ev, void *humppa) +{ + struct io *io = humppa; + size_t w; + ssize_t n; + int saved_errno; + + io_frame_enter("io_dispatch", io, ev); + + if (ev == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + if (ev & EV_WRITE && (w = io_queued(io))) { + if ((n = iobuf_write(&io->iobuf, io->sock)) < 0) { + if (n == IOBUF_WANT_WRITE) /* kqueue bug? */ + goto read; + if (n == IOBUF_CLOSED) + io_callback(io, IO_DISCONNECTED); + else { + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + } + goto leave; + } + if (w > io->lowat && w - n <= io->lowat) + io_callback(io, IO_LOWAT); + } + read: + + if (ev & EV_READ) { + iobuf_normalize(&io->iobuf); + if ((n = iobuf_read(&io->iobuf, io->sock)) < 0) { + if (n == IOBUF_CLOSED) + io_callback(io, IO_DISCONNECTED); + else { + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + } + goto leave; + } + if (n) + io_callback(io, IO_DATAIN); + } + +leave: + io_frame_leave(io); +} + +void +io_callback(struct io *io, int evt) +{ + io->cb(io, evt, io->arg); +} + +int +io_connect(struct io *io, const struct sockaddr *sa, const struct sockaddr *bsa) +{ + int sock, errno_save; + + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) + goto fail; + + io_set_nonblocking(sock); + io_set_nolinger(sock); + + if (bsa && bind(sock, bsa, SA_LEN(bsa)) == -1) + goto fail; + + if (connect(sock, sa, SA_LEN(sa)) == -1) + if (errno != EINPROGRESS) + goto fail; + + io->sock = sock; + io_reset(io, EV_WRITE, io_dispatch_connect); + + return (sock); + + fail: + if (sock != -1) { + errno_save = errno; + close(sock); + errno = errno_save; + io->error = strerror(errno); + } + return (-1); +} + +void +io_dispatch_connect(int fd, short ev, void *humppa) +{ + struct io *io = humppa; + int r, e; + socklen_t sl; + + io_frame_enter("io_dispatch_connect", io, ev); + + if (ev == EV_TIMEOUT) { + close(fd); + io->sock = -1; + io_callback(io, IO_TIMEOUT); + } else { + sl = sizeof(e); + r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &sl); + if (r == -1) { + warn("io_dispatch_connect: getsockopt"); + e = errno; + } + if (e) { + close(fd); + io->sock = -1; + io->error = strerror(e); + io_callback(io, e == ETIMEDOUT ? IO_TIMEOUT : IO_ERROR); + } + else { + io->state = IO_STATE_UP; + io_callback(io, IO_CONNECTED); + } + } + + io_frame_leave(io); +} + +#ifdef IO_TLS + +static const char* +io_tls_error(void) +{ + static char buf[128]; + unsigned long e; + + e = ERR_peek_last_error(); + if (e) { + ERR_error_string(e, buf); + return (buf); + } + + return ("No TLS error"); +} + +int +io_start_tls(struct io *io, void *tls) +{ + int mode; + + mode = io->flags & IO_RW; + if (mode == 0 || mode == IO_RW) + errx(1, "io_start_tls(): full-duplex or unset"); + + if (io->tls) + errx(1, "io_start_tls(): TLS already started"); + io->tls = tls; + + if (SSL_set_fd(io->tls, io->sock) == 0) { + ssl_error("io_start_tls:SSL_set_fd"); + return (-1); + } + + if (mode == IO_WRITE) { + io->state = IO_STATE_CONNECT_TLS; + SSL_set_connect_state(io->tls); + io_reset(io, EV_WRITE, io_dispatch_connect_tls); + } else { + io->state = IO_STATE_ACCEPT_TLS; + SSL_set_accept_state(io->tls); + io_reset(io, EV_READ, io_dispatch_accept_tls); + } + + return (0); +} + +void +io_dispatch_accept_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int e, ret; + + io_frame_enter("io_dispatch_accept_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + if ((ret = SSL_accept(io->tls)) > 0) { + io->state = IO_STATE_UP; + io_callback(io, IO_TLSREADY); + goto leave; + } + + switch ((e = SSL_get_error(io->tls, ret))) { + case SSL_ERROR_WANT_READ: + io_reset(io, EV_READ, io_dispatch_accept_tls); + break; + case SSL_ERROR_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_accept_tls); + break; + default: + io->error = io_tls_error(); + ssl_error("io_dispatch_accept_tls:SSL_accept"); + io_callback(io, IO_ERROR); + break; + } + + leave: + io_frame_leave(io); +} + +void +io_dispatch_connect_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int e, ret; + + io_frame_enter("io_dispatch_connect_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + if ((ret = SSL_connect(io->tls)) > 0) { + io->state = IO_STATE_UP; + io_callback(io, IO_TLSREADY); + goto leave; + } + + switch ((e = SSL_get_error(io->tls, ret))) { + case SSL_ERROR_WANT_READ: + io_reset(io, EV_READ, io_dispatch_connect_tls); + break; + case SSL_ERROR_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_connect_tls); + break; + default: + io->error = io_tls_error(); + ssl_error("io_dispatch_connect_ssl:SSL_connect"); + io_callback(io, IO_TLSERROR); + break; + } + + leave: + io_frame_leave(io); +} + +void +io_dispatch_read_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int n, saved_errno; + + io_frame_enter("io_dispatch_read_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + +again: + iobuf_normalize(&io->iobuf); + switch ((n = iobuf_read_tls(&io->iobuf, (SSL*)io->tls))) { + case IOBUF_WANT_READ: + io_reset(io, EV_READ, io_dispatch_read_tls); + break; + case IOBUF_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_read_tls); + break; + case IOBUF_CLOSED: + io_callback(io, IO_DISCONNECTED); + break; + case IOBUF_ERROR: + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + break; + case IOBUF_TLSERROR: + io->error = io_tls_error(); + ssl_error("io_dispatch_read_tls:SSL_read"); + io_callback(io, IO_ERROR); + break; + default: + io_debug("io_dispatch_read_tls(...) -> r=%d\n", n); + io_callback(io, IO_DATAIN); + if (current == io && IO_READING(io) && SSL_pending(io->tls)) + goto again; + } + + leave: + io_frame_leave(io); +} + +void +io_dispatch_write_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int n, saved_errno; + size_t w2, w; + + io_frame_enter("io_dispatch_write_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + w = io_queued(io); + switch ((n = iobuf_write_tls(&io->iobuf, (SSL*)io->tls))) { + case IOBUF_WANT_READ: + io_reset(io, EV_READ, io_dispatch_write_tls); + break; + case IOBUF_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_write_tls); + break; + case IOBUF_CLOSED: + io_callback(io, IO_DISCONNECTED); + break; + case IOBUF_ERROR: + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + break; + case IOBUF_TLSERROR: + io->error = io_tls_error(); + ssl_error("io_dispatch_write_tls:SSL_write"); + io_callback(io, IO_ERROR); + break; + default: + io_debug("io_dispatch_write_tls(...) -> w=%d\n", n); + w2 = io_queued(io); + if (w > io->lowat && w2 <= io->lowat) + io_callback(io, IO_LOWAT); + break; + } + + leave: + io_frame_leave(io); +} + +void +io_reload_tls(struct io *io) +{ + short ev = 0; + void (*dispatch)(int, short, void*) = NULL; + + switch (io->state) { + case IO_STATE_CONNECT_TLS: + ev = EV_WRITE; + dispatch = io_dispatch_connect_tls; + break; + case IO_STATE_ACCEPT_TLS: + ev = EV_READ; + dispatch = io_dispatch_accept_tls; + break; + case IO_STATE_UP: + ev = 0; + if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) { + ev = EV_READ; + dispatch = io_dispatch_read_tls; + } + else if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && + io_queued(io)) { + ev = EV_WRITE; + dispatch = io_dispatch_write_tls; + } + if (!ev) + return; /* paused */ + break; + default: + errx(1, "io_reload_tls(): bad state"); + } + + io_reset(io, ev, dispatch); +} + +#endif /* IO_TLS */ diff --git a/usr.sbin/smtpd/ioev.h b/usr.sbin/smtpd/ioev.h new file mode 100644 index 00000000..f155a7fc --- /dev/null +++ b/usr.sbin/smtpd/ioev.h @@ -0,0 +1,70 @@ +/* $OpenBSD: ioev.h,v 1.18 2019/09/11 04:19:19 martijn Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum { + IO_CONNECTED = 0, /* connection successful */ + IO_TLSREADY, /* TLS started successfully */ + IO_TLSERROR, /* XXX - needs more work */ + IO_DATAIN, /* new data in input buffer */ + IO_LOWAT, /* output queue running low */ + IO_DISCONNECTED, /* error? */ + IO_TIMEOUT, /* error? */ + IO_ERROR, /* details? */ +}; + +#define IO_IN 0x01 +#define IO_OUT 0x02 + +struct io; + +void io_set_nonblocking(int); +void io_set_nolinger(int); + +struct io *io_new(void); +void io_free(struct io *); +void io_set_read(struct io *); +void io_set_write(struct io *); +void io_set_fd(struct io *, int); +void io_set_callback(struct io *io, void(*)(struct io *, int, void *), void *); +void io_set_timeout(struct io *, int); +void io_set_lowat(struct io *, size_t); +void io_pause(struct io *, int); +void io_resume(struct io *, int); +void io_reload(struct io *); +int io_connect(struct io *, const struct sockaddr *, const struct sockaddr *); +int io_start_tls(struct io *, void *); +const char* io_strio(struct io *); +const char* io_strevent(int); +const char* io_error(struct io *); +void* io_tls(struct io *); +int io_fileno(struct io *); +int io_paused(struct io *, int); + +/* Buffered output functions */ +int io_write(struct io *, const void *, size_t); +int io_writev(struct io *, const struct iovec *, int); +int io_print(struct io *, const char *); +int io_printf(struct io *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +int io_vprintf(struct io *, const char *, va_list); +size_t io_queued(struct io *); + +/* Buffered input functions */ +void* io_data(struct io *); +size_t io_datalen(struct io *); +char* io_getline(struct io *, size_t *); +void io_drop(struct io *, size_t); diff --git a/usr.sbin/smtpd/libressl.c b/usr.sbin/smtpd/libressl.c new file mode 100644 index 00000000..57d74389 --- /dev/null +++ b/usr.sbin/smtpd/libressl.c @@ -0,0 +1,213 @@ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +/* + * SSL operations needed when running in a privilege separated environment. + * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . + */ + +#include "includes.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "ssl.h" + +#define SSL_ECDH_CURVE "prime256v1" + +/* + * Read a bio that contains our certificate in "PEM" format, + * possibly followed by a sequence of CA certificates that should be + * sent to the peer in the Certificate message. + */ +static int +ssl_ctx_use_certificate_chain_bio(SSL_CTX *ctx, BIO *in) +{ + int ret = 0; + X509 *x = NULL; + + ERR_clear_error(); /* clear error stack for SSL_CTX_use_certificate() */ + + x = PEM_read_bio_X509_AUX(in, NULL, ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata); + if (x == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + + if (ERR_peek_error() != 0) + ret = 0; + /* Key/certificate mismatch doesn't imply ret==0 ... */ + if (ret) { + /* + * If we could set up our certificate, now proceed to + * the CA certificates. + */ + X509 *ca; + int r; + unsigned long err; + + if (ctx->extra_certs != NULL) { + sk_X509_pop_free(ctx->extra_certs, X509_free); + ctx->extra_certs = NULL; + } + + while ((ca = PEM_read_bio_X509(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata)) != NULL) { + r = SSL_CTX_add_extra_chain_cert(ctx, ca); + if (!r) { + X509_free(ca); + ret = 0; + goto end; + } + /* + * Note that we must not free r if it was successfully + * added to the chain (while we must free the main + * certificate, since its reference count is increased + * by SSL_CTX_use_certificate). + */ + } + + /* When the while loop ends, it's usually just EOF. */ + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + ret = 0; /* some real error */ + } + +end: + if (x != NULL) + X509_free(x); + return (ret); +} + +int +SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *buf, int len) +{ + BIO *in; + int ret = 0; + + in = BIO_new_mem_buf(buf, len); + if (in == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB); + goto end; + } + + ret = ssl_ctx_use_certificate_chain_bio(ctx, in); + +end: + BIO_free(in); + return (ret); +} + +#ifndef HAVE_SSL_CTX_SET_ECDH_AUTO +void +SSL_CTX_set_ecdh_auto(SSL_CTX *ctx, int enable) +{ + int nid; + EC_KEY *ecdh; + + if (!enable) + return; + + if ((nid = OBJ_sn2nid(SSL_ECDH_CURVE)) == 0) { + ssl_error("ssl_set_ecdh_auto"); + fatal("ssl_set_ecdh_auto: unknown curve name " + SSL_ECDH_CURVE); + } + + if ((ecdh = EC_KEY_new_by_curve_name(nid)) == NULL) { + ssl_error("ssl_set_ecdh_auto"); + fatal("ssl_set_ecdh_auto: unable to create curve " + SSL_ECDH_CURVE); + } + + SSL_CTX_set_tmp_ecdh(ctx, ecdh); + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); + EC_KEY_free(ecdh); +} +#endif + +#ifndef HAVE_SSL_CTX_SET_DH_AUTO +void +SSL_CTX_set_dh_auto(SSL_CTX *ctx, int enable) +{ + if (!enable) + return; + + /* stub until OpenSSL catches up with this ... */ + log_warnx("OpenSSL does not support SSL_CTX_set_dh_auto (yet ?)"); + return; +} +#endif diff --git a/usr.sbin/smtpd/limit.c b/usr.sbin/smtpd/limit.c new file mode 100644 index 00000000..25e7a026 --- /dev/null +++ b/usr.sbin/smtpd/limit.c @@ -0,0 +1,124 @@ +/* $OpenBSD: limit.c,v 1.5 2016/06/15 19:59:03 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +void +limit_mta_set_defaults(struct mta_limits *limits) +{ + limits->maxconn_per_host = 10; + limits->maxconn_per_route = 5; + limits->maxconn_per_source = 100; + limits->maxconn_per_connector = 20; + limits->maxconn_per_relay = 100; + limits->maxconn_per_domain = 100; + + limits->conndelay_host = 0; + limits->conndelay_route = 5; + limits->conndelay_source = 0; + limits->conndelay_connector = 0; + limits->conndelay_relay = 2; + limits->conndelay_domain = 0; + + limits->discdelay_route = 3; + + limits->max_mail_per_session = 100; + limits->sessdelay_transaction = 0; + limits->sessdelay_keepalive = 10; + + limits->max_failures_per_session = 25; + + limits->family = AF_UNSPEC; + + limits->task_hiwat = 50; + limits->task_lowat = 30; + limits->task_release = 10; +} + +int +limit_mta_set(struct mta_limits *limits, const char *key, int64_t value) +{ + if (!strcmp(key, "max-conn-per-host")) + limits->maxconn_per_host = value; + else if (!strcmp(key, "max-conn-per-route")) + limits->maxconn_per_route = value; + else if (!strcmp(key, "max-conn-per-source")) + limits->maxconn_per_source = value; + else if (!strcmp(key, "max-conn-per-connector")) + limits->maxconn_per_connector = value; + else if (!strcmp(key, "max-conn-per-relay")) + limits->maxconn_per_relay = value; + else if (!strcmp(key, "max-conn-per-domain")) + limits->maxconn_per_domain = value; + + else if (!strcmp(key, "conn-delay-host")) + limits->conndelay_host = value; + else if (!strcmp(key, "conn-delay-route")) + limits->conndelay_route = value; + else if (!strcmp(key, "conn-delay-source")) + limits->conndelay_source = value; + else if (!strcmp(key, "conn-delay-connector")) + limits->conndelay_connector = value; + else if (!strcmp(key, "conn-delay-relay")) + limits->conndelay_relay = value; + else if (!strcmp(key, "conn-delay-domain")) + limits->conndelay_domain = value; + + else if (!strcmp(key, "reconn-delay-route")) + limits->discdelay_route = value; + + else if (!strcmp(key, "session-mail-max")) + limits->max_mail_per_session = value; + else if (!strcmp(key, "session-transaction-delay")) + limits->sessdelay_transaction = value; + else if (!strcmp(key, "session-keepalive")) + limits->sessdelay_keepalive = value; + + else if (!strcmp(key, "max-failures-per-session")) + limits->max_failures_per_session = value; + + else if (!strcmp(key, "task-hiwat")) + limits->task_hiwat = value; + else if (!strcmp(key, "task-lowat")) + limits->task_lowat = value; + else if (!strcmp(key, "task-release")) + limits->task_release = value; + + else + return (0); + + return (1); +} diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c new file mode 100644 index 00000000..6ac21245 --- /dev/null +++ b/usr.sbin/smtpd/lka.c @@ -0,0 +1,914 @@ +/* $OpenBSD: lka.c,v 1.243 2019/12/21 10:23:37 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +static void lka_imsg(struct mproc *, struct imsg *); +static void lka_shutdown(void); +static void lka_sig_handler(int, short, void *); +static int lka_authenticate(const char *, const char *, const char *); +static int lka_credentials(const char *, const char *, char *, size_t); +static int lka_userinfo(const char *, const char *, struct userinfo *); +static int lka_addrname(const char *, const struct sockaddr *, + struct addrname *); +static int lka_mailaddrmap(const char *, const char *, const struct mailaddr *); + +static void proc_timeout(int fd, short event, void *p); + +struct event ev_proc_ready; + +static void +lka_imsg(struct mproc *p, struct imsg *imsg) +{ + struct table *table; + int ret; + struct sockaddr_storage ss; + struct userinfo userinfo; + struct addrname addrname; + struct envelope evp; + struct mailaddr maddr; + struct msg m; + union lookup lk; + char buf[LINE_MAX]; + const char *tablename, *username, *password, *label, *procname; + uint64_t reqid; + int v; + struct timeval tv; + const char *direction; + const char *rdns; + const char *command; + const char *response; + const char *ciphers; + const char *address; + const char *domain; + const char *helomethod; + const char *heloname; + const char *filter_name; + const char *result; + struct sockaddr_storage ss_src, ss_dest; + int filter_response; + int filter_phase; + const char *filter_param; + uint32_t msgid; + uint32_t subsystems; + uint64_t evpid; + size_t msgsz; + int ok; + int fcrdns; + + if (imsg == NULL) + lka_shutdown(); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + case IMSG_GETNAMEINFO: + case IMSG_RES_QUERY: + resolver_dispatch_request(p, imsg); + return; + + case IMSG_CERT_INIT: + case IMSG_CERT_CERTIFICATE: + case IMSG_CERT_VERIFY: + cert_dispatch_request(p, imsg); + return; + + case IMSG_MTA_DNS_HOST: + case IMSG_MTA_DNS_MX: + case IMSG_MTA_DNS_MX_PREFERENCE: + dns_imsg(p, imsg); + return; + + case IMSG_SMTP_CHECK_SENDER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &username); + m_get_mailaddr(&m, &maddr); + m_end(&m); + + ret = lka_mailaddrmap(tablename, username, &maddr); + + m_create(p, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + m_close(p); + return; + + case IMSG_SMTP_EXPAND_RCPT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_envelope(&m, &evp); + m_end(&m); + lka_session(reqid, &evp); + return; + + case IMSG_SMTP_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_end(&m); + + ret = lka_addrname(tablename, (struct sockaddr*)&ss, + &addrname); + + m_create(p, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_string(p, addrname.name); + m_close(p); + return; + + case IMSG_SMTP_AUTHENTICATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &username); + m_get_string(&m, &password); + m_end(&m); + + if (!tablename[0]) { + m_create(p_parent, IMSG_LKA_AUTHENTICATE, + 0, 0, -1); + m_add_id(p_parent, reqid); + m_add_string(p_parent, username); + m_add_string(p_parent, password); + m_close(p_parent); + return; + } + + ret = lka_authenticate(tablename, username, password); + + m_create(p, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + m_close(p); + return; + + case IMSG_MDA_LOOKUP_USERINFO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &username); + m_end(&m); + + ret = lka_userinfo(tablename, username, &userinfo); + + m_create(p, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_data(p, &userinfo, sizeof(userinfo)); + m_close(p); + return; + + case IMSG_MTA_LOOKUP_CREDENTIALS: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &label); + m_end(&m); + + lka_credentials(tablename, label, buf, sizeof(buf)); + + m_create(p, IMSG_MTA_LOOKUP_CREDENTIALS, 0, 0, -1); + m_add_id(p, reqid); + m_add_string(p, buf); + m_close(p); + return; + + case IMSG_MTA_LOOKUP_SOURCE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_end(&m); + + table = table_find(env, tablename); + + m_create(p, IMSG_MTA_LOOKUP_SOURCE, 0, 0, -1); + m_add_id(p, reqid); + + if (table == NULL) { + log_warn("warn: source address table %s missing", + tablename); + m_add_int(p, LKA_TEMPFAIL); + } + else { + ret = table_fetch(table, K_SOURCE, &lk); + if (ret == -1) + m_add_int(p, LKA_TEMPFAIL); + else if (ret == 0) + m_add_int(p, LKA_PERMFAIL); + else { + m_add_int(p, LKA_OK); + m_add_sockaddr(p, + (struct sockaddr *)&lk.source.addr); + } + } + m_close(p); + return; + + case IMSG_MTA_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_end(&m); + + ret = lka_addrname(tablename, (struct sockaddr*)&ss, + &addrname); + + m_create(p, IMSG_MTA_LOOKUP_HELO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_string(p, addrname.name); + m_close(p); + return; + + case IMSG_MTA_LOOKUP_SMARTHOST: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &domain); + m_get_string(&m, &tablename); + m_end(&m); + + table = table_find(env, tablename); + + m_create(p, IMSG_MTA_LOOKUP_SMARTHOST, 0, 0, -1); + m_add_id(p, reqid); + + if (table == NULL) { + log_warn("warn: smarthost table %s missing", tablename); + m_add_int(p, LKA_TEMPFAIL); + } + else { + if (domain == NULL) + ret = table_fetch(table, K_RELAYHOST, &lk); + else + ret = table_lookup(table, K_RELAYHOST, domain, &lk); + + if (ret == -1) + m_add_int(p, LKA_TEMPFAIL); + else if (ret == 0) + m_add_int(p, LKA_PERMFAIL); + else { + m_add_int(p, LKA_OK); + m_add_string(p, lk.relayhost); + } + } + m_close(p); + return; + + case IMSG_CONF_START: + return; + + case IMSG_CONF_END: + if (tracing & TRACE_TABLES) + table_dump_all(env); + + /* fork & exec tables that need it */ + table_open_all(env); + +#if HAVE_PLEDGE + /* revoke proc & exec */ + if (pledge("stdio rpath inet dns getpw recvfd sendfd", + NULL) == -1) + err(1, "pledge"); +#endif + + /* setup proc registering task */ + evtimer_set(&ev_proc_ready, proc_timeout, &ev_proc_ready); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&ev_proc_ready, &tv); + return; + + case IMSG_LKA_OPEN_FORWARD: + lka_session_forward_reply(imsg->data, imsg->fd); + return; + + case IMSG_LKA_AUTHENTICATE: + imsg->hdr.type = IMSG_SMTP_AUTHENTICATE; + m_forward(p_pony, imsg); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CTL_UPDATE_TABLE: + ret = 0; + table = table_find(env, imsg->data); + if (table == NULL) { + log_warnx("warn: Lookup table not found: " + "\"%s\"", (char *)imsg->data); + } else + ret = table_update(table); + + m_compose(p_control, + (ret == 1) ? IMSG_CTL_OK : IMSG_CTL_FAIL, + imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_LKA_PROCESSOR_FORK: + m_msg(&m, imsg); + m_get_string(&m, &procname); + m_get_u32(&m, &subsystems); + m_end(&m); + + m_create(p, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, -1); + m_add_string(p, procname); + m_close(p); + + lka_proc_forked(procname, subsystems, imsg->fd); + return; + + case IMSG_LKA_PROCESSOR_ERRFD: + m_msg(&m, imsg); + m_get_string(&m, &procname); + m_end(&m); + + lka_proc_errfd(procname, imsg->fd); + shutdown(imsg->fd, SHUT_WR); + return; + + case IMSG_REPORT_SMTP_LINK_CONNECT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &rdns); + m_get_int(&m, &fcrdns); + m_get_sockaddr(&m, (struct sockaddr *)&ss_src); + m_get_sockaddr(&m, (struct sockaddr *)&ss_dest); + m_end(&m); + + lka_report_smtp_link_connect(direction, &tv, reqid, rdns, fcrdns, &ss_src, &ss_dest); + return; + + case IMSG_REPORT_SMTP_LINK_GREETING: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &domain); + m_end(&m); + + lka_report_smtp_link_greeting(direction, reqid, &tv, domain); + return; + + case IMSG_REPORT_SMTP_LINK_DISCONNECT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_end(&m); + + lka_report_smtp_link_disconnect(direction, &tv, reqid); + return; + + case IMSG_REPORT_SMTP_LINK_IDENTIFY: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &helomethod); + m_get_string(&m, &heloname); + m_end(&m); + + lka_report_smtp_link_identify(direction, &tv, reqid, helomethod, heloname); + return; + + case IMSG_REPORT_SMTP_LINK_TLS: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &ciphers); + m_end(&m); + + lka_report_smtp_link_tls(direction, &tv, reqid, ciphers); + return; + + case IMSG_REPORT_SMTP_LINK_AUTH: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &username); + m_get_string(&m, &result); + m_end(&m); + + lka_report_smtp_link_auth(direction, &tv, reqid, username, result); + return; + + case IMSG_REPORT_SMTP_TX_RESET: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_end(&m); + + lka_report_smtp_tx_reset(direction, &tv, reqid, msgid); + return; + + case IMSG_REPORT_SMTP_TX_BEGIN: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_end(&m); + + lka_report_smtp_tx_begin(direction, &tv, reqid, msgid); + return; + + case IMSG_REPORT_SMTP_TX_MAIL: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_string(&m, &address); + m_get_int(&m, &ok); + m_end(&m); + + lka_report_smtp_tx_mail(direction, &tv, reqid, msgid, address, ok); + return; + + case IMSG_REPORT_SMTP_TX_RCPT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_string(&m, &address); + m_get_int(&m, &ok); + m_end(&m); + + lka_report_smtp_tx_rcpt(direction, &tv, reqid, msgid, address, ok); + return; + + case IMSG_REPORT_SMTP_TX_ENVELOPE: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_id(&m, &evpid); + m_end(&m); + + lka_report_smtp_tx_envelope(direction, &tv, reqid, msgid, evpid); + return; + + case IMSG_REPORT_SMTP_TX_DATA: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_int(&m, &ok); + m_end(&m); + + lka_report_smtp_tx_data(direction, &tv, reqid, msgid, ok); + return; + + case IMSG_REPORT_SMTP_TX_COMMIT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_size(&m, &msgsz); + m_end(&m); + + lka_report_smtp_tx_commit(direction, &tv, reqid, msgid, msgsz); + return; + + case IMSG_REPORT_SMTP_TX_ROLLBACK: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_end(&m); + + lka_report_smtp_tx_rollback(direction, &tv, reqid, msgid); + return; + + case IMSG_REPORT_SMTP_PROTOCOL_CLIENT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &command); + m_end(&m); + + lka_report_smtp_protocol_client(direction, &tv, reqid, command); + return; + + case IMSG_REPORT_SMTP_PROTOCOL_SERVER: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &response); + m_end(&m); + + lka_report_smtp_protocol_server(direction, &tv, reqid, response); + return; + + case IMSG_REPORT_SMTP_FILTER_RESPONSE: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_phase); + m_get_int(&m, &filter_response); + m_get_string(&m, &filter_param); + m_end(&m); + + lka_report_smtp_filter_response(direction, &tv, reqid, + filter_phase, filter_response, filter_param); + return; + + case IMSG_REPORT_SMTP_TIMEOUT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_end(&m); + + lka_report_smtp_timeout(direction, &tv, reqid); + return; + + case IMSG_FILTER_SMTP_PROTOCOL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_phase); + m_get_string(&m, &filter_param); + m_end(&m); + + lka_filter_protocol(reqid, filter_phase, filter_param); + return; + + case IMSG_FILTER_SMTP_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &filter_name); + m_end(&m); + + lka_filter_begin(reqid, filter_name); + return; + + case IMSG_FILTER_SMTP_END: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + lka_filter_end(reqid); + return; + + case IMSG_FILTER_SMTP_DATA_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + lka_filter_data_begin(reqid); + return; + + case IMSG_FILTER_SMTP_DATA_END: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + lka_filter_data_end(reqid); + return; + + } + + errx(1, "lka_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +static void +lka_sig_handler(int sig, short event, void *p) +{ + int status; + pid_t pid; + + switch (sig) { + case SIGCHLD: + do { + pid = waitpid(-1, &status, WNOHANG); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + break; + default: + fatalx("lka_sig_handler: unexpected signal"); + } +} + +void +lka_shutdown(void) +{ + log_debug("debug: lookup agent exiting"); + _exit(0); +} + +int +lka(void) +{ + struct passwd *pw; + struct event ev_sigchld; + + purge_config(PURGE_LISTENERS); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + config_process(PROC_LKA); + + if (initgroups(pw->pw_name, pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("lka: cannot drop privileges"); + + imsg_callback = lka_imsg; + event_init(); + + signal_set(&ev_sigchld, SIGCHLD, lka_sig_handler, NULL); + signal_add(&ev_sigchld, NULL); + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_PARENT); + config_peer(PROC_QUEUE); + config_peer(PROC_CONTROL); + config_peer(PROC_PONY); + + /* Ignore them until we get our config */ + mproc_disable(p_pony); + + lka_report_init(); + lka_filter_init(); + +#if HAVE_PLEDGE + /* proc & exec will be revoked before serving requests */ + if (pledge("stdio rpath inet dns getpw recvfd sendfd proc exec", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +proc_timeout(int fd, short event, void *p) +{ + struct event *ev = p; + struct timeval tv; + + if (!lka_proc_ready()) + goto reset; + + lka_filter_ready(); + mproc_enable(p_pony); + return; + +reset: + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(ev, &tv); +} + + +static int +lka_authenticate(const char *tablename, const char *user, const char *password) +{ + struct table *table; + union lookup lk; + + log_debug("debug: lka: authenticating for %s:%s", tablename, user); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: could not find table %s needed for authentication", + tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_CREDENTIALS, user, &lk)) { + case -1: + log_warnx("warn: user credentials lookup fail for %s:%s", + tablename, user); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + if (crypt_checkpass(password, lk.creds.password) == 0) + return (LKA_OK); + return (LKA_PERMFAIL); + } +} + +static int +lka_credentials(const char *tablename, const char *label, char *dst, size_t sz) +{ + struct table *table; + union lookup lk; + char *buf; + int buflen, r; + + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: credentials table %s missing", tablename); + return (LKA_TEMPFAIL); + } + + dst[0] = '\0'; + + switch (table_lookup(table, K_CREDENTIALS, label, &lk)) { + case -1: + log_warnx("warn: credentials lookup fail for %s:%s", + tablename, label); + return (LKA_TEMPFAIL); + case 0: + log_warnx("warn: credentials not found for %s:%s", + tablename, label); + return (LKA_PERMFAIL); + default: + if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', + lk.creds.username, '\0', lk.creds.password)) == -1) { + log_warn("warn"); + return (LKA_TEMPFAIL); + } + + r = base64_encode((unsigned char *)buf, buflen, dst, sz); + free(buf); + + if (r == -1) { + log_warnx("warn: credentials parse error for %s:%s", + tablename, label); + return (LKA_TEMPFAIL); + } + return (LKA_OK); + } +} + +static int +lka_userinfo(const char *tablename, const char *username, struct userinfo *res) +{ + struct table *table; + union lookup lk; + + log_debug("debug: lka: userinfo %s:%s", tablename, username); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: cannot find user table %s", tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_USERINFO, username, &lk)) { + case -1: + log_warnx("warn: failure during userinfo lookup %s:%s", + tablename, username); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + *res = lk.userinfo; + return (LKA_OK); + } +} + +static int +lka_addrname(const char *tablename, const struct sockaddr *sa, + struct addrname *res) +{ + struct table *table; + union lookup lk; + const char *source; + + source = sa_to_text(sa); + + log_debug("debug: lka: helo %s:%s", tablename, source); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: cannot find helo table %s", tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_ADDRNAME, source, &lk)) { + case -1: + log_warnx("warn: failure during helo lookup %s:%s", + tablename, source); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + *res = lk.addrname; + return (LKA_OK); + } +} + +static int +lka_mailaddrmap(const char *tablename, const char *username, const struct mailaddr *maddr) +{ + struct table *table; + struct maddrnode *mn; + union lookup lk; + int found; + + log_debug("debug: lka: mailaddrmap %s:%s", tablename, username); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: cannot find mailaddrmap table %s", tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_MAILADDRMAP, username, &lk)) { + case -1: + log_warnx("warn: failure during mailaddrmap lookup %s:%s", + tablename, username); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + found = 0; + TAILQ_FOREACH(mn, &lk.maddrmap->queue, entries) { + if (!mailaddr_match(maddr, &mn->mailaddr)) + continue; + found = 1; + break; + } + maddrmap_free(lk.maddrmap); + if (found) + return (LKA_OK); + return (LKA_PERMFAIL); + } + return (LKA_OK); +} diff --git a/usr.sbin/smtpd/lka_filter.c b/usr.sbin/smtpd/lka_filter.c new file mode 100644 index 00000000..2dc66057 --- /dev/null +++ b/usr.sbin/smtpd/lka_filter.c @@ -0,0 +1,1746 @@ +/* $OpenBSD: lka_filter.c,v 1.62 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define PROTOCOL_VERSION "0.6" + +struct filter; +struct filter_session; +static void filter_protocol_internal(struct filter_session *, uint64_t *, uint64_t, enum filter_phase, const char *); +static void filter_protocol(uint64_t, enum filter_phase, const char *); +static void filter_protocol_next(uint64_t, uint64_t, enum filter_phase); +static void filter_protocol_query(struct filter *, uint64_t, uint64_t, const char *, const char *); + +static void filter_data_internal(struct filter_session *, uint64_t, uint64_t, const char *); +static void filter_data(uint64_t, const char *); +static void filter_data_next(uint64_t, uint64_t, const char *); +static void filter_data_query(struct filter *, uint64_t, uint64_t, const char *); + +static int filter_builtins_notimpl(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_connect(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_helo(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_mail_from(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_rcpt_to(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_data(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_commit(struct filter_session *, struct filter *, uint64_t, const char *); + +static void filter_result_proceed(uint64_t); +static void filter_result_junk(uint64_t); +static void filter_result_rewrite(uint64_t, const char *); +static void filter_result_reject(uint64_t, const char *); +static void filter_result_disconnect(uint64_t, const char *); + +static void filter_session_io(struct io *, int, void *); +void lka_filter_process_response(const char *, const char *); + + +struct filter_session { + uint64_t id; + struct io *io; + + char *lastparam; + + char *filter_name; + struct sockaddr_storage ss_src; + struct sockaddr_storage ss_dest; + char *rdns; + int fcrdns; + + char *helo; + char *username; + char *mail_from; + + enum filter_phase phase; +}; + +static struct filter_exec { + enum filter_phase phase; + const char *phase_name; + int (*func)(struct filter_session *, struct filter *, uint64_t, const char *); +} filter_execs[FILTER_PHASES_COUNT] = { + { FILTER_CONNECT, "connect", filter_builtins_connect }, + { FILTER_HELO, "helo", filter_builtins_helo }, + { FILTER_EHLO, "ehlo", filter_builtins_helo }, + { FILTER_STARTTLS, "starttls", filter_builtins_notimpl }, + { FILTER_AUTH, "auth", filter_builtins_notimpl }, + { FILTER_MAIL_FROM, "mail-from", filter_builtins_mail_from }, + { FILTER_RCPT_TO, "rcpt-to", filter_builtins_rcpt_to }, + { FILTER_DATA, "data", filter_builtins_data }, + { FILTER_DATA_LINE, "data-line", filter_builtins_notimpl }, + { FILTER_RSET, "rset", filter_builtins_notimpl }, + { FILTER_QUIT, "quit", filter_builtins_notimpl }, + { FILTER_NOOP, "noop", filter_builtins_notimpl }, + { FILTER_HELP, "help", filter_builtins_notimpl }, + { FILTER_WIZ, "wiz", filter_builtins_notimpl }, + { FILTER_COMMIT, "commit", filter_builtins_commit }, +}; + +struct filter { + uint64_t id; + uint32_t phases; + const char *name; + const char *proc; + struct filter **chain; + size_t chain_size; + struct filter_config *config; +}; +static struct dict filters; + +struct filter_entry { + TAILQ_ENTRY(filter_entry) entries; + uint64_t id; + const char *name; +}; + +struct filter_chain { + TAILQ_HEAD(, filter_entry) chain[nitems(filter_execs)]; +}; + +static struct dict filter_smtp_in; + +static struct tree sessions; +static int filters_inited; + +static struct dict filter_chains; + +struct reporter_proc { + TAILQ_ENTRY(reporter_proc) entries; + const char *name; +}; +TAILQ_HEAD(reporters, reporter_proc); + +static struct dict report_smtp_in; +static struct dict report_smtp_out; + +static struct smtp_events { + const char *event; +} smtp_events[] = { + { "link-connect" }, + { "link-disconnect" }, + { "link-greeting" }, + { "link-identify" }, + { "link-tls" }, + { "link-auth" }, + + { "tx-reset" }, + { "tx-begin" }, + { "tx-mail" }, + { "tx-rcpt" }, + { "tx-envelope" }, + { "tx-data" }, + { "tx-commit" }, + { "tx-rollback" }, + + { "protocol-client" }, + { "protocol-server" }, + + { "filter-report" }, + { "filter-response" }, + + { "timeout" }, +}; + +static int processors_inited = 0; +static struct dict processors; + +struct processor_instance { + char *name; + struct io *io; + struct io *errfd; + int ready; + uint32_t subsystems; +}; + +static void processor_io(struct io *, int, void *); +static void processor_errfd(struct io *, int, void *); +void lka_filter_process_response(const char *, const char *); + +int +lka_proc_ready(void) +{ + void *iter; + struct processor_instance *pi; + + iter = NULL; + while (dict_iter(&processors, &iter, NULL, (void **)&pi)) + if (!pi->ready) + return 0; + return 1; +} + +static void +lka_proc_config(struct processor_instance *pi) +{ + io_printf(pi->io, "config|smtpd-version|%s\n", SMTPD_VERSION); + io_printf(pi->io, "config|smtp-session-timeout|%d\n", SMTPD_SESSION_TIMEOUT); + if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_IN) + io_printf(pi->io, "config|subsystem|smtp-in\n"); + if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_OUT) + io_printf(pi->io, "config|subsystem|smtp-out\n"); + io_printf(pi->io, "config|ready\n"); +} + +void +lka_proc_forked(const char *name, uint32_t subsystems, int fd) +{ + struct processor_instance *processor; + + if (!processors_inited) { + dict_init(&processors); + processors_inited = 1; + } + + processor = xcalloc(1, sizeof *processor); + processor->name = xstrdup(name); + processor->io = io_new(); + processor->subsystems = subsystems; + + io_set_nonblocking(fd); + + io_set_fd(processor->io, fd); + io_set_callback(processor->io, processor_io, processor->name); + dict_xset(&processors, name, processor); +} + +void +lka_proc_errfd(const char *name, int fd) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + io_set_nonblocking(fd); + + processor->errfd = io_new(); + io_set_fd(processor->errfd, fd); + io_set_callback(processor->errfd, processor_errfd, processor->name); + + lka_proc_config(processor); +} + +struct io * +lka_proc_get_io(const char *name) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + return processor->io; +} + +static void +processor_register(const char *name, const char *line) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + if (strcmp(line, "register|ready") == 0) { + processor->ready = 1; + return; + } + + if (strncmp(line, "register|report|", 16) == 0) { + lka_report_register_hook(name, line+16); + return; + } + + if (strncmp(line, "register|filter|", 16) == 0) { + lka_filter_register_hook(name, line+16); + return; + } + + fatalx("Invalid register line received: %s", line); +} + +static void +processor_io(struct io *io, int evt, void *arg) +{ + struct processor_instance *processor; + const char *name = arg; + char *line = NULL; + ssize_t len; + + switch (evt) { + case IO_DATAIN: + while ((line = io_getline(io, &len)) != NULL) { + if (strncmp("register|", line, 9) == 0) { + processor_register(name, line); + continue; + } + + processor = dict_xget(&processors, name); + if (!processor->ready) + fatalx("Non-register message before register|" + "ready: %s", line); + else if (strncmp(line, "filter-result|", 14) == 0 || + strncmp(line, "filter-dataline|", 16) == 0) + lka_filter_process_response(name, line); + else if (strncmp(line, "report|", 7) == 0) + lka_report_proc(name, line); + else + fatalx("Invalid filter message type: %s", line); + } + } +} + +static void +processor_errfd(struct io *io, int evt, void *arg) +{ + const char *name = arg; + char *line = NULL; + ssize_t len; + + switch (evt) { + case IO_DATAIN: + while ((line = io_getline(io, &len)) != NULL) + log_warnx("%s: %s", name, line); + } +} + +void +lka_filter_init(void) +{ + void *iter; + const char *name; + struct filter *filter; + struct filter_config *filter_config; + size_t i; + char buffer[LINE_MAX]; /* for traces */ + + dict_init(&filters); + dict_init(&filter_chains); + + /* first pass, allocate and init individual filters */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { + switch (filter_config->filter_type) { + case FILTER_TYPE_BUILTIN: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->phases |= (1<phase); + filter->config = filter_config; + dict_set(&filters, name, filter); + log_trace(TRACE_FILTERS, "filters init type=builtin, name=%s, hooks=%08x", + name, filter->phases); + break; + + case FILTER_TYPE_PROC: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->proc = filter_config->proc; + filter->config = filter_config; + dict_set(&filters, name, filter); + log_trace(TRACE_FILTERS, "filters init type=proc, name=%s, proc=%s", + name, filter_config->proc); + break; + + case FILTER_TYPE_CHAIN: + break; + } + } + + /* second pass, allocate and init filter chains but don't build yet */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { + switch (filter_config->filter_type) { + case FILTER_TYPE_CHAIN: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->chain = xcalloc(filter_config->chain_size, sizeof(void **)); + filter->chain_size = filter_config->chain_size; + filter->config = filter_config; + + buffer[0] = '\0'; + for (i = 0; i < filter->chain_size; ++i) { + filter->chain[i] = dict_xget(&filters, filter_config->chain[i]); + if (i) + (void)strlcat(buffer, ", ", sizeof buffer); + (void)strlcat(buffer, filter->chain[i]->name, sizeof buffer); + } + log_trace(TRACE_FILTERS, "filters init type=chain, name=%s { %s }", name, buffer); + + dict_set(&filters, name, filter); + break; + + case FILTER_TYPE_BUILTIN: + case FILTER_TYPE_PROC: + break; + } + } +} + +void +lka_filter_register_hook(const char *name, const char *hook) +{ + struct dict *subsystem; + struct filter *filter; + const char *filter_name; + void *iter; + size_t i; + + if (strncasecmp(hook, "smtp-in|", 8) == 0) { + subsystem = &filter_smtp_in; + hook += 8; + } + else + fatalx("Invalid message direction: %s", hook); + + for (i = 0; i < nitems(filter_execs); i++) + if (strcmp(hook, filter_execs[i].phase_name) == 0) + break; + if (i == nitems(filter_execs)) + fatalx("Unrecognized report name: %s", hook); + + iter = NULL; + while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) + if (filter->proc && strcmp(name, filter->proc) == 0) + filter->phases |= (1<chain[i]); + dict_set(&filter_chains, filter_name, filter_chain); + + if (filter->chain) { + for (i = 0; i < filter->chain_size; i++) { + subfilter = filter->chain[i]; + for (j = 0; j < nitems(filter_execs); ++j) { + if (subfilter->phases & (1<id = generate_uid(); + filter_entry->name = subfilter->name; + TAILQ_INSERT_TAIL(&filter_chain->chain[j], + filter_entry, entries); + } + } + } + continue; + } + + for (i = 0; i < nitems(filter_execs); ++i) { + if (filter->phases & (1<id = generate_uid(); + filter_entry->name = filter_name; + TAILQ_INSERT_TAIL(&filter_chain->chain[i], + filter_entry, entries); + } + } + } +} + +int +lka_filter_proc_in_session(uint64_t reqid, const char *proc) +{ + struct filter_session *fs; + struct filter *filter; + size_t i; + + if ((fs = tree_get(&sessions, reqid)) == NULL) + return 0; + + filter = dict_get(&filters, fs->filter_name); + if (filter == NULL || (filter->proc == NULL && filter->chain == NULL)) + return 0; + + if (filter->proc) + return strcmp(filter->proc, proc) == 0 ? 1 : 0; + + for (i = 0; i < filter->chain_size; i++) + if (filter->chain[i]->proc && + strcmp(filter->chain[i]->proc, proc) == 0) + return 1; + + return 0; +} + +void +lka_filter_begin(uint64_t reqid, const char *filter_name) +{ + struct filter_session *fs; + + if (!filters_inited) { + tree_init(&sessions); + filters_inited = 1; + } + + fs = xcalloc(1, sizeof (struct filter_session)); + fs->id = reqid; + fs->filter_name = xstrdup(filter_name); + tree_xset(&sessions, fs->id, fs); + + log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-begin", reqid); +} + +void +lka_filter_end(uint64_t reqid) +{ + struct filter_session *fs; + + fs = tree_xpop(&sessions, reqid); + free(fs->rdns); + free(fs->helo); + free(fs->mail_from); + free(fs->username); + free(fs->lastparam); + free(fs); + log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-end", reqid); +} + +void +lka_filter_data_begin(uint64_t reqid) +{ + struct filter_session *fs; + int sp[2]; + int fd = -1; + + fs = tree_xget(&sessions, reqid); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + goto end; + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + fd = sp[0]; + fs->io = io_new(); + io_set_fd(fs->io, sp[1]); + io_set_callback(fs->io, filter_session_io, fs); + +end: + m_create(p_pony, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, fd); + m_add_id(p_pony, reqid); + m_add_int(p_pony, fd != -1 ? 1 : 0); + m_close(p_pony); + log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-begin fd=%d", reqid, fd); +} + +void +lka_filter_data_end(uint64_t reqid) +{ + struct filter_session *fs; + + fs = tree_xget(&sessions, reqid); + if (fs->io) { + io_free(fs->io); + fs->io = NULL; + } + log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-end", reqid); +} + +static void +filter_session_io(struct io *io, int evt, void *arg) +{ + struct filter_session *fs = arg; + char *line = NULL; + ssize_t len; + + log_trace(TRACE_IO, "filter session: %p: %s %s", fs, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(fs->io, &len); + /* No complete line received */ + if (line == NULL) + return; + + filter_data(fs->id, line); + + goto nextline; + + case IO_DISCONNECTED: + io_free(fs->io); + fs->io = NULL; + break; + } +} + +void +lka_filter_process_response(const char *name, const char *line) +{ + uint64_t reqid; + uint64_t token; + char buffer[LINE_MAX]; + char *ep = NULL; + char *kind = NULL; + char *qid = NULL; + /*char *phase = NULL;*/ + char *response = NULL; + char *parameter = NULL; + struct filter_session *fs; + + (void)strlcpy(buffer, line, sizeof buffer); + if ((ep = strchr(buffer, '|')) == NULL) + fatalx("Missing token: %s", line); + ep[0] = '\0'; + + kind = buffer; + + qid = ep+1; + if ((ep = strchr(qid, '|')) == NULL) + fatalx("Missing reqid: %s", line); + ep[0] = '\0'; + + reqid = strtoull(qid, &ep, 16); + if (qid[0] == '\0' || *ep != '\0') + fatalx("Invalid reqid: %s", line); + if (errno == ERANGE && reqid == ULLONG_MAX) + fatal("Invalid reqid: %s", line); + + qid = ep+1; + if ((ep = strchr(qid, '|')) == NULL) + fatal("Missing directive: %s", line); + ep[0] = '\0'; + + token = strtoull(qid, &ep, 16); + if (qid[0] == '\0' || *ep != '\0') + fatalx("Invalid token: %s", line); + if (errno == ERANGE && token == ULLONG_MAX) + fatal("Invalid token: %s", line); + + response = ep+1; + + /* session can legitimately disappear on a resume */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; + + if (strcmp(kind, "filter-dataline") == 0) { + if (fs->phase != FILTER_DATA_LINE) + fatalx("filter-dataline out of dataline phase"); + filter_data_next(token, reqid, response); + return; + } + if (fs->phase == FILTER_DATA_LINE) + fatalx("filter-result in dataline phase"); + + if ((ep = strchr(response, '|'))) { + parameter = ep + 1; + ep[0] = '\0'; + } + + if (strcmp(response, "proceed") == 0) { + if (parameter != NULL) + fatalx("Unexpected parameter after proceed: %s", line); + filter_protocol_next(token, reqid, 0); + return; + } else if (strcmp(response, "junk") == 0) { + if (parameter != NULL) + fatalx("Unexpected parameter after junk: %s", line); + if (fs->phase == FILTER_COMMIT) + fatalx("filter-reponse junk after DATA"); + filter_result_junk(reqid); + return; + } else { + if (parameter == NULL) + fatalx("Missing parameter: %s", line); + + if (strcmp(response, "rewrite") == 0) + filter_result_rewrite(reqid, parameter); + else if (strcmp(response, "reject") == 0) + filter_result_reject(reqid, parameter); + else if (strcmp(response, "disconnect") == 0) + filter_result_disconnect(reqid, parameter); + else + fatalx("Invalid directive: %s", line); + } +} + +void +lka_filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) +{ + filter_protocol(reqid, phase, param); +} + +static void +filter_protocol_internal(struct filter_session *fs, uint64_t *token, uint64_t reqid, enum filter_phase phase, const char *param) +{ + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; + struct timeval tv; + const char *phase_name = filter_execs[phase].phase_name; + int resume = 1; + + if (!*token) { + fs->phase = phase; + resume = 0; + } + + /* XXX - this sanity check requires a protocol change, stub for now */ + phase = fs->phase; + if (fs->phase != phase) + fatalx("misbehaving filter"); + + /* based on token, identify the filter_entry we should apply */ + filter_chain = dict_get(&filter_chains, fs->filter_name); + filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); + if (*token) { + TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) + if (filter_entry->id == *token) + break; + if (filter_entry == NULL) + fatalx("misbehaving filter"); + filter_entry = TAILQ_NEXT(filter_entry, entries); + } + + /* no filter_entry, we either had none or reached end of chain */ + if (filter_entry == NULL) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, resume=%s, " + "action=proceed", + fs->id, phase_name, resume ? "y" : "n"); + filter_result_proceed(reqid); + return; + } + + /* process param with current filter_entry */ + *token = filter_entry->id; + filter = dict_get(&filters, filter_entry->name); + if (filter->proc) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=deferred, filter=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name); + filter_protocol_query(filter, filter_entry->id, reqid, + filter_execs[fs->phase].phase_name, param); + return; /* deferred response */ + } + + if (filter_execs[fs->phase].func(fs, filter, reqid, param)) { + if (filter->config->rewrite) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=rewrite, filter=%s, query=%s, response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, + filter->config->rewrite); + filter_result_rewrite(reqid, filter->config->rewrite); + return; + } + else if (filter->config->disconnect) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=disconnect, filter=%s, query=%s, response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, + filter->config->disconnect); + filter_result_disconnect(reqid, filter->config->disconnect); + return; + } + else if (filter->config->junk) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=junk, filter=%s, query=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param); + filter_result_junk(reqid); + return; + } else if (filter->config->report) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=report, filter=%s, query=%s response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, filter->config->report); + + gettimeofday(&tv, NULL); + lka_report_filter_report(fs->id, filter->name, 1, + "smtp-in", &tv, filter->config->report); + } else if (filter->config->bypass) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=bypass, filter=%s, query=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param); + filter_result_proceed(reqid); + return; + } else { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=reject, filter=%s, query=%s, response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, + filter->config->reject); + filter_result_reject(reqid, filter->config->reject); + return; + } + } + + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=proceed, filter=%s, query=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param); + + /* filter_entry resulted in proceed, try next filter */ + filter_protocol_internal(fs, token, reqid, phase, param); + return; +} + +static void +filter_data_internal(struct filter_session *fs, uint64_t token, uint64_t reqid, const char *line) +{ + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; + + if (!token) + fs->phase = FILTER_DATA_LINE; + if (fs->phase != FILTER_DATA_LINE) + fatalx("misbehaving filter"); + + /* based on token, identify the filter_entry we should apply */ + filter_chain = dict_get(&filter_chains, fs->filter_name); + filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); + if (token) { + TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) + if (filter_entry->id == token) + break; + if (filter_entry == NULL) + fatalx("misbehaving filter"); + filter_entry = TAILQ_NEXT(filter_entry, entries); + } + + /* no filter_entry, we either had none or reached end of chain */ + if (filter_entry == NULL) { + io_printf(fs->io, "%s\n", line); + return; + } + + /* pass data to the filter */ + filter = dict_get(&filters, filter_entry->name); + filter_data_query(filter, filter_entry->id, reqid, line); +} + +static void +filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) +{ + struct filter_session *fs; + uint64_t token = 0; + char *nparam = NULL; + + fs = tree_xget(&sessions, reqid); + + switch (phase) { + case FILTER_HELO: + case FILTER_EHLO: + free(fs->helo); + fs->helo = xstrdup(param); + break; + case FILTER_MAIL_FROM: + free(fs->mail_from); + fs->mail_from = xstrdup(param + 1); + *strchr(fs->mail_from, '>') = '\0'; + param = fs->mail_from; + + break; + case FILTER_RCPT_TO: + nparam = xstrdup(param + 1); + *strchr(nparam, '>') = '\0'; + param = nparam; + break; + case FILTER_STARTTLS: + /* TBD */ + break; + default: + break; + } + + free(fs->lastparam); + fs->lastparam = xstrdup(param); + + filter_protocol_internal(fs, &token, reqid, phase, param); + if (nparam) + free(nparam); +} + +static void +filter_protocol_next(uint64_t token, uint64_t reqid, enum filter_phase phase) +{ + struct filter_session *fs; + + /* session can legitimately disappear on a resume */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; + + filter_protocol_internal(fs, &token, reqid, phase, fs->lastparam); +} + +static void +filter_data(uint64_t reqid, const char *line) +{ + struct filter_session *fs; + + fs = tree_xget(&sessions, reqid); + + filter_data_internal(fs, 0, reqid, line); +} + +static void +filter_data_next(uint64_t token, uint64_t reqid, const char *line) +{ + struct filter_session *fs; + + /* session can legitimately disappear on a resume */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; + + filter_data_internal(fs, token, reqid, line); +} + +static void +filter_protocol_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *phase, const char *param) +{ + int n; + struct filter_session *fs; + struct timeval tv; + + gettimeofday(&tv, NULL); + + fs = tree_xget(&sessions, reqid); + if (strcmp(phase, "connect") == 0) + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s|%s\n", + PROTOCOL_VERSION, + (long long int)tv.tv_sec, tv.tv_usec, + phase, reqid, token, fs->rdns, param); + else + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s\n", + PROTOCOL_VERSION, + (long long int)tv.tv_sec, tv.tv_usec, + phase, reqid, token, param); + if (n == -1) + fatalx("failed to write to processor"); +} + +static void +filter_data_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *line) +{ + int n; + struct timeval tv; + + gettimeofday(&tv, NULL); + + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%s|%lld.%06ld|smtp-in|data-line|" + "%016"PRIx64"|%016"PRIx64"|%s\n", + PROTOCOL_VERSION, + (long long int)tv.tv_sec, tv.tv_usec, + reqid, token, line); + if (n == -1) + fatalx("failed to write to processor"); +} + +static void +filter_result_proceed(uint64_t reqid) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_PROCEED); + m_close(p_pony); +} + +static void +filter_result_junk(uint64_t reqid) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_JUNK); + m_close(p_pony); +} + +static void +filter_result_rewrite(uint64_t reqid, const char *param) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_REWRITE); + m_add_string(p_pony, param); + m_close(p_pony); +} + +static void +filter_result_reject(uint64_t reqid, const char *message) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_REJECT); + m_add_string(p_pony, message); + m_close(p_pony); +} + +static void +filter_result_disconnect(uint64_t reqid, const char *message) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_DISCONNECT); + m_add_string(p_pony, message); + m_close(p_pony); +} + + +/* below is code for builtin filters */ + +static int +filter_check_rdns_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->rdns_table == NULL) + return 0; + + if (table_match(filter->config->rdns_table, kind, key) > 0) + ret = 1; + + return filter->config->not_rdns_table < 0 ? !ret : ret; +} + +static int +filter_check_rdns_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->rdns_regex == NULL) + return 0; + + if (table_match(filter->config->rdns_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_rdns_regex < 0 ? !ret : ret; +} + +static int +filter_check_src_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->src_table == NULL) + return 0; + + if (table_match(filter->config->src_table, kind, key) > 0) + ret = 1; + return filter->config->not_src_table < 0 ? !ret : ret; +} + +static int +filter_check_src_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->src_regex == NULL) + return 0; + + if (table_match(filter->config->src_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_src_regex < 0 ? !ret : ret; +} + +static int +filter_check_helo_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->helo_table == NULL) + return 0; + + if (table_match(filter->config->helo_table, kind, key) > 0) + ret = 1; + return filter->config->not_helo_table < 0 ? !ret : ret; +} + +static int +filter_check_helo_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->helo_regex == NULL) + return 0; + + if (table_match(filter->config->helo_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_helo_regex < 0 ? !ret : ret; +} + +static int +filter_check_auth(struct filter *filter, const char *username) +{ + int ret = 0; + + if (!filter->config->auth) + return 0; + + ret = username ? 1 : 0; + + return filter->config->not_auth < 0 ? !ret : ret; +} + +static int +filter_check_auth_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->auth_table == NULL) + return 0; + + if (key && table_match(filter->config->auth_table, kind, key) > 0) + ret = 1; + + return filter->config->not_auth_table < 0 ? !ret : ret; +} + +static int +filter_check_auth_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->auth_regex == NULL) + return 0; + + if (key && table_match(filter->config->auth_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_auth_regex < 0 ? !ret : ret; +} + + +static int +filter_check_mail_from_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->mail_from_table == NULL) + return 0; + + if (table_match(filter->config->mail_from_table, kind, key) > 0) + ret = 1; + return filter->config->not_mail_from_table < 0 ? !ret : ret; +} + +static int +filter_check_mail_from_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->mail_from_regex == NULL) + return 0; + + if (table_match(filter->config->mail_from_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_mail_from_regex < 0 ? !ret : ret; +} + +static int +filter_check_rcpt_to_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->rcpt_to_table == NULL) + return 0; + + if (table_match(filter->config->rcpt_to_table, kind, key) > 0) + ret = 1; + return filter->config->not_rcpt_to_table < 0 ? !ret : ret; +} + +static int +filter_check_rcpt_to_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->rcpt_to_regex == NULL) + return 0; + + if (table_match(filter->config->rcpt_to_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_rcpt_to_regex < 0 ? !ret : ret; +} + +static int +filter_check_fcrdns(struct filter *filter, int fcrdns) +{ + int ret = 0; + + if (!filter->config->fcrdns) + return 0; + + ret = fcrdns == 1; + return filter->config->not_fcrdns < 0 ? !ret : ret; +} + +static int +filter_check_rdns(struct filter *filter, const char *hostname) +{ + int ret = 0; + struct netaddr netaddr; + + if (!filter->config->rdns) + return 0; + + /* this is a hack until smtp session properly deals with lack of rdns */ + ret = strcmp("", hostname); + if (ret == 0) + return filter->config->not_rdns < 0 ? !ret : ret; + + /* if text_to_netaddress succeeds, + * we don't have an rDNS so the filter should match + */ + ret = !text_to_netaddr(&netaddr, hostname); + return filter->config->not_rdns < 0 ? !ret : ret; +} + +static int +filter_builtins_notimpl(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return 0; +} + +static int +filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_t reqid) +{ + return filter_check_fcrdns(filter, fs->fcrdns) || + filter_check_rdns(filter, fs->rdns) || + filter_check_rdns_table(filter, K_DOMAIN, fs->rdns) || + filter_check_rdns_regex(filter, fs->rdns) || + filter_check_src_table(filter, K_NETADDR, ss_to_text(&fs->ss_src)) || + filter_check_src_regex(filter, ss_to_text(&fs->ss_src)) || + filter_check_helo_table(filter, K_DOMAIN, fs->helo) || + filter_check_helo_regex(filter, fs->helo) || + filter_check_auth(filter, fs->username) || + filter_check_auth_table(filter, K_STRING, fs->username) || + filter_check_auth_table(filter, K_CREDENTIALS, fs->username) || + filter_check_auth_regex(filter, fs->username) || + filter_check_mail_from_table(filter, K_MAILADDR, fs->mail_from) || + filter_check_mail_from_regex(filter, fs->mail_from); +} + +static int +filter_builtins_connect(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_helo(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_mail_from(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid) || + filter_check_rcpt_to_table(filter, K_MAILADDR, param) || + filter_check_rcpt_to_regex(filter, param); +} + +static int +filter_builtins_data(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_commit(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static void +report_smtp_broadcast(uint64_t, const char *, struct timeval *, const char *, + const char *, ...) __attribute__((__format__ (printf, 5, 6))); + +void +lka_report_init(void) +{ + struct reporters *tailq; + size_t i; + + dict_init(&report_smtp_in); + dict_init(&report_smtp_out); + + for (i = 0; i < nitems(smtp_events); ++i) { + tailq = xcalloc(1, sizeof (struct reporters)); + TAILQ_INIT(tailq); + dict_xset(&report_smtp_in, smtp_events[i].event, tailq); + + tailq = xcalloc(1, sizeof (struct reporters)); + TAILQ_INIT(tailq); + dict_xset(&report_smtp_out, smtp_events[i].event, tailq); + } +} + +void +lka_report_register_hook(const char *name, const char *hook) +{ + struct dict *subsystem; + struct reporter_proc *rp; + struct reporters *tailq; + void *iter; + size_t i; + + if (strncmp(hook, "smtp-in|", 8) == 0) { + subsystem = &report_smtp_in; + hook += 8; + } + else if (strncmp(hook, "smtp-out|", 9) == 0) { + subsystem = &report_smtp_out; + hook += 9; + } + else + fatalx("Invalid message direction: %s", hook); + + if (strcmp(hook, "*") == 0) { + iter = NULL; + while (dict_iter(subsystem, &iter, NULL, (void **)&tailq)) { + rp = xcalloc(1, sizeof *rp); + rp->name = xstrdup(name); + TAILQ_INSERT_TAIL(tailq, rp, entries); + } + return; + } + + for (i = 0; i < nitems(smtp_events); i++) + if (strcmp(hook, smtp_events[i].event) == 0) + break; + if (i == nitems(smtp_events)) + fatalx("Unrecognized report name: %s", hook); + + tailq = dict_get(subsystem, hook); + rp = xcalloc(1, sizeof *rp); + rp->name = xstrdup(name); + TAILQ_INSERT_TAIL(tailq, rp, entries); +} + +static void +report_smtp_broadcast(uint64_t reqid, const char *direction, struct timeval *tv, const char *event, + const char *format, ...) +{ + va_list ap; + struct dict *d; + struct reporters *tailq; + struct reporter_proc *rp; + + if (strcmp("smtp-in", direction) == 0) + d = &report_smtp_in; + + else if (strcmp("smtp-out", direction) == 0) + d = &report_smtp_out; + + else + fatalx("unexpected direction: %s", direction); + + tailq = dict_xget(d, event); + TAILQ_FOREACH(rp, tailq, entries) { + if (!lka_filter_proc_in_session(reqid, rp->name)) + continue; + + va_start(ap, format); + if (io_printf(lka_proc_get_io(rp->name), + "report|%s|%lld.%06ld|%s|%s|%016"PRIx64"%s", + PROTOCOL_VERSION, (long long int)tv->tv_sec, tv->tv_usec, direction, + event, reqid, format[0] != '\n' ? "|" : "") == -1 || + io_vprintf(lka_proc_get_io(rp->name), format, ap) == -1) + fatalx("failed to write to processor"); + va_end(ap); + } +} + +void +lka_report_smtp_link_connect(const char *direction, struct timeval *tv, uint64_t reqid, const char *rdns, + int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + struct filter_session *fs; + char src[NI_MAXHOST + 5]; + char dest[NI_MAXHOST + 5]; + uint16_t src_port = 0; + uint16_t dest_port = 0; + const char *fcrdns_str; + + if (ss_src->ss_family == AF_INET) + src_port = ntohs(((const struct sockaddr_in *)ss_src)->sin_port); + else if (ss_src->ss_family == AF_INET6) + src_port = ntohs(((const struct sockaddr_in6 *)ss_src)->sin6_port); + + if (ss_dest->ss_family == AF_INET) + dest_port = ntohs(((const struct sockaddr_in *)ss_dest)->sin_port); + else if (ss_dest->ss_family == AF_INET6) + dest_port = ntohs(((const struct sockaddr_in6 *)ss_dest)->sin6_port); + + if (strcmp(ss_to_text(ss_src), "local") == 0) { + (void)snprintf(src, sizeof src, "unix:%s", SMTPD_SOCKET); + (void)snprintf(dest, sizeof dest, "unix:%s", SMTPD_SOCKET); + } else { + (void)snprintf(src, sizeof src, "%s:%d", ss_to_text(ss_src), src_port); + (void)snprintf(dest, sizeof dest, "%s:%d", ss_to_text(ss_dest), dest_port); + } + + switch (fcrdns) { + case 1: + fcrdns_str = "pass"; + break; + case 0: + fcrdns_str = "fail"; + break; + default: + fcrdns_str = "error"; + break; + } + + fs = tree_xget(&sessions, reqid); + fs->rdns = xstrdup(rdns); + fs->fcrdns = fcrdns; + fs->ss_src = *ss_src; + fs->ss_dest = *ss_dest; + + report_smtp_broadcast(reqid, direction, tv, "link-connect", + "%s|%s|%s|%s\n", rdns, fcrdns_str, src, dest); +} + +void +lka_report_smtp_link_disconnect(const char *direction, struct timeval *tv, uint64_t reqid) +{ + report_smtp_broadcast(reqid, direction, tv, "link-disconnect", "\n"); +} + +void +lka_report_smtp_link_greeting(const char *direction, uint64_t reqid, + struct timeval *tv, const char *domain) +{ + report_smtp_broadcast(reqid, direction, tv, "link-greeting", "%s\n", + domain); +} + +void +lka_report_smtp_link_auth(const char *direction, struct timeval *tv, uint64_t reqid, + const char *username, const char *result) +{ + struct filter_session *fs; + + if (strcmp(result, "pass") == 0) { + fs = tree_xget(&sessions, reqid); + fs->username = xstrdup(username); + } + report_smtp_broadcast(reqid, direction, tv, "link-auth", "%s|%s\n", + username, result); +} + +void +lka_report_smtp_link_identify(const char *direction, struct timeval *tv, + uint64_t reqid, const char *method, const char *heloname) +{ + report_smtp_broadcast(reqid, direction, tv, "link-identify", "%s|%s\n", + method, heloname); +} + +void +lka_report_smtp_link_tls(const char *direction, struct timeval *tv, uint64_t reqid, const char *ciphers) +{ + report_smtp_broadcast(reqid, direction, tv, "link-tls", "%s\n", + ciphers); +} + +void +lka_report_smtp_tx_reset(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-reset", "%08x\n", + msgid); +} + +void +lka_report_smtp_tx_begin(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-begin", "%08x\n", + msgid); +} + +void +lka_report_smtp_tx_mail(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok) +{ + const char *result; + + switch (ok) { + case 1: + result = "ok"; + break; + case 0: + result = "permfail"; + break; + default: + result = "tempfail"; + break; + } + report_smtp_broadcast(reqid, direction, tv, "tx-mail", "%08x|%s|%s\n", + msgid, result, address); +} + +void +lka_report_smtp_tx_rcpt(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok) +{ + const char *result; + + switch (ok) { + case 1: + result = "ok"; + break; + case 0: + result = "permfail"; + break; + default: + result = "tempfail"; + break; + } + report_smtp_broadcast(reqid, direction, tv, "tx-rcpt", "%08x|%s|%s\n", + msgid, result, address); +} + +void +lka_report_smtp_tx_envelope(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, uint64_t evpid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-envelope", + "%08x|%016"PRIx64"\n", msgid, evpid); +} + +void +lka_report_smtp_tx_data(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, int ok) +{ + const char *result; + + switch (ok) { + case 1: + result = "ok"; + break; + case 0: + result = "permfail"; + break; + default: + result = "tempfail"; + break; + } + report_smtp_broadcast(reqid, direction, tv, "tx-data", "%08x|%s\n", + msgid, result); +} + +void +lka_report_smtp_tx_commit(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, size_t msgsz) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-commit", "%08x|%zd\n", + msgid, msgsz); +} + +void +lka_report_smtp_tx_rollback(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-rollback", "%08x\n", + msgid); +} + +void +lka_report_smtp_protocol_client(const char *direction, struct timeval *tv, uint64_t reqid, const char *command) +{ + report_smtp_broadcast(reqid, direction, tv, "protocol-client", "%s\n", + command); +} + +void +lka_report_smtp_protocol_server(const char *direction, struct timeval *tv, uint64_t reqid, const char *response) +{ + report_smtp_broadcast(reqid, direction, tv, "protocol-server", "%s\n", + response); +} + +void +lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint64_t reqid, + int phase, int response, const char *param) +{ + const char *phase_name; + const char *response_name; + + switch (phase) { + case FILTER_CONNECT: + phase_name = "connected"; + break; + case FILTER_HELO: + phase_name = "helo"; + break; + case FILTER_EHLO: + phase_name = "ehlo"; + break; + case FILTER_STARTTLS: + phase_name = "tls"; + break; + case FILTER_AUTH: + phase_name = "auth"; + break; + case FILTER_MAIL_FROM: + phase_name = "mail-from"; + break; + case FILTER_RCPT_TO: + phase_name = "rcpt-to"; + break; + case FILTER_DATA: + phase_name = "data"; + break; + case FILTER_DATA_LINE: + phase_name = "data-line"; + break; + case FILTER_RSET: + phase_name = "rset"; + break; + case FILTER_QUIT: + phase_name = "quit"; + break; + case FILTER_NOOP: + phase_name = "noop"; + break; + case FILTER_HELP: + phase_name = "help"; + break; + case FILTER_WIZ: + phase_name = "wiz"; + break; + case FILTER_COMMIT: + phase_name = "commit"; + break; + default: + phase_name = ""; + } + + switch (response) { + case FILTER_PROCEED: + response_name = "proceed"; + break; + case FILTER_JUNK: + response_name = "junk"; + break; + case FILTER_REWRITE: + response_name = "rewrite"; + break; + case FILTER_REJECT: + response_name = "reject"; + break; + case FILTER_DISCONNECT: + response_name = "disconnect"; + break; + default: + response_name = ""; + } + + report_smtp_broadcast(reqid, direction, tv, "filter-response", + "%s|%s%s%s\n", phase_name, response_name, param ? "|" : "", + param ? param : ""); +} + +void +lka_report_smtp_timeout(const char *direction, struct timeval *tv, uint64_t reqid) +{ + report_smtp_broadcast(reqid, direction, tv, "timeout", "\n"); +} + +void +lka_report_filter_report(uint64_t reqid, const char *name, int builtin, + const char *direction, struct timeval *tv, const char *message) +{ + report_smtp_broadcast(reqid, direction, tv, "filter-report", + "%s|%s|%s\n", builtin ? "builtin" : "proc", + name, message); +} + +void +lka_report_proc(const char *name, const char *line) +{ + char buffer[LINE_MAX]; + struct timeval tv; + char *ep, *sp, *direction; + uint64_t reqid; + + if (strlcpy(buffer, line + 7, sizeof(buffer)) >= sizeof(buffer)) + fatalx("Invalid report: line too long: %s", line); + + errno = 0; + tv.tv_sec = strtoll(buffer, &ep, 10); + if (ep[0] != '.' || errno != 0) + fatalx("Invalid report: invalid time: %s", line); + sp = ep + 1; + tv.tv_usec = strtol(sp, &ep, 10); + if (ep[0] != '|' || errno != 0) + fatalx("Invalid report: invalid time: %s", line); + if (ep - sp != 6) + fatalx("Invalid report: invalid time: %s", line); + + direction = ep + 1; + if (strncmp(direction, "smtp-in|", 8) == 0) { + direction[7] = '\0'; + direction += 7; +#if 0 + } else if (strncmp(direction, "smtp-out|", 9) == 0) { + direction[8] = '\0'; + direction += 8; +#endif + } else + fatalx("Invalid report: invalid direction: %s", line); + + reqid = strtoull(sp, &ep, 16); + if (ep[0] != '|' || errno != 0) + fatalx("Invalid report: invalid reqid: %s", line); + sp = ep + 1; + + lka_report_filter_report(reqid, name, 0, direction, &tv, sp); +} diff --git a/usr.sbin/smtpd/lka_session.c b/usr.sbin/smtpd/lka_session.c new file mode 100644 index 00000000..999e01d6 --- /dev/null +++ b/usr.sbin/smtpd/lka_session.c @@ -0,0 +1,556 @@ +/* $OpenBSD: lka_session.c,v 1.93 2019/09/20 17:46:05 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define EXPAND_DEPTH 10 + +#define F_WAITING 0x01 + +struct lka_session { + uint64_t id; /* given by smtp */ + + TAILQ_HEAD(, envelope) deliverylist; + struct expand expand; + + int flags; + int error; + const char *errormsg; + struct envelope envelope; + struct xnodes nodes; + /* waiting for fwdrq */ + struct rule *rule; + struct expandnode *node; +}; + +static void lka_expand(struct lka_session *, struct rule *, + struct expandnode *); +static void lka_submit(struct lka_session *, struct rule *, + struct expandnode *); +static void lka_resume(struct lka_session *); + +static int init; +static struct tree sessions; + +void +lka_session(uint64_t id, struct envelope *envelope) +{ + struct lka_session *lks; + struct expandnode xn; + + if (init == 0) { + init = 1; + tree_init(&sessions); + } + + lks = xcalloc(1, sizeof(*lks)); + lks->id = id; + RB_INIT(&lks->expand.tree); + TAILQ_INIT(&lks->deliverylist); + tree_xset(&sessions, lks->id, lks); + + lks->envelope = *envelope; + + TAILQ_INIT(&lks->nodes); + memset(&xn, 0, sizeof xn); + xn.type = EXPAND_ADDRESS; + xn.u.mailaddr = lks->envelope.rcpt; + lks->expand.parent = NULL; + lks->expand.rule = NULL; + lks->expand.queue = &lks->nodes; + expand_insert(&lks->expand, &xn); + lka_resume(lks); +} + +void +lka_session_forward_reply(struct forward_req *fwreq, int fd) +{ + struct lka_session *lks; + struct dispatcher *dsp; + struct rule *rule; + struct expandnode *xn; + int ret; + + lks = tree_xget(&sessions, fwreq->id); + xn = lks->node; + rule = lks->rule; + + lks->flags &= ~F_WAITING; + + switch (fwreq->status) { + case 0: + /* permanent failure while lookup ~/.forward */ + log_trace(TRACE_EXPAND, "expand: ~/.forward failed for user %s", + fwreq->user); + lks->error = LKA_PERMFAIL; + break; + case 1: + if (fd == -1) { + dsp = dict_get(env->sc_dispatchers, lks->rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (dsp->u.local.expand_only) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s and no default action on rule", fwreq->user); + lks->error = LKA_PERMFAIL; + } + else { + log_trace(TRACE_EXPAND, "expand: no .forward for " + "user %s, just deliver", fwreq->user); + lka_submit(lks, rule, xn); + } + } + else { + dsp = dict_get(env->sc_dispatchers, rule->dispatcher); + + /* expand for the current user and rule */ + lks->expand.rule = rule; + lks->expand.parent = xn; + + /* forwards_get() will close the descriptor no matter what */ + ret = forwards_get(fd, &lks->expand); + if (ret == -1) { + log_trace(TRACE_EXPAND, "expand: temporary " + "forward error for user %s", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (ret == 0) { + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (dsp->u.local.expand_only) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s and no default action on rule", fwreq->user); + lks->error = LKA_PERMFAIL; + } + else { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s, just deliver", fwreq->user); + lka_submit(lks, rule, xn); + } + } + } + break; + default: + /* temporary failure while looking up ~/.forward */ + lks->error = LKA_TEMPFAIL; + } + + if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + + lka_resume(lks); +} + +static void +lka_resume(struct lka_session *lks) +{ + struct envelope *ep; + struct expandnode *xn; + + if (lks->error) + goto error; + + /* pop next node and expand it */ + while ((xn = TAILQ_FIRST(&lks->nodes))) { + TAILQ_REMOVE(&lks->nodes, xn, tq_entry); + lka_expand(lks, xn->rule, xn); + if (lks->flags & F_WAITING) + return; + if (lks->error) + goto error; + } + + /* delivery list is empty, reject */ + if (TAILQ_FIRST(&lks->deliverylist) == NULL) { + log_trace(TRACE_EXPAND, "expand: lka_done: expanded to empty " + "delivery list"); + lks->error = LKA_PERMFAIL; + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + } + error: + if (lks->error) { + m_create(p_pony, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); + m_add_id(p_pony, lks->id); + m_add_int(p_pony, lks->error); + + if (lks->errormsg) + m_add_string(p_pony, lks->errormsg); + else { + if (lks->error == LKA_PERMFAIL) + m_add_string(p_pony, "550 Invalid recipient"); + else if (lks->error == LKA_TEMPFAIL) + m_add_string(p_pony, "451 Temporary failure"); + } + + m_close(p_pony); + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + free(ep); + } + } + else { + /* Process the delivery list and submit envelopes to queue */ + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + m_create(p_queue, IMSG_LKA_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_id(p_queue, lks->id); + m_add_envelope(p_queue, ep); + m_close(p_queue); + free(ep); + } + + m_create(p_queue, IMSG_LKA_ENVELOPE_COMMIT, 0, 0, -1); + m_add_id(p_queue, lks->id); + m_close(p_queue); + } + + expand_clear(&lks->expand); + tree_xpop(&sessions, lks->id); + free(lks); +} + +static void +lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct forward_req fwreq; + struct envelope ep; + struct expandnode node; + struct mailaddr maddr; + struct dispatcher *dsp; + struct table *userbase; + int r; + union lookup lk; + char *tag; + const char *srs_decoded; + + if (xn->depth >= EXPAND_DEPTH) { + log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep."); + lks->error = LKA_PERMFAIL; + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + return; + } + + switch (xn->type) { + case EXPAND_INVALID: + case EXPAND_INCLUDE: + fatalx("lka_expand: unexpected type"); + break; + + case EXPAND_ADDRESS: + + log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s " + "[depth=%d]", + xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth); + + + ep = lks->envelope; + ep.dest = xn->u.mailaddr; + if (xn->parent) /* nodes with parent are forward addresses */ + ep.flags |= EF_INTERNAL; + + /* handle SRS */ + if (env->sc_srs_key != NULL && + ep.sender.user[0] == '\0' && + (strncasecmp(ep.rcpt.user, "SRS0=", 5) == 0 || + strncasecmp(ep.rcpt.user, "SRS1=", 5) == 0)) { + srs_decoded = srs_decode(mailaddr_to_text(&ep.rcpt)); + if (srs_decoded && + text_to_mailaddr(&ep.rcpt, srs_decoded)) { + /* flag envelope internal and override rcpt */ + ep.flags |= EF_INTERNAL; + xn->u.mailaddr = ep.rcpt; + lks->envelope = ep; + } + else { + log_warn("SRS failed to decode: %s", + mailaddr_to_text(&ep.rcpt)); + } + } + + /* Pass the node through the ruleset */ + rule = ruleset_match(&ep); + if (rule == NULL || rule->reject) { + lks->error = (errno == EAGAIN) ? + LKA_TEMPFAIL : LKA_PERMFAIL; + break; + } + + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->type == DISPATCHER_REMOTE) { + lka_submit(lks, rule, xn); + } + else if (dsp->u.local.table_virtual) { + /* expand */ + lks->expand.rule = rule; + lks->expand.parent = xn; + + /* temporary replace the mailaddr with a copy where + * we eventually strip the '+'-part before lookup. + */ + maddr = xn->u.mailaddr; + xlowercase(maddr.user, xn->u.mailaddr.user, + sizeof maddr.user); + r = aliases_virtual_get(&lks->expand, &maddr); + if (r == -1) { + lks->error = LKA_TEMPFAIL; + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "error in virtual alias lookup"); + } + else if (r == 0) { + lks->error = LKA_PERMFAIL; + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "no aliases for virtual"); + } + if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + } + else { + lks->expand.rule = rule; + lks->expand.parent = xn; + xn->rule = rule; + + memset(&node, 0, sizeof node); + node.type = EXPAND_USERNAME; + xlowercase(node.u.user, xn->u.mailaddr.user, + sizeof node.u.user); + expand_insert(&lks->expand, &node); + } + break; + + case EXPAND_USERNAME: + log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s " + "[depth=%d, sameuser=%d]", + xn->u.user, xn->depth, xn->sameuser); + + /* expand aliases with the given rule */ + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + + lks->expand.rule = rule; + lks->expand.parent = xn; + + if (!xn->sameuser && + (dsp->u.local.table_alias || dsp->u.local.table_virtual)) { + if (dsp->u.local.table_alias) + r = aliases_get(&lks->expand, xn->u.user); + if (dsp->u.local.table_virtual) + r = aliases_virtual_get(&lks->expand, &xn->u.mailaddr); + if (r == -1) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "error in alias lookup"); + lks->error = LKA_TEMPFAIL; + if (lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + } + if (r) + break; + } + + /* gilles+hackers@ -> gilles@ */ + if ((tag = strchr(xn->u.user, *env->sc_subaddressing_delim)) != NULL) { + *tag++ = '\0'; + (void)strlcpy(xn->subaddress, tag, sizeof xn->subaddress); + } + + userbase = table_find(env, dsp->u.local.table_userbase); + r = table_lookup(userbase, K_USERINFO, xn->u.user, &lk); + if (r == -1) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "backend error while searching user"); + lks->error = LKA_TEMPFAIL; + break; + } + if (r == 0) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "user-part does not match system user"); + lks->error = LKA_PERMFAIL; + break; + } + xn->realuser = 1; + + if (xn->sameuser && xn->parent->forwarded) { + log_trace(TRACE_EXPAND, "expand: lka_expand: same " + "user, submitting"); + lka_submit(lks, rule, xn); + break; + } + + /* no aliases found, query forward file */ + lks->rule = rule; + lks->node = xn; + xn->forwarded = 1; + + memset(&fwreq, 0, sizeof(fwreq)); + fwreq.id = lks->id; + (void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user)); + (void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory)); + fwreq.uid = lk.userinfo.uid; + fwreq.gid = lk.userinfo.gid; + + m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1, + &fwreq, sizeof(fwreq)); + lks->flags |= F_WAITING; + break; + + case EXPAND_FILENAME: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } + log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s " + "[depth=%d]", xn->u.buffer, xn->depth); + lka_submit(lks, rule, xn); + break; + + case EXPAND_ERROR: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } + log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s " + "[depth=%d]", xn->u.buffer, xn->depth); + if (xn->u.buffer[0] == '4') + lks->error = LKA_TEMPFAIL; + else if (xn->u.buffer[0] == '5') + lks->error = LKA_PERMFAIL; + lks->errormsg = xn->u.buffer; + break; + + case EXPAND_FILTER: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } + log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s " + "[depth=%d]", xn->u.buffer, xn->depth); + lka_submit(lks, rule, xn); + break; + } +} + +static struct expandnode * +lka_find_ancestor(struct expandnode *xn, enum expand_type type) +{ + while (xn && (xn->type != type)) + xn = xn->parent; + if (xn == NULL) { + log_warnx("warn: lka_find_ancestor: no ancestors of type %d", + type); + fatalx(NULL); + } + return (xn); +} + +static void +lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct envelope *ep; + struct dispatcher *dsp; + const char *user; + const char *format; + + ep = xmemdup(&lks->envelope, sizeof *ep); + (void)strlcpy(ep->dispatcher, rule->dispatcher, sizeof ep->dispatcher); + + dsp = dict_xget(env->sc_dispatchers, ep->dispatcher); + + switch (dsp->type) { + case DISPATCHER_REMOTE: + if (xn->type != EXPAND_ADDRESS) + fatalx("lka_deliver: expect address"); + ep->type = D_MTA; + ep->dest = xn->u.mailaddr; + break; + + case DISPATCHER_BOUNCE: + case DISPATCHER_LOCAL: + if (xn->type != EXPAND_USERNAME && + xn->type != EXPAND_FILENAME && + xn->type != EXPAND_FILTER) + fatalx("lka_deliver: wrong type: %d", xn->type); + + ep->type = D_MDA; + ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr; + if (xn->type == EXPAND_USERNAME) { + (void)strlcpy(ep->mda_user, xn->u.user, sizeof(ep->mda_user)); + (void)strlcpy(ep->mda_subaddress, xn->subaddress, sizeof(ep->mda_subaddress)); + } + else { + user = !xn->parent->realuser ? + SMTPD_USER : + xn->parent->u.user; + (void)strlcpy(ep->mda_user, user, sizeof (ep->mda_user)); + + /* this battle needs to be fought ... */ + if (xn->type == EXPAND_FILTER && + strcmp(ep->mda_user, SMTPD_USER) == 0) + log_warnx("commands executed from aliases " + "run with %s privileges", SMTPD_USER); + + if (xn->type == EXPAND_FILENAME) + format = PATH_LIBEXEC"/mail.mboxfile -f %%{mbox.from} %s"; + else if (xn->type == EXPAND_FILTER) + format = "%s"; + (void)snprintf(ep->mda_exec, sizeof(ep->mda_exec), + format, xn->u.buffer); + } + break; + } + + TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry); +} diff --git a/usr.sbin/smtpd/log.c b/usr.sbin/smtpd/log.c new file mode 100644 index 00000000..14f681e3 --- /dev/null +++ b/usr.sbin/smtpd/log.c @@ -0,0 +1,220 @@ +/* $OpenBSD: log.c,v 1.20 2017/03/21 12:06:56 bluhm Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include +#include + +static int debug; +static int verbose; +const char *log_procname; + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "%s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} diff --git a/usr.sbin/smtpd/log.h b/usr.sbin/smtpd/log.h new file mode 100644 index 00000000..81d0973c --- /dev/null +++ b/usr.sbin/smtpd/log.h @@ -0,0 +1,52 @@ +/* $OpenBSD: log.h,v 1.8 2018/04/26 20:57:59 eric Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include "openbsd-compat.h" + +#include + +#include +#ifdef HAVE_SYS_CDEFS_H +#include +#endif + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ diff --git a/usr.sbin/smtpd/mail.lmtp.8 b/usr.sbin/smtpd/mail.lmtp.8 new file mode 100644 index 00000000..98dee00d --- /dev/null +++ b/usr.sbin/smtpd/mail.lmtp.8 @@ -0,0 +1,55 @@ +.\" $OpenBSD: mail.lmtp.8,v 1.1 2017/02/14 15:16:34 gilles Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 14 2017 $ +.Dt MAIL.LMTP 8 +.Os +.Sh NAME +.Nm mail.lmtp +.Nd deliver mail through LMTP +.Sh SYNOPSIS +.Nm mail.lmtp +.Op Fl d Ar destination +.Op Fl f Ar from +.Op Fl l Ar lhlo +.Ar user ... +.Sh DESCRIPTION +.Nm +reads the standard input up to an end-of-file and delivers it to +an LMTP server for each +.Ar user Ns 's +address. +The +.Ar user +must be a valid user name or email address. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d Ar destination +Specify the destination LMTP address. +.It Fl f Ar from +Specify the sender's name or email address. +.It Fl l Ar lhlo +Specify the LHLO argument used in the LMTP session. +By default, +.Nm mail.lmtp +will default to "localhost". +.El +.Sh EXIT STATUS +.Ex -std mail.lmtp +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/usr.sbin/smtpd/mail.lmtp.c b/usr.sbin/smtpd/mail.lmtp.c new file mode 100644 index 00000000..90b89990 --- /dev/null +++ b/usr.sbin/smtpd/mail.lmtp.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2017 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum phase { + PHASE_BANNER, + PHASE_HELO, + PHASE_MAILFROM, + PHASE_RCPTTO, + PHASE_DATA, + PHASE_EOM, + PHASE_QUIT +}; + +struct session { + const char *lhlo; + const char *mailfrom; + char *rcptto; + + char **rcpts; + int n_rcpts; +}; + +static int lmtp_connect(const char *); +static void lmtp_engine(int, struct session *); +static void stream_file(FILE *); + +int +main(int argc, char *argv[]) +{ + int ch; + int conn; + const char *destination = "localhost"; + struct session session; + + if (! geteuid()) + errx(EX_TEMPFAIL, "mail.lmtp: may not be executed as root"); + + session.lhlo = "localhost"; + session.mailfrom = getenv("SENDER"); + session.rcptto = NULL; + + while ((ch = getopt(argc, argv, "d:l:f:ru")) != -1) { + switch (ch) { + case 'd': + destination = optarg; + break; + case 'l': + session.lhlo = optarg; + break; + case 'f': + session.mailfrom = optarg; + break; + + case 'r': + session.rcptto = getenv("RECIPIENT"); + break; + + case 'u': + session.rcptto = getenv("USER"); + break; + + default: + break; + } + } + argc -= optind; + argv += optind; + + if (session.mailfrom == NULL) + errx(EX_TEMPFAIL, "sender must be specified with -f"); + + if (argc == 0 && session.rcptto == NULL) + errx(EX_TEMPFAIL, "no recipient was specified"); + + if (session.rcptto) { + session.rcpts = &session.rcptto; + session.n_rcpts = 1; + } + else { + session.rcpts = argv; + session.n_rcpts = argc; + } + + conn = lmtp_connect(destination); + lmtp_engine(conn, &session); + + return (0); +} + +static int +lmtp_connect_inet(const char *destination) +{ + struct addrinfo hints, *res, *res0; + char *destcopy = NULL; + const char *hostname = NULL; + const char *servname = NULL; + const char *cause = NULL; + char *p; + int n, s = -1, save_errno; + + if ((destcopy = strdup(destination)) == NULL) + err(EX_TEMPFAIL, NULL); + + servname = "25"; + hostname = destcopy; + p = destcopy; + if (*p == '[') { + if ((p = strchr(destcopy, ']')) == NULL) + errx(EX_TEMPFAIL, "inet: invalid address syntax"); + + /* remove [ and ] */ + *p = '\0'; + hostname++; + if (strncasecmp(hostname, "IPv6:", 5) == 0) + hostname += 5; + + /* extract port if any */ + switch (*(p+1)) { + case ':': + servname = p+2; + break; + case '\0': + break; + default: + errx(EX_TEMPFAIL, "inet: invalid address syntax"); + } + } + else if ((p = strchr(destcopy, ':')) != NULL) { + *p++ = '\0'; + servname = p; + } + + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + n = getaddrinfo(hostname, servname, &hints, &res0); + if (n) + errx(EX_TEMPFAIL, "inet: %s", gai_strerror(n)); + + for (res = res0; res; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s == -1) { + cause = "socket"; + continue; + } + + if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { + cause = "connect"; + save_errno = errno; + close(s); + errno = save_errno; + s = -1; + continue; + } + break; + } + + freeaddrinfo(res0); + if (s == -1) + errx(EX_TEMPFAIL, "%s", cause); + + free(destcopy); + return s; +} + +static int +lmtp_connect_unix(const char *destination) +{ + struct sockaddr_un addr; + int s; + + if (*destination != '/') + errx(EX_TEMPFAIL, "unix: path must be absolute"); + + if ((s = socket(PF_LOCAL, SOCK_STREAM, 0)) == -1) + err(EX_TEMPFAIL, NULL); + + memset(&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + if (strlcpy(addr.sun_path, destination, sizeof addr.sun_path) + >= sizeof addr.sun_path) + errx(EX_TEMPFAIL, "unix: socket path is too long"); + + if (connect(s, (struct sockaddr *)&addr, sizeof addr) == -1) + err(EX_TEMPFAIL, "connect"); + + return s; +} + +static int +lmtp_connect(const char *destination) +{ + if (destination[0] == '/') + return lmtp_connect_unix(destination); + return lmtp_connect_inet(destination); +} + +static void +lmtp_engine(int fd_read, struct session *session) +{ + int fd_write = 0; + FILE *file_read = 0; + FILE *file_write = 0; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + enum phase phase = PHASE_BANNER; + + if ((fd_write = dup(fd_read)) == -1) + err(EX_TEMPFAIL, "dup"); + + if ((file_read = fdopen(fd_read, "r")) == NULL) + err(EX_TEMPFAIL, "fdopen"); + + if ((file_write = fdopen(fd_write, "w")) == NULL) + err(EX_TEMPFAIL, "fdopen"); + + do { + fflush(file_write); + + if ((linelen = getline(&line, &linesize, file_read)) == -1) { + if (ferror(file_read)) + err(EX_TEMPFAIL, "getline"); + else + errx(EX_TEMPFAIL, "unexpected EOF from LMTP server"); + } + line[strcspn(line, "\n")] = '\0'; + line[strcspn(line, "\r")] = '\0'; + + if (linelen < 4 || + !isdigit((unsigned char)line[0]) || + !isdigit((unsigned char)line[1]) || + !isdigit((unsigned char)line[2]) || + (line[3] != ' ' && line[3] != '-')) + errx(EX_TEMPFAIL, "LMTP server sent an invalid line"); + + if (line[0] != (phase == PHASE_DATA ? '3' : '2')) + errx(EX_TEMPFAIL, "LMTP server error: %s", line); + + if (line[3] == '-') + continue; + + switch (phase) { + + case PHASE_BANNER: + fprintf(file_write, "LHLO %s\r\n", session->lhlo); + phase++; + break; + + case PHASE_HELO: + fprintf(file_write, "MAIL FROM:<%s>\r\n", session->mailfrom); + phase++; + break; + + case PHASE_MAILFROM: + fprintf(file_write, "RCPT TO:<%s>\r\n", session->rcpts[session->n_rcpts - 1]); + if (session->n_rcpts - 1 == 0) { + phase++; + break; + } + session->n_rcpts--; + break; + + case PHASE_RCPTTO: + fprintf(file_write, "DATA\r\n"); + phase++; + break; + + case PHASE_DATA: + stream_file(file_write); + fprintf(file_write, ".\r\n"); + phase++; + break; + + case PHASE_EOM: + fprintf(file_write, "QUIT\r\n"); + phase++; + break; + + case PHASE_QUIT: + exit(0); + } + } while (1); +} + +static void +stream_file(FILE *conn) +{ + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (line[0] == '.') + fprintf(conn, "."); + fprintf(conn, "%s\r\n", line); + } + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, "getline"); +} diff --git a/usr.sbin/smtpd/mail.maildir.8 b/usr.sbin/smtpd/mail.maildir.8 new file mode 100644 index 00000000..ce822698 --- /dev/null +++ b/usr.sbin/smtpd/mail.maildir.8 @@ -0,0 +1,45 @@ +.\" $OpenBSD: mail.maildir.8,v 1.5 2018/05/30 12:37:57 jmc Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: May 30 2018 $ +.Dt MAIL.MAILDIR 8 +.Os +.Sh NAME +.Nm mail.maildir +.Nd store mail in a maildir +.Sh SYNOPSIS +.Nm mail.maildir +.Op Fl j +.Op Ar pathname +.Sh DESCRIPTION +.Nm +reads the standard input up to an end-of-file and adds it to the +mail directory located in +.Ar pathname +or to the mail directory +.Pa Maildir +located in the user's home directory. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl j +Scan message for X-Spam and move to Junk folder if result is positive. +.El +.Sh EXIT STATUS +.Ex -std mail.maildir +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/usr.sbin/smtpd/mail.maildir.c b/usr.sbin/smtpd/mail.maildir.c new file mode 100644 index 00000000..fe6adba6 --- /dev/null +++ b/usr.sbin/smtpd/mail.maildir.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2017 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAILADDR_ESCAPE "!#$%&'*/?^`{|}~" + +static int maildir_subdir(const char *, char *, size_t); +static void maildir_mkdirs(const char *); +static void maildir_engine(const char *, int); +static int mkdirs_component(const char *, mode_t); +static int mkdirs(const char *, mode_t); + +int +main(int argc, char *argv[]) +{ + int ch; + int junk = 0; + + if (! geteuid()) + errx(1, "mail.maildir: may not be executed as root"); + + while ((ch = getopt(argc, argv, "j")) != -1) { + switch (ch) { + case 'j': + junk = 1; + break; + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) + errx(1, "mail.maildir: only one maildir is allowed"); + + maildir_engine(argv[0], junk); + + return (0); +} + +static int +maildir_subdir(const char *extension, char *dest, size_t len) +{ + char *sanitized; + + if (strlcpy(dest, extension, len) >= len) + return 0; + + for (sanitized = dest; *sanitized; sanitized++) + if (strchr(MAILADDR_ESCAPE, *sanitized)) + *sanitized = ':'; + + return 1; +} + +static void +maildir_mkdirs(const char *dirname) +{ + uint i; + int ret; + char pathname[PATH_MAX]; + char *subdirs[] = { "cur", "tmp", "new" }; + + if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) { + if (errno == EINVAL || errno == ENAMETOOLONG) + err(1, NULL); + err(EX_TEMPFAIL, NULL); + } + + for (i = 0; i < nitems(subdirs); ++i) { + ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname, + subdirs[i]); + if (ret < 0 || (size_t)ret >= sizeof pathname) + errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]); + if (mkdir(pathname, 0700) == -1 && errno != EEXIST) + err(EX_TEMPFAIL, NULL); + } +} + +static void +maildir_engine(const char *dirname, int junk) +{ + char rootpath[PATH_MAX]; + char junkpath[PATH_MAX]; + char extpath[PATH_MAX]; + char subdir[PATH_MAX]; + char filename[PATH_MAX]; + char hostname[HOST_NAME_MAX+1]; + + char tmp[PATH_MAX]; + char new[PATH_MAX]; + + int ret; + + int fd; + FILE *fp; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + struct stat sb; + char *home; + char *extension; + + int is_junk = 0; + int in_hdr = 1; + + if (dirname == NULL) { + if ((home = getenv("HOME")) == NULL) + err(1, NULL); + ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home); + if (ret < 0 || (size_t)ret >= sizeof rootpath) + errc(1, ENAMETOOLONG, "%s/Maildir", home); + dirname = rootpath; + } + maildir_mkdirs(dirname); + + if (junk) { + /* create Junk subdirectory */ + ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname); + if (ret < 0 || (size_t)ret >= sizeof junkpath) + errc(1, ENAMETOOLONG, "%s/.Junk", dirname); + maildir_mkdirs(junkpath); + } + + if ((extension = getenv("EXTENSION")) != NULL) { + if (maildir_subdir(extension, subdir, sizeof(subdir)) && + subdir[0]) { + ret = snprintf(extpath, sizeof extpath, "%s/.%s", + dirname, subdir); + if (ret < 0 || (size_t)ret >= sizeof extpath) + errc(1, ENAMETOOLONG, "%s/.%s", + dirname, subdir); + if (stat(extpath, &sb) != -1) { + dirname = extpath; + maildir_mkdirs(dirname); + } + } + } + + if (gethostname(hostname, sizeof hostname) != 0) + (void)strlcpy(hostname, "localhost", sizeof hostname); + + (void)snprintf(filename, sizeof filename, "%lld.%08x.%s", + (long long int) time(NULL), + arc4random(), + hostname); + + (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename); + + fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600); + if (fd == -1) + err(EX_TEMPFAIL, NULL); + if ((fp = fdopen(fd, "w")) == NULL) + err(EX_TEMPFAIL, NULL); + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (line[0] == '\0') + in_hdr = 0; + if (junk && in_hdr && + (strcasecmp(line, "x-spam: yes") == 0 || + strcasecmp(line, "x-spam-flag: yes") == 0)) + is_junk = 1; + fprintf(fp, "%s\n", line); + } + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, NULL); + + if (fflush(fp) == EOF || + ferror(fp) || + fsync(fd) == -1 || + fclose(fp) == EOF) + err(EX_TEMPFAIL, NULL); + + (void)snprintf(new, sizeof new, "%s/new/%s", + is_junk ? junkpath : dirname, filename); + + if (rename(tmp, new) == -1) + err(EX_TEMPFAIL, NULL); + + exit(0); +} + + +static int +mkdirs_component(const char *path, mode_t mode) +{ + struct stat sb; + + if (stat(path, &sb) == -1) { + if (errno != ENOENT) + return 0; + if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1) + return 0; + } + else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + return 0; + } + + return 1; +} + +static int +mkdirs(const char *path, mode_t mode) +{ + char buf[PATH_MAX]; + int i = 0; + int done = 0; + const char *p; + + /* absolute path required */ + if (*path != '/') { + errno = EINVAL; + return 0; + } + + /* make sure we don't exceed PATH_MAX */ + if (strlen(path) >= sizeof buf) { + errno = ENAMETOOLONG; + return 0; + } + + memset(buf, 0, sizeof buf); + for (p = path; *p; p++) { + if (*p == '/') { + if (buf[0] != '\0') + if (!mkdirs_component(buf, mode)) + return 0; + while (*p == '/') + p++; + buf[i++] = '/'; + buf[i++] = *p; + if (*p == '\0' && ++done) + break; + continue; + } + buf[i++] = *p; + } + if (!done) + if (!mkdirs_component(buf, mode)) + return 0; + + if (chmod(path, mode) == -1) + return 0; + + return 1; +} diff --git a/usr.sbin/smtpd/mail.mboxfile.8 b/usr.sbin/smtpd/mail.mboxfile.8 new file mode 100644 index 00000000..015adcb5 --- /dev/null +++ b/usr.sbin/smtpd/mail.mboxfile.8 @@ -0,0 +1,34 @@ +.\" $OpenBSD: mail.mboxfile.8,v 1.1 2018/07/25 10:19:28 gilles Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 25 2018 $ +.Dt MAIL.MDA 8 +.Os +.Sh NAME +.Nm mail.mboxfile +.Nd deliver mail to a file in mbox format +.Sh SYNOPSIS +.Nm mail.mboxfile +.Ar file +.Sh DESCRIPTION +.Nm +appends mail to a file in mbox format and acknowledges delivery success or failure +with its exit status. +.Sh EXIT STATUS +.Ex -std mail.mboxfile +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/usr.sbin/smtpd/mail.mboxfile.c b/usr.sbin/smtpd/mail.mboxfile.c new file mode 100644 index 00000000..097a8d96 --- /dev/null +++ b/usr.sbin/smtpd/mail.mboxfile.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void mboxfile_engine(const char *sender, const char *filename); + +int +main(int argc, char *argv[]) +{ + int ch; + char *sender = ""; + + if (! geteuid()) + errx(1, "mail.mboxfile: may not be executed as root"); + + while ((ch = getopt(argc, argv, "f:")) != -1) { + switch (ch) { + case 'f': + sender = optarg; + break; + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) + errx(1, "mail.mboxfile: only one mboxfile is allowed"); + + mboxfile_engine(sender, argv[0]); + + return (0); +} + +static void +mboxfile_engine(const char *sender, const char *filename) +{ + int fd; + FILE *fp; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + time_t now; + + time(&now); + +#ifndef O_EXLOCK +#define O_EXLOCK 0 +#endif + fd = open(filename, O_CREAT | O_APPEND | O_WRONLY | O_EXLOCK, 0600); +#ifndef HAVE_O_EXLOCK + /* XXX : do something! */ +#endif + if (fd == -1) + err(EX_TEMPFAIL, NULL); + + if ((fp = fdopen(fd, "w")) == NULL) + err(EX_TEMPFAIL, NULL); + + fprintf(fp, "From %s %s", sender, ctime(&now)); + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (strncmp(line, "From ", 5) == 0) + fprintf(fp, ">%s\n", line); + else + fprintf(fp, "%s\n", line); + } + fprintf(fp, "\n"); + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, NULL); + + if (fflush(fp) == EOF || + ferror(fp) || + (fsync(fd) == -1 && errno != EINVAL) || + fclose(fp) == EOF) + err(EX_TEMPFAIL, NULL); +} diff --git a/usr.sbin/smtpd/mail.mda.8 b/usr.sbin/smtpd/mail.mda.8 new file mode 100644 index 00000000..61fed733 --- /dev/null +++ b/usr.sbin/smtpd/mail.mda.8 @@ -0,0 +1,35 @@ +.\" $OpenBSD: mail.mda.8,v 1.1 2017/08/09 07:56:10 gilles Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: August 9 2017 $ +.Dt MAIL.MDA 8 +.Os +.Sh NAME +.Nm mail.mda +.Nd deliver mail to a program +.Sh SYNOPSIS +.Nm mail.mda +.Ar program +.Sh DESCRIPTION +.Nm +executes the program and its parameters. +The program must read from the standard input up to an end-of-file +and acknowledge delivery success or failure with its exit status. +.Sh EXIT STATUS +.Ex -std mail.mda +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/usr.sbin/smtpd/mail.mda.c b/usr.sbin/smtpd/mail.mda.c new file mode 100644 index 00000000..23958071 --- /dev/null +++ b/usr.sbin/smtpd/mail.mda.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + int ch; + int ret; + + if (! geteuid()) + errx(1, "mail.mda: may not be executed as root"); + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + errx(1, "mail.mda: command required"); + + if (argc > 1) + errx(1, "mail.mda: only one command is supported"); + + /* could not obtain a shell or could not obtain wait status, + * tempfail */ + if ((ret = system(argv[0])) == -1) + errx(EX_TEMPFAIL, "%s", strerror(errno)); + + /* not exited properly but we have no details, + * tempfail */ + if (! WIFEXITED(ret)) + exit(EX_TEMPFAIL); + + exit(WEXITSTATUS(ret)); +} diff --git a/usr.sbin/smtpd/mail/Makefile b/usr.sbin/smtpd/mail/Makefile new file mode 100644 index 00000000..b2bc2a26 --- /dev/null +++ b/usr.sbin/smtpd/mail/Makefile @@ -0,0 +1,20 @@ +# $OpenBSD: Makefile,v 1.8 2018/07/25 10:19:28 gilles Exp $ +.PATH: ${.CURDIR}/.. + +PROGS= mail.lmtp mail.maildir mail.mboxfile mail.mda + +MAN= mail.lmtp.8 mail.maildir.8 mail.mboxfile.8 mail.mda.8 + +BINOWN= root +BINGRP= wheel + +BINDIR= /usr/libexec + +CFLAGS+= -fstack-protector-all +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -Werror-implicit-function-declaration + +.include diff --git a/usr.sbin/smtpd/mailaddr.c b/usr.sbin/smtpd/mailaddr.c new file mode 100644 index 00000000..4346e3dc --- /dev/null +++ b/usr.sbin/smtpd/mailaddr.c @@ -0,0 +1,135 @@ +/* $OpenBSD: mailaddr.c,v 1.3 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2015 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static int +mailaddr_line_split(char **line, char **ret) +{ + static char buffer[LINE_MAX]; + int esc, dq, sq; + size_t i; + char *s; + + memset(buffer, 0, sizeof buffer); + esc = dq = sq = 0; + i = 0; + for (s = *line; (*s) && (i < sizeof(buffer)); ++s) { + if (esc) { + buffer[i++] = *s; + esc = 0; + continue; + } + if (*s == '\\') { + esc = 1; + continue; + } + if (*s == ',' && !dq && !sq) { + *ret = buffer; + *line = s+1; + return (1); + } + + buffer[i++] = *s; + esc = 0; + + if (*s == '"' && !sq) + dq ^= 1; + if (*s == '\'' && !dq) + sq ^= 1; + } + + if (esc || dq || sq || i == sizeof(buffer)) + return (-1); + + *ret = buffer; + *line = s; + return (i ? 1 : 0); +} + +int +mailaddr_line(struct maddrmap *maddrmap, const char *s) +{ + struct maddrnode mn; + char buffer[LINE_MAX]; + char *p, *subrcpt; + int ret; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + + p = buffer; + while ((ret = mailaddr_line_split(&p, &subrcpt)) > 0) { + subrcpt = strip(subrcpt); + if (subrcpt[0] == '\0') + continue; + if (!text_to_mailaddr(&mn.mailaddr, subrcpt)) + return 0; + log_debug("subrcpt: [%s]", subrcpt); + maddrmap_insert(maddrmap, &mn); + } + + if (ret >= 0) + return 1; + /* expand_line_split() returned < 0 */ + return 0; +} + +void +maddrmap_init(struct maddrmap *maddrmap) +{ + TAILQ_INIT(&maddrmap->queue); +} + +void +maddrmap_insert(struct maddrmap *maddrmap, struct maddrnode *maddrnode) +{ + struct maddrnode *mn; + + mn = xmemdup(maddrnode, sizeof *maddrnode); + TAILQ_INSERT_TAIL(&maddrmap->queue, mn, entries); +} + +void +maddrmap_free(struct maddrmap *maddrmap) +{ + struct maddrnode *mn; + + while ((mn = TAILQ_FIRST(&maddrmap->queue))) { + TAILQ_REMOVE(&maddrmap->queue, mn, entries); + free(mn); + } + free(maddrmap); +} diff --git a/usr.sbin/smtpd/makemap.8 b/usr.sbin/smtpd/makemap.8 new file mode 100644 index 00000000..674bef6f --- /dev/null +++ b/usr.sbin/smtpd/makemap.8 @@ -0,0 +1,174 @@ +.\" $OpenBSD: makemap.8,v 1.30 2018/11/25 14:41:16 gilles Exp $ +.\" +.\" Copyright (c) 2009 Jacek Masiulaniec +.\" Copyright (c) 2008-2009 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: November 25 2018 $ +.Dt MAKEMAP 8 +.Os +.Sh NAME +.Nm makemap +.Nd create database maps for smtpd +.Sh SYNOPSIS +.Nm makemap +.Op Fl U +.Op Fl d Ar dbtype +.Op Fl o Ar dbfile +.Op Fl t Ar type +.Ar file +.Sh DESCRIPTION +Maps provide a generic interface for associating textual key to a value. +Such associations may be accessed through a plaintext file, database, or DNS. +The format of these file types is described below. +.Nm +itself creates the database maps used by keyed map lookups specified in +.Xr smtpd.conf 5 . +.Pp +.Nm +reads input from +.Ar file +and writes data to a file whose name is made by adding a +.Dq .db +suffix to +.Ar file . +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +In all cases, +.Nm +reads lines consisting of words separated by whitespace. +The first word of a line is the database key; +the remainder represents the mapped value. +The database key and value may optionally be separated +by the colon character. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d Ar dbtype +Specify the format of the database. +Available formats are +.Ar hash +and +.Ar btree . +The default value is +.Ar hash . +.It Fl o Ar dbfile +Write the generated database to +.Ar dbfile . +.It Fl t Ar type +Specify the format of the resulting map file. +The default map format is suitable for storing simple, unstructured, +key-to-value string associations. +However, if the mapped value has special meaning, +as in the case of the virtual domains file, +a suitable +.Ar type +must be provided. +The available output types are: +.Bl -tag -width "aliases" +.It Cm aliases +The mapped value is a comma-separated list of mail destinations. +This format can be used for building user aliases and +user mappings for virtual domain files. +.It Cm set +There is no mapped value \(en a map of this type will only allow for +the lookup of keys. +This format can be used for building primary domain maps. +.El +.It Fl U +Instead of generating a database map from text input, +dump the contents of a database map as text +with the key and value separated with a tab. +.El +.Sh PRIMARY DOMAINS +Primary domains can be kept in tables. +To create a primary domain table, add each primary domain on a +single line by itself. +.Pp +In addition to adding an entry to the primary domain map, +one must add a filter rule that accepts mail for the domain +map, for example: +.Bd -literal -offset indent +table domains db:/etc/mail/domains.db + +action "local" mbox + +match for domain action "local" +.Ed +.Sh VIRTUAL DOMAINS +Virtual domains may also be kept in tables. +To create a virtual domain table, add each virtual domain on a +single line by itself. +.Pp +Virtual domains expect a mapping of virtual users to real users +in order to determine if a recipient is accepted or not. +The mapping format is an extension to +.Xr aliases 5 , +which allows the use of +.Dq user@domain.tld +to accept user only on the specified domain, +.Dq user +to accept the user for any of the virtual domains, +.Dq @domain.tld +to provide a catch-all for the specified domain and +.Dq @ +to provide a global catch-all for all domains. +.Xr smtpd 8 +will perform the lookups in that specific order. +.Pp +To create single virtual address, add +.Dq user@example.com user +to the users map. +To handle all mail destined to any user at example.com, add +.Dq @example.com user +to the virtual map. +.Pp +In addition to adding an entry to the virtual map, +one must add a filter rule that accepts mail for virtual domains, +for example: +.Bd -literal -offset indent +table vdomains db:/etc/mail/vdomains.db +table vusers db:/etc/mail/users.db + +action "local" mbox virtual + +match for domain action "local" +match for domain "example.org" action "local" +.Ed +.Sh FILES +.Bl -tag -width "/etc/mail/aliasesXXX" -compact +.It Pa /etc/mail/aliases +List of user mail aliases. +.It Pa /etc/mail/secrets +List of remote host credentials. +.El +.Sh EXIT STATUS +.Ex -std makemap +.Sh SEE ALSO +.Xr aliases 5 , +.Xr smtpd.conf 5 , +.Xr table 5 , +.Xr newaliases 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +command first appeared in +.Ox 4.6 +as a replacement for the equivalent command shipped with sendmail. diff --git a/usr.sbin/smtpd/makemap.c b/usr.sbin/smtpd/makemap.c new file mode 100644 index 00000000..10e3f555 --- /dev/null +++ b/usr.sbin/smtpd/makemap.c @@ -0,0 +1,521 @@ +/* $OpenBSD: makemap.c,v 1.73 2020/02/24 16:16:07 millert Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008-2009 Jacek Masiulaniec + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifdef HAVE_SYS_FILE_H +#include /* Needed for flock */ +#endif +#include +#include +#include +#include +#include + +#include +#ifdef HAVE_DB_H +#include +#elif defined(HAVE_DB1_DB_H) +#include +#elif defined(HAVE_DB_185_H) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UTIL_H +#include +#endif +#ifdef HAVE_LIBUTIL_H +#include +#endif + +#include "smtpd.h" +#include "log.h" + +#define PATH_ALIASES SMTPD_CONFDIR "/aliases" + +static void usage(void); +static int parse_map(DB *, int *, char *); +static int parse_entry(DB *, int *, char *, size_t, size_t); +static int parse_mapentry(DB *, int *, char *, size_t, size_t); +static int parse_setentry(DB *, int *, char *, size_t, size_t); +static int make_plain(DBT *, char *); +static int make_aliases(DBT *, char *); +static char *conf_aliases(char *); +static int dump_db(const char *, DBTYPE); + +char *source; +static int mode; + +enum output_type { + T_PLAIN, + T_ALIASES, + T_SET +} type; + +/* + * Stub functions so that makemap compiles using minimum object files. + */ +int +fork_proc_backend(const char *backend, const char *conf, const char *procname) +{ + return (-1); +} + +int +makemap(int prog_mode, int argc, char *argv[]) +{ + struct stat sb; + char dbname[PATH_MAX]; + DB *db; + const char *opts; + char *conf, *oflag = NULL; + int ch, dbputs = 0, Uflag = 0; + DBTYPE dbtype = DB_HASH; + char *p; + gid_t gid; + int fd = -1; + + gid = getgid(); + if (setresgid(gid, gid, gid) == -1) + err(1, "setresgid"); + + if ((env = config_default()) == NULL) + err(1, NULL); + + log_init(1, LOG_MAIL); + + mode = prog_mode; + conf = CONF_FILE; + type = T_PLAIN; + opts = "b:C:d:ho:O:t:U"; + if (mode == P_NEWALIASES) + opts = "f:h"; + + while ((ch = getopt(argc, argv, opts)) != -1) { + switch (ch) { + case 'b': + if (optarg && strcmp(optarg, "i") == 0) + mode = P_NEWALIASES; + break; + case 'C': + break; /* for compatibility */ + case 'd': + if (strcmp(optarg, "hash") == 0) + dbtype = DB_HASH; + else if (strcmp(optarg, "btree") == 0) + dbtype = DB_BTREE; + else + errx(1, "unsupported DB type '%s'", optarg); + break; + case 'f': + conf = optarg; + break; + case 'o': + oflag = optarg; + break; + case 'O': + if (strncmp(optarg, "AliasFile=", 10) != 0) + break; + type = T_ALIASES; + p = strchr(optarg, '='); + source = ++p; + break; + case 't': + if (strcmp(optarg, "aliases") == 0) + type = T_ALIASES; + else if (strcmp(optarg, "set") == 0) + type = T_SET; + else + errx(1, "unsupported type '%s'", optarg); + break; + case 'U': + Uflag = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* sendmail-compat makemap ... re-execute using proper interface */ + if (argc == 2) { + if (oflag) + usage(); + + p = strstr(argv[1], ".db"); + if (p == NULL || strcmp(p, ".db") != 0) { + if (!bsnprintf(dbname, sizeof dbname, "%s.db", + argv[1])) + errx(1, "database name too long"); + } + else { + if (strlcpy(dbname, argv[1], sizeof dbname) + >= sizeof dbname) + errx(1, "database name too long"); + } + + execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname, + "-", (char *)NULL); + err(1, "execl"); + } + + if (mode == P_NEWALIASES) { + if (geteuid()) + errx(1, "need root privileges"); + if (argc != 0) + usage(); + type = T_ALIASES; + if (source == NULL) + source = conf_aliases(conf); + } else { + if (argc != 1) + usage(); + source = argv[0]; + } + + if (Uflag) + return dump_db(source, dbtype); + + if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1) + err(1, "asprintf"); + + if (strcmp(source, "-") != 0) + if (stat(source, &sb) == -1) + err(1, "stat: %s", source); + + if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag)) + errx(1, "path too long"); + if ((fd = mkstemp(dbname)) == -1) + err(1, "mkstemp"); + + db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL); + if (db == NULL) { + warn("dbopen: %s", dbname); + goto bad; + } + + if (strcmp(source, "-") != 0) + if (fchmod(db->fd(db), sb.st_mode) == -1 || + fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) { + warn("couldn't carry ownership and perms to %s", + dbname); + goto bad; + } + + if (!parse_map(db, &dbputs, source)) + goto bad; + + if (db->close(db) == -1) { + warn("dbclose: %s", dbname); + goto bad; + } + + /* force to disk before renaming over an existing file */ + if (fsync(fd) == -1) { + warn("fsync: %s", dbname); + goto bad; + } + if (close(fd) == -1) { + fd = -1; + warn("close: %s", dbname); + goto bad; + } + fd = -1; + + if (rename(dbname, oflag) == -1) { + warn("rename"); + goto bad; + } + + if (mode == P_NEWALIASES) + printf("%s: %d aliases\n", source, dbputs); + else if (dbputs == 0) + warnx("warning: empty map created: %s", oflag); + + return 0; +bad: + if (fd != -1) + close(fd); + unlink(dbname); + return 1; +} + +static int +parse_map(DB *db, int *dbputs, char *filename) +{ + FILE *fp; + char *line; + size_t len; + size_t lineno = 0; + + if (strcmp(filename, "-") == 0) + fp = fdopen(0, "r"); + else + fp = fopen(filename, "r"); + if (fp == NULL) { + warn("%s", filename); + return 0; + } + + if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) { + if (errno == EWOULDBLOCK) + warnx("%s is locked", filename); + else + warn("%s: flock", filename); + fclose(fp); + return 0; + } + + while ((line = fparseln(fp, &len, &lineno, + NULL, FPARSELN_UNESCCOMM)) != NULL) { + if (!parse_entry(db, dbputs, line, len, lineno)) { + free(line); + fclose(fp); + return 0; + } + free(line); + } + + fclose(fp); + return 1; +} + +static int +parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) +{ + switch (type) { + case T_PLAIN: + case T_ALIASES: + return parse_mapentry(db, dbputs, line, len, lineno); + case T_SET: + return parse_setentry(db, dbputs, line, len, lineno); + } + return 0; +} + +static int +parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) +{ + DBT key; + DBT val; + char *keyp; + char *valp; + + keyp = line; + while (isspace((unsigned char)*keyp)) + keyp++; + if (*keyp == '\0') + return 1; + + valp = keyp; + strsep(&valp, " \t:"); + if (valp == NULL || valp == keyp) + goto bad; + while (*valp == ':' || isspace((unsigned char)*valp)) + valp++; + if (*valp == '\0') + goto bad; + + /* Check for dups. */ + key.data = keyp; + key.size = strlen(keyp) + 1; + + xlowercase(key.data, key.data, strlen(key.data) + 1); + if (db->get(db, &key, &val, 0) == 0) { + warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); + return 0; + } + + if (type == T_PLAIN) { + if (!make_plain(&val, valp)) + goto bad; + } + else if (type == T_ALIASES) { + if (!make_aliases(&val, valp)) + goto bad; + } + + if (db->put(db, &key, &val, 0) == -1) { + warn("dbput"); + return 0; + } + + (*dbputs)++; + + free(val.data); + + return 1; + +bad: + warnx("%s:%zd: invalid entry", source, lineno); + return 0; +} + +static int +parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) +{ + DBT key; + DBT val; + char *keyp; + + keyp = line; + while (isspace((unsigned char)*keyp)) + keyp++; + if (*keyp == '\0') + return 1; + + val.data = ""; + val.size = strlen(val.data) + 1; + + /* Check for dups. */ + key.data = keyp; + key.size = strlen(keyp) + 1; + xlowercase(key.data, key.data, strlen(key.data) + 1); + if (db->get(db, &key, &val, 0) == 0) { + warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); + return 0; + } + + if (db->put(db, &key, &val, 0) == -1) { + warn("dbput"); + return 0; + } + + (*dbputs)++; + + return 1; +} + +static int +make_plain(DBT *val, char *text) +{ + val->data = xstrdup(text); + val->size = strlen(text) + 1; + + return (val->size); +} + +static int +make_aliases(DBT *val, char *text) +{ + struct expandnode xn; + char *subrcpt; + char *origtext; + + val->data = NULL; + val->size = 0; + + origtext = xstrdup(text); + + while ((subrcpt = strsep(&text, ",")) != NULL) { + /* subrcpt: strip initial and trailing whitespace. */ + subrcpt = strip(subrcpt); + if (*subrcpt == '\0') + goto error; + + if (!text_to_expandnode(&xn, subrcpt)) + goto error; + } + + val->data = origtext; + val->size = strlen(origtext) + 1; + return (val->size); + +error: + free(origtext); + + return 0; +} + +static char * +conf_aliases(char *cfgpath) +{ + struct table *table; + char *path; + char *p; + + if (parse_config(env, cfgpath, 0)) + exit(1); + + table = table_find(env, "aliases"); + if (table == NULL) + return (PATH_ALIASES); + + path = xstrdup(table->t_config); + p = strstr(path, ".db"); + if (p == NULL || strcmp(p, ".db") != 0) { + return (path); + } + *p = '\0'; + return (path); +} + +static int +dump_db(const char *dbname, DBTYPE dbtype) +{ + DB *db; + DBT key, val; + char *keystr, *valstr; + int r; + + db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL); + if (db == NULL) + err(1, "dbopen: %s", dbname); + + for (r = db->seq(db, &key, &val, R_FIRST); r == 0; + r = db->seq(db, &key, &val, R_NEXT)) { + keystr = key.data; + valstr = val.data; + if (keystr[key.size - 1] == '\0') + key.size--; + if (valstr[val.size - 1] == '\0') + val.size--; + printf("%.*s\t%.*s\n", (int)key.size, keystr, + (int)val.size, valstr); + } + if (r == -1) + err(1, "db->seq: %s", dbname); + + if (db->close(db) == -1) + err(1, "dbclose: %s", dbname); + + return 0; +} + +static void +usage(void) +{ + if (mode == P_NEWALIASES) + fprintf(stderr, "usage: newaliases [-f file]\n"); + else + fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] " + "[-t type] file\n"); + exit(1); +} diff --git a/usr.sbin/smtpd/mda.c b/usr.sbin/smtpd/mda.c new file mode 100644 index 00000000..5e8fec19 --- /dev/null +++ b/usr.sbin/smtpd/mda.c @@ -0,0 +1,919 @@ +/* $OpenBSD: mda.c,v 1.141 2019/10/03 08:50:08 gilles Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" + +#define MDA_HIWAT 65536 + +struct mda_envelope { + TAILQ_ENTRY(mda_envelope) entry; + uint64_t session_id; + uint64_t id; + time_t creation; + char *sender; + char *rcpt; + char *dest; + char *user; + char *dispatcher; + char *mda_subaddress; + char *mda_exec; +}; + +#define USER_WAITINFO 0x01 +#define USER_RUNNABLE 0x02 +#define USER_ONHOLD 0x04 +#define USER_HOLDQ 0x08 + +struct mda_user { + uint64_t id; + TAILQ_ENTRY(mda_user) entry; + TAILQ_ENTRY(mda_user) entry_runnable; + char name[LOGIN_NAME_MAX]; + char usertable[PATH_MAX]; + size_t evpcount; + TAILQ_HEAD(, mda_envelope) envelopes; + int flags; + size_t running; + struct userinfo userinfo; +}; + +struct mda_session { + uint64_t id; + struct mda_user *user; + struct mda_envelope *evp; + struct io *io; + FILE *datafp; +}; + +static void mda_io(struct io *, int, void *); +static int mda_check_loop(FILE *, struct mda_envelope *); +static int mda_getlastline(int, char *, size_t); +static void mda_done(struct mda_session *); +static void mda_fail(struct mda_user *, int, const char *, + enum enhanced_status_code); +static void mda_drain(void); +static void mda_log(const struct mda_envelope *, const char *, const char *); +static void mda_queue_ok(uint64_t); +static void mda_queue_tempfail(uint64_t, const char *, + enum enhanced_status_code); +static void mda_queue_permfail(uint64_t, const char *, enum enhanced_status_code); +static void mda_queue_loop(uint64_t); +static struct mda_user *mda_user(const struct envelope *); +static void mda_user_free(struct mda_user *); +static const char *mda_user_to_text(const struct mda_user *); +static struct mda_envelope *mda_envelope(uint64_t, const struct envelope *); +static void mda_envelope_free(struct mda_envelope *); +static struct mda_session * mda_session(struct mda_user *); +static const char *mda_sysexit_to_str(int); + +static struct tree sessions; +static struct tree users; + +static TAILQ_HEAD(, mda_user) runnable; + +void +mda_imsg(struct mproc *p, struct imsg *imsg) +{ + struct mda_session *s; + struct mda_user *u; + struct mda_envelope *e; + struct envelope evp; + struct deliver deliver; + struct msg m; + const void *data; + const char *error, *parent_error, *syserror; + uint64_t reqid; + size_t sz; + char out[256], buf[LINE_MAX]; + int n; + enum lka_resp_status status; + enum mda_resp_status mda_status; + int mda_sysexit; + + switch (imsg->hdr.type) { + case IMSG_MDA_LOOKUP_USERINFO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, (int *)&status); + if (status == LKA_OK) + m_get_data(&m, &data, &sz); + m_end(&m); + + u = tree_xget(&users, reqid); + + if (status == LKA_TEMPFAIL) + mda_fail(u, 0, + "Temporary failure in user lookup", + ESC_OTHER_ADDRESS_STATUS); + else if (status == LKA_PERMFAIL) + mda_fail(u, 1, + "Permanent failure in user lookup", + ESC_DESTINATION_MAILBOX_HAS_MOVED); + else { + if (sz != sizeof(u->userinfo)) + fatalx("mda: userinfo size mismatch"); + memmove(&u->userinfo, data, sz); + u->flags &= ~USER_WAITINFO; + u->flags |= USER_RUNNABLE; + TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); + mda_drain(); + } + return; + + case IMSG_QUEUE_DELIVER: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + + u = mda_user(&evp); + + if (u->evpcount >= env->sc_mda_task_hiwat) { + if (!(u->flags & USER_ONHOLD)) { + log_debug("debug: mda: hiwat reached for " + "user \"%s\": holding envelopes", + mda_user_to_text(u)); + u->flags |= USER_ONHOLD; + } + } + + if (u->flags & USER_ONHOLD) { + u->flags |= USER_HOLDQ; + m_create(p_queue, IMSG_MDA_DELIVERY_HOLD, + 0, 0, -1); + m_add_evpid(p_queue, evp.id); + m_add_id(p_queue, u->id); + m_close(p_queue); + return; + } + + e = mda_envelope(u->id, &evp); + TAILQ_INSERT_TAIL(&u->envelopes, e, entry); + u->evpcount += 1; + stat_increment("mda.pending", 1); + + if (!(u->flags & USER_RUNNABLE) && + !(u->flags & USER_WAITINFO)) { + u->flags |= USER_RUNNABLE; + TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); + } + + mda_drain(); + return; + + case IMSG_MDA_OPEN_MESSAGE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + s = tree_xget(&sessions, reqid); + e = s->evp; + + if (imsg->fd == -1) { + log_debug("debug: mda: cannot get message fd"); + mda_queue_tempfail(e->id, + "Cannot get message fd", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "Cannot get message fd"); + mda_done(s); + return; + } + + log_debug("debug: mda: got message fd %d " + "for session %016"PRIx64 " evpid %016"PRIx64, + imsg->fd, s->id, e->id); + + if ((s->datafp = fdopen(imsg->fd, "r")) == NULL) { + log_warn("warn: mda: fdopen"); + close(imsg->fd); + mda_queue_tempfail(e->id, "fdopen failed", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "fdopen failed"); + mda_done(s); + return; + } + + /* check delivery loop */ + if (mda_check_loop(s->datafp, e)) { + log_debug("debug: mda: loop detected"); + mda_queue_loop(e->id); + mda_log(e, "PermFail", "Loop detected"); + mda_done(s); + return; + } + + /* start queueing delivery headers */ + if (e->sender[0]) + /* + * XXX: remove existing Return-Path, + * if any + */ + n = io_printf(s->io, + "Return-Path: <%s>\n" + "Delivered-To: %s\n", + e->sender, + e->rcpt ? e->rcpt : e->dest); + else + n = io_printf(s->io, + "Delivered-To: %s\n", + e->rcpt ? e->rcpt : e->dest); + if (n == -1) { + log_warn("warn: mda: " + "fail to write delivery info"); + mda_queue_tempfail(e->id, "Out of memory", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "Out of memory"); + mda_done(s); + return; + } + + /* request parent to fork a helper process */ + memset(&deliver, 0, sizeof deliver); + (void)text_to_mailaddr(&deliver.sender, s->evp->sender); + (void)text_to_mailaddr(&deliver.rcpt, s->evp->rcpt); + (void)text_to_mailaddr(&deliver.dest, s->evp->dest); + if (s->evp->mda_exec) + (void)strlcpy(deliver.mda_exec, s->evp->mda_exec, sizeof deliver.mda_exec); + if (s->evp->mda_subaddress) + (void)strlcpy(deliver.mda_subaddress, s->evp->mda_subaddress, sizeof deliver.mda_subaddress); + (void)strlcpy(deliver.dispatcher, s->evp->dispatcher, sizeof deliver.dispatcher); + deliver.userinfo = s->user->userinfo; + + log_debug("debug: mda: querying mda fd " + "for session %016"PRIx64 " evpid %016"PRIx64, + s->id, s->evp->id); + + m_create(p_parent, IMSG_MDA_FORK, 0, 0, -1); + m_add_id(p_parent, reqid); + m_add_data(p_parent, &deliver, sizeof(deliver)); + m_close(p_parent); + return; + + case IMSG_MDA_FORK: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + s = tree_xget(&sessions, reqid); + e = s->evp; + if (imsg->fd == -1) { + log_warn("warn: mda: fail to retrieve mda fd"); + mda_queue_tempfail(e->id, "Cannot get mda fd", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "Cannot get mda fd"); + mda_done(s); + return; + } + + log_debug("debug: mda: got mda fd %d " + "for session %016"PRIx64 " evpid %016"PRIx64, + imsg->fd, s->id, s->evp->id); + + io_set_nonblocking(imsg->fd); + io_set_fd(s->io, imsg->fd); + io_set_write(s->io); + return; + + case IMSG_MDA_DONE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, (int *)&mda_status); + m_get_int(&m, (int *)&mda_sysexit); + m_get_string(&m, &parent_error); + m_end(&m); + + s = tree_xget(&sessions, reqid); + e = s->evp; + /* + * Grab last line of mda stdout/stderr if available. + */ + out[0] = '\0'; + if (imsg->fd != -1) + mda_getlastline(imsg->fd, out, sizeof(out)); + + /* + * Choose between parent's description of error and + * child's output, the latter having preference over + * the former. + */ + error = NULL; + if (mda_status == MDA_OK) { + if (s->datafp || (s->io && io_queued(s->io))) { + error = "mda exited prematurely"; + mda_status = MDA_TEMPFAIL; + } + } else + error = out[0] ? out : parent_error; + + syserror = NULL; + if (mda_sysexit) + syserror = mda_sysexit_to_str(mda_sysexit); + + /* update queue entry */ + switch (mda_status) { + case MDA_TEMPFAIL: + mda_queue_tempfail(e->id, error, + ESC_OTHER_MAIL_SYSTEM_STATUS); + (void)snprintf(buf, sizeof buf, + "Error (%s%s%s)", + syserror ? syserror : "", + syserror ? ": " : "", + error); + mda_log(e, "TempFail", buf); + break; + case MDA_PERMFAIL: + mda_queue_permfail(e->id, error, + ESC_OTHER_MAIL_SYSTEM_STATUS); + (void)snprintf(buf, sizeof buf, + "Error (%s%s%s)", + syserror ? syserror : "", + syserror ? ": " : "", + error); + mda_log(e, "PermFail", buf); + break; + case MDA_OK: + mda_queue_ok(e->id); + mda_log(e, "Ok", "Delivered"); + break; + } + mda_done(s); + return; + } + + errx(1, "mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +void +mda_postfork() +{ +} + +void +mda_postprivdrop() +{ + tree_init(&sessions); + tree_init(&users); + TAILQ_INIT(&runnable); +} + +static void +mda_io(struct io *io, int evt, void *arg) +{ + struct mda_session *s = arg; + char *ln = NULL; + size_t sz = 0; + ssize_t len; + + log_trace(TRACE_IO, "mda: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_LOWAT: + + /* done */ + done: + if (s->datafp == NULL) { + log_debug("debug: mda: all data sent for session" + " %016"PRIx64 " evpid %016"PRIx64, + s->id, s->evp->id); + io_free(io); + s->io = NULL; + return; + } + + while (io_queued(s->io) < MDA_HIWAT) { + if ((len = getline(&ln, &sz, s->datafp)) == -1) + break; + if (io_write(s->io, ln, len) == -1) { + m_create(p_parent, IMSG_MDA_KILL, + 0, 0, -1); + m_add_id(p_parent, s->id); + m_add_string(p_parent, "Out of memory"); + m_close(p_parent); + io_pause(io, IO_OUT); + free(ln); + return; + } + } + + free(ln); + ln = NULL; + if (ferror(s->datafp)) { + log_debug("debug: mda: ferror on session %016"PRIx64, + s->id); + m_create(p_parent, IMSG_MDA_KILL, 0, 0, -1); + m_add_id(p_parent, s->id); + m_add_string(p_parent, "Error reading body"); + m_close(p_parent); + io_pause(io, IO_OUT); + return; + } + + if (feof(s->datafp)) { + log_debug("debug: mda: end-of-file for session" + " %016"PRIx64 " evpid %016"PRIx64, + s->id, s->evp->id); + fclose(s->datafp); + s->datafp = NULL; + if (io_queued(s->io) == 0) + goto done; + } + return; + + case IO_TIMEOUT: + log_debug("debug: mda: timeout on session %016"PRIx64, s->id); + io_pause(io, IO_OUT); + return; + + case IO_ERROR: + log_debug("debug: mda: io error on session %016"PRIx64": %s", + s->id, io_error(io)); + io_pause(io, IO_OUT); + return; + + case IO_DISCONNECTED: + log_debug("debug: mda: io disconnected on session %016"PRIx64, + s->id); + io_pause(io, IO_OUT); + return; + + default: + log_debug("debug: mda: unexpected event on session %016"PRIx64, + s->id); + io_pause(io, IO_OUT); + return; + } +} + +static int +mda_check_loop(FILE *fp, struct mda_envelope *e) +{ + char *buf = NULL; + size_t sz = 0; + ssize_t len; + int ret = 0; + + while ((len = getline(&buf, &sz, fp)) != -1) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + if (strchr(buf, ':') == NULL && !isspace((unsigned char)*buf)) + break; + + if (strncasecmp("Delivered-To: ", buf, 14) == 0) { + if (strcasecmp(buf + 14, e->dest) == 0) { + ret = 1; + break; + } + } + } + + free(buf); + fseek(fp, SEEK_SET, 0); + return (ret); +} + +static int +mda_getlastline(int fd, char *dst, size_t dstsz) +{ + FILE *fp; + char *ln = NULL; + size_t sz = 0; + ssize_t len; + int out = 0; + + if (lseek(fd, 0, SEEK_SET) < 0) { + log_warn("warn: mda: lseek"); + close(fd); + return (-1); + } + fp = fdopen(fd, "r"); + if (fp == NULL) { + log_warn("warn: mda: fdopen"); + close(fd); + return (-1); + } + while ((len = getline(&ln, &sz, fp)) != -1) { + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + out = 1; + } + fclose(fp); + + if (out) { + (void)strlcpy(dst, "\"", dstsz); + (void)strnvis(dst + 1, ln, dstsz - 2, VIS_SAFE | VIS_CSTYLE | VIS_NL); + (void)strlcat(dst, "\"", dstsz); + } + + free(ln); + return (0); +} + +static void +mda_fail(struct mda_user *user, int permfail, const char *error, + enum enhanced_status_code code) +{ + struct mda_envelope *e; + + while ((e = TAILQ_FIRST(&user->envelopes))) { + TAILQ_REMOVE(&user->envelopes, e, entry); + if (permfail) { + mda_log(e, "PermFail", error); + mda_queue_permfail(e->id, error, code); + } + else { + mda_log(e, "TempFail", error); + mda_queue_tempfail(e->id, error, code); + } + mda_envelope_free(e); + } + + mda_user_free(user); +} + +static void +mda_drain(void) +{ + struct mda_user *u; + + while ((u = (TAILQ_FIRST(&runnable)))) { + + TAILQ_REMOVE(&runnable, u, entry_runnable); + + if (u->evpcount == 0 && u->running == 0) { + log_debug("debug: mda: all done for user \"%s\"", + mda_user_to_text(u)); + mda_user_free(u); + continue; + } + + if (u->evpcount == 0) { + log_debug("debug: mda: no more envelope for \"%s\"", + mda_user_to_text(u)); + u->flags &= ~USER_RUNNABLE; + continue; + } + + if (u->running >= env->sc_mda_max_user_session) { + log_debug("debug: mda: " + "maximum number of session reached for user \"%s\"", + mda_user_to_text(u)); + u->flags &= ~USER_RUNNABLE; + continue; + } + + if (tree_count(&sessions) >= env->sc_mda_max_session) { + log_debug("debug: mda: " + "maximum number of session reached"); + TAILQ_INSERT_HEAD(&runnable, u, entry_runnable); + return; + } + + mda_session(u); + + if (u->evpcount == env->sc_mda_task_lowat) { + if (u->flags & USER_ONHOLD) { + log_debug("debug: mda: down to lowat for user " + "\"%s\": releasing", + mda_user_to_text(u)); + u->flags &= ~USER_ONHOLD; + } + if (u->flags & USER_HOLDQ) { + m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, + 0, 0, -1); + m_add_id(p_queue, u->id); + m_add_int(p_queue, env->sc_mda_task_release); + m_close(p_queue); + } + } + + /* re-add the user at the tail of the queue */ + TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); + } +} + +static void +mda_done(struct mda_session *s) +{ + log_debug("debug: mda: session %016" PRIx64 " done", s->id); + + tree_xpop(&sessions, s->id); + + mda_envelope_free(s->evp); + + s->user->running--; + if (!(s->user->flags & USER_RUNNABLE)) { + log_debug("debug: mda: user \"%s\" becomes runnable", + s->user->name); + TAILQ_INSERT_TAIL(&runnable, s->user, entry_runnable); + s->user->flags |= USER_RUNNABLE; + } + + if (s->datafp) + fclose(s->datafp); + if (s->io) + io_free(s->io); + + free(s); + + stat_decrement("mda.running", 1); + + mda_drain(); +} + +static void +mda_log(const struct mda_envelope *evp, const char *prefix, const char *status) +{ + char rcpt[LINE_MAX]; + + rcpt[0] = '\0'; + if (evp->rcpt) + (void)snprintf(rcpt, sizeof rcpt, "rcpt=<%s> ", evp->rcpt); + + log_info("%016"PRIx64" mda delivery evpid=%016" PRIx64 " from=<%s> to=<%s> " + "%suser=%s delay=%s result=%s stat=%s", + evp->session_id, + evp->id, + evp->sender ? evp->sender : "", + evp->dest, + rcpt, + evp->user, + duration_to_text(time(NULL) - evp->creation), + prefix, + status); +} + +static void +mda_queue_ok(uint64_t evpid) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_OK, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_close(p_queue); +} + +static void +mda_queue_tempfail(uint64_t evpid, const char *reason, + enum enhanced_status_code code) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_add_string(p_queue, reason); + m_add_int(p_queue, (int)code); + m_close(p_queue); +} + +static void +mda_queue_permfail(uint64_t evpid, const char *reason, + enum enhanced_status_code code) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_add_string(p_queue, reason); + m_add_int(p_queue, (int)code); + m_close(p_queue); +} + +static void +mda_queue_loop(uint64_t evpid) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_LOOP, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_close(p_queue); +} + +static struct mda_user * +mda_user(const struct envelope *evp) +{ + struct dispatcher *dsp; + struct mda_user *u; + void *i; + + i = NULL; + dsp = dict_xget(env->sc_dispatchers, evp->dispatcher); + while (tree_iter(&users, &i, NULL, (void**)(&u))) { + if (!strcmp(evp->mda_user, u->name) && + !strcmp(dsp->u.local.table_userbase, u->usertable)) + return (u); + } + + u = xcalloc(1, sizeof *u); + u->id = generate_uid(); + TAILQ_INIT(&u->envelopes); + (void)strlcpy(u->name, evp->mda_user, sizeof(u->name)); + (void)strlcpy(u->usertable, dsp->u.local.table_userbase, + sizeof(u->usertable)); + + tree_xset(&users, u->id, u); + + m_create(p_lka, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1); + m_add_id(p_lka, u->id); + m_add_string(p_lka, dsp->u.local.table_userbase); + m_add_string(p_lka, evp->mda_user); + m_close(p_lka); + u->flags |= USER_WAITINFO; + + stat_increment("mda.user", 1); + + if (dsp->u.local.user) + log_debug("mda: new user %016" PRIx64 + " for \"%s\" delivering as \"%s\"", + u->id, mda_user_to_text(u), dsp->u.local.user); + else + log_debug("mda: new user %016" PRIx64 + " for \"%s\"", u->id, mda_user_to_text(u)); + + return (u); +} + +static void +mda_user_free(struct mda_user *u) +{ + tree_xpop(&users, u->id); + + if (u->flags & USER_HOLDQ) { + m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, u->id); + m_add_int(p_queue, 0); + m_close(p_queue); + } + + free(u); + stat_decrement("mda.user", 1); +} + +static const char * +mda_user_to_text(const struct mda_user *u) +{ + static char buf[1024]; + + (void)snprintf(buf, sizeof(buf), "%s:%s", u->usertable, u->name); + + return (buf); +} + +static struct mda_envelope * +mda_envelope(uint64_t session_id, const struct envelope *evp) +{ + struct mda_envelope *e; + char buf[LINE_MAX]; + + e = xcalloc(1, sizeof *e); + e->session_id = session_id; + e->id = evp->id; + e->creation = evp->creation; + buf[0] = '\0'; + if (evp->sender.user[0] && evp->sender.domain[0]) + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->sender.user, evp->sender.domain); + e->sender = xstrdup(buf); + (void)snprintf(buf, sizeof buf, "%s@%s", evp->dest.user, + evp->dest.domain); + e->dest = xstrdup(buf); + (void)snprintf(buf, sizeof buf, "%s@%s", evp->rcpt.user, + evp->rcpt.domain); + e->rcpt = xstrdup(buf); + e->user = evp->mda_user[0] ? + xstrdup(evp->mda_user) : xstrdup(evp->dest.user); + e->dispatcher = xstrdup(evp->dispatcher); + if (evp->mda_exec[0]) + e->mda_exec = xstrdup(evp->mda_exec); + if (evp->mda_subaddress[0]) + e->mda_subaddress = xstrdup(evp->mda_subaddress); + stat_increment("mda.envelope", 1); + return (e); +} + +static void +mda_envelope_free(struct mda_envelope *e) +{ + free(e->sender); + free(e->dest); + free(e->rcpt); + free(e->user); + free(e->mda_exec); + free(e); + + stat_decrement("mda.envelope", 1); +} + +static struct mda_session * +mda_session(struct mda_user * u) +{ + struct mda_session *s; + + s = xcalloc(1, sizeof *s); + s->id = generate_uid(); + s->user = u; + s->io = io_new(); + io_set_callback(s->io, mda_io, s); + + tree_xset(&sessions, s->id, s); + + s->evp = TAILQ_FIRST(&u->envelopes); + TAILQ_REMOVE(&u->envelopes, s->evp, entry); + u->evpcount--; + u->running++; + + stat_decrement("mda.pending", 1); + stat_increment("mda.running", 1); + + log_debug("debug: mda: new session %016" PRIx64 + " for user \"%s\" evpid %016" PRIx64, s->id, + mda_user_to_text(u), s->evp->id); + + m_create(p_queue, IMSG_MDA_OPEN_MESSAGE, 0, 0, -1); + m_add_id(p_queue, s->id); + m_add_msgid(p_queue, evpid_to_msgid(s->evp->id)); + m_close(p_queue); + + return (s); +} + +static const char * +mda_sysexit_to_str(int sysexit) +{ + switch (sysexit) { + case EX_USAGE: + return "command line usage error"; + case EX_DATAERR: + return "data format error"; + case EX_NOINPUT: + return "cannot open input"; + case EX_NOUSER: + return "user unknown"; + case EX_NOHOST: + return "host name unknown"; + case EX_UNAVAILABLE: + return "service unavailable"; + case EX_SOFTWARE: + return "internal software error"; + case EX_OSERR: + return "system resource problem"; + case EX_OSFILE: + return "critical OS file missing"; + case EX_CANTCREAT: + return "can't create user output file"; + case EX_IOERR: + return "input/output error"; + case EX_TEMPFAIL: + return "temporary failure"; + case EX_PROTOCOL: + return "remote error in protocol"; + case EX_NOPERM: + return "permission denied"; + case EX_CONFIG: + return "local configuration error"; + default: + break; + } + return NULL; +} + diff --git a/usr.sbin/smtpd/mda_mbox.c b/usr.sbin/smtpd/mda_mbox.c new file mode 100644 index 00000000..8918e3ee --- /dev/null +++ b/usr.sbin/smtpd/mda_mbox.c @@ -0,0 +1,94 @@ +/* $OpenBSD: mda_mbox.c,v 1.2 2020/02/03 15:41:22 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" + + +void +mda_mbox(struct deliver *deliver) +{ + int ret; + char sender[LINE_MAX]; + char *envp[] = { + "HOME=/", + "PATH=" _PATH_DEFPATH, + "LOGNAME=root", + "USER=root", + NULL, + }; + + if (deliver->sender.user[0] == '\0' && + deliver->sender.domain[0] == '\0') + ret = snprintf(sender, sizeof sender, "MAILER-DAEMON"); + else + ret = snprintf(sender, sizeof sender, "%s@%s", + deliver->sender.user, deliver->sender.domain); + if (ret < 0 || (size_t)ret >= sizeof sender) + errx(EX_TEMPFAIL, "sender address too long"); + + execle(PATH_MAILLOCAL, PATH_MAILLOCAL, "-f", + sender, deliver->userinfo.username, (char *)NULL, envp); + perror("execl"); + _exit(EX_TEMPFAIL); +} + +void +mda_mbox_init(struct deliver *deliver) +{ + int fd; + int ret; + char buffer[LINE_MAX]; + + ret = snprintf(buffer, sizeof buffer, "%s/%s", + _PATH_MAILDIR, deliver->userinfo.username); + if (ret < 0 || (size_t)ret >= sizeof buffer) + errx(EX_TEMPFAIL, "mailbox pathname too long"); + + if ((fd = open(buffer, O_CREAT|O_EXCL, 0)) == -1) { + if (errno == EEXIST) + return; + err(EX_TEMPFAIL, "open"); + } + + if (fchown(fd, deliver->userinfo.uid, deliver->userinfo.gid) == -1) + err(EX_TEMPFAIL, "fchown"); + + if (fchmod(fd, S_IRUSR|S_IWUSR) == -1) + err(EX_TEMPFAIL, "fchown"); +} diff --git a/usr.sbin/smtpd/mda_unpriv.c b/usr.sbin/smtpd/mda_unpriv.c new file mode 100644 index 00000000..2143b9a0 --- /dev/null +++ b/usr.sbin/smtpd/mda_unpriv.c @@ -0,0 +1,110 @@ +/* $OpenBSD: mda_unpriv.c,v 1.6 2020/02/02 22:13:48 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" + + +void +mda_unpriv(struct dispatcher *dsp, struct deliver *deliver, + const char *pw_name, const char *pw_dir) +{ + int idx; + char *mda_environ[11]; + char mda_exec[LINE_MAX]; + char mda_wrapper[LINE_MAX]; + const char *mda_command; + const char *mda_command_wrap; + + if (deliver->mda_exec[0]) + mda_command = deliver->mda_exec; + else + mda_command = dsp->u.local.command; + + if (strlcpy(mda_exec, mda_command, sizeof (mda_exec)) + >= sizeof (mda_exec)) + errx(1, "mda command line too long"); + + if (mda_expand_format(mda_exec, sizeof mda_exec, deliver, + &deliver->userinfo, NULL) == -1) + errx(1, "mda command line could not be expanded"); + + mda_command = mda_exec; + + /* setup environment similar to other MTA */ + idx = 0; + xasprintf(&mda_environ[idx++], "PATH=%s", _PATH_DEFPATH); + xasprintf(&mda_environ[idx++], "DOMAIN=%s", deliver->rcpt.domain); + xasprintf(&mda_environ[idx++], "HOME=%s", pw_dir); + xasprintf(&mda_environ[idx++], "RECIPIENT=%s@%s", deliver->dest.user, deliver->dest.domain); + xasprintf(&mda_environ[idx++], "SHELL=/bin/sh"); + xasprintf(&mda_environ[idx++], "LOCAL=%s", deliver->rcpt.user); + xasprintf(&mda_environ[idx++], "LOGNAME=%s", pw_name); + xasprintf(&mda_environ[idx++], "USER=%s", pw_name); + + if (deliver->sender.user[0]) + xasprintf(&mda_environ[idx++], "SENDER=%s@%s", + deliver->sender.user, deliver->sender.domain); + else + xasprintf(&mda_environ[idx++], "SENDER="); + + if (deliver->mda_subaddress[0]) + xasprintf(&mda_environ[idx++], "EXTENSION=%s", deliver->mda_subaddress); + + mda_environ[idx++] = (char *)NULL; + + if (dsp->u.local.mda_wrapper) { + mda_command_wrap = dict_get(env->sc_mda_wrappers, + dsp->u.local.mda_wrapper); + if (mda_command_wrap == NULL) + errx(1, "could not find wrapper %s", + dsp->u.local.mda_wrapper); + + if (strlcpy(mda_wrapper, mda_command_wrap, sizeof (mda_wrapper)) + >= sizeof (mda_wrapper)) + errx(1, "mda command line too long"); + + if (mda_expand_format(mda_wrapper, sizeof mda_wrapper, deliver, + &deliver->userinfo, mda_command) == -1) + errx(1, "mda command line could not be expanded"); + mda_command = mda_wrapper; + } + execle("/bin/sh", "/bin/sh", "-c", mda_command, (char *)NULL, + mda_environ); + + perror("execle"); + _exit(1); +} + diff --git a/usr.sbin/smtpd/mda_variables.c b/usr.sbin/smtpd/mda_variables.c new file mode 100644 index 00000000..b672e492 --- /dev/null +++ b/usr.sbin/smtpd/mda_variables.c @@ -0,0 +1,374 @@ +/* $OpenBSD: mda_variables.c,v 1.6 2019/09/19 07:35:36 gilles Exp $ */ + +/* + * Copyright (c) 2011-2017 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define EXPAND_DEPTH 10 + +ssize_t mda_expand_format(char *, size_t, const struct deliver *, + const struct userinfo *, const char *); +static ssize_t mda_expand_token(char *, size_t, const char *, + const struct deliver *, const struct userinfo *, const char *); +static int mod_lowercase(char *, size_t); +static int mod_uppercase(char *, size_t); +static int mod_strip(char *, size_t); + +static struct modifiers { + char *name; + int (*f)(char *buf, size_t len); +} token_modifiers[] = { + { "lowercase", mod_lowercase }, + { "uppercase", mod_uppercase }, + { "strip", mod_strip }, + { "raw", NULL }, /* special case, must stay last */ +}; + +#define MAXTOKENLEN 128 + +static ssize_t +mda_expand_token(char *dest, size_t len, const char *token, + const struct deliver *dlv, const struct userinfo *ui, const char *mda_command) +{ + char rtoken[MAXTOKENLEN]; + char tmp[EXPAND_BUFFER]; + const char *string; + char *lbracket, *rbracket, *content, *sep, *mods; + ssize_t i; + ssize_t begoff, endoff; + const char *errstr = NULL; + int replace = 1; + int raw = 0; + + begoff = 0; + endoff = EXPAND_BUFFER; + mods = NULL; + + if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken) + return -1; + + /* token[x[:y]] -> extracts optional x and y, converts into offsets */ + if ((lbracket = strchr(rtoken, '[')) && + (rbracket = strchr(rtoken, ']'))) { + /* ] before [ ... or empty */ + if (rbracket < lbracket || rbracket - lbracket <= 1) + return -1; + + *lbracket = *rbracket = '\0'; + content = lbracket + 1; + + if ((sep = strchr(content, ':')) == NULL) + endoff = begoff = strtonum(content, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + else { + *sep = '\0'; + if (content != sep) + begoff = strtonum(content, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + if (*(++sep)) { + if (errstr == NULL) + endoff = strtonum(sep, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + } + } + if (errstr) + return -1; + + /* token:mod_1,mod_2,mod_n -> extract modifiers */ + mods = strchr(rbracket + 1, ':'); + } else { + if ((mods = strchr(rtoken, ':')) != NULL) + *mods++ = '\0'; + } + + /* token -> expanded token */ + if (!strcasecmp("sender", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("rcpt", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("dest", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("sender.user", rtoken)) + string = dlv->sender.user; + else if (!strcasecmp("sender.domain", rtoken)) + string = dlv->sender.domain; + else if (!strcasecmp("user.username", rtoken)) + string = ui->username; + else if (!strcasecmp("user.directory", rtoken)) { + string = ui->directory; + replace = 0; + } + else if (!strcasecmp("rcpt.user", rtoken)) + string = dlv->rcpt.user; + else if (!strcasecmp("rcpt.domain", rtoken)) + string = dlv->rcpt.domain; + else if (!strcasecmp("dest.user", rtoken)) + string = dlv->dest.user; + else if (!strcasecmp("dest.domain", rtoken)) + string = dlv->dest.domain; + else if (!strcasecmp("mda", rtoken)) { + string = mda_command; + replace = 0; + } + else if (!strcasecmp("mbox.from", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp); + string = tmp; + } + else + return -1; + + if (string != tmp) { + if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) + return -1; + string = tmp; + } + + /* apply modifiers */ + if (mods != NULL) { + do { + if ((sep = strchr(mods, '|')) != NULL) + *sep++ = '\0'; + for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { + if (!strcasecmp(token_modifiers[i].name, mods)) { + if (token_modifiers[i].f == NULL) { + raw = 1; + break; + } + if (!token_modifiers[i].f(tmp, sizeof tmp)) + return -1; /* modifier error */ + break; + } + } + if ((size_t)i == nitems(token_modifiers)) + return -1; /* modifier not found */ + } while ((mods = sep) != NULL); + } + + if (!raw && replace) + for (i = 0; (size_t)i < strlen(tmp); ++i) + if (strchr(MAILADDR_ESCAPE, tmp[i])) + tmp[i] = ':'; + + /* expanded string is empty */ + i = strlen(string); + if (i == 0) + return 0; + + /* begin offset beyond end of string */ + if (begoff >= i) + return -1; + + /* end offset beyond end of string, make it end of string */ + if (endoff >= i) + endoff = i - 1; + + /* negative begin offset, make it relative to end of string */ + if (begoff < 0) + begoff += i; + /* negative end offset, make it relative to end of string, + * note that end offset is inclusive. + */ + if (endoff < 0) + endoff += i - 1; + + /* check that final offsets are valid */ + if (begoff < 0 || endoff < 0 || endoff < begoff) + return -1; + endoff += 1; /* end offset is inclusive */ + + /* check that substring does not exceed destination buffer length */ + i = endoff - begoff; + if ((size_t)i + 1 >= len) + return -1; + + string += begoff; + for (; i; i--) { + *dest = *string; + dest++; + string++; + } + + return endoff - begoff; +} + + +ssize_t +mda_expand_format(char *buf, size_t len, const struct deliver *dlv, + const struct userinfo *ui, const char *mda_command) +{ + char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; + char exptok[EXPAND_BUFFER]; + ssize_t exptoklen; + char token[MAXTOKENLEN]; + size_t ret, tmpret; + + if (len < sizeof tmpbuf) { + log_warnx("mda_expand_format: tmp buffer < rule buffer"); + return -1; + } + + memset(tmpbuf, 0, sizeof tmpbuf); + pbuf = buf; + ptmp = tmpbuf; + ret = tmpret = 0; + + /* special case: ~/ only allowed expanded at the beginning */ + if (strncmp(pbuf, "~/", 2) == 0) { + tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); + if (tmpret >= sizeof tmpbuf) { + log_warnx("warn: user directory for %s too large", + ui->directory); + return 0; + } + ret += tmpret; + ptmp += tmpret; + pbuf += 2; + } + + + /* expansion loop */ + for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { + if (*pbuf == '%' && *(pbuf + 1) == '%') { + *ptmp++ = *pbuf++; + pbuf += 1; + tmpret = 1; + continue; + } + + if (*pbuf != '%' || *(pbuf + 1) != '{') { + *ptmp++ = *pbuf++; + tmpret = 1; + continue; + } + + /* %{...} otherwise fail */ + if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL) + return 0; + + /* extract token from %{token} */ + if ((size_t)(ebuf - pbuf) - 1 >= sizeof token) + return 0; + + memcpy(token, pbuf+2, ebuf-pbuf-1); + if (strchr(token, '}') == NULL) + return 0; + *strchr(token, '}') = '\0'; + + exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv, + ui, mda_command); + if (exptoklen == -1) + return -1; + + /* writing expanded token at ptmp will overflow tmpbuf */ + if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen) + return -1; + + memcpy(ptmp, exptok, exptoklen); + pbuf = ebuf + 1; + ptmp += exptoklen; + tmpret = exptoklen; + } + if (ret >= sizeof tmpbuf) + return -1; + + if ((ret = strlcpy(buf, tmpbuf, len)) >= len) + return -1; + + return ret; +} + +static int +mod_lowercase(char *buf, size_t len) +{ + char tmp[EXPAND_BUFFER]; + + if (!lowercase(tmp, buf, sizeof tmp)) + return 0; + if (strlcpy(buf, tmp, len) >= len) + return 0; + return 1; +} + +static int +mod_uppercase(char *buf, size_t len) +{ + char tmp[EXPAND_BUFFER]; + + if (!uppercase(tmp, buf, sizeof tmp)) + return 0; + if (strlcpy(buf, tmp, len) >= len) + return 0; + return 1; +} + +static int +mod_strip(char *buf, size_t len) +{ + char *tag, *at; + unsigned int i; + + /* gilles+hackers -> gilles */ + if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) { + /* gilles+hackers@poolp.org -> gilles@poolp.org */ + if ((at = strchr(tag, '@')) != NULL) { + for (i = 0; i <= strlen(at); ++i) + tag[i] = at[i]; + } else + *tag = '\0'; + } + return 1; +} diff --git a/usr.sbin/smtpd/mproc.c b/usr.sbin/smtpd/mproc.c new file mode 100644 index 00000000..bde229e1 --- /dev/null +++ b/usr.sbin/smtpd/mproc.c @@ -0,0 +1,676 @@ +/* $OpenBSD: mproc.c,v 1.36 2020/03/17 09:01:53 tobhe Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static void mproc_dispatch(int, short, void *); + +static ssize_t imsg_read_nofd(struct imsgbuf *); + +int +mproc_fork(struct mproc *p, const char *path, char *argv[]) +{ + int sp[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + return (-1); + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + if ((p->pid = fork()) == -1) + goto err; + + if (p->pid == 0) { + /* child process */ + dup2(sp[0], STDIN_FILENO); + closefrom(STDERR_FILENO + 1); + execv(path, argv); + err(1, "execv: %s", path); + } + + /* parent process */ + close(sp[0]); + mproc_init(p, sp[1]); + return (0); + +err: + log_warn("warn: Failed to start process %s, instance of %s", argv[0], path); + close(sp[0]); + close(sp[1]); + return (-1); +} + +void +mproc_init(struct mproc *p, int fd) +{ + imsg_init(&p->imsgbuf, fd); +} + +void +mproc_clear(struct mproc *p) +{ + log_debug("debug: clearing p=%s, fd=%d, pid=%d", p->name, p->imsgbuf.fd, p->pid); + + event_del(&p->ev); + close(p->imsgbuf.fd); + imsg_clear(&p->imsgbuf); +} + +void +mproc_enable(struct mproc *p) +{ + if (p->enable == 0) { + log_trace(TRACE_MPROC, "mproc: %s -> %s: enabled", + proc_name(smtpd_process), + proc_name(p->proc)); + p->enable = 1; + } + mproc_event_add(p); +} + +void +mproc_disable(struct mproc *p) +{ + if (p->enable == 1) { + log_trace(TRACE_MPROC, "mproc: %s -> %s: disabled", + proc_name(smtpd_process), + proc_name(p->proc)); + p->enable = 0; + } + mproc_event_add(p); +} + +void +mproc_event_add(struct mproc *p) +{ + short events; + + if (p->enable) + events = EV_READ; + else + events = 0; + + if (p->imsgbuf.w.queued) + events |= EV_WRITE; + + if (p->events) + event_del(&p->ev); + + p->events = events; + if (events) { + event_set(&p->ev, p->imsgbuf.fd, events, mproc_dispatch, p); + event_add(&p->ev, NULL); + } +} + +static void +mproc_dispatch(int fd, short event, void *arg) +{ + struct mproc *p = arg; + struct imsg imsg; + ssize_t n; + + p->events = 0; + + if (event & EV_READ) { + + if (p->proc == PROC_CLIENT) + n = imsg_read_nofd(&p->imsgbuf); + else + n = imsg_read(&p->imsgbuf); + + switch (n) { + case -1: + if (errno == EAGAIN) + break; + log_warn("warn: %s -> %s: imsg_read", + proc_name(smtpd_process), p->name); + fatal("exiting"); + /* NOTREACHED */ + case 0: + /* this pipe is dead, so remove the event handler */ + log_debug("debug: %s -> %s: pipe closed", + proc_name(smtpd_process), p->name); + p->handler(p, NULL); + return; + default: + break; + } + } + + if (event & EV_WRITE) { + n = msgbuf_write(&p->imsgbuf.w); + if (n == 0 || (n == -1 && errno != EAGAIN)) { + /* this pipe is dead, so remove the event handler */ + log_debug("debug: %s -> %s: pipe closed", + proc_name(smtpd_process), p->name); + p->handler(p, NULL); + return; + } + } + + for (;;) { + if ((n = imsg_get(&p->imsgbuf, &imsg)) == -1) { + + if (smtpd_process == PROC_CONTROL && + p->proc == PROC_CLIENT) { + log_warnx("warn: client sent invalid imsg " + "over control socket"); + p->handler(p, NULL); + return; + } + log_warn("fatal: %s: error in imsg_get for %s", + proc_name(smtpd_process), p->name); + fatalx(NULL); + } + if (n == 0) + break; + + p->handler(p, &imsg); + + imsg_free(&imsg); + } + + mproc_event_add(p); +} + +/* This should go into libutil */ +static ssize_t +imsg_read_nofd(struct imsgbuf *ibuf) +{ + ssize_t n; + char *buf; + size_t len; + + buf = ibuf->r.buf + ibuf->r.wpos; + len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + + while ((n = recv(ibuf->fd, buf, len, 0)) == -1) { + if (errno != EINTR) + return (n); + } + + ibuf->r.wpos += n; + return (n); +} + +void +m_forward(struct mproc *p, struct imsg *imsg) +{ + imsg_compose(&p->imsgbuf, imsg->hdr.type, imsg->hdr.peerid, + imsg->hdr.pid, imsg->fd, imsg->data, + imsg->hdr.len - sizeof(imsg->hdr)); + + if (imsg->hdr.type != IMSG_STAT_DECREMENT && + imsg->hdr.type != IMSG_STAT_INCREMENT) + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s (forward)", + proc_name(smtpd_process), + proc_name(p->proc), + imsg->hdr.len - sizeof(imsg->hdr), + imsg_to_str(imsg->hdr.type)); + + mproc_event_add(p); +} + +void +m_compose(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd, + void *data, size_t len) +{ + imsg_compose(&p->imsgbuf, type, peerid, pid, fd, data, len); + + if (type != IMSG_STAT_DECREMENT && + type != IMSG_STAT_INCREMENT) + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", + proc_name(smtpd_process), + proc_name(p->proc), + len, + imsg_to_str(type)); + + mproc_event_add(p); +} + +void +m_composev(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, + int fd, const struct iovec *iov, int n) +{ + size_t len; + int i; + + imsg_composev(&p->imsgbuf, type, peerid, pid, fd, iov, n); + + len = 0; + for (i = 0; i < n; i++) + len += iov[i].iov_len; + + if (type != IMSG_STAT_DECREMENT && + type != IMSG_STAT_INCREMENT) + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", + proc_name(smtpd_process), + proc_name(p->proc), + len, + imsg_to_str(type)); + + mproc_event_add(p); +} + +void +m_create(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd) +{ + p->m_pos = 0; + p->m_type = type; + p->m_peerid = peerid; + p->m_pid = pid; + p->m_fd = fd; +} + +void +m_add(struct mproc *p, const void *data, size_t len) +{ + size_t alloc; + void *tmp; + + if (p->m_pos + len + IMSG_HEADER_SIZE > MAX_IMSGSIZE) { + log_warnx("warn: message too large"); + fatal(NULL); + } + + alloc = p->m_alloc ? p->m_alloc : 128; + while (p->m_pos + len > alloc) + alloc *= 2; + if (alloc != p->m_alloc) { + log_trace(TRACE_MPROC, "mproc: %s -> %s: realloc %zu -> %zu", + proc_name(smtpd_process), + proc_name(p->proc), + p->m_alloc, + alloc); + + tmp = recallocarray(p->m_buf, p->m_alloc, alloc, 1); + if (tmp == NULL) + fatal("realloc"); + p->m_alloc = alloc; + p->m_buf = tmp; + } + + memmove(p->m_buf + p->m_pos, data, len); + p->m_pos += len; +} + +void +m_close(struct mproc *p) +{ + if (imsg_compose(&p->imsgbuf, p->m_type, p->m_peerid, p->m_pid, p->m_fd, + p->m_buf, p->m_pos) == -1) + fatal("imsg_compose"); + + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", + proc_name(smtpd_process), + proc_name(p->proc), + p->m_pos, + imsg_to_str(p->m_type)); + + mproc_event_add(p); +} + +void +m_flush(struct mproc *p) +{ + if (imsg_compose(&p->imsgbuf, p->m_type, p->m_peerid, p->m_pid, p->m_fd, + p->m_buf, p->m_pos) == -1) + fatal("imsg_compose"); + + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s (flush)", + proc_name(smtpd_process), + proc_name(p->proc), + p->m_pos, + imsg_to_str(p->m_type)); + + p->m_pos = 0; + + if (imsg_flush(&p->imsgbuf) == -1) + fatal("imsg_flush"); +} + +static struct imsg * current; + +static void +m_error(const char *error) +{ + char buf[512]; + + (void)snprintf(buf, sizeof buf, "%s: %s: %s", + proc_name(smtpd_process), + imsg_to_str(current->hdr.type), + error); + fatalx("%s", buf); +} + +void +m_msg(struct msg *m, struct imsg *imsg) +{ + current = imsg; + m->pos = imsg->data; + m->end = m->pos + (imsg->hdr.len - sizeof(imsg->hdr)); +} + +void +m_end(struct msg *m) +{ + if (m->pos != m->end) + m_error("not at msg end"); +} + +int +m_is_eom(struct msg *m) +{ + return (m->pos == m->end); +} + +static inline void +m_get(struct msg *m, void *dst, size_t sz) +{ + if (sz > MAX_IMSGSIZE || + m->end - m->pos < (ssize_t)sz) + fatalx("msg too short"); + + memmove(dst, m->pos, sz); + m->pos += sz; +} + +void +m_add_int(struct mproc *m, int v) +{ + m_add(m, &v, sizeof(v)); +}; + +void +m_add_u32(struct mproc *m, uint32_t u32) +{ + m_add(m, &u32, sizeof(u32)); +}; + +void +m_add_size(struct mproc *m, size_t sz) +{ + m_add(m, &sz, sizeof(sz)); +}; + +void +m_add_time(struct mproc *m, time_t v) +{ + m_add(m, &v, sizeof(v)); +}; + +void +m_add_timeval(struct mproc *m, struct timeval *tv) +{ + m_add(m, tv, sizeof(*tv)); +} + + +void +m_add_string(struct mproc *m, const char *v) +{ + if (v) { + m_add(m, "s", 1); + m_add(m, v, strlen(v) + 1); + } + else + m_add(m, "\0", 1); +}; + +void +m_add_data(struct mproc *m, const void *v, size_t len) +{ + m_add_size(m, len); + m_add(m, v, len); +}; + +void +m_add_id(struct mproc *m, uint64_t v) +{ + m_add(m, &v, sizeof(v)); +} + +void +m_add_evpid(struct mproc *m, uint64_t v) +{ + m_add(m, &v, sizeof(v)); +} + +void +m_add_msgid(struct mproc *m, uint32_t v) +{ + m_add(m, &v, sizeof(v)); +} + +void +m_add_sockaddr(struct mproc *m, const struct sockaddr *sa) +{ + m_add_size(m, SA_LEN(sa)); + m_add(m, sa, SA_LEN(sa)); +} + +void +m_add_mailaddr(struct mproc *m, const struct mailaddr *maddr) +{ + m_add(m, maddr, sizeof(*maddr)); +} + +void +m_add_envelope(struct mproc *m, const struct envelope *evp) +{ + char buf[sizeof(*evp)]; + + envelope_dump_buffer(evp, buf, sizeof(buf)); + m_add_evpid(m, evp->id); + m_add_string(m, buf); +} + +void +m_add_params(struct mproc *m, struct dict *d) +{ + const char *key; + char *value; + void *iter; + + if (d == NULL) { + m_add_size(m, 0); + return; + } + m_add_size(m, dict_count(d)); + iter = NULL; + while (dict_iter(d, &iter, &key, (void **)&value)) { + m_add_string(m, key); + m_add_string(m, value); + } +} + +void +m_get_int(struct msg *m, int *i) +{ + m_get(m, i, sizeof(*i)); +} + +void +m_get_u32(struct msg *m, uint32_t *u32) +{ + m_get(m, u32, sizeof(*u32)); +} + +void +m_get_size(struct msg *m, size_t *sz) +{ + m_get(m, sz, sizeof(*sz)); +} + +void +m_get_time(struct msg *m, time_t *t) +{ + m_get(m, t, sizeof(*t)); +} + +void +m_get_timeval(struct msg *m, struct timeval *tv) +{ + m_get(m, tv, sizeof(*tv)); +} + +void +m_get_string(struct msg *m, const char **s) +{ + uint8_t *end; + char c; + + if (m->pos >= m->end) + m_error("msg too short"); + + c = *m->pos++; + if (c == '\0') { + *s = NULL; + return; + } + + if (m->pos >= m->end) + m_error("msg too short"); + end = memchr(m->pos, 0, m->end - m->pos); + if (end == NULL) + m_error("unterminated string"); + + *s = m->pos; + m->pos = end + 1; +} + +void +m_get_data(struct msg *m, const void **data, size_t *sz) +{ + m_get_size(m, sz); + + if (*sz == 0) { + *data = NULL; + return; + } + + if (m->pos + *sz > m->end) + m_error("msg too short"); + + *data = m->pos; + m->pos += *sz; +} + +void +m_get_evpid(struct msg *m, uint64_t *evpid) +{ + m_get(m, evpid, sizeof(*evpid)); +} + +void +m_get_msgid(struct msg *m, uint32_t *msgid) +{ + m_get(m, msgid, sizeof(*msgid)); +} + +void +m_get_id(struct msg *m, uint64_t *id) +{ + m_get(m, id, sizeof(*id)); +} + +void +m_get_sockaddr(struct msg *m, struct sockaddr *sa) +{ + size_t len; + + m_get_size(m, &len); + m_get(m, sa, len); +} + +void +m_get_mailaddr(struct msg *m, struct mailaddr *maddr) +{ + m_get(m, maddr, sizeof(*maddr)); +} + +void +m_get_envelope(struct msg *m, struct envelope *evp) +{ + uint64_t evpid; + const char *buf; + + m_get_evpid(m, &evpid); + m_get_string(m, &buf); + if (buf == NULL) + fatalx("empty envelope buffer"); + + if (!envelope_load_buffer(evp, buf, strlen(buf))) + fatalx("failed to retrieve envelope"); + evp->id = evpid; +} + +void +m_get_params(struct msg *m, struct dict *d) +{ + size_t c; + const char *key; + const char *value; + char *tmp; + + dict_init(d); + + m_get_size(m, &c); + + for (; c; c--) { + m_get_string(m, &key); + m_get_string(m, &value); + if ((tmp = strdup(value)) == NULL) + fatal("m_get_params"); + dict_set(d, key, tmp); + } +} + +void +m_clear_params(struct dict *d) +{ + char *value; + + while (dict_poproot(d, (void **)&value)) + free(value); +} diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c new file mode 100644 index 00000000..922170ae --- /dev/null +++ b/usr.sbin/smtpd/mta.c @@ -0,0 +1,2647 @@ +/* $OpenBSD: mta.c,v 1.234 2019/12/21 10:34:07 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define MAXERROR_PER_ROUTE 4 + +#define DELAY_CHECK_SOURCE 1 +#define DELAY_CHECK_SOURCE_SLOW 10 +#define DELAY_CHECK_SOURCE_FAST 0 +#define DELAY_CHECK_LIMIT 5 + +#define DELAY_QUADRATIC 1 +#define DELAY_ROUTE_BASE 15 +#define DELAY_ROUTE_MAX 3600 + +#define RELAY_ONHOLD 0x01 +#define RELAY_HOLDQ 0x02 + +static void mta_handle_envelope(struct envelope *, const char *); +static void mta_query_smarthost(struct envelope *); +static void mta_on_smarthost(struct envelope *, const char *); +static void mta_query_mx(struct mta_relay *); +static void mta_query_secret(struct mta_relay *); +static void mta_query_preference(struct mta_relay *); +static void mta_query_source(struct mta_relay *); +static void mta_on_mx(void *, void *, void *); +static void mta_on_secret(struct mta_relay *, const char *); +static void mta_on_preference(struct mta_relay *, int); +static void mta_on_source(struct mta_relay *, struct mta_source *); +static void mta_on_timeout(struct runq *, void *); +static void mta_connect(struct mta_connector *); +static void mta_route_enable(struct mta_route *); +static void mta_route_disable(struct mta_route *, int, int); +static void mta_drain(struct mta_relay *); +static void mta_delivery_flush_event(int, short, void *); +static void mta_flush(struct mta_relay *, int, const char *); +static struct mta_route *mta_find_route(struct mta_connector *, time_t, int*, + time_t*, struct mta_mx **); +static void mta_log(const struct mta_envelope *, const char *, const char *, + const char *, const char *); + +SPLAY_HEAD(mta_relay_tree, mta_relay); +static struct mta_relay *mta_relay(struct envelope *, struct relayhost *); +static void mta_relay_ref(struct mta_relay *); +static void mta_relay_unref(struct mta_relay *); +static void mta_relay_show(struct mta_relay *, struct mproc *, uint32_t, time_t); +static int mta_relay_cmp(const struct mta_relay *, const struct mta_relay *); +SPLAY_PROTOTYPE(mta_relay_tree, mta_relay, entry, mta_relay_cmp); + +SPLAY_HEAD(mta_host_tree, mta_host); +static struct mta_host *mta_host(const struct sockaddr *); +static void mta_host_ref(struct mta_host *); +static void mta_host_unref(struct mta_host *); +static int mta_host_cmp(const struct mta_host *, const struct mta_host *); +SPLAY_PROTOTYPE(mta_host_tree, mta_host, entry, mta_host_cmp); + +SPLAY_HEAD(mta_domain_tree, mta_domain); +static struct mta_domain *mta_domain(char *, int); +#if 0 +static void mta_domain_ref(struct mta_domain *); +#endif +static void mta_domain_unref(struct mta_domain *); +static int mta_domain_cmp(const struct mta_domain *, const struct mta_domain *); +SPLAY_PROTOTYPE(mta_domain_tree, mta_domain, entry, mta_domain_cmp); + +SPLAY_HEAD(mta_source_tree, mta_source); +static struct mta_source *mta_source(const struct sockaddr *); +static void mta_source_ref(struct mta_source *); +static void mta_source_unref(struct mta_source *); +static const char *mta_source_to_text(struct mta_source *); +static int mta_source_cmp(const struct mta_source *, const struct mta_source *); +SPLAY_PROTOTYPE(mta_source_tree, mta_source, entry, mta_source_cmp); + +static struct mta_connector *mta_connector(struct mta_relay *, + struct mta_source *); +static void mta_connector_free(struct mta_connector *); +static const char *mta_connector_to_text(struct mta_connector *); + +SPLAY_HEAD(mta_route_tree, mta_route); +static struct mta_route *mta_route(struct mta_source *, struct mta_host *); +static void mta_route_ref(struct mta_route *); +static void mta_route_unref(struct mta_route *); +static const char *mta_route_to_text(struct mta_route *); +static int mta_route_cmp(const struct mta_route *, const struct mta_route *); +SPLAY_PROTOTYPE(mta_route_tree, mta_route, entry, mta_route_cmp); + +struct mta_block { + SPLAY_ENTRY(mta_block) entry; + struct mta_source *source; + char *domain; +}; + +SPLAY_HEAD(mta_block_tree, mta_block); +void mta_block(struct mta_source *, char *); +void mta_unblock(struct mta_source *, char *); +int mta_is_blocked(struct mta_source *, char *); +static int mta_block_cmp(const struct mta_block *, const struct mta_block *); +SPLAY_PROTOTYPE(mta_block_tree, mta_block, entry, mta_block_cmp); + +static struct mta_relay_tree relays; +static struct mta_domain_tree domains; +static struct mta_host_tree hosts; +static struct mta_source_tree sources; +static struct mta_route_tree routes; +static struct mta_block_tree blocks; + +static struct tree wait_mx; +static struct tree wait_preference; +static struct tree wait_secret; +static struct tree wait_smarthost; +static struct tree wait_source; +static struct tree flush_evp; +static struct event ev_flush_evp; + +static struct runq *runq_relay; +static struct runq *runq_connector; +static struct runq *runq_route; +static struct runq *runq_hoststat; + +static time_t max_seen_conndelay_route; +static time_t max_seen_discdelay_route; + +#define HOSTSTAT_EXPIRE_DELAY (4 * 3600) +struct hoststat { + char name[HOST_NAME_MAX+1]; + time_t tm; + char error[LINE_MAX]; + struct tree deferred; +}; +static struct dict hoststat; + +void mta_hoststat_update(const char *, const char *); +void mta_hoststat_cache(const char *, uint64_t); +void mta_hoststat_uncache(const char *, uint64_t); +void mta_hoststat_reschedule(const char *); +static void mta_hoststat_remove_entry(struct hoststat *); + +void +mta_imsg(struct mproc *p, struct imsg *imsg) +{ + struct mta_relay *relay; + struct mta_domain *domain; + struct mta_host *host; + struct mta_route *route; + struct mta_block *block; + struct mta_mx *mx, *imx; + struct mta_source *source; + struct hoststat *hs; + struct sockaddr_storage ss; + struct envelope evp, *e; + struct msg m; + const char *secret; + const char *hostname; + const char *dom; + const char *smarthost; + uint64_t reqid; + time_t t; + char buf[LINE_MAX]; + int dnserror, preference, v, status; + void *iter; + uint64_t u64; + + switch (imsg->hdr.type) { + case IMSG_QUEUE_TRANSFER: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + mta_handle_envelope(&evp, NULL); + return; + + case IMSG_MTA_OPEN_MESSAGE: + mta_session_imsg(p, imsg); + return; + + case IMSG_MTA_LOOKUP_CREDENTIALS: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &secret); + m_end(&m); + relay = tree_xpop(&wait_secret, reqid); + mta_on_secret(relay, secret[0] ? secret : NULL); + return; + + case IMSG_MTA_LOOKUP_SOURCE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + if (status == LKA_OK) + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_end(&m); + + relay = tree_xpop(&wait_source, reqid); + mta_on_source(relay, (status == LKA_OK) ? + mta_source((struct sockaddr *)&ss) : NULL); + return; + + case IMSG_MTA_LOOKUP_SMARTHOST: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + smarthost = NULL; + if (status == LKA_OK) + m_get_string(&m, &smarthost); + m_end(&m); + + e = tree_xpop(&wait_smarthost, reqid); + mta_on_smarthost(e, smarthost); + return; + + case IMSG_MTA_LOOKUP_HELO: + mta_session_imsg(p, imsg); + return; + + case IMSG_MTA_DNS_HOST: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &hostname); + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_get_int(&m, &preference); + m_end(&m); + domain = tree_xget(&wait_mx, reqid); + mx = xcalloc(1, sizeof *mx); + mx->mxname = xstrdup(hostname); + mx->host = mta_host((struct sockaddr*)&ss); + mx->preference = preference; + TAILQ_FOREACH(imx, &domain->mxs, entry) { + if (imx->preference > mx->preference) { + TAILQ_INSERT_BEFORE(imx, mx, entry); + return; + } + } + TAILQ_INSERT_TAIL(&domain->mxs, mx, entry); + return; + + case IMSG_MTA_DNS_HOST_END: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &dnserror); + m_end(&m); + domain = tree_xpop(&wait_mx, reqid); + domain->mxstatus = dnserror; + if (domain->mxstatus == DNS_OK) { + log_debug("debug: MXs for domain %s:", + domain->name); + TAILQ_FOREACH(mx, &domain->mxs, entry) + log_debug(" %s preference %d", + sa_to_text(mx->host->sa), + mx->preference); + } + else { + log_debug("debug: Failed MX query for %s:", + domain->name); + } + domain->lastmxquery = time(NULL); + waitq_run(&domain->mxs, domain); + return; + + case IMSG_MTA_DNS_MX_PREFERENCE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &dnserror); + if (dnserror == 0) + m_get_int(&m, &preference); + m_end(&m); + + relay = tree_xpop(&wait_preference, reqid); + if (dnserror) { + log_warnx("warn: Couldn't find backup " + "preference for %s: error %d", + mta_relay_to_text(relay), dnserror); + preference = INT_MAX; + } + mta_on_preference(relay, preference); + return; + + case IMSG_CTL_RESUME_ROUTE: + u64 = *((uint64_t *)imsg->data); + if (u64) + log_debug("resuming route: %llu", + (unsigned long long)u64); + else + log_debug("resuming all routes"); + SPLAY_FOREACH(route, mta_route_tree, &routes) { + if (u64 && route->id != u64) + continue; + + if (route->flags & ROUTE_DISABLED) { + log_info("smtp-out: Enabling route %s per admin request", + mta_route_to_text(route)); + if (!runq_cancel(runq_route, route)) { + log_warnx("warn: route not on runq"); + fatalx("exiting"); + } + route->flags &= ~ROUTE_DISABLED; + route->flags |= ROUTE_NEW; + route->nerror = 0; + route->penalty = 0; + mta_route_unref(route); /* from mta_route_disable */ + } + + if (u64) + break; + } + return; + + case IMSG_CTL_MTA_SHOW_HOSTS: + t = time(NULL); + SPLAY_FOREACH(host, mta_host_tree, &hosts) { + (void)snprintf(buf, sizeof(buf), + "%s %s refcount=%d nconn=%zu lastconn=%s", + sockaddr_to_text(host->sa), + host->ptrname, + host->refcount, + host->nconn, + host->lastconn ? duration_to_text(t - host->lastconn) : "-"); + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTS, + imsg->hdr.peerid, 0, -1, + buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTS, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_RELAYS: + t = time(NULL); + SPLAY_FOREACH(relay, mta_relay_tree, &relays) + mta_relay_show(relay, p, imsg->hdr.peerid, t); + m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_ROUTES: + SPLAY_FOREACH(route, mta_route_tree, &routes) { + v = runq_pending(runq_route, route, &t); + (void)snprintf(buf, sizeof(buf), + "%llu. %s %c%c%c%c nconn=%zu nerror=%d penalty=%d timeout=%s", + (unsigned long long)route->id, + mta_route_to_text(route), + route->flags & ROUTE_NEW ? 'N' : '-', + route->flags & ROUTE_DISABLED ? 'D' : '-', + route->flags & ROUTE_RUNQ ? 'Q' : '-', + route->flags & ROUTE_KEEPALIVE ? 'K' : '-', + route->nconn, + route->nerror, + route->penalty, + v ? duration_to_text(t - time(NULL)) : "-"); + m_compose(p, IMSG_CTL_MTA_SHOW_ROUTES, + imsg->hdr.peerid, 0, -1, + buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_ROUTES, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + iter = NULL; + while (dict_iter(&hoststat, &iter, &hostname, + (void **)&hs)) { + (void)snprintf(buf, sizeof(buf), + "%s|%llu|%s", + hostname, (unsigned long long) hs->tm, + hs->error); + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTSTATS, + imsg->hdr.peerid, 0, -1, + buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTSTATS, + imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_BLOCK: + m_msg(&m, imsg); + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_get_string(&m, &dom); + m_end(&m); + source = mta_source((struct sockaddr*)&ss); + if (*dom != '\0') { + if (!(strlcpy(buf, dom, sizeof(buf)) + >= sizeof(buf))) + mta_block(source, buf); + } + else + mta_block(source, NULL); + mta_source_unref(source); + m_compose(p, IMSG_CTL_OK, imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_UNBLOCK: + m_msg(&m, imsg); + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_get_string(&m, &dom); + m_end(&m); + source = mta_source((struct sockaddr*)&ss); + if (*dom != '\0') { + if (!(strlcpy(buf, dom, sizeof(buf)) + >= sizeof(buf))) + mta_unblock(source, buf); + } + else + mta_unblock(source, NULL); + mta_source_unref(source); + m_compose(p, IMSG_CTL_OK, imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_BLOCK: + SPLAY_FOREACH(block, mta_block_tree, &blocks) { + (void)snprintf(buf, sizeof(buf), "%s -> %s", + mta_source_to_text(block->source), + block->domain ? block->domain : "*"); + m_compose(p, IMSG_CTL_MTA_SHOW_BLOCK, + imsg->hdr.peerid, 0, -1, buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_BLOCK, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + } + + errx(1, "mta_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +void +mta_postfork(void) +{ +} + +void +mta_postprivdrop(void) +{ + SPLAY_INIT(&relays); + SPLAY_INIT(&domains); + SPLAY_INIT(&hosts); + SPLAY_INIT(&sources); + SPLAY_INIT(&routes); + SPLAY_INIT(&blocks); + + tree_init(&wait_secret); + tree_init(&wait_smarthost); + tree_init(&wait_mx); + tree_init(&wait_preference); + tree_init(&wait_source); + tree_init(&flush_evp); + dict_init(&hoststat); + + evtimer_set(&ev_flush_evp, mta_delivery_flush_event, NULL); + + runq_init(&runq_relay, mta_on_timeout); + runq_init(&runq_connector, mta_on_timeout); + runq_init(&runq_route, mta_on_timeout); + runq_init(&runq_hoststat, mta_on_timeout); +} + + +/* + * Local error on the given source. + */ +void +mta_source_error(struct mta_relay *relay, struct mta_route *route, const char *e) +{ + struct mta_connector *c; + + /* + * Remember the source as broken for this connector. + */ + c = mta_connector(relay, route->src); + if (!(c->flags & CONNECTOR_ERROR_SOURCE)) + log_info("smtp-out: Error on %s: %s", + mta_route_to_text(route), e); + c->flags |= CONNECTOR_ERROR_SOURCE; +} + +void +mta_route_error(struct mta_relay *relay, struct mta_route *route) +{ +#if 0 + route->nerror += 1; + + if (route->nerror > MAXERROR_PER_ROUTE) { + log_info("smtp-out: Too many errors on %s: " + "disabling for a while", mta_route_to_text(route)); + mta_route_disable(route, 2, ROUTE_DISABLED_SMTP); + } +#endif +} + +void +mta_route_ok(struct mta_relay *relay, struct mta_route *route) +{ + struct mta_connector *c; + + if (!(route->flags & ROUTE_NEW)) + return; + + log_debug("debug: mta-routing: route %s is now valid.", + mta_route_to_text(route)); + + route->nerror = 0; + route->flags &= ~ROUTE_NEW; + + c = mta_connector(relay, route->src); + mta_connect(c); +} + +void +mta_route_down(struct mta_relay *relay, struct mta_route *route) +{ +#if 0 + mta_route_disable(route, 2, ROUTE_DISABLED_SMTP); +#endif +} + +void +mta_route_collect(struct mta_relay *relay, struct mta_route *route) +{ + struct mta_connector *c; + + log_debug("debug: mta_route_collect(%s)", + mta_route_to_text(route)); + + relay->nconn -= 1; + relay->domain->nconn -= 1; + route->nconn -= 1; + route->src->nconn -= 1; + route->dst->nconn -= 1; + route->lastdisc = time(NULL); + + /* First connection failed */ + if (route->flags & ROUTE_NEW) + mta_route_disable(route, 1, ROUTE_DISABLED_NET); + + c = mta_connector(relay, route->src); + c->nconn -= 1; + mta_connect(c); + mta_route_unref(route); /* from mta_find_route() */ + mta_relay_unref(relay); /* from mta_connect() */ +} + +struct mta_task * +mta_route_next_task(struct mta_relay *relay, struct mta_route *route) +{ + struct mta_task *task; + + if ((task = TAILQ_FIRST(&relay->tasks))) { + TAILQ_REMOVE(&relay->tasks, task, entry); + relay->ntask -= 1; + task->relay = NULL; + + /* When the number of tasks is down to lowat, query some evp */ + if (relay->ntask == (size_t)relay->limits->task_lowat) { + if (relay->state & RELAY_ONHOLD) { + log_info("smtp-out: back to lowat on %s: releasing", + mta_relay_to_text(relay)); + relay->state &= ~RELAY_ONHOLD; + } + if (relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, relay->limits->task_release); + m_close(p_queue); + } + } + else if (relay->ntask == 0 && relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, 0); + m_close(p_queue); + } + } + + return (task); +} + +static void +mta_handle_envelope(struct envelope *evp, const char *smarthost) +{ + struct mta_relay *relay; + struct mta_task *task; + struct mta_envelope *e; + struct dispatcher *dispatcher; + struct mailaddr maddr; + struct relayhost relayh; + char buf[LINE_MAX]; + + dispatcher = dict_xget(env->sc_dispatchers, evp->dispatcher); + if (dispatcher->u.remote.smarthost && smarthost == NULL) { + mta_query_smarthost(evp); + return; + } + + memset(&relayh, 0, sizeof(relayh)); + relayh.tls = RELAY_TLS_OPPORTUNISTIC; + if (smarthost && !text_to_relayhost(&relayh, smarthost)) { + log_warnx("warn: Failed to parse smarthost %s", smarthost); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "Cannot parse smarthost"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + + if (relayh.flags & RELAY_AUTH && dispatcher->u.remote.auth == NULL) { + log_warnx("warn: No auth table on action \"%s\" for relay %s", + evp->dispatcher, smarthost); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "No auth table for relaying"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + + if (dispatcher->u.remote.tls_required) { + /* Reject relay if smtp+notls:// is requested */ + if (relayh.tls == RELAY_TLS_NO) { + log_warnx("warn: TLS required for action \"%s\"", + evp->dispatcher); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "TLS required for relaying"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + /* Update smtp:// to smtp+tls:// */ + if (relayh.tls == RELAY_TLS_OPPORTUNISTIC) + relayh.tls = RELAY_TLS_STARTTLS; + } + + relay = mta_relay(evp, &relayh); + /* ignore if we don't know the limits yet */ + if (relay->limits && + relay->ntask >= (size_t)relay->limits->task_hiwat) { + if (!(relay->state & RELAY_ONHOLD)) { + log_info("smtp-out: hiwat reached on %s: holding envelopes", + mta_relay_to_text(relay)); + relay->state |= RELAY_ONHOLD; + } + } + + /* + * If the relay has too many pending tasks, tell the + * scheduler to hold it until further notice + */ + if (relay->state & RELAY_ONHOLD) { + relay->state |= RELAY_HOLDQ; + m_create(p_queue, IMSG_MTA_DELIVERY_HOLD, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_id(p_queue, relay->id); + m_close(p_queue); + mta_relay_unref(relay); /* from here */ + return; + } + + task = NULL; + TAILQ_FOREACH(task, &relay->tasks, entry) + if (task->msgid == evpid_to_msgid(evp->id)) + break; + + if (task == NULL) { + task = xmalloc(sizeof *task); + TAILQ_INIT(&task->envelopes); + task->relay = relay; + relay->ntask += 1; + TAILQ_INSERT_TAIL(&relay->tasks, task, entry); + task->msgid = evpid_to_msgid(evp->id); + if (evp->sender.user[0] || evp->sender.domain[0]) + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->sender.user, evp->sender.domain); + else + buf[0] = '\0'; + + if (dispatcher->u.remote.mail_from && evp->sender.user[0]) { + memset(&maddr, 0, sizeof (maddr)); + if (text_to_mailaddr(&maddr, + dispatcher->u.remote.mail_from)) { + (void)snprintf(buf, sizeof buf, "%s@%s", + maddr.user[0] ? maddr.user : evp->sender.user, + maddr.domain[0] ? maddr.domain : evp->sender.domain); + } + } + + task->sender = xstrdup(buf); + stat_increment("mta.task", 1); + } + + e = xcalloc(1, sizeof *e); + e->id = evp->id; + e->creation = evp->creation; + e->smtpname = xstrdup(evp->smtpname); + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->dest.user, evp->dest.domain); + e->dest = xstrdup(buf); + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->rcpt.user, evp->rcpt.domain); + if (strcmp(buf, e->dest)) + e->rcpt = xstrdup(buf); + e->task = task; + if (evp->dsn_orcpt.user[0] && evp->dsn_orcpt.domain[0]) { + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->dsn_orcpt.user, evp->dsn_orcpt.domain); + e->dsn_orcpt = xstrdup(buf); + } + (void)strlcpy(e->dsn_envid, evp->dsn_envid, + sizeof e->dsn_envid); + e->dsn_notify = evp->dsn_notify; + e->dsn_ret = evp->dsn_ret; + + TAILQ_INSERT_TAIL(&task->envelopes, e, entry); + log_debug("debug: mta: received evp:%016" PRIx64 + " for <%s>", e->id, e->dest); + + stat_increment("mta.envelope", 1); + + mta_drain(relay); + mta_relay_unref(relay); /* from here */ +} + +static void +mta_delivery_flush_event(int fd, short event, void *arg) +{ + struct mta_envelope *e; + struct timeval tv; + + if (tree_poproot(&flush_evp, NULL, (void**)(&e))) { + + if (e->delivery == IMSG_MTA_DELIVERY_OK) { + m_create(p_queue, IMSG_MTA_DELIVERY_OK, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_add_int(p_queue, e->ext); + m_close(p_queue); + } else if (e->delivery == IMSG_MTA_DELIVERY_TEMPFAIL) { + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_add_string(p_queue, e->status); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + } + else if (e->delivery == IMSG_MTA_DELIVERY_PERMFAIL) { + m_create(p_queue, IMSG_MTA_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_add_string(p_queue, e->status); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + } + else if (e->delivery == IMSG_MTA_DELIVERY_LOOP) { + m_create(p_queue, IMSG_MTA_DELIVERY_LOOP, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_close(p_queue); + } + else { + log_warnx("warn: bad delivery type %d for %016" PRIx64, + e->delivery, e->id); + fatalx("aborting"); + } + + log_debug("debug: mta: flush for %016"PRIx64" (-> %s)", e->id, e->dest); + + free(e->smtpname); + free(e->dest); + free(e->rcpt); + free(e->dsn_orcpt); + free(e); + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev_flush_evp, &tv); + } +} + +void +mta_delivery_log(struct mta_envelope *e, const char *source, const char *relay, + int delivery, const char *status) +{ + if (delivery == IMSG_MTA_DELIVERY_OK) + mta_log(e, "Ok", source, relay, status); + else if (delivery == IMSG_MTA_DELIVERY_TEMPFAIL) + mta_log(e, "TempFail", source, relay, status); + else if (delivery == IMSG_MTA_DELIVERY_PERMFAIL) + mta_log(e, "PermFail", source, relay, status); + else if (delivery == IMSG_MTA_DELIVERY_LOOP) + mta_log(e, "PermFail", source, relay, "Loop detected"); + else { + log_warnx("warn: bad delivery type %d for %016" PRIx64, + delivery, e->id); + fatalx("aborting"); + } + + e->delivery = delivery; + if (status) + (void)strlcpy(e->status, status, sizeof(e->status)); +} + +void +mta_delivery_notify(struct mta_envelope *e) +{ + struct timeval tv; + + tree_xset(&flush_evp, e->id, e); + if (tree_count(&flush_evp) == 1) { + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev_flush_evp, &tv); + } +} + +static void +mta_query_mx(struct mta_relay *relay) +{ + uint64_t id; + + if (relay->status & RELAY_WAIT_MX) + return; + + log_debug("debug: mta: querying MX for %s...", + mta_relay_to_text(relay)); + + if (waitq_wait(&relay->domain->mxs, mta_on_mx, relay)) { + id = generate_uid(); + tree_xset(&wait_mx, id, relay->domain); + if (relay->domain->as_host) + m_create(p_lka, IMSG_MTA_DNS_HOST, 0, 0, -1); + else + m_create(p_lka, IMSG_MTA_DNS_MX, 0, 0, -1); + m_add_id(p_lka, id); + m_add_string(p_lka, relay->domain->name); + m_close(p_lka); + } + relay->status |= RELAY_WAIT_MX; + mta_relay_ref(relay); +} + +static void +mta_query_limits(struct mta_relay *relay) +{ + if (relay->status & RELAY_WAIT_LIMITS) + return; + + relay->limits = dict_get(env->sc_limits_dict, relay->domain->name); + if (relay->limits == NULL) + relay->limits = dict_get(env->sc_limits_dict, "default"); + + if (max_seen_conndelay_route < relay->limits->conndelay_route) + max_seen_conndelay_route = relay->limits->conndelay_route; + if (max_seen_discdelay_route < relay->limits->discdelay_route) + max_seen_discdelay_route = relay->limits->discdelay_route; +} + +static void +mta_query_secret(struct mta_relay *relay) +{ + if (relay->status & RELAY_WAIT_SECRET) + return; + + log_debug("debug: mta: querying secret for %s...", + mta_relay_to_text(relay)); + + tree_xset(&wait_secret, relay->id, relay); + relay->status |= RELAY_WAIT_SECRET; + + m_create(p_lka, IMSG_MTA_LOOKUP_CREDENTIALS, 0, 0, -1); + m_add_id(p_lka, relay->id); + m_add_string(p_lka, relay->authtable); + m_add_string(p_lka, relay->authlabel); + m_close(p_lka); + + mta_relay_ref(relay); +} + +static void +mta_query_smarthost(struct envelope *evp0) +{ + struct dispatcher *dispatcher; + struct envelope *evp; + + evp = malloc(sizeof(*evp)); + memmove(evp, evp0, sizeof(*evp)); + + dispatcher = dict_xget(env->sc_dispatchers, evp->dispatcher); + + log_debug("debug: mta: querying smarthost for %s:%s...", + evp->dispatcher, dispatcher->u.remote.smarthost); + + tree_xset(&wait_smarthost, evp->id, evp); + + m_create(p_lka, IMSG_MTA_LOOKUP_SMARTHOST, 0, 0, -1); + m_add_id(p_lka, evp->id); + if (dispatcher->u.remote.smarthost_domain) + m_add_string(p_lka, evp->dest.domain); + else + m_add_string(p_lka, NULL); + m_add_string(p_lka, dispatcher->u.remote.smarthost); + m_close(p_lka); + + log_debug("debug: mta: querying smarthost"); +} + +static void +mta_query_preference(struct mta_relay *relay) +{ + if (relay->status & RELAY_WAIT_PREFERENCE) + return; + + log_debug("debug: mta: querying preference for %s...", + mta_relay_to_text(relay)); + + tree_xset(&wait_preference, relay->id, relay); + relay->status |= RELAY_WAIT_PREFERENCE; + + m_create(p_lka, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(p_lka, relay->id); + m_add_string(p_lka, relay->domain->name); + m_add_string(p_lka, relay->backupname); + m_close(p_lka); + + mta_relay_ref(relay); +} + +static void +mta_query_source(struct mta_relay *relay) +{ + log_debug("debug: mta: querying source for %s...", + mta_relay_to_text(relay)); + + relay->sourceloop += 1; + + if (relay->sourcetable == NULL) { + /* + * This is a recursive call, but it only happens once, since + * another source will not be queried immediately. + */ + mta_relay_ref(relay); + mta_on_source(relay, mta_source(NULL)); + return; + } + + m_create(p_lka, IMSG_MTA_LOOKUP_SOURCE, 0, 0, -1); + m_add_id(p_lka, relay->id); + m_add_string(p_lka, relay->sourcetable); + m_close(p_lka); + + tree_xset(&wait_source, relay->id, relay); + relay->status |= RELAY_WAIT_SOURCE; + mta_relay_ref(relay); +} + +static void +mta_on_mx(void *tag, void *arg, void *data) +{ + struct mta_domain *domain = data; + struct mta_relay *relay = arg; + + log_debug("debug: mta: ... got mx (%p, %s, %s)", + tag, domain->name, mta_relay_to_text(relay)); + + switch (domain->mxstatus) { + case DNS_OK: + break; + case DNS_RETRY: + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "Temporary failure in MX lookup"; + break; + case DNS_EINVAL: + relay->fail = IMSG_MTA_DELIVERY_PERMFAIL; + relay->failstr = "Invalid domain name"; + break; + case DNS_ENONAME: + relay->fail = IMSG_MTA_DELIVERY_PERMFAIL; + relay->failstr = "Domain does not exist"; + break; + case DNS_ENOTFOUND: + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + if (relay->domain->as_host) + relay->failstr = "Host not found"; + else + relay->failstr = "No MX found for domain"; + break; + default: + fatalx("bad DNS lookup error code"); + break; + } + + if (domain->mxstatus) + log_info("smtp-out: Failed to resolve MX for %s: %s", + mta_relay_to_text(relay), relay->failstr); + + relay->status &= ~RELAY_WAIT_MX; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_drain() */ +} + +static void +mta_on_secret(struct mta_relay *relay, const char *secret) +{ + log_debug("debug: mta: ... got secret for %s: %s", + mta_relay_to_text(relay), secret); + + if (secret) + relay->secret = strdup(secret); + + if (relay->secret == NULL) { + log_warnx("warn: Failed to retrieve secret " + "for %s", mta_relay_to_text(relay)); + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "Could not retrieve credentials"; + } + + relay->status &= ~RELAY_WAIT_SECRET; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_query_secret() */ +} + +static void +mta_on_smarthost(struct envelope *evp, const char *smarthost) +{ + if (smarthost == NULL) { + log_warnx("warn: Failed to retrieve smarthost " + "for envelope %"PRIx64, evp->id); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "Cannot retrieve smarthost"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + + log_debug("debug: mta: ... got smarthost for %016"PRIx64": %s", + evp->id, smarthost); + mta_handle_envelope(evp, smarthost); + free(evp); +} + +static void +mta_on_preference(struct mta_relay *relay, int preference) +{ + log_debug("debug: mta: ... got preference for %s: %d", + mta_relay_to_text(relay), preference); + + relay->backuppref = preference; + + relay->status &= ~RELAY_WAIT_PREFERENCE; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_query_preference() */ +} + +static void +mta_on_source(struct mta_relay *relay, struct mta_source *source) +{ + struct mta_connector *c; + void *iter; + int delay, errmask; + + log_debug("debug: mta: ... got source for %s: %s", + mta_relay_to_text(relay), source ? mta_source_to_text(source) : "NULL"); + + relay->lastsource = time(NULL); + delay = DELAY_CHECK_SOURCE_SLOW; + + if (source) { + c = mta_connector(relay, source); + if (c->flags & CONNECTOR_NEW) { + c->flags &= ~CONNECTOR_NEW; + delay = DELAY_CHECK_SOURCE; + } + mta_connect(c); + if ((c->flags & CONNECTOR_ERROR) == 0) + relay->sourceloop = 0; + else + delay = DELAY_CHECK_SOURCE_FAST; + mta_source_unref(source); /* from constructor */ + } + else { + log_warnx("warn: Failed to get source address for %s", + mta_relay_to_text(relay)); + } + + if (tree_count(&relay->connectors) == 0) { + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "Could not retrieve source address"; + } + if (tree_count(&relay->connectors) < relay->sourceloop) { + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "No valid route to remote MX"; + + errmask = 0; + iter = NULL; + while (tree_iter(&relay->connectors, &iter, NULL, (void **)&c)) + errmask |= c->flags; + + if (errmask & CONNECTOR_ERROR_ROUTE_SMTP) + relay->failstr = "Destination seem to reject all mails"; + else if (errmask & CONNECTOR_ERROR_ROUTE_NET) + relay->failstr = "Network error on destination MXs"; + else if (errmask & CONNECTOR_ERROR_MX) + relay->failstr = "No MX found for destination"; + else if (errmask & CONNECTOR_ERROR_FAMILY) + relay->failstr = "Address family mismatch on destination MXs"; + else if (errmask & CONNECTOR_ERROR_BLOCKED) + relay->failstr = "All routes to destination blocked"; + else + relay->failstr = "No valid route to destination"; + } + + relay->nextsource = relay->lastsource + delay; + relay->status &= ~RELAY_WAIT_SOURCE; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_query_source() */ +} + +static void +mta_connect(struct mta_connector *c) +{ + struct mta_route *route; + struct mta_mx *mx; + struct mta_limits *l = c->relay->limits; + int limits; + time_t nextconn, now; + + /* toggle the block flag */ + if (mta_is_blocked(c->source, c->relay->domain->name)) + c->flags |= CONNECTOR_ERROR_BLOCKED; + else + c->flags &= ~CONNECTOR_ERROR_BLOCKED; + + again: + + log_debug("debug: mta: connecting with %s", mta_connector_to_text(c)); + + /* Do not connect if this connector has an error. */ + if (c->flags & CONNECTOR_ERROR) { + log_debug("debug: mta: connector error"); + return; + } + + if (c->flags & CONNECTOR_WAIT) { + log_debug("debug: mta: cancelling connector timeout"); + runq_cancel(runq_connector, c); + c->flags &= ~CONNECTOR_WAIT; + } + + /* No job. */ + if (c->relay->ntask == 0) { + log_debug("debug: mta: no task for connector"); + return; + } + + /* Do not create more connections than necessary */ + if ((c->relay->nconn_ready >= c->relay->ntask) || + (c->relay->nconn > 2 && c->relay->nconn >= c->relay->ntask / 2)) { + log_debug("debug: mta: enough connections already"); + return; + } + + limits = 0; + nextconn = now = time(NULL); + + if (c->relay->domain->lastconn + l->conndelay_domain > nextconn) { + log_debug("debug: mta: cannot use domain %s before %llus", + c->relay->domain->name, + (unsigned long long) c->relay->domain->lastconn + l->conndelay_domain - now); + nextconn = c->relay->domain->lastconn + l->conndelay_domain; + } + if (c->relay->domain->nconn >= l->maxconn_per_domain) { + log_debug("debug: mta: hit domain limit"); + limits |= CONNECTOR_LIMIT_DOMAIN; + } + + if (c->source->lastconn + l->conndelay_source > nextconn) { + log_debug("debug: mta: cannot use source %s before %llus", + mta_source_to_text(c->source), + (unsigned long long) c->source->lastconn + l->conndelay_source - now); + nextconn = c->source->lastconn + l->conndelay_source; + } + if (c->source->nconn >= l->maxconn_per_source) { + log_debug("debug: mta: hit source limit"); + limits |= CONNECTOR_LIMIT_SOURCE; + } + + if (c->lastconn + l->conndelay_connector > nextconn) { + log_debug("debug: mta: cannot use %s before %llus", + mta_connector_to_text(c), + (unsigned long long) c->lastconn + l->conndelay_connector - now); + nextconn = c->lastconn + l->conndelay_connector; + } + if (c->nconn >= l->maxconn_per_connector) { + log_debug("debug: mta: hit connector limit"); + limits |= CONNECTOR_LIMIT_CONN; + } + + if (c->relay->lastconn + l->conndelay_relay > nextconn) { + log_debug("debug: mta: cannot use %s before %llus", + mta_relay_to_text(c->relay), + (unsigned long long) c->relay->lastconn + l->conndelay_relay - now); + nextconn = c->relay->lastconn + l->conndelay_relay; + } + if (c->relay->nconn >= l->maxconn_per_relay) { + log_debug("debug: mta: hit relay limit"); + limits |= CONNECTOR_LIMIT_RELAY; + } + + /* We can connect now, find a route */ + if (!limits && nextconn <= now) + route = mta_find_route(c, now, &limits, &nextconn, &mx); + else + route = NULL; + + /* No route */ + if (route == NULL) { + + if (c->flags & CONNECTOR_ERROR) { + /* XXX we might want to clear this flag later */ + log_debug("debug: mta-routing: no route available for %s: errors on connector", + mta_connector_to_text(c)); + return; + } + else if (limits) { + log_debug("debug: mta-routing: no route available for %s: limits reached", + mta_connector_to_text(c)); + nextconn = now + DELAY_CHECK_LIMIT; + } + else { + log_debug("debug: mta-routing: no route available for %s: must wait a bit", + mta_connector_to_text(c)); + } + log_debug("debug: mta: retrying to connect on %s in %llus...", + mta_connector_to_text(c), + (unsigned long long) nextconn - time(NULL)); + c->flags |= CONNECTOR_WAIT; + runq_schedule_at(runq_connector, nextconn, c); + return; + } + + log_debug("debug: mta-routing: spawning new connection on %s", + mta_route_to_text(route)); + + c->nconn += 1; + c->lastconn = time(NULL); + + c->relay->nconn += 1; + c->relay->lastconn = c->lastconn; + c->relay->domain->nconn += 1; + c->relay->domain->lastconn = c->lastconn; + route->nconn += 1; + route->lastconn = c->lastconn; + route->src->nconn += 1; + route->src->lastconn = c->lastconn; + route->dst->nconn += 1; + route->dst->lastconn = c->lastconn; + + mta_session(c->relay, route, mx->mxname); /* this never fails synchronously */ + mta_relay_ref(c->relay); + + goto again; +} + +static void +mta_on_timeout(struct runq *runq, void *arg) +{ + struct mta_connector *connector = arg; + struct mta_relay *relay = arg; + struct mta_route *route = arg; + struct hoststat *hs = arg; + + if (runq == runq_relay) { + log_debug("debug: mta: ... timeout for %s", + mta_relay_to_text(relay)); + relay->status &= ~RELAY_WAIT_CONNECTOR; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_drain() */ + } + else if (runq == runq_connector) { + log_debug("debug: mta: ... timeout for %s", + mta_connector_to_text(connector)); + connector->flags &= ~CONNECTOR_WAIT; + mta_connect(connector); + } + else if (runq == runq_route) { + route->flags &= ~ROUTE_RUNQ; + mta_route_enable(route); + mta_route_unref(route); + } + else if (runq == runq_hoststat) { + log_debug("debug: mta: ... timeout for hoststat %s", + hs->name); + mta_hoststat_remove_entry(hs); + free(hs); + } +} + +static void +mta_route_disable(struct mta_route *route, int penalty, int reason) +{ + unsigned long long delay; + + route->penalty += penalty; + route->lastpenalty = time(NULL); + delay = (unsigned long long)DELAY_ROUTE_BASE * route->penalty * route->penalty; + if (delay > DELAY_ROUTE_MAX) + delay = DELAY_ROUTE_MAX; +#if 0 + delay = 60; +#endif + + log_info("smtp-out: Disabling route %s for %llus", + mta_route_to_text(route), delay); + + if (route->flags & ROUTE_DISABLED) + runq_cancel(runq_route, route); + else + mta_route_ref(route); + + route->flags |= reason & ROUTE_DISABLED; + runq_schedule(runq_route, delay, route); +} + +static void +mta_route_enable(struct mta_route *route) +{ + if (route->flags & ROUTE_DISABLED) { + log_info("smtp-out: Enabling route %s", + mta_route_to_text(route)); + route->flags &= ~ROUTE_DISABLED; + route->flags |= ROUTE_NEW; + route->nerror = 0; + } + + if (route->penalty) { +#if DELAY_QUADRATIC + route->penalty -= 1; + route->lastpenalty = time(NULL); +#else + route->penalty = 0; +#endif + } +} + +static void +mta_drain(struct mta_relay *r) +{ + char buf[64]; + + log_debug("debug: mta: draining %s " + "refcount=%d, ntask=%zu, nconnector=%zu, nconn=%zu", + mta_relay_to_text(r), + r->refcount, r->ntask, tree_count(&r->connectors), r->nconn); + + /* + * All done. + */ + if (r->ntask == 0) { + log_debug("debug: mta: all done for %s", mta_relay_to_text(r)); + return; + } + + /* + * If we know that this relay is failing flush the tasks. + */ + if (r->fail) { + mta_flush(r, r->fail, r->failstr); + return; + } + + /* Query secret if needed. */ + if (r->flags & RELAY_AUTH && r->secret == NULL) + mta_query_secret(r); + + /* Query our preference if needed. */ + if (r->backupname && r->backuppref == -1) + mta_query_preference(r); + + /* Query the domain MXs if needed. */ + if (r->domain->lastmxquery == 0) + mta_query_mx(r); + + /* Query the limits if needed. */ + if (r->limits == NULL) + mta_query_limits(r); + + /* Wait until we are ready to proceed. */ + if (r->status & RELAY_WAITMASK) { + buf[0] = '\0'; + if (r->status & RELAY_WAIT_MX) + (void)strlcat(buf, " MX", sizeof buf); + if (r->status & RELAY_WAIT_PREFERENCE) + (void)strlcat(buf, " preference", sizeof buf); + if (r->status & RELAY_WAIT_SECRET) + (void)strlcat(buf, " secret", sizeof buf); + if (r->status & RELAY_WAIT_SOURCE) + (void)strlcat(buf, " source", sizeof buf); + if (r->status & RELAY_WAIT_CONNECTOR) + (void)strlcat(buf, " connector", sizeof buf); + log_debug("debug: mta: %s waiting for%s", + mta_relay_to_text(r), buf); + return; + } + + /* + * We have pending task, and it's maybe time too try a new source. + */ + if (r->nextsource <= time(NULL)) + mta_query_source(r); + else { + log_debug("debug: mta: scheduling relay %s in %llus...", + mta_relay_to_text(r), + (unsigned long long) r->nextsource - time(NULL)); + runq_schedule_at(runq_relay, r->nextsource, r); + r->status |= RELAY_WAIT_CONNECTOR; + mta_relay_ref(r); + } +} + +static void +mta_flush(struct mta_relay *relay, int fail, const char *error) +{ + struct mta_envelope *e; + struct mta_task *task; + const char *domain; + void *iter; + struct mta_connector *c; + size_t n, r; + + log_debug("debug: mta_flush(%s, %d, \"%s\")", + mta_relay_to_text(relay), fail, error); + + if (fail != IMSG_MTA_DELIVERY_TEMPFAIL && fail != IMSG_MTA_DELIVERY_PERMFAIL) + errx(1, "unexpected delivery status %d", fail); + + n = 0; + while ((task = TAILQ_FIRST(&relay->tasks))) { + TAILQ_REMOVE(&relay->tasks, task, entry); + while ((e = TAILQ_FIRST(&task->envelopes))) { + TAILQ_REMOVE(&task->envelopes, e, entry); + + /* + * host was suspended, cache envelope id in hoststat tree + * so that it can be retried when a delivery succeeds for + * that domain. + */ + domain = strchr(e->dest, '@'); + if (fail == IMSG_MTA_DELIVERY_TEMPFAIL && domain) { + r = 0; + iter = NULL; + while (tree_iter(&relay->connectors, &iter, + NULL, (void **)&c)) { + if (c->flags & CONNECTOR_ERROR_ROUTE) + r++; + } + if (tree_count(&relay->connectors) == r) + mta_hoststat_cache(domain+1, e->id); + } + + mta_delivery_log(e, NULL, relay->domain->name, fail, error); + mta_delivery_notify(e); + + n++; + } + free(task->sender); + free(task); + } + + stat_decrement("mta.task", relay->ntask); + stat_decrement("mta.envelope", n); + relay->ntask = 0; + + /* release all waiting envelopes for the relay */ + if (relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, -1); + m_close(p_queue); + } +} + +/* + * Find a route to use for this connector + */ +static struct mta_route * +mta_find_route(struct mta_connector *c, time_t now, int *limits, + time_t *nextconn, struct mta_mx **pmx) +{ + struct mta_route *route, *best; + struct mta_limits *l = c->relay->limits; + struct mta_mx *mx; + int level, limit_host, limit_route; + int family_mismatch, seen, suspended_route; + time_t tm; + + log_debug("debug: mta-routing: searching new route for %s...", + mta_connector_to_text(c)); + + tm = 0; + limit_host = 0; + limit_route = 0; + suspended_route = 0; + family_mismatch = 0; + level = -1; + best = NULL; + seen = 0; + + TAILQ_FOREACH(mx, &c->relay->domain->mxs, entry) { + /* + * New preference level + */ + if (mx->preference > level) { +#ifndef IGNORE_MX_PREFERENCE + /* + * Use the current best MX if found. + */ + if (best) + break; + + /* + * No candidate found. There are valid MXs at this + * preference level but they reached their limit, or + * we can't connect yet. + */ + if (limit_host || limit_route || tm) + break; + + /* + * If we are a backup MX, do not relay to MXs with + * a greater preference value. + */ + if (c->relay->backuppref >= 0 && + mx->preference >= c->relay->backuppref) + break; + + /* + * Start looking at MXs on this preference level. + */ +#endif + level = mx->preference; + } + + if (mx->host->flags & HOST_IGNORE) + continue; + + /* Found a possibly valid mx */ + seen++; + + if ((c->source->sa && + c->source->sa->sa_family != mx->host->sa->sa_family) || + (l->family && l->family != mx->host->sa->sa_family)) { + log_debug("debug: mta-routing: skipping host %s: AF mismatch", + mta_host_to_text(mx->host)); + family_mismatch = 1; + continue; + } + + if (mx->host->nconn >= l->maxconn_per_host) { + log_debug("debug: mta-routing: skipping host %s: too many connections", + mta_host_to_text(mx->host)); + limit_host = 1; + continue; + } + + if (mx->host->lastconn + l->conndelay_host > now) { + log_debug("debug: mta-routing: skipping host %s: cannot use before %llus", + mta_host_to_text(mx->host), + (unsigned long long) mx->host->lastconn + l->conndelay_host - now); + if (tm == 0 || mx->host->lastconn + l->conndelay_host < tm) + tm = mx->host->lastconn + l->conndelay_host; + continue; + } + + route = mta_route(c->source, mx->host); + + if (route->flags & ROUTE_DISABLED) { + log_debug("debug: mta-routing: skipping route %s: suspend", + mta_route_to_text(route)); + suspended_route |= route->flags & ROUTE_DISABLED; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->nconn && (route->flags & ROUTE_NEW)) { + log_debug("debug: mta-routing: skipping route %s: not validated yet", + mta_route_to_text(route)); + limit_route = 1; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->nconn >= l->maxconn_per_route) { + log_debug("debug: mta-routing: skipping route %s: too many connections", + mta_route_to_text(route)); + limit_route = 1; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->lastconn + l->conndelay_route > now) { + log_debug("debug: mta-routing: skipping route %s: cannot use before %llus (delay after connect)", + mta_route_to_text(route), + (unsigned long long) route->lastconn + l->conndelay_route - now); + if (tm == 0 || route->lastconn + l->conndelay_route < tm) + tm = route->lastconn + l->conndelay_route; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->lastdisc + l->discdelay_route > now) { + log_debug("debug: mta-routing: skipping route %s: cannot use before %llus (delay after disconnect)", + mta_route_to_text(route), + (unsigned long long) route->lastdisc + l->discdelay_route - now); + if (tm == 0 || route->lastdisc + l->discdelay_route < tm) + tm = route->lastdisc + l->discdelay_route; + mta_route_unref(route); /* from here */ + continue; + } + + /* Use the route with the lowest number of connections. */ + if (best && route->nconn >= best->nconn) { + log_debug("debug: mta-routing: skipping route %s: current one is better", + mta_route_to_text(route)); + mta_route_unref(route); /* from here */ + continue; + } + + if (best) + mta_route_unref(best); /* from here */ + best = route; + *pmx = mx; + log_debug("debug: mta-routing: selecting candidate route %s", + mta_route_to_text(route)); + } + + if (best) + return (best); + + /* Order is important */ + if (seen == 0) { + log_info("smtp-out: No MX found for %s", + mta_connector_to_text(c)); + c->flags |= CONNECTOR_ERROR_MX; + } + else if (limit_route) { + log_debug("debug: mta: hit route limit"); + *limits |= CONNECTOR_LIMIT_ROUTE; + } + else if (limit_host) { + log_debug("debug: mta: hit host limit"); + *limits |= CONNECTOR_LIMIT_HOST; + } + else if (tm) { + if (tm > *nextconn) + *nextconn = tm; + } + else if (family_mismatch) { + log_info("smtp-out: Address family mismatch on %s", + mta_connector_to_text(c)); + c->flags |= CONNECTOR_ERROR_FAMILY; + } + else if (suspended_route) { + log_info("smtp-out: No valid route for %s", + mta_connector_to_text(c)); + if (suspended_route & ROUTE_DISABLED_NET) + c->flags |= CONNECTOR_ERROR_ROUTE_NET; + if (suspended_route & ROUTE_DISABLED_SMTP) + c->flags |= CONNECTOR_ERROR_ROUTE_SMTP; + } + + return (NULL); +} + +static void +mta_log(const struct mta_envelope *evp, const char *prefix, const char *source, + const char *relay, const char *status) +{ + log_info("%016"PRIx64" mta delivery evpid=%016"PRIx64" " + "from=<%s> to=<%s> rcpt=<%s> source=\"%s\" " + "relay=\"%s\" delay=%s result=\"%s\" stat=\"%s\"", + evp->session, + evp->id, + evp->task->sender, + evp->dest, + evp->rcpt ? evp->rcpt : "-", + source ? source : "-", + relay, + duration_to_text(time(NULL) - evp->creation), + prefix, + status); +} + +static struct mta_relay * +mta_relay(struct envelope *e, struct relayhost *relayh) +{ + struct dispatcher *dispatcher; + struct mta_relay key, *r; + + dispatcher = dict_xget(env->sc_dispatchers, e->dispatcher); + + memset(&key, 0, sizeof key); + + key.pki_name = dispatcher->u.remote.pki; + key.ca_name = dispatcher->u.remote.ca; + key.authtable = dispatcher->u.remote.auth; + key.sourcetable = dispatcher->u.remote.source; + key.helotable = dispatcher->u.remote.helo_source; + key.heloname = dispatcher->u.remote.helo; + key.srs = dispatcher->u.remote.srs; + + if (relayh->hostname[0]) { + key.domain = mta_domain(relayh->hostname, 1); + } + else { + key.domain = mta_domain(e->dest.domain, 0); + if (dispatcher->u.remote.backup) { + key.backupname = dispatcher->u.remote.backupmx; + if (key.backupname == NULL) + key.backupname = e->smtpname; + } + } + + key.tls = relayh->tls; + key.flags |= relayh->flags; + key.port = relayh->port; + key.authlabel = relayh->authlabel; + if (!key.authlabel[0]) + key.authlabel = NULL; + + if ((key.tls == RELAY_TLS_STARTTLS || key.tls == RELAY_TLS_SMTPS) && + dispatcher->u.remote.tls_noverify == 0) + key.flags |= RELAY_TLS_VERIFY; + + if ((r = SPLAY_FIND(mta_relay_tree, &relays, &key)) == NULL) { + r = xcalloc(1, sizeof *r); + TAILQ_INIT(&r->tasks); + r->id = generate_uid(); + r->dispatcher = dispatcher; + r->tls = key.tls; + r->flags = key.flags; + r->domain = key.domain; + r->backupname = key.backupname ? + xstrdup(key.backupname) : NULL; + r->backuppref = -1; + r->port = key.port; + r->pki_name = key.pki_name ? xstrdup(key.pki_name) : NULL; + r->ca_name = key.ca_name ? xstrdup(key.ca_name) : NULL; + if (key.authtable) + r->authtable = xstrdup(key.authtable); + if (key.authlabel) + r->authlabel = xstrdup(key.authlabel); + if (key.sourcetable) + r->sourcetable = xstrdup(key.sourcetable); + if (key.helotable) + r->helotable = xstrdup(key.helotable); + if (key.heloname) + r->heloname = xstrdup(key.heloname); + r->srs = key.srs; + SPLAY_INSERT(mta_relay_tree, &relays, r); + stat_increment("mta.relay", 1); + } else { + mta_domain_unref(key.domain); /* from here */ + } + + r->refcount++; + return (r); +} + +static void +mta_relay_ref(struct mta_relay *r) +{ + r->refcount++; +} + +static void +mta_relay_unref(struct mta_relay *relay) +{ + struct mta_connector *c; + + if (--relay->refcount) + return; + + /* Make sure they are no envelopes held for this relay */ + if (relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, 0); + m_close(p_queue); + } + + log_debug("debug: mta: freeing %s", mta_relay_to_text(relay)); + SPLAY_REMOVE(mta_relay_tree, &relays, relay); + + while ((tree_poproot(&relay->connectors, NULL, (void**)&c))) + mta_connector_free(c); + + free(relay->authlabel); + free(relay->authtable); + free(relay->backupname); + free(relay->pki_name); + free(relay->ca_name); + free(relay->helotable); + free(relay->heloname); + free(relay->secret); + free(relay->sourcetable); + + mta_domain_unref(relay->domain); /* from constructor */ + free(relay); + stat_decrement("mta.relay", 1); +} + +const char * +mta_relay_to_text(struct mta_relay *relay) +{ + static char buf[1024]; + char tmp[32]; + const char *sep = ","; + + (void)snprintf(buf, sizeof buf, "[relay:%s", relay->domain->name); + + if (relay->port) { + (void)strlcat(buf, sep, sizeof buf); + (void)snprintf(tmp, sizeof tmp, "port=%d", (int)relay->port); + (void)strlcat(buf, tmp, sizeof buf); + } + + (void)strlcat(buf, sep, sizeof buf); + switch(relay->tls) { + case RELAY_TLS_OPPORTUNISTIC: + (void)strlcat(buf, "smtp", sizeof buf); + break; + case RELAY_TLS_STARTTLS: + (void)strlcat(buf, "smtp+tls", sizeof buf); + break; + case RELAY_TLS_SMTPS: + (void)strlcat(buf, "smtps", sizeof buf); + break; + case RELAY_TLS_NO: + if (relay->flags & RELAY_LMTP) + (void)strlcat(buf, "lmtp", sizeof buf); + else + (void)strlcat(buf, "smtp+notls", sizeof buf); + break; + default: + (void)strlcat(buf, "???", sizeof buf); + } + + if (relay->flags & RELAY_AUTH) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "auth=", sizeof buf); + (void)strlcat(buf, relay->authtable, sizeof buf); + (void)strlcat(buf, ":", sizeof buf); + (void)strlcat(buf, relay->authlabel, sizeof buf); + } + + if (relay->pki_name) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "pki_name=", sizeof buf); + (void)strlcat(buf, relay->pki_name, sizeof buf); + } + + if (relay->domain->as_host) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "mx", sizeof buf); + } + + if (relay->backupname) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "backup=", sizeof buf); + (void)strlcat(buf, relay->backupname, sizeof buf); + } + + if (relay->sourcetable) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "sourcetable=", sizeof buf); + (void)strlcat(buf, relay->sourcetable, sizeof buf); + } + + if (relay->helotable) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "helotable=", sizeof buf); + (void)strlcat(buf, relay->helotable, sizeof buf); + } + + if (relay->heloname) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "heloname=", sizeof buf); + (void)strlcat(buf, relay->heloname, sizeof buf); + } + + (void)strlcat(buf, "]", sizeof buf); + + return (buf); +} + +static void +mta_relay_show(struct mta_relay *r, struct mproc *p, uint32_t id, time_t t) +{ + struct mta_connector *c; + void *iter; + char buf[1024], flags[1024], dur[64]; + time_t to; + + flags[0] = '\0'; + +#define SHOWSTATUS(f, n) do { \ + if (r->status & (f)) { \ + if (flags[0]) \ + (void)strlcat(flags, ",", sizeof(flags)); \ + (void)strlcat(flags, (n), sizeof(flags)); \ + } \ + } while(0) + + SHOWSTATUS(RELAY_WAIT_MX, "MX"); + SHOWSTATUS(RELAY_WAIT_PREFERENCE, "preference"); + SHOWSTATUS(RELAY_WAIT_SECRET, "secret"); + SHOWSTATUS(RELAY_WAIT_LIMITS, "limits"); + SHOWSTATUS(RELAY_WAIT_SOURCE, "source"); + SHOWSTATUS(RELAY_WAIT_CONNECTOR, "connector"); +#undef SHOWSTATUS + + if (runq_pending(runq_relay, r, &to)) + (void)snprintf(dur, sizeof(dur), "%s", duration_to_text(to - t)); + else + (void)strlcpy(dur, "-", sizeof(dur)); + + (void)snprintf(buf, sizeof(buf), "%s refcount=%d ntask=%zu nconn=%zu lastconn=%s timeout=%s wait=%s%s", + mta_relay_to_text(r), + r->refcount, + r->ntask, + r->nconn, + r->lastconn ? duration_to_text(t - r->lastconn) : "-", + dur, + flags, + (r->state & RELAY_ONHOLD) ? "ONHOLD" : ""); + m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, id, 0, -1, buf, strlen(buf) + 1); + + iter = NULL; + while (tree_iter(&r->connectors, &iter, NULL, (void **)&c)) { + + if (runq_pending(runq_connector, c, &to)) + (void)snprintf(dur, sizeof(dur), "%s", duration_to_text(to - t)); + else + (void)strlcpy(dur, "-", sizeof(dur)); + + flags[0] = '\0'; + +#define SHOWFLAG(f, n) do { \ + if (c->flags & (f)) { \ + if (flags[0]) \ + (void)strlcat(flags, ",", sizeof(flags)); \ + (void)strlcat(flags, (n), sizeof(flags)); \ + } \ + } while(0) + + SHOWFLAG(CONNECTOR_NEW, "NEW"); + SHOWFLAG(CONNECTOR_WAIT, "WAIT"); + + SHOWFLAG(CONNECTOR_ERROR_FAMILY, "ERROR_FAMILY"); + SHOWFLAG(CONNECTOR_ERROR_SOURCE, "ERROR_SOURCE"); + SHOWFLAG(CONNECTOR_ERROR_MX, "ERROR_MX"); + SHOWFLAG(CONNECTOR_ERROR_ROUTE_NET, "ERROR_ROUTE_NET"); + SHOWFLAG(CONNECTOR_ERROR_ROUTE_SMTP, "ERROR_ROUTE_SMTP"); + SHOWFLAG(CONNECTOR_ERROR_BLOCKED, "ERROR_BLOCKED"); + + SHOWFLAG(CONNECTOR_LIMIT_HOST, "LIMIT_HOST"); + SHOWFLAG(CONNECTOR_LIMIT_ROUTE, "LIMIT_ROUTE"); + SHOWFLAG(CONNECTOR_LIMIT_SOURCE, "LIMIT_SOURCE"); + SHOWFLAG(CONNECTOR_LIMIT_RELAY, "LIMIT_RELAY"); + SHOWFLAG(CONNECTOR_LIMIT_CONN, "LIMIT_CONN"); + SHOWFLAG(CONNECTOR_LIMIT_DOMAIN, "LIMIT_DOMAIN"); +#undef SHOWFLAG + + (void)snprintf(buf, sizeof(buf), + " connector %s refcount=%d nconn=%zu lastconn=%s timeout=%s flags=%s", + mta_source_to_text(c->source), + c->refcount, + c->nconn, + c->lastconn ? duration_to_text(t - c->lastconn) : "-", + dur, + flags); + m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, id, 0, -1, buf, + strlen(buf) + 1); + + + } +} + +static int +mta_relay_cmp(const struct mta_relay *a, const struct mta_relay *b) +{ + int r; + + if (a->domain < b->domain) + return (-1); + if (a->domain > b->domain) + return (1); + + if (a->tls < b->tls) + return (-1); + if (a->tls > b->tls) + return (1); + + if (a->flags < b->flags) + return (-1); + if (a->flags > b->flags) + return (1); + + if (a->port < b->port) + return (-1); + if (a->port > b->port) + return (1); + + if (a->authtable == NULL && b->authtable) + return (-1); + if (a->authtable && b->authtable == NULL) + return (1); + if (a->authtable && ((r = strcmp(a->authtable, b->authtable)))) + return (r); + if (a->authlabel == NULL && b->authlabel) + return (-1); + if (a->authlabel && b->authlabel == NULL) + return (1); + if (a->authlabel && ((r = strcmp(a->authlabel, b->authlabel)))) + return (r); + if (a->sourcetable == NULL && b->sourcetable) + return (-1); + if (a->sourcetable && b->sourcetable == NULL) + return (1); + if (a->sourcetable && ((r = strcmp(a->sourcetable, b->sourcetable)))) + return (r); + if (a->helotable == NULL && b->helotable) + return (-1); + if (a->helotable && b->helotable == NULL) + return (1); + if (a->helotable && ((r = strcmp(a->helotable, b->helotable)))) + return (r); + if (a->heloname == NULL && b->heloname) + return (-1); + if (a->heloname && b->heloname == NULL) + return (1); + if (a->heloname && ((r = strcmp(a->heloname, b->heloname)))) + return (r); + + if (a->pki_name == NULL && b->pki_name) + return (-1); + if (a->pki_name && b->pki_name == NULL) + return (1); + if (a->pki_name && ((r = strcmp(a->pki_name, b->pki_name)))) + return (r); + + if (a->ca_name == NULL && b->ca_name) + return (-1); + if (a->ca_name && b->ca_name == NULL) + return (1); + if (a->ca_name && ((r = strcmp(a->ca_name, b->ca_name)))) + return (r); + + if (a->backupname == NULL && b->backupname) + return (-1); + if (a->backupname && b->backupname == NULL) + return (1); + if (a->backupname && ((r = strcmp(a->backupname, b->backupname)))) + return (r); + + if (a->srs < b->srs) + return (-1); + if (a->srs > b->srs) + return (1); + + return (0); +} + +SPLAY_GENERATE(mta_relay_tree, mta_relay, entry, mta_relay_cmp); + +static struct mta_host * +mta_host(const struct sockaddr *sa) +{ + struct mta_host key, *h; + struct sockaddr_storage ss; + + memmove(&ss, sa, SA_LEN(sa)); + key.sa = (struct sockaddr*)&ss; + h = SPLAY_FIND(mta_host_tree, &hosts, &key); + + if (h == NULL) { + h = xcalloc(1, sizeof(*h)); + h->sa = xmemdup(sa, SA_LEN(sa)); + SPLAY_INSERT(mta_host_tree, &hosts, h); + stat_increment("mta.host", 1); + } + + h->refcount++; + return (h); +} + +static void +mta_host_ref(struct mta_host *h) +{ + h->refcount++; +} + +static void +mta_host_unref(struct mta_host *h) +{ + if (--h->refcount) + return; + + SPLAY_REMOVE(mta_host_tree, &hosts, h); + free(h->sa); + free(h->ptrname); + free(h); + stat_decrement("mta.host", 1); +} + +const char * +mta_host_to_text(struct mta_host *h) +{ + static char buf[1024]; + + if (h->ptrname) + (void)snprintf(buf, sizeof buf, "%s (%s)", + sa_to_text(h->sa), h->ptrname); + else + (void)snprintf(buf, sizeof buf, "%s", sa_to_text(h->sa)); + + return (buf); +} + +static int +mta_host_cmp(const struct mta_host *a, const struct mta_host *b) +{ + if (SA_LEN(a->sa) < SA_LEN(b->sa)) + return (-1); + if (SA_LEN(a->sa) > SA_LEN(b->sa)) + return (1); + return (memcmp(a->sa, b->sa, SA_LEN(a->sa))); +} + +SPLAY_GENERATE(mta_host_tree, mta_host, entry, mta_host_cmp); + +static struct mta_domain * +mta_domain(char *name, int as_host) +{ + struct mta_domain key, *d; + + key.name = name; + key.as_host = as_host; + d = SPLAY_FIND(mta_domain_tree, &domains, &key); + + if (d == NULL) { + d = xcalloc(1, sizeof(*d)); + d->name = xstrdup(name); + d->as_host = as_host; + TAILQ_INIT(&d->mxs); + SPLAY_INSERT(mta_domain_tree, &domains, d); + stat_increment("mta.domain", 1); + } + + d->refcount++; + return (d); +} + +#if 0 +static void +mta_domain_ref(struct mta_domain *d) +{ + d->refcount++; +} +#endif + +static void +mta_domain_unref(struct mta_domain *d) +{ + struct mta_mx *mx; + + if (--d->refcount) + return; + + while ((mx = TAILQ_FIRST(&d->mxs))) { + TAILQ_REMOVE(&d->mxs, mx, entry); + mta_host_unref(mx->host); /* from IMSG_DNS_HOST */ + free(mx->mxname); + free(mx); + } + + SPLAY_REMOVE(mta_domain_tree, &domains, d); + free(d->name); + free(d); + stat_decrement("mta.domain", 1); +} + +static int +mta_domain_cmp(const struct mta_domain *a, const struct mta_domain *b) +{ + if (a->as_host < b->as_host) + return (-1); + if (a->as_host > b->as_host) + return (1); + return (strcasecmp(a->name, b->name)); +} + +SPLAY_GENERATE(mta_domain_tree, mta_domain, entry, mta_domain_cmp); + +static struct mta_source * +mta_source(const struct sockaddr *sa) +{ + struct mta_source key, *s; + struct sockaddr_storage ss; + + if (sa) { + memmove(&ss, sa, SA_LEN(sa)); + key.sa = (struct sockaddr*)&ss; + } else + key.sa = NULL; + s = SPLAY_FIND(mta_source_tree, &sources, &key); + + if (s == NULL) { + s = xcalloc(1, sizeof(*s)); + if (sa) + s->sa = xmemdup(sa, SA_LEN(sa)); + SPLAY_INSERT(mta_source_tree, &sources, s); + stat_increment("mta.source", 1); + } + + s->refcount++; + return (s); +} + +static void +mta_source_ref(struct mta_source *s) +{ + s->refcount++; +} + +static void +mta_source_unref(struct mta_source *s) +{ + if (--s->refcount) + return; + + SPLAY_REMOVE(mta_source_tree, &sources, s); + free(s->sa); + free(s); + stat_decrement("mta.source", 1); +} + +static const char * +mta_source_to_text(struct mta_source *s) +{ + static char buf[1024]; + + if (s->sa == NULL) + return "[]"; + (void)snprintf(buf, sizeof buf, "%s", sa_to_text(s->sa)); + return (buf); +} + +static int +mta_source_cmp(const struct mta_source *a, const struct mta_source *b) +{ + if (a->sa == NULL) + return ((b->sa == NULL) ? 0 : -1); + if (b->sa == NULL) + return (1); + if (SA_LEN(a->sa) < SA_LEN(b->sa)) + return (-1); + if (SA_LEN(a->sa) > SA_LEN(b->sa)) + return (1); + return (memcmp(a->sa, b->sa, SA_LEN(a->sa))); +} + +SPLAY_GENERATE(mta_source_tree, mta_source, entry, mta_source_cmp); + +static struct mta_connector * +mta_connector(struct mta_relay *relay, struct mta_source *source) +{ + struct mta_connector *c; + + c = tree_get(&relay->connectors, (uintptr_t)(source)); + if (c == NULL) { + c = xcalloc(1, sizeof(*c)); + c->relay = relay; + c->source = source; + c->flags |= CONNECTOR_NEW; + mta_source_ref(source); + tree_xset(&relay->connectors, (uintptr_t)(source), c); + stat_increment("mta.connector", 1); + log_debug("debug: mta: new %s", mta_connector_to_text(c)); + } + + return (c); +} + +static void +mta_connector_free(struct mta_connector *c) +{ + log_debug("debug: mta: freeing %s", + mta_connector_to_text(c)); + + if (c->flags & CONNECTOR_WAIT) { + log_debug("debug: mta: cancelling timeout for %s", + mta_connector_to_text(c)); + runq_cancel(runq_connector, c); + } + mta_source_unref(c->source); /* from constructor */ + free(c); + + stat_decrement("mta.connector", 1); +} + +static const char * +mta_connector_to_text(struct mta_connector *c) +{ + static char buf[1024]; + + (void)snprintf(buf, sizeof buf, "[connector:%s->%s,0x%x]", + mta_source_to_text(c->source), + mta_relay_to_text(c->relay), + c->flags); + return (buf); +} + +static struct mta_route * +mta_route(struct mta_source *src, struct mta_host *dst) +{ + struct mta_route key, *r; + static uint64_t rid = 0; + + key.src = src; + key.dst = dst; + r = SPLAY_FIND(mta_route_tree, &routes, &key); + + if (r == NULL) { + r = xcalloc(1, sizeof(*r)); + r->src = src; + r->dst = dst; + r->flags |= ROUTE_NEW; + r->id = ++rid; + SPLAY_INSERT(mta_route_tree, &routes, r); + mta_source_ref(src); + mta_host_ref(dst); + stat_increment("mta.route", 1); + } + else if (r->flags & ROUTE_RUNQ) { + log_debug("debug: mta: mta_route_ref(): cancelling runq for route %s", + mta_route_to_text(r)); + r->flags &= ~(ROUTE_RUNQ | ROUTE_KEEPALIVE); + runq_cancel(runq_route, r); + r->refcount--; /* from mta_route_unref() */ + } + + r->refcount++; + return (r); +} + +static void +mta_route_ref(struct mta_route *r) +{ + r->refcount++; +} + +static void +mta_route_unref(struct mta_route *r) +{ + time_t sched, now; + int delay; + + if (--r->refcount) + return; + + /* + * Nothing references this route, but we might want to keep it alive + * for a while. + */ + now = time(NULL); + sched = 0; + + if (r->penalty) { +#if DELAY_QUADRATIC + delay = DELAY_ROUTE_BASE * r->penalty * r->penalty; +#else + delay = 15 * 60; +#endif + if (delay > DELAY_ROUTE_MAX) + delay = DELAY_ROUTE_MAX; + sched = r->lastpenalty + delay; + log_debug("debug: mta: mta_route_unref(): keeping route %s alive for %llus (penalty %d)", + mta_route_to_text(r), (unsigned long long) sched - now, r->penalty); + } else if (!(r->flags & ROUTE_KEEPALIVE)) { + if (r->lastconn + max_seen_conndelay_route > now) + sched = r->lastconn + max_seen_conndelay_route; + if (r->lastdisc + max_seen_discdelay_route > now && + r->lastdisc + max_seen_discdelay_route < sched) + sched = r->lastdisc + max_seen_discdelay_route; + + if (sched > now) + log_debug("debug: mta: mta_route_unref(): keeping route %s alive for %llus (imposed delay)", + mta_route_to_text(r), (unsigned long long) sched - now); + } + + if (sched > now) { + r->flags |= ROUTE_RUNQ; + runq_schedule_at(runq_route, sched, r); + r->refcount++; + return; + } + + log_debug("debug: mta: mta_route_unref(): really discarding route %s", + mta_route_to_text(r)); + + SPLAY_REMOVE(mta_route_tree, &routes, r); + mta_source_unref(r->src); /* from constructor */ + mta_host_unref(r->dst); /* from constructor */ + free(r); + stat_decrement("mta.route", 1); +} + +static const char * +mta_route_to_text(struct mta_route *r) +{ + static char buf[1024]; + + (void)snprintf(buf, sizeof buf, "%s <-> %s", + mta_source_to_text(r->src), + mta_host_to_text(r->dst)); + + return (buf); +} + +static int +mta_route_cmp(const struct mta_route *a, const struct mta_route *b) +{ + if (a->src < b->src) + return (-1); + if (a->src > b->src) + return (1); + + if (a->dst < b->dst) + return (-1); + if (a->dst > b->dst) + return (1); + + return (0); +} + +SPLAY_GENERATE(mta_route_tree, mta_route, entry, mta_route_cmp); + +void +mta_block(struct mta_source *src, char *dom) +{ + struct mta_block key, *b; + + key.source = src; + key.domain = dom; + + b = SPLAY_FIND(mta_block_tree, &blocks, &key); + if (b != NULL) + return; + + b = xcalloc(1, sizeof(*b)); + if (dom) + b->domain = xstrdup(dom); + b->source = src; + mta_source_ref(src); + SPLAY_INSERT(mta_block_tree, &blocks, b); +} + +void +mta_unblock(struct mta_source *src, char *dom) +{ + struct mta_block key, *b; + + key.source = src; + key.domain = dom; + + b = SPLAY_FIND(mta_block_tree, &blocks, &key); + if (b == NULL) + return; + + SPLAY_REMOVE(mta_block_tree, &blocks, b); + + mta_source_unref(b->source); + free(b->domain); + free(b); +} + +int +mta_is_blocked(struct mta_source *src, char *dom) +{ + struct mta_block key; + + key.source = src; + key.domain = dom; + + if (SPLAY_FIND(mta_block_tree, &blocks, &key)) + return (1); + + return (0); +} + +static +int +mta_block_cmp(const struct mta_block *a, const struct mta_block *b) +{ + if (a->source < b->source) + return (-1); + if (a->source > b->source) + return (1); + if (!a->domain && b->domain) + return (-1); + if (a->domain && !b->domain) + return (1); + if (a->domain == b->domain) + return (0); + return (strcasecmp(a->domain, b->domain)); +} + +SPLAY_GENERATE(mta_block_tree, mta_block, entry, mta_block_cmp); + + + +/* hoststat errors are not critical, we do best effort */ +void +mta_hoststat_update(const char *host, const char *error) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) { + if ((hs = calloc(1, sizeof *hs)) == NULL) + return; + tree_init(&hs->deferred); + runq_schedule(runq_hoststat, HOSTSTAT_EXPIRE_DELAY, hs); + } + (void)strlcpy(hs->name, buf, sizeof hs->name); + (void)strlcpy(hs->error, error, sizeof hs->error); + hs->tm = time(NULL); + dict_set(&hoststat, buf, hs); + + runq_cancel(runq_hoststat, hs); + runq_schedule(runq_hoststat, HOSTSTAT_EXPIRE_DELAY, hs); +} + +void +mta_hoststat_cache(const char *host, uint64_t evpid) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) + return; + + if (tree_count(&hs->deferred) >= env->sc_mta_max_deferred) + return; + + tree_set(&hs->deferred, evpid, NULL); +} + +void +mta_hoststat_uncache(const char *host, uint64_t evpid) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) + return; + + tree_pop(&hs->deferred, evpid); +} + +void +mta_hoststat_reschedule(const char *host) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + uint64_t evpid; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) + return; + + while (tree_poproot(&hs->deferred, &evpid, NULL)) { + m_compose(p_queue, IMSG_MTA_SCHEDULE, 0, 0, -1, + &evpid, sizeof evpid); + } +} + +static void +mta_hoststat_remove_entry(struct hoststat *hs) +{ + while (tree_poproot(&hs->deferred, NULL, NULL)) + ; + dict_pop(&hoststat, hs->name); + runq_cancel(runq_hoststat, hs); +} diff --git a/usr.sbin/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c new file mode 100644 index 00000000..632f5be7 --- /dev/null +++ b/usr.sbin/smtpd/mta_session.c @@ -0,0 +1,2004 @@ +/* $OpenBSD: mta_session.c,v 1.135 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +#define MAX_TRYBEFOREDISABLE 10 + +#define MTA_HIWAT 65535 + +enum mta_state { + MTA_INIT, + MTA_BANNER, + MTA_EHLO, + MTA_HELO, + MTA_LHLO, + MTA_STARTTLS, + MTA_AUTH, + MTA_AUTH_PLAIN, + MTA_AUTH_LOGIN, + MTA_AUTH_LOGIN_USER, + MTA_AUTH_LOGIN_PASS, + MTA_READY, + MTA_MAIL, + MTA_RCPT, + MTA_DATA, + MTA_BODY, + MTA_EOM, + MTA_LMTP_EOM, + MTA_RSET, + MTA_QUIT, +}; + +#define MTA_FORCE_ANYSSL 0x0001 +#define MTA_FORCE_SMTPS 0x0002 +#define MTA_FORCE_TLS 0x0004 +#define MTA_FORCE_PLAIN 0x0008 +#define MTA_WANT_SECURE 0x0010 +#define MTA_DOWNGRADE_PLAIN 0x0080 + +#define MTA_TLS 0x0100 +#define MTA_TLS_VERIFIED 0x0200 + +#define MTA_FREE 0x0400 +#define MTA_LMTP 0x0800 +#define MTA_WAIT 0x1000 +#define MTA_HANGON 0x2000 +#define MTA_RECONN 0x4000 + +#define MTA_EXT_STARTTLS 0x01 +#define MTA_EXT_PIPELINING 0x02 +#define MTA_EXT_AUTH 0x04 +#define MTA_EXT_AUTH_PLAIN 0x08 +#define MTA_EXT_AUTH_LOGIN 0x10 +#define MTA_EXT_SIZE 0x20 + +struct mta_session { + uint64_t id; + struct mta_relay *relay; + struct mta_route *route; + char *helo; + char *mxname; + + char *username; + + int flags; + + int attempt; + int use_smtps; + int use_starttls; + int use_smtp_tls; + int ready; + + struct event ev; + struct io *io; + int ext; + + size_t ext_size; + + size_t msgtried; + size_t msgcount; + size_t rcptcount; + int hangon; + + enum mta_state state; + struct mta_task *task; + struct mta_envelope *currevp; + FILE *datafp; + size_t datalen; + + size_t failures; + + char replybuf[2048]; +}; + +static void mta_session_init(void); +static void mta_start(int fd, short ev, void *arg); +static void mta_io(struct io *, int, void *); +static void mta_free(struct mta_session *); +static void mta_getnameinfo_cb(void *, int, const char *, const char *); +static void mta_on_ptr(void *, void *, void *); +static void mta_on_timeout(struct runq *, void *); +static void mta_connect(struct mta_session *); +static void mta_enter_state(struct mta_session *, int); +static void mta_flush_task(struct mta_session *, int, const char *, size_t, int); +static void mta_error(struct mta_session *, const char *, ...); +static void mta_send(struct mta_session *, char *, ...); +static ssize_t mta_queue_data(struct mta_session *); +static void mta_response(struct mta_session *, char *); +static const char * mta_strstate(int); +static void mta_cert_init(struct mta_session *); +static void mta_cert_init_cb(void *, int, const char *, const void *, size_t); +static void mta_cert_verify(struct mta_session *); +static void mta_cert_verify_cb(void *, int); +static void mta_tls_verified(struct mta_session *); +static struct mta_session *mta_tree_pop(struct tree *, uint64_t); +static const char * dsn_strret(enum dsn_ret); +static const char * dsn_strnotify(uint8_t); + +void mta_hoststat_update(const char *, const char *); +void mta_hoststat_reschedule(const char *); +void mta_hoststat_cache(const char *, uint64_t); +void mta_hoststat_uncache(const char *, uint64_t); + + +static void mta_filter_begin(struct mta_session *); +static void mta_filter_end(struct mta_session *); +static void mta_connected(struct mta_session *); +static void mta_disconnected(struct mta_session *); + +static void mta_report_link_connect(struct mta_session *, const char *, int, + const struct sockaddr_storage *, + const struct sockaddr_storage *); +static void mta_report_link_greeting(struct mta_session *, const char *); +static void mta_report_link_identify(struct mta_session *, const char *, const char *); +static void mta_report_link_tls(struct mta_session *, const char *); +static void mta_report_link_disconnect(struct mta_session *); +static void mta_report_link_auth(struct mta_session *, const char *, const char *); +static void mta_report_tx_reset(struct mta_session *, uint32_t); +static void mta_report_tx_begin(struct mta_session *, uint32_t); +static void mta_report_tx_mail(struct mta_session *, uint32_t, const char *, int); +static void mta_report_tx_rcpt(struct mta_session *, uint32_t, const char *, int); +static void mta_report_tx_envelope(struct mta_session *, uint32_t, uint64_t); +static void mta_report_tx_data(struct mta_session *, uint32_t, int); +static void mta_report_tx_commit(struct mta_session *, uint32_t, size_t); +static void mta_report_tx_rollback(struct mta_session *, uint32_t); +static void mta_report_protocol_client(struct mta_session *, const char *); +static void mta_report_protocol_server(struct mta_session *, const char *); +#if 0 +static void mta_report_filter_response(struct mta_session *, int, int, const char *); +#endif +static void mta_report_timeout(struct mta_session *); + + +static struct tree wait_helo; +static struct tree wait_ptr; +static struct tree wait_fd; +static struct tree wait_tls_init; +static struct tree wait_tls_verify; + +static struct runq *hangon; + +#define SESSION_FILTERED(s) \ + ((s)->relay->dispatcher->u.remote.filtername) + +static void +mta_session_init(void) +{ + static int init = 0; + + if (!init) { + tree_init(&wait_helo); + tree_init(&wait_ptr); + tree_init(&wait_fd); + tree_init(&wait_tls_init); + tree_init(&wait_tls_verify); + runq_init(&hangon, mta_on_timeout); + init = 1; + } +} + +void +mta_session(struct mta_relay *relay, struct mta_route *route, const char *mxname) +{ + struct mta_session *s; + struct timeval tv; + + mta_session_init(); + + s = xcalloc(1, sizeof *s); + s->id = generate_uid(); + s->relay = relay; + s->route = route; + s->mxname = xstrdup(mxname); + + mta_filter_begin(s); + + if (relay->flags & RELAY_LMTP) + s->flags |= MTA_LMTP; + switch (relay->tls) { + case RELAY_TLS_SMTPS: + s->flags |= MTA_FORCE_SMTPS; + s->flags |= MTA_WANT_SECURE; + break; + case RELAY_TLS_STARTTLS: + s->flags |= MTA_FORCE_TLS; + s->flags |= MTA_WANT_SECURE; + break; + case RELAY_TLS_OPPORTUNISTIC: + /* do not force anything, try tls then smtp */ + break; + case RELAY_TLS_NO: + s->flags |= MTA_FORCE_PLAIN; + break; + default: + fatalx("bad value for relay->tls: %d", relay->tls); + } + + log_debug("debug: mta: %p: spawned for relay %s", s, + mta_relay_to_text(relay)); + stat_increment("mta.session", 1); + + if (route->dst->ptrname || route->dst->lastptrquery) { + /* We want to delay the connection since to always notify + * the relay asynchronously. + */ + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_set(&s->ev, mta_start, s); + evtimer_add(&s->ev, &tv); + } else if (waitq_wait(&route->dst->ptrname, mta_on_ptr, s)) { + resolver_getnameinfo(s->route->dst->sa, 0, mta_getnameinfo_cb, s); + } +} + +void +mta_session_imsg(struct mproc *p, struct imsg *imsg) +{ + struct mta_session *s; + struct msg m; + uint64_t reqid; + const char *name; + int status; + struct stat sb; + + switch (imsg->hdr.type) { + + case IMSG_MTA_OPEN_MESSAGE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + s = mta_tree_pop(&wait_fd, reqid); + if (s == NULL) { + if (imsg->fd != -1) + close(imsg->fd); + return; + } + + if (imsg->fd == -1) { + log_debug("debug: mta: failed to obtain msg fd"); + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Could not get message fd", 0, 0); + mta_enter_state(s, MTA_READY); + return; + } + + if ((s->ext & MTA_EXT_SIZE) && s->ext_size != 0) { + if (fstat(imsg->fd, &sb) == -1) { + log_debug("debug: mta: failed to stat msg fd"); + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Could not stat message fd", 0, 0); + mta_enter_state(s, MTA_READY); + close(imsg->fd); + return; + } + if (sb.st_size > (off_t)s->ext_size) { + log_debug("debug: mta: message too large for peer"); + mta_flush_task(s, IMSG_MTA_DELIVERY_PERMFAIL, + "message too large for peer", 0, 0); + mta_enter_state(s, MTA_READY); + close(imsg->fd); + return; + } + } + + s->datafp = fdopen(imsg->fd, "r"); + if (s->datafp == NULL) + fatal("mta: fdopen"); + + mta_enter_state(s, MTA_MAIL); + return; + + case IMSG_MTA_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + if (status == LKA_OK) + m_get_string(&m, &name); + m_end(&m); + + s = mta_tree_pop(&wait_helo, reqid); + if (s == NULL) + return; + + if (status == LKA_OK) { + s->helo = xstrdup(name); + mta_connect(s); + } else { + mta_source_error(s->relay, s->route, + "Failed to retrieve helo string"); + mta_free(s); + } + return; + + default: + errx(1, "mta_session_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); + } +} + +static struct mta_session * +mta_tree_pop(struct tree *wait, uint64_t reqid) +{ + struct mta_session *s; + + s = tree_xpop(wait, reqid); + if (s->flags & MTA_FREE) { + log_debug("debug: mta: %p: zombie session", s); + mta_free(s); + return (NULL); + } + s->flags &= ~MTA_WAIT; + + return (s); +} + +static void +mta_free(struct mta_session *s) +{ + struct mta_relay *relay; + struct mta_route *route; + + log_debug("debug: mta: %p: session done", s); + + mta_disconnected(s); + + if (s->ready) + s->relay->nconn_ready -= 1; + + if (s->flags & MTA_HANGON) { + log_debug("debug: mta: %p: cancelling hangon timer", s); + runq_cancel(hangon, s); + } + + if (s->io) + io_free(s->io); + + if (s->task) + fatalx("current task should have been deleted already"); + if (s->datafp) { + fclose(s->datafp); + s->datalen = 0; + } + free(s->helo); + + relay = s->relay; + route = s->route; + free(s->username); + free(s->mxname); + free(s); + stat_decrement("mta.session", 1); + mta_route_collect(relay, route); +} + +static void +mta_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) +{ + struct mta_session *s = arg; + struct mta_host *h; + + h = s->route->dst; + h->lastptrquery = time(NULL); + if (host) + h->ptrname = xstrdup(host); + waitq_run(&h->ptrname, h->ptrname); +} + +static void +mta_on_timeout(struct runq *runq, void *arg) +{ + struct mta_session *s = arg; + + log_debug("mta: timeout for session hangon"); + + s->flags &= ~MTA_HANGON; + s->hangon++; + + mta_enter_state(s, MTA_READY); +} + +static void +mta_on_ptr(void *tag, void *arg, void *data) +{ + struct mta_session *s = arg; + + mta_connect(s); +} + +static void +mta_start(int fd, short ev, void *arg) +{ + struct mta_session *s = arg; + + mta_connect(s); +} + +static void +mta_connect(struct mta_session *s) +{ + struct sockaddr_storage ss; + struct sockaddr *sa; + int portno; + const char *schema; + + if (s->helo == NULL) { + if (s->relay->helotable && s->route->src->sa) { + m_create(p_lka, IMSG_MTA_LOOKUP_HELO, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->relay->helotable); + m_add_sockaddr(p_lka, s->route->src->sa); + m_close(p_lka); + tree_xset(&wait_helo, s->id, s); + s->flags |= MTA_WAIT; + return; + } + else if (s->relay->heloname) + s->helo = xstrdup(s->relay->heloname); + else + s->helo = xstrdup(env->sc_hostname); + } + + if (s->io) { + io_free(s->io); + s->io = NULL; + } + + s->use_smtps = s->use_starttls = s->use_smtp_tls = 0; + + switch (s->attempt) { + case 0: + if (s->flags & MTA_FORCE_SMTPS) + s->use_smtps = 1; /* smtps */ + else if (s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL)) + s->use_starttls = 1; /* tls, tls+smtps */ + else if (!(s->flags & MTA_FORCE_PLAIN)) + s->use_smtp_tls = 1; + break; + case 1: + if (s->flags & MTA_FORCE_ANYSSL) { + s->use_smtps = 1; /* tls+smtps */ + break; + } + else if (s->flags & MTA_DOWNGRADE_PLAIN) { + /* smtp, with tls failure */ + break; + } + default: + mta_free(s); + return; + } + portno = s->use_smtps ? 465 : 25; + + /* Override with relay-specified port */ + if (s->relay->port) + portno = s->relay->port; + + memmove(&ss, s->route->dst->sa, SA_LEN(s->route->dst->sa)); + sa = (struct sockaddr *)&ss; + + if (sa->sa_family == AF_INET) + ((struct sockaddr_in *)sa)->sin_port = htons(portno); + else if (sa->sa_family == AF_INET6) + ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); + + s->attempt += 1; + if (s->use_smtp_tls) + schema = "smtp://"; + else if (s->use_starttls) + schema = "smtp+tls://"; + else if (s->use_smtps) + schema = "smtps://"; + else if (s->flags & MTA_LMTP) + schema = "lmtp://"; + else + schema = "smtp+notls://"; + + log_info("%016"PRIx64" mta " + "connecting address=%s%s:%d host=%s", + s->id, schema, sa_to_text(s->route->dst->sa), + portno, s->route->dst->ptrname); + + mta_enter_state(s, MTA_INIT); + s->io = io_new(); + io_set_callback(s->io, mta_io, s); + io_set_timeout(s->io, 300000); + if (io_connect(s->io, sa, s->route->src->sa) == -1) { + /* + * This error is most likely a "no route", + * so there is no need to try again. + */ + log_debug("debug: mta: io_connect failed: %s", io_error(s->io)); + if (errno == EADDRNOTAVAIL) + mta_source_error(s->relay, s->route, io_error(s->io)); + else + mta_error(s, "Connection failed: %s", io_error(s->io)); + mta_free(s); + } +} + +static void +mta_enter_state(struct mta_session *s, int newstate) +{ + struct mta_envelope *e; + size_t envid_sz; + int oldstate; + ssize_t q; + char ibuf[LINE_MAX]; + char obuf[LINE_MAX]; + int offset; + const char *srs_sender; + +again: + oldstate = s->state; + + log_trace(TRACE_MTA, "mta: %p: %s -> %s", s, + mta_strstate(oldstate), + mta_strstate(newstate)); + + s->state = newstate; + + memset(s->replybuf, 0, sizeof s->replybuf); + + /* don't try this at home! */ +#define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while (0) + + switch (s->state) { + case MTA_INIT: + case MTA_BANNER: + break; + + case MTA_EHLO: + s->ext = 0; + mta_send(s, "EHLO %s", s->helo); + mta_report_link_identify(s, "EHLO", s->helo); + break; + + case MTA_HELO: + s->ext = 0; + mta_send(s, "HELO %s", s->helo); + mta_report_link_identify(s, "HELO", s->helo); + break; + + case MTA_LHLO: + s->ext = 0; + mta_send(s, "LHLO %s", s->helo); + mta_report_link_identify(s, "LHLO", s->helo); + break; + + case MTA_STARTTLS: + if (s->flags & MTA_DOWNGRADE_PLAIN) + mta_enter_state(s, MTA_AUTH); + if (s->flags & MTA_TLS) /* already started */ + mta_enter_state(s, MTA_AUTH); + else if ((s->ext & MTA_EXT_STARTTLS) == 0) { + if (s->flags & MTA_FORCE_TLS || s->flags & MTA_WANT_SECURE) { + mta_error(s, "TLS required but not supported by remote host"); + s->flags |= MTA_RECONN; + } + else + /* server doesn't support starttls, do not use it */ + mta_enter_state(s, MTA_AUTH); + } + else + mta_send(s, "STARTTLS"); + break; + + case MTA_AUTH: + if (s->relay->secret && s->flags & MTA_TLS) { + if (s->ext & MTA_EXT_AUTH) { + if (s->ext & MTA_EXT_AUTH_PLAIN) { + mta_enter_state(s, MTA_AUTH_PLAIN); + break; + } + if (s->ext & MTA_EXT_AUTH_LOGIN) { + mta_enter_state(s, MTA_AUTH_LOGIN); + break; + } + log_debug("debug: mta: %p: no supported AUTH method on session", s); + mta_error(s, "no supported AUTH method"); + } + else { + log_debug("debug: mta: %p: AUTH not advertised on session", s); + mta_error(s, "AUTH not advertised"); + } + } + else if (s->relay->secret) { + log_debug("debug: mta: %p: not using AUTH on non-TLS " + "session", s); + mta_error(s, "Refuse to AUTH over unsecure channel"); + mta_connect(s); + } else { + mta_enter_state(s, MTA_READY); + } + break; + + case MTA_AUTH_PLAIN: + memset(ibuf, 0, sizeof ibuf); + if (base64_decode(s->relay->secret, (unsigned char *)ibuf, + sizeof(ibuf)-1) == -1) { + log_debug("debug: mta: %p: credentials too large on session", s); + mta_error(s, "Credentials too large"); + break; + } + s->username = xstrdup(ibuf+1); + mta_send(s, "AUTH PLAIN %s", s->relay->secret); + break; + + case MTA_AUTH_LOGIN: + mta_send(s, "AUTH LOGIN"); + break; + + case MTA_AUTH_LOGIN_USER: + memset(ibuf, 0, sizeof ibuf); + if (base64_decode(s->relay->secret, (unsigned char *)ibuf, + sizeof(ibuf)-1) == -1) { + log_debug("debug: mta: %p: credentials too large on session", s); + mta_error(s, "Credentials too large"); + break; + } + s->username = xstrdup(ibuf+1); + + memset(obuf, 0, sizeof obuf); + base64_encode((unsigned char *)ibuf + 1, strlen(ibuf + 1), obuf, sizeof obuf); + mta_send(s, "%s", obuf); + + memset(ibuf, 0, sizeof ibuf); + memset(obuf, 0, sizeof obuf); + break; + + case MTA_AUTH_LOGIN_PASS: + memset(ibuf, 0, sizeof ibuf); + if (base64_decode(s->relay->secret, (unsigned char *)ibuf, + sizeof(ibuf)-1) == -1) { + log_debug("debug: mta: %p: credentials too large on session", s); + mta_error(s, "Credentials too large"); + break; + } + + offset = strlen(ibuf+1)+2; + memset(obuf, 0, sizeof obuf); + base64_encode((unsigned char *)ibuf + offset, strlen(ibuf + offset), obuf, sizeof obuf); + mta_send(s, "%s", obuf); + + memset(ibuf, 0, sizeof ibuf); + memset(obuf, 0, sizeof obuf); + break; + + case MTA_READY: + /* Ready to send a new mail */ + if (s->ready == 0) { + s->ready = 1; + s->relay->nconn_ready += 1; + mta_route_ok(s->relay, s->route); + } + + if (s->msgtried >= MAX_TRYBEFOREDISABLE) { + log_info("%016"PRIx64" mta host-rejects-all-mails", + s->id); + mta_route_down(s->relay, s->route); + mta_enter_state(s, MTA_QUIT); + break; + } + + if (s->msgcount >= s->relay->limits->max_mail_per_session) { + log_debug("debug: mta: " + "%p: cannot send more message to relay %s", s, + mta_relay_to_text(s->relay)); + mta_enter_state(s, MTA_QUIT); + break; + } + + /* + * When downgrading from opportunistic TLS, clear flag and + * possibly reuse the same task (forbidden in other cases). + */ + if (s->flags & MTA_DOWNGRADE_PLAIN) + s->flags &= ~MTA_DOWNGRADE_PLAIN; + else if (s->task) + fatalx("task should be NULL at this point"); + + if (s->task == NULL) + s->task = mta_route_next_task(s->relay, s->route); + if (s->task == NULL) { + log_debug("debug: mta: %p: no task for relay %s", + s, mta_relay_to_text(s->relay)); + + if (s->relay->nconn > 1 || + s->hangon >= s->relay->limits->sessdelay_keepalive) { + mta_enter_state(s, MTA_QUIT); + break; + } + + log_debug("mta: debug: last connection: hanging on for %llds", + (long long)(s->relay->limits->sessdelay_keepalive - + s->hangon)); + s->flags |= MTA_HANGON; + runq_schedule(hangon, 1, s); + break; + } + + log_debug("debug: mta: %p: handling next task for relay %s", s, + mta_relay_to_text(s->relay)); + + stat_increment("mta.task.running", 1); + + m_create(p_queue, IMSG_MTA_OPEN_MESSAGE, 0, 0, -1); + m_add_id(p_queue, s->id); + m_add_msgid(p_queue, s->task->msgid); + m_close(p_queue); + + tree_xset(&wait_fd, s->id, s); + s->flags |= MTA_WAIT; + break; + + case MTA_MAIL: + s->currevp = TAILQ_FIRST(&s->task->envelopes); + + e = s->currevp; + s->hangon = 0; + s->msgtried++; + envid_sz = strlen(e->dsn_envid); + + /* SRS-encode if requested for the relay action, AND we're not + * bouncing, AND we have an RCPT which means we are forwarded, + * AND the RCPT has a '@' just for sanity check (will always). + */ + if (env->sc_srs_key != NULL && + s->relay->srs && + strchr(s->task->sender, '@') && + e->rcpt && + strchr(e->rcpt, '@')) { + /* encode and replace task sender with new SRS-sender */ + srs_sender = srs_encode(s->task->sender, + strchr(e->rcpt, '@') + 1); + if (srs_sender) { + free(s->task->sender); + s->task->sender = xstrdup(srs_sender); + } + } + + if (s->ext & MTA_EXT_DSN) { + mta_send(s, "MAIL FROM:<%s>%s%s%s%s", + s->task->sender, + e->dsn_ret ? " RET=" : "", + e->dsn_ret ? dsn_strret(e->dsn_ret) : "", + envid_sz ? " ENVID=" : "", + envid_sz ? e->dsn_envid : ""); + } else + mta_send(s, "MAIL FROM:<%s>", s->task->sender); + break; + + case MTA_RCPT: + if (s->currevp == NULL) + s->currevp = TAILQ_FIRST(&s->task->envelopes); + + e = s->currevp; + if (s->ext & MTA_EXT_DSN) { + mta_send(s, "RCPT TO:<%s>%s%s%s%s", + e->dest, + e->dsn_notify ? " NOTIFY=" : "", + e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "", + e->dsn_orcpt ? " ORCPT=rfc822;" : "", + e->dsn_orcpt ? e->dsn_orcpt : ""); + } else + mta_send(s, "RCPT TO:<%s>", e->dest); + + mta_report_tx_envelope(s, s->task->msgid, e->id); + s->rcptcount++; + break; + + case MTA_DATA: + fseek(s->datafp, 0, SEEK_SET); + mta_send(s, "DATA"); + break; + + case MTA_BODY: + if (s->datafp == NULL) { + log_trace(TRACE_MTA, "mta: %p: end-of-file", s); + mta_enter_state(s, MTA_EOM); + break; + } + + if ((q = mta_queue_data(s)) == -1) { + s->flags |= MTA_FREE; + break; + } + if (q == 0) { + mta_enter_state(s, MTA_BODY); + break; + } + + log_trace(TRACE_MTA, "mta: %p: >>> [...%zd bytes...]", s, q); + break; + + case MTA_EOM: + mta_send(s, "."); + break; + + case MTA_LMTP_EOM: + /* LMTP reports status of each delivery, so enable read */ + io_set_read(s->io); + break; + + case MTA_RSET: + if (s->datafp) { + fclose(s->datafp); + s->datafp = NULL; + s->datalen = 0; + } + mta_send(s, "RSET"); + break; + + case MTA_QUIT: + mta_send(s, "QUIT"); + break; + + default: + fatalx("mta_enter_state: unknown state"); + } +#undef mta_enter_state +} + +/* + * Handle a response to an SMTP command + */ +static void +mta_response(struct mta_session *s, char *line) +{ + struct mta_envelope *e; + struct sockaddr_storage ss; + struct sockaddr *sa; + const char *domain; + char *pbuf; + socklen_t sa_len; + char buf[LINE_MAX]; + int delivery; + + switch (s->state) { + + case MTA_BANNER: + if (line[0] != '2') { + mta_error(s, "BANNER rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + + pbuf = ""; + if (strlen(line) > 4) { + (void)strlcpy(buf, line + 4, sizeof buf); + if ((pbuf = strchr(buf, ' '))) + *pbuf = '\0'; + pbuf = valid_domainpart(buf) ? buf : ""; + } + mta_report_link_greeting(s, pbuf); + + if (s->flags & MTA_LMTP) + mta_enter_state(s, MTA_LHLO); + else + mta_enter_state(s, MTA_EHLO); + break; + + case MTA_EHLO: + if (line[0] != '2') { + /* rejected at ehlo state */ + if ((s->relay->flags & RELAY_AUTH) || + (s->flags & MTA_WANT_SECURE)) { + mta_error(s, "EHLO rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_HELO); + return; + } + if (!(s->flags & MTA_FORCE_PLAIN)) + mta_enter_state(s, MTA_STARTTLS); + else + mta_enter_state(s, MTA_READY); + break; + + case MTA_HELO: + if (line[0] != '2') { + mta_error(s, "HELO rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_READY); + break; + + case MTA_LHLO: + if (line[0] != '2') { + mta_error(s, "LHLO rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_READY); + break; + + case MTA_STARTTLS: + if (line[0] != '2') { + if (!(s->flags & MTA_WANT_SECURE)) { + mta_enter_state(s, MTA_AUTH); + return; + } + /* XXX mark that the MX doesn't support STARTTLS */ + mta_error(s, "STARTTLS rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + + mta_cert_init(s); + break; + + case MTA_AUTH_PLAIN: + if (line[0] != '2') { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_report_link_auth(s, s->username, "pass"); + mta_enter_state(s, MTA_READY); + break; + + case MTA_AUTH_LOGIN: + if (strncmp(line, "334 ", 4) != 0) { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_AUTH_LOGIN_USER); + break; + + case MTA_AUTH_LOGIN_USER: + if (strncmp(line, "334 ", 4) != 0) { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_AUTH_LOGIN_PASS); + break; + + case MTA_AUTH_LOGIN_PASS: + if (line[0] != '2') { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_report_link_auth(s, s->username, "pass"); + mta_enter_state(s, MTA_READY); + break; + + case MTA_MAIL: + if (line[0] != '2') { + if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + + mta_flush_task(s, delivery, line, 0, 0); + mta_enter_state(s, MTA_RSET); + return; + } + mta_report_tx_begin(s, s->task->msgid); + mta_report_tx_mail(s, s->task->msgid, s->task->sender, 1); + mta_enter_state(s, MTA_RCPT); + break; + + case MTA_RCPT: + e = s->currevp; + + /* remove envelope from hosttat cache if there */ + if ((domain = strchr(e->dest, '@')) != NULL) { + domain++; + mta_hoststat_uncache(domain, e->id); + } + + s->currevp = TAILQ_NEXT(s->currevp, entry); + if (line[0] == '2') { + s->failures = 0; + /* + * this host is up, reschedule envelopes that + * were cached for reschedule. + */ + if (domain) + mta_hoststat_reschedule(domain); + } + else { + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + s->failures++; + + /* remove failed envelope from task list */ + TAILQ_REMOVE(&s->task->envelopes, e, entry); + stat_decrement("mta.envelope", 1); + + /* log right away */ + (void)snprintf(buf, sizeof(buf), "%s", + mta_host_to_text(s->route->dst)); + + e->session = s->id; + /* XXX */ + /* + * getsockname() can only fail with ENOBUFS here + * best effort, don't log source ... + */ + sa_len = sizeof(ss); + sa = (struct sockaddr *)&ss; + if (getsockname(io_fileno(s->io), sa, &sa_len) == -1) + mta_delivery_log(e, NULL, buf, delivery, line); + else + mta_delivery_log(e, sa_to_text(sa), + buf, delivery, line); + + if (domain) + mta_hoststat_update(domain, e->status); + mta_delivery_notify(e); + + if (s->relay->limits->max_failures_per_session && + s->failures == s->relay->limits->max_failures_per_session) { + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Too many consecutive errors, closing connection", 0, 1); + mta_enter_state(s, MTA_QUIT); + break; + } + + /* + * if no more envelopes, flush failed queue + */ + if (TAILQ_EMPTY(&s->task->envelopes)) { + mta_flush_task(s, IMSG_MTA_DELIVERY_OK, + "No envelope", 0, 0); + mta_enter_state(s, MTA_RSET); + break; + } + } + + switch (line[0]) { + case '2': + mta_report_tx_rcpt(s, + s->task->msgid, e->dest, 1); + break; + case '4': + mta_report_tx_rcpt(s, + s->task->msgid, e->dest, -1); + break; + case '5': + mta_report_tx_rcpt(s, + s->task->msgid, e->dest, 0); + break; + } + + if (s->currevp == NULL) + mta_enter_state(s, MTA_DATA); + else + mta_enter_state(s, MTA_RCPT); + break; + + case MTA_DATA: + if (line[0] == '2' || line[0] == '3') { + mta_report_tx_data(s, s->task->msgid, 1); + mta_enter_state(s, MTA_BODY); + break; + } + + if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + mta_report_tx_data(s, s->task->msgid, + delivery == IMSG_MTA_DELIVERY_TEMPFAIL ? -1 : 0); + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + mta_flush_task(s, delivery, line, 0, 0); + mta_enter_state(s, MTA_RSET); + break; + + case MTA_LMTP_EOM: + case MTA_EOM: + if (line[0] == '2') { + delivery = IMSG_MTA_DELIVERY_OK; + s->msgtried = 0; + s->msgcount++; + } + else if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + if (delivery != IMSG_MTA_DELIVERY_OK) { + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + } + else { + mta_report_tx_commit(s, s->task->msgid, s->datalen); + mta_report_tx_reset(s, s->task->msgid); + } + mta_flush_task(s, delivery, line, (s->flags & MTA_LMTP) ? 1 : 0, 0); + if (s->task) { + s->rcptcount--; + mta_enter_state(s, MTA_LMTP_EOM); + } else { + s->rcptcount = 0; + if (s->relay->limits->sessdelay_transaction) { + log_debug("debug: mta: waiting for %llds before next transaction", + (long long int)s->relay->limits->sessdelay_transaction); + s->hangon = s->relay->limits->sessdelay_transaction -1; + s->flags |= MTA_HANGON; + runq_schedule(hangon, + s->relay->limits->sessdelay_transaction, s); + } + else + mta_enter_state(s, MTA_READY); + } + break; + + case MTA_RSET: + s->rcptcount = 0; + + if (s->task) { + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + } + if (s->relay->limits->sessdelay_transaction) { + log_debug("debug: mta: waiting for %llds after reset", + (long long int)s->relay->limits->sessdelay_transaction); + s->hangon = s->relay->limits->sessdelay_transaction -1; + s->flags |= MTA_HANGON; + runq_schedule(hangon, + s->relay->limits->sessdelay_transaction, s); + } + else + mta_enter_state(s, MTA_READY); + break; + + default: + fatalx("mta_response() bad state"); + } +} + +static void +mta_io(struct io *io, int evt, void *arg) +{ + struct mta_session *s = arg; + char *line, *msg, *p; + size_t len; + const char *error; + int cont; + + log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + + case IO_CONNECTED: + mta_connected(s); + + if (s->use_smtps) { + io_set_write(io); + mta_cert_init(s); + } + else { + mta_enter_state(s, MTA_BANNER); + io_set_read(io); + } + break; + + case IO_TLSREADY: + log_info("%016"PRIx64" mta tls ciphers=%s", + s->id, ssl_to_text(io_tls(s->io))); + s->flags |= MTA_TLS; + + mta_report_link_tls(s, + ssl_to_text(io_tls(s->io))); + + mta_cert_verify(s); + break; + + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if (line == NULL) { + if (io_datalen(s->io) >= LINE_MAX) { + mta_error(s, "Input too long"); + mta_free(s); + } + return; + } + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); + mta_report_protocol_server(s, line); + + if ((error = parse_smtp_response(line, len, &msg, &cont))) { + mta_error(s, "Bad response: %s", error); + mta_free(s); + return; + } + + /* read extensions */ + if (s->state == MTA_EHLO) { + if (strcmp(msg, "STARTTLS") == 0) + s->ext |= MTA_EXT_STARTTLS; + else if (strncmp(msg, "AUTH ", 5) == 0) { + s->ext |= MTA_EXT_AUTH; + if ((p = strstr(msg, " PLAIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + s->ext |= MTA_EXT_AUTH_PLAIN; + if ((p = strstr(msg, " LOGIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + s->ext |= MTA_EXT_AUTH_LOGIN; + } + else if (strcmp(msg, "PIPELINING") == 0) + s->ext |= MTA_EXT_PIPELINING; + else if (strcmp(msg, "DSN") == 0) + s->ext |= MTA_EXT_DSN; + else if (strncmp(msg, "SIZE ", 5) == 0) { + s->ext_size = strtonum(msg+5, 0, UINT32_MAX, &error); + if (error == NULL) + s->ext |= MTA_EXT_SIZE; + } + } + + /* continuation reply, we parse out the repeating statuses and ESC */ + if (cont) { + if (s->replybuf[0] == '\0') + (void)strlcat(s->replybuf, line, sizeof s->replybuf); + else if (len > 4) { + p = line + 4; + if (isdigit((unsigned char)p[0]) && p[1] == '.' && + isdigit((unsigned char)p[2]) && p[3] == '.' && + isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5])) + p += 5; + (void)strlcat(s->replybuf, p, sizeof s->replybuf); + } + goto nextline; + } + + /* last line of a reply, check if we're on a continuation to parse out status and ESC. + * if we overflow reply buffer or are not on continuation, log entire last line. + */ + if (s->replybuf[0] == '\0') + (void)strlcat(s->replybuf, line, sizeof s->replybuf); + else if (len > 4) { + p = line + 4; + if (isdigit((unsigned char)p[0]) && p[1] == '.' && + isdigit((unsigned char)p[2]) && p[3] == '.' && + isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5])) + p += 5; + if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf) + (void)strlcpy(s->replybuf, line, sizeof s->replybuf); + } + + if (s->state == MTA_QUIT) { + log_info("%016"PRIx64" mta disconnected reason=quit messages=%zu", + s->id, s->msgcount); + mta_free(s); + return; + } + io_set_write(io); + mta_response(s, s->replybuf); + if (s->flags & MTA_FREE) { + mta_free(s); + return; + } + if (s->flags & MTA_RECONN) { + s->flags &= ~MTA_RECONN; + mta_connect(s); + return; + } + + if (io_datalen(s->io)) { + log_debug("debug: mta: remaining data in input buffer"); + mta_error(s, "Remote host sent too much data"); + if (s->flags & MTA_WAIT) + s->flags |= MTA_FREE; + else + mta_free(s); + } + break; + + case IO_LOWAT: + if (s->state == MTA_BODY) { + mta_enter_state(s, MTA_BODY); + if (s->flags & MTA_FREE) { + mta_free(s); + return; + } + } + + if (io_queued(s->io) == 0) + io_set_read(io); + break; + + case IO_TIMEOUT: + log_debug("debug: mta: %p: connection timeout", s); + mta_error(s, "Connection timeout"); + mta_report_timeout(s); + if (!s->ready) + mta_connect(s); + else + mta_free(s); + break; + + case IO_ERROR: + case IO_TLSERROR: + log_debug("debug: mta: %p: IO error: %s", s, io_error(io)); + + if (s->state == MTA_STARTTLS && s->use_smtp_tls) { + /* error in non-strict SSL negotiation, downgrade to plain */ + log_info("smtp-out: Error on session %016"PRIx64 + ": opportunistic TLS failed, " + "downgrading to plain", s->id); + s->flags &= ~MTA_TLS; + s->flags |= MTA_DOWNGRADE_PLAIN; + mta_connect(s); + break; + } + + mta_error(s, "IO Error: %s", io_error(io)); + mta_free(s); + break; + + case IO_DISCONNECTED: + log_debug("debug: mta: %p: disconnected in state %s", + s, mta_strstate(s->state)); + mta_error(s, "Connection closed unexpectedly"); + if (!s->ready) + mta_connect(s); + else + mta_free(s); + break; + + default: + fatalx("mta_io() bad event"); + } +} + +static void +mta_send(struct mta_session *s, char *fmt, ...) +{ + va_list ap; + char *p; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&p, fmt, ap)) == -1) + fatal("mta: vasprintf"); + va_end(ap); + + log_trace(TRACE_MTA, "mta: %p: >>> %s", s, p); + + if (strncasecmp(p, "AUTH PLAIN ", 11) == 0) + mta_report_protocol_client(s, "AUTH PLAIN ********"); + else if (s->state == MTA_AUTH_LOGIN_USER || s->state == MTA_AUTH_LOGIN_PASS) + mta_report_protocol_client(s, "********"); + else + mta_report_protocol_client(s, p); + + io_xprintf(s->io, "%s\r\n", p); + + free(p); +} + +/* + * Queue some data into the input buffer + */ +static ssize_t +mta_queue_data(struct mta_session *s) +{ + char *ln = NULL; + size_t sz = 0, q; + ssize_t len; + + q = io_queued(s->io); + + while (io_queued(s->io) < MTA_HIWAT) { + if ((len = getline(&ln, &sz, s->datafp)) == -1) + break; + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + s->datalen += io_xprintf(s->io, "%s%s\r\n", *ln == '.' ? "." : "", ln); + } + + free(ln); + if (ferror(s->datafp)) { + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Error reading content file", 0, 0); + return (-1); + } + + if (feof(s->datafp)) { + fclose(s->datafp); + s->datafp = NULL; + } + + return (io_queued(s->io) - q); +} + +static void +mta_flush_task(struct mta_session *s, int delivery, const char *error, size_t count, + int cache) +{ + struct mta_envelope *e; + char relay[LINE_MAX]; + size_t n; + struct sockaddr_storage ss; + struct sockaddr *sa; + socklen_t sa_len; + const char *domain; + + (void)snprintf(relay, sizeof relay, "%s", mta_host_to_text(s->route->dst)); + n = 0; + while ((e = TAILQ_FIRST(&s->task->envelopes))) { + + if (count && n == count) { + stat_decrement("mta.envelope", n); + return; + } + + TAILQ_REMOVE(&s->task->envelopes, e, entry); + + /* we're about to log, associate session to envelope */ + e->session = s->id; + e->ext = s->ext; + + /* XXX */ + /* + * getsockname() can only fail with ENOBUFS here + * best effort, don't log source ... + */ + sa = (struct sockaddr *)&ss; + sa_len = sizeof(ss); + if (getsockname(io_fileno(s->io), sa, &sa_len) == -1) + mta_delivery_log(e, NULL, relay, delivery, error); + else + mta_delivery_log(e, sa_to_text(sa), + relay, delivery, error); + + mta_delivery_notify(e); + + domain = strchr(e->dest, '@'); + if (domain) { + domain++; + mta_hoststat_update(domain, error); + if (cache) + mta_hoststat_cache(domain, e->id); + } + + n++; + } + + free(s->task->sender); + free(s->task); + s->task = NULL; + + if (s->datafp) { + fclose(s->datafp); + s->datafp = NULL; + } + + stat_decrement("mta.envelope", n); + stat_decrement("mta.task.running", 1); + stat_decrement("mta.task", 1); +} + +static void +mta_error(struct mta_session *s, const char *fmt, ...) +{ + va_list ap; + char *error; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&error, fmt, ap)) == -1) + fatal("mta: vasprintf"); + va_end(ap); + + if (s->msgcount) + log_info("smtp-out: Error on session %016"PRIx64 + " after %zu message%s sent: %s", s->id, s->msgcount, + (s->msgcount > 1) ? "s" : "", error); + else + log_info("%016"PRIx64" mta error reason=%s", + s->id, error); + + /* + * If not connected yet, and the error is not local, just ignore it + * and try to reconnect. + */ + if (s->state == MTA_INIT && + (errno == ETIMEDOUT || errno == ECONNREFUSED)) { + log_debug("debug: mta: not reporting route error yet"); + free(error); + return; + } + + mta_route_error(s->relay, s->route); + + if (s->task) + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, error, 0, 0); + + free(error); +} + +static void +mta_cert_init(struct mta_session *s) +{ + const char *name; + int fallback; + + if (s->relay->pki_name) { + name = s->relay->pki_name; + fallback = 0; + } + else { + name = s->helo; + fallback = 1; + } + + if (cert_init(name, fallback, mta_cert_init_cb, s)) { + tree_xset(&wait_tls_init, s->id, s); + s->flags |= MTA_WAIT; + } +} + +static void +mta_cert_init_cb(void *arg, int status, const char *name, const void *cert, + size_t cert_len) +{ + struct mta_session *s = arg; + void *ssl; + char *xname = NULL, *xcert = NULL; + + if (s->flags & MTA_WAIT) + mta_tree_pop(&wait_tls_init, s->id); + + if (status == CA_FAIL && s->relay->pki_name) { + log_info("%016"PRIx64" mta closing reason=ca-failure", s->id); + mta_free(s); + return; + } + + if (name) + xname = xstrdup(name); + if (cert) + xcert = xmemdup(cert, cert_len); + ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers); + free(xname); + free(xcert); + if (ssl == NULL) + fatal("mta: ssl_mta_init"); + io_start_tls(s->io, ssl); +} + +static void +mta_cert_verify(struct mta_session *s) +{ + const char *name; + int fallback; + + if (s->relay->ca_name) { + name = s->relay->ca_name; + fallback = 0; + } + else { + name = s->helo; + fallback = 1; + } + + if (cert_verify(io_tls(s->io), name, fallback, mta_cert_verify_cb, s)) { + tree_xset(&wait_tls_verify, s->id, s); + io_pause(s->io, IO_IN); + s->flags |= MTA_WAIT; + } +} + +static void +mta_cert_verify_cb(void *arg, int status) +{ + struct mta_session *s = arg; + int match, resume = 0; + X509 *cert; + + if (s->flags & MTA_WAIT) { + mta_tree_pop(&wait_tls_verify, s->id); + resume = 1; + } + + if (status == CERT_OK) { + cert = SSL_get_peer_certificate(io_tls(s->io)); + if (!cert) + status = CERT_NOCERT; + else { + match = 0; + (void)ssl_check_name(cert, s->mxname, &match); + X509_free(cert); + if (!match) { + log_info("%016"PRIx64" mta " + "ssl_check_name: no match for '%s' in cert", + s->id, s->mxname); + status = CERT_INVALID; + } + } + } + + if (status == CERT_OK) + s->flags |= MTA_TLS_VERIFIED; + else if (s->relay->flags & RELAY_TLS_VERIFY) { + errno = 0; + mta_error(s, "SSL certificate check failed"); + mta_free(s); + return; + } + + mta_tls_verified(s); + if (resume) + io_resume(s->io, IO_IN); +} + +static void +mta_tls_verified(struct mta_session *s) +{ + X509 *x; + + x = SSL_get_peer_certificate(io_tls(s->io)); + if (x) { + log_info("%016"PRIx64" mta " + "server-cert-check result=\"%s\"", + s->id, + (s->flags & MTA_TLS_VERIFIED) ? "success" : "failure"); + X509_free(x); + } + + if (s->use_smtps) { + mta_enter_state(s, MTA_BANNER); + io_set_read(s->io); + } + else + mta_enter_state(s, MTA_EHLO); +} + +static const char * +dsn_strret(enum dsn_ret ret) +{ + if (ret == DSN_RETHDRS) + return "HDRS"; + else if (ret == DSN_RETFULL) + return "FULL"; + else { + log_debug("mta: invalid ret %d", ret); + return "???"; + } +} + +static const char * +dsn_strnotify(uint8_t arg) +{ + static char buf[32]; + size_t sz; + + buf[0] = '\0'; + if (arg & DSN_SUCCESS) + (void)strlcat(buf, "SUCCESS,", sizeof(buf)); + + if (arg & DSN_FAILURE) + (void)strlcat(buf, "FAILURE,", sizeof(buf)); + + if (arg & DSN_DELAY) + (void)strlcat(buf, "DELAY,", sizeof(buf)); + + if (arg & DSN_NEVER) + (void)strlcat(buf, "NEVER,", sizeof(buf)); + + /* trim trailing comma */ + sz = strlen(buf); + if (sz) + buf[sz - 1] = '\0'; + + return (buf); +} + +#define CASE(x) case x : return #x + +static const char * +mta_strstate(int state) +{ + switch (state) { + CASE(MTA_INIT); + CASE(MTA_BANNER); + CASE(MTA_EHLO); + CASE(MTA_HELO); + CASE(MTA_STARTTLS); + CASE(MTA_AUTH); + CASE(MTA_AUTH_PLAIN); + CASE(MTA_AUTH_LOGIN); + CASE(MTA_AUTH_LOGIN_USER); + CASE(MTA_AUTH_LOGIN_PASS); + CASE(MTA_READY); + CASE(MTA_MAIL); + CASE(MTA_RCPT); + CASE(MTA_DATA); + CASE(MTA_BODY); + CASE(MTA_EOM); + CASE(MTA_LMTP_EOM); + CASE(MTA_RSET); + CASE(MTA_QUIT); + default: + return "MTA_???"; + } +} + +static void +mta_filter_begin(struct mta_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->relay->dispatcher->u.remote.filtername); + m_close(p_lka); +} + +static void +mta_filter_end(struct mta_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +mta_connected(struct mta_session *s) +{ + struct sockaddr sa_src; + struct sockaddr sa_dest; + int sa_len; + + log_info("%016"PRIx64" mta connected", s->id); + + if (getsockname(io_fileno(s->io), &sa_src, &sa_len) == -1) + bzero(&sa_src, sizeof sa_src); + if (getpeername(io_fileno(s->io), &sa_dest, &sa_len) == -1) + bzero(&sa_dest, sizeof sa_dest); + + mta_report_link_connect(s, + s->route->dst->ptrname, 1, + (struct sockaddr_storage *)&sa_src, + (struct sockaddr_storage *)&sa_dest); +} + +static void +mta_disconnected(struct mta_session *s) +{ + mta_report_link_disconnect(s); + mta_filter_end(s); +} + + +static void +mta_report_link_connect(struct mta_session *s, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_connect("smtp-out", s->id, rdns, fcrdns, ss_src, ss_dest); +} + +static void +mta_report_link_greeting(struct mta_session *s, + const char *domain) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_greeting("smtp-out", s->id, domain); +} + +static void +mta_report_link_identify(struct mta_session *s, const char *method, const char *identity) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_identify("smtp-out", s->id, method, identity); +} + +static void +mta_report_link_tls(struct mta_session *s, const char *ssl) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_tls("smtp-out", s->id, ssl); +} + +static void +mta_report_link_disconnect(struct mta_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_disconnect("smtp-out", s->id); +} + +static void +mta_report_link_auth(struct mta_session *s, const char *user, const char *result) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_auth("smtp-out", s->id, user, result); +} + +static void +mta_report_tx_reset(struct mta_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_reset("smtp-out", s->id, msgid); +} + +static void +mta_report_tx_begin(struct mta_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_begin("smtp-out", s->id, msgid); +} + +static void +mta_report_tx_mail(struct mta_session *s, uint32_t msgid, const char *address, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_mail("smtp-out", s->id, msgid, address, ok); +} + +static void +mta_report_tx_rcpt(struct mta_session *s, uint32_t msgid, const char *address, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rcpt("smtp-out", s->id, msgid, address, ok); +} + +static void +mta_report_tx_envelope(struct mta_session *s, uint32_t msgid, uint64_t evpid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_envelope("smtp-out", s->id, msgid, evpid); +} + +static void +mta_report_tx_data(struct mta_session *s, uint32_t msgid, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_data("smtp-out", s->id, msgid, ok); +} + +static void +mta_report_tx_commit(struct mta_session *s, uint32_t msgid, size_t msgsz) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_commit("smtp-out", s->id, msgid, msgsz); +} + +static void +mta_report_tx_rollback(struct mta_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rollback("smtp-out", s->id, msgid); +} + +static void +mta_report_protocol_client(struct mta_session *s, const char *command) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_client("smtp-out", s->id, command); +} + +static void +mta_report_protocol_server(struct mta_session *s, const char *response) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_server("smtp-out", s->id, response); +} + +#if 0 +static void +mta_report_filter_response(struct mta_session *s, int phase, int response, const char *param) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_filter_response("smtp-out", s->id, phase, response, param); +} +#endif + +static void +mta_report_timeout(struct mta_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_timeout("smtp-out", s->id); +} diff --git a/usr.sbin/smtpd/newaliases.8 b/usr.sbin/smtpd/newaliases.8 new file mode 100644 index 00000000..b82e4515 --- /dev/null +++ b/usr.sbin/smtpd/newaliases.8 @@ -0,0 +1,86 @@ +.\" $OpenBSD: newaliases.8,v 1.12 2018/07/20 15:35:33 millert Exp $ +.\" +.\" Copyright (c) 2009 Jacek Masiulaniec +.\" Copyright (c) 2008-2009 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 20 2018 $ +.Dt NEWALIASES 8 +.Os +.Sh NAME +.Nm newaliases +.Nd rebuild mail aliases +.Sh SYNOPSIS +.Nm newaliases +.Op Fl f Ar file +.Sh DESCRIPTION +The +.Nm +utility makes changes to the mail aliases file visible to +.Xr smtpd 8 . +It should be run every time the +.Xr aliases 5 +file is changed. +The location of the alias file is defined in +.Xr smtpd.conf 5 , +and defaults to +.Pa /etc/mail/aliases . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl f Ar file +Use +.Ar file +as the configuration file, +instead of the default +.Pa /etc/mail/smtpd.conf . +.El +.Pp +If using database (db) files, +.Nm +is equivalent to running +.Xr makemap 8 +as follows: +.Bd -literal -offset indent +# makemap -t aliases /etc/mail/aliases +.Ed +.Pp +If using plain text files, +.Nm +is equivalent to running +.Xr smtpctl 8 +as follows: +.Bd -literal -offset indent +# smtpctl update table aliases +.Ed +.Sh FILES +.Bl -tag -width "/etc/mail/aliasesXXX" -compact +.It Pa /etc/mail/aliases +List of local user mail aliases. +.It Pa /etc/mail/virtual +List of virtual host aliases. +.El +.Sh EXIT STATUS +.Ex -std newaliases +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr makemap 8 , +.Xr smtpctl 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +command first appeared in +.Ox 4.6 +as a replacement for the equivalent command shipped with sendmail. diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y new file mode 100644 index 00000000..db5be747 --- /dev/null +++ b/usr.sbin/smtpd/parse.y @@ -0,0 +1,3598 @@ +/* $OpenBSD: parse.y,v 1.277 2020/02/24 23:54:27 millert Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include "includes.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UTIL_H +#include +#endif + +#include + +#include "smtpd.h" +#include "ssl.h" +#include "log.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +struct smtpd *conf = NULL; +static int errors = 0; + +struct table *table = NULL; +struct mta_limits *limits; +static struct pki *pki; +static struct ca *sca; + +struct dispatcher *dispatcher; +struct rule *rule; +struct filter_proc *processor; +struct filter_config *filter_config; +static uint32_t last_dynchain_id = 1; + +enum listen_options { + LO_FAMILY = 0x000001, + LO_PORT = 0x000002, + LO_SSL = 0x000004, + LO_FILTER = 0x000008, + LO_PKI = 0x000010, + LO_AUTH = 0x000020, + LO_TAG = 0x000040, + LO_HOSTNAME = 0x000080, + LO_HOSTNAMES = 0x000100, + LO_MASKSOURCE = 0x000200, + LO_NODSN = 0x000400, + LO_SENDERS = 0x000800, + LO_RECEIVEDAUTH = 0x001000, + LO_MASQUERADE = 0x002000, + LO_CA = 0x004000, + LO_PROXY = 0x008000, +}; + +static struct listen_opts { + char *ifx; + int family; + in_port_t port; + uint16_t ssl; + char *filtername; + char *pki; + char *ca; + uint16_t auth; + struct table *authtable; + char *tag; + char *hostname; + struct table *hostnametable; + struct table *sendertable; + uint16_t flags; + + uint32_t options; +} listen_opts; + +static void create_sock_listener(struct listen_opts *); +static void create_if_listener(struct listen_opts *); +static void config_listener(struct listener *, struct listen_opts *); +static int host_v4(struct listen_opts *); +static int host_v6(struct listen_opts *); +static int host_dns(struct listen_opts *); +static int interface(struct listen_opts *); + +int delaytonum(char *); +int is_if_in_group(const char *, const char *); + +static int config_lo_mask_source(struct listen_opts *); + +typedef struct { + union { + int64_t number; + struct table *table; + char *string; + struct host *host; + struct mailaddr *maddr; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ACTION ALIAS ANY ARROW AUTH AUTH_OPTIONAL +%token BACKUP BOUNCE BYPASS +%token CA CERT CHAIN CHROOT CIPHERS COMMIT COMPRESSION CONNECT +%token DATA DATA_LINE DHE DISCONNECT DOMAIN +%token EHLO ENABLE ENCRYPTION ERROR EXPAND_ONLY +%token FCRDNS FILTER FOR FORWARD_ONLY FROM +%token GROUP +%token HELO HELO_SRC HOST HOSTNAME HOSTNAMES +%token INCLUDE INET4 INET6 +%token JUNK +%token KEY +%token LIMIT LISTEN LMTP LOCAL +%token MAIL_FROM MAILDIR MASK_SRC MASQUERADE MATCH MAX_MESSAGE_SIZE MAX_DEFERRED MBOX MDA MTA MX +%token NO_DSN NO_VERIFY NOOP +%token ON +%token PHASE PKI PORT PROC PROC_EXEC PROXY_V2 +%token QUEUE QUIT +%token RCPT_TO RDNS RECIPIENT RECEIVEDAUTH REGEX RELAY REJECT REPORT REWRITE RSET +%token SCHEDULER SENDER SENDERS SMTP SMTP_IN SMTP_OUT SMTPS SOCKET SRC SRS SUB_ADDR_DELIM +%token TABLE TAG TAGGED TLS TLS_REQUIRE TTL +%token USER USERBASE +%token VERIFY VIRTUAL +%token WARN_INTERVAL WRAPPER + +%token STRING +%token NUMBER +%type table +%type size negation +%type tables tablenew tableref +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar varset '\n' + | grammar bounce '\n' + | grammar ca '\n' + | grammar mda '\n' + | grammar mta '\n' + | grammar pki '\n' + | grammar proc '\n' + | grammar queue '\n' + | grammar scheduler '\n' + | grammar smtp '\n' + | grammar srs '\n' + | grammar listen '\n' + | grammar table '\n' + | grammar dispatcher '\n' + | grammar match '\n' + | grammar filter '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +varset : STRING '=' STRING { + char *s = $1; + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +comma : ',' + | nl + | /* empty */ + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl + ; + +negation : '!' { $$ = 1; } + | /* empty */ { $$ = 0; } + ; + +assign : '=' | ARROW; + + +keyval : STRING assign STRING { + table_add(table, $1, $3); + free($1); + free($3); + } + ; + +keyval_list : keyval + | keyval comma keyval_list + ; + +stringel : STRING { + table_add(table, $1, NULL); + free($1); + } + ; + +string_list : stringel + | stringel comma string_list + ; + +tableval_list : string_list { } + | keyval_list { } + ; + +bounce: +BOUNCE WARN_INTERVAL { + memset(conf->sc_bounce_warn, 0, sizeof conf->sc_bounce_warn); +} bouncedelays +; + + +ca: +CA STRING { + char buf[HOST_NAME_MAX+1]; + + /* if not catchall, check that it is a valid domain */ + if (strcmp($2, "*") != 0) { + if (!res_hnok($2)) { + yyerror("not a valid domain name: %s", $2); + free($2); + YYERROR; + } + } + xlowercase(buf, $2, sizeof(buf)); + free($2); + sca = dict_get(conf->sc_ca_dict, buf); + if (sca == NULL) { + sca = xcalloc(1, sizeof *sca); + (void)strlcpy(sca->ca_name, buf, sizeof(sca->ca_name)); + dict_set(conf->sc_ca_dict, sca->ca_name, sca); + } +} ca_params +; + + +ca_params_opt: +CERT STRING { + sca->ca_cert_file = $2; +} +; + +ca_params: +ca_params_opt +; + + +mda: +MDA LIMIT limits_mda +| MDA WRAPPER STRING STRING { + if (dict_get(conf->sc_mda_wrappers, $3)) { + yyerror("mda wrapper already declared with that name: %s", $3); + YYERROR; + } + dict_set(conf->sc_mda_wrappers, $3, $4); +} +; + + +mta: +MTA MAX_DEFERRED NUMBER { + conf->sc_mta_max_deferred = $3; +} +| MTA LIMIT FOR DOMAIN STRING { + struct mta_limits *d; + + limits = dict_get(conf->sc_limits_dict, $5); + if (limits == NULL) { + limits = xcalloc(1, sizeof(*limits)); + dict_xset(conf->sc_limits_dict, $5, limits); + d = dict_xget(conf->sc_limits_dict, "default"); + memmove(limits, d, sizeof(*limits)); + } + free($5); +} limits_mta +| MTA LIMIT { + limits = dict_get(conf->sc_limits_dict, "default"); +} limits_mta +; + + +pki: +PKI STRING { + char buf[HOST_NAME_MAX+1]; + + /* if not catchall, check that it is a valid domain */ + if (strcmp($2, "*") != 0) { + if (!res_hnok($2)) { + yyerror("not a valid domain name: %s", $2); + free($2); + YYERROR; + } + } + xlowercase(buf, $2, sizeof(buf)); + free($2); + pki = dict_get(conf->sc_pki_dict, buf); + if (pki == NULL) { + pki = xcalloc(1, sizeof *pki); + (void)strlcpy(pki->pki_name, buf, sizeof(pki->pki_name)); + dict_set(conf->sc_pki_dict, pki->pki_name, pki); + } +} pki_params +; + +pki_params_opt: +CERT STRING { + pki->pki_cert_file = $2; +} +| KEY STRING { + pki->pki_key_file = $2; +} +| DHE STRING { + if (strcasecmp($2, "none") == 0) + pki->pki_dhe = 0; + else if (strcasecmp($2, "auto") == 0) + pki->pki_dhe = 1; + else if (strcasecmp($2, "legacy") == 0) + pki->pki_dhe = 2; + else { + yyerror("invalid DHE keyword: %s", $2); + free($2); + YYERROR; + } + free($2); +} +; + + +pki_params: +pki_params_opt pki_params +| /* empty */ +; + + +proc: +PROC STRING STRING { + if (dict_get(conf->sc_filter_processes_dict, $2)) { + yyerror("processor already exists with that name: %s", $2); + free($2); + free($3); + YYERROR; + } + processor = xcalloc(1, sizeof *processor); + processor->command = $3; +} proc_params { + dict_set(conf->sc_filter_processes_dict, $2, processor); + processor = NULL; +} +; + + +proc_params_opt: +USER STRING { + if (processor->user) { + yyerror("user already specified for this processor"); + free($2); + YYERROR; + } + processor->user = $2; +} +| GROUP STRING { + if (processor->group) { + yyerror("group already specified for this processor"); + free($2); + YYERROR; + } + processor->group = $2; +} +| CHROOT STRING { + if (processor->chroot) { + yyerror("chroot already specified for this processor"); + free($2); + YYERROR; + } + processor->chroot = $2; +} +; + +proc_params: +proc_params_opt proc_params +| /* empty */ +; + + +queue: +QUEUE COMPRESSION { + conf->sc_queue_flags |= QUEUE_COMPRESSION; +} +| QUEUE ENCRYPTION { + conf->sc_queue_flags |= QUEUE_ENCRYPTION; +} +| QUEUE ENCRYPTION STRING { + if (strcasecmp($3, "stdin") == 0 || strcasecmp($3, "-") == 0) { + conf->sc_queue_key = "stdin"; + free($3); + } + else + conf->sc_queue_key = $3; + conf->sc_queue_flags |= QUEUE_ENCRYPTION; +} +| QUEUE TTL STRING { + conf->sc_ttl = delaytonum($3); + if (conf->sc_ttl == -1) { + yyerror("invalid ttl delay: %s", $3); + free($3); + YYERROR; + } + free($3); +} +; + + +scheduler: +SCHEDULER LIMIT limits_scheduler +; + + +smtp: +SMTP LIMIT limits_smtp +| SMTP CIPHERS STRING { + conf->sc_tls_ciphers = $3; +} +| SMTP MAX_MESSAGE_SIZE size { + conf->sc_maxsize = $3; +} +| SMTP SUB_ADDR_DELIM STRING { + if (strlen($3) != 1) { + yyerror("subaddressing-delimiter must be one character"); + free($3); + YYERROR; + } + if (isspace((unsigned char)*$3) || !isprint((unsigned char)*$3) || *$3 == '@') { + yyerror("sub-addr-delim uses invalid character"); + free($3); + YYERROR; + } + conf->sc_subaddressing_delim = $3; +} +; + +srs: +SRS KEY STRING { + conf->sc_srs_key = $3; +} +| SRS KEY BACKUP STRING { + conf->sc_srs_key_backup = $4; +} +| SRS TTL STRING { + conf->sc_srs_ttl = delaytonum($3); + if (conf->sc_srs_ttl == -1) { + yyerror("ttl delay \"%s\" is invalid", $3); + free($3); + YYERROR; + } + + conf->sc_srs_ttl /= 86400; + if (conf->sc_srs_ttl == 0) { + yyerror("ttl delay \"%s\" is too short", $3); + free($3); + YYERROR; + } + free($3); +} +; + + +dispatcher_local_option: +USER STRING { + if (dispatcher->u.local.is_mbox) { + yyerror("user may not be specified for this dispatcher"); + YYERROR; + } + + if (dispatcher->u.local.forward_only) { + yyerror("user may not be specified for forward-only"); + YYERROR; + } + + if (dispatcher->u.local.expand_only) { + yyerror("user may not be specified for expand-only"); + YYERROR; + } + + if (dispatcher->u.local.user) { + yyerror("user already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.local.user = $2; +} +| ALIAS tables { + struct table *t = $2; + + if (dispatcher->u.local.table_alias) { + yyerror("alias mapping already specified for this dispatcher"); + YYERROR; + } + + if (dispatcher->u.local.table_virtual) { + yyerror("virtual mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ALIAS)) { + yyerror("table \"%s\" may not be used for alias lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.local.table_alias = strdup(t->t_name); +} +| VIRTUAL tables { + struct table *t = $2; + + if (dispatcher->u.local.table_virtual) { + yyerror("virtual mapping already specified for this dispatcher"); + YYERROR; + } + + if (dispatcher->u.local.table_alias) { + yyerror("alias mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ALIAS)) { + yyerror("table \"%s\" may not be used for virtual lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.local.table_virtual = strdup(t->t_name); +} +| USERBASE tables { + struct table *t = $2; + + if (dispatcher->u.local.table_userbase) { + yyerror("userbase mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_USERINFO)) { + yyerror("table \"%s\" may not be used for userbase lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.local.table_userbase = strdup(t->t_name); +} +| WRAPPER STRING { + if (! dict_get(conf->sc_mda_wrappers, $2)) { + yyerror("no mda wrapper with that name: %s", $2); + YYERROR; + } + dispatcher->u.local.mda_wrapper = $2; +} +; + +dispatcher_local_options: +dispatcher_local_option dispatcher_local_options +| /* empty */ +; + +dispatcher_local: +MBOX { + dispatcher->u.local.is_mbox = 1; + asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.local -f %%{mbox.from} -- %%{user.username}"); +} dispatcher_local_options +| MAILDIR { + asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.maildir"); +} dispatcher_local_options +| MAILDIR JUNK { + asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.maildir -j"); +} dispatcher_local_options +| MAILDIR STRING { + if (strncmp($2, "~/", 2) == 0) + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir \"%%{user.directory}/%s\"", $2+2); + else + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir \"%s\"", $2); +} dispatcher_local_options +| MAILDIR STRING JUNK { + if (strncmp($2, "~/", 2) == 0) + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir -j \"%%{user.directory}/%s\"", $2+2); + else + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir -j \"%s\"", $2); +} dispatcher_local_options +| LMTP STRING { + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.lmtp -d \"%s\" -u", $2); +} dispatcher_local_options +| LMTP STRING RCPT_TO { + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.lmtp -d \"%s\" -r", $2); +} dispatcher_local_options +| MDA STRING { + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.mda \"%s\"", $2); +} dispatcher_local_options +| FORWARD_ONLY { + dispatcher->u.local.forward_only = 1; +} dispatcher_local_options +| EXPAND_ONLY { + dispatcher->u.local.expand_only = 1; +} dispatcher_local_options + +; + +dispatcher_remote_option: +HELO STRING { + if (dispatcher->u.remote.helo) { + yyerror("helo already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.helo = $2; +} +| HELO_SRC tables { + struct table *t = $2; + + if (dispatcher->u.remote.helo_source) { + yyerror("helo-source mapping already specified for this dispatcher"); + YYERROR; + } + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { + yyerror("table \"%s\" may not be used for helo-source lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.helo_source = strdup(t->t_name); +} +| PKI STRING { + if (dispatcher->u.remote.pki) { + yyerror("pki already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.pki = $2; +} +| CA STRING { + if (dispatcher->u.remote.ca) { + yyerror("ca already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.ca = $2; +} +| SRC tables { + struct table *t = $2; + + if (dispatcher->u.remote.source) { + yyerror("source mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_SOURCE)) { + yyerror("table \"%s\" may not be used for source lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.source = strdup(t->t_name); +} +| MAIL_FROM STRING { + if (dispatcher->u.remote.mail_from) { + yyerror("mail-from already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.mail_from = $2; +} +| BACKUP MX STRING { + if (dispatcher->u.remote.backup) { + yyerror("backup already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.smarthost) { + yyerror("backup and host are mutually exclusive"); + YYERROR; + } + + dispatcher->u.remote.backup = 1; + dispatcher->u.remote.backupmx = $3; +} +| BACKUP { + if (dispatcher->u.remote.backup) { + yyerror("backup already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.smarthost) { + yyerror("backup and host are mutually exclusive"); + YYERROR; + } + + dispatcher->u.remote.backup = 1; +} +| HOST tables { + struct table *t = $2; + + if (dispatcher->u.remote.smarthost) { + yyerror("host mapping already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.backup) { + yyerror("backup and host are mutually exclusive"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_RELAYHOST)) { + yyerror("table \"%s\" may not be used for host lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.smarthost = strdup(t->t_name); +} +| DOMAIN tables { + struct table *t = $2; + + if (dispatcher->u.remote.smarthost) { + yyerror("host mapping already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.backup) { + yyerror("backup and domain are mutually exclusive"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_RELAYHOST)) { + yyerror("table \"%s\" may not be used for host lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.smarthost = strdup(t->t_name); + dispatcher->u.remote.smarthost_domain = 1; +} +| TLS { + if (dispatcher->u.remote.tls_required == 1) { + yyerror("tls already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.tls_required = 1; +} +| TLS NO_VERIFY { + if (dispatcher->u.remote.tls_required == 1) { + yyerror("tls already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.tls_required = 1; + dispatcher->u.remote.tls_noverify = 1; +} +| AUTH tables { + struct table *t = $2; + + if (dispatcher->u.remote.smarthost == NULL) { + yyerror("auth may not be specified without host on a dispatcher"); + YYERROR; + } + + if (dispatcher->u.remote.auth) { + yyerror("auth mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_CREDENTIALS)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.auth = strdup(t->t_name); +} +| FILTER STRING { + struct filter_config *fc; + + if (dispatcher->u.remote.filtername) { + yyerror("filter already specified for this dispatcher"); + YYERROR; + } + + if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } + fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_OUT; + dispatcher->u.remote.filtername = $2; +} +| FILTER { + char buffer[128]; + char *filtername; + + if (dispatcher->u.remote.filtername) { + yyerror("filter already specified for this dispatcher"); + YYERROR; + } + + do { + (void)snprintf(buffer, sizeof buffer, "", last_dynchain_id++); + } while (dict_check(conf->sc_filters_dict, buffer)); + + filtername = xstrdup(buffer); + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_OUT; + dict_init(&filter_config->chain_procs); + dispatcher->u.remote.filtername = filtername; +} '{' filter_list '}' { + dict_set(conf->sc_filters_dict, dispatcher->u.remote.filtername, filter_config); + filter_config = NULL; +} +| SRS { + if (conf->sc_srs_key == NULL) { + yyerror("an srs key is required for srs to be specified in an action"); + YYERROR; + } + if (dispatcher->u.remote.srs == 1) { + yyerror("srs already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.srs = 1; +} +; + +dispatcher_remote_options: +dispatcher_remote_option dispatcher_remote_options +| /* empty */ +; + +dispatcher_remote : +RELAY dispatcher_remote_options +; + +dispatcher_type: +dispatcher_local { + dispatcher->type = DISPATCHER_LOCAL; +} +| dispatcher_remote { + dispatcher->type = DISPATCHER_REMOTE; +} +; + +dispatcher_option: +TTL STRING { + if (dispatcher->ttl) { + yyerror("ttl already specified for this dispatcher"); + YYERROR; + } + + dispatcher->ttl = delaytonum($2); + if (dispatcher->ttl == -1) { + yyerror("ttl delay \"%s\" is invalid", $2); + free($2); + YYERROR; + } + free($2); +} +; + +dispatcher_options: +dispatcher_option dispatcher_options +| /* empty */ +; + +dispatcher: +ACTION STRING { + if (dict_get(conf->sc_dispatchers, $2)) { + yyerror("dispatcher already declared with that name: %s", $2); + YYERROR; + } + dispatcher = xcalloc(1, sizeof *dispatcher); +} dispatcher_type dispatcher_options { + if (dispatcher->type == DISPATCHER_LOCAL) + if (dispatcher->u.local.table_userbase == NULL) + dispatcher->u.local.table_userbase = ""; + dict_set(conf->sc_dispatchers, $2, dispatcher); + dispatcher = NULL; +} +; + +match_option: +negation TAG tables { + struct table *t = $3; + + if (rule->flag_tag) { + yyerror("tag already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING)) { + yyerror("table \"%s\" may not be used for tag lookups", + t->t_name); + YYERROR; + } + + rule->flag_tag = $1 ? -1 : 1; + rule->table_tag = strdup(t->t_name); +} +| +negation TAG REGEX tables { + struct table *t = $4; + + if (rule->flag_tag) { + yyerror("tag already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for tag lookups", + t->t_name); + YYERROR; + } + + rule->flag_tag = $1 ? -1 : 1; + rule->flag_tag_regex = 1; + rule->table_tag = strdup(t->t_name); +} + +| negation HELO tables { + struct table *t = $3; + + if (rule->flag_smtp_helo) { + yyerror("helo already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for helo lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_helo = $1 ? -1 : 1; + rule->table_smtp_helo = strdup(t->t_name); +} +| negation HELO REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_helo) { + yyerror("helo already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for helo lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_helo = $1 ? -1 : 1; + rule->flag_smtp_helo_regex = 1; + rule->table_smtp_helo = strdup(t->t_name); +} +| negation TLS { + if (rule->flag_smtp_starttls) { + yyerror("tls already specified for this rule"); + YYERROR; + } + rule->flag_smtp_starttls = $1 ? -1 : 1; +} +| negation AUTH { + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + rule->flag_smtp_auth = $1 ? -1 : 1; +} +| negation AUTH tables { + struct table *t = $3; + + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING|K_CREDENTIALS)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->table_smtp_auth = strdup(t->t_name); +} +| negation AUTH REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->flag_smtp_auth_regex = 1; + rule->table_smtp_auth = strdup(t->t_name); +} +| negation MAIL_FROM tables { + struct table *t = $3; + + if (rule->flag_smtp_mail_from) { + yyerror("mail-from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for mail-from lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} +| negation MAIL_FROM REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_mail_from) { + yyerror("mail-from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for mail-from lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->flag_smtp_mail_from_regex = 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} +| negation RCPT_TO tables { + struct table *t = $3; + + if (rule->flag_smtp_rcpt_to) { + yyerror("rcpt-to already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for rcpt-to lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} +| negation RCPT_TO REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_rcpt_to) { + yyerror("rcpt-to already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for rcpt-to lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->flag_smtp_rcpt_to_regex = 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} + +| negation FROM SOCKET { + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_socket = 1; +} +| negation FROM LOCAL { + struct table *t = table_find(conf, ""); + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM ANY { + struct table *t = table_find(conf, ""); + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM SRC tables { + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_NETADDR)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM SRC REGEX tables { + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_regex = 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM RDNS { + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_rdns = 1; +} +| negation FROM RDNS tables { + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for rdns lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_rdns = 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM RDNS REGEX tables { + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for rdns lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_regex = 1; + rule->flag_from_rdns = 1; + rule->table_from = strdup(t->t_name); +} + +| negation FROM AUTH { + struct table *anyhost = table_find(conf, ""); + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_auth = $1 ? -1 : 1; +} +| negation FROM AUTH tables { + struct table *anyhost = table_find(conf, ""); + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING|K_CREDENTIALS)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->table_smtp_auth = strdup(t->t_name); +} +| negation FROM AUTH REGEX tables { + struct table *anyhost = table_find(conf, ""); + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->flag_smtp_auth_regex = 1; + rule->table_smtp_auth = strdup(t->t_name); +} + +| negation FROM MAIL_FROM tables { + struct table *anyhost = table_find(conf, ""); + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} +| negation FROM MAIL_FROM REGEX tables { + struct table *anyhost = table_find(conf, ""); + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->flag_smtp_mail_from_regex = 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} + +| negation FOR LOCAL { + struct table *t = table_find(conf, ""); + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + rule->flag_for = $1 ? -1 : 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR ANY { + struct table *t = table_find(conf, ""); + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + rule->flag_for = $1 ? -1 : 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR DOMAIN tables { + struct table *t = $4; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for 'for' lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = $1 ? -1 : 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR DOMAIN REGEX tables { + struct table *t = $5; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for 'for' lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = $1 ? -1 : 1; + rule->flag_for_regex = 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR RCPT_TO tables { + struct table *anyhost = table_find(conf, ""); + struct table *t = $4; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for for lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = 1; + rule->table_for = strdup(anyhost->t_name); + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} +| negation FOR RCPT_TO REGEX tables { + struct table *anyhost = table_find(conf, ""); + struct table *t = $5; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for for lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = 1; + rule->table_for = strdup(anyhost->t_name); + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->flag_smtp_rcpt_to_regex = 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} +; + +match_options: +match_option match_options +| /* empty */ +; + +match_dispatcher: +STRING { + if (dict_get(conf->sc_dispatchers, $1) == NULL) { + yyerror("no such dispatcher: %s", $1); + YYERROR; + } + rule->dispatcher = $1; +} +; + +action: +REJECT { + rule->reject = 1; +} +| ACTION match_dispatcher +; + +match: +MATCH { + rule = xcalloc(1, sizeof *rule); +} match_options action { + if (!rule->flag_from) { + rule->table_from = strdup(""); + rule->flag_from = 1; + } + if (!rule->flag_for) { + rule->table_for = strdup(""); + rule->flag_for = 1; + } + TAILQ_INSERT_TAIL(conf->sc_rules, rule, r_entry); + rule = NULL; +} +; + +filter_action_builtin: +filter_action_builtin_nojunk +| JUNK { + filter_config->junk = 1; +} +| BYPASS { + filter_config->bypass = 1; +} +; + +filter_action_builtin_nojunk: +REJECT STRING { + filter_config->reject = $2; +} +| DISCONNECT STRING { + filter_config->disconnect = $2; +} +| REWRITE STRING { + filter_config->rewrite = $2; +} +| REPORT STRING { + filter_config->report = $2; +} +; + +filter_phase_check_fcrdns: +negation FCRDNS { + filter_config->not_fcrdns = $1 ? -1 : 1; + filter_config->fcrdns = 1; +} +; + +filter_phase_check_rdns: +negation RDNS { + filter_config->not_rdns = $1 ? -1 : 1; + filter_config->rdns = 1; +} +; + +filter_phase_check_rdns_table: +negation RDNS tables { + filter_config->not_rdns_table = $1 ? -1 : 1; + filter_config->rdns_table = $3; +} +; +filter_phase_check_rdns_regex: +negation RDNS REGEX tables { + filter_config->not_rdns_regex = $1 ? -1 : 1; + filter_config->rdns_regex = $4; +} +; + +filter_phase_check_src_table: +negation SRC tables { + filter_config->not_src_table = $1 ? -1 : 1; + filter_config->src_table = $3; +} +; +filter_phase_check_src_regex: +negation SRC REGEX tables { + filter_config->not_src_regex = $1 ? -1 : 1; + filter_config->src_regex = $4; +} +; + +filter_phase_check_helo_table: +negation HELO tables { + filter_config->not_helo_table = $1 ? -1 : 1; + filter_config->helo_table = $3; +} +; +filter_phase_check_helo_regex: +negation HELO REGEX tables { + filter_config->not_helo_regex = $1 ? -1 : 1; + filter_config->helo_regex = $4; +} +; + +filter_phase_check_auth: +negation AUTH { + filter_config->not_auth = $1 ? -1 : 1; + filter_config->auth = 1; +} +; +filter_phase_check_auth_table: +negation AUTH tables { + filter_config->not_auth_table = $1 ? -1 : 1; + filter_config->auth_table = $3; +} +; +filter_phase_check_auth_regex: +negation AUTH REGEX tables { + filter_config->not_auth_regex = $1 ? -1 : 1; + filter_config->auth_regex = $4; +} +; + +filter_phase_check_mail_from_table: +negation MAIL_FROM tables { + filter_config->not_mail_from_table = $1 ? -1 : 1; + filter_config->mail_from_table = $3; +} +; +filter_phase_check_mail_from_regex: +negation MAIL_FROM REGEX tables { + filter_config->not_mail_from_regex = $1 ? -1 : 1; + filter_config->mail_from_regex = $4; +} +; + +filter_phase_check_rcpt_to_table: +negation RCPT_TO tables { + filter_config->not_rcpt_to_table = $1 ? -1 : 1; + filter_config->rcpt_to_table = $3; +} +; +filter_phase_check_rcpt_to_regex: +negation RCPT_TO REGEX tables { + filter_config->not_rcpt_to_regex = $1 ? -1 : 1; + filter_config->rcpt_to_regex = $4; +} +; + +filter_phase_global_options: +filter_phase_check_fcrdns | +filter_phase_check_rdns | +filter_phase_check_rdns_regex | +filter_phase_check_rdns_table | +filter_phase_check_src_regex | +filter_phase_check_src_table; + +filter_phase_connect_options: +filter_phase_global_options; + +filter_phase_helo_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; + +filter_phase_auth_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_global_options; + +filter_phase_mail_from_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_global_options; + +filter_phase_rcpt_to_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_check_rcpt_to_table | +filter_phase_check_rcpt_to_regex | +filter_phase_global_options; + +filter_phase_data_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_global_options; + +/* +filter_phase_quit_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; + +filter_phase_rset_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; + +filter_phase_noop_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; +*/ + +filter_phase_commit_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_global_options; + + +filter_phase_connect: +CONNECT { + filter_config->phase = FILTER_CONNECT; +} MATCH filter_phase_connect_options filter_action_builtin +; + + +filter_phase_helo: +HELO { + filter_config->phase = FILTER_HELO; +} MATCH filter_phase_helo_options filter_action_builtin +; + +filter_phase_ehlo: +EHLO { + filter_config->phase = FILTER_EHLO; +} MATCH filter_phase_helo_options filter_action_builtin +; + +filter_phase_auth: +AUTH { +} MATCH filter_phase_auth_options filter_action_builtin +; + +filter_phase_mail_from: +MAIL_FROM { + filter_config->phase = FILTER_MAIL_FROM; +} MATCH filter_phase_mail_from_options filter_action_builtin +; + +filter_phase_rcpt_to: +RCPT_TO { + filter_config->phase = FILTER_RCPT_TO; +} MATCH filter_phase_rcpt_to_options filter_action_builtin +; + +filter_phase_data: +DATA { + filter_config->phase = FILTER_DATA; +} MATCH filter_phase_data_options filter_action_builtin +; + +/* +filter_phase_data_line: +DATA_LINE { + filter_config->phase = FILTER_DATA_LINE; +} MATCH filter_action_builtin +; + +filter_phase_quit: +QUIT { + filter_config->phase = FILTER_QUIT; +} filter_phase_quit_options filter_action_builtin +; + +filter_phase_rset: +RSET { + filter_config->phase = FILTER_RSET; +} MATCH filter_phase_rset_options filter_action_builtin +; + +filter_phase_noop: +NOOP { + filter_config->phase = FILTER_NOOP; +} MATCH filter_phase_noop_options filter_action_builtin +; +*/ + +filter_phase_commit: +COMMIT { + filter_config->phase = FILTER_COMMIT; +} MATCH filter_phase_commit_options filter_action_builtin_nojunk +; + + + +filter_phase: +filter_phase_connect +| filter_phase_helo +| filter_phase_ehlo +| filter_phase_auth +| filter_phase_mail_from +| filter_phase_rcpt_to +| filter_phase_data +/*| filter_phase_data_line*/ +/*| filter_phase_quit*/ +/*| filter_phase_noop*/ +/*| filter_phase_rset*/ +| filter_phase_commit +; + + +filterel: +STRING { + struct filter_config *fr; + struct filter_proc *fp; + size_t i; + + if ((fr = dict_get(conf->sc_filters_dict, $1)) == NULL) { + yyerror("no filter exist with that name: %s", $1); + free($1); + YYERROR; + } + if (fr->filter_type == FILTER_TYPE_CHAIN) { + yyerror("no filter chain allowed within a filter chain: %s", $1); + free($1); + YYERROR; + } + + for (i = 0; i < filter_config->chain_size; i++) { + if (strcmp(filter_config->chain[i], $1) == 0) { + yyerror("no filter allowed twice within a filter chain: %s", $1); + free($1); + YYERROR; + } + } + + if (fr->proc) { + if ((fp = dict_get(&filter_config->chain_procs, fr->proc))) { + yyerror("no proc allowed twice within a filter chain: %s", fr->proc); + free($1); + YYERROR; + } + dict_set(&filter_config->chain_procs, fr->proc, NULL); + } + + fr->filter_subsystem |= filter_config->filter_subsystem; + filter_config->chain_size += 1; + filter_config->chain = reallocarray(filter_config->chain, filter_config->chain_size, sizeof(char *)); + if (filter_config->chain == NULL) + err(1, NULL); + filter_config->chain[filter_config->chain_size - 1] = $1; +} +; + +filter_list: +filterel +| filterel comma filter_list +; + +filter: +FILTER STRING PROC STRING { + struct filter_proc *fp; + + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + free($4); + YYERROR; + } + if ((fp = dict_get(conf->sc_filter_processes_dict, $4)) == NULL) { + yyerror("no processor exist with that name: %s", $4); + free($4); + YYERROR; + } + + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_PROC; + filter_config->name = $2; + filter_config->proc = $4; + dict_set(conf->sc_filters_dict, $2, filter_config); + filter_config = NULL; +} +| +FILTER STRING PROC_EXEC STRING { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + free($4); + YYERROR; + } + + processor = xcalloc(1, sizeof *processor); + processor->command = $4; + + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_PROC; + filter_config->name = $2; + filter_config->proc = xstrdup($2); + dict_set(conf->sc_filters_dict, $2, filter_config); +} proc_params { + dict_set(conf->sc_filter_processes_dict, filter_config->proc, processor); + processor = NULL; + filter_config = NULL; +} +| +FILTER STRING PHASE { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + YYERROR; + } + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->name = $2; + filter_config->filter_type = FILTER_TYPE_BUILTIN; + dict_set(conf->sc_filters_dict, $2, filter_config); +} filter_phase { + filter_config = NULL; +} +| +FILTER STRING CHAIN { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + YYERROR; + } + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + dict_init(&filter_config->chain_procs); +} '{' filter_list '}' { + dict_set(conf->sc_filters_dict, $2, filter_config); + filter_config = NULL; +} +; + +size : NUMBER { + if ($1 < 0) { + yyerror("invalid size: %" PRId64, $1); + YYERROR; + } + $$ = $1; + } + | STRING { + long long result; + + if (scan_scaled($1, &result) == -1 || result < 0) { + yyerror("invalid size: %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = result; + } + ; + +bouncedelay : STRING { + time_t d; + int i; + + d = delaytonum($1); + if (d < 0) { + yyerror("invalid bounce delay: %s", $1); + free($1); + YYERROR; + } + free($1); + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (conf->sc_bounce_warn[i] != 0) + continue; + conf->sc_bounce_warn[i] = d; + break; + } + } + ; + +bouncedelays : bouncedelays ',' bouncedelay + | bouncedelay + ; + +opt_limit_mda : STRING NUMBER { + if (!strcmp($1, "max-session")) { + conf->sc_mda_max_session = $2; + } + else if (!strcmp($1, "max-session-per-user")) { + conf->sc_mda_max_user_session = $2; + } + else if (!strcmp($1, "task-lowat")) { + conf->sc_mda_task_lowat = $2; + } + else if (!strcmp($1, "task-hiwat")) { + conf->sc_mda_task_hiwat = $2; + } + else if (!strcmp($1, "task-release")) { + conf->sc_mda_task_release = $2; + } + else { + yyerror("invalid scheduler limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_smtp : opt_limit_smtp limits_smtp + | /* empty */ + ; + +opt_limit_smtp : STRING NUMBER { + if (!strcmp($1, "max-rcpt")) { + conf->sc_session_max_rcpt = $2; + } + else if (!strcmp($1, "max-mails")) { + conf->sc_session_max_mails = $2; + } + else { + yyerror("invalid session limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_mda : opt_limit_mda limits_mda + | /* empty */ + ; + +opt_limit_mta : INET4 { + limits->family = AF_INET; + } + | INET6 { + limits->family = AF_INET6; + } + | STRING NUMBER { + if (!limit_mta_set(limits, $1, $2)) { + yyerror("invalid mta limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_mta : opt_limit_mta limits_mta + | /* empty */ + ; + +opt_limit_scheduler : STRING NUMBER { + if (!strcmp($1, "max-inflight")) { + conf->sc_scheduler_max_inflight = $2; + } + else if (!strcmp($1, "max-evp-batch-size")) { + conf->sc_scheduler_max_evp_batch_size = $2; + } + else if (!strcmp($1, "max-msg-batch-size")) { + conf->sc_scheduler_max_msg_batch_size = $2; + } + else if (!strcmp($1, "max-schedule")) { + conf->sc_scheduler_max_schedule = $2; + } + else { + yyerror("invalid scheduler limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_scheduler: opt_limit_scheduler limits_scheduler + | /* empty */ + ; + + +opt_sock_listen : FILTER STRING { + struct filter_config *fc; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + free($2); + YYERROR; + } + if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } + fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + listen_opts.options |= LO_FILTER; + listen_opts.filtername = $2; + } + | FILTER { + char buffer[128]; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + YYERROR; + } + + do { + (void)snprintf(buffer, sizeof buffer, "", last_dynchain_id++); + } while (dict_check(conf->sc_filters_dict, buffer)); + + listen_opts.options |= LO_FILTER; + listen_opts.filtername = xstrdup(buffer); + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + dict_init(&filter_config->chain_procs); + } '{' filter_list '}' { + dict_set(conf->sc_filters_dict, listen_opts.filtername, filter_config); + filter_config = NULL; + } + | MASK_SRC { + if (config_lo_mask_source(&listen_opts)) { + YYERROR; + } + } + | TAG STRING { + if (listen_opts.options & LO_TAG) { + yyerror("tag already specified"); + YYERROR; + } + listen_opts.options |= LO_TAG; + + if (strlen($2) >= SMTPD_TAG_SIZE) { + yyerror("tag name too long"); + free($2); + YYERROR; + } + listen_opts.tag = $2; + } + ; + +opt_if_listen : INET4 { + if (listen_opts.options & LO_FAMILY) { + yyerror("address family already specified"); + YYERROR; + } + listen_opts.options |= LO_FAMILY; + listen_opts.family = AF_INET; + } + | INET6 { + if (listen_opts.options & LO_FAMILY) { + yyerror("address family already specified"); + YYERROR; + } + listen_opts.options |= LO_FAMILY; + listen_opts.family = AF_INET6; + } + | PORT STRING { + struct servent *servent; + + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + servent = getservbyname($2, "tcp"); + if (servent == NULL) { + yyerror("invalid port: %s", $2); + free($2); + YYERROR; + } + free($2); + listen_opts.port = ntohs(servent->s_port); + } + | PORT SMTP { + struct servent *servent; + + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + servent = getservbyname("smtp", "tcp"); + if (servent == NULL) { + yyerror("invalid port: smtp"); + YYERROR; + } + listen_opts.port = ntohs(servent->s_port); + } + | PORT SMTPS { + struct servent *servent; + + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + servent = getservbyname("smtps", "tcp"); + if (servent == NULL) { + yyerror("invalid port: smtps"); + YYERROR; + } + listen_opts.port = ntohs(servent->s_port); + } + | PORT NUMBER { + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + if ($2 <= 0 || $2 > (int)USHRT_MAX) { + yyerror("invalid port: %" PRId64, $2); + YYERROR; + } + listen_opts.port = $2; + } + | FILTER STRING { + struct filter_config *fc; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + YYERROR; + } + if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } + fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + listen_opts.options |= LO_FILTER; + listen_opts.filtername = $2; + } + | FILTER { + char buffer[128]; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + YYERROR; + } + + do { + (void)snprintf(buffer, sizeof buffer, "", last_dynchain_id++); + } while (dict_check(conf->sc_filters_dict, buffer)); + + listen_opts.options |= LO_FILTER; + listen_opts.filtername = xstrdup(buffer); + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + dict_init(&filter_config->chain_procs); + } '{' filter_list '}' { + dict_set(conf->sc_filters_dict, listen_opts.filtername, filter_config); + filter_config = NULL; + } + | SMTPS { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_SMTPS; + } + | SMTPS VERIFY { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_SMTPS|F_TLS_VERIFY; + } + | TLS { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_STARTTLS; + } + | TLS_REQUIRE { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE; + } + | TLS_REQUIRE VERIFY { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE|F_TLS_VERIFY; + } + | PKI STRING { + if (listen_opts.options & LO_PKI) { + yyerror("pki already specified"); + YYERROR; + } + listen_opts.options |= LO_PKI; + listen_opts.pki = $2; + } + | CA STRING { + if (listen_opts.options & LO_CA) { + yyerror("ca already specified"); + YYERROR; + } + listen_opts.options |= LO_CA; + listen_opts.ca = $2; + } + | AUTH { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; + } + | AUTH_OPTIONAL { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.auth = F_AUTH; + } + | AUTH tables { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.authtable = $2; + listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; + } + | AUTH_OPTIONAL tables { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.authtable = $2; + listen_opts.auth = F_AUTH; + } + | TAG STRING { + if (listen_opts.options & LO_TAG) { + yyerror("tag already specified"); + YYERROR; + } + listen_opts.options |= LO_TAG; + + if (strlen($2) >= SMTPD_TAG_SIZE) { + yyerror("tag name too long"); + free($2); + YYERROR; + } + listen_opts.tag = $2; + } + | HOSTNAME STRING { + if (listen_opts.options & LO_HOSTNAME) { + yyerror("hostname already specified"); + YYERROR; + } + listen_opts.options |= LO_HOSTNAME; + + listen_opts.hostname = $2; + } + | HOSTNAMES tables { + struct table *t = $2; + + if (listen_opts.options & LO_HOSTNAMES) { + yyerror("hostnames already specified"); + YYERROR; + } + listen_opts.options |= LO_HOSTNAMES; + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { + yyerror("invalid use of table \"%s\" as " + "HOSTNAMES parameter", t->t_name); + YYERROR; + } + listen_opts.hostnametable = t; + } + | MASK_SRC { + if (config_lo_mask_source(&listen_opts)) { + YYERROR; + } + } + | RECEIVEDAUTH { + if (listen_opts.options & LO_RECEIVEDAUTH) { + yyerror("received-auth already specified"); + YYERROR; + } + listen_opts.options |= LO_RECEIVEDAUTH; + listen_opts.flags |= F_RECEIVEDAUTH; + } + | NO_DSN { + if (listen_opts.options & LO_NODSN) { + yyerror("no-dsn already specified"); + YYERROR; + } + listen_opts.options |= LO_NODSN; + listen_opts.flags &= ~F_EXT_DSN; + } + | PROXY_V2 { + if (listen_opts.options & LO_PROXY) { + yyerror("proxy-v2 already specified"); + YYERROR; + } + listen_opts.options |= LO_PROXY; + listen_opts.flags |= F_PROXY; + } + | SENDERS tables { + struct table *t = $2; + + if (listen_opts.options & LO_SENDERS) { + yyerror("senders already specified"); + YYERROR; + } + listen_opts.options |= LO_SENDERS; + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_MAILADDRMAP)) { + yyerror("invalid use of table \"%s\" as " + "SENDERS parameter", t->t_name); + YYERROR; + } + listen_opts.sendertable = t; + } + | SENDERS tables MASQUERADE { + struct table *t = $2; + + if (listen_opts.options & LO_SENDERS) { + yyerror("senders already specified"); + YYERROR; + } + listen_opts.options |= LO_SENDERS|LO_MASQUERADE; + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_MAILADDRMAP)) { + yyerror("invalid use of table \"%s\" as " + "SENDERS parameter", t->t_name); + YYERROR; + } + listen_opts.sendertable = t; + } + ; + +listener_type : socket_listener + | if_listener + ; + +socket_listener : SOCKET sock_listen { + if (conf->sc_sock_listener) { + yyerror("socket listener already configured"); + YYERROR; + } + create_sock_listener(&listen_opts); + } + ; + +if_listener : STRING if_listen { + listen_opts.ifx = $1; + create_if_listener(&listen_opts); + } + ; + +sock_listen : opt_sock_listen sock_listen + | /* empty */ + ; + +if_listen : opt_if_listen if_listen + | /* empty */ + ; + + +listen : LISTEN { + memset(&listen_opts, 0, sizeof listen_opts); + listen_opts.family = AF_UNSPEC; + listen_opts.flags |= F_EXT_DSN; + } ON listener_type + ; + +table : TABLE STRING STRING { + char *p, *backend, *config; + + p = $3; + if (*p == '/') { + backend = "static"; + config = $3; + } + else { + backend = $3; + config = NULL; + for (p = $3; *p && *p != ':'; p++) + ; + if (*p == ':') { + *p = '\0'; + backend = $3; + config = p+1; + } + } + if (config != NULL && *config != '/') { + yyerror("invalid backend parameter for table: %s", + $2); + free($2); + free($3); + YYERROR; + } + table = table_create(conf, backend, $2, config); + if (!table_config(table)) { + yyerror("invalid configuration file %s for table %s", + config, table->t_name); + free($2); + free($3); + YYERROR; + } + table = NULL; + free($2); + free($3); + } + | TABLE STRING { + table = table_create(conf, "static", $2, NULL); + free($2); + } '{' tableval_list '}' { + table = NULL; + } + ; + +tablenew : STRING { + struct table *t; + + t = table_create(conf, "static", NULL, NULL); + table_add(t, $1, NULL); + free($1); + $$ = t; + } + | '{' { + table = table_create(conf, "static", NULL, NULL); + } tableval_list '}' { + $$ = table; + table = NULL; + } + ; + +tableref : '<' STRING '>' { + struct table *t; + + if ((t = table_find(conf, $2)) == NULL) { + yyerror("no such table: %s", $2); + free($2); + YYERROR; + } + free($2); + $$ = t; + } + ; + +tables : tablenew { $$ = $1; } + | tableref { $$ = $1; } + ; + + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "action", ACTION }, + { "alias", ALIAS }, + { "any", ANY }, + { "auth", AUTH }, + { "auth-optional", AUTH_OPTIONAL }, + { "backup", BACKUP }, + { "bounce", BOUNCE }, + { "bypass", BYPASS }, + { "ca", CA }, + { "cert", CERT }, + { "chain", CHAIN }, + { "chroot", CHROOT }, + { "ciphers", CIPHERS }, + { "commit", COMMIT }, + { "compression", COMPRESSION }, + { "connect", CONNECT }, + { "data", DATA }, + { "data-line", DATA_LINE }, + { "dhe", DHE }, + { "disconnect", DISCONNECT }, + { "domain", DOMAIN }, + { "ehlo", EHLO }, + { "encryption", ENCRYPTION }, + { "expand-only", EXPAND_ONLY }, + { "fcrdns", FCRDNS }, + { "filter", FILTER }, + { "for", FOR }, + { "forward-only", FORWARD_ONLY }, + { "from", FROM }, + { "group", GROUP }, + { "helo", HELO }, + { "helo-src", HELO_SRC }, + { "host", HOST }, + { "hostname", HOSTNAME }, + { "hostnames", HOSTNAMES }, + { "include", INCLUDE }, + { "inet4", INET4 }, + { "inet6", INET6 }, + { "junk", JUNK }, + { "key", KEY }, + { "limit", LIMIT }, + { "listen", LISTEN }, + { "lmtp", LMTP }, + { "local", LOCAL }, + { "mail-from", MAIL_FROM }, + { "maildir", MAILDIR }, + { "mask-src", MASK_SRC }, + { "masquerade", MASQUERADE }, + { "match", MATCH }, + { "max-deferred", MAX_DEFERRED }, + { "max-message-size", MAX_MESSAGE_SIZE }, + { "mbox", MBOX }, + { "mda", MDA }, + { "mta", MTA }, + { "mx", MX }, + { "no-dsn", NO_DSN }, + { "no-verify", NO_VERIFY }, + { "noop", NOOP }, + { "on", ON }, + { "phase", PHASE }, + { "pki", PKI }, + { "port", PORT }, + { "proc", PROC }, + { "proc-exec", PROC_EXEC }, + { "proxy-v2", PROXY_V2 }, + { "queue", QUEUE }, + { "quit", QUIT }, + { "rcpt-to", RCPT_TO }, + { "rdns", RDNS }, + { "received-auth", RECEIVEDAUTH }, + { "recipient", RECIPIENT }, + { "regex", REGEX }, + { "reject", REJECT }, + { "relay", RELAY }, + { "report", REPORT }, + { "rewrite", REWRITE }, + { "rset", RSET }, + { "scheduler", SCHEDULER }, + { "senders", SENDERS }, + { "smtp", SMTP }, + { "smtp-in", SMTP_IN }, + { "smtp-out", SMTP_OUT }, + { "smtps", SMTPS }, + { "socket", SOCKET }, + { "src", SRC }, + { "srs", SRS }, + { "sub-addr-delim", SUB_ADDR_DELIM }, + { "table", TABLE }, + { "tag", TAG }, + { "tagged", TAGGED }, + { "tls", TLS }, + { "tls-require", TLS_REQUIRE }, + { "ttl", TTL }, + { "user", USER }, + { "userbase", USERBASE }, + { "verify", VERIFY }, + { "virtual", VIRTUAL }, + { "warn-interval", WARN_INTERVAL }, + { "wrapper", WRAPPER }, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "%s", __func__); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "%s", __func__); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + + if (c == '=') { + if ((c = lgetc(0)) != EOF && c == '>') + return (ARROW); + lungetc(c); + c = '='; + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "%s", __func__); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("warn: cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("warn: %s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("warn: %s: group/world readable/writeable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("%s", __func__); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("%s", __func__); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s: %s", __func__, nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("%s", __func__); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(struct smtpd *x_conf, const char *filename, int opts) +{ + struct sym *sym, *next; + + conf = x_conf; + errors = 0; + + if ((file = pushfile(filename, 0)) == NULL) { + purge_config(PURGE_EVERYTHING); + return (-1); + } + topfile = file; + + /* + * parse configuration + */ + setservent(1); + yyparse(); + errors = file->errors; + popfile(); + endservent(); + + /* If the socket listener was not configured, create a default one. */ + if (!conf->sc_sock_listener) { + memset(&listen_opts, 0, sizeof listen_opts); + create_sock_listener(&listen_opts); + } + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if ((conf->sc_opts & SMTPD_OPT_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (TAILQ_EMPTY(conf->sc_rules)) { + log_warnx("warn: no rules, nothing to do"); + errors++; + } + + if (errors) { + purge_config(PURGE_EVERYTHING); + return (-1); + } + + return (0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + sym = strndup(s, val - s); + if (sym == NULL) + errx(1, "%s: strndup", __func__); + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +static void +create_sock_listener(struct listen_opts *lo) +{ + struct listener *l = xcalloc(1, sizeof(*l)); + lo->hostname = conf->sc_hostname; + l->ss.ss_family = AF_LOCAL; +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + l->ss.ss_len = sizeof(struct sockaddr *); +#endif + l->local = 1; + conf->sc_sock_listener = l; + config_listener(l, lo); +} + +static void +create_if_listener(struct listen_opts *lo) +{ + uint16_t flags; + + if (lo->port != 0 && lo->ssl == F_SSL) + errx(1, "invalid listen option: tls/smtps on same port"); + + if (lo->auth != 0 && !lo->ssl) + errx(1, "invalid listen option: auth requires tls/smtps"); + + if (lo->pki && !lo->ssl) + errx(1, "invalid listen option: pki requires tls/smtps"); + + flags = lo->flags; + + if (lo->port) { + lo->flags = lo->ssl|lo->auth|flags; + lo->port = htons(lo->port); + } + else { + if (lo->ssl & F_SMTPS) { + lo->port = htons(465); + lo->flags = F_SMTPS|lo->auth|flags; + } + + if (!lo->ssl || (lo->ssl & F_STARTTLS)) { + lo->port = htons(25); + lo->flags = lo->auth|flags; + if (lo->ssl & F_STARTTLS) + lo->flags |= F_STARTTLS; + } + } + + if (interface(lo)) + return; + if (host_v4(lo)) + return; + if (host_v6(lo)) + return; + if (host_dns(lo)) + return; + + errx(1, "invalid virtual ip or interface: %s", lo->ifx); +} + +static void +config_listener(struct listener *h, struct listen_opts *lo) +{ + h->fd = -1; + h->port = lo->port; + h->flags = lo->flags; + + if (lo->hostname == NULL) + lo->hostname = conf->sc_hostname; + + if (lo->options & LO_FILTER) { + h->flags |= F_FILTERED; + (void)strlcpy(h->filter_name, + lo->filtername, + sizeof(h->filter_name)); + } + + h->pki_name[0] = '\0'; + + if (lo->authtable != NULL) + (void)strlcpy(h->authtable, lo->authtable->t_name, sizeof(h->authtable)); + if (lo->pki != NULL) { + if (!lowercase(h->pki_name, lo->pki, sizeof(h->pki_name))) { + log_warnx("pki name too long: %s", lo->pki); + fatalx(NULL); + } + if (dict_get(conf->sc_pki_dict, h->pki_name) == NULL) { + log_warnx("pki name not found: %s", lo->pki); + fatalx(NULL); + } + } + + if (lo->ca != NULL) { + if (!lowercase(h->ca_name, lo->ca, sizeof(h->ca_name))) { + log_warnx("ca name too long: %s", lo->ca); + fatalx(NULL); + } + if (dict_get(conf->sc_ca_dict, h->ca_name) == NULL) { + log_warnx("ca name not found: %s", lo->ca); + fatalx(NULL); + } + } + if (lo->tag != NULL) + (void)strlcpy(h->tag, lo->tag, sizeof(h->tag)); + + (void)strlcpy(h->hostname, lo->hostname, sizeof(h->hostname)); + if (lo->hostnametable) + (void)strlcpy(h->hostnametable, lo->hostnametable->t_name, sizeof(h->hostnametable)); + if (lo->sendertable) { + (void)strlcpy(h->sendertable, lo->sendertable->t_name, sizeof(h->sendertable)); + if (lo->options & LO_MASQUERADE) + h->flags |= F_MASQUERADE; + } + + if (lo->ssl & F_TLS_VERIFY) + h->flags |= F_TLS_VERIFY; + + if (lo->ssl & F_STARTTLS_REQUIRE) + h->flags |= F_STARTTLS_REQUIRE; + + if (h != conf->sc_sock_listener) + TAILQ_INSERT_TAIL(conf->sc_listeners, h, entry); +} + +static int +host_v4(struct listen_opts *lo) +{ + struct in_addr ina; + struct sockaddr_in *sain; + struct listener *h; + + if (lo->family != AF_UNSPEC && lo->family != AF_INET) + return (0); + + memset(&ina, 0, sizeof(ina)); + if (inet_pton(AF_INET, lo->ifx, &ina) != 1) + return (0); + + h = xcalloc(1, sizeof(*h)); + sain = (struct sockaddr_in *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_family = AF_INET; + sain->sin_addr.s_addr = ina.s_addr; + sain->sin_port = lo->port; + + if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + h->local = 1; + config_listener(h, lo); + + return (1); +} + +static int +host_v6(struct listen_opts *lo) +{ + struct in6_addr ina6; + struct sockaddr_in6 *sin6; + struct listener *h; + + if (lo->family != AF_UNSPEC && lo->family != AF_INET6) + return (0); + + memset(&ina6, 0, sizeof(ina6)); + if (inet_pton(AF_INET6, lo->ifx, &ina6) != 1) + return (0); + + h = xcalloc(1, sizeof(*h)); + sin6 = (struct sockaddr_in6 *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = AF_INET6; + sin6->sin6_port = lo->port; + memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6)); + + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + h->local = 1; + config_listener(h, lo); + + return (1); +} + +static int +host_dns(struct listen_opts *lo) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct listener *h; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = lo->family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + error = getaddrinfo(lo->ifx, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("warn: host_dns: could not parse \"%s\": %s", lo->ifx, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + h = xcalloc(1, sizeof(*h)); + + h->ss.ss_family = res->ai_family; + if (res->ai_family == AF_INET) { + sain = (struct sockaddr_in *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_addr.s_addr = ((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr; + sain->sin_port = lo->port; + if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + h->local = 1; + } else { + sin6 = (struct sockaddr_in6 *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + sin6->sin6_port = lo->port; + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + h->local = 1; + } + + config_listener(h, lo); + + cnt++; + } + + freeaddrinfo(res0); + return (cnt); +} + +static int +interface(struct listen_opts *lo) +{ + struct ifaddrs *ifap, *p; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct listener *h; + int ret = 0; + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + for (p = ifap; p != NULL; p = p->ifa_next) { + if (p->ifa_addr == NULL) + continue; + if (strcmp(p->ifa_name, lo->ifx) != 0 && + !is_if_in_group(p->ifa_name, lo->ifx)) + continue; + if (lo->family != AF_UNSPEC && lo->family != p->ifa_addr->sa_family) + continue; + + h = xcalloc(1, sizeof(*h)); + + switch (p->ifa_addr->sa_family) { + case AF_INET: + sain = (struct sockaddr_in *)&h->ss; + *sain = *(struct sockaddr_in *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_port = lo->port; + if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + h->local = 1; + break; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&h->ss; + *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_port = lo->port; + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + h->local = 1; + break; + + default: + free(h); + continue; + } + + config_listener(h, lo); + ret = 1; + } + + freeifaddrs(ifap); + + return ret; +} + +int +delaytonum(char *str) +{ + unsigned int factor; + size_t len; + const char *errstr = NULL; + int delay; + + /* we need at least 1 digit and 1 unit */ + len = strlen(str); + if (len < 2) + goto bad; + + switch(str[len - 1]) { + + case 's': + factor = 1; + break; + + case 'm': + factor = 60; + break; + + case 'h': + factor = 60 * 60; + break; + + case 'd': + factor = 24 * 60 * 60; + break; + + default: + goto bad; + } + + str[len - 1] = '\0'; + delay = strtonum(str, 1, INT_MAX / factor, &errstr); + if (errstr) + goto bad; + + return (delay * factor); + +bad: + return (-1); +} + +int +is_if_in_group(const char *ifname, const char *groupname) +{ +#ifdef HAVE_STRUCT_IFGROUPREQ + unsigned int len; + struct ifgroupreq ifgr; + struct ifg_req *ifg; + int s; + int ret = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + err(1, "socket"); + + memset(&ifgr, 0, sizeof(ifgr)); + if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ) + errx(1, "interface name too large"); + + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) { + if (errno == EINVAL || errno == ENOTTY) + goto end; + err(1, "SIOCGIFGROUP"); + } + + len = ifgr.ifgr_len; + ifgr.ifgr_groups = xcalloc(len/sizeof(struct ifg_req), + sizeof(struct ifg_req)); + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGROUP"); + + ifg = ifgr.ifgr_groups; + for (; ifg && len >= sizeof(struct ifg_req); ifg++) { + len -= sizeof(struct ifg_req); + if (strcmp(ifg->ifgrq_group, groupname) == 0) { + ret = 1; + break; + } + } + free(ifgr.ifgr_groups); + +end: + close(s); + return ret; +#else + return (0); +#endif +} + +static int +config_lo_mask_source(struct listen_opts *lo) { + if (lo->options & LO_MASKSOURCE) { + yyerror("mask-source already specified"); + return -1; + } + lo->options |= LO_MASKSOURCE; + lo->flags |= F_MASK_SOURCE; + + return 0; +} + diff --git a/usr.sbin/smtpd/parser.c b/usr.sbin/smtpd/parser.c new file mode 100644 index 00000000..4c2321ec --- /dev/null +++ b/usr.sbin/smtpd/parser.c @@ -0,0 +1,341 @@ +/* $OpenBSD: parser.c,v 1.42 2020/01/06 11:02:38 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "parser.h" + +uint64_t text_to_evpid(const char *); +uint32_t text_to_msgid(const char *); + +struct node { + int type; + const char *token; + struct node *parent; + TAILQ_ENTRY(node) entry; + TAILQ_HEAD(, node) children; + int (*cmd)(int, struct parameter*); +}; + +static struct node *root; + +static int text_to_sockaddr(struct sockaddr *, int, const char *); + +#define ARGVMAX 64 + +int +cmd_install(const char *pattern, int (*cmd)(int, struct parameter*)) +{ + struct node *node, *tmp; + char *s, *str, *argv[ARGVMAX], **ap; + int i, n; + + /* Tokenize */ + str = s = strdup(pattern); + if (str == NULL) + err(1, "strdup"); + n = 0; + for (ap = argv; n < ARGVMAX && (*ap = strsep(&str, " \t")) != NULL;) { + if (**ap != '\0') { + ap++; + n++; + } + } + *ap = NULL; + + if (root == NULL) { + root = calloc(1, sizeof (*root)); + TAILQ_INIT(&root->children); + } + node = root; + + for (i = 0; i < n; i++) { + TAILQ_FOREACH(tmp, &node->children, entry) { + if (!strcmp(tmp->token, argv[i])) { + node = tmp; + break; + } + } + if (tmp == NULL) { + tmp = calloc(1, sizeof (*tmp)); + TAILQ_INIT(&tmp->children); + if (!strcmp(argv[i], "")) + tmp->type = P_STR; + else if (!strcmp(argv[i], "")) + tmp->type = P_INT; + else if (!strcmp(argv[i], "")) + tmp->type = P_MSGID; + else if (!strcmp(argv[i], "")) + tmp->type = P_EVPID; + else if (!strcmp(argv[i], "")) + tmp->type = P_ROUTEID; + else if (!strcmp(argv[i], "")) + tmp->type = P_ADDR; + else + tmp->type = P_TOKEN; + tmp->token = strdup(argv[i]); + tmp->parent = node; + TAILQ_INSERT_TAIL(&node->children, tmp, entry); + node = tmp; + } + } + + if (node->cmd) + errx(1, "duplicate pattern: %s", pattern); + node->cmd = cmd; + + free(s); + return (n); +} + +static int +cmd_check(const char *str, struct node *node, struct parameter *res) +{ + const char *e; + + switch (node->type) { + case P_TOKEN: + if (!strcmp(str, node->token)) + return (1); + return (0); + + case P_STR: + res->u.u_str = str; + return (1); + + case P_INT: + res->u.u_int = strtonum(str, INT_MIN, INT_MAX, &e); + if (e) + return (0); + return (1); + + case P_MSGID: + if (strlen(str) != 8) + return (0); + res->u.u_msgid = text_to_msgid(str); + if (res->u.u_msgid == 0) + return (0); + return (1); + + case P_EVPID: + if (strlen(str) != 16) + return (0); + res->u.u_evpid = text_to_evpid(str); + if (res->u.u_evpid == 0) + return (0); + return (1); + + case P_ROUTEID: + res->u.u_routeid = strtonum(str, 1, LLONG_MAX, &e); + if (e) + return (0); + return (1); + + case P_ADDR: + if (text_to_sockaddr((struct sockaddr *)&res->u.u_ss, PF_UNSPEC, str) == 0) + return (1); + return (0); + + default: + errx(1, "bad token type: %d", node->type); + return (0); + } +} + +int +cmd_run(int argc, char **argv) +{ + struct parameter param[ARGVMAX]; + struct node *node, *tmp, *stack[ARGVMAX], *best; + int i, j, np; + + node = root; + np = 0; + + for (i = 0; i < argc; i++) { + TAILQ_FOREACH(tmp, &node->children, entry) { + if (cmd_check(argv[i], tmp, ¶m[np])) { + stack[i] = tmp; + node = tmp; + param[np].type = node->type; + if (node->type != P_TOKEN) + np++; + break; + } + } + if (tmp == NULL) { + best = NULL; + TAILQ_FOREACH(tmp, &node->children, entry) { + if (tmp->type != P_TOKEN) + continue; + if (strstr(tmp->token, argv[i]) != tmp->token) + continue; + if (best) + goto fail; + best = tmp; + } + if (best == NULL) + goto fail; + stack[i] = best; + node = best; + param[np].type = node->type; + if (node->type != P_TOKEN) + np++; + } + } + + if (node->cmd == NULL) + goto fail; + + return (node->cmd(np, np ? param : NULL)); + +fail: + if (TAILQ_FIRST(&node->children) == NULL) { + fprintf(stderr, "invalid command\n"); + return (-1); + } + + fprintf(stderr, "possibilities are:\n"); + TAILQ_FOREACH(tmp, &node->children, entry) { + for (j = 0; j < i; j++) + fprintf(stderr, "%s%s", j?" ":"", stack[j]->token); + fprintf(stderr, "%s%s\n", i?" ":"", tmp->token); + } + + return (-1); +} + +int +cmd_show_params(int argc, struct parameter *argv) +{ + int i; + + for (i = 0; i < argc; i++) { + switch(argv[i].type) { + case P_STR: + printf(" str:\"%s\"", argv[i].u.u_str); + break; + case P_INT: + printf(" int:%d", argv[i].u.u_int); + break; + case P_MSGID: + printf(" msgid:%08"PRIx32, argv[i].u.u_msgid); + break; + case P_EVPID: + printf(" evpid:%016"PRIx64, argv[i].u.u_evpid); + break; + case P_ROUTEID: + printf(" routeid:%016"PRIx64, argv[i].u.u_routeid); + break; + default: + printf(" ???:%d", argv[i].type); + } + } + printf ("\n"); + return (1); +} + +static int +text_to_sockaddr(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *in; + struct sockaddr_in6 *in6; + char *cp, *str2; + const char *errstr; + + switch (family) { + case PF_UNSPEC: + if (text_to_sockaddr(sa, PF_INET, str) == 0) + return (0); + return text_to_sockaddr(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + in = (struct sockaddr_in *)sa; + memset(in, 0, sizeof *in); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + in->sin_len = sizeof(struct sockaddr_in); +#endif + in->sin_family = PF_INET; + in->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + cp = strchr(str, SCOPE_DELIMITER); + if (cp) { + str2 = strdup(str); + if (str2 == NULL) + return (-1); + str2[cp - str] = '\0'; + if (inet_pton(PF_INET6, str2, &in6a) != 1) { + free(str2); + return (-1); + } + cp++; + free(str2); + } else if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + in6 = (struct sockaddr_in6 *)sa; + memset(in6, 0, sizeof *in6); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + in6->sin6_len = sizeof(struct sockaddr_in6); +#endif + in6->sin6_family = PF_INET6; + in6->sin6_addr = in6a; + + if (cp == NULL) + return (0); + + if (IN6_IS_ADDR_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_NODELOCAL(&in6a)) + if ((in6->sin6_scope_id = if_nametoindex(cp))) + return (0); + + in6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + return (0); + + default: + break; + } + + return (-1); +} diff --git a/usr.sbin/smtpd/parser.h b/usr.sbin/smtpd/parser.h new file mode 100644 index 00000000..f0114e9e --- /dev/null +++ b/usr.sbin/smtpd/parser.h @@ -0,0 +1,43 @@ +/* $OpenBSD: parser.h,v 1.29 2014/02/04 15:22:39 eric Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum { + P_TOKEN, + P_STR, + P_INT, + P_MSGID, + P_EVPID, + P_ROUTEID, + P_ADDR, +}; + +struct parameter { + int type; + union { + const char *u_str; + int u_int; + uint32_t u_msgid; + uint64_t u_evpid; + uint64_t u_routeid; + struct sockaddr_storage u_ss; + } u; +}; + +int cmd_install(const char *, int (*)(int, struct parameter *)); +int cmd_run(int, char **); +int cmd_show_params(int argc, struct parameter *argv); diff --git a/usr.sbin/smtpd/pony.c b/usr.sbin/smtpd/pony.c new file mode 100644 index 00000000..1865b339 --- /dev/null +++ b/usr.sbin/smtpd/pony.c @@ -0,0 +1,212 @@ +/* $OpenBSD: pony.c,v 1.27 2019/06/13 11:45:35 eric Exp $ */ + +/* + * Copyright (c) 2014 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +void mda_imsg(struct mproc *, struct imsg *); +void mta_imsg(struct mproc *, struct imsg *); +void smtp_imsg(struct mproc *, struct imsg *); + +static void pony_shutdown(void); + +void +pony_imsg(struct mproc *p, struct imsg *imsg) +{ + struct msg m; + int v; + + if (imsg == NULL) + pony_shutdown(); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + case IMSG_GETADDRINFO_END: + case IMSG_GETNAMEINFO: + case IMSG_RES_QUERY: + resolver_dispatch_result(p, imsg); + return; + + case IMSG_CERT_INIT: + case IMSG_CERT_VERIFY: + cert_dispatch_result(p, imsg); + return; + + case IMSG_CONF_START: + return; + case IMSG_CONF_END: + smtp_configure(); + return; + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + /* smtp imsg */ + case IMSG_SMTP_CHECK_SENDER: + case IMSG_SMTP_EXPAND_RCPT: + case IMSG_SMTP_LOOKUP_HELO: + case IMSG_SMTP_AUTHENTICATE: + case IMSG_SMTP_MESSAGE_COMMIT: + case IMSG_SMTP_MESSAGE_CREATE: + case IMSG_SMTP_MESSAGE_OPEN: + case IMSG_FILTER_SMTP_PROTOCOL: + case IMSG_FILTER_SMTP_DATA_BEGIN: + case IMSG_QUEUE_ENVELOPE_SUBMIT: + case IMSG_QUEUE_ENVELOPE_COMMIT: + case IMSG_QUEUE_SMTP_SESSION: + case IMSG_CTL_SMTP_SESSION: + case IMSG_CTL_PAUSE_SMTP: + case IMSG_CTL_RESUME_SMTP: + smtp_imsg(p, imsg); + return; + + /* mta imsg */ + case IMSG_QUEUE_TRANSFER: + case IMSG_MTA_OPEN_MESSAGE: + case IMSG_MTA_LOOKUP_CREDENTIALS: + case IMSG_MTA_LOOKUP_SMARTHOST: + case IMSG_MTA_LOOKUP_SOURCE: + case IMSG_MTA_LOOKUP_HELO: + case IMSG_MTA_DNS_HOST: + case IMSG_MTA_DNS_HOST_END: + case IMSG_MTA_DNS_MX_PREFERENCE: + case IMSG_CTL_RESUME_ROUTE: + case IMSG_CTL_MTA_SHOW_HOSTS: + case IMSG_CTL_MTA_SHOW_RELAYS: + case IMSG_CTL_MTA_SHOW_ROUTES: + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + case IMSG_CTL_MTA_BLOCK: + case IMSG_CTL_MTA_UNBLOCK: + case IMSG_CTL_MTA_SHOW_BLOCK: + mta_imsg(p, imsg); + return; + + /* mda imsg */ + case IMSG_MDA_LOOKUP_USERINFO: + case IMSG_QUEUE_DELIVER: + case IMSG_MDA_OPEN_MESSAGE: + case IMSG_MDA_FORK: + case IMSG_MDA_DONE: + mda_imsg(p, imsg); + return; + default: + break; + } + + errx(1, "session_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +static void +pony_shutdown(void) +{ + log_debug("debug: pony agent exiting"); + _exit(0); +} + +int +pony(void) +{ + struct passwd *pw; + + mda_postfork(); + mta_postfork(); + smtp_postfork(); + + /* do not purge listeners and pki, they are purged + * in smtp_configure() + */ + purge_config(PURGE_TABLES|PURGE_RULES); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + if (chroot(PATH_CHROOT) == -1) + fatal("pony: chroot"); + if (chdir("/") == -1) + fatal("pony: chdir(\"/\")"); + + config_process(PROC_PONY); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("pony: cannot drop privileges"); + + imsg_callback = pony_imsg; + event_init(); + + mda_postprivdrop(); + mta_postprivdrop(); + smtp_postprivdrop(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_PARENT); + config_peer(PROC_QUEUE); + config_peer(PROC_LKA); + config_peer(PROC_CONTROL); + config_peer(PROC_CA); + + ca_engine_init(); + +#if HAVE_PLEDGE + if (pledge("stdio inet unix recvfd sendfd", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} diff --git a/usr.sbin/smtpd/proxy.c b/usr.sbin/smtpd/proxy.c new file mode 100644 index 00000000..fdaf4f27 --- /dev/null +++ b/usr.sbin/smtpd/proxy.c @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2017 Antoine Kaufmann + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "smtpd.h" + + +/* + * The PROXYv2 protocol is described here: + * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + */ + +#define PROXY_CLOCAL 0x0 +#define PROXY_CPROXY 0x1 +#define PROXY_AF_UNSPEC 0x0 +#define PROXY_AF_INET 0x1 +#define PROXY_AF_INET6 0x2 +#define PROXY_AF_UNIX 0x3 +#define PROXY_TF_UNSPEC 0x0 +#define PROXY_TF_STREAM 0x1 +#define PROXY_TF_DGRAM 0x2 + +#define PROXY_SESSION_TIMEOUT 300 + +static const uint8_t pv2_signature[] = { + 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, + 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A +}; + +struct proxy_hdr_v2 { + uint8_t sig[12]; + uint8_t ver_cmd; + uint8_t fam; + uint16_t len; +} __attribute__((packed)); + + +struct proxy_addr_ipv4 { + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; +} __attribute__((packed)); + +struct proxy_addr_ipv6 { + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; +} __attribute__((packed)); + +struct proxy_addr_unix { + uint8_t src_addr[108]; + uint8_t dst_addr[108]; +} __attribute__((packed)); + +union proxy_addr { + struct proxy_addr_ipv4 ipv4; + struct proxy_addr_ipv6 ipv6; + struct proxy_addr_unix un; +} __attribute__((packed)); + +struct proxy_session { + struct listener *l; + struct io *io; + + uint64_t id; + int fd; + uint16_t header_len; + uint16_t header_total; + uint16_t addr_len; + uint16_t addr_total; + + struct sockaddr_storage ss; + struct proxy_hdr_v2 hdr; + union proxy_addr addr; + + void (*cb_accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *); + void (*cb_dropped)(struct listener *, int, + const struct sockaddr_storage *); +}; + +static void proxy_io(struct io *, int, void *); +static void proxy_error(struct proxy_session *, const char *, const char *); +static int proxy_header_validate(struct proxy_session *); +static int proxy_translate_ss(struct proxy_session *); + +int +proxy_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, + void (*accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *), + void (*dropped)(struct listener *, int, + const struct sockaddr_storage *)); + +int +proxy_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, + void (*accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *), + void (*dropped)(struct listener *, int, + const struct sockaddr_storage *)) +{ + struct proxy_session *s; + + if ((s = calloc(1, sizeof(*s))) == NULL) + return (-1); + + if ((s->io = io_new()) == NULL) { + free(s); + return (-1); + } + + s->id = generate_uid(); + s->l = listener; + s->fd = sock; + s->header_len = 0; + s->addr_len = 0; + s->ss = *ss; + s->cb_accepted = accepted; + s->cb_dropped = dropped; + + io_set_callback(s->io, proxy_io, s); + io_set_fd(s->io, sock); + io_set_timeout(s->io, PROXY_SESSION_TIMEOUT * 1000); + io_set_read(s->io); + + log_info("%016"PRIx64" smtp event=proxy address=%s", + s->id, ss_to_text(&s->ss)); + + return 0; +} + +static void +proxy_io(struct io *io, int evt, void *arg) +{ + struct proxy_session *s = arg; + struct proxy_hdr_v2 *h = &s->hdr; + uint8_t *buf; + size_t len, off; + + switch (evt) { + + case IO_DATAIN: + buf = io_data(io); + len = io_datalen(io); + + if (s->header_len < sizeof(s->hdr)) { + /* header is incomplete */ + off = sizeof(s->hdr) - s->header_len; + off = (len < off ? len : off); + memcpy((uint8_t *) &s->hdr + s->header_len, buf, off); + + s->header_len += off; + buf += off; + len -= off; + io_drop(s->io, off); + + if (s->header_len < sizeof(s->hdr)) { + /* header is still not complete */ + return; + } + + if (proxy_header_validate(s) != 0) + return; + } + + if (s->addr_len < s->addr_total) { + /* address is incomplete */ + off = s->addr_total - s->addr_len; + off = (len < off ? len : off); + memcpy((uint8_t *) &s->addr + s->addr_len, buf, off); + + s->header_len += off; + s->addr_len += off; + buf += off; + len -= off; + io_drop(s->io, off); + + if (s->addr_len < s->addr_total) { + /* address is still not complete */ + return; + } + } + + if (s->header_len < s->header_total) { + /* additional parameters not complete */ + /* these are ignored for now, but we still need to drop + * the bytes from the buffer */ + off = s->header_total - s->header_len; + off = (len < off ? len : off); + + s->header_len += off; + io_drop(s->io, off); + + if (s->header_len < s->header_total) + /* not complete yet */ + return; + } + + switch(h->ver_cmd & 0xF) { + case PROXY_CLOCAL: + /* local address, no need to modify ss */ + break; + + case PROXY_CPROXY: + if (proxy_translate_ss(s) != 0) + return; + break; + + default: + proxy_error(s, "protocol error", "unknown command"); + return; + } + + s->cb_accepted(s->l, s->fd, &s->ss, s->io); + /* we passed off s->io, so it does not need to be freed here */ + free(s); + break; + + case IO_TIMEOUT: + proxy_error(s, "timeout", NULL); + break; + + case IO_DISCONNECTED: + proxy_error(s, "disconnected", NULL); + break; + + case IO_ERROR: + proxy_error(s, "IO error", io_error(io)); + break; + + default: + fatalx("proxy_io()"); + } + +} + +static void +proxy_error(struct proxy_session *s, const char *reason, const char *extra) +{ + if (extra) + log_info("proxy %p event=closed address=%s reason=\"%s (%s)\"", + s, ss_to_text(&s->ss), reason, extra); + else + log_info("proxy %p event=closed address=%s reason=\"%s\"", + s, ss_to_text(&s->ss), reason); + + s->cb_dropped(s->l, s->fd, &s->ss); + io_free(s->io); + free(s); +} + +static int +proxy_header_validate(struct proxy_session *s) +{ + struct proxy_hdr_v2 *h = &s->hdr; + + if (memcmp(h->sig, pv2_signature, + sizeof(pv2_signature)) != 0) { + proxy_error(s, "protocol error", "invalid signature"); + return (-1); + } + + if ((h->ver_cmd >> 4) != 2) { + proxy_error(s, "protocol error", "invalid version"); + return (-1); + } + + switch (h->fam) { + case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC): + s->addr_total = 0; + break; + + case (PROXY_AF_INET << 4 | PROXY_TF_STREAM): + s->addr_total = sizeof(s->addr.ipv4); + break; + + case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM): + s->addr_total = sizeof(s->addr.ipv6); + break; + + case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM): + s->addr_total = sizeof(s->addr.un); + break; + + default: + proxy_error(s, "protocol error", "unsupported address family"); + return (-1); + } + + s->header_total = ntohs(h->len); + if (s->header_total > UINT16_MAX - sizeof(struct proxy_hdr_v2)) { + proxy_error(s, "protocol error", "header too long"); + return (-1); + } + s->header_total += sizeof(struct proxy_hdr_v2); + + if (s->header_total < sizeof(struct proxy_hdr_v2) + s->addr_total) { + proxy_error(s, "protocol error", "address info too short"); + return (-1); + } + + return 0; +} + +static int +proxy_translate_ss(struct proxy_session *s) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) &s->ss; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &s->ss; + struct sockaddr_un *sun = (struct sockaddr_un *) &s->ss; + size_t sun_len; + + switch (s->hdr.fam) { + case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC): + /* unspec: only supported for local */ + proxy_error(s, "address translation", "UNSPEC family not " + "supported for PROXYing"); + return (-1); + + case (PROXY_AF_INET << 4 | PROXY_TF_STREAM): + memset(&s->ss, 0, sizeof(s->ss)); + sin->sin_family = AF_INET; + sin->sin_port = s->addr.ipv4.src_port; + sin->sin_addr.s_addr = s->addr.ipv4.src_addr; + break; + + case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM): + memset(&s->ss, 0, sizeof(s->ss)); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = s->addr.ipv6.src_port; + memcpy(sin6->sin6_addr.s6_addr, s->addr.ipv6.src_addr, + sizeof(s->addr.ipv6.src_addr)); + break; + + case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM): + memset(&s->ss, 0, sizeof(s->ss)); + sun_len = strnlen(s->addr.un.src_addr, + sizeof(s->addr.un.src_addr)); + if (sun_len > sizeof(sun->sun_path)) { + proxy_error(s, "address translation", "Unix socket path" + " longer than supported"); + return (-1); + } + sun->sun_family = AF_UNIX; + memcpy(sun->sun_path, s->addr.un.src_addr, sun_len); + break; + + default: + fatalx("proxy_translate_ss()"); + } + + return 0; +} diff --git a/usr.sbin/smtpd/queue.c b/usr.sbin/smtpd/queue.c new file mode 100644 index 00000000..434e3647 --- /dev/null +++ b/usr.sbin/smtpd/queue.c @@ -0,0 +1,750 @@ +/* $OpenBSD: queue.c,v 1.190 2020/04/22 11:35:34 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static void queue_imsg(struct mproc *, struct imsg *); +static void queue_timeout(int, short, void *); +static void queue_bounce(struct envelope *, struct delivery_bounce *); +static void queue_shutdown(void); +static void queue_log(const struct envelope *, const char *, const char *); +static void queue_msgid_walk(int, short, void *); + + +static void +queue_imsg(struct mproc *p, struct imsg *imsg) +{ + struct delivery_bounce bounce; + struct msg_walkinfo *wi; + struct timeval tv; + struct bounce_req_msg *req_bounce; + struct envelope evp; + struct msg m; + const char *reason; + uint64_t reqid, evpid, holdq; + uint32_t msgid; + time_t nexttry; + size_t n_evp; + int fd, mta_ext, ret, v, flags, code; + + if (imsg == NULL) + queue_shutdown(); + + memset(&bounce, 0, sizeof(struct delivery_bounce)); + + switch (imsg->hdr.type) { + case IMSG_SMTP_MESSAGE_CREATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + ret = queue_message_create(&msgid); + + m_create(p, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); + m_add_id(p, reqid); + if (ret == 0) + m_add_int(p, 0); + else { + m_add_int(p, 1); + m_add_msgid(p, msgid); + } + m_close(p); + return; + + case IMSG_SMTP_MESSAGE_ROLLBACK: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + + queue_message_delete(msgid); + + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_ROLLBACK, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_msgid(&m, &msgid); + m_end(&m); + + ret = queue_message_commit(msgid); + + m_create(p, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, (ret == 0) ? 0 : 1); + m_close(p); + + if (ret) { + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + } + return; + + case IMSG_SMTP_MESSAGE_OPEN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_msgid(&m, &msgid); + m_end(&m); + + fd = queue_message_fd_rw(msgid); + + m_create(p, IMSG_SMTP_MESSAGE_OPEN, 0, 0, fd); + m_add_id(p, reqid); + m_add_int(p, (fd == -1) ? 0 : 1); + m_close(p); + return; + + case IMSG_QUEUE_SMTP_SESSION: + bounce_fd(imsg->fd); + return; + + case IMSG_LKA_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_envelope(&m, &evp); + m_end(&m); + + if (evp.id == 0) + log_warnx("warn: imsg_queue_submit_envelope: evpid=0"); + if (evpid_to_msgid(evp.id) == 0) + log_warnx("warn: imsg_queue_submit_envelope: msgid=0, " + "evpid=%016"PRIx64, evp.id); + ret = queue_envelope_create(&evp); + m_create(p_pony, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_id(p_pony, reqid); + if (ret == 0) + m_add_int(p_pony, 0); + else { + m_add_int(p_pony, 1); + m_add_evpid(p_pony, evp.id); + } + m_close(p_pony); + if (ret) { + m_create(p_scheduler, + IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + } + return; + + case IMSG_LKA_ENVELOPE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + m_create(p_pony, IMSG_QUEUE_ENVELOPE_COMMIT, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, 1); + m_close(p_pony); + return; + + case IMSG_SCHED_ENVELOPE_REMOVE: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_ACK, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + + /* already removed by scheduler */ + if (queue_envelope_load(evpid, &evp) == 0) + return; + + queue_log(&evp, "Remove", "Removed by administrator"); + queue_envelope_delete(evpid); + return; + + case IMSG_SCHED_ENVELOPE_EXPIRE: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_ACK, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + + /* already removed by scheduler*/ + if (queue_envelope_load(evpid, &evp) == 0) + return; + + bounce.type = B_FAILED; + envelope_set_errormsg(&evp, "Envelope expired"); + envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); + envelope_set_esc_code(&evp, ESC_DELIVERY_TIME_EXPIRED); + queue_bounce(&evp, &bounce); + queue_log(&evp, "Expire", "Envelope expired"); + queue_envelope_delete(evpid); + return; + + case IMSG_SCHED_ENVELOPE_BOUNCE: + CHECK_IMSG_DATA_SIZE(imsg, sizeof *req_bounce); + req_bounce = imsg->data; + evpid = req_bounce->evpid; + + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: bounce: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 0); /* not in-flight */ + m_close(p_scheduler); + return; + } + queue_bounce(&evp, &req_bounce->bounce); + evp.lastbounce = req_bounce->timestamp; + if (!queue_envelope_update(&evp)) + log_warnx("warn: could not update envelope %016"PRIx64, evpid); + return; + + case IMSG_SCHED_ENVELOPE_DELIVER: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: deliver: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + evp.lasttry = time(NULL); + m_create(p_pony, IMSG_QUEUE_DELIVER, 0, 0, -1); + m_add_envelope(p_pony, &evp); + m_close(p_pony); + return; + + case IMSG_SCHED_ENVELOPE_INJECT: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + bounce_add(evpid); + return; + + case IMSG_SCHED_ENVELOPE_TRANSFER: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + evp.lasttry = time(NULL); + m_create(p_pony, IMSG_QUEUE_TRANSFER, 0, 0, -1); + m_add_envelope(p_pony, &evp); + m_close(p_pony); + return; + + case IMSG_CTL_LIST_ENVELOPES: + if (imsg->hdr.len == sizeof imsg->hdr) { + m_forward(p_control, imsg); + return; + } + + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_int(&m, &flags); + m_get_time(&m, &nexttry); + m_end(&m); + + if (queue_envelope_load(evpid, &evp) == 0) + return; /* Envelope is gone, drop it */ + + /* + * XXX consistency: The envelope might already be on + * its way back to the scheduler. We need to detect + * this properly and report that state. + */ + if (flags & EF_INFLIGHT) { + /* + * Not exactly correct but pretty close: The + * value is not recorded on the envelope unless + * a tempfail occurs. + */ + evp.lasttry = nexttry; + } + + m_create(p_control, IMSG_CTL_LIST_ENVELOPES, + imsg->hdr.peerid, 0, -1); + m_add_int(p_control, flags); + m_add_time(p_control, nexttry); + m_add_envelope(p_control, &evp); + m_close(p_control); + return; + + case IMSG_MDA_OPEN_MESSAGE: + case IMSG_MTA_OPEN_MESSAGE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_msgid(&m, &msgid); + m_end(&m); + fd = queue_message_fd_r(msgid); + m_create(p, imsg->hdr.type, 0, 0, fd); + m_add_id(p, reqid); + m_close(p); + return; + + case IMSG_MDA_DELIVERY_OK: + case IMSG_MTA_DELIVERY_OK: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK) + m_get_int(&m, &mta_ext); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warn("queue: dsn: failed to load envelope"); + return; + } + if (evp.dsn_notify & DSN_SUCCESS) { + bounce.type = B_DELIVERED; + bounce.dsn_ret = evp.dsn_ret; + envelope_set_esc_class(&evp, ESC_STATUS_OK); + if (imsg->hdr.type == IMSG_MDA_DELIVERY_OK) + queue_bounce(&evp, &bounce); + else if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK && + (mta_ext & MTA_EXT_DSN) == 0) { + bounce.mta_without_dsn = 1; + queue_bounce(&evp, &bounce); + } + } + queue_envelope_delete(evpid); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_OK, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + return; + + case IMSG_MDA_DELIVERY_TEMPFAIL: + case IMSG_MTA_DELIVERY_TEMPFAIL: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_string(&m, &reason); + m_get_int(&m, &code); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: tempfail: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + envelope_set_errormsg(&evp, "%s", reason); + envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); + envelope_set_esc_code(&evp, code); + evp.retry++; + if (!queue_envelope_update(&evp)) + log_warnx("warn: could not update envelope %016"PRIx64, evpid); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + return; + + case IMSG_MDA_DELIVERY_PERMFAIL: + case IMSG_MTA_DELIVERY_PERMFAIL: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_string(&m, &reason); + m_get_int(&m, &code); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: permfail: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + bounce.type = B_FAILED; + envelope_set_errormsg(&evp, "%s", reason); + envelope_set_esc_class(&evp, ESC_STATUS_PERMFAIL); + envelope_set_esc_code(&evp, code); + queue_bounce(&evp, &bounce); + queue_envelope_delete(evpid); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + return; + + case IMSG_MDA_DELIVERY_LOOP: + case IMSG_MTA_DELIVERY_LOOP: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: loop: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + envelope_set_errormsg(&evp, "%s", "Loop detected"); + envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); + envelope_set_esc_code(&evp, ESC_ROUTING_LOOP_DETECTED); + bounce.type = B_FAILED; + queue_bounce(&evp, &bounce); + queue_envelope_delete(evp.id); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_LOOP, 0, 0, -1); + m_add_evpid(p_scheduler, evp.id); + m_close(p_scheduler); + return; + + case IMSG_MTA_DELIVERY_HOLD: + case IMSG_MDA_DELIVERY_HOLD: + imsg->hdr.type = IMSG_QUEUE_HOLDQ_HOLD; + m_forward(p_scheduler, imsg); + return; + + case IMSG_MTA_SCHEDULE: + imsg->hdr.type = IMSG_QUEUE_ENVELOPE_SCHEDULE; + m_forward(p_scheduler, imsg); + return; + + case IMSG_MTA_HOLDQ_RELEASE: + case IMSG_MDA_HOLDQ_RELEASE: + m_msg(&m, imsg); + m_get_id(&m, &holdq); + m_get_int(&m, &v); + m_end(&m); + m_create(p_scheduler, IMSG_QUEUE_HOLDQ_RELEASE, 0, 0, -1); + if (imsg->hdr.type == IMSG_MTA_HOLDQ_RELEASE) + m_add_int(p_scheduler, D_MTA); + else + m_add_int(p_scheduler, D_MDA); + m_add_id(p_scheduler, holdq); + m_add_int(p_scheduler, v); + m_close(p_scheduler); + return; + + case IMSG_CTL_PAUSE_MDA: + case IMSG_CTL_PAUSE_MTA: + case IMSG_CTL_RESUME_MDA: + case IMSG_CTL_RESUME_MTA: + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CTL_DISCOVER_EVPID: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: discover: failed to load " + "envelope %016" PRIx64, evpid); + n_evp = 0; + m_compose(p_control, imsg->hdr.type, + imsg->hdr.peerid, 0, -1, + &n_evp, sizeof n_evp); + return; + } + + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, + 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, + 0, 0, -1); + m_add_msgid(p_scheduler, evpid_to_msgid(evpid)); + m_close(p_scheduler); + n_evp = 1; + m_compose(p_control, imsg->hdr.type, imsg->hdr.peerid, + 0, -1, &n_evp, sizeof n_evp); + return; + + case IMSG_CTL_DISCOVER_MSGID: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + /* handle concurrent walk requests */ + wi = xcalloc(1, sizeof *wi); + wi->msgid = msgid; + wi->peerid = imsg->hdr.peerid; + evtimer_set(&wi->ev, queue_msgid_walk, wi); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&wi->ev, &tv); + return; + } + + errx(1, "queue_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +static void +queue_msgid_walk(int fd, short event, void *arg) +{ + struct envelope evp; + struct timeval tv; + struct msg_walkinfo *wi = arg; + int r; + + r = queue_message_walk(&evp, wi->msgid, &wi->done, &wi->data); + if (r == -1) { + if (wi->n_evp) { + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, + 0, 0, -1); + m_add_msgid(p_scheduler, wi->msgid); + m_close(p_scheduler); + } + + m_compose(p_control, IMSG_CTL_DISCOVER_MSGID, wi->peerid, 0, -1, + &wi->n_evp, sizeof wi->n_evp); + evtimer_del(&wi->ev); + free(wi); + return; + } + + if (r) { + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + wi->n_evp += 1; + } + + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_set(&wi->ev, queue_msgid_walk, wi); + evtimer_add(&wi->ev, &tv); +} + +static void +queue_bounce(struct envelope *e, struct delivery_bounce *d) +{ + struct envelope b; + + b = *e; + b.type = D_BOUNCE; + b.agent.bounce = *d; + b.retry = 0; + b.lasttry = 0; + b.creation = time(NULL); + b.ttl = 3600 * 24 * 7; + + if (e->dsn_notify & DSN_NEVER) + return; + + if (b.id == 0) + log_warnx("warn: queue_bounce: evpid=0"); + if (evpid_to_msgid(b.id) == 0) + log_warnx("warn: queue_bounce: msgid=0, evpid=%016"PRIx64, + b.id); + if (e->type == D_BOUNCE) { + log_warnx("warn: queue: double bounce!"); + } else if (e->sender.user[0] == '\0') { + log_warnx("warn: queue: no return path!"); + } else if (!queue_envelope_create(&b)) { + log_warnx("warn: queue: cannot bounce!"); + } else { + log_debug("debug: queue: bouncing evp:%016" PRIx64 + " as evp:%016" PRIx64, e->id, b.id); + + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_envelope(p_scheduler, &b); + m_close(p_scheduler); + + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1); + m_add_msgid(p_scheduler, evpid_to_msgid(b.id)); + m_close(p_scheduler); + + stat_increment("queue.bounce", 1); + } +} + +static void +queue_shutdown(void) +{ + log_debug("debug: queue agent exiting"); + queue_close(); + _exit(0); +} + +int +queue(void) +{ + struct passwd *pw; + struct timeval tv; + struct event ev_qload; + + purge_config(PURGE_EVERYTHING & ~PURGE_DISPATCHERS); + + if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL) + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + env->sc_queue_flags |= QUEUE_EVPCACHE; + env->sc_queue_evpcache_size = 1024; + + if (chroot(PATH_SPOOL) == -1) + fatal("queue: chroot"); + if (chdir("/") == -1) + fatal("queue: chdir(\"/\")"); + + config_process(PROC_QUEUE); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) + log_info("queue: queue compression enabled"); + + if (env->sc_queue_key) { + if (!crypto_setup(env->sc_queue_key, strlen(env->sc_queue_key))) + fatalx("crypto_setup: invalid key for queue encryption"); + log_info("queue: queue encryption enabled"); + } + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("queue: cannot drop privileges"); + + imsg_callback = queue_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_PARENT); + config_peer(PROC_CONTROL); + config_peer(PROC_LKA); + config_peer(PROC_SCHEDULER); + config_peer(PROC_PONY); + + /* setup queue loading task */ + evtimer_set(&ev_qload, queue_timeout, &ev_qload); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&ev_qload, &tv); + +#if HAVE_PLEDGE + if (pledge("stdio rpath wpath cpath flock recvfd sendfd", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +queue_timeout(int fd, short event, void *p) +{ + static uint32_t msgid = 0; + struct envelope evp; + struct event *ev = p; + struct timeval tv; + int r; + + r = queue_envelope_walk(&evp); + if (r == -1) { + if (msgid) { + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + } + log_debug("debug: queue: done loading queue into scheduler"); + return; + } + + if (r) { + if (msgid && evpid_to_msgid(evp.id) != msgid) { + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + } + msgid = evpid_to_msgid(evp.id); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + } + + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(ev, &tv); +} + +static void +queue_log(const struct envelope *e, const char *prefix, const char *status) +{ + char rcpt[LINE_MAX]; + + (void)strlcpy(rcpt, "-", sizeof rcpt); + if (strcmp(e->rcpt.user, e->dest.user) || + strcmp(e->rcpt.domain, e->dest.domain)) + (void)snprintf(rcpt, sizeof rcpt, "%s@%s", + e->rcpt.user, e->rcpt.domain); + + log_info("%s: %s for %016" PRIx64 ": from=<%s@%s>, to=<%s@%s>, " + "rcpt=<%s>, delay=%s, stat=%s", + e->type == D_MDA ? "delivery" : "relay", + prefix, + e->id, e->sender.user, e->sender.domain, + e->dest.user, e->dest.domain, + rcpt, + duration_to_text(time(NULL) - e->creation), + status); +} diff --git a/usr.sbin/smtpd/queue_backend.c b/usr.sbin/smtpd/queue_backend.c new file mode 100644 index 00000000..fa945f47 --- /dev/null +++ b/usr.sbin/smtpd/queue_backend.c @@ -0,0 +1,806 @@ +/* $OpenBSD: queue_backend.c,v 1.66 2020/04/22 11:35:34 eric Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static const char* envelope_validate(struct envelope *); + +extern struct queue_backend queue_backend_fs; +extern struct queue_backend queue_backend_null; +extern struct queue_backend queue_backend_proc; +extern struct queue_backend queue_backend_ram; + +static void queue_envelope_cache_add(struct envelope *); +static void queue_envelope_cache_update(struct envelope *); +static void queue_envelope_cache_del(uint64_t evpid); + +TAILQ_HEAD(evplst, envelope); + +static struct tree evpcache_tree; +static struct evplst evpcache_list; +static struct queue_backend *backend; + +static int (*handler_close)(void); +static int (*handler_message_create)(uint32_t *); +static int (*handler_message_commit)(uint32_t, const char*); +static int (*handler_message_delete)(uint32_t); +static int (*handler_message_fd_r)(uint32_t); +static int (*handler_envelope_create)(uint32_t, const char *, size_t, uint64_t *); +static int (*handler_envelope_delete)(uint64_t); +static int (*handler_envelope_update)(uint64_t, const char *, size_t); +static int (*handler_envelope_load)(uint64_t, char *, size_t); +static int (*handler_envelope_walk)(uint64_t *, char *, size_t); +static int (*handler_message_walk)(uint64_t *, char *, size_t, + uint32_t, int *, void **); + +#ifdef QUEUE_PROFILING + +static struct { + struct timespec t0; + const char *name; +} profile; + +static inline void profile_enter(const char *name) +{ + if ((profiling & PROFILE_QUEUE) == 0) + return; + + profile.name = name; + clock_gettime(CLOCK_MONOTONIC, &profile.t0); +} + +static inline void profile_leave(void) +{ + struct timespec t1, dt; + + if ((profiling & PROFILE_QUEUE) == 0) + return; + + clock_gettime(CLOCK_MONOTONIC, &t1); + timespecsub(&t1, &profile.t0, &dt); + log_debug("profile-queue: %s %lld.%09ld", profile.name, + (long long)dt.tv_sec, dt.tv_nsec); +} +#else +#define profile_enter(x) do {} while (0) +#define profile_leave() do {} while (0) +#endif + +static int +queue_message_path(uint32_t msgid, char *buf, size_t len) +{ + return bsnprintf(buf, len, "%s/%08"PRIx32, PATH_TEMPORARY, msgid); +} + +int +queue_init(const char *name, int server) +{ + struct passwd *pwq; + struct group *gr; + int r; + + pwq = getpwnam(SMTPD_QUEUE_USER); + if (pwq == NULL) + errx(1, "unknown user %s", SMTPD_QUEUE_USER); + + gr = getgrnam(SMTPD_QUEUE_GROUP); + if (gr == NULL) + errx(1, "unknown group %s", SMTPD_QUEUE_GROUP); + + tree_init(&evpcache_tree); + TAILQ_INIT(&evpcache_list); + + if (!strcmp(name, "fs")) + backend = &queue_backend_fs; + else if (!strcmp(name, "null")) + backend = &queue_backend_null; + else if (!strcmp(name, "ram")) + backend = &queue_backend_ram; + else + backend = &queue_backend_proc; + + if (server) { + if (ckdir(PATH_SPOOL, 0711, 0, 0, 1) == 0) + errx(1, "error in spool directory setup"); + if (ckdir(PATH_SPOOL PATH_OFFLINE, 0770, 0, gr->gr_gid, 1) == 0) + errx(1, "error in offline directory setup"); + if (ckdir(PATH_SPOOL PATH_PURGE, 0700, pwq->pw_uid, 0, 1) == 0) + errx(1, "error in purge directory setup"); + + mvpurge(PATH_SPOOL PATH_TEMPORARY, PATH_SPOOL PATH_PURGE); + + if (ckdir(PATH_SPOOL PATH_TEMPORARY, 0700, pwq->pw_uid, 0, 1) == 0) + errx(1, "error in purge directory setup"); + } + + r = backend->init(pwq, server, name); + + log_trace(TRACE_QUEUE, "queue-backend: queue_init(%d) -> %d", server, r); + + return (r); +} + +int +queue_close(void) +{ + if (handler_close) + return (handler_close()); + + return (1); +} + +int +queue_message_create(uint32_t *msgid) +{ + int r; + + profile_enter("queue_message_create"); + r = handler_message_create(msgid); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_create() -> %d (%08"PRIx32")", + r, *msgid); + + return (r); +} + +int +queue_message_delete(uint32_t msgid) +{ + char msgpath[PATH_MAX]; + uint64_t evpid; + void *iter; + int r; + + profile_enter("queue_message_delete"); + r = handler_message_delete(msgid); + profile_leave(); + + /* in case the message is incoming */ + queue_message_path(msgid, msgpath, sizeof(msgpath)); + unlink(msgpath); + + /* remove remaining envelopes from the cache if any (on rollback) */ + evpid = msgid_to_evpid(msgid); + for (;;) { + iter = NULL; + if (!tree_iterfrom(&evpcache_tree, &iter, evpid, &evpid, NULL)) + break; + if (evpid_to_msgid(evpid) != msgid) + break; + queue_envelope_cache_del(evpid); + } + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_delete(%08"PRIx32") -> %d", msgid, r); + + return (r); +} + +int +queue_message_commit(uint32_t msgid) +{ + int r; + char msgpath[PATH_MAX]; + char tmppath[PATH_MAX]; + FILE *ifp = NULL; + FILE *ofp = NULL; + + profile_enter("queue_message_commit"); + + queue_message_path(msgid, msgpath, sizeof(msgpath)); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + bsnprintf(tmppath, sizeof tmppath, "%s.comp", msgpath); + ifp = fopen(msgpath, "r"); + ofp = fopen(tmppath, "w+"); + if (ifp == NULL || ofp == NULL) + goto err; + if (!compress_file(ifp, ofp)) + goto err; + fclose(ifp); + fclose(ofp); + ifp = NULL; + ofp = NULL; + + if (rename(tmppath, msgpath) == -1) { + if (errno == ENOSPC) + return (0); + unlink(tmppath); + log_warn("rename"); + return (0); + } + } + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + bsnprintf(tmppath, sizeof tmppath, "%s.enc", msgpath); + ifp = fopen(msgpath, "r"); + ofp = fopen(tmppath, "w+"); + if (ifp == NULL || ofp == NULL) + goto err; + if (!crypto_encrypt_file(ifp, ofp)) + goto err; + fclose(ifp); + fclose(ofp); + ifp = NULL; + ofp = NULL; + + if (rename(tmppath, msgpath) == -1) { + if (errno == ENOSPC) + return (0); + unlink(tmppath); + log_warn("rename"); + return (0); + } + } + + r = handler_message_commit(msgid, msgpath); + profile_leave(); + + /* in case it's not done by the backend */ + unlink(msgpath); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_commit(%08"PRIx32") -> %d", + msgid, r); + + return (r); + +err: + if (ifp) + fclose(ifp); + if (ofp) + fclose(ofp); + return 0; +} + +int +queue_message_fd_r(uint32_t msgid) +{ + int fdin = -1, fdout = -1, fd = -1; + FILE *ifp = NULL; + FILE *ofp = NULL; + + profile_enter("queue_message_fd_r"); + fdin = handler_message_fd_r(msgid); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_fd_r(%08"PRIx32") -> %d", msgid, fdin); + + if (fdin == -1) + return (-1); + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + if ((fdout = mktmpfile()) == -1) + goto err; + if ((fd = dup(fdout)) == -1) + goto err; + if ((ifp = fdopen(fdin, "r")) == NULL) + goto err; + fdin = fd; + fd = -1; + if ((ofp = fdopen(fdout, "w+")) == NULL) + goto err; + + if (!crypto_decrypt_file(ifp, ofp)) + goto err; + + fclose(ifp); + ifp = NULL; + fclose(ofp); + ofp = NULL; + lseek(fdin, SEEK_SET, 0); + } + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + if ((fdout = mktmpfile()) == -1) + goto err; + if ((fd = dup(fdout)) == -1) + goto err; + if ((ifp = fdopen(fdin, "r")) == NULL) + goto err; + fdin = fd; + fd = -1; + if ((ofp = fdopen(fdout, "w+")) == NULL) + goto err; + + if (!uncompress_file(ifp, ofp)) + goto err; + + fclose(ifp); + ifp = NULL; + fclose(ofp); + ofp = NULL; + lseek(fdin, SEEK_SET, 0); + } + + return (fdin); + +err: + if (fd != -1) + close(fd); + if (fdin != -1) + close(fdin); + if (fdout != -1) + close(fdout); + if (ifp) + fclose(ifp); + if (ofp) + fclose(ofp); + return -1; +} + +int +queue_message_fd_rw(uint32_t msgid) +{ + char buf[PATH_MAX]; + + queue_message_path(msgid, buf, sizeof(buf)); + + return open(buf, O_RDWR | O_CREAT | O_EXCL, 0600); +} + +static int +queue_envelope_dump_buffer(struct envelope *ep, char *evpbuf, size_t evpbufsize) +{ + char *evp; + size_t evplen; + size_t complen; + char compbuf[sizeof(struct envelope)]; + size_t enclen; + char encbuf[sizeof(struct envelope)]; + + evp = evpbuf; + evplen = envelope_dump_buffer(ep, evpbuf, evpbufsize); + if (evplen == 0) + return (0); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + complen = compress_chunk(evp, evplen, compbuf, sizeof compbuf); + if (complen == 0) + return (0); + evp = compbuf; + evplen = complen; + } + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + enclen = crypto_encrypt_buffer(evp, evplen, encbuf, sizeof encbuf); + if (enclen == 0) + return (0); + evp = encbuf; + evplen = enclen; + } + + memmove(evpbuf, evp, evplen); + + return (evplen); +} + +static int +queue_envelope_load_buffer(struct envelope *ep, char *evpbuf, size_t evpbufsize) +{ + char *evp; + size_t evplen; + char compbuf[sizeof(struct envelope)]; + size_t complen; + char encbuf[sizeof(struct envelope)]; + size_t enclen; + + evp = evpbuf; + evplen = evpbufsize; + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + enclen = crypto_decrypt_buffer(evp, evplen, encbuf, sizeof encbuf); + if (enclen == 0) + return (0); + evp = encbuf; + evplen = enclen; + } + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + complen = uncompress_chunk(evp, evplen, compbuf, sizeof compbuf); + if (complen == 0) + return (0); + evp = compbuf; + evplen = complen; + } + + return (envelope_load_buffer(ep, evp, evplen)); +} + +static void +queue_envelope_cache_add(struct envelope *e) +{ + struct envelope *cached; + + while (tree_count(&evpcache_tree) >= env->sc_queue_evpcache_size) + queue_envelope_cache_del(TAILQ_LAST(&evpcache_list, evplst)->id); + + cached = xcalloc(1, sizeof *cached); + *cached = *e; + TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); + tree_xset(&evpcache_tree, e->id, cached); + stat_increment("queue.evpcache.size", 1); +} + +static void +queue_envelope_cache_update(struct envelope *e) +{ + struct envelope *cached; + + if ((cached = tree_get(&evpcache_tree, e->id)) == NULL) { + queue_envelope_cache_add(e); + stat_increment("queue.evpcache.update.missed", 1); + } else { + TAILQ_REMOVE(&evpcache_list, cached, entry); + *cached = *e; + TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); + stat_increment("queue.evpcache.update.hit", 1); + } +} + +static void +queue_envelope_cache_del(uint64_t evpid) +{ + struct envelope *cached; + + if ((cached = tree_pop(&evpcache_tree, evpid)) == NULL) + return; + + TAILQ_REMOVE(&evpcache_list, cached, entry); + free(cached); + stat_decrement("queue.evpcache.size", 1); +} + +int +queue_envelope_create(struct envelope *ep) +{ + int r; + char evpbuf[sizeof(struct envelope)]; + size_t evplen; + uint64_t evpid; + uint32_t msgid; + + ep->creation = time(NULL); + evplen = queue_envelope_dump_buffer(ep, evpbuf, sizeof evpbuf); + if (evplen == 0) + return (0); + + evpid = ep->id; + msgid = evpid_to_msgid(evpid); + + profile_enter("queue_envelope_create"); + r = handler_envelope_create(msgid, evpbuf, evplen, &ep->id); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_create(%016"PRIx64", %zu) -> %d (%016"PRIx64")", + evpid, evplen, r, ep->id); + + if (!r) { + ep->creation = 0; + ep->id = 0; + } + + if (r && env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_add(ep); + + return (r); +} + +int +queue_envelope_delete(uint64_t evpid) +{ + int r; + + if (env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_del(evpid); + + profile_enter("queue_envelope_delete"); + r = handler_envelope_delete(evpid); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_delete(%016"PRIx64") -> %d", + evpid, r); + + return (r); +} + +int +queue_envelope_load(uint64_t evpid, struct envelope *ep) +{ + const char *e; + char evpbuf[sizeof(struct envelope)]; + size_t evplen; + struct envelope *cached; + + if ((env->sc_queue_flags & QUEUE_EVPCACHE) && + (cached = tree_get(&evpcache_tree, evpid))) { + *ep = *cached; + stat_increment("queue.evpcache.load.hit", 1); + return (1); + } + + ep->id = evpid; + profile_enter("queue_envelope_load"); + evplen = handler_envelope_load(ep->id, evpbuf, sizeof evpbuf); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_load(%016"PRIx64") -> %zu", + evpid, evplen); + + if (evplen == 0) + return (0); + + if (queue_envelope_load_buffer(ep, evpbuf, evplen)) { + if ((e = envelope_validate(ep)) == NULL) { + ep->id = evpid; + if (env->sc_queue_flags & QUEUE_EVPCACHE) { + queue_envelope_cache_add(ep); + stat_increment("queue.evpcache.load.missed", 1); + } + return (1); + } + log_warnx("warn: invalid envelope %016" PRIx64 ": %s", + evpid, e); + } + return (0); +} + +int +queue_envelope_update(struct envelope *ep) +{ + char evpbuf[sizeof(struct envelope)]; + size_t evplen; + int r; + + evplen = queue_envelope_dump_buffer(ep, evpbuf, sizeof evpbuf); + if (evplen == 0) + return (0); + + profile_enter("queue_envelope_update"); + r = handler_envelope_update(ep->id, evpbuf, evplen); + profile_leave(); + + if (r && env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_update(ep); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_update(%016"PRIx64") -> %d", + ep->id, r); + + return (r); +} + +int +queue_message_walk(struct envelope *ep, uint32_t msgid, int *done, void **data) +{ + char evpbuf[sizeof(struct envelope)]; + uint64_t evpid; + int r; + const char *e; + + profile_enter("queue_message_walk"); + r = handler_message_walk(&evpid, evpbuf, sizeof evpbuf, + msgid, done, data); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_walk() -> %d (%016"PRIx64")", + r, evpid); + + if (r == -1) + return (r); + + if (r && queue_envelope_load_buffer(ep, evpbuf, (size_t)r)) { + if ((e = envelope_validate(ep)) == NULL) { + ep->id = evpid; + /* + * do not cache the envelope here, while discovering + * envelopes one could re-run discover on already + * scheduled envelopes which leads to triggering of + * strict checks in caching. Envelopes could anyway + * be loaded from backend if it isn't cached. + */ + return (1); + } + log_warnx("warn: invalid envelope %016" PRIx64 ": %s", + evpid, e); + } + return (0); +} + +int +queue_envelope_walk(struct envelope *ep) +{ + const char *e; + uint64_t evpid; + char evpbuf[sizeof(struct envelope)]; + int r; + + profile_enter("queue_envelope_walk"); + r = handler_envelope_walk(&evpid, evpbuf, sizeof evpbuf); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_walk() -> %d (%016"PRIx64")", + r, evpid); + + if (r == -1) + return (r); + + if (r && queue_envelope_load_buffer(ep, evpbuf, (size_t)r)) { + if ((e = envelope_validate(ep)) == NULL) { + ep->id = evpid; + if (env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_add(ep); + return (1); + } + log_warnx("warn: invalid envelope %016" PRIx64 ": %s", + evpid, e); + } + return (0); +} + +uint32_t +queue_generate_msgid(void) +{ + uint32_t msgid; + + while ((msgid = arc4random()) == 0) + ; + + return msgid; +} + +uint64_t +queue_generate_evpid(uint32_t msgid) +{ + uint32_t rnd; + uint64_t evpid; + + while ((rnd = arc4random()) == 0) + ; + + evpid = msgid; + evpid <<= 32; + evpid |= rnd; + + return evpid; +} + +static const char* +envelope_validate(struct envelope *ep) +{ + if (ep->version != SMTPD_ENVELOPE_VERSION) + return "version mismatch"; + + if (memchr(ep->helo, '\0', sizeof(ep->helo)) == NULL) + return "invalid helo"; + if (ep->helo[0] == '\0') + return "empty helo"; + + if (memchr(ep->hostname, '\0', sizeof(ep->hostname)) == NULL) + return "invalid hostname"; + if (ep->hostname[0] == '\0') + return "empty hostname"; + + if (memchr(ep->errorline, '\0', sizeof(ep->errorline)) == NULL) + return "invalid error line"; + + if (dict_get(env->sc_dispatchers, ep->dispatcher) == NULL) + return "unknown dispatcher"; + + return NULL; +} + +void +queue_api_on_close(int(*cb)(void)) +{ + handler_close = cb; +} + +void +queue_api_on_message_create(int(*cb)(uint32_t *)) +{ + handler_message_create = cb; +} + +void +queue_api_on_message_commit(int(*cb)(uint32_t, const char *)) +{ + handler_message_commit = cb; +} + +void +queue_api_on_message_delete(int(*cb)(uint32_t)) +{ + handler_message_delete = cb; +} + +void +queue_api_on_message_fd_r(int(*cb)(uint32_t)) +{ + handler_message_fd_r = cb; +} + +void +queue_api_on_envelope_create(int(*cb)(uint32_t, const char *, size_t, uint64_t *)) +{ + handler_envelope_create = cb; +} + +void +queue_api_on_envelope_delete(int(*cb)(uint64_t)) +{ + handler_envelope_delete = cb; +} + +void +queue_api_on_envelope_update(int(*cb)(uint64_t, const char *, size_t)) +{ + handler_envelope_update = cb; +} + +void +queue_api_on_envelope_load(int(*cb)(uint64_t, char *, size_t)) +{ + handler_envelope_load = cb; +} + +void +queue_api_on_envelope_walk(int(*cb)(uint64_t *, char *, size_t)) +{ + handler_envelope_walk = cb; +} + +void +queue_api_on_message_walk(int(*cb)(uint64_t *, char *, size_t, + uint32_t, int *, void **)) +{ + handler_message_walk = cb; +} diff --git a/usr.sbin/smtpd/queue_fs.c b/usr.sbin/smtpd/queue_fs.c new file mode 100644 index 00000000..097ba1e2 --- /dev/null +++ b/usr.sbin/smtpd/queue_fs.c @@ -0,0 +1,695 @@ +/* $OpenBSD: queue_fs.c,v 1.20 2020/02/25 17:03:13 millert Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#if HAVE_SYS_MOUNT_H +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_SYS_STATFS_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define PATH_QUEUE "/queue" +#define PATH_INCOMING "/incoming" +#define PATH_EVPTMP PATH_INCOMING "/envelope.tmp" +#define PATH_MESSAGE "/message" + +/* percentage of remaining space / inodes required to accept new messages */ +#define MINSPACE 5 +#define MININODES 5 + +struct qwalk { + FTS *fts; + int depth; +}; + +static int fsqueue_check_space(void); +static void fsqueue_envelope_path(uint64_t, char *, size_t); +static void fsqueue_envelope_incoming_path(uint64_t, char *, size_t); +static int fsqueue_envelope_dump(char *, const char *, size_t, int, int); +static void fsqueue_message_path(uint32_t, char *, size_t); +static void fsqueue_message_incoming_path(uint32_t, char *, size_t); +static void *fsqueue_qwalk_new(void); +static int fsqueue_qwalk(void *, uint64_t *); +static void fsqueue_qwalk_close(void *); + +struct tree evpcount; +static struct timespec startup; + +#define REF (int*)0xf00 + +static int +queue_fs_message_create(uint32_t *msgid) +{ + char rootdir[PATH_MAX]; + struct stat sb; + + if (!fsqueue_check_space()) + return 0; + +again: + *msgid = queue_generate_msgid(); + + /* prevent possible collision later when moving to Q_QUEUE */ + fsqueue_message_path(*msgid, rootdir, sizeof(rootdir)); + if (stat(rootdir, &sb) != -1) + goto again; + + /* we hit an unexpected error, temporarily fail */ + if (errno != ENOENT) { + *msgid = 0; + return 0; + } + + fsqueue_message_incoming_path(*msgid, rootdir, sizeof(rootdir)); + if (mkdir(rootdir, 0700) == -1) { + if (errno == EEXIST) + goto again; + + if (errno == ENOSPC) { + *msgid = 0; + return 0; + } + + log_warn("warn: queue-fs: mkdir"); + *msgid = 0; + return 0; + } + + return (1); +} + +static int +queue_fs_message_commit(uint32_t msgid, const char *path) +{ + char incomingdir[PATH_MAX]; + char queuedir[PATH_MAX]; + char msgdir[PATH_MAX]; + char msgpath[PATH_MAX]; + + /* before-first, move the message content in the incoming directory */ + fsqueue_message_incoming_path(msgid, msgpath, sizeof(msgpath)); + if (strlcat(msgpath, PATH_MESSAGE, sizeof(msgpath)) + >= sizeof(msgpath)) + return (0); + if (rename(path, msgpath) == -1) + return (0); + + fsqueue_message_incoming_path(msgid, incomingdir, sizeof(incomingdir)); + fsqueue_message_path(msgid, msgdir, sizeof(msgdir)); + if (strlcpy(queuedir, msgdir, sizeof(queuedir)) + >= sizeof(queuedir)) + return (0); + + /* first attempt to rename */ + if (rename(incomingdir, msgdir) == 0) + return 1; + if (errno == ENOSPC) + return 0; + if (errno != ENOENT) { + log_warn("warn: queue-fs: rename"); + return 0; + } + + /* create the bucket */ + *strrchr(queuedir, '/') = '\0'; + if (mkdir(queuedir, 0700) == -1) { + if (errno == ENOSPC) + return 0; + if (errno != EEXIST) { + log_warn("warn: queue-fs: mkdir"); + return 0; + } + } + + /* rename */ + if (rename(incomingdir, msgdir) == -1) { + if (errno == ENOSPC) + return 0; + log_warn("warn: queue-fs: rename"); + return 0; + } + + return 1; +} + +static int +queue_fs_message_fd_r(uint32_t msgid) +{ + int fd; + char path[PATH_MAX]; + + fsqueue_message_path(msgid, path, sizeof(path)); + if (strlcat(path, PATH_MESSAGE, sizeof(path)) + >= sizeof(path)) + return -1; + + if ((fd = open(path, O_RDONLY)) == -1) { + log_warn("warn: queue-fs: open"); + return -1; + } + + return fd; +} + +static int +queue_fs_message_delete(uint32_t msgid) +{ + char path[PATH_MAX]; + struct stat sb; + + fsqueue_message_incoming_path(msgid, path, sizeof(path)); + if (stat(path, &sb) == -1) + fsqueue_message_path(msgid, path, sizeof(path)); + + if (rmtree(path, 0) == -1) + log_warn("warn: queue-fs: rmtree"); + + tree_pop(&evpcount, msgid); + + return 1; +} + +static int +queue_fs_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + char path[PATH_MAX]; + int queued = 0, i, r = 0, *n; + struct stat sb; + + if (msgid == 0) { + log_warnx("warn: queue-fs: msgid=0, evpid=%016"PRIx64, *evpid); + goto done; + } + + fsqueue_message_incoming_path(msgid, path, sizeof(path)); + if (stat(path, &sb) == -1) + queued = 1; + + for (i = 0; i < 20; i ++) { + *evpid = queue_generate_evpid(msgid); + if (queued) + fsqueue_envelope_path(*evpid, path, sizeof(path)); + else + fsqueue_envelope_incoming_path(*evpid, path, + sizeof(path)); + + if ((r = fsqueue_envelope_dump(path, buf, len, 0, 0)) != 0) + goto done; + } + r = 0; + log_warnx("warn: queue-fs: could not allocate evpid"); + +done: + if (r) { + n = tree_pop(&evpcount, msgid); + if (n == NULL) + n = REF; + n += 1; + tree_xset(&evpcount, msgid, n); + } + return (r); +} + +static int +queue_fs_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + char pathname[PATH_MAX]; + FILE *fp; + size_t r; + + fsqueue_envelope_path(evpid, pathname, sizeof(pathname)); + + fp = fopen(pathname, "r"); + if (fp == NULL) { + if (errno != ENOENT && errno != ENFILE) + log_warn("warn: queue-fs: fopen"); + return 0; + } + + r = fread(buf, 1, len, fp); + if (r) { + if (r == len) { + log_warn("warn: queue-fs: too large"); + r = 0; + } + else + buf[r] = '\0'; + } + fclose(fp); + + return (r); +} + +static int +queue_fs_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + char dest[PATH_MAX]; + + fsqueue_envelope_path(evpid, dest, sizeof(dest)); + + return (fsqueue_envelope_dump(dest, buf, len, 1, 1)); +} + +static int +queue_fs_envelope_delete(uint64_t evpid) +{ + char pathname[PATH_MAX]; + uint32_t msgid; + int *n; + + fsqueue_envelope_path(evpid, pathname, sizeof(pathname)); + if (unlink(pathname) == -1) + if (errno != ENOENT) + return 0; + + msgid = evpid_to_msgid(evpid); + n = tree_pop(&evpcount, msgid); + n -= 1; + + if (n - REF == 0) + queue_fs_message_delete(msgid); + else + tree_xset(&evpcount, msgid, n); + + return (1); +} + +static int +queue_fs_message_walk(uint64_t *evpid, char *buf, size_t len, + uint32_t msgid, int *done, void **data) +{ + struct dirent *dp; + DIR *dir = *data; + char path[PATH_MAX]; + char msgid_str[9]; + char *tmp; + int r, *n; + + if (*done) + return (-1); + + if (!bsnprintf(path, sizeof path, "%s/%02x/%08x", + PATH_QUEUE, (msgid & 0xff000000) >> 24, msgid)) + fatalx("queue_fs_message_walk: path does not fit buffer"); + + if (dir == NULL) { + if ((dir = opendir(path)) == NULL) { + log_warn("warn: queue_fs: opendir: %s", path); + *done = 1; + return (-1); + } + + *data = dir; + } + + (void)snprintf(msgid_str, sizeof msgid_str, "%08" PRIx32, msgid); + while ((dp = readdir(dir)) != NULL) { +#if defined(HAVE_STRUCT_DIR_D_TYPE) + if (dp->d_type != DT_REG) + continue; +#endif + + /* ignore files other than envelopes */ + if (strlen(dp->d_name) != 16 || + strncmp(dp->d_name, msgid_str, 8)) + continue; + + tmp = NULL; + *evpid = strtoull(dp->d_name, &tmp, 16); + if (tmp && *tmp != '\0') { + log_debug("debug: fsqueue: bogus file %s", dp->d_name); + continue; + } + + memset(buf, 0, len); + r = queue_fs_envelope_load(*evpid, buf, len); + if (r) { + n = tree_pop(&evpcount, msgid); + if (n == NULL) + n = REF; + + n += 1; + tree_xset(&evpcount, msgid, n); + } + + return (r); + } + + (void)closedir(dir); + *done = 1; + return (-1); +} + +static int +queue_fs_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + static int done = 0; + static void *hdl = NULL; + int r, *n; + uint32_t msgid; + + if (done) + return (-1); + + if (hdl == NULL) + hdl = fsqueue_qwalk_new(); + + if (fsqueue_qwalk(hdl, evpid)) { + memset(buf, 0, len); + r = queue_fs_envelope_load(*evpid, buf, len); + if (r) { + msgid = evpid_to_msgid(*evpid); + n = tree_pop(&evpcount, msgid); + if (n == NULL) + n = REF; + n += 1; + tree_xset(&evpcount, msgid, n); + } + return (r); + } + + fsqueue_qwalk_close(hdl); + done = 1; + return (-1); +} + +static int +fsqueue_check_space(void) +{ +#ifdef __OpenBSD__ + struct statfs buf; + uint64_t used; + uint64_t total; + + if (statfs(PATH_QUEUE, &buf) == -1) { + log_warn("warn: queue-fs: statfs"); + return 0; + } + + /* + * f_bfree and f_ffree is not set on all filesystems. + * They could be signed or unsigned integers. + * Some systems will set them to 0, others will set them to -1. + */ + if (buf.f_bfree == 0 || buf.f_ffree == 0 || + (int64_t)buf.f_bfree == -1 || (int64_t)buf.f_ffree == -1) + return 1; + + used = buf.f_blocks - buf.f_bfree; + total = buf.f_bavail + used; + if (total != 0) + used = (float)used / (float)total * 100; + else + used = 100; + if (100 - used < MINSPACE) { + log_warnx("warn: not enough disk space: %llu%% left", + (unsigned long long) 100 - used); + log_warnx("warn: temporarily rejecting messages"); + return 0; + } + + used = buf.f_files - buf.f_ffree; + total = buf.f_favail + used; + if (total != 0) + used = (float)used / (float)total * 100; + else + used = 100; + if (100 - used < MININODES) { + log_warnx("warn: not enough inodes: %llu%% left", + (unsigned long long) 100 - used); + log_warnx("warn: temporarily rejecting messages"); + return 0; + } +#endif + return 1; +} + +static void +fsqueue_envelope_path(uint64_t evpid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%02x/%08x/%016" PRIx64, + PATH_QUEUE, + (evpid_to_msgid(evpid) & 0xff000000) >> 24, + evpid_to_msgid(evpid), + evpid)) + fatalx("fsqueue_envelope_path: path does not fit buffer"); +} + +static void +fsqueue_envelope_incoming_path(uint64_t evpid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%08x/%016" PRIx64, + PATH_INCOMING, + evpid_to_msgid(evpid), + evpid)) + fatalx("fsqueue_envelope_incoming_path: path does not fit buffer"); +} + +static int +fsqueue_envelope_dump(char *dest, const char *evpbuf, size_t evplen, + int do_atomic, int do_sync) +{ + const char *path = do_atomic ? PATH_EVPTMP : dest; + FILE *fp = NULL; + int fd; + size_t w; + + if ((fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600)) == -1) { + log_warn("warn: queue-fs: open"); + goto tempfail; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + log_warn("warn: queue-fs: fdopen"); + goto tempfail; + } + + w = fwrite(evpbuf, 1, evplen, fp); + if (w < evplen) { + log_warn("warn: queue-fs: short write"); + goto tempfail; + } + if (fflush(fp)) { + log_warn("warn: queue-fs: fflush"); + goto tempfail; + } + if (do_sync && fsync(fileno(fp))) { + log_warn("warn: queue-fs: fsync"); + goto tempfail; + } + if (fclose(fp) != 0) { + log_warn("warn: queue-fs: fclose"); + fp = NULL; + goto tempfail; + } + fp = NULL; + fd = -1; + + if (do_atomic && rename(path, dest) == -1) { + log_warn("warn: queue-fs: rename"); + goto tempfail; + } + return (1); + +tempfail: + if (fp) + fclose(fp); + else if (fd != -1) + close(fd); + if (unlink(path) == -1) + log_warn("warn: queue-fs: unlink"); + return (0); +} + +static void +fsqueue_message_path(uint32_t msgid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%02x/%08x", + PATH_QUEUE, + (msgid & 0xff000000) >> 24, + msgid)) + fatalx("fsqueue_message_path: path does not fit buffer"); +} + +static void +fsqueue_message_incoming_path(uint32_t msgid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%08x", + PATH_INCOMING, + msgid)) + fatalx("fsqueue_message_incoming_path: path does not fit buffer"); +} + +static void * +fsqueue_qwalk_new(void) +{ + char path[PATH_MAX]; + char * const path_argv[] = { path, NULL }; + struct qwalk *q; + + q = xcalloc(1, sizeof(*q)); + (void)strlcpy(path, PATH_QUEUE, sizeof(path)); + q->fts = fts_open(path_argv, + FTS_PHYSICAL | FTS_NOCHDIR, NULL); + + if (q->fts == NULL) + err(1, "fsqueue_qwalk_new: fts_open: %s", path); + + return (q); +} + +static void +fsqueue_qwalk_close(void *hdl) +{ + struct qwalk *q = hdl; + + fts_close(q->fts); + + free(q); +} + +static int +fsqueue_qwalk(void *hdl, uint64_t *evpid) +{ + struct qwalk *q = hdl; + FTSENT *e; + char *tmp; + + while ((e = fts_read(q->fts)) != NULL) { + switch (e->fts_info) { + case FTS_D: + q->depth += 1; + if (q->depth == 2 && e->fts_namelen != 2) { + log_debug("debug: fsqueue: bogus directory %s", + e->fts_path); + fts_set(q->fts, e, FTS_SKIP); + break; + } + if (q->depth == 3 && e->fts_namelen != 8) { + log_debug("debug: fsqueue: bogus directory %s", + e->fts_path); + fts_set(q->fts, e, FTS_SKIP); + break; + } + break; + + case FTS_DP: + case FTS_DNR: + q->depth -= 1; + break; + + case FTS_F: + if (q->depth != 3) + break; + if (e->fts_namelen != 16) + break; +#if HAVE_STRUCT_STAT_ST_MTIM + if (timespeccmp(&e->fts_statp->st_mtim, &startup, >)) +#endif +#if HAVE_STRUCT_STAT_ST_MTIMSPEC + if (timespeccmp(&e->fts_statp->st_mtimspec, &startup, >)) +#endif + break; + tmp = NULL; + *evpid = strtoull(e->fts_name, &tmp, 16); + if (tmp && *tmp != '\0') { + log_debug("debug: fsqueue: bogus file %s", + e->fts_path); + break; + } + return (1); + default: + break; + } + } + + return (0); +} + +static int +queue_fs_init(struct passwd *pw, int server, const char *conf) +{ + unsigned int n; + char *paths[] = { PATH_QUEUE, PATH_INCOMING }; + char path[PATH_MAX]; + int ret; + + /* remove incoming/ if it exists */ + if (server) + mvpurge(PATH_SPOOL PATH_INCOMING, PATH_SPOOL PATH_PURGE); + + fsqueue_envelope_path(0, path, sizeof(path)); + + ret = 1; + for (n = 0; n < nitems(paths); n++) { + (void)strlcpy(path, PATH_SPOOL, sizeof(path)); + if (strlcat(path, paths[n], sizeof(path)) >= sizeof(path)) + errx(1, "path too long %s%s", PATH_SPOOL, paths[n]); + if (ckdir(path, 0700, pw->pw_uid, 0, server) == 0) + ret = 0; + } + + if (clock_gettime(CLOCK_REALTIME, &startup)) + err(1, "clock_gettime"); + + tree_init(&evpcount); + + queue_api_on_message_create(queue_fs_message_create); + queue_api_on_message_commit(queue_fs_message_commit); + queue_api_on_message_delete(queue_fs_message_delete); + queue_api_on_message_fd_r(queue_fs_message_fd_r); + queue_api_on_envelope_create(queue_fs_envelope_create); + queue_api_on_envelope_delete(queue_fs_envelope_delete); + queue_api_on_envelope_update(queue_fs_envelope_update); + queue_api_on_envelope_load(queue_fs_envelope_load); + queue_api_on_envelope_walk(queue_fs_envelope_walk); + queue_api_on_message_walk(queue_fs_message_walk); + + return (ret); +} + +struct queue_backend queue_backend_fs = { + queue_fs_init, +}; diff --git a/usr.sbin/smtpd/queue_null.c b/usr.sbin/smtpd/queue_null.c new file mode 100644 index 00000000..1e608be8 --- /dev/null +++ b/usr.sbin/smtpd/queue_null.c @@ -0,0 +1,120 @@ +/* $OpenBSD: queue_null.c,v 1.8 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static int +queue_null_message_create(uint32_t *msgid) +{ + *msgid = queue_generate_msgid(); + return (1); +} + +static int +queue_null_message_commit(uint32_t msgid, const char *path) +{ + return (1); +} + +static int +queue_null_message_delete(uint32_t msgid) +{ + return (1); +} + +static int +queue_null_message_fd_r(uint32_t msgid) +{ + return (-1); +} + +static int +queue_null_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + *evpid = queue_generate_evpid(msgid); + return (1); +} + +static int +queue_null_envelope_delete(uint64_t evpid) +{ + return (1); +} + +static int +queue_null_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + return (1); +} + +static int +queue_null_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + return (0); +} + +static int +queue_null_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + return (-1); +} + +static int +queue_null_init(struct passwd *pw, int server, const char *conf) +{ + queue_api_on_message_create(queue_null_message_create); + queue_api_on_message_commit(queue_null_message_commit); + queue_api_on_message_delete(queue_null_message_delete); + queue_api_on_message_fd_r(queue_null_message_fd_r); + queue_api_on_envelope_create(queue_null_envelope_create); + queue_api_on_envelope_delete(queue_null_envelope_delete); + queue_api_on_envelope_update(queue_null_envelope_update); + queue_api_on_envelope_load(queue_null_envelope_load); + queue_api_on_envelope_walk(queue_null_envelope_walk); + + return (1); +} + +struct queue_backend queue_backend_null = { + queue_null_init, +}; diff --git a/usr.sbin/smtpd/queue_proc.c b/usr.sbin/smtpd/queue_proc.c new file mode 100644 index 00000000..d6e0f409 --- /dev/null +++ b/usr.sbin/smtpd/queue_proc.c @@ -0,0 +1,337 @@ +/* $OpenBSD: queue_proc.c,v 1.8 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static struct imsgbuf ibuf; +static struct imsg imsg; +static size_t rlen; +static char *rdata; + +static void +queue_proc_call(void) +{ + ssize_t n; + + if (imsg_flush(&ibuf) == -1) { + log_warn("warn: queue-proc: imsg_flush"); + fatalx("queue-proc: exiting"); + } + + while (1) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) { + log_warn("warn: queue-proc: imsg_get"); + break; + } + if (n) { + rlen = imsg.hdr.len - IMSG_HEADER_SIZE; + rdata = imsg.data; + + if (imsg.hdr.type != PROC_QUEUE_OK) { + log_warnx("warn: queue-proc: bad response"); + break; + } + return; + } + + if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) { + log_warn("warn: queue-proc: imsg_read"); + break; + } + + if (n == 0) { + log_warnx("warn: queue-proc: pipe closed"); + break; + } + } + + fatalx("queue-proc: exiting"); +} + +static void +queue_proc_read(void *dst, size_t len) +{ + if (len > rlen) { + log_warnx("warn: queue-proc: bad msg len"); + fatalx("queue-proc: exiting"); + } + + memmove(dst, rdata, len); + rlen -= len; + rdata += len; +} + +static void +queue_proc_end(void) +{ + if (rlen) { + log_warnx("warn: queue-proc: bogus data"); + fatalx("queue-proc: exiting"); + } + imsg_free(&imsg); +} + +/* + * API + */ + +static int +queue_proc_close(void) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_CLOSE, 0, 0, -1, NULL, 0); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_create(uint32_t *msgid) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_CREATE, 0, 0, -1, NULL, 0); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + if (r == 1) + queue_proc_read(msgid, sizeof(*msgid)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_commit(uint32_t msgid, const char *path) +{ + int r, fd; + + fd = open(path, O_RDONLY); + if (fd == -1) { + log_warn("queue-proc: open: %s", path); + return (0); + } + + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_COMMIT, 0, 0, fd, &msgid, + sizeof(msgid)); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_delete(uint32_t msgid) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_DELETE, 0, 0, -1, &msgid, + sizeof(msgid)); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_fd_r(uint32_t msgid) +{ + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_FD_R, 0, 0, -1, &msgid, + sizeof(msgid)); + + queue_proc_call(); + queue_proc_end(); + + return (imsg.fd); +} + +static int +queue_proc_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + struct ibuf *b; + int r; + + msgid = evpid_to_msgid(*evpid); + b = imsg_create(&ibuf, PROC_QUEUE_ENVELOPE_CREATE, 0, 0, + sizeof(msgid) + len); + if (imsg_add(b, &msgid, sizeof(msgid)) == -1 || + imsg_add(b, buf, len) == -1) + return (0); + imsg_close(&ibuf, b); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + if (r == 1) + queue_proc_read(evpid, sizeof(*evpid)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_delete(uint64_t evpid) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_DELETE, 0, 0, -1, &evpid, + sizeof(evpid)); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + struct ibuf *b; + int r; + + b = imsg_create(&ibuf, PROC_QUEUE_ENVELOPE_UPDATE, 0, 0, + len + sizeof(evpid)); + if (imsg_add(b, &evpid, sizeof(evpid)) == -1 || + imsg_add(b, buf, len) == -1) + return (0); + imsg_close(&ibuf, b); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_LOAD, 0, 0, -1, &evpid, + sizeof(evpid)); + + queue_proc_call(); + + if (rlen > len) { + log_warnx("warn: queue-proc: buf too small"); + fatalx("queue-proc: exiting"); + } + + r = rlen; + queue_proc_read(buf, rlen); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_WALK, 0, 0, -1, NULL, 0); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + + if (r > 0) { + queue_proc_read(evpid, sizeof(*evpid)); + if (rlen > len) { + log_warnx("warn: queue-proc: buf too small"); + fatalx("queue-proc: exiting"); + } + if (r != (int)rlen) { + log_warnx("warn: queue-proc: len mismatch"); + fatalx("queue-proc: exiting"); + } + queue_proc_read(buf, rlen); + } + queue_proc_end(); + + return (r); +} + +static int +queue_proc_init(struct passwd *pw, int server, const char *conf) +{ + uint32_t version; + int fd; + + fd = fork_proc_backend("queue", conf, "queue-proc"); + if (fd == -1) + fatalx("queue-proc: exiting"); + + imsg_init(&ibuf, fd); + + version = PROC_QUEUE_API_VERSION; + imsg_compose(&ibuf, PROC_QUEUE_INIT, 0, 0, -1, + &version, sizeof(version)); + + queue_api_on_close(queue_proc_close); + queue_api_on_message_create(queue_proc_message_create); + queue_api_on_message_commit(queue_proc_message_commit); + queue_api_on_message_delete(queue_proc_message_delete); + queue_api_on_message_fd_r(queue_proc_message_fd_r); + queue_api_on_envelope_create(queue_proc_envelope_create); + queue_api_on_envelope_delete(queue_proc_envelope_delete); + queue_api_on_envelope_update(queue_proc_envelope_update); + queue_api_on_envelope_load(queue_proc_envelope_load); + queue_api_on_envelope_walk(queue_proc_envelope_walk); + + queue_proc_call(); + queue_proc_end(); + + return (1); +} + +struct queue_backend queue_backend_proc = { + queue_proc_init, +}; diff --git a/usr.sbin/smtpd/queue_ram.c b/usr.sbin/smtpd/queue_ram.c new file mode 100644 index 00000000..50ce17e1 --- /dev/null +++ b/usr.sbin/smtpd/queue_ram.c @@ -0,0 +1,336 @@ +/* $OpenBSD: queue_ram.c,v 1.9 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +struct qr_envelope { + char *buf; + size_t len; +}; + +struct qr_message { + char *buf; + size_t len; + struct tree envelopes; +}; + +static struct tree messages; + +static struct qr_message * +get_message(uint32_t msgid) +{ + struct qr_message *msg; + + msg = tree_get(&messages, msgid); + if (msg == NULL) + log_warn("warn: queue-ram: message not found"); + + return (msg); +} + +static int +queue_ram_message_create(uint32_t *msgid) +{ + struct qr_message *msg; + + msg = calloc(1, sizeof(*msg)); + if (msg == NULL) { + log_warn("warn: queue-ram: calloc"); + return (0); + } + tree_init(&msg->envelopes); + + do { + *msgid = queue_generate_msgid(); + } while (tree_check(&messages, *msgid)); + + tree_xset(&messages, *msgid, msg); + + return (1); +} + +static int +queue_ram_message_commit(uint32_t msgid, const char *path) +{ + struct qr_message *msg; + struct stat sb; + size_t n; + FILE *f; + int ret; + + if ((msg = tree_get(&messages, msgid)) == NULL) { + log_warnx("warn: queue-ram: msgid not found"); + return (0); + } + + f = fopen(path, "rb"); + if (f == NULL) { + log_warn("warn: queue-ram: fopen: %s", path); + return (0); + } + if (fstat(fileno(f), &sb) == -1) { + log_warn("warn: queue-ram: fstat"); + fclose(f); + return (0); + } + + msg->len = sb.st_size; + msg->buf = malloc(msg->len); + if (msg->buf == NULL) { + log_warn("warn: queue-ram: malloc"); + fclose(f); + return (0); + } + + ret = 0; + n = fread(msg->buf, 1, msg->len, f); + if (ferror(f)) + log_warn("warn: queue-ram: fread"); + else if ((off_t)n != sb.st_size) + log_warnx("warn: queue-ram: bad read"); + else { + ret = 1; + stat_increment("queue.ram.message.size", msg->len); + } + fclose(f); + + return (ret); +} + +static int +queue_ram_message_delete(uint32_t msgid) +{ + struct qr_message *msg; + struct qr_envelope *evp; + uint64_t evpid; + + if ((msg = tree_pop(&messages, msgid)) == NULL) { + log_warnx("warn: queue-ram: not found"); + return (0); + } + while (tree_poproot(&messages, &evpid, (void**)&evp)) { + stat_decrement("queue.ram.envelope.size", evp->len); + free(evp->buf); + free(evp); + } + stat_decrement("queue.ram.message.size", msg->len); + free(msg->buf); + free(msg); + return (0); +} + +static int +queue_ram_message_fd_r(uint32_t msgid) +{ + struct qr_message *msg; + size_t n; + FILE *f; + int fd, fd2; + + if ((msg = tree_get(&messages, msgid)) == NULL) { + log_warnx("warn: queue-ram: not found"); + return (-1); + } + + fd = mktmpfile(); + if (fd == -1) { + log_warn("warn: queue-ram: mktmpfile"); + return (-1); + } + + fd2 = dup(fd); + if (fd2 == -1) { + log_warn("warn: queue-ram: dup"); + close(fd); + return (-1); + } + f = fdopen(fd2, "w"); + if (f == NULL) { + log_warn("warn: queue-ram: fdopen"); + close(fd); + close(fd2); + return (-1); + } + n = fwrite(msg->buf, 1, msg->len, f); + if (n != msg->len) { + log_warn("warn: queue-ram: write"); + close(fd); + fclose(f); + return (-1); + } + fclose(f); + lseek(fd, 0, SEEK_SET); + return (fd); +} + +static int +queue_ram_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + struct qr_envelope *evp; + struct qr_message *msg; + + if ((msg = get_message(msgid)) == NULL) + return (0); + + do { + *evpid = queue_generate_evpid(msgid); + } while (tree_check(&msg->envelopes, *evpid)); + evp = calloc(1, sizeof *evp); + if (evp == NULL) { + log_warn("warn: queue-ram: calloc"); + return (0); + } + evp->len = len; + evp->buf = malloc(len); + if (evp->buf == NULL) { + log_warn("warn: queue-ram: malloc"); + free(evp); + return (0); + } + memmove(evp->buf, buf, len); + tree_xset(&msg->envelopes, *evpid, evp); + stat_increment("queue.ram.envelope.size", len); + return (1); +} + +static int +queue_ram_envelope_delete(uint64_t evpid) +{ + struct qr_envelope *evp; + struct qr_message *msg; + + if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) + return (0); + + if ((evp = tree_pop(&msg->envelopes, evpid)) == NULL) { + log_warnx("warn: queue-ram: not found"); + return (0); + } + stat_decrement("queue.ram.envelope.size", evp->len); + free(evp->buf); + free(evp); + if (tree_empty(&msg->envelopes)) { + tree_xpop(&messages, evpid_to_msgid(evpid)); + stat_decrement("queue.ram.message.size", msg->len); + free(msg->buf); + free(msg); + } + return (1); +} + +static int +queue_ram_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + struct qr_envelope *evp; + struct qr_message *msg; + void *tmp; + + if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) + return (0); + + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { + log_warn("warn: queue-ram: not found"); + return (0); + } + tmp = malloc(len); + if (tmp == NULL) { + log_warn("warn: queue-ram: malloc"); + return (0); + } + memmove(tmp, buf, len); + free(evp->buf); + evp->len = len; + evp->buf = tmp; + stat_decrement("queue.ram.envelope.size", evp->len); + stat_increment("queue.ram.envelope.size", len); + return (1); +} + +static int +queue_ram_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + struct qr_envelope *evp; + struct qr_message *msg; + + if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) + return (0); + + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { + log_warn("warn: queue-ram: not found"); + return (0); + } + if (len < evp->len) { + log_warnx("warn: queue-ram: buffer too small"); + return (0); + } + memmove(buf, evp->buf, evp->len); + return (evp->len); +} + +static int +queue_ram_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + return (-1); +} + +static int +queue_ram_init(struct passwd *pw, int server, const char * conf) +{ + tree_init(&messages); + + queue_api_on_message_create(queue_ram_message_create); + queue_api_on_message_commit(queue_ram_message_commit); + queue_api_on_message_delete(queue_ram_message_delete); + queue_api_on_message_fd_r(queue_ram_message_fd_r); + queue_api_on_envelope_create(queue_ram_envelope_create); + queue_api_on_envelope_delete(queue_ram_envelope_delete); + queue_api_on_envelope_update(queue_ram_envelope_update); + queue_api_on_envelope_load(queue_ram_envelope_load); + queue_api_on_envelope_walk(queue_ram_envelope_walk); + + return (1); +} + +struct queue_backend queue_backend_ram = { + queue_ram_init, +}; diff --git a/usr.sbin/smtpd/report_smtp.c b/usr.sbin/smtpd/report_smtp.c new file mode 100644 index 00000000..7802eaae --- /dev/null +++ b/usr.sbin/smtpd/report_smtp.c @@ -0,0 +1,335 @@ +/* $OpenBSD: report_smtp.c,v 1.11 2020/01/07 23:03:37 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" +#include "rfc5322.h" + +void +report_smtp_link_connect(const char *direction, uint64_t qid, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_CONNECT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, rdns); + m_add_int(p_lka, fcrdns); + m_add_sockaddr(p_lka, (const struct sockaddr *)ss_src); + m_add_sockaddr(p_lka, (const struct sockaddr *)ss_dest); + m_close(p_lka); +} + +void +report_smtp_link_greeting(const char *direction, uint64_t qid, + const char *domain) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_GREETING, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, domain); + m_close(p_lka); +} + +void +report_smtp_link_identify(const char *direction, uint64_t qid, const char *method, const char *identity) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_IDENTIFY, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, method); + m_add_string(p_lka, identity); + m_close(p_lka); +} + +void +report_smtp_link_tls(const char *direction, uint64_t qid, const char *ssl) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_TLS, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, ssl); + m_close(p_lka); +} + +void +report_smtp_link_disconnect(const char *direction, uint64_t qid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_DISCONNECT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_close(p_lka); +} + +void +report_smtp_link_auth(const char *direction, uint64_t qid, const char *user, const char *result) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_AUTH, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, user); + m_add_string(p_lka, result); + m_close(p_lka); +} + +void +report_smtp_tx_reset(const char *direction, uint64_t qid, uint32_t msgid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_RESET, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_close(p_lka); +} + +void +report_smtp_tx_begin(const char *direction, uint64_t qid, uint32_t msgid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_BEGIN, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_close(p_lka); +} + +void +report_smtp_tx_mail(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_MAIL, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_string(p_lka, address); + m_add_int(p_lka, ok); + m_close(p_lka); +} + +void +report_smtp_tx_rcpt(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_RCPT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_string(p_lka, address); + m_add_int(p_lka, ok); + m_close(p_lka); +} + +void +report_smtp_tx_envelope(const char *direction, uint64_t qid, uint32_t msgid, uint64_t evpid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_ENVELOPE, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_id(p_lka, evpid); + m_close(p_lka); +} + +void +report_smtp_tx_data(const char *direction, uint64_t qid, uint32_t msgid, int ok) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_DATA, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_int(p_lka, ok); + m_close(p_lka); +} + +void +report_smtp_tx_commit(const char *direction, uint64_t qid, uint32_t msgid, size_t msgsz) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_COMMIT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_size(p_lka, msgsz); + m_close(p_lka); +} + +void +report_smtp_tx_rollback(const char *direction, uint64_t qid, uint32_t msgid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_ROLLBACK, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_close(p_lka); +} + +void +report_smtp_protocol_client(const char *direction, uint64_t qid, const char *command) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_PROTOCOL_CLIENT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, command); + m_close(p_lka); +} + +void +report_smtp_protocol_server(const char *direction, uint64_t qid, const char *response) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_PROTOCOL_SERVER, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, response); + m_close(p_lka); +} + +void +report_smtp_filter_response(const char *direction, uint64_t qid, int phase, int response, const char *param) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_FILTER_RESPONSE, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_int(p_lka, phase); + m_add_int(p_lka, response); + m_add_string(p_lka, param); + m_close(p_lka); +} + +void +report_smtp_timeout(const char *direction, uint64_t qid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TIMEOUT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_close(p_lka); +} diff --git a/usr.sbin/smtpd/resolver.c b/usr.sbin/smtpd/resolver.c new file mode 100644 index 00000000..f0f0f8ea --- /dev/null +++ b/usr.sbin/smtpd/resolver.c @@ -0,0 +1,462 @@ +/* $OpenBSD: resolver.c,v 1.5 2019/06/13 11:45:35 eric Exp $ */ + +/* + * Copyright (c) 2017-2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define p_resolver p_lka + +struct request { + SPLAY_ENTRY(request) entry; + uint32_t id; + void (*cb_ai)(void *, int, struct addrinfo *); + void (*cb_ni)(void *, int, const char *, const char *); + void (*cb_res)(void *, int, int, int, const void *, int); + void *arg; + struct addrinfo *ai; +}; + +struct session { + uint32_t reqid; + struct mproc *proc; + char *host; + char *serv; +}; + +SPLAY_HEAD(reqtree, request); + +static void resolver_init(void); +static void resolver_getaddrinfo_cb(struct asr_result *, void *); +static void resolver_getnameinfo_cb(struct asr_result *, void *); +static void resolver_res_query_cb(struct asr_result *, void *); + +static int request_cmp(struct request *, struct request *); +SPLAY_PROTOTYPE(reqtree, request, entry, request_cmp); + +/* musl work-around */ +void portable_freeaddrinfo(struct addrinfo *); + +static struct reqtree reqs; + +void +resolver_getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, void (*cb)(void *, int, struct addrinfo *), + void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, EAI_MEMORY, NULL); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_ai = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_GETADDRINFO, req->id, 0, -1); + m_add_int(p_resolver, hints ? hints->ai_flags : 0); + m_add_int(p_resolver, hints ? hints->ai_family : 0); + m_add_int(p_resolver, hints ? hints->ai_socktype : 0); + m_add_int(p_resolver, hints ? hints->ai_protocol : 0); + m_add_string(p_resolver, hostname); + m_add_string(p_resolver, servname); + m_close(p_resolver); +} + +void +resolver_getnameinfo(const struct sockaddr *sa, int flags, + void(*cb)(void *, int, const char *, const char *), void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, EAI_MEMORY, NULL, NULL); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_ni = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_GETNAMEINFO, req->id, 0, -1); + m_add_sockaddr(p_resolver, sa); + m_add_int(p_resolver, flags); + m_close(p_resolver); +} + +void +resolver_res_query(const char *dname, int class, int type, + void (*cb)(void *, int, int, int, const void *, int), void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, NETDB_INTERNAL, 0, 0, NULL, 0); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_res = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_RES_QUERY, req->id, 0, -1); + m_add_string(p_resolver, dname); + m_add_int(p_resolver, class); + m_add_int(p_resolver, type); + m_close(p_resolver); +} + +void +resolver_dispatch_request(struct mproc *proc, struct imsg *imsg) +{ + const char *hostname, *servname, *dname; + struct session *s; + struct asr_query *q; + struct addrinfo hints; + struct sockaddr_storage ss; + struct sockaddr *sa; + struct msg m; + uint32_t reqid; + int class, type, flags, save_errno; + + reqid = imsg->hdr.peerid; + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + servname = NULL; + memset(&hints, 0 , sizeof(hints)); + m_get_int(&m, &hints.ai_flags); + m_get_int(&m, &hints.ai_family); + m_get_int(&m, &hints.ai_socktype); + m_get_int(&m, &hints.ai_protocol); + m_get_string(&m, &hostname); + m_get_string(&m, &servname); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (q = getaddrinfo_async(hostname, servname, &hints, NULL)) && + (event_asr_run(q, resolver_getaddrinfo_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) + free(s); + + m_create(proc, IMSG_GETADDRINFO_END, reqid, 0, -1); + m_add_int(proc, EAI_SYSTEM); + m_add_int(proc, save_errno); + m_close(proc); + break; + + case IMSG_GETNAMEINFO: + sa = (struct sockaddr*)&ss; + m_get_sockaddr(&m, sa); + m_get_int(&m, &flags); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (s->host = malloc(NI_MAXHOST)) && + (s->serv = malloc(NI_MAXSERV)) && + (q = getnameinfo_async(sa, SA_LEN(sa), s->host, NI_MAXHOST, + s->serv, NI_MAXSERV, flags, NULL)) && + (event_asr_run(q, resolver_getnameinfo_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) { + free(s->host); + free(s->serv); + free(s); + } + + m_create(proc, IMSG_GETNAMEINFO, reqid, 0, -1); + m_add_int(proc, EAI_SYSTEM); + m_add_int(proc, save_errno); + m_add_string(proc, NULL); + m_add_string(proc, NULL); + m_close(proc); + break; + + case IMSG_RES_QUERY: + m_get_string(&m, &dname); + m_get_int(&m, &class); + m_get_int(&m, &type); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (q = res_query_async(dname, class, type, NULL)) && + (event_asr_run(q, resolver_res_query_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) + free(s); + + m_create(proc, IMSG_RES_QUERY, reqid, 0, -1); + m_add_int(proc, NETDB_INTERNAL); + m_add_int(proc, save_errno); + m_add_int(proc, 0); + m_add_int(proc, 0); + m_add_data(proc, NULL, 0); + m_close(proc); + break; + + default: + fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); + } +} + +void +resolver_dispatch_result(struct mproc *proc, struct imsg *imsg) +{ + struct request key, *req; + struct sockaddr_storage ss; + struct addrinfo *ai; + struct msg m; + const char *cname, *host, *serv; + const void *data; + size_t datalen; + int gai_errno, herrno, rcode, count; + + key.id = imsg->hdr.peerid; + req = SPLAY_FIND(reqtree, &reqs, &key); + if (req == NULL) + fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); + + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + ai = calloc(1, sizeof(*ai)); + if (ai == NULL) { + log_warn("%s: calloc", __func__); + break; + } + m_get_int(&m, &ai->ai_flags); + m_get_int(&m, &ai->ai_family); + m_get_int(&m, &ai->ai_socktype); + m_get_int(&m, &ai->ai_protocol); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_get_string(&m, &cname); + m_end(&m); + + ai->ai_addr = malloc(SS_LEN(&ss)); + if (ai->ai_addr == NULL) { + log_warn("%s: malloc", __func__); + free(ai); + break; + } + + memmove(ai->ai_addr, &ss, SS_LEN(&ss)); + + if (cname) { + ai->ai_canonname = strdup(cname); + if (ai->ai_canonname == NULL) { + log_warn("%s: strdup", __func__); + free(ai->ai_addr); + free(ai); + break; + } + } + + ai->ai_next = req->ai; + req->ai = ai; + break; + + case IMSG_GETADDRINFO_END: + m_get_int(&m, &gai_errno); + m_get_int(&m, &errno); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_ai(req->arg, gai_errno, req->ai); + free(req); + break; + + case IMSG_GETNAMEINFO: + m_get_int(&m, &gai_errno); + m_get_int(&m, &errno); + m_get_string(&m, &host); + m_get_string(&m, &serv); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_ni(req->arg, gai_errno, host, serv); + free(req); + break; + + case IMSG_RES_QUERY: + m_get_int(&m, &herrno); + m_get_int(&m, &errno); + m_get_int(&m, &rcode); + m_get_int(&m, &count); + m_get_data(&m, &data, &datalen); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_res(req->arg, herrno, rcode, count, data, datalen); + free(req); + break; + } +} + +static void +resolver_init(void) +{ + static int init = 0; + + if (init == 0) { + SPLAY_INIT(&reqs); + init = 1; + } +} + +static void +resolver_getaddrinfo_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + struct addrinfo *ai; + + for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { + m_create(s->proc, IMSG_GETADDRINFO, s->reqid, 0, -1); + m_add_int(s->proc, ai->ai_flags); + m_add_int(s->proc, ai->ai_family); + m_add_int(s->proc, ai->ai_socktype); + m_add_int(s->proc, ai->ai_protocol); + m_add_sockaddr(s->proc, ai->ai_addr); + m_add_string(s->proc, ai->ai_canonname); + m_close(s->proc); + } + + m_create(s->proc, IMSG_GETADDRINFO_END, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_gai_errno); + m_add_int(s->proc, ar->ar_errno); + m_close(s->proc); + + if (ar->ar_addrinfo) + portable_freeaddrinfo(ar->ar_addrinfo); + free(s); +} + +static void +resolver_getnameinfo_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + + m_create(s->proc, IMSG_GETNAMEINFO, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_gai_errno); + m_add_int(s->proc, ar->ar_errno); + m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->host); + m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->serv); + m_close(s->proc); + + free(s->host); + free(s->serv); + free(s); +} + +static void +resolver_res_query_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + + m_create(s->proc, IMSG_RES_QUERY, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_h_errno); + m_add_int(s->proc, ar->ar_errno); + m_add_int(s->proc, ar->ar_rcode); + m_add_int(s->proc, ar->ar_count); + m_add_data(s->proc, ar->ar_data, ar->ar_datalen); + m_close(s->proc); + + free(ar->ar_data); + free(s); +} + +static int +request_cmp(struct request *a, struct request *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return 0; +} + +SPLAY_GENERATE(reqtree, request, entry, request_cmp); diff --git a/usr.sbin/smtpd/rfc5322.c b/usr.sbin/smtpd/rfc5322.c new file mode 100644 index 00000000..0af66772 --- /dev/null +++ b/usr.sbin/smtpd/rfc5322.c @@ -0,0 +1,266 @@ +/* $OpenBSD: rfc5322.c,v 1.2 2018/10/24 18:59:29 gilles Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include "rfc5322.h" + +struct buf { + char *buf; + size_t bufsz; + size_t buflen; + size_t bufmax; +}; + +static int buf_alloc(struct buf *, size_t); +static int buf_grow(struct buf *, size_t); +static int buf_cat(struct buf *, const char *); + +struct rfc5322_parser { + const char *line; + int state; /* last parser state */ + int next; /* parser needs data */ + int unfold; + const char *currhdr; + struct buf hdr; + struct buf val; +}; + +struct rfc5322_parser * +rfc5322_parser_new(void) +{ + struct rfc5322_parser *parser; + + parser = calloc(1, sizeof(*parser)); + if (parser == NULL) + return NULL; + + rfc5322_clear(parser); + parser->hdr.bufmax = 1024; + parser->val.bufmax = 65536; + + return parser; +} + +void +rfc5322_free(struct rfc5322_parser *parser) +{ + free(parser->hdr.buf); + free(parser->val.buf); + free(parser); +} + +void +rfc5322_clear(struct rfc5322_parser *parser) +{ + parser->line = NULL; + parser->state = RFC5322_NONE; + parser->next = 0; + parser->hdr.buflen = 0; + parser->val.buflen = 0; +} + +int +rfc5322_push(struct rfc5322_parser *parser, const char *line) +{ + if (parser->line) { + errno = EALREADY; + return -1; + } + + parser->line = line; + parser->next = 0; + + return 0; +} + +int +rfc5322_unfold_header(struct rfc5322_parser *parser) +{ + if (parser->unfold) { + errno = EALREADY; + return -1; + } + + if (parser->currhdr == NULL) { + errno = EOPNOTSUPP; + return -1; + } + + if (buf_cat(&parser->val, parser->currhdr) == -1) + return -1; + + parser->currhdr = NULL; + parser->unfold = 1; + + return 0; +} + +static int +_rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) +{ + size_t len; + const char *pos, *line; + + line = parser->line; + + switch(parser->state) { + + case RFC5322_HEADER_START: + case RFC5322_HEADER_CONT: + res->hdr = parser->hdr.buf; + + if (line && (line[0] == ' ' || line[0] == '\t')) { + parser->line = NULL; + parser->next = 1; + if (parser->unfold) { + if (buf_cat(&parser->val, "\n") == -1 || + buf_cat(&parser->val, line) == -1) + return -1; + } + res->value = line; + return RFC5322_HEADER_CONT; + } + + if (parser->unfold) { + parser->val.buflen = 0; + parser->unfold = 0; + res->value = parser->val.buf; + } + return RFC5322_HEADER_END; + + case RFC5322_NONE: + case RFC5322_HEADER_END: + if (line && (pos = strchr(line, ':'))) { + len = pos - line; + if (buf_grow(&parser->hdr, len + 1) == -1) + return -1; + (void)memcpy(parser->hdr.buf, line, len); + parser->hdr.buf[len] = '\0'; + parser->hdr.buflen = len + 1; + parser->line = NULL; + parser->next = 1; + parser->currhdr = pos + 1; + res->hdr = parser->hdr.buf; + res->value = pos + 1; + return RFC5322_HEADER_START; + } + + return RFC5322_END_OF_HEADERS; + + case RFC5322_END_OF_HEADERS: + if (line == NULL) + return RFC5322_END_OF_MESSAGE; + + if (line[0] == '\0') { + parser->line = NULL; + parser->next = 1; + res->value = line; + return RFC5322_BODY_START; + } + + errno = EINVAL; + return -1; + + case RFC5322_BODY_START: + case RFC5322_BODY: + if (line == NULL) + return RFC5322_END_OF_MESSAGE; + + parser->line = NULL; + parser->next = 1; + res->value = line; + return RFC5322_BODY; + + case RFC5322_END_OF_MESSAGE: + errno = ENOMSG; + return -1; + + default: + errno = EINVAL; + return -1; + } +} + +int +rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) +{ + memset(res, 0, sizeof(*res)); + + if (parser->next) + return RFC5322_NONE; + + return (parser->state = _rfc5322_next(parser, res)); +} + +static int +buf_alloc(struct buf *b, size_t need) +{ + char *buf; + size_t alloc; + + if (b->buf && b->bufsz >= need) + return 0; + + if (need >= b->bufmax) { + errno = ERANGE; + return -1; + } + +#define N 256 + alloc = N * (need / N) + ((need % N) ? N : 0); +#undef N + buf = reallocarray(b->buf, alloc, 1); + if (buf == NULL) + return -1; + + b->buf = buf; + b->bufsz = alloc; + + return 0; +} + +static int +buf_grow(struct buf *b, size_t sz) +{ + if (SIZE_T_MAX - b->buflen <= sz) { + errno = ERANGE; + return -1; + } + + return buf_alloc(b, b->buflen + sz); +} + +static int +buf_cat(struct buf *b, const char *s) +{ + size_t len = strlen(s); + + if (buf_grow(b, len + 1) == -1) + return -1; + + (void)memmove(b->buf + b->buflen, s, len + 1); + b->buflen += len; + return 0; +} diff --git a/usr.sbin/smtpd/rfc5322.h b/usr.sbin/smtpd/rfc5322.h new file mode 100644 index 00000000..0979bd4c --- /dev/null +++ b/usr.sbin/smtpd/rfc5322.h @@ -0,0 +1,41 @@ +/* $OpenBSD: rfc5322.h,v 1.1 2018/08/23 10:07:06 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct rfc5322_result { + const char *hdr; + const char *value; +}; + +#define RFC5322_ERR -1 +#define RFC5322_NONE 0 +#define RFC5322_HEADER_START 1 +#define RFC5322_HEADER_CONT 2 +#define RFC5322_HEADER_END 3 +#define RFC5322_END_OF_HEADERS 4 +#define RFC5322_BODY_START 5 +#define RFC5322_BODY 6 +#define RFC5322_END_OF_MESSAGE 7 + +struct rfc5322_parser; + +struct rfc5322_parser *rfc5322_parser_new(void); +void rfc5322_free(struct rfc5322_parser *); +void rfc5322_clear(struct rfc5322_parser *); +int rfc5322_push(struct rfc5322_parser *, const char *); +int rfc5322_next(struct rfc5322_parser *, struct rfc5322_result *); +int rfc5322_unfold_header(struct rfc5322_parser *); diff --git a/usr.sbin/smtpd/ruleset.c b/usr.sbin/smtpd/ruleset.c new file mode 100644 index 00000000..719a2913 --- /dev/null +++ b/usr.sbin/smtpd/ruleset.c @@ -0,0 +1,265 @@ +/* $OpenBSD: ruleset.c,v 1.47 2019/11/25 14:18:33 gilles Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +#define MATCH_RESULT(r, neg) ((r) == -1 ? -1 : ((neg) < 0 ? !(r) : (r))) + +static int +ruleset_match_tag(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service = K_STRING; + + if (!r->flag_tag) + return 1; + + if (r->flag_tag_regex) + service = K_REGEX; + + table = table_find(env, r->table_tag); + ret = table_match(table, service, evp->tag); + + return MATCH_RESULT(ret, r->flag_tag); +} + +static int +ruleset_match_from(struct rule *r, const struct envelope *evp) +{ + int ret; + int has_rdns; + const char *key; + struct table *table; + enum table_service service = K_NETADDR; + + if (!r->flag_from) + return 1; + + if (evp->flags & EF_INTERNAL) { + /* if expanded from an empty table_from, skip rule + * if no table + */ + if (r->table_from == NULL) + return 0; + key = "local"; + } + else if (r->flag_from_rdns) { + has_rdns = strcmp(evp->hostname, "") != 0; + if (r->table_from == NULL) + return MATCH_RESULT(has_rdns, r->flag_from); + if (!has_rdns) + return 0; + key = evp->hostname; + } + else { + key = ss_to_text(&evp->ss); + if (r->flag_from_socket) { + if (strcmp(key, "local") == 0) + return MATCH_RESULT(1, r->flag_from); + else + return r->flag_from < 0 ? 1 : 0; + } + } + if (r->flag_from_regex) + service = K_REGEX; + + table = table_find(env, r->table_from); + ret = table_match(table, service, key); + + return MATCH_RESULT(ret, r->flag_from); +} + +static int +ruleset_match_to(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service = K_DOMAIN; + + if (!r->flag_for) + return 1; + + if (r->flag_for_regex) + service = K_REGEX; + + table = table_find(env, r->table_for); + ret = table_match(table, service, evp->dest.domain); + + return MATCH_RESULT(ret, r->flag_for); +} + +static int +ruleset_match_smtp_helo(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service = K_DOMAIN; + + if (!r->flag_smtp_helo) + return 1; + + if (r->flag_smtp_helo_regex) + service = K_REGEX; + + table = table_find(env, r->table_smtp_helo); + ret = table_match(table, service, evp->helo); + + return MATCH_RESULT(ret, r->flag_smtp_helo); +} + +static int +ruleset_match_smtp_starttls(struct rule *r, const struct envelope *evp) +{ + if (!r->flag_smtp_starttls) + return 1; + + /* XXX - not until TLS flag is added to envelope */ + return -1; +} + +static int +ruleset_match_smtp_auth(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service; + + if (!r->flag_smtp_auth) + return 1; + + if (!(evp->flags & EF_AUTHENTICATED)) + ret = 0; + else if (r->table_smtp_auth) { + + if (r->flag_smtp_auth_regex) + service = K_REGEX; + else + service = strchr(evp->username, '@') ? + K_MAILADDR : K_STRING; + table = table_find(env, r->table_smtp_auth); + ret = table_match(table, service, evp->username); + } + else + ret = 1; + + return MATCH_RESULT(ret, r->flag_smtp_auth); +} + +static int +ruleset_match_smtp_mail_from(struct rule *r, const struct envelope *evp) +{ + int ret; + const char *key; + struct table *table; + enum table_service service = K_MAILADDR; + + if (!r->flag_smtp_mail_from) + return 1; + + if (r->flag_smtp_mail_from_regex) + service = K_REGEX; + + if ((key = mailaddr_to_text(&evp->sender)) == NULL) + return -1; + + table = table_find(env, r->table_smtp_mail_from); + ret = table_match(table, service, key); + + return MATCH_RESULT(ret, r->flag_smtp_mail_from); +} + +static int +ruleset_match_smtp_rcpt_to(struct rule *r, const struct envelope *evp) +{ + int ret; + const char *key; + struct table *table; + enum table_service service = K_MAILADDR; + + if (!r->flag_smtp_rcpt_to) + return 1; + + if (r->flag_smtp_rcpt_to_regex) + service = K_REGEX; + + if ((key = mailaddr_to_text(&evp->dest)) == NULL) + return -1; + + table = table_find(env, r->table_smtp_rcpt_to); + ret = table_match(table, service, key); + + return MATCH_RESULT(ret, r->flag_smtp_rcpt_to); +} + +struct rule * +ruleset_match(const struct envelope *evp) +{ + struct rule *r; + int i = 0; + +#define MATCH_EVAL(x) \ + switch ((x)) { \ + case -1: goto tempfail; \ + case 0: continue; \ + default: break; \ + } + TAILQ_FOREACH(r, env->sc_rules, r_entry) { + ++i; + MATCH_EVAL(ruleset_match_tag(r, evp)); + MATCH_EVAL(ruleset_match_from(r, evp)); + MATCH_EVAL(ruleset_match_to(r, evp)); + MATCH_EVAL(ruleset_match_smtp_helo(r, evp)); + MATCH_EVAL(ruleset_match_smtp_auth(r, evp)); + MATCH_EVAL(ruleset_match_smtp_starttls(r, evp)); + MATCH_EVAL(ruleset_match_smtp_mail_from(r, evp)); + MATCH_EVAL(ruleset_match_smtp_rcpt_to(r, evp)); + goto matched; + } +#undef MATCH_EVAL + + errno = 0; + log_trace(TRACE_RULES, "no rule matched"); + return (NULL); + +tempfail: + errno = EAGAIN; + log_trace(TRACE_RULES, "temporary failure in processing of a rule"); + return (NULL); + +matched: + log_trace(TRACE_RULES, "rule #%d matched: %s", i, rule_to_text(r)); + return r; +} diff --git a/usr.sbin/smtpd/runq.c b/usr.sbin/smtpd/runq.c new file mode 100644 index 00000000..786d36fb --- /dev/null +++ b/usr.sbin/smtpd/runq.c @@ -0,0 +1,183 @@ +/* $OpenBSD: runq.c,v 1.3 2019/06/14 19:55:25 eric Exp $ */ + +/* + * Copyright (c) 2013,2019 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "smtpd.h" + +struct job { + TAILQ_ENTRY(job) entry; + time_t when; + void *arg; +}; + +struct runq { + TAILQ_HEAD(, job) jobs; + void (*cb)(struct runq *, void *); + struct event ev; +}; + +static void runq_timeout(int, short, void *); + +static struct runq *active; + +static void +runq_reset(struct runq *runq) +{ + struct timeval tv; + struct job *job; + time_t now; + + job = TAILQ_FIRST(&runq->jobs); + if (job == NULL) + return; + + now = time(NULL); + if (job->when <= now) + tv.tv_sec = 0; + else + tv.tv_sec = job->when - now; + tv.tv_usec = 0; + evtimer_add(&runq->ev, &tv); +} + +static void +runq_timeout(int fd, short ev, void *arg) +{ + struct runq *runq = arg; + struct job *job; + time_t now; + + active = runq; + now = time(NULL); + + while((job = TAILQ_FIRST(&runq->jobs))) { + if (job->when > now) + break; + TAILQ_REMOVE(&runq->jobs, job, entry); + runq->cb(runq, job->arg); + free(job); + } + + active = NULL; + runq_reset(runq); +} + +int +runq_init(struct runq **runqp, void (*cb)(struct runq *, void *)) +{ + struct runq *runq; + + runq = malloc(sizeof(*runq)); + if (runq == NULL) + return (0); + + runq->cb = cb; + TAILQ_INIT(&runq->jobs); + evtimer_set(&runq->ev, runq_timeout, runq); + + *runqp = runq; + + return (1); +} + +int +runq_schedule(struct runq *runq, time_t delay, void *arg) +{ + time_t t; + + time(&t); + return runq_schedule_at(runq, t + delay, arg); +} + +int +runq_schedule_at(struct runq *runq, time_t when, void *arg) +{ + struct job *job, *tmpjob; + + job = malloc(sizeof(*job)); + if (job == NULL) + return (0); + + job->arg = arg; + job->when = when; + + TAILQ_FOREACH(tmpjob, &runq->jobs, entry) { + if (tmpjob->when > job->when) { + TAILQ_INSERT_BEFORE(tmpjob, job, entry); + goto done; + } + } + TAILQ_INSERT_TAIL(&runq->jobs, job, entry); + + done: + if (runq != active && job == TAILQ_FIRST(&runq->jobs)) { + evtimer_del(&runq->ev); + runq_reset(runq); + } + return (1); +} + +int +runq_cancel(struct runq *runq, void *arg) +{ + struct job *job, *first; + + first = TAILQ_FIRST(&runq->jobs); + TAILQ_FOREACH(job, &runq->jobs, entry) { + if (job->arg == arg) { + TAILQ_REMOVE(&runq->jobs, job, entry); + free(job); + if (runq != active && job == first) { + evtimer_del(&runq->ev); + runq_reset(runq); + } + return (1); + } + } + + return (0); +} + +int +runq_pending(struct runq *runq, void *arg, time_t *when) +{ + struct job *job; + + TAILQ_FOREACH(job, &runq->jobs, entry) { + if (job->arg == arg) { + if (when) + *when = job->when; + return (1); + } + } + + return (0); +} diff --git a/usr.sbin/smtpd/scheduler.c b/usr.sbin/smtpd/scheduler.c new file mode 100644 index 00000000..ea70a83d --- /dev/null +++ b/usr.sbin/smtpd/scheduler.c @@ -0,0 +1,618 @@ +/* $OpenBSD: scheduler.c,v 1.60 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008-2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static void scheduler_imsg(struct mproc *, struct imsg *); +static void scheduler_shutdown(void); +static void scheduler_reset_events(void); +static void scheduler_timeout(int, short, void *); + +static struct scheduler_backend *backend = NULL; +static struct event ev; +static size_t ninflight = 0; +static int *types; +static uint64_t *evpids; +static uint32_t *msgids; +static struct evpstate *state; + +extern const char *backend_scheduler; + +void +scheduler_imsg(struct mproc *p, struct imsg *imsg) +{ + struct bounce_req_msg req; + struct envelope evp; + struct scheduler_info si; + struct msg m; + uint64_t evpid, id, holdq; + uint32_t msgid; + uint32_t inflight; + size_t n, i; + time_t timestamp; + int v, r, type; + + if (imsg == NULL) + scheduler_shutdown(); + + switch (imsg->hdr.type) { + + case IMSG_QUEUE_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: inserting evp:%016" PRIx64, evp.id); + scheduler_info(&si, &evp); + stat_increment("scheduler.envelope.incoming", 1); + backend->insert(&si); + return; + + case IMSG_QUEUE_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: committing msg:%08" PRIx32, msgid); + n = backend->commit(msgid); + stat_decrement("scheduler.envelope.incoming", n); + stat_increment("scheduler.envelope", n); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DISCOVER_EVPID: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + r = backend->query(evp.id); + if (r) { + log_debug("debug: scheduler: evp:%016" PRIx64 + " already scheduled", evp.id); + return; + } + log_trace(TRACE_SCHEDULER, + "scheduler: discovering evp:%016" PRIx64, evp.id); + scheduler_info(&si, &evp); + stat_increment("scheduler.envelope.incoming", 1); + backend->insert(&si); + return; + + case IMSG_QUEUE_DISCOVER_MSGID: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + r = backend->query(msgid); + if (r) { + log_debug("debug: scheduler: msgid:%08" PRIx32 + " already scheduled", msgid); + return; + } + log_trace(TRACE_SCHEDULER, + "scheduler: committing msg:%08" PRIx32, msgid); + n = backend->commit(msgid); + stat_decrement("scheduler.envelope.incoming", n); + stat_increment("scheduler.envelope", n); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_MESSAGE_ROLLBACK: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + log_trace(TRACE_SCHEDULER, "scheduler: aborting msg:%08" PRIx32, + msgid); + n = backend->rollback(msgid); + stat_decrement("scheduler.envelope.incoming", n); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_ENVELOPE_REMOVE: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_u32(&m, &inflight); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: queue requested removal of evp:%016" PRIx64, + evpid); + stat_decrement("scheduler.envelope", 1); + if (!inflight) + backend->remove(evpid); + else { + backend->delete(evpid); + ninflight -= 1; + stat_decrement("scheduler.envelope.inflight", 1); + } + + scheduler_reset_events(); + return; + + case IMSG_QUEUE_ENVELOPE_ACK: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: queue ack removal of evp:%016" PRIx64, + evpid); + ninflight -= 1; + stat_decrement("scheduler.envelope.inflight", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_OK: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: deleting evp:%016" PRIx64 " (ok)", evpid); + backend->delete(evpid); + ninflight -= 1; + stat_increment("scheduler.delivery.ok", 1); + stat_decrement("scheduler.envelope.inflight", 1); + stat_decrement("scheduler.envelope", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_TEMPFAIL: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: updating evp:%016" PRIx64, evp.id); + scheduler_info(&si, &evp); + backend->update(&si); + ninflight -= 1; + stat_increment("scheduler.delivery.tempfail", 1); + stat_decrement("scheduler.envelope.inflight", 1); + + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (env->sc_bounce_warn[i] == 0) + break; + timestamp = si.creation + env->sc_bounce_warn[i]; + if (si.nexttry >= timestamp && + si.lastbounce < timestamp) { + req.evpid = evp.id; + req.timestamp = timestamp; + req.bounce.type = B_DELAYED; + req.bounce.delay = env->sc_bounce_warn[i]; + req.bounce.ttl = si.ttl; + m_compose(p, IMSG_SCHED_ENVELOPE_BOUNCE, 0, 0, -1, + &req, sizeof req); + break; + } + } + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_PERMFAIL: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: deleting evp:%016" PRIx64 " (fail)", evpid); + backend->delete(evpid); + ninflight -= 1; + stat_increment("scheduler.delivery.permfail", 1); + stat_decrement("scheduler.envelope.inflight", 1); + stat_decrement("scheduler.envelope", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_LOOP: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: deleting evp:%016" PRIx64 " (loop)", evpid); + backend->delete(evpid); + ninflight -= 1; + stat_increment("scheduler.delivery.loop", 1); + stat_decrement("scheduler.envelope.inflight", 1); + stat_decrement("scheduler.envelope", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_HOLDQ_HOLD: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_id(&m, &holdq); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: holding evp:%016" PRIx64 " on %016" PRIx64, + evpid, holdq); + backend->hold(evpid, holdq); + ninflight -= 1; + stat_decrement("scheduler.envelope.inflight", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_HOLDQ_RELEASE: + m_msg(&m, imsg); + m_get_int(&m, &type); + m_get_id(&m, &holdq); + m_get_int(&m, &r); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: releasing %d on holdq (%d, %016" PRIx64 ")", + r, type, holdq); + backend->release(type, holdq, r); + scheduler_reset_events(); + return; + + case IMSG_CTL_PAUSE_MDA: + log_trace(TRACE_SCHEDULER, "scheduler: pausing mda"); + env->sc_flags |= SMTPD_MDA_PAUSED; + return; + + case IMSG_CTL_RESUME_MDA: + log_trace(TRACE_SCHEDULER, "scheduler: resuming mda"); + env->sc_flags &= ~SMTPD_MDA_PAUSED; + scheduler_reset_events(); + return; + + case IMSG_CTL_PAUSE_MTA: + log_trace(TRACE_SCHEDULER, "scheduler: pausing mta"); + env->sc_flags |= SMTPD_MTA_PAUSED; + return; + + case IMSG_CTL_RESUME_MTA: + log_trace(TRACE_SCHEDULER, "scheduler: resuming mta"); + env->sc_flags &= ~SMTPD_MTA_PAUSED; + scheduler_reset_events(); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_setverbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CTL_LIST_MESSAGES: + msgid = *(uint32_t *)(imsg->data); + n = backend->messages(msgid, msgids, env->sc_scheduler_max_msg_batch_size); + m_compose(p, IMSG_CTL_LIST_MESSAGES, imsg->hdr.peerid, 0, -1, + msgids, n * sizeof (*msgids)); + return; + + case IMSG_CTL_LIST_ENVELOPES: + id = *(uint64_t *)(imsg->data); + n = backend->envelopes(id, state, env->sc_scheduler_max_evp_batch_size); + for (i = 0; i < n; i++) { + m_create(p_queue, IMSG_CTL_LIST_ENVELOPES, + imsg->hdr.peerid, 0, -1); + m_add_evpid(p_queue, state[i].evpid); + m_add_int(p_queue, state[i].flags); + m_add_time(p_queue, state[i].time); + m_close(p_queue); + } + m_compose(p_queue, IMSG_CTL_LIST_ENVELOPES, + imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_CTL_SCHEDULE: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "scheduling msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "scheduling evp:%016" PRIx64, id); + r = backend->schedule(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_QUEUE_ENVELOPE_SCHEDULE: + id = *(uint64_t *)(imsg->data); + backend->schedule(id); + scheduler_reset_events(); + return; + + case IMSG_CTL_REMOVE: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "removing msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "removing evp:%016" PRIx64, id); + r = backend->remove(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_EVP: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "suspending msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "suspending evp:%016" PRIx64, id); + r = backend->suspend(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_EVP: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "resuming msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "resuming evp:%016" PRIx64, id); + r = backend->resume(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + } + + errx(1, "scheduler_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); +} + +static void +scheduler_shutdown(void) +{ + log_debug("debug: scheduler agent exiting"); + _exit(0); +} + +static void +scheduler_reset_events(void) +{ + struct timeval tv; + + evtimer_del(&ev); + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev, &tv); +} + +int +scheduler(void) +{ + struct passwd *pw; + + backend = scheduler_backend_lookup(backend_scheduler); + if (backend == NULL) + errx(1, "cannot find scheduler backend \"%s\"", + backend_scheduler); + + purge_config(PURGE_EVERYTHING & ~PURGE_DISPATCHERS); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + config_process(PROC_SCHEDULER); + + backend->init(backend_scheduler); + + if (chroot(PATH_CHROOT) == -1) + fatal("scheduler: chroot"); + if (chdir("/") == -1) + fatal("scheduler: chdir(\"/\")"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("scheduler: cannot drop privileges"); + + evpids = xcalloc(env->sc_scheduler_max_schedule, sizeof *evpids); + types = xcalloc(env->sc_scheduler_max_schedule, sizeof *types); + msgids = xcalloc(env->sc_scheduler_max_msg_batch_size, sizeof *msgids); + state = xcalloc(env->sc_scheduler_max_evp_batch_size, sizeof *state); + + imsg_callback = scheduler_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_CONTROL); + config_peer(PROC_QUEUE); + + evtimer_set(&ev, scheduler_timeout, NULL); + scheduler_reset_events(); + +#if HAVE_PLEDGE + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +scheduler_timeout(int fd, short event, void *p) +{ + struct timeval tv; + size_t i; + size_t d_inflight; + size_t d_envelope; + size_t d_removed; + size_t d_expired; + size_t d_updated; + size_t count; + int mask, r, delay; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + mask = SCHED_UPDATE; + + if (ninflight < env->sc_scheduler_max_inflight) { + mask |= SCHED_EXPIRE | SCHED_REMOVE | SCHED_BOUNCE; + if (!(env->sc_flags & SMTPD_MDA_PAUSED)) + mask |= SCHED_MDA; + if (!(env->sc_flags & SMTPD_MTA_PAUSED)) + mask |= SCHED_MTA; + } + + count = env->sc_scheduler_max_schedule; + + log_trace(TRACE_SCHEDULER, "scheduler: getting batch: mask=0x%x, count=%zu", mask, count); + + r = backend->batch(mask, &delay, &count, evpids, types); + + log_trace(TRACE_SCHEDULER, "scheduler: got r=%i, delay=%i, count=%zu", r, delay, count); + + if (r < 0) + fatalx("scheduler: error in batch handler"); + + if (r == 0) { + + if (delay < -1) + fatalx("scheduler: invalid delay %d", delay); + + if (delay == -1) { + log_trace(TRACE_SCHEDULER, "scheduler: sleeping"); + return; + } + + tv.tv_sec = delay; + tv.tv_usec = 0; + log_trace(TRACE_SCHEDULER, + "scheduler: waiting for %s", duration_to_text(tv.tv_sec)); + evtimer_add(&ev, &tv); + return; + } + + d_inflight = 0; + d_envelope = 0; + d_removed = 0; + d_expired = 0; + d_updated = 0; + + for (i = 0; i < count; i++) { + switch(types[i]) { + case SCHED_REMOVE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " removed", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_envelope += 1; + d_removed += 1; + d_inflight += 1; + break; + + case SCHED_EXPIRE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " expired", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_EXPIRE, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_envelope += 1; + d_expired += 1; + d_inflight += 1; + break; + + case SCHED_UPDATE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (update)", evpids[i]); + d_updated += 1; + break; + + case SCHED_BOUNCE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (bounce)", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_INJECT, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_inflight += 1; + break; + + case SCHED_MDA: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (mda)", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_DELIVER, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_inflight += 1; + break; + + case SCHED_MTA: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (mta)", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_TRANSFER, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_inflight += 1; + break; + } + } + + stat_decrement("scheduler.envelope", d_envelope); + stat_increment("scheduler.envelope.inflight", d_inflight); + stat_increment("scheduler.envelope.expired", d_expired); + stat_increment("scheduler.envelope.removed", d_removed); + stat_increment("scheduler.envelope.updated", d_updated); + + ninflight += d_inflight; + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev, &tv); +} diff --git a/usr.sbin/smtpd/scheduler_backend.c b/usr.sbin/smtpd/scheduler_backend.c new file mode 100644 index 00000000..ad2b4cab --- /dev/null +++ b/usr.sbin/smtpd/scheduler_backend.c @@ -0,0 +1,82 @@ +/* $OpenBSD: scheduler_backend.c,v 1.16 2018/05/24 11:38:24 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +extern struct scheduler_backend scheduler_backend_null; +extern struct scheduler_backend scheduler_backend_proc; +extern struct scheduler_backend scheduler_backend_ramqueue; + +struct scheduler_backend * +scheduler_backend_lookup(const char *name) +{ + if (!strcmp(name, "null")) + return &scheduler_backend_null; + if (!strcmp(name, "ramqueue")) + return &scheduler_backend_ramqueue; + + return &scheduler_backend_proc; +} + +void +scheduler_info(struct scheduler_info *sched, struct envelope *evp) +{ + struct dispatcher *disp; + + disp = evp->type == D_BOUNCE ? + env->sc_dispatcher_bounce : + dict_xget(env->sc_dispatchers, evp->dispatcher); + + switch (disp->type) { + case DISPATCHER_LOCAL: + sched->type = D_MDA; + break; + case DISPATCHER_REMOTE: + sched->type = D_MTA; + break; + case DISPATCHER_BOUNCE: + sched->type = D_BOUNCE; + break; + } + sched->ttl = disp->ttl ? disp->ttl : env->sc_ttl; + + sched->evpid = evp->id; + sched->creation = evp->creation; + sched->retry = evp->retry; + sched->lasttry = evp->lasttry; + sched->lastbounce = evp->lastbounce; + sched->nexttry = 0; +} diff --git a/usr.sbin/smtpd/scheduler_null.c b/usr.sbin/smtpd/scheduler_null.c new file mode 100644 index 00000000..40db6205 --- /dev/null +++ b/usr.sbin/smtpd/scheduler_null.c @@ -0,0 +1,164 @@ +/* $OpenBSD: scheduler_null.c,v 1.9 2015/01/20 17:37:54 deraadt Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" + +static int scheduler_null_init(const char *); +static int scheduler_null_insert(struct scheduler_info *); +static size_t scheduler_null_commit(uint32_t); +static size_t scheduler_null_rollback(uint32_t); +static int scheduler_null_update(struct scheduler_info *); +static int scheduler_null_delete(uint64_t); +static int scheduler_null_hold(uint64_t, uint64_t); +static int scheduler_null_release(int, uint64_t, int); +static int scheduler_null_batch(int, int*, size_t*, uint64_t*, int*); +static size_t scheduler_null_messages(uint32_t, uint32_t *, size_t); +static size_t scheduler_null_envelopes(uint64_t, struct evpstate *, size_t); +static int scheduler_null_schedule(uint64_t); +static int scheduler_null_remove(uint64_t); +static int scheduler_null_suspend(uint64_t); +static int scheduler_null_resume(uint64_t); + +struct scheduler_backend scheduler_backend_null = { + scheduler_null_init, + + scheduler_null_insert, + scheduler_null_commit, + scheduler_null_rollback, + + scheduler_null_update, + scheduler_null_delete, + scheduler_null_hold, + scheduler_null_release, + + scheduler_null_batch, + + scheduler_null_messages, + scheduler_null_envelopes, + scheduler_null_schedule, + scheduler_null_remove, + scheduler_null_suspend, + scheduler_null_resume, +}; + +static int +scheduler_null_init(const char *arg) +{ + return (1); +} + +static int +scheduler_null_insert(struct scheduler_info *si) +{ + return (0); +} + +static size_t +scheduler_null_commit(uint32_t msgid) +{ + return (0); +} + +static size_t +scheduler_null_rollback(uint32_t msgid) +{ + return (0); +} + +static int +scheduler_null_update(struct scheduler_info *si) +{ + return (0); +} + +static int +scheduler_null_delete(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_hold(uint64_t evpid, uint64_t holdq) +{ + return (0); +} + +static int +scheduler_null_release(int type, uint64_t holdq, int n) +{ + return (0); +} + +static int +scheduler_null_batch(int typemask, int *delay, size_t *count, uint64_t *evpids, int *types) +{ + *delay = 0; + + return (0); +} + +static int +scheduler_null_schedule(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_remove(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_suspend(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_resume(uint64_t evpid) +{ + return (0); +} + +static size_t +scheduler_null_messages(uint32_t from, uint32_t *dst, size_t size) +{ + return (0); +} + +static size_t +scheduler_null_envelopes(uint64_t from, struct evpstate *dst, size_t size) +{ + return (0); +} diff --git a/usr.sbin/smtpd/scheduler_proc.c b/usr.sbin/smtpd/scheduler_proc.c new file mode 100644 index 00000000..5f4e8b70 --- /dev/null +++ b/usr.sbin/smtpd/scheduler_proc.c @@ -0,0 +1,446 @@ +/* $OpenBSD: scheduler_proc.c,v 1.8 2015/12/05 13:14:21 claudio Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static struct imsgbuf ibuf; +static struct imsg imsg; +static size_t rlen; +static char *rdata; + +static void +scheduler_proc_call(void) +{ + ssize_t n; + + if (imsg_flush(&ibuf) == -1) { + log_warn("warn: scheduler-proc: imsg_flush"); + fatalx("scheduler-proc: exiting"); + } + + while (1) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) { + log_warn("warn: scheduler-proc: imsg_get"); + break; + } + if (n) { + rlen = imsg.hdr.len - IMSG_HEADER_SIZE; + rdata = imsg.data; + + if (imsg.hdr.type != PROC_SCHEDULER_OK) { + log_warnx("warn: scheduler-proc: bad response"); + break; + } + return; + } + + if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) { + log_warn("warn: scheduler-proc: imsg_read"); + break; + } + + if (n == 0) { + log_warnx("warn: scheduler-proc: pipe closed"); + break; + } + } + + fatalx("scheduler-proc: exiting"); +} + +static void +scheduler_proc_read(void *dst, size_t len) +{ + if (len > rlen) { + log_warnx("warn: scheduler-proc: bad msg len"); + fatalx("scheduler-proc: exiting"); + } + + memmove(dst, rdata, len); + rlen -= len; + rdata += len; +} + +static void +scheduler_proc_end(void) +{ + if (rlen) { + log_warnx("warn: scheduler-proc: bogus data"); + fatalx("scheduler-proc: exiting"); + } + imsg_free(&imsg); +} + +/* + * API + */ + +static int +scheduler_proc_init(const char *conf) +{ + int fd, r; + uint32_t version; + + fd = fork_proc_backend("scheduler", conf, "scheduler-proc"); + if (fd == -1) + fatalx("scheduler-proc: exiting"); + + imsg_init(&ibuf, fd); + + version = PROC_SCHEDULER_API_VERSION; + imsg_compose(&ibuf, PROC_SCHEDULER_INIT, 0, 0, -1, + &version, sizeof(version)); + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (1); +} + +static int +scheduler_proc_insert(struct scheduler_info *si) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_INSERT"); + + imsg_compose(&ibuf, PROC_SCHEDULER_INSERT, 0, 0, -1, si, sizeof(*si)); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static size_t +scheduler_proc_commit(uint32_t msgid) +{ + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_COMMIT"); + + imsg_compose(&ibuf, PROC_SCHEDULER_COMMIT, 0, 0, -1, + &msgid, sizeof(msgid)); + + scheduler_proc_call(); + scheduler_proc_read(&s, sizeof(s)); + scheduler_proc_end(); + + return (s); +} + +static size_t +scheduler_proc_rollback(uint32_t msgid) +{ + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_ROLLBACK"); + + imsg_compose(&ibuf, PROC_SCHEDULER_ROLLBACK, 0, 0, -1, + &msgid, sizeof(msgid)); + + scheduler_proc_call(); + scheduler_proc_read(&s, sizeof(s)); + scheduler_proc_end(); + + return (s); +} + +static int +scheduler_proc_update(struct scheduler_info *si) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_UPDATE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_UPDATE, 0, 0, -1, si, sizeof(*si)); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + if (r == 1) + scheduler_proc_read(si, sizeof(*si)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_delete(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_DELETE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_DELETE, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_hold(uint64_t evpid, uint64_t holdq) +{ + struct ibuf *buf; + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_HOLD"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_HOLD, 0, 0, + sizeof(evpid) + sizeof(holdq)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &evpid, sizeof(evpid)) == -1) + return (-1); + if (imsg_add(buf, &holdq, sizeof(holdq)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_release(int type, uint64_t holdq, int n) +{ + struct ibuf *buf; + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_RELEASE"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_RELEASE, 0, 0, + sizeof(holdq) + sizeof(n)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &type, sizeof(type)) == -1) + return (-1); + if (imsg_add(buf, &holdq, sizeof(holdq)) == -1) + return (-1); + if (imsg_add(buf, &n, sizeof(n)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_batch(int typemask, int *delay, size_t *count, uint64_t *evpids, int *types) +{ + struct ibuf *buf; + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_BATCH"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_BATCH, 0, 0, + sizeof(typemask) + sizeof(*count)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &typemask, sizeof(typemask)) == -1) + return (-1); + if (imsg_add(buf, count, sizeof(*count)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_read(delay, sizeof(*delay)); + scheduler_proc_read(count, sizeof(*count)); + if (r > 0) { + scheduler_proc_read(evpids, sizeof(*evpids) * (*count)); + scheduler_proc_read(types, sizeof(*types) * (*count)); + } + scheduler_proc_end(); + + return (r); +} + +static size_t +scheduler_proc_messages(uint32_t from, uint32_t *dst, size_t size) +{ + struct ibuf *buf; + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_MESSAGES"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_MESSAGES, 0, 0, + sizeof(from) + sizeof(size)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &from, sizeof(from)) == -1) + return (-1); + if (imsg_add(buf, &size, sizeof(size)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + s = rlen / sizeof(*dst); + scheduler_proc_read(dst, s * sizeof(*dst)); + scheduler_proc_end(); + + return (s); +} + +static size_t +scheduler_proc_envelopes(uint64_t from, struct evpstate *dst, size_t size) +{ + struct ibuf *buf; + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_ENVELOPES"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_ENVELOPES, 0, 0, + sizeof(from) + sizeof(size)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &from, sizeof(from)) == -1) + return (-1); + if (imsg_add(buf, &size, sizeof(size)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + s = rlen / sizeof(*dst); + scheduler_proc_read(dst, s * sizeof(*dst)); + scheduler_proc_end(); + + return (s); +} + +static int +scheduler_proc_schedule(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_SCHEDULE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_SCHEDULE, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_remove(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_REMOVE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_REMOVE, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_suspend(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_SUSPEND"); + + imsg_compose(&ibuf, PROC_SCHEDULER_SUSPEND, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_resume(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_RESUME"); + + imsg_compose(&ibuf, PROC_SCHEDULER_RESUME, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +struct scheduler_backend scheduler_backend_proc = { + scheduler_proc_init, + scheduler_proc_insert, + scheduler_proc_commit, + scheduler_proc_rollback, + scheduler_proc_update, + scheduler_proc_delete, + scheduler_proc_hold, + scheduler_proc_release, + scheduler_proc_batch, + scheduler_proc_messages, + scheduler_proc_envelopes, + scheduler_proc_schedule, + scheduler_proc_remove, + scheduler_proc_suspend, + scheduler_proc_resume, +}; diff --git a/usr.sbin/smtpd/scheduler_ramqueue.c b/usr.sbin/smtpd/scheduler_ramqueue.c new file mode 100644 index 00000000..0c04fc0b --- /dev/null +++ b/usr.sbin/smtpd/scheduler_ramqueue.c @@ -0,0 +1,1204 @@ +/* $OpenBSD: scheduler_ramqueue.c,v 1.45 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +TAILQ_HEAD(evplist, rq_envelope); + +struct rq_message { + uint32_t msgid; + struct tree envelopes; +}; + +struct rq_envelope { + TAILQ_ENTRY(rq_envelope) entry; + SPLAY_ENTRY(rq_envelope) t_entry; + + uint64_t evpid; + uint64_t holdq; + enum delivery_type type; + +#define RQ_EVPSTATE_PENDING 0 +#define RQ_EVPSTATE_SCHEDULED 1 +#define RQ_EVPSTATE_INFLIGHT 2 +#define RQ_EVPSTATE_HELD 3 + uint8_t state; + +#define RQ_ENVELOPE_EXPIRED 0x01 +#define RQ_ENVELOPE_REMOVED 0x02 +#define RQ_ENVELOPE_SUSPEND 0x04 +#define RQ_ENVELOPE_UPDATE 0x08 +#define RQ_ENVELOPE_OVERFLOW 0x10 + uint8_t flags; + + time_t ctime; + time_t sched; + time_t expire; + + struct rq_message *message; + + time_t t_inflight; + time_t t_scheduled; +}; + +struct rq_holdq { + struct evplist q; + size_t count; +}; + +struct rq_queue { + size_t evpcount; + struct tree messages; + SPLAY_HEAD(prioqtree, rq_envelope) q_priotree; + + struct evplist q_pending; + struct evplist q_inflight; + + struct evplist q_mta; + struct evplist q_mda; + struct evplist q_bounce; + struct evplist q_update; + struct evplist q_expired; + struct evplist q_removed; +}; + +static int rq_envelope_cmp(struct rq_envelope *, struct rq_envelope *); + +SPLAY_PROTOTYPE(prioqtree, rq_envelope, t_entry, rq_envelope_cmp); +static int scheduler_ram_init(const char *); +static int scheduler_ram_insert(struct scheduler_info *); +static size_t scheduler_ram_commit(uint32_t); +static size_t scheduler_ram_rollback(uint32_t); +static int scheduler_ram_update(struct scheduler_info *); +static int scheduler_ram_delete(uint64_t); +static int scheduler_ram_hold(uint64_t, uint64_t); +static int scheduler_ram_release(int, uint64_t, int); +static int scheduler_ram_batch(int, int *, size_t *, uint64_t *, int *); +static size_t scheduler_ram_messages(uint32_t, uint32_t *, size_t); +static size_t scheduler_ram_envelopes(uint64_t, struct evpstate *, size_t); +static int scheduler_ram_schedule(uint64_t); +static int scheduler_ram_remove(uint64_t); +static int scheduler_ram_suspend(uint64_t); +static int scheduler_ram_resume(uint64_t); +static int scheduler_ram_query(uint64_t); + +static void sorted_insert(struct rq_queue *, struct rq_envelope *); + +static void rq_queue_init(struct rq_queue *); +static void rq_queue_merge(struct rq_queue *, struct rq_queue *); +static void rq_queue_dump(struct rq_queue *, const char *); +static void rq_queue_schedule(struct rq_queue *rq); +static struct evplist *rq_envelope_list(struct rq_queue *, struct rq_envelope *); +static void rq_envelope_schedule(struct rq_queue *, struct rq_envelope *); +static int rq_envelope_remove(struct rq_queue *, struct rq_envelope *); +static int rq_envelope_suspend(struct rq_queue *, struct rq_envelope *); +static int rq_envelope_resume(struct rq_queue *, struct rq_envelope *); +static void rq_envelope_delete(struct rq_queue *, struct rq_envelope *); +static const char *rq_envelope_to_text(struct rq_envelope *); + +struct scheduler_backend scheduler_backend_ramqueue = { + scheduler_ram_init, + + scheduler_ram_insert, + scheduler_ram_commit, + scheduler_ram_rollback, + + scheduler_ram_update, + scheduler_ram_delete, + scheduler_ram_hold, + scheduler_ram_release, + + scheduler_ram_batch, + + scheduler_ram_messages, + scheduler_ram_envelopes, + scheduler_ram_schedule, + scheduler_ram_remove, + scheduler_ram_suspend, + scheduler_ram_resume, + scheduler_ram_query, +}; + +static struct rq_queue ramqueue; +static struct tree updates; +static struct tree holdqs[3]; /* delivery type */ + +static time_t currtime; + +#define BACKOFF_TRANSFER 400 +#define BACKOFF_DELIVERY 10 +#define BACKOFF_OVERFLOW 3 + +static time_t +scheduler_backoff(time_t t0, time_t base, uint32_t step) +{ + return (t0 + base * step * step); +} + +static time_t +scheduler_next(time_t t0, time_t base, uint32_t step) +{ + time_t t; + + /* XXX be more efficient */ + while ((t = scheduler_backoff(t0, base, step)) <= currtime) + step++; + + return (t); +} + +static int +scheduler_ram_init(const char *arg) +{ + rq_queue_init(&ramqueue); + tree_init(&updates); + tree_init(&holdqs[D_MDA]); + tree_init(&holdqs[D_MTA]); + tree_init(&holdqs[D_BOUNCE]); + + return (1); +} + +static int +scheduler_ram_insert(struct scheduler_info *si) +{ + struct rq_queue *update; + struct rq_message *message; + struct rq_envelope *envelope; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(si->evpid); + + /* find/prepare a ramqueue update */ + if ((update = tree_get(&updates, msgid)) == NULL) { + update = xcalloc(1, sizeof *update); + stat_increment("scheduler.ramqueue.update", 1); + rq_queue_init(update); + tree_xset(&updates, msgid, update); + } + + /* find/prepare the msgtree message in ramqueue update */ + if ((message = tree_get(&update->messages, msgid)) == NULL) { + message = xcalloc(1, sizeof *message); + message->msgid = msgid; + tree_init(&message->envelopes); + tree_xset(&update->messages, msgid, message); + stat_increment("scheduler.ramqueue.message", 1); + } + + /* create envelope in ramqueue message */ + envelope = xcalloc(1, sizeof *envelope); + envelope->evpid = si->evpid; + envelope->type = si->type; + envelope->message = message; + envelope->ctime = si->creation; + envelope->expire = si->creation + si->ttl; + envelope->sched = scheduler_backoff(si->creation, + (si->type == D_MTA) ? BACKOFF_TRANSFER : BACKOFF_DELIVERY, si->retry); + tree_xset(&message->envelopes, envelope->evpid, envelope); + + update->evpcount++; + stat_increment("scheduler.ramqueue.envelope", 1); + + envelope->state = RQ_EVPSTATE_PENDING; + TAILQ_INSERT_TAIL(&update->q_pending, envelope, entry); + + si->nexttry = envelope->sched; + + return (1); +} + +static size_t +scheduler_ram_commit(uint32_t msgid) +{ + struct rq_queue *update; + size_t r; + + currtime = time(NULL); + + update = tree_xpop(&updates, msgid); + r = update->evpcount; + + if (tracing & TRACE_SCHEDULER) + rq_queue_dump(update, "update to commit"); + + rq_queue_merge(&ramqueue, update); + + if (tracing & TRACE_SCHEDULER) + rq_queue_dump(&ramqueue, "resulting queue"); + + rq_queue_schedule(&ramqueue); + + free(update); + stat_decrement("scheduler.ramqueue.update", 1); + + return (r); +} + +static size_t +scheduler_ram_rollback(uint32_t msgid) +{ + struct rq_queue *update; + struct rq_envelope *evp; + size_t r; + + currtime = time(NULL); + + if ((update = tree_pop(&updates, msgid)) == NULL) + return (0); + r = update->evpcount; + + while ((evp = TAILQ_FIRST(&update->q_pending))) { + TAILQ_REMOVE(&update->q_pending, evp, entry); + rq_envelope_delete(update, evp); + } + + free(update); + stat_decrement("scheduler.ramqueue.update", 1); + + return (r); +} + +static int +scheduler_ram_update(struct scheduler_info *si) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(si->evpid); + msg = tree_xget(&ramqueue.messages, msgid); + evp = tree_xget(&msg->envelopes, si->evpid); + + /* it *must* be in-flight */ + if (evp->state != RQ_EVPSTATE_INFLIGHT) + errx(1, "evp:%016" PRIx64 " not in-flight", si->evpid); + + TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); + + /* + * If the envelope was removed while inflight, schedule it for + * removal immediately. + */ + if (evp->flags & RQ_ENVELOPE_REMOVED) { + TAILQ_INSERT_TAIL(&ramqueue.q_removed, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->t_scheduled = currtime; + return (1); + } + + evp->sched = scheduler_next(evp->ctime, + (si->type == D_MTA) ? BACKOFF_TRANSFER : BACKOFF_DELIVERY, si->retry); + + evp->state = RQ_EVPSTATE_PENDING; + if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) + sorted_insert(&ramqueue, evp); + + si->nexttry = evp->sched; + + return (1); +} + +static int +scheduler_ram_delete(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(evpid); + msg = tree_xget(&ramqueue.messages, msgid); + evp = tree_xget(&msg->envelopes, evpid); + + /* it *must* be in-flight */ + if (evp->state != RQ_EVPSTATE_INFLIGHT) + errx(1, "evp:%016" PRIx64 " not in-flight", evpid); + + TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); + + rq_envelope_delete(&ramqueue, evp); + + return (1); +} + +#define HOLDQ_MAXSIZE 1000 + +static int +scheduler_ram_hold(uint64_t evpid, uint64_t holdq) +{ + struct rq_holdq *hq; + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(evpid); + msg = tree_xget(&ramqueue.messages, msgid); + evp = tree_xget(&msg->envelopes, evpid); + + /* it *must* be in-flight */ + if (evp->state != RQ_EVPSTATE_INFLIGHT) + errx(1, "evp:%016" PRIx64 " not in-flight", evpid); + + TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); + + /* If the envelope is suspended, just mark it as pending */ + if (evp->flags & RQ_ENVELOPE_SUSPEND) { + evp->state = RQ_EVPSTATE_PENDING; + return (0); + } + + hq = tree_get(&holdqs[evp->type], holdq); + if (hq == NULL) { + hq = xcalloc(1, sizeof(*hq)); + TAILQ_INIT(&hq->q); + tree_xset(&holdqs[evp->type], holdq, hq); + stat_increment("scheduler.ramqueue.holdq", 1); + } + + /* If the holdq is full, just "tempfail" the envelope */ + if (hq->count >= HOLDQ_MAXSIZE) { + evp->state = RQ_EVPSTATE_PENDING; + evp->flags |= RQ_ENVELOPE_UPDATE; + evp->flags |= RQ_ENVELOPE_OVERFLOW; + sorted_insert(&ramqueue, evp); + stat_increment("scheduler.ramqueue.hold-overflow", 1); + return (0); + } + + evp->state = RQ_EVPSTATE_HELD; + evp->holdq = holdq; + /* This is an optimization: upon release, the envelopes will be + * inserted in the pending queue from the first element to the last. + * Since elements already in the queue were received first, they + * were scheduled first, so they will be reinserted before the + * current element. + */ + TAILQ_INSERT_HEAD(&hq->q, evp, entry); + hq->count += 1; + stat_increment("scheduler.ramqueue.hold", 1); + + return (1); +} + +static int +scheduler_ram_release(int type, uint64_t holdq, int n) +{ + struct rq_holdq *hq; + struct rq_envelope *evp; + int i, update; + + currtime = time(NULL); + + hq = tree_get(&holdqs[type], holdq); + if (hq == NULL) + return (0); + + if (n == -1) { + n = 0; + update = 1; + } + else + update = 0; + + for (i = 0; n == 0 || i < n; i++) { + evp = TAILQ_FIRST(&hq->q); + if (evp == NULL) + break; + + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + evp->holdq = 0; + + /* When released, all envelopes are put in the pending queue + * and will be rescheduled immediately. As an optimization, + * we could just schedule them directly. + */ + evp->state = RQ_EVPSTATE_PENDING; + if (update) + evp->flags |= RQ_ENVELOPE_UPDATE; + sorted_insert(&ramqueue, evp); + } + + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[type], holdq); + free(hq); + stat_decrement("scheduler.ramqueue.holdq", 1); + } + stat_decrement("scheduler.ramqueue.hold", i); + + return (i); +} + +static int +scheduler_ram_batch(int mask, int *delay, size_t *count, uint64_t *evpids, int *types) +{ + struct rq_envelope *evp; + size_t i, n; + time_t t; + + currtime = time(NULL); + + rq_queue_schedule(&ramqueue); + if (tracing & TRACE_SCHEDULER) + rq_queue_dump(&ramqueue, "scheduler_ram_batch()"); + + i = 0; + n = 0; + + for (;;) { + + if (mask & SCHED_REMOVE && (evp = TAILQ_FIRST(&ramqueue.q_removed))) { + TAILQ_REMOVE(&ramqueue.q_removed, evp, entry); + types[i] = SCHED_REMOVE; + evpids[i] = evp->evpid; + rq_envelope_delete(&ramqueue, evp); + + if (++i == *count) + break; + } + + if (mask & SCHED_EXPIRE && (evp = TAILQ_FIRST(&ramqueue.q_expired))) { + TAILQ_REMOVE(&ramqueue.q_expired, evp, entry); + types[i] = SCHED_EXPIRE; + evpids[i] = evp->evpid; + rq_envelope_delete(&ramqueue, evp); + + if (++i == *count) + break; + } + + if (mask & SCHED_UPDATE && (evp = TAILQ_FIRST(&ramqueue.q_update))) { + TAILQ_REMOVE(&ramqueue.q_update, evp, entry); + types[i] = SCHED_UPDATE; + evpids[i] = evp->evpid; + + if (evp->flags & RQ_ENVELOPE_OVERFLOW) + t = BACKOFF_OVERFLOW; + else if (evp->type == D_MTA) + t = BACKOFF_TRANSFER; + else + t = BACKOFF_DELIVERY; + + evp->sched = scheduler_next(evp->ctime, t, 0); + evp->flags &= ~(RQ_ENVELOPE_UPDATE|RQ_ENVELOPE_OVERFLOW); + evp->state = RQ_EVPSTATE_PENDING; + if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) + sorted_insert(&ramqueue, evp); + + if (++i == *count) + break; + } + + if (mask & SCHED_BOUNCE && (evp = TAILQ_FIRST(&ramqueue.q_bounce))) { + TAILQ_REMOVE(&ramqueue.q_bounce, evp, entry); + types[i] = SCHED_BOUNCE; + evpids[i] = evp->evpid; + + TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); + evp->state = RQ_EVPSTATE_INFLIGHT; + evp->t_inflight = currtime; + + if (++i == *count) + break; + } + + if (mask & SCHED_MDA && (evp = TAILQ_FIRST(&ramqueue.q_mda))) { + TAILQ_REMOVE(&ramqueue.q_mda, evp, entry); + types[i] = SCHED_MDA; + evpids[i] = evp->evpid; + + TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); + evp->state = RQ_EVPSTATE_INFLIGHT; + evp->t_inflight = currtime; + + if (++i == *count) + break; + } + + if (mask & SCHED_MTA && (evp = TAILQ_FIRST(&ramqueue.q_mta))) { + TAILQ_REMOVE(&ramqueue.q_mta, evp, entry); + types[i] = SCHED_MTA; + evpids[i] = evp->evpid; + + TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); + evp->state = RQ_EVPSTATE_INFLIGHT; + evp->t_inflight = currtime; + + if (++i == *count) + break; + } + + /* nothing seen this round */ + if (i == n) + break; + + n = i; + } + + if (i) { + *count = i; + return (1); + } + + if ((evp = TAILQ_FIRST(&ramqueue.q_pending))) { + if (evp->sched < evp->expire) + t = evp->sched; + else + t = evp->expire; + *delay = (t < currtime) ? 0 : (t - currtime); + } + else + *delay = -1; + + return (0); +} + +static size_t +scheduler_ram_messages(uint32_t from, uint32_t *dst, size_t size) +{ + uint64_t id; + size_t n; + void *i; + + for (n = 0, i = NULL; n < size; n++) { + if (tree_iterfrom(&ramqueue.messages, &i, from, &id, NULL) == 0) + break; + dst[n] = id; + } + + return (n); +} + +static size_t +scheduler_ram_envelopes(uint64_t from, struct evpstate *dst, size_t size) +{ + struct rq_message *msg; + struct rq_envelope *evp; + void *i; + size_t n; + + if ((msg = tree_get(&ramqueue.messages, evpid_to_msgid(from))) == NULL) + return (0); + + for (n = 0, i = NULL; n < size; ) { + + if (tree_iterfrom(&msg->envelopes, &i, from, NULL, + (void**)&evp) == 0) + break; + + if (evp->flags & (RQ_ENVELOPE_REMOVED | RQ_ENVELOPE_EXPIRED)) + continue; + + dst[n].evpid = evp->evpid; + dst[n].flags = 0; + dst[n].retry = 0; + dst[n].time = 0; + + if (evp->state == RQ_EVPSTATE_PENDING) { + dst[n].time = evp->sched; + dst[n].flags = EF_PENDING; + } + else if (evp->state == RQ_EVPSTATE_SCHEDULED) { + dst[n].time = evp->t_scheduled; + dst[n].flags = EF_PENDING; + } + else if (evp->state == RQ_EVPSTATE_INFLIGHT) { + dst[n].time = evp->t_inflight; + dst[n].flags = EF_INFLIGHT; + } + else if (evp->state == RQ_EVPSTATE_HELD) { + /* same as scheduled */ + dst[n].time = evp->t_scheduled; + dst[n].flags = EF_PENDING; + dst[n].flags |= EF_HOLD; + } + if (evp->flags & RQ_ENVELOPE_SUSPEND) + dst[n].flags |= EF_SUSPEND; + + n++; + } + + return (n); +} + +static int +scheduler_ram_schedule(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (evp->state == RQ_EVPSTATE_INFLIGHT) + return (0); + rq_envelope_schedule(&ramqueue, evp); + return (1); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) { + if (evp->state == RQ_EVPSTATE_INFLIGHT) + continue; + rq_envelope_schedule(&ramqueue, evp); + r++; + } + return (r); + } +} + +static int +scheduler_ram_remove(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (rq_envelope_remove(&ramqueue, evp)) + return (1); + return (0); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) + if (rq_envelope_remove(&ramqueue, evp)) + r++; + return (r); + } +} + +static int +scheduler_ram_suspend(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (rq_envelope_suspend(&ramqueue, evp)) + return (1); + return (0); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) + if (rq_envelope_suspend(&ramqueue, evp)) + r++; + return (r); + } +} + +static int +scheduler_ram_resume(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (rq_envelope_resume(&ramqueue, evp)) + return (1); + return (0); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) + if (rq_envelope_resume(&ramqueue, evp)) + r++; + return (r); + } +} + +static int +scheduler_ram_query(uint64_t evpid) +{ + uint32_t msgid; + + if (evpid > 0xffffffff) + msgid = evpid_to_msgid(evpid); + else + msgid = evpid; + + if (tree_get(&ramqueue.messages, msgid) == NULL) + return (0); + + return (1); +} + +static void +sorted_insert(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_envelope *evp2; + + SPLAY_INSERT(prioqtree, &rq->q_priotree, evp); + evp2 = SPLAY_NEXT(prioqtree, &rq->q_priotree, evp); + if (evp2) + TAILQ_INSERT_BEFORE(evp2, evp, entry); + else + TAILQ_INSERT_TAIL(&rq->q_pending, evp, entry); +} + +static void +rq_queue_init(struct rq_queue *rq) +{ + memset(rq, 0, sizeof *rq); + tree_init(&rq->messages); + TAILQ_INIT(&rq->q_pending); + TAILQ_INIT(&rq->q_inflight); + TAILQ_INIT(&rq->q_mta); + TAILQ_INIT(&rq->q_mda); + TAILQ_INIT(&rq->q_bounce); + TAILQ_INIT(&rq->q_update); + TAILQ_INIT(&rq->q_expired); + TAILQ_INIT(&rq->q_removed); + SPLAY_INIT(&rq->q_priotree); +} + +static void +rq_queue_merge(struct rq_queue *rq, struct rq_queue *update) +{ + struct rq_message *message, *tomessage; + struct rq_envelope *envelope; + uint64_t id; + void *i; + + while (tree_poproot(&update->messages, &id, (void*)&message)) { + if ((tomessage = tree_get(&rq->messages, id)) == NULL) { + /* message does not exist. re-use structure */ + tree_xset(&rq->messages, id, message); + continue; + } + /* need to re-link all envelopes before merging them */ + i = NULL; + while ((tree_iter(&message->envelopes, &i, &id, + (void*)&envelope))) + envelope->message = tomessage; + tree_merge(&tomessage->envelopes, &message->envelopes); + free(message); + stat_decrement("scheduler.ramqueue.message", 1); + } + + /* Sorted insert in the pending queue */ + while ((envelope = TAILQ_FIRST(&update->q_pending))) { + TAILQ_REMOVE(&update->q_pending, envelope, entry); + sorted_insert(rq, envelope); + } + + rq->evpcount += update->evpcount; +} + +#define SCHEDULEMAX 1024 + +static void +rq_queue_schedule(struct rq_queue *rq) +{ + struct rq_envelope *evp; + size_t n; + + n = 0; + while ((evp = TAILQ_FIRST(&rq->q_pending))) { + if (evp->sched > currtime && evp->expire > currtime) + break; + + if (n == SCHEDULEMAX) + break; + + if (evp->state != RQ_EVPSTATE_PENDING) + errx(1, "evp:%016" PRIx64 " flags=0x%x", evp->evpid, + evp->flags); + + if (evp->expire <= currtime) { + TAILQ_REMOVE(&rq->q_pending, evp, entry); + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + TAILQ_INSERT_TAIL(&rq->q_expired, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->flags |= RQ_ENVELOPE_EXPIRED; + evp->t_scheduled = currtime; + continue; + } + rq_envelope_schedule(rq, evp); + n += 1; + } +} + +static struct evplist * +rq_envelope_list(struct rq_queue *rq, struct rq_envelope *evp) +{ + switch (evp->state) { + case RQ_EVPSTATE_PENDING: + return &rq->q_pending; + + case RQ_EVPSTATE_SCHEDULED: + if (evp->flags & RQ_ENVELOPE_EXPIRED) + return &rq->q_expired; + if (evp->flags & RQ_ENVELOPE_REMOVED) + return &rq->q_removed; + if (evp->flags & RQ_ENVELOPE_UPDATE) + return &rq->q_update; + if (evp->type == D_MTA) + return &rq->q_mta; + if (evp->type == D_MDA) + return &rq->q_mda; + if (evp->type == D_BOUNCE) + return &rq->q_bounce; + errx(1, "%016" PRIx64 " bad evp type %d", evp->evpid, evp->type); + + case RQ_EVPSTATE_INFLIGHT: + return &rq->q_inflight; + + case RQ_EVPSTATE_HELD: + return (NULL); + } + + errx(1, "%016" PRIx64 " bad state %d", evp->evpid, evp->state); + return (NULL); +} + +static void +rq_envelope_schedule(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_holdq *hq; + struct evplist *q = NULL; + + switch (evp->type) { + case D_MTA: + q = &rq->q_mta; + break; + case D_MDA: + q = &rq->q_mda; + break; + case D_BOUNCE: + q = &rq->q_bounce; + break; + } + + if (evp->flags & RQ_ENVELOPE_UPDATE) + q = &rq->q_update; + + if (evp->state == RQ_EVPSTATE_HELD) { + hq = tree_xget(&holdqs[evp->type], evp->holdq); + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[evp->type], evp->holdq); + free(hq); + } + evp->holdq = 0; + stat_decrement("scheduler.ramqueue.hold", 1); + } + else if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) { + TAILQ_REMOVE(&rq->q_pending, evp, entry); + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + } + + TAILQ_INSERT_TAIL(q, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->t_scheduled = currtime; +} + +static int +rq_envelope_remove(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_holdq *hq; + struct evplist *evl; + + if (evp->flags & (RQ_ENVELOPE_REMOVED | RQ_ENVELOPE_EXPIRED)) + return (0); + /* + * If envelope is inflight, mark it envelope for removal. + */ + if (evp->state == RQ_EVPSTATE_INFLIGHT) { + evp->flags |= RQ_ENVELOPE_REMOVED; + return (1); + } + + if (evp->state == RQ_EVPSTATE_HELD) { + hq = tree_xget(&holdqs[evp->type], evp->holdq); + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[evp->type], evp->holdq); + free(hq); + } + evp->holdq = 0; + stat_decrement("scheduler.ramqueue.hold", 1); + } + else if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) { + evl = rq_envelope_list(rq, evp); + TAILQ_REMOVE(evl, evp, entry); + if (evl == &rq->q_pending) + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + } + + TAILQ_INSERT_TAIL(&rq->q_removed, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->flags |= RQ_ENVELOPE_REMOVED; + evp->t_scheduled = currtime; + + return (1); +} + +static int +rq_envelope_suspend(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_holdq *hq; + struct evplist *evl; + + if (evp->flags & RQ_ENVELOPE_SUSPEND) + return (0); + + if (evp->state == RQ_EVPSTATE_HELD) { + hq = tree_xget(&holdqs[evp->type], evp->holdq); + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[evp->type], evp->holdq); + free(hq); + } + evp->holdq = 0; + evp->state = RQ_EVPSTATE_PENDING; + stat_decrement("scheduler.ramqueue.hold", 1); + } + else if (evp->state != RQ_EVPSTATE_INFLIGHT) { + evl = rq_envelope_list(rq, evp); + TAILQ_REMOVE(evl, evp, entry); + if (evl == &rq->q_pending) + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + } + + evp->flags |= RQ_ENVELOPE_SUSPEND; + + return (1); +} + +static int +rq_envelope_resume(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct evplist *evl; + + if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) + return (0); + + if (evp->state != RQ_EVPSTATE_INFLIGHT) { + evl = rq_envelope_list(rq, evp); + if (evl == &rq->q_pending) + sorted_insert(rq, evp); + else + TAILQ_INSERT_TAIL(evl, evp, entry); + } + + evp->flags &= ~RQ_ENVELOPE_SUSPEND; + + return (1); +} + +static void +rq_envelope_delete(struct rq_queue *rq, struct rq_envelope *evp) +{ + tree_xpop(&evp->message->envelopes, evp->evpid); + if (tree_empty(&evp->message->envelopes)) { + tree_xpop(&rq->messages, evp->message->msgid); + free(evp->message); + stat_decrement("scheduler.ramqueue.message", 1); + } + + free(evp); + rq->evpcount--; + stat_decrement("scheduler.ramqueue.envelope", 1); +} + +static const char * +rq_envelope_to_text(struct rq_envelope *e) +{ + static char buf[256]; + char t[64]; + + (void)snprintf(buf, sizeof buf, "evp:%016" PRIx64 " [", e->evpid); + + if (e->type == D_BOUNCE) + (void)strlcat(buf, "bounce", sizeof buf); + else if (e->type == D_MDA) + (void)strlcat(buf, "mda", sizeof buf); + else if (e->type == D_MTA) + (void)strlcat(buf, "mta", sizeof buf); + + (void)snprintf(t, sizeof t, ",expire=%s", + duration_to_text(e->expire - currtime)); + (void)strlcat(buf, t, sizeof buf); + + + switch (e->state) { + case RQ_EVPSTATE_PENDING: + (void)snprintf(t, sizeof t, ",pending=%s", + duration_to_text(e->sched - currtime)); + (void)strlcat(buf, t, sizeof buf); + break; + + case RQ_EVPSTATE_SCHEDULED: + (void)snprintf(t, sizeof t, ",scheduled=%s", + duration_to_text(currtime - e->t_scheduled)); + (void)strlcat(buf, t, sizeof buf); + break; + + case RQ_EVPSTATE_INFLIGHT: + (void)snprintf(t, sizeof t, ",inflight=%s", + duration_to_text(currtime - e->t_inflight)); + (void)strlcat(buf, t, sizeof buf); + break; + + case RQ_EVPSTATE_HELD: + (void)snprintf(t, sizeof t, ",held=%s", + duration_to_text(currtime - e->t_inflight)); + (void)strlcat(buf, t, sizeof buf); + break; + default: + errx(1, "%016" PRIx64 " bad state %d", e->evpid, e->state); + } + + if (e->flags & RQ_ENVELOPE_REMOVED) + (void)strlcat(buf, ",removed", sizeof buf); + if (e->flags & RQ_ENVELOPE_EXPIRED) + (void)strlcat(buf, ",expired", sizeof buf); + if (e->flags & RQ_ENVELOPE_SUSPEND) + (void)strlcat(buf, ",suspended", sizeof buf); + + (void)strlcat(buf, "]", sizeof buf); + + return (buf); +} + +static void +rq_queue_dump(struct rq_queue *rq, const char * name) +{ + struct rq_message *message; + struct rq_envelope *envelope; + void *i, *j; + uint64_t id; + + log_debug("debug: /--- ramqueue: %s", name); + + i = NULL; + while ((tree_iter(&rq->messages, &i, &id, (void*)&message))) { + log_debug("debug: | msg:%08" PRIx32, message->msgid); + j = NULL; + while ((tree_iter(&message->envelopes, &j, &id, + (void*)&envelope))) + log_debug("debug: | %s", + rq_envelope_to_text(envelope)); + } + log_debug("debug: \\---"); +} + +static int +rq_envelope_cmp(struct rq_envelope *e1, struct rq_envelope *e2) +{ + time_t ref1, ref2; + + ref1 = (e1->sched < e1->expire) ? e1->sched : e1->expire; + ref2 = (e2->sched < e2->expire) ? e2->sched : e2->expire; + if (ref1 != ref2) + return (ref1 < ref2) ? -1 : 1; + + if (e1->evpid != e2->evpid) + return (e1->evpid < e2->evpid) ? -1 : 1; + + return 0; +} + +SPLAY_GENERATE(prioqtree, rq_envelope, t_entry, rq_envelope_cmp); diff --git a/usr.sbin/smtpd/sendmail.8 b/usr.sbin/smtpd/sendmail.8 new file mode 100644 index 00000000..1696a861 --- /dev/null +++ b/usr.sbin/smtpd/sendmail.8 @@ -0,0 +1,86 @@ +.\" $OpenBSD: sendmail.8,v 1.4 2015/10/23 15:48:16 jung Exp $ +.\" +.\" Copyright (C) 2013 Ryan Kavanagh +.\" All rights reserved. +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.Dd $Mdocdate: October 23 2015 $ +.Dt SENDMAIL 8 +.Os +.Sh NAME +.Nm sendmail +.Nd a mail enqueuer for +.Xr smtpd 8 +.Sh SYNOPSIS +.Nm +.Op Fl tv +.Op Fl F Ar name +.Op Fl f Ar from +.Ar to ... +.Sh DESCRIPTION +The +.Nm +utility is a local enqueuer for the +.Xr smtpd 8 +daemon, +compatible with +.Xr mailwrapper 8 . +The message is read on standard input (stdin) until +.Nm +encounters an end-of-file. +The +.Nm +enqueuer is not intended to be used directly to send mail, +but rather via a frontend known as a mail user agent. +.Pp +Unless the optional +.Fl t +flag is specified, +one or more recipients must be specified on the command line. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl F Ar name +Set the sender's full name. +.It Fl f Ar from +Set the sender's address. +.It Fl t +Read the message's To:, Cc:, and Bcc: fields for recipients. +The Bcc: field will be deleted before sending. +.It Fl v +Enable verbose output. +.El +.Pp +To maintain compatibility with Sendmail, Inc.'s implementation of +.Nm , +various other flags are accepted, +but have no effect. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr smtpctl 8 , +.Xr smtpd 8 +.Sh AUTHORS +.Sy OpenSMTPD +is primarily developed by Gilles Chehade, +Eric Faurot, +and Charles Longeau, +with contributions from various +.Ox +hackers. +It is distributed under the ISC license. +.Pp +This manpage was written by +.An Ryan Kavanagh +.Aq Mt rak@debian.org +for the Debian project and is distributed under the ISC license. diff --git a/usr.sbin/smtpd/smtp.1 b/usr.sbin/smtpd/smtp.1 new file mode 100644 index 00000000..3cc03844 --- /dev/null +++ b/usr.sbin/smtpd/smtp.1 @@ -0,0 +1,96 @@ +.\" $OpenBSD: smtp.1,v 1.7 2018/07/04 08:23:43 jmc Exp $ +.\" +.\" Copyright (c) 2018, Eric Faurot +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 4 2018 $ +.Dt SMTP 1 +.Os +.Sh NAME +.Nm smtp +.Nd Simple Mail Transfer Protocol client +.Sh SYNOPSIS +.Nm +.Op Fl Chnv +.Op Fl F Ar from +.Op Fl H Ar helo +.Op Fl s Ar server +.Op Ar recipient ... +.Sh DESCRIPTION +The +.Nm +utility is a Simple Mail Transfer Protocol +.Pq SMTP +client which can be used to run an SMTP transaction against an SMTP server. +.Pp +By default, +.Nm +reads the mail content from the standard input, establishes an SMTP session, +and runs an SMTP transaction for all the specified recipients. +The content is sent unaltered as mail data. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl C +Do not require server certificate to be valid. +.It Fl F Ar from +Set the return-path (MAIL FROM) for the SMTP transaction. +Default to the current username. +.It Fl H Ar helo +Define the hostname to advertise (HELO) when establishing the SMTP session. +.It Fl h +Display version and usage. +.It Fl n +Do not actually execute a transaction, +just try to establish an SMTP session and quit. +When this option is given, no message is read from the standard input. +.It Fl s Ar server +Specify the server to connect to and connection parameters. +The format is +.Sm off +.Op Ar proto No :// Op Ar user : pass No @ +.Ar host Op : Ar port . +.Sm on +The following protocols are available: +.Pp +.Bl -tag -width "smtp+notls" -compact +.It smtp +Normal SMTP session with opportunistic STARTTLS. +.It smtp+tls +Normal SMTP session with mandatory STARTTLS. +.It smtp+notls +Plain text SMTP session without TLS. +.It lmtp +LMTP session with opportunistic STARTTLS. +.It lmtp+tls +LMTP session with mandatory STARTTLS. +.It lmtp+notls +Plain text LMTP session without TLS. +.It smtps +SMTP session with forced TLS on connection. +.El +.Pp +Defaults to +.Dq smtp://localhost:25 . +.It Fl v +Be more verbose. +This option can be specified multiple times. +.El +.Sh SEE ALSO +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.4 . diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c new file mode 100644 index 00000000..602fd0d6 --- /dev/null +++ b/usr.sbin/smtpd/smtp.c @@ -0,0 +1,387 @@ +/* $OpenBSD: smtp.c,v 1.166 2019/08/10 16:07:01 gilles Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2009 Jacek Masiulaniec + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +static void smtp_setup_events(void); +static void smtp_pause(void); +static void smtp_resume(void); +static void smtp_accept(int, short, void *); +static void smtp_dropped(struct listener *, int, const struct sockaddr_storage *); +static int smtp_enqueue(void); +static int smtp_can_accept(void); +static void smtp_setup_listeners(void); +static int smtp_sni_callback(SSL *, int *, void *); + +int +proxy_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, + void (*accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *), + void (*dropped)(struct listener *, int, + const struct sockaddr_storage *)); + +static void smtp_accepted(struct listener *, int, const struct sockaddr_storage *, struct io *); + + +#define SMTP_FD_RESERVE 5 +#define getdtablecount() 0 + +static size_t sessions; +static size_t maxsessions; + +void +smtp_imsg(struct mproc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_SMTP_CHECK_SENDER: + case IMSG_SMTP_EXPAND_RCPT: + case IMSG_SMTP_LOOKUP_HELO: + case IMSG_SMTP_AUTHENTICATE: + case IMSG_FILTER_SMTP_PROTOCOL: + case IMSG_FILTER_SMTP_DATA_BEGIN: + smtp_session_imsg(p, imsg); + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + case IMSG_SMTP_MESSAGE_CREATE: + case IMSG_SMTP_MESSAGE_OPEN: + case IMSG_QUEUE_ENVELOPE_SUBMIT: + case IMSG_QUEUE_ENVELOPE_COMMIT: + smtp_session_imsg(p, imsg); + return; + + case IMSG_QUEUE_SMTP_SESSION: + m_compose(p, IMSG_QUEUE_SMTP_SESSION, 0, 0, smtp_enqueue(), + imsg->data, imsg->hdr.len - sizeof imsg->hdr); + return; + + case IMSG_CTL_SMTP_SESSION: + m_compose(p, IMSG_CTL_SMTP_SESSION, imsg->hdr.peerid, 0, + smtp_enqueue(), NULL, 0); + return; + + case IMSG_CTL_PAUSE_SMTP: + log_debug("debug: smtp: pausing listening sockets"); + smtp_pause(); + env->sc_flags |= SMTPD_SMTP_PAUSED; + return; + + case IMSG_CTL_RESUME_SMTP: + log_debug("debug: smtp: resuming listening sockets"); + env->sc_flags &= ~SMTPD_SMTP_PAUSED; + smtp_resume(); + return; + } + + errx(1, "smtp_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +void +smtp_postfork(void) +{ + smtp_setup_listeners(); +} + +void +smtp_postprivdrop(void) +{ +} + +void +smtp_configure(void) +{ + smtp_setup_events(); +} + +static void +smtp_setup_listeners(void) +{ + struct listener *l; + int opt; + + TAILQ_FOREACH(l, env->sc_listeners, entry) { + if ((l->fd = socket(l->ss.ss_family, SOCK_STREAM, 0)) == -1) { + if (errno == EAFNOSUPPORT) { + log_warn("smtpd: socket"); + continue; + } + fatal("smtpd: socket"); + } + opt = 1; +#ifdef SO_REUSEADDR + if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt, + sizeof(opt)) == -1) + fatal("smtpd: setsockopt"); +#else + if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEPORT, &opt, + sizeof(opt)) < 0) + fatal("smtpd: setsockopt"); +#endif +#ifdef IPV6_V6ONLY + /* + * If using IPv6, bind only to IPv6 if possible. + * This avoids ambiguities with IPv4-mapped IPv6 addresses. + */ + if (l->ss.ss_family == AF_INET6) + if (setsockopt(l->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, + sizeof(opt)) < 0) + fatal("smtpd: setsockopt"); +#endif + if (bind(l->fd, (struct sockaddr *)&l->ss, SS_LEN(&l->ss)) == -1) + fatal("smtpd: bind"); + } +} + +static void +smtp_setup_events(void) +{ + struct listener *l; + struct pki *pki; + SSL_CTX *ssl_ctx; + void *iter; + const char *k; + + TAILQ_FOREACH(l, env->sc_listeners, entry) { + log_debug("debug: smtp: listen on %s port %d flags 0x%01x" + " pki \"%s\"" + " ca \"%s\"", ss_to_text(&l->ss), ntohs(l->port), + l->flags, l->pki_name, l->ca_name); + + io_set_nonblocking(l->fd); + if (listen(l->fd, SMTPD_BACKLOG) == -1) + fatal("listen"); + event_set(&l->ev, l->fd, EV_READ|EV_PERSIST, smtp_accept, l); + + if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) + event_add(&l->ev, NULL); + } + + iter = NULL; + while (dict_iter(env->sc_pki_dict, &iter, &k, (void **)&pki)) { + if (!ssl_setup((SSL_CTX **)&ssl_ctx, pki, smtp_sni_callback, + env->sc_tls_ciphers)) + fatal("smtp_setup_events: ssl_setup failure"); + dict_xset(env->sc_ssl_dict, k, ssl_ctx); + } + + purge_config(PURGE_PKI_KEYS); + + maxsessions = (getdtablesize() - getdtablecount()) / 2 - SMTP_FD_RESERVE; + log_debug("debug: smtp: will accept at most %zu clients", maxsessions); +} + +static void +smtp_pause(void) +{ + struct listener *l; + + if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED)) + return; + + TAILQ_FOREACH(l, env->sc_listeners, entry) + event_del(&l->ev); +} + +static void +smtp_resume(void) +{ + struct listener *l; + + if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED)) + return; + + TAILQ_FOREACH(l, env->sc_listeners, entry) + event_add(&l->ev, NULL); +} + +static int +smtp_enqueue(void) +{ + struct listener *listener = env->sc_sock_listener; + int fd[2]; + + /* + * Some enqueue requests buffered in IMSG may still arrive even after + * call to smtp_pause() because enqueue listener is not a real socket + * and thus cannot be paused properly. + */ + if (env->sc_flags & SMTPD_SMTP_PAUSED) + return (-1); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd)) + return (-1); + + if ((smtp_session(listener, fd[0], &listener->ss, env->sc_hostname, NULL)) == -1) { + close(fd[0]); + close(fd[1]); + return (-1); + } + + sessions++; + stat_increment("smtp.session", 1); + stat_increment("smtp.session.local", 1); + + return (fd[1]); +} + +static void +smtp_accept(int fd, short event, void *p) +{ + struct listener *listener = p; + struct sockaddr_storage ss; + socklen_t len; + int sock; + + if (env->sc_flags & SMTPD_SMTP_PAUSED) + fatalx("smtp_session: unexpected client"); + + if (!smtp_can_accept()) { + log_warnx("warn: Disabling incoming SMTP connections: " + "Client limit reached"); + goto pause; + } + + len = sizeof(ss); + if ((sock = accept(fd, (struct sockaddr *)&ss, &len)) == -1) { + if (errno == ENFILE || errno == EMFILE) { + log_warn("warn: Disabling incoming SMTP connections"); + goto pause; + } + if (errno == EINTR || errno == EWOULDBLOCK || + errno == ECONNABORTED) + return; + fatal("smtp_accept"); + } + + if (listener->flags & F_PROXY) { + io_set_nonblocking(sock); + if (proxy_session(listener, sock, &ss, + smtp_accepted, smtp_dropped) == -1) { + close(sock); + return; + } + return; + } + + smtp_accepted(listener, sock, &ss, NULL); + return; + +pause: + smtp_pause(); + env->sc_flags |= SMTPD_SMTP_DISABLED; + return; +} + +static int +smtp_can_accept(void) +{ + if (sessions + 1 == maxsessions) + return 0; + return (getdtablesize() - getdtablecount() - SMTP_FD_RESERVE >= 2); +} + +void +smtp_collect(void) +{ + sessions--; + stat_decrement("smtp.session", 1); + + if (!smtp_can_accept()) + return; + + if (env->sc_flags & SMTPD_SMTP_DISABLED) { + log_warnx("warn: smtp: " + "fd exhaustion over, re-enabling incoming connections"); + env->sc_flags &= ~SMTPD_SMTP_DISABLED; + smtp_resume(); + } +} + +static int +smtp_sni_callback(SSL *ssl, int *ad, void *arg) +{ + const char *sn; + void *ssl_ctx; + + sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (sn == NULL) + return SSL_TLSEXT_ERR_NOACK; + ssl_ctx = dict_get(env->sc_ssl_dict, sn); + if (ssl_ctx == NULL) + return SSL_TLSEXT_ERR_NOACK; + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; +} + +static void +smtp_accepted(struct listener *listener, int sock, const struct sockaddr_storage *ss, struct io *io) +{ + int ret; + + ret = smtp_session(listener, sock, ss, NULL, io); + if (ret == -1) { + log_warn("warn: Failed to create SMTP session"); + close(sock); + return; + } + io_set_nonblocking(sock); + + sessions++; + stat_increment("smtp.session", 1); + if (listener->ss.ss_family == AF_LOCAL) + stat_increment("smtp.session.local", 1); + if (listener->ss.ss_family == AF_INET) + stat_increment("smtp.session.inet4", 1); + if (listener->ss.ss_family == AF_INET6) + stat_increment("smtp.session.inet6", 1); +} + +static void +smtp_dropped(struct listener *listener, int sock, const struct sockaddr_storage *ss) +{ + close(sock); + sessions--; +} diff --git a/usr.sbin/smtpd/smtp.h b/usr.sbin/smtpd/smtp.h new file mode 100644 index 00000000..dc91d878 --- /dev/null +++ b/usr.sbin/smtpd/smtp.h @@ -0,0 +1,95 @@ +/* $OpenBSD: smtp.h,v 1.3 2019/09/02 20:05:21 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define TLS_NO 0 +#define TLS_YES 1 +#define TLS_FORCE 2 +#define TLS_SMTPS 3 + +#define CERT_OK 0 +#define CERT_UNKNOWN 1 +#define CERT_INVALID 2 + +#define FAIL_INTERNAL 1 /* malloc, etc. (check errno) */ +#define FAIL_CONN 2 /* disconnect, timeout... (check errno) */ +#define FAIL_PROTO 3 /* protocol violation */ +#define FAIL_IMPL 4 /* server lacks a required feature */ +#define FAIL_RESP 5 /* rejected command */ + +struct smtp_params { + + /* Client options */ + size_t linemax; /* max input line size */ + size_t ibufmax; /* max input buffer size */ + size_t obufmax; /* max output buffer size */ + + /* Connection settings */ + const struct sockaddr *dst; /* address to connect to */ + const struct sockaddr *src; /* address to bind to */ + int timeout; /* timeout in seconds */ + + /* TLS options */ + int tls_req; /* requested TLS mode */ + int tls_verify; /* need valid server certificate */ + + /* SMTP options */ + int lmtp; /* use LMTP protocol */ + const char *helo; /* string to use with HELO */ + const char *auth_user; /* for AUTH */ + const char *auth_pass; /* for AUTH */ +}; + +struct smtp_rcpt { + const char *to; + const char *dsn_notify; + const char *dsn_orcpt; + int done; +}; + +struct smtp_mail { + const char *from; + const char *dsn_ret; + const char *dsn_envid; + struct smtp_rcpt *rcpt; + int rcptcount; + FILE *fp; +}; + +struct smtp_status { + struct smtp_rcpt *rcpt; + const char *cmd; + const char *status; +}; + +struct smtp_client; + +/* smtp_client.c */ +struct smtp_client *smtp_connect(const struct smtp_params *, void *); +void smtp_cert_verified(struct smtp_client *, int); +void smtp_set_tls(struct smtp_client *, void *); +void smtp_quit(struct smtp_client *); +void smtp_sendmail(struct smtp_client *, struct smtp_mail *); + +/* callbacks */ +void smtp_verify_server_cert(void *, struct smtp_client *, void *); +void smtp_require_tls(void *, struct smtp_client *); +void smtp_ready(void *, struct smtp_client *); +void smtp_failed(void *, struct smtp_client *, int, const char *); +void smtp_closed(void *, struct smtp_client *); +void smtp_status(void *, struct smtp_client *, struct smtp_status *); +void smtp_done(void *, struct smtp_client *, struct smtp_mail *); diff --git a/usr.sbin/smtpd/smtp/Makefile b/usr.sbin/smtpd/smtp/Makefile new file mode 100644 index 00000000..380e3ad6 --- /dev/null +++ b/usr.sbin/smtpd/smtp/Makefile @@ -0,0 +1,24 @@ +# $OpenBSD: Makefile,v 1.1 2018/04/26 13:57:13 eric Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= smtp + +BINDIR= /usr/bin +MAN= smtp.1 + +SRCS+= iobuf.c +SRCS+= ioev.c +SRCS+= log.c +SRCS+= smtp_client.c +SRCS+= smtpc.c +SRCS+= ssl.c +SRCS+= ssl_smtpd.c +SRCS+= ssl_verify.c + +CPPFLAGS+= -DIO_TLS + +LDADD+= -levent -lutil -lssl -lcrypto -lm -lz +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} + +.include diff --git a/usr.sbin/smtpd/smtp_client.c b/usr.sbin/smtpd/smtp_client.c new file mode 100644 index 00000000..8e146e1b --- /dev/null +++ b/usr.sbin/smtpd/smtp_client.c @@ -0,0 +1,923 @@ +/* $OpenBSD: smtp_client.c,v 1.14 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "ioev.h" +#include "smtp.h" + +#define TRACE_SMTPCLT 2 +#define TRACE_IO 3 + +enum { + STATE_INIT = 0, + STATE_BANNER, + STATE_EHLO, + STATE_HELO, + STATE_LHLO, + STATE_STARTTLS, + STATE_AUTH, + STATE_AUTH_PLAIN, + STATE_AUTH_LOGIN, + STATE_AUTH_LOGIN_USER, + STATE_AUTH_LOGIN_PASS, + STATE_READY, + STATE_MAIL, + STATE_RCPT, + STATE_DATA, + STATE_BODY, + STATE_EOM, + STATE_RSET, + STATE_QUIT, + + STATE_LAST +}; + +#define base64_encode __b64_ntop +#define base64_decode __b64_pton + +#define FLAG_TLS 0x01 +#define FLAG_TLS_VERIFIED 0x02 + +#define SMTP_EXT_STARTTLS 0x01 +#define SMTP_EXT_PIPELINING 0x02 +#define SMTP_EXT_AUTH 0x04 +#define SMTP_EXT_AUTH_PLAIN 0x08 +#define SMTP_EXT_AUTH_LOGIN 0x10 +#define SMTP_EXT_DSN 0x20 +#define SMTP_EXT_SIZE 0x40 + +struct smtp_client { + void *tag; + struct smtp_params params; + + int state; + int flags; + int ext; + size_t ext_size; + + struct io *io; + char *reply; + size_t replysz; + + struct smtp_mail *mail; + int rcptidx; + int rcptok; +}; + +void log_trace_verbose(int); +void log_trace(int, const char *, ...) + __attribute__((format (printf, 2, 3))); + +static void smtp_client_io(struct io *, int, void *); +static void smtp_client_free(struct smtp_client *); +static void smtp_client_state(struct smtp_client *, int); +static void smtp_client_abort(struct smtp_client *, int, const char *); +static void smtp_client_cancel(struct smtp_client *, int, const char *); +static void smtp_client_sendcmd(struct smtp_client *, char *, ...); +static void smtp_client_sendbody(struct smtp_client *); +static int smtp_client_readline(struct smtp_client *); +static int smtp_client_replycat(struct smtp_client *, const char *); +static void smtp_client_response(struct smtp_client *, const char *); +static void smtp_client_mail_abort(struct smtp_client *); +static void smtp_client_mail_status(struct smtp_client *, const char *); +static void smtp_client_rcpt_status(struct smtp_client *, struct smtp_rcpt *, const char *); + +static const char *strstate[STATE_LAST] = { + "INIT", + "BANNER", + "EHLO", + "HELO", + "LHLO", + "STARTTLS", + "AUTH", + "AUTH_PLAIN", + "AUTH_LOGIN", + "AUTH_LOGIN_USER", + "AUTH_LOGIN_PASS", + "READY", + "MAIL", + "RCPT", + "DATA", + "BODY", + "EOM", + "RSET", + "QUIT", +}; + +struct smtp_client * +smtp_connect(const struct smtp_params *params, void *tag) +{ + struct smtp_client *proto; + + proto = calloc(1, sizeof *proto); + if (proto == NULL) + return NULL; + + memmove(&proto->params, params, sizeof(*params)); + proto->tag = tag; + proto->io = io_new(); + if (proto->io == NULL) { + free(proto); + return NULL; + } + io_set_callback(proto->io, smtp_client_io, proto); + io_set_timeout(proto->io, proto->params.timeout); + + if (io_connect(proto->io, proto->params.dst, proto->params.src) == -1) { + smtp_client_abort(proto, FAIL_CONN, io_error(proto->io)); + return NULL; + } + + return proto; +} + +void +smtp_cert_verified(struct smtp_client *proto, int verified) +{ + if (verified == CERT_OK) + proto->flags |= FLAG_TLS_VERIFIED; + + else if (proto->params.tls_verify) { + errno = EAUTH; + smtp_client_abort(proto, FAIL_CONN, + "Invalid server certificate"); + return; + } + + io_resume(proto->io, IO_IN); + + if (proto->state == STATE_INIT) + smtp_client_state(proto, STATE_BANNER); + else { + /* Clear extensions before re-issueing an EHLO command. */ + proto->ext = 0; + smtp_client_state(proto, STATE_EHLO); + } +} + +void +smtp_set_tls(struct smtp_client *proto, void *ctx) +{ + io_start_tls(proto->io, ctx); +} + +void +smtp_quit(struct smtp_client *proto) +{ + if (proto->state != STATE_READY) + fatalx("connection is not ready"); + + smtp_client_state(proto, STATE_QUIT); +} + +void +smtp_sendmail(struct smtp_client *proto, struct smtp_mail *mail) +{ + if (proto->state != STATE_READY) + fatalx("connection is not ready"); + + proto->mail = mail; + smtp_client_state(proto, STATE_MAIL); +} + +static void +smtp_client_free(struct smtp_client *proto) +{ + if (proto->mail) + fatalx("current task should have been deleted already"); + + smtp_closed(proto->tag, proto); + + if (proto->io) + io_free(proto->io); + + free(proto->reply); + free(proto); +} + +/* + * End the session immediatly. + */ +static void +smtp_client_abort(struct smtp_client *proto, int err, const char *reason) +{ + smtp_failed(proto->tag, proto, err, reason); + + if (proto->mail) + smtp_client_mail_abort(proto); + + smtp_client_free(proto); +} + +/* + * Properly close the session. + */ +static void +smtp_client_cancel(struct smtp_client *proto, int err, const char *reason) +{ + if (proto->mail) + fatal("not supposed to have a mail"); + + smtp_failed(proto->tag, proto, err, reason); + + smtp_client_state(proto, STATE_QUIT); +} + +static void +smtp_client_state(struct smtp_client *proto, int newstate) +{ + struct smtp_rcpt *rcpt; + char ibuf[LINE_MAX], obuf[LINE_MAX]; + size_t n; + int oldstate; + + if (proto->reply) + proto->reply[0] = '\0'; + + again: + oldstate = proto->state; + proto->state = newstate; + + log_trace(TRACE_SMTPCLT, "%p: %s -> %s", proto, + strstate[oldstate], + strstate[newstate]); + + /* don't try this at home! */ +#define smtp_client_state(_s, _st) do { newstate = _st; goto again; } while (0) + + switch (proto->state) { + case STATE_BANNER: + io_set_read(proto->io); + break; + + case STATE_EHLO: + smtp_client_sendcmd(proto, "EHLO %s", proto->params.helo); + break; + + case STATE_HELO: + smtp_client_sendcmd(proto, "HELO %s", proto->params.helo); + break; + + case STATE_LHLO: + smtp_client_sendcmd(proto, "LHLO %s", proto->params.helo); + break; + + case STATE_STARTTLS: + if (proto->params.tls_req == TLS_NO || proto->flags & FLAG_TLS) + smtp_client_state(proto, STATE_AUTH); + else if (proto->ext & SMTP_EXT_STARTTLS) + smtp_client_sendcmd(proto, "STARTTLS"); + else if (proto->params.tls_req == TLS_FORCE) + smtp_client_cancel(proto, FAIL_IMPL, + "TLS not supported by remote host"); + else + smtp_client_state(proto, STATE_AUTH); + break; + + case STATE_AUTH: + if (!proto->params.auth_user) + smtp_client_state(proto, STATE_READY); + else if ((proto->flags & FLAG_TLS) == 0) + smtp_client_cancel(proto, FAIL_IMPL, + "Authentication requires TLS"); + else if ((proto->ext & SMTP_EXT_AUTH) == 0) + smtp_client_cancel(proto, FAIL_IMPL, + "AUTH not supported by remote host"); + else if (proto->ext & SMTP_EXT_AUTH_PLAIN) + smtp_client_state(proto, STATE_AUTH_PLAIN); + else if (proto->ext & SMTP_EXT_AUTH_LOGIN) + smtp_client_state(proto, STATE_AUTH_LOGIN); + else + smtp_client_cancel(proto, FAIL_IMPL, + "No supported AUTH method"); + break; + + case STATE_AUTH_PLAIN: + (void)strlcpy(ibuf, "-", sizeof(ibuf)); + (void)strlcat(ibuf, proto->params.auth_user, sizeof(ibuf)); + if (strlcat(ibuf, ":", sizeof(ibuf)) >= sizeof(ibuf)) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + n = strlcat(ibuf, proto->params.auth_pass, sizeof(ibuf)); + if (n >= sizeof(ibuf)) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + *strchr(ibuf, ':') = '\0'; + ibuf[0] = '\0'; + if (base64_encode(ibuf, n, obuf, sizeof(obuf)) == -1) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + smtp_client_sendcmd(proto, "AUTH PLAIN %s", obuf); + explicit_bzero(ibuf, sizeof ibuf); + explicit_bzero(obuf, sizeof obuf); + break; + + case STATE_AUTH_LOGIN: + smtp_client_sendcmd(proto, "AUTH LOGIN"); + break; + + case STATE_AUTH_LOGIN_USER: + if (base64_encode(proto->params.auth_user, + strlen(proto->params.auth_user), obuf, + sizeof(obuf)) == -1) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + smtp_client_sendcmd(proto, "%s", obuf); + explicit_bzero(obuf, sizeof obuf); + break; + + case STATE_AUTH_LOGIN_PASS: + if (base64_encode(proto->params.auth_pass, + strlen(proto->params.auth_pass), obuf, + sizeof(obuf)) == -1) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + smtp_client_sendcmd(proto, "%s", obuf); + explicit_bzero(obuf, sizeof obuf); + break; + + case STATE_READY: + smtp_ready(proto->tag, proto); + break; + + case STATE_MAIL: + if (proto->ext & SMTP_EXT_DSN) + smtp_client_sendcmd(proto, "MAIL FROM:<%s>%s%s%s%s", + proto->mail->from, + proto->mail->dsn_ret ? " RET=" : "", + proto->mail->dsn_ret ? proto->mail->dsn_ret : "", + proto->mail->dsn_envid ? " ENVID=" : "", + proto->mail->dsn_envid ? proto->mail->dsn_envid : ""); + else + smtp_client_sendcmd(proto, "MAIL FROM:<%s>", + proto->mail->from); + break; + + case STATE_RCPT: + if (proto->rcptidx == proto->mail->rcptcount) { + smtp_client_state(proto, STATE_DATA); + break; + } + rcpt = &proto->mail->rcpt[proto->rcptidx]; + if (proto->ext & SMTP_EXT_DSN) + smtp_client_sendcmd(proto, "RCPT TO:<%s>%s%s%s%s", + rcpt->to, + rcpt->dsn_notify ? " NOTIFY=" : "", + rcpt->dsn_notify ? rcpt->dsn_notify : "", + rcpt->dsn_orcpt ? " ORCPT=" : "", + rcpt->dsn_orcpt ? rcpt->dsn_orcpt : ""); + else + smtp_client_sendcmd(proto, "RCPT TO:<%s>", rcpt->to); + break; + + case STATE_DATA: + if (proto->rcptok == 0) { + smtp_client_mail_abort(proto); + smtp_client_state(proto, STATE_RSET); + } + else + smtp_client_sendcmd(proto, "DATA"); + break; + + case STATE_BODY: + fseek(proto->mail->fp, 0, SEEK_SET); + smtp_client_sendbody(proto); + break; + + case STATE_EOM: + smtp_client_sendcmd(proto, "."); + break; + + case STATE_RSET: + smtp_client_sendcmd(proto, "RSET"); + break; + + case STATE_QUIT: + smtp_client_sendcmd(proto, "QUIT"); + break; + + default: + fatalx("%s: bad state %d", __func__, proto->state); + } +#undef smtp_client_state +} + +/* + * Handle a response to an SMTP command + */ +static void +smtp_client_response(struct smtp_client *proto, const char *line) +{ + struct smtp_rcpt *rcpt; + int i, seen; + + switch (proto->state) { + case STATE_BANNER: + if (line[0] != '2') + smtp_client_abort(proto, FAIL_RESP, line); + else if (proto->params.lmtp) + smtp_client_state(proto, STATE_LHLO); + else + smtp_client_state(proto, STATE_EHLO); + break; + + case STATE_EHLO: + if (line[0] != '2') { + /* + * Either rejected or not implemented. If we want to + * use EHLO extensions, report an SMTP error. + * Otherwise, fallback to using HELO. + */ + if ((proto->params.tls_req == TLS_FORCE) || + (proto->params.auth_user)) + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_HELO); + break; + } + smtp_client_state(proto, STATE_STARTTLS); + break; + + case STATE_HELO: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_LHLO: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_STARTTLS: + if (line[0] != '2') { + if ((proto->params.tls_req == TLS_FORCE) || + (proto->params.auth_user)) { + smtp_client_cancel(proto, FAIL_RESP, line); + break; + } + smtp_client_state(proto, STATE_AUTH); + } + else + smtp_require_tls(proto->tag, proto); + break; + + case STATE_AUTH_PLAIN: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_AUTH_LOGIN: + if (strncmp(line, "334 ", 4)) + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_AUTH_LOGIN_USER); + break; + + case STATE_AUTH_LOGIN_USER: + if (strncmp(line, "334 ", 4)) + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_AUTH_LOGIN_PASS); + break; + + case STATE_AUTH_LOGIN_PASS: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_MAIL: + if (line[0] != '2') { + smtp_client_mail_status(proto, line); + smtp_client_state(proto, STATE_RSET); + } + else + smtp_client_state(proto, STATE_RCPT); + break; + + case STATE_RCPT: + rcpt = &proto->mail->rcpt[proto->rcptidx++]; + if (line[0] != '2') + smtp_client_rcpt_status(proto, rcpt, line); + else { + proto->rcptok++; + smtp_client_state(proto, STATE_RCPT); + } + break; + + case STATE_DATA: + if (line[0] != '2' && line[0] != '3') { + smtp_client_mail_status(proto, line); + smtp_client_state(proto, STATE_RSET); + } + else + smtp_client_state(proto, STATE_BODY); + break; + + case STATE_EOM: + if (proto->params.lmtp) { + /* + * LMTP reports a status of each accepted RCPT. + * Report status for the first pending RCPT and read + * more lines if another rcpt needs a status. + */ + for (i = 0, seen = 0; i < proto->mail->rcptcount; i++) { + rcpt = &proto->mail->rcpt[i]; + if (rcpt->done) + continue; + if (seen) { + io_set_read(proto->io); + return; + } + smtp_client_rcpt_status(proto, + &proto->mail->rcpt[i], line); + seen = 1; + } + } + smtp_client_mail_status(proto, line); + smtp_client_state(proto, STATE_READY); + break; + + case STATE_RSET: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_QUIT: + smtp_client_free(proto); + break; + + default: + fatalx("%s: bad state %d", __func__, proto->state); + } +} + +static void +smtp_client_io(struct io *io, int evt, void *arg) +{ + struct smtp_client *proto = arg; + + log_trace(TRACE_IO, "%p: %s %s", proto, io_strevent(evt), io_strio(io)); + + switch (evt) { + case IO_CONNECTED: + if (proto->params.tls_req == TLS_SMTPS) { + io_set_write(io); + smtp_require_tls(proto->tag, proto); + } + else + smtp_client_state(proto, STATE_BANNER); + break; + + case IO_TLSREADY: + proto->flags |= FLAG_TLS; + io_pause(proto->io, IO_IN); + smtp_verify_server_cert(proto->tag, proto, io_tls(proto->io)); + break; + + case IO_DATAIN: + while (smtp_client_readline(proto)) + ; + break; + + case IO_LOWAT: + if (proto->state == STATE_BODY) + smtp_client_sendbody(proto); + else + io_set_read(io); + break; + + case IO_TIMEOUT: + errno = ETIMEDOUT; + smtp_client_abort(proto, FAIL_CONN, "Connection timeout"); + break; + + case IO_ERROR: + smtp_client_abort(proto, FAIL_CONN, io_error(io)); + break; + + case IO_TLSERROR: + smtp_client_abort(proto, FAIL_CONN, io_error(io)); + break; + + case IO_DISCONNECTED: + smtp_client_abort(proto, FAIL_CONN, io_error(io)); + break; + + default: + fatalx("%s: bad event %d", __func__, evt); + } +} + +/* + * return 1 if a new line is expected. + */ +static int +smtp_client_readline(struct smtp_client *proto) +{ + const char *e; + size_t len; + char *line, *msg, *p; + int cont; + + line = io_getline(proto->io, &len); + if (line == NULL) { + if (io_datalen(proto->io) >= proto->params.linemax) + smtp_client_abort(proto, FAIL_PROTO, "Line too long"); + return 0; + } + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + log_trace(TRACE_SMTPCLT, "%p: <<< %s", proto, line); + + /* Validate SMTP */ + if (len > 3) { + msg = line + 4; + cont = (line[3] == '-'); + } else if (len == 3) { + msg = line + 3; + cont = 0; + } else { + smtp_client_abort(proto, FAIL_PROTO, "Response too short"); + return 0; + } + + /* Validate reply code. */ + if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) || + !isdigit((unsigned char)line[2])) { + smtp_client_abort(proto, FAIL_PROTO, "Invalid reply code"); + return 0; + } + + /* Validate reply message. */ + for (p = msg; *p; p++) + if (!isprint((unsigned char)*p)) { + smtp_client_abort(proto, FAIL_PROTO, + "Non-printable characters in response"); + return 0; + } + + /* Read extensions. */ + if (proto->state == STATE_EHLO) { + if (strcmp(msg, "STARTTLS") == 0) + proto->ext |= SMTP_EXT_STARTTLS; + else if (strncmp(msg, "AUTH ", 5) == 0) { + proto->ext |= SMTP_EXT_AUTH; + if ((p = strstr(msg, " PLAIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + proto->ext |= SMTP_EXT_AUTH_PLAIN; + if ((p = strstr(msg, " LOGIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + proto->ext |= SMTP_EXT_AUTH_LOGIN; + } + else if (strcmp(msg, "PIPELINING") == 0) + proto->ext |= SMTP_EXT_PIPELINING; + else if (strcmp(msg, "DSN") == 0) + proto->ext |= SMTP_EXT_DSN; + else if (strncmp(msg, "SIZE ", 5) == 0) { + proto->ext_size = strtonum(msg + 5, 0, SIZE_T_MAX, &e); + if (e == NULL) + proto->ext |= SMTP_EXT_SIZE; + } + } + + if (smtp_client_replycat(proto, line) == -1) { + smtp_client_abort(proto, FAIL_INTERNAL, NULL); + return 0; + } + + if (cont) + return 1; + + if (io_datalen(proto->io)) { + /* + * There should be no pending data after a response is read, + * except for the multiple status lines after a LMTP message. + * It can also happen with pipelineing, but we don't do that + * for now. + */ + if (!(proto->params.lmtp && proto->state == STATE_EOM)) { + smtp_client_abort(proto, FAIL_PROTO, "Trailing data"); + return 0; + } + } + + io_set_write(proto->io); + smtp_client_response(proto, proto->reply); + return 0; +} + +/* + * Concatenate the given response line. + */ +static int +smtp_client_replycat(struct smtp_client *proto, const char *line) +{ + size_t len; + char *tmp; + int first; + + if (proto->reply && proto->reply[0]) { + /* + * If the line is the continuation of an multi-line response, + * skip the status and ESC parts. First, skip the status, then + * skip the separator amd ESC if found. + */ + first = 0; + line += 3; + if (line[0]) { + line += 1; + if (isdigit((unsigned char)line[0]) && line[1] == '.' && + isdigit((unsigned char)line[2]) && line[3] == '.' && + isdigit((unsigned char)line[4]) && + isspace((unsigned char)line[5])) + line += 5; + } + } else + first = 1; + + if (proto->reply) { + len = strlcat(proto->reply, line, proto->replysz); + if (len < proto->replysz) + return 0; + } + else + len = strlen(line); + + if (len > proto->params.ibufmax) { + errno = EMSGSIZE; + return -1; + } + + /* Allocate by multiples of 2^8 */ + len += (len % 256) ? (256 - (len % 256)) : 0; + + tmp = realloc(proto->reply, len); + if (tmp == NULL) + return -1; + if (proto->reply == NULL) + tmp[0] = '\0'; + + proto->reply = tmp; + proto->replysz = len; + (void)strlcat(proto->reply, line, proto->replysz); + + /* Replace the separator with a space for the first line. */ + if (first && proto->reply[3]) + proto->reply[3] = ' '; + + return 0; +} + +static void +smtp_client_sendbody(struct smtp_client *proto) +{ + ssize_t len; + size_t sz = 0, total, w; + char *ln = NULL; + int n; + + total = io_queued(proto->io); + w = 0; + + while (total < proto->params.obufmax) { + if ((len = getline(&ln, &sz, proto->mail->fp)) == -1) + break; + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + n = io_printf(proto->io, "%s%s\r\n", *ln == '.'?".":"", ln); + if (n == -1) { + free(ln); + smtp_client_abort(proto, FAIL_INTERNAL, NULL); + return; + } + total += n; + w += n; + } + free(ln); + + if (ferror(proto->mail->fp)) { + smtp_client_abort(proto, FAIL_INTERNAL, "Cannot read message"); + return; + } + + log_trace(TRACE_SMTPCLT, "%p: >>> [...%zd bytes...]", proto, w); + + if (feof(proto->mail->fp)) + smtp_client_state(proto, STATE_EOM); +} + +static void +smtp_client_sendcmd(struct smtp_client *proto, char *fmt, ...) +{ + va_list ap; + char *p; + int len; + + va_start(ap, fmt); + len = vasprintf(&p, fmt, ap); + va_end(ap); + + if (len == -1) { + smtp_client_abort(proto, FAIL_INTERNAL, NULL); + return; + } + + log_trace(TRACE_SMTPCLT, "mta: %p: >>> %s", proto, p); + + len = io_printf(proto->io, "%s\r\n", p); + free(p); + + if (len == -1) + smtp_client_abort(proto, FAIL_INTERNAL, NULL); +} + +static void +smtp_client_mail_status(struct smtp_client *proto, const char *status) +{ + int i; + + for (i = 0; i < proto->mail->rcptcount; i++) + smtp_client_rcpt_status(proto, &proto->mail->rcpt[i], status); + + smtp_done(proto->tag, proto, proto->mail); + proto->mail = NULL; +} + +static void +smtp_client_mail_abort(struct smtp_client *proto) +{ + smtp_done(proto->tag, proto, proto->mail); + proto->mail = NULL; +} + +static void +smtp_client_rcpt_status(struct smtp_client *proto, struct smtp_rcpt *rcpt, const char *line) +{ + struct smtp_status status; + + if (rcpt->done) + return; + + rcpt->done = 1; + status.rcpt = rcpt; + status.cmd = strstate[proto->state]; + status.status = line; + smtp_status(proto->tag, proto, &status); +} diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c new file mode 100644 index 00000000..aefce155 --- /dev/null +++ b/usr.sbin/smtpd/smtp_session.c @@ -0,0 +1,3223 @@ +/* $OpenBSD: smtp_session.c,v 1.426 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008-2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" +#include "rfc5322.h" + +#define SMTP_LINE_MAX 65535 +#define DATA_HIWAT 65535 +#define APPEND_DOMAIN_BUFFER_SIZE SMTP_LINE_MAX + +enum smtp_state { + STATE_NEW = 0, + STATE_CONNECTED, + STATE_TLS, + STATE_HELO, + STATE_AUTH_INIT, + STATE_AUTH_USERNAME, + STATE_AUTH_PASSWORD, + STATE_AUTH_FINALIZE, + STATE_BODY, + STATE_QUIT, +}; + +enum session_flags { + SF_EHLO = 0x0001, + SF_8BITMIME = 0x0002, + SF_SECURE = 0x0004, + SF_AUTHENTICATED = 0x0008, + SF_BOUNCE = 0x0010, + SF_VERIFIED = 0x0020, + SF_BADINPUT = 0x0080, +}; + +enum { + TX_OK = 0, + TX_ERROR_ENVELOPE, + TX_ERROR_SIZE, + TX_ERROR_IO, + TX_ERROR_LOOP, + TX_ERROR_MALFORMED, + TX_ERROR_RESOURCES, + TX_ERROR_INTERNAL, +}; + +enum smtp_command { + CMD_HELO = 0, + CMD_EHLO, + CMD_STARTTLS, + CMD_AUTH, + CMD_MAIL_FROM, + CMD_RCPT_TO, + CMD_DATA, + CMD_RSET, + CMD_QUIT, + CMD_HELP, + CMD_WIZ, + CMD_NOOP, + CMD_COMMIT, +}; + +struct smtp_rcpt { + TAILQ_ENTRY(smtp_rcpt) entry; + uint64_t evpid; + struct mailaddr maddr; + size_t destcount; +}; + +struct smtp_tx { + struct smtp_session *session; + uint32_t msgid; + + struct envelope evp; + size_t rcptcount; + size_t destcount; + TAILQ_HEAD(, smtp_rcpt) rcpts; + + time_t time; + int error; + size_t datain; + size_t odatalen; + FILE *ofile; + struct io *filter; + struct rfc5322_parser *parser; + int rcvcount; + int has_date; + int has_message_id; + + uint8_t junk; +}; + +struct smtp_session { + uint64_t id; + struct io *io; + struct listener *listener; + void *ssl_ctx; + struct sockaddr_storage ss; + char rdns[HOST_NAME_MAX+1]; + char smtpname[HOST_NAME_MAX+1]; + int fcrdns; + + int flags; + enum smtp_state state; + + uint8_t banner_sent; + char helo[LINE_MAX]; + char cmd[LINE_MAX]; + char username[SMTPD_MAXMAILADDRSIZE]; + + size_t mailcount; + struct event pause; + + struct smtp_tx *tx; + + enum smtp_command last_cmd; + enum filter_phase filter_phase; + const char *filter_param; + + uint8_t junk; +}; + +#define ADVERTISE_TLS(s) \ + ((s)->listener->flags & F_STARTTLS && !((s)->flags & SF_SECURE)) + +#define ADVERTISE_AUTH(s) \ + ((s)->listener->flags & F_AUTH && (s)->flags & SF_SECURE && \ + !((s)->flags & SF_AUTHENTICATED)) + +#define ADVERTISE_EXT_DSN(s) \ + ((s)->listener->flags & F_EXT_DSN) + +#define SESSION_FILTERED(s) \ + ((s)->listener->flags & F_FILTERED) + +#define SESSION_DATA_FILTERED(s) \ + ((s)->listener->flags & F_FILTERED) + + +static int smtp_mailaddr(struct mailaddr *, char *, int, char **, const char *); +static void smtp_session_init(void); +static void smtp_lookup_servername(struct smtp_session *); +static void smtp_getnameinfo_cb(void *, int, const char *, const char *); +static void smtp_getaddrinfo_cb(void *, int, struct addrinfo *); +static void smtp_connected(struct smtp_session *); +static void smtp_send_banner(struct smtp_session *); +static void smtp_tls_verified(struct smtp_session *); +static void smtp_io(struct io *, int, void *); +static void smtp_enter_state(struct smtp_session *, int); +static void smtp_reply(struct smtp_session *, char *, ...); +static void smtp_command(struct smtp_session *, char *); +static void smtp_rfc4954_auth_plain(struct smtp_session *, char *); +static void smtp_rfc4954_auth_login(struct smtp_session *, char *); +static void smtp_free(struct smtp_session *, const char *); +static const char *smtp_strstate(int); +static void smtp_cert_init(struct smtp_session *); +static void smtp_cert_init_cb(void *, int, const char *, const void *, size_t); +static void smtp_cert_verify(struct smtp_session *); +static void smtp_cert_verify_cb(void *, int); +static void smtp_auth_failure_pause(struct smtp_session *); +static void smtp_auth_failure_resume(int, short, void *); + +static int smtp_tx(struct smtp_session *); +static void smtp_tx_free(struct smtp_tx *); +static void smtp_tx_create_message(struct smtp_tx *); +static void smtp_tx_mail_from(struct smtp_tx *, const char *); +static void smtp_tx_rcpt_to(struct smtp_tx *, const char *); +static void smtp_tx_open_message(struct smtp_tx *); +static void smtp_tx_commit(struct smtp_tx *); +static void smtp_tx_rollback(struct smtp_tx *); +static int smtp_tx_dataline(struct smtp_tx *, const char *); +static int smtp_tx_filtered_dataline(struct smtp_tx *, const char *); +static void smtp_tx_eom(struct smtp_tx *); +static void smtp_filter_fd(struct smtp_tx *, int); +static int smtp_message_fd(struct smtp_tx *, int); +static void smtp_message_begin(struct smtp_tx *); +static void smtp_message_end(struct smtp_tx *); +static int smtp_filter_printf(struct smtp_tx *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +static int smtp_message_printf(struct smtp_tx *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); + +static int smtp_check_rset(struct smtp_session *, const char *); +static int smtp_check_helo(struct smtp_session *, const char *); +static int smtp_check_ehlo(struct smtp_session *, const char *); +static int smtp_check_auth(struct smtp_session *s, const char *); +static int smtp_check_starttls(struct smtp_session *, const char *); +static int smtp_check_mail_from(struct smtp_session *, const char *); +static int smtp_check_rcpt_to(struct smtp_session *, const char *); +static int smtp_check_data(struct smtp_session *, const char *); +static int smtp_check_noparam(struct smtp_session *, const char *); + +static void smtp_filter_phase(enum filter_phase, struct smtp_session *, const char *); + +static void smtp_proceed_connected(struct smtp_session *); +static void smtp_proceed_rset(struct smtp_session *, const char *); +static void smtp_proceed_helo(struct smtp_session *, const char *); +static void smtp_proceed_ehlo(struct smtp_session *, const char *); +static void smtp_proceed_auth(struct smtp_session *, const char *); +static void smtp_proceed_starttls(struct smtp_session *, const char *); +static void smtp_proceed_mail_from(struct smtp_session *, const char *); +static void smtp_proceed_rcpt_to(struct smtp_session *, const char *); +static void smtp_proceed_data(struct smtp_session *, const char *); +static void smtp_proceed_noop(struct smtp_session *, const char *); +static void smtp_proceed_help(struct smtp_session *, const char *); +static void smtp_proceed_wiz(struct smtp_session *, const char *); +static void smtp_proceed_quit(struct smtp_session *, const char *); +static void smtp_proceed_commit(struct smtp_session *, const char *); +static void smtp_proceed_rollback(struct smtp_session *, const char *); + +static void smtp_filter_begin(struct smtp_session *); +static void smtp_filter_end(struct smtp_session *); +static void smtp_filter_data_begin(struct smtp_session *); +static void smtp_filter_data_end(struct smtp_session *); + +static void smtp_report_link_connect(struct smtp_session *, const char *, int, + const struct sockaddr_storage *, + const struct sockaddr_storage *); +static void smtp_report_link_greeting(struct smtp_session *, const char *); +static void smtp_report_link_identify(struct smtp_session *, const char *, const char *); +static void smtp_report_link_tls(struct smtp_session *, const char *); +static void smtp_report_link_disconnect(struct smtp_session *); +static void smtp_report_link_auth(struct smtp_session *, const char *, const char *); +static void smtp_report_tx_reset(struct smtp_session *, uint32_t); +static void smtp_report_tx_begin(struct smtp_session *, uint32_t); +static void smtp_report_tx_mail(struct smtp_session *, uint32_t, const char *, int); +static void smtp_report_tx_rcpt(struct smtp_session *, uint32_t, const char *, int); +static void smtp_report_tx_envelope(struct smtp_session *, uint32_t, uint64_t); +static void smtp_report_tx_data(struct smtp_session *, uint32_t, int); +static void smtp_report_tx_commit(struct smtp_session *, uint32_t, size_t); +static void smtp_report_tx_rollback(struct smtp_session *, uint32_t); +static void smtp_report_protocol_client(struct smtp_session *, const char *); +static void smtp_report_protocol_server(struct smtp_session *, const char *); +static void smtp_report_filter_response(struct smtp_session *, int, int, const char *); +static void smtp_report_timeout(struct smtp_session *); + + +/* musl work-around */ +void portable_freeaddrinfo(struct addrinfo *); + + +static struct { + int code; + enum filter_phase filter_phase; + const char *cmd; + + int (*check)(struct smtp_session *, const char *); + void (*proceed)(struct smtp_session *, const char *); +} commands[] = { + { CMD_HELO, FILTER_HELO, "HELO", smtp_check_helo, smtp_proceed_helo }, + { CMD_EHLO, FILTER_EHLO, "EHLO", smtp_check_ehlo, smtp_proceed_ehlo }, + { CMD_STARTTLS, FILTER_STARTTLS, "STARTTLS", smtp_check_starttls, smtp_proceed_starttls }, + { CMD_AUTH, FILTER_AUTH, "AUTH", smtp_check_auth, smtp_proceed_auth }, + { CMD_MAIL_FROM, FILTER_MAIL_FROM, "MAIL FROM", smtp_check_mail_from, smtp_proceed_mail_from }, + { CMD_RCPT_TO, FILTER_RCPT_TO, "RCPT TO", smtp_check_rcpt_to, smtp_proceed_rcpt_to }, + { CMD_DATA, FILTER_DATA, "DATA", smtp_check_data, smtp_proceed_data }, + { CMD_RSET, FILTER_RSET, "RSET", smtp_check_rset, smtp_proceed_rset }, + { CMD_QUIT, FILTER_QUIT, "QUIT", smtp_check_noparam, smtp_proceed_quit }, + { CMD_NOOP, FILTER_NOOP, "NOOP", smtp_check_noparam, smtp_proceed_noop }, + { CMD_HELP, FILTER_HELP, "HELP", smtp_check_noparam, smtp_proceed_help }, + { CMD_WIZ, FILTER_WIZ, "WIZ", smtp_check_noparam, smtp_proceed_wiz }, + { CMD_COMMIT, FILTER_COMMIT, ".", smtp_check_noparam, smtp_proceed_commit }, + { -1, 0, NULL, NULL }, +}; + +static struct tree wait_lka_helo; +static struct tree wait_lka_mail; +static struct tree wait_lka_rcpt; +static struct tree wait_parent_auth; +static struct tree wait_queue_msg; +static struct tree wait_queue_fd; +static struct tree wait_queue_commit; +static struct tree wait_ssl_init; +static struct tree wait_ssl_verify; +static struct tree wait_filters; +static struct tree wait_filter_fd; + +static void +header_append_domain_buffer(char *buffer, char *domain, size_t len) +{ + size_t i; + int escape, quote, comment, bracket; + int has_domain, has_bracket, has_group; + int pos_bracket, pos_component, pos_insert; + char copy[APPEND_DOMAIN_BUFFER_SIZE]; + + escape = quote = comment = bracket = 0; + has_domain = has_bracket = has_group = 0; + pos_bracket = pos_insert = pos_component = 0; + for (i = 0; buffer[i]; ++i) { + if (buffer[i] == '(' && !escape && !quote) + comment++; + if (buffer[i] == '"' && !escape && !comment) + quote = !quote; + if (buffer[i] == ')' && !escape && !quote && comment) + comment--; + if (buffer[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { + bracket++; + has_bracket = 1; + } + if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { + bracket--; + pos_bracket = i; + } + if (buffer[i] == '@' && !escape && !comment && !quote) + has_domain = 1; + if (buffer[i] == ':' && !escape && !comment && !quote) + has_group = 1; + + /* update insert point if not in comment and not on a whitespace */ + if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) + pos_component = i; + } + + /* parse error, do not attempt to modify */ + if (escape || quote || comment || bracket) + return; + + /* domain already present, no need to modify */ + if (has_domain) + return; + + /* address is group, skip */ + if (has_group) + return; + + /* there's an address between brackets, just append domain */ + if (has_bracket) { + pos_bracket--; + while (isspace((unsigned char)buffer[pos_bracket])) + pos_bracket--; + if (buffer[pos_bracket] == '<') + return; + pos_insert = pos_bracket + 1; + } + else { + /* otherwise append address to last component */ + pos_insert = pos_component + 1; + + /* empty address */ + if (buffer[pos_component] == '\0' || + isspace((unsigned char)buffer[pos_component])) + return; + } + + if (snprintf(copy, sizeof copy, "%.*s@%s%s", + (int)pos_insert, buffer, + domain, + buffer+pos_insert) >= (int)sizeof copy) + return; + + memcpy(buffer, copy, len); +} + +static void +header_address_rewrite_buffer(char *buffer, const char *address, size_t len) +{ + size_t i; + int address_len; + int escape, quote, comment, bracket; + int has_bracket, has_group; + int pos_bracket_beg, pos_bracket_end, pos_component_beg, pos_component_end; + int insert_beg, insert_end; + char copy[APPEND_DOMAIN_BUFFER_SIZE]; + + escape = quote = comment = bracket = 0; + has_bracket = has_group = 0; + pos_bracket_beg = pos_bracket_end = pos_component_beg = pos_component_end = 0; + for (i = 0; buffer[i]; ++i) { + if (buffer[i] == '(' && !escape && !quote) + comment++; + if (buffer[i] == '"' && !escape && !comment) + quote = !quote; + if (buffer[i] == ')' && !escape && !quote && comment) + comment--; + if (buffer[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { + bracket++; + has_bracket = 1; + pos_bracket_beg = i+1; + } + if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { + bracket--; + pos_bracket_end = i; + } + if (buffer[i] == ':' && !escape && !comment && !quote) + has_group = 1; + + /* update insert point if not in comment and not on a whitespace */ + if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) + pos_component_end = i; + } + + /* parse error, do not attempt to modify */ + if (escape || quote || comment || bracket) + return; + + /* address is group, skip */ + if (has_group) + return; + + /* there's an address between brackets, just replace everything brackets */ + if (has_bracket) { + insert_beg = pos_bracket_beg; + insert_end = pos_bracket_end; + } + else { + if (pos_component_end == 0) + pos_component_beg = 0; + else { + for (pos_component_beg = pos_component_end; pos_component_beg >= 0; --pos_component_beg) + if (buffer[pos_component_beg] == ')' || isspace((unsigned char)buffer[pos_component_beg])) + break; + pos_component_beg += 1; + pos_component_end += 1; + } + insert_beg = pos_component_beg; + insert_end = pos_component_end; + } + + /* check that masquerade won' t overflow */ + address_len = strlen(address); + if (strlen(buffer) - (insert_end - insert_beg) + address_len >= len) + return; + + (void)strlcpy(copy, buffer, sizeof copy); + (void)strlcpy(copy+insert_beg, address, sizeof (copy) - insert_beg); + (void)strlcat(copy, buffer+insert_end, sizeof (copy)); + memcpy(buffer, copy, len); +} + +static void +header_domain_append_callback(struct smtp_tx *tx, const char *hdr, + const char *val) +{ + size_t i, j, linelen; + int escape, quote, comment, skip; + char buffer[APPEND_DOMAIN_BUFFER_SIZE]; + const char *line, *end; + + if (smtp_message_printf(tx, "%s:", hdr) == -1) + return; + + j = 0; + escape = quote = comment = skip = 0; + memset(buffer, 0, sizeof buffer); + + for (line = val; line; line = end) { + end = strchr(line, '\n'); + if (end) { + linelen = end - line; + end++; + } + else + linelen = strlen(line); + + for (i = 0; i < linelen; ++i) { + if (line[i] == '(' && !escape && !quote) + comment++; + if (line[i] == '"' && !escape && !comment) + quote = !quote; + if (line[i] == ')' && !escape && !quote && comment) + comment--; + if (line[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + + /* found a separator, buffer contains a full address */ + if (line[i] == ',' && !escape && !quote && !comment) { + if (!skip && j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { + header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0] && + tx->session->listener->flags & F_MASQUERADE && + !(strcasecmp(hdr, "From"))) + header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), + sizeof buffer); + } + if (smtp_message_printf(tx, "%s,", buffer) == -1) + return; + j = 0; + skip = 0; + memset(buffer, 0, sizeof buffer); + } + else { + if (skip) { + if (smtp_message_printf(tx, "%c", line[i]) == -1) + return; + } + else { + buffer[j++] = line[i]; + if (j == sizeof (buffer) - 1) { + if (smtp_message_printf(tx, "%s", buffer) == -1) + return; + skip = 1; + j = 0; + memset(buffer, 0, sizeof buffer); + } + } + } + } + if (skip) { + if (smtp_message_printf(tx, "\n") == -1) + return; + } + else { + buffer[j++] = '\n'; + if (j == sizeof (buffer) - 1) { + if (smtp_message_printf(tx, "%s", buffer) == -1) + return; + skip = 1; + j = 0; + memset(buffer, 0, sizeof buffer); + } + } + } + + /* end of header, if buffer is not empty we'll process it */ + if (buffer[0]) { + if (j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { + header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0] && + tx->session->listener->flags & F_MASQUERADE && + !(strcasecmp(hdr, "From"))) + header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), + sizeof buffer); + } + smtp_message_printf(tx, "%s", buffer); + } +} + +static void +smtp_session_init(void) +{ + static int init = 0; + + if (!init) { + tree_init(&wait_lka_helo); + tree_init(&wait_lka_mail); + tree_init(&wait_lka_rcpt); + tree_init(&wait_parent_auth); + tree_init(&wait_queue_msg); + tree_init(&wait_queue_fd); + tree_init(&wait_queue_commit); + tree_init(&wait_ssl_init); + tree_init(&wait_ssl_verify); + tree_init(&wait_filters); + tree_init(&wait_filter_fd); + init = 1; + } +} + +int +smtp_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, const char *hostname, struct io *io) +{ + struct smtp_session *s; + + smtp_session_init(); + + if ((s = calloc(1, sizeof(*s))) == NULL) + return (-1); + + s->id = generate_uid(); + s->listener = listener; + memmove(&s->ss, ss, sizeof(*ss)); + + if (io != NULL) + s->io = io; + else + s->io = io_new(); + + io_set_callback(s->io, smtp_io, s); + io_set_fd(s->io, sock); + io_set_timeout(s->io, SMTPD_SESSION_TIMEOUT * 1000); + io_set_write(s->io); + s->state = STATE_NEW; + + (void)strlcpy(s->smtpname, listener->hostname, sizeof(s->smtpname)); + + log_trace(TRACE_SMTP, "smtp: %p: connected to listener %p " + "[hostname=%s, port=%d, tag=%s]", s, listener, + listener->hostname, ntohs(listener->port), listener->tag); + + /* For local enqueueing, the hostname is already set */ + if (hostname) { + s->flags |= SF_AUTHENTICATED; + /* A bit of a hack */ + if (!strcmp(hostname, "localhost")) + s->flags |= SF_BOUNCE; + (void)strlcpy(s->rdns, hostname, sizeof(s->rdns)); + s->fcrdns = 1; + smtp_lookup_servername(s); + } else { + resolver_getnameinfo((struct sockaddr *)&s->ss, NI_NAMEREQD, + smtp_getnameinfo_cb, s); + } + + /* session may have been freed by now */ + + return (0); +} + +static void +smtp_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) +{ + struct smtp_session *s = arg; + struct addrinfo hints; + + if (gaierrno) { + (void)strlcpy(s->rdns, "", sizeof(s->rdns)); + + if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) + s->fcrdns = 0; + else { + log_warnx("getnameinfo: %s: %s", ss_to_text(&s->ss), + gai_strerror(gaierrno)); + s->fcrdns = -1; + } + + smtp_lookup_servername(s); + return; + } + + (void)strlcpy(s->rdns, host, sizeof(s->rdns)); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = s->ss.ss_family; + hints.ai_socktype = SOCK_STREAM; + resolver_getaddrinfo(s->rdns, NULL, &hints, smtp_getaddrinfo_cb, s); +} + +static void +smtp_getaddrinfo_cb(void *arg, int gaierrno, struct addrinfo *ai0) +{ + struct smtp_session *s = arg; + struct addrinfo *ai; + char fwd[64], rev[64]; + + if (gaierrno) { + if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) + s->fcrdns = 0; + else { + log_warnx("getaddrinfo: %s: %s", s->rdns, + gai_strerror(gaierrno)); + s->fcrdns = -1; + } + } + else { + strlcpy(rev, ss_to_text(&s->ss), sizeof(rev)); + for (ai = ai0; ai; ai = ai->ai_next) { + strlcpy(fwd, sa_to_text(ai->ai_addr), sizeof(fwd)); + if (!strcmp(fwd, rev)) { + s->fcrdns = 1; + break; + } + } + portable_freeaddrinfo(ai0); + } + + smtp_lookup_servername(s); +} + +void +smtp_session_imsg(struct mproc *p, struct imsg *imsg) +{ + struct smtp_session *s; + struct smtp_rcpt *rcpt; + char user[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTP_LINE_MAX]; + struct msg m; + const char *line, *helo; + uint64_t reqid, evpid; + uint32_t msgid; + int status, success; + int filter_response; + const char *filter_param; + uint8_t i; + + switch (imsg->hdr.type) { + + case IMSG_SMTP_CHECK_SENDER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + m_end(&m); + s = tree_xpop(&wait_lka_mail, reqid); + switch (status) { + case LKA_OK: + smtp_tx_create_message(s->tx); + break; + + case LKA_PERMFAIL: + smtp_tx_free(s->tx); + smtp_reply(s, "%d %s", 530, "Sender rejected"); + break; + case LKA_TEMPFAIL: + smtp_tx_free(s->tx); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + break; + } + return; + + case IMSG_SMTP_EXPAND_RCPT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + m_get_string(&m, &line); + m_end(&m); + s = tree_xpop(&wait_lka_rcpt, reqid); + + tmp[0] = '\0'; + if (s->tx->evp.rcpt.user[0]) { + (void)strlcpy(tmp, s->tx->evp.rcpt.user, sizeof tmp); + if (s->tx->evp.rcpt.domain[0]) { + (void)strlcat(tmp, "@", sizeof tmp); + (void)strlcat(tmp, s->tx->evp.rcpt.domain, + sizeof tmp); + } + } + + switch (status) { + case LKA_OK: + fatalx("unexpected ok"); + case LKA_PERMFAIL: + smtp_reply(s, "%s: <%s>", line, tmp); + break; + case LKA_TEMPFAIL: + smtp_reply(s, "%s: <%s>", line, tmp); + break; + } + return; + + case IMSG_SMTP_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + s = tree_xpop(&wait_lka_helo, reqid); + m_get_int(&m, &status); + if (status == LKA_OK) { + m_get_string(&m, &helo); + (void)strlcpy(s->smtpname, helo, sizeof(s->smtpname)); + } + m_end(&m); + smtp_connected(s); + return; + + case IMSG_SMTP_MESSAGE_CREATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + s = tree_xpop(&wait_queue_msg, reqid); + if (success) { + m_get_msgid(&m, &msgid); + s->tx->msgid = msgid; + s->tx->evp.id = msgid_to_evpid(msgid); + s->tx->rcptcount = 0; + smtp_reply(s, "250 %s Ok", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + } else { + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_tx_free(s->tx); + smtp_enter_state(s, STATE_QUIT); + } + m_end(&m); + return; + + case IMSG_SMTP_MESSAGE_OPEN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_queue_fd, reqid); + if (!success || imsg->fd == -1) { + if (imsg->fd != -1) + close(imsg->fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + log_debug("smtp: %p: fd %d from queue", s, imsg->fd); + + if (smtp_message_fd(s->tx, imsg->fd)) { + if (!SESSION_DATA_FILTERED(s)) + smtp_message_begin(s->tx); + else + smtp_filter_data_begin(s); + } + return; + + case IMSG_FILTER_SMTP_DATA_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_filter_fd, reqid); + if (!success || imsg->fd == -1) { + if (imsg->fd != -1) + close(imsg->fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + log_debug("smtp: %p: fd %d from lka", s, imsg->fd); + + smtp_filter_fd(s->tx, imsg->fd); + smtp_message_begin(s->tx); + return; + + case IMSG_QUEUE_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + s = tree_xget(&wait_lka_rcpt, reqid); + if (success) { + m_get_evpid(&m, &evpid); + s->tx->evp.id = evpid; + s->tx->destcount++; + smtp_report_tx_envelope(s, s->tx->msgid, evpid); + } + else + s->tx->error = TX_ERROR_ENVELOPE; + m_end(&m); + return; + + case IMSG_QUEUE_ENVELOPE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + if (!success) + fatalx("commit evp failed: not supposed to happen"); + s = tree_xpop(&wait_lka_rcpt, reqid); + if (s->tx->error) { + /* + * If an envelope failed, we can't cancel the last + * RCPT only so we must cancel the whole transaction + * and close the connection. + */ + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + } + else { + rcpt = xcalloc(1, sizeof(*rcpt)); + rcpt->evpid = s->tx->evp.id; + rcpt->destcount = s->tx->destcount; + rcpt->maddr = s->tx->evp.rcpt; + TAILQ_INSERT_TAIL(&s->tx->rcpts, rcpt, entry); + + s->tx->destcount = 0; + s->tx->rcptcount++; + smtp_reply(s, "250 %s %s: Recipient ok", + esc_code(ESC_STATUS_OK, ESC_DESTINATION_ADDRESS_VALID), + esc_description(ESC_DESTINATION_ADDRESS_VALID)); + } + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + s = tree_xpop(&wait_queue_commit, reqid); + if (!success) { + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_tx_free(s->tx); + smtp_enter_state(s, STATE_QUIT); + return; + } + + smtp_reply(s, "250 %s %08x Message accepted for delivery", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS), + s->tx->msgid); + smtp_report_tx_commit(s, s->tx->msgid, s->tx->odatalen); + smtp_report_tx_reset(s, s->tx->msgid); + + log_info("%016"PRIx64" smtp message " + "msgid=%08x size=%zu nrcpt=%zu proto=%s", + s->id, + s->tx->msgid, + s->tx->odatalen, + s->tx->rcptcount, + s->flags & SF_EHLO ? "ESMTP" : "SMTP"); + TAILQ_FOREACH(rcpt, &s->tx->rcpts, entry) { + log_info("%016"PRIx64" smtp envelope " + "evpid=%016"PRIx64" from=<%s%s%s> to=<%s%s%s>", + s->id, + rcpt->evpid, + s->tx->evp.sender.user, + s->tx->evp.sender.user[0] == '\0' ? "" : "@", + s->tx->evp.sender.domain, + rcpt->maddr.user, + rcpt->maddr.user[0] == '\0' ? "" : "@", + rcpt->maddr.domain); + } + smtp_tx_free(s->tx); + s->mailcount++; + smtp_enter_state(s, STATE_HELO); + return; + + case IMSG_SMTP_AUTHENTICATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_parent_auth, reqid); + strnvis(user, s->username, sizeof user, VIS_WHITE | VIS_SAFE); + if (success == LKA_OK) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=ok", + s->id, user); + s->flags |= SF_AUTHENTICATED; + smtp_report_link_auth(s, user, "pass"); + smtp_reply(s, "235 %s Authentication succeeded", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + } + else if (success == LKA_PERMFAIL) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=permfail", + s->id, user); + smtp_report_link_auth(s, user, "fail"); + smtp_auth_failure_pause(s); + return; + } + else if (success == LKA_TEMPFAIL) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=tempfail", + s->id, user); + smtp_report_link_auth(s, user, "error"); + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + } + else + fatalx("bad lka response"); + + smtp_enter_state(s, STATE_HELO); + return; + + case IMSG_FILTER_SMTP_PROTOCOL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_response); + if (filter_response != FILTER_PROCEED && + filter_response != FILTER_JUNK) + m_get_string(&m, &filter_param); + else + filter_param = NULL; + m_end(&m); + + s = tree_xpop(&wait_filters, reqid); + + switch (filter_response) { + case FILTER_REJECT: + case FILTER_DISCONNECT: + if (!valid_smtp_response(filter_param) || + (filter_param[0] != '4' && filter_param[0] != '5')) + filter_param = "421 Internal server error"; + if (!strncmp(filter_param, "421", 3)) + filter_response = FILTER_DISCONNECT; + + smtp_report_filter_response(s, s->filter_phase, + filter_response, filter_param); + + smtp_reply(s, "%s", filter_param); + + if (filter_response == FILTER_DISCONNECT) + smtp_enter_state(s, STATE_QUIT); + else if (s->filter_phase == FILTER_COMMIT) + smtp_proceed_rollback(s, NULL); + break; + + + case FILTER_JUNK: + if (s->tx) + s->tx->junk = 1; + else + s->junk = 1; + /* fallthrough */ + + case FILTER_PROCEED: + filter_param = s->filter_param; + /* fallthrough */ + + case FILTER_REWRITE: + smtp_report_filter_response(s, s->filter_phase, + filter_response, + filter_param == s->filter_param ? NULL : filter_param); + if (s->filter_phase == FILTER_CONNECT) { + smtp_proceed_connected(s); + return; + } + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == s->filter_phase) { + if (filter_response == FILTER_REWRITE) + if (!commands[i].check(s, filter_param)) + break; + commands[i].proceed(s, filter_param); + break; + } + break; + } + return; + } + + log_warnx("smtp_session_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); + fatalx(NULL); +} + +static void +smtp_tls_verified(struct smtp_session *s) +{ + X509 *x; + + x = SSL_get_peer_certificate(io_tls(s->io)); + if (x) { + log_info("%016"PRIx64" smtp " + "client-cert-check result=\"%s\"", + s->id, + (s->flags & SF_VERIFIED) ? "success" : "failure"); + X509_free(x); + } + + if (s->listener->flags & F_SMTPS) { + stat_increment("smtp.smtps", 1); + io_set_write(s->io); + smtp_send_banner(s); + } + else { + stat_increment("smtp.tls", 1); + smtp_enter_state(s, STATE_HELO); + } +} + +static void +smtp_io(struct io *io, int evt, void *arg) +{ + struct smtp_session *s = arg; + char *line; + size_t len; + int eom; + + log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + + case IO_TLSREADY: + log_info("%016"PRIx64" smtp tls ciphers=%s", + s->id, ssl_to_text(io_tls(s->io))); + + smtp_report_link_tls(s, ssl_to_text(io_tls(s->io))); + + s->flags |= SF_SECURE; + s->helo[0] = '\0'; + + smtp_cert_verify(s); + break; + + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if ((line == NULL && io_datalen(s->io) >= SMTP_LINE_MAX) || + (line && len >= SMTP_LINE_MAX)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s Line too long", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + + /* No complete line received */ + if (line == NULL) + return; + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + /* Message body */ + eom = 0; + if (s->state == STATE_BODY) { + if (strcmp(line, ".")) { + s->tx->datain += strlen(line) + 1; + if (s->tx->datain > env->sc_maxsize) + s->tx->error = TX_ERROR_SIZE; + } + eom = (s->tx->filter == NULL) ? + smtp_tx_dataline(s->tx, line) : + smtp_tx_filtered_dataline(s->tx, line); + if (eom == 0) + goto nextline; + } + + /* Pipelining not supported */ + if (io_datalen(s->io)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s %s: Pipelining not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + + if (eom) { + io_set_write(io); + if (s->tx->filter == NULL) + smtp_tx_eom(s->tx); + return; + } + + /* Must be a command */ + if (strlcpy(s->cmd, line, sizeof(s->cmd)) >= sizeof(s->cmd)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s Command line too long", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + io_set_write(io); + smtp_command(s, line); + break; + + case IO_LOWAT: + if (s->state == STATE_QUIT) { + log_info("%016"PRIx64" smtp disconnected " + "reason=quit", + s->id); + smtp_free(s, "done"); + break; + } + + /* Wait for the client to start tls */ + if (s->state == STATE_TLS) { + smtp_cert_init(s); + break; + } + + io_set_read(io); + break; + + case IO_TIMEOUT: + log_info("%016"PRIx64" smtp disconnected " + "reason=timeout", + s->id); + smtp_report_timeout(s); + smtp_free(s, "timeout"); + break; + + case IO_DISCONNECTED: + log_info("%016"PRIx64" smtp disconnected " + "reason=disconnect", + s->id); + smtp_free(s, "disconnected"); + break; + + case IO_ERROR: + log_info("%016"PRIx64" smtp disconnected " + "reason=\"io-error: %s\"", + s->id, io_error(io)); + smtp_free(s, "IO error"); + break; + + default: + fatalx("smtp_io()"); + } +} + +static void +smtp_command(struct smtp_session *s, char *line) +{ + char *args; + int cmd, i; + + log_trace(TRACE_SMTP, "smtp: %p: <<< %s", s, line); + + /* + * These states are special. + */ + if (s->state == STATE_AUTH_INIT) { + smtp_report_protocol_client(s, "********"); + smtp_rfc4954_auth_plain(s, line); + return; + } + if (s->state == STATE_AUTH_USERNAME || s->state == STATE_AUTH_PASSWORD) { + smtp_report_protocol_client(s, "********"); + smtp_rfc4954_auth_login(s, line); + return; + } + + if (s->state == STATE_HELO && strncasecmp(line, "AUTH PLAIN ", 11) == 0) + smtp_report_protocol_client(s, "AUTH PLAIN ********"); + else + smtp_report_protocol_client(s, line); + + + /* + * Unlike other commands, "mail from" and "rcpt to" contain a + * space in the command name. + */ + if (strncasecmp("mail from:", line, 10) == 0 || + strncasecmp("rcpt to:", line, 8) == 0) + args = strchr(line, ':'); + else + args = strchr(line, ' '); + + if (args) { + *args++ = '\0'; + while (isspace((unsigned char)*args)) + args++; + } + + cmd = -1; + for (i = 0; commands[i].code != -1; i++) + if (!strcasecmp(line, commands[i].cmd)) { + cmd = commands[i].code; + break; + } + + s->last_cmd = cmd; + switch (cmd) { + /* + * INIT + */ + case CMD_HELO: + if (!smtp_check_helo(s, args)) + break; + smtp_filter_phase(FILTER_HELO, s, args); + break; + + case CMD_EHLO: + if (!smtp_check_ehlo(s, args)) + break; + smtp_filter_phase(FILTER_EHLO, s, args); + break; + + /* + * SETUP + */ + case CMD_STARTTLS: + if (!smtp_check_starttls(s, args)) + break; + + smtp_filter_phase(FILTER_STARTTLS, s, NULL); + break; + + case CMD_AUTH: + if (!smtp_check_auth(s, args)) + break; + smtp_filter_phase(FILTER_AUTH, s, args); + break; + + case CMD_MAIL_FROM: + if (!smtp_check_mail_from(s, args)) + break; + smtp_filter_phase(FILTER_MAIL_FROM, s, args); + break; + + /* + * TRANSACTION + */ + case CMD_RCPT_TO: + if (!smtp_check_rcpt_to(s, args)) + break; + smtp_filter_phase(FILTER_RCPT_TO, s, args); + break; + + case CMD_RSET: + if (!smtp_check_rset(s, args)) + break; + smtp_filter_phase(FILTER_RSET, s, NULL); + break; + + case CMD_DATA: + if (!smtp_check_data(s, args)) + break; + smtp_filter_phase(FILTER_DATA, s, NULL); + break; + + /* + * ANY + */ + case CMD_QUIT: + if (!smtp_check_noparam(s, args)) + break; + smtp_filter_phase(FILTER_QUIT, s, NULL); + break; + + case CMD_NOOP: + if (!smtp_check_noparam(s, args)) + break; + smtp_filter_phase(FILTER_NOOP, s, NULL); + break; + + case CMD_HELP: + if (!smtp_check_noparam(s, args)) + break; + smtp_proceed_help(s, NULL); + break; + + case CMD_WIZ: + if (!smtp_check_noparam(s, args)) + break; + smtp_proceed_wiz(s, NULL); + break; + + default: + smtp_reply(s, "500 %s %s: Command unrecognized", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + break; + } +} + +static int +smtp_check_rset(struct smtp_session *s, const char *args) +{ + if (!smtp_check_noparam(s, args)) + return 0; + + if (s->helo[0] == '\0') { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + return 1; +} + +static int +smtp_check_helo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: HELO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_ehlo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: EHLO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_auth(struct smtp_session *s, const char *args) +{ + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->flags & SF_AUTHENTICATED) { + smtp_reply(s, "503 %s %s: Already authenticated", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!ADVERTISE_AUTH(s)) { + smtp_reply(s, "503 %s %s: Command not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: No parameters given", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_starttls(struct smtp_session *s, const char *args) +{ + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!(s->listener->flags & F_STARTTLS)) { + smtp_reply(s, "503 %s %s: Command not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->flags & SF_SECURE) { + smtp_reply(s, "503 %s %s: Channel already secured", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args != NULL) { + smtp_reply(s, "501 %s %s: No parameters allowed", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_mail_from(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + struct mailaddr sender; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->listener->flags & F_STARTTLS_REQUIRE && + !(s->flags & SF_SECURE)) { + smtp_reply(s, + "530 %s %s: Must issue a STARTTLS command first", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->listener->flags & F_AUTH_REQUIRE && + !(s->flags & SF_AUTHENTICATED)) { + smtp_reply(s, + "530 %s %s: Must issue an AUTH command first", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->mailcount >= env->sc_session_max_mails) { + /* we can pretend we had too many recipients */ + smtp_reply(s, "452 %s %s: Too many messages sent", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return 0; + } + + if (smtp_mailaddr(&sender, copy, 1, ©, + s->smtpname) == 0) { + smtp_reply(s, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + return 0; + } + + return 1; +} + +static int +smtp_check_rcpt_to(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (s->tx == NULL) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->tx->rcptcount >= env->sc_session_max_rcpt) { + smtp_reply(s->tx->session, "451 %s %s: Too many recipients", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return 0; + } + + if (smtp_mailaddr(&s->tx->evp.rcpt, copy, 0, ©, + s->tx->session->smtpname) == 0) { + smtp_reply(s->tx->session, + "501 %s Recipient address syntax error", + esc_code(ESC_STATUS_PERMFAIL, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); + return 0; + } + + return 1; +} + +static int +smtp_check_data(struct smtp_session *s, const char *args) +{ + if (!smtp_check_noparam(s, args)) + return 0; + + if (s->tx == NULL) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->tx->rcptcount == 0) { + smtp_reply(s, "503 %s %s: No recipient specified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_noparam(struct smtp_session *s, const char *args) +{ + if (args != NULL) { + smtp_reply(s, "500 %s %s: command does not accept arguments.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + return 1; +} + +static void +smtp_query_filters(enum filter_phase phase, struct smtp_session *s, const char *args) +{ + m_create(p_lka, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_int(p_lka, phase); + m_add_string(p_lka, args); + m_close(p_lka); + tree_xset(&wait_filters, s->id, s); +} + +static void +smtp_filter_begin(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->filter_name); + m_close(p_lka); +} + +static void +smtp_filter_end(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +smtp_filter_data_begin(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); + tree_xset(&wait_filter_fd, s->id, s); +} + +static void +smtp_filter_data_end(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + if (s->tx->filter == NULL) + return; + + io_free(s->tx->filter); + s->tx->filter = NULL; + + m_create(p_lka, IMSG_FILTER_SMTP_DATA_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *param) +{ + uint8_t i; + + s->filter_phase = phase; + s->filter_param = param; + + if (SESSION_FILTERED(s)) { + smtp_query_filters(phase, s, param ? param : ""); + return; + } + + if (s->filter_phase == FILTER_CONNECT) { + smtp_proceed_connected(s); + return; + } + + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == s->filter_phase) { + commands[i].proceed(s, param); + break; + } +} + +static void +smtp_proceed_rset(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "250 %s Reset state", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + + if (s->tx) { + if (s->tx->msgid) + smtp_tx_rollback(s->tx); + smtp_tx_free(s->tx); + } +} + +static void +smtp_proceed_helo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + + smtp_report_link_identify(s, "HELO", s->helo); + + smtp_enter_state(s, STATE_HELO); + + smtp_reply(s, "250 %s Hello %s %s%s%s, pleased to meet you", + s->smtpname, + s->helo, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); +} + +static void +smtp_proceed_ehlo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + s->flags |= SF_EHLO; + s->flags |= SF_8BITMIME; + + smtp_report_link_identify(s, "EHLO", s->helo); + + smtp_enter_state(s, STATE_HELO); + smtp_reply(s, "250-%s Hello %s %s%s%s, pleased to meet you", + s->smtpname, + s->helo, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); + + smtp_reply(s, "250-8BITMIME"); + smtp_reply(s, "250-ENHANCEDSTATUSCODES"); + smtp_reply(s, "250-SIZE %zu", env->sc_maxsize); + if (ADVERTISE_EXT_DSN(s)) + smtp_reply(s, "250-DSN"); + if (ADVERTISE_TLS(s)) + smtp_reply(s, "250-STARTTLS"); + if (ADVERTISE_AUTH(s)) + smtp_reply(s, "250-AUTH PLAIN LOGIN"); + smtp_reply(s, "250 HELP"); +} + +static void +smtp_proceed_auth(struct smtp_session *s, const char *args) +{ + char tmp[SMTP_LINE_MAX]; + char *eom, *method; + + (void)strlcpy(tmp, args, sizeof tmp); + + method = tmp; + eom = strchr(tmp, ' '); + if (eom == NULL) + eom = strchr(tmp, '\t'); + if (eom != NULL) + *eom++ = '\0'; + if (strcasecmp(method, "PLAIN") == 0) + smtp_rfc4954_auth_plain(s, eom); + else if (strcasecmp(method, "LOGIN") == 0) + smtp_rfc4954_auth_login(s, eom); + else + smtp_reply(s, "504 %s %s: AUTH method \"%s\" not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_SECURITY_FEATURES_NOT_SUPPORTED), + esc_description(ESC_SECURITY_FEATURES_NOT_SUPPORTED), + method); +} + +static void +smtp_proceed_starttls(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "220 %s Ready to start TLS", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_TLS); +} + +static void +smtp_proceed_mail_from(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (!smtp_tx(s)) { + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + if (smtp_mailaddr(&s->tx->evp.sender, copy, 1, ©, + s->smtpname) == 0) { + smtp_reply(s, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + smtp_tx_free(s->tx); + return; + } + + smtp_tx_mail_from(s->tx, args); +} + +static void +smtp_proceed_rcpt_to(struct smtp_session *s, const char *args) +{ + smtp_tx_rcpt_to(s->tx, args); +} + +static void +smtp_proceed_data(struct smtp_session *s, const char *args) +{ + smtp_tx_open_message(s->tx); +} + +static void +smtp_proceed_quit(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "221 %s Bye", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); +} + +static void +smtp_proceed_noop(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "250 %s Ok", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); +} + +static void +smtp_proceed_help(struct smtp_session *s, const char *args) +{ + const char *code = esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS); + + smtp_reply(s, "214-%s This is " SMTPD_NAME, code); + smtp_reply(s, "214-%s To report bugs in the implementation, " + "please contact bugs@openbsd.org", code); + smtp_reply(s, "214-%s with full details", code); + smtp_reply(s, "214 %s End of HELP info", code); +} + +static void +smtp_proceed_wiz(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "500 %s %s: this feature is not supported yet ;-)", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); +} + +static void +smtp_proceed_commit(struct smtp_session *s, const char *args) +{ + smtp_message_end(s->tx); +} + +static void +smtp_proceed_rollback(struct smtp_session *s, const char *args) +{ + struct smtp_tx *tx; + + tx = s->tx; + + fclose(tx->ofile); + tx->ofile = NULL; + + smtp_tx_rollback(tx); + smtp_tx_free(tx); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_rfc4954_auth_plain(struct smtp_session *s, char *arg) +{ + char buf[1024], *user, *pass; + int len; + + switch (s->state) { + case STATE_HELO: + if (arg == NULL) { + smtp_enter_state(s, STATE_AUTH_INIT); + smtp_reply(s, "334 "); + return; + } + smtp_enter_state(s, STATE_AUTH_INIT); + /* FALLTHROUGH */ + + case STATE_AUTH_INIT: + /* String is not NUL terminated, leave room. */ + if ((len = base64_decode(arg, (unsigned char *)buf, + sizeof(buf) - 1)) == -1) + goto abort; + /* buf is a byte string, NUL terminate. */ + buf[len] = '\0'; + + /* + * Skip "foo" in "foo\0user\0pass", if present. + */ + user = memchr(buf, '\0', len); + if (user == NULL || user >= buf + len - 2) + goto abort; + user++; /* skip NUL */ + if (strlcpy(s->username, user, sizeof(s->username)) + >= sizeof(s->username)) + goto abort; + + pass = memchr(user, '\0', len - (user - buf)); + if (pass == NULL || pass >= buf + len - 2) + goto abort; + pass++; /* skip NUL */ + + m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->authtable); + m_add_string(p_lka, user); + m_add_string(p_lka, pass); + m_close(p_lka); + tree_xset(&wait_parent_auth, s->id, s); + return; + + default: + fatal("smtp_rfc4954_auth_plain: unknown state"); + } + +abort: + smtp_reply(s, "501 %s %s: Syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), + esc_description(ESC_SYNTAX_ERROR)); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_rfc4954_auth_login(struct smtp_session *s, char *arg) +{ + char buf[LINE_MAX]; + + switch (s->state) { + case STATE_HELO: + smtp_enter_state(s, STATE_AUTH_USERNAME); + if (arg != NULL && *arg != '\0') { + smtp_rfc4954_auth_login(s, arg); + return; + } + smtp_reply(s, "334 VXNlcm5hbWU6"); + return; + + case STATE_AUTH_USERNAME: + memset(s->username, 0, sizeof(s->username)); + if (base64_decode(arg, (unsigned char *)s->username, + sizeof(s->username) - 1) == -1) + goto abort; + + smtp_enter_state(s, STATE_AUTH_PASSWORD); + smtp_reply(s, "334 UGFzc3dvcmQ6"); + return; + + case STATE_AUTH_PASSWORD: + memset(buf, 0, sizeof(buf)); + if (base64_decode(arg, (unsigned char *)buf, + sizeof(buf)-1) == -1) + goto abort; + + m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->authtable); + m_add_string(p_lka, s->username); + m_add_string(p_lka, buf); + m_close(p_lka); + tree_xset(&wait_parent_auth, s->id, s); + return; + + default: + fatal("smtp_rfc4954_auth_login: unknown state"); + } + +abort: + smtp_reply(s, "501 %s %s: Syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), + esc_description(ESC_SYNTAX_ERROR)); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_lookup_servername(struct smtp_session *s) +{ + if (s->listener->hostnametable[0]) { + m_create(p_lka, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->hostnametable); + m_add_sockaddr(p_lka, (struct sockaddr*)&s->listener->ss); + m_close(p_lka); + tree_xset(&wait_lka_helo, s->id, s); + return; + } + + smtp_connected(s); +} + +static void +smtp_connected(struct smtp_session *s) +{ + smtp_enter_state(s, STATE_CONNECTED); + + log_info("%016"PRIx64" smtp connected address=%s host=%s", + s->id, ss_to_text(&s->ss), s->rdns); + + smtp_filter_begin(s); + + smtp_report_link_connect(s, s->rdns, s->fcrdns, &s->ss, + &s->listener->ss); + + smtp_filter_phase(FILTER_CONNECT, s, ss_to_text(&s->ss)); +} + +static void +smtp_proceed_connected(struct smtp_session *s) +{ + if (s->listener->flags & F_SMTPS) + smtp_cert_init(s); + else + smtp_send_banner(s); +} + +static void +smtp_send_banner(struct smtp_session *s) +{ + smtp_reply(s, "220 %s ESMTP %s", s->smtpname, SMTPD_NAME); + s->banner_sent = 1; + smtp_report_link_greeting(s, s->smtpname); +} + +void +smtp_enter_state(struct smtp_session *s, int newstate) +{ + log_trace(TRACE_SMTP, "smtp: %p: %s -> %s", s, + smtp_strstate(s->state), + smtp_strstate(newstate)); + + s->state = newstate; +} + +static void +smtp_reply(struct smtp_session *s, char *fmt, ...) +{ + va_list ap; + int n; + char buf[LINE_MAX*2], tmp[LINE_MAX*2]; + + va_start(ap, fmt); + n = vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + if (n < 0) + fatalx("smtp_reply: response format error"); + if (n < 4) + fatalx("smtp_reply: response too short"); + if (n >= (int)sizeof buf) { + /* only first three bytes are used by SMTP logic, + * so if _our_ reply does not fit entirely in the + * buffer, it's ok to truncate. + */ + } + + log_trace(TRACE_SMTP, "smtp: %p: >>> %s", s, buf); + smtp_report_protocol_server(s, buf); + + switch (buf[0]) { + case '2': + if (s->tx) { + if (s->last_cmd == CMD_MAIL_FROM) { + smtp_report_tx_begin(s, s->tx->msgid); + smtp_report_tx_mail(s, s->tx->msgid, s->cmd + 10, 1); + } + else if (s->last_cmd == CMD_RCPT_TO) + smtp_report_tx_rcpt(s, s->tx->msgid, s->cmd + 8, 1); + } + break; + case '3': + if (s->tx) { + if (s->last_cmd == CMD_DATA) + smtp_report_tx_data(s, s->tx->msgid, 1); + } + break; + case '5': + case '4': + /* do not report smtp_tx_mail/smtp_tx_rcpt errors + * if they happened outside of a transaction. + */ + if (s->tx) { + if (s->last_cmd == CMD_MAIL_FROM) + smtp_report_tx_mail(s, s->tx->msgid, + s->cmd + 10, buf[0] == '4' ? -1 : 0); + else if (s->last_cmd == CMD_RCPT_TO) + smtp_report_tx_rcpt(s, + s->tx->msgid, s->cmd + 8, buf[0] == '4' ? -1 : 0); + else if (s->last_cmd == CMD_DATA && s->tx->rcptcount) + smtp_report_tx_data(s, s->tx->msgid, + buf[0] == '4' ? -1 : 0); + } + + if (s->flags & SF_BADINPUT) { + log_info("%016"PRIx64" smtp " + "bad-input result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_INIT) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH PLAIN (...)\" result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_USERNAME) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH LOGIN (username)\" result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_PASSWORD) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH LOGIN (password)\" result=\"%.*s\"", + s->id, n, buf); + } + else { + strnvis(tmp, s->cmd, sizeof tmp, VIS_SAFE | VIS_CSTYLE); + log_info("%016"PRIx64" smtp " + "failed-command command=\"%s\" " + "result=\"%.*s\"", + s->id, tmp, n, buf); + } + break; + } + + io_xprintf(s->io, "%s\r\n", buf); +} + +static void +smtp_free(struct smtp_session *s, const char * reason) +{ + if (s->tx) { + if (s->tx->msgid) + smtp_tx_rollback(s->tx); + smtp_tx_free(s->tx); + } + + smtp_report_link_disconnect(s); + smtp_filter_end(s); + + if (s->flags & SF_SECURE && s->listener->flags & F_SMTPS) + stat_decrement("smtp.smtps", 1); + if (s->flags & SF_SECURE && s->listener->flags & F_STARTTLS) + stat_decrement("smtp.tls", 1); + + io_free(s->io); + free(s); + + smtp_collect(); +} + +static int +smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, + const char *domain) +{ + char *p, *e; + + if (line == NULL) + return (0); + + if (*line != '<') + return (0); + + e = strchr(line, '>'); + if (e == NULL) + return (0); + *e++ = '\0'; + while (*e == ' ') + e++; + *args = e; + + if (!text_to_mailaddr(maddr, line + 1)) + return (0); + + p = strchr(maddr->user, ':'); + if (p != NULL) { + p++; + memmove(maddr->user, p, strlen(p) + 1); + } + + /* accept empty return-path in MAIL FROM, required for bounces */ + if (mailfrom && maddr->user[0] == '\0' && maddr->domain[0] == '\0') + return (1); + + /* no or invalid user-part, reject */ + if (maddr->user[0] == '\0' || !valid_localpart(maddr->user)) + return (0); + + /* no domain part, local user */ + if (maddr->domain[0] == '\0') { + (void)strlcpy(maddr->domain, domain, + sizeof(maddr->domain)); + } + + if (!valid_domainpart(maddr->domain)) + return (0); + + return (1); +} + +static void +smtp_cert_init(struct smtp_session *s) +{ + const char *name; + int fallback; + + if (s->listener->pki_name[0]) { + name = s->listener->pki_name; + fallback = 0; + } + else { + name = s->smtpname; + fallback = 1; + } + + if (cert_init(name, fallback, smtp_cert_init_cb, s)) + tree_xset(&wait_ssl_init, s->id, s); +} + +static void +smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert, + size_t cert_len) +{ + struct smtp_session *s = arg; + void *ssl, *ssl_ctx; + + tree_pop(&wait_ssl_init, s->id); + + if (status == CA_FAIL) { + log_info("%016"PRIx64" smtp disconnected " + "reason=ca-failure", + s->id); + smtp_free(s, "CA failure"); + return; + } + + ssl_ctx = dict_get(env->sc_ssl_dict, name); + ssl = ssl_smtp_init(ssl_ctx, s->listener->flags & F_TLS_VERIFY); + io_set_read(s->io); + io_start_tls(s->io, ssl); +} + +static void +smtp_cert_verify(struct smtp_session *s) +{ + const char *name; + int fallback; + + if (s->listener->ca_name[0]) { + name = s->listener->ca_name; + fallback = 0; + } + else { + name = s->smtpname; + fallback = 1; + } + + if (cert_verify(io_tls(s->io), name, fallback, smtp_cert_verify_cb, s)) { + tree_xset(&wait_ssl_verify, s->id, s); + io_pause(s->io, IO_IN); + } +} + +static void +smtp_cert_verify_cb(void *arg, int status) +{ + struct smtp_session *s = arg; + const char *reason = NULL; + int resume; + + resume = tree_pop(&wait_ssl_verify, s->id) != NULL; + + switch (status) { + case CERT_OK: + reason = "cert-ok"; + s->flags |= SF_VERIFIED; + break; + case CERT_NOCA: + reason = "no-ca"; + break; + case CERT_NOCERT: + reason = "no-client-cert"; + break; + case CERT_INVALID: + reason = "cert-invalid"; + break; + default: + reason = "cert-check-failed"; + break; + } + + log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason); + + if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) { + log_info("%016"PRIx64" smtp disconnected " + " reason=%s", s->id, + reason); + smtp_free(s, "SSL certificate check failed"); + return; + } + + smtp_tls_verified(s); + if (resume) + io_resume(s->io, IO_IN); +} + +static void +smtp_auth_failure_resume(int fd, short event, void *p) +{ + struct smtp_session *s = p; + + smtp_reply(s, "535 Authentication failed"); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_auth_failure_pause(struct smtp_session *s) +{ + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = arc4random_uniform(1000000); + log_trace(TRACE_SMTP, "smtp: timing-attack protection triggered, " + "will defer answer for %lu microseconds", tv.tv_usec); + evtimer_set(&s->pause, smtp_auth_failure_resume, s); + evtimer_add(&s->pause, &tv); +} + +static int +smtp_tx(struct smtp_session *s) +{ + struct smtp_tx *tx; + + tx = calloc(1, sizeof(*tx)); + if (tx == NULL) + return 0; + + TAILQ_INIT(&tx->rcpts); + + s->tx = tx; + tx->session = s; + + /* setup the envelope */ + tx->evp.ss = s->ss; + (void)strlcpy(tx->evp.tag, s->listener->tag, sizeof(tx->evp.tag)); + (void)strlcpy(tx->evp.smtpname, s->smtpname, sizeof(tx->evp.smtpname)); + (void)strlcpy(tx->evp.hostname, s->rdns, sizeof tx->evp.hostname); + (void)strlcpy(tx->evp.helo, s->helo, sizeof(tx->evp.helo)); + (void)strlcpy(tx->evp.username, s->username, sizeof(tx->evp.username)); + + if (s->flags & SF_BOUNCE) + tx->evp.flags |= EF_BOUNCE; + if (s->flags & SF_AUTHENTICATED) + tx->evp.flags |= EF_AUTHENTICATED; + + if ((tx->parser = rfc5322_parser_new()) == NULL) { + free(tx); + return 0; + } + + return 1; +} + +static void +smtp_tx_free(struct smtp_tx *tx) +{ + struct smtp_rcpt *rcpt; + + rfc5322_free(tx->parser); + + while ((rcpt = TAILQ_FIRST(&tx->rcpts))) { + TAILQ_REMOVE(&tx->rcpts, rcpt, entry); + free(rcpt); + } + + if (tx->ofile) + fclose(tx->ofile); + + tx->session->tx = NULL; + + free(tx); +} + +static void +smtp_tx_mail_from(struct smtp_tx *tx, const char *line) +{ + char *opt; + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, line, sizeof tmp); + copy = tmp; + + if (smtp_mailaddr(&tx->evp.sender, copy, 1, ©, + tx->session->smtpname) == 0) { + smtp_reply(tx->session, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + smtp_tx_free(tx); + return; + } + + while ((opt = strsep(©, " "))) { + if (*opt == '\0') + continue; + + if (strncasecmp(opt, "AUTH=", 5) == 0) + log_debug("debug: smtp: AUTH in MAIL FROM command"); + else if (strncasecmp(opt, "SIZE=", 5) == 0) + log_debug("debug: smtp: SIZE in MAIL FROM command"); + else if (strcasecmp(opt, "BODY=7BIT") == 0) + /* XXX only for this transaction */ + tx->session->flags &= ~SF_8BITMIME; + else if (strcasecmp(opt, "BODY=8BITMIME") == 0) + ; + else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "RET=", 4) == 0) { + opt += 4; + if (strcasecmp(opt, "HDRS") == 0) + tx->evp.dsn_ret = DSN_RETHDRS; + else if (strcasecmp(opt, "FULL") == 0) + tx->evp.dsn_ret = DSN_RETFULL; + } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ENVID=", 6) == 0) { + opt += 6; + if (strlcpy(tx->evp.dsn_envid, opt, sizeof(tx->evp.dsn_envid)) + >= sizeof(tx->evp.dsn_envid)) { + smtp_reply(tx->session, + "503 %s %s: option too large, truncated: %s", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); + smtp_tx_free(tx); + return; + } + } else { + smtp_reply(tx->session, "503 %s %s: Unsupported option %s", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); + smtp_tx_free(tx); + return; + } + } + + /* only check sendertable if defined and user has authenticated */ + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0]) { + m_create(p_lka, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); + m_add_id(p_lka, tx->session->id); + m_add_string(p_lka, tx->session->listener->sendertable); + m_add_string(p_lka, tx->session->username); + m_add_mailaddr(p_lka, &tx->evp.sender); + m_close(p_lka); + tree_xset(&wait_lka_mail, tx->session->id, tx->session); + } + else + smtp_tx_create_message(tx); +} + +static void +smtp_tx_create_message(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_close(p_queue); + tree_xset(&wait_queue_msg, tx->session->id, tx->session); +} + +static void +smtp_tx_rcpt_to(struct smtp_tx *tx, const char *line) +{ + char *opt, *p; + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, line, sizeof tmp); + copy = tmp; + + if (tx->rcptcount >= env->sc_session_max_rcpt) { + smtp_reply(tx->session, "451 %s %s: Too many recipients", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return; + } + + if (smtp_mailaddr(&tx->evp.rcpt, copy, 0, ©, + tx->session->smtpname) == 0) { + smtp_reply(tx->session, + "501 %s Recipient address syntax error", + esc_code(ESC_STATUS_PERMFAIL, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); + return; + } + + while ((opt = strsep(©, " "))) { + if (*opt == '\0') + continue; + + if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "NOTIFY=", 7) == 0) { + opt += 7; + while ((p = strsep(&opt, ","))) { + if (strcasecmp(p, "SUCCESS") == 0) + tx->evp.dsn_notify |= DSN_SUCCESS; + else if (strcasecmp(p, "FAILURE") == 0) + tx->evp.dsn_notify |= DSN_FAILURE; + else if (strcasecmp(p, "DELAY") == 0) + tx->evp.dsn_notify |= DSN_DELAY; + else if (strcasecmp(p, "NEVER") == 0) + tx->evp.dsn_notify |= DSN_NEVER; + } + + if (tx->evp.dsn_notify & DSN_NEVER && + tx->evp.dsn_notify & (DSN_SUCCESS | DSN_FAILURE | + DSN_DELAY)) { + smtp_reply(tx->session, + "553 NOTIFY option NEVER cannot be" + " combined with other options"); + return; + } + } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ORCPT=", 6) == 0) { + opt += 6; + + if (strncasecmp(opt, "rfc822;", 7) == 0) + opt += 7; + + if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt) || + !valid_localpart(tx->evp.dsn_orcpt.user) || + !valid_domainpart(tx->evp.dsn_orcpt.domain)) { + smtp_reply(tx->session, + "553 ORCPT address syntax error"); + return; + } + } else { + smtp_reply(tx->session, "503 Unsupported option %s", opt); + return; + } + } + + m_create(p_lka, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); + m_add_id(p_lka, tx->session->id); + m_add_envelope(p_lka, &tx->evp); + m_close(p_lka); + tree_xset(&wait_lka_rcpt, tx->session->id, tx->session); +} + +static void +smtp_tx_open_message(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_OPEN, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + tree_xset(&wait_queue_fd, tx->session->id, tx->session); +} + +static void +smtp_tx_commit(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + tree_xset(&wait_queue_commit, tx->session->id, tx->session); + smtp_filter_data_end(tx->session); +} + +static void +smtp_tx_rollback(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_ROLLBACK, 0, 0, -1); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + smtp_report_tx_rollback(tx->session, tx->msgid); + smtp_report_tx_reset(tx->session, tx->msgid); + smtp_filter_data_end(tx->session); +} + +static int +smtp_tx_dataline(struct smtp_tx *tx, const char *line) +{ + struct rfc5322_result res; + int r; + + log_trace(TRACE_SMTP, "<<< [MSG] %s", line); + + if (!strcmp(line, ".")) { + smtp_report_protocol_client(tx->session, "."); + log_trace(TRACE_SMTP, "<<< [EOM]"); + if (tx->error) + return 1; + line = NULL; + } + else { + /* ignore data line if an error is set */ + if (tx->error) + return 0; + + /* escape lines starting with a '.' */ + if (line[0] == '.') + line += 1; + } + + if (rfc5322_push(tx->parser, line) == -1) { + log_warnx("failed to push dataline"); + tx->error = TX_ERROR_INTERNAL; + return 0; + } + + for(;;) { + r = rfc5322_next(tx->parser, &res); + switch (r) { + case -1: + if (errno == ENOMEM) + tx->error = TX_ERROR_INTERNAL; + else + tx->error = TX_ERROR_MALFORMED; + return 0; + + case RFC5322_NONE: + /* Need more data */ + return 0; + + case RFC5322_HEADER_START: + /* ignore bcc */ + if (!strcasecmp("Bcc", res.hdr)) + continue; + + if (!strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) { + rfc5322_unfold_header(tx->parser); + continue; + } + + if (!strcasecmp("Received", res.hdr)) { + if (++tx->rcvcount >= MAX_HOPS_COUNT) { + log_warnx("warn: loop detected"); + tx->error = TX_ERROR_LOOP; + return 0; + } + } + else if (!tx->has_date && !strcasecmp("Date", res.hdr)) + tx->has_date = 1; + else if (!tx->has_message_id && + !strcasecmp("Message-Id", res.hdr)) + tx->has_message_id = 1; + + smtp_message_printf(tx, "%s:%s\n", res.hdr, res.value); + break; + + case RFC5322_HEADER_CONT: + + if (!strcasecmp("Bcc", res.hdr) || + !strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) + continue; + + smtp_message_printf(tx, "%s\n", res.value); + break; + + case RFC5322_HEADER_END: + if (!strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) + header_domain_append_callback(tx, res.hdr, + res.value); + break; + + case RFC5322_END_OF_HEADERS: + if (tx->session->listener->local || + tx->session->listener->port == 587) { + + if (!tx->has_date) { + log_debug("debug: %p: adding Date", tx); + smtp_message_printf(tx, "Date: %s\n", + time_to_text(tx->time)); + } + + if (!tx->has_message_id) { + log_debug("debug: %p: adding Message-ID", tx); + smtp_message_printf(tx, + "Message-ID: <%016"PRIx64"@%s>\n", + generate_uid(), + tx->session->listener->hostname); + } + } + break; + + case RFC5322_BODY_START: + case RFC5322_BODY: + smtp_message_printf(tx, "%s\n", res.value); + break; + + case RFC5322_END_OF_MESSAGE: + return 1; + + default: + fatalx("%s", __func__); + } + } +} + +static int +smtp_tx_filtered_dataline(struct smtp_tx *tx, const char *line) +{ + if (!strcmp(line, ".")) + line = NULL; + else { + /* ignore data line if an error is set */ + if (tx->error) + return 0; + } + io_printf(tx->filter, "%s\n", line ? line : "."); + return line ? 0 : 1; +} + +static void +smtp_tx_eom(struct smtp_tx *tx) +{ + smtp_filter_phase(FILTER_COMMIT, tx->session, NULL); +} + +static int +smtp_message_fd(struct smtp_tx *tx, int fd) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("smtp: %p: message fd %d", s, fd); + + if ((tx->ofile = fdopen(fd, "w")) == NULL) { + close(fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return 0; + } + return 1; +} + +static void +filter_session_io(struct io *io, int evt, void *arg) +{ + struct smtp_tx*tx = arg; + char*line = NULL; + ssize_t len; + + log_trace(TRACE_IO, "filter session io (smtp): %p: %s %s", tx, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(tx->filter, &len); + /* No complete line received */ + if (line == NULL) + return; + + if (smtp_tx_dataline(tx, line)) { + smtp_tx_eom(tx); + return; + } + + goto nextline; + } +} + +static void +smtp_filter_fd(struct smtp_tx *tx, int fd) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("smtp: %p: filter fd %d", s, fd); + + tx->filter = io_new(); + io_set_fd(tx->filter, fd); + io_set_callback(tx->filter, filter_session_io, tx); +} + +static void +smtp_message_begin(struct smtp_tx *tx) +{ + struct smtp_session *s; + X509 *x; + int (*m_printf)(struct smtp_tx *, const char *, ...); + + m_printf = smtp_message_printf; + if (tx->filter) + m_printf = smtp_filter_printf; + + s = tx->session; + + log_debug("smtp: %p: message begin", s); + + smtp_reply(s, "354 Enter mail, end with \".\"" + " on a line by itself"); + + if (s->junk || (s->tx && s->tx->junk)) + m_printf(tx, "X-Spam: Yes\n"); + + m_printf(tx, "Received: "); + if (!(s->listener->flags & F_MASK_SOURCE)) { + m_printf(tx, "from %s (%s %s%s%s)", + s->helo, + s->rdns, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); + } + m_printf(tx, "\n\tby %s (%s) with %sSMTP%s%s id %08x", + s->smtpname, + SMTPD_NAME, + s->flags & SF_EHLO ? "E" : "", + s->flags & SF_SECURE ? "S" : "", + s->flags & SF_AUTHENTICATED ? "A" : "", + tx->msgid); + + if (s->flags & SF_SECURE) { + x = SSL_get_peer_certificate(io_tls(s->io)); + m_printf(tx, " (%s:%s:%d:%s)", + SSL_get_version(io_tls(s->io)), + SSL_get_cipher_name(io_tls(s->io)), + SSL_get_cipher_bits(io_tls(s->io), NULL), + (s->flags & SF_VERIFIED) ? "YES" : (x ? "FAIL" : "NO")); + X509_free(x); + + if (s->listener->flags & F_RECEIVEDAUTH) { + m_printf(tx, " auth=%s", + s->username[0] ? "yes" : "no"); + if (s->username[0]) + m_printf(tx, " user=%s", s->username); + } + } + + if (tx->rcptcount == 1) { + m_printf(tx, "\n\tfor <%s@%s>", + tx->evp.rcpt.user, + tx->evp.rcpt.domain); + } + + m_printf(tx, ";\n\t%s\n", time_to_text(time(&tx->time))); + + smtp_enter_state(s, STATE_BODY); +} + +static void +smtp_message_end(struct smtp_tx *tx) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("debug: %p: end of message, error=%d", s, tx->error); + + fclose(tx->ofile); + tx->ofile = NULL; + + switch(tx->error) { + case TX_OK: + smtp_tx_commit(tx); + return; + + case TX_ERROR_SIZE: + smtp_reply(s, "554 %s %s: Transaction failed, message too big", + esc_code(ESC_STATUS_PERMFAIL, ESC_MESSAGE_TOO_BIG_FOR_SYSTEM), + esc_description(ESC_MESSAGE_TOO_BIG_FOR_SYSTEM)); + break; + + case TX_ERROR_LOOP: + smtp_reply(s, "500 %s %s: Loop detected", + esc_code(ESC_STATUS_PERMFAIL, ESC_ROUTING_LOOP_DETECTED), + esc_description(ESC_ROUTING_LOOP_DETECTED)); + break; + + case TX_ERROR_MALFORMED: + smtp_reply(s, "550 %s %s: Message is not RFC 2822 compliant", + esc_code(ESC_STATUS_PERMFAIL, ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED), + esc_description(ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED)); + break; + + case TX_ERROR_IO: + case TX_ERROR_RESOURCES: + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + break; + + default: + /* fatal? */ + smtp_reply(s, "421 Internal server error"); + } + + smtp_tx_rollback(tx); + smtp_tx_free(tx); + smtp_enter_state(s, STATE_HELO); +} + +static int +smtp_filter_printf(struct smtp_tx *tx, const char *fmt, ...) +{ + va_list ap; + int len; + + if (tx->error) + return -1; + + va_start(ap, fmt); + len = io_vprintf(tx->filter, fmt, ap); + va_end(ap); + + if (len < 0) { + log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); + tx->error = TX_ERROR_IO; + } + else + tx->odatalen += len; + + return len; +} + +static int +smtp_message_printf(struct smtp_tx *tx, const char *fmt, ...) +{ + va_list ap; + int len; + + if (tx->error) + return -1; + + va_start(ap, fmt); + len = vfprintf(tx->ofile, fmt, ap); + va_end(ap); + + if (len == -1) { + log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); + tx->error = TX_ERROR_IO; + } + else + tx->odatalen += len; + + return len; +} + +#define CASE(x) case x : return #x + +const char * +smtp_strstate(int state) +{ + static char buf[32]; + + switch (state) { + CASE(STATE_NEW); + CASE(STATE_CONNECTED); + CASE(STATE_TLS); + CASE(STATE_HELO); + CASE(STATE_AUTH_INIT); + CASE(STATE_AUTH_USERNAME); + CASE(STATE_AUTH_PASSWORD); + CASE(STATE_AUTH_FINALIZE); + CASE(STATE_BODY); + CASE(STATE_QUIT); + default: + (void)snprintf(buf, sizeof(buf), "STATE_??? (%d)", state); + return (buf); + } +} + + +static void +smtp_report_link_connect(struct smtp_session *s, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_connect("smtp-in", s->id, rdns, fcrdns, ss_src, ss_dest); +} + +static void +smtp_report_link_greeting(struct smtp_session *s, + const char *domain) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_greeting("smtp-in", s->id, domain); +} + +static void +smtp_report_link_identify(struct smtp_session *s, const char *method, const char *identity) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_identify("smtp-in", s->id, method, identity); +} + +static void +smtp_report_link_tls(struct smtp_session *s, const char *ssl) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_tls("smtp-in", s->id, ssl); +} + +static void +smtp_report_link_disconnect(struct smtp_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_disconnect("smtp-in", s->id); +} + +static void +smtp_report_link_auth(struct smtp_session *s, const char *user, const char *result) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_auth("smtp-in", s->id, user, result); +} + +static void +smtp_report_tx_reset(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_reset("smtp-in", s->id, msgid); +} + +static void +smtp_report_tx_begin(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_begin("smtp-in", s->id, msgid); +} + +static void +smtp_report_tx_mail(struct smtp_session *s, uint32_t msgid, const char *address, int ok) +{ + char mailaddr[SMTPD_MAXMAILADDRSIZE]; + char *p; + + if (! SESSION_FILTERED(s)) + return; + + if ((p = strchr(address, '<')) == NULL) + return; + (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); + if ((p = strchr(mailaddr, '>')) == NULL) + return; + *p = '\0'; + + report_smtp_tx_mail("smtp-in", s->id, msgid, mailaddr, ok); +} + +static void +smtp_report_tx_rcpt(struct smtp_session *s, uint32_t msgid, const char *address, int ok) +{ + char mailaddr[SMTPD_MAXMAILADDRSIZE]; + char *p; + + if (! SESSION_FILTERED(s)) + return; + + if ((p = strchr(address, '<')) == NULL) + return; + (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); + if ((p = strchr(mailaddr, '>')) == NULL) + return; + *p = '\0'; + + report_smtp_tx_rcpt("smtp-in", s->id, msgid, mailaddr, ok); +} + +static void +smtp_report_tx_envelope(struct smtp_session *s, uint32_t msgid, uint64_t evpid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_envelope("smtp-in", s->id, msgid, evpid); +} + +static void +smtp_report_tx_data(struct smtp_session *s, uint32_t msgid, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_data("smtp-in", s->id, msgid, ok); +} + +static void +smtp_report_tx_commit(struct smtp_session *s, uint32_t msgid, size_t msgsz) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_commit("smtp-in", s->id, msgid, msgsz); +} + +static void +smtp_report_tx_rollback(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rollback("smtp-in", s->id, msgid); +} + +static void +smtp_report_protocol_client(struct smtp_session *s, const char *command) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_client("smtp-in", s->id, command); +} + +static void +smtp_report_protocol_server(struct smtp_session *s, const char *response) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_server("smtp-in", s->id, response); +} + +static void +smtp_report_filter_response(struct smtp_session *s, int phase, int response, const char *param) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_filter_response("smtp-in", s->id, phase, response, param); +} + +static void +smtp_report_timeout(struct smtp_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_timeout("smtp-in", s->id); +} diff --git a/usr.sbin/smtpd/smtpc.c b/usr.sbin/smtpd/smtpc.c new file mode 100644 index 00000000..59479703 --- /dev/null +++ b/usr.sbin/smtpd/smtpc.c @@ -0,0 +1,465 @@ +/* $OpenBSD: smtpc.c,v 1.10 2019/09/21 09:04:08 semarie Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "smtp.h" +#include "ssl.h" +#include "log.h" + +static void parse_server(char *); +static void parse_message(FILE *); +static void resume(void); + +static int verbose = 1; +static int done = 0; +static int noaction = 0; +static struct addrinfo *res0, *ai; +static struct smtp_params params; +static struct smtp_mail mail; +static const char *servname = NULL; + +static SSL_CTX *ssl_ctx; + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, + "usage: %s [-Chnv] [-F from] [-H helo] [-s server] [-S name] rcpt ...\n", + __progname); + exit(1); +} + +int +main(int argc, char **argv) +{ + char hostname[256]; + int ch, i; + char *server = "localhost"; + struct passwd *pw; + + log_init(1, 0); + + if (gethostname(hostname, sizeof(hostname)) == -1) + fatal("gethostname"); + + if ((pw = getpwuid(getuid())) == NULL) + fatal("getpwuid"); + + memset(¶ms, 0, sizeof(params)); + + params.linemax = 16392; + params.ibufmax = 65536; + params.obufmax = 65536; + params.timeout = 100000; + params.helo = hostname; + + params.tls_verify = 1; + + memset(&mail, 0, sizeof(mail)); + mail.from = pw->pw_name; + + while ((ch = getopt(argc, argv, "CF:H:S:hns:v")) != -1) { + switch (ch) { + case 'C': + params.tls_verify = 0; + break; + case 'F': + mail.from = optarg; + break; + case 'H': + params.helo = optarg; + break; + case 'S': + servname = optarg; + break; + case 'h': + usage(); + break; + case 'n': + noaction = 1; + break; + case 's': + server = optarg; + break; + case 'v': + verbose++; + break; + default: + usage(); + } + } + + log_setverbose(verbose); + + argc -= optind; + argv += optind; + + if (argc) { + mail.rcpt = calloc(argc, sizeof(*mail.rcpt)); + if (mail.rcpt == NULL) + fatal("calloc"); + for (i = 0; i < argc; i++) + mail.rcpt[i].to = argv[i]; + mail.rcptcount = argc; + } + + ssl_init(); + event_init(); + + ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL); + if (!SSL_CTX_load_verify_locations(ssl_ctx, + X509_get_default_cert_file(), NULL)) + fatal("SSL_CTX_load_verify_locations"); + if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method())) + fatal("SSL_CTX_set_ssl_version"); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL); + +#if HAVE_PLEDGE + if (pledge("stdio inet dns tmppath", NULL) == -1) + fatal("pledge"); +#endif + + if (!noaction) + parse_message(stdin); + +#if HAVE_PLEDGE + if (pledge("stdio inet dns", NULL) == -1) + fatal("pledge"); +#endif + + parse_server(server); + +#if HAVE_PLEDGE + if (pledge("stdio inet", NULL) == -1) + fatal("pledge"); +#endif + + resume(); + + log_debug("done..."); + + return 0; +} + +static void +parse_server(char *server) +{ + struct addrinfo hints; + char *scheme, *creds, *host, *port, *p, *c; + int error; + + creds = NULL; + host = NULL; + port = NULL; + scheme = server; + + p = strstr(server, "://"); + if (p) { + *p = '\0'; + p += 3; + /* check for credentials */ + c = strchr(p, '@'); + if (c) { + creds = p; + *c = '\0'; + host = c + 1; + } else + host = p; + } else { + /* Assume a simple server name */ + scheme = "smtp"; + host = server; + } + + if (host[0] == '[') { + /* IPV6 address? */ + p = strchr(host, ']'); + if (p) { + if (p[1] == ':' || p[1] == '\0') { + *p++ = '\0'; /* remove ']' */ + host++; /* skip '[' */ + if (*p == ':') + port = p + 1; + } + } + } + else { + port = strchr(host, ':'); + if (port) + *port++ = '\0'; + } + + if (port && port[0] == '\0') + port = NULL; + + if (creds) { + p = strchr(creds, ':'); + if (p == NULL) + fatalx("invalid credentials"); + *p = '\0'; + + params.auth_user = creds; + params.auth_pass = p + 1; + } + params.tls_req = TLS_YES; + + if (!strcmp(scheme, "lmtp")) { + params.lmtp = 1; + } + else if (!strcmp(scheme, "lmtp+tls")) { + params.lmtp = 1; + params.tls_req = TLS_FORCE; + } + else if (!strcmp(scheme, "lmtp+notls")) { + params.lmtp = 1; + params.tls_req = TLS_NO; + } + else if (!strcmp(scheme, "smtps")) { + params.tls_req = TLS_SMTPS; + if (port == NULL) + port = "smtps"; + } + else if (!strcmp(scheme, "smtp")) { + } + else if (!strcmp(scheme, "smtp+tls")) { + params.tls_req = TLS_FORCE; + } + else if (!strcmp(scheme, "smtp+notls")) { + params.tls_req = TLS_NO; + } + else + fatalx("invalid url scheme %s", scheme); + + if (port == NULL) + port = "smtp"; + + if (servname == NULL) + servname = host; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + if (error) + fatalx("%s: %s", host, gai_strerror(error)); + ai = res0; +} + +void +parse_message(FILE *ifp) +{ + char *line = NULL; + size_t linesz = 0; + ssize_t len; + + if ((mail.fp = tmpfile()) == NULL) + fatal("tmpfile"); + + for (;;) { + if ((len = getline(&line, &linesz, ifp)) == -1) { + if (feof(ifp)) + break; + fatal("getline"); + } + + if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') + line[--len - 1] = '\n'; + + if (fwrite(line, 1, len, mail.fp) != (size_t)len) + fatal("fwrite"); + + if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF) + fatal("fputc"); + } + + fclose(ifp); + rewind(mail.fp); +} + +void +resume(void) +{ + static int started = 0; + char host[256]; + char serv[16]; + + if (done) { + event_loopexit(NULL); + return; + } + + if (ai == NULL) + fatalx("no more host"); + + getnameinfo(ai->ai_addr, SA_LEN(ai->ai_addr), + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV); + log_debug("trying host %s port %s...", host, serv); + + params.dst = ai->ai_addr; + if (smtp_connect(¶ms, NULL) == NULL) + fatal("smtp_connect"); + + if (started == 0) { + started = 1; + event_loop(0); + } +} + +void +log_trace(int lvl, const char *emsg, ...) +{ + va_list ap; + + if (verbose > lvl) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx) +{ + SSL *ssl = ctx; + X509 *cert; + long res; + int match; + + if ((cert = SSL_get_peer_certificate(ssl))) { + (void)ssl_check_name(cert, servname, &match); + X509_free(cert); + res = SSL_get_verify_result(ssl); + if (res == X509_V_OK) { + if (match) { + log_debug("valid certificate"); + smtp_cert_verified(proto, CERT_OK); + } + else { + log_debug("certificate does not match hostname"); + smtp_cert_verified(proto, CERT_INVALID); + } + return; + } + log_debug("certificate validation error %ld", res); + } + else + log_debug("no certificate provided"); + + smtp_cert_verified(proto, CERT_INVALID); +} + +void +smtp_require_tls(void *tag, struct smtp_client *proto) +{ + SSL *ssl = NULL; + + if ((ssl = SSL_new(ssl_ctx)) == NULL) + fatal("SSL_new"); + smtp_set_tls(proto, ssl); +} + +void +smtp_ready(void *tag, struct smtp_client *proto) +{ + log_debug("connection ready..."); + + if (done || noaction) + smtp_quit(proto); + else + smtp_sendmail(proto, &mail); +} + +void +smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail) +{ + switch (failure) { + case FAIL_INTERNAL: + log_warnx("internal error: %s", detail); + break; + case FAIL_CONN: + log_warnx("connection error: %s", detail); + break; + case FAIL_PROTO: + log_warnx("protocol error: %s", detail); + break; + case FAIL_IMPL: + log_warnx("missing feature: %s", detail); + break; + case FAIL_RESP: + log_warnx("rejected by server: %s", detail); + break; + default: + fatalx("unknown failure %d: %s", failure, detail); + } +} + +void +smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status) +{ + log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status); +} + +void +smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail) +{ + int i; + + log_debug("mail done..."); + + if (noaction) + return; + + for (i = 0; i < mail->rcptcount; i++) + if (!mail->rcpt[i].done) + return; + + done = 1; +} + +void +smtp_closed(void *tag, struct smtp_client *proto) +{ + log_debug("connection closed..."); + + ai = ai->ai_next; + if (noaction && ai == NULL) + done = 1; + + resume(); +} diff --git a/usr.sbin/smtpd/smtpctl.8 b/usr.sbin/smtpd/smtpctl.8 new file mode 100644 index 00000000..1efcff63 --- /dev/null +++ b/usr.sbin/smtpd/smtpctl.8 @@ -0,0 +1,336 @@ +.\" $OpenBSD: smtpctl.8,v 1.64 2018/09/18 06:21:45 miko Exp $ +.\" +.\" Copyright (c) 2006 Pierre-Yves Ritschard +.\" Copyright (c) 2012 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 18 2018 $ +.Dt SMTPCTL 8 +.Os +.Sh NAME +.Nm smtpctl , +.Nm mailq +.Nd control the Simple Mail Transfer Protocol daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar argument ... +.Nm mailq +.Sh DESCRIPTION +The +.Nm +program controls +.Xr smtpd 8 . +Commands may be abbreviated to the minimum unambiguous prefix; for example, +.Cm sh ro +for +.Cm show routes . +.Pp +The +.Nm mailq +command is provided for compatibility with other MTAs +and is simply a shortcut for +.Cm show queue . +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm discover Ar envelope-id | message-id +Schedule a single envelope, or all envelopes with the same message ID +that were manually moved to the queue. +.It Cm encrypt Op Ar string +Encrypt the password +.Ar string +to a representation suitable for user credentials and print it to the +standard output. +If +.Ar string +is not provided, cleartext passwords are read from standard input. +.Pp +It is advised to avoid providing the password as a parameter as it will be +visible from +.Xr top 1 +and +.Xr ps 1 +output. +.It Cm log brief +Disable verbose debug logging. +.It Cm log verbose +Enable verbose debug logging. +.It Cm monitor +Display updates of some +.Xr smtpd 8 +internal counters in one second intervals. +Each line reports the increment of all counters since the last update, +except for some counters which are always absolute values. +The first line reports the current value of each counter. +The fields are: +.Pp +.Bl -bullet -compact +.It +Current number of active SMTP clients (absolute value). +.It +New SMTP clients. +.It +Disconnected clients. +.It +Current number of envelopes in the queue (absolute value). +.It +Newly enqueued envelopes. +.It +Dequeued envelopes. +.It +Successful deliveries. +.It +Temporary failures. +.It +Permanent failures. +.It +Message loops. +.It +Expired envelopes. +.It +Envelopes removed by the administrator. +.It +Generated bounces. +.El +.It Cm pause envelope Ar envelope-id | message-id | Cm all +Temporarily suspend scheduling for the envelope with the given ID, +envelopes with the given message ID, +or all envelopes. +.It Cm pause mda +Temporarily stop deliveries to local users. +.It Cm pause mta +Temporarily stop relaying and deliveries to +remote users. +.It Cm pause smtp +Temporarily stop accepting incoming sessions. +.It Cm profile Ar subsystem +Enables real-time profiling of +.Ar subsystem . +Supported subsystems are: +.Pp +.Bl -bullet -compact +.It +queue, to profile cost of queue IO +.It +imsg, to profile cost of event handlers +.El +.It Cm remove Ar envelope-id | message-id | Cm all +Remove a single envelope, +envelopes with the given message ID, +or all envelopes. +.It Cm resume envelope Ar envelope-id | message-id | Cm all +Resume scheduling for the envelope with the given ID, +envelopes with the given message ID, +or all envelopes. +.It Cm resume mda +Resume deliveries to local users. +.It Cm resume mta +Resume relaying and deliveries to remote users. +.It Cm resume route Ar route-id +Resume routing on disabled route +.Ar route-id . +.It Cm resume smtp +Resume accepting incoming sessions. +.It Cm schedule Ar envelope-id | message-id | Cm all +Mark as ready for immediate delivery +a single envelope, +envelopes with the given message ID, +or all envelopes. +.It Cm show envelope Ar envelope-id +Display envelope content for the given ID. +.It Cm show hosts +Display the list of known remote MX hosts. +For each of them, it shows the IP address, the canonical hostname, +a reference count, the number of active connections to this host, +and the elapsed time since the last connection. +.It Cm show hoststats +Display status of last delivery for domains that have been active in the +last 4 hours. +It consists of the following fields, separated by a "|": +.Pp +.Bl -bullet -compact +.It +Domain. +.It +.Ux +timestamp of last delivery. +.It +Status of last delivery. +.El +.It Cm show message Ar envelope-id +Display message content for the given ID. +.It Cm show queue +Display information concerning envelopes that are currently in the queue. +Each line of output describes a single envelope. +It consists of the following fields, separated by a "|": +.Pp +.Bl -bullet -compact +.It +Envelope ID. +.It +Address family of the client which enqueued the mail. +.It +Type of delivery: one of "mta", "mda" or "bounce". +.It +Various flags on the envelope. +.It +Sender address (return path). +.It +The original recipient address. +.It +The destination address. +.It +Time of creation. +.It +Time of expiration. +.It +Time of last delivery or relaying attempt. +.It +Number of delivery or relaying attempts. +.It +Current runstate: either "pending" or "inflight" if +.Xr smtpd 8 +is running, or "offline" otherwise. +.It +Delay in seconds before the next attempt if pending, or time elapsed +if currently running. +This field is blank if +.Xr smtpd 8 +is not running. +.It +Error string for the last failed delivery or relay attempt. +.El +.It Cm show relays +Display the list of currently active relays and associated connectors. +For each relay, it shows a number of counters and information on its +internal state on a single line. +Then comes the list of connectors +(source addresses to connect from for this relay). +.It Cm show routes +Display status of routes currently known by +.Xr smtpd 8 . +Each line consists of a route number, a source address, a destination +address, a set of flags, the number of connections on this +route, the current penalty level which determines the amount of time +the route is disabled if an error occurs, and the delay before it +gets reactivated. +The following flags are defined: +.Pp +.Bl -tag -width xx -compact +.It D +The route is currently disabled. +.It N +The route is new. +No SMTP session has been established yet. +.It Q +The route has a timeout registered to lower its penalty level and possibly +reactivate or discard it. +.El +.It Cm show stats +Displays runtime statistics concerning +.Xr smtpd 8 . +.It Cm show status +Shows if MTA, MDA and SMTP systems are currently running or paused. +.It Cm spf walk +Recursively look up SPF records for the domains read from stdin. +For example: +.Bd -literal -offset indent +# smtpctl spf walk < domains.txt +.Ed +.It Cm trace Ar subsystem +Enables real-time tracing of +.Ar subsystem . +Supported subsystems are: +.Pp +.Bl -bullet -compact +.It +imsg +.It +io +.It +smtp (incoming sessions) +.It +filters +.It +mta (outgoing sessions) +.It +bounce +.It +scheduler +.It +expand (aliases/virtual/forward expansion) +.It +lookup (user/credentials lookups) +.It +stat +.It +rules (matched by incoming sessions) +.It +mproc +.It +all +.El +.It Cm unprofile Ar subsystem +Disables real-time profiling of +.Ar subsystem . +.It Cm untrace Ar subsystem +Disables real-time tracing of +.Ar subsystem . +.It Cm update table Ar name +Updates the contents of table +.Ar name , +for tables using the +.Dq file +backend. +.El +.Pp +When +.Nm smtpd +receives a message, it generates a +.Ar message-id +for the message, and one +.Ar envelope-id +per recipient. +The +.Ar message-id +is a 32-bit random identifier that is guaranteed to be +unique on the host system. +The +.Ar envelope-id +is a 64-bit unique identifier that encodes the +.Ar message-id +in the 32 upper bits and a random envelope identifier +in the 32 lower bits. +.Pp +A command which specifies a +.Ar message-id +applies to all recipients of a message; +a command which specifies an +.Ar envelope-id +applies to a specific recipient of a message. +.Sh FILES +.Bl -tag -width "/var/run/smtpd.sockXXX" -compact +.It Pa /var/run/smtpd.sock +.Ux Ns -domain +socket used for communication with +.Xr smtpd 8 . +.El +.Sh SEE ALSO +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 4.6 . diff --git a/usr.sbin/smtpd/smtpctl.c b/usr.sbin/smtpd/smtpctl.c new file mode 100644 index 00000000..7dba4224 --- /dev/null +++ b/usr.sbin/smtpd/smtpctl.c @@ -0,0 +1,1469 @@ +/* $OpenBSD: smtpctl.c,v 1.167 2020/02/24 16:16:07 millert Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2006 Gilles Chehade + * Copyright (c) 2006 Pierre-Yves Ritschard + * Copyright (c) 2005 Claudio Jeker + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2003 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +/* #include */ +/* #include */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include +#else +#include "bsd-vis.h" +#endif +#include + +#include "smtpd.h" +#include "parser.h" +#include "log.h" + +#ifndef PATH_GZCAT +#define PATH_GZCAT "/usr/bin/gzcat" +#endif +#define PATH_CAT "/bin/cat" +#define PATH_QUEUE "/queue" +#ifndef PATH_ENCRYPT +#define PATH_ENCRYPT "/usr/bin/encrypt" +#endif + +#ifndef HAVE_DB_API +#define makemap(x, y, z) 1 +#endif + + +int srv_connect(void); +int srv_connected(void); + +void usage(void); +static void show_queue_envelope(struct envelope *, int); +static void getflag(uint *, int, char *, char *, size_t); +static void display(const char *); +static int str_to_trace(const char *); +static int str_to_profile(const char *); +static void show_offline_envelope(uint64_t); +static int is_gzip_fp(FILE *); +static int is_encrypted_fp(FILE *); +static int is_encrypted_buffer(const char *); +static int is_gzip_buffer(const char *); +static FILE *offline_file(void); +static void sendmail_compat(int, char **); + +extern int spfwalk(int, struct parameter *); + +extern char *__progname; +int sendmail; +struct smtpd *env; +struct imsgbuf *ibuf; +struct imsg imsg; +char *rdata; +size_t rlen; +time_t now; + +struct queue_backend queue_backend_null; +struct queue_backend queue_backend_proc; +struct queue_backend queue_backend_ram; + +__dead void +usage(void) +{ + if (sendmail) + fprintf(stderr, "usage: %s [-tv] [-f from] [-F name] to ...\n", + __progname); + else + fprintf(stderr, "usage: %s command [argument ...]\n", + __progname); + exit(1); +} + +void stat_increment(const char *k, size_t v) +{ +} + +void stat_decrement(const char *k, size_t v) +{ +} + +int +srv_connect(void) +{ + struct sockaddr_un s_un; + int ctl_sock, saved_errno; + + /* connect to smtpd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + (void)strlcpy(s_un.sun_path, SMTPD_SOCKET, sizeof(s_un.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + saved_errno = errno; + close(ctl_sock); + errno = saved_errno; + return (0); + } + + ibuf = xcalloc(1, sizeof(struct imsgbuf)); + imsg_init(ibuf, ctl_sock); + + return (1); +} + +int +srv_connected(void) +{ + return ibuf != NULL ? 1 : 0; +} + +FILE * +offline_file(void) +{ + char path[PATH_MAX]; + int fd; + FILE *fp; + + if (!bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL, + PATH_OFFLINE, (long long int) time(NULL))) + err(EX_UNAVAILABLE, "snprintf"); + + if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) + unlink(path); + err(EX_UNAVAILABLE, "cannot create temporary file %s", path); + } + + if (fchmod(fd, 0600) == -1) { + unlink(path); + err(EX_SOFTWARE, "fchmod"); + } + + return fp; +} + + +static void +srv_flush(void) +{ + if (imsg_flush(ibuf) == -1) + err(1, "write error"); +} + +static void +srv_send(int msg, const void *data, size_t len) +{ + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + imsg_compose(ibuf, msg, IMSG_VERSION, 0, -1, data, len); +} + +static void +srv_recv(int type) +{ + ssize_t n; + + srv_flush(); + + while (1) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n) { + if (imsg.hdr.type == IMSG_CTL_FAIL && + imsg.hdr.peerid != 0 && + imsg.hdr.peerid != IMSG_VERSION) + errx(1, "incompatible smtpctl and smtpd"); + if (type != -1 && type != (int)imsg.hdr.type) + errx(1, "bad message type"); + rdata = imsg.data; + rlen = imsg.hdr.len - sizeof(imsg.hdr); + break; + } + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + } +} + +static void +srv_read(void *dst, size_t sz) +{ + if (sz == 0) + return; + if (rlen < sz) + errx(1, "message too short"); + if (dst) + memmove(dst, rdata, sz); + rlen -= sz; + rdata += sz; +} + +static void +srv_get_int(int *i) +{ + srv_read(i, sizeof(*i)); +} + +static void +srv_get_time(time_t *t) +{ + srv_read(t, sizeof(*t)); +} + +static void +srv_get_evpid(uint64_t *evpid) +{ + srv_read(evpid, sizeof(*evpid)); +} + +static void +srv_get_string(const char **s) +{ + const char *end; + size_t len; + + if (rlen == 0) + errx(1, "message too short"); + + rlen -= 1; + if (*rdata++ == '\0') { + *s = NULL; + return; + } + + if (rlen == 0) + errx(1, "bogus string"); + + end = memchr(rdata, 0, rlen); + if (end == NULL) + errx(1, "unterminated string"); + + len = end + 1 - rdata; + + *s = rdata; + rlen -= len; + rdata += len; +} + +static void +srv_get_envelope(struct envelope *evp) +{ + uint64_t evpid; + const char *str; + + srv_get_evpid(&evpid); + srv_get_string(&str); + + envelope_load_buffer(evp, str, strlen(str)); + evp->id = evpid; +} + +static void +srv_end(void) +{ + if (rlen) + errx(1, "bogus data"); + imsg_free(&imsg); +} + +static int +srv_check_result(int verbose_) +{ + srv_recv(-1); + srv_end(); + + switch (imsg.hdr.type) { + case IMSG_CTL_OK: + if (verbose_) + printf("command succeeded\n"); + return (0); + case IMSG_CTL_FAIL: + if (verbose_) { + if (rlen) + printf("command failed: %s\n", rdata); + else + printf("command failed\n"); + } + return (1); + default: + errx(1, "wrong message in response: %u", imsg.hdr.type); + } + return (0); +} + +static int +srv_iter_messages(uint32_t *res) +{ + static uint32_t *msgids = NULL, from = 0; + static size_t n, curr; + static int done = 0; + + if (done) + return (0); + + if (msgids == NULL) { + srv_send(IMSG_CTL_LIST_MESSAGES, &from, sizeof(from)); + srv_recv(IMSG_CTL_LIST_MESSAGES); + if (rlen == 0) { + srv_end(); + done = 1; + return (0); + } + msgids = malloc(rlen); + n = rlen / sizeof(*msgids); + srv_read(msgids, rlen); + srv_end(); + + curr = 0; + from = msgids[n - 1] + 1; + if (from == 0) + done = 1; + } + + *res = msgids[curr++]; + if (curr == n) { + free(msgids); + msgids = NULL; + } + + return (1); +} + +static int +srv_iter_envelopes(uint32_t msgid, struct envelope *evp) +{ + static uint32_t currmsgid = 0; + static uint64_t from = 0; + static int done = 0, need_send = 1, found; + int flags; + time_t nexttry; + + if (currmsgid != msgid) { + if (currmsgid != 0 && !done) + errx(1, "must finish current iteration first"); + currmsgid = msgid; + from = msgid_to_evpid(msgid); + done = 0; + found = 0; + need_send = 1; + } + + if (done) + return (0); + + again: + if (need_send) { + found = 0; + srv_send(IMSG_CTL_LIST_ENVELOPES, &from, sizeof(from)); + } + need_send = 0; + + srv_recv(IMSG_CTL_LIST_ENVELOPES); + if (rlen == 0) { + srv_end(); + if (!found || evpid_to_msgid(from) != msgid) { + done = 1; + return (0); + } + need_send = 1; + goto again; + } + + srv_get_int(&flags); + srv_get_time(&nexttry); + srv_get_envelope(evp); + srv_end(); + + evp->flags |= flags; + evp->nexttry = nexttry; + + from = evp->id + 1; + found++; + return (1); +} + +static int +srv_iter_evpids(uint32_t msgid, uint64_t *evpid, int *offset) +{ + static uint64_t *evpids = NULL, *tmp; + static int n, tmpalloc, alloc = 0; + struct envelope evp; + + if (*offset == 0) { + n = 0; + while (srv_iter_envelopes(msgid, &evp)) { + if (n == alloc) { + tmpalloc = alloc ? (alloc * 2) : 128; + tmp = recallocarray(evpids, alloc, tmpalloc, + sizeof(*evpids)); + if (tmp == NULL) + err(1, "recallocarray"); + evpids = tmp; + alloc = tmpalloc; + } + evpids[n++] = evp.id; + } + } + + if (*offset >= n) + return (0); + *evpid = evpids[*offset]; + *offset += 1; + return (1); +} + +static void +srv_foreach_envelope(struct parameter *argv, int ctl, size_t *total, size_t *ok) +{ + uint32_t msgid; + uint64_t evpid; + int i; + + *total = 0; + *ok = 0; + + if (argv == NULL) { + while (srv_iter_messages(&msgid)) { + i = 0; + while (srv_iter_evpids(msgid, &evpid, &i)) { + *total += 1; + srv_send(ctl, &evpid, sizeof(evpid)); + if (srv_check_result(0) == 0) + *ok += 1; + } + } + } else if (argv->type == P_MSGID) { + i = 0; + while (srv_iter_evpids(argv->u.u_msgid, &evpid, &i)) { + srv_send(ctl, &evpid, sizeof(evpid)); + if (srv_check_result(0) == 0) + *ok += 1; + } + } else { + *total += 1; + srv_send(ctl, &argv->u.u_evpid, sizeof(evpid)); + if (srv_check_result(0) == 0) + *ok += 1; + } +} + +static void +srv_show_cmd(int cmd, const void *data, size_t len) +{ + int done = 0; + + srv_send(cmd, data, len); + + do { + srv_recv(cmd); + if (rlen) { + printf("%s\n", rdata); + srv_read(NULL, rlen); + } + else + done = 1; + srv_end(); + } while (!done); +} + +static void +droppriv(void) +{ + struct passwd *pw; + + if (geteuid()) + return; + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + errx(1, "unknown user " SMTPD_USER); + + if ((setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))) + err(1, "cannot drop privileges"); +} + +static int +do_permission_denied(int argc, struct parameter *argv) +{ + errx(1, "need root privileges"); +} + +static int +do_log_brief(int argc, struct parameter *argv) +{ + int v = 0; + + srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_log_verbose(int argc, struct parameter *argv) +{ + int v = TRACE_DEBUG; + + srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_monitor(int argc, struct parameter *argv) +{ + struct stat_digest last, digest; + size_t count; + + memset(&last, 0, sizeof(last)); + count = 0; + + while (1) { + srv_send(IMSG_CTL_GET_DIGEST, NULL, 0); + srv_recv(IMSG_CTL_GET_DIGEST); + srv_read(&digest, sizeof(digest)); + srv_end(); + + if (count % 25 == 0) { + if (count != 0) + printf("\n"); + printf("--- client --- " + "-- envelope -- " + "---- relay/delivery --- " + "------- misc -------\n" + "curr conn disc " + "curr enq deq " + "ok tmpfail prmfail loop " + "expire remove bounce\n"); + } + printf("%4zu %4zu %4zu " + "%4zu %4zu %4zu " + "%4zu %4zu %4zu %4zu " + "%4zu %4zu %4zu\n", + digest.clt_connect - digest.clt_disconnect, + digest.clt_connect - last.clt_connect, + digest.clt_disconnect - last.clt_disconnect, + + digest.evp_enqueued - digest.evp_dequeued, + digest.evp_enqueued - last.evp_enqueued, + digest.evp_dequeued - last.evp_dequeued, + + digest.dlv_ok - last.dlv_ok, + digest.dlv_tempfail - last.dlv_tempfail, + digest.dlv_permfail - last.dlv_permfail, + digest.dlv_loop - last.dlv_loop, + + digest.evp_expired - last.evp_expired, + digest.evp_removed - last.evp_removed, + digest.evp_bounce - last.evp_bounce); + + last = digest; + count++; + sleep(1); + } + + return (0); +} + +static int +do_pause_envelope(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_PAUSE_EVP, &total, &ok); + printf("%zu envelope%s paused\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_pause_mda(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_PAUSE_MDA, NULL, 0); + return srv_check_result(1); +} + +static int +do_pause_mta(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_PAUSE_MTA, NULL, 0); + return srv_check_result(1); +} + +static int +do_pause_smtp(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_PAUSE_SMTP, NULL, 0); + return srv_check_result(1); +} + +static int +do_profile(int argc, struct parameter *argv) +{ + int v; + + v = str_to_profile(argv[0].u.u_str); + + srv_send(IMSG_CTL_PROFILE_ENABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_remove(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_REMOVE, &total, &ok); + printf("%zu envelope%s removed\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_resume_envelope(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_RESUME_EVP, &total, &ok); + printf("%zu envelope%s resumed\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_resume_mda(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_RESUME_MDA, NULL, 0); + return srv_check_result(1); +} + +static int +do_resume_mta(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_RESUME_MTA, NULL, 0); + return srv_check_result(1); +} + +static int +do_resume_route(int argc, struct parameter *argv) +{ + uint64_t v; + + if (argc == 0) + v = 0; + else + v = argv[0].u.u_routeid; + + srv_send(IMSG_CTL_RESUME_ROUTE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_resume_smtp(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_RESUME_SMTP, NULL, 0); + return srv_check_result(1); +} + +static int +do_schedule(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_SCHEDULE, &total, &ok); + printf("%zu envelope%s scheduled\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_show_envelope(int argc, struct parameter *argv) +{ + char buf[PATH_MAX]; + + if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/%016" PRIx64, + PATH_SPOOL, + PATH_QUEUE, + (evpid_to_msgid(argv[0].u.u_evpid) & 0xff000000) >> 24, + evpid_to_msgid(argv[0].u.u_evpid), + argv[0].u.u_evpid)) + errx(1, "unable to retrieve envelope"); + + display(buf); + + return (0); +} + +static int +do_show_hoststats(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTSTATS, NULL, 0); + + return (0); +} + +static int +do_show_message(int argc, struct parameter *argv) +{ + char buf[PATH_MAX]; + uint32_t msgid; + + if (argv[0].type == P_EVPID) + msgid = evpid_to_msgid(argv[0].u.u_evpid); + else + msgid = argv[0].u.u_msgid; + + if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/message", + PATH_SPOOL, + PATH_QUEUE, + (msgid & 0xff000000) >> 24, + msgid)) + errx(1, "unable to retrieve message"); + + display(buf); + + return (0); +} + +static int +do_show_queue(int argc, struct parameter *argv) +{ + struct envelope evp; + uint32_t msgid; + FTS *fts; + FTSENT *ftse; + char *qpath[] = {"/queue", NULL}; + char *tmp; + uint64_t evpid; + + now = time(NULL); + + if (!srv_connect()) { + log_init(1, LOG_MAIL); + queue_init("fs", 0); + if (chroot(PATH_SPOOL) == -1 || chdir("/") == -1) + err(1, "%s", PATH_SPOOL); + fts = fts_open(qpath, FTS_PHYSICAL|FTS_NOCHDIR, NULL); + if (fts == NULL) + err(1, "%s/queue", PATH_SPOOL); + + while ((ftse = fts_read(fts)) != NULL) { + switch (ftse->fts_info) { + case FTS_DP: + case FTS_DNR: + break; + case FTS_F: + tmp = NULL; + evpid = strtoull(ftse->fts_name, &tmp, 16); + if (tmp && *tmp != '\0') + break; + show_offline_envelope(evpid); + } + } + + fts_close(fts); + return (0); + } + + if (argc == 0) { + msgid = 0; + while (srv_iter_messages(&msgid)) + while (srv_iter_envelopes(msgid, &evp)) + show_queue_envelope(&evp, 1); + } else if (argv[0].type == P_MSGID) { + while (srv_iter_envelopes(argv[0].u.u_msgid, &evp)) + show_queue_envelope(&evp, 1); + } + + return (0); +} + +static int +do_show_hosts(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTS, NULL, 0); + + return (0); +} + +static int +do_show_relays(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_RELAYS, NULL, 0); + + return (0); +} + +static int +do_show_routes(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_ROUTES, NULL, 0); + + return (0); +} + +static int +do_show_stats(int argc, struct parameter *argv) +{ + struct stat_kv kv; + time_t duration; + + memset(&kv, 0, sizeof kv); + + while (1) { + srv_send(IMSG_CTL_GET_STATS, &kv, sizeof kv); + srv_recv(IMSG_CTL_GET_STATS); + srv_read(&kv, sizeof(kv)); + srv_end(); + + if (kv.iter == NULL) + break; + + if (strcmp(kv.key, "uptime") == 0) { + duration = time(NULL) - kv.val.u.counter; + printf("uptime=%lld\n", (long long)duration); + printf("uptime.human=%s\n", + duration_to_text(duration)); + } + else { + switch (kv.val.type) { + case STAT_COUNTER: + printf("%s=%zd\n", + kv.key, kv.val.u.counter); + break; + case STAT_TIMESTAMP: + printf("%s=%" PRId64 "\n", + kv.key, (int64_t)kv.val.u.timestamp); + break; + case STAT_TIMEVAL: + printf("%s=%lld.%lld\n", + kv.key, (long long)kv.val.u.tv.tv_sec, + (long long)kv.val.u.tv.tv_usec); + break; + case STAT_TIMESPEC: + printf("%s=%lld.%06ld\n", + kv.key, + (long long)kv.val.u.ts.tv_sec * 1000000 + + kv.val.u.ts.tv_nsec / 1000000, + kv.val.u.ts.tv_nsec % 1000000); + break; + } + } + } + + return (0); +} + +static int +do_show_status(int argc, struct parameter *argv) +{ + uint32_t sc_flags; + + srv_send(IMSG_CTL_SHOW_STATUS, NULL, 0); + srv_recv(IMSG_CTL_SHOW_STATUS); + srv_read(&sc_flags, sizeof(sc_flags)); + srv_end(); + printf("MDA %s\n", + (sc_flags & SMTPD_MDA_PAUSED) ? "paused" : "running"); + printf("MTA %s\n", + (sc_flags & SMTPD_MTA_PAUSED) ? "paused" : "running"); + printf("SMTP %s\n", + (sc_flags & SMTPD_SMTP_PAUSED) ? "paused" : "running"); + return (0); +} + +static int +do_trace(int argc, struct parameter *argv) +{ + int v; + + v = str_to_trace(argv[0].u.u_str); + + srv_send(IMSG_CTL_TRACE_ENABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_unprofile(int argc, struct parameter *argv) +{ + int v; + + v = str_to_profile(argv[0].u.u_str); + + srv_send(IMSG_CTL_PROFILE_DISABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_untrace(int argc, struct parameter *argv) +{ + int v; + + v = str_to_trace(argv[0].u.u_str); + + srv_send(IMSG_CTL_TRACE_DISABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_update_table(int argc, struct parameter *argv) +{ + const char *name = argv[0].u.u_str; + + srv_send(IMSG_CTL_UPDATE_TABLE, name, strlen(name) + 1); + return srv_check_result(1); +} + +static int +do_encrypt(int argc, struct parameter *argv) +{ + const char *p = NULL; + + droppriv(); + + if (argv) + p = argv[0].u.u_str; + execl(PATH_ENCRYPT, "encrypt", "--", p, (char *)NULL); + errx(1, "execl"); +} + +static int +do_block_mta(int argc, struct parameter *argv) +{ + struct ibuf *m; + + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + m = imsg_create(ibuf, IMSG_CTL_MTA_BLOCK, IMSG_VERSION, 0, + sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1); + if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1) + errx(1, "imsg_add"); + if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1) + errx(1, "imsg_add"); + imsg_close(ibuf, m); + + return srv_check_result(1); +} + +static int +do_unblock_mta(int argc, struct parameter *argv) +{ + struct ibuf *m; + + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + + m = imsg_create(ibuf, IMSG_CTL_MTA_UNBLOCK, IMSG_VERSION, 0, + sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1); + if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1) + errx(1, "imsg_add"); + if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1) + errx(1, "imsg_add"); + imsg_close(ibuf, m); + + return srv_check_result(1); +} + +static int +do_show_mta_block(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_BLOCK, NULL, 0); + + return (0); +} + +static int +do_discover(int argc, struct parameter *argv) +{ + uint64_t evpid; + uint32_t msgid; + size_t n_evp; + + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + + if (argv[0].type == P_EVPID) { + evpid = argv[0].u.u_evpid; + srv_send(IMSG_CTL_DISCOVER_EVPID, &evpid, sizeof evpid); + srv_recv(IMSG_CTL_DISCOVER_EVPID); + } else { + msgid = argv[0].u.u_msgid; + srv_send(IMSG_CTL_DISCOVER_MSGID, &msgid, sizeof msgid); + srv_recv(IMSG_CTL_DISCOVER_MSGID); + } + + if (rlen == 0) { + srv_end(); + return (0); + } else { + srv_read(&n_evp, sizeof n_evp); + srv_end(); + } + + printf("%zu envelope%s discovered\n", n_evp, (n_evp != 1) ? "s" : ""); + return (0); +} + +static int +do_spf_walk(int argc, struct parameter *argv) +{ + droppriv(); + + return spfwalk(argc, argv); +} + +#define cmd_install_priv(s, f) \ + cmd_install((s), privileged ? (f) : do_permission_denied) + +int +main(int argc, char **argv) +{ + gid_t gid; + struct group *gr; + int privileged; + char *argv_mailq[] = { "show", "queue", NULL }; + +#ifndef HAVE___PROGNAME + __progname = ssh_get_progname(argv[0]); +#endif + + /* check that smtpctl was installed setgid */ + if ((gr = getgrnam(SMTPD_QUEUE_GROUP)) == NULL) + errx(1, "unknown group %s", SMTPD_QUEUE_GROUP); + else if (gr->gr_gid != getegid()) + errx(1, "this program must be setgid %s", SMTPD_QUEUE_GROUP); + + sendmail_compat(argc, argv); + privileged = geteuid() == 0; + + gid = getgid(); + if (setresgid(gid, gid, gid) == -1) + err(1, "setresgid"); + + /* Privileged commands */ + cmd_install_priv("discover ", do_discover); + cmd_install_priv("discover ", do_discover); + cmd_install_priv("pause mta from for ", do_block_mta); + cmd_install_priv("resume mta from for ", do_unblock_mta); + cmd_install_priv("show mta paused", do_show_mta_block); + cmd_install_priv("log brief", do_log_brief); + cmd_install_priv("log verbose", do_log_verbose); + cmd_install_priv("monitor", do_monitor); + cmd_install_priv("pause envelope ", do_pause_envelope); + cmd_install_priv("pause envelope ", do_pause_envelope); + cmd_install_priv("pause envelope all", do_pause_envelope); + cmd_install_priv("pause mda", do_pause_mda); + cmd_install_priv("pause mta", do_pause_mta); + cmd_install_priv("pause smtp", do_pause_smtp); + cmd_install_priv("profile ", do_profile); + cmd_install_priv("remove ", do_remove); + cmd_install_priv("remove ", do_remove); + cmd_install_priv("remove all", do_remove); + cmd_install_priv("resume envelope ", do_resume_envelope); + cmd_install_priv("resume envelope ", do_resume_envelope); + cmd_install_priv("resume envelope all", do_resume_envelope); + cmd_install_priv("resume mda", do_resume_mda); + cmd_install_priv("resume mta", do_resume_mta); + cmd_install_priv("resume route ", do_resume_route); + cmd_install_priv("resume smtp", do_resume_smtp); + cmd_install_priv("schedule ", do_schedule); + cmd_install_priv("schedule ", do_schedule); + cmd_install_priv("schedule all", do_schedule); + cmd_install_priv("show envelope ", do_show_envelope); + cmd_install_priv("show hoststats", do_show_hoststats); + cmd_install_priv("show message ", do_show_message); + cmd_install_priv("show message ", do_show_message); + cmd_install_priv("show queue", do_show_queue); + cmd_install_priv("show queue ", do_show_queue); + cmd_install_priv("show hosts", do_show_hosts); + cmd_install_priv("show relays", do_show_relays); + cmd_install_priv("show routes", do_show_routes); + cmd_install_priv("show stats", do_show_stats); + cmd_install_priv("show status", do_show_status); + cmd_install_priv("trace ", do_trace); + cmd_install_priv("unprofile ", do_unprofile); + cmd_install_priv("untrace ", do_untrace); + cmd_install_priv("update table ", do_update_table); + + /* Unprivileged commands */ + cmd_install("encrypt", do_encrypt); + cmd_install("encrypt ", do_encrypt); + cmd_install("spf walk", do_spf_walk); + + if (strcmp(__progname, "mailq") == 0) + return cmd_run(2, argv_mailq); + if (strcmp(__progname, "smtpctl") == 0) + return cmd_run(argc - 1, argv + 1); + + errx(1, "unsupported mode"); + return (0); +} + +void +sendmail_compat(int argc, char **argv) +{ + FILE *offlinefp = NULL; + gid_t gid; + int i, r; + + if (strcmp(__progname, "sendmail") == 0 || + strcmp(__progname, "send-mail") == 0) { + /* + * determine whether we are called with flags + * that should invoke makemap/newaliases. + */ + for (i = 1; i < argc; i++) + if (strncmp(argv[i], "-bi", 3) == 0) + exit(makemap(P_SENDMAIL, argc, argv)); + + if (!srv_connect()) + offlinefp = offline_file(); + + gid = getgid(); + if (setresgid(gid, gid, gid) == -1) + err(1, "setresgid"); + +#if HAVE_PLEDGE + /* we'll reduce further down the road */ + if (pledge("stdio rpath wpath cpath tmppath flock " + "dns getpw recvfd", NULL) == -1) + err(1, "pledge"); +#endif + + sendmail = 1; + exit(enqueue(argc, argv, offlinefp)); + } else if (strcmp(__progname, "makemap") == 0) + exit(makemap(P_MAKEMAP, argc, argv)); + else if (strcmp(__progname, "newaliases") == 0) { + r = makemap(P_NEWALIASES, argc, argv); + /* + * if server is available, notify of table update. + * only makes sense for static tables AND if server is up. + */ + if (srv_connect()) { + srv_send(IMSG_CTL_UPDATE_TABLE, "aliases", strlen("aliases") + 1); + srv_check_result(0); + } + exit(r); + } +} + +static void +show_queue_envelope(struct envelope *e, int online) +{ + const char *src = "?", *agent = "?"; + char status[128], runstate[128], errline[LINE_MAX]; + + status[0] = '\0'; + + getflag(&e->flags, EF_BOUNCE, "bounce", status, sizeof(status)); + getflag(&e->flags, EF_AUTHENTICATED, "auth", status, sizeof(status)); + getflag(&e->flags, EF_INTERNAL, "internal", status, sizeof(status)); + getflag(&e->flags, EF_SUSPEND, "suspend", status, sizeof(status)); + getflag(&e->flags, EF_HOLD, "hold", status, sizeof(status)); + + if (online) { + if (e->flags & EF_PENDING) + (void)snprintf(runstate, sizeof runstate, "pending|%zd", + (ssize_t)(e->nexttry - now)); + else if (e->flags & EF_INFLIGHT) + (void)snprintf(runstate, sizeof runstate, + "inflight|%zd", (ssize_t)(now - e->lasttry)); + else + (void)snprintf(runstate, sizeof runstate, "invalid|"); + e->flags &= ~(EF_PENDING|EF_INFLIGHT); + } + else + (void)strlcpy(runstate, "offline|", sizeof runstate); + + if (e->flags) + errx(1, "%016" PRIx64 ": unexpected flags 0x%04x", e->id, + e->flags); + + if (status[0]) + status[strlen(status) - 1] = '\0'; + + if (e->type == D_MDA) + agent = "mda"; + else if (e->type == D_MTA) + agent = "mta"; + else if (e->type == D_BOUNCE) + agent = "bounce"; + + if (e->ss.ss_family == AF_LOCAL) + src = "local"; + else if (e->ss.ss_family == AF_INET) + src = "inet4"; + else if (e->ss.ss_family == AF_INET6) + src = "inet6"; + + strnvis(errline, e->errorline, sizeof(errline), 0); + + printf("%016"PRIx64 + "|%s|%s|%s|%s@%s|%s@%s|%s@%s" + "|%zu|%zu|%zu|%zu|%s|%s\n", + + e->id, + + src, + agent, + status, + e->sender.user, e->sender.domain, + e->rcpt.user, e->rcpt.domain, + e->dest.user, e->dest.domain, + + (size_t) e->creation, + (size_t) (e->creation + e->ttl), + (size_t) e->lasttry, + (size_t) e->retry, + runstate, + errline); +} + +static void +getflag(uint *bitmap, int bit, char *bitstr, char *buf, size_t len) +{ + if (*bitmap & bit) { + *bitmap &= ~bit; + (void)strlcat(buf, bitstr, len); + (void)strlcat(buf, ",", len); + } +} + +static void +show_offline_envelope(uint64_t evpid) +{ + FILE *fp = NULL; + char pathname[PATH_MAX]; + size_t plen; + char *p; + size_t buflen; + char buffer[sizeof(struct envelope)]; + + struct envelope evp; + + if (!bsnprintf(pathname, sizeof pathname, + "/queue/%02x/%08x/%016"PRIx64, + (evpid_to_msgid(evpid) & 0xff000000) >> 24, + evpid_to_msgid(evpid), evpid)) + goto end; + fp = fopen(pathname, "r"); + if (fp == NULL) + goto end; + + buflen = fread(buffer, 1, sizeof (buffer) - 1, fp); + p = buffer; + plen = buflen; + buffer[buflen] = '\0'; + + if (is_encrypted_buffer(p)) { + warnx("offline encrypted queue is not supported yet"); + goto end; + } + + if (is_gzip_buffer(p)) { + warnx("offline compressed queue is not supported yet"); + goto end; + } + + if (!envelope_load_buffer(&evp, p, plen)) + goto end; + evp.id = evpid; + show_queue_envelope(&evp, 0); + +end: + if (fp) + fclose(fp); +} + +static void +display(const char *s) +{ + FILE *fp; + char *key; + int gzipped; + char *gzcat_argv0 = strrchr(PATH_GZCAT, '/') + 1; + + if ((fp = fopen(s, "r")) == NULL) + err(1, "fopen"); + + if (is_encrypted_fp(fp)) { + int i; + FILE *ofp = NULL; + + if ((ofp = tmpfile()) == NULL) + err(1, "tmpfile"); + + for (i = 0; i < 3; i++) { + key = getpass("key> "); + if (crypto_setup(key, strlen(key))) + break; + } + if (i == 3) + errx(1, "crypto-setup: invalid key"); + + if (!crypto_decrypt_file(fp, ofp)) { + printf("object is encrypted: %s\n", key); + exit(1); + } + + fclose(fp); + fp = ofp; + fseek(fp, 0, SEEK_SET); + } + gzipped = is_gzip_fp(fp); + + lseek(fileno(fp), 0, SEEK_SET); + (void)dup2(fileno(fp), STDIN_FILENO); + if (gzipped) + execl(PATH_GZCAT, gzcat_argv0, (char *)NULL); + else + execl(PATH_CAT, "cat", (char *)NULL); + err(1, "execl"); +} + +static int +str_to_trace(const char *str) +{ + if (!strcmp(str, "imsg")) + return TRACE_IMSG; + if (!strcmp(str, "io")) + return TRACE_IO; + if (!strcmp(str, "smtp")) + return TRACE_SMTP; + if (!strcmp(str, "filters")) + return TRACE_FILTERS; + if (!strcmp(str, "mta")) + return TRACE_MTA; + if (!strcmp(str, "bounce")) + return TRACE_BOUNCE; + if (!strcmp(str, "scheduler")) + return TRACE_SCHEDULER; + if (!strcmp(str, "lookup")) + return TRACE_LOOKUP; + if (!strcmp(str, "stat")) + return TRACE_STAT; + if (!strcmp(str, "rules")) + return TRACE_RULES; + if (!strcmp(str, "mproc")) + return TRACE_MPROC; + if (!strcmp(str, "expand")) + return TRACE_EXPAND; + if (!strcmp(str, "all")) + return ~TRACE_DEBUG; + errx(1, "invalid trace keyword: %s", str); + return (0); +} + +static int +str_to_profile(const char *str) +{ + if (!strcmp(str, "imsg")) + return PROFILE_IMSG; + if (!strcmp(str, "queue")) + return PROFILE_QUEUE; + errx(1, "invalid profile keyword: %s", str); + return (0); +} + +static int +is_gzip_buffer(const char *buffer) +{ + uint16_t magic; + + memcpy(&magic, buffer, sizeof magic); +#define GZIP_MAGIC 0x8b1f + return (magic == GZIP_MAGIC); +} + +static int +is_gzip_fp(FILE *fp) +{ + uint8_t magic[2]; + int ret = 0; + + if (fread(&magic, 1, sizeof magic, fp) != sizeof magic) + goto end; + + ret = is_gzip_buffer((const char *)&magic); +end: + fseek(fp, 0, SEEK_SET); + return ret; +} + + +/* XXX */ +/* + * queue supports transparent encryption. + * encrypted chunks are prefixed with an API version byte + * which we ensure is unambiguous with gzipped / plain + * objects. + */ + +static int +is_encrypted_buffer(const char *buffer) +{ + uint8_t magic; + + magic = *buffer; +#define ENCRYPTION_MAGIC 0x1 + return (magic == ENCRYPTION_MAGIC); +} + +static int +is_encrypted_fp(FILE *fp) +{ + uint8_t magic; + int ret = 0; + + if (fread(&magic, 1, sizeof magic, fp) != sizeof magic) + goto end; + + ret = is_encrypted_buffer((const char *)&magic); +end: + fseek(fp, 0, SEEK_SET); + return ret; +} diff --git a/usr.sbin/smtpd/smtpctl/Makefile b/usr.sbin/smtpd/smtpctl/Makefile new file mode 100644 index 00000000..ef8148be --- /dev/null +++ b/usr.sbin/smtpd/smtpctl/Makefile @@ -0,0 +1,56 @@ +# $OpenBSD: Makefile,v 1.47 2018/07/03 01:34:43 mortimer Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= smtpctl +BINOWN= root +BINGRP= _smtpq + +BINMODE?=2555 + +BINDIR= /usr/sbin +MAN= smtpctl.8 aliases.5 forward.5 makemap.8 newaliases.8 + +CFLAGS+= -fstack-protector-all +CFLAGS+= -I${.CURDIR}/.. +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -Werror-implicit-function-declaration +CFLAGS+= -DNO_IO +CFLAGS+= -DCONFIG_MINIMUM +YFLAGS= + +SRCS= enqueue.c +SRCS+= parser.c +SRCS+= log.c +SRCS+= envelope.c +SRCS+= crypto.c +SRCS+= queue_backend.c +SRCS+= queue_fs.c +SRCS+= smtpctl.c +SRCS+= util.c +SRCS+= compress_backend.c +SRCS+= compress_gzip.c +SRCS+= to.c +SRCS+= expand.c +SRCS+= tree.c +SRCS+= config.c +SRCS+= dict.c +SRCS+= aliases.c +SRCS+= limit.c +SRCS+= makemap.c +SRCS+= parse.y +SRCS+= mailaddr.c +SRCS+= table.c +SRCS+= table_static.c +SRCS+= table_db.c +SRCS+= table_getpwnam.c +SRCS+= table_proc.c +SRCS+= unpack_dns.c +SRCS+= spfwalk.c + +LDADD+= -levent -lutil -lz -lcrypto +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBZ} ${LIBCRYPTO} +.include diff --git a/usr.sbin/smtpd/smtpd-api.h b/usr.sbin/smtpd/smtpd-api.h new file mode 100644 index 00000000..f83edd05 --- /dev/null +++ b/usr.sbin/smtpd/smtpd-api.h @@ -0,0 +1,290 @@ +/* $OpenBSD: smtpd-api.h,v 1.36 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2011 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SMTPD_API_H_ +#define _SMTPD_API_H_ + +#include "dict.h" +#include "tree.h" + +struct mailaddr { + char user[SMTPD_MAXLOCALPARTSIZE]; + char domain[SMTPD_MAXDOMAINPARTSIZE]; +}; + +#define PROC_QUEUE_API_VERSION 2 + +enum { + PROC_QUEUE_OK, + PROC_QUEUE_FAIL, + PROC_QUEUE_INIT, + PROC_QUEUE_CLOSE, + PROC_QUEUE_MESSAGE_CREATE, + PROC_QUEUE_MESSAGE_DELETE, + PROC_QUEUE_MESSAGE_COMMIT, + PROC_QUEUE_MESSAGE_FD_R, + PROC_QUEUE_ENVELOPE_CREATE, + PROC_QUEUE_ENVELOPE_DELETE, + PROC_QUEUE_ENVELOPE_LOAD, + PROC_QUEUE_ENVELOPE_UPDATE, + PROC_QUEUE_ENVELOPE_WALK, +}; + +#define PROC_SCHEDULER_API_VERSION 2 + +struct scheduler_info; + +enum { + PROC_SCHEDULER_OK, + PROC_SCHEDULER_FAIL, + PROC_SCHEDULER_INIT, + PROC_SCHEDULER_INSERT, + PROC_SCHEDULER_COMMIT, + PROC_SCHEDULER_ROLLBACK, + PROC_SCHEDULER_UPDATE, + PROC_SCHEDULER_DELETE, + PROC_SCHEDULER_HOLD, + PROC_SCHEDULER_RELEASE, + PROC_SCHEDULER_BATCH, + PROC_SCHEDULER_MESSAGES, + PROC_SCHEDULER_ENVELOPES, + PROC_SCHEDULER_SCHEDULE, + PROC_SCHEDULER_REMOVE, + PROC_SCHEDULER_SUSPEND, + PROC_SCHEDULER_RESUME, +}; + +enum envelope_flags { + EF_AUTHENTICATED = 0x01, + EF_BOUNCE = 0x02, + EF_INTERNAL = 0x04, /* Internal expansion forward */ + + /* runstate, not saved on disk */ + + EF_PENDING = 0x10, + EF_INFLIGHT = 0x20, + EF_SUSPEND = 0x40, + EF_HOLD = 0x80, +}; + +struct evpstate { + uint64_t evpid; + uint16_t flags; + uint16_t retry; + time_t time; +}; + +enum delivery_type { + D_MDA, + D_MTA, + D_BOUNCE, +}; + +struct scheduler_info { + uint64_t evpid; + enum delivery_type type; + uint16_t retry; + time_t creation; + time_t ttl; + time_t lasttry; + time_t lastbounce; + time_t nexttry; +}; + +#define SCHED_REMOVE 0x01 +#define SCHED_EXPIRE 0x02 +#define SCHED_UPDATE 0x04 +#define SCHED_BOUNCE 0x08 +#define SCHED_MDA 0x10 +#define SCHED_MTA 0x20 + +#define PROC_TABLE_API_VERSION 2 + +struct table_open_params { + uint32_t version; + char name[LINE_MAX]; +}; + +enum table_service { + K_NONE = 0x000, + K_ALIAS = 0x001, /* returns struct expand */ + K_DOMAIN = 0x002, /* returns struct destination */ + K_CREDENTIALS = 0x004, /* returns struct credentials */ + K_NETADDR = 0x008, /* returns struct netaddr */ + K_USERINFO = 0x010, /* returns struct userinfo */ + K_SOURCE = 0x020, /* returns struct source */ + K_MAILADDR = 0x040, /* returns struct mailaddr */ + K_ADDRNAME = 0x080, /* returns struct addrname */ + K_MAILADDRMAP = 0x100, /* returns struct maddrmap */ + K_RELAYHOST = 0x200, /* returns struct relayhost */ + K_STRING = 0x400, + K_REGEX = 0x800, +}; +#define K_ANY 0xfff + +enum { + PROC_TABLE_OK, + PROC_TABLE_FAIL, + PROC_TABLE_OPEN, + PROC_TABLE_CLOSE, + PROC_TABLE_UPDATE, + PROC_TABLE_CHECK, + PROC_TABLE_LOOKUP, + PROC_TABLE_FETCH, +}; + +enum enhanced_status_code { + /* 0.0 */ + ESC_OTHER_STATUS = 00, + + /* 1.x */ + ESC_OTHER_ADDRESS_STATUS = 10, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS = 11, + ESC_BAD_DESTINATION_SYSTEM_ADDRESS = 12, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX = 13, + ESC_DESTINATION_MAILBOX_ADDRESS_AMBIGUOUS = 14, + ESC_DESTINATION_ADDRESS_VALID = 15, + ESC_DESTINATION_MAILBOX_HAS_MOVED = 16, + ESC_BAD_SENDER_MAILBOX_ADDRESS_SYNTAX = 17, + ESC_BAD_SENDER_SYSTEM_ADDRESS = 18, + + /* 2.x */ + ESC_OTHER_MAILBOX_STATUS = 20, + ESC_MAILBOX_DISABLED = 21, + ESC_MAILBOX_FULL = 22, + ESC_MESSAGE_LENGTH_TOO_LARGE = 23, + ESC_MAILING_LIST_EXPANSION_PROBLEM = 24, + + /* 3.x */ + ESC_OTHER_MAIL_SYSTEM_STATUS = 30, + ESC_MAIL_SYSTEM_FULL = 31, + ESC_SYSTEM_NOT_ACCEPTING_MESSAGES = 32, + ESC_SYSTEM_NOT_CAPABLE_OF_SELECTED_FEATURES = 33, + ESC_MESSAGE_TOO_BIG_FOR_SYSTEM = 34, + ESC_SYSTEM_INCORRECTLY_CONFIGURED = 35, + + /* 4.x */ + ESC_OTHER_NETWORK_ROUTING_STATUS = 40, + ESC_NO_ANSWER_FROM_HOST = 41, + ESC_BAD_CONNECTION = 42, + ESC_DIRECTORY_SERVER_FAILURE = 43, + ESC_UNABLE_TO_ROUTE = 44, + ESC_MAIL_SYSTEM_CONGESTION = 45, + ESC_ROUTING_LOOP_DETECTED = 46, + ESC_DELIVERY_TIME_EXPIRED = 47, + + /* 5.x */ + ESC_INVALID_RECIPIENT = 50, + ESC_INVALID_COMMAND = 51, + ESC_SYNTAX_ERROR = 52, + ESC_TOO_MANY_RECIPIENTS = 53, + ESC_INVALID_COMMAND_ARGUMENTS = 54, + ESC_WRONG_PROTOCOL_VERSION = 55, + + /* 6.x */ + ESC_OTHER_MEDIA_ERROR = 60, + ESC_MEDIA_NOT_SUPPORTED = 61, + ESC_CONVERSION_REQUIRED_AND_PROHIBITED = 62, + ESC_CONVERSION_REQUIRED_BUT_NOT_SUPPORTED = 63, + ESC_CONVERSION_WITH_LOSS_PERFORMED = 64, + ESC_CONVERSION_FAILED = 65, + + /* 7.x */ + ESC_OTHER_SECURITY_STATUS = 70, + ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED = 71, + ESC_MAILING_LIST_EXPANSION_PROHIBITED = 72, + ESC_SECURITY_CONVERSION_REQUIRED_NOT_POSSIBLE = 73, + ESC_SECURITY_FEATURES_NOT_SUPPORTED = 74, + ESC_CRYPTOGRAPHIC_FAILURE = 75, + ESC_CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED = 76, + ESC_MESSAGE_INTEGRITY_FAILURE = 77, +}; + +enum enhanced_status_class { + ESC_STATUS_OK = 2, + ESC_STATUS_TEMPFAIL = 4, + ESC_STATUS_PERMFAIL = 5, +}; + +static inline uint32_t +evpid_to_msgid(uint64_t evpid) +{ + return (evpid >> 32); +} + +static inline uint64_t +msgid_to_evpid(uint32_t msgid) +{ + return ((uint64_t)msgid << 32); +} + + +/* esc.c */ +const char *esc_code(enum enhanced_status_class, enum enhanced_status_code); +const char *esc_description(enum enhanced_status_code); + + +/* queue */ +void queue_api_on_close(int(*)(void)); +void queue_api_on_message_create(int(*)(uint32_t *)); +void queue_api_on_message_commit(int(*)(uint32_t, const char*)); +void queue_api_on_message_delete(int(*)(uint32_t)); +void queue_api_on_message_fd_r(int(*)(uint32_t)); +void queue_api_on_envelope_create(int(*)(uint32_t, const char *, size_t, uint64_t *)); +void queue_api_on_envelope_delete(int(*)(uint64_t)); +void queue_api_on_envelope_update(int(*)(uint64_t, const char *, size_t)); +void queue_api_on_envelope_load(int(*)(uint64_t, char *, size_t)); +void queue_api_on_envelope_walk(int(*)(uint64_t *, char *, size_t)); +void queue_api_on_message_walk(int(*)(uint64_t *, char *, size_t, + uint32_t, int *, void **)); +void queue_api_no_chroot(void); +void queue_api_set_chroot(const char *); +void queue_api_set_user(const char *); +int queue_api_dispatch(void); + +/* scheduler */ +void scheduler_api_on_init(int(*)(void)); +void scheduler_api_on_insert(int(*)(struct scheduler_info *)); +void scheduler_api_on_commit(size_t(*)(uint32_t)); +void scheduler_api_on_rollback(size_t(*)(uint32_t)); +void scheduler_api_on_update(int(*)(struct scheduler_info *)); +void scheduler_api_on_delete(int(*)(uint64_t)); +void scheduler_api_on_hold(int(*)(uint64_t, uint64_t)); +void scheduler_api_on_release(int(*)(int, uint64_t, int)); +void scheduler_api_on_batch(int(*)(int, int *, size_t *, uint64_t *, int *)); +void scheduler_api_on_messages(size_t(*)(uint32_t, uint32_t *, size_t)); +void scheduler_api_on_envelopes(size_t(*)(uint64_t, struct evpstate *, size_t)); +void scheduler_api_on_schedule(int(*)(uint64_t)); +void scheduler_api_on_remove(int(*)(uint64_t)); +void scheduler_api_on_suspend(int(*)(uint64_t)); +void scheduler_api_on_resume(int(*)(uint64_t)); +void scheduler_api_no_chroot(void); +void scheduler_api_set_chroot(const char *); +void scheduler_api_set_user(const char *); +int scheduler_api_dispatch(void); + +/* table */ +void table_api_on_update(int(*)(void)); +void table_api_on_check(int(*)(int, struct dict *, const char *)); +void table_api_on_lookup(int(*)(int, struct dict *, const char *, char *, size_t)); +void table_api_on_fetch(int(*)(int, struct dict *, char *, size_t)); +int table_api_dispatch(void); +const char *table_api_get_name(void); + +#endif diff --git a/usr.sbin/smtpd/smtpd-defines.h b/usr.sbin/smtpd/smtpd-defines.h new file mode 100644 index 00000000..f22a546f --- /dev/null +++ b/usr.sbin/smtpd/smtpd-defines.h @@ -0,0 +1,68 @@ +/* $OpenBSD: smtpd-defines.h,v 1.12 2020/02/24 16:16:08 millert Exp $ */ + +/* + * Copyright (c) 2013 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#define SMTPD_TABLENAME_SIZE (64 + 1) +#define SMTPD_TAG_SIZE (32 + 1) + +/* buffer sizes for email address components */ +#define SMTPD_MAXLOCALPARTSIZE (255 + 1) +#define SMTPD_MAXDOMAINPARTSIZE (255 + 1) +#define SMTPD_MAXMAILADDRSIZE (255 + 1) + +/* buffer size for virtual username (can be email addresses) */ +#define SMTPD_VUSERNAME_SIZE (255 + 1) +#define SMTPD_SUBADDRESS_SIZE (255 + 1) + +#ifndef SMTPD_USER +#define SMTPD_USER "_smtpd" +#endif +#ifndef PATH_CHROOT +#define PATH_CHROOT "/var/empty" +#endif +#ifndef SMTPD_QUEUE_USER +#define SMTPD_QUEUE_USER "_smtpq" +#endif +#ifndef SMTPD_QUEUE_GROUP +#define SMTPD_QUEUE_GROUP "_smtpq" +#endif +#ifndef PATH_SPOOL +#define PATH_SPOOL "/var/spool/smtpd" +#endif + +#ifndef PATH_MAILLOCAL +#define PATH_MAILLOCAL PATH_LIBEXEC "/mail.local" +#endif + +#ifndef PATH_MAKEMAP +#define PATH_MAKEMAP "/usr/sbin/makemap" +#endif + +#define SUBADDRESSING_DELIMITER "+" + + +/* sendmail compat */ + +#define EX_OK 0 +#define EX_NOHOST 68 +#define EX_UNAVAILABLE 69 +#define EX_SOFTWARE 70 +#define EX_TEMPFAIL 75 diff --git a/usr.sbin/smtpd/smtpd-filters.7 b/usr.sbin/smtpd/smtpd-filters.7 new file mode 100644 index 00000000..5af7008e --- /dev/null +++ b/usr.sbin/smtpd/smtpd-filters.7 @@ -0,0 +1,653 @@ +.\" $OpenBSD: smtpd-filters.7,v 1.6 2020/04/25 09:44:02 eric Exp $ +.\" +.\" Copyright (c) 2008 Janne Johansson +.\" Copyright (c) 2009 Jacek Masiulaniec +.\" Copyright (c) 2012 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.Dd $Mdocdate: April 25 2020 $ +.Dt FILTERS 7 +.Os +.Sh NAME +.Nm filters +.Nd filtering API for the +.Xr smtpd 8 +daemon +.Sh DESCRIPTION +The +.Xr smtpd 8 +daemon provides a Simple Mail Transfer Protocol (SMTP) implementation +that allows an ordinary machine to become Mail eXchangers (MX). +Many features that are commonly used by MX, +such as delivery reporting or Spam filtering, +are outside the scope of SMTP and too complex to fit in +.Xr smtpd 8 . +.Pp +Because an MX needs to provide these features, +.Xr smtpd 8 +provides an API to extend its behavior through pluggable +.Nm . +.Pp +At runtime, +.Xr smtpd 8 +can report events to +.Nm +and query what it should answer to these events. +This allows the decision logic to rely on third-party programs. +.Sh DESIGN +The +.Nm +are programs that run as unique standalone processes, +they do not share +.Xr smtpd 8 +memory space. +They are executed by +.Xr smtpd 8 +at startup and expected to run in an infinite loop, +reading events and filtering requests from +.Xr stdin 4 , +writing responses to +.Xr stdout 4 +and logging to +.Xr stderr 4 . +They are not allowed to terminate. +.Pp +Because +.Nm +are standalone programs that communicate with +.Xr smtpd 8 +through +.Xr fd 4 , +they may run as different users than +.Xr smtpd 8 +and may be written in any language. +The +.Nm +must not use blocking I/O, +they must support answering asynchronously to +.Xr smtpd 8 . +.Sh REPORT AND FILTER +The API relies on two streams, +report and filter. +.Pp +The report stream is a one-way stream which allows +.Xr smtpd 8 +to inform +.Nm +in real-time about events that are occurring in the daemon. +The report events do not expect an answer from +.Nm , +it is just meant to provide them with informations. +A filter should be able to replicate the +.Xr smtpd 8 +state for a session by gathering informations coming from report events. +No decision is ever taken by the report stream. +.Pp +The filter stream is a two-way stream which allows +.Xr smtpd 8 +to query +.Nm +about what it should do with a session at a given phase. +The filter requests expects an answer from +.Nm , +.Xr smtpd 8 +will not let the session move forward until then. +A decision must always be taken by the filter stream. +.Pp +It is sometimes possible to rely on filter requests to gather information, +but because a reponse is expected by +.Xr smtpd 8 , +this is more costly than using report events. +The correct pattern for writing filters is to use the report events to +create a local state for a session, +then use filter requests to take decisions based on this state. +The only case when using filter request instead of report events is correct, +is when a decision is required for the filter request and there is no need for +more information than that of the event. +.Sh PROTOCOL +The protocol is straightforward, +it consists of a human-readable line exchanges between +.Nm +and +.Xr smtpd 8 +through +.Xr fd 4 . +.Pp +The protocol begins with a handshake. +First, +.Xr smtpd 8 +provides +.Nm +with general configuration information in the form of key-value lines: +.Bd -literal -offset indent +config|smtpd-version|6.6.1 +config|smtp-session-timeout|300 +config|subsystem|smtp-in +config|ready +.Ed +.Pp +Then, +.Nm +register the stream, +subsystem and event they want to handle: +.Bd -literal -offset indent +register|report|smtp-in|link-connect +register|ready +.Ed +.Pp +Finally, +.Xr smtpd 8 +will emit report events and filter requests, +expecting +.Nm +to react accordingly either by responding or not depending on the stream: +.Bd -literal -offset indent +report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +report|0.5|1576147242.200225|smtp-in|link-connect|7641dfb3798eb5bf|mail.openbsd.org|pass|199.185.178.25:31205|45.77.67.80:25 +report|0.5|1576148447.982572|smtp-in|link-connect|7641dfc063102cbd|mail.openbsd.org|pass|199.185.178.25:24786|45.77.67.80:25 +.Ed +.Pp +The char +.Dq | +may only appear in the last field of a payload, +in which case it should be considered a regular char and not a separator. +Other fields have strict formatting excluding the possibility of having a +.Dq | . +.Pp +The list of subsystems and events, +as well as the format of requests and reponses, +will be documented in the sections below. +.Sh CONFIGURATION +During the initial handshake, +.Xr smtpd 8 +will emit a serie of configuration keys and values. +The list is meant to be ignored by +.Nm +that do not require it and consumed gracefully by filters that do. +.Pp +There are currently three keys: +.Bd -literal -offset indent +config|smtpd-version|6.6.1 +config|smtp-session-timeout|300 +config|subsystem|smtp-in +.Ed +.Pp +When +.Xr smtpd 8 +has sent all configuration keys it emits the following line: +.Bd -literal -offset indent +config|ready +.Ed +.Sh REPORT EVENTS +There is currently only one subsystem supported in the API: +smtp-in. +.Pp +Each report event is generated by +.Xr smtpd 8 +as a single line similar to the one below: +.Bd -literal -offset indent +report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +The format consists of a protocol prefix containing the stream, +the protocol version, +the timestamp, +the subsystem, +the event and the unique session identifier separated by +.Dq | : +.Bd -literal -offset indent +report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00 +.Ed +.Pp +It is followed by a suffix containing the event-specific parameters, +also separated by +.Dq | : +.Bd -literal -offset indent +mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +The list of events and event-specific parameters are provided here for smtp-in: +.Bl -tag -width Ds +.It Ic link-connect : Ar rdns fcrdns src dest +This event is generated upon connection. +.Pp +.Ar rdns +contains the reverse DNS hostname for the remote end or an empty string if none. +.Pp +.Ar fcrdns +contains the string +.Dq pass +or +.Dq fail +depending on if the remote end validates FCrDNS. +.Pp +.Ar src +holds either the IP address and port from source address, +in the format +.Dq address:port +or the path to a UNIX socket in the format +.Dq unix:/path . +.Pp +.Ar dest +holds either the IP address and port from destination address, +in the format +.Dq address:port +or the path to a UNIX socket in the format +.Dq unix:/path . +.It Ic link-greeting : Ar hostname +This event is generated upon display of the server banner. +.Pp +.Ar hostname +contains the hostname displayed in the banner. +.It Ic link-identify : Ar method identity +This event is generated upon +.Dq HELO +or +.Dq EHLO +command from the client. +.Pp +.Ar method +contains the string +.Dq HELO +or +.Dq EHLO +indicating the method used by the client. +.Pp +.Ar identity +contains the identity provided by the client. +.It Ic link-tls : Ar tls-string +This event is generated upon successful negotiation of TLS. +.Pp +.Ar tls-string +contains a colon-separated list of TLS properties including the TLS version, +the cipher suite used by the session and the cipher strenght in bits. +.It Ic link-disconnect +This event is generated upon disconnection of the client. +.It Ic link-auth : Ar username result +This event is generated upon authentication attempt of the client. +.Pp +.Ar username +contains the username used for the authentication attempt. +.Pp +.Ar result +contains the string +.Dq pass , +.Dq fail +or +.Dq error +depending on the result of the authentication attempt. +.It Ic tx-reset : Op message-id +This event is generated when a transaction is reset. +.Pp +If reset happend while in a transaction, +.Ar message-id +contains the identifier of the transaction being reset. +.It Ic tx-begin : Ar message-id +This event is generated when a transaction is initiated. +.Pp +.Ar message-id +contains the identifier for the transaction. +.It Ic tx-mail : Ar message-id Ar result address +This event is generated when client emits +.Dq MAIL FROM . +.Pp +.Ar message-id +contains the identifier for the transaction. +.Pp +.Ar result +contains +.Dq ok +if the sender was accepted, +.Dq permfail +if it was rejected +or +.Dq tempfail +if it was rejected for a transient error. +.Pp +.Ar address +contains the e-mail address of the sender. +The address is normalized and sanitized, +the protocol +.Dq < +and +.Dq > +are removed and so are parameters to +.Dq MAIL FROM . +.It Ic tx-rcpt : Ar message-id Ar result address +This event is generated when client emits +.Dq RCPT TO . +.Pp +.Ar message-id +contains the identifier for the transaction. +.Pp +.Ar result +contains +.Dq ok +if the recipient was accepted, +.Dq permfail +if it was rejected +or +.Dq tempfail +if it was rejected for a transient error. +.Pp +.Ar address +contains the e-mail address of the recipient. +The address is normalized and sanitized, +the protocol +.Dq < +and +.Dq > +are removed and so are parameters to +.Dq RCPT TO . +.It Ic tx-envelope : Ar message-id Ar envelope-id +This event is generated when an envelope is accepted. +.Pp +.Ar envelope-id +contains the unique identifier for the envelope. +.It Ic tx-data : Ar message-id Ar result +This event is generated when client has emitted +.Dq DATA . +.Pp +.Ar message-id +contains the unique identifier for the transaction. +.Pp +.Ar result +contains +.Dq ok +if server accepted to process the message, +.Dq permfail +if it has not accepted and +.Dq tempfail +if a transient error is preventing the processing of message. +.It Ic tx-commit : Ar message-id Ar message-size +This event is generated when a transaction has been accepted by the server. +.Pp +.Ar message-id +contains the unique identifier for the SMTP transaction. +.Pp +.Ar message-size +contains the size of the message submitted in the +.Dq DATA +phase of the SMTP transaction. +.It Ic tx-rollback : Ar message-id +This event is generated when a transaction has been rejected by the server. +.Pp +.Ar message-id +contains the unique identifier for the SMTP transaction. +.It Ic protocol-client : Ar command +This event is generated for every command submitted by the client. +It contains the raw command as received by the server. +.Pp +.Ar command +contains the command emitted by the client to the server. +.It Ic protocol-server : Ar response +This event is generated for every response emitted by the server. +It contains the raw response as emitted by the server. +.Pp +.Ar response +contains the response emitted by the server to the client. +.It Ic filter-report : Ar filter-kind Ar name message +This event is generated when a filter emits a report. +.Pp +.Ar filter-kind may be either +.Dq builtin +or +.Dq proc +depending on if the filter is an +.Xr smtpd 8 +builtin filter or a proc filter implementing the API. +.Pp +.Ar name +is the name of the filter that generated the report. +.Pp +.Ar message +is a filter-specific message. +.It Ic filter-response : Ar phase response Op param +This event is generated when a filter responds to a filtering request. +.Pp +.Ar phase +contains the phase name for the request. +The phases are documented in the next section. +.Pp +.Ar response +contains the response of the filter to the request, +it is either one of +.Dq proceed , +.Dq report , +.Dq reject , +.Dq disconnect , +.Dq junk or +.Dq rewrite . +.Pp +If specified, +.Ar param +is the parameter to the response. +.It Ic timeout +This event is generated when a timeout happens for a session. +.El +.Sh FILTER REQUESTS +There is currently only one subsystem supported in the API: +smtp-in. +.Pp +The filter requests allow +.Xr smtpd 8 +to query +.Nm +about what to do with a session at a particular phase. +In addition, +they allow +.Nm +to alter the content of a message by adding, +modifying, +or suppressing lines of input in a way that is similar to what program like +.Xr sed 1 +or +.Xr grep 1 +would do. +.Pp +Each filter request is generated by +.Xr smtpd 8 +as a single line similar to the one below: +.Bd -literal -offset indent +filter|0.5|1576146008.006099|smtp-in|connect|7641df9771b4ed00|1ef1c203cc576e5d|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +The format consists of a protocol prefix containing the stream, +the protocol version, +the timestamp, +the subsystem, +the filtering phase, +the unique session identifier and an opaque token separated by +.Dq | +that the filter should provide in its response: +.Bd -literal -offset indent +filter|0.5|1576146008.006099|smtp-in|connect|7641df9771b4ed00|1ef1c203cc576e5d +.Ed +.Pp +It is followed by a suffix containing the phase-specific parameters to the +filter request, +also separated by +.Dq | : +.Bd -literal -offset indent +mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +Unlike with report events, +.Xr smtpd 8 +expects answers from filter requests and will not allow a session to move +forward before the filter has instructed +.Xr smtpd 8 +what to do with it. +.Pp +For all phases, +excepted +.Dq data-line , +the responses must follow the same construct, +a message type +.Dq filter-result , +followed by the unique session id, +the opaque token, +a decision and optional decision-specific parameters: +.Bd -literal -offset indent +filter-result|7641df9771b4ed00|1ef1c203cc576e5d|proceed +filter-result|7641df9771b4ed00|1ef1c203cc576e5d|reject|550 nope +.Ed +.Pp +The possible decisions to a +.Dq filter-result +message will be described below. +.Pp +For the +.Dq data-line +phase, +.Nm +are fed with a stream of lines corresponding to the message to filter, +and terminated by a single dot: +.Bd -literal -offset indent +filter|0.5|1576146008.006099|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|line 1 +filter|0.5|1576146008.006103|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|line 2 +filter|0.5|1576146008.006105|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|. +.Ed +.Pp +They are expected to produce an output stream similarly terminate by a single +dot. +A filter may inject, +suppress, +modify or echo back the lines it receives. +Ultimately, +.Xr smtpd 8 +will assume that the message consists of the output from +.Nm . +.Pp +Note that filters may be chained and the lines that are input into a filter +are the lines that are output from previous filter. +.Pp +The response to +.Dq data-line +requests use their own construct. +A +.Dq filter-dataline +prefix, +followed by the unique session identifier, +the opaque token and the output line as follows: +.Bd -literal -offset indent +filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|line 1 +filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|line 2 +filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|. +.Ed +.Pp +The list of events and event-specific parameters are provided here for smtp-in: +.Bl -tag -width Ds +.It Ic connect : Ar rdns fcrdns src dest +This request is emitted after connection, +before the banner is displayed. +.It Ic helo : Ar identity +This request is emitted after the client has emitted +.Dq HELO . +.It Ic ehlo : Ar identity +This request is emitted after the client has emitted +.Dq EHLO . +.It Ic starttls : Ar tls-string +This request is emitted after the client has requested +.Dq STARTTLS . +.It Ic auth : Ar auth +This request is emitted after the client has requested +.Dq AUTH . +.It Ic mail-from : Ar address +This request is emitted after the client has requested +.Dq MAIL FROM . +.It Ic rcpt-to : Ar address +This request is emitted after the client has requested +.Dq RCPT TO . +.It Ic data +This request is emitted after the client has requested +.Dq DATA . +.It Ic data-line : Ar line +This request is emitted for each line of input in the +.Dq DATA +phase. +The lines are raw dot-escaped SMTP DATA input, +terminated with a single dot. +.It Ic commit +This request is emitted after the final single dot is received. +.El +.Pp +For every filtering phase, +excepted +.Dq data-line , +the following decisions may be taken by a filter: +.Bl -tag -width Ds +.It Ic proceed +No action is taken, +session or transaction may be passed to the next filter. +.It Ic junk +The session or transaction is marked as Spam. +.Xr smtpd 8 +will prepend a +.Dq X-Spam +header to the message. +.It Ic reject Ar error +The command is rejected with the message +.Ar error . +The message must be a valid SMTP message including status code, +5xx or 4xx. +.Pp +Messages starting with a 5xx status result in a permanent failure, +those starting with a 4xx status result in a temporary failure. +.Pp +Messages starting with a 421 status will result in a client disconnect. +.It Ic disconnect Ar error +The client is disconnected with the message +.Ar error . +The message must be a valid SMTP message including status code, +5xx or 4xx. +.Pp +Messages starting with a 5xx status result in a permanent failure, +those starting with a 4xx status result in a temporary failure. +.It Ic rewrite Ar parameter +The command parameter is rewritten. +.Pp +This decision allows a filter to perform a rewrite of client-submitted +commands before they are processed by the SMTP engine. +.Ar parameter +is expected to be a valid SMTP parameter for the command. +.It Ic report Ar parameter +Generates a report with +.Ar parameter +for this filter. +.El +.\".Sh EXAMPLES +.\"This example filter written in +.\".Xr sh 1 +.\"will echo back... +.\".Bd -literal -offset indent +.\"XXX +.\".Ed +.\".Pp +.\"This example filter will filter... +.\".Bd -literal -offset indent +.\"XXX +.\".Ed +.\".Pp +.\"Note that libraries may provide a simpler interface to +.\".Nm +.\"that does not require implementing the protocol itself. +.\".Ed +.Sh SEE ALSO +.Xr smtpd 8 +.Sh HISTORY +.Nm +first appeared in +.Ox 6.6 . diff --git a/usr.sbin/smtpd/smtpd.8 b/usr.sbin/smtpd/smtpd.8 new file mode 100644 index 00000000..e3429f07 --- /dev/null +++ b/usr.sbin/smtpd/smtpd.8 @@ -0,0 +1,167 @@ +.\" $OpenBSD: smtpd.8,v 1.32 2017/01/03 22:11:39 jmc Exp $ +.\" +.\" Copyright (c) 2012, Eric Faurot +.\" Copyright (c) 2008, Gilles Chehade +.\" Copyright (c) 2008, Pierre-Yves Ritschard +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 3 2017 $ +.Dt SMTPD 8 +.Os +.Sh NAME +.Nm smtpd +.Nd Simple Mail Transfer Protocol daemon +.Sh SYNOPSIS +.Nm +.Op Fl dFhnv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Ar file +.Op Fl P Ar system +.Op Fl T Ar trace +.Sh DESCRIPTION +.Nm +is a Simple Mail Transfer Protocol +.Pq SMTP +daemon which can be used as a machine's primary mail system. +.Nm +can listen on a network interface and handle SMTP +transactions; it can also be fed messages through the standard +.Xr sendmail 8 +interface. +It can relay messages through remote mail transfer agents or store them +locally using either the mbox or maildir format. +This implementation supports SMTP as defined by RFC 5321 as well as several +extensions. +A running +.Nm +can be controlled through +.Xr smtpctl 8 . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl F +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Xr syslogd 8 . +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl h +Display version and usage. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl P Ar system +Pause a specific subsystem at startup. +Normal operation can be resumed using +.Xr smtpctl 8 . +This option can be used multiple times. +The accepted values are: +.Pp +.Bl -tag -width "smtpXXX" -compact +.It mda +Do not schedule local deliveries. +.It mta +Do not schedule remote transfers. +.It smtp +Do not listen on SMTP sockets. +.El +.It Fl T Ar trace +Enables real-time tracing at startup. +Normal operation can be resumed using +.Xr smtpctl 8 . +This option can be used multiple times. +The accepted values are: +.Pp +.Bl -bullet -compact +.It +imsg +.It +io +.It +smtp (incoming sessions) +.It +filters +.It +transfer (outgoing sessions) +.It +bounce +.It +scheduler +.It +expand (aliases/virtual/forward expansion) +.It +lookup (user/credentials lookups) +.It +stat +.It +rules (matched by incoming sessions) +.It +mproc +.It +all +.El +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/etc/mail/smtpd.confXXX" -compact +.It Pa /etc/mail/mailname +Alternate server name to use. +.It Pa /etc/mail/smtpd.conf +Default +.Nm +configuration file. +.It Pa /var/run/smtpd.sock +.Ux Ns -domain +socket used for communication with +.Xr smtpctl 8 . +.It Pa /var/spool/smtpd/ +Spool directories for mail during processing. +.It Pa ~/.forward +User email forwarding information. +.El +.Sh SEE ALSO +.Xr forward 5 , +.Xr smtpd.conf 5 , +.Xr mailwrapper 8 , +.Xr smtpctl 8 +.Sh STANDARDS +.Rs +.%A J. Klensin +.%D October 2008 +.%R RFC 5321 +.%T Simple Mail Transfer Protocol +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 4.6 . diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c new file mode 100644 index 00000000..f18b1446 --- /dev/null +++ b/usr.sbin/smtpd/smtpd.c @@ -0,0 +1,2328 @@ +/* $OpenBSD: smtpd.c,v 1.333 2020/05/06 16:03:30 millert Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2009 Jacek Masiulaniec + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include /* Needed for flock */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BSD_AUTH +#include +#endif + +#ifdef USE_PAM +#if defined(HAVE_SECURITY_PAM_APPL_H) +#include +#elif defined (HAVE_PAM_PAM_APPL_H) +#include +#endif +#endif + +#ifdef HAVE_CRYPT_H +#include /* needed for crypt() */ +#endif +#include +#include +#include +#include +#include +#include /* needed for setgroups */ +#include +#include +#include +#include +#include +#ifdef HAVE_LOGIN_CAP_H +#include +#endif +#ifdef HAVE_PATHS_H +#include +#endif +#include +#include +#include +#ifdef HAVE_SHADOW_H +#include /* needed for getspnam() */ +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UTIL_H +#include +#endif + +#include +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +extern char *__progname; + +#define SMTPD_MAXARG 32 + +static void parent_imsg(struct mproc *, struct imsg *); +static void usage(void); +static int smtpd(void); +static void parent_shutdown(void); +static void parent_send_config(int, short, void *); +static void parent_send_config_lka(void); +static void parent_send_config_pony(void); +static void parent_send_config_ca(void); +static void parent_sig_handler(int, short, void *); +static void forkmda(struct mproc *, uint64_t, struct deliver *); +static int parent_forward_open(char *, char *, uid_t, gid_t); +static struct child *child_add(pid_t, int, const char *); +static struct mproc *start_child(int, char **, char *); +static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int); +static void setup_peers(struct mproc *, struct mproc *); +static void setup_done(struct mproc *); +static void setup_proc(void); +static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int); +static int imsg_wait(struct imsgbuf *, struct imsg *, int); + +static void offline_scan(int, short, void *); +static int offline_add(char *, uid_t, gid_t); +static void offline_done(void); +static int offline_enqueue(char *, uid_t, gid_t); + +static void purge_task(void); +static int parent_auth_user(const char *, const char *); +static void load_pki_tree(void); +static void load_pki_keys(void); + +static void fork_filter_processes(void); +static void fork_filter_process(const char *, const char *, const char *, const char *, const char *, uint32_t); + +enum child_type { + CHILD_DAEMON, + CHILD_MDA, + CHILD_PROCESSOR, + CHILD_ENQUEUE_OFFLINE, +}; + +struct child { + pid_t pid; + enum child_type type; + const char *title; + int mda_out; + uint64_t mda_id; + char *path; + char *cause; +}; + +struct offline { + TAILQ_ENTRY(offline) entry; + uid_t uid; + gid_t gid; + char *path; +}; + +#define OFFLINE_READMAX 20 +#define OFFLINE_QUEUEMAX 5 +static size_t offline_running = 0; +TAILQ_HEAD(, offline) offline_q; + +static struct event config_ev; +static struct event offline_ev; +static struct timeval offline_timeout; + +static pid_t purge_pid = -1; + +extern char **environ; +void (*imsg_callback)(struct mproc *, struct imsg *); + +enum smtp_proc_type smtpd_process; + +struct smtpd *env = NULL; + +struct mproc *p_control = NULL; +struct mproc *p_lka = NULL; +struct mproc *p_parent = NULL; +struct mproc *p_queue = NULL; +struct mproc *p_scheduler = NULL; +struct mproc *p_pony = NULL; +struct mproc *p_ca = NULL; + +const char *backend_queue = "fs"; +const char *backend_scheduler = "ramqueue"; +const char *backend_stat = "ram"; + +int profiling = 0; +int debug = 0; +int foreground = 0; +int control_socket = -1; + +struct tree children; + +/* Saved arguments to main(). */ +char **saved_argv; +int saved_argc; + +static void +parent_imsg(struct mproc *p, struct imsg *imsg) +{ + struct forward_req *fwreq; + struct filter_proc *processor; + struct deliver deliver; + struct child *c; + struct msg m; + const void *data; + const char *username, *password, *cause, *procname; + uint64_t reqid; + size_t sz; + void *i; + int fd, n, v, ret; + + if (imsg == NULL) + fatalx("process %s socket closed", p->name); + + switch (imsg->hdr.type) { + case IMSG_LKA_OPEN_FORWARD: + CHECK_IMSG_DATA_SIZE(imsg, sizeof *fwreq); + fwreq = imsg->data; + fd = parent_forward_open(fwreq->user, fwreq->directory, + fwreq->uid, fwreq->gid); + fwreq->status = 0; + if (fd == -1 && errno != ENOENT) { + if (errno == EAGAIN) + fwreq->status = -1; + } + else + fwreq->status = 1; + m_compose(p, IMSG_LKA_OPEN_FORWARD, 0, 0, fd, + fwreq, sizeof *fwreq); + return; + + case IMSG_LKA_AUTHENTICATE: + /* + * If we reached here, it means we want root to lookup + * system user. + */ + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &username); + m_get_string(&m, &password); + m_end(&m); + + ret = parent_auth_user(username, password); + + m_create(p, IMSG_LKA_AUTHENTICATE, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + m_close(p); + return; + + case IMSG_MDA_FORK: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(deliver)) + fatalx("expected deliver"); + memmove(&deliver, data, sz); + forkmda(p, reqid, &deliver); + return; + + case IMSG_MDA_KILL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &cause); + m_end(&m); + + i = NULL; + while ((n = tree_iter(&children, &i, NULL, (void**)&c))) + if (c->type == CHILD_MDA && + c->mda_id == reqid && + c->cause == NULL) + break; + if (!n) { + log_debug("debug: smtpd: " + "kill request: proc not found"); + return; + } + + c->cause = xstrdup(cause); + log_debug("debug: smtpd: kill requested for %u: %s", + c->pid, c->cause); + kill(c->pid, SIGTERM); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_LKA_PROCESSOR_ERRFD: + m_msg(&m, imsg); + m_get_string(&m, &procname); + m_end(&m); + + processor = dict_xget(env->sc_filter_processes_dict, procname); + m_create(p_lka, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, processor->errfd); + m_add_string(p_lka, procname); + m_close(p_lka); + return; + } + + errx(1, "parent_imsg: unexpected %s imsg from %s", + imsg_to_str(imsg->hdr.type), proc_title(p->proc)); +} + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dFhnv] [-D macro=value] " + "[-f file] [-P system] [-T trace]\n", __progname); + exit(1); +} + +static void +parent_shutdown(void) +{ + pid_t pid; + + mproc_clear(p_ca); + mproc_clear(p_pony); + mproc_clear(p_control); + mproc_clear(p_lka); + mproc_clear(p_scheduler); + mproc_clear(p_queue); + + do { + pid = waitpid(WAIT_MYPGRP, NULL, 0); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + unlink(SMTPD_SOCKET); + + log_info("Exiting"); + exit(0); +} + +static void +parent_send_config(int fd, short event, void *p) +{ + parent_send_config_lka(); + parent_send_config_pony(); + parent_send_config_ca(); + purge_config(PURGE_PKI); +} + +static void +parent_send_config_pony(void) +{ + log_debug("debug: parent_send_config: configuring pony process"); + m_compose(p_pony, IMSG_CONF_START, 0, 0, -1, NULL, 0); + m_compose(p_pony, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +void +parent_send_config_lka() +{ + log_debug("debug: parent_send_config_ruleset: reloading"); + m_compose(p_lka, IMSG_CONF_START, 0, 0, -1, NULL, 0); + m_compose(p_lka, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +static void +parent_send_config_ca(void) +{ + log_debug("debug: parent_send_config: configuring ca process"); + m_compose(p_ca, IMSG_CONF_START, 0, 0, -1, NULL, 0); + m_compose(p_ca, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +static void +parent_sig_handler(int sig, short event, void *p) +{ + struct child *child; + int status, fail; + pid_t pid; + char *cause; + + switch (sig) { + case SIGTERM: + case SIGINT: + log_debug("debug: got signal %d", sig); + parent_shutdown(); + /* NOT REACHED */ + + case SIGCHLD: + do { + int len; + enum mda_resp_status mda_status; + int mda_sysexit; + + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + continue; + + fail = 0; + if (WIFSIGNALED(status)) { + fail = 1; + len = asprintf(&cause, "terminated; signal %d", + WTERMSIG(status)); + mda_status = MDA_TEMPFAIL; + mda_sysexit = 0; + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fail = 1; + len = asprintf(&cause, + "exited abnormally"); + mda_sysexit = WEXITSTATUS(status); + if (mda_sysexit == EX_OSERR || + mda_sysexit == EX_TEMPFAIL) + mda_status = MDA_TEMPFAIL; + else + mda_status = MDA_PERMFAIL; + } else { + len = asprintf(&cause, "exited okay"); + mda_status = MDA_OK; + mda_sysexit = 0; + } + } else + /* WIFSTOPPED or WIFCONTINUED */ + continue; + + if (len == -1) + fatal("asprintf"); + + if (pid == purge_pid) + purge_pid = -1; + + child = tree_pop(&children, pid); + if (child == NULL) + goto skip; + + switch (child->type) { + case CHILD_PROCESSOR: + if (fail) { + log_warnx("warn: lost processor: %s %s", + child->title, cause); + parent_shutdown(); + } + break; + + case CHILD_DAEMON: + if (fail) + log_warnx("warn: lost child: %s %s", + child->title, cause); + break; + + case CHILD_MDA: + if (WIFSIGNALED(status) && + WTERMSIG(status) == SIGALRM) { + char *tmp; + if (asprintf(&tmp, + "terminated; timeout") != -1) { + free(cause); + cause = tmp; + } + } + else if (child->cause && + WIFSIGNALED(status) && + WTERMSIG(status) == SIGTERM) { + free(cause); + cause = child->cause; + child->cause = NULL; + } + free(child->cause); + log_debug("debug: smtpd: mda process done " + "for session %016"PRIx64 ": %s", + child->mda_id, cause); + + m_create(p_pony, IMSG_MDA_DONE, 0, 0, + child->mda_out); + m_add_id(p_pony, child->mda_id); + m_add_int(p_pony, mda_status); + m_add_int(p_pony, mda_sysexit); + m_add_string(p_pony, cause); + m_close(p_pony); + + break; + + case CHILD_ENQUEUE_OFFLINE: + if (fail) + log_warnx("warn: smtpd: " + "couldn't enqueue offline " + "message %s; smtpctl %s", + child->path, cause); + else + unlink(child->path); + free(child->path); + offline_done(); + break; + + default: + fatalx("smtpd: unexpected child type"); + } + free(child); + skip: + free(cause); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + + break; + default: + fatalx("smtpd: unexpected signal"); + } +} + +int +main(int argc, char *argv[]) +{ + int c, i; + int opts, flags; + const char *conffile = CONF_FILE; + int save_argc = argc; + char **save_argv = argv; + char *rexec = NULL; + struct smtpd *conf; + +#ifndef HAVE___PROGNAME + __progname = ssh_get_progname(argv[0]); +#endif + +#ifndef HAVE_SETPROCTITLE + /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ + saved_argc = argc; + saved_argv = xcalloc(argc + 1, sizeof(*saved_argv)); + for (i = 0; i < argc; i++) + saved_argv[i] = xstrdup(argv[i]); + saved_argv[i] = NULL; + + /* Prepare for later setproctitle emulation */ + compat_init_setproctitle(argc, argv); + argv = saved_argv; + + /* this is to work around GNU getopt + portable setproctitle() fuckery */ + save_argc = saved_argc; + save_argv = saved_argv; +#endif + + if ((conf = config_default()) == NULL) + err(1, NULL); + + env = conf; + + flags = 0; + opts = 0; + debug = 0; + tracing = 0; + + log_init(1, LOG_MAIL); + + TAILQ_INIT(&offline_q); + + while ((c = getopt(argc, argv, "B:dD:hnP:f:FT:vx:")) != -1) { + switch (c) { + case 'B': + if (strstr(optarg, "queue=") == optarg) + backend_queue = strchr(optarg, '=') + 1; + else if (strstr(optarg, "scheduler=") == optarg) + backend_scheduler = strchr(optarg, '=') + 1; + else if (strstr(optarg, "stat=") == optarg) + backend_stat = strchr(optarg, '=') + 1; + else + log_warnx("warn: " + "invalid backend specifier %s", + optarg); + break; + case 'd': + foreground = 1; + foreground_log = 1; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("warn: " + "could not parse macro definition %s", + optarg); + break; + case 'h': + log_info("version: " SMTPD_NAME " " SMTPD_VERSION); + usage(); + break; + case 'n': + debug = 2; + opts |= SMTPD_OPT_NOACTION; + break; + case 'f': + conffile = optarg; + break; + case 'F': + foreground = 1; + break; + + case 'T': + if (!strcmp(optarg, "imsg")) + tracing |= TRACE_IMSG; + else if (!strcmp(optarg, "io")) + tracing |= TRACE_IO; + else if (!strcmp(optarg, "smtp")) + tracing |= TRACE_SMTP; + else if (!strcmp(optarg, "filters")) + tracing |= TRACE_FILTERS; + else if (!strcmp(optarg, "mta") || + !strcmp(optarg, "transfer")) + tracing |= TRACE_MTA; + else if (!strcmp(optarg, "bounce") || + !strcmp(optarg, "bounces")) + tracing |= TRACE_BOUNCE; + else if (!strcmp(optarg, "scheduler")) + tracing |= TRACE_SCHEDULER; + else if (!strcmp(optarg, "lookup")) + tracing |= TRACE_LOOKUP; + else if (!strcmp(optarg, "stat") || + !strcmp(optarg, "stats")) + tracing |= TRACE_STAT; + else if (!strcmp(optarg, "rules")) + tracing |= TRACE_RULES; + else if (!strcmp(optarg, "mproc")) + tracing |= TRACE_MPROC; + else if (!strcmp(optarg, "expand")) + tracing |= TRACE_EXPAND; + else if (!strcmp(optarg, "table") || + !strcmp(optarg, "tables")) + tracing |= TRACE_TABLES; + else if (!strcmp(optarg, "queue")) + tracing |= TRACE_QUEUE; + else if (!strcmp(optarg, "all")) + tracing |= ~TRACE_DEBUG; + else if (!strcmp(optarg, "profstat")) + profiling |= PROFILE_TOSTAT; + else if (!strcmp(optarg, "profile-imsg")) + profiling |= PROFILE_IMSG; + else if (!strcmp(optarg, "profile-queue")) + profiling |= PROFILE_QUEUE; + else + log_warnx("warn: unknown trace flag \"%s\"", + optarg); + break; + case 'P': + if (!strcmp(optarg, "smtp")) + flags |= SMTPD_SMTP_PAUSED; + else if (!strcmp(optarg, "mta")) + flags |= SMTPD_MTA_PAUSED; + else if (!strcmp(optarg, "mda")) + flags |= SMTPD_MDA_PAUSED; + break; + case 'v': + tracing |= TRACE_DEBUG; + break; + case 'x': + rexec = optarg; + break; + default: + usage(); + } + } + + argv += optind; + argc -= optind; + + if (argc || *argv) + usage(); + + env->sc_opts |= opts; + + ssl_init(); + + if (parse_config(conf, conffile, opts)) + exit(1); + + if (RAND_status() != 1) + errx(1, "PRNG is not seeded"); + + if (strlcpy(env->sc_conffile, conffile, PATH_MAX) + >= PATH_MAX) + errx(1, "config file exceeds PATH_MAX"); + + if (env->sc_opts & SMTPD_OPT_NOACTION) { + if (env->sc_queue_key && + crypto_setup(env->sc_queue_key, + strlen(env->sc_queue_key)) == 0) { + fatalx("crypto_setup:" + "invalid key for queue encryption"); + } + load_pki_tree(); + load_pki_keys(); + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + env->sc_flags |= flags; + + /* check for root privileges */ + if (geteuid()) + errx(1, "need root privileges"); + + log_init(foreground_log, LOG_MAIL); + log_trace_verbose(tracing); + load_pki_tree(); + load_pki_keys(); + + log_debug("debug: using \"%s\" queue backend", backend_queue); + log_debug("debug: using \"%s\" scheduler backend", backend_scheduler); + log_debug("debug: using \"%s\" stat backend", backend_stat); + + if (env->sc_hostname[0] == '\0') + errx(1, "machine does not have a hostname set"); + env->sc_uptime = time(NULL); + + if (rexec == NULL) { + smtpd_process = PROC_PARENT; + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + if (env->sc_queue_key == NULL) { + char *password; + + password = getpass("queue key: "); + if (password == NULL) + err(1, "getpass"); + + env->sc_queue_key = strdup(password); + explicit_bzero(password, strlen(password)); + if (env->sc_queue_key == NULL) + err(1, "strdup"); + } + else { + char *buf = NULL; + size_t sz = 0; + ssize_t len; + + if (strcasecmp(env->sc_queue_key, "stdin") == 0) { + if ((len = getline(&buf, &sz, stdin)) == -1) + err(1, "getline"); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + env->sc_queue_key = buf; + } + } + } + + log_info("info: %s %s starting", SMTPD_NAME, SMTPD_VERSION); + + if (!foreground) + if (daemon(0, 0) == -1) + err(1, "failed to daemonize"); + + /* setup all processes */ + + p_ca = start_child(save_argc, save_argv, "ca"); + p_ca->proc = PROC_CA; + + p_control = start_child(save_argc, save_argv, "control"); + p_control->proc = PROC_CONTROL; + + p_lka = start_child(save_argc, save_argv, "lka"); + p_lka->proc = PROC_LKA; + + p_pony = start_child(save_argc, save_argv, "pony"); + p_pony->proc = PROC_PONY; + + p_queue = start_child(save_argc, save_argv, "queue"); + p_queue->proc = PROC_QUEUE; + + p_scheduler = start_child(save_argc, save_argv, "scheduler"); + p_scheduler->proc = PROC_SCHEDULER; + + setup_peers(p_control, p_ca); + setup_peers(p_control, p_lka); + setup_peers(p_control, p_pony); + setup_peers(p_control, p_queue); + setup_peers(p_control, p_scheduler); + setup_peers(p_pony, p_ca); + setup_peers(p_pony, p_lka); + setup_peers(p_pony, p_queue); + setup_peers(p_queue, p_lka); + setup_peers(p_queue, p_scheduler); + + if (env->sc_queue_key) { + if (imsg_compose(&p_queue->imsgbuf, IMSG_SETUP_KEY, 0, + 0, -1, env->sc_queue_key, strlen(env->sc_queue_key) + + 1) == -1) + fatal("imsg_compose"); + if (imsg_flush(&p_queue->imsgbuf) == -1) + fatal("imsg_flush"); + } + + setup_done(p_ca); + setup_done(p_control); + setup_done(p_lka); + setup_done(p_pony); + setup_done(p_queue); + setup_done(p_scheduler); + + log_debug("smtpd: setup done"); + + return smtpd(); + } + + if (!strcmp(rexec, "ca")) { + smtpd_process = PROC_CA; + setup_proc(); + + return ca(); + } + + else if (!strcmp(rexec, "control")) { + smtpd_process = PROC_CONTROL; + setup_proc(); + + /* the control socket ensures that only one smtpd instance is running */ + control_socket = control_create_socket(); + + env->sc_stat = stat_backend_lookup(backend_stat); + if (env->sc_stat == NULL) + errx(1, "could not find stat backend \"%s\"", backend_stat); + + return control(); + } + + else if (!strcmp(rexec, "lka")) { + smtpd_process = PROC_LKA; + setup_proc(); + + return lka(); + } + + else if (!strcmp(rexec, "pony")) { + smtpd_process = PROC_PONY; + setup_proc(); + + return pony(); + } + + else if (!strcmp(rexec, "queue")) { + smtpd_process = PROC_QUEUE; + setup_proc(); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) + env->sc_comp = compress_backend_lookup("gzip"); + + if (!queue_init(backend_queue, 1)) + errx(1, "could not initialize queue backend"); + + return queue(); + } + + else if (!strcmp(rexec, "scheduler")) { + smtpd_process = PROC_SCHEDULER; + setup_proc(); + + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (env->sc_bounce_warn[i] == 0) + break; + log_debug("debug: bounce warning after %s", + duration_to_text(env->sc_bounce_warn[i])); + } + + return scheduler(); + } + + fatalx("bad rexec: %s", rexec); + + return (1); +} + +static struct mproc * +start_child(int save_argc, char **save_argv, char *rexec) +{ + struct mproc *p; + char *argv[SMTPD_MAXARG]; + int sp[2], argc = 0; + pid_t pid; + + if (save_argc >= SMTPD_MAXARG - 2) + fatalx("too many arguments"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + fatal("socketpair"); + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + switch (pid = fork()) { + case -1: + fatal("%s: fork", save_argv[0]); + case 0: + break; + default: + close(sp[0]); + p = calloc(1, sizeof(*p)); + if (p == NULL) + fatal("calloc"); + if((p->name = strdup(rexec)) == NULL) + fatal("strdup"); + mproc_init(p, sp[1]); + p->pid = pid; + p->handler = parent_imsg; + return p; + } + + if (sp[0] != 3) { + if (dup2(sp[0], 3) == -1) + fatal("%s: dup2", rexec); + } else if (fcntl(sp[0], F_SETFD, 0) == -1) + fatal("%s: fcntl", rexec); + + xclosefrom(4); + + for (argc = 0; argc < save_argc; argc++) + argv[argc] = save_argv[argc]; + argv[argc++] = "-x"; + argv[argc++] = rexec; + argv[argc++] = NULL; + + execvp(argv[0], argv); + fatal("%s: execvp", rexec); +} + +static void +setup_peers(struct mproc *a, struct mproc *b) +{ + int sp[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + fatal("socketpair"); + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + if (imsg_compose(&a->imsgbuf, IMSG_SETUP_PEER, b->proc, b->pid, sp[0], + NULL, 0) == -1) + fatal("imsg_compose"); + if (imsg_flush(&a->imsgbuf) == -1) + fatal("imsg_flush"); + + if (imsg_compose(&b->imsgbuf, IMSG_SETUP_PEER, a->proc, a->pid, sp[1], + NULL, 0) == -1) + fatal("imsg_compose"); + if (imsg_flush(&b->imsgbuf) == -1) + fatal("imsg_flush"); +} + +static void +setup_done(struct mproc *p) +{ + struct imsg imsg; + + if (imsg_compose(&p->imsgbuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1) + fatal("imsg_compose"); + if (imsg_flush(&p->imsgbuf) == -1) + fatal("imsg_flush"); + + if (imsg_wait(&p->imsgbuf, &imsg, 10000) == -1) + fatal("imsg_wait"); + + if (imsg.hdr.type != IMSG_SETUP_DONE) + fatalx("expect IMSG_SETUP_DONE"); + + log_debug("setup_done: %s[%d] done", p->name, p->pid); + + imsg_free(&imsg); +} + +static void +setup_proc(void) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + int setup = 1; + + log_procinit(proc_title(smtpd_process)); + + p_parent = calloc(1, sizeof(*p_parent)); + if (p_parent == NULL) + fatal("calloc"); + if((p_parent->name = strdup("parent")) == NULL) + fatal("strdup"); + p_parent->proc = PROC_PARENT; + p_parent->handler = imsg_dispatch; + mproc_init(p_parent, 3); + + ibuf = &p_parent->imsgbuf; + + while (setup) { + if (imsg_wait(ibuf, &imsg, 10000) == -1) + fatal("imsg_wait"); + + switch (imsg.hdr.type) { + case IMSG_SETUP_KEY: + env->sc_queue_key = strdup(imsg.data); + break; + case IMSG_SETUP_PEER: + setup_peer(imsg.hdr.peerid, imsg.hdr.pid, imsg.fd); + break; + case IMSG_SETUP_DONE: + setup = 0; + break; + default: + fatal("bad imsg %d", imsg.hdr.type); + } + imsg_free(&imsg); + } + + if (imsg_compose(ibuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1) + fatal("imsg_compose"); + + if (imsg_flush(ibuf) == -1) + fatal("imsg_flush"); + + log_debug("setup_proc: %s done", proc_title(smtpd_process)); +} + +static struct mproc * +setup_peer(enum smtp_proc_type proc, pid_t pid, int sock) +{ + struct mproc *p, **pp; + + log_debug("setup_peer: %s -> %s[%u] fd=%d", proc_title(smtpd_process), + proc_title(proc), pid, sock); + + if (sock == -1) + fatalx("peer socket not received"); + + switch (proc) { + case PROC_LKA: + pp = &p_lka; + break; + case PROC_QUEUE: + pp = &p_queue; + break; + case PROC_CONTROL: + pp = &p_control; + break; + case PROC_SCHEDULER: + pp = &p_scheduler; + break; + case PROC_PONY: + pp = &p_pony; + break; + case PROC_CA: + pp = &p_ca; + break; + default: + fatalx("unknown peer"); + } + + if (*pp) + fatalx("peer already set"); + + p = calloc(1, sizeof(*p)); + if (p == NULL) + fatal("calloc"); + if((p->name = strdup(proc_title(proc))) == NULL) + fatal("strdup"); + mproc_init(p, sock); + p->pid = pid; + p->proc = proc; + p->handler = imsg_dispatch; + + *pp = p; + + return p; +} + +static int +imsg_wait(struct imsgbuf *ibuf, struct imsg *imsg, int timeout) +{ + struct pollfd pfd[1]; + ssize_t n; + + pfd[0].fd = ibuf->fd; + pfd[0].events = POLLIN; + + while (1) { + if ((n = imsg_get(ibuf, imsg)) == -1) + return -1; + if (n) + return 1; + + n = poll(pfd, 1, timeout); + if (n == -1) + return -1; + if (n == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) || n == 0) + return -1; + } +} + +int +smtpd(void) { + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sigchld; + struct event ev_sighup; + struct timeval tv; + + imsg_callback = parent_imsg; + + tree_init(&children); + + child_add(p_queue->pid, CHILD_DAEMON, proc_title(PROC_QUEUE)); + child_add(p_control->pid, CHILD_DAEMON, proc_title(PROC_CONTROL)); + child_add(p_lka->pid, CHILD_DAEMON, proc_title(PROC_LKA)); + child_add(p_scheduler->pid, CHILD_DAEMON, proc_title(PROC_SCHEDULER)); + child_add(p_pony->pid, CHILD_DAEMON, proc_title(PROC_PONY)); + child_add(p_ca->pid, CHILD_DAEMON, proc_title(PROC_CA)); + + event_init(); + + signal_set(&ev_sigint, SIGINT, parent_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, parent_sig_handler, NULL); + signal_set(&ev_sigchld, SIGCHLD, parent_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, parent_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + config_peer(PROC_CONTROL); + config_peer(PROC_LKA); + config_peer(PROC_QUEUE); + config_peer(PROC_CA); + config_peer(PROC_PONY); + + evtimer_set(&config_ev, parent_send_config, NULL); + memset(&tv, 0, sizeof(tv)); + evtimer_add(&config_ev, &tv); + + /* defer offline scanning for a second */ + evtimer_set(&offline_ev, offline_scan, NULL); + offline_timeout.tv_sec = 1; + offline_timeout.tv_usec = 0; + evtimer_add(&offline_ev, &offline_timeout); + + if (pidfile(NULL) < 0) + err(1, "pidfile"); + + fork_filter_processes(); + + purge_task(); + +#if HAVE_PLEDGE + if (pledge("stdio rpath wpath cpath fattr tmppath " + "getpw sendfd proc exec id inet chown unix", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +load_pki_tree(void) +{ + struct pki *pki; + struct ca *sca; + const char *k; + void *iter_dict; + + log_debug("debug: init ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { + log_debug("info: loading pki information for %s", k); + if (pki->pki_cert_file == NULL) + fatalx("load_pki_tree: missing certificate file"); + if (pki->pki_key_file == NULL) + fatalx("load_pki_tree: missing key file"); + + if (!ssl_load_certificate(pki, pki->pki_cert_file)) + fatalx("load_pki_tree: failed to load certificate file"); + } + + log_debug("debug: init ca-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_ca_dict, &iter_dict, &k, (void **)&sca)) { + log_debug("info: loading CA information for %s", k); + if (!ssl_load_cafile(sca, sca->ca_cert_file)) + fatalx("load_pki_tree: failed to load CA file"); + } +} + +void +load_pki_keys(void) +{ + struct pki *pki; + const char *k; + void *iter_dict; + + log_debug("debug: init ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { + log_debug("info: loading pki keys for %s", k); + + if (!ssl_load_keyfile(pki, pki->pki_key_file, k)) + fatalx("load_pki_keys: failed to load key file"); + } +} + +int +fork_proc_backend(const char *key, const char *conf, const char *procname) +{ + pid_t pid; + int sp[2]; + char path[PATH_MAX]; + char name[PATH_MAX]; + char *arg; + + if (strlcpy(name, conf, sizeof(name)) >= sizeof(name)) { + log_warnx("warn: %s-proc: conf too long", key); + return (0); + } + + arg = strchr(name, ':'); + if (arg) + *arg++ = '\0'; + + if (snprintf(path, sizeof(path), PATH_LIBEXEC "/%s-%s", key, name) >= + (ssize_t)sizeof(path)) { + log_warn("warn: %s-proc: exec path too long", key); + return (-1); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) { + log_warn("warn: %s-proc: socketpair", key); + return (-1); + } + + if ((pid = fork()) == -1) { + log_warn("warn: %s-proc: fork", key); + close(sp[0]); + close(sp[1]); + return (-1); + } + + if (pid == 0) { + /* child process */ + dup2(sp[0], STDIN_FILENO); + closefrom(STDERR_FILENO + 1); + + if (procname == NULL) + procname = name; + + execl(path, procname, arg, (char *)NULL); + err(1, "execl: %s", path); + } + + /* parent process */ + close(sp[0]); + + return (sp[1]); +} + +struct child * +child_add(pid_t pid, int type, const char *title) +{ + struct child *child; + + if ((child = calloc(1, sizeof(*child))) == NULL) + fatal("smtpd: child_add: calloc"); + + child->pid = pid; + child->type = type; + child->title = title; + + tree_xset(&children, pid, child); + + return (child); +} + +static void +purge_task(void) +{ + struct passwd *pw; + DIR *d; + int n; + uid_t uid; + gid_t gid; + + n = 0; + if ((d = opendir(PATH_SPOOL PATH_PURGE))) { + while (readdir(d) != NULL) + n++; + closedir(d); + } else + log_warn("warn: purge_task: opendir"); + + if (n > 2) { + switch (purge_pid = fork()) { + case -1: + log_warn("warn: purge_task: fork"); + break; + case 0: + if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL) + fatalx("unknown user " SMTPD_QUEUE_USER); + if (chroot(PATH_SPOOL PATH_PURGE) == -1) + fatal("smtpd: chroot"); + if (chdir("/") == -1) + fatal("smtpd: chdir"); + uid = pw->pw_uid; + gid = pw->pw_gid; + if (setgroups(1, &gid) || + setresgid(gid, gid, gid) || + setresuid(uid, uid, uid)) + fatal("smtpd: cannot drop privileges"); + rmtree("/", 1); + _exit(0); + break; + default: + break; + } + } +} + +static void +fork_filter_processes(void) +{ + const char *name; + void *iter; + const char *fn; + struct filter_config *fc; + struct filter_config *fcs; + struct filter_proc *fp; + size_t i; + + /* For each filter chain, assign the registered subsystem to subfilters */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) { + if (fc->chain) { + for (i = 0; i < fc->chain_size; ++i) { + fcs = dict_xget(env->sc_filters_dict, fc->chain[i]); + fcs->filter_subsystem |= fc->filter_subsystem; + } + } + } + + /* For each filter, assign the registered subsystem to underlying proc */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) { + if (fc->proc) { + fp = dict_xget(env->sc_filter_processes_dict, fc->proc); + fp->filter_subsystem |= fc->filter_subsystem; + } + } + + iter = NULL; + while (dict_iter(env->sc_filter_processes_dict, &iter, &name, (void **)&fp)) + fork_filter_process(name, fp->command, fp->user, fp->group, fp->chroot, fp->filter_subsystem); +} + +static void +fork_filter_process(const char *name, const char *command, const char *user, const char *group, const char *chroot_path, uint32_t subsystems) +{ + pid_t pid; + struct filter_proc *processor; + char buf; + int sp[2], errfd[2]; + struct passwd *pw; + struct group *gr; + char exec[_POSIX_ARG_MAX]; + int execr; + + if (user == NULL) + user = SMTPD_USER; + if ((pw = getpwnam(user)) == NULL) + err(1, "getpwnam"); + + if (group) { + if ((gr = getgrnam(group)) == NULL) + err(1, "getgrnam"); + } + else { + if ((gr = getgrgid(pw->pw_gid)) == NULL) + err(1, "getgrgid"); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + err(1, "socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, errfd) == -1) + err(1, "socketpair"); + + if ((pid = fork()) == -1) + err(1, "fork"); + + /* parent passes the child fd over to lka */ + if (pid > 0) { + processor = dict_xget(env->sc_filter_processes_dict, name); + processor->errfd = errfd[1]; + child_add(pid, CHILD_PROCESSOR, name); + close(sp[0]); + close(errfd[0]); + m_create(p_lka, IMSG_LKA_PROCESSOR_FORK, 0, 0, sp[1]); + m_add_string(p_lka, name); + m_add_u32(p_lka, (uint32_t)subsystems); + m_close(p_lka); + return; + } + + close(sp[1]); + close(errfd[1]); + dup2(sp[0], STDIN_FILENO); + dup2(sp[0], STDOUT_FILENO); + dup2(errfd[0], STDERR_FILENO); + + if (chroot_path) { + if (chroot(chroot_path) != 0 || chdir("/") != 0) + err(1, "chroot: %s", chroot_path); + } + + if (setgroups(1, &gr->gr_gid) || + setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + err(1, "fork_filter_process: cannot drop privileges"); + + xclosefrom(STDERR_FILENO + 1); + + if (setsid() < 0) + err(1, "setsid"); + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || + signal(SIGINT, SIG_DFL) == SIG_ERR || + signal(SIGTERM, SIG_DFL) == SIG_ERR || + signal(SIGCHLD, SIG_DFL) == SIG_ERR || + signal(SIGHUP, SIG_DFL) == SIG_ERR) + err(1, "signal"); + + if (command[0] == '/') + execr = snprintf(exec, sizeof(exec), "exec %s", command); + else + execr = snprintf(exec, sizeof(exec), "exec %s/%s", + PATH_LIBEXEC, command); + if (execr >= (int) sizeof(exec)) + errx(1, "%s: exec path too long", name); + + /* + * Wait for lka to acknowledge that it received the fd. + * This prevents a race condition between the filter sending an error + * message, and exiting and lka not being able to log it because of + * SIGCHLD. + * (Ab)use read to determine if the fd is installed; since stderr is + * never going to be read from we can shutdown(2) the write-end in lka. + */ + if (read(STDERR_FILENO, &buf, 1) != 0) + errx(1, "lka didn't properly close write end of error socket"); + if (system(exec) == -1) + err(1, NULL); + + /* there's no successful exit from a processor */ + _exit(1); +} + +static void +forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) +{ + char ebuf[128], sfn[32]; + struct dispatcher *dsp; + struct child *child; + pid_t pid; + int allout, pipefd[2]; + struct passwd *pw; + const char *pw_name; + uid_t pw_uid; + gid_t pw_gid; + const char *pw_dir; + + dsp = dict_xget(env->sc_dispatchers, deliver->dispatcher); + if (dsp->type != DISPATCHER_LOCAL) + fatalx("non-local dispatcher called from forkmda()"); + + log_debug("debug: smtpd: forking mda for session %016"PRIx64 + ": %s as %s", id, deliver->userinfo.username, + dsp->u.local.user ? dsp->u.local.user : deliver->userinfo.username); + + if (dsp->u.local.user) { + if ((pw = getpwnam(dsp->u.local.user)) == NULL) { + (void)snprintf(ebuf, sizeof ebuf, + "delivery user '%s' does not exist", + dsp->u.local.user); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_PERMFAIL); + m_add_int(p_pony, EX_NOUSER); + m_add_string(p_pony, ebuf); + m_close(p_pony); + return; + } + pw_name = pw->pw_name; + pw_uid = pw->pw_uid; + pw_gid = pw->pw_gid; + pw_dir = pw->pw_dir; + } + else { + pw_name = deliver->userinfo.username; + pw_uid = deliver->userinfo.uid; + pw_gid = deliver->userinfo.gid; + pw_dir = deliver->userinfo.directory; + } + + if (pw_uid == 0 && deliver->mda_exec[0]) { + pw_name = deliver->userinfo.username; + pw_uid = deliver->userinfo.uid; + pw_gid = deliver->userinfo.gid; + pw_dir = deliver->userinfo.directory; + } + + if (pw_uid == 0 && !dsp->u.local.is_mbox) { + (void)snprintf(ebuf, sizeof ebuf, "not allowed to deliver to: %s", + deliver->userinfo.username); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_PERMFAIL); + m_add_int(p_pony, EX_NOPERM); + m_add_string(p_pony, ebuf); + m_close(p_pony); + return; + } + + if (pipe(pipefd) == -1) { + (void)snprintf(ebuf, sizeof ebuf, "pipe: %s", strerror(errno)); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_TEMPFAIL); + m_add_int(p_pony, EX_OSERR); + m_add_string(p_pony, ebuf); + m_close(p_pony); + return; + } + + /* prepare file which captures stdout and stderr */ + (void)strlcpy(sfn, "/tmp/smtpd.out.XXXXXXXXXXX", sizeof(sfn)); + allout = mkstemp(sfn); + if (allout == -1) { + (void)snprintf(ebuf, sizeof ebuf, "mkstemp: %s", strerror(errno)); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_TEMPFAIL); + m_add_int(p_pony, EX_OSERR); + m_add_string(p_pony, ebuf); + m_close(p_pony); + close(pipefd[0]); + close(pipefd[1]); + return; + } + unlink(sfn); + + pid = fork(); + if (pid == -1) { + (void)snprintf(ebuf, sizeof ebuf, "fork: %s", strerror(errno)); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_TEMPFAIL); + m_add_int(p_pony, EX_OSERR); + m_add_string(p_pony, ebuf); + m_close(p_pony); + close(pipefd[0]); + close(pipefd[1]); + close(allout); + return; + } + + /* parent passes the child fd over to mda */ + if (pid > 0) { + child = child_add(pid, CHILD_MDA, NULL); + child->mda_out = allout; + child->mda_id = id; + close(pipefd[0]); + m_create(p, IMSG_MDA_FORK, 0, 0, pipefd[1]); + m_add_id(p, id); + m_close(p); + return; + } + + /* mbox helper, create mailbox before privdrop if it doesn't exist */ + if (dsp->u.local.is_mbox) + mda_mbox_init(deliver); + + if (chdir(pw_dir) == -1 && chdir("/") == -1) + err(1, "chdir"); + if (setgroups(1, &pw_gid) || + setresgid(pw_gid, pw_gid, pw_gid) || + setresuid(pw_uid, pw_uid, pw_uid)) + err(1, "forkmda: cannot drop privileges"); + if (dup2(pipefd[0], STDIN_FILENO) == -1 || + dup2(allout, STDOUT_FILENO) == -1 || + dup2(allout, STDERR_FILENO) == -1) + err(1, "forkmda: dup2"); + closefrom(STDERR_FILENO + 1); + if (setsid() < 0) + err(1, "setsid"); + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || + signal(SIGINT, SIG_DFL) == SIG_ERR || + signal(SIGTERM, SIG_DFL) == SIG_ERR || + signal(SIGCHLD, SIG_DFL) == SIG_ERR || + signal(SIGHUP, SIG_DFL) == SIG_ERR) + err(1, "signal"); + + /* avoid hangs by setting 5m timeout */ + alarm(300); + + if (dsp->u.local.is_mbox && + dsp->u.local.mda_wrapper == NULL && + deliver->mda_exec[0] == '\0') + mda_mbox(deliver); + else + mda_unpriv(dsp, deliver, pw_name, pw_dir); +} + +static void +offline_scan(int fd, short ev, void *arg) +{ + char *path_argv[2]; + FTS *fts = arg; + FTSENT *e; + int n = 0; + + path_argv[0] = PATH_SPOOL PATH_OFFLINE; + path_argv[1] = NULL; + + if (fts == NULL) { + log_debug("debug: smtpd: scanning offline queue..."); + fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); + if (fts == NULL) { + log_warn("fts_open: %s", path_argv[0]); + return; + } + } + + while ((e = fts_read(fts)) != NULL) { + if (e->fts_info != FTS_F) + continue; + + /* offline files must be at depth 1 */ + if (e->fts_level != 1) + continue; + + /* offline file group must match parent directory group */ + if (e->fts_statp->st_gid != e->fts_parent->fts_statp->st_gid) + continue; + + if (e->fts_statp->st_size == 0) { + if (unlink(e->fts_accpath) == -1) + log_warnx("warn: smtpd: could not unlink %s", e->fts_accpath); + continue; + } + + if (offline_add(e->fts_name, e->fts_statp->st_uid, + e->fts_statp->st_gid)) { + log_warnx("warn: smtpd: " + "could not add offline message %s", e->fts_name); + continue; + } + + if ((n++) == OFFLINE_READMAX) { + evtimer_set(&offline_ev, offline_scan, fts); + offline_timeout.tv_sec = 0; + offline_timeout.tv_usec = 100000; + evtimer_add(&offline_ev, &offline_timeout); + return; + } + } + + log_debug("debug: smtpd: offline scanning done"); + fts_close(fts); +} + +static int +offline_enqueue(char *name, uid_t uid, gid_t gid) +{ + char *path; + struct stat sb; + pid_t pid; + struct child *child; + struct passwd *pw; + int pathlen; + + pathlen = asprintf(&path, "%s/%s", PATH_SPOOL PATH_OFFLINE, name); + if (pathlen == -1) { + log_warnx("warn: smtpd: asprintf"); + return (-1); + } + + if (pathlen >= PATH_MAX) { + log_warnx("warn: smtpd: pathname exceeds PATH_MAX"); + free(path); + return (-1); + } + + log_debug("debug: smtpd: enqueueing offline message %s", path); + + if ((pid = fork()) == -1) { + log_warn("warn: smtpd: fork"); + free(path); + return (-1); + } + + if (pid == 0) { + char *envp[2], *p = NULL, *tmp; + int fd; + FILE *fp; + size_t sz = 0; + ssize_t len; + arglist args; + + closefrom(STDERR_FILENO + 1); + + memset(&args, 0, sizeof(args)); + + if ((fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) == -1) { + log_warn("warn: smtpd: open: %s", path); + _exit(1); + } + + if (fstat(fd, &sb) == -1) { + log_warn("warn: smtpd: fstat: %s", path); + _exit(1); + } + + if (!S_ISREG(sb.st_mode)) { + log_warnx("warn: smtpd: file %s (uid %d) not regular", + path, sb.st_uid); + _exit(1); + } + + if (sb.st_nlink != 1) { + log_warnx("warn: smtpd: file %s is hard-link", path); + _exit(1); + } + + if (sb.st_uid != uid) { + log_warnx("warn: smtpd: file %s has bad uid %d", + path, sb.st_uid); + _exit(1); + } + + if (sb.st_gid != gid) { + log_warnx("warn: smtpd: file %s has bad gid %d", + path, sb.st_gid); + _exit(1); + } + + pw = getpwuid(sb.st_uid); + if (pw == NULL) { + log_warnx("warn: smtpd: getpwuid for uid %d failed", + sb.st_uid); + _exit(1); + } + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + _exit(1); + + if ((fp = fdopen(fd, "r")) == NULL) + _exit(1); + + if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) + _exit(1); + + if (setsid() == -1 || + signal(SIGPIPE, SIG_DFL) == SIG_ERR || + dup2(fileno(fp), STDIN_FILENO) == -1) + _exit(1); + + if ((len = getline(&p, &sz, fp)) == -1) + _exit(1); + + if (p[len - 1] != '\n') + _exit(1); + p[len - 1] = '\0'; + + addargs(&args, "%s", "sendmail"); + addargs(&args, "%s", "-S"); + + while ((tmp = strsep(&p, "|")) != NULL) + addargs(&args, "%s", tmp); + + free(p); + if (lseek(fileno(fp), len, SEEK_SET) == -1) + _exit(1); + + envp[0] = "PATH=" _PATH_DEFPATH; + envp[1] = (char *)NULL; + environ = envp; + + execvp(PATH_SMTPCTL, args.list); + _exit(1); + } + + offline_running++; + child = child_add(pid, CHILD_ENQUEUE_OFFLINE, NULL); + child->path = path; + + return (0); +} + +static int +offline_add(char *path, uid_t uid, gid_t gid) +{ + struct offline *q; + + if (offline_running < OFFLINE_QUEUEMAX) + /* skip queue */ + return offline_enqueue(path, uid, gid); + + q = malloc(sizeof(*q) + strlen(path) + 1); + if (q == NULL) + return (-1); + q->uid = uid; + q->gid = gid; + q->path = (char *)q + sizeof(*q); + memmove(q->path, path, strlen(path) + 1); + TAILQ_INSERT_TAIL(&offline_q, q, entry); + + return (0); +} + +static void +offline_done(void) +{ + struct offline *q; + + offline_running--; + + while (offline_running < OFFLINE_QUEUEMAX) { + if ((q = TAILQ_FIRST(&offline_q)) == NULL) + break; /* all done */ + TAILQ_REMOVE(&offline_q, q, entry); + offline_enqueue(q->path, q->uid, q->gid); + free(q); + } +} + +static int +parent_forward_open(char *username, char *directory, uid_t uid, gid_t gid) +{ + char pathname[PATH_MAX]; + int fd; + struct stat sb; + + if (!bsnprintf(pathname, sizeof (pathname), "%s/.forward", + directory)) { + log_warnx("warn: smtpd: %s: pathname too large", pathname); + return -1; + } + + if (stat(directory, &sb) == -1) { + log_warn("warn: smtpd: parent_forward_open: %s", directory); + return -1; + } + if (sb.st_mode & S_ISVTX) { + log_warnx("warn: smtpd: parent_forward_open: %s is sticky", + directory); + errno = EAGAIN; + return -1; + } + + do { + fd = open(pathname, O_RDONLY|O_NOFOLLOW|O_NONBLOCK); + } while (fd == -1 && errno == EINTR); + if (fd == -1) { + if (errno == ENOENT) + return -1; + if (errno == EMFILE || errno == ENFILE || errno == EIO) { + errno = EAGAIN; + return -1; + } + if (errno == ELOOP) + log_warnx("warn: smtpd: parent_forward_open: %s: " + "cannot follow symbolic links", pathname); + else + log_warn("warn: smtpd: parent_forward_open: %s", pathname); + return -1; + } + + if (!secure_file(fd, pathname, directory, uid, 1)) { + log_warnx("warn: smtpd: %s: unsecure file", pathname); + close(fd); + return -1; + } + + return fd; +} + +void +imsg_dispatch(struct mproc *p, struct imsg *imsg) +{ + struct timespec t0, t1, dt; + int msg; + + if (imsg == NULL) { + imsg_callback(p, imsg); + return; + } + + log_imsg(smtpd_process, p->proc, imsg); + + if (profiling & PROFILE_IMSG) + clock_gettime(CLOCK_MONOTONIC, &t0); + + msg = imsg->hdr.type; + imsg_callback(p, imsg); + + if (profiling & PROFILE_IMSG) { + clock_gettime(CLOCK_MONOTONIC, &t1); + timespecsub(&t1, &t0, &dt); + + log_debug("profile-imsg: %s %s %s %d %lld.%09ld", + proc_name(smtpd_process), + proc_name(p->proc), + imsg_to_str(msg), + (int)imsg->hdr.len, + (long long)dt.tv_sec, + dt.tv_nsec); + + if (profiling & PROFILE_TOSTAT) { + char key[STAT_KEY_SIZE]; + /* can't profstat control process yet */ + if (smtpd_process == PROC_CONTROL) + return; + + if (!bsnprintf(key, sizeof key, + "profiling.imsg.%s.%s.%s", + proc_name(smtpd_process), + proc_name(p->proc), + imsg_to_str(msg))) + return; + stat_set(key, stat_timespec(&dt)); + } + } +} + +void +log_imsg(int to, int from, struct imsg *imsg) +{ + + if (to == PROC_CONTROL && imsg->hdr.type == IMSG_STAT_SET) + return; + + if (imsg->fd != -1) + log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu, fd=%d)", + proc_name(to), + proc_name(from), + imsg_to_str(imsg->hdr.type), + imsg->hdr.len - IMSG_HEADER_SIZE, + imsg->fd); + else + log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu)", + proc_name(to), + proc_name(from), + imsg_to_str(imsg->hdr.type), + imsg->hdr.len - IMSG_HEADER_SIZE); +} + +const char * +proc_title(enum smtp_proc_type proc) +{ + switch (proc) { + case PROC_PARENT: + return "[priv]"; + case PROC_LKA: + return "lookup"; + case PROC_QUEUE: + return "queue"; + case PROC_CONTROL: + return "control"; + case PROC_SCHEDULER: + return "scheduler"; + case PROC_PONY: + return "pony express"; + case PROC_CA: + return "klondike"; + case PROC_CLIENT: + return "client"; + case PROC_PROCESSOR: + return "processor"; + } + return "unknown"; +} + +const char * +proc_name(enum smtp_proc_type proc) +{ + switch (proc) { + case PROC_PARENT: + return "parent"; + case PROC_LKA: + return "lka"; + case PROC_QUEUE: + return "queue"; + case PROC_CONTROL: + return "control"; + case PROC_SCHEDULER: + return "scheduler"; + case PROC_PONY: + return "pony"; + case PROC_CA: + return "ca"; + case PROC_CLIENT: + return "client-proc"; + default: + return "unknown"; + } +} + +#define CASE(x) case x : return #x + +const char * +imsg_to_str(int type) +{ + static char buf[32]; + + switch (type) { + CASE(IMSG_NONE); + + CASE(IMSG_CTL_OK); + CASE(IMSG_CTL_FAIL); + + CASE(IMSG_CTL_GET_DIGEST); + CASE(IMSG_CTL_GET_STATS); + CASE(IMSG_CTL_LIST_MESSAGES); + CASE(IMSG_CTL_LIST_ENVELOPES); + CASE(IMSG_CTL_MTA_SHOW_HOSTS); + CASE(IMSG_CTL_MTA_SHOW_RELAYS); + CASE(IMSG_CTL_MTA_SHOW_ROUTES); + CASE(IMSG_CTL_MTA_SHOW_HOSTSTATS); + CASE(IMSG_CTL_MTA_BLOCK); + CASE(IMSG_CTL_MTA_UNBLOCK); + CASE(IMSG_CTL_MTA_SHOW_BLOCK); + CASE(IMSG_CTL_PAUSE_EVP); + CASE(IMSG_CTL_PAUSE_MDA); + CASE(IMSG_CTL_PAUSE_MTA); + CASE(IMSG_CTL_PAUSE_SMTP); + CASE(IMSG_CTL_PROFILE); + CASE(IMSG_CTL_PROFILE_DISABLE); + CASE(IMSG_CTL_PROFILE_ENABLE); + CASE(IMSG_CTL_RESUME_EVP); + CASE(IMSG_CTL_RESUME_MDA); + CASE(IMSG_CTL_RESUME_MTA); + CASE(IMSG_CTL_RESUME_SMTP); + CASE(IMSG_CTL_RESUME_ROUTE); + CASE(IMSG_CTL_REMOVE); + CASE(IMSG_CTL_SCHEDULE); + CASE(IMSG_CTL_SHOW_STATUS); + CASE(IMSG_CTL_TRACE_DISABLE); + CASE(IMSG_CTL_TRACE_ENABLE); + CASE(IMSG_CTL_UPDATE_TABLE); + CASE(IMSG_CTL_VERBOSE); + CASE(IMSG_CTL_DISCOVER_EVPID); + CASE(IMSG_CTL_DISCOVER_MSGID); + + CASE(IMSG_CTL_SMTP_SESSION); + + CASE(IMSG_GETADDRINFO); + CASE(IMSG_GETADDRINFO_END); + CASE(IMSG_GETNAMEINFO); + CASE(IMSG_RES_QUERY); + + CASE(IMSG_CERT_INIT); + CASE(IMSG_CERT_CERTIFICATE); + CASE(IMSG_CERT_VERIFY); + + CASE(IMSG_SETUP_KEY); + CASE(IMSG_SETUP_PEER); + CASE(IMSG_SETUP_DONE); + + CASE(IMSG_CONF_START); + CASE(IMSG_CONF_END); + + CASE(IMSG_STAT_INCREMENT); + CASE(IMSG_STAT_DECREMENT); + CASE(IMSG_STAT_SET); + + CASE(IMSG_LKA_AUTHENTICATE); + CASE(IMSG_LKA_OPEN_FORWARD); + CASE(IMSG_LKA_ENVELOPE_SUBMIT); + CASE(IMSG_LKA_ENVELOPE_COMMIT); + + CASE(IMSG_QUEUE_DELIVER); + CASE(IMSG_QUEUE_DELIVERY_OK); + CASE(IMSG_QUEUE_DELIVERY_TEMPFAIL); + CASE(IMSG_QUEUE_DELIVERY_PERMFAIL); + CASE(IMSG_QUEUE_DELIVERY_LOOP); + CASE(IMSG_QUEUE_DISCOVER_EVPID); + CASE(IMSG_QUEUE_DISCOVER_MSGID); + CASE(IMSG_QUEUE_ENVELOPE_ACK); + CASE(IMSG_QUEUE_ENVELOPE_COMMIT); + CASE(IMSG_QUEUE_ENVELOPE_REMOVE); + CASE(IMSG_QUEUE_ENVELOPE_SCHEDULE); + CASE(IMSG_QUEUE_ENVELOPE_SUBMIT); + CASE(IMSG_QUEUE_HOLDQ_HOLD); + CASE(IMSG_QUEUE_HOLDQ_RELEASE); + CASE(IMSG_QUEUE_MESSAGE_COMMIT); + CASE(IMSG_QUEUE_MESSAGE_ROLLBACK); + CASE(IMSG_QUEUE_SMTP_SESSION); + CASE(IMSG_QUEUE_TRANSFER); + + CASE(IMSG_MDA_DELIVERY_OK); + CASE(IMSG_MDA_DELIVERY_TEMPFAIL); + CASE(IMSG_MDA_DELIVERY_PERMFAIL); + CASE(IMSG_MDA_DELIVERY_LOOP); + CASE(IMSG_MDA_DELIVERY_HOLD); + CASE(IMSG_MDA_DONE); + CASE(IMSG_MDA_FORK); + CASE(IMSG_MDA_HOLDQ_RELEASE); + CASE(IMSG_MDA_LOOKUP_USERINFO); + CASE(IMSG_MDA_KILL); + CASE(IMSG_MDA_OPEN_MESSAGE); + + CASE(IMSG_MTA_DELIVERY_OK); + CASE(IMSG_MTA_DELIVERY_TEMPFAIL); + CASE(IMSG_MTA_DELIVERY_PERMFAIL); + CASE(IMSG_MTA_DELIVERY_LOOP); + CASE(IMSG_MTA_DELIVERY_HOLD); + CASE(IMSG_MTA_DNS_HOST); + CASE(IMSG_MTA_DNS_HOST_END); + CASE(IMSG_MTA_DNS_MX); + CASE(IMSG_MTA_DNS_MX_PREFERENCE); + CASE(IMSG_MTA_HOLDQ_RELEASE); + CASE(IMSG_MTA_LOOKUP_CREDENTIALS); + CASE(IMSG_MTA_LOOKUP_SOURCE); + CASE(IMSG_MTA_LOOKUP_HELO); + CASE(IMSG_MTA_LOOKUP_SMARTHOST); + CASE(IMSG_MTA_OPEN_MESSAGE); + CASE(IMSG_MTA_SCHEDULE); + + CASE(IMSG_SCHED_ENVELOPE_BOUNCE); + CASE(IMSG_SCHED_ENVELOPE_DELIVER); + CASE(IMSG_SCHED_ENVELOPE_EXPIRE); + CASE(IMSG_SCHED_ENVELOPE_INJECT); + CASE(IMSG_SCHED_ENVELOPE_REMOVE); + CASE(IMSG_SCHED_ENVELOPE_TRANSFER); + + CASE(IMSG_SMTP_AUTHENTICATE); + CASE(IMSG_SMTP_MESSAGE_COMMIT); + CASE(IMSG_SMTP_MESSAGE_CREATE); + CASE(IMSG_SMTP_MESSAGE_ROLLBACK); + CASE(IMSG_SMTP_MESSAGE_OPEN); + CASE(IMSG_SMTP_CHECK_SENDER); + CASE(IMSG_SMTP_EXPAND_RCPT); + CASE(IMSG_SMTP_LOOKUP_HELO); + + CASE(IMSG_SMTP_REQ_CONNECT); + CASE(IMSG_SMTP_REQ_HELO); + CASE(IMSG_SMTP_REQ_MAIL); + CASE(IMSG_SMTP_REQ_RCPT); + CASE(IMSG_SMTP_REQ_DATA); + CASE(IMSG_SMTP_REQ_EOM); + CASE(IMSG_SMTP_EVENT_RSET); + CASE(IMSG_SMTP_EVENT_COMMIT); + CASE(IMSG_SMTP_EVENT_ROLLBACK); + CASE(IMSG_SMTP_EVENT_DISCONNECT); + + CASE(IMSG_LKA_PROCESSOR_FORK); + CASE(IMSG_LKA_PROCESSOR_ERRFD); + + CASE(IMSG_REPORT_SMTP_LINK_CONNECT); + CASE(IMSG_REPORT_SMTP_LINK_DISCONNECT); + CASE(IMSG_REPORT_SMTP_LINK_TLS); + CASE(IMSG_REPORT_SMTP_LINK_GREETING); + CASE(IMSG_REPORT_SMTP_LINK_IDENTIFY); + CASE(IMSG_REPORT_SMTP_LINK_AUTH); + + CASE(IMSG_REPORT_SMTP_TX_RESET); + CASE(IMSG_REPORT_SMTP_TX_BEGIN); + CASE(IMSG_REPORT_SMTP_TX_ENVELOPE); + CASE(IMSG_REPORT_SMTP_TX_COMMIT); + CASE(IMSG_REPORT_SMTP_TX_ROLLBACK); + + CASE(IMSG_REPORT_SMTP_PROTOCOL_CLIENT); + CASE(IMSG_REPORT_SMTP_PROTOCOL_SERVER); + + CASE(IMSG_FILTER_SMTP_BEGIN); + CASE(IMSG_FILTER_SMTP_END); + CASE(IMSG_FILTER_SMTP_PROTOCOL); + CASE(IMSG_FILTER_SMTP_DATA_BEGIN); + CASE(IMSG_FILTER_SMTP_DATA_END); + + CASE(IMSG_CA_RSA_PRIVENC); + CASE(IMSG_CA_RSA_PRIVDEC); + CASE(IMSG_CA_ECDSA_SIGN); + default: + (void)snprintf(buf, sizeof(buf), "IMSG_??? (%d)", type); + + return buf; + } +} + +#ifdef BSD_AUTH +int +parent_auth_bsd(const char *username, const char *password) +{ + char user[LOGIN_NAME_MAX]; + char pass[LINE_MAX]; + int ret; + + (void)strlcpy(user, username, sizeof(user)); + (void)strlcpy(pass, password, sizeof(pass)); + + ret = auth_userokay(user, NULL, "auth-smtp", pass); + if (ret) + return LKA_OK; + return LKA_PERMFAIL; +} +#endif + +#ifdef USE_PAM +int +pam_conv_password(int num_msg, const struct pam_message **msg, + struct pam_response **respp, void *password) +{ + struct pam_response *response; + + if (num_msg != 1) + return PAM_CONV_ERR; + + response = calloc(1, sizeof(struct pam_response)); + if (response == NULL || (response->resp = strdup(password)) == NULL) { + free(response); + return PAM_BUF_ERR; + } + + *respp = response; + return PAM_SUCCESS; +} +int +parent_auth_pam(const char *username, const char *password) +{ + int rc; + pam_handle_t *pamh = NULL; + struct pam_conv conv = { pam_conv_password, (char *)password }; + + if ((rc = pam_start(USE_PAM_SERVICE, username, &conv, &pamh)) != PAM_SUCCESS) + goto end; + if ((rc = pam_authenticate(pamh, 0)) != PAM_SUCCESS) + goto end; + if ((rc = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) + goto end; + +end: + pam_end(pamh, rc); + + switch (rc) { + case PAM_SUCCESS: + return LKA_OK; + case PAM_SYSTEM_ERR: + case PAM_ABORT: + case PAM_AUTHINFO_UNAVAIL: + return LKA_TEMPFAIL; + default: + return LKA_PERMFAIL; + } +} +#endif + +#ifdef HAVE_GETSPNAM +int +parent_auth_getspnam(const char *username, const char *password) +{ + struct spwd *pw; + char *ep; + + errno = 0; + do { + pw = getspnam(username); + } while (pw == NULL && errno == EINTR); + + if (pw == NULL) { + if (errno) + return LKA_TEMPFAIL; + return LKA_PERMFAIL; + } + + if ((ep = crypt(password, pw->sp_pwdp)) == NULL) + return LKA_PERMFAIL; + + if (strcmp(pw->sp_pwdp, ep) == 0) + return LKA_OK; + + return LKA_PERMFAIL; +} +#endif + +int +parent_auth_pwd(const char *username, const char *password) +{ + struct passwd *pw; + char *ep; + + errno = 0; + do { + pw = getpwnam(username); + } while (pw == NULL && errno == EINTR); + + if (pw == NULL) { + if (errno) + return LKA_TEMPFAIL; + return LKA_PERMFAIL; + } + + if ((ep = crypt(password, pw->pw_passwd)) == NULL) + return LKA_PERMFAIL; + + if (strcmp(pw->pw_passwd, ep) == 0) + return LKA_OK; + + return LKA_PERMFAIL; +} + +int +parent_auth_user(const char *username, const char *password) +{ +#if defined(BSD_AUTH) + return (parent_auth_bsd(username, password)); +#elif defined(USE_PAM) + return (parent_auth_pam(username, password)); +#elif defined(HAVE_GETSPNAM) + return (parent_auth_getspnam(username, password)); +#else + return (parent_auth_pwd(username, password)); +#endif +} diff --git a/usr.sbin/smtpd/smtpd.conf b/usr.sbin/smtpd/smtpd.conf new file mode 100644 index 00000000..a7ba6c64 --- /dev/null +++ b/usr.sbin/smtpd/smtpd.conf @@ -0,0 +1,19 @@ +# $OpenBSD: smtpd.conf,v 1.10 2018/05/24 11:40:17 gilles Exp $ + +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. + +table aliases file:/etc/mail/aliases + +# To accept external mail, replace with: listen on all +# +listen on localhost + +action "local" maildir alias +action "relay" relay + +# Uncomment the following to accept external mail for domain "example.org" +# +# match from any for domain "example.org" action "local" +match for local action "local" +match from local for any action "relay" diff --git a/usr.sbin/smtpd/smtpd.conf.5 b/usr.sbin/smtpd/smtpd.conf.5 new file mode 100644 index 00000000..c543c662 --- /dev/null +++ b/usr.sbin/smtpd/smtpd.conf.5 @@ -0,0 +1,1240 @@ +.\" $OpenBSD: smtpd.conf.5,v 1.250 2020/04/25 09:20:38 eric Exp $ +.\" +.\" Copyright (c) 2008 Janne Johansson +.\" Copyright (c) 2009 Jacek Masiulaniec +.\" Copyright (c) 2012 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.Dd $Mdocdate: April 25 2020 $ +.Dt SMTPD.CONF 5 +.Os +.Sh NAME +.Nm smtpd.conf +.Nd Simple Mail Transfer Protocol daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the mail daemon +.Xr smtpd 8 . +.Pp +When mail arrives, +each +.Dq RCPT TO: +command generates a mail envelope. +If an envelope matches +any of a pre-designated set of criteria +(using the +.Ic match +directive), +the message is accepted for delivery. +A copy of the message, as well as its associated envelopes, +is saved in the mail queue and later dispatched +according to an associated set of actions +(using the +.Ic action +directive). +If an envelope does not match any options, +it is rejected. +The match rules are evaluated sequentially, +with the first match winning. +.Pp +The format of the configuration file is fairly flexible. +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +Argument names not beginning with a letter, digit, or underscore, +as well as reserved words +(such as +.Ic listen , +.Ic match , +and +.Cm port ) , +must be quoted. +Arguments containing whitespace should be surrounded by double quotes +.Pq \&" . +.Pp +Macros can be defined that are later expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters, +but may not be reserved words. +Macros are not expanded inside quotes. +For example: +.Bd -literal -offset indent +lan_addr = "192.168.0.1" +listen on $lan_addr +listen on $lan_addr tls auth +.Ed +.Pp +The syntax of +.Nm +is described below. +.Bl -tag -width Ds +.It Ic action Ar name method Op Ar options +When the queue runner processes an envelope from the mail queue, +it carries out the +.Ic action +.Ar name , +selected by the +.Ic match No ... Cm action +directive when the message was received. +The +.Ic action +directive provides configuration data for delivery attempts. +Required lookups are performed at the time of each delivery attempt. +Consequently, changing an +.Ic action +directive or the files it references and restarting the +.Xr smtpd 8 +daemon causes the changes to take effect for subsequent delivery +attempts for the respective dispatcher +.Ar name , +even for messages that were already stuck in the queue +prior to the configuration changes. +.Pp +The delivery +.Ar method +parameter may be one of the following: +.Bl -tag -width Ds +.It Cm expand-only +Only accept the message if a delivery method was specified +in an aliases or +.Pa .forward +file. +.It Cm forward-only +Only accept the message if the recipient results in a remote address +after the processing of aliases or forward file. +.It Cm lmtp Ar destination Op Ar rcpt-to +Deliver the message to an LMTP server at +.Ar destination . +The location may be expressed as host:port or as a UNIX socket. +.Pp +Optionally, +.Ar rcpt-to +might be specified to use the +recipient email address (after expansion) instead of the +local user in the LMTP session as RCPT TO. +.It Cm maildir Op Ar pathname Op Cm junk +Deliver the message to the maildir in +.Ar pathname +if specified, or by default to +.Pa ~/Maildir . +.Pp +The +.Ar pathname +may contain format specifiers that are expanded before use +.Pq see Sx FORMAT SPECIFIERS . +.Pp +If the +.Cm junk +argument is provided, the message will be moved to the +.Ql Junk +folder if it contains a positive +.Ql X-Spam +header. +This folder will be created under +.Ar pathname +if it does not yet exist. +.It Cm mbox +Deliver the message to the user's mbox with +.Xr mail.local 8 . +.It Cm mda Ar command +Delegate the delivery to a +.Ar command +that receives the message on its standard input. +.Pp +The +.Ar command +may contain format specifiers that are expanded before use +.Pq see Sx FORMAT SPECIFIERS . +.It Cm relay +Relay the message to another SMTP server. +.El +.Pp +The local delivery methods support additional options: +.Bl -tag -width Ds +.It Cm alias Pf < Ar table Ns > +Use the mapping +.Ar table +for +.Xr aliases 5 +expansion. +.It Xo +.Cm ttl +.Sm off +.Ar n +.Brq Cm s | m | h | d +.Sm on +.Xc +Specify how long a message may remain in the queue. +.It Cm user Ar username +Specify the +.Ar username +for performing the delivery, to be looked up with +.Xr getpwnam 3 . +.Pp +This is used for virtual hosting where a single username +is in charge of handling delivery for all virtual users. +.Pp +This option is not usable with the +.Cm mbox +delivery method. +.It Cm userbase Pf < Ar table Ns > +Use the mapping +.Ar table +for user lookups instead of the +.Xr getpwnam 3 +function. +.Pp +The +.Cm userbase +does not apply for the +.Cm user +option. +.It Cm virtual Pf < Ar table Ns > +Use the mapping +.Ar table +for virtual expansion. +The aliasing table format is described in +.Xr table 5 . +.It Cm wrapper Ar name +Use the wrapper specified in +.Cm mda wrapper . +.El +.Pp +The relay delivery methods also support additional options: +.Bl -tag -width Ds +.It Cm backup +Operate as a backup mail exchanger delivering messages to any mail exchanger +with higher priority. +.It Cm backup mx Ar name +Operate as a backup mail exchanger delivering messages to any mail exchanger +with higher priority than mail exchanger identified as +.Ar name . +.It Cm helo Ar heloname +Advertise +.Ar heloname +as the hostname to other mail exchangers during the HELO phase. +.It Cm helo-src Pf < Ar table Ns > +Use the mapping +.Ar table +to look up a hostname matching the source address, +to advertise during the HELO phase. +.It Cm domain Pf < Ar domains Ns > +Do not perform MX lookups but look up destination domain in +.Ar domains +and use matching relay url as relay host. +.It Cm host Ar relay-url +Do not perform MX lookups but relay messages to the relay host described by +.Ar relay-url . +The format for +.Ar relay-url +is +.Sm off +.Op Ar proto No :// Op Ar label No @ +.Ar host Op : Ar port . +.Sm on +The following protocols are available: +.Pp +.Bl -tag -width "smtp+notls" -compact +.It smtp +Normal SMTP session with opportunistic STARTTLS +(the default). +.It smtp+tls +Normal SMTP session with mandatory STARTTLS. +.It smtp+notls +Plain text SMTP session without TLS. +.It lmtp +LMTP session. +.Ar port +is required. +.It smtps +SMTP session with forced TLS on connection, default port is 465. +.El +Unless noted, +.Ar port +defaults to 25. +.Pp +The +.Ar label +corresponds to an entry in a credentials table, +as documented in +.Xr table 5 . +It is used with the +.Dq smtp+tls +and +.Dq smtps +protocols for authentication. +Server certificates for those protocols are verified by default. +.It Cm srs +When relaying a mail resulting from a forward, +use the Sender Rewriting Scheme to rewrite sender address. +.It Cm tls Op Cm no-verify +Require TLS to be used when relaying, using mandatory STARTTLS by default. +When used with a smarthost, the protocol must not be +.Dq smtp+notls:// . +If +.Cm no-verify +is specified, do not require a valid certificate. +.It Cm auth Pf < Ar table Ns > +Use the mapping +.Ar table +for connecting to +.Ar relay-url +using credentials. +This option is usable only with +.Cm host +option. +The credential table format is described in +.Xr table 5 . +.It Cm mail-from Ar mailaddr +Use +.Ar mailaddr +as the MAIL FROM address within the SMTP transaction. +.It Cm src Ar sourceaddr | Pf < Ar sourceaddr Ns > +Use the string or list table +.Ar sourceaddr +for the source IP address, +which is useful on machines with multiple interfaces. +If the list contains more than one address, all of them are used +in such a way that traffic is routed as efficiently as possible. +.El +.It Ic bounce Cm warn-interval Ar delay Op , Ar delay ... +Send warning messages to the envelope sender when temporary delivery +failures cause a message to remain on the queue for longer than +.Ar delay . +Each +.Ar delay +parameter consists of a positive decimal integer and a unit +.Cm s , m , h , +or +.Cm d . +At most four +.Ar delay +parameters can be specified. +The default is +.Qq Ic bounce Cm warn-interval No 4h , +sending a single warning after four hours. +.It Ic ca Ar caname Cm cert Ar cafile +Associate the Certificate Authority (CA) certificate file +.Ar cafile +with host +.Ar caname , +and use that file as the CA certificate for that host. +.Ar caname +is the server's name, +derived from the default hostname +or set using either +.Pa /etc/mail/mailname +or using the +.Ic hostname +directive. +.It Ic filter Ar chain-name Ic chain Brq Ar filter-name Op , Ar ... +Register a chain of filters +.Ar chain-name , +consisting of the filters listed from +.Ar filter-name . +Filters part of a filter chain are executed in order of declaration for +each phase that they are registered for. +A filter chain may be used in place of a filter for any directive but +filter chains themselves. +.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions decision +Register a filter +.Ar filter-name . +A +.Ar decision +about what to do with the mail is taken at phase +.Ar phase-name +when matching +.Ar conditions . +Phases, matching conditions, and decisions are described in +.Sx MAIL FILTERING , +below. +.It Ic filter Ar filter-name Ic proc Ar proc-name +Register +.Qq proc +filter +.Ar filter-name +backed by the +.Ar proc-name +process. +.It Ic filter Ar filter-name Ic proc-exec Ar command +Register and execute +.Qq proc +filter +.Ar filter-name +from +.Ar command . +If +.Ar command +starts with a slash it is executed with an absolute path, +else it will be run from +.Dq /usr/local/libexec/smtpd/ . +.It Ic include Qq Ar pathname +Replace this directive with the content of the additional configuration +file at the absolute +.Ar pathname . +.It Ic listen on Ar interface Oo Ar family Oc Op Ar options +Listen on the +.Ar interface +for incoming connections, using the same syntax as for +.Xr ifconfig 8 . +The +.Ar interface +parameter may also be an interface group, an IP address, or a domain name. +Listening can optionally be restricted to a specific address +.Ar family , +which can be either +.Cm inet4 +or +.Cm inet6 . +.Pp +The +.Ar options +are as follows: +.Bl -tag -width Ds +.It Cm auth Op Pf < Ar authtable Ns > +Support SMTPAUTH: clients may only start SMTP transactions +after successful authentication. +Users are authenticated against either their own normal login credentials +or a credentials table +.Ar authtable , +the format of which is described in +.Xr table 5 . +.It Cm auth-optional Op Pf < Ar authtable Ns > +Support SMTPAUTH optionally: +clients need not authenticate, but may do so. +This allows a +.Ic listen on +directive to both accept incoming mail from untrusted senders +and permit outgoing mail from authenticated users +(using +.Cm match auth ) . +It can be used in situations where it is not possible to listen on a separate port +(usually the submission port, 587) +for users to authenticate. +.It Ic ca Ar caname +For secure connections, +use the CA certificate associated with +.Ar caname +(declared in a +.Ic ca +directive) +as the CA certificate when verifying client certificates. +.It Ic filter Ar name +Apply filter +.Ar name +on connections handled by this listener. +.It Cm hostname Ar hostname +Use +.Ar hostname +in the greeting banner instead of the default server name. +.It Cm hostnames Pf < Ar names Ns > +Override the server name for specific addresses. +The +.Ar names +table contains a mapping of IP addresses to hostnames. +If the address on which the connection arrives appears in the mapping, +the associated hostname is used. +.It Cm mask-src +Omit the +.Sy from +part when prepending +.Dq Received +headers. +.It Cm no-dsn +Disable the DSN (Delivery Status Notification) extension. +.It Cm pki Ar pkiname +For secure connections, +use the certificate associated with +.Ar pkiname +(declared in a +.Ic pki +directive) +to prove a mail server's identity. +.It Cm port Op Ar port +Listen on the given +.Ar port +instead of the default port 25. +.It Cm proxy-v2 +Support the PROXYv2 protocol, +rewriting appropriately source address received from proxy. +.It Cm received-auth +In +.Dq Received +headers, report whether the session was authenticated +and by which local user. +.It Cm senders Pf < Ar users Ns > Op Cm masquerade +Look up the authenticated user in the +.Ar users +mapping table to find the email addresses that user is allowed +to submit mail as. +In addition, if the +.Cm masquerade +option is provided, +the From header is rewritten +to match the sender provided in the SMTP session. +.It Cm smtps +Support SMTPS, by default on port 465. +Mutually exclusive with +.Cm tls . +.It Cm tag Ar tag +Clients connecting to the listener are tagged with the given +.Ar tag . +.It Cm tls +Support STARTTLS, by default on port 25. +Mutually exclusive with +.Cm smtps . +.It Cm tls-require Op Cm verify +Like +.Cm tls , +but force clients to establish a secure connection +before being allowed to start an SMTP transaction. +With the +.Cm verify +option, clients must also provide a valid certificate +to establish an SMTP session. +.El +.It Ic listen on Cm socket Op Ar options +Listen for incoming SMTP connections on the Unix domain socket +.Pa /var/run/smtpd.sock . +This is done by default, even if the directive is absent. +.Pp +The +.Ar options +are as follows: +.Bl -tag -width Ds +.It Ic filter Ar name +Apply filter +.Ar name +on connections handled by this listener. +.It Cm mask-src +Omit the +.Sy from +part when prepending +.Dq Received +headers. +.It Cm tag Ar tag +Clients connecting to the listener are tagged with the given +.Ar tag . +.El +.It Ic match Ar options Cm action Ar name +If at least one mail envelope matches the +.Ar options +of one +.Ic match Cm action +directive, receive the incoming message, put a copy into each +matching envelope, and atomically save the envelopes to the mail +spool for later processing by the respective dispatcher +.Ar name . +.Pp +The following matching options are supported and can all be negated: +.Bl -tag -width Ds +.It Xo +.Op Ic \&! +.Cm for any +.Xc +Specify that session may address any destination. +.It Xo +.Op Ic \&! +.Cm for local +.Xc +Specify that session may address any local domain. +This is the default, and may be omitted. +.It Xo +.Op Ic \&! +.Cm for domain +.Ar domain | Pf < Ar domain Ns > +.Xc +Specify that session may address the string or list table +.Ar domain . +.It Xo +.Op Ic \&! +.Cm for domain regex +.Ar domain | Pf < Ar domain Ns > +.Xc +Specify that session may address the regex or regex table +.Ar domain . +.It Xo +.Op Ic \&! +.Cm for rcpt-to +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that session may address the string or list table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm for rcpt-to regex +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that session may address the regex or regex table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm from any +.Xc +Specify that session may originate from any source. +.It Xo +.Op Ic \&! +.Cm from auth +.Xc +Specify that session may originate from any authenticated user, +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from auth +.Ar user | Pf < Ar user Ns > +.Xc +Specify that session may originate from authenticated user or user list +.Ar user , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from auth +.Ar user | Pf < Ar user Ns > +.Xc +Specify that session may originate from authenticated regex or regex list +.Ar user , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from local +.Xc +Specify that session may only originate from a local IP address, +or from the local enqueuer. +This is the default, and may be omitted. +.It Xo +.Op Ic \&! +.Cm from mail-from +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that session may originate from sender or sender list +.Ar sender , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from mail-from regex +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that session may originate from regex or regex list +.Ar sender , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from rdns +.Xc +Specify that session may only originate from an IP address that +resolves to a reverse DNS. +.It Xo +.Op Ic \&! +.Cm from rdns +.Ar hostname | Pf < Ar hostname Ns > +.Xc +Specify that session may only originate from an IP address that +resolves to a reverse DNS matching string or list string +.Ar hostname . +.It Xo +.Op Ic \&! +.Cm from rdns regex +.Ar hostname | Pf < Ar hostname Ns > +.Xc +Specify that session may only originate from an IP address that +resolves to a reverse DNS matching regex or list regex +.Ar hostname . +.It Xo +.Op Ic \&! +.Cm from socket +.Xc +Specify that session may only originate from the local enqueuer. +.It Xo +.Op Ic \&! +.Cm from src +.Ar address | Pf < Ar address Ns > +.Xc +Specify that session may only originate from string or list table +.Ar address +which can be a specific address or a subnet expressed in CIDR-notation. +.It Xo +.Op Ic \&! +.Cm from src regex +.Ar address | Pf < Ar address Ns > +.Xc +Specify that session may only originate from regex or regex table +.Ar address +which can be a specific address or a subnet expressed in CIDR-notation. +.El +.Pp +In addition, the following transaction options: +.Bl -tag -width Ds +.It Xo +.Op Ic \&! +.Cm auth +.Xc +Matches transactions which have been authenticated. +.It Xo +.Op Ic \&! +.Cm auth +.Ar username | Pf < Ar username Ns > +.Xc +Matches transactions which have been authenticated for user or user list +.Ar username . +.It Xo +.Op Ic \&! +.Cm auth regex +.Ar username | Pf < Ar username Ns > +.Xc +Matches transactions which have been authenticated for regex or regex list +.Ar username . +.It Xo +.Op Ic \&! +.Cm helo +.Ar helo-name | Pf < Ar helo-name Ns > +.Xc +Specify that session's HELO / EHLO should match the string or list table +.Ar helo-name . +.It Xo +.Op Ic \&! +.Cm helo regex +.Ar helo-name | Pf < Ar helo-name Ns > +.Xc +Specify that session's HELO / EHLO should match the regex or regex table +.Ar helo-name . +.It Xo +.Op Ic \&! +.Cm mail-from +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that transactions's MAIL FROM should match the string or list table +.Ar sender . +.It Xo +.Op Ic \&! +.Cm mail-from regex +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that transactions's MAIL FROM should match the regex or regex table +.Ar sender . +.It Xo +.Op Ic \&! +.Cm rcpt-to +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that transaction's RCPT TO should match the string or list table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm rcpt-to regex +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that transaction's RCPT TO should match the regex or regex table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm tag Ar tag +.Xc +Matches transactions tagged with the given +.Ar tag . +.It Xo +.Op Ic \&! +.Cm tag regex Ar tag +.Xc +Matches transactions tagged with the given +.Ar tag +regex. +.It Xo +.Op Ic \&! +.Cm tls +.Xc +Specify that transaction should take place in a TLS channel. +.El +.It Ic match Ar options Cm reject +Reject the incoming message during the SMTP dialogue. +The same +.Ar options +are supported as for the +.Ic match Cm action +directive. +.It Ic mda Cm wrapper Ar name command +Associate +.Ar command +with the mail delivery agent wrapper named +.Ar name . +When a local delivery specifies a wrapper, the +.Ar command +associated with the wrapper will be executed instead. +The command may contain format specifiers +.Pq see Sx FORMAT SPECIFIERS . +.It Ic mta Cm max-deferred Ar number +When delivery to a given host is suspended due to temporary failures, +cache at most +.Ar number +envelopes for that host such that they can be delivered +as soon as another delivery succeeds to that host. +The default is 100. +.It Ic pki Ar pkiname Cm cert Ar certfile +Associate certificate file +.Ar certfile +with host +.Ar pkiname , +and use that file to prove the identity of the mail server to clients. +.Ar pkiname +is the server's name, +derived from the default hostname +or set using either +.Pa /etc/mail/mailname +or using the +.Ic hostname +directive. +If a fallback certificate or SNI is wanted, the +.Sq * +wildcard may be used as +.Ar pkiname . +.Pp +A certificate chain may be created by appending one or many certificates, +including a Certificate Authority certificate, +to +.Ar certfile . +The creation of certificates is documented in +.Xr starttls 8 . +.It Ic pki Ar pkiname Cm key Ar keyfile +Associate the key located in +.Ar keyfile +with host +.Ar pkiname . +.It Ic pki Ar pkiname Cm dhe Ar params +Specify the DHE parameters to use for DHE cipher suites with host +.Ar pkiname . +Valid parameter values are +.Cm none , +.Cm legacy , +and +.Cm auto . +For +.Cm legacy , +a fixed key length of 1024 bits is used, whereas for +.Cm auto , +the key length is determined automatically. +The default is +.Cm none , +which disables DHE cipher suites. +.It Ic proc Ar proc-name Ar command +Register an external process named +.Ar proc-name +from +.Ar command . +Such processes may be used to share the same instance between multiple filters. +If +.Ar command +starts with a slash it is executed with an absolute path, +else it will be run from +.Dq /usr/local/libexec/smtpd/ . +.It Ic queue Cm compression +Store queue files in a compressed format. +This may be useful to save disk space. +.It Ic queue Cm encryption Op Ar key +Encrypt queue files with +.Xr EVP_aes_256_gcm 3 . +If no +.Ar key +is specified, it is read with +.Xr getpass 3 . +If the string +.Cm stdin +or a single dash +.Pq Ql - +is given instead of a +.Ar key , +the key is read from the standard input. +.It Ic queue Cm ttl Ar delay +Set the default expiration time for temporarily undeliverable +messages, given as a positive decimal integer followed by a unit +.Cm s , m , h , +or +.Cm d . +The default is four days +.Pq 4d . +.It Ic smtp Cm ciphers Ar control +Set the +.Ar control +string for +.Xr SSL_CTX_set_cipher_list 3 . +The default is +.Qq HIGH:!aNULL:!MD5 . +.It Ic smtp limit Cm max-mails Ar count +Limit the number of messages to +.Ar count +for each session. +The default is 100. +.It Ic smtp limit Cm max-rcpt Ar count +Limit the number of recipients to +.Ar count +for each transaction. +The default is 1000. +.It Ic smtp Cm max-message-size Ar size +Reject messages larger than +.Ar size , +given as a positive number of bytes or as a string to be parsed with +.Xr scan_scaled 3 . +The default is +.Qq 35M . +.It Ic smtp Cm sub-addr-delim Ar character +When resolving the local part of a local email address, ignore the ASCII +.Ar character +and all characters following it. +The default is +.Ql + . +.It Ic srs Cm key Ar secret +Set the secret key to use for SRS, +the Sender Rewriting Scheme. +.It Ic srs Cm key backup Ar secret +Set a backup secret key to use as a fallback for SRS. +This can be used to implement SRS key rotation. +.It Ic srs Cm ttl Ar delay +Set the time-to-live delay for SRS envelopes. +After this delay, +a bounce reply to the SRS address will be discarded to limit risks of forged addresses. +The default is four days +.Pq 4d . +.It Ic table Ar name Oo Ar type : Oc Ns Ar pathname +Tables provide additional configuration information for +.Xr smtpd 8 +in the form of lists or key-value mappings. +The format of the entries depends on what the table is used for. +Refer to +.Xr table 5 +for the exhaustive documentation. +.Pp +Each table is identified by an arbitrary, unique +.Ar name . +.Pp +If the +.Ar type +is +.Cm db , +information is stored in a file created with +.Xr makemap 8 ; +if it is +.Cm file +or omitted, information is stored in a plain text file +using the format described in +.Xr table 5 . +The +.Ar pathname +to the file must be absolute. +.It Ic table Ar name Brq Ar value Op , Ar ... +Instead of using a separate file, declare a list table +containing the given static +.Ar value Ns s . +The table must contain at least one value and may declare multiple values as a +comma-separated (whitespace optional) list. +.It Ic table Ar name Brq Ar key Ns = Ns Ar value Op , Ar ... +Instead of using a separate file, declare a mapping table +containing the given static +.Ar key Ns - Ns Ar value +pairs. +The table must contain at least one key-value pair and may declare +multiple pairs as a comma-separated (whitespace optional) list. +.El +.Ss MAIL FILTERING +In a regular workflow, +.Xr smtpd 8 +may accept or reject a message based only on the content of envelopes. +Its decisions are about the handling of the message, +not about the handling of an active session. +.Pp +Filtering extends the decision making process by allowing +.Xr smtpd 8 +to stop at each phase of an SMTP session, +check that conditions are met, +then decide if a session is allowed to move forward. +.Pp +With filtering, +a session may be interrupted at any phase before an envelope is complete. +A message may also be rejected after being submitted, +regardless of whether the envelope was accepted or not. +.Pp +The following phases are currently supported: +.Bl -column mail-from -offset indent +.It connect Ta upon connection, before a banner is displayed +.It helo Ta after HELO command is submitted +.It ehlo Ta after EHLO command is submitted +.It mail-from Ta after MAIL FROM command is submitted +.It rcpt-to Ta after RCPT TO command is submitted +.It data Ta after DATA command is submitted +.It commit Ta after message is fully is submitted +.El +.Pp +At each phase, various conditions may be matched. +The fcrdns, rdns, and src data are available in all phases, +but other data must have been already submitted before they are available. +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It fcrdns Ta forward-confirmed reverse DNS is valid +.It rdns Ta session has a reverse DNS +.It rdns Pf < Ar table Ns > Ta session has a reverse DNS in table +.It src Pf < Ar table Ns > Ta source address is in table +.It helo Pf < Ar table Ns > Ta helo name is in table +.It auth Ta session is authenticated +.It auth Pf < Ar table Ns > Ta session username is in table +.It mail-from Pf < Ar table Ns > Ta sender address is in table +.It rcpt-to Pf < Ar table Ns > Ta recipient address is in table +.El +.Pp +These conditions may all be negated by prefixing them with an exclamation mark: +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It !fcrdns Ta forward-confirmed reverse DNS is invalid +.El +.Pp +Any conditions using a table may indicate that tables hold regex by +prefixing the table name with the keyword regex. +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It helo regex Pf < Ar table Ns > Ta helo name matches a regex in table +.El +.Pp +Finally, a number of decisions may be taken: +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It bypass Ta the session or transaction bypasses filters +.It disconnect Ar message Ta the session is disconnected with message +.It junk Ta the session or transaction is junked, i.e., an +.Ql X-Spam: yes +header is added to any messages +.It reject Ar message Ta the command is rejected with message +.It rewrite Ar value Ta the command parameter is rewritten with value +.El +.Pp +Decisions that involve a message require that the message be RFC valid, +meaning that they should either start with a 4xx or 5xx status code. +Descisions can be taken at any phase, +though junking can only happen before a message is committed. +.Ss FORMAT SPECIFIERS +Some configuration directives support expansion of their parameters at runtime. +Such directives (for example +.Ic action Cm maildir , +.Ic action Cm mda ) +may use format specifiers which are expanded before delivery or +relaying. +The following formats are currently supported: +.Bl -column %{user.directory} -offset indent +.It %{sender} Ta sender email address, may be empty string +.It %{sender.user} Ta user part of the sender email address, may be empty +.It %{sender.domain} Ta domain part of the sender email address, may be empty +.It %{rcpt} Ta recipient email address +.It %{rcpt.user} Ta user part of the recipient email address +.It %{rcpt.domain} Ta domain part of the recipient email address +.It %{dest} Ta recipient email address after expansion +.It %{dest.user} Ta user part after expansion +.It %{dest.domain} Ta domain part after expansion +.It %{user.username} Ta local user +.It %{user.directory} Ta home directory of the local user +.It %{mbox.from} Ta name used in mbox From separator lines +.It %{mda} Ta mda command, only available for mda wrappers +.El +.Pp +Expansion formats also support partial expansion using the optional +bracket notations with substring offset. +For example, with recipient domain +.Dq example.org : +.Bl -column %{rcpt.domain[0:-4]} -offset indent +.It %{rcpt.domain[0]} Ta expands to Dq e +.It %{rcpt.domain[1]} Ta expands to Dq x +.It %{rcpt.domain[8:]} Ta expands to Dq org +.It %{rcpt.domain[-3:]} Ta expands to Dq org +.It %{rcpt.domain[0:6]} Ta expands to Dq example +.It %{rcpt.domain[0:-4]} Ta expands to Dq example +.El +.Pp +In addition, modifiers may be applied to the token. +For example, with recipient +.Dq User+Tag@Example.org : +.Bl -column %{rcpt:lowercase|strip} -offset indent +.It %{rcpt:lowercase} Ta expands to Dq user+tag@example.org +.It %{rcpt:uppercase} Ta expands to Dq USER+TAG@EXAMPLE.ORG +.It %{rcpt:strip} Ta expands to Dq User@Example.org +.It %{rcpt:lowercase|strip} Ta expands to Dq user@example.org +.El +.Pp +For security concerns, expanded values are sanitized and potentially +dangerous characters are replaced with +.Sq \&: . +In situations where they are desirable, the +.Dq raw +modifier may be applied. +For example, with recipient +.Dq user+t?g@example.org : +.Bl -column %{rcpt:raw} -offset indent +.It %{rcpt} Ta expands to Dq user+t:g@example.org +.It %{rcpt:raw} Ta expands to Dq user+t?g@example.org +.El +.Sh FILES +.Bl -tag -width "/etc/mail/smtpd.confXXX" -compact +.It Pa /etc/mail/smtpd.conf +Default +.Xr smtpd 8 +configuration file. +.It Pa /etc/mail/mailname +If this file exists, +the first line is used as the server name. +Otherwise, the server name is derived from the local hostname returned by +.Xr gethostname 3 , +either directly if it is a fully qualified domain name, +or by retrieving the associated canonical name through +.Xr getaddrinfo 3 . +.It Pa /var/run/smtpd.sock +Unix domain socket for incoming SMTP connections. +.It Pa /var/spool/smtpd/ +Spool directories for mail during processing. +.El +.Sh EXAMPLES +The default +.Nm +file which ships with +.Ox +listens on the loopback network interface +.Pq Pa lo0 +and allows for mail from users and daemons on the local machine, +as well as permitting email to remote servers. +Some more complex configurations are given below. +.Pp +This first example is the same as the default configuration, +but all outgoing mail is forwarded to a remote SMTP server. +A secrets file is needed to specify a username and password: +.Bd -literal -offset indent +# touch /etc/mail/secrets +# chmod 640 /etc/mail/secrets +# chown root:_smtpd /etc/mail/secrets +# echo "bob username:password" > /etc/mail/secrets +.Ed +.Pp +.Nm +would look like this: +.Bd -literal -offset indent +table aliases file:/etc/mail/aliases +table secrets file:/etc/mail/secrets + +listen on lo0 + +action "local_mail" mbox alias +action "outbound" relay host smtp+tls://bob@smtp.example.com \e + auth + +match from local for local action "local_mail" +match from local for any action "outbound" +.Ed +.Pp +In this second example, +the aim is to permit mail delivery and relaying only for users that can authenticate +(using their normal login credentials). +An RSA certificate must be provided to prove the server's identity. +The mail server listens on all interfaces the default routes point to. +Mail with a local destination is sent to an external MDA. +First, the RSA certificate is created: +.Bd -literal -offset indent +# openssl genrsa \-out /etc/ssl/private/mail.example.com.key 4096 +# openssl req \-new \-x509 \-key /etc/ssl/private/mail.example.com.key \e + \-out /etc/ssl/mail.example.com.crt \-days 365 +# chmod 600 /etc/ssl/mail.example.com.crt +# chmod 600 /etc/ssl/private/mail.example.com.key +.Ed +.Pp +In the example above, +a certificate valid for one year was created. +The configuration file would look like this: +.Bd -literal -offset indent +pki mail.example.com cert "/etc/ssl/mail.example.com.crt" +pki mail.example.com key "/etc/ssl/private/mail.example.com.key" + +table aliases file:/etc/mail/aliases + +listen on lo0 +listen on egress tls pki mail.example.com auth + +action mda_with_aliases mda "/path/to/mda \-f \-" alias +action mda_without_aliases mda "/path/to/mda \-f \-" +action "outbound" relay + +match for local action mda_with_aliases +match from any for domain example.com action mda_without_aliases +match for any action "outbound" +match auth from any for any action "outbound" +.Ed +.Pp +For sites that wish to sign messages using DKIM, +the following example uses +.Sy opensmtpd-filter-dkimsign +for DKIM signing: +.Bd -literal -offset indent +table aliases file:/etc/mail/aliases + +filter "dkimsign" proc-exec "filter-dkimsign -d -s \e + -k /etc/mail/dkim/private.key" user _dkimsign group _dkimsign + +listen on socket filter "dkimsign" +listen on lo0 filter "dkimsign" + +action "local_mail" mbox alias +action "outbound" relay + +match for local action "local_mail" +match for any action "outbound" +.Ed +.Pp +Alternatively, the +.Sy opensmtpd-filter-rspamd +package may be used to provide integration with +.Sy rspamd , +a third-party daemon which provides multiple antispam features +as well as DKIM signing. +As well as configuring +.Sy rspamd +itself, +it requires use of the +.Cm proc-exec +keyword: +.Bd -literal -offset indent +filter "rspamd" proc-exec "filter-rspamd" +.Ed +.Pp +Sites that accept non-local messages may be able to cut down on the +volume of spam received by rejecting forged messages that claim +to be from the local domain. +The following example uses a list table +.Em other-relays +to specify the IP addresses of relays that may legitimately +originate mail with the owner's domain as the sender. +.Bd -literal -offset indent +table aliases file:/etc/mail/aliases +table other-relays file:/etc/mail/other-relays + +listen on lo0 +listen on egress + +action "local_mail" mbox alias +action "outbound" relay + +match for local action "local_mail" +match for any action "outbound" +match !from src mail\-from "@example.com" for any \e + reject +match from any for domain example.com action "local_mail" +.Ed +.Sh SEE ALSO +.Xr mailer.conf 5 , +.Xr table 5 , +.Xr makemap 8 , +.Xr smtpd 8 +.Sh HISTORY +.Xr smtpd 8 +first appeared in +.Ox 4.6 . diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h new file mode 100644 index 00000000..4385f747 --- /dev/null +++ b/usr.sbin/smtpd/smtpd.h @@ -0,0 +1,1784 @@ +/* $OpenBSD: smtpd.h,v 1.656 2020/04/08 07:30:44 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include "openbsd-compat.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#include +#include +#include + +#include "smtpd-defines.h" +#include "smtpd-api.h" +#include "ioev.h" + +#define CHECK_IMSG_DATA_SIZE(imsg, expected_sz) do { \ + if ((imsg)->hdr.len - IMSG_HEADER_SIZE != (expected_sz)) \ + fatalx("smtpd: imsg %d: data size expected %zd got %zd",\ + (imsg)->hdr.type, \ + (expected_sz), (imsg)->hdr.len - IMSG_HEADER_SIZE); \ +} while (0) + +#ifndef SMTPD_CONFDIR +#define SMTPD_CONFDIR "/etc" +#endif +#define CONF_FILE SMTPD_CONFDIR "/smtpd.conf" +#define MAILNAME_FILE SMTPD_CONFDIR "/mailname" +#ifndef CA_FILE +#define CA_FILE "/etc/ssl/cert.pem" +#endif + +#define PROC_COUNT 7 + +#define MAX_HOPS_COUNT 100 +#define DEFAULT_MAX_BODY_SIZE (35*1024*1024) + +#define EXPAND_BUFFER 1024 + +#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) +#ifndef SMTPD_USER +#define SMTPD_USER "_smtpd" +#endif +#ifndef SMTPD_QUEUE_USER +#define SMTPD_QUEUE_USER "_smtpq" +#endif +#ifndef SMTPD_SOCKDIR +#define SMTPD_SOCKDIR "/var/run" +#endif +#define SMTPD_SOCKET SMTPD_SOCKDIR "/smtpd.sock" +#ifndef SMTPD_NAME +#define SMTPD_NAME "OpenSMTPD" +#endif +#define SMTPD_VERSION "6.7.0-portable" +#define SMTPD_SESSION_TIMEOUT 300 +#define SMTPD_BACKLOG 5 + +#ifndef PATH_SMTPCTL +#define PATH_SMTPCTL "/usr/sbin/smtpctl" +#endif + +#define PATH_OFFLINE "/offline" +#define PATH_PURGE "/purge" +#define PATH_TEMPORARY "/temporary" + +#ifndef PATH_LIBEXEC +#define PATH_LIBEXEC "/usr/local/libexec/smtpd" +#endif + + +/* + * RFC 5322 defines these characters as valid, some of them are + * potentially dangerous and need to be escaped. + */ +#define MAILADDR_ALLOWED "!#$%&'*/?^`{|}~+-=_" +#define MAILADDR_ESCAPE "!#$%&'*?`{|}~" + + +#define F_STARTTLS 0x01 +#define F_SMTPS 0x02 +#define F_SSL (F_STARTTLS | F_SMTPS) +#define F_AUTH 0x08 +#define F_STARTTLS_REQUIRE 0x20 +#define F_AUTH_REQUIRE 0x40 +#define F_MASK_SOURCE 0x100 +#define F_TLS_VERIFY 0x200 +#define F_EXT_DSN 0x400 +#define F_RECEIVEDAUTH 0x800 +#define F_MASQUERADE 0x1000 +#define F_FILTERED 0x2000 +#define F_PROXY 0x4000 + +#define RELAY_TLS_OPPORTUNISTIC 0 +#define RELAY_TLS_STARTTLS 1 +#define RELAY_TLS_SMTPS 2 +#define RELAY_TLS_NO 3 + +#define RELAY_AUTH 0x08 +#define RELAY_LMTP 0x80 +#define RELAY_TLS_VERIFY 0x200 + +#define MTA_EXT_DSN 0x400 + + +#define P_SENDMAIL 0 +#define P_NEWALIASES 1 +#define P_MAKEMAP 2 + +#define CERT_ERROR -1 +#define CERT_OK 0 +#define CERT_NOCA 1 +#define CERT_NOCERT 2 +#define CERT_INVALID 3 + +struct userinfo { + char username[SMTPD_VUSERNAME_SIZE]; + char directory[PATH_MAX]; + uid_t uid; + gid_t gid; +}; + +struct netaddr { + struct sockaddr_storage ss; + int bits; +}; + +struct relayhost { + uint16_t flags; + int tls; + char hostname[HOST_NAME_MAX+1]; + uint16_t port; + char authlabel[PATH_MAX]; +}; + +struct credentials { + char username[LINE_MAX]; + char password[LINE_MAX]; +}; + +struct destination { + char name[HOST_NAME_MAX+1]; +}; + +struct source { + struct sockaddr_storage addr; +}; + +struct addrname { + struct sockaddr_storage addr; + char name[HOST_NAME_MAX+1]; +}; + +union lookup { + struct expand *expand; + struct credentials creds; + struct netaddr netaddr; + struct source source; + struct destination domain; + struct userinfo userinfo; + struct mailaddr mailaddr; + struct addrname addrname; + struct maddrmap *maddrmap; + char relayhost[LINE_MAX]; +}; + +/* + * Bump IMSG_VERSION whenever a change is made to enum imsg_type. + * This will ensure that we can never use a wrong version of smtpctl with smtpd. + */ +#define IMSG_VERSION 16 + +enum imsg_type { + IMSG_NONE, + + IMSG_CTL_OK, + IMSG_CTL_FAIL, + + IMSG_CTL_GET_DIGEST, + IMSG_CTL_GET_STATS, + IMSG_CTL_LIST_MESSAGES, + IMSG_CTL_LIST_ENVELOPES, + IMSG_CTL_MTA_SHOW_HOSTS, + IMSG_CTL_MTA_SHOW_RELAYS, + IMSG_CTL_MTA_SHOW_ROUTES, + IMSG_CTL_MTA_SHOW_HOSTSTATS, + IMSG_CTL_MTA_BLOCK, + IMSG_CTL_MTA_UNBLOCK, + IMSG_CTL_MTA_SHOW_BLOCK, + IMSG_CTL_PAUSE_EVP, + IMSG_CTL_PAUSE_MDA, + IMSG_CTL_PAUSE_MTA, + IMSG_CTL_PAUSE_SMTP, + IMSG_CTL_PROFILE, + IMSG_CTL_PROFILE_DISABLE, + IMSG_CTL_PROFILE_ENABLE, + IMSG_CTL_RESUME_EVP, + IMSG_CTL_RESUME_MDA, + IMSG_CTL_RESUME_MTA, + IMSG_CTL_RESUME_SMTP, + IMSG_CTL_RESUME_ROUTE, + IMSG_CTL_REMOVE, + IMSG_CTL_SCHEDULE, + IMSG_CTL_SHOW_STATUS, + IMSG_CTL_TRACE_DISABLE, + IMSG_CTL_TRACE_ENABLE, + IMSG_CTL_UPDATE_TABLE, + IMSG_CTL_VERBOSE, + IMSG_CTL_DISCOVER_EVPID, + IMSG_CTL_DISCOVER_MSGID, + + IMSG_CTL_SMTP_SESSION, + + IMSG_GETADDRINFO, + IMSG_GETADDRINFO_END, + IMSG_GETNAMEINFO, + IMSG_RES_QUERY, + + IMSG_CERT_INIT, + IMSG_CERT_CERTIFICATE, + IMSG_CERT_VERIFY, + + IMSG_SETUP_KEY, + IMSG_SETUP_PEER, + IMSG_SETUP_DONE, + + IMSG_CONF_START, + IMSG_CONF_END, + + IMSG_STAT_INCREMENT, + IMSG_STAT_DECREMENT, + IMSG_STAT_SET, + + IMSG_LKA_AUTHENTICATE, + IMSG_LKA_OPEN_FORWARD, + IMSG_LKA_ENVELOPE_SUBMIT, + IMSG_LKA_ENVELOPE_COMMIT, + + IMSG_QUEUE_DELIVER, + IMSG_QUEUE_DELIVERY_OK, + IMSG_QUEUE_DELIVERY_TEMPFAIL, + IMSG_QUEUE_DELIVERY_PERMFAIL, + IMSG_QUEUE_DELIVERY_LOOP, + IMSG_QUEUE_DISCOVER_EVPID, + IMSG_QUEUE_DISCOVER_MSGID, + IMSG_QUEUE_ENVELOPE_ACK, + IMSG_QUEUE_ENVELOPE_COMMIT, + IMSG_QUEUE_ENVELOPE_REMOVE, + IMSG_QUEUE_ENVELOPE_SCHEDULE, + IMSG_QUEUE_ENVELOPE_SUBMIT, + IMSG_QUEUE_HOLDQ_HOLD, + IMSG_QUEUE_HOLDQ_RELEASE, + IMSG_QUEUE_MESSAGE_COMMIT, + IMSG_QUEUE_MESSAGE_ROLLBACK, + IMSG_QUEUE_SMTP_SESSION, + IMSG_QUEUE_TRANSFER, + + IMSG_MDA_DELIVERY_OK, + IMSG_MDA_DELIVERY_TEMPFAIL, + IMSG_MDA_DELIVERY_PERMFAIL, + IMSG_MDA_DELIVERY_LOOP, + IMSG_MDA_DELIVERY_HOLD, + IMSG_MDA_DONE, + IMSG_MDA_FORK, + IMSG_MDA_HOLDQ_RELEASE, + IMSG_MDA_LOOKUP_USERINFO, + IMSG_MDA_KILL, + IMSG_MDA_OPEN_MESSAGE, + + IMSG_MTA_DELIVERY_OK, + IMSG_MTA_DELIVERY_TEMPFAIL, + IMSG_MTA_DELIVERY_PERMFAIL, + IMSG_MTA_DELIVERY_LOOP, + IMSG_MTA_DELIVERY_HOLD, + IMSG_MTA_DNS_HOST, + IMSG_MTA_DNS_HOST_END, + IMSG_MTA_DNS_MX, + IMSG_MTA_DNS_MX_PREFERENCE, + IMSG_MTA_HOLDQ_RELEASE, + IMSG_MTA_LOOKUP_CREDENTIALS, + IMSG_MTA_LOOKUP_SOURCE, + IMSG_MTA_LOOKUP_HELO, + IMSG_MTA_LOOKUP_SMARTHOST, + IMSG_MTA_OPEN_MESSAGE, + IMSG_MTA_SCHEDULE, + + IMSG_SCHED_ENVELOPE_BOUNCE, + IMSG_SCHED_ENVELOPE_DELIVER, + IMSG_SCHED_ENVELOPE_EXPIRE, + IMSG_SCHED_ENVELOPE_INJECT, + IMSG_SCHED_ENVELOPE_REMOVE, + IMSG_SCHED_ENVELOPE_TRANSFER, + + IMSG_SMTP_AUTHENTICATE, + IMSG_SMTP_MESSAGE_COMMIT, + IMSG_SMTP_MESSAGE_CREATE, + IMSG_SMTP_MESSAGE_ROLLBACK, + IMSG_SMTP_MESSAGE_OPEN, + IMSG_SMTP_CHECK_SENDER, + IMSG_SMTP_EXPAND_RCPT, + IMSG_SMTP_LOOKUP_HELO, + + IMSG_SMTP_REQ_CONNECT, + IMSG_SMTP_REQ_HELO, + IMSG_SMTP_REQ_MAIL, + IMSG_SMTP_REQ_RCPT, + IMSG_SMTP_REQ_DATA, + IMSG_SMTP_REQ_EOM, + IMSG_SMTP_EVENT_RSET, + IMSG_SMTP_EVENT_COMMIT, + IMSG_SMTP_EVENT_ROLLBACK, + IMSG_SMTP_EVENT_DISCONNECT, + + IMSG_LKA_PROCESSOR_FORK, + IMSG_LKA_PROCESSOR_ERRFD, + + IMSG_REPORT_SMTP_LINK_CONNECT, + IMSG_REPORT_SMTP_LINK_DISCONNECT, + IMSG_REPORT_SMTP_LINK_GREETING, + IMSG_REPORT_SMTP_LINK_IDENTIFY, + IMSG_REPORT_SMTP_LINK_TLS, + IMSG_REPORT_SMTP_LINK_AUTH, + IMSG_REPORT_SMTP_TX_RESET, + IMSG_REPORT_SMTP_TX_BEGIN, + IMSG_REPORT_SMTP_TX_MAIL, + IMSG_REPORT_SMTP_TX_RCPT, + IMSG_REPORT_SMTP_TX_ENVELOPE, + IMSG_REPORT_SMTP_TX_DATA, + IMSG_REPORT_SMTP_TX_COMMIT, + IMSG_REPORT_SMTP_TX_ROLLBACK, + IMSG_REPORT_SMTP_PROTOCOL_CLIENT, + IMSG_REPORT_SMTP_PROTOCOL_SERVER, + IMSG_REPORT_SMTP_FILTER_RESPONSE, + IMSG_REPORT_SMTP_TIMEOUT, + + IMSG_FILTER_SMTP_BEGIN, + IMSG_FILTER_SMTP_END, + IMSG_FILTER_SMTP_PROTOCOL, + IMSG_FILTER_SMTP_DATA_BEGIN, + IMSG_FILTER_SMTP_DATA_END, + + IMSG_CA_RSA_PRIVENC, + IMSG_CA_RSA_PRIVDEC, + IMSG_CA_ECDSA_SIGN, +}; + +enum smtp_proc_type { + PROC_PARENT = 0, + PROC_LKA, + PROC_QUEUE, + PROC_CONTROL, + PROC_SCHEDULER, + PROC_PONY, + PROC_CA, + PROC_PROCESSOR, + PROC_CLIENT, +}; + +enum table_type { + T_NONE = 0, + T_DYNAMIC = 0x01, /* table with external source */ + T_LIST = 0x02, /* table holding a list */ + T_HASH = 0x04, /* table holding a hash table */ +}; + +struct table { + char t_name[LINE_MAX]; + enum table_type t_type; + char t_config[PATH_MAX]; + + void *t_handle; + struct table_backend *t_backend; +}; + +struct table_backend { + const char *name; + const unsigned int services; + int (*config)(struct table *); + int (*add)(struct table *, const char *, const char *); + void (*dump)(struct table *); + int (*open)(struct table *); + int (*update)(struct table *); + void (*close)(struct table *); + int (*lookup)(struct table *, enum table_service, const char *, char **); + int (*fetch)(struct table *, enum table_service, char **); +}; + + +enum bounce_type { + B_FAILED, + B_DELAYED, + B_DELIVERED +}; + +enum dsn_ret { + DSN_RETFULL = 1, + DSN_RETHDRS +}; + +struct delivery_bounce { + enum bounce_type type; + time_t delay; + time_t ttl; + enum dsn_ret dsn_ret; + int mta_without_dsn; +}; + +enum expand_type { + EXPAND_INVALID, + EXPAND_USERNAME, + EXPAND_FILENAME, + EXPAND_FILTER, + EXPAND_INCLUDE, + EXPAND_ADDRESS, + EXPAND_ERROR, +}; + +enum filter_phase { + FILTER_CONNECT, + FILTER_HELO, + FILTER_EHLO, + FILTER_STARTTLS, + FILTER_AUTH, + FILTER_MAIL_FROM, + FILTER_RCPT_TO, + FILTER_DATA, + FILTER_DATA_LINE, + FILTER_RSET, + FILTER_QUIT, + FILTER_NOOP, + FILTER_HELP, + FILTER_WIZ, + FILTER_COMMIT, + FILTER_PHASES_COUNT /* must be last */ +}; + +struct expandnode { + RB_ENTRY(expandnode) entry; + TAILQ_ENTRY(expandnode) tq_entry; + enum expand_type type; + int sameuser; + int realuser; + int forwarded; + struct rule *rule; + struct expandnode *parent; + unsigned int depth; + union { + /* + * user field handles both expansion user and system user + * so we MUST make it large enough to fit a mailaddr user + */ + char user[SMTPD_MAXLOCALPARTSIZE]; + char buffer[EXPAND_BUFFER]; + struct mailaddr mailaddr; + } u; + char subaddress[SMTPD_SUBADDRESS_SIZE]; +}; + +struct expand { + RB_HEAD(expandtree, expandnode) tree; + TAILQ_HEAD(xnodes, expandnode) *queue; + size_t nb_nodes; + struct rule *rule; + struct expandnode *parent; +}; + +struct maddrnode { + TAILQ_ENTRY(maddrnode) entries; + struct mailaddr mailaddr; +}; + +struct maddrmap { + TAILQ_HEAD(xmaddr, maddrnode) queue; +}; + +#define DSN_SUCCESS 0x01 +#define DSN_FAILURE 0x02 +#define DSN_DELAY 0x04 +#define DSN_NEVER 0x08 + +#define DSN_ENVID_LEN 100 + +#define SMTPD_ENVELOPE_VERSION 3 +struct envelope { + TAILQ_ENTRY(envelope) entry; + + char dispatcher[HOST_NAME_MAX+1]; + + char tag[SMTPD_TAG_SIZE]; + + uint32_t version; + uint64_t id; + enum envelope_flags flags; + + char smtpname[HOST_NAME_MAX+1]; + char helo[HOST_NAME_MAX+1]; + char hostname[HOST_NAME_MAX+1]; + char username[SMTPD_MAXMAILADDRSIZE]; + char errorline[LINE_MAX]; + struct sockaddr_storage ss; + + struct mailaddr sender; + struct mailaddr rcpt; + struct mailaddr dest; + + char mda_user[SMTPD_VUSERNAME_SIZE]; + char mda_subaddress[SMTPD_SUBADDRESS_SIZE]; + char mda_exec[LINE_MAX]; + + enum delivery_type type; + union { + struct delivery_bounce bounce; + } agent; + + uint16_t retry; + time_t creation; + time_t ttl; + time_t lasttry; + time_t nexttry; + time_t lastbounce; + + struct mailaddr dsn_orcpt; + char dsn_envid[DSN_ENVID_LEN+1]; + uint8_t dsn_notify; + enum dsn_ret dsn_ret; + + uint8_t esc_class; + uint8_t esc_code; +}; + +struct listener { + uint16_t flags; + int fd; + struct sockaddr_storage ss; + in_port_t port; + struct timeval timeout; + struct event ev; + char filter_name[PATH_MAX]; + char pki_name[PATH_MAX]; + char ca_name[PATH_MAX]; + char tag[SMTPD_TAG_SIZE]; + char authtable[LINE_MAX]; + char hostname[HOST_NAME_MAX+1]; + char hostnametable[PATH_MAX]; + char sendertable[PATH_MAX]; + + TAILQ_ENTRY(listener) entry; + + int local; /* there must be a better way */ +}; + +struct smtpd { + char sc_conffile[PATH_MAX]; + size_t sc_maxsize; + +#define SMTPD_OPT_VERBOSE 0x00000001 +#define SMTPD_OPT_NOACTION 0x00000002 + uint32_t sc_opts; + +#define SMTPD_EXITING 0x00000001 /* unused */ +#define SMTPD_MDA_PAUSED 0x00000002 +#define SMTPD_MTA_PAUSED 0x00000004 +#define SMTPD_SMTP_PAUSED 0x00000008 +#define SMTPD_MDA_BUSY 0x00000010 +#define SMTPD_MTA_BUSY 0x00000020 +#define SMTPD_BOUNCE_BUSY 0x00000040 +#define SMTPD_SMTP_DISABLED 0x00000080 + uint32_t sc_flags; + +#define QUEUE_COMPRESSION 0x00000001 +#define QUEUE_ENCRYPTION 0x00000002 +#define QUEUE_EVPCACHE 0x00000004 + uint32_t sc_queue_flags; + char *sc_queue_key; + size_t sc_queue_evpcache_size; + + size_t sc_session_max_rcpt; + size_t sc_session_max_mails; + + struct dict *sc_mda_wrappers; + size_t sc_mda_max_session; + size_t sc_mda_max_user_session; + size_t sc_mda_task_hiwat; + size_t sc_mda_task_lowat; + size_t sc_mda_task_release; + + size_t sc_mta_max_deferred; + + size_t sc_scheduler_max_inflight; + size_t sc_scheduler_max_evp_batch_size; + size_t sc_scheduler_max_msg_batch_size; + size_t sc_scheduler_max_schedule; + + struct dict *sc_filter_processes_dict; + + int sc_ttl; +#define MAX_BOUNCE_WARN 4 + time_t sc_bounce_warn[MAX_BOUNCE_WARN]; + char sc_hostname[HOST_NAME_MAX+1]; + struct stat_backend *sc_stat; + struct compress_backend *sc_comp; + + time_t sc_uptime; + + /* This is a listener for a local socket used by smtp_enqueue(). */ + struct listener *sc_sock_listener; + + TAILQ_HEAD(listenerlist, listener) *sc_listeners; + + TAILQ_HEAD(rulelist, rule) *sc_rules; + + + struct dict *sc_filters_dict; + struct dict *sc_dispatchers; + struct dispatcher *sc_dispatcher_bounce; + + struct dict *sc_ca_dict; + struct dict *sc_pki_dict; + struct dict *sc_ssl_dict; + + struct dict *sc_tables_dict; /* keyed lookup */ + + struct dict *sc_limits_dict; + + char *sc_tls_ciphers; + + char *sc_subaddressing_delim; + + char *sc_srs_key; + char *sc_srs_key_backup; + int sc_srs_ttl; +}; + +#define TRACE_DEBUG 0x0001 +#define TRACE_IMSG 0x0002 +#define TRACE_IO 0x0004 +#define TRACE_SMTP 0x0008 +#define TRACE_FILTERS 0x0010 +#define TRACE_MTA 0x0020 +#define TRACE_BOUNCE 0x0040 +#define TRACE_SCHEDULER 0x0080 +#define TRACE_LOOKUP 0x0100 +#define TRACE_STAT 0x0200 +#define TRACE_RULES 0x0400 +#define TRACE_MPROC 0x0800 +#define TRACE_EXPAND 0x1000 +#define TRACE_TABLES 0x2000 +#define TRACE_QUEUE 0x4000 + +#define PROFILE_TOSTAT 0x0001 +#define PROFILE_IMSG 0x0002 +#define PROFILE_QUEUE 0x0004 + +struct forward_req { + uint64_t id; + uint8_t status; + + char user[SMTPD_VUSERNAME_SIZE]; + uid_t uid; + gid_t gid; + char directory[PATH_MAX]; +}; + +struct deliver { + char dispatcher[EXPAND_BUFFER]; + + struct mailaddr sender; + struct mailaddr rcpt; + struct mailaddr dest; + + char mda_subaddress[SMTPD_SUBADDRESS_SIZE]; + char mda_exec[LINE_MAX]; + + struct userinfo userinfo; +}; + +struct mta_host { + SPLAY_ENTRY(mta_host) entry; + struct sockaddr *sa; + char *ptrname; + int refcount; + size_t nconn; + time_t lastconn; + time_t lastptrquery; + +#define HOST_IGNORE 0x01 + int flags; +}; + +struct mta_mx { + TAILQ_ENTRY(mta_mx) entry; + struct mta_host *host; + char *mxname; + int preference; +}; + +struct mta_domain { + SPLAY_ENTRY(mta_domain) entry; + char *name; + int as_host; + TAILQ_HEAD(, mta_mx) mxs; + int mxstatus; + int refcount; + size_t nconn; + time_t lastconn; + time_t lastmxquery; +}; + +struct mta_source { + SPLAY_ENTRY(mta_source) entry; + struct sockaddr *sa; + int refcount; + size_t nconn; + time_t lastconn; +}; + +struct mta_connector { + struct mta_source *source; + struct mta_relay *relay; + +#define CONNECTOR_ERROR_FAMILY 0x0001 +#define CONNECTOR_ERROR_SOURCE 0x0002 +#define CONNECTOR_ERROR_MX 0x0004 +#define CONNECTOR_ERROR_ROUTE_NET 0x0008 +#define CONNECTOR_ERROR_ROUTE_SMTP 0x0010 +#define CONNECTOR_ERROR_ROUTE 0x0018 +#define CONNECTOR_ERROR_BLOCKED 0x0020 +#define CONNECTOR_ERROR 0x00ff + +#define CONNECTOR_LIMIT_HOST 0x0100 +#define CONNECTOR_LIMIT_ROUTE 0x0200 +#define CONNECTOR_LIMIT_SOURCE 0x0400 +#define CONNECTOR_LIMIT_RELAY 0x0800 +#define CONNECTOR_LIMIT_CONN 0x1000 +#define CONNECTOR_LIMIT_DOMAIN 0x2000 +#define CONNECTOR_LIMIT 0xff00 + +#define CONNECTOR_NEW 0x10000 +#define CONNECTOR_WAIT 0x20000 + int flags; + + int refcount; + size_t nconn; + time_t lastconn; +}; + +struct mta_route { + SPLAY_ENTRY(mta_route) entry; + uint64_t id; + struct mta_source *src; + struct mta_host *dst; +#define ROUTE_NEW 0x01 +#define ROUTE_RUNQ 0x02 +#define ROUTE_KEEPALIVE 0x04 +#define ROUTE_DISABLED 0xf0 +#define ROUTE_DISABLED_NET 0x10 +#define ROUTE_DISABLED_SMTP 0x20 + int flags; + int nerror; + int penalty; + int refcount; + size_t nconn; + time_t lastconn; + time_t lastdisc; + time_t lastpenalty; +}; + +struct mta_limits { + size_t maxconn_per_host; + size_t maxconn_per_route; + size_t maxconn_per_source; + size_t maxconn_per_connector; + size_t maxconn_per_relay; + size_t maxconn_per_domain; + + time_t conndelay_host; + time_t conndelay_route; + time_t conndelay_source; + time_t conndelay_connector; + time_t conndelay_relay; + time_t conndelay_domain; + + time_t discdelay_route; + + size_t max_mail_per_session; + time_t sessdelay_transaction; + time_t sessdelay_keepalive; + + size_t max_failures_per_session; + + int family; + + int task_hiwat; + int task_lowat; + int task_release; +}; + +struct mta_relay { + SPLAY_ENTRY(mta_relay) entry; + uint64_t id; + + struct dispatcher *dispatcher; + struct mta_domain *domain; + struct mta_limits *limits; + int tls; + int flags; + char *backupname; + int backuppref; + char *sourcetable; + uint16_t port; + char *pki_name; + char *ca_name; + char *authtable; + char *authlabel; + char *helotable; + char *heloname; + char *secret; + int srs; + + int state; + size_t ntask; + TAILQ_HEAD(, mta_task) tasks; + + struct tree connectors; + size_t sourceloop; + time_t lastsource; + time_t nextsource; + + int fail; + char *failstr; + +#define RELAY_WAIT_MX 0x01 +#define RELAY_WAIT_PREFERENCE 0x02 +#define RELAY_WAIT_SECRET 0x04 +#define RELAY_WAIT_LIMITS 0x08 +#define RELAY_WAIT_SOURCE 0x10 +#define RELAY_WAIT_CONNECTOR 0x20 +#define RELAY_WAIT_SMARTHOST 0x40 +#define RELAY_WAITMASK 0x7f + int status; + + int refcount; + size_t nconn; + size_t nconn_ready; + time_t lastconn; +}; + +struct mta_envelope { + TAILQ_ENTRY(mta_envelope) entry; + uint64_t id; + uint64_t session; + time_t creation; + char *smtpname; + char *dest; + char *rcpt; + struct mta_task *task; + int delivery; + + int ext; + char *dsn_orcpt; + char dsn_envid[DSN_ENVID_LEN+1]; + uint8_t dsn_notify; + enum dsn_ret dsn_ret; + + char status[LINE_MAX]; +}; + +struct mta_task { + TAILQ_ENTRY(mta_task) entry; + struct mta_relay *relay; + uint32_t msgid; + TAILQ_HEAD(, mta_envelope) envelopes; + char *sender; +}; + +struct passwd; + +struct queue_backend { + int (*init)(struct passwd *, int, const char *); +}; + +struct compress_backend { + size_t (*compress_chunk)(void *, size_t, void *, size_t); + size_t (*uncompress_chunk)(void *, size_t, void *, size_t); + int (*compress_file)(FILE *, FILE *); + int (*uncompress_file)(FILE *, FILE *); +}; + +/* auth structures */ +enum auth_type { + AUTH_BSD, + AUTH_PWD, +}; + +struct auth_backend { + int (*authenticate)(char *, char *); +}; + +struct scheduler_backend { + int (*init)(const char *); + + int (*insert)(struct scheduler_info *); + size_t (*commit)(uint32_t); + size_t (*rollback)(uint32_t); + + int (*update)(struct scheduler_info *); + int (*delete)(uint64_t); + int (*hold)(uint64_t, uint64_t); + int (*release)(int, uint64_t, int); + + int (*batch)(int, int*, size_t*, uint64_t*, int*); + + size_t (*messages)(uint32_t, uint32_t *, size_t); + size_t (*envelopes)(uint64_t, struct evpstate *, size_t); + int (*schedule)(uint64_t); + int (*remove)(uint64_t); + int (*suspend)(uint64_t); + int (*resume)(uint64_t); + int (*query)(uint64_t); +}; + +enum stat_type { + STAT_COUNTER, + STAT_TIMESTAMP, + STAT_TIMEVAL, + STAT_TIMESPEC, +}; + +struct stat_value { + enum stat_type type; + union stat_v { + size_t counter; + time_t timestamp; + struct timeval tv; + struct timespec ts; + } u; +}; + +#define STAT_KEY_SIZE 1024 +struct stat_kv { + void *iter; + char key[STAT_KEY_SIZE]; + struct stat_value val; +}; + +struct stat_backend { + void (*init)(void); + void (*close)(void); + void (*increment)(const char *, size_t); + void (*decrement)(const char *, size_t); + void (*set)(const char *, const struct stat_value *); + int (*iter)(void **, char **, struct stat_value *); +}; + +struct stat_digest { + time_t startup; + time_t timestamp; + + size_t clt_connect; + size_t clt_disconnect; + + size_t evp_enqueued; + size_t evp_dequeued; + + size_t evp_expired; + size_t evp_removed; + size_t evp_bounce; + + size_t dlv_ok; + size_t dlv_permfail; + size_t dlv_tempfail; + size_t dlv_loop; +}; + + +struct mproc { + pid_t pid; + char *name; + int proc; + void (*handler)(struct mproc *, struct imsg *); + struct imsgbuf imsgbuf; + + char *m_buf; + size_t m_alloc; + size_t m_pos; + uint32_t m_type; + uint32_t m_peerid; + pid_t m_pid; + int m_fd; + + int enable; + short events; + struct event ev; + void *data; +}; + +struct msg { + const uint8_t *pos; + const uint8_t *end; +}; + +extern enum smtp_proc_type smtpd_process; + +extern int tracing; +extern int foreground_log; +extern int profiling; + +extern struct mproc *p_control; +extern struct mproc *p_parent; +extern struct mproc *p_lka; +extern struct mproc *p_queue; +extern struct mproc *p_scheduler; +extern struct mproc *p_pony; +extern struct mproc *p_ca; + +extern struct smtpd *env; +extern void (*imsg_callback)(struct mproc *, struct imsg *); + +/* inter-process structures */ + +struct bounce_req_msg { + uint64_t evpid; + time_t timestamp; + struct delivery_bounce bounce; +}; + +enum dns_error { + DNS_OK = 0, + DNS_RETRY, + DNS_EINVAL, + DNS_ENONAME, + DNS_ENOTFOUND, +}; + +enum lka_resp_status { + LKA_OK, + LKA_TEMPFAIL, + LKA_PERMFAIL +}; + +enum filter_type { + FILTER_TYPE_BUILTIN, + FILTER_TYPE_PROC, + FILTER_TYPE_CHAIN, +}; + +enum filter_subsystem { + FILTER_SUBSYSTEM_SMTP_IN = 1<<0, + FILTER_SUBSYSTEM_SMTP_OUT = 1<<1, +}; + +struct filter_proc { + const char *command; + const char *user; + const char *group; + const char *chroot; + int errfd; + enum filter_subsystem filter_subsystem; +}; + +struct filter_config { + char *name; + enum filter_subsystem filter_subsystem; + enum filter_type filter_type; + enum filter_phase phase; + char *reject; + char *disconnect; + char *rewrite; + char *report; + uint8_t junk; + uint8_t bypass; + char *proc; + + const char **chain; + size_t chain_size; + struct dict chain_procs; + + int8_t not_fcrdns; + int8_t fcrdns; + + int8_t not_rdns; + int8_t rdns; + + int8_t not_rdns_table; + struct table *rdns_table; + + int8_t not_rdns_regex; + struct table *rdns_regex; + + int8_t not_src_table; + struct table *src_table; + + int8_t not_src_regex; + struct table *src_regex; + + int8_t not_helo_table; + struct table *helo_table; + + int8_t not_helo_regex; + struct table *helo_regex; + + int8_t not_auth; + int8_t auth; + + int8_t not_auth_table; + struct table *auth_table; + + int8_t not_auth_regex; + struct table *auth_regex; + + int8_t not_mail_from_table; + struct table *mail_from_table; + + int8_t not_mail_from_regex; + struct table *mail_from_regex; + + int8_t not_rcpt_to_table; + struct table *rcpt_to_table; + + int8_t not_rcpt_to_regex; + struct table *rcpt_to_regex; + +}; + +enum filter_status { + FILTER_PROCEED, + FILTER_REWRITE, + FILTER_REJECT, + FILTER_DISCONNECT, + FILTER_JUNK, +}; + +enum ca_resp_status { + CA_OK, + CA_FAIL +}; + +enum mda_resp_status { + MDA_OK, + MDA_TEMPFAIL, + MDA_PERMFAIL +}; + +struct msg_walkinfo { + struct event ev; + uint32_t msgid; + uint32_t peerid; + size_t n_evp; + void *data; + int done; +}; + + +enum dispatcher_type { + DISPATCHER_LOCAL, + DISPATCHER_REMOTE, + DISPATCHER_BOUNCE, +}; + +struct dispatcher_local { + uint8_t is_mbox; /* only for MBOX */ + + uint8_t expand_only; + uint8_t forward_only; + + char *mda_wrapper; + char *command; + + char *table_alias; + char *table_virtual; + char *table_userbase; + + char *user; +}; + +struct dispatcher_remote { + char *helo; + char *helo_source; + + char *source; + + char *ca; + char *pki; + + char *mail_from; + + char *smarthost; + int smarthost_domain; + + char *auth; + int tls_required; + int tls_noverify; + + int backup; + char *backupmx; + + char *filtername; + + int srs; +}; + +struct dispatcher_bounce { +}; + +struct dispatcher { + enum dispatcher_type type; + union dispatcher_agent { + struct dispatcher_local local; + struct dispatcher_remote remote; + struct dispatcher_bounce bounce; + } u; + + time_t ttl; +}; + +struct rule { + TAILQ_ENTRY(rule) r_entry; + + uint8_t reject; + + int8_t flag_tag; + int8_t flag_from; + int8_t flag_for; + int8_t flag_from_rdns; + int8_t flag_from_socket; + + int8_t flag_tag_regex; + int8_t flag_from_regex; + int8_t flag_for_regex; + + int8_t flag_smtp_helo; + int8_t flag_smtp_starttls; + int8_t flag_smtp_auth; + int8_t flag_smtp_mail_from; + int8_t flag_smtp_rcpt_to; + + int8_t flag_smtp_helo_regex; + int8_t flag_smtp_starttls_regex; + int8_t flag_smtp_auth_regex; + int8_t flag_smtp_mail_from_regex; + int8_t flag_smtp_rcpt_to_regex; + + + char *table_tag; + char *table_from; + char *table_for; + + char *table_smtp_helo; + char *table_smtp_auth; + char *table_smtp_mail_from; + char *table_smtp_rcpt_to; + + char *dispatcher; +}; + + +/* aliases.c */ +int aliases_get(struct expand *, const char *); +int aliases_virtual_get(struct expand *, const struct mailaddr *); +int alias_parse(struct expandnode *, const char *); + + +/* auth.c */ +struct auth_backend *auth_backend_lookup(enum auth_type); + + +/* bounce.c */ +void bounce_add(uint64_t); +void bounce_fd(int); + + +/* ca.c */ +int ca(void); +int ca_X509_verify(void *, void *, const char *, const char *, const char **); +void ca_imsg(struct mproc *, struct imsg *); +void ca_init(void); +void ca_engine_init(void); + + +/* cert.c */ +int cert_init(const char *, int, + void (*)(void *, int, const char *, const void *, size_t), void *); +int cert_verify(const void *, const char *, int, void (*)(void *, int), void *); +void cert_dispatch_request(struct mproc *, struct imsg *); +void cert_dispatch_result(struct mproc *, struct imsg *); + + +/* compress_backend.c */ +struct compress_backend *compress_backend_lookup(const char *); +size_t compress_chunk(void *, size_t, void *, size_t); +size_t uncompress_chunk(void *, size_t, void *, size_t); +int compress_file(FILE *, FILE *); +int uncompress_file(FILE *, FILE *); + +/* config.c */ +#define PURGE_LISTENERS 0x01 +#define PURGE_TABLES 0x02 +#define PURGE_RULES 0x04 +#define PURGE_PKI 0x08 +#define PURGE_PKI_KEYS 0x10 +#define PURGE_DISPATCHERS 0x20 +#define PURGE_EVERYTHING 0xff +struct smtpd *config_default(void); +void purge_config(uint8_t); +void config_process(enum smtp_proc_type); +void config_peer(enum smtp_proc_type); + + +/* control.c */ +int control(void); +int control_create_socket(void); + + +/* crypto.c */ +int crypto_setup(const char *, size_t); +int crypto_encrypt_file(FILE *, FILE *); +int crypto_decrypt_file(FILE *, FILE *); +size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t); +size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t); + + +/* dns.c */ +void dns_imsg(struct mproc *, struct imsg *); + + +/* enqueue.c */ +int enqueue(int, char **, FILE *); + + +/* envelope.c */ +void envelope_set_errormsg(struct envelope *, char *, ...); +void envelope_set_esc_class(struct envelope *, enum enhanced_status_class); +void envelope_set_esc_code(struct envelope *, enum enhanced_status_code); +int envelope_load_buffer(struct envelope *, const char *, size_t); +int envelope_dump_buffer(const struct envelope *, char *, size_t); + + +/* expand.c */ +int expand_cmp(struct expandnode *, struct expandnode *); +void expand_insert(struct expand *, struct expandnode *); +struct expandnode *expand_lookup(struct expand *, struct expandnode *); +void expand_clear(struct expand *); +void expand_free(struct expand *); +int expand_line(struct expand *, const char *, int); +int expand_to_text(struct expand *, char *, size_t); +RB_PROTOTYPE(expandtree, expandnode, nodes, expand_cmp); + + +/* forward.c */ +int forwards_get(int, struct expand *); + + +/* limit.c */ +void limit_mta_set_defaults(struct mta_limits *); +int limit_mta_set(struct mta_limits *, const char*, int64_t); + + +/* lka.c */ +int lka(void); + + +/* lka_proc.c */ +int lka_proc_ready(void); +void lka_proc_forked(const char *, uint32_t, int); +void lka_proc_errfd(const char *, int); +struct io *lka_proc_get_io(const char *); + + +/* lka_report.c */ +void lka_report_init(void); +void lka_report_register_hook(const char *, const char *); +void lka_report_smtp_link_connect(const char *, struct timeval *, uint64_t, const char *, int, + const struct sockaddr_storage *, const struct sockaddr_storage *); +void lka_report_smtp_link_disconnect(const char *, struct timeval *, uint64_t); +void lka_report_smtp_link_greeting(const char *, uint64_t, struct timeval *, + const char *); +void lka_report_smtp_link_identify(const char *, struct timeval *, uint64_t, const char *, const char *); +void lka_report_smtp_link_tls(const char *, struct timeval *, uint64_t, const char *); +void lka_report_smtp_link_auth(const char *, struct timeval *, uint64_t, const char *, const char *); +void lka_report_smtp_tx_reset(const char *, struct timeval *, uint64_t, uint32_t); +void lka_report_smtp_tx_begin(const char *, struct timeval *, uint64_t, uint32_t); +void lka_report_smtp_tx_mail(const char *, struct timeval *, uint64_t, uint32_t, const char *, int); +void lka_report_smtp_tx_rcpt(const char *, struct timeval *, uint64_t, uint32_t, const char *, int); +void lka_report_smtp_tx_envelope(const char *, struct timeval *, uint64_t, uint32_t, uint64_t); +void lka_report_smtp_tx_commit(const char *, struct timeval *, uint64_t, uint32_t, size_t); +void lka_report_smtp_tx_data(const char *, struct timeval *, uint64_t, uint32_t, int); +void lka_report_smtp_tx_rollback(const char *, struct timeval *, uint64_t, uint32_t); +void lka_report_smtp_protocol_client(const char *, struct timeval *, uint64_t, const char *); +void lka_report_smtp_protocol_server(const char *, struct timeval *, uint64_t, const char *); +void lka_report_smtp_filter_response(const char *, struct timeval *, uint64_t, + int, int, const char *); +void lka_report_smtp_timeout(const char *, struct timeval *, uint64_t); +void lka_report_filter_report(uint64_t, const char *, int, const char *, + struct timeval *, const char *); +void lka_report_proc(const char *, const char *); + + +/* lka_filter.c */ +void lka_filter_init(void); +void lka_filter_register_hook(const char *, const char *); +void lka_filter_ready(void); +int lka_filter_proc_in_session(uint64_t, const char *); +void lka_filter_begin(uint64_t, const char *); +void lka_filter_end(uint64_t); +void lka_filter_protocol(uint64_t, enum filter_phase, const char *); +void lka_filter_data_begin(uint64_t); +void lka_filter_data_end(uint64_t); +int lka_filter_response(uint64_t, const char *, const char *); + + +/* lka_session.c */ +void lka_session(uint64_t, struct envelope *); +void lka_session_forward_reply(struct forward_req *, int); + + +/* log.c */ +void vlog(int, const char *, va_list); +void logit(int, const char *, ...) __attribute__((format (printf, 2, 3))); + + +/* mda.c */ +void mda_postfork(void); +void mda_postprivdrop(void); +void mda_imsg(struct mproc *, struct imsg *); + + +/* mda_mbox.c */ +void mda_mbox_init(struct deliver *); +void mda_mbox(struct deliver *); + + +/* mda_unpriv.c */ +void mda_unpriv(struct dispatcher *, struct deliver *, const char *, const char *); + + +/* mda_variables.c */ +ssize_t mda_expand_format(char *, size_t, const struct deliver *, + const struct userinfo *, const char *); + + +/* makemap.c */ +int makemap(int, int, char **); + + +/* mailaddr.c */ +int mailaddr_line(struct maddrmap *, const char *); +void maddrmap_init(struct maddrmap *); +void maddrmap_insert(struct maddrmap *, struct maddrnode *); +void maddrmap_free(struct maddrmap *); + + +/* mproc.c */ +int mproc_fork(struct mproc *, const char*, char **); +void mproc_init(struct mproc *, int); +void mproc_clear(struct mproc *); +void mproc_enable(struct mproc *); +void mproc_disable(struct mproc *); +void mproc_event_add(struct mproc *); +void m_compose(struct mproc *, uint32_t, uint32_t, pid_t, int, void *, size_t); +void m_composev(struct mproc *, uint32_t, uint32_t, pid_t, int, + const struct iovec *, int); +void m_forward(struct mproc *, struct imsg *); +void m_create(struct mproc *, uint32_t, uint32_t, pid_t, int); +void m_add(struct mproc *, const void *, size_t); +void m_add_int(struct mproc *, int); +void m_add_u32(struct mproc *, uint32_t); +void m_add_size(struct mproc *, size_t); +void m_add_time(struct mproc *, time_t); +void m_add_timeval(struct mproc *, struct timeval *tv); +void m_add_string(struct mproc *, const char *); +void m_add_data(struct mproc *, const void *, size_t); +void m_add_evpid(struct mproc *, uint64_t); +void m_add_msgid(struct mproc *, uint32_t); +void m_add_id(struct mproc *, uint64_t); +void m_add_sockaddr(struct mproc *, const struct sockaddr *); +void m_add_mailaddr(struct mproc *, const struct mailaddr *); +void m_add_envelope(struct mproc *, const struct envelope *); +void m_add_params(struct mproc *, struct dict *); +void m_close(struct mproc *); +void m_flush(struct mproc *); + +void m_msg(struct msg *, struct imsg *); +int m_is_eom(struct msg *); +void m_end(struct msg *); +void m_get_int(struct msg *, int *); +void m_get_size(struct msg *, size_t *); +void m_get_u32(struct msg *, uint32_t *); +void m_get_time(struct msg *, time_t *); +void m_get_timeval(struct msg *, struct timeval *); +void m_get_string(struct msg *, const char **); +void m_get_data(struct msg *, const void **, size_t *); +void m_get_evpid(struct msg *, uint64_t *); +void m_get_msgid(struct msg *, uint32_t *); +void m_get_id(struct msg *, uint64_t *); +void m_get_sockaddr(struct msg *, struct sockaddr *); +void m_get_mailaddr(struct msg *, struct mailaddr *); +void m_get_envelope(struct msg *, struct envelope *); +void m_get_params(struct msg *, struct dict *); +void m_clear_params(struct dict *); + + +/* mta.c */ +void mta_postfork(void); +void mta_postprivdrop(void); +void mta_imsg(struct mproc *, struct imsg *); +void mta_route_ok(struct mta_relay *, struct mta_route *); +void mta_route_error(struct mta_relay *, struct mta_route *); +void mta_route_down(struct mta_relay *, struct mta_route *); +void mta_route_collect(struct mta_relay *, struct mta_route *); +void mta_source_error(struct mta_relay *, struct mta_route *, const char *); +void mta_delivery_log(struct mta_envelope *, const char *, const char *, int, const char *); +void mta_delivery_notify(struct mta_envelope *); +struct mta_task *mta_route_next_task(struct mta_relay *, struct mta_route *); +const char *mta_host_to_text(struct mta_host *); +const char *mta_relay_to_text(struct mta_relay *); + + +/* mta_session.c */ +void mta_session(struct mta_relay *, struct mta_route *, const char *); +void mta_session_imsg(struct mproc *, struct imsg *); + + +/* parse.y */ +int parse_config(struct smtpd *, const char *, int); +int cmdline_symset(char *); + + +/* queue.c */ +int queue(void); + + +/* queue_backend.c */ +uint32_t queue_generate_msgid(void); +uint64_t queue_generate_evpid(uint32_t); +int queue_init(const char *, int); +int queue_close(void); +int queue_message_create(uint32_t *); +int queue_message_delete(uint32_t); +int queue_message_commit(uint32_t); +int queue_message_fd_r(uint32_t); +int queue_message_fd_rw(uint32_t); +int queue_envelope_create(struct envelope *); +int queue_envelope_delete(uint64_t); +int queue_envelope_load(uint64_t, struct envelope *); +int queue_envelope_update(struct envelope *); +int queue_envelope_walk(struct envelope *); +int queue_message_walk(struct envelope *, uint32_t, int *, void **); + + +/* report_smtp.c */ +void report_smtp_link_connect(const char *, uint64_t, const char *, int, + const struct sockaddr_storage *, const struct sockaddr_storage *); +void report_smtp_link_disconnect(const char *, uint64_t); +void report_smtp_link_greeting(const char *, uint64_t, const char *); +void report_smtp_link_identify(const char *, uint64_t, const char *, const char *); +void report_smtp_link_tls(const char *, uint64_t, const char *); +void report_smtp_link_auth(const char *, uint64_t, const char *, const char *); +void report_smtp_tx_reset(const char *, uint64_t, uint32_t); +void report_smtp_tx_begin(const char *, uint64_t, uint32_t); +void report_smtp_tx_mail(const char *, uint64_t, uint32_t, const char *, int); +void report_smtp_tx_rcpt(const char *, uint64_t, uint32_t, const char *, int); +void report_smtp_tx_envelope(const char *, uint64_t, uint32_t, uint64_t); +void report_smtp_tx_data(const char *, uint64_t, uint32_t, int); +void report_smtp_tx_commit(const char *, uint64_t, uint32_t, size_t); +void report_smtp_tx_rollback(const char *, uint64_t, uint32_t); +void report_smtp_protocol_client(const char *, uint64_t, const char *); +void report_smtp_protocol_server(const char *, uint64_t, const char *); +void report_smtp_filter_response(const char *, uint64_t, int, int, const char *); +void report_smtp_timeout(const char *, uint64_t); + + +/* ruleset.c */ +struct rule *ruleset_match(const struct envelope *); + + +/* scheduler.c */ +int scheduler(void); + + +/* scheduler_bakend.c */ +struct scheduler_backend *scheduler_backend_lookup(const char *); +void scheduler_info(struct scheduler_info *, struct envelope *); + + +/* pony.c */ +int pony(void); +void pony_imsg(struct mproc *, struct imsg *); + + +/* resolver.c */ +void resolver_getaddrinfo(const char *, const char *, const struct addrinfo *, + void(*)(void *, int, struct addrinfo*), void *); +void resolver_getnameinfo(const struct sockaddr *, int, + void(*)(void *, int, const char *, const char *), void *); +void resolver_res_query(const char *, int, int, + void (*cb)(void *, int, int, int, const void *, int), void *); +void resolver_dispatch_request(struct mproc *, struct imsg *); +void resolver_dispatch_result(struct mproc *, struct imsg *); + + +/* smtp.c */ +void smtp_postfork(void); +void smtp_postprivdrop(void); +void smtp_imsg(struct mproc *, struct imsg *); +void smtp_configure(void); +void smtp_collect(void); + + +/* smtp_session.c */ +int smtp_session(struct listener *, int, const struct sockaddr_storage *, + const char *, struct io *); +void smtp_session_imsg(struct mproc *, struct imsg *); + + +/* smtpf_session.c */ +int smtpf_session(struct listener *, int, const struct sockaddr_storage *, + const char *); +void smtpf_session_imsg(struct mproc *, struct imsg *); + + +/* smtpd.c */ +void imsg_dispatch(struct mproc *, struct imsg *); +const char *proc_name(enum smtp_proc_type); +const char *proc_title(enum smtp_proc_type); +const char *imsg_to_str(int); +void log_imsg(int, int, struct imsg *); +int fork_proc_backend(const char *, const char *, const char *); + + +/* srs.c */ +const char *srs_encode(const char *, const char *); +const char *srs_decode(const char *); + + +/* ssl_smtpd.c */ +void *ssl_mta_init(void *, char *, off_t, const char *); +void *ssl_smtp_init(void *, int); + + +/* stat_backend.c */ +struct stat_backend *stat_backend_lookup(const char *); +void stat_increment(const char *, size_t); +void stat_decrement(const char *, size_t); +void stat_set(const char *, const struct stat_value *); +struct stat_value *stat_counter(size_t); +struct stat_value *stat_timestamp(time_t); +struct stat_value *stat_timeval(struct timeval *); +struct stat_value *stat_timespec(struct timespec *); + + +/* table.c */ +struct table *table_find(struct smtpd *, const char *); +struct table *table_create(struct smtpd *, const char *, const char *, + const char *); +int table_config(struct table *); +int table_open(struct table *); +int table_update(struct table *); +void table_close(struct table *); +void table_dump(struct table *); +int table_check_use(struct table *, uint32_t, uint32_t); +int table_check_type(struct table *, uint32_t); +int table_check_service(struct table *, uint32_t); +int table_match(struct table *, enum table_service, const char *); +int table_lookup(struct table *, enum table_service, const char *, + union lookup *); +int table_fetch(struct table *, enum table_service, union lookup *); +void table_destroy(struct smtpd *, struct table *); +void table_add(struct table *, const char *, const char *); +int table_domain_match(const char *, const char *); +int table_netaddr_match(const char *, const char *); +int table_mailaddr_match(const char *, const char *); +int table_regex_match(const char *, const char *); +void table_open_all(struct smtpd *); +void table_dump_all(struct smtpd *); +void table_close_all(struct smtpd *); + + +/* to.c */ +int email_to_mailaddr(struct mailaddr *, char *); +int text_to_netaddr(struct netaddr *, const char *); +int text_to_mailaddr(struct mailaddr *, const char *); +int text_to_relayhost(struct relayhost *, const char *); +int text_to_userinfo(struct userinfo *, const char *); +int text_to_credentials(struct credentials *, const char *); +int text_to_expandnode(struct expandnode *, const char *); +uint64_t text_to_evpid(const char *); +uint32_t text_to_msgid(const char *); +const char *sa_to_text(const struct sockaddr *); +const char *ss_to_text(const struct sockaddr_storage *); +const char *time_to_text(time_t); +const char *duration_to_text(time_t); +const char *rule_to_text(struct rule *); +const char *sockaddr_to_text(struct sockaddr *); +const char *mailaddr_to_text(const struct mailaddr *); +const char *expandnode_to_text(struct expandnode *); + + +/* util.c */ +typedef struct arglist arglist; +struct arglist { + char **list; + uint num; + uint nalloc; +}; +void addargs(arglist *, char *, ...) + __attribute__((format(printf, 2, 3))); +int bsnprintf(char *, size_t, const char *, ...) + __attribute__((format (printf, 3, 4))); +int safe_fclose(FILE *); +int hostname_match(const char *, const char *); +int mailaddr_match(const struct mailaddr *, const struct mailaddr *); +int valid_localpart(const char *); +int valid_domainpart(const char *); +int valid_domainname(const char *); +int valid_smtp_response(const char *); +int secure_file(int, char *, char *, uid_t, int); +int lowercase(char *, const char *, size_t); +void xlowercase(char *, const char *, size_t); +int uppercase(char *, const char *, size_t); +uint64_t generate_uid(void); +int availdesc(void); +int ckdir(const char *, mode_t, uid_t, gid_t, int); +int rmtree(char *, int); +int mvpurge(char *, char *); +int mktmpfile(void); +const char *parse_smtp_response(char *, size_t, char **, int *); +int xasprintf(char **, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void *xmalloc(size_t); +void *xcalloc(size_t, size_t); +char *xstrdup(const char *); +void *xmemdup(const void *, size_t); +char *strip(char *); +int io_xprint(struct io *, const char *); +int io_xprintf(struct io *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void log_envelope(const struct envelope *, const char *, const char *, + const char *); +int session_socket_error(int); +int getmailname(char *, size_t); +int base64_encode(unsigned char const *, size_t, char *, size_t); +int base64_decode(char const *, unsigned char *, size_t); +int base64_encode_rfc3548(unsigned char const *, size_t, + char *, size_t); +void xclosefrom(int); + +void log_trace_verbose(int); +void log_trace(int, const char *, ...) + __attribute__((format (printf, 2, 3))); + +/* waitq.c */ +int waitq_wait(void *, void (*)(void *, void *, void *), void *); +void waitq_run(void *, void *); + + +/* runq.c */ +struct runq; + +int runq_init(struct runq **, void (*)(struct runq *, void *)); +int runq_schedule(struct runq *, time_t, void *); +int runq_schedule_at(struct runq *, time_t, void *); +int runq_cancel(struct runq *, void *); +int runq_pending(struct runq *, void *, time_t *); diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile new file mode 100644 index 00000000..34a4dc74 --- /dev/null +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -0,0 +1,102 @@ +# $OpenBSD: Makefile,v 1.91 2018/06/03 14:04:06 gilles Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= smtpd + +SRCS= aliases.c +SRCS+= bounce.c +SRCS+= ca.c +SRCS+= cert.c +SRCS+= compress_backend.c +SRCS+= config.c +SRCS+= control.c +SRCS+= crypto.c +SRCS+= dict.c +SRCS+= dns.c +SRCS+= unpack_dns.c +SRCS+= envelope.c +SRCS+= esc.c +SRCS+= expand.c +SRCS+= forward.c +SRCS+= iobuf.c +SRCS+= ioev.c +SRCS+= limit.c +SRCS+= lka.c +SRCS+= lka_filter.c +SRCS+= lka_session.c +SRCS+= log.c +SRCS+= mailaddr.c +SRCS+= mda.c +SRCS+= mda_mbox.c +SRCS+= mda_unpriv.c +SRCS+= mda_variables.c +SRCS+= mproc.c +SRCS+= mta.c +SRCS+= mta_session.c +SRCS+= parse.y +SRCS+= pony.c +SRCS+= proxy.c +SRCS+= queue.c +SRCS+= queue_backend.c +SRCS+= report_smtp.c +SRCS+= resolver.c +SRCS+= ruleset.c +SRCS+= runq.c +SRCS+= scheduler.c +SRCS+= scheduler_backend.c +SRCS+= smtp.c +SRCS+= smtp_session.c +SRCS+= smtpd.c +SRCS+= srs.c +SRCS+= ssl.c +SRCS+= ssl_smtpd.c +SRCS+= ssl_verify.c +SRCS+= stat_backend.c +SRCS+= table.c +SRCS+= to.c +SRCS+= tree.c +SRCS+= util.c +SRCS+= waitq.c + +# RFC parsers +SRCS+= rfc5322.c + +# backends +SRCS+= compress_gzip.c + +SRCS+= table_db.c +SRCS+= table_getpwnam.c +SRCS+= table_proc.c +SRCS+= table_static.c + +SRCS+= queue_fs.c +SRCS+= queue_null.c +SRCS+= queue_proc.c +SRCS+= queue_ram.c + +SRCS+= scheduler_ramqueue.c +SRCS+= scheduler_null.c +SRCS+= scheduler_proc.c + +SRCS+= stat_ramstat.c + +MAN= sendmail.8 smtpd.8 smtpd.conf.5 table.5 +BINDIR= /usr/sbin + +LDADD+= -levent -lutil -lssl -lcrypto -lm -lz +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} + +CFLAGS+= -fstack-protector-all +CFLAGS+= -I${.CURDIR}/.. +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -Werror-implicit-function-declaration +#CFLAGS+= -Werror # during development phase (breaks some archs) +CFLAGS+= -DIO_TLS +CFLAGS+= -DQUEUE_PROFILING +YFLAGS= + +.include diff --git a/usr.sbin/smtpd/spfwalk.c b/usr.sbin/smtpd/spfwalk.c new file mode 100644 index 00000000..0832d1bc --- /dev/null +++ b/usr.sbin/smtpd/spfwalk.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2017 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include + +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd-defines.h" +#include "smtpd-api.h" +#include "unpack_dns.h" +#include "parser.h" + +struct target { + void (*dispatch)(struct dns_rr *, struct target *); + int cidr4; + int cidr6; +}; + +int spfwalk(int, struct parameter *); + +static void dispatch_txt(struct dns_rr *, struct target *); +static void dispatch_mx(struct dns_rr *, struct target *); +static void dispatch_a(struct dns_rr *, struct target *); +static void dispatch_aaaa(struct dns_rr *, struct target *); +static void lookup_record(int, const char *, struct target *); +static void dispatch_record(struct asr_result *, void *); +static ssize_t parse_txt(const char *, size_t, char *, size_t); +static int parse_target(char *, struct target *); +void *xmalloc(size_t size); + +int ip_v4 = 0; +int ip_v6 = 0; +int ip_both = 1; + +struct dict seen; + +int +spfwalk(int argc, struct parameter *argv) +{ + struct target tgt; + const char *ip_family = NULL; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + if (argv) + ip_family = argv[0].u.u_str; + + if (ip_family) { + if (strcmp(ip_family, "-4") == 0) { + ip_both = 0; + ip_v4 = 1; + } else if (strcmp(ip_family, "-6") == 0) { + ip_both = 0; + ip_v6 = 1; + } else + errx(1, "invalid ip_family"); + } + + dict_init(&seen); + event_init(); + + tgt.cidr4 = tgt.cidr6 = -1; + tgt.dispatch = dispatch_txt; + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + while (linelen-- > 0 && isspace((unsigned char)line[linelen])) + line[linelen] = '\0'; + + if (linelen > 0) + lookup_record(T_TXT, line, &tgt); + } + + free(line); + +#if HAVE_PLEDGE + if (pledge("dns stdio", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + + return 0; +} + +void +lookup_record(int type, const char *record, struct target *tgt) +{ + struct asr_query *as; + struct target *ntgt; + + as = res_query_async(record, C_IN, type, NULL); + if (as == NULL) + err(1, "res_query_async"); + ntgt = xmalloc(sizeof(*ntgt)); + *ntgt = *tgt; + event_asr_run(as, dispatch_record, (void *)ntgt); +} + +void +dispatch_record(struct asr_result *ar, void *arg) +{ + struct target *tgt = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + + /* best effort */ + if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA) + goto end; + + unpack_init(&pack, ar->ar_data, ar->ar_datalen); + unpack_header(&pack, &h); + unpack_query(&pack, &q); + + for (; h.ancount; h.ancount--) { + unpack_rr(&pack, &rr); + /**/ + tgt->dispatch(&rr, tgt); + } +end: + free(tgt); +} + +void +dispatch_txt(struct dns_rr *rr, struct target *tgt) +{ + char buf[4096]; + char *argv[512]; + char buf2[512]; + struct target ltgt; + struct in6_addr ina; + char **ap = argv; + char *in = buf; + char *record, *end; + ssize_t n; + + if (rr->rr_type != T_TXT) + return; + n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf)); + if (n == -1 || n == sizeof(buf)) + return; + buf[n] = '\0'; + + if (strncasecmp("v=spf1 ", buf, 7)) + return; + + while ((*ap = strsep(&in, " ")) != NULL) { + if (strcasecmp(*ap, "v=spf1") == 0) + continue; + + end = *ap + strlen(*ap)-1; + if (*end == '.') + *end = '\0'; + + if (dict_set(&seen, *ap, &seen)) + continue; + + if (**ap == '-' || **ap == '~') + continue; + + if (**ap == '+' || **ap == '?') + (*ap)++; + + ltgt.cidr4 = ltgt.cidr6 = -1; + + if (strncasecmp("ip4:", *ap, 4) == 0) { + if ((ip_v4 == 1 || ip_both == 1) && + inet_net_pton(AF_INET, *(ap) + 4, + &ina, sizeof(ina)) != -1) + printf("%s\n", *(ap) + 4); + continue; + } + if (strncasecmp("ip6:", *ap, 4) == 0) { + if ((ip_v6 == 1 || ip_both == 1) && + inet_net_pton(AF_INET6, *(ap) + 4, + &ina, sizeof(ina)) != -1) + printf("%s\n", *(ap) + 4); + continue; + } + if (strcasecmp("a", *ap) == 0) { + print_dname(rr->rr_dname, buf2, sizeof(buf2)); + buf2[strlen(buf2) - 1] = '\0'; + ltgt.dispatch = dispatch_a; + lookup_record(T_A, buf2, <gt); + ltgt.dispatch = dispatch_aaaa; + lookup_record(T_AAAA, buf2, <gt); + continue; + } + if (strncasecmp("a:", *ap, 2) == 0) { + record = *(ap) + 2; + if (parse_target(record, <gt) < 0) + continue; + ltgt.dispatch = dispatch_a; + lookup_record(T_A, record, <gt); + ltgt.dispatch = dispatch_aaaa; + lookup_record(T_AAAA, record, <gt); + continue; + } + if (strncasecmp("exists:", *ap, 7) == 0) { + ltgt.dispatch = dispatch_a; + lookup_record(T_A, *(ap) + 7, <gt); + continue; + } + if (strncasecmp("include:", *ap, 8) == 0) { + ltgt.dispatch = dispatch_txt; + lookup_record(T_TXT, *(ap) + 8, <gt); + continue; + } + if (strncasecmp("redirect=", *ap, 9) == 0) { + ltgt.dispatch = dispatch_txt; + lookup_record(T_TXT, *(ap) + 9, <gt); + continue; + } + if (strcasecmp("mx", *ap) == 0) { + print_dname(rr->rr_dname, buf2, sizeof(buf2)); + buf2[strlen(buf2) - 1] = '\0'; + ltgt.dispatch = dispatch_mx; + lookup_record(T_MX, buf2, <gt); + continue; + } + if (strncasecmp("mx:", *ap, 3) == 0) { + record = *(ap) + 3; + if (parse_target(record, <gt) < 0) + continue; + ltgt.dispatch = dispatch_mx; + lookup_record(T_MX, record, <gt); + continue; + } + } + *ap = NULL; +} + +void +dispatch_mx(struct dns_rr *rr, struct target *tgt) +{ + char buf[512]; + struct target ltgt; + + if (rr->rr_type != T_MX) + return; + + print_dname(rr->rr.mx.exchange, buf, sizeof(buf)); + buf[strlen(buf) - 1] = '\0'; + if (buf[strlen(buf) - 1] == '.') + buf[strlen(buf) - 1] = '\0'; + + ltgt = *tgt; + ltgt.dispatch = dispatch_a; + lookup_record(T_A, buf, <gt); + ltgt.dispatch = dispatch_aaaa; + lookup_record(T_AAAA, buf, <gt); +} + +void +dispatch_a(struct dns_rr *rr, struct target *tgt) +{ + char buffer[512]; + const char *ptr; + + if (rr->rr_type != T_A) + return; + + if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr, + buffer, sizeof buffer))) { + if (tgt->cidr4 >= 0) + printf("%s/%d\n", ptr, tgt->cidr4); + else + printf("%s\n", ptr); + } +} + +void +dispatch_aaaa(struct dns_rr *rr, struct target *tgt) +{ + char buffer[512]; + const char *ptr; + + if (rr->rr_type != T_AAAA) + return; + + if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6, + buffer, sizeof buffer))) { + if (tgt->cidr6 >= 0) + printf("%s/%d\n", ptr, tgt->cidr6); + else + printf("%s\n", ptr); + } +} + +ssize_t +parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz) +{ + size_t len; + ssize_t r = 0; + + while (rdatalen) { + len = *(const unsigned char *)rdata; + if (len >= rdatalen) { + errno = EINVAL; + return -1; + } + + rdata++; + rdatalen--; + + if (len == 0) + continue; + + if (len >= dstsz) { + errno = EOVERFLOW; + return -1; + } + memmove(dst, rdata, len); + dst += len; + dstsz -= len; + + rdata += len; + rdatalen -= len; + r += len; + } + + return r; +} + +int +parse_target(char *record, struct target *tgt) +{ + const char *err; + char *m4, *m6; + + m4 = record; + strsep(&m4, "/"); + if (m4 == NULL) + return 0; + + m6 = m4; + strsep(&m6, "/"); + + if (*m4) { + tgt->cidr4 = strtonum(m4, 0, 32, &err); + if (err) + return tgt->cidr4 = -1; + } + + if (m6 == NULL) + return 0; + + tgt->cidr6 = strtonum(m6, 0, 128, &err); + if (err) + return tgt->cidr6 = -1; + + return 0; +} diff --git a/usr.sbin/smtpd/srs.c b/usr.sbin/smtpd/srs.c new file mode 100644 index 00000000..bb4f4d9e --- /dev/null +++ b/usr.sbin/smtpd/srs.c @@ -0,0 +1,379 @@ +/* $OpenBSD: srs.c,v 1.3 2019/09/29 10:03:49 gilles Exp $ */ + +/* + * Copyright (c) 2019 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "smtpd.h" +#include "log.h" + +static uint8_t base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +static int +minrange(uint16_t tref, uint16_t t2, int drift, int mod) +{ + if (tref > drift) { + /* t2 must fall in between tref and tref - drift */ + if (t2 <= tref && t2>= tref - drift) + return 1; + } + else { + /* t2 must fall in between 0 and tref, or wrap */ + if (t2 <= tref || t2 >= mod - (drift - tref)) + return 1; + } + return 0; +} + +static int +maxrange(uint16_t tref, uint16_t t2, int drift, int mod) +{ + if (tref + drift < 1024) { + /* t2 must fall in between tref and tref + drift */ + if (t2 >= tref && t2 <= tref + drift) + return 1; + } + else { + /* t2 must fall in between tref + drift, or wrap */ + if (t2 >= tref || t2 <= (tref + drift) % 1024) + return 1; + } + return 0; +} + +static int +timestamp_check_range(uint16_t tref, uint16_t t2) +{ + if (! minrange(tref, t2, env->sc_srs_ttl, 1024) && + ! maxrange(tref, t2, 1, 1024)) + return 0; + + return 1; +} + +static const unsigned char * +srs_hash(const char *key, const char *value) +{ + SHA_CTX c; + static unsigned char md[SHA_DIGEST_LENGTH]; + + SHA1_Init(&c); + SHA1_Update(&c, key, strlen(key)); + SHA1_Update(&c, value, strlen(value)); + SHA1_Final(md, &c); + return md; +} + +static const char * +srs0_encode(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + uint16_t timestamp; + int ret; + + /* compute 10 bits timestamp according to spec */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* TT==@ */ + ret = snprintf(tmp, sizeof tmp, "%c%c=%s=%s@%s", + base32[(timestamp>>5) & 0x1F], + base32[timestamp & 0x1F], + maddr.domain, maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* compute HHHH */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS0=HHHH= prefix */ + ret = snprintf(dest, sizeof dest, "SRS0=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +static const char * +srs1_encode_srs0(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + int ret; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* ==@ */ + ret = snprintf(tmp, sizeof tmp, "%s==%s@%s", + maddr.domain, maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* compute HHHH */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS1=HHHH= prefix */ + ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +static const char * +srs1_encode_srs1(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + int ret; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* @ */ + ret = snprintf(tmp, sizeof tmp, "%s@%s", maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* sanity check: there's at least room for a checksum + * with allowed delimiter =, + or - + */ + if (strlen(tmp) < 5) + return sender; + if (tmp[4] != '=' && tmp[4] != '+' && tmp[4] != '-') + return sender; + + /* compute HHHH */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp + 5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS1=HHHH= prefix skipping previous hops' HHHH */ + ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp + 5); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +const char * +srs_encode(const char *sender, const char *rcpt_domain) +{ + if (strncasecmp(sender, "SRS0=", 5) == 0) + return srs1_encode_srs0(sender+5, rcpt_domain); + if (strncasecmp(sender, "SRS1=", 5) == 0) + return srs1_encode_srs1(sender+5, rcpt_domain); + return srs0_encode(sender, rcpt_domain); +} + +static const char * +srs0_decode(const char *rcpt) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + char *p; + uint8_t *idx; + int ret; + uint16_t timestamp, srs_timestamp; + + /* sanity check: we have room for a checksum and delimiter */ + if (strlen(rcpt) < 5) + return NULL; + + /* compute checksum */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* compare prefix checksum with computed checksum */ + if (strncmp(md, rcpt, 4) != 0) { + if (env->sc_srs_key_backup == NULL) + return NULL; + base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), + SHA_DIGEST_LENGTH, md, sizeof md); + if (strncmp(md, rcpt, 4) != 0) + return NULL; + } + rcpt += 5; + + /* sanity check: we have room for a timestamp and delimiter */ + if (strlen(rcpt) < 3) + return NULL; + + /* decode timestamp */ + if ((idx = strchr(base32, rcpt[0])) == NULL) + return NULL; + srs_timestamp = ((idx - base32) << 5); + + if ((idx = strchr(base32, rcpt[1])) == NULL) + return NULL; + srs_timestamp |= (idx - base32); + rcpt += 3; + + /* compute current 10 bits timestamp */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* check that SRS timestamp isn't too far from current */ + if (timestamp != srs_timestamp) + if (! timestamp_check_range(timestamp, srs_timestamp)) + return NULL; + + if (! text_to_mailaddr(&maddr, rcpt)) + return NULL; + + /* sanity check: we have at least one SRS separator */ + if ((p = strchr(maddr.user, '=')) == NULL) + return NULL; + *p++ = '\0'; + + /* maddr.user holds "domain\0user", with p pointing at user */ + ret = snprintf(dest, sizeof dest, "%s@%s", p, maddr.user); + if (ret == -1 || ret >= (int)sizeof dest) + return NULL; + + return dest; +} + +static const char * +srs1_decode(const char *rcpt) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + char *p; + uint8_t *idx; + int ret; + uint16_t timestamp, srs_timestamp; + + /* sanity check: we have room for a checksum and delimiter */ + if (strlen(rcpt) < 5) + return NULL; + + /* compute checksum */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* compare prefix checksum with computed checksum */ + if (strncmp(md, rcpt, 4) != 0) { + if (env->sc_srs_key_backup == NULL) + return NULL; + base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), + SHA_DIGEST_LENGTH, md, sizeof md); + if (strncmp(md, rcpt, 4) != 0) + return NULL; + } + rcpt += 5; + + if (! text_to_mailaddr(&maddr, rcpt)) + return NULL; + + /* sanity check: we have at least one SRS separator */ + if ((p = strchr(maddr.user, '=')) == NULL) + return NULL; + *p++ = '\0'; + + /* maddr.user holds "domain\0user", with p pointing at user */ + ret = snprintf(dest, sizeof dest, "SRS0%s@%s", p, maddr.user); + if (ret == -1 || ret >= (int)sizeof dest) + return NULL; + + + /* we're ready to return decoded address, but let's check if + * SRS0 timestamp is valid. + */ + + /* first, get rid of SRS0 checksum (=HHHH=), we can't check it */ + if (strlen(p) < 6) + return NULL; + p += 6; + + /* we should be pointing to a timestamp, check that we're indeed */ + if (strlen(p) < 3) + return NULL; + if (p[2] != '=' && p[2] != '+' && p[2] != '-') + return NULL; + p[2] = '\0'; + + if ((idx = strchr(base32, p[0])) == NULL) + return NULL; + srs_timestamp = ((idx - base32) << 5); + + if ((idx = strchr(base32, p[1])) == NULL) + return NULL; + srs_timestamp |= (idx - base32); + + /* compute current 10 bits timestamp */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* check that SRS timestamp isn't too far from current */ + if (timestamp != srs_timestamp) + if (! timestamp_check_range(timestamp, srs_timestamp)) + return NULL; + + return dest; +} + +const char * +srs_decode(const char *rcpt) +{ + if (strncasecmp(rcpt, "SRS0=", 5) == 0) + return srs0_decode(rcpt + 5); + if (strncasecmp(rcpt, "SRS1=", 5) == 0) + return srs1_decode(rcpt + 5); + + return NULL; +} diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c new file mode 100644 index 00000000..a37d6fea --- /dev/null +++ b/usr.sbin/smtpd/ssl.c @@ -0,0 +1,458 @@ +/* $OpenBSD: ssl.c,v 1.93 2019/06/05 06:40:13 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008 Reyk Floeter + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "ssl.h" + +void +ssl_init(void) +{ + static int inited = 0; + + if (inited) + return; + + SSL_library_init(); + SSL_load_error_strings(); + + OpenSSL_add_all_algorithms(); + + /* Init hardware crypto engines. */ + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); + inited = 1; +} + +int +ssl_setup(SSL_CTX **ctxp, struct pki *pki, + int (*sni_cb)(SSL *,int *,void *), const char *ciphers) +{ + SSL_CTX *ctx; + uint8_t sid[SSL_MAX_SID_CTX_LENGTH]; + + ctx = ssl_ctx_create(pki->pki_name, pki->pki_cert, pki->pki_cert_len, ciphers); + + /* + * Set session ID context to a random value. We don't support + * persistent caching of sessions so it is OK to set a temporary + * session ID context that is valid during run time. + */ + arc4random_buf(sid, sizeof(sid)); + if (!SSL_CTX_set_session_id_context(ctx, sid, sizeof(sid))) + goto err; + + if (sni_cb) + SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb); + + SSL_CTX_set_dh_auto(ctx, 0); + + SSL_CTX_set_ecdh_auto(ctx, 1); + + *ctxp = ctx; + return 1; + +err: + SSL_CTX_free(ctx); + ssl_error("ssl_setup"); + return 0; +} + +char * +ssl_load_file(const char *name, off_t *len, mode_t perm) +{ + struct stat st; + off_t size; + char *buf = NULL; + int fd, saved_errno; + char mode[12]; + + if ((fd = open(name, O_RDONLY)) == -1) + return (NULL); + if (fstat(fd, &st) != 0) + goto fail; + if (st.st_uid != 0) { + log_warnx("warn: %s: not owned by uid 0", name); + errno = EACCES; + goto fail; + } + if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) { + strmode(perm, mode); + log_warnx("warn: %s: insecure permissions: must be at most %s", + name, &mode[1]); + errno = EACCES; + goto fail; + } + size = st.st_size; + if ((buf = calloc(1, size + 1)) == NULL) + goto fail; + if (read(fd, buf, size) != size) + goto fail; + close(fd); + + *len = size + 1; + return (buf); + +fail: + free(buf); + saved_errno = errno; + close(fd); + errno = saved_errno; + return (NULL); +} + +#if 0 +static int +ssl_password_cb(char *buf, int size, int rwflag, void *u) +{ + size_t len; + if (u == NULL) { + explicit_bzero(buf, size); + return (0); + } + if ((len = strlcpy(buf, u, size)) >= (size_t)size) + return (0); + return (len); +} +#endif + +static int +ssl_password_cb(char *buf, int size, int rwflag, void *u) +{ + int ret = 0; + size_t len; + char *pass; + + pass = getpass((const char *)u); + if (pass == NULL) + return 0; + len = strlen(pass); + if (strlcpy(buf, pass, size) >= (size_t)size) + goto end; + ret = len; +end: + if (len) + explicit_bzero(pass, len); + return ret; +} + +char * +ssl_load_key(const char *name, off_t *len, char *pass, mode_t perm, const char *pkiname) +{ + FILE *fp = NULL; + EVP_PKEY *key = NULL; + BIO *bio = NULL; + long size; + char *data, *buf, *filebuf; + struct stat st; + char mode[12]; + char prompt[2048]; + + /* Initialize SSL library once */ + ssl_init(); + + /* + * Read (possibly) encrypted key from file + */ + if ((fp = fopen(name, "r")) == NULL) + return (NULL); + if ((filebuf = malloc_conceal(BUFSIZ)) == NULL) + goto fail; + setvbuf(fp, filebuf, _IOFBF, BUFSIZ); + + if (fstat(fileno(fp), &st) != 0) + goto fail; + if (st.st_uid != 0) { + log_warnx("warn: %s: not owned by uid 0", name); + errno = EACCES; + goto fail; + } + if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) { + strmode(perm, mode); + log_warnx("warn: %s: insecure permissions: must be at most %s", + name, &mode[1]); + errno = EACCES; + goto fail; + } + + (void)snprintf(prompt, sizeof prompt, "passphrase for %s: ", pkiname); + key = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, prompt); + fclose(fp); + fp = NULL; + freezero(filebuf, BUFSIZ); + filebuf = NULL; + if (key == NULL) + goto fail; + /* + * Write unencrypted key to memory buffer + */ + if ((bio = BIO_new(BIO_s_mem())) == NULL) + goto fail; + if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) + goto fail; + if ((size = BIO_get_mem_data(bio, &data)) <= 0) + goto fail; + if ((buf = calloc_conceal(1, size + 1)) == NULL) + goto fail; + memcpy(buf, data, size); + + BIO_free_all(bio); + EVP_PKEY_free(key); + + *len = (off_t)size + 1; + return (buf); + +fail: + ssl_error("ssl_load_key"); + BIO_free_all(bio); + EVP_PKEY_free(key); + if (fp) + fclose(fp); + freezero(filebuf, BUFSIZ); + return (NULL); +} + +SSL_CTX * +ssl_ctx_create(const char *pkiname, char *cert, off_t cert_len, const char *ciphers) +{ + SSL_CTX *ctx; + size_t pkinamelen = 0; + + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: could not create SSL context"); + } + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_timeout(ctx, SSL_SESSION_TIMEOUT); + SSL_CTX_set_options(ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TICKET); + SSL_CTX_set_options(ctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION); + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (ciphers == NULL) + ciphers = SSL_CIPHERS; + if (!SSL_CTX_set_cipher_list(ctx, ciphers)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: could not set cipher list"); + } + + if (cert != NULL) { + if (pkiname != NULL) + pkinamelen = strlen(pkiname) + 1; + if (!SSL_CTX_use_certificate_chain_mem(ctx, cert, cert_len)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: invalid certificate chain"); + } else if (!ssl_ctx_fake_private_key(ctx, + pkiname, pkinamelen, cert, cert_len, NULL, NULL)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: could not fake private key"); + } else if (!SSL_CTX_check_private_key(ctx)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: invalid private key"); + } + } + + return (ctx); +} + +int +ssl_load_certificate(struct pki *p, const char *pathname) +{ + p->pki_cert = ssl_load_file(pathname, &p->pki_cert_len, 0755); + if (p->pki_cert == NULL) + return 0; + return 1; +} + +int +ssl_load_keyfile(struct pki *p, const char *pathname, const char *pkiname) +{ + char pass[1024]; + + p->pki_key = ssl_load_key(pathname, &p->pki_key_len, pass, 0740, pkiname); + if (p->pki_key == NULL) + return 0; + return 1; +} + +int +ssl_load_cafile(struct ca *c, const char *pathname) +{ + c->ca_cert = ssl_load_file(pathname, &c->ca_cert_len, 0755); + if (c->ca_cert == NULL) + return 0; + return 1; +} + +const char * +ssl_to_text(const SSL *ssl) +{ + static char buf[256]; + + (void)snprintf(buf, sizeof buf, "%s:%s:%d", + SSL_get_version(ssl), + SSL_get_cipher_name(ssl), + SSL_get_cipher_bits(ssl, NULL)); + + return (buf); +} + +void +ssl_error(const char *where) +{ + unsigned long code; + char errbuf[128]; + + for (; (code = ERR_get_error()) != 0 ;) { + ERR_error_string_n(code, errbuf, sizeof(errbuf)); + log_debug("debug: SSL library error: %s: %s", where, errbuf); + } +} + +int +ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, + X509 **x509ptr, EVP_PKEY **pkeyptr) +{ + BIO *in; + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + EC_KEY *eckey = NULL; + void *exdata = NULL; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_BUF_LIB); + return (0); + } + + if ((x509 = PEM_read_bio_X509(in, NULL, + ssl_password_cb, NULL)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_PEM_LIB); + goto fail; + } + + if ((pkey = X509_get_pubkey(x509)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_X509_LIB); + goto fail; + } + + BIO_free(in); + in = NULL; + + if (data != NULL && datalen) { + if (((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL && + (eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) || + (exdata = malloc(datalen)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_EVP_LIB); + goto fail; + } + + memcpy(exdata, data, datalen); + if (rsa) + RSA_set_ex_data(rsa, 0, exdata); +#if defined(SUPPORT_ECDSA) + if (eckey) + ECDSA_set_ex_data(eckey, 0, exdata); +#endif + RSA_free(rsa); /* dereference, will be cleaned up with pkey */ +#if defined(SUPPORT_ECDSA) + EC_KEY_free(eckey); /* dereference, will be cleaned up with pkey */ +#endif + } + + *x509ptr = x509; + *pkeyptr = pkey; + + return (1); + + fail: + RSA_free(rsa); + EC_KEY_free(eckey); + BIO_free(in); + EVP_PKEY_free(pkey); + X509_free(x509); + free(exdata); + + return (0); +} + +int +ssl_ctx_fake_private_key(SSL_CTX *ctx, const void *data, size_t datalen, + char *buf, off_t len, X509 **x509ptr, EVP_PKEY **pkeyptr) +{ + int ret = 0; + EVP_PKEY *pkey = NULL; + X509 *x509 = NULL; + + if (!ssl_load_pkey(data, datalen, buf, len, &x509, &pkey)) + return (0); + + /* + * Use the public key as the "private" key - the secret key + * parameters are hidden in an extra process that will be + * contacted by the RSA engine. The SSL/TLS library needs at + * least the public key parameters in the current process. + */ + ret = SSL_CTX_use_PrivateKey(ctx, pkey); + if (!ret) + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_LIB_SSL); + + if (pkeyptr != NULL) + *pkeyptr = pkey; + else + EVP_PKEY_free(pkey); + + if (x509ptr != NULL) + *x509ptr = x509; + else + X509_free(x509); + + return (ret); +} diff --git a/usr.sbin/smtpd/ssl.h b/usr.sbin/smtpd/ssl.h new file mode 100644 index 00000000..11c80c68 --- /dev/null +++ b/usr.sbin/smtpd/ssl.h @@ -0,0 +1,71 @@ +/* $OpenBSD: ssl.h,v 1.21 2019/09/18 11:26:30 eric Exp $ */ +/* + * Copyright (c) 2013 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define SSL_CIPHERS "HIGH:!aNULL:!MD5" +#define SSL_SESSION_TIMEOUT 300 + +struct pki { + char pki_name[HOST_NAME_MAX+1]; + + char *pki_cert_file; + char *pki_cert; + off_t pki_cert_len; + + char *pki_key_file; + char *pki_key; + off_t pki_key_len; + + EVP_PKEY *pki_pkey; + + int pki_dhe; +}; + +struct ca { + char ca_name[HOST_NAME_MAX+1]; + + char *ca_cert_file; + char *ca_cert; + off_t ca_cert_len; +}; + + +/* ssl.c */ +void ssl_init(void); +int ssl_setup(SSL_CTX **, struct pki *, + int (*)(SSL *, int *, void *), const char *); +SSL_CTX *ssl_ctx_create(const char *, char *, off_t, const char *); +int ssl_cmp(struct pki *, struct pki *); +char *ssl_load_file(const char *, off_t *, mode_t); +char *ssl_load_key(const char *, off_t *, char *, mode_t, const char *); + +const char *ssl_to_text(const SSL *); +void ssl_error(const char *); + +int ssl_load_certificate(struct pki *, const char *); +int ssl_load_keyfile(struct pki *, const char *, const char *); +int ssl_load_cafile(struct ca *, const char *); +int ssl_load_pkey(const void *, size_t, char *, off_t, + X509 **, EVP_PKEY **); +int ssl_ctx_fake_private_key(SSL_CTX *, const void *, size_t, + char *, off_t, X509 **, EVP_PKEY **); + +/* ssl_privsep.c */ +int ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); +int SSL_CTX_use_certificate_chain_mem(SSL_CTX *, void *, int); + +/* ssl_verify.c */ +int ssl_check_name(X509 *, const char *, int *); diff --git a/usr.sbin/smtpd/ssl_smtpd.c b/usr.sbin/smtpd/ssl_smtpd.c new file mode 100644 index 00000000..4e5b7e75 --- /dev/null +++ b/usr.sbin/smtpd/ssl_smtpd.c @@ -0,0 +1,105 @@ +/* $OpenBSD: ssl_smtpd.c,v 1.13 2015/12/30 16:02:08 benno Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard + * Copyright (c) 2008 Reyk Floeter + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + + +void * +ssl_mta_init(void *pkiname, char *cert, off_t cert_len, const char *ciphers) +{ + SSL_CTX *ctx = NULL; + SSL *ssl = NULL; + + ctx = ssl_ctx_create(pkiname, cert, cert_len, ciphers); + + if ((ssl = SSL_new(ctx)) == NULL) + goto err; + if (!SSL_set_ssl_method(ssl, SSLv23_client_method())) + goto err; + + SSL_CTX_free(ctx); + return (void *)(ssl); + +err: + SSL_free(ssl); + SSL_CTX_free(ctx); + ssl_error("ssl_mta_init"); + return (NULL); +} + +/* dummy_verify */ +static int +dummy_verify(int ok, X509_STORE_CTX *store) +{ + /* + * We *want* SMTP to request an optional client certificate, however we don't want the + * verification to take place in the SMTP process. This dummy verify will allow us to + * asynchronously verify in the lookup process. + */ + return 1; +} + +void * +ssl_smtp_init(void *ssl_ctx, int verify) +{ + SSL *ssl = NULL; + + log_debug("debug: session_start_ssl: switching to SSL"); + + if (verify) + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, dummy_verify); + + if ((ssl = SSL_new(ssl_ctx)) == NULL) + goto err; + if (!SSL_set_ssl_method(ssl, SSLv23_server_method())) + goto err; + + return (void *)(ssl); + +err: + SSL_free(ssl); + ssl_error("ssl_smtp_init"); + return (NULL); +} diff --git a/usr.sbin/smtpd/ssl_verify.c b/usr.sbin/smtpd/ssl_verify.c new file mode 100644 index 00000000..2e784b97 --- /dev/null +++ b/usr.sbin/smtpd/ssl_verify.c @@ -0,0 +1,297 @@ +/* $OpenBSD: ssl_verify.c,v 1.2 2019/11/02 03:16:45 gilles Exp $ */ +/* + * Copyright (c) 2014 Jeremie Courreges-Anglas + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Adapted from lib/libtls/tls_verify.c */ + +#include "includes.h" + +#include + +#include +#include + +#include +#include + +#include + +#if 0 +#include +#include "tls_internal.h" +#endif + +#include "ssl.h" +#include "log.h" + +struct tls; +#define tls_set_errorx(ctx, ...) log_warnx(__VA_ARGS__) +union tls_addr { + struct in_addr in; + struct in6_addr in6; +}; + +static int +tls_match_name(const char *cert_name, const char *name) +{ + const char *cert_domain, *domain, *next_dot; + + if (strcasecmp(cert_name, name) == 0) + return 0; + + /* Wildcard match? */ + if (cert_name[0] == '*') { + /* + * Valid wildcards: + * - "*.domain.tld" + * - "*.sub.domain.tld" + * - etc. + * Reject "*.tld". + * No attempt to prevent the use of eg. "*.co.uk". + */ + cert_domain = &cert_name[1]; + /* Disallow "*" */ + if (cert_domain[0] == '\0') + return -1; + /* Disallow "*foo" */ + if (cert_domain[0] != '.') + return -1; + /* Disallow "*.." */ + if (cert_domain[1] == '.') + return -1; + next_dot = strchr(&cert_domain[1], '.'); + /* Disallow "*.bar" */ + if (next_dot == NULL) + return -1; + /* Disallow "*.bar.." */ + if (next_dot[1] == '.') + return -1; + + domain = strchr(name, '.'); + + /* No wildcard match against a name with no host part. */ + if (name[0] == '.') + return -1; + /* No wildcard match against a name with no domain part. */ + if (domain == NULL || strlen(domain) == 1) + return -1; + + if (strcasecmp(cert_domain, domain) == 0) + return 0; + } + + return -1; +} + +/* + * See RFC 5280 section 4.2.1.6 for SubjectAltName details. + * alt_match is set to 1 if a matching alternate name is found. + * alt_exists is set to 1 if any known alternate name exists in the certificate. + */ +static int +tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name, + int *alt_match, int *alt_exists) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + union tls_addr addrbuf; + int addrlen, type; + int count, i; + int rv = 0; + + *alt_match = 0; + *alt_exists = 0; + + altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, + NULL, NULL); + if (altname_stack == NULL) + return 0; + + if (inet_pton(AF_INET, name, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 4; + } else if (inet_pton(AF_INET6, name, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 16; + } else { + type = GEN_DNS; + addrlen = 0; + } + + count = sk_GENERAL_NAME_num(altname_stack); + for (i = 0; i < count; i++) { + GENERAL_NAME *altname; + + altname = sk_GENERAL_NAME_value(altname_stack, i); + + if (altname->type == GEN_DNS || altname->type == GEN_IPADD) + *alt_exists = 1; + + if (altname->type != type) + continue; + + if (type == GEN_DNS) { + const unsigned char *data; + int format, len; + + format = ASN1_STRING_type(altname->d.dNSName); + if (format == V_ASN1_IA5STRING) { + data = ASN1_STRING_get0_data(altname->d.dNSName); + len = ASN1_STRING_length(altname->d.dNSName); + + if (len < 0 || (size_t)len != strlen(data)) { + tls_set_errorx(ctx, + "error verifying name '%s': " + "NUL byte in subjectAltName, " + "probably a malicious certificate", + name); + rv = -1; + break; + } + + /* + * Per RFC 5280 section 4.2.1.6: + * " " is a legal domain name, but that + * dNSName must be rejected. + */ + if (strcmp(data, " ") == 0) { + tls_set_errorx(ctx, + "error verifying name '%s': " + "a dNSName of \" \" must not be " + "used", name); + rv = -1; + break; + } + + if (tls_match_name(data, name) == 0) { + *alt_match = 1; + break; + } + } else { +#ifdef DEBUG + fprintf(stdout, "%s: unhandled subjectAltName " + "dNSName encoding (%d)\n", getprogname(), + format); +#endif + } + + } else if (type == GEN_IPADD) { + const unsigned char *data; + int datalen; + + datalen = ASN1_STRING_length(altname->d.iPAddress); + data = ASN1_STRING_get0_data(altname->d.iPAddress); + + if (datalen < 0) { + tls_set_errorx(ctx, + "Unexpected negative length for an " + "IP address: %d", datalen); + rv = -1; + break; + } + + /* + * Per RFC 5280 section 4.2.1.6: + * IPv4 must use 4 octets and IPv6 must use 16 octets. + */ + if (datalen == addrlen && + memcmp(data, &addrbuf, addrlen) == 0) { + *alt_match = 1; + break; + } + } + } + + sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free); + return rv; +} + +static int +tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, + int *cn_match) +{ + X509_NAME *subject_name; + char *common_name = NULL; + union tls_addr addrbuf; + int common_name_len; + int rv = 0; + + *cn_match = 0; + + subject_name = X509_get_subject_name(cert); + if (subject_name == NULL) + goto done; + + common_name_len = X509_NAME_get_text_by_NID(subject_name, + NID_commonName, NULL, 0); + if (common_name_len < 0) + goto done; + + common_name = calloc(common_name_len + 1, 1); + if (common_name == NULL) + goto done; + + X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, + common_name_len + 1); + + /* NUL bytes in CN? */ + if (common_name_len < 0 || + (size_t)common_name_len != strlen(common_name)) { + tls_set_errorx(ctx, "error verifying name '%s': " + "NUL byte in Common Name field, " + "probably a malicious certificate", name); + rv = -1; + goto done; + } + + /* + * We don't want to attempt wildcard matching against IP addresses, + * so perform a simple comparison here. + */ + if (inet_pton(AF_INET, name, &addrbuf) == 1 || + inet_pton(AF_INET6, name, &addrbuf) == 1) { + if (strcmp(common_name, name) == 0) + *cn_match = 1; + goto done; + } + + if (tls_match_name(common_name, name) == 0) + *cn_match = 1; + + done: + free(common_name); + return rv; +} + +int +ssl_check_name(X509 *cert, const char *name, int *match) +{ + int alt_exists; + + *match = 0; + + if (tls_check_subject_altname(NULL, cert, name, match, + &alt_exists) == -1) + return -1; + + /* + * As per RFC 6125 section 6.4.4, if any known alternate name existed + * in the certificate, we do not attempt to match on the CN. + */ + if (*match || alt_exists) + return 0; + + return tls_check_common_name(NULL, cert, name, match); +} diff --git a/usr.sbin/smtpd/stat_backend.c b/usr.sbin/smtpd/stat_backend.c new file mode 100644 index 00000000..30cb299b --- /dev/null +++ b/usr.sbin/smtpd/stat_backend.c @@ -0,0 +1,124 @@ +/* $OpenBSD: stat_backend.c,v 1.11 2018/12/27 10:35:26 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "log.h" +#include "smtpd.h" + +extern struct stat_backend stat_backend_ramstat; + +struct stat_backend * +stat_backend_lookup(const char *name) +{ + return &stat_backend_ramstat; +} + +void +stat_increment(const char *key, size_t count) +{ + struct stat_value *value; + + if (count == 0) + return; + + value = stat_counter(count); + + m_create(p_control, IMSG_STAT_INCREMENT, 0, 0, -1); + m_add_string(p_control, key); + m_add_data(p_control, value, sizeof(*value)); + m_close(p_control); +} + +void +stat_decrement(const char *key, size_t count) +{ + struct stat_value *value; + + if (count == 0) + return; + + value = stat_counter(count); + + m_create(p_control, IMSG_STAT_DECREMENT, 0, 0, -1); + m_add_string(p_control, key); + m_add_data(p_control, value, sizeof(*value)); + m_close(p_control); +} + +void +stat_set(const char *key, const struct stat_value *value) +{ + m_create(p_control, IMSG_STAT_SET, 0, 0, -1); + m_add_string(p_control, key); + m_add_data(p_control, value, sizeof(*value)); + m_close(p_control); +} + +/* helpers */ + +struct stat_value * +stat_counter(size_t counter) +{ + static struct stat_value value; + + value.type = STAT_COUNTER; + value.u.counter = counter; + return &value; +} + +struct stat_value * +stat_timestamp(time_t timestamp) +{ + static struct stat_value value; + + value.type = STAT_TIMESTAMP; + value.u.timestamp = timestamp; + return &value; +} + +struct stat_value * +stat_timeval(struct timeval *tv) +{ + static struct stat_value value; + + value.type = STAT_TIMEVAL; + value.u.tv = *tv; + return &value; +} + +struct stat_value * +stat_timespec(struct timespec *ts) +{ + static struct stat_value value; + + value.type = STAT_TIMESPEC; + value.u.ts = *ts; + return &value; +} diff --git a/usr.sbin/smtpd/stat_ramstat.c b/usr.sbin/smtpd/stat_ramstat.c new file mode 100644 index 00000000..bbf1541a --- /dev/null +++ b/usr.sbin/smtpd/stat_ramstat.c @@ -0,0 +1,162 @@ +/* $OpenBSD: stat_ramstat.c,v 1.11 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + + +static void ramstat_init(void); +static void ramstat_close(void); +static void ramstat_increment(const char *, size_t); +static void ramstat_decrement(const char *, size_t); +static void ramstat_set(const char *, const struct stat_value *); +static int ramstat_iter(void **, char **, struct stat_value *); + +struct ramstat_entry { + RB_ENTRY(ramstat_entry) entry; + char key[STAT_KEY_SIZE]; + struct stat_value value; +}; +RB_HEAD(stats_tree, ramstat_entry) stats; +RB_PROTOTYPE(stats_tree, ramstat_entry, entry, ramstat_entry_cmp); + +struct stat_backend stat_backend_ramstat = { + ramstat_init, + ramstat_close, + ramstat_increment, + ramstat_decrement, + ramstat_set, + ramstat_iter +}; + +static void +ramstat_init(void) +{ + log_trace(TRACE_STAT, "ramstat: init"); + + RB_INIT(&stats); + + /* ramstat_set() should be called for each key we want + * to have displayed by smtpctl show stats at startup. + */ + ramstat_set("uptime", stat_timestamp(env->sc_uptime)); +} + +static void +ramstat_close(void) +{ + log_trace(TRACE_STAT, "ramstat: close"); +} + +static void +ramstat_increment(const char *name, size_t val) +{ + struct ramstat_entry *np, lk; + + log_trace(TRACE_STAT, "ramstat: increment: %s", name); + (void)strlcpy(lk.key, name, sizeof (lk.key)); + np = RB_FIND(stats_tree, &stats, &lk); + if (np == NULL) { + np = xcalloc(1, sizeof *np); + (void)strlcpy(np->key, name, sizeof (np->key)); + RB_INSERT(stats_tree, &stats, np); + } + log_trace(TRACE_STAT, "ramstat: %s (%p): %zd -> %zd", + name, name, np->value.u.counter, np->value.u.counter + val); + np->value.u.counter += val; +} + +static void +ramstat_decrement(const char *name, size_t val) +{ + struct ramstat_entry *np, lk; + + log_trace(TRACE_STAT, "ramstat: decrement: %s", name); + (void)strlcpy(lk.key, name, sizeof (lk.key)); + np = RB_FIND(stats_tree, &stats, &lk); + if (np == NULL) { + np = xcalloc(1, sizeof *np); + (void)strlcpy(np->key, name, sizeof (np->key)); + RB_INSERT(stats_tree, &stats, np); + } + log_trace(TRACE_STAT, "ramstat: %s (%p): %zd -> %zd", + name, name, np->value.u.counter, np->value.u.counter - val); + np->value.u.counter -= val; +} + +static void +ramstat_set(const char *name, const struct stat_value *val) +{ + struct ramstat_entry *np, lk; + + log_trace(TRACE_STAT, "ramstat: set: %s", name); + (void)strlcpy(lk.key, name, sizeof (lk.key)); + np = RB_FIND(stats_tree, &stats, &lk); + if (np == NULL) { + np = xcalloc(1, sizeof *np); + (void)strlcpy(np->key, name, sizeof (np->key)); + RB_INSERT(stats_tree, &stats, np); + } + log_trace(TRACE_STAT, "ramstat: %s: n/a -> n/a", name); + np->value = *val; +} + +static int +ramstat_iter(void **iter, char **name, struct stat_value *val) +{ + struct ramstat_entry *np; + + log_trace(TRACE_STAT, "ramstat: iter"); + if (RB_EMPTY(&stats)) + return 0; + + if (*iter == NULL) + np = RB_MIN(stats_tree, &stats); + else + np = RB_NEXT(stats_tree, &stats, *iter); + + *iter = np; + if (np == NULL) + return 0; + + *name = np->key; + *val = np->value; + return 1; +} + + +static int +ramstat_entry_cmp(struct ramstat_entry *e1, struct ramstat_entry *e2) +{ + return strcmp(e1->key, e2->key); +} + +RB_GENERATE(stats_tree, ramstat_entry, entry, ramstat_entry_cmp); diff --git a/usr.sbin/smtpd/table.5 b/usr.sbin/smtpd/table.5 new file mode 100644 index 00000000..e9d4fa4b --- /dev/null +++ b/usr.sbin/smtpd/table.5 @@ -0,0 +1,258 @@ +.\" $OpenBSD: table.5,v 1.11 2019/08/11 13:00:57 gilles Exp $ +.\" +.\" Copyright (c) 2013 Eric Faurot +.\" Copyright (c) 2013 Gilles Chehade +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.Dd $Mdocdate: August 11 2019 $ +.Dt TABLE 5 +.Os +.Sh NAME +.Nm table +.Nd format description for smtpd tables +.Sh DESCRIPTION +This manual page documents the file format for the various tables used in the +.Xr smtpd 8 +mail daemon. +.Pp +The format described here applies to tables as defined in +.Xr smtpd.conf 5 . +.Sh TABLE TYPES +There are two types of tables: lists and mappings. +A list consists of a series of values, +while a mapping consists of a series of keys and their associated values. +The following illustrates how to declare them as static tables: +.Bd -literal -offset indent +table mylist { value1, value2, value3 } +table mymapping { key1 = value1, key2 = value2, key3 = value3 } +.Ed +.Pp +When using a +.Ql file +table, a list will be written with each value on a line by itself. +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +.Bd -literal -offset indent +value1 +value2 +value3 +.Ed +.Pp +A mapping will be written with each key and value on a line, +whitespaces separating both columns: +.Bd -literal -offset indent +key1 value1 +key2 value2 +key3 value3 +.Ed +.Pp +A file table can be converted to a Berkeley database using the +.Xr makemap 8 +utility with no syntax change. +.Pp +Tables using a +.Ql file +or Berkeley DB backend will be referenced as follows: +.Bd -unfilled -offset indent +.Ic table Ar name Cm file : Ns Pa /path/to/file +.Ic table Ar name Cm db : Ns Pa /path/to/file.db +.Ed +.Ss Aliasing tables +Aliasing tables are mappings that associate a recipient to one or many +destinations. +They can be used in two contexts: primary domain aliases and virtual domain +mapping. +.Bd -unfilled -offset indent +.Ic action Ar name method Cm alias Pf < table Ns > +.Ic action Ar name method Cm virtual Pf < table Ns > +.Ed +.Pp +In a primary domain context, the key is the user part of the recipient address, +whilst the value is one or many recipients as described in +.Xr aliases 5 : +.Bd -literal -offset indent +user1 otheruser +user2 otheruser1,otheruser2 +user3 otheruser@example.com +.Ed +.Pp +In a virtual domain context, the key is either a user part, a full email +address or a catch all, following selection rules described in +.Xr smtpd.conf 5 , +and the value is one or many recipients as described in +.Xr aliases 5 : +.Bd -literal -offset indent +user1 otheruser +user2@example.org otheruser1,otheruser2 +@example.org otheruser@example.com +@ catchall@example.com +.Ed +.Pp +The following directive shares the same table format, +but with a different meaning. +Here, the user is allowed to send mail from the listed addresses: +.Bd -unfilled -offset indent +.Ic listen on Ar interface Cm auth Oo Ar ... Oc Cm senders Pf < Ar table Ns > +.Ed +.Ss Domain tables +Domain tables are simple lists of domains or hosts. +.Bd -unfilled -offset indent +.Ic match Cm for domain Pf < table Ns > Cm action Ar name +.Ic match Cm helo Pf < table Ns > Oo Ar ... Oc Cm action Ar name +.Ed +.Pp +In that context, the list of domains will be matched against the recipient +domain or against the HELO name advertised by the sending host, +respectively. +For +.Ql static , +.Ql file +and +.Xr dbopen 3 +backends, a wildcard may be used so the domain table may contain: +.Bd -literal -offset indent +example.org +*.example.org +.Ed +.Ss Credentials tables +Credentials tables are mappings of credentials. +They can be used in two contexts: +.Bd -unfilled -offset indent +.Ic listen on Ar interface Cm tls Oo Ar ... Oc Cm auth Pf < Ar table Ns > +.Ic action Ar name Cm relay host Ar relay-url Cm auth Pf < Ar table Ns > +.Ed +.Pp +In a listener context, the credentials are a mapping of username and encrypted +passwords: +.Bd -literal -offset indent +user1 $2b$10$hIJ4QfMcp.90nJwKqGbKM.MybArjHOTpEtoTV.DgLYAiThuoYmTSe +user2 $2b$10$bwSmUOBGcZGamIfRuXGTvuTo3VLbPG9k5yeKNMBtULBhksV5KdGsK +.Ed +.Pp +The passwords are to be encrypted using the +.Xr smtpctl 8 +encrypt subcommand. +.Pp +In a relay context, the credentials are a mapping of labels and +username:password pairs: +.Bd -literal -offset indent +label1 user:password +.Ed +.Pp +The label must be unique and is used as a selector for the proper credentials +when multiple credentials are valid for a single destination. +The password is not encrypted as it must be provided to the remote host. +.Ss Netaddr tables +Netaddr tables are lists of IPv4 and IPv6 network addresses. +They can only be used in the following context: +.Pp +.D1 Ic match Cm from src Pf < Ar table Ns > Cm action Ar name +.Pp +When used as a "from source", the address of a client is compared to the list +of addresses in the table until a match is found. +.Pp +A netaddr table can contain exact addresses or netmasks, and looks as follow: +.Bd -literal -offset indent +192.168.1.1 +::1 +ipv6:::1 +192.168.1.0/24 +.Ed +.Ss Userinfo tables +User info tables are used in rule context to specify an alternate user base, +mapping virtual users to local system users by UID, GID and home directory. +.Pp +.D1 Ic action Ar name method Cm userbase Pf < Ar table Ns > +.Pp +A userinfo table looks as follows: +.Bd -literal -offset indent +joe 1000:100:/home/virtual/joe +jack 1000:100:/home/virtual/jack +.Ed +.Pp +In this example, both joe and jack are virtual users mapped to the local +system user with UID 1000 and GID 100, but different home directories. +These directories may contain a +.Xr forward 5 +file. +This can be used in conjunction with an alias table +that maps an email address or the domain part to the desired virtual +username. +For example: +.Bd -literal -offset indent +joe@example.org joe +jack@example.com jack +.Ed +.Ss Source tables +Source tables are lists of IPv4 and IPv6 addresses. +They can only be used in the following context: +.Pp +.D1 Ic action Ar name Cm relay src Pf < Ar table Ns > +.Pp +Successive queries to the source table will return the elements one by one. +.Pp +A source table looks as follow: +.Bd -literal -offset indent +192.168.1.2 +192.168.1.3 +::1 +::2 +ipv6:::3 +ipv6:::4 +.Ed +.Ss Mailaddr tables +Mailaddr tables are lists of email addresses. +They can be used in the following contexts: +.Bd -unfilled -offset indent +.Ic match Cm mail\-from Pf < Ar table Ns > Cm action Ar name +.Ic match Cm rcpt\-to Pf < Ar table Ns > Cm action Ar name +.Ed +.Pp +A mailaddr entry is used to match an email address against a username, +a domain or a full email address. +A "*" wildcard may be used in part of the domain name. +.Pp +A mailaddr table looks as follow: +.Bd -literal -offset indent +user +@domain +user@domain +user@*.domain +.Ed +.Ss Addrname tables +Addrname tables are used to map IP addresses to hostnames. +They can be used in both listen context and relay context: +.Bd -unfilled -offset indent +.Ic listen on Ar interface Cm hostnames Pf < Ar table Ns > +.Ic action Ar name Cm relay helo\-src Pf < Ar table Ns > +.Ed +.Pp +In listen context, the table is used to look up the server name to advertise +depending on the local address of the socket on which a connection is accepted. +In relay context, the table is used to determine the hostname for the HELO +sequence of the SMTP protocol, depending on the local address used for the +outgoing connection. +.Pp +The format is a mapping from inet4 or inet6 addresses to hostnames: +.Bd -literal -offset indent +::1 localhost +127.0.0.1 localhost +88.190.23.165 www.opensmtpd.org +.Ed +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr makemap 8 , +.Xr smtpd 8 diff --git a/usr.sbin/smtpd/table.c b/usr.sbin/smtpd/table.c new file mode 100644 index 00000000..469eeee1 --- /dev/null +++ b/usr.sbin/smtpd/table.c @@ -0,0 +1,709 @@ +/* $OpenBSD: table.c,v 1.48 2019/01/10 07:40:52 eric Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2008 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +struct table_backend *table_backend_lookup(const char *); + +extern struct table_backend table_backend_static; +#ifdef HAVE_DB_API +extern struct table_backend table_backend_db; +#endif +extern struct table_backend table_backend_getpwnam; +extern struct table_backend table_backend_proc; + +static const char * table_service_name(enum table_service); +static int table_parse_lookup(enum table_service, const char *, const char *, + union lookup *); +static int parse_sockaddr(struct sockaddr *, int, const char *); + +static unsigned int last_table_id = 0; + +static struct table_backend *backends[] = { + &table_backend_static, +#ifdef HAVE_DB_API + &table_backend_db, +#endif + &table_backend_getpwnam, + &table_backend_proc, + NULL +}; + +struct table_backend * +table_backend_lookup(const char *backend) +{ + int i; + + if (!strcmp(backend, "file")) + backend = "static"; + + for (i = 0; backends[i]; i++) + if (!strcmp(backends[i]->name, backend)) + return (backends[i]); + + return NULL; +} + +static const char * +table_service_name(enum table_service s) +{ + switch (s) { + case K_NONE: return "NONE"; + case K_ALIAS: return "ALIAS"; + case K_DOMAIN: return "DOMAIN"; + case K_CREDENTIALS: return "CREDENTIALS"; + case K_NETADDR: return "NETADDR"; + case K_USERINFO: return "USERINFO"; + case K_SOURCE: return "SOURCE"; + case K_MAILADDR: return "MAILADDR"; + case K_ADDRNAME: return "ADDRNAME"; + case K_MAILADDRMAP: return "MAILADDRMAP"; + case K_RELAYHOST: return "RELAYHOST"; + case K_STRING: return "STRING"; + case K_REGEX: return "REGEX"; + } + return "???"; +} + +struct table * +table_find(struct smtpd *conf, const char *name) +{ + return dict_get(conf->sc_tables_dict, name); +} + +int +table_match(struct table *table, enum table_service kind, const char *key) +{ + return table_lookup(table, kind, key, NULL); +} + +int +table_lookup(struct table *table, enum table_service kind, const char *key, + union lookup *lk) +{ + char lkey[1024], *buf = NULL; + int r; + + r = -1; + if (table->t_backend->lookup == NULL) + errno = ENOTSUP; + else if (!lowercase(lkey, key, sizeof lkey)) { + log_warnx("warn: lookup key too long: %s", key); + errno = EINVAL; + } + else + r = table->t_backend->lookup(table, kind, lkey, lk ? &buf : NULL); + + if (r == 1) { + log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s%s", + lk ? "lookup" : "match", + key, + table_service_name(kind), + table->t_backend->name, + table->t_name, + lk ? "\"" : "", + lk ? buf : "true", + lk ? "\"" : ""); + if (buf) + r = table_parse_lookup(kind, lkey, buf, lk); + } + else + log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s", + lk ? "lookup" : "match", + key, + table_service_name(kind), + table->t_backend->name, + table->t_name, + (r == -1) ? "error: " : (lk ? "none" : "false"), + (r == -1) ? strerror(errno) : ""); + + free(buf); + + return (r); +} + +int +table_fetch(struct table *table, enum table_service kind, union lookup *lk) +{ + char *buf = NULL; + int r; + + r = -1; + if (table->t_backend->fetch == NULL) + errno = ENOTSUP; + else + r = table->t_backend->fetch(table, kind, &buf); + + if (r == 1) { + log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> \"%s\"", + table_service_name(kind), + table->t_backend->name, + table->t_name, + buf); + r = table_parse_lookup(kind, NULL, buf, lk); + } + else + log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> %s%s", + table_service_name(kind), + table->t_backend->name, + table->t_name, + (r == -1) ? "error: " : "none", + (r == -1) ? strerror(errno) : ""); + + free(buf); + + return (r); +} + +struct table * +table_create(struct smtpd *conf, const char *backend, const char *name, + const char *config) +{ + struct table *t; + struct table_backend *tb; + char path[LINE_MAX]; + size_t n; + struct stat sb; + + if (name && table_find(conf, name)) + fatalx("table_create: table \"%s\" already defined", name); + + if ((tb = table_backend_lookup(backend)) == NULL) { + if ((size_t)snprintf(path, sizeof(path), PATH_LIBEXEC"/table-%s", + backend) >= sizeof(path)) { + fatalx("table_create: path too long \"" + PATH_LIBEXEC"/table-%s\"", backend); + } + if (stat(path, &sb) == 0) { + tb = table_backend_lookup("proc"); + (void)strlcpy(path, backend, sizeof(path)); + if (config) { + (void)strlcat(path, ":", sizeof(path)); + if (strlcat(path, config, sizeof(path)) + >= sizeof(path)) + fatalx("table_create: config file path too long"); + } + config = path; + } + } + + if (tb == NULL) + fatalx("table_create: backend \"%s\" does not exist", backend); + + t = xcalloc(1, sizeof(*t)); + t->t_backend = tb; + + if (config) { + if (strlcpy(t->t_config, config, sizeof t->t_config) + >= sizeof t->t_config) + fatalx("table_create: table config \"%s\" too large", + t->t_config); + } + + if (strcmp(tb->name, "static") != 0) + t->t_type = T_DYNAMIC; + + if (name == NULL) + (void)snprintf(t->t_name, sizeof(t->t_name), "", + last_table_id++); + else { + n = strlcpy(t->t_name, name, sizeof(t->t_name)); + if (n >= sizeof(t->t_name)) + fatalx("table_create: table name too long"); + } + + dict_set(conf->sc_tables_dict, t->t_name, t); + + return (t); +} + +void +table_destroy(struct smtpd *conf, struct table *t) +{ + dict_xpop(conf->sc_tables_dict, t->t_name); + free(t); +} + +int +table_config(struct table *t) +{ + if (t->t_backend->config == NULL) + return (1); + return (t->t_backend->config(t)); +} + +void +table_add(struct table *t, const char *key, const char *val) +{ + if (t->t_backend->add == NULL) + fatalx("table_add: cannot add to table"); + + if (t->t_backend->add(t, key, val) == 0) + log_warnx("warn: failed to add \"%s\" in table \"%s\"", key, t->t_name); +} + +void +table_dump(struct table *t) +{ + const char *type; + char buf[LINE_MAX]; + + switch(t->t_type) { + case T_NONE: + type = "NONE"; + break; + case T_DYNAMIC: + type = "DYNAMIC"; + break; + case T_LIST: + type = "LIST"; + break; + case T_HASH: + type = "HASH"; + break; + default: + type = "???"; + break; + } + + if (t->t_config[0]) + snprintf(buf, sizeof(buf), " config=\"%s\"", t->t_config); + else + buf[0] = '\0'; + + log_debug("TABLE \"%s\" backend=%s type=%s%s", t->t_name, + t->t_backend->name, type, buf); + + if (t->t_backend->dump) + t->t_backend->dump(t); +} + +int +table_check_type(struct table *t, uint32_t mask) +{ + return t->t_type & mask; +} + +int +table_check_service(struct table *t, uint32_t mask) +{ + return t->t_backend->services & mask; +} + +int +table_check_use(struct table *t, uint32_t tmask, uint32_t smask) +{ + return table_check_type(t, tmask) && table_check_service(t, smask); +} + +int +table_open(struct table *t) +{ + if (t->t_backend->open == NULL) + return (1); + return (t->t_backend->open(t)); +} + +void +table_close(struct table *t) +{ + if (t->t_backend->close) + t->t_backend->close(t); +} + +int +table_update(struct table *t) +{ + if (t->t_backend->update == NULL) + return (1); + return (t->t_backend->update(t)); +} + + +/* + * quick reminder: + * in *_match() s1 comes from session, s2 comes from table + */ + +int +table_domain_match(const char *s1, const char *s2) +{ + return hostname_match(s1, s2); +} + +int +table_mailaddr_match(const char *s1, const char *s2) +{ + struct mailaddr m1; + struct mailaddr m2; + + if (!text_to_mailaddr(&m1, s1)) + return 0; + if (!text_to_mailaddr(&m2, s2)) + return 0; + return mailaddr_match(&m1, &m2); +} + +static int table_match_mask(struct sockaddr_storage *, struct netaddr *); +static int table_inet4_match(struct sockaddr_in *, struct netaddr *); +static int table_inet6_match(struct sockaddr_in6 *, struct netaddr *); + +int +table_netaddr_match(const char *s1, const char *s2) +{ + struct netaddr n1; + struct netaddr n2; + + if (strcasecmp(s1, s2) == 0) + return 1; + if (!text_to_netaddr(&n1, s1)) + return 0; + if (!text_to_netaddr(&n2, s2)) + return 0; + if (n1.ss.ss_family != n2.ss.ss_family) + return 0; + if (SS_LEN(&n1.ss) != SS_LEN(&n2.ss)) + return 0; + return table_match_mask(&n1.ss, &n2); +} + +static int +table_match_mask(struct sockaddr_storage *ss, struct netaddr *ssmask) +{ + if (ss->ss_family == AF_INET) + return table_inet4_match((struct sockaddr_in *)ss, ssmask); + + if (ss->ss_family == AF_INET6) + return table_inet6_match((struct sockaddr_in6 *)ss, ssmask); + + return (0); +} + +static int +table_inet4_match(struct sockaddr_in *ss, struct netaddr *ssmask) +{ + in_addr_t mask; + int i; + + /* a.b.c.d/8 -> htonl(0xff000000) */ + mask = 0; + for (i = 0; i < ssmask->bits; ++i) + mask = (mask >> 1) | 0x80000000; + mask = htonl(mask); + + /* (addr & mask) == (net & mask) */ + if ((ss->sin_addr.s_addr & mask) == + (((struct sockaddr_in *)ssmask)->sin_addr.s_addr & mask)) + return 1; + + return 0; +} + +static int +table_inet6_match(struct sockaddr_in6 *ss, struct netaddr *ssmask) +{ + struct in6_addr *in; + struct in6_addr *inmask; + struct in6_addr mask; + int i; + + memset(&mask, 0, sizeof(mask)); + for (i = 0; i < ssmask->bits / 8; i++) + mask.s6_addr[i] = 0xff; + i = ssmask->bits % 8; + if (i) + mask.s6_addr[ssmask->bits / 8] = 0xff00 >> i; + + in = &ss->sin6_addr; + inmask = &((struct sockaddr_in6 *)&ssmask->ss)->sin6_addr; + + for (i = 0; i < 16; i++) { + if ((in->s6_addr[i] & mask.s6_addr[i]) != + (inmask->s6_addr[i] & mask.s6_addr[i])) + return (0); + } + + return (1); +} + +int +table_regex_match(const char *string, const char *pattern) +{ + regex_t preg; + int cflags = REG_EXTENDED|REG_NOSUB; + + if (strncmp(pattern, "(?i)", 4) == 0) { + cflags |= REG_ICASE; + pattern += 4; + } + + if (regcomp(&preg, pattern, cflags) != 0) + return (0); + + if (regexec(&preg, string, 0, NULL, 0) != 0) + return (0); + + return (1); +} + +void +table_dump_all(struct smtpd *conf) +{ + struct table *t; + void *iter; + + iter = NULL; + while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) + table_dump(t); +} + +void +table_open_all(struct smtpd *conf) +{ + struct table *t; + void *iter; + + iter = NULL; + while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) + if (!table_open(t)) + fatalx("failed to open table %s", t->t_name); +} + +void +table_close_all(struct smtpd *conf) +{ + struct table *t; + void *iter; + + iter = NULL; + while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) + table_close(t); +} + +static int +table_parse_lookup(enum table_service service, const char *key, + const char *line, union lookup *lk) +{ + char buffer[LINE_MAX], *p; + size_t len; + + len = strlen(line); + + switch (service) { + case K_ALIAS: + lk->expand = calloc(1, sizeof(*lk->expand)); + if (lk->expand == NULL) + return (-1); + if (!expand_line(lk->expand, line, 1)) { + expand_free(lk->expand); + return (-1); + } + return (1); + + case K_DOMAIN: + if (strlcpy(lk->domain.name, line, sizeof(lk->domain.name)) + >= sizeof(lk->domain.name)) + return (-1); + return (1); + + case K_CREDENTIALS: + + /* credentials are stored as user:password */ + if (len < 3) + return (-1); + + /* too big to fit in a smtp session line */ + if (len >= LINE_MAX) + return (-1); + + p = strchr(line, ':'); + if (p == NULL) { + if (strlcpy(lk->creds.username, key, sizeof (lk->creds.username)) + >= sizeof (lk->creds.username)) + return (-1); + if (strlcpy(lk->creds.password, line, sizeof(lk->creds.password)) + >= sizeof(lk->creds.password)) + return (-1); + return (1); + } + + if (p == line || p == line + len - 1) + return (-1); + + memmove(lk->creds.username, line, p - line); + lk->creds.username[p - line] = '\0'; + + if (strlcpy(lk->creds.password, p+1, sizeof(lk->creds.password)) + >= sizeof(lk->creds.password)) + return (-1); + + return (1); + + case K_NETADDR: + if (!text_to_netaddr(&lk->netaddr, line)) + return (-1); + return (1); + + case K_USERINFO: + if (!bsnprintf(buffer, sizeof(buffer), "%s:%s", key, line)) + return (-1); + if (!text_to_userinfo(&lk->userinfo, buffer)) + return (-1); + return (1); + + case K_SOURCE: + if (parse_sockaddr((struct sockaddr *)&lk->source.addr, + PF_UNSPEC, line) == -1) + return (-1); + return (1); + + case K_MAILADDR: + if (!text_to_mailaddr(&lk->mailaddr, line)) + return (-1); + return (1); + + case K_MAILADDRMAP: + lk->maddrmap = calloc(1, sizeof(*lk->maddrmap)); + if (lk->maddrmap == NULL) + return (-1); + maddrmap_init(lk->maddrmap); + if (!mailaddr_line(lk->maddrmap, line)) { + maddrmap_free(lk->maddrmap); + return (-1); + } + return (1); + + case K_ADDRNAME: + if (parse_sockaddr((struct sockaddr *)&lk->addrname.addr, + PF_UNSPEC, key) == -1) + return (-1); + if (strlcpy(lk->addrname.name, line, sizeof(lk->addrname.name)) + >= sizeof(lk->addrname.name)) + return (-1); + return (1); + + case K_RELAYHOST: + if (strlcpy(lk->relayhost, line, sizeof(lk->relayhost)) + >= sizeof(lk->relayhost)) + return (-1); + return (1); + + default: + return (-1); + } +} + +static int +parse_sockaddr(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char *cp, *str2; + const char *errstr; + + switch (family) { + case PF_UNSPEC: + if (parse_sockaddr(sa, PF_INET, str) == 0) + return (0); + return parse_sockaddr(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + sin = (struct sockaddr_in *)sa; + memset(sin, 0, sizeof *sin); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin->sin_len = sizeof(struct sockaddr_in); +#endif + sin->sin_family = PF_INET; + sin->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + if (strncasecmp("ipv6:", str, 5) == 0) + str += 5; + cp = strchr(str, SCOPE_DELIMITER); + if (cp) { + str2 = strdup(str); + if (str2 == NULL) + return (-1); + str2[cp - str] = '\0'; + if (inet_pton(PF_INET6, str2, &in6a) != 1) { + free(str2); + return (-1); + } + cp++; + free(str2); + } else if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + sin6 = (struct sockaddr_in6 *)sa; + memset(sin6, 0, sizeof *sin6); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = in6a; + + if (cp == NULL) + return (0); + + if (IN6_IS_ADDR_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_NODELOCAL(&in6a)) + if ((sin6->sin6_scope_id = if_nametoindex(cp))) + return (0); + + sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + return (0); + + default: + break; + } + + return (-1); +} diff --git a/usr.sbin/smtpd/table_db.c b/usr.sbin/smtpd/table_db.c new file mode 100644 index 00000000..f7d766dd --- /dev/null +++ b/usr.sbin/smtpd/table_db.c @@ -0,0 +1,282 @@ +/* $OpenBSD: table_db.c,v 1.21 2019/06/28 13:32:51 deraadt Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_DB_H +#include +#elif defined(HAVE_DB1_DB_H) +#include +#elif defined(HAVE_DB_185_H) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + + +/* db(3) backend */ +static int table_db_config(struct table *); +static int table_db_update(struct table *); +static int table_db_open(struct table *); +static void *table_db_open2(struct table *); +static int table_db_lookup(struct table *, enum table_service, const char *, char **); +static int table_db_fetch(struct table *, enum table_service, char **); +static void table_db_close(struct table *); +static void table_db_close2(void *); + +static char *table_db_get_entry(void *, const char *, size_t *); +static char *table_db_get_entry_match(void *, const char *, size_t *, + int(*)(const char *, const char *)); + +struct table_backend table_backend_db = { + "db", + K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO|K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP, + table_db_config, + NULL, + NULL, + table_db_open, + table_db_update, + table_db_close, + table_db_lookup, + table_db_fetch, +}; + +static struct keycmp { + enum table_service service; + int (*func)(const char *, const char *); +} keycmp[] = { + { K_DOMAIN, table_domain_match }, + { K_NETADDR, table_netaddr_match }, + { K_MAILADDR, table_mailaddr_match } +}; + +struct dbhandle { + DB *db; + char pathname[PATH_MAX]; + time_t mtime; + int iter; +}; + +static int +table_db_config(struct table *table) +{ + struct dbhandle *handle; + + handle = table_db_open2(table); + if (handle == NULL) + return 0; + + table_db_close2(handle); + return 1; +} + +static int +table_db_update(struct table *table) +{ + struct dbhandle *handle; + + handle = table_db_open2(table); + if (handle == NULL) + return 0; + + table_db_close2(table->t_handle); + table->t_handle = handle; + return 1; +} + +static int +table_db_open(struct table *table) +{ + table->t_handle = table_db_open2(table); + if (table->t_handle == NULL) + return 0; + return 1; +} + +static void +table_db_close(struct table *table) +{ + table_db_close2(table->t_handle); + table->t_handle = NULL; +} + +static void * +table_db_open2(struct table *table) +{ + struct dbhandle *handle; + struct stat sb; + + handle = xcalloc(1, sizeof *handle); + if (strlcpy(handle->pathname, table->t_config, sizeof handle->pathname) + >= sizeof handle->pathname) + goto error; + + if (stat(handle->pathname, &sb) == -1) + goto error; + + handle->mtime = sb.st_mtime; + handle->db = dbopen(table->t_config, O_RDONLY, 0600, DB_HASH, NULL); + if (handle->db == NULL) + goto error; + + return handle; + +error: + if (handle->db) + handle->db->close(handle->db); + free(handle); + return NULL; +} + +static void +table_db_close2(void *hdl) +{ + struct dbhandle *handle = hdl; + handle->db->close(handle->db); + free(handle); +} + +static int +table_db_lookup(struct table *table, enum table_service service, const char *key, + char **dst) +{ + struct dbhandle *handle = table->t_handle; + char *line; + size_t len = 0; + int ret; + int (*match)(const char *, const char *) = NULL; + size_t i; + struct stat sb; + + if (stat(handle->pathname, &sb) == -1) + return -1; + + /* DB has changed, close and reopen */ + if (sb.st_mtime != handle->mtime) { + table_db_update(table); + handle = table->t_handle; + } + + for (i = 0; i < nitems(keycmp); ++i) + if (keycmp[i].service == service) + match = keycmp[i].func; + + if (match == NULL) + line = table_db_get_entry(handle, key, &len); + else + line = table_db_get_entry_match(handle, key, &len, match); + if (line == NULL) + return 0; + + ret = 1; + if (dst) + *dst = line; + else + free(line); + + return ret; +} + +static int +table_db_fetch(struct table *table, enum table_service service, char **dst) +{ + struct dbhandle *handle = table->t_handle; + DBT dbk; + DBT dbd; + int r; + + if (handle->iter == 0) + r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); + else + r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT); + handle->iter = 1; + if (!r) { + r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); + if (!r) + return 0; + } + + *dst = strdup(dbk.data); + if (*dst == NULL) + return -1; + + return 1; +} + + +static char * +table_db_get_entry_match(void *hdl, const char *key, size_t *len, + int(*func)(const char *, const char *)) +{ + struct dbhandle *handle = hdl; + DBT dbk; + DBT dbd; + int r; + char *buf = NULL; + + for (r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); !r; + r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT)) { + buf = xmemdup(dbk.data, dbk.size); + if (func(key, buf)) { + *len = dbk.size; + return buf; + } + free(buf); + } + return NULL; +} + +static char * +table_db_get_entry(void *hdl, const char *key, size_t *len) +{ + struct dbhandle *handle = hdl; + int ret; + DBT dbk; + DBT dbv; + char pkey[LINE_MAX]; + + /* workaround the stupidity of the DB interface */ + if (strlcpy(pkey, key, sizeof pkey) >= sizeof pkey) + errx(1, "table_db_get_entry: key too long"); + dbk.data = pkey; + dbk.size = strlen(pkey) + 1; + + if ((ret = handle->db->get(handle->db, &dbk, &dbv, 0)) != 0) + return NULL; + + *len = dbv.size; + + return xmemdup(dbv.data, dbv.size); +} diff --git a/usr.sbin/smtpd/table_getpwnam.c b/usr.sbin/smtpd/table_getpwnam.c new file mode 100644 index 00000000..ccf889be --- /dev/null +++ b/usr.sbin/smtpd/table_getpwnam.c @@ -0,0 +1,120 @@ +/* $OpenBSD: table_getpwnam.c,v 1.12 2018/12/27 14:23:41 eric Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + + +/* getpwnam(3) backend */ +static int table_getpwnam_config(struct table *); +static int table_getpwnam_update(struct table *); +static int table_getpwnam_open(struct table *); +static int table_getpwnam_lookup(struct table *, enum table_service, const char *, + char **); +static void table_getpwnam_close(struct table *); + +struct table_backend table_backend_getpwnam = { + "getpwnam", + K_USERINFO, + table_getpwnam_config, + NULL, + NULL, + table_getpwnam_open, + table_getpwnam_update, + table_getpwnam_close, + table_getpwnam_lookup, +}; + + +static int +table_getpwnam_config(struct table *table) +{ + if (table->t_config[0]) + return 0; + return 1; +} + +static int +table_getpwnam_update(struct table *table) +{ + return 1; +} + +static int +table_getpwnam_open(struct table *table) +{ + return 1; +} + +static void +table_getpwnam_close(struct table *table) +{ + return; +} + +static int +table_getpwnam_lookup(struct table *table, enum table_service kind, const char *key, + char **dst) +{ + struct passwd *pw; + + if (kind != K_USERINFO) + return -1; + + errno = 0; + do { + pw = getpwnam(key); + } while (pw == NULL && errno == EINTR); + + if (pw == NULL) { + if (errno) + return -1; + return 0; + } + if (dst == NULL) + return 1; + + if (asprintf(dst, "%d:%d:%s", + pw->pw_uid, + pw->pw_gid, + pw->pw_dir) == -1) { + *dst = NULL; + return -1; + } + + return (1); +} diff --git a/usr.sbin/smtpd/table_proc.c b/usr.sbin/smtpd/table_proc.c new file mode 100644 index 00000000..44589bd7 --- /dev/null +++ b/usr.sbin/smtpd/table_proc.c @@ -0,0 +1,283 @@ +/* $OpenBSD: table_proc.c,v 1.16 2019/10/03 04:51:15 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#ifdef HAVE_PATHS_H +#include +#endif +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +struct table_proc_priv { + pid_t pid; + struct imsgbuf ibuf; +}; + +static struct imsg imsg; +static size_t rlen; +static char *rdata; + +extern char **environ; + +static void +table_proc_call(struct table_proc_priv *p) +{ + ssize_t n; + + if (imsg_flush(&p->ibuf) == -1) { + log_warn("warn: table-proc: imsg_flush"); + fatalx("table-proc: exiting"); + } + + while (1) { + if ((n = imsg_get(&p->ibuf, &imsg)) == -1) { + log_warn("warn: table-proc: imsg_get"); + break; + } + if (n) { + rlen = imsg.hdr.len - IMSG_HEADER_SIZE; + rdata = imsg.data; + + if (imsg.hdr.type != PROC_TABLE_OK) { + log_warnx("warn: table-proc: bad response"); + break; + } + return; + } + + if ((n = imsg_read(&p->ibuf)) == -1 && errno != EAGAIN) { + log_warn("warn: table-proc: imsg_read"); + break; + } + + if (n == 0) { + log_warnx("warn: table-proc: pipe closed"); + break; + } + } + + fatalx("table-proc: exiting"); +} + +static void +table_proc_read(void *dst, size_t len) +{ + if (len > rlen) { + log_warnx("warn: table-proc: bad msg len"); + fatalx("table-proc: exiting"); + } + + if (dst) + memmove(dst, rdata, len); + + rlen -= len; + rdata += len; +} + +static void +table_proc_end(void) +{ + if (rlen) { + log_warnx("warn: table-proc: bogus data"); + fatalx("table-proc: exiting"); + } + imsg_free(&imsg); +} + +/* + * API + */ + +static int +table_proc_open(struct table *table) +{ + struct table_proc_priv *priv; + struct table_open_params op; + int fd; + + priv = xcalloc(1, sizeof(*priv)); + + fd = fork_proc_backend("table", table->t_config, table->t_name); + if (fd == -1) + fatalx("table-proc: exiting"); + + imsg_init(&priv->ibuf, fd); + + memset(&op, 0, sizeof op); + op.version = PROC_TABLE_API_VERSION; + (void)strlcpy(op.name, table->t_name, sizeof op.name); + imsg_compose(&priv->ibuf, PROC_TABLE_OPEN, 0, 0, -1, &op, sizeof op); + + table_proc_call(priv); + table_proc_end(); + + table->t_handle = priv; + + return (1); +} + +static int +table_proc_update(struct table *table) +{ + struct table_proc_priv *priv = table->t_handle; + int r; + + imsg_compose(&priv->ibuf, PROC_TABLE_UPDATE, 0, 0, -1, NULL, 0); + + table_proc_call(priv); + table_proc_read(&r, sizeof(r)); + table_proc_end(); + + return (r); +} + +static void +table_proc_close(struct table *table) +{ + struct table_proc_priv *priv = table->t_handle; + + imsg_compose(&priv->ibuf, PROC_TABLE_CLOSE, 0, 0, -1, NULL, 0); + if (imsg_flush(&priv->ibuf) == -1) + fatal("imsg_flush"); + + table->t_handle = NULL; +} + +static int +imsg_add_params(struct ibuf *buf) +{ + size_t count = 0; + + if (imsg_add(buf, &count, sizeof(count)) == -1) + return (-1); + + return (0); +} + +static int +table_proc_lookup(struct table *table, enum table_service s, const char *k, char **dst) +{ + struct table_proc_priv *priv = table->t_handle; + struct ibuf *buf; + int r; + + buf = imsg_create(&priv->ibuf, + dst ? PROC_TABLE_LOOKUP : PROC_TABLE_CHECK, 0, 0, + sizeof(s) + strlen(k) + 1); + + if (buf == NULL) + return (-1); + if (imsg_add(buf, &s, sizeof(s)) == -1) + return (-1); + if (imsg_add_params(buf) == -1) + return (-1); + if (imsg_add(buf, k, strlen(k) + 1) == -1) + return (-1); + imsg_close(&priv->ibuf, buf); + + table_proc_call(priv); + table_proc_read(&r, sizeof(r)); + + if (r == 1 && dst) { + if (rlen == 0) { + log_warnx("warn: table-proc: empty response"); + fatalx("table-proc: exiting"); + } + if (rdata[rlen - 1] != '\0') { + log_warnx("warn: table-proc: not NUL-terminated"); + fatalx("table-proc: exiting"); + } + *dst = strdup(rdata); + if (*dst == NULL) + r = -1; + table_proc_read(NULL, rlen); + } + + table_proc_end(); + + return (r); +} + +static int +table_proc_fetch(struct table *table, enum table_service s, char **dst) +{ + struct table_proc_priv *priv = table->t_handle; + struct ibuf *buf; + int r; + + buf = imsg_create(&priv->ibuf, PROC_TABLE_FETCH, 0, 0, sizeof(s)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &s, sizeof(s)) == -1) + return (-1); + if (imsg_add_params(buf) == -1) + return (-1); + imsg_close(&priv->ibuf, buf); + + table_proc_call(priv); + table_proc_read(&r, sizeof(r)); + + if (r == 1) { + if (rlen == 0) { + log_warnx("warn: table-proc: empty response"); + fatalx("table-proc: exiting"); + } + if (rdata[rlen - 1] != '\0') { + log_warnx("warn: table-proc: not NUL-terminated"); + fatalx("table-proc: exiting"); + } + *dst = strdup(rdata); + if (*dst == NULL) + r = -1; + table_proc_read(NULL, rlen); + } + + table_proc_end(); + + return (r); +} + +struct table_backend table_backend_proc = { + "proc", + K_ANY, + NULL, + NULL, + NULL, + table_proc_open, + table_proc_update, + table_proc_close, + table_proc_lookup, + table_proc_fetch, +}; diff --git a/usr.sbin/smtpd/table_static.c b/usr.sbin/smtpd/table_static.c new file mode 100644 index 00000000..8f78ae11 --- /dev/null +++ b/usr.sbin/smtpd/table_static.c @@ -0,0 +1,398 @@ +/* $OpenBSD: table_static.c,v 1.32 2018/12/28 14:21:02 eric Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +struct table_static_priv { + int type; + struct dict dict; + void *iter; +}; + +/* static backend */ +static int table_static_config(struct table *); +static int table_static_add(struct table *, const char *, const char *); +static void table_static_dump(struct table *); +static int table_static_update(struct table *); +static int table_static_open(struct table *); +static int table_static_lookup(struct table *, enum table_service, const char *, + char **); +static int table_static_fetch(struct table *, enum table_service, char **); +static void table_static_close(struct table *); + +struct table_backend table_backend_static = { + "static", + K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO| + K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP|K_RELAYHOST| + K_STRING|K_REGEX, + table_static_config, + table_static_add, + table_static_dump, + table_static_open, + table_static_update, + table_static_close, + table_static_lookup, + table_static_fetch +}; + +static struct keycmp { + enum table_service service; + int (*func)(const char *, const char *); +} keycmp[] = { + { K_DOMAIN, table_domain_match }, + { K_NETADDR, table_netaddr_match }, + { K_MAILADDR, table_mailaddr_match }, + { K_REGEX, table_regex_match }, +}; + + +static void +table_static_priv_free(struct table_static_priv *priv) +{ + void *p; + + while (dict_poproot(&priv->dict, (void **)&p)) + if (p != priv) + free(p); + free(priv); +} + +static int +table_static_priv_add(struct table_static_priv *priv, const char *key, const char *val) +{ + char lkey[1024]; + void *old, *new = NULL; + + if (!lowercase(lkey, key, sizeof lkey)) { + errno = ENAMETOOLONG; + return (-1); + } + + if (val) { + new = strdup(val); + if (new == NULL) + return (-1); + } + + /* use priv if value is null, so we can detect duplicate entries */ + old = dict_set(&priv->dict, lkey, new ? new : priv); + if (old) { + if (old != priv) + free(old); + return (1); + } + + return (0); +} + +static int +table_static_priv_load(struct table_static_priv *priv, const char *path) +{ + FILE *fp; + char *buf = NULL, *p; + int lineno = 0; + size_t sz = 0; + ssize_t flen; + char *keyp; + char *valp; + int ret = 0; + + if ((fp = fopen(path, "r")) == NULL) { + log_warn("%s: fopen", path); + return 0; + } + + while ((flen = getline(&buf, &sz, fp)) != -1) { + lineno++; + if (buf[flen - 1] == '\n') + buf[--flen] = '\0'; + + keyp = buf; + while (isspace((unsigned char)*keyp)) { + ++keyp; + --flen; + } + if (*keyp == '\0') + continue; + while (isspace((unsigned char)keyp[flen - 1])) + keyp[--flen] = '\0'; + if (*keyp == '#') { + if (priv->type == T_NONE) { + keyp++; + while (isspace((unsigned char)*keyp)) + ++keyp; + if (!strcmp(keyp, "@list")) + priv->type = T_LIST; + } + continue; + } + + if (priv->type == T_NONE) { + for (p = keyp; *p; p++) { + if (*p == ' ' || *p == '\t' || *p == ':') { + priv->type = T_HASH; + break; + } + } + if (priv->type == T_NONE) + priv->type = T_LIST; + } + + if (priv->type == T_LIST) { + table_static_priv_add(priv, keyp, NULL); + continue; + } + + /* T_HASH */ + valp = keyp; + strsep(&valp, " \t:"); + if (valp) { + while (*valp) { + if (!isspace((unsigned char)*valp) && + !(*valp == ':' && + isspace((unsigned char)*(valp + 1)))) + break; + ++valp; + } + if (*valp == '\0') + valp = NULL; + } + if (valp == NULL) { + log_warnx("%s: invalid map entry line %d", + path, lineno); + goto end; + } + + table_static_priv_add(priv, keyp, valp); + } + + if (ferror(fp)) { + log_warn("%s: getline", path); + goto end; + } + + /* Accept empty alias files; treat them as hashes */ + if (priv->type == T_NONE) + priv->type = T_HASH; + + ret = 1; +end: + free(buf); + fclose(fp); + return ret; +} + +static int +table_static_config(struct table *t) +{ + struct table_static_priv *priv, *old; + + /* already up, and no config file? ok */ + if (t->t_handle && *t->t_config == '\0') + return 1; + + /* new config */ + priv = calloc(1, sizeof(*priv)); + if (priv == NULL) + return 0; + priv->type = t->t_type; + dict_init(&priv->dict); + + if (*t->t_config) { + /* load the config file */ + if (table_static_priv_load(priv, t->t_config) == 0) { + table_static_priv_free(priv); + return 0; + } + } + + if ((old = t->t_handle)) + table_static_priv_free(old); + t->t_handle = priv; + t->t_type = priv->type; + + return 1; +} + +static int +table_static_add(struct table *table, const char *key, const char *val) +{ + struct table_static_priv *priv = table->t_handle; + int r; + + /* cannot add to a table read from a file */ + if (*table->t_config) + return 0; + + if (table->t_type == T_NONE) + table->t_type = val ? T_HASH : T_LIST; + else if (table->t_type == T_LIST && val) + return 0; + else if (table->t_type == T_HASH && val == NULL) + return 0; + + if (priv == NULL) { + if (table_static_config(table) == 0) + return 0; + priv = table->t_handle; + } + + r = table_static_priv_add(priv, key, val); + if (r == -1) + return 0; + return 1; +} + +static void +table_static_dump(struct table *table) +{ + struct table_static_priv *priv = table->t_handle; + const char *key; + char *value; + void *iter; + + iter = NULL; + while (dict_iter(&priv->dict, &iter, &key, (void**)&value)) { + if (value && (void*)value != (void*)priv) + log_debug(" \"%s\" -> \"%s\"", key, value); + else + log_debug(" \"%s\"", key); + } +} + +static int +table_static_update(struct table *table) +{ + if (table_static_config(table) == 1) { + log_info("info: Table \"%s\" successfully updated", table->t_name); + return 1; + } + + log_info("info: Failed to update table \"%s\"", table->t_name); + return 0; +} + +static int +table_static_open(struct table *table) +{ + if (table->t_handle == NULL) + return table_static_config(table); + return 1; +} + +static void +table_static_close(struct table *table) +{ + struct table_static_priv *priv = table->t_handle; + + if (priv) + table_static_priv_free(priv); + table->t_handle = NULL; +} + +static int +table_static_lookup(struct table *table, enum table_service service, const char *key, + char **dst) +{ + struct table_static_priv *priv = table->t_handle; + char *line; + int ret; + int (*match)(const char *, const char *) = NULL; + size_t i; + void *iter; + const char *k; + char *v; + + for (i = 0; i < nitems(keycmp); ++i) + if (keycmp[i].service == service) + match = keycmp[i].func; + + line = NULL; + iter = NULL; + ret = 0; + while (dict_iter(&priv->dict, &iter, &k, (void **)&v)) { + if (match) { + if (match(key, k)) { + line = v; + ret = 1; + } + } + else { + if (strcmp(key, k) == 0) { + line = v; + ret = 1; + } + } + if (ret) + break; + } + + if (dst == NULL) + return ret ? 1 : 0; + + if (ret == 0) + return 0; + + *dst = strdup(line); + if (*dst == NULL) + return -1; + + return 1; +} + +static int +table_static_fetch(struct table *t, enum table_service service, char **dst) +{ + struct table_static_priv *priv = t->t_handle; + const char *k; + + if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) { + priv->iter = NULL; + if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) + return 0; + } + + *dst = strdup(k); + if (*dst == NULL) + return -1; + + return 1; +} diff --git a/usr.sbin/smtpd/to.c b/usr.sbin/smtpd/to.c new file mode 100644 index 00000000..81a1bb54 --- /dev/null +++ b/usr.sbin/smtpd/to.c @@ -0,0 +1,880 @@ +/* $OpenBSD: to.c,v 1.44 2019/11/12 20:21:46 gilles Exp $ */ + +/* + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * Copyright (c) 2012 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +static const char *in6addr_to_text(const struct in6_addr *); +static int alias_is_filter(struct expandnode *, const char *, size_t); +static int alias_is_username(struct expandnode *, const char *, size_t); +static int alias_is_address(struct expandnode *, const char *, size_t); +static int alias_is_filename(struct expandnode *, const char *, size_t); +static int alias_is_include(struct expandnode *, const char *, size_t); +static int alias_is_error(struct expandnode *, const char *, size_t); + +static int broken_inet_net_pton_ipv6(const char *, void *, size_t); + +const char * +sockaddr_to_text(struct sockaddr *sa) +{ + static char buf[NI_MAXHOST]; + + if (getnameinfo(sa, SA_LEN(sa), buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST)) + return ("(unknown)"); + else + return (buf); +} + +static const char * +in6addr_to_text(const struct in6_addr *addr) +{ + struct sockaddr_in6 sa_in6; + uint16_t tmp16; + + memset(&sa_in6, 0, sizeof(sa_in6)); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sa_in6.sin6_len = sizeof(sa_in6); +#endif + sa_in6.sin6_family = AF_INET6; + memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); + + /* XXX thanks, KAME, for this ugliness... adopted from route/show.c */ + if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) { + memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16)); + sa_in6.sin6_scope_id = ntohs(tmp16); + sa_in6.sin6_addr.s6_addr[2] = 0; + sa_in6.sin6_addr.s6_addr[3] = 0; + } + + return (sockaddr_to_text((struct sockaddr *)&sa_in6)); +} + +int +text_to_mailaddr(struct mailaddr *maddr, const char *email) +{ + char *username; + char *hostname; + char buffer[LINE_MAX]; + + if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer) + return 0; + + memset(maddr, 0, sizeof *maddr); + + username = buffer; + hostname = strrchr(username, '@'); + + if (hostname == NULL) { + if (strlcpy(maddr->user, username, sizeof maddr->user) + >= sizeof maddr->user) + return 0; + } + else if (username == hostname) { + *hostname++ = '\0'; + if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) + >= sizeof maddr->domain) + return 0; + } + else { + *hostname++ = '\0'; + if (strlcpy(maddr->user, username, sizeof maddr->user) + >= sizeof maddr->user) + return 0; + if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) + >= sizeof maddr->domain) + return 0; + } + + return 1; +} + +const char * +mailaddr_to_text(const struct mailaddr *maddr) +{ + static char buffer[LINE_MAX]; + + (void)strlcpy(buffer, maddr->user, sizeof buffer); + (void)strlcat(buffer, "@", sizeof buffer); + if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer) + return NULL; + + return buffer; +} + + +const char * +sa_to_text(const struct sockaddr *sa) +{ + static char buf[NI_MAXHOST + 5]; + char *p; + + buf[0] = '\0'; + p = buf; + + if (sa->sa_family == AF_LOCAL) + (void)strlcpy(buf, "local", sizeof buf); + else if (sa->sa_family == AF_INET) { + in_addr_t addr; + + addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr; + addr = ntohl(addr); + (void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d", + (addr >> 24) & 0xff, (addr >> 16) & 0xff, + (addr >> 8) & 0xff, addr & 0xff); + } + else if (sa->sa_family == AF_INET6) { + const struct sockaddr_in6 *in6; + const struct in6_addr *in6_addr; + + in6 = (const struct sockaddr_in6 *)sa; + p = buf; + in6_addr = &in6->sin6_addr; + (void)bsnprintf(p, NI_MAXHOST, "[%s]", in6addr_to_text(in6_addr)); + } + + return (buf); +} + +const char * +ss_to_text(const struct sockaddr_storage *ss) +{ + return (sa_to_text((const struct sockaddr*)ss)); +} + +const char * +time_to_text(time_t when) +{ + struct tm *lt; + static char buf[40]; + char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + const char *tz; + long offset; + + lt = localtime(&when); + if (lt == NULL || when == 0) + fatalx("time_to_text: localtime"); + +#if HAVE_STRUCT_TM_TM_GMTOFF + offset = lt->tm_gmtoff; + tz = lt->tm_zone; +#elif defined HAVE_DECL_ALTZONE && defined HAVE_DECL_TIMEZONE + offset = lt->tm_isdst > 0 ? altzone : timezone; + tz = lt->tm_isdst > 0 ? tzname[1] : tzname[0]; +#endif + + /* We do not use strftime because it is subject to locale substitution*/ + if (!bsnprintf(buf, sizeof(buf), + "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", + day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], + lt->tm_year + 1900, + lt->tm_hour, lt->tm_min, lt->tm_sec, + offset >= 0 ? '+' : '-', + abs((int)offset / 3600), + abs((int)offset % 3600) / 60, + tz)) + fatalx("time_to_text: bsnprintf"); + + return buf; +} + +const char * +duration_to_text(time_t t) +{ + static char dst[64]; + char buf[64]; + int h, m, s; + long long d; + + if (t == 0) { + (void)strlcpy(dst, "0s", sizeof dst); + return (dst); + } + + dst[0] = '\0'; + if (t < 0) { + (void)strlcpy(dst, "-", sizeof dst); + t = -t; + } + + s = t % 60; + t /= 60; + m = t % 60; + t /= 60; + h = t % 24; + d = t / 24; + + if (d) { + (void)snprintf(buf, sizeof buf, "%lldd", d); + (void)strlcat(dst, buf, sizeof dst); + } + if (h) { + (void)snprintf(buf, sizeof buf, "%dh", h); + (void)strlcat(dst, buf, sizeof dst); + } + if (m) { + (void)snprintf(buf, sizeof buf, "%dm", m); + (void)strlcat(dst, buf, sizeof dst); + } + if (s) { + (void)snprintf(buf, sizeof buf, "%ds", s); + (void)strlcat(dst, buf, sizeof dst); + } + + return (dst); +} + +int +text_to_netaddr(struct netaddr *netaddr, const char *s) +{ + struct sockaddr_storage ss; + struct sockaddr_in ssin; + struct sockaddr_in6 ssin6; + int bits; + char buf[NI_MAXHOST]; + size_t len; + + memset(&ssin, 0, sizeof(struct sockaddr_in)); + memset(&ssin6, 0, sizeof(struct sockaddr_in6)); + + if (strncasecmp("IPv6:", s, 5) == 0) + s += 5; + + bits = inet_net_pton(AF_INET, s, &ssin.sin_addr, + sizeof(struct in_addr)); + if (bits != -1) { + ssin.sin_family = AF_INET; + memcpy(&ss, &ssin, sizeof(ssin)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss.ss_len = sizeof(struct sockaddr_in); +#endif + } else { + if (s[0] != '[') { + if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) + return 0; + } + else { + s++; + if (strncasecmp("IPv6:", s, 5) == 0) + s += 5; + if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) + return 0; + if (buf[len-1] != ']') + return 0; + buf[len-1] = 0; + } + bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr, + sizeof(struct in6_addr)); + if (bits == -1) { + if (errno != EAFNOSUPPORT) + return 0; + bits = broken_inet_net_pton_ipv6(buf, &ssin6.sin6_addr, + sizeof(struct in6_addr)); + if (bits == -1) + return 0; + } + ssin6.sin6_family = AF_INET6; + memcpy(&ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss.ss_len = sizeof(struct sockaddr_in6); +#endif + } + + netaddr->ss = ss; + netaddr->bits = bits; + return 1; +} + +int +text_to_relayhost(struct relayhost *relay, const char *s) +{ + static const struct schema { + const char *name; + int tls; + uint16_t flags; + uint16_t port; + } schemas [] = { + /* + * new schemas should be *appended* otherwise the default + * schema index needs to be updated later in this function. + */ + { "smtp://", RELAY_TLS_OPPORTUNISTIC, 0, 25 }, + { "smtp+tls://", RELAY_TLS_STARTTLS, 0, 25 }, + { "smtp+notls://", RELAY_TLS_NO, 0, 25 }, + /* need to specify an explicit port for LMTP */ + { "lmtp://", RELAY_TLS_NO, RELAY_LMTP, 0 }, + { "smtps://", RELAY_TLS_SMTPS, 0, 465 } + }; + const char *errstr = NULL; + char *p, *q; + char buffer[1024]; + char *beg, *end; + size_t i; + size_t len; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + + for (i = 0; i < nitems(schemas); ++i) + if (strncasecmp(schemas[i].name, s, + strlen(schemas[i].name)) == 0) + break; + + if (i == nitems(schemas)) { + /* there is a schema, but it's not recognized */ + if (strstr(buffer, "://")) + return 0; + + /* no schema, default to smtp:// */ + i = 0; + p = buffer; + } + else + p = buffer + strlen(schemas[i].name); + + relay->tls = schemas[i].tls; + relay->flags = schemas[i].flags; + relay->port = schemas[i].port; + + /* first, we extract the label if any */ + if ((q = strchr(p, '@')) != NULL) { + *q = 0; + if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel)) + >= sizeof (relay->authlabel)) + return 0; + p = q + 1; + } + + /* then, we extract the mail exchanger */ + beg = end = p; + if (*beg == '[') { + if ((end = strchr(beg, ']')) == NULL) + return 0; + /* skip ']', it has to be included in the relay hostname */ + ++end; + len = end - beg; + } + else { + for (end = beg; *end; ++end) + if (!isalnum((unsigned char)*end) && + *end != '_' && *end != '.' && *end != '-') + break; + len = end - beg; + } + if (len >= sizeof relay->hostname) + return 0; + for (i = 0; i < len; ++i) + relay->hostname[i] = beg[i]; + relay->hostname[i] = 0; + + /* finally, we extract the port */ + p = beg + len; + if (*p == ':') { + relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr); + if (errstr) + return 0; + } + + if (!valid_domainpart(relay->hostname)) + return 0; + if ((relay->flags & RELAY_LMTP) && (relay->port == 0)) + return 0; + if (relay->authlabel[0]) { + /* disallow auth on non-tls scheme. */ + if (relay->tls != RELAY_TLS_STARTTLS && + relay->tls != RELAY_TLS_SMTPS) + return 0; + relay->flags |= RELAY_AUTH; + } + + return 1; +} + +uint64_t +text_to_evpid(const char *s) +{ + uint64_t ulval; + char *ep; + + errno = 0; + ulval = strtoull(s, &ep, 16); + if (s[0] == '\0' || *ep != '\0') + return 0; + if (errno == ERANGE && ulval == ULLONG_MAX) + return 0; + if (ulval == 0) + return 0; + return (ulval); +} + +uint32_t +text_to_msgid(const char *s) +{ + uint64_t ulval; + char *ep; + + errno = 0; + ulval = strtoull(s, &ep, 16); + if (s[0] == '\0' || *ep != '\0') + return 0; + if (errno == ERANGE && ulval == ULLONG_MAX) + return 0; + if (ulval == 0) + return 0; + if (ulval > 0xffffffff) + return 0; + return (ulval & 0xffffffff); +} + +const char * +rule_to_text(struct rule *r) +{ + static char buf[4096]; + + memset(buf, 0, sizeof buf); + (void)strlcpy(buf, "match", sizeof buf); + if (r->flag_tag) { + if (r->flag_tag < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " tag ", sizeof buf); + (void)strlcat(buf, r->table_tag, sizeof buf); + } + + if (r->flag_from) { + if (r->flag_from < 0) + (void)strlcat(buf, " !", sizeof buf); + if (r->flag_from_socket) + (void)strlcat(buf, " from socket", sizeof buf); + else if (r->flag_from_rdns) { + (void)strlcat(buf, " from rdns", sizeof buf); + if (r->table_from) { + (void)strlcat(buf, " ", sizeof buf); + (void)strlcat(buf, r->table_from, sizeof buf); + } + } + else if (strcmp(r->table_from, "") == 0) + (void)strlcat(buf, " from any", sizeof buf); + else if (strcmp(r->table_from, "") == 0) + (void)strlcat(buf, " from local", sizeof buf); + else { + (void)strlcat(buf, " from src ", sizeof buf); + (void)strlcat(buf, r->table_from, sizeof buf); + } + } + + if (r->flag_for) { + if (r->flag_for < 0) + (void)strlcat(buf, " !", sizeof buf); + if (strcmp(r->table_for, "") == 0) + (void)strlcat(buf, " for any", sizeof buf); + else if (strcmp(r->table_for, "") == 0) + (void)strlcat(buf, " for local", sizeof buf); + else { + (void)strlcat(buf, " for domain ", sizeof buf); + (void)strlcat(buf, r->table_for, sizeof buf); + } + } + + if (r->flag_smtp_helo) { + if (r->flag_smtp_helo < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " helo ", sizeof buf); + (void)strlcat(buf, r->table_smtp_helo, sizeof buf); + } + + if (r->flag_smtp_auth) { + if (r->flag_smtp_auth < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " auth", sizeof buf); + if (r->table_smtp_auth) { + (void)strlcat(buf, " ", sizeof buf); + (void)strlcat(buf, r->table_smtp_auth, sizeof buf); + } + } + + if (r->flag_smtp_starttls) { + if (r->flag_smtp_starttls < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " tls", sizeof buf); + } + + if (r->flag_smtp_mail_from) { + if (r->flag_smtp_mail_from < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " mail-from ", sizeof buf); + (void)strlcat(buf, r->table_smtp_mail_from, sizeof buf); + } + + if (r->flag_smtp_rcpt_to) { + if (r->flag_smtp_rcpt_to < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " rcpt-to ", sizeof buf); + (void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf); + } + (void)strlcat(buf, " action ", sizeof buf); + if (r->reject) + (void)strlcat(buf, "reject", sizeof buf); + else + (void)strlcat(buf, r->dispatcher, sizeof buf); + return buf; +} + + +int +text_to_userinfo(struct userinfo *userinfo, const char *s) +{ + char buf[PATH_MAX]; + char *p; + const char *errstr; + + memset(buf, 0, sizeof buf); + p = buf; + while (*s && *s != ':') + *p++ = *s++; + if (*s++ != ':') + goto error; + + if (strlcpy(userinfo->username, buf, + sizeof userinfo->username) >= sizeof userinfo->username) + goto error; + + memset(buf, 0, sizeof buf); + p = buf; + while (*s && *s != ':') + *p++ = *s++; + if (*s++ != ':') + goto error; + userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr); + if (errstr) + goto error; + + memset(buf, 0, sizeof buf); + p = buf; + while (*s && *s != ':') + *p++ = *s++; + if (*s++ != ':') + goto error; + userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr); + if (errstr) + goto error; + + if (strlcpy(userinfo->directory, s, + sizeof userinfo->directory) >= sizeof userinfo->directory) + goto error; + + return 1; + +error: + return 0; +} + +int +text_to_credentials(struct credentials *creds, const char *s) +{ + char *p; + char buffer[LINE_MAX]; + size_t offset; + + p = strchr(s, ':'); + if (p == NULL) { + creds->username[0] = '\0'; + if (strlcpy(creds->password, s, sizeof creds->password) + >= sizeof creds->password) + return 0; + return 1; + } + + offset = p - s; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + p = buffer + offset; + *p = '\0'; + + if (strlcpy(creds->username, buffer, sizeof creds->username) + >= sizeof creds->username) + return 0; + if (strlcpy(creds->password, p+1, sizeof creds->password) + >= sizeof creds->password) + return 0; + + return 1; +} + +int +text_to_expandnode(struct expandnode *expandnode, const char *s) +{ + size_t l; + + l = strlen(s); + if (alias_is_error(expandnode, s, l) || + alias_is_include(expandnode, s, l) || + alias_is_filter(expandnode, s, l) || + alias_is_filename(expandnode, s, l) || + alias_is_address(expandnode, s, l) || + alias_is_username(expandnode, s, l)) + return (1); + + return (0); +} + +const char * +expandnode_to_text(struct expandnode *expandnode) +{ + switch (expandnode->type) { + case EXPAND_FILTER: + case EXPAND_FILENAME: + case EXPAND_INCLUDE: + case EXPAND_ERROR: + case EXPAND_USERNAME: + return expandnode->u.user; + case EXPAND_ADDRESS: + return mailaddr_to_text(&expandnode->u.mailaddr); + case EXPAND_INVALID: + break; + } + + return NULL; +} + +/******/ +static int +alias_is_filter(struct expandnode *alias, const char *line, size_t len) +{ + int v = 0; + + if (*line == '"') + v = 1; + if (*(line+v) == '|') { + if (strlcpy(alias->u.buffer, line + v + 1, + sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) + return 0; + if (v) { + v = strlen(alias->u.buffer); + if (v == 0) + return (0); + if (alias->u.buffer[v-1] != '"') + return (0); + alias->u.buffer[v-1] = '\0'; + } + alias->type = EXPAND_FILTER; + return (1); + } + return (0); +} + +static int +alias_is_username(struct expandnode *alias, const char *line, size_t len) +{ + memset(alias, 0, sizeof *alias); + + if (strlcpy(alias->u.user, line, + sizeof(alias->u.user)) >= sizeof(alias->u.user)) + return 0; + + while (*line) { + if (!isalnum((unsigned char)*line) && + *line != '_' && *line != '.' && *line != '-' && *line != '+') + return 0; + ++line; + } + + alias->type = EXPAND_USERNAME; + return 1; +} + +static int +alias_is_address(struct expandnode *alias, const char *line, size_t len) +{ + char *domain; + + memset(alias, 0, sizeof *alias); + + if (len < 3) /* x@y */ + return 0; + + domain = strchr(line, '@'); + if (domain == NULL) + return 0; + + /* @ cannot start or end an address */ + if (domain == line || domain == line + len - 1) + return 0; + + /* scan pre @ for disallowed chars */ + *domain++ = '\0'; + (void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user)); + (void)strlcpy(alias->u.mailaddr.domain, domain, + sizeof(alias->u.mailaddr.domain)); + + while (*line) { + char allowedset[] = "!#$%*/?|^{}`~&'+-=_."; + if (!isalnum((unsigned char)*line) && + strchr(allowedset, *line) == NULL) + return 0; + ++line; + } + + while (*domain) { + char allowedset[] = "-."; + if (!isalnum((unsigned char)*domain) && + strchr(allowedset, *domain) == NULL) + return 0; + ++domain; + } + + alias->type = EXPAND_ADDRESS; + return 1; +} + +static int +alias_is_filename(struct expandnode *alias, const char *line, size_t len) +{ + memset(alias, 0, sizeof *alias); + + if (*line != '/') + return 0; + + if (strlcpy(alias->u.buffer, line, + sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) + return 0; + alias->type = EXPAND_FILENAME; + return 1; +} + +static int +alias_is_include(struct expandnode *alias, const char *line, size_t len) +{ + size_t skip; + + memset(alias, 0, sizeof *alias); + + if (strncasecmp(":include:", line, 9) == 0) + skip = 9; + else if (strncasecmp("include:", line, 8) == 0) + skip = 8; + else + return 0; + + if (!alias_is_filename(alias, line + skip, len - skip)) + return 0; + + alias->type = EXPAND_INCLUDE; + return 1; +} + +static int +alias_is_error(struct expandnode *alias, const char *line, size_t len) +{ + size_t skip; + + memset(alias, 0, sizeof *alias); + + if (strncasecmp(":error:", line, 7) == 0) + skip = 7; + else if (strncasecmp("error:", line, 6) == 0) + skip = 6; + else + return 0; + + if (strlcpy(alias->u.buffer, line + skip, + sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) + return 0; + + if (strlen(alias->u.buffer) < 5) + return 0; + + /* [45][0-9]{2} [a-zA-Z0-9].* */ + if (alias->u.buffer[3] != ' ' || + !isalnum((unsigned char)alias->u.buffer[4]) || + (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') || + !isdigit((unsigned char)alias->u.buffer[1]) || + !isdigit((unsigned char)alias->u.buffer[2])) + return 0; + + alias->type = EXPAND_ERROR; + return 1; +} + +static int +broken_inet_net_pton_ipv6(const char *src, void *dst, size_t size) +{ + int ret; + int bits; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")]; + char *sep; + const char *errstr; + + if (strlcpy(buf, src, sizeof buf) >= sizeof buf) { + errno = EMSGSIZE; + return (-1); + } + + sep = strchr(buf, '/'); + if (sep != NULL) + *sep++ = '\0'; + + ret = inet_pton(AF_INET6, buf, dst); + if (ret != 1) + return (-1); + + if (sep == NULL) + return 128; + + bits = strtonum(sep, 0, 128, &errstr); + if (errstr) + return (-1); + + return bits; +} diff --git a/usr.sbin/smtpd/tree.c b/usr.sbin/smtpd/tree.c new file mode 100644 index 00000000..1d720a59 --- /dev/null +++ b/usr.sbin/smtpd/tree.c @@ -0,0 +1,259 @@ +/* $OpenBSD: tree.c,v 1.6 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include + +#include +#include +#include +#include + +#include "tree.h" + +struct treeentry { + SPLAY_ENTRY(treeentry) entry; + uint64_t id; + void *data; +}; + +static int treeentry_cmp(struct treeentry *, struct treeentry *); + +SPLAY_PROTOTYPE(_tree, treeentry, entry, treeentry_cmp); + +int +tree_check(struct tree *t, uint64_t id) +{ + struct treeentry key; + + key.id = id; + return (SPLAY_FIND(_tree, &t->tree, &key) != NULL); +} + +void * +tree_set(struct tree *t, uint64_t id, void *data) +{ + struct treeentry *entry, key; + char *old; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) { + if ((entry = malloc(sizeof *entry)) == NULL) + err(1, "tree_set: malloc"); + entry->id = id; + SPLAY_INSERT(_tree, &t->tree, entry); + old = NULL; + t->count += 1; + } else + old = entry->data; + + entry->data = data; + + return (old); +} + +void +tree_xset(struct tree *t, uint64_t id, void *data) +{ + struct treeentry *entry; + + if ((entry = malloc(sizeof *entry)) == NULL) + err(1, "tree_xset: malloc"); + entry->id = id; + entry->data = data; + if (SPLAY_INSERT(_tree, &t->tree, entry)) + errx(1, "tree_xset(%p, 0x%016"PRIx64 ")", t, id); + t->count += 1; +} + +void * +tree_get(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + return (NULL); + + return (entry->data); +} + +void * +tree_xget(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + errx(1, "tree_get(%p, 0x%016"PRIx64 ")", t, id); + + return (entry->data); +} + +void * +tree_pop(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + void *data; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + return (NULL); + + data = entry->data; + SPLAY_REMOVE(_tree, &t->tree, entry); + free(entry); + t->count -= 1; + + return (data); +} + +void * +tree_xpop(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + void *data; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + errx(1, "tree_xpop(%p, 0x%016" PRIx64 ")", t, id); + + data = entry->data; + SPLAY_REMOVE(_tree, &t->tree, entry); + free(entry); + t->count -= 1; + + return (data); +} + +int +tree_poproot(struct tree *t, uint64_t *id, void **data) +{ + struct treeentry *entry; + + entry = SPLAY_ROOT(&t->tree); + if (entry == NULL) + return (0); + if (id) + *id = entry->id; + if (data) + *data = entry->data; + SPLAY_REMOVE(_tree, &t->tree, entry); + free(entry); + t->count -= 1; + + return (1); +} + +int +tree_root(struct tree *t, uint64_t *id, void **data) +{ + struct treeentry *entry; + + entry = SPLAY_ROOT(&t->tree); + if (entry == NULL) + return (0); + if (id) + *id = entry->id; + if (data) + *data = entry->data; + return (1); +} + +int +tree_iter(struct tree *t, void **hdl, uint64_t *id, void **data) +{ + struct treeentry *curr = *hdl; + + if (curr == NULL) + curr = SPLAY_MIN(_tree, &t->tree); + else + curr = SPLAY_NEXT(_tree, &t->tree, curr); + + if (curr) { + *hdl = curr; + if (id) + *id = curr->id; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +int +tree_iterfrom(struct tree *t, void **hdl, uint64_t k, uint64_t *id, void **data) +{ + struct treeentry *curr = *hdl, key; + + if (curr == NULL) { + if (k == 0) + curr = SPLAY_MIN(_tree, &t->tree); + else { + key.id = k; + curr = SPLAY_FIND(_tree, &t->tree, &key); + if (curr == NULL) { + SPLAY_INSERT(_tree, &t->tree, &key); + curr = SPLAY_NEXT(_tree, &t->tree, &key); + SPLAY_REMOVE(_tree, &t->tree, &key); + } + } + } else + curr = SPLAY_NEXT(_tree, &t->tree, curr); + + if (curr) { + *hdl = curr; + if (id) + *id = curr->id; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +void +tree_merge(struct tree *dst, struct tree *src) +{ + struct treeentry *entry; + + while (!SPLAY_EMPTY(&src->tree)) { + entry = SPLAY_ROOT(&src->tree); + SPLAY_REMOVE(_tree, &src->tree, entry); + if (SPLAY_INSERT(_tree, &dst->tree, entry)) + errx(1, "tree_merge: duplicate"); + } + dst->count += src->count; + src->count = 0; +} + +static int +treeentry_cmp(struct treeentry *a, struct treeentry *b) +{ + if (a->id < b->id) + return (-1); + if (a->id > b->id) + return (1); + return (0); +} + +SPLAY_GENERATE(_tree, treeentry, entry, treeentry_cmp); diff --git a/usr.sbin/smtpd/tree.h b/usr.sbin/smtpd/tree.h new file mode 100644 index 00000000..3d719f09 --- /dev/null +++ b/usr.sbin/smtpd/tree.h @@ -0,0 +1,48 @@ +/* $OpenBSD: tree.h,v 1.1 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot + * Copyright (c) 2011 Gilles Chehade + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _TREE_H_ +#define _TREE_H_ + +SPLAY_HEAD(_tree, treeentry); + +struct tree { + struct _tree tree; + size_t count; +}; + + +/* tree.c */ +#define tree_init(t) do { SPLAY_INIT(&((t)->tree)); (t)->count = 0; } while(0) +#define tree_empty(t) SPLAY_EMPTY(&((t)->tree)) +#define tree_count(t) ((t)->count) +int tree_check(struct tree *, uint64_t); +void *tree_set(struct tree *, uint64_t, void *); +void tree_xset(struct tree *, uint64_t, void *); +void *tree_get(struct tree *, uint64_t); +void *tree_xget(struct tree *, uint64_t); +void *tree_pop(struct tree *, uint64_t); +void *tree_xpop(struct tree *, uint64_t); +int tree_poproot(struct tree *, uint64_t *, void **); +int tree_root(struct tree *, uint64_t *, void **); +int tree_iter(struct tree *, void **, uint64_t *, void **); +int tree_iterfrom(struct tree *, void **, uint64_t, uint64_t *, void **); +void tree_merge(struct tree *, struct tree *); + +#endif diff --git a/usr.sbin/smtpd/unpack_dns.c b/usr.sbin/smtpd/unpack_dns.c new file mode 100644 index 00000000..974d5727 --- /dev/null +++ b/usr.sbin/smtpd/unpack_dns.c @@ -0,0 +1,300 @@ +/* $OpenBSD: unpack_dns.c,v 1.1 2018/01/06 07:57:53 sunil Exp $ */ + +/* + * Copyright (c) 2011-2014 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include +#endif +#include + +#include + +#include "unpack_dns.h" + +static int unpack_data(struct unpack *, void *, size_t); +static int unpack_u16(struct unpack *, uint16_t *); +static int unpack_u32(struct unpack *, uint32_t *); +static int unpack_inaddr(struct unpack *, struct in_addr *); +static int unpack_in6addr(struct unpack *, struct in6_addr *); +static int unpack_dname(struct unpack *, char *, size_t); + +void +unpack_init(struct unpack *unpack, const char *buf, size_t len) +{ + unpack->buf = buf; + unpack->len = len; + unpack->offset = 0; + unpack->err = NULL; +} + +int +unpack_header(struct unpack *p, struct dns_header *h) +{ + if (unpack_data(p, h, HFIXEDSZ) == -1) + return (-1); + + h->flags = ntohs(h->flags); + h->qdcount = ntohs(h->qdcount); + h->ancount = ntohs(h->ancount); + h->nscount = ntohs(h->nscount); + h->arcount = ntohs(h->arcount); + + return (0); +} + +int +unpack_query(struct unpack *p, struct dns_query *q) +{ + unpack_dname(p, q->q_dname, sizeof(q->q_dname)); + unpack_u16(p, &q->q_type); + unpack_u16(p, &q->q_class); + + return (p->err) ? (-1) : (0); +} + +int +unpack_rr(struct unpack *p, struct dns_rr *rr) +{ + uint16_t rdlen; + size_t save_offset; + + unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); + unpack_u16(p, &rr->rr_type); + unpack_u16(p, &rr->rr_class); + unpack_u32(p, &rr->rr_ttl); + unpack_u16(p, &rdlen); + + if (p->err) + return (-1); + + if (p->len - p->offset < rdlen) { + p->err = "too short"; + return (-1); + } + + save_offset = p->offset; + + switch (rr->rr_type) { + + case T_CNAME: + unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname)); + break; + + case T_MX: + unpack_u16(p, &rr->rr.mx.preference); + unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange)); + break; + + case T_NS: + unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname)); + break; + + case T_PTR: + unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname)); + break; + + case T_SOA: + unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname)); + unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname)); + unpack_u32(p, &rr->rr.soa.serial); + unpack_u32(p, &rr->rr.soa.refresh); + unpack_u32(p, &rr->rr.soa.retry); + unpack_u32(p, &rr->rr.soa.expire); + unpack_u32(p, &rr->rr.soa.minimum); + break; + + case T_A: + if (rr->rr_class != C_IN) + goto other; + unpack_inaddr(p, &rr->rr.in_a.addr); + break; + + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + unpack_in6addr(p, &rr->rr.in_aaaa.addr6); + break; + default: + other: + rr->rr.other.rdata = p->buf + p->offset; + rr->rr.other.rdlen = rdlen; + p->offset += rdlen; + } + + if (p->err) + return (-1); + + /* make sure that the advertised rdlen is really ok */ + if (p->offset - save_offset != rdlen) + p->err = "bad dlen"; + + return (p->err) ? (-1) : (0); +} + +ssize_t +dname_expand(const unsigned char *data, size_t len, size_t offset, + size_t *newoffset, char *dst, size_t max) +{ + size_t n, count, end, ptr, start; + ssize_t res; + + if (offset >= len) + return (-1); + + res = 0; + end = start = offset; + + for (; (n = data[offset]); ) { + if ((n & 0xc0) == 0xc0) { + if (offset + 2 > len) + return (-1); + ptr = 256 * (n & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (-1); + if (end < offset + 2) + end = offset + 2; + offset = start = ptr; + continue; + } + if (offset + n + 1 > len) + return (-1); + + /* copy n + at offset+1 */ + if (dst != NULL && max != 0) { + count = (max < n + 1) ? (max) : (n + 1); + memmove(dst, data + offset, count); + dst += count; + max -= count; + } + res += n + 1; + offset += n + 1; + if (end < offset) + end = offset; + } + if (end < offset + 1) + end = offset + 1; + + if (dst != NULL && max != 0) + dst[0] = 0; + if (newoffset) + *newoffset = end; + return (res + 1); +} + +char * +print_dname(const char *_dname, char *buf, size_t max) +{ + const unsigned char *dname = _dname; + char *res; + size_t left, n, count; + + if (_dname[0] == 0) { + (void)strlcpy(buf, ".", max); + return buf; + } + + res = buf; + left = max - 1; + for (n = 0; dname[0] && left; n += dname[0]) { + count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); + memmove(buf, dname + 1, count); + dname += dname[0] + 1; + left -= count; + buf += count; + if (left) { + left -= 1; + *buf++ = '.'; + } + } + buf[0] = 0; + + return (res); +} + +static int +unpack_data(struct unpack *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = "too short"; + return (-1); + } + + memmove(data, p->buf + p->offset, len); + p->offset += len; + + return (0); +} + +static int +unpack_u16(struct unpack *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +static int +unpack_u32(struct unpack *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +static int +unpack_inaddr(struct unpack *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +static int +unpack_in6addr(struct unpack *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +static int +unpack_dname(struct unpack *p, char *dst, size_t max) +{ + ssize_t e; + + if (p->err) + return (-1); + + e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max); + if (e == -1) { + p->err = "bad domain name"; + return (-1); + } + if (e < 0 || e > MAXDNAME) { + p->err = "domain name too long"; + return (-1); + } + + return (0); +} diff --git a/usr.sbin/smtpd/unpack_dns.h b/usr.sbin/smtpd/unpack_dns.h new file mode 100644 index 00000000..2318a0c5 --- /dev/null +++ b/usr.sbin/smtpd/unpack_dns.h @@ -0,0 +1,96 @@ +/* $OpenBSD: unpack_dns.h,v 1.1 2018/01/06 07:57:53 sunil Exp $ */ + +/* + * Copyright (c) 2011-2014 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include +#include + +struct unpack { + const char *buf; + size_t len; + size_t offset; + const char *err; +}; + +struct dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct dns_query { + char q_dname[MAXDNAME]; + uint16_t q_type; + uint16_t q_class; +}; + +struct dns_rr { + char rr_dname[MAXDNAME]; + uint16_t rr_type; + uint16_t rr_class; + uint32_t rr_ttl; + union { + struct { + char cname[MAXDNAME]; + } cname; + struct { + uint16_t preference; + char exchange[MAXDNAME]; + } mx; + struct { + char nsname[MAXDNAME]; + } ns; + struct { + char ptrname[MAXDNAME]; + } ptr; + struct { + char mname[MAXDNAME]; + char rname[MAXDNAME]; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + struct { + struct in_addr addr; + } in_a; + struct { + struct in6_addr addr6; + } in_aaaa; + struct { + uint16_t rdlen; + const void *rdata; + } other; + } rr; +}; + +void unpack_init(struct unpack *, const char *, size_t); +int unpack_header(struct unpack *, struct dns_header *); +int unpack_rr(struct unpack *, struct dns_rr *); +int unpack_query(struct unpack *, struct dns_query *); +char *print_dname(const char *, char *, size_t); +ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, + char *, size_t); + diff --git a/usr.sbin/smtpd/util.c b/usr.sbin/smtpd/util.c new file mode 100644 index 00000000..b2b1458c --- /dev/null +++ b/usr.sbin/smtpd/util.c @@ -0,0 +1,870 @@ +/* $OpenBSD: util.c,v 1.151 2020/02/24 23:54:28 millert Exp $ */ + +/* + * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2009 Jacek Masiulaniec + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "smtpd.h" +#include "log.h" + +const char *log_in6addr(const struct in6_addr *); +const char *log_sockaddr(struct sockaddr *); +static int parse_mailname_file(char *, size_t); + +int tracing = 0; +int foreground_log = 0; + +void * +xmalloc(size_t size) +{ + void *r; + + if ((r = malloc(size)) == NULL) + fatal("malloc"); + + return (r); +} + +void * +xcalloc(size_t nmemb, size_t size) +{ + void *r; + + if ((r = calloc(nmemb, size)) == NULL) + fatal("calloc"); + + return (r); +} + +char * +xstrdup(const char *str) +{ + char *r; + + if ((r = strdup(str)) == NULL) + fatal("strdup"); + + return (r); +} + +void * +xmemdup(const void *ptr, size_t size) +{ + void *r; + + if ((r = malloc(size)) == NULL) + fatal("malloc"); + + memmove(r, ptr, size); + + return (r); +} + +int +xasprintf(char **ret, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = vasprintf(ret, format, ap); + va_end(ap); + if (r == -1) + fatal("vasprintf"); + + return (r); +} + + +#if !defined(NO_IO) +int +io_xprintf(struct io *io, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = io_vprintf(io, fmt, ap); + va_end(ap); + if (len == -1) + fatal("io_xprintf(%p, %s, ...)", io, fmt); + + return len; +} + +int +io_xprint(struct io *io, const char *str) +{ + int len; + + len = io_print(io, str); + if (len == -1) + fatal("io_xprint(%p, %s, ...)", io, str); + + return len; +} +#endif + +char * +strip(char *s) +{ + size_t l; + + while (isspace((unsigned char)*s)) + s++; + + for (l = strlen(s); l; l--) { + if (!isspace((unsigned char)s[l-1])) + break; + s[l-1] = '\0'; + } + + return (s); +} + +int +bsnprintf(char *str, size_t size, const char *format, ...) +{ + int ret; + va_list ap; + + va_start(ap, format); + ret = vsnprintf(str, size, format, ap); + va_end(ap); + if (ret < 0 || ret >= (int)size) + return 0; + + return 1; +} + + +int +ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create) +{ + char mode_str[12]; + int ret; + struct stat sb; + + if (stat(path, &sb) == -1) { + if (errno != ENOENT || create == 0) { + log_warn("stat: %s", path); + return (0); + } + + /* chmod is deferred to avoid umask effect */ + if (mkdir(path, 0) == -1) { + log_warn("mkdir: %s", path); + return (0); + } + + if (chown(path, owner, group) == -1) { + log_warn("chown: %s", path); + return (0); + } + + if (chmod(path, mode) == -1) { + log_warn("chmod: %s", path); + return (0); + } + + if (stat(path, &sb) == -1) { + log_warn("stat: %s", path); + return (0); + } + } + + ret = 1; + + /* check if it's a directory */ + if (!S_ISDIR(sb.st_mode)) { + ret = 0; + log_warnx("%s is not a directory", path); + } + + /* check that it is owned by owner/group */ + if (sb.st_uid != owner) { + ret = 0; + log_warnx("%s is not owned by uid %d", path, owner); + } + if (sb.st_gid != group) { + ret = 0; + log_warnx("%s is not owned by gid %d", path, group); + } + + /* check permission */ + if ((sb.st_mode & 07777) != mode) { + ret = 0; + strmode(mode, mode_str); + mode_str[10] = '\0'; + log_warnx("%s must be %s (%o)", path, mode_str + 1, mode); + } + + return ret; +} + +int +rmtree(char *path, int keepdir) +{ + char *path_argv[2]; + FTS *fts; + FTSENT *e; + int ret, depth; + + path_argv[0] = path; + path_argv[1] = NULL; + ret = 0; + depth = 0; + + fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); + if (fts == NULL) { + log_warn("fts_open: %s", path); + return (-1); + } + + while ((e = fts_read(fts)) != NULL) { + switch (e->fts_info) { + case FTS_D: + depth++; + break; + case FTS_DP: + case FTS_DNR: + depth--; + if (keepdir && depth == 0) + continue; + if (rmdir(e->fts_path) == -1) { + log_warn("rmdir: %s", e->fts_path); + ret = -1; + } + break; + + case FTS_F: + if (unlink(e->fts_path) == -1) { + log_warn("unlink: %s", e->fts_path); + ret = -1; + } + } + } + + fts_close(fts); + + return (ret); +} + +int +mvpurge(char *from, char *to) +{ + size_t n; + int retry; + const char *sep; + char buf[PATH_MAX]; + + if ((n = strlen(to)) == 0) + fatalx("to is empty"); + + sep = (to[n - 1] == '/') ? "" : "/"; + retry = 0; + +again: + (void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random()); + if (rename(from, buf) == -1) { + /* ENOTDIR has actually 2 meanings, and incorrect input + * could lead to an infinite loop. Consider that after + * 20 tries something is hopelessly wrong. + */ + if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) { + if ((retry++) >= 20) + return (-1); + goto again; + } + return -1; + } + + return 0; +} + + +int +mktmpfile(void) +{ + char path[PATH_MAX]; + int fd; + + if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX", + PATH_TEMPORARY)) { + log_warn("snprintf"); + fatal("exiting"); + } + + if ((fd = mkstemp(path)) == -1) { + log_warn("cannot create temporary file %s", path); + fatal("exiting"); + } + unlink(path); + return (fd); +} + + +/* Close file, signifying temporary error condition (if any) to the caller. */ +int +safe_fclose(FILE *fp) +{ + if (ferror(fp)) { + fclose(fp); + return 0; + } + if (fflush(fp)) { + fclose(fp); + if (errno == ENOSPC) + return 0; + fatal("safe_fclose: fflush"); + } + if (fsync(fileno(fp))) + fatal("safe_fclose: fsync"); + if (fclose(fp)) + fatal("safe_fclose: fclose"); + + return 1; +} + +int +hostname_match(const char *hostname, const char *pattern) +{ + while (*pattern != '\0' && *hostname != '\0') { + if (*pattern == '*') { + while (*pattern == '*') + pattern++; + while (*hostname != '\0' && + tolower((unsigned char)*hostname) != + tolower((unsigned char)*pattern)) + hostname++; + continue; + } + + if (tolower((unsigned char)*pattern) != + tolower((unsigned char)*hostname)) + return 0; + pattern++; + hostname++; + } + + return (*hostname == '\0' && *pattern == '\0'); +} + +int +mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2) +{ + struct mailaddr m1 = *maddr1; + struct mailaddr m2 = *maddr2; + char *p; + + /* catchall */ + if (m2.user[0] == '\0' && m2.domain[0] == '\0') + return 1; + + if (m2.domain[0] && !hostname_match(m1.domain, m2.domain)) + return 0; + + if (m2.user[0]) { + /* if address from table has a tag, we must respect it */ + if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) { + /* otherwise, strip tag from session address if any */ + p = strchr(m1.user, *env->sc_subaddressing_delim); + if (p) + *p = '\0'; + } + if (strcasecmp(m1.user, m2.user)) + return 0; + } + return 1; +} + +int +valid_localpart(const char *s) +{ +#define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c))) +nextatom: + if (!IS_ATEXT(*s) || *s == '\0') + return 0; + while (*(++s) != '\0') { + if (*s == '.') + break; + if (IS_ATEXT(*s)) + continue; + return 0; + } + if (*s == '.') { + s++; + goto nextatom; + } + return 1; +} + +int +valid_domainpart(const char *s) +{ + struct in_addr ina; + struct in6_addr ina6; + char *c, domain[SMTPD_MAXDOMAINPARTSIZE]; + const char *p; + size_t dlen; + + if (*s == '[') { + if (strncasecmp("[IPv6:", s, 6) == 0) + p = s + 6; + else + p = s + 1; + + if (strlcpy(domain, p, sizeof domain) >= sizeof domain) + return 0; + + c = strchr(domain, ']'); + if (!c || c[1] != '\0') + return 0; + + *c = '\0'; + + if (inet_pton(AF_INET6, domain, &ina6) == 1) + return 1; + if (inet_pton(AF_INET, domain, &ina) == 1) + return 1; + + return 0; + } + + if (*s == '\0') + return 0; + + dlen = strlen(s); + if (dlen >= sizeof domain) + return 0; + + if (s[dlen - 1] == '.') + return 0; + + return res_hnok(s); +} + +#define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c))) +#define LABELMAX 63 +#define DNAMEMAX 253 + +int +valid_domainname(const char *str) +{ + const char *label, *s; + + /* + * Expect a sequence of dot-separated labels, possibly with a trailing + * dot. The empty string is rejected, as well a single dot. + */ + for (s = str; *s; s++) { + + /* Start of a new label. */ + label = s; + while (LABELCHR(*s)) + s++; + + /* Must have at least one char and at most LABELMAX. */ + if (s == label || s - label > LABELMAX) + return 0; + + /* If last label, stop here. */ + if (*s == '\0') + break; + + /* Expect a dot as label separator or last char. */ + if (*s != '.') + return 0; + } + + /* Must have at leat one label and no more than DNAMEMAX chars. */ + if (s == str || s - str > DNAMEMAX) + return 0; + + return 1; +} + +int +valid_smtp_response(const char *s) +{ + if (strlen(s) < 5) + return 0; + + if ((s[0] < '2' || s[0] > '5') || + (s[1] < '0' || s[1] > '9') || + (s[2] < '0' || s[2] > '9') || + (s[3] != ' ')) + return 0; + + return 1; +} + +int +secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread) +{ + char buf[PATH_MAX]; + char homedir[PATH_MAX]; + struct stat st; + char *cp; + + if (realpath(path, buf) == NULL) + return 0; + + if (realpath(userdir, homedir) == NULL) + homedir[0] = '\0'; + + /* Check the open file to avoid races. */ + if (fstat(fd, &st) == -1 || + !S_ISREG(st.st_mode) || + st.st_uid != uid || + (st.st_mode & (mayread ? 022 : 066)) != 0) + return 0; + + /* For each component of the canonical path, walking upwards. */ + for (;;) { + if ((cp = dirname(buf)) == NULL) + return 0; + (void)strlcpy(buf, cp, sizeof(buf)); + + if (stat(buf, &st) == -1 || + (st.st_uid != 0 && st.st_uid != uid) || + (st.st_mode & 022) != 0) + return 0; + + /* We can stop checking after reaching homedir level. */ + if (strcmp(homedir, buf) == 0) + break; + + /* + * dirname should always complete with a "/" path, + * but we can be paranoid and check for "." too + */ + if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) + break; + } + + return 1; +} + +void +addargs(arglist *args, char *fmt, ...) +{ + va_list ap; + char *cp; + uint nalloc; + int r; + char **tmp; + + va_start(ap, fmt); + r = vasprintf(&cp, fmt, ap); + va_end(ap); + if (r == -1) + fatal("addargs: argument too long"); + + nalloc = args->nalloc; + if (args->list == NULL) { + nalloc = 32; + args->num = 0; + } else if (args->num+2 >= nalloc) + nalloc *= 2; + + tmp = reallocarray(args->list, nalloc, sizeof(char *)); + if (tmp == NULL) + fatal("addargs: reallocarray"); + args->list = tmp; + args->nalloc = nalloc; + args->list[args->num++] = cp; + args->list[args->num] = NULL; +} + +int +lowercase(char *buf, const char *s, size_t len) +{ + if (len == 0) + return 0; + + if (strlcpy(buf, s, len) >= len) + return 0; + + while (*buf != '\0') { + *buf = tolower((unsigned char)*buf); + buf++; + } + + return 1; +} + +int +uppercase(char *buf, const char *s, size_t len) +{ + if (len == 0) + return 0; + + if (strlcpy(buf, s, len) >= len) + return 0; + + while (*buf != '\0') { + *buf = toupper((unsigned char)*buf); + buf++; + } + + return 1; +} + +void +xlowercase(char *buf, const char *s, size_t len) +{ + if (len == 0) + fatalx("lowercase: len == 0"); + + if (!lowercase(buf, s, len)) + fatalx("lowercase: truncation"); +} + +uint64_t +generate_uid(void) +{ + static uint32_t id; + static uint8_t inited; + uint64_t uid; + + if (!inited) { + id = arc4random(); + inited = 1; + } + while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0) + ; + + return (uid); +} + +int +session_socket_error(int fd) +{ + int error; + socklen_t len; + + len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) + fatal("session_socket_error: getsockopt"); + + return (error); +} + +const char * +parse_smtp_response(char *line, size_t len, char **msg, int *cont) +{ + if (len >= LINE_MAX) + return "line too long"; + + if (len > 3) { + if (msg) + *msg = line + 4; + if (cont) + *cont = (line[3] == '-'); + } else if (len == 3) { + if (msg) + *msg = line + 3; + if (cont) + *cont = 0; + } else + return "line too short"; + + /* validate reply code */ + if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) || + !isdigit((unsigned char)line[2])) + return "reply code out of range"; + + return NULL; +} + +static int +parse_mailname_file(char *hostname, size_t len) +{ + FILE *fp; + char *buf = NULL; + size_t bufsz = 0; + ssize_t buflen; + + if ((fp = fopen(MAILNAME_FILE, "r")) == NULL) + return 1; + + if ((buflen = getline(&buf, &bufsz, fp)) == -1) + goto error; + + if (buf[buflen - 1] == '\n') + buf[buflen - 1] = '\0'; + + if (strlcpy(hostname, buf, len) >= len) { + fprintf(stderr, MAILNAME_FILE " entry too long"); + goto error; + } + + return 0; +error: + fclose(fp); + free(buf); + return 1; +} + +int +getmailname(char *hostname, size_t len) +{ + struct addrinfo hints, *res = NULL; + int error; + + /* Try MAILNAME_FILE first */ + if (parse_mailname_file(hostname, len) == 0) + return 0; + + /* Next, gethostname(3) */ + if (gethostname(hostname, len) == -1) { + fprintf(stderr, "getmailname: gethostname() failed\n"); + return -1; + } + + if (strchr(hostname, '.') != NULL) + return 0; + + /* Canonicalize if domain part is missing */ + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + error = getaddrinfo(hostname, NULL, &hints, &res); + if (error) + return 0; /* Continue with non-canon hostname */ + + if (strlcpy(hostname, res->ai_canonname, len) >= len) { + fprintf(stderr, "hostname too long"); + freeaddrinfo(res); + return -1; + } + + freeaddrinfo(res); + return 0; +} + +int +base64_encode(unsigned char const *src, size_t srclen, + char *dest, size_t destsize) +{ + return __b64_ntop(src, srclen, dest, destsize); +} + +int +base64_decode(char const *src, unsigned char *dest, size_t destsize) +{ + return __b64_pton(src, dest, destsize); +} + +int +base64_encode_rfc3548(unsigned char const *src, size_t srclen, + char *dest, size_t destsize) +{ + size_t i; + int ret; + + if ((ret = base64_encode(src, srclen, dest, destsize)) == -1) + return -1; + + for (i = 0; i < destsize; ++i) { + if (dest[i] == '/') + dest[i] = '_'; + else if (dest[i] == '+') + dest[i] = '-'; + } + + return ret; +} + +void +log_trace(int mask, const char *emsg, ...) +{ + va_list ap; + + if (tracing & mask) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +log_trace_verbose(int v) +{ + tracing = v; + + /* Set debug logging in log.c */ + log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log); +} + +void +xclosefrom(int lowfd) +{ +#if defined HAVE_CLOSEFROM_INT + if (closefrom(lowfd) == -1) + err(1, "closefrom"); +#else + closefrom(lowfd); +#endif +} + +void +portable_freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *p; + + do { + p = ai; + ai = ai->ai_next; + free(p->ai_canonname); + free(p); + } while (ai); +} diff --git a/usr.sbin/smtpd/waitq.c b/usr.sbin/smtpd/waitq.c new file mode 100644 index 00000000..082a1e51 --- /dev/null +++ b/usr.sbin/smtpd/waitq.c @@ -0,0 +1,104 @@ +/* $OpenBSD: waitq.c,v 1.6 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "smtpd.h" + +struct waiter { + TAILQ_ENTRY(waiter) entry; + void (*cb)(void *, void *, void *); + void *arg; +}; + +struct waitq { + SPLAY_ENTRY(waitq) entry; + void *tag; + TAILQ_HEAD(, waiter) waiters; +}; + +static int waitq_cmp(struct waitq *, struct waitq *); + +SPLAY_HEAD(waitqtree, waitq); +SPLAY_PROTOTYPE(waitqtree, waitq, entry, waitq_cmp); + +static struct waitqtree waitqs = SPLAY_INITIALIZER(&waitqs); + +static int +waitq_cmp(struct waitq *a, struct waitq *b) +{ + if (a->tag < b->tag) + return (-1); + if (a->tag > b->tag) + return (1); + return (0); +} + +SPLAY_GENERATE(waitqtree, waitq, entry, waitq_cmp); + +int +waitq_wait(void *tag, void (*cb)(void *, void *, void *), void *arg) +{ + struct waitq *wq, key; + struct waiter *w; + + key.tag = tag; + wq = SPLAY_FIND(waitqtree, &waitqs, &key); + if (wq == NULL) { + wq = xmalloc(sizeof *wq); + wq->tag = tag; + TAILQ_INIT(&wq->waiters); + SPLAY_INSERT(waitqtree, &waitqs, wq); + } + + w = xmalloc(sizeof *w); + w->cb = cb; + w->arg = arg; + TAILQ_INSERT_TAIL(&wq->waiters, w, entry); + + return (w == TAILQ_FIRST(&wq->waiters)); +} + +void +waitq_run(void *tag, void *result) +{ + struct waitq *wq, key; + struct waiter *w; + + key.tag = tag; + wq = SPLAY_FIND(waitqtree, &waitqs, &key); + SPLAY_REMOVE(waitqtree, &waitqs, wq); + + while ((w = TAILQ_FIRST(&wq->waiters))) { + TAILQ_REMOVE(&wq->waiters, w, entry); + w->cb(tag, w->arg, result); + free(w); + } + free(wq); +} -- cgit v1.2.3-59-g8ed1b