diff options
-rw-r--r-- | .builds/freebsd.yml | 23 | ||||
-rw-r--r-- | .builds/openbsd.yml | 16 | ||||
-rw-r--r-- | .github/workflows/alpine.yml | 4 | ||||
-rw-r--r-- | .github/workflows/arch.yml | 4 | ||||
-rw-r--r-- | .github/workflows/fedora-gcc10.yml | 15 | ||||
-rw-r--r-- | .github/workflows/fedora.yml | 15 | ||||
-rw-r--r-- | .github/workflows/macos-latest.yml | 23 | ||||
-rw-r--r-- | .github/workflows/ubuntu-gcc10.yml | 15 | ||||
-rw-r--r-- | .github/workflows/ubuntu.yml | 4 | ||||
-rw-r--r-- | CHANGES.md | 14 | ||||
-rw-r--r-- | README.md | 25 | ||||
-rw-r--r-- | ci/docker/Dockerfile.alpine | 13 | ||||
-rw-r--r-- | ci/docker/Dockerfile.archlinux | 1 | ||||
-rw-r--r-- | ci/docker/Dockerfile.fedora | 51 | ||||
-rw-r--r-- | ci/docker/Dockerfile.fedora-gcc10 | 54 | ||||
-rw-r--r-- | ci/docker/Dockerfile.ubuntu | 1 | ||||
-rw-r--r-- | ci/docker/Dockerfile.ubuntu-gcc10 | 54 | ||||
-rw-r--r-- | configure.ac | 103 | ||||
-rw-r--r-- | contrib/CVS/Entries | 4 | ||||
-rw-r--r-- | contrib/CVS/Repository | 1 | ||||
-rw-r--r-- | contrib/CVS/Root | 1 | ||||
-rw-r--r-- | contrib/libexec/CVS/Entries | 3 | ||||
-rw-r--r-- | contrib/libexec/CVS/Repository | 1 | ||||
-rw-r--r-- | contrib/libexec/CVS/Root | 1 | ||||
-rw-r--r-- | contrib/libexec/Makefile.am | 2 | ||||
-rw-r--r-- | contrib/libexec/encrypt/Makefile.am | 2 | ||||
-rw-r--r-- | contrib/libexec/lockspool/Makefile.am | 20 | ||||
-rw-r--r-- | contrib/libexec/lockspool/locking.c | 181 | ||||
-rw-r--r-- | contrib/libexec/lockspool/lockspool.1 | 77 | ||||
-rw-r--r-- | contrib/libexec/lockspool/lockspool.c | 124 | ||||
-rw-r--r-- | contrib/libexec/lockspool/mail.local.h | 42 | ||||
-rw-r--r-- | contrib/libexec/lockspool/pathnames.h | 38 | ||||
-rw-r--r-- | contrib/libexec/mail.local/CVS/Entries | 7 | ||||
-rw-r--r-- | contrib/libexec/mail.local/CVS/Repository | 1 | ||||
-rw-r--r-- | contrib/libexec/mail.local/CVS/Root | 1 | ||||
-rw-r--r-- | contrib/libexec/mail.local/Makefile.am | 4 | ||||
-rw-r--r-- | contrib/libexec/mail.local/locking.c | 47 | ||||
-rw-r--r-- | contrib/libexec/mail.local/mail.local.8 | 26 | ||||
-rw-r--r-- | contrib/libexec/mail.local/mail.local.c | 233 | ||||
-rw-r--r-- | contrib/libexec/mail.local/mail.local.h | 10 | ||||
-rw-r--r-- | contrib/libexec/mail.local/pathnames.h | 2 | ||||
-rw-r--r-- | mk/mail/mail.lmtp/Makefile.am | 4 | ||||
-rw-r--r-- | mk/mail/mail.maildir/Makefile.am | 4 | ||||
-rw-r--r-- | mk/mail/mail.mboxfile/Makefile.am | 4 | ||||
-rw-r--r-- | mk/mail/mail.mda/Makefile.am | 4 | ||||
-rw-r--r-- | mk/pathnames | 5 | ||||
-rw-r--r-- | mk/smtp/Makefile.am | 18 | ||||
-rw-r--r-- | mk/smtpctl/Makefile.am | 61 | ||||
-rw-r--r-- | mk/smtpd/Makefile.am | 195 | ||||
-rw-r--r-- | openbsd-compat/Makefile.am | 41 | ||||
-rw-r--r-- | openbsd-compat/defines.h | 18 | ||||
-rw-r--r-- | openbsd-compat/err_h/err.h | 3 | ||||
-rw-r--r-- | openbsd-compat/explicit_bzero.c | 2 | ||||
-rw-r--r-- | openbsd-compat/fgetln.c | 2 | ||||
-rw-r--r-- | openbsd-compat/freezero.c | 1 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr.c | 869 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr.h | 95 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr_compat.c | 102 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr_compat.h | 80 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr_debug.c | 362 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr_private.h | 359 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr_run.3 | 316 | ||||
-rw-r--r-- | openbsd-compat/libasr/asr_utils.c | 574 | ||||
-rw-r--r-- | openbsd-compat/libasr/getaddrinfo.c | 55 | ||||
-rw-r--r-- | openbsd-compat/libasr/getaddrinfo_async.c | 756 | ||||
-rw-r--r-- | openbsd-compat/libasr/gethostnamadr.c | 200 | ||||
-rw-r--r-- | openbsd-compat/libasr/gethostnamadr_async.c | 676 | ||||
-rw-r--r-- | openbsd-compat/libasr/getnameinfo.c | 205 | ||||
-rw-r--r-- | openbsd-compat/libasr/getnameinfo_async.c | 300 | ||||
-rw-r--r-- | openbsd-compat/libasr/getnetnamadr.c | 134 | ||||
-rw-r--r-- | openbsd-compat/libasr/getnetnamadr_async.c | 52 | ||||
-rw-r--r-- | openbsd-compat/libasr/getrrsetbyname.c | 83 | ||||
-rw-r--r-- | openbsd-compat/libasr/getrrsetbyname_async.c | 590 | ||||
-rw-r--r-- | openbsd-compat/libasr/libasr.la | 41 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_debug.c | 2 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_init.c | 103 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_mkquery.c | 119 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_query.c | 112 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_search_async.c | 327 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_send.c | 61 | ||||
-rw-r--r-- | openbsd-compat/libasr/res_send_async.c | 806 | ||||
-rw-r--r-- | openbsd-compat/libasr/sethostent.c | 36 | ||||
-rw-r--r-- | openbsd-compat/libasr/thread_private.h | 8 | ||||
-rw-r--r-- | openbsd-compat/openbsd-compat.h | 14 | ||||
-rw-r--r-- | openbsd-compat/pipe2.c | 46 | ||||
-rw-r--r-- | openbsd-compat/res_hnok.c | 169 | ||||
-rw-r--r-- | openbsd-compat/res_randomid.c | 13 | ||||
-rw-r--r-- | openbsd-compat/setproctitle.c | 5 | ||||
-rw-r--r-- | regress/config/Makefile | 17 | ||||
-rw-r--r-- | regress/config/include.conf | 1 | ||||
-rw-r--r-- | regress/config/test0.conf | 3 | ||||
-rw-r--r-- | regress/config/test1.conf | 5 | ||||
-rw-r--r-- | regress/config/test10.conf | 6 | ||||
-rw-r--r-- | regress/config/test11.conf | 25 | ||||
-rw-r--r-- | regress/config/test2.conf | 5 | ||||
-rw-r--r-- | regress/config/test3.conf | 5 | ||||
-rw-r--r-- | regress/config/test4.conf | 4 | ||||
-rw-r--r-- | regress/config/test5.conf | 9 | ||||
-rw-r--r-- | regress/config/test6.conf | 5 | ||||
-rw-r--r-- | regress/config/test7.conf | 12 | ||||
-rw-r--r-- | regress/config/test8.conf | 13 | ||||
-rw-r--r-- | regress/config/test9.conf | 8 | ||||
-rw-r--r-- | regress/expand/Makefile | 6 | ||||
-rw-r--r-- | regress/expand/expand.c | 129 | ||||
-rw-r--r-- | regress/expand/test0 | 7 | ||||
-rw-r--r-- | regress/filters/Makefile | 9 | ||||
-rwxr-xr-x | regress/smtp/test.base | 56 | ||||
-rw-r--r-- | regress/smtp/test.mailfrom | 110 | ||||
-rw-r--r-- | regress/smtp/test.rcptto | 112 | ||||
-rwxr-xr-x | regress/smtp/test.smtp0 | 89 | ||||
-rwxr-xr-x | regress/smtp/test.smtp1 | 88 | ||||
-rwxr-xr-x | regress/smtp/test.smtp2 | 50 | ||||
-rwxr-xr-x | regress/smtp/test.smtp4 | 155 | ||||
-rw-r--r-- | regress/smtp/test.tls | 10 | ||||
-rw-r--r-- | smtpd/lka_proc.c | 190 | ||||
-rw-r--r-- | smtpd/lka_report.c | 511 | ||||
-rw-r--r-- | smtpd/smtpctl/CVS/Entries | 2 | ||||
-rw-r--r-- | smtpd/smtpctl/CVS/Repository | 1 | ||||
-rw-r--r-- | smtpd/smtpctl/CVS/Root | 1 | ||||
-rw-r--r-- | smtpd/smtpd/CVS/Repository | 1 | ||||
-rw-r--r-- | smtpd/smtpd/CVS/Root | 1 | ||||
-rw-r--r-- | tests/certificate_test/smtpd.conf | 13 | ||||
-rwxr-xr-x | tests/certificate_test/test.sh | 20 | ||||
-rwxr-xr-x | tests/test_all.sh | 6 | ||||
-rw-r--r-- | tests/test_email.txt | 13 | ||||
-rw-r--r-- | usr.sbin/smtpd/Makefile (renamed from smtpd/Makefile) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/aliases.5 (renamed from smtpd/aliases.5) | 6 | ||||
-rw-r--r-- | usr.sbin/smtpd/aliases.c (renamed from smtpd/aliases.c) | 6 | ||||
-rw-r--r-- | usr.sbin/smtpd/bounce.c (renamed from smtpd/bounce.c) | 104 | ||||
-rw-r--r-- | usr.sbin/smtpd/ca.c (renamed from smtpd/ca.c) | 188 | ||||
-rw-r--r-- | usr.sbin/smtpd/cert.c (renamed from smtpd/cert.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/compress_backend.c (renamed from smtpd/compress_backend.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/compress_gzip.c (renamed from smtpd/compress_gzip.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/config.c (renamed from smtpd/config.c) | 10 | ||||
-rw-r--r-- | usr.sbin/smtpd/control.c (renamed from smtpd/control.c) | 2 | ||||
-rw-r--r-- | usr.sbin/smtpd/crypto.c (renamed from smtpd/crypto.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/dict.c (renamed from smtpd/dict.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/dict.h (renamed from smtpd/dict.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/dns.c (renamed from smtpd/dns.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/enqueue.c (renamed from smtpd/enqueue.c) | 106 | ||||
-rw-r--r-- | usr.sbin/smtpd/envelope.c (renamed from smtpd/envelope.c) | 12 | ||||
-rw-r--r-- | usr.sbin/smtpd/esc.c (renamed from smtpd/esc.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/expand.c (renamed from smtpd/expand.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/filter.c (renamed from smtpd/filter.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/forward.5 (renamed from smtpd/forward.5) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/forward.c (renamed from smtpd/forward.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/iobuf.c (renamed from smtpd/iobuf.c) | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/iobuf.h (renamed from smtpd/iobuf.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/ioev.c (renamed from smtpd/ioev.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/ioev.h (renamed from smtpd/ioev.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/libressl.c (renamed from smtpd/libressl.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/limit.c (renamed from smtpd/limit.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka.c (renamed from smtpd/lka.c) | 19 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka_filter.c (renamed from smtpd/lka_filter.c) | 760 | ||||
-rw-r--r-- | usr.sbin/smtpd/lka_session.c (renamed from smtpd/lka_session.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/log.c (renamed from smtpd/log.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/log.h (renamed from smtpd/log.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.lmtp.8 (renamed from smtpd/mail.lmtp.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.lmtp.c (renamed from smtpd/mail.lmtp.c) | 84 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.maildir.8 (renamed from smtpd/mail.maildir.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.maildir.c (renamed from smtpd/mail.maildir.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.mboxfile.8 (renamed from smtpd/mail.mboxfile.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.mboxfile.c (renamed from smtpd/mail.mboxfile.c) | 2 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.mda.8 (renamed from smtpd/mail.mda.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail.mda.c (renamed from smtpd/mail.mda.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mail/Makefile (renamed from smtpd/mail/Makefile) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mailaddr.c (renamed from smtpd/mailaddr.c) | 14 | ||||
-rw-r--r-- | usr.sbin/smtpd/makemap.8 (renamed from smtpd/makemap.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/makemap.c (renamed from smtpd/makemap.c) | 14 | ||||
-rw-r--r-- | usr.sbin/smtpd/mda.c (renamed from smtpd/mda.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mda_mbox.c | 94 | ||||
-rw-r--r-- | usr.sbin/smtpd/mda_unpriv.c (renamed from smtpd/mda_unpriv.c) | 10 | ||||
-rw-r--r-- | usr.sbin/smtpd/mda_variables.c (renamed from smtpd/mda_variables.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/mproc.c (renamed from smtpd/mproc.c) | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta.c (renamed from smtpd/mta.c) | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/mta_session.c (renamed from smtpd/mta_session.c) | 424 | ||||
-rw-r--r-- | usr.sbin/smtpd/newaliases.8 (renamed from smtpd/newaliases.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/parse.y (renamed from smtpd/parse.y) | 326 | ||||
-rw-r--r-- | usr.sbin/smtpd/parser.c (renamed from smtpd/parser.c) | 7 | ||||
-rw-r--r-- | usr.sbin/smtpd/parser.h (renamed from smtpd/parser.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/pony.c (renamed from smtpd/pony.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/proxy.c (renamed from smtpd/proxy.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue.c (renamed from smtpd/queue.c) | 11 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue_backend.c (renamed from smtpd/queue_backend.c) | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue_fs.c (renamed from smtpd/queue_fs.c) | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue_null.c (renamed from smtpd/queue_null.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue_proc.c (renamed from smtpd/queue_proc.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/queue_ram.c (renamed from smtpd/queue_ram.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/report_smtp.c (renamed from smtpd/report_smtp.c) | 24 | ||||
-rw-r--r-- | usr.sbin/smtpd/resolver.c (renamed from smtpd/resolver.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/rfc5322.c (renamed from smtpd/rfc5322.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/rfc5322.h (renamed from smtpd/rfc5322.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/ruleset.c (renamed from smtpd/ruleset.c) | 18 | ||||
-rw-r--r-- | usr.sbin/smtpd/runq.c (renamed from smtpd/runq.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/scheduler.c (renamed from smtpd/scheduler.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/scheduler_backend.c (renamed from smtpd/scheduler_backend.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/scheduler_null.c (renamed from smtpd/scheduler_null.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/scheduler_proc.c (renamed from smtpd/scheduler_proc.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/scheduler_ramqueue.c (renamed from smtpd/scheduler_ramqueue.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/sendmail.8 (renamed from smtpd/sendmail.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.1 (renamed from smtpd/smtp.1) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.c (renamed from smtpd/smtp.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp.h (renamed from smtpd/smtp.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp/Makefile (renamed from smtpd/smtp/Makefile) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_client.c (renamed from smtpd/smtp_client.c) | 13 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtp_session.c (renamed from smtpd/smtp_session.c) | 332 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpc.c (renamed from smtpd/smtpc.c) | 4 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl.8 (renamed from smtpd/smtpctl.8) | 11 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl.c (renamed from smtpd/smtpctl.c) | 15 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpctl/Makefile (renamed from smtpd/smtpctl/Makefile) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd-api.h (renamed from smtpd/smtpd-api.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd-defines.h (renamed from smtpd/smtpd-defines.h) | 10 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd-filters.7 | 653 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.8 (renamed from smtpd/smtpd.8) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.c (renamed from smtpd/smtpd.c) | 115 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.conf (renamed from smtpd/smtpd.conf) | 2 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.conf.5 (renamed from smtpd/smtpd.conf.5) | 278 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd.h (renamed from smtpd/smtpd.h) | 60 | ||||
-rw-r--r-- | usr.sbin/smtpd/smtpd/Makefile (renamed from smtpd/smtpd/Makefile) | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/spfwalk.c (renamed from smtpd/spfwalk.c) | 166 | ||||
-rw-r--r-- | usr.sbin/smtpd/srs.c (renamed from smtpd/srs.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl.c (renamed from smtpd/ssl.c) | 5 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl.h (renamed from smtpd/ssl.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl_smtpd.c (renamed from smtpd/ssl_smtpd.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/ssl_verify.c (renamed from smtpd/ssl_verify.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/stat_backend.c (renamed from smtpd/stat_backend.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/stat_ramstat.c (renamed from smtpd/stat_ramstat.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/table.5 (renamed from smtpd/table.5) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/table.c (renamed from smtpd/table.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/table_db.c (renamed from smtpd/table_db.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/table_getpwnam.c (renamed from smtpd/table_getpwnam.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/table_proc.c (renamed from smtpd/table_proc.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/table_static.c (renamed from smtpd/table_static.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/to.c (renamed from smtpd/to.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/tree.c (renamed from smtpd/tree.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/tree.h (renamed from smtpd/tree.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/unpack_dns.c (renamed from smtpd/unpack_dns.c) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/unpack_dns.h (renamed from smtpd/unpack_dns.h) | 0 | ||||
-rw-r--r-- | usr.sbin/smtpd/util.c (renamed from smtpd/util.c) | 8 | ||||
-rw-r--r-- | usr.sbin/smtpd/waitq.c (renamed from smtpd/waitq.c) | 0 |
240 files changed, 12271 insertions, 2671 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 00000000..51993106 --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,23 @@ +image: freebsd/latest +packages: +- autoconf +- automake +- bison +- libevent +- libtool +- libressl +- libasr +- py37-ansible +- db6 +- python3 +- python37 +- python +sources: +- https://github.com/OpenSMTPD/OpenSMTPD +- https://github.com/OpenSMTPD/ci +tasks: +- ansible: | + ls -lah + BUILD_DIR=$(pwd)/OpenSMTPD + cd ci/ansible + ansible-playbook test.yml --inventory inventory/freebsd --skip-tags checkout --extra-vars "build_dir=$BUILD_DIR" diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml new file mode 100644 index 00000000..97b9634c --- /dev/null +++ b/.builds/openbsd.yml @@ -0,0 +1,16 @@ +image: openbsd/6.8 +packages: +- autoconf-2.69p3 +- automake-1.14.1p1 +- libtool +sources: +- https://github.com/OpenSMTPD/OpenSMTPD +tasks: +- configure: | + cd OpenSMTPD + export AUTOCONF_VERSION=2.69 + ./bootstrap + ./configure +- build: | + cd OpenSMTPD + make diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml index 6f2d29f6..23816eab 100644 --- a/.github/workflows/alpine.yml +++ b/.github/workflows/alpine.yml @@ -2,10 +2,10 @@ name: Alpine Linux (amd64 musl openssl) on: push: branches: - - portable + - master pull_request: branches: - - portable + - master jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml index a3528152..2ee226a2 100644 --- a/.github/workflows/arch.yml +++ b/.github/workflows/arch.yml @@ -2,10 +2,10 @@ name: Archlinux (amd64 glibc libressl) on: push: branches: - - portable + - master pull_request: branches: - - portable + - master jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/fedora-gcc10.yml b/.github/workflows/fedora-gcc10.yml new file mode 100644 index 00000000..698d9d5b --- /dev/null +++ b/.github/workflows/fedora-gcc10.yml @@ -0,0 +1,15 @@ +name: Fedora (amd64 glibc openssl gcc10) +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Fedora (amd64 glibc openssl gcc10) + run: docker build . --file ci/docker/Dockerfile.fedora-gcc10 --tag opensmtpd:fedora-gcc10 diff --git a/.github/workflows/fedora.yml b/.github/workflows/fedora.yml new file mode 100644 index 00000000..448a6eda --- /dev/null +++ b/.github/workflows/fedora.yml @@ -0,0 +1,15 @@ +name: Fedora (amd64 glibc openssl) +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Fedora (amd64 glibc openssl) + run: docker build . --file ci/docker/Dockerfile.fedora --tag opensmtpd:fedora diff --git a/.github/workflows/macos-latest.yml b/.github/workflows/macos-latest.yml new file mode 100644 index 00000000..1960a39a --- /dev/null +++ b/.github/workflows/macos-latest.yml @@ -0,0 +1,23 @@ +name: macOS Catalina 10.15 +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v1 + - name: macOS Catalina 10.15 + run: | + brew install automake libevent openssl@1.1 bison + export PATH="/usr/local/opt/bison/bin:$PATH" + export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/bison/lib" + export CFLAGS=-I/usr/local/opt/openssl@1.1/include + ./bootstrap + ./configure --with-gnu-ld --sysconfdir=/etc/mail --with-auth-pam + make + sudo make install diff --git a/.github/workflows/ubuntu-gcc10.yml b/.github/workflows/ubuntu-gcc10.yml new file mode 100644 index 00000000..e593df39 --- /dev/null +++ b/.github/workflows/ubuntu-gcc10.yml @@ -0,0 +1,15 @@ +name: Ubuntu (amd64 glibc openssl gcc10) +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Ubuntu (amd64 glibc openssl gcc10) + run: docker build . --file ci/docker/Dockerfile.ubuntu-gcc10 --tag opensmtpd:ubuntu-gcc10 diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index c068c6d5..c4bfd437 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -2,10 +2,10 @@ name: Ubuntu (amd64 glibc openssl) on: push: branches: - - portable + - master pull_request: branches: - - portable + - master jobs: build: runs-on: ubuntu-latest @@ -1,3 +1,17 @@ +# Release 6.6.3p1 (2020-02-10) + +Following the 6.6.2p1 release, various improvements were done in OpenBSD -current to mitigate the risk of similar bugs. + +This release back-ports them to the portable version of OpenSMTPD. + +# Release 6.6.2p1 (2020-01-28) + +This is CRITICAL security bugfix for +[CVE-2020-7247](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7247) + +Read more details in +[this blog post](https://poolp.org/posts/2020-01-30/opensmtpd-advisory-dissected/) + # Release 6.6.1p1 (2019-11-06) ## Changes in this release (since 6.6.0p1) @@ -1,10 +1,10 @@ # OpenSMTPD -[![Version](https://img.shields.io/badge/Version-6.6.1p1-brihtgreen.svg)](https://github.com/OpenSMTPD/OpenSMTPD/releases/tag/6.6.1p1) +[![Version](https://img.shields.io/badge/Version-6.7.1p1-brihtgreen.svg)](https://github.com/OpenSMTPD/OpenSMTPD/releases/tag/6.7.1p1) [![Coverity Scan analysis](https://scan.coverity.com/projects/278/badge.svg)](https://scan.coverity.com/projects/opensmtpd-opensmtpd) [![Packaging status](https://repology.org/badge/tiny-repos/opensmtpd.svg)](https://repology.org/project/opensmtpd/versions) [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://www.isc.org/licenses/) -[![Clang Analysis](https://opensmtpd.email/reports/clang/badge.svg)](https://opensmtpd.email/reports/clang/index.html) + OpenSMTPD is a FREE implementation of the server-side SMTP protocol as @@ -29,9 +29,8 @@ mailing list: http://www.opensmtpd.org/list.html and to join the IRC channel: #OpenSMTPD @ irc.freenode.net -Also note that we have a wiki at -https://github.com/OpenSMTPD/OpenSMTPD/wiki that you are encouraged to -contribute to. +The manual pages are available online at https://www.opensmtpd.org/manual.html, +which you are encouraged to contribute to. Cheers! @@ -49,14 +48,16 @@ Portable OpenSMTPD relies on: * libtool (http://www.gnu.org/software/libtool/) * libressl (https://www.libressl.org/) or OpenSSL (https://www.openssl.org/) - * libasr (https://opensmtpd.org/archives/libasr-1.0.3.tar.gz) -By default OpenSMTPD expects latest versions of all dependencies unless noted otherwise +By default OpenSMTPD expects latest versions of all dependencies unless noted otherwise. + +Note that some distributions have different packages for a same library, you should always use the `-dev` or `-devel` package (for example, `libevent-dev` or `libevent-devel`) if you're going to build OpenSMTPD yourself. + ## Get the source - git clone -b portable git://github.com/OpenSMTPD/OpenSMTPD.git opensmtpd + git clone git://github.com/OpenSMTPD/OpenSMTPD.git opensmtpd ## Build @@ -76,10 +77,12 @@ libasr directory: ./configure --with-libasr=/usr/local -### Mac OS X: +### MacOS: - ./configure --with-libevent=/opt/local --with-libasr=/opt/local + ./configure --with-libevent=/opt/local +Though MacOS includes a copy of bison in the bases system, you will +need to install a more recent version from, e.g., MacPorts. ## Install @@ -143,7 +146,7 @@ script allows overriding these using the options: ### NetBSD, Linux (Debian, Arch Linux, ...) - mkdir /var/empty + mkdir /var/empty useradd -c "SMTP Daemon" -d /var/empty -s /sbin/nologin _smtpd useradd -c "SMTPD Queue" -d /var/empty -s /sbin/nologin _smtpq diff --git a/ci/docker/Dockerfile.alpine b/ci/docker/Dockerfile.alpine index 01069644..2c7c66fc 100644 --- a/ci/docker/Dockerfile.alpine +++ b/ci/docker/Dockerfile.alpine @@ -1,10 +1,4 @@ -FROM alpine:3.10 as build - -# Allow container to expose ports at runtime, if necessary -# https://docs.docker.com/engine/reference/#expose -EXPOSE 25 -EXPOSE 465 -EXPOSE 587 +FROM alpine:3.11 as build # creates /opensmtpd dir and makes all following commands to run in it # https://docs.docker.com/engine/reference/builder/#workdir @@ -19,15 +13,14 @@ RUN apk add --no-cache \ fts-dev \ gcc \ fts \ - libasr-dev \ libevent-dev \ libtool \ libtool \ linux-pam-dev \ make \ musl-dev \ - openssl \ - openssl-dev \ + libressl \ + libressl-dev \ zlib-dev # create users and directories diff --git a/ci/docker/Dockerfile.archlinux b/ci/docker/Dockerfile.archlinux index 73e0d24d..dcd46684 100644 --- a/ci/docker/Dockerfile.archlinux +++ b/ci/docker/Dockerfile.archlinux @@ -22,7 +22,6 @@ RUN pacman -Suy --noconfirm \ libtool \ bison \ gettext \ - libasr \ libevent \ libressl \ pam \ diff --git a/ci/docker/Dockerfile.fedora b/ci/docker/Dockerfile.fedora new file mode 100644 index 00000000..d6c01bdf --- /dev/null +++ b/ci/docker/Dockerfile.fedora @@ -0,0 +1,51 @@ +FROM fedora:latest + +# Allow container to expose ports at runtime, if necessary +# https://docs.docker.com/engine/reference/#expose +EXPOSE 25 +EXPOSE 465 +EXPOSE 587 + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN dnf -y install \ + autoconf \ + automake \ + bison \ + @development-tools \ + libevent-devel \ + openssl-devel \ + libtool \ + pam-devel \ + zlib-devel + +# create users and directories +RUN mkdir -p /var/lib/opensmtpd/empty \ + && useradd _smtpd \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && useradd _smtpq \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + +RUN ./bootstrap \ + && ./configure \ + --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-auth-pam \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases diff --git a/ci/docker/Dockerfile.fedora-gcc10 b/ci/docker/Dockerfile.fedora-gcc10 new file mode 100644 index 00000000..1ab3bcbd --- /dev/null +++ b/ci/docker/Dockerfile.fedora-gcc10 @@ -0,0 +1,54 @@ +FROM fedora:32 + +# Allow container to expose ports at runtime, if necessary +# https://docs.docker.com/engine/reference/#expose +EXPOSE 25 +EXPOSE 465 +EXPOSE 587 + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN dnf -y install \ + autoconf \ + automake \ + bison \ + @development-tools \ + libevent-devel \ + openssl-devel \ + libtool \ + pam-devel \ + zlib-devel \ + libdb-devel + +# create users and directories +RUN mkdir -p /var/lib/opensmtpd/empty \ + && useradd _smtpd \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && useradd _smtpq \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + +RUN export CC=gcc-10 CXX=g++-10 +RUN ./bootstrap \ + && ./configure \ + --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-auth-pam \ + --with-table-db \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases diff --git a/ci/docker/Dockerfile.ubuntu b/ci/docker/Dockerfile.ubuntu index a5301135..6626033d 100644 --- a/ci/docker/Dockerfile.ubuntu +++ b/ci/docker/Dockerfile.ubuntu @@ -17,7 +17,6 @@ RUN apt update \ automake \ bison \ build-essential \ - libasr-dev \ libevent-dev \ libssl-dev \ libtool \ diff --git a/ci/docker/Dockerfile.ubuntu-gcc10 b/ci/docker/Dockerfile.ubuntu-gcc10 new file mode 100644 index 00000000..2ebbdf58 --- /dev/null +++ b/ci/docker/Dockerfile.ubuntu-gcc10 @@ -0,0 +1,54 @@ +FROM ubuntu:latest + +# Allow container to expose ports at runtime, if necessary +# https://docs.docker.com/engine/reference/#expose +EXPOSE 25 +EXPOSE 465 +EXPOSE 587 + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN apt update \ + && apt install -y --no-install-recommends \ + autoconf \ + automake \ + bison \ + build-essential \ + libevent-dev \ + libssl-dev \ + libtool \ + libpam0g-dev \ + zlib1g-dev \ + gcc-10 + +# create users and directories +RUN mkdir -p /var/lib/opensmtpd/empty \ + && useradd _smtpd \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && useradd _smtpq \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + +RUN export CC=gcc-10 CXX=g++-10 +RUN ./bootstrap \ + && ./configure \ + --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-auth-pam \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases diff --git a/configure.ac b/configure.ac index 354a80d0..d46aeae7 100644 --- a/configure.ac +++ b/configure.ac @@ -173,6 +173,16 @@ AC_CHECK_HEADER([fts.h], #include <sys/stat.h> ]) +need_libasr=no +AC_CHECK_HEADER([asr.h], + [], + [need_libasr=yes], + [ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +]) +AM_CONDITIONAL([NEED_LIBASR], [test x"$need_libasr" = x"yes"]) # # CHECKS FOR TYPES @@ -466,6 +476,20 @@ AC_SEARCH_LIBS([fparseln], [Define if you have the fparseln() function.]) ]) +AC_SEARCH_LIBS([res_hnok], + [resolv], + [ + AC_DEFINE([HAVE_RES_HNOK], [1], + [Define if you have the res_hnok() function.]) + ]) + +AC_SEARCH_LIBS([res_randomid], + [resolv], + [ + AC_DEFINE([HAVE_RES_RANDOMID], [1], + [Define if you have the res_randomid() function.]) + ]) + AC_SEARCH_LIBS([res_9_b64_ntop], [resolv], [ @@ -526,7 +550,7 @@ AC_SEARCH_LIBS([event_asr_run], [event], [ AC_DEFINE([HAVE_EVENT_ASR_RUN], [1], - [Define if you have the basename() function.]) + [Define if you have the event_asr_run() function.]) ]) AC_CHECK_FUNCS([ \ @@ -563,11 +587,13 @@ AC_CHECK_FUNCS([ \ memmove \ nanosleep \ nsleep \ + pipe2 \ pidfile \ pledge \ reallocarray \ recallocarray \ res_hnok \ + res_randomid \ setenv \ seteuid \ setegid \ @@ -864,7 +890,6 @@ fi #l432 (customized) # Check for some target-specific stuff -ASR_LIB=-lasr case "$host" in *-*-darwin*) @@ -894,6 +919,7 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) supported by bsd-setproctitle.c]) AC_DEFINE([BROKEN_STRNVIS], [1], [OSX strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=1 ;; *-*-dragonfly*) ;; @@ -908,11 +934,13 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) fi AC_DEFINE([BROKEN_STRNVIS], [1], [NetBSD strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=1 ;; *-*-freebsd*) AC_DEFINE([BROKEN_GLOB], [1], [FreeBSD glob does not do what we need]) AC_DEFINE([BROKEN_STRNVIS], [1], [FreeBSD strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=1 ;; *-*-openbsd*) use_pie=auto @@ -921,6 +949,7 @@ main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) AC_DEFINE([BROKEN_STRNVIS], [0], [FreeBSD strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=0 YACC='yacc' ASR_LIB= AC_DEFINE([NOOP_ASR_FREEADDRINFO], [0], [OpenBSD doesn't need ASR_FREEADDRINFO]) @@ -1615,61 +1644,6 @@ AC_EXEEXT #l4757 -# Search for asr (based on zlib checks) -dnl asr is required -AC_ARG_WITH([libasr], - [ --with-libasr=PATH Specify path to libasr installation], - [ if test "x$withval" = "xno"; then - AC_MSG_ERROR([*** asr is required ***]) - elif test "x$withval" != "xyes"; then - if test -d "$withval/lib"; then - if test -n "${need_dash_r}"; then - LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" - else - LDFLAGS="-L${withval}/lib ${LDFLAGS}" - fi - else - if test -n "${need_dash_r}"; then - LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" - else - LDFLAGS="-L${withval} ${LDFLAGS}" - fi - fi - if test -d "$withval/include"; then - CPPFLAGS="-I${withval}/include ${CPPFLAGS}" - else - CPPFLAGS="-I${withval} ${CPPFLAGS}" - fi - fi ] -) - -AC_CHECK_HEADER([asr.h], ,[AC_MSG_ERROR([*** asr.h missing - please install libasr ***])], -[#include <sys/types.h> -#include <sys/socket.h> -#include <netinet/in.h>]) -AC_CHECK_LIB([asr], [asr_run], , - [ - saved_CPPFLAGS="$CPPFLAGS" - saved_LDFLAGS="$LDFLAGS" - save_LIBS="$LIBS" - dnl Check default asr install dir - if test -n "${need_dash_r}"; then - LDFLAGS="-L/usr/local/lib -R/usr/local/lib ${saved_LDFLAGS}" - else - LDFLAGS="-L/usr/local/lib ${saved_LDFLAGS}" - fi - CPPFLAGS="-I/usr/local/include ${saved_CPPFLAGS}" -# don't add -lasr to LIBS, it's only added when needed -# LIBS="$LIBS -lasr" - AC_TRY_LINK_FUNC([asr_run], , - [ - AC_MSG_ERROR([*** libasr missing - please install first or check config.log ***]) - ] - ) - ] -) - - # Search for fts AC_ARG_WITH([libfts], [ --with-libfts=PATH Specify path to libfts installation (default: none, part of libc)], @@ -2007,6 +1981,12 @@ AM_CONDITIONAL([HAVE_DB_API], [test "x$use_db_api" = "x1"]) AM_COND_IF([HAVE_DB_API], [AC_DEFINE([HAVE_DB_API], [1], [Define to 1 if HAVE_DB_API])]) + +if test "$need_libasr" = "no" -a "x$ac_cv_search_event_asr_run" = "xno"; then + LIBS="$LIBS -lasr" +fi + + LIBS="$LIBS ${SMTPDLIBS}" ##end of chl @@ -2035,6 +2015,7 @@ AM_CONDITIONAL([NEED_GETOPT], [test "x$ac_cv_func_getopt" != "xyes"]) AM_CONDITIONAL([NEED_GETPEEREID], [test "x$ac_cv_func_getpeereid" != "xyes"]) AM_CONDITIONAL([NEED_NANOSLEEP], [test "x$ac_cv_func_nanosleep" != "xyes"]) AM_CONDITIONAL([NEED_PIDFILE], [test "x$ac_cv_func_pidfile" != "xyes"]) +AM_CONDITIONAL([NEED_PIPE2], [test "x$ac_cv_func_pipe2" != "xyes"]) AM_CONDITIONAL([NEED_REALLOCARRAY], [test "x$ac_cv_func_reallocarray" != "xyes"]) AM_CONDITIONAL([NEED_RECALLOCARRAY], [test "x$ac_cv_func_recallocarray" != "xyes"]) AM_CONDITIONAL([NEED_SETPROCTITLE], [test "x$ac_cv_func_setproctitle" != "xyes"]) @@ -2055,6 +2036,9 @@ AM_CONDITIONAL([NEED_WAITPID], [test "x$ac_cv_func_waitpid" != "xyes"]) AM_CONDITIONAL([NEED_VIS], [test "x$ac_cv_func_strnvis" != "xyes" -o "x$BROKEN_STRNVIS" = "x1"]) AM_CONDITIONAL([NEED_USLEEP], [test "x$ac_cv_func_usleep" != "xyes"]) +AM_CONDITIONAL([NEED_RES_HNOK], [test "x$ac_cv_search_res_hnok" = "xno" -a x"$ac_cv_func_res_hnok" != "xyes" -a x"$need_libasr" = x"yes"]) +AM_CONDITIONAL([NEED_RES_RANDOMID], [test "x$ac_cv_search_res_randomid" = "xno" -a x"$ac_cv_func_res_randomid" != "xyes" -a x"$need_libasr" = x"yes"]) + AM_CONDITIONAL([NEED_ARC4RANDOM], [test "x$ac_cv_func_arc4random" != "xyes" -a "x$ac_cv_have_decl_LIBRESSL_VERSION_NUMBER" != "xyes"]) AM_CONDITIONAL([NEED_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM], [test "x$ac_cv_have_decl_LIBRESSL_VERSION_NUMBER" != "xyes"]) @@ -2062,6 +2046,10 @@ AM_CONDITIONAL([NEED_PROGNAME], [test "x$ac_cv_libc_defines___progname" != "xyes ## +AM_COND_IF([NEED_PROGNAME], [AC_DEFINE([NEED_PROGNAME], [1], [Define to 1 if NEED_PROGNAME])]) +AM_COND_IF([NEED_SETPROCTITLE], [AC_DEFINE([NEED_SETPROCTITLE], [1], [Define to 1 if NEED_SETPROCTITLE])]) + + AC_CONFIG_FILES([Makefile openbsd-compat/Makefile mk/Makefile @@ -2076,6 +2064,7 @@ AC_CONFIG_FILES([Makefile contrib/Makefile contrib/libexec/Makefile contrib/libexec/mail.local/Makefile + contrib/libexec/lockspool/Makefile contrib/libexec/encrypt/Makefile ]) diff --git a/contrib/CVS/Entries b/contrib/CVS/Entries deleted file mode 100644 index 82a9d101..00000000 --- a/contrib/CVS/Entries +++ /dev/null @@ -1,4 +0,0 @@ -D/lib//// -D/libexec//// -/Makefile/-1.50/Mon Mar 5 11:15:41 2012// -/Makefile.inc/-1.2/Sun Jan 28 19:34:26 2001// diff --git a/contrib/CVS/Repository b/contrib/CVS/Repository deleted file mode 100644 index 31ac4266..00000000 --- a/contrib/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -src/libexec diff --git a/contrib/CVS/Root b/contrib/CVS/Root deleted file mode 100644 index 7040dfb5..00000000 --- a/contrib/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -anoncvs@anoncvs.spacehopper.org:/cvs diff --git a/contrib/libexec/CVS/Entries b/contrib/libexec/CVS/Entries deleted file mode 100644 index cc09ac02..00000000 --- a/contrib/libexec/CVS/Entries +++ /dev/null @@ -1,3 +0,0 @@ -D/mail.local//// -/Makefile/1.50/Mon Jul 9 11:41:26 2012// -/Makefile.inc/1.2/Mon Jul 9 11:41:26 2012// diff --git a/contrib/libexec/CVS/Repository b/contrib/libexec/CVS/Repository deleted file mode 100644 index 31ac4266..00000000 --- a/contrib/libexec/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -src/libexec diff --git a/contrib/libexec/CVS/Root b/contrib/libexec/CVS/Root deleted file mode 100644 index 7040dfb5..00000000 --- a/contrib/libexec/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -anoncvs@anoncvs.spacehopper.org:/cvs diff --git a/contrib/libexec/Makefile.am b/contrib/libexec/Makefile.am index 6079ae6a..0e3a271f 100644 --- a/contrib/libexec/Makefile.am +++ b/contrib/libexec/Makefile.am @@ -1 +1 @@ -SUBDIRS = mail.local encrypt +SUBDIRS = mail.local lockspool encrypt diff --git a/contrib/libexec/encrypt/Makefile.am b/contrib/libexec/encrypt/Makefile.am index 6ad7b82d..2f96e60d 100644 --- a/contrib/libexec/encrypt/Makefile.am +++ b/contrib/libexec/encrypt/Makefile.am @@ -1,7 +1,7 @@ pkglibexec_PROGRAMS = encrypt encrypt_SOURCES = encrypt.c -encrypt_SOURCES += $(top_srcdir)/smtpd/log.c +encrypt_SOURCES += $(top_srcdir)/usr.sbin/smtpd/log.c AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat diff --git a/contrib/libexec/lockspool/Makefile.am b/contrib/libexec/lockspool/Makefile.am new file mode 100644 index 00000000..2801c101 --- /dev/null +++ b/contrib/libexec/lockspool/Makefile.am @@ -0,0 +1,20 @@ +pkglibexec_PROGRAMS = lockspool + +lockspool_SOURCES = lockspool.c +lockspool_SOURCES += locking.c +lockspool_SOURCES += $(top_srcdir)/usr.sbin/smtpd/log.c + +EXTRA_DIST = mail.local.h pathnames.h + +AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat -I$(top_srcdir)/mail.local + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + +install-exec-hook: $(CONFIGFILES) $(MANPAGES) + chown root $(DESTDIR)$(pkglibexecdir)/lockspool || true + chmod 4555 $(DESTDIR)$(pkglibexecdir)/lockspool || true + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/contrib/libexec/lockspool/locking.c b/contrib/libexec/lockspool/locking.c new file mode 100644 index 00000000..e4922dd6 --- /dev/null +++ b/contrib/libexec/lockspool/locking.c @@ -0,0 +1,181 @@ +/* $OpenBSD: locking.c,v 1.14 2020/02/09 14:59:20 millert Exp $ */ + +/* + * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com> + * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu> + * All rights reserved. + * + * 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 above 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. The name of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 AUTHORS 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. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pwd.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include "pathnames.h" +#include "mail.local.h" + +static char lpath[PATH_MAX]; + +void +rellock(void) +{ + + if (lpath[0]) + unlink(lpath); +} + +int +getlock(const char *name, struct passwd *pw) +{ + struct stat sb, fsb; + int lfd=-1; + char buf[8*1024]; + int tries = 0; + + (void)snprintf(lpath, sizeof lpath, "%s/%s.lock", + _PATH_MAILDIR, name); + + if (stat(_PATH_MAILDIR, &sb) != -1 && + (sb.st_mode & S_IWOTH) == S_IWOTH) { + /* + * We have a writeable spool, deal with it as + * securely as possible. + */ + time_t ctim = -1; + + seteuid(pw->pw_uid); + if (lstat(lpath, &sb) != -1) + ctim = sb.st_ctime; + while (1) { + /* + * Deal with existing user.lock files + * or directories or symbolic links that + * should not be here. + */ + if (readlink(lpath, buf, sizeof buf-1) != -1) { + if (lstat(lpath, &sb) != -1 && + S_ISLNK(sb.st_mode)) { + seteuid(sb.st_uid); + unlink(lpath); + seteuid(pw->pw_uid); + } + goto again; + } + if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL|O_EXLOCK, + S_IRUSR|S_IWUSR)) != -1) + break; +again: + if (tries > 10) { + mwarn("%s: %s", lpath, strerror(errno)); + seteuid(0); + return(-1); + } + if (tries > 9 && + (lfd = open(lpath, O_WRONLY|O_EXLOCK, 0)) != -1) { + if (fstat(lfd, &fsb) != -1 && + lstat(lpath, &sb) != -1) { + if (fsb.st_dev == sb.st_dev && + fsb.st_ino == sb.st_ino && + ctim == fsb.st_ctime ) { + seteuid(fsb.st_uid); + baditem(lpath); + seteuid(pw->pw_uid); + } + } + close(lfd); + } + sleep(1U << tries); + tries++; + continue; + } + seteuid(0); + } else { + /* + * Only root can write the spool directory. + */ + while (1) { + if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL, + S_IRUSR|S_IWUSR)) != -1) + break; + if (tries > 9) { + mwarn("%s: %s", lpath, strerror(errno)); + return(-1); + } + sleep(1U << tries); + tries++; + } + } + return(lfd); +} + +void +baditem(char *path) +{ + char npath[PATH_MAX]; + int fd; + + if (unlink(path) == 0) + return; + snprintf(npath, sizeof npath, "%s/mailXXXXXXXXXX", _PATH_MAILDIR); + if ((fd = mkstemp(npath)) == -1) + return; + close(fd); + if (rename(path, npath) == -1) + unlink(npath); + else + mwarn("nasty spool item %s renamed to %s", path, npath); + /* XXX if we fail to rename, another attempt will happen later */ +} + +void +mwarn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); +} + +void +merr(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); + exit(eval); +} diff --git a/contrib/libexec/lockspool/lockspool.1 b/contrib/libexec/lockspool/lockspool.1 new file mode 100644 index 00000000..ea5524bf --- /dev/null +++ b/contrib/libexec/lockspool/lockspool.1 @@ -0,0 +1,77 @@ +.\" $OpenBSD: lockspool.1,v 1.14 2019/01/25 00:19:26 millert Exp $ +.\" +.\" Copyright (c) 1998 Todd C. Miller <millert@openbsd.org> +.\" +.\" 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 25 2019 $ +.Dt LOCKSPOOL 1 +.Os +.Sh NAME +.Nm lockspool +.Nd lock user's system mailbox +.Sh SYNOPSIS +.Nm lockspool +.Op Ar username +.Sh DESCRIPTION +.Nm +is useful for a client mail program to attain proper locking. +.Nm +obtains a +.Pa username.lock +for the calling user and retains it until stdin is closed or a signal like +.Dv SIGINT , +.Dv SIGTERM , +or +.Dv SIGHUP +is received. +Additionally, the superuser may specify the name of a user in order +to lock a different mailbox. +.Pp +If +.Nm +is able to create the lock file, +.Dq 1 +is written to stdout, otherwise +.Dq 0 +is written and an error message is written to stderr. +.Nm +will try up to 10 times to get the lock (sleeping +for a short period in between tries). +.Pp +Typical usage is for a user mail agent (such as +.Xr mail 1 ) +to open a pipe to +.Nm +when it needs to lock the user's mail spool. +Closing the pipe will cause +.Nm +to release the lock. +.Sh FILES +.Bl -tag -width /var/mail/username.lock -compact +.It Pa /var/mail/username.lock +user's mail lock file +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and 1 if an error occurs. +.Sh SEE ALSO +.Xr mail 1 , +.Xr mail.local 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +program appeared in +.Ox 2.4 . diff --git a/contrib/libexec/lockspool/lockspool.c b/contrib/libexec/lockspool/lockspool.c new file mode 100644 index 00000000..9277241b --- /dev/null +++ b/contrib/libexec/lockspool/lockspool.c @@ -0,0 +1,124 @@ +/* $OpenBSD: lockspool.c,v 1.21 2020/02/09 14:59:20 millert Exp $ */ + +/* + * Copyright (c) 1998 Theo de Raadt <deraadt@theos.com> + * Copyright (c) 1998 Todd C. Miller <millert@openbsd.org> + * All rights reserved. + * + * 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 above 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. + * + * THIS SOFTWARE IS PROVIDED ``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 AUTHORS 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. + */ + +#include "includes.h" + +#include <signal.h> +#include <pwd.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <paths.h> +#include <stdlib.h> +#include <poll.h> +#include <err.h> + +#include "mail.local.h" + +void unhold(int); +void usage(void); + +extern char *__progname; + +int +main(int argc, char *argv[]) +{ + struct passwd *pw; + struct pollfd pfd; + ssize_t nread; + char *from, c; + int holdfd; + +#if HAVE_UNVEIL + if (unveil(_PATH_MAILDIR, "rwc") == -1) + err(1, "unveil"); +#endif +#if HAVE_PLEDGE + if (pledge("stdio rpath wpath getpw cpath fattr", NULL) == -1) + err(1, "pledge"); +#endif + + openlog(__progname, LOG_PERROR, LOG_MAIL); + + if (argc != 1 && argc != 2) + usage(); + if (argc == 2 && getuid() != 0) + merr(1, "you must be root to lock someone else's spool"); + + signal(SIGTERM, unhold); + signal(SIGINT, unhold); + signal(SIGHUP, unhold); + signal(SIGPIPE, unhold); + + if (argc == 2) + pw = getpwnam(argv[1]); + else + pw = getpwuid(getuid()); + if (pw == NULL) + exit (1); + from = pw->pw_name; + + holdfd = getlock(from, pw); + if (holdfd == -1) { + write(STDOUT_FILENO, "0\n", 2); + exit (1); + } + write(STDOUT_FILENO, "1\n", 2); + + /* wait for the other end of the pipe to close, then release the lock */ + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; + do { + if (poll(&pfd, 1, INFTIM) == -1) { + if (errno != EINTR) + break; + } + do { + nread = read(STDIN_FILENO, &c, 1); + } while (nread == 1 || (nread == -1 && errno == EINTR)); + } while (nread == -1 && errno == EAGAIN); + rellock(); + exit (0); +} + +/*ARGSUSED*/ +void +unhold(int signo) +{ + + rellock(); + _exit(0); +} + +void +usage(void) +{ + + merr(1, "usage: %s [username]", __progname); +} diff --git a/contrib/libexec/lockspool/mail.local.h b/contrib/libexec/lockspool/mail.local.h new file mode 100644 index 00000000..bc3137cb --- /dev/null +++ b/contrib/libexec/lockspool/mail.local.h @@ -0,0 +1,42 @@ +/* $OpenBSD: mail.local.h,v 1.7 2020/02/09 14:59:21 millert Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * 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 above 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. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS 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. + */ + +void baditem(char *); +int deliver(int, char *, int); +void merr(int, const char *, ...); +void mwarn(const char *, ...); +int getlock(const char *, struct passwd *); +void notifybiff(char *); +void rellock(void); +int storemail(char *); +int lockspool(const char *, struct passwd *); +void unlockspool(void); +void usage(void); diff --git a/contrib/libexec/lockspool/pathnames.h b/contrib/libexec/lockspool/pathnames.h new file mode 100644 index 00000000..0a2c2731 --- /dev/null +++ b/contrib/libexec/lockspool/pathnames.h @@ -0,0 +1,38 @@ +/* $OpenBSD: pathnames.h,v 1.5 2003/06/02 19:38:24 millert Exp $*/ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * 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 above 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. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS 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. + * + * from: @(#)pathnames.h 5.3 (Berkeley) 1/17/91 + */ +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif + +#define _PATH_LOCTMP "/tmp/local.XXXXXXXXXX" +#define _PATH_LOCKSPOOL PATH_LIBEXEC"/lockspool" diff --git a/contrib/libexec/mail.local/CVS/Entries b/contrib/libexec/mail.local/CVS/Entries deleted file mode 100644 index e02ca255..00000000 --- a/contrib/libexec/mail.local/CVS/Entries +++ /dev/null @@ -1,7 +0,0 @@ -/locking.c/1.10/Mon Jan 10 21:00:50 2011// -/mail.local.c/1.32/Tue Oct 27 23:59:31 2009// -/Makefile/1.3/Mon Jul 9 11:41:26 2012// -/mail.local.8/1.29/Mon Jul 9 11:36:35 2012// -/mail.local.h/1.5/Mon Jul 9 11:36:35 2012// -/pathnames.h/1.5/Mon Jul 9 11:36:35 2012// -D diff --git a/contrib/libexec/mail.local/CVS/Repository b/contrib/libexec/mail.local/CVS/Repository deleted file mode 100644 index ca372a1e..00000000 --- a/contrib/libexec/mail.local/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -src/libexec/mail.local diff --git a/contrib/libexec/mail.local/CVS/Root b/contrib/libexec/mail.local/CVS/Root deleted file mode 100644 index 7040dfb5..00000000 --- a/contrib/libexec/mail.local/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -anoncvs@anoncvs.spacehopper.org:/cvs diff --git a/contrib/libexec/mail.local/Makefile.am b/contrib/libexec/mail.local/Makefile.am index 97325c74..217659c1 100644 --- a/contrib/libexec/mail.local/Makefile.am +++ b/contrib/libexec/mail.local/Makefile.am @@ -2,11 +2,11 @@ pkglibexec_PROGRAMS = mail.local mail_local_SOURCES = mail.local.c mail_local_SOURCES += locking.c -mail_local_SOURCES += $(top_srcdir)/smtpd/log.c +mail_local_SOURCES += $(top_srcdir)/usr.sbin/smtpd/log.c EXTRA_DIST = mail.local.h pathnames.h -AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat +AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat -DPATH_LIBEXEC=\"$(pkglibexecdir)\" LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a diff --git a/contrib/libexec/mail.local/locking.c b/contrib/libexec/mail.local/locking.c index ab6eb1a8..85a48d5e 100644 --- a/contrib/libexec/mail.local/locking.c +++ b/contrib/libexec/mail.local/locking.c @@ -1,4 +1,4 @@ -/* $OpenBSD: locking.c,v 1.10 2011/01/10 21:00:50 millert Exp $ */ +/* $OpenBSD: locking.c,v 1.14 2020/02/09 14:59:20 millert Exp $ */ /* * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com> @@ -30,13 +30,15 @@ #include "includes.h" -#include <sys/param.h> +#include <sys/types.h> + #include <sys/stat.h> #include <fcntl.h> #include <pwd.h> #include <syslog.h> #include <time.h> #include <unistd.h> +#include <limits.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> @@ -45,7 +47,7 @@ #include "pathnames.h" #include "mail.local.h" -static char lpath[MAXPATHLEN]; +static char lpath[PATH_MAX]; void rellock(void) @@ -56,7 +58,7 @@ rellock(void) } int -getlock(char *name, struct passwd *pw) +getlock(const char *name, struct passwd *pw) { struct stat sb, fsb; int lfd=-1; @@ -66,9 +68,8 @@ getlock(char *name, struct passwd *pw) (void)snprintf(lpath, sizeof lpath, "%s/%s.lock", _PATH_MAILDIR, name); - if (stat(_PATH_MAILDIR, &sb) == -1) - merr(FATAL, "%s: %s", _PATH_MAILDIR, strerror(errno)); - if ((sb.st_mode & S_IWOTH) == S_IWOTH) { + if (stat(_PATH_MAILDIR, &sb) != -1 && + (sb.st_mode & S_IWOTH) == S_IWOTH) { /* * We have a writeable spool, deal with it as * securely as possible. @@ -93,19 +94,12 @@ getlock(char *name, struct passwd *pw) } goto again; } -#ifndef O_EXLOCK -#define O_EXLOCK 0 -#endif if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL|O_EXLOCK, S_IRUSR|S_IWUSR)) != -1) break; -#ifndef O_EXLOCK - /* XXX : do something! */ -#endif again: if (tries > 10) { - merr(NOTFATAL, "%s: %s", lpath, - strerror(errno)); + mwarn("%s: %s", lpath, strerror(errno)); seteuid(0); return(-1); } @@ -121,6 +115,7 @@ again: seteuid(pw->pw_uid); } } + close(lfd); } sleep(1U << tries); tries++; @@ -136,7 +131,7 @@ again: S_IRUSR|S_IWUSR)) != -1) break; if (tries > 9) { - merr(NOTFATAL, "%s: %s", lpath, strerror(errno)); + mwarn("%s: %s", lpath, strerror(errno)); return(-1); } sleep(1U << tries); @@ -149,7 +144,7 @@ again: void baditem(char *path) { - char npath[MAXPATHLEN]; + char npath[PATH_MAX]; int fd; if (unlink(path) == 0) @@ -161,19 +156,27 @@ baditem(char *path) if (rename(path, npath) == -1) unlink(npath); else - merr(NOTFATAL, "nasty spool item %s renamed to %s", - path, npath); + mwarn("nasty spool item %s renamed to %s", path, npath); /* XXX if we fail to rename, another attempt will happen later */ } void -merr(int isfatal, const char *fmt, ...) +mwarn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); +} + +void +merr(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_ERR, fmt, ap); va_end(ap); - if (isfatal) - exit(1); + exit(eval); } diff --git a/contrib/libexec/mail.local/mail.local.8 b/contrib/libexec/mail.local/mail.local.8 index f77fa6da..330a4473 100644 --- a/contrib/libexec/mail.local/mail.local.8 +++ b/contrib/libexec/mail.local/mail.local.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: mail.local.8,v 1.29 2010/09/03 11:35:08 jmc Exp $ +.\" $OpenBSD: mail.local.8,v 1.31 2014/09/16 21:28:51 jmc Exp $ .\" Copyright (c) 1990 The Regents of the University of California. .\" All rights reserved. .\" @@ -28,7 +28,7 @@ .\" .\" from: @(#)mail.local.8 6.8 (Berkeley) 4/27/91 .\" -.Dd $Mdocdate: September 3 2010 $ +.Dd $Mdocdate: September 16 2014 $ .Dt MAIL.LOCAL 8 .Os .Sh NAME @@ -125,7 +125,7 @@ user's mailbox directory .Xr flock 2 , .Xr getservbyname 3 , .Xr comsat 8 , -.Xr sendmail 8 +.Xr smtpd 8 .Sh HISTORY A superset of .Nm @@ -134,17 +134,12 @@ A superset of as the program .Xr mail 1 . .Sh BUGS -Since -.Xr sendmail 8 -bases its idea of whether a message has been delivered or not -on the return value from -.Nm mail.local , -using quotas in +Using quotas in .Pa /var/mail -can be problematic. -By default, +can be problematic if using .Xr sendmail 8 -will ask +as an MTA, +since it asks .Nm to deliver a message to multiple recipients if possible. This causes problems in a quota environment since a message may be @@ -157,8 +152,13 @@ to attempt redelivery later. That means that some users will keep getting the same message every time .Xr sendmail 8 runs its queue. +This problem does not exist for +.Xr smtpd 8 +users. .Pp -If you are running with disk quotas on +If you are running +.Xr sendmail 8 +and have disk quotas on .Pa /var/mail it is imperative that you unset the .Dq m diff --git a/contrib/libexec/mail.local/mail.local.c b/contrib/libexec/mail.local/mail.local.c index d1f7d2f2..a574b3fe 100644 --- a/contrib/libexec/mail.local/mail.local.c +++ b/contrib/libexec/mail.local/mail.local.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mail.local.c,v 1.32 2009/10/27 23:59:31 deraadt Exp $ */ +/* $OpenBSD: mail.local.c,v 1.39 2020/02/09 14:59:20 millert Exp $ */ /*- * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com> @@ -33,20 +33,25 @@ #include "includes.h" -#include <sys/param.h> +#include <sys/types.h> + #include <sys/stat.h> #include <sys/socket.h> +#include <sys/wait.h> #include <netinet/in.h> +#include <sysexits.h> #include <syslog.h> #include <fcntl.h> #include <netdb.h> #include <pwd.h> #include <time.h> #include <unistd.h> +#include <limits.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <signal.h> #include "pathnames.h" #include "mail.local.h" @@ -54,21 +59,21 @@ int main(int argc, char *argv[]) { struct passwd *pw; - int ch, fd, eval, lockfile=1, holdme=0; + int ch, fd, eval, lockfile=1; uid_t uid; char *from; openlog("mail.local", LOG_PERROR, LOG_MAIL); from = NULL; - while ((ch = getopt(argc, argv, "lLdf:r:H")) != -1) + while ((ch = getopt(argc, argv, "lLdf:r:")) != -1) switch (ch) { case 'd': /* backward compatible */ break; case 'f': case 'r': /* backward compatible */ if (from) - merr(FATAL, "multiple -f options"); + merr(EX_USAGE, "multiple -f options"); from = optarg; break; case 'l': @@ -77,25 +82,14 @@ main(int argc, char *argv[]) case 'L': lockfile=0; break; - case 'H': - holdme=1; - break; default: usage(); } argc -= optind; argv += optind; - /* Support -H flag for backwards compat */ - if (holdme) { - execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL); - merr(FATAL, "execl: lockspool: %s", strerror(errno)); - } else { - if (!*argv) - usage(); - if (geteuid() != 0) - merr(FATAL, "may only be run by the superuser"); - } + if (!*argv) + usage(); /* * If from not specified, use the name from getlogin() if the @@ -108,8 +102,10 @@ main(int argc, char *argv[]) from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; fd = storemail(from); - for (eval = 0; *argv; ++argv) - eval |= deliver(fd, *argv, lockfile); + for (eval = 0; *argv; ++argv) { + if ((ch = deliver(fd, *argv, lockfile)) != 0) + eval = ch; + } exit(eval); } @@ -119,24 +115,31 @@ storemail(char *from) FILE *fp = NULL; time_t tval; int fd, eline; - ssize_t len; - size_t linesz; + size_t len; char *line, *tbuf; if ((tbuf = strdup(_PATH_LOCTMP)) == NULL) - merr(FATAL, "unable to allocate memory"); + merr(EX_OSERR, "unable to allocate memory"); if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+"))) - merr(FATAL, "unable to open temporary file"); + merr(EX_OSERR, "unable to open temporary file"); (void)unlink(tbuf); free(tbuf); (void)time(&tval); (void)fprintf(fp, "From %s %s", from, ctime(&tval)); - for (eline = 1, line = NULL, linesz = 0; - (len = getline(&line, &linesz, stdin)) != -1;) { + for (eline = 1, tbuf = NULL; (line = fgetln(stdin, &len));) { + /* We have to NUL-terminate the line since fgetln does not */ if (line[len - 1] == '\n') - line[--len] = '\0'; + line[len - 1] = '\0'; + else { + /* No trailing newline, so alloc space and copy */ + if ((tbuf = malloc(len + 1)) == NULL) + merr(EX_OSERR, "unable to allocate memory"); + memcpy(tbuf, line, len); + tbuf[len] = '\0'; + line = tbuf; + } if (line[0] == '\0') eline = 1; else { @@ -149,13 +152,13 @@ storemail(char *from) if (ferror(fp)) break; } - free(line); + free(tbuf); /* Output a newline; note, empty messages are allowed. */ (void)putc('\n', fp); (void)fflush(fp); if (ferror(fp)) - merr(FATAL, "temporary file write error"); + merr(EX_OSERR, "temporary file write error"); return(fd); } @@ -164,8 +167,8 @@ deliver(int fd, char *name, int lockfile) { struct stat sb, fsb; struct passwd *pw; - int mbfd=-1, rval=1, lfd=-1; - char biffmsg[100], buf[8*1024], path[MAXPATHLEN]; + int mbfd=-1, lfd=-1, rval=EX_OSERR; + char biffmsg[100], buf[8*1024], path[PATH_MAX]; off_t curoff; size_t off; ssize_t nr, nw; @@ -175,30 +178,27 @@ deliver(int fd, char *name, int lockfile) * handled in the sendmail aliases file. */ if (!(pw = getpwnam(name))) { - merr(NOTFATAL, "unknown name: %s", name); - return(1); + mwarn("unknown name: %s", name); + return(EX_NOUSER); } (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name); if (lockfile) { - lfd = getlock(name, pw); + lfd = lockspool(name, pw); if (lfd == -1) - return (1); + return(EX_OSERR); } /* after this point, always exit via bad to remove lockfile */ retry: if (lstat(path, &sb)) { if (errno != ENOENT) { - merr(NOTFATAL, "%s: %s", path, strerror(errno)); + mwarn("%s: %s", path, strerror(errno)); goto bad; } -#ifndef O_EXLOCK -#define O_EXLOCK 0 -#endif if ((mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY|O_EXLOCK, - S_IRUSR|S_IWUSR)) < 0) { + S_IRUSR|S_IWUSR)) == -1) { #ifndef HAVE_O_EXLOCK /* XXX : do something! */ #endif @@ -206,7 +206,8 @@ retry: /* file appeared since lstat */ goto retry; } else { - merr(NOTFATAL, "%s: %s", path, strerror(errno)); + mwarn("%s: %s", path, strerror(errno)); + rval = EX_CANTCREAT; goto bad; } } @@ -216,49 +217,48 @@ retry: * that if the ownership or permissions were changed there * was a reason for doing so. */ - if (fchown(mbfd, pw->pw_uid, pw->pw_gid) < 0) { - merr(NOTFATAL, "chown %u:%u: %s", - pw->pw_uid, pw->pw_gid, name); + if (fchown(mbfd, pw->pw_uid, pw->pw_gid) == -1) { + mwarn("chown %u:%u: %s", pw->pw_uid, pw->pw_gid, name); goto bad; } } else { if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)) { - merr(NOTFATAL, "%s: linked or special file", path); + mwarn("%s: linked or special file", path); goto bad; } if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK, - S_IRUSR|S_IWUSR)) < 0) { - merr(NOTFATAL, "%s: %s", path, strerror(errno)); + S_IRUSR|S_IWUSR)) == -1) { + mwarn("%s: %s", path, strerror(errno)); goto bad; } - if (fstat(mbfd, &fsb)) { + if (fstat(mbfd, &fsb) == -1) { /* relating error to path may be bad style */ - merr(NOTFATAL, "%s: %s", path, strerror(errno)); + mwarn("%s: %s", path, strerror(errno)); goto bad; } if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) { - merr(NOTFATAL, "%s: changed after open", path); + mwarn("%s: changed after open", path); goto bad; } /* paranoia? */ if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)) { - merr(NOTFATAL, "%s: linked or special file", path); + mwarn("%s: linked or special file", path); + rval = EX_CANTCREAT; goto bad; } } curoff = lseek(mbfd, 0, SEEK_END); - (void)snprintf(biffmsg, sizeof biffmsg, "%s@%qd\n", name, - (long long int) curoff); + (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, curoff); if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { - merr(NOTFATAL, "temporary file: %s", strerror(errno)); + mwarn("temporary file: %s", strerror(errno)); goto bad; } while ((nr = read(fd, buf, sizeof(buf))) > 0) - for (off = 0; off < (size_t)nr; off += nw) - if ((nw = write(mbfd, buf + off, nr - off)) < 0) { - merr(NOTFATAL, "%s: %s", path, strerror(errno)); + for (off = 0; off < nr; off += nw) + if ((nw = write(mbfd, buf + off, nr - off)) == -1) { + mwarn("%s: %s", path, strerror(errno)); (void)ftruncate(mbfd, curoff); goto bad; } @@ -267,14 +267,12 @@ retry: rval = 0; } else { (void)ftruncate(mbfd, curoff); - merr(FATAL, "temporary file: %s", strerror(errno)); + mwarn("temporary file: %s", strerror(errno)); } bad: - if (lfd != -1) { - rellock(); - close(lfd); - } + if (lfd != -1) + unlockspool(); if (mbfd != -1) { (void)fsync(mbfd); /* Don't wait for update. */ @@ -289,39 +287,106 @@ bad: void notifybiff(char *msg) { - static struct sockaddr_in addr; + static struct addrinfo *res0; + struct addrinfo hints, *res; static int f = -1; - struct hostent *hp; - struct servent *sp; size_t len; + int error; - if (!addr.sin_family) { - /* Be silent if biff service not available. */ - if (!(sp = getservbyname("biff", "udp"))) - return; - if (!(hp = gethostbyname("localhost"))) { - merr(NOTFATAL, "localhost: %s", strerror(errno)); + if (res0 == NULL) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + error = getaddrinfo("localhost", "biff", &hints, &res0); + if (error) { + /* Be silent if biff service not available. */ + if (error != EAI_SERVICE) { + mwarn("localhost: %s", gai_strerror(error)); + } return; } -#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN - addr.sin_len = sizeof(struct sockaddr_in); -#endif - addr.sin_family = hp->h_addrtype; - addr.sin_port = sp->s_port; - bcopy(hp->h_addr, &addr.sin_addr, (size_t)hp->h_length); } - if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { - merr(NOTFATAL, "socket: %s", strerror(errno)); + + if (f == -1) { + for (res = res0; res != NULL; res = res->ai_next) { + f = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (f != -1) + break; + } + } + if (f == -1) { + mwarn("socket: %s", strerror(errno)); return; } - len = strlen(msg) + 1; - if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) - != (ssize_t)len) - merr(NOTFATAL, "sendto biff: %s", strerror(errno)); + + len = strlen(msg) + 1; /* XXX */ + if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len) + mwarn("sendto biff: %s", strerror(errno)); +} + +static int lockfd = -1; +static pid_t lockpid = -1; + +int +lockspool(const char *name, struct passwd *pw) +{ + int pfd[2]; + char ch; + + if (geteuid() == 0) + return getlock(name, pw); + + /* If not privileged, open pipe to lockspool(1) instead */ + if (pipe2(pfd, O_CLOEXEC) == -1) { + merr(EX_OSERR, "pipe: %s", strerror(errno)); + return -1; + } + + signal(SIGPIPE, SIG_IGN); + switch ((lockpid = fork())) { + case -1: + merr(EX_OSERR, "fork: %s", strerror(errno)); + return -1; + case 0: + /* child */ + close(pfd[0]); + dup2(pfd[1], STDOUT_FILENO); + execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL); + merr(EX_OSERR, "execl: lockspool: %s", strerror(errno)); + /* NOTREACHED */ + break; + default: + /* parent */ + close(pfd[1]); + lockfd = pfd[0]; + break; + } + + if (read(lockfd, &ch, 1) != 1 || ch != '1') { + unlockspool(); + merr(EX_OSERR, "lockspool: unable to get lock"); + } + + return lockfd; +} + +void +unlockspool(void) +{ + if (lockpid != -1) { + waitpid(lockpid, NULL, 0); + lockpid = -1; + } else { + rellock(); + } + close(lockfd); + lockfd = -1; } void usage(void) { - merr(FATAL, "usage: mail.local [-Ll] [-f from] user ..."); + merr(EX_USAGE, "usage: mail.local [-Ll] [-f from] user ..."); } diff --git a/contrib/libexec/mail.local/mail.local.h b/contrib/libexec/mail.local/mail.local.h index 0377aa20..bc3137cb 100644 --- a/contrib/libexec/mail.local/mail.local.h +++ b/contrib/libexec/mail.local/mail.local.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mail.local.h,v 1.5 2006/04/01 22:48:57 deraadt Exp $ */ +/* $OpenBSD: mail.local.h,v 1.7 2020/02/09 14:59:21 millert Exp $ */ /*- * Copyright (c) 1990 The Regents of the University of California. @@ -29,14 +29,14 @@ * SUCH DAMAGE. */ -#define FATAL 1 -#define NOTFATAL 0 - void baditem(char *); int deliver(int, char *, int); void merr(int, const char *, ...); -int getlock(char *, struct passwd *); +void mwarn(const char *, ...); +int getlock(const char *, struct passwd *); void notifybiff(char *); void rellock(void); int storemail(char *); +int lockspool(const char *, struct passwd *); +void unlockspool(void); void usage(void); diff --git a/contrib/libexec/mail.local/pathnames.h b/contrib/libexec/mail.local/pathnames.h index a39a6c90..0a2c2731 100644 --- a/contrib/libexec/mail.local/pathnames.h +++ b/contrib/libexec/mail.local/pathnames.h @@ -35,4 +35,4 @@ #endif #define _PATH_LOCTMP "/tmp/local.XXXXXXXXXX" -#define _PATH_LOCKSPOOL "/usr/libexec/lockspool" +#define _PATH_LOCKSPOOL PATH_LIBEXEC"/lockspool" diff --git a/mk/mail/mail.lmtp/Makefile.am b/mk/mail/mail.lmtp/Makefile.am index 9847dfdf..29c33dd1 100644 --- a/mk/mail/mail.lmtp/Makefile.am +++ b/mk/mail/mail.lmtp/Makefile.am @@ -2,8 +2,8 @@ include $(top_srcdir)/mk/pathnames pkglibexec_PROGRAMS = mail.lmtp -mail_lmtp_SOURCES = $(smtpd_srcdir)/mail.lmtp.c -mail_lmtp_SOURCES+= $(smtpd_srcdir)/log.c +mail_lmtp_SOURCES = $(top_srcdir)/usr.sbin/smtpd/mail.lmtp.c +mail_lmtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ -I$(top_srcdir)/openbsd-compat diff --git a/mk/mail/mail.maildir/Makefile.am b/mk/mail/mail.maildir/Makefile.am index d8f696ee..c2afe4d0 100644 --- a/mk/mail/mail.maildir/Makefile.am +++ b/mk/mail/mail.maildir/Makefile.am @@ -2,8 +2,8 @@ include $(top_srcdir)/mk/pathnames pkglibexec_PROGRAMS = mail.maildir -mail_maildir_SOURCES = $(smtpd_srcdir)/mail.maildir.c -mail_maildir_SOURCES+= $(smtpd_srcdir)/log.c +mail_maildir_SOURCES = $(top_srcdir)/usr.sbin/smtpd/mail.maildir.c +mail_maildir_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ -I$(top_srcdir)/openbsd-compat diff --git a/mk/mail/mail.mboxfile/Makefile.am b/mk/mail/mail.mboxfile/Makefile.am index d57362c0..de5f4ea5 100644 --- a/mk/mail/mail.mboxfile/Makefile.am +++ b/mk/mail/mail.mboxfile/Makefile.am @@ -2,8 +2,8 @@ include $(top_srcdir)/mk/pathnames pkglibexec_PROGRAMS = mail.mboxfile -mail_mboxfile_SOURCES = $(smtpd_srcdir)/mail.mboxfile.c -mail_mboxfile_SOURCES+= $(smtpd_srcdir)/log.c +mail_mboxfile_SOURCES = $(top_srcdir)/usr.sbin/smtpd/mail.mboxfile.c +mail_mboxfile_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ -I$(top_srcdir)/openbsd-compat diff --git a/mk/mail/mail.mda/Makefile.am b/mk/mail/mail.mda/Makefile.am index b04aefda..84bbfdb3 100644 --- a/mk/mail/mail.mda/Makefile.am +++ b/mk/mail/mail.mda/Makefile.am @@ -2,8 +2,8 @@ include $(top_srcdir)/mk/pathnames pkglibexec_PROGRAMS = mail.mda -mail_mda_SOURCES = $(smtpd_srcdir)/mail.mda.c -mail_mda_SOURCES+= $(smtpd_srcdir)/log.c +mail_mda_SOURCES = $(top_srcdir)/usr.sbin/smtpd/mail.mda.c +mail_mda_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ -I$(top_srcdir)/openbsd-compat diff --git a/mk/pathnames b/mk/pathnames index 38582bcc..5cff32da 100644 --- a/mk/pathnames +++ b/mk/pathnames @@ -1,4 +1,4 @@ -smtpd_srcdir = $(top_srcdir)/smtpd +smtpd_srcdir = $(top_srcdir)/usr.sbin/smtpd compat_srcdir = $(top_srcdir)/openbsd-compat regress_srcdir = $(top_srcdir)/regress/bin @@ -6,6 +6,5 @@ PATHS= -DSMTPD_CONFDIR=\"$(sysconfdir)\" \ -DPATH_CHROOT=\"$(PRIVSEP_PATH)\" \ -DPATH_SMTPCTL=\"$(sbindir)/smtpctl\" \ -DPATH_MAILLOCAL=\"$(pkglibexecdir)/mail.local\" \ + -DPATH_MAKEMAP=\"$(sbindir)/makemap\" \ -DPATH_LIBEXEC=\"$(pkglibexecdir)\" - - diff --git a/mk/smtp/Makefile.am b/mk/smtp/Makefile.am index e955a271..875cacdc 100644 --- a/mk/smtp/Makefile.am +++ b/mk/smtp/Makefile.am @@ -2,13 +2,13 @@ include $(top_srcdir)/mk/pathnames bin_PROGRAMS= smtp -smtp_SOURCES= $(smtpd_srcdir)/iobuf.c -smtp_SOURCES+= $(smtpd_srcdir)/ioev.c -smtp_SOURCES+= $(smtpd_srcdir)/log.c -smtp_SOURCES+= $(smtpd_srcdir)/smtp_client.c -smtp_SOURCES+= $(smtpd_srcdir)/smtpc.c -smtp_SOURCES+= $(smtpd_srcdir)/ssl.c -smtp_SOURCES+= $(smtpd_srcdir)/ssl_verify.c +smtp_SOURCES= $(top_srcdir)/usr.sbin/smtpd/iobuf.c +smtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ioev.c +smtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c +smtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/smtp_client.c +smtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/smtpc.c +smtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ssl.c +smtp_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ssl_verify.c smtp_CFLAGS= -DIO_TLS @@ -30,7 +30,7 @@ CFLAGS+= -D_GNU_SOURCE CPPFLAGS= -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ MANPAGES= smtp.1.out -MANPAGES_IN= $(smtpd_srcdir)/smtp.1 +MANPAGES_IN= $(top_srcdir)/usr.sbin/smtpd/smtp.1 EXTRA_DIST= $(MANPAGES_IN) @@ -42,7 +42,7 @@ FIXPATHSCMD= $(SED) $(PATHSUBS) $(MANPAGES): $(MANPAGES_IN) - manpage=$(smtpd_srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + manpage=$(top_srcdir)/usr.sbin/smtpd/`echo $@ | sed 's/\.out$$//'`; \ if test "$(MANTYPE)" = "man"; then \ $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/../mdoc2man.awk > $@; \ else \ diff --git a/mk/smtpctl/Makefile.am b/mk/smtpctl/Makefile.am index 8b14b6bd..f7d1b418 100644 --- a/mk/smtpctl/Makefile.am +++ b/mk/smtpctl/Makefile.am @@ -2,37 +2,37 @@ include $(top_srcdir)/mk/pathnames sbin_PROGRAMS= smtpctl -smtpctl_SOURCES= $(smtpd_srcdir)/enqueue.c -smtpctl_SOURCES+= $(smtpd_srcdir)/parser.c -smtpctl_SOURCES+= $(smtpd_srcdir)/log.c -smtpctl_SOURCES+= $(smtpd_srcdir)/envelope.c -smtpctl_SOURCES+= $(smtpd_srcdir)/queue_backend.c -smtpctl_SOURCES+= $(smtpd_srcdir)/queue_fs.c -smtpctl_SOURCES+= $(smtpd_srcdir)/smtpctl.c -smtpctl_SOURCES+= $(smtpd_srcdir)/spfwalk.c -smtpctl_SOURCES+= $(smtpd_srcdir)/util.c -smtpctl_SOURCES+= $(smtpd_srcdir)/unpack_dns.c -smtpctl_SOURCES+= $(smtpd_srcdir)/compress_backend.c -smtpctl_SOURCES+= $(smtpd_srcdir)/compress_gzip.c -smtpctl_SOURCES+= $(smtpd_srcdir)/to.c -smtpctl_SOURCES+= $(smtpd_srcdir)/expand.c -smtpctl_SOURCES+= $(smtpd_srcdir)/tree.c -smtpctl_SOURCES+= $(smtpd_srcdir)/dict.c +smtpctl_SOURCES= $(top_srcdir)/usr.sbin/smtpd/enqueue.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/parser.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/envelope.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_backend.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_fs.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/smtpctl.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/spfwalk.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/util.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/unpack_dns.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/compress_backend.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/compress_gzip.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/to.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/expand.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/tree.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/dict.c if HAVE_DB_API -smtpctl_SOURCES+= $(smtpd_srcdir)/config.c -smtpctl_SOURCES+= $(smtpd_srcdir)/parse.y -smtpctl_SOURCES+= $(smtpd_srcdir)/limit.c -smtpctl_SOURCES+= $(smtpd_srcdir)/table.c -smtpctl_SOURCES+= $(smtpd_srcdir)/table_static.c -smtpctl_SOURCES+= $(smtpd_srcdir)/table_db.c -smtpctl_SOURCES+= $(smtpd_srcdir)/table_getpwnam.c -smtpctl_SOURCES+= $(smtpd_srcdir)/table_proc.c -smtpctl_SOURCES+= $(smtpd_srcdir)/mailaddr.c -smtpctl_SOURCES+= $(smtpd_srcdir)/makemap.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/config.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/parse.y +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/limit.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_static.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_db.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_getpwnam.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_proc.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mailaddr.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/makemap.c endif -smtpctl_SOURCES+= $(smtpd_srcdir)/crypto.c +smtpctl_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/crypto.c smtpctl_CFLAGS= -DNO_IO -DCONFIG_MINIMUM smtpctl_CFLAGS+= -DPATH_GZCAT=\"$(ZCAT)\" \ @@ -59,7 +59,7 @@ CFLAGS+= -D_GNU_SOURCE CPPFLAGS= -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ MANPAGES= smtpctl.8.out sendmail.8.out makemap.8.out newaliases.8.out -MANPAGES_IN= $(smtpd_srcdir)/smtpctl.8 $(smtpd_srcdir)/sendmail.8 $(smtpd_srcdir)/makemap.8 $(smtpd_srcdir)/newaliases.8 +MANPAGES_IN= $(top_srcdir)/usr.sbin/smtpd/smtpctl.8 $(top_srcdir)/usr.sbin/smtpd/sendmail.8 $(top_srcdir)/usr.sbin/smtpd/makemap.8 $(top_srcdir)/usr.sbin/smtpd/newaliases.8 EXTRA_DIST= $(MANPAGES_IN) @@ -69,9 +69,12 @@ PATHSUBS= -e 's|/var/run/smtpd.sock|$(sockdir)/smtpd.sock|g' \ FIXPATHSCMD= $(SED) $(PATHSUBS) +if NEED_LIBASR +AM_CPPFLAGS+= -I$(top_srcdir)/openbsd-compat/libasr +endif $(MANPAGES): $(MANPAGES_IN) - manpage=$(smtpd_srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + manpage=$(top_srcdir)/usr.sbin/smtpd/`echo $@ | sed 's/\.out$$//'`; \ if test "$(MANTYPE)" = "man"; then \ $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/../mdoc2man.awk > $@; \ else \ diff --git a/mk/smtpd/Makefile.am b/mk/smtpd/Makefile.am index f5cbe8c8..6d652d48 100644 --- a/mk/smtpd/Makefile.am +++ b/mk/smtpd/Makefile.am @@ -14,85 +14,84 @@ include $(top_srcdir)/mk/pathnames sbin_PROGRAMS= smtpd -smtpd_SOURCES= $(smtpd_srcdir)/aliases.c -smtpd_SOURCES+= $(smtpd_srcdir)/bounce.c -smtpd_SOURCES+= $(smtpd_srcdir)/ca.c -smtpd_SOURCES+= $(smtpd_srcdir)/cert.c -smtpd_SOURCES+= $(smtpd_srcdir)/compress_backend.c -smtpd_SOURCES+= $(smtpd_srcdir)/config.c -smtpd_SOURCES+= $(smtpd_srcdir)/control.c -smtpd_SOURCES+= $(smtpd_srcdir)/dict.c -smtpd_SOURCES+= $(smtpd_srcdir)/dns.c -smtpd_SOURCES+= $(smtpd_srcdir)/esc.c -smtpd_SOURCES+= $(smtpd_srcdir)/envelope.c -smtpd_SOURCES+= $(smtpd_srcdir)/expand.c -smtpd_SOURCES+= $(smtpd_srcdir)/forward.c -smtpd_SOURCES+= $(smtpd_srcdir)/iobuf.c -smtpd_SOURCES+= $(smtpd_srcdir)/ioev.c -smtpd_SOURCES+= $(smtpd_srcdir)/limit.c -smtpd_SOURCES+= $(smtpd_srcdir)/lka.c -smtpd_SOURCES+= $(smtpd_srcdir)/lka_filter.c -smtpd_SOURCES+= $(smtpd_srcdir)/lka_proc.c -smtpd_SOURCES+= $(smtpd_srcdir)/lka_report.c -smtpd_SOURCES+= $(smtpd_srcdir)/lka_session.c -smtpd_SOURCES+= $(smtpd_srcdir)/log.c -smtpd_SOURCES+= $(smtpd_srcdir)/mda.c -smtpd_SOURCES+= $(smtpd_srcdir)/mda_unpriv.c -smtpd_SOURCES+= $(smtpd_srcdir)/mda_variables.c -smtpd_SOURCES+= $(smtpd_srcdir)/mproc.c -smtpd_SOURCES+= $(smtpd_srcdir)/mailaddr.c -smtpd_SOURCES+= $(smtpd_srcdir)/mta.c -smtpd_SOURCES+= $(smtpd_srcdir)/mta_session.c -smtpd_SOURCES+= $(smtpd_srcdir)/parse.y -smtpd_SOURCES+= $(smtpd_srcdir)/pony.c -smtpd_SOURCES+= $(smtpd_srcdir)/proxy.c -smtpd_SOURCES+= $(smtpd_srcdir)/queue.c -smtpd_SOURCES+= $(smtpd_srcdir)/queue_backend.c -smtpd_SOURCES+= $(smtpd_srcdir)/report_smtp.c -smtpd_SOURCES+= $(smtpd_srcdir)/resolver.c -smtpd_SOURCES+= $(smtpd_srcdir)/rfc5322.c -smtpd_SOURCES+= $(smtpd_srcdir)/ruleset.c -smtpd_SOURCES+= $(smtpd_srcdir)/runq.c -smtpd_SOURCES+= $(smtpd_srcdir)/scheduler.c -smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_backend.c -smtpd_SOURCES+= $(smtpd_srcdir)/smtp.c -smtpd_SOURCES+= $(smtpd_srcdir)/smtp_session.c -smtpd_SOURCES+= $(smtpd_srcdir)/smtpd.c -smtpd_SOURCES+= $(smtpd_srcdir)/srs.c -smtpd_SOURCES+= $(smtpd_srcdir)/ssl.c -smtpd_SOURCES+= $(smtpd_srcdir)/ssl_smtpd.c -smtpd_SOURCES+= $(smtpd_srcdir)/ssl_verify.c -smtpd_SOURCES+= $(smtpd_srcdir)/stat_backend.c -smtpd_SOURCES+= $(smtpd_srcdir)/table.c -smtpd_SOURCES+= $(smtpd_srcdir)/to.c -smtpd_SOURCES+= $(smtpd_srcdir)/tree.c -smtpd_SOURCES+= $(smtpd_srcdir)/unpack_dns.c -smtpd_SOURCES+= $(smtpd_srcdir)/util.c -smtpd_SOURCES+= $(smtpd_srcdir)/waitq.c +smtpd_SOURCES= $(top_srcdir)/usr.sbin/smtpd/aliases.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/bounce.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ca.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/cert.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/compress_backend.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/config.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/control.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/dict.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/dns.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/esc.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/envelope.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/expand.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/forward.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/iobuf.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/limit.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/lka.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/lka_filter.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/lka_session.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/log.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mda.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mda_mbox.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mda_unpriv.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mda_variables.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mproc.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mailaddr.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mta.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/mta_session.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/parse.y +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/pony.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/proxy.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_backend.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/report_smtp.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/resolver.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/rfc5322.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ruleset.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/runq.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/scheduler.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/scheduler_backend.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/smtp.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/smtp_session.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/smtpd.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/srs.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ssl.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ssl_smtpd.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ssl_verify.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/stat_backend.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/to.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/tree.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/unpack_dns.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/util.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/waitq.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/ioev.c # backends -smtpd_SOURCES+= $(smtpd_srcdir)/crypto.c -smtpd_SOURCES+= $(smtpd_srcdir)/compress_gzip.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/crypto.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/compress_gzip.c if HAVE_DB_API -smtpd_SOURCES+= $(smtpd_srcdir)/table_db.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_db.c endif -smtpd_SOURCES+= $(smtpd_srcdir)/table_getpwnam.c -smtpd_SOURCES+= $(smtpd_srcdir)/table_proc.c -smtpd_SOURCES+= $(smtpd_srcdir)/table_static.c -smtpd_SOURCES+= $(smtpd_srcdir)/queue_fs.c -smtpd_SOURCES+= $(smtpd_srcdir)/queue_null.c -smtpd_SOURCES+= $(smtpd_srcdir)/queue_proc.c -smtpd_SOURCES+= $(smtpd_srcdir)/queue_ram.c -smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_null.c -smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_proc.c -smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_ramqueue.c -smtpd_SOURCES+= $(smtpd_srcdir)/stat_ramstat.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_getpwnam.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_proc.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/table_static.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_fs.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_null.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_proc.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/queue_ram.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/scheduler_null.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/scheduler_proc.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/scheduler_ramqueue.c +smtpd_SOURCES+= $(top_srcdir)/usr.sbin/smtpd/stat_ramstat.c smtpd_CFLAGS= -DIO_TLS smtpd_CFLAGS+= -DCA_FILE=\"$(CA_FILE)\" -AM_CPPFLAGS= -I$(smtpd_srcdir) \ +AM_CPPFLAGS= -I$(top_srcdir)/usr.sbin/smtpd \ -I$(compat_srcdir) if !NEED_ERR_H AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h @@ -102,6 +101,9 @@ AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/paths_h endif LIBCOMPAT= $(top_builddir)/openbsd-compat/libopenbsd.a +if NEED_LIBASR +AM_CPPFLAGS+= -I$(top_srcdir)/openbsd-compat/libasr +endif LDADD= $(LIBCOMPAT) $(DB_LIB) $(ASR_LIB) @@ -109,43 +111,46 @@ LDADD= $(LIBCOMPAT) $(DB_LIB) $(ASR_LIB) # EAI_NODATA defined # {v,}asprintf # setres{g,u}id -CFLAGS+= -D_GNU_SOURCE -DNEED_EVENT_ASR_RUN -CPPFLAGS= -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ +AM_CFLAGS= -D_GNU_SOURCE -DNEED_EVENT_ASR_RUN +AM_CPPFLAGS+= -I$(srcdir) $(PATHS) @DEFS@ MANPAGES= aliases.5.out forward.5.out smtpd.8.out \ smtpd.conf.5.out table.5.out -MANPAGES_IN= $(smtpd_srcdir)/aliases.5 -MANPAGES_IN+= $(smtpd_srcdir)/forward.5 -MANPAGES_IN+= $(smtpd_srcdir)/smtpd.8 -MANPAGES_IN+= $(smtpd_srcdir)/smtpd.conf.5 -MANPAGES_IN+= $(smtpd_srcdir)/table.5 +MANPAGES_IN= $(top_srcdir)/usr.sbin/smtpd/aliases.5 +MANPAGES_IN+= $(top_srcdir)/usr.sbin/smtpd/forward.5 +MANPAGES_IN+= $(top_srcdir)/usr.sbin/smtpd/smtpd.8 +MANPAGES_IN+= $(top_srcdir)/usr.sbin/smtpd/smtpd.conf.5 +MANPAGES_IN+= $(top_srcdir)/usr.sbin/smtpd/table.5 CONFIGFILES= smtpd.conf.out -CONFIGFILES_IN= $(smtpd_srcdir)/smtpd.conf +CONFIGFILES_IN= $(top_srcdir)/usr.sbin/smtpd/smtpd.conf EXTRA_DIST= $(CONFIGFILES_IN) $(MANPAGES_IN) -EXTRA_DIST+= $(smtpd_srcdir)/smtpd.h -EXTRA_DIST+= $(smtpd_srcdir)/smtpd-api.h -EXTRA_DIST+= $(smtpd_srcdir)/smtpd-defines.h -EXTRA_DIST+= $(smtpd_srcdir)/ioev.h -EXTRA_DIST+= $(smtpd_srcdir)/iobuf.h -EXTRA_DIST+= $(smtpd_srcdir)/log.h -EXTRA_DIST+= $(smtpd_srcdir)/ssl.h -EXTRA_DIST+= $(smtpd_srcdir)/parser.h - -EXTRA_DIST+= $(backends_srcdir)/queue_utils.h -EXTRA_DIST+= $(filters_srcdir)/asr_event.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/rfc5322.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/unpack_dns.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/tree.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/smtp.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/smtpd.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/smtpd-api.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/smtpd-defines.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/ioev.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/iobuf.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/log.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/ssl.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/parser.h +EXTRA_DIST+= $(top_srcdir)/usr.sbin/smtpd/dict.h PATHSUBS= -e 's|/etc/mail/|$(sysconfdir)/|g' \ - -e 's|/var/run/smtpd.sock|$(sockdir)/smtpd.sock|g' + -e 's|/var/run/smtpd.sock|$(sockdir)/smtpd.sock|g' \ + -e 's|/usr/local/libexec/smtpd/|$(pkglibexecdir)|g' FIXPATHSCMD= $(SED) $(PATHSUBS) $(MANPAGES): $(MANPAGES_IN) - manpage=$(smtpd_srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + manpage=$(top_srcdir)/usr.sbin/smtpd/`echo $@ | sed 's/\.out$$//'`; \ if test "$(MANTYPE)" = "man"; then \ $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/../mdoc2man.awk > $@; \ else \ @@ -153,8 +158,8 @@ $(MANPAGES): $(MANPAGES_IN) fi $(CONFIGFILES): $(CONFIGFILES_IN) - conffile=$(smtpd_srcdir)/`echo $@ | sed 's/.out$$//'`; \ - $(CAT) $(srcdir)/$${conffile} > $@ + conffile=$(top_srcdir)/usr.sbin/smtpd/`echo $@ | sed 's/.out$$//'`; \ + $(CAT) $${conffile} > $@ # smtpd.conf @@ -181,8 +186,8 @@ install-exec-hook: $(CONFIGFILES) $(MANPAGES) uninstall-hook: # XXX to make "make distcheck" happy we need to rm smtpd.conf -# rm $(DESTDIR)$(sysconfdir)/smtpd.conf - rm -f $(DESTDIR)$(mandir)/$(mansubdir)5/aliases.5 \ + rm -f $(DESTDIR)$(sysconfdir)/smtpd.conf \ + $(DESTDIR)$(mandir)/$(mansubdir)5/aliases.5 \ $(DESTDIR)$(mandir)/$(mansubdir)5/forward.5 \ $(DESTDIR)$(mandir)/$(mansubdir)5/table.5 \ $(DESTDIR)$(mandir)/$(mansubdir)5/smtpd.conf.5 \ diff --git a/openbsd-compat/Makefile.am b/openbsd-compat/Makefile.am index 9d9d52af..170c2b6c 100644 --- a/openbsd-compat/Makefile.am +++ b/openbsd-compat/Makefile.am @@ -1,7 +1,26 @@ noinst_LIBRARIES = libopenbsd.a +AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/usr.sbin/smtpd -I$(top_srcdir)/openbsd-compat + libopenbsd_a_SOURCES = empty.c +if NEED_LIBASR +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/libasr + +libopenbsd_a_SOURCES += libasr/asr.c +libopenbsd_a_SOURCES += libasr/asr_debug.c +libopenbsd_a_SOURCES += libasr/asr_compat.c +libopenbsd_a_SOURCES += libasr/asr_utils.c +libopenbsd_a_SOURCES += libasr/getaddrinfo_async.c +libopenbsd_a_SOURCES += libasr/gethostnamadr_async.c +libopenbsd_a_SOURCES += libasr/getnameinfo_async.c +libopenbsd_a_SOURCES += libasr/getnetnamadr_async.c +libopenbsd_a_SOURCES += libasr/res_search_async.c +libopenbsd_a_SOURCES += libasr/res_send_async.c +endif + + + if NEED_PROGNAME libopenbsd_a_SOURCES += progname.c endif @@ -95,6 +114,10 @@ if NEED_PIDFILE libopenbsd_a_SOURCES += pidfile.c endif +if NEED_PIPE2 +libopenbsd_a_SOURCES += pipe2.c +endif + if NEED_REALLOCARRAY libopenbsd_a_SOURCES += reallocarray.c endif @@ -103,6 +126,14 @@ if NEED_RECALLOCARRAY libopenbsd_a_SOURCES += recallocarray.c endif +if NEED_RES_HNOK +libopenbsd_a_SOURCES += res_hnok.c +endif + +if NEED_RES_RANDOMID +libopenbsd_a_SOURCES += res_randomid.c +endif + if NEED_SETPROCTITLE libopenbsd_a_SOURCES += setproctitle.c endif @@ -185,13 +216,19 @@ EXTRA_DIST += defines.h EXTRA_DIST += entropy.h EXTRA_DIST += imsg.h EXTRA_DIST += includes.h -EXTRA_DIST += log.h +EXTRA_DIST += $(top_srcdir)/usr.sbin/smtpd/log.h EXTRA_DIST += openbsd-compat.h EXTRA_DIST += sys/queue.h EXTRA_DIST += sys/tree.h EXTRA_DIST += bsd-vis.h -AM_CPPFLAGS = -I$(top_srcdir)/smtpd -I$(top_srcdir)/openbsd-compat +if NEED_LIBASR +EXTRA_DIST += libasr/asr_compat.h +EXTRA_DIST += libasr/asr_private.h +EXTRA_DIST += libasr/asr.h +EXTRA_DIST += libasr/thread_private.h +endif + if !NEED_ERR_H AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h diff --git a/openbsd-compat/defines.h b/openbsd-compat/defines.h index 6a6e64a4..a6e528eb 100644 --- a/openbsd-compat/defines.h +++ b/openbsd-compat/defines.h @@ -34,6 +34,10 @@ # define EAUTH 80 #endif +#ifndef INFTIM +#define INFTIM (-1) +#endif + #ifndef HOST_NAME_MAX # ifdef _POSIX_HOST_NAME_MAX # define HOST_NAME_MAX _POSIX_HOST_NAME_MAX @@ -64,7 +68,7 @@ #endif #ifndef LOGIN_NAME_MAX -# define LOGIN_NAME_MAX 9 +# define LOGIN_NAME_MAX 32 #endif #ifndef MAXLOGNAME @@ -92,6 +96,10 @@ # define O_NONBLOCK 00004 /* Non Blocking Open */ #endif +#ifndef O_EXLOCK +#define O_EXLOCK 0 +#endif + #ifndef S_ISDIR # define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR)) #endif /* S_ISDIR */ @@ -214,6 +222,10 @@ typedef uint16_t in_port_t; #endif /* user may have set a different path */ +#if !defined(_PATH_MAILDIR) +# define _PATH_MAILDIR "/var/spool/mail" +#endif + #if defined(_PATH_MAILDIR) && defined(MAIL_DIRECTORY) # undef _PATH_MAILDIR #endif /* defined(_PATH_MAILDIR) && defined(MAIL_DIRECTORY) */ @@ -491,4 +503,8 @@ typedef uint16_t in_port_t; #define LOG_PERROR 0 #endif +#ifndef MAXDNAME +#define MAXDNAME 1025 +#endif + #endif /* _DEFINES_H */ diff --git a/openbsd-compat/err_h/err.h b/openbsd-compat/err_h/err.h index a56b6188..92aa69f6 100644 --- a/openbsd-compat/err_h/err.h +++ b/openbsd-compat/err_h/err.h @@ -10,6 +10,9 @@ void err(int, const char *, ...); __attribute__ ((noreturn)) void errx(int, const char *, ...); +__attribute__ ((noreturn)) +void errc(int, int, const char *, ...); + void warn(const char *, ...); void warnx(const char *, ...); diff --git a/openbsd-compat/explicit_bzero.c b/openbsd-compat/explicit_bzero.c index 58c81d68..d9f4abf5 100644 --- a/openbsd-compat/explicit_bzero.c +++ b/openbsd-compat/explicit_bzero.c @@ -5,7 +5,6 @@ */ #include "includes.h" -#ifndef HAVE_EXPLICIT_BZERO #include <string.h> @@ -14,4 +13,3 @@ explicit_bzero(void *buf, size_t len) { memset(buf, 0, len); } -#endif diff --git a/openbsd-compat/fgetln.c b/openbsd-compat/fgetln.c index 6c3290d0..1c51ff78 100644 --- a/openbsd-compat/fgetln.c +++ b/openbsd-compat/fgetln.c @@ -18,6 +18,8 @@ * portable fgetln() version, NOT reentrant */ +#include "includes.h" + #include <stdio.h> #include <stdlib.h> #include <errno.h> diff --git a/openbsd-compat/freezero.c b/openbsd-compat/freezero.c index 9b64e2b0..da20d132 100644 --- a/openbsd-compat/freezero.c +++ b/openbsd-compat/freezero.c @@ -22,6 +22,7 @@ #include "includes.h" #include <stdlib.h> +#include <strings.h> void freezero(void *ptr, size_t sz) diff --git a/openbsd-compat/libasr/asr.c b/openbsd-compat/libasr/asr.c new file mode 100644 index 00000000..008544ad --- /dev/null +++ b/openbsd-compat/libasr/asr.c @@ -0,0 +1,869 @@ +/* $OpenBSD: asr.c,v 1.61 2018/10/22 17:31:24 krw Exp $ */ +/* + * Copyright (c) 2010-2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <fcntl.h> +#include <resolv.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +#include "thread_private.h" + +#define DEFAULT_CONF "lookup file\n" +#define DEFAULT_LOOKUP "lookup bind file" + +#define RELOAD_DELAY 15 /* seconds */ + +static void asr_check_reload(struct asr *); +static struct asr_ctx *asr_ctx_create(void); +static void asr_ctx_ref(struct asr_ctx *); +static void asr_ctx_free(struct asr_ctx *); +static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *); +static int asr_ctx_from_file(struct asr_ctx *, const char *); +static int asr_ctx_from_string(struct asr_ctx *, const char *); +static int asr_ctx_parse(struct asr_ctx *, const char *); +static int asr_parse_nameserver(struct sockaddr *, const char *); +static int asr_ndots(const char *); +static void pass0(char **, int, struct asr_ctx *); +static int strsplit(char *, char **, int); +static void asr_ctx_envopts(struct asr_ctx *); +static void *__THREAD_NAME(_asr); + +static struct asr *_asr = NULL; + +#ifndef HAVE_ISSETUGID +#define issetugid() ((getuid() != geteuid())) +#endif + +/* Allocate and configure an async "resolver". */ +static void * +_asr_resolver(void) +{ + static int init = 0; + struct asr *asr; + + if (init == 0) { +#ifdef DEBUG + if (getenv("ASR_DEBUG")) + _asr_debug = stderr; +#endif + init = 1; + } + + if ((asr = calloc(1, sizeof(*asr))) == NULL) + goto fail; + + asr_check_reload(asr); + if (asr->a_ctx == NULL) { + if ((asr->a_ctx = asr_ctx_create()) == NULL) + goto fail; + if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1) + goto fail; + asr_ctx_envopts(asr->a_ctx); + } + +#ifdef DEBUG + _asr_dump_config(_asr_debug, asr); +#endif + return (asr); + + fail: + if (asr) { + if (asr->a_ctx) + asr_ctx_free(asr->a_ctx); + free(asr); + } + + return (NULL); +} + +/* + * Free the "asr" async resolver (or the thread-local resolver if NULL). + * Drop the reference to the current context. + */ +void +_asr_resolver_done(void *arg) +{ + struct asr *asr = arg; + struct asr **priv; + + if (asr == NULL) { + priv = _THREAD_PRIVATE(_asr, _asr, &_asr); + if (*priv == NULL) + return; + asr = *priv; + *priv = NULL; + } + + _asr_ctx_unref(asr->a_ctx); + free(asr); +} + +/* + * Cancel an async query. + */ +void +asr_abort(struct asr_query *as) +{ + _asr_async_free(as); +} + +/* + * Resume the "as" async query resolution. Return one of ASYNC_COND, + * or ASYNC_DONE and put query-specific return values in the user-allocated + * memory at "ar". + */ +int +asr_run(struct asr_query *as, struct asr_result *ar) +{ + int r, saved_errno = errno; + + memset(ar, 0, sizeof(*ar)); + + DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar, + _asr_querystr(as->as_type), as->as_ctx); + r = as->as_run(as, ar); + + DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, _asr_transitionstr(r)); +#ifdef DEBUG + if (r == ASYNC_COND) +#endif + DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout); + DPRINT("\n"); + if (r == ASYNC_DONE) + _asr_async_free(as); + + errno = saved_errno; + + return (r); +} +DEF_WEAK(asr_run); + +static int +poll_intrsafe(struct pollfd *fds, nfds_t nfds, int timeout) +{ + struct timespec pollstart, pollend, elapsed; + int r; + + if (clock_gettime(CLOCK_MONOTONIC, &pollstart)) + return -1; + + while ((r = poll(fds, 1, timeout)) == -1 && errno == EINTR) { + if (clock_gettime(CLOCK_MONOTONIC, &pollend)) + return -1; + timespecsub(&pollend, &pollstart, &elapsed); + timeout -= elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000; + if (timeout < 1) + return 0; + } + + return r; +} + +/* + * Same as asr_run, but run in a loop that handles the fd conditions result. + */ +int +asr_run_sync(struct asr_query *as, struct asr_result *ar) +{ + struct pollfd fds[1]; + int r, saved_errno = errno; + + while ((r = asr_run(as, ar)) == ASYNC_COND) { + fds[0].fd = ar->ar_fd; + fds[0].events = (ar->ar_cond == ASR_WANT_READ) ? POLLIN:POLLOUT; + + if (poll_intrsafe(fds, 1, ar->ar_timeout) == -1) { + memset(ar, 0, sizeof(*ar)); + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + ar->ar_rrset_errno = NETDB_INTERNAL; + _asr_async_free(as); + errno = saved_errno; + return ASYNC_DONE; + } + + /* + * Otherwise, just ignore the error and let asr_run() + * catch the failure. + */ + } + + errno = saved_errno; + + return (r); +} +DEF_WEAK(asr_run_sync); + +/* + * Create a new async request of the given "type" on the async context "ac". + * Take a reference on it so it does not get deleted while the async query + * is running. + */ +struct asr_query * +_asr_async_new(struct asr_ctx *ac, int type) +{ + struct asr_query *as; + + DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type, + ac ? ac->ac_refcount : 0); + if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL) + return (NULL); + + ac->ac_refcount += 1; + as->as_ctx = ac; + as->as_fd = -1; + as->as_type = type; + as->as_state = ASR_STATE_INIT; + + return (as); +} + +/* + * Free an async query and unref the associated context. + */ +void +_asr_async_free(struct asr_query *as) +{ + DPRINT("asr: asr_async_free(%p)\n", as); + + if (as->as_subq) + _asr_async_free(as->as_subq); + + switch (as->as_type) { + case ASR_SEND: + if (as->as_fd != -1) + close(as->as_fd); + if (as->as.dns.obuf && !(as->as_flags & ASYNC_EXTOBUF)) + free(as->as.dns.obuf); + if (as->as.dns.ibuf) + free(as->as.dns.ibuf); + if (as->as.dns.dname) + free(as->as.dns.dname); + break; + + case ASR_SEARCH: + if (as->as.search.name) + free(as->as.search.name); + break; + + case ASR_GETRRSETBYNAME: + if (as->as.rrset.name) + free(as->as.rrset.name); + break; + + case ASR_GETHOSTBYNAME: + case ASR_GETHOSTBYADDR: + if (as->as.hostnamadr.name) + free(as->as.hostnamadr.name); + break; + + case ASR_GETADDRINFO: + if (as->as.ai.aifirst) + freeaddrinfo(as->as.ai.aifirst); + if (as->as.ai.hostname) + free(as->as.ai.hostname); + if (as->as.ai.servname) + free(as->as.ai.servname); + if (as->as.ai.fqdn) + free(as->as.ai.fqdn); + break; + + case ASR_GETNAMEINFO: + break; + } + + _asr_ctx_unref(as->as_ctx); + free(as); +} + +/* + * Get a context from the given resolver. This takes a new reference to + * the returned context, which *must* be explicitly dropped when done + * using this context. + */ +struct asr_ctx * +_asr_use_resolver(void *arg) +{ + struct asr *asr = arg; + struct asr **priv; + + if (asr == NULL) { + DPRINT("using thread-local resolver\n"); + priv = _THREAD_PRIVATE(_asr, _asr, &_asr); + if (*priv == NULL) { + DPRINT("setting up thread-local resolver\n"); + *priv = _asr_resolver(); + } + asr = *priv; + } + if (asr != NULL) { + asr_check_reload(asr); + asr_ctx_ref(asr->a_ctx); + return (asr->a_ctx); + } + return (NULL); +} + +static void +asr_ctx_ref(struct asr_ctx *ac) +{ + DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount); + ac->ac_refcount += 1; +} + +/* + * Drop a reference to an async context, freeing it if the reference + * count drops to 0. + */ +void +_asr_ctx_unref(struct asr_ctx *ac) +{ + DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac, + ac ? ac->ac_refcount : 0); + if (ac == NULL) + return; + if (--ac->ac_refcount) + return; + + asr_ctx_free(ac); +} + +static void +asr_ctx_free(struct asr_ctx *ac) +{ + int i; + + if (ac->ac_domain) + free(ac->ac_domain); + for (i = 0; i < ASR_MAXNS; i++) + free(ac->ac_ns[i]); + for (i = 0; i < ASR_MAXDOM; i++) + free(ac->ac_dom[i]); + + free(ac); +} + +/* + * Reload the configuration file if it has changed on disk. + */ +static void +asr_check_reload(struct asr *asr) +{ + struct asr_ctx *ac; + struct stat st; + struct timespec ts; + pid_t pid; + + pid = getpid(); + if (pid != asr->a_pid) { + asr->a_pid = pid; + asr->a_rtime = 0; + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + return; + + if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0) + return; + asr->a_rtime = ts.tv_sec; + + DPRINT("asr: checking for update of \"%s\"\n", _PATH_RESCONF); + if (stat(_PATH_RESCONF, &st) == -1 || + asr->a_mtime == st.st_mtime || + (ac = asr_ctx_create()) == NULL) + return; + asr->a_mtime = st.st_mtime; + + DPRINT("asr: reloading config file\n"); + if (asr_ctx_from_file(ac, _PATH_RESCONF) == -1) { + asr_ctx_free(ac); + return; + } + + asr_ctx_envopts(ac); + if (asr->a_ctx) + _asr_ctx_unref(asr->a_ctx); + asr->a_ctx = ac; +} + +/* + * Construct a fully-qualified domain name for the given name and domain. + * If "name" ends with a '.' it is considered as a FQDN by itself. + * Otherwise, the domain, which must be a FQDN, is appended to "name" (it + * may have a leading dot which would be ignored). If the domain is null, + * then "." is used. Return the length of the constructed FQDN or (0) on + * error. + */ +size_t +_asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen) +{ + size_t len; + + if (domain == NULL) + domain = "."; + else if ((len = strlen(domain)) == 0) + return (0); + else if (domain[len -1] != '.') + return (0); + + len = strlen(name); + if (len == 0) { + if (strlcpy(buf, domain, buflen) >= buflen) + return (0); + } else if (name[len - 1] != '.') { + if (domain[0] == '.') + domain += 1; + if (strlcpy(buf, name, buflen) >= buflen || + strlcat(buf, ".", buflen) >= buflen || + strlcat(buf, domain, buflen) >= buflen) + return (0); + } else { + if (strlcpy(buf, name, buflen) >= buflen) + return (0); + } + + return (strlen(buf)); +} + +/* + * Count the dots in a string. + */ +static int +asr_ndots(const char *s) +{ + int n; + + for (n = 0; *s; s++) + if (*s == '.') + n += 1; + + return (n); +} + +/* + * Allocate a new empty context. + */ +static struct asr_ctx * +asr_ctx_create(void) +{ + struct asr_ctx *ac; + + if ((ac = calloc(1, sizeof(*ac))) == NULL) + return (NULL); + + ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH; + ac->ac_refcount = 1; + ac->ac_ndots = 1; +#ifndef ASR_IPV4_BEFORE_IPV6 + ac->ac_family[0] = AF_INET6; + ac->ac_family[1] = AF_INET; +#else + ac->ac_family[0] = AF_INET; + ac->ac_family[1] = AF_INET6; +#endif + ac->ac_family[2] = -1; + + ac->ac_nscount = 0; + ac->ac_nstimeout = 5; + ac->ac_nsretries = 4; + + return (ac); +} + +struct asr_ctx * +_asr_no_resolver(void) +{ + return asr_ctx_create(); +} + +/* + * Add a search domain to the async context. + */ +static int +asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain) +{ + char buf[MAXDNAME]; + + if (ac->ac_domcount == ASR_MAXDOM) + return (-1); + + if (_asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0) + return (-1); + + if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL) + return (0); + + ac->ac_domcount += 1; + + return (1); +} + +static int +strsplit(char *line, char **tokens, int ntokens) +{ + int ntok; + char *cp, **tp; + + for (cp = line, tp = tokens, ntok = 0; + ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; ) + if (**tp != '\0') { + tp++; + ntok++; + } + + return (ntok); +} + +/* + * Pass on a split config line. + */ +static void +pass0(char **tok, int n, struct asr_ctx *ac) +{ + int i, j, d; + const char *e; + struct sockaddr_storage ss; + + if (!strcmp(tok[0], "nameserver")) { + if (ac->ac_nscount == ASR_MAXNS) + return; + if (n != 2) + return; + if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1])) + return; + if ((ac->ac_ns[ac->ac_nscount] = calloc(1, SS_LEN(&ss))) == NULL) + return; + memmove(ac->ac_ns[ac->ac_nscount], &ss, SS_LEN(&ss)); + ac->ac_nscount += 1; + + } else if (!strcmp(tok[0], "domain")) { + if (n != 2) + return; + if (ac->ac_domain) + return; + ac->ac_domain = strdup(tok[1]); + + } else if (!strcmp(tok[0], "lookup")) { + /* ensure that each lookup is only given once */ + for (i = 1; i < n; i++) + for (j = i + 1; j < n; j++) + if (!strcmp(tok[i], tok[j])) + return; + ac->ac_dbcount = 0; + for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) { + if (!strcmp(tok[i], "yp")) { + /* silently deprecated */ + } else if (!strcmp(tok[i], "bind")) + ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS; + else if (!strcmp(tok[i], "file")) + ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE; + } + } else if (!strcmp(tok[0], "search")) { + /* resolv.conf says the last line wins */ + for (i = 0; i < ASR_MAXDOM; i++) { + free(ac->ac_dom[i]); + ac->ac_dom[i] = NULL; + } + ac->ac_domcount = 0; + for (i = 1; i < n; i++) + asr_ctx_add_searchdomain(ac, tok[i]); + + } else if (!strcmp(tok[0], "family")) { + if (n == 1 || n > 3) + return; + for (i = 1; i < n; i++) + if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) + return; + for (i = 1; i < n; i++) + ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \ + AF_INET6 : AF_INET; + ac->ac_family[i - 1] = -1; + + } else if (!strcmp(tok[0], "options")) { + for (i = 1; i < n; i++) { + if (!strcmp(tok[i], "tcp")) + ac->ac_options |= RES_USEVC; + else if (!strcmp(tok[i], "edns0")) + ac->ac_options |= RES_USE_EDNS0; + else if ((!strncmp(tok[i], "ndots:", 6))) { + e = NULL; + d = strtonum(tok[i] + 6, 1, 16, &e); + if (e == NULL) + ac->ac_ndots = d; + } + } + } +} + +/* + * Setup an async context with the config specified in the string "str". + */ +static int +asr_ctx_from_string(struct asr_ctx *ac, const char *str) +{ + char buf[512], *ch; + + asr_ctx_parse(ac, str); + + if (ac->ac_dbcount == 0) { + /* No lookup directive */ + asr_ctx_parse(ac, DEFAULT_LOOKUP); + } + + if (ac->ac_nscount == 0) + asr_ctx_parse(ac, "nameserver 127.0.0.1"); + + if (ac->ac_domain == NULL) + if (gethostname(buf, sizeof buf) == 0) { + ch = strchr(buf, '.'); + if (ch) + ac->ac_domain = strdup(ch + 1); + else /* Assume root. see resolv.conf(5) */ + ac->ac_domain = strdup(""); + } + + /* If no search domain was specified, use the local subdomains */ + if (ac->ac_domcount == 0) + for (ch = ac->ac_domain; ch; ) { + asr_ctx_add_searchdomain(ac, ch); + ch = strchr(ch, '.'); + if (ch && asr_ndots(++ch) == 0) + break; + } + + return (0); +} + +/* + * Setup the "ac" async context from the file at location "path". + */ +static int +asr_ctx_from_file(struct asr_ctx *ac, const char *path) +{ + FILE *cf; + char buf[4096]; + ssize_t r; + + cf = fopen(path, "re"); + if (cf == NULL) + return (-1); + + r = fread(buf, 1, sizeof buf - 1, cf); + if (feof(cf) == 0) { + DPRINT("asr: config file too long: \"%s\"\n", path); + r = -1; + } + fclose(cf); + if (r == -1) + return (-1); + buf[r] = '\0'; + + return asr_ctx_from_string(ac, buf); +} + +/* + * Parse lines in the configuration string. For each one, split it into + * tokens and pass them to "pass0" for processing. + */ +static int +asr_ctx_parse(struct asr_ctx *ac, const char *str) +{ + size_t len; + const char *line; + char buf[1024]; + char *tok[10]; + int ntok; + + line = str; + while (*line) { + len = strcspn(line, "\n\0"); + if (len < sizeof buf) { + memmove(buf, line, len); + buf[len] = '\0'; + } else + buf[0] = '\0'; + line += len; + if (*line == '\n') + line++; + buf[strcspn(buf, ";#")] = '\0'; + if ((ntok = strsplit(buf, tok, 10)) == 0) + continue; + + pass0(tok, ntok, ac); + } + + return (0); +} + +/* + * Check for environment variables altering the configuration as described + * in resolv.conf(5). Although not documented there, this feature is disabled + * for setuid/setgid programs. + */ +static void +asr_ctx_envopts(struct asr_ctx *ac) +{ + char buf[4096], *e; + size_t s; + + if (issetugid()) { + ac->ac_options |= RES_NOALIASES; + return; + } + + if ((e = getenv("RES_OPTIONS")) != NULL) { + strlcpy(buf, "options ", sizeof buf); + strlcat(buf, e, sizeof buf); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(ac, buf); + } + + if ((e = getenv("LOCALDOMAIN")) != NULL) { + strlcpy(buf, "search ", sizeof buf); + strlcat(buf, e, sizeof buf); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(ac, buf); + } +} + +/* + * Parse a resolv.conf(5) nameserver string into a sockaddr. + */ +static int +asr_parse_nameserver(struct sockaddr *sa, const char *s) +{ + in_port_t portno = 53; + + if (_asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1) + return (-1); + + if (sa->sa_family == PF_INET) + ((struct sockaddr_in *)sa)->sin_port = htons(portno); + else if (sa->sa_family == PF_INET6) + ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); + + return (0); +} + +/* + * Turn a (uncompressed) DNS domain name into a regular nul-terminated string + * where labels are separated by dots. The result is put into the "buf" buffer, + * truncated if it exceeds "max" chars. The function returns "buf". + */ +char * +_asr_strdname(const char *_dname, char *buf, size_t max) +{ + const unsigned char *dname = _dname; + char *res; + size_t left, n, count; + + if (_dname[0] == 0) { + 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); +} + +/* + * Read and split the next line from the given namedb file. + * Return -1 on error, or put the result in the "tokens" array of + * size "ntoken" and returns the number of token on the line. + */ +int +_asr_parse_namedb_line(FILE *file, char **tokens, int ntoken, char *lbuf, size_t sz) +{ + size_t len; + char *buf; + int ntok; + + again: + if ((buf = fgetln(file, &len)) == NULL) + return (-1); + + if (len >= sz) + goto again; + + if (buf[len - 1] == '\n') + len--; + else { + memcpy(lbuf, buf, len); + buf = lbuf; + } + + buf[len] = '\0'; + buf[strcspn(buf, "#")] = '\0'; + if ((ntok = strsplit(buf, tokens, ntoken)) == 0) + goto again; + + return (ntok); +} + +/* + * Update the async context so that it uses the next configured DB. + * Return 0 on success, or -1 if no more DBs is available. + */ +int +_asr_iter_db(struct asr_query *as) +{ + if (as->as_db_idx >= as->as_ctx->ac_dbcount) { + DPRINT("asr_iter_db: done\n"); + return (-1); + } + + as->as_db_idx += 1; + DPRINT("asr_iter_db: %i\n", as->as_db_idx); + + return (0); +} diff --git a/openbsd-compat/libasr/asr.h b/openbsd-compat/libasr/asr.h new file mode 100644 index 00000000..e9725e6b --- /dev/null +++ b/openbsd-compat/libasr/asr.h @@ -0,0 +1,95 @@ +/* $OpenBSD: asr.h,v 1.1 2014/03/26 18:13:15 eric Exp $ */ +/* + * Copyright (c) 2012-2014 Eric Faurot <eric@openbsd.org> + * + * 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. + */ + +/* + * Expected fd conditions + */ +#define ASR_WANT_READ 1 +#define ASR_WANT_WRITE 2 + +/* + * Structure through which asynchronous query results are returned when + * calling asr_run(). + */ +struct asr_result { + /* Fields set if the query is not done yet (asr_run returns 0) */ + int ar_cond; /* ASR_WANT_READ or ASR_WANT_WRITE */ + int ar_fd; /* the fd waiting for io condition */ + int ar_timeout; /* time to wait for in milliseconds */ + + /* Error fields. Depends on the query type. */ + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + /* Result for res_*_async() calls */ + int ar_count; /* number of answers in the dns reply */ + int ar_rcode; /* response code in the dns reply */ + void *ar_data; /* raw reply packet (must be freed) */ + int ar_datalen; /* reply packet length */ + struct sockaddr_storage ar_ns; /* nameserver that responded */ + + /* Result for other calls. Must be freed properly. */ + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; + +/* + * Asynchronous query management. + */ + +/* Forward declaration. The API uses opaque pointers as query handles. */ +struct asr_query; + +int asr_run(struct asr_query *, struct asr_result *); +int asr_run_sync(struct asr_query *, struct asr_result *); +void asr_abort(struct asr_query *); + +/* + * Asynchronous version of the resolver functions. Similar prototypes, with + * an extra context parameter at the end which must currently be set to NULL. + * All functions return a handle suitable for use with the management functions + * above. + */ +struct asr_query *res_send_async(const unsigned char *, int, void *); +struct asr_query *res_query_async(const char *, int, int, void *); +struct asr_query *res_search_async(const char *, int, int, void *); + +struct asr_query *getrrsetbyname_async(const char *, unsigned int, unsigned int, + unsigned int, void *); + +struct asr_query *gethostbyname_async(const char *, void *); +struct asr_query *gethostbyname2_async(const char *, int, void *); +struct asr_query *gethostbyaddr_async(const void *, socklen_t, int, void *); + +struct asr_query *getnetbyname_async(const char *, void *); +struct asr_query *getnetbyaddr_async(in_addr_t, int, void *); + +struct asr_query *getaddrinfo_async(const char *, const char *, + const struct addrinfo *, void *); +struct asr_query *getnameinfo_async(const struct sockaddr *, socklen_t, char *, + size_t, char *, size_t, int, void *); + +/* only there for -portable */ +void asr_freeaddrinfo(struct addrinfo *); + +/* from in event.h */ +struct event_asr * event_asr_run(struct asr_query *, + void (*)(struct asr_result *, void *), void *); diff --git a/openbsd-compat/libasr/asr_compat.c b/openbsd-compat/libasr/asr_compat.c new file mode 100644 index 00000000..ee958357 --- /dev/null +++ b/openbsd-compat/libasr/asr_compat.c @@ -0,0 +1,102 @@ +/* $OpenBSD: asr_debug.c,v 1.25 2018/04/28 15:16:49 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif + +#include "asr_compat.h" + +#ifndef HAVE___P_CLASS +const char * +__p_class(int c) +{ + switch(c) { + case C_IN: return "IN"; + case C_CHAOS: return "CHAOS"; + case C_HS: return "HESIOD"; + case C_ANY: return "ANY"; + default: return "?"; + } +}; +#endif /* !HAVE___P_CLASS */ + +#ifndef HAVE___P_TYPE +const char * +__p_type(int t) +{ + switch(t) { + case T_A: return "A"; + case T_NS: return "NS"; + case T_MD: return "MD"; + case T_MF: return "MF"; + case T_CNAME: return "CNAME"; + case T_SOA: return "SOA"; + case T_MB: return "MB"; + case T_MG: return "MG"; + case T_MR: return "MR"; + case T_NULL: return "NULL"; + case T_WKS: return "WKS"; + case T_PTR: return "PTR"; + case T_HINFO: return "HINFO"; + case T_MINFO: return "MINFO"; + case T_MX: return "MX"; + case T_TXT: return "TXT"; + case T_RP: return "RP"; + case T_AFSDB: return "AFSDB"; + case T_X25: return "X25"; + case T_ISDN: return "ISDN"; + case T_RT: return "RT"; + case T_NSAP: return "NSAP"; + case T_NSAP_PTR:return"NSAP_PTR"; + case T_SIG: return "SIG"; + case T_KEY: return "KEY"; + case T_PX: return "PX"; + case T_GPOS: return "GPOS"; + case T_AAAA: return "AAAA"; + case T_LOC: return "LOC"; + case T_NXT: return "NXT"; + case T_EID: return "EID"; + case T_NIMLOC: return "NIMLOC"; + case T_SRV: return "SRV"; + case T_ATMA: return "ATMA"; + case T_OPT: return "OPT"; + case T_IXFR: return "IXFR"; + case T_AXFR: return "AXFR"; + case T_MAILB: return "MAILB"; + case T_MAILA: return "MAILA"; +#ifdef T_UINFO + case T_UINFO: return "UINFO"; +#endif +#ifdef T_UID + case T_UID: return "UID"; +#endif +#ifdef T_GID + case T_GID: return "GID"; +#endif + case T_NAPTR: return "NAPTR"; +#ifdef T_UNSPEC + case T_UNSPEC: return "UNSPEC"; +#endif + case T_ANY: return "ANY"; + default: return "?"; + } +} +#endif /* !HAVE___P_TYPE */ diff --git a/openbsd-compat/libasr/asr_compat.h b/openbsd-compat/libasr/asr_compat.h new file mode 100644 index 00000000..2c7686a4 --- /dev/null +++ b/openbsd-compat/libasr/asr_compat.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * 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. + */ + + +/* source compat */ +#define ASR_BUFSIZ 1024 + +#define DEF_WEAK(x) +#define __THREAD_NAME(x) __thread_name_ ## x + +#ifndef __BEGIN_HIDDEN_DECLS +#define __BEGIN_HIDDEN_DECLS +#endif +#ifndef __END_HIDDEN_DECLS +#define __END_HIDDEN_DECLS +#endif + +/* + * netdb.h + */ +#ifndef NETDB_SUCCESS +#define NETDB_SUCCESS 0 +#endif + +#ifndef NETDB_INTERNAL +#define NETDB_INTERNAL -1 +#endif + +#ifndef AI_FQDN +#define AI_FQDN AI_CANONNAME +#endif + +#ifndef AI_MASK +#define AI_MASK \ + (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV | AI_ADDRCONFIG | AI_FQDN) +#endif + +#ifndef SCOPE_DELIMITER +#define SCOPE_DELIMITER '%' +#endif + +#ifndef _PATH_HOSTS +#define _PATH_HOSTS "/etc/hosts" +#endif + +#ifndef _PATH_NETWORKS +#define _PATH_NETWORKS "/etc/networks" +#endif + +/* + * arpa/nameserv.h + */ +#ifndef T_OPT +#define T_OPT 41 +#endif + +#ifndef DNS_MESSAGEEXTFLAG_DO +#define DNS_MESSAGEEXTFLAG_DO 0x8000U +#endif + +#ifndef HAVE___P_CLASS +const char * __p_class(int); +#endif + +#ifndef HAVE___P_TYPE +const char * __p_type(int); +#endif diff --git a/openbsd-compat/libasr/asr_debug.c b/openbsd-compat/libasr/asr_debug.c new file mode 100644 index 00000000..be80436a --- /dev/null +++ b/openbsd-compat/libasr/asr_debug.c @@ -0,0 +1,362 @@ +/* $OpenBSD: asr_debug.c,v 1.26 2019/07/03 03:24:03 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <arpa/inet.h> +#include <netdb.h> + +#include <asr.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" + +static const char *rcodetostr(uint16_t); +static const char *print_dname(const char *, char *, size_t); +static const char *print_header(const struct asr_dns_header *, char *, size_t); +static const char *print_query(const struct asr_dns_query *, char *, size_t); +static const char *print_rr(const struct asr_dns_rr *, char *, size_t); + +FILE *_asr_debug = NULL; + +#define OPCODE_SHIFT 11 + +static const char * +rcodetostr(uint16_t v) +{ + switch (v) { + case NOERROR: return "NOERROR"; + case FORMERR: return "FORMERR"; + case SERVFAIL: return "SERVFAIL"; + case NXDOMAIN: return "NXDOMAIN"; + case NOTIMP: return "NOTIMP"; + case REFUSED: return "REFUSED"; + default: return "?"; + } +} + +static const char * +print_dname(const char *_dname, char *buf, size_t max) +{ + return (_asr_strdname(_dname, buf, max)); +} + +static const char * +print_rr(const struct asr_dns_rr *rr, char *buf, size_t max) +{ + char *res; + char tmp[256]; + char tmp2[256]; + int r; + + res = buf; + + r = snprintf(buf, max, "%s %u %s %s ", + print_dname(rr->rr_dname, tmp, sizeof tmp), + rr->rr_ttl, + __p_class(rr->rr_class), + __p_type(rr->rr_type)); + if (r < 0 || (size_t)r >= max) { + buf[0] = '\0'; + return (buf); + } + + if ((size_t)r >= max) + return (buf); + + max -= r; + buf += r; + + switch (rr->rr_type) { + case T_CNAME: + print_dname(rr->rr.cname.cname, buf, max); + break; + case T_MX: + snprintf(buf, max, "%lu %s", + (unsigned long)rr->rr.mx.preference, + print_dname(rr->rr.mx.exchange, tmp, sizeof tmp)); + break; + case T_NS: + print_dname(rr->rr.ns.nsname, buf, max); + break; + case T_PTR: + print_dname(rr->rr.ptr.ptrname, buf, max); + break; + case T_SOA: + snprintf(buf, max, "%s %s %lu %lu %lu %lu %lu", + print_dname(rr->rr.soa.rname, tmp, sizeof tmp), + print_dname(rr->rr.soa.mname, tmp2, sizeof tmp2), + (unsigned long)rr->rr.soa.serial, + (unsigned long)rr->rr.soa.refresh, + (unsigned long)rr->rr.soa.retry, + (unsigned long)rr->rr.soa.expire, + (unsigned long)rr->rr.soa.minimum); + break; + case T_A: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntop(AF_INET, + &rr->rr.in_a.addr, tmp, sizeof tmp)); + break; + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntop(AF_INET6, + &rr->rr.in_aaaa.addr6, tmp, sizeof tmp)); + break; + default: + other: + snprintf(buf, max, "(rdlen=%i)", (int)rr->rr.other.rdlen); + break; + } + + return (res); +} + +static const char * +print_query(const struct asr_dns_query *q, char *buf, size_t max) +{ + char b[256]; + + snprintf(buf, max, "%s %s %s", + print_dname(q->q_dname, b, sizeof b), + __p_class(q->q_class), __p_type(q->q_type)); + + return (buf); +} + +static const char * +print_header(const struct asr_dns_header *h, char *buf, size_t max) +{ + snprintf(buf, max, + "id:0x%04x %s op:%i %s %s %s %s z:%i %s %s r:%s qd:%i an:%i ns:%i ar:%i", + ((int)h->id), + (h->flags & QR_MASK) ? "QR":" ", + (int)(OPCODE(h->flags) >> OPCODE_SHIFT), + (h->flags & AA_MASK) ? "AA":" ", + (h->flags & TC_MASK) ? "TC":" ", + (h->flags & RD_MASK) ? "RD":" ", + (h->flags & RA_MASK) ? "RA":" ", + (h->flags & Z_MASK), + (h->flags & AD_MASK) ? "AD":" ", + (h->flags & CD_MASK) ? "CD":" ", + rcodetostr(RCODE(h->flags)), + h->qdcount, h->ancount, h->nscount, h->arcount); + + return (buf); +} + +void +_asr_dump_packet(FILE *f, const void *data, size_t len) +{ + char buf[1024]; + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int i, an, ns, ar, n; + + if (f == NULL) + return; + + _asr_unpack_init(&p, data, len); + + if (_asr_unpack_header(&p, &h) == -1) { + fprintf(f, ";; BAD PACKET: %s\n", strerror(p.err)); + return; + } + + fprintf(f, ";; HEADER %s\n", print_header(&h, buf, sizeof buf)); + + if (h.qdcount) + fprintf(f, ";; QUERY SECTION:\n"); + for (i = 0; i < h.qdcount; i++) { + if (_asr_unpack_query(&p, &q) == -1) + goto error; + fprintf(f, "%s\n", print_query(&q, buf, sizeof buf)); + } + + an = 0; + ns = an + h.ancount; + ar = ns + h.nscount; + n = ar + h.arcount; + + for (i = 0; i < n; i++) { + if (i == an) + fprintf(f, "\n;; ANSWER SECTION:\n"); + if (i == ns) + fprintf(f, "\n;; AUTHORITY SECTION:\n"); + if (i == ar) + fprintf(f, "\n;; ADDITIONAL SECTION:\n"); + + if (_asr_unpack_rr(&p, &rr) == -1) + goto error; + fprintf(f, "%s\n", print_rr(&rr, buf, sizeof buf)); + } + + if (p.offset != len) + fprintf(f, ";; REMAINING GARBAGE %zu\n", len - p.offset); + + error: + if (p.err) + fprintf(f, ";; ERROR AT OFFSET %zu/%zu: %s\n", p.offset, p.len, + strerror(p.err)); +} + +const char * +_asr_print_sockaddr(const struct sockaddr *sa, char *buf, size_t len) +{ + char h[256]; + int portno; + union { + const struct sockaddr *sa; + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + } s; + + s.sa = sa; + + switch (sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &s.sin->sin_addr, h, sizeof h); + portno = ntohs(s.sin->sin_port); + break; + case AF_INET6: + inet_ntop(AF_INET6, &s.sin6->sin6_addr, h, sizeof h); + portno = ntohs(s.sin6->sin6_port); + break; + default: + snprintf(buf, len, "?"); + return (buf); + } + + snprintf(buf, len, "%s:%i", h, portno); + return (buf); +} + +void +_asr_dump_config(FILE *f, struct asr *a) +{ + char buf[256]; + int i; + struct asr_ctx *ac; + unsigned int o; + + if (f == NULL) + return; + + ac = a->a_ctx; + + fprintf(f, "--------- ASR CONFIG ---------------\n"); + fprintf(f, "DOMAIN \"%s\"\n", ac->ac_domain); + fprintf(f, "SEARCH\n"); + for (i = 0; i < ac->ac_domcount; i++) + fprintf(f, " \"%s\"\n", ac->ac_dom[i]); + fprintf(f, "OPTIONS\n"); + fprintf(f, " options:"); + o = ac->ac_options; + +#define PRINTOPT(flag, n) if (o & (flag)) { fprintf(f, " " n); o &= ~(flag); } + PRINTOPT(RES_INIT, "INIT"); + PRINTOPT(RES_DEBUG, "DEBUG"); + PRINTOPT(RES_USEVC, "USEVC"); + PRINTOPT(RES_IGNTC, "IGNTC"); + PRINTOPT(RES_RECURSE, "RECURSE"); + PRINTOPT(RES_DEFNAMES, "DEFNAMES"); + PRINTOPT(RES_STAYOPEN, "STAYOPEN"); + PRINTOPT(RES_DNSRCH, "DNSRCH"); + PRINTOPT(RES_NOALIASES, "NOALIASES"); + PRINTOPT(RES_USE_EDNS0, "USE_EDNS0"); + PRINTOPT(RES_USE_DNSSEC, "USE_DNSSEC"); + if (o) + fprintf(f, " 0x%08x", o); + fprintf(f, "\n"); + + fprintf(f, " ndots: %i\n", ac->ac_ndots); + fprintf(f, " family:"); + for (i = 0; ac->ac_family[i] != -1; i++) + fprintf(f, " %s", (ac->ac_family[i] == AF_INET)?"inet4":"inet6"); + fprintf(f, "\n"); + fprintf(f, "NAMESERVERS timeout=%i retry=%i\n", + ac->ac_nstimeout, + ac->ac_nsretries); + for (i = 0; i < ac->ac_nscount; i++) + fprintf(f, " %s\n", _asr_print_sockaddr(ac->ac_ns[i], buf, + sizeof buf)); + fprintf(f, "LOOKUP %s", ac->ac_db); + fprintf(f, "\n------------------------------------\n"); +} + +#define CASE(n) case n: return #n + +const char * +_asr_statestr(int state) +{ + switch (state) { + CASE(ASR_STATE_INIT); + CASE(ASR_STATE_NEXT_DOMAIN); + CASE(ASR_STATE_NEXT_DB); + CASE(ASR_STATE_SAME_DB); + CASE(ASR_STATE_NEXT_FAMILY); + CASE(ASR_STATE_NEXT_NS); + CASE(ASR_STATE_UDP_SEND); + CASE(ASR_STATE_UDP_RECV); + CASE(ASR_STATE_TCP_WRITE); + CASE(ASR_STATE_TCP_READ); + CASE(ASR_STATE_PACKET); + CASE(ASR_STATE_SUBQUERY); + CASE(ASR_STATE_NOT_FOUND); + CASE(ASR_STATE_HALT); + default: + return "?"; + } +}; + +const char * +_asr_querystr(int type) +{ + switch (type) { + CASE(ASR_SEND); + CASE(ASR_SEARCH); + CASE(ASR_GETRRSETBYNAME); + CASE(ASR_GETHOSTBYNAME); + CASE(ASR_GETHOSTBYADDR); + CASE(ASR_GETADDRINFO); + CASE(ASR_GETNAMEINFO); + default: + return "?"; + } +} + +const char * +_asr_transitionstr(int type) +{ + switch (type) { + CASE(ASYNC_COND); + CASE(ASYNC_DONE); + default: + return "?"; + } +} diff --git a/openbsd-compat/libasr/asr_private.h b/openbsd-compat/libasr/asr_private.h new file mode 100644 index 00000000..acf0e874 --- /dev/null +++ b/openbsd-compat/libasr/asr_private.h @@ -0,0 +1,359 @@ +/* $OpenBSD: asr_private.h,v 1.47 2018/04/28 15:16:49 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <stdio.h> + +#include "asr_compat.h" + +#define QR_MASK (0x1 << 15) +#define OPCODE_MASK (0xf << 11) +#define AA_MASK (0x1 << 10) +#define TC_MASK (0x1 << 9) +#define RD_MASK (0x1 << 8) +#define RA_MASK (0x1 << 7) +#define Z_MASK (0x1 << 6) +#define AD_MASK (0x1 << 5) +#define CD_MASK (0x1 << 4) +#define RCODE_MASK (0xf) + +#define OPCODE(v) ((v) & OPCODE_MASK) +#define RCODE(v) ((v) & RCODE_MASK) + + +struct asr_pack { + char *buf; + size_t len; + size_t offset; + int err; +}; + +struct asr_unpack { + const char *buf; + size_t len; + size_t offset; + int err; +}; + +struct asr_dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct asr_dns_query { + char q_dname[MAXDNAME]; + uint16_t q_type; + uint16_t q_class; +}; + +struct asr_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; +}; + + +#define ASR_MAXNS 5 +#define ASR_MAXDB 3 +#define ASR_MAXDOM 10 + +enum async_type { + ASR_SEND, + ASR_SEARCH, + ASR_GETRRSETBYNAME, + ASR_GETHOSTBYNAME, + ASR_GETHOSTBYADDR, + ASR_GETADDRINFO, + ASR_GETNAMEINFO, +}; + +#define ASR_DB_FILE 'f' +#define ASR_DB_DNS 'b' + +struct asr_ctx { + int ac_refcount; + int ac_options; + int ac_ndots; + char *ac_domain; + int ac_domcount; + char *ac_dom[ASR_MAXDOM]; + int ac_dbcount; + char ac_db[ASR_MAXDB + 1]; + int ac_family[3]; + + int ac_nscount; + int ac_nstimeout; + int ac_nsretries; + struct sockaddr *ac_ns[ASR_MAXNS]; + +}; + +struct asr { + pid_t a_pid; + time_t a_mtime; + time_t a_rtime; + struct asr_ctx *a_ctx; +}; + +#define ASYNC_COND 0 +#define ASYNC_DONE 1 + +#define ASYNC_DOM_FQDN 0x00000001 +#define ASYNC_DOM_NDOTS 0x00000002 +#define ASYNC_DOM_DOMAIN 0x00000004 +#define ASYNC_DOM_ASIS 0x00000008 + +#define ASYNC_NODATA 0x00000100 +#define ASYNC_AGAIN 0x00000200 + +#define ASYNC_GETNET 0x00001000 +#define ASYNC_EXTOBUF 0x00002000 + +#define ASYNC_NO_INET 0x00010000 +#define ASYNC_NO_INET6 0x00020000 + +struct asr_query { + int (*as_run)(struct asr_query *, struct asr_result *); + struct asr_ctx *as_ctx; + int as_type; + int as_flags; + int as_state; + + /* cond */ + int as_timeout; + int as_fd; + struct asr_query *as_subq; + + /* loop indices in ctx */ + int as_dom_step; + int as_dom_idx; + int as_dom_flags; + int as_family_idx; + int as_db_idx; + + int as_count; + + union { + struct { + uint16_t reqid; + int class; + int type; + char *dname; /* not fqdn! */ + int rcode; /* response code */ + int ancount; /* answer count */ + + int nsidx; + int nsloop; + + /* io buffers for query/response */ + unsigned char *obuf; + size_t obuflen; + size_t obufsize; + unsigned char *ibuf; + size_t ibuflen; + size_t ibufsize; + size_t datalen; /* for tcp io */ + uint16_t pktlen; + } dns; + + struct { + int class; + int type; + char *name; + int saved_h_errno; + } search; + + struct { + int flags; + int class; + int type; + char *name; + } rrset; + + struct { + char *name; + int family; + char addr[16]; + int addrlen; + int subq_h_errno; + } hostnamadr; + + struct { + char *hostname; + char *servname; + int port_tcp; + int port_udp; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + struct addrinfo hints; + char *fqdn; + struct addrinfo *aifirst; + struct addrinfo *ailast; + } ai; + + struct { + char *hostname; + char *servname; + size_t hostnamelen; + size_t servnamelen; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + int flags; + } ni; +#define MAXTOKEN 10 + } as; + +}; + +#define AS_DB(p) ((p)->as_ctx->ac_db[(p)->as_db_idx - 1]) +#define AS_FAMILY(p) ((p)->as_ctx->ac_family[(p)->as_family_idx]) + +enum asr_state { + ASR_STATE_INIT, + ASR_STATE_NEXT_DOMAIN, + ASR_STATE_NEXT_DB, + ASR_STATE_SAME_DB, + ASR_STATE_NEXT_FAMILY, + ASR_STATE_NEXT_NS, + ASR_STATE_UDP_SEND, + ASR_STATE_UDP_RECV, + ASR_STATE_TCP_WRITE, + ASR_STATE_TCP_READ, + ASR_STATE_PACKET, + ASR_STATE_SUBQUERY, + ASR_STATE_NOT_FOUND, + ASR_STATE_HALT, +}; + +#define MAXPACKETSZ 4096 + +__BEGIN_HIDDEN_DECLS + +/* asr_utils.c */ +void _asr_pack_init(struct asr_pack *, char *, size_t); +int _asr_pack_header(struct asr_pack *, const struct asr_dns_header *); +int _asr_pack_query(struct asr_pack *, uint16_t, uint16_t, const char *); +int _asr_pack_edns0(struct asr_pack *, uint16_t, int); +void _asr_unpack_init(struct asr_unpack *, const char *, size_t); +int _asr_unpack_header(struct asr_unpack *, struct asr_dns_header *); +int _asr_unpack_query(struct asr_unpack *, struct asr_dns_query *); +int _asr_unpack_rr(struct asr_unpack *, struct asr_dns_rr *); +int _asr_sockaddr_from_str(struct sockaddr *, int, const char *); +ssize_t _asr_dname_from_fqdn(const char *, char *, size_t); +ssize_t _asr_addr_as_fqdn(const char *, int, char *, size_t); + +/* asr.c */ +void _asr_resolver_done(void *); +struct asr_ctx *_asr_use_resolver(void *); +struct asr_ctx *_asr_no_resolver(void); +void _asr_ctx_unref(struct asr_ctx *); +struct asr_query *_asr_async_new(struct asr_ctx *, int); +void _asr_async_free(struct asr_query *); +size_t _asr_make_fqdn(const char *, const char *, char *, size_t); +char *_asr_strdname(const char *, char *, size_t); +int _asr_iter_db(struct asr_query *); +int _asr_parse_namedb_line(FILE *, char **, int, char *, size_t); + +/* *_async.c */ +struct asr_query *_res_query_async_ctx(const char *, int, int, struct asr_ctx *); +struct asr_query *_res_search_async_ctx(const char *, int, int, struct asr_ctx *); +struct asr_query *_gethostbyaddr_async_ctx(const void *, socklen_t, int, + struct asr_ctx *); + +int _asr_iter_domain(struct asr_query *, const char *, char *, size_t); + +#ifdef DEBUG + +#define DPRINT(...) do { if(_asr_debug) { \ + fprintf(_asr_debug, __VA_ARGS__); \ + } } while (0) +#define DPRINT_PACKET(n, p, s) do { if(_asr_debug) { \ + fprintf(_asr_debug, "----- %s -----\n", n); \ + _asr_dump_packet(_asr_debug, (p), (s)); \ + fprintf(_asr_debug, "--------------\n"); \ + } } while (0) + +#else /* DEBUG */ + +#define DPRINT(...) +#define DPRINT_PACKET(...) + +#endif /* DEBUG */ + +const char *_asr_querystr(int); +const char *_asr_statestr(int); +const char *_asr_transitionstr(int); +const char *_asr_print_sockaddr(const struct sockaddr *, char *, size_t); +void _asr_dump_config(FILE *, struct asr *); +void _asr_dump_packet(FILE *, const void *, size_t); + +extern FILE *_asr_debug; + +#define async_set_state(a, s) do { \ + DPRINT("asr: [%s@%p] %s -> %s\n", \ + _asr_querystr((a)->as_type), \ + as, \ + _asr_statestr((a)->as_state), \ + _asr_statestr((s))); \ + (a)->as_state = (s); } while (0) + +__END_HIDDEN_DECLS diff --git a/openbsd-compat/libasr/asr_run.3 b/openbsd-compat/libasr/asr_run.3 new file mode 100644 index 00000000..61c1b02c --- /dev/null +++ b/openbsd-compat/libasr/asr_run.3 @@ -0,0 +1,316 @@ +.\" $OpenBSD: asr_run.3,v 1.3 2017/02/18 19:23:05 jca Exp $ +.\" +.\" Copyright (c) 2012-2014, Eric Faurot <eric@openbsd.org> +.\" +.\" 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 18 2017 $ +.Dt ASR_RUN 3 +.Os +.Sh NAME +.Nm asr_run , +.Nm asr_run_sync , +.Nm asr_abort , +.Nm res_send_async , +.Nm res_query_async , +.Nm res_search_async , +.Nm getrrsetbyname_async , +.Nm gethostbyname_async , +.Nm gethostbyname2_async , +.Nm gethostbyaddr_async , +.Nm getnetbyname_async , +.Nm getnetbyaddr_async , +.Nm getaddrinfo_async , +.Nm getnameinfo_async +.Nd asynchronous resolver functions +.Sh SYNOPSIS +.In sys/types.h +.In sys/socket.h +.In netdb.h +.In asr.h +.Ft int +.Fn asr_run "struct asr_query *aq" "struct asr_result *ar" +.Ft int +.Fn asr_run_sync "struct asr_query *aq" "struct asr_result *ar" +.Ft void +.Fn asr_abort "struct asr_query *aq" +.Ft struct asr_query * +.Fn res_send_async "const unsigned char *pkt" "int pktlen" "void *asr" +.Ft struct asr_query * +.Fn res_query_async "const char *name" "int class" "int type" "void *asr" +.Ft struct asr_query * +.Fn res_search_async "const char *name" "int class" "int type" "void *asr" +.Ft struct asr_query * +.Fn getrrsetbyname_async "const char *hostname" "unsigned int rdclass" "unsigned int rdtype" "unsigned int flags" "void *asr" +.Ft struct asr_query * +.Fn gethostbyname_async "const char *name" "void *asr" +.Ft struct asr_query * +.Fn gethostbyname2_async "const char *name" "int af" "void *asr" +.Ft struct asr_query * +.Fn gethostbyaddr_async "const void *addr" "socklen_t len" "int af" "void *asr" +.Ft struct asr_query * +.Fn getnetbyname_async "const char *name" "void *asr" +.Ft struct asr_query * +.Fn getnetbyaddr_async "in_addr_t net" "int type" "void *asr" +.Ft struct asr_query * +.Fn getaddrinfo_async "const char *hostname" "const char *servname" "const struct addrinfo *hints" "void *asr" +.Ft struct asr_query * +.Fn getnameinfo_async "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" "void *asr" +.Sh DESCRIPTION +The +.Nm asr +functions provide a simple interface for asynchronous address +resolution and nameserver querying. +They should be used in place of the classical resolver functions +of libc when blocking is not desirable. +.Pp +The principle of operation is as follows: +All async requests are made against an +.Nm asr +context which basically defines a list of sources to query and a +strategy to do so. +The user creates a query through one of the dedicated functions, and +gets a handle representing the internal query. +A query is a state-machine that can be run to try to fulfill a +particular request. +This is done by calling in a generic API that performs the state +transitions until it needs to give the control back to the user, +either because a result is available, or because the next transition +implies a blocking call (a file descriptor needs to be read from or +written to). +The user is responsible for dealing with the situation: either get +the result, or wait until the fd conditions are met, and then call +back into the resolving machinery when it is ready to proceed. +.Pp +The +.Fn asr_run +function drives the resolving process. +It runs the asynchronous query represented by the +.Fa aq +handle until a result is available, or until it cannot continue +without blocking. +The results are returned to the user through the +.Fa ar +parameter, which must be a valid pointer to user allocated memory. +.Fa ar +is defined as: +.Bd -literal +struct asr_result { + + /* Fields set if the query is not done yet (asr_run returns 0) */ + int ar_cond; /* ASR_WANT_READ or ASR_WANT_WRITE */ + int ar_fd; /* the fd waiting for io condition */ + int ar_timeout; /* time to wait for in milliseconds */ + + /* Error fields. Depends on the query type. */ + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + /* Result for res_*_async() calls */ + int ar_count; /* number of answers in the dns reply */ + int ar_rcode; /* response code in the dns reply */ + void *ar_data; /* raw reply packet (must be freed) */ + int ar_datalen; /* reply packet length */ + struct sockaddr_storage ar_ns; /* nameserver that responded */ + + /* Result for other calls. Must be freed properly. */ + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; +.Ed +.Pp +The function returns one of the following values: +.Bl -tag -width "0 " -offset indent +.It 0 +The query cannot be processed further until a specific condition on a +file descriptor becomes true. +The following members of the +.Fa ar +structure are filled: +.Pp +.Bl -tag -width "ar_timeout " -compact +.It Fa ar_cond +one of ASR_WANT_READ or ASR_WANT_WRITE, +.It Fa ar_fd +the file descriptor waiting for an IO operation, +.It Fa ar_timeout +the amount of time to wait for in milliseconds. +.El +.Pp +The caller is expected to call +.Fn asr_run +again once the condition holds or the timeout expires. +.It 1 +The query is completed. +The members relevant to the actual async query type are set accordingly, +including error conditions. +In any case, the query is cleared and its handle is invalidated. +.El +.Pp +Note that although the query itself may fail (the error being properly reported +in the +.Fa ar +structure), the +.Fn asr_run +function itself cannot fail and it always preserves errno. +.Pp +The +.Fn asr_run_sync +function is a wrapper around +.Fn asr_run +that handles the read/write conditions, thus falling back to a blocking +interface. +It only returns 1. +It also preserves errno. +.Pp +The +.Fn asr_abort +function clears a running query. +It can be called when the query is waiting on a file descriptor. +Note that a completed query is already cleared when +.Fn asr_run +returns, so +.Fn asr_abort +must not be called in this case. +.Pp +The remaining functions are used to initiate different kinds of query +on the +.Fa asr +resolver context. +The specific operational details for each of them are described below. +All functions return a handle to an internal query, or NULL if they could +not allocate the necessary resources to initiate the query. +All other errors (especially invalid parameters) are reported when calling +.Fn asr_run . +They usually have the same interface as an existing resolver function, with +an additional +.Ar asr +argument, which specifies the context to use for this request. +For now, the argument must always be NULL, which will use the default +context for the current thread. +.Pp +The +.Fn res_send_async , +.Fn res_query_async +and +.Fn res_search_async +functions are asynchronous versions of the standard libc resolver routines. +Their interface is very similar, except that the response buffer is always +allocated internally. +The return value is found upon completion in the +.Fa ar_datalen +member of the response structure. +In addition, the +.Fa ar_ns +structure contains the address of the DNS server that sent the response, +.Fa ar_rcode +contains the code returned by the server in the DNS response packet, and +.Fa ar_count +contains the number of answers in the packet. +If a response is received it is placed in a newly allocated buffer +and returned as +.Fa ar_data +member. +This buffer must be freed by the caller. +On error, the +.Fa ar_errno +and +.Fa ar_h_errno +members are set accordingly. +.Pp +The +.Fn getrrsetbyname_async +function is an asynchronous version of +.Xr getrrsetbyname 3 . +Upon completion, the return code is found in +.Fa ar_rrset_errno +and the address to the newly allocated result set is set in +.Fa ar_rrsetinfo . +As for the blocking function, it must be freed by calling +.Xr freerrset 3 . +.Pp +The +.Fn gethostbyname_async , +.Fn gethostbyname2_async +and +.Fn gethostbyaddr_async +functions provide an asynchronous version of the network host entry functions. +Upon completion, +.Ar ar_h_errno +is set and the resulting hostent address, if found, is set +in the +.Ar ar_hostent +field. +Note that unlike their blocking counterparts, these functions always return a +pointer to newly allocated memory, which must be released by the caller using +.Xr free 3 . +.Pp +Similarly, the +.Fn getnetbyname_async +and +.Fn getnetbyaddr_async +functions provide an asynchronous version of the network entry functions. +Upon completion, +.Ar ar_h_errno +is set and the resulting netent address, if found, is set +in the +.Ar ar_netent +field. +The memory there is also allocated for the request, and it must be freed by +.Xr free 3 . +.Pp +The +.Fn getaddrinfo_async +function is an asynchronous version of the +.Xr getaddrinfo 3 +call. +It provides a chain of addrinfo structures with all valid combinations of +socket address for the given +.Fa hostname , +.Fa servname +and +.Fa hints . +Those three parameters have the same meaning as for the blocking counterpart. +Upon completion the return code is set in +.Fa ar_gai_errno . +The +.Fa ar_errno +member may also be set. +On success, the +.Fa ar_addrinfo +member points to a newly allocated list of addrinfo. +This list must be freed with +.Xr freeaddrinfo 3 . +.Sh WORKING WITH THREADS +This implementation of the asynchronous resolver interface is thread-safe +and lock-free internally, but the following restriction applies: +Two different threads must not create queries on the same context or +run queries originating from the same context at the same time. +If they want to do that, all calls must be protected by a mutex around +that context. +.Pp +It is generally not a problem since the main point of the asynchronous +resolver is to multiplex queries within a single thread of control, +so sharing a resolver among threads is not useful. +.Sh SEE ALSO +.Xr getaddrinfo 3 , +.Xr gethostbyname 3 , +.Xr getnameinfo 3 , +.Xr getnetbyname 3 , +.Xr getrrsetbyname 3 , +.Xr res_send 3 , +.Xr resolv.conf 5 diff --git a/openbsd-compat/libasr/asr_utils.c b/openbsd-compat/libasr/asr_utils.c new file mode 100644 index 00000000..e3d24c93 --- /dev/null +++ b/openbsd-compat/libasr/asr_utils.c @@ -0,0 +1,574 @@ +/* $OpenBSD: asr_utils.c,v 1.18 2017/09/23 20:55:06 jca Exp $ */ +/* + * Copyright (c) 2009-2012 Eric Faurot <eric@faurot.net> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int dname_check_label(const char *, size_t); +static ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, + char *, size_t); + +static int unpack_data(struct asr_unpack *, void *, size_t); +static int unpack_u16(struct asr_unpack *, uint16_t *); +static int unpack_u32(struct asr_unpack *, uint32_t *); +static int unpack_inaddr(struct asr_unpack *, struct in_addr *); +static int unpack_in6addr(struct asr_unpack *, struct in6_addr *); +static int unpack_dname(struct asr_unpack *, char *, size_t); + +static int pack_data(struct asr_pack *, const void *, size_t); +static int pack_u16(struct asr_pack *, uint16_t); +static int pack_dname(struct asr_pack *, const char *); + +static int +dname_check_label(const char *s, size_t l) +{ + if (l == 0 || l > 63) + return (-1); + + return (0); +} + +ssize_t +_asr_dname_from_fqdn(const char *str, char *dst, size_t max) +{ + ssize_t res; + size_t l, n; + char *d; + + res = 0; + + /* special case: the root domain */ + if (str[0] == '.') { + if (str[1] != '\0') + return (-1); + if (dst && max >= 1) + *dst = '\0'; + return (1); + } + + for (; *str; str = d + 1) { + + d = strchr(str, '.'); + if (d == NULL || d == str) + return (-1); + + l = (d - str); + + if (dname_check_label(str, l) == -1) + return (-1); + + res += l + 1; + + if (dst) { + *dst++ = l; + max -= 1; + n = (l > max) ? max : l; + memmove(dst, str, n); + max -= n; + if (max == 0) + dst = NULL; + else + dst += n; + } + } + + if (dst) + *dst++ = '\0'; + + return (res + 1); +} + +static 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); + + if (dname_check_label(data + offset + 1, n) == -1) + 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); +} + +void +_asr_pack_init(struct asr_pack *pack, char *buf, size_t len) +{ + pack->buf = buf; + pack->len = len; + pack->offset = 0; + pack->err = 0; +} + +void +_asr_unpack_init(struct asr_unpack *unpack, const char *buf, size_t len) +{ + unpack->buf = buf; + unpack->len = len; + unpack->offset = 0; + unpack->err = 0; +} + +static int +unpack_data(struct asr_unpack *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = EOVERFLOW; + return (-1); + } + + memmove(data, p->buf + p->offset, len); + p->offset += len; + + return (0); +} + +static int +unpack_u16(struct asr_unpack *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +static int +unpack_u32(struct asr_unpack *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +static int +unpack_inaddr(struct asr_unpack *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +static int +unpack_in6addr(struct asr_unpack *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +static int +unpack_dname(struct asr_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 = EINVAL; + return (-1); + } + if (e < 0 || e > MAXDNAME) { + p->err = ERANGE; + return (-1); + } + + return (0); +} + +int +_asr_unpack_header(struct asr_unpack *p, struct asr_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 +_asr_unpack_query(struct asr_unpack *p, struct asr_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 +_asr_unpack_rr(struct asr_unpack *p, struct asr_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 = EOVERFLOW; + 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 = EINVAL; + + return (p->err) ? (-1) : (0); +} + +static int +pack_data(struct asr_pack *p, const void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len < p->offset + len) { + p->err = EOVERFLOW; + return (-1); + } + + memmove(p->buf + p->offset, data, len); + p->offset += len; + + return (0); +} + +static int +pack_u16(struct asr_pack *p, uint16_t v) +{ + v = htons(v); + + return (pack_data(p, &v, 2)); +} + +static int +pack_dname(struct asr_pack *p, const char *dname) +{ + /* dname compression would be nice to have here. + * need additionnal context. + */ + return (pack_data(p, dname, strlen(dname) + 1)); +} + +int +_asr_pack_header(struct asr_pack *p, const struct asr_dns_header *h) +{ + struct asr_dns_header c; + + c.id = h->id; + c.flags = htons(h->flags); + c.qdcount = htons(h->qdcount); + c.ancount = htons(h->ancount); + c.nscount = htons(h->nscount); + c.arcount = htons(h->arcount); + + return (pack_data(p, &c, HFIXEDSZ)); +} + +int +_asr_pack_query(struct asr_pack *p, uint16_t type, uint16_t class, const char *dname) +{ + pack_dname(p, dname); + pack_u16(p, type); + pack_u16(p, class); + + return (p->err) ? (-1) : (0); +} + +int +_asr_pack_edns0(struct asr_pack *p, uint16_t pktsz, int dnssec_do) +{ + DPRINT("asr EDNS0 pktsz:%hu dnssec:%s\n", pktsz, + dnssec_do ? "yes" : "no"); + + pack_dname(p, ""); /* root */ + pack_u16(p, T_OPT); /* OPT */ + pack_u16(p, pktsz); /* UDP payload size */ + + /* extended RCODE and flags */ + pack_u16(p, 0); + pack_u16(p, dnssec_do ? DNS_MESSAGEEXTFLAG_DO : 0); + + pack_u16(p, 0); /* RDATA len */ + + return (p->err) ? (-1) : (0); +} + +int +_asr_sockaddr_from_str(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 (_asr_sockaddr_from_str(sa, PF_INET, str) == 0) + return (0); + return _asr_sockaddr_from_str(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: + 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); +} + +ssize_t +_asr_addr_as_fqdn(const char *addr, int family, char *dst, size_t max) +{ + const struct in6_addr *in6_addr; + in_addr_t in_addr; + + switch (family) { + case AF_INET: + in_addr = ntohl(*((const in_addr_t *)addr)); + snprintf(dst, max, + "%d.%d.%d.%d.in-addr.arpa.", + in_addr & 0xff, + (in_addr >> 8) & 0xff, + (in_addr >> 16) & 0xff, + (in_addr >> 24) & 0xff); + break; + case AF_INET6: + in6_addr = (const struct in6_addr *)addr; + snprintf(dst, max, + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "ip6.arpa.", + in6_addr->s6_addr[15] & 0xf, + (in6_addr->s6_addr[15] >> 4) & 0xf, + in6_addr->s6_addr[14] & 0xf, + (in6_addr->s6_addr[14] >> 4) & 0xf, + in6_addr->s6_addr[13] & 0xf, + (in6_addr->s6_addr[13] >> 4) & 0xf, + in6_addr->s6_addr[12] & 0xf, + (in6_addr->s6_addr[12] >> 4) & 0xf, + in6_addr->s6_addr[11] & 0xf, + (in6_addr->s6_addr[11] >> 4) & 0xf, + in6_addr->s6_addr[10] & 0xf, + (in6_addr->s6_addr[10] >> 4) & 0xf, + in6_addr->s6_addr[9] & 0xf, + (in6_addr->s6_addr[9] >> 4) & 0xf, + in6_addr->s6_addr[8] & 0xf, + (in6_addr->s6_addr[8] >> 4) & 0xf, + in6_addr->s6_addr[7] & 0xf, + (in6_addr->s6_addr[7] >> 4) & 0xf, + in6_addr->s6_addr[6] & 0xf, + (in6_addr->s6_addr[6] >> 4) & 0xf, + in6_addr->s6_addr[5] & 0xf, + (in6_addr->s6_addr[5] >> 4) & 0xf, + in6_addr->s6_addr[4] & 0xf, + (in6_addr->s6_addr[4] >> 4) & 0xf, + in6_addr->s6_addr[3] & 0xf, + (in6_addr->s6_addr[3] >> 4) & 0xf, + in6_addr->s6_addr[2] & 0xf, + (in6_addr->s6_addr[2] >> 4) & 0xf, + in6_addr->s6_addr[1] & 0xf, + (in6_addr->s6_addr[1] >> 4) & 0xf, + in6_addr->s6_addr[0] & 0xf, + (in6_addr->s6_addr[0] >> 4) & 0xf); + break; + default: + return (-1); + } + return (0); +} diff --git a/openbsd-compat/libasr/getaddrinfo.c b/openbsd-compat/libasr/getaddrinfo.c new file mode 100644 index 00000000..37fe2d4d --- /dev/null +++ b/openbsd-compat/libasr/getaddrinfo.c @@ -0,0 +1,55 @@ +/* $OpenBSD: getaddrinfo.c,v 1.9 2015/10/08 14:08:44 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> + +int +getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct asr_query *as; + struct asr_result ar; + int saved_errno = errno; + + if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0) + res_init(); + + as = getaddrinfo_async(hostname, servname, hints, NULL); + if (as == NULL) { + if (errno == ENOMEM) { + errno = saved_errno; + return (EAI_MEMORY); + } + return (EAI_SYSTEM); + } + + asr_run_sync(as, &ar); + + *res = ar.ar_addrinfo; + if (ar.ar_gai_errno == EAI_SYSTEM) + errno = ar.ar_errno; + + return (ar.ar_gai_errno); +} +DEF_WEAK(getaddrinfo); diff --git a/openbsd-compat/libasr/getaddrinfo_async.c b/openbsd-compat/libasr/getaddrinfo_async.c new file mode 100644 index 00000000..1fd44ff7 --- /dev/null +++ b/openbsd-compat/libasr/getaddrinfo_async.c @@ -0,0 +1,756 @@ +/* $OpenBSD: getaddrinfo_async.c,v 1.56 2018/11/03 09:13:24 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <net/if.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <ifaddrs.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +struct match { + int family; + int socktype; + int protocol; +}; + +static int getaddrinfo_async_run(struct asr_query *, struct asr_result *); +static int get_port(const char *, const char *, int); +static int iter_family(struct asr_query *, int); +static int addrinfo_add(struct asr_query *, const struct sockaddr *, const char *); +static int addrinfo_from_file(struct asr_query *, int, FILE *); +static int addrinfo_from_pkt(struct asr_query *, char *, size_t); +static int addrconfig_setup(struct asr_query *); + +static const struct match matches[] = { + { PF_INET, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET, SOCK_RAW, 0 }, + { PF_INET6, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET6, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET6, SOCK_RAW, 0 }, + { -1, 0, 0, }, +}; + +#define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC) +#define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0 || matches[(b)].protocol == 0) +/* Do not match SOCK_RAW unless explicitly specified */ +#define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \ + matches[(b)].socktype != SOCK_RAW)) + +enum { + DOM_INIT, + DOM_DOMAIN, + DOM_DONE +}; + +struct asr_query * +getaddrinfo_async(const char *hostname, const char *servname, + const struct addrinfo *hints, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0) + ac = _asr_use_resolver(asr); + else + ac = _asr_no_resolver(); + if ((as = _asr_async_new(ac, ASR_GETADDRINFO)) == NULL) + goto abort; /* errno set */ + as->as_run = getaddrinfo_async_run; + + if (hostname) { + if ((as->as.ai.hostname = strdup(hostname)) == NULL) + goto abort; /* errno set */ + } + if (servname && (as->as.ai.servname = strdup(servname)) == NULL) + goto abort; /* errno set */ + if (hints) + memmove(&as->as.ai.hints, hints, sizeof *hints); + else { + memset(&as->as.ai.hints, 0, sizeof as->as.ai.hints); + as->as.ai.hints.ai_family = PF_UNSPEC; + as->as.ai.hints.ai_flags = AI_ADDRCONFIG; + } + + _asr_ctx_unref(ac); + return (as); + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getaddrinfo_async); + +static int +getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar) +{ + char fqdn[MAXDNAME]; + const char *str; + struct addrinfo *ai; + int i, family, r; + FILE *f; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* + * First, make sure the parameters are valid. + */ + + as->as_count = 0; + + if (as->as.ai.hostname == NULL && + as->as.ai.servname == NULL) { + ar->ar_gai_errno = EAI_NONAME; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ai.hostname && as->as.ai.hostname[0] == '\0') { + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + ai = &as->as.ai.hints; + +#ifdef EAI_BADHINTS + if (ai->ai_addrlen || + ai->ai_canonname || + ai->ai_addr || + ai->ai_next) { + ar->ar_gai_errno = EAI_BADHINTS; + async_set_state(as, ASR_STATE_HALT); + break; + } +#endif + + if (ai->ai_flags & ~AI_MASK || + (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) { + ar->ar_gai_errno = EAI_BADFLAGS; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_family != PF_UNSPEC && + ai->ai_family != PF_INET && + ai->ai_family != PF_INET6) { + ar->ar_gai_errno = EAI_FAMILY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_socktype && + ai->ai_socktype != SOCK_DGRAM && + ai->ai_socktype != SOCK_STREAM && + ai->ai_socktype != SOCK_RAW) { + ar->ar_gai_errno = EAI_SOCKTYPE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_socktype == SOCK_RAW && + get_port(as->as.ai.servname, NULL, 1) != 0) { + ar->ar_gai_errno = EAI_SERVICE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Restrict result set to configured address families */ + if (ai->ai_flags & AI_ADDRCONFIG) { + if (addrconfig_setup(as) == -1) { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + + /* Make sure there is at least a valid combination */ + for (i = 0; matches[i].family != -1; i++) + if (MATCH_FAMILY(ai->ai_family, i) && + MATCH_SOCKTYPE(ai->ai_socktype, i) && + MATCH_PROTO(ai->ai_protocol, i)) + break; + if (matches[i].family == -1) { +#ifdef EAI_BADHINTS + ar->ar_gai_errno = EAI_BADHINTS; +#else + ar->ar_gai_errno = EAI_FAIL; +#endif + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_UDP) + as->as.ai.port_udp = get_port(as->as.ai.servname, "udp", + as->as.ai.hints.ai_flags & AI_NUMERICSERV); + if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_TCP) + as->as.ai.port_tcp = get_port(as->as.ai.servname, "tcp", + as->as.ai.hints.ai_flags & AI_NUMERICSERV); + if (as->as.ai.port_tcp == -2 || as->as.ai.port_udp == -2 || + (as->as.ai.port_tcp == -1 && as->as.ai.port_udp == -1) || + (ai->ai_protocol && (as->as.ai.port_udp == -1 || + as->as.ai.port_tcp == -1))) { + ar->ar_gai_errno = EAI_SERVICE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + ar->ar_gai_errno = 0; + + /* If hostname is NULL, use local address */ + if (as->as.ai.hostname == NULL) { + for (family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + /* + * We could use statically built sockaddrs for + * those, rather than parsing over and over. + */ + if (family == PF_INET) + str = (ai->ai_flags & AI_PASSIVE) ? \ + "0.0.0.0" : "127.0.0.1"; + else /* PF_INET6 */ + str = (ai->ai_flags & AI_PASSIVE) ? \ + "::" : "::1"; + /* This can't fail */ + _asr_sockaddr_from_str(&sa.sa, family, str); + if ((r = addrinfo_add(as, &sa.sa, NULL))) { + ar->ar_gai_errno = r; + break; + } + } + if (ar->ar_gai_errno == 0 && as->as_count == 0) { + ar->ar_gai_errno = EAI_NODATA; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Try numeric addresses first */ + for (family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + + if (_asr_sockaddr_from_str(&sa.sa, family, + as->as.ai.hostname) == -1) + continue; + + if ((r = addrinfo_add(as, &sa.sa, NULL))) + ar->ar_gai_errno = r; + break; + } + if (ar->ar_gai_errno || as->as_count) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_flags & AI_NUMERICHOST) { + ar->ar_gai_errno = EAI_NONAME; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DB: + if (_asr_iter_db(as) == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + as->as_family_idx = 0; + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_NEXT_FAMILY: + as->as_family_idx += 1; + if (as->as.ai.hints.ai_family != AF_UNSPEC || + AS_FAMILY(as) == -1) { + /* The family was specified, or we have tried all + * families with this DB. + */ + if (as->as_count) { + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + } + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_NEXT_DOMAIN: + /* domain search is only for dns */ + if (AS_DB(as) != ASR_DB_DNS) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + as->as_family_idx = 0; + + free(as->as.ai.fqdn); + as->as.ai.fqdn = NULL; + r = _asr_iter_domain(as, as->as.ai.hostname, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + if (r == 0) { + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + as->as.ai.fqdn = strdup(fqdn); + if (as->as.ai.fqdn == NULL) { + ar->ar_gai_errno = EAI_MEMORY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_SAME_DB: + /* query the current DB again */ + switch (AS_DB(as)) { + case ASR_DB_DNS: + if (as->as.ai.fqdn == NULL) { + /* First try, initialize domain iteration */ + as->as_dom_flags = 0; + as->as_dom_step = DOM_INIT; + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + } + + family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.ai.hints.ai_family; + + if (family == AF_INET && + as->as_flags & ASYNC_NO_INET) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } else if (family == AF_INET6 && + as->as_flags & ASYNC_NO_INET6) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + as->as_subq = _res_query_async_ctx(as->as.ai.fqdn, + C_IN, (family == AF_INET6) ? T_AAAA : T_A, + as->as_ctx); + + if (as->as_subq == NULL) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + f = fopen(_PATH_HOSTS, "re"); + if (f == NULL) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.ai.hints.ai_family; + + r = addrinfo_from_file(as, family, f); + if (r == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_FAMILY); + fclose(f); + break; + + default: + async_set_state(as, ASR_STATE_NEXT_DB); + } + break; + + case ASR_STATE_SUBQUERY: + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + as->as_subq = NULL; + + if (ar->ar_datalen == -1) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + r = addrinfo_from_pkt(as, ar->ar_data, ar->ar_datalen); + if (r == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_FAMILY); + free(ar->ar_data); + break; + + case ASR_STATE_NOT_FOUND: + /* No result found. Maybe we can try again. */ + if (as->as_flags & ASYNC_AGAIN) + ar->ar_gai_errno = EAI_AGAIN; + else + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_gai_errno == 0) { + ar->ar_count = as->as_count; + ar->ar_addrinfo = as->as.ai.aifirst; + as->as.ai.aifirst = NULL; + } else { + ar->ar_count = 0; + ar->ar_addrinfo = NULL; + } + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Retreive the port number for the service name "servname" and + * the protocol "proto". + */ +static int +get_port(const char *servname, const char *proto, int numonly) +{ +#ifdef HAVE_GETSERVBYNAME_R_4_ARGS + struct servent se; +#endif +#ifdef HAVE_STRUCT_SERVENT_DATA + struct servent_data sed; +#endif + int port; + const char *e; + + if (servname == NULL) + return (0); + + e = NULL; + port = strtonum(servname, 0, USHRT_MAX, &e); + if (e == NULL) + return (port); + if (errno == ERANGE) + return (-2); /* invalid */ + if (numonly) + return (-2); + + port = -1; +#ifdef HAVE_STRUCT_SERVENT_DATA + memset(&sed, 0, sizeof(sed)); +#endif +#ifdef HAVE_GETSERVBYNAME_R_4_ARGS + if (getservbyname_r(servname, proto, &se, &sed) != -1) + port = ntohs(se.s_port); +#endif +#ifdef HAVE_ENDSERVENT_R + endservent_r(&sed); +#endif + + return (port); +} + +/* + * Iterate over the address families that are to be queried. Use the + * list on the async context, unless a specific family was given in hints. + */ +static int +iter_family(struct asr_query *as, int first) +{ + if (first) { + as->as_family_idx = 0; + if (as->as.ai.hints.ai_family != PF_UNSPEC) + return as->as.ai.hints.ai_family; + return AS_FAMILY(as); + } + + if (as->as.ai.hints.ai_family != PF_UNSPEC) + return (-1); + + as->as_family_idx++; + + return AS_FAMILY(as); +} + +/* + * Use the sockaddr at "sa" to extend the result list on the "as" context, + * with the specified canonical name "cname". This function adds one + * entry per protocol/socktype match. + */ +static int +addrinfo_add(struct asr_query *as, const struct sockaddr *sa, const char *cname) +{ + struct addrinfo *ai; + int i, port, proto; + + for (i = 0; matches[i].family != -1; i++) { + if (matches[i].family != sa->sa_family || + !MATCH_SOCKTYPE(as->as.ai.hints.ai_socktype, i) || + !MATCH_PROTO(as->as.ai.hints.ai_protocol, i)) + continue; + + proto = as->as.ai.hints.ai_protocol; + if (!proto) + proto = matches[i].protocol; + + if (proto == IPPROTO_TCP) + port = as->as.ai.port_tcp; + else if (proto == IPPROTO_UDP) + port = as->as.ai.port_udp; + else + port = 0; + + /* servname specified, but not defined for this protocol */ + if (port == -1) + continue; + + ai = calloc(1, sizeof(*ai) + SA_LEN(sa)); + if (ai == NULL) + return (EAI_MEMORY); + ai->ai_family = sa->sa_family; + ai->ai_socktype = matches[i].socktype; + ai->ai_protocol = proto; + ai->ai_flags = as->as.ai.hints.ai_flags; + ai->ai_addrlen = SA_LEN(sa); + ai->ai_addr = (void *)(ai + 1); + if (cname && + as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) { + if ((ai->ai_canonname = strdup(cname)) == NULL) { + free(ai); + return (EAI_MEMORY); + } + } + memmove(ai->ai_addr, sa, SA_LEN(sa)); + if (sa->sa_family == PF_INET) + ((struct sockaddr_in *)ai->ai_addr)->sin_port = + htons(port); + else if (sa->sa_family == PF_INET6) + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = + htons(port); + + if (as->as.ai.aifirst == NULL) + as->as.ai.aifirst = ai; + if (as->as.ai.ailast) + as->as.ai.ailast->ai_next = ai; + as->as.ai.ailast = ai; + as->as_count += 1; + } + + return (0); +} + +void +asr_freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *ai_next; + + while (ai) { + ai_next = ai->ai_next; + if (ai->ai_canonname) + free(ai->ai_canonname); + free(ai); + ai = ai_next; + } +} + +static int +addrinfo_from_file(struct asr_query *as, int family, FILE *f) +{ + char *tokens[MAXTOKEN], *c, buf[ASR_BUFSIZ + 1]; + int n, i; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } u; + + for (;;) { + n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf)); + if (n == -1) + break; /* ignore errors reading the file */ + + for (i = 1; i < n; i++) { + if (strcasecmp(as->as.ai.hostname, tokens[i])) + continue; + if (_asr_sockaddr_from_str(&u.sa, family, tokens[0]) == -1) + continue; + break; + } + if (i == n) + continue; + + if (as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) + c = tokens[1]; + else + c = NULL; + + if (addrinfo_add(as, &u.sa, c)) + return (-1); /* errno set */ + } + return (0); +} + +static int +addrinfo_from_pkt(struct asr_query *as, char *pkt, size_t pktlen) +{ + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int i; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } u; + char buf[MAXDNAME], *c; + + _asr_unpack_init(&p, pkt, pktlen); + _asr_unpack_header(&p, &h); + for (; h.qdcount; h.qdcount--) + _asr_unpack_query(&p, &q); + + for (i = 0; i < h.ancount; i++) { + _asr_unpack_rr(&p, &rr); + if (rr.rr_type != q.q_type || + rr.rr_class != q.q_class) + continue; + + memset(&u, 0, sizeof u); + if (rr.rr_type == T_A) { +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + u.sain.sin_len = sizeof u.sain; +#endif + u.sain.sin_family = AF_INET; + u.sain.sin_addr = rr.rr.in_a.addr; + u.sain.sin_port = 0; + } else if (rr.rr_type == T_AAAA) { +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + u.sain6.sin6_len = sizeof u.sain6; +#endif + u.sain6.sin6_family = AF_INET6; + u.sain6.sin6_addr = rr.rr.in_aaaa.addr6; + u.sain6.sin6_port = 0; + } else + continue; + + if (as->as.ai.hints.ai_flags & AI_CANONNAME) { + _asr_strdname(rr.rr_dname, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + c = res_hnok(buf) ? buf : NULL; + } else if (as->as.ai.hints.ai_flags & AI_FQDN) + c = as->as.ai.fqdn; + else + c = NULL; + + if (addrinfo_add(as, &u.sa, c)) + return (-1); /* errno set */ + } + return (0); +} + +static int +addrconfig_setup(struct asr_query *as) +{ + struct ifaddrs *ifa, *ifa0; + struct sockaddr_in *sinp; + struct sockaddr_in6 *sin6p; + + if (getifaddrs(&ifa0) == -1) + return (-1); + + as->as_flags |= ASYNC_NO_INET | ASYNC_NO_INET6; + + for (ifa = ifa0; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + + switch (ifa->ifa_addr->sa_family) { + case PF_INET: + sinp = (struct sockaddr_in *)ifa->ifa_addr; + + if (sinp->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + continue; + + as->as_flags &= ~ASYNC_NO_INET; + break; + case PF_INET6: + sin6p = (struct sockaddr_in6 *)ifa->ifa_addr; + + if (IN6_IS_ADDR_LOOPBACK(&sin6p->sin6_addr)) + continue; + + if (IN6_IS_ADDR_LINKLOCAL(&sin6p->sin6_addr)) + continue; + + as->as_flags &= ~ASYNC_NO_INET6; + break; + } + } + + freeifaddrs(ifa0); + + return (0); +} diff --git a/openbsd-compat/libasr/gethostnamadr.c b/openbsd-compat/libasr/gethostnamadr.c new file mode 100644 index 00000000..2fce46b3 --- /dev/null +++ b/openbsd-compat/libasr/gethostnamadr.c @@ -0,0 +1,200 @@ +/* $OpenBSD: gethostnamadr.c,v 1.13 2015/09/14 07:38:37 guenther Exp $ */ +/* + * Copyright (c) 2012,2013 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/param.h> /* ALIGN */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +static int _gethostbyname(const char *, int, struct hostent *, char *, size_t, + int *); +static int _fillhostent(const struct hostent *, struct hostent *, char *, + size_t); + +static struct hostent _hostent; +static char _entbuf[4096]; + +static char *_empty[] = { NULL, }; + +static int +_fillhostent(const struct hostent *h, struct hostent *r, char *buf, size_t len) +{ + char **ptr, *end, *pos; + size_t n, i; + int naliases, naddrs; + + bzero(buf, len); + bzero(r, sizeof(*r)); + r->h_aliases = _empty; + r->h_addr_list = _empty; + + end = buf + len; + ptr = (char **)ALIGN(buf); + + if ((char *)ptr >= end) + return (ERANGE); + + for (naliases = 0; h->h_aliases[naliases]; naliases++) + ; + for (naddrs = 0; h->h_addr_list[naddrs]; naddrs++) + ; + + pos = (char *)(ptr + (naliases + 1) + (naddrs + 1)); + if (pos >= end) + return (ERANGE); + + r->h_name = NULL; + r->h_addrtype = h->h_addrtype; + r->h_length = h->h_length; + r->h_aliases = ptr; + r->h_addr_list = ptr + naliases + 1; + + n = strlcpy(pos, h->h_name, end - pos); + if (n >= end - pos) + return (ERANGE); + r->h_name = pos; + pos += n + 1; + + for (i = 0; i < naliases; i++) { + n = strlcpy(pos, h->h_aliases[i], end - pos); + if (n >= end - pos) + return (ERANGE); + r->h_aliases[i] = pos; + pos += n + 1; + } + + pos = (char *)ALIGN(pos); + if (pos >= end) + return (ERANGE); + + for (i = 0; i < naddrs; i++) { + if (r->h_length > end - pos) + return (ERANGE); + memmove(pos, h->h_addr_list[i], r->h_length); + r->h_addr_list[i] = pos; + pos += r->h_length; + } + + return (0); +} + +static int +_gethostbyname(const char *name, int af, struct hostent *ret, char *buf, + size_t buflen, int *h_errnop) +{ + struct asr_query *as; + struct asr_result ar; + int r; + + if (af == -1) + as = gethostbyname_async(name, NULL); + else + as = gethostbyname2_async(name, af, NULL); + + if (as == NULL) + return (errno); + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + *h_errnop = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (0); + + r = _fillhostent(ar.ar_hostent, ret, buf, buflen); + free(ar.ar_hostent); + + return (r); +} + +struct hostent * +gethostbyname(const char *name) +{ + struct hostent *h; + + res_init(); + + if (_res.options & RES_USE_INET6 && + (h = gethostbyname2(name, AF_INET6))) + return (h); + + return gethostbyname2(name, AF_INET); +} +DEF_WEAK(gethostbyname); + +struct hostent * +gethostbyname2(const char *name, int af) +{ + int r; + + res_init(); + + r = _gethostbyname(name, af, &_hostent, _entbuf, sizeof(_entbuf), + &h_errno); + if (r) { + h_errno = NETDB_INTERNAL; + errno = r; + } + + if (h_errno) + return (NULL); + + return (&_hostent); +} +DEF_WEAK(gethostbyname2); + +struct hostent * +gethostbyaddr(const void *addr, socklen_t len, int af) +{ + struct asr_query *as; + struct asr_result ar; + int r; + + res_init(); + + as = gethostbyaddr_async(addr, len, af, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (NULL); + + r = _fillhostent(ar.ar_hostent, &_hostent, _entbuf, sizeof(_entbuf)); + free(ar.ar_hostent); + + if (r) { + h_errno = NETDB_INTERNAL; + errno = r; + return (NULL); + } + + return (&_hostent); +} diff --git a/openbsd-compat/libasr/gethostnamadr_async.c b/openbsd-compat/libasr/gethostnamadr_async.c new file mode 100644 index 00000000..2636e848 --- /dev/null +++ b/openbsd-compat/libasr/gethostnamadr_async.c @@ -0,0 +1,676 @@ +/* $OpenBSD: gethostnamadr_async.c,v 1.45 2019/06/27 05:26:37 martijn Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <resolv.h> /* for res_hnok */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +#define MAXALIASES 35 +#define MAXADDRS 35 + +struct hostent_ext { + struct hostent h; + char *aliases[MAXALIASES + 1]; + char *addrs[MAXADDRS + 1]; + char *end; + char *pos; +}; + +struct netent_ext { + struct netent n; + char *aliases[MAXALIASES + 1]; + char *end; + char *pos; +}; + +static int gethostnamadr_async_run(struct asr_query *, struct asr_result *); +static struct hostent_ext *hostent_alloc(int); +static int hostent_set_cname(struct hostent_ext *, const char *, int); +static int hostent_add_alias(struct hostent_ext *, const char *, int); +static int hostent_add_addr(struct hostent_ext *, const void *, size_t); +static struct hostent_ext *hostent_from_addr(int, const char *, const char *); +static struct hostent_ext *hostent_file_match(FILE *, int, int, const char *, + int); +static struct hostent_ext *hostent_from_packet(int, int, char *, size_t); +static void netent_from_hostent(struct asr_result *ar); + +struct asr_query * +gethostbyname_async(const char *name, void *asr) +{ + return gethostbyname2_async(name, AF_INET, asr); +} +DEF_WEAK(gethostbyname_async); + +struct asr_query * +gethostbyname2_async(const char *name, int af, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + /* the original segfaults */ + if (name == NULL) { + errno = EINVAL; + return (NULL); + } + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETHOSTBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = gethostnamadr_async_run; + + as->as.hostnamadr.family = af; + if (af == AF_INET) + as->as.hostnamadr.addrlen = INADDRSZ; + else if (af == AF_INET6) + as->as.hostnamadr.addrlen = IN6ADDRSZ; + as->as.hostnamadr.name = strdup(name); + if (as->as.hostnamadr.name == NULL) + goto abort; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(gethostbyname2_async); + +struct asr_query * +gethostbyaddr_async(const void *addr, socklen_t len, int af, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + as = _gethostbyaddr_async_ctx(addr, len, af, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(gethostbyaddr_async); + +struct asr_query * +_gethostbyaddr_async_ctx(const void *addr, socklen_t len, int af, + struct asr_ctx *ac) +{ + struct asr_query *as; + + if ((as = _asr_async_new(ac, ASR_GETHOSTBYADDR)) == NULL) + goto abort; /* errno set */ + as->as_run = gethostnamadr_async_run; + + as->as.hostnamadr.family = af; + as->as.hostnamadr.addrlen = len; + if (len > 0) + memmove(as->as.hostnamadr.addr, addr, (len > 16) ? 16 : len); + + return (as); + + abort: + if (as) + _asr_async_free(as); + return (NULL); +} + +static int +gethostnamadr_async_run(struct asr_query *as, struct asr_result *ar) +{ + struct hostent_ext *h; + int r, type, saved_errno; + FILE *f; + char name[MAXDNAME], *data, addr[16], *c; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.hostnamadr.family != AF_INET && + as->as.hostnamadr.family != AF_INET6) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EAFNOSUPPORT; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if ((as->as.hostnamadr.family == AF_INET && + as->as.hostnamadr.addrlen != INADDRSZ) || + (as->as.hostnamadr.family == AF_INET6 && + as->as.hostnamadr.addrlen != IN6ADDRSZ)) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EINVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYNAME) { + + if (as->as.hostnamadr.name[0] == '\0') { + ar->ar_h_errno = NO_DATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Name might be an IP address string */ + for (c = as->as.hostnamadr.name; *c; c++) + if (!isdigit((unsigned char)*c) && + *c != '.' && *c != ':') + break; + if (*c == 0 && + inet_pton(as->as.hostnamadr.family, + as->as.hostnamadr.name, addr) == 1) { + h = hostent_from_addr(as->as.hostnamadr.family, + as->as.hostnamadr.name, addr); + if (h == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + } + else { + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + } + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DB: + + if (_asr_iter_db(as) == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + + switch (AS_DB(as)) { + + case ASR_DB_DNS: + + /* Create a subquery to do the DNS lookup */ + + if (as->as_type == ASR_GETHOSTBYNAME) { + type = (as->as.hostnamadr.family == AF_INET) ? + T_A : T_AAAA; + as->as_subq = _res_search_async_ctx( + as->as.hostnamadr.name, + C_IN, type, as->as_ctx); + } else { + _asr_addr_as_fqdn(as->as.hostnamadr.addr, + as->as.hostnamadr.family, + name, sizeof(name)); + as->as_subq = _res_query_async_ctx( + name, C_IN, T_PTR, as->as_ctx); + } + + if (as->as_subq == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + + /* Try to find a match in the host file */ + + if ((f = fopen(_PATH_HOSTS, "re")) == NULL) + break; + + if (as->as_type == ASR_GETHOSTBYNAME) + data = as->as.hostnamadr.name; + else + data = as->as.hostnamadr.addr; + + h = hostent_file_match(f, as->as_type, + as->as.hostnamadr.family, data, + as->as.hostnamadr.addrlen); + saved_errno = errno; + fclose(f); + errno = saved_errno; + + if (h == NULL) { + if (errno) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + } + /* otherwise not found */ + break; + } + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + async_set_state(as, ASR_STATE_HALT); + break; + } + break; + + case ASR_STATE_SUBQUERY: + + /* Run the DNS subquery. */ + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* Done. */ + as->as_subq = NULL; + + /* + * We either got no packet or a packet without an answer. + * Saveguard the h_errno and use the next DB. + */ + if (ar->ar_count == 0) { + free(ar->ar_data); + as->as.hostnamadr.subq_h_errno = ar->ar_h_errno; + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + /* Read the hostent from the packet. */ + + h = hostent_from_packet(as->as_type, + as->as.hostnamadr.family, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + if (h == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYADDR) { + if (hostent_add_addr(h, as->as.hostnamadr.addr, + as->as.hostnamadr.addrlen) == -1) { + free(h); + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + + /* + * No valid hostname or address found in the dns packet. + * Ignore it. + */ + if ((as->as_type == ASR_GETHOSTBYNAME && + h->h.h_addr_list[0] == NULL) || + h->h.h_name == NULL) { + free(h); + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_NOT_FOUND: + ar->ar_errno = 0; + if (as->as.hostnamadr.subq_h_errno) + ar->ar_h_errno = as->as.hostnamadr.subq_h_errno; + else + ar->ar_h_errno = HOST_NOT_FOUND; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_h_errno == NETDB_SUCCESS && + as->as_flags & ASYNC_GETNET) + netent_from_hostent(ar); + if (ar->ar_h_errno) { + ar->ar_hostent = NULL; + ar->ar_netent = NULL; + } else + ar->ar_errno = 0; + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Create a hostent from a numeric address string. + */ +static struct hostent_ext * +hostent_from_addr(int family, const char *name, const char *addr) +{ + struct hostent_ext *h; + + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + if (hostent_set_cname(h, name, 0) == -1) + goto fail; + if (hostent_add_addr(h, addr, h->h.h_length) == -1) + goto fail; + return (h); +fail: + free(h); + return (NULL); +} + +/* + * Lookup the first matching entry in the hostfile, either by address or by + * name depending on reqtype, and build a hostent from the line. + */ +static struct hostent_ext * +hostent_file_match(FILE *f, int reqtype, int family, const char *data, + int datalen) +{ + char *tokens[MAXTOKEN], addr[16], buf[ASR_BUFSIZ + 1]; + struct hostent_ext *h; + int n, i; + + for (;;) { + n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf)); + if (n == -1) { + errno = 0; /* ignore errors reading the file */ + return (NULL); + } + + /* there must be an address and at least one name */ + if (n < 2) + continue; + + if (reqtype == ASR_GETHOSTBYNAME) { + for (i = 1; i < n; i++) { + if (strcasecmp(data, tokens[i])) + continue; + if (inet_pton(family, tokens[0], addr) == 1) + goto found; + } + } else { + if (inet_pton(family, tokens[0], addr) == 1 && + memcmp(addr, data, datalen) == 0) + goto found; + } + } + +found: + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + if (hostent_set_cname(h, tokens[1], 0) == -1) + goto fail; + for (i = 2; i < n; i ++) + if (hostent_add_alias(h, tokens[i], 0) == -1) + goto fail; + if (hostent_add_addr(h, addr, h->h.h_length) == -1) + goto fail; + return (h); +fail: + free(h); + return (NULL); +} + +/* + * Fill the hostent from the given DNS packet. + */ +static struct hostent_ext * +hostent_from_packet(int reqtype, int family, char *pkt, size_t pktlen) +{ + struct hostent_ext *h; + struct asr_unpack p; + struct asr_dns_header hdr; + struct asr_dns_query q; + struct asr_dns_rr rr; + char dname[MAXDNAME]; + + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + + _asr_unpack_init(&p, pkt, pktlen); + _asr_unpack_header(&p, &hdr); + for (; hdr.qdcount; hdr.qdcount--) + _asr_unpack_query(&p, &q); + strlcpy(dname, q.q_dname, sizeof(dname)); + + for (; hdr.ancount; hdr.ancount--) { + _asr_unpack_rr(&p, &rr); + if (rr.rr_class != C_IN) + continue; + switch (rr.rr_type) { + + case T_CNAME: + if (reqtype == ASR_GETHOSTBYNAME) { + if (hostent_add_alias(h, rr.rr_dname, 1) == -1) + goto fail; + } else { + if (strcasecmp(rr.rr_dname, dname) == 0) + strlcpy(dname, rr.rr.cname.cname, + sizeof(dname)); + } + break; + + case T_PTR: + if (reqtype != ASR_GETHOSTBYADDR) + break; + if (strcasecmp(rr.rr_dname, dname) != 0) + continue; + if (hostent_set_cname(h, rr.rr.ptr.ptrname, 1) == -1) + hostent_add_alias(h, rr.rr.ptr.ptrname, 1); + break; + + case T_A: + if (reqtype != ASR_GETHOSTBYNAME) + break; + if (family != AF_INET) + break; + if (hostent_set_cname(h, rr.rr_dname, 1) == -1) + ; + if (hostent_add_addr(h, &rr.rr.in_a.addr, 4) == -1) + goto fail; + break; + + case T_AAAA: + if (reqtype != ASR_GETHOSTBYNAME) + break; + if (family != AF_INET6) + break; + if (hostent_set_cname(h, rr.rr_dname, 1) == -1) + ; + if (hostent_add_addr(h, &rr.rr.in_aaaa.addr6, 16) == -1) + goto fail; + break; + } + } + + return (h); +fail: + free(h); + return (NULL); +} + +static struct hostent_ext * +hostent_alloc(int family) +{ + struct hostent_ext *h; + size_t alloc; + + alloc = sizeof(*h) + 1024; + if ((h = calloc(1, alloc)) == NULL) + return (NULL); + + h->h.h_addrtype = family; + h->h.h_length = (family == AF_INET) ? 4 : 16; + h->h.h_aliases = h->aliases; + h->h.h_addr_list = h->addrs; + h->pos = (char *)(h) + sizeof(*h); + h->end = h->pos + 1024; + + return (h); +} + +static int +hostent_set_cname(struct hostent_ext *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t n; + + if (h->h.h_name) + return (-1); + + if (isdname) { + _asr_strdname(name, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + if (!res_hnok(buf)) + return (-1); + name = buf; + } + + n = strlen(name) + 1; + if (h->pos + n >= h->end) + return (-1); + + h->h.h_name = h->pos; + memmove(h->pos, name, n); + h->pos += n; + return (0); +} + +static int +hostent_add_alias(struct hostent_ext *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t i, n; + + for (i = 0; i < MAXALIASES; i++) + if (h->aliases[i] == NULL) + break; + if (i == MAXALIASES) + return (0); + + if (isdname) { + _asr_strdname(name, buf, sizeof buf); + buf[strlen(buf)-1] = '\0'; + if (!res_hnok(buf)) + return (-1); + name = buf; + } + + n = strlen(name) + 1; + if (h->pos + n >= h->end) + return (0); + + h->aliases[i] = h->pos; + memmove(h->pos, name, n); + h->pos += n; + return (0); +} + +static int +hostent_add_addr(struct hostent_ext *h, const void *addr, size_t size) +{ + int i; + + for (i = 0; i < MAXADDRS; i++) + if (h->addrs[i] == NULL) + break; + if (i == MAXADDRS) + return (0); + + if (h->pos + size >= h->end) + return (0); + + h->addrs[i] = h->pos; + memmove(h->pos, addr, size); + h->pos += size; + return (0); +} + +static void +netent_from_hostent(struct asr_result *ar) +{ + struct in_addr *addr; + struct netent_ext *n; + struct hostent_ext *h; + char **na, **ha; + size_t sz; + + /* Allocate and initialize the output. */ + if ((n = calloc(1, sizeof(*n) + 1024)) == NULL) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = errno; + goto out; + } + n->pos = (char *)(n) + sizeof(*n); + n->end = n->pos + 1024; + n->n.n_name = n->pos; + n->n.n_aliases = n->aliases; + + /* Copy the fixed-size data. */ + h = (struct hostent_ext *)ar->ar_hostent; + addr = (struct in_addr *)h->h.h_addr; + n->n.n_net = ntohl(addr->s_addr); + n->n.n_addrtype = h->h.h_addrtype; + + /* Copy the network name. */ + sz = strlen(h->h.h_name) + 1; + memcpy(n->pos, h->h.h_name, sz); + n->pos += sz; + + /* + * Copy the aliases. + * No overflow check is needed because we are merely copying + * a part of the data from a structure of the same size. + */ + na = n->aliases; + for (ha = h->aliases; *ha != NULL; ha++) { + sz = strlen(*ha) + 1; + memcpy(n->pos, *ha, sz); + *na++ = n->pos; + n->pos += sz; + } + *na = NULL; + + /* Handle the return values. */ + ar->ar_netent = &n->n; +out: + free(ar->ar_hostent); + ar->ar_hostent = NULL; +} diff --git a/openbsd-compat/libasr/getnameinfo.c b/openbsd-compat/libasr/getnameinfo.c new file mode 100644 index 00000000..7bee468d --- /dev/null +++ b/openbsd-compat/libasr/getnameinfo.c @@ -0,0 +1,205 @@ +/* $OpenBSD: getnameinfo.c,v 1.9 2019/07/03 03:24:03 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> + +static size_t asr_print_addr(const struct sockaddr *, char *, size_t); +static size_t asr_print_port(const struct sockaddr *, const char *, char *, size_t); + +#define SA_IN(sa) ((struct sockaddr_in*)(sa)) +#define SA_IN6(sa) ((struct sockaddr_in6*)(sa)) + +/* + * Print the textual representation (as given by inet_ntop(3)) of the address + * set in "sa". + * + * Return the total length of the string it tried to create or 0 if an error + * occured, in which case errno is set. On success, the constructed string + * is guaranteed to be NUL-terminated. Overflow must be detected by checking + * the returned size against buflen. + * + */ +static size_t +asr_print_addr(const struct sockaddr *sa, char *buf, size_t buflen) +{ + unsigned int ifidx; + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char scope[IF_NAMESIZE + 1], *ifname; + const void *addr; + size_t s; + + switch(sa->sa_family) { + case AF_INET: + addr = &SA_IN(sa)->sin_addr; + break; + case AF_INET6: + addr = &SA_IN6(sa)->sin6_addr; + break; + default: + errno = EINVAL; + return (0); + } + + if (inet_ntop(sa->sa_family, addr, tmp, sizeof(tmp)) == NULL) + return (0); /* errno set */ + + s = strlcpy(buf, tmp, buflen); + + if (sa->sa_family == AF_INET6 && SA_IN6(sa)->sin6_scope_id) { + + scope[0] = SCOPE_DELIMITER; + scope[1] = '\0'; + + ifidx = SA_IN6(sa)->sin6_scope_id; + ifname = NULL; + + if (IN6_IS_ADDR_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) || + IN6_IS_ADDR_MC_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) || + IN6_IS_ADDR_MC_INTFACELOCAL(&(SA_IN6(sa)->sin6_addr))) + ifname = if_indextoname(ifidx, scope + 1); + + if (ifname == NULL) + (void)snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx); + + if (s < buflen) + (void)strlcat(buf, scope, buflen); + + s += strlen(scope); + } + + return (s); +} + +/* + * Print the textual representation of the port set on "sa". + * + * If proto is not NULL, it is used as parameter to "getservbyport_r(3)" to + * return a service name. If it's not set, or if no matching service is found, + * it prints the portno. + * + * Return the total length of the string it tried to create or 0 if an error + * occured, in which case errno is set. On success, the constructed string + * is guaranteed to be NUL-terminated. Overflow must be detected by checking + * the returned size against buflen. + */ +static size_t +asr_print_port(const struct sockaddr *sa, const char *proto, char *buf, size_t buflen) +{ + struct servent s; + struct servent_data sd; + int port, r, saved_errno; + size_t n; + + switch(sa->sa_family) { + case AF_INET: + port = SA_IN(sa)->sin_port; + break; + case AF_INET6: + port = SA_IN6(sa)->sin6_port; + break; + default: + errno = EINVAL; + return (0); + } + + if (proto) { + memset(&sd, 0, sizeof (sd)); + saved_errno = errno; + if (getservbyport_r(port, proto, &s, &sd) != -1) { + n = strlcpy(buf, s.s_name, buflen); + endservent_r(&sd); + return (n); + } + errno = saved_errno; + } + + r = snprintf(buf, buflen, "%u", ntohs(port)); + if (r < 0 || r >= buflen) /* Actually, this can not happen */ + return (0); + + return (r); +} + +int +getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct asr_query *as; + struct asr_result ar; + int saved_errno = errno; + const char *proto; + size_t r; + + /* + * Take a shortcut if we don't care about hostname, + * or if NI_NUMERICHOST is set. + */ + if (host == NULL || hostlen == 0 || + (host && hostlen && (flags & NI_NUMERICHOST))) { + if (host) { + r = asr_print_addr(sa, host, hostlen); + if (r == 0) + return (EAI_SYSTEM); /* errno set */ + if (r >= hostlen) + return (EAI_OVERFLOW); + } + + if (serv && servlen) { + if (flags & NI_NUMERICSERV) + proto = NULL; + else + proto = (flags & NI_DGRAM) ? "udp" : "tcp"; + r = asr_print_port(sa, proto, serv, servlen); + if (r == 0) + return (EAI_SYSTEM); /* errno set */ + if (r >= servlen) + return (EAI_OVERFLOW); + } + + errno = saved_errno; + return (0); + } + + res_init(); + + as = getnameinfo_async(sa, salen, host, hostlen, serv, servlen, flags, + NULL); + if (as == NULL) { + if (errno == ENOMEM) { + errno = saved_errno; + return (EAI_MEMORY); + } + return (EAI_SYSTEM); + } + + asr_run_sync(as, &ar); + if (ar.ar_gai_errno == EAI_SYSTEM) + errno = ar.ar_errno; + + return (ar.ar_gai_errno); +} +DEF_WEAK(getnameinfo); diff --git a/openbsd-compat/libasr/getnameinfo_async.c b/openbsd-compat/libasr/getnameinfo_async.c new file mode 100644 index 00000000..faba8860 --- /dev/null +++ b/openbsd-compat/libasr/getnameinfo_async.c @@ -0,0 +1,300 @@ +/* $OpenBSD: getnameinfo_async.c,v 1.14 2019/07/03 03:24:03 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int getnameinfo_async_run(struct asr_query *, struct asr_result *); +static int _servname(struct asr_query *); +static int _numerichost(struct asr_query *); + +struct asr_query * +getnameinfo_async(const struct sockaddr *sa, socklen_t slen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETNAMEINFO)) == NULL) + goto abort; /* errno set */ + as->as_run = getnameinfo_async_run; + + if (sa->sa_family == AF_INET) + memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain)); + else if (sa->sa_family == AF_INET6) + memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain6)); + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + as->as.ni.sa.sa.sa_len = slen; +#endif + as->as.ni.hostname = host; + as->as.ni.hostnamelen = hostlen; + as->as.ni.servname = serv; + as->as.ni.servnamelen = servlen; + as->as.ni.flags = flags; + + _asr_ctx_unref(ac); + return (as); + + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getnameinfo_async); + +static int +getnameinfo_async_run(struct asr_query *as, struct asr_result *ar) +{ + void *addr; + socklen_t addrlen; + int r; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* Make sure the parameters are all valid. */ + + if (as->as.ni.sa.sa.sa_family != AF_INET && + as->as.ni.sa.sa.sa_family != AF_INET6) { + ar->ar_gai_errno = EAI_FAMILY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if ((as->as.ni.sa.sa.sa_family == AF_INET && + (SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain))) || + (as->as.ni.sa.sa.sa_family == AF_INET6 && + (SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain6)))) { + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Set the service name first, if needed. */ + if (_servname(as) == -1) { + ar->ar_gai_errno = EAI_OVERFLOW; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ni.hostname == NULL || as->as.ni.hostnamelen == 0) { + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ni.flags & NI_NUMERICHOST) { + if (_numerichost(as) == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else if (errno == ENOSPC) + ar->ar_gai_errno = EAI_OVERFLOW; + else { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + } + } else + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ni.sa.sa.sa_family == AF_INET) { + addrlen = sizeof(as->as.ni.sa.sain.sin_addr); + addr = &as->as.ni.sa.sain.sin_addr; + } else { + addrlen = sizeof(as->as.ni.sa.sain6.sin6_addr); + addr = &as->as.ni.sa.sain6.sin6_addr; + } + + /* + * Create a subquery to lookup the address. + */ + as->as_subq = _gethostbyaddr_async_ctx(addr, addrlen, + as->as.ni.sa.sa.sa_family, + as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_gai_errno = EAI_MEMORY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* + * Request done. + */ + as->as_subq = NULL; + + if (ar->ar_hostent == NULL) { + if (as->as.ni.flags & NI_NAMEREQD) { + ar->ar_gai_errno = EAI_NONAME; + } else if (_numerichost(as) == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else if (errno == ENOSPC) + ar->ar_gai_errno = EAI_OVERFLOW; + else { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + } + } else + ar->ar_gai_errno = 0; + } else { + if (strlcpy(as->as.ni.hostname, + ar->ar_hostent->h_name, + as->as.ni.hostnamelen) >= as->as.ni.hostnamelen) + ar->ar_gai_errno = EAI_OVERFLOW; + else + ar->ar_gai_errno = 0; + free(ar->ar_hostent); + } + + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + + +/* + * Set the service name on the result buffer is not NULL. + * return (-1) if the buffer is too small. + */ +static int +_servname(struct asr_query *as) +{ + struct servent s; +#ifdef HAVE_STRUCT_SERVENT_DATA + struct servent_data sd; +#endif + int port, r; + char *buf = as->as.ni.servname; + size_t buflen = as->as.ni.servnamelen; + + if (as->as.ni.servname == NULL || as->as.ni.servnamelen == 0) + return (0); + + if (as->as.ni.sa.sa.sa_family == AF_INET) + port = as->as.ni.sa.sain.sin_port; + else + port = as->as.ni.sa.sain6.sin6_port; + + if (!(as->as.ni.flags & NI_NUMERICSERV)) { +#ifdef HAVE_STRUCT_SERVENT_DATA + memset(&sd, 0, sizeof (sd)); +#endif +#ifdef HAVE_GETSERVBYPORT_R_4_ARGS + r = getservbyport_r(port, + (as->as.ni.flags & NI_DGRAM) ? "udp" : "tcp", + &s, &sd); +#else + r = -1; +#endif + if (r != -1) { + r = strlcpy(buf, s.s_name, buflen) >= buflen; +#ifdef HAVE_ENDSERVENT_R + endservent_r(&sd); +#endif + return (r ? -1 : 0); + } + } + + r = snprintf(buf, buflen, "%u", ntohs(port)); + if (r < 0 || (size_t)r >= buflen) + return (-1); + + return (0); +} + +/* + * Write the numeric address + */ +static int +_numerichost(struct asr_query *as) +{ + unsigned int ifidx; + char scope[IF_NAMESIZE + 1], *ifname; + void *addr; + char *buf = as->as.ni.hostname; + size_t buflen = as->as.ni.hostnamelen; + + if (as->as.ni.sa.sa.sa_family == AF_INET) + addr = &as->as.ni.sa.sain.sin_addr; + else + addr = &as->as.ni.sa.sain6.sin6_addr; + + if (inet_ntop(as->as.ni.sa.sa.sa_family, addr, buf, buflen) == NULL) + return (-1); /* errno set */ + + if (as->as.ni.sa.sa.sa_family == AF_INET6 && + as->as.ni.sa.sain6.sin6_scope_id) { + + scope[0] = SCOPE_DELIMITER; + scope[1] = '\0'; + + ifidx = as->as.ni.sa.sain6.sin6_scope_id; + ifname = NULL; + + if (IN6_IS_ADDR_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) || + IN6_IS_ADDR_MC_NODELOCAL(&as->as.ni.sa.sain6.sin6_addr)) + ifname = if_indextoname(ifidx, scope + 1); + + if (ifname == NULL) + snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx); + + strlcat(buf, scope, buflen); + } + + return (0); +} diff --git a/openbsd-compat/libasr/getnetnamadr.c b/openbsd-compat/libasr/getnetnamadr.c new file mode 100644 index 00000000..141b4a9a --- /dev/null +++ b/openbsd-compat/libasr/getnetnamadr.c @@ -0,0 +1,134 @@ +/* $OpenBSD: getnetnamadr.c,v 1.9 2015/01/16 16:48:51 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/param.h> /* ALIGN */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> + +static void _fillnetent(const struct netent *, struct netent *, char *buf, + size_t); + +static struct netent _netent; +static char _entbuf[4096]; + +static char *_empty[] = { NULL, }; + +static void +_fillnetent(const struct netent *e, struct netent *r, char *buf, size_t len) +{ + char **ptr, *end, *pos; + size_t n, i; + int naliases; + + bzero(buf, len); + bzero(r, sizeof(*r)); + r->n_aliases = _empty; + + end = buf + len; + ptr = (char **)ALIGN(buf); + + if ((char *)ptr >= end) + return; + + for (naliases = 0; e->n_aliases[naliases]; naliases++) + ; + + r->n_name = NULL; + r->n_addrtype = e->n_addrtype; + r->n_net = e->n_net; + r->n_aliases = ptr; + + pos = (char *)(ptr + (naliases + 1)); + if (pos > end) + r->n_aliases = _empty; + + n = strlcpy(pos, e->n_name, end - pos); + if (n >= end - pos) + return; + r->n_name = pos; + pos += n + 1; + + for (i = 0; i < naliases; i++) { + n = strlcpy(pos, e->n_aliases[i], end - pos); + if (n >= end - pos) + return; + r->n_aliases[i] = pos; + pos += n + 1; + } +} + +struct netent * +getnetbyname(const char *name) +{ + struct asr_query *as; + struct asr_result ar; + + res_init(); + + as = getnetbyname_async(name, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + _fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf)); + free(ar.ar_netent); + + return (&_netent); +} + +struct netent * +getnetbyaddr(in_addr_t net, int type) +{ + struct asr_query *as; + struct asr_result ar; + + res_init(); + + as = getnetbyaddr_async(net, type, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + _fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf)); + free(ar.ar_netent); + + return (&_netent); +} diff --git a/openbsd-compat/libasr/getnetnamadr_async.c b/openbsd-compat/libasr/getnetnamadr_async.c new file mode 100644 index 00000000..1e02d008 --- /dev/null +++ b/openbsd-compat/libasr/getnetnamadr_async.c @@ -0,0 +1,52 @@ +/* $OpenBSD: getnetnamadr_async.c,v 1.26 2018/04/28 15:16:49 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> +#include <asr.h> + +#include "asr_private.h" + +struct asr_query * +getnetbyname_async(const char *name, void *asr) +{ + struct asr_query *as; + + if ((as = gethostbyname_async(name, asr)) != NULL) + as->as_flags |= ASYNC_GETNET; + return (as); +} +DEF_WEAK(getnetbyname_async); + +struct asr_query * +getnetbyaddr_async(in_addr_t net, int family, void *asr) +{ + struct in_addr in; + struct asr_query *as; + + in.s_addr = htonl(net); + as = gethostbyaddr_async(&in, sizeof(in), family, asr); + if (as != NULL) + as->as_flags |= ASYNC_GETNET; + return (as); +} +DEF_WEAK(getnetbyaddr_async); diff --git a/openbsd-compat/libasr/getrrsetbyname.c b/openbsd-compat/libasr/getrrsetbyname.c new file mode 100644 index 00000000..24df2c8b --- /dev/null +++ b/openbsd-compat/libasr/getrrsetbyname.c @@ -0,0 +1,83 @@ +/* $OpenBSD: getrrsetbyname.c,v 1.6 2015/09/14 07:38:37 guenther Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> + +int +getrrsetbyname(const char *name, unsigned int class, unsigned int type, + unsigned int flags, struct rrsetinfo **res) +{ + struct asr_query *as; + struct asr_result ar; + int r, saved_errno = errno; + + res_init(); + + as = getrrsetbyname_async(name, class, type, flags, NULL); + if (as == NULL) { + r = (errno == ENOMEM) ? ERRSET_NOMEMORY : ERRSET_FAIL; + errno = saved_errno; + return (r); + } + + asr_run_sync(as, &ar); + + *res = ar.ar_rrsetinfo; + + return (ar.ar_rrset_errno); +} + +/* from net/getrrsetbyname.c */ +void +freerrset(struct rrsetinfo *rrset) +{ + u_int16_t i; + + if (rrset == NULL) + return; + + if (rrset->rri_rdatas) { + for (i = 0; i < rrset->rri_nrdatas; i++) { + if (rrset->rri_rdatas[i].rdi_data == NULL) + break; + free(rrset->rri_rdatas[i].rdi_data); + } + free(rrset->rri_rdatas); + } + + if (rrset->rri_sigs) { + for (i = 0; i < rrset->rri_nsigs; i++) { + if (rrset->rri_sigs[i].rdi_data == NULL) + break; + free(rrset->rri_sigs[i].rdi_data); + } + free(rrset->rri_sigs); + } + + if (rrset->rri_name) + free(rrset->rri_name); + free(rrset); +} +DEF_WEAK(freerrset); diff --git a/openbsd-compat/libasr/getrrsetbyname_async.c b/openbsd-compat/libasr/getrrsetbyname_async.c new file mode 100644 index 00000000..9ff7f6c7 --- /dev/null +++ b/openbsd-compat/libasr/getrrsetbyname_async.c @@ -0,0 +1,590 @@ +/* $OpenBSD: getrrsetbyname_async.c,v 1.11 2017/02/23 17:04:02 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int getrrsetbyname_async_run(struct asr_query *, struct asr_result *); +static void get_response(struct asr_result *, const char *, int); + +struct asr_query * +getrrsetbyname_async(const char *hostname, unsigned int rdclass, + unsigned int rdtype, unsigned int flags, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETRRSETBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = getrrsetbyname_async_run; + + as->as.rrset.flags = flags; + as->as.rrset.class = rdclass; + as->as.rrset.type = rdtype; + as->as.rrset.name = strdup(hostname); + if (as->as.rrset.name == NULL) + goto abort; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + abort: + if (as) + _asr_async_free(as); + + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getrrsetbyname_async); + +static int +getrrsetbyname_async_run(struct asr_query *as, struct asr_result *ar) +{ + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* Check for invalid class and type. */ + if (as->as.rrset.class > 0xffff || as->as.rrset.type > 0xffff) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Do not allow queries of class or type ANY. */ + if (as->as.rrset.class == 0xff || as->as.rrset.type == 0xff) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Do not allow flags yet, unimplemented. */ + if (as->as.rrset.flags) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Create a delegate the lookup to a subquery. */ + as->as_subq = _res_query_async_ctx( + as->as.rrset.name, + as->as.rrset.class, + as->as.rrset.type, + as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_rrset_errno = ERRSET_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + as->as_subq = NULL; + + /* No packet received.*/ + if (ar->ar_datalen == -1) { + switch (ar->ar_h_errno) { + case HOST_NOT_FOUND: + ar->ar_rrset_errno = ERRSET_NONAME; + break; + case NO_DATA: + ar->ar_rrset_errno = ERRSET_NODATA; + break; + default: + ar->ar_rrset_errno = ERRSET_FAIL; + break; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Got a packet but no answer. */ + if (ar->ar_count == 0) { + free(ar->ar_data); + switch (ar->ar_rcode) { + case NXDOMAIN: + ar->ar_rrset_errno = ERRSET_NONAME; + break; + case NOERROR: + ar->ar_rrset_errno = ERRSET_NODATA; + break; + default: + ar->ar_rrset_errno = ERRSET_FAIL; + break; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + get_response(ar, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_rrset_errno) + ar->ar_rrsetinfo = NULL; + return (ASYNC_DONE); + + default: + ar->ar_rrset_errno = ERRSET_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* The rest of this file is taken from the orignal implementation. */ + +/* $OpenBSD: getrrsetbyname_async.c,v 1.11 2017/02/23 17:04:02 eric Exp $ */ + +/* + * Copyright (c) 2001 Jakob Schlyter. All rights reserved. + * + * 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 above 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ + +/* + * Portions Copyright (c) 1999-2001 Internet Software Consortium. + * + * 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 INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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 MAXPACKET 1024*64 + +struct dns_query { + char *name; + u_int16_t type; + u_int16_t class; + struct dns_query *next; +}; + +struct dns_rr { + char *name; + u_int16_t type; + u_int16_t class; + u_int16_t ttl; + u_int16_t size; + void *rdata; + struct dns_rr *next; +}; + +struct dns_response { + HEADER header; + struct dns_query *query; + struct dns_rr *answer; + struct dns_rr *authority; + struct dns_rr *additional; +}; + +static struct dns_response *parse_dns_response(const u_char *, int); +static struct dns_query *parse_dns_qsection(const u_char *, int, + const u_char **, int); +static struct dns_rr *parse_dns_rrsection(const u_char *, int, const u_char **, + int); + +static void free_dns_query(struct dns_query *); +static void free_dns_rr(struct dns_rr *); +static void free_dns_response(struct dns_response *); + +static int count_dns_rr(struct dns_rr *, u_int16_t, u_int16_t); + +static void +get_response(struct asr_result *ar, const char *pkt, int pktlen) +{ + struct rrsetinfo *rrset = NULL; + struct dns_response *response = NULL; + struct dns_rr *rr; + struct rdatainfo *rdata; + unsigned int index_ans, index_sig; + + /* parse result */ + response = parse_dns_response(pkt, pktlen); + if (response == NULL) { + ar->ar_rrset_errno = ERRSET_FAIL; + goto fail; + } + + if (response->header.qdcount != 1) { + ar->ar_rrset_errno = ERRSET_FAIL; + goto fail; + } + + /* initialize rrset */ + rrset = calloc(1, sizeof(struct rrsetinfo)); + if (rrset == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + rrset->rri_rdclass = response->query->class; + rrset->rri_rdtype = response->query->type; + rrset->rri_ttl = response->answer->ttl; + rrset->rri_nrdatas = response->header.ancount; + + /* check for authenticated data */ + if (response->header.ad == 1) + rrset->rri_flags |= RRSET_VALIDATED; + + /* copy name from answer section */ + rrset->rri_name = strdup(response->answer->name); + if (rrset->rri_name == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* count answers */ + rrset->rri_nrdatas = count_dns_rr(response->answer, rrset->rri_rdclass, + rrset->rri_rdtype); + rrset->rri_nsigs = count_dns_rr(response->answer, rrset->rri_rdclass, + T_RRSIG); + + /* allocate memory for answers */ + rrset->rri_rdatas = calloc(rrset->rri_nrdatas, + sizeof(struct rdatainfo)); + if (rrset->rri_rdatas == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* allocate memory for signatures */ + rrset->rri_sigs = calloc(rrset->rri_nsigs, sizeof(struct rdatainfo)); + if (rrset->rri_sigs == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* copy answers & signatures */ + for (rr = response->answer, index_ans = 0, index_sig = 0; + rr; rr = rr->next) { + + rdata = NULL; + + if (rr->class == rrset->rri_rdclass && + rr->type == rrset->rri_rdtype) + rdata = &rrset->rri_rdatas[index_ans++]; + + if (rr->class == rrset->rri_rdclass && + rr->type == T_RRSIG) + rdata = &rrset->rri_sigs[index_sig++]; + + if (rdata) { + rdata->rdi_length = rr->size; + rdata->rdi_data = malloc(rr->size); + + if (rdata->rdi_data == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + memcpy(rdata->rdi_data, rr->rdata, rr->size); + } + } + free_dns_response(response); + + ar->ar_rrsetinfo = rrset; + ar->ar_rrset_errno = ERRSET_SUCCESS; + return; + +fail: + if (rrset != NULL) + freerrset(rrset); + if (response != NULL) + free_dns_response(response); +} + +/* + * DNS response parsing routines + */ +static struct dns_response * +parse_dns_response(const u_char *answer, int size) +{ + struct dns_response *resp; + const u_char *cp; + + /* allocate memory for the response */ + resp = calloc(1, sizeof(*resp)); + if (resp == NULL) + return (NULL); + + /* initialize current pointer */ + cp = answer; + + /* copy header */ + memcpy(&resp->header, cp, HFIXEDSZ); + cp += HFIXEDSZ; + + /* fix header byte order */ + resp->header.qdcount = ntohs(resp->header.qdcount); + resp->header.ancount = ntohs(resp->header.ancount); + resp->header.nscount = ntohs(resp->header.nscount); + resp->header.arcount = ntohs(resp->header.arcount); + + /* there must be at least one query */ + if (resp->header.qdcount < 1) { + free_dns_response(resp); + return (NULL); + } + + /* parse query section */ + resp->query = parse_dns_qsection(answer, size, &cp, + resp->header.qdcount); + if (resp->header.qdcount && resp->query == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse answer section */ + resp->answer = parse_dns_rrsection(answer, size, &cp, + resp->header.ancount); + if (resp->header.ancount && resp->answer == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse authority section */ + resp->authority = parse_dns_rrsection(answer, size, &cp, + resp->header.nscount); + if (resp->header.nscount && resp->authority == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse additional section */ + resp->additional = parse_dns_rrsection(answer, size, &cp, + resp->header.arcount); + if (resp->header.arcount && resp->additional == NULL) { + free_dns_response(resp); + return (NULL); + } + + return (resp); +} + +static struct dns_query * +parse_dns_qsection(const u_char *answer, int size, const u_char **cp, int count) +{ + struct dns_query *head, *curr, *prev; + int i, length; + char name[MAXDNAME]; + + for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) { + + /* allocate and initialize struct */ + curr = calloc(1, sizeof(struct dns_query)); + if (curr == NULL) { + free_dns_query(head); + return (NULL); + } + if (head == NULL) + head = curr; + if (prev != NULL) + prev->next = curr; + + /* name */ + length = dn_expand(answer, answer + size, *cp, name, + sizeof(name)); + if (length < 0) { + free_dns_query(head); + return (NULL); + } + curr->name = strdup(name); + if (curr->name == NULL) { + free_dns_query(head); + return (NULL); + } + *cp += length; + + /* type */ + curr->type = _getshort(*cp); + *cp += INT16SZ; + + /* class */ + curr->class = _getshort(*cp); + *cp += INT16SZ; + } + + return (head); +} + +static struct dns_rr * +parse_dns_rrsection(const u_char *answer, int size, const u_char **cp, + int count) +{ + struct dns_rr *head, *curr, *prev; + int i, length; + char name[MAXDNAME]; + + for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) { + + /* allocate and initialize struct */ + curr = calloc(1, sizeof(struct dns_rr)); + if (curr == NULL) { + free_dns_rr(head); + return (NULL); + } + if (head == NULL) + head = curr; + if (prev != NULL) + prev->next = curr; + + /* name */ + length = dn_expand(answer, answer + size, *cp, name, + sizeof(name)); + if (length < 0) { + free_dns_rr(head); + return (NULL); + } + curr->name = strdup(name); + if (curr->name == NULL) { + free_dns_rr(head); + return (NULL); + } + *cp += length; + + /* type */ + curr->type = _getshort(*cp); + *cp += INT16SZ; + + /* class */ + curr->class = _getshort(*cp); + *cp += INT16SZ; + + /* ttl */ + curr->ttl = _getlong(*cp); + *cp += INT32SZ; + + /* rdata size */ + curr->size = _getshort(*cp); + *cp += INT16SZ; + + /* rdata itself */ + curr->rdata = malloc(curr->size); + if (curr->rdata == NULL) { + free_dns_rr(head); + return (NULL); + } + memcpy(curr->rdata, *cp, curr->size); + *cp += curr->size; + } + + return (head); +} + +static void +free_dns_query(struct dns_query *p) +{ + if (p == NULL) + return; + + if (p->name) + free(p->name); + free_dns_query(p->next); + free(p); +} + +static void +free_dns_rr(struct dns_rr *p) +{ + if (p == NULL) + return; + + if (p->name) + free(p->name); + if (p->rdata) + free(p->rdata); + free_dns_rr(p->next); + free(p); +} + +static void +free_dns_response(struct dns_response *p) +{ + if (p == NULL) + return; + + free_dns_query(p->query); + free_dns_rr(p->answer); + free_dns_rr(p->authority); + free_dns_rr(p->additional); + free(p); +} + +static int +count_dns_rr(struct dns_rr *p, u_int16_t class, u_int16_t type) +{ + int n = 0; + + while (p) { + if (p->class == class && p->type == type) + n++; + p = p->next; + } + + return (n); +} diff --git a/openbsd-compat/libasr/libasr.la b/openbsd-compat/libasr/libasr.la new file mode 100644 index 00000000..71346e8a --- /dev/null +++ b/openbsd-compat/libasr/libasr.la @@ -0,0 +1,41 @@ +# libasr.la - a libtool library file +# Generated by libtool (GNU libtool) 2.4.6 +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='libasr.0.dylib' + +# Names of this library. +library_names='libasr.0.dylib libasr.dylib' + +# The name of the static archive. +old_library='libasr.a' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags=' ' + +# Libraries that this one depends upon. +dependency_libs=' -L/usr/local/Cellar/openssl@1.1/1.1.1d//lib -L/usr/local/lib -lz -lcrypto -lssl -levent -lresolv' + +# Names of additional weak libraries provided by this library +weak_library_names='' + +# Version information for libasr. +current=0 +age=0 +revision=3 + +# Is this an already installed library? +installed=no + +# Should we warn about portability when linking against -modules? +shouldnotlink=no + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='/tmp/lib' diff --git a/openbsd-compat/libasr/res_debug.c b/openbsd-compat/libasr/res_debug.c new file mode 100644 index 00000000..ca9c5ee0 --- /dev/null +++ b/openbsd-compat/libasr/res_debug.c @@ -0,0 +1,2 @@ +/* $OpenBSD: res_debug.c,v 1.1 2012/09/08 11:08:21 eric Exp $ */ +/* NOTHING */ diff --git a/openbsd-compat/libasr/res_init.c b/openbsd-compat/libasr/res_init.c new file mode 100644 index 00000000..04243c47 --- /dev/null +++ b/openbsd-compat/libasr/res_init.c @@ -0,0 +1,103 @@ +/* $OpenBSD: res_init.c,v 1.11 2019/06/17 05:54:45 otto Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <arpa/nameser.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" +#include "thread_private.h" + + +struct __res_state _res; +struct __res_state_ext _res_ext; + +int h_errno; + +int +res_init(void) +{ + static void *resinit_mutex; + struct asr_ctx *ac; + int i; + + ac = _asr_use_resolver(NULL); + + /* + * The first thread to call res_init() will setup the global _res + * structure from the async context, not overriding fields set early + * by the user. + */ + _MUTEX_LOCK(&resinit_mutex); + if (!(_res.options & RES_INIT)) { + if (_res.retry == 0) + _res.retry = ac->ac_nsretries; + if (_res.retrans == 0) + _res.retrans = ac->ac_nstimeout; + if (_res.options == 0) + _res.options = ac->ac_options; + if (_res.lookups[0] == '\0') + strlcpy(_res.lookups, ac->ac_db, sizeof(_res.lookups)); + + for (i = 0; i < ac->ac_nscount && i < MAXNS; i++) { + /* + * No need to check for length since we copy to a + * struct sockaddr_storage with a size of 256 bytes + * and sa_len has only 8 bits. + */ + memcpy(&_res_ext.nsaddr_list[i], ac->ac_ns[i], + ac->ac_ns[i]->sa_len); + if (ac->ac_ns[i]->sa_len <= sizeof(_res.nsaddr_list[i])) + memcpy(&_res.nsaddr_list[i], ac->ac_ns[i], + ac->ac_ns[i]->sa_len); + else + memset(&_res.nsaddr_list[i], 0, + sizeof(_res.nsaddr_list[i])); + } + _res.nscount = i; + _res.options |= RES_INIT; + } + _MUTEX_UNLOCK(&resinit_mutex); + + /* + * If the program is not threaded, we want to reflect (some) changes + * made by the user to the global _res structure. + * This is a bit of a hack: if there is already an async query on + * this context, it might change things in its back. It is ok + * as long as the user only uses the blocking resolver API. + * If needed we could consider cloning the context if there is + * a running query. + */ + if (!__isthreaded) { + ac->ac_nsretries = _res.retry; + ac->ac_nstimeout = _res.retrans; + ac->ac_options = _res.options; + strlcpy(ac->ac_db, _res.lookups, sizeof(ac->ac_db)); + ac->ac_dbcount = strlen(ac->ac_db); + } + + _asr_ctx_unref(ac); + + return (0); +} +DEF_WEAK(res_init); diff --git a/openbsd-compat/libasr/res_mkquery.c b/openbsd-compat/libasr/res_mkquery.c new file mode 100644 index 00000000..959ecc47 --- /dev/null +++ b/openbsd-compat/libasr/res_mkquery.c @@ -0,0 +1,119 @@ +/* $OpenBSD: res_mkquery.c,v 1.13 2019/01/14 06:49:42 otto Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/nameser.h> /* for MAXDNAME */ +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" + +/* This function is apparently needed by some ports. */ +int +res_mkquery(int op, const char *dname, int class, int type, + const unsigned char *data, int datalen, const unsigned char *newrr, + unsigned char *buf, int buflen) +{ + struct asr_ctx *ac; + struct asr_pack p; + struct asr_dns_header h; + char fqdn[MAXDNAME]; + char dn[MAXDNAME]; + + /* we currently only support QUERY */ + if (op != QUERY || data) + return (-1); + + if (dname[0] == '\0' || dname[strlen(dname) - 1] != '.') { + if (strlcpy(fqdn, dname, sizeof(fqdn)) >= sizeof(fqdn) || + strlcat(fqdn, ".", sizeof(fqdn)) >= sizeof(fqdn)) + return (-1); + dname = fqdn; + } + + if (_asr_dname_from_fqdn(dname, dn, sizeof(dn)) == -1) + return (-1); + + ac = _asr_use_resolver(NULL); + + memset(&h, 0, sizeof h); + h.id = res_randomid(); + if (ac->ac_options & RES_RECURSE) + h.flags |= RD_MASK; +#ifdef RES_USE_CD + if (ac->ac_options & RES_USE_CD) + h.flags |= CD_MASK; +#endif + h.qdcount = 1; + if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + h.arcount = 1; + + _asr_pack_init(&p, buf, buflen); + _asr_pack_header(&p, &h); + _asr_pack_query(&p, type, class, dn); + if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + _asr_pack_edns0(&p, MAXPACKETSZ, + ac->ac_options & RES_USE_DNSSEC); + + _asr_ctx_unref(ac); + + if (p.err) + return (-1); + + return (p.offset); +} + +/* + * This function is not documented, but used by sendmail. + * Put here because it uses asr_private.h too. + */ +int +res_querydomain(const char *name, + const char *domain, + int class, + int type, + u_char *answer, + int anslen) +{ + char fqdn[MAXDNAME], ndom[MAXDNAME]; + size_t n; + + /* we really want domain to end with a dot for now */ + if (domain && ((n = strlen(domain)) == 0 || domain[n - 1 ] != '.')) { + if (strlcpy(ndom, domain, sizeof(ndom)) >= sizeof(ndom) || + strlcat(ndom, ".", sizeof(ndom)) >= sizeof(ndom)) { + h_errno = NETDB_INTERNAL; + errno = EINVAL; + return (-1); + } + domain = ndom; + } + + if (_asr_make_fqdn(name, domain, fqdn, sizeof fqdn) == 0) { + h_errno = NETDB_INTERNAL; + errno = EINVAL; + return (-1); + } + + return (res_query(fqdn, class, type, answer, anslen)); +} diff --git a/openbsd-compat/libasr/res_query.c b/openbsd-compat/libasr/res_query.c new file mode 100644 index 00000000..3f891416 --- /dev/null +++ b/openbsd-compat/libasr/res_query.c @@ -0,0 +1,112 @@ +/* $OpenBSD: res_query.c,v 1.9 2015/10/05 02:57:16 guenther Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +int +res_query(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_query_async(name, class, type, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + asr_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + if (ar.ar_h_errno != NETDB_SUCCESS) + return (-1); + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} +DEF_WEAK(res_query); + +int +res_search(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_search_async(name, class, type, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + asr_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + if (ar.ar_h_errno != NETDB_SUCCESS) + return (-1); + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} diff --git a/openbsd-compat/libasr/res_search_async.c b/openbsd-compat/libasr/res_search_async.c new file mode 100644 index 00000000..6436ab85 --- /dev/null +++ b/openbsd-compat/libasr/res_search_async.c @@ -0,0 +1,327 @@ +/* $OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int res_search_async_run(struct asr_query *, struct asr_result *); +static size_t domcat(const char *, const char *, char *, size_t); + +/* + * Unlike res_query_async(), this function returns a valid packet only if + * h_errno is NETDB_SUCCESS. + */ +struct asr_query * +res_search_async(const char *name, int class, int type, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); + + ac = _asr_use_resolver(asr); + as = _res_search_async_ctx(name, class, type, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(res_search_async); + +struct asr_query * +_res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac) +{ + struct asr_query *as; + + DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, + type); + + if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL) + goto err; /* errno set */ + as->as_run = res_search_async_run; + if ((as->as.search.name = strdup(name)) == NULL) + goto err; /* errno set */ + + as->as.search.class = class; + as->as.search.type = type; + + return (as); + err: + if (as) + _asr_async_free(as); + return (NULL); +} + +#define HERRNO_UNSET -2 + +static int +res_search_async_run(struct asr_query *as, struct asr_result *ar) +{ + int r; + char fqdn[MAXDNAME]; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.search.name[0] == '\0') { + ar->ar_h_errno = NO_DATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + as->as.search.saved_h_errno = HERRNO_UNSET; + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + + case ASR_STATE_NEXT_DOMAIN: + /* + * Reset flags to be able to identify the case in + * STATE_SUBQUERY. + */ + as->as_dom_flags = 0; + + r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + if (r == 0) { + ar->ar_errno = EINVAL; + ar->ar_h_errno = NO_RECOVERY; + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + } + as->as_subq = _res_query_async_ctx(fqdn, + as->as.search.class, as->as.search.type, as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_errno = errno; + if (errno == EINVAL) + ar->ar_h_errno = NO_RECOVERY; + else + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + } + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + as->as_subq = NULL; + + if (ar->ar_h_errno == NETDB_SUCCESS) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* + * The original res_search() does this in the domain search + * loop, but only for ECONNREFUSED. I think we can do better + * because technically if we get an errno, it means + * we couldn't reach any nameserver, so there is no point + * in trying further. + */ + if (ar->ar_errno) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + free(ar->ar_data); + + /* + * The original resolver does something like this. + */ + if (as->as_dom_flags & ASYNC_DOM_NDOTS) + as->as.search.saved_h_errno = ar->ar_h_errno; + + if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { + if (ar->ar_h_errno == NO_DATA) + as->as_flags |= ASYNC_NODATA; + else if (ar->ar_h_errno == TRY_AGAIN) + as->as_flags |= ASYNC_AGAIN; + } + + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + + case ASR_STATE_NOT_FOUND: + + if (as->as.search.saved_h_errno != HERRNO_UNSET) + ar->ar_h_errno = as->as.search.saved_h_errno; + else if (as->as_flags & ASYNC_NODATA) + ar->ar_h_errno = NO_DATA; + else if (as->as_flags & ASYNC_AGAIN) + ar->ar_h_errno = TRY_AGAIN; + /* + * Else, we got the ar_h_errno value set by res_query_async() + * for the last domain. + */ + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Concatenate a name and a domain name. The result has no trailing dot. + * Return the resulting string length, or 0 in case of error. + */ +static size_t +domcat(const char *name, const char *domain, char *buf, size_t buflen) +{ + size_t r; + + r = _asr_make_fqdn(name, domain, buf, buflen); + if (r == 0) + return (0); + buf[r - 1] = '\0'; + + return (r - 1); +} + +enum { + DOM_INIT, + DOM_DOMAIN, + DOM_DONE +}; + +/* + * Implement the search domain strategy. + * + * This function works as a generator that constructs complete domains in + * buffer "buf" of size "len" for the given host name "name", according to the + * search rules defined by the resolving context. It is supposed to be called + * multiple times (with the same name) to generate the next possible domain + * name, if any. + * + * It returns -1 if all possibilities have been exhausted, 0 if there was an + * error generating the next name, or the resulting name length. + */ +int +_asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len) +{ + const char *c; + int dots; + + switch (as->as_dom_step) { + + case DOM_INIT: + /* First call */ + + /* + * If "name" is an FQDN, that's the only result and we + * don't try anything else. + */ + if (strlen(name) && name[strlen(name) - 1] == '.') { + DPRINT("asr: iter_domain(\"%s\") fqdn\n", name); + as->as_dom_flags |= ASYNC_DOM_FQDN; + as->as_dom_step = DOM_DONE; + return (domcat(name, NULL, buf, len)); + } + + /* + * Otherwise, we iterate through the specified search domains. + */ + as->as_dom_step = DOM_DOMAIN; + as->as_dom_idx = 0; + + /* + * If "name" as enough dots, use it as-is first, as indicated + * in resolv.conf(5). + */ + dots = 0; + for (c = name; *c; c++) + dots += (*c == '.'); + if (dots >= as->as_ctx->ac_ndots) { + DPRINT("asr: iter_domain(\"%s\") ndots\n", name); + as->as_dom_flags |= ASYNC_DOM_NDOTS; + if (strlcpy(buf, name, len) >= len) + return (0); + return (strlen(buf)); + } + /* Otherwise, starts using the search domains */ + /* FALLTHROUGH */ + + case DOM_DOMAIN: + if (as->as_dom_idx < as->as_ctx->ac_domcount && + (as->as_ctx->ac_options & RES_DNSRCH || ( + as->as_ctx->ac_options & RES_DEFNAMES && + as->as_dom_idx == 0 && + strchr(name, '.') == NULL))) { + DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n", + name, as->as_ctx->ac_dom[as->as_dom_idx]); + as->as_dom_flags |= ASYNC_DOM_DOMAIN; + return (domcat(name, + as->as_ctx->ac_dom[as->as_dom_idx++], buf, len)); + } + + /* No more domain to try. */ + + as->as_dom_step = DOM_DONE; + + /* + * If the name was not tried as an absolute name before, + * do it now. + */ + if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) { + DPRINT("asr: iter_domain(\"%s\") as is\n", name); + as->as_dom_flags |= ASYNC_DOM_ASIS; + if (strlcpy(buf, name, len) >= len) + return (0); + return (strlen(buf)); + } + /* Otherwise, we are done. */ + + case DOM_DONE: + default: + DPRINT("asr: iter_domain(\"%s\") done\n", name); + return (-1); + } +} diff --git a/openbsd-compat/libasr/res_send.c b/openbsd-compat/libasr/res_send.c new file mode 100644 index 00000000..32c94081 --- /dev/null +++ b/openbsd-compat/libasr/res_send.c @@ -0,0 +1,61 @@ +/* $OpenBSD: res_send.c,v 1.8 2014/03/26 18:13:15 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +int +res_send(const u_char *buf, int buflen, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + errno = EINVAL; + return (-1); + } + + as = res_send_async(buf, buflen, NULL); + if (as == NULL) + return (-1); /* errno set */ + + asr_run_sync(as, &ar); + + if (ar.ar_errno) { + errno = ar.ar_errno; + return (-1); + } + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} diff --git a/openbsd-compat/libasr/res_send_async.c b/openbsd-compat/libasr/res_send_async.c new file mode 100644 index 00000000..7eeeef48 --- /dev/null +++ b/openbsd-compat/libasr/res_send_async.c @@ -0,0 +1,806 @@ +/* $OpenBSD: res_send_async.c,v 1.39 2019/09/28 11:21:07 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <resolv.h> /* for res_random */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +#define OP_QUERY (0) + +static int res_send_async_run(struct asr_query *, struct asr_result *); +static int sockaddr_connect(const struct sockaddr *, int); +static int udp_send(struct asr_query *); +static int udp_recv(struct asr_query *); +static int tcp_write(struct asr_query *); +static int tcp_read(struct asr_query *); +static int validate_packet(struct asr_query *); +static int setup_query(struct asr_query *, const char *, const char *, int, int); +static int ensure_ibuf(struct asr_query *, size_t); +static int iter_ns(struct asr_query *); + +#define AS_NS_SA(p) ((p)->as_ctx->ac_ns[(p)->as.dns.nsidx - 1]) + + +struct asr_query * +res_send_async(const unsigned char *buf, int buflen, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + + DPRINT_PACKET("asr: res_send_async()", buf, buflen); + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_SEND)) == NULL) { + _asr_ctx_unref(ac); + return (NULL); /* errno set */ + } + as->as_run = res_send_async_run; + + as->as_flags |= ASYNC_EXTOBUF; + as->as.dns.obuf = (unsigned char *)buf; + as->as.dns.obuflen = buflen; + as->as.dns.obufsize = buflen; + + _asr_unpack_init(&p, buf, buflen); + _asr_unpack_header(&p, &h); + _asr_unpack_query(&p, &q); + if (p.err) { + errno = EINVAL; + goto err; + } + as->as.dns.reqid = h.id; + as->as.dns.type = q.q_type; + as->as.dns.class = q.q_class; + as->as.dns.dname = strdup(q.q_dname); + if (as->as.dns.dname == NULL) + goto err; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + err: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(res_send_async); + +/* + * Unlike res_query(), this version will actually return the packet + * if it has received a valid one (errno == 0) even if h_errno is + * not NETDB_SUCCESS. So the packet *must* be freed if necessary. + */ +struct asr_query * +res_query_async(const char *name, int class, int type, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + DPRINT("asr: res_query_async(\"%s\", %i, %i)\n", name, class, type); + + ac = _asr_use_resolver(asr); + as = _res_query_async_ctx(name, class, type, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(res_query_async); + +struct asr_query * +_res_query_async_ctx(const char *name, int class, int type, struct asr_ctx *a_ctx) +{ + struct asr_query *as; + + DPRINT("asr: res_query_async_ctx(\"%s\", %i, %i)\n", name, class, type); + + if ((as = _asr_async_new(a_ctx, ASR_SEND)) == NULL) + return (NULL); /* errno set */ + as->as_run = res_send_async_run; + + /* This adds a "." to name if it doesn't already has one. + * That's how res_query() behaves (through res_mkquery"). + */ + if (setup_query(as, name, NULL, class, type) == -1) + goto err; /* errno set */ + + return (as); + + err: + if (as) + _asr_async_free(as); + + return (NULL); +} + +static int +res_send_async_run(struct asr_query *as, struct asr_result *ar) +{ + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as_ctx->ac_nscount == 0) { + ar->ar_errno = ECONNREFUSED; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_NEXT_NS); + break; + + case ASR_STATE_NEXT_NS: + + if (iter_ns(as) == -1) { + ar->ar_errno = ETIMEDOUT; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_ctx->ac_options & RES_USEVC || + as->as.dns.obuflen > PACKETSZ) + async_set_state(as, ASR_STATE_TCP_WRITE); + else + async_set_state(as, ASR_STATE_UDP_SEND); + break; + + case ASR_STATE_UDP_SEND: + + if (udp_send(as) == -1) { + async_set_state(as, ASR_STATE_NEXT_NS); + break; + } + async_set_state(as, ASR_STATE_UDP_RECV); + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + break; + + case ASR_STATE_UDP_RECV: + + if (udp_recv(as) == -1) { + if (errno == ENOMEM) { + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + break; + } + if (errno != EOVERFLOW) { + /* Fail or timeout */ + async_set_state(as, ASR_STATE_NEXT_NS); + break; + } + if (as->as_ctx->ac_options & RES_IGNTC) + async_set_state(as, ASR_STATE_PACKET); + else + async_set_state(as, ASR_STATE_TCP_WRITE); + } else + async_set_state(as, ASR_STATE_PACKET); + break; + + case ASR_STATE_TCP_WRITE: + + switch (tcp_write(as)) { + case -1: /* fail or timeout */ + async_set_state(as, ASR_STATE_NEXT_NS); + break; + case 0: + async_set_state(as, ASR_STATE_TCP_READ); + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + case 1: + ar->ar_cond = ASR_WANT_WRITE; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_TCP_READ: + + switch (tcp_read(as)) { + case -1: /* Fail or timeout */ + if (errno == ENOMEM) { + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_NS); + break; + case 0: + async_set_state(as, ASR_STATE_PACKET); + break; + case 1: + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_PACKET: + + memmove(&ar->ar_ns, AS_NS_SA(as), SA_LEN(AS_NS_SA(as))); + ar->ar_datalen = as->as.dns.ibuflen; + ar->ar_data = as->as.dns.ibuf; + as->as.dns.ibuf = NULL; + ar->ar_errno = 0; + ar->ar_rcode = as->as.dns.rcode; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + if (ar->ar_errno) { + ar->ar_h_errno = TRY_AGAIN; + ar->ar_count = 0; + ar->ar_datalen = -1; + ar->ar_data = NULL; + } else if (as->as.dns.ancount) { + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_count = as->as.dns.ancount; + } else { + ar->ar_count = 0; + switch (as->as.dns.rcode) { + case NXDOMAIN: + ar->ar_h_errno = HOST_NOT_FOUND; + break; + case SERVFAIL: + ar->ar_h_errno = TRY_AGAIN; + break; + case NOERROR: + ar->ar_h_errno = NO_DATA; + break; + default: + ar->ar_h_errno = NO_RECOVERY; + } + } + return (ASYNC_DONE); + + default: + + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +static int +sockaddr_connect(const struct sockaddr *sa, int socktype) +{ + int errno_save, sock, flags; + + if ((sock = socket(sa->sa_family, socktype, 0)) == -1) + goto fail; + + if ((flags = fcntl(sock, F_GETFL, 0)) == -1) + goto fail; + + flags |= O_NONBLOCK; + + if ((flags = fcntl(sock, F_SETFL, flags)) == -1) + goto fail; + + if (connect(sock, sa, SA_LEN(sa)) == -1) { + /* + * In the TCP case, the caller will be asked to poll for + * POLLOUT so that we start writing the packet in tcp_write() + * when the connection is established, or fail there on error. + */ + if (errno == EINPROGRESS) + return (sock); + goto fail; + } + + return (sock); + + fail: + + if (sock != -1) { + errno_save = errno; + close(sock); + errno = errno_save; + } + + return (-1); +} + +/* + * Prepare the DNS packet for the query type "type", class "class" and domain + * name created by the concatenation on "name" and "dom". + * Return 0 on success, set errno and return -1 on error. + */ +static int +setup_query(struct asr_query *as, const char *name, const char *dom, + int class, int type) +{ + struct asr_pack p; + struct asr_dns_header h; + char fqdn[MAXDNAME]; + char dname[MAXDNAME]; + + if (as->as_flags & ASYNC_EXTOBUF) { + errno = EINVAL; + DPRINT("attempting to write in user packet"); + return (-1); + } + + if (_asr_make_fqdn(name, dom, fqdn, sizeof(fqdn)) > sizeof(fqdn)) { + errno = EINVAL; + DPRINT("asr_make_fqdn: name too long\n"); + return (-1); + } + + if (_asr_dname_from_fqdn(fqdn, dname, sizeof(dname)) == -1) { + errno = EINVAL; + DPRINT("asr_dname_from_fqdn: invalid\n"); + return (-1); + } + + if (as->as.dns.obuf == NULL) { + as->as.dns.obufsize = PACKETSZ; + as->as.dns.obuf = malloc(as->as.dns.obufsize); + if (as->as.dns.obuf == NULL) + return (-1); /* errno set */ + } + as->as.dns.obuflen = 0; + + memset(&h, 0, sizeof h); + h.id = res_randomid(); + if (as->as_ctx->ac_options & RES_RECURSE) + h.flags |= RD_MASK; +#ifdef RES_USE_CD + if (as->as_ctx->ac_options & RES_USE_CD) + h.flags |= CD_MASK; +#endif + h.qdcount = 1; + if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + h.arcount = 1; + + _asr_pack_init(&p, as->as.dns.obuf, as->as.dns.obufsize); + _asr_pack_header(&p, &h); + _asr_pack_query(&p, type, class, dname); + if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + _asr_pack_edns0(&p, MAXPACKETSZ, + as->as_ctx->ac_options & RES_USE_DNSSEC); + if (p.err) { + DPRINT("error packing query"); + errno = EINVAL; + return (-1); + } + + /* Remember the parameters. */ + as->as.dns.reqid = h.id; + as->as.dns.type = type; + as->as.dns.class = class; + if (as->as.dns.dname) + free(as->as.dns.dname); + as->as.dns.dname = strdup(dname); + if (as->as.dns.dname == NULL) { + DPRINT("strdup"); + return (-1); /* errno set */ + } + as->as.dns.obuflen = p.offset; + + DPRINT_PACKET("asr_setup_query", as->as.dns.obuf, as->as.dns.obuflen); + + return (0); +} + +/* + * Create a connect UDP socket and send the output packet. + * + * Return 0 on success, or -1 on error (errno set). + */ +static int +udp_send(struct asr_query *as) +{ + ssize_t n; + int save_errno; +#ifdef DEBUG + char buf[256]; +#endif + + DPRINT("asr: [%p] connecting to %s UDP\n", as, + _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf)); + + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_DGRAM); + if (as->as_fd == -1) + return (-1); /* errno set */ + + n = send(as->as_fd, as->as.dns.obuf, as->as.dns.obuflen, 0); + if (n == -1) { + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (-1); + } + + return (0); +} + +/* + * Try to receive a valid packet from the current UDP socket. + * + * Return 0 if a full packet could be read, or -1 on error (errno set). + */ +static int +udp_recv(struct asr_query *as) +{ + ssize_t n; + int save_errno; + + if (ensure_ibuf(as, MAXPACKETSZ) == -1) { + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (-1); + } + + n = recv(as->as_fd, as->as.dns.ibuf, as->as.dns.ibufsize, 0); + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + if (n == -1) + return (-1); + + as->as.dns.ibuflen = n; + + DPRINT_PACKET("asr_udp_recv()", as->as.dns.ibuf, as->as.dns.ibuflen); + + if (validate_packet(as) == -1) + return (-1); /* errno set */ + + return (0); +} + +/* + * Write the output packet to the TCP socket. + * + * Return 0 when all bytes have been sent, 1 there is no buffer space on the + * socket or it is not connected yet, or -1 on error (errno set). + */ +static int +tcp_write(struct asr_query *as) +{ + struct msghdr msg; + struct iovec iov[2]; + uint16_t len; + ssize_t n; + size_t offset; + int i; +#ifdef DEBUG + char buf[256]; +#endif + + /* First try to connect if not already */ + if (as->as_fd == -1) { + DPRINT("asr: [%p] connecting to %s TCP\n", as, + _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf)); + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_STREAM); + if (as->as_fd == -1) + return (-1); /* errno set */ +/* + * Some systems (MacOS X) have SO_NOSIGPIPE instead of MSG_NOSIGNAL. + * If neither is available the system is probably broken. We might + * want to detect this at configure time. + */ +#ifdef SO_NOSIGPIPE + i = 1; + if (setsockopt(as->as_fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&i, + sizeof(i)) == -1) + return (-1); /* errno set */ +#endif + as->as.dns.datalen = 0; /* bytes sent */ + return (1); + } + + i = 0; + + /* Prepend de packet length if not sent already. */ + if (as->as.dns.datalen < sizeof(len)) { + offset = 0; + len = htons(as->as.dns.obuflen); + iov[i].iov_base = (char *)(&len) + as->as.dns.datalen; + iov[i].iov_len = sizeof(len) - as->as.dns.datalen; + i++; + } else + offset = as->as.dns.datalen - sizeof(len); + + iov[i].iov_base = as->as.dns.obuf + offset; + iov[i].iov_len = as->as.dns.obuflen - offset; + i++; + + memset(&msg, 0, sizeof msg); + msg.msg_iov = iov; + msg.msg_iovlen = i; + + send_again: +/* See above. */ +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + n = sendmsg(as->as_fd, &msg, MSG_NOSIGNAL); + if (n == -1) { + if (errno == EINTR) + goto send_again; + goto close; /* errno set */ + } + + as->as.dns.datalen += n; + + if (as->as.dns.datalen == as->as.dns.obuflen + sizeof(len)) { + /* All sent. Prepare for TCP read */ + as->as.dns.datalen = 0; + return (0); + } + + /* More data to write */ + return (1); + +close: + close(as->as_fd); + as->as_fd = -1; + return (-1); +} + +/* + * Try to read a valid packet from the current TCP socket. + * + * Return 0 if a full packet could be read, 1 if more data is needed and the + * socket must be read again, or -1 on error (errno set). + */ +static int +tcp_read(struct asr_query *as) +{ + ssize_t n; + size_t offset, len; + char *pos; + int save_errno, nfds; + struct pollfd pfd; + + /* We must read the packet len first */ + if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) { + + pos = (char *)(&as->as.dns.pktlen) + as->as.dns.datalen; + len = sizeof(as->as.dns.pktlen) - as->as.dns.datalen; + + read_again0: + n = read(as->as_fd, pos, len); + if (n == -1) { + if (errno == EINTR) + goto read_again0; + goto close; /* errno set */ + } + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.datalen += n; + if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) + return (1); /* need more data */ + + as->as.dns.ibuflen = ntohs(as->as.dns.pktlen); + if (ensure_ibuf(as, as->as.dns.ibuflen) == -1) + goto close; /* errno set */ + + pfd.fd = as->as_fd; + pfd.events = POLLIN; + poll_again: + nfds = poll(&pfd, 1, 0); + if (nfds == -1) { + if (errno == EINTR) + goto poll_again; + goto close; /* errno set */ + } + if (nfds == 0) + return (1); /* no more data available */ + } + + offset = as->as.dns.datalen - sizeof(as->as.dns.pktlen); + pos = as->as.dns.ibuf + offset; + len = as->as.dns.ibuflen - offset; + + read_again: + n = read(as->as_fd, pos, len); + if (n == -1) { + if (errno == EINTR) + goto read_again; + goto close; /* errno set */ + } + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.datalen += n; + + /* See if we got all the advertised bytes. */ + if (as->as.dns.datalen != as->as.dns.ibuflen + sizeof(as->as.dns.pktlen)) + return (1); + + DPRINT_PACKET("asr_tcp_read()", as->as.dns.ibuf, as->as.dns.ibuflen); + + if (validate_packet(as) == -1) + goto close; /* errno set */ + + errno = 0; +close: + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (errno ? -1 : 0); +} + +/* + * Make sure the input buffer is at least "n" bytes long, and allocate or + * extend it if necessary. Return 0 on success, or set errno and return -1. + */ +static int +ensure_ibuf(struct asr_query *as, size_t n) +{ + char *t; + + if (as->as.dns.ibufsize >= n) + return (0); + + t = recallocarray(as->as.dns.ibuf, as->as.dns.ibufsize, n, 1); + if (t == NULL) + return (-1); /* errno set */ + as->as.dns.ibuf = t; + as->as.dns.ibufsize = n; + + return (0); +} + +/* + * Check if the received packet is valid. + * Return 0 on success, or set errno and return -1. + */ +static int +validate_packet(struct asr_query *as) +{ + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int r; + + _asr_unpack_init(&p, as->as.dns.ibuf, as->as.dns.ibuflen); + + _asr_unpack_header(&p, &h); + if (p.err) + goto inval; + + if (h.id != as->as.dns.reqid) { + DPRINT("incorrect reqid\n"); + goto inval; + } + if (h.qdcount != 1) + goto inval; + /* Should be zero, we could allow this */ + if ((h.flags & Z_MASK) != 0) + goto inval; + /* Actually, it depends on the request but we only use OP_QUERY */ + if (OPCODE(h.flags) != OP_QUERY) + goto inval; + /* Must be a response */ + if ((h.flags & QR_MASK) == 0) + goto inval; + + as->as.dns.rcode = RCODE(h.flags); + as->as.dns.ancount = h.ancount; + + _asr_unpack_query(&p, &q); + if (p.err) + goto inval; + + if (q.q_type != as->as.dns.type || + q.q_class != as->as.dns.class || + strcasecmp(q.q_dname, as->as.dns.dname)) { + DPRINT("incorrect type/class/dname '%s' != '%s'\n", + q.q_dname, as->as.dns.dname); + goto inval; + } + + /* Check for truncation */ + if (h.flags & TC_MASK && !(as->as_ctx->ac_options & RES_IGNTC)) { + DPRINT("truncated\n"); + errno = EOVERFLOW; + return (-1); + } + + /* Validate the rest of the packet */ + for (r = h.ancount + h.nscount + h.arcount; r; r--) + _asr_unpack_rr(&p, &rr); + + /* Report any error found when unpacking the RRs. */ + if (p.err) { + DPRINT("unpack: %s\n", strerror(p.err)); + errno = p.err; + return (-1); + } + + if (p.offset != as->as.dns.ibuflen) { + DPRINT("trailing garbage\n"); + errno = EMSGSIZE; + return (-1); + } + + return (0); + + inval: + errno = EINVAL; + return (-1); +} + +/* + * Set the async context nameserver index to the next nameserver, cycling + * over the list until the maximum retry counter is reached. Return 0 on + * success, or -1 if all nameservers were used. + */ +static int +iter_ns(struct asr_query *as) +{ + for (;;) { + if (as->as.dns.nsloop >= as->as_ctx->ac_nsretries) + return (-1); + + as->as.dns.nsidx += 1; + if (as->as.dns.nsidx <= as->as_ctx->ac_nscount) + break; + as->as.dns.nsidx = 0; + as->as.dns.nsloop++; + DPRINT("asr: iter_ns(): cycle %i\n", as->as.dns.nsloop); + } + + as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as.dns.nsloop); + if (as->as.dns.nsloop > 0) + as->as_timeout /= as->as_ctx->ac_nscount; + if (as->as_timeout < 1000) + as->as_timeout = 1000; + + return (0); +} diff --git a/openbsd-compat/libasr/sethostent.c b/openbsd-compat/libasr/sethostent.c new file mode 100644 index 00000000..61fa3e2f --- /dev/null +++ b/openbsd-compat/libasr/sethostent.c @@ -0,0 +1,36 @@ +/* $OpenBSD: sethostent.c,v 1.2 2018/04/28 15:09:35 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * 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 <netdb.h> +#include <stddef.h> + +void +sethostent(int stayopen) +{ +} + +void +endhostent(void) +{ +} + +struct hostent * +gethostent(void) +{ + h_errno = NETDB_INTERNAL; + return NULL; +} diff --git a/openbsd-compat/libasr/thread_private.h b/openbsd-compat/libasr/thread_private.h new file mode 100644 index 00000000..23951975 --- /dev/null +++ b/openbsd-compat/libasr/thread_private.h @@ -0,0 +1,8 @@ +/* + * + */ +#define __is_threaded (0) +#define _THREAD_PRIVATE_MUTEX(x) +#define _THREAD_PRIVATE_MUTEX_LOCK(x) +#define _THREAD_PRIVATE_MUTEX_UNLOCK(x) +#define _THREAD_PRIVATE(a, b, c) (c) diff --git a/openbsd-compat/openbsd-compat.h b/openbsd-compat/openbsd-compat.h index 6c73e5b5..dcb643f1 100644 --- a/openbsd-compat/openbsd-compat.h +++ b/openbsd-compat/openbsd-compat.h @@ -96,15 +96,13 @@ const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); char *strsep(char **stringp, const char *delim); #endif -#ifndef HAVE_SETPROCTITLE +#ifdef NEED_SETPROCTITLE void setproctitle(const char *fmt, ...); void compat_init_setproctitle(int argc, char *argv[]); #endif #if !defined(HAVE_GETOPT) || !defined(HAVE_GETOPT_OPTRESET) int BSDgetopt(int argc, char * const *argv, const char *opts); -char *BSDoptarg; /* argument associated with option */ -int BSDoptind; /* index into parent argv vector */ #endif /* Home grown routines */ @@ -122,15 +120,19 @@ int getpeereid(int , uid_t *, gid_t *); unsigned int arc4random(void); #endif -#if defined(HAVE_ARC4RANDOM_STIR) +#if 0 +#if defined(LIBRESSL_VERSION_NUMBER) +# define arc4random_stir() +#elif defined(HAVE_ARC4RANDOM_STIR) void arc4random_stir(void); -#elif defined(HAVE_ARC4RANDOM) || defined(LIBRESSL_VERSION_NUMBER) +#elif defined(HAVE_ARC4RANDOM) /* Recent system/libressl implementation; no need for explicit stir */ # define arc4random_stir() #else /* openbsd-compat/arc4random.c provides arc4random_stir() */ void arc4random_stir(void); #endif +#endif #if !defined(HAVE_ARC4RANDOM_BUF) || defined(LIBRESSL_VERSION_NUMBER) void arc4random_buf(void *, size_t); @@ -332,6 +334,8 @@ const char *strerror(int); int usleep(unsigned int useconds); #endif +int pipe2(int pipefd[2], int flags); + char *get_progname(char *); diff --git a/openbsd-compat/pipe2.c b/openbsd-compat/pipe2.c new file mode 100644 index 00000000..fd5c3f49 --- /dev/null +++ b/openbsd-compat/pipe2.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Gilles Chehade <gilles@poolp.org> + * Copyright (C) 2020 Jason A. Donenfeld <Jason@zx2c4.com>. 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 <fcntl.h> +#include <unistd.h> + +int +pipe2(int pipefd[2], int flags) +{ + if (pipe(pipefd) == -1) + return -1; + + if ((flags & O_NONBLOCK) && + (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(pipefd[1], F_SETFL, O_NONBLOCK) == -1)) { + close(pipefd[0]); + close(pipefd[1]); + return -1; + } + + if ((flags & O_CLOEXEC) && + (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1 || + fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1)) { + close(pipefd[0]); + close(pipefd[1]); + return -1; + } + + return 0; +} diff --git a/openbsd-compat/res_hnok.c b/openbsd-compat/res_hnok.c new file mode 100644 index 00000000..a4b54baf --- /dev/null +++ b/openbsd-compat/res_hnok.c @@ -0,0 +1,169 @@ +/* $OpenBSD: res_comp.c,v 1.14 2008/04/16 22:35:23 deraadt Exp $ */ + +/* + * ++Copyright++ 1985, 1993 + * - + * Copyright (c) 1985, 1993 + * The Regents of the University of California. All rights reserved. + * + * 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 above 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. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS 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. + * - + * Portions Copyright (c) 1993 by Digital Equipment Corporation. + * + * 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, and that + * the name of Digital Equipment Corporation not be used in advertising or + * publicity pertaining to distribution of the document or software without + * specific, written prior permission. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT + * CORPORATION 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. + * - + * --Copyright-- + */ + +/* OPENBSD ORIGINAL: lib/libc/net/res_comp.c */ + +#include "includes.h" + +/* + * Verify that a domain name uses an acceptable character set. + */ + +/* + * Note the conspicuous absence of ctype macros in these definitions. On + * non-ASCII hosts, we can't depend on string literals or ctype macros to + * tell us anything about network-format data. The rest of the BIND system + * is not careful about this, but for some reason, we're doing it right here. + */ +#define PERIOD 0x2e +#define hyphenchar(c) ((c) == 0x2d) +#define bslashchar(c) ((c) == 0x5c) +#define underscorechar(c) ((c) == 0x5f) +#define periodchar(c) ((c) == PERIOD) +#define asterchar(c) ((c) == 0x2a) +#define alphachar(c) (((c) >= 0x41 && (c) <= 0x5a) \ + || ((c) >= 0x61 && (c) <= 0x7a)) +#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39) + +#define borderchar(c) (alphachar(c) || digitchar(c)) +#define middlechar(c) (borderchar(c) || hyphenchar(c) || underscorechar(c)) +#define domainchar(c) ((c) > 0x20 && (c) < 0x7f) + +int +res_hnok(const char *dn) +{ + int pch = PERIOD, ch = *dn++; + + while (ch != '\0') { + int nch = *dn++; + + if (periodchar(ch)) { + ; + } else if (periodchar(pch)) { + if (!borderchar(ch)) + return (0); + } else if (periodchar(nch) || nch == '\0') { + if (!borderchar(ch)) + return (0); + } else { + if (!middlechar(ch)) + return (0); + } + pch = ch, ch = nch; + } + return (1); +} + +#if 0 + +/* + * hostname-like (A, MX, WKS) owners can have "*" as their first label + * but must otherwise be as a host name. + */ +int +res_ownok(const char *dn) +{ + if (asterchar(dn[0])) { + if (periodchar(dn[1])) + return (res_hnok(dn+2)); + if (dn[1] == '\0') + return (1); + } + return (res_hnok(dn)); +} + +/* + * SOA RNAMEs and RP RNAMEs can have any printable character in their first + * label, but the rest of the name has to look like a host name. + */ +int +res_mailok(const char *dn) +{ + int ch, escaped = 0; + + /* "." is a valid missing representation */ + if (*dn == '\0') + return(1); + + /* otherwise <label>.<hostname> */ + while ((ch = *dn++) != '\0') { + if (!domainchar(ch)) + return (0); + if (!escaped && periodchar(ch)) + break; + if (escaped) + escaped = 0; + else if (bslashchar(ch)) + escaped = 1; + } + if (periodchar(ch)) + return (res_hnok(dn)); + return(0); +} + +/* + * This function is quite liberal, since RFC 1034's character sets are only + * recommendations. + */ +int +res_dnok(const char *dn) +{ + int ch; + + while ((ch = *dn++) != '\0') + if (!domainchar(ch)) + return (0); + return (1); +} + +#endif /* if 0 */ diff --git a/openbsd-compat/res_randomid.c b/openbsd-compat/res_randomid.c new file mode 100644 index 00000000..c848c41d --- /dev/null +++ b/openbsd-compat/res_randomid.c @@ -0,0 +1,13 @@ +#include "includes.h" + +#include <time.h> + +unsigned int +res_randomid(void) +{ + struct timespec ts; + + /* This is from musl C library */ + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_nsec + ts.tv_nsec / 65536UL & 0xffff; +} diff --git a/openbsd-compat/setproctitle.c b/openbsd-compat/setproctitle.c index eef70c14..71e1595e 100644 --- a/openbsd-compat/setproctitle.c +++ b/openbsd-compat/setproctitle.c @@ -68,8 +68,7 @@ static size_t argv_env_len = 0; void compat_init_setproctitle(int argc, char *argv[]) { -#if !defined(HAVE_SETPROCTITLE) && \ - defined(SPT_TYPE) && SPT_TYPE == SPT_REUSEARGV +#if defined(SPT_TYPE) && SPT_TYPE == SPT_REUSEARGV extern char **environ; char *lastargv = NULL; char **envp = environ; @@ -156,8 +155,6 @@ setproctitle(const char *fmt, ...) pst.pst_command = ptitle; pstat(PSTAT_SETCMD, pst, strlen(ptitle), 0, 0); #elif SPT_TYPE == SPT_REUSEARGV -/* debug("setproctitle: copy \"%s\" into len %d", - buf, argv_env_len); */ len = strlcpy(argv_start, ptitle, argv_env_len); for(; len < argv_env_len; len++) argv_start[len] = SPT_PADCHAR; diff --git a/regress/config/Makefile b/regress/config/Makefile deleted file mode 100644 index ba2dded8..00000000 --- a/regress/config/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -FILES = test0.conf -FILES+= test1.conf -FILES+= test2.conf -FILES+= test3.conf -FILES+= test4.conf -FILES+= test5.conf -FILES+= test6.conf -FILES+= test7.conf -FILES+= test8.conf -FILES+= test9.conf -FILES+= test10.conf -FILES+= test11.conf - -test: -.for FILE in $(FILES) - smtpd -n -f $(FILE) -.endfor diff --git a/regress/config/include.conf b/regress/config/include.conf deleted file mode 100644 index 667bc640..00000000 --- a/regress/config/include.conf +++ /dev/null @@ -1 +0,0 @@ -table sources { 127.0.0.1 } diff --git a/regress/config/test0.conf b/regress/config/test0.conf deleted file mode 100644 index cbd2a2f1..00000000 --- a/regress/config/test0.conf +++ /dev/null @@ -1,3 +0,0 @@ -listen on lo0 - -accept for local deliver to mbox diff --git a/regress/config/test1.conf b/regress/config/test1.conf deleted file mode 100644 index 8983f6c8..00000000 --- a/regress/config/test1.conf +++ /dev/null @@ -1,5 +0,0 @@ -listen on lo0 - -include "include.conf" - -accept from <sources> for local deliver to mbox diff --git a/regress/config/test10.conf b/regress/config/test10.conf deleted file mode 100644 index b40480d6..00000000 --- a/regress/config/test10.conf +++ /dev/null @@ -1,6 +0,0 @@ -listen on lo0 - -accept from any for local deliver to mbox -accept from any for local deliver to maildir -accept from any for local deliver to maildir "~/Maildir" -accept from any for local deliver to mda "/bin/cat" diff --git a/regress/config/test11.conf b/regress/config/test11.conf deleted file mode 100644 index 3b43f72d..00000000 --- a/regress/config/test11.conf +++ /dev/null @@ -1,25 +0,0 @@ -listen on lo0 - -accept for any relay -accept for any relay as example -accept for any relay as "@example.org" -accept for any relay as example@example.org - -accept for any relay backup mx1.example.org -accept for any relay backup mx1.example.org as example -accept for any relay backup mx1.example.org as "@example.org" -accept for any relay backup mx1.example.org as example@example.org - -accept for any relay via mx1.example.org -accept for any relay via smtp://mx1.example.org -accept for any relay via smtp://mx1.example.org:25 -accept for any relay via tls://mx1.example.org:25 -accept for any relay via smtps://mx1.example.org - -accept for any relay via mx1.example.org as example@example.org -accept for any relay via smtp://mx1.example.org as example@example.org -accept for any relay via smtp://mx1.example.org:25 as example@example.org -accept for any relay via tls://mx1.example.org:25 as example@example.org -accept for any relay via smtps://mx1.example.org as example@example.org - - diff --git a/regress/config/test2.conf b/regress/config/test2.conf deleted file mode 100644 index dc67a4e4..00000000 --- a/regress/config/test2.conf +++ /dev/null @@ -1,5 +0,0 @@ -listen on lo0 - -expire 4d - -accept for local deliver to mbox diff --git a/regress/config/test3.conf b/regress/config/test3.conf deleted file mode 100644 index 8ca91bf4..00000000 --- a/regress/config/test3.conf +++ /dev/null @@ -1,5 +0,0 @@ -listen on lo0 - -hostname test.org - -accept for local deliver to mbox diff --git a/regress/config/test4.conf b/regress/config/test4.conf deleted file mode 100644 index 6e5f4b04..00000000 --- a/regress/config/test4.conf +++ /dev/null @@ -1,4 +0,0 @@ -listen on lo0 port 25 -listen on lo0 port smtp - -accept for local deliver to mbox diff --git a/regress/config/test5.conf b/regress/config/test5.conf deleted file mode 100644 index dc1c42ad..00000000 --- a/regress/config/test5.conf +++ /dev/null @@ -1,9 +0,0 @@ -listen on lo0 port 25 -listen on lo0 port smtp - -table aliases_dynamic "/etc/mail/aliases" -table aliases_static { root => test } - -accept for local alias <aliases_dynamic> deliver to mbox -accept for local alias <aliases_static> deliver to mbox -accept for local alias { k => v } deliver to mbox diff --git a/regress/config/test6.conf b/regress/config/test6.conf deleted file mode 100644 index a2a6ded2..00000000 --- a/regress/config/test6.conf +++ /dev/null @@ -1,5 +0,0 @@ -listen on lo0 - -max-message-size 1G - -accept for local deliver to mbox diff --git a/regress/config/test7.conf b/regress/config/test7.conf deleted file mode 100644 index 274273bd..00000000 --- a/regress/config/test7.conf +++ /dev/null @@ -1,12 +0,0 @@ -listen on lo0 - -table sources { 192.168.1.2 } - -accept from any for local deliver to mbox -accept from local for local deliver to mbox -accept from 192.168.1.0/24 for local deliver to mbox -accept from 192.168.1.1 for local deliver to mbox -accept from { 192.168.1.1 } for local deliver to mbox -accept from { 192.168.1.1, 192.168.1.2 } for local deliver to mbox -accept from <sources> for local deliver to mbox - diff --git a/regress/config/test8.conf b/regress/config/test8.conf deleted file mode 100644 index 9cf0c269..00000000 --- a/regress/config/test8.conf +++ /dev/null @@ -1,13 +0,0 @@ -listen on lo0 - -table aliases "/etc/mail/aliases" -table domains { example.org, example.com } - -accept from any for any deliver to mbox -accept from any for local deliver to mbox -accept from any for domain example.org deliver to mbox -accept from any for domain example.org alias <aliases> deliver to mbox -accept from any for domain <domains> deliver to mbox -accept from any for domain <domains> alias <aliases> deliver to mbox -accept from any for domain { example.org, example.com } deliver to mbox -accept from any for domain { example.org, example.com } alias <aliases> deliver to mbox diff --git a/regress/config/test9.conf b/regress/config/test9.conf deleted file mode 100644 index 7bcb1fcb..00000000 --- a/regress/config/test9.conf +++ /dev/null @@ -1,8 +0,0 @@ -listen on lo0 - -table vusers "/etc/mail/aliases" -table domains { example.org, example.com } -accept from any for domain "*" virtual <vusers> deliver to mbox -accept from any for domain example.org virtual <vusers> deliver to mbox -accept from any for domain <domains> virtual <vusers> deliver to mbox -accept from any for domain { example.org, example.com } virtual <vusers> deliver to mbox diff --git a/regress/expand/Makefile b/regress/expand/Makefile deleted file mode 100644 index aa1781c9..00000000 --- a/regress/expand/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -PROG= expand -NOMAN= 1 - -LDFLAGS+= -lutil - -.include <bsd.prog.mk> diff --git a/regress/expand/expand.c b/regress/expand/expand.c deleted file mode 100644 index bfed154b..00000000 --- a/regress/expand/expand.c +++ /dev/null @@ -1,129 +0,0 @@ -/* $OpenBSD: expand.c,v 1.18 2012/10/10 18:02:37 eric Exp $ */ - -/* - * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> - * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> - * - * 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 <err.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <util.h> - -#define MAX_LINE_SIZE 2048 - -static char * -strip(char *s) -{ - size_t l; - - while (*s == ' ' || *s == '\t') - s++; - - for (l = strlen(s); l; l--) { - if (s[l-1] != ' ' && s[l-1] != '\t') - break; - s[l-1] = '\0'; - } - - return (s); -} - -static int -expand_line_split(char **line, char **ret) -{ - static char buffer[MAX_LINE_SIZE]; - int esc, i, dq, sq; - char *s; - - bzero(buffer, sizeof buffer); - esc = dq = sq = i = 0; - for (s = *line; (*s) && (i < (int)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(const char *s) -{ - char buffer[MAX_LINE_SIZE]; - char *p, *subrcpt; - int ret; - - bzero(buffer, sizeof buffer); - if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) - return 0; - - p = buffer; - while ((ret = expand_line_split(&p, &subrcpt)) > 0) { - printf(" -> [%s]", subrcpt); - printf(" (%s)\n", strip(subrcpt)); - } - - if (ret >= 0) - return 1; - return 0; -} - -int -main(int argc, char **argv) -{ - FILE *fp; - char *line; - size_t len; - size_t lineno; - - fp = fopen(argv[1], "r"); - if (fp == NULL) - err(1, "fopen"); - - while ((line = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { - printf("=> [%s]\n", line); - if (! expand_line(line)) - printf("error!\n"); - free(line); - } - - return (0); -} diff --git a/regress/expand/test0 b/regress/expand/test0 deleted file mode 100644 index 456f6a67..00000000 --- a/regress/expand/test0 +++ /dev/null @@ -1,7 +0,0 @@ -# -/dev/null # test -/dev/null, eric, foo, bar, |/some/mda "test,comma" -a b \, \" \' d -a"bc'd", e'fg"h'i, "zer,tyu", 'qsd,fgh' -lsf qls mksq jmlq ksjmlsq kf -lskdjf ,,fsk ,foskfs,,,fsdf,s,f,sf diff --git a/regress/filters/Makefile b/regress/filters/Makefile deleted file mode 100644 index dcbb48d2..00000000 --- a/regress/filters/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -CFLAGS = -I../../smtpd/ -LDFLAGS = -L../../smtpd/libsmtpdfilter -lsmtpdfilter -levent -lutil - -SRCS = filter.c - -INSTALLPATH= /usr/libexec/smtpd - -all: - $(CC) -o $(INSTALLPATH)/filter $(CFLAGS) $(SRCS) $(LDFLAGS) diff --git a/regress/smtp/test.base b/regress/smtp/test.base deleted file mode 100755 index 02e76cdb..00000000 --- a/regress/smtp/test.base +++ /dev/null @@ -1,56 +0,0 @@ -#! /usr/bin/smtpscript -# -# This file is simply used to test the grammar and the behaviour of -# the scripting engine. Not SMTP related. -# - -test-case no-autoconnect { -} - -test-case no-autoconnect expect fail { - fail "on purpose" -} - -test-case no-autoconnect { - fail "ahaha! I failed" -} - -test-case no-autoconnect skip { - fail "should be skipped!" -} - -test-case no-autoconnect { - sleep 500 -} - -test-case no-autoconnect { - connect "localhost" port 25 -} - -proc proc0 %foo %bar { - sleep 1 - sleep 2 - sleep 3 -} - -proc proc1 { - noop -} - -test-case no-autoconnect { - - repeat 2 repeat 3 repeat 4 sleep 7 - sleep 8 - { - call proc0 - sleep 10 - } - - repeat 5 random { - call proc1 - { sleep 1 } - { sleep 2 } - } - - random {} -} diff --git a/regress/smtp/test.mailfrom b/regress/smtp/test.mailfrom deleted file mode 100644 index 0420f1c2..00000000 --- a/regress/smtp/test.mailfrom +++ /dev/null @@ -1,110 +0,0 @@ -proc init-helo { - expect smtp ok - writeln "HELO regress" - expect smtp helo -} - -# Allow emtpy MAIL FROM address (bounce messages) -test-case name "mailfrom.empty" { - call init-helo - writeln "MAIL FROM: <>" - expect smtp ok -} - -# Allow emtpy MAIL FROM address (bounce messages) -test-case name "mailfrom.at-sign" { - call init-helo - writeln "MAIL FROM: <@>" - expect smtp ok -} - -# Reject address without an '@' -test-case name "mailfrom.no-at-sign" { - call init-helo - writeln "MAIL FROM: <a>" - expect smtp permfail -} - -# Reject address with empty domain -test-case name "mailfrom.no-domain" { - call init-helo - writeln "MAIL FROM: <a@>" - expect smtp permfail -} - -# Reject address with empty user -test-case name "mailfrom.no-user" { - call init-helo - writeln "MAIL FROM: <@a>" - expect smtp permfail -} - -# Accept address after source-route stripping -test-case name "mailfrom.src-route" { - call init-helo - writeln "MAIL FROM: <whatever:a@a>" - expect smtp ok -} - -# Reject address with empty user after source-route stripping -test-case name "mailfrom.src-route-no-user" { - call init-helo - writeln "MAIL FROM: <a:@a>" - expect smtp permfail -} - -# Reject space as user -test-case name "mailfrom.space-as-user" { - call init-helo - writeln "MAIL FROM: < @a>" - expect smtp permfail -} - -# Reject space as domain -test-case name "mailfrom.space-as-domain" { - call init-helo - writeln "MAIL FROM: <a@ >" - expect smtp permfail -} - -# Reject unsupported options -test-case name "mailfrom.option" { - call init-helo - writeln "MAIL FROM: <user@domain> OPT" - expect smtp permfail -} - -# Allow SIZE=* -test-case name "mailfrom.option-size" { - call init-helo - writeln "MAIL FROM: <user@domain> SIZE=3005" - expect smtp ok -} - -# Allow BODY=7BIT -test-case name "mailfrom.option-7bit" { - call init-helo - writeln "MAIL FROM: <user@domain> BODY=7BIT" - expect smtp ok -} - -# Allow BODY=8BITMIME -test-case name "mailfrom.option-8bitmime" { - call init-helo - writeln "MAIL FROM: <user@domain> BODY=8BITMIME" - expect smtp ok -} - -# Allow multiple options -test-case name "mailfrom.option-8bitmime-size" { - call init-helo - writeln "MAIL FROM: <user@domain> BODY=8BITMIME SIZE=100" - expect smtp ok -} - -# Allow AUTH= (ignored) -test-case name "mailfrom.option-auth" { - call init-helo - writeln "MAIL FROM: <user@domain> AUTH=WHATEVER" - expect smtp ok -} diff --git a/regress/smtp/test.rcptto b/regress/smtp/test.rcptto deleted file mode 100644 index f6cfd888..00000000 --- a/regress/smtp/test.rcptto +++ /dev/null @@ -1,112 +0,0 @@ -proc init-helo-mailfrom { - expect smtp ok - writeln "HELO regress" - expect smtp helo - writeln "MAIL FROM: <>" - expect smtp ok -} - -# RCPT TO can't be empty -test-case name "rcptto.empty" { - call init-helo-mailfrom - writeln "RCPT TO: <>" - expect smtp permfail -} - -# RCPT TO can't be empty -test-case name "rcptto.at-sign" { - call init-helo-mailfrom - writeln "RCPT TO: <@>" - expect smtp permfail -} - -# Reject address without a '@' -test-case name "rcptto.no-at-sign" { - call init-helo-mailfrom - writeln "RCPT TO: <a>" - expect smtp permfail -} - -# Reject address with empty domain -test-case name "rcptto.no-domain" { - call init-helo-mailfrom - writeln "RCPT TO: <a@>" - expect smtp permfail -} - -# Reject address with empty user -test-case name "rcptto.no-user" { - call init-helo-mailfrom - writeln "RCPT TO: <@a>" - expect smtp permfail -} - -# Accept address after source-route stripping -test-case name "rcptto.src-route" { - call init-helo-mailfrom - writeln "RCPT TO: <whatever:a@a>" - expect smtp ok -} - -# Reject address with empty user after source-route stripping -test-case name "rcptto.src-route-no-user" { - call init-helo-mailfrom - writeln "RCPT TO: <a:@a>" - expect smtp permfail -} - -# Reject address with space as user -test-case name "rcptto.space-as-user" { - call init-helo-mailfrom - writeln "RCPT TO: < @a>" - expect smtp permfail -} - -# Reject address with space as domain -test-case name "rcptto.space-as-domain" { - call init-helo-mailfrom - writeln "RCPT TO: <a@ >" - expect smtp permfail -} - -# Reject options -test-case name "rcptto.option" { - call init-helo-mailfrom - writeln "RCPT TO: <user@domain> OPT" - expect smtp permfail -} - -# Reject options -test-case name "rcptto.option-size" { - call init-helo-mailfrom - writeln "RCPT TO: <user@domain> SIZE=3005" - expect smtp permfail -} - -# Reject options -test-case name "rcptto.option-7bit" { - call init-helo-mailfrom - writeln "RCPT TO: <user@domain> BODY=7BIT" - expect smtp permfail -} - -# Reject options -test-case name "rcptto.option-8bitmime" { - call init-helo-mailfrom - writeln "RCPT TO: <user@domain> BODY=8BITMIME" - expect smtp permfail -} - -# Reject options -test-case name "rcptto.option-8bitmime-size" { - call init-helo-mailfrom - writeln "RCPT TO: <user@domain> BODY=8BITMIME" - expect smtp permfail -} - -# Reject options -test-case name "rcptto.option-auth" { - call init-helo-mailfrom - writeln "RCPT TO: <user@domain> AUTH=WHATEVER" - expect smtp permfail -} diff --git a/regress/smtp/test.smtp0 b/regress/smtp/test.smtp0 deleted file mode 100755 index c56d002e..00000000 --- a/regress/smtp/test.smtp0 +++ /dev/null @@ -1,89 +0,0 @@ -#! /usr/bin/smtpscript -# -# Simple test cases for the SMTP server. -# - -# Make sure we get an smtp welcome message -test-case { - expect smtp -} - -# Make sure that the server accept QUIT and disconnect immediatly -test-case { - expect smtp - writeln "QUIT" - expect smtp ok - expect disconnect -} - -# Make sure that the server rejects an empty HELO -test-case { - expect smtp - writeln "HELO" - expect smtp permfail -} - -# Make sure that the server rejects an empty EHLO -test-case { - expect smtp - writeln "EHLO" - expect smtp permfail -} - -# Make sure that the server accepts HELO -test-case { - expect smtp - writeln "HELO myself" - expect smtp ok -} - -# Make sure that the server accepts EHLO -test-case { - expect smtp - writeln "EHLO myself" - expect smtp helo -} - -# Make sure that the server doesn't accept mail before HELO -test-case { - expect smtp - writeln "MAIL FROM: <me@localhost>" - expect smtp permfail -} - -# Simple mail transfer test, without pipelining -test-case { - expect smtp - writeln "EHLO fkf" - expect smtp helo - writeln "MAIL FROM: <test@blabla>" - expect smtp ok - writeln "RCPT TO: <gilles -@localhost>" - expect smtp ok -# writeln "DATA" -# expect smtp ok -# writeln "foo" -# writeln "." -# expect smtp ok -# writeln "QUIT" -# expect smtp -# expect disconnect -} - -# Simple mail transfer test, with pipelining -test-case { - expect smtp - writeln "EHLO fkf" - expect smtp helo - writeln "MAIL FROM: <test@blabla>" - writeln "RCPT TO: <test@localhost>" - writeln "DATA" - repeat 3 expect smtp ok - writeln "foo" - writeln "." - expect smtp ok - writeln "QUIT" - expect smtp - expect disconnect -} diff --git a/regress/smtp/test.smtp1 b/regress/smtp/test.smtp1 deleted file mode 100755 index 9d4d8c43..00000000 --- a/regress/smtp/test.smtp1 +++ /dev/null @@ -1,88 +0,0 @@ -#! /usr/bin/smtpscript -# -# Simple test cases for the SMTP server. -# - -# Make sure we get an smtp welcome message -test-case { - expect smtp -} - -# Make sure that the server accept QUIT and disconnect immediatly -test-case { - expect smtp - writeln "QUIT" - expect smtp ok - expect disconnect -} - -# Make sure that the server rejects an empty HELO -test-case { - expect smtp - writeln "HELO" - expect smtp permfail -} - -# Make sure that the server rejects an empty EHLO -test-case { - expect smtp - writeln "EHLO" - expect smtp permfail -} - -# Make sure that the server accepts HELO -test-case { - expect smtp - writeln "HELO myself" - expect smtp ok -} - -# Make sure that the server accepts EHLO -test-case { - expect smtp - writeln "EHLO myself" - expect smtp helo -} - -# Make sure that the server doesn't accept mail before HELO -test-case { - expect smtp - writeln "MAIL FROM: <me@localhost>" - expect smtp permfail -} - -# Simple mail transfer test, without pipelining -test-case { - expect smtp - writeln "EHLO fkf" - expect smtp helo - writeln "MAIL FROM: <test@blabla>" - expect smtp ok - writeln "RCPT TO: <gilles@localhost>" - expect smtp ok - writeln "DATA" - expect smtp ok - writeln "foo" - writeln "." - expect smtp ok - writeln "QUIT" - expect smtp - expect disconnect -} - -# Simple mail transfer test, with pipelining -test-case { - expect smtp - writeln "EHLO fkf" - expect smtp helo - writeln "MAIL FROM: <test@blabla>" - writeln "RCPT TO: <gilles@localhost>" - writeln "DATA" - repeat 3 expect smtp ok - writeln "foo" - writeln "." - expect smtp ok - writeln "QUIT" - expect smtp - expect disconnect -} diff --git a/regress/smtp/test.smtp2 b/regress/smtp/test.smtp2 deleted file mode 100755 index 1d5fb841..00000000 --- a/regress/smtp/test.smtp2 +++ /dev/null @@ -1,50 +0,0 @@ -#! /usr/bin/smtpscript -# -# Make sure that the SMTP limits are ok -# - -# Make sure the server disconnects on the 5th useless command -test-case { - expect smtp - repeat 4 { - writeln "FOO" - expect smtp permfail - } - writeln "FOO" - expect disconnect -} - -# 1000 RCPT per message -test-case { - expect smtp - writeln "EHLO fkf" - expect smtp helo - writeln "MAIL FROM: <test@blabla>" - expect smtp ok - repeat 1000 { - writeln "RCPT TO: <test@localhost>" - expect smtp ok - } - writeln "RCPT TO: <test@localhost>" - expect smtp tempfail -} - -# 100 messages per session -test-case { - expect smtp - writeln "EHLO fkf" - expect smtp helo - repeat 100 { - writeln "MAIL FROM: <test@blabla>" - expect smtp ok - writeln "RCPT TO: <test@foo.bar>" - expect smtp ok - writeln "DATA" - expect smtp ok - writeln "foo" - writeln "." - expect smtp ok - } - writeln "MAIL FROM: <test@blabla>" - expect smtp tempfail -} diff --git a/regress/smtp/test.smtp4 b/regress/smtp/test.smtp4 deleted file mode 100755 index a2c5299e..00000000 --- a/regress/smtp/test.smtp4 +++ /dev/null @@ -1,155 +0,0 @@ -#! /usr/bin/smtpscript -# -# Make sure that broken sessions are handled as they should -# - -test-case { - expect smtp - writeln "EHLO" - expect smtp permfail -} - -test-case { - expect smtp - writeln "HELO" - expect smtp permfail -} - -test-case { - expect smtp - writeln "HELO " - expect smtp permfail -} - -test-case { - expect smtp - writeln " HELO " - expect smtp permfail -} - -test-case { - expect smtp - writeln "HELO l" - expect smtp helo -} - - -test-case { - expect smtp - writeln "HELO l" - expect smtp ok -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "RCPT TO:" - expect smtp permfail -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "DATA" - expect smtp permfail -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM" - expect smtp permfail -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "DATA" - expect smtp permfail -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "RCPT TO:" - expect smtp permfail -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "RCPT TO:<root@localhost>" - expect smtp ok - writeln "RSET" - expect smtp ok - writeln "DATA" - expect smtp permfail -} - -test-case { - expect smtp - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "RCPT TO:<root@localhost>" - expect smtp ok - writeln "RSET" - expect smtp ok - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "DATA" - expect smtp permfail -} - -test-case { - expect smtp ok - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "RCPT TO:<root@localhost>" - expect smtp ok - writeln "RSET" - expect smtp ok - writeln "MAIL FROM:<opensmtpd@opensmtpd.org>" - expect smtp ok - writeln "RCPT TO:<root@localhost>" - expect smtp ok - writeln "DATA" - expect smtp ok -} - -test-case { - expect smtp ok - writeln "EHLO l" - expect smtp helo - writeln "MAIL FROM:<opensmtpd@opensmtpd.org> SIZE=1000" - expect smtp ok -} - -test-case { - expect smtp ok - writeln "HELO l" - expect smtp ok - writeln "MAIL FROM:<opensmtpd@opensmtpd.org> SIZE=1000" - expect smtp ok -} diff --git a/regress/smtp/test.tls b/regress/smtp/test.tls deleted file mode 100644 index f7ebdf19..00000000 --- a/regress/smtp/test.tls +++ /dev/null @@ -1,10 +0,0 @@ -test-case name "starttls" { - expect smtp ok - writeln "EHLO regress" - expect smtp helo - writeln "STARTTLS" - expect smtp ok - starttls - writeln "EHLO regress" - expect smtp helo -} diff --git a/smtpd/lka_proc.c b/smtpd/lka_proc.c deleted file mode 100644 index a49f007d..00000000 --- a/smtpd/lka_proc.c +++ /dev/null @@ -1,190 +0,0 @@ -/* $OpenBSD: lka_proc.c,v 1.12 2019/09/30 13:27:12 gilles Exp $ */ - -/* - * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> - * - * 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 <sys/types.h> -#include <sys/queue.h> -#include <sys/tree.h> -#include <sys/socket.h> - -#include <netinet/in.h> - -#include <errno.h> -#include <event.h> -#include <imsg.h> -#include <inttypes.h> -#include <limits.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "smtpd.h" -#include "log.h" - -static int inited = 0; -static struct dict processors; - -struct processor_instance { - char *name; - struct io *io; - struct io *errfd; - int ready; -}; - -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); - io_printf(pi->io, "config|ready\n"); -} - -void -lka_proc_forked(const char *name, int fd) -{ - struct processor_instance *processor; - - if (!inited) { - dict_init(&processors); - inited = 1; - } - - processor = xcalloc(1, sizeof *processor); - processor->name = xstrdup(name); - processor->io = io_new(); - - 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); - } -} diff --git a/smtpd/lka_report.c b/smtpd/lka_report.c deleted file mode 100644 index f28a8eeb..00000000 --- a/smtpd/lka_report.c +++ /dev/null @@ -1,511 +0,0 @@ -/* $OpenBSD: lka_report.c,v 1.34 2019/10/03 05:42:57 gilles Exp $ */ - -/* - * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> - * - * 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 <sys/types.h> -#include <sys/queue.h> -#include <sys/tree.h> -#include <sys/socket.h> - -#include <netinet/in.h> - -#include <errno.h> -#include <event.h> -#include <imsg.h> -#include <inttypes.h> -#include <limits.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "smtpd.h" -#include "log.h" - -#define PROTOCOL_VERSION "0.4" - -struct reporter_proc { - TAILQ_ENTRY(reporter_proc) entries; - const char *name; -}; -TAILQ_HEAD(reporters, reporter_proc); - -static struct dict smtp_in; -static struct dict 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 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(&smtp_in); - dict_init(&smtp_out); - - for (i = 0; i < nitems(smtp_events); ++i) { - tailq = xcalloc(1, sizeof (struct reporters)); - TAILQ_INIT(tailq); - dict_xset(&smtp_in, smtp_events[i].event, tailq); - - tailq = xcalloc(1, sizeof (struct reporters)); - TAILQ_INIT(tailq); - dict_xset(&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 = &smtp_in; - hook += 8; - } -#if 0 - /* No smtp-out event has been implemented yet */ - else if (strncmp(hook, "smtp-out|", 9) == 0) { - subsystem = &smtp_out; - hook += 9; - } -#endif - 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 = &smtp_in; - - else if (strcmp("smtp-out", direction) == 0) - d = &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) -{ - 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; - } - - 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) -{ - 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, address, result); -} - -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, address, result); -} - -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/smtpctl/CVS/Entries b/smtpd/smtpctl/CVS/Entries deleted file mode 100644 index 4c1dedd7..00000000 --- a/smtpd/smtpctl/CVS/Entries +++ /dev/null @@ -1,2 +0,0 @@ -/Makefile/1.46/Wed Apr 25 07:51:42 2018// -D diff --git a/smtpd/smtpctl/CVS/Repository b/smtpd/smtpctl/CVS/Repository deleted file mode 100644 index edc50474..00000000 --- a/smtpd/smtpctl/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -src/usr.sbin/smtpd/smtpctl diff --git a/smtpd/smtpctl/CVS/Root b/smtpd/smtpctl/CVS/Root deleted file mode 100644 index 8a89d1a7..00000000 --- a/smtpd/smtpctl/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -gilles@cvs.openbsd.org:/cvs diff --git a/smtpd/smtpd/CVS/Repository b/smtpd/smtpd/CVS/Repository deleted file mode 100644 index 11be251c..00000000 --- a/smtpd/smtpd/CVS/Repository +++ /dev/null @@ -1 +0,0 @@ -src/usr.sbin/smtpd/smtpd diff --git a/smtpd/smtpd/CVS/Root b/smtpd/smtpd/CVS/Root deleted file mode 100644 index 8a89d1a7..00000000 --- a/smtpd/smtpd/CVS/Root +++ /dev/null @@ -1 +0,0 @@ -gilles@cvs.openbsd.org:/cvs diff --git a/tests/certificate_test/smtpd.conf b/tests/certificate_test/smtpd.conf deleted file mode 100644 index 34887103..00000000 --- a/tests/certificate_test/smtpd.conf +++ /dev/null @@ -1,13 +0,0 @@ -pki_domain = "localhost" -pki $pki_domain cert "/etc/ssl/private/sites/fullchain.cer" -pki $pki_domain key "/etc/ssl/private/sites/site.key" - -#Encrypted password is "password" -table passwords {"user"="$6$tf940h4BpywpeKID$pWYiqoWywVPybeHaEcqHSRBD/7UxBmYhx7iHvxj/B3LBxCWwnFx7.3JwMISsN9EpPMwEZELvbNehVLl0IvvZo/"} - -listen on localhost tls hostname $pki_domain pki $pki_domain auth-optional <passwords> -listen on localhost port 465 smtps hostname $pki_domain pki $pki_domain auth-optional <passwords> -listen on localhost port 587 tls-require hostname $pki_domain pki $pki_domain auth <passwords> - -action "local" maildir "/tmp/" -match auth from any for any action "local" diff --git a/tests/certificate_test/test.sh b/tests/certificate_test/test.sh deleted file mode 100755 index 1eb50b40..00000000 --- a/tests/certificate_test/test.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -set -euxo pipefail -BASEDIR=$(dirname $0) - -# Setup TLS -mkdir -p /etc/ssl/private/sites/ -openssl genrsa -out /etc/ssl/private/sites/site.key 4096 -openssl req -new -x509 -key /etc/ssl/private/sites/site.key -out /etc/ssl/private/sites/fullchain.cer -subj "/CN='localhost'" -chmod 600 /etc/ssl/private/sites/site.key -chmod 644 /etc/ssl/private/sites/fullchain.cer - -smtpd -dv -f "$BASEDIR/smtpd.conf" & - -#Wait for smtpd to be ready to receive connections -sleep 3 - -#OpenSSL is crazy and will treat a capital "R" or "Q" as a command without the -quiet flag -#OpenSMTPD doesn't support pipelining, so wait 0.1 seconds between lines -awk '{print $0; system("sleep .1");}' "$BASEDIR/../test_email.txt" | \ - openssl s_client -quiet -connect localhost:25 -starttls smtp diff --git a/tests/test_all.sh b/tests/test_all.sh deleted file mode 100755 index ca619479..00000000 --- a/tests/test_all.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -euxo pipefail -BASEDIR=$(dirname $0) - -echo "Testing TLS" -"$BASEDIR/certificate_test/test.sh" diff --git a/tests/test_email.txt b/tests/test_email.txt deleted file mode 100644 index 4fd3acfd..00000000 --- a/tests/test_email.txt +++ /dev/null @@ -1,13 +0,0 @@ -HELO localhost -AUTH LOGIN -dXNlcg== -cGFzc3dvcmQ= -MAIL FROM:<_smtpd@localhost> -RCPT TO:<_smtpd@localhost> -DATA -Subject: Test Email - -It works - -. -QUIT diff --git a/smtpd/Makefile b/usr.sbin/smtpd/Makefile index a3dbc9d1..a3dbc9d1 100644 --- a/smtpd/Makefile +++ b/usr.sbin/smtpd/Makefile diff --git a/smtpd/aliases.5 b/usr.sbin/smtpd/aliases.5 index 802ca9a6..7c250c81 100644 --- a/smtpd/aliases.5 +++ b/usr.sbin/smtpd/aliases.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: aliases.5,v 1.15 2018/05/24 11:38:24 gilles Exp $ +.\" $OpenBSD: aliases.5,v 1.16 2020/04/23 21:28:10 jmc Exp $ .\" .\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> .\" @@ -14,7 +14,7 @@ .\" 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 24 2018 $ +.Dd $Mdocdate: April 23 2020 $ .Dt ALIASES 5 .Os .Sh NAME @@ -77,7 +77,7 @@ will only use the part that precedes .Sq + as a .Em key . -.It Ar error : Ns Ar code Ar message +.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). diff --git a/smtpd/aliases.c b/usr.sbin/smtpd/aliases.c index cd47d684..0f8a5c1e 100644 --- a/smtpd/aliases.c +++ b/usr.sbin/smtpd/aliases.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aliases.c,v 1.77 2018/12/28 12:47:28 eric Exp $ */ +/* $OpenBSD: aliases.c,v 1.78 2020/04/28 21:46:43 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -171,6 +171,10 @@ aliases_virtual_get(struct expand *expand, const struct mailaddr *maddr) 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 */ diff --git a/smtpd/bounce.c b/usr.sbin/smtpd/bounce.c index 41526d8d..4a4a0992 100644 --- a/smtpd/bounce.c +++ b/usr.sbin/smtpd/bounce.c @@ -1,4 +1,4 @@ -/* $OpenBSD: bounce.c,v 1.80 2018/12/08 08:01:15 sunil Exp $ */ +/* $OpenBSD: bounce.c,v 1.82 2020/04/24 11:34:07 eric Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> @@ -186,7 +186,7 @@ bounce_add(uint64_t evpid) line = evp.errorline; if (strlen(line) > 4 && (*line == '1' || *line == '6')) line += 4; - (void)snprintf(buf, sizeof(buf), "%s@%s: %s\n", evp.dest.user, + (void)snprintf(buf, sizeof(buf), "%s@%s: %s", evp.dest.user, evp.dest.domain, line); be = xmalloc(sizeof *be); @@ -198,8 +198,7 @@ bounce_add(uint64_t evpid) be->esc_class = evp.esc_class; be->esc_code = evp.esc_code; TAILQ_INSERT_TAIL(&msg->envelopes, be, entry); - buf[strcspn(buf, "\n")] = '\0'; - log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, buf); + log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, be->report); msg->timeout = time(NULL) + 1; TAILQ_INSERT_TAIL(&pending, msg, entry); @@ -313,7 +312,7 @@ bounce_send(struct bounce_session *s, const char *fmt, ...) log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", s, p); - io_xprintf(s->io, "%s\n", p); + io_xprintf(s->io, "%s\r\n", p); free(p); } @@ -345,27 +344,27 @@ bounce_duration(long long int d) } #define NOTICE_INTRO \ - " Hi!\n\n" \ - " This is the MAILER-DAEMON, please DO NOT REPLY to this email.\n" + " 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\n" - " the following list of recipients:\n\n"; + " 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\n" - " list of recipients:\n\n"; + " 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.\n" - " The message is kept in the queue for up to %s.\n" - " You DO NOT NEED to re-send the message to these recipients.\n\n"; + " 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.\n\n"; + " Your message was successfully delivered to these recipients.\r\n\r\n"; const char *notice_relay = - " Your message was relayed to these recipients.\n\n"; + " Your message was relayed to these recipients.\r\n\r\n"; static int bounce_next_message(struct bounce_session *s) @@ -453,16 +452,16 @@ bounce_next(struct bounce_session *s) /* Construct an appropriate notice. */ io_xprintf(s->io, - "Subject: Delivery status notification: %s\n" - "From: Mailer Daemon <MAILER-DAEMON@%s>\n" - "To: %s\n" - "Date: %s\n" - "MIME-Version: 1.0\n" + "Subject: Delivery status notification: %s\r\n" + "From: Mailer Daemon <MAILER-DAEMON@%s>\r\n" + "To: %s\r\n" + "Date: %s\r\n" + "MIME-Version: 1.0\r\n" "Content-Type: multipart/mixed;" - "boundary=\"%16" PRIu64 "/%s\"\n" - "\n" - "This is a MIME-encapsulated message.\n" - "\n", + "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, @@ -471,12 +470,12 @@ bounce_next(struct bounce_session *s) s->smtpname); io_xprintf(s->io, - "--%16" PRIu64 "/%s\n" - "Content-Description: Notification\n" - "Content-Type: text/plain; charset=us-ascii\n" - "\n" + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Notification\r\n" + "Content-Type: text/plain; charset=us-ascii\r\n" + "\r\n" NOTICE_INTRO - "\n", + "\r\n", s->boundary, s->smtpname); switch (s->msg->bounce.type) { @@ -497,35 +496,36 @@ bounce_next(struct bounce_session *s) TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { io_xprint(s->io, evp->report); + io_xprint(s->io, "\r\n"); } - io_xprint(s->io, "\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:\n" - "\n"); + " Below is a copy of the original message:\r\n" + "\r\n"); io_xprintf(s->io, - "--%16" PRIu64 "/%s\n" - "Content-Description: Delivery Report\n" - "Content-Type: message/delivery-status\n" - "\n", + "--%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\n" - "\n", + "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\n" - "Action: %s\n" - "Status: %s\n" - "\n", + "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), @@ -540,10 +540,10 @@ bounce_next(struct bounce_session *s) case BOUNCE_DATA_MESSAGE: io_xprintf(s->io, - "--%16" PRIu64 "/%s\n" - "Content-Description: Message headers\n" - "Content-Type: text/rfc822-headers\n" - "\n", + "--%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); @@ -557,14 +557,14 @@ bounce_next(struct bounce_session *s) fclose(s->msgfp); s->msgfp = NULL; io_xprintf(s->io, - "\n--%16" PRIu64 "/%s--\n", s->boundary, + "\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\n", + io_xprintf(s->io, "%s%s\r\n", (len == 2 && line[0] == '.') ? "." : "", line); } free(line); @@ -579,7 +579,7 @@ bounce_next(struct bounce_session *s) } io_xprintf(s->io, - "\n--%16" PRIu64 "/%s--\n", s->boundary, s->smtpname); + "\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); @@ -730,6 +730,10 @@ bounce_io(struct io *io, int evt, void *arg) 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))) { diff --git a/smtpd/ca.c b/usr.sbin/smtpd/ca.c index a27db87a..b36033b4 100644 --- a/smtpd/ca.c +++ b/usr.sbin/smtpd/ca.c @@ -65,13 +65,11 @@ 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; @@ -230,17 +228,13 @@ 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; @@ -313,7 +307,6 @@ ca_imsg(struct mproc *p, struct imsg *imsg) RSA_free(rsa); return; -#if defined(SUPPORT_ECDSA) case IMSG_CA_ECDSA_SIGN: m_msg(&m, imsg); m_get_id(&m, &id); @@ -329,6 +322,7 @@ ca_imsg(struct mproc *p, struct imsg *imsg) 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); @@ -339,7 +333,6 @@ ca_imsg(struct mproc *p, struct imsg *imsg) free(to); EC_KEY_free(ecdsa); return; -#endif } errx(1, "ca_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); } @@ -510,11 +503,11 @@ rsae_keygen(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) } -#if defined(SUPPORT_ECDSA) /* * ECDSA privsep engine (called from unprivileged processes) */ +#if defined(SUPPORT_ECDSA) const ECDSA_METHOD *ecdsa_default = NULL; static ECDSA_METHOD *ecdsae_method = NULL; @@ -538,6 +531,11 @@ ECDSA_METHOD_new_temporary(const char *name, int flags) ecdsa->flags = flags; return ecdsa; } +#else +const EC_KEY_METHOD *ecdsa_default = NULL; + +static EC_KEY_METHOD *ecdsae_method = NULL; +#endif static ECDSA_SIG * ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, @@ -554,8 +552,13 @@ ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, uint64_t id; ECDSA_SIG *sig = NULL; +#if defined(SUPPORT_ECDSA) if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL) return (0); +#else + if ((pkiname = EC_KEY_get_ex_data(eckey, 0)) == NULL) + return (0); +#endif /* * Send a synchronous imsg because we cannot defer the ECDSA @@ -613,6 +616,7 @@ ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, return (sig); } +#if defined(SUPPORT_ECDSA) ECDSA_SIG * ecdsae_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) @@ -621,6 +625,7 @@ ecdsae_do_sign(const unsigned char *dgst, int dgst_len, 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 @@ -638,6 +643,114 @@ ecdsae_do_verify(const unsigned char *dgst, int dgst_len, log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); return (ecdsa_default->ecdsa_do_verify(dgst, dgst_len, sig, eckey)); } +#else +int +ecdsae_keygen(EC_KEY *eckey) +{ + int (*keygen)(EC_KEY *key); + EC_KEY_METHOD_get_keygen(ecdsa_default, + &keygen); + return keygen(eckey); +} + +int +ecdsae_compute_key(unsigned char **psec, size_t *pseclen, + const EC_POINT *pub_key, const EC_KEY *ecdh) +{ + int (*ckey)(unsigned char **psec, + size_t *pseclen, + const EC_POINT *pub_key, + const EC_KEY *ecdh); + EC_KEY_METHOD_get_compute_key(ecdsa_default, + &ckey); + return ckey(psec, pseclen, pub_key, ecdh); +} + +int +ecdsae_sign(int type, const unsigned char *dgst, int dlen, unsigned char *sig, + unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey) +{ + int (*sign)(int type, const unsigned char *dgst, + int dlen, unsigned char *sig, + unsigned int *siglen, + const BIGNUM *kinv, const BIGNUM *r, + EC_KEY *eckey); + + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + EC_KEY_METHOD_get_sign(ecdsa_default, + &sign, + NULL, + NULL); + return (sign(type, dgst, dlen, sig, siglen, kinv, r, eckey)); +} + + +ECDSA_SIG * +ecdsae_do_sign(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + ECDSA_SIG *(*psign_sig)(const unsigned char *dgst, + int dgst_len, + const BIGNUM *in_kinv, + const BIGNUM *in_r, + EC_KEY *eckey); + + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (EC_KEY_get_ex_data(eckey, 0) != NULL) + return (ecdsae_send_enc_imsg(dgst, dgst_len, inv, rp, eckey)); + EC_KEY_METHOD_get_sign(ecdsa_default, + NULL, + NULL, + &psign_sig); + return (psign_sig(dgst, dgst_len, inv, rp, eckey)); +} + +int +ecdsae_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, + BIGNUM **r) +{ + int (*psign_setup)(EC_KEY *eckey, BN_CTX *ctx_in, + BIGNUM **kinvp, BIGNUM **rp); + + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + EC_KEY_METHOD_get_sign(ecdsa_default, + NULL, + &psign_setup, + NULL); + return (psign_setup(eckey, ctx, kinv, r)); +} + +int +ecdsae_verify(int type, const unsigned char *dgst, int dgst_len, + const unsigned char *sigbuf, int sig_len, EC_KEY *eckey) +{ + int (*verify)(int type, const unsigned + char *dgst, int dgst_len, + const unsigned char *sigbuf, + int sig_len, EC_KEY *eckey); + + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + EC_KEY_METHOD_get_verify(ecdsa_default, + &verify, + NULL); + return (verify(type, dgst, dgst_len, sigbuf, sig_len, eckey)); +} + +int +ecdsae_do_verify(const unsigned char *dgst, int dgst_len, + const ECDSA_SIG *sig, EC_KEY *eckey) +{ + int (*pverify_sig)(const unsigned char *dgst, + int dgst_len, + const ECDSA_SIG *sig, + EC_KEY *eckey); + + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + EC_KEY_METHOD_get_verify(ecdsa_default, + NULL, + &pverify_sig); + return (pverify_sig(dgst, dgst_len, sig, eckey)); +} #endif static void @@ -765,13 +878,66 @@ ecdsa_engine_init(void) ssl_error(errstr); fatalx("%s", errstr); } +#else +static void +ecdsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((ecdsae_method = EC_KEY_METHOD_new(NULL)) == NULL) { + errstr = "EC_KEY_new"; + goto fail; + } + + EC_KEY_METHOD_set_keygen(ecdsae_method, ecdsae_keygen); + EC_KEY_METHOD_set_compute_key(ecdsae_method, ecdsae_compute_key); + EC_KEY_METHOD_set_sign(ecdsae_method, ecdsae_sign, ecdsae_sign_setup, + ecdsae_do_sign); + EC_KEY_METHOD_set_verify(ecdsae_method, ecdsae_verify, ecdsae_do_verify); + + if ((e = ENGINE_get_default_EC()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, "ECDSA privsep engine")) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((ecdsa_default = EC_KEY_get_default_method()) == NULL) { + errstr = "EC_KEY_get_default_method"; + goto fail; + } + } else if ((ecdsa_default = ENGINE_get_EC(e)) == NULL) { + errstr = "ENGINE_get_EC"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown ECDSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (!ENGINE_set_EC(e, ecdsae_method)) { + errstr = "ENGINE_set_EC"; + goto fail; + } + if (!ENGINE_set_default_EC(e)) { + errstr = "ENGINE_set_default_EC"; + 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/usr.sbin/smtpd/cert.c index 79b1df91..79b1df91 100644 --- a/smtpd/cert.c +++ b/usr.sbin/smtpd/cert.c diff --git a/smtpd/compress_backend.c b/usr.sbin/smtpd/compress_backend.c index 1b974662..1b974662 100644 --- a/smtpd/compress_backend.c +++ b/usr.sbin/smtpd/compress_backend.c diff --git a/smtpd/compress_gzip.c b/usr.sbin/smtpd/compress_gzip.c index dd60aeec..dd60aeec 100644 --- a/smtpd/compress_gzip.c +++ b/usr.sbin/smtpd/compress_gzip.c diff --git a/smtpd/config.c b/usr.sbin/smtpd/config.c index 20743527..8fe983d6 100644 --- a/smtpd/config.c +++ b/usr.sbin/smtpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.50 2019/09/20 17:46:05 gilles Exp $ */ +/* $OpenBSD: config.c,v 1.51 2019/12/18 10:00:39 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -91,7 +91,7 @@ config_default(void) 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_processors_dict = calloc(1, sizeof(*conf->sc_processors_dict)); + 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)); @@ -105,7 +105,7 @@ config_default(void) conf->sc_ssl_dict == NULL || conf->sc_limits_dict == NULL || conf->sc_mda_wrappers == NULL || - conf->sc_processors_dict == NULL || + conf->sc_filter_processes_dict == NULL || conf->sc_dispatcher_bounce == NULL || conf->sc_filters_dict == NULL || limits == NULL) @@ -118,7 +118,7 @@ config_default(void) dict_init(conf->sc_ssl_dict); dict_init(conf->sc_tables_dict); dict_init(conf->sc_limits_dict); - dict_init(conf->sc_processors_dict); + dict_init(conf->sc_filter_processes_dict); limit_mta_set_defaults(limits); @@ -157,7 +157,7 @@ error: free(conf->sc_ssl_dict); free(conf->sc_limits_dict); free(conf->sc_mda_wrappers); - free(conf->sc_processors_dict); + free(conf->sc_filter_processes_dict); free(conf->sc_dispatcher_bounce); free(conf->sc_filters_dict); free(limits); diff --git a/smtpd/control.c b/usr.sbin/smtpd/control.c index 0e35bbd1..dbb2840d 100644 --- a/smtpd/control.c +++ b/usr.sbin/smtpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.123 2018/05/31 21:06:12 gilles Exp $ */ +/* $OpenBSD: control.c,v 1.125 2020/09/23 19:11:50 martijn Exp $ */ /* * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> diff --git a/smtpd/crypto.c b/usr.sbin/smtpd/crypto.c index 20a422cd..20a422cd 100644 --- a/smtpd/crypto.c +++ b/usr.sbin/smtpd/crypto.c diff --git a/smtpd/dict.c b/usr.sbin/smtpd/dict.c index e660f0a5..e660f0a5 100644 --- a/smtpd/dict.c +++ b/usr.sbin/smtpd/dict.c diff --git a/smtpd/dict.h b/usr.sbin/smtpd/dict.h index c5d47e1a..c5d47e1a 100644 --- a/smtpd/dict.h +++ b/usr.sbin/smtpd/dict.h diff --git a/smtpd/dns.c b/usr.sbin/smtpd/dns.c index a3107e89..a3107e89 100644 --- a/smtpd/dns.c +++ b/usr.sbin/smtpd/dns.c diff --git a/smtpd/enqueue.c b/usr.sbin/smtpd/enqueue.c index 905ba585..0ef694b5 100644 --- a/smtpd/enqueue.c +++ b/usr.sbin/smtpd/enqueue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: enqueue.c,v 1.116 2019/07/02 09:36:20 martijn Exp $ */ +/* $OpenBSD: enqueue.c,v 1.118 2020/03/18 20:17:14 eric Exp $ */ /* * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> @@ -136,32 +136,51 @@ struct { 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 len) +qp_encoded_write(FILE *fp, char *buf) { - while (len) { - if (*buf == '=') - fprintf(fp, "=3D"); - else if (*buf == ' ' || *buf == '\t') { - char *p = buf; - - while (*p != '\n') { - if (*p != ' ' && *p != '\t') - break; - p++; - } - if (*p == '\n') + 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 + } else { + QP_TEST_WRAP(fp, buf, linelen, 1); fprintf(fp, "%c", *buf & 0xff); - } - else if (!isprint((unsigned char)*buf) && *buf != '\n') + } + /* + * 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); - else + /* Point 2: 33 through 60 inclusive, and 62 through 126 */ + } else { + QP_TEST_WRAP(fp, buf, linelen, 1); fprintf(fp, "%c", *buf); - buf++; - len--; + } } + fprintf(fp, "\r\n"); } int @@ -174,7 +193,6 @@ enqueue(int argc, char *argv[], FILE *ofp) size_t sz = 0, envid_sz = 0; ssize_t len; char *line; - int dotted; int inheaders = 1; int save_argc; char **save_argv; @@ -312,7 +330,7 @@ enqueue(int argc, char *argv[], FILE *ofp) if (!get_responses(fout, 1)) goto fail; - if (!send_line(fout, verbose, "EHLO localhost\n")) + if (!send_line(fout, verbose, "EHLO localhost\r\n")) goto fail; if (!get_responses(fout, 1)) goto fail; @@ -320,7 +338,7 @@ enqueue(int argc, char *argv[], FILE *ofp) if (msg.dsn_envid != NULL) envid_sz = strlen(msg.dsn_envid); - if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\n", + 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 : "", @@ -331,7 +349,7 @@ enqueue(int argc, char *argv[], FILE *ofp) goto fail; for (i = 0; i < msg.rcpt_cnt; i++) { - if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\n", + 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 : "")) @@ -340,41 +358,41 @@ enqueue(int argc, char *argv[], FILE *ofp) goto fail; } - if (!send_line(fout, verbose, "DATA\n")) + 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>\n", + 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\n", + 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\n")) + "MIME-Version: 1.0\r\n")) goto fail; if (!msg.saw_content_type && !send_line(fout, 0, - "Content-Type: text/plain; charset=unknown-8bit\n")) + "Content-Type: text/plain; charset=unknown-8bit\r\n")) goto fail; if (!msg.saw_content_disposition && !send_line(fout, 0, - "Content-Disposition: inline\n")) + "Content-Disposition: inline\r\n")) goto fail; if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, - "Content-Transfer-Encoding: quoted-printable\n")) + "Content-Transfer-Encoding: quoted-printable\r\n")) goto fail; } /* add separating newline */ if (msg.noheader) { - if (!send_line(fout, 0, "\n")) + if (!send_line(fout, 0, "\r\n")) goto fail; inheaders = 0; } @@ -390,12 +408,11 @@ enqueue(int argc, char *argv[], FILE *ofp) /* newlines have been normalized on first parsing */ if (buf[len-1] != '\n') errx(EX_SOFTWARE, "expect EOL"); + len--; - dotted = 0; if (buf[0] == '.') { if (fputc('.', fout) == EOF) goto fail; - dotted = 1; } line = buf; @@ -409,7 +426,7 @@ enqueue(int argc, char *argv[], FILE *ofp) if (msg.saw_content_transfer_encoding || msg.noheader || inheaders || !msg.need_linesplit) { - if (!send_line(fout, 0, "%.*s", (int)len, line)) + if (!send_line(fout, 0, "%.*s\r\n", (int)len, line)) goto fail; if (inheaders && buf[0] == '\n') inheaders = 0; @@ -417,28 +434,15 @@ enqueue(int argc, char *argv[], FILE *ofp) } /* we don't have a content transfer encoding, use our default */ - do { - if (len < LINESPLIT) { - qp_encoded_write(fout, line, len); - break; - } - else { - qp_encoded_write(fout, line, - LINESPLIT - 2 - dotted); - if (!send_line(fout, 0, "=\n")) - goto fail; - line += LINESPLIT - 2 - dotted; - len -= LINESPLIT - 2 - dotted; - } - } while (len); + qp_encoded_write(fout, line); } free(buf); - if (!send_line(fout, verbose, ".\n")) + if (!send_line(fout, verbose, ".\r\n")) goto fail; if (!get_responses(fout, 1)) goto fail; - if (!send_line(fout, verbose, "QUIT\n")) + if (!send_line(fout, verbose, "QUIT\r\n")) goto fail; if (!get_responses(fout, 1)) goto fail; diff --git a/smtpd/envelope.c b/usr.sbin/smtpd/envelope.c index c02a0e75..35d98b79 100644 --- a/smtpd/envelope.c +++ b/usr.sbin/smtpd/envelope.c @@ -1,4 +1,4 @@ -/* $OpenBSD: envelope.c,v 1.46 2019/09/19 16:00:59 gilles Exp $ */ +/* $OpenBSD: envelope.c,v 1.47 2019/11/25 14:18:32 gilles Exp $ */ /* * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> @@ -179,6 +179,7 @@ envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) 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"); @@ -408,6 +409,9 @@ ascii_load_field(const char *field, struct envelope *ep, char *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); @@ -654,6 +658,12 @@ ascii_dump_field(const char *field, const struct envelope *ep, 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); diff --git a/smtpd/esc.c b/usr.sbin/smtpd/esc.c index 64a44c79..64a44c79 100644 --- a/smtpd/esc.c +++ b/usr.sbin/smtpd/esc.c diff --git a/smtpd/expand.c b/usr.sbin/smtpd/expand.c index a4306fc0..a4306fc0 100644 --- a/smtpd/expand.c +++ b/usr.sbin/smtpd/expand.c diff --git a/smtpd/filter.c b/usr.sbin/smtpd/filter.c index 614486b7..614486b7 100644 --- a/smtpd/filter.c +++ b/usr.sbin/smtpd/filter.c diff --git a/smtpd/forward.5 b/usr.sbin/smtpd/forward.5 index 5a68f229..5a68f229 100644 --- a/smtpd/forward.5 +++ b/usr.sbin/smtpd/forward.5 diff --git a/smtpd/forward.c b/usr.sbin/smtpd/forward.c index 7494c6ce..7494c6ce 100644 --- a/smtpd/forward.c +++ b/usr.sbin/smtpd/forward.c diff --git a/smtpd/iobuf.c b/usr.sbin/smtpd/iobuf.c index ceb2ac01..dec10660 100644 --- a/smtpd/iobuf.c +++ b/usr.sbin/smtpd/iobuf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: iobuf.c,v 1.12 2019/10/03 07:03:23 gilles Exp $ */ +/* $OpenBSD: iobuf.c,v 1.13 2020/04/24 11:34:07 eric Exp $ */ /* * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> * @@ -176,10 +176,9 @@ iobuf_getline(struct iobuf *iobuf, size_t *rlen) * the next call to iobuf_normalize() or iobuf_extend(). */ iobuf_drop(iobuf, i + 1); - len = (i && buf[i - 1] == '\r') ? i - 1 : i; - buf[len] = '\0'; + buf[i] = '\0'; if (rlen) - *rlen = len; + *rlen = i; return (buf); } diff --git a/smtpd/iobuf.h b/usr.sbin/smtpd/iobuf.h index c454d0a1..c454d0a1 100644 --- a/smtpd/iobuf.h +++ b/usr.sbin/smtpd/iobuf.h diff --git a/smtpd/ioev.c b/usr.sbin/smtpd/ioev.c index e0a8a096..e0a8a096 100644 --- a/smtpd/ioev.c +++ b/usr.sbin/smtpd/ioev.c diff --git a/smtpd/ioev.h b/usr.sbin/smtpd/ioev.h index f155a7fc..f155a7fc 100644 --- a/smtpd/ioev.h +++ b/usr.sbin/smtpd/ioev.h diff --git a/smtpd/libressl.c b/usr.sbin/smtpd/libressl.c index 57d74389..57d74389 100644 --- a/smtpd/libressl.c +++ b/usr.sbin/smtpd/libressl.c diff --git a/smtpd/limit.c b/usr.sbin/smtpd/limit.c index 25e7a026..25e7a026 100644 --- a/smtpd/limit.c +++ b/usr.sbin/smtpd/limit.c diff --git a/smtpd/lka.c b/usr.sbin/smtpd/lka.c index 6d1d3cf3..6ac21245 100644 --- a/smtpd/lka.c +++ b/usr.sbin/smtpd/lka.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka.c,v 1.240 2019/08/28 15:50:36 martijn Exp $ */ +/* $OpenBSD: lka.c,v 1.243 2019/12/21 10:23:37 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -98,6 +98,7 @@ lka_imsg(struct mproc *p, struct imsg *imsg) int filter_phase; const char *filter_param; uint32_t msgid; + uint32_t subsystems; uint64_t evpid; size_t msgsz; int ok; @@ -278,6 +279,7 @@ lka_imsg(struct mproc *p, struct imsg *imsg) 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); @@ -291,7 +293,11 @@ lka_imsg(struct mproc *p, struct imsg *imsg) m_add_int(p, LKA_TEMPFAIL); } else { - ret = table_fetch(table, K_RELAYHOST, &lk); + 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) @@ -368,13 +374,14 @@ lka_imsg(struct mproc *p, struct imsg *imsg) 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, imsg->fd); + lka_proc_forked(procname, subsystems, imsg->fd); return; case IMSG_LKA_PROCESSOR_ERRFD: @@ -611,13 +618,9 @@ lka_imsg(struct mproc *p, struct imsg *imsg) m_msg(&m, imsg); m_get_id(&m, &reqid); m_get_string(&m, &filter_name); - m_get_sockaddr(&m, (struct sockaddr *)&ss_src); - m_get_sockaddr(&m, (struct sockaddr *)&ss_dest); - m_get_string(&m, &rdns); - m_get_int(&m, &fcrdns); m_end(&m); - lka_filter_begin(reqid, filter_name, &ss_src, &ss_dest, rdns, fcrdns); + lka_filter_begin(reqid, filter_name); return; case IMSG_FILTER_SMTP_END: diff --git a/smtpd/lka_filter.c b/usr.sbin/smtpd/lka_filter.c index 056d9e8f..7931ede4 100644 --- a/smtpd/lka_filter.c +++ b/usr.sbin/smtpd/lka_filter.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lka_filter.c,v 1.50 2019/09/11 20:06:26 gilles Exp $ */ +/* $OpenBSD: lka_filter.c,v 1.63 2020/09/16 11:19:42 martijn Exp $ */ /* * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> @@ -37,7 +37,7 @@ #include "smtpd.h" #include "log.h" -#define PROTOCOL_VERSION "0.4" +#define PROTOCOL_VERSION "0.6" struct filter; struct filter_session; @@ -56,6 +56,8 @@ static int filter_builtins_connect(struct filter_session *, struct filter *, uin 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); @@ -80,6 +82,7 @@ struct filter_session { int fcrdns; char *helo; + char *username; char *mail_from; enum filter_phase phase; @@ -97,14 +100,14 @@ static struct filter_exec { { 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_notimpl }, + { 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_notimpl }, + { FILTER_COMMIT, "commit", filter_builtins_commit }, }; struct filter { @@ -128,13 +131,210 @@ struct filter_chain { TAILQ_HEAD(, filter_entry) chain[nitems(filter_execs)]; }; -static struct dict smtp_in; +static struct dict filter_smtp_in; static struct tree sessions; -static int inited; +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|admd|%s\n", + env->sc_admd != NULL ? env->sc_admd : env->sc_hostname); + 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) { @@ -217,7 +417,7 @@ lka_filter_register_hook(const char *name, const char *hook) size_t i; if (strncasecmp(hook, "smtp-in|", 8) == 0) { - subsystem = &smtp_in; + subsystem = &filter_smtp_in; hook += 8; } else @@ -294,7 +494,7 @@ lka_filter_proc_in_session(uint64_t reqid, const char *proc) return 0; filter = dict_get(&filters, fs->filter_name); - if (filter->proc == NULL && filter->chain == NULL) + if (filter == NULL || (filter->proc == NULL && filter->chain == NULL)) return 0; if (filter->proc) @@ -309,27 +509,18 @@ lka_filter_proc_in_session(uint64_t reqid, const char *proc) } void -lka_filter_begin(uint64_t reqid, - const char *filter_name, - const struct sockaddr_storage *ss_src, - const struct sockaddr_storage *ss_dest, - const char *rdns, - int fcrdns) +lka_filter_begin(uint64_t reqid, const char *filter_name) { struct filter_session *fs; - if (!inited) { + if (!filters_inited) { tree_init(&sessions); - inited = 1; + filters_inited = 1; } fs = xcalloc(1, sizeof (struct filter_session)); fs->id = reqid; fs->filter_name = xstrdup(filter_name); - fs->ss_src = *ss_src; - fs->ss_dest = *ss_dest; - fs->rdns = xstrdup(rdns); - fs->fcrdns = fcrdns; tree_xset(&sessions, fs->id, fs); log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-begin", reqid); @@ -344,6 +535,7 @@ lka_filter_end(uint64_t 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); @@ -443,22 +635,22 @@ lka_filter_process_response(const char *name, const char *line) fatalx("Missing reqid: %s", line); ep[0] = '\0'; - token = strtoull(qid, &ep, 16); + reqid = 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); + 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'; - reqid = strtoull(qid, &ep, 16); + token = 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); + fatalx("Invalid token: %s", line); + if (errno == ERANGE && token == ULLONG_MAX) + fatal("Invalid token: %s", line); response = ep+1; @@ -606,6 +798,14 @@ filter_protocol_internal(struct filter_session *fs, uint64_t *token, uint64_t re 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", @@ -655,7 +855,7 @@ filter_data_internal(struct filter_session *fs, uint64_t token, uint64_t reqid, /* no filter_entry, we either had none or reached end of chain */ if (filter_entry == NULL) { - io_printf(fs->io, "%s\r\n", line); + io_printf(fs->io, "%s\n", line); return; } @@ -692,7 +892,6 @@ filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) param = nparam; break; case FILTER_STARTTLS: - case FILTER_AUTH: /* TBD */ break; default: @@ -916,6 +1115,47 @@ filter_check_helo_regex(struct filter *filter, const char *key) } 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; @@ -1017,6 +1257,10 @@ filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_ 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); } @@ -1046,3 +1290,459 @@ filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64 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/usr.sbin/smtpd/lka_session.c index 999e01d6..999e01d6 100644 --- a/smtpd/lka_session.c +++ b/usr.sbin/smtpd/lka_session.c diff --git a/smtpd/log.c b/usr.sbin/smtpd/log.c index 14f681e3..14f681e3 100644 --- a/smtpd/log.c +++ b/usr.sbin/smtpd/log.c diff --git a/smtpd/log.h b/usr.sbin/smtpd/log.h index 81d0973c..81d0973c 100644 --- a/smtpd/log.h +++ b/usr.sbin/smtpd/log.h diff --git a/smtpd/mail.lmtp.8 b/usr.sbin/smtpd/mail.lmtp.8 index 98dee00d..98dee00d 100644 --- a/smtpd/mail.lmtp.8 +++ b/usr.sbin/smtpd/mail.lmtp.8 diff --git a/smtpd/mail.lmtp.c b/usr.sbin/smtpd/mail.lmtp.c index f24549ad..90b89990 100644 --- a/smtpd/mail.lmtp.c +++ b/usr.sbin/smtpd/mail.lmtp.c @@ -43,20 +43,21 @@ enum phase { struct session { const char *lhlo; const char *mailfrom; + char *rcptto; char **rcpts; int n_rcpts; }; -static FILE *lmtp_connect(const char *); -static void lmtp_engine(FILE *, struct session *); +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; - FILE *conn; + int conn; const char *destination = "localhost"; struct session session; @@ -64,9 +65,10 @@ main(int argc, char *argv[]) errx(EX_TEMPFAIL, "mail.lmtp: may not be executed as root"); session.lhlo = "localhost"; - session.mailfrom = NULL; + session.mailfrom = getenv("SENDER"); + session.rcptto = NULL; - while ((ch = getopt(argc, argv, "d:l:f:")) != -1) { + while ((ch = getopt(argc, argv, "d:l:f:ru")) != -1) { switch (ch) { case 'd': destination = optarg; @@ -77,6 +79,15 @@ main(int argc, char *argv[]) case 'f': session.mailfrom = optarg; break; + + case 'r': + session.rcptto = getenv("RECIPIENT"); + break; + + case 'u': + session.rcptto = getenv("USER"); + break; + default: break; } @@ -87,11 +98,17 @@ main(int argc, char *argv[]) if (session.mailfrom == NULL) errx(EX_TEMPFAIL, "sender must be specified with -f"); - if (argc == 0) + if (argc == 0 && session.rcptto == NULL) errx(EX_TEMPFAIL, "no recipient was specified"); - session.rcpts = argv; - session.n_rcpts = argc; + 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); @@ -99,7 +116,7 @@ main(int argc, char *argv[]) return (0); } -static FILE * +static int lmtp_connect_inet(const char *destination) { struct addrinfo hints, *res, *res0; @@ -173,10 +190,10 @@ lmtp_connect_inet(const char *destination) errx(EX_TEMPFAIL, "%s", cause); free(destcopy); - return fdopen(s, "r+"); + return s; } -static FILE * +static int lmtp_connect_unix(const char *destination) { struct sockaddr_un addr; @@ -197,10 +214,10 @@ lmtp_connect_unix(const char *destination) if (connect(s, (struct sockaddr *)&addr, sizeof addr) == -1) err(EX_TEMPFAIL, "connect"); - return fdopen(s, "r+"); + return s; } -static FILE * +static int lmtp_connect(const char *destination) { if (destination[0] == '/') @@ -209,17 +226,30 @@ lmtp_connect(const char *destination) } static void -lmtp_engine(FILE *conn, struct session *session) +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(conn); - if ((linelen = getline(&line, &linesize, conn)) == -1) { - if (ferror(conn)) + 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"); @@ -228,9 +258,9 @@ lmtp_engine(FILE *conn, struct session *session) line[strcspn(line, "\r")] = '\0'; if (linelen < 4 || - !isdigit(line[0]) || - !isdigit(line[1]) || - !isdigit(line[2]) || + !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"); @@ -243,17 +273,17 @@ lmtp_engine(FILE *conn, struct session *session) switch (phase) { case PHASE_BANNER: - fprintf(conn, "LHLO %s\r\n", session->lhlo); + fprintf(file_write, "LHLO %s\r\n", session->lhlo); phase++; break; case PHASE_HELO: - fprintf(conn, "MAIL FROM:<%s>\r\n", session->mailfrom); + fprintf(file_write, "MAIL FROM:<%s>\r\n", session->mailfrom); phase++; break; case PHASE_MAILFROM: - fprintf(conn, "RCPT TO:<%s>\r\n", session->rcpts[session->n_rcpts - 1]); + fprintf(file_write, "RCPT TO:<%s>\r\n", session->rcpts[session->n_rcpts - 1]); if (session->n_rcpts - 1 == 0) { phase++; break; @@ -262,18 +292,18 @@ lmtp_engine(FILE *conn, struct session *session) break; case PHASE_RCPTTO: - fprintf(conn, "DATA\r\n"); + fprintf(file_write, "DATA\r\n"); phase++; break; case PHASE_DATA: - stream_file(conn); - fprintf(conn, ".\r\n"); + stream_file(file_write); + fprintf(file_write, ".\r\n"); phase++; break; case PHASE_EOM: - fprintf(conn, "QUIT\r\n"); + fprintf(file_write, "QUIT\r\n"); phase++; break; diff --git a/smtpd/mail.maildir.8 b/usr.sbin/smtpd/mail.maildir.8 index ce822698..ce822698 100644 --- a/smtpd/mail.maildir.8 +++ b/usr.sbin/smtpd/mail.maildir.8 diff --git a/smtpd/mail.maildir.c b/usr.sbin/smtpd/mail.maildir.c index fe6adba6..fe6adba6 100644 --- a/smtpd/mail.maildir.c +++ b/usr.sbin/smtpd/mail.maildir.c diff --git a/smtpd/mail.mboxfile.8 b/usr.sbin/smtpd/mail.mboxfile.8 index 015adcb5..015adcb5 100644 --- a/smtpd/mail.mboxfile.8 +++ b/usr.sbin/smtpd/mail.mboxfile.8 diff --git a/smtpd/mail.mboxfile.c b/usr.sbin/smtpd/mail.mboxfile.c index c45d337c..097a8d96 100644 --- a/smtpd/mail.mboxfile.c +++ b/usr.sbin/smtpd/mail.mboxfile.c @@ -103,7 +103,7 @@ mboxfile_engine(const char *sender, const char *filename) if (fflush(fp) == EOF || ferror(fp) || - fsync(fd) == -1 || + (fsync(fd) == -1 && errno != EINVAL) || fclose(fp) == EOF) err(EX_TEMPFAIL, NULL); } diff --git a/smtpd/mail.mda.8 b/usr.sbin/smtpd/mail.mda.8 index 61fed733..61fed733 100644 --- a/smtpd/mail.mda.8 +++ b/usr.sbin/smtpd/mail.mda.8 diff --git a/smtpd/mail.mda.c b/usr.sbin/smtpd/mail.mda.c index 23958071..23958071 100644 --- a/smtpd/mail.mda.c +++ b/usr.sbin/smtpd/mail.mda.c diff --git a/smtpd/mail/Makefile b/usr.sbin/smtpd/mail/Makefile index b2bc2a26..b2bc2a26 100644 --- a/smtpd/mail/Makefile +++ b/usr.sbin/smtpd/mail/Makefile diff --git a/smtpd/mailaddr.c b/usr.sbin/smtpd/mailaddr.c index 4346e3dc..0965aea8 100644 --- a/smtpd/mailaddr.c +++ b/usr.sbin/smtpd/mailaddr.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mailaddr.c,v 1.3 2018/05/31 21:06:12 gilles Exp $ */ +/* $OpenBSD: mailaddr.c,v 1.4 2020/09/22 18:04:27 martijn Exp $ */ /* * Copyright (c) 2015 Gilles Chehade <gilles@poolp.org> @@ -82,12 +82,10 @@ int mailaddr_line(struct maddrmap *maddrmap, const char *s) { struct maddrnode mn; - char buffer[LINE_MAX]; - char *p, *subrcpt; + char *p, *subrcpt, *buffer; int ret; - memset(buffer, 0, sizeof buffer); - if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + if ((buffer = strdup(s)) == NULL) return 0; p = buffer; @@ -95,12 +93,16 @@ mailaddr_line(struct maddrmap *maddrmap, const char *s) subrcpt = strip(subrcpt); if (subrcpt[0] == '\0') continue; - if (!text_to_mailaddr(&mn.mailaddr, subrcpt)) + if (!text_to_mailaddr(&mn.mailaddr, subrcpt)) { + free(buffer); return 0; + } log_debug("subrcpt: [%s]", subrcpt); maddrmap_insert(maddrmap, &mn); } + free(buffer); + if (ret >= 0) return 1; /* expand_line_split() returned < 0 */ diff --git a/smtpd/makemap.8 b/usr.sbin/smtpd/makemap.8 index 674bef6f..674bef6f 100644 --- a/smtpd/makemap.8 +++ b/usr.sbin/smtpd/makemap.8 diff --git a/smtpd/makemap.c b/usr.sbin/smtpd/makemap.c index 274833e9..10e3f555 100644 --- a/smtpd/makemap.c +++ b/usr.sbin/smtpd/makemap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: makemap.c,v 1.72 2018/12/28 11:40:29 eric Exp $ */ +/* $OpenBSD: makemap.c,v 1.73 2020/02/24 16:16:07 millert Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -69,7 +69,6 @@ static int make_aliases(DBT *, char *); static char *conf_aliases(char *); static int dump_db(const char *, DBTYPE); -struct smtpd *env; char *source; static int mode; @@ -99,8 +98,13 @@ makemap(int prog_mode, int argc, char *argv[]) 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); @@ -177,9 +181,9 @@ makemap(int prog_mode, int argc, char *argv[]) errx(1, "database name too long"); } - execlp("makemap", "makemap", "-d", argv[0], "-o", dbname, "-", - (char *)NULL); - err(1, "execlp"); + execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname, + "-", (char *)NULL); + err(1, "execl"); } if (mode == P_NEWALIASES) { diff --git a/smtpd/mda.c b/usr.sbin/smtpd/mda.c index 5e8fec19..5e8fec19 100644 --- a/smtpd/mda.c +++ b/usr.sbin/smtpd/mda.c 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 <gilles@poolp.org> + * + * 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 <sys/types.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <limits.h> + +#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/usr.sbin/smtpd/mda_unpriv.c index ee54e101..2143b9a0 100644 --- a/smtpd/mda_unpriv.c +++ b/usr.sbin/smtpd/mda_unpriv.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mda_unpriv.c,v 1.5 2018/12/27 15:41:50 gilles Exp $ */ +/* $OpenBSD: mda_unpriv.c,v 1.6 2020/02/02 22:13:48 gilles Exp $ */ /* * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> @@ -42,7 +42,7 @@ mda_unpriv(struct dispatcher *dsp, struct deliver *deliver, const char *pw_name, const char *pw_dir) { int idx; - char *mda_environ[10]; + char *mda_environ[11]; char mda_exec[LINE_MAX]; char mda_wrapper[LINE_MAX]; const char *mda_command; @@ -74,6 +74,12 @@ mda_unpriv(struct dispatcher *dsp, struct deliver *deliver, 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); diff --git a/smtpd/mda_variables.c b/usr.sbin/smtpd/mda_variables.c index b672e492..b672e492 100644 --- a/smtpd/mda_variables.c +++ b/usr.sbin/smtpd/mda_variables.c diff --git a/smtpd/mproc.c b/usr.sbin/smtpd/mproc.c index 213996df..dac38af2 100644 --- a/smtpd/mproc.c +++ b/usr.sbin/smtpd/mproc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mproc.c,v 1.35 2019/10/03 05:50:28 gilles Exp $ */ +/* $OpenBSD: mproc.c,v 1.36 2020/03/17 09:01:53 tobhe Exp $ */ /* * Copyright (c) 2012 Eric Faurot <eric@faurot.net> @@ -90,7 +90,8 @@ 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); + if (p->events) + event_del(&p->ev); close(p->imsgbuf.fd); imsg_clear(&p->imsgbuf); } @@ -637,6 +638,8 @@ m_get_envelope(struct msg *m, struct envelope *evp) 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"); diff --git a/smtpd/mta.c b/usr.sbin/smtpd/mta.c index 0908db25..922170ae 100644 --- a/smtpd/mta.c +++ b/usr.sbin/smtpd/mta.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta.c,v 1.232 2019/09/20 17:46:05 gilles Exp $ */ +/* $OpenBSD: mta.c,v 1.234 2019/12/21 10:34:07 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -929,6 +929,10 @@ mta_query_smarthost(struct envelope *evp0) 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); @@ -1767,6 +1771,7 @@ mta_relay(struct envelope *e, struct relayhost *relayh) 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; diff --git a/smtpd/mta_session.c b/usr.sbin/smtpd/mta_session.c index fa5be18f..ad1c3c84 100644 --- a/smtpd/mta_session.c +++ b/usr.sbin/smtpd/mta_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mta_session.c,v 1.122 2019/09/20 17:46:05 gilles Exp $ */ +/* $OpenBSD: mta_session.c,v 1.136 2020/05/21 15:38:05 millert Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -107,6 +107,8 @@ struct mta_session { char *helo; char *mxname; + char *username; + int flags; int attempt; @@ -130,6 +132,7 @@ struct mta_session { struct mta_task *task; struct mta_envelope *currevp; FILE *datafp; + size_t datalen; size_t failures; @@ -165,6 +168,36 @@ 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; @@ -173,6 +206,9 @@ 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) { @@ -203,6 +239,8 @@ mta_session(struct mta_relay *relay, struct mta_route *route, const char *mxname s->route = route; s->mxname = xstrdup(mxname); + mta_filter_begin(s); + if (relay->flags & RELAY_LMTP) s->flags |= MTA_LMTP; switch (relay->tls) { @@ -351,6 +389,8 @@ mta_free(struct mta_session *s) log_debug("debug: mta: %p: session done", s); + mta_disconnected(s); + if (s->ready) s->relay->nconn_ready -= 1; @@ -364,12 +404,15 @@ mta_free(struct mta_session *s) if (s->task) fatalx("current task should have been deleted already"); - if (s->datafp) + 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); @@ -555,16 +598,19 @@ again: 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: @@ -615,6 +661,14 @@ again: 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; @@ -630,6 +684,7 @@ again: 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); @@ -771,11 +826,12 @@ again: e->dest, e->dsn_notify ? " NOTIFY=" : "", e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "", - e->dsn_orcpt ? " ORCPT=" : "", + 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; @@ -816,6 +872,7 @@ again: if (s->datafp) { fclose(s->datafp); s->datafp = NULL; + s->datalen = 0; } mta_send(s, "RSET"); break; @@ -840,6 +897,7 @@ mta_response(struct mta_session *s, char *line) struct sockaddr_storage ss; struct sockaddr *sa; const char *domain; + char *pbuf; socklen_t sa_len; char buf[LINE_MAX]; int delivery; @@ -852,6 +910,16 @@ mta_response(struct mta_session *s, char *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 @@ -912,15 +980,18 @@ mta_response(struct mta_session *s, char *line) 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; } @@ -930,6 +1001,7 @@ mta_response(struct mta_session *s, char *line) 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; } @@ -939,9 +1011,11 @@ mta_response(struct mta_session *s, char *line) 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; @@ -951,10 +1025,13 @@ mta_response(struct mta_session *s, char *line) 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; @@ -978,6 +1055,8 @@ mta_response(struct mta_session *s, char *line) 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 @@ -1029,6 +1108,21 @@ mta_response(struct mta_session *s, char *line) } } + 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 @@ -1037,13 +1131,19 @@ mta_response(struct mta_session *s, char *line) 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; @@ -1059,6 +1159,14 @@ mta_response(struct mta_session *s, char *line) 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--; @@ -1080,6 +1188,11 @@ mta_response(struct mta_session *s, char *line) 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); @@ -1112,7 +1225,7 @@ mta_io(struct io *io, int evt, void *arg) switch (evt) { case IO_CONNECTED: - log_info("%016"PRIx64" mta connected", s->id); + mta_connected(s); if (s->use_smtps) { io_set_write(io); @@ -1129,6 +1242,9 @@ mta_io(struct io *io, int evt, void *arg) 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; @@ -1143,7 +1259,12 @@ mta_io(struct io *io, int evt, void *arg) 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); @@ -1179,14 +1300,13 @@ mta_io(struct io *io, int evt, void *arg) if (cont) { if (s->replybuf[0] == '\0') (void)strlcat(s->replybuf, line, sizeof s->replybuf); - else { - line = line + 4; - if (isdigit((int)*line) && *(line + 1) == '.' && - isdigit((int)*line+2) && *(line + 3) == '.' && - isdigit((int)*line+4) && isspace((int)*(line + 5))) - (void)strlcat(s->replybuf, line+5, sizeof s->replybuf); - else - (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; } @@ -1194,17 +1314,17 @@ mta_io(struct io *io, int evt, void *arg) /* 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') { + if (s->replybuf[0] == '\0') + (void)strlcat(s->replybuf, line, sizeof s->replybuf); + else if (len > 4) { p = line + 4; - if (isdigit((int)*p) && *(p + 1) == '.' && - isdigit((int)*p+2) && *(p + 3) == '.' && - isdigit((int)*p+4) && isspace((int)*(p + 5))) + 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); } - else - (void)strlcpy(s->replybuf, line, sizeof s->replybuf); if (s->state == MTA_QUIT) { log_info("%016"PRIx64" mta disconnected reason=quit messages=%zu", @@ -1250,6 +1370,7 @@ mta_io(struct io *io, int evt, void *arg) 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 @@ -1257,40 +1378,20 @@ mta_io(struct io *io, int evt, void *arg) break; case IO_ERROR: + case IO_TLSERROR: log_debug("debug: mta: %p: IO error: %s", s, io_error(io)); - if (!s->ready) { - mta_error(s, "IO Error: %s", io_error(io)); - mta_connect(s); - break; - } - else if (!(s->flags & (MTA_FORCE_TLS|MTA_FORCE_SMTPS|MTA_FORCE_ANYSSL))) { - /* error in non-strict SSL negotiation, downgrade to plain */ - if (s->flags & MTA_TLS) { - 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_TLSERROR: - log_debug("debug: mta: %p: TLS IO error: %s", s, io_error(io)); - if (!(s->flags & (MTA_FORCE_TLS|MTA_FORCE_SMTPS|MTA_FORCE_ANYSSL))) { + if (s->state == MTA_STARTTLS && s->use_smtp_tls) { /* error in non-strict SSL negotiation, downgrade to plain */ - log_info("smtp-out: TLS Error on session %016"PRIx64 - ": TLS failed, " + 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; @@ -1324,6 +1425,13 @@ mta_send(struct mta_session *s, char *fmt, ...) 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); @@ -1346,7 +1454,7 @@ mta_queue_data(struct mta_session *s) break; if (ln[len - 1] == '\n') ln[len - 1] = '\0'; - io_xprintf(s->io, "%s%s\r\n", *ln == '.' ? "." : "", ln); + s->datalen += io_xprintf(s->io, "%s%s\r\n", *ln == '.' ? "." : "", ln); } free(ln); @@ -1562,8 +1670,12 @@ mta_cert_verify_cb(void *arg, int status) match = 0; (void)ssl_check_name(cert, s->mxname, &match); X509_free(cert); - if (!match) + if (!match) { + log_info("%016"PRIx64" mta " + "ssl_check_name: no match for '%s' in cert", + s->id, s->mxname); status = CERT_INVALID; + } } } @@ -1672,3 +1784,225 @@ mta_strstate(int state) 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_storage sa_src; + struct sockaddr_storage sa_dest; + int sa_len; + + log_info("%016"PRIx64" mta connected", s->id); + + sa_len = sizeof sa_src; + if (getsockname(io_fileno(s->io), + (struct sockaddr *)&sa_src, &sa_len) == -1) + bzero(&sa_src, sizeof sa_src); + sa_len = sizeof sa_dest; + if (getpeername(io_fileno(s->io), + (struct sockaddr *)&sa_dest, &sa_len) == -1) + bzero(&sa_dest, sizeof sa_dest); + + mta_report_link_connect(s, + s->route->dst->ptrname, 1, + &sa_src, + &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/usr.sbin/smtpd/newaliases.8 index b82e4515..b82e4515 100644 --- a/smtpd/newaliases.8 +++ b/usr.sbin/smtpd/newaliases.8 diff --git a/smtpd/parse.y b/usr.sbin/smtpd/parse.y index e7ee2c8c..a82f8206 100644 --- a/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.263 2019/09/22 11:49:53 semarie Exp $ */ +/* $OpenBSD: parse.y,v 1.281 2020/09/23 19:11:50 martijn Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -111,7 +111,7 @@ static struct ca *sca; struct dispatcher *dispatcher; struct rule *rule; -struct processor *processor; +struct filter_proc *processor; struct filter_config *filter_config; static uint32_t last_dynchain_id = 1; @@ -179,8 +179,8 @@ typedef struct { %} -%token ACTION ALIAS ANY ARROW AUTH AUTH_OPTIONAL -%token BACKUP BOUNCE +%token ACTION ADMD 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 @@ -215,6 +215,7 @@ grammar : /* empty */ | grammar include '\n' | grammar varset '\n' | grammar bounce '\n' + | grammar admd '\n' | grammar ca '\n' | grammar mda '\n' | grammar mta '\n' @@ -316,6 +317,21 @@ BOUNCE WARN_INTERVAL { ; +admd: +ADMD STRING { + size_t i; + + for (i = 0; $2[i] != '\0'; i++) { + if (!isprint($2[i])) { + yyerror("not a valid admd"); + free($2); + YYERROR; + } + } + conf->sc_admd = $2; +}; + + ca: CA STRING { char buf[HOST_NAME_MAX+1]; @@ -440,7 +456,7 @@ pki_params_opt pki_params proc: PROC STRING STRING { - if (dict_get(conf->sc_processors_dict, $2)) { + if (dict_get(conf->sc_filter_processes_dict, $2)) { yyerror("processor already exists with that name: %s", $2); free($2); free($3); @@ -449,7 +465,7 @@ PROC STRING STRING { processor = xcalloc(1, sizeof *processor); processor->command = $3; } proc_params { - dict_set(conf->sc_processors_dict, $2, processor); + dict_set(conf->sc_filter_processes_dict, $2, processor); processor = NULL; } ; @@ -535,7 +551,7 @@ SMTP LIMIT limits_smtp free($3); YYERROR; } - if (isspace((int)*$3) || !isprint((int)*$3) || *$3== '@') { + if (isspace((unsigned char)*$3) || !isprint((unsigned char)*$3) || *$3 == '@') { yyerror("sub-addr-delim uses invalid character"); free($3); YYERROR; @@ -572,7 +588,7 @@ SRS KEY STRING { dispatcher_local_option: USER STRING { - if (dispatcher->u.local.requires_root) { + if (dispatcher->u.local.is_mbox) { yyerror("user may not be specified for this dispatcher"); YYERROR; } @@ -668,9 +684,8 @@ dispatcher_local_option dispatcher_local_options dispatcher_local: MBOX { - dispatcher->u.local.requires_root = 1; - dispatcher->u.local.user = xstrdup("root"); - asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.local -f %%{mbox.from} %%{user.username}"); + 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"); @@ -696,11 +711,11 @@ MBOX { } dispatcher_local_options | LMTP STRING { asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.lmtp -f \"%%{sender}\" -d %s %%{user.username}", $2); + PATH_LIBEXEC"/mail.lmtp -d \"%s\" -u", $2); } dispatcher_local_options | LMTP STRING RCPT_TO { asprintf(&dispatcher->u.local.command, - PATH_LIBEXEC"/mail.lmtp -f \"%%{sender}\" -d %s %%{dest}", $2); + PATH_LIBEXEC"/mail.lmtp -d \"%s\" -r", $2); } dispatcher_local_options | MDA STRING { asprintf(&dispatcher->u.local.command, @@ -824,6 +839,27 @@ HELO STRING { 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"); @@ -862,6 +898,45 @@ HELO STRING { 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, "<dynchain:%08x>", 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"); @@ -1027,7 +1102,7 @@ negation TAG REGEX tables { YYERROR; } - if (!table_check_use(t, T_DYNAMIC|T_LIST, K_CREDENTIALS)) { + 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; @@ -1234,6 +1309,101 @@ negation TAG REGEX tables { rule->table_from = strdup(t->t_name); } +| negation FROM AUTH { + struct table *anyhost = table_find(conf, "<anyhost>"); + + 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, "<anyhost>"); + 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, "<anyhost>"); + 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, "<anyhost>"); + 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, "<anyhost>"); + 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, "<localnames>"); @@ -1290,6 +1460,47 @@ negation TAG REGEX tables { rule->flag_for_regex = 1; rule->table_for = strdup(t->t_name); } +| negation FOR RCPT_TO tables { + struct table *anyhost = table_find(conf, "<anydestination>"); + 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, "<anydestination>"); + 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: @@ -1336,6 +1547,9 @@ filter_action_builtin_nojunk | JUNK { filter_config->junk = 1; } +| BYPASS { + filter_config->bypass = 1; +} ; filter_action_builtin_nojunk: @@ -1406,6 +1620,25 @@ negation HELO REGEX tables { } ; +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; @@ -1448,9 +1681,20 @@ 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; @@ -1458,6 +1702,9 @@ 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 | @@ -1467,6 +1714,9 @@ 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; @@ -1491,6 +1741,9 @@ 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; @@ -1515,6 +1768,11 @@ 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; @@ -1571,6 +1829,7 @@ 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 @@ -1585,6 +1844,7 @@ filter_phase_connect filterel: STRING { struct filter_config *fr; + struct filter_proc *fp; size_t i; if ((fr = dict_get(conf->sc_filters_dict, $1)) == NULL) { @@ -1607,7 +1867,7 @@ STRING { } if (fr->proc) { - if (dict_check(&filter_config->chain_procs, 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; @@ -1615,6 +1875,7 @@ STRING { 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) @@ -1630,13 +1891,15 @@ filterel 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 (! dict_get(conf->sc_processors_dict, $4)) { + if ((fp = dict_get(conf->sc_filter_processes_dict, $4)) == NULL) { yyerror("no processor exist with that name: %s", $4); free($4); YYERROR; @@ -1667,7 +1930,7 @@ FILTER STRING PROC_EXEC STRING { filter_config->proc = xstrdup($2); dict_set(conf->sc_filters_dict, $2, filter_config); } proc_params { - dict_set(conf->sc_processors_dict, filter_config->proc, processor); + dict_set(conf->sc_filter_processes_dict, filter_config->proc, processor); processor = NULL; filter_config = NULL; } @@ -1842,16 +2105,19 @@ limits_scheduler: opt_limit_scheduler limits_scheduler opt_sock_listen : FILTER STRING { + struct filter_config *fc; + if (listen_opts.options & LO_FILTER) { yyerror("filter already specified"); free($2); YYERROR; } - if (dict_get(conf->sc_filters_dict, $2) == NULL) { + 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; } @@ -1871,6 +2137,7 @@ opt_sock_listen : FILTER STRING { 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); @@ -1881,6 +2148,20 @@ opt_sock_listen : FILTER STRING { 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 { @@ -1963,15 +2244,18 @@ opt_if_listen : INET4 { listen_opts.port = $2; } | FILTER STRING { + struct filter_config *fc; + if (listen_opts.options & LO_FILTER) { yyerror("filter already specified"); YYERROR; } - if (dict_get(conf->sc_filters_dict, $2) == NULL) { + 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; } @@ -1991,6 +2275,7 @@ opt_if_listen : INET4 { 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); @@ -2338,12 +2623,14 @@ lookup(char *s) /* this has to be sorted always */ static const struct keywords keywords[] = { { "action", ACTION }, + { "admd", ADMD }, { "alias", ALIAS }, { "any", ANY }, { "auth", AUTH }, { "auth-optional", AUTH_OPTIONAL }, { "backup", BACKUP }, { "bounce", BOUNCE }, + { "bypass", BYPASS }, { "ca", CA }, { "cert", CERT }, { "chain", CHAIN }, @@ -2921,7 +3208,6 @@ static void create_sock_listener(struct listen_opts *lo) { struct listener *l = xcalloc(1, sizeof(*l)); - lo->tag = "local"; lo->hostname = conf->sc_hostname; l->ss.ss_family = AF_LOCAL; #ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN diff --git a/smtpd/parser.c b/usr.sbin/smtpd/parser.c index 997e3405..4c2321ec 100644 --- a/smtpd/parser.c +++ b/usr.sbin/smtpd/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.41 2017/07/31 16:38:33 gilles Exp $ */ +/* $OpenBSD: parser.c,v 1.42 2020/01/06 11:02:38 gilles Exp $ */ /* * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> @@ -221,6 +221,11 @@ cmd_run(int argc, char **argv) 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++) diff --git a/smtpd/parser.h b/usr.sbin/smtpd/parser.h index f0114e9e..f0114e9e 100644 --- a/smtpd/parser.h +++ b/usr.sbin/smtpd/parser.h diff --git a/smtpd/pony.c b/usr.sbin/smtpd/pony.c index 1865b339..1865b339 100644 --- a/smtpd/pony.c +++ b/usr.sbin/smtpd/pony.c diff --git a/smtpd/proxy.c b/usr.sbin/smtpd/proxy.c index fdaf4f27..fdaf4f27 100644 --- a/smtpd/proxy.c +++ b/usr.sbin/smtpd/proxy.c diff --git a/smtpd/queue.c b/usr.sbin/smtpd/queue.c index 5b256958..434e3647 100644 --- a/smtpd/queue.c +++ b/usr.sbin/smtpd/queue.c @@ -1,4 +1,4 @@ -/* $OpenBSD: queue.c,v 1.189 2018/12/30 23:09:58 guenther Exp $ */ +/* $OpenBSD: queue.c,v 1.190 2020/04/22 11:35:34 eric Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -692,7 +692,6 @@ static void queue_timeout(int fd, short event, void *p) { static uint32_t msgid = 0; - struct dispatcher *dsp; struct envelope evp; struct event *ev = p; struct timeval tv; @@ -711,13 +710,6 @@ queue_timeout(int fd, short event, void *p) } if (r) { - dsp = dict_get(env->sc_dispatchers, evp.dispatcher); - if (dsp == NULL) { - log_warnx("warn: queue: missing dispatcher \"%s\"" - " for envelope %016"PRIx64", ignoring", - evp.dispatcher, evp.id); - goto reset; - } if (msgid && evpid_to_msgid(evp.id) != msgid) { m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1); @@ -730,7 +722,6 @@ queue_timeout(int fd, short event, void *p) m_close(p_scheduler); } -reset: tv.tv_sec = 0; tv.tv_usec = 10; evtimer_add(ev, &tv); diff --git a/smtpd/queue_backend.c b/usr.sbin/smtpd/queue_backend.c index 6fc720da..fa945f47 100644 --- a/smtpd/queue_backend.c +++ b/usr.sbin/smtpd/queue_backend.c @@ -1,4 +1,4 @@ -/* $OpenBSD: queue_backend.c,v 1.65 2018/12/30 23:09:58 guenther Exp $ */ +/* $OpenBSD: queue_backend.c,v 1.66 2020/04/22 11:35:34 eric Exp $ */ /* * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> @@ -732,6 +732,9 @@ envelope_validate(struct envelope *ep) 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; } diff --git a/smtpd/queue_fs.c b/usr.sbin/smtpd/queue_fs.c index b3ac4f95..097ba1e2 100644 --- a/smtpd/queue_fs.c +++ b/usr.sbin/smtpd/queue_fs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: queue_fs.c,v 1.19 2019/06/28 13:32:51 deraadt Exp $ */ +/* $OpenBSD: queue_fs.c,v 1.20 2020/02/25 17:03:13 millert Exp $ */ /* * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> @@ -235,8 +235,7 @@ queue_fs_envelope_create(uint32_t msgid, const char *buf, size_t len, fsqueue_envelope_incoming_path(*evpid, path, sizeof(path)); - r = fsqueue_envelope_dump(path, buf, len, 0, 0); - if (r >= 0) + if ((r = fsqueue_envelope_dump(path, buf, len, 0, 0)) != 0) goto done; } r = 0; diff --git a/smtpd/queue_null.c b/usr.sbin/smtpd/queue_null.c index 1e608be8..1e608be8 100644 --- a/smtpd/queue_null.c +++ b/usr.sbin/smtpd/queue_null.c diff --git a/smtpd/queue_proc.c b/usr.sbin/smtpd/queue_proc.c index d6e0f409..d6e0f409 100644 --- a/smtpd/queue_proc.c +++ b/usr.sbin/smtpd/queue_proc.c diff --git a/smtpd/queue_ram.c b/usr.sbin/smtpd/queue_ram.c index 50ce17e1..50ce17e1 100644 --- a/smtpd/queue_ram.c +++ b/usr.sbin/smtpd/queue_ram.c diff --git a/smtpd/report_smtp.c b/usr.sbin/smtpd/report_smtp.c index cb5ef24a..7802eaae 100644 --- a/smtpd/report_smtp.c +++ b/usr.sbin/smtpd/report_smtp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: report_smtp.c,v 1.10 2019/09/19 14:40:53 gilles Exp $ */ +/* $OpenBSD: report_smtp.c,v 1.11 2020/01/07 23:03:37 gilles Exp $ */ /* * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> @@ -180,15 +180,6 @@ void report_smtp_tx_mail(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) { struct timeval tv; - char buffer[SMTPD_MAXMAILADDRSIZE]; - char *p; - - if ((p = strchr(address, '<')) == NULL) - return; - (void)strlcpy(buffer, p + 1, sizeof buffer); - if ((p = strchr(buffer, '>')) == NULL) - return; - *p = '\0'; gettimeofday(&tv, NULL); @@ -197,7 +188,7 @@ report_smtp_tx_mail(const char *direction, uint64_t qid, uint32_t msgid, const c m_add_timeval(p_lka, &tv); m_add_id(p_lka, qid); m_add_u32(p_lka, msgid); - m_add_string(p_lka, buffer); + m_add_string(p_lka, address); m_add_int(p_lka, ok); m_close(p_lka); } @@ -206,15 +197,6 @@ void report_smtp_tx_rcpt(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) { struct timeval tv; - char buffer[SMTPD_MAXMAILADDRSIZE]; - char *p; - - if ((p = strchr(address, '<')) == NULL) - return; - (void)strlcpy(buffer, p + 1, sizeof buffer); - if ((p = strchr(buffer, '>')) == NULL) - return; - *p = '\0'; gettimeofday(&tv, NULL); @@ -223,7 +205,7 @@ report_smtp_tx_rcpt(const char *direction, uint64_t qid, uint32_t msgid, const c m_add_timeval(p_lka, &tv); m_add_id(p_lka, qid); m_add_u32(p_lka, msgid); - m_add_string(p_lka, buffer); + m_add_string(p_lka, address); m_add_int(p_lka, ok); m_close(p_lka); } diff --git a/smtpd/resolver.c b/usr.sbin/smtpd/resolver.c index f0f0f8ea..f0f0f8ea 100644 --- a/smtpd/resolver.c +++ b/usr.sbin/smtpd/resolver.c diff --git a/smtpd/rfc5322.c b/usr.sbin/smtpd/rfc5322.c index 0af66772..0af66772 100644 --- a/smtpd/rfc5322.c +++ b/usr.sbin/smtpd/rfc5322.c diff --git a/smtpd/rfc5322.h b/usr.sbin/smtpd/rfc5322.h index 0979bd4c..0979bd4c 100644 --- a/smtpd/rfc5322.h +++ b/usr.sbin/smtpd/rfc5322.h diff --git a/smtpd/ruleset.c b/usr.sbin/smtpd/ruleset.c index 2eae58c0..719a2913 100644 --- a/smtpd/ruleset.c +++ b/usr.sbin/smtpd/ruleset.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ruleset.c,v 1.46 2019/11/12 20:21:46 gilles Exp $ */ +/* $OpenBSD: ruleset.c,v 1.47 2019/11/25 14:18:33 gilles Exp $ */ /* * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> @@ -154,6 +154,8 @@ 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; @@ -161,14 +163,14 @@ ruleset_match_smtp_auth(struct rule *r, const struct envelope *evp) if (!(evp->flags & EF_AUTHENTICATED)) ret = 0; else if (r->table_smtp_auth) { - /* XXX - not until smtp_session->username is added to envelope */ - /* - * table = table_find(m->from_table); - * key = evp->username; - * return table_match(table, K_CREDENTIALS, key); - */ - return -1; + 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; diff --git a/smtpd/runq.c b/usr.sbin/smtpd/runq.c index 786d36fb..786d36fb 100644 --- a/smtpd/runq.c +++ b/usr.sbin/smtpd/runq.c diff --git a/smtpd/scheduler.c b/usr.sbin/smtpd/scheduler.c index ea70a83d..ea70a83d 100644 --- a/smtpd/scheduler.c +++ b/usr.sbin/smtpd/scheduler.c diff --git a/smtpd/scheduler_backend.c b/usr.sbin/smtpd/scheduler_backend.c index ad2b4cab..ad2b4cab 100644 --- a/smtpd/scheduler_backend.c +++ b/usr.sbin/smtpd/scheduler_backend.c diff --git a/smtpd/scheduler_null.c b/usr.sbin/smtpd/scheduler_null.c index 40db6205..40db6205 100644 --- a/smtpd/scheduler_null.c +++ b/usr.sbin/smtpd/scheduler_null.c diff --git a/smtpd/scheduler_proc.c b/usr.sbin/smtpd/scheduler_proc.c index 5f4e8b70..5f4e8b70 100644 --- a/smtpd/scheduler_proc.c +++ b/usr.sbin/smtpd/scheduler_proc.c diff --git a/smtpd/scheduler_ramqueue.c b/usr.sbin/smtpd/scheduler_ramqueue.c index 0c04fc0b..0c04fc0b 100644 --- a/smtpd/scheduler_ramqueue.c +++ b/usr.sbin/smtpd/scheduler_ramqueue.c diff --git a/smtpd/sendmail.8 b/usr.sbin/smtpd/sendmail.8 index 1696a861..1696a861 100644 --- a/smtpd/sendmail.8 +++ b/usr.sbin/smtpd/sendmail.8 diff --git a/smtpd/smtp.1 b/usr.sbin/smtpd/smtp.1 index 3cc03844..3cc03844 100644 --- a/smtpd/smtp.1 +++ b/usr.sbin/smtpd/smtp.1 diff --git a/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c index 602fd0d6..602fd0d6 100644 --- a/smtpd/smtp.c +++ b/usr.sbin/smtpd/smtp.c diff --git a/smtpd/smtp.h b/usr.sbin/smtpd/smtp.h index dc91d878..dc91d878 100644 --- a/smtpd/smtp.h +++ b/usr.sbin/smtpd/smtp.h diff --git a/smtpd/smtp/Makefile b/usr.sbin/smtpd/smtp/Makefile index 380e3ad6..380e3ad6 100644 --- a/smtpd/smtp/Makefile +++ b/usr.sbin/smtpd/smtp/Makefile diff --git a/smtpd/smtp_client.c b/usr.sbin/smtpd/smtp_client.c index 22e79890..8e146e1b 100644 --- a/smtpd/smtp_client.c +++ b/usr.sbin/smtpd/smtp_client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_client.c,v 1.12 2019/09/10 12:08:26 eric Exp $ */ +/* $OpenBSD: smtp_client.c,v 1.14 2020/04/24 11:34:07 eric Exp $ */ /* * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> @@ -680,6 +680,10 @@ smtp_client_readline(struct smtp_client *proto) return 0; } + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + log_trace(TRACE_SMTPCLT, "%p: <<< %s", proto, line); /* Validate SMTP */ @@ -779,9 +783,10 @@ smtp_client_replycat(struct smtp_client *proto, const char *line) line += 3; if (line[0]) { line += 1; - if (isdigit((int)line[0]) && line[1] == '.' && - isdigit((int)line[2]) && line[3] == '.' && - isdigit((int)line[4]) && isspace((int)line[5])) + 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 diff --git a/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c index d2487807..c103fa19 100644 --- a/smtpd/smtp_session.c +++ b/usr.sbin/smtpd/smtp_session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtp_session.c,v 1.415 2019/10/04 08:34:29 gilles Exp $ */ +/* $OpenBSD: smtp_session.c,v 1.427 2020/11/20 20:37:56 jung Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -259,9 +259,32 @@ 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; @@ -439,7 +462,7 @@ header_address_rewrite_buffer(char *buffer, const char *address, size_t len) 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(buffer[pos_component_beg])) + if (buffer[pos_component_beg] == ')' || isspace((unsigned char)buffer[pos_component_beg])) break; pos_component_beg += 1; pos_component_end += 1; @@ -698,7 +721,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) { struct smtp_session *s; struct smtp_rcpt *rcpt; - char user[LOGIN_NAME_MAX]; + char user[SMTPD_MAXMAILADDRSIZE]; char tmp[SMTP_LINE_MAX]; struct msg m; const char *line, *helo; @@ -787,7 +810,6 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) s->tx->msgid = msgid; s->tx->evp.id = msgid_to_evpid(msgid); s->tx->rcptcount = 0; - report_smtp_tx_begin("smtp-in", s->id, s->tx->msgid); smtp_reply(s, "250 %s Ok", esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); } else { @@ -856,7 +878,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) m_get_evpid(&m, &evpid); s->tx->evp.id = evpid; s->tx->destcount++; - report_smtp_tx_envelope("smtp-in", s->id, s->tx->msgid, evpid); + smtp_report_tx_envelope(s, s->tx->msgid, evpid); } else s->tx->error = TX_ERROR_ENVELOPE; @@ -903,9 +925,9 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) m_end(&m); s = tree_xpop(&wait_queue_commit, reqid); if (!success) { - smtp_tx_free(s->tx); 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; } @@ -913,6 +935,8 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) 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", @@ -952,7 +976,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) "result=ok", s->id, user); s->flags |= SF_AUTHENTICATED; - report_smtp_link_auth("smtp-in", s->id, user, "pass"); + smtp_report_link_auth(s, user, "pass"); smtp_reply(s, "235 %s Authentication succeeded", esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); } @@ -961,7 +985,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) "authentication user=%s " "result=permfail", s->id, user); - report_smtp_link_auth("smtp-in", s->id, user, "fail"); + smtp_report_link_auth(s, user, "fail"); smtp_auth_failure_pause(s); return; } @@ -970,7 +994,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) "authentication user=%s " "result=tempfail", s->id, user); - report_smtp_link_auth("smtp-in", s->id, user, "error"); + smtp_report_link_auth(s, user, "error"); smtp_reply(s, "421 %s Temporary failure", esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); } @@ -1002,7 +1026,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) if (!strncmp(filter_param, "421", 3)) filter_response = FILTER_DISCONNECT; - report_smtp_filter_response("smtp-in", s->id, s->filter_phase, + smtp_report_filter_response(s, s->filter_phase, filter_response, filter_param); smtp_reply(s, "%s", filter_param); @@ -1026,7 +1050,7 @@ smtp_session_imsg(struct mproc *p, struct imsg *imsg) /* fallthrough */ case FILTER_REWRITE: - report_smtp_filter_response("smtp-in", s->id, s->filter_phase, + smtp_report_filter_response(s, s->filter_phase, filter_response, filter_param == s->filter_param ? NULL : filter_param); if (s->filter_phase == FILTER_CONNECT) { @@ -1093,7 +1117,7 @@ smtp_io(struct io *io, int evt, void *arg) log_info("%016"PRIx64" smtp tls ciphers=%s", s->id, ssl_to_text(io_tls(s->io))); - report_smtp_link_tls("smtp-in", 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'; @@ -1118,6 +1142,10 @@ smtp_io(struct io *io, int evt, void *arg) 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) { @@ -1186,7 +1214,7 @@ smtp_io(struct io *io, int evt, void *arg) log_info("%016"PRIx64" smtp disconnected " "reason=timeout", s->id); - report_smtp_timeout("smtp-in", s->id); + smtp_report_timeout(s); smtp_free(s, "timeout"); break; @@ -1221,20 +1249,20 @@ smtp_command(struct smtp_session *s, char *line) * These states are special. */ if (s->state == STATE_AUTH_INIT) { - report_smtp_protocol_client("smtp-in", s->id, "********"); + smtp_report_protocol_client(s, "********"); smtp_rfc4954_auth_plain(s, line); return; } if (s->state == STATE_AUTH_USERNAME || s->state == STATE_AUTH_PASSWORD) { - report_smtp_protocol_client("smtp-in", s->id, "********"); + smtp_report_protocol_client(s, "********"); smtp_rfc4954_auth_login(s, line); return; } if (s->state == STATE_HELO && strncasecmp(line, "AUTH PLAIN ", 11) == 0) - report_smtp_protocol_client("smtp-in", s->id, "AUTH PLAIN ********"); + smtp_report_protocol_client(s, "AUTH PLAIN ********"); else - report_smtp_protocol_client("smtp-in", s->id, line); + smtp_report_protocol_client(s, line); /* @@ -1649,10 +1677,6 @@ smtp_filter_begin(struct smtp_session *s) 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_add_sockaddr(p_lka, (struct sockaddr *)&s->ss); - m_add_sockaddr(p_lka, (struct sockaddr *)&s->listener->ss); - m_add_string(p_lka, s->rdns); - m_add_int(p_lka, s->fcrdns); m_close(p_lka); } @@ -1724,14 +1748,14 @@ smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *p 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); } - - smtp_reply(s, "250 %s Reset state", - esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); } static void @@ -1740,7 +1764,7 @@ 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; - report_smtp_link_identify("smtp-in", s->id, "HELO", s->helo); + smtp_report_link_identify(s, "HELO", s->helo); smtp_enter_state(s, STATE_HELO); @@ -1760,7 +1784,7 @@ smtp_proceed_ehlo(struct smtp_session *s, const char *args) s->flags |= SF_EHLO; s->flags |= SF_8BITMIME; - report_smtp_link_identify("smtp-in", s->id, "EHLO", s->helo); + 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", @@ -2047,7 +2071,7 @@ smtp_connected(struct smtp_session *s) smtp_filter_begin(s); - report_smtp_link_connect("smtp-in", s->id, s->rdns, s->fcrdns, &s->ss, + 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)); @@ -2067,7 +2091,7 @@ smtp_send_banner(struct smtp_session *s) { smtp_reply(s, "220 %s ESMTP %s", s->smtpname, SMTPD_NAME); s->banner_sent = 1; - report_smtp_link_greeting("smtp-in", s->id, s->smtpname); + smtp_report_link_greeting(s, s->smtpname); } void @@ -2102,20 +2126,23 @@ smtp_reply(struct smtp_session *s, char *fmt, ...) } 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) - report_smtp_tx_mail("smtp-in", s->id, s->tx->msgid, s->cmd + 10, 1); + 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) - report_smtp_tx_rcpt("smtp-in", s->id, s->tx->msgid, s->cmd + 8, 1); + smtp_report_tx_rcpt(s, s->tx->msgid, s->cmd + 8, 1); } break; case '3': if (s->tx) { if (s->last_cmd == CMD_DATA) - report_smtp_tx_data("smtp-in", s->id, s->tx->msgid, 1); + smtp_report_tx_data(s, s->tx->msgid, 1); } break; case '5': @@ -2125,13 +2152,13 @@ smtp_reply(struct smtp_session *s, char *fmt, ...) */ if (s->tx) { if (s->last_cmd == CMD_MAIL_FROM) - report_smtp_tx_mail("smtp-in", s->id, s->tx->msgid, + smtp_report_tx_mail(s, s->tx->msgid, s->cmd + 10, buf[0] == '4' ? -1 : 0); else if (s->last_cmd == CMD_RCPT_TO) - report_smtp_tx_rcpt("smtp-in", s->id, + 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) - report_smtp_tx_data("smtp-in", s->id, s->tx->msgid, + smtp_report_tx_data(s, s->tx->msgid, buf[0] == '4' ? -1 : 0); } @@ -2169,7 +2196,6 @@ smtp_reply(struct smtp_session *s, char *fmt, ...) } io_xprintf(s->io, "%s\r\n", buf); - report_smtp_protocol_server("smtp-in", s->id, buf); } static void @@ -2181,7 +2207,7 @@ smtp_free(struct smtp_session *s, const char * reason) smtp_tx_free(s->tx); } - report_smtp_link_disconnect("smtp-in", s->id); + smtp_report_link_disconnect(s); smtp_filter_end(s); if (s->flags & SF_SECURE && s->listener->flags & F_SMTPS) @@ -2224,25 +2250,23 @@ smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, memmove(maddr->user, p, strlen(p) + 1); } - if (!valid_localpart(maddr->user) || - !valid_domainpart(maddr->domain)) { - /* accept empty return-path in MAIL FROM, required for bounces */ - if (mailfrom && maddr->user[0] == '\0' && maddr->domain[0] == '\0') - return (1); - - /* no user-part, reject */ - if (maddr->user[0] == '\0') - return (0); + /* accept empty return-path in MAIL FROM, required for bounces */ + if (mailfrom && maddr->user[0] == '\0' && maddr->domain[0] == '\0') + return (1); - /* no domain, local user */ - if (maddr->domain[0] == '\0') { - (void)strlcpy(maddr->domain, domain, - sizeof(maddr->domain)); - 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); } @@ -2394,6 +2418,7 @@ smtp_tx(struct smtp_session *s) (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; @@ -2562,7 +2587,14 @@ smtp_tx_rcpt_to(struct smtp_tx *tx, const char *line) } } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ORCPT=", 6) == 0) { opt += 6; - if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt)) { + + if (strncasecmp(opt, "rfc822;", 7) == 0) + opt += 7; + + if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt) || + !valid_localpart(tx->evp.dsn_orcpt.user) || + (strlen(tx->evp.dsn_orcpt.domain) != 0 && + !valid_domainpart(tx->evp.dsn_orcpt.domain))) { smtp_reply(tx->session, "553 ORCPT address syntax error"); return; @@ -2598,8 +2630,6 @@ smtp_tx_commit(struct smtp_tx *tx) m_add_msgid(p_queue, tx->msgid); m_close(p_queue); tree_xset(&wait_queue_commit, tx->session->id, tx->session); - report_smtp_tx_commit("smtp-in", tx->session->id, tx->msgid, tx->odatalen); - report_smtp_tx_reset("smtp-in", tx->session->id, tx->msgid); smtp_filter_data_end(tx->session); } @@ -2609,8 +2639,8 @@ 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); - report_smtp_tx_rollback("smtp-in", tx->session->id, tx->msgid); - report_smtp_tx_reset("smtp-in", tx->session->id, tx->msgid); + smtp_report_tx_rollback(tx->session, tx->msgid); + smtp_report_tx_reset(tx->session, tx->msgid); smtp_filter_data_end(tx->session); } @@ -2623,7 +2653,7 @@ smtp_tx_dataline(struct smtp_tx *tx, const char *line) log_trace(TRACE_SMTP, "<<< [MSG] %s", line); if (!strcmp(line, ".")) { - report_smtp_protocol_client("smtp-in", tx->session->id, "."); + smtp_report_protocol_client(tx->session, "."); log_trace(TRACE_SMTP, "<<< [EOM]"); if (tx->error) return 1; @@ -3006,3 +3036,189 @@ smtp_strstate(int 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/usr.sbin/smtpd/smtpc.c index 59479703..c0f5d992 100644 --- a/smtpd/smtpc.c +++ b/usr.sbin/smtpd/smtpc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpc.c,v 1.10 2019/09/21 09:04:08 semarie Exp $ */ +/* $OpenBSD: smtpc.c,v 1.11 2020/09/14 18:32:11 millert Exp $ */ /* * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> @@ -193,7 +193,7 @@ parse_server(char *server) *p = '\0'; p += 3; /* check for credentials */ - c = strchr(p, '@'); + c = strrchr(p, '@'); if (c) { creds = p; *c = '\0'; diff --git a/smtpd/smtpctl.8 b/usr.sbin/smtpd/smtpctl.8 index 1efcff63..b29f2063 100644 --- a/smtpd/smtpctl.8 +++ b/usr.sbin/smtpd/smtpctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpctl.8,v 1.64 2018/09/18 06:21:45 miko Exp $ +.\" $OpenBSD: smtpctl.8,v 1.65 2020/09/14 09:48:08 martijn Exp $ .\" .\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> .\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> @@ -15,7 +15,7 @@ .\" 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 $ +.Dd $Mdocdate: September 14 2020 $ .Dt SMTPCTL 8 .Os .Sh NAME @@ -247,8 +247,13 @@ Shows if MTA, MDA and SMTP systems are currently running or paused. Recursively look up SPF records for the domains read from stdin. For example: .Bd -literal -offset indent -# smtpctl spf walk < domains.txt +$ smtpctl spf walk < domains.txt .Ed +.Pp +SPF records may contain macros which cannot be included in a static list and +must be resolved dynamically at connection time. +.Cm spf walk +cannot provide full results in these cases. .It Cm trace Ar subsystem Enables real-time tracing of .Ar subsystem . diff --git a/smtpd/smtpctl.c b/usr.sbin/smtpd/smtpctl.c index 81c6a17a..1cc37994 100644 --- a/smtpd/smtpctl.c +++ b/usr.sbin/smtpd/smtpctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpctl.c,v 1.165 2019/07/23 08:11:10 gilles Exp $ */ +/* $OpenBSD: smtpctl.c,v 1.167 2020/02/24 16:16:07 millert Exp $ */ /* * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> @@ -1062,13 +1062,20 @@ 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]); +#ifdef NEED_PROGNAME + __progname = 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; @@ -1150,7 +1157,7 @@ sendmail_compat(int argc, char **argv) */ for (i = 1; i < argc; i++) if (strncmp(argv[i], "-bi", 3) == 0) - exit(makemap(P_NEWALIASES, argc, argv)); + exit(makemap(P_SENDMAIL, argc, argv)); if (!srv_connect()) offlinefp = offline_file(); diff --git a/smtpd/smtpctl/Makefile b/usr.sbin/smtpd/smtpctl/Makefile index ef8148be..ef8148be 100644 --- a/smtpd/smtpctl/Makefile +++ b/usr.sbin/smtpd/smtpctl/Makefile diff --git a/smtpd/smtpd-api.h b/usr.sbin/smtpd/smtpd-api.h index f83edd05..f83edd05 100644 --- a/smtpd/smtpd-api.h +++ b/usr.sbin/smtpd/smtpd-api.h diff --git a/smtpd/smtpd-defines.h b/usr.sbin/smtpd/smtpd-defines.h index c5202e34..f22a546f 100644 --- a/smtpd/smtpd-defines.h +++ b/usr.sbin/smtpd/smtpd-defines.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd-defines.h,v 1.10 2018/12/27 15:41:50 gilles Exp $ */ +/* $OpenBSD: smtpd-defines.h,v 1.12 2020/02/24 16:16:08 millert Exp $ */ /* * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org> @@ -48,6 +48,14 @@ #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 "+" 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 <jj@openbsd.org> +.\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> +.\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> +.\" +.\" 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/usr.sbin/smtpd/smtpd.8 index e3429f07..e3429f07 100644 --- a/smtpd/smtpd.8 +++ b/usr.sbin/smtpd/smtpd.8 diff --git a/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index 662e103a..9307fc3b 100644 --- a/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.c,v 1.325 2019/09/03 04:48:20 martijn Exp $ */ +/* $OpenBSD: smtpd.c,v 1.335 2020/09/23 19:11:50 martijn Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -80,6 +80,7 @@ #include <util.h> #endif +#include <openssl/rand.h> #include <openssl/ssl.h> #include <openssl/evp.h> @@ -112,17 +113,17 @@ 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 *); +static int offline_add(char *, uid_t, gid_t); static void offline_done(void); -static int offline_enqueue(char *); +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_processors(void); -static void fork_processor(const char *, const char *, const char *, const char *, const char *); +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, @@ -143,6 +144,8 @@ struct child { struct offline { TAILQ_ENTRY(offline) entry; + uid_t uid; + gid_t gid; char *path; }; @@ -191,7 +194,7 @@ static void parent_imsg(struct mproc *p, struct imsg *imsg) { struct forward_req *fwreq; - struct processor *processor; + struct filter_proc *processor; struct deliver deliver; struct child *c; struct msg m; @@ -295,7 +298,7 @@ parent_imsg(struct mproc *p, struct imsg *imsg) m_get_string(&m, &procname); m_end(&m); - processor = dict_xget(env->sc_processors_dict, procname); + 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); @@ -517,11 +520,11 @@ main(int argc, char *argv[]) char *rexec = NULL; struct smtpd *conf; -#ifndef HAVE___PROGNAME - __progname = ssh_get_progname(argv[0]); +#ifdef NEED_PROGNAME + __progname = get_progname(argv[0]); #endif + __progname = xstrdup(__progname); -#ifndef HAVE_SETPROCTITLE /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ saved_argc = argc; saved_argv = xcalloc(argc + 1, sizeof(*saved_argv)); @@ -529,14 +532,15 @@ main(int argc, char *argv[]) saved_argv[i] = xstrdup(argv[i]); saved_argv[i] = NULL; +#ifdef NEED_SETPROCTITLE /* Prepare for later setproctitle emulation */ compat_init_setproctitle(argc, argv); argv = saved_argv; +#endif /* 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); @@ -1140,13 +1144,13 @@ smtpd(void) { if (pidfile(NULL) < 0) err(1, "pidfile"); - fork_processors(); + fork_filter_processes(); purge_task(); #if HAVE_PLEDGE if (pledge("stdio rpath wpath cpath fattr tmppath " - "getpw sendfd proc exec id inet unix", NULL) == -1) + "getpw sendfd proc exec id inet chown unix", NULL) == -1) err(1, "pledge"); #endif @@ -1319,22 +1323,46 @@ purge_task(void) } static void -fork_processors(void) +fork_filter_processes(void) { const char *name; - struct processor *processor; 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_processors_dict, &iter, &name, (void **)&processor)) - fork_processor(name, processor->command, processor->user, processor->group, processor->chroot); + 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_processor(const char *name, const char *command, const char *user, const char *group, const char *chroot_path) +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 processor *processor; + struct filter_proc *processor; char buf; int sp[2], errfd[2]; struct passwd *pw; @@ -1366,13 +1394,14 @@ fork_processor(const char *name, const char *command, const char *user, const ch /* parent passes the child fd over to lka */ if (pid > 0) { - processor = dict_xget(env->sc_processors_dict, name); + 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; } @@ -1391,7 +1420,7 @@ fork_processor(const char *name, const char *command, const char *user, const ch 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_processor: cannot drop privileges"); + err(1, "fork_filter_process: cannot drop privileges"); xclosefrom(STDERR_FILENO + 1); @@ -1444,6 +1473,8 @@ forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) 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, @@ -1481,7 +1512,7 @@ forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) pw_dir = deliver->userinfo.directory; } - if (pw_uid == 0 && !dsp->u.local.requires_root) { + 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); @@ -1547,6 +1578,11 @@ forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) 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) || @@ -1570,7 +1606,12 @@ forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) /* avoid hangs by setting 5m timeout */ alarm(300); - mda_unpriv(dsp, deliver, pw_name, pw_dir); + 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 @@ -1611,7 +1652,8 @@ offline_scan(int fd, short ev, void *arg) continue; } - if (offline_add(e->fts_name)) { + 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; @@ -1631,7 +1673,7 @@ offline_scan(int fd, short ev, void *arg) } static int -offline_enqueue(char *name) +offline_enqueue(char *name, uid_t uid, gid_t gid) { char *path; struct stat sb; @@ -1693,6 +1735,18 @@ offline_enqueue(char *name) _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", @@ -1749,17 +1803,19 @@ offline_enqueue(char *name) } static int -offline_add(char *path) +offline_add(char *path, uid_t uid, gid_t gid) { struct offline *q; if (offline_running < OFFLINE_QUEUEMAX) /* skip queue */ - return offline_enqueue(path); + 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); @@ -1778,7 +1834,7 @@ offline_done(void) if ((q = TAILQ_FIRST(&offline_q)) == NULL) break; /* all done */ TAILQ_REMOVE(&offline_q, q, entry); - offline_enqueue(q->path); + offline_enqueue(q->path, q->uid, q->gid); free(q); } } @@ -2109,6 +2165,9 @@ imsg_to_str(int type) 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); diff --git a/smtpd/smtpd.conf b/usr.sbin/smtpd/smtpd.conf index efecf2a6..a7ba6c64 100644 --- a/smtpd/smtpd.conf +++ b/usr.sbin/smtpd/smtpd.conf @@ -9,7 +9,7 @@ table aliases file:/etc/mail/aliases # listen on localhost -action "local" mbox alias <aliases> +action "local" maildir alias <aliases> action "relay" relay # Uncomment the following to accept external mail for domain "example.org" diff --git a/smtpd/smtpd.conf.5 b/usr.sbin/smtpd/smtpd.conf.5 index 580d5838..36207c39 100644 --- a/smtpd/smtpd.conf.5 +++ b/usr.sbin/smtpd/smtpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: smtpd.conf.5,v 1.226 2019/09/20 18:47:23 jmc Exp $ +.\" $OpenBSD: smtpd.conf.5,v 1.255 2020/09/23 19:11:50 martijn Exp $ .\" .\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org> .\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> @@ -17,7 +17,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" -.Dd $Mdocdate: September 20 2019 $ +.Dd $Mdocdate: September 23 2020 $ .Dt SMTPD.CONF 5 .Os .Sh NAME @@ -141,8 +141,14 @@ may contain format specifiers that are expanded before use .Pp If the .Cm junk -argument is provided, the message will be moved to the Junk -folder if it contains a positive X-Spam header. +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 . @@ -228,6 +234,10 @@ 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 . @@ -270,6 +280,14 @@ and .Dq smtps protocols for authentication. Server certificates for those protocols are verified by default. +.It Cm pki Ar pkiname +For secure connections, +use the certificate associated with +.Ar pkiname +(declared in a +.Ic pki +directive) +to prove the client's identity to the remote mail server. .It Cm srs When relaying a mail resulting from a forward, use the Sender Rewriting Scheme to rewrite sender address. @@ -295,13 +313,19 @@ The credential table format is described in Use .Ar mailaddr as the MAIL FROM address within the SMTP transaction. -.It Cm src Ar address | Pf < Ar address Ns > +.It Cm src Ar sourceaddr | Pf < Ar sourceaddr Ns > Use the string or list table -.Ar address -for the source IP address. +.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 admd Ar authservid +The Administrative Management Domain this mailserver belongs to. +The authservid will be forwarded to filters using it to identify or mark +authentication-results headers. +If omitted it defaults to the server name. .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 @@ -333,64 +357,26 @@ 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 named -.Ar chain-name -and consisting of the filters listed from +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 Ic disconnect Ar message -Register builtin filter -.Ar filter-name -matching -.Ar conditions -to disconnect session with -.Ar message . -Phase and matching conditions are documented in a specific section, -see -.Sx BUILTIN FILTERING . -.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions Ic junk -Register builtin filter -.Ar filter-name -matching -.Ar conditions -to mark a session or a transaction as junk. -Phase and matching conditions are documented in a specific section, -see -.Sx BUILTIN FILTERING . -.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions Ic reject Ar message -Register builtin filter -.Ar filter-name -matching -.Ar conditions -to reject session with -.Ar message . -Phase and matching conditions are documented in a specific section, -see -.Sx BUILTIN FILTERING . -.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions Ic report Ar message -Register builtin filter -.Ar filter-name -matching -.Ar conditions -to report on session with -.Ar message -and proceed with the transaction. -Phase and matching conditions are documented in a specific section, -see -.Sx BUILTIN FILTERING . -.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions Ic rewrite Ar value -Register builtin filter -.Ar filter-name -matching -.Ar conditions -to rewrite phase parameter with new -.Ar value . -Phase and matching conditions are documented in a specific section, -see -.Sx BUILTIN FILTERING . +.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 @@ -536,17 +522,29 @@ With the option, clients must also provide a valid certificate to establish an SMTP session. .El -.It Ic listen on Cm socket Op Cm mask-src +.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. -If the -.Cm mask-src -option is specified, printing of the HELO name, hostname, and IP -address of the originating host is suppressed in Received: header lines. -.\" XXX The option -.\" Cm filter Ar string -.\" is parsed, but not implemented, see smtpf_session.c. +.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 @@ -586,11 +584,47 @@ 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 regex +.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, @@ -598,6 +632,22 @@ 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 @@ -650,6 +700,20 @@ In addition, the following transaction options: 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 @@ -897,7 +961,7 @@ 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 BUILTIN FILTERING +.Ss MAIL FILTERING In a regular workflow, .Xr smtpd 8 may accept or reject a message based only on the content of envelopes. @@ -913,7 +977,7 @@ then decide if a session is allowed to move forward. With filtering, a session may be interrupted at any phase before an envelope is complete. A message may also be rejected after being submitted, -disregarding if the envelope was accepted or not. +regardless of whether the envelope was accepted or not. .Pp The following phases are currently supported: .Bl -column mail-from -offset indent @@ -926,37 +990,39 @@ The following phases are currently supported: .It commit Ta after message is fully is submitted .El .Pp -At each phase, -multiple criterias may be checked: +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 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 +.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 -All criterias from previous phases are available to subsequent phases, -so while the helo criteria is not available before the helo or ehlo phase, -the fcrdns criteria is available in all phases. -.Pp -Criterias may all be negated by prefixing them with an exclamation mark: +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 +.It !fcrdns Ta forward-confirmed reverse DNS is invalid .El .Pp -Any criteria using a table may indicate that tables hold regex by +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 -Finally, -four decisions may be taken: +.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 +.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 @@ -964,7 +1030,7 @@ four decisions may be taken: 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, -however junking can only happen before a message is committed. +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 @@ -1076,8 +1142,8 @@ action "local_mail" mbox alias <aliases> action "outbound" relay host smtp+tls://bob@smtp.example.com \e auth <secrets> -match for local action "local_mail" -match for any action "outbound" +match from local for local action "local_mail" +match from local for any action "outbound" .Ed .Pp In this second example, @@ -1117,26 +1183,40 @@ 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 -.Sy dkimproxy -package may be used as a filter. -The following example is the same as the default configuration, -but all outgoing mail is passed to dkimproxy_out on port 10027 -for signing. -The signed messages are received on port 10028 and tagged for relaying. +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 -listen on lo0 -listen on lo0 port 10028 tag DKIM +filter "dkimsign" proc-exec "filter-dkimsign -d <domain> -s <selector> \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 <aliases> action "outbound" relay -action "relay_dkim" relay host smtp://127.0.0.1:10027 match for local action "local_mail" -match tag DKIM for any action "outbound" -match for any action "relay_dkim" +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 diff --git a/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index e81da539..beb1d7b4 100644 --- a/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: smtpd.h,v 1.642 2019/11/03 23:58:51 gilles Exp $ */ +/* $OpenBSD: smtpd.h,v 1.659 2020/09/23 19:11:50 martijn Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> @@ -73,7 +73,7 @@ #ifndef SMTPD_NAME #define SMTPD_NAME "OpenSMTPD" #endif -#define SMTPD_VERSION "6.6.1-portable" +#define SMTPD_VERSION "6.7.0-portable" #define SMTPD_SESSION_TIMEOUT 300 #define SMTPD_BACKLOG 5 @@ -124,8 +124,9 @@ #define MTA_EXT_DSN 0x400 -#define P_NEWALIASES 0 -#define P_MAKEMAP 1 +#define P_SENDMAIL 0 +#define P_NEWALIASES 1 +#define P_MAKEMAP 2 #define CERT_ERROR -1 #define CERT_OK 0 @@ -515,6 +516,7 @@ struct envelope { 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; @@ -610,7 +612,7 @@ struct smtpd { size_t sc_scheduler_max_msg_batch_size; size_t sc_scheduler_max_schedule; - struct dict *sc_processors_dict; + struct dict *sc_filter_processes_dict; int sc_ttl; #define MAX_BOUNCE_WARN 4 @@ -648,6 +650,8 @@ struct smtpd { char *sc_srs_key; char *sc_srs_key_backup; int sc_srs_ttl; + + char *sc_admd; }; #define TRACE_DEBUG 0x0001 @@ -1057,22 +1061,29 @@ enum lka_resp_status { LKA_PERMFAIL }; -struct processor { +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_type { - FILTER_TYPE_BUILTIN, - FILTER_TYPE_PROC, - FILTER_TYPE_CHAIN, + 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; @@ -1080,6 +1091,7 @@ struct filter_config { char *rewrite; char *report; uint8_t junk; + uint8_t bypass; char *proc; const char **chain; @@ -1110,6 +1122,15 @@ struct filter_config { 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; @@ -1160,7 +1181,7 @@ enum dispatcher_type { }; struct dispatcher_local { - uint8_t requires_root; /* only for MBOX */ + uint8_t is_mbox; /* only for MBOX */ uint8_t expand_only; uint8_t forward_only; @@ -1187,6 +1208,8 @@ struct dispatcher_remote { char *mail_from; char *smarthost; + int smarthost_domain; + char *auth; int tls_required; int tls_noverify; @@ -1194,6 +1217,8 @@ struct dispatcher_remote { int backup; char *backupmx; + char *filtername; + int srs; }; @@ -1359,7 +1384,7 @@ int lka(void); /* lka_proc.c */ int lka_proc_ready(void); -void lka_proc_forked(const char *, int); +void lka_proc_forked(const char *, uint32_t, int); void lka_proc_errfd(const char *, int); struct io *lka_proc_get_io(const char *); @@ -1398,7 +1423,7 @@ 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 *, const struct sockaddr_storage *, const struct sockaddr_storage *, const char *, int); +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); @@ -1422,6 +1447,11 @@ 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 *); diff --git a/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index 00c7951f..858bfeda 100644 --- a/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.91 2018/06/03 14:04:06 gilles Exp $ +# $OpenBSD: Makefile,v 1.109 2020/09/23 19:11:50 martijn Exp $ .PATH: ${.CURDIR}/.. @@ -24,12 +24,11 @@ SRCS+= ioev.c SRCS+= limit.c SRCS+= lka.c SRCS+= lka_filter.c -SRCS+= lka_proc.c -SRCS+= lka_report.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 diff --git a/smtpd/spfwalk.c b/usr.sbin/smtpd/spfwalk.c index ebae9320..46bf3129 100644 --- a/smtpd/spfwalk.c +++ b/usr.sbin/smtpd/spfwalk.c @@ -33,6 +33,8 @@ #include <err.h> #include <errno.h> #include <event.h> +#include <imsg.h> +#include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -44,15 +46,23 @@ #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 *); -static void dispatch_mx(struct dns_rr *); -static void dispatch_a(struct dns_rr *); -static void dispatch_aaaa(struct dns_rr *); -static void lookup_record(int, const char *, void (*)(struct dns_rr *)); +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, 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; @@ -63,6 +73,7 @@ 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; @@ -85,12 +96,15 @@ spfwalk(int argc, struct parameter *argv) 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(line[linelen])) + while (linelen-- > 0 && isspace((unsigned char)line[linelen])) line[linelen] = '\0'; if (linelen > 0) - lookup_record(T_TXT, line, dispatch_txt); + lookup_record(T_TXT, line, &tgt); } free(line); @@ -106,20 +120,33 @@ spfwalk(int argc, struct parameter *argv) } void -lookup_record(int type, const char *record, void (*cb)(struct dns_rr *)) +lookup_record(int type, char *record, struct target *tgt) { struct asr_query *as; + struct target *ntgt; + size_t i; + if (strchr(record, '%') != NULL) { + for (i = 0; record[i] != '\0'; i++) { + if (!isprint(record[i])) + record[i] = '?'; + } + warnx("%s: %s contains macros and can't be resolved", __func__, + record); + return; + } as = res_query_async(record, C_IN, type, NULL); if (as == NULL) err(1, "res_query_async"); - event_asr_run(as, dispatch_record, cb); + ntgt = xmalloc(sizeof(*ntgt)); + *ntgt = *tgt; + event_asr_run(as, dispatch_record, (void *)ntgt); } void dispatch_record(struct asr_result *ar, void *arg) { - void (*cb)(struct dns_rr *) = arg; + struct target *tgt = arg; struct unpack pack; struct dns_header h; struct dns_query q; @@ -127,7 +154,7 @@ dispatch_record(struct asr_result *ar, void *arg) /* best effort */ if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA) - return; + goto end; unpack_init(&pack, ar->ar_data, ar->ar_datalen); unpack_header(&pack, &h); @@ -136,20 +163,23 @@ dispatch_record(struct asr_result *ar, void *arg) for (; h.ancount; h.ancount--) { unpack_rr(&pack, &rr); /**/ - cb(&rr); + tgt->dispatch(&rr, tgt); } +end: + free(tgt); } void -dispatch_txt(struct dns_rr *rr) +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 buf[4096]; - char buf2[512]; - char *in = buf; - char *argv[512]; - char **ap = argv; - char *end; + char **ap = argv; + char *in = buf; + char *record, *end; ssize_t n; if (rr->rr_type != T_TXT) @@ -179,6 +209,8 @@ dispatch_txt(struct dns_rr *rr) 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, @@ -196,35 +228,50 @@ dispatch_txt(struct dns_rr *rr) if (strcasecmp("a", *ap) == 0) { print_dname(rr->rr_dname, buf2, sizeof(buf2)); buf2[strlen(buf2) - 1] = '\0'; - lookup_record(T_A, buf2, dispatch_a); - lookup_record(T_AAAA, buf2, dispatch_aaaa); + 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) { - lookup_record(T_A, *(ap) + 2, dispatch_a); - lookup_record(T_AAAA, *(ap) + 2, dispatch_aaaa); + 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) { - lookup_record(T_A, *(ap) + 7, dispatch_a); + ltgt.dispatch = dispatch_a; + lookup_record(T_A, *(ap) + 7, <gt); continue; } if (strncasecmp("include:", *ap, 8) == 0) { - lookup_record(T_TXT, *(ap) + 8, dispatch_txt); + ltgt.dispatch = dispatch_txt; + lookup_record(T_TXT, *(ap) + 8, <gt); continue; } if (strncasecmp("redirect=", *ap, 9) == 0) { - lookup_record(T_TXT, *(ap) + 9, dispatch_txt); + 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'; - lookup_record(T_MX, buf2, dispatch_mx); + ltgt.dispatch = dispatch_mx; + lookup_record(T_MX, buf2, <gt); continue; } if (strncasecmp("mx:", *ap, 3) == 0) { - lookup_record(T_MX, *(ap) + 3, dispatch_mx); + record = *(ap) + 3; + if (parse_target(record, <gt) < 0) + continue; + ltgt.dispatch = dispatch_mx; + lookup_record(T_MX, record, <gt); continue; } } @@ -232,9 +279,10 @@ dispatch_txt(struct dns_rr *rr) } void -dispatch_mx(struct dns_rr *rr) +dispatch_mx(struct dns_rr *rr, struct target *tgt) { char buf[512]; + struct target ltgt; if (rr->rr_type != T_MX) return; @@ -243,12 +291,16 @@ dispatch_mx(struct dns_rr *rr) buf[strlen(buf) - 1] = '\0'; if (buf[strlen(buf) - 1] == '.') buf[strlen(buf) - 1] = '\0'; - lookup_record(T_A, buf, dispatch_a); - lookup_record(T_AAAA, buf, dispatch_aaaa); + + 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) +dispatch_a(struct dns_rr *rr, struct target *tgt) { char buffer[512]; const char *ptr; @@ -257,12 +309,16 @@ dispatch_a(struct dns_rr *rr) return; if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr, - buffer, sizeof buffer))) - printf("%s\n", ptr); + 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) +dispatch_aaaa(struct dns_rr *rr, struct target *tgt) { char buffer[512]; const char *ptr; @@ -271,11 +327,15 @@ dispatch_aaaa(struct dns_rr *rr) return; if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6, - buffer, sizeof buffer))) - printf("%s\n", ptr); + buffer, sizeof buffer))) { + if (tgt->cidr6 >= 0) + printf("%s/%d\n", ptr, tgt->cidr6); + else + printf("%s\n", ptr); + } } -static ssize_t +ssize_t parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz) { size_t len; @@ -309,3 +369,33 @@ parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz) 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/usr.sbin/smtpd/srs.c index bb4f4d9e..bb4f4d9e 100644 --- a/smtpd/srs.c +++ b/usr.sbin/smtpd/srs.c diff --git a/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c index a37d6fea..97f7b1df 100644 --- a/smtpd/ssl.c +++ b/usr.sbin/smtpd/ssl.c @@ -400,11 +400,12 @@ ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, #if defined(SUPPORT_ECDSA) if (eckey) ECDSA_set_ex_data(eckey, 0, exdata); +#else + if (eckey) + EC_KEY_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; diff --git a/smtpd/ssl.h b/usr.sbin/smtpd/ssl.h index 11c80c68..11c80c68 100644 --- a/smtpd/ssl.h +++ b/usr.sbin/smtpd/ssl.h diff --git a/smtpd/ssl_smtpd.c b/usr.sbin/smtpd/ssl_smtpd.c index 4e5b7e75..4e5b7e75 100644 --- a/smtpd/ssl_smtpd.c +++ b/usr.sbin/smtpd/ssl_smtpd.c diff --git a/smtpd/ssl_verify.c b/usr.sbin/smtpd/ssl_verify.c index 2e784b97..2e784b97 100644 --- a/smtpd/ssl_verify.c +++ b/usr.sbin/smtpd/ssl_verify.c diff --git a/smtpd/stat_backend.c b/usr.sbin/smtpd/stat_backend.c index 30cb299b..30cb299b 100644 --- a/smtpd/stat_backend.c +++ b/usr.sbin/smtpd/stat_backend.c diff --git a/smtpd/stat_ramstat.c b/usr.sbin/smtpd/stat_ramstat.c index bbf1541a..bbf1541a 100644 --- a/smtpd/stat_ramstat.c +++ b/usr.sbin/smtpd/stat_ramstat.c diff --git a/smtpd/table.5 b/usr.sbin/smtpd/table.5 index e9d4fa4b..e9d4fa4b 100644 --- a/smtpd/table.5 +++ b/usr.sbin/smtpd/table.5 diff --git a/smtpd/table.c b/usr.sbin/smtpd/table.c index 469eeee1..469eeee1 100644 --- a/smtpd/table.c +++ b/usr.sbin/smtpd/table.c diff --git a/smtpd/table_db.c b/usr.sbin/smtpd/table_db.c index f7d766dd..f7d766dd 100644 --- a/smtpd/table_db.c +++ b/usr.sbin/smtpd/table_db.c diff --git a/smtpd/table_getpwnam.c b/usr.sbin/smtpd/table_getpwnam.c index ccf889be..ccf889be 100644 --- a/smtpd/table_getpwnam.c +++ b/usr.sbin/smtpd/table_getpwnam.c diff --git a/smtpd/table_proc.c b/usr.sbin/smtpd/table_proc.c index 44589bd7..44589bd7 100644 --- a/smtpd/table_proc.c +++ b/usr.sbin/smtpd/table_proc.c diff --git a/smtpd/table_static.c b/usr.sbin/smtpd/table_static.c index 8f78ae11..8f78ae11 100644 --- a/smtpd/table_static.c +++ b/usr.sbin/smtpd/table_static.c diff --git a/smtpd/to.c b/usr.sbin/smtpd/to.c index 81a1bb54..81a1bb54 100644 --- a/smtpd/to.c +++ b/usr.sbin/smtpd/to.c diff --git a/smtpd/tree.c b/usr.sbin/smtpd/tree.c index 1d720a59..1d720a59 100644 --- a/smtpd/tree.c +++ b/usr.sbin/smtpd/tree.c diff --git a/smtpd/tree.h b/usr.sbin/smtpd/tree.h index 3d719f09..3d719f09 100644 --- a/smtpd/tree.h +++ b/usr.sbin/smtpd/tree.h diff --git a/smtpd/unpack_dns.c b/usr.sbin/smtpd/unpack_dns.c index 974d5727..974d5727 100644 --- a/smtpd/unpack_dns.c +++ b/usr.sbin/smtpd/unpack_dns.c diff --git a/smtpd/unpack_dns.h b/usr.sbin/smtpd/unpack_dns.h index 2318a0c5..2318a0c5 100644 --- a/smtpd/unpack_dns.h +++ b/usr.sbin/smtpd/unpack_dns.h diff --git a/smtpd/util.c b/usr.sbin/smtpd/util.c index eec3a303..5f9d2113 100644 --- a/smtpd/util.c +++ b/usr.sbin/smtpd/util.c @@ -1,4 +1,4 @@ -/* $OpenBSD: util.c,v 1.150 2019/10/03 04:49:12 gilles Exp $ */ +/* $OpenBSD: util.c,v 1.152 2020/11/29 20:07:38 tb Exp $ */ /* * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. @@ -178,7 +178,7 @@ bsnprintf(char *str, size_t size, const char *format, ...) va_start(ap, format); ret = vsnprintf(str, size, format, ap); va_end(ap); - if (ret < 0 || ret >= (int)size) + if (ret < 0 || (size_t)ret >= size) return 0; return 1; @@ -464,7 +464,7 @@ valid_domainpart(const char *s) if (strlcpy(domain, p, sizeof domain) >= sizeof domain) return 0; - c = strchr(domain, (int)']'); + c = strchr(domain, ']'); if (!c || c[1] != '\0') return 0; @@ -491,7 +491,7 @@ valid_domainpart(const char *s) return res_hnok(s); } -#define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((int)(c)) || isdigit((int)(c))) +#define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c))) #define LABELMAX 63 #define DNAMEMAX 253 diff --git a/smtpd/waitq.c b/usr.sbin/smtpd/waitq.c index 082a1e51..082a1e51 100644 --- a/smtpd/waitq.c +++ b/usr.sbin/smtpd/waitq.c |