summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL249
-rw-r--r--README.md96
-rw-r--r--addons/clamav-0.90.1_output.patch20
-rw-r--r--conf-break9
-rw-r--r--conf-cc18
-rw-r--r--conf-delivery29
-rw-r--r--conf-groups5
-rw-r--r--conf-home17
-rw-r--r--conf-idn28
-rw-r--r--conf-ids15
-rw-r--r--conf-instances14
-rw-r--r--conf-ld11
-rw-r--r--conf-log6
-rw-r--r--conf-man7
-rw-r--r--conf-patrn6
-rw-r--r--conf-qlibs3
-rw-r--r--conf-qmq8
-rw-r--r--conf-spawn6
-rw-r--r--conf-split15
-rw-r--r--conf-svcdir5
-rw-r--r--conf-ucspissl3
-rw-r--r--conf-users15
-rw-r--r--ctl/badloadertypes4
-rw-r--r--ctl/badmailfrom5
-rw-r--r--ctl/badmimetypes12
-rw-r--r--ctl/badrcptto4
-rw-r--r--ctl/locals1
-rw-r--r--ctl/rules.qmtpd.cdbbin0 -> 2308 bytes
-rw-r--r--ctl/rules.qmtpd.txt7
-rw-r--r--ctl/rules.smtpd.cdbbin0 -> 2651 bytes
-rw-r--r--ctl/rules.smtpd.txt10
-rw-r--r--doc/BLURB251
-rw-r--r--doc/CHANGELOG196
-rw-r--r--doc/CHANGELOG_V3108
-rw-r--r--doc/CONTRIBUTERS31
-rw-r--r--doc/EXTTODO228
-rw-r--r--doc/LICENSE63
-rw-r--r--doc/LOGGING94
-rw-r--r--doc/Old/PROPOSAL.mav124
-rw-r--r--doc/Old/README.djbdns63
-rw-r--r--doc/Old/README.mav96
-rw-r--r--doc/Old/README.qmq73
-rw-r--r--doc/Old/README.recipients256
-rw-r--r--doc/Old/README.wildmat100
-rw-r--r--doc/Postgrey.txt233
-rw-r--r--doc/Qmail/BLURB222
-rw-r--r--doc/Qmail/FAQ706
-rw-r--r--doc/Qmail/INSTALL.alias40
-rw-r--r--doc/Qmail/INSTALL.ctl38
-rw-r--r--doc/Qmail/INSTALL.ids72
-rw-r--r--doc/Qmail/INSTALL.maildir59
-rw-r--r--doc/Qmail/INSTALL.mbox53
-rw-r--r--doc/Qmail/INSTALL.qmail84
-rw-r--r--doc/Qmail/INTERNALS186
-rw-r--r--doc/Qmail/PIC.local2alias37
-rw-r--r--doc/Qmail/PIC.local2ext41
-rw-r--r--doc/Qmail/PIC.local2local40
-rw-r--r--doc/Qmail/PIC.local2rem38
-rw-r--r--doc/Qmail/PIC.local2virt44
-rw-r--r--doc/Qmail/PIC.nullclient38
-rw-r--r--doc/Qmail/PIC.relaybad8
-rw-r--r--doc/Qmail/PIC.relaygood33
-rw-r--r--doc/Qmail/PIC.rem2local36
-rw-r--r--doc/Qmail/README269
-rw-r--r--doc/Qmail/REMOVE.binmail16
-rw-r--r--doc/Qmail/REMOVE.sendmail28
-rw-r--r--doc/Qmail/SYSDEPS17
-rw-r--r--doc/Qmail/TEST.deliver82
-rw-r--r--doc/Qmail/TEST.receive41
-rw-r--r--doc/Qmail/THANKS337
-rw-r--r--doc/Qmail/THOUGHTS418
-rw-r--r--doc/Qmail/TODO.djb23
-rw-r--r--doc/Qmail/TODO.done23
-rw-r--r--doc/README.clamav27
-rw-r--r--doc/README.smtpreply72
-rw-r--r--doc/TODO14
-rw-r--r--doc/smtpreplies13
-rw-r--r--etc/qmail-mrtg.pop3d.sample35
-rw-r--r--etc/qmail-mrtg.send.sample108
-rw-r--r--etc/qmail-mrtg.smtpd.sample179
-rw-r--r--man/Makefile512
-rw-r--r--man/Makefile.mandoc512
-rw-r--r--man/TARGETS105
-rw-r--r--man/addresses.5260
-rw-r--r--man/bouncesaying.171
-rw-r--r--man/columnt.129
-rw-r--r--man/condredirect.163
-rw-r--r--man/datetime.373
-rw-r--r--man/dnscname.835
-rw-r--r--man/dnsfq.834
-rw-r--r--man/dnsip.831
-rw-r--r--man/dnsmxip.842
-rw-r--r--man/dnsptr.827
-rw-r--r--man/dnstlsa.851
-rw-r--r--man/dnstxt.829
-rw-r--r--man/dot-qmail.9396
-rw-r--r--man/envelopes.5231
-rw-r--r--man/except.133
-rw-r--r--man/fastforward.1123
-rw-r--r--man/forgeries.7104
-rw-r--r--man/forward.124
-rw-r--r--man/hostname.814
-rw-r--r--man/ipmeprint.815
-rw-r--r--man/maildir.5239
-rw-r--r--man/maildir2mbox.153
-rw-r--r--man/maildirmake.115
-rw-r--r--man/maildirwatch.123
-rw-r--r--man/mailsubj.138
-rw-r--r--man/matchup.1111
-rw-r--r--man/mbox.5235
-rw-r--r--man/newaliases.1366
-rw-r--r--man/newinclude.188
-rw-r--r--man/preline.157
-rw-r--r--man/printforward.116
-rw-r--r--man/printmaillist.115
-rw-r--r--man/qbiff.131
-rw-r--r--man/qmail-authuser.9433
-rw-r--r--man/qmail-badloadertypes.948
-rw-r--r--man/qmail-badmimetypes.946
-rw-r--r--man/qmail-clean.813
-rw-r--r--man/qmail-command.8149
-rw-r--r--man/qmail-control.9110
-rw-r--r--man/qmail-dkim.8217
-rw-r--r--man/qmail-dksign.9336
-rw-r--r--man/qmail-dkverify.8137
-rw-r--r--man/qmail-getpw.9114
-rw-r--r--man/qmail-header.5332
-rw-r--r--man/qmail-inject.8309
-rw-r--r--man/qmail-limits.933
-rw-r--r--man/qmail-local.899
-rw-r--r--man/qmail-log.5448
-rw-r--r--man/qmail-lspawn.846
-rw-r--r--man/qmail-mfrules.9108
-rw-r--r--man/qmail-mrtg.8141
-rw-r--r--man/qmail-newmrh.941
-rw-r--r--man/qmail-newu.943
-rw-r--r--man/qmail-pop3d.846
-rw-r--r--man/qmail-popup.8131
-rw-r--r--man/qmail-postgrey.890
-rw-r--r--man/qmail-pw2u.9241
-rw-r--r--man/qmail-qmaint.865
-rw-r--r--man/qmail-qmqpc.837
-rw-r--r--man/qmail-qmqpd.825
-rw-r--r--man/qmail-qmtpd.836
-rw-r--r--man/qmail-qread.825
-rw-r--r--man/qmail-qstat.818
-rw-r--r--man/qmail-queue.8199
-rw-r--r--man/qmail-recipients.948
-rw-r--r--man/qmail-remote.8795
-rw-r--r--man/qmail-rspawn.821
-rw-r--r--man/qmail-send.9265
-rw-r--r--man/qmail-showctl.812
-rw-r--r--man/qmail-smtpam.8110
-rw-r--r--man/qmail-smtpd.81013
-rw-r--r--man/qmail-start.994
-rw-r--r--man/qmail-tcpok.824
-rw-r--r--man/qmail-tcpto.830
-rw-r--r--man/qmail-todo.8128
-rw-r--r--man/qmail-users.9117
-rw-r--r--man/qmail-vmailuser.9108
-rw-r--r--man/qreceipt.133
-rw-r--r--man/setforward.1204
-rw-r--r--man/setmaillist.172
-rw-r--r--man/spfquery.8147
-rw-r--r--man/splogger.860
-rw-r--r--man/sqmail.9130
-rw-r--r--man/srsforward.196
-rw-r--r--man/srsreverse.987
-rw-r--r--man/tai64nfrac.518
-rw-r--r--man/tcp-environ.586
-rw-r--r--man/xqp.118
-rw-r--r--man/xrecipient.114
-rw-r--r--man/xsender.114
-rw-r--r--package/build1
-rw-r--r--package/command-cp6
-rw-r--r--package/command-ln1
-rw-r--r--package/commands-analog25
-rw-r--r--package/commands-base10
-rw-r--r--package/commands-clients4
-rw-r--r--package/commands-control5
-rw-r--r--package/commands-dkim2
-rw-r--r--package/commands-dns10
-rw-r--r--package/commands-forward8
-rw-r--r--package/commands-log4
-rw-r--r--package/commands-mbox9
-rw-r--r--package/commands-pam4
-rw-r--r--package/commands-pop2
-rw-r--r--package/commands-queue5
-rw-r--r--package/commands-recipients1
-rw-r--r--package/commands-scan1
-rw-r--r--package/commands-server3
-rw-r--r--package/commands-setup0
-rw-r--r--package/commands-srs2
-rw-r--r--package/commands-user4
-rw-r--r--package/commands-x5092
-rwxr-xr-xpackage/compile96
-rwxr-xr-xpackage/control36
-rwxr-xr-xpackage/dir19
-rw-r--r--package/files543
-rwxr-xr-xpackage/ids106
-rwxr-xr-xpackage/install12
-rwxr-xr-xpackage/legacy23
-rwxr-xr-xpackage/man124
-rw-r--r--package/path1
-rwxr-xr-xpackage/qmq162
-rwxr-xr-xpackage/report10
-rwxr-xr-xpackage/rts76
-rwxr-xr-xpackage/run71
-rwxr-xr-xpackage/scripts92
-rwxr-xr-xpackage/service83
-rw-r--r--package/services=d24
-rwxr-xr-xpackage/sslenv42
-rwxr-xr-xpackage/ucspissl34
-rwxr-xr-xpackage/upgrade117
-rw-r--r--package/version1
-rw-r--r--scripts/Makefile64
-rw-r--r--scripts/TARGETS5
-rw-r--r--scripts/it-pam=d1
-rw-r--r--scripts/it-recipients=d1
-rw-r--r--scripts/it-scan=d1
-rw-r--r--scripts/it-x509=d2
-rw-r--r--scripts/it=d4
-rw-r--r--scripts/ksh-auto.sh2
-rw-r--r--scripts/ldap-pam.pl149
-rw-r--r--scripts/mkdkimkey.sh213
-rw-r--r--scripts/multiple-queues.sh112
-rw-r--r--scripts/perl-auto.sh2
-rw-r--r--scripts/qmail-alias2recipients.sh4
-rw-r--r--scripts/qmail-queue-scan.sh144
-rw-r--r--scripts/warn-auto.sh2
-rw-r--r--scripts/x509fingerprint.sh10
-rwxr-xr-xservice/run_log13
-rwxr-xr-xservice/run_pop3d8
-rwxr-xr-xservice/run_pop3sd7
-rwxr-xr-xservice/run_postgrey22
-rwxr-xr-xservice/run_qmqpd10
-rwxr-xr-xservice/run_qmtpd9
-rwxr-xr-xservice/run_qmtpsd11
-rwxr-xr-xservice/run_send3
-rwxr-xr-xservice/run_smtpd12
-rwxr-xr-xservice/run_smtpsd11
-rwxr-xr-xservice/run_smtpsub11
-rw-r--r--service/ssl.env19
-rw-r--r--src/Makefile1521
-rw-r--r--src/TARGETS270
-rw-r--r--src/auto-gid.c47
-rw-r--r--src/auto-int.c38
-rw-r--r--src/auto-int8.c37
-rw-r--r--src/auto-str.c41
-rw-r--r--src/auto-uid.c47
-rw-r--r--src/base64.c119
-rw-r--r--src/bouncesaying.c38
-rw-r--r--src/chkshsgr.c13
-rw-r--r--src/chkspawn.c48
-rw-r--r--src/columnt.c104
-rw-r--r--src/commands.c40
-rw-r--r--src/condredirect.c81
-rwxr-xr-xsrc/config-fast.sh35
-rwxr-xr-xsrc/config.sh64
-rw-r--r--src/constmap.c168
-rw-r--r--src/control.c122
-rw-r--r--src/crypt.lib1
-rw-r--r--src/date822fmt.c29
-rwxr-xr-xsrc/datemail.sh1
-rw-r--r--src/datetime.c53
-rw-r--r--src/datetime_un.c35
-rw-r--r--src/ddist.sh31
-rw-r--r--src/deferrals.sh14
-rw-r--r--src/direntry.h18
-rw-r--r--src/direntry.h28
-rw-r--r--src/dkim.cpp197
-rw-r--r--src/dkimbase.cpp320
-rw-r--r--src/dkimsign.cpp1106
-rw-r--r--src/dkimverify.cpp1443
-rw-r--r--src/dns.c201
-rw-r--r--src/dns_tlsa.c53
-rw-r--r--src/dnscname.c32
-rw-r--r--src/dnsdoe.c14
-rw-r--r--src/dnsfq.c64
-rw-r--r--src/dnsip.c46
-rw-r--r--src/dnsmxip.c106
-rw-r--r--src/dnsptr.c37
-rw-r--r--src/dnstlsa.c96
-rw-r--r--src/dnstxt.c32
-rw-r--r--src/except.c34
-rw-r--r--src/failures.sh14
-rw-r--r--src/fastforward.c399
-rw-r--r--src/fifo.c9
-rwxr-xr-xsrc/find-systype.sh144
-rw-r--r--src/fmtqfn.c22
-rw-r--r--src/fork.h17
-rw-r--r--src/fork.h27
-rw-r--r--src/forward.c58
-rw-r--r--src/gfrom.c8
-rw-r--r--src/headerbody.c78
-rw-r--r--src/hfield.c113
-rw-r--r--src/hier.c163
-rw-r--r--src/hmac_md5.c52
-rw-r--r--src/hostname.c16
-rw-r--r--src/include/auto_break.h6
-rw-r--r--src/include/auto_patrn.h6
-rw-r--r--src/include/auto_qmail.h6
-rw-r--r--src/include/auto_spawn.h6
-rw-r--r--src/include/auto_split.h6
-rw-r--r--src/include/auto_uids.h16
-rw-r--r--src/include/auto_usera.h6
-rw-r--r--src/include/base64.h9
-rw-r--r--src/include/commands.h12
-rw-r--r--src/include/constmap.h21
-rw-r--r--src/include/control.h12
-rw-r--r--src/include/date822fmt.h7
-rw-r--r--src/include/datetime.h20
-rw-r--r--src/include/dkim.h154
-rw-r--r--src/include/dkimbase.h79
-rw-r--r--src/include/dkimsign.h113
-rw-r--r--src/include/dkimverify.h152
-rw-r--r--src/include/dns.h27
-rw-r--r--src/include/dnsdoe.h6
-rw-r--r--src/include/dnsgettxt.h7
-rw-r--r--src/include/exit.h16
-rw-r--r--src/include/extra.h7
-rw-r--r--src/include/fifo.h6
-rw-r--r--src/include/fmtqfn.h8
-rw-r--r--src/include/gfrom.h6
-rw-r--r--src/include/global.h53
-rw-r--r--src/include/headerbody.h6
-rw-r--r--src/include/hfield.h38
-rw-r--r--src/include/hier.h10
-rw-r--r--src/include/hmac_md5.h7
-rw-r--r--src/include/ipalloc.h22
-rw-r--r--src/include/ipme.h14
-rw-r--r--src/include/maildir.h13
-rw-r--r--src/include/md5.h49
-rw-r--r--src/include/mfrules.h9
-rw-r--r--src/include/myctime.h8
-rw-r--r--src/include/newfield.h12
-rw-r--r--src/include/now.h8
-rw-r--r--src/include/prioq.h15
-rw-r--r--src/include/prot.h7
-rw-r--r--src/include/qlx.h18
-rw-r--r--src/include/qmail.h24
-rw-r--r--src/include/qsutil.h17
-rw-r--r--src/include/quote.h10
-rw-r--r--src/include/rcpthosts.h7
-rw-r--r--src/include/readsubdir.h20
-rw-r--r--src/include/readwrite.h11
-rw-r--r--src/include/received.h9
-rw-r--r--src/include/recipients.h8
-rw-r--r--src/include/sendtodo.h14
-rw-r--r--src/include/sha1.h31
-rw-r--r--src/include/sha256.h18
-rw-r--r--src/include/smtpdlog.h73
-rw-r--r--src/include/spf.h111
-rw-r--r--src/include/srs2.h126
-rw-r--r--src/include/strset.h29
-rw-r--r--src/include/tcpto.h25
-rw-r--r--src/include/tls_errors.h42
-rw-r--r--src/include/tls_remote.h32
-rw-r--r--src/include/tls_start.h7
-rw-r--r--src/include/tls_timeoutio.h15
-rw-r--r--src/include/token822.h36
-rw-r--r--src/include/trigger.h9
-rw-r--r--src/include/triggerpull.h6
-rw-r--r--src/include/ucspitls.h45
-rw-r--r--src/include/wildmat.h6
-rw-r--r--src/install.c139
-rw-r--r--src/instcheck.c73
-rw-r--r--src/ipalloc.c7
-rw-r--r--src/ipme.c95
-rw-r--r--src/ipmeprint.c39
-rw-r--r--src/it-analog=d25
-rw-r--r--src/it-base=d10
-rw-r--r--src/it-clients=d4
-rw-r--r--src/it-control=d5
-rw-r--r--src/it-dkim=d2
-rw-r--r--src/it-dns=d10
-rw-r--r--src/it-forward=d8
-rw-r--r--src/it-log=d4
-rw-r--r--src/it-mbox=d9
-rw-r--r--src/it-pam=d4
-rw-r--r--src/it-pop=d2
-rw-r--r--src/it-queue=d5
-rw-r--r--src/it-server=d3
-rw-r--r--src/it-setup=d4
-rw-r--r--src/it-srs=d2
-rw-r--r--src/it-user=d4
-rw-r--r--src/it=d16
-rw-r--r--src/maildir.c97
-rw-r--r--src/maildir2mbox.c156
-rw-r--r--src/maildirmake.c24
-rw-r--r--src/maildirwatch.c123
-rwxr-xr-xsrc/mailsubj.sh7
-rwxr-xr-xsrc/make-compile.sh1
-rwxr-xr-xsrc/make-load.sh2
-rwxr-xr-xsrc/make-makelib.sh16
-rw-r--r--src/matchup.c489
-rw-r--r--src/md5c.c327
-rw-r--r--src/mfrules.c146
-rwxr-xr-xsrc/migrate.sh6
-rw-r--r--src/myctime.c36
-rw-r--r--src/newaliases.c326
-rw-r--r--src/newfield.c59
-rw-r--r--src/newinclude.c317
-rw-r--r--src/now.c8
-rw-r--r--src/predate.c113
-rw-r--r--src/preline.c86
-rw-r--r--src/printforward.c142
-rw-r--r--src/printmaillist.c53
-rw-r--r--src/prioq.c54
-rw-r--r--src/prot.c21
-rw-r--r--src/qbiff.c141
-rw-r--r--src/qmail-authuser.c441
-rw-r--r--src/qmail-badloadertypes.c68
-rw-r--r--src/qmail-badmimetypes.c67
-rw-r--r--src/qmail-clean.c100
-rw-r--r--src/qmail-dkim.cpp343
-rw-r--r--src/qmail-dksign.c511
-rw-r--r--src/qmail-dkverify.c365
-rw-r--r--src/qmail-getpw.c85
-rw-r--r--src/qmail-inject.c793
-rw-r--r--src/qmail-local.c725
-rw-r--r--src/qmail-lspawn.c241
-rw-r--r--src/qmail-mfrules.c173
-rw-r--r--src/qmail-mrtg-queue.sh4
-rw-r--r--src/qmail-mrtg.c322
-rw-r--r--src/qmail-newmrh.c75
-rw-r--r--src/qmail-newu.c132
-rw-r--r--src/qmail-pop3d.c313
-rw-r--r--src/qmail-popup.c301
-rw-r--r--src/qmail-postgrey.c105
-rw-r--r--src/qmail-pw2u.c320
-rw-r--r--src/qmail-qmaint.c594
-rw-r--r--src/qmail-qmqpc.c178
-rw-r--r--src/qmail-qmqpd.c190
-rw-r--r--src/qmail-qmtpd.c354
-rw-r--r--src/qmail-qread.c162
-rwxr-xr-xsrc/qmail-qstat.sh12
-rw-r--r--src/qmail-queue.c305
-rw-r--r--src/qmail-recipients.c77
-rw-r--r--src/qmail-remote.c1458
-rw-r--r--src/qmail-rspawn.c99
-rw-r--r--src/qmail-send.c1440
-rw-r--r--src/qmail-showctl.c372
-rw-r--r--src/qmail-smtpam.c631
-rw-r--r--src/qmail-smtpd.c1715
-rw-r--r--src/qmail-start.c165
-rw-r--r--src/qmail-tcpok.c36
-rw-r--r--src/qmail-tcpto.c95
-rw-r--r--src/qmail-todo.c641
-rwxr-xr-xsrc/qmail-upq.sh14
-rw-r--r--src/qmail-vmailuser.c148
-rw-r--r--src/qmail.c139
-rw-r--r--src/qreceipt.c130
-rw-r--r--src/qsutil.c85
-rw-r--r--src/quote.c81
-rw-r--r--src/rcpthosts.c70
-rw-r--r--src/readsubdir.c44
-rw-r--r--src/received.c172
-rw-r--r--src/recipients.c290
-rw-r--r--src/recipients.sh16
-rw-r--r--src/rhosts.sh18
-rw-r--r--src/rxdelay.sh7
-rw-r--r--src/select.h18
-rw-r--r--src/select.h213
-rw-r--r--src/senders.sh23
-rw-r--r--src/sendmail.c161
-rw-r--r--src/setforward.c173
-rw-r--r--src/setmaillist.c93
-rw-r--r--src/sha1.c188
-rw-r--r--src/sha256.c167
-rwxr-xr-xsrc/smtpdlog.c271
-rw-r--r--src/spawn.c276
-rw-r--r--src/spf.c647
-rw-r--r--src/spfdnsip.c406
-rw-r--r--src/spfquery.c98
-rw-r--r--src/splogger.c70
-rw-r--r--src/srs2.c641
-rw-r--r--src/srsforward.c169
-rw-r--r--src/srsreverse.c172
-rw-r--r--src/strset.c125
-rw-r--r--src/successes.sh13
-rw-r--r--src/suids.sh22
-rw-r--r--src/tai64nfrac.c85
-rw-r--r--src/tcpto.c169
-rw-r--r--src/tcpto_clean.c21
-rw-r--r--src/tls_errors.c158
-rw-r--r--src/tls_remote.c387
-rw-r--r--src/tls_start.c84
-rw-r--r--src/tls_timeoutio.c99
-rw-r--r--src/token822.c461
-rw-r--r--src/trigger.c41
-rw-r--r--src/triggerpull.c16
-rw-r--r--src/trycpp.c7
-rw-r--r--src/trycrypt.c4
-rw-r--r--src/trydnsresolv.c4
-rw-r--r--src/trydrent.c8
-rw-r--r--src/tryflock.c8
-rw-r--r--src/tryidn2.c6
-rw-r--r--src/trylsock.c4
-rw-r--r--src/trymkffo.c7
-rw-r--r--src/trynpbg1.c26
-rw-r--r--src/tryqlibs.c6
-rw-r--r--src/tryrsolv.c6
-rw-r--r--src/trysalen.c11
-rw-r--r--src/trysgact.c10
-rw-r--r--src/trysgprm.c10
-rw-r--r--src/tryshadow.c4
-rw-r--r--src/tryshsgr.c14
-rw-r--r--src/tryslib.c4
-rw-r--r--src/tryspnam.c9
-rw-r--r--src/trysysel.c11
-rw-r--r--src/trysyslog.c9
-rw-r--r--src/tryulong32.c11
-rw-r--r--src/tryuserpw.c9
-rw-r--r--src/tryutmp.c7
-rw-r--r--src/tryvfork.c4
-rw-r--r--src/trywaitp.c7
-rw-r--r--src/warn-auto.sh3
-rw-r--r--src/warn-shsgr3
-rw-r--r--src/wildmat.c109
-rw-r--r--src/xqp.sh9
-rw-r--r--src/xrecipient.sh6
-rw-r--r--src/xsender.sh9
-rw-r--r--src/zddist.sh7
-rw-r--r--src/zdeferrals.sh8
-rw-r--r--src/zfailures.sh8
-rw-r--r--src/zoverall.sh77
-rw-r--r--src/zrecipients.sh10
-rw-r--r--src/zrhosts.sh10
-rw-r--r--src/zrxdelay.sh8
-rw-r--r--src/zsenders.sh13
-rw-r--r--src/zsendmail.sh18
-rw-r--r--src/zsuccesses.sh8
-rw-r--r--src/zsuids.sh13
534 files changed, 54437 insertions, 0 deletions
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..18c293f
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,249 @@
+Configuration and Installation of s/qmail
+-----------------------------------------
+
+HOW TO INSTALL:
+- s/qmail uses D.J.B's slashpackage convention
+ for installing while trying to conserve the
+ standard qmail installations:
+ * untar the sqmail tar file under '/package'
+ * Move to /package/mail/sqmail/sqmail-V.R.F
+ and go on with installation
+- Set up the s/qmail package with the following
+ step-by-step options or simple run (as 'root'):
+ * package/install -- does it all
+
+A) REQUIREMENTS
+
+1. Compiler & make utilities.
+2. fehQlibs are installed (typically as /usr/local/qlibs)
+3. The directory /package is in place.
+4. Header files and libs for *SSL.
+5. Header files and libraries for IDN2 support.
+6. The UCSPI-SSL package to be installed.
+7. Header files and libraries for IDN2 support (optional).
+
+Optional but very useful:
+
+8. The UCSPI-TCP6 package (tcprules, rblsmtpd).
+9. DJB's Daemontools installed and working.
+10. MRTG to display logging.
+
+
+B) CONFIGURATION
+
+1. Configuration is done by means of the
+ `conf-XX` files in this main directory.
+
+2. Short description:
+
+ conf-break -- the character for VERP addresses [-]
+ conf-cc -- compiler (no change required)
+ conf-delivery -- qmail-start default-delivery
+ conf-djbdns -- DJBNDS libs (not supported yet)
+ conf-groups*) -- s/qmail groups
+ conf-home -- home dir of s/qmail [/var/qmail]
+ conf-idn2 -- include optional path for libidn2
+ conf-ids*) -- Unix ids for s/qmail
+ conf-instances -- QMQ instances to be raised
+ conf-ld -- loader options to be adjusted (for i386; AMD64 default)
+ conf-log -- target dir of s/qmail logs [/var/log]
+ conf-man -- target dir of man pages, usually automatically recognized
+ conf-patrn -- s/qmail paternalism [002]
+ conf-qmq -- QMQ environment settings
+ conf-spawn -- silent concurrency limit [120]
+ conf-split -- depth of s/qmail dirs [23]
+ conf-svcdir -- supervise's directory [/service]
+ conf-ssl -- path to *SSL header files [empty for defaults]
+ conf-ucspissl -- path to UCSPI-SSL dirs
+ conf-users*) -- user names
+
+ Configurations labeled with *) need to be treated together.
+
+3. Depending on your settings, you may need to
+ adjust the following:
+
+ a) conf-cc: Perhaps remove the -DIDN2 option
+ if libidn2 is not installed.
+ Other options are:
+ -DHIDEVIRTUALUSER
+ -DDEFERREDBOUNCES
+ -DSHOWLOG
+ b) conf-ld: Adjust architecture of executables.
+ If you use OpenSSL/LibreSSL from sources outside the
+ default, you need to include the link path (-L).
+ c) conf-idn2: Include optional path to 'libidn2'.
+
+4. s/qmail user settings:
+
+ a) conf-ids: The UIDs and GIDs
+ b) conf-groups:The s/qmail group names.
+ c) conf-users: The s/qmail user names.
+
+5. Directories and system interaction:
+
+ a) conf-home
+ b) conf-qlibs
+ c) conf-ssl
+ d) conf-ucspissl
+ e) conf-log
+ f) conf-man
+ g) conf-svcdir
+
+6. Run-time issues:
+
+ a) conf-break
+ b) conf-patrn
+ c) conf-split
+ d) conf-delivery
+ e) conf-instances (still not working yet)
+ f) conf-qmq (still not uptodate jet)
+
+
+C) INSTALLATION
+
+1. Upon configuration and verification
+ to meet requirements, simply do
+
+ package/install
+
+2. Detail description of installation steps:
+
+ package/dir -- sets up the directories
+ package/ids -- sets up the s/qmail users
+ package/ucspissl -- hooks up the required sources and libs with package ucspi-ssl
+ package/compile -- compiles the sources
+ package/upgrade -- potentially does the upgrade
+ package/legacy -- installs the binaries in the qmail directory
+ package/man -- installes the man pages
+
+ All done be package/install. Additional (initial) settings:
+
+ package/control -- populates the mininmal required control files for running
+ package/sslenv -- sets up the SSL/TLS environments together with X.509 certs and key files (from ucspi-ssl)
+ package/service -- sets up the run script for daemontools' /service and additionally the logging
+ package/scripts setup optional, undocumented and unmaintained scripts
+ package/run -- touches qmail/alias/ files and sets default-delivery
+
+3. Installation on OpenBSD
+
+ s/qmail should be placed under
+ /usr/local/qmail
+ -- or --
+ mount -u -o suid /var
+
+4. Upgrade from an existing Qmail
+
+ s/qmail will keep your current qmail setup (except for the binaries):
+
+ * Make sure, to have ucspi-ssl installed
+ * Extract s/qmail under /package
+ * cd /package/mail/sqmail-V.R.F
+ * package/ucspissl
+ * package/compile
+ * package/legacy
+ * package/man
+ * package/upgrade
+
+ In case your qmail installation is out of default, use the conf-* settings (ie. ids).
+ Make sure, that your qmail 'todo' queue and the 'tcpto' table is empty (qmail-tcpto, qmail-tcpok).
+
+ You need to change the port separator in the control files from ':' to ';' - if applicable.
+
+5. Deinstallation and re-do installation
+
+ Within s/qmail's installation directory (where this file resides)
+ simply do:
+
+ rm -r compile
+
+ Alternatively, you can do
+
+ cd compile; make clean
+
+ To re-install man-pages:
+
+ cd man; rm *.gz; make clean
+
+ Now you can continue with re-installation.
+
+6. Additional compile-time options
+
+ conf-cc allows you to customize compilation for the following needs:
+
+ - Internationalization: Include the option -IDN2.
+ Be sure, to have IDN2 installed prior of compilation.
+
+ - Virtual user obfuscation: Include the option -DHIDEVRITUALUSER.
+ Now, the virtual user extension is excluded in the mail header
+ for the displayed addresses. Vpopmail, however, requires this!
+
+ - Delayed bounces: Use -DDEFERREDBOUNCES.
+ Now, qmail-remote will retry mail delivery even for not DNS
+ resolveable host names and IP addresses until queue lifetime
+ expires.
+
+ - DKIM private key names used for signing are shown
+ in qmail-remote logs via optin -DSHOWLOG.
+
+ - Check conf-cc for more restrictive settings.
+
+
+D) DKIM CONFIGURATION
+
+1. Key generation:
+ You need to generate a public/private key pair.
+ The private key is used to sign outgoing mails.
+ The public key needs to be in the DNS as DKIM TXT record.
+ Use the script mkdkimkey (after make in that directory)
+ to generate RSA/Ed25519 key pairs in the required format.
+
+2. Signing operation:
+ Populate the private key in the directory
+ ssl/domainkeys/<domain>
+ and symlink it as 'default' (= selector).
+ Key roll-over is easily supported with different selectors.
+ Create
+ control/dkimdomains
+ with the entry '=:' defaulting to your domain/MTA.
+ Several domain entries with different attributes can be used.
+ Upon raising the file 'control/dkimdomains' all outgoing
+ emails will be automatically DKIM signed in case the
+ sending domains are listed therein.
+
+3. Verification operation:
+ Use qmail-dkverify as paramater in your 'smtpd.tcpd' file:
+ :allow,QMAILQUEUE="bin/qmail-qmail-dkverify"
+ Usually, qmail-dkverify works in annotation mode only, thus
+ simply inlcudes a header for further message processing like this:
+ X-Authentication-Results: piplus.fehcom.de; dkim=pass; bigchief.fehcom.de
+
+ If you however set 'DKIM=+' as environment variable, mails
+ failing DKIM verification (wrong signature) will be rejected upon receipt.
+ This is not recommended, since mails may be subject of re-writing
+ by mail-scanning MTAs.
+
+Note: DKIM is inappropriate with QMTP(S) delivery.
+
+E) MISCELLANEOUS
+
+1. s/qmail comes with a full set of updated man-pages.
+
+2. s/qmail supports SPF and SRS natively without additional libs.
+
+3. qmail-postgrey requires postgrey: [https://postgrey.schweikert.ch/]
+
+4. Further documentation can be found in ./doc
+
+5. Convenience files can be found in ./etc
+
+6. Samples for control files are provided in ./ctl
+
+7. Additional scripts are located in ./scripts
+
+8. Start-scripts (for Daemontools) reside in ./service
+
+
+Visit https://www.fehcom.de/sqmail/sqmail.html to
+access online man-pages and documentation.
+
+Date: June, 18th 2023 (feh)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bb3b6c6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+/* \mainpage
+
+s/qmail -- fast, secure, and reliable email transmission
+========================================================
+
+WHAT IT IS:
+----------
+- s/qmail is a fork of qmail (1.03) including the
+ features of the Spamcontrol patch together with
+ IPv6 capabilities and is 64 bit clean
+- s/qmail is API- and plug-in-compatible with qmail,
+ thus add-ons like vpopmail, ezmlm, and vmailmgr
+ and many others work without changes
+- TLS 1.3 enabled using ucspi-ssl
+- X.509 cert pinning and allowing strict TLS mode
+- Automatic TLSA lookup
+- DKIM signing and verification with RSA and Ed25519
+- Wide scale QMTPS support
+- Multi-tenancy capabilities
+- s/qmail uses the concept of D.J. Bernstein's
+ coding without compromise
+- Linux systemd compatible
+
+
+INCLUDING:
+--------
+The following (DJB) packages are included:
+
+- checkpassword (as qmail-authuser)
+- fastforward
+- qmailanalog
+- qmail-mrtg
+
+
+REQUIREMENTS:
+------------
+- fehQlibs (-22) -- can't build w/o it
+- ucspi-ssl (> 0.12.x) -- won't run without it
+- ucspi-tcp6 (generating the cdb)
+- daemontools package (supervising the services),
+ any other will do as well
+- libidn2 for EAI support
+- OpenSSL > 1.1.1 or LiberSSL > 3.7.0 to support Ed25519 signatures
+
+
+INSTALLATION:
+------------
+- Read the INSTALL document
+
+
+INTERNAL CHANGES FROM QMAIL:
+---------------------------
+- Group is now 'sqmail' instead of 'qmail'
+- Exttodo + Bigtodo is default
+- qmail(-queue) supports additional control tokens and return codes
+- Old qmail code for sendmail compatibility removed
+- Added QMTPS capabilities (receiving and sending)
+- IPv6 supported by default
+- AMD64 enabled (64 + 32 bit clean), works on ARM/ARM64
+- SPF DNS lookup for qmail-smtpd
+- SRS: srsforward & srsreverse
+- SMTPUTF8 + IDN2 support for qmail-remote
+- fehQlibs DNS stub resolver
+- qmail-postgrey client
+- TLSA DNS lookup for qmail-remote
+- Additional queue/dkim staging directories
+
+
+USER INTERFACE CHANGES:
+----------------------
+- Port separator is ';' instead of ':' (due to IPv6)
+- smtproutes supports authentication and localip setting
+- Added SPF capabilities for qmail-smtpd + spfquery for testing
+- Added DNS test routines: dnsmxip, dnsfq, dnscname, dnstxt, dnstlsa
+- Added fastforward package
+- Added qmailanalog and qmail-mrtg
+- Additional TLS control files for X.509 certificates,
+ key files and verification handling
+- RECIPIENTS extension from Spamcontol
+- Added PAMs for Recipient verification
+ (qmail-vmailuser, qmail-smtpam)
+- Added qmail-authuser PAM for SMTP and POP3 authentication;
+ supporting Dovecot natively
+- Added qmail-qmaint for queue maintenance
+- Added 'implicit TLS' support for qmail-remote and qmail-smtpam
+ Port numbers may now prepended with 's' -> implicit TLS
+- qmail-users: changed name from 'users/cdb' to 'users/assign.cdb'
+- Added qmail-dksign and qmail-dkverify together with qmail-dkim
+- DKIM keys are given at SQMAIL/ssl/domainkeys/<domain>
+
+
+s/qmail (4.2) -- this README covers the main s/qmail merits.
+
+See doc/CHANGELOG for version information.
+
+Date: June, 18th 2023 (feh)
diff --git a/addons/clamav-0.90.1_output.patch b/addons/clamav-0.90.1_output.patch
new file mode 100644
index 0000000..2ef5e27
--- /dev/null
+++ b/addons/clamav-0.90.1_output.patch
@@ -0,0 +1,20 @@
+--- output.c 2007-03-02 01:43:18.000000000 +0100
++++ ../../clamav-0.90.1-mod/shared/output.c 2007-03-03 18:49:06.000000000 +0100
+@@ -266,7 +266,16 @@
+ if(mprintf_disabled)
+ return;
+
+- fd = stdout;
++/* fd = stdout; */
++
++
++/* fd = stdout; (missing flag 'mprintf_stdout') --eh. */
++
++ if(mprintf_stdout)
++ fd = stdout;
++ else
++ fd = stderr;
++
+
+ /* legend:
+ * ! - error
diff --git a/conf-break b/conf-break
new file mode 100644
index 0000000..2cfacf5
--- /dev/null
+++ b/conf-break
@@ -0,0 +1,9 @@
+-
+
+# This character is the user-ext delimiter. The default delimiter is -,
+# meaning that user joe controls joe-anything. Some system administrators
+# prefer + or =.
+
+# You can override this choice at run time with the qmail-users mechanism.
+
+# Multicharacter delimiters are not permitted.
diff --git a/conf-cc b/conf-cc
new file mode 100644
index 0000000..605c327
--- /dev/null
+++ b/conf-cc
@@ -0,0 +1,18 @@
+cc -O2 -Wall -Wno-narrowing -Iinclude -I`head -1 ../conf-qlibs`/include `head -1 ../conf-ssl`
+
+# This will work for both i386 and AMD64 architecture enabling INET6 support.
+# IDN2 support is NOT enabled by default. You do not have 'libidns2' installed and set: -DIDN2
+
+# For obfuscation, you can hide the virtual user's local part for VERP addresses; inappropriate for VPOPMAIL:
+
+cc -O2 -Wall -Wno-narrowing -Iinclude -I`head -1 ../conf-qlibs`/include `head -1 ../conf-ssl` -DHIDEVIRTUALUSER
+
+# qmail-remote will bounce mails immediately, if no DNS record is found; or mail may stay in the queue until it expires:
+
+cc -O2 -Wall -Wno-narrowing -Iinclude -I`head -1 ../conf-qlibs`/include `head -1 ../conf-ssl` -DDEFERREDBOUNCES
+
+# security might be enhanced, using the following compiler flags:
+
+cc -Wall -pipe -z relro -z now -pie -fPIE -fstack-protector-all -D_FORTIFY_SOURCE=2 -O2 -DIDN2
+
+# This is for gcc and with strong security in mind.
diff --git a/conf-delivery b/conf-delivery
new file mode 100644
index 0000000..ddee2a0
--- /dev/null
+++ b/conf-delivery
@@ -0,0 +1,29 @@
+./Maildir/
+
+** Note: Only the first line will be evaluated! **
+
+(1) This is the qmail-start standard delivery to local Maildirs.
+
+./Mailbox
+
+(2) This is the qmail-start standard delivery to local mbox'es.
+
+./Mailbox splogger qmail
+
+(3) This is the qmail-start standard delivery to local mbox'es
+ and additional logging to syslog via splogger.
+
+'|preline procmail' splogger qmail
+
+(4) Using procmail to deliver messages to /var/spool/mail/$USER by default.
+ Using splogger to send the log through syslog.
+
+'|dot-forward .forward |preline procmail' splogger qmail
+
+(5) Using dot-forward to support sendmail-style ~/.forward files.
+ Using procmail to deliver messages to /var/spool/mail/$USER by default.
+ Using splogger to send the log through syslog.
+
+'|preline -f /usr/local/libexec/dovecot/dovecot-lda'
+
+(6) Using dovecot's local delivery agent.
diff --git a/conf-groups b/conf-groups
new file mode 100644
index 0000000..77353b5
--- /dev/null
+++ b/conf-groups
@@ -0,0 +1,5 @@
+sqmail
+nofiles
+
+# The s/qmail groups: sqmail is used for binary and man files;
+# nofiles for auxiliary files.
diff --git a/conf-home b/conf-home
new file mode 100644
index 0000000..ae70bfa
--- /dev/null
+++ b/conf-home
@@ -0,0 +1,17 @@
+/var/qmail
+
+# This is the sqmail home directory. It must be a local directory, not
+# shared among machines. This is where qmail queues all mail messages.
+
+/usr/local/qmail
+
+# This is the alternative of OS with don't allow suid on /var (OpenBSD).
+
+# The queue (except for bounce message contents) is crashproof, if the
+# filesystem guarantees that single-byte writes are atomic and that
+# directory operations are synchronous. These guarantees are provided by
+# fixed-block filesystems such as UFS and by journaling filesystems. Under
+# Linux, make sure that all mail-handling filesystems are mounted with
+# synchronous metadata.
+
+# Note: The sqmail binaries do not need to share the same mount point.
diff --git a/conf-idn2 b/conf-idn2
new file mode 100644
index 0000000..5d45d02
--- /dev/null
+++ b/conf-idn2
@@ -0,0 +1,8 @@
+-L /usr/local/lib
+
+# On Linux system, an 'empty' line is fine.
+
+-L /usr/local/lib
+
+# In case, the libidn2 is residing elsewhere,
+# (eg. FreeBSD) you need to include the path.
diff --git a/conf-ids b/conf-ids
new file mode 100644
index 0000000..48a98f8
--- /dev/null
+++ b/conf-ids
@@ -0,0 +1,15 @@
+# sqmail Unix group-ids and user-ids
+# Change ids on your own behalf;
+# sqmail user names require change of conf-users in addition
+#
+2108:nofiles:sqmail group for auxiliar files:
+2109:sqmail:sqmail group for binary files:
+#
+7790:alias:sqmail Alias user:nofiles:alias
+7791:qmaild:sqmail Daemon user:nofiles
+7792:qmaill:sqmail Log user:nofiles
+7793:qmailp:sqmail Password user:nofiles
+7794:qmailq:sqmail Queue user:sqmail:queue
+7795:qmailr:sqmail Remote user:sqmail
+7796:qmails:sqmail Send user:sqmail
+7797:sqmtls:sqmail TLS user:nofiles:ssl
diff --git a/conf-instances b/conf-instances
new file mode 100644
index 0000000..dd96595
--- /dev/null
+++ b/conf-instances
@@ -0,0 +1,14 @@
+## Here, you define the multiple queue instances by name
+## Lines with preceeding '#' are ignored as well as any comment after the '#'
+## Avoid withspaces here
+## IPv4 and IPv6 addresses are possible -- bind & delivery IP address
+#
+# Instance-ID : Alias Name : IP Address
+# ----------- ---------- ----------
+#
+#00:Internal_Me:#base-IP # Mails for me will be delivered here
+#01:Customer_1:#customer-IP # 1st Customer delivery instance
+#02:Customer_2:#customer-IP # 2nd Customer delivery instance
+#80:INTERNET:#outgoing-IP # Regular Mails send to the INTERNET are going this way
+#90:BOUNCES:#2nd-out-IP # Bounce Mails will make that way; avoid blacklisting by means of a separate IP
+#99:BACKUP:127.0.0.1 # Spam and Virus storms will be redirected to this instance -- on demand
diff --git a/conf-ld b/conf-ld
new file mode 100644
index 0000000..0ce8ac7
--- /dev/null
+++ b/conf-ld
@@ -0,0 +1,11 @@
+cc -s -m64
+
+cc -s -z noexecstack -m64
+
+# This is for AMD64 architecture; use else:
+
+cc -s
+
+# This will be used to link .o files into an executable.
+
+# Note: UCSPI-SSL's conf-ld needs to use the same architecture!
diff --git a/conf-log b/conf-log
new file mode 100644
index 0000000..6dc338b
--- /dev/null
+++ b/conf-log
@@ -0,0 +1,6 @@
+/var/log
+
+# This is the s/qmail high-level log dir.
+# Running package/service will generate automatically the
+# required individual log dirs named after the service,
+# ie. qmail-send, qmail-smtpd .... etc.
diff --git a/conf-man b/conf-man
new file mode 100644
index 0000000..bbb5a1a
--- /dev/null
+++ b/conf-man
@@ -0,0 +1,7 @@
+
+
+# Here, the location of the s/qmail man pages can be specified.
+# If this line is empty (or dir invalid), the default is taken
+# from manpath.
+# Typical directories -- /usr/local/man, /usr/share/man -- are
+# considered automatically; except for OpenBSD.
diff --git a/conf-patrn b/conf-patrn
new file mode 100644
index 0000000..3c62a89
--- /dev/null
+++ b/conf-patrn
@@ -0,0 +1,6 @@
+002
+
+# These stat bits are not allowed in ~ and ~/.qmail. On most systems, the
+# default umask is 022 or 077, so 022 will work here.
+
+# Note that ~ftp, ~www, ~uucp, etc. should be owned by root.
diff --git a/conf-qlibs b/conf-qlibs
new file mode 100644
index 0000000..325b721
--- /dev/null
+++ b/conf-qlibs
@@ -0,0 +1,3 @@
+/usr/local/qlibs
+
+# This is the path to your qlibs directory (-I not required here)
diff --git a/conf-qmq b/conf-qmq
new file mode 100644
index 0000000..10d773f
--- /dev/null
+++ b/conf-qmq
@@ -0,0 +1,8 @@
+# conf-qmq environment settings
+# Here, you may change some global settings
+
+# Note: The multiple-queue script is provisionally only
+
+export SKELETON_CONCURRENCYREMOTE="120"
+export SKELETON_QUEUELIFETIME="1440" # 24 Hours
+export SKELETON_PORT="1000" # high ports for QMQ
diff --git a/conf-spawn b/conf-spawn
new file mode 100644
index 0000000..35950a3
--- /dev/null
+++ b/conf-spawn
@@ -0,0 +1,6 @@
+120
+
+# This is a silent concurrency limit. You can't set it above 1024.
+# On some systems you can't set it above 124 or you need to adjust
+# kernel parameters.
+# s/qmail will refuse to compile if the limit is too high.
diff --git a/conf-split b/conf-split
new file mode 100644
index 0000000..45989a9
--- /dev/null
+++ b/conf-split
@@ -0,0 +1,15 @@
+23
+
+# This is the queue subdirectory split.
+
+127
+
+# This can ben benefial for a server handling 100k messages/day.
+
+521
+
+# This helps for ~ 500k messages/day.
+
+1223
+
+# You should think about splitting your service.
diff --git a/conf-svcdir b/conf-svcdir
new file mode 100644
index 0000000..9722552
--- /dev/null
+++ b/conf-svcdir
@@ -0,0 +1,5 @@
+/service
+
+# This is the daemontools supervise '/service' high-level directory.
+# s/qmail services are installed in specific subdirectories here under,
+# typically together with their logging companions.
diff --git a/conf-ucspissl b/conf-ucspissl
new file mode 100644
index 0000000..024382d
--- /dev/null
+++ b/conf-ucspissl
@@ -0,0 +1,3 @@
+/package/host/superscript.com/net/ucspi-ssl
+
+# Define here the path to UCSPI-SSL
diff --git a/conf-users b/conf-users
new file mode 100644
index 0000000..42a5276
--- /dev/null
+++ b/conf-users
@@ -0,0 +1,15 @@
+alias
+qmaild
+qmaill
+root
+qmailp
+qmailq
+qmailr
+qmails
+
+# The s/qmail system is heavily partitioned for security; it does almost
+# nothing as root (except for authentication based on PAMs).
+
+# The first eight lines of this file are the alias user, the daemon user,
+# the log user, the owner of miscellaneous files such as binaries, the
+# passwd user, the queue user, the remote user, and the send user.
diff --git a/ctl/badloadertypes b/ctl/badloadertypes
new file mode 100644
index 0000000..8c7b744
--- /dev/null
+++ b/ctl/badloadertypes
@@ -0,0 +1,4 @@
+Mi5kb
+MzIuZ
+MyLmR
+MyLkR
diff --git a/ctl/badmailfrom b/ctl/badmailfrom
new file mode 100644
index 0000000..1d64b47
--- /dev/null
+++ b/ctl/badmailfrom
@@ -0,0 +1,5 @@
+# Wildmat evaluates from least specific to most specific
+*
+!
+!*@*.*
+*%*
diff --git a/ctl/badmimetypes b/ctl/badmimetypes
new file mode 100644
index 0000000..9020fa8
--- /dev/null
+++ b/ctl/badmimetypes
@@ -0,0 +1,12 @@
+TVqQAAMAA
+TVpQAAIAA
+TVpAALQAc
+TVpyAXkAX
+TVrmAU4AA
+TVrhARwAk
+TVoFAQUAA
+TVoAAAQAA
+TVoIARMAA
+TVouARsAA
+TVrQAT8AA
+TVoAAAEAA
diff --git a/ctl/badrcptto b/ctl/badrcptto
new file mode 100644
index 0000000..a9a118c
--- /dev/null
+++ b/ctl/badrcptto
@@ -0,0 +1,4 @@
+# Wildmat evaluates from least specifc to most specific
+*%*
+*\ *@*
+!*@*.*
diff --git a/ctl/locals b/ctl/locals
new file mode 100644
index 0000000..2fbb50c
--- /dev/null
+++ b/ctl/locals
@@ -0,0 +1 @@
+localhost
diff --git a/ctl/rules.qmtpd.cdb b/ctl/rules.qmtpd.cdb
new file mode 100644
index 0000000..3d9f9d5
--- /dev/null
+++ b/ctl/rules.qmtpd.cdb
Binary files differ
diff --git a/ctl/rules.qmtpd.txt b/ctl/rules.qmtpd.txt
new file mode 100644
index 0000000..1a1d32d
--- /dev/null
+++ b/ctl/rules.qmtpd.txt
@@ -0,0 +1,7 @@
+10.0.0.0/8:allow
+172.16.0.0/12:allow
+192.168.0.0/16:allow
+127.0.0.1/31:allow
+fe80::/10:allow
+fc00::/7:allow
+:deny
diff --git a/ctl/rules.smtpd.cdb b/ctl/rules.smtpd.cdb
new file mode 100644
index 0000000..fd6b949
--- /dev/null
+++ b/ctl/rules.smtpd.cdb
Binary files differ
diff --git a/ctl/rules.smtpd.txt b/ctl/rules.smtpd.txt
new file mode 100644
index 0000000..99dcba4
--- /dev/null
+++ b/ctl/rules.smtpd.txt
@@ -0,0 +1,10 @@
+10./8:allow,RELAYCLIENT=""
+172./12:allow,RELAYCLIENT=""
+192.168./16:allow,RELAYCLIENT=""
+127.0.0.1/31:allow,RELAYCLIENT=""
+::1/127:allow,RELAYCLIENT=""
+fe80::/10:allow,RELAYCLIENT=""
+fc00::/7:allow,RELAYCLIENT=""
+=localhost:allow,RELAYCLIENT=""
+=:allow,SPF='3',HELOCHECK="!"
+:deny
diff --git a/doc/BLURB b/doc/BLURB
new file mode 100644
index 0000000..ba7ad5a
--- /dev/null
+++ b/doc/BLURB
@@ -0,0 +1,251 @@
+s/qmail BLURB
+=============
+
+s/sqmail inherits all features of qmail, since it includes its
+concept and its code.
+
+Confidentially: s/qmail adds transmission confidentially by means
+of TLS encryption. TLS encryption is provdided by for all protocols
+except for QMTP while requiring UCSPI-SSL.
+
+Privacy: s/qmail does currently not provide email privacy.
+The persistance storage (Queue) is unencrypted and shared.
+This might be changed in forthcoming releases.
+
+Authentication: s/sqmail supports user authentication for sending
+and receiving mails by means of SMTP(S). QMTP and QMQP however, are
+solely host-to-host mail transfer protocols.
+
+Distribution: s/qmail uses the concept of distributed queues to be
+fed either by SMTP or QMTP/QMQP.
+
+Multi-domain capability: s/qmails allows to set up differently
+parametrized transport/distribution pathes based on the domains
+under control of the MTA. This concept is close to a multi-tenant
+behavior; regarding the domain, not the individual recipient/sender.
+
+
+Authenticated Email Senders
+===========================
+
+Within s/qmail both
+
+* qmail-smtpd for receiving emails and
+* qmail-remote for sending emails
+
+support authentication regarding the methods
+
+- PLAIN,
+- LOGIN, and
+- CRAM-MD5.
+
+Additionally,
+
+* qmail-smtpd accepts authentication based on
+
+- X.509 client certs.
+
+* qmail-popup together with
+* qmail-pop3d
+
+provide authentication by means of the methods
+
+- USER and
+- APOP.
+
+The authentication module
+
+* qmail-authuser
+
+replaces the old
+
+* checkpassword and perhaps
+* cmd5checkpw
+
+programs with much more flexibility.
+Given a LDAP infrastucture,
+
+* qmail-ldapam
+
+can be used to call the user data from here.
+
+
+Validation receiving Mails
+==========================
+
+Within s/sqmail
+
+* qmail-smtpd,
+* qmail-qmtpd, and
+* qmail-qmqpd
+
+are able to receive email from the Internet.
+
+While
+
+* qmail-qmtpd and
+* qmail-qmqpd
+
+use QMTP/QMQP transmitting emails and are currently
+only supported by Postfix, Qmail, and s/qmail in a
+dedicated environment,
+
+* qmail-smtpd
+
+supports both SMTP and ESMTP and is a potential
+target for spam, virii, and other unsolicited email.
+
+Thus
+
+* qmail-smtpd
+
+supports greylisting and provides filters for the
+
+- SMTP envelope information,
+- the email content (with different mechanisms) and in
+ particular to check/validate the existance of a potenial
+- email recipient.
+
+For this purpose, the modules
+
+* qmail-smtpam,
+* qmail-vmailuser,
+* ldapam, and
+* qmail-authuser together with
+* qmail-ldapam
+
+are available. The RECIPIENTS mechanism supports a
+domain dependent validation based on a PAM mechanism
+or perhaps a cdb.
+
+Domain based SPF lookups are provided for
+
+* qmail-smtpd.
+
+
+Anti-Spam Mechanisms
+====================
+
+* rblsmtpd (out of the package ucspi-tcp6)
+
+supports
+
+- Relay Black Lists (RBL) and
+- Greetdelay
+
+prior of receiving mail by
+
+* qmail-smtpd.
+
+In adddition,
+
+* qmail-smtpd
+
+provides by means of the
+
+- QMAILQUEUE hook
+
+an interface to SpamAssassin and other tools.
+A wrapper script is included.
+
+Further, the well known
+
+- postgrey
+
+server can be used by
+
+* qmail-postgrey
+
+as an add-on to be called by
+
+* qmail-smtpd.
+
+
+Anti-Virus Mechanism
+====================
+
+* qmail-smtpd
+
+uses
+
+- MIME and
+- LOADER type
+
+filters to allow an on-the-fly recognition of executable.
+
+Anti-Virus tools are supported either by
+
+- QHPSI or by the
+- QMAILQUEUE hook.
+
+A (combined) wrapper script for
+
+* qmail-queue
+
+is provided.
+
+
+Bounce Control
+==============
+
+Within s/qmail
+
+* qmail-send
+
+is responsible to generated bounces, ie. None Deliverable Reports (NDR).
+s/qmail uses qmail's concept to generate the NDRs in the QSMBF (qmail-send
+Message Bounce Format) unaltered (http://cr.yp.to/proto/qsbmf.txt).
+
+To control NDR, s/qmail provides two means:
+
+* qmail-send
+
+can be adviced -- while generating a NDR -- to limit it to N bytes.
+Effectively this means the orgininal message is truncated and not
+completely bounced.
+
+Upon transmitting bounce messages to third-party MTAs
+
+* qmail-remote
+
+can be set-up to use a particular
+
+- bounce queue (s/qmail instance)
+
+to take care of this delivery. Thus generic message transmission
+is decoupled from bounce processing and does not inflict with it.
+
+
+Logging, Monitoring, and Housekeeping
+=====================================
+
+s/qmail writes log information for
+
+- qmail-send (qmail-local & qmail-remote/qmail-smtpam) on FD 2
+- qmail-popup (authentication information only) on FD 5
+- qmail-smtpd (see 'LOGGING') on FD 2
+
+Either the log information is fed by means of 'splogger'
+into the Syslog, or treated by daemontool's 'multilog'
+which automatically does the housekeeping and provides
+a TAI64N timestamp for each line (event).
+
+Using 'multilog', the log information can be
+picked up by 'qmail-mrtg' and graphically
+displayed using 'MRTG' or 'RRDtool'.
+
+The log information can be analysed using
+the 'qmailanalog' facility and for convenience
+the program 'tai64nfrac' is included.
+
+The separate package 'newanalyse' provides
+an easy customizable umbrella script for analysis
+and long-haule housekeeping together with the
+capability to track each incoming and outgoing
+mail.
+
+
+E. Hoffmann -- 2021/01/01.
+
+
+
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
new file mode 100644
index 0000000..e48d1ed
--- /dev/null
+++ b/doc/CHANGELOG
@@ -0,0 +1,196 @@
+s/qmail 4.0 CHANGE log
+======================
+
+Older changes can be found in CHANGELOG_V3.
+
+Version Descripition
+--------------------
+
+4.0.00 Initial version, removed SRS, fixed SPF.
+4.0.01 Recovered SRS and added srsforward + srsreverse
+ as compile option; still depending on librsrs2.
+ Added man pages for srsforward + srsreverse.
+ Fixed columnt (buf incorrectly used).
+B(2) Changed 'puts' to 'out'; where applicable.
+ Fixed dnsq call in qmail-smtpd concerning
+ lookup type "M" -> 'M', "A" -> 'A' (char ).
+B(3) Fixed missing timestamp for mails in maildir.c
+ making qmail-pop3d behaving erratic.
+ Substituted put -> out almost everywhere.
+ Fixed wrong 'identity' in Received header ('unknown')
+ due to misplaced 'if' nesting.
+ Streamlined qmail-authuser to support APOP auth
+ even for Unix system accounts (tx Drew).
+ Fixed wrong CAPA announcement in qmail-popup
+ (APOP instead of UIDL).
+4.0.02 Removed dependency on libsrs2 providing srs2.[c|h]
+ natively together with sha1[_hmac].[c|h].
+ Complete refactoring of sha1 and sha1_hmac.
+ Included Drew W's enhancements for Dovecot auth
+ in qmail-authuser.
+ Fixed bug in IPv4/IPv6 matching for spf_mx.
+4.0.03 Enhanced qmail-authuser.
+ Redone srsforward and srsreverse + man pages.
+ Fixed qmail-smtpd to cope with new DNS resolver
+ behaviour (in particular for SPF segfaulting for bounces).
+ Finally streamlined man pages.
+4.0.04 SMTPUT8 is now triggered via environment variable UTF8 for
+ qmail-smtpd.
+ Fixed segfaulting qmail-smtpd in case of multiple recipients
+ in the RCPT TO dialog.
+ qmail-smtpd exits now if Auth and Auth not announced or PAM missing.
+4.0.05 Fixed bug in qmail-remote with wrong CNAME address mangling (tx. Leah).
+ Removed SMTPUTF8 compiler flags in qmail-remote and qmail-smtpam
+ which now auto-detect UTF8 encoded addresses.
+4.0.06 Fixed qmail-smtpd segfaulting while wrongly evalute 'fakehelo' for SPF.
+ Added compatibility for other tcpserver/sslserver programs
+ calling qmail-smtpd and different IPv6 environment variables (4Leah).
+4.0.07 Straightend some code in SPF evalution which might prevent it (tx Leah).
+ Fixed bug returning wrong SPF results in case a TXT but no SPF record is given.
+ Fixed qmail-remote potentially not binding to IPv4 addresses (tx. MB).
+ Fixed qmail-authuser insuffient handle of passwords using crypt (tx. MB).
+4.0.08 Fix for qmail-vmailuser not respecting vpopmail's home dir (tx. Ueli H.).
+ Changed qmail-remote to cope better with fehQlibs-15 and IPv4 qualification.
+ Fixed CVE-2011-0411: Pipelining command injection for qmail-smtpd.
+ Fixed the Guninski CVE-2005-1513 (in fehQlibs-15): Buffer overflow
+ if size of mail > 4 GByte.
+4.0.09 Reworked fix for CVE-2011-0411 to provide a general solution. (tx. Fabian)
+ Applied fix to qmail-popup as well.
+4.0.10 GCC 10 refactoring (together with fehQlibs-15b).
+ qmail-remote now recognizes a MX retrieved IP to be itself and skips it.
+EOL for 4.0
+
+4.1.00 Added TLSA DNS lookup for qmail-remote.
+4.1.01 Added qmail-ldapam; needs tweaking and verification still.
+4.1.02 Added qmail-postgrey client together with the qmail-smtpd IF (permisssion by jan.mojzis).
+4.1.03 Fixed TLSA off-by-one error for qmail-remote.
+ Removed idedit.c (could be used in later version).
+ Disabled compilation of qmail-ldapam. (cleanups, beta version).
+ Added postgrey run script together with adjustments for doc and man.
+4.1.04 Included Reiser FS patch; see unlinking problems also with vdeliver (qmail-queue, qmail-local).
+ Fixed 'incorrect' xtext generation in qmail-remote.
+ Added qmail-qmaint providing sanity checks on the queue and
+ allowing removal of messages (based on E. Huss code).
+ Integrated DANE lookup (exceptions) into tlsdestinations + doc.
+4.1.04+ Fixed bug not freeing X509 cert, thus TLSA fails. The X509_digest API is stupid.
+4.1.05 Added selector evalution in tlsa_check and re-formulated logic.
+ Moved header files to ./include directory (and changed conf-cc accordingly).
+4.1.06 Compliance with fehQlibs-17 (could solve [20201123#1/4.0.10]).
+ Fixed bug in smtproutes not authenticating [20210213#1/4.0.10].
+ Reformulated qmail-smtpd smtproutes to support setting localip [RfC:20201112#1/4.0.10].
+4.1.07 Fixed bug in qmail-smtpd confusing badmailfrom with badrcptto [20120312#1/4.0.10].
+ Adjusted header files to compile on ARM64 (Clang) and with GCC-10 (AMD64).
+4.1.08 Removed references to qmail-ldapam in package.
+ Changed SPF DEFEXP macro using expand for domaiGn rather than 'spf.pobox.com' [20210212#1/4.0.10].
+4.1.09 Fixes for qmail-remote and rewriting the SIZE extension interface (tx. Drew):
+ a) (Occasional) wrong parsing of multiple X.509 fingerprints in dnstlsa and tls_remote.c
+ which might qmail-remote advice to reject valid TLSA indicated connections.
+ b) Wrong SIZE indication (mailfrom, mailfrom_xtext) in SMTP dialogue [20210622#1/4.1.08] (tx. Drew).
+ c) Wrong SMTPUTF8 indication (mailfrom, mailfrom_xtext) [20210622#2/4.1.08].
+ Note: qmail-rspawn API left unchanged wrt vanilla qmail.
+4.1.10 Fixed flaw in qmail-remote not producing immediate bounce for server's 5xx reply code.
+ Fixed bug in qmail-remote introduded in sqmail-4.1.09 evaluating size information for qmtp delivery.
+4.1.11 Fixed bug in qmail-vmailuser not evaluating vpopmail's user directories correctly.
+ Fixed bug in qmail-smtpam segfaulting. Sitting there since 3.0; nobody is using it.
+ Added 'implicit TLS' support for qmail-remote in control/smtproutes, ./authusers, ./tlsdestinations.
+ Added 'implicit TLS' support for qmail-smtpam on the command line.
+4.1.12 Improved and streamlined qmail-remote TLS errors.
+ Multiple DNS queries vor TLSA check; first early; second after cert received.
+ TLSA check working again; stupid OpenSSL doc ;-)
+4.1.13 Better RFC 6698 (TLSA) conformance for PKIX-EE (with full X.509 chain given).
+4.1.14 TLSA record lookup follows now a CNAME query. Pretty unusual for MX environments.
+ Removed recognition of 451 SMTP return code as greylisting in qmail-remote logs.
+4.1.14a Fixed two integration bugs in 4.1.14 and straightend TLSA lookup and evalution.
+4.1.15 Off-by-one error in dnstlsa (cert finterprint too short) and
+ corrections (and simplifications) to evaluate the TLSA finterprints (tls_remote.c).
+4.1.16 Additional corrections for TLSA evaluation with several fingerprints.
+ TLSA lookup not bound to PTR lookup anymore but just hostname of MX.
+ qmail-local does not disclose virtual user name extension in 'Delivered-To' field.
+ Installation routine removes now potential remnants in ./src diretory.
+ Removed irritating 'greylisting' log info from qmail-remote for certain SMTP reply codes.
+ qmail-queue fast injection race condition fix from Manvendra included.
+ qmail-remote evaluates MX distance according to IPv4/IPv6 local bindings.
+4.1.17 Fixed OpenSSL's X509_pubkey_digest() function for TLSA.
+EOL for 4.1
+
+
+4.2.00 Taken over qmail-ldapam development from 4.1.
+4.2.03 Synced with current s/qmail (4.1.16); enhanced RECIPIENTS mechanmism to read
+ users/assign.cdb. Note: This breaks old qmail, since the name was just 'cdb' here.
+ Adjusted qmail-newu to confirm with this decision.
+4.2.04 First step integrating libdkim (from Kai Peter's implementation and adjustments
+ for current OpenSSL and LibreSSL).
+4.2.05 libdkim implemented (native C++) als qmail-dkim; added stub qmail-dksign.
+ Synced with sqmail-4.1.17. New requirement: fehQlibs-20 due to dns_txt.c changes.
+4.2.06 Integration tests and documentation for qmail-dksign.
+4.2.07 Integration tests successful; except for DKIM over QMTP. Needs changes for qmail-qmtpd.
+ Included man pages for qmail-dkim.8 and qmail-dksign.8.
+4.2.08 Replace 'execve' with 'pathexec' in qmail-rspawn and qmail-dksign.
+ Fixed permissions on DKIM 'default' files. Preliminary qmail-dkverify.c.
+ Removed creation of qmail-ldapam; still a useful solution is required (separate package?).
+ Changed defaults for qmail-dksign to the anticipated ones; verified CRLF prior of signing.
+ qmail-dkim options work now as expected. Fixed wrong hash functions in dkimsign (tx. Pascal).
+ DKIM signing working now.
+4.2.09 Removed 'Allman' code from DKIM. Adjusted qmail-dksign man page.
+ First attempt for qmail-dkverify.c. Removed the qmail-ldap dependencies.
+4.2.10 Included 'Ed25519' signatures in dkimsign.cpp. Works fine - but untested.
+ Removed chdir(auto_qmail) dependency from qmail-dkim; universal usage again.
+ Moved back to include tabs for the DKIM header; double WSP seems not to work well here.
+ Removed ADSP (Author Domain Signing Practice) from dkverify.cpp (RFC 6541; experimental).
+4.2.11 qmail-remote recognizes now Greylisting after HELO with SMTP Reply > 400 (and tries again).
+ Big reminder: Always use byte arrays in constmap hash tables => tls_destination()++.
+ Added 'l' (length) flag in dkimdomains for specific customization.
+ Changed dkimsign's BodyLength calculation; was strange before.
+4.2.12 Progress on dkimverify.cpp.
+4.2.13 dkimverify.cpp stripped down and working now with socket interface.
+4.2.14 Fixed bug in spf_exists return wrong results for DNS lookup (tx. Laurentiu).
+ First version with working qmail-dkverify. Tests pending.
+4.2.15 qmail-dkverify working now; except for Ed25519 signatures.
+ Replaced socket interface by file interface for reporting results to qmail-dkverify.
+ Stripped CR from outgoing mails. qmail-dksign ignores input domains for which no privkey exists.
+4.2.16 qmail-dkverify considers now d=domain in X-Authentication results.
+ Removed obsolete 'selector' file in ssl/domainkeys/<domain> and rather
+ permit now tailored selector names in ssl/domainkeys/<domain>/<selector> to pick up private key.
+ Ed25519 signing and verification working now. Fixed wrong variable for 'sender' upon call.
+4.2.17 Fixed premature close of cdb in fastforward; removed slurpclose.c.
+ Final trimming and documentation.
+ qmail-remotes's cafile and cipher handling reworked.
+4.2.18 Removed 'selector' as file name for qmail-dksign and used 'default' instead, making it more robust.
+ Changed erroneous 'domain' to 'sdid' in qmail-dksign (tx. Pascal). Udated man page for qmail-dksign.
+4.2.19 Changed back to 4.2.16 behavior of reading the DKIM private key based on selector.
+ Added new default signing capability for qmail-dksign to consider only 'own' domains,
+ which are given in rcpthosts. The token '=:' can be used in control/dkimdomains.
+ Compatibility with LibreSSL 3.7.x and Ed25519 signature operations (tx. Nicolai).
+ Improved robustness and error message handling for qmail-dksign.
+4.2.20 Updated mkdkimkey.sh; no TLSA lookup for bounces.
+ dkimverify update for message with both RSA and Ed25519 signatures and selection.
+ Added more verbose logging to qmail-remote in case of unsuccessful delivery.
+ qmail-rspawn does not read control/dkimdomains but rather stats it -> less FDs.
+4.2.21 Fixed wrong DKIM ed25519 indication in DKIM header. DKIM ed25519 key stripped from ASN.1 header
+ in order to conform with RFC 8463 while prepending that for DKIM verification.
+ SPF evaluation considers now fehQlibs-22 new CIDR API.
+4.2.22 Internal version with first attempt for hybrid DKIM signatures.
+ Fixed qmail-remote abends in case of contacting RFC (2)821 none-compliant SMTP MTAs.
+4.2.23 Fix for qmail-remote handling of none StartTLS MTAs to fallback for unencrypted service.
+4.2.23 Hybrid DKIM signatures working now; required changes of qmail-dkim API and qmail-dksign.
+4.2.23a Some typos in documentation and spelling mistakes fixed.
+4.2.24 Fixed SPF PTR lookup (cleared up weired logic) [202310503#1/4.2.24] and straightened error output line.
+ Tweaks for DNS behavior in case of missing DNS records and bouncing for qmail-remote.
+ Added Return Code values in man pages for DNS client programs.
+4.2.25 Fixed bug in DKIM validation not considering Pubkey if k= is missing in DNS TXT record => DKIM fail.
+4.2.26 Backported fixes for [20230922#1/4.3.01], [20230920#1/4.3.01], and [20230823#1/4.3.00] included.
+4.2.27 Fixed qmail-smtpd Auth bug segfaulting if no/wrong arguments [20230931#1/4.2.27]
+4.2.27a Misspelled prototype in smtpd.log may lead to confusing auth eror messages [20231003#1/4.2.27a].
+4.2.27b control/domainips adds erroneously a \0 to helohost which violates RFC 2821 [20231004#1/4.2.27b].
+4.2.28 Backported TLSA handling for qmail-remote from s/qmail 4.3.
+4.2.29 DKIM sender evaluated in lowercase for signing [20231109#1/4.2.29];
+ DKIM header for verification does not depend on position of 'Content' header (missing verification).
+ Fixed irritating log output in case no DKIM key is found.
+ DKIM signing now robust against wrong keys and remnant files left in DKIM staging area.
+ Fixed crash in qmail-smtpd while logging SPF evaluation with un-terminated spfbounce [20231203#1/4.2.29].
+ Fixed 'missing' mails for bounces problem in case DKIM signing failed due to missing key [20231119#1/4.2.29].
+EOL for 4.2
+4.2.29a Fix for EHLO X-fields and StartTLS in qmail-remote.
+ Fix for recipients() and assign.cdb reading.
+ Fix for qmail-dkverify with incomplete information in email header.
+ Fix for qmail-dksign reading from inital stage file in case of signing errors.
diff --git a/doc/CHANGELOG_V3 b/doc/CHANGELOG_V3
new file mode 100644
index 0000000..4e8b2f9
--- /dev/null
+++ b/doc/CHANGELOG_V3
@@ -0,0 +1,108 @@
+Changelog of s/qmail
+--------------------
+
+
+3.0.0 First public release (2015-12-24).
+3.0.1 Second public release (2016-01-12).
+ Fixed [20160108#1/3.0.0] and additional cleanups.
+3.0.2 Third public release (2016-02-01).
+ Fixed [20160131#1/3.0.1] and additional cleanups.
+
+3.1.4 Minor installation issues.
+ Enhanced qmail-authuser for virtual users.
+ 'Pi' release (2016-04-23).
+3.1.5 Fixed [20160428#1/3.1.4] strict Auth error.
+ 'Pi+' release (2016-04-01).
+3.1.6 Fixed [20160414#1/3.0.2] hook for more FDs.
+ 'Pi++' release (2016-05-05).
+3.1.7 Fixed [20160522#1/3.1.6] qmail-smtpd abends
+ with Mail From: <..@[ ..]> addresses including '[]',
+ in particular double bounces.
+ Fixed [20160522#2/3.1.6] badmailfrom wrong RC 110.
+ [20160527#1/3.1.6] OpenBSD installation adjustment.
+ 'Pi3+' release (2016-06-04).
+3.1.8 Fixed [20160615#1/3.1.7] qmail-smtpd does not
+ return for err_size(). (bug present since Spamcontrol)
+3.1.9 Fixed [20160712#1/3.1.8] Bounces are not deleted from queue
+ if Bouncemaxbytes not set.
+ Wrong if/else nesting in qmail-send.c (tx. Pascal Nobus).
+
+3.2.13 Initial release with SPF capabilities.
+ Fixed OpenBSD fastforward bug [20161001#1] (prototyping).
+3.2.14 Added SPF information in qmail-smtpd log.
+ qmail-mrtg changed to display SPF authorized/failed sessions.
+ Fixed IP bitstring evalation; SPF redirect is working now.
+ Fixed userid evaluation in qmail-authuser.
+ Fixes for OpenBSD installation.
+ SPF Header is written befor SMTP received header.
+3.2.15 Included LibreSSL hook (ucspi-ssl-0.98++ required).
+ Added Maildir extensions in qmail-local from Tobi.
+ Fixed SPF qmail-mrtg evaluation.
+ Fixed man page installation + installation issues for OpenBSD.
+3.2.16 Added qmail-vpopbox and qmail-vmailbox PAM for Recipients.
+3.2.17 Final release of version 3.2; minor adjustments only.
+ The scripts have been reworked and integrated into the
+ package production chain.
+ This version is expected to work with OpenSSL 1.0/1.1 + LibreSSL
+ together with ucspi-ssl-0.99.
+3.2.18 Fixed bug [20170217#1/3.2.18] wrong order of badmailform evaluation
+ & DNS MF check within qmail-smtpd.
+3.2.19 Fixed bug [20170307#1/3.2.19] wrong nesting in badmailfrom evaluation
+ in qmail-smtpd.
+
+3.3.3 Initial release including Andre Oppermann's EXTTODO for qmail-send
+ (without explicit permission [asked 3x], though BSD licensed).
+ Fixed bug in package/run script not to include 'defaultdelivery'.
+3.3.4 qmail-authuser supports now Dovecot as IdP.
+ Added PAM qmail-vmailuser (for Recipients extension).
+3.3.5 Added SHA1 and SHA256 as hash method for passwords in qmail-authentication.
+ Fixed bug [20170625#1/3.3.5] wrong IP addresss display in qmail-remote log
+ if lowest MX is IPv6 and connection is IPv4.
+3.3.6 Fixed qmail-remote TLS bug [20170626#1/3.3.6] with missing parms -tx Standa.
+3.3.7 Fixed wrong compactification of IPv6 addresses (at least somehow ..).
+ Added SMTPUTF8 support in qmail-smtpd, qmail-remote, and qmail-smtpam.
+ Added IDN2 support for qmail-remote.
+3.3.8 Finished testing, updated docs.
+3.3.9 Added 'socket option' for qmail-authuser (Dovecot).
+ Added symlinking s/qmail sendmail in package/run script.
+ Fixed smtplf missing '\r' for header line.
+3.3.10 Fixed qmail-authuser for Dovecot -- gossiping.
+3.3.11 Fixed flaw in qmail-smtpd (since 3.2.19) for DNSMF lookup (timeout in case of bounces).
+ Changed defaults for SMTPUTF8/IDN2 installation.
+3.3.12 Fixed bug in qmail-remote tlsdestination. One \0 byte too much.
+3.3.13 Fixed two small SMTPUTF8 bugs in qmail-remote (tx. M. Mausz) and
+ a wrong displayed Received header due to a qmail-smtpd bug.
+3.3.13a Spelling mistake in Makefile (spfdinsip.o instead spfdnsip.o).
+3.3.14 Fixed OpenSSL 1.1.0.f-2 SSL state engine query call (tx. Hans-Christian Jehg).
+3.3.15 Fixed wrong character count for tlsdestinations; comparisons don't work.
+3.3.16 Reworked OpenSSL renegotiation call within tls_timemout.c.
+3.3.17 Maintainence release; use option -O0 for gcc 4.7.2; otherwise qmail-smtpd abends with SPF enabled.
+3.3.18 Potential fix for spfdnsip.c as back-port from aQmail (the first one).
+3.3.19 Bug in qmail-remote.c's evaluation of 'control/domaincerts' with missing attributes (crash on read).
+ Strange enough, this bug is not present in qmail-smtpam.c; optimized too much. (tx. J.C. Burley)
+3.3.20 Bug in qmail-remote.c & qmail-smtpam.c evalutating tls remote host name
+ for the |domain in tlsdestinations. (tx. Johannes Weberhofer)
+3.3.21 Bug in qmail-smtpam not reading tlsdestinations. (tx. Ueli)
+3.3.22 Crash of qmail-remote if domaincerts are populated with '*' as domain. (tx. Oleg)
+ Error in qmail-smtpd not requiring TLS before Auth.
+ package/ucspissl updated to support different OpenSSL versions (as given in conf-ucspissl).
+
+*) backported fixes from s/qmail 3.4 (see below).
+
+3.4 Major release based on fehQlbis(-13).
+ Bugs fixed: qmail-remote*: Ciphers in tlsdestinations are not evaluated and used.
+ Flaw fixed: qmail-smtpd: Wrong copy of authhost to relayhost.
+ Core changes: Replaced substdio by buffer. New dns stub resolver based on fehQlibs.
+ Added SW: dnscname - return A/AAAA record for CNAME.
+3.4.24 Buffer name conventions straightend.
+3.4.26 Flaw fix: qmail-authuser* now chdirs to sqmail home.
+3.4.27 More specific return codes (110, 111, 112). Fixed buffer in qmail-remote. dns.c finished.
+3.4.28 qmail-authuser now takes full advantage of the POP3 logging scheme; extended for APOP.
+3.4.29 Fixed missing QUIT flush in qmail-remote* ;-). Removed by mistake.
+3.4.30 First beta.
+3.4.31 Second beta: Fixed missing buffer flushes in qmail-smtpd and buffer mangling in qmail-local.
+ 'hostname' is now installed in $QMAILHOME/bin.
+3.4.40 First attempt to include SRS seriously (after 2nd beta).
+3.4.41 Fix for qmail-remote: flagallalias (statement missing).
+ Fix for qmail-smtpd*: Returning SMTP session in case of DNS temp failures (and not pass thru).
+3.4.42 Integrated SRS with libsrs2.
diff --git a/doc/CONTRIBUTERS b/doc/CONTRIBUTERS
new file mode 100644
index 0000000..af07311
--- /dev/null
+++ b/doc/CONTRIBUTERS
@@ -0,0 +1,31 @@
+Contributers to s/qmail:
+-----------------------
+
+- D.J. Bernstein - the original Qmail 1.03
+- M. Delany - Wildmat patch
+- N. Balazas - MFCHECK patch
+- C. Johnson - Tarpitting for qmail-smtpd
+- S. Gifford - IPME and MOREIPME extension & STARTTLS hook
+- W. Harris - SIZE extension
+- M. Stumpf - Logging for qmail-smtpd
+- C. Cazabon - Null Sender patch
+- K. Dabrowski - qmail-smtpd Auth extension
+- R. Nelson - Inspired Warlord extension (virusscan patch) & doublebouncetrim
+- B. Guenter - Bigtodo + Queue Extra extension
+- M. Andree - sendmail extensions
+- E. Sjölund - qmail-local fix for .qmail delivery
+- F. Denis - Bounce size limitiation
+- B. Kalkbrenner - qmail-remote Auth
+- A.B. Guzmain - Outgoing IP patch
+- W. Harris - parts of TLS implementation for qmail-remote
+- K. Fujikawa, F. von Leitner, T. Spier (blazing) - IPv6 extensions
+- J. Saout - SPF hook (tx; great solution)
+- A. Oppermann - EXTTODO + BIGTODO development (included in his LDAP patch)
+- A. Gulbrandsen - some ideas about EAI support have been taken from his patch
+- Shevek - libsrs2 framework
+- Alt.N - libdkim
+
+
+I would like to thank those authors for their significant
+contribution to s/qmail and respect their initial work though
+the current code may not directly reflect their input.
diff --git a/doc/EXTTODO b/doc/EXTTODO
new file mode 100644
index 0000000..991f108
--- /dev/null
+++ b/doc/EXTTODO
@@ -0,0 +1,228 @@
+EXTTODO by Claudio Jeker <jeker@n-r-g.com> and
+Andre Oppermann <opi@nrg4u.com>
+(c) 1998,1999,2000,2001,2002 Internet Business Solutions Ltd.
+
+The EXTTODO patch is a part of the qmail-ldap patch.
+This patches for qmail come with NO WARRANTY.
+
+These patches are under the BSD license.
+
+RELEASE: 5. Jan. 2003
+
+EXTTODO:
+======================
+
+TOC:
+ WHAT DOES IT DO
+ INSTALL
+ CONFIG FILES
+ SETUP
+ BIG PICTURE
+
+NEWS:
+
+ This is the first release of the EXTTODO patch.
+
+================================================================================
+
+WHAT DOES IT DO
+
+ The exttodo patch addresses a problem known as the silly qmail (queue)
+ problem. This problem is found only on system with high injection rates.
+
+ qmail with a big local and remote concurrency could deliver a tremendous
+ amount of messages but normally this can not be achieved because qmail-send
+ becomes a bottleneck on those high volumes servers.
+ qmail-send preprocesses all new messages before distributing them for local
+ or remote delivering. In one run qmail-send does one todo run but has the
+ ability to close multiple jobs. Because of this layout qmail-send can not
+ feed all the new available (local/remote) delivery slots and therefor it is
+ not possible to achieve the maximum throughput.
+ This would be a minor problem if one qmail-send run could be done in extreme
+ short time but because of many file system calls (fsync and (un)link) a todo
+ run is expensive and throttles the throughput.
+
+ The exttodo patch tries to solve the problem by moving the todo routine into
+ an external program. This reduces the run time in qmail-send.
+
+ exttodo adds a new program to qmail called qmail-todo. qmail-todo prepares
+ incoming messages for local and remote delivering (by creating info/<messid>
+ local/<messid> and remote/<messid> and removing todo/<messid>). See also
+ INTERNALS. As next qmail-todo transmits the <messid> to qmail-send which will
+ add this message into the priority queue which schedules the message for
+ delivery.
+
+INSTALL
+
+ To enable the exttodo patch you need to define EXTERNAL_TODO while compiling
+ qmail(-ldap) this can be done with the -D flag of cc (e.g. cc -DEXTERNAL_TODO).
+
+ NOTE: the exttodo patch can also be used on qmail systems without the
+ qmail-ldap patch.
+
+================================================================================
+
+CONFIG FILES
+
+ No additional control files are used or needed.
+
+================================================================================
+
+SETUP
+
+ qmail-todo will be started by qmail-start and therefor no additional setup
+ is needed.
+
+ To verify that exttodo is running just check if qmail-todo is running.
+
+================================================================================
+
+BIG PICTURE
+
+ +-------+ +-------+
+ | clean | | clean |
+ +--0-1--+ +--0-1--+ +-----------+
+ trigger ^ | ^ | +->0,1 lspawn |
+ | | v | v / +-----------+
+ +-------+ v +--2-3--+ +--5-6--+ /
+ | | | | 0<--7 1,2<-+
+ | queue |--+--| todo | | send |
+ | | | | 1-->8 3,4<-+
+ +-------+ +-------+ +---0---+ \
+ | \ +-----------+
+ v +->0,1 rspwan |
+ +---0---+ +-----------+
+ | logger|
+ +-------+
+
+Communication between qmail-send and qmail-todo
+
+todo -> send:
+ D[LRB]<mesgid>\0
+ Start delivery for new message with id <messid>.
+ the character L, R or B defines the type
+ of delivery, local, remote or both respectively.
+ L<string>\0
+ Dump string to the logger without adding additional \n or similar.
+send -> todo:
+ H Got a SIGHUP reread ~/control/locals and ~/control/virtualdomains
+ X Quit ASAP.
+
+qmail-todo sends "\0" terminated messages whereas qmail-send just send one
+character to qmail-todo.
+
+
+EXTTODO by Claudio Jeker <jeker@n-r-g.com> and
+Andre Oppermann <opi@nrg4u.com>
+(c) 1998,1999,2000,2001,2002 Internet Business Solutions Ltd.
+
+The EXTTODO patch is a part of the qmail-ldap patch.
+This patches for qmail come with NO WARRANTY.
+
+These patches are under the BSD license.
+
+RELEASE: 5. Jan. 2003
+
+EXTTODO:
+======================
+
+TOC:
+ WHAT DOES IT DO
+ INSTALL
+ CONFIG FILES
+ SETUP
+ BIG PICTURE
+
+NEWS:
+
+ This is the first release of the EXTTODO patch.
+
+================================================================================
+
+WHAT DOES IT DO
+
+ The exttodo patch addresses a problem known as the silly qmail (queue)
+ problem. This problem is found only on system with high injection rates.
+
+ qmail with a big local and remote concurrency could deliver a tremendous
+ amount of messages but normally this can not be achieved because qmail-send
+ becomes a bottleneck on those high volumes servers.
+ qmail-send preprocesses all new messages before distributing them for local
+ or remote delivering. In one run qmail-send does one todo run but has the
+ ability to close multiple jobs. Because of this layout qmail-send can not
+ feed all the new available (local/remote) delivery slots and therefor it is
+ not possible to achieve the maximum throughput.
+ This would be a minor problem if one qmail-send run could be done in extreme
+ short time but because of many file system calls (fsync and (un)link) a todo
+ run is expensive and throttles the throughput.
+
+ The exttodo patch tries to solve the problem by moving the todo routine into
+ an external program. This reduces the run time in qmail-send.
+
+ exttodo adds a new program to qmail called qmail-todo. qmail-todo prepares
+ incoming messages for local and remote delivering (by creating info/<messid>
+ local/<messid> and remote/<messid> and removing todo/<messid>). See also
+ INTERNALS. As next qmail-todo transmits the <messid> to qmail-send which will
+ add this message into the priority queue which schedules the message for
+ delivery.
+
+INSTALL
+
+ To enable the exttodo patch you need to define EXTERNAL_TODO while compiling
+ qmail(-ldap) this can be done with the -D flag of cc (e.g. cc -DEXTERNAL_TODO).
+
+ NOTE: the exttodo patch can also be used on qmail systems without the
+ qmail-ldap patch.
+
+================================================================================
+
+CONFIG FILES
+
+ No additional control files are used or needed.
+
+================================================================================
+
+SETUP
+
+ qmail-todo will be started by qmail-start and therefor no additional setup
+ is needed.
+
+ To verify that exttodo is running just check if qmail-todo is running.
+
+================================================================================
+
+BIG PICTURE
+
+ +-------+ +-------+
+ | clean | | clean |
+ +--0-1--+ +--0-1--+ +-----------+
+ trigger ^ | ^ | +->0,1 lspawn |
+ | | v | v / +-----------+
+ +-------+ v +--2-3--+ +--5-6--+ /
+ | | | | 0<--7 1,2<-+
+ | queue |--+--| todo | | send |
+ | | | | 1-->8 3,4<-+
+ +-------+ +-------+ +---0---+ \
+ | \ +-----------+
+ v +->0,1 rspwan |
+ +---0---+ +-----------+
+ | logger|
+ +-------+
+
+Communication between qmail-send and qmail-todo
+
+todo -> send:
+ D[LRB]<mesgid>\0
+ Start delivery for new message with id <messid>.
+ the character L, R or B defines the type
+ of delivery, local, remote or both respectively.
+ L<string>\0
+ Dump string to the logger without adding additional \n or similar.
+send -> todo:
+ H Got a SIGHUP reread ~/control/locals and ~/control/virtualdomains
+ X Quit ASAP.
+
+qmail-todo sends "\0" terminated messages whereas qmail-send just send one
+character to qmail-todo.
+
+
diff --git a/doc/LICENSE b/doc/LICENSE
new file mode 100644
index 0000000..12d3dcb
--- /dev/null
+++ b/doc/LICENSE
@@ -0,0 +1,63 @@
+AUTHOR
+======
+
+Author:
+ Dr. Erwin Hoffmann - FEHCom Germany
+Web-Site:
+ https://www.fehcom.de/sqmail.html
+E-Mail:
+ feh@fehcom.de
+
+
+LICENSE
+=======
+
+s/qmail is free software placed into the Public Domain.
+s/qmail is based on D.J. Bernstein's 'qmail' also put in the Public Domain.
+
+This includes:
+ You can download and use s/qmail (and parts of it) as you like.
+ You can modify the source code without notification to or permission by the author.
+Please check:
+ http://www.cr.yp.to/softwarelaw.html
+Note:
+ s/qmail may use/may depend on third party software with different
+ license and/or distribution conditions.
+
+
+DEPENDENCIES
+============
+
+s/qmail depends on the following package:
+ fehQlibs found on https://www/ipnet/qlibs.html,
+ ucspi-ssl found on https://www.fehcom.de/ipnet/ucspi-ssl.html.
+ ucspi-tcp6 (for rblsmtpd and other add-ons) found at https://www.fehcom.de/ipnet/ucspi-tcp6.html.
+s/qmail uses:
+ OpenSSL or LibreSSL routines and requires those for encryption services.
+ MD5, SHA1, SHA2 routines from the Public Domain or given the included License.
+ Other parties contributions (Wildmat, SPF, EXTTODO) also available in the Public Domain
+ or used by permission.
+
+
+Note:
+-----
+
+The author of the program may unsolicitedly change the dependencies.
+Thus, it is you obligation to follow and consider any changes!
+
+
+FITNESS
+=======
+
+The Author does not guarantee a specific fitness of s/qmail.
+If you use s/qmail, it's on your own risk.
+
+
+DISTRIBUTION
+============
+
+s/qmail may be included in ports and packages under the following conditions:
+
+ - The files VERSION and BUILD has to be part of the distribution.
+ - This LICENSE file has to be included in the distribution.
+
diff --git a/doc/LOGGING b/doc/LOGGING
new file mode 100644
index 0000000..6f07dc5
--- /dev/null
+++ b/doc/LOGGING
@@ -0,0 +1,94 @@
+Logging of SMTP Sessions
+========================
+
+Normally, qmail-smtpd doesn't log anything.
+
+Within s/qmail, qmail-smtpd logs some accepted and some (important) rejected SMTP session attempts.
+
+Format: "qmail-smtpd: pid PID Action::Type::Condition: Information"
+
+In order to track a complete SMTP transaction (including tcpserver/sslserver + rblsmtpd)
+the log line includes now the PID.
+
+Here's the glue:
+
+
+ Action Type Condition Explanation
+ -----------------------------------------
+
+ Reject AUTH missing AUTHentication missing
+ Reject AUTH setup AUTHentication impossible due to missing PAM
+ Reject AUTH type AUTHentication of 'type' rejected
+ Reject Auth Method AUTHentication Method rejected
+ Accept AUTH type AUTHentication of 'type' accepted
+
+ Reject DATA Invalid_Size DATA exceeds sizelimit
+ Reject DATA Bad_MIME DATA includes BASE 64 MIME type listed in badmimetypes
+ Reject DATA Bad_Loader DATA includes BASE64 loader type listed in badmimetypes
+ Reject DATA Virus_Infected DATA includes virus infected message (<scanner> | 'AV scanner')
+ Reject DATA Spam_Message DATA includes an identified Spam message.
+
+ Reject ORIG Bad_Mailfrom ORIG is in badmailfrom
+ Reject ORIG DNS_MF Domain part of ORIG has no DNS MX RR
+ Reject ORIG Failed_Auth ORIG tried SMTP Authentication; but failed
+ Reject ORIG Require_Auth SMTP Authentication required; but not granted
+ Reject ORIG Invalid_Sender ORIG not allowed to send
+ Reject ORIG Missing_Auth SMTP Authentication required, but not granted
+ Reject ORIG SPF ORIG was rejected due to failed SPF permissions
+ Accept ORIG Local_Sender ORIG was identified as local sender address
+ Accept ORIG Relay_Mailfrom ORIG was accepted als Relaymailfrom
+
+ Reject RCPT Bad_Rcptto RCPT is in badrcptto
+ Reject RCPT Toomany_Rcptto Too many RCPTs
+ Reject RCPT Failed_Rcptto RCPT could not acceptd as per recipients/cdb.
+ Accept RCPT Recipients_Cdb RCPT was accepted as per recipients/cdb.
+ Accept RCPT Recipients_Pam RCPT was accepted as per recipients/pam plug-in.
+ Accept RCPT Recipients_Wild RCPT was accepted as per recipients/wildlisting.
+ Accept RCPT Rcpthosts_Rcptto RCPT was accepted as per rcpthosts/morercpthosts
+
+ Reject SNDR Bad_Helo SNDR's HELO is in the badhelo
+ Reject SNDR DNS_HELO SNDR's HELO has no DNS A RR
+ Reject SNDR Invalid_Relay SNDR's tries relaying; but not allowd
+ Accept SNDR Relay_Client SNDR was identified as relay client
+
+ Reject TLS missing TLS connection could not be established
+ Reject TLS required TLS connection could not be established
+
+ Accept SPF Recipients_Cdb ORIG was authorized and RCPT accepted as per recipients/cdb.
+ Accept SPF Recipients_Pam ORIG was authorized and RCPT accepted as per recipients/pam plug-in.
+ Accept SPF Recipients_Wild ORIG was authorized and RCPT was accepted as per recipients/wildlisting.
+ Accept SPF Rcpthosts_Rcptto ORIG was authorized and RCPT was accepted as per rcpthosts/morercpthosts
+
+ Reject SPF Fail ORIG authorization failed per SPF
+
+ Deferred GREY Grey_Listed SNDR was temporarily greylisted
+
+ Reject DKIM Signature DATA failed DKIM verification
+
+
+SNDR (S) corresponds to the sending MTA.
+ORIG (F) is the "MAIL From: <Return-Path>".
+RCPT (T) is the "RCPT To: <Forwarding-Path>".
+DATA is the Message.
+GREY is triple of envelope data: SNDR+ORIG+RCPT.
+
+Protocol
+--------
+ SMTP plain SMTP
+ ESMTP 'enhanced' SMTP
+ ESMTPA ESMTP + authentication
+ ESMPTS TLS secured EMSTP
+ ESMTPSA TLS secured ESMTP + auth
+ ESMTP[SA]UTF8 ESMTP[SA] with UTF-8
+
+
+
+The Information is typically constructed from the SMTP envelope like:
+
+ S:IP:FQDN P:Protocol H:Helo F:Mailfrom T:Rcptto
+
+
+This scheme is easy extendable to other successful/deferred SMTP sessions.
+
+In addition for POP3 services this scheme is used; but now logging takes place on FD 5.
+
diff --git a/doc/Old/PROPOSAL.mav b/doc/Old/PROPOSAL.mav
new file mode 100644
index 0000000..4e10d8a
--- /dev/null
+++ b/doc/Old/PROPOSAL.mav
@@ -0,0 +1,124 @@
+Mail From: Address Verification, MAV-2005
+Copyright 2005
+
+Erwin Hoffmann, feh@fehcom.de
+
+
+1. Scope
+
+SMTP is a protocol with very few commands. Only 'Helo'/'Ehlo',
+'Mail From:', 'Rcpt To:', 'Data' and 'Quit' are necessary
+to initiate, perform, and terminate a SMTP session. Here,
+the 'Helo'/'Ehlo' provides information about the sending MTA,
+which in current MTA implementations is not always required,
+while the 'Mail From:' and 'Rcpt To:' is used to build the
+SMTP envelope.
+
+Apart from the 'Rcpt To:' information, the recipient MTA can
+not verify any other information. Both the 'Helo'/'Ehlo' and the
+'Mail From:' is often forged or faked, thus not reliable in
+particular in case of Spam emails.
+
+The proposed 'Mail From:' Address Verification (MAV) implements
+a scheme, how the associated information can be verified at the
+responsible sending email gateway and perhaps can be promoted to the
+recipient MTA. In this scheme, the provided 'Mail From:' information
+is authoritive.
+
+
+2. Responsible Email Gateway
+
+MAV takes place at the responsible email gateway. The responsible
+email gateway acts as relaying gateway for those networks and users
+solely transmitting (and receiving) SMTP emails through this gateway.
+
+Though SMTP is a Host-to-Host protocol, SMTP Authentication yields
+a User-to-Host mechanism. Thus, the responsible gateway has to take
+care about the following senders:
+
+(1) networks/hosts, identified by there IP or FQDN (available by
+ DNS lookup),
+(2) users/senders, identified by means of SMTP Authentication or other
+ mechanisms like POP-before-SMTP.
+
+With MAV, it is possible to check and verify the integrity of the
+provided 'Mail From:' envelope address
+
+(a) domain-based, by means of the provided IP-address/FQDN of the
+ sending MTA,
+(b) user-based, in case SMTP Authentication (or another user-based
+ method) is in place.
+
+Typically in the first case, only the domain-part of the 'Mail From:'
+SMTP envelope address can be verified (the part right from the '@',
+i.e. user@domain), while in the second case the full qualified
+address may be subject of the MAV, providing a mapping between the
+userid for SMTP Authentication and the chosen 'Mail From:' address.
+
+
+3. Comparision with other verification schemes
+
+Today, it is common to reject emails in case it fails certain
+authorization/verification criteria:
+
+(1) Testing the IP address of the sending MTA against Realtime Blacklists
+ (RBL) available on the Internet,
+(2) verification of the domain-part of the provided 'Mail From:' address
+ doing a DNS lookup (reverse Return-Path must exist) or SMTP lookups,
+(3) employing the Sender Policy Framework (SPF), thus checking whether
+ the domain-part of the 'Mail From:' address is authoritive with
+ respect to the sending MTA,
+(4) verifying (locally) the existance of the forseen recipient ('Rcpt To:'),
+(5) checking the contents of the email by means of baysean approaches
+ or by checksums.
+
+In any case, the receiving MTA is responsible to realize more or less
+complex checks to accept or reject emails applying those means.
+
+Opposite to this, MAV adds a qualification to the responsible email
+gateway; comparable with SMTP Authentication.
+
+
+4. MAV enabled responsibe email gateway
+
+The tasks of a MAV enabled responsibe gateway are the following:
+
+(1) The gateway is knowledgeable about those emails to be allowed
+ for unrestricted relaying. Typically this is facilitated due
+ to the knowledge to the sender's IP/FQDN or by means of SMTP
+ Authentication, Pop-before-SMTP, or any other.
+(2) The gateway has access to a list which maps the sender
+ qualification information with a list of allowed domains as
+ part of the 'Mail From:' address or particular 'Mail From:'
+ addresses.
+(3) Emails failing this test will be rejected initially during
+ the SMTP session.
+(4) Emails passing the test are allowed to relay.
+(5) The gateway adds the keyword 'ESMTPM' into the receiving
+ email header. Thus, the next hop email system is able to
+ verify the authoritive usage of the 'Mail From:' address.
+
+
+5. Dependencies on other email RFCs
+
+- RFC 2821: Service extensions: None.
+- RFC 1893: Enhanced Mail System Status Codes: None.
+- RFC 3848: ESMTP and LMTP Transmission Types Registration: Yes.
+ MAV adds a new keyword 'ESMTPM' which complements the keywords
+ 'ESMTPA' and 'ESMTPS'; thus in addition the combinations
+ 'ESMTPAM', 'ESMTPSM', and 'ESMTPSAM' are valid.
+
+
+6. Security considerations
+
+Information in the email header is easy to forge or manipulate.
+
+
+7. History
+
+Parts of the MAV approach was first introduced in the SPAMCONTROL
+patch for Qmail 1.03, based on ideas initiated by the LDI, Mainz, Germany.
+
+
+
+
diff --git a/doc/Old/README.djbdns b/doc/Old/README.djbdns
new file mode 100644
index 0000000..c87897b
--- /dev/null
+++ b/doc/Old/README.djbdns
@@ -0,0 +1,63 @@
+QMAIL + DJBDNS
+==============
+
+You may want to link qmail's DNS lookups
+against DJBDNS and not against libresolv
+as provided by Nikola Vladov.
+
+Here's the provisionell bootstrapping recipe
+
+1. Step:
+
+- Install: qmail as ./qmail-1.03
+
+- make qmail (after you have raised accounts + dirs)
+
+- Install: djbdns as ./djbdns-1.05.
+ *) You may need to fix "error.h" in the above djbdns-dir:
+ Edit conf-cc:
+
+ cc -O2 -include /usr/include/errno.h
+
+ **) You want to increase the UDP buffer from 513 to 4097 byte:
+ Edit dns_transmit.c:
+
+ int dns_transmit_get(struct dns_transmit *d,const iopause_fd *x,const struct taia *when)
+ {
+ char udpbuf[4097]; /* instead original buffer [513] byte */
+ unsigned char ch;
+
+- Now do 'make setup' in djbdns-1.05.
+
+
+2. Step:
+
+- Download: http://riemann.fmi.uni-sofia.bg/vladov/ftp/djbdns+qmail.tar.gz
+ (it is also part of SPAMCONTROL).
+
+- Untar Nikola's patch in djbdns-1.05 (and read his README.qmail).
+
+- Adjust the path to the qmail dir: conf-qmail (if necessary).
+
+- Install Nikola's patch: make -f Makefile.qmail
+
+- Test the patch: make -f Makefile.qmail check
+
+
+3. Step:
+
+- Untar SPAMCONTROL in the qmail-1.03 source directory.
+
+- Edit conf-djbdns and include the path to djbdns-1.05 (if necessary).
+
+- Run install_spamcontrol.sh and see in the spamcontrol.log if changes applied.
+
+- (Re)Make qmail: make setup check.
+
+
+4. Step:
+
+- Enjoy and relax. Now qmail-remote + qmail-smtpd use djbdns libs instead of libresolv.
+
+
+--eh. 2010-04-26
diff --git a/doc/Old/README.mav b/doc/Old/README.mav
new file mode 100644
index 0000000..761155f
--- /dev/null
+++ b/doc/Old/README.mav
@@ -0,0 +1,96 @@
+Mail Address Verification (MAV)
+===============================
+
+Introduction
+------------
+
+Mail Address Verification (MAV) makes the
+'Mail From:' envelope sender address authoritive.
+This is facilitated by comparing the received
+'Mail From:' address in the SMTP dialoge, with a list
+of addresses/domains included in a list matching
+
+(1) the userid (=> $TCPREMOTEINFO).
+(2) the IP (=> $TCPREMOTEIP),
+(3) the FQDN (=> $TCPREMOTHOST),
+
+of the connecting SMTP client to qmail-smtpd.
+
+
+MAV invocation
+--------------
+
+Use the evironment variable 'LOCALMFCHECK' by
+means of the qmail-smtpd start script or by means
+of tcpserver's cdb file with the following definitions:
+
+(1) LOCALMFCHECK="" - unqualified checking against
+ control/rcpthosts
+(2) LOCALMFSCHECK="!" - qualified checking against
+ control/mailfromrules.cdb
+(3) LOCALMFCHECK="example.com" - qualified checking
+ with fixed name
+
+
+MAV database
+------------
+
+Include into the file contol/mailfromrules
+a list of assigned senders and designated 'Mail From:'
+addresses in the following format:
+
+12.34.56.:@example.com
+12.34.56.78:jffy@example.com,fred@noexample.com
+=example.com:@example.com
+joe@example.com:joe.stein@example.com
+
+
+Note 1: The addresses are included in a tcpserver
+compatible format.
+
+Note 2: The length of the assigned email 'Mail From:'
+addresses is only limited by memory.
+
+Note 3: All assigned 'Mail From:' addresses have to
+include a '@'. Checks are done for spaces. Comments
+are allowed.
+
+Note 4: All addresses are evaluated in lower case.
+
+
+Run bin/qmail-mfrules to construct control/mailfromrules.cdb
+out of control/mailfromrules.
+
+
+Return codes
+------------
+
+In case, the match was not successful, the sending MTA
+client receives the following message:
+
+"553 sorry, invalid sender address specified (#5.7.1)"
+
+The message can be customized by means of the environment
+variable REPLYMAV="texstring" including 'textstring' between
+'specified' and the EMSSC code.
+
+
+Others information
+------------------
+
+Read PROPOSAL.mav.
+
+Read man qmail-mfrules.
+Read man qmail-smtpd.
+Read man qmail-control.
+Perform qmail-showctl.
+
+
+Erwin Hoffmann, Cologne 2005-04-26.
+
+
+
+
+
+
+
diff --git a/doc/Old/README.qmq b/doc/Old/README.qmq
new file mode 100644
index 0000000..1940cd1
--- /dev/null
+++ b/doc/Old/README.qmq
@@ -0,0 +1,73 @@
+Qmail Multiple Queue (Option) -- QMQ(0)
+---------------------------------------
+
+1. What is QMQ ?
+
+Qmail Multiple Queue -- is an option (of SPAMCONTROL).
+SPAMCONTROL is useful on Qmail hosts attached to the
+Internet and receiving e-mails, shortly named MTA
+(Mail Transfer Agents).
+While SPAMCONTROL tries to take control of the
+incoming SMTP traffic, QMQ allows you to control
+the e-mail communication to -- and from -- the
+(downstream) e-mail domains you are responsible for.
+
+2. How does QMQ work ?
+
+In addition to standard Qmail (patched with SPAMCONTROL)
+to receive e-mails from the Internet, you set up > N <
+secondary instances of Qmail to deliver e-mails to your
+downstream domains.
+The different Qmail instances are typically set up on
+one host; the communication from the primary instance to
+the secondary is faciliated by QMTP, though SMTP can be
+used as well.
+While the primary instance is patched with SPAMCONTROL,
+all seconderis can be plain (Vanilla) Qmail.
+Once the primary Qmail instance receives an e-mail for
+a QMQ domain, it will forward the e-mail via QMTP to
+one of the secondary instances, which is responsible
+for furthter delivery.
+This not only will avoid the so-called "Silly Qmail
+Syndrom" but will allow you to fine-tune the delivery
+conditions and set-up (e.g. Virus/Spam scanners)
+for any recipient domain.
+
+3. How to set up multiple Qmail instances ?
+
+You are free to set them up.
+However, you can use the scheme, I have developed:
+a) Modify "conf-qmq" to your needs.
+ Here, you define the (local) instances by name
+ and their (QMTP) port numbers.
+b) Execute ./qmtpt ..../ . This will raise
+ - ./qmail/skeleton --
+ - ./qmail/source
+
+4. What is the benefit of QMQ ?
+
+a) Decoupling: Delivery to domain >i< is independent
+ of domain >k<.
+b) Independent delivery parms and perhaps filters for
+ any secondary domain.
+c) Primary instance does not suffer from "Silly Qmail
+ Syndrom".
+d) Set up of a dedicated Bounce Queue.
+e) Thruput is increase by a factor of 10 - 100.
+
+
+5. Consideratons:
+
+a) Using 'qmail-qstat' practically, very littly
+ e-mails stay in step 'preprocessed' (on the
+ primary instance) will be realised.
+b) Adjust your delivery channels to your needs.
+ With QMQ, Qmail will easly flood them up.
+
+
+Erwin Hoffmann
+Cologne, 17-08-2007
+
+
+
+
diff --git a/doc/Old/README.recipients b/doc/Old/README.recipients
new file mode 100644
index 0000000..90a4003
--- /dev/null
+++ b/doc/Old/README.recipients
@@ -0,0 +1,256 @@
+README - qmail-smtpd RECIPIENTS extension
+=========================================
+
+1. Scope:
+
+qmail-smtpd accepts messages if the SMTP domain part of
+recipient address ("RCPT to: <recip@domain>") matches an
+entry in control/rcpthosts or control/morercpthosts.cdb.
+
+The existence of a mailbox/maildir for the corresponding
+SMTP recipient is checked later in the delivery chain.
+
+In case no Mailbox/Maildir exists, the message is bounced
+back to the SMTP sender ("MAIL From: <send@example.com>").
+
+For normal SMTP mail traffic thats fine as long as the rate
+of undeliverable messages dont exceed 10% and the sender is
+'legitmate'; ie. exists.
+
+Todays situation is different: Spam and Virus attacks with
+forged/faked sender addresses to a bunch of random
+recipient addresses yield a undeliverable rate up to 90%.
+
+Worse, the generated bounces will never reach the sender and
+a double-bounce is eventually send to the postmaster.
+
+
+2. qmail-smtpd RECIPIENTS:
+
+The RECIPIENTS extension makes qmail-smtpd aware of acceptable
+recipients, which are fetched from an external source.
+Which source to query depends on the domain-part of the
+recipient address.
+
+- The recipients are kept either in 'fastforward' compatible
+ cdbs for quick lookup during the SMTP session, or
+- are available by means of a 'checkpassword' compatible
+ Plugable Authentication Module (PAM).
+
+The RECIPIENTS mechanism supports natively Qmail's address
+extensions (VERP). If a recipient address like 'foo@mydomain.com'
+defined, all VERP addresses like 'foo-bar@mydomain.com' are
+accepted for SMTP reception.
+
+The RECIPIENTS lookup is triggered by the recipient domain, thus
+is domain-specific. The domain-part of the envelope address
+is evaluated in lower case. You can specify which lookup is performed
+per domain within control/recipients. Consider the following:
+
+a) An entry 'example.com' is used to match 'example.com' and
+ in addition all subdomain addresses '*.example.com';
+ depending in addition on 'control/rcpthosts'.
+b) An entry '@example.com' serves as exact match for the
+ domain address.
+c) The entry '*' will match all domains for the respective lookup.
+d) Reversely, domains flagged as '!domain.com' are not queried
+ and all recipients for this domain are accepted.
+e) A 'fail-open' behaviour can be achieved adding '!*' as last
+ statement in control/recipients. Thus, emails for domains not
+ listed in control/recipients will finally be accepted.
+
+Thus, the RECIPIENTS extension can be used in a 'fail-closed' or
+'fail-open' mode for the domains included in control/recipients.
+Without including '!*' on the last line, the recipient check is done
+'fail-closed', thus if all queries are negative, the incoming email
+with this recipient address will be rejected.
+
+The RECIPIENTS check is done only in a none-RELAYCLIENT case
+and after control/rcpthosts, control/morercpthosts.cdb has been
+successfully consulted.
+
+NOTE: The new wilddomain mechanism superseeds the old cdb-only
+ wilddomain syntax (which is not working anymore).
+ The PAM should be in your $PATH or referenced with full path.
+
+
+3. Setting up the recipients control file:
+
+Release 0.5 the RECIPIENTS extension provides a flexible
+new syntax to interprete control/recipients on a domain
+base, as part of the RCPT TO: envelope address.
+
+a) Read 'man qmail-smtpd' and 'man qmail-recipients.'
+ Some additional scripts can be found in doc.
+
+b) Legacy:
+ Put 'recipients.cdb' into control/recipients.
+ This is a backward compatible mode.
+
+c) Per Domain cdbs:
+ Put 'example.com:example.cdb' in
+ control/recipients and you advise the
+ RECIPIENTS extension to do a per-domain lookup.
+
+d) Global cdbs:
+ Use '*:users/recipients.cdb' in
+ control/recipients.
+ This is equivalent to (1.).
+
+e) Per Domain PAM:
+ Put 'example.com|checkpassword true'
+ into control/recipients and the RECIPIENT
+ extension will use the program defined
+ after the "|" to check the existence of
+ the provided RCPT TO.
+
+f) Global PAM:
+ Put '*|ldapam myldapserver' into
+ control/recipients and you delegate the entire
+ verification of the RCPT TO to the program in charge.
+
+g) Wildcarded domain:
+ Prepend the domain name with a '!' and
+ emails for this domain will be entirely accepted:
+ '!localhost'.
+
+h) Pass-Thru for unlisted domains:
+ Use '!*' as last statement in control/recipients.
+
+Lines in control/recipients starting with a '#'
+are not evaluated, thus are treated as comment lines.
+
+
+4. Generating a cdb with recipient addresses:
+
+a) Build a list of recipients (with full qualified address).
+- Use 'qmail-pwd2recipients' to build this list for
+ local system users.
+- Use 'qmail-alias2recipients' to build this list for
+ qmail alias users (ie. postmaster, root).
+- Use 'qmail-users2recipients' to build this list for
+ qmail users (as per users/assign).
+- You can use 'qmail-vpopmail2recipients' for
+ vpopmail users.
+
+ Verify that list to be found under users/recipients.
+ If you have a different Qmail home directory, modify the
+ above scripts.
+
+ You may need to change "localhost" in the above scripts
+ to the real hostname.
+
+b) Run qmail-recipients to transform that list into a cdb:
+ users/recipients.cdb
+
+c) After the successful generation of the recipients.cdb
+ you can rename it to your taste.
+
+d) Edit control/recipients and
+ include users/recipients.cdb therein.
+
+e) If you have 'fastforward' cdbs (those which are generated
+ by 'setforward') you have to place the output somewhere
+ in a subdirectory under Qmail's home directory and
+ include those into control/recipients.
+
+ At that time, your control/recipients file may look like:
+
+ mydomain.com:control/mydomain.cdb
+ users/recipients.cdb
+ etc/fastforward.cdb
+
+f) You can add an arbitary number of cdbs to control/recipients.
+ Any change regarding control/recipients and/or the content
+ of the cdbs is effective on the fly.
+
+
+5. VERP support
+
+The RECIPIENTS extension allows now per default VERP support.
+The local part of the recipient addresses is truncted AFTER
+the character defined as AUTO_BREAK and only the first part
+of the address (plus domain) is used for the evaluation.
+
+a) If you run EZMLM, you have to set up a list of recipient
+ addresses for all your mailing lists.
+
+b) Simply put the full qualified list name apppended with the VERP
+ charcater into the recipients database (or into the LDAP dir).
+
+c) Sample: If your list is called:
+
+ mylist@example.com
+
+ define
+
+ mylist-@example.com
+
+ This makes VERP addresses distinguishable from normal addresses.
+
+d) In order to support generic and VERP addresses, you have to
+ add both address schemes into the recipient database:
+
+ me@example.com
+ me-@example.com
+
+
+6. Using a checkpassword compatible PAM:
+
+The checkpassword API is defined in:
+
+ http://cr.yp.to/checkpwd/interface.html
+
+and typically consists of the string:
+
+ username\0password\0timestamp\0otherdata\0
+
+written to file descriptor 3 (FD 3) to be read by the
+checkpassword compatible PAM.
+
+For email address (recipient) verification, we replace
+ username\0
+with
+ email-address\0
+ie.
+ recipient@domain.tld\0
+
+The PAM fetches this information and checks for it's
+existance in any external resource, for example a LDAP
+directory or a SQL database.
+
+The PAM returns a '0' in case of successful verification,
+otherwise a '1'; and perhaps a '111' in case of problems.
+
+RECIPIENT's checkpassword API allows to enter up to five
+additional arguments; which are specific to the PAM.
+
+The attached PERL ldap_mail.pl serves as a sample.
+
+
+7. Customization:
+
+The RECIPIENTS extension needs no customization except for
+the following circumstances:
+
+a) You may need to adjust the provided scripts
+ 'qmail-pwd2recipients', 'qmail-users2recipients', and
+ 'qmail-alias2recipient' to your need; these are samples.
+
+b) The script 'qmail-vpopmail2recipients' is contributed
+ by David Du SERRE-TELMON, pls. check whether it
+ suits your vpopmail installation.
+
+c) A phyton script to generate "Recipients" users out of
+ - /var/qmail/users/assign
+ - /var/qmail/alias
+ -/etc/aliases
+ and the vpopmail's virtual users can be found at:
+
+ http://www.epigenomics.org/software/oss/qmail/create_recipients.py
+
+ Contributed by Robert Sander
+
+
+
+Erwin Hoffmann (www.fehcom.de) - Cologne 2009-09-02
diff --git a/doc/Old/README.wildmat b/doc/Old/README.wildmat
new file mode 100644
index 0000000..ccfbe0e
--- /dev/null
+++ b/doc/Old/README.wildmat
@@ -0,0 +1,100 @@
+/* THIS FILE IS INCLUDED FOR HISTORICAL REASONS ONLY */
+
+
+EADME.wildmat.orig Wed Dec 3 11:46:31 1997
+--- README.wildmat Wed Dec 3 11:53:33 1997
+***************
+*** 0 ****
+--- 1,50 ----
++ wilmat patch version 0.2 for qmail 1.01
++ Mark Delany <markd@mira.net.au>
++ 19971203
++
++ Changes:
++ --------
++ 0.1 Initial code
++ 0.2 Fixed buglet relating to systems that had no badmailfrom file
++ but do have a badmailpattern file
++
++ While the 'badmailfrom' provides some ability to block spam it is
++ fairly restricted as the match must be exact on either the full string
++ or the domain. This means that it's very difficult to block the
++ 1234567@aol.com type addresses that some spammers are employing as you
++ potentially require a large number of entries in 'badmailfrom'.
++
++ This patch provides the ability to use simple patterns to reject mail
++ from unwanted envelope sender addresses. Naturally all such methods
++ are of limited use against spam as a determined spammer cannot be
++ stopped on the current Internet, but it does help until the time comes
++ that we can really stop spammers.
++
++ The wildmat patch introduces a new control file called
++ 'badmailpatterns' and is used by qmail-smtpd in conjunction with
++ 'badmailfrom'. You should continue to use 'badmailfrom' when you can
++ as this is much more CPU-efficient than 'badmailpatterns'.
++
++ For those familiar with INN, the wildmat patch uses the wildmat()
++ routine out of INN and evaluates in the same way. Namely that the
++ envelope sender is pushed thru all patterns and the final match or
++ non-match is used to determine whether to reject the mail. It's
++ implemented this way so that 'not' patterns work.
++
++ Here is a sample 'badmailpatterns' file:
++
++ *@earthlink.net
++ !fred@earthlink.net
++ [0-9][0-9][0-9][0-9][0-9][0-9]@[0-9][0-9][0-9][0-9].com
++ answerme@save*
++
++ This file stops all mail from Earthlink except from
++ fred@earthlink.net. It also stops all mail with addresses like:
++ 123456@1234.com and answerme@savetrees.com
++
++ This patch does not update the documentation or qmail-showctl.
++
++ Thanks to Rich Salz for providing wildmat.c by way of the INN
++ distribution. wildmat.c is fast, small and completely self-contained.
++
++ --
+*** wildmat.c.orig Wed Dec 3 11:46:31 1997
+--- wildmat.c Wed Dec 3 11:46:31 1997
+***************
+*** 0 ****
+--- 1,172 ----
++ /* $Revision: 1.1 $
++ **
++ ** Do shell-style pattern matching for ?, \, [], and * characters.
++ ** Might not be robust in face of malformed patterns; e.g., "foo[a-"
++ ** could cause a segmentation violation. It is 8bit clean.
++ **
++ ** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
++ ** Rich $alz is now <rsalz@osf.org>.
++ ** April, 1991: Replaced mutually-recursive calls with in-line code
++ ** for the star character.
++ **
++ ** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
++ ** This can greatly speed up failing wildcard patterns. For example:
++ ** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
++ ** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
++ ** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
++ ** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without
++ ** the ABORT code, it takes 22310 calls to fail. Ugh. The following
++ ** explanation is from Lars:
++ ** The precondition that must be fulfilled is that DoMatch will consume
++ ** at least one character in text. This is true if *p is neither '*' nor
++ ** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic
++ ** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx". With
++ ** FALSE, each star-loop has to run to the end of the text; with ABORT
++ ** only the last one does.
++ **
++ ** Once the control of one instance of DoMatch enters the star-loop, that
++ ** instance will return either TRUE or ABORT, and any calling instance
++ ** will therefore return immediately after (without calling recursively
++ ** again). In effect, only one star-loop is ever active. It would be
++ ** possible to modify the code to maintain this context explicitly,
++ ** eliminating all recursive calls at the cost of some complication and
++ ** loss of clarity (and the ABORT stuff seems to be unclear enough by
++ ** itself). I think it would be unwise to try to get this into a
++ ** released version unless you have a good test data base to try it out
++ ** on.
++ */
diff --git a/doc/Postgrey.txt b/doc/Postgrey.txt
new file mode 100644
index 0000000..dca92d3
--- /dev/null
+++ b/doc/Postgrey.txt
@@ -0,0 +1,233 @@
+POSTGREY(1) User Contributed Perl Documentation POSTGREY(1)
+
+
+
+
+NAME
+ postgrey - Postfix Greylisting Policy Server
+
+SYNOPSIS
+ postgrey [options...]
+
+ -h, --help display this help and exit
+ --version output version information and exit
+ -v, --verbose increase verbosity level
+ --syslog-facility Syslog facility to use (default mail)
+ -q, --quiet decrease verbosity level
+ -u, --unix=PATH listen on unix socket PATH
+ --socketmode=MODE unix socket permission (default 0666)
+ -i, --inet=[HOST:]PORT listen on PORT, localhost if HOST is not specified
+ -d, --daemonize run in the background
+ --pidfile=PATH put daemon pid into this file
+ --user=USER run as USER (default: postgrey)
+ --group=GROUP run as group GROUP (default: nogroup)
+ --dbdir=PATH put db files in PATH (default: /var/spool/postfix/postgrey)
+ --delay=N greylist for N seconds (default: 300)
+ --max-age=N delete entries older than N days since the last time
+ that they have been seen (default: 35)
+ --retry-window=N allow only N days for the first retrial (default: 2)
+ append 'h' if you want to specify it in hours
+ --greylist-action=A if greylisted, return A to Postfix (default: DEFER_IF_PERMIT)
+ --greylist-text=TXT response when a mail is greylisted
+ (default: Greylisted + help url, see below)
+ --lookup-by-subnet strip the last N bits from IP addresses, determined by ipv4cidr and ipv6cidr (default)
+ --ipv4cidr=N What cidr to use for the subnet on IPv4 addresses when using lookup-by-subnet (default: 24)
+ --ipv6cidr=N What cidr to use for the subnet on IPv6 addresses when using lookup-by-subnet (default: 64)
+ --lookup-by-host do not strip the last 8 bits from IP addresses
+ --privacy store data using one-way hash functions
+ --hostname=NAME set the hostname (default: `hostname`)
+ --exim don't reuse a socket for more than one query (exim compatible)
+ --whitelist-clients=FILE default: /etc/postfix/postgrey_whitelist_clients
+ --whitelist-recipients=FILE default: /etc/postfix/postgrey_whitelist_recipients
+ --auto-whitelist-clients=N whitelist host after first successful delivery
+ N is the minimal count of mails before a client is
+ whitelisted (turned on by default with value 5)
+ specify N=0 to disable.
+ --listen-queue-size=N allow for N waiting connections to our socket
+ --x-greylist-header=TXT header when a mail was delayed by greylisting
+ default: X-Greylist: delayed <seconds> seconds by postgrey-<version> at <server>; <date>
+
+ Note that the --whitelist-x options can be specified multiple times,
+ and that per default /etc/postfix/postgrey_whitelist_clients.local is
+ also read, so that you can put there local entries.
+
+DESCRIPTION
+ Postgrey is a Postfix policy server implementing greylisting.
+
+ When a request for delivery of a mail is received by Postfix via SMTP,
+ the triplet "CLIENT_IP" / "SENDER" / "RECIPIENT" is built. If it is the
+ first time that this triplet is seen, or if the triplet was first seen
+ less than delay seconds (300 is the default), then the mail gets
+ rejected with a temporary error. Hopefully spammers or viruses will not
+ try again later, as it is however required per RFC.
+
+ Note that you shouldn't use the --lookup-by-host option unless you know
+ what you are doing: there are a lot of mail servers that use a pool of
+ addresses to send emails, so that they can change IP every time they
+ try again. That's why without this option postgrey will strip the last
+ byte of the IP address when doing lookups in the database.
+
+ Installation
+ o Create a "postgrey" user and the directory where to put the
+ database dbdir (default: "/var/spool/postfix/postgrey")
+
+ o Write an init script to start postgrey at boot and start it. Like
+ this for example:
+
+ postgrey --inet=10023 -d
+
+ contrib/postgrey.init in the postgrey source distribution includes
+ a LSB-compliant init script by Adrian von Bidder for the Debian
+ system.
+
+ o Put something like this in /etc/main.cf:
+
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ ...
+ reject_unauth_destination
+ check_policy_service inet:127.0.0.1:10023
+
+ o Install the provided postgrey_whitelist_clients and
+ postgrey_whitelist_recipients in /etc/postfix.
+
+ o Put in /etc/postfix/postgrey_whitelist_recipients users that do not
+ want greylisting.
+
+ Whitelists
+ Whitelists allow you to specify client addresses or recipient address,
+ for which no greylisting should be done. Per default postgrey will read
+ the following files:
+
+ /etc/postfix/postgrey_whitelist_clients
+ /etc/postfix/postgrey_whitelist_clients.local
+ /etc/postfix/postgrey_whitelist_recipients
+
+ You can specify alternative paths with the --whitelist-x options.
+
+ Postgrey whitelists follow similar syntax rules as Postfix access
+ tables. The following can be specified for recipient addresses:
+
+ domain.addr
+ "domain.addr" domain and subdomains.
+
+ name@ "name@.*" and extended addresses "name+blabla@.*".
+
+ name@domain.addr
+ "name@domain.addr" and extended addresses.
+
+ /regexp/ anything that matches "regexp" (the full address is matched).
+
+ The following can be specified for client addresses:
+
+ domain.addr
+ "domain.addr" domain and subdomains.
+
+ IP1.IP2.IP3.IP4
+ IP address IP1.IP2.IP3.IP4. You can also leave off one
+ number, in which case only the first specified numbers will
+ be checked.
+
+ IP1.IP2.IP3.IP4/MASK
+ CIDR-syle network. Example: 192.168.1.0/24
+
+ /regexp/ anything that matches "regexp" (the full address is matched).
+
+ Auto-whitelisting clients
+ With the option --auto-whitelist-clients a client IP address will be
+ automatically whitelisted if the following conditions are met:
+
+ o At least 5 successfull attempts of delivering a mail (after
+ greylisting was done). That number can be changed by specifying a
+ number after the --auto-whitelist-clients argument. Only one
+ attempt per hour counts.
+
+ o The client was last seen before --max-age days (35 per default).
+
+ Greylist Action
+ To set the action to be returned to postfix when a message fails
+ postgrey's tests and should be deferred, use the
+ --greylist-action=ACTION option.
+
+ By default, postgrey returns DEFER_IF_PERMIT, which causes postfix to
+ check the rest of the restrictions and defer the message only if it
+ would otherwise be accepted. A delay action of 451 causes postfix to
+ always defer the message with an SMTP reply code of 451 (temp fail).
+
+ See the postfix manual page access(5) for a discussion of the actions
+ allowed.
+
+ Greylist Text
+ When a message is greylisted, an error message like this will be sent
+ at the SMTP-level:
+
+ Greylisted, see http://postgrey.schweikert.ch/help/example.com.html
+
+ Usually no user should see that error message and the idea of that URL
+ is to provide some help to system administrators seeing that message or
+ users of broken mail clients which try to send mails directly and get a
+ greylisting error. Note that the default help-URL contains the original
+ recipient domain (example.com), so that domain-specific help can be
+ presented to the user (on the default page it is said to contact
+ postmaster@example.com)
+
+ You can change the text (and URL) with the --greylist-text parameter.
+ The following special variables will be replaced in the text:
+
+ %s How many seconds left until the greylisting is over (300).
+
+ %r Mail-domain of the recipient (example.com).
+
+ Greylist Header
+ When a message is greylisted, an additional header can be prepended to
+ the header section of the mail:
+
+ X-Greylist: delayed %t seconds by postgrey-%v at %h; %d
+
+ You can change the text with the --x-greylist-header parameter. The
+ following special variables will be replaced in the text:
+
+ %t How many seconds the mail has been delayed due to greylisting.
+
+ %v The version of postgrey.
+
+ %d The date.
+
+ %h The host.
+
+
+ Privacy
+ The --privacy option enable the use of a SHA1 hash function to store
+ IPs and emails in the greylisting database. This will defeat straight
+ forward attempts to retrieve mail user behaviours.
+
+ SEE ALSO
+ See <http://www.greylisting.org/> for a description of what greylisting
+ is and <http://www.postfix.org/SMTPD_POLICY_README.html> for a
+ description of how Postfix policy servers work.
+
+COPYRIGHT
+ Copyright (c) 2004-2007 by ETH Zurich. All rights reserved. Copyright
+ (c) 2007 by Open Systems AG. All rights reserved.
+
+LICENSE
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 675 Mass Ave, Cambridge, MA 02139, USA.
+
+AUTHOR
+ David Schweikert <david@schweikert.ch>
+
+
+
+perl v5.32.0 2015-09-01 POSTGREY(1)
diff --git a/doc/Qmail/BLURB b/doc/Qmail/BLURB
new file mode 100644
index 0000000..48ae4c4
--- /dev/null
+++ b/doc/Qmail/BLURB
@@ -0,0 +1,222 @@
+Qmail BLURB
+===========
+
+qmail is a secure, reliable, efficient, simple message transfer agent.
+It is meant as a replacement for the entire sendmail-binmail system on
+typical Internet-connected UNIX hosts.
+
+Secure: Security isn't just a goal, but an absolute requirement. Mail
+delivery is critical for users; it cannot be turned off, so it must be
+completely secure. (This is why I started writing qmail: I was sick of
+the security holes in sendmail and other MTAs.)
+
+Reliable: qmail's straight-paper-path philosophy guarantees that a
+message, once accepted into the system, will never be lost. qmail also
+supports maildir, a new, super-reliable user mailbox format. Maildirs,
+unlike mbox files and mh folders, won't be corrupted if the system
+crashes during delivery. Even better, not only can a user safely read
+his mail over NFS, but any number of NFS clients can deliver mail to him
+at the same time.
+
+Efficient: On a Pentium under BSD/OS, qmail can easily sustain 200000
+local messages per day---that's separate messages injected and delivered
+to mailboxes in a real test! Although remote deliveries are inherently
+limited by the slowness of DNS and SMTP, qmail overlaps 20 simultaneous
+deliveries by default, so it zooms quickly through mailing lists. (This
+is why I finished qmail: I had to get a big mailing list set up.)
+
+Simple: qmail is vastly smaller than any other Internet MTA. Some
+reasons why: (1) Other MTAs have separate forwarding, aliasing, and
+mailing list mechanisms. qmail has one simple forwarding mechanism that
+lets users handle their own mailing lists. (2) Other MTAs offer a
+spectrum of delivery modes, from fast+unsafe to slow+queued. qmail-send
+is instantly triggered by new items in the queue, so the qmail system
+has just one delivery mode: fast+queued. (3) Other MTAs include, in
+effect, a specialized version of inetd that watches the load average.
+qmail's design inherently limits the machine load, so qmail-smtpd can
+safely run from your system's inetd.
+
+Replacement for sendmail: qmail supports host and user masquerading,
+full host hiding, virtual domains, null clients, list-owner rewriting,
+relay control, double-bounce recording, arbitrary RFC 822 address lists,
+cross-host mailing list loop detection, per-recipient checkpointing,
+downed host backoffs, independent message retry schedules, etc. In
+short, it's up to speed on modern MTA features. qmail also includes a
+drop-in ``sendmail'' wrapper so that it will be used transparently by
+your current UAs.
+
+Mailing Lists
+=============
+
+Mailing list management is one of qmail's strengths. Notable features:
+
+* qmail lets each user handle his own mailing lists. The delivery
+instructions for user-whatever go into ~user/.qmail-whatever.
+
+* qmail makes it really easy to set up mailing list owners. If the user
+touches ~user/.qmail-whatever-owner, all bounces will come back to him.
+
+* qmail supports VERPs, which permit completely reliable automated
+bounce handling for mailing lists of any size.
+
+* SPEED---qmail blasts through mailing lists an order of magnitude
+faster than sendmail. For example, one message was successfully
+delivered to 150 hosts around the world in just 70 seconds, with qmail's
+out-of-the-box configuration.
+
+* qmail automatically prevents mailing list loops, even across hosts.
+
+* qmail allows inconceivably gigantic mailing lists. No random limits.
+
+* qmail handles aliasing and forwarding with the same simple mechanism.
+For example, Postmaster is controlled by ~alias/.qmail-postmaster. This
+means that cross-host loop detection also applies to aliases.
+
+* qmail supports the ezmlm mailing list manager, which easily and
+automatically handles bounces, subscription requests, and archives.
+
+Features
+========
+
+Here are some of qmail's features.
+
+Setup:
+* automatic adaptation to your UNIX variant---no configuration needed
+* AIX, BSD/OS, FreeBSD, HP/UX, Irix, Linux, OSF/1, SunOS, Solaris, and more
+* automatic per-host configuration (config, config-fast)
+* quick installation---no big list of decisions to make
+
+Security:
+* clear separation between addresses, files, and programs
+* minimization of setuid code (qmail-queue)
+* minimization of root code (qmail-start, qmail-lspawn)
+* five-way trust partitioning---security in depth
+* optional logging of one-way hashes, entire contents, etc. (QUEUE_EXTRA)
+
+Message construction (qmail-inject):
+* RFC 822, RFC 1123
+* full support for address groups
+* automatic conversion of old-style address lists to RFC 822 format
+* sendmail hook for compatibility with current user agents
+* header line length limited only by memory
+* host masquerading (control/defaulthost)
+* user masquerading ($MAILUSER, $MAILHOST)
+* automatic Mail-Followup-To creation ($QMAILMFTFILE)
+
+SMTP service (qmail-smtpd):
+* RFC 821, RFC 1123, RFC 1651, RFC 1652, RFC 1854
+* 8-bit clean
+* 931/1413/ident/TAP callback (tcp-env)
+* relay control---stop unauthorized relaying by outsiders (control/rcpthosts)
+* no interference between relay control and forwarding
+* tcpd hook---reject SMTP connections from known abusers
+* automatic recognition of local IP addresses
+* per-buffer timeouts
+* hop counting
+
+Queue management (qmail-send):
+* instant handling of messages added to queue
+* parallelism limit (control/concurrencyremote, control/concurrencylocal)
+* split queue directory---no slowdown when queue gets big
+* quadratic retry schedule---old messages tried less often
+* independent message retry schedules
+* automatic safe queueing---no loss of mail if system crashes
+* automatic per-recipient checkpointing
+* automatic queue cleanups (qmail-clean)
+* queue viewing (qmail-qread)
+* detailed delivery statistics (qmailanalog, available separately)
+
+Bounces (qmail-send):
+* QSBMF bounce messages---both machine-readable and human-readable
+* HCMSSC support---language-independent RFC 1893 error codes
+* double bounces sent to postmaster
+
+Routing by domain (qmail-send):
+* any number of names for local host (control/locals)
+* any number of virtual domains (control/virtualdomains)
+* domain wildcards (control/virtualdomains)
+* configurable percent hack support (control/percenthack)
+* UUCP hook
+
+SMTP delivery (qmail-remote):
+* RFC 821, RFC 974, RFC 1123
+* 8-bit clean
+* automatic downed host backoffs
+* artificial routing---smarthost, localnet, mailertable (control/smtproutes)
+* per-buffer timeouts
+* passive SMTP queue---perfect for SLIP/PPP (serialmail, available separately)
+
+Forwarding and mailing lists (qmail-local):
+* address wildcards (.qmail-default, .qmail-foo-default, etc.)
+* sendmail .forward compatibility (dot-forward, available separately)
+* fast forwarding databases (fastforward, available separately)
+* sendmail /etc/aliases compatibility (fastforward/newaliases)
+* mailing list owners---automatically divert bounces and vacation messages
+* VERPs---automatic recipient identification for mailing list bounces
+* Delivered-To---automatic loop prevention, even across hosts
+* automatic mailing list management (ezmlm, available separately)
+
+Local delivery (qmail-local):
+* user-controlled address hierarchy---fred controls fred-anything
+* mbox delivery
+* reliable NFS delivery (maildir)
+* user-controlled program delivery: procmail etc. (qmail-command)
+* optional new-mail notification (qbiff)
+* optional NRUDT return receipts (qreceipt)
+* conditional filtering (condredirect, bouncesaying)
+
+POP3 service (qmail-popup, qmail-pop3d):
+* RFC 1939
+* UIDL support
+* TOP support
+* APOP hook
+* modular password checking (checkpassword, available separately)
+
+
+Internals
+=========
+
+qmail's modular, lightweight design and sensible queue management make
+it the fastest available message transfer agent. Here's how it stacks up
+against the competition in five different speed measurements.
+
+* Scheduling: I sent a message to 8192 ``trash'' recipients on my home
+machine. All the deliveries were done in a mere 78 seconds---a rate of
+over 9 million deliveries a day! Compare this to the speed advertised
+for Zmailer's scheduling: 1.1 million deliveries a day on a
+SparcStation-10/50. (My home machine is a 16MB Pentium-100 under BSD/OS,
+with the default qmail configuration. qmail's logs were piped through
+accustamp and written to disk as usual.)
+
+* Local mailing lists: When qmail is delivering a message to a mailbox,
+it physically writes the message to disk before it announces success---
+that way, mail doesn't get lost if the power goes out. I tried sending a
+message to 1024 local mailboxes on the same disk on my home machine; all
+the deliveries were done in 25.5 seconds. That's more than 3.4 million
+deliveries a day! Sending 1024 copies to a _single_ mailbox was just as
+fast. Compare these figures to Zmailer's advertised rate for throwing
+recipients away without even delivering the message---only 0.48 million
+per day on the SparcStation.
+
+* Mailing lists with remote recipients: qmail uses the same delivery
+strategy that makes LSOFT's LSMTP so fast for outgoing mailing lists---
+you choose how many parallel SMTP connections you want to run, and qmail
+runs exactly that many. Of course, performance varies depending on how
+far away your recipients are. The advantage of qmail over other packages
+is its smallness: for example, one Linux user is running 60 simultaneous
+connections, without swapping, on a machine with just 16MB of memory!
+
+* Separate local messages: What LSOFT doesn't tell you about LSMTP is
+how many _separate_ messages it can handle in a day. Does it get bogged
+down as the queue fills up? On my home machine, I disabled qmail's
+deliveries and then sent 5000 separate messages to one recipient. The
+messages were all safely written to the queue disk in 23 minutes, with
+no slowdown as the queue filled up. After I reenabled deliveries, all
+the messages were delivered to the recipient's mailbox in under 12
+minutes. End-to-end rate: more than 200000 individual messages a day!
+
+* Overall performance: What really matters is how well qmail performs
+with your mail load. Red Hat Software found one day that their mail hub,
+a 48MB Pentium running sendmail 8.7, was running out of steam at 70000
+messages a day. They shifted the load to qmail---on a _smaller_ machine,
+a 16MB 486/66---and now they're doing fine.
diff --git a/doc/Qmail/FAQ b/doc/Qmail/FAQ
new file mode 100644
index 0000000..8540dbd
--- /dev/null
+++ b/doc/Qmail/FAQ
@@ -0,0 +1,706 @@
+1. Controlling the appearance of outgoing messages
+1.1. How do I set up host masquerading?
+1.2. How do I set up user masquerading?
+1.3. How do I set up Mail-Followup-To automatically?
+
+2. Routing outgoing messages
+2.1. How do I send local messages to another host?
+2.2. How do I set up a null client?
+2.3. How do I send outgoing mail through UUCP?
+2.4. How do I set up a separate queue for a SLIP/PPP link?
+2.5. How do I deal with ``CNAME lookup failed temporarily''?
+
+3. Routing incoming messages by host
+3.1. How do I receive mail for another host name?
+3.2. How do I set up a virtual domain?
+3.3. How do I set up several virtual domains for one user?
+
+4. Routing incoming messages by user
+4.1. How do I forward unrecognized usernames to another host?
+4.2. How do I set up a mailing list?
+4.3. How do I use majordomo with qmail?
+4.4. How do I use procmail with qmail?
+4.5. How do I use elm's filter with qmail?
+4.6. How do I create aliases with dots?
+4.7. How do I use sendmail's .forward files with qmail?
+4.8. How do I use sendmail's /etc/aliases with qmail?
+4.9. How do I make qmail defer messages during NFS or NIS outages?
+4.10. How do I change which account controls an address?
+
+5. Setting up servers
+5.1. How do I run qmail-smtpd under tcpserver?
+5.2. How do I set up qmail-qmtpd?
+5.3. How do I set up qmail-pop3d?
+5.4. How do I allow selected clients to use this host as a relay?
+5.5. How do I fix up messages from broken SMTP clients?
+5.6. How do I set up qmail-qmqpd?
+
+6. Configuring MUAs to work with qmail
+6.1. How do I make BSD mail generate a Date with the local time zone?
+6.2. How do I make pine work with qmail?
+6.3. How do I make MH work with qmail?
+6.4. How do I stop Sun's dtcm from hanging?
+
+7. Managing the mail system
+7.1. How do I safely stop qmail-send?
+7.2. How do I manually run the queue?
+7.3. How do I rejuvenate a message?
+7.4. How do I organize a big network?
+7.5. How do I back up and restore the queue disk?
+7.6. How do I run a supervised copy of qmail?
+7.7. How do I avoid syslog?
+
+8. Miscellany
+8.1. How do I tell qmail to do more deliveries at once?
+8.2. How do I keep a copy of all incoming and outgoing mail messages?
+8.3. How do I switch slowly from sendmail to qmail?
+
+
+
+1. Controlling the appearance of outgoing messages
+
+
+1.1. How do I set up host masquerading? All the users on this host,
+zippy.af.mil, are users on af.mil. When joe sends a message to fred, the
+message should say ``From: joe@af.mil'' and ``To: fred@af.mil'', without
+``zippy'' anywhere.
+
+Answer: echo af.mil > /var/qmail/control/defaulthost; chmod 644
+/var/qmail/control/defaulthost.
+
+
+1.2. How do I set up user masquerading? I'd like my own From lines to
+show boss@af.mil rather than god@heaven.af.mil.
+
+Answer: Add MAILHOST=af.mil and MAILUSER=boss to your environment. To
+override From lines supplied by your MUA, add QMAILINJECT=f to your
+environment.
+
+
+1.3. How do I set up Mail-Followup-To automatically? When I send a
+message to the sos@heaven.af.mil mailing list, I'd like to include
+``Mail-Followup-To: sos@heaven.af.mil''.
+
+Answer: Add QMAILMFTFILE=$HOME/.lists to your environment, and put
+sos@heaven.af.mil into ~/.lists.
+
+
+
+2. Routing outgoing messages
+
+
+2.1. How do I send local messages to another host? All the mail for
+af.mil should be delivered to our disk server, pokey.af.mil. I've set up
+an MX from af.mil to pokey.af.mil, but when a user on the af.mil host
+sends a message to boss@af.mil, af.mil tries to deliver it locally. How
+do I stop that?
+
+Answer: Remove af.mil from /var/qmail/control/locals. If qmail-send is
+running, give it a HUP. Make sure the MX is set up properly before you
+do this. Also make sure that pokey can receive mail for af.mil---see
+question 3.1.
+
+
+2.2. How do I set up a null client? I'd like zippy.af.mil to
+send all mail to bigbang.af.mil.
+
+Answer: echo :bigbang.af.mil > /var/qmail/control/smtproutes;
+chmod 644 /var/qmail/control/smtproutes. Disable local delivery as in
+question 2.1. Turn off qmail-smtpd in /etc/inetd.conf.
+
+
+2.3. How do I send outgoing mail through UUCP? I need qmail to send all
+outgoing mail via UUCP to my upstream UUCP site, gonzo.
+
+Answer: Put
+
+ :alias-uucp
+
+into control/virtualdomains and
+
+ |preline -df /usr/bin/uux - -r -gC
+ -a"${SENDER:-MAILER-DAEMON}" gonzo!rmail "($DEFAULT@$HOST)"
+
+(all on one line) into ~alias/.qmail-uucp-default. (For some UUCP
+software you will need to use -d instead of -df.) If qmail-send is
+running, give it a HUP.
+
+
+2.4. How do I set up a separate queue for a SLIP/PPP link?
+
+Answer: Use serialmail (http://pobox.com/~djb/serialmail.html).
+
+
+2.5. How do I deal with ``CNAME lookup failed temporarily''? The log
+showed that a message was deferred for this reason. Why is qmail doing
+CNAME lookups, anyway?
+
+Answer: The SMTP standard does not permit aliased hostnames, so qmail
+has to do a CNAME lookup in DNS for every recipient host. If the
+relevant DNS server is down, qmail defers the message. It will try again
+soon.
+
+
+
+3. Routing incoming messages by host
+
+
+3.1. How do I receive mail for another host name? I'd like our disk
+server, pokey.af.mil, to receive mail addressed to af.mil. I've set up
+an MX from af.mil to pokey.af.mil, but how do I get pokey to treat
+af.mil as a name for the local host?
+
+Answer: Add af.mil to /var/qmail/control/locals and to
+/var/qmail/control/rcpthosts. If qmail-send is running, give it a HUP
+(or do svc -h /var/run/qmail if qmail is supervised).
+
+
+3.2. How do I set up a virtual domain? I'd like any mail for
+nowhere.mil, including root@nowhere.mil and postmaster@nowhere.mil and
+so on, to be delivered to Bob. I've set up the MX already.
+
+Answer: Put
+
+ nowhere.mil:bob
+
+into control/virtualdomains. Add nowhere.mil to control/rcpthosts. If
+qmail-send is running, give it a HUP (or do svc -h /var/run/qmail if
+qmail is supervised).
+
+Now mail for whatever@nowhere.mil will be delivered locally to
+bob-whatever. Bob can set up ~bob/.qmail-default to catch all the
+possible addresses, ~bob/.qmail-info to catch info@nowhere.mil, etc.
+
+
+3.3. How do I set up several virtual domains for one user? Bob wants
+another virtual domain, everywhere.org, but he wants to handle
+nowhere.mil users and everywhere.org users differently. How can we do
+that without setting up a second account?
+
+Answer: Put two lines into control/virtualdomains:
+
+ nowhere.mil:bob-nowhere
+ everywhere.org:bob-everywhere
+
+Add nowhere.mil and everywhere.org to control/rcpthosts. If qmail-send
+is running, give it a HUP (or do svc -h /var/run/qmail if qmail is
+supervised).
+
+Now Bob can set up separate .qmail-nowhere-* and everywhere-* files. He
+can even set up .qmail-nowhere-default and .qmail-everywhere-default.
+
+
+
+4. Routing incoming messages by user
+
+
+4.1. How do I forward unrecognized usernames to another host? I'd like
+to set up a LUSER_RELAY pointing at bigbang.af.mil.
+
+Answer: Put
+
+ | forward "$LOCAL"@bigbang.af.mil
+
+into ~alias/.qmail-default.
+
+
+4.2. How do I set up a mailing list? I'd like me-sos@my.host.name to be
+forwarded to a bunch of people.
+
+Answer: Put a list of addresses into ~me/.qmail-sos, one per line. Then
+incoming mail for me-sos will be forwarded to each of those addresses.
+You should also touch ~me/.qmail-sos-owner so that bounces come back to
+you rather than the original sender.
+
+Alternative: ezmlm (http://pobox.com/~djb/ezmlm.html) is a modern
+mailing list manager, supporting automatic subscriptions, confirmations,
+archives, fully automatic bounce handling (including warnings to
+subscribers saying which messages they've missed), and more.
+
+
+4.3. How do I use majordomo with qmail?
+
+Answer: See ftp://ftp.eyrie.org/pub/software/majordomo/mjqmail and
+http://www.qmail.org for various methods. majordomo 2.0 is expected to
+support qmail directly.
+
+Beware that majordomo's lists are not crashproof.
+
+
+
+4.4. How do I use procmail with qmail?
+
+Answer: Put
+
+ | preline procmail
+
+into ~/.qmail. You'll have to use a full path for procmail unless
+procmail is in the system's startup PATH. Note that procmail will try to
+deliver to /var/spool/mail/$USER by default; to change this, see
+INSTALL.mbox.
+
+
+4.5. How do I use elm's filter with qmail?
+
+Answer: Put
+
+ | preline filter
+
+into ~/.qmail. You'll have to use a full path for filter unless filter
+is in the system's startup PATH.
+
+
+4.6. How do I create aliases with dots? I tried setting up
+~alias/.qmail-P.D.Q.Bach, but it doesn't do anything.
+
+Answer: Use .qmail-p:d:q:bach. Dots are converted to colons, and
+uppercase is converted to lowercase.
+
+
+4.7. How do I use sendmail's .forward files with qmail?
+
+Answer: Install the dot-forward package
+(http://pobox.com/~djb/dot-forward.html).
+
+
+4.8. How do I use sendmail's /etc/aliases with qmail?
+
+Answer: Install the fastforward package
+(http://pobox.com/~djb/fastforward.html).
+
+
+4.9. How do I make qmail defer messages during NFS or NIS outages? If
+~joe suddenly disappears, I'd like mail for joe to be deferred.
+
+Answer: Build a qmail-users database, so that qmail no longer checks
+home directories and the password database. This takes three steps.
+First, put your complete user list (including local and NIS passwords)
+into /var/qmail/users/passwd. Second, run
+
+ # qmail-pw2u -h < /var/qmail/users/passwd > /var/qmail/users/assign
+
+Here -h means that every user must have a home directory; if you happen
+to run qmail-pw2u during an NFS outage, it will print an error message
+and stop. Third, run
+
+ # qmail-newu
+
+Make sure to rebuild the database whenever you change your user list.
+
+
+4.10. How do I change which account controls an address? I set up
+~alias/.qmail-www, but qmail is looking at ~www/.qmail instead.
+
+Answer: If you do
+
+ # chown root ~www
+
+then qmail will no longer consider www to be a user; see qmail-getpw.0.
+For more precise control over address assignments, see qmail-users.0.
+
+
+
+5. Setting up servers
+
+
+5.1. How do I run qmail-smtpd under tcpserver? inetd is barfing at high
+loads, cutting off service for ten-minute stretches. I'd also like
+better connection logging.
+
+Answer: First, install the tcpserver program, part of the ucspi-tcp
+package (http://pobox.com/~djb/ucspi-tcp.html). Second, remove the smtp
+line from /etc/inetd.conf, and put the line
+
+ tcpserver -u 7770 -g 2108 0 smtp /var/qmail/bin/qmail-smtpd &
+
+into your system startup files. Replace 7770 with your qmaild uid, and
+replace 2108 with your nofiles gid. Don't forget the &. The change will
+take effect at your next reboot.
+
+By default, tcpserver allows at most 40 simultaneous qmail-smtpd
+processes. To raise this limit to 400, use tcpserver -c 400. To keep
+track of who's connecting and for how long, run (on two lines)
+
+ tcpserver -v -u 7770 -g 2108 0 smtp /var/qmail/bin/qmail-smtpd \
+ 2>&1 | /var/qmail/bin/splogger smtpd 3 &
+
+
+5.2. How do I set up qmail-qmtpd?
+
+Answer: Two steps. First, put a
+
+ qmtp 209/tcp
+
+line into /etc/services. Second, put (all on one line)
+
+ qmtp stream tcp nowait qmaild
+ /var/qmail/bin/tcp-env tcp-env /var/qmail/bin/qmail-qmtpd
+
+into /etc/inetd.conf, and give inetd a HUP.
+
+If you have tcpserver installed, skip the inetd step, and set up
+
+ tcpserver -u 7770 -g 2108 0 qmtp /var/qmail/bin/qmail-qmtpd &
+
+replacing 7770 and 2108 with the qmaild uid and nofiles gid. See
+question 5.1 for more details on tcpserver.
+
+
+5.3. How do I set up qmail-pop3d? My old POP server works with mbox
+delivery; I'd like to switch to maildir delivery.
+
+Answer: Four steps. First, install the checkpassword program
+(http://pobox.com/~djb/checkpwd.html). Second, make sure you have a
+
+ pop3 110/tcp
+
+line in /etc/services. Third, put (all on one line, including
+qmail-popup twice)
+
+ pop3 stream tcp nowait root
+ /var/qmail/bin/qmail-popup qmail-popup
+ YOURHOST /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir
+
+into /etc/inetd.conf, and give inetd a HUP; replace YOURHOST with your
+host's fully qualified domain name. Fourth, set up Maildir delivery for
+any user who wants to read mail via POP.
+
+If you have tcpserver installed, skip the inetd step, and set up (on two
+lines)
+
+ tcpserver 0 pop3 /var/qmail/bin/qmail-popup YOURHOST \
+ /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir &
+
+replacing YOURHOST with your host's fully qualified domain name. See
+question 5.1 for more details on tcpserver.
+
+Security note: pop3d should be used only within a secure network;
+otherwise an eavesdropper can steal passwords.
+
+
+5.4. How do I allow selected clients to use this host as a relay? I see
+that qmail-smtpd rejects messages to any host not listed in
+control/rcpthosts.
+
+Answer: Three steps. First, install tcp-wrappers, available separately,
+including hosts_options. Second, change your qmail-smtpd line in
+inetd.conf to
+
+ smtp stream tcp nowait qmaild /usr/local/bin/tcpd
+ /var/qmail/bin/tcp-env /var/qmail/bin/qmail-smtpd
+
+(all on one line) and give inetd a HUP. Third, in tcpd's hosts.allow,
+make a line setting the environment variable RELAYCLIENT to the empty
+string for the selected clients:
+
+ tcp-env: 1.2.3.4, 1.2.3.5: setenv = RELAYCLIENT
+
+Here 1.2.3.4 and 1.2.3.5 are the clients' IP addresses. qmail-smtpd
+ignores control/rcpthosts when RELAYCLIENT is set. (It also appends
+RELAYCLIENT to each envelope recipient address. See question 5.5 for an
+application.)
+
+Alternative procedure, if you are using tcpserver 0.80 or above: Create
+/etc/tcp.smtp containing
+
+ 1.2.3.6:allow,RELAYCLIENT=""
+ 127.:allow,RELAYCLIENT=""
+
+to allow clients with IP addresses 1.2.3.6 and 127.*. Run
+
+ tcprules /etc/tcp.smtp.cdb /etc/tcp.smtp.tmp < /etc/tcp.smtp
+
+Finally, insert
+
+ -x /etc/tcp.smtp.cdb
+
+after tcpserver in your qmail-smtpd invocation.
+
+
+5.5. How do I fix up messages from broken SMTP clients?
+
+Answer: Three steps. First, put
+
+ | bouncesaying 'Permission denied' [ "@$HOST" != "@fixme" ]
+ | qmail-inject -f "$SENDER" -- "$DEFAULT"
+
+into ~alias/.qmail-fixup-default. Second, put
+
+ fixme:fixup
+
+into /var/qmail/control/virtualdomains, and give qmail-send a HUP.
+Third, follow the procedure in question 5.4, but set RELAYCLIENT to the
+string ``@fixme'':
+
+ tcp-env: 1.2.3.6, 1.2.3.7: setenv = RELAYCLIENT @fixme
+
+Here 1.2.3.6 and 1.2.3.7 are the clients' IP addresses. If you are using
+tcpserver instead of inetd and tcpd, put
+
+ 1.2.3.6:allow,RELAYCLIENT="@fixme"
+ 1.2.3.7:allow,RELAYCLIENT="@fixme"
+
+into /etc/tcp.smtp, and run tcprules as in question 5.4.
+
+
+5.6. How do I set up qmail-qmqpd? I'd like to allow fast queueing of
+outgoing mail from authorized clients.
+
+Answer: Make sure you have installed tcpserver 0.80 or above. Create
+/etc/qmqp.tcp in tcprules format to allow connections from authorized
+hosts. For example, if queueing is allowed from 1.2.3.*:
+
+ 1.2.3.:allow
+ :deny
+
+Convert /etc/qmqp.tcp to /etc/qmqp.cdb:
+
+ tcprules /etc/qmqp.cdb /etc/qmqp.tmp < /etc/qmqp.tcp
+
+Finally, set up
+
+ tcpserver -x /etc/qmqp.cdb -u 7770 -g 2108 0 628 /var/qmail/bin/qmail-qmqpd &
+
+replacing 7770 and 2108 with the qmaild uid and nofiles gid. See
+question 5.1 for more details on tcpserver.
+
+
+
+6. Configuring MUAs to work with qmail
+
+
+6.1. How do I make BSD mail generate a Date with the local time zone?
+When I send mail, I'd rather use the local time zone than GMT, since
+some MUAs don't know how to display Date in the receiver's time zone.
+
+Answer: Put
+
+ set sendmail=/var/qmail/bin/datemail
+
+into your .mailrc or your system-wide Mail.rc. Beware that BSD mail is
+neither secure nor reliable.
+
+
+6.2. How do I make pine work with qmail?
+
+Answer: Put
+
+ sendmail-path=/usr/lib/sendmail -oem -oi -t
+
+into /usr/local/lib/pine.conf. (This will work with sendmail too.)
+Beware that pine is neither secure nor reliable.
+
+
+6.3. How do I make MH work with qmail?
+
+Answer: Put
+
+ postproc: /usr/mh/lib/spost
+
+into each user's .mh_profile. (This will work with sendmail too.) Beware
+that MH is neither secure nor reliable.
+
+
+6.4. How do I stop Sun's dtcm from hanging?
+
+Answer: There is a novice programming error in dtcm, known as ``failure
+to close the output side of the pipe in the child.'' Sun has, at the
+time of this writing, not yet provided a patch. Sorry.
+
+
+
+7. Managing the mail system
+
+
+7.1. How do I safely stop qmail-send? Back when we were running
+sendmail, it was always tricky to kill sendmail without risking the loss
+of current deliveries; what should I do with qmail-send?
+
+Answer: Go ahead and kill the qmail-send process. It will shut down
+cleanly. Wait for ``exiting'' to show up in the log. To restart qmail,
+run /var/qmail/rc the same way it is run from your system boot scripts,
+with the proper PATH, resource limits, etc.
+
+Alternative, if qmail is supervised: svc -t /var/run/qmail. The
+supervise process will kill qmail, wait for it to stop, and restart it.
+Use -d instead of -t if you don't want qmail to restart automatically;
+to manually restart it, use -u.
+
+
+7.2. How do I manually run the queue? I'd like qmail to try delivering
+all the remote messages right now.
+
+Answer: Give the qmail-send process an ALRM. (Do svc -a /var/run/qmail
+if qmail is supervised.)
+
+You may want to run qmail-tcpok first, to guarantee that qmail-remote
+will try all addresses. Normally, if an address fails repeatedly,
+qmail-remote leaves it alone for an hour.
+
+
+7.3. How do I rejuvenate a message? Somebody broke into Eric's computer
+again; it's going to be down for at least another two days. I know Eric
+has been expecting an important message---in fact, I see it sitting here
+in /var/qmail/queue/mess/15/26902. It's been in the queue for six days;
+how can I make sure it isn't bounced tomorrow?
+
+Answer: Just touch /var/qmail/queue/info/15/26902. (This is the only
+form of queue modification that's safe while qmail is running.)
+
+
+7.4. How do I organize a big network? I have a lot of machines, and I
+don't know where to start.
+
+Answer: First, choose the domain name where your users will receive
+mail. This is normally the shortest domain name you control. If you are
+in charge of *.movie.edu, you can use addresses like joe@movie.edu.
+
+Second, choose the machine that will know what to do with different
+users at movie.edu. Set up a host name in DNS for this machine:
+
+ mailhost.movie.edu IN A 1.2.3.4
+ 4.3.2.1.in-addr.arpa IN PTR mailhost.movie.edu
+
+Here 1.2.3.4 is the IP address of that machine.
+
+Third, make a list of machines where mail should end up. For example, if
+mail for Bob should end up on Bob's workstation, put Bob's workstation
+onto the list. For each of these machines, set up a host name in DNS:
+
+ bobshost.movie.edu IN A 1.2.3.7
+ 7.3.2.1.in-addr.arpa IN PTR bobshost.movie.edu
+
+Fourth, install qmail on bobshost.movie.edu. qmail will automatically
+configure itself to accept messages for bob@bobshost.movie.edu and
+deliver them to ~bob/Mailbox on bobshost. Do the same for the other
+machines where mail should end up.
+
+Fifth, install qmail on mailhost.movie.edu. Put
+
+ movie.edu:alias-movie
+
+into control/virtualdomains on mailhost. Then forward bob@movie.edu to
+bob@bobshost.movie.edu, by putting
+
+ bob@bobshost.movie.edu
+
+into ~alias/.qmail-movie-bob. Do the same for other users.
+
+Sixth, put movie.edu into control/rcpthosts on mailhost.movie.edu, so
+that mailhost.movie.edu will accept messages for users at movie.edu.
+
+Seventh, set up an MX record in DNS to deliver movie.edu messages to
+mailhost:
+
+ movie.edu IN MX 10 mailhost.movie.edu
+
+Eighth, on all your machines, put movie.edu into control/defaulthost.
+
+
+7.5. How do I back up and restore the queue disk?
+
+Answer: You can't.
+
+One difficulty is that you can't get a consistent snapshot of the queue
+while qmail-send is running. Another difficulty is that messages in the
+queue must have filenames that match their inode numbers.
+
+However, the big problem is that backups---even twice-daily backups---
+are far too unreliable for mail. If your disk dies, there will be very
+little overlap between the messages saved in the last backup and the
+messages that were lost.
+
+There are several ways to add real reliability to a mail server. Battery
+backups will keep your server alive, letting you park the disk to avoid
+a head crash, when the power goes out. Solid-state disks have their own
+battery backups. RAID boxes let you replace dead disks without losing
+any data.
+
+
+7.6. How do I run a supervised copy of qmail? svc sounds useful.
+
+Answer: Install daemontools (http://pobox.com/~djb/daemontools.html).
+Create a /var/run/qmail directory. Change
+
+ /var/qmail/rc
+
+to
+
+ supervise /var/run/qmail /var/qmail/rc
+
+in your boot scripts. Make sure that supervise is in the startup PATH.
+Now you can use svc to stop or restart qmail, and svstat to check
+whether qmail is running.
+
+
+7.7. How do I avoid syslog? It chews up a lot of CPU time and isn't
+reliable.
+
+Answer: Install daemontools (http://pobox.com/~djb/daemontools.html).
+Make a /var/log/qmail directory, owned by qmaill, mode 2700. Do
+
+ qmail-start ./Mailbox /usr/local/bin/accustamp \
+ | setuser qmaill /usr/local/bin/cyclog /var/log/qmail &
+
+in /var/qmail/rc.
+
+If you are logging tcpserver connections, make a /var/log/smtpd
+directory, and use cyclog /var/log/smtpd for tcpserver. You shouldn't
+run several copies of cyclog with the same log directory.
+
+By default, cyclog keeps 10 automatically rotated log files, each
+containing up to 100KB of log data. To keep 20 files with 1MB each, use
+cyclog -s 1000000 -n 20.
+
+
+
+8. Miscellany
+
+
+8.1. How do I tell qmail to do more deliveries at once? It's running
+only 20 parallel qmail-remote processes.
+
+Answer: Decide how many deliveries you want to allow at once. Put that
+number into control/concurrencyremote. Restart qmail-send as in question
+7.1. If your system has resource limits, make sure you set the
+descriptors limit to at least double the concurrency plus 5; otherwise
+you'll get lots of unnecessary deferrals whenever a big burst of mail
+shows up. Note that qmail also imposes a compile-time concurrency limit,
+120 by default; this is set in conf-spawn.
+
+
+8.2. How do I keep a copy of all incoming and outgoing mail messages?
+
+Answer: Set QUEUE_EXTRA to "Tlog\0" and QUEUE_EXTRALEN to 5 in extra.h.
+Recompile qmail. Put ./msg-log into ~alias/.qmail-log.
+
+You can also use QUEUE_EXTRA to, e.g., record the Message-ID of every
+message: run
+
+ | awk '/^$/ { exit } /^[mM][eE][sS][sS][aA][gG][eE]-/ { print }'
+
+from ~alias/.qmail-log.
+
+
+8.3. How do I switch slowly from sendmail to qmail? I'm thinking of
+moving the heaven.af.mil network over to qmail, but first I'd like to
+give my users a chance to try out qmail without affecting current
+sendmail deliveries. We're using NFS.
+
+Answer: Find a host in your network, say pc.heaven.af.mil, that isn't
+running an SMTP server. (If addresses at pc.heaven.af.mil are used, you
+should already have an MX pointing pc.heaven.af.mil to your mail hub.)
+
+Set up a new MX record pointing lists.heaven.af.mil to pc.heaven.af.mil.
+Install qmail on pc.heaven.af.mil. Replace pc with lists in the control
+files. Make the qmail man pages available on all your machines.
+
+Now tell your users about qmail. A user can forward joe@heaven.af.mil to
+joe@lists.heaven.af.mil to get ~/Mailbox delivery; he can set up .qmail
+files; he can start running his own mailing lists @lists.heaven.af.mil.
+
+When you're ready to turn sendmail off, you can set up pc.heaven.af.mil
+as your new mail hub. Add heaven.af.mil to control/locals, and change
+the heaven.af.mil MX to point to pc.heaven.af.mil. Make sure you leave
+lists.heaven.af.mil in control/locals so that transition addresses will
+continue to work.
diff --git a/doc/Qmail/INSTALL.alias b/doc/Qmail/INSTALL.alias
new file mode 100644
index 0000000..672365a
--- /dev/null
+++ b/doc/Qmail/INSTALL.alias
@@ -0,0 +1,40 @@
+qmail lets each user control all addresses of the form user-anything.
+Addresses that don't start with a username are controlled by a special
+user, alias. Delivery instructions for foo go into ~alias/.qmail-foo;
+delivery instructions for user-foo go into ~user/.qmail-foo. See
+dot-qmail.0 for the full story.
+
+qmail doesn't have any built-in support for /etc/aliases. If you have a
+big /etc/aliases and you'd like to keep it, install the fastforward
+package, available separately. /etc/aliases should already include the
+aliases discussed below---Postmaster, MAILER-DAEMON, and root.
+
+If you don't have a big /etc/aliases, you'll find it easier to use
+qmail's native alias mechanism. Here's a checklist of aliases you should
+set up right now.
+
+* Postmaster. You're not an Internet citizen if this address doesn't
+work. Simply touch (and chmod 644) ~alias/.qmail-postmaster; any mail
+for Postmaster will be delivered to ~alias/Mailbox.
+
+* MAILER-DAEMON. Not required, but users sometimes respond to bounce
+messages. Touch (and chmod 644) ~alias/.qmail-mailer-daemon.
+
+* root. Under qmail, root never receives mail. Your system may generate
+mail messages to root every night; if you don't have an alias for root,
+those messages will bounce. (They'll end up double-bouncing to the
+postmaster.) Set up an alias for root in ~alias/.qmail-root. .qmail
+files are similar to .forward files, but beware that they are strictly
+line-oriented---see dot-qmail.0 for details.
+
+* Other non-user accounts. Under qmail, non-user accounts don't get
+mail; ``user'' means a non-root account that owns ~account. Set up
+aliases for any non-user accounts that normally receive mail.
+
+Note that special accounts such as ftp, www, and uucp should always have
+home directories owned by root.
+
+* Default. If you want, you can touch ~alias/.qmail-default to catch
+everything else. Beware: this will also catch typos and other addresses
+that should probably be bounced instead. It won't catch addresses that
+start with a user name---the user can set up his own ~/.qmail-default.
diff --git a/doc/Qmail/INSTALL.ctl b/doc/Qmail/INSTALL.ctl
new file mode 100644
index 0000000..00ce689
--- /dev/null
+++ b/doc/Qmail/INSTALL.ctl
@@ -0,0 +1,38 @@
+As you've seen, qmail has essentially no pre-compilation configuration.
+You should never have to recompile it unless you want to change the
+qmail home directory, usernames, or uids.
+
+qmail does allow quite a bit of easy post-installation configuration. If
+you care how your machine greets other machines via SMTP, for example,
+you can put an appropriate line into /var/qmail/control/smtpgreeting.
+
+But this is all optional---if control/smtpgreeting doesn't exist, qmail
+will do something reasonable by default. You shouldn't worry much about
+configuration right now. You can always come back and tune things later.
+
+There's one big exception. You MUST tell qmail your hostname. Just run
+the config-fast script:
+
+ # ./config-fast your.full.host.name
+
+config-fast puts your.full.host.name into control/me. It also puts it
+into control/locals and control/rcpthosts, so that qmail will accept
+mail for your.full.host.name.
+
+You can instead use the config script, which looks up your host name in
+DNS:
+
+ # ./config
+
+config also looks up your local IP addresses in DNS to decide which
+hosts to accept mail for.
+
+(Why doesn't qmail do these lookups on the fly? This was a deliberate
+design decision. qmail does all its local functions---header rewriting,
+checking if a recipient is local, etc.---without talking to the network.
+The point is that qmail can continue accepting and delivering local mail
+even if your network connection goes down.)
+
+Next, read through FAQ for information on setting up optional features
+like masquerading. If you really want to learn right now what all the
+configuration possibilities are, see qmail-control.0.
diff --git a/doc/Qmail/INSTALL.ids b/doc/Qmail/INSTALL.ids
new file mode 100644
index 0000000..a50e10d
--- /dev/null
+++ b/doc/Qmail/INSTALL.ids
@@ -0,0 +1,72 @@
+Here's how to set up the qmail groups and the qmail users.
+
+On some systems there are commands that make this easy. Solaris and
+Linux:
+
+ # groupadd nofiles
+ # useradd -g nofiles -d /var/qmail/alias alias
+ # useradd -g nofiles -d /var/qmail qmaild
+ # useradd -g nofiles -d /var/qmail qmaill
+ # useradd -g nofiles -d /var/qmail qmailp
+ # groupadd qmail
+ # useradd -g qmail -d /var/qmail qmailq
+ # useradd -g qmail -d /var/qmail qmailr
+ # useradd -g qmail -d /var/qmail qmails
+
+FreeBSD 2.2:
+
+ # pw groupadd nofiles
+ # pw useradd alias -g nofiles -d /var/qmail/alias -s /nonexistent
+ # pw useradd qmaild -g nofiles -d /var/qmail -s /nonexistent
+ # pw useradd qmaill -g nofiles -d /var/qmail -s /nonexistent
+ # pw useradd qmailp -g nofiles -d /var/qmail -s /nonexistent
+ # pw groupadd qmail
+ # pw useradd qmailq -g qmail -d /var/qmail -s /nonexistent
+ # pw useradd qmailr -g qmail -d /var/qmail -s /nonexistent
+ # pw useradd qmails -g qmail -d /var/qmail -s /nonexistent
+
+BSDI 2.0:
+
+ # addgroup nofiles
+ # adduser -g nofiles -H/var/qmail/alias -G,,, -s/dev/null -P'*' alias
+ # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmaild
+ # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmaill
+ # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmailp
+ # addgroup qmail
+ # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmailq
+ # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmailr
+ # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmails
+
+AIX:
+
+ # mkgroup -A nofiles
+ # mkuser pgrp=nofiles home=/var/qmail/alias shell=/bin/true alias
+ # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmaild
+ # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmaill
+ # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmailp
+ # mkgroup -A qmail
+ # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmailq
+ # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmailr
+ # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmails
+
+On other systems, you will have to edit /etc/group and /etc/passwd
+manually. First add two new lines to /etc/group, something like
+
+ qmail:*:2107:
+ nofiles:*:2108:
+
+where 2107 and 2108 are different from the other gids in /etc/group.
+Next (using vipw) add six new lines to /etc/passwd, something like
+
+ alias:*:7790:2108::/var/qmail/alias:/bin/true
+ qmaild:*:7791:2108::/var/qmail:/bin/true
+ qmaill:*:7792:2108::/var/qmail:/bin/true
+ qmailp:*:7793:2108::/var/qmail:/bin/true
+ qmailq:*:7794:2107::/var/qmail:/bin/true
+ qmailr:*:7795:2107::/var/qmail:/bin/true
+ qmails:*:7796:2107::/var/qmail:/bin/true
+
+where 7790 through 7796 are _new_ uids, 2107 is the qmail gid, and 2108
+is the nofiles gid. Make sure you use the nofiles gid for qmaild,
+qmaill, qmailp, and alias, and the qmail gid for qmailq, qmailr, and
+qmails.
diff --git a/doc/Qmail/INSTALL.maildir b/doc/Qmail/INSTALL.maildir
new file mode 100644
index 0000000..72373aa
--- /dev/null
+++ b/doc/Qmail/INSTALL.maildir
@@ -0,0 +1,59 @@
+This file points out some reasons that you might want to switch from
+mbox format to a new format, maildir.
+
+
+1. The trouble with mbox
+
+The mbox format---the format of ~user/Mailbox, understood by BSD Mail
+and lots of other MUAs---is inherently unreliable.
+
+Think about it: what happens if the system crashes while a program is
+appending a new message to ~user/Mailbox? The message will be truncated.
+Even worse, if it was truncated in the middle of a line, it will end up
+being merged with the next message! Sure, the mailer understands that it
+wasn't successful, so it'll try delivering the message again later, but
+it can't fix your corrupted mbox.
+
+Other formats, such as mh folders, are just as unreliable.
+
+qmail supports maildir, a crashproof format for incoming mail messages.
+maildir is fast and easy for MUAs to use. Even better, maildir works
+wonders over NFS---see below.
+
+I don't want to cram maildir down people's throats, so it's not the
+default. Nevertheless, I encourage you to start asking for maildir
+versions of your favorite MUAs, and to switch over to maildir as soon as
+you can.
+
+
+2. Sun's Network F_ail_u_re System
+
+Anyone who tells you that mail can be safely delivered in mbox format
+over NFS is pulling your leg---as explained above, mbox format is
+inherently unreliable even on a single machine.
+
+Anyway, NFS is the most unreliable computing environment ever invented,
+and qmail doesn't even pretend to support mbox over NFS.
+
+You should switch to maildir, which works fine over NFS without any
+locking. You can safely read your mail over NFS if it's in maildir
+format. Any number of machines can deliver mail to you at the same time.
+(On the other hand, for efficiency, it's better to get NFS out of the
+picture---your mail should be delivered on the server that contains your
+home directory.)
+
+Here's how to set up qmail to use maildir for your incoming mail:
+
+ % maildirmake $HOME/Maildir
+ % echo ./Maildir/ > ~/.qmail
+
+Make sure you include the trailing slash on Maildir/.
+
+The system administrator can set up Maildir as the default for everybody
+by creating a maildir in the new-user template directory and replacing
+./Mailbox with ./Maildir/ in /var/qmail/rc.
+
+Until your MUA supports maildir, you'll probably want to convert maildir
+format to (gaaack) mbox format. I've supplied a maildir2mbox utility
+that does the trick, along with some tiny qail and elq and pinq wrappers
+that call maildir2mbox before calling Mail or elm or pine.
diff --git a/doc/Qmail/INSTALL.mbox b/doc/Qmail/INSTALL.mbox
new file mode 100644
index 0000000..93ca16c
--- /dev/null
+++ b/doc/Qmail/INSTALL.mbox
@@ -0,0 +1,53 @@
+The qmail package includes a local delivery agent, qmail-local, which
+provides user-controlled mailing lists, cross-host alias loop detection,
+and many other important qmail features.
+
+There's one important difference between qmail-local and binmail:
+qmail-local delivers mail by default into ~user/Mailbox, rather than
+/var/spool/mail/user. It uses mbox format, with lockf locking on systems
+that don't have flock (HP/UX, Solaris), and flock locking otherwise.
+
+This file explains how to switch your system to ~user/Mailbox. You
+aren't required to do this; for further discussion of /var/spool/mail,
+and an explanation of how to continue using binmail for local
+deliveries, see INSTALL.vsm.
+
+The basic procedure for switching to ~user/Mailbox is simple:
+
+ * Move each /var/spool/mail/user to ~user/Mailbox. For safety, do
+ this in single-user mode.
+
+ * As root, set up a symbolic link from /var/spool/mail/user to
+ ~user/Mailbox for each user. /var/spool/mail should be mode 1777,
+ so users will not be able to accidentally remove these links.
+
+A few mail programs are unable to handle symbolic links, so you will
+have to configure them to look at ~user/Mailbox directly:
+
+ * procmail: Change SYSTEM_MBOX in config.h and recompile; or, with
+ recent versions, define MAILSPOOLHOME in src/authenticate.c.
+
+An alternative to symbolic links is hlfsd. Consult the documentation for
+hlfsd if it is included in your operating system.
+
+If /var/spool/mail is large, you can gain extra speed by configuring
+all your mail software to look at ~user/Mailbox directly:
+
+ * Most MUAs: Put ``setenv MAIL $HOME/Mailbox'' in your system-wide
+ .cshrc and ``MAIL=$HOME/Mailbox; export MAIL'' in your system-wide
+ .profile.
+
+ * elm: Change "mailbox" to "Mailbox" around line 388 of newmbox.c and
+ recompile. (elm looks at $MAIL, but without this change elm will
+ fail if two users try to read mail simultaneously.)
+
+ * pine: Put ``inbox-path=Mailbox'' in your system-wide pine.conf.
+ (For pine versions more recent than 3.91, see also FAQ 6.2.)
+
+ * qpopper 2.2: Change /.mail to /Mailbox in pop_dropcopy.c and
+ recompile with -DHOMEDIRMAIL in CFLAGS.
+
+Some vendors, in a misguided attempt to solve the security problems of
+/var/spool/mail, have made all their mail software setgid mail. After
+you move the mailboxes, you can---and, for security, should---remove
+those setgid-mail bits.
diff --git a/doc/Qmail/INSTALL.qmail b/doc/Qmail/INSTALL.qmail
new file mode 100644
index 0000000..e3b0f09
--- /dev/null
+++ b/doc/Qmail/INSTALL.qmail
@@ -0,0 +1,84 @@
+SAVE COPIES OF YOUR OUTGOING MAIL! Like any other piece of software (and
+information generally), the qmail system comes with NO WARRANTY. It's
+much more secure and reliable than sendmail, but that's not saying much.
+
+
+Things you have to decide before starting:
+
+* The qmail home directory, normally /var/qmail. To change this
+directory, edit conf-qmail now.
+
+* The names of the qmail users and the qmail groups. To change these
+names, edit conf-users and conf-groups now.
+
+
+To create /var/qmail and configure qmail (won't interfere with sendmail):
+
+ 1. Create the qmail home directory:
+ # mkdir /var/qmail
+
+ 2. Read INSTALL.ids. You must set up the qmail group and the qmail
+ users before compiling the programs.
+
+ 3. Compile the programs and create the qmail directory tree:
+ # make setup check
+
+ 4. Read INSTALL.ctl and FAQ. Minimal survival command:
+ # ./config
+
+ 5. Read INSTALL.alias. Minimal survival command:
+ # (cd ~alias; touch .qmail-postmaster .qmail-mailer-daemon .qmail-root)
+ # chmod 644 ~alias/.qmail*
+
+ 6. Read INSTALL.mbox and INSTALL.vsm.
+
+ 7. Read INSTALL.maildir.
+
+ 8. Copy /var/qmail/boot/home (or proc) to /var/qmail/rc.
+
+
+To test qmail deliveries (won't interfere with sendmail):
+
+ 9. Enable deliveries of messages injected into qmail:
+ # csh -cf '/var/qmail/rc &'
+
+10. Read TEST.deliver.
+
+
+To upgrade from sendmail to qmail:
+
+11. Read SENDMAIL. This is what your users will want to know about the
+ switch from sendmail to qmail.
+
+12. Read REMOVE.sendmail. You must remove sendmail before installing
+ qmail.
+
+13. Read REMOVE.binmail.
+
+14. Add
+ csh -cf '/var/qmail/rc &'
+ to your boot scripts, so that the qmail daemons are restarted
+ whenever your system reboots. Make sure you include the &.
+
+15. Make qmail's ``sendmail'' wrapper available to MUAs:
+ # ln -s /var/qmail/bin/sendmail /usr/lib/sendmail
+ # ln -s /var/qmail/bin/sendmail /usr/sbin/sendmail
+ /usr/sbin might not exist on your system.
+
+16. Set up qmail-smtpd in /etc/inetd.conf (all on one line):
+ smtp stream tcp nowait qmaild /var/qmail/bin/tcp-env
+ tcp-env /var/qmail/bin/qmail-smtpd
+
+17. Reboot. (Or kill -HUP your inetd and make sure the qmail daemons
+ are running.)
+
+18. Read TEST.receive.
+
+
+
+That's it! To report success:
+ % ( echo 'First M. Last'; cat `cat SYSDEPS` ) | mail djb-qst@cr.yp.to
+Replace First M. Last with your name.
+
+If you have questions about qmail, join the qmail mailing list; see
+http://pobox.com/~djb/qmail.html.
diff --git a/doc/Qmail/INTERNALS b/doc/Qmail/INTERNALS
new file mode 100644
index 0000000..effda6f
--- /dev/null
+++ b/doc/Qmail/INTERNALS
@@ -0,0 +1,186 @@
+1. Overview
+
+Here's the data flow in the qmail suite:
+
+ qmail-qmpqd _
+ \
+ qmail-qmtpd __\
+ \
+ qmail-smtpd ---- qmail-queue --- qmail-send --- qmail-rspawn --- qmail-remote
+ / | \
+ qmail-inject -_/ qmail-clean \_ qmail-lspawn --- qmail-local
+
+Every message is added to a central queue directory by qmail-queue.
+qmail-queue is invoked as needed, usually by qmail-inject for locally
+generated messages, qmail-smtpd for messages received through SMTP,
+qmail-local for forwarded messages, or qmail-send for bounce messages.
+
+Every message is then delivered by qmail-send, in cooperation with
+qmail-lspawn and qmail-rspawn, and cleaned up by qmail-clean. These four
+programs are long-running daemons.
+
+The queue is designed to be crashproof, provided that the underlying
+filesystem is crashproof. All cleanups are handled by qmail-send and
+qmail-clean without human intervention. See section 6 for more details.
+
+
+2. Queue structure
+
+Each message in the queue is identified by a unique number, let's say
+457. The queue is organized into several directories, each of which may
+contain files related to message 457:
+
+ mess/457: the message
+ todo/X/457: the envelope: where the message came from, where it's going
+ intd/457: the envelope, under construction by qmail-queue
+ info/457: the envelope sender address, after preprocessing
+ local/457: local envelope recipient addresses, after preprocessing
+ remote/457: remote envelope recipient addresses, after preprocessing
+ bounce/457: permanent delivery errors
+
+Here are all possible states for a message. + means a file exists; -
+means it does not exist; ? means it may or may not exist; X is a hash directory.
+
+ S1. -mess -intd -todo -info -local -remote -bounce
+ S2. +mess -intd -todo -info -local -remote -bounce
+ S3. +mess +intd -todo -info -local -remote -bounce
+ S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued)
+ S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed)
+
+Guarantee: If mess/457 exists, it has inode number 457.
+
+
+3. How messages enter the queue
+
+To add a message to the queue, qmail-queue first creates a file in a
+separate directory, pid/, with a unique name. The filesystem assigns
+that file a unique inode number. qmail-queue looks at that number, say
+457. By the guarantee above, message 457 must be in state S1.
+
+qmail-queue renames pid/whatever as mess/457, moving to S2. It writes
+the message to mess/457. It then creates intd/457, moving to S3, and
+writes the envelope information to intd/457.
+
+Finally qmail-queue creates a new link, todo/457, for intd/457, moving
+to S4. At that instant the message has been successfully queued, and
+qmail-queue leaves it for further handling by qmail-send.
+
+qmail-queue starts a 24-hour timer before touching any files, and
+commits suicide if the timer expires.
+
+
+4. How queued messages are preprocessed
+
+Once a message has been queued, qmail-send must decide which recipients
+are local and which recipients are remote. It may also rewrite some
+recipient addresses.
+
+When qmail-send notices todo/457, it knows that message 457 is in S4. It
+removes info/457, local/457, and remote/457 if they exist. Then it reads
+through todo/457. It creates info/457, possibly local/457, and possibly
+remote/457. When it is done, it removes intd/457. The message is still
+in S4 at this point. Finally qmail-send removes todo/457, moving to S5.
+At that instant the message has been successfully preprocessed.
+
+
+5. How preprocessed messages are delivered
+
+Messages at S5 are handled as follows. Each address in local/457 and
+remote/457 is marked either NOT DONE or DONE.
+
+ DONE: The message was successfully delivered, or the last delivery
+ attempt met with permanent failure. Either way, qmail-send
+ should not attempt further delivery to this address.
+
+ NOT DONE: If there have been any delivery attempts, they have all
+ met with temporary failure. Either way, qmail-send should
+ try delivery in the future.
+
+qmail-send may at its leisure try to deliver a message to a NOT DONE
+address. If the message is successfully delivered, qmail-send marks the
+address as DONE. If the delivery attempt meets with permanent failure,
+qmail-send first appends a note to bounce/457, creating bounce/457 if
+necessary; then it marks the address as DONE. Note that bounce/457 is
+not crashproof.
+
+qmail-send may handle bounce/457 at any time, as follows: it (1) injects
+a new bounce message, created from bounce/457 and mess/457; (2) deletes
+bounce/457.
+
+When all addresses in local/457 are DONE, qmail-send deletes local/457.
+Same for remote/457.
+
+When local/457 and remote/457 are gone, qmail-send eliminates the
+message, as follows. First, if bounce/457 exists, qmail-send handles it
+as described above. Once bounce/457 is definitely gone, qmail-send
+deletes info/457, moving to S2, and finally mess/457, moving to S1.
+
+
+6. Cleanups
+
+If the computer crashes while qmail-queue is trying to queue a message,
+or while qmail-send is eliminating a message, the message may be left in
+state S2 or S3.
+
+When qmail-send sees a message in state S2 or S3---other than one
+it is currently eliminating!---where mess/457 is more than 36 hours old,
+it deletes intd/457 if that exists, then deletes mess/457. Note that any
+qmail-queue handling the message must be dead.
+
+Similarly, when qmail-send sees a file in the pid/ directory that is
+more than 36 hours old, it deletes it.
+
+Cleanups are not necessary if the computer crashes while qmail-send is
+delivering a message. At worst a message may be delivered twice. (There
+is no way for a distributed mail system to eliminate the possibility of
+duplication. What if an SMTP connection is broken just before the server
+acknowledges successful receipt of the message? The client must assume
+the worst and send the message again. Similarly, if the computer crashes
+just before qmail-send marks a message as DONE, the new qmail-send must
+assume the worst and send the message again. The usual solutions in the
+database literature---e.g., keeping log files---amount to saying that
+it's the recipient's computer's job to discard duplicate messages.)
+
+
+7. Bounces
+
+Bounces (aka 'None-Delivery Reports, NDR) are formated as QMBF messages.
+Generated by qmail-send, bounce message handling is not bullet proof.
+The size of bounce messages is typically larger than the original email
+and maybe therefore be subject of rejection by the sender, resulting
+in 'double bounces' (redirected to the postmaster).
+
+Bounce control can be achieved by means of 'control/bouncemaxbytes'
+truncating the bounce message to the specified size. Further, bounce
+hosts and be set up by 'control/smtproutes' and 'control/qmtroutes'.
+Double bounces can also be redirected to a special address provided in
+'control/doublebounceto' allowing in addition to dump double bounces.
+
+
+8. Further notes
+
+Currently info/457 serves two purposes: first, it records the envelope
+sender; second, its modification time is used to decide when a message
+has been in the queue too long. In the future info/457 may store more
+information. Any non-backwards-compatible changes will be identified by
+version numbers.
+
+When qmail-queue has successfully placed a message into the queue, it
+pulls a trigger offered by qmail-send. Here is the current triggering
+mechanism: lock/trigger is a named pipe. Before scanning todo/,
+qmail-send opens lock/trigger O_NDELAY for reading. It then selects for
+readability on lock/trigger. qmail-queue pulls the trigger by writing a
+byte O_NDELAY to lock/trigger. This makes lock/trigger readable and
+wakes up qmail-send. Before scanning todo/ again, qmail-send closes and
+reopens lock/trigger.
+
+The 'bigtodo' enhancements splits up the 'todo' dir into the number
+of subdirectories given by 'conf-split'. With a very large number of
+email in the state 'todo' this helps improving stat'ing and speeds up
+performance at almost no costs.
+
+--
+
+Note: The original description was written by DJB and is mostly unaltered.
+
+
diff --git a/doc/Qmail/PIC.local2alias b/doc/Qmail/PIC.local2alias
new file mode 100644
index 0000000..75cff56
--- /dev/null
+++ b/doc/Qmail/PIC.local2alias
@@ -0,0 +1,37 @@
+ Original message:
+
+ To: help
+ Hi.
+
+qmail-inject Fill in the complete envelope and header:
+
+ | (envelope) from joe@heaven.af.mil to help@heaven.af.mil
+ | From: joe@heaven.af.mil
+ | To: help@heaven.af.mil
+ |
+ | Hi.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, help@heaven.af.mil.
+ | Is heaven.af.mil in locals? Yes.
+ | Deliver locally to help@heaven.af.mil.
+ V
+
+qmail-lspawn ./Mailbox
+
+ | Look at mailbox name, help.
+ | Is help listed in qmail-users? No.
+ | Is there a help account? No.
+ | Give control of the message to alias.
+ | Run qmail-local.
+ V
+
+qmail-local alias ~alias help - help heaven.af.mil joe@heaven.af.mil ./Mailbox
+
+ Does ~alias/.qmail-help exist? Yes: "john".
+ Forward message to john.
diff --git a/doc/Qmail/PIC.local2ext b/doc/Qmail/PIC.local2ext
new file mode 100644
index 0000000..a8bf644
--- /dev/null
+++ b/doc/Qmail/PIC.local2ext
@@ -0,0 +1,41 @@
+ Original message:
+
+ To: fred-sos
+ Hi.
+
+qmail-inject Fill in the complete envelope and header:
+
+ | (envelope) from joe@heaven.af.mil to fred-sos@heaven.af.mil
+ | From: joe@heaven.af.mil
+ | To: fred-sos@heaven.af.mil
+ |
+ | Hi.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, fred-sos@heaven.af.mil.
+ | Is heaven.af.mil in locals? Yes.
+ | Deliver locally to fred-sos@heaven.af.mil.
+ V
+
+qmail-lspawn ./Mailbox
+
+ | Look at mailbox name, fred-sos.
+ | Is fred-sos listed in qmail-users? No.
+ | Is there a fred-sos account? No.
+ | Is there a fred account? Yes.
+ | Is fred's uid nonzero? Yes.
+ | Is ~fred visible to the qmailp user? Yes.
+ | Is ~fred owned by fred? Yes.
+ | Give control of the message to fred.
+ | Run qmail-local.
+ V
+
+qmail-local fred ~fred fred-sos - sos heaven.af.mil joe@heaven.af.mil ./Mailbox
+
+ Does ~fred/.qmail-sos exist? Yes: "./Extramail".
+ Write message to ./Extramail in mbox format.
diff --git a/doc/Qmail/PIC.local2local b/doc/Qmail/PIC.local2local
new file mode 100644
index 0000000..3a067e0
--- /dev/null
+++ b/doc/Qmail/PIC.local2local
@@ -0,0 +1,40 @@
+ Original message:
+
+ To: fred
+ Hi.
+
+qmail-inject Fill in the complete envelope and header:
+
+ | (envelope) from joe@heaven.af.mil to fred@heaven.af.mil
+ | From: joe@heaven.af.mil
+ | To: fred@heaven.af.mil
+ |
+ | Hi.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, fred@heaven.af.mil.
+ | Is heaven.af.mil in locals? Yes.
+ | Deliver locally to fred@heaven.af.mil.
+ V
+
+qmail-lspawn ./Mailbox
+
+ | Look at mailbox name, fred.
+ | Is fred listed in qmail-users? No.
+ | Is there a fred account? Yes.
+ | Is fred's uid nonzero? Yes.
+ | Is ~fred visible to the qmailp user? Yes.
+ | Is ~fred owned by fred? Yes.
+ | Give control of the message to fred.
+ | Run qmail-local.
+ V
+
+qmail-local fred ~fred fred '' '' heaven.af.mil joe@heaven.af.mil ./Mailbox
+
+ Does ~fred/.qmail exist? No.
+ Write message to ./Mailbox in mbox format.
diff --git a/doc/Qmail/PIC.local2rem b/doc/Qmail/PIC.local2rem
new file mode 100644
index 0000000..6857af5
--- /dev/null
+++ b/doc/Qmail/PIC.local2rem
@@ -0,0 +1,38 @@
+ Original message:
+
+ To: bill@irs.gov
+ Hi.
+
+qmail-inject Fill in the complete envelope and header:
+
+ | (envelope) from joe@heaven.af.mil to bill@irs.gov
+ | From: joe@heaven.af.mil
+ | To: bill@irs.gov
+ |
+ | Hi.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, bill@irs.gov.
+ | Is irs.gov in locals? No.
+ | Is bill@irs.gov in virtualdomains? No.
+ | Is irs.gov in virtualdomains? No.
+ | Is .gov in virtualdomains? No.
+ | Deliver remotely to bill@irs.gov.
+ V
+
+qmail-rspawn Run qmail-remote.
+
+ |
+ V
+
+qmail-remote Look at host name, irs.gov.
+ Is irs.gov listed in smtproutes? No.
+ Look up DNS MX/A for irs.gov and connect to it by SMTP:
+
+ MAIL FROM:<joe@heaven.af.mil>
+ RCPT TO:<bill@irs.gov>
diff --git a/doc/Qmail/PIC.local2virt b/doc/Qmail/PIC.local2virt
new file mode 100644
index 0000000..60f80c8
--- /dev/null
+++ b/doc/Qmail/PIC.local2virt
@@ -0,0 +1,44 @@
+ Original message:
+
+ To: dude@tommy.gov
+ Hi.
+
+qmail-inject Fill in the complete envelope and header:
+
+ | (envelope) from joe@heaven.af.mil to dude@tommy.gov
+ | From: joe@heaven.af.mil
+ | To: dude@tommy.gov
+ |
+ | Hi.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, dude@tommy.gov.
+ | Is tommy.gov in locals? No.
+ | Is dude@tommy.gov in virtualdomains? No.
+ | Is tommy.gov in virtualdomains? Yes: "tommy.gov:fred".
+ | Deliver locally to fred-dude@tommy.gov.
+ V
+
+qmail-lspawn ./Mailbox
+
+ | Look at mailbox name, fred-dude.
+ | Is fred-dude listed in qmail-users? No.
+ | Is there a fred-dude account? No.
+ | Is there a fred account? Yes.
+ | Is fred's uid nonzero? Yes.
+ | Is ~fred visible to the qmailp user? Yes.
+ | Is ~fred owned by fred? Yes.
+ | Give control of the message to fred.
+ | Run qmail-local.
+ V
+
+qmail-local fred ~fred fred-dude - dude tommy.gov joe@heaven.af.mil ./Mailbox
+
+ Does ~fred/.qmail-dude exist? No.
+ Does ~fred/.qmail-default exist? Yes: "./Mail.tommy".
+ Write message to ./Mail.tommy in mbox format.
diff --git a/doc/Qmail/PIC.nullclient b/doc/Qmail/PIC.nullclient
new file mode 100644
index 0000000..a90d7cb
--- /dev/null
+++ b/doc/Qmail/PIC.nullclient
@@ -0,0 +1,38 @@
+ Original message:
+
+ To: bill@irs.gov
+ Hi.
+
+qmail-inject Fill in the complete envelope and header:
+
+ | (envelope) from joe@heaven.af.mil to bill@irs.gov
+ | From: joe@heaven.af.mil
+ | To: bill@irs.gov
+ |
+ | Hi.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, bill@irs.gov.
+ | Is irs.gov in locals? No.
+ | Is bill@irs.gov in virtualdomains? No.
+ | Is irs.gov in virtualdomains? No.
+ | Is .gov in virtualdomains? No.
+ | Deliver remotely to bill@irs.gov.
+ V
+
+qmail-rspawn Run qmail-remote.
+
+ |
+ V
+
+qmail-remote Look at host name, irs.gov.
+ Is irs.gov listed in smtproutes? Yes: ":bigbang.af.mil".
+ Look up DNS A for bigbang.af.mil and connect by SMTP:
+
+ MAIL FROM:<joe@heaven.af.mil>
+ RCPT TO:<bill@irs.gov>
diff --git a/doc/Qmail/PIC.relaybad b/doc/Qmail/PIC.relaybad
new file mode 100644
index 0000000..513f74f
--- /dev/null
+++ b/doc/Qmail/PIC.relaybad
@@ -0,0 +1,8 @@
+qmail-smtpd Receive message by SMTP from another host:
+
+ MAIL FROM:<spammer@aol.com>
+ RCPT TO:<bill@irs.gov>
+
+ Is $RELAYCLIENT set? No.
+ Is irs.gov in rcpthosts? No.
+ Reject RCPT.
diff --git a/doc/Qmail/PIC.relaygood b/doc/Qmail/PIC.relaygood
new file mode 100644
index 0000000..0d62fa9
--- /dev/null
+++ b/doc/Qmail/PIC.relaygood
@@ -0,0 +1,33 @@
+qmail-smtpd Receive message by SMTP from another host:
+
+ | MAIL FROM:<joe@heaven.af.mil>
+ | RCPT TO:<bill@irs.gov>
+ |
+ | Is $RELAYCLIENT set? Yes: "".
+ | Accept RCPT.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, bill@irs.gov.
+ | Is irs.gov in locals? No.
+ | Is bill@irs.gov in virtualdomains? No.
+ | Is irs.gov in virtualdomains? No.
+ | Is .gov in virtualdomains? No.
+ | Deliver remotely to bill@irs.gov.
+ V
+
+qmail-rspawn Run qmail-remote.
+
+ |
+ V
+
+qmail-remote Look at host name, irs.gov.
+ Is irs.gov listed in smtproutes? No.
+ Look up DNS MX/A for irs.gov and connect to it by SMTP:
+
+ MAIL FROM:<joe@heaven.af.mil>
+ RCPT TO:<bill@irs.gov>
diff --git a/doc/Qmail/PIC.rem2local b/doc/Qmail/PIC.rem2local
new file mode 100644
index 0000000..62fe61a
--- /dev/null
+++ b/doc/Qmail/PIC.rem2local
@@ -0,0 +1,36 @@
+qmail-smtpd Receive message by SMTP from another host:
+
+ | MAIL FROM:<bill@irs.gov>
+ | RCPT TO:<joe@heaven.af.mil>
+ |
+ | Is $RELAYCLIENT set? No.
+ | Is heaven.af.mil in rcpthosts? Yes.
+ | Accept RCPT.
+ V
+
+qmail-queue Store message safely on disk.
+ Trigger qmail-send.
+ |
+ V
+
+qmail-send Look at envelope recipient, joe@heaven.af.mil.
+ | Is heaven.af.mil in locals? Yes.
+ | Deliver locally to joe@heaven.af.mil.
+ V
+
+qmail-lspawn ./Mailbox
+
+ | Look at mailbox name, joe.
+ | Is joe listed in qmail-users? No.
+ | Is there a joe account? Yes.
+ | Is joe's uid nonzero? Yes.
+ | Is ~joe visible to the qmailp user? Yes.
+ | Is ~joe owned by joe? Yes.
+ | Give control of the message to joe.
+ | Run qmail-local.
+ V
+
+qmail-local joe ~joe joe '' '' heaven.af.mil bill@irs.gov ./Mailbox
+
+ Does ~joe/.qmail exist? No.
+ Write message to ./Mailbox in mbox format.
diff --git a/doc/Qmail/README b/doc/Qmail/README
new file mode 100644
index 0000000..5208eaf
--- /dev/null
+++ b/doc/Qmail/README
@@ -0,0 +1,269 @@
+qmail 1.03
+19980615
+Copyright 1998
+D. J. Bernstein, qmail@pobox.com
+
+qmail is a secure, reliable, efficient, simple message transfer agent.
+It is meant as a replacement for the entire sendmail-binmail system on
+typical Internet-connected UNIX hosts. See BLURB, BLURB2, BLURB3, and
+BLURB4 for more detailed advertisements.
+
+INSTALL says how to set up and test qmail. If you're upgrading from a
+previous version, read UPGRADE instead.
+
+See PIC.* for some ``end-to-end'' pictures of mail flowing through the
+qmail system.
+
+See http://pobox.com/~djb/qmail.html for other qmail-related software
+and a pointer to the qmail mailing list.
+
+Other documentation: http://pobox.com/~djb/proto.html shows solutions to
+several Internet mail problems; many of these solutions are implemented
+in qmail. CHANGES and THANKS show how qmail has changed since it was
+first released. SECURITY, INTERNALS, THOUGHTS, and TODO record many of
+the qmail design decisions.
+
+The rest of this file is a list of systypes where various versions of
+qmail have been reported to work. 0.96 was the final gamma version; 1.00
+had exactly the same code as 0.96. To see your systype, make systype;
+cat systype.
+
+1.00: a.ux-3.0-svr2-:-:-:mc68030-:- (tnx RF)
+1.01: aix-3-2-:-:-:000000406300-:- (tnx DG)
+1.01: aix-3-2-:-:-:000011216700-:- (tnx JLB)
+1.01: aix-4-1-:-:-:000041574c00-:- (tnx M2H)
+1.01: aix-4-1-:-:-:000088581000-:- (tnx HJB)
+1.01: aix-4-1-:-:-:002b51134c00-:- (tnx MP)
+1.00: aix-4-1-:-:-:00910033a000-:- (tnx KJJ)
+1.01: aix-4-2-:-:-:000055247900-:- (tnx JLB)
+1.01: aix-4-2-:-:-:000062295800-:- (tnx TD)
+1.01: aix-4-2-:-:-:000136094c00-:- (tnx T2U)
+1.00: aix-4-2-:-:-:000205254600-:- (tnx MGM)
+1.01: aix-4-2-:-:-:005255bc4c00-:- (tnx DS)
+1.01: aix-4-2-:-:-:006030944c00-:-
+1.01: bsd.386-1.1-0-:i386-:-:i386-:- (tnx T2M)
+1.01: bsd.os-2.0-:i386-:-:pentium-:- (tnx MSS)
+1.01: bsd.os-2.0.1-:i386-:-:i486-:- (tnx KR)
+0.96: bsd.os-2.1-:i386-:-:-:- (tnx DAR)
+1.00: bsd.os-2.1-:i386-:-:i486-:- (tnx RJC)
+0.96: bsd.os-2.1-:i386-:-:pentium-:- (tnx UO)
+1.01: bsd.os-3.0-:i386-:-:-:- (tnx VU)
+1.01: bsd.os-3.0-:i386-:-:pentium-:- (tnx RJO)
+1.01: bsd.os-3.1-:i386-:-:pentium-:- (tnx ABC)
+1.01: bsd.os-3.1-:i386-:-:pentium.ii-:- (tnx UO)
+0.96: dgux-5.4r2.01-generic-:-:-:aviion-:- (tnx HWM)
+1.01: freebsd-2.1.0-release-:i386-:-:i486-dx-:- (tnx VV)
+1.01: freebsd-2.1.0-release-:i386-:-:i486.dx2-:- (tnx JLB)
+1.00: freebsd-2.1.0-release-:i386-:-:i486dx-:- (tnx chrisj=???)
+1.01: freebsd-2.1.0-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx MBS)
+1.01: freebsd-2.1.5-release-:i386-:-:i486-dx-:- (tnx B1F)
+0.96: freebsd-2.1.5-release-:i386-:-:i486dx-:- (tnx FN)
+1.01: freebsd-2.1.5-release-:i386-:-:unknown.-:- (tnx BMF)
+1.00: freebsd-2.1.6-release-:i386-:-:-:- (tnx TM)
+0.96: freebsd-2.1.6-release-:i386-:-:Pentium-Pro.150-:- (tnx CH)
+1.01: freebsd-2.1.6-release-:i386-:-:cy486dlc-:- (tnx M3H)
+0.96: freebsd-2.1.6.1-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx MF)
+1.01: freebsd-2.1.7-release-:i386-:-:i486-dx-:- (tnx AAF)
+1.00: freebsd-2.1.7-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx JBB)
+1.01: freebsd-2.1.7-release-:i386-:-:pentium.815\100-:- (tnx B1F)
+1.01: freebsd-2.2-970422-releng-:i386-:-:-:- (tnx TM)
+1.00: freebsd-2.2-release-:i386-:-:-:- (tnx MT)
+1.01: freebsd-2.2-stable-:i386-:-:cyrix.5x86-:- (tnx A2B)
+1.01: freebsd-2.2-stable-:i386-:-:pentium-:- (tnx gary@systemics=???)
+1.01: freebsd-2.2.1-release-:i386-:-:-:- (tnx M2R)
+1.01: freebsd-2.2.1-release-:i386-:-:i486-dx-:- (tnx PGR)
+1.00: freebsd-2.2.1-release-:i386-:-:i486.dx2-:- (tnx BR)
+1.01: freebsd-2.2.1-release-:i386-:-:pentium-:- (tnx REB)
+1.01: freebsd-2.2.1-release-:i386-:-:pentium.pro-:- (tnx JS)
+1.01: freebsd-2.2.2-release-:i386-:-:amd.am5x86.write-through-:- (tnx AGB)
+1.01: freebsd-2.2.2-release-:i386-:-:i486-dx-:- (tnx A2L)
+1.01: freebsd-2.2.2-release-:i386-:-:i486.dx2-:- (tnx D3S)
+1.01: freebsd-2.2.2-release-:i386-:-:pentium-:- (tnx B2F)
+1.01: freebsd-2.2.2-release-:i386-:-:pentium.pro-:- (tnx M2G)
+1.01: freebsd-2.2.5-release-:i386-:-:i486-dx-:- (tnx R2N)
+1.01: freebsd-2.2.5-release-:i386-:-:i486.dx2-:- (tnx AY)
+1.01: freebsd-2.2.5-release-:i386-:-:pentium.pro-:- (tnx AI)
+1.01: freebsd-2.2.5-stable-:i386-:-:i486.dx2-:- (tnx JK)
+1.01: freebsd-2.2.5-stable-:i386-:-:pentium-:- (tnx root@defiant=???)
+1.01: freebsd-2.2.6-release-:i386-:-:-:- (tnx TM)
+1.01: freebsd-2.2.6-release-:i386-:-:amd.am5x86.write-through-:- (tnx root@skully=???)
+1.00: freebsd-3.0-970209-snap-:i386-:-:-:- (tnx YF)
+1.01: freebsd-3.0-970428-snap-:i386-:-:pentium-:- (tnx M3S)
+1.01: freebsd-3.0-970807-snap-:i386-:-:amd.k6-:- (tnx KMD)
+1.01: freebsd-3.0-980309-snap-:i386-:-:pentium-:- (tnx MM)
+1.01: freebsd-3.0-current-:i386-:-:pentium-:- (tnx KB)
+1.01: hp-ux-a.09.05-a-:-:-:9000.712-:- (tnx SV)
+1.01: hp-ux-a.09.07-a-:-:-:9000.712-:- (tnx LB)
+1.00: hp-ux-b.09.00-a-:-:-:9000.360-:- (tnx VV)
+1.01: hp-ux-b.10.20-a-:-:-:9000.755-:- (tnx BCK)
+1.01: irix-5.3-11091812-:-:-:ip22-:- (tnx JL)
+1.01: irix-6.2-03131015-:-:-:ip22-:- (tnx DS)
+1.01: irix64-6.2-03131016-:-:-:ip19-:- (tnx AH)
+1.01: irix64-6.2-06101031-:-:-:ip28-:- (tnx DB)
+1.01: linux-1.2.13-:i386-:-:i486-:- (tnx RF)
+1.01: linux-1.2.13-:i386-:-:pentium-:- (tnx MEE)
+1.01: linux-1.99.4-:i386-:-:pentium-:- (tnx C2H)
+1.01: linux-2.0.0-:i386-:-:i486-:- (tnx kragen@gentle=???)
+1.01: linux-2.0.0-:i386-:-:pentium-:- (tnx MJD)
+1.01: linux-2.0.6-:i386-:-:pentium-:-
+1.00: linux-2.0.6-:i386-:-:ppro-:- (tnx MR)
+1.01: linux-2.0.7-:i386-:-:i486-:- (tnx TLM)
+1.01: linux-2.0.9-:i386-:-:i486-:- (tnx VBM)
+0.96: linux-2.0.13-:i386-:-:pentium-:- (tnx BW)
+1.01: linux-2.0.15-:i386-:-:i486-:- (tnx JCD)
+1.01: linux-2.0.18-:i386-:-:i486-:- (tnx tk@avalon=???)
+1.01: linux-2.0.18-:i386-:-:pentium-:- (tnx root@webtvchat=???)
+1.00: linux-2.0.22-:i386-:-:pentium-:- (tnx MDI)
+1.00: linux-2.0.23-:i386-:-:i486-:- (tnx B2L)
+1.01: linux-2.0.24-:i386-:-:i486-:- (tnx GLM)
+1.00: linux-2.0.24-:i386-:-:pentium-:- (tnx VV)
+0.96: linux-2.0.25-:i386-:-:i486-:- (tnx BDB)
+1.01: linux-2.0.25-:i386-:-:pentium-:- (tnx KA)
+0.93: linux-2.0.26-:i386-:-:i486-:- (tnx blynch@texas=???)
+1.01: linux-2.0.26-:i386-:-:pentium-:- (tnx robbie@opus=???)
+1.00: linux-2.0.27-:-:-:sparc-:- (tnx SVD)
+1.00: linux-2.0.27-:i386-:-:i386-:- (tnx ECG)
+1.01: linux-2.0.27-:i386-:-:i486-:- (tnx BN)
+1.01: linux-2.0.27-:i386-:-:pentium-:- (tnx EK)
+1.01: linux-2.0.27-:i386-:-:ppro-:- (tnx L3L)
+1.01: linux-2.0.28-:i386-:-:i486-:- (tnx AAF)
+1.00: linux-2.0.28-:i386-:-:pentium-:- (tnx root@duggy=???)
+1.01: linux-2.0.28-:i386-:-:ppro-:- (tnx S3T)
+1.01: linux-2.0.28-osfmach3-:-:-:ppc-:- (tnx CG)
+1.01: linux-2.0.29-:alpha-:-:alpha-:- (tnx MB)
+1.01: linux-2.0.29-:i386-:-:i386-:- (tnx AJK)
+1.01: linux-2.0.29-:i386-:-:i486-:- (tnx FPL)
+1.01: linux-2.0.29-:i386-:-:pentium-:- (tnx FW)
+1.00: linux-2.0.29-:i386-:-:ppro-:- (tnx MMM)
+1.01: linux-2.0.30-:-:-:sparc-:- (tnx J2P)
+1.01: linux-2.0.30-:alpha-:-:alpha-:- (tnx WS)
+1.01: linux-2.0.30-:i386-:-:i386-:- (tnx OK)
+1.00: linux-2.0.30-:i386-:-:i486-:- (tnx KUT)
+1.01: linux-2.0.30-:i386-:-:i486-:- (tnx PK)
+1.01: linux-2.0.30-:i386-:-:pentium-:- (tnx AV)
+1.00: linux-2.0.30-:i386-:-:ppro-:- (tnx root@gate=???)
+1.01: linux-2.0.30-osfmach3-:-:-:ppc-:- (tnx PTW)
+1.01: linux-2.0.30u11-:i386-:-:pentium-:- (tnx JTB)
+1.01: linux-2.0.31-:i386-:-:i486-:- (tnx SAE)
+1.01: linux-2.0.31-:i386-:-:pentium-:- (tnx B3W)
+1.01: linux-2.0.31-:i386-:-:ppro-:- (tnx JAK)
+1.01: linux-2.0.32-:-:-:ie86-:- (tnx root@vmlinuz=???)
+1.01: linux-2.0.32-:alpha-:-:alpha-:- (tnx NR)
+1.01: linux-2.0.32-:i386-:-:i486-:- (tnx SC)
+1.01: linux-2.0.32-:i386-:-:pentium-:- (tnx HT)
+1.01: linux-2.0.32-:i386-:-:ppro-:- (tnx RK)
+1.01: linux-2.0.33-:i386-:-:i486-:- (tnx RAB)
+1.01: linux-2.0.33-:i386-:-:pentium-:- (tnx AF)
+1.01: linux-2.0.33-:i386-:-:ppro-:- (tnx B2W)
+1.01: linux-2.1.9-:i386-:-:i486-:- (tnx SJB)
+1.01: linux-2.1.10-:i386-:-:i486-:- (tnx JB)
+0.96: linux-2.1.13-:i386-:-:i486-:- (tnx ML)
+0.96: linux-2.1.14-:i386-:-:pentium-:- (tnx SCW)
+0.96: linux-2.1.23-:i386-:-:pentium-:- (tnx JF)
+1.01: linux-2.1.24-:-:-:ppc-:- (tnx meta=???)
+0.96: linux-2.1.25-:i386-:-:i486-:- (tnx JBF)
+0.96: linux-2.1.25-:i386-:-:pentium-:- (tnx UO)
+1.00: linux-2.1.26-:i386-:-:i486-:- (tnx DK)
+1.00: linux-2.1.27-:i386-:-:pentium-:- (tnx JF)
+1.01: linux-2.1.28-:i386-:-:i486-:- (tnx HDG)
+1.00: linux-2.1.28-:i386-:-:pentium-:- (tnx RGS)
+1.00: linux-2.1.29-:i386-:-:i486-:- (tnx SJW)
+1.01: linux-2.1.35-:i386-:-:pentium-:- (tnx JF)
+1.01: linux-2.1.36-:i386-:-:i486-:- (tnx ML)
+1.01: linux-2.1.42-:i386-:-:i486-:- (tnx wtanaka=???)
+1.01: linux-2.1.46-:i386-:-:pentium-:- (tnx VR)
+1.01: linux-2.1.51-:i386-:-:pentium-:- (tnx KO)
+1.01: linux-2.1.61-:i386-:-:i486-:- (tnx RO)
+1.01: linux-2.1.65-:i386-:-:i486-:- (tnx F2T)
+1.01: linux-2.1.71-:i386-:-:ppro-:- (tnx MJG)
+1.01: linux-2.1.78-:i386-:-:pentium-:- (tnx AS)
+1.01: linux-2.1.82-:i386-:-:pentium-:- (tnx AY)
+1.01: linux-2.1.85-:i386-:-:pentium-:- (tnx PJH)
+1.00: machten-4-0.4-:-:-:powerpc-:- (tnx RAM)
+1.01: netbsd-1.1-:i386-:-:pentium.(genuineintel.586-class.cpu)-:- (tnx GL)
+1.01: netbsd-1.2-:hp300-:-:-:- (tnx ML)
+1.01: netbsd-1.2-:i386-:-:i486dx.(genuineintel.486-class.cpu)-:- (tnx T2K)
+0.96: netbsd-1.2-:i386-:-:pentium.(genuineintel.586-class.cpu)-:- (tnx GH)
+1.01: netbsd-1.2.1-:mac68k-:-:apple.macintosh.se/30..(68030)-:- (tnx HM)
+1.01: netbsd-1.2.1-:sparc-:-:fmi,mb86904.@.110.mhz,.on-chip.fpu-:- (tnx ZU)
+0.96: netbsd-1.2c-:pmax-:-:-:- (tnx JLW)
+1.01: netbsd-1.3-:hp300-:-:hp.9000/433.(33mhz.mc68040.cpu+mmu+fpu,.4k.on-chip.physical.i/d.caches)-:- (tnx TB)
+1.01: netbsd-1.3.1-:sun3-:-:sun.3/60-:- (tnx MBS)
+1.01: netbsd-1.3_alpha-:i386-:-:intel.pentium.(p54c).(586-class)-:- (tnx GL)
+1.01: nextstep-3.1-:mc680x0-:-:68040-:- (tnx JRY)
+1.01: nextstep-3.3-:hppa-:-:7100lc-:-
+1.01: nextstep-3.3-:i386-:-:pentium-:- (tnx HM)
+1.01: nextstep-3.3-:mc680x0-:-:68040-:- (tnx WEB)
+1.01: nextstep-4.1-:mc680x0-:-:68040-:- (tnx FN)
+1.00: openbsd-2.0-hoth#0-:openbsd.i386-:-:i386-:- (tnx MBS)
+1.00: openbsd-2.0-mr_potatoe_head#2-:openbsd.i386-:-:i386-:- (tnx JJMK)
+0.96: openbsd-2.0-puma#1-:openbsd.m68k-:-:mac68k-:- (tnx AKB)
+1.01: openbsd-2.1-asgard#1-:openbsd.i386-:-:i386-:- (tnx ETT)
+1.01: openbsd-2.1-generic#71-:openbsd.sparc-:-:sparc-:- (tnx MMM2)
+1.01: openbsd-2.1-katana#2-:openbsd.i386-:-:i386-:- (tnx CHR)
+1.01: openbsd-2.1-puma#0-:openbsd.m68k-:-:mac68k-:- (tnx AKB)
+1.01: openbsd-2.2-ele#2-:openbsd.i386-:-:i386-:- (tnx RC)
+1.01: openbsd-2.2-generic#424-:openbsd.i386-:-:i386-:- (tnx ETT)
+1.01: osf1-v2.0-240-:-:-:alpha-:- (tnx JF)
+1.00: osf1-v3.2-148-:-:-:alpha-:- (tnx DL)
+1.01: osf1-v3.2-148-:-:-:alpha-:- (tnx RSK)
+1.01: osf1-v3.2-41-:-:-:alpha-:- (tnx MSD)
+1.01: osf1-v3.2-mp-4.2-:-:-:alpha-:- (tnx MSD)
+1.01: osf1-v4.0-386-:-:-:alpha-:- (tnx TEE)
+1.01: osf1-v4.0-464-:-:-:alpha-:- (tnx AWB)
+1.01: osf1-v4.0-564-:-:-:alpha-:- (tnx A2P)
+1.01: osf1-v4.0-564.32-:-:-:alpha-:- (tnx TLF)
+1.01: osf1-v4.0-878-:-:-:alpha-:- (tnx BJM)
+1.01: sco_sv-3.2-2-:-:-:i386-:- (tnx PW)
+1.01: sinix-l-5.41-d0005-:-:-:mx300i-:- (tnx IH)
+1.01: sunos-4.1.1-1-:mc68020-:sun3-:sun3-:sun3- (tnx JWB)
+1.01: sunos-4.1.1-1-:mc68020-:sun3-:sun3x-:sun3x- (tnx TT)
+1.01: sunos-4.1.3-jl-2-:sparc-:sun4-:sun4c-:sun4c- (tnx T2K)
+1.01: sunos-4.1.3_u1-1-:sparc-:sun4-:sun4c-:sun4c- (tnx MBS)
+1.01: sunos-4.1.3_u1-1-:sparc-:sun4-:sun4m-:sun4m- (tnx RSK)
+1.01: sunos-4.1.3_u1-10-:sparc-:sun4-:sun4m-:sun4m- (tnx aoki=???)
+1.00: sunos-4.1.3_u1-4-:unknown-:sun4-:sun4m-:sun4m- (tnx J2B)
+1.01: sunos-4.1.3_u1-6-:sparc-:sun4-:sun4m-:sun4m- (tnx RD)
+1.01: sunos-4.1.4-1-:unknown-:sun4-:sun4m-:sun4m- (tnx M3S)
+1.01: sunos-4.1.4-2-:sparc-:sun4-:sun4m-:sun4m-
+1.01: sunos-5.3-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx JDJ)
+1.01: sunos-5.4-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx jimo=???)
+0.96: sunos-5.4-generic_101945-10-:sparc-:sun4-:sun4m-:sun4m- (tnx W2K)
+1.00: sunos-5.4-generic_101945-34-:sparc-:sun4-:sun4m-:sun4m- (tnx ACB)
+0.96: sunos-5.4-generic_101946-35-:i386-:i86pc-:i86pc-:i86pc- (tnx CK)
+1.01: sunos-5.5-generic-:i386-:i86pc-:i86pc-:i86pc- (tnx seong=???)
+1.01: sunos-5.5-generic-:sparc-:sun4-:sun4c-:sun4c- (tnx SPM)
+1.01: sunos-5.5-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx RDM)
+1.01: sunos-5.5-generic-:sparc-:sun4-:sun4u-:sun4u- (tnx YC)
+1.01: sunos-5.5-generic_103093-02-:sparc-:sun4-:sun4m-:sun4m- (tnx RF)
+0.96: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4m-:sun4m- (tnx RDM)
+1.01: sunos-5.5-generic_103093-06-:sparc-:sun4-:sun4m-:sun4m- (tnx ERH)
+1.01: sunos-5.5-generic_103093-10-:sparc-:sun4-:sun4d-:sun4d- (tnx KT)
+1.01: sunos-5.5-generic_103094-05-:i386-:i86pc-:i86pc-:i86pc- (tnx M2G)
+1.01: sunos-5.5.1-generic-:i386-:i86pc-:i86pc-:i86pc- (tnx cro=???)
+1.01: sunos-5.5.1-generic-:sparc-:sun4-:sun4c-:sun4c- (tnx CG)
+1.01: sunos-5.5.1-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx MBS)
+1.01: sunos-5.5.1-generic-:sparc-:sun4-:sun4u-:sun4u-
+0.96: sunos-5.5.1-generic_103640-02-:sparc-:sun4-:sun4m-:sun4m- (tnx SGC)
+1.00: sunos-5.5.1-generic_103640-03-:sparc-:sun4-:sun4u-:sun4u- (tnx EG)
+1.00: sunos-5.5.1-generic_103640-05-:sparc-:sun4-:sun4m-:sun4m- (tnx L2L)
+1.01: sunos-5.5.1-generic_103640-05-:sparc-:sun4-:sun4u-:sun4u- (tnx KY)
+1.01: sunos-5.5.1-generic_103640-06-:sparc-:sun4-:sun4u-:sun4u- (tnx RA)
+1.01: sunos-5.5.1-generic_103640-08-:sparc-:sun4-:sun4c-:sun4c- (tnx RA)
+1.01: sunos-5.5.1-generic_103640-08-:sparc-:sun4-:sun4d-:sun4d- (tnx MS)
+1.01: sunos-5.5.1-generic_103640-08-:sparc-:sun4-:sun4m-:sun4m- (tnx S2P)
+1.01: sunos-5.5.1-generic_103640-08-:sparc-:sun4-:sun4u-:sun4u- (tnx CM)
+1.01: sunos-5.5.1-generic_103640-12-:sparc-:sun4-:sun4m-:sun4m- (tnx IK)
+1.01: sunos-5.5.1-generic_103640-18-:sparc-:sun4-:sun4u-:sun4u- (tnx PMH)
+1.01: sunos-5.5.1-generic_103641-08-:i386-:i86pc-:i86pc-:i86pc- (tnx TL)
+1.01: sunos-5.5.1-generic_103641-12-:i386-:i86pc-:i86pc-:i86pc- (tnx JS)
+1.01: sunos-5.5.1-generic_105428-01-:sparc-:sun4-:sun4u-:sun4u- (tnx BCM)
+0.96: sunos-5.5.1-generic_patch-:i386-:i86pc-:i86pc-:i86pc- (tnx D2K)
+1.01: sunos-5.6-generic-:sparc-:sun4-:sun4c-:sun4c- (tnx DS)
+1.01: sunos-5.6-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx BDM)
+1.01: sunos-5.6-generic-:sparc-:sun4-:sun4u-:sun4u- (tnx RPS)
+1.01: sunos-5.6-generic_105182-01-:i386-:i86pc-:i86pc-:i86pc- (tnx JFK)
+1.01: sunos-5.6-generic_105182-04-:i386-:i86pc-:i86pc-:i86pc- (tnx YC)
+0.96: ultrix-4.3-1-:pmax-:-:risc-:- (tnx YF)
+1.01: ultrix-4.4-0-:-:-:risc-:- (tnx RSK)
+1.01: unix_sv-4.2mp-2.1.2-:i386-:-:i386-:- (tnx J2W)
diff --git a/doc/Qmail/REMOVE.binmail b/doc/Qmail/REMOVE.binmail
new file mode 100644
index 0000000..9532ac9
--- /dev/null
+++ b/doc/Qmail/REMOVE.binmail
@@ -0,0 +1,16 @@
+Here's how to remove binmail from your system. Don't do this if you have
+configured qmail to use binmail for local delivery.
+
+
+1. Find the binmail binary on your system: /usr/libexec/mail.local if
+ that exists, otherwise /bin/mail.
+
+2. Remove permissions from the binmail binary:
+ # chmod 0 /usr/libexec/mail.local
+
+3. If the binmail binary was /bin/mail, make sure that ``mail'' still
+ invokes a usable mailer. Under SVR4 you may want to link mail to
+ mailx.
+
+4. Comment out the comsat line in /etc/inetd.conf, and kill -HUP your
+ inetd.
diff --git a/doc/Qmail/REMOVE.sendmail b/doc/Qmail/REMOVE.sendmail
new file mode 100644
index 0000000..5be6e78
--- /dev/null
+++ b/doc/Qmail/REMOVE.sendmail
@@ -0,0 +1,28 @@
+Here's how to remove sendmail from your system.
+
+1. Find sendmail in your boot scripts. It's usually in either /etc/rc or
+ /etc/init.d/sendmail. It looks like
+ sendmail -bd -q15m
+ -q15m means that it should run the queue every 15 minutes; you may
+ see a different number. Comment out this line.
+
+2. Kill the sendmail daemon. You should first kill -STOP the daemon; if
+ any children are running, you should kill -CONT, wait, kill -STOP
+ again, and repeat ad nauseam. If there aren't any children, kill
+ -TERM and then kill -CONT.
+
+3. Check whether you have any messages in the sendmail queue,
+ /var/spool/mqueue. If you do, you will have to try flushing them with
+ sendmail.bak -q. If necessary, wait a while and run sendmail.bak -q
+ again. Repeat until the queue is empty. This may take several days.
+
+4. Remove the setuid bit on the sendmail binary, to prevent local users
+ from gaining extra privileges through sendmail's security holes. The
+ binary may be at several different locations:
+ # chmod 0 /usr/lib/sendmail
+ # chmod 0 /usr/sbin/sendmail
+ # chmod 0 /usr/lib/sendmail.mx
+
+5. Move the sendmail binary out of the way:
+ # mv /usr/lib/sendmail /usr/lib/sendmail.bak
+ # mv /usr/sbin/sendmail /usr/sbin/sendmail.bak
diff --git a/doc/Qmail/SYSDEPS b/doc/Qmail/SYSDEPS
new file mode 100644
index 0000000..0bb01ec
--- /dev/null
+++ b/doc/Qmail/SYSDEPS
@@ -0,0 +1,17 @@
+VERSION
+systype
+hasshsgr.h
+hasnpbg1.h
+select.h
+hasflock.h
+hassalen.h
+fork.h
+hassgact.h
+direntry.h
+hassgprm.h
+haswaitp.h
+hasmkffo.h
+uint32.h
+dns.lib
+socket.lib
+syslog.lib
diff --git a/doc/Qmail/TEST.deliver b/doc/Qmail/TEST.deliver
new file mode 100644
index 0000000..4fc4c32
--- /dev/null
+++ b/doc/Qmail/TEST.deliver
@@ -0,0 +1,82 @@
+You can do several tests of qmail delivery without setting up qmail to
+accept messages through SMTP or through /usr/lib/sendmail:
+
+1. After you start qmail, look for a
+ qmail: status: local 0/10 remote 0/20
+ line in syslog. qmail-send always prints either ``cannot start'' or
+ ``status''. (The big number is a splogger timestamp.)
+
+2. Do a ps and look for the qmail daemons. There should be four of
+ them, all idle: qmail-send, running as qmails; qmail-lspawn, running
+ as root; qmail-rspawn, running as qmailr; and qmail-clean, running
+ as qmailq. You will also see splogger, running as qmaill.
+
+3. Local-local test: Send yourself an empty message. (Replace ``me''
+ with your username. Make sure to include the ``to:'' colon.)
+ % echo to: me | /var/qmail/bin/qmail-inject
+ The message will show up immediately in your mailbox, and syslog
+ will show something like this:
+ qmail: new msg 53
+ qmail: info msg 53: bytes 246 from <me@domain> qp 20345 uid 666
+ qmail: starting delivery 1: msg 53 to local me@domain
+ qmail: status: local 1/10 remote 0/20
+ qmail: delivery 1: success: did_1+0+0/
+ qmail: status: local 0/10 remote 0/20
+ qmail: end msg 53
+ (53 is an inode number; 20345 is a process ID; your numbers will
+ probably be different.)
+
+4. Local-error test: Send a message to a nonexistent local address.
+ % echo to: nonexistent | /var/qmail/bin/qmail-inject
+ qmail: new msg 53
+ qmail: info msg 53: bytes 246 from <me@domain> qp 20351 uid 666
+ qmail: starting delivery 2: msg 53 to local nonexistent@domain
+ qmail: status: local 1/10 remote 0/20
+ qmail: delivery 2: failure: No_such_address.__#5.1.1_/
+ qmail: status: local 0/10 remote 0/20
+ qmail: bounce msg 53 qp 20357
+ qmail: end msg 53
+ qmail: new msg 54
+ qmail: info msg 54: bytes 743 from <> qp 20357 uid 666
+ qmail: starting delivery 3: msg 54 to local me@domain
+ qmail: status: local 1/10 remote 0/20
+ qmail: delivery 3: success: did_1+0+0/
+ qmail: status: local 0/10 remote 0/20
+ qmail: end msg 54
+ You will now have a bounce message in your mailbox.
+
+5. Local-remote test: Send an empty message to your account on another
+ machine.
+ % echo to: me@wherever | /var/qmail/bin/qmail-inject
+ qmail: new msg 53
+ qmail: info msg 53: bytes 246 from <me@domain> qp 20372 uid 666
+ qmail: starting delivery 4: msg 53 to remote me@wherever
+ qmail: status: local 0/10 remote 1/20
+ qmail: delivery 4: success: 1.2.3.4_accepted_message./...
+ qmail: status: local 0/10 remote 0/20
+ qmail: end msg 53
+ There will be a pause between ``starting delivery'' and ``success'';
+ SMTP is slow. Check that the message is in your mailbox on the other
+ machine.
+
+6. Local-postmaster test: Send mail to postmaster, any capitalization.
+ % echo to: POSTmaster | /var/qmail/bin/qmail-inject
+ Look for the message in the alias mailbox, normally ~alias/Mailbox.
+
+7. Double-bounce test: Send a message with a completely bad envelope.
+ % /var/qmail/bin/qmail-inject -f nonexistent
+ To: unknownuser
+ Subject: testing
+
+ This is a test. This is only a test.
+ %
+ (Use end-of-file, not dot, to end the message.) Look for the double
+ bounce in the alias mailbox.
+
+8. Group membership test:
+ % cat > ~me/.qmail-groups
+ |groups >> MYGROUPS; exit 0
+ % /var/qmail/bin/qmail-inject me-groups < /dev/null
+ % cat ~me/MYGROUPS
+ MYGROUPS will show your normal gid and nothing else. (Under Solaris,
+ make sure to use /usr/ucb/groups; /usr/bin/groups is broken.)
diff --git a/doc/Qmail/TEST.receive b/doc/Qmail/TEST.receive
new file mode 100644
index 0000000..7644845
--- /dev/null
+++ b/doc/Qmail/TEST.receive
@@ -0,0 +1,41 @@
+You can do several tests of messages entering the qmail system:
+
+1. SMTP server test: Forge some mail locally via SMTP. Replace ``me''
+ with your username and ``domain'' with your host's name.
+ % telnet 127.0.0.1 25
+ Trying 127.0.0.1...
+ Connected to 127.0.0.1.
+ Escape character is '^]'.
+ 220 domain ESMTP
+ helo dude
+ 250 domain
+ mail <me@domain>
+ 250 ok
+ rcpt <me@domain>
+ 250 ok
+ data
+ 354 go ahead
+ Subject: testing
+
+ This is a test.
+ .
+ 250 ok 812345679 qp 12345
+ quit
+ 221 domain
+ Connection closed by foreign host.
+ %
+ Look for the message in your mailbox. (Note for programmers: Most
+ SMTP servers need more text after MAIL and RCPT. See RFC 821.)
+
+2. Remote-local test: Send yourself some mail from another machine.
+ Look for the message in your mailbox.
+
+3. Remote-error test: Send some mail from another machine to
+ nonexistent@domain. Look for a bounce message in the remote mailbox.
+
+4. UA test: Try sending mail, first to a local account, then to a
+ remote account, with your normal user agent.
+
+5. Remote-postmaster test: Send mail from another machine to
+ PoStMaStEr@domain. Look for the message in the alias mailbox,
+ normally ~alias/Mailbox.
diff --git a/doc/Qmail/THANKS b/doc/Qmail/THANKS
new file mode 100644
index 0000000..b1ad88e
--- /dev/null
+++ b/doc/Qmail/THANKS
@@ -0,0 +1,337 @@
+Thanks to lots of people for success and failure reports, code, ideas,
+and documentation. See CHANGES for details of specific contributions.
+Sorry if I left anyone out.
+
+A2B = Are Bryne
+A2L = Ali Lomonaco
+A2P = Andrea Paolini
+AAF = Adam A. Frey
+AB = Alan Briggs
+ABC = Alan B. Clegg
+AC = Arne Coucheron
+ACB = Andy C. Brandt
+AF = Andreas Faerber
+AG = Armin Gruner
+AGB = Andre Grosse Bley
+AH = Amos Hayes
+AI = Akihiro Iijima
+AJ = Alan Jaffray
+AJK = Antti-Juhani Kaijanaho
+AKB = Allen K. Briggs
+AL = Andreas Lamprecht
+ALB = Allan L. Bazinet
+ANR = Adriano Nagelschmidt Rodrigues
+AP = Andrew Pam
+AS = Akos Szalkai
+AV = Alex Vostrikov
+AWB = Andy W. Barclay
+AY = Araki Yasuhiro
+B1F = Bo Fussing
+B2F = Brad Forschinger
+B2H = Buck Huppmann
+B2L = Brent Laminack
+B2W = Bil Wendling
+B3W = Boris Wedl
+BB = Bruce Bodger
+BC = Bob Collie
+BCK = Benjamin C. Kite
+BCM = Bill C. Miller
+BDB = Boris D. Beletsky
+BDM = Byron D. Miller
+BEO = Bruce E. O'Neel
+BET = Bennett E. Todd
+BG = Bert Gijsbers
+BH = Brad Howes
+BJ = Brian Jackson
+BJM = Barry J. Miller
+BL = Brian Litzinger
+BMF = Brian M. Fisk
+BN = Bill Nugent
+BP = Bruce Perens
+BR = Brian J. Reichert
+BS = Bjoern Stabell
+BT = Brad Templeton
+BTW = Brian T. Wightman
+BW = Bill Weinman
+BZ = Blaz Zupan
+C2F = Chuck Foster
+C2H = Christoph Heidermanns
+C2S = Craig Shrimpton
+CEJ = Colin Eric Johnson
+CF = C. Ferree
+CG = Chris Garrigues
+CH = Chael Hall
+CHR = Craig H. Rowland
+CK = Christoph Kaesling
+CL = Carsten Leonhardt
+CLS = Christopher L. Seawood
+CM = Charles Mattair
+CMP = Chase M. Phillips
+CR = Christian Riede
+CS = Cloyce Spradling
+CSH = Clayton S. Haapala
+D1H = Dieter Heidner
+D2H = Dan Hollis
+D2K = Dax Kelson
+D2S = Dan Senie
+D3S = Don Samek
+DA = Dave Arcuri
+DAR = Daniel A. Reish
+DB = David Buscher
+DBK = Douglas B. Kerry
+DC = Dan Cross
+DCC = Daniel C. Cotey
+DE = Daniel Egnor
+DEH = Daniel E. Harris
+DF = Dale Farnsworth
+DG = David Guntner
+DK = Dave Kopper
+DL = Daniel Lawrence
+DM = David Mazieres
+DML = David M. Lew
+DP = Dave Platt
+DS = Dave Sill
+DST = Daniel S. Thibadeau
+DWS = David Wayne Summers
+EC = Evan Champion
+ECG = Eric C. Garrison
+EG = Eivind Gjelseth
+EK = Eric Krohn
+EP = Emanuele Pucciarelli
+ERH = Eric R. Hankins
+ES = Eric Smith
+ESM = Edward S. Marshall
+ET = Eivind Tagseth
+ETT = Emmanuel T. Tardieu
+F2T = Frank Thieme
+FE = Frank Ederveen
+FN = Faried Nawaz
+FPL = Frederik P. Lindberg
+FT = Frank Tegtmeyer
+FW = Frank Wagner
+G1A = Graham Adams
+G2A = Greg Andrews
+GAW = Greg A. Woods
+GB = Glenn Barry
+GH = Gene Hightower
+GL = Giles Lean
+GLM = Grant L. Miller
+H2S = Harley Silver
+HCJ = Helio Coelho Jr.
+HDG = Hans de Graaff
+HG = Howard Goldstein
+HHO = Harald Hanche-Olsen
+HJB = Herbert J. Bernstein
+HM = Hirokazu Morikawa
+HS = Harlan Stenn
+HT = Henry Timmerman
+HW = Hal Wine
+HWM = Henry W. Miller
+IH = Ingmar Hupp
+IK = Ivan Kohler
+IKW = Ian Keith Wynne
+IS = Icarus Sparry
+IW = Ian Westcott
+J1B = John Banghart
+J1K = Jost Krieger
+J2B = Jos Backus
+J2K = Johannes Kroeger
+J2M = Joel Maslak
+J2P = John Parker
+J2W = Jim Whitby
+JAB = Jeremy A. Bussard
+JAK = Johan A. Kullstam
+JB = Joshua Buysse
+JBB = Jason B. Brown
+JBF = John B. Fleming
+JC = Jim Clausing
+JCD = Jeffrey C. Dege
+JD = Joe Doupnik
+JDHB = Johannes D. H. Beekhuizen
+JDJ = Joshua D. Juran
+JF = Janos Farkas
+JFK = James F. Kane III
+JGM = John G. Myers
+JJB = J. J. Bailey
+JJMK = Jonathan J. M. Katz
+JJR = Jaron J. Rubenstein
+JK = Jari Kirma
+JL = Jim Littlefield
+JLB = Julie L. Baumler
+JLH = Jason L. Haar
+JLW = Jason L. Wright
+JM = Jim Meehan
+JMS = Jason M. Stokes
+JMT = John M. Twilley
+JP = John Palkovic
+JPB = Joe Block
+JPH = Justin P. Hannah
+JPR = Jean-Pierre Radley
+JRL = John R. Levine
+JRM = Jason R. Mastaler
+JRY = Jamie R. Yukes
+JS = Jesper Skriver
+JTB = Jonathan T. Bowie
+JW = John Whittaker
+JWB = James W. Birdsall
+K1J = Kyle Jones
+K2J = Kevin Johnson
+KA = Klaus Aigte
+KB = Keith Burdis
+KE = Kenny Elliott
+KJJ = Kevin J. Johnson
+KJS = Kevin J. Sawyer
+KMD = Kevin M. Dulzo
+KO = Keith Owens
+KR = Kenji Rikitake
+KT = Karsten Thygesen
+KUT = Kai Uwe Tempel
+KY = Kentaro Yoshitomi
+L2L = Louis Larry
+L3L = Luis Lopes
+LB = Laurentiu Badea
+LL = lilo
+LW = Lionel Widdifield
+M2C = Mark Crimmins
+M2G = Michael R. Gile
+M2H = Martin Hager
+M2L = M. Lyons
+M2R = Mark Riekenberg
+M2S = Mikael Suokas
+M3H = Michael Holzt
+M3L = Michael Lazarou
+M3S = Morten Skjelland
+M4S = Michael Shields
+MB = Martin Budsj?
+MBS = Michael B. Scher
+MC = Michael Cooley
+MD = Mark Delany
+MDI = Miguel de Icaza
+ME = Marc Ewing
+MEE = Mads E. Eilertsen
+MF = Massimo Fusaro
+MG = Michael Graff
+MGM = Mitchell G. Morris
+MH = Markus Hofmann
+MJD = Mark-Jason Dominus
+MJG = Manuel J. Galan
+ML = Martin Lucina
+MLH = May Liss Haarstad
+MM = Martin Mersberger
+MMM = Momchil M. Momchev
+MMM2 = Marc M. Martinez
+MP = Matt Paduano
+MR = Mosfeq Rashid
+MRG = Matthew R. Green
+MS = Mark Spears
+MSD = Mandell S. Degerness
+MSS = Matthew S. Soffen
+MT = Mark Thompson
+MW = Mate Wierdl
+MWE = Mark W. Eichin
+NA = Norm Aleks
+NAA = Nicholas A. Amato
+NH = Nick Holloway
+NND = N. Dudorov
+NR = Norbert Roeding
+NW = Nicholas Waples
+OK = Oezguer Kesim
+OR = Ollivier Robert
+OS = Oliver Seiler
+PB = Peter Bowyer
+PCO = Peter C. Olsen
+PGF = Paul Fox
+PGR = Phil G. Rorex
+PH = Paul Harrington
+PJG = Paul Graham
+PJH = Peter J. Hunter
+PK = Petri Kaukasoina
+PMH = Peter M. Haworth
+PO = Paul Overell
+PS = Paul Svensson
+PT = Paul Taylor
+PTW = P. T. Withington
+PW = Peter Wilkinson
+R2N = Rivo Nurges
+RA = Russ Allbery
+RAB = Randolph Allen Bentson
+RAM = Robin A. McCollum
+RB = Robert Bridgham
+RC = Ryan Crum
+RD = Rahul Dhesi
+RDM = Raul D. Miller
+REB = Ronald E. Bickers
+RF = Rainer Fraedrich
+RFH = Robert F. Harrison
+RGS = Richard G. Sharman
+RJC = Robert J. Carter
+RJH = Randy Harmon
+RJO = Richard J. Ohnemus
+RK = Riho Kurg
+RL = Robert Luce
+RM = Rich McClellan
+RN = Russell Nelson
+RO = Roberto Oppedisano
+RPS = Russell P. Sutherland
+RS = Robert Sanders
+RSK = Robert S. Krzaczek
+S1R = Satish Ramachandran
+S2P = Stefan Puscasu
+S2R = Sean Reifschneider
+S2S = Scott Schwartz
+S2T = Steve Taylor
+S3T = Steffen Thorsen
+SA = Satoshi Adachi
+SAE = Stefaan A. Eeckels
+SAS = Steven A. Schrader
+SB = Stephane Bortzmeyer
+SC = Stefan Cars
+SCW = Steven C. Work
+SG = Steven Grimm
+SGC = Stephen G. Comings
+SJ = Sudish Joseph
+SJB = SJ Burns
+SJW = Stephen J. White
+SLB = Steven L. Baur
+SM = Shawn McHorse
+SP = Stephen Parker
+SPM = Salvatore P. Miccicke
+SS = Simon Shapiro
+SSB = Stik Bakken
+ST = Steve Tylock
+SV = Sven Velt
+SVD = Stef Van Dessel
+T2K = Tomoya Konishi
+T2M = Toni Mueller
+T2U = Todd Underwood
+TA = Tetsuo Aoki
+TB = Tobias Brox
+TD = Tom Demmer
+TEE = Thomas E. Erskine
+TG = Tim Goodwin
+TH = Ton Hospel
+TJH = Timothy J. Hunt
+TK = Terry Kennedy
+TL = Timothy Lorenc
+TLF = Timo L. Felbinger
+TLM = Timothy L. Mayo
+TM = Toshinori Maeno
+TN = Thomas Neumann
+TRR = Tracy R. Reed
+TT = Takaki Taniguchi
+TU = Tetsu Ushijima
+TV = Tommi Virtanen
+TVP = Tom van Peer
+UO = Uwe Ohse
+VBM = Vladimir B. Machulsky
+VR = Vincenzo Romano
+VU = Viriya Upatising
+VV = Vince Vielhaber
+W2K = Wolfram Kahl
+WEB = William E. Baxter
+WK = Werner Koch
+WS = Wilbur Sims
+WW = Wei Wu
+YC = Yuji Chikahiro
+YF = Yaroslav Faybishenko
+ZU = Zin Uda
diff --git a/doc/Qmail/THOUGHTS b/doc/Qmail/THOUGHTS
new file mode 100644
index 0000000..d6910da
--- /dev/null
+++ b/doc/Qmail/THOUGHTS
@@ -0,0 +1,418 @@
+Please note that this file is not called ``Internet Mail For Dummies.''
+It _records_ my thoughts on various issues. It does not _explain_ them.
+Paragraphs are not organized except by section. The required background
+varies wildly from one paragraph to the next.
+
+In this file, ``sendmail'' means Allman's creation; ``sendmail-clone''
+means the program in this package.
+
+
+1. Security
+
+There are lots of interesting remote denial-of-service attacks on any
+mail system. A long-term solution is to insist on prepayment for
+unauthorized resource use. The tricky technical problem is to make the
+prepayment enforcement mechanism cheaper than the expected cost of the
+attacks. (For local denial-of-service attacks it's enough to be able to
+figure out which user is responsible.)
+
+qmail-send's log was originally designed for profiling. It subsequently
+sprouted some tracing features. However, there's no way to verify
+securely that a particular message came from a particular local user;
+how do you know the recipient is telling you the truth about the
+contents of the message? With QUEUE_EXTRA it'd be possible to record a
+one-way hash of each outgoing message, but a user who wants to send
+``bad'' mail can avoid qmail entirely.
+
+I originally decided on security grounds not to put qmail advertisements
+into SMTP responses: advertisements often act as version identifiers.
+But this problem went away when I found a stable qmail URL.
+
+As qmail grows in popularity, the mere knowledge that rcpthosts is so
+easily available will deter people from setting up unauthorized MXs.
+(I've never seen an unauthorized MX, but I can imagine that it would be
+rather annoying.) Note that, unlike the bat book checkcompat() kludge,
+rcpthosts doesn't interfere with mailing lists.
+
+qmail-start doesn't bother with tty dissociation. On some old machines
+this means that random people can send tty signals to the qmail daemons.
+That's a security flaw in the job control subsystem, not in qmail.
+
+The resolver library isn't too bloated (before 4.9.4, at least), but it
+uses stdio, which _is_ bloated. Reading /etc/resolv.conf costs lots of
+memory in each qmail-remote process. So it's tempting to incorporate a
+smaller resolver library into qmail. (Bonus: I'd avoid system-specific
+problems with old resolvers.) The problem is that I'd then be writing a
+fundamentally insecure library. I'd no longer be able to blame the BIND
+authors and vendors for the fact that attackers can easily use DNS to
+steal mail. Solution: insist that the resolver run on the same host; the
+kernel can guarantee the security of low-numbered 127.0.0.1 UDP ports.
+
+NFS is the primary enemy of security partitioning under UNIX. Here's the
+story. Sun knew from the start that NFS was completely insecure. It
+tried to hide that fact by disallowing root access over NFS. Intruders
+nevertheless broke into system after system, first obtaining bin access
+and then obtaining root access. Various people thus decided to compound
+Sun's error and build a wall between root and all other users: if all
+system files are owned by root, and if there are no security holes other
+than NFS, someone who breaks in via NFS won't be able to wipe out the
+operating system---he'll merely be able to wipe out all user files. This
+clueless policy means that, for example, all the qmail users have to be
+replaced by root. See what I mean by ``enemy''? ... Basic NFS comments:
+Aside from the cryptographic problem of having hosts communicate
+securely, it's obvious that there's an administrative problem of mapping
+client uids to server uids. If a host is secure and under your control,
+you shouldn't have to map anything. If a host is under someone else's
+control, you'll want to map his uids to one local account; it's his
+client's job to decide which of his users get to talk NFS in the first
+place. Sun's original map---root to nobody, everyone else left alone---
+is, as far as I can tell, always wrong.
+
+
+2. Injecting mail locally (qmail-inject, sendmail-clone)
+
+RFC 822 section 3.4.9 prohibits certain visual effects in headers, and
+the 822bis draft prohibits even more. qmail-inject could enforce these
+absurd restrictions, but why waste the time? If you will suffer from
+someone sending you ``flash mail,'' go find a better mail reader.
+
+qmail-inject's ``Cc: recipient list not shown: ;'' successfully stops
+sendmail from adding Apparently-To. Unfortunately, old versions of
+sendmail will append a host name. This wasn't fixed until sendmail 8.7.
+How many years has it been since RFC 822 came out?
+
+sendmail discards duplicate addresses. This has probably resulted in
+more lost and stolen mail over the years than the entire Chicago branch
+of the United States Postal Service. The qmail system delivers messages
+exactly as it's told to do. Along the same lines: qmail-inject is both
+unable and unwilling to support anything like sendmail's (default)
+nometoo option. Of course, a list manager could support nometoo.
+
+There should be a mechanism in qmail-inject that does for envelope
+recipients what Return-Path does for the envelope sender. Then
+qmail-inject -n could print the recipients.
+
+Should qmail-inject bounce messages with no recipients? Should there be
+an option for this? If it stays as is (accept the message), qmail-inject
+could at least avoid invoking qmail-queue.
+
+It is possible to extract non-unique Message-IDs out of qmail-inject.
+Here's how: stop qmail-inject before it gets to the third line of
+main(), then wait until the pids wrap around, then restart qmail-inject
+and blast the message through, then start another qmail-inject with the
+same pid in the same second. I'm not sure how to fix this without
+system-supplied sequence numbers. (Of course, the user could just type
+in his own non-unique Message-IDs.)
+
+The bat book says: ``Rules that hide hosts in a domain should be applied
+only to sender addresses.'' Recipient masquerading works fine with
+qmail. None of sendmail's pitfalls apply, basically because qmail has a
+straight paper path.
+
+I predicted that I would receive some pressure to make up for the
+failings of MUA writers who don't understand the concept of reliability.
+(``Like, duh, you mean I'm supposed to check the sendmail exit code?'')
+I was right.
+
+
+3. Receiving mail from the network (tcp-env, qmail-smtpd)
+
+qmail-smtpd doesn't allow privacy-invading commands like VRFY and EXPN.
+If you really want to publish such information, use a mechanism that
+legitimate users actually know about, such as fingerd or httpd.
+
+RFC 1123 says that VRFY and EXPN are important to track down cross-host
+mailing list loops. With Delivered-To, mailing list loops do no damage,
+_and_ one of the list administrators gets a bounce message that shows
+exactly how the loop occurred. Solve the problem, not the symptom.
+
+Should dns.c make special allowances for 127.0.0.1/localhost?
+
+badmailfrom (like 8BITMIME) is a waste of code space.
+
+In theory a MAIL or RCPT argument can contain unquoted LFs. In practice
+there are a huge number of clients that terminate commands with just LF,
+even if they use CR properly inside DATA.
+
+
+4. Adding messages to the queue (qmail-queue)
+
+Should qmail-queue try to make sure enough disk space is free in
+advance? When qmail-queue is invoked by qmail-local or (with ESMTP)
+qmail-smtpd or qmail-qmtpd or qmail-qmqpd, it could be told a size in
+advance. I wish UNIX had an atomic allocate-disk-space routine...
+
+The qmail.h interface (reflecting the qmail-queue interface, which in
+turn reflects the current queue file structure) is constitutionally
+incapable of handling an address that contains a 0 byte. I can't imagine
+that this will be a problem.
+
+Should qmail-queue not bother queueing a message with no recipients?
+
+
+5. Handling queued mail (qmail-send, qmail-clean)
+
+The queue directory must be local. Mounting it over NFS is extremely
+dangerous---not that this stops people from running sendmail that way!
+Diskless hosts should use mini-qmail instead.
+
+Queue reliability demands that single-byte writes be atomic. This is
+true for a fixed-block filesystem such as UFS, and for a logging
+filesystem such as LFS.
+
+qmail-send uses 8 bytes of memory per queued message. Double that for
+reallocation. (Fix: use a small forest of heaps; i.e., keep several
+prioqs.) Double again for buddy malloc()s. (Fix: be clever about the
+heap sizes.) 32 bytes is worrisome, but not devastating. Even on my
+disk-heavy memory-light machine, I'd run out of inodes long before
+running out of memory.
+
+Some mail systems organize the queue by host. This is pointless as a
+means of splitting up the queue directory. The real issue is what to do
+when you suddenly find out that a host is up. For local SLIP/PPP links
+you know in advance which hosts need this treatment, so you can handle
+them with virtualdomains and serialmail.
+
+For the old queue structure I implemented recipient list compression:
+if mail goes out to a giant mailing list, and most of the recipients are
+delivered, make a new, compressed, todo list. But this really isn't
+worth the effort: it saves only a tiny bit of CPU time.
+
+qmail-send doesn't have any notions of precedence, priority, fairness,
+importance, etc. It handles the queue in first-seen-first-served order.
+One could put a lot of work into doing something different, but that
+work would be a waste: given the triggering mechanism and qmail's
+deferral strategy, it is exceedingly rare for the queue to contain more
+than one deliverable message at any given moment.
+
+Exception: Even with all the concurrency tricks, qmail-send can end up
+spending a few minutes on a mailing list with thousands of remote
+entries. A user might send a new message to a remote address in the
+meantime. The simplest way to handle this would be to put big messages
+on a separate channel.
+
+qmail-send will never start a pass for a job that it already has. This
+means that, if one delivery takes longer than the retry interval, the
+next pass will be delayed. I implemented the opposite strategy for the
+old queue structure. Some hassles: mark() had to understand how job
+input was buffered; every new delivery had to check whether the same
+mpos in the same message was already being done.
+
+Some things that qmail-send does synchronously: queueing a bounce
+message; doing a cleanup via qmail-clean; classifying and rewriting all
+the addresses in a new message. As usual, making these asynchronous
+would require some housekeeping, but could speed things up a bit.
+(I'm willing to assume POSIX waitpid() for asynchronous bounces; putting
+an unbounded buffer into wait_pid() for the sake of NeXTSTEP 3 is not
+worthwhile.)
+
+Disk I/O is a bottleneck; UFS is reliable but it isn't fast. A good
+logging filesystem offers much better performance, but logging
+filesystems aren't widely available. Solution: Keep a journal, separate
+from the queue, adequate to rebuild the queue (with at worst some
+duplicate deliveries). Compress the journal. This would dramatically
+reduce total disk I/O.
+
+Bounce aggregation is a dubious feature. Bounce records aren't
+crashproof; there can be a huge delay between a failure and a bounce;
+the resulting bounce format is unnecessarily complicated. I'm tempted to
+scrap the bounce directory and send one bounce for each failing
+recipient, with appropriate modifications in the accompanying text.
+
+qmail-stop implementation: setuid to UID_SEND; kill -TERM -1. Or run
+qmail-start under an external service controller, such as supervise;
+that's why it runs in the foreground.
+
+The readdir() interface hides I/O errors. Lower-level interfaces would
+lead me into a thicket of portability problems. I'm really not sure what
+to do about this. Of course, a hard I/O error means that mail is toast,
+but a soft I/O error shouldn't cause any trouble.
+
+job_open() or pass_dochan() could be paranoid about the same id,channel
+already being open; but, since messdone() is so paranoid, the worst
+possible effect of a bug along these lines would be double delivery.
+
+Mathematical amusement: The optimal retry schedule is essentially,
+though not exactly, independent of the actual distribution of message
+delay times. What really matters is how much cost you assign to retries
+and to particular increases in latency. qmail's current quadratic retry
+schedule says that an hour-long delay in a day-old message is worth the
+same as a ten-minute delay in an hour-old message; this doesn't seem so
+unreasonable.
+
+Insider information: AOL retries their messages every five minutes for
+three days straight. Hmmm.
+
+
+6. Sending mail through the network (qmail-rspawn, qmail-remote)
+
+Are there any hosts, anywhere, whose mailers are bogged down by huge
+messages to multiple recipients at a single host? For typical hosts,
+multiple RCPTs per SMTP aren't an ``efficiency feature''; they're a
+_slowness_ feature. Separate SMTP transactions have much lower latency.
+
+I've heard three complaints about bandwidth use from masochists sending
+messages through a modem through a smarthost to thousands of users---
+without sublists! They can get much better performance with QMQP.
+
+In the opposite direction: It's tempting to remove the @host part of the
+qmail-remote recip argument. Or at least avoid double-dns_cname.
+
+There are lots of reasons that qmail-rspawn should take a more active
+role in qmail-remote's activities. It should call separate programs to
+do (1) MX lookups, (2) SMTP connections, (3) QMTP connections. (But this
+wouldn't be so important if the DNS library didn't burn so much memory.)
+
+I bounce ambiguous MXs. (An ``ambiguous MX'' is a best-preference MX
+record sending me mail for a host that I don't recognize as local.)
+Automatically treating ambiguous MXs as local is incompatible with my
+design decision to keep local delivery working when the network goes
+down. It puts more faith in DNS than DNS deserves. Much better: Have
+your MX records generated automatically from control/locals.
+
+If I successfully connect to an MX host but it temporarily refuses to
+accept the message, I give up and put the message back into the queue.
+But several documents seem to suggest that I should try further MX
+records. What are they thinking? My approach deals properly with downed
+hosts, hosts that are unreachable through a firewall, and load
+balancing; what else do people use multiple MX records for?
+
+Currently qmail-remote sends data in 1024-byte buffers. Perhaps it
+should try to take account of the MTU.
+
+Perhaps qmail-remote should allocate a fixed amount of DNS/connect()
+time across any number of MXs; this idea is due to Mark Delany.
+
+RFC 821 doesn't say what it means by ``text.'' qmail-remote assumes that
+the server's reply text doesn't contain bare LFs.
+
+RFC 821 and RFC 1123 prohibit host names in MAIL FROM and RCPT TO from
+being aliases. qmail-remote, like sendmail, rewrites aliases in RCPT;
+people who don't list aliases in control/locals or sendmail's Cw are
+implicitly relying on this conversion. It is course quite silly for an
+internal DNS detail to have such an effect on mail delivery, but that's
+how the Internet works. On the other hand, the compatibility arguments
+do not apply to MAIL FROM. qmail-remote no longer bothers with CNAME
+lookups for the envelope sender host.
+
+
+7. Delivering mail locally (qmail-lspawn, qmail-local)
+
+qmail-local doesn't support comsat. comsat is a pointless abomination.
+Use qbiff if you want that kind of notification.
+
+The getpwnam() interface hides I/O errors. Solution: qmail-pw2u.
+
+
+8. sendmail V8's new features
+
+sendmail-8.8.0/doc/op/op.me includes a list of big improvements of
+sendmail 8.8.0 over sendmail 5.67. Here's how qmail stacks up against
+each of those improvements. (Of course, qmail has its own improvements,
+but that's not the point of this list.)
+
+Connection caching, MX piggybacking: Nope. (Profile. Don't speculate.)
+
+Response to RCPT command is fast: Yup.
+
+IP addresses show up in Received lines: Yup.
+
+Self domain literal is properly handled: Yup.
+
+Different timeouts for QUIT, RCPT, etc.: No, just a single timeout.
+
+Proper <> handling, route-address pruning: Yes, but not configurable.
+
+ESMTP support: Yup. (Server-side, including PIPELINING.)
+
+8-bit clean: Yup. (Including server-side 8BITMIME support; same as
+sendmail with the 8 option.)
+
+Configurable user database: Yup.
+
+BIND support: Yup.
+
+Keyed files: Yes, in fastforward.
+
+931/1413/Ident/TAP: Yup.
+
+Correct 822 address list parsing: Yup. (Note that sendmail still has
+some major problems with quoting.)
+
+List-owner handling: Yup.
+
+Dynamic header allocation: Yup.
+
+Minimum number of disk blocks: Yes, via tunefs -m. (Or quotas; the right
+setup has qmailq with a small quota, qmails with a larger quota, so that
+qmail-send always has room to work.)
+
+Checkpointing: Yes, but not configurable---qmail always checkpoints.
+
+Error message configuration: Nope.
+
+GECOS matching: Not directly, but easy to hook in.
+
+Hop limit configuration: No. (qmail's limit is 100 hops. qmail offers
+automatic loop protection much more advanced than hop counting.)
+
+MIME error messages: No. (qmail uses QSBMF error messages, which are
+much easier to parse.)
+
+Forward file path: Yes, via /etc/passwd.
+
+Incoming SMTP configuration: Yes, via inetd or tcpserver.
+
+Privacy options: Yes, but they're not options.
+
+Best-MX mangling: Nope. See section 6 for further discussion.
+
+7-bit mangling: Nope. qmail always uses 8 bits.
+
+Support for up to 20 MX records: Yes, and more. qmail has no limits
+other than memory.
+
+Correct quoting of name-and-address headers: Yup.
+
+VRFY and EXPN now different: Nope. qmail always hides this information.
+
+Multi-word classes, deferred macro expansion, separate envelope/header
+$g processing, separate per-mailer envelope and header processing, new
+command line flags, new configuration lines, new mailer flags, new
+macros: These are sendmail-specific; they wouldn't even make sense for
+qmail. For example, _of course_ qmail handles envelopes and headers
+separately; they're almost entirely different objects!
+
+
+9. Miscellany
+
+sendmail-clone and qsmhook are too bletcherous to be documented. (The
+official replacement for qsmhook is preline, together with the
+qmail-command environment variables.)
+
+I've considered making install atomic, but this is very difficult to do
+right, and pointless if it isn't done right.
+
+RN suggests automatically putting together a reasonable set of lines for
+/etc/passwd. I perceive this as getting into the adduser business, which
+is worrisome: I'll be lynched the first time I screw up somebody's
+passwd file. This should be left to OS-specific installation scripts.
+
+The BSD 4.2 inetd didn't allow a username. I think I can safely forget
+about this. (DS notes that the username works under Ultrix even though
+it's undocumented.)
+
+I should clean up the bput/put choices.
+
+Some of the stralloc_0()s indicate that certain lower-level routines
+should grok stralloc.
+
+qmail assumes that all times are positive; that pid_t, time_t and ino_t
+fit into unsigned long; that gid_t fits into int; that the character set
+is ASCII; and that all pointers are interchangeable. Do I care?
+
+The bat book justifies sendmail's insane line-splitting mechanism by
+pointing out that it might be useful for ``a 40-character braille
+print-driving program.'' C'mon, guys, is that your best excuse?
+
+qmail's mascot is a dolphin.
diff --git a/doc/Qmail/TODO.djb b/doc/Qmail/TODO.djb
new file mode 100644
index 0000000..7ce36b2
--- /dev/null
+++ b/doc/Qmail/TODO.djb
@@ -0,0 +1,23 @@
+(??) consider stripping vdoms for VERPs; tnx PJH
+(??) consider ~ in qmail-local for doing defaultdelivery (not recursively)
+(??) consider POP bulletins
+turn qmail-upq into a more serious queue-moving utility
+(--) consider fast-greeting option in qmail-smtpd -- partly done
+(na) build a returnmail package
+
+(++) expand strerr coverage -- done
+(++) redo control interface -- partly done
+(++) allow concurrency over 255 -- done
+(na) allow more channels at compile time -- done
+(na) test for linux fifo close bug at compile time
+
+(??) eliminate qsmhook -- done
+(??) finish OTBS conversion
+(na) use mess822 in qmail-inject
+(na) use mess822 in qreceipt
+(na) use mess822 in qbiff
+(na) use mess822 in maildirwatch
+(??) eliminate token822, headerbody, hfield
+(+-) replace INTERNALS and THOUGHTS with a real paper describing qmail
+(++) handle IPv6 -- done
+(-?) rewrite everything from scratch
diff --git a/doc/Qmail/TODO.done b/doc/Qmail/TODO.done
new file mode 100644
index 0000000..6892073
--- /dev/null
+++ b/doc/Qmail/TODO.done
@@ -0,0 +1,23 @@
+(??) consider stripping vdoms for VERPs; tnx PJH
+(??) consider ~ in qmail-local for doing defaultdelivery (not recursively)
+(??) consider POP bulletins
+turn qmail-upq into a more serious queue-moving utility -- done (qmail-queuefix)
+(--) consider fast-greeting option in qmail-smtpd -- partly done
+(na) build a returnmail package
+
+(++) expand strerr coverage -- done
+(++) redo control interface -- partly done
+(++) allow concurrency over 255 -- done
+(na) allow more channels at compile time -- done
+(na) test for linux fifo close bug at compile time -- irrelevant
+
+(??) eliminate qsmhook -- done
+(??) finish OTBS conversion
+(na) use mess822 in qmail-inject
+(na) use mess822 in qreceipt
+(na) use mess822 in qbiff
+(na) use mess822 in maildirwatch
+(??) eliminate token822, headerbody, hfield
+(+-) replace INTERNALS and THOUGHTS with a real paper describing qmail -- mostly done
+(++) handle IPv6 -- done
+(-?) rewrite everything from scratch -- what shall I say?
diff --git a/doc/README.clamav b/doc/README.clamav
new file mode 100644
index 0000000..2fdc361
--- /dev/null
+++ b/doc/README.clamav
@@ -0,0 +1,27 @@
+Patch to ClamAV 0.8x/0.9x
+=========================
+
+There is a bug in ClamAV 0.9x not
+to write scanning results to STDERR.
+Instead all logging is done to STDOUT.
+
+This inhibits the logging for qmail-smtpd.
+
+The intended behavior of ClamAV can be
+re-established applying the patch
+
+ clamav-0.90.1_output.patch_
+
+to
+
+ output.c
+
+in ClamAV's source directory
+
+ ./shared.
+
+
+--eh. (14.04.2013)
+
+
+
diff --git a/doc/README.smtpreply b/doc/README.smtpreply
new file mode 100644
index 0000000..84ff016
--- /dev/null
+++ b/doc/README.smtpreply
@@ -0,0 +1,72 @@
+SMTP Reply Codes with s/qmail
+=============================
+
+SMTP allows to reject Sessions based on some technical
+and/or political criteria, which are not well expressed
+in the RFCs (2821, 2554, 2505, 1122).
+
+As protocol mechanism between the client and the server
+are defined as Commands and Replies. SMTP uses a
+three-letter Reply Code. The first digit tells whether
+a command was accepted and completed (2), transaction begin
+(3), or whether there was as transient (4) or permanent failure (5).
+
+In addition, an explanatory description may be given.
+
+RFC 1893 introduces a concept of "Enhanced Mail System
+Status Codes" (EMSSC) which should provide easy parseable
+SMTP server conditions and transaction stati, usually
+at the end of the SMTP reply and included in paranthesis,
+eg. (#5.5.1).
+
+The STMP Reply Codes and the EMSSC are detailed in the
+corresponding RFCs, but don't fit well to each other,
+thus either providing redundant information or almost
+no additional information at all. In short, the EMSSC
+is nowadays almost meaningless.
+
+Here's a breakdown of s/qmail's SMTP Reply Codes,
+informational texts, and the used EMSSC.
+
+Reply Informational text (EMSSC)
+---------------------------------------------------
+
+ 400 proabably greylisted (#4.3.0) [REPLY_GREYLISTED]
+ 421 unable to check recipients (#4.3.0)
+ 421 greylisted (#4.3.0) [REPLY_GREYLISTED]
+ 450 sorry, mailbox currently unavailable (#4.2.1) [1]
+ 450 greylisted (#4.3.0) [REPLY_GREYLISTED]
+ 451 DNS temporary failure (#4.3.0)
+ 452 sorry, too many recipients (#4.5.3)
+ 454 TLS not available due to temporary reason (#5.7.3)
+
+ 501 auth exchange canceled (#5.0.0)
+ 501 malformed auth input (#5.5.4)
+ 503 you're already authenticated (#5.5.0)
+ 503 no auth during mail transaction (#5.5.0)
+ 503 sorry, SMTP Authentication not available (#5.7.3)
+ 503 DATA command not accepted at this time (#5.5.1)
+ 504 auth type unimplemented (#5.5.1)
+ 535 authorization failed (#5.7.1)
+ 535 STARTTLS required (#5.7.1)
+
+ 550 sorry, invalid HELO/EHLO greeting [*] (#5.7.1) [REPLY_HELO]
+ 550 sorry, your envelope recipient is in my badrcptto list [*] (#5.7.1) [REPLY_BADRCPTTO]
+ 550 sorry, mailbox currently unavailable [*] (#4.2.1) [2] [REPLY_MAILBOX]
+
+ 552 sorry, that message size exceeds my databytes limit [*] (#5.3.4) [REPLY_MAXSIZE]
+ 553 sorry, your envelope sender is in my badmailfrom list [*] (#5.7.1) [REPLY_BADMAILFROM]
+ 553 sorry, invalid sender address specified [*] (#5.7.1) [REPLY_SENDERINVALID]
+ 553 sorry, that domain isn't in my list of allowed rcpthosts [*] (#5.7.1) [REPLY_NOGATEWAY]
+ 553 sorry, your envelope sender domain must exist [*] (#5.7.1) [REPLY_SENDEREXIST]
+
+ 554 too many hops, this message is looping (#5.4.6)
+ 554 sorry, invalid message content [*] (#5.3.2) [REPLY_CONTENT]
+
+
+Note:
+
+[1] or [2] depends on setting of environment variable RECIPIENTS550; default [2].
+[*] Additional text can be included here via environment variables provided in paranthesis,
+ eg. REPLY_HELO='see RFC 2821 section 3.6'.
+
diff --git a/doc/TODO b/doc/TODO
new file mode 100644
index 0000000..38def0d
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,14 @@
+Some ideas for s/qmail future features
+======================================
+
+Cleanups:
+- srs2.c refactoring.
+- qmail-ldapam.c refactoring and integration. (separate package)
+- maildir++ patch inclusion? (done)
+
+Extensions:
+- QMQ integration.
+- DKIM API. (done)
+- GUUID instead inodes for queue files.
+- SMTP pipelining for delivery.
+- Native IDN2 support.
diff --git a/doc/smtpreplies b/doc/smtpreplies
new file mode 100644
index 0000000..a47adde
--- /dev/null
+++ b/doc/smtpreplies
@@ -0,0 +1,13 @@
+# In this file, you can include customizable SMTP reply messages for qmail-smtpd
+# Call this file in the qmail-smtpd run script (i.e. '. /var/qmail/etc/smtpreplies')
+# such the variables are available in the environment
+#
+export REPLY_GREYLISTED=""
+export REPLY_HELO=""
+export REPLY_BADRCPTTO=""
+export REPLY_MAILBOX=""
+export REPLY_BADMAILFROM=""
+export REPLY_SENDERENV=""
+export REPLY_NOGATEWAY=""
+export REPLY_MAILFROM=""
+export REPLY_CONTENT=""
diff --git a/etc/qmail-mrtg.pop3d.sample b/etc/qmail-mrtg.pop3d.sample
new file mode 100644
index 0000000..736d99e
--- /dev/null
+++ b/etc/qmail-mrtg.pop3d.sample
@@ -0,0 +1,35 @@
+WorkDir: HTDOCS
+EnableIPv6: no
+#############################################################
+
+Title[pop3-session]: HOSTNAME - qmail-popup (Accepted+Rejected User)
+MaxBytes[pop3-session]: 100
+Options[pop3-session]: gauge, nopercent
+Target[pop3-session]: `cat LOGFILE | QMAILHOME/bin/qmail-mrtg -A`
+PageTop[pop3-session]: <font face=arial size=3><B>HOSTNAME</B> - qmail-popup (Accepted+Rejected User)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[pop3-session]: Sessions
+Legend1[pop3-session]: User accepted
+Legend2[pop3-session]: User rejected
+Legend3[pop3-session]: max. 5 minutes user accepted
+Legend4[pop3-session]: max. 5 minutes user rejected
+LegendI[pop3-session]: &nbsp;Accepted:
+LegendO[pop3-session]: &nbsp;Rejected:
+WithPeak[pop3-session]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[pop3-connection]: HOSTNAME - qmail-pop3d (Connections)
+MaxBytes[pop3-connection]: 100
+Options[pop3-connection]: gauge, nopercent
+Target[pop3-connection]: `cat LOGFILE | QMAILHOME/bin/qmail-mrtg -B`
+PageTop[pop3-connection]: <font face=arial size=3><B>HOSTNAME</B> - qmail-pop3d (Connections)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[pop3-connection]: Connections
+Legend1[pop3-connection]: qmail-pop3d accepted
+Legend2[pop3-connection]: qmail-pop3d rejected
+Legend3[pop3-connection]: max. 5 minutes qmail-pop3d accepted
+Legend4[pop3-connection]: max. 5 minutes qmail-pop3d rejected
+LegendI[pop3-connection]: &nbsp;Accepted:
+LegendO[pop3-connection]: &nbsp;Rejected:
+WithPeak[pop3-connection]: ymwd
+
+#-------------------------------------------------------------------
diff --git a/etc/qmail-mrtg.send.sample b/etc/qmail-mrtg.send.sample
new file mode 100644
index 0000000..ecee472
--- /dev/null
+++ b/etc/qmail-mrtg.send.sample
@@ -0,0 +1,108 @@
+WorkDir: HTDOCS
+EnableIPv6: no
+#############################################################
+
+Title[deliveries]: HOSTNAME - qmail-send (Deliveries/TLS transmitted)
+MaxBytes[deliveries]: 1000
+AbsMax[deliveries]: 10000
+Options[deliveries]: gauge
+Target[deliveries]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -1`
+PageTop[deliveries]: <font face=arial size=3><B>HOSTNAME</B> - qmail-send (Deliveries)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[deliveries]: Deliveries
+YLegend[deliveries]: Deliveries
+Legend1[deliveries]: Deliveries
+LegendI[deliveries]: &nbsp;Deliveries:
+LegendO[deliveries]: &nbsp;TLS tansmitted:
+WithPeak[deliveries]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[bytes]: HOSTNAME - qmail-send (Bytes Transfered)
+MaxBytes[bytes]: 1000
+AbsMax[bytes]: 100000000
+Options[bytes]: gauge
+Target[bytes]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -2`
+PageTop[bytes]: <font face=arial size=3><B>HOSTNAME</B> - qmail-send (Bytes Transfered)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[bytes]: kB
+YLegend[bytes]: KBytes
+Legend1[bytes]: KBytes
+LegendI[bytes]: &nbsp;KBytes:
+LegendO[bytes]: &nbsp;KBytes:
+WithPeak[bytes]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[concurrency]: HOSTNAME - qmail-send (Concurrency)
+MaxBytes[concurrency]: 1000
+AbsMax[concurrency]: 10000
+Options[concurrency]: gauge
+Target[concurrency]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -3`
+PageTop[concurrency]: <font face=arial size=3><B>HOSTNAME</B> - qmail-send (Concurrency)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[concurrency]: Concurrency
+YLegend[concurrency]: Concurrency
+Legend1[concurrency]: Concurrency
+LegendI[concurrency]: &nbsp;Local:
+LegendO[concurrency]: &nbsp;Remote:
+WithPeak[concurrency]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[smtp-problems]: HOSTNAME - qmail-remote (Failures/Deferrals)
+MaxBytes[smtp-problems]: 100
+AbsMax[smtp-problems]: 10000
+Options[smtp-problems]: gauge
+Target[smtp-problems]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -4`
+PageTop[smtp-problems]: <font face=arial size=3><B>HOSTNAME</B> - qmail-remote (Failures/Deferrals)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[smtp-problems]: Messages
+YLegend[smtp-problems]: Messages
+Legend1[smtp-problems]: Messages
+LegendI[smtp-problems]: &nbsp;Failures:
+LegendO[smtp-problems]: &nbsp;Deferrals:
+WithPeak[smtp-problems]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[bounces]: HOSTNAME - qmail-send (Bounces)
+MaxBytes[bounces]: 100
+AbsMax[bounces]: 10000
+Options[bounces]: gauge
+Target[bounces]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -5`
+PageTop[bounces]: <font face=arial size=3><B>HOSTNAME</B> - qmail-send (Bounces)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[bounces]: Messages
+YLegend[bounces]: Messages
+Legend1[bounces]: Messages
+LegendI[bounces]: &nbsp;Bounces:
+LegendO[bounces]: &nbsp;Triple bounces:
+WithPeak[bounces]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[qmtp-deliveries]: HOSTNAME - qmail-remote (QMTP/QMTPS Sessions)
+MaxBytes[qmtp-deliveries]: 100
+AbsMax[qmtp-deliveries]: 10000
+Options[qmtp-deliveries]: gauge
+Target[qmtp-deliveries]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -6`
+PageTop[qmtp-deliveries]: <font face=arial size=3><B>HOSTNAME</B> - qmail-remote (QMTP/QMTPS)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[qmtp-deliveries]: Messages
+YLegend[qmtp-deliveries]: Messages
+Legend1[qmtp-deliveries]: Messages
+LegendI[qmtp-deliveries]: &nbsp;QMTP:
+LegendO[qmtp-deliveries]: &nbsp;QMTPS:
+WithPeak[qmtp-deliveries]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[queue-size]: HOSTNAME - qmail-queue (Queue Size)
+MaxBytes[queue-size]: 1000
+AbsMax[queue-size]: 10000
+Options[queue-size]: gauge
+Target[queue-size]: `SQMAIL/bin/qmail-mrtg-queue`
+PageTop[queue-size]: <font face=arial size=3><B>HOSTNAME</B> - qmail-queue (Queue Size)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+ShortLegend[queue-size]: Messages
+YLegend[queue-size]: Messages
+Legend1[queue-size]: Messages
+LegendI[queue-size]: &nbsp;Messages:
+LegendO[queue-size]: &nbsp;Unprocessed Messages:
+WithPeak[queue-size]: ymwd
+
+#-------------------------------------------------------------------
diff --git a/etc/qmail-mrtg.smtpd.sample b/etc/qmail-mrtg.smtpd.sample
new file mode 100644
index 0000000..6868eb5
--- /dev/null
+++ b/etc/qmail-mrtg.smtpd.sample
@@ -0,0 +1,179 @@
+WorkDir: HTDOCS
+EnableIPv6: no
+#############################################################
+
+Title[smtpd-session]: HOSTNAME - qmail-smtpd (Total Sessions)
+MaxBytes[smtpd-session]: 100
+Options[smtpd-session]: gauge, nopercent
+Target[smtpd-session]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -a`
+PageTop[smtpd-session]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Total Sessions)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[smtpd-session]: Sessions
+Legend1[smtpd-session]: qmail-smtpd accepted
+Legend2[smtpd-session]: qmail-smtpd rejected
+Legend3[smtpd-session]: max. 5 minutes qmail-smtpd accepted
+Legend4[smtpd-session]: max. 5 minutes qmail-smtpd rejected
+LegendI[smtpd-session]: &nbsp;Accepted:
+LegendO[smtpd-session]: &nbsp;Rejected:
+WithPeak[smtpd-session]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[accepted-session]: HOSTNAME - qmail-smtpd (Accepted and Rejected Sessions)
+MaxBytes[accepted-session]: 100
+Options[accepted-session]: gauge, nopercent
+Target[accepted-session]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -b`
+PageTop[accepted-session]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Accepted+Rejected Sessions)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[accepted-session]: Sessions
+Legend1[accepted-session]: Originator accepted
+Legend2[accepted-session]: Recipient accepted
+Legend3[accepted-session]: max. 5 minutes originator accepted
+Legend4[accepted-session]: max. 5 minutes recipient accepted
+LegendI[accepted-session]: &nbsp;Originator:
+LegendO[accepted-session]: &nbsp;Recipient:
+WithPeak[accepted-session]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[rejected-sender]: HOSTNAME - qmail-smtpd (Rejected Sender)
+MaxBytes[rejected-sender]: 100
+Options[rejected-sender]: gauge, nopercent
+Target[rejected-sender]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -c`
+PageTop[rejected-sender]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Rejected Sender)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[rejected-sender]: Sessions
+Legend1[rejected-sender]: Invalid relay
+Legend2[rejected-sender]: Badhelo greeting
+Legend3[rejected-sender]: max. 5 minutes relaying attempts rejected
+Legend4[rejected-sender]: max. 5 minutes badhelo greeting rejected
+LegendI[rejected-sender]: &nbsp;Relay:
+LegendO[rejected-sender]: &nbsp;HELO/EHLO:
+WithPeak[rejected-sender]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[rejected-originator]: HOSTNAME - qmail-smtpd (Rejected Originators)
+MaxBytes[rejected-originator]: 100
+Options[rejected-originator]: gauge, nopercent
+Target[rejected-originator]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -d`
+PageTop[rejected-originator]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Rejected Originators)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[rejected-originator]: Sessions
+Legend1[rejected-originator]: Badmailfrom rejected
+Legend2[rejected-originator]: DNS MF rejected
+Legend3[rejected-originator]: max. 5 minutes badmailfrom rejected
+Legend4[rejected-originator]: max. 5 minutes DNS MF rejected
+LegendI[rejected-originator]: &nbsp;Badmailfrom:
+LegendO[rejected-originator]: &nbsp;DNS MF:
+WithPeak[rejected-originator]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[rejected-recipient]: HOSTNAME - qmail-smtpd (Rejected Recipients)
+MaxBytes[rejected-recipient]: 100
+Options[rejected-recipient]: gauge, nopercent
+Target[rejected-recipient]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -e`
+PageTop[rejected-recipient]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Rejected Recipients)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[rejected-recipient]: Sessions
+Legend1[rejected-recipient]: Badrcptto rejected
+Legend2[rejected-recipient]: Invalid recipients
+Legend3[rejected-recipient]: max. 5 minutes badrcptto rejected
+Legend4[rejected-recipient]: max. 5 minutes invalid recipients rejected
+LegendI[rejected-recipient]: &nbsp;Badrcptto:
+LegendO[rejected-recipient]: &nbsp;Recipient:
+WithPeak[rejected-recipient]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[rejected-base64]: HOSTNAME - qmail-smtpd (Rejected BASE64)
+MaxBytes[rejected-base64]: 100
+Options[rejected-base64]: gauge, nopercent
+Target[rejected-base64]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -f`
+PageTop[rejected-base64]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Rejected BASE64)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[rejected-base64]: Sessions
+Legend1[rejected-base64]: Bad mimetypes
+Legend2[rejected-base64]: Bad loadertypes
+Legend3[rejected-base64]: max. 5 minutes mime rejected
+Legend4[rejected-base64]: max. 5 minutes loader rejected
+LegendI[rejected-base64]: &nbsp;Bad MIME:
+LegendO[rejected-base64]: &nbsp;Bad LOADER:
+WithPeak[rejected-base64]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[rejected-data]: HOSTNAME - qmail-smtpd (Rejected Data)
+MaxBytes[rejected-data]: 100
+Options[rejected-data]: gauge, nopercent
+Target[rejected-data]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -g`
+PageTop[rejected-data]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Rejected Data)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[rejected-data]: Sessions
+Legend1[rejected-data]: Virus infected
+Legend2[rejected-data]: Spam messages
+Legend3[rejected-data]: max. 5 minutes virus rejected
+Legend4[rejected-data]: max. 5 minutes spam rejected
+LegendI[rejected-data]: &nbsp;Virus:
+LegendO[rejected-data]: &nbsp;Spam:
+WithPeak[rejected-data]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[auth-session]: HOSTNAME - qmail-smtpd (Authentication)
+MaxBytes[auth-session]: 100
+Options[auth-session]: gauge, nopercent
+Target[auth-session]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -h`
+PageTop[auth-session]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (Authentication)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[auth-session]: Sessions
+Legend1[auth-session]: Authentication accepted
+Legend2[auth-session]: Authentication rejected
+Legend3[auth-session]: max. 5 minutes Authentication accepted
+Legend4[auth-session]: max. 5 minutes Authentication rejected
+LegendI[auth-session]: &nbsp;Accepted:
+LegendO[auth-session]: &nbsp;Rejected:
+WithPeak[auth-session]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[tls-session]: HOSTNAME - qmail-smtpd (TLS Sessions)
+MaxBytes[tls-session]: 100
+Options[tls-session]: gauge, nopercent
+Target[tls-session]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -i`
+PageTop[tls-session]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (TLS Sessions)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[tls-session]: TLS Sessions
+Legend1[tls-session]: Accepted
+Legend2[tls-session]: Rejected
+Legend3[tls-session]: max. 5 minutes TLS accepted
+Legend4[tls-session]: max. 5 minutes TLS rejected
+LegendI[tls-session]: &nbsp;Accepted:
+LegendO[tls-session]: &nbsp;Rejected:
+WithPeak[tls-session]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[spf-session]: HOSTNAME - qmail-smtpd (SPF)
+MaxBytes[spf-session]: 100
+Options[spf-session]: gauge, nopercent
+Target[spf-session]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -j`
+PageTop[spf-session]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (SPF Sessions)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[spf-session]: SPF Sessions
+Legend1[spf-session]: Accepted
+Legend2[spf-session]: Rejected
+Legend3[spf-session]: max. 5 minutes SPF accepted
+Legend4[spf-session]: max. 5 minutes SPF rejected
+LegendI[spf-session]: &nbsp;Accepted:
+LegendO[spf-session]: &nbsp;Rejected:
+WithPeak[spf-session]: ymwd
+
+#-------------------------------------------------------------------
+
+Title[smtp-connection]: HOSTNAME - qmail-smtpd (tcpserver/sslserver + rblsmtpd Connections)
+MaxBytes[smtp-connection]: 100
+Options[smtp-connection]: gauge, nopercent
+Target[smtp-connection]: `cat LOGFILE | SQMAIL/bin/qmail-mrtg -z`
+PageTop[smtp-connection]: <font face=arial size=3><B>HOSTNAME</B> - qmail-smtpd (tcpserver/sslserver + rblsmtpd Connections)</font><br><font size=1 face=arial>s/qmail MRTG Stats collector <a href=http://HOSTNAME>HOSTNAME</a></font>
+YLegend[smtp-connection]: Connections
+Legend1[smtp-connection]: tcpserver/sslserver connection ok
+Legend2[smtp-connection]: tcpserver/sslserver connection deny/rejected
+Legend3[smtp-connection]: max. 5 minutes connection ok
+Legend4[smtp-connection]: max. 5 minutes connectiosn deny/rejected
+LegendI[smtp-connection]: &nbsp;connection ok:
+LegendO[smtp-connection]: &nbsp;connection deny:
+WithPeak[smtp-connection]: ymwd
+
+#-------------------------------------------------------------------
diff --git a/man/Makefile b/man/Makefile
new file mode 100644
index 0000000..52b85ee
--- /dev/null
+++ b/man/Makefile
@@ -0,0 +1,512 @@
+# Don't edit Makefile! Use ../conf-* for configuration.
+
+SHELL=/bin/sh
+
+default: modules docs dns
+
+addresses.0: \
+addresses.5
+ nroff -man addresses.5 > addresses.0
+
+bouncesaying.0: \
+bouncesaying.1
+ nroff -man bouncesaying.1 > bouncesaying.0
+
+columnt.0: \
+columnt.1
+ nroff -man columnt.1 > columnt.0
+
+condredirect.0: \
+condredirect.1
+ nroff -man condredirect.1 > condredirect.0
+
+dns:\
+dnscname.0 dnsfq.0 dnsip.0 dnsmxip.0 dnsptr.0 dnstxt.0 \
+hostname.0 ipmeprint.0
+
+dnscname.0: \
+dnscname.8
+ nroff -man dnscname.8 > dnscname.0
+
+dnsfq.0: \
+dnsfq.8
+ nroff -man dnsfq.8 > dnsfq.0
+
+dnsip.0: \
+dnsip.8
+ nroff -man dnsip.8 > dnsip.0
+
+dnsmxip.0: \
+dnsmxip.8
+ nroff -man dnsmxip.8 > dnsmxip.0
+
+dnsptr.0: \
+dnsptr.8
+ nroff -man dnsptr.8 > dnsptr.0
+
+dnstxt.0: \
+dnstxt.8
+ nroff -man dnstxt.8 > dnstxt.0
+
+datetime.0: \
+datetime.3
+ nroff -man datetime.3 > datetime.0
+
+docs:\
+addresses.0 dot-qmail.0 envelopes.0 forgeries.0 mbox.0 maildir.0 \
+qmail-command.0 qmail-control.0 qmail-header.0 qmail-limits.0 \
+tcp-environ.0
+
+dot-qmail.0: \
+dot-qmail.5
+ nroff -man dot-qmail.5 > dot-qmail.0
+
+dot-qmail.5: \
+dot-qmail.9 ../conf-home ../conf-break ../conf-spawn
+ cat dot-qmail.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > dot-qmail.5
+
+envelopes.0: \
+envelopes.5
+ nroff -man envelopes.5 > envelopes.0
+
+except.0: \
+except.1
+ nroff -man except.1 > except.0
+
+fastforward.0: \
+fastforward.1
+ nroff -man fastforward.1 > fastforward.0
+
+forgeries.0: \
+forgeries.7
+ nroff -man forgeries.7 > forgeries.0
+
+forward.0: \
+forward.1
+ nroff -man forward.1 > forward.0
+
+hostname.0: \
+hostname.8
+ nroff -man hostname.8 > hostname.0
+
+ipmeprint.0: \
+ipmeprint.8
+ nroff -man ipmeprint.8 > ipmeprint.0
+
+maildir.0: \
+maildir.5
+ nroff -man maildir.5 > maildir.0
+
+maildir2mbox.0: \
+maildir2mbox.1
+ nroff -man maildir2mbox.1 > maildir2mbox.0
+
+maildirmake.0: \
+maildirmake.1
+ nroff -man maildirmake.1 > maildirmake.0
+
+maildirwatch.0: \
+maildirwatch.1
+ nroff -man maildirwatch.1 > maildirwatch.0
+
+mailsubj.0: \
+mailsubj.1
+ nroff -man mailsubj.1 > mailsubj.0
+
+matchup.0: \
+matchup.1
+ nroff -man matchup.1 > matchup.0
+
+mbox.0: \
+mbox.5
+ nroff -man mbox.5 > mbox.0
+
+modules: \
+qmail-local.0 qmail-lspawn.0 qmail-getpw.0 qmail-remote.0 qmail-smtpam.0 \
+qmail-todo.0 qmail-vmailuser.0 qmail-authuser.0 qmail-postgrey.0 \
+qmail-rspawn.0 qmail-clean.0 qmail-send.0 qmail-start.0 splogger.0 spfquery.0 \
+qmail-queue.0 qmail-inject.0 mailsubj.0 qmail-showctl.0 qmail-newu.0 qmail-qmaint.0 \
+qmail-badmimetypes.0 qmail-badloadertypes.0 qmail-recipients.0 qmail-mfrules.0 \
+qmail-pw2u.0 qmail-qread.0 qmail-qstat.0 qmail-tcpto.0 qmail-tcpok.0 \
+qmail-pop3d.0 qmail-popup.0 qmail-qmqpc.0 qmail-qmqpd.0 qmail-qmtpd.0 \
+qmail-smtpd.0 qmail-newmrh.0 qmail-mrtg.0 qmail-users.0 qreceipt.0 qbiff.0 \
+forward.0 preline.0 condredirect.0 bouncesaying.0 except.0 maildirmake.0 \
+maildir2mbox.0 maildirwatch.0 sqmail.0 tai64nfrac.0 \
+columnt.0 matchup.0 xqp.0 xrecipient.0 xsender.0 newaliases.0 newinclude.0 \
+fastforward.0 printforward.0 printmaillist.0 setforward.0 setmaillist.0 \
+srsforward.0 srsreverse.0 \
+qmail-dkim.0 qmail-dksign.0 qmail-dkverify.0 \
+
+newaliases.0: \
+newaliases.1
+ nroff -man newaliases.1 > newaliases.0
+
+newinclude.0: \
+newinclude.1
+ nroff -man newinclude.1 > newinclude.0
+
+preline.0: \
+preline.1
+ nroff -man preline.1 > preline.0
+
+printforward.0: \
+printforward.1
+ nroff -man printforward.1 > printforward.0
+
+printmaillist.0: \
+printmaillist.1
+ nroff -man printmaillist.1 > printmaillist.0
+
+qbiff.0: \
+qbiff.1
+ nroff -man qbiff.1 > qbiff.0
+
+qmail-clean.0: \
+qmail-clean.8
+ nroff -man qmail-clean.8 > qmail-clean.0
+
+qmail-authuser.0: \
+qmail-authuser.8
+ nroff -man qmail-authuser.8 > qmail-authuser.0
+
+qmail-authuser.8: \
+qmail-authuser.9 ../conf-home
+ cat qmail-authuser.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-authuser.8
+
+qmail-badmimetypes.0: \
+qmail-badmimetypes.8
+ nroff -man qmail-badmimetypes.8 > qmail-badmimetypes.0
+
+qmail-badmimetypes.8: \
+qmail-badmimetypes.9 ../conf-home
+ cat qmail-badmimetypes.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-badmimetypes.8
+
+qmail-badloadertypes.0: \
+qmail-badloadertypes.8
+ nroff -man qmail-badloadertypes.8 > qmail-badloadertypes.0
+
+qmail-badloadertypes.8: \
+qmail-badloadertypes.9 ../conf-home
+ cat qmail-badloadertypes.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-badloadertypes.8
+
+qmail-command.0: \
+qmail-command.8
+ nroff -man qmail-command.8 > qmail-command.0
+
+qmail-control.0: \
+qmail-control.5
+ nroff -man qmail-control.5 > qmail-control.0
+
+qmail-control.5: \
+qmail-control.9 ../conf-home
+ cat qmail-control.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-control.5
+
+qmail-dkim.0: \
+qmail-dkim.8
+ nroff -man qmail-dkim.8 > qmail-dkim.0
+
+qmail-dksign.0: \
+qmail-dksign.8
+ nroff -man qmail-dksign.8 > qmail-dksign.0
+
+qmail-dksign.8: \
+qmail-dksign.9 ../conf-home
+ cat qmail-dksign.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-dksign.8
+
+qmail-dkverify.0: \
+qmail-dkverify.8
+ nroff -man qmail-dkverify.8 > qmail-dkverify.0
+
+qmail-getpw.0: \
+qmail-getpw.8
+ nroff -man qmail-getpw.8 > qmail-getpw.0
+
+qmail-getpw.8: \
+qmail-getpw.9 ../conf-home
+ cat qmail-getpw.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-getpw.8
+
+qmail-header.0: \
+qmail-header.5
+ nroff -man qmail-header.5 > qmail-header.0
+
+qmail-inject.0: \
+qmail-inject.8
+ nroff -man qmail-inject.8 > qmail-inject.0
+
+qmail-limits.0: \
+qmail-limits.7
+ nroff -man qmail-limits.7 > qmail-limits.0
+
+qmail-limits.7: \
+qmail-limits.9 ../conf-home ../conf-break ../conf-spawn
+ cat qmail-limits.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > qmail-limits.7
+
+qmail-local.0: \
+qmail-local.8
+ nroff -man qmail-local.8 > qmail-local.0
+
+qmail-log.0: \
+qmail-log.5
+ nroff -man qmail-log.5 > qmail-log.0
+
+qmail-lspawn.0: \
+qmail-lspawn.8
+ nroff -man qmail-lspawn.8 > qmail-lspawn.0
+
+qmail-mfrules.0: \
+qmail-mfrules.8
+ nroff -man qmail-mfrules.8 > qmail-mfrules.0
+
+qmail-mfrules.8: \
+qmail-mfrules.9 ../conf-home
+ cat qmail-mfrules.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-mfrules.8
+
+qmail-mrtg.0: \
+qmail-mrtg.8
+ nroff -man qmail-mrtg.8 > qmail-mrtg.0
+
+qmail-newmrh.0: \
+qmail-newmrh.8
+ nroff -man qmail-newmrh.8 > qmail-newmrh.0
+
+qmail-newmrh.8: \
+qmail-newmrh.9 ../conf-home
+ cat qmail-newmrh.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-newmrh.8
+
+qmail-newu.0: \
+qmail-newu.8
+ nroff -man qmail-newu.8 > qmail-newu.0
+
+qmail-newu.8: \
+qmail-newu.9 ../conf-home
+ cat qmail-newu.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-newu.8
+
+qmail-pop3d.0: \
+qmail-pop3d.8
+ nroff -man qmail-pop3d.8 > qmail-pop3d.0
+
+qmail-popup.0: \
+qmail-popup.8
+ nroff -man qmail-popup.8 > qmail-popup.0
+
+qmail-postgrey.0: \
+qmail-postgrey.8
+ nroff -man qmail-postgrey.8 > qmail-postgrey.0
+
+qmail-pw2u.0: \
+qmail-pw2u.8
+ nroff -man qmail-pw2u.8 > qmail-pw2u.0
+
+qmail-pw2u.8: \
+qmail-pw2u.9 ../conf-home
+ cat qmail-pw2u.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-pw2u.8
+
+qmail-qmqpc.0: \
+qmail-qmqpc.8
+ nroff -man qmail-qmqpc.8 > qmail-qmqpc.0
+
+qmail-qmqpd.0: \
+qmail-qmqpd.8
+ nroff -man qmail-qmqpd.8 > qmail-qmqpd.0
+
+qmail-qmtpd.0: \
+qmail-qmtpd.8
+ nroff -man qmail-qmtpd.8 > qmail-qmtpd.0
+
+qmail-qread.0: \
+qmail-qread.8
+ nroff -man qmail-qread.8 > qmail-qread.0
+
+qmail-qstat.0: \
+qmail-qstat.8
+ nroff -man qmail-qstat.8 > qmail-qstat.0
+
+qmail-qmaint.0: \
+qmail-qmaint.8
+ nroff -man qmail-qmaint.8 > qmail-qmaint.0
+
+qmail-queue.0: \
+qmail-queue.8
+ nroff -man qmail-queue.8 > qmail-queue.0
+
+qmail-recipients.0: \
+qmail-recipients.8
+ nroff -man qmail-recipients.8 > qmail-recipients.0
+
+qmail-recipients.8: \
+qmail-recipients.9 ../conf-home
+ cat qmail-recipients.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-recipients.8
+
+qmail-remote.0: \
+qmail-remote.8
+ nroff -man qmail-remote.8 > qmail-remote.0
+
+qmail-rspawn.0: \
+qmail-rspawn.8
+ nroff -man qmail-rspawn.8 > qmail-rspawn.0
+
+qmail-send.0: \
+qmail-send.8
+ nroff -man qmail-send.8 > qmail-send.0
+
+qmail-send.8: \
+qmail-send.9 ../conf-home
+ cat qmail-send.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-send.8
+
+qmail-showctl.0: \
+qmail-showctl.8
+ nroff -man qmail-showctl.8 > qmail-showctl.0
+
+qmail-smtpam.0: \
+qmail-smtpam.8
+ nroff -man qmail-smtpam.8 > qmail-smtpam.0
+
+qmail-smtpd.0: \
+qmail-smtpd.8
+ nroff -man qmail-smtpd.8 > qmail-smtpd.0
+
+qmail-start.0: \
+qmail-start.8
+ nroff -man qmail-start.8 > qmail-start.0
+
+qmail-start.8: \
+qmail-start.9 ../conf-home ../conf-break ../conf-spawn
+ cat qmail-start.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > qmail-start.8
+
+qmail-tcpok.0: \
+qmail-tcpok.8
+ nroff -man qmail-tcpok.8 > qmail-tcpok.0
+
+qmail-tcpto.0: \
+qmail-tcpto.8
+ nroff -man qmail-tcpto.8 > qmail-tcpto.0
+
+qmail-todo.0: \
+qmail-todo.8
+ nroff -man qmail-todo.8 > qmail-todo.0
+
+qmail-users.0: \
+qmail-users.5
+ nroff -man qmail-users.5 > qmail-users.0
+
+qmail-users.5: \
+qmail-users.9 ../conf-home
+ cat qmail-users.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-users.5
+
+qmail-vmailuser.0: \
+qmail-vmailuser.8
+ nroff -man qmail-vmailuser.8 > qmail-vmailuser.0
+
+qmail-vmailuser.8: \
+qmail-vmailuser.9 ../conf-home
+ cat qmail-vmailuser.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-vmailuser.8
+
+qreceipt.0: \
+qreceipt.1
+ nroff -man qreceipt.1 > qreceipt.0
+
+setforward.0: \
+setforward.1
+ nroff -man setforward.1 > setforward.0
+
+setmaillist.0: \
+setmaillist.1
+ nroff -man setmaillist.1 > setmaillist.0
+
+spfquery.0: \
+spfquery.8
+ nroff -man spfquery.8 > spfquery.0
+
+splogger.0: \
+splogger.8
+ nroff -man splogger.8 > splogger.0
+
+sqmail.0: \
+sqmail.7
+ nroff -man sqmail.7 > sqmail.0
+
+sqmail.7: \
+sqmail.9 ../package/version
+ cat sqmail.9 \
+ | sed s}VERSION}"`head -1 ../package/version`"}g \
+ > sqmail.7
+
+srsforward.0: \
+srsforward.1
+ nroff -man srsforward.1 > srsforward.0
+
+srsreverse.0: \
+srsreverse.8
+ nroff -man srsreverse.8 > srsreverse.0
+
+srsreverse.8: \
+srsreverse.9 ../conf-home
+ cat srsreverse.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > srsreverse.8
+
+tai64nfrac.0: \
+tai64nfrac.5
+ nroff -man tai64nfrac.5 > tai64nfrac.0
+
+tcp-environ.0: \
+tcp-environ.5
+ nroff -man tcp-environ.5 > tcp-environ.0
+
+xqp.0: \
+xqp.1
+ nroff -man xqp.1 > xqp.0
+
+xrecipient.0: \
+xrecipient.1
+ nroff -man xrecipient.1 > xrecipient.0
+
+xsender.0: \
+xsender.1
+ nroff -man xsender.1 > xsender.0
+
+clean: \
+TARGETS
+ rm -f `cat TARGETS`
+# gzip -q -d *.gz
+
diff --git a/man/Makefile.mandoc b/man/Makefile.mandoc
new file mode 100644
index 0000000..3369cbb
--- /dev/null
+++ b/man/Makefile.mandoc
@@ -0,0 +1,512 @@
+# Don't edit Makefile! Use ../conf-* for configuration.
+
+SHELL=/bin/sh
+
+default: modules docs dns
+
+addresses.0: \
+addresses.5
+ mandoc -man addresses.5 > addresses.0
+
+bouncesaying.0: \
+bouncesaying.1
+ mandoc -man bouncesaying.1 > bouncesaying.0
+
+columnt.0: \
+columnt.1
+ mandoc -man columnt.1 > columnt.0
+
+condredirect.0: \
+condredirect.1
+ mandoc -man condredirect.1 > condredirect.0
+
+dns:\
+dnscname.0 dnsfq.0 dnsip.0 dnsmxip.0 dnsptr.0 dnstxt.0 \
+hostname.0 ipmeprint.0
+
+dnscname.0: \
+dnscname.8
+ mandoc -man dnscname.8 > dnscname.0
+
+dnsfq.0: \
+dnsfq.8
+ mandoc -man dnsfq.8 > dnsfq.0
+
+dnsip.0: \
+dnsip.8
+ mandoc -man dnsip.8 > dnsip.0
+
+dnsmxip.0: \
+dnsmxip.8
+ mandoc -man dnsmxip.8 > dnsmxip.0
+
+dnsptr.0: \
+dnsptr.8
+ mandoc -man dnsptr.8 > dnsptr.0
+
+dnstxt.0: \
+dnstxt.8
+ mandoc -man dnstxt.8 > dnstxt.0
+
+datetime.0: \
+datetime.3
+ mandoc -man datetime.3 > datetime.0
+
+docs:\
+addresses.0 dot-qmail.0 envelopes.0 forgeries.0 mbox.0 maildir.0 \
+qmail-command.0 qmail-control.0 qmail-header.0 qmail-limits.0 \
+tcp-environ.0
+
+dot-qmail.0: \
+dot-qmail.5
+ mandoc -man dot-qmail.5 > dot-qmail.0
+
+dot-qmail.5: \
+dot-qmail.9 ../conf-home ../conf-break ../conf-spawn
+ cat dot-qmail.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > dot-qmail.5
+
+envelopes.0: \
+envelopes.5
+ mandoc -man envelopes.5 > envelopes.0
+
+except.0: \
+except.1
+ mandoc -man except.1 > except.0
+
+fastforward.0: \
+fastforward.1
+ mandoc -man fastforward.1 > fastforward.0
+
+forgeries.0: \
+forgeries.7
+ mandoc -man forgeries.7 > forgeries.0
+
+forward.0: \
+forward.1
+ mandoc -man forward.1 > forward.0
+
+hostname.0: \
+hostname.8
+ mandoc -man hostname.8 > hostname.0
+
+ipmeprint.0: \
+ipmeprint.8
+ mandoc -man ipmeprint.8 > ipmeprint.0
+
+maildir.0: \
+maildir.5
+ mandoc -man maildir.5 > maildir.0
+
+maildir2mbox.0: \
+maildir2mbox.1
+ mandoc -man maildir2mbox.1 > maildir2mbox.0
+
+maildirmake.0: \
+maildirmake.1
+ mandoc -man maildirmake.1 > maildirmake.0
+
+maildirwatch.0: \
+maildirwatch.1
+ mandoc -man maildirwatch.1 > maildirwatch.0
+
+mailsubj.0: \
+mailsubj.1
+ mandoc -man mailsubj.1 > mailsubj.0
+
+matchup.0: \
+matchup.1
+ mandoc -man matchup.1 > matchup.0
+
+mbox.0: \
+mbox.5
+ mandoc -man mbox.5 > mbox.0
+
+modules: \
+qmail-local.0 qmail-lspawn.0 qmail-getpw.0 qmail-remote.0 qmail-smtpam.0 \
+qmail-todo.0 qmail-vmailuser.0 qmail-authuser.0 qmail-postgrey.0 \
+qmail-rspawn.0 qmail-clean.0 qmail-send.0 qmail-start.0 splogger.0 spfquery.0 \
+qmail-queue.0 qmail-inject.0 mailsubj.0 qmail-showctl.0 qmail-newu.0 qmail-qmaint.0 \
+qmail-badmimetypes.0 qmail-badloadertypes.0 qmail-recipients.0 qmail-mfrules.0 \
+qmail-pw2u.0 qmail-qread.0 qmail-qstat.0 qmail-tcpto.0 qmail-tcpok.0 \
+qmail-pop3d.0 qmail-popup.0 qmail-qmqpc.0 qmail-qmqpd.0 qmail-qmtpd.0 \
+qmail-smtpd.0 qmail-newmrh.0 qmail-mrtg.0 qmail-users.0 qreceipt.0 qbiff.0 \
+forward.0 preline.0 condredirect.0 bouncesaying.0 except.0 maildirmake.0 \
+maildir2mbox.0 maildirwatch.0 sqmail.0 tai64nfrac.0 \
+columnt.0 matchup.0 xqp.0 xrecipient.0 xsender.0 newaliases.0 newinclude.0 \
+fastforward.0 printforward.0 printmaillist.0 setforward.0 setmaillist.0 \
+srsforward.0 srsreverse.0 \
+qmail-dkim.0 qmail-dksign.0 qmail-dkverify.0 \
+
+newaliases.0: \
+newaliases.1
+ mandoc -man newaliases.1 > newaliases.0
+
+newinclude.0: \
+newinclude.1
+ mandoc -man newinclude.1 > newinclude.0
+
+preline.0: \
+preline.1
+ mandoc -man preline.1 > preline.0
+
+printforward.0: \
+printforward.1
+ mandoc -man printforward.1 > printforward.0
+
+printmaillist.0: \
+printmaillist.1
+ mandoc -man printmaillist.1 > printmaillist.0
+
+qbiff.0: \
+qbiff.1
+ mandoc -man qbiff.1 > qbiff.0
+
+qmail-clean.0: \
+qmail-clean.8
+ mandoc -man qmail-clean.8 > qmail-clean.0
+
+qmail-authuser.0: \
+qmail-authuser.8
+ mandoc -man qmail-authuser.8 > qmail-authuser.0
+
+qmail-authuser.8: \
+qmail-authuser.9 ../conf-home
+ cat qmail-authuser.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-authuser.8
+
+qmail-badmimetypes.0: \
+qmail-badmimetypes.8
+ mandoc -man qmail-badmimetypes.8 > qmail-badmimetypes.0
+
+qmail-badmimetypes.8: \
+qmail-badmimetypes.9 ../conf-home
+ cat qmail-badmimetypes.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-badmimetypes.8
+
+qmail-badloadertypes.0: \
+qmail-badloadertypes.8
+ mandoc -man qmail-badloadertypes.8 > qmail-badloadertypes.0
+
+qmail-badloadertypes.8: \
+qmail-badloadertypes.9 ../conf-home
+ cat qmail-badloadertypes.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-badloadertypes.8
+
+qmail-command.0: \
+qmail-command.8
+ mandoc -man qmail-command.8 > qmail-command.0
+
+qmail-control.0: \
+qmail-control.5
+ mandoc -man qmail-control.5 > qmail-control.0
+
+qmail-control.5: \
+qmail-control.9 ../conf-home
+ cat qmail-control.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-control.5
+
+qmail-dkim.0: \
+qmail-dkim.8
+ mandoc -man qmail-dkim.8 > qmail-dkim.0
+
+qmail-dksign.0: \
+qmail-dksign.8
+ mandoc -man qmail-dksign.8 > qmail-dksign.0
+
+qmail-dksign.8: \
+qmail-dksign.9 ../conf-home
+ cat qmail-dksign.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-dksign.8
+
+qmail-dkverify.0: \
+qmail-dkverify.8
+ mandoc -man qmail-dkverify.8 > qmail-dkverify.0
+
+qmail-getpw.0: \
+qmail-getpw.8
+ mandoc -man qmail-getpw.8 > qmail-getpw.0
+
+qmail-getpw.8: \
+qmail-getpw.9 ../conf-home
+ cat qmail-getpw.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-getpw.8
+
+qmail-header.0: \
+qmail-header.5
+ mandoc -man qmail-header.5 > qmail-header.0
+
+qmail-inject.0: \
+qmail-inject.8
+ mandoc -man qmail-inject.8 > qmail-inject.0
+
+qmail-limits.0: \
+qmail-limits.7
+ mandoc -man qmail-limits.7 > qmail-limits.0
+
+qmail-limits.7: \
+qmail-limits.9 ../conf-home ../conf-break ../conf-spawn
+ cat qmail-limits.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > qmail-limits.7
+
+qmail-local.0: \
+qmail-local.8
+ mandoc -man qmail-local.8 > qmail-local.0
+
+qmail-log.0: \
+qmail-log.5
+ mandoc -man qmail-log.5 > qmail-log.0
+
+qmail-lspawn.0: \
+qmail-lspawn.8
+ mandoc -man qmail-lspawn.8 > qmail-lspawn.0
+
+qmail-mfrules.0: \
+qmail-mfrules.8
+ mandoc -man qmail-mfrules.8 > qmail-mfrules.0
+
+qmail-mfrules.8: \
+qmail-mfrules.9 ../conf-home
+ cat qmail-mfrules.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-mfrules.8
+
+qmail-mrtg.0: \
+qmail-mrtg.8
+ mandoc -man qmail-mrtg.8 > qmail-mrtg.0
+
+qmail-newmrh.0: \
+qmail-newmrh.8
+ mandoc -man qmail-newmrh.8 > qmail-newmrh.0
+
+qmail-newmrh.8: \
+qmail-newmrh.9 ../conf-home
+ cat qmail-newmrh.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-newmrh.8
+
+qmail-newu.0: \
+qmail-newu.8
+ mandoc -man qmail-newu.8 > qmail-newu.0
+
+qmail-newu.8: \
+qmail-newu.9 ../conf-home
+ cat qmail-newu.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-newu.8
+
+qmail-pop3d.0: \
+qmail-pop3d.8
+ mandoc -man qmail-pop3d.8 > qmail-pop3d.0
+
+qmail-popup.0: \
+qmail-popup.8
+ mandoc -man qmail-popup.8 > qmail-popup.0
+
+qmail-postgrey.0: \
+qmail-postgrey.8
+ mandoc -man qmail-postgrey.8 > qmail-postgrey.0
+
+qmail-pw2u.0: \
+qmail-pw2u.8
+ mandoc -man qmail-pw2u.8 > qmail-pw2u.0
+
+qmail-pw2u.8: \
+qmail-pw2u.9 ../conf-home
+ cat qmail-pw2u.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-pw2u.8
+
+qmail-qmqpc.0: \
+qmail-qmqpc.8
+ mandoc -man qmail-qmqpc.8 > qmail-qmqpc.0
+
+qmail-qmqpd.0: \
+qmail-qmqpd.8
+ mandoc -man qmail-qmqpd.8 > qmail-qmqpd.0
+
+qmail-qmtpd.0: \
+qmail-qmtpd.8
+ mandoc -man qmail-qmtpd.8 > qmail-qmtpd.0
+
+qmail-qread.0: \
+qmail-qread.8
+ mandoc -man qmail-qread.8 > qmail-qread.0
+
+qmail-qstat.0: \
+qmail-qstat.8
+ mandoc -man qmail-qstat.8 > qmail-qstat.0
+
+qmail-qmaint.0: \
+qmail-qmaint.8
+ mandoc -man qmail-qmaint.8 > qmail-qmaint.0
+
+qmail-queue.0: \
+qmail-queue.8
+ mandoc -man qmail-queue.8 > qmail-queue.0
+
+qmail-recipients.0: \
+qmail-recipients.8
+ mandoc -man qmail-recipients.8 > qmail-recipients.0
+
+qmail-recipients.8: \
+qmail-recipients.9 ../conf-home
+ cat qmail-recipients.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-recipients.8
+
+qmail-remote.0: \
+qmail-remote.8
+ mandoc -man qmail-remote.8 > qmail-remote.0
+
+qmail-rspawn.0: \
+qmail-rspawn.8
+ mandoc -man qmail-rspawn.8 > qmail-rspawn.0
+
+qmail-send.0: \
+qmail-send.8
+ mandoc -man qmail-send.8 > qmail-send.0
+
+qmail-send.8: \
+qmail-send.9 ../conf-home
+ cat qmail-send.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-send.8
+
+qmail-showctl.0: \
+qmail-showctl.8
+ mandoc -man qmail-showctl.8 > qmail-showctl.0
+
+qmail-smtpam.0: \
+qmail-smtpam.8
+ mandoc -man qmail-smtpam.8 > qmail-smtpam.0
+
+qmail-smtpd.0: \
+qmail-smtpd.8
+ mandoc -man qmail-smtpd.8 > qmail-smtpd.0
+
+qmail-start.0: \
+qmail-start.8
+ mandoc -man qmail-start.8 > qmail-start.0
+
+qmail-start.8: \
+qmail-start.9 ../conf-home ../conf-break ../conf-spawn
+ cat qmail-start.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > qmail-start.8
+
+qmail-tcpok.0: \
+qmail-tcpok.8
+ mandoc -man qmail-tcpok.8 > qmail-tcpok.0
+
+qmail-tcpto.0: \
+qmail-tcpto.8
+ mandoc -man qmail-tcpto.8 > qmail-tcpto.0
+
+qmail-todo.0: \
+qmail-todo.8
+ mandoc -man qmail-todo.8 > qmail-todo.0
+
+qmail-users.0: \
+qmail-users.5
+ mandoc -man qmail-users.5 > qmail-users.0
+
+qmail-users.5: \
+qmail-users.9 ../conf-home
+ cat qmail-users.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-users.5
+
+qmail-vmailuser.0: \
+qmail-vmailuser.8
+ mandoc -man qmail-vmailuser.8 > qmail-vmailuser.0
+
+qmail-vmailuser.8: \
+qmail-vmailuser.9 ../conf-home
+ cat qmail-vmailuser.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > qmail-vmailuser.8
+
+qreceipt.0: \
+qreceipt.1
+ mandoc -man qreceipt.1 > qreceipt.0
+
+setforward.0: \
+setforward.1
+ mandoc -man setforward.1 > setforward.0
+
+setmaillist.0: \
+setmaillist.1
+ mandoc -man setmaillist.1 > setmaillist.0
+
+spfquery.0: \
+spfquery.8
+ mandoc -man spfquery.8 > spfquery.0
+
+splogger.0: \
+splogger.8
+ mandoc -man splogger.8 > splogger.0
+
+sqmail.0: \
+sqmail.7
+ mandoc -man sqmail.7 > sqmail.0
+
+sqmail.7: \
+sqmail.9 ../package/version
+ cat sqmail.9 \
+ | sed s}VERSION}"`head -1 ../package/version`"}g \
+ > sqmail.7
+
+srsforward.0: \
+srsforward.1
+ mandoc -man srsforward.1 > srsforward.0
+
+srsreverse.0: \
+srsreverse.8
+ mandoc -man srsreverse.8 > srsreverse.0
+
+srsreverse.8: \
+srsreverse.9 ../conf-home
+ cat srsreverse.9 \
+ | sed s}SQMAIL}"`head -1 ../conf-home`"}g \
+ > srsreverse.8
+
+tai64nfrac.0: \
+tai64nfrac.5
+ mandoc -man tai64nfrac.5 > tai64nfrac.0
+
+tcp-environ.0: \
+tcp-environ.5
+ mandoc -man tcp-environ.5 > tcp-environ.0
+
+xqp.0: \
+xqp.1
+ mandoc -man xqp.1 > xqp.0
+
+xrecipient.0: \
+xrecipient.1
+ mandoc -man xrecipient.1 > xrecipient.0
+
+xsender.0: \
+xsender.1
+ mandoc -man xsender.1 > xsender.0
+
+clean: \
+TARGETS
+ rm -f `cat TARGETS`
+# gzip -q -d *.gz
+
diff --git a/man/TARGETS b/man/TARGETS
new file mode 100644
index 0000000..89773bb
--- /dev/null
+++ b/man/TARGETS
@@ -0,0 +1,105 @@
+addresses.0
+bouncesaying.0
+columnt.0
+condredirect.0
+datetime.0
+dot-qmail.0
+dot-qmail.5
+dnscname.0
+dnsfq.0
+dnsip.0
+dnsptr.0
+dnsmxip.0
+dnstxt.0
+envelopes.0
+except.0
+fastforward.0
+forgeries.0
+forward.0
+hostname.0
+ipmeprint.0
+maildir.0
+maildir2mbox.0
+maildirmake.0
+maildirwatch.0
+mailsubj.0
+matchup.0
+mbox.0
+newaliases.0
+newinclude.0
+preline.0
+printforward.0
+printmaillist.0
+qbiff.0
+qmail-authuser.0
+qmail-authuser.8
+qmail-badloadertypes.0
+qmail-badloadertypes.8
+qmail-badmimetypes.0
+qmail-badmimetypes.8
+qmail-clean.0
+qmail-command.0
+qmail-dksign.0
+qmail-dksign.8
+qmail-dkim.0
+qmail-dkverify.0
+qmail-getpw.0
+qmail-getpw.8
+qmail-header.0
+qmail-inject.0
+qmail-limits.0
+qmail-limits.7
+qmail-local.0
+qmail-lspawn.0
+qmail-mfrules.0
+qmail-mfrules.8
+qmail-mrtg.0
+qmail-newmrh.0
+qmail-newmrh.8
+qmail-newu.0
+qmail-newu.8
+qmail-pop3d.0
+qmail-popup.0
+qmail-postgrey.0
+qmail-pw2u.0
+qmail-pw2u.8
+qmail-qmqpc.0
+qmail-qmqpd.0
+qmail-qmtpd.0
+qmail-qread.0
+qmail-qstat.0
+qmail-qmaint.0
+qmail-queue.0
+qmail-recipients.0
+qmail-recipients.8
+qmail-remote.0
+qmail-rspawn.0
+qmail-send.0
+qmail-send.8
+qmail-showctl.0
+qmail-smtpam.0
+qmail-smtpd.0
+qmail-start.0
+qmail-start.8
+qmail-tcpok.0
+qmail-tcpto.0
+qmail-todo.0
+qmail-users.0
+qmail-users.5
+qmail-vmailuser.0
+qmail-vmailuser.8
+qreceipt.0
+setforward.0
+setmaillist.0
+spfquery.0
+splogger.0
+sqmail.0
+sqmail.7
+srsforward.0
+srsreverse.0
+srsreverse.8
+tai64nfrac.0
+tcp-environ.0
+xqp.0
+xrecipient.0
+xsender.0
diff --git a/man/addresses.5 b/man/addresses.5
new file mode 100644
index 0000000..72a234f
--- /dev/null
+++ b/man/addresses.5
@@ -0,0 +1,260 @@
+.TH s/qmail: addresses 5
+.SH "NAME"
+addresses \- formats for Internet mail addresses
+.SH "INTRODUCTION"
+A
+.B mail address
+is a string of characters containing @.
+
+Every mail address has a
+.B local part
+and a
+.B domain part\fR.
+The domain part is everything after the final @.
+The local part is everything before.
+
+For example, the mail addresses
+
+.EX
+ God@heaven.af.mil
+ @heaven.af.mil
+ @at@@heaven.af.mil
+.EE
+
+all have domain part
+.BR heaven.af.mil .
+The local parts are
+.BR God ,
+empty,
+and
+.BR @at@ .
+
+Some domains have owners.
+It is up to the owner of
+.B heaven.af.mil
+to say how mail messages will be delivered to addresses with domain part
+.BR heaven.af.mil .
+
+The domain part of an address is interpreted without regard to case, so
+
+.EX
+ God@heaven.af.mil
+.br
+ God@HEAVEN.AF.MIL
+.br
+ God@Heaven.AF.Mil
+.EE
+
+all refer to the same domain.
+
+There is one exceptional address that does not contain an @:
+namely, the empty string.
+The empty string cannot be used as a recipient address.
+It can be used as a sender address so that
+the real sender doesn't receive bounces.
+.SH "QMAIL EXTENSIONS"
+The
+.B qmail
+system allows several further types of addresses in mail envelopes.
+
+First, an envelope recipient address without an @ is interpreted as being at
+.IR envnoathost .
+For example, if
+.I envnoathost
+is
+.BR heaven.af.mil ,
+the address
+.B God
+will be rewritten as
+.BR God@heaven.af.mil .
+
+Second, the address
+.B #@[]
+is used as an envelope sender address for double bounces.
+
+Third, envelope sender addresses of the form
+.I pre\fB@\fIhost\fB-@[]
+are used to support variable envelope return paths (VERPs).
+.B qmail-send
+will rewrite
+.I pre\fB@\fIhost\fB-@[]
+as
+.I prerecip\fB=\fIdomain\fB@\fIhost
+for deliveries to
+.IR recip\fB@\fIdomain .
+Bounces directly from
+.B qmail-send
+will come back to
+.IR pre\fB@\fIhost .
+.SH "CHOOSING MAIL ADDRESSES"
+Here are some suggestions on choosing mail addresses for the Internet.
+
+Do not use non-ASCII characters.
+Under RFC 822 and RFC 821,
+these characters cannot be used in mail headers or in SMTP commands.
+In practice, they are regularly corrupted.
+
+Do not use ASCII control characters.
+NUL is regularly corrupted.
+CR and LF cannot be used in some combinations
+and are corrupted in all.
+None of these characters are usable on business cards.
+
+Avoid spaces and the characters
+
+.EX
+ \\"<>()[],;:
+.EE
+
+These all require quoting in mail headers and in SMTP.
+Many existing mail programs do not handle quoting properly.
+
+Do not use @ in a local part.
+@ requires quoting in mail headers and in SMTP.
+Many programs incorrectly look for the first @,
+rather than the last @,
+to find the domain part of an address.
+
+In a local part,
+do not use two consecutive dots, a dot at the beginning, or a dot at the end.
+Any of these would require quoting in mail headers.
+
+Do not use an empty local part; it cannot appear in SMTP commands.
+
+Avoid local parts longer than 64 characters.
+
+Be wary of uppercase letters in local parts.
+Some mail programs (and users!) will incorrectly convert
+.B God@heaven.af.mil
+to
+.BR god@heaven.af.mil .
+
+Be wary of the following characters:
+
+.EX
+ $&!#~`'^*|{}
+.EE
+
+Some users will not know
+how to feed these characters safely to their mail programs.
+
+In domain names, stick to letters, digits, dash, and dot.
+One popular DNS resolver has,
+under the banner of security,
+recently begun destroying domain names
+that contain certain other characters,
+including underscore.
+Exception: A dotted-decimal IP address in brackets,
+such as
+.BR [127.0.0.1] ,
+identifies a domain owned by whoever owns the host at that IP address,
+and can be used safely.
+
+In a domain name,
+do not use two consecutive dots,
+a dot at the beginning,
+or a dot at the end.
+This means that,
+when a domain name is broken down into components separated by dots,
+there are no empty components.
+
+Always use at least one dot in a domain name.
+If you own the
+.B mil
+domain,
+don't bother using the address
+.BR root@mil ;
+most users will be unable to send messages to that address.
+Same for the root domain.
+
+Avoid domain names longer than 64 characters.
+.SH "ENCODED ADDRESSES IN SMTP COMMANDS"
+RFC 821 defines an encoding of mail addresses in SMTP.
+For example, the addresses
+
+.EX
+ God@heaven.af.mil
+.br
+ a"quote@heaven.af.mil
+.br
+ The Almighty.One@heaven.af.mil
+.EE
+
+could be encoded in RCPT commands as
+
+.EX
+ RCPT TO:<God@heaven.af.mil>
+.br
+ RCPT TO:<a\\"quote@heaven.af.mil>
+.br
+ RCPT TO:<The\\ Almighty.One@heaven.af.mil>
+.EE
+
+There are several restrictions in RFC 821
+on the mail addresses that can be used over SMTP.
+Non-ASCII characters are prohibited.
+The local part must not be empty.
+The domain part must be a sequence of elements separated by dots,
+where each element is either a component,
+a sequence of digits preceded by #,
+or a dotted-decimal IP address surrounded by brackets.
+The only allowable characters in components are
+letters, digits, and dashes.
+Every component must (believe it or not)
+have at least three characters;
+the first character must be a letter;
+the last character must not be a hyphen.
+.SH "ENCODED ADDRESSES IN MAIL HEADERS"
+RFC 822 defines an encoding of mail addresses
+in certain header fields in a mail message.
+For example, the addresses
+
+.EX
+ God@heaven.af.mil
+.br
+ a"quote@heaven.af.mil
+.br
+ The Almighty.One@heaven.af.mil
+.EE
+
+could be encoded in a
+.B To
+field as
+
+.EX
+ To: God@heaven.af.mil,
+.br
+ <@brl.mil:"a\\"quote"@heaven.af.mil>,
+.br
+ "The Almighty".One@heaven.af.mil
+.EE
+
+or perhaps
+
+.EX
+ To: < "God"@heaven .af.mil>,
+.br
+ "a\\"quote" (Who?) @ heaven . af. mil
+.br
+ , God<"The Almighty.One"@heaven.af.mil>
+.EE
+
+There are several restrictions on the mail addresses that can
+be used in these header fields.
+Non-ASCII characters are prohibited.
+The domain part must be a sequence of elements separated by dots,
+where each element either (1) begins with [ and ends with ]
+or (2) is a nonempty string of printable ASCII characters
+not including any of
+
+.EX
+ \\".<>()[],;:
+.EE
+
+and not including space.
+.SH "SEE ALSO"
+envelopes(5),
+qmail-header(5),
+qmail-inject(8),
+qmail-remote(8),
+qmail-smtpd(8)
diff --git a/man/bouncesaying.1 b/man/bouncesaying.1
new file mode 100644
index 0000000..9f46b67
--- /dev/null
+++ b/man/bouncesaying.1
@@ -0,0 +1,71 @@
+.TH s/qmail: bouncesaying 1
+.SH NAME
+bouncesaying \- perhaps bounce each incoming message
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |bouncesaying
+.I error
+[
+.I program
+[
+.I arg ...
+]
+]
+.SH DESCRIPTION
+.B bouncesaying
+feeds each new mail message to
+.I program
+with the given arguments.
+If
+.I program
+exits 0,
+.B bouncesaying
+prints
+.I error
+and bounces the message.
+
+If
+.I program
+exits 111,
+.B bouncesaying
+exits 111,
+so delivery will be retried later.
+
+If
+.I program
+exits anything else
+(or does not exist),
+.B bouncesaying
+exits 0,
+so the rest of
+.B .qmail
+will be processed as usual.
+
+Note that
+it is not safe for
+.I program
+to fork a child that
+reads the message in the background.
+
+If
+.I program
+is not supplied,
+.B bouncesaying
+always bounces the message:
+
+.EX
+ |bouncesaying 'This address no longer accepts mail.'
+.EE
+
+.B WARNING:
+If you create a
+.B .qmail
+file to enable
+.BR bouncesaying ,
+make sure to also add a line specifying delivery to your normal mailbox.
+.SH "SEE ALSO"
+condredirect(1),
+except(1),
+dot-qmail(5),
+qmail-command(8)
diff --git a/man/columnt.1 b/man/columnt.1
new file mode 100644
index 0000000..24eeeef
--- /dev/null
+++ b/man/columnt.1
@@ -0,0 +1,29 @@
+.TH s/qmail: columnt 1
+.SH NAME
+columnt \- align columns in a table
+.SH SYNTAX
+.B columnt
+.SH DESCRIPTION
+.B columnt
+reads a table of whitespace-separated lines.
+
+.B columnt
+then prints the table,
+changing the spacing so that
+the first column takes the same amount of space in every line,
+the second column takes the same amount of space in every line,
+etc.
+
+In the
+.B columnt
+output,
+all columns except the last are right-justified;
+the last column is left-justified.
+There are two spaces between adjacent columns.
+
+.B columnt
+needs enough memory to read the entire input.
+Other than this,
+it has no limits on line length or on the number of columns.
+.SH "SEE ALSO"
+column(1)
diff --git a/man/condredirect.1 b/man/condredirect.1
new file mode 100644
index 0000000..b9418db
--- /dev/null
+++ b/man/condredirect.1
@@ -0,0 +1,63 @@
+.TH s/qmail: condredirect 1
+.SH NAME
+condredirect \- perhaps redirect mail to another address
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |condredirect
+.I newaddress
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B condredirect
+feeds each new mail message to
+.I program
+with the given arguments.
+If
+.I program
+exits 0,
+.B condredirect
+forwards the mail message to
+.IR newaddress ,
+and then exits 99,
+so further commands in
+.B .qmail
+are ignored.
+
+If
+.I program
+exits 111,
+.B condredirect
+exits 111,
+so delivery will be retried later.
+
+If
+.I program
+exits anything else
+(or does not exist),
+.B condredirect
+exits 0,
+so the rest of
+.B .qmail
+will be processed as usual.
+
+Note that
+it is not safe for
+.I program
+to fork a child that
+reads the message in the background.
+
+.B WARNING:
+If you create a
+.B .qmail
+file to enable
+.BR condredirect ,
+make sure to also add a line specifying delivery to your normal mailbox.
+.SH "SEE ALSO"
+bouncesaying(1),
+except(1),
+dot-qmail(5),
+qmail-command(8),
+qmail-queue(8)
diff --git a/man/datetime.3 b/man/datetime.3
new file mode 100644
index 0000000..f62c02d
--- /dev/null
+++ b/man/datetime.3
@@ -0,0 +1,73 @@
+.TH s/qmail: datetime 3
+.SH NAME
+datetime \- convert between TAI labels and seconds
+.SH SYNTAX
+.B #include <datetime.h>
+
+void \fBdatetime_tai\fP(&\fIdt\fR,\fIt\fR);
+
+datetime_sec \fBdatetime_untai\fP(&\fIdt\fR);
+
+struct datetime \fIdt\fR;
+.br
+datetime_sec \fIt\fR;
+.SH DESCRIPTION
+International Atomic Time, TAI,
+is the fundamental unit for time measurements.
+TAI has one label for every second of real time,
+without complications such as leap seconds.
+
+A
+struct datetime
+variable,
+such as
+.IR dt ,
+stores a TAI label.
+.I dt\fB.year
+is the year number minus 1900;
+.I dt\fB.mon
+is the month number, from 0 (January) through 11 (December);
+.I dt\fB.mday
+is the day of the month, from 1 through 31;
+.I dt\fB.hour
+is the hour, from 0 through 23;
+.I dt\fB.min
+is the minute, from 0 through 59;
+.I dt\fB.sec
+is the second, from 0 through 59;
+.I dt\fB.wday
+is the day of the week, from 0 (Sunday) through 6 (Saturday);
+.I dt\fB.yday
+is the day of the year, from 0 through 365.
+
+The
+.B datetime
+library supports more convenient TAI manipulation with
+the datetime_sec type.
+A datetime_sec value, such as
+.IR t ,
+is an integer referring to the
+.IR t th
+second after the beginning of 1970 TAI.
+The first second of 1970 TAI was 0;
+the next second was 1;
+the last second of 1969 TAI was -1.
+The difference between two datetime_sec values is a number
+of real-time seconds.
+
+.B datetime_tai
+converts a datetime_sec to a TAI label.
+
+.B datetime_untai
+reads a TAI label
+(specifically
+.IR dt\fB.year ,
+.IR dt\fB.mon ,
+.IR dt\fB.mday ,
+.IR dt\fB.hour ,
+.IR dt\fB.min ,
+and
+.IR dt\fB.sec )
+and returns a datetime_sec.
+.SH "SEE ALSO"
+now(3)
diff --git a/man/dnscname.8 b/man/dnscname.8
new file mode 100644
index 0000000..7fd3889
--- /dev/null
+++ b/man/dnscname.8
@@ -0,0 +1,35 @@
+.TH s/qmail: dnscname 8
+.SH NAME
+dnscname
+.SH SYNOPSIS
+.B dnscname
+.I fqdn
+.SH DESCRIPTION
+.B dnscame
+takes the given
+.I fqdn
+for a host and employs an one-stage
+.I CNAME
+DNS lookup for
+.IR fqdn .
+The retrieved DNS name could instead be an alias,
+rather than a \fIcanonical name\fR.
+Use
+.B dnsfq
+to evaluate the entire
+.I CNAME
+chain.
+.SH "EXIT CODES"
+.B dnscname
+return
+.I 0
+on success,
+.I 1
+in case no CNAME was found, and
+.I 111
+in case of memory errors.
+.SH "SEE ALSO"
+dnsfq(8),
+dnsmxip(8),
+dnsptr(8),
+dnstxt(8).
diff --git a/man/dnsfq.8 b/man/dnsfq.8
new file mode 100644
index 0000000..4773fcb
--- /dev/null
+++ b/man/dnsfq.8
@@ -0,0 +1,34 @@
+.TH s/qmail: dnsfq 8
+.SH NAME
+dnsfq
+.SH SYNOPSIS
+.B dnsfq
+.I fqdn
+.SH DESCRIPTION
+.B fqdns
+takes the given
+.I fqdn
+for a host and employs a
+.I CNAME
+DNS lookup while finally retrieving the
+.I AAAA
+and
+.I A
+record following the chain of potential alias names.
+On output, the entire chain of nested DNS information
+is displayed together with the retrieved IP(v4|v6)
+addresses.
+.SH "EXIT CODES"
+.B dnsfq
+returns
+.I 0
+on success,
+.I 1
+if DNS query errors did occure, and
+.I 111
+in case of memory errors.
+.SH "SEE ALSO"
+dnscname(8),
+dnsmxip(8),
+dnsptr(8),
+dnstxt(8).
diff --git a/man/dnsip.8 b/man/dnsip.8
new file mode 100644
index 0000000..eaa9930
--- /dev/null
+++ b/man/dnsip.8
@@ -0,0 +1,31 @@
+.TH s/qmail: dnsip 8
+.SH NAME
+dnsip
+.SH SYNOPSIS
+.B dnsip
+.I fqdn .
+.SH DESCRIPTION
+.B dnsip
+does a DNS
+.I AAAA
+and
+.I A
+lookup and displays the retrieved
+.I IPv6
+and
+.I IPv4
+addresses on one line for the given
+.IR fqdn .
+.SH "EXIT CODES"
+.B dnsip
+always returns
+.I 0
+except of
+.I 111
+in case of memory errors.
+.SH "SEE ALSO"
+dnscname(8),
+dnsmxip(8),
+dnsfq(8),
+dnsptr(8),
+dnstxt(8).
diff --git a/man/dnsmxip.8 b/man/dnsmxip.8
new file mode 100644
index 0000000..cc3250d
--- /dev/null
+++ b/man/dnsmxip.8
@@ -0,0 +1,42 @@
+.TH s/qmail: dnsmxip 8
+.SH NAME
+dnsmxip
+.SH SYNOPSIS
+.B dnsmxip
+.I fqdn
+.SH DESCRIPTION
+.B dnsmxip
+takes the given
+.I fqdn
+as domain name and employs a
+.I MX
+lookup for
+.I fqdn
+while evaluating for the retrieved MX host(s) the respective
+.I AAAA
+and
+.I A
+address(es).
+
+On output, for each MX
+.I host
+its provided
+.I weight
+and the respective
+.I AAAA
+and
+.I A
+addresses (in perenthesis) are displayed on separate lines.
+.SH "EXIT CODES"
+.B dnsmxip
+returns
+.I 0
+and eventually
+.I 1
+in case of DNS query errors.
+.SH "SEE ALSO"
+dnscname(8),
+dnsip(8),
+dnsfq(8),
+dnsptr(8),
+dnstxt(8).
diff --git a/man/dnsptr.8 b/man/dnsptr.8
new file mode 100644
index 0000000..c3df614
--- /dev/null
+++ b/man/dnsptr.8
@@ -0,0 +1,27 @@
+.TH s/qmail: dnsptr 8
+.SH NAME
+dnsptr
+.SH SYNOPSIS
+.B dnsptr
+.I IPv4
+or
+.IR IPv6 .
+.SH DESCRIPTION
+.B dnsptr
+does a DNS
+.I PTR
+lookup and displays the retrieved
+.IR fqdn .
+.SH "EXIT CODES"
+.B dnsptr
+always returns
+.I 0
+except for wrong IP address
+formats while returning
+.IR 100 .
+.SH "SEE ALSO"
+dnscname(8),
+dnsmxip(8),
+dnsfq(8),
+dnsip(8),
+dnstxt(8).
diff --git a/man/dnstlsa.8 b/man/dnstlsa.8
new file mode 100644
index 0000000..879ed39
--- /dev/null
+++ b/man/dnstlsa.8
@@ -0,0 +1,51 @@
+.TH s/qmail: dnstlsa 8
+.SH NAME
+dnstlsa
+.SH SYNOPSIS
+.B dnstlsa
+.I [-v] [-p port] [-u(dp)|-t(cp)] fqdn
+.SH DESCRIPTION
+.B dnstlsa
+uses the
+.I fqdn
+for a host employing a
+DNS query for a synthesized hostname given as
+.I _port._[tcp|udp].fqdn
+while doing an initial CNAME resolution
+followed by a TLSA query
+and displays the result(s).
+If
+.I -p\ port
+is missing
+.I port\ 25
+is assumed.
+If either
+.I -u
+or
+.I -t
+is omitted,
+.I tcp
+is used.
+Each entry is shown on one line, telling
+.IR Usage ,
+.IR Selector ,
+.IR Matching\ Type
+together with the hex-encoded fingerprint or certificate.
+
+In verbose mode
+.I -v
+the synthezised record is displayed as well.
+.SH "EXIT CODES"
+.B dnstlsa
+returns
+.I 0
+on success,
+.I 1
+for DNS query errors, and
+.I 111
+in case of memory errors.
+.SH "SEE ALSO"
+dnstxt(8),
+dnsfq(8),
+dnsmxip(8),
+dnsptr(8).
diff --git a/man/dnstxt.8 b/man/dnstxt.8
new file mode 100644
index 0000000..933f06f
--- /dev/null
+++ b/man/dnstxt.8
@@ -0,0 +1,29 @@
+.TH s/qmail: dnstxt 8
+.SH NAME
+dnstxt
+.SH SYNOPSIS
+.B dnstxt
+.I fqdn
+.SH DESCRIPTION
+.B dnstxt
+takes the given
+.I fqdn
+for a host employing a
+.I TXT
+DNS lookup for
+.I fqdn
+and displays the result(s).
+.SH "EXIT CODES"
+.B dnstxt
+returns
+.I 0
+on success,
+.I 1
+for DNS query errors, and
+.I 111
+in case of memory errors.
+.SH "SEE ALSO"
+dnscname(8),
+dnsfq(8),
+dnsmxip(8),
+dnsptr(8).
diff --git a/man/dot-qmail.9 b/man/dot-qmail.9
new file mode 100644
index 0000000..f01f24e
--- /dev/null
+++ b/man/dot-qmail.9
@@ -0,0 +1,396 @@
+.TH s/qmail: dot-qmail 5
+.SH NAME
+dot-qmail \- control the delivery of mail messages
+.SH DESCRIPTION
+Normally the
+.B qmail-local
+program delivers each incoming message to your system mailbox,
+.IR homedir\fB/Mailbox ,
+where
+.I homedir
+is your home directory.
+
+It can instead
+write the mail to a different file or directory,
+forward it to another address,
+distribute it to a mailing list,
+or even execute programs,
+all under your control.
+.SH "THE QMAIL FILE"
+To change
+.BR qmail-local 's
+behavior, set up a
+.B .qmail
+file in your home directory.
+
+.B .qmail
+contains one or more lines.
+Each line is a delivery instruction.
+.B qmail-local
+follows each instruction in turn.
+There are five types of delivery instructions:
+(1) comment; (2) program; (3) forward; (4) mbox; (5) maildir.
+.TP 5
+(1)
+A comment line begins with a number sign:
+
+.EX
+ # this is a comment
+.EE
+
+.B qmail-local
+ignores the line.
+.TP 5
+(2)
+A program line begins with a vertical bar:
+
+.EX
+ |preline /usr/ucb/vacation djb
+.EE
+
+.B qmail-local
+takes the rest of the line as a command to supply to
+.BR sh .
+See
+.B qmail-command(8)
+for further information.
+.TP 5
+(3)
+A forward line begins with an ampersand:
+
+.EX
+ &me@new.job.com
+.EE
+
+.B qmail-local
+takes the rest of the line as a mail address;
+it uses
+.B qmail-queue
+to forward the message to that address.
+The address must contain a fully qualified domain name;
+it must not contain extra spaces, angle brackets, or comments:
+
+.EX
+ # the following examples are WRONG
+.br
+ &me@new
+.br
+ &<me@new.job.com>
+.br
+ & me@new.job.com
+.br
+ &me@new.job.com (New Address)
+.EE
+
+If the address begins with a letter or number,
+you may leave out the ampersand:
+
+.EX
+ me@new.job.com
+.EE
+
+Note that
+.B qmail-local
+omits its new
+.B Return-Path
+line when forwarding messages.
+.TP 5
+(4)
+An
+.I mbox
+line begins with a slash or dot,
+and does not end with a slash:
+
+.EX
+ /home/djb/Mailbox.sos
+.EE
+
+.B qmail-local
+takes the entire line as a filename.
+It appends the mail message to that file,
+using
+.BR flock -style
+file locking if possible.
+.B qmail-local
+stores the mail message in
+.I mbox
+format, as described in
+.BR mbox(5) .
+
+.B WARNING:
+On many systems,
+anyone who can read a file can
+.B flock
+it, and thus hold up
+.BR qmail-local 's
+delivery forever.
+Do not deliver mail to a publicly accessible file!
+
+If
+.B qmail-local
+is able to lock the file, but has trouble writing to it
+(because, for example, the disk is full),
+it will truncate the file back to its original length.
+However, it cannot prevent mailbox corruption if the system
+crashes during delivery.
+.TP 5
+(5)
+A
+.I maildir
+line begins with a slash or dot,
+and ends with a slash:
+
+.EX
+ /home/djb/Maildir/
+.EE
+
+.B qmail-local
+takes the entire line as the name of a directory in
+.I maildir
+format.
+It reliably stores the incoming message in that directory.
+See
+.B maildir(5)
+for more details.
+.PP
+If
+.B .qmail
+has the execute bit set,
+it must not contain any
+program lines,
+.I mbox
+lines,
+or
+.I maildir
+lines.
+If
+.B qmail-local
+sees any such lines,
+it will stop and indicate a temporary failure.
+
+If
+.B .qmail
+is completely empty (0 bytes long), or does not exist,
+.B qmail-local
+follows the
+.I defaultdelivery
+instructions set by your system administrator;
+normally
+.I defaultdelivery
+is
+.BR ./Mailbox ,
+so
+.B qmail-local
+appends the mail message to
+.B Mailbox
+in
+.I mbox
+format.
+
+.B .qmail
+may contain extra spaces and tabs at the end of a line.
+Blank lines are allowed, but not for the first line of
+.BR .qmail .
+
+If
+.B .qmail
+is world-writable or group-writable,
+.B qmail-local
+stops and indicates a temporary failure.
+.SH "SAFE QMAIL EDITING"
+Incoming messages can arrive at any moment.
+If you want to safely edit your
+.B .qmail
+file, first set the sticky bit on your home directory:
+
+.EX
+ chmod +t $HOME
+.EE
+
+.B qmail-local
+will temporarily defer delivery of any message to you
+if your home directory is sticky
+(or group-writable or other-writable,
+which should never happen).
+Make sure to
+
+.EX
+ chmod -t $HOME
+.EE
+
+when you are done!
+It's a good idea to test your new
+.B .qmail
+file as follows:
+
+.EX
+ qmail-local -n $USER ~ $USER '' '' '' '' ./Mailbox
+.EE
+
+.SH "EXTENSION ADDRESSES"
+In the
+.B qmail
+system,
+you control all local addresses of the form
+.IR user\fBBREAK\fIanything ,
+as well as the address
+.I user
+itself,
+where
+.I user
+is your account name.
+Delivery to
+.I user\fBBREAK\fIanything
+is controlled by the file
+.IR homedir/\fB.qmail\-\fIanything .
+(These rules may be changed by the system administrator;
+see
+.BR qmail-users (5).)
+
+The
+.B alias
+user controls all other addresses.
+Delivery to
+.I local
+is controlled by the file
+.IR homedir/\fB.qmail\-\fIlocal ,
+where
+.I homedir
+is
+.BR alias 's
+home directory.
+
+In the following description,
+.B qmail-local
+is handling a message addressed to
+.IR local@domain ,
+where
+.I local
+is controlled by
+.BR .qmail\-\fIext .
+Here is what it does.
+
+If
+.B .qmail\-\fIext
+is completely empty,
+.B qmail-local
+follows the
+.I defaultdelivery
+instructions set by your system administrator.
+
+If
+.B .qmail\-\fIext
+doesn't exist,
+.B qmail-local
+will try some default
+.B .qmail
+files.
+For example,
+if
+.I ext
+is
+.BR foo-bar ,
+.B qmail-local
+will try first
+.BR .qmail-foo-bar ,
+then
+.BR .qmail-foo-default ,
+and finally
+.BR .qmail-default .
+If none of these exist,
+.B qmail-local
+will bounce the message.
+(Exception: for the basic
+.I user
+address,
+.B qmail-local
+treats a nonexistent
+.B .qmail
+the same as an empty
+.BR .qmail .)
+
+.B WARNING:
+For security,
+.B qmail-local
+replaces any dots in
+.I ext
+with colons before checking
+.BR .qmail\-\fIext .
+For convenience,
+.B qmail-local
+converts any uppercase letters in
+.I ext
+to lowercase.
+
+When
+.B qmail-local
+forwards a message as instructed in
+.B .qmail\-\fIext
+(or
+.BR .qmail-default ),
+it checks whether
+.B .qmail\-\fIext\fB-owner\fP
+exists.
+If so,
+it uses
+.I local\fB-owner@\fIdomain
+as the envelope sender for the forwarded message.
+Otherwise it retains the envelope sender of the original message.
+Exception:
+.B qmail-local
+always retains the original envelope sender
+if it is the empty address or
+.BR #@[] ,
+i.e., if this is a bounce message.
+
+.B qmail-local
+also supports
+.B variable envelope return paths
+(VERPs):
+if
+.B .qmail\-\fIext\fB-owner\fP
+and
+.B .qmail\-\fIext\fB-owner-default\fP
+both exist, it uses
+.I local\fB\-owner\-@\fIdomain\fB-@[]
+as the envelope sender.
+This will cause a recipient
+.I recip\fB@\fIreciphost
+to see an envelope sender of
+.IR local\fB\-owner\-\fIrecip\fB=\fIreciphost\fB@\fIdomain .
+.SH "ERROR HANDLING"
+If a delivery instruction fails,
+.B qmail-local
+stops immediately and reports failure.
+.B qmail-local
+handles forwarding after all other instructions,
+so any error in another type of delivery will prevent all forwarding.
+
+If a program returns exit code 99,
+.B qmail-local
+ignores all succeeding lines in
+.BR .qmail ,
+but it still pays attention to previous forward lines.
+
+To set up independent instructions,
+where a temporary or permanent failure in one instruction
+does not affect the others,
+move each instruction into a separate
+.B .qmail\-\fIext
+file, and set up a central
+.B .qmail
+file that forwards to all of the
+.BR .qmail\-\fIext s.
+Note that
+.B qmail-local
+can handle any number of forward lines simultaneously.
+
+.SH "SEE ALSO"
+envelopes(5),
+maildir(5),
+mbox(5),
+qmail-users(5),
+qmail-local(8),
+qmail-command(8),
+qmail-queue(8),
+qmail-lspawn(8)
diff --git a/man/envelopes.5 b/man/envelopes.5
new file mode 100644
index 0000000..9f06ed7
--- /dev/null
+++ b/man/envelopes.5
@@ -0,0 +1,231 @@
+.TH s/qmail: envelopes 5
+.SH "NAME"
+envelopes \- sender/recipient lists attached to messages
+.SH "INTRODUCTION"
+Electronic mail messages are delivered in
+.IR envelopes .
+
+An envelope lists a
+.I sender
+and one or more
+.IR recipients .
+Usually these
+envelope addresses are the same
+as the addresses listed in the message header:
+
+.EX
+ (envelope) from djb to root
+.br
+ From: djb
+.br
+ To: root
+.EE
+
+In more complicated situations, though,
+the envelope addresses may differ from the header addresses.
+.SH "ENVELOPE EXAMPLES"
+When a message is delivered to
+several people at different locations,
+it is first photocopied
+and placed into several envelopes:
+
+.EX
+ (envelope) from djb to root
+.br
+ From: djb Copy #1 of message
+.br
+ To: root, god@brl.mil
+.EE
+
+.EX
+ (envelope) from djb to god@brl.mil
+.br
+ From: djb Copy #2 of message
+.br
+ To: root, god@brl.mil
+.EE
+
+When a message is delivered
+to several people at the same location,
+the sender doesn't have to photocopy it.
+He can instead stuff it into
+one envelope with several addresses;
+the recipients will make the photocopy:
+
+.EX
+ (envelope) from djb to god@brl.mil, angel@brl.mil
+.br
+ From: djb
+.br
+ To: god@brl.mil, angel@brl.mil, joe, frde
+.EE
+
+Bounced mail is sent back to the envelope sender address.
+The bounced mail doesn't list an envelope sender,
+so bounce loops are impossible:
+
+.EX
+ (envelope) from <> to djb
+.br
+ From: MAILER-DAEMON
+.br
+ To: djb
+.br
+ Subject: unknown user frde
+.EE
+
+The recipient of a message may make another copy
+and forward it in a new envelope:
+
+.EX
+ (envelope) from djb to joe
+.br
+ From: djb Original message
+.br
+ To: joe
+.EE
+
+.EX
+ (envelope) from joe to fred
+.br
+ From: djb Forwarded message
+.br
+ To: joe
+.EE
+
+A mailing list works almost the same way:
+
+.EX
+ (envelope) from djb to sos-list
+.br
+ From: djb Original message
+.br
+ To: sos-list
+.EE
+
+.EX
+ (envelope) from sos-owner to god@brl.mil
+.br
+ From: djb Forwarded message
+.br
+ To: sos-list to recipient #1
+.EE
+
+.EX
+ (envelope) from sos-owner to frde
+.br
+ From: djb Forwarded message
+.br
+ To: sos-list to recipient #2
+.EE
+
+Notice that the mailing list is set up
+to replace the envelope sender with something new,
+.BR sos-owner .
+So bounces will come back to
+.BR sos-owner :
+
+.EX
+ (envelope) from <> to sos-owner
+.br
+ From: MAILER-DAEMON
+.br
+ To: sos-owner
+.br
+ Subject: unknown user frde
+.EE
+
+It's a good idea to set up an extra address,
+.BR sos-owner ,
+like this:
+the original envelope sender (\fBdjb\fP)
+has no way to fix bad
+.B sos-list
+addresses,
+and of course bounces must not be sent to
+.B sos-list
+itself.
+.SH "HOW ENVELOPE ADDRESSES ARE STORED"
+Envelope sender and envelope recipient addresses
+are transmitted and recorded in several ways.
+
+When a user injects mail through
+.BR qmail-inject ,
+he can supply a
+.B Return-Path
+line or a
+.B \-f
+option for the envelope sender;
+by default the envelope sender is his login name.
+The envelope recipient addresses can be taken
+from the command line or from various header fields,
+depending on the options to
+.BR qmail-inject .
+Similar comments apply to
+.BR sendmail .
+
+When a message is transferred from one machine to another through SMTP,
+the envelope sender is given in a
+.B MAIL FROM
+command,
+the envelope recipients are given in
+.B RCPT TO
+commands,
+and the message is supplied separately by a
+.B DATA
+command.
+
+When a message is delivered by
+.B qmail
+to a single local recipient,
+.B qmail-local
+records the recipient in
+.B Delivered-To
+and the envelope sender in
+.BR Return-Path .
+It uses
+.B Delivered-To
+to detect mail forwarding loops.
+
+.B sendmail
+normally records the envelope sender in
+.BR Return-Path .
+It does not record envelope recipient addresses,
+on the theory that they are redundant:
+you received the mail,
+so you must have been one of the envelope recipients.
+
+Note that,
+if the header doesn't have any recipient addresses,
+.B sendmail
+will move envelope recipient addresses back into the header.
+This situation occurs if all addresses were originally listed as
+.BR Bcc ,
+since
+.B Bcc
+is automatically removed.
+When
+.B sendmail
+sees this, it creates a new
+.B Apparently-To
+header field with the envelope recipient addresses.
+This has the strange effect that each blind-carbon-copy recipient will see
+a list of all recipients on the same machine.
+
+When a message is stored in
+.B mbox
+format,
+the envelope sender is recorded at the top of the message
+as a UUCP-style
+.B From
+(no colon) line.
+Note that this line is less reliable than the
+.B Return-Path
+line added by
+.B qmail-local
+or
+.B sendmail\fP.
+.SH "SEE ALSO"
+qmail-header(5),
+qmail-local(8),
+qmail-inject(8)
diff --git a/man/except.1 b/man/except.1
new file mode 100644
index 0000000..336bc1a
--- /dev/null
+++ b/man/except.1
@@ -0,0 +1,33 @@
+.TH s/qmail: except 1
+.SH NAME
+except \- reverse the exit code of a program
+.SH SYNOPSIS
+.B except
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B except
+runs
+.I program
+with the given arguments.
+
+If
+.I program
+exits 0,
+.B except
+exits 100.
+If
+.I program
+exits 111,
+.B except
+exits 111.
+If
+.I program
+exits anything else,
+.B except
+exits 0.
+.SH "SEE ALSO"
+bouncesaying(1),
+condredirect(1)
diff --git a/man/fastforward.1 b/man/fastforward.1
new file mode 100644
index 0000000..d56e7dc
--- /dev/null
+++ b/man/fastforward.1
@@ -0,0 +1,123 @@
+.TH s/qmail: fastforward 1
+.SH NAME
+fastforward \- forward mail according to a cdb database
+.SH SYNOPSIS
+in
+.BR .qmail-default :
+.B | fastforward
+[
+.B \-nNpPdD
+]
+.I cdb
+.SH DESCRIPTION
+.B fastforward
+forwards each incoming message
+according to instructions in
+.I cdb
+created by
+.BR setforward .
+
+If there is no forwarding instruction in
+.I cdb
+for the incoming recipient address,
+.B fastforward
+will bounce the message.
+
+You can override
+.B .qmail-default
+with a specific
+.BR .qmail-\fIrecipient ;
+see
+.BR dot-qmail (5).
+
+Warning to system administrators:
+Messages do not reach
+.B ~alias/.qmail-default
+unless they are controlled by the
+.B alias
+user.
+See
+.BR qmail-getpw (8).
+
+.B SECURITY WARNING:
+If
+.I cdb
+includes instructions pointing to a mailing list owned by another user,
+that user gains some amount of control over
+.BR fastforward 's
+behavior.
+In particular, he can force
+.B fastforward
+to open any file that you can access,
+and to read any world-readable file that you own,
+even if the file is in a world-inaccessible directory.
+.SH "OPTIONS"
+.TP 5
+.B \-n
+No delivery.
+.B fastforward
+will print a description of its actions,
+but will not actually read or forward a message.
+.TP
+.B \-N
+(Default.)
+Forward a message as usual.
+.TP
+.B \-p
+Pass through.
+If
+.B fastforward
+does not find the recipient in
+.IR cdb ,
+it exits 0,
+giving the message to further commands in
+.BR .qmail-default .
+If
+.B fastforward
+finds the recipient,
+it forwards the message and exits 99,
+so that further commands are skipped.
+.TP
+.B \-P
+(Default.)
+Do not pass through.
+If
+.B fastforward
+finds the recipient,
+it forwards the message and exits 0.
+Otherwise it bounces the message.
+.TP
+.B \-d
+Use
+.B $DEFAULT@$HOST
+as the recipient address, or
+.B $EXT@$HOST
+if
+.B $DEFAULT
+is not set.
+.TP
+.B \-D
+(Default.)
+Use
+.B $RECIPIENT
+as the recipient address.
+.SH VERSION
+The original
+.B fastforward
+verion is 0.51, and the respective
+.B fastforward
+home page is
+.BR http://pobox.com/~djb/fastforward.html .
+However, this version is tightly integrated into
+.BR s/qmail .
+
+.SH "SEE ALSO"
+newaliases(1),
+printforward(1),
+setforward(1),
+dot-qmail(5),
+qmail-command(8),
+qmail-local(8),
+qmail-recpients(8),
+qmail-authuser(8),
+qmail-getpw(8)
diff --git a/man/forgeries.7 b/man/forgeries.7
new file mode 100644
index 0000000..85cc947
--- /dev/null
+++ b/man/forgeries.7
@@ -0,0 +1,104 @@
+.TH s/qmail: forgeries 7
+.SH "NAME"
+forgeries \- how easy it is to forge mail
+.SH "SUMMARY"
+An electronic mail message can easily be forged.
+Almost everything in it,
+including the return address,
+is completely under the control of the sender.
+
+An electronic mail message can be manually traced to its origin
+if (1) all system administrators of intermediate machines
+are both cooperative and competent,
+(2) the sender did not break low-level TCP/IP security,
+and
+(3) all intermediate machines are secure.
+
+Users of
+.I cryptography
+can automatically ensure the integrity and secrecy
+of their mail messages, as long as
+the sending and receiving machines are secure.
+.SH "FORGERIES"
+Like postal mail,
+electronic mail can be created entirely at the whim of the sender.
+.BR From ,
+.BR Sender ,
+.BR Return-Path ,
+and
+.BR Message-ID
+can all contain whatever information the sender wants.
+
+For example, if you inject a message through
+.B sendmail
+or
+.B qmail-inject
+or
+.BR SMTP ,
+you can simply type in a
+.B From
+field.
+In fact,
+.B qmail-inject
+lets you set up
+.BR MAILUSER ,
+.BR MAILHOST ,
+and
+.B MAILNAME
+environment variables
+to produce your desired
+.B From
+field on every message.
+.SH "TRACING FORGERIES"
+Like postal mail,
+electronic mail is postmarked when it is sent.
+Each machine that receives an electronic mail message
+adds a
+.B Received
+line to the top.
+
+A modern
+.B Received
+line contains quite a bit of information.
+In conjunction with the machine's logs,
+it lets a competent system administrator
+determine where the machine received the message from,
+as long as the sender did not break low-level TCP/IP security
+or security on that machine.
+
+Large multi-user machines often come with inadequate logging software.
+Fortunately, a system administrator can easily obtain a copy of a
+931/1413/Ident/TAP server, such as
+.BR pidentd .
+Unfortunately,
+some system administrators fail to do this,
+and are thus unable to figure out which local user
+was responsible for generating a message.
+
+If all intermediate system administrators are competent,
+and the sender did not break machine security or low-level TCP/IP security,
+it is possible to trace a message backwards.
+Unfortunately, some traces are stymied by intermediate system
+administrators who are uncooperative or untrustworthy.
+.SH "CRYPTOGRAPHY"
+The sender of a mail message may place his message into a
+.I cryptographic
+envelope stamped with his seal.
+Strong cryptography guarantees that any two messages with the same seal
+were sent by the same cryptographic entity:
+perhaps a single person, perhaps a group of cooperating people,
+but in any case somebody who knows a secret originally held
+only by the creator of the seal.
+The seal is called a
+.I public key\fR.
+
+Unfortunately, the creator of the seal is often an insecure machine,
+or an untrustworthy central agency,
+but most of the time seals are kept secure.
+
+One popular cryptographic program is
+.BR pgp .
+.SH "SEE ALSO"
+pgp(1),
+identd(8),
+qmail-header(8)
diff --git a/man/forward.1 b/man/forward.1
new file mode 100644
index 0000000..76d56e7
--- /dev/null
+++ b/man/forward.1
@@ -0,0 +1,24 @@
+.TH s/qmail: forward 1
+.SH NAME
+forward \- forward new mail to one or more addresses
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |forward
+.I address ...
+.SH DESCRIPTION
+.B forward
+forwards each new mail message to the specified list of addresses.
+It is a simple wrapper around
+.BR qmail-queue .
+It achieves the same results as listing each
+.I address
+separately in
+.BR .qmail ,
+but it is more programmable since
+.I address
+can be constructed on the fly.
+.SH "SEE ALSO"
+dot-qmail(5),
+qmail-command(8),
+qmail-queue(8)
diff --git a/man/hostname.8 b/man/hostname.8
new file mode 100644
index 0000000..9276f1e
--- /dev/null
+++ b/man/hostname.8
@@ -0,0 +1,14 @@
+.TH s/qmail: hostname 8
+
+.SH NAME
+hostname
+.SH SYNOPSIS
+.B hostname
+.SH DESCRIPTION
+.B hostname
+evaluates from the system its
+.I hostname
+employing a DNS lookup while erhaps including the domain
+and displays it as \fIFull Qualified Domain Name\fR (\fBFQDN\fR).
+.SH "SEE ALSO"
+ipmeprint(8).
diff --git a/man/ipmeprint.8 b/man/ipmeprint.8
new file mode 100644
index 0000000..473d83e
--- /dev/null
+++ b/man/ipmeprint.8
@@ -0,0 +1,15 @@
+.TH s/qmail: ipmeprint 8
+
+.SH NAME
+ipmeprint
+.SH SYNOPSIS
+.B ipmeprint
+.SH DESCRIPTION
+.B ipmeprints
+reads the kernel's bindings to
+.I IPv4
+and
+.IP IPv6
+addresses and displays those one per line.
+.SH "SEE ALSO"
+hostname(9).
diff --git a/man/maildir.5 b/man/maildir.5
new file mode 100644
index 0000000..49b2b23
--- /dev/null
+++ b/man/maildir.5
@@ -0,0 +1,239 @@
+.TH s/qmail: maildir 5
+.SH "NAME"
+maildir \- directory for incoming mail messages
+.SH "INTRODUCTION"
+.I maildir
+is a structure for
+directories of incoming mail messages.
+It solves the reliability problems that plague
+.I mbox
+files and
+.I mh
+folders.
+.SH "RELIABILITY ISSUES"
+A machine may crash while it is delivering a message.
+For both
+.I mbox
+files and
+.I mh
+folders this means that the message will be silently truncated.
+Even worse: for
+.I mbox
+format, if the message is truncated in the middle of a line,
+it will be silently joined to the next message.
+The mail transport agent will try again later to deliver the message,
+but it is unacceptable that a corrupted message should show up at all.
+In
+.IR maildir ,
+every message is guaranteed complete upon delivery.
+
+A machine may have two programs simultaneously delivering mail
+to the same user.
+The
+.I mbox
+and
+.I mh
+formats require the programs to update a single central file.
+If the programs do not use some locking mechanism,
+the central file will be corrupted.
+There are several
+.I mbox
+and
+.I mh
+locking mechanisms,
+none of which work portably and reliably.
+In contrast, in
+.IR maildir ,
+no locks are ever necessary.
+Different delivery processes never touch the same file.
+
+A user may try to delete messages from his mailbox at the same
+moment that the machine delivers a new message.
+For
+.I mbox
+and
+.I mh
+formats, the user's mail-reading program must know
+what locking mechanism the mail-delivery programs use.
+In contrast, in
+.IR maildir ,
+any delivered message
+can be safely updated or deleted by a mail-reading program.
+
+Many sites use Sun's
+.B Network F\fPa\fBil\fPur\fBe System
+(NFS),
+presumably because the operating system vendor does not offer
+anything else.
+NFS exacerbates all of the above problems.
+Some NFS implementations don't provide
+.B any
+reliable locking mechanism.
+With
+.I mbox
+and
+.I mh
+formats,
+if two machines deliver mail to the same user,
+or if a user reads mail anywhere except the delivery machine,
+the user's mail is at risk.
+.I maildir
+works without trouble over NFS.
+.SH "THE MAILDIR STRUCTURE"
+A directory in
+.I maildir
+format has three subdirectories,
+all on the same filesystem:
+.BR tmp ,
+.BR new ,
+and
+.BR cur .
+
+Each file in
+.B new
+is a newly delivered mail message.
+The modification time of the file is the delivery date of the message.
+The message is delivered
+.I without
+an extra UUCP-style
+.B From_
+line,
+.I without
+any
+.B >From
+quoting,
+and
+.I without
+an extra blank line at the end.
+The message is normally in RFC 822 format,
+starting with a
+.B Return-Path
+line and a
+.B Delivered-To
+line,
+but it could contain arbitrary binary data.
+It might not even end with a newline.
+
+Files in
+.B cur
+are just like files in
+.BR new .
+The big difference is that files in
+.B cur
+are no longer new mail:
+they have been seen by the user's mail-reading program.
+.SH "HOW A MESSAGE IS DELIVERED"
+The
+.B tmp
+directory is used to ensure reliable delivery,
+as discussed here.
+
+A program delivers a mail message in six steps.
+First, it
+.B chdir()\fPs
+to the
+.I maildir
+directory.
+Second, it
+.B stat()s
+the name
+.BR tmp/\fItime.pid.host ,
+where
+.I time
+is the number of seconds since the beginning of 1970 GMT,
+.I pid
+is the program's process ID,
+and
+.I host
+is the host name.
+Third, if
+.B stat()
+returned anything other than ENOENT,
+the program sleeps for two seconds, updates
+.IR time ,
+and tries the
+.B stat()
+again, a limited number of times.
+Fourth, the program
+creates
+.BR tmp/\fItime.pid.host .
+Fifth, the program
+.I NFS-writes
+the message to the file.
+Sixth, the program
+.BR link() s
+the file to
+.BR new/\fItime.pid.host .
+At that instant the message has been successfully delivered.
+
+The delivery program is required to start a 24-hour timer before
+creating
+.BR tmp/\fItime.pid.host ,
+and to abort the delivery
+if the timer expires.
+Upon error, timeout, or normal completion,
+the delivery program may attempt to
+.B unlink()
+.BR tmp/\fItime.pid.host .
+
+.I NFS-writing
+means
+(1) as usual, checking the number of bytes returned from each
+.B write()
+call;
+(2) calling
+.B fsync()
+and checking its return value;
+(3) calling
+.B close()
+and checking its return value.
+(Standard NFS implementations handle
+.B fsync()
+incorrectly
+but make up for it by abusing
+.BR close() .)
+.SH "HOW A MESSAGE IS READ"
+A mail reader operates as follows.
+
+It looks through the
+.B new
+directory for new messages.
+Say there is a new message,
+.BR new/\fIunique .
+The reader may freely display the contents of
+.BR new/\fIunique ,
+delete
+.BR new/\fIunique ,
+or rename
+.B new/\fIunique
+as
+.BR cur/\fIunique:info .
+See
+.B http://pobox.com/~djb/proto/maildir.html
+for the meaning of
+.IR info .
+
+The reader is also expected to look through the
+.B tmp
+directory and to clean up any old files found there.
+A file in
+.B tmp
+may be safely removed if it
+has not been accessed in 36 hours.
+
+It is a good idea for readers to skip all filenames in
+.B new
+and
+.B cur
+starting with a dot.
+Other than this, readers should not attempt to parse filenames.
+.SH "ENVIRONMENT VARIABLES"
+Mail readers supporting
+.I maildir
+use the
+.B MAILDIR
+environment variable
+as the name of the user's primary mail directory.
+.SH "SEE ALSO"
+mbox(5),
+qmail-local(8)
diff --git a/man/maildir2mbox.1 b/man/maildir2mbox.1
new file mode 100644
index 0000000..c63a6a8
--- /dev/null
+++ b/man/maildir2mbox.1
@@ -0,0 +1,53 @@
+.TH s/qmail: maildir2mbox 1
+.SH NAME
+maildir2mbox \- move mail from a maildir to an mbox
+.SH SYNOPSIS
+.B maildir2mbox
+.SH DESCRIPTION
+.B maildir2mbox
+moves mail from a
+.IR maildir -format
+directory to an
+.IR mbox -format
+file.
+
+You must supply three environment variables to
+.BR maildir2mbox :
+.B MAILDIR
+is the name of your
+.I maildir
+directory;
+.B MAIL
+is the name of your
+.I mbox
+file;
+and
+.B MAILTMP
+is a temporary file that
+.B maildir2mbox
+can overwrite.
+.B MAILTMP
+and
+.B MAIL
+must be on the same filesystem.
+
+.B maildir2mbox
+is reliable:
+it will not remove messages
+from
+.B MAILDIR
+until the messages have been successfully appended to
+.BR MAIL .
+
+.B maildir2mbox
+locks
+.B MAIL
+to protect against simultaneous access by a mail reader.
+This locking system does not protect against simultaneous access
+by another
+.BR maildir2mbox ;
+you should run only one
+.B maildir2mbox
+at a time.
+.SH "SEE ALSO"
+maildir(5)
diff --git a/man/maildirmake.1 b/man/maildirmake.1
new file mode 100644
index 0000000..875ab50
--- /dev/null
+++ b/man/maildirmake.1
@@ -0,0 +1,15 @@
+.TH s/qmail: maildirmake 1
+.SH NAME
+maildirmake \- create a maildir for incoming mail
+.SH SYNOPSIS
+.B maildirmake
+.I dir
+.SH DESCRIPTION
+.B maildirmake
+makes a new directory,
+.IR dir ,
+in
+.B maildir
+format.
+.SH "SEE ALSO"
+maildir(5)
diff --git a/man/maildirwatch.1 b/man/maildirwatch.1
new file mode 100644
index 0000000..c33b17e
--- /dev/null
+++ b/man/maildirwatch.1
@@ -0,0 +1,23 @@
+.TH s/qmail: maildirwatch 1
+.SH NAME
+maildirwatch \- look for new mail in a maildir
+.SH SYNOPSIS
+.B maildirwatch
+.SH DESCRIPTION
+.B maildirwatch
+watches your
+.I maildir
+for new mail.
+You must supply a
+.B MAILDIR
+environment variable
+with the name of your
+.I maildir
+directory.
+
+.B maildirwatch
+prints a new mail summary twice per minute.
+It is designed to run inside a (VT100-compatible) window;
+it clears the window before each summary.
+.SH "SEE ALSO"
+maildir(5)
diff --git a/man/mailsubj.1 b/man/mailsubj.1
new file mode 100644
index 0000000..ed4772d
--- /dev/null
+++ b/man/mailsubj.1
@@ -0,0 +1,38 @@
+.TH s/qmail: mailsubj 1
+.SH NAME
+mailsubj \- send a mail message with a subject line
+.SH SYNOPSIS
+.B mailsubj
+.I subject
+.I recip ...
+.SH DESCRIPTION
+.B mailsubj
+inserts
+.I subject
+and the list of
+.IR recip s
+into a mail message:
+
+.EX
+ Subject: subject
+.br
+ To: recip ...
+.br
+
+.br
+ body
+.EE
+
+.B mailsubj
+reads the body of the message from its standard input.
+Then it sends the message.
+
+Note that
+.I subject
+and
+.I recip
+must be quoted properly for the message header.
+.SH "SEE ALSO"
+addresses(5),
+qmail-header(8),
+qmail-inject(8)
diff --git a/man/matchup.1 b/man/matchup.1
new file mode 100644
index 0000000..1a3fbf0
--- /dev/null
+++ b/man/matchup.1
@@ -0,0 +1,111 @@
+.TH s/qmail: matchup 1
+.SH NAME
+matchup \- collect information on messages and deliveries
+.SH SYNTAX
+.B matchup
+.SH DESCRIPTION
+.B matchup
+reads a series of lines from
+.BR qmail-send ,
+with a numeric timestamp in front of each line.
+.B matchup
+matches the end of each delivery attempt with the start of the delivery attempt
+and with the relevant message information;
+it replaces
+.BR qmail-send 's
+message reports and delivery reports
+with message lines and delivery lines in the format described below.
+
+.B matchup
+exits after it sees end of file.
+It prints pending messages and deliveries on descriptor 5,
+in a format suitable for input to a future invocation of
+.BR matchup :
+
+.EX
+ <log.1 matchup >out.1 5>pending.2
+.br
+ cat pending.2 log.2 | matchup >out.2 5>pending.3
+.br
+ cat pending.3 log.3 | matchup >out.3 5>pending.4
+.EE
+
+Note that the 5> notation does not work with csh.
+.SH "MESSAGE LINES"
+A message line summarizes the delivery results for a message
+that has left the queue:
+
+.EX
+ m \fIbirth\fR \fIdone\fR \fIbytes\fR \fInk\fR \fInz\fR \fInd\fR <\fIsender\fR> \fIqp\fR \fIuid\fR
+.EE
+
+Here
+.I birth
+and
+.I done
+are timestamps,
+.I bytes
+is the number of bytes in the message,
+.I nk
+is the number of successful deliveries,
+.I nz
+is the number of deferred delivery attempts,
+.I nd
+is the number of failed delivery attempts,
+.I sender
+is the message's return path,
+.I qp
+is the message's long-term queue identifier,
+and
+.I uid
+is the userid of the user that queued the message.
+
+Note that
+.B matchup
+converts
+.I sender
+to lowercase.
+This can lose information,
+since a few hosts pay attention to the case in the box part of an address.
+.SH "DELIVERY LINES"
+A delivery line shows the result of a single delivery attempt:
+
+.EX
+ d \fIresult\fR \fIbirth\fR \fIdstart\fR \fIddone\fR \fIbytes\fR
+.br
+ <\fIsender\fR> \fIchan\fR.\fIrecip\fR \fIqp\fR \fIuid\fR \fIreason\fR
+.EE
+
+Here
+.IR birth ,
+.IR bytes ,
+.IR sender ,
+.IR qp ,
+and
+.I uid
+are message information as above;
+.I chan
+is the channel for this delivery;
+.I recip
+is the recipient address for this delivery;
+.I dstart
+and
+.I ddone
+are timestamps;
+.I result
+is the letter k for success, z for deferral, d for failure;
+and
+.I reason
+is a more detailed explanation of the delivery result.
+
+.B matchup
+converts
+.I recip
+to lowercase.
+.SH "SEE ALSO"
+xqp(1),
+xrecipient(1),
+xsender(1),
+accustamp(1),
+qmail-log(5),
+splogger(8)
diff --git a/man/mbox.5 b/man/mbox.5
new file mode 100644
index 0000000..e9860e4
--- /dev/null
+++ b/man/mbox.5
@@ -0,0 +1,235 @@
+.TH s/qmail: mbox 5
+.SH "NAME"
+mbox \- file containing mail messages
+.SH "INTRODUCTION"
+The most common format for storage of mail messages is
+.I mbox
+format.
+An
+.I mbox
+is a single file containing zero or more mail messages.
+.SH "MESSAGE FORMAT"
+A message encoded in
+.I mbox
+format begins with a
+.B From_
+line, continues with a series of
+.B \fRnon-\fBFrom_
+lines,
+and ends with a blank line.
+A
+.B From_
+line means any line that begins with the characters
+F, r, o, m, space:
+
+.EX
+ From god@heaven.af.mil Sat Jan 3 01:05:34 1996
+.br
+ Return-Path: <god@heaven.af.mil>
+.br
+ Delivered-To: djb@silverton.berkeley.edu
+.br
+ Date: 3 Jan 1996 01:05:34 -0000
+.br
+ From: God <god@heaven.af.mil>
+.br
+ To: djb@silverton.berkeley.edu (D. J. Bernstein)
+.br
+
+.br
+ How's that mail system project coming along?
+.br
+
+.EE
+
+The final line is a completely blank line (no spaces or tabs).
+Notice that blank lines may also appear elsewhere in the message.
+
+The
+.B From_
+line always looks like
+.B From
+.I envsender
+.I date
+.IR moreinfo .
+.I envsender
+is one word, without spaces or tabs;
+it is usually the envelope sender of the message.
+.I date
+is the delivery date of the message.
+It always contains exactly 24 characters in
+.B asctime
+format.
+.I moreinfo
+is optional; it may contain arbitrary information.
+
+Between the
+.B From_
+line and the blank line is a message in RFC 822 format,
+as described in
+.BR qmail-header(5) ,
+subject to
+.B >From quoting
+as described below.
+.SH "HOW A MESSAGE IS DELIVERED"
+Here is how a program appends a message to an
+.I mbox
+file.
+
+It first creates a
+.B From_
+line given the message's envelope sender and the current date.
+If the envelope sender is empty (i.e., if this is a bounce message),
+the program uses
+.B MAILER-DAEMON
+instead.
+If the envelope sender contains spaces, tabs, or newlines,
+the program replaces them with hyphens.
+
+The program then copies the message, applying
+.B >From quoting
+to each line.
+.B >From quoting
+ensures that the resulting lines are not
+.B From_
+lines:
+the program prepends a
+.B >
+to any
+.B From_
+line,
+.B >From_
+line,
+.B >>From_
+line,
+.B >>>From_
+line,
+etc.
+
+Finally the program appends a blank line to the message.
+If the last line of the message was a partial line,
+it writes two newlines;
+otherwise it writes one.
+.SH "HOW A MESSAGE IS READ"
+A reader scans through an
+.I mbox
+file looking for
+.B From_
+lines.
+Any
+.B From_
+line marks the beginning of a message.
+The reader should not attempt to take advantage of the fact that every
+.B From_
+line (past the beginning of the file)
+is preceded by a blank line.
+
+Once the reader finds a message,
+it extracts a (possibly corrupted) envelope sender
+and delivery date out of the
+.B From_
+line.
+It then reads until the next
+.B From_
+line or end of file, whichever comes first.
+It strips off the final blank line
+and
+deletes the
+quoting of
+.B >From_
+lines and
+.B >>From_
+lines and so on.
+The result is an RFC 822 message.
+.SH "COMMON MBOX VARIANTS"
+There are many variants of
+.I mbox
+format.
+The variant described above is
+.I mboxrd
+format, popularized by Rahul Dhesi in June 1995.
+
+The original
+.I mboxo
+format quotes only
+.B From_
+lines, not
+.B >From_
+lines.
+As a result it is impossible to tell whether
+
+.EX
+ From: djb@silverton.berkeley.edu (D. J. Bernstein)
+.br
+ To: god@heaven.af.mil
+.br
+
+.br
+ >From now through August I'll be doing beta testing.
+.br
+ Thanks for your interest.
+.EE
+
+was quoted in the original message.
+An
+.I mboxrd
+reader will always strip off the quoting.
+
+.I mboxcl
+format is like
+.I mboxo
+format, but includes a Content-Length field with the
+number of bytes in the message.
+.I mboxcl2
+format is like
+.I mboxcl
+but has no
+.B >From
+quoting.
+These formats are used by SVR4 mailers.
+.I mboxcl2
+cannot be read safely by
+.I mboxrd
+readers.
+.SH "UNSPECIFIED DETAILS"
+There are many locking mechanisms for
+.I mbox
+files.
+.B qmail-local
+always uses
+.B flock
+on systems that have it, otherwise
+.BR lockf .
+
+The delivery date in a
+.B From_
+line does not specify a time zone.
+.B qmail-local
+always creates the delivery date in GMT
+so that
+.I mbox
+files can be safely transported from one time zone to another.
+
+If the mtime on a nonempty
+.I mbox
+file is greater than the atime,
+the file has new mail.
+If the mtime is smaller than the atime,
+the new mail has been read.
+If the atime equals the mtime,
+there is no way to tell whether the file has new mail,
+since
+.B qmail-local
+takes much less than a second to run.
+One solution is for a mail reader to artificially set the
+atime to the mtime plus 1.
+Then the file has new mail if and only if the atime is
+less than or equal to the mtime.
+
+Some mail readers place
+.B Status
+fields in each message to indicate which messages have been read.
+.SH "SEE ALSO"
+maildir(5),
+qmail-header(5),
+qmail-local(8)
diff --git a/man/newaliases.1 b/man/newaliases.1
new file mode 100644
index 0000000..a51ff64
--- /dev/null
+++ b/man/newaliases.1
@@ -0,0 +1,366 @@
+.TH s/qmail: newaliases 1
+.SH NAME
+newaliases \- create a forwarding database from /etc/aliases
+.SH SYNOPSIS
+.B newaliases
+.SH DESCRIPTION
+.B newaliases
+reads a table of
+sendmail-style
+forwarding instructions from
+.B /etc/aliases
+and converts them into a forwarding database in
+.BR /etc/aliases.cdb .
+The forwarding database can be used by
+.BR fastforward .
+
+For safety,
+.B newaliases
+writes the forwarding database to
+.B /etc/aliases.tmp
+and then moves
+.B /etc/aliases.tmp
+to
+.BR /etc/aliases.cdb .
+If there is a problem creating
+.BR /etc/aliases.tmp ,
+.B newaliases
+complains and leaves
+.B /etc/aliases.cdb
+alone.
+Deliveries can continue using
+.B /etc/aliases.cdb
+in the meantime.
+
+.B newaliases
+always creates
+.B /etc/aliases.cdb
+world-readable.
+
+.B newaliases
+makes no attempt to protect against
+simultaneous updates of
+.BR /etc/aliases.cdb .
+.SH "INSTRUCTION FORMAT"
+.B newaliases
+imitates
+sendmail's
+handling of
+.BR /etc/aliases .
+For example,
+
+.EX
+ root: alice, bill
+.EE
+
+says that mail for
+.B root
+should be forwarded to
+.B alice
+and
+.BR bill .
+
+.B COMPATIBILITY WARNING:
+.B newaliases
+does not support file deliveries.
+You can use the file delivery mechanism described in
+.B dot-qmail(5)
+instead.
+.SH "SIMPLE ALIASES"
+The simplest type of forwarding instruction
+is a line of the form
+
+.EX
+ alias: recip
+.EE
+
+Any message sent to
+.I alias
+will be forwarded to the recipient address
+.IR recip .
+Addresses are compared to
+.I alias
+without regard to case.
+
+Forwarding instructions are cumulative.
+If
+.I recip
+is itself an alias,
+messages to
+.I alias
+will be forwarded the same way as
+messages to
+.IR recip .
+For example, with the following instructions,
+messages to
+.B postmaster@heaven.af.mil
+or
+.B root@heaven.af.mil
+will be delivered to Bob:
+
+.EX
+ postmaster@heaven.af.mil: bob@heaven.af.mil
+.EE
+.br
+.EX
+ root@heaven.af.mil: postmaster@heaven.af.mil
+.EE
+
+.B COMPATIBILITY WARNING:
+With
+sendmail,
+entries in
+.B /etc/aliases
+can override usernames.
+With
+.BR s/qmail ,
+if you install
+.B fastforward
+in
+.BR ~alias/.qmail-default ,
+it will not see addresses that are controlled by other users.
+See
+.BR qmail-getpw (8).
+To change this, see
+.BR qmail-users (5).
+
+.B COMPATIBILITY WARNING:
+Various versions of
+sendmail
+do various strange things with circular alias definitions.
+See
+.BR setforward (1)
+for details on
+.BR fastforward 's
+behavior.
+
+.B COMPATIBILITY WARNING:
+If there are several forwarding instructions for a single
+.IR alias ,
+sendmail
+will complain;
+.B fastforward
+will silently use the first instruction.
+.SH "WILDCARDS"
+.I alias
+can have the form
+.I user@host.dom
+for one user at one host,
+.I @host.dom
+for all users at one host, or
+.I user
+for one user at all hosts.
+
+.B COMPATIBILITY WARNING:
+sendmail
+supports only
+.IR user ;
+it does not support per-host aliases.
+It accepts
+.I user@host.dom
+if
+.I host.dom
+is a local host,
+but it then treats it the same way as
+.IR user ,
+applying to all local hosts and virtual domains.
+.SH "ADDRESS FORMATS"
+Addresses in
+.B /etc/aliases
+are parsed the same way as addresses in RFC 822 message headers.
+Parenthesized comments and bracketed addresses are permitted:
+
+.EX
+ root: bob (Bob, the postmaster)
+ joe: Joe Shmoe <shmoe@heaven.af.mil>
+.EE
+
+Addresses with special characters must be quoted:
+
+.EX
+ fred: "spaced out mailbox"@heaven.af.mil
+.EE
+
+Address groups are not permitted,
+since colons have a different use in
+.BR /etc/aliases .
+
+Any recipient address without a fully qualified domain name is
+fed through the
+.BR defaulthost ,
+.BR defaultdomain ,
+and
+.B plusdomain
+mechanisms described in
+.BR qmail-header (5).
+
+.B COMPATIBILITY WARNING:
+sendmail's
+handling of quotes and backslashes violates RFC 821 and RFC 822,
+and is not supported by
+.BR newaliases .
+The
+.B qmail-local
+delivery mechanism
+lets each user manage several addresses,
+so there is no need for a special syntax to get around forwarding.
+.SH "MULTIPLE RECIPIENTS"
+An instruction may list more than one recipient address:
+
+.EX
+ alias: recip1, recip2, recip3
+.EE
+
+Any message sent to
+.I alias
+will be forwarded to all of the addresses.
+
+A forwarding instruction may be split across several lines.
+Each line past the first must either (1) begin with space or tab
+or (2) be empty:
+
+.EX
+ hostmaster:
+.EE
+.br
+.EX
+ fred,
+.EE
+.br
+.EX
+ joe
+.EE
+
+.B COMPATIBILITY WARNING:
+sendmail
+requires the colon to be on the first line
+of a multi-line forwarding instruction.
+.B newaliases
+doesn't care whether the colon is present at all.
+
+.B COMPATIBILITY WARNING:
+sendmail
+does not permit blank lines in the middle of continuations.
+This has the undesirable effect that a blank line behaves differently
+from a line containing a single space.
+.SH "COMMENTS"
+Any line in
+.B /etc/aliases
+that begins with # is ignored:
+
+.EX
+ # this is a comment
+.EE
+
+A comment may be split across several lines.
+Each line past the first must either (1) begin with space or tab
+or (2) be empty.
+
+.B COMPATIBILITY WARNING:
+sendmail
+does not permit continuations of comment lines.
+.SH "PROGRAMS"
+If a recipient address does not contain a domain name,
+and begins with a vertical bar,
+.B newaliases
+takes the rest of the address as a program to run:
+
+.EX
+ weather: "|weather-server"
+.EE
+
+.B fastforward
+will run
+.B weather-server
+when a message arrives for
+.BR weather .
+
+.B COMPATIBILITY WARNING:
+Internet addresses can legitimately start with
+a slash or vertical bar.
+.B newaliases
+treats anything with an unquoted @ as an address.
+sendmail appears to have various problems
+coping with these addresses,
+and with commands that contain @ signs.
+
+.B COMPATIBILITY WARNING:
+.B newaliases
+does not allow a vertical bar before double quotes.
+.SH "INCLUDE FILES"
+A recipient address of the form
+.B :include:\fIfile
+means ``every address listed in
+.IR file .''
+(Actually
+.B fastforward
+reads
+.IR file\fB.bin ;
+see
+.BR newinclude (1)
+for further details.)
+
+Note that
+.I file
+is read by
+.BR fastforward ,
+not
+.BR newaliases ,
+so the system administrator does not have to run
+.B newaliases
+every time
+.I file
+changes.
+.I file
+must be world-readable
+and accessible to
+.BR fastforward .
+
+.B COMPATIBILITY WARNING:
+If an
+.B :include:
+file is unreadable or nonexistent,
+sendmail
+skips it;
+.B fastforward
+defers delivery of the message.
+
+.B COMPATIBILITY WARNING:
+sendmail
+does not permit spaces inside the literal text
+.BR :include: .
+.B newaliases
+does.
+
+.B COMPATIBILITY WARNING:
+Versions of
+sendmail
+before V8 did not strip quotes from
+.B :include:
+filenames.
+.SH "ALIAS OWNERS"
+If there is an alias for
+.BR owner-\fIlist ,
+any message forwarded through
+.I list
+will have its envelope sender set to
+.BR owner-\fIlist ,
+so that bounces go back to
+.BR owner-\fIlist .
+
+.B COMPATIBILITY WARNING:
+When an alias includes the same recipient both inside and outside
+a mailing list,
+.B fastforward
+sends the message twice,
+once with each envelope sender.
+sendmail
+sends the message only once;
+its choice of envelope sender for that recipient
+depends on the phase of the moon.
+.SH "SEE ALSO"
+fastforward(1),
+setforward(1),
+newinclude(1),
+printforward(1),
+dot-qmail(5)
diff --git a/man/newinclude.1 b/man/newinclude.1
new file mode 100644
index 0000000..44edb9d
--- /dev/null
+++ b/man/newinclude.1
@@ -0,0 +1,88 @@
+.TH s/qmail: newinclude 1
+.SH NAME
+newinclude \- create a binary mailing list from an :include: file
+.SH SYNOPSIS
+.B newinclude
+.I list
+.SH DESCRIPTION
+.B newinclude
+reads a
+sendmail-style
+.B :include:
+file,
+.IR list ,
+and converts it into a binary format in
+.I list\fB.bin
+for use by
+.BR fastforward .
+
+.B newinclude
+first writes the mailing list to
+.IR list\fB.tmp ,
+and then moves it to
+.IR list\fB.bin .
+If there is any problem creating
+.IR list\fB.tmp ,
+.B newinclude
+leaves
+.I list\fB.bin
+alone.
+
+.B newinclude
+always creates
+.I list\fB.bin
+world-readable.
+
+.B COMPATIBILITY WARNING:
+sendmail
+reads
+.I list
+directly;
+.B fastforward
+needs
+.IR list\fB.bin .
+sendmail's strategy is a disaster if you save
+.I list
+to disk at the same moment that
+sendmail
+reads it;
+the list will be truncated at a random spot,
+perhaps in the middle of an address.
+Furthermore, if the system crashes while you are writing
+.IR list ,
+.I list
+could be filled with all sorts of garbage.
+.SH "LIST FORMAT"
+.I list
+may contain any number of lines;
+each line may contain any number of addresses
+or further
+.B :include:
+files.
+See
+.BR newaliases (1)
+for details on the address format.
+Any line in
+.I file
+beginning with # is ignored.
+
+.B COMPATIBILITY WARNING:
+.B newinclude
+does not support file or program deliveries in
+.B :include:
+files.
+You can use the secure delivery mechanisms described in
+.B dot-qmail(5)
+instead.
+
+.B COMPATIBILITY WARNING:
+Versions of
+sendmail
+before V8 did not allow comments in
+.B :include:
+files.
+.SH "SEE ALSO"
+fastforward(1),
+newaliases(1),
+setmaillist(1),
+dot-qmail(5)
diff --git a/man/preline.1 b/man/preline.1
new file mode 100644
index 0000000..d324ff8
--- /dev/null
+++ b/man/preline.1
@@ -0,0 +1,57 @@
+.TH s/qmail: preline 1
+.SH NAME
+preline \- prepend lines to message
+.SH SYNOPSIS
+in
+.BR .qmail\fIext :
+.B | preline \fIcommand
+.SH DESCRIPTION
+.B preline
+feeds each incoming mail message through
+.IR command .
+At the top of each message it inserts
+a UUCP-style
+.B From_
+line, a
+.B Return-Path
+line, and a
+.B Delivered-To
+line.
+
+.B preline
+is useful for
+.B procmail
+and
+ELM's
+.BR filter ,
+which
+do not understand the
+.B qmail-command
+environment variables.
+.SH OPTIONS
+.TP
+.B \-d
+Do not include the
+.B Delivered-To
+line. You should use this option when the
+recipient of the incoming mail message is actually under remote control,
+but was sent here through
+.B control/virtualdomains
+for manual routing.
+.TP
+.B \-f
+Do not include the
+.B From_
+line. You should use this option except for
+.IR command s
+that create
+.I mbox
+files.
+.TP
+.B \-r
+Do not include the
+.B Return-Path
+line.
+.SH "SEE ALSO"
+mbox(5),
+qmail-command(8)
diff --git a/man/printforward.1 b/man/printforward.1
new file mode 100644
index 0000000..f4beaa0
--- /dev/null
+++ b/man/printforward.1
@@ -0,0 +1,16 @@
+.TH s/qmail: printforward 1
+.SH NAME
+printforward \- print the instructions in a forwarding database
+.SH SYNOPSIS
+.B printforward
+.SH DESCRIPTION
+.B printforward
+reads a forwarding database from its standard input
+and prints all the forwarding instructions
+in a format accepted by
+.BR setforward .
+.SH "SEE ALSO"
+fastforward(1),
+newaliases(1),
+printmaillist(1),
+setforward(1)
diff --git a/man/printmaillist.1 b/man/printmaillist.1
new file mode 100644
index 0000000..803cdab
--- /dev/null
+++ b/man/printmaillist.1
@@ -0,0 +1,15 @@
+.TH s/qmail: printmaillist 1
+.SH NAME
+printmaillist \- print the contents of a binary mailing list
+.SH SYNOPSIS
+.B printmaillist
+.SH DESCRIPTION
+.B printmaillist
+reads a binary mailing list from its standard input
+and prints all the forwarding instructions
+in a format accepted by
+.BR setmaillist .
+.SH "SEE ALSO"
+newinclude(1),
+printforward(1),
+setmaillist(1)
diff --git a/man/qbiff.1 b/man/qbiff.1
new file mode 100644
index 0000000..085d97e
--- /dev/null
+++ b/man/qbiff.1
@@ -0,0 +1,31 @@
+.TH s/qmail: qbiff 1
+.SH NAME
+qbiff \- announce new mail the moment it arrives
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |qbiff
+.SH DESCRIPTION
+.B qbiff
+writes a message to your screen
+whenever a new mail message is delivered,
+if you ran
+.B biff y
+after logging in.
+
+.B WARNING:
+If you create a
+.B .qmail
+file to enable
+.BR qbiff ,
+make sure to also add a line specifying delivery to your normal mailbox.
+For example:
+
+.EX
+ /home/joe/Mailbox
+.br
+ |qbiff
+.EE
+.SH "SEE ALSO"
+biff(1),
+dot-qmail(5)
diff --git a/man/qmail-authuser.9 b/man/qmail-authuser.9
new file mode 100644
index 0000000..affb9fa
--- /dev/null
+++ b/man/qmail-authuser.9
@@ -0,0 +1,433 @@
+.TH s/qmail: qmail-authuser 8
+
+.SH "NAME"
+qmail-authuser \- user authentication
+.SH "SYNOPSIS"
+.B qmail-authuser
+[
+.I qmail-pop3d maildirname
+|
+.I -s authsocket [-x service=authmethod]
+]
+.I subprogram [ args ]
+.SH "DESCRIPTION"
+.B qmail-authuser
+is a versatile authentication PAM for SMTP and/or POP3 services
+providing four different operation modes depending on the input
+of the configuration file
+.I SQMAIL/users/authuser
+and the given arguments.
+It can be used as substitude for the programs
+.IR checkpassword ,
+.IR cmd5checkpw ,
+.IR checkvpw ,
+and
+.I vchkpw
+supporting the same arguments on call.
+.TP 5
+Native mode:
+.B qmail-authuser
+reads
+.I SQMAIL/users/authuser
+and uses the information as local authentication database.
+.TP 5
+System mode:
+.B qmail-authuser
+accesses the Unix
+.I /etc/password
+file (or it's shadow companion) as authentication source.
+.TP 5
+Virtual user mode:
+.B qmail-authuser
+calls either the virtual domain auth handler
+.B vchkpw
+or
+.BR checkvpw .
+.TP 5
+Dovecot mode:
+.B qmail-authuser
+queries
+.B dovecot
+as authentication provider.
+.SH "USAGE"
+.B qmail-authuser
+can be called by
+.B qmail-smtpd
+and/or
+.B qmail-popup
+while following the
+.BR checkpassword 's
+interface specification and enabling
+LOGIN, PLAIN, and CRAM-MD5 authentication for SMTP
+as well as USER and APOP for POP3.
+
+The information supplied on descriptor 3
+is an \fIauthuser\fR name terminated by \e0,
+a \fIpassword\fR or \fIresponse\fR terminated by \e0,
+and a \fIchallenge\fR for CRAM-MD5 or APOP
+authentication terminated by \e0.
+There must be at most 512 bytes of data before end of file.
+
+In case
+.I authuser
+and
+.I password
+match,
+.B qmail-authuser
+calls
+.B pathexec
+to run
+.B subprogram
+with the given arguments and perhaps setting up the user environment.
+The use of
+.B subprogram
+is required and can be expressed as
+.B /bin/true
+or
+.B /usr/bin/true
+for compliance reasons.
+.SH "FILES"
+.I SQMAIL/users/authuser
+contains pairs of
+.I authuser
+and
+.I password
+tokens separated by a colon (":").
+Both tokens may include white spaces (if supported by the OS) and may
+use special characters for certain actions. The provided
+.I password
+token should have a significant length (> 2 characters).
+
+Lines starting with the \'#\' sign are regarded as comment.
+Trailing empty spaces in lines are removed prior of evaluation.
+.SH "AUTHUSER"
+The
+.I authuser
+token is the public part of the identity and
+may include a composit information, typically the
+.I userid
+and the
+.I domain
+respectively, described as
+.IR userid@domain .
+.B qmail-authuser
+may treat both parts independently.
+Domain specific authentication may be considered using the
+.I @domain
+part within the
+.I authuser
+token. However, as an abbreviation,
+this may be provided simply as
+.IR @ ,
+telling
+.B qmail-authuser
+to consider all unspecified authusers solely and transparently
+as \'virtual users\'.
+On the other hand, the
+.I authuser
+token may be wildcarded as
+.IR * .
+Now,
+.B qmail-authuser
+is instructed to query the local Unix system as identity provider.
+
+More specific
+.I authuser
+tokens have precedence over less specific, irrespectively of their order.
+System mode has precedence over virtual user mode.
+Particular users and domains can be disabled from authentication
+prepending the name with a \'!\' overruling acceptance:
+.IR !authuser .
+
+Note: Virtual Domain Managers require to include the domain within
+.I authuser
+in order to identify the domain the user belongs to.
+.SH "NATIVE MODE"
+.B qmail-authuser
+recalculates the digest using the provided challenge
+and the passwords from
+.IR SQMAIL/users/authuser
+and compares it with response (2nd parameter).
+
+If no challenge is provided,
+.B qmail-authuser
+compares the supplied password with the stored
+.I password
+token in
+.IR SQMAIL/users/authuser .
+Thus,
+.B qmail-authuser
+can be used as PAM identity provider for
+PLAIN, LOGIN, CRAM-MD5 and APOP auth methods.
+.SH "SYSTEM MODE"
+.B qmail-authuser
+may also been used as a replacement for the
+.B checkpassword
+PAM, allowing to evaluate the
+.I /etc/passwd
+and
+.I shadow
+files for the auth methods USER, PLAIN & LOGIN
+while only considerung the user part in
+.IR authuser .
+In this case,
+.B qmail-authuser
+has to be \'sticky\' and running as
+.IR root .
+.SH "VIRTUAL USER MODE"
+.B qmail-authuser
+includes the call of both
+.IR vpopmail 's
+.B vchkpw
+and
+.IR vmailmgr 's
+.B checkvpw
+(which need to be in the path)
+and transfers the received authentication information transparently to those.
+.SH "DOVECOT MODE"
+.B qmail-authuser
+is also capabable to connect to a Unix socket created for authentication by
+.IR Dovecot .
+.SH "POP3 AND APOP"
+Calling
+.B qmail-authuser
+for POP3 authentication with the option
+.I qmail-pop3d
+together with the format of the mailbox given as
+.IR maildirname ,
+which is typically
+.I Maildir
+or
+.IR mbox .
+The required environment variables
+\fIUSER\fR, \fIHOME\fR, and \fISHELL\fR
+for the respective user are evaluated from
+.IR /etc/passwd .
+APOP authentication is possible for a given user, if
+.I authuser
+and the
+.I password
+is included in
+.IR SQMAIL/users/authuser .
+Upon successful authentication
+.B qmail-authuser
+changes to $\fIHOME\fR.
+.SH "QUERY AND STORAGE MODES"
+The first character
+.I X
+of the
+.I password
+token is used to indicate the password's query and storage method.
+The following cases may be considered:
+
+.EX
+ (1a) authuser:clearpwd
+ (1b) authuser:%pwdhash
+ (2a) authuser:?
+ (2b) *:?
+ (3a) authuser:+
+ (3b) @domain:+
+ (3c) @:+
+ (3d) authuser:&
+ (3e) @domain:&
+ (3f) @:&
+ (4a) authuser:=
+ (4b) @domain:=
+ (4c) @:=
+.EE
+
+(1) Local query/storage:
+Here, together with the
+.I authuser
+plaintext (1a) or hashed passwords (1b)
+may be provisioned in the
+.I SQMAIL/users/authuser
+control file.
+In case of
+.IR %pwdhash ,
+the password is stored as MD5, SHA1, or SHA256 hash prepended with the \'%\'.
+If the plaintext password is given as
+.I password
+this means that the following password is taken literally
+though allwowing a leading \'%\'.
+
+(2) Unix system query/storage:
+In case the
+.I password
+token consists of
+.IR '?' ,
+the received authentication information is used to emulate a
+standard Unix user login taking the
+.I userid
+information as system user account. Therefore, no particular
+.I password
+token is required here.
+The inclusion of any specific
+.I authuser
+information can be avoided in case
+.I '*'
+is used as shortcut within
+.I SQMAIL/users/authuser
+followed by
+.I '?'
+as
+.I password
+token. Now, the received
+.I userid
+and password is taken from the Unix system for authentication (crypt).
+
+(3) Virtual domain query/storage:
+Alternatively,
+.B qmail-authuser
+may call either
+.B checkvpw
+once a
+.I '+'
+or
+.B vchkpw
+in case
+.I '&'
+is given as
+.I password
+token.
+
+(4) Dovecot as Identity Provider:
+.B Dovecot
+can be used as authentication backend in case a
+.I '='
+is included as
+.I password
+token. Assuming
+.B doveadm
+is in the path, a particular
+.B auth-qmail
+listener (socket) is tested by
+.I doveadm
+with the arguments
+.I \'auth test -a\'
+provided the socket is available via
+.IR \'-s\ authsocket\' .
+
+
+The definition of the auth socket
+needs to be included in
+.BR Dovecot 's
+control file in the following way:
+
+.EX
+service auth {
+ unix_listener /var/run/dovecot/auth-qmail {
+ mode = 0600
+ user = qmaild
+ group = nofiles
+ }
+}
+.EE
+
+Reversely, this socket has to be
+specified as calling argument for
+.I qmail-authuser
+providing
+.I -s /var/run/dovecot/auth-sqmail
+together with an additional executable (true).
+The name of the auth socket can
+be freely chosen.
+
+A particular authentication method
+can be specified by means of
+.I -x service=authmethod
+in the call of
+.BR qmail-authuser .
+Check the
+.b doveadmn
+documentation for particular authentication methods,
+typically available as \fIsmtp\fR and \fIpop3\fR.
+
+Note: All authentication storage and query mechanism
+can be used concurrently, depending on the settings
+of the
+.I authuser
+and
+.I password
+token in
+.IR SQMAIL/users/authuser .
+.SH "SECURITY"
+.B qmail-authuser
+is invoked in the environment of
+.B qmail-smtpd
+or
+.B qmail-popup
+which is typically run as user
+.IR qmaild .
+The file
+.I SQMAIL/users/authuser
+shall be
+.I qmaild
+owned and belonging to the group
+.I sqmail
+and SHOULD NOT be readble by the \fIworld\fR.
+
+Since the given
+.I authuser
+token is visible in the email, it could be typically chosen as
+.I user@domain
+making it usable for virtual domain managers and allowing
+a common
+.I password
+for ESMTP/IMAP4/POP3 services.
+
+The included
+.I password
+token shall solely be used for ESMTP/IMAP4/POP3 authentication
+and should possess enough entropy.
+
+A sticky and root-owned
+.B qmail-authuser
+is a potential security risk.
+.SH "PASSWORD HASHES"
+Instead of plaintext passwords, additionally
+MD5, SHA1, or SHA256 hashes of the passwords may be used. However,
+in spite of rainbow tables this requires none-trivial passwords.
+.SH "AUTH METHODS"
+In case hashed passwords or the UNIX passwords are used,
+only the auth methods USER, PLAIN, and LOGIN are working.
+Those methods are only secure on encrypted
+connections and otherwise are easy victim of an eavesdropper.
+Challenge/Response methods - like CRAM-MD5 and APOP -
+require having access to the plain-text passwords. For
+.B vchkpw
+C/R is possible querying the local \'vpopmail\' database.
+.SH "EXIT CODES"
+In case the provided
+.I authuser
+or
+.I userid
+does not exist, or the digest and the response,
+or the passwords
+differ,
+.B qmail-authuser
+exits 1.
+If
+.B qmail-authuser
+is misused, it may instead exit 2.
+In case
+.I SQMAIL/users/authuser
+is not readeable,
+.B qmail-authuser
+exits 110.
+If there is a temporary problem checking the password,
+.B qmail-authuser
+exits 111.
+.SH "CREDITS"
+The MD5 implmentation originates from RSA though now supporting a
+64 bit OS. SHA1 has been created by Steve Reid, and
+SHA256 was done by Brad Conte, all released in the Public Domain.
+Drew Wells receives credits for putting me into the current direction.
+.SH "SEE ALSO"
+qmail-popup(8),
+qmail-smtpd(8),
+checkpassword(8),
+vchkpw(8),
+checkvpw(8),
+doveadm(1),
+doveadm-auth(1).
diff --git a/man/qmail-badloadertypes.9 b/man/qmail-badloadertypes.9
new file mode 100644
index 0000000..daf07cf
--- /dev/null
+++ b/man/qmail-badloadertypes.9
@@ -0,0 +1,48 @@
+.TH s/qmail: qmail-badloadertypes 8
+
+.SH "NAME"
+qmail-badloadertypes \- prepare badloadertypes for qmail-smtpd
+.SH SYNOPSIS
+.B qmail-badloadertypes
+
+.SH "DESCRIPTION"
+.B qmail-badloadertypes
+reads the instructions in
+.B SQMAIL/control/badloadertypes
+and writes them into
+.B SQMAIL/control/badloadertypes.cdb
+in a binary format suited
+for quick access by
+.BR qmail-smtpd .
+
+If there is a problem with
+.BR control/badloadertypes ,
+.B qmail-badloadertypes
+complains and leaves
+.B control/badloadertypes.cdb
+alone.
+
+.B qmail-badloadertypes
+ensures that
+.B SQMAIL/control/badloadertypes.cdb
+is updated atomically,
+so
+.B qmail-smtpd
+never has to wait for
+.B qmail-badloadertypes
+to finish.
+However,
+.B qmail-badloadertypes
+makes no attempt to protect against two simultaneous updates of
+.BR control/badloadertypes.cdb .
+For convenience,
+.B qmail-badloadertypes
+allows comments (lines starting with '#') and
+copies only the significant leading characters to
+.BR control/badloadertypes.cdb .
+
+The binary
+.B control/badloadertypes.cdb
+format is portable across machines.
+.SH "SEE ALSO"
+qmail-smtpd(8)
diff --git a/man/qmail-badmimetypes.9 b/man/qmail-badmimetypes.9
new file mode 100644
index 0000000..b9dab16
--- /dev/null
+++ b/man/qmail-badmimetypes.9
@@ -0,0 +1,46 @@
+.TH s/qmail: qmail-badmimetype 8
+.SH NAME
+qmail-badmimetypes \- prepare badmimetypes for qmail-smtpd
+.SH SYNOPSIS
+.B qmail-badmimetype
+.SH DESCRIPTION
+.B qmail-badmimetypes
+reads the instructions in
+.B SQMAIL/control/badmimetypes
+and writes them into
+.B SQMAIL/control/badmimetypes.cdb
+in a binary format suited
+for quick access by
+.BR qmail-smtpd .
+
+If there is a problem with
+.BR control/badmimetypes ,
+.B qmail-badmimetypes
+complains and leaves
+.B control/badmimetypes.cdb
+alone.
+
+.B qmail-badmimetypes
+ensures that
+.B control/badmimetypes.cdb
+is updated atomically,
+so
+.B qmail-smtpd
+never has to wait for
+.B qmail-badmimetypes
+to finish.
+However,
+.B qmail-badmimetypes
+makes no attempt to protect against two simultaneous updates of
+.BR control/badmimetypes.cdb .
+For convenience,
+.B qmail-badmimetypes
+allows comments (lines starting with '#') and
+copies only the significant leading characters to
+.BR control/badmimetypes.cdb .
+
+The binary
+.B control/badmimetypes.cdb
+format is portable across machines.
+.SH "SEE ALSO"
+qmail-smtpd(8)
diff --git a/man/qmail-clean.8 b/man/qmail-clean.8
new file mode 100644
index 0000000..b4cbc1d
--- /dev/null
+++ b/man/qmail-clean.8
@@ -0,0 +1,13 @@
+.TH s/qmail: qmail-clean 8
+.SH NAME
+qmail-clean \- clean up the queue directory
+.SH SYNOPSIS
+.B qmail-clean
+.SH DESCRIPTION
+.B qmail-clean
+reads a cleanup command from descriptor 0,
+performs the cleanup,
+prints the results to descriptor 1,
+and repeats.
+.SH "SEE ALSO"
+qmail-send(8)
diff --git a/man/qmail-command.8 b/man/qmail-command.8
new file mode 100644
index 0000000..33f28d7
--- /dev/null
+++ b/man/qmail-command.8
@@ -0,0 +1,149 @@
+.TH s/qmail: qmail-command 8
+.SH NAME
+qmail-command \- user-specified mail delivery program
+.SH SYNOPSIS
+in
+.BR .qmail\fIext :
+.B |\fIcommand
+.SH DESCRIPTION
+.B qmail-local
+will, upon your request,
+feed each incoming mail message through a program of your choice.
+
+When a mail message arrives,
+.B qmail-local
+runs
+.B sh -c \fIcommand
+in your home directory.
+It makes the message available on
+.IR command 's
+standard input.
+
+.B WARNING:
+The mail message does not begin with
+.BR qmail-local 's
+usual
+.B Return-Path
+and
+.B Delivered-To
+lines.
+
+Note that
+.B qmail-local
+uses the same file descriptor for every delivery
+in your
+.B .qmail
+file, so it is not safe for
+.I command
+to fork a child that
+reads the message in the background while the parent exits.
+.SH "EXIT CODES"
+.IR command 's
+exit codes are interpreted as follows:
+0 means that the delivery was successful;
+99 means that the delivery was successful,
+but that
+.B qmail-local
+should ignore all further delivery instructions;
+100 means that the delivery failed permanently (hard error);
+111 means that the delivery failed but should be tried again
+in a little while (soft error).
+
+Currently 64, 65, 70, 76, 77, 78, and 112 are considered hard errors,
+and all other codes are considered soft errors,
+but
+.I command
+should avoid relying on this.
+.SH "ENVIRONMENT VARIABLES"
+.B qmail-local
+supplies several useful environment variables to
+.IR command .
+.B WARNING:
+These environment variables are not quoted.
+They may contain special characters.
+They are under the control of a possibly malicious remote user.
+
+.B SENDER
+is the envelope sender address.
+.B NEWSENDER
+is the forwarding envelope sender address,
+as setup in
+.BR dot-qmail(5) .
+.B RECIPIENT
+is the envelope recipient address,
+.IR local@domain .
+.B USER
+is
+.IR user .
+.B HOME
+is your home directory,
+.IR homedir .
+.B HOST
+is the
+.I domain
+part of the recipient address.
+.B LOCAL
+is the
+.I local
+part.
+.B EXT
+is the
+address extension,
+.IR ext .
+
+.B HOST2
+is the portion of
+.B HOST
+preceding the last dot;
+.B HOST3
+is the portion of
+.B HOST
+preceding the second-to-last dot;
+.B HOST4
+is the portion of
+.B HOST
+preceding the third-to-last dot.
+
+.B EXT2
+is the portion of
+.B EXT
+following the first dash;
+.B EXT3
+is the portion
+following the second dash;
+.B EXT4
+is the portion
+following the third dash.
+.B DEFAULT
+is the portion
+corresponding to the
+.B default
+part of the
+.BR .qmail\- ...
+file name;
+.B DEFAULT
+is not set if
+the file name does not end with
+.BR default .
+
+.B DTLINE
+and
+.B RPLINE
+are the usual
+.B Delivered-To
+and
+.B Return-Path
+lines,
+including newlines.
+.B UFLINE
+is the UUCP-style
+.B From_
+line that
+.B qmail-local
+adds to
+.IR mbox -format
+files.
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5),
+qmail-local(8)
diff --git a/man/qmail-control.9 b/man/qmail-control.9
new file mode 100644
index 0000000..5aa1de6
--- /dev/null
+++ b/man/qmail-control.9
@@ -0,0 +1,110 @@
+.TH s/qmail: qmail-control 5
+.SH "NAME"
+qmail-control \- qmail configuration files
+.SH "INTRODUCTION"
+You can change the behavior of the
+.B qmail
+system by modifying
+.BR s/qmail 's
+.I control files
+in
+.BR SQMAIL/control .
+
+.B s/qmail
+can survive with just one control file,
+.IR me ,
+containing the
+fully-qualified name of the current host.
+This file is used as the default for
+other hostname-related control files.
+
+Comments (\'# comment\') are allowed
+in
+.IR badmailfrom ,
+.IR badmimetypes ,
+.IR badloadertypes ,
+.IR dkimdomains ,
+.IR locals ,
+.IR percenthack ,
+.IR qmqpservers ,
+.IR rcpthosts ,
+.IR smtproutes ,
+.IR srsdomains ,
+.IR tlsdestinations ,
+and
+.IR virtualdomains .
+Trailing spaces and tabs are allowed in any control.
+
+The following table lists all control files
+other than
+.IR me .
+See the corresponding man pages for further details.
+
+.RS
+.nf
+.ta 5c 10c
+control default used by
+
+.I authsenders \fR(none) \fRqmail-remote
+.I badhelo \fR(none) \fRqmail-smtpd
+.I badmailfrom \fR(none) \fRqmail-smtpd
+.I badmimetypes \fR$BADMIMETYPE \fRqmail-smtpd
+.I badloadertypes \fR$BADLOADERTYPE \fRqmail-smtpd
+.I badrcptto \fR(none) \fRqmail-smtpd
+.I bouncefrom \fRMAILER-DAEMON \fRqmail-send
+.I bouncehost \fIme \fRqmail-send
+.I bouncemaxbytes \fI0 \fRqmail-send
+.I concurrencylocal \fR10 \fRqmail-send
+.I concurrencyremote \fR20 \fRqmail-send
+.I dkimdomains \fR(none) \fRqmail-dksign
+.I domaincerts \fR(none) \fRqmail-remote
+.I domainips \fR(none) \fRqmail-remote, \frqmail-smtpam
+.I defaultdomain \fIme \fRqmail-inject
+.I defaulthost \fIme \fRqmail-inject
+.I databytes \fR$DATABYTES \fRqmail-smtpd
+.I doublebouncehost \fIme \fRqmail-send
+.I doublebounceto \fRpostmaster \fRqmail-send
+.I envnoathost \fIme \fRqmail-send
+.I helohost \fIme \fRqmail-remote
+.I idhost \fIme \fRqmail-inject
+.I localiphost \fIme \fRqmail-smtpd
+.I locals \fIme \fRqmail-send
+.I morercpthosts \fR(none) \fRqmail-smtpd
+.I mailfromrules \fR(none) \fRqmail-smtpd
+.I percenthack \fR(none) \fRqmail-send
+.I plusdomain \fIme \fRqmail-inject
+.I qmqpservers \fR(none) \fRqmail-qmqpc
+.I qmtproutes \fR(none) \fRqmail-remote
+.I queuelifetime \fR604800 \fRqmail-send
+.I rcpthosts \fR(none) \fRqmail-smtpd
+.I recipients \fR(none) \fRqmail-smtpd
+.I spfexplain \fRSPF_DEFEXP \fRqmail-smtpd
+.I spflocalrules \fR(none) \fRqmail-smtpd
+.I srsdomains \fR(none) \fRsrsforward, \fRsrsreverse
+.I smtpgreeting \fIme \fRqmail-smtpd
+.I smtproutes \fR(none) \fRqmail-remote
+.I timeoutconnect \fR60 \fRqmail-remote, \fRqmail-smtpam
+.I timeoutremote \fR1200 \fRqmail-remote, \fRqmail-smtpam
+.I timeoutsmtpd \fR1200 \fRqmail-smtpd
+.I tlsdestinations \fR(none) \fRqmail-remote, \fRqmail-smtpam
+.I virtualdomains \fR(none) \fRqmail-send
+.fi
+
+.RE
+.IR Defaultvalues
+following a $ sign (ie. $RELAYCLIENT) depend on the
+corresponding environment variable.
+
+.IR Use
+.BR qmail-showctl
+to display actual settings.
+
+.SH "SEE ALSO"
+srsforward(1),
+qmail-dksgin(8),
+qmail-inject(8),
+qmail-qmqpc(8),
+qmail-remote(8),
+qmail-send(8),
+qmail-showctl(8),
+qmail-smtpd(8).
diff --git a/man/qmail-dkim.8 b/man/qmail-dkim.8
new file mode 100644
index 0000000..53463e9
--- /dev/null
+++ b/man/qmail-dkim.8
@@ -0,0 +1,217 @@
+.TH s/qmail: qmail-dkim 8
+.SH "NAME"
+qmail-dkim \- libdkim implementation for s/qmail
+.SH "SYNOPSIS"
+.B qmail-dkim
+[
+.I -h
+.I -v
+.I -V
+.I -s[ecckey]
+.I -b[1|2|3]
+.I -c[s|t|u]
+.I -d domain
+.I -i identity
+.I -l
+.I -q
+.I -t
+.I -x expire_time
+.I -y selector
+.I -Y selector2
+.I -z[1|2|3|4|5]
+]
+.I in_message
+.I RSA_private_key
+.I out_message
+.I Ed25519_private_key
+.SH "DESCRIPTION"
+.B qmail-dkim
+is the implementation of
+.B libdkim
+for s/qmail providing API compatibility
+and supporting RSA and Ed25519 DKIM signatures
+in single or hybrid mode.
+In hybrid mode, two
+.I private keys
+and two
+.I selectors
+need to be provided.
+.B qmail-dkim
+supports distinct operations:
+.TP 5
+.B qmail-dkim \fI-s in_message RSA_private_key out_message\fR
+DKIM signes
+.I in_message
+with the given
+.I private_key
+and returns
+.IR out_message .
+.TP 5
+.B qmail-dkim \fI-s in_message RSA_private_key out_message Ed255_private_key\fR
+signs
+.I in_message
+with both a RSA
+.I RSA_private_key
+and a
+.IR Ed25519_private_key.
+Here, the RSA default selector is \fIdefault\fR and the
+Ed25519 default selector is \fIeddy\fR; both subject of change.
+.TP 5
+.B qmail-dkim \fI-v in_message\fR
+verifies the
+.IR in_message .
+.SH "DKIM FORMATS"
+DKIM needs a common understanding of the attributes
+subject for signing and verification.
+The following attributes can be set:
+.TP 5
+-c
+is the 'canonicalization', thus how a validiation client
+should deal with signature verification of the
+message headers and/or body. Here, the choices are given
+via an appended character:
+.I r
+relax on header,
+.I s
+simple (strict) on message body,
+.I t
+relax/simple, or eventually
+.I u
+simple relaxed.
+Finally, the hash function to be used in the signature
+can be given as
+.TP 5
+-z
+following either with
+.I 1
+using sha1, or
+.I 2
+using sha256, or finally as default
+.I 3
+providing both signature values in the mail header.
+.I 4
+telling
+.B qmail-dkim
+to use the Ed25519 signature scheme.
+.I 5
+allows
+.B qmail-dkim
+to attach both a
+.I RSA-SHA256
+as well as a
+.I Ed25519
+signature to the message, which considered to be a
+.I hybrid
+mode.
+
+.SH "DKIM SIGNING"
+.B qmail-dkim
+will include (several) message headers detailing the
+.B DKIM signature
+with at least the following fields:
+.TP 3
+a
+=<signature type>
+.TP 3
+c
+=<used canoncicalization>
+.TP 3
+s
+=<selector>
+.TP 3
+d
+=<identity>
+.TP 3
+i
+=<identifier>
+.TP 3
+h
+=<included header1:header2:...>
+.TP 3
+bh
+=<hash of the canonicalized body until its upper limit length; if given>
+.TP 3
+b
+=<base64 encoded signature>
+.P
+Additional settings can be achieved using the following options:
+.TP 5
+.I -d domain
+is the signer's domain name and together with the prepended
+.TP 5
+.I -y selector
+it is used for the DNS TXT lookup of the public key; supporting
+mainly key roll-over. The first selector is used for RSA signatures.
+.TP 5
+.I -Y selector2
+Same as \fI-y\fR but now for Ed25519 signatures.
+.TP 5
+.I -I identifier
+giving an additional hint about the agent or identifier
+responsible for the signing like 'postmaster@domain'; defaults to
+.IR domain .
+.TP 5
+.I -t expire_time
+given in seconds, tells how log the signature is valid.
+It defaults to
+.I 604800
+secconds (seven days).
+.P
+Further, some more option fields can be displayed in the header:
+.TP 5
+.I -l
+include a body length tag.
+.TP 5
+.I -q
+include the query method tag.
+
+.SH "DKIM VERIFICATION"
+.B qmail-dkim
+as invoked by
+.B qmail-dkverify
+extracting the received DKIM header fields,
+and following the signature verification procedure
+as given here, while fetching the signer's
+.I public key
+using a DNS TXT lookup.
+Now, the respective header lines, and/or
+the message body will be hashed and compared
+against the values taken from the signatures.
+
+The results will be indicated by either return code
+.I 0
+in case of success,
+.I 1
+in case of mismatch, or
+.I -1
+if other failures were encountered.
+
+Given the call argument
+.TP 3
+-v
+.B qmail-dkim
+will provide the DKIM results
+.I pass
+or
+.I fail
+including verbose reasons on the commmand line.
+This is the legacy mode.
+
+.RE
+Rather, invoking
+.B qmail-dkim
+with argument
+.TP 3
+-V
+it communicates the results over a file interface
+to be picked up by
+.IR qmail-dkverify .
+
+.SH "SEE ALSO"
+qmail-queue(8),
+qmail-remote(8),
+qmail-dksign(8),
+qmail-dkverify(8),
+qmail-send(8),
+qmail-log(8).
+
diff --git a/man/qmail-dksign.9 b/man/qmail-dksign.9
new file mode 100644
index 0000000..08d310e
--- /dev/null
+++ b/man/qmail-dksign.9
@@ -0,0 +1,336 @@
+.TH s/qmail: qmail-dksign 8
+.SH "NAME"
+qmail-dksign \- DKIM sign outgoing messages
+.SH "SYNOPSIS"
+.B qmail-dksign
+.I host
+.I sender
+.I recip
+[
+.I recip ...
+]
+.SH "DESCRIPTION"
+.B qmail-dksign
+is a stub routine to be invoked by
+.B qmail-spawn
+in place of
+.B qmail-remote
+and is required to customize the signing policy
+for outgoing emails according to RFC 6893/8463 by means of
+.B qmail-dkim
+and finally to invoke
+.B qmail-remote
+for subsequent message delivery.
+
+.B qmail-dksign
+is also an extension to
+.B qmail-queue
+(with comparable permissions) using
+.I queue/dkim/<n>/<m>
+to provide a temporary but persistent staging
+area for outgoing messages to be DKIM signed.
+.SH "CONTROL FILE"
+.B qmail-dksign
+will be only called by
+.B qmail-rspawn
+if
+.I SQMAIL/control/dkimdomains
+is present.
+
+.IR dkimdomains :
+\'domain:selector[,selector2]|sdid|[auid|~]|expire|c:z:l\'
+allows multitenant and hybrid DKIM signing settings per sending
+.IR domain .
+
+.I domain
+is the sender's envelope domain in order to fetch the
+individually tailored DKIM signing paramaters for these.
+
+The following DKIM parameters can be specified:
+.TP 5
+.I selector
+is used as prepending name label for
+.IR domain :
+.IR selector._domainkey.domain .
+If not explicitely given, it defaults to
+.I default
+and is mostly used to support the key roll-over.
+.TP 5
+.I selector,selector2
+defines a hybrid selector and allows to provide
+two different selectors together
+with their private keys for concurrently signing of messages
+according to both the RSA-SHA256 and the Ed25519 algorithm.
+.TP 5
+.I sdid
+Here, you can overwrite the 'Signing Domain Identifier' (SDID),
+thus decouple the information given in the DKIM header from
+the envelope domain sender. This allows to setup common DNS
+public keys for several domains irrespectively of the sending
+.IR domain .
+.TP 5
+.I auid
+is the 'Agent/User Identifier' of the signer,
+in case it is not the sending
+.IR domain .
+In most cases it can be neglected and is obsolete.
+Rather, you can specifiy that the
+.I auid
+is always included as
+.I originator
+of the mail while providing the tilde symbol
+.I ~
+here as generic substitude.
+.TP 5
+.I expire
+determins the validity period of the signature in DKIM signed
+message. Due to the assumed key-rollover, it is limited
+and defaults to
+.I 604800
+secs since the email was signed.
+.TP 5
+.I c
+is the 'canonicalization'; thus how a validation client
+should deal with signature verification of the received
+message header and/or body. Here, the choices are
+.I r
+relax (allow mangling of whitespaces and cases; default)
+.I s
+simple (=strict)
+.I t
+relax on header, simple on body,
+.I u
+simple on header, relax on body.
+.TP 5
+.I z
+The signature algorithm can be specified as
+.I 1
+RSA with sha1,
+.I 2
+RSA with sha256 (as default), or
+.I 3
+providing both signature values in the mail header;
+.I 4
+Ed25519 ECC signatures.
+.I 5
+tells
+.B qmail-dksign
+to include both
+.I RSA-SHA256
+and
+.I Ed25519
+signatures in the mail header.
+Here, you need two different
+.I selectors
+and
+.IR private\ keys.
+Finally, setting
+.TP 5
+.I l
+(literal) advices
+.I qmail-dkim
+to include the body hash length (after canonicalization)
+to the DKIM header. This might be useful to cope with programs
+like mailing list servers adding a 'footer' to the mail
+after the signing operation has been completed.
+
+.RE
+RSA and Ed25519 signatures can now be used simultaneously
+while providing different keys available as distinct selectors.
+Those settings are handed-over to
+.B qmail-dkim
+to provide the signing of emails.
+.B qmail-dksign
+calls
+.B qmail-dkim
+to automatically include the query method
+.I q=dns/txt
+in the DKIM header.
+.SH "SELECTING DOMAINS FOR SIGNING"
+.B qmail-dksign
+can be instructed to sign all outgoing mails with the
+MTA's private key. This is achieved by simply using
+.I *:
+in
+.IR control/dkimdomains .
+Rather, the signing operation can be restricted for domains
+.B s/qmail
+has responsibility for, as given in
+.IR rcpthosts .
+This is commanded via
+.IR =: .
+Alternatively, in multitenant mode
+.B qmail-dksign
+may use domain specific DKIM settings and private keys
+for the sending domains and permitting parenting.
+Particular domains for which outgoing emails shall
+not be DKIM signed can be given as:
+.IR !nodkim.org .
+
+.EE
+ *:
+ =:default,eddy||~||:5
+ .heaven.com:||me@devil.com|500000|r:3
+ cloud1.com:january|postmaster@cloud.com|||t::l
+ cloud7.com:february|postmaster@cloud.com|||u:1
+ mybuddy.org:eddy||||:4
+ !nodkim.org:
+.EX
+
+Note: The owner of the crypto material (public and private keys) is
+.IR qmailq .
+.SH "CRYPTO MATERIAL"
+.B qmail-dksign
+follows the conventions from
+.B qmail-remote
+to use the directory
+.I SQMAIL/ssl/domainkeys
+to store public and private keys.
+
+Each
+.I domain
+may have its own key material resulting in a structure
+.IR SQMAIL/ssl/domainkeys/<domain>/ ,
+where the following keyfiles are expected:
+.TP 5
+.IR <selector>\ (default:\ 'default')
+is a mandatory symbolic link to
+.I [rsa|ed25519].private_<selector>
+used for signing.
+.TP 5
+.I rsa.public_<selector>
+is the DER-header enriched and base64 encoded RSA public key.
+.TP 5
+.I ed25519.public_<selector>
+is the 'naked' base64 encoded Ed25519 public key.
+
+.RE
+Here,
+.I <selector>
+is the name of the current
+.IR selector .
+After having generated keys and providing a new
+.IR selector ,
+this name has to be included as
+.I selector
+for the given domain in
+.I SQMAIL/control/dkimdomains
+in order to become active for signing.
+
+In case of
+.I hybrid\ signatures
+different selectors need to be given for the
+RSA and the Ed25519 keys each.
+They have to be provided concatinated by a colon in
+.IR dkimdomains .
+White spaces are not allowed. If the RSA selector is
+.IR default ,
+it can be omitted while followed by the colon and the
+Ed25519 selector name.
+
+.SH "SHARING KEYS FOR DIFFERENT DOMAINS"
+Different
+.I domains
+may however share common keys for signing and verification.
+In order to allow a common private key for signing, simply
+create symlinks for the others domains under
+.I SQMAIL/ssl/domainkeys/
+to the master one.
+.B qmail-dksign
+will now pick up those and use the provided key for signing.
+
+However, in general this reqires to deploy DKIM records
+for those domains sharing the same public key but require
+different domain names as distinguished DNS TXT records.
+
+Rather, you may want to publish just one
+DKIM DNS TXT record which is commonly shared for all
+concerning domains. Since the
+.I sending\ domain
+is used as default for the
+.IR SDID ,
+you need now to provide the same
+.I SDID
+explicitely for each domain of concern in
+.IR control/dkimdomains .
+
+The '<selector>' - and not the SDID -
+together with the literal
+.I ._domainkey.
+and the domain name defines the binding of the
+private key with the DKIM TXT record:
+.IR <selector>._domainkey.<domain> .
+
+.SH "GNERATING CRYPTO MATERIAL"
+Public/private keys can be generated by
+.I OpenSSL
+or
+.I LibreSSL
+or compatible TLS implementations and
+shall be provided in canonical format.
+The directory
+.I SQMAIL/ssl/domainkeys/
+and the resulting key needs to be readable by
+.IR qmailq ,
+the user
+.B qmail-dksign
+and
+.B qmail-dkim
+runs under. The private key shall
+.B NEVER
+exposed to the public.
+
+The script
+.B mkdkimkey
+is enabled to generate
+.I RSA
+or
+.I Ed25519
+private and public keys in the required format
+together with a
+.I BIND
+compliant DKIM DNS TXT record.
+.SH "RESPONSES"
+.B qmail-dksign
+may provide the following responses indicating an error:
+.TP 5
+Z
+Unable to switch to target directory.
+.TP 5
+Z
+Unable to create DKIM stage file: <file>
+.TP 5
+Z
+Unable to unlink DKIM stage file.
+.TP 5
+Z
+Unable to read control files.
+.TP 5
+Z
+Unable to read message.
+.TP 5
+D
+SMTP cannot transfer messages with partial final lines.
+.TP 5
+K
+can't read private file: <file> continue without signing.
+.TP 5
+Z
+unable to run qmail-remote. (=> configuration/permission error)
+.SH "SYSTEM IMPACT"
+.B qmail-dksign
+makes heavy use of system file descriptors.
+Given a high
+.I concurrencyremote
+you may run out of file descriptors which thus need to be enhanced
+either system-wide or for the specific users
+.I qmailr
+and
+.IR qmails .
+.SH "SEE ALSO"
+qmail-queue(8),
+qmail-remote(8),
+qmail-dkim(8),
+qmail-dkverify(8),
+qmail-log(8).
+
diff --git a/man/qmail-dkverify.8 b/man/qmail-dkverify.8
new file mode 100644
index 0000000..eb56952
--- /dev/null
+++ b/man/qmail-dkverify.8
@@ -0,0 +1,137 @@
+.TH s/qmail: qmail-dkverify 8
+.SH "NAME"
+qmail-dkverify \- verification of DKIM signatures in messages upon receipt
+.SH "SYNOPSIS"
+.B qmail-dkverify
+.SH "DESCRIPTION"
+.B qmail-dkverify
+is invoked faciliting the
+.I QMAILQUEUE(_EXTRA)
+mechanism.
+
+.SH "CALLING CHAIN"
+Verifying DKIM signatures upon receipt involves the
+following calling chain:
+
+1.
+.B qmail-smtpd
+called from
+.B sslserver
+/
+.BR tcpserver.
+
+2.
+.B qmail-dkverify
+called by the
+.I QMAILQUEUE(_EXTRA)
+mechanism as (first) replacement for
+.B qmail-queue
+as a stub.
+The incoming message is enhanced by the required CR
+characters line-by-line and stored in
+.IR queue/dkim/[split]/xyz .
+
+3.
+.B qmail-dkim
+is called by
+.B qmail-dkverify
+as a child performing the actual verification on
+.I queue/dkim/[split]/xyz
+while using a DNS TXT lookup for the sender's public key
+given in the DKIM message header and
+calling the fehQlibs DNS routines.
+The verification results are persisted at
+.IR queue/dkim/[split]/zyx .
+
+4.
+.B qmail-dkverify
+(as parent) reading the evaluated DKIM information from
+.B qmail-dkim
+and assembling a DKIM header line with the results
+prepended to the message.
+
+5.
+.B qmail-queue
+is finally called to queue the message for delivery.
+
+.SH "INVOCATION AND USAGE"
+In order to invoke
+.B qmail-dkverify
+the environment variable
+.I QMAILQUEUE="bin/qmail-dkverify"
+has to be populated in the context of
+.BR qmail-smtpd .
+
+Since
+.B qmail-smtpd
+is typically called by means of
+.B sslserver
+or
+.BR tcpserver ,
+the
+.I tcpd.smtp.cdb
+database as compiled by
+.B tcprules
+can be enhanced to include a line like
+.I :alllow:QMAILQUEUE="bin/qmail-dkverify"
+making use of the QMAILQUEUE_EXTRA mechanism.
+
+Alternatively, this environment variable could be
+defined as part of
+.BR qmail-smtpd 's
+start script which would now enable to
+provide DKIM signature checking for all
+SMTP sessions irrespectively of their origin.
+
+Usually,
+.B qmail-dkverify
+works in annotation mode only.
+
+However, setting additionally the environment variable
+.I DKIM="+"
+would command
+.B qmail-dkverify
+to reject mails failing the
+DKIM signature verification.
+In case of a rejection, the
+.B qmail-smtpd
+log shows the following message:
+.IR Reject::DKIM::Signature .
+
+Note:
+.B qmail-dkverify
+shall not be used for authenticated
+SMTP sessions, typically provided on the
+.I Submission
+port.
+
+.SH "LOGGING"
+No particular logging is currently forseen.
+Rather, each individual RFC 822 message is enhanced by
+the following header line in case a DKIM signature
+is recognized:
+
+.I X-Authentication-Results: sender dkim=[pass|fail (verbose error message)] MTA
+including the
+.I sender
+and the evaluating
+.I MTA
+as given in
+.IR control/me .
+In case of a \fIfail\fR, the verbose reason
+follows in parenthesis.
+
+.SH "SYSTEM IMPACT"
+.B qmail-dkverify
+does several reads and writes on the
+received messages. Apart from the cryptographic
+operations, this will slow down message exchange
+and increase the load on the system.
+
+.SH "SEE ALSO"
+qmail-queue(8),
+qmail-remote(8),
+qmail-dkim(8),
+qmail-dksign(8),
+qmail-log(8).
+
diff --git a/man/qmail-getpw.9 b/man/qmail-getpw.9
new file mode 100644
index 0000000..c246b0e
--- /dev/null
+++ b/man/qmail-getpw.9
@@ -0,0 +1,114 @@
+.TH s/qmail: qmail-getpw 8
+.SH NAME
+qmail-getpw \- give addresses to users
+.SH SYNOPSIS
+.B qmail-getpw
+.I local
+.SH DESCRIPTION
+In
+.BR s/qmail ,
+each user controls a vast array of local addresses.
+.B qmail-getpw
+finds the user that controls a particular address,
+.IR local .
+It prints six pieces of information,
+each terminated by NUL:
+.IR user ;
+.IR uid ;
+.IR gid ;
+.IR homedir ;
+.IR dash ;
+and
+.IR ext .
+The user's account name is
+.IR user ;
+the user's uid and gid in decimal are
+.I uid
+and
+.IR gid ;
+the user's home directory is
+.IR homedir ;
+and messages to
+.I local
+will be handled by
+.IR homedir\fB/.qmail\fIdashext .
+
+In case of trouble,
+.B qmail-getpw
+exits nonzero without printing anything.
+
+.B WARNING:
+The operating system's
+.B getpwnam
+function, which is at the heart of
+.BR qmail-getpw ,
+is inherently unreliable:
+it fails to distinguish between temporary errors and nonexistent users.
+Future versions of
+.B getpwnam
+should return ETXTBSY to indicate temporary errors
+and ESRCH to indicate nonexistent users.
+.SH "RULES"
+.B qmail-getpw
+considers an account in
+.B /etc/passwd
+to be a user if
+(1) the account has a nonzero uid,
+(2) the account's home directory exists (and is visible to
+.BR qmail-getpw ),
+and
+(3) the account owns its home directory.
+.B qmail-getpw
+ignores account names containing uppercase letters.
+.B qmail-getpw
+also assumes that all account names are shorter than 32 characters.
+
+.B qmail-getpw
+gives each user
+control over the basic
+.I user
+address and
+all addresses of the form
+.IR user\fBBREAK\fIanything .
+When
+.I local
+is
+.IR user ,
+.I dash
+and
+.I ext
+are both empty.
+When
+.I local
+is
+.IR user\fBBREAK\fIanything ,
+.I dash
+is a hyphen and
+.I ext
+is
+.IR anything .
+.I user
+may appear in any combination of uppercase and lowercase letters
+at the front of
+.IR local .
+
+A catch-all user,
+.BR alias ,
+controls all other addresses.
+In this case
+.I ext
+is
+.I local
+and
+.I dash
+is a hyphen.
+
+You can override all of
+.BR qmail-getpw 's
+decisions with the
+.B qmail-users
+mechanism, which is reliable, highly configurable, and much faster than
+.BR qmail-getpw .
+.SH "SEE ALSO"
+qmail-users(5),
+qmail-lspawn(8)
diff --git a/man/qmail-header.5 b/man/qmail-header.5
new file mode 100644
index 0000000..7142364
--- /dev/null
+++ b/man/qmail-header.5
@@ -0,0 +1,332 @@
+.TH s/qmail: qmail-header 5
+.SH NAME
+qmail-header \- format of a mail message
+.SH OVERVIEW
+At the top of every mail message is a
+highly structured
+.BR header .
+Many programs expect the header to carry certain information,
+as described below.
+The main function of
+.B qmail-inject
+is to make sure that each outgoing message has an appropriate header.
+
+For more detailed information, see
+.BR http://pobox.com/~djb/proto/immhf.html .
+.SH "MESSAGE STRUCTURE"
+A message contains a series of
+.I header fields\fR,
+a blank line,
+and a
+.IR body :
+
+.EX
+ Received: (qmail-queue invoked by uid 666);
+.br
+ 30 Jul 1996 11:54:54 -0000
+.br
+ From: djb@silverton.berkeley.edu (D. J. Bernstein)
+.br
+ To: fred@silverton.berkeley.edu
+.br
+ Date: 30 Jul 1996 11:54:54 -0000
+.br
+ Subject: Go, Bears!
+.br
+
+.br
+ I've got money on this one. How about you?
+.br
+
+.br
+ ---Dan (this is the third line of the body)
+.EE
+
+Each header field has a
+.IR name ,
+a colon,
+some
+.IR contents ,
+and a newline:
+
+.EX
+ Subject: Go, Bears!
+.EE
+
+The field contents may be folded across several lines.
+Each line past the first must begin with a space or tab:
+
+.EX
+ Received: (qmail-queue invoked by uid 666);
+.br
+ 30 Jul 1996 11:54:54 -0000
+.EE
+
+The field name must not contain spaces, tabs, or colons.
+Also, an empty field name is illegal.
+.B qmail-inject
+does not allow field names with unprintable characters.
+
+Case is irrelevant in field names:
+.B subject
+and
+.B SUBJECT
+and
+.B SuBjEcT
+have the same meaning.
+.SH "ADDRESS LISTS"
+Certain fields, such as
+.BR To ,
+contain
+.I address lists\fR.
+
+An address list contains some number of
+.I addresses
+or
+.I address groups\fR,
+separated by commas:
+
+.EX
+ a@b, c@d (Somebody), A Person <e@f>,
+.br
+ random group: g@h, i@j;, k@l
+.EE
+
+An
+.I address group
+has some text, a colon, a list of addresses,
+and a semicolon:
+
+.EX
+ random group: g@h, i@j;
+.EE
+
+An address can appear in several forms.
+The most common form is
+.IR box@host .
+
+Every address must include a host name.
+If
+.B qmail-inject
+sees a lone box name
+it adds the
+.I default host name\fR.
+
+All host names should be fully qualified.
+.B qmail-inject
+appends the
+.I default domain name
+to any name without dots:
+
+.EX
+ djb@silverton -> djb@silverton.berkeley.edu
+.EE
+
+It appends the
+.I plus domain name
+to any name
+that ends with a plus sign:
+
+.EX
+ eric@mammoth.cs+ -> eric@mammoth.cs.berkeley.edu
+.EE
+
+A host name may be a dotted-decimal address:
+
+.EX
+ djb@[128.32.183.163]
+.EE
+
+RFC 822 allows mailbox names inside angle brackets
+to include
+.I source routes\fR,
+but
+.B qmail-inject
+strips all source routes out of addresses.
+.SH "SENDER ADDRESSES"
+.B qmail-inject
+looks for sender address lists in the following fields:
+.BR Sender ,
+.BR From ,
+.BR Reply-To ,
+.BR Return-Path ,
+.BR Return-Receipt-To ,
+.BR Errors-To ,
+.BR Resent-Sender ,
+.BR Resent-From ,
+.BR Resent-Reply-To .
+
+If there is no
+.B From
+field,
+.B qmail-inject
+adds a new
+.B From
+field with the name of the user invoking
+.B qmail-inject.
+
+RFC 822 requires that certain sender fields contain
+only a single address, but
+.B qmail-inject
+does not enforce this restriction.
+.SH "RECIPIENT ADDRESSES"
+.B qmail-inject
+looks for recipient address lists in the following fields:
+.BR To ,
+.BR Cc ,
+.BR Bcc ,
+.BR Apparently-To ,
+.BR Resent-To ,
+.BR Resent-Cc ,
+.BR Resent-Bcc .
+
+Every message must contain at least one
+.B To
+or
+.B Cc
+or
+.BR Bcc .
+.B qmail-inject
+deletes any
+.B Bcc
+field.
+If there is no
+.B To
+or
+.B Cc
+field,
+.B qmail-inject
+adds a line
+
+.EX
+ Cc: recipient list not shown: ;
+.EE
+
+This complies with RFC 822;
+it also works around some strange
+.B sendmail
+behavior, in case the message is passed through
+.B sendmail
+on another machine.
+.SH STAMPS
+Every message must contain a
+.B Date
+field, with the date in a strict format defined by RFC 822.
+If necessary
+.B qmail-inject
+creates a new
+.B Date
+field with the current date (in GMT).
+
+Every message should contain a
+.B Message-Id
+field.
+The field contents are a unique worldwide identifier for this message.
+If necessary
+.B qmail-inject
+creates a new
+.B Message-Id
+field.
+
+Another important field is
+.BR Received .
+Every time the message is sent from one system to another,
+a new
+.B Received
+field is added to the top of the message.
+.B qmail-inject
+does not create any
+.B Received
+fields.
+.SH "RESENT MESSAGES"
+A message is
+.I resent
+if it contains any of the following fields:
+.BR Resent-Sender ,
+.BR Resent-From ,
+.BR Resent-Reply-To ,
+.BR Resent-To ,
+.BR Resent-Cc ,
+.BR Resent-Bcc ,
+.BR Resent-Date ,
+.BR Resent-Message-ID .
+
+If a message is resent,
+.B qmail-inject
+changes its behavior as follows.
+
+It deletes any
+.B Resent-Bcc
+field (as well as any
+.B Bcc
+field);
+if there are no
+.B Resent-To
+or
+.B Resent-Cc
+fields,
+.B qmail-inject
+adds an appropriate
+.B Resent-Cc
+line.
+It does
+.I not
+add a
+.B Cc
+line,
+even if neither
+.B To
+nor
+.B Cc
+is present.
+
+If there is no
+.B Resent-From
+field,
+.B qmail-inject
+adds a new
+.B Resent-From
+field.
+It does
+.I not
+add a new
+.B From
+field.
+
+.B qmail-inject
+adds
+.B Resent-Date
+if one is not already present;
+same for
+.BR Resent-Message-Id .
+It does
+.I not
+add new
+.B Date
+or
+.B Message-Id
+fields.
+.SH "OTHER FEATURES"
+Addresses are separated by commas, not spaces.
+When
+.B qmail-inject
+sees an illegal space,
+it inserts a comma:
+
+.EX
+ djb fred -> djb, fred
+.EE
+
+.B qmail-inject
+removes all
+.B Return-Path
+header fields.
+
+.B qmail-inject
+also removes any
+.B Content-Length
+fields.
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-inject(8)
diff --git a/man/qmail-inject.8 b/man/qmail-inject.8
new file mode 100644
index 0000000..33d37e2
--- /dev/null
+++ b/man/qmail-inject.8
@@ -0,0 +1,309 @@
+.TH s/qmail: qmail-inject 8
+.SH NAME
+qmail-inject \- preprocess and send a mail message
+.SH SYNOPSIS
+.B qmail-inject
+[
+.B \-nNaAhH
+] [
+.B \-f\fIsender
+] [
+.I recip ...
+]
+.SH DESCRIPTION
+.B qmail-inject
+reads a mail message from its standard input,
+adds appropriate information to the message header,
+and invokes
+.B qmail-queue
+to send the message
+to one or more recipients.
+
+See
+.B qmail-header(5)
+for information on how
+.B qmail-inject
+rewrites header fields.
+
+.B qmail-inject
+normally exits 0.
+It exits 100 if it was invoked improperly
+or if there is a severe syntax error in the message.
+It exits 111 for temporary errors.
+.SH "ENVIRONMENT VARIABLES"
+For the convenience of users who do not run
+.B qmail-inject
+directly,
+.B qmail-inject
+takes many options through environment variables.
+
+The user name in the
+.B From
+header field is set by
+.BR QMAILUSER ,
+.BR MAILUSER ,
+.BR USER ,
+or
+.BR LOGNAME ,
+whichever comes first.
+
+The host name is normally set by the
+.I defaulthost
+control
+but can be overridden with
+.B QMAILHOST
+or
+.BR MAILHOST .
+
+The personal name is
+.BR QMAILNAME ,
+.BR MAILNAME ,
+or
+.BR NAME .
+
+The default envelope sender address is the same as the
+default
+.B From
+address,
+but it can be overridden with
+.B QMAILSUSER
+and
+.BR QMAILSHOST .
+It may also be modified by the
+.B r
+and
+.B m
+letters described below.
+Bounces will be sent to this address.
+
+If
+.B QMAILMFTFILE
+is set,
+.B qmail-inject
+reads a list of mailing list addresses,
+one per line,
+from that file.
+If To+Cc includes one of those addresses (without regard to case),
+.B qmail-inject
+adds a Mail-Followup-To field
+with all the To+Cc addresses.
+.B qmail-inject
+does not add Mail-Followup-To
+to a message that already has one.
+
+The
+.B QMAILINJECT
+environment variable
+can contain any of the following letters:
+.TP
+.B c
+Use address-comment style for the
+.B From
+field.
+Normally
+.B qmail-inject
+uses name-address style.
+.TP
+.B s
+Do not look at any incoming
+.B Return-Path
+field.
+Normally, if
+.B Return-Path
+is supplied, it sets the envelope sender address,
+overriding all environment variables.
+.B Return-Path
+is deleted in any case.
+.TP
+.B f
+Delete any incoming
+.B From
+field.
+Normally, if
+.B From
+is supplied, it overrides the usual
+.B From
+field created by
+.BR qmail-inject .
+.TP
+.B i
+Delete any incoming
+.B Message-ID
+field.
+Normally, if
+.B Message-ID
+is supplied, it overrides the usual
+.B Message-ID
+field created by
+.BR qmail-inject .
+.TP
+.B r
+Use a per-recipient VERP.
+.B qmail-inject
+will append each recipient address to the envelope sender
+of the copy going to that recipient.
+.TP
+.B m
+Use a per-message VERP.
+.B qmail-inject
+will append the current date and process ID to the envelope sender.
+.SH OPTIONS
+.TP
+.B \-a
+Send the message to all addresses given as
+.I recip
+arguments;
+do not use header recipient addresses.
+.TP
+.B \-h
+Send the message to all header recipient addresses.
+For non-forwarded messages, this means
+the addresses listed under
+.BR To ,
+.BR Cc ,
+.BR Bcc ,
+.BR Apparently-To .
+For forwarded messages, this means
+the addresses listed under
+.BR Resent-To ,
+.BR Resent-Cc ,
+.BR Resent-Bcc .
+Do not use any
+.I recip
+arguments.
+.TP
+.B \-A
+(Default.)
+Send the message to all addresses given as
+.I recip
+arguments.
+If no
+.I recip
+arguments are supplied,
+send the message to all header recipient addresses.
+.TP
+.B \-H
+Send the message to all header recipient addresses,
+and to all addresses given as
+.I recip
+arguments.
+.TP
+.B \-f\fIsender
+Pass
+.I sender
+to
+.B qmail-queue
+as the envelope sender address.
+This overrides
+.B Return-Path
+and all environment variables.
+.TP
+.B \-N
+(Default.)
+Feed the resulting message to
+.BR qmail-queue .
+.TP
+.B \-n
+Print the message rather than feeding it to
+.BR qmail-queue .
+.SH "CONTROL FILES"
+.TP 5
+.I defaultdomain
+Default domain name.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR defaultdomain ,
+which is probably not what you want.
+.B qmail-inject
+adds this name to any host name without dots,
+including
+.I defaulthost
+if
+.I defaulthost
+does not have dots.
+(Exception: see
+.IR plusdomain .)
+
+The
+.B QMAILDEFAULTDOMAIN
+environment variable
+overrides
+.IR defaultdomain .
+.TP 5
+.I defaulthost
+Default host name.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR defaulthost ,
+which is probably not what you want.
+.B qmail-inject
+adds this name to any address without a host name.
+.I defaulthost
+need not be the current host's name.
+For example,
+you may prefer that outgoing mail show
+just your domain name.
+
+The
+.B QMAILDEFAULTHOST
+environment variable overrides
+.IR defaulthost .
+.TP 5
+.I idhost
+Host name for Message-IDs.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR idhost ,
+which is certainly not what you want.
+.I idhost
+need not be the current host's name.
+For example, you may prefer to use fake
+host names in Message-IDs.
+However,
+.I idhost
+must be a fully-qualified name within your domain,
+and each host in your domain should use a different
+.IR idhost .
+
+The
+.B QMAILIDHOST
+environment variable overrides
+.IR idhost .
+.TP 5
+.I plusdomain
+Plus domain name.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR plusdomain ,
+which is probably not what you want.
+.B qmail-inject
+adds this name to any host name that ends with a plus sign,
+including
+.I defaulthost
+if
+.I defaulthost
+ends with a plus sign.
+If a host name does not have dots but ends with a plus sign,
+.B qmail-inject
+uses
+.IR plusdomain ,
+not
+.IR defaultdomain .
+
+The
+.B QMAILPLUSDOMAIN
+environment variable overrides
+.IR plusdomain .
+.SH "SEE ALSO"
+addresses(5),
+qmail-control(5),
+qmail-header(5),
+qmail-queue(8)
diff --git a/man/qmail-limits.9 b/man/qmail-limits.9
new file mode 100644
index 0000000..47f81f4
--- /dev/null
+++ b/man/qmail-limits.9
@@ -0,0 +1,33 @@
+.TH s/qmail: qmail-limits 7
+.SH "NAME"
+qmail-limits \- artificial limits in the qmail system
+
+.SH "DESCRIPTION"
+The
+.B qmail
+system is able to handle messages of any size,
+addresses of any size, mailing lists of any size, and so on,
+except as limited by the available memory and disk space.
+
+However, it imposes certain artificial limits:
+.TP 5
+1.
+.B qmail-lspawn
+silently limits the number of simultaneous local deliveries to SPAWN.
+.B qmail-rspawn
+silently limits the number of simultaneous remote deliveries to SPAWN.
+.TP 5
+2.
+.B qmail-queue
+rejects any message with an envelope address longer than 1000 characters.
+.TP 5
+3.
+.B qmail-lspawn
+truncates any overly long error report from a delivery program.
+It appends a note saying that it did so.
+
+.SH "SEE ALSO"
+qmail-lspawn(8),
+qmail-queue(8),
+qmail-rspawn(8),
+ulimit(3).
diff --git a/man/qmail-local.8 b/man/qmail-local.8
new file mode 100644
index 0000000..9074d4e
--- /dev/null
+++ b/man/qmail-local.8
@@ -0,0 +1,99 @@
+.TH s/qmail: qmail-local 8
+.SH NAME
+qmail-local \- deliver or forward a mail message
+.SH SYNOPSIS
+.B qmail-local
+[
+.B \-nN
+]
+.I user
+.I homedir
+.I local
+.I dash
+.I ext
+.I domain
+.I sender
+.I defaultdelivery
+.SH DESCRIPTION
+.B qmail-local
+reads a mail message
+and delivers it to
+.I user
+by the procedure described in
+.BR dot-qmail(5) .
+
+The message's envelope recipient is
+.IR local@domain .
+.B qmail-local
+records
+.I local@domain
+in a new
+.B Delivered-To
+header field without the virtual user name extension.
+If exactly the same
+.B Delivered-To: \fIlocal@domain
+already appears in the header,
+.B qmail-local
+bounces the message,
+to prevent mail forwarding loops.
+
+The message's envelope sender is
+.IR sender .
+.B qmail-local
+records
+.I sender
+in a new
+.B Return-Path
+header field.
+
+.I homedir
+is the user's home directory.
+It must be an absolute directory name.
+
+.I dash
+and
+.I ext
+identify the
+.B .qmail\fIdashext
+file used by
+.BR qmail-local ;
+see
+.BR dot-qmail(5) .
+Normally
+.I dash
+is either empty or a lone hyphen.
+If it is empty,
+.B qmail-local
+treats a nonexistent
+.B .qmail\fIext
+the same way as an empty
+.BR .qmail\fIext :
+namely, following the delivery instructions in
+.IR defaultdelivery .
+
+The standard input for
+.B qmail-local
+must be a seekable file,
+so that
+.B qmail-local
+can read it more than once.
+.SH "OPTIONS"
+.TP
+.B \-n
+Instead of reading and delivering the message,
+print a description of the delivery instructions.
+.TP
+.B \-N
+(Default.) Read and deliver the message.
+.SH "EXIT CODES"
+0 if the delivery is completely successful;
+nonzero if any delivery instruction failed.
+Exit code 111
+indicates temporary failure.
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5),
+qmail-command(8),
+qmail-queue(8),
+qmail-send(8),
+qmail-lspawn(8)
diff --git a/man/qmail-log.5 b/man/qmail-log.5
new file mode 100644
index 0000000..a7584e1
--- /dev/null
+++ b/man/qmail-log.5
@@ -0,0 +1,448 @@
+.TH s/qmail: qmail-log 5
+.SH NAME
+qmail-log \- s/qmail activity record
+.SH DESCRIPTION
+.B qmail-send
+prints a series of lines describing its activities.
+Each possible line is described below.
+.SH "STATUS"
+.TP
+.B status: local \fIl\fR/\fIL\fR remote \fIr\fR/\fIR\fR ...
+.B qmail-send
+is waiting for
+.I l
+local deliveries
+and
+.I r
+remote deliveries.
+The concurrency limits are
+.I L
+and
+.IR R .
+.TP
+.B status: exiting
+.B qmail-send
+is done.
+.SH "FATAL PROBLEMS"
+.TP
+.B alert: cannot start: ...
+.B qmail-send
+is unable to prepare itself for delivering messages;
+it is giving up.
+This normally indicates a serious configuration error,
+but it can be caused by a temporary lack of resources.
+.TP
+.B alert: oh no! lost ...
+One of the other daemons has died.
+.B qmail-send
+will exit as soon as possible.
+.SH "SERIOUS PROBLEMS"
+.TP
+.B alert: unable to append to bounce message...
+.B qmail-send
+is unable to record a permanent failure,
+usually because the disk is full.
+This is a very serious problem;
+.B qmail-send
+cannot proceed without recording the results.
+It will try again in ten seconds.
+.TP
+.B alert: out of memory...
+.B qmail-send
+tried to allocate more memory and failed.
+It will try again in ten seconds.
+.TP
+.B alert: unable to opendir...
+.B qmail-send
+is having trouble reading a file list from disk,
+usually because the system's file descriptor table is full,
+but possibly because permissions are set incorrectly.
+It will try again in ten seconds.
+.TP
+.B alert: unable to switch back...
+.B qmail-send
+was sent SIGHUP,
+and it is unable to reenter the queue directory.
+This is a very serious problem;
+.B qmail-send
+cannot proceed outside the queue directory.
+It will try again in ten seconds.
+.TP
+.B alert: unable to reread...
+.B qmail-send
+was sent SIGHUP,
+but it is unable to read the new controls.
+It will continue operating with the original controls.
+.SH "MESSAGES"
+.TP
+.B new msg \fIm\fR
+.B qmail-send
+is going to preprocess a queued message.
+The message number,
+.IR m ,
+is its disk inode number.
+After a message is removed from the queue,
+its number can be reused immediately.
+.TP
+.B info msg \fIm\fR: bytes \fIb\fR from <\fIs\fR> qp \fIq\fR uid \fIu\fR
+Message
+.I m
+contains
+.I b
+bytes;
+its envelope sender is
+.IR s ;
+it was queued by a user with user ID
+.IR u .
+.I q
+is a long-term queue identifier,
+the process ID of the
+.B qmail-queue
+that queued the message.
+.TP
+.B bounce msg \fIm\fR qp \fIq\fR
+Message
+.I m
+had some delivery failures.
+The long-term queue identifier of the bounce (or double-bounce) message
+is
+.IR q .
+.TP
+.B double bounce: discarding ...
+Message
+.I m
+was discarded due to an \'empty\' recipient in
+.
+.IR doublebounceto .
+.TP
+.B triple bounce: discarding ...
+Message
+.I m
+had some delivery failures,
+but it is already a double-bounce message,
+so it must be thrown away.
+Triple-bounce messages do not exist.
+.TP
+.B end msg \fIm\fR
+.B qmail-send
+is about to remove
+message
+.I m
+from the queue.
+.SH "DELIVERIES"
+.TP
+.B starting delivery \fId\fR: msg \fIm\fR to ...
+.B qmail-send
+is telling
+.B qmail-lspawn
+or
+.B qmail-rspawn
+to deliver message
+.I m
+to one recipient.
+The delivery number,
+.IR d ,
+starts at 1 and increases by 1 for each new delivery.
+.TP
+.B delivery \fId\fR: success: ...
+Delivery
+.I d
+was successful.
+.TP
+.B delivery \fId\fR: failure: ...
+Delivery
+.I d
+failed permanently.
+The message will bounce.
+.TP
+.B delivery \fId\fR: deferral: ...
+Delivery
+.I d
+failed temporarily.
+This recipient will be retried later.
+.TP
+.B delivery \fId\fR: report mangled, will defer
+There is a serious bug in
+.B qmail-lspawn
+or
+.BR qmail-rspawn .
+This recipient will be retried later.
+.SH "WARNINGS"
+.TP
+.B internal error: delivery report out of range
+.B qmail-lspawn
+or
+.B qmail-rspawn
+has supplied a report on a nonexistent delivery.
+This is a serious bug.
+.TP
+.B qmail-clean unable to clean up ...
+For some reason
+.B qmail-clean
+is unable to remove the indicated file.
+It will try again later.
+.TP
+.B trouble fsyncing ...
+.B qmail-send
+was unable to write to disk the results of preprocessing a queued message.
+It will try again later.
+.TP
+.B trouble in select
+There is an operating system bug.
+.TP
+.B trouble injecting bounce message...
+.B qmail-send
+was unable to queue a bounce message,
+usually because the disk is full.
+It will try again later.
+.TP
+.B trouble marking ...
+.B qmail-send
+was unable to record the result of a successful or permanently
+unsuccessful delivery.
+This means that the delivery will be tried again later.
+.TP
+.B trouble opening ...
+.B qmail-send
+was unable to open the list of local or remote recipients
+for a message.
+It will try again later.
+.TP
+.B trouble reading ...
+Either
+.B qmail-send
+is unable to read a recipient list,
+or it is unable to read the envelope of a queued
+message, or it is out of memory.
+Whatever it was doing, it will try again later.
+.TP
+.B trouble writing to ...
+.B qmail-send
+was unable to preprocess a queued message,
+usually because the disk is full.
+It will try again later.
+.TP
+.B unable to create ...
+.B qmail-send
+was unable to preprocess a queued message,
+usually because the disk is out of inodes.
+It will try again later.
+.TP unable to create .... [info,delivery]
+.B qmail-send
+could not setup a valid file descriptor.
+This is a fatal error.
+.TP
+.B unable to open ...
+.B qmail-send
+is unable to read the envelope of a queued message
+for preprocessing.
+It will try again later.
+.TP
+.B unable to start qmail-queue...
+.B qmail-send
+is unable to queue a bounce message,
+usually because the machine is almost out of memory.
+It will try again later.
+This can also be caused by incorrect settings of
+.B $QMAILQUEUE
+or errors in a program or script which
+.B $QMAILQUEUE
++points to.
+.TP
+.B unable to stat ...
+.B qmail-send
+is unable to obtain information about a file that should exist.
+It will try again later.
+.TP
+.B unable to unlink ...
+.B qmail-send
+is unable to remove a file.
+It will try again later.
+.TP
+.B unable to utime ...
+.B qmail-send
+is about to exit,
+and it is unable to record on disk
+the next scheduled delivery time for a message.
+The message will be retried as soon as
+.B qmail-send
+is restarted.
+.TP
+.B unknown record type in ...
+There is a serious bug in either
+.B qmail-queue
+or
+.BR qmail-send .
+
+.SH "UNIFIED SMTPD/POP3D LOGGING"
+.B qmail-smtpd
+and
+.B qmail-popup
+log additional information in a unified extensible format
+\fIAction::Type::Condition\fR \fIInformation\fR.
+
+.B Action
+is either
+.IR Reject ,
+.IR Accept ,
+or additionally
+.IR Info .
+
+The
+.B Type
+belongs to the following information:
+.TP
+.I SNDR
+the client's hostname,
+.TP
+.I SPF
+indicating SPF validation,
+.TP
+.I TLS
+labeling TLS connections,
+.TP
+.I AUTH
+for Authenticated sessions. Further
+.TP
+.I ORIG
+relates to the return path \fIF:<Return-Path>\fR, and
+.TP
+.I RCTP
+to the forwarding path \fIT:<Forwarding-Path>\fR, and finally
+.TP
+.I DATA
+to the message.
+
+.TP 0
+The following \fBConditions\fR are provided:
+.TP 4
+.I Bad_Helo
+the client's HELO/EHLO greeting string was found in
+.IR badhelo
+or rejected because of one of the following conditions indicated
+in the information section: '!' (HELO/EHLO not provided/empty)
+, '\.'/'*' (HELO/EHLO rejected due to a direct/wildmat match with entries in
+.IR badhelo ).
+.TP
+.I Bad_Loader
+the content of a base64 encoded MIME part matched an
+entry in
+.IR badloadertypes.cdb .
+.TP
+.I Bad_MIME
+a base64 encoded MIME part matched an entry n
+.IR badmimetypes.cdb .
+.TP
+.I Bad_Mailfrom
+the provided <Return-Path> matched an entry in
+.I badmailfrom
+additionally with the rejection conditions: '@' (address), '*'
+(wildmat), '-' (badmailfromunknown), and '+' (spoofing).
+.TP
+.I Bad_Rcptto
+the provided <Forwarding-Path> matched an entry in
+.IR badrcptto .
+.TP
+.I DNS_Helo
+the client's HELO/EHLO greeting did not match it's
+FQDN or no DNS A/MX RR was found as indicated with the
+following symbols: '=' (HELO/EHLO does not match
+.BR TCPREMOTEHOST )
+, 'A' (DNS A-Name lookup failed for HELO/EHLO)
+, 'M' (DNS MX lookup failed for HELO/EHLO).
+.TP
+.I DNS_MF
+no DNS MX RR was found for the <Return-Path>.
+.TP
+.I Failed_Rcptto
+the <Forwarding-Path> did not match entry in the provdided
+cdbs as per
+.IR recipients .
+.TP
+.I Invalid_Relay
+the none-RELAYCLIENT provided a <Forwarding-Path> not
+allowed as per
+.I rcpthosts
+or
+.IR morercpthosts.cdb .
+.TP
+.I Invalid_Sender
+the <Return-Path> of a RELAYCLIENT did not match the
+provided value of LOCALMFCHECK or did not match against
+.I mailfromrules.cdb
+or was not found in
+.I rcpthosts
+or
+.IR morercpthosts.cdb .
+.TP
+.I Invalid_Size
+the message size exceeded the maximum as provided by
+DATEBYTES or
+.IR databytes .
+.TP
+.I Toomany_Rcptto
+the number of Recipients ('RCPT TO:') exaggerated the
+value provided as MAXRECPIENTS.
+.TP
+.I Cipher
+TLS session used this cipher.
+.TP
+.I Missing
+depending on the context, either the required
+Start-TLS or AUTH s/qmail: is not granted.
+.TP
+.I Pam
+SMTP authentication was granted by pam.
+.TP
+.I Recipients_Rcptto
+the <Forwarding-Path> matched an entry in the cdbs available per
+.IR reccients .
+.TP
+.I Recipients_Verp
+the Forwarding-Path was recogized as VERP and matched an entry
+in the cdbs available per
+.IR recipients .
+.TP
+.I Recipients_Domain
+the Forwarding-Path matched a wildcard domain entry in the cdbs
+available per
+.IR recipients .
+.TP
+.I Rcpthosts_Rcptto
+the domain part of the <Forwarding-Path> matched an entry in
+.I rcpthosts
+or
+.IR morercpthosts.cdb .
+
+.TP 0
+The displayed \fBInformation\fR:
+
+.TP 4
+.I P:protocol
+the effective SMTP or POP3 protocol in use.
+.TP
+.I S:IP:FQDN
+the sender's IP and FQDN address available via
+TCPREMOTEIP(6) and TCPREMOTEHOST.
+.TP
+.I H:string
+the client's HELO/EHLO greeting string.
+.TP
+.I F:Return-Path
+the provided 'MAIL FROM:' address (if any).
+.TP
+.I T:Forwarding-Path
+the given 'RCPT TO:' address.
+.TP
+.I ?~ 'userid'
+in case of authentication the provided userid.
+.TP
+.I != 'DN'
+in case of a TLS session, the presented client's
+\'Subject\' Distinguished Name (DN) - if available
+(otherwise \'unknown\').
+
+.SH "SEE ALSO"
+qmail-send(8),
+qmail-smtpd(8),
+qmail-control(9)
diff --git a/man/qmail-lspawn.8 b/man/qmail-lspawn.8
new file mode 100644
index 0000000..e97a93d
--- /dev/null
+++ b/man/qmail-lspawn.8
@@ -0,0 +1,46 @@
+.TH s/qmail: qmail-lspawn 8
+.SH NAME
+qmail-lspawn \- schedule local deliveries
+.SH SYNOPSIS
+.B qmail-lspawn
+.I defaultdelivery
+.SH DESCRIPTION
+.B qmail-lspawn
+reads a series of local delivery commands from descriptor 0,
+invokes
+.B qmail-local
+to perform the deliveries,
+and prints the results to descriptor 1.
+It passes
+.I defaultdelivery
+to
+.B qmail-local
+as the default delivery instruction.
+
+.B qmail-lspawn
+invokes
+.B qmail-local
+asynchronously,
+so the results may not be in the same order as the commands.
+
+For each recipient address,
+.B qmail-lspawn
+finds out which local user controls that address.
+It first checks the
+.B qmail-users
+mechanism; if the address is not listed there, it invokes
+.BR qmail-getpw .
+.B qmail-lspawn
+then runs
+.B qmail-local
+under the user's uid and gid.
+It does not set up any supplementary groups.
+
+.B qmail-lspawn
+treats an empty mailbox name as a trash address.
+.SH "SEE ALSO"
+envelopes(5),
+qmail-users(5),
+qmail-getpw(8),
+qmail-send(8),
+qmail-local(8)
diff --git a/man/qmail-mfrules.9 b/man/qmail-mfrules.9
new file mode 100644
index 0000000..17d575f
--- /dev/null
+++ b/man/qmail-mfrules.9
@@ -0,0 +1,108 @@
+.TH s/qmail: qmail-mfrules 8
+.SH "NAME"
+qmail-mfrules \- prepare mfrules for qmail-smtpd
+.SH SYNOPSIS
+.B qmail-mfrules
+
+.SH "DESCRIPTION"
+.B qmail-mfrules
+reads the addresses provided in
+.BR SQMAIL/control/mailfromrules ,
+converts them into lowercase, and writes them into
+.B SQMAIL/control/mailfromrules.cdb
+in a binary format suited
+for quick access by
+.BR qmail-smtpd .
+
+If there is a problem with
+.BR control/mailfromrules ,
+.B qmail-mfrules
+complains and leaves
+.B control/mailfromrules.cdb
+alone.
+
+.B qmail-mfrules
+ensures that
+.B control/mailfromrules.cdb
+is updated atomically,
+so
+.B qmail-smtpd
+never has to wait for
+.B qmail-mfrules
+to finish.
+However,
+.B qmail-mfrules
+makes no attempt to protect against two simultaneous updates of
+.BR control/mailfromrules.cdb .
+
+The binary
+.B control/mailfromrules.cdb
+format is portable across machines.
+
+.SH "RULE FORMAT"
+A rule is one line. A file containing rules may also contain comments: lines
+beginning with # are ignored. All addresses are evaluated case-insensitive.
+
+Each rule contains an address, an ampersend sign '&', and a list of strings separated by
+commas to be used for 'Mail From: Address Verification' (MAV). When
+.BR qmail-smtpd (8)
+receives a connection from that address, it checks whether the received
+envelope sender address correspondes with a MAV string (from the right
+to the left).
+The MAV string for an address may be NULL in order to allow any envelope
+sender address. NULLSENDER envelope addresses are not subject of the MAV.
+
+.SH "RULE BASE"
+.BR qmail-smtpd (8)
+looks for rules with various addresses in the following order:
+.IP 1
+$TCPREMOTEINFO, if $TCPREMOTEINFO is set (e.g. by SMTP Authentication);
+.IP 2.
+$TCPREMOTEINFO@$TCPREMOTEIP, if $TCPREMOTEINFO is set;
+.IP 3.
+$TCPREMOTEINFO@=$TCPREMOTEHOST, if $TCPREMOTEINFO is set and $TCPREMOTEHOST is
+set;
+.IP 4.
+the dotted decimal $TCPREMOTEIP address;
+.IP 5.
+the compactified $TCPREMOTEIP6 address;
+.IP 6.
+=$TCPREMOTEHOST, if $TCPREMOTEHOST is set;
+.IP 7.
+shorter and shorter prefixes of $TCPREMOTEIP ending with a dot;
+.IP 8.
+shorter and shorter values of $TCPREMOTEIP6 ending with a colon;
+.IP 9.
+shorter and shorter suffixes of $TCPREMOTEHOST starting with a dot, preceded
+by =, if $TCPREMOTEHOST is set; and finally
+.IP 10.
+=, if $TCPREMOTEHOST is set.
+.P
+.B qmail-smtpd
+employes the first matching rule for the MAV check. You should use the
+.B -p
+option to
+.BR sslserver
+if you rely on $TCPREMOTEHOST here.
+
+For example, here are some rules:
+
+.EX
+ jsmith@virtualdomain.com&john.smith@virtualdomain.com
+ joe@18.23.0.32&joe@example.com
+ 18.23&@example.com
+ =.heaven.mil&God@heaven.mil,st.peter@heaven.mil,-angles@heaven.mil
+ fe80:&user@myhost.local
+ 2001::feh:abc9:&me@fehnet.com
+.EE
+
+.SH "IP-ADDRESSES"
+.B qmail-mfrules
+recognizes the dotted-decimal IPv4 and the compactified
+IPv6 addresses tokenized by the 'dot' or the 'colon' character
+and compares the respective parts from right to left.
+However, the CIDR address format is not supported (yet).
+
+
+.SH "SEE ALSO"
+qmail-smtpd(8)
diff --git a/man/qmail-mrtg.8 b/man/qmail-mrtg.8
new file mode 100644
index 0000000..5cbef21
--- /dev/null
+++ b/man/qmail-mrtg.8
@@ -0,0 +1,141 @@
+.TH s/qmail: qmail-mrtg 8
+
+.SH NAME
+qmail-mrtg \- prepare s/qmail logs for MRTG analysis
+.SH SYNOPSIS
+.B qmail-mrtg [ -1 | -2 | -3 | -4 | -5 | -6 | -a | -b | -c | -d | -e | -f | -g | -h | -i | -j | k | -z | -A | -B ] [time]
+
+.SH DESCRIPTION
+.B qmail-mrtg
+reads the
+.B multilog
+tagged
+.B s/qmail
+logs with TAI64N timestamps on standard input
+to produce a counter for specifc
+.B s/qmail
+events and display them on standard output
+suitable for MRTG processing.
+
+.SH USAGE
+.B qmail-mrtg
+can be used to analyse
+.BR qmail-send ,
+.BR qmail-smtpd ,
+and
+.B qmail-pop3d
+logs in order to feed the results into MRTG.
+
+Typically,
+.B qmail-mrtg
+is called by the
+.B crontab
+facility together with a configuration files telling
+.B qmail-mrtg
+what to analyse.
+
+.SH ARGUMENTS
+.B qmail-mrtg
+posses three different sets of commands.
+Reading
+.B qmail-send
+logs:
+.I -1
+Deliveries/TLS transmitted,
+.I -2
+Message KBytes enqueued,
+.I -3
+Local/Remote Concurrency,
+.I -4
+Failure/Deferred Messages,
+.I -5
+Bounces/Triple bounces,
+.I -6
+qmtp/qmtps Messages.
+
+.B qmail-smtpd
+logs:
+.I -a
+total sessions,
+.I -b
+accepted/rejected sessions,
+.I -c
+rejected sessions (MTA),
+.I -d
+rejected originator,
+.I -e
+rejected recipient,
+.I -f
+rejected data (Mime + Loader),
+.I -g
+rejected data (Virus + Spam),
+.I -h
+authenticated sessions,
+.I -i
+accepted/rejected TLS sessions,
+.I -j
+recognized/rejected SPF sessions.
+.I -k
+deferred SMTP sessions (greylisted).
+Summaries are provided by
+.I -z
+total sessions, including
+.B qmail-smtpd
+and
+.BR tcpserver /
+.BR sslserver /
+.BR rblsmtpd .
+
+.BR qmail-pop3d /
+.B qmail-popup
+logs:
+.I -A
+accepted/rejected POP3 user,
+.I -B
+.BR qmail-pop3d /
+.BR tcpserver /
+.B sslserver
+connections.
+
+The intervals to evaluate the information given on STDIN
+defaults to
+.IR 305\ secs
+and can be changed by the second argument for
+.BR qmail-mrtg .
+
+.SH "CONFIGURATION FILES"
+.B qmail-mrtg
+depends on a configuration file for each service.
+Sample configuration files are provided.
+
+.SH "CRON INVOCATION"
+Since
+.B qmail-mrtg
+typically is invoked by the
+.B cron
+facility, additional information neeeds to be supplied:
+
+.EX
+ */5 * * * * env LANG=C mrtg /etc/qmail-mrtg.send.cfg &>/dev/null
+ */5 * * * * env LANG=C mrtg /etc/qmail-mrtg.smtpd.cfg &>/dev/null
+ */5 * * * * env LANG=C mrtg /etc/qmail-mrtg.pop3d.cfg &>/dev/null
+.EE
+
+Note: The default interval of
+.IR 305\ secs
+allows a certain overlap for cron not to loose events at the very
+edge.
+
+.SH "CREDITS"
+.B MRTG
+is a program created by Tobias Oetiker and Dave Rand
+(http://oss.oetiker.ch/mrtg/).
+
+.SH "SEE ALSO"
+mrtg(1),
+crontab(5),
+cron(8),
+qmail-log(8),
+qmail-send(8),
+qmail-smtpd(8),
+qmail-popup(8).
diff --git a/man/qmail-newmrh.9 b/man/qmail-newmrh.9
new file mode 100644
index 0000000..941dc03
--- /dev/null
+++ b/man/qmail-newmrh.9
@@ -0,0 +1,41 @@
+.TH s/qmail: qmail-newmrh 8
+.SH NAME
+qmail-newmrh \- prepare morercpthosts for qmail-smtpd
+.SH SYNOPSIS
+.B qmail-newmrh
+.SH DESCRIPTION
+.B qmail-newmrh
+reads the instructions in
+.B SQMAIL/control/morercpthosts
+and writes them into
+.B SQMAIL/control/morercpthosts.cdb
+in a binary format suited
+for quick access by
+.BR qmail-smtpd .
+
+If there is a problem with
+.BR control/morercpthosts ,
+.B qmail-newmrh
+complains and leaves
+.B control/morercpthosts.cdb
+alone.
+
+.B qmail-newmrh
+ensures that
+.B control/morercpthosts.cdb
+is updated atomically,
+so
+.B qmail-smtpd
+never has to wait for
+.B qmail-newmrh
+to finish.
+However,
+.B qmail-newmrh
+makes no attempt to protect against two simultaneous updates of
+.BR control/morercpthosts.cdb .
+
+The binary
+.B control/morercpthosts.cdb
+format is portable across machines.
+.SH "SEE ALSO"
+qmail-smtpd(8)
diff --git a/man/qmail-newu.9 b/man/qmail-newu.9
new file mode 100644
index 0000000..a030794
--- /dev/null
+++ b/man/qmail-newu.9
@@ -0,0 +1,43 @@
+.TH s/qmail: qmail-newu 8
+.SH NAME
+qmail-newu \- prepare address assignments for qmail-lspawn
+.SH SYNOPSIS
+.B qmail-newu
+.SH DESCRIPTION
+.B qmail-newu
+reads the assignments in
+.B SQMAIL/users/assign
+and writes them into
+.B SQMAIL/users/assign.cdb
+in a binary format suited
+for quick access by
+.BR qmail-lspawn .
+
+If there is a problem with
+.BR users/assign ,
+.B qmail-newu
+complains and leaves
+.B users/assign.cdb
+alone.
+
+.B qmail-newu
+ensures that
+.B users/assign.cdb
+is updated atomically,
+so
+.B qmail-lspawn
+never has to wait for
+.B qmail-newu
+to finish.
+However,
+.B qmail-newu
+makes no attempt to protect against two simultaneous updates of
+.BR users/assign.cdb .
+
+The binary
+.B users/assign.cdb
+format is portable across machines.
+.SH "SEE ALSO"
+qmail-users(5),
+qmail-lspawn(8),
+qmail-pw2u(8)
diff --git a/man/qmail-pop3d.8 b/man/qmail-pop3d.8
new file mode 100644
index 0000000..14afa93
--- /dev/null
+++ b/man/qmail-pop3d.8
@@ -0,0 +1,46 @@
+.TH s/qmail: qmail-pop3d 8
+.SH NAME
+qmail-pop3d \- provide mail via POP3
+.SH SYNOPSIS
+.B qmail-pop3d
+.I maildirname
+.SH DESCRIPTION
+.B qmail-pop3d
+lets a user read and delete his mail through the network.
+
+Mail is stored in a
+.B maildir
+called
+.IR maildirname ,
+normally
+.BR Maildir ,
+in the user's home directory.
+
+.B qmail-pop3d
+is normally invoked
+under
+.BR qmail-popup ,
+which reads a username and password,
+and
+.BR qmail-authuser ,
+which checks the password and sets up environment variables.
+
+.B qmail-pop3d
+has a 20-minute idle timeout.
+
+.B qmail-pop3d
+supports TOP, USER, UIDL, STLS, and LAST.
+
+.B qmail-pop3d
+appends an extra blank line to every message
+to work around serious bugs in certain clients.
+
+.B qmail-pop3d
+is based on a program contributed by Russ Nelson.
+
+.SH "SEE ALSO"
+maildir(5),
+qmail-authuser(8),
+qmail-local(8),
+qmail-popup(8),
+qmail-log(8).
diff --git a/man/qmail-popup.8 b/man/qmail-popup.8
new file mode 100644
index 0000000..bc4aeef
--- /dev/null
+++ b/man/qmail-popup.8
@@ -0,0 +1,131 @@
+.TH s/qmail: qmail-popup 8
+.SH NAME
+qmail-popup \- read a POP username and password
+.SH SYNOPSIS
+.B qmail-popup
+.I hostname
+.I subprogram
+.SH DESCRIPTION
+.B qmail-popup
+reads a POP username and password from the network.
+It then runs
+.IR subprogram .
+
+.B qmail-popup
+expects descriptor 0 to read from the network
+and descriptor 1 to write to the network.
+It reads a username and password from descriptor 0
+in POP's USER-PASS style or APOP style.
+File descriptor 5 is used to provide additional logging.
+It invokes
+.IR subprogram ,
+with the same descriptors 0 and 1;
+descriptor 2 writing to the network;
+and descriptor 3 reading the username, a 0 byte, the password,
+another 0 byte,
+an APOP timestamp derived from
+.IR hostname ,
+and a final 0 byte.
+.B qmail-popup
+then waits for
+.I subprogram
+to finish.
+It prints an error message if
+.I subprogram
+crashes or exits nonzero.
+
+.B qmail-popup
+has a 20-minute idle timeout.
+
+.SH "AUTHENTICATION"
+.B qmail-popup
+supports both username/password and APOP authentication.
+This latter is invoked, once the
+environment variable
+.I POP3AUTH='apop'
+or
+.I POP3AUTH='+apop'
+is set.
+In this case, you need to provide a
+APOP-capable PAM, eg.
+.BR qmail-authuser .
+
+.B qmail-popup
+should be used only within a secure network.
+Otherwise an eavesdropper can steal passwords.
+Even if you use APOP,
+an active attacker can still take over the connection
+and wreak havoc.
+
+.SH "STLS/POP3S SUPPORT"
+.B qmail-popup
+can be adviced to work on a TLS encrypted connection.
+
+At first, using
+.B sslserver
+and binding
+.BR qmail-popup ,
+.B qmail-pop3d
+on (in particular) the POP3S port
+.I 995
+provides mandatory TLS encryption.
+
+Second, in case you provide
+the environment variable
+.I UCSPITLS=''
+together with
+.BR sslserver ,
+.B qmail-popup
+communicates with the
+.B sslserver
+program interface through a control socket,
+a reading and a writing pipe created dynamically
+during the session start after announcing
+.I STLS
+to the client, thus allowing TLS encryption on request.
+In case
+.IR UCSPITLS='!'
+is set, STLS is required; while setting
+.IR UCSPITLS='-'
+disables STLS.
+
+.SH "LOGGING"
+.B qmail-popup
+provides logging of accepted and rejected POP3 sessions
+using about the same format as
+.BR qmail-smtpd .
+The authentication mechanism is indicated via
+.I User
+in case the userid/password method was used, and
+.I Apop
+if APOP challenge/response was applicable.
+The communication protocol may be either
+.I POP3
+or
+.I POP3S
+for of a STLS/POP3S secured connection.
+The
+.I username
+provided for authentication is displayed after the
+sequence
+.IR '?~' .
+In case
+.B qmail-popup
+is setup requiring STLS by means of
+.IR UCSPITLS='!' ,
+the log displays 'Any' as auth method
+and 'unknown' as username.
+
+
+The log is available on file descriptor 5.
+In order to display the result use the redirection '5>&1'.
+
+.B qmail-popup
+is based on a program contributed by Russ Nelson.
+
+.SH "SEE ALSO"
+maildir(5),
+qmail-authuser(8),
+qmail-pop3d(8),
+qmail-log(8).
+
diff --git a/man/qmail-postgrey.8 b/man/qmail-postgrey.8
new file mode 100644
index 0000000..b2532ce
--- /dev/null
+++ b/man/qmail-postgrey.8
@@ -0,0 +1,90 @@
+.TH s/qmail: qmail-postgrey 8
+.SH NAME
+qmail-postgrey \- send SMTP connection data to greylisting server
+.SH SYNOPSIS
+.B qmail-postgrey ip%netid;port Mail From: Rcpt To: TCPREMOTEIP TCPREMOTEHOST
+.SH DESCRIPTION
+.B qmail-postgrey
+is usually invoked by
+.B qmail-smtpd
+automatically provissioning the SMTP connection information
+.IR Mail\ From: ,
+.IR Rcpt\ To: ,
+.IR TCPREMOTEIP
+and
+.I TCPREMOTEHOST
+to a greylising server given by
+.IR IPv4|IPv6%netid;port .
+.I port
+defaults to
+.I 60000
+and thus can be omitted.
+IPv6 LLU addresses can be specified
+adding the
+.I netid
+name following the percentage sign.
+.SH "GREYLISTING SERVER"
+Since there is neither a formal API defined for the
+greylisting lookup nor for the behavior and return
+codes of the greylisting server,
+.B qmail-postgrey
+only works well with
+.I David\ Schweikert's
+.B postgrey
+implementation.
+
+Here, the server's response upon recognizing the triple
+.RI CLIENT_IP ,
+.I (SMTP\ envelope)\ SENDER
+and
+.I (SMTP\ envelope)\ RECIPIENT
+is either
+.IR action=DUNNO ,
+.I action=PREPEND
+or
+.I action=DEFER_IF_PERMIT
+and in case of the last,
+.B qmail-postgrey
+returns with
+.I 10
+telling
+.B qmail-smtpd
+to respond to the client with a SMTP
+.I 450\ greylisted
+reply code. Otherwise
+.B qmail-postgrey
+returns
+.IR 0 .
+.SH "INVOCATION"
+Unlike for testing reasons,
+.B qmail-postgrey
+is called directly from
+.B qmail-smtpd
+in case the environment variable
+.I POSTGREY
+is defined and provissioned with the greylisting
+server's IP address (and perhaps netid and port)
+listening there.
+
+The environment variable
+.I POSTGREY
+is typically defined within
+.B sslserver\'s
+.IR cdb .
+Additionally,
+.I REPLY_GREYLISTED
+can be used as environment variable
+to provide some more descriptive
+information to the sending MTA which will eventually
+be visible in a bounce message.
+.SH "CREDITS"
+.B qmail-postgrey
+and its integration into
+.B qmail-smtpd
+is based on
+.I Jan\ Mojzis
+implementation and used by permission.
+.SH "SEE ALSO"
+qmail-control(5),
+qmail-smtpd(8),
+https://postgrey.schweikert.ch
diff --git a/man/qmail-pw2u.9 b/man/qmail-pw2u.9
new file mode 100644
index 0000000..269d1f4
--- /dev/null
+++ b/man/qmail-pw2u.9
@@ -0,0 +1,241 @@
+.TH s/qmail: qmail-pw2u 8
+.SH NAME
+qmail-pw2u \- build address assignments from a passwd file
+.SH SYNOPSIS
+.B qmail-pw2u
+[
+.B \-/ohHuUC
+]
+[
+.B \-c\fIchar
+]
+.SH DESCRIPTION
+.B qmail-pw2u
+reads a V7-format passwd file from standard input
+and prints a
+.BR qmail-users -format
+assignment file.
+
+A V7-format passwd file is a series of lines.
+Each line has the format
+
+.EX
+ user:password:uid:gid:gecos:home:shell
+.EE
+
+where
+.I user
+is an account name,
+.I uid
+and
+.I gid
+are the user id and group id of that account,
+and
+.I home
+is the account's home directory.
+.IR password ,
+.IR gecos ,
+and
+.I shell
+are ignored by
+.BR qmail-pw2u .
+
+If you put the output of
+.B qmail-pw2u
+into
+.BR SQMAIL/users/assign ,
+and then run
+.BR qmail-newu ,
+.B qmail-lspawn
+will obey the assignments printed by
+.BR qmail-pw2u .
+.B WARNING:
+After changing any users, uids, gids, or home directories
+in your passwd file,
+you must run
+.B qmail-pw2u
+and
+.B qmail-newu
+again if you want
+.B qmail-lspawn
+to see the changes.
+.SH RULES
+By default,
+.B qmail-pw2u
+follows the same rules as
+.BR qmail-getpw .
+It skips
+.I user
+if (1)
+.I uid
+is zero,
+(2)
+.I home
+does not exist,
+(3)
+.I user
+does not own
+.IR home ,
+or
+(4)
+.I user
+contains uppercase letters.
+It then gives each remaining
+.I user
+control over the basic
+.I user
+address and
+all addresses of the form
+.IR user\fBBREAK\fIanything .
+A catch-all user,
+.BR alias ,
+controls all other addresses.
+
+You may change these rules by setting up files in
+.BR SQMAIL/users :
+.TP
+.B include
+Allowed users, one per line.
+If
+.B include
+exists, and
+.I user
+is not listed in
+.BR include ,
+.I user
+is ignored.
+.TP
+.B exclude
+Ignored users, one per line.
+If
+.B exclude
+exists, and
+.I user
+is listed in
+.BR exclude ,
+.I user
+is ignored.
+.TP
+.B mailnames
+Replacement names for users.
+Each line has the form
+
+.EX
+ user:mailname1:mailname2:...
+.EE
+
+The addresses
+.I mailname1
+and
+.I mailname1\fBBREAK\fIext
+and
+.I mailname2
+and so on will be delivered
+to
+.IR user .
+
+.B WARNING:
+The addresses
+.I user
+and
+.I user\fBBREAK\fIext
+will not be delivered to
+.I user
+unless
+.I user
+is listed as one of the
+.IR mailname s.
+
+A line in
+.B mailnames
+is silently ignored if the user does not exist.
+.TP
+.B subusers
+Extra addresses.
+Each line has the form
+
+.EX
+ sub:user:pre:
+.EE
+
+.I sub
+will be handled by
+.IR home\fB/.qmail\-\fIpre ,
+where
+.I home
+is
+.IR user 's
+home directory;
+.I sub\fBBREAK\fIext
+will be handled by
+.IR home\fB/.qmail\-\fIpre\fB\-\fIext .
+.TP
+.B append
+Extra assignments,
+printed at the end of
+.BR qmail-pw2u 's
+output.
+.SH OPTIONS
+.TP
+.B \-o
+(Default.)
+Skip
+.I user
+if
+.I home
+does not exist (or is not visible to
+.BR qmail-pw2u ).
+Skip
+.I user
+if
+.I home
+is not owned by
+.IR user .
+.TP
+.B \-h
+Stop if
+.I home
+does not exist.
+This is appropriate if every user is supposed to have a home directory.
+Skip
+.I user
+if
+.I home
+is not owned by
+.IR user .
+.TP
+.B \-H
+Do not check the existence or ownership of
+.IR home .
+.TP
+.B \-U
+(Default.)
+Skip
+.I user
+if there are any uppercase letters in
+.IR user .
+.TP
+.B \-u
+Allow uppercase letters in
+.IR user .
+.TP
+.B \-c\fIchar
+Use
+.I char
+as the user-extension delimiter
+in place of
+.BR BREAK .
+.TP
+.B \-C
+Disable the user-extension mechanism.
+.TP
+.B \-/
+Use
+.IR home\fB/.qmail\-/ ...
+instead of
+.IR home\fB/.qmail\- ...
+.SH "SEE ALSO"
+qmail-users(5),
+qmail-lspawn(8),
+qmail-newu(8),
+qmail-getpw(8)
diff --git a/man/qmail-qmaint.8 b/man/qmail-qmaint.8
new file mode 100644
index 0000000..54342b4
--- /dev/null
+++ b/man/qmail-qmaint.8
@@ -0,0 +1,65 @@
+.TH s/qmail: qmail-qmaint 8
+.SH NAME
+qmail-qmaint \- queue maintenance
+.SH SYNOPSIS
+.B qmail-qmaint
+[
+.I -i
+]
+|
+[
+.I -d messid
+]
+.SH DESCRIPTION
+.B qmail-qmaint
+inspects
+.B s/qmail's
+queue and validates its consistancy.
+In
+.I -i
+interactive mode, individual fixes
+can be commanded.
+Queue maintanence also allows to remove
+particular messages from the queue referencing their
+.I messid
+as given by
+.B qmail-qread
+(without the leading pound sign '#') by means of
+.IR -d\ messid .
+Here, only pre-processed and bounce messages are taken
+into consideration.
+
+.B qmail-qmaint
+must be run either as root or with user id
+.I qmails
+and group id
+.IR sqmail .
+.SH "WARNING"
+It is strongly advised to use
+.B qmail-qmaint
+only in case
+.B qmail-send
+was shut down before. Queue inspection on a `sane` queue
+is however none-destructive.
+.SH "EXIT CODES"
+.B qmail-qmaint
+unlike
+.B qmail-queue
+prints diagnostics messages.
+It exits
+0 if
+it has successfully inspected the queue
+or the message has been deleted.
+It may exit
+99 in case of a warning, or
+100 if an operation can not be completed, or
+110 if a directory can not be accessed.
+.SH "SEE ALSO"
+qmail-qstat(8),
+qmail-qread(8),
+qmail-send(8),
+qmail-queue(9)
+.SH "CREDITS"
+.B qmail-qmaint
+is based on the program 'queue-fix'
+written be Eric Huss.
diff --git a/man/qmail-qmqpc.8 b/man/qmail-qmqpc.8
new file mode 100644
index 0000000..5a04e38
--- /dev/null
+++ b/man/qmail-qmqpc.8
@@ -0,0 +1,37 @@
+.TH s/qmail: qmail-qmqpc 8
+.SH NAME
+qmail-qmqpc \- queue a mail message via QMQP
+.SH SYNOPSIS
+.B qmail-qmqpc
+.SH DESCRIPTION
+.B qmail-qmqpc
+offers the same interface as
+.BR qmail-queue ,
+but it gives the message to a QMQP server
+instead of storing it locally.
+
+In a
+.B mini-qmail
+installation,
+.B qmail-queue
+is replaced with a symbolic link to
+.BR qmail-qmqpc .
+.SH "CONTROL FILES"
+.TP 5
+.I qmqpservers
+IP addresses of QMQP servers, one address per line and eventually
+include the name of the interface to bind to for IPv6 LLUs:
+
+.EX
+ 192.168.1.1
+ 2001:fefe::31
+ fe80::fefe:1%eth0
+.EE
+
+.B qmail-qmqpc
+will try each address in turn until it establishes a QMQP connection
+or runs out of addresses.
+.SH "SEE ALSO"
+qmail-control(5),
+qmail-queue(8),
+qmail-qmqpd(8)
diff --git a/man/qmail-qmqpd.8 b/man/qmail-qmqpd.8
new file mode 100644
index 0000000..1913a7e
--- /dev/null
+++ b/man/qmail-qmqpd.8
@@ -0,0 +1,25 @@
+.TH s/qmail: qmail-qmqpd 8
+.SH NAME
+qmail-qmqpd \- receive mail via QMQP
+.SH SYNOPSIS
+.B qmail-qmqpd
+.SH DESCRIPTION
+.B qmail-qmqpd
+receives mail messages via the Quick Mail Queueing Protocol (QMQP)
+and invokes
+.B qmail-queue
+to deposit them into the outgoing queue.
+.B qmail-qmqpd
+must be supplied several environment variables;
+see
+.BR tcp-environ(5) .
+
+.B qmail-qmqpd
+will relay messages to any destination.
+It should be invoked only for connections from preauthorized users.
+.SH "SEE ALSO"
+tcpserver(1),
+sslserver(1),
+tcp-environ(5),
+qmail-qmqpc(8),
+qmail-queue(8)
diff --git a/man/qmail-qmtpd.8 b/man/qmail-qmtpd.8
new file mode 100644
index 0000000..545ea8c
--- /dev/null
+++ b/man/qmail-qmtpd.8
@@ -0,0 +1,36 @@
+.TH s/qmail: qmail-qmtpd 8
+.SH NAME
+qmail-qmtpd \- receive mail via QMTP/QMTPS
+.SH SYNOPSIS
+.B qmail-qmtpd
+.SH DESCRIPTION
+.B qmail-qmtpd
+receives mail messages via the Quick Mail Transfer Protocol (QMTP)
+or the TLS secured QMTP (QMTPS) version
+and invokes
+.B qmail-queue
+to deposit them into the outgoing queue.
+.B qmail-qmtpd
+must be supplied several environment variables;
+see
+.BR tcp-environ(5) .
+In case a valid X.509 client certificate is recognized,
+QMTPS enables
+.I relaying
+of mail messages.
+
+.B qmail-qmtpd
+supports the
+.IR rcpthosts ,
+.IR morercpthosts ,
+.BR RELAYCLIENT ,
+.IR databytes ,
+and
+.B DATABYTES
+mechanisms described in
+.BR qmail-smtpd(8) .
+.SH "SEE ALSO"
+tcp-environ(5),
+qmail-control(5),
+qmail-queue(8),
+qmail-smtpd(8)
diff --git a/man/qmail-qread.8 b/man/qmail-qread.8
new file mode 100644
index 0000000..5774f6b
--- /dev/null
+++ b/man/qmail-qread.8
@@ -0,0 +1,25 @@
+.TH s/qmail: qmail-qread 8
+.SH NAME
+qmail-qread \- list outgoing messages and recipients
+.SH SYNOPSIS
+.B qmail-qread
+.SH DESCRIPTION
+.B qmail-qread
+scans the outgoing queue of messages.
+For each message it prints various human-readable information,
+including the date the message entered the queue,
+the number of bytes in the message,
+the message sender,
+and all the recipients still under consideration.
+
+.B qmail-qread
+must be run either as
+.B root
+or with user id
+.B qmails
+and group id
+.BR sqmail .
+.SH "SEE ALSO"
+qmail-qstat(8),
+qmail-qmaint(8),
+qmail-send(8)
diff --git a/man/qmail-qstat.8 b/man/qmail-qstat.8
new file mode 100644
index 0000000..e21068a
--- /dev/null
+++ b/man/qmail-qstat.8
@@ -0,0 +1,18 @@
+.TH s/qmail: qmail-qstat 8
+.SH NAME
+qmail-qstat \- summarize status of mail queue
+.SH SYNOPSIS
+.B qmail-qstat
+.SH DESCRIPTION
+.B qmail-qstat
+gives a human-readable breakdown
+of the number of messages at various spots in the mail queue.
+
+.B qmail-qstat
+must be run either as
+.B root
+or with group id
+.BR sqmail .
+.SH "SEE ALSO"
+qmail-qread(8),
+qmail-send(8)
diff --git a/man/qmail-queue.8 b/man/qmail-queue.8
new file mode 100644
index 0000000..b025c95
--- /dev/null
+++ b/man/qmail-queue.8
@@ -0,0 +1,199 @@
+.TH s/qmail: qmail-queue 8
+.SH NAME
+qmail-queue \- queue a mail message for delivery
+.SH SYNOPSIS
+.B qmail-queue
+.SH DESCRIPTION
+.B qmail-queue
+reads a mail message from descriptor 0.
+It then reads envelope information from descriptor 1.
+It places the message into the outgoing queue
+for future delivery by
+.BR qmail-send .
+
+The envelope information is
+an envelope sender address
+followed by a list of envelope recipient addresses.
+The sender address is preceded by the letter F
+and terminated by a 0 byte.
+Each recipient address is preceded by the letter T
+and terminated by a 0 byte.
+The list of recipient addresses is terminated by an extra 0 byte.
+If
+.B qmail-queue
+sees end-of-file before the extra 0 byte,
+it aborts without placing the message into the queue.
+
+Every envelope recipient address
+should contain a username,
+an @ sign,
+and a fully qualified domain name.
+
+.B qmail-queue
+always adds a
+.B Received
+line to the top of the message.
+Other than this,
+.B qmail-queue
+does not inspect the message
+and does not enforce any restrictions on its contents.
+However, the recipients probably expect to see a proper header,
+as described in
+.BR qmail-header(5) .
+
+Programs included with qmail which invoke
+.B qmail-queue
+will invoke the contents of
+.B QMAILQUEUE
+instead, if that environment variable is set.
+.SH "FILESYSTEM RESTRICTIONS"
+.B qmail-queue
+imposes two constraints on the queue structure:
+each
+.B mess
+subdirectory must be in the same filesystem as the
+.B pid
+directory; and each
+.B todo
+subdirectory must be in the same filesystem as the
+.B intd
+directory.
+.SH "EXIT CODES"
+.B qmail-queue
+does not print diagnostics.
+It exits
+0 if
+it has successfully queued the message.
+It exits between 1 and 99 if
+it has failed to queue the message.
+
+All
+.B qmail-queue
+error codes between 11 and 40
+indicate permanent errors:
+.TP 5
+.B 11
+Address too long.
+.TP
+.B 31
+Mail server permanently refuses to send the message to any recipients.
+(Not used by
+.BR qmail-queue),
+.TP
+.B 32
+Mail server does not accept the message.
+(The message includes an identified virus.)
+.TP
+.B 33
+Mail server does not accept the message.
+(The message is identified as spam.)
+.TP
+.B 34
+Mail server does not accept the message.
+(The message carries an invalid MIME attachment.)
+.PP
+All other
+.B qmail-queue
+error codes indicate temporary errors:
+.TP 5
+.B 51
+Out of memory.
+.TP
+.B 52
+Timeout.
+.TP
+.B 53
+Write error; e.g., disk full.
+.TP
+.B 54
+Unable to read the message or envelope.
+.TP
+.B 55
+Unable to read a configuration file.
+The virus scanner called via the
+.BR QHPSI
+returned with return code other then
+0 or QHPSIRC.
+.TP
+.B 56
+Problem making a network connection from this host.
+(Not used by
+.BR qmail-queue .)
+.TP
+.B 61
+Problem with the qmail home directory.
+.TP
+.B 62
+Problem with the queue directory.
+.TP
+.B 63
+Problem with queue/pid.
+.TP
+.B 64
+Problem with queue/mess.
+.TP
+.B 65
+Problem with queue/intd.
+.TP
+.B 66
+Problem with queue/todo.
+.TP
+.B 71
+Mail server temporarily refuses to send the message to any recipients.
+(Not used by
+.BR qmail-queue .)
+.TP
+.B 72
+Connection to mail server timed out.
+(Not used by
+.BR qmail-queue .)
+.TP
+.B 73
+Connection to mail server rejected.
+(Not used by
+.BR qmail-queue .)
+.TP
+.B 74
+Connection to mail server succeeded,
+but communication failed.
+(Not used by
+.BR qmail-queue .)
+.TP
+.B 81
+Internal bug; e.g., segmentation fault.
+.TP
+.B 91
+Envelope format error.
+.SH "QHPSI ARGUMENTS"
+The Qmail High Performance Scanner interface QHPSI allows
+.B qmail-queue
+to read up to seven arguments taken from the environment to be used
+as a call-interface for an external virus scanner:
+.TP 5
+.B QHPSI
+is set to the file name of the virus scanner, ie. QHPSI='/usr/local/bin/clamscan'.
+The path can be omitted, if the virus scanner is in the default path.
+.TP
+.B QHPSIARG1...3
+Optional additional arguments can be included here, ie. QHPSIARG1="--verbose".
+Useful to suppress output in case an email is
+clean and to enable mailbox support for the virus scanner.
+.TP
+.B QHPSIRC
+To specify the return code of the virus scanner in case of an infection; default is 1.
+.TP
+.B QHPSIMINSIZE
+The minimal size of the message to invoke the virus scanner; default is 0.
+A typical choice would be QHPSIMINSIZE=10000 (~10k).
+.TP
+.B QHPSIMAXSIZE
+The maximal size of the message to invoke the virus scanner; default is unrestricted.
+A typical choice would be QHPSIMAXSIZE=1000000 (~1M).
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-header(5),
+qmail-inject(8),
+qmail-qmqpc(8),
+qmail-send(8),
+qmail-smtpd(8)
diff --git a/man/qmail-recipients.9 b/man/qmail-recipients.9
new file mode 100644
index 0000000..04974fe
--- /dev/null
+++ b/man/qmail-recipients.9
@@ -0,0 +1,48 @@
+.TH s/qmail: qmail-recipients 8
+.SH NAME
+qmail-recipients \- prepare recipients for qmail-smtpd
+.SH SYNOPSIS
+.B qmail-recipients
+.SH DESCRIPTION
+.B qmail-recipients
+reads the addresses provided in
+.BR SQMAIL/users/recipients ,
+converting into lowercase, and writes them into
+.B SQMAIL/users/recipients.cdb
+in a binary format suited
+for quick access by
+.BR qmail-smtpd .
+
+If there is a problem with
+.BR users/recipients ,
+.B qmail-recipients
+complains and leaves
+.B users/recipients.cdb
+alone.
+
+.B qmail-recipients
+ensures that
+.B users/recipients.cdb
+is updated atomically,
+so
+.B qmail-smtpd
+never has to wait for
+.B qmail-recipients
+to finish.
+However,
+.B qmail-recipients
+makes no attempt to protect against two simultaneous updates of
+.BR users/recipients.cdb .
+
+The binary
+.B users/recipients.cdb
+is compatible with
+.B setforward
+generated \'fastforward\' cdbs and it's
+format is portable across machines.
+
+.SH "SEE ALSO"
+qmail-smtpd(8),
+qmail-vmailusr(8),
+setforward(8),
+fastforward(8).
diff --git a/man/qmail-remote.8 b/man/qmail-remote.8
new file mode 100644
index 0000000..8f94e8a
--- /dev/null
+++ b/man/qmail-remote.8
@@ -0,0 +1,795 @@
+.TH s/qmail: qmail-remote 8
+.SH NAME
+qmail-remote \- send mail via SMTP(S) or QMTP(S)
+.SH SYNOPSIS
+.B qmail-remote
+.I host
+.I sender
+.I recip
+[
+.I recip ...
+]
+.SH DESCRIPTION
+.B qmail-remote
+reads a mail message from its input
+and sends the message
+to one or more recipients
+at a remote host.
+
+The remote host is
+.BR qmail-remote 's
+first argument,
+.IR host .
+.B qmail-remote
+sends the message to
+.IR host ,
+or to a mail exchanger for
+.I host
+listed in the Domain Name System,
+via the Simple Mail Transfer Protocol (SMTP/ESMTP)
+perhaps encrypted via STARTTLS/TLS
+or the Quick Mail Transfer Protocol (QMTP/QMTPS).
+Prior of setting up a TLS connection,
+.B qmail-remote
+will lookup automatically the corresponding TLSA
+record in the DNS and uses this for X.509 certificate
+validation.
+.I host
+can be either a fully-qualified domain name:
+
+.EX
+ silverton.berkeley.edu
+.EE
+
+or an IPv4 or IPv6 address enclosed in brackets:
+
+.EX
+ [128.32.183.163]
+ [2001::163]
+.EE
+
+In case the primary mail exchanger for that Domain
+will issue a 5xy reply message during the connection,
+.B qmail-remote
+will contact all responsible mail exchangers in turn
+in order to deliver the message anyway.
+
+The envelope recipient addresses are listed as
+.I recip
+arguments to
+.BR qmail-remote .
+The envelope sender address is listed as
+.I sender\fP.
+
+In case the remote host issues the EHLO SIZE extension,
+.I qmail-remote
+will handover the size of the message (in byte)
+prior of transmission and respects the remote host's reply code.
+
+Note that
+.B qmail-remote
+does not take options
+and does not follow the
+.B getopt
+standard.
+.SH "TRANSPARENCY"
+End-of-file in SMTP is encoded as dot CR LF.
+A dot at the beginning of a line is encoded as dot dot.
+It is impossible in SMTP to send a message that does not end with a newline.
+.B qmail-remote
+respects SMTPUTF8 and EAI addresses
+and converts the UNIX newline convention into the
+SMTP newline convention by inserting CR before each LF.
+
+.SH "RESULTS"
+.B qmail-remote
+prints some number of
+.I recipient reports\fP,
+followed by a
+.I message report\fR.
+Each report is terminated by a 0 byte.
+Each report begins with a single letter:
+.TP 5
+r
+Recipient report: acceptance.
+.TP 5
+h
+Recipient report: permanent rejection.
+.TP 5
+s
+Recipient report: temporary rejection.
+.TP 5
+K
+Message report: success.
+.I host
+has taken responsibility for delivering the message to each
+acceptable recipient.
+.TP 5
+Z
+Message report: greylisted or temporary failure.
+.TP 5
+D
+Message report: permanent failure.
+.PP
+After this letter comes a human-readable description of
+what happened.
+
+.B qmail-remote
+may use SMTP Authenticaton to connect to remote hosts.
+The following reports are provided:
+.TP 5
+K
+no supported AUTH s/qmail: method found, continuing without authentication.
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (AUTH s/qmail: PLAIN).
+.TP 5
+Z
+Connected to
+.I host
+but unable to base64encode (plain).
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (plain).
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (AUTH s/qmail: LOGIN).
+.TP 5
+Z
+Connected to
+.I host
+but unable to base64encode user.
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (username).
+.TP 5
+Z
+Connected to
+.I host
+but unable to base64encode pass.
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (AUTH s/qmail: CRAM-MD5).
+.TP 5
+Z
+Connected to
+.I host
+but unable to base64decode challenge.
+.TP 5
+Z
+Connected to
+.I host
+but unable to base64encode username+digest.
+.TP 5
+Z
+Connected to
+.I host
+but authentication was rejected (username+digest).
+.PP
+The recipient reports will always be printed in the same order as
+.BR qmail-remote 's
+.I recip
+arguments.
+Note that in failure cases there may be fewer
+recipient reports
+than
+.I recip
+arguments.
+.PP
+In case a CNAME can not be resovled
+.B qmail-remote
+issues the following message:
+.TP 5
+Z
+CNAME lookup failed temporarily for:
+.IR host .
+.PP
+If a SMTP connection is bound to a none-existing IP address
+.B qmail-remote
+will complain with the message:
+.TP 5
+Z
+System resources temporarily unavailable.
+.TP 5
+Z
+System can't bind to local ip address:
+.IR ip .
+.PP
+In case a QMTP connection can not be established
+.B qmail-remote
+will issue the error message:
+.TP 5
+Z
+recipient
+.I host
+did not talk proper QMTP.
+.PP
+On demand
+.B qmail-remote
+supports TLS/STARTTLS and will log the following notifications:
+.TP 5
+K
+TLS transmitted message accepted
+.TP 5
+K
+TLS (verfied CA) transmitted message accepted
+.TP 5
+K
+TLS (verified CA+DN*) transmitted message accepted
+.TP 5
+K
+TLS (verified CA+DN) transmitted message accepted
+.TP 5
+K
+TLS (CERT pinning) transmitted message accepted
+.TP 5
+K
+TLS (TLSA validated) transmitted message accepted
+.PP
+.B qmail-remote
+needs to read some X.509 certificates and key files
+prior of setting up a TLS connection. Failures are indicated as:
+.TP 5
+Z
+Can't load X.509 certificate:
+.IR certfile .
+.TP 5
+Z
+Can't load X.509 private key:
+.IR keyfile .
+.TP 5
+Z
+Keyfile does not match X.509 certificate:
+.IR password .
+.TP 5
+Z
+I wasn't able to process the TLS ciphers:
+.IR ciphers .
+.TP 5
+Z
+I wasn't able to setup CAFILE:
+.I cafile
+or CADIR:
+.I cadir
+for TLS.
+.PP
+Connection problems for TLS are not uncommon.
+Here,
+.I host
+is the domain or host to connect with and
+.I remotehost
+is the corresponding MX.
+.B
+qmail-remote
+provides the following diagnostic messages:
+.TP 5
+Z
+I wasn't able to create TLS context for:
+.I host
+at
+.IR remotehost .
+.TP 5
+Z
+I wasn't able to establish a TLS connection with:
+.I remotehost
+for
+.IR host .
+.TP 5
+Z
+TLS connection/protocol error with host:
+.I remotehost
+for
+.IR host .
+.TP 5
+Z
+I wasn't able to negotiate a StartTLS connection with:
+.I remotehost
+for
+.IR host .
+.PP
+For each MX to reach via TLS,
+.B qmail-remote
+performs an automatic TLSA lookup comparing the received
+X.509 fingerprints with the issued cert during the TLS handshake.
+X.509 certificate checks can also been performed. Failures here
+are given as:
+.TP 5
+Z
+Unable to obtain X.500 certificate from:
+.I remotehost
+for
+.IR host .
+.TP 5
+Z
+Unable to validate X.500 certificate Subject for:
+.I host
+at
+.IR remotehost .
+.TP 5
+Z
+TLSA X.509 cert required but missing from:
+.I remotehost
+for
+.IR host .
+.TP 5
+Z
+Received X.500 certificate from:
+.I remotehost
+for
+.I host
+does not match provided fingerprint:
+.IR hashvalue .
+.TP 5
+Z
+Received X.500 certificate from:
+.I remotehost
+for
+.I host
+posses an unknown digest method.
+.PP
+.SH "CONTROL FILES"
+.TP 5
+.I authsenders
+Authenticated sender.
+For each
+.I sender
+included in
+.IR authsenders :
+.I sender\fB:\fIrelay\fB;\fI[s]port\fB|\fIuser\fB|\fIpassword
+.B qmail-remote
+will try SMTP Authentication
+of type CRAM-MD5, LOGIN, or PLAIN
+with the provided user name
+.I user
+and password
+.I password
+(the authentication information)
+and eventually relay the
+mail through
+.I relay
+on port
+.IR port .
+If
+.I port
+is given als or prepended with
+.I s
+like
+.I s587
+\'implicit TLS\' is used omitting StartTLS upon connection.
+The use of
+.I relay
+and
+.I port
+follows the same rules as for
+.IR smtproutes
+Note: In case
+.I sender
+is empty,
+.B qmail-remote
+will try to deliver each outgoing mail
+SMTP authenticated. If the authentication
+information is missing, the mail is
+delivered none-authenticated.
+.I authsenders
+can be constructed as follows:
+
+.EX
+ @example.com:relay.example.com|user|passwd
+ info@example.com:relay.example.com;26|infouser|infopasswd
+ :mailrelay.example.com|e=mc2|testpass
+.EE
+.TP 5
+.I domaincerts
+In case
+.B qmail-remote
+needs to present a client certificate to the server
+(for authentication purposes) the PEM encoded
+X.509 certificate can be provided per sending domain:
+.IR domain\fB:\fIcertificate\fB|\fIkeyfile\fB|\fIpassword .
+If
+.I domain
+equals '*' this
+.I certificate
+is used as default.
+The file
+.I certificate
+may include the private key, thus
+.I keyfile
+can be omitted. Additionally, the private key can be protected with a
+.IR password .
+
+.TP 5
+.I domainips
+IP addresses to be used for outgoing connections.
+Each line has the form
+.IR domain\fB:\fIlocalip(%ifname)\fB|\fIhelohost ,
+without any extra spaces.
+If
+.I domain
+matches the domain part in
+.IR sender ,
+.B qmail-remote
+will bind to
+.IR localip
+when connecting to
+.IR host .
+LLU IPv6 addresses need to be appended with the binding
+.IR ifname
+following
+.IR localip
+with a '%'.
+If it matches, it will set the provided HELO string as greeting;
+otherwise, it will use the default.
+.I domain
+can be the wildcard
+.I *
+in which case
+.B qmail-remote
+binds to the provided address for any sender domain name.
+.TP 5
+.I helohost
+Current host name,
+for use solely in saying ehlo/hello to the remote SMTP server.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-remote
+refuses to run.
+.TP 5
+.I qmtproutes
+Additional QMTP routes which have precedence over
+.IR smtproutes .
+QMTP routes should obey the form
+.IR domain\fB:\fIrelay\fB;\fIport ,
+without any extra spaces.
+.I qmtproutes
+follows the same syntax as
+.IR smtproutes .
+By default,
+.B qmail-remote
+connects to QMTP service port 209. However
+you can chose a dedicated high-port for QMTP communication
+as defined in
+.IR qmtproutes .
+In case the QMTP port is chosen to be
+.I 6209
+the TLS secured QMTPS protocol will be used,
+irrespectively of the settings in
+.IR tlsdestinations .
+.TP 5
+.I smtproutes
+Artificial SMTP routes.
+Each route has the well-known form
+.I domain\fB:\fIrelay
+or the enhanced syntax
+.I domain\fB:\fIrelay;\fI[s]port\fB|\fIuser\fB|\fIpassword|localip
+without any extra spaces.
+If
+.I domain
+matches
+.IR host ,
+.B qmail-remote
+will connect to
+.IR relay ,
+as if
+.I host
+had
+.I relay
+as its only MX.
+(It will also avoid doing any CNAME lookups on
+.IR recip .)
+.I host
+may include a semi-colon and a port number to use instead of the
+normal SMTP port, 25.
+If
+.I port
+is given as or prepended with
+.I s
+\'implicit TLS\' is assumed.
+In case, a userid and password is
+present,
+.B qmail-remote
+will try a SMTP authenticated session:
+
+.EX
+ inside.af.mil:firewall.af.mil;26
+ :submission.myrelay.com;s587|myuserid|mypasswd
+.EE
+
+However,
+.I authsenders
+routes have precedence.
+
+.I relay
+may be empty;
+this tells
+.B qmail-remote
+to look up MX records as usual.
+.I smtproutes
+may include wildcards:
+
+.EX
+ .af.mil:
+ :heaven.af.mil
+.EE
+
+Here
+any address ending with
+.B .af.mil
+(but not
+.B af.mil
+itself)
+is routed by its MX records;
+any other address is artificially routed to
+.BR heaven.af.mil .
+
+The outgoing IP address used by
+.B qmail-remote
+can be specified:
+
+.EX
+ :bouncehost.org||10.1.1.0
+ :partnermx.net;42||2001::fefe
+.EE
+
+Note:
+.I localip
+can be private IP address subject of NAT'ing.
+
+Additionally,
+.I smtproutes
+allows to forward bounces (with a 'Nullsender' MAIL FROM: <>)
+literally expressed as '!@'
+to a particular bounce host:
+
+.EX
+ !@:bouncehost.af.mil;27
+.EE
+
+The
+.B qmail
+system does not protect you if you create an artificial
+mail loop between machines.
+However,
+you are always safe using
+.I smtproutes
+if you do not accept mail from the network.
+.TP 5
+.I timeoutconnect
+Number of seconds
+.B qmail-remote
+will wait for the remote SMTP server to accept a connection.
+Default: 60.
+The kernel normally imposes a 75-second upper limit.
+.TP 5
+.I timeoutremote
+Number of seconds
+.B qmail-remote
+will wait for each response from the remote SMTP server.
+Default: 1200.
+.TP 5
+.I tlsdestinations
+If present, this file advices
+.B qmail-remote
+to use TLS (optionally or mandatory) encryption for specific destination domains
+as provided by the forward-path and to validate/verify
+the server certificate perhaps for a particular sender's domain:
+.I destination:cafile|ciphers|verifydepth;[s]port|domain
+or
+.IR destination:=fingerprint|ciphers|verifydepth;[s]port|domain .
+Unless explicitely configured,
+.B qmail-remote
+accepts any or no certificate provided by the server (opportunistic encryption)
+using the following (single character) rules:
+
+.EX
+ (0) *: # Enable TLS but fallback to NOTLS (default);
+ server authentication is optional, given further settings
+.EE
+
+Special settings:
+
+.EX
+ (1) ?: # fallthru to no TLS in case of TLS protocol errors (exceptional)
+ (2) -: # allow anonymous connections
+ (3) /: # disable TLSA lookup and verification
+.EE
+
+Double character rules instruct
+.B qmail-remote
+to require a STARTTLS or SMTPS connection (mandatory TLS):
+
+.EX
+ (4) -*: # at least anonymous connections
+ (5) +*: # require and validate X.509 certs
+ (6) ~*: # cert + validate SAN/DN, however accept wildcard certs and partial matching
+ (7) =*: # cert + validate SAN/DN against FQDN
+ (8) /*: # don't do TSLA lookup and X.509 matching
+.EE
+
+Additionally,
+.B qmail-remote
+can be told to use per-domain connection settings:
+
+.EX
+ (9) example.com:
+ (10) securityfirst.com:/etc/ssl/cafile|!SSLv2:HIGH
+ (11) remote.com:/etc/ssl/certdir/||3;465
+ (12) mx.partner.com:/etc/ssl/partnerca||2|mydomain.net
+ (13) =mx.myfriend.com:/etc/ssl/cacert||4
+ (14) ~wildneighbor.net:
+ (15) -adhonlydomain.com:||aNULL:!kRSA
+ (16) %peer.partner.com:=E44194C56EF.....
+ (17) !nosslhost.example.com:
+ (18) hiddenpartner.org:;35
+ (19) ?tlsold.net:
+ (20) /nodane.org:
+.EE
+
+The ninth line requires from
+.B qmail-remote
+to demand a STARTTLS connection for any destination
+address targeting domain
+.IR example.com .
+
+The tenth line accepts STARTTLS connections
+for
+.I securityfirst.com
+only, if the X.509 certificate can be verified against
+the CA cert as provided via
+.I /etc/ssl/cafile
+and with the acceptable ciphers
+.IR SSLv2:HIGH .
+
+Line number eleven tells
+.B qmail-remote
+to use a
+.I SMTPS
+connection on port
+.I 465
+to any host at
+.I remote.com
+and accept this host only, if the peer's cert
+can be validated against the CA certs available
+in
+.I /etc/ssl/certdir/
+and does not exceed a verification depth of
+.IR 3 .
+
+Line twelve shows an example, how
+.I tlsdestinations
+can be bound exclusively to a sender domain. In the shown case,
+only if
+.I mx.mydomain.net
+is used as sender domain,
+a connection for the destination address
+.I mx.partner.com
+is mandatory secured by TLS with a CA cert available as
+.I /etc/ssl/partnerca
+with a verification depth of
+.IR 2 .
+
+Furthermore, the sample on line thirteen demonstrates the case where
+.B qmail-remote
+sees a destination address concatinated with
+.IR = .
+Now it will only accept the certificate,
+if the X.509's DN can be validated
+against the FQDN of the server (by means of a DNS lookup)
+and it verifies against the
+.IR cacert
+CA certificate and does not exceed a verification depth of
+.IR 1 .
+
+In case a certain
+.I destination
+may use 'wildcard' domain names in the SAN/DN,
+.B qmail-remote
+can cope with this (line fourteeen)
+prepending the destination with a '~':
+.IR ~wildneighor.net .
+This mechanism also supports partial matching
+of SAN/DN and domain name.
+
+In the same sense (line fiveteen),
+.B qmail-remote
+may accept TLS connections based on Anonymous DH (ADH)
+- where the server does not provide a cert for authentication -
+once the domain name is prepended with a
+.I -
+as key encryption cipher and discards
+.I !RSA
+for authentication if told so.
+
+Certificate pinning for a particular
+.I %host
+indicated by the leading character '%' is shown on line sixteen.
+Instead of the CA file, now the
+.I =fingerprint
+of the peer host certificate needs to be provided.
+The X.509 fingerprint
+should prepended with an equal sign ('=') and to
+be stripped from additional colons (':'). The fingerprint
+string is evaluated case-insensitive.
+.BR qmail-remote 's
+certificate pinning supports SHA1, SHA224, SHA256, and SHA512
+digests, determined by the length of the fingerprint given.
+
+Note, that in this case, no TLSA validation is performed;
+it is thus a 'silent' verification'.
+.B qmail-remote
+can be instructed to omit the STARTTLS command for the recipient address
+.I nosslhost.example.com
+as indicated with a leading
+.I !
+as shown on line seventeen. This behavior can be relaxed (line nineteen) using
+.I ?
+followed by a colon, a host, or domain name. Now
+.B qmail-remote
+will initally try a TLS connection by however is alllowed to switch back
+to none-encryption mode, in case this is not possible due
+protocol reasons.
+
+.B qmail-remote
+allows an \'implicit TLS\' connection on any port, if
+.I port
+is prended with an
+.I s
+even without providing the port.
+
+In case, no particular ciphers or CA certs are
+required, a colon/semi-colon ':;' can be used as shortcut (line eighteen).
+Generally, any port can be provided after the semi-colon.
+If however,
+.I port
+equals
+.IR 465 ,
+SMTPS will be used instead of STARTTLS and if
+.I port
+equals
+.IR 6209 ,
+QMTPS is the chosen transport protocol.
+The settings here overrule previous instructions.
+
+Finally, TLSA lookups can be disabled, prepending a
+domain name with
+.I /
+for the target domain as shown on line twenty.
+
+Note that 'destination' is subject of the
+forwarding rules as provided by
+.IR authsenders ,
+.IR qmtproutes ,
+and
+.IR smtproutes .
+.SH "RETURN CODES"
+.B qmail-remote
+always exits
+.I 0
+for SMTP(S) delivery.
+In case of QMTP(S)
+.I 1
+is returned in case a buffer feed fails and
+.I 0
+otherwise.
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-control(5),
+qmail-send(8),
+qmail-smtpd(8),
+qmail-smtpam(8),
+qmail-dksign(8),
+qmail-dkim(8),
+qmail-tcpto(8)
diff --git a/man/qmail-rspawn.8 b/man/qmail-rspawn.8
new file mode 100644
index 0000000..71a43d7
--- /dev/null
+++ b/man/qmail-rspawn.8
@@ -0,0 +1,21 @@
+.TH s/qmail: qmail-rspawn 8
+.SH NAME
+qmail-rspawn \- schedule remote deliveries
+.SH SYNOPSIS
+.B qmail-rspawn
+.SH DESCRIPTION
+.B qmail-rspawn
+reads a series of remote delivery commands from descriptor 0,
+invokes
+.B qmail-remote
+to perform the deliveries,
+and prints the results to descriptor 1.
+
+.B qmail-rspawn
+invokes
+.B qmail-remote
+asynchronously,
+so the results may not be in the same order as the commands.
+.SH "SEE ALSO"
+qmail-send(8),
+qmail-remote(8)
diff --git a/man/qmail-send.9 b/man/qmail-send.9
new file mode 100644
index 0000000..334bfa9
--- /dev/null
+++ b/man/qmail-send.9
@@ -0,0 +1,265 @@
+.TH s/qmail: qmail-send 8
+.SH NAME
+qmail-send \- deliver mail messages from the queue
+.SH SYNOPSIS
+.B qmail-send
+.SH DESCRIPTION
+.B qmail-send
+handles messages placed into the outgoing queue by
+.BR qmail-queue .
+It uses
+.B qmail-lspawn
+to deliver messages to local recipients and
+.B qmail-rspawn
+to deliver messages to remote recipients.
+If a message is temporarily undeliverable to one or more addresses,
+.B qmail-send
+leaves it in the queue and tries the addresses again later.
+
+.B qmail-send
+prints a readable record of its activities to descriptor 0.
+It writes commands to
+.BR qmail-lspawn ,
+.BR qmail-rspawn ,
+and
+.B qmail-clean
+on descriptors 1, 3, and 5,
+and reads responses from descriptors 2, 4, and 6.
+Communication with
+.B qmail-todo
+is based on decriptors 7 and 8.
+.B qmail-send
+is responsible for avoiding deadlock.
+
+If
+.B qmail-send
+receives a TERM signal,
+it will exit cleanly, after waiting
+(possibly more than a minute)
+for current delivery attempts to finish.
+
+If
+.B qmail-send
+receives an ALRM signal,
+it will reschedule every message in the queue for immediate delivery.
+
+.SH "CONTROL FILES"
+.B WARNING:
+.B qmail-send
+reads its control files only when it starts.
+If you change the control files,
+you must stop and restart
+.BR qmail-send .
+Exception:
+If
+.B qmail-send
+receives a HUP signal,
+it will reread
+.IR locals ,
+.IR virtualdomains ,
+as well as
+.IR concurrencylocal ,
+.IR concurrencyremote ,
+and in addition
+.IR queuelifetime .
+.TP 5
+.I bouncefrom
+Bounce username.
+Default:
+.BR MAILER-DAEMON .
+.TP 5
+.I bouncehost
+Bounce host.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR bouncehost ,
+which is probably not what you want.
+If a message is permanently undeliverable,
+.B qmail-send
+sends a
+.B single-bounce
+notice back to the message's envelope sender.
+The notice is
+.B From: \fIbouncefrom\fB@\fIbouncehost\fR,
+although its envelope sender is empty.
+.TP 5
+.I bouncemaxbytes
+Maximum size (in bytes) of bounce messages.
+Bounce messages exceeding this limit will be truncated.
+Default is 0; which means no limit.
+.TP 5
+.I concurrencylocal
+Maximum number of simultaneous local delivery attempts.
+Default: 10.
+If 0, local deliveries will be put on hold.
+.I concurrencylocal
+is limited at compile time to
+SPAWN.
+.TP 5
+.I concurrencyremote
+Maximum number of simultaneous remote delivery attempts.
+Default: 20.
+If 0, remote deliveries will be put on hold.
+.I concurrencyremote
+is limited at compile time to
+SPAWN.
+.TP 5
+.I doublebouncehost
+Double-bounce host.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR doublebouncehost ,
+which is probably not what you want.
+.TP 5
+.I doublebounceto
+User to receive double-bounces.
+Default:
+.BR postmaster .
+If a single-bounce notice is permanently undeliverable,
+.B qmail-send
+sends a
+.B double-bounce
+notice to
+.IR doublebounceto\fB@\fIdoublebouncehost .
+(If that bounces,
+.B qmail-send
+gives up.)
+As a special case, if the first line of
+.IR doublebounceto
+contains a '@' or an empty line
+.B qmail-send
+will discard all double-bounces.
+.TP 5
+.I envnoathost
+Presumed domain name for addresses without @ signs.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR envnoathost ,
+which is probably not what you want.
+If
+.B qmail-send
+sees an envelope recipient address without an @ sign,
+it appends
+.B @\fIenvnoathost\fR.
+.TP 5
+.I locals
+List of domain names that the current host
+receives mail for,
+one per line.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-send
+refuses to run.
+An address
+.I user@domain
+is considered local if
+.I domain
+is listed in
+.IR locals .
+.TP 5
+.I percenthack
+List of domain names where the percent hack is applied.
+If
+.I domain
+is listed in
+.IR percenthack ,
+any address of the form
+.I user%fqdn@domain
+is rewritten as
+.IR user@fqdn .
+.I user
+may contain %,
+so the percent hack may be applied repeatedly.
+.B qmail-send
+handles
+.I percenthack
+before
+.IR locals .
+.TP 5
+.I queuelifetime
+Number of seconds
+a message can stay in the queue.
+Default: 604800 (one week).
+After this time expires,
+.B qmail-send
+will try the message once more,
+but it will treat any temporary delivery failures as
+permanent failures.
+.TP 5
+.I virtualdomains
+List of virtual users or domains, one per line.
+A virtual user has the form
+.IR user\fB@\fIdomain\fB:\fIprepend ,
+without any extra spaces.
+When
+.B qmail-send
+sees the recipient address
+.IR user\fB@\fIdomain ,
+it converts it to
+.I prepend\fB-\fIuser\fB@\fIdomain
+and treats it as local.
+
+A virtual domain has the form
+.IR domain\fB:\fIprepend .
+It applies to any recipient address at
+.IR domain .
+For example, if
+
+.EX
+ nowhere.mil:joeBREAKfoo
+.EE
+
+is in
+.IR virtualdomains ,
+and a message arrives for
+.BR info@nowhere.mil ,
+.B qmail-send
+will rewrite the recipient address as
+.B joeBREAKfoo-info@nowhere.mil
+and deliver the message locally.
+
+.I virtualdomains
+may contain wildcards:
+
+.EX
+ .fax:uucpBREAKfax
+ :aliasBREAKcatchall
+ .nowhere.mil:joeBREAKfoo-host
+.EE
+
+.I virtualdomains
+may also contain exceptions:
+an empty
+.I prepend
+means that
+.I domain
+is not a virtual domain.
+
+.B qmail-send
+handles
+.I virtualdomains
+after
+.IR locals :
+if a domain is listed in
+.IR locals ,
+.I virtualdomains
+does not apply.
+.SH "SEE ALSO"
+nice(1),
+addresses(5),
+envelopes(5),
+qmail-control(5),
+qmail-log(5),
+qmail-todo(8),
+qmail-queue(8),
+qmail-clean(8),
+qmail-lspawn(8),
+qmail-rspawn(8)
diff --git a/man/qmail-showctl.8 b/man/qmail-showctl.8
new file mode 100644
index 0000000..ddd90d7
--- /dev/null
+++ b/man/qmail-showctl.8
@@ -0,0 +1,12 @@
+.TH s/qmail: qmail-showctl 8
+.SH NAME
+qmail-showctl \- analyze the qmail configuration files
+.SH SYNOPSIS
+.B qmail-showctl
+.SH DESCRIPTION
+.B qmail-showctl
+explains the current
+.B s/qmail
+configuration.
+.SH "SEE ALSO"
+qmail-control(8)
diff --git a/man/qmail-smtpam.8 b/man/qmail-smtpam.8
new file mode 100644
index 0000000..9fe8e90
--- /dev/null
+++ b/man/qmail-smtpam.8
@@ -0,0 +1,110 @@
+.TH s/qmail: qmail-smtpam 8
+.SH NAME
+qmail-smtpam \- SMTP client PAM
+.SH SYNOPSIS
+.B qmail-smtpam
+.I host
+.I [s]port
+.SH DESCRIPTION
+.B qmail-smtpam
+reads an email address from FD 3
+and tries to verify this
+connecting to the remote
+.IR host
+on
+.IR port .
+If
+.I port
+starts is
+.I s
+\'implicit TLS\' ist used on that port.
+In a standard SMTP dialog,
+.B qmail-smtpam
+supplies the HELO greeting,
+a MAIL FROM: <> address, and
+the purported RCPT TO: <address>.
+.SH "CONTROL FILES"
+.TP 5
+.I domainips
+IP addresses to be used on outgoing connections.
+Each line has the form
+.IR domain\fB:\fIlocalip(%ifname)\fB|\fIhelohost ,
+without any extra spaces.
+If
+.I domain
+matches the domain part in
+.IR sender ,
+.B qmail-smtpam
+will bind to
+.IR localip
+when connecting to
+.IR host .
+LLU IPv6 addresses need to be appended with the binding
+.IR ifname
+following
+.IR localip
+with a '%'.
+If it matches, it will set the provided HELO string as greeting;
+otherwise, it will use the default.
+.TP 5
+.I helohost
+Current host name,
+for use solely in saying hello to the remote SMTP server.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-smtpam
+refuses to run.
+.TP 5
+.I timeoutconnect
+Number of seconds
+.B qmail-smtpam
+will wait for the remote SMTP server to accept a connection.
+Default: 60.
+The kernel normally imposes a 75-second upper limit.
+.TP 5
+.I timeoutremote
+Number of seconds
+.B qmail-smtpam
+will wait for each response from the remote SMTP server.
+Default: 1200.
+.TP 5
+.I tlsdestinations
+If present, this file advices
+.B qmail-smtpam
+to use TLS encryption for specific destination domains
+as provided by the forward-path and perhaps to validate/verify
+the domain's server certificate:
+.IR destination:cafile|verifydepth;[s]port|ciphers|domain .
+If
+.I port
+is give as or prepended with
+.I s
+\'implict TLS\' is used; omitting StartTLS.
+Unless explicitely configured,
+.B qmail-smtpam
+accepts any or no certificate provided by the server,
+thus uses TLS for encryption only.
+.B qmail-smtpam
+uses the same certificate validation/verification
+mechanism as
+.B qmail-remote
+except for distinguishing among the sender's domain information.
+.SH "RETURN CODES"
+.B qmail-smtpam
+exits
+.I 0
+if the remote server
+replies with '250', otherwise
+.IR 1 .
+In case the control files can not
+be read or a communication problem has
+occured, it exits
+.IR 111 .
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-control(5),
+qmail-remote(8),
+qmail-smtpd(8)
diff --git a/man/qmail-smtpd.8 b/man/qmail-smtpd.8
new file mode 100644
index 0000000..951b0a7
--- /dev/null
+++ b/man/qmail-smtpd.8
@@ -0,0 +1,1013 @@
+.TH s/qmail: qmail-smtpd 8
+.SH "NAME"
+qmail-smtpd \- receive mail via SMTP
+.SH "SYNOPSIS"
+.B qmail-smtpd
+[
+.I checkprogram
+.I subprogram
+]
+.SH "DESCRIPTION"
+.B qmail-smtpd
+receives mail messages via the Simple Mail Transfer Protocol (SMTP)
+and invokes
+.B qmail-queue
+to deposit them into the outgoing queue.
+.B qmail-smtpd
+must be supplied with several environment variables;
+see
+.BR tcp-environ(5) .
+
+.B qmail-smtpd
+is responsible for counting hops.
+It rejects any message with 100 or more
+.B Received
+or
+.B Delivered-To
+header fields.
+
+.B qmail-smtpd
+supports ESMTP and offers 8BITMIME, DATA, PIPELINING, SIZE, AUTH, STARTTLS, and SMTPUTF8 options.
+.B qmail-smtpd
+includes a 'Mail From:' parameter parser and obeys 'Auth', 'Size', and 'SMTPUTF8' advertisements.
+.B qmail-smtpd
+supports SMTPUTF8 SMTP envelope addresses and provides 8 bit clean message transmission.
+.B qmail-smtpd
+STARTTLS and SMTPS implementation requires the use of
+.B sslserver
+from ucspi-ssl.
+
+Authentication is facilitated in case the environment variable
+SMTPAUTH is set which tells
+.B qmail-smtpd
+to accept LOGIN, PLAIN, and eventually CRAM-MD5 Auth types
+and if additionally a PAM
+.I checkprogram
+is available which reads on file descriptor 3 the username, a 0 byte,
+the password or CRAM-MD5 digest/response derived from the SMTP client,
+another 0 byte, a CRAM-MD5 challenge (if applicable to the Auth type),
+and a final 0 byte.
+.I checkprogram
+invokes
+.I subprogram
+upon successful authentication, which should return 0 to
+.BR qmail-smtpd ,
+effectively setting the environment variables RELAYCLIENT and
+TCPREMOTEINFO or TCP6REMOTEINFO
+(any supplied value replaced with the authenticated username).
+.B qmail-smtpd
+will reject the authentication attempt if it receives a nonzero return
+value from
+.I checkprogram
+or
+.IR subprogram .
+
+STARTTLS support is enabled setting the environment variable UCSPITLS.
+In this case,
+.B qmail-smtpd
+communicates with the
+.B sslserver
+program interface through a control socket, a reading and a writing pipe, dynamically
+defined during the session start to be used for transport layer encryption.
+.B qmail-smtpd
+provides mutual authentication based on X.509 client certs and relaying
+with additional SMTP Return-Path validation.
+
+.B qmail-smtpd
+may employ additional DNS look-ups for the 'Mail From:' envelope sender
+address and/or the HELO/EHLO greeting string from the MTA client.
+
+.B qmail-smtpd
+implements a SPF record check for the domain part of the received
+.I Mail-From:\ <return-path>
+address or
+the
+.I HELO/EHLO
+statement in case the domain information is missing.
+This behavior is triggered by the environment variable
+.BR SPF .
+
+.B qmail-smtpd
+can be advised to communicate with a Greylisting server prior of acceptance, like
+.BR postgrey ,
+submitting the connection information
+.IR Mail\ From: ,
+.IR Rcpt\ To: ,
+.IR TCPREMOTEIP
+and
+.I TCPREMOTEHOST
+given its IPv4/IPv6 address as environment variable
+.IR POSTGREY
+and potentially including the port number (60000 is default)
+following the IP address separated by a semi-colon.
+For IPv6 LLU addresses the interface name followwing a percent sign can be included:
+.IR fe80::1%eth0;60000 .
+A return value of
+.I 10
+will advise
+.B qmail-smtpd
+to defer the SMTP connection providing a
+.I 450\ greylisted\ (#4.3.0)
+response to the connecting MTA, which can be tailored (see below).
+This mechanism shall not be used for SMTP connections on the
+.I Submission
+port.
+Setting
+.I POSTGREY='-'
+disables the lookup.
+
+.SH "TRANSPARENCY"
+.B qmail-smtpd
+converts the SMTP newline convention into the UNIX newline convention
+by converting CR LF into LF.
+Usually, it returns a temporary error and drops the connection on bare LFs.
+
+.B qmail-smtpd
+accepts messages that contain long lines or non-ASCII characters
+and thus is initially capable for SMTPUTF8 support.
+
+.SH "CONTROL FILES"
+.TP 5
+.IR badhelo
+Unacceptable HELO/EHLO greeting strings.
+.B qmail-smtpd
+will reject every connection attempt
+if the client MTA's HELO/EHLO greeting compares with
+a wildmat pattern provided in
+.IR badhelo
+in case the environment variable
+.B HELOCHECK
+is set.
+.IR badhelo
+checks have precedence over DNS lookups.
+DNS lookups can be avoided, if the announced
+HELO/EHLO greeting string is concatinated
+with a trailing '!' and included in
+.IR badhelo :
+
+.EX
+ localhost
+ localhost.localdomain
+ 127.0.0.1
+ mygreetingstring
+ [192.168.1.2]!
+.EE
+
+.TP 5
+.I badmailfrom
+Unacceptable envelope sender addresses.
+.B qmail-smtpd
+will reject every recipient address for a message
+if the envelope sender address is listed in
+.IR badmailfrom .
+A line in
+.I badmailfrom
+may be of the form
+.BR @\fIhost ,
+meaning every address at
+.IR host .
+Additionally, any envelope sender address can be filtered
+with a wildmat check:
+
+.EX
+ *@earthlink.net
+ !fred@earthlink.net
+ [0-9][0-9][0-9][0-9][0-9]@[0-9][0-9][0-9].com
+ answerme@save*
+ *%*
+ @yahoo.com-
+ @hotmail.com=
+ @mydomain.tld+
+ ~yahoo.com
+ ?nobody@example.com
+.EE
+
+A
+.I badmailfrom
+file with this contents reject all mail from Earthlink except from
+fred@earthlink.net. It also rejects all mail with addresses like:
+12345@123.com and answerme@savetrees.com. Further, any mail with
+a sender address containing a percent sign (%) is rejected.
+
+This implementation recognises 'extended' addresss in
+.I badmailfrom
+allowing to reject mails with particluar spoofed domain addresses:
+
+(1) The address is appended with a '-'.
+Now, if
+.I TCP(6)REMOTEHOST
+equals 'unknown', mails with the corresponding address are rejected
+(badmailfromunknown).
+
+(2) The address is appended with a '='.
+In case
+.I TCP(6)REMEOTEHOST
+is set mails, whose domain part of the envelope addresses
+.B not
+matching
+the corresponding entry are rejected (badmailfromwellknown).
+
+(3) The address is appended with a '+'.
+If
+.I RELAYCLIENT
+is not set and the sender address matches a corresponding entry
+(anti-spoofing for internal addresses).
+
+(4) The address is enhanced with a leading '~'.
+This requires a (left to right partial) matching of
+.I TCP(6)REMOTEHOST
+with the domain part of the envelope address.
+Thus, this specific entry in
+.I badmailfrom
+uses
+.I TCP(6)REMOTEHOST
+in the first place (badmailfrommismachteddomains).
+
+(5) The address is enhanced with a leading '?'.
+Emails with the corresponding sender address pass by all further
+.I badmailfrom
+tests including the
+.I MFDNSCHECK
+check.
+
+Note: The 'enhanced' addresses are not subject of the wildmat check
+and are evaluated in lower-case.
+
+The wildmat check is done in the order:
+Least significant to most significant.
+Example:
+
+.EX
+ *
+ !
+ !*@*.*
+ *viagra*
+.EE
+
+.TP 5
+.I badloadertypes.cdb
+Unacceptable base64 loader types in the message.
+.B qmail-smtpd
+will reject every message if 5 significant
+characters (eg.
+.BR Mi5kb)
+anyware in the base64 encoded attachment is identical
+to those compiled into
+.IR badloadertypes.cdb .
+Use
+.B qmail-badloadertypes
+to derive
+.I badloadertypes.cdb
+from
+.IR badloadertypes .
+In order to make the search efficient, all bad loader
+types have to start with the same character (eg. 'M').
+The control file
+.I badloadertypes.cdb
+is evaluated if the environment variable BADLOADERTYPE
+is set to the first character according to the contents of
+.IR badloadertypes .
+.TP
+.I badmimetypes.cdb
+Unacceptable base64 encoded MIME types in message.
+.B qmail-smtpd
+will reject every message if the first 9 significant
+characters (eg.
+.BR TVqQAAMAA )
+of any of it's embedded MIME types is identical with one
+compiled into
+.IR badmimetypes.cdb .
+Use
+.B qmail-badmimetypes
+to derive
+.I badmimetypes.cdb
+from
+.IR badmimetypes .
+The control file
+.I badmimetypes.cdb
+is evaluated if the environment variable
+.I BADMIMETYPE
+is set.
+In addition, irregular BASE64 attachments carrying whitespaces can
+be rejected defining
+.IR BADMIMETYPE='!' .
+.TP 5
+.I badrcptto
+Unacceptable envelope recipient addresses.
+.B qmail-smtpd
+will reject every incoming message
+if the envelope recipient address is listed in
+.IR badrcptto .
+This control file is complementary to
+.IR badmailfrom .
+A line in
+.I badrcptto
+may be of the form
+.BR @\fIhost ,
+meaning every address at
+.IR host .
+.I badrcptto
+employes the same filtering logic for the envelope recipient as
+.IR badmailfrom .
+Effectively,
+.IR badrcptto
+allows a 'whitelisting' of envelope recipient addresses:
+
+.EX
+ *
+ !user1@mydomain.com
+ !user2@mydomain.com
+ !*@anotherdomain.com
+.EE
+
+.IR badrcptto
+allows to tag recipient addresses to be reachable from
+authorized clients only (aka relayclients), prepending it
+in
+.IR badrcptto
+with
+.IR + .
+
+.EX
+ +localaddress@mydomain.com
+.EE
+
+.TP 5
+.I databytes
+Maximum number of bytes allowed in a message,
+or 0 for no limit.
+Default: 0.
+If a message exceeds this limit,
+.B qmail-smtpd
+returns a permanent error code to the client;
+in contrast, if
+the disk is full or
+.B qmail-smtpd
+hits a resource limit,
+.B qmail-smtpd
+returns a temporary error code.
+
+.I databytes
+counts bytes as stored on disk, not as transmitted through the network.
+It does not count the
+.B qmail-smtpd
+Received line, the
+.B qmail-queue
+Received line, or the envelope.
+
+If the environment variable DATABYTES
+is set, it overrides
+.IR databytes .
+.TP 5
+.I localiphost
+Replacement host name for local IP addresses.
+Default:
+.IR me ,
+if that is supplied.
+.B qmail-smtpd
+is responsible for recognizing native IPv4/IPv6 addresses for the
+current host.
+When it sees a recipient address of the form
+.I box@[d.d.d.d]
+or
+.IR box@[a:b:c:d:e:f:g:h] ,
+where
+.I d.d.d.d
+or
+.IR a:b:c:d:e:f:g:h
+is a local IPv4/IPv6 address,
+it replaces
+.I [d.d.d.d]
+or
+.IR [a:b:c:d:e:f:g:h]
+with
+.IR localiphost .
+This is done before
+.IR rcpthosts .
+.TP 5
+.I morercpthosts
+Extra allowed RCPT domains.
+If
+.I rcpthosts
+and
+.I morercpthosts
+both exist,
+.I morercpthosts
+is effectively appended to
+.IR rcpthosts .
+
+You must run
+.B qmail-newmrh
+whenever
+.I morercpthosts
+changes.
+
+Rule of thumb for large sites:
+Put your 50 most commonly used domains into
+.IR rcpthosts ,
+and the rest into
+.IR morercpthosts .
+.TP 5
+.I mailfromrules
+Acceptable 'Mail From:' addresses for
+RELAYCLIENTs are included here. Use
+.B qmail-mfrules
+to derive
+.TP 5
+.I mailfromrules.cdb
+from
+.IR mailfromrules .
+.TP 5
+.I rcpthosts
+Allowed RCPT domains.
+If
+.I rcpthosts
+is supplied,
+.B qmail-smtpd
+will reject
+any envelope recipient address with a domain not listed in
+.IR rcpthosts .
+
+Exception:
+If the environment variable RELAYCLIENT is set,
+.B qmail-smtpd
+will ignore
+.IR rcpthosts ,
+and will append the value of RELAYCLIENT
+to each incoming recipient address.
+
+.I rcpthosts
+may include wildcards:
+
+.EX
+ heaven.af.mil
+ .heaven.af.mil
+.EE
+
+Envelope recipient addresses without @ signs are
+always allowed through.
+.TP 5
+.I recipients
+List of external resources providing acceptable,
+full-qualified envelope addresses
+(\'RCPT to: <recip@domain>\')
+to be used for recipient verification
+during the SMTP session.
+
+The external sources can be either
+.B fastforward
+compliant cdbs including the envelope addresses,
+where the path to a cdb has to be referenced
+relative to Qmail's home directory, or a
+.B qmail-users
+build cdb available as
+.IR users/assign.cdb ,
+or a
+.B checkpassword
+compatible Plugable Authentication Modules
+(PAM), receiving the envelope address on FD 3
+as 'recip@domain\\0\\0\\0' and returning '0'
+in a case of success and '1' in case of failure.
+The use of a PAM is indicated with a delimiting '|' and
+it will be called with up to five additional parameters;
+while a cdb follows a ':', which can be omitted.
+
+The list of external sources is consulted line-by-line for each
+recipient envelope address until the first positive answer,
+or a final negative response is encountered.
+Which external source to be queried, depends on the domain part of the
+recipient envelope address specified on the left side of the
+.I recipients
+file, while the external resource is provided right from the delimitor.
+
+The addresses' domain part is evaluated in lower-case.
+An exact domain match can be encompassed by means of a leading '@'.
+The '*' is a generic wildcard for all domains.
+Specific domains can be excluded from the lookup by means of a
+leading '!'; thus all recipient addresses are accepted for this domain.
+Additionally, a '!*' can be used as wildcard for all domains not encountered
+before in
+.I recipients
+(pass-thru).
+
+A
+.I recipients
+file is always constructed like 'domain:cdb','domain|pam',
+or simply 'cdb':
+
+.EX
+ !nocheck.com
+ mydomain.com:users/recipients.cdb
+ @mx.mydomain.com:=
+ example.com|bin/qmail-smtpam mx.example.com
+ *:etc/fastforward.cdb
+ *|PATH/ldapam ldapserver host port DN passwd
+ !*
+.EE
+
+.B qmail-smtpd
+will semi-automatically consult
+.I users/assign.cdb
+generated by
+.B qmail-newu
+in case the domain name is
+followed by a colon and the equal sign '='.
+Now, the received \'Rcpt to:\' address
+is compared against each local part address
+(starting with a '=') in
+.IR users/assign.cdb .
+However, no VERP addresses are considered,
+which are indicated therein via a '+'.
+
+Lagacy format:
+
+.EX
+ users/recipients.cdb
+ etc/fastforward.cdb
+.EE
+
+Note: Excluded domains starting with a '!'
+should be placed in the beginning of the
+.I recipients
+file for performance reasons, while the pass-thru
+statement '!*' has to be on the last line.
+The recipients check is applied after the
+.I rcpthosts
+evaluation.
+
+.B qmail-recipients
+may be used to construct a
+.I users/recipients.cdb
+from
+.IR users/recipients .
+
+The
+.B qmail-smtpd
+recipients mechanism supports Qmail's address extension (VERP).
+Unqualified envelope recipients are appended with \'@localhost\'.
+.TP 5
+.I smtpgreeting
+SMTP greeting message.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-smtpd
+will refuse to run.
+The first word of
+.I smtpgreeting
+should be the current host's name.
+.TP 5
+.I spfexplain
+An additional SPF explanation can be given here to provide more
+specific information for the sender in case of a reject.
+SPF macro expansion is possible. It will override the default one, e.g.:
+
+.EE
+See https://example.com/spfrules.html (#5.7.1)
+.EX
+.TP 5
+.I spflocalrules
+As 'last resort', it is possible to include SPF local rules here
+(on one line), that will be applied before other SPF rules would fail.
+This can be used to allow certain MX to send mails anyway. Example:
+
+.EE
+include:spf.trusted-forwarder.org
+.EX
+.TP 5
+.I timeoutsmtpd
+Number of seconds
+.B qmail-smtpd
+will wait for each new buffer of data from the remote SMTP client.
+Default: 1200.
+
+.SH "CONDITIONAL CONTROL FILES"
+The control files \fIrcpthosts\fR, \fImorecpthosts\fR,
+\fIrecipients\fR, \fIbadhelo\fR
+are 'conditional' control files and evaluated
+only if the environment variable RELAYCLIENT is not set.
+On the other hand,
+\fImailfromrules.cdb\fR is only taken into account, if
+RELAYCLIENT is set.
+This allows
+.B qmail-smtpd
+to relay mail messages from local clients and to filter
+mails with certain SMTP envelope conditions
+originating from particular clients ('Split Horizon').
+Other conditional control files are
+\fIbadloadertypes\fR,
+\fIbadmimetypes\fR
+which depend on the setting of the corresponding
+environment variables.
+
+Further, the control files \fIspfexplain\fR and
+\fIspflocalrules\fR are only evaluated if the
+environment variable
+.I SPF
+is defined and greater than 0 and
+.I RELAYCLIENT
+is not set.
+
+.SH "ENVIRONMENT VARIABLES READ"
+Environment variables may be defined globally in the
+.B qmail-smtpd
+startup script and/or individually as part of the
+.BR sslserver 's
+cdb database.
+The environment variables may be quoted ("variable", or 'variable') and
+in case of global use, have to be exported.
+.B qmail-smtpd
+supports the following legacy environment variables, typically
+provided by
+.B sslserver
+or
+.B tcpserver:
+.IR TCP(6)REMOTEIP ,
+.IR TCP(6)REMOTEHOST
+.IR TCP(6)REMOTEINFO
+and
+.IR TCPLOCALPORT
+as well as
+.IR RELAYCLIENT .
+Additionally,
+.B qmail-smtpd
+may use several environment variables for different purposes.
+.P
+Controlling the SMTP HELO/EHLO:
+.IP
+.TP 5
+.I HELOCHECK=''
+enables a check of the provided HELO/EHLO greeting against
+the content of the control file
+.IR badhelo .
+In case no HELO/EHLO greeting is given, SMTP
+connections can be rejected, if
+.I HELOCHECK='!'
+is set. Checks on the presence and the content of
+the HELO/EHLO greeting string is facilitated, setting
+.IR HELOCHECK='.' .
+To enforce the match of the HELO/EHLO greeting with
+the remote host's FQDN (
+.IR TCP(6)REMOTEHOST ),
+use
+.IR HELOCHECK='=' .
+.TP 5
+.I HELOCHECK='A' | HELOCHECK='M'
+enable DNS A/MX lookup for the HELO/EHLO greeting string.
+In addition, the HELO/EHLO string is checked against
+the content of
+.IR badhelo .
+.TP 5
+.I UTF8
+display the
+.I SMTPUTF8
+greeting string. This is off by default.
+.p
+Since
+.B qmail-smtpd
+is 8 bit clean, setting of
+.I UTF8
+has no real consequences except for displaying this
+setting in the log as
+.IR ESMTP[SA]UTF8 .
+.P
+Controlling the SMTP Mail From:
+.IP
+.TP 5
+.I LOCALMFCHECK
+is used to enable a 'Mail From:' address Verification (MAV) for RELAYCLIENTs.
+Thus, the domain part of the 'Mail From:' envelope sender address
+has to match an entry in
+.IR rcpthosts
+or
+.IR morercpthosts
+control files, if not explicitly defined otherwise.
+
+If LOCALMFCHECK='!' is set, the control file
+.I mailfromrules.cdb
+is evaluated and the MAV is facilitated employing the environment variables
+.IR TCP(6)REMOTEINFO ,
+.IR TCP(6)REMOTIP ,
+or
+.I TCP(6)REMOTEHOST
+as a key.
+However, if LOCALMFCHECK='=' is provided,
+.IR TCP(6)REMOTEINFO
+(i.e. set by Auth) has to match the 'Mail From:'
+envelope address (case insensitive).
+Alternativley, using LOCALMFCHECK='?' the email address
+embedded in the DN of a X.509 client is used and compared
+against the 'Mail From:' envelope address.
+Of course, this requires
+.B sslserver
+to request a client cert for mutual authentication.
+
+Note: Adding a qualifier to LOCALMFCHCEK,
+the domain part of the 'Mail From:' address is compared
+against the provided string.
+.TP 5
+.IR MFDNSCHECK
+enable DNS MX lookup for the domain part of the 'Mail From:' envelope sender address.
+.TP 5
+.I SPF='0'|'1'|'2'|'3'|'4'|'5'|'6'
+SPF Records will be evaluated for the current SMTP session in case
+.B SPF
+is defined. The value of
+.B SPF
+may be given between 1 and 6 to enable SPF checks.
+.I 1
+selects 'annotate-only' mode, where
+.B qmail-smtpd
+will annotate incoming email with a
+.B Received-SPF
+header, but will not reject any messages.
+.I 2
+will produce temporary failures on DNS lookup problems
+so you can be sure always to have a meaningful Received-SPF header.
+.I 3
+selects 'reject' mode, where incoming mail will be rejected
+if the SPF record says 'fail'.
+.I 4
+selects a more stricter rejection mode, which is like 'reject' mode,
+except that incoming mail will also be rejected, when the SPF record
+says 'softfail'. Further,
+.I 5
+will reject when the SPF record says 'neutral', and
+.I 6
+rejects, if no SPF records are available at all
+(or a syntax error was encountered).
+If
+.B SPF
+is given as
+.IR 0 ,
+SPF checks are disabled.
+
+Note: Additional control files are
+.I spfexplain
+and
+.IR spflocalrules .
+
+.P
+Controlling the SMTP RCPT TO:
+.IP
+.TP 5
+.I MAXRECIPIENTS
+is the number of Rcpt To:'s
+.B qmail-smtpd
+will accept in a SMTP session.
+If MAXRECIPIENTS ist not set, any number is allowed.
+.TP 5
+.IR TARPITCOUNT
+is the number of Rcpt To:
+.B qmail-smtpd
+accepts before it starts tarpitting.
+Default: 0 which means no tarpitting.
+.TP 5
+.IR TARPITDELAY
+tarpitdelay is the time in seconds of delay
+to be introduced after each subsequent Rcpt To:.
+
+Smart Rejection Notes:
+If
+.IR TARPITCOUNT
+is set and
+.IR TARPITDELAY
+= 0 (default)
+.B qmail-smtpd
+will issue after recognising
+.IR TARPITCOUNT
+invalid Rcpt To: a Recipient failure;
+thus additional Rcpt Tos will not be accepted.
+If, however
+.IR TARPITCOUNT
+is set and
+.IR TARPITDELAY
+= 999
+.B qmail-smtpd
+will issue after
+.IR TARPITCOUNT
+invalid Rcpt To: a Recipient failure
+.TP 5
+.I RECIPIENTS450
+tells
+.b qmail-smtpd
+to issue a SMTP reply '450' (temporary rejection)
+instead the default '550'
+in case the recipient was not listed in any
+.I recipients
+cdb.
+
+.P
+Controlling the email body:
+.IP
+.TP 5
+.I BADLOADERTYPE='c'
+tells
+.B qmail-smtpd
+to evaluate the control file
+.I badloadertypes.cdb
+with the starting string 'c'.
+If
+.I BADLOADERTYPE='-'
+is set, the check is disabled.
+In case
+.I BADLOADERTYPE='+'
+is defined, the check is disabled for
+.IR RELAYCLIENTS .
+.TP 5
+.I BADMIMETYPE
+see control file
+.IR badmimetypes.cdb .
+In case
+.I BADMIMETYPE='-'
+is set;
+.I badmimetypes.cdb
+is not considered; thus the check is disabled.
+Providing
+.I BADMIMTETYPE='+'
+the check is disabled if in addition
+.IR RELAYCLIENTS
+are recognized.
+
+.TP 5
+.I BASE64
+tells QHPSI to enable virus checking only if a base64 encoded
+attachment was identified.
+.TP 5
+.I DATABYTES
+see control file
+.IR databytes .
+.TP 5
+.I QHPSI
+is used by
+.B qmail-smtpd
+to supply the name of the virus scanner and it's path.
+.P
+Environment variables for SMTP authentication:
+.IP
+.TP 5
+.I SMTPAUTH
+is used to enable SMTP Authentication for the
+Auth types
+LOGIN and PLAIN.
+In case
+.TP 5
+.I SMTPAUTH='+cram'
+is defined,
+.B qmail-smtpd
+honors LOGIN, PLAIN, and additionally CRAM-MD5 authentication.
+Simply
+.TP 5
+.I SMTPAUTH='cram'
+restricts authentication just to CRAM-MD5.
+If however
+.TP 5
+.I SMTPAUTH='!'
+starts with an exclamation mark, Auth is required.
+You can enforce 'Submission' using this option
+and binding
+.B qmail-smtpd
+to the SUBMISSION port \'587'\.
+In particular,
+.TP 5
+.I SMTPAUTH='!cram'
+may be useful.
+In opposite, if
+.TP 5
+.I SMTPAUTH='-'
+starts with a dash, Auth disabled for particular
+connections.
+Note: The use of 'cram' requires a CRAM-MD5 enabled PAM.
+.P
+Setting up the TLS/STARTTLS environment:
+.IP
+.TP 5
+.I UCSPITLS
+enables encrypted SMTP communication
+via STARTTLS in case
+.B sslserver
+is provided.
+If
+.I UCSPITLS='!'
+is set, STARTTLS is required; while setting
+.I UCSPITLS='-'
+disables STARTTLS.
+Further,
+.I UCSPITLS='?'
+may be used to force the client to present a X.509 cert
+for authentication purpose which may be refined
+requesting
+.I UCSPITLS='@'
+to additionally fetch the email address
+from the client's cert to be perhaps subject of
+.IR LOCALMFCHECK .
+.P
+Other environment variables used:
+.IP
+.TP 5
+.I DELIVERTO
+mail address for special recipients.
+.TP 5
+.I RBLSMTPD
+feed from
+.B rblsmtpd
+including the information received from the
+inquired RBL hosts and displayed as
+.I X-RBL-Info:
+message header.
+.TP 5
+.I POSTGREY
+triggering the call of
+.B qmail-postgrey
+and feeding it with the IP address and port of the
+.I greylisting
+server. If
+.I POSTGREY
+is set to
+.I -
+no lookup is performed.
+
+.SH "CUSTOMIZABLE RETURN MESSAGES"
+In case of rejected or defered SMTP connections
+.B qmail-smtpd
+can provide additional informations in the SMTP reply message
+which are sandwiched between the reply code and the EMMSC.
+.B qmail-smtpd
+recognizes these environment variables:
+.TP 5
+.I REPLY_GREYLISTED
+following 450 greylisting
+.TP 5
+.I REPLY_HELO
+following 550 Bad Helo
+.TP 5
+.I REPLY_MAILBOX
+following 550 mailbox not existing
+.TP 5
+.I REPLY_MAXSIZE
+following 552 message size to large
+.TP 5
+.I REPLY_BADMAILFROM
+following 553 badmail from
+.TP 5
+.I REPLY_BADRCPTTO
+following 553 badrcpt to
+.TP 5
+.I REPLY_SENDEREXIST
+following 553 SMTP sender DNS
+.TP 5
+.I REPLY_NOGATEWAY
+following 553 No gateway
+.TP 5
+.I REPLY_SENDERINVALID
+following 553 SMTP sender invalid
+.TP 5
+.I REPLY_CONTENT
+following 554 Message content invalid
+
+.SH "ENVIRONMENT VARIABLES SET"
+By means of the following environment variables,
+the SMTP session can be interrogated:
+.TP 5
+.I HELOHOST
+the HELO/EHLO greeting of the SMTP client.
+.TP 5
+.I AUTHPROTOCOL
+the ESMTPA protocol used for authentication.
+.TP 5
+.I AUTHUSER
+the supplied username for authentication.
+.TP 5
+.I MAILFROM
+containes the received 'Mail From:' address.
+.TP 5
+.I RCPTTO
+containes all received 'Rcpt To:' addresses separated by blanks.
+.TP 5
+.I TCP(6)REMOTEINFO
+in authentication mode set to the accepted username.
+.TP 5
+.I SSL_*
+information from
+.BR sslserver ,
+if applicable.
+
+.SH "SEE ALSO"
+tcp-environ(5),
+qmail-control(5),
+qmail-inject(8),
+qmail-newmrh(8),
+qmail-newbmt(8),
+qmail-authuser(8),
+qmail-recipients(8),
+qmail-postgrey(8),
+qmail-smtpam(8),
+qmail-mfrules(8),
+qmail-queue(8),
+qmail-remote(8),
+qmail-send(8),
+qmail-log(8),
+tcpserver(8),
+sslserver(8).
+
diff --git a/man/qmail-start.9 b/man/qmail-start.9
new file mode 100644
index 0000000..b801ac2
--- /dev/null
+++ b/man/qmail-start.9
@@ -0,0 +1,94 @@
+.TH s/qmail: qmail-start 8
+.SH NAME
+qmail-start \- turn on mail delivery
+.SH SYNOPSIS
+.B qmail-start
+[
+.I defaultdelivery
+[
+.I logger arg ...
+]
+]
+.SH DESCRIPTION
+.B qmail-start
+invokes
+.BR qmail-send ,
+.BR qmail-lspawn ,
+.BR qmail-rspawn ,
+and
+.BR qmail-clean ,
+under the proper uids and gids.
+These four daemons cooperate to deliver messages from the queue.
+
+.B qmail-start
+arranges for
+.BR qmail-send 's
+activity record to be sent to
+.BR qmail-start 's
+output.
+See
+.B qmail-log(5)
+for the format of the activity record.
+Other than this,
+.B qmail-start
+does not print anything, even on failure.
+
+If
+.I defaultdelivery
+is supplied,
+.B qmail-start
+passes it to
+.BR qmail-lspawn .
+
+If
+.I logger
+is supplied,
+.B qmail-start
+invokes
+.I logger
+with the given arguments,
+and feeds
+.BR qmail-send 's
+activity record through
+.IR logger .
+
+Environment variables given to
+.B qmail-start
+will eventually be passed on to
+.BR qmail-local ,
+so make sure to clean up the environment if you run
+.B qmail-start
+manually:
+
+.EX
+ # env - PATH="HOME/bin:$PATH"
+.br
+ qmail-start ./Mailbox splogger qmail &
+.br
+ (all on one line)
+.EE
+
+Resource limits, controlling ttys, et al. are also passed from
+.B qmail-start
+to
+.BR qmail-local .
+
+Note that
+.B qmail-send
+normally juggles several simultaneous deliveries.
+To reduce
+.BR qmail-send 's
+impact on other programs,
+you can run
+.B qmail-start
+with a low priority.
+.SH "SEE ALSO"
+logger(1),
+splogger(1),
+nice(1),
+qmail-log(5),
+qmail-local(8),
+qmail-clean(8),
+qmail-lspawn(8),
+qmail-rspawn(8),
+qmail-send(8)
diff --git a/man/qmail-tcpok.8 b/man/qmail-tcpok.8
new file mode 100644
index 0000000..3052c96
--- /dev/null
+++ b/man/qmail-tcpok.8
@@ -0,0 +1,24 @@
+.TH s/qmail: qmail-tcpok 8
+.SH NAME
+qmail-tcpok \- clear TCP timeout table
+.SH SYNOPSIS
+.B qmail-tcpok
+.SH DESCRIPTION
+.B qmail-tcpok
+erases
+.BR qmail-remote 's
+current list of timeouts,
+so that
+.B qmail-remote
+does not make any assumptions about failing addresses.
+
+.B qmail-tcpok
+must be run either as
+.B root
+or with user id
+.B qmailr
+and group id
+.BR sqmail .
+.SH "SEE ALSO"
+qmail-remote(8),
+qmail-tcpto(8)
diff --git a/man/qmail-tcpto.8 b/man/qmail-tcpto.8
new file mode 100644
index 0000000..ed44617
--- /dev/null
+++ b/man/qmail-tcpto.8
@@ -0,0 +1,30 @@
+.TH s/qmail: qmail-tcpto 8
+.SH NAME
+qmail-tcpto \- print TCP timeout table
+.SH SYNOPSIS
+.B qmail-tcpto
+.SH DESCRIPTION
+After an SMTP connection attempt times out,
+.B qmail-remote
+records the relevant IP address.
+If the same address fails again (after at least two minutes with
+no intervening successful connections),
+.B qmail-remote
+assumes that further attempts will fail for at least another hour.
+
+.B qmail-tcpto
+prints
+.BR qmail-remote 's
+current list of timeouts.
+
+.B qmail-tcpto
+must be run either as
+.B root
+or with user id
+.B qmailr
+and group id
+.BR sqmail .
+.SH "SEE ALSO"
+qmail-qread(8),
+qmail-remote(8),
+qmail-tcpok(8)
diff --git a/man/qmail-todo.8 b/man/qmail-todo.8
new file mode 100644
index 0000000..740f5b3
--- /dev/null
+++ b/man/qmail-todo.8
@@ -0,0 +1,128 @@
+.TH s/qmail: qmail-todo 8
+.SH NAME
+qmail-todo \- schedule state change of message for delivery
+.SH SYNOPSIS
+.B qmail-todo
+.SH DESCRIPTION
+.B s/qmail
+with a high local and remote concurrency number
+is able to deliver a tremendous amount of messages (throughput).
+Depending on the provided resources however,
+often this can not be achieved since
+.B qmail-send
+becomes a bottleneck on delivery.
+
+.B qmail-send
+preprocesses all new messages before deploying them for
+.I local
+or for
+.I remote
+delivering. In a particulur run,
+.B qmail-send
+does one 'todo' processing, but has the ability to close multiple jobs.
+Due to this layout, potentially
+.B qmail-send
+can not feed all the new available (local/remote) delivery slots
+and therefore, it is not possible to achieve the maximum throughput.
+
+This is a minor problem, given
+.B qmail-send
+is able to complete this in short time; but due to
+many file system calls (fsync and (un)link) a 'todo'
+run is expensive and throttles the throughput.
+
+.B qmail-todo
+solves this 'silly qmail (queue) problem'
+which is apparent only on system with high injection rates,
+delegating the scheduling of 'todo' runs to a dedicated process.
+
+.SH "COMMUNICATION"
+.B qmail-todo
+interfaces with
+.B qmail-send
+on file descriptors \fI[1,8]\fR on sending
+and \fI[7,0]\fR for receiving.
+.B qmail-todo
+communicates with
+.B qmail-clean
+on file descriptors \fI[2,0]\fR for sending
+and \fI[3,1]\fR for receiving.
+
+.B qmail-todo
+and
+.B qmail-send
+share an extended and peristent message exchange format:
+
+.EX
+D[LRB]<mesgid>\0
+ Start delivery for new message with id <messid>.
+ The character L, R or B defines the type
+ of delivery: Local, Remote, or Both, respectively.
+.EE
+
+.EX
+L<string>\0
+ Dump string to the logger without adding additional
+ '\\n' or similar.
+.EE
+
+.B qmail-todo
+sends "\\0" terminated messages, whereas
+.B qmail-send
+just sends one character to
+.BR qmail-todo .
+
+.SH "BIG PICTURE"
+.EX
+ +-------+ +-------+
+ | clean | | clean |
+ +--0-1--+ +--0-1--+ +-----------+
+ trigger ^ | ^ | +->0,1 lspawn |
+ | | v | v / +-----------+
+ +-------+ v +--2-3--+ +--5-6--+ /
+ | | | | 0<--7 1,2<-+
+ | queue |--+--| todo | | send |
+ | | | | 1-->8 3,4<-+
+ +-------+ +-------+ +---0---+ \\
+ | \\ +-----------+
+ v +->0,1 rspwan |
+ +---0---+ +-----------+
+ | logger|
+ +-------+
+.EE
+
+.SH "EXIT CODES"
+.B qmail-todo
+exits
+.I 0
+if the messages have been processed successfully.
+It exits
+.I 1
+in case there is a communication problem with
+.BR qmail-send .
+The exit code
+.I 111
+together with a diagnostic message is facilitated by
+.B qmail-todo
+in case it failes reading the required control files.
+
+.SH "DIAGNOSTICS"
+.B qmail-todo
+provides additional diagnostic messages to
+.B qmail-send
+to be displayed in the logs. In particular, in
+case of problems creating and (un)linking files.
+
+.SH "CREDITS"
+.B qmail-todo
+included in
+.B s/qmail
+has been created by Andre Oppermann (http://www.nrg4u.com)
+as part of this LDAP patch for
+.BR qmail .
+This man-page uses parts of his EXTERNAL discription.
+
+
+.SH "SEE ALSO"
+qmail-send(8),
+qmail-queue(8).
diff --git a/man/qmail-users.9 b/man/qmail-users.9
new file mode 100644
index 0000000..6ef5548
--- /dev/null
+++ b/man/qmail-users.9
@@ -0,0 +1,117 @@
+.TH s/qmail: qmail-users 5
+.SH NAME
+qmail-users \- assign mail addresses to users
+.SH OVERVIEW
+The file
+.B SQMAIL/users/assign
+assigns the local part of mail addresses to users. For example,
+
+.EX
+ =joe.shmoe:joe:503:78:/home/joe:::
+.EE
+
+says that mail for
+.B joe.shmoe
+should be delivered to user
+.BR joe ,
+with uid 503 and gid 78,
+as specified by
+.BR /home/joe/.qmail .
+
+Assignments fed to
+.B qmail-newu
+will be used by
+.B qmail-lspawn
+to control
+.BR qmail-local 's
+deliveries.
+Use
+.B qmail-newu (8)
+to generate
+.I users/assign.cdb
+from
+.IR users/assign .
+A change to
+.B SQMAIL/users/assign
+will have no effect until
+.B qmail-newu
+is run.
+.SH STRUCTURE
+.B SQMAIL/users/assign
+is a series of assignments, one per line.
+It ends with a line containing a single dot.
+Lines must not contain NUL.
+.SH "SIMPLE ASSIGNMENTS"
+A simple assignment is a line of the form
+
+.EX
+ =local:user:uid:gid:homedir:dash:ext:
+.EE
+
+Here
+.I local
+is an address;
+.IR user ,
+.IR uid ,
+and
+.I gid
+are the account name, uid, and gid
+of the user in charge of
+.IR local ;
+and messages to
+.I local
+will be controlled by
+.IR homedir\fB/.qmail\fIdashext .
+
+If there are several assignments for the same
+.I local
+address,
+.B qmail-lspawn
+will use the first one.
+
+.I local
+is interpreted without regard to case.
+.SH "WILDCARD ASSIGNMENTS"
+A wildcard assignment is a line of the form
+
+.EX
+ +loc:user:uid:gid:homedir:dash:pre:
+.EE
+
+This assignment applies to any address beginning with
+.IR loc ,
+including
+.I loc
+itself.
+It means the same as
+
+.EX
+ =locext:user:uid:gid:homedir:dash:preext:
+.EE
+
+for every string
+.IR ext .
+
+A more specific wildcard assignment overrides a less specific
+assignment, and a simple assignment overrides any wildcard assignment.
+For example:
+
+.EX
+ +:alias:7790:2108:SQMAIL/alias:-::
+ +joe-:joe:507:100:/home/joe:-::
+ =joe:joe:507:100:/home/joe:::
+.EE
+
+The address
+.B joe
+is handled by the third line;
+the address
+.B joe-direct
+is handled by the second line;
+the address
+.B bill
+is handled by the first line.
+.SH "SEE ALSO"
+qmail-pw2u(8),
+qmail-newu(8),
+qmail-lspawn(8)
diff --git a/man/qmail-vmailuser.9 b/man/qmail-vmailuser.9
new file mode 100644
index 0000000..e19898d
--- /dev/null
+++ b/man/qmail-vmailuser.9
@@ -0,0 +1,108 @@
+.TH s/qmail: qmail-vmailuser 8
+
+.SH "NAME"
+qmail-vmailuser \- recipient maildir validation
+
+.SH "SYNOPSIS"
+.B qmail-vmailuser
+.I [homedir]
+.I [-C]
+.SH "DESCRIPTION"
+.B qmail-vmailuser
+is a maildir verification PAM supporting
+.I VMailMgr
+and
+.I Vpopmail
+users for virtual domains.
+Invoked via
+.BR qmail-smtpd 's
+recipient mechanism, it checks the
+existence of the recipient directory
+for the provisioned virtual users in
+.IR SQMAIL/control/virtualusers .
+
+.B qmail-vmailuser
+follows
+.BR checkpassword 's
+interface specification evaluating the
+SMTP forwarding path (RCPT TO:) taken from
+discriptor 3 with a length of max 128 bytes.
+
+The forwarding path
+.I vuser@domain
+is tokenized to determine the
+virtual user in
+.I SQMAIL/control/virtualusers
+given by
+.I domain
+in the first step and then validating for
+.I vuser
+the existance of (v)user's mail directory
+in lower case while substituting dots by colons.
+.SH "USAGE"
+.B qmail-vmailuser
+is called as PAM from
+.BR qmail-smtpd 's
+control file
+.IR SQMAIL/control/recipients :
+
+.EX
+ domain|bin/qmail-vmailuser
+ *|bin/qmail-vmailuser /homedir -C
+.EE
+
+No specific settings are required to support
+either
+.I VMailMgr
+or
+.IR Vpopmail ,
+except for the
+.I homedir
+and perhaps the option
+.I -C
+evaluating
+.I vuser
+in case respect mode.
+Since
+.I homedir
+defaults mostly to
+.IR /home ,
+this argument can be omitted.
+.SH "SECURITY"
+For successfull operation
+.B qmail-vmailuser
+requires to stat
+.IR vuser 's
+directory though without reading
+it's actual contents. Due to
+restrictions given by
+.IR Vpopmail ,
+.B qmail-vmailuser
+needs to belong to
+.I vpopmail:vchkpw
+or gnerally to be
+root-owned and 'sticky'.
+.SH "RETURN CODES"
+If for the provided
+.I vuser@domain
+the user directory does not exist
+.B qmail-vmailuser
+exits 1.
+If
+.B qmail-vmailuser
+is misused, it may instead exit 2.
+If there is a temporary problem,
+.B qmail-vmailuser
+exits 111.
+In case
+.B qmail-vmailuser
+can't read
+.I SQMAIL/control/recipients
+it exits 110.
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-send(8),
+qmail-smtpd(8),
+qmail-recipients(8),
+qmail-authuser(8).
diff --git a/man/qreceipt.1 b/man/qreceipt.1
new file mode 100644
index 0000000..37b39ed
--- /dev/null
+++ b/man/qreceipt.1
@@ -0,0 +1,33 @@
+.TH s/qmail: qreceipt 1
+.SH NAME
+qreceipt \- respond to delivery notice requests
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |qreceipt
+.I youraddress
+.SH DESCRIPTION
+When a mail message arrives with
+.I youraddress
+listed in a
+.B Notice-Requested-Upon-Delivery-To
+header field,
+.B qreceipt
+sends a success notice back to the envelope sender.
+
+.B WARNING:
+If you create a
+.B .qmail
+file to enable
+.BR qreceipt ,
+make sure to also add a line specifying delivery to your normal mailbox.
+For example:
+
+.EX
+ /home/joe/Mailbox
+.br
+ |qreceipt joe@nowhere.mil
+.EE
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5)
diff --git a/man/setforward.1 b/man/setforward.1
new file mode 100644
index 0000000..1c2925c
--- /dev/null
+++ b/man/setforward.1
@@ -0,0 +1,204 @@
+.TH s/qmail: setforward 1
+.SH NAME
+setforward \- create a forwarding database
+.SH SYNOPSIS
+.B setforward
+.I cdb
+.I tmp
+.SH DESCRIPTION
+.B setforward
+reads a table of forwarding instructions from its standard input.
+It converts the table into a forwarding database.
+The forwarding database can be used by
+.BR fastforward .
+
+.B setforward
+writes the forwarding database to
+.IR tmp ;
+it then moves
+.I tmp
+to
+.IR cdb .
+.I tmp
+and
+.I cdb
+must be on the same filesystem.
+
+If there is a problem creating
+.IR tmp ,
+.B setforward
+complains and leaves
+.I cdb
+alone.
+
+The forwarding database format is portable across machines.
+.SH "INSTRUCTION FORMAT"
+A forwarding instruction contains a
+.I target\fR,
+a colon, a series of commands, and a semicolon.
+Each command is a
+.I recipient address\fR,
+.I owner address\fR,
+.I external mailing list\fR,
+or
+.I program\fR.
+Commands are separated by commas.
+
+For example,
+
+.EX
+ root@yp.to: god@heaven.af.mil, staff@af.mil;
+.EE
+
+says that mail for
+.B root@yp.to
+should be forwarded to the recipient addresses
+.B god@heaven.af.mil
+and
+.BR staff@af.mil .
+
+When
+.B setforward
+sees # it ignores all text from # to the end of the line:
+
+.EX
+ # this is a comment
+.EE
+
+.B setforward
+ignores all other line endings,
+so you can split a forwarding instruction across lines.
+It also ignores spaces and tabs.
+Exception:
+you can put a space (or tab or comma or whatever)
+into a target or command by putting a backslash in front of it.
+(However, NUL bytes are not permitted anywhere.)
+.SH "TARGETS"
+When
+.B fastforward
+sees the incoming address
+.IR user@host.dom ,
+it tries three targets:
+.IR user@host.dom ,
+.IR @host.dom ,
+and
+.IR user@ .
+It obeys the commands for the first target that it finds.
+Target names are interpreted without regard to case.
+
+All the commands for a single target must be listed in a single instruction.
+Exception: an owner address can be listed in a separate instruction.
+.SH "RECIPIENT ADDRESSES"
+If a command begins with an ampersand,
+.B setforward
+takes the remaining bytes in the command as a recipient address:
+
+.EX
+ boss@yp.to: &god@heaven.af.mil;
+.EE
+
+.B fastforward
+sends each incoming mail message
+to the recipient address.
+The recipient address must include a fully qualified domain name.
+It cannot be longer than 800 bytes.
+
+If a recipient address is itself a target in the forwarding table,
+.B fastforward
+will recursively handle the instructions for that target.
+Note that
+.I @host.dom
+and
+.I user@
+wildcards do not apply here;
+they apply only to the incoming address.
+
+If a command begins with a letter or number,
+.B setforward
+takes the entire command as a recipient address:
+
+.EX
+ boss@yp.to: god@heaven.af.mil;
+.EE
+.SH "OWNER ADDRESSES"
+If a command begins with a question mark,
+.B setforward
+takes the remaining bytes in the command as an owner address:
+
+.EX
+ sos@heaven.af.mil: ?owner-sos@heaven.af.mil;
+.EE
+
+.B fastforward
+uses that address as the envelope sender for forwarded mail,
+so bounces will go back to that address.
+(Normally, if a message is forwarded to a bad address,
+it will bounce back to the original envelope sender.)
+.SH "EXTERNAL MAILING LISTS"
+If a command begins with a dot or slash,
+.B setforward
+takes the entire command as the name of a binary mailing list file created by
+.BR setmaillist :
+
+.EX
+ sos@heaven.af.mil: /etc/lists/sos.bin;
+.EE
+
+.B fastforward
+will read and obey the commands in that file.
+The file must be world-readable
+and accessible to
+.BR fastforward .
+.SH "PROGRAMS"
+If a command begins with a vertical bar or exclamation point,
+.B setforward
+takes the rest of the command as the name of a program to run:
+
+.EX
+ dew@: |dew-monitor;
+.EE
+
+For a vertical bar,
+.B fastforward
+feeds the message
+to that program.
+An exclamation point works the same way except that
+.B fastforward
+inserts
+.BR $UFLINE ,
+.BR $RPLINE ,
+and
+.B $DTLINE
+in front of the message.
+.SH "DUPLICATES"
+When
+.B fastforward
+is building the recipient list for a message,
+it keeps track of the recipient addresses and external mailing lists
+it has used.
+If the same command shows up again, it skips it.
+For example:
+
+.EX
+ everybody@yp.to: programmers@yp.to, testers@yp.to;
+ programmers@yp.to: joe@yp.to, bob@yp.to;
+ testers@yp.to: joe@yp.to, fred@yp.to;
+.EE
+
+A message to
+.B everybody@yp.to
+will be sent to
+.B joe@yp.to
+only once.
+(This also means that addresses in an internal forwarding loop
+are discarded.)
+
+Exception:
+If a target has an owner address,
+commands for that target are considered different
+from commands for ``outside'' targets.
+.SH "SEE ALSO"
+newaliases(1),
+preline(1),
+printforward(1),
+setmaillist(1)
diff --git a/man/setmaillist.1 b/man/setmaillist.1
new file mode 100644
index 0000000..59fbf7d
--- /dev/null
+++ b/man/setmaillist.1
@@ -0,0 +1,72 @@
+.TH s/qmail: setmaillist 1
+.SH NAME
+setmaillist \- create a binary mailing list
+.SH SYNOPSIS
+.B setmaillist
+.I bin
+.I tmp
+.SH DESCRIPTION
+.B setmaillist
+reads a mailing list from its standard input.
+
+.B setmaillist
+writes the mailing list in a binary format to
+.IR tmp ;
+it then moves
+.I tmp
+to
+.IR bin .
+.I tmp
+and
+.I bin
+must be on the same filesystem.
+
+If there is a problem creating
+.IR tmp ,
+.B setmaillist
+complains and leaves
+.I bin
+alone.
+
+The binary mailing list format is portable across machines.
+
+.B setmaillist
+always creates
+.I bin
+world-readable.
+.SH "MAILING LIST FORMAT"
+The mailing list read by
+.B setmaillist
+is a series of lines.
+NUL bytes are not allowed.
+
+If a line begins with a dot or slash,
+.B setmaillist
+takes the entire line as an include file name.
+
+If a line begins with an ampersand,
+.B setmaillist
+takes the rest of the line as a recipient address.
+If a line begins with a letter or number,
+.B setmaillist
+takes the entire line as a recipient address.
+Each recipient address must include a fully qualified domain name.
+Recipient addresses longer than 800 bytes are not allowed.
+
+.B setmaillist
+ignores blank lines
+and lines beginning with #.
+It also ignores spaces and tabs at the ends of lines.
+
+For example,
+
+.EX
+ god@heaven.af.mil
+ djb@silverton.berkeley.edu
+.EE
+
+is a mailing list with two addresses.
+.SH "SEE ALSO"
+setforward(1),
+newinclude(1),
+printmaillist(1)
diff --git a/man/spfquery.8 b/man/spfquery.8
new file mode 100644
index 0000000..4c26323
--- /dev/null
+++ b/man/spfquery.8
@@ -0,0 +1,147 @@
+.TH s/qmail: spfquery 8
+.SH NAME
+spfquery \- SPF test program
+.SH SYNOPSIS
+.B spfquery
+.I sender-ip
+.I sender-helo
+.I envelope-from
+.I [local rules]
+.I [-v]
+.SH DESCRIPTION
+.B spfquery
+is a test program to allow evaluation
+of
+.I SPF records
+fetched on demand by means of
+.BR qmail-smtpd .
+
+.SH "ARGUMENTS"
+.B spfquery
+uses the given arguments
+.IR sender-ip ,
+.IR sender-helo ,
+and
+.I envelope-from
+to perform a DNS SPF TXT lookup
+and evaluates the results.
+In addition, \'local-rules\' might
+be included as
+.IR local-rules .
+By means of the (last) option
+.I -v
+a verbose output is provided.
+
+.SH "RESPONSE"
+The result of
+.B spfquery
+shows the SPF return codes of the retrieved
+information after the DNS evaluation.
+Additionally, the mechanisms and
+results are displayed as chain
+of resulting codes. In case the option
+.I -v
+is given, the received DNS SPF TXT records
+for the analysed domain are shown in raw
+format to allow further diagnostics.
+
+.SH "SPF MECHANISMS"
+.B spfquery
+and of course
+.B qmail-smtpd
+support all mechanisms defined in
+.IR RFC\ 7208 ,
+in particular:
+.IR A/AAAA ,
+.IR IPv4 ,
+.IR IPv6 ,
+.IR MX ,
+.IR PTR ,
+.IR Exists .
+Nesting of SPF records - indicated by the commands
+.I include:
+and
+.I redirect=
+- is allowed and the chain is followed.
+Further,
+.I exp(lanation)=
+is supported.
+
+.SH "SPF QUALIFIERS"
+SPF makes uses of command and explanation qualifiers.
+Command and explanation characters are:
+.I +
+pass (default),
+.I -
+fail,
+.I ~
+softfail,
+.I ?
+neutral.
+
+.SH "EXPLANATION CHARACTERS"
+This implementation uses the following
+additional explanation characters:
+.I o
+none,
+.I u
+unknown,
+.I d
+DNS problem (not used).
+
+.SH "MACRO EXPANSION"
+Macros (keyword) expansion is supported conforming to
+.IR RFC\ 7208 .
+
+
+.SH "SPF EVALUATION"
+.B spfquery
+provides a brief summary of results for the evaluation:
+.I S
+the sending IP,
+.I O
+the envelope-from address,
+.I C
+the requested domain for lookup,
+.I H
+the HELO/EHLO of the contacted MTA,
+.I M
+the SPF lookup mechanis as explained,
+.I I
+the included domanin for lookup,
+.I D
+the (re)direct to follow,
+.I P
+a potential problem observed.
+These letters are followed by an equal sign '='
+and detail the information.
+.I R
+is the lookup result obtained, followed by a
+colon ':'.
+
+.SH "DIAGNOSTICS"
+Additional DNS diagnostic routines are available:
+.B dnstxt
+returns the DNS TXT for
+.IR host .
+.B dnsptr
+returns the DNS PTR for
+.IR IP .
+.B dnsmxip
+returns the MTA IPs for
+.IR domain .
+
+.SH "CREDITS"
+The
+.B spfquery
+program and the SPF integration into
+.B s/qmail
+follows mainly the implementation of
+Jana Saout (http://www.saout.de/misc/spf/)
+and is used by permission.
+
+.SH "SEE ALSO"
+qmail-control(5),
+qmail-smtpd(8)
+dnsmxip(8),
+dnstxt(8).
diff --git a/man/splogger.8 b/man/splogger.8
new file mode 100644
index 0000000..c9137a3
--- /dev/null
+++ b/man/splogger.8
@@ -0,0 +1,60 @@
+.TH s/qmail: splogger 8
+.SH NAME
+splogger \- make entries in syslog
+.SH SYNOPSIS
+.B splogger
+[
+.I tag
+[
+.I fac
+]
+]
+.SH DESCRIPTION
+.B splogger
+reads a series of messages and feeds them to
+.BR syslog .
+At the front of each message it puts
+.I tag
+(default:
+.BR splogger )
+and a numerical timestamp.
+
+.B splogger
+checks for
+.B alert:
+or
+.B warning:
+at the beginning of each message.
+It selects a priority of
+LOG_ALERT, LOG_WARNING, or LOG_INFO accordingly.
+
+.B splogger
+logs messages with facility
+.IR fac .
+.I fac
+(default: 2)
+must be numeric.
+
+.B splogger
+converts unprintable characters to question marks.
+
+.B splogger
+does not log blank lines.
+
+.B splogger
+folds messages after 800 characters,
+since
+.B syslog
+can't handle long messages.
+.B splogger
+uses a + after the timestamp
+to mark folded lines.
+
+Note that the
+.B syslog
+mechanism is inherently unreliable:
+it does not guarantee that messages will be logged.
+It is also very slow.
+.SH "SEE ALSO"
+syslog(3),
+logger(8)
diff --git a/man/sqmail.9 b/man/sqmail.9
new file mode 100644
index 0000000..921a95c
--- /dev/null
+++ b/man/sqmail.9
@@ -0,0 +1,130 @@
+.TH s/qmail: s/qmail 7
+.SH "NAME"
+s/qmail \- overview of s/qmail documentation
+.SH "INTRODUCTION"
+.B s/qmail
+is a secure, encrypting, authenticating, reliable, efficient,
+yet simple IPv4/IPv6 message transfer agent based on
+.B qmail
+and ought to be plug-in compatible.
+The
+.B s/qmail
+software includes Dan Bernstein's
+.B fastforward
+and
+.B qmailanalog
+package in addition with other enhancements taken mainly from the
+.B Spamcontrol
+patch.
+
+The current version of
+.B s/qmail
+depends on the
+.B fehQlibs
+and
+.B OpenSSL
+or
+.BR LibreSSL .
+
+Users who want to control incoming messages
+should read
+.BR dot-qmail (5).
+Available commands for the
+.B .qmail
+file include
+.BR qbiff (1),
+.BR qreceipt (1),
+.BR forward (1),
+.BR fastforward (1),
+.BR bouncesaying (1),
+and
+.BR condredirect (1).
+Other helpful commands include
+.BR maildirmake (1),
+.BR maildir2mbox (1),
+and
+.BR maildirwatch (1).
+
+System administrators who want to control the entire
+.B s/qmail
+system should start with
+.BR qmail-control (5),
+.BR qmail-mfrules (8),
+and
+.BR qmail-start (8).
+
+There are four queue-monitoring/mangement tools:
+.BR qmail-qread (8),
+.BR qmail-qstat (8),
+.BR qmail-qmaint (8),
+and
+.BR qmail-tcpto (8).
+.BR qmail-mrtg (8)
+allows to feed the
+.B s/qmail
+logs to
+.BR MRTG .
+Incoming SMTP connections are handled by
+.BR qmail-smtpd (8)
+and
+.BR qmail-recipients (8)
+optionally together with
+.BR qmail-smtpam (8),
+.BR qmail-authuser (8)
+and perhaps with
+.BR qmail-vmailusers (8)
+if virtual mail managers like
+.B vpopmail
+or
+.B vmailmgr
+are in use.
+
+SRS is availalable within
+.B s/qmail
+by means of the additional commands
+.BR srsforward (1)
+and
+.BR srsreverse (1).
+DKIM message signing and verification is achieved with
+.B qmail-dksign (8)
+and
+.BR qmail-dkverify (8).
+
+.B s/qmail
+offers two command-line message-sending interfaces:
+.BR qmail-inject (8)
+and
+.BR mailsubj (1).
+For background information on Internet mail messages,
+see
+.BR addresses (5),
+.BR envelopes (5),
+.BR qmail-header (5),
+and
+.BR forgeries (7).
+
+Miscellaneous documentation includes
+.BR qmail-limits (7)
+and
+.BR qmail-pop3d (8).
+
+Apart from the Internet mail message transport protocols
+.I ESMTP/ESMTPS
+.B s/qmail
+supports
+.I QMTP/QMTPS
+together with the Pop Office message protocols
+.IR POP3/POP3S
+depending on the
+.B ucspi-ssl
+package for TLS support.
+
+This documentation describes version
+VERSION
+of
+.BR s/qmail .
+See
+.B https://www.fehcom.de/djbware.html
+for other
+.BR s/qmail -related
+software.
diff --git a/man/srsforward.1 b/man/srsforward.1
new file mode 100644
index 0000000..930c3df
--- /dev/null
+++ b/man/srsforward.1
@@ -0,0 +1,96 @@
+.TH s/qmail: srsforward 1
+.SH NAME
+srsforward \- forward mail to one or more addresses including a SRS extension
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |srsforward
+.I address ...
+.SH DESCRIPTION
+.B srsforward
+forwards mails for dedicated recipient
+.I srsdomains
+to the specified list of addresses
+while extending the SMTP 'RCPT TO:' envelope address with
+SRS (Sender Rewriting Scheme) information.
+It is a simple wrapper around
+.B qmail-queue
+rewriting the SMTP recipient address. The forwarded email
+ought to be acceptable for SPF enabled recipient MTAs.
+Additionally, it mitigates the forgery of addresses for bounces.
+.SH "CONTROL FILE"
+.B srsforward
+reads the control file
+.IR srsdomains .
+Here, you can specify
+
+.I srsdomain:SRS_secret1 SRS_secret2 ...|[+,-,=]|[srsaddress(.)]
+
+.I srsdomain
+is
+.B s/qmail's
+recipient domain; typically
+.I defaultdomain
+or any domain given in
+.IR rcpthosts .
+.I srsdomain
+can be simply expressed as '*', thus the
+following informations are
+applicable for all
+.B srsfoward
+domains as default values, while
+particular
+.I srsdomain
+settings have precedence.
+Reversely, recipient
+domains can be disable for SRS fowarding:
+.IR !nosrsfoward.example.com: .
+
+.B srsforward
+accepts several 'secrets' for each
+.I srsdomain
+separated by empty spaces.
+
+.BR srsfoward 's
+.I delimiter
+is a character chosen out of the set
+.I +,-,=
+with default
+.I =
+and thus is optional.
+
+.B srsforward
+may include
+.I srsaddress
+to construct the domain part of the RCPT TO:
+envelope address for SRS fowarded mails. If
+.I srsaddress
+ends with a dot '.',
+this name is used to prepend the original
+host name and typically is chosen as
+.IR srs. .
+Otherwise, the original host name is
+used as default
+.I srsaddress
+for forwarding and also relevant for
+potential bounces being subject of
+.BR srsreverse .
+.SH "ENVIRONMENT VARIABLES"
+.B srsforward
+reads the environment variables
+.IR HOST ,
+which is used to determine the
+.IR srsdomain ,
+.IR DTLINE ,
+and
+.IR NEWSENDER .
+.SH REFERENCE
+.B srsforward
+uses srs2.c from
+.IR libsrs2 .
+.SH "SEE ALSO"
+srsreverse(1),
+dot-qmail(5),
+qmail-command(8),
+qmail-queue(8),
+qmail-send(8).
diff --git a/man/srsreverse.9 b/man/srsreverse.9
new file mode 100644
index 0000000..5057330
--- /dev/null
+++ b/man/srsreverse.9
@@ -0,0 +1,87 @@
+.TH s/qmail: srsreverse 1
+.SH NAME
+srsreverse \- reconstruct the original address from its SRS extension
+and forward bounce mail
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |srsreverse
+.SH DESCRIPTION
+Upon reception by
+.BR qmail-smtpd ,
+.B qmail-local
+may feed a locally delivered bounce email through
+.B srsrevers
+in order to reconstruct the original sender from
+the received SRS address provided in the local part
+and to forward the bounce mail to its original address.
+.SH "SRS DOMAINS"
+In order to accept emails for SRS modified
+return addresses, you need to setup those in
+.IR rcpthosts .
+If your domain is
+.I example.com
+in
+.I rcpthosts
+you probably want to set up additionally
+.IR srs.example.com .
+However,
+.I .example.com
+would be fine as well.
+.SH "VIRTUAL SRS USER"
+SRS can facilitate a virtual user typically named
+.I srs
+and thus requires an entry like
+.I srs.example.com:srs
+in
+.IR virtualdomains .
+.SH "DOT QMAIL"
+.B srsreverse
+is called from a
+.I dot-qmail
+file which could be
+.IR SQMAIL/alias/.qmail-srs-default .
+.SH "CONTROL FILES"
+.B srsreverse
+reads the control file
+.I virtualdomains
+to exfiltrate the (virtual) SRS user name for the received domain,
+if given. With the evaluated
+.IR srsdomain ,
+.B srsrevers
+fetches the
+.I SRS secret
+from
+.I srsdomains
+in order to validate the SRS bounce address.
+.SH "ENVIRONMENT VARIABLES"
+.B srsrverse
+reads the environment variables
+.IR DTLINE ,
+.IR HOST ,
+and
+.IR RECIPIENTS .
+.I HOST
+is used to determine the
+.IR srsdomain .
+The forwarding bounce address is reconstructed from
+the local part of
+.IR RECIPIENTS .
+.SH VERP
+The Sender Rewriting Scheme SRS can be considered
+as tailored form of VERP: Variable Envelope Return Path.
+The chosen primary delimiter
+.I =
+is recognized by
+.BR qmail-smtpd 's
+recipient extension.
+.SH REFERENCE
+.B srsreverse
+uses srs2.c from
+.IR libsrs2 .
+.SH "SEE ALSO"
+srsforward(1),
+dot-qmail(5),
+qmail-command(8),
+qmail-queue(8),
+qmail-send(8).
diff --git a/man/tai64nfrac.5 b/man/tai64nfrac.5
new file mode 100644
index 0000000..6a2cc5f
--- /dev/null
+++ b/man/tai64nfrac.5
@@ -0,0 +1,18 @@
+.TH s/qmail: tai64nfrac 5
+.SH NAME
+tai64nfrac \- evaluate the TAI64 timestamp and write the fractional seconds
+.SH SYNOPSIS
+.B tai64nfrac
+
+.SH DESCRIPTION
+Reads a TAI64N external format timestamp following the '@'
+as first character from
+.I stdin
+and
+writes the fractional seconds since epoch (TAI, not UTC) to
+.IR stdout .
+Returns the following characters after the timestamp unaltered.
+
+.SH "SEE ALSO"
+tcpserver(1),
+sslserver(1).
diff --git a/man/tcp-environ.5 b/man/tcp-environ.5
new file mode 100644
index 0000000..244d32a
--- /dev/null
+++ b/man/tcp-environ.5
@@ -0,0 +1,86 @@
+.TH s/qmail: tcp-environ 5
+.SH NAME
+tcp-environ \- TCP-related environment variables
+.SH DESCRIPTION
+The following environment variables
+describe a TCP connection.
+They are set up by
+.B tcpclient
+and
+.B tcpserver
+as well as
+.BR sslclient
+and
+.BR sslserver .
+
+Note that
+.BR TCPLOCALHOST ,
+.BR TCP6LOCALHOST ,
+.BR TCPREMOTEHOST ,
+.BR TCP6REMOTEHOST ,
+and
+.BR TCPREMOTEINFO ,
+.BR TCP6REMOTEINFO ,
+can contain arbitrary characters.
+.TP 5
+PROTO
+The string
+.BR TCP ,
+or
+.BR TCP6 .
+.TP 5
+TCPLOCALHOST/TCP6LOCALHOST
+The domain name of the local host,
+with uppercase letters converted to lowercase.
+If there is no currently available domain name
+for the local IP address,
+.BR TCPLOCALHOST ,
+.B TCP6LOCALHOST
+is not set.
+.TP 5
+TCPLOCALIP
+The IPv4 address of the local host, in dotted-decimal form.
+.TP 5
+TCP6LOCALIP
+The compactified IPv6 address of the local host.
+.TP 5
+TCPLOCALPORT/TCP6LOCALPORT
+The local TCP port number, in decimal.
+.TP 5
+TCPREMOTEHOST/TCP6RMOTEHOST
+The domain name of the remote host,
+with uppercase letters converted to lowercase.
+If there is no currently available domain name
+for the remote IP address,
+.B TCPREMOTEHOST
+or
+.B TCP6REMOTEHOST
+is not set.
+.TP 5
+TCPREMOTEINFO/TCP6REMOTEINFO
+A connection-specific string, perhaps a username,
+supplied by the remote host
+via 931/1413/IDENT/TAP.
+If the remote host did not supply connection information,
+.BR TCPREMOTEINFO ,
+.B TCP6REMOTEINFO
+is not set.
+.TP 5
+TCPREMOTEIP
+The IPv4 address of the remote host.
+.TP 5
+TCP6REMOTEIP
+The IPv6 address of the remote host.
+.TP 5
+TCPREMOTEPORT/TCP6REMOTEPORT
+The remote TCP port number.
+.TP 5
+TCP6INTERFACE
+contains the interface name for IPv6 connections.
+
+.SH "SEE ALSO"
+tcpclient(1),
+tcpserver(1),
+sslclient(1),
+sslserver(1),
+tcp(4)
diff --git a/man/xqp.1 b/man/xqp.1
new file mode 100644
index 0000000..14bf370
--- /dev/null
+++ b/man/xqp.1
@@ -0,0 +1,18 @@
+.TH s/qmail: xqp 1
+.SH NAME
+xqp \- locate a message given its qp
+.SH SYNTAX
+.B xqp
+.I qp
+.SH DESCRIPTION
+.B xqp
+reads message lines and delivery lines printed by
+.BR matchup .
+It prints the lines that involve messages with long-term queue identifier
+.IR qp .
+
+Long-term queue identifiers are not permanent identifiers.
+They are based on process IDs;
+15-bit process IDs can easily wrap around in less than an hour on a busy system.
+.SH "SEE ALSO"
+matchup(1)
diff --git a/man/xrecipient.1 b/man/xrecipient.1
new file mode 100644
index 0000000..ec58832
--- /dev/null
+++ b/man/xrecipient.1
@@ -0,0 +1,14 @@
+.TH s/qmail: xrecipient 1
+.SH NAME
+xrecipient \- locate all deliveries to one recipient
+.SH SYNTAX
+.B xrecipient
+.I channel.recipient
+.SH DESCRIPTION
+.B xrecipient
+reads message lines and delivery lines printed by
+.BR matchup .
+It prints the delivery lines that involve messages sent to
+.IR channel.recipient .
+.SH "SEE ALSO"
+matchup(1)
diff --git a/man/xsender.1 b/man/xsender.1
new file mode 100644
index 0000000..f919f8a
--- /dev/null
+++ b/man/xsender.1
@@ -0,0 +1,14 @@
+.TH s/qmail: xsender 1
+.SH NAME
+xsender \- locate all messages from one sender
+.SH SYNTAX
+.B xsender
+.I sender
+.SH DESCRIPTION
+.B xsender
+reads message lines and delivery lines printed by
+.BR matchup .
+It prints the lines that involve messages with return path
+.IR sender .
+.SH "SEE ALSO"
+matchup(1)
diff --git a/package/build b/package/build
new file mode 100644
index 0000000..390677b
--- /dev/null
+++ b/package/build
@@ -0,0 +1 @@
+20240226150615
diff --git a/package/command-cp b/package/command-cp
new file mode 100644
index 0000000..5115c14
--- /dev/null
+++ b/package/command-cp
@@ -0,0 +1,6 @@
+
+
+Additional directories to copy commands into, one per line.
+The first empty line terminates the list.
+
+Note: This file is 'empty' here, due to legacy installation.
diff --git a/package/command-ln b/package/command-ln
new file mode 100644
index 0000000..85feb78
--- /dev/null
+++ b/package/command-ln
@@ -0,0 +1 @@
+/usr/local/bin
diff --git a/package/commands-analog b/package/commands-analog
new file mode 100644
index 0000000..cbc4c74
--- /dev/null
+++ b/package/commands-analog
@@ -0,0 +1,25 @@
+columnt
+ddist
+deferrals
+failures
+matchup
+recipients
+rhosts
+rxdelay
+senders
+successes
+suids
+xqp
+xrecipient
+xsender
+zddist
+zdeferrals
+zfailures
+zoverall
+zrecipients
+zrhosts
+zrxdelay
+zsenders
+zsendmail
+zsuccesses
+zsuids
diff --git a/package/commands-base b/package/commands-base
new file mode 100644
index 0000000..501724b
--- /dev/null
+++ b/package/commands-base
@@ -0,0 +1,10 @@
+qmail-clean
+qmail-inject
+qmail-local
+qmail-lspawn
+qmail-send
+qmail-queue
+qmail-rspawn
+qmail-send
+qmail-start
+qmail-todo
diff --git a/package/commands-clients b/package/commands-clients
new file mode 100644
index 0000000..8383dcb
--- /dev/null
+++ b/package/commands-clients
@@ -0,0 +1,4 @@
+mailsubj
+qmail-remote
+qmail-qmqpc
+sendmail
diff --git a/package/commands-control b/package/commands-control
new file mode 100644
index 0000000..d05937c
--- /dev/null
+++ b/package/commands-control
@@ -0,0 +1,5 @@
+qmail-mfrules
+qmail-showctl
+qmail-badloadertypes
+qmail-badmimetypes
+qmail-recipients
diff --git a/package/commands-dkim b/package/commands-dkim
new file mode 100644
index 0000000..1ad1bf5
--- /dev/null
+++ b/package/commands-dkim
@@ -0,0 +1,2 @@
+qmail-dkim
+qmail-dksign
diff --git a/package/commands-dns b/package/commands-dns
new file mode 100644
index 0000000..c1d692c
--- /dev/null
+++ b/package/commands-dns
@@ -0,0 +1,10 @@
+dnscname
+dnsfq
+dnsip
+dnsmxip
+dnsptr
+dnstlsa
+dnstxt
+hostname
+ipmeprint
+spfquery
diff --git a/package/commands-forward b/package/commands-forward
new file mode 100644
index 0000000..1d2627d
--- /dev/null
+++ b/package/commands-forward
@@ -0,0 +1,8 @@
+fastforward
+forward
+setforward
+newaliases
+newinclude
+printforward
+printmaillist
+setmaillist
diff --git a/package/commands-log b/package/commands-log
new file mode 100644
index 0000000..3fe9f6f
--- /dev/null
+++ b/package/commands-log
@@ -0,0 +1,4 @@
+qmail-mrtg
+qmail-mrtg-queue
+splogger
+tai64nfrac
diff --git a/package/commands-mbox b/package/commands-mbox
new file mode 100644
index 0000000..fc32c41
--- /dev/null
+++ b/package/commands-mbox
@@ -0,0 +1,9 @@
+condredirect
+bouncesaying
+except
+maildirmake
+maildir2mbox
+maildirwatch
+preline
+qbiff
+qreceipt
diff --git a/package/commands-pam b/package/commands-pam
new file mode 100644
index 0000000..27a6a69
--- /dev/null
+++ b/package/commands-pam
@@ -0,0 +1,4 @@
+qmail-authuser
+qmail-smtpam
+qmail-vmailuser
+qmail-postgrey
diff --git a/package/commands-pop b/package/commands-pop
new file mode 100644
index 0000000..f14d6da
--- /dev/null
+++ b/package/commands-pop
@@ -0,0 +1,2 @@
+qmail-popup
+qmail-pop3d
diff --git a/package/commands-queue b/package/commands-queue
new file mode 100644
index 0000000..d439b37
--- /dev/null
+++ b/package/commands-queue
@@ -0,0 +1,5 @@
+qmail-qread
+qmail-qstat
+qmail-tcpok
+qmail-tcpto
+qmail-qmaint
diff --git a/package/commands-recipients b/package/commands-recipients
new file mode 100644
index 0000000..c83d272
--- /dev/null
+++ b/package/commands-recipients
@@ -0,0 +1 @@
+qmail-alias2recipients
diff --git a/package/commands-scan b/package/commands-scan
new file mode 100644
index 0000000..456c4b3
--- /dev/null
+++ b/package/commands-scan
@@ -0,0 +1 @@
+qmail-queue-scan
diff --git a/package/commands-server b/package/commands-server
new file mode 100644
index 0000000..be8bba9
--- /dev/null
+++ b/package/commands-server
@@ -0,0 +1,3 @@
+qmail-qmtpd
+qmail-qmqpd
+qmail-smtpd
diff --git a/package/commands-setup b/package/commands-setup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/package/commands-setup
diff --git a/package/commands-srs b/package/commands-srs
new file mode 100644
index 0000000..02db41a
--- /dev/null
+++ b/package/commands-srs
@@ -0,0 +1,2 @@
+srsforward
+srsreverse
diff --git a/package/commands-user b/package/commands-user
new file mode 100644
index 0000000..55d4d04
--- /dev/null
+++ b/package/commands-user
@@ -0,0 +1,4 @@
+qmail-getpw
+qmail-newu
+qmail-newmrh
+qmail-pw2u
diff --git a/package/commands-x509 b/package/commands-x509
new file mode 100644
index 0000000..a91ec38
--- /dev/null
+++ b/package/commands-x509
@@ -0,0 +1,2 @@
+x509fingerprint
+mkdkimkey
diff --git a/package/compile b/package/compile
new file mode 100755
index 0000000..b8aa789
--- /dev/null
+++ b/package/compile
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+safe umask 022
+[ -d package ] || barf "no package directory"
+[ -d src ] || barf "no src directory"
+
+here=`env - PATH=$PATH pwd`
+
+[ -d compile ] || safe mkdir -p compile
+[ -d commmand ] || safe mkdir -p command
+[ -r compile/home ] || echo $here > compile/home
+[ -h compile/src ] || safe ln -s $here/src compile/src
+
+for i in `ls src`
+do
+ [ ! -d src/$i ] && [ -h compile/$i ] || safe ln -sf src/$i compile/$i
+done
+
+for i in `sed -e '/^it-/!d' -e 's/^it-//' < compile/it=d`
+do
+ all="$all $i"
+done
+
+other="`grep -v '^it-' compile/it=d`"
+usage() { shout "usage: package/compile [ [-]$all ]"; exit 100; }
+
+targets=""
+if [ $# -eq 0 ]
+then
+ targets="$all"
+else
+ if [ "$1" = "-" ]
+ then
+ shift
+ suppress=":"
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ suppress="$suppress$i:"
+ done
+ for i in $all
+ do
+ case "$suppress" in
+ *:$i:*)
+ ;;
+ *)
+ targets="$targets $i"
+ ;;
+ esac
+ done
+ else
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ targets="$targets $i"
+ done
+ fi
+fi
+
+[ "X$all" != "X" ] && [ "X$targets" = "X" ] && usage
+
+commands=""
+for i in $targets
+do
+ commands="$commands `cat package/commands-$i`"
+done
+
+safe cd compile
+safe make $other `echo "$targets" | sed -e 's/ / it-/g'`
+safe cd $here
+
+for i in $commands
+do
+ i=${i%:}
+ safe rm -f command/$i'{new}'
+ safe cp -p compile/$i command/$i'{new}'
+ safe mv -f command/$i'{new}' command/$i
+done
+
+exit 0
diff --git a/package/control b/package/control
new file mode 100755
index 0000000..8f2bdf8
--- /dev/null
+++ b/package/control
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 $mypwd/conf-home`
+
+safe umask 022
+[ -d package ] || barf "no package directory"
+[ -d command ] || barf "no command directory"
+[ -d ctl ] || barf "no ctl directory"
+[ -d $home/control ] || barf "no s/qmail/control directory"
+
+[ -x command/hostname ] || barf "no 'hostname' file"
+
+[ -f $home/control/me ] || \
+safe command/hostname > $home/control/me
+
+[ -f $home/control/rcpthosts ] || \
+safe command/hostname > $home/control/rcpthosts
+
+[ -f $home/control/tlsdestinations ] || \
+safe echo '*:' > $home/control/tlsdestinations
+
+for i in `ls ctl`
+do
+ [ -f $home/control/$i ] || safe cp ctl/$i $home/control/
+done
+
+shout "s/qmail control files populated."
+
+exit 0
diff --git a/package/dir b/package/dir
new file mode 100755
index 0000000..ab9b8a0
--- /dev/null
+++ b/package/dir
@@ -0,0 +1,19 @@
+#!/bin/sh
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 $mypwd/conf-home`
+
+if [ -d $home ]
+then
+ shout "Keeping directory $home."
+ shout "The queue directory will use now bigtodo ..."
+else
+ safe mkdir -p $home
+fi
+
+exit 0
diff --git a/package/files b/package/files
new file mode 100644
index 0000000..64878ca
--- /dev/null
+++ b/package/files
@@ -0,0 +1,543 @@
+INSTALL
+README.md
+addons
+addons/clamav-0.90.1_output.patch
+conf-break
+conf-cc
+conf-delivery
+conf-groups
+conf-home
+conf-idn2
+conf-ids
+conf-instances
+conf-ld
+conf-log
+conf-man
+conf-patrn
+conf-qlibs
+conf-qmq
+conf-spawn
+conf-split
+conf-svcdir
+conf-ucspissl
+conf-users
+ctl
+ctl/badloadertypes
+ctl/badmailfrom
+ctl/badmimetypes
+ctl/badrcptto
+ctl/locals
+ctl/rules.qmtpd.cdb
+ctl/rules.qmtpd.txt
+ctl/rules.smtpd.cdb
+ctl/rules.smtpd.txt
+doc
+doc/BLURB
+doc/CHANGELOG
+doc/CHANGELOG_V3
+doc/CONTRIBUTERS
+doc/EXTTODO
+doc/LICENSE
+doc/LOGGING
+doc/Old/PROPOSAL.mav
+doc/Old/README.djbdns
+doc/Old/README.mav
+doc/Old/README.qmq
+doc/Old/README.recipients
+doc/Old/README.wildmat
+doc/Postgrey.txt
+doc/Qmail/BLURB
+doc/Qmail/FAQ
+doc/Qmail/INSTALL.alias
+doc/Qmail/INSTALL.ctl
+doc/Qmail/INSTALL.ids
+doc/Qmail/INSTALL.maildir
+doc/Qmail/INSTALL.mbox
+doc/Qmail/INSTALL.qmail
+doc/Qmail/INTERNALS
+doc/Qmail/PIC.local2alias
+doc/Qmail/PIC.local2ext
+doc/Qmail/PIC.local2local
+doc/Qmail/PIC.local2rem
+doc/Qmail/PIC.local2virt
+doc/Qmail/PIC.nullclient
+doc/Qmail/PIC.relaybad
+doc/Qmail/PIC.relaygood
+doc/Qmail/PIC.rem2local
+doc/Qmail/README
+doc/Qmail/REMOVE.binmail
+doc/Qmail/REMOVE.sendmail
+doc/Qmail/SYSDEPS
+doc/Qmail/TEST.deliver
+doc/Qmail/TEST.receive
+doc/Qmail/THANKS
+doc/Qmail/THOUGHTS
+doc/Qmail/TODO.djb
+doc/Qmail/TODO.done
+doc/README.clamav
+doc/README.smtpreply
+doc/TODO
+doc/TODO
+doc/smtpreplies
+etc/qmail-mrtg.pop3d.sample
+etc/qmail-mrtg.send.sample
+etc/qmail-mrtg.smtpd.sample
+man
+man/Makefile
+man/Makefile.mandoc
+man/TARGETS
+man/addresses.5
+man/bouncesaying.1
+man/columnt.1
+man/condredirect.1
+man/datetime.3
+man/dnscname.8
+man/dnsfq.8
+man/dnsip.8
+man/dnsmxip.8
+man/dnsptr.8
+man/dnstlsa.8
+man/dnstxt.8
+man/dot-qmail.9
+man/envelopes.5
+man/except.1
+man/fastforward.1
+man/forgeries.7
+man/forward.1
+man/hostname.8
+man/ipmeprint.8
+man/maildir.5
+man/maildir2mbox.1
+man/maildirmake.1
+man/maildirwatch.1
+man/mailsubj.1
+man/matchup.1
+man/mbox.5
+man/newaliases.1
+man/newinclude.1
+man/preline.1
+man/printforward.1
+man/printmaillist.1
+man/qbiff.1
+man/qmail-authuser.9
+man/qmail-badloadertypes.9
+man/qmail-badmimetypes.9
+man/qmail-clean.8
+man/qmail-command.8
+man/qmail-control.9
+man/qmail-dkim.8
+man/qmail-dksign.9
+man/qmail-dkverify.8
+man/qmail-getpw.9
+man/qmail-header.5
+man/qmail-inject.8
+man/qmail-limits.9
+man/qmail-local.8
+man/qmail-log.5
+man/qmail-lspawn.8
+man/qmail-mfrules.9
+man/qmail-mrtg.8
+man/qmail-newmrh.9
+man/qmail-newu.9
+man/qmail-pop3d.8
+man/qmail-popup.8
+man/qmail-postgrey.8
+man/qmail-pw2u.9
+man/qmail-qmaint.8
+man/qmail-qmqpc.8
+man/qmail-qmqpd.8
+man/qmail-qmtpd.8
+man/qmail-qread.8
+man/qmail-qstat.8
+man/qmail-queue.8
+man/qmail-recipients.9
+man/qmail-remote.8
+man/qmail-rspawn.8
+man/qmail-send.9
+man/qmail-showctl.8
+man/qmail-smtpam.8
+man/qmail-smtpd.8
+man/qmail-start.9
+man/qmail-tcpok.8
+man/qmail-tcpto.8
+man/qmail-todo.8
+man/qmail-users.9
+man/qmail-vmailuser.9
+man/qreceipt.1
+man/setforward.1
+man/setmaillist.1
+man/spfquery.8
+man/splogger.8
+man/sqmail.9
+man/srsforward.1
+man/srsreverse.9
+man/tai64nfrac.5
+man/tcp-environ.5
+man/xqp.1
+man/xrecipient.1
+man/xsender.1
+package
+package/build
+package/command-cp
+package/command-ln
+package/commands-analog
+package/commands-base
+package/commands-clients
+package/commands-control
+package/commands-dkim
+package/commands-dns
+package/commands-forward
+package/commands-log
+package/commands-mbox
+package/commands-pam
+package/commands-pop
+package/commands-queue
+package/commands-recipients
+package/commands-scan
+package/commands-server
+package/commands-setup
+package/commands-srs
+package/commands-user
+package/commands-x509
+package/compile
+package/control
+package/dir
+package/files
+package/ids
+package/install
+package/legacy
+package/man
+package/path
+package/qmq
+package/report
+package/rts
+package/run
+package/scripts
+package/service
+package/services=d
+package/sslenv
+package/ucspissl
+package/upgrade
+package/version
+scripts
+scripts/Makefile
+scripts/TARGETS
+scripts/it-pam=d
+scripts/it-recipients=d
+scripts/it-scan=d
+scripts/it-x509=d
+scripts/it=d
+scripts/ksh-auto.sh
+scripts/ldap-pam.pl
+scripts/mkdkimkey.sh
+scripts/multiple-queues.sh
+scripts/perl-auto.sh
+scripts/qmail-alias2recipients.sh
+scripts/qmail-queue-scan.sh
+scripts/warn-auto.sh
+scripts/x509fingerprint.sh
+service/run_log
+service/run_pop3d
+service/run_pop3sd
+service/run_postgrey
+service/run_qmqpd
+service/run_qmtpd
+service/run_qmtpsd
+service/run_send
+service/run_smtpd
+service/run_smtpsd
+service/run_smtpsub
+service/ssl.env
+src
+src/Makefile
+src/TARGETS
+src/auto-gid.c
+src/auto-int.c
+src/auto-int8.c
+src/auto-str.c
+src/auto-uid.c
+src/base64.c
+src/bouncesaying.c
+src/chkshsgr.c
+src/chkspawn.c
+src/columnt.c
+src/commands.c
+src/condredirect.c
+src/config-fast.sh
+src/config.sh
+src/constmap.c
+src/control.c
+src/crypt.lib
+src/date822fmt.c
+src/datemail.sh
+src/datetime.c
+src/datetime_un.c
+src/ddist.sh
+src/deferrals.sh
+src/direntry.h1
+src/direntry.h2
+src/dkim.cpp
+src/dkimbase.cpp
+src/dkimsign.cpp
+src/dkimverify.cpp
+src/dns.c
+src/dns_tlsa.c
+src/dnscname.c
+src/dnsdoe.c
+src/dnsfq.c
+src/dnsip.c
+src/dnsmxip.c
+src/dnsptr.c
+src/dnstlsa.c
+src/dnstxt.c
+src/except.c
+src/failures.sh
+src/fastforward.c
+src/fifo.c
+src/find-systype.sh
+src/fmtqfn.c
+src/fork.h1
+src/fork.h2
+src/forward.c
+src/gfrom.c
+src/headerbody.c
+src/hfield.c
+src/hier.c
+src/hmac_md5.c
+src/hostname.c
+src/include
+src/include/auto_break.h
+src/include/auto_patrn.h
+src/include/auto_qmail.h
+src/include/auto_spawn.h
+src/include/auto_split.h
+src/include/auto_uids.h
+src/include/auto_usera.h
+src/include/base64.h
+src/include/commands.h
+src/include/constmap.h
+src/include/control.h
+src/include/date822fmt.h
+src/include/datetime.h
+src/include/dkim.h
+src/include/dkimbase.h
+src/include/dkimsign.h
+src/include/dkimverify.h
+src/include/dns.h
+src/include/dnsgettxt.h
+src/include/dnsdoe.h
+src/include/exit.h
+src/include/extra.h
+src/include/fifo.h
+src/include/fmtqfn.h
+src/include/gfrom.h
+src/include/global.h
+src/include/headerbody.h
+src/include/hfield.h
+src/include/hier.h
+src/include/hmac_md5.h
+src/include/ipalloc.h
+src/include/ipme.h
+src/include/maildir.h
+src/include/md5.h
+src/include/mfrules.h
+src/include/myctime.h
+src/include/newfield.h
+src/include/now.h
+src/include/prioq.h
+src/include/prot.h
+src/include/qlx.h
+src/include/qmail.h
+src/include/qsutil.h
+src/include/quote.h
+src/include/rcpthosts.h
+src/include/readsubdir.h
+src/include/readwrite.h
+src/include/received.h
+src/include/recipients.h
+src/include/sendtodo.h
+src/include/sha1.h
+src/include/sha256.h
+src/include/smtpdlog.h
+src/include/spf.h
+src/include/srs2.h
+src/include/strset.h
+src/include/tcpto.h
+src/include/tls_errors.h
+src/include/tls_remote.h
+src/include/tls_start.h
+src/include/tls_timeoutio.h
+src/include/token822.h
+src/include/trigger.h
+src/include/triggerpull.h
+src/include/ucspitls.h
+src/include/wildmat.h
+src/install.c
+src/instcheck.c
+src/ipalloc.c
+src/ipme.c
+src/ipmeprint.c
+src/it-analog=d
+src/it-base=d
+src/it-clients=d
+src/it-control=d
+src/it-dkim=d
+src/it-dns=d
+src/it-forward=d
+src/it-log=d
+src/it-mbox=d
+src/it-pam=d
+src/it-pop=d
+src/it-queue=d
+src/it-server=d
+src/it-setup=d
+src/it-srs=d
+src/it-user=d
+src/it=d
+src/maildir.c
+src/maildir2mbox.c
+src/maildirmake.c
+src/maildirwatch.c
+src/mailsubj.sh
+src/make-compile.sh
+src/make-load.sh
+src/make-makelib.sh
+src/matchup.c
+src/md5c.c
+src/mfrules.c
+src/migrate.sh
+src/myctime.c
+src/newaliases.c
+src/newfield.c
+src/newinclude.c
+src/now.c
+src/predate.c
+src/preline.c
+src/printforward.c
+src/printmaillist.c
+src/prioq.c
+src/prot.c
+src/qbiff.c
+src/qmail-authuser.c
+src/qmail-badloadertypes.c
+src/qmail-badmimetypes.c
+src/qmail-clean.c
+src/qmail-dkim.cpp
+src/qmail-dksign.c
+src/qmail-dkverify.c
+src/qmail-getpw.c
+src/qmail-inject.c
+src/qmail-local.c
+src/qmail-lspawn.c
+src/qmail-mfrules.c
+src/qmail-mrtg-queue.sh
+src/qmail-mrtg.c
+src/qmail-newmrh.c
+src/qmail-newu.c
+src/qmail-pop3d.c
+src/qmail-popup.c
+src/qmail-postgrey.c
+src/qmail-pw2u.c
+src/qmail-qmaint.c
+src/qmail-qmqpc.c
+src/qmail-qmqpd.c
+src/qmail-qmtpd.c
+src/qmail-qread.c
+src/qmail-qstat.sh
+src/qmail-queue.c
+src/qmail-recipients.c
+src/qmail-remote.c
+src/qmail-rspawn.c
+src/qmail-send.c
+src/qmail-showctl.c
+src/qmail-smtpam.c
+src/qmail-smtpd.c
+src/qmail-start.c
+src/qmail-tcpok.c
+src/qmail-tcpto.c
+src/qmail-todo.c
+src/qmail-upq.sh
+src/qmail-vmailuser.c
+src/qmail.c
+src/qreceipt.c
+src/qsutil.c
+src/quote.c
+src/rcpthosts.c
+src/readsubdir.c
+src/received.c
+src/recipients.c
+src/recipients.sh
+src/rhosts.sh
+src/rxdelay.sh
+src/select.h1
+src/select.h2
+src/senders.sh
+src/sendmail.c
+src/setforward.c
+src/setmaillist.c
+src/sha1.c
+src/sha256.c
+src/smtpdlog.c
+src/spawn.c
+src/spf.c
+src/spfdnsip.c
+src/spfquery.c
+src/splogger.c
+src/srs2.c
+src/srsforward.c
+src/srsreverse.c
+src/strset.c
+src/successes.sh
+src/suids.sh
+src/tai64nfrac.c
+src/tcpto.c
+src/tcpto_clean.c
+src/tls_errors.c
+src/tls_remote.c
+src/tls_start.c
+src/tls_timeoutio.c
+src/token822.c
+src/trigger.c
+src/triggerpull.c
+src/trycpp.c
+src/trycrypt.c
+src/trydnsresolv.c
+src/trydrent.c
+src/tryflock.c
+src/tryidn2.c
+src/trylsock.c
+src/trymkffo.c
+src/trynpbg1.c
+src/tryqlibs.c
+src/tryrsolv.c
+src/trysalen.c
+src/trysgact.c
+src/trysgprm.c
+src/tryshadow.c
+src/tryshsgr.c
+src/tryslib.c
+src/tryspnam.c
+src/trysysel.c
+src/trysyslog.c
+src/tryulong32.c
+src/tryuserpw.c
+src/tryutmp.c
+src/tryvfork.c
+src/trywaitp.c
+src/warn-auto.sh
+src/warn-shsgr
+src/wildmat.c
+src/xqp.sh
+src/xrecipient.sh
+src/xsender.sh
+src/zddist.sh
+src/zdeferrals.sh
+src/zfailures.sh
+src/zoverall.sh
+src/zrecipients.sh
+src/zrhosts.sh
+src/zrxdelay.sh
+src/zsenders.sh
+src/zsendmail.sh
+src/zsuccesses.sh
+src/zsuids.sh
diff --git a/package/ids b/package/ids
new file mode 100755
index 0000000..7b18788
--- /dev/null
+++ b/package/ids
@@ -0,0 +1,106 @@
+#!/bin/sh
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 $mypwd/conf-home`
+
+if [ -f $mypwd/conf-ids ]
+then
+ sqmailids=`grep -v "^#" $mypwd/conf-ids | grep ":" | tr ' ' '_'`
+else
+ barf "Can't open file '$mypwd/conf-ids' with sqmail ids!"
+fi
+
+unix=`uname -a | cut -d' ' -f1 | tr [a-z] [A-Z]`
+
+if [ "$unix" = "LINUX" ]
+then
+ qshl="/bin/false"
+ shout "Linux (Shell: $qshl):"
+
+ for line in $sqmailids
+ do
+ if [ `echo $line | grep "group"` ]
+ then
+ qgid=`echo "$line" | cut -d":" -f1`
+ qgrp=`echo "$line" | cut -d":" -f2`
+ if [ `grep -c ^$qgrp /etc/group` -eq 0 ]
+ then
+ safe groupadd -g $qgid $qgrp
+ else
+ shout "Group '$qgrp' already existing!"
+ fi
+ fi
+
+ if [ `echo $line | grep "user"` ]
+ then
+ quid=`echo "$line" | cut -d":" -f1`
+ qusr=`echo "$line" | cut -d":" -f2`
+ qcom=`echo "$line" | cut -d":" -f3 | tr "_" " "`
+ qgrp=`echo "$line" | cut -d":" -f4`
+ qdir="$home/`echo "$line" | cut -d":" -f5`"
+ if [ `grep -c $qusr /etc/passwd` -eq 0 ]
+ then
+ shout "Generating user: $qusr:$quid - $qgr - $qdir - $qshl - $qcom"
+ safe useradd -u $quid -g $qgrp -d $qdir $qusr -s $qshl -c "$qcom"
+ else
+ shout "User '$qusr' already existing!"
+ fi
+ fi
+ done
+
+elif [ `echo "$unix" | grep "BSD"` ]
+then
+ qshl="/sbin/nologin"
+ shout "BSD ($unix) (Shell: $qshl):"
+
+ for line in $sqmailids
+ do
+ if [ `echo $line | grep "group"` ]
+ then
+ qgid=`echo "$line" | cut -d":" -f1`
+ qgrp=`echo "$line" | cut -d":" -f2`
+ if [ `grep -c ^$qgrp /etc/group` -eq 0 ]
+ then
+ if [ `echo "$unix" | grep "OPENBSD"` ]
+ then
+ safe groupadd -g $qgid $qgrp
+ else
+ safe pw groupadd $qgrp -g $qgid $qgrp
+ fi
+ else
+ shout "Group '$qgrp' already existing!"
+ fi
+ fi
+ if [ `echo $line | grep "user"` ]
+ then
+ quid=`echo "$line" | cut -d":" -f1`
+ qusr=`echo "$line" | cut -d":" -f2`
+ qcom=`echo "$line" | cut -d":" -f3 | tr "_" " "`
+ qgrp=`echo "$line" | cut -d":" -f4`
+ qdir="$home/`echo "$line" | cut -d":" -f5`"
+ if [ `grep -c $qusr /etc/passwd` -eq 0 ]
+ then
+ shout "Generating user: $qusr:$quid - $qgrp - $qdir - $qshl - $qcom"
+ if [ `echo "$unix" | grep "OPENBSD"` ]
+ then
+ safe useradd -u $quid -g $qgrp -d $qdir -s $qshl -c "$qcom" $qusr
+ else
+ safe pw useradd $qusr -u $quid -g $qgrp -d $qdir -s $qshl -c "$qcom"
+ fi
+ else
+ shout "User '$qusr' already existing!"
+ fi
+ fi
+ done
+
+else
+ shout "Unix OS not recognized; please install s/qmail user/groups manually \\
+ and continue with installation step-by-step."
+fi
+
+exit 0
diff --git a/package/install b/package/install
new file mode 100755
index 0000000..a3e121c
--- /dev/null
+++ b/package/install
@@ -0,0 +1,12 @@
+#!/bin/sh -e
+package/dir ${1+"$@"}
+package/ids ${1+"$@"}
+package/ucspissl ${1+"$@"}
+package/compile ${1+"$@"}
+package/upgrade ${1+"$@"}
+package/legacy ${1+"$@"}
+package/man ${1+"$@"}
+package/control ${1+"$@"}
+package/sslenv ${1+"$@"}
+package/service ${1+"$@"}
+package/run ${1+"$@"}
diff --git a/package/legacy b/package/legacy
new file mode 100755
index 0000000..3acee1d
--- /dev/null
+++ b/package/legacy
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+
+safe umask 022
+[ -d package ] || barf "no package directory"
+[ -d src ] || barf "no src directory"
+[ -d compile ] || barf "no compile directory"
+
+safe cd compile
+
+[ -f install ] && safe ./install
+[ -f instcheck ] && safe ./instcheck
+
+safe cd $here
+
+shout "s/qmail binary files installed in `head -n 1 conf-home`/bin."
+
+exit 0
diff --git a/package/man b/package/man
new file mode 100755
index 0000000..6b79049
--- /dev/null
+++ b/package/man
@@ -0,0 +1,124 @@
+#!/bin/sh
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+me=`cat package/path | cut -d'/' -f2`
+unix=`uname -a | cut -d' ' -f1 | tr [a-z] [A-Z]`
+mandir=""
+usemanpath=0
+usemandoc=0
+
+if [ `which manpath` 2>/dev/null ]
+then
+ usemanpath=1
+fi
+
+if [ `which mandoc` 2>/dev/null ]
+then
+ usemandoc=1
+ shout "Using mandoc facility for man files."
+fi
+
+safe umask 022
+[ -d man ] || barf "no man directory"
+
+if [ -f conf-man ]
+then
+ mandir=`head -1 conf-man`
+ if [ -d "$mandir" ]
+ then
+ shout "Setting manual man-dir: $mandir."
+ else
+ if [ $usemanpath -eq 0 ]
+ then
+ barf "`manpath` not available; use conf-man instead."
+ fi
+ mandir=`manpath | awk -F: '{print $1}'`
+ if [ -d "$mandir" ]
+ then
+ shout "Setting manpath man-dir: $mandir."
+ else
+ barf "can't determine man-path directory."
+ fi
+ fi
+else
+ barf "can't determine man-path directory."
+ exit 1
+fi
+
+cd man
+if [ $usemandoc -eq 1 ]
+then
+ safe make -f Makefile.mandoc
+else
+ safe make
+fi
+
+if [ $usemandoc -eq 0 ]
+then
+ shout "Installing ${me} compressed man-files in ${mandir}."
+else
+ shout "Installing ${me} un-compressed man-files in ${mandir}."
+fi
+
+for i in `find . -name "*[1-8]"`
+do
+ all="$all $i"
+done
+
+for manfile in $all
+do
+ dir="man`echo $manfile | awk -F. '{print $NF}'`"
+ [ -d $mandir/$dir ] || safe mkdir $mandir/$dir
+ if [ $usemandoc -eq 0 ]
+ then
+ safe gzip $manfile && \
+ install -m 644 "$manfile.gz" $mandir/$dir/"${manfile#*/}.gz"
+ else
+ safe install -m 644 $manfile $mandir/$dir/${manfile#*./}
+ fi
+done
+
+## nroff: Required for old catman systems only
+
+if [ $usemandoc -eq 0 ]
+then
+ shout "Installing ${me} nroff'ed man-files in ${mandir}/catX."
+
+ all=""
+ for i in `find . -name "*0"`
+ do
+ all="$all $i"
+ done
+
+ for manfile in $all
+ do
+ catname=${manfile%.0}
+ catfiles=`ls -1 ${catname}* | grep -v '.0' | grep -v '.9'`
+
+ for catfile in $catfiles
+ do
+ dir="$mandir/cat`echo $catfile | awk -F. '{print $(NF-1)}'`"
+ safe mkdir -p $dir
+ safe install -m 644 $manfile $dir/${manfile#*/}
+ done
+ done
+else
+ if [ `which makewhatis` 2>/dev/null ]
+ then
+ makewhatis $mandir
+ shout "Installing ${me} mandoc files in db (makewhatis)."
+ elif [ `which catman` 2>/dev/null ]
+ then
+ catman $mandir
+ shout "Installing ${me} mandoc files in db (catman)."
+ else
+ man -w $mandir
+ shout "Installing ${me} mandoc files in db (man -w)."
+ fi
+fi
+
+cd ..
+
+exit 0
diff --git a/package/path b/package/path
new file mode 100644
index 0000000..c4103a3
--- /dev/null
+++ b/package/path
@@ -0,0 +1 @@
+mail/sqmail
diff --git a/package/qmq b/package/qmq
new file mode 100755
index 0000000..2667280
--- /dev/null
+++ b/package/qmq
@@ -0,0 +1,162 @@
+#!/bin/sh -e
+
+action=$1
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 ${mypwd}/conf-home`
+qmaill=`grep Log ${mypwd}/conf-ids | awk -F: '{print $2}'`
+
+QMQ_HOME=""
+if [ -f ${mypwd}/conf-home ]
+then
+ export QMQ_HOME=`head -1 ${mypwd}/conf-home`
+fi
+[ -d "${QMQ_HOME}" ] || barf "s/qmail home dir '${QMQ_HOME}' not available."
+
+QMQ_LOGS=""
+if [ -f ${mypwd}/conf-log ]
+then
+ export QMQ_LOGS=`head -1 ${mypwd}/conf-log`
+fi
+[ -d "${QMQ_LOGS}" ] || barf "s/qmail log dir '${QMQ_LOGS}' not available."
+
+SVC_DIR=""
+if [ -f ${mypwd}/conf-svcdir ]
+then
+ export SVC_DIR=`head -1 ${mypwd}/conf-svcdir`
+fi
+[ -d "${SVC_DIR}" ] || barf "supervise dir '${SVC_DIR}' not available."
+
+export QMQ_ME=`hostname`
+
+instances=""
+if [ -f ${mypwd}/conf-instances ]
+then
+ myinstances=`grep -v "^#" ${mypwd}/conf-instances | cut -d'#' -f1`
+ shout "Setting s/qmail QMQ instances:"
+ shout "-------------------------------------------------------------------"
+ echo "${myinstances}"
+ shout "-------------------------------------------------------------------"
+else
+ barf "No QMQ instances defined."
+fi
+
+if [ -f ${mypwd}/conf-qmq ]
+then
+ . ${mypwd}/conf-qmq
+ shout "Setting s/qmail QMQ skeleton environment:"
+ safe env | grep SKELETON
+else
+ barf "No QMQ environment available."
+fi
+
+
+if [ "X${myinstances}" != "X" ]
+then
+ shout "--> Use '$0 build' to setup the instances."
+ shout "--> Use '$0 conf' to deploy the instances."
+ shout "--> Use '$0 all' to setup and deploy the instances."
+ shout "-------------------------------------------------------------------"
+ shout "Note (1): s/qmail will be installed at '${QMQ_HOME}'."
+ shout "Note (2): s/qmail-logs will be installed at '${QMQ_LOGS}/[qmail|qmtp]-INSTANCEID' ...."
+ shout "Note (3): 'service' base directory is '${SVC_DIR}'."
+ shout "Note (4): 'qmail-send' will be initially touched 'down' at every instance."
+ shout "Note (5): Initial configuration is: 'queuelifetime=${SKELETON_QUEUELIFETIME}', concurrencyremote=${SKELETON_CONCURRENCYREMOTE}'."
+ shout "Note (6): Communication from the primary qmail instance to the secondaries is based on 'QMTP'."
+ shout "-------------------------------------------------------------------"
+ shout "Enter 'ctl-c' to abort; otherwise '$0 $action' will continue in 10 secs."
+ sleep 10
+fi
+
+if [ "X$action" = "Xbuild" -o "X$action" = "Xall" ]
+then
+ safe cp ${mypwd}/conf-home ${mypwd}/conf-home.org
+
+ for mapping in ${myinstances}
+ do
+ instance=${mapping%%:*}
+ ipaddress=$(echo ${mapping} | cut -d':' -f3)
+ [ "X${ipaddress}" = "X" ] && ipaddress=0
+
+ QMQ_INST="${QMQ_HOME%/*}/qmail-${instance}"
+ shout "Setting up QMQ instance: ${QMQ_INST} (IP: $ipaddress)"
+
+ safe mkdir -p ${QMQ_INST}
+ safe mkdir -p ${QMQ_LOGS}/qmail-${instance}
+ safe chown $qmaill ${QMQ_LOGS}/qmail-${instance}
+ safe mkdir -p ${QMQ_LOGS}/qmtp-${instance}
+ safe chown $qmaill ${QMQ_LOGS}/qmtp-${instance}
+
+ safe cd ${mypwd}/compile
+ safe echo "${QMQ_INST}" > ${mypwd}/conf-home
+ safe make
+ safe ./install
+ safe echo "${SKELETON_ME}" > ${QMQ_INST}/control/me
+ safe echo "${SKELETON_CONCURRENCYREMOTE}" > ${QMQ_INST}/control/concurrencyremote
+ safe echo "#!@:localhost;bounceport -- typically 1090" > ${QMQ_INST}/control/qmtproutes
+ [ "${ipaddress}" != "0" ] && safe echo "*:${ipaddress}" > ${QMQ_INST}/control/domainips
+ done
+
+ safe mv ${mypwd}/conf-home.org ${mypwd}/conf-home
+
+ shout "All s/qmail QMQ instances build."
+fi
+
+if [ "X$action" = "Xconf" -o "X$action" = "Xall" ]
+then
+ for mapping in ${myinstances}
+ do
+ instance=${mapping%%:*}
+ ipaddress=$(echo ${mapping} | cut -d':' -f3)
+ [ "X${ipaddress}" = "X" ] && ipaddress=0
+
+ QMQ_INST="${QMQ_HOME%/*}/qmail-${instance}"
+ shout "Configurating initially QMQ instance: ${QMQ_INST} (IP address: $ipaddress)"
+
+ port=$((${SKELETON_PORT}+${instance}))
+ shout "Selecting port ${port} for instance ${instance} ..."
+#
+## qmail-send/qmail-start
+#
+ safe mkdir -p ${QMQ_INST}/svc/qmail-${instance}/log
+ safe cp ${mypwd}/service/run_log ${QMQ_INST}/svc/qmail-${instance}/log/run
+ safe chmod +x ${QMQ_INST}/svc/qmail-${instance}/log/run
+
+ safe eval sed -e 's!/var/qmail!${QMQ_INST}!g' \
+ ${mypwd}/service/run_send \
+ > ${QMQ_INST}/svc/qmail-${instance}/run
+ safe chmod +x ${QMQ_INST}/svc/qmail-${instance}/run
+ safe touch ${QMQ_INST}/svc/qmail-${instance}/down
+#
+## qmail-qmtpd
+#
+ safe mkdir -p ${QMQ_INST}/svc/qmtp-${instance}/log
+ safe cp ${mypwd}/service/run_log ${QMQ_INST}/svc/qmtp-${instance}/log/run
+ safe chmod +x ${QMQ_INST}/svc/qmtp-${instance}/log/run
+
+ safe eval sed -e 's!/var/qmail!${QMQ_INST}!g' \
+ -e 's!0\ qmtp!$ipaddress\ $port!g' \
+ ${mypwd}/service/run_qmtpd \
+ > ${QMQ_INST}/svc/qmtp-${instance}/run
+ safe chmod +x ${QMQ_INST}/svc/qmtp-${instance}/run
+ safe touch ${QMQ_INST}/svc/qmtp-${instance}/down
+
+#
+## link to /svc
+#
+# safe ln -s ${QMQ_INST}/svc/qmtp-${instance} ${SVC_DIR}/qmail-${instance}-qmtpd
+# safe ln -s ${QMQ_INST}/svc/qmail-${instance} ${SVC_DIR}/qmail-${instance}-send
+ done
+
+ shout "All QMQ instances configured, available unter ${SVC_DIR} but toched 'down'."
+else
+ barf "Please provide either 'build' and 'conf' for individual actions; or 'all' for all-inclusive."
+fi
+
+exit 0
diff --git a/package/report b/package/report
new file mode 100755
index 0000000..ef2f2cb
--- /dev/null
+++ b/package/report
@@ -0,0 +1,10 @@
+#!/bin/sh -e
+test -d compile || ( echo 'Wrong working directory.'; exit 1 )
+here=`env - PATH=$PATH pwd`
+( echo sqmail-`head -1 package/version`
+ echo $here
+ if test -r compile/sysdeps
+ then
+ cat compile/sysdeps
+ fi
+) | mail sqmail@fehcom.de
diff --git a/package/rts b/package/rts
new file mode 100755
index 0000000..d213f0d
--- /dev/null
+++ b/package/rts
@@ -0,0 +1,76 @@
+#!/bin/sh
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+umask 022
+[ -d package ] || barf "no package directory"
+[ -d src ] || barf "no src directory"
+[ -d compile ] || barf "no compile directory"
+[ -d etc ] || barf "no etc directory"
+[ -f `head -1 src/conf-tcpbin`/tcpserver ] || barf "tcpserver not installed"
+
+for i in `sed -e '/^it-/!d' -e 's/^it-//' < compile/it=d`
+do
+ all="$all $i"
+done
+usage() { shout "usage: package/rts [ [-]$all ]"; exit 100; }
+
+targets=""
+if [ $# -eq 0 ]
+then
+ targets="$all"
+else
+ if [ "$1" = "-" ]
+ then
+ shift
+ suppress=":"
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ suppress="$suppress$i:"
+ done
+ for i in $all
+ do
+ case "$suppress" in
+ *:$i:*)
+ ;;
+ *)
+ targets="$targets $i"
+ ;;
+ esac
+ done
+ else
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ targets="$targets $i"
+ done
+ fi
+fi
+
+[ "X$all" != "X" ] && [ "X$targets" = "X" ] && usage
+
+here=`env - PATH=$PATH pwd`
+safe cd compile
+PATH="$here/compile:/command:$PATH"
+export PATH
+. $here/compile/rts.it > $here/compile/out.it 2>&1
+cat -v $here/compile/out.it | diff - $here/compile/exp.it
+for i in $targets
+do
+ . $here/compile/rts.$i 2>&1 | cat -v > $here/compile/out.$i
+ diff $here/compile/out.$i $here/compile/exp.$i
+done
diff --git a/package/run b/package/run
new file mode 100755
index 0000000..e5b2428
--- /dev/null
+++ b/package/run
@@ -0,0 +1,71 @@
+#!/bin/sh -e
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 ${mypwd}/conf-home`
+
+bindir=""
+if [ -f ${mypwd}/conf-home ]
+then
+ bindir="${home}/bin"
+fi
+
+sendmail=`which sendmail`
+dir=`dirname ${sendmail}`
+
+if [ ${dir} != ${bindir} ]
+then
+ if [ -L ${sendmail} ]
+ then
+ cd ${dir}
+ safe rm ${sendmail}
+ safe ln -s ${bindir}/sendmail sendmail
+ shout "Replaced system's sendmail with ${bindir}/sendmail"
+ cd ${mypwd}
+ else
+ cd ${dir}
+ safe mv sendmail sendmail_
+ safe chmod 000 sendmail_
+ safe ln -s ${bindir}/sendmail sendmail
+ shout "Replaced system's sendmail with ${bindir}/sendmail"
+ cd ${mypwd}
+ fi
+fi
+
+aliasdir=""
+if [ -f ${mypwd}/conf-home ]
+then
+ aliasdir="${home}/alias"
+fi
+shout "Setting s/qmail alias-dir: ${aliasdir}"
+
+[ -d "${aliasdir}" ] || safe mkdir -p ${aliasdir}
+
+[ -f ${aliasdir}/.qmail-root ] || safe touch ${aliasdir}/.qmail-root
+[ -f ${aliasdir}/.qmail-mailer-daemon ] || safe touch ${aliasdir}/.qmail-mailer-daemon
+[ -f ${aliasdir}/.qmail-postmaster ] || safe touch ${aliasdir}/.qmail-postmaster
+
+if [ -f ${mypwd}/conf-delivery ]
+then
+ defaultdelivery="`head -1 ${mypwd}/conf-delivery`"
+ if [ "x$defaultdelivery" = "x" ]
+ then
+ barf "No 'defaultdelivery' defined. Check conf-delivery."
+ fi
+
+ if [ -f "${home}/svc/qmail-send/run" ]
+ then
+ safe cat ${home}/svc/qmail-send/run | \
+ eval sed -e 's%./Maildir/%$defaultdelivery%' > run.delivery && \
+ mv run.delivery ${home}/svc/qmail-send/run && \
+ chmod +x ${home}/svc/qmail-send/run && \
+ shout "Setting qmail-start default-delivery: $defaultdelivery"
+ fi
+fi
+
+exit 0
diff --git a/package/scripts b/package/scripts
new file mode 100755
index 0000000..1e92cd7
--- /dev/null
+++ b/package/scripts
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+destination="`head -1 conf-home`/bin"
+group="`head -1 conf-groups`"
+
+safe umask 022
+[ -d package ] || barf "no package directory"
+[ -d scripts ] || barf "no src directory"
+
+for i in `sed -e '/^it-/!d' -e 's/^it-//' < scripts/it=d`
+do
+ all="$all $i"
+done
+
+other="`grep -v '^it-' scripts/it=d`"
+usage() { shout "usage: package/scripts [ [-]$all ]"; exit 100; }
+
+targets=""
+if [ $# -eq 0 ]
+then
+ targets="$all"
+else
+ if [ "$1" = "-" ]
+ then
+ shift
+ suppress=":"
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ suppress="$suppress$i:"
+ done
+ for i in $all
+ do
+ case "$suppress" in
+ *:$i:*)
+ ;;
+ *)
+ targets="$targets $i"
+ ;;
+ esac
+ done
+ else
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ targets="$targets $i"
+ done
+ fi
+fi
+
+[ "X$all" != "X" ] && [ "X$targets" = "X" ] && usage
+
+commands=""
+for i in $targets
+do
+ commands="$commands `cat package/commands-$i`"
+done
+
+safe cd scripts
+safe make $other `echo "$targets" | sed -e 's/ / it-/g'`
+
+for i in $commands
+do
+ i=${i%:}
+ safe rm -f command/$i'{new}'
+ safe cp -p scripts/$i command/$i'{new}'
+ safe mv -f command/$i'{new}' command/$i
+done
+
+safe cd $here
+
+shout "s/qmail additional script files installed in $destination with RC=$rc."
+shout "Note: Some of the scripts work only in a specific envrionment; ldap-pam requires PERL module 'Net::LDAP'."
+
+exit 0
diff --git a/package/service b/package/service
new file mode 100755
index 0000000..7233fda
--- /dev/null
+++ b/package/service
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+shout() { echo "${0}: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+safe umask 022
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 $mypwd/conf-home`
+logdir=`head -1 $mypwd/conf-log`
+svcdir=`head -1 $mypwd/conf-svcdir`
+[ -d $home/control ] || barf "no control directory"
+
+nofiles=`grep auxiliar $mypwd/conf-ids | awk -F: '{print $2}'`
+qmaill=`grep Log $mypwd/conf-ids | awk -F: '{print $2}'`
+
+[ -d ${svcdir} ] || barf "No /service directory available."
+[ `which supervise` ] || barf "No supervise available. Skipping"
+[ `which multilog` ] || log=0
+
+for i in `sed -e '/^svc:/!d' -e 's/^svc://' < package/services=d`
+do
+ all="$all $i"
+done
+
+# Generate log service dir
+
+[ "X$logdir" = "X" ] || safe mkdir -p $logdir
+[ -d $logdir ] && log=1
+
+# Walks thru all configured services
+
+for i in $all
+do
+ service=`echo $i | awk -F: '{print $1}'`
+ svcname=`echo $i | awk -F: '{print $2}'`
+ svc_run=`echo $i | awk -F: '{print $3}'`
+
+# Generate $home/svc dirs and populate
+
+ [ ! -d /$home/svc/$svcname ] && \
+ safe mkdir -p $home/svc/$svcname && \
+ safe cat $mypwd/service/$svc_run \
+ | eval sed -e 's}/var/qmail}$home}g' \
+ > $home/svc/$svcname/run && \
+ safe chmod +x $home/svc/$svcname/run && \
+ safe touch $home/svc/$svcname/down && \
+ shout "Created '$home/svc/$svcname' dir"
+
+# Generate logdir for services and set permissions
+
+ [ ! -d $logdir/$svcname -a $log -eq 1 ] && \
+ safe mkdir -p $logdir/$svcname && \
+ shout "Created '$logdir/$svcname' dir"
+
+ [ -d $logdir/$svcname -a $log -eq 1 ] && \
+ safe chown $qmaill:$nofiles $logdir/$svcname && \
+ shout "Set permissions for '$logdir/$svcname' dir"
+
+# Include log services
+
+ [ ! -d /$home/svc/$svcname/log -a -d $logdir/$svcname -a $log -eq 1 ] && \
+ safe mkdir -p $home/svc/$svcname/log && \
+ safe cat $mypwd/service/run_log \
+ | eval sed -e 's}qmaill}$qmaill}g' \
+ -e 's}nofiles}$nofiles}g' \
+ > $home/svc/$svcname/log/run && \
+ safe chmod +x $home/svc/$svcname/log/run && \
+ safe ln -s $logdir/$svcname $home/svc/$svcname/log/main && \
+ shout "Created '$home/svc/$svcname/log' dir"
+
+# Put the service under supervise (/$svcdir)
+
+ [ ! -d /$svcdir/$svcname ] && \
+ safe ln -s $home/svc/$svcname /$svcdir/$svcname && \
+ safe ln -s $home/control /$svcdir/$svcname/ctl && \
+ shout "Created '/$svcdir/$svcname' dir -- still 'down'"
+
+done
+
+exit 0
diff --git a/package/services=d b/package/services=d
new file mode 100644
index 0000000..6a7dac5
--- /dev/null
+++ b/package/services=d
@@ -0,0 +1,24 @@
+svc:send:qmail-send:run_send
+svc:smtp:qmail-smtpd:run_smtpd
+svc:smtps:qmail-smtpsd:run_smtpsd
+svc:submission:qmail-smtpsub:run_smtpsub
+svc:pop3:qmail-pop3d:run_pop3d
+svc:pop3s:qmail-pop3sd:run_pop3sd
+#svc:qmqp:qmail-qmqpd:run_qmqpd
+#svc:qmtp:qmail-qmtpd:run_qmtpd
+#svc:qmtps:qmail-qmtpsd:run_qmtpsd
+
+s/qmail supports those svc services listed after the fist colon.
+The part after the second colon referes to the service-name of the
+/service directory; eg. /service/qmail-send.
+
+After the the second colon follows the provided default
+'run' script for the service.
+
+Logging will be automatically actived with the
+service name under a common directory following the
+'log' service; e.g. /var/log/qmail-send.
+
+Services mentioned here will be auto-generated.
+
+Lines without the initial token 'svc' will be disregarded.
diff --git a/package/sslenv b/package/sslenv
new file mode 100755
index 0000000..67cd601
--- /dev/null
+++ b/package/sslenv
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+shout() { echo "${0}: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+safe umask 022
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+home=`head -1 $mypwd/conf-home`
+
+nofiles=`grep auxiliar $mypwd/conf-ids | awk -F: '{print $2}'`
+sqmtls=`grep TLS $mypwd/conf-ids | awk -F: '{print $2}'`
+
+safe mkdir -p $home/ssl
+safe chown $sqmtls:$nofiles $home/ssl
+
+ucspi=`head -1 $mypwd/conf-ucspissl`
+
+if [ -d $ucspi/etc ]
+then
+ for i in `ls $ucspi/etc`
+ do
+ shout "Copying ucspi-ssl file '$i' to $home/ssl"
+ safe cp "$ucspi/etc/$i" "$home/ssl/$i"
+ safe chown $sqmtls:$nofiles "$home/ssl/$i"
+ done
+else
+ barf "Can't find ucspi-ssl dir. Check 'conf-ucspissl'."
+fi
+
+[ -f $mypwd/service/ssl.env ] && \
+safe cat $mypwd/service/ssl.env \
+ | eval sed -e 's}QMAIL}$home}g' \
+ -e 's}SQMTLS}$sqmtls}g' \
+ -e 's}NOFILES}$nofiles}g' \
+ > $home/ssl/ssl.env
+
+shout "Environment for s/qmail TLS defined in $home/ssl/ssl.env"
+
+exit 0
diff --git a/package/ucspissl b/package/ucspissl
new file mode 100755
index 0000000..c59a07e
--- /dev/null
+++ b/package/ucspissl
@@ -0,0 +1,34 @@
+#!/bin/sh
+shout() { echo "${0}: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+here=`env - PATH=$PATH pwd`
+mypwd=${here%package}
+mypwd=${mypwd%/}
+
+sslfiles="conf-ssl compile/ssl.lib compile/ucspissl.a compile/ucspissl.h"
+
+home=`head -1 $mypwd/conf-ucspissl`
+[ -d $mypwd/compile ] || safe mkdir -p $mypwd/compile
+
+
+if [ -d $home ]
+then
+ shout "Linking ucspi-ssl sources."
+ for sfile in `echo $sslfiles`
+ do
+ if [ -f $home/$sfile ]
+ then
+ rm -f "$mypwd/compile/$sfile" 2>/dev/null
+ safe ln -fs "$home/$sfile" "$mypwd/$sfile"
+ safe ls -l "$mypwd/$sfile"
+ else
+ barf "ucspi-ssl file $sfile missing. s/qmail won't compile."
+ fi
+ done
+else
+ barf "Can't find ucspi-ssl dir. Check 'conf-ucspissl'."
+fi
+
+exit 0
diff --git a/package/upgrade b/package/upgrade
new file mode 100755
index 0000000..ff26087
--- /dev/null
+++ b/package/upgrade
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+shout() { echo "$0: $@" >&2; }
+barf() { shout "fatal: $@"; exit 111; }
+safe() { "$@" || barf "cannot $@"; }
+
+safe umask 022
+[ -d package ] || barf "no package directory"
+[ -d src ] || barf "no src directory"
+[ -d compile ] || barf "no compile directory"
+
+for i in `sed -e '/^it-/!d' -e 's/^it-//' < compile/it=d`
+do
+ all="$all $i"
+done
+
+usage() { shout "usage: package/upgrade [ [-]$all ]"; exit 100; }
+
+targets=""
+if [ $# -eq 0 ]
+then
+ for i in $all
+ do
+ targets="$all"
+ done
+else
+ if [ "$1" = "-" ]
+ then
+ shift
+ suppress=":"
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ suppress="$suppress$i:"
+ done
+ for i in $all
+ do
+ case "$suppress" in
+ *:$i:*)
+ ;;
+ *)
+ targets="$targets $i"
+ ;;
+ esac
+ done
+ else
+ for i in ${1+"$@"}
+ do
+ case "$all " in
+ *\ $i\ *)
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ targets="$targets $i"
+ done
+ fi
+fi
+
+[ "X$targets" = "X" ] && barf "no targets"
+
+version="`head -1 package/version`"
+here="`pwd`"
+command="`echo $here | sed -e 's/-'$version'$//'`/command"
+package="`basename $here | sed -e 's/-'$version'$//'`"
+shout "symlink $package -> $package-$version"
+safe rm -f $package
+safe ln -s $package-$version $package
+safe mv -f $package ..
+
+commands=""
+for i in $targets
+do
+ commands="$commands `cat package/commands-$i`"
+done
+
+if [ -r package/command-cp ]
+then
+ for i in `sed -e '/^$/q' < package/command-cp`
+ do
+ shout "copying commands into $i"
+ safe mkdir -p $i
+ for j in $commands
+ do
+ j=${j%:}
+ safe rm -f $i/$j'{new}'
+ safe cp -p command/$j $i/$j'{new}'
+ safe mv -f $i/$j'{new}' $i/$j
+ done
+ done
+fi
+
+if [ -r package/command-ln ]
+then
+ for i in `sed -e '/^$/q' < package/command-ln`
+ do
+ safe mkdir -p $i
+ for j in $commands
+ do
+ k=${j%:}
+ [ "$j" = "$k:" ] && \
+ shout "linking command $k into $i" && \
+ safe rm -f $i/$k'{new}' && \
+ safe ln -s $command/$k $i/$k'{new}' && \
+ safe mv -f $i/$k'{new}' $i/$k
+ done
+ done
+fi
+
+exit 0
diff --git a/package/version b/package/version
new file mode 100644
index 0000000..4be682d
--- /dev/null
+++ b/package/version
@@ -0,0 +1 @@
+4.2.29a
diff --git a/scripts/Makefile b/scripts/Makefile
new file mode 100644
index 0000000..26ed772
--- /dev/null
+++ b/scripts/Makefile
@@ -0,0 +1,64 @@
+# Don't edit Makefile! Use conf-* for configuration.
+
+SHELL=/bin/sh
+
+default: it-scan it-recipients \
+it-x509 it-pam
+
+clean: \
+TARGETS
+ rm -f `cat TARGETS`
+
+it-pam: \
+ldap-pam
+
+it-recipients: \
+qmail-alias2recipients
+
+it-scan: \
+qmail-queue-scan
+
+it-x509: \
+x509fingerprint mkdkimkey
+
+ldap-pam: \
+perl-auto.sh ldap-pam.pl ../conf-home
+ cat perl-auto.sh ldap-pam.pl \
+ | sed s}PERL}"`which perl`"}g \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > ldap-pam
+ chmod 755 ldap-pam
+
+mkdkimkey: \
+warn-auto.sh mkdkimkey.sh ../conf-home
+ cat warn-auto.sh mkdkimkey.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > mkdkimkey
+ chmod 755 mkdkimkey
+
+qmail-alias2recipients: \
+warn-auto.sh qmail-alias2recipients.sh ../conf-home
+ cat warn-auto.sh qmail-alias2recipients.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > qmail-alias2recipients
+ chmod 755 qmail-alias2recipients
+
+qmail-mrtg-queue: \
+warn-auto.sh qmail-mrtg-queue.sh ../conf-home
+ cat warn-auto.sh qmail-mrtg-queue.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > qmail-mrtg-queue
+ chmod 755 qmail-mrtg-queue
+
+qmail-queue-scan: \
+warn-auto.sh qmail-queue-scan.sh ../conf-home
+ cat warn-auto.sh qmail-queue-scan.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > qmail-queue-scan
+ chmod 755 qmail-queue-scan
+
+x509fingerprint: \
+warn-auto.sh x509fingerprint.sh
+ cat warn-auto.sh x509fingerprint.sh \
+ > x509fingerprint
+ chmod 755 x509fingerprint
diff --git a/scripts/TARGETS b/scripts/TARGETS
new file mode 100644
index 0000000..909572d
--- /dev/null
+++ b/scripts/TARGETS
@@ -0,0 +1,5 @@
+ldap-pam
+mkdkimkey
+qmail-alias2recipients
+qmail-queue-scan
+x509fingerprint
diff --git a/scripts/it-pam=d b/scripts/it-pam=d
new file mode 100644
index 0000000..a3b6f9e
--- /dev/null
+++ b/scripts/it-pam=d
@@ -0,0 +1 @@
+ldap-pam
diff --git a/scripts/it-recipients=d b/scripts/it-recipients=d
new file mode 100644
index 0000000..7de3576
--- /dev/null
+++ b/scripts/it-recipients=d
@@ -0,0 +1 @@
+qmail-alias2recipients
diff --git a/scripts/it-scan=d b/scripts/it-scan=d
new file mode 100644
index 0000000..456c4b3
--- /dev/null
+++ b/scripts/it-scan=d
@@ -0,0 +1 @@
+qmail-queue-scan
diff --git a/scripts/it-x509=d b/scripts/it-x509=d
new file mode 100644
index 0000000..a91ec38
--- /dev/null
+++ b/scripts/it-x509=d
@@ -0,0 +1,2 @@
+x509fingerprint
+mkdkimkey
diff --git a/scripts/it=d b/scripts/it=d
new file mode 100644
index 0000000..ba3e272
--- /dev/null
+++ b/scripts/it=d
@@ -0,0 +1,4 @@
+it-pam
+it-recipients
+it-x509
+it-scan
diff --git a/scripts/ksh-auto.sh b/scripts/ksh-auto.sh
new file mode 100644
index 0000000..f62c82a
--- /dev/null
+++ b/scripts/ksh-auto.sh
@@ -0,0 +1,2 @@
+#!KSH
+# WARNING: This file was auto-generated. Do not edit!
diff --git a/scripts/ldap-pam.pl b/scripts/ldap-pam.pl
new file mode 100644
index 0000000..675a2c9
--- /dev/null
+++ b/scripts/ldap-pam.pl
@@ -0,0 +1,149 @@
+#
+# checkpassword compatible LDAP pam for ADDRESSS (version 0.9.2)
+#
+# Usage: ldpap-pam -h:host:port -d:DN -w:password -b:base -s:scope -c:certificate -l(ogging) -ll
+#
+# Warning: This PERL code might not work on all versions of PERL !
+#
+# Some code taken from:
+# ldap_verify.pl by Ted Fines, Jan. 2005. version 0.1
+#
+# Customization:
+# Default search attribute is 'Mail'.
+#
+# Requires:
+# 'Net::LDAP' on http://www.cpan.org
+#
+# History:
+# 0.9.2 Fixed bug evalulation reference mailname (tx. Sven)
+#
+#---------------------------------------------------------------------
+use Net::LDAP;
+use IO::Handle;
+use warnings;
+use strict;
+#
+## Verbose output
+#
+my $ME='ldap-pam';
+my $LOGGING=0;
+#
+my @INPUT;
+my $LDAPHOST='localhost';
+my $LDAPPORT='default';
+my $LDAPUSR='';
+my $LDAPPWD='';
+my $LDAPBASE='';
+my $LDAPCERT='';
+my $TIMEOUT='30';
+#
+my $LDAPSCOPE='sub';
+my $ATTRIBUTE='mail';
+my $PROTOCOL_LEN=512;
+#
+## Check Arguments
+#
+if ( $#ARGV == -1) {
+ print STDERR "[Usage] ldpap-pam -h:host:port -t:timeout -d:DN -w:password -b:base -s:scope -c:certificate -l[l](ogging)\n";
+ print STDERR "[Defaults] Host:$LDAPHOST - Port:$LDAPPORT - DN:anonymous - Scope:$LDAPSCOPE - Attribute:$ATTRIBUTE - Timeout:$TIMEOUT \n";
+ exit 111;
+}
+
+while ( $#ARGV >= 0 ) {
+ $_=$ARGV[0];
+ s/^-h// && do { @INPUT = split(/:/,$_); $LDAPHOST=$INPUT[1];
+ if ( $INPUT[2] ) { $LDAPPORT=$INPUT[2]; } };
+ s/^-t// && do { @INPUT = split(/:/,$_); $TIMEOUT=$INPUT[1]; };
+ s/^-d// && do { @INPUT = split(/:/,$_); $LDAPUSR=$INPUT[1]; };
+ s/^-w// && do { @INPUT = split(/:/,$_); $LDAPPWD=$INPUT[1]; };
+ s/^-b// && do { @INPUT = split(/:/,$_); $LDAPBASE=$INPUT[1]; };
+ s/^-s// && do { @INPUT = split(/:/,$_); $LDAPSCOPE=$INPUT[1]; };
+ s/^-c// && do { @INPUT = split(/:/,$_); $LDAPCERT=$INPUT[1]; };
+ s/^-ll// && do { $LOGGING = 2; };
+ s/^-l// && do { $LOGGING = 1; };
+ shift;
+}
+#
+##
+my $ldap;
+my $mesg;
+my $rawinput;
+my $verifyaddr;
+my $verifyresult;
+#
+my $num_params = 1;
+my $input_descriptor = 3;
+#
+# These codes from DJB's checkpassword page.
+#
+my ($verify_ok,$verify_none,$resp_misused,$resp_tempfailure) = (0,1,2,111);
+#
+my $fhin = new IO::Handle;
+my $fherr = new IO::Handle;
+$fhin->fdopen($input_descriptor,"r");
+$fherr->fdopen(fileno(STDERR),"w");
+
+if (($fhin->opened) && ($fherr->opened)) {
+ $fhin->read($rawinput,$PROTOCOL_LEN);
+ my @checkfields = split(/\0/,$rawinput);
+ if (scalar(@checkfields) != $num_params) {
+ if ($LOGGING) { print STDERR "$ME [Error] Wrong format of input address specified.\n"; }
+ exit $resp_misused;
+ }
+ $verifyaddr = $checkfields[0];
+ #
+ # This section is the 'bottom line' so to speak, where the yea or nay is given.
+ #
+ $verifyresult = &ldap_mail($verifyaddr);
+ if ($verifyresult == $verify_ok) {
+ if ($LOGGING) { print STDERR "$ME [Info] Address '$verifyaddr' verified at LDAP Server '$LDAPHOST'.\n"; }
+ } elsif ($verifyresult == $verify_none) {
+ if ($LOGGING) { print STDERR "$ME [Info] Could not verify address '$verifyaddr' at LDAP Server '$LDAPHOST'.\n"; }
+ }
+ exit $verifyresult;
+}
+print STDERR "$ME [Error] Could not connect to LDAP Server '$LDAPHOST:$LDAPPORT'.\n";
+exit $resp_tempfailure;
+
+sub ldap_mail {
+ (my $mailaddr) = @_;
+
+ if ( $LDAPCERT ne "" && $LDAPUSR ne "" && $LDAPPWD ne "" ) {
+ if ( $LDAPPORT eq "default" ) { $LDAPPORT='636'; }
+ $ldap = Net::LDAPS->new($LDAPHOST, port => $LDAPPORT, timeout => $TIMEOUT) or &mydie();
+ $mesg = $ldap->bind($LDAPUSR, password => $LDAPPWD, version => 3, verify => require, cafile => $LDAPCERT);
+ } elsif ( $LDAPUSR ne "" && $LDAPPWD ne "" ) {
+ if ( $LDAPPORT eq "default" ) { $LDAPPORT='389'; }
+ $ldap = Net::LDAP->new($LDAPHOST, port => $LDAPPORT, timeout => $TIMEOUT) or &mydie();
+ $mesg = $ldap->bind($LDAPUSR, password => $LDAPPWD, version => 3);
+ } else {
+ if ( $LDAPPORT eq "default" ) { $LDAPPORT='389'; }
+ $ldap = Net::LDAP->new($LDAPHOST, port => $LDAPPORT, timeout => $TIMEOUT) or &mydie();
+ $mesg = $ldap->bind(version => 3);
+ }
+ if ( $mesg->code ) { &mydie($mesg->code) };
+
+ $mesg = $ldap->search (base => $LDAPBASE, scope => $LDAPSCOPE, filter => "$ATTRIBUTE=$mailaddr");
+ $mesg->code && &mydie($mesg);
+ $ldap->unbind;
+
+ my $href= $mesg->as_struct;
+ my @mailnames = keys %$href;
+ foreach (@mailnames) {
+ my $DN = $_;
+ my $valref = $$href{$_};
+ my $mailname = @$valref{$ATTRIBUTE};
+ if ( $LOGGING == 2 ) { print STDERR "$ME [Debug] Returned DN '$DN' with '@$mailname' for address '$mailaddr' at LDAP Server '$LDAPHOST'.\n"; }
+ if ( "lc($mailaddr)" eq "lc((@$mailname)[0])" ) { return $verify_ok; }
+ }
+ return $verify_none;
+}
+
+sub mydie {
+ if (scalar(@_) > 0) {
+ print STDERR "$ME [Error] Strange message received from LDAP Server '$LDAPHOST:$LDAPPORT': '$mesg->code', '$mesg->error_name', '$mesg->error->text'.\n";
+ } else {
+ print STDERR "$ME [Error] Could not connect to LDAP Server '$LDAPHOST:$LDAPPORT'.\n";
+ }
+ exit $resp_tempfailure;
+}
diff --git a/scripts/mkdkimkey.sh b/scripts/mkdkimkey.sh
new file mode 100644
index 0000000..5714173
--- /dev/null
+++ b/scripts/mkdkimkey.sh
@@ -0,0 +1,213 @@
+#********************************************************************************
+# Create/Handle domainkeys for openqmail/eQmail/(net)qmail and derivatives #
+# #
+# Author: Kai Peter (parts taken from Joerg Backschues), ©2014 #
+# Version: 0.32 -> 0.46 #
+# Licence: This program is Copyright(C) ©2015 Kai Peter. It can be copied and #
+# modified according to the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 #
+# or a later version. This software comes without any warranty. #
+# #
+# Description: Creation of domain keys and DNS TXT records for bind #
+# #
+# Addendum for s/qmail: #
+# #
+# a) This version is modified for s/qmail (etc/dkimkey -> ssl/domainkeys) #
+# b) RSA and Ed25519 private/public keys are considered (-> RFC 8463) #
+# c) tinydns supports DKIM records while just providing the public key; #
+# beware of the 'selector'; it is set to 'default' #
+# d) Unlike previous versions, the new private key is *not* automatically #
+# linked to a file named 'default'; but rather to #
+# rsa|ed25519.private_<selector> -> <selector> #
+# e) The ed25519 public key is stripped from the ASN1 header information #
+# This base64 encoded key is available as 'ed25519.basekey_<Selector> #
+# f) RSA and Ed25519 private keys may share the same <Selector> name. #
+# If identical, the Ed25519 private key is linked as '<Selector>_' #
+# to be picked up automatically by qmail-dksign for simultaneous signing. #
+# #
+# Changelog: #
+# #
+# 0.46 Fix for RSA DKIM TXT file generation, compactified TXT data. #
+#********************************************************************************
+DKDIR="HOME/ssl/domainkeys"
+USR="qmailq"
+GRP="sqmail"
+MODULUS=2048
+CURVE=0
+VERBOSE=0
+SELECTOR="default"
+DOMAIN=""
+ASN1="MCowBQYDK2VwAyEA"
+PRINT=0
+
+OPENSSL=$(which openssl 2>/dev/null)
+if [ $? -ne 0 ] ; then
+ echo "Couldn't find openssl! Aborting!" ; exit 0 ;
+fi
+
+showHelp() {
+echo "Usage: $(basename $0) [-hpc] [-s selector] [-m modulus] domain"
+echo
+echo "-h (show this help and exit)"
+echo "-v (verbose output)"
+echo "-p (print TXT record for domain)"
+echo "-s <selector> (set the selector)"
+echo "-m <N> (set RSA modulo size; default: 2048 bits)"
+echo "-c (generate Ed25519 keys)"
+echo ""
+echo "RSA key generation:"
+echo "=================="
+echo ""
+echo " mkdkimkey -s rsa-selector domain"
+echo ""
+echo "Ed25519 key generation:"
+echo "======================"
+echo ""
+echo " mkdkimkey -c -s ed-selector domain"
+echo ""
+echo "Key activation:"
+echo "=============="
+echo " The created private key 'rsa|ed25519.private_<selector>'"
+echo " is automatically symlinked to '<selector>'."
+echo " If included in 'control/dkimdomains', this file name"
+echo " is picked up by qmail-dksign for signing outgoing mails."
+echo " The default name for '<selector>' is 'default'."
+echo " This is implicitely assumed if no particular '<selector>' is given."
+echo ""
+exit 1 ;
+}
+
+readInit() {
+
+FLAG=""
+PARAMS="vhpcs:m:"
+
+if [ $# -gt 0 ]; then
+ while getopts ${PARAMS} FLAG
+ do
+ case ${FLAG} in
+ v) VERBOSE=1;;
+ p) PRINT=1;;
+ s) SELECTOR=${OPTARG};;
+ m) MODULUS=${OPTARG};;
+ c) CURVE=1;;
+ h) showHelp;;
+ *) showHelp;;
+ esac
+ done
+ shift $((OPTIND-1))
+fi
+
+# Validate the input a bit ...
+DOMAIN=$1 ; if [ "x${DOMAIN}" = "x" ] ; then showHelp ; fi
+# Only one argument is allowed ($1) and have to follow any other options
+if [ $2 ] ; then echo "Syntax ERROR!;" showHelp ; fi
+
+# Create main DKIM directory for keys if required
+
+if [ ! -d ${DKDIR} ] ; then mkdir -p ${DKDIR} ; fi
+}
+
+showTXT() {
+# backslashes MUST NOT be used in TXT records to quote semicolons !
+
+cd ${DKDIR}/${DOMAIN}
+
+echo "Domain's public key in '${DKDIR}/${DOMAIN}' used for TXT DNS record:"
+
+echo -n "${SELECTOR}._domainkey.${DOMAIN}. IN TXT "
+if [ -f rsa.public_${SELECTOR} ]; then
+ key=`grep -v -e '^-' rsa.public_${SELECTOR} | tr -d '\n'`
+ echo "\"v=DKIM1;k=rsa;t=y;p=${key}\""
+elif [ -f ed25519.public_${SELECTOR} ]; then
+ key=`grep -v -e '^-' ed25519.public_${SELECTOR} | tr -d '\n'`
+ basekey=${key#${ASN1}}
+ if [ `echo -n ${basekey} | wc -c | awk '{print $1}'` -eq 44 ]; then
+ echo "\"v=DKIM1;k=ed25519;t=y;p=${basekey}\""
+ echo ${basekey} > ed25519.basekey_${SELECTOR}
+ else
+ (errString="error generating Ed25519 public key" && showError; echo ${key}; echo ${basekey}; exit 1)
+ fi
+fi
+echo -n "You need to publish this TXT record in the DNS before activating [rsa|ed25519].private_${SELECTOR} -> ${SELECTOR} for signing."
+
+echo ; exit 0;
+}
+
+showError() {
+ echo "Domainkey for domain '${DOMAIN}' with selector '${SELECTOR}' [${errString}]!";
+}
+
+###############################################################################
+# Main
+###############################################################################
+
+# Read input args; create dirs, do some validation
+
+readInit ${@}
+
+if [ ${PRINT} -eq 1 ]; then showTXT; fi
+
+# Do some tests for existing keys
+
+if [ ${VERBOSE} -eq 1 ] ; then
+ if [ ${CURVE} -eq 0 ] ; then
+ test -f ${DKDIR}/${DOMAIN}/rsa.private_${SELECTOR} && \
+ (errString="already exists" && showError)
+ else
+ test -f ${DKDIR}/${DOMAIN}/ed25519.private_${SELECTOR} && \
+ (errString="already exists" && showError)
+ fi
+fi
+
+# Create a directory for domain and populate it with new keys
+# Existing old keys are safed as 'previous'
+
+mkdir -p ${DKDIR}/${DOMAIN}
+cd ${DKDIR}/${DOMAIN}
+
+if [ ${CURVE} -eq 0 ] ; then
+ if [ -f rsa.public_${SELECTOR} ]; then
+ cp rsa.public_${SELECTOR} rsa.public_${SELECTOR}.previous
+ cp rsa.private_${SELECTOR} rsa.private_${SELECTOR}.previous
+ fi
+ ${OPENSSL} genrsa -out rsa.private_${SELECTOR} ${MODULUS}
+ ${OPENSSL} rsa -in rsa.private_${SELECTOR} \
+ -out rsa.public_${SELECTOR} -pubout -outform PEM
+ ln -sf rsa.private_${SELECTOR} ${SELECTOR}
+else
+ if [ -f ed25519.public_${SELECTOR} ]; then
+ cp ed25519.public_${SELECTOR} ed25519.public_${SELECTOR}.previous
+ cp ed25519.private_${SELECTOR} ed25519.private_${SELECTOR}.previous
+ fi
+ ${OPENSSL} genpkey -algorithm Ed25519 \
+ -out ed25519.private_${SELECTOR}
+ ${OPENSSL} pkey -in ed25519.private_${SELECTOR} \
+ -out ed25519.public_${SELECTOR} -pubout
+ if [ -f ${SELECTOR} ]; then
+ ln -sf ed25519.private_${SELECTOR} "${SELECTOR}_"
+ else
+ ln -sf ed25519.private_${SELECTOR} ${SELECTOR}
+ fi
+fi
+
+# Set permissions
+
+chmod 0711 ${DKDIR}
+chmod 0700 ${DKDIR}/${DOMAIN}
+chmod 0600 ${DKDIR}/${DOMAIN}/*
+chown -R ${USR}:${GRP} ${DKDIR}
+
+# Do some tests
+
+if [ ${CURVE} -eq 0 ] ; then
+ test -f rsa.public_${SELECTOR} || \
+ (errString="does not exist" && showError)
+else
+ test -f ed25519.public_${SELECTOR} || \
+ (errString="does not exist" && showError)
+fi
+
+# Done
+
+[ "$?" = 0 ] && showTXT
+exit 0
diff --git a/scripts/multiple-queues.sh b/scripts/multiple-queues.sh
new file mode 100644
index 0000000..d6239d7
--- /dev/null
+++ b/scripts/multiple-queues.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+#set -o xtrace
+
+SQMAIL=HOME
+QMAIL_LOGS="/var/log"
+SVC_HOME="/service"
+
+SKELETON_SOURCE="${SQMAL}/source"
+SKELETON_ME="mail.example.com"
+SKELETON_CONCURRENCYREMOTE="120"
+SKELETON_QUEUELIFETIME="1440"
+SKELETON_PORT="1000"
+
+SKELETON_DIR="${SQMAL}/skeleton"
+SKELETON_QMAIL_SEND="start_run"
+SKELETON_QMAIL_LOGS="log_start_run"
+SKELETON_QMAIL_SMTP="smtpd_run"
+SKELETON_SMTP_LOGS="log_smtpd_run"
+SKELETON_QMAIL_QMTPD="qmtpd_run"
+SKELETON_QMAIL_LOGS="log_qmtpd_run"
+
+if [[ -f conf-qmq ]]; then
+ MYINSTANCES=$(grep -v "^#" conf-qmq | cut -d"#" -f1)
+ echo "The follwing qmail instances are defined:"
+ echo ""
+ grep -v "^#" conf-qmq
+ echo ""
+ echo "--> Use '$0 build' to setup the instances."
+ echo "--> Use '$0 conf' to deploy the instances."
+ echo "--> Use '$0 all' to setup and deploy the instances."
+ echo ""
+ echo "Note (1): qmail will be installed at '${SQMAL}'."
+ echo "Note (2): qmail-logs will be installed at '${QMAIL_LOGS}/qmail-send-INSTANCEID' ...."
+ echo "Note (3): 'service' base directory is '${SVC_HOME}'."
+ echo "Note (4): 'qmail-send' will be initially touched 'down' at every instance."
+ echo "Note (5): Initial configuration is: 'queuelifetime=${SKELETON_QUEUELIFETIME}', concurrencyremote=${SKELETON_CONCURRENCYREMOTE}'."
+ echo "Note (6): Communication from the primary qmail instance to the secondaries is bsed on 'QMTP'.
+ echo ""
+ echo "Enter 'ctl-c' to abort; or continue installation."
+else
+ echo "Configuration file 'conf-qmq' not available."
+ exit 1
+fi
+
+set -A INSTANCES ${MYINSTANCES}
+
+if [[ "$1" = "build" || "$1" = "all" ]]; then
+ for MAPPING in ${INSTANCES[@]}
+ do
+ INSTANCE=$(echo "${MAPPING}" | awk -F: '{print $1}')
+ NAME=$(echo "${MAPPING}" | awk -F: '{print $2}')
+ if [[ "x${NAME}" != "x" ]]; then
+ mkdir ${SQMAL}-${INSTANCE}
+ mkdir ${QMAL_LOGS}/qmail-${INSTANCE}
+ chown qmaill ${QMAL_LOGS}/qmail-${INSTANCE}
+ mkdir ${QMAL_LOGS}/qmtp-${INSTANCE}
+ chown qmaill ${QMAL_LOGS}/qmtp-${INSTANCE}
+ cd ${QMAIL_SOURCE}
+ echo "${SQMAL}-${INSTANCE}" > conf-qmail
+ make
+ make setup check
+ echo "${SKELETON_ME}" > ${SQMAL}-${INSTANCE}/control/me
+ echo "${SKELETON_CONCURRENCYREMOTE}" > ${SQMAL}-${INSTANCE}/control/concurrencyremote
+ echo "${SKELETON_QUEUELIFETIME}" > ${SQMAL}-${INSTANCE}/control/queuelifetime
+ fi
+ done
+elif [[ "$1" == "conf" || "$1" == "all" ]]; then
+ for MAPPING in ${INSTANCES[@]}
+ do
+ INSTANCE=$(echo "${MAPPING}" | awk -F: '{print $1}')
+ NAME=$(echo "${MAPPING}" | awk -F: '{print $2}')
+ if [[ "x${NAME}" != "x" ]]; then
+ integer PORT=$((${SKELETON_PORT}+${INSTANCE}))
+ echo "Selecting ${PORT} for instance ${INSTANCE} ..."
+#
+## qmail-send/qmail-start
+#
+ mkdir -p ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start/log
+ touch ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start/down
+
+ sed s/INSTANCE/${INSTANCE}/g ${SKELETON_DIR}/${SKELETON_QMAIL_LOGS} > \
+ ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start/log/run
+ chmod +x ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start/log/run
+
+ sed s/INSTANCE/${INSTANCE}/g ${SKELETON_DIR}/${SKELETON_QMAIL_SEND} > \
+ ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start/run
+ chmod +x ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start/run
+ ln -s ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start ${SVC_HOME}/qmail-${INSTANCE}-start
+#
+## qmail-qmtpd
+#
+ mkdir -p ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-qmtpd/log
+
+ sed s/INSTANCE/${INSTANCE}/g ${SKELETON_DIR}/ ${SKELETON_QMTPD_LOGS} > \
+ ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-qmtpd/log/run
+ chmod +x ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-qmtpd/log/run
+
+ sed s/INSTANCE/${INSTANCE}/g ${SKELETON_DIR}/ ${SKELETON_QMAIL_QMTPD} | \
+ sed s/PORT/${PORT}/g > ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-qmtpd/run
+ chmod +x ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-qmtpd/run
+#
+## link to /svc
+#
+ ln -s ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-qmtpd ${SVC_HOME}/qmail-${INSTANCE}-qmtpd
+ ln -s ${SQMAL}-${INSTANCE}/svc/qmail-${INSTANCE}-start ${SVC_HOME}/qmail-${INSTANCE}-start
+ fi
+ done
+else
+ echo "Please provide either 'build' and 'conf' for individual steps; or 'all' for all-inclusive."
+fi
+
+exit 0
diff --git a/scripts/perl-auto.sh b/scripts/perl-auto.sh
new file mode 100644
index 0000000..738ec79
--- /dev/null
+++ b/scripts/perl-auto.sh
@@ -0,0 +1,2 @@
+#!PERL
+# WARNING: This file was auto-generated. Do not edit!
diff --git a/scripts/qmail-alias2recipients.sh b/scripts/qmail-alias2recipients.sh
new file mode 100644
index 0000000..60398d3
--- /dev/null
+++ b/scripts/qmail-alias2recipients.sh
@@ -0,0 +1,4 @@
+# Tx. Warren Odom (28 Apr 2005)
+
+cd HOME/alias
+ls -l .qmail-* | grep -v .qmail-default | tr -s " " | awk '{print $9}' | sed s/^\.qmail-// | awk '{print $2"@localhost"}' | sort -u >> HOME/users/recipients
diff --git a/scripts/qmail-queue-scan.sh b/scripts/qmail-queue-scan.sh
new file mode 100644
index 0000000..3ab29fb
--- /dev/null
+++ b/scripts/qmail-queue-scan.sh
@@ -0,0 +1,144 @@
+#
+# Notes:
+# chmod 1755 qmail-queue.scan; chown qmailq:qmail qmail-queue.scan
+# SQMAIL/tmp has to exist prior with owner qmaill:qmail
+#
+# Usage:
+# Generate: control/spamdomains
+# Input: recipient-domain:spam-threshold
+# Sample: example.com:8
+# allspams:0
+# #testdomain:11 # entries with leading '#' are disregarded
+#
+# Caution:
+# This script is a sample. Depending on your anti-virus and/or anti-spam
+# software, some heavy tweaking is required.
+#
+#
+# Dependencies:
+# Korn shell
+# DJB's MESS822 package (or not)
+#
+# Environment variables used:
+#
+# $MAILFROM -- set by qmail-smtpd
+# $RCPTTO -- set by qmail-smtpd
+#
+# Virus scanner:
+# The AV scanner returns '0' if ok -- or '1'/'2' if virus -- anything else = error
+#
+# Spam scanner:
+# SpamAssassin > 3.0 (spamc/spamd)
+#
+# Output:
+# RC=0; if ok
+# RC=32; if virus
+# RC=33; if spam
+# RC=81; if error
+#
+# Performance:
+# Put SQMAIL/tmp on a RAMDISK
+#
+# License:
+# Public Domain
+#
+# Author:
+# Dr. Erwin Hoffmann - FEHCom
+#
+# Version:
+# 0.9.7
+#
+#------------------------------------------------------------------------------------------------
+SQMAIL=HOME
+#
+alias -x SCANNER='/usr/local/bin/clamdscan'
+#alias -x SCANNER='/etc/iscan/vscan'
+alias -x SPAMMER='/usr/bin/spamc'
+alias -x 822FIELD='/usr/local/bin/822field'
+# alias -x 822FIELD='/usr/bin/grep'
+#
+VERBOSE=0
+#
+SCANNERARGS="--no-summary"
+SPAMMERARGS=""
+#
+## No code change necessary from here
+#
+typeset SPAM
+integer SPAMC=0
+integer SPAMTHRESHOLD=-1
+integer SPAMTH
+typeset SPAMDOMAINS
+
+ID="${RANDOM}$$"
+MESSAGE="${SQMAIL}/tmp/msg.${ID}"
+export DTLINE="spam-queue"
+#
+[[ ! -d ${SQMAIL}/tmp ]] && exit 53
+cat > ${MESSAGE} || exit 53
+#
+## Virus scanning
+#
+if [[ "x${QHPSI}" = "x" ]]; then
+ VIRUS=$(SCANNER ${SCANNERARGS} ${MESSAGE})
+ VRC=$?
+ [[ ${VERBOSE} -gt 0 ]] && print -u2 "AV Scanner info [`SCANNER` -V]: ${VIRUS}"
+
+# VIRUS=$(echo "${VIRUS}" | grep -e "\*\*") ## for TrendMicro only
+ case ${VRC} in
+ (0) RC=0;;
+ (1|2) exec 1>&2; echo "Infected email not delivered (${VIRUS})"; RC=32;;
+ (*) exec 1>&2; echo "`SCANNER -V` internal error (${VRC})"; RC=81;;
+ esac
+fi
+#
+## Check Spamlevel for each domain
+#
+if [[ -f ${SQMAIL}/control/spamdomains && ${RC} -eq 0 ]]; then
+ SPAMDOMAINS=$(grep -v "^#" ${SQMAIL}/control/spamdomains)
+ for LINE in ${SPAMDOMAINS}
+ do
+ DOMAIN=${LINE%:*}
+ SPAMTH=${LINE#*:*}
+ [[ $(echo "${RCPTTO}" | grep -ci "${DOMAIN}") -gt 0 ]] && SPAMTHRESHOLD=${SPAMTH}
+ done
+
+ [[ ${VERBOSE} -gt 0 ]] && print -u2 "Rcptdomain: ${RCPTTO#*@} -- Threshold: ${SPAMTHRESHOLD}"
+
+ if [[ ${SPAMTHRESHOLD} -ge 0 ]]; then
+#
+## Spam recognition -- the following codes is only useful for SpamAssassins spamc version 3.x
+#
+ SPAM=$(SPAMMER ${SPAMMERARGS} < ${MESSAGE} > ${MESSAGE}_$$ && mv ${MESSAGE}_$$ ${MESSAGE} || exit 53)
+ SPAM=$(822FIELD "X-Spam-Level" < ${MESSAGE} | head -n 1)
+ SPAM=${SPAM# }
+
+ [[ ${VERBOSE} -gt 0 ]] && print -u2 echo "[$(echo `SPAMMER -V` | tr -d '\n')]: ${SPAMTHRESHOLD}"
+
+ if [[ "x${SPAM}" != "x" ]]; then
+ if [[ $(echo "${SPAM}" | grep -c "X-Spam-Level") -gt 0 ]]; then
+ SPAMC=$(echo "${SPAM##X-Spam-Level:}" tr -d ' ' | tr -d '\n' | wc -c)
+ fi
+ else
+ SPAMC=$(echo "${SPAM}" | awk -F"/" '{print $1}' | awk -F"." '{print $1}')
+ fi
+ [[ ${VERBOSE} -gt 0 ]] && print -u2 "Spam: ${SPAM} - Spamc: ${SPAMC}"
+#
+## Spam rejection
+#
+ if [[ ${SPAMC} -gt 0 && ${SPAMC} -gt ${SPAMTHRESHOLD} ]]; then
+#
+# If you enable one of the next lines, the sender receives a rejection, while the email is let thru
+#
+# ${SQMAIL}/bin/forward ${MAILFROM} "${RCPTTO}" < ${MESSAGE}
+# ${SQMAIL}/bin/forward ${MAILFROM} "${DELIVERTO}" < ${MESSAGE}
+ export SPAMSCORE="${SPAMC}"
+ RC=33
+ fi
+ fi
+fi
+
+[[ ${RC} -eq 0 ]] && ${SQMAIL}/bin/qmail-queue < ${MESSAGE}
+
+rm ${MESSAGE}
+exit ${RC}
diff --git a/scripts/warn-auto.sh b/scripts/warn-auto.sh
new file mode 100644
index 0000000..36d2313
--- /dev/null
+++ b/scripts/warn-auto.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+# WARNING: This file was auto-generated. Do not edit!
diff --git a/scripts/x509fingerprint.sh b/scripts/x509fingerprint.sh
new file mode 100644
index 0000000..5550150
--- /dev/null
+++ b/scripts/x509fingerprint.sh
@@ -0,0 +1,10 @@
+
+cert=$1
+[ -f "$cert" ] || (echo "$0: Provide path to certificate as argument."; exit 2;)
+
+openssl x509 -sha1 -in $cert -noout -fingerprint | tr -d ':'
+openssl x509 -sha224 -in $cert -noout -fingerprint | tr -d ':'
+openssl x509 -sha256 -in $cert -noout -fingerprint | tr -d ':'
+openssl x509 -sha512 -in $cert -noout -fingerprint | tr -d ':'
+
+exit $?
diff --git a/service/run_log b/service/run_log
new file mode 100755
index 0000000..25d0f62
--- /dev/null
+++ b/service/run_log
@@ -0,0 +1,13 @@
+#!/bin/sh
+LOG_USER="qmaill"
+LOG_GROUP=`id -g ${LOG_USER}`
+LOG_NAME=`basename ${PWD%/log}`
+LOG_DIR="/var/log/${LOG_NAME}"
+if [ ! -d "${LOG_DIR}" ]; then
+ mkdir -p "${LOG_DIR}"
+ chown ${LOG_USER}:${LOG_GROUP} "${LOG_DIR}"
+ echo "Creating log dir for ${LOG_NAME}:"
+ ls -ld "${LOG_DIR}"
+fi
+exec 2>&1
+exec setuidgid ${LOG_USER} multilog t s2000000 "${LOG_DIR}"
diff --git a/service/run_pop3d b/service/run_pop3d
new file mode 100755
index 0000000..952912c
--- /dev/null
+++ b/service/run_pop3d
@@ -0,0 +1,8 @@
+#!/bin/sh
+HOSTNAME=`hostname`
+export UCSPITLS=""
+. /var/qmail/ssl/ssl.env
+exec env PATH="/var/qmail/bin:$PATH" \
+ sslserver -seVn -Rp -l $HOSTNAME 0 pop3 \
+ qmail-popup $HOSTNAME qmail-authuser \
+ qmail-pop3d Maildir true 2>&1 5>&1
diff --git a/service/run_pop3sd b/service/run_pop3sd
new file mode 100755
index 0000000..409949b
--- /dev/null
+++ b/service/run_pop3sd
@@ -0,0 +1,7 @@
+#!/bin/sh
+HOSTNAME=`hostname`
+. /var/qmail/ssl/ssl.env
+exec env PATH="/var/qmail/bin:$PATH" \
+ sslserver -seV -Rp -l $HOSTNAME 0 pop3s \
+ qmail-popup $HOSTNAME qmail-authuser \
+ qmail-pop3d Maildir true 2>&1 5>&1
diff --git a/service/run_postgrey b/service/run_postgrey
new file mode 100755
index 0000000..29a2c39
--- /dev/null
+++ b/service/run_postgrey
@@ -0,0 +1,22 @@
+#!/bin/sh
+HOST_IP="127.0.0.1"
+HOST_PORT="60000"
+PIDFILE_DIR="/var/qmail/etc/"
+WHITELIST_CLIENTS="/var/qmail/etc/whitelist_clients"
+WHITELIST_RECIPIENTS="/var/qmail/etc/whitelist_recipients"
+touch $WHITELIST_CLIENTS
+touch $WHITELIST_RECIPIENTS
+mkdir -p /var/spool/postfix/postgrey
+chown postgrey /var/spool/postfix/postgrey
+chmod +s /var/spool/postfix/postgrey
+DBDIR_PATH="/var/qmail/etc/"
+POSTGREY_DIR=" ../../Postgrey/postgrey-1.36"
+# Assuming postgrey is not in the $PATH
+exec 2>&1 # Logging!
+exec ${POSTGREY_DIR}/postgrey -v --inet="$HOST_IP:$HOST_PORT" \\
+ --whitelist-clients="$WHITELIST_CLIENTS" \\
+ --whitelist-recipients="$WHITELIST_RECIPIENTS" \\
+ --dbdir="$DBDIR_PATH" \\
+ --pidfile="$PIDFILE_DIR"
+# --user=qmaild --group=nofiles
+
diff --git a/service/run_qmqpd b/service/run_qmqpd
new file mode 100755
index 0000000..857ecb4
--- /dev/null
+++ b/service/run_qmqpd
@@ -0,0 +1,10 @@
+#!/bin/sh
+QMAILU=`id -u qmaild`
+QMAILG=`id -g qmaild`
+HOSTNAME=`hostname`
+QMQP="628"
+exec env PATH="/var/qmail/bin:$PATH" \
+ tcpserver -v -Rp -l $HOSTNAME \
+ -Xx /var/qmail/control/rules.qmqpd.cdb \
+ -u $QMAILU -g $QMAILG 0 $QMQP \
+ qmail-qmqpd 2>&1
diff --git a/service/run_qmtpd b/service/run_qmtpd
new file mode 100755
index 0000000..695cc1f
--- /dev/null
+++ b/service/run_qmtpd
@@ -0,0 +1,9 @@
+#!/bin/sh
+QMAILU=`id -u qmaild`
+QMAILG=`id -g qmaild`
+HOSTNAME=`hostname`
+exec env PATH="/var/qmail/bin:$PATH" \
+ tcpserver -v -Rp -l $HOSTNAME \
+ -Xx /var/qmail/control/rules.smtpd.cdb \
+ -u $QMAILU -g $QMAILG 0 qmtp \
+ qmail-qmtpd 2>&1
diff --git a/service/run_qmtpsd b/service/run_qmtpsd
new file mode 100755
index 0000000..db66d11
--- /dev/null
+++ b/service/run_qmtpsd
@@ -0,0 +1,11 @@
+#!/bin/sh
+QMAILU=`id -u qmaild`
+QMAILG=`id -g qmaild`
+QMTPS="6209"
+HOSTNAME=`hostname`
+. /var/qmail/ssl/ssl.env
+exec env PATH="/var/qmail/bin:$PATH" \
+ sslserver -seV -Rp -l $HOSTNAME \
+ -Xx /var/qmail/control/rules.smtpd.cdb \
+ -u $QMAILU -g $QMAILG 0 $QMTPS \
+ qmail-qmtpd 2>&1
diff --git a/service/run_send b/service/run_send
new file mode 100755
index 0000000..86ac38c
--- /dev/null
+++ b/service/run_send
@@ -0,0 +1,3 @@
+#!/bin/sh
+exec env - PATH="/var/qmail/bin:$PATH" \
+ qmail-start ./Maildir/
diff --git a/service/run_smtpd b/service/run_smtpd
new file mode 100755
index 0000000..68edafa
--- /dev/null
+++ b/service/run_smtpd
@@ -0,0 +1,12 @@
+#!/bin/sh
+QMAILU=`id -u qmaild`
+QMAILG=`id -g qmaild`
+HOSTNAME=`hostname`
+export UCSPITLS=""
+export SPF="3"
+. /var/qmail/ssl/ssl.env
+exec env PATH="/var/qmail/bin:$PATH" \
+ sslserver -seVn -Rp -l $HOSTNAME \
+ -Xx /var/qmail/control/rules.smtpd.cdb \
+ -u $QMAILU -g $QMAILG 0 smtp \
+ qmail-smtpd 2>&1
diff --git a/service/run_smtpsd b/service/run_smtpsd
new file mode 100755
index 0000000..c373b83
--- /dev/null
+++ b/service/run_smtpsd
@@ -0,0 +1,11 @@
+#!/bin/sh
+QMAILU=`id -u qmaild`
+QMAILG=`id -g qmaild`
+HOSTNAME=`hostname`
+export SMTPAUTH=""
+. /var/qmail/ssl/ssl.env
+exec env PATH="/var/qmail/bin:/usr/local/bin:$PATH" \
+ sslserver -seV -Rp -l $HOSTNAME \
+ -Xx /var/qmail/control/rules.smtpd.cdb \
+ -u $QMAILU -g $QMAILG 0 smtps \
+ qmail-smtpd qmail-authuser true 2>&1
diff --git a/service/run_smtpsub b/service/run_smtpsub
new file mode 100755
index 0000000..6292bf5
--- /dev/null
+++ b/service/run_smtpsub
@@ -0,0 +1,11 @@
+#!/bin/sh
+QMAILU=`id -u qmaild`
+QMAILG=`id -g qmaild`
+HOSTNAME=`hostname`
+export SMTPAUTH="!"
+export UCSPITLS="!"
+. /var/qmail/ssl/ssl.env
+exec env PATH="/var/qmail/bin:/usr/local/bin:$PATH" \
+ sslserver -seVn -Rp -l $HOSTNAME \
+ -u $QMAILU -g $QMAILG 0 submission \
+ qmail-smtpd qmail-authuser true 2>&1
diff --git a/service/ssl.env b/service/ssl.env
new file mode 100644
index 0000000..bfa5875
--- /dev/null
+++ b/service/ssl.env
@@ -0,0 +1,19 @@
+# Environment
+SSL_USER=SQMTLS
+SSL_GROUP=NOFILES
+SSL_DIR="QMAIL/ssl"
+SSL_CHROOT="$SSL_DIR"
+# Checks
+SSL_UID=`id -u $SSL_USER`
+[ $? -ne 0 ] && ( echo "No such user '$SSL_USER'" ; exit )
+SSL_GID=`id -g $SSL_USER`
+[ $? -ne 0 ] && ( echo "No such group '$SSL_GROUP'" ; exit )
+# Files
+CERTCHAINFILE="$SSL_DIR/chain6.pem"
+CERTFILE="$SSL_DIR/localhost.pem"
+KEYFILE="$SSL_DIR/localhost.key"
+DHFILE="$SSL_DIR/dh1024.pem"
+# Exported
+export SSL_UID SSL_GID SSL_CHROOT
+export CERTFILE KEYFILE DHFILE
+#export CERTCHAINFILE
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..8509761
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,1521 @@
+# Don't edit Makefile! Use ../conf-* for configuration.
+
+SHELL=/bin/sh
+
+default: \
+it-analog it-base it-clients it-control it-dns \
+it-forward it-log it-mbox it-pam it-pop it-queue \
+it-user it-setup it-server it-srs it-dkim
+
+auto-ccld.sh: \
+../conf-cc ../conf-ld warn-auto.sh
+ ( cat warn-auto.sh; \
+ echo CC=\'`head -1 ../conf-cc`\'; \
+ echo LD=\'`head -1 ../conf-ld`\' \
+ ) > auto-ccld.sh
+
+auto-gid: \
+load auto-gid.o qlibs.lib
+ ./load auto-gid `cat qlibs.lib`
+
+auto-gid.o: \
+compile auto-gid.c
+ ./compile auto-gid.c
+
+auto-int: \
+load auto-int.o qlibs.lib
+ ./load auto-int `cat qlibs.lib`
+
+auto-int.o: \
+compile auto-int.c
+ ./compile auto-int.c
+
+auto-int8: \
+load auto-int8.o qlibs.lib
+ ./load auto-int8 `cat qlibs.lib`
+
+auto-int8.o: \
+compile auto-int8.c
+ ./compile auto-int8.c
+
+auto-str: \
+load auto-str.o qlibs.lib
+ ./load auto-str `cat qlibs.lib`
+
+auto-str.o: \
+compile auto-str.c
+ ./compile auto-str.c
+
+auto-uid: \
+load auto-uid.o qlibs.lib
+ ./load auto-uid `cat qlibs.lib`
+
+auto-uid.o: \
+compile auto-uid.c
+ ./compile auto-uid.c
+
+auto_break.c: \
+auto-str ../conf-break
+ ./auto-str auto_break \
+ "`head -1 ../conf-break`" > auto_break.c
+
+auto_break.o: \
+compile auto_break.c
+ ./compile auto_break.c
+
+auto_patrn.c: \
+auto-int8 ../conf-patrn
+ ./auto-int8 auto_patrn `head -1 ../conf-patrn` > auto_patrn.c
+
+auto_patrn.o: \
+compile auto_patrn.c
+ ./compile auto_patrn.c
+
+auto_qmail.c: \
+auto-str ../conf-home
+ ./auto-str auto_qmail `head -1 ../conf-home` > auto_qmail.c
+
+auto_qmail.o: \
+compile auto_qmail.c
+ ./compile auto_qmail.c
+
+auto_spawn.c: \
+auto-int ../conf-spawn
+ ./auto-int auto_spawn `head -1 ../conf-spawn` > auto_spawn.c
+
+auto_spawn.o: \
+compile auto_spawn.c
+ ./compile auto_spawn.c
+
+auto_split.c: \
+auto-int ../conf-split
+ ./auto-int auto_split `head -1 ../conf-split` > auto_split.c
+
+auto_split.o: \
+compile auto_split.c
+ ./compile auto_split.c
+
+auto_uids.c: \
+auto-uid auto-gid ../conf-users ../conf-groups
+ ( ./auto-uid auto_uida `head -1 ../conf-users` \
+ &&./auto-uid auto_uidd `head -2 ../conf-users | tail -1` \
+ &&./auto-uid auto_uidl `head -3 ../conf-users | tail -1` \
+ &&./auto-uid auto_uido `head -4 ../conf-users | tail -1` \
+ &&./auto-uid auto_uidp `head -5 ../conf-users | tail -1` \
+ &&./auto-uid auto_uidq `head -6 ../conf-users | tail -1` \
+ &&./auto-uid auto_uidr `head -7 ../conf-users | tail -1` \
+ &&./auto-uid auto_uids `head -8 ../conf-users | tail -1` \
+ &&./auto-gid auto_gidq `head -1 ../conf-groups` \
+ &&./auto-gid auto_gidn `head -2 ../conf-groups | tail -1` \
+ ) > auto_uids.c.tmp && mv auto_uids.c.tmp auto_uids.c
+
+auto_uids.o: \
+compile auto_uids.c
+ ./compile auto_uids.c
+
+auto_usera.c: \
+auto-str ../conf-users
+ ./auto-str auto_usera `head -1 ../conf-users` > auto_usera.c
+
+auto_usera.o: \
+compile auto_usera.c
+ ./compile auto_usera.c
+
+base64.o: \
+compile base64.c
+ ./compile base64.c
+
+md5c.o : \
+compile md5c.c
+ ./compile md5c.c
+
+hmac_md5.o : \
+compile hmac_md5.c
+ ./compile hmac_md5.c
+
+bouncesaying: \
+load bouncesaying.o qlibs.lib
+ ./load bouncesaying `cat qlibs.lib`
+
+bouncesaying.o: \
+compile bouncesaying.c
+ ./compile bouncesaying.c
+
+chkshsgr: \
+load chkshsgr.o
+ ./load chkshsgr
+
+chkshsgr.o: \
+compile chkshsgr.c
+ ./compile chkshsgr.c
+
+chkspawn: \
+load chkspawn.o auto_spawn.o qlibs.lib
+ ./load chkspawn auto_spawn.o `cat qlibs.lib`
+
+chkspawn.o: \
+compile chkspawn.c
+ ./compile chkspawn.c
+
+clean: \
+TARGETS
+ rm -f `cat TARGETS`
+
+columnt: \
+load columnt.o qlibs.lib
+ ./load columnt `cat qlibs.lib`
+
+columnt.o: \
+compile columnt.c
+ ./compile columnt.c
+
+commands.o: \
+compile commands.c
+ ./compile commands.c
+
+compile: \
+make-compile warn-auto.sh systype
+ ( cat warn-auto.sh; ./make-compile "`cat systype`" ) > \
+ compile
+ chmod 755 compile
+
+condredirect: \
+load condredirect.o qmail.o auto_qmail.o qlibs.lib
+ ./load condredirect qmail.o auto_qmail.o `cat qlibs.lib`
+
+condredirect.o: \
+compile condredirect.c
+ ./compile condredirect.c
+
+config: \
+warn-auto.sh config.sh ../conf-home ../conf-break ../conf-split
+ cat warn-auto.sh config.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPLIT}"`head -1 ../conf-split`"}g \
+ > config
+ chmod 755 config
+
+config-fast: \
+warn-auto.sh config-fast.sh ../conf-home ../conf-break ../conf-split
+ cat warn-auto.sh config-fast.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPLIT}"`head -1 ../conf-split`"}g \
+ > config-fast
+ chmod 755 config-fast
+
+constmap.o: \
+compile constmap.c
+ ./compile constmap.c
+
+control.o: \
+compile control.c
+ ./compile control.c
+
+date822fmt.o: \
+compile date822fmt.c
+ ./compile date822fmt.c
+
+datemail: \
+warn-auto.sh datemail.sh ../conf-home ../conf-break ../conf-split
+ cat warn-auto.sh datemail.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPLIT}"`head -1 ../conf-split`"}g \
+ > datemail
+ chmod 755 datemail
+
+datetime.a: \
+makelib datetime.o datetime_un.o
+ ./makelib datetime.a datetime.o datetime_un.o
+
+datetime.o: \
+compile datetime.c
+ ./compile datetime.c
+
+datetime_un.o: \
+compile datetime_un.c
+ ./compile datetime_un.c
+
+ddist: \
+warn-auto.sh ddist.sh ../conf-home
+ cat warn-auto.sh ddist.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > ddist
+ chmod 755 ddist
+
+deferrals: \
+warn-auto.sh deferrals.sh ../conf-home
+ cat warn-auto.sh deferrals.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > deferrals
+ chmod 755 deferrals
+
+direntry.h: \
+compile trydrent.c direntry.h1 direntry.h2
+ ( ./compile trydrent.c >/dev/null 2>&1 \
+ && cat direntry.h2 || cat direntry.h1 ) > direntry.h
+ rm -f trydrent.o
+
+dkim.o: \
+compile dkim.cpp
+ ./compile dkim.cpp
+
+dkimbase.o: \
+compile dkimbase.cpp
+ ./compile dkimbase.cpp
+
+dkimsign.o: \
+compile dkimsign.cpp
+ ./compile dkimsign.cpp
+
+dkimverify.o: \
+compile dkimverify.cpp
+ ./compile dkimverify.cpp
+
+dns.lib: \
+tryrsolv.c compile load
+ ( (./compile tryrsolv.c && \
+ ./load tryrsolv -L`head -1 ../conf-qlibs` -ldnsresolv ) \
+ && echo "-L`head -1 ../conf-qlibs` -ldnsresolv" || exit 0 ) > dns.lib
+ rm -f tryrsolv.o tryrsolv
+
+dns.o: \
+compile dns.c dns_tlsa.c
+ ./compile dns.c dns_tlsa.c
+
+dnscname: \
+load dnscname.o \
+dns.lib socket.lib qlibs.lib dns.o ipalloc.o
+ ./load dnscname dns.o ipalloc.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnscname.o: \
+compile dnscname.c
+ ./compile dnscname.c
+
+dnsfq: \
+load dnsfq.o \
+dns.lib socket.lib qlibs.lib dns.o ipalloc.o
+ ./load dnsfq dns.o ipalloc.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnsfq.o: \
+compile dnsfq.c
+ ./compile dnsfq.c
+
+dnsip: \
+load dnsip.o dns.o ipalloc.o \
+dns.lib socket.lib qlibs.lib
+ ./load dnsip dns.o ipalloc.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnsip.o: \
+compile dnsip.c
+ ./compile dnsip.c
+
+dnsmxip: \
+load dnsmxip.o ipalloc.o dns.o dns.lib socket.lib
+ ./load dnsmxip ipalloc.o dns.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnsmxip.o: \
+compile dnsmxip.c
+ ./compile dnsmxip.c
+
+dnsptr: \
+load dnsptr.o dns.o ipalloc.o \
+dns.lib socket.lib qlibs.lib
+ ./load dnsptr dns.o ipalloc.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnsptr.o: \
+compile dnsptr.c
+ ./compile dnsptr.c
+
+dnstlsa: \
+load dnstlsa.o dns_tlsa.o ipalloc.o dns.o \
+dns.lib socket.lib qlibs.lib
+ ./load dnstlsa dns_tlsa.o ipalloc.o dns.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnstlsa.o: \
+compile dnstlsa.c dns_tlsa.c
+ ./compile dnstlsa.c dns_tlsa.c
+
+dnstxt: \
+load dnstxt.o ipalloc.o dns.o \
+dns.lib socket.lib qlibs.lib
+ ./load dnstxt ipalloc.o dns.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+dnstxt.o: \
+compile dnstxt.c
+ ./compile dnstxt.c
+
+except: \
+load except.o qlibs.lib
+ ./load except `cat qlibs.lib`
+
+except.o: \
+compile except.c
+ ./compile except.c
+
+failures: \
+warn-auto.sh failures.sh ../conf-home
+ cat warn-auto.sh failures.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > failures
+ chmod 755 failures
+
+fastforward: \
+load fastforward.o qmail.o auto_qmail.o strset.o qlibs.lib
+ ./load fastforward qmail.o auto_qmail.o strset.o \
+ `cat qlibs.lib`
+
+fastforward.o: \
+compile fastforward.c
+ ./compile fastforward.c
+
+fifo.o: \
+compile fifo.c
+ ./compile fifo.c
+
+find-systype: \
+find-systype.sh auto-ccld.sh
+ cat auto-ccld.sh find-systype.sh > find-systype
+ chmod 755 find-systype
+
+fmtqfn.o: \
+compile fmtqfn.c
+ ./compile fmtqfn.c
+
+forward: \
+load forward.o qmail.o auto_qmail.o qlibs.lib
+ ./load forward qmail.o auto_qmail.o \
+ `cat qlibs.lib`
+
+forward.o: \
+compile forward.c
+ ./compile forward.c
+
+gfrom.o: \
+compile gfrom.c
+ ./compile gfrom.c
+
+hasflock.h: \
+tryflock.c compile load
+ ( ( ./compile tryflock.c && ./load tryflock ) >/dev/null \
+ 2>&1 \
+ && echo \#define HASFLOCK 1 || exit 0 ) > hasflock.h
+ rm -f tryflock.o tryflock
+
+hasmkffo.h: \
+trymkffo.c compile load
+ ( ( ./compile trymkffo.c && ./load trymkffo ) >/dev/null \
+ 2>&1 \
+ && echo \#define HASMKFIFO 1 || exit 0 ) > hasmkffo.h
+ rm -f trymkffo.o trymkffo
+
+hasspnam.h: \
+tryspnam.c compile load
+ ( ( ./compile tryspnam.c && ./load tryspnam ) >/dev/null \
+ 2>&1 \
+ && echo \#define HASGETSPNAM 1 || exit 0 ) > hasspnam.h
+ rm -f tryspnam.o tryspnam
+
+hasuserpw.h: \
+tryuserpw.c s.lib compile load
+ ( ( ./compile tryuserpw.c \
+ && ./load tryuserpw `cat s.lib` ) >/dev/null 2>&1 \
+ && echo \#define HASGETUSERPW 1 || exit 0 ) > hasuserpw.h
+ rm -f tryuserpw.o tryuserpw
+
+hassalen.h: \
+trysalen.c compile
+ ( ./compile trysalen.c >/dev/null 2>&1 \
+ && echo \#define HASSALEN 1 || exit 0 ) > hassalen.h
+
+hassgact.h: \
+trysgact.c compile load
+ ( ( ./compile trysgact.c && ./load trysgact ) >/dev/null \
+ 2>&1 \
+ && echo \#define HASSIGACTION 1 || exit 0 ) > hassgact.h
+ rm -f trysgact.o trysgact
+
+hassgprm.h: \
+trysgprm.c compile load
+ ( ( ./compile trysgprm.c && ./load trysgprm ) >/dev/null \
+ 2>&1 \
+ && echo \#define HASSIGPROCMASK 1 || exit 0 ) > hassgprm.h
+ rm -f trysgprm.o trysgprm
+
+hasshsgr.h: \
+chkshsgr warn-shsgr tryshsgr.c compile load
+ ./chkshsgr || ( cat warn-shsgr; exit 1 )
+ ( ( ./compile tryshsgr.c \
+ && ./load tryshsgr && ./tryshsgr ) >/dev/null 2>&1 \
+ && echo \#define HASSHORTSETGROUPS 1 || exit 0 ) > \
+ hasshsgr.h
+ rm -f tryshsgr.o tryshsgr
+
+hasutmp.h: \
+tryutmp.c compile
+ ( ./compile tryutmp.c >/dev/null 2>&1 \
+ && echo \#define HASUTMP 1 || exit 0 ) > hasutmp.h
+ rm -f tryutmp.o
+
+haswaitp.h: \
+trywaitp.c compile load
+ ( ( ./compile trywaitp.c && ./load trywaitp ) >/dev/null \
+ 2>&1 \
+ && echo \#define HASWAITPID 1 || exit 0 ) > haswaitp.h
+ rm -f trywaitp.o trywaitp
+
+headerbody.o: \
+compile headerbody.c
+ ./compile headerbody.c
+
+hfield.o: \
+compile hfield.c
+ ./compile hfield.c
+
+hier.o: \
+compile hier.c
+ ./compile hier.c
+
+hostname: \
+load hostname.o dns.lib socket.lib
+ ./load hostname `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+hostname.o: \
+compile hostname.c
+ ./compile hostname.c
+
+idn2.lib: \
+tryidn2.c compile load
+ ( (./compile tryidn2.c && \
+ ./load tryidn2 `head -1 ../conf-idn2` -lidn2 ) >/dev/null 2>&1 \
+ && echo "`head -1 ../conf-idn2` -lidn2" || exit 0 ) > idn2.lib
+ rm -f tryind2.o tryidn2
+
+install: \
+load install.o hier.o auto_qmail.o auto_split.o auto_uids.o fifo.o qlibs.lib
+ ./load install hier.o auto_qmail.o auto_split.o auto_uids.o fifo.o \
+ `cat qlibs.lib`
+
+install.o: \
+compile install.c
+ ./compile install.c
+
+instcheck: \
+load instcheck.o hier.o auto_qmail.o auto_split.o auto_uids.o qlibs.lib
+ ./load instcheck hier.o auto_qmail.o auto_split.o auto_uids.o \
+ `cat qlibs.lib`
+
+instcheck.o: \
+compile instcheck.c
+ ./compile instcheck.c
+
+ipalloc.o: \
+compile ipalloc.c
+ ./compile ipalloc.c
+
+ipme.o: \
+compile ipme.c hassalen.h
+ ./compile ipme.c
+
+ipmeprint: \
+load ipmeprint.o ipme.o ipalloc.o auto_qmail.o \
+dns.lib socket.lib qlibs.lib
+ ./load ipmeprint ipme.o auto_qmail.o ipalloc.o \
+ `cat qlibs.lib` `cat socket.lib` `cat dns.lib`
+
+ipmeprint.o: \
+compile ipmeprint.c
+ ./compile ipmeprint.c
+
+it-analog: \
+columnt matchup \
+ddist deferrals failures senders successes suids \
+recipients rhosts rhosts rxdelay \
+xqp xrecipient xsender \
+zddist zdeferrals zfailures zrecipients zrhosts \
+zrxdelay zsenders zsendmail zsuccesses zsuids zoverall
+
+it-base: \
+qmail-local qmail-rspawn qmail-lspawn qmail-send qmail-qmaint \
+qmail-clean qmail-start qmail-queue qmail-inject qmail-todo
+
+it-mbox: \
+forward predate preline condredirect bouncesaying except \
+datemail maildirmake maildir2mbox maildirwatch qbiff qreceipt
+
+it-clients: \
+mailsubj qmail-remote qmail-qmqpc sendmail
+
+it-dkim: \
+qmail-dkim qmail-dksign qmail-dkverify
+
+it-dns: \
+dnscname dnsptr dnsip dnsmxip dnsfq dnstlsa dnstxt \
+hostname ipmeprint spfquery
+
+it-pop: \
+qmail-popup qmail-pop3d
+
+it-forward: \
+fastforward forward printforward setforward newaliases \
+printmaillist setmaillist newinclude
+
+it-control: \
+qmail-badmimetypes qmail-badloadertypes \
+qmail-mfrules qmail-recipients qmail-showctl
+
+it-log: \
+splogger qmail-mrtg qmail-mrtg-queue tai64nfrac
+
+it-pam: \
+qmail-authuser qmail-smtpam qmail-vmailuser \
+qmail-postgrey
+
+it-queue: \
+qmail-qread qmail-qstat qmail-tcpto qmail-tcpok qmail-upq
+
+it-server: \
+qmail-qmtpd qmail-qmqpd qmail-smtpd
+
+it-setup: \
+config config-fast install instcheck
+
+it-srs: \
+srsforward srsreverse
+
+it-user: \
+qmail-getpw qmail-newu qmail-pw2u qmail-newmrh
+
+load: \
+make-load warn-auto.sh systype
+ ( cat warn-auto.sh; ./make-load "`cat systype` `cat qlibs.lib`" ) > load
+ chmod 755 load
+
+maildir.o: \
+compile maildir.c
+ ./compile maildir.c
+
+maildir2mbox: \
+load maildir2mbox.o maildir.o prioq.o now.o myctime.o gfrom.o \
+datetime.a
+ ./load maildir2mbox maildir.o prioq.o now.o myctime.o \
+ gfrom.o datetime.a `cat qlibs.lib`
+
+maildir2mbox.o: \
+compile maildir2mbox.c
+ ./compile maildir2mbox.c
+
+maildirmake: \
+load maildirmake.o
+ ./load maildirmake `cat qlibs.lib`
+
+maildirmake.o: \
+compile maildirmake.c
+ ./compile maildirmake.c
+
+maildirwatch: \
+load maildirwatch.o hfield.o headerbody.o maildir.o prioq.o now.o
+ ./load maildirwatch hfield.o headerbody.o maildir.o \
+ prioq.o now.o `cat qlibs.lib`
+
+maildirwatch.o: \
+compile maildirwatch.c
+ ./compile maildirwatch.c
+
+mailsubj: \
+warn-auto.sh mailsubj.sh ../conf-home ../conf-break ../conf-split
+ cat warn-auto.sh mailsubj.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPLIT}"`head -1 ../conf-split`"}g \
+ > mailsubj
+ chmod 755 mailsubj
+
+make-compile: \
+make-compile.sh auto-ccld.sh
+ cat auto-ccld.sh make-compile.sh > make-compile
+ chmod 755 make-compile
+
+make-load: \
+make-load.sh auto-ccld.sh
+ cat auto-ccld.sh make-load.sh > make-load
+ chmod 755 make-load
+
+make-makelib: \
+make-makelib.sh auto-ccld.sh
+ cat auto-ccld.sh make-makelib.sh > make-makelib
+ chmod 755 make-makelib
+
+makelib: \
+make-makelib warn-auto.sh systype
+ ( cat warn-auto.sh; ./make-makelib "`cat systype`" ) > \
+ makelib
+ chmod 755 makelib
+
+matchup: \
+load matchup.o qlibs.lib
+ ./load matchup `cat qlibs.lib`
+
+matchup.o: \
+compile matchup.c
+ ./compile matchup.c
+
+myctime.o: \
+compile myctime.c
+ ./compile myctime.c
+
+mfrules.o: \
+compile mfrules.c
+ ./compile mfrules.c
+
+newaliases: \
+load newaliases.o auto_qmail.o token822.o control.o qlibs.lib
+ ./load newaliases auto_qmail.o token822.o control.o \
+ `cat qlibs.lib`
+
+newaliases.o: \
+compile newaliases.c
+ ./compile newaliases.c
+
+newinclude: \
+load newinclude.o auto_qmail.o token822.o control.o qlibs.lib
+ ./load newinclude auto_qmail.o token822.o control.o \
+ `cat qlibs.lib`
+
+newinclude.o: \
+compile newinclude.c
+ ./compile newinclude.c
+
+newfield.o: \
+compile newfield.c
+ ./compile newfield.c
+
+now.o: \
+compile now.c
+ ./compile now.c
+
+predate: \
+load predate.o datetime.a qlibs.lib
+ ./load predate datetime.a `cat qlibs.lib`
+
+predate.o: \
+compile predate.c
+ ./compile predate.c
+
+preline: \
+load preline.o qlibs.lib
+ ./load preline `cat qlibs.lib`
+
+preline.o: \
+compile preline.c
+ ./compile preline.c
+
+printforward: \
+load printforward.o qlibs.lib
+ ./load printforward `cat qlibs.lib`
+
+printforward.o: \
+compile printforward.c
+ ./compile printforward.c
+
+printmaillist: \
+load printmaillist.o qlibs.lib
+ ./load printmaillist `cat qlibs.lib`
+
+printmaillist.o: \
+compile printmaillist.c
+ ./compile printmaillist.c
+
+prioq.o: \
+compile prioq.c
+ ./compile prioq.c
+
+qbiff: \
+load qbiff.o headerbody.o hfield.o qlibs.lib
+ ./load qbiff headerbody.o hfield.o `cat qlibs.lib`
+
+qbiff.o: \
+compile hasutmp.h qbiff.c
+ ./compile qbiff.c
+
+qlibs.lib: \
+tryqlibs.c compile load
+ ( (./compile tryqlibs.c && \
+ ./load tryqlibs -L`head -1 ../conf-qlibs` -lqlibs ) \
+ && echo "-L`head -1 ../conf-qlibs` -lqlibs" || exit 0 ) > qlibs.lib
+ rm -f tryqlibs.o tryqlibs
+
+qmail-authuser: \
+load qmail-authuser.o auto_qmail.o control.o hmac_md5.o md5c.o \
+constmap.o shadow.lib sha1.o sha256.o \
+qlibs.lib shadow.lib crypt.lib s.lib
+ ./load qmail-authuser auto_qmail.o control.o \
+ constmap.o hmac_md5.o md5c.o sha1.o sha256.o \
+ `cat shadow.lib` `cat qlibs.lib` `cat crypt.lib` `cat s.lib`
+
+qmail-authuser.o: \
+compile qmail-authuser.c hasspnam.h hasuserpw.h
+ ./compile qmail-authuser.c
+
+qmail-clean: \
+load qmail-clean.o fmtqfn.o now.o auto_qmail.o auto_split.o qlibs.lib
+ ./load qmail-clean fmtqfn.o now.o auto_qmail.o auto_split.o `cat qlibs.lib`
+
+qmail-clean.o: \
+compile qmail-clean.c
+ ./compile qmail-clean.c
+
+qmail-dkim: \
+load qmail-dkim.o libqdkim.a dkim.o dkimbase.o dkimsign.o dkimverify.o \
+ qlibs.lib dns.lib ssl.lib
+ ./load qmail-dkim libqdkim.a \
+ -lstdc++ `cat dns.lib` `cat qlibs.lib` `cat ssl.lib`
+
+qmail-dkim.o: \
+compile qmail-dkim.cpp dkim.cpp dkimbase.cpp dkimsign.cpp dkimverify.cpp
+ ./compile qmail-dkim.cpp
+
+qmail-dksign: \
+load qmail-dksign.o control.o constmap.o fmtqfn.o rcpthosts.o qmail-dkim \
+auto_qmail.o auto_split.o qlibs.lib
+ ./load qmail-dksign control.o constmap.o fmtqfn.o rcpthosts.o \
+ auto_qmail.o auto_split.o `cat qlibs.lib`
+
+qmail-dksign.o: \
+compile qmail-dksign.c
+ ./compile qmail-dksign.c
+
+qmail-dkverify: \
+load qmail-dkverify.o control.o fmtqfn.o qmail-dkim \
+auto_qmail.o auto_split.o qmail.o qlibs.lib
+ ./load qmail-dkverify qmail.o control.o fmtqfn.o \
+ auto_qmail.o auto_split.o `cat qlibs.lib`
+
+qmail-dkverify.o: \
+compile qmail-dkverify.c
+ ./compile qmail-dkverify.c
+
+qmail-getpw: \
+load qmail-getpw.o auto_break.o auto_usera.o qlibs.lib
+ ./load qmail-getpw auto_break.o auto_usera.o `cat qlibs.lib`
+
+qmail-getpw.o: \
+compile qmail-getpw.c
+ ./compile qmail-getpw.c
+
+qmail-inject: \
+load qmail-inject.o headerbody.o hfield.o newfield.o quote.o now.o \
+control.o date822fmt.o qmail.o datetime.a token822.o auto_qmail.o qlibs.lib
+ ./load qmail-inject headerbody.o hfield.o newfield.o \
+ constmap.o quote.o now.o control.o date822fmt.o qmail.o datetime.a \
+ token822.o auto_qmail.o `cat qlibs.lib`
+
+qmail-inject.o: \
+compile qmail-inject.c
+ ./compile qmail-inject.c
+
+qmail-clean: \
+load qmail-clean.o fmtqfn.o now.o auto_qmail.o auto_split.o qlibs.lib
+
+qmail-local: \
+load qmail-local.o auto_qmail.o auto_break.o auto_patrn.o \
+qmail.o quote.o now.o gfrom.o myctime.o datetime.a socket.lib qlibs.lib
+ ./load qmail-local qmail.o quote.o now.o gfrom.o myctime.o \
+ datetime.a auto_qmail.o auto_break.o auto_patrn.o \
+ `cat socket.lib` `cat qlibs.lib`
+
+qmail-local.o: \
+compile qmail-local.c
+ ./compile qmail-local.c
+
+qmail-lspawn: \
+load qmail-lspawn.o spawn.o \
+auto_qmail.o auto_uids.o auto_spawn.o qlibs.lib
+ ./load qmail-lspawn spawn.o \
+ auto_qmail.o auto_uids.o auto_spawn.o `cat qlibs.lib`
+
+qmail-lspawn.o: \
+compile qmail-lspawn.c
+ ./compile qmail-lspawn.c
+
+qmail-badmimetypes: \
+load qmail-badmimetypes.o auto_qmail.o qlibs.lib
+ ./load qmail-badmimetypes auto_qmail.o `cat qlibs.lib`
+
+qmail-badmimetypes.o: \
+compile qmail-badmimetypes.c
+ ./compile qmail-badmimetypes.c
+
+qmail-badloadertypes: \
+load qmail-badloadertypes.o auto_qmail.o qlibs.lib
+ ./load qmail-badloadertypes auto_qmail.o `cat qlibs.lib`
+
+qmail-badloadertypes.8: \
+qmail-badloadertypes.9 ../conf-break ../conf-spawn
+ cat qmail-badloadertypes.9 \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPAWN}"`head -1 ../conf-spawn`"}g \
+ > qmail-badloadertypes.8
+
+qmail-badloadertypes.o: \
+compile qmail-badloadertypes.c
+ ./compile qmail-badloadertypes.c
+
+qmail-newmrh: \
+load qmail-newmrh.o auto_qmail.o qlibs.lib
+ ./load qmail-newmrh auto_qmail.o `cat qlibs.lib`
+
+qmail-newmrh.o: \
+compile qmail-newmrh.c
+ ./compile qmail-newmrh.c
+
+qmail-recipients: \
+load qmail-recipients.o auto_qmail.o qlibs.lib
+ ./load qmail-recipients auto_qmail.o `cat qlibs.lib`
+
+qmail-recipients.o: \
+compile qmail-recipients.c
+ ./compile qmail-recipients.c
+
+qmail-vmailuser: \
+load qmail-vmailuser.o auto_qmail.o control.o constmap.o qlibs.lib
+ ./load qmail-vmailuser auto_qmail.o control.o constmap.o \
+ `cat qlibs.lib`
+
+qmail-vmailuser.o: \
+compile qmail-vmailuser.c
+ ./compile qmail-vmailuser.c
+
+qmail-smtpam: \
+load qmail-smtpam.o control.o now.o dns.o constmap.o \
+ipalloc.o ipme.o quote.o auto_qmail.o tcpto.o \
+tls_timeoutio.o tls_errors.o tls_remote.o dns_tlsa.o \
+ssl.lib dns.lib socket.lib qlibs.lib ucspissl.a
+ ./load qmail-smtpam constmap.o control.o dns_tlsa.o \
+ tcpto.o now.o dns.o ipalloc.o ipme.o quote.o auto_qmail.o \
+ tls_errors.o tls_remote.o tls_timeoutio.o ucspissl.a \
+ `cat ssl.lib` `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+qmail-smtpam.o: \
+compile qmail-smtpam.c
+ ./compile qmail-smtpam.c
+
+qmail-mfrules: \
+load qmail-mfrules.o auto_qmail.o qlibs.lib
+ ./load qmail-mfrules auto_qmail.o `cat qlibs.lib`
+
+qmail-mfrules.o: \
+compile qmail-mfrules.c
+ ./compile qmail-mfrules.c
+
+qmail-mrtg: \
+load qmail-mrtg.o now.o qlibs.lib
+ ./load qmail-mrtg now.o `cat qlibs.lib`
+
+qmail-mrtg.o: \
+compile qmail-mfrules.c
+ ./compile qmail-mrtg.c
+
+qmail-mrtg-queue: \
+warn-auto.sh qmail-mrtg-queue.sh ../conf-home
+ cat warn-auto.sh qmail-mrtg-queue.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > qmail-mrtg-queue
+ chmod 755 qmail-mrtg-queue
+
+qmail-newu: \
+load qmail-newu.o auto_qmail.o qlibs.lib
+ ./load qmail-newu auto_qmail.o `cat qlibs.lib`
+
+qmail-newu.o: \
+compile qmail-newu.c
+ ./compile qmail-newu.c
+
+qmail-pop3d: \
+load qmail-pop3d.o commands.o maildir.o prioq.o now.o socket.lib qlibs.lib
+ ./load qmail-pop3d commands.o maildir.o prioq.o now.o \
+ `cat socket.lib` `cat qlibs.lib`
+
+qmail-pop3d.o: \
+compile qmail-pop3d.c
+ ./compile qmail-pop3d.c
+
+qmail-popup: \
+load qmail-popup.o commands.o now.o tls_start.o socket.lib qlibs.lib
+ ./load qmail-popup commands.o tls_start.o now.o \
+ `cat socket.lib` `cat qlibs.lib`
+
+qmail-popup.o: \
+compile qmail-popup.c
+ ./compile qmail-popup.c
+
+qmail-postgrey: \
+load qmail-postgrey.o socket.lib qlibs.lib
+ ./load qmail-postgrey \
+ `cat socket.lib` `cat qlibs.lib`
+
+qmail-postgrey.o: \
+compile qmail-postgrey.c
+ ./compile qmail-postgrey.c
+
+qmail-pw2u: \
+load qmail-pw2u.o constmap.o control.o auto_usera.o auto_break.o auto_qmail.o qlibs.lib
+ ./load qmail-pw2u constmap.o control.o \
+ auto_usera.o auto_break.o auto_qmail.o `cat qlibs.lib`
+
+qmail-pw2u.o: \
+compile qmail-pw2u.c
+ ./compile qmail-pw2u.c
+
+qmail-qmqpc: \
+load qmail-qmqpc.o control.o auto_qmail.o socket.lib qlibs.lib
+ ./load qmail-qmqpc control.o auto_qmail.o `cat socket.lib` `cat qlibs.lib`
+
+qmail-qmqpc.o: \
+compile qmail-qmqpc.c
+ ./compile qmail-qmqpc.c
+
+qmail-qmqpd: \
+load qmail-qmqpd.o received.o now.o date822fmt.o qmail.o auto_qmail.o \
+datetime.a qlibs.lib
+ ./load qmail-qmqpd received.o now.o date822fmt.o datetime.a qmail.o \
+ auto_qmail.o `cat qlibs.lib`
+
+qmail-qmqpd.o: \
+compile qmail-qmqpd.c
+ ./compile qmail-qmqpd.c
+
+qmail-qmtpd: \
+load qmail-qmtpd.o rcpthosts.o control.o constmap.o received.o \
+date822fmt.o now.o qmail.o datetime.a auto_qmail.o qlibs.lib
+ ./load qmail-qmtpd rcpthosts.o auto_qmail.o control.o constmap.o \
+ received.o date822fmt.o now.o qmail.o datetime.a `cat qlibs.lib`
+
+qmail-qmtpd.o: \
+compile qmail-qmtpd.c
+ ./compile qmail-qmtpd.c
+
+qmail-qread: \
+load qmail-qread.o fmtqfn.o readsubdir.o date822fmt.o datetime.a \
+auto_qmail.o auto_split.o qlibs.lib
+ ./load qmail-qread fmtqfn.o readsubdir.o date822fmt.o \
+ datetime.a auto_qmail.o auto_split.o `cat qlibs.lib`
+
+qmail-qread.o: \
+compile qmail-qread.c
+ ./compile qmail-qread.c
+
+qmail-qstat: \
+warn-auto.sh qmail-qstat.sh ../conf-home ../conf-break ../conf-split
+ cat warn-auto.sh qmail-qstat.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPLIT}"`head -1 ../conf-split`"}g \
+ > qmail-qstat
+ chmod 755 qmail-qstat
+
+qmail-queue: \
+load qmail-queue.o triggerpull.o fmtqfn.o now.o date822fmt.o \
+datetime.a auto_qmail.o auto_split.o auto_uids.o qlibs.lib
+ ./load qmail-queue auto_qmail.o auto_split.o auto_uids.o \
+ triggerpull.o fmtqfn.o now.o date822fmt.o datetime.a `cat qlibs.lib`
+
+qmail-queue.o: \
+compile qmail-queue.c
+ ./compile qmail-queue.c
+
+qmail-qmaint: \
+load qmail-qmaint.o auto_qmail.o auto_split.o auto_uids.o fifo.o \
+fmtqfn.o readsubdir.o qlibs.lib
+ ./load qmail-qmaint auto_qmail.o auto_split.o auto_uids.o fifo.o \
+ fmtqfn.o readsubdir.o `cat qlibs.lib`
+
+qmail-qmaint.o: \
+compile qmail-qmaint.c
+ ./compile qmail-qmaint.c
+
+qmail-remote: \
+load qmail-remote.o control.o tcpto.o now.o dns.o ipalloc.o ipme.o \
+quote.o tls_timeoutio.o tls_errors.o tls_remote.o dns_tlsa.o \
+base64.o constmap.o md5c.o hmac_md5.o auto_qmail.o \
+ssl.lib dns.lib socket.lib qlibs.lib idn2.lib ucspissl.a
+ ./load qmail-remote control.o tcpto.o now.o \
+ base64.o constmap.o md5c.o hmac_md5.o ipalloc.o ipme.o \
+ quote.o dns.o ucspissl.a auto_qmail.o dns_tlsa.o \
+ tls_errors.o tls_remote.o tls_timeoutio.o ucspissl.a \
+ `cat ssl.lib` `cat dns.lib` `cat socket.lib` `cat qlibs.lib` `cat idn2.lib`
+
+qmail-remote.o: \
+compile qmail-remote.c
+ ./compile qmail-remote.c
+
+qmail-rspawn: \
+load qmail-rspawn.o spawn.o tcpto_clean.o now.o \
+auto_qmail.o auto_uids.o auto_spawn.o qlibs.lib
+ ./load qmail-rspawn spawn.o tcpto_clean.o now.o \
+ auto_qmail.o auto_uids.o auto_spawn.o `cat qlibs.lib`
+
+qmail-rspawn.o: \
+compile qmail-rspawn.c
+ ./compile qmail-rspawn.c
+
+qmail-send: \
+load qmail-send.o qsutil.o control.o constmap.o newfield.o prioq.o \
+trigger.o fmtqfn.o quote.o now.o readsubdir.o qmail.o date822fmt.o \
+datetime.a auto_qmail.o auto_split.o qlibs.lib
+ ./load qmail-send qsutil.o control.o constmap.o newfield.o \
+ prioq.o trigger.o fmtqfn.o quote.o now.o readsubdir.o \
+ qmail.o date822fmt.o datetime.a auto_qmail.o auto_split.o `cat qlibs.lib`
+
+qmail-send.o: \
+compile qmail-send.c
+ ./compile qmail-send.c
+
+qmail-showctl: \
+load qmail-showctl.o auto_uids.o control.o auto_qmail.o auto_break.o \
+auto_patrn.o auto_spawn.o auto_split.o qlibs.lib
+ ./load qmail-showctl auto_uids.o auto_qmail.o auto_break.o auto_patrn.o \
+ auto_spawn.o auto_split.o control.o `cat qlibs.lib`
+
+qmail-showctl.o: \
+compile qmail-showctl.c
+ ./compile qmail-showctl.c
+
+qmail-smtpd: \
+load qmail-smtpd.o auto_break.o rcpthosts.o commands.o \
+ipme.o ipalloc.o constmap.o control.o received.o \
+recipients.o mfrules.o tls_start.o smtpdlog.o dns.o \
+date822fmt.o now.o qmail.o wildmat.o spf.o spfdnsip.o \
+datetime.a auto_qmail.o base64.o socket.lib qlibs.lib
+ ./load qmail-smtpd rcpthosts.o recipients.o commands.o \
+ mfrules.o tls_start.o auto_break.o smtpdlog.o ipme.o \
+ ipalloc.o constmap.o control.o dns.o spf.o spfdnsip.o \
+ date822fmt.o now.o qmail.o wildmat.o received.o \
+ base64.o datetime.a auto_qmail.o \
+ `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+qmail-smtpd.o: \
+compile qmail-smtpd.c
+ ./compile qmail-smtpd.c
+
+qmail-start: \
+load qmail-start.o auto_uids.o qlibs.lib
+ ./load qmail-start auto_uids.o `cat qlibs.lib`
+
+qmail-start.o: \
+compile qmail-start.c
+ ./compile qmail-start.c
+
+qmail-tcpok: \
+load qmail-tcpok.o auto_qmail.o qlibs.lib
+ ./load qmail-tcpok auto_qmail.o `cat qlibs.lib`
+
+qmail-tcpok.o: \
+compile qmail-tcpok.c
+ ./compile qmail-tcpok.c
+
+qmail-tcpto: \
+load qmail-tcpto.o now.o auto_qmail.o qlibs.lib
+ ./load qmail-tcpto now.o auto_qmail.o `cat qlibs.lib`
+
+qmail-tcpto.o: \
+compile qmail-tcpto.c
+ ./compile qmail-tcpto.c
+
+qmail-todo: \
+load qmail-todo.o control.o constmap.o trigger.o fmtqfn.o \
+now.o qsutil.o readsubdir.o auto_qmail.o auto_split.o qlibs.lib
+ ./load qmail-todo control.o constmap.o trigger.o fmtqfn.o now.o \
+ readsubdir.o qsutil.o auto_qmail.o auto_split.o `cat qlibs.lib`
+
+qmail-todo.o: \
+compile qmail-todo.c
+ ./compile qmail-todo.c
+
+qmail-upq: \
+warn-auto.sh qmail-upq.sh ../conf-home ../conf-break ../conf-split
+ cat warn-auto.sh qmail-upq.sh \
+ | sed s}QMAIL}"`head -1 ../conf-home`"}g \
+ | sed s}BREAK}"`head -1 ../conf-break`"}g \
+ | sed s}SPLIT}"`head -1 ../conf-split`"}g \
+ > qmail-upq
+ chmod 755 qmail-upq
+
+qmail.o: \
+compile qmail.c
+ ./compile qmail.c
+
+qreceipt: \
+load qreceipt.o headerbody.o hfield.o quote.o token822.o qmail.o \
+auto_qmail.o qlibs.lib
+ ./load qreceipt headerbody.o hfield.o quote.o token822.o \
+ qmail.o auto_qmail.o `cat qlibs.lib`
+
+qreceipt.o: \
+compile qreceipt.c
+ ./compile qreceipt.c
+
+qsutil.o: \
+compile qsutil.c
+ ./compile qsutil.c
+
+quote.o: \
+compile quote.c
+ ./compile quote.c
+
+rcpthosts.o: \
+compile rcpthosts.c
+ ./compile rcpthosts.c
+
+recipients.o: \
+compile recipients.c
+ ./compile recipients.c
+
+recipients: \
+warn-auto.sh recipients.sh ../conf-home
+ cat warn-auto.sh recipients.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > recipients
+ chmod 755 recipients
+
+rhosts: \
+warn-auto.sh rhosts.sh ../conf-home
+ cat warn-auto.sh rhosts.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > rhosts
+ chmod 755 rhosts
+
+rxdelay: \
+warn-auto.sh rxdelay.sh ../conf-home
+ cat warn-auto.sh rxdelay.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > rxdelay
+ chmod 755 rxdelay
+
+senders: \
+warn-auto.sh senders.sh ../conf-home
+ cat warn-auto.sh senders.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > senders
+ chmod 755 senders
+
+smtpdlog.o: \
+compile smtpdlog.c
+ ./compile smtpdlog.c
+
+s.lib: \
+tryslib.c compile load
+ ( ( ./compile tryslib.c && \
+ ./load tryslib -ls ) >/dev/null 2>&1 \
+ && echo -ls || exit 0 ) > s.lib
+ rm -f tryslib.o tryslib
+
+shadow.lib: \
+tryshadow.c compile load
+ ( ( ./compile tryshadow.c && \
+ ./load tryshadow -lshadow ) >/dev/null 2>&1 \
+ && echo -lshadow || exit 0 ) > shadow.lib
+ rm -f tryshadow.o tryshadow
+
+successes: \
+warn-auto.sh successes.sh ../conf-home
+ cat warn-auto.sh successes.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > successes
+ chmod 755 successes
+
+suids: \
+warn-auto.sh suids.sh ../conf-home
+ cat warn-auto.sh suids.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > suids
+ chmod 755 suids
+
+readsubdir.o: \
+compile readsubdir.c
+ ./compile readsubdir.c
+
+received.o: \
+compile received.c
+ ./compile received.c
+
+sendmail: \
+load sendmail.o auto_qmail.o qlibs.lib
+ ./load sendmail auto_qmail.o `cat qlibs.lib`
+
+sendmail.o: \
+compile sendmail.c
+ ./compile sendmail.c
+
+setforward: \
+load setforward.o qlibs.lib
+ ./load setforward `cat qlibs.lib`
+
+setforward.o: \
+compile setforward.c
+ ./compile setforward.c
+
+setmaillist: \
+load setmaillist.o qlibs.lib
+ ./load setmaillist `cat qlibs.lib`
+
+setmaillist.o: \
+compile setmaillist.c
+ ./compile setmaillist.c
+
+sha1.o: \
+compile sha1.c
+ ./compile sha1.c
+
+sha256.o : \
+compile sha256.c
+ ./compile sha256.c
+
+socket.lib: \
+trylsock.c compile load
+ ( ( ./compile trylsock.c && \
+ ./load trylsock -lsocket -lnsl ) >/dev/null 2>&1 \
+ && echo -lsocket -lnsl || exit 0 ) > socket.lib
+ rm -f trylsock.o trylsock
+
+spawn.o: \
+compile chkspawn spawn.c
+ ./chkspawn
+ ./compile spawn.c
+
+spfdnsip.o: \
+compile spfdnsip.c
+ ./compile spfdnsip.c
+
+spf.o: \
+compile spf.c
+ ./compile spf.c
+
+spfquery: \
+load spfquery.o spf.o ipme.o ipalloc.o now.o dns.o \
+spfdnsip.o datetime.a dns.lib qlibs.lib
+ ./load spfquery spf.o ipme.o ipalloc.o spfdnsip.o \
+ now.o dns.o datetime.a `cat dns.lib` `cat socket.lib` `cat qlibs.lib`
+
+spfquery.o: \
+compile spfquery.c
+ ./compile spfquery.c
+
+splogger: \
+load splogger.o syslog.lib socket.lib qlibs.lib
+ ./load splogger `cat syslog.lib` `cat socket.lib` `cat qlibs.lib`
+
+splogger.o: \
+compile splogger.c
+ ./compile splogger.c
+
+srs2.o: \
+ compile srs2.c
+ ./compile srs2.c
+
+srsforward: \
+load srsforward.o qmail.o auto_qmail.o control.o constmap.o \
+srs2.o sha1.o \
+qlibs.lib
+ ./load srsforward qmail.o auto_qmail.o control.o constmap.o \
+ srs2.o sha1.o `cat qlibs.lib`
+
+srsforward.o: \
+compile srsforward.c
+ ./compile srsforward.c
+
+srsreverse: \
+load srsreverse.o qmail.o auto_break.o auto_qmail.o \
+control.o constmap.o srs2.o sha1.o qlibs.lib
+ ./load srsreverse qmail.o auto_break.o auto_qmail.o \
+ control.o constmap.o srs2.o sha1.o \
+ `cat qlibs.lib`
+
+srsreverse.o: \
+compile srsreverse.c
+ ./compile srsreverse.c
+
+strset.o: \
+compile strset.c
+ ./compile strset.c
+
+syslog.lib: \
+trysyslog.c compile load
+ ( ( ./compile trysyslog.c && \
+ ./load trysyslog -lgen ) >/dev/null 2>&1 \
+ && echo -lgen || exit 0 ) > syslog.lib
+ rm -f trysyslog.o trysyslog
+
+systype: \
+find-systype trycpp.c
+ ./find-systype > systype
+
+tai64nfrac: \
+load tai64nfrac.o qlibs.lib
+ ./load tai64nfrac `cat qlibs.lib`
+
+tai64nfrac.o: \
+compile tai64nfrac.c
+ ./compile tai64nfrac.c
+
+tcpto.o: \
+compile tcpto.c
+ ./compile tcpto.c
+
+tcpto_clean.o: \
+compile tcpto_clean.c
+ ./compile tcpto_clean.c
+
+tls_errors.o: \
+compile tls_errors.c
+ ./compile tls_errors.c
+
+tls_remote.o: \
+compile tls_remote.c
+ ./compile tls_remote.c
+
+tls_start.o: \
+compile tls_start.c
+ ./compile tls_start.c tls_errors.c
+
+tls_timeoutio.o: \
+compile tls_timeoutio.c
+ ./compile tls_timeoutio.c
+
+token822.o: \
+compile token822.c
+ ./compile token822.c
+
+trigger.o: \
+compile trigger.c
+ ./compile trigger.c
+
+triggerpull.o: \
+compile triggerpull.c
+ ./compile triggerpull.c
+
+wildmat.o: \
+compile wildmat.c
+ ./compile wildmat.c
+
+xqp: \
+warn-auto.sh xqp.sh ../conf-home
+ cat warn-auto.sh xqp.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > xqp
+ chmod 755 xqp
+
+xrecipient: \
+warn-auto.sh xrecipient.sh ../conf-home
+ cat warn-auto.sh xrecipient.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > xrecipient
+ chmod 755 xrecipient
+
+xsender: \
+warn-auto.sh xsender.sh ../conf-home
+ cat warn-auto.sh xsender.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > xsender
+ chmod 755 xsender
+
+zddist: \
+warn-auto.sh zddist.sh ../conf-home
+ cat warn-auto.sh zddist.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zddist
+ chmod 755 zddist
+
+zdeferrals: \
+warn-auto.sh zdeferrals.sh ../conf-home
+ cat warn-auto.sh zdeferrals.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zdeferrals
+ chmod 755 zdeferrals
+
+zfailures: \
+warn-auto.sh zfailures.sh ../conf-home
+ cat warn-auto.sh zfailures.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zfailures
+ chmod 755 zfailures
+
+zoverall: \
+warn-auto.sh zoverall.sh ../conf-home
+ cat warn-auto.sh zoverall.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zoverall
+ chmod 755 zoverall
+
+zrecipients: \
+warn-auto.sh zrecipients.sh ../conf-home
+ cat warn-auto.sh zrecipients.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zrecipients
+ chmod 755 zrecipients
+
+zrhosts: \
+warn-auto.sh zrhosts.sh ../conf-home
+ cat warn-auto.sh zrhosts.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zrhosts
+ chmod 755 zrhosts
+
+zrxdelay: \
+warn-auto.sh zrxdelay.sh ../conf-home
+ cat warn-auto.sh zrxdelay.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zrxdelay
+ chmod 755 zrxdelay
+
+zsenders: \
+warn-auto.sh zsenders.sh ../conf-home
+ cat warn-auto.sh zsenders.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zsenders
+ chmod 755 zsenders
+
+zsendmail: \
+warn-auto.sh zsendmail.sh ../conf-home
+ cat warn-auto.sh zsendmail.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zsendmail
+ chmod 755 zsendmail
+
+zsuccesses: \
+warn-auto.sh zsuccesses.sh ../conf-home
+ cat warn-auto.sh zsuccesses.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zsuccesses
+ chmod 755 zsuccesses
+
+zsuids: \
+warn-auto.sh zsuids.sh ../conf-home
+ cat warn-auto.sh zsuids.sh \
+ | sed s}HOME}"`head -1 ../conf-home`"}g \
+ > zsuids
+ chmod 755 zsuids
+
+# cpp sources
+
+SRCS = dkim.cpp dkimbase.cpp dkimsign.cpp dkimverify.cpp
+OBJS = $(SRCS:.cpp=.o)
+
+.cpp.o:
+ g++ -O2 -std=c11 $< $*
+
+libqdkim.a: $(OBJS)
+ @rm -f libqdkim.a
+ ar cr libqdkim.a $(OBJS)
+ ranlib libqdkim.a
+
diff --git a/src/TARGETS b/src/TARGETS
new file mode 100644
index 0000000..c83e46e
--- /dev/null
+++ b/src/TARGETS
@@ -0,0 +1,270 @@
+auto-ccld.sh
+auto-gid
+auto-gid.o
+auto-int
+auto-int.o
+auto-int8
+auto-int8.o
+auto-str
+auto-str.o
+auto-uid
+auto-uid.o
+auto_break.o
+auto_patrn.c
+auto_patrn.o
+auto_qmail.c
+auto_qmail.o
+auto_spawn.c
+auto_spawn.o
+auto_split.c
+auto_split.o
+auto_uids.c
+auto_uids.o
+auto_usera.o
+base64.o
+bouncesaying
+bouncesaying.o
+chkspawn
+chkspawn.o
+columnt
+columnt.o
+commands.o
+compile
+condredirect
+condredirect.o
+constmap.o
+control.o
+date822fmt.o
+datetime.a
+datetime.o
+datetime_un.o
+ddist
+deferrals
+dkim.o
+dkimbase.o
+dkimsign.o
+dkimverify.o
+dns.lib
+dns.o
+dns_tlsa.o
+dnscname
+dnscname.o
+dnsfq
+dnsfq.o
+dnsip
+dnsip.o
+dnsmxip
+dnsmxip.o
+dnsptr
+dnsptr.o
+dnstlsa
+dnstlsa.o
+dnstxt
+dnstxt.o
+except
+except.o
+failures
+fastforward
+fastforward.o
+fifo.o
+find-systype
+fmtqfn.o
+forward
+forward.o
+gfrom.o
+headerbody.o
+hfield.o
+hier.o
+hmac_md5.o
+hostname
+hostname.o
+idedit
+idedit.o
+idn2.lib
+install.o
+instcheck.o
+ipalloc.o
+ipme.o
+ipmeprint
+ipmeprint.o
+libdkim.a
+load
+maildir.o
+maildir2mbox
+maildir2mbox.o
+maildirmake
+maildirmake.o
+maildirwatch
+maildirwatch.o
+mailsubj
+make-compile
+make-load
+make-makelib
+makelib
+matchup
+matchup.o
+md5c.o
+mfrules.o
+myctime.o
+newaliases
+newaliases.o
+newfield.o
+newinclude
+newinclude.o
+now.o
+predate.o
+preline
+preline.o
+printforward
+printforward.o
+printmaillist
+printmaillist.o
+prioq.o
+qbiff
+qbiff.o
+qlibs.lib
+qmail-authuser
+qmail-authuser.o
+qmail-badloadertypes
+qmail-badloadertypes.o
+qmail-badmimetypes
+qmail-badmimetypes.o
+qmail-clean
+qmail-clean.o
+qmail-dkim
+qmail-dkim.o
+qmail-dksign.o
+qmail-dkverify.o
+qmail-getpw
+qmail-getpw.o
+qmail-inject
+qmail-inject.o
+qmail-ldapam
+qmail-ldapam.o
+qmail-local
+qmail-local.o
+qmail-lspawn
+qmail-lspawn.o
+qmail-mfrules
+qmail-mfrules.o
+qmail-mrtg
+qmail-mrtg-queue
+qmail-mrtg.o
+qmail-newmrh
+qmail-newmrh.o
+qmail-newu
+qmail-newu.o
+qmail-pop3d
+qmail-pop3d.o
+qmail-popup
+qmail-popup.o
+qmail-postgrey
+qmail-postgrey.o
+qmail-pw2u
+qmail-pw2u.o
+qmail-qmaint
+qmail-qmaint.o
+qmail-qmqpc
+qmail-qmqpc.o
+qmail-qmqpd
+qmail-qmqpd.o
+qmail-qmtpd
+qmail-qmtpd.o
+qmail-qread
+qmail-qread.o
+qmail-qstat
+qmail-queue
+qmail-queue.o
+qmail-recipients
+qmail-recipients.o
+qmail-remote
+qmail-remote.o
+qmail-rspawn
+qmail-rspawn.o
+qmail-send
+qmail-send.o
+qmail-showctl
+qmail-showctl.o
+qmail-smtpam
+qmail-smtpam.o
+qmail-smtpd
+qmail-smtpd.o
+qmail-start
+qmail-start.o
+qmail-tcpok
+qmail-tcpok.o
+qmail-tcpto
+qmail-tcpto.o
+qmail-todo
+qmail-todo.o
+qmail-upq
+qmail-vmailuser
+qmail-vmailuser.o
+qmail.o
+qreceipt
+qreceipt.o
+qsutil.o
+quote.o
+rcpthosts.o
+readsubdir.o
+received.o
+recipients
+recipients.o
+rhosts
+rxdelay
+s.lib
+senders
+sendmail
+sendmail.o
+setforward
+setforward.o
+setmaillist
+setmaillist.o
+sha1.o
+sha256.o
+shadow.lib
+smtpdlog.o
+socket.lib
+spawn.o
+spf.o
+spfdnsip.o
+spfquery
+spfquery.o
+splogger
+splogger.o
+srs2.o
+srsforward.o
+srsreverse.o
+strset.o
+successes
+suids
+syslog.lib
+systype
+tai64nfrac
+tai64nfrac.o
+tcpto.o
+tcpto_clean.o
+tls_errors.o
+tls_remote.o
+tls_start.o
+tls_timeoutio.o
+token822.o
+trigger.o
+triggerpull.o
+tryrsolv.o
+trysalen.o
+wildmat.o
+xqp
+xrecipient
+xsender
+zddist
+zdeferrals
+zfailures
+zoverall
+zrecipients
+zrhosts
+zrxdelay
+zsenders
+zsendmail
+zsuccesses
+zsuids
diff --git a/src/auto-gid.c b/src/auto-gid.c
new file mode 100644
index 0000000..c5a39df
--- /dev/null
+++ b/src/auto-gid.c
@@ -0,0 +1,47 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <grp.h>
+#include "buffer.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char inbuf[256];
+buffer b = BUFFER_INIT(write,1,inbuf,sizeof(inbuf));
+
+void outs(char *s)
+{
+ if (buffer_puts(&b,s) == -1) _exit(111);
+}
+
+int main(int argc, char **argv)
+{
+ char *name;
+ char *value;
+ struct group *gr;
+ char strnum[FMT_ULONG];
+
+ name = argv[1];
+ if (!name) _exit(100);
+ value = argv[2];
+ if (!value) _exit(100);
+
+ gr = getgrnam(value);
+ if (!gr) {
+ buffer_puts(buffer_2,"fatal: unable to find group ");
+ buffer_puts(buffer_2,value);
+ buffer_puts(buffer_2,"\n");
+ buffer_flush(buffer_2);
+ _exit(111);
+ }
+
+ strnum[fmt_ulong(strnum,(unsigned long) gr->gr_gid)] = 0;
+
+ outs("int ");
+ outs(name);
+ outs(" = ");
+ outs(strnum);
+ outs(";\n");
+ if (buffer_flush(&b) == -1) _exit(111);
+ _exit(0);
+}
diff --git a/src/auto-int.c b/src/auto-int.c
new file mode 100644
index 0000000..58f44b2
--- /dev/null
+++ b/src/auto-int.c
@@ -0,0 +1,38 @@
+#include <unistd.h>
+#include "buffer.h"
+#include <unistd.h>
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char inbuf[256];
+buffer b = BUFFER_INIT(write,1,inbuf,sizeof(inbuf));
+
+void out(char *s)
+{
+ if (buffer_puts(&b,s) == -1) _exit(111);
+}
+
+int main(int argc, char **argv)
+{
+ char *name;
+ char *value;
+ unsigned long num;
+ char strnum[FMT_ULONG];
+
+ name = argv[1];
+ if (!name) _exit(100);
+ value = argv[2];
+ if (!value) _exit(100);
+
+ scan_ulong(value,&num);
+ strnum[fmt_ulong(strnum,num)] = 0;
+
+ out("int ");
+ out(name);
+ out(" = ");
+ out(strnum);
+ out(";\n");
+ if (buffer_flush(&b) == -1) _exit(111);
+ _exit(0);
+}
diff --git a/src/auto-int8.c b/src/auto-int8.c
new file mode 100644
index 0000000..fd5ead6
--- /dev/null
+++ b/src/auto-int8.c
@@ -0,0 +1,37 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char inbuf[256];
+buffer b = BUFFER_INIT(write,1,inbuf,sizeof(inbuf));
+
+void out(char *s)
+{
+ if (buffer_puts(&b,s) == -1) _exit(111);
+}
+
+int main(int argc, char **argv)
+{
+ char *name;
+ char *value;
+ unsigned long num;
+ char strnum[FMT_ULONG];
+
+ name = argv[1];
+ if (!name) _exit(100);
+ value = argv[2];
+ if (!value) _exit(100);
+
+ scan_8long(value,&num);
+ strnum[fmt_ulong(strnum,num)] = 0;
+
+ out("int ");
+ out(name);
+ out(" = ");
+ out(strnum);
+ out(";\n");
+ if (buffer_flush(&b) == -1) _exit(111);
+ _exit(0);
+}
diff --git a/src/auto-str.c b/src/auto-str.c
new file mode 100644
index 0000000..72e93bd
--- /dev/null
+++ b/src/auto-str.c
@@ -0,0 +1,41 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "exit.h"
+
+char inbuf[BUFFER_SMALL];
+buffer b = BUFFER_INIT(write,1,inbuf,sizeof(inbuf));
+
+void out(char *s)
+{
+ if (buffer_puts(&b,s) == -1) _exit(111);
+}
+
+int main(int argc, char **argv)
+{
+ char *name;
+ char *value;
+ unsigned char ch;
+ char octal[4];
+
+ name = argv[1];
+ if (!name) _exit(100);
+ value = argv[2];
+ if (!value) _exit(100);
+
+ out("char ");
+ out(name);
+ out("[] = \"\\\n");
+
+ while ((ch = *value++)) {
+ out("\\");
+ octal[3] = 0;
+ octal[2] = '0' + (ch & 7); ch >>= 3;
+ octal[1] = '0' + (ch & 7); ch >>= 3;
+ octal[0] = '0' + (ch & 7);
+ out(octal);
+ }
+
+ out("\\\n\";\n");
+ if (buffer_flush(&b) == -1) _exit(111);
+ _exit(0);
+}
diff --git a/src/auto-uid.c b/src/auto-uid.c
new file mode 100644
index 0000000..21f469b
--- /dev/null
+++ b/src/auto-uid.c
@@ -0,0 +1,47 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include "buffer.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char inbuf[256];
+buffer b = BUFFER_INIT(write,1,inbuf,sizeof(inbuf));
+
+void outs(char *s) /* was named puts, but Solaris pwd.h includes stdio.h. dorks. */
+{
+ if (buffer_puts(&b,s) == -1) _exit(111);
+}
+
+int main(int argc, char **argv)
+{
+ char *name;
+ char *value;
+ struct passwd *pw;
+ char strnum[FMT_ULONG];
+
+ name = argv[1];
+ if (!name) _exit(100);
+ value = argv[2];
+ if (!value) _exit(100);
+
+ pw = getpwnam(value);
+ if (!pw) {
+ buffer_puts(buffer_2,"fatal: unable to find user ");
+ buffer_puts(buffer_2,value);
+ buffer_puts(buffer_2,"\n");
+ buffer_flush(buffer_2);
+ _exit(111);
+ }
+
+ strnum[fmt_ulong(strnum,(unsigned long) pw->pw_uid)] = 0;
+
+ outs("int ");
+ outs(name);
+ outs(" = ");
+ outs(strnum);
+ outs(";\n");
+ if (buffer_flush(&b) == -1) _exit(111);
+ _exit(0);
+}
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 0000000..fd38fe3
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,119 @@
+#include "base64.h"
+#include "str.h"
+
+static char *b64alpha =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+#define B64PAD '='
+
+/* returns 0 ok, 1 illegal, -1 problem */
+
+int b64decode(const unsigned char *in,int l,stralloc *out)
+/* stralloc *out => not null terminated */
+{
+ int p = 0;
+ int n;
+ unsigned int x;
+ int i, j;
+ char *s;
+ unsigned char b[3];
+
+ if (l == 0) {
+ if (!stralloc_copys(out,"")) return -1;
+ return 0;
+ }
+
+ while (in[l-1] == B64PAD) {
+ p ++;
+ l--;
+ }
+
+ n = (l + p) / 4;
+ i = (n * 3) - p;
+ if (!stralloc_ready(out,i)) return -1;
+ out->len = i;
+ s = out->s;
+
+ for (i = 0; i < n - 1; i++) {
+ x = 0;
+ for (j = 0; j < 4; j++) {
+ if (in[j] >= 'A' && in[j] <= 'Z')
+ x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
+ else if (in[j] >= 'a' && in[j] <= 'z')
+ x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
+ else if (in[j] >= '0' && in[j] <= '9')
+ x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
+ else if (in[j] == '+')
+ x = (x << 6) + 62;
+ else if (in[j] == '/')
+ x = (x << 6) + 63;
+ else if (in[j] == '=')
+ x = (x << 6);
+ else return 1;
+ }
+
+ s[2] = (unsigned char)(x & 255); x >>= 8;
+ s[1] = (unsigned char)(x & 255); x >>= 8;
+ s[0] = (unsigned char)(x & 255); x >>= 8;
+ s += 3; in += 4;
+ }
+
+ x = 0;
+ for (j = 0; j < 4; j++) {
+ if (in[j] >= 'A' && in[j] <= 'Z')
+ x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
+ else if (in[j] >= 'a' && in[j] <= 'z')
+ x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
+ else if (in[j] >= '0' && in[j] <= '9')
+ x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
+ else if (in[j] == '+')
+ x = (x << 6) + 62;
+ else if (in[j] == '/')
+ x = (x << 6) + 63;
+ else if (in[j] == '=')
+ x = (x << 6);
+ else return 1;
+ }
+
+ b[2] = (unsigned char)(x & 255); x >>= 8;
+ b[1] = (unsigned char)(x & 255); x >>= 8;
+ b[0] = (unsigned char)(x & 255); x >>= 8;
+
+ for (i = 0; i < 3 - p; i++)
+ s[i] = b[i];
+
+ return 0;
+}
+
+int b64encode(stralloc *in,stralloc *out)
+{
+ unsigned char a, b, c;
+ int i;
+ char *s;
+
+ if (in->len == 0)
+ {
+ if (!stralloc_copys(out,"")) return -1;
+ return 0;
+ }
+
+ i = in->len / 3 * 4 + 4;
+ if (!stralloc_ready(out,i)) return -1;
+ s = out->s;
+
+ for (i = 0; i < in->len; i += 3) {
+ a = in->s[i];
+ b = i + 1 < in->len ? in->s[i + 1] : 0;
+ c = i + 2 < in->len ? in->s[i + 2] : 0;
+
+ *s++ = b64alpha[a >> 2];
+ *s++ = b64alpha[((a & 3 ) << 4) | (b >> 4)];
+
+ if (i + 1 >= in->len) *s++ = B64PAD;
+ else *s++ = b64alpha[((b & 0x0f) << 2) | (c >> 6)];
+
+ if (i + 2 >= in->len) *s++ = B64PAD;
+ else *s++ = b64alpha[c & 0x3f];
+ }
+ out->len = s - out->s;
+ return 0;
+}
diff --git a/src/bouncesaying.c b/src/bouncesaying.c
new file mode 100644
index 0000000..416d76d
--- /dev/null
+++ b/src/bouncesaying.c
@@ -0,0 +1,38 @@
+#include <unistd.h>
+#include "logmsg.h"
+#include "wait.h"
+#include "sig.h"
+#include "exit.h"
+
+#define WHO "bouncesaying"
+
+int main(int argc,char **argv)
+{
+ int pid;
+ int wstat;
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"bouncesaying error [ program [ arg ... ] ]");
+
+ if (argv[2]) {
+ pid = fork();
+ if (pid == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ if (pid == 0) {
+ execvp(argv[2],argv + 2);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+ if (wait_pid(&wstat,pid) == -1)
+ logmsg(WHO,111,FATAL,"wait failed");
+ if (wait_crashed(wstat))
+ logmsg(WHO,111,FATAL,"child crashed");
+ switch (wait_exitcode(wstat)) {
+ case 0: break;
+ case 111: logmsg(WHO,111,FATAL,"temporary child error");
+ default: _exit(0);
+ }
+ }
+
+ logmsg(WHO,100,LOG,argv[1]);
+}
diff --git a/src/chkshsgr.c b/src/chkshsgr.c
new file mode 100644
index 0000000..fc752bd
--- /dev/null
+++ b/src/chkshsgr.c
@@ -0,0 +1,13 @@
+#include <grp.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "exit.h"
+
+int main()
+{
+ short x[4];
+
+ x[0] = x[1] = 0;
+ if (getgroups(1,x) == 0) if (setgroups(1,x) == -1) _exit(1);
+ _exit(0);
+}
diff --git a/src/chkspawn.c b/src/chkspawn.c
new file mode 100644
index 0000000..511489a
--- /dev/null
+++ b/src/chkspawn.c
@@ -0,0 +1,48 @@
+#include "buffer.h"
+#include "fmt.h"
+#include "select.h"
+#include "exit.h"
+#include "auto_spawn.h"
+#define MAXSPAWN 1000 /* Silent spawn limit increased to 1000 */
+
+char num[FMT_ULONG];
+fd_set fds;
+
+int main()
+{
+ unsigned long hiddenlimit;
+ unsigned long maxnumd;
+
+ hiddenlimit = sizeof(fds) * 8;
+ maxnumd = (hiddenlimit - 5) / 2;
+
+ if (auto_spawn < 1) {
+ buffer_puts(buffer_2,"Oops. You have set conf-spawn lower than 1.\n");
+ buffer_flush(buffer_2);
+ _exit(1);
+ }
+
+ if (auto_spawn > MAXSPAWN) {
+ buffer_puts(buffer_2,"Oops. You have set conf-spawn higher than MAXSPAWN.\n");
+ buffer_flush(buffer_2);
+ _exit(1);
+ }
+
+ if (auto_spawn > maxnumd) {
+ buffer_puts(buffer_2,"Oops. Your system's FD_SET() has a hidden limit of ");
+ buffer_put(buffer_2,num,fmt_ulong(num,hiddenlimit));
+ buffer_puts(buffer_2," descriptors.\n\
+This means that the qmail daemons could crash if you set the run-time\n\
+concurrency higher than ");
+ buffer_put(buffer_2,num,fmt_ulong(num,maxnumd));
+ buffer_puts(buffer_2,". So I'm going to insist that the concurrency\n\
+limit in conf-spawn be at most ");
+ buffer_put(buffer_2,num,fmt_ulong(num,maxnumd));
+ buffer_puts(buffer_2,". Right now it's ");
+ buffer_put(buffer_2,num,fmt_ulong(num,(unsigned long) auto_spawn));
+ buffer_puts(buffer_2,".\n");
+ buffer_flush(buffer_2);
+ _exit(1);
+ }
+ _exit(0);
+}
diff --git a/src/columnt.c b/src/columnt.c
new file mode 100644
index 0000000..9e4cf0e
--- /dev/null
+++ b/src/columnt.c
@@ -0,0 +1,104 @@
+#include <unistd.h>
+#include "alloc.h"
+#include "logmsg.h"
+#include "buffer.h"
+#include "stralloc.h"
+#include "exit.h"
+#include "readclose.h"
+
+#define WHO "columnt"
+
+#define BSIZE 4096
+
+char outbuf[BSIZE];
+buffer bo = BUFFER_INIT(write,1,outbuf,sizeof(outbuf));
+
+void nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+void die_read() { logmsg(WHO,110,ERROR,"unable to read input: "); }
+void die_write() { logmsg(WHO,110,ERROR,"unable to write output: "); }
+
+stralloc file = {0};
+int *width;
+int maxfield = 0;
+
+void nothing()
+{
+ ;
+}
+
+void printline()
+{
+ if (buffer_put(&bo,"\n",1) == -1) die_write();
+}
+
+void maxfield_check(int fieldnum,char *buf,int len)
+{
+ if (fieldnum > maxfield) maxfield = fieldnum;
+}
+
+void width_check(int fieldnum,char *buf,int len)
+{
+ if (len > width[fieldnum]) width[fieldnum] = len;
+}
+
+void width_init()
+{
+ int i;
+
+ width = (int *) alloc((maxfield + 1) * sizeof(int));
+ if (!width) nomem();
+ for (i = 0; i <= maxfield; ++i)
+ width[i] = 0;
+}
+
+void printfield(int fieldnum,char *buf,int len)
+{
+ int i;
+
+ if (fieldnum < maxfield)
+ for (i = len; i < width[fieldnum]; ++i)
+ if (buffer_put(&bo," ",1) == -1) die_write();
+
+ if (buffer_put(&bo,buf,len) == -1) die_write();
+
+ if (fieldnum < maxfield)
+ if (buffer_put(&bo," ",2) == -1) die_write();
+}
+
+void split(void (*dofield)(), void (*doline)())
+{
+ int i;
+ int j;
+ int fieldpos;
+ int fieldnum;
+
+ for (j = i = 0; j < file.len; ++j)
+ if (file.s[j] == '\n') {
+ fieldnum = 0;
+ for (;;) {
+ while ((file.s[i] == ' ') || (file.s[i] == '\t')) ++i;
+ if (i == j) break;
+ fieldpos = i;
+ while ((file.s[i] != ' ') && (file.s[i] != '\t') && (file.s[i] != '\n')) ++i;
+ dofield(fieldnum++,file.s + fieldpos,i - fieldpos);
+ }
+ doline();
+ i = j + 1;
+ }
+}
+
+int main()
+{
+ if (readclose_append(0,&file,BSIZE) == -1) die_read();
+ if (!file.len) _exit(0);
+ if (file.s[file.len - 1] != '\n')
+ if (!stralloc_append(&file,"\n")) nomem();
+
+ split(maxfield_check,nothing);
+ width_init();
+ split(width_check,nothing);
+ split(printfield,printline);
+
+ if (buffer_flush(&bo) == -1) die_write();
+ _exit(0);
+}
diff --git a/src/commands.c b/src/commands.c
new file mode 100644
index 0000000..8602f7c
--- /dev/null
+++ b/src/commands.c
@@ -0,0 +1,40 @@
+#include "commands.h"
+#include "buffer.h"
+#include "stralloc.h"
+#include "str.h"
+#include "case.h"
+
+static stralloc cmd = {0};
+
+int commands(buffer *b,struct commands *c)
+{
+ int i;
+ char *arg;
+
+ for (;;) {
+ if (!stralloc_copys(&cmd,"")) return -1;
+
+ for (;;) {
+ if (!stralloc_readyplus(&cmd,1)) return -1;
+ i = buffer_get(b,cmd.s + cmd.len,1);
+ if (i != 1) return i;
+ if (cmd.s[cmd.len] == '\n') break;
+ ++cmd.len;
+ }
+
+ if (cmd.len > 0) if (cmd.s[cmd.len - 1] == '\r') --cmd.len;
+
+ cmd.s[cmd.len] = 0;
+
+ i = str_chr(cmd.s,' ');
+ arg = cmd.s + i;
+ while (*arg == ' ') ++arg;
+ cmd.s[i] = 0;
+
+ for (i = 0; c[i].text; ++i)
+ if (case_equals(c[i].text,cmd.s)) break;
+
+ c[i].fun(arg);
+ if (c[i].flush) c[i].flush();
+ }
+}
diff --git a/src/condredirect.c b/src/condredirect.c
new file mode 100644
index 0000000..6f2d89a
--- /dev/null
+++ b/src/condredirect.c
@@ -0,0 +1,81 @@
+#include <unistd.h>
+#include "sig.h"
+#include "exit.h"
+#include "env.h"
+#include "logmsg.h"
+#include "wait.h"
+#include "seek.h"
+#include "qmail.h"
+#include "buffer.h"
+#include "fmt.h"
+
+#define WHO "condredirect"
+
+struct qmail qqt;
+
+ssize_t mywrite(int fd,char *buf,int len)
+{
+ qmail_put(&qqt,buf,len);
+ return len;
+}
+
+char inbuf[BUFFER_INSIZE];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+char outbuf[1];
+buffer bo = BUFFER_INIT(mywrite,-1,outbuf,sizeof(outbuf));
+
+char num[FMT_ULONG];
+
+int main(int argc,char **argv)
+{
+ char *sender;
+ char *dtline;
+ int pid;
+ int wstat;
+ char *qqx;
+
+ if (!argv[1] || !argv[2])
+ logmsg(WHO,100,USAGE,"condredirect newaddress program [ arg ... ]");
+
+ pid = fork();
+ if (pid == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ if (pid == 0) {
+ execvp(argv[2],argv + 2);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+ if (wait_pid(&wstat,pid) == -1)
+ logmsg(WHO,111,FATAL,"wait failed");
+ if (wait_crashed(wstat))
+ logmsg(WHO,111,FATAL,"child crashed");
+ switch (wait_exitcode(wstat)) {
+ case 0: break;
+ case 111: logmsg(WHO,111,FATAL,"temporary child error");
+ default: _exit(0);
+ }
+
+ if (seek_begin(0) == -1)
+ logmsg(WHO,111,FATAL,"unable to rewind: ");
+ sig_pipeignore();
+
+ sender = env_get("SENDER");
+ if (!sender) logmsg(WHO,100,ERROR,"SENDER not set");
+ dtline = env_get("DTLINE");
+ if (!dtline) logmsg(WHO,100,ERROR,"DTLINE not set");
+
+ if (qmail_open(&qqt) == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ qmail_puts(&qqt,dtline);
+ if (buffer_copy(&bo,&bi) != 0)
+ logmsg(WHO,111,FATAL,"unable to read message: ");
+ buffer_flush(&bo);
+
+ num[fmt_ulong(num,qmail_qp(&qqt))] = 0;
+
+ qmail_from(&qqt,sender);
+ qmail_to(&qqt,argv[1]);
+ qqx = qmail_close(&qqt);
+ if (*qqx) logmsg(WHO,*qqx == 'D' ? 100 : 111,FATAL,qqx + 1);
+ logmsg(WHO,0,LOG,B("qp ",num));
+}
diff --git a/src/config-fast.sh b/src/config-fast.sh
new file mode 100755
index 0000000..f41796c
--- /dev/null
+++ b/src/config-fast.sh
@@ -0,0 +1,35 @@
+fqdn="$1"
+echo Your fully qualified host name is "$fqdn".
+
+echo Putting "$fqdn" into control/me...
+echo "$fqdn" > HOME/control/me
+chmod 644 HOME/control/me
+
+( echo "$fqdn" | sed 's/^\([^\.]*\)\.\([^\.]*\)\./\2\./' | (
+ read ddom
+ echo Putting "$ddom" into control/defaultdomain...
+ echo "$ddom" > HOME/control/defaultdomain
+ chmod 644 HOME/control/defaultdomain
+) )
+
+( echo "$fqdn" | sed 's/^.*\.\([^\.]*\)\.\([^\.]*\)$/\1.\2/' | (
+ read pdom
+ echo Putting "$pdom" into control/plusdomain...
+ echo "$pdom" > HOME/control/plusdomain
+ chmod 644 HOME/control/plusdomain
+) )
+
+echo Putting "$fqdn" into control/locals...
+echo "$fqdn" >> HOME/control/locals
+chmod 644 HOME/control/locals
+
+echo Putting "$fqdn" into control/rcpthosts...
+echo "$fqdn" >> HOME/control/rcpthosts
+chmod 644 HOME/control/rcpthosts
+echo "Now qmail will refuse to accept SMTP messages except to $fqdn."
+echo 'Make sure to change rcpthosts if you add hosts to locals or virtualdomains!'
+
+echo Enabling TLS "*:" into control/tlsdestinations ...
+echo "*:" >> HOME/control/tlsdestinations
+chmod 644 HOME/control/tlsdestinations
+echo "Now qmail-remote will send TLS encrypted mails to enabled destinations."
diff --git a/src/config.sh b/src/config.sh
new file mode 100755
index 0000000..a2ecd25
--- /dev/null
+++ b/src/config.sh
@@ -0,0 +1,64 @@
+./hostname | tr '[A-Z]' '[a-z]' | (
+ if read host
+ then
+ echo Your hostname is "$host".
+ ./dnsfq "$host" | tr '[A-Z]' '[a-z]' | (
+ if read fqdn
+ then
+ echo Your host\'s fully qualified name in DNS is "$fqdn".
+ echo Putting "$fqdn" into control/me...
+ echo "$fqdn" > HOME/control/me
+ chmod 644 HOME/control/me
+ ( echo "$fqdn" | sed 's/^\([^\.]*\)\.\([^\.]*\)\./\2\./' | (
+ read ddom
+ echo Putting "$ddom" into control/defaultdomain...
+ echo "$ddom" > HOME/control/defaultdomain
+ chmod 644 HOME/control/defaultdomain
+ ) )
+ ( echo "$fqdn" | sed 's/^.*\.\([^\.]*\)\.\([^\.]*\)$/\1.\2/' | (
+ read pdom
+ echo Putting "$pdom" into control/plusdomain...
+ echo "$pdom" > HOME/control/plusdomain
+ chmod 644 HOME/control/plusdomain
+ ) )
+ echo ' '
+ echo Checking local IP addresses:
+ : > HOME/control/locals
+ chmod 644 HOME/control/locals
+ ( ./dnsip "$fqdn"
+ ./ipmeprint ) | sort -u | \
+ (
+ while read localip
+ do
+ echo "$localip: " | tr -d '\012'
+ ./dnsptr "$localip" 2>/dev/null | (
+ if read local
+ then
+ echo Adding "$local" to control/locals...
+ echo "$local" >> HOME/control/locals
+ else
+ echo PTR lookup failed. I assume this address has no DNS name.
+ fi
+ )
+ done
+ )
+ echo ' '
+ echo If there are any other domain names that point to you,
+ echo you will have to add them to HOME/control/locals.
+ echo You don\'t have to worry about aliases, i.e., domains with CNAME records.
+ echo ' '
+ echo Copying HOME/control/locals to HOME/control/rcpthosts...
+ cp HOME/control/locals HOME/control/rcpthosts
+ chmod 644 HOME/control/rcpthosts
+ echo 'Now qmail will refuse to accept SMTP messages except to those hosts.'
+ echo 'Make sure to change rcpthosts if you add hosts to locals or virtualdomains!'
+ else
+ echo Sorry, I couldn\'t find your host\'s canonical name in DNS.
+ echo You will have to set up control/me yourself.
+ fi
+ )
+ else
+ echo Sorry, I couldn\'t find your hostname.
+ echo You will have to set up control/me yourself.
+ fi
+)
diff --git a/src/constmap.c b/src/constmap.c
new file mode 100644
index 0000000..ea153ea
--- /dev/null
+++ b/src/constmap.c
@@ -0,0 +1,168 @@
+#include "constmap.h"
+#include "alloc.h"
+#include "case.h"
+
+static constmap_hash hash(char *s,int len)
+{
+ unsigned char ch;
+ constmap_hash h;
+ h = 5381;
+ while (len > 0) {
+ ch = *s++ - 'A';
+ if (ch <= 'Z' - 'A') ch += 'a' - 'A';
+ h = ((h << 5) + h) ^ ch;
+ --len;
+ }
+ return h;
+}
+
+char *constmap(struct constmap *cm,char *s,int len)
+{
+ constmap_hash h;
+ int pos;
+ h = hash(s,len);
+ pos = cm->first[h & cm->mask];
+ while (pos != -1) {
+ if (h == cm->hash[pos])
+ if (len == cm->inputlen[pos])
+ if (!case_diffb(cm->input[pos],len,s))
+ return cm->input[pos] + cm->inputlen[pos] + 1;
+ pos = cm->next[pos];
+ }
+ return 0;
+}
+
+int constmap_init(struct constmap *cm,char *s,int len,int flagcolon)
+{
+ int i;
+ int j;
+ int k;
+ int pos;
+ constmap_hash h;
+
+ cm->num = 0;
+ for (j = 0; j < len; ++j) if (!s[j]) ++cm->num;
+
+ h = 64;
+ while (h && (h < cm->num)) h += h;
+ cm->mask = h - 1;
+
+ cm->first = (int *) alloc(sizeof(int) * h);
+ if (cm->first) {
+ cm->input = (char **) alloc(sizeof(char *) * cm->num);
+ if (cm->input) {
+ cm->inputlen = (int *) alloc(sizeof(int) * cm->num);
+ if (cm->inputlen) {
+ cm->hash = (constmap_hash *) alloc(sizeof(constmap_hash) * cm->num);
+ if (cm->hash) {
+ cm->next = (int *) alloc(sizeof(int) * cm->num);
+ if (cm->next) {
+ for (h = 0; h <= cm->mask; ++h)
+ cm->first[h] = -1;
+ pos = 0;
+ i = 0;
+ for (j = 0; j < len; ++j)
+ if (!s[j]) {
+ k = j - i;
+ if (flagcolon) {
+ for (k = i; k < j; ++k)
+ if (s[k] == ':') break;
+ if (k >= j) { i = j + 1; continue; }
+ k -= i;
+ }
+ cm->input[pos] = s + i;
+ cm->inputlen[pos] = k;
+ h = hash(s + i,k);
+ cm->hash[pos] = h;
+ h &= cm->mask;
+ cm->next[pos] = cm->first[h];
+ cm->first[h] = pos;
+ ++pos;
+ i = j + 1;
+ }
+ return 1;
+ }
+ alloc_free(cm->hash);
+ }
+ alloc_free(cm->inputlen);
+ }
+ alloc_free(cm->input);
+ }
+ alloc_free(cm->first);
+ }
+ return 0;
+}
+
+int constmap_init_char(struct constmap *cm,char *s,int len,int flagcolon,char flagchar)
+{
+ int i;
+ int j;
+ int k;
+ int pos;
+ constmap_hash h;
+
+ if (!flagchar || flagchar == 0 || flagchar == '\0') {
+ flagchar = ':';
+ }
+
+ cm->num = 0;
+ for (j = 0; j < len; ++j) if (!s[j]) ++cm->num;
+
+ h = 64;
+ while (h && (h < cm->num)) h += h;
+ cm->mask = h - 1;
+
+ cm->first = (int *) alloc(sizeof(int) * h);
+ if (cm->first) {
+ cm->input = (char **) alloc(sizeof(char *) * cm->num);
+ if (cm->input) {
+ cm->inputlen = (int *) alloc(sizeof(int) * cm->num);
+ if (cm->inputlen) {
+ cm->hash = (constmap_hash *) alloc(sizeof(constmap_hash) * cm->num);
+ if (cm->hash) {
+ cm->next = (int *) alloc(sizeof(int) * cm->num);
+ if (cm->next) {
+ for (h = 0; h <= cm->mask; ++h)
+ cm->first[h] = -1;
+ pos = 0;
+ i = 0;
+ for (j = 0; j < len; ++j)
+ if (!s[j]) {
+ k = j - i;
+ if (flagcolon) {
+ for (k = i; k < j; ++k)
+ if (s[k] == flagchar) break;
+ if (k >= j) { i = j + 1; continue; }
+ k -= i;
+ }
+ cm->input[pos] = s + i;
+ cm->inputlen[pos] = k;
+ h = hash(s + i,k);
+ cm->hash[pos] = h;
+ h &= cm->mask;
+ cm->next[pos] = cm->first[h];
+ cm->first[h] = pos;
+ ++pos;
+ i = j + 1;
+ }
+ return 1;
+ }
+ alloc_free(cm->hash);
+ }
+ alloc_free(cm->inputlen);
+ }
+ alloc_free(cm->input);
+ }
+ alloc_free(cm->first);
+ }
+ return 0;
+}
+
+void constmap_free(struct constmap *cm)
+{
+ alloc_free(cm->next);
+ alloc_free(cm->hash);
+ alloc_free(cm->inputlen);
+ alloc_free(cm->input);
+ alloc_free(cm->first);
+}
diff --git a/src/control.c b/src/control.c
new file mode 100644
index 0000000..2558225
--- /dev/null
+++ b/src/control.c
@@ -0,0 +1,122 @@
+#include <unistd.h>
+#include "open.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "logmsg.h"
+#include "control.h"
+#include "alloc.h"
+#include "scan.h"
+#include "error.h"
+
+static char inbuf[2048];
+static stralloc line = {0};
+static stralloc me = {0};
+static int meok = 0;
+
+/** @file control.c
+*/
+
+static void striptrailingwhitespace(stralloc *sa)
+{
+ while (sa->len > 0)
+ switch (sa->s[sa->len - 1]) {
+ case '\n': case ' ': case '\t':
+ --sa->len;
+ break;
+ default:
+ return;
+ }
+}
+
+int control_init(void)
+{
+ int r;
+
+ r = control_readline(&me,"control/me");
+ if (r == 1) meok = 1;
+ return r;
+}
+
+int control_rldef(stralloc *sa,char *fn,int flagme,char *def)
+{
+ int r;
+
+ r = control_readline(sa,fn);
+ if (r) return r;
+ if (flagme) if (meok) return stralloc_copy(sa,&me) ? 1 : -1;
+ if (def) return stralloc_copys(sa,def) ? 1 : -1;
+ return r;
+}
+
+int control_readline(stralloc *sa,char *fn)
+{
+ buffer b;
+ int fd;
+ int match;
+
+ fd = open_read(fn);
+ if (fd == -1) { if (errno == ENOENT) return 0; return -1; }
+
+ buffer_init(&b,read,fd,inbuf,sizeof(inbuf));
+
+ if (getln(&b,sa,&match,'\n') == -1) { close(fd); return -1; }
+
+ striptrailingwhitespace(sa);
+
+ close(fd);
+ return 1;
+}
+
+int control_readint(int *i,char *fn)
+{
+ unsigned long u;
+
+ switch (control_readline(&line,fn)) {
+ case 0: return 0;
+ case -1: return -1;
+ }
+ if (!stralloc_0(&line)) return -1;
+ if (!scan_ulong(line.s,&u)) return 0;
+ *i = u;
+
+ return 1;
+}
+
+int control_readfile(stralloc *sa,char *fn,int flagme)
+{
+ buffer b;
+ int fd;
+ int match;
+
+ if (!stralloc_copys(sa,"")) return -1;
+
+ fd = open_read(fn);
+ if (fd == -1) {
+ if (errno == ENOENT) {
+ if (flagme && meok) {
+ if (!stralloc_copy(sa,&me)) return -1;
+ if (!stralloc_0(sa)) return -1;
+ return 1;
+ }
+ return 0;
+ }
+ return -1;
+ }
+
+ buffer_init(&b,read,fd,inbuf,sizeof(inbuf));
+
+ for (;;) {
+ if (getln(&b,&line,&match,'\n') == -1) break;
+ if (!match && !line.len) { close(fd); return 1; }
+ striptrailingwhitespace(&line);
+ if (!stralloc_0(&line)) break;
+ if (line.s[0])
+ if (line.s[0] != '#')
+ if (!stralloc_cat(sa,&line)) break;
+ if (!match) { close(fd); return 1; }
+ }
+
+ close(fd);
+ return -1;
+}
diff --git a/src/crypt.lib b/src/crypt.lib
new file mode 100644
index 0000000..2fd0d0c
--- /dev/null
+++ b/src/crypt.lib
@@ -0,0 +1 @@
+-lcrypt
diff --git a/src/date822fmt.c b/src/date822fmt.c
new file mode 100644
index 0000000..fc2d1f7
--- /dev/null
+++ b/src/date822fmt.c
@@ -0,0 +1,29 @@
+#include "datetime.h"
+#include "fmt.h"
+#include "date822fmt.h"
+
+static char *montab[12] = {
+"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+unsigned int date822fmt(char *s,struct datetime *dt)
+{
+ unsigned int i;
+ unsigned int len;
+
+ len = 0;
+ i = fmt_uint(s,dt->mday); len += i; if (s) s += i;
+ i = fmt_str(s," "); len += i; if (s) s += i;
+ i = fmt_str(s,montab[dt->mon]); len += i; if (s) s += i;
+ i = fmt_str(s," "); len += i; if (s) s += i;
+ i = fmt_uint(s,dt->year + 1900); len += i; if (s) s += i;
+ i = fmt_str(s," "); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt->hour,2); len += i; if (s) s += i;
+ i = fmt_str(s,":"); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt->min,2); len += i; if (s) s += i;
+ i = fmt_str(s,":"); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt->sec,2); len += i; if (s) s += i;
+ i = fmt_str(s," -0000\n"); len += i; if (s) s += i;
+
+ return len;
+}
diff --git a/src/datemail.sh b/src/datemail.sh
new file mode 100755
index 0000000..66ea257
--- /dev/null
+++ b/src/datemail.sh
@@ -0,0 +1 @@
+exec HOME/bin/predate HOME/bin/sendmail ${1+"$@"}
diff --git a/src/datetime.c b/src/datetime.c
new file mode 100644
index 0000000..3db5f7f
--- /dev/null
+++ b/src/datetime.c
@@ -0,0 +1,53 @@
+/* 19950925 */
+#include "datetime.h"
+
+void datetime_tai(struct datetime *dt,datetime_sec t)
+{
+ int day;
+ int tod;
+ int year;
+ int yday;
+ int wday;
+ int mon;
+
+ tod = t % 86400;
+ day = t / 86400;
+ if (tod < 0) { tod += 86400; --day; }
+
+ dt->hour = tod / 3600;
+ tod %= 3600;
+ dt->min = tod / 60;
+ dt->sec = tod % 60;
+
+ wday = (day + 4) % 7; if (wday < 0) wday += 7;
+ dt->wday = wday;
+
+ day -= 11017;
+ /* day 0 is march 1, 2000 */
+ year = 5 + day / 146097;
+ day = day % 146097; if (day < 0) { day += 146097; --year; }
+ /* from now on, day is nonnegative */
+ year *= 4;
+ if (day == 146096) { year += 3; day = 36524; }
+ else { year += day / 36524; day %= 36524; }
+ year *= 25;
+ year += day / 1461;
+ day %= 1461;
+ year *= 4;
+ yday = (day < 306);
+ if (day == 1460) { year += 3; day = 365; }
+ else { year += day / 365; day %= 365; }
+ yday += day;
+
+ day *= 10;
+ mon = (day + 5) / 306;
+ day = day + 5 - 306 * mon;
+ day /= 10;
+ if (mon >= 10) { yday -= 306; ++year; mon -= 10; }
+ else { yday += 59; mon += 2; }
+
+ dt->yday = yday;
+ dt->year = year - 1900;
+ dt->mon = mon;
+ dt->mday = day + 1;
+}
diff --git a/src/datetime_un.c b/src/datetime_un.c
new file mode 100644
index 0000000..e84806d
--- /dev/null
+++ b/src/datetime_un.c
@@ -0,0 +1,35 @@
+#include "datetime.h"
+
+/* roughly 100x faster than mktime() */
+
+datetime_sec datetime_untai(struct datetime *dt)
+{
+ int year;
+ int day;
+ int mon;
+
+ year = dt->year + 1900;
+
+ mon = dt->mon;
+ if (mon >= 2) { mon -= 2; }
+ else { mon += 10; --year; }
+
+ day = (dt->mday - 1) * 10 + 5 + 306 * mon;
+ day /= 10;
+
+ if (day == 365) { year -= 3; day = 1460; }
+ else { day += 365 * (year % 4); }
+ year /= 4;
+
+ day += 1461 * (year % 25);
+ year /= 25;
+
+ if (day == 36524) { year -= 3; day = 146096; }
+ else { day += 36524 * (year % 4); }
+ year /= 4;
+
+ day += 146097 * (year - 5);
+ day += 11017;
+
+ return ((day * 24 + dt->hour) * 60 + dt->min) * 60 + dt->sec;
+}
diff --git a/src/ddist.sh b/src/ddist.sh
new file mode 100644
index 0000000..b632572
--- /dev/null
+++ b/src/ddist.sh
@@ -0,0 +1,31 @@
+
+awk '/^d k/ { print $5 - $3 }' \
+| sort -n \
+| awk '
+ { x += 1; cumulative[$1] = x }
+ END {
+ if (x > 0) {
+ for (p = 0;p <= 100;++p) mindel[p] = -1
+ for (d in cumulative) {
+ p = int((cumulative[d] * 100) / x)
+ if (mindel[p] == -1) mindel[p] = d
+ else if (d < mindel[p]) mindel[p] = d
+ totdel[p] += d
+ numdel[p] += 1
+ }
+ td = 0
+ nd = 0
+ for (p = 0;p <= 100;++p) {
+ td += totdel[p]
+ nd += numdel[p]
+ if (p >= 10)
+ if (nd > 0)
+ if (mindel[p] >= 0) {
+ str1 = sprintf("%.2f",mindel[p])
+ str2 = sprintf("%.2f",td / nd)
+ print str1, str2, p
+ }
+ }
+ }
+ }
+'
diff --git a/src/deferrals.sh b/src/deferrals.sh
new file mode 100644
index 0000000..84b19f6
--- /dev/null
+++ b/src/deferrals.sh
@@ -0,0 +1,14 @@
+
+awk '
+ /^d z/ {
+ reason = $11
+ temp[reason] += 1
+ xdelay[reason] += $5 - $4
+ }
+ END {
+ for (reason in temp) {
+ str = sprintf("%.2f",xdelay[reason])
+ print temp[reason],str,reason
+ }
+ }
+'
diff --git a/src/direntry.h1 b/src/direntry.h1
new file mode 100644
index 0000000..f737676
--- /dev/null
+++ b/src/direntry.h1
@@ -0,0 +1,8 @@
+#ifndef DIRENTRY_H
+#define DIRENTRY_H
+
+#include <sys/types.h>
+#include <sys/dir.h>
+#define direntry struct direct
+
+#endif
diff --git a/src/direntry.h2 b/src/direntry.h2
new file mode 100644
index 0000000..0302ebe
--- /dev/null
+++ b/src/direntry.h2
@@ -0,0 +1,8 @@
+#ifndef DIRENTRY_H
+#define DIRENTRY_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#define direntry struct dirent
+
+#endif
diff --git a/src/dkim.cpp b/src/dkim.cpp
new file mode 100644
index 0000000..8f36644
--- /dev/null
+++ b/src/dkim.cpp
@@ -0,0 +1,197 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#include <string.h>
+#include "dkim.h"
+#include "dkimsign.h"
+#include "dkimverify.h"
+
+#define DKIMID ('D' | 'K'<<8 | 'I'<<16 | 'M'<<24)
+/* taken from removed file "ressource.h" */
+#ifdef VERSION
+#define VERSION_STRING VERSION
+#else
+#define VERSION_STRING "1.4.0"
+#endif
+
+static void InitContext(DKIMContext* pContext,bool bSign,void* pObject)
+{
+ pContext->reserved1 = DKIMID;
+ pContext->reserved2 = bSign ? 1 : 0;
+ pContext->reserved3 = pObject;
+}
+
+static void* ValidateContext(DKIMContext* pContext,bool bSign)
+{
+ if (pContext->reserved1 != DKIMID)
+ return NULL;
+
+ if (pContext->reserved2 != (unsigned int)(bSign ? 1 : 0))
+ return NULL;
+
+ return pContext->reserved3;
+}
+
+int DKIM_CALL DKIMSignInit(DKIMContext* pSignContext,DKIMSignOptions* pOptions)
+{
+ int nRet = DKIM_OUT_OF_MEMORY;
+
+ CDKIMSign* pSign = new CDKIMSign;
+
+ if (pSign) {
+ nRet = pSign->Init(pOptions);
+ if (nRet != DKIM_SUCCESS)
+ delete pSign;
+ }
+
+ if (nRet == DKIM_SUCCESS) { InitContext(pSignContext,true,pSign); }
+ return nRet;
+}
+
+int DKIM_CALL DKIMSignProcess(DKIMContext* pSignContext,char* szBuffer,int nBufLength)
+{
+ CDKIMSign* pSign = (CDKIMSign*)ValidateContext(pSignContext,true);
+
+ if (pSign) { return pSign->Process(szBuffer,nBufLength,false); }
+ return DKIM_INVALID_CONTEXT;
+}
+
+int DKIM_CALL DKIMSignGetSig2(DKIMContext* pSignContext,char* szRSAPrivKey,char* szECCPrivKey,char** pszSignature)
+{
+ CDKIMSign* pSign = (CDKIMSign*)ValidateContext(pSignContext,true);
+
+ if (pSign) { return pSign->GetSig2(szRSAPrivKey,szECCPrivKey,pszSignature); }
+ return DKIM_INVALID_CONTEXT;
+}
+
+void DKIM_CALL DKIMSignFree(DKIMContext* pSignContext)
+{
+ CDKIMSign* pSign = (CDKIMSign*)ValidateContext(pSignContext,true);
+
+ if (pSign) {
+ delete pSign;
+ pSignContext->reserved3 = NULL;
+ }
+}
+
+int DKIM_CALL DKIMVerifyInit(DKIMContext* pVerifyContext,DKIMVerifyOptions* pOptions)
+{
+ int nRet = DKIM_OUT_OF_MEMORY;
+
+ CDKIMVerify* pVerify = new CDKIMVerify;
+
+ if (pVerify) {
+ nRet = pVerify->Init(pOptions);
+ if (nRet != DKIM_SUCCESS)
+ delete pVerify;
+ }
+
+ if (nRet == DKIM_SUCCESS) {
+ InitContext(pVerifyContext,false,pVerify);
+ }
+
+ return nRet;
+}
+
+
+int DKIM_CALL DKIMVerifyProcess(DKIMContext* pVerifyContext,const char* const szBuffer,int nBufLength)
+{
+ CDKIMVerify* pVerify = (CDKIMVerify*)ValidateContext(pVerifyContext,false);
+
+ if (pVerify) {
+ return pVerify->Process(szBuffer,nBufLength,false);
+ }
+
+ return DKIM_INVALID_CONTEXT;
+}
+
+int DKIM_CALL DKIMVerifyResults(DKIMContext* pVerifyContext)
+{
+ CDKIMVerify* pVerify = (CDKIMVerify*)ValidateContext(pVerifyContext,false);
+
+ if (pVerify) {
+ return pVerify->GetResults();
+ }
+ return DKIM_INVALID_CONTEXT;
+}
+
+int DKIM_CALL DKIMVerifyGetDetails(DKIMContext* pVerifyContext,int* nSigCount,DKIMVerifyDetails** pDetails,char* szPractices)
+{
+ szPractices[0] = '\0';
+
+ CDKIMVerify* pVerify = (CDKIMVerify*)ValidateContext(pVerifyContext,false);
+
+ if (pVerify) {
+ strcpy(szPractices,pVerify->GetPractices());
+ return pVerify->GetDetails(nSigCount,pDetails);
+ }
+
+ return DKIM_INVALID_CONTEXT;
+}
+
+
+void DKIM_CALL DKIMVerifyFree(DKIMContext* pVerifyContext)
+{
+ CDKIMVerify* pVerify = (CDKIMVerify*)ValidateContext(pVerifyContext,false);
+
+ if (pVerify) {
+ delete pVerify;
+ pVerifyContext->reserved3 = NULL;
+ }
+}
+
+const char* DKIM_CALL DKIMVersion()
+{
+ return VERSION_STRING;
+}
+
+static const char* DKIMErrorStrings[-1-DKIM_MAX_ERROR] = {
+ "DKIM_FAIL",
+ "DKIM_BAD_SYNTAX",
+ "DKIM_SIGNATURE_BAD",
+ "DKIM_SIGNATURE_BAD_BUT_TESTING",
+ "DKIM_SIGNATURE_EXPIRED",
+ "DKIM_SELECTOR_INVALID",
+ "DKIM_SELECTOR_GRANULARITY_MISMATCH",
+ "DKIM_SELECTOR_KEY_REVOKED",
+ "DKIM_SELECTOR_DOMAIN_NAME_TOO_LONG",
+ "DKIM_SELECTOR_DNS_TEMP_FAILURE",
+ "DKIM_SELECTOR_DNS_PERM_FAILURE",
+ "DKIM_SELECTOR_PUBLIC_KEY_INVALID",
+ "DKIM_NO_SIGNATURES",
+ "DKIM_NO_VALID_SIGNATURES",
+ "DKIM_BODY_HASH_MISMATCH",
+ "DKIM_SELECTOR_ALGORITHM_MISMATCH",
+ "DKIM_STAT_INCOMPAT",
+ "DKIM_UNSIGNED_FROM",
+ "DKIM_OUT_OF_MEMORY",
+ "DKIM_INVALID_CONTEXT",
+ "DKIM_NO_SENDER",
+ "DKIM_BAD_PRIVATE_KEY",
+ "DKIM_BUFFER_TOO_SMALL",
+};
+
+const char* DKIM_CALL DKIMGetErrorString(int ErrorCode) {
+ if (ErrorCode >= 0 || ErrorCode <= DKIM_MAX_ERROR)
+ return "Unknown";
+ else
+ return DKIMErrorStrings[-1-ErrorCode];
+}
diff --git a/src/dkimbase.cpp b/src/dkimbase.cpp
new file mode 100644
index 0000000..f6abf45
--- /dev/null
+++ b/src/dkimbase.cpp
@@ -0,0 +1,320 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#include <string.h>
+#include <algorithm>
+#include "dkim.h"
+#include "dkimbase.h"
+
+
+CDKIMBase::CDKIMBase()
+{
+ m_From = NULL;
+ m_Sender = NULL;
+ m_hTag = NULL;
+ m_hTagSize = 0;
+ m_hTagPos = 0;
+ m_Line = NULL;
+ m_LineSize = 0;
+ m_LinePos = 0;
+ m_InHeaders = true;
+}
+
+CDKIMBase::~CDKIMBase() // delete
+{
+ Free(m_Line);
+ Free(m_From);
+ Free(m_Sender);
+ Free(m_hTag);
+}
+
+int CDKIMBase::Init(void)
+{
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Alloc - allocate buffer
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMBase::Alloc(char*& szBuffer,int nRequiredSize)
+{
+ szBuffer = new char[nRequiredSize];
+
+ return (szBuffer == NULL) ? DKIM_OUT_OF_MEMORY : DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ReAlloc - extend buffer if necessary, leaving room for future expansion
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMBase::ReAlloc(char*& szBuffer,int& nBufferSize,int nRequiredSize)
+{
+ if (nRequiredSize > nBufferSize) {
+ char* newp;
+ int nNewSize = nRequiredSize + BUFFER_ALLOC_INCREMENT;
+
+ if (Alloc(newp,nNewSize) == DKIM_SUCCESS) {
+ if (szBuffer != NULL && nBufferSize > 0) {
+ memcpy(newp,szBuffer,nBufferSize);
+ delete[] szBuffer;
+ }
+ szBuffer = newp;
+ nBufferSize = nNewSize;
+ } else {
+ return DKIM_OUT_OF_MEMORY; // memory alloc error!
+ }
+ }
+
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Process - split buffers into lines without any CRs or LFs at the end.
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMBase::Free(char* szBuffer)
+{
+ if (szBuffer)
+ delete[] szBuffer;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Process - split buffers into lines without any CRs or LFs at the end.
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMBase::Process(const char* szBuffer,int nBufLength,bool bEOF)
+{ const char* p = szBuffer;
+ const char* e = szBuffer + nBufLength;
+
+ while (p < e) {
+ if (*p != '\n' || m_LinePos == 0 || m_Line[m_LinePos - 1] != '\r') {
+ // add char to line
+ if (m_LinePos >= m_LineSize) {
+ int nRet = ReAlloc(m_Line,m_LineSize,m_LinePos + 1);
+ if (nRet != DKIM_SUCCESS) return nRet;
+ }
+ m_Line[m_LinePos++] = *p;
+ } else {
+ // back up past the CR
+ m_LinePos--;
+
+ if (m_InHeaders) {
+ // process header line
+ if (m_LinePos == 0) {
+ m_InHeaders = false;
+ int Result = ProcessHeaders();
+ if (Result != DKIM_SUCCESS)
+ return Result;
+ } else {
+ // append the header to the headers list
+ if (m_Line[0] != ' ' && m_Line[0] != '\t') {
+ HeaderList.push_back(string(m_Line,m_LinePos));
+// fprintf(stderr," dkimbase.cpp:Process:Input: %s \n",m_Line);
+ } else {
+ if (!HeaderList.empty()) {
+ HeaderList.back().append("\r\n",2).append(m_Line,m_LinePos);
+ } else {
+ // no header to append to...
+ }
+ }
+ }
+ } else {
+ // process body line
+ int Result = ProcessBody(m_Line,m_LinePos,bEOF);
+ if (Result != DKIM_SUCCESS) {
+ m_LinePos = 0;
+ return Result;
+ }
+ }
+
+ m_LinePos = 0;
+ }
+
+ p++;
+ }
+
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessFinal - process leftovers if stopping before the body or mid-line
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMBase::ProcessFinal(void)
+{
+ if (m_LinePos > 0) {
+ Process("\r\n",2,true);
+ }
+
+ if (m_InHeaders) {
+ m_InHeaders = false;
+ ProcessHeaders();
+ /* type conversion should be safe as length is zero */
+ ProcessBody((char *)"",0,true);
+ }
+
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessHeaders - process the headers (to be implemented by derived class)
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMBase::ProcessHeaders()
+{
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessBody - process body line (to be implemented by derived class)
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMBase::ProcessBody(char* szBuffer, int nBufLength, bool bEOF)
+{
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// RemoveSWSP - remove streaming white space from buffer/string inline
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct isswsp
+{
+ bool operator()(char ch) { return(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); }
+};
+
+void CDKIMBase::RemoveSWSP(char* szBuffer)
+{
+ *remove_if(szBuffer,szBuffer + strlen(szBuffer),isswsp()) = '\0';
+}
+
+void CDKIMBase::RemoveSWSP(char* pBuffer,int& nBufLength)
+{
+ nBufLength = remove_if(pBuffer,pBuffer+nBufLength,isswsp()) - pBuffer;
+}
+
+void CDKIMBase::RemoveSWSP(string& sBuffer)
+{
+ sBuffer.erase(remove_if(sBuffer.begin(),sBuffer.end(),isswsp()),sBuffer.end());
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// CompressSWSP - compress streaming white space into single spaces from buffer/string inline
+//
+//////////////////////////////////////////////////////////////////////////////////////////
+
+void CDKIMBase::CompressSWSP(char* pBuffer,int& nBufLength)
+{
+ char* pSrc = pBuffer;
+ char* pDst = pBuffer;
+ char* pEnd = pBuffer + nBufLength;
+
+ while (pSrc != pEnd) {
+ if (isswsp()(*pSrc)) {
+
+ do {
+ ++pSrc;
+ } while (pSrc != pEnd && isswsp()(*pSrc));
+
+ if (pSrc == pEnd)
+ break;
+
+ *pDst++ = ' ';
+ }
+
+ *pDst++ = *pSrc++;
+ }
+
+ nBufLength = pDst - pBuffer;
+}
+
+void CDKIMBase::CompressSWSP(string& sBuffer)
+{
+ string::iterator iSrc = sBuffer.begin();
+ string::iterator iDst = sBuffer.begin();
+ string::iterator iEnd = sBuffer.end();
+
+ while (iSrc != iEnd) {
+ if (isswsp()(*iSrc)) {
+
+ do {
+ ++iSrc;
+ } while (iSrc != iEnd && isswsp()(*iSrc));
+
+ if (iSrc == iEnd)
+ break;
+
+ *iDst++ = ' ';
+ }
+
+ *iDst++ = *iSrc++;
+ }
+
+ sBuffer.erase(iDst, iEnd);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+//
+// RelaxHeader - relax a header field (lower case the name, remove swsp before and after :)
+//
+// modified 4/21/06 STB to remove white space before colon
+//
+//////////////////////////////////////////////////////////////////////////////////////////
+
+string CDKIMBase::RelaxHeader(const string& sHeader)
+{
+ string sTemp = sHeader;
+
+ CompressSWSP(sTemp);
+
+ string::size_type cpos = sTemp.find(':');
+
+ if (cpos == string::npos) {
+ // no colon?!
+ } else {
+ // lower case the header field name
+ for (unsigned i = 0; i < cpos; i++) {
+ if (sTemp[i] >= 'A' && sTemp[i] <= 'Z')
+ sTemp[i] += 'a'-'A';
+ }
+
+ // remove the space after the :
+ if (cpos + 1 < sTemp.length() && sTemp[cpos+1] == ' ')
+ sTemp.erase(cpos + 1, 1);
+
+ // remove the space before the :
+ if (cpos > 0 && sTemp[cpos - 1] == ' ')
+ sTemp.erase(cpos - 1,1);
+ }
+
+ return sTemp;
+}
diff --git a/src/dkimsign.cpp b/src/dkimsign.cpp
new file mode 100644
index 0000000..03b03e2
--- /dev/null
+++ b/src/dkimsign.cpp
@@ -0,0 +1,1106 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+
+#define _strnicmp strncasecmp
+#define _stricmp strcasecmp
+#define LOWORD(l) ((unsigned)(l) & 0xffff)
+#define HIWORD(l) ((unsigned)(l) >> 16)
+
+#include <string.h>
+#include <map>
+
+#include "dkim.h"
+#include "dkimsign.h"
+
+/*****************************************************************************
+*
+* Generating Ed25519 signed message:
+*
+* 1. RSA SHA1/SHA256 signatures are generated in streaming mode together with
+* their hashes. Two different 'contexts' (ctx) are used here:
+* m_Hdr_shaXctx => Used for signing (EVP_Sign...) -- covering the header only
+* m_[B,E]dy_shaXctx => Used for hashing (EVP_Digest..) -- covering the body only
+*
+* 2. Private keys
+* For hybrid signing we need two distinct keys:
+* - RSAKey
+* - ECCKey
+* These private keys needs to be passed concurrently to the signature functions.
+* Given those keys, the signature operation itself is executed in one step.
+*
+* 3. Public keys
+* The 'public keys' need to be deployed in the DNS:
+* - The RSA public key is DER-header enriched base64-encoded; thus is 9 byte larger
+* than the 'naked' public key, which size depends on the given parameters.
+* - The Ed25519 public key is also base64-encoded with a constant length of 60 byte.
+*
+* 4. DKIM message preparation scheme
+* According to RFC 6376 Sec. 3.7, we have a conducted hash for
+* - the previously available headers in the message;
+* selected and given in order by h=...,
+* - any existing DKIM signature fields b=...,
+* - except for previous added 'X-Authentication ...' header fields,
+* - and all (new) synthezised DKIM header tokens; except of course for the
+* signature itself - treated as 'null string': b="".
+* All this is subject of canonicalization (adding/removing CRLF, whitespaces ...).
++ As a result, the input for further calculations depends on this order given.
+*
+* Results following the 'preparation scheme':
+* - The message body hash is included in the DKIM header => bh=[m_[B,E]dy_shaXctx].
+* - The message signature (including the result of bh=...) => b=[m_Hdr_shaXctx]
+*
+* We consider SHA256 as default hash function and SHA1 as exception (on demand).
+*
+* 5. Generating (ECC) signatures
+* According to RFC 8032 Sect 4., we have two possible Ed25519 signature schemes:
+*
+* a) PureEd25519, as a one shot signature calculation swallowing the
+* complete message and employing a shortened SHA-512 hash input.
+* b) HashEd25519 working again in 'streaming mode' and permitting a choice
+* for the hash function - which is in RFC 8463 - defined to be SHA-256.
+*
+* RFC 8463 in Sect 3 is a bit ambiguous about the signing function:
+* Ed25519-256 vs. PureEd25519.
+* In fact (after consulting John Levine), it is PureEd25519.
+*
+* In order to allow parallel RSA/Ed25519 processing, we need to generate:
+* m_Hdr_sha256ctx => Used for RSA signatures
+* m_Bdy_sha256ctx => The SHA256 hash of selected header parts and body (RSA)
+* m_Edy_sha256ctx => The SHA256 hash of selected header parts and body (Ed25519)
+* m_Hdr_ed25519ctx => The signature of the messsage header using PureEd25519
+* following the 'preparation' scheme
+*
+* Now, two cryptographic informations are provided in the header:
+* bh=[m_Edy_sha256ctx] => The SHA256 digest of the message (BodyHash),
+* b=[m_Hdr_ed25519ctx] => The PureED25519 signature.
+* including the value of bh=... (EmailSignature)
+* having a length of 512 bits => 64 bytes.
+*
+* 6. Hybrid signatures (RSA and Ed25519)
+* They involve
+* m_Hdr_sha256ctx => Used for RSA signatures
+* m_Hdr_ed25519ctx => PureED25519 signature
+* m_Bdy_sha256ctx => SHA256 digest of the message (BodyHash) for RSA
+* m_Edy_sha256ctx => SHA256 digest of the message (BodyHash) for Ed25519
+*
+* The EVP_DigestFinal routine has to be replaced by EVP_DigestFinal_ex.
+* However; after the first call, its content seems to be garbeled.
+* A common MD for both RSA and Ed2551 seems to be infeasible.
+*
+* ------
+*
+* The particular function and variable names chosen here do not obviously match
+* what they are intended to do. However, in order to keep traceablility of the
+* changes, I left those untouched.
+*
+*****************************************************************************/
+
+CDKIMSign::CDKIMSign()
+{
+ m_EmptyLineCount = 0;
+ m_pfnHdrCallback = NULL;
+
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_SignInit(&m_Hdr_sha1ctx,EVP_sha1());
+ EVP_SignInit(&m_Hdr_sha256ctx,EVP_sha256());
+ EVP_DigestInit(&m_Bdy_sha1ctx,EVP_sha1());
+ EVP_DigestInit(&m_Bdy_sha256ctx,EVP_sha256());
+#else
+ m_Hdr_sha1ctx = EVP_MD_CTX_create();
+ EVP_SignInit_ex(m_Hdr_sha1ctx,EVP_sha1(),NULL);
+
+ m_Hdr_sha256ctx = EVP_MD_CTX_create();
+ EVP_SignInit_ex(m_Hdr_sha256ctx,EVP_sha256(),NULL);
+
+ m_Bdy_sha1ctx = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(m_Bdy_sha1ctx,EVP_sha1(),NULL);
+
+ m_Bdy_sha256ctx = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(m_Bdy_sha256ctx,EVP_sha256(),NULL);
+
+ m_Hdr_ed25519ctx = EVP_MD_CTX_create();
+
+ m_Edy_sha256ctx = EVP_MD_CTX_create();
+ EVP_DigestInit_ex(m_Edy_sha256ctx,EVP_sha256(),NULL);
+#endif
+}
+
+CDKIMSign::~CDKIMSign()
+{
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_MD_CTX_cleanup(&m_Hdr_sha1ctx);
+ EVP_MD_CTX_cleanup(&m_Hdr_sha256ctx);
+ EVP_MD_CTX_cleanup(&m_Bdy_sha1ctx);
+ EVP_MD_CTX_cleanup(&m_Bdy_sha256ctx);
+#else
+ EVP_MD_CTX_free(m_Hdr_sha1ctx);
+ EVP_MD_CTX_free(m_Hdr_sha256ctx);
+ EVP_MD_CTX_free(m_Hdr_ed25519ctx);
+ EVP_MD_CTX_free(m_Bdy_sha1ctx);
+ EVP_MD_CTX_free(m_Bdy_sha256ctx);
+ EVP_MD_CTX_free(m_Edy_sha256ctx);
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Init - save the options
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMSign::Init(DKIMSignOptions* pOptions)
+{
+ int nRet = CDKIMBase::Init();
+
+ m_Canon = pOptions->nCanon;
+
+ // as of draft 01, these are the only allowed signing types:
+ if ((m_Canon != DKIM_SIGN_SIMPLE_RELAXED) &&
+ (m_Canon != DKIM_SIGN_RELAXED) &&
+ (m_Canon != DKIM_SIGN_RELAXED_SIMPLE)) {
+ m_Canon = DKIM_SIGN_SIMPLE;
+ }
+
+ sSelector.assign(pOptions->szSelector);
+ eSelector.assign(pOptions->szSelectorE);
+
+ m_pfnHdrCallback = pOptions->pfnHeaderCallback;
+
+ sDomain.assign(pOptions->szDomain);
+
+ m_IncludeBodyLengthTag = (pOptions->nIncludeBodyLengthTag != 0);
+
+ m_nBodyLength = 0;
+
+ m_ExpireTime = pOptions->expireTime;
+
+ sIdentity.assign(pOptions->szIdentity);
+
+ m_nIncludeTimeStamp = pOptions->nIncludeTimeStamp;
+ m_nIncludeQueryMethod = pOptions->nIncludeQueryMethod;
+ m_nIncludeCopiedHeaders = pOptions->nIncludeCopiedHeaders;
+
+ // NOTE: the following line is not backwards compatible with MD 8.0.3
+ // because the szRequiredHeaders member was added after the release
+ //sRequiredHeaders.assign(pOptions->szRequiredHeaders);
+
+ //make sure there is a colon after the last header in the list
+ if ((sRequiredHeaders.size() > 0) &&
+ sRequiredHeaders.at(sRequiredHeaders.size() - 1) != ':') {
+ sRequiredHeaders.append(":");
+ }
+
+ m_nHash = pOptions->nHash;
+ m_bReturnedSigAssembled = false;
+ m_sCopiedHeaders.erase();
+
+ // Initializes ED25519 header fields SigHdrs
+#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
+ SigHdrs.assign("");
+ m_SigHdrs = 0;
+#endif
+
+ return nRet;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Hash - update the hash
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::Hash(const char *szBuffer,int nBufLength,bool bHdr)
+{
+
+ /** START DEBUG CODE **
+ if (nBufLength == 2 && szBuffer[0] == '\r' && szBuffer[1] == '\n') {
+ printf("[CRLF]\n");
+ } else {
+ char *szDbg = new char[nBufLength+1];
+ strncpy(szDbg,szBuffer,nBufLength);
+ szDbg[nBufLength] = '\0';
+ printf("[%s]\n",szDbg);
+ } ***
+
+ if (fpdebug == NULL) {
+ fpdebug = fopen("canon.msg", "wb");
+ }
+
+ fwrite(szBuffer,1,nBufLength,fpdebug);
+
+ ** END DEBUG CODE **/
+
+ if (bHdr) { /* Generate signature: b=... */
+ if ((m_nHash == DKIM_HASH_SHA1) ||
+ (m_nHash == DKIM_HASH_SHA1_AND_SHA256))
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_SignUpdate(&m_Hdr_sha1ctx,szBuffer,nBufLength);
+#else
+ EVP_SignUpdate(m_Hdr_sha1ctx,szBuffer,nBufLength);
+#endif
+ if ((m_nHash == DKIM_HASH_SHA256) ||
+ (m_nHash == DKIM_HASH_SHA1_AND_SHA256) ||
+ (m_nHash == DKIM_HASH_RSA256_AND_ED25519))
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_SignUpdate(&m_Hdr_sha256ctx,szBuffer,nBufLength);
+#else
+ EVP_SignUpdate(m_Hdr_sha256ctx,szBuffer,nBufLength);
+#endif
+#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
+ if ((m_nHash == DKIM_HASH_ED25519) ||
+ (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) {
+ SigHdrs.append(szBuffer,nBufLength);
+ m_SigHdrs += nBufLength;
+ }
+#endif
+ } else { /* lets go for body hash values: bh=... (either SHA1 or SHA256) */
+ if ((m_nHash == DKIM_HASH_SHA1) ||
+ (m_nHash == DKIM_HASH_SHA1_AND_SHA256))
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_DigestUpdate(&m_Bdy_sha1ctx,szBuffer,nBufLength);
+#else
+ EVP_DigestUpdate(m_Bdy_sha1ctx,szBuffer,nBufLength);
+#endif
+ if (m_nHash != DKIM_HASH_SHA1)
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_DigestUpdate(&m_Bdy_sha256ctx,szBuffer,nBufLength);
+#else
+ EVP_DigestUpdate(m_Bdy_sha256ctx,szBuffer,nBufLength);
+#endif
+#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
+ if ((m_nHash == DKIM_HASH_ED25519) ||
+ (m_nHash == DKIM_HASH_RSA256_AND_ED25519))
+ EVP_DigestUpdate(m_Edy_sha256ctx,szBuffer,nBufLength);
+#endif
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// SignThisTag - return boolean whether or not to sign this tag
+//
+////////////////////////////////////////////////////////////////////////////////
+bool CDKIMSign::SignThisTag(const string& sTag)
+{
+ bool bRet = true;
+
+ if (_strnicmp(sTag.c_str(),"X-",2) == 0 ||
+ _stricmp(sTag.c_str(),"Authentication-Results:") == 0 ||
+ _stricmp(sTag.c_str(),"Return-Path:") == 0) {
+ bRet = false;
+ }
+
+ return bRet;
+}
+
+bool ConvertHeaderToQuotedPrintable(const char* source, char* dest)
+{
+ bool bConvert = false;
+
+ // do quoted printable
+ static unsigned char hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+
+ unsigned char *d = (unsigned char*)dest;
+ for (const unsigned char *s = (const unsigned char *)source; *s != '\0'; s++)
+ {
+ if (*s >= 33 && *s <= 126 && *s != '=' && *s != ':' && *s != ';' && *s != '|') {
+ *d++ = *s;
+ } else {
+ bConvert = true;
+ *d++ = '=';
+ *d++ = hexchars[*s >> 4];
+ *d++ = hexchars[*s & 15];
+ }
+ }
+ *d = '\0';
+
+ return bConvert;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GetHeaderParams - Extract any needed header parameters
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::GetHeaderParams(const string &sHdr)
+{
+ if (_strnicmp(sHdr.c_str(),"X",1) == 0) return;
+ if (_strnicmp(sHdr.c_str(),"From:",5) == 0) { sFrom.assign(sHdr.c_str() + 5); }
+ if (_strnicmp(sHdr.c_str(),"Sender:",7) == 0) { sSender.assign(sHdr.c_str() + 7); }
+
+ if (m_nIncludeCopiedHeaders) {
+ string::size_type pos = sHdr.find(':');
+
+ if (pos != string::npos) {
+ string sTag, sValue;
+ char *workBuffer = new char[sHdr.size() * 3 + 1];
+
+ sTag.assign(sHdr.substr(0,pos));
+ sValue.assign(sHdr.substr(pos + 1,string::npos));
+
+ ConvertHeaderToQuotedPrintable(sTag.c_str(),workBuffer);
+ if (!m_sCopiedHeaders.empty()) { m_sCopiedHeaders.append("|"); }
+ m_sCopiedHeaders.append(workBuffer); m_sCopiedHeaders.append(":");
+ ConvertHeaderToQuotedPrintable(sValue.c_str(),workBuffer);
+ m_sCopiedHeaders.append(workBuffer);
+
+ delete[] workBuffer;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessHeaders - sign headers and save needed parameters (this is a lie)
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMSign::ProcessHeaders(void)
+{
+ map<string,list<string>::reverse_iterator> IterMap;
+ map<string,list<string>::reverse_iterator>::iterator IterMapIter;
+ list<string>::reverse_iterator riter;
+ list<string>::iterator iter;
+ string sTag;
+ bool bFromHeaderFound = false;
+
+ // walk the header list
+ for (iter = HeaderList.begin(); iter != HeaderList.end(); iter++) {
+ sTag.assign(*iter);
+
+ // look for a colon
+ string::size_type pos = sTag.find(':');
+
+ if (pos != string::npos) {
+ int nSignThisTag = 1;
+
+ // hack off anything past the colon
+ sTag.erase(pos + 1,string::npos);
+
+ // is this the From: header?
+ if (_stricmp(sTag.c_str(),"From:") == 0) {
+ bFromHeaderFound = true;
+ nSignThisTag = 1;
+ IsRequiredHeader(sTag); // remove from required header list
+ }
+ // is this in the list of headers that must be signed?
+ else if (IsRequiredHeader(sTag)) {
+ nSignThisTag = 1;
+ }
+ else {
+ if(m_pfnHdrCallback) {
+ nSignThisTag = m_pfnHdrCallback(iter->c_str());
+ } else {
+ nSignThisTag = SignThisTag(sTag) ? 1 : 0;
+ }
+ }
+
+ // save header parameters
+ GetHeaderParams(*iter);
+
+ if (nSignThisTag > 0) {
+ // add this tag to h=
+ hParam.append(sTag);
+
+ IterMapIter = IterMap.find(sTag);
+
+ riter = (IterMapIter == IterMap.end()) ? HeaderList.rbegin() : IterMapIter->second;
+
+ // walk the list in reverse looking for the last instance of this header
+ while (riter != HeaderList.rend()) {
+ if (_strnicmp(riter->c_str(),sTag.c_str(),sTag.size()) == 0) {
+ ProcessHeader(*riter);
+
+ // save the reverse iterator position for this tag
+ riter++;
+ IterMap[sTag] = riter;
+ break;
+ }
+ riter++;
+ }
+ }
+ }
+ }
+
+ if(!bFromHeaderFound) {
+ string sFrom("From:");
+ hParam.append(sFrom);
+ IsRequiredHeader(sFrom); // remove from required header list
+// Hash("\r\n",2);
+ }
+
+ hParam.append(sRequiredHeaders);
+
+// string::size_type end = sRequiredHeaders.find(':');
+// while (end != string::npos)
+// {
+// Hash("\r\n",2);
+// end = sRequiredHeaders.find(':', end+1);
+// }
+
+ // remove the last colon from h=
+ if (hParam.at(hParam.size() - 1) == ':')
+ hParam.erase(hParam.size() - 1,string::npos);
+
+ return DKIM_SUCCESS;
+}
+
+void CDKIMSign::ProcessHeader(const string &sHdr)
+{
+ switch (HIWORD(m_Canon)) {
+ case DKIM_CANON_SIMPLE:
+ Hash(sHdr.c_str(),sHdr.size(),true);
+ Hash("\r\n",2,true);
+ break;
+
+ case DKIM_CANON_NOWSP: {
+ string sTemp = sHdr;
+ RemoveSWSP(sTemp);
+
+ // convert characters before ':' to lower case
+ for (char *s = (char*)sTemp.c_str(); *s != '\0' && *s != ':'; s++) {
+ if (*s >= 'A' && *s <= 'Z')
+ *s += 'a' - 'A';
+ }
+
+ Hash(sTemp.c_str(),sTemp.size(),true);
+ Hash("\r\n",2,true);
+ }
+ break;
+
+ case DKIM_CANON_RELAXED: {
+ string sTemp = RelaxHeader(sHdr);
+ Hash(sTemp.c_str(),sTemp.length(),true);
+ Hash("\r\n",2,true);
+ }
+ break;
+ }
+}
+
+int CDKIMSign::ProcessBody(char *szBuffer,int nBufLength,bool bEOF)
+{
+ switch(LOWORD(m_Canon)) {
+ case DKIM_CANON_SIMPLE:
+ if (nBufLength > 0) {
+ while (m_EmptyLineCount > 0) {
+ Hash("\r\n",2,false);
+ m_nBodyLength += 2;
+ m_EmptyLineCount--;
+ }
+ Hash(szBuffer,nBufLength,false);
+ Hash("\r\n",2,false);
+ m_nBodyLength += nBufLength + 2;
+ } else {
+ m_EmptyLineCount++;
+ if (bEOF) {
+ Hash("\r\n",2,false);
+ m_nBodyLength += 2;
+ }
+ }
+ break;
+ case DKIM_CANON_NOWSP:
+ RemoveSWSP(szBuffer,nBufLength);
+ if (nBufLength > 0) {
+ Hash(szBuffer,nBufLength,false);
+ m_nBodyLength += nBufLength;
+ }
+ break;
+ case DKIM_CANON_RELAXED:
+ CompressSWSP(szBuffer,nBufLength);
+ if (nBufLength > 0) {
+ while (m_EmptyLineCount > 0) {
+ Hash("\r\n",2,false);
+ m_nBodyLength += 2;
+ m_EmptyLineCount--;
+ }
+ Hash(szBuffer,nBufLength,false);
+ m_nBodyLength += nBufLength;
+ if (!bEOF) {
+ Hash("\r\n",2,false);
+ m_nBodyLength += 2;
+ }
+ } else
+ m_EmptyLineCount++;
+ break;
+ }
+
+ return DKIM_SUCCESS;
+}
+
+bool CDKIMSign::ParseFromAddress(void)
+{
+ string::size_type pos;
+ string sAddress;
+
+ if (!sFrom.empty()) {
+ sAddress.assign(sFrom);
+ } else if (!sSender.empty()) {
+ sAddress.assign(sSender);
+ } else {
+ return false;
+ }
+
+ // simple for now, beef it up later
+
+ // remove '<' and anything before it
+ pos = sAddress.find('<');
+ if(pos != string::npos)
+ sAddress.erase(0,pos);
+
+ // remove '>' and anything after it
+ pos = sAddress.find('>');
+ if (pos != string::npos)
+ sAddress.erase(pos,string::npos);
+
+ // look for '@' symbol
+ pos = sAddress.find('@');
+ if (pos == string::npos)
+ return false;
+
+ if (sDomain.empty()) {
+ sDomain.assign (sAddress.c_str() + pos + 1);
+ RemoveSWSP(sDomain);
+ }
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// InitSig - initialize signature folding algorithm
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::InitSig(void)
+{
+ m_sSig.reserve(1024);
+ m_sSig.assign("DKIM-Signature:");
+ m_nSigPos = m_sSig.size();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AddTagToSig - add tag and value to signature folding if necessary
+// if bFold, fold at cbrk char
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::AddTagToSig(const char* const Tag,const string &sValue,char cbrk,bool bFold)
+{
+ int nTagLen = strlen(Tag);
+
+ AddInterTagSpace((!bFold) ? sValue.size() + nTagLen + 2 : nTagLen + 2);
+
+ m_sSig.append(Tag);
+ m_sSig.append("=");
+ m_nSigPos += 1 + nTagLen;
+
+ if (!bFold) {
+ m_sSig.append(sValue);
+ m_nSigPos += sValue.size();
+ } else {
+ AddFoldedValueToSig(sValue,cbrk);
+ }
+ m_sSig.append(";");
+ m_nSigPos++;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AddTagToSig - add tag and numeric value to signature folding if necessary
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::AddTagToSig(const char* const Tag,unsigned long nValue)
+{
+ char szValue[64];
+ sprintf(szValue,"%lu",nValue);
+ AddTagToSig(Tag,szValue,0,false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AddInterTagSpace - add space or fold here
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::AddInterTagSpace(int nSizeOfNextTag)
+{
+ if (m_nSigPos + nSizeOfNextTag + 1 > OptimalHeaderLineLength) {
+// m_sSig.append("\r\n\t");
+ m_sSig.append("\r\n "); /* s/qmail style */
+ m_nSigPos = 1;
+ } else {
+ m_sSig.append(" ");
+ m_nSigPos++;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AddTagToSig - add value to signature folding if necessary
+// if cbrk == 0 fold anywhere, otherwise fold only at cbrk
+//
+////////////////////////////////////////////////////////////////////////////////
+void CDKIMSign::AddFoldedValueToSig(const string &sValue,char cbrk)
+{
+ string::size_type pos = 0;
+
+ if (cbrk == 0) {
+ // fold anywhere
+ while (pos < sValue.size()) {
+ string::size_type len = OptimalHeaderLineLength - m_nSigPos;
+ if (len > sValue.size() - pos)
+ len = sValue.size() - pos;
+ m_sSig.append(sValue.substr(pos,len));
+ m_nSigPos += len;
+ pos += len;
+
+ if (pos < sValue.size()) {
+// m_sSig.append("\r\n\t");
+ m_sSig.append("\r\n "); /* s/qmail style */
+ m_nSigPos = 1;
+ }
+ }
+ } else {
+ // fold only at cbrk
+ while (pos < sValue.size()) {
+ string::size_type len = OptimalHeaderLineLength - m_nSigPos;
+ string::size_type brkpos;
+
+ if (sValue.size() - pos < len) {
+ brkpos = sValue.size();
+ } else {
+ brkpos = sValue.rfind(cbrk,pos + len);
+ }
+
+ if (brkpos == string::npos || brkpos < pos) {
+ brkpos = sValue.find(cbrk,pos);
+ if (brkpos == string::npos) {
+ brkpos = sValue.size();
+ }
+ }
+
+ len = brkpos - pos + 1;
+
+ m_sSig.append(sValue.substr(pos,len));
+
+ m_nSigPos += len;
+ pos += len;
+
+ if (pos < sValue.size()) {
+// m_sSig.append("\r\n\t");
+ m_sSig.append("\r\n "); /* s/qmail style */
+ m_nSigPos = 1;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GetSig - compute hash and return signature header in szSignature
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMSign::GetSig2(char* szRSAKey,char* szECCKey,char** pszSignature)
+{
+ if (szRSAKey == NULL && szECCKey == NULL) {
+ return DKIM_BAD_PRIVATE_KEY;
+ }
+
+ if (pszSignature == NULL) {
+ return DKIM_BUFFER_TOO_SMALL;
+ }
+
+ int nRet = AssembleReturnedSig(szRSAKey,szECCKey);
+
+ if (nRet != DKIM_SUCCESS)
+ return nRet;
+
+ *pszSignature = (char*)m_sReturnedSig.c_str();
+
+ return DKIM_SUCCESS;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// IsRequiredHeader - Check if header in required list. If so, delete
+// header from list.
+//
+////////////////////////////////////////////////////////////////////////////////
+bool CDKIMSign::IsRequiredHeader(const string& sTag)
+{
+ string::size_type start = 0;
+ string::size_type end = sRequiredHeaders.find(':');
+
+ while (end != string::npos) {
+ // check for a zero-length header
+ if(start == end) {
+ sRequiredHeaders.erase(start,1);
+ } else {
+ if (_stricmp(sTag.c_str(),sRequiredHeaders.substr(start,end - start + 1).c_str()) == 0) {
+ sRequiredHeaders.erase(start,end - start + 1);
+ return true;
+ } else {
+ start = end + 1;
+ }
+ }
+
+ end = sRequiredHeaders.find(':',start);
+ }
+
+ return false;
+}
+////////////////////////////////////////////////////////////////////////////////
+//
+// ConstructSignature
+//
+// Here, we don't construct the 'signature' but rather the DKIM header
+// multiply and indidually crafted for each distinct nSigAlg method
+//
+// nSigAlg: DKIM_HASH_SHA1, DKIM_HASH_SHA256, DKIM_HASH_ED25519
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMSign::ConstructSignature(char* szPrivKey,int nSigAlg)
+{
+ string sSignedSig;
+ unsigned char* sig;
+ EVP_PKEY *pkey = 0;
+ BIO *bio, *b64;
+ unsigned int siglen;
+ int size;
+ int len;
+ char* buf;
+ int nSignRet;
+
+ /* construct the DKIM-Signature: header and add to hash */
+ InitSig();
+
+ AddTagToSig("v","1",0,false);
+
+ switch (nSigAlg) {
+ case DKIM_HASH_SHA1:
+ AddTagToSig("a","rsa-sha1",0,false); break;
+ case DKIM_HASH_SHA256:
+ AddTagToSig("a","rsa-sha256",0,false); break;
+ case DKIM_HASH_ED25519:
+ AddTagToSig("a","ed25519-sha256",0,false); break;
+ }
+
+ switch (m_Canon) {
+ case DKIM_SIGN_SIMPLE:
+ AddTagToSig("c","simple/simple",0,false); break;
+ case DKIM_SIGN_SIMPLE_RELAXED:
+ AddTagToSig("c","simple/relaxed",0,false); break;
+ case DKIM_SIGN_RELAXED:
+ AddTagToSig("c","relaxed/relaxed",0,false); break;
+ case DKIM_SIGN_RELAXED_SIMPLE:
+ AddTagToSig("c","relaxed/simple",0,false); break;
+ }
+
+ AddTagToSig("d",sDomain,0,false);
+ if (nSigAlg == DKIM_HASH_ED25519)
+ AddTagToSig("s",eSelector,0,false);
+ else
+ AddTagToSig("s",sSelector,0,false);
+ if (m_IncludeBodyLengthTag) { AddTagToSig("l",m_nBodyLength); }
+ if (m_nIncludeTimeStamp != 0) { time_t t; time(&t); AddTagToSig("t",t); }
+ if (m_ExpireTime != 0) { AddTagToSig("x",m_ExpireTime); }
+ if (!sIdentity.empty()) { AddTagToSig("i",sIdentity,0,false); }
+ if (m_nIncludeQueryMethod) { AddTagToSig("q","dns/txt",0,false); }
+
+ AddTagToSig("h",hParam,':',true); // copied headers follow the ':'
+ if (m_nIncludeCopiedHeaders) { AddTagToSig("z",m_sCopiedHeaders,0,true); }
+
+ /* Set up context for (body) hash */
+
+ unsigned char Hash[4096];
+ unsigned int nHashLen = 0;
+
+ switch (nSigAlg) {
+ case DKIM_HASH_SHA1:
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_DigestFinal(&m_Bdy_sha1ctx,Hash,&nHashLen); break;
+#else
+ EVP_DigestFinal_ex(m_Bdy_sha1ctx,Hash,&nHashLen); break;
+#endif
+ case DKIM_HASH_SHA256:
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_DigestFinal(&m_Bdy_sha256ctx,Hash,&nHashLen); break;
+#else
+ EVP_DigestFinal_ex(m_Bdy_sha256ctx,Hash,&nHashLen); break;
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ case DKIM_HASH_ED25519:
+ EVP_DigestFinal_ex(m_Edy_sha256ctx,Hash,&nHashLen); break;
+#endif
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (!bio) return DKIM_OUT_OF_MEMORY;
+
+ b64 = BIO_new(BIO_f_base64());
+ if (!b64) {
+ BIO_free(bio);
+ return DKIM_OUT_OF_MEMORY;
+ }
+ BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
+ BIO_push(b64,bio);
+ if (BIO_write(b64,Hash,nHashLen) < (int)nHashLen) {
+ BIO_free_all(b64);
+ return DKIM_OUT_OF_MEMORY;
+ }
+ BIO_flush(b64);
+
+ len = nHashLen * 2;
+ buf = new char[len];
+
+ if (buf == NULL) {
+ BIO_free_all(b64);
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ size = BIO_read(bio,buf,len);
+ BIO_free_all(b64);
+
+ // this should never happen
+ if (size >= len) {
+ delete[] buf;
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ buf[size] = '\0';
+ AddTagToSig("bh",buf,0,true);
+ delete[] buf;
+
+ AddInterTagSpace(3);
+
+ m_sSig.append("b=");
+ m_nSigPos += 2;
+
+ // Force a full copy - no reference copies please
+ sSignedSig.assign(m_sSig.c_str());
+
+ // note that since we're not calling hash here, need to dump this
+ // to the debug file if you want the full canonical form
+
+ string sTemp;
+
+ if (HIWORD(m_Canon) == DKIM_CANON_RELAXED) {
+ sTemp = RelaxHeader(sSignedSig);
+ } else {
+ sTemp = sSignedSig.c_str();
+ }
+
+ /* Update streaming signatures */
+
+ switch (nSigAlg) {
+ case DKIM_HASH_SHA1:
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_SignUpdate(&m_Hdr_sha1ctx,sTemp.c_str(),sTemp.size()); break;
+#else
+ EVP_SignUpdate(m_Hdr_sha1ctx,sTemp.c_str(),sTemp.size()); break;
+#endif
+ case DKIM_HASH_SHA256:
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_SignUpdate(&m_Hdr_sha256ctx,sTemp.c_str(),sTemp.size()); break;
+#else
+ EVP_SignUpdate(m_Hdr_sha256ctx,sTemp.c_str(),sTemp.size()); break;
+#endif
+#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
+ case DKIM_HASH_ED25519:
+ SigHdrs.append(sTemp.c_str(),sTemp.size());
+ m_SigHdrs += sTemp.size(); break;
+#endif
+ }
+
+ bio = BIO_new_mem_buf(szPrivKey, -1);
+ if (bio == NULL) return DKIM_OUT_OF_MEMORY;
+
+ pkey = PEM_read_bio_PrivateKey(bio,NULL,NULL,NULL); // FIXME - done
+ BIO_free(bio);
+
+ if (!pkey) { return DKIM_BAD_PRIVATE_KEY; }
+ siglen = EVP_PKEY_size(pkey);
+
+ sig = (unsigned char*) OPENSSL_malloc(siglen);
+ if (sig == NULL) {
+ EVP_PKEY_free(pkey);
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ /* Finish streaming signature and potentially go for Ed25519 signatures */
+
+ size_t sig_len;
+ unsigned char* SignMsg;
+
+ switch (nSigAlg) {
+ case DKIM_HASH_SHA1:
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ nSignRet = EVP_SignFinal(&m_Hdr_sha1ctx,sig,&siglen,pkey); break;
+#else
+ nSignRet = EVP_SignFinal(m_Hdr_sha1ctx,sig,&siglen,pkey); break;
+#endif
+ case DKIM_HASH_SHA256:
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ nSignRet = EVP_SignFinal(&m_Hdr_sha256ctx,sig,&siglen,pkey); break;
+#else
+ nSignRet = EVP_SignFinal(m_Hdr_sha256ctx,sig,&siglen,pkey); break;
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ case DKIM_HASH_ED25519:
+ EVP_DigestSignInit(m_Hdr_ed25519ctx,NULL,NULL,NULL,pkey);
+ SignMsg = (unsigned char*) SigHdrs.c_str();
+ EVP_DigestSign(m_Hdr_ed25519ctx,NULL,&sig_len,SignMsg,m_SigHdrs);
+ sig = (unsigned char*) OPENSSL_malloc(sig_len);
+ nSignRet = EVP_DigestSign(m_Hdr_ed25519ctx,sig,&sig_len,SignMsg,m_SigHdrs);
+ siglen = (unsigned int) sig_len; break;
+#endif
+ }
+ EVP_PKEY_free(pkey);
+
+ if (!nSignRet) {
+ OPENSSL_free(sig);
+ return DKIM_BAD_PRIVATE_KEY; // key too small
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (!bio) {
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ b64 = BIO_new(BIO_f_base64());
+ if (!b64) {
+ BIO_free(bio);
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
+ BIO_push(b64,bio);
+
+ if (BIO_write(b64,sig,siglen) < (int) siglen) {
+ OPENSSL_free(sig);
+ BIO_free_all(b64);
+ return DKIM_OUT_OF_MEMORY;
+ }
+ BIO_flush(b64);
+ OPENSSL_free(sig);
+
+ len = siglen * 2;
+ buf = new char[len];
+
+ if (buf == NULL) {
+ BIO_free_all(b64);
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ size = BIO_read(bio,buf,len);
+ BIO_free_all(b64);
+
+ // this should never happen
+ if (size >= len) {
+ delete[] buf;
+ return DKIM_OUT_OF_MEMORY;
+ }
+
+ buf[size] = '\0';
+ AddFoldedValueToSig(buf,0);
+ delete[] buf;
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// AssembleReturnSig
+//
+// calls ConstructSignature
+// for all different hashes and signature key files
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMSign::AssembleReturnedSig(char* szRSAKey,char* szECCKey)
+{
+ int nRet;
+
+ if (m_bReturnedSigAssembled)
+ return DKIM_SUCCESS;
+
+ ProcessFinal();
+
+ if (ParseFromAddress() == false) {
+ return DKIM_NO_SENDER;
+ }
+
+ string ed25519Sig, sha256Sig, sha1Sig;
+
+ if ((m_nHash == DKIM_HASH_ED25519) ||
+ (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) {
+ nRet = ConstructSignature(szECCKey,DKIM_HASH_ED25519);
+ if (nRet == DKIM_SUCCESS) {
+ ed25519Sig.assign(m_sSig);
+ } else {
+ return nRet;
+ }
+ }
+
+ if ((m_nHash == DKIM_HASH_SHA256) ||
+ (m_nHash == DKIM_HASH_SHA1_AND_SHA256) ||
+ (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) {
+ nRet = ConstructSignature(szRSAKey,DKIM_HASH_SHA256);
+ if (nRet == DKIM_SUCCESS) {
+ sha256Sig.assign(m_sSig);
+ } else {
+ return nRet;
+ }
+ }
+
+ if ((m_nHash == DKIM_HASH_SHA1) ||
+ (m_nHash == DKIM_HASH_SHA1_AND_SHA256)) {
+ nRet = ConstructSignature(szRSAKey,DKIM_HASH_SHA1);
+ if (nRet == DKIM_SUCCESS) {
+ sha1Sig.assign(m_sSig);
+ } else {
+ return nRet;
+ }
+ }
+
+// fclose(fpdebug);
+// fpdebug = NULL;
+
+ if (!ed25519Sig.empty()) {
+/* if (!m_sReturnedSig.empty()) {
+ m_sReturnedSig.append("\r\n");
+ }
+ */
+ m_sReturnedSig.assign(ed25519Sig);
+ }
+
+ if (!sha1Sig.empty()) {
+ if (!m_sReturnedSig.empty()) {
+ m_sReturnedSig.append("\r\n");
+ }
+ m_sReturnedSig.append(sha1Sig);
+ }
+
+ if (!sha256Sig.empty()) {
+ if (!m_sReturnedSig.empty()) {
+ m_sReturnedSig.append("\r\n");
+ }
+ m_sReturnedSig.append(sha256Sig);
+ }
+
+ m_bReturnedSigAssembled = true;
+ return DKIM_SUCCESS;
+}
diff --git a/src/dkimverify.cpp b/src/dkimverify.cpp
new file mode 100644
index 0000000..c9f1003
--- /dev/null
+++ b/src/dkimverify.cpp
@@ -0,0 +1,1443 @@
+/*****************************************************************************
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <vector>
+#include <algorithm>
+#include "dkim.h"
+#include "dkimverify.h"
+#include "dnsgettxt.h"
+extern "C" {
+#include "dns.h"
+#include "stralloc.h"
+}
+
+/*****************************************************************************
+*
+* Verifying DKIM Ed25519 signatures:
+*
+* The received DKIM header includes two cryptographic relevant informations:
+*
+* a) The 'body hash' => bh=[sha1|sha256]
+* b) The signature => b=[RSA-SHA1|RSA-SHA256|PureEd25519]
+*
+* Several DKIM headers (=signatures) may be present in the email.
+* Here, it is limited to max. Shall we really evaluate all?
+*
+* Caution: Using hybrid signatures, calling the destructor will core dump
+* given EVP_MD_CTX_free() upon the next call of EVP_DigestInit.
+* Using the destructor with EVP_MD_CTX_reset() however works.
+*
+*****************************************************************************/
+
+#define _strnicmp strncasecmp
+#define _stricmp strcasecmp
+#define MAX_SIGNATURES 10 // maximum number of DKIM signatures to process/message
+#define FDLOG stderr /* writing to another FD requires a method */
+
+string SigHdr;
+size_t m_SigHdr;
+
+extern "C" int stralloc_copys(stralloc *,char const *);
+
+int dig_ascii(char *digascii,unsigned const char *digest,const int len)
+{
+ static const char hextab[] = "0123456789abcdef";
+ int j;
+
+ for (j = 0; j < len; j++) {
+ digascii[2 * j] = hextab[(unsigned char)digest[j] >> 4];
+ digascii[2 * j + 1] = hextab[(unsigned char)digest[j] & 0x0f];
+ }
+ digascii[2 * len] = '\0';
+
+ return (2 * j); // 2*len
+}
+
+
+int _DNSGetTXT(const char *szFQDN,char *Buffer,int nBufLen)
+{
+ stralloc out = {0};
+ stralloc sa = {0};
+ Buffer[0] = '\0'; // need to be initialized
+
+ if (!stralloc_copys(&sa,szFQDN)) return -1;
+
+ DNS_INIT
+
+ switch (dns_txt(&out,&sa)) {
+ case -1: return -1;
+ case 0: return 0;
+ }
+
+ if (nBufLen < out.len)
+ return -2;
+
+ if (!stralloc_0(&out)) return -1;
+ memcpy(Buffer,out.s,out.len); // Return-by-value; sigh
+
+ return out.len;
+}
+
+int _DKIM_ReportResult(const char* ResFile,const char* result,const char* reason)
+{
+ int len = 0;
+
+ FILE* out = fopen(ResFile,"wb+");
+ if (out == NULL) return -1;
+
+ if (result) {
+ len = strlen(result);
+ fwrite(result,1,len,out);
+ fwrite("\r",1,1,out);
+ }
+
+ if (reason) {
+ fwrite(reason,1,strlen(reason),out);
+ fwrite("\r",1,1,out);
+ }
+ fclose(out);
+
+ return len;
+}
+
+const char* DKIM_ErrorResult(const int res)
+{
+ const char* errormsg = "";
+
+ switch (res) {
+ case DKIM_FAIL:
+ errormsg = " (verify error: message is suspicious)";
+ break;
+ case DKIM_BAD_SYNTAX:
+ errormsg = " (signature error: could not parse or has bad tags/values)";
+ break;
+ case DKIM_SIGNATURE_BAD:
+ errormsg = " (signature error: RSA/ED25519 verify failed)";
+ break;
+ case DKIM_SIGNATURE_BAD_BUT_TESTING:
+ errormsg = " (signature error: RSA/ED25519 verify failed but testing)";
+ break;
+ case DKIM_SIGNATURE_EXPIRED:
+ errormsg = " (signature error: signature x= value expired)";
+ break;
+ case DKIM_SELECTOR_INVALID:
+ errormsg = " (signature error: selector doesn't parse or contains invalid values)";
+ break;
+ case DKIM_SELECTOR_GRANULARITY_MISMATCH:
+ errormsg = " (signature error: selector g= doesn't match i=)";
+ break;
+ case DKIM_SELECTOR_KEY_REVOKED:
+ errormsg = " (signature error: revoked p= empty)";
+ break;
+ case DKIM_SELECTOR_DOMAIN_NAME_TOO_LONG:
+ errormsg = " (dns error: selector domain name too long to request)";
+ break;
+ case DKIM_SELECTOR_DNS_TEMP_FAILURE:
+ errormsg = " (dns error: temporary dns failure requesting selector)";
+ break;
+ case DKIM_SELECTOR_DNS_PERM_FAILURE:
+ errormsg = " (dns error: permanent dns failure requesting selector)";
+ break;
+ case DKIM_SELECTOR_PUBLIC_KEY_INVALID:
+ errormsg = " (signature error: selector p= value invalid or wrong format)";
+ break;
+ case DKIM_NO_SIGNATURES:
+ errormsg = " (process error: no signatures)";
+ break;
+ case DKIM_NO_VALID_SIGNATURES:
+ errormsg = " (process error: no valid signatures)";
+ break;
+ case DKIM_BODY_HASH_MISMATCH:
+ errormsg = " (signature verify error: message body does not hash to bh= value)";
+ break;
+ case DKIM_SELECTOR_ALGORITHM_MISMATCH:
+ errormsg = " (signature error: selector h= doesn't match signature a=)";
+ break;
+ case DKIM_STAT_INCOMPAT:
+ errormsg = " (signature error: incompatible v= value)";
+ break;
+ case DKIM_UNSIGNED_FROM:
+ errormsg = " (signature error: not all message's From headers in signature)";
+ break;
+ case DKIM_OUT_OF_MEMORY:
+ errormsg = " (internal error: memory allocation failed)";
+ break;
+ case DKIM_INVALID_CONTEXT:
+ errormsg = " (internal error: DKIMContext structure invalid for this operation)";
+ break;
+ case DKIM_NO_SENDER:
+ errormsg = " (signing error: Could not find From: or Sender: header in message)";
+ break;
+ case DKIM_BAD_PRIVATE_KEY:
+ errormsg = " (signing error: Could not parse private key)";
+ break;
+ case DKIM_BUFFER_TOO_SMALL:
+ errormsg = " (signing error: Buffer passed in is not large enough)";
+ break;
+ }
+
+ return errormsg;
+}
+
+SignatureInfo::SignatureInfo(bool s)
+{
+ VerifiedBodyCount = 0;
+ UnverifiedBodyCount = 0;
+
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_MD_CTX_init(&m_Hdr_ctx);
+ EVP_MD_CTX_init(&m_Bdy_ctx);
+#else
+ m_Hdr_ctx = EVP_MD_CTX_new();
+ m_Bdy_ctx = EVP_MD_CTX_new();
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ m_Msg_ctx = EVP_MD_CTX_new();
+#endif
+ m_pSelector = NULL;
+ Status = DKIM_SUCCESS;
+ m_nHash = 0;
+ EmptyLineCount = 0;
+ m_SaveCanonicalizedData = s;
+}
+
+SignatureInfo::~SignatureInfo()
+{
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_MD_CTX_cleanup(&m_Hdr_ctx);
+ EVP_MD_CTX_cleanup(&m_Bdy_ctx);
+#else
+ /** FIXME: No free but reset ! **/
+ EVP_MD_CTX_reset(m_Hdr_ctx);
+ EVP_MD_CTX_reset(m_Bdy_ctx);
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ EVP_MD_CTX_reset(m_Msg_ctx);
+#endif
+}
+
+inline bool isswsp(char ch)
+{
+ return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Parse a DKIM tag-list. Returns true for success
+//
+////////////////////////////////////////////////////////////////////////////////
+bool ParseTagValueList(char *tagvaluelist,const char *wanted[],char *values[])
+{
+ char *s = tagvaluelist;
+
+ for (;;) {
+ // skip whitespace
+ while (isswsp(*s))
+ s++;
+
+ // if at the end of the string, return success. Note: this allows a list with no entries
+ if (*s == '\0')
+ return true;
+
+ // get tag name
+ if (!isalpha(*s))
+ return false;
+
+ char *tag = s;
+ do {
+ s++;
+ } while (isalnum(*s) || *s == '-');
+
+ char *endtag = s;
+
+ // skip whitespace before equals
+ while (isswsp(*s))
+ s++;
+
+ // next character must be equals
+ if (*s != '=')
+ return false;
+ s++;
+
+ // null-terminate tag name
+ *endtag = '\0';
+
+ // skip whitespace after equals
+ while (isswsp(*s))
+ s++;
+
+ // get tag value
+ char *value = s;
+
+ while (*s != ';' && ((*s == '\t' || *s == '\r' || *s == '\n') || (*s >= ' ' && *s <= '~')))
+ s++;
+
+ char *e = s;
+
+ // make sure the next character is the null terminator (which means we're done) or a semicolon (not done)
+ bool done = false;
+ if (*s == '\0')
+ done = true;
+ else {
+ if (*s != ';')
+ return false;
+ s++;
+ }
+
+ // skip backwards past any trailing whitespace
+ while (e > value && isswsp(e[-1]))
+ e--;
+
+ // null-terminate tag value
+ *e = '\0';
+
+ // check to see if we want this tag
+ for (unsigned i = 0; wanted[i] != NULL; i++) {
+ if (strcmp(wanted[i],tag) == 0) {
+ // return failure if we already have a value for this tag (duplicates not allowed)
+ if (values[i] != NULL)
+ return false;
+ values[i] = value;
+ break;
+ }
+ }
+
+ if (done)
+ return true;
+ }
+}
+////////////////////////////////////////////////////////////////////////////////
+//
+// Convert hex char to value (0-15)
+//
+////////////////////////////////////////////////////////////////////////////////
+char Tohex(char ch)
+{
+ if (ch >= '0' && ch <= '9')
+ return (ch - '0');
+ else if (ch >= 'A' && ch <= 'F')
+ return (ch - 'A' + 10);
+ else if (ch >= 'a' && ch <= 'f')
+ return (ch - 'a' + 10);
+ else {
+ assert(0);
+ return 0;
+ }
+}
+////////////////////////////////////////////////////////////////////////////////
+//
+// Decode quoted printable string in-place
+//
+////////////////////////////////////////////////////////////////////////////////
+void DecodeQuotedPrintable(char* ptr)
+{
+ char *s = ptr;
+ while (*s != '\0' && *s != '=')
+ s++;
+
+ if (*s == '\0')
+ return;
+
+ char *d = s;
+ do {
+ if (*s == '=' && isxdigit(s[1]) && isxdigit(s[2])) {
+ *d++ = (Tohex(s[1]) << 4) | Tohex(s[2]);
+ s += 3;
+ } else {
+ *d++ = *s++;
+ }
+ } while (*s != '\0');
+ *d = '\0';
+}
+////////////////////////////////////////////////////////////////////////////////
+//
+// Decode base64 string in-place, returns number of bytes output
+//
+////////////////////////////////////////////////////////////////////////////////
+unsigned DecodeBase64(char *ptr)
+{
+ static const char base64_table[256] = {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
+ -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
+
+ unsigned char* s = (unsigned char* )ptr;
+ unsigned char* d = (unsigned char* )ptr;
+ unsigned b64accum = 0;
+ unsigned char b64shift = 0;
+
+ while (*s != '\0') {
+ unsigned char value = base64_table[*s++];
+ if ((signed char) value >= 0) {
+ b64accum = (b64accum << 6) | value;
+ b64shift += 6;
+ if (b64shift >= 8) {
+ b64shift -= 8;
+ *d++ = (b64accum >> b64shift);
+ }
+ }
+ }
+
+ return (char* )d-ptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Match a string with a pattern (used for g= value)
+// Supports a single, optional "*" wildcard character.
+//
+////////////////////////////////////////////////////////////////////////////////
+bool WildcardMatch(const char *p, const char *s)
+{
+ // special case: An empty "g=" value never matches any addresses
+ if (*p == '\0')
+ return false;
+
+ const char* wildcard = strchr(p,'*');
+ if (wildcard == NULL) {
+ return strcmp(s, p) == 0;
+ } else {
+ unsigned beforewildcardlen = wildcard - p;
+ unsigned afterwildcardlen = strlen(wildcard + 1);
+ unsigned slen = strlen(s);
+ return (slen >= beforewildcardlen + afterwildcardlen) &&
+ (strncmp(s,p,beforewildcardlen) == 0) && strcmp(s + slen - afterwildcardlen,wildcard + 1) == 0;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Parse addresses from a string. Returns true if at least one address found
+//
+////////////////////////////////////////////////////////////////////////////////
+bool ParseAddresses(string str,vector<string> &Addresses)
+{
+ char* s = (char* )str.c_str();
+
+ while (*s != '\0') {
+ char* start = s;
+ char* from = s;
+ char* to = s;
+ char* lt = NULL; // pointer to less than character (<) which starts the address if found
+
+ while (*from != '\0') {
+ if (*from == '(') {
+ // skip over comment
+ from++;
+ for (int depth = 1; depth != 0; from++) {
+ if (*from == '\0')
+ break;
+ else if (*from == '(')
+ depth++;
+ else if (*from == ')')
+ depth--;
+ else if (*from == '\\' && from[1] != '\0')
+ from++;
+ }
+ }
+ else if (*from == ')') {
+ // ignore closing parenthesis outside of comment
+ from++;
+ } else if (*from == ',' || *from == ';') {
+ // comma/semicolon ends the address
+ from++;
+ break;
+ }
+ else if (*from == ' ' || *from == '\t' || *from == '\r' || *from == '\n') {
+ // ignore whitespace
+ from++;
+ } else if (*from == '"') {
+ // copy the contents of a quoted string
+ from++;
+ while (*from != '\0') {
+ if (*from == '"') {
+ from++;
+ break;
+ } else if (*from == '\\' && from[1] != '\0')
+ *to++ = *from++;
+ *to++ = *from++;
+ }
+ } else if (*from == '\\' && from[1] != '\0') {
+ // copy quoted-pair
+ *to++ = *from++;
+ *to++ = *from++;
+ } else {
+ // copy any other char
+ *to = *from++;
+ // save pointer to '<' for later...
+ if (*to == '<')
+ lt = to;
+ to++;
+ }
+ }
+
+ *to = '\0';
+
+ // if there's < > get what's inside
+ if (lt != NULL) {
+ start = lt+1;
+ char *gt = strchr(start, '>');
+ if (gt != NULL)
+ *gt = '\0';
+ } else {
+ // look for and strip group name
+ char *colon = strchr(start, ':');
+ if (colon != NULL) {
+ char *at = strchr(start, '@');
+ if (at == NULL || colon < at)
+ start = colon+1;
+ }
+ }
+
+ if (*start != '\0' && strchr(start, '@') != NULL) {
+ Addresses.push_back(start); // save address
+ }
+
+ s = from;
+ }
+
+ return !Addresses.empty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+CDKIMVerify::CDKIMVerify()
+{
+ m_pfnSelectorCallback = NULL;
+// m_pfnPracticesCallback = NULL;
+ m_HonorBodyLengthTag = false;
+ m_CheckPractices = false;
+// Kai:
+// m_SubjectIsRequired = true;
+ m_SubjectIsRequired = false;
+ m_SaveCanonicalizedData = false;
+ m_AllowUnsignedFromHeaders = false;
+}
+
+CDKIMVerify::~CDKIMVerify() {} // Destructor
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Init - save the options
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMVerify::Init(DKIMVerifyOptions* pOptions)
+{
+ int nRet = CDKIMBase::Init();
+
+ m_pfnSelectorCallback = pOptions->pfnSelectorCallback;
+ // m_pfnPracticesCallback = pOptions->pfnPracticesCallback;
+
+ m_HonorBodyLengthTag = pOptions->nHonorBodyLengthTag != 0;
+ m_CheckPractices = pOptions->nCheckPractices != 0;
+// Kai:
+// m_SubjectIsRequired = pOptions->nSubjectRequired == 0;
+ m_SubjectIsRequired = pOptions->nSubjectRequired == 1;
+ m_SaveCanonicalizedData = pOptions->nSaveCanonicalizedData != 0;
+ m_AllowUnsignedFromHeaders = pOptions->nAllowUnsignedFromHeaders != 0;
+
+ return nRet;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GetResults - return the pass/fail/neutral verification result
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMVerify::GetResults(void)
+{
+ // char mdi[128];
+ // char digi[128];
+
+ ProcessFinal();
+
+ unsigned char *SignMsg;
+ unsigned SuccessCount = 0;
+ int TestingFailures = 0;
+ int RealFailures = 0;
+ int res = 0;
+
+ list<string> SuccessfulDomains; // can contain duplicates
+
+ for (list<SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) {
+ if (i->Status == DKIM_SUCCESS) {
+ if (!i->BodyHashData.empty()) { // FIRST: Get the body hash
+ unsigned char md[EVP_MAX_MD_SIZE];
+ unsigned len = 0;
+
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ res = EVP_DigestFinal(&i->m_Bdy_ctx,md,&len);
+#else
+ res = EVP_DigestFinal_ex(i->m_Bdy_ctx,md,&len);
+ EVP_MD_CTX_reset(i->m_Bdy_ctx);
+#endif
+ // dig_ascii(digi,md,32);
+ // dig_ascii(mdi,(unsigned const char *)i->BodyHashData.data(),32);
+
+ if (!res || len != i->BodyHashData.length() || memcmp(i->BodyHashData.data(),md,len) != 0) {
+ // body hash mismatch
+
+ // if the selector is in testing mode...
+ if (i->m_pSelector->Testing) {
+ i->Status = DKIM_SIGNATURE_BAD_BUT_TESTING; // todo: make a new error code for this?
+ TestingFailures++;
+ } else {
+ i->Status = DKIM_BODY_HASH_MISMATCH;
+ RealFailures++;
+ }
+ continue; // next signature
+ }
+ } else {
+ // hash CRLF separating the body from the signature
+ i->Hash("\r\n",2);
+ }
+
+ // SECOND: Fetch the signature
+
+ string sSignedSig = i->Header;
+ string sSigValue = sSignedSig.substr(sSignedSig.find(':') + 1);
+
+ static const char* tags[] = {"b",NULL};
+ char* values[sizeof(tags)/sizeof(tags[0])] = {NULL};
+
+ char* pSigValue = (char* ) sSigValue.c_str(); // our signature
+ if (ParseTagValueList(pSigValue,tags,values) && values[0] != NULL) {
+ sSignedSig.erase(15 + values[0] - pSigValue,strlen(values[0]));
+ }
+
+ if (i->HeaderCanonicalization == DKIM_CANON_RELAXED) {
+ sSignedSig = RelaxHeader(sSignedSig);
+ }
+ else if (i->HeaderCanonicalization == DKIM_CANON_NOWSP) {
+ RemoveSWSP(sSignedSig);
+ // convert "DKIM-Signature" to lower case
+ sSignedSig.replace(0,14,"dkim-signature",14);
+ }
+
+ i->Hash(sSignedSig.c_str(),sSignedSig.length()); // include generated DKIM signature header
+ assert(i->m_pSelector != NULL);
+
+ if (EVP_PKEY_base_id(i->m_pSelector->PublicKey) != EVP_PKEY_ED25519)
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ res = EVP_VerifyFinal(&i->m_Hdr_ctx,(unsigned char *)i->SignatureData.data(),i->SignatureData.length(),i->m_pSelector->PublicKey);
+#else
+ res = EVP_VerifyFinal(i->m_Hdr_ctx,(unsigned char *)i->SignatureData.data(),i->SignatureData.length(),i->m_pSelector->PublicKey);
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ else if (EVP_PKEY_base_id(i->m_pSelector->PublicKey) == EVP_PKEY_ED25519) {
+ EVP_DigestVerifyInit(i->m_Msg_ctx,NULL,NULL,NULL,i->m_pSelector->PublicKey); // late initialization
+
+ SignMsg = (unsigned char *) SigHdr.data();
+ res = EVP_DigestVerify(i->m_Msg_ctx,(unsigned char *)i->SignatureData.data(),(size_t)i->SignatureData.length(),
+ SignMsg,m_SigHdr);
+ }
+#endif
+
+ if (res == 1) {
+ if (i->UnverifiedBodyCount == 0)
+ i->Status = DKIM_SUCCESS;
+ else
+ i->Status = DKIM_SUCCESS_BUT_EXTRA;
+ SuccessCount++;
+ SuccessfulDomains.push_back(i->Domain);
+ } else {
+ // if the selector is in testing mode...
+ if (i->m_pSelector->Testing) {
+ i->Status = DKIM_SIGNATURE_BAD_BUT_TESTING;
+ TestingFailures++;
+ } else {
+ i->Status = DKIM_SIGNATURE_BAD;
+ RealFailures++;
+ }
+ }
+ } else if (i->Status == DKIM_SELECTOR_GRANULARITY_MISMATCH ||
+ i->Status == DKIM_SELECTOR_ALGORITHM_MISMATCH ||
+ i->Status == DKIM_SELECTOR_KEY_REVOKED) {
+ // treat these as failures
+ // todo: maybe see if the selector is in testing mode?
+ RealFailures++;
+ }
+ } // loop over signature infos done
+
+
+ // get the From address's domain if we might need it
+ string sFromDomain;
+ if (SuccessCount > 0 || m_CheckPractices) {
+ for (list<string>::iterator i = HeaderList.begin(); i != HeaderList.end(); ++i) {
+ if (_strnicmp(i->c_str(),"From",4) == 0) {
+ // skip over whitespace between the header name and :
+ const char* s = i->c_str() + 4;
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (*s == ':') {
+ vector<string> Addresses;
+ if (ParseAddresses(s + 1, Addresses)) {
+ unsigned atpos = Addresses[0].find('@');
+ sFromDomain = Addresses[0].substr(atpos + 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // if a signature from the From domain verified successfully, return success now
+ // without checking the author domain signing practices
+ if (SuccessCount > 0 && !sFromDomain.empty()) {
+ for (list<string>::iterator i = SuccessfulDomains.begin(); i != SuccessfulDomains.end(); ++i) {
+ // see if the successful domain is the same as or a parent of the From domain
+ if (i->length() > sFromDomain.length())
+ continue;
+ if (_stricmp(i->c_str(),sFromDomain.c_str() + sFromDomain.length() - i->length()) != 0)
+ continue;
+ if (i->length() == sFromDomain.length() || sFromDomain.c_str()[sFromDomain.length() - i->length() - 1] == '.') {
+ return SuccessCount == Signatures.size() ? DKIM_SUCCESS : DKIM_PARTIAL_SUCCESS;
+ }
+ }
+ }
+
+ /* Removed obsolete ADSP check */
+
+ // return neutral for everything else
+ return DKIM_NEUTRAL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Hash - update the hash or update the Ed25519 signature input
+//
+////////////////////////////////////////////////////////////////////////////////
+void SignatureInfo::Hash(const char* szBuffer,unsigned nBufLength,bool IsBody)
+{
+#if 0
+ /** START DEBUG CODE **/
+ if(nBufLength == 2 && szBuffer[0] == '\r' && szBuffer[1] == '\n')
+ {
+ printf("[CRLF]\n");
+ } else {
+ char* szDbg = new char[nBufLength+1];
+ strncpy(szDbg, szBuffer, nBufLength);
+ szDbg[nBufLength] = '\0';
+ printf("[%s]\n", szDbg);
+ }
+ /** END DEBUG CODE **/
+#endif
+
+ if (IsBody && BodyLength != (unsigned) -1) { // trick: 2's complement
+ VerifiedBodyCount += nBufLength;
+ if (VerifiedBodyCount > BodyLength) {
+ nBufLength = BodyLength - (VerifiedBodyCount - nBufLength);
+ UnverifiedBodyCount += VerifiedBodyCount - BodyLength;
+ VerifiedBodyCount = BodyLength;
+ if (nBufLength == 0) return;
+ }
+ }
+
+ if (IsBody && !BodyHashData.empty()) {
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_DigestUpdate(&m_Bdy_ctx,szBuffer,nBufLength);
+ } else {
+ EVP_VerifyUpdate(&m_Hdr_ctx,szBuffer,nBufLength);
+#else
+ EVP_DigestUpdate(m_Bdy_ctx,szBuffer,nBufLength);
+ } else {
+ EVP_VerifyUpdate(m_Hdr_ctx,szBuffer,nBufLength);
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ SigHdr.append(szBuffer,nBufLength);
+ m_SigHdr += nBufLength;
+#endif
+ }
+
+ if (m_SaveCanonicalizedData) {
+ CanonicalizedData.append(szBuffer,nBufLength);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessHeaders - Look for DKIM-Signatures and start processing them
+// look for DKIM-Signature header(s)
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMVerify::ProcessHeaders(void)
+{
+ for (list<string>::iterator i = HeaderList.begin(); i != HeaderList.end(); ++i) {
+ if (strlen(i->c_str()) < 14) continue; // too short
+ if (_strnicmp(i->c_str(),"DKIM-Signature",14) == 0) {
+ // skip over whitespace between the header name and :
+ const char *s = i->c_str() + 14;
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (*s == ':') {
+ // found
+ SignatureInfo sig(m_SaveCanonicalizedData);
+ sig.Status = ParseDKIMSignature(*i,sig);
+ Signatures.push_back(sig); // save signature
+
+ if (Signatures.size() >= MAX_SIGNATURES)
+ break;
+ }
+ }
+ }
+
+ if (Signatures.empty())
+ return DKIM_NO_SIGNATURES;
+
+ bool ValidSigFound = false;
+
+ for (list<SignatureInfo>::iterator s = Signatures.begin(); s != Signatures.end(); ++s) {
+ SignatureInfo &sig = *s;
+ if (sig.Status != DKIM_SUCCESS) continue;
+ SelectorInfo &sel = GetSelector(sig.Selector,sig.Domain);
+ sig.m_pSelector = &sel;
+
+ if (sel.Status != DKIM_SUCCESS) {
+ sig.Status = sel.Status;
+ } else {
+ // check the granularity
+ if (!WildcardMatch(sel.Granularity.c_str(),sig.IdentityLocalPart.c_str()))
+ sig.Status = DKIM_SELECTOR_GRANULARITY_MISMATCH; // this error causes the signature to fail
+
+ // check the hash algorithm
+ if ((sig.m_nHash == DKIM_HASH_SHA1 && !sel.AllowSHA1) ||
+ (sig.m_nHash == DKIM_HASH_SHA256 && !sel.AllowSHA256))
+ sig.Status = DKIM_SELECTOR_ALGORITHM_MISMATCH; // causes signature to fail
+
+ // check for same domain
+ if (sel.SameDomain && _stricmp(sig.Domain.c_str(),sig.IdentityDomain.c_str()) != 0)
+ sig.Status = DKIM_BAD_SYNTAX;
+ }
+
+ if (sig.Status != DKIM_SUCCESS) continue;
+
+ // initialize the hashes
+ if (sig.m_nHash == DKIM_HASH_SHA1) {
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_VerifyInit(&sig.m_Hdr_ctx,EVP_sha1());
+ EVP_DigestInit(&sig.m_Bdy_ctx,EVP_sha1());
+#else
+ EVP_VerifyInit_ex(sig.m_Hdr_ctx,EVP_sha1(),NULL);
+ EVP_DigestInit_ex(sig.m_Bdy_ctx,EVP_sha1(),NULL);
+#endif
+ }
+ if (sig.m_nHash == DKIM_HASH_SHA256) {
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_VerifyInit(&sig.m_Hdr_ctx,EVP_sha256());
+ EVP_DigestInit(&sig.m_Bdy_ctx,EVP_sha256());
+#else
+ EVP_VerifyInit_ex(sig.m_Hdr_ctx,EVP_sha256(),NULL);
+ EVP_DigestInit_ex(sig.m_Bdy_ctx,EVP_sha256(),NULL);
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ SigHdr.assign("");
+ m_SigHdr = 0;
+ }
+#endif
+
+ // compute the hash of the header
+ vector<list<string>::reverse_iterator> used;
+
+ for (vector<string>::iterator x = sig.SignedHeaders.begin(); x != sig.SignedHeaders.end(); ++x) {
+ list<string>::reverse_iterator i;
+ for (i = HeaderList.rbegin(); i != HeaderList.rend(); ++i) {
+ if (_strnicmp(i->c_str(),x->c_str(),x->length()) == 0) {
+ // skip over whitespace between the header name and :
+ const char* s = i->c_str()+x->length();
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (*s == ':' && find(used.begin(),used.end(),i) == used.end())
+ break;
+ }
+ }
+
+ if (i != HeaderList.rend()) {
+ used.push_back(i);
+
+ // hash this header
+ if (sig.HeaderCanonicalization == DKIM_CANON_SIMPLE) {
+ sig.Hash(i->c_str(),i->length());
+ } else if (sig.HeaderCanonicalization == DKIM_CANON_RELAXED) {
+ string sTemp = RelaxHeader(*i);
+ sig.Hash(sTemp.c_str(),sTemp.length());
+ } else if (sig.HeaderCanonicalization == DKIM_CANON_NOWSP) {
+ string sTemp = *i;
+ RemoveSWSP(sTemp);
+
+ // convert characters before ':' to lower case
+ for (char* s = (char*)sTemp.c_str(); *s != '\0' && *s != ':'; s++) {
+ if (*s >= 'A' && *s <= 'Z')
+ *s += 'a' - 'A';
+ }
+ sig.Hash(sTemp.c_str(),sTemp.length());
+ }
+ sig.Hash("\r\n",2);
+ }
+ }
+
+ if (sig.BodyHashData.empty()) {
+ // hash CRLF separating headers from body
+ sig.Hash("\r\n",2);
+ }
+
+ if (!m_AllowUnsignedFromHeaders) {
+ // make sure the message has no unsigned From headers
+ list<string>::reverse_iterator i;
+ for (i = HeaderList.rbegin(); i != HeaderList.rend(); ++i) {
+ if (_strnicmp(i->c_str(),"From",4) == 0) {
+ // skip over whitespace between the header name and :
+ const char *s = i->c_str() + 4;
+ while (*s == ' ' || *s == '\t')
+ s++;
+ if (*s == ':') {
+ if (find(used.begin(),used.end(),i) == used.end()) {
+ // this From header was not signed
+ break;
+ }
+ }
+ }
+ }
+ if (i != HeaderList.rend()) {
+ // treat signature as invalid
+ sig.Status = DKIM_UNSIGNED_FROM;
+ continue;
+ }
+ }
+
+ ValidSigFound = true;
+ }
+
+ if (!ValidSigFound)
+ return DKIM_NO_VALID_SIGNATURES;
+
+ return DKIM_SUCCESS;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Strictly parse an unsigned integer. Don't allow spaces, negative sign,
+// 0x prefix, etc. Values greater than 2^32-1 are capped at 2^32-1
+//
+////////////////////////////////////////////////////////////////////////////////
+bool ParseUnsigned(const char *s, unsigned *result)
+{
+ unsigned temp = 0, last = 0;
+ bool overflowed = false;
+
+ do {
+ if (*s < '0' || *s > '9')
+ return false; // returns false for an initial '\0'
+
+ temp = temp * 10 + (*s - '0');
+ if (temp < last)
+ overflowed = true;
+ last = temp;
+
+ s++;
+ } while (*s != '\0');
+
+ *result = overflowed ? -1 : temp;
+ return true;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ParseDKIMSignature - Parse a DKIM-Signature header field
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMVerify::ParseDKIMSignature(const string& sHeader,SignatureInfo &sig)
+{
+ // for strtok_r()
+ char *saveptr;
+
+ // save header for later
+ sig.Header = sHeader;
+
+ string sValue = sHeader.substr(sHeader.find(':') + 1);
+
+ static const char *tags[] = {"v","a","b","d","h","s","c","i","l","q","t","x","bh",NULL};
+ char *values[sizeof(tags)/sizeof(tags[0])] = {NULL};
+
+ if (!ParseTagValueList((char*) sValue.c_str(),tags,values))
+ return DKIM_BAD_SYNTAX;
+
+ // check signature version
+ if (values[0] == NULL) return DKIM_BAD_SYNTAX;
+
+ // signature MUST have a=, b=, d=, h=, s=
+ if (values[1] == NULL || values[2] == NULL || values[3] == NULL || values[4] == NULL || values[5] == NULL)
+ return DKIM_BAD_SYNTAX;
+
+ // algorithm ('a=') can be "rsa-sha1" or "rsa-sha256" or "ed25519"
+ if (strcmp(values[1],"rsa-sha1") == 0) {
+ sig.m_nHash = DKIM_HASH_SHA1;
+ } else if (strcmp(values[1],"rsa-sha256") == 0) {
+ sig.m_nHash = DKIM_HASH_SHA256;
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ } else if (strcmp(values[1],"ed25519-sha256") == 0) {
+ sig.m_nHash = DKIM_HASH_SHA256;
+#endif
+ } else {
+ return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for unknown algorithm
+ }
+
+ // make sure the signature data is not empty: b=[...]
+ unsigned SigDataLen = DecodeBase64(values[2]);
+
+ if (SigDataLen == 0)
+ return DKIM_BAD_SYNTAX;
+
+ sig.SignatureData.assign(values[2],SigDataLen);
+
+ // check for body hash in DKIM header: bh=[...];
+ unsigned BodyHashLen = DecodeBase64(values[12]);
+ if (BodyHashLen == 0) return DKIM_BAD_SYNTAX;
+ sig.BodyHashData.assign(values[12],BodyHashLen);
+
+ // domain must not be empty
+ if (*values[3] == '\0')
+ return DKIM_BAD_SYNTAX;
+ sig.Domain = values[3];
+
+ // signed headers must not be empty (more verification is done later)
+ if (*values[4] == '\0')
+ return DKIM_BAD_SYNTAX;
+
+ // selector must not be empty
+ if (*values[5] == '\0')
+ return DKIM_BAD_SYNTAX;
+ sig.Selector = values[5];
+
+ // canonicalization
+ if (values[6] == NULL) {
+ sig.HeaderCanonicalization = sig.BodyCanonicalization = DKIM_CANON_SIMPLE;
+ } else {
+ char* slash = strchr(values[6],'/');
+ if (slash != NULL)
+ *slash = '\0';
+
+ if (strcmp(values[6],"simple") == 0)
+ sig.HeaderCanonicalization = DKIM_CANON_SIMPLE;
+ else if (strcmp(values[6],"relaxed") == 0)
+ sig.HeaderCanonicalization = DKIM_CANON_RELAXED;
+ else
+ return DKIM_BAD_SYNTAX;
+
+ if (slash == NULL || strcmp(slash + 1,"simple") == 0)
+ sig.BodyCanonicalization = DKIM_CANON_SIMPLE;
+ else if (strcmp(slash + 1,"relaxed") == 0)
+ sig.BodyCanonicalization = DKIM_CANON_RELAXED;
+ else
+ return DKIM_BAD_SYNTAX;
+ }
+
+ // identity
+ if (values[7] == NULL) {
+ sig.IdentityLocalPart.erase();
+ sig.IdentityDomain = sig.Domain;
+ } else {
+ // quoted-printable decode the value
+ DecodeQuotedPrintable(values[7]);
+
+ // must have a '@' separating the local part from the domain
+ char* at = strchr(values[7],'@');
+ if (at == NULL)
+ return DKIM_BAD_SYNTAX;
+ *at = '\0';
+
+ char* ilocalpart = values[7];
+ char* idomain = at + 1;
+
+ // i= domain must be the same as or a subdomain of the d= domain
+ int idomainlen = strlen(idomain);
+ int ddomainlen = strlen(values[3]);
+
+ // todo: maybe create a new error code for invalid identity domain
+ if (idomainlen < ddomainlen)
+ return DKIM_BAD_SYNTAX;
+ if (_stricmp(idomain + idomainlen - ddomainlen,values[3]) != 0)
+ return DKIM_BAD_SYNTAX;
+ if (idomainlen > ddomainlen && idomain[idomainlen - ddomainlen - 1] != '.')
+ return DKIM_BAD_SYNTAX;
+
+ sig.IdentityLocalPart = ilocalpart;
+ sig.IdentityDomain = idomain;
+ }
+
+ // body count
+ if (values[8] == NULL || !m_HonorBodyLengthTag) {
+ sig.BodyLength = (unsigned) -1;
+ } else {
+ if (!ParseUnsigned(values[8],&sig.BodyLength))
+ return DKIM_BAD_SYNTAX;
+ }
+
+ // query methods
+ if (values[9] != NULL) {
+ // make sure "dns" is in the list
+ bool HasDNS = false;
+ char* s = strtok_r(values[9],":",&saveptr);
+ while (s != NULL) {
+ if (strncmp(s,"dns",3) == 0 && (s[3] == '\0' || s[3] == '/')) {
+ HasDNS = true;
+ break;
+ }
+ s = strtok_r(NULL,": \t",&saveptr); /* FIXME */
+// s = strtok_r(NULL,": ",&saveptr); /* FIXME */
+ }
+ if (!HasDNS)
+ return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for unknown query method
+ }
+
+ // signature time
+ unsigned SignedTime = -1;
+ if (values[10] != NULL) {
+ if (!ParseUnsigned(values[10],&SignedTime))
+ return DKIM_BAD_SYNTAX;
+ }
+
+ // expiration time
+ if (values[11] == NULL) {
+ sig.ExpireTime = (unsigned) -1; // common trick; feh
+ } else {
+ if (!ParseUnsigned(values[11],&sig.ExpireTime))
+ return DKIM_BAD_SYNTAX;
+
+ if (sig.ExpireTime != (unsigned) -1) {
+ // the value of x= MUST be greater than the value of t= if both are present
+ if (SignedTime != (unsigned) -1 && sig.ExpireTime <= SignedTime)
+ return DKIM_BAD_SYNTAX;
+
+ // todo: if possible, use the received date/time instead of the current time
+ unsigned curtime = time(NULL);
+ if (curtime > sig.ExpireTime)
+ return DKIM_SIGNATURE_EXPIRED;
+ }
+ }
+
+ // parse the signed headers list
+ bool HasFrom = false, HasSubject = false;
+ RemoveSWSP(values[4]); // header names shouldn't have spaces in them so this should be ok...
+ char* s = strtok_r(values[4],":",&saveptr);
+ while (s != NULL) {
+ if (_stricmp(s,"From") == 0)
+ HasFrom = true;
+ else if (_stricmp(s,"Subject") == 0)
+ HasSubject = true;
+
+ sig.SignedHeaders.push_back(s);
+ s = strtok_r(NULL,":",&saveptr);
+ }
+
+ if (!HasFrom)
+ return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for h= missing From
+ if (m_SubjectIsRequired && !HasSubject)
+ return DKIM_BAD_SYNTAX; // todo: maybe create a new error code for h= missing Subject
+
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// ProcessBody - Process message body data
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMVerify::ProcessBody(char* szBuffer,int nBufLength,bool bEOF)
+{
+ bool MoreBodyNeeded = false;
+
+ for (list<SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) {
+ if (i->Status == DKIM_SUCCESS) {
+ if (i->BodyCanonicalization == DKIM_CANON_SIMPLE) {
+ if (nBufLength > 0) {
+ while (i->EmptyLineCount > 0) {
+ i->Hash("\r\n",2,true);
+ i->EmptyLineCount--;
+ }
+ i->Hash(szBuffer,nBufLength,true);
+ i->Hash("\r\n",2,true);
+ } else {
+ i->EmptyLineCount++;
+ if (bEOF)
+ i->Hash("\r\n",2,true);
+ }
+ } else if (i->BodyCanonicalization == DKIM_CANON_RELAXED) {
+ CompressSWSP(szBuffer, nBufLength);
+ if (nBufLength > 0) {
+ while (i->EmptyLineCount > 0) {
+ i->Hash("\r\n",2,true);
+ i->EmptyLineCount--;
+ }
+ i->Hash(szBuffer,nBufLength,true);
+ if (!bEOF)
+ i->Hash("\r\n",2,true);
+ } else i->EmptyLineCount++;
+ } else if (i->BodyCanonicalization == DKIM_CANON_NOWSP) {
+ RemoveSWSP(szBuffer,nBufLength);
+ i->Hash(szBuffer,nBufLength,true);
+ }
+
+ if (i->UnverifiedBodyCount == 0)
+ MoreBodyNeeded = true;
+ }
+ }
+
+ if (!MoreBodyNeeded)
+ return DKIM_FINISHED_BODY;
+
+ return DKIM_SUCCESS;
+}
+
+SelectorInfo::SelectorInfo(const string &sSelector,const string &sDomain) : Domain(sDomain),Selector(sSelector)
+{
+ AllowSHA1 = true;
+ AllowSHA256 = true;
+ PublicKey = NULL;
+ Testing = false;
+ SameDomain = false;
+ Status = DKIM_SUCCESS;
+}
+
+SelectorInfo::~SelectorInfo()
+{
+ if (PublicKey != NULL) {
+ EVP_PKEY_free(PublicKey);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Parse - Parse a DKIM selector from DNS data
+//
+////////////////////////////////////////////////////////////////////////////////
+int SelectorInfo::Parse(char* Buffer)
+{
+ // for strtok_r()
+ char *saveptr;
+ char *PubKeyBase64; /*- public key Base64 encoded */
+ char ed25519PubKey[61];
+
+ static const char *tags[] = {"v","g","h","k","p","s","t","n",NULL}; // 0, 1, 2, 3, 4
+ char *values[sizeof(tags)/sizeof(tags[0])] = {NULL};
+
+ ParseTagValueList(Buffer,tags,values);
+
+ // return DKIM_SELECTOR_INVALID;
+ if (values[0] != NULL) {
+ // make sure the version is "DKIM1"
+ if (strcmp(values[0],"DKIM1") != 0)
+ return DKIM_SELECTOR_INVALID; // todo: maybe create a new error code for unsupported selector version
+
+ // make sure v= is the first tag in the response // todo: maybe don't enforce this, it seems unnecessary
+ for (unsigned j = 1; j < sizeof(values)/sizeof(values[0]); j++) {
+ if (values[j] != NULL && values[j] < values[0]) {
+ return DKIM_SELECTOR_INVALID;
+ }
+ }
+ }
+
+ // selector MUST have p= tag
+ if (values[4] == NULL)
+ return DKIM_SELECTOR_INVALID;
+
+ PubKeyBase64 = values[4]; // gotcha
+
+ // granularity -- [g= ... ]
+ if (values[1] == NULL)
+ Granularity = "*";
+ else
+ Granularity = values[1];
+
+ // hash algorithm -- [h=sha1|sha256] (not required)
+ if (values[2] == NULL) {
+ AllowSHA1 = true;
+ AllowSHA256 = true;
+ } else {
+ // MUST include "sha1" or "sha256"
+ char* s = strtok_r(values[2],":",&saveptr);
+ while (s != NULL) {
+ if (strcmp(s,"sha1") == 0)
+ { AllowSHA1 = true; AllowSHA256 = false; }
+ else if (strcmp(s,"sha256") == 0)
+ { AllowSHA256 = true; AllowSHA1 = false; }
+ s = strtok_r(NULL,":",&saveptr);
+ }
+ if (!(AllowSHA1 || AllowSHA256))
+ return DKIM_SELECTOR_INVALID; // todo: maybe create a new error code for unsupported hash algorithm
+ }
+
+ // key type -- [k=rsa|ed25519] (not required)
+ if (values[3] != NULL) {
+ // key type MUST be "rsa" or "ed25519"
+ if (strcmp(values[3],"rsa") != 0 && strcmp(values[3],"ed25519") != 0) // none of either
+ return DKIM_SELECTOR_INVALID;
+ if (strcmp(values[3],"ed25519") == 0) {
+ AllowSHA1 = false;
+ AllowSHA256 = true;
+ strcpy(ed25519PubKey,"MCowBQYDK2VwAyEA");
+ /*
+ * rfc8463
+ * since Ed25519 public keys are 256 bits long,
+ * the base64-encoded key is only 44 octets
+ */
+ if (strlen(values[4]) > 44)
+ return DKIM_SELECTOR_PUBLIC_KEY_INVALID;
+ strcat(ed25519PubKey,values[4]);
+ PubKeyBase64 = ed25519PubKey;
+ }
+ }
+
+ // service type -- [s= ...] (not required)
+ if (values[5] != NULL) {
+ // make sure "*" or "email" is in the list
+ bool ServiceTypeMatch = false;
+ char* s = strtok_r(values[5],":",&saveptr);
+ while (s != NULL) {
+ if (strcmp(s, "*") == 0 || strcmp(s,"email") == 0) {
+ ServiceTypeMatch = true;
+ break;
+ }
+ s = strtok_r(NULL,":",&saveptr);
+ }
+ if (!ServiceTypeMatch)
+ return DKIM_SELECTOR_INVALID;
+ }
+
+ // flags -- [t= ...] (not required)
+ if (values[6] != NULL) {
+ char *s = strtok_r(values[6],":",&saveptr);
+ while (s != NULL) {
+ if (strcmp(s,"y") == 0) {
+ Testing = true;
+ } else if (strcmp(s,"s") == 0) {
+ SameDomain = true;
+ }
+ s = strtok_r(NULL,":",&saveptr);
+ }
+ }
+
+ // public key data
+ unsigned PublicKeyLen = DecodeBase64(PubKeyBase64);
+
+ if (PublicKeyLen == 0) {
+ return DKIM_SELECTOR_KEY_REVOKED; // this error causes the signature to fail
+ } else {
+ const unsigned char *PublicKeyData = (unsigned char* )PubKeyBase64; // 0-terminated
+
+ EVP_PKEY *pkey = d2i_PUBKEY(NULL,&PublicKeyData,PublicKeyLen); /* retrieve and return PubKey from data */
+
+ if (pkey == NULL)
+ return DKIM_SELECTOR_PUBLIC_KEY_INVALID;
+
+ // make sure public key is the correct type (we only support rsa & ed25519)
+#if ((OPENSSL_VERSION_NUMBER < 0x10101000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ if (pkey->type == EVP_PKEY_RSA || pkey->type == EVP_PKEY_RSA2) {
+#else
+ if ((EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA) ||
+ (EVP_PKEY_base_id(pkey) == EVP_PKEY_RSA2) ||
+ (EVP_PKEY_base_id(pkey) == EVP_PKEY_ED25519)) {
+#endif
+ PublicKey = pkey;
+ } else {
+ EVP_PKEY_free(pkey);
+ return DKIM_SELECTOR_PUBLIC_KEY_INVALID;
+ }
+ }
+
+ return DKIM_SUCCESS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GetSelector - Get a DKIM selector for a domain
+//
+////////////////////////////////////////////////////////////////////////////////
+SelectorInfo& CDKIMVerify::GetSelector(const string &sSelector,const string &sDomain)
+{
+ // see if we already have this selector
+ for (list<SelectorInfo>::iterator i = Selectors.begin(); i != Selectors.end(); ++i) {
+ if (_stricmp(i->Selector.c_str(),sSelector.c_str()) == 0 && _stricmp(i->Domain.c_str(),sDomain.c_str()) == 0) {
+ return *i;
+ }
+ }
+
+ Selectors.push_back(SelectorInfo(sSelector,sDomain));
+ SelectorInfo& sel = Selectors.back();
+
+ string sFQDN = sSelector;
+ sFQDN += "._domainkey.";
+ sFQDN += sDomain;
+
+ int BufLen = 1024;
+ char Buffer[BufLen];
+
+ int DNSResult;
+
+ if (m_pfnSelectorCallback) {
+ DNSResult = m_pfnSelectorCallback(sFQDN.c_str(),Buffer,BufLen);
+ } else
+ DNSResult = _DNSGetTXT(sFQDN.c_str(),Buffer,BufLen);
+
+// Buffer++; BufLen--;
+
+ switch (DNSResult) {
+ case -1: case -2: case -3: case -5: sel.Status = DKIM_SELECTOR_DNS_TEMP_FAILURE; break;
+ case 0: case -6: sel.Status = DKIM_SELECTOR_DNS_PERM_FAILURE; break;
+ default: sel.Status = sel.Parse(Buffer);
+ }
+
+ return sel;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GetDetails - Get DKIM verification details (per signature)
+//
+////////////////////////////////////////////////////////////////////////////////
+int CDKIMVerify::GetDetails(int* nSigCount,DKIMVerifyDetails** pDetails)
+{
+ Details.clear();
+
+ for (list < SignatureInfo>::iterator i = Signatures.begin(); i != Signatures.end(); ++i) {
+ DKIMVerifyDetails d;
+ d.szSignature = (char* )i->Header.c_str();
+ d.szSignatureDomain = (char* )i->Domain.c_str();
+ d.szIdentityDomain = (char* )i->IdentityDomain.c_str();
+ d.szCanonicalizedData = (char* )i->CanonicalizedData.c_str();
+ d.nResult = i->Status;
+ Details.push_back(d);
+ }
+
+ *nSigCount = Details.size();
+ *pDetails = (*nSigCount != 0) ? &Details[0] : NULL;
+
+ return DKIM_SUCCESS;
+}
diff --git a/src/dns.c b/src/dns.c
new file mode 100644
index 0000000..3f1154b
--- /dev/null
+++ b/src/dns.c
@@ -0,0 +1,201 @@
+#include <netdb.h>
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <sys/socket.h>
+#include "ip.h"
+#include "ipalloc.h"
+#include "fmt.h"
+#include "alloc.h"
+#include "str.h"
+#include "stralloc.h"
+#include "dnsresolv.h"
+#include "case.h"
+#include "dns.h"
+#include "buffer.h"
+#include "exit.h"
+
+/**
+ @file dns.c
+ @brief DNS helpers: dns_ipplus, dns_ipalloc, dns_ip (IPv4+IPv6), dns_mxip
+ */
+
+static stralloc glue = {0};
+static stralloc ip = {0};
+
+static int dns_ipplus(ipalloc *ia,stralloc *sa,int pref)
+{
+ struct ip_mx ix;
+ int error = 0;
+ char ip4[4];
+ char ip6[16];
+ int i;
+
+ /* Case 1: sa is just IPv4 */
+
+ if (ip4_scanbracket(sa->s,ip4)) {
+ if (!stralloc_copys(&glue,sa->s)) return DNS_MEM;
+ if (!stralloc_0(&glue)) return DNS_MEM;
+ if (glue.s[0]) {
+ ix.pref = 0;
+ ix.af = AF_INET;
+ byte_copy(&ix.addr,4,ip4); // = ip; //cp
+ if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+ return 0;
+ }
+ }
+
+ /* Case 2: sa is just IPv6 */
+
+ if (ip6_scanbracket(sa->s,ip6)) {
+ if (!stralloc_copys(&glue,sa->s)) return DNS_MEM;
+ if (!stralloc_0(&glue)) return DNS_MEM;
+ if (glue.s[0]) {
+ ix.pref = 0;
+ ix.af = AF_INET6;
+ byte_copy(&ix.addr,16,ip6); // = ip; //cp
+ if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+ return 0;
+ }
+ }
+
+ /* Case 3: sa is fqdn and looking for IPv6 */
+
+ if (dns_ip6(&ip,sa) > 0) {
+ for (i = 0; i + 16 <= ip.len; i += 16) {
+ if (ip6_isv4mapped(ip.s + i)) continue;
+ ix.af = AF_INET6;
+ ix.pref = pref;
+ byte_copy(&ix.addr,16,ip.s + i); // = ip; //cp
+ str_copy(ix.mxh,sa->s); // mx hostname
+ if (!ipalloc_append(ia,&ix)) { error = DNS_MEM; break; }
+ error = 0;
+ }
+ } else
+ error = 1;
+
+ /* Case 4: sa is fqdn and looking for IPv4 */
+
+ if (dns_ip4(&ip,sa) > 0) {
+ for (i = 0; i + 4 <= ip.len; i += 4) {
+ ix.af = AF_INET;
+ ix.pref = pref;
+ byte_copy(&ix.addr,4,ip.s + i); // = ip; //cp
+ str_copy(ix.mxh,sa->s); // mx hostname
+ if (!ipalloc_append(ia,&ix)) { error = DNS_MEM; break; }
+ error = 0;
+ }
+ } else
+ error += 2;
+
+ return error;
+}
+
+int dns_ipalloc(ipalloc *ia,stralloc *sa)
+{
+ if (!ipalloc_readyplus(ia,0)) return DNS_MEM;
+ ia->len = 0;
+
+ return dns_ipplus(ia,sa,0);
+}
+
+/* dns_mxip */
+
+int dns_mxip(ipalloc *ia,stralloc *sa,unsigned long random) {
+ struct mx { stralloc sa; unsigned short p; } *mx;
+ struct ip_mx ix;
+ int nummx;
+ int i;
+ int j = 0;
+ int len;
+ int flagsoft;
+ uint16 pref;
+
+ /* Case 1: sa is just IPv4 or IPv6 */
+
+ if (!ipalloc_readyplus(ia,0)) return DNS_MEM;
+ ia->len = 0;
+
+ if (!stralloc_copys(&glue,sa->s)) return DNS_MEM;
+ if (!stralloc_0(&glue)) return DNS_MEM;
+ if (glue.s[0]) {
+ ix.pref = 0;
+ if (!glue.s[ip4_scan(glue.s,(char *)&ix.addr.ip4)] || \
+ !glue.s[ip4_scanbracket(glue.s,(char *)&ix.addr.ip4)]) {
+ ix.af = AF_INET;
+ if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+ return 0;
+ }
+ if (!glue.s[ip6_scan(glue.s,(char *)&ix.addr.ip6)] || \
+ !glue.s[ip6_scanbracket(glue.s,(char *)&ix.addr.ip6)]) {
+ ix.af = AF_INET6;
+ if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+ return 0;
+ }
+ }
+
+ /* Case 2: sa is FQDN and do a mx lookup */
+
+ DNS_INIT
+ nummx = 0;
+ len = 0;
+ i = dns_mx(&ip,sa);
+ mx = (struct mx *) alloc(i * sizeof(struct mx));
+ if (!mx) return DNS_MEM;
+
+ if (i) {
+ do {
+ j = str_chr(ip.s + len + 2,'\0'); /* several answers */
+ mx[nummx].sa.s = 0;
+ if (!stralloc_copys(&mx[nummx].sa,ip.s + len + 2)) { /* mxhost name */
+ alloc_free(mx); return DNS_MEM;
+ }
+ ip.s[len + 3] = '\0';
+ uint16_unpack_big(ip.s + len,&pref);
+ mx[nummx].p = pref;
+ len += j + 3;
+ ++nummx;
+ } while (len < ip.len);
+ }
+
+ if (!nummx) return dns_ipalloc(ia,sa); /* e.g., CNAME -> A */
+ flagsoft = 0;
+
+ while (nummx > 0) {
+ unsigned long numsame;
+ i = 0;
+ numsame = 1;
+ for (j = 1; j < nummx; ++j) {
+ if (mx[j].p < mx[i].p) {
+ i = j;
+ numsame = 1;
+ }
+ else if (mx[j].p == mx[i].p) {
+ ++numsame;
+ random = random * 69069 + 1;
+ if ((random / 2) < (2147483647 / numsame)) i = j;
+ }
+ }
+
+ switch (dns_ipplus(ia,&mx[i].sa,mx[i].p)) {
+ case -1: return DNS_MEM;
+ case -2: case -3: flagsoft = -5; break;
+ }
+
+ alloc_free(mx[i].sa.s);
+ mx[i] = mx[--nummx];
+ }
+
+ alloc_free(mx);
+ return flagsoft;
+}
+
+int dns_ip(ipalloc *ia,stralloc *sa)
+{
+
+ if (!ipalloc_readyplus(ia,0)) return DNS_MEM;
+ ia->len = 0;
+
+ return dns_ipplus(ia,sa,0);
+}
diff --git a/src/dns_tlsa.c b/src/dns_tlsa.c
new file mode 100644
index 0000000..4b674c1
--- /dev/null
+++ b/src/dns_tlsa.c
@@ -0,0 +1,53 @@
+#include "byte.h"
+#include "stralloc.h"
+#include "uint_t.h"
+#include "dns.h"
+#include "logmsg.h"
+
+static char *q = 0;
+
+int dns_tlsa_packet(stralloc *out,const char *buf,unsigned int len)
+{
+ unsigned int pos;
+ char header[12];
+ uint16 datalen;
+ uint16 numanswers;
+ int ranswers = 0;
+
+ if (!stralloc_copys(out,"")) return DNS_MEM;
+
+ pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR;
+ uint16_unpack_big(header + 6,&numanswers);
+ pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR;
+ pos += 4;
+
+ while (numanswers--) {
+ pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR;
+ pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR;
+ uint16_unpack_big(header + 8,&datalen);
+ if (byte_equal(header,2,DNS_T_TLSA))
+ if (byte_equal(header + 2,2,DNS_C_IN)) {
+ if (pos + datalen > len) return DNS_ERR;
+ if (!stralloc_catb(out,buf + pos,datalen)) return DNS_MEM;
+ }
+ pos += datalen;
+ ++ranswers;
+ }
+ if (!stralloc_0(out)) return DNS_MEM;
+
+ return ranswers;
+}
+
+int dns_tlsa(stralloc *out,const stralloc *fqdn)
+{
+ int rc = 0;
+
+ if (dns_domain_fromdot(&q,fqdn->s,fqdn->len) <= 0) return DNS_ERR;
+ if (dns_resolve(q,DNS_T_TLSA) >= 0) {
+ if ((rc = dns_tlsa_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR;
+ dns_transmit_free(&dns_resolve_tx);
+ dns_domain_free(&q);
+ }
+
+ return rc;
+}
diff --git a/src/dnscname.c b/src/dnscname.c
new file mode 100644
index 0000000..546d273
--- /dev/null
+++ b/src/dnscname.c
@@ -0,0 +1,32 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "dnsresolv.h"
+#include "exit.h"
+#include "dns.h"
+#include "logmsg.h"
+
+#define WHO "dnscname"
+
+stralloc sa = {0};
+stralloc out = {0};
+
+int main(int argc,char **argv)
+{
+ int r;
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnscname fqdn");
+
+ if (!stralloc_copys(&sa,argv[1]))
+ logmsg(WHO,111,FATAL,"out of memory");
+
+ DNS_INIT
+ if ((r = dns_cname(&out,&sa)) < 0) _exit(1);
+ if (r > 0) {
+ buffer_putflush(buffer_1,out.s,out.len);
+ buffer_putsflush(buffer_1,"\n");
+ }
+
+ _exit(0);
+}
diff --git a/src/dnsdoe.c b/src/dnsdoe.c
new file mode 100644
index 0000000..ad6b253
--- /dev/null
+++ b/src/dnsdoe.c
@@ -0,0 +1,14 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "exit.h"
+#include "dnsresolv.h"
+#include "dns.h"
+
+void dnsdoe(int r)
+{
+ switch (r) {
+ case DNS_HARD: buffer_putsflush(buffer_2,"hard error\n"); _exit(100);
+ case DNS_SOFT: buffer_putsflush(buffer_2,"soft error\n"); _exit(111);
+ case DNS_MEM: buffer_putsflush(buffer_2,"out of memory\n"); _exit(111);
+ }
+}
diff --git a/src/dnsfq.c b/src/dnsfq.c
new file mode 100644
index 0000000..a174541
--- /dev/null
+++ b/src/dnsfq.c
@@ -0,0 +1,64 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "dnsresolv.h"
+#include "dns.h"
+#include "ip.h"
+#include "exit.h"
+#include "logmsg.h"
+
+#define WHO "dnsfq"
+#define MAXCNAME 10
+
+stralloc ca = {0};
+stralloc sa = {0};
+stralloc ia = {0};
+
+int main(int argc,char **argv)
+{
+ int i, r;
+ char ip4str[IP4_FMT];
+ char ip6str[IP6_FMT];
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnsfq fqdn");
+
+ if (!stralloc_copys(&sa,argv[1]))
+ logmsg(WHO,111,FATAL,"out of memory");
+
+ DNS_INIT
+ buffer_put(buffer_1,sa.s,sa.len);
+ buffer_puts(buffer_1," ");
+ for (i = 0; i <= MAXCNAME; i++) {
+ if ((r = dns_cname(&ca,&sa)) < 0) _exit(1);
+ if (r > 0) {
+ if (!stralloc_copy(&sa,&ca))
+ logmsg(WHO,111,FATAL,"out of memory");
+ buffer_puts(buffer_1,"-> ");
+ buffer_put(buffer_1,sa.s,sa.len);
+ buffer_puts(buffer_1," ");
+ }
+ else break;
+ }
+ buffer_putsflush(buffer_1,"\n");
+
+ if ((i = dns_ip6(&ia,&sa)) > 0) {
+ for (i = 0; i + 16 <= ia.len; i += 16) {
+ if (ip6_isv4mapped(ia.s + i)) continue;
+ buffer_put(buffer_1,ip6str,ip6_fmt(ip6str,ia.s + i));
+ buffer_puts(buffer_1,"\n");
+ }
+ }
+
+ if ((i = dns_ip4(&ia,&sa)) > 0) {
+ for (i = 0; i + 4 <= ia.len;i += 4) {
+ buffer_put(buffer_1,ip4str,ip4_fmt(ip4str,ia.s + i));
+ buffer_puts(buffer_1,"\n");
+ }
+ }
+ buffer_flush(buffer_1);
+
+ _exit(0);
+}
diff --git a/src/dnsip.c b/src/dnsip.c
new file mode 100644
index 0000000..2c84d04
--- /dev/null
+++ b/src/dnsip.c
@@ -0,0 +1,46 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "dnsresolv.h"
+#include "ip.h"
+#include "exit.h"
+#include "fmt.h"
+#include "dns.h"
+#include "logmsg.h"
+
+#define WHO "dnsip"
+
+stralloc sa = {0};
+stralloc out = {0};
+
+int main(int argc, char **argv)
+{
+ int i;
+ char ip4str[IP4_FMT];
+ char ip6str[IP6_FMT];
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnsip fqdn");
+
+ if (!stralloc_copys(&sa,argv[1]))
+ logmsg(WHO,111,FATAL,"out of memory");
+
+ DNS_INIT
+ if (dns_ip6(&out,&sa) > 0) /* IPv6 first */
+ for (i = 0; i + 16 <= out.len; i += 16) {
+ if (ip6_isv4mapped(out.s + i)) continue;
+ buffer_put(buffer_1,ip6str,ip6_fmt(ip6str,out.s + i));
+ buffer_puts(buffer_1,"\n");
+ }
+
+ if (dns_ip4(&out,&sa) > 0)
+ for (i = 0; i + 4 <= out.len;i += 4) {
+ buffer_put(buffer_1,ip4str,ip4_fmt(ip4str,out.s + i));
+ buffer_puts(buffer_1,"\n");
+ }
+ buffer_putsflush(buffer_1,"");
+
+ _exit(0);
+}
diff --git a/src/dnsmxip.c b/src/dnsmxip.c
new file mode 100644
index 0000000..de3bb7c
--- /dev/null
+++ b/src/dnsmxip.c
@@ -0,0 +1,106 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "fmt.h"
+#include "dnsresolv.h"
+#include "ip.h"
+#include "now.h"
+#include "exit.h"
+#include "dns.h"
+#include "logmsg.h"
+#include "str.h"
+
+#define WHO "dnsmxip"
+
+static stralloc sa = {0};
+static stralloc ia = {0};
+static stralloc out = {0};
+static stralloc ipaddr = {0};
+
+int dns_ia(stralloc *ip,char *s)
+{
+ int i;
+ int j = 0;
+ int r = 0;
+ char ip4str[IP4_FMT];
+ char ip6str[IP6_FMT];
+
+ if (!stralloc_copys(&sa,s)) return -1;
+ if (sa.s[sa.len-1] != '.')
+ if (!stralloc_append(&sa,".")) return -1;
+ if (!stralloc_copys(ip,"")) return -1;
+
+ DNS_INIT
+ if (dns_ip6(&ia,&sa) > 0) {
+ for (i = 0; i + 16 <= ia.len; i += 16) {
+ if (ip6_isv4mapped(ia.s + i)) continue;
+ j = ip6_fmt(ip6str,ia.s + i);
+ r += j;
+ if (!stralloc_catb(ip,ip6str,j)) return -1;
+ if (!stralloc_cats(ip," ")) return -1;
+ r++;
+ }
+ }
+
+ if (dns_ip4(&ia,&sa) > 0) {
+ for (i = 0; i + 4 <= ia.len; i += 4) {
+ j = ip4_fmt(ip4str,ia.s + i);
+ r += j;
+ if (!stralloc_catb(ip,ip4str,j)) return -1;
+ if (!stralloc_cats(ip," ")) return -1;
+ r++;
+ }
+ }
+ if (!stralloc_0(ip)) return -1;
+
+ return r?r-1:0;
+}
+
+int main(int argc,char **argv)
+{
+ int j, k, r;
+ uint16 u;
+ int len;
+ char num[FMT_ULONG];
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnsmx fqdn");
+ if (!stralloc_copys(&sa,argv[1]))
+ logmsg(WHO,111,FATAL,"out of memory");
+
+ DNS_INIT
+ if ((r = dns_mx(&out,&sa)) < 0) _exit(1);
+ if (r > 0) {
+ j = len = 0;
+ do {
+ j = str_chr(out.s + len + 2,'\0');
+ k = dns_ia(&ipaddr,out.s + len + 2); /* IP */
+ if (k) {
+ buffer_put(buffer_1,out.s + len + 2,j); /* MX */
+ buffer_puts(buffer_1,": ");
+ out.s[len + 3] = '\0';
+ uint16_unpack_big(out.s + len,&u);
+ buffer_put(buffer_1,num,fmt_ulong(num,u)) ;
+ buffer_puts(buffer_1," [");
+ buffer_put(buffer_1,ipaddr.s,k);
+ buffer_puts(buffer_1,"]");
+ }
+ buffer_putsflush(buffer_1,"\n");
+ len += j + 3;
+ } while (len < out.len);
+ } else { /* A/AAAA */
+ k = dns_ia(&ipaddr,argv[1]); /* IP */
+ if (k) {
+ buffer_puts(buffer_1,argv[1]);
+ buffer_puts(buffer_1,": -");
+ buffer_puts(buffer_1," [");
+ buffer_put(buffer_1,ipaddr.s,k);
+ buffer_puts(buffer_1,"]");
+ buffer_putsflush(buffer_1,"\n");
+ }
+ }
+
+ _exit(0);
+}
diff --git a/src/dnsptr.c b/src/dnsptr.c
new file mode 100644
index 0000000..25a4731
--- /dev/null
+++ b/src/dnsptr.c
@@ -0,0 +1,37 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "str.h"
+#include "dnsresolv.h"
+#include "dns.h"
+#include "ip.h"
+#include "exit.h"
+#include "logmsg.h"
+
+#define WHO "dnsptr"
+
+stralloc out = {0};
+char ip4[4];
+char ip6[16];
+
+int main(int argc,char **argv)
+{
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnsptr ipv4 || ipv6 (compactified)");
+
+ DNS_INIT
+ if (str_chr(argv[1],':') < str_len(argv[1])) {
+ if (!ip6_scan(argv[1],ip6))
+ logmsg(WHO,111,FATAL,"wrong IPv6 format");
+ if (dns_name6(&out,ip6) > 0)
+ buffer_put(buffer_1,out.s,out.len);
+ } else {
+ if (!ip4_scan(argv[1],ip4))
+ logmsg(WHO,111,FATAL,"wrong IPv4 format");
+ if (dns_name4(&out,ip4) > 0)
+ buffer_put(buffer_1,out.s,out.len);
+ }
+ buffer_putsflush(buffer_1,"\n");
+
+ _exit(0);
+}
diff --git a/src/dnstlsa.c b/src/dnstlsa.c
new file mode 100644
index 0000000..9871fff
--- /dev/null
+++ b/src/dnstlsa.c
@@ -0,0 +1,96 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "dnsresolv.h"
+#include "dns.h"
+#include "exit.h"
+#include "logmsg.h"
+#include "getoptb.h"
+#include "str.h"
+#include "byte.h"
+
+#define WHO "dnstlsa"
+
+static stralloc cn = {0};
+static stralloc sa = {0};
+static stralloc out = {0};
+
+int main(int argc,char **argv)
+{
+ int r;
+ uint16 usage;
+ uint16 selector;
+ uint16 type;
+ char *port = "25";
+ char proto[7] = "._tcp.";
+ char *host;
+ unsigned char ch;
+ int opt;
+ int i, j, k;
+ int verbose = 0;
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnstlsa [-v] [-p port] [-u(dp)|-t(cp)] host (tcp on port 25 is default)" );
+
+ while ((opt = getopt(argc,argv,"vutp:")) != opteof) {
+ switch (opt) {
+ case 'p': port = optarg; break;
+ case 't': break;
+ case 'u': str_copy(proto,"._udp."); break;
+ case 'v': verbose = 1;
+ }
+ }
+ if (optind < argc)
+ host = argv[optind++];
+
+ if (!stralloc_copyb(&sa, "_",1)) logmsg(WHO,111,FATAL,"out of memory");
+ if (!stralloc_cats(&sa,port)) logmsg(WHO,111,FATAL,"out of memory");
+ if (!stralloc_cats(&sa,proto)) logmsg(WHO,111,FATAL,"out of memory");
+ if (!stralloc_cats(&sa,host)) logmsg(WHO,111,FATAL,"out of memory");
+
+ DNS_INIT
+ if (dns_cname(&cn,&sa) > 0)
+ { if ((r = dns_tlsa(&out,&cn)) < 0) _exit(1); }
+ else
+ if ((r = dns_tlsa(&out,&sa)) < 0) _exit(1);
+ if (!stralloc_0(&sa)) logmsg(WHO,111,FATAL,"out of memory");
+ if (verbose) logmsg(WHO,0,INFO,B("checking for TLSA records: ",sa.s,"\n"));
+
+ if (r > 0 && out.len > 4) {
+ for (i = 0; i <= out.len; i++) {
+ usage = (unsigned char) out.s[i];
+ selector = (unsigned char) out.s[i + 1];
+ type = (unsigned char) out.s[i + 2];
+
+ if (usage == 0) buffer_puts(buffer_1,"Usage: [0], ");
+ if (usage == 1) buffer_puts(buffer_1,"Usage: [1], ");
+ if (usage == 2) buffer_puts(buffer_1,"Usage: [2], ");
+ if (usage == 3) buffer_puts(buffer_1,"Usage: [3], ");
+
+ if (selector == 0) buffer_puts(buffer_1,"Selector: [0], ");
+ if (selector == 1) buffer_puts(buffer_1,"Selector: [1], ");
+
+ if (type == 0) buffer_puts(buffer_1,"Type: [0] "); // full cert
+ if (type == 1) buffer_puts(buffer_1,"Type: [1] "); // sha256
+ if (type == 2) buffer_puts(buffer_1,"Type: [2] "); // sha512
+
+ /* Staff of Ra
+ "(is) six kadams high." However, the builder (h)as
+ to subtract one kadam out of respect for the Hebrew God. */
+
+ for (j = i + 3, k = 0; j <= out.len; ++j) {
+ ch = (unsigned char) out.s[j];
+ if ((type == 1 && k == 32) || (type == 2 && k == 64)) {
+ buffer_putsflush(buffer_1,"\n");
+ i = j - 1; break;
+ } else {
+ buffer_put(buffer_1,"0123456789abcdef" + (ch >> 4),1);
+ buffer_put(buffer_1,"0123456789abcdef" + (ch & 0x0f),1);
+ k++;
+ }
+ }
+ }
+ }
+
+ _exit(0);
+}
diff --git a/src/dnstxt.c b/src/dnstxt.c
new file mode 100644
index 0000000..385928e
--- /dev/null
+++ b/src/dnstxt.c
@@ -0,0 +1,32 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "dnsresolv.h"
+#include "dns.h"
+#include "exit.h"
+#include "logmsg.h"
+
+#define WHO "dnstext"
+
+stralloc sa = {0};
+stralloc out = {0};
+
+int main(int argc,char **argv)
+{
+ int r;
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"dnstxt fqdn");
+
+ if (!stralloc_copys(&sa,argv[1]))
+ logmsg(WHO,111,FATAL,"out of memory");
+
+ DNS_INIT
+ if ((r = dns_txt(&out,&sa)) < 0) _exit(1);
+ if (r > 0) {
+ buffer_put(buffer_1,out.s,out.len);
+ buffer_putsflush(buffer_1,"\n");
+ }
+
+ _exit(0);
+}
diff --git a/src/except.c b/src/except.c
new file mode 100644
index 0000000..edee976
--- /dev/null
+++ b/src/except.c
@@ -0,0 +1,34 @@
+#include <unistd.h>
+#include "wait.h"
+#include "logmsg.h"
+#include "exit.h"
+
+#define WHO "except"
+
+int main(int argc, char **argv)
+{
+ int pid;
+ int wstat;
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"except program [ arg ... ]");
+
+ pid = fork();
+ if (pid == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ if (pid == 0) {
+ execvp(argv[1],argv + 1);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+
+ if (wait_pid(&wstat,pid) == -1)
+ logmsg(WHO,111,FATAL,"wait failed");
+ if (wait_crashed(wstat))
+ logmsg(WHO,111,FATAL,"child crashed");
+ switch (wait_exitcode(wstat)) {
+ case 0: _exit(100);
+ case 111: logmsg(WHO,111,FATAL,"temporary child error");
+ default: _exit(0);
+ }
+}
diff --git a/src/failures.sh b/src/failures.sh
new file mode 100644
index 0000000..c3fe532
--- /dev/null
+++ b/src/failures.sh
@@ -0,0 +1,14 @@
+
+awk '
+ /^d d/ {
+ reason = $11
+ fail[reason] += 1
+ xdelay[reason] += $5 - $4
+ }
+ END {
+ for (reason in fail) {
+ str = sprintf("%.2f",xdelay[reason])
+ print fail[reason],str,reason
+ }
+ }
+'
diff --git a/src/fastforward.c b/src/fastforward.c
new file mode 100644
index 0000000..52e2e12
--- /dev/null
+++ b/src/fastforward.c
@@ -0,0 +1,399 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "readclose.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "strset.h"
+#include "getoptb.h"
+#include "exit.h"
+#include "logmsg.h"
+#include "env.h"
+#include "sig.h"
+#include "qmail.h"
+#include "fmt.h"
+#include "case.h"
+#include "alloc.h"
+#include "seek.h"
+#include "wait.h"
+#include "byte.h"
+#include "str.h"
+#include "open.h"
+#include "cdbread.h"
+
+#define WHO "fastforward"
+
+static void usage()
+{
+ logmsg(WHO,100,USAGE,"fastforward [ -nNpP ] data.cdb");
+}
+static void nomem()
+{
+ logmsg(WHO,111,FATAL,"out of memory");
+}
+
+static void print(char *s)
+{
+ char ch;
+ while ((ch = *s++)) {
+ buffer_put(buffer_2,&ch,1);
+ }
+}
+
+static void printsafe(char *s)
+{
+ char ch;
+ while ((ch = *s++)) {
+ if (ch < 32) ch = '_';
+ buffer_put(buffer_2,&ch,1);
+ }
+}
+
+struct qmail qq;
+char qp[FMT_ULONG];
+char qqbuf[1];
+
+ssize_t qqwrite(int fd,char *buf,int len)
+{
+ qmail_put(&qq,buf,len);
+ return len;
+}
+
+buffer bufq = BUFFER_INIT(qqwrite,-1,qqbuf,sizeof(qqbuf));
+
+char messbuf[4096];
+buffer mess = BUFFER_INIT(read,0,messbuf,sizeof(messbuf));
+
+int flagdeliver = 1;
+int flagpassthrough = 0;
+
+char *dtline;
+stralloc sender = {0};
+stralloc programs = {0};
+stralloc forward = {0};
+
+strset done;
+stralloc todo = {0};
+
+stralloc mailinglist = {0};
+
+void dofile(char *fn)
+{
+ int fd;
+ struct stat st;
+ int i;
+ int j;
+
+ if (!stralloc_copys(&mailinglist,"")) nomem();
+
+ fd = open_read(fn);
+ if (fd == -1)
+ logmsg(WHO,111,FATAL,B("unable to read: ",fn));
+ if (fstat(fd,&st) == -1)
+ logmsg(WHO,111,FATAL,B("unable to read: ",fn));
+ if ((st.st_mode & 0444) != 0444)
+ logmsg(WHO,111,FATAL,B(fn," is not world-readable"));
+ if (readclose_append(fd,&mailinglist,1024) == -1)
+ logmsg(WHO,111,FATAL,B("unable to read: ",fn));
+
+ i = 0;
+ for (j = 0; j < mailinglist.len; ++j)
+ if (!mailinglist.s[j]) {
+ if ((mailinglist.s[i] == '.') || (mailinglist.s[i] == '/')) {
+ if (!stralloc_cats(&todo,mailinglist.s + i)) nomem();
+ if (!stralloc_0(&todo)) nomem();
+ }
+ else if ((mailinglist.s[i] == '&') && (j - i < 900)) {
+ if (!stralloc_cats(&todo,mailinglist.s + i)) nomem();
+ if (!stralloc_0(&todo)) nomem();
+ }
+ i = j + 1;
+ }
+}
+
+char *fncdb;
+int fdcdb;
+stralloc key = {0};
+uint32 dlen;
+stralloc data = {0};
+struct cdb cdb;
+
+void cdbreaderror()
+{
+ logmsg(WHO,111,FATAL,B("unable to read: ",fncdb));
+}
+
+int findtarget(int flagwild,char *prepend,char *addr)
+{
+ int r;
+ int at;
+
+ if (!stralloc_copys(&key,prepend)) nomem();
+ if (!stralloc_cats(&key,addr)) nomem();
+ case_lowerb(key.s,key.len);
+
+ r = cdb_find(&cdb,key.s,key.len);
+ if (r == -1) cdbreaderror();
+ if (r) return 1;
+
+ if (!flagwild) return 0;
+ at = str_rchr(addr,'@');
+ if (!addr[at]) return 0;
+
+ if (!stralloc_copys(&key,prepend)) nomem();
+ if (!stralloc_cats(&key,addr + at)) nomem();
+ case_lowerb(key.s,key.len);
+
+ r = cdb_find(&cdb,key.s,key.len);
+ if (r == -1) cdbreaderror();
+ if (r) return 1;
+
+ if (!stralloc_copys(&key,prepend)) nomem();
+ if (!stralloc_catb(&key,addr,at + 1)) nomem();
+ case_lowerb(key.s,key.len);
+
+ r = cdb_find(&cdb,key.s,key.len);
+ if (r == -1) cdbreaderror();
+ if (r) return 1;
+
+ return 0;
+}
+
+int gettarget(int flagwild,char *prepend,char *addr)
+{
+ if (!findtarget(flagwild,prepend,addr)) return 0;
+ dlen = cdb_datalen(&cdb);
+ if (!stralloc_ready(&data,(unsigned int) dlen)) nomem();
+ data.len = dlen;
+ if (cdb_read(&cdb,data.s,data.len,cdb_datapos(&cdb)) == -1)
+ cdbreaderror();
+
+ return 1;
+}
+
+void doprogram(char *arg)
+{
+ char *args[5];
+ int child;
+ int wstat;
+
+ if (!flagdeliver) {
+ print("run ");
+ printsafe(arg);
+ print("\n");
+ buffer_flush(buffer_2);
+ return;
+ }
+
+ if (*arg == '!') {
+ args[0] = "preline";
+ args[1] = "sh";
+ args[2] = "-c";
+ args[3] = arg + 1;
+ args[4] = 0;
+ }
+ else {
+ args[0] = "sh";
+ args[1] = "-c";
+ args[2] = arg + 1;
+ args[3] = 0;
+ }
+
+ switch (child = vfork()) {
+ case -1:
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ case 0:
+ sig_pipedefault();
+ execvp(*args,args);
+ logmsg(WHO,111,FATAL,B("unable to run: ",arg));
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+ logmsg(WHO,111,FATAL,B("child crashed in: ",arg));
+
+ switch (wait_exitcode(wstat)) {
+ case 64: case 65: case 70: case 76: case 77: case 78: case 112:
+ case 100: _exit(100);
+ case 0: break;
+ default: _exit(111);
+ }
+
+ if (seek_begin(0) == -1)
+ logmsg(WHO,111,FATAL,"unable to rewind input: ");
+}
+
+void dodata()
+{
+ int i;
+ int j;
+ i = 0;
+
+ for (j = 0; j < data.len; ++j)
+ if (!data.s[j]) {
+ if ((data.s[i] == '|') || (data.s[i] == '!'))
+ doprogram(data.s + i);
+ else if ((data.s[i] == '.') || (data.s[i] == '/')) {
+ if (!stralloc_cats(&todo,data.s + i)) nomem();
+ if (!stralloc_0(&todo)) nomem();
+ }
+ else if ((data.s[i] == '&') && (j - i < 900)) {
+ if (!stralloc_cats(&todo,data.s + i)) nomem();
+ if (!stralloc_0(&todo)) nomem();
+ }
+ i = j + 1;
+ }
+}
+
+void dorecip(char *addr)
+{
+
+ if (!findtarget(0,"?",addr))
+ if (gettarget(0,":",addr)) {
+ dodata();
+ return;
+ }
+ if (!stralloc_cats(&forward,addr)) nomem();
+ if (!stralloc_0(&forward)) nomem();
+}
+
+void doorigrecip(char *addr)
+{
+ if (sender.len)
+ if ((sender.len != 4) || byte_diff(sender.s,4,"#@[]"))
+ if (gettarget(1,"?",addr))
+ if (!stralloc_copy(&sender,&data)) nomem();
+ if (!gettarget(1,":",addr))
+ if (flagpassthrough)
+ _exit(0);
+ else
+ logmsg(WHO,100,ERROR,"Sorry, no mailbox here by that name. (#5.1.1)");
+ dodata();
+}
+
+stralloc recipient = {0};
+int flagdefault = 0;
+
+int main(int argc,char **argv)
+{
+ int opt;
+ char *x;
+ int i;
+
+ sig_pipeignore();
+
+ dtline = env_get("DTLINE");
+ if (!dtline) dtline = "";
+
+ x = env_get("SENDER");
+ if (!x) x = "original envelope sender";
+ if (!stralloc_copys(&sender,x)) nomem();
+
+ if (!stralloc_copys(&forward,"")) nomem();
+ if (!strset_init(&done)) nomem();
+
+ while ((opt = getopt(argc,argv,"nNpPdD")) != opteof)
+ switch (opt) {
+ case 'n': flagdeliver = 0; break;
+ case 'N': flagdeliver = 1; break;
+ case 'p': flagpassthrough = 1; break;
+ case 'P': flagpassthrough = 0; break;
+ case 'd': flagdefault = 1; break;
+ case 'D': flagdefault = 0; break;
+ default: usage();
+ }
+ argv += optind;
+
+ fncdb = *argv;
+ if (!fncdb) usage();
+ fdcdb = open_read(fncdb);
+ if (fdcdb == -1) cdbreaderror();
+ cdb_init(&cdb,fdcdb);
+
+ if (flagdefault) {
+ x = env_get("DEFAULT");
+ if (!x) x = env_get("EXT");
+ if (!x) logmsg(WHO,100,FATAL,"$DEFAULT or $EXT must be set");
+ if (!stralloc_copys(&recipient,x)) nomem();
+ if (!stralloc_cats(&recipient,"@")) nomem();
+ x = env_get("HOST");
+ if (!x) logmsg(WHO,100,FATAL,"$HOST must be set");
+ if (!stralloc_cats(&recipient,x)) nomem();
+ if (!stralloc_0(&recipient)) nomem();
+ x = recipient.s;
+ }
+ else {
+ x = env_get("RECIPIENT");
+ if (!x) logmsg(WHO,100,FATAL,"$RECIPIENT must be set");
+ }
+ if (!strset_add(&done,x)) nomem();
+ doorigrecip(x);
+
+ while (todo.len) {
+ i = todo.len - 1;
+ while ((i > 0) && todo.s[i - 1]) --i;
+ todo.len = i;
+
+ if (strset_in(&done,todo.s + i)) continue;
+
+ x = alloc(str_len(todo.s + i) + 1);
+ if (!x) nomem();
+ str_copy(x,todo.s + i);
+ if (!strset_add(&done,x)) nomem();
+
+ x = todo.s + i;
+ if (*x == 0)
+ continue;
+ else if ((*x == '.') || (*x == '/'))
+ dofile(x);
+ else
+ dorecip(x + 1);
+ }
+
+ if (!forward.len) {
+ if (!flagdeliver) {
+ print("no forwarding\n");
+ buffer_flush(buffer_2);
+ }
+ _exit(flagpassthrough ? 99 : 0);
+ }
+
+ if (!stralloc_0(&sender)) nomem();
+
+ if (!flagdeliver) {
+ print("from <");
+ printsafe(sender.s);
+ print(">\n");
+ while (forward.len) {
+ i = forward.len - 1;
+ while ((i > 0) && forward.s[i - 1]) --i;
+ forward.len = i;
+ print("to <");
+ printsafe(forward.s + i);
+ print(">\n");
+ }
+ buffer_flush(buffer_2);
+ _exit(flagpassthrough ? 99 : 0);
+ }
+
+ if (qmail_open(&qq) == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ qmail_puts(&qq,dtline);
+ if (buffer_copy(&bufq,&mess) != 0)
+ logmsg(WHO,111,FATAL,"unable to read message: ");
+ buffer_flush(&bufq);
+ qp[fmt_ulong(qp,qmail_qp(&qq))] = 0;
+
+ qmail_from(&qq,sender.s);
+
+ while (forward.len) {
+ i = forward.len - 1;
+ while ((i > 0) && forward.s[i - 1]) --i;
+ forward.len = i;
+ qmail_to(&qq,forward.s + i);
+ }
+
+ x = qmail_close(&qq);
+ if (*x) logmsg(WHO,*x == 'D' ? 100 : 111,FATAL,x + 1);
+ logmsg(WHO,flagpassthrough ? 99 : 0,LOG,B("qp ",qp));
+}
diff --git a/src/fifo.c b/src/fifo.c
new file mode 100644
index 0000000..5547294
--- /dev/null
+++ b/src/fifo.c
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "fifo.h"
+
+#ifdef HASMKFIFO
+int fifo_make(char *fn, int mode) { return mkfifo(fn,mode); }
+#else
+int fifo_make(char *fn, int mode) { return mknod(fn,S_IFIFO | mode,0); }
+#endif
diff --git a/src/find-systype.sh b/src/find-systype.sh
new file mode 100755
index 0000000..16266d3
--- /dev/null
+++ b/src/find-systype.sh
@@ -0,0 +1,144 @@
+# oper-:arch-:syst-:chip-:kern-
+# oper = operating system type; e.g., sunos-4.1.4
+# arch = machine language; e.g., sparc
+# syst = which binaries can run; e.g., sun4
+# chip = chip model; e.g., micro-2-80
+# kern = kernel version; e.g., sun4m
+# dependence: arch --- chip
+# \ \
+# oper --- syst --- kern
+# so, for example, syst is interpreted in light of oper, but chip is not.
+# anyway, no slashes, no extra colons, no uppercase letters.
+# the point of the extra -'s is to ease parsing: can add hierarchies later.
+# e.g., *:i386-*:*:pentium-*:* would handle pentium-100 as well as pentium,
+# and i386-486 (486s do have more instructions, you know) as well as i386.
+# the idea here is to include ALL useful available information.
+
+exec 2>/dev/null
+sys="`uname -s | tr '/:[A-Z]' '..[a-z]'`"
+if [ x"$sys" != x ]
+then
+ unamer="`uname -r | tr /: ..`"
+ unamem="`uname -m | tr /: ..`"
+ unamev="`uname -v | tr /: ..`"
+
+ case "$sys" in
+ bsd.os)
+ # in bsd 4.4, uname -v does not have useful info.
+ # in bsd 4.4, uname -m is arch, not chip.
+ oper="$sys-$unamer"
+ arch="$unamem"
+ syst=""
+ chip="`sysctl -n hw.model`"
+ kern=""
+ ;;
+ freebsd)
+ # see above about bsd 4.4
+ oper="$sys-$unamer"
+ arch="$unamem"
+ syst=""
+ chip="`sysctl -n hw.model`" # hopefully
+ kern=""
+ ;;
+ netbsd)
+ # see above about bsd 4.4
+ oper="$sys-$unamer"
+ arch="$unamem"
+ syst=""
+ chip="`sysctl -n hw.model`" # hopefully
+ kern=""
+ ;;
+ linux)
+ # as in bsd 4.4, uname -v does not have useful info.
+ oper="$sys-$unamer"
+ syst=""
+ chip="$unamem"
+ kern=""
+ case "$chip" in
+ i386|i486|i586|i686)
+ arch="i386"
+ ;;
+ alpha)
+ arch="alpha"
+ ;;
+ esac
+ ;;
+ aix)
+ # naturally IBM has to get uname -r and uname -v backwards. dorks.
+ oper="$sys-$unamev-$unamer"
+ arch="`arch | tr /: ..`"
+ syst=""
+ chip="$unamem"
+ kern=""
+ ;;
+ sunos)
+ oper="$sys-$unamer-$unamev"
+ arch="`(uname -p || mach) | tr /: ..`"
+ syst="`arch | tr /: ..`"
+ chip="$unamem" # this is wrong; is there any way to get the real info?
+ kern="`arch -k | tr /: ..`"
+ ;;
+ unix_sv)
+ oper="$sys-$unamer-$unamev"
+ arch="`uname -m`"
+ syst=""
+ chip="$unamem"
+ kern=""
+ ;;
+ *)
+ oper="$sys-$unamer-$unamev"
+ arch="`arch | tr /: ..`"
+ syst=""
+ chip="$unamem"
+ kern=""
+ ;;
+ esac
+else
+ $CC -c trycpp.c
+ $LD -o trycpp trycpp.o
+ case `./trycpp` in
+ nextstep)
+ oper="nextstep-`hostinfo | sed -n 's/^[ ]*NeXT Mach \([^:]*\):.*$/\1/p'`"
+ arch="`hostinfo | sed -n 's/^Processor type: \(.*\) (.*)$/\1/p' | tr /: ..`"
+ syst=""
+ chip="`hostinfo | sed -n 's/^Processor type: .* (\(.*\))$/\1/p' | tr ' /:' '...'`"
+ kern=""
+ ;;
+ *)
+ oper="unknown"
+ arch=""
+ syst=""
+ chip=""
+ kern=""
+ ;;
+ esac
+ rm -f trycpp.o trycpp
+fi
+
+case "$chip" in
+80486)
+ # let's try to be consistent here. (BSD/OS)
+ chip=i486
+ ;;
+i486DX)
+ # respect the hyphen hierarchy. (FreeBSD)
+ chip=i486-dx
+ ;;
+i486.DX2)
+ # respect the hyphen hierarchy. (FreeBSD)
+ chip=i486-dx2
+ ;;
+Intel.586)
+ # no, you nitwits, there is no such chip. (NeXTStep)
+ chip=pentium
+ ;;
+i586)
+ # no, you nitwits, there is no such chip. (Linux)
+ chip=pentium
+ ;;
+i686)
+ # STOP SAYING THAT! (Linux)
+ chip=ppro
+esac
+
+echo "$oper-:$arch-:$syst-:$chip-:$kern-" | tr ' [A-Z]' '.[a-z]'
diff --git a/src/fmtqfn.c b/src/fmtqfn.c
new file mode 100644
index 0000000..139cccf
--- /dev/null
+++ b/src/fmtqfn.c
@@ -0,0 +1,22 @@
+#include "fmtqfn.h"
+#include "fmt.h"
+#include "auto_split.h"
+
+unsigned int fmtqfn(char *s,char *dirslash,unsigned long id,int flagsplit)
+{
+ unsigned int len;
+ unsigned int i;
+
+ len = 0;
+ i = fmt_str(s,dirslash); len += i; if (s) s += i;
+
+ if (flagsplit) {
+ i = fmt_ulong(s,id % auto_split); len += i; if (s) s += i;
+ i = fmt_str(s,"/"); len += i; if (s) s += i;
+ }
+
+ i = fmt_ulong(s,id); len += i; if (s) s += i;
+ if (s) *s++ = 0; ++len;
+
+ return len;
+}
diff --git a/src/fork.h1 b/src/fork.h1
new file mode 100644
index 0000000..a1accc7
--- /dev/null
+++ b/src/fork.h1
@@ -0,0 +1,7 @@
+#ifndef FORK_H
+#define FORK_H
+
+int fork();
+#define vfork fork
+
+#endif
diff --git a/src/fork.h2 b/src/fork.h2
new file mode 100644
index 0000000..fa3dd5d
--- /dev/null
+++ b/src/fork.h2
@@ -0,0 +1,7 @@
+#ifndef FORK_H
+#define FORK_H
+
+int fork();
+int vfork();
+
+#endif
diff --git a/src/forward.c b/src/forward.c
new file mode 100644
index 0000000..7cc8e53
--- /dev/null
+++ b/src/forward.c
@@ -0,0 +1,58 @@
+#include <unistd.h>
+#include "sig.h"
+#include "exit.h"
+#include "env.h"
+#include "qmail.h"
+#include "logmsg.h"
+#include "buffer.h"
+#include "fmt.h"
+
+#define WHO "forward"
+
+void die_nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+
+struct qmail qqt;
+
+ssize_t mywrite(int fd, char *buf, int len)
+{
+ qmail_put(&qqt,buf,len);
+ return len;
+}
+
+char inbuf[BUFFER_INSIZE];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+char outbuf[1];
+buffer bo = BUFFER_INIT(mywrite,-1,outbuf,sizeof(outbuf));
+
+char num[FMT_ULONG];
+
+int main(int argc, char **argv)
+{
+ char *sender;
+ char *dtline;
+ char *qqx;
+
+ sig_pipeignore();
+
+ sender = env_get("NEWSENDER");
+ if (!sender)
+ logmsg(WHO,100,FATAL,"NEWSENDER not set");
+ dtline = env_get("DTLINE");
+ if (!dtline)
+ logmsg(WHO,100,FATAL,"DTLINE not set");
+
+ if (qmail_open(&qqt) == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ qmail_puts(&qqt,dtline);
+ if (buffer_copy(&bo,&bi) != 0)
+ logmsg(WHO,111,FATAL,"unable to read message: ");
+ buffer_flush(&bo);
+
+ num[fmt_ulong(num,qmail_qp(&qqt))] = 0;
+
+ qmail_from(&qqt,sender);
+ while (*++argv) qmail_to(&qqt,*argv);
+ qqx = qmail_close(&qqt);
+ if (*qqx) logmsg(WHO,*qqx == 'D' ? 100 : 111,FATAL,qqx + 1);
+ logmsg(WHO,0,LOG,B("qp ",num));
+}
diff --git a/src/gfrom.c b/src/gfrom.c
new file mode 100644
index 0000000..c04b65f
--- /dev/null
+++ b/src/gfrom.c
@@ -0,0 +1,8 @@
+#include "str.h"
+#include "gfrom.h"
+
+int gfrom(char *s,int len)
+{
+ while ((len > 0) && (*s == '>')) { ++s; --len; }
+ return (len >= 5) && !str_diffn(s,"From ",5);
+}
diff --git a/src/headerbody.c b/src/headerbody.c
new file mode 100644
index 0000000..82c5684
--- /dev/null
+++ b/src/headerbody.c
@@ -0,0 +1,78 @@
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "hfield.h"
+#include "headerbody.h"
+
+static int getsa(buffer *b,stralloc *sa,int *match)
+{
+ if (!*match) return 0;
+ if (getln(b,sa,match,'\n') == -1) return -1;
+ if (*match) return 1;
+ if (!sa->len) return 0;
+ if (!stralloc_append(sa,"\n")) return -1;
+
+ return 1;
+}
+
+static stralloc line = {0};
+static stralloc nextline = {0};
+
+int headerbody(b,dohf,hdone,dobl)
+buffer *b;
+void (*dohf)();
+void (*hdone)();
+void (*dobl)();
+{
+ int match;
+ int flaglineok;
+ match = 1;
+ flaglineok = 0;
+
+ for (;;) {
+ switch (getsa(b,&nextline,&match)) {
+ case -1:
+ return -1;
+ case 0:
+ if (flaglineok) dohf(&line);
+ hdone();
+ /* no message body; could insert blank line here */
+ return 0;
+ }
+
+ if (flaglineok) {
+ if ((nextline.s[0] == ' ') || (nextline.s[0] == '\t')) {
+ if (!stralloc_cat(&line,&nextline)) return -1;
+ continue;
+ }
+ dohf(&line);
+ }
+
+ if (nextline.len == 1) {
+ hdone();
+ dobl(&nextline);
+ break;
+ }
+
+ if (stralloc_starts(&nextline,"From ")) {
+ if (!stralloc_copys(&line,"MBOX-Line: ")) return -1;
+ if (!stralloc_cat(&line,&nextline)) return -1;
+ } else if (hfield_valid(nextline.s,nextline.len)) {
+ if (!stralloc_copy(&line,&nextline)) return -1;
+ } else {
+ hdone();
+ if (!stralloc_copys(&line,"\n")) return -1;
+ dobl(&line);
+ dobl(&nextline);
+ break;
+ }
+ flaglineok = 1;
+ }
+
+ for (;;)
+ switch (getsa(b,&nextline,&match)) {
+ case -1: return -1;
+ case 0: return 0;
+ case 1: dobl(&nextline);
+ }
+}
diff --git a/src/hfield.c b/src/hfield.c
new file mode 100644
index 0000000..2376e1f
--- /dev/null
+++ b/src/hfield.c
@@ -0,0 +1,113 @@
+#include "hfield.h"
+
+static char *(hname[]) = {
+ "unknown-header"
+, "sender"
+, "from"
+, "reply-to"
+, "to"
+, "cc"
+, "bcc"
+, "date"
+, "message-id"
+, "subject"
+, "resent-sender"
+, "resent-from"
+, "resent-reply-to"
+, "resent-to"
+, "resent-cc"
+, "resent-bcc"
+, "resent-date"
+, "resent-message-id"
+, "return-receipt-to"
+, "errors-to"
+, "apparently-to"
+, "received"
+, "return-path"
+, "delivered-to"
+, "content-length"
+, "content-type"
+, "content-transfer-encoding"
+, "notice-requested-upon-delivery-to"
+, "mail-followup-to"
+, 0
+};
+
+static int hmatch( char *s,int len,char *t)
+{
+ int i;
+ char ch;
+
+ for (i = 0; (ch = t[i]); ++i) {
+ if (i >= len) return 0;
+ if (ch != s[i]) {
+ if (ch == '-') return 0;
+ if (ch - 32 != s[i]) return 0;
+ }
+ }
+ for (;;) {
+ if (i >= len) return 0;
+ ch = s[i];
+ if (ch == ':') return 1;
+ if ((ch != ' ') && (ch != '\t')) return 0;
+ ++i;
+ }
+}
+
+int hfield_known(char *s,int len)
+{
+ int i;
+ char *t;
+
+ for (i = 1; (t = hname[i]); ++i)
+ if (hmatch(s,len,t))
+ return i;
+
+ return 0;
+}
+
+int hfield_valid(char *s,int len)
+{
+ int i;
+ int j;
+ char ch;
+
+ for (j = 0; j < len; ++j)
+ if (s[j] == ':') break;
+
+ if (j >= len) return 0;
+
+ while (j) {
+ ch = s[j - 1];
+ if ((ch != ' ') && (ch != '\t'))
+ break;
+ --j;
+ }
+ if (!j) return 0;
+
+ for (i = 0; i < j; ++i) {
+ ch = s[i];
+ if (ch <= 32) return 0;
+ if (ch >= 127) return 0;
+ }
+ return 1;
+}
+
+unsigned int hfield_skipname(char *s,int len)
+{
+ int i;
+ char ch;
+
+ for (i = 0; i < len; ++i)
+ if (s[i] == ':') break;
+
+ if (i < len) ++i;
+ while (i < len) {
+ ch = s[i];
+ if ((ch != '\t') && (ch != '\n') && (ch != '\r') && (ch != ' '))
+ break;
+ ++i;
+ }
+
+ return i;
+}
diff --git a/src/hier.c b/src/hier.c
new file mode 100644
index 0000000..d5d5b38
--- /dev/null
+++ b/src/hier.c
@@ -0,0 +1,163 @@
+#include "auto_qmail.h"
+#include "auto_split.h"
+#include "auto_uids.h"
+#include "fmt.h"
+#include "fifo.h"
+#include "ipalloc.h"
+#include "tcpto.h"
+#include "hier.h"
+
+char buf[100 + FMT_ULONG];
+
+void dsplit(char *base,int uid,int mode) /* base must be under 100 bytes */
+{
+ char *x;
+ unsigned long i;
+
+ d(auto_qmail,base,uid,auto_gidq,mode);
+
+ for (i = 0; i < auto_split; ++i) {
+ x = buf;
+ x += fmt_str(x,base);
+ x += fmt_str(x,"/");
+ x += fmt_ulong(x,i);
+ *x = 0;
+
+ d(auto_qmail,buf,uid,auto_gidq,mode);
+ }
+}
+
+void hier()
+{
+ h(auto_qmail,auto_uido,auto_gidq,0755);
+
+ d(auto_qmail,"control",auto_uido,auto_gidq,0755);
+ d(auto_qmail,"users",auto_uido,auto_gidq,0755);
+ d(auto_qmail,"bin",auto_uido,auto_gidq,0755);
+ d(auto_qmail,"alias",auto_uida,auto_gidq,02755);
+
+ d(auto_qmail,"queue",auto_uidq,auto_gidq,0750);
+ d(auto_qmail,"queue/pid",auto_uidq,auto_gidq,0700);
+ d(auto_qmail,"queue/bounce",auto_uids,auto_gidq,0700);
+
+ dsplit("queue/dkim",auto_uidq,0750);
+ dsplit("queue/mess",auto_uidq,0750);
+ dsplit("queue/todo",auto_uidq,0750);
+ dsplit("queue/intd",auto_uidq,0700);
+ dsplit("queue/info",auto_uids,0700);
+ dsplit("queue/local",auto_uids,0700);
+ dsplit("queue/remote",auto_uids,0700);
+
+ d(auto_qmail,"queue/lock",auto_uidq,auto_gidq,0750);
+ z(auto_qmail,"queue/lock/tcpto",TCPTO_BUFSIZ,auto_uidr,auto_gidq,0644);
+ z(auto_qmail,"queue/lock/sendmutex",0,auto_uids,auto_gidq,0600);
+ p(auto_qmail,"queue/lock/trigger",auto_uids,auto_gidq,0622);
+
+ c(auto_qmail,"bin","qmail-queue",auto_uidq,auto_gidq,04711);
+ c(auto_qmail,"bin","qmail-qmaint",auto_uidq,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-lspawn",auto_uido,auto_gidq,0700);
+ c(auto_qmail,"bin","qmail-start",auto_uido,auto_gidq,0700);
+ c(auto_qmail,"bin","qmail-getpw",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-local",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-remote",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-smtpam",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-rspawn",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-clean",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-send",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-todo",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","splogger",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-newu",auto_uido,auto_gidq,0700);
+ c(auto_qmail,"bin","qmail-newmrh",auto_uido,auto_gidq,0700);
+
+ c(auto_qmail,"bin","qmail-dkim",auto_uidq,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-dksign",auto_uidq,auto_gidq,04711);
+ c(auto_qmail,"bin","qmail-dkverify",auto_uidq,auto_gidq,04711);
+
+ c(auto_qmail,"bin","qmail-authuser",auto_uido,auto_gidq,06711);
+ c(auto_qmail,"bin","qmail-vmailuser",auto_uido,auto_gidq,06711);
+ c(auto_qmail,"bin","qmail-postgrey",auto_uido,auto_gidq,06711);
+ c(auto_qmail,"bin","qmail-badloadertypes",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-badmimetypes",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-recipients",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-mfrules",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-mrtg",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-mrtg-queue",auto_uido,auto_gidq,0755);
+
+ c(auto_qmail,"bin","qmail-pw2u",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-inject",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-showctl",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-qread",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-qstat",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-tcpto",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-tcpok",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-pop3d",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-popup",auto_uido,auto_gidq,0711);
+ c(auto_qmail,"bin","qmail-qmqpc",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-qmqpd",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-qmtpd",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qmail-smtpd",auto_uido,auto_gidq,0755);
+
+ c(auto_qmail,"bin","predate",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","datemail",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","mailsubj",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","sendmail",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qreceipt",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","qbiff",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","forward",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","preline",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","condredirect",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","bouncesaying",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","except",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","maildirmake",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","maildir2mbox",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","maildirwatch",auto_uido,auto_gidq,0755);
+
+ c(auto_qmail,"bin","fastforward",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","printforward",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","setforward",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","newaliases",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","printmaillist",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","setmaillist",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","newinclude",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","srsforward",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","srsreverse",auto_uido,auto_gidq,0755);
+
+ c(auto_qmail,"bin","ipmeprint",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","spfquery",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnscname",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnsfq",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnsip",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnsmxip",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnsptr",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnstlsa",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","dnstxt",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","hostname",auto_uido,auto_gidq,0755);
+
+ c(auto_qmail,"bin","columnt",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","ddist",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","deferrals",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","failures",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","matchup",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","recipients",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","rhosts",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","rxdelay",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","senders",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","successes",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","suids",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","tai64nfrac",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","xqp",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","xrecipient",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","xsender",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zddist",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zdeferrals",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zfailures",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zfailures",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zoverall",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zrecipients",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zrhosts",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zrxdelay",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zsenders",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zsendmail",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zsuccesses",auto_uido,auto_gidq,0755);
+ c(auto_qmail,"bin","zsuids",auto_uido,auto_gidq,0755);
+}
diff --git a/src/hmac_md5.c b/src/hmac_md5.c
new file mode 100644
index 0000000..310f0ef
--- /dev/null
+++ b/src/hmac_md5.c
@@ -0,0 +1,52 @@
+#include "global.h"
+#include "md5.h"
+#include "str.h"
+#include "byte.h"
+
+/**
+@file hmac_md5
+@brief caculates HMAC digest from challenge + password (DJB version)
+@param input: unsigned char *text : pointer to challenge
+ int text_len : length of challenge
+ unsigned char *key : pointer to password
+ int key_len : length of password
+ output: unsigned char *digest: pointer to calculated digest
+*/
+
+void hmac_md5(unsigned char *text,int text_len,unsigned char * key,int key_len,unsigned char *digest)
+{
+ MD5_CTX context;
+ unsigned char k_ipad[65]; /* inner padding - key XORd with ipad */
+ unsigned char k_opad[65]; /* outer padding - key XORd with opad */
+ unsigned char tk[16];
+ int i;
+
+ if (key_len > 64) {
+ MD5_CTX tctx;
+ MD5Init(&tctx);
+ MD5Update(&tctx,key,key_len);
+ MD5Final(tk,&tctx);
+ key = tk;
+ key_len = 16;
+ }
+
+ byte_zero(k_ipad,sizeof(k_ipad));
+ byte_zero(k_opad,sizeof(k_opad));
+ byte_copy(k_ipad,key_len,key);
+ byte_copy(k_opad,key_len,key);
+
+ for (i = 0; i < 64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+
+ MD5Init(&context); /* init context for 1st pass */
+ MD5Update(&context,k_ipad,64); /* start with inner pad */
+ MD5Update(&context,text,text_len); /* then text of datagram */
+ MD5Final(digest,&context); /* finish up 1st pass */
+
+ MD5Init(&context); /* init context for 2nd pass */
+ MD5Update(&context,k_opad,64); /* start with outer pad */
+ MD5Update(&context,digest,16); /* then results of 1st hash */
+ MD5Final(digest,&context); /* finish up 2nd pass */
+}
diff --git a/src/hostname.c b/src/hostname.c
new file mode 100644
index 0000000..6a55309
--- /dev/null
+++ b/src/hostname.c
@@ -0,0 +1,16 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "exit.h"
+
+char host[256];
+
+int main()
+{
+ host[0] = 0; /* sigh */
+ gethostname(host,sizeof(host));
+ host[sizeof(host) - 1] = 0;
+ buffer_puts(buffer_1small,host);
+ buffer_puts(buffer_1small,"\n");
+ buffer_flush(buffer_1small);
+ _exit(0);
+}
diff --git a/src/include/auto_break.h b/src/include/auto_break.h
new file mode 100644
index 0000000..b7f3a63
--- /dev/null
+++ b/src/include/auto_break.h
@@ -0,0 +1,6 @@
+#ifndef AUTO_BREAK_H
+#define AUTO_BREAK_H
+
+extern char auto_break[];
+
+#endif
diff --git a/src/include/auto_patrn.h b/src/include/auto_patrn.h
new file mode 100644
index 0000000..77cdf1f
--- /dev/null
+++ b/src/include/auto_patrn.h
@@ -0,0 +1,6 @@
+#ifndef AUTO_PATRN_H
+#define AUTO_PATRN_H
+
+extern int auto_patrn;
+
+#endif
diff --git a/src/include/auto_qmail.h b/src/include/auto_qmail.h
new file mode 100644
index 0000000..0c56001
--- /dev/null
+++ b/src/include/auto_qmail.h
@@ -0,0 +1,6 @@
+#ifndef AUTO_QMAIL_H
+#define AUTO_QMAIL_H
+
+extern char auto_qmail[];
+
+#endif
diff --git a/src/include/auto_spawn.h b/src/include/auto_spawn.h
new file mode 100644
index 0000000..165d988
--- /dev/null
+++ b/src/include/auto_spawn.h
@@ -0,0 +1,6 @@
+#ifndef AUTO_SPAWN_H
+#define AUTO_SPAWN_H
+
+extern int auto_spawn;
+
+#endif
diff --git a/src/include/auto_split.h b/src/include/auto_split.h
new file mode 100644
index 0000000..3754129
--- /dev/null
+++ b/src/include/auto_split.h
@@ -0,0 +1,6 @@
+#ifndef AUTO_SPLIT_H
+#define AUTO_SPLIT_H
+
+extern int auto_split;
+
+#endif
diff --git a/src/include/auto_uids.h b/src/include/auto_uids.h
new file mode 100644
index 0000000..1252ecb
--- /dev/null
+++ b/src/include/auto_uids.h
@@ -0,0 +1,16 @@
+#ifndef AUTO_UIDS_H
+#define AUTO_UIDS_H
+
+extern int auto_uida;
+extern int auto_uidd;
+extern int auto_uidl;
+extern int auto_uido;
+extern int auto_uidp;
+extern int auto_uidq;
+extern int auto_uidr;
+extern int auto_uids;
+
+extern int auto_gidn;
+extern int auto_gidq;
+
+#endif
diff --git a/src/include/auto_usera.h b/src/include/auto_usera.h
new file mode 100644
index 0000000..49d7755
--- /dev/null
+++ b/src/include/auto_usera.h
@@ -0,0 +1,6 @@
+#ifndef AUTO_USERA_H
+#define AUTO_USERA_H
+
+extern char auto_usera[];
+
+#endif
diff --git a/src/include/base64.h b/src/include/base64.h
new file mode 100644
index 0000000..9384411
--- /dev/null
+++ b/src/include/base64.h
@@ -0,0 +1,9 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+#include "stralloc.h"
+
+extern int b64decode(const unsigned char *,int,stralloc *);
+extern int b64encode(stralloc *,stralloc *);
+
+#endif
diff --git a/src/include/commands.h b/src/include/commands.h
new file mode 100644
index 0000000..3fd2cb8
--- /dev/null
+++ b/src/include/commands.h
@@ -0,0 +1,12 @@
+#ifndef COMMANDS_H
+#define COMMANDS_H
+
+struct commands {
+ char *text;
+ void (*fun)();
+ void (*flush)();
+} ;
+
+int commands();
+
+#endif
diff --git a/src/include/constmap.h b/src/include/constmap.h
new file mode 100644
index 0000000..750702e
--- /dev/null
+++ b/src/include/constmap.h
@@ -0,0 +1,21 @@
+#ifndef CONSTMAP_H
+#define CONSTMAP_H
+
+typedef unsigned long constmap_hash;
+
+struct constmap {
+ int num;
+ constmap_hash mask;
+ constmap_hash *hash;
+ int *first;
+ int *next;
+ char **input;
+ int *inputlen;
+} ;
+
+int constmap_init(struct constmap *,char *,int,int);
+int constmap_init_char(struct constmap *,char *,int,int,char);
+void constmap_free();
+char *constmap();
+
+#endif
diff --git a/src/include/control.h b/src/include/control.h
new file mode 100644
index 0000000..732042c
--- /dev/null
+++ b/src/include/control.h
@@ -0,0 +1,12 @@
+#ifndef CONTROL_H
+#define CONTROL_H
+
+#include "stralloc.h"
+
+int control_init(void);
+int control_readline(stralloc *,char *);
+int control_rldef(stralloc *,char *,int,char *);
+int control_readint(int *,char *);
+int control_readfile(stralloc *,char *,int);
+
+#endif
diff --git a/src/include/date822fmt.h b/src/include/date822fmt.h
new file mode 100644
index 0000000..a2f1432
--- /dev/null
+++ b/src/include/date822fmt.h
@@ -0,0 +1,7 @@
+#ifndef DATE822FMT_H
+#define DATE822FMT_H
+
+unsigned int date822fmt(char *,struct datetime *);
+#define DATE822FMT 60
+
+#endif
diff --git a/src/include/datetime.h b/src/include/datetime.h
new file mode 100644
index 0000000..68d1618
--- /dev/null
+++ b/src/include/datetime.h
@@ -0,0 +1,20 @@
+#ifndef DATETIME_H
+#define DATETIME_H
+
+struct datetime {
+ int hour;
+ int min;
+ int sec;
+ int wday;
+ int mday;
+ int yday;
+ int mon;
+ int year;
+} ;
+
+typedef long datetime_sec;
+
+void datetime_tai();
+datetime_sec datetime_untai();
+
+#endif
diff --git a/src/include/dkim.h b/src/include/dkim.h
new file mode 100644
index 0000000..508b2df
--- /dev/null
+++ b/src/include/dkim.h
@@ -0,0 +1,154 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#define DKIM_CALL
+#define MAKELONG(a,b) ((long)(((unsigned)(a) & 0xffff) | (((unsigned)(b) & 0xffff) << 16)))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// DKIM hash algorithms
+#define DKIM_HASH_SHA1 1
+#define DKIM_HASH_SHA256 2
+#define DKIM_HASH_SHA1_AND_SHA256 3
+#define DKIM_HASH_ED25519 4
+#define DKIM_HASH_RSA256_AND_ED25519 5
+
+// DKIM canonicalization methods
+#define DKIM_CANON_SIMPLE 1
+#define DKIM_CANON_NOWSP 2
+#define DKIM_CANON_RELAXED 3
+
+#define DKIM_SIGN_SIMPLE MAKELONG(DKIM_CANON_SIMPLE,DKIM_CANON_SIMPLE)
+#define DKIM_SIGN_SIMPLE_RELAXED MAKELONG(DKIM_CANON_RELAXED,DKIM_CANON_SIMPLE)
+#define DKIM_SIGN_RELAXED MAKELONG(DKIM_CANON_RELAXED,DKIM_CANON_RELAXED)
+#define DKIM_SIGN_RELAXED_SIMPLE MAKELONG(DKIM_CANON_SIMPLE,DKIM_CANON_RELAXED)
+
+// DKIM Error codes
+#define DKIM_SUCCESS 0 // operation successful
+#define DKIM_FAIL -1 // verify error: message is suspicious
+#define DKIM_BAD_SYNTAX -2 // signature error: DKIM-Signature could not parse or has bad tags/values
+#define DKIM_SIGNATURE_BAD -3 // signature error: RSA/ED25519 verify failed
+#define DKIM_SIGNATURE_BAD_BUT_TESTING -4 // signature error: RSA/ED25519 verify failed but testing
+#define DKIM_SIGNATURE_EXPIRED -5 // signature error: x= is old
+#define DKIM_SELECTOR_INVALID -6 // signature error: selector doesn't parse or contains invalid values
+#define DKIM_SELECTOR_GRANULARITY_MISMATCH -7 // signature error: selector g= doesn't match i=
+#define DKIM_SELECTOR_KEY_REVOKED -8 // signature error: selector p= empty
+#define DKIM_SELECTOR_DOMAIN_NAME_TOO_LONG -9 // signature error: selector domain name too long to request
+#define DKIM_SELECTOR_DNS_TEMP_FAILURE -10 // signature error: temporary dns failure requesting selector
+#define DKIM_SELECTOR_DNS_PERM_FAILURE -11 // signature error: permanent dns failure requesting selector
+#define DKIM_SELECTOR_PUBLIC_KEY_INVALID -12 // signature error: selector p= value invalid or wrong format
+#define DKIM_NO_SIGNATURES -13 // process error, no sigs
+#define DKIM_NO_VALID_SIGNATURES -14 // process error, no valid sigs
+#define DKIM_BODY_HASH_MISMATCH -15 // sigature verify error: message body does not hash to bh value
+#define DKIM_SELECTOR_ALGORITHM_MISMATCH -16 // signature error: selector h= doesn't match signature a=
+#define DKIM_STAT_INCOMPAT -17 // signature error: incompatible v=
+#define DKIM_UNSIGNED_FROM -18 // signature error: not all message's From headers in signature
+#define DKIM_OUT_OF_MEMORY -20 // memory allocation failed
+#define DKIM_INVALID_CONTEXT -21 // DKIMContext structure invalid for this operation
+#define DKIM_NO_SENDER -22 // signing error: Could not find From: or Sender: header in message
+#define DKIM_BAD_PRIVATE_KEY -23 // signing error: Could not parse private key
+#define DKIM_BUFFER_TOO_SMALL -24 // signing error: Buffer passed in is not large enough
+#define DKIM_MAX_ERROR -25 // set this to 1 greater than the highest error code (but negative)
+
+// DKIM_SUCCESS // verify result: all signatures verified
+ // signature result: signature verified
+#define DKIM_FINISHED_BODY 1 // process result: no more message body is needed
+#define DKIM_PARTIAL_SUCCESS 2 // verify result: at least one but not all signatures verified
+#define DKIM_NEUTRAL 3 // verify result: no signatures verified but message is not suspicous
+#define DKIM_SUCCESS_BUT_EXTRA 4 // signature result: signature verified but it did not include all of the body
+
+
+
+// This function is called once for each header in the message
+// return 1 to include this header in the signature and 0 to exclude.
+typedef int (DKIM_CALL *DKIMHEADERCALLBACK)(const char* szHeader);
+
+// This function is called to retrieve a TXT record from DNS
+typedef int (DKIM_CALL *DKIMDNSCALLBACK)(const char* szFQDN,char* szBuffer,int nBufLen);
+
+typedef struct DKIMContext_t
+{
+ unsigned int reserved1;
+ unsigned int reserved2;
+ void* reserved3;
+} DKIMContext;
+
+typedef struct DKIMSignOptions_t
+{
+ int nCanon; // canonization
+ int nIncludeBodyLengthTag; // 0 = don't include l= tag, 1 = include l= tag
+ int nIncludeTimeStamp; // 0 = don't include t= tag, 1 = include t= tag
+ int nIncludeQueryMethod; // 0 = don't include q= tag, 1 = include q= tag
+ char szSelector[64]; // selector - required
+ char szSelectorE[64]; // 2nd selector - optional
+ char szDomain[256]; // domain - optional - if empty, domain is computed from sender
+ char szIdentity[256]; // for i= tag, if empty tag will not be included in sig
+ unsigned long expireTime; // for x= tag, if 0 tag will not be included in sig
+ DKIMHEADERCALLBACK pfnHeaderCallback; // header callback
+ char szRequiredHeaders[256]; // colon-separated list of headers that must be signed
+ int nHash; // use one of the DKIM_HASH_xx constants here
+ // even if not present in the message
+ int nIncludeCopiedHeaders; // 0 = don't include z= tag, 1 = include z= tag
+} DKIMSignOptions;
+
+typedef struct DKIMVerifyOptions_t
+{
+ DKIMDNSCALLBACK pfnSelectorCallback; // selector record callback
+ DKIMDNSCALLBACK pfnPracticesCallback; // ADSP record callback
+ int nHonorBodyLengthTag; // 0 = ignore l= tag, 1 = use l= tag to limit the amount of body verified
+ int nCheckPractices; // 0 = use default (unknown) practices, 1 = request and use author domain signing practices
+ int nSubjectRequired; // 0 = subject is required to be signed, 1 = not required
+ int nSaveCanonicalizedData; // 0 = canonicalized data is not saved, 1 = canonicalized data is saved
+ int nAllowUnsignedFromHeaders; // 0 = From headers not included in the signature are not allowed, 1 = allowed
+} DKIMVerifyOptions;
+
+typedef struct DKIMVerifyDetails_t
+{
+ char *szSignature;
+ char *szSignatureDomain;
+ char *szIdentityDomain;
+ char *szCanonicalizedData;
+ int nResult;
+} DKIMVerifyDetails;
+
+int DKIM_CALL DKIMSignInit(DKIMContext* pSignContext,DKIMSignOptions* pOptions);
+int DKIM_CALL DKIMSignProcess(DKIMContext* pSignContext,char* szBuffer,int nBufLength);
+int DKIM_CALL DKIMSignGetSig2(DKIMContext* pSignContext,char* szRSAPrivKey,char *szECCPrivKey,char** pszSignature);
+void DKIM_CALL DKIMSignFree(DKIMContext* pSignContext);
+
+int DKIM_CALL DKIMVerifyInit(DKIMContext* pVerifyContext,DKIMVerifyOptions* pOptions);
+int DKIM_CALL DKIMVerifyProcess(DKIMContext* pVerifyContext,const char* szBuffer,int nBufLength);
+int DKIM_CALL DKIMVerifyResults(DKIMContext* pVerifyContext);
+int DKIM_CALL DKIMVerifyGetDetails(DKIMContext* pVerifyContext,int* nSigCount,DKIMVerifyDetails** pDetails,char* szPractices);
+void DKIM_CALL DKIMVerifyFree(DKIMContext* pVerifyContext);
+
+// const char *DKIM_CALL DKIMVersion();
+
+const char *DKIM_CALL DKIMGetErrorString(int ErrorCode);
+
+int _DKIM_ReportResult(char const *,char const *,char const *);
+const char *DKIM_ErrorResult(const int);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/include/dkimbase.h b/src/include/dkimbase.h
new file mode 100644
index 0000000..25aac02
--- /dev/null
+++ b/src/include/dkimbase.h
@@ -0,0 +1,79 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#ifndef DKIMBASE_H
+#define DKIMBASE_H
+
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+
+#define BUFFER_ALLOC_INCREMENT 256
+
+#include <string>
+#include <list>
+
+using namespace std;
+
+class CDKIMBase
+{
+public:
+
+ CDKIMBase();
+ ~CDKIMBase();
+
+ int Init(void);
+
+ int Process(const char* szBuffer,int nBufLength,bool bEOF);
+ int ProcessFinal(void);
+
+ int Alloc(char*& szBuffer,int nRequiredSize);
+ int ReAlloc(char*& szBuffer,int& nBufferLength,int nRequiredSize);
+ void Free(char* szBuffer);
+
+ static void RemoveSWSP(char* szBuffer);
+ static void RemoveSWSP(char* pBuffer,int& nBufLength);
+ static void RemoveSWSP(string& sBuffer);
+
+ static void CompressSWSP(char* pBuffer,int& nBufLength);
+ static void CompressSWSP(string& sBuffer);
+
+ static string RelaxHeader(const string& sHeader);
+
+ virtual int ProcessHeaders(void);
+ virtual int ProcessBody(char* szBuffer,int nBufLength,bool bEOF);
+
+protected:
+ char* m_From;
+ char* m_Sender;
+ char* m_hTag;
+ int m_hTagSize;
+ int m_hTagPos;
+ char* m_Line;
+ int m_LineSize;
+ int m_LinePos;
+ bool m_InHeaders;
+
+ list<string> HeaderList;
+};
+
+
+#endif // DKIMBASE_H
diff --git a/src/include/dkimsign.h b/src/include/dkimsign.h
new file mode 100644
index 0000000..150a0b8
--- /dev/null
+++ b/src/include/dkimsign.h
@@ -0,0 +1,113 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#ifndef DKIMSIGN_H
+#define DKIMSIGN_H
+
+#include "dkimbase.h"
+
+class CDKIMSign : public CDKIMBase
+{
+public:
+ CDKIMSign();
+ ~CDKIMSign();
+
+ //int Init() = delete;
+ int Init(DKIMSignOptions* pOptions);
+ int GetSig2(char* szRSAPrivKey,char* szECCPrivKey,char** pszSignature);
+
+ virtual int ProcessHeaders(void) override;
+ virtual int ProcessBody(char* szBuffer,int nBufLength,bool bEOF) override;
+
+ enum CKDKIMConstants { OptimalHeaderLineLength = 65 };
+
+ void Hash(const char* szBuffer,int nBufLength,bool bHdr);
+
+protected:
+
+ bool SignThisTag(const string& sTag);
+ void GetHeaderParams(const string& sHdr);
+ void ProcessHeader(const string& sHdr);
+ bool ParseFromAddress(void);
+
+ void InitSig(void);
+ void AddTagToSig(const char* const Tag,const string &sValue,char cbrk,bool bFold);
+ void AddTagToSig(const char* const Tag,unsigned long nValue);
+ void AddInterTagSpace(int nSizeOfNextTag);
+ void AddFoldedValueToSig(const string &sValue,char cbrk);
+
+ bool IsRequiredHeader(const string& sTag);
+ int ConstructSignature(char* szSignKey,int nSigAlg);
+
+ int AssembleReturnedSig(char* szRSAPrivKey,char* szECCPrivKey);
+
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_MD_CTX m_Hdr_sha1ctx; /* the RSA SHA1 signature */
+ EVP_MD_CTX m_Hdr_sha256ctx; /* the RSA SHA256 signature */
+
+ EVP_MD_CTX m_Bdy_sha1ctx; /* the SHA1 digest */
+ EVP_MD_CTX m_Bdy_sha256ctx; /* the SHA256 digest */
+#else
+ EVP_MD_CTX *m_Hdr_sha1ctx; /* the RSA SHA1 signature */
+ EVP_MD_CTX *m_Hdr_sha256ctx; /* the RSA SHA256 signature */
+ EVP_MD_CTX *m_Hdr_ed25519ctx; /* the PureEd25519 signature */
+
+ EVP_MD_CTX *m_Bdy_sha1ctx; /* the SHA1 digest */
+ EVP_MD_CTX *m_Bdy_sha256ctx; /* the SHA256 digest for RSA */
+ EVP_MD_CTX *m_Edy_sha256ctx; /* the SHA256 digest for Ed25519 */
+#endif
+
+ int m_Canon; /* canonization method */
+
+ int m_EmptyLineCount;
+
+ string hParam;
+ string sFrom;
+ string sSender;
+ string sSelector;
+ string eSelector; /* Used for Ed25519 signatures */
+ string sDomain;
+ string sIdentity; /* for i= tag, if empty tag will not be included in sig */
+ string sRequiredHeaders;
+
+ bool m_IncludeBodyLengthTag;
+ int m_nBodyLength;
+ time_t m_ExpireTime;
+ int m_nIncludeTimeStamp; // 0 = don't include t= tag, 1 = include t= tag
+ int m_nIncludeQueryMethod; // 0 = don't include q= tag, 1 = include q= tag
+ int m_nHash; // use one of the DKIM_HASH_xx constants here
+ int m_nIncludeCopiedHeaders; // 0 = don't include z= tag, 1 = include z= tag
+
+ DKIMHEADERCALLBACK m_pfnHdrCallback;
+
+ string m_sSig; // DKIM-Signature ....
+ int m_nSigPos;
+
+ string m_sReturnedSig;
+ bool m_bReturnedSigAssembled;
+
+ string m_sCopiedHeaders;
+
+ string SigHdrs;
+ int m_SigHdrs;
+};
+
+#endif // DKIMSIGN_H
diff --git a/src/include/dkimverify.h b/src/include/dkimverify.h
new file mode 100644
index 0000000..64de2a1
--- /dev/null
+++ b/src/include/dkimverify.h
@@ -0,0 +1,152 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+*****************************************************************************/
+#ifndef DKIMVERIFY_H
+#define DKIMVERIFY_H
+
+#include "dkimbase.h"
+#include <vector>
+
+/* not used anymore
+#define DKIM_ADSP_UNKNOWN 1
+#define DKIM_ADSP_ALL 2
+#define DKIM_ADSP_DISCARDABLE 3
+*/
+
+#define DKIM_POLICY_DOMAIN_NAME_TOO_LONG -50 // internal error
+#define DKIM_POLICY_DNS_TEMP_FAILURE -51 // internal error
+#define DKIM_POLICY_DNS_PERM_FAILURE -52 // internal error
+#define DKIM_POLICY_INVALID -53 // internal error
+
+/* dito
+#define DKIM_SIG_VERSION_PRE_02 0
+#define DKIM_SIG_VERSION_02_PLUS 1
+*/
+
+class SelectorInfo
+{
+public:
+ SelectorInfo(const string &sSelector,const string &sDomain);
+ ~SelectorInfo();
+
+ string Domain;
+ string Selector;
+ string Granularity;
+ bool AllowSHA1;
+ bool AllowSHA256;
+ EVP_PKEY *PublicKey; /* the public key */
+ bool Testing;
+ bool SameDomain;
+
+ int Status;
+
+ int Parse(char* Buffer);
+};
+
+class SignatureInfo
+{
+public:
+ SignatureInfo(bool SaveCanonicalizedData);
+ ~SignatureInfo();
+
+ void Hash(const char* szBuffer,unsigned nBufLength,bool IsBody=false);
+
+ string Header;
+ unsigned Version;
+ string Domain;
+ string Selector;
+ string SignatureData;
+ string BodyHashData;
+ string IdentityLocalPart;
+ string IdentityDomain;
+ string CanonicalizedData;
+ vector<string> SignedHeaders;
+ unsigned BodyLength;
+ unsigned HeaderCanonicalization;
+ unsigned BodyCanonicalization;
+ unsigned ExpireTime;
+
+ unsigned VerifiedBodyCount;
+ unsigned UnverifiedBodyCount;
+
+#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
+ EVP_MD_CTX m_Hdr_ctx;
+ EVP_MD_CTX m_Bdy_ctx;
+#else
+ EVP_MD_CTX *m_Hdr_ctx;
+ EVP_MD_CTX *m_Bdy_ctx;
+#endif
+#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
+ EVP_MD_CTX *m_Msg_ctx;
+#endif
+
+ SelectorInfo *m_pSelector;
+
+ int Status;
+ int m_nHash; // use one of the DKIM_HASH_xxx constants here
+ unsigned EmptyLineCount;
+ bool m_SaveCanonicalizedData;
+};
+
+class CDKIMVerify : public CDKIMBase
+{
+public:
+
+ CDKIMVerify();
+ ~CDKIMVerify();
+// virtual ~CDKIMVerify() = 0;
+
+ int Init(DKIMVerifyOptions* pOptions);
+
+ int GetResults(void);
+ int GetDetails(int* nSigCount,DKIMVerifyDetails** pDetails);
+// int _DNSGetTXT(const char* szFQDN,char* Buffer,int nBufLen);
+
+ virtual int ProcessHeaders(void);
+ virtual int ProcessBody(char* szBuffer,int nBufLength,bool bEOF);
+
+ const char* GetPractices() { return Practices.c_str(); }
+
+protected:
+
+ int ParseDKIMSignature(const string& sHeader,SignatureInfo &sig);
+
+ SelectorInfo& GetSelector(const string &sSelector,const string &sDomain);
+
+// int GetADSP(const string &sDomain,int &iADSP);
+
+ list<SignatureInfo> Signatures;
+ list<SelectorInfo> Selectors;
+
+ DKIMDNSCALLBACK m_pfnSelectorCallback; // selector record callback
+// DKIMDNSCALLBACK m_pfnPracticesCallback; // ADSP record callback
+
+ bool m_HonorBodyLengthTag;
+ bool m_CheckPractices;
+ bool m_SubjectIsRequired;
+ bool m_SaveCanonicalizedData;
+ bool m_AllowUnsignedFromHeaders;
+
+ vector<DKIMVerifyDetails> Details;
+ string Practices;
+};
+
+#endif //DKIMVERIFY_H
diff --git a/src/include/dns.h b/src/include/dns.h
new file mode 100644
index 0000000..6293478
--- /dev/null
+++ b/src/include/dns.h
@@ -0,0 +1,27 @@
+#ifndef DNS_H
+#define DNS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dnsresolv.h"
+#include "ipalloc.h"
+#include "stralloc.h"
+
+#define DNS_INIT static char seed[128]; dns_random_init(seed);
+#define DNS_NXD 0
+#define DNS_SOFT -5
+#define DNS_HARD -6
+
+void dns_init(int);
+int dns_ip(ipalloc *,stralloc *);
+int dns_mxip(ipalloc *,stralloc *,unsigned long);
+int dns_tlsa(stralloc *,const stralloc *);
+int dns_mxhost(stralloc *,const stralloc *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/include/dnsdoe.h b/src/include/dnsdoe.h
new file mode 100644
index 0000000..eacd7fc
--- /dev/null
+++ b/src/include/dnsdoe.h
@@ -0,0 +1,6 @@
+#ifndef DNSDOE_H
+#define DNSDOE_H
+
+void dnsdoe(int);
+
+#endif
diff --git a/src/include/dnsgettxt.h b/src/include/dnsgettxt.h
new file mode 100644
index 0000000..6d6b8ea
--- /dev/null
+++ b/src/include/dnsgettxt.h
@@ -0,0 +1,7 @@
+#ifdef cplusplus
+extern "C" {
+
+int DNSGetTXT(const char* szFQDN,char* Buffer,int nBufLen);
+extern void dns_random_init(const char [12]);
+
+#endif
diff --git a/src/include/exit.h b/src/include/exit.h
new file mode 100644
index 0000000..d7351ba
--- /dev/null
+++ b/src/include/exit.h
@@ -0,0 +1,16 @@
+#ifndef EXIT_H
+#define EXIT_H
+
+/* Return code conventions:
+
+ 110: Unable to access dir
+ 111: General (memory) error
+ 112: Unable to access file
+*/
+
+void _exit();
+
+int rename(const char *, const char *);
+
+
+#endif
diff --git a/src/include/extra.h b/src/include/extra.h
new file mode 100644
index 0000000..c598175
--- /dev/null
+++ b/src/include/extra.h
@@ -0,0 +1,7 @@
+#ifndef EXTRA_H
+#define EXTRA_H
+
+#define QUEUE_EXTRA ""
+#define QUEUE_EXTRALEN 0
+
+#endif
diff --git a/src/include/fifo.h b/src/include/fifo.h
new file mode 100644
index 0000000..f48c863
--- /dev/null
+++ b/src/include/fifo.h
@@ -0,0 +1,6 @@
+#ifndef FIFO_H
+#define FIFO_H
+
+int fifo_make(char *,int);
+
+#endif
diff --git a/src/include/fmtqfn.h b/src/include/fmtqfn.h
new file mode 100644
index 0000000..e11e51e
--- /dev/null
+++ b/src/include/fmtqfn.h
@@ -0,0 +1,8 @@
+#ifndef FMTQFN_H
+#define FMTQFN_H
+
+unsigned int fmtqfn(char *,char *,unsigned long,int);
+
+#define FMTQFN 40 /* maximum space needed, if len(dirslash) <= 10 */
+
+#endif
diff --git a/src/include/gfrom.h b/src/include/gfrom.h
new file mode 100644
index 0000000..121ff6d
--- /dev/null
+++ b/src/include/gfrom.h
@@ -0,0 +1,6 @@
+#ifndef GFROM_H
+#define GFROM_H
+
+int gfrom(char *,int);
+
+#endif
diff --git a/src/include/global.h b/src/include/global.h
new file mode 100644
index 0000000..2d8ccf4
--- /dev/null
+++ b/src/include/global.h
@@ -0,0 +1,53 @@
+/* GLOBAL.H - RSAREF types and constants */
+
+#include <string.h>
+#include "uint_t.h"
+
+/* Copyright (C) RSA Laboratories, a division of RSA Data Security,
+ Inc., created 1991. All rights reserved.
+ */
+
+#ifndef _GLOBAL_H_
+#define _GLOBAL_H_ 1
+
+/* PROTOTYPES should be set to one if and only if the compiler supports
+ function argument prototyping.
+ The following makes PROTOTYPES default to 1 if it has not already been
+ defined as 0 with C compiler flags.
+ */
+#ifndef PROTOTYPES
+#define PROTOTYPES 1
+#endif
+
+/* POINTER defines a generic pointer type */
+typedef unsigned char *POINTER;
+
+/* UINT2 defines a two byte word */
+typedef unsigned short int UINT2;
+
+/* UINT4 defines a four byte word */
+#ifdef UINT32_H
+#define UINT4 uint32
+#else
+typedef unsigned long int UINT4;
+#endif
+
+#ifndef NULL_PTR
+#define NULL_PTR ((POINTER)0)
+#endif
+
+#ifndef UNUSED_ARG
+#define UNUSED_ARG(x) x = *(&x);
+#endif
+
+/* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
+ If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
+ returns an empty list.
+ */
+#if PROTOTYPES
+#define PROTO_LIST(list) list
+#else
+#define PROTO_LIST(list) ()
+#endif
+
+#endif /* end _GLOBAL_H_ */
diff --git a/src/include/headerbody.h b/src/include/headerbody.h
new file mode 100644
index 0000000..a074981
--- /dev/null
+++ b/src/include/headerbody.h
@@ -0,0 +1,6 @@
+#ifndef HEADERBODY_H
+#define HEADERBODY_H
+
+int headerbody();
+
+#endif
diff --git a/src/include/hfield.h b/src/include/hfield.h
new file mode 100644
index 0000000..ab367f4
--- /dev/null
+++ b/src/include/hfield.h
@@ -0,0 +1,38 @@
+#ifndef HFIELD_H
+#define HFIELD_H
+
+unsigned int hfield_skipname();
+int hfield_known();
+int hfield_valid();
+
+#define H_SENDER 1
+#define H_FROM 2
+#define H_REPLYTO 3
+#define H_TO 4
+#define H_CC 5
+#define H_BCC 6
+#define H_DATE 7
+#define H_MESSAGEID 8
+#define H_SUBJECT 9
+#define H_R_SENDER 10
+#define H_R_FROM 11
+#define H_R_REPLYTO 12
+#define H_R_TO 13
+#define H_R_CC 14
+#define H_R_BCC 15
+#define H_R_DATE 16
+#define H_R_MESSAGEID 17
+#define H_RETURNRECEIPTTO 18
+#define H_ERRORSTO 19
+#define H_APPARENTLYTO 20
+#define H_RECEIVED 21
+#define H_RETURNPATH 22
+#define H_DELIVEREDTO 23
+#define H_CONTENTLENGTH 24
+#define H_CONTENTTYPE 25
+#define H_CONTENTTRANSFERENCODING 26
+#define H_NOTICEREQUESTEDUPONDELIVERYTO 27
+#define H_MAILFOLLOWUPTO 28
+#define H_NUM 29
+
+#endif
diff --git a/src/include/hier.h b/src/include/hier.h
new file mode 100644
index 0000000..f040e79
--- /dev/null
+++ b/src/include/hier.h
@@ -0,0 +1,10 @@
+#ifndef HIER_H
+#define HIER_H
+
+void c(char *,char *,char *,int,int,int);
+void h(char *,int,int,int);
+void d(char *,char *,int,int,int);
+void p(char *,char *,int,int,int);
+void z(char *,char *,int,int,int,int);
+
+#endif
diff --git a/src/include/hmac_md5.h b/src/include/hmac_md5.h
new file mode 100644
index 0000000..87a2c37
--- /dev/null
+++ b/src/include/hmac_md5.h
@@ -0,0 +1,7 @@
+#ifndef HMAC_MD5_H
+#define HMAC_MD5_H
+
+void hmac_md5(unsigned char *,int,unsigned char *,int,unsigned char *);
+
+#endif
+
diff --git a/src/include/ipalloc.h b/src/include/ipalloc.h
new file mode 100644
index 0000000..0f58e92
--- /dev/null
+++ b/src/include/ipalloc.h
@@ -0,0 +1,22 @@
+#ifndef IPALLOC_H
+#define IPALLOC_H
+
+#include "ip.h"
+#include "genalloc.h"
+#define NAME_LEN 256
+
+struct ip_mx {
+ unsigned short af;
+ union {
+ struct ip4_address ip4;
+ struct ip6_address ip6;
+ } addr;
+ int pref;
+ char mxh[NAME_LEN];
+};
+
+GEN_ALLOC_typedef(ipalloc,struct ip_mx,ix,len,a)
+int ipalloc_readyplus();
+int ipalloc_append();
+
+#endif
diff --git a/src/include/ipme.h b/src/include/ipme.h
new file mode 100644
index 0000000..9705f45
--- /dev/null
+++ b/src/include/ipme.h
@@ -0,0 +1,14 @@
+#ifndef IPME_H
+#define IPME_H
+
+#include "ip.h"
+#include "ipalloc.h"
+
+extern ipalloc ipme;
+
+int ipme_init();
+int ipme_is4();
+int ipme_is6();
+int ipme_is();
+
+#endif
diff --git a/src/include/maildir.h b/src/include/maildir.h
new file mode 100644
index 0000000..5e48822
--- /dev/null
+++ b/src/include/maildir.h
@@ -0,0 +1,13 @@
+#ifndef MAILDIR_H
+#define MAILDIR_H
+
+#include "logmsg.h"
+#include "prioq.h"
+
+extern struct strerr maildir_chdir_err;
+extern struct strerr maildir_scan_err;
+
+int maildir_chdir(void);
+void maildir_clean(stralloc *);
+int maildir_scan(prioq *,stralloc *,int,int);
+#endif
diff --git a/src/include/md5.h b/src/include/md5.h
new file mode 100644
index 0000000..94774ba
--- /dev/null
+++ b/src/include/md5.h
@@ -0,0 +1,49 @@
+/* MD5.H - header file for MD5C.C
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+#ifndef _MD5_H_
+#define _MD5_H_ 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* MD5 context. */
+typedef struct {
+ UINT4 state[4]; /* state (ABCD) */
+ UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+void MD5Init PROTO_LIST ((MD5_CTX *));
+void MD5Update PROTO_LIST
+ ((MD5_CTX *, unsigned char *, unsigned int));
+void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/include/mfrules.h b/src/include/mfrules.h
new file mode 100644
index 0000000..b79f338
--- /dev/null
+++ b/src/include/mfrules.h
@@ -0,0 +1,9 @@
+#ifndef MFRULES_H
+#define MFRULES_H
+
+#include "stralloc.h"
+
+extern stralloc key;
+int mfrules(int,char *,char *,char *,char *);
+
+#endif
diff --git a/src/include/myctime.h b/src/include/myctime.h
new file mode 100644
index 0000000..97a30ca
--- /dev/null
+++ b/src/include/myctime.h
@@ -0,0 +1,8 @@
+#ifndef MYCTIME_H
+#define MYCTIME_H
+
+#include "datetime.h"
+
+char *myctime(datetime_sec);
+
+#endif
diff --git a/src/include/newfield.h b/src/include/newfield.h
new file mode 100644
index 0000000..049fb5e
--- /dev/null
+++ b/src/include/newfield.h
@@ -0,0 +1,12 @@
+#ifndef NEWFIELD_H
+#define NEWFIELD_H
+
+#include "stralloc.h"
+
+extern stralloc newfield_date;
+int newfield_datemake();
+
+extern stralloc newfield_msgid;
+int newfield_msgidmake();
+
+#endif
diff --git a/src/include/now.h b/src/include/now.h
new file mode 100644
index 0000000..1379d76
--- /dev/null
+++ b/src/include/now.h
@@ -0,0 +1,8 @@
+#ifndef NOW_H
+#define NOW_H
+
+#include "datetime.h"
+
+datetime_sec now();
+
+#endif
diff --git a/src/include/prioq.h b/src/include/prioq.h
new file mode 100644
index 0000000..3547b1c
--- /dev/null
+++ b/src/include/prioq.h
@@ -0,0 +1,15 @@
+#ifndef PRIOQ_H
+#define PRIOQ_H
+
+#include "datetime.h"
+#include "genalloc.h"
+
+struct prioq_elt { datetime_sec dt; unsigned long id; } ;
+
+GEN_ALLOC_typedef(prioq,struct prioq_elt,p,len,a)
+
+int prioq_insert();
+int prioq_min();
+void prioq_delmin();
+
+#endif
diff --git a/src/include/prot.h b/src/include/prot.h
new file mode 100644
index 0000000..08203da
--- /dev/null
+++ b/src/include/prot.h
@@ -0,0 +1,7 @@
+#ifndef PROT_H
+#define PROT_H
+
+int prot_gid();
+int prot_uid();
+
+#endif
diff --git a/src/include/qlx.h b/src/include/qlx.h
new file mode 100644
index 0000000..713946d
--- /dev/null
+++ b/src/include/qlx.h
@@ -0,0 +1,18 @@
+#ifndef QLX_H
+#define QLX_H
+
+/* 0, 111, 100 are qmail-local success, soft, hard */
+
+#define QLX_USAGE 112
+#define QLX_BUG 101
+#define QLX_ROOT 113
+#define QLX_NFS 115
+#define QLX_NOALIAS 116
+#define QLX_CDB 117
+#define QLX_SYS 118
+#define QLX_NOMEM 119
+#define QLX_EXECSOFT 120
+#define QLX_EXECPW 121
+#define QLX_EXECHARD 126
+
+#endif
diff --git a/src/include/qmail.h b/src/include/qmail.h
new file mode 100644
index 0000000..7db214d
--- /dev/null
+++ b/src/include/qmail.h
@@ -0,0 +1,24 @@
+#ifndef QMAIL_H
+#define QMAIL_H
+
+#include "buffer.h"
+
+struct qmail {
+ int flagerr;
+ unsigned long pid;
+ int fdm;
+ int fde;
+ buffer ss;
+ char buf[1024];
+} ;
+
+extern int qmail_open(struct qmail *);
+extern void qmail_put(struct qmail *,char *, int);
+extern void qmail_puts(struct qmail *,char *);
+extern void qmail_from(struct qmail *,char *);
+extern void qmail_to(struct qmail *,char *);
+extern void qmail_fail(struct qmail *);
+extern char *qmail_close(struct qmail *);
+extern unsigned long qmail_qp(struct qmail *);
+
+#endif
diff --git a/src/include/qsutil.h b/src/include/qsutil.h
new file mode 100644
index 0000000..a77a3f8
--- /dev/null
+++ b/src/include/qsutil.h
@@ -0,0 +1,17 @@
+#ifndef QSUTIL_H
+#define QSUTIL_H
+
+#include "stralloc.h"
+
+void log1s(char *);
+void log2s(char *,char *);
+void log3s(char *,char *,char *);
+void log4s(char *,char *,char *,char *);
+void log5s(char *,char *,char *,char *,char *);
+void logsa(stralloc *);
+void nomem();
+void pausedir(char *);
+void logsafe(char *);
+int issafe(char);
+
+#endif
diff --git a/src/include/quote.h b/src/include/quote.h
new file mode 100644
index 0000000..4afbc94
--- /dev/null
+++ b/src/include/quote.h
@@ -0,0 +1,10 @@
+#ifndef QUOTE_H
+#define QUOTE_H
+
+#include "stralloc.h"
+
+int quote_need(char *,unsigned int);
+int quote(stralloc *, stralloc *);
+int quote2(stralloc *,char *);
+
+#endif
diff --git a/src/include/rcpthosts.h b/src/include/rcpthosts.h
new file mode 100644
index 0000000..0c58797
--- /dev/null
+++ b/src/include/rcpthosts.h
@@ -0,0 +1,7 @@
+#ifndef RCPTHOSTS_H
+#define RCPTHOSTS_H
+
+int rcpthosts_init();
+int rcpthosts();
+
+#endif
diff --git a/src/include/readsubdir.h b/src/include/readsubdir.h
new file mode 100644
index 0000000..e612fac
--- /dev/null
+++ b/src/include/readsubdir.h
@@ -0,0 +1,20 @@
+#ifndef READSUBDIR_H
+#define READSUBDIR_H
+
+#include "direntry.h"
+
+typedef struct readsubdir
+{
+ DIR *dir;
+ int pos;
+ char *name;
+ void (*pause)();
+}
+readsubdir;
+
+void readsubdir_init();
+int readsubdir_next();
+
+#define READSUBDIR_NAMELEN 10
+
+#endif
diff --git a/src/include/readwrite.h b/src/include/readwrite.h
new file mode 100644
index 0000000..4fdb771
--- /dev/null
+++ b/src/include/readwrite.h
@@ -0,0 +1,11 @@
+#ifndef READWRITE_H
+#define READWRITE_H
+
+#include <unistd.h>
+/* Already in unistd.h */
+/*
+int read();
+int write();
+*/
+
+#endif
diff --git a/src/include/received.h b/src/include/received.h
new file mode 100644
index 0000000..2b2ce75
--- /dev/null
+++ b/src/include/received.h
@@ -0,0 +1,9 @@
+#ifndef RECEIVED_H
+#define RECEIVED_H
+
+#include "qmail.h"
+
+void received(struct qmail *,char *,char *,char *,char *,char *,char *,char *,char *);
+void spfheader(struct qmail *,char *,char *,char *,char *,char *);
+
+#endif
diff --git a/src/include/recipients.h b/src/include/recipients.h
new file mode 100644
index 0000000..b73f37e
--- /dev/null
+++ b/src/include/recipients.h
@@ -0,0 +1,8 @@
+#ifndef RECIPIENTS_H
+#define RECIPIENTS_H
+
+int recipients_init(void);
+int recipients(char *,int);
+ssize_t safewrite();
+
+#endif
diff --git a/src/include/sendtodo.h b/src/include/sendtodo.h
new file mode 100644
index 0000000..86b4434
--- /dev/null
+++ b/src/include/sendtodo.h
@@ -0,0 +1,14 @@
+#ifndef SENDTODO_H
+#define SENDTODO_H
+
+/* critical timing feature #1: if not triggered, do not busy-loop */
+/* critical timing feature #2: if triggered, respond within fixed time */
+/* important timing feature: when triggered, respond instantly */
+#define SLEEP_TODO 1500 /* check todo/ every 25 minutes in any case */
+#define SLEEP_FUZZ 1 /* slop a bit on sleeps to avoid zeno effect */
+#define SLEEP_FOREVER 86400 /* absolute maximum time spent in select() */
+#define SLEEP_CLEANUP 76431 /* time between cleanups */
+#define SLEEP_SYSFAIL 123
+#define OSSIFIED 129600 /* 36 hours; _must_ exceed q-q's DEATH (24 hours) */
+
+#endif
diff --git a/src/include/sha1.h b/src/include/sha1.h
new file mode 100644
index 0000000..43d141d
--- /dev/null
+++ b/src/include/sha1.h
@@ -0,0 +1,31 @@
+#ifndef SHA1_H
+#define SHA1_H
+
+/*
+ SHA-1 in C
+ By Steve Reid <steve@edmweb.com>
+ 100% Public Domain
+
+ adopted for s/qmail (feh)
+ */
+
+#include <stdint.h>
+/* SHA1 implementation */
+
+#define SHA1_BLOCKSIZE 64
+#define SHA1_DIGESTSIZE 20
+
+typedef struct
+{
+ uint32_t state[5];
+ uint32_t count[2];
+ uint8_t buffer[SHA1_BLOCKSIZE];
+} sha1_ctx;
+
+void sha1_init(sha1_ctx *context);
+void sha1_update(sha1_ctx *context, const uint8_t *data, uint32_t len);
+void sha1_final(uint8_t hash[SHA1_DIGESTSIZE], sha1_ctx *context);
+void sha1_transform(uint32_t state[5], const uint8_t buffer[SHA1_BLOCKSIZE]);
+void sha1_hash(char *hash, const char *data, uint32_t len);
+
+#endif /* SHA1_H */
diff --git a/src/include/sha256.h b/src/include/sha256.h
new file mode 100644
index 0000000..e8979c5
--- /dev/null
+++ b/src/include/sha256.h
@@ -0,0 +1,18 @@
+#ifndef SHA256_H
+#define SHA256_H
+
+typedef struct
+{
+ uint8_t data[64];
+ uint32_t datalen;
+ uint32_t bitlen[2];
+ uint32_t state[8];
+} sha256_ctx;
+
+static void sha256_init(sha256_ctx *ctx);
+static void sha256_transform(sha256_ctx *ctx, uint8_t *data);
+static void sha256_update(sha256_ctx *ctx, uint8_t *data, uint32_t len);
+static void sha256_final(uint8_t *hash, sha256_ctx *ctx);
+extern void sha256_hash(char *hash,const char *data, size_t len);
+
+#endif
diff --git a/src/include/smtpdlog.h b/src/include/smtpdlog.h
new file mode 100644
index 0000000..0feb126
--- /dev/null
+++ b/src/include/smtpdlog.h
@@ -0,0 +1,73 @@
+#ifndef SMTPDLOG_H
+#define SMTPDLOG_H
+#define FDLOG 2
+
+void flush();
+void out();
+
+void smtpdlog_init(void);
+void smtp_loga(char *,char *,char *,char *,char *,char *,char *,char *,char *);
+void smtp_logb(char *,char *,char *,char *,char *,char *,char *);
+void smtp_logg(char *,char *,char *,char *,char *,char *,char *);
+void smtp_logh(char *,char *,char *,char *,char *);
+void smtp_logi(char *,char *,char *,char *,char *,char *,char *,char *);
+void smtp_logr(char *,char *,char *,char *,char *,char *,char *,char *);
+
+void die_read(void);
+void die_alarm(void);
+void die_nomem(void);
+void die_control(void);
+void die_ipme(void);
+void die_starttls(void);
+void die_recipients(void);
+void straynewline(void);
+
+void err_unimpl(void);
+void err_syntax(void);
+void err_noop(void);
+void err_vrfy(void);
+void err_wantrcpt(void);
+void err_qqt(void);
+
+int err_child(void);
+int err_fork(void);
+int err_pipe(void);
+int err_write(void);
+int err_starttls(void);
+void err_tlsreq(char *,char *,char *,char *,char *);
+
+void err_helo(char *,char *,char *,char *,char *,char *,char *,char *);
+void err_spf(char *,char *,char *,char *,char *,char *,char *,char *);
+
+void err_authsetup(char *,char *,char *,char *,char *);
+void err_authd(void);
+void err_authmail(void);
+void err_authfail(char *,char *,char *,char *,char *,char *,char *);
+void err_authinvalid(char *,char *,char *,char *,char *);
+void err_authabrt(void);
+void err_authreq(char *,char *,char *,char *,char *);
+void err_submission(char *,char *,char *,char *,char *);
+int err_authabort(void);
+int err_authinput(void);
+int err_noauth(void);
+
+void err_wantmail(void);
+void err_mav(char *,char *,char *,char *,char *,char *,char *);
+void err_bmf(char *,char *,char *,char *,char *,char *,char *,char *);
+void err_mfdns(char *,char *,char *,char *,char *,char *,char *);
+
+void err_nogateway(char *,char *,char *,char *,char *,char *,char *);
+void err_brt(char *,char *,char *,char *,char *,char *,char *);
+void err_rcpts(char *,char *,char *,char *,char *,char *,char *);
+void err_recipient(char *,char *,char *,char *,char *,char *,char *);
+
+void straynewline(void);
+void err_notorious(void);
+void err_size(char *,char *,char *,char *,char *,char *,char *);
+void err_data(char *,char *,char *,char *,char *,char *,char *,char *);
+
+int err_postgl(void);
+int err_forkgl(void);
+void postgrey(char *,char *,char *,char *,char *,char *,char *);
+
+#endif
diff --git a/src/include/spf.h b/src/include/spf.h
new file mode 100644
index 0000000..ca20418
--- /dev/null
+++ b/src/include/spf.h
@@ -0,0 +1,111 @@
+#ifndef SPF_H
+#define SPF_H
+
+#include "stralloc.h"
+#include "ipalloc.h"
+
+/* (Internal) Processing codes */
+
+#define SPF_INIT -1
+#define SPF_EXT -2 /* x */
+#define SPF_ME -3
+#define SPF_EXHAUST -4
+#define SPF_LOOP -5
+#define SPF_MULTIRR -6
+#define SPF_LOCAL -7
+#define SPF_ERROR -8
+#define SPF_NOMEM -9
+#define SPF_SYNTAX -10 /* Setup problem */
+
+/* (External) Resulting codes */
+
+#define SPF_OK 0 /* + Pass */
+#define SPF_NONE 1 /* o None */
+#define SPF_UNKNOWN 2 /* u Unknown method */
+#define SPF_NEUTRAL 3 /* ? Neutral */
+#define SPF_SOFTFAIL 4 /* ~ Softfail */
+#define SPF_FAIL 5 /* - Not Permitted */
+#define SPF_DNSSOFT 6 /* d From DNS; not used */
+
+#define LOOKUP_LIMIT 10
+
+/* spfinfo: S=remoteip|O=mailfrom|C=identity/domain|H=helo|M(echanism)=query|D=redirect|I=domain|P=problem|R:result */
+
+#define SPF_DEFEXP "See http://%{d}/why.html?sender=%{s}&ip=%{i}&receiver=%{r}"
+
+extern int flagip6;
+extern stralloc spfmf;
+extern stralloc spfhelo;
+extern stralloc spfinfo;
+extern stralloc spfdomain;
+extern stralloc dnsname;
+extern stralloc spflocalrules;
+extern stralloc spfrecord;
+extern stralloc expdomain;
+extern stralloc spfexplain;
+extern stralloc spfexpmsg;
+
+/* this table and macro came from wget more or less */
+/* and was in turn stolen by me++ from libspf as is :) */
+
+const static unsigned char urlchr_table[256] =
+{
+ 1, 1, 1, 1, 1, 1, 1, 1, /* NUL SOH STX ETX EOT ENQ ACK BEL */
+ 1, 1, 1, 1, 1, 1, 1, 1, /* BS HT LF VT FF CR SO SI */
+ 1, 1, 1, 1, 1, 1, 1, 1, /* DLE DC1 DC2 DC3 DC4 NAK SYN ETB */
+ 1, 1, 1, 1, 1, 1, 1, 1, /* CAN EM SUB ESC FS GS RS US */
+ 1, 0, 1, 1, 0, 1, 1, 0, /* SP ! " # $ % & ' */
+ 0, 0, 0, 1, 0, 0, 0, 1, /* ( ) * + , - . / */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* 0 1 2 3 4 5 6 7 */
+ 0, 0, 1, 1, 1, 1, 1, 1, /* 8 9 : ; < = > ? */
+ 1, 0, 0, 0, 0, 0, 0, 0, /* @ A B C D E F G */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* H I J K L M N O */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* P Q R S T U V W */
+ 0, 0, 0, 1, 1, 1, 1, 0, /* X Y Z [ \ ] ^ _ */
+ 1, 0, 0, 0, 0, 0, 0, 0, /* ` a b c d e f g */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* h i j k l m n o */
+ 0, 0, 0, 0, 0, 0, 0, 0, /* p q r s t u v w */
+ 0, 0, 0, 1, 1, 1, 1, 1, /* x y z { | } ~ DEL */
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+#define WSPACE(x) ((x) == ' ' || (x) == '\t' || (x) == '\r' || (x) == '\n')
+#define NXTOK(b, p, a) do { (b) = (p); \
+ while((p) < (a)->len && !WSPACE((a)->s[(p)])) ++(p); \
+ while((p) < (a)->len && WSPACE((a)->s[(p)])) (a)->s[(p)++] = 0; \
+ } while(0)
+
+/* spfdnsip.c */
+
+int match_ip4(unsigned char [4],int,char [4]);
+int match_ip6(unsigned char [16],int,char [16]);
+int get_prefix(char *);
+int spf_records(stralloc *,stralloc *);
+int spf_include(char *,char *);
+int spf_a(char *,char *);
+int spf_mx(char *,char *);
+int spf_ptr(char *,char *);
+int spf_ip4(char *,char *);
+int spf_ip6(char *,char *);
+int spf_exists(char *,char *);
+
+/* spf.c */
+
+int spf_query(const char *,const char *,const char *,const char *,const int);
+int spf_lookup(stralloc *);
+int spf_mechanism(char *,char *,char *,char *);
+int spf_parse(stralloc *,char *,char *);
+int spf_macros(stralloc *,char *,char *);
+int spf_info(char *,const char *);
+
+#endif
+
diff --git a/src/include/srs2.h b/src/include/srs2.h
new file mode 100644
index 0000000..e993928
--- /dev/null
+++ b/src/include/srs2.h
@@ -0,0 +1,126 @@
+#ifndef SRS2_H
+#define SRS2_H
+#include <stdint.h>
+#include <time.h>
+
+/* Adjusted to s/qmail (feh) */
+
+/* Copyright (c) 2004 Shevek (srs@anarres.org)
+ * All rights reserved.
+ *
+ * This file is a part of libsrs2 from http://www.libsrs2.org/
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, under the terms of either the GNU General Public
+ * License version 2 or the BSD license, at the discretion of the
+ * user. Copies of these licenses have been included in the libsrs2
+ * distribution. See the the file called LICENSE for more
+ * information.
+ */
+
+/* This is ugly, but reasonably safe. */
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+
+#define SRSSEP '='
+#define SRS0TAG "SRS0"
+#define SRS1TAG "SRS1"
+
+/* Error codes */
+
+#define SRS_ERRTYPE_MASK 0xf000
+#define SRS_ERRTYPE_NONE 0x0000
+#define SRS_ERRTYPE_CONFIG 0x1000
+#define SRS_ERRTYPE_INPUT 0x2000
+#define SRS_ERRTYPE_SYNTAX 0x4000
+#define SRS_ERRTYPE_SRS 0x8000
+
+#define SRS_SUCCESS (0)
+#define SRS_ENOTSRSADDRESS (1)
+#define SRS_ENOTREWRITTEN (2)
+
+#define SRS_ENOSECRETS (SRS_ERRTYPE_CONFIG | 1)
+#define SRS_ESEPARATORINVALID (SRS_ERRTYPE_CONFIG | 2)
+
+#define SRS_ENOSENDERATSIGN (SRS_ERRTYPE_INPUT | 1)
+#define SRS_EBUFTOOSMALL (SRS_ERRTYPE_INPUT | 2)
+
+#define SRS_ENOSRS0HOST (SRS_ERRTYPE_SYNTAX | 1)
+#define SRS_ENOSRS0USER (SRS_ERRTYPE_SYNTAX | 2)
+#define SRS_ENOSRS0HASH (SRS_ERRTYPE_SYNTAX | 3)
+#define SRS_ENOSRS0STAMP (SRS_ERRTYPE_SYNTAX | 4)
+#define SRS_ENOSRS1HOST (SRS_ERRTYPE_SYNTAX | 5)
+#define SRS_ENOSRS1USER (SRS_ERRTYPE_SYNTAX | 6)
+#define SRS_ENOSRS1HASH (SRS_ERRTYPE_SYNTAX | 7)
+#define SRS_EBADTIMESTAMPCHAR (SRS_ERRTYPE_SYNTAX | 8)
+#define SRS_EHASHTOOSHORT (SRS_ERRTYPE_SYNTAX | 9)
+
+#define SRS_ETIMESTAMPOUTOFDATE (SRS_ERRTYPE_SRS | 1)
+#define SRS_EHASHINVALID (SRS_ERRTYPE_SRS | 2)
+
+#define SRS_ERROR_TYPE(x) ((x) & SRS_ERRTYPE_MASK)
+
+/* SRS implementation */
+
+#define SRS_IS_SRS_ADDRESS(x) ( \
+ (strncasecmp((x),"SRS",3) == 0) && \
+ (strchr("01", (x)[3]) != NULL) && \
+ (strchr("-+=", (x)[4]) != NULL) \
+)
+
+typedef void *(*srs_malloc_t)(size_t);
+typedef void *(*srs_realloc_t)(void *,size_t);
+typedef void (*srs_free_t)(void *);
+
+typedef int srs_bool;
+
+typedef struct _srs_t {
+ /* Rewriting parameters */
+// stralloc cookies;
+ char **secrets;
+ int numsecrets;
+ char separator;
+
+ /* Security parameters */
+ int maxage; /* Maximum allowed age in seconds */
+ int hashlen;
+ int hashmin;
+
+ /* Behaviour parameters */
+ srs_bool alwaysrewrite; /* Rewrite even into same domain? */
+ srs_bool noforward; /* Never perform forwards rewriting */
+ srs_bool noreverse; /* Never perform reverse rewriting */
+ char **neverrewrite; /* A list of non-rewritten domains */
+} srs_t;
+
+/* Interface */
+int srs_set_malloc(srs_malloc_t m,srs_realloc_t r,srs_free_t f);
+srs_t *srs_new();
+void srs_init(srs_t *);
+void srs_free(srs_t *);
+int srs_forward(srs_t *,char *,int,const char *,const char *);
+int srs_forward_alloc(srs_t *,char **,const char *,const char *);
+int srs_reverse(srs_t *,char *,int, const char *);
+int srs_reverse_alloc(srs_t *,char **,const char *);
+const char *srs_strerror(int);
+int srs_add_secret(srs_t *,const char *);
+const char * srs_get_secret(srs_t *,int);
+ /* You probably shouldn't call these. */
+int srs_timestamp_create(srs_t *,char *,time_t);
+int srs_timestamp_check(srs_t *,const char *);
+
+#define SRS_PARAM_DECLARE(n, t) \
+ int srs_set_ ## n (srs_t *srs, t value); \
+ t srs_get_ ## n (srs_t *srs);
+
+SRS_PARAM_DECLARE(alwaysrewrite,srs_bool)
+SRS_PARAM_DECLARE(separator,char)
+SRS_PARAM_DECLARE(maxage,int)
+SRS_PARAM_DECLARE(hashlen,int)
+SRS_PARAM_DECLARE(hashmin,int)
+SRS_PARAM_DECLARE(noforward,srs_bool)
+SRS_PARAM_DECLARE(noreverse,srs_bool)
+
+#endif /* SRS2_H */
diff --git a/src/include/strset.h b/src/include/strset.h
new file mode 100644
index 0000000..4a5703e
--- /dev/null
+++ b/src/include/strset.h
@@ -0,0 +1,29 @@
+#ifndef STRSET_H
+#define STRSET_H
+
+#include "uint_t.h"
+
+typedef struct strset_list
+{
+ uint32 h;
+ int next;
+}
+strset_list;
+
+typedef struct
+{
+ int mask; /* mask + 1 is power of 2, size of hash table */
+ int n; /* number of entries used in list and x */
+ int a; /* number of entries allocated in list and x */
+ int *first; /* first[h] is front of hash list h */
+ strset_list *p; /* p[i].next is next; p[i].h is hash of x[i] */
+ char **x; /* x[i] is entry i */
+}
+strset;
+
+extern uint32 strset_hash(char *);
+extern int strset_init(strset *);
+extern char *strset_in(strset *,char *);
+extern int strset_add(strset *,char *);
+
+#endif
diff --git a/src/include/tcpto.h b/src/include/tcpto.h
new file mode 100644
index 0000000..ca4f97a
--- /dev/null
+++ b/src/include/tcpto.h
@@ -0,0 +1,25 @@
+#ifndef TCPTO_H
+#define TCPTO_H
+
+#define TCPTO_BUFSIZ 1024
+
+/* persistency structure: record
+struct tcpto {
+ unsigned char af; -- 1 byte -- IPv4: x'2' / IPv6: x'a' (10)
+ unsigned char nul[3]; -- 3 byte
+ unsigned char errorcount -- 1 byte -- if err_timeout || err_conrefused || err_proto (TLS)
+ unsigned char nul[3]; -- 3 byte
+ unsigned long when; -- 8 byte
+ union {
+ struct ip_address ip;
+ struct ip6_address ip6;
+ unsigned char nul[16]; -- 16 byte -- IPv4: filled up with '.' = x'2e'
+ } addr;
+}; total: 32 byte
+*/
+
+int tcpto();
+void tcpto_err();
+void tcpto_clean();
+
+#endif
diff --git a/src/include/tls_errors.h b/src/include/tls_errors.h
new file mode 100644
index 0000000..a61e8fd
--- /dev/null
+++ b/src/include/tls_errors.h
@@ -0,0 +1,42 @@
+#ifndef TLS_CLIENTS_H
+#define TLS_CLIENTS_H
+
+#include "stralloc.h"
+
+extern void temp_tlscon();
+extern void temp_tlspeercert();
+extern void temp_tlspeervalid();
+extern void temp_tlspeerverify();
+
+extern stralloc host;
+extern stralloc remotehost;
+extern stralloc cafile;
+extern stralloc cadir;
+extern stralloc ciphers;
+extern stralloc certfile;
+extern stralloc keyfile;
+extern stralloc keypwd;
+
+void temp_nomem(void);
+void temp_tlsctx(void);
+void temp_tlsca(void);
+void temp_tlscipher(void);
+void temp_tlscert(void);
+void temp_tlscertfp(void);
+void temp_tlsdigest(void);
+void temp_tlshost(void);
+void temp_tlskey(void);
+void temp_tlschk(void);
+void temp_tlsctx(void);
+void temp_tlserr(void);
+void temp_tlsepeercert(void);
+void temp_tlsepeerverify(void);
+void temp_invaliddigest(void);
+void temp_tlsainvalid(void);
+void temp_tlsamissing(void);
+
+void zerodie(void);
+void out(char *);
+void outsafe(stralloc *);
+
+#endif
diff --git a/src/include/tls_remote.h b/src/include/tls_remote.h
new file mode 100644
index 0000000..c3c7933
--- /dev/null
+++ b/src/include/tls_remote.h
@@ -0,0 +1,32 @@
+#ifndef TLS_REMOTE_H
+#define TLS_REMOTE_H
+
+#include <openssl/ssl.h>
+
+/* the version is like this: 0xMNNFFPPS: major minor fix patch status */
+#if OPENSSL_VERSION_NUMBER < 0x00908000L
+# error "Need OpenSSL version at least 0.9.8"
+#endif
+
+extern char *tlsdestinfo;
+extern struct constmap maptlsdestinations;
+extern char *tlsdomaininfo;
+extern struct constmap mapdomaincerts;
+extern stralloc ciphers;
+
+int tls_domaincerts(const stralloc);
+int tls_destination(const stralloc);
+int tlsa_check(const STACK_OF(X509) *,const stralloc,const unsigned long);
+int tls_fingerprint(X509 *,const char *,const int);
+int tls_chainfile(SSL_CTX *,const char *);
+int tls_certkey(SSL_CTX *,const char *,const char *,char *);
+int tls_conn(SSL *,int);
+int tls_setup(int,char *,char *);
+int tls_checkpeer(SSL *,X509 *,const stralloc,const int,const int);
+int tls_checkcrl(SSL *);
+int tls_error(void);
+int tls_exit(SSL *);
+
+int utf8string(unsigned char *,int);
+
+#endif
diff --git a/src/include/tls_start.h b/src/include/tls_start.h
new file mode 100644
index 0000000..d0417f9
--- /dev/null
+++ b/src/include/tls_start.h
@@ -0,0 +1,7 @@
+#ifndef TLS_START_H
+#define TLS_START_H
+
+int starttls_init(void);
+int starttls_info(void);
+
+#endif
diff --git a/src/include/tls_timeoutio.h b/src/include/tls_timeoutio.h
new file mode 100644
index 0000000..175757e
--- /dev/null
+++ b/src/include/tls_timeoutio.h
@@ -0,0 +1,15 @@
+#ifndef TLS_TIMEOUTIO_H
+#define TLS_TIMEOUTIO_H
+
+#include <openssl/ssl.h>
+
+int tls_timeoutconn(int t, int rfd, int wfd, SSL *tls);
+int tls_timeoutaccept(int t, int rfd, int wfd, SSL *tls);
+int tsl_timeoutrehandshake(int t, int rfd, int wfd, SSL *tls);
+
+int tls_timeoutread(int t, int rfd, int wfd, SSL *tls, char *buf, int len);
+int tls_timeoutwrite(int t, int rfd, int wfd, SSL *tls, char *buf, int len);
+
+int tls_timeoutio(int (*fun)(), int t, int rfd, int wfd, SSL *tls, char *buf, int len);
+
+#endif
diff --git a/src/include/token822.h b/src/include/token822.h
new file mode 100644
index 0000000..42c909f
--- /dev/null
+++ b/src/include/token822.h
@@ -0,0 +1,36 @@
+#ifndef TOKEN822_H
+#define TOKEN822_H
+
+#include "genalloc.h"
+
+struct token822 {
+ int type;
+ char *s;
+ int slen;
+};
+
+GEN_ALLOC_typedef(token822_alloc,struct token822,t,len,a)
+
+int token822_parse();
+int token822_addrlist();
+int token822_unquote();
+int token822_unparse();
+void token822_free();
+void token822_reverse();
+int token822_ready();
+int token822_readyplus();
+int token822_append();
+
+#define TOKEN822_ATOM 1
+#define TOKEN822_QUOTE 2
+#define TOKEN822_LITERAL 3
+#define TOKEN822_COMMENT 4
+#define TOKEN822_LEFT 5
+#define TOKEN822_RIGHT 6
+#define TOKEN822_AT 7
+#define TOKEN822_COMMA 8
+#define TOKEN822_SEMI 9
+#define TOKEN822_COLON 10
+#define TOKEN822_DOT 11
+
+#endif
diff --git a/src/include/trigger.h b/src/include/trigger.h
new file mode 100644
index 0000000..d0f4a3e
--- /dev/null
+++ b/src/include/trigger.h
@@ -0,0 +1,9 @@
+#ifndef TRIGGER_H
+#define TRIGGER_H
+
+extern void trigger_set();
+extern void trigger_selprep();
+extern int trigger_pulled();
+extern void triggerpull();
+
+#endif
diff --git a/src/include/triggerpull.h b/src/include/triggerpull.h
new file mode 100644
index 0000000..188f4f2
--- /dev/null
+++ b/src/include/triggerpull.h
@@ -0,0 +1,6 @@
+#ifndef TRIGGERPULL_H
+#define TRIGGERPULL_H
+
+void triggerpull(void);
+
+#endif
diff --git a/src/include/ucspitls.h b/src/include/ucspitls.h
new file mode 100644
index 0000000..40f8a81
--- /dev/null
+++ b/src/include/ucspitls.h
@@ -0,0 +1,45 @@
+#ifndef UCSPITLS_H
+#define UCSPITLS_H
+
+#include <openssl/ssl.h>
+#include <openssl/opensslv.h>
+#include <openssl/ec.h>
+#include "stralloc.h"
+
+#define SSL_NAME_LEN 256
+#define OPENSSL_VERSION_NUMBER 0x101010100L
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#define ssl_client() (ssl_context(SSLv23_client_method()))
+#define ssl_server() (ssl_context(SSLv23_server_method()))
+#else
+#define ssl_client() (ssl_context(TLS_client_method()))
+#define ssl_server() (ssl_context(TLS_server_method()))
+#endif
+
+extern int ssl_errno;
+int ssl_io(SSL *,int,int,unsigned int);
+SSL_CTX *ssl_context(SSL_METHOD *);
+int ssl_timeoutconn(SSL *,unsigned int);
+int ssl_timeoutaccept(SSL *,unsigned int);
+SSL *ssl_new(SSL_CTX *,int);
+int ssl_certkey(SSL_CTX *,const char *,const char *,pem_password_cb *);
+int ssl_ca(SSL_CTX *,const char *,const char *,int);
+int ssl_cca(SSL_CTX *,const char *);
+int ssl_ciphers(SSL_CTX *,const char *);
+int ssl_verify(SSL *,const char *);
+int ssl_params(SSL_CTX *,const char *,int);
+int ssl_server_env(SSL *,stralloc *);
+int ssl_client_env(SSL *,stralloc *);
+char *ssl_error_str(int);
+
+#define ssl_errstr() (SSL_load_error_strings())
+#define ssl_free(ssl) (SSL_free((ssl)))
+#define ssl_close(ssl) (close(SSL_get_fd((ssl))))
+
+#define ssl_pending(ssl) (SSL_pending((ssl)))
+#define ssl_shutdown(ssl) (SSL_shutdown((ssl)))
+#define ssl_shutdown_pending(ssl) (SSL_get_shutdown((ssl)) & SSL_RECEIVED_SHUTDOWN)
+#define ssl_shutdown_sent(ssl) (SSL_get_shutdown((ssl)) & SSL_SENT_SHUTDOWN)
+
+#endif
diff --git a/src/include/wildmat.h b/src/include/wildmat.h
new file mode 100644
index 0000000..501b7e4
--- /dev/null
+++ b/src/include/wildmat.h
@@ -0,0 +1,6 @@
+#ifndef WILDMAT_H
+#define WILDMAT_H
+
+extern int wildmat(char *,char *);
+
+#endif
diff --git a/src/install.c b/src/install.c
new file mode 100644
index 0000000..c738fb7
--- /dev/null
+++ b/src/install.c
@@ -0,0 +1,139 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "buffer.h"
+#include "logmsg.h"
+#include "open.h"
+#include "exit.h"
+#include "fifo.h"
+
+extern void hier();
+
+#define WHO "install"
+
+int fdsourcedir = -1;
+
+void h(char *home,int uid,int gid,int mode)
+{
+ if (mkdir(home,0700) == -1)
+ if (errno != EEXIST)
+ logmsg(WHO,111,FATAL,B("unable to mkdir: ",home));
+ if (chown(home,uid,gid) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chown: ",home));
+ if (chmod(home,mode) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chmod: ",home));
+}
+
+void d(char *home,char *subdir,int uid,int gid,int mode)
+{
+ if (chdir(home) == -1)
+ logmsg(WHO,110,FATAL,B("unable to switch to: ",home));
+ if (mkdir(subdir,0700) == -1)
+ if (errno != EEXIST)
+ logmsg(WHO,111,FATAL,B("unable to mkdir: ",home,"/",subdir));
+ if (chown(subdir,uid,gid) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chown: ",home,"/",subdir));
+ if (chmod(subdir,mode) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chmod: ",home,"/",subdir));
+}
+
+void p(char *home,char *fifo,int uid,int gid,int mode)
+{
+ if (chdir(home) == -1)
+ logmsg(WHO,110,FATAL,B("unable to switch to: ",home));
+ if (fifo_make(fifo,0700) == -1)
+ if (errno != EEXIST)
+ logmsg(WHO,111,FATAL,B("unable to mkfifo: ",home,"/",fifo));
+ if (chown(fifo,uid,gid) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chown: ",home,"/",fifo));
+ if (chmod(fifo,mode) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chmod: ",home,"/",fifo));
+}
+
+char inbuf[BUFFER_INSIZE];
+buffer bi;
+char outbuf[BUFFER_OUTSIZE];
+buffer bo;
+
+void c(char *home,char *subdir,char *file,int uid,int gid,int mode)
+{
+ int fdin;
+ int fdout;
+
+ if (fchdir(fdsourcedir) == -1)
+ logmsg(WHO,110,FATAL,"unable to switch back to source directory: ");
+
+ fdin = open_read(file);
+ if (fdin == -1)
+ logmsg(WHO,111,FATAL,B("unable to read: ",file));
+ buffer_init(&bi,read,fdin,inbuf,sizeof(inbuf));
+
+ if (chdir(home) == -1)
+ logmsg(WHO,110,FATAL,B("unable to switch to: ",home));
+ if (chdir(subdir) == -1)
+ logmsg(WHO,110,FATAL,B("unable to switch to: ",home,"/",subdir));
+
+ fdout = open_trunc(file);
+ if (fdout == -1)
+ logmsg(WHO,111,FATAL,B("unable to write .../",subdir,"/",file));
+ buffer_init(&bo,write,fdout,outbuf,sizeof(outbuf));
+
+ switch (buffer_copy(&bo,&bi)) {
+ case -2:
+ logmsg(WHO,111,FATAL,B("unable to read: ",file));
+ case -3:
+ logmsg(WHO,111,FATAL,B("unable to write .../",subdir,"/",file));
+ }
+
+ close(fdin);
+ if (buffer_flush(&bo) == -1)
+ logmsg(WHO,111,FATAL,B("unable to write .../",subdir,"/",file));
+ if (fsync(fdout) == -1)
+ logmsg(WHO,111,FATAL,B("unable to write .../",subdir,"/",file));
+ if (close(fdout) == -1) /* NFS silliness */
+ logmsg(WHO,111,FATAL,B("unable to write .../",subdir,"/",file));
+
+ if (chown(file,uid,gid) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chown .../",subdir,"/",file));
+ if (chmod(file,mode) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chmod .../",subdir,"/",file));
+}
+
+void z(char *home,char *file,int len,int uid,int gid,int mode)
+{
+ int fdout;
+
+ if (chdir(home) == -1)
+ logmsg(WHO,110,FATAL,B("unable to switch to: ",home));
+
+ fdout = open_trunc(file);
+ if (fdout == -1)
+ logmsg(WHO,111,FATAL,B("unable to write: ",home,"/",file));
+ buffer_init(&bo,write,fdout,outbuf,sizeof(outbuf));
+
+ while (len-- > 0)
+ if (buffer_put(&bo,"",1) == -1)
+ logmsg(WHO,111,FATAL,B("unable to write: ",home,"/",file));
+
+ if (buffer_flush(&bo) == -1)
+ logmsg(WHO,111,FATAL,B("unable to write: ",home,"/",file));
+ if (fsync(fdout) == -1)
+ logmsg(WHO,111,FATAL,B("unable to write: ",home,"/",file));
+ if (close(fdout) == -1) /* NFS silliness */
+ logmsg(WHO,111,FATAL,B("unable to write: ",home,"/",file));
+
+ if (chown(file,uid,gid) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chown: ",home,"/",file));
+ if (chmod(file,mode) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chmod: ",home,"/",file));
+}
+
+int main()
+{
+ fdsourcedir = open_read(".");
+ if (fdsourcedir == -1)
+ logmsg(WHO,110,FATAL,"unable to open current directory: ");
+
+ umask(077);
+ hier();
+ _exit(0);
+}
diff --git a/src/instcheck.c b/src/instcheck.c
new file mode 100644
index 0000000..e47da87
--- /dev/null
+++ b/src/instcheck.c
@@ -0,0 +1,73 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "logmsg.h"
+#include "exit.h"
+#include "hier.h"
+
+extern void hier();
+
+#define WHO "instcheck"
+
+void perm(char *prefix1,char *prefix2,char *prefix3,char *file,int type,int uid,int gid,int mode)
+{
+ struct stat st;
+
+ if (stat(file,&st) == -1) {
+ if (errno == ENOENT)
+ logmsg(WHO,0,WARN,B("file does nost exist:",prefix1,prefix2,prefix3,file));
+ else
+ logmsg(WHO,errno,WARN,B("unable to stat: ../",file));
+ return;
+ }
+
+ if ((uid != -1) && (st.st_uid != uid))
+ logmsg(WHO,0,WARN,B("file has wrong owner: ",prefix1,prefix2,prefix3,file));
+ if ((gid != -1) && (st.st_gid != gid))
+ logmsg(WHO,0,WARN,B("file has wrong group: ",prefix1,prefix2,prefix3,file));
+ if ((st.st_mode & 07777) != mode)
+ logmsg(WHO,0,WARN,B("file has wrong permissions: ",prefix1,prefix2,prefix3,file));
+ if ((st.st_mode & S_IFMT) != type)
+ logmsg(WHO,0,WARN,B("file has wrong type: ",prefix1,prefix2,prefix3,file));
+}
+
+void h(char *home,int uid,int gid,int mode)
+{
+ perm("","","",home,S_IFDIR,uid,gid,mode);
+}
+
+void d(char *home,char *subdir,int uid,int gid,int mode)
+{
+ if (chdir(home) == -1)
+ logmsg(WHO,111,FATAL,B("unable to switch to: ",home));
+ perm("",home,"/",subdir,S_IFDIR,uid,gid,mode);
+}
+
+void p(char *home,char *fifo,int uid,int gid,int mode)
+{
+ if (chdir(home) == -1)
+ logmsg(WHO,111,FATAL,B("unable to switch to: ",home));
+ perm("",home,"/",fifo,S_IFIFO,uid,gid,mode);
+}
+
+void c(char *home,char *subdir,char *file,int uid,int gid,int mode)
+{
+ if (chdir(home) == -1)
+ logmsg(WHO,111,FATAL,B("unable to switch to: ",home));
+ if (chdir(subdir) == -1)
+ logmsg(WHO,111,FATAL,B("unable to switch to: ",home,"/",subdir));
+ perm(".../",subdir,"/",file,S_IFREG,uid,gid,mode);
+}
+
+void z(char *home,char *file,int len,int uid,int gid,int mode)
+{
+ if (chdir(home) == -1)
+ logmsg(WHO,111,FATAL,B("unable to switch to: ",home));
+ perm("",home,"/",file,S_IFREG,uid,gid,mode);
+}
+
+int main()
+{
+ hier();
+ _exit(0);
+}
diff --git a/src/ipalloc.c b/src/ipalloc.c
new file mode 100644
index 0000000..390bd7b
--- /dev/null
+++ b/src/ipalloc.c
@@ -0,0 +1,7 @@
+#include "alloc.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "genalloc.h"
+
+GEN_ALLOC_readyplus(ipalloc,struct ip_mx,ix,len,a,i,n,x,22,ipalloc_readyplus)
+GEN_ALLOC_append(ipalloc,struct ip_mx,ix,len,a,i,n,x,22,ipalloc_readyplus,ipalloc_append)
diff --git a/src/ipme.c b/src/ipme.c
new file mode 100644
index 0000000..ba19722
--- /dev/null
+++ b/src/ipme.c
@@ -0,0 +1,95 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <ifaddrs.h>
+#ifndef SIOCGIFCONF /* whatever works */
+#include <sys/sockio.h>
+#endif
+#include "hassalen.h"
+#include "byte.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "stralloc.h"
+#include "ipme.h"
+
+/** @file ipme.c
+ @brief ipme_is4, ipme_is6, ipme_is46, ipme_init
+ */
+
+static int ipmeok = 0;
+ipalloc ipme = {0};
+
+int ipme_is4(struct ip4_address *ip)
+{
+ int i;
+
+ if (ipme_init() != 1) return -1;
+
+ for (i = 0; i < ipme.len; ++i)
+ if (ipme.ix[i].af == AF_INET && byte_equal(&ipme.ix[i].addr,4,ip))
+ return 1;
+ return 0;
+}
+
+int ipme_is6(struct ip6_address *ip)
+{
+ int i;
+
+ if (ipme_init() != 1) return -1;
+
+ for (i = 0; i < ipme.len; ++i)
+ if (ipme.ix[i].af == AF_INET6 && byte_equal(&ipme.ix[i].addr,16,ip))
+ return 1;
+ return 0;
+}
+
+int ipme_is(struct ip_mx *mxip)
+{
+ switch (mxip->af) {
+ case AF_INET: return ipme_is4(&mxip->addr.ip4);
+ case AF_INET6: return ipme_is6(&mxip->addr.ip6);
+ }
+ return 0;
+}
+
+/* @brief ipme_init uses now getifaddrs() instead of ioctl calls */
+
+int ipme_init()
+{
+ struct ifaddrs *ifap, *ifa;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct ip_mx ix;
+
+ if (ipmeok) return 1;
+ if (!ipalloc_readyplus(&ipme,0)) return 0;
+ ipme.len = 0;
+ ix.pref = 0;
+
+ if (getifaddrs(&ifap)) return 0;
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next)
+ if (ifa->ifa_addr) {
+ if (ifa->ifa_addr->sa_family == AF_INET) {
+ sin = (struct sockaddr_in *) ifa->ifa_addr;
+ byte_copy(&ix.addr.ip4,4,&sin->sin_addr);
+ ix.af = AF_INET;
+ if (!ipalloc_append(&ipme,&ix)) return 0;
+ }
+ if (ifa->ifa_addr->sa_family == AF_INET6) {
+ sin6 = (struct sockaddr_in6 *) ifa->ifa_addr;
+ byte_copy(&ix.addr.ip6,16,&sin6->sin6_addr);
+ ix.af = AF_INET6;
+ if (!ipalloc_append(&ipme,&ix)) return 0;
+ }
+ }
+
+ freeifaddrs(ifap);
+ ipmeok = 1;
+
+ return 1;
+}
diff --git a/src/ipmeprint.c b/src/ipmeprint.c
new file mode 100644
index 0000000..6379219
--- /dev/null
+++ b/src/ipmeprint.c
@@ -0,0 +1,39 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include "buffer.h"
+#include "ip.h"
+#include "ipme.h"
+#include "exit.h"
+#include "fmt.h"
+
+char ipaddr[IPFMT];
+
+int main()
+{
+ int j;
+
+ switch (ipme_init()) {
+ case 0: buffer_putsflush(buffer_2,"out of memory\n"); _exit(111);
+ case -1: buffer_putsflush(buffer_2,"hard error\n"); _exit(100);
+ }
+
+ for (j = 0;j < ipme.len;++j) {
+ switch (ipme.ix[j].af) {
+ case AF_INET:
+ buffer_put(buffer_1,ipaddr,ip4_fmt(ipaddr,&ipme.ix[j].addr.ip4.d));
+ break;
+ case AF_INET6:
+ buffer_put(buffer_1,ipaddr,ip6_fmt(ipaddr,&ipme.ix[j].addr.ip6.d));
+ break;
+ default:
+ buffer_puts(buffer_1,"Unknown address family = ");
+ buffer_put(buffer_1,ipaddr,fmt_ulong(ipaddr,ipme.ix[j].af));
+ }
+ buffer_puts(buffer_1,"\n");
+ }
+
+ buffer_flush(buffer_1);
+ _exit(0);
+}
diff --git a/src/it-analog=d b/src/it-analog=d
new file mode 100644
index 0000000..cbc4c74
--- /dev/null
+++ b/src/it-analog=d
@@ -0,0 +1,25 @@
+columnt
+ddist
+deferrals
+failures
+matchup
+recipients
+rhosts
+rxdelay
+senders
+successes
+suids
+xqp
+xrecipient
+xsender
+zddist
+zdeferrals
+zfailures
+zoverall
+zrecipients
+zrhosts
+zrxdelay
+zsenders
+zsendmail
+zsuccesses
+zsuids
diff --git a/src/it-base=d b/src/it-base=d
new file mode 100644
index 0000000..ee4adf5
--- /dev/null
+++ b/src/it-base=d
@@ -0,0 +1,10 @@
+qmail-clean
+qmail-inject
+qmail-local
+qmail-lspawn
+qmail-send
+qmail-queue
+qmail-rspawn
+qmail-send
+qmail-start
+qmail-todo
diff --git a/src/it-clients=d b/src/it-clients=d
new file mode 100644
index 0000000..eb741bd
--- /dev/null
+++ b/src/it-clients=d
@@ -0,0 +1,4 @@
+mailsubj
+qmail-remote
+qmail-qmqpc
+sendmail
diff --git a/src/it-control=d b/src/it-control=d
new file mode 100644
index 0000000..a88caf6
--- /dev/null
+++ b/src/it-control=d
@@ -0,0 +1,5 @@
+qmail-mfrules
+qmail-showctl
+qmail-badloadertypes
+qmail-badmimetypes
+qmail-recipients
diff --git a/src/it-dkim=d b/src/it-dkim=d
new file mode 100644
index 0000000..1ad1bf5
--- /dev/null
+++ b/src/it-dkim=d
@@ -0,0 +1,2 @@
+qmail-dkim
+qmail-dksign
diff --git a/src/it-dns=d b/src/it-dns=d
new file mode 100644
index 0000000..c1d692c
--- /dev/null
+++ b/src/it-dns=d
@@ -0,0 +1,10 @@
+dnscname
+dnsfq
+dnsip
+dnsmxip
+dnsptr
+dnstlsa
+dnstxt
+hostname
+ipmeprint
+spfquery
diff --git a/src/it-forward=d b/src/it-forward=d
new file mode 100644
index 0000000..135e855
--- /dev/null
+++ b/src/it-forward=d
@@ -0,0 +1,8 @@
+fastforward
+forward
+setforward
+newaliases
+newinclude
+printforward
+printmaillist
+setmaillist
diff --git a/src/it-log=d b/src/it-log=d
new file mode 100644
index 0000000..3fe9f6f
--- /dev/null
+++ b/src/it-log=d
@@ -0,0 +1,4 @@
+qmail-mrtg
+qmail-mrtg-queue
+splogger
+tai64nfrac
diff --git a/src/it-mbox=d b/src/it-mbox=d
new file mode 100644
index 0000000..11e36d6
--- /dev/null
+++ b/src/it-mbox=d
@@ -0,0 +1,9 @@
+condredirect
+bouncesaying
+except
+maildirmake
+maildir2mbox
+maildirwatch
+preline
+qbiff
+qreceipt
diff --git a/src/it-pam=d b/src/it-pam=d
new file mode 100644
index 0000000..27a6a69
--- /dev/null
+++ b/src/it-pam=d
@@ -0,0 +1,4 @@
+qmail-authuser
+qmail-smtpam
+qmail-vmailuser
+qmail-postgrey
diff --git a/src/it-pop=d b/src/it-pop=d
new file mode 100644
index 0000000..2b2f59c
--- /dev/null
+++ b/src/it-pop=d
@@ -0,0 +1,2 @@
+qmail-popup
+qmail-pop3d
diff --git a/src/it-queue=d b/src/it-queue=d
new file mode 100644
index 0000000..d439b37
--- /dev/null
+++ b/src/it-queue=d
@@ -0,0 +1,5 @@
+qmail-qread
+qmail-qstat
+qmail-tcpok
+qmail-tcpto
+qmail-qmaint
diff --git a/src/it-server=d b/src/it-server=d
new file mode 100644
index 0000000..9ecb56d
--- /dev/null
+++ b/src/it-server=d
@@ -0,0 +1,3 @@
+qmail-qmtpd
+qmail-qmqpd
+qmail-smtpd
diff --git a/src/it-setup=d b/src/it-setup=d
new file mode 100644
index 0000000..bcc9a56
--- /dev/null
+++ b/src/it-setup=d
@@ -0,0 +1,4 @@
+config
+config-fast
+install
+instcheck
diff --git a/src/it-srs=d b/src/it-srs=d
new file mode 100644
index 0000000..02db41a
--- /dev/null
+++ b/src/it-srs=d
@@ -0,0 +1,2 @@
+srsforward
+srsreverse
diff --git a/src/it-user=d b/src/it-user=d
new file mode 100644
index 0000000..55d4d04
--- /dev/null
+++ b/src/it-user=d
@@ -0,0 +1,4 @@
+qmail-getpw
+qmail-newu
+qmail-newmrh
+qmail-pw2u
diff --git a/src/it=d b/src/it=d
new file mode 100644
index 0000000..cedc560
--- /dev/null
+++ b/src/it=d
@@ -0,0 +1,16 @@
+it-analog
+it-base
+it-clients
+it-control
+it-dkim
+it-dns
+it-forward
+it-log
+it-mbox
+it-pam
+it-pop
+it-queue
+it-server
+it-srs
+it-setup
+it-user
diff --git a/src/maildir.c b/src/maildir.c
new file mode 100644
index 0000000..8832d75
--- /dev/null
+++ b/src/maildir.c
@@ -0,0 +1,97 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "prioq.h"
+#include "env.h"
+#include "stralloc.h"
+#include "direntry.h"
+#include "datetime.h"
+#include "now.h"
+#include "str.h"
+#include "maildir.h"
+#include "logmsg.h"
+
+#define WHO "maildir"
+
+int maildir_chdir()
+{
+ char *maildir;
+ maildir = env_get("MAILDIR");
+ if (!maildir)
+ logmsg(WHO,111,ERROR,"MAILDIR not set");
+ if (chdir(maildir) == -1)
+ logmsg(WHO,110,FATAL,B("unable to chdir to: ",maildir));
+ return 0;
+}
+
+void maildir_clean(stralloc *tmpname)
+{
+ DIR *dir;
+ direntry *d;
+ datetime_sec time;
+ struct stat st;
+
+ time = now();
+
+ dir = opendir("tmp");
+ if (!dir) return;
+
+ while ((d = readdir(dir))) {
+ if (d->d_name[0] == '.') continue;
+ if (!stralloc_copys(tmpname,"tmp/")) break;
+ if (!stralloc_cats(tmpname,d->d_name)) break;
+ if (!stralloc_0(tmpname)) break;
+ if (stat(tmpname->s,&st) == 0)
+ if (time > st.st_atime + 129600)
+ unlink(tmpname->s);
+ }
+ closedir(dir);
+}
+
+static int append(prioq *pq, stralloc *filenames, char *subdir, datetime_sec time)
+{
+ DIR *dir;
+ direntry *d;
+ struct prioq_elt pe;
+ unsigned int pos;
+ struct stat st;
+
+ dir = opendir(subdir);
+ if (!dir)
+ logmsg(WHO,112,FATAL,B("unable to scan $MAILDIR/:",subdir));
+
+ while ((d = readdir(dir))) {
+ if (d->d_name[0] == '.') continue;
+ pos = filenames->len;
+ if (!stralloc_cats(filenames,subdir)) break;
+ if (!stralloc_cats(filenames,"/")) break;
+ if (!stralloc_cats(filenames,d->d_name)) break;
+ if (!stralloc_0(filenames)) break;
+ if (stat(filenames->s + pos,&st) == 0)
+ if (st.st_mtime < time) { /* don't want to mix up the order */
+ pe.dt = st.st_mtime;
+ pe.id = pos;
+ if (!prioq_insert(pq,&pe)) break;
+ }
+ }
+
+ closedir(dir);
+ if (d) logmsg(WHO,112,FATAL,B("unable to read $MAILDIR/:",subdir));
+ return 0;
+}
+
+int maildir_scan(prioq *pq, stralloc *filenames, int flagnew, int flagcur)
+{
+ struct prioq_elt pe;
+ datetime_sec time;
+
+ if (!stralloc_copys(filenames,"")) return 0;
+ while (prioq_min(pq,&pe))
+ prioq_delmin(pq);
+
+ time = now();
+
+ if (flagnew) if (append(pq,filenames,"new",time) == -1) return -1;
+ if (flagcur) if (append(pq,filenames,"cur",time) == -1) return -1;
+ return 0;
+}
diff --git a/src/maildir2mbox.c b/src/maildir2mbox.c
new file mode 100644
index 0000000..ba187e7
--- /dev/null
+++ b/src/maildir2mbox.c
@@ -0,0 +1,156 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "env.h"
+#include "genalloc.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "logmsg.h"
+#include "open.h"
+#include "lock.h"
+#include "gfrom.h"
+#include "str.h"
+#include "exit.h"
+#include "myctime.h"
+#include "maildir.h"
+#include "prioq.h"
+
+char *mbox;
+char *mboxtmp;
+
+int rename(const char *,const char *); // stdio.h
+
+stralloc filenames = {0};
+prioq pq = {0};
+prioq pq2 = {0};
+
+stralloc line = {0};
+
+stralloc ufline = {0};
+
+char inbuf[BUFFER_INSIZE];
+char outbuf[BUFFER_OUTSIZE];
+
+#define WHO "maildir2mbox"
+
+void die_nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+
+int main()
+{
+ buffer bi;
+ buffer bo;
+ struct prioq_elt pe;
+ int fdoldmbox;
+ int fdnewmbox;
+ int fd;
+ int match;
+ int fdlock;
+
+ umask(077);
+
+ mbox = env_get("MAIL");
+ if (!mbox) logmsg(WHO,111,FATAL,"MAIL not set");
+ mboxtmp = env_get("MAILTMP");
+ if (!mboxtmp) logmsg(WHO,111,FATAL,"MAILTMP not set");
+
+ if (maildir_chdir() == -1)
+ logmsg(WHO,110,FATAL,"Can't changet maildir");
+ maildir_clean(&filenames);
+ if (maildir_scan(&pq,&filenames,1,1) == -1)
+ logmsg(WHO,112,FATAL,"Can't read maidir");
+
+ if (!prioq_min(&pq,&pe)) _exit(0); /* nothing new */
+
+ fdlock = open_append(mbox);
+ if (fdlock == -1)
+ logmsg(WHO,111,FATAL,B("unable to lock: ",mbox));
+ if (lock_ex(fdlock) == -1)
+ logmsg(WHO,111,FATAL,B("unable to lock: ",mbox));
+
+ fdoldmbox = open_read(mbox);
+ if (fdoldmbox == -1)
+ logmsg(WHO,112,FATAL,B("unable to read: ",mbox));
+
+ fdnewmbox = open_trunc(mboxtmp);
+ if (fdnewmbox == -1)
+ logmsg(WHO,112,FATAL,B("unable to create: ",mboxtmp));
+
+ buffer_init(&bi,read,fdoldmbox,inbuf,sizeof(inbuf));
+ buffer_init(&bo,write,fdnewmbox,outbuf,sizeof(outbuf));
+
+ switch (buffer_copy(&bo,&bi)) {
+ case -2: logmsg(WHO,112,FATAL,B("unable to read: ",mbox));
+ case -3: logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ }
+
+ while (prioq_min(&pq,&pe)) {
+ prioq_delmin(&pq);
+ if (!prioq_insert(&pq2,&pe)) die_nomem();
+
+ fd = open_read(filenames.s + pe.id);
+ if (fd == -1)
+ logmsg(WHO,112,FATAL,B("unable to read: $MAILDIR/",filenames.s + pe.id));
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ if (getln(&bi,&line,&match,'\n') != 0)
+ logmsg(WHO,112,FATAL,B("unable to read: $MAILDIR/",filenames.s + pe.id));
+
+ if (!stralloc_copys(&ufline,"From XXX ")) die_nomem();
+ if (match)
+ if (stralloc_starts(&line,"Return-Path: <")) {
+ if (line.s[14] == '>') {
+ if (!stralloc_copys(&ufline,"From MAILER-DAEMON ")) die_nomem();
+ } else {
+ int i;
+ if (!stralloc_ready(&ufline,line.len)) die_nomem();
+ if (!stralloc_copys(&ufline,"From ")) die_nomem();
+
+ for (i = 14;i < line.len - 2;++i)
+ if ((line.s[i] == ' ') || (line.s[i] == '\t'))
+ ufline.s[ufline.len++] = '-';
+ else
+ ufline.s[ufline.len++] = line.s[i];
+ if (!stralloc_cats(&ufline," ")) die_nomem();
+ }
+ }
+ if (!stralloc_cats(&ufline,myctime(pe.dt))) die_nomem();
+ if (buffer_put(&bo,ufline.s,ufline.len) == -1)
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+
+ while (match && line.len) {
+ if (gfrom(line.s,line.len))
+ if (buffer_puts(&bo,">") == -1)
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ if (buffer_put(&bo,line.s,line.len) == -1)
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ if (!match) {
+ if (buffer_puts(&bo,"\n") == -1)
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ break;
+ }
+ if (getln(&bi,&line,&match,'\n') != 0)
+ logmsg(WHO,112,FATAL,B("unable to read: $MAILDIR/",filenames.s + pe.id));
+ }
+ if (buffer_puts(&bo,"\n"))
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+
+ close(fd);
+ }
+
+ if (buffer_flush(&bo) == -1)
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ if (fsync(fdnewmbox) == -1)
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ if (close(fdnewmbox) == -1) /* NFS dorks */
+ logmsg(WHO,112,FATAL,B("unable to write to: ",mboxtmp));
+ if (rename(mboxtmp,mbox) == -1)
+ logmsg(WHO,112,FATAL,B("unable to move ",mboxtmp," to: ",mbox));
+
+ while (prioq_min(&pq2,&pe)) {
+ prioq_delmin(&pq2);
+ if (unlink(filenames.s + pe.id) == -1)
+ logmsg(WHO,0,WARN,B("$MAILDIR/",filenames.s + pe.id," will be delivered twice; unable to unlink"));
+ }
+
+ _exit(0);
+}
diff --git a/src/maildirmake.c b/src/maildirmake.c
new file mode 100644
index 0000000..47edc44
--- /dev/null
+++ b/src/maildirmake.c
@@ -0,0 +1,24 @@
+#include <sys/stat.h>
+#include <unistd.h>
+#include "logmsg.h"
+#include "exit.h"
+
+#define WHO "maildirmake"
+
+int main(int argc, char **argv)
+{
+ umask(077);
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"maildirmake name");
+ if (mkdir(argv[1],0700) == -1)
+ logmsg(WHO,111,FATAL,B("unable to mkdir: ",argv[1]));
+ if (chdir(argv[1]) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",argv[1]));
+ if (mkdir("tmp",0700) == -1)
+ logmsg(WHO,111,FATAL,B("unable to mkdir: ",argv[1],"/tmp"));
+ if (mkdir("new",0700) == -1)
+ logmsg(WHO,111,FATAL,B("unable to mkdir: ",argv[1],"/new"));
+ if (mkdir("cur",0700) == -1)
+ logmsg(WHO,111,FATAL,B("unable to mkdir: ",argv[1],"/cur"));
+ _exit(0);
+}
diff --git a/src/maildirwatch.c b/src/maildirwatch.c
new file mode 100644
index 0000000..aea5cbb
--- /dev/null
+++ b/src/maildirwatch.c
@@ -0,0 +1,123 @@
+#include <unistd.h>
+#include "getln.h"
+#include "buffer.h"
+#include "prioq.h"
+#include "stralloc.h"
+#include "str.h"
+#include "exit.h"
+#include "hfield.h"
+#include "logmsg.h"
+#include "open.h"
+#include "headerbody.h"
+#include "maildir.h"
+
+#define WHO "maildirwatch"
+
+void die_nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+
+stralloc recipient = {0};
+stralloc sender = {0};
+stralloc fromline = {0};
+stralloc text = {0};
+
+void addtext(char *s, int n)
+{
+ if (!stralloc_catb(&text,s,n)) die_nomem();
+ if (text.len > 158) text.len = 158;
+}
+
+void dobody(stralloc *h) { addtext(h->s,h->len); }
+
+void doheader(stralloc *h)
+{
+ int i;
+ switch (hfield_known(h->s,h->len)) {
+ case H_SUBJECT:
+ i = hfield_skipname(h->s,h->len);
+ addtext(h->s + i,h->len - i);
+ break;
+ case H_DELIVEREDTO:
+ i = hfield_skipname(h->s,h->len);
+ if (i < h->len)
+ if (!stralloc_copyb(&recipient,h->s + i,h->len - i - 1)) die_nomem();
+ break;
+ case H_RETURNPATH:
+ i = hfield_skipname(h->s,h->len);
+ if (i < h->len)
+ if (!stralloc_copyb(&sender,h->s + i,h->len - i - 1)) die_nomem();
+ break;
+ case H_FROM:
+ if (!stralloc_copyb(&fromline,h->s,h->len - 1)) die_nomem();
+ break;
+ }
+}
+void finishheader() { ; }
+
+stralloc filenames = {0};
+prioq pq = {0};
+
+char inbuf[BUFFER_INSIZE];
+buffer bi;
+
+int main()
+{
+ struct prioq_elt pe;
+ int fd;
+ int i;
+
+ if (maildir_chdir() == -1)
+ logmsg(WHO,111,FATAL,"Can't change to maildir");
+
+ for (;;) {
+ maildir_clean(&filenames);
+ if (maildir_scan(&pq,&filenames,1,0) == -1)
+ logmsg(WHO,111,FATAL,"Can't read maildir");
+
+ buffer_putsflush(buffer_1,"\033[;H\033[;J");
+
+ while (prioq_min(&pq,&pe)) {
+ prioq_delmin(&pq);
+
+ fd = open_read(filenames.s + pe.id);
+ if (fd == -1) continue;
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ if (!stralloc_copys(&sender,"?")) die_nomem();
+ if (!stralloc_copys(&recipient,"?")) die_nomem();
+ if (!stralloc_copys(&fromline,"")) die_nomem();
+ if (!stralloc_copys(&text,"")) die_nomem();
+ if (headerbody(&bi,doheader,finishheader,dobody) == -1)
+ logmsg(WHO,111,FATAL,"trouble reading new message");
+
+ for (i = 0; i < fromline.len; ++i)
+ if ((fromline.s[i] < 32) || (fromline.s[i] > 126))
+ fromline.s[i] = '/';
+ for (i = 0; i < sender.len; ++i)
+ if ((sender.s[i] < 32) || (sender.s[i] > 126))
+ sender.s[i] = '?';
+ for (i = 0; i < recipient.len; ++i)
+ if ((recipient.s[i] < 32) || (recipient.s[i] > 126))
+ recipient.s[i] = '?';
+ for (i = 0; i < text.len; ++i)
+ if ((text.s[i] < 32) || (text.s[i] > 126))
+ text.s[i] = '/';
+ buffer_puts(buffer_1,"FROM ");
+ buffer_put(buffer_1,sender.s,sender.len);
+ buffer_puts(buffer_1," TO <");
+ buffer_put(buffer_1,recipient.s,recipient.len);
+ buffer_puts(buffer_1,">\n");
+ if (fromline.len) {
+ buffer_puts(buffer_1,"\033[1m");
+ buffer_put(buffer_1,fromline.s,fromline.len);
+ buffer_puts(buffer_1,"\033[0m\n");
+ }
+ buffer_put(buffer_1,text.s,text.len);
+ buffer_puts(buffer_1,"\n\n");
+
+ close(fd);
+ }
+
+ buffer_flush(buffer_1);
+ sleep(30);
+ }
+}
diff --git a/src/mailsubj.sh b/src/mailsubj.sh
new file mode 100755
index 0000000..f7a40ce
--- /dev/null
+++ b/src/mailsubj.sh
@@ -0,0 +1,7 @@
+subject="$1"
+shift
+( echo Subject: "$subject"
+ echo To: ${1+"$@"}
+ echo ''
+ cat
+) | HOME/bin/qmail-inject
diff --git a/src/make-compile.sh b/src/make-compile.sh
new file mode 100755
index 0000000..a1eb501
--- /dev/null
+++ b/src/make-compile.sh
@@ -0,0 +1 @@
+echo exec "$CC" -c '${1+"$@"}'
diff --git a/src/make-load.sh b/src/make-load.sh
new file mode 100755
index 0000000..de07d2e
--- /dev/null
+++ b/src/make-load.sh
@@ -0,0 +1,2 @@
+echo 'main="$1"; shift'
+echo exec "$LD" '-o "$main" "$main".o ${1+"$@"}'
diff --git a/src/make-makelib.sh b/src/make-makelib.sh
new file mode 100755
index 0000000..d6b7c8c
--- /dev/null
+++ b/src/make-makelib.sh
@@ -0,0 +1,16 @@
+echo 'main="$1"; shift'
+echo 'rm -f "$main"'
+echo 'ar cr "$main" ${1+"$@"}'
+
+case "$1" in
+sunos-5.*) ;;
+unix_sv*) ;;
+irix64-*) ;;
+irix-*) ;;
+dgux-*) ;;
+hp-ux-*) ;;
+sco*) ;;
+*)
+ echo 'ranlib "$main"'
+ ;;
+esac
diff --git a/src/matchup.c b/src/matchup.c
new file mode 100644
index 0000000..047ea65
--- /dev/null
+++ b/src/matchup.c
@@ -0,0 +1,489 @@
+#include <unistd.h>
+#include "genalloc.h"
+#include "alloc.h"
+#include "stralloc.h"
+#include "logmsg.h"
+#include "getln.h"
+#include "buffer.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "str.h"
+#include "fmt.h"
+#include "scan.h"
+#include "case.h"
+
+#define WHO "matchup"
+
+void nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+void die_read() { logmsg(WHO,110,ERROR,"unable to read input: "); }
+void die_write() { logmsg(WHO,110,ERROR,"unable to write output: "); }
+void die_write5() { logmsg(WHO,111,FATAL,"unable to write fd 5: "); }
+
+void out(char *buf,int len)
+{
+ if (buffer_put(buffer_1,buf,len) == -1) die_write();
+}
+void outs(char *buf)
+{
+ if (buffer_puts(buffer_1,buf) == -1) die_write();
+}
+
+char buf5[512];
+buffer bo5 = BUFFER_INIT(write,5,buf5,sizeof(buf5));
+
+void out5(char *buf,int len)
+{
+ if (buffer_put(&bo5,buf,len) == -1)
+ die_write5();
+}
+void outs5(char *buf)
+{
+ if (buffer_puts(&bo5,buf) == -1)
+ die_write5();
+}
+
+GEN_ALLOC_typedef(ulongalloc,unsigned long,u,len,a)
+GEN_ALLOC_ready(ulongalloc,unsigned long,u,len,a,i,n,x,30,ulongalloc_ready)
+GEN_ALLOC_readyplus(ulongalloc,unsigned long,u,len,a,i,n,x,30,ulongalloc_readyplus)
+
+char strnum[FMT_ULONG];
+
+stralloc pool = {0};
+unsigned int poolbytes = 0;
+
+int nummsg = 0;
+ulongalloc msg = {0};
+ulongalloc bytes = {0};
+ulongalloc qp = {0};
+ulongalloc uid = {0};
+ulongalloc numk = {0};
+ulongalloc numd = {0};
+ulongalloc numz = {0};
+ulongalloc sender = {0};
+ulongalloc birth = {0};
+
+int msg_find(unsigned long m)
+{
+ int i;
+ for (i = 0; i < nummsg; ++i)
+ if (msg.u[i] == m) return i;
+ return -1;
+}
+
+int msg_add(unsigned long m)
+{
+ int i;
+ for (i = 0; i < nummsg; ++i)
+ if (msg.u[i] == m) return i;
+ i = nummsg++;
+ if (!ulongalloc_ready(&msg,nummsg)) nomem();
+ if (!ulongalloc_ready(&bytes,nummsg)) nomem();
+ if (!ulongalloc_ready(&qp,nummsg)) nomem();
+ if (!ulongalloc_ready(&uid,nummsg)) nomem();
+ if (!ulongalloc_ready(&numk,nummsg)) nomem();
+ if (!ulongalloc_ready(&numd,nummsg)) nomem();
+ if (!ulongalloc_ready(&numz,nummsg)) nomem();
+ if (!ulongalloc_ready(&sender,nummsg)) nomem();
+ if (!ulongalloc_ready(&birth,nummsg)) nomem();
+ msg.u[i] = m;
+ return i;
+}
+
+void msg_kill(int i)
+{
+ poolbytes -= str_len(pool.s + sender.u[i]) + 1;
+ poolbytes -= str_len(pool.s + birth.u[i]) + 1;
+
+ --nummsg;
+ msg.u[i] = msg.u[nummsg];
+ bytes.u[i] = bytes.u[nummsg];
+ qp.u[i] = qp.u[nummsg];
+ uid.u[i] = uid.u[nummsg];
+ numk.u[i] = numk.u[nummsg];
+ numd.u[i] = numd.u[nummsg];
+ numz.u[i] = numz.u[nummsg];
+ sender.u[i] = sender.u[nummsg];
+ birth.u[i] = birth.u[nummsg];
+}
+
+int numdel = 0;
+ulongalloc del = {0};
+ulongalloc dmsg = {0};
+ulongalloc dchan = {0};
+ulongalloc drecip = {0};
+ulongalloc dstart = {0};
+
+int del_find(unsigned long d)
+{
+ int i;
+ for (i = 0; i < numdel; ++i)
+ if (del.u[i] == d) return i;
+ return -1;
+}
+
+int del_add(unsigned long d)
+{
+ int i;
+ for (i = 0; i < numdel; ++i)
+ if (del.u[i] == d) return i;
+ i = numdel++;
+ if (!ulongalloc_ready(&del,numdel)) nomem();
+ if (!ulongalloc_ready(&dmsg,numdel)) nomem();
+ if (!ulongalloc_ready(&dchan,numdel)) nomem();
+ if (!ulongalloc_ready(&drecip,numdel)) nomem();
+ if (!ulongalloc_ready(&dstart,numdel)) nomem();
+ del.u[i] = d;
+ return i;
+}
+
+void del_kill(int i)
+{
+ poolbytes -= str_len(pool.s + dchan.u[i]) + 1;
+ poolbytes -= str_len(pool.s + drecip.u[i]) + 1;
+ poolbytes -= str_len(pool.s + dstart.u[i]) + 1;
+ --numdel;
+ del.u[i] = del.u[numdel];
+ dmsg.u[i] = dmsg.u[numdel];
+ dchan.u[i] = dchan.u[numdel];
+ drecip.u[i] = drecip.u[numdel];
+ dstart.u[i] = dstart.u[numdel];
+}
+
+stralloc pool2 = {0};
+
+void garbage()
+{
+ int i;
+ char *x;
+
+ if (pool.len - poolbytes < poolbytes + 4096) return;
+
+ if (!stralloc_copys(&pool2,"")) nomem();
+
+ for (i = 0; i < nummsg; ++i) {
+ x = pool.s + birth.u[i];
+ birth.u[i] = pool2.len;
+ if (!stralloc_cats(&pool2,x)) nomem();
+ if (!stralloc_0(&pool2)) nomem();
+ x = pool.s + sender.u[i];
+ sender.u[i] = pool2.len;
+ if (!stralloc_cats(&pool2,x)) nomem();
+ if (!stralloc_0(&pool2)) nomem();
+ }
+
+ for (i = 0; i < numdel; ++i) {
+ x = pool.s + dstart.u[i];
+ dstart.u[i] = pool2.len;
+ if (!stralloc_cats(&pool2,x)) nomem();
+ if (!stralloc_0(&pool2)) nomem();
+ x = pool.s + dchan.u[i];
+ dchan.u[i] = pool2.len;
+ if (!stralloc_cats(&pool2,x)) nomem();
+ if (!stralloc_0(&pool2)) nomem();
+ x = pool.s + drecip.u[i];
+ drecip.u[i] = pool2.len;
+ if (!stralloc_cats(&pool2,x)) nomem();
+ if (!stralloc_0(&pool2)) nomem();
+ }
+
+ if (!stralloc_copy(&pool,&pool2)) nomem();
+
+ poolbytes = pool.len; /* redundant, but doesn't hurt */
+}
+
+stralloc line = {0};
+int match;
+
+#define FIELDS 20
+int field[FIELDS];
+
+void clear()
+{
+ while (numdel > 0)
+ del_kill(0);
+ garbage();
+}
+
+void starting()
+{
+ unsigned long d;
+ unsigned long m;
+ int dpos;
+
+ scan_ulong(line.s + field[3],&d);
+ scan_ulong(line.s + field[5],&m);
+
+ dpos = del_add(d);
+
+ dmsg.u[dpos] = m;
+
+ dstart.u[dpos] = pool.len;
+ if (!stralloc_cats(&pool,line.s + field[0])) nomem();
+ if (!stralloc_0(&pool)) nomem();
+
+ dchan.u[dpos] = pool.len;
+ if (!stralloc_cats(&pool,line.s + field[7])) nomem();
+ if (!stralloc_0(&pool)) nomem();
+
+ drecip.u[dpos] = pool.len;
+ if (!stralloc_cats(&pool,line.s + field[8])) nomem();
+ if (!stralloc_0(&pool)) nomem();
+ case_lowers(pool.s + drecip.u[dpos]);
+
+ poolbytes += pool.len - dstart.u[dpos];
+}
+
+void delivery()
+{
+ unsigned long d;
+ unsigned long m;
+ int dpos;
+ int mpos;
+ char *result = "?";
+ char *reason = "";
+
+ scan_ulong(line.s + field[2],&d);
+
+ dpos = del_find(d);
+ if (dpos == -1) return;
+
+ m = dmsg.u[dpos];
+ mpos = msg_find(m);
+
+ if (str_start(line.s + field[3],"succ")) {
+ if (mpos != -1) ++numk.u[mpos];
+ result = "d k ";
+ reason = line.s + field[4];
+ }
+ else if (str_start(line.s + field[3],"fail")) {
+ if (mpos != -1) ++numd.u[mpos];
+ result = "d d ";
+ reason = line.s + field[4];
+ }
+ else if (str_start(line.s + field[3],"defer")) {
+ if (mpos != -1) ++numz.u[mpos];
+ result = "d z ";
+ reason = line.s + field[4];
+ }
+ else if (str_start(line.s + field[3],"report")) {
+ if (mpos != -1) ++numz.u[mpos];
+ result = "d z ";
+ reason = "report_mangled";
+ }
+
+ outs(result);
+
+ if (mpos != -1) {
+ outs(pool.s + birth.u[mpos]);
+ outs(" "); outs(pool.s + dstart.u[dpos]);
+ outs(" "); outs(line.s + field[0]);
+ outs(" "); out(strnum,fmt_ulong(strnum,bytes.u[mpos]));
+ outs(" "); outs(pool.s + sender.u[mpos]);
+ outs(" "); outs(pool.s + dchan.u[dpos]);
+ outs("."); outs(pool.s + drecip.u[dpos]);
+ outs(" "); out(strnum,fmt_ulong(strnum,qp.u[mpos]));
+ outs(" "); out(strnum,fmt_ulong(strnum,uid.u[mpos]));
+ outs(" "); outs(reason);
+ } else {
+ outs(pool.s + dstart.u[dpos]);
+ outs(" "); outs(pool.s + dstart.u[dpos]);
+ outs(" "); outs(line.s + field[0]);
+ outs(" 0 ? "); outs(pool.s + dchan.u[dpos]);
+ outs("."); outs(pool.s + drecip.u[dpos]);
+ outs(" ? ? "); outs(reason);
+ }
+
+ outs("\n");
+
+ del_kill(dpos);
+ garbage();
+}
+
+void newmsg()
+{
+ unsigned long m;
+ int mpos;
+
+ scan_ulong(line.s + field[3],&m);
+ mpos = msg_find(m);
+ if (mpos == -1) return;
+ msg_kill(mpos);
+ garbage();
+}
+
+void endmsg()
+{
+ unsigned long m;
+ int mpos;
+
+ scan_ulong(line.s + field[3],&m);
+ mpos = msg_find(m);
+ if (mpos == -1) return;
+
+ outs("m "); outs(pool.s + birth.u[mpos]);
+ outs(" "); outs(line.s + field[0]);
+ outs(" "); out(strnum,fmt_ulong(strnum,bytes.u[mpos]));
+ outs(" "); out(strnum,fmt_ulong(strnum,numk.u[mpos]));
+ outs(" "); out(strnum,fmt_ulong(strnum,numd.u[mpos]));
+ outs(" "); out(strnum,fmt_ulong(strnum,numz.u[mpos]));
+ outs(" "); outs(pool.s + sender.u[mpos]);
+ outs(" "); out(strnum,fmt_ulong(strnum,qp.u[mpos]));
+ outs(" "); out(strnum,fmt_ulong(strnum,uid.u[mpos]));
+ outs("\n");
+
+ msg_kill(mpos);
+ garbage();
+}
+
+void info()
+{
+ unsigned long m;
+ int mpos;
+
+ scan_ulong(line.s + field[3],&m);
+ mpos = msg_add(m);
+
+ scan_ulong(line.s + field[5],&bytes.u[mpos]);
+ scan_ulong(line.s + field[9],&qp.u[mpos]);
+ scan_ulong(line.s + field[11],&uid.u[mpos]);
+
+ numk.u[mpos] = 0;
+ numd.u[mpos] = 0;
+ numz.u[mpos] = 0;
+
+ birth.u[mpos] = pool.len;
+ if (!stralloc_cats(&pool,line.s + field[0])) nomem();
+ if (!stralloc_0(&pool)) nomem();
+
+ sender.u[mpos] = pool.len;
+ if (!stralloc_cats(&pool,line.s + field[7])) nomem();
+ if (!stralloc_0(&pool)) nomem();
+ case_lowers(pool.s + sender.u[mpos]);
+
+ poolbytes += pool.len - birth.u[mpos];
+}
+
+void extra()
+{
+ unsigned long m;
+ int mpos;
+
+ scan_ulong(line.s + field[2],&m);
+ mpos = msg_find(m);
+ if (mpos == -1) return;
+
+ scan_ulong(line.s + field[3],&numk.u[mpos]);
+ scan_ulong(line.s + field[4],&numz.u[mpos]);
+ scan_ulong(line.s + field[5],&numd.u[mpos]);
+}
+
+void pending()
+{
+ int i;
+
+ for (i = 0; i < nummsg; ++i) {
+ outs5(pool.s + birth.u[i]);
+ outs5(" info msg ");
+ out5(strnum,fmt_ulong(strnum,msg.u[i]));
+ outs5(": bytes ");
+ out5(strnum,fmt_ulong(strnum,bytes.u[i]));
+ outs5(" from ");
+ outs5(pool.s + sender.u[i]);
+ outs5(" qp ");
+ out5(strnum,fmt_ulong(strnum,qp.u[i]));
+ outs5(" uid ");
+ out5(strnum,fmt_ulong(strnum,uid.u[i]));
+ outs5("\n");
+ outs5(pool.s + birth.u[i]);
+ outs5(" extra ");
+ out5(strnum,fmt_ulong(strnum,msg.u[i]));
+ outs5(" ");
+ out5(strnum,fmt_ulong(strnum,numk.u[i]));
+ outs5(" ");
+ out5(strnum,fmt_ulong(strnum,numz.u[i]));
+ outs5(" ");
+ out5(strnum,fmt_ulong(strnum,numd.u[i]));
+ outs5("\n");
+ }
+
+ for (i = 0; i < numdel; ++i) {
+ outs5(pool.s + dstart.u[i]);
+ outs5(" starting delivery ");
+ out5(strnum,fmt_ulong(strnum,del.u[i]));
+ outs5(": msg ");
+ out5(strnum,fmt_ulong(strnum,dmsg.u[i]));
+ outs5(" to ");
+ outs5(pool.s + dchan.u[i]);
+ outs5(" ");
+ outs5(pool.s + drecip.u[i]);
+ outs5("\n");
+ }
+
+ out5(line.s,line.len);
+ if (buffer_flush(&bo5) == -1) die_write5();
+}
+
+stralloc outline = {0};
+
+int main()
+{
+ int i;
+ int j;
+ char ch;
+
+ if (!stralloc_copys(&pool,"")) nomem();
+
+ if (!ulongalloc_ready(&msg,1)) nomem();
+ if (!ulongalloc_ready(&bytes,1)) nomem();
+ if (!ulongalloc_ready(&qp,1)) nomem();
+ if (!ulongalloc_ready(&uid,1)) nomem();
+ if (!ulongalloc_ready(&numk,1)) nomem();
+ if (!ulongalloc_ready(&numd,1)) nomem();
+ if (!ulongalloc_ready(&numz,1)) nomem();
+ if (!ulongalloc_ready(&del,1)) nomem();
+ if (!ulongalloc_ready(&dmsg,1)) nomem();
+
+ for (;;) {
+ if (getln(buffer_0,&line,&match,'\n') == -1) die_read();
+ if (!match) break;
+
+ if (!stralloc_copy(&outline,&line)) nomem();
+
+ for (i = 0; i < line.len; ++i) {
+ ch = line.s[i];
+ if ((ch == '\n') || (ch == ' ') || (ch == '\t')) line.s[i] = 0;
+ }
+ j = 0;
+ for (i = 0; i < FIELDS; ++i) {
+ while (j < line.len) if (line.s[j]) break; else ++j;
+ field[i] = j;
+ while (j < line.len) if (!line.s[j]) break; else ++j;
+ }
+ if (!stralloc_0(&line)) nomem();
+
+ if (str_equal(line.s + field[1],"status:")) ;
+ else if (str_equal(line.s + field[1],"starting")) starting();
+ else if (str_equal(line.s + field[1],"delivery")) delivery();
+ else if (str_equal(line.s + field[1],"new")) newmsg();
+ else if (str_equal(line.s + field[1],"end")) endmsg();
+ else if (str_equal(line.s + field[1],"info")) info();
+ else if (str_equal(line.s + field[1],"extra")) extra();
+ else if (str_equal(line.s + field[1],"running")) clear();
+ else if (str_equal(line.s + field[1],"exiting")) clear();
+ else if (str_equal(line.s + field[1],"number")) ;
+ else if (str_equal(line.s + field[1],"local")) ;
+ else if (str_equal(line.s + field[1],"remote")) ;
+ else if (str_equal(line.s + field[1],"warning:")) out(outline.s,outline.len);
+ else if (str_equal(line.s + field[1],"alert:")) out(outline.s,outline.len);
+ else {
+ outs("? ");
+ out(outline.s,outline.len);
+ }
+ }
+
+ if (buffer_flush(buffer_1) == -1) die_write();
+
+ pending();
+
+ _exit(0);
+}
diff --git a/src/md5c.c b/src/md5c.c
new file mode 100644
index 0000000..3143159
--- /dev/null
+++ b/src/md5c.c
@@ -0,0 +1,327 @@
+/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ */
+
+/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ rights reserved.
+
+ License to copy and use this software is granted provided that it
+ is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ Algorithm" in all material mentioning or referencing this software
+ or this function.
+
+ License is also granted to make and use derivative works provided
+ that such works are identified as "derived from the RSA Data
+ Security, Inc. MD5 Message-Digest Algorithm" in all material
+ mentioning or referencing the derived work.
+
+ RSA Data Security, Inc. makes no representations concerning either
+ the merchantability of this software or the suitability of this
+ software for any particular purpose. It is provided "as is"
+ without express or implied warranty of any kind.
+
+ These notices must be retained in any copies of any part of this
+ documentation and/or software.
+ */
+
+#include "global.h"
+#include "md5.h"
+#include <stdint.h>
+
+/* Constants for MD5Transform routine. */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+
+static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64]));
+static void Encode PROTO_LIST
+ ((unsigned char *, UINT4 *, unsigned int));
+static void Decode PROTO_LIST
+ ((UINT4 *, unsigned char *, unsigned int));
+static void MD5_memcpy PROTO_LIST ((POINTER, POINTER, unsigned int));
+static void MD5_memset PROTO_LIST ((POINTER, int, unsigned int));
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions. */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits. */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ Rotation is separate from addition to prevent recomputation. */
+
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context. */
+void MD5Init (context)
+MD5_CTX *context; /* context */
+{
+ context->count[0] = context->count[1] = 0;
+
+ /* Load magic initialization constants.
+ */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/* MD5 block update operation. Continues an MD5 message-digest
+ operation, processing another message block, and updating the
+ context. */
+
+void MD5Update (context, input, inputLen)
+MD5_CTX *context; /* context */
+unsigned char *input; /* input block */
+unsigned int inputLen; /* length of input block */
+{
+ unsigned int i, index, partLen;
+
+ /* Compute number of bytes mod 64 */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((UINT4)inputLen << 3))
+ < ((UINT4)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((UINT4)inputLen >> 29);
+
+ partLen = 64 - index;
+
+ /* Transform as many times as possible. */
+ if (inputLen >= partLen) {
+ MD5_memcpy
+ ((POINTER)&context->buffer[index], (POINTER)input, partLen);
+ MD5Transform (context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform (context->state, &input[i]);
+
+ index = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ MD5_memcpy
+ ((POINTER)&context->buffer[index], (POINTER)&input[i],
+ inputLen-i);
+}
+
+/* MD5 finalization. Ends an MD5 message-digest operation, writing the
+ the message digest and zeroizing the context. */
+
+void MD5Final (digest, context)
+unsigned char digest[16]; /* message digest */
+MD5_CTX *context; /* context */
+{
+ unsigned char bits[8];
+ unsigned int index, padLen;
+
+ /* Save number of bits */
+ Encode (bits, context->count, 8);
+
+ /* Pad out to 56 mod 64. */
+ index = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (index < 56) ? (56 - index) : (120 - index);
+ MD5Update (context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update (context, bits, 8);
+
+ /* Store state in digest */
+ Encode (digest, context->state, 16);
+
+ /* Zeroize sensitive information. */
+ MD5_memset ((POINTER)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block. */
+
+static void MD5Transform (state, block)
+UINT4 state[4];
+unsigned char block[64];
+{
+ UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode (x, block, 64);
+
+ /* Round 1 */
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information. */
+ MD5_memset ((POINTER)x, 0, sizeof (x));
+}
+
+/* Encodes input (UINT4) into output (unsigned char).
+ Assumes len is a multiple of 4. */
+
+static void Encode (output, input, len)
+unsigned char *output;
+UINT4 *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4) {
+ output[j] = (unsigned char)(input[i] & 0xff);
+ output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
+ output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
+ output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
+ }
+}
+
+/* Decodes input (unsigned char) into output (UINT4).
+ Assumes len is a multiple of 4. */
+
+static void Decode (output, input, len)
+UINT4 *output;
+unsigned char *input;
+unsigned int len;
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < len; i++, j += 4)
+ output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) |
+ (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24);
+}
+
+/* Note: Replace "for loop" with standard memcpy if possible. */
+
+static void MD5_memcpy (output, input, len)
+POINTER output;
+POINTER input;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ output[i] = input[i];
+}
+
+/* Note: Replace "for loop" with standard memset if possible. */
+
+static void MD5_memset (output, value, len)
+POINTER output;
+int value;
+unsigned int len;
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ ((char *)output)[i] = (char)value;
+}
diff --git a/src/mfrules.c b/src/mfrules.c
new file mode 100644
index 0000000..9c6240f
--- /dev/null
+++ b/src/mfrules.c
@@ -0,0 +1,146 @@
+#include "alloc.h"
+#include "stralloc.h"
+#include "open.h"
+#include "cdbread.h"
+#include "case.h"
+#include "mfrules.h"
+#include "str.h"
+#include "byte.h"
+#include "close.h"
+
+/* return -9: problems reading cdb */
+/* return -1: key matches; data not */
+/* return 0: no key */
+/* return 1: key matches without data */
+/* return 2: key and data match */
+
+stralloc key = {0};
+
+static struct cdb cdb;
+
+static int mffind(char *mf)
+{
+ char *x;
+ char *data;
+ unsigned int datalen;
+ int plus = 0;
+ int dlen;
+ int len;
+ int mflen;
+ int delta;
+
+ switch (cdb_find(&cdb,key.s,key.len)) {
+ case -1: return -9;
+ case 0: return 0;
+ }
+
+ datalen = cdb_datalen(&cdb);
+ data = alloc(datalen);
+ if (!data) return -9;
+ if (!datalen) return 1;
+ mflen = str_len(mf);
+
+ if (cdb_read(&cdb,data,datalen,cdb_datapos(&cdb)) == -1) {
+ alloc_free(data);
+ return -9;
+ }
+
+ x = data; dlen = datalen - 1; /* trailing separator */
+
+ while (dlen > 0) {
+ plus = byte_rchr(data,dlen,'+');
+ x = data + plus + 1;
+ len = dlen - plus;
+ delta = (mflen > len) ? mflen - len : 0;
+ if (!byte_diff(x,len,mf + delta)) { alloc_free(data); return 2; }
+ dlen = plus - 1;
+ }
+
+ alloc_free(data);
+ return -1;
+}
+
+int mfsearch(char *ip,char *host,char *info,char *mf)
+{
+ int r;
+
+ if (info) {
+ if (!stralloc_copys(&key,info)) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+
+ if (!stralloc_cats(&key,"@")) return -9;
+ if (!stralloc_cats(&key,ip)) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+
+ if (host) {
+ if (!stralloc_copys(&key,info)) return -9;
+ if (!stralloc_cats(&key,"@=")) return -9;
+ if (!stralloc_cats(&key,host)) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+ }
+ }
+
+ if (!stralloc_copys(&key,ip)) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+
+ if (host) {
+ if (!stralloc_copys(&key,"=")) return -9;
+ if (!stralloc_cats(&key,host)) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+ }
+
+ if (!stralloc_copys(&key,ip)) return -9; /* IPv6 */
+ while (key.len > 0) {
+ if (ip[key.len - 1] == ':') {
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+ }
+ --key.len;
+ }
+
+ if (!stralloc_copys(&key,ip)) return -9; /* IPv4 */
+ while (key.len > 0) {
+ if (ip[key.len - 1] == '.') {
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+ }
+ --key.len;
+ }
+
+ if (host) {
+ while (*host) {
+ if (*host == '.') {
+ if (!stralloc_copys(&key,"=")) return -9;
+ if (!stralloc_cats(&key,host)) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+ }
+ ++host;
+ }
+ if (!stralloc_copys(&key,"=")) return -9;
+ r = mffind(mf);
+ if (r < -1 || r > 0) return r;
+ }
+
+ key.len = 0;
+/* return mffind(mf); */
+ return -1;
+}
+
+int mfrules(int fd,char *ip,char *host,char *info,char *mf)
+{
+ int r;
+
+ cdb_init(&cdb,fd);
+ case_lowers(mf);
+ r = mfsearch(ip,host,info,mf);
+ cdb_free(&cdb);
+ close(fd);
+
+ return r;
+}
diff --git a/src/migrate.sh b/src/migrate.sh
new file mode 100755
index 0000000..c08f7e6
--- /dev/null
+++ b/src/migrate.sh
@@ -0,0 +1,6 @@
+#/bin/sh
+
+for FILE in *.c
+do
+ cat $FILE | sed -f sedfile > $FILE.new && mv $FILE.new $FILE
+done
diff --git a/src/myctime.c b/src/myctime.c
new file mode 100644
index 0000000..6f554c9
--- /dev/null
+++ b/src/myctime.c
@@ -0,0 +1,36 @@
+#include "datetime.h"
+#include "fmt.h"
+#include "myctime.h"
+
+static char *daytab[7] = {
+"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
+};
+static char *montab[12] = {
+"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+static char result[30];
+
+char *myctime(datetime_sec t)
+{
+ struct datetime dt;
+ unsigned int len;
+ datetime_tai(&dt,t);
+ len = 0;
+ len += fmt_str(result + len,daytab[dt.wday]);
+ result[len++] = ' ';
+ len += fmt_str(result + len,montab[dt.mon]);
+ result[len++] = ' ';
+ len += fmt_uint0(result + len,dt.mday,2);
+ result[len++] = ' ';
+ len += fmt_uint0(result + len,dt.hour,2);
+ result[len++] = ':';
+ len += fmt_uint0(result + len,dt.min,2);
+ result[len++] = ':';
+ len += fmt_uint0(result + len,dt.sec,2);
+ result[len++] = ' ';
+ len += fmt_uint(result + len,1900 + dt.year);
+ result[len++] = '\n';
+ result[len++] = 0;
+ return result;
+}
diff --git a/src/newaliases.c b/src/newaliases.c
new file mode 100644
index 0000000..98aa095
--- /dev/null
+++ b/src/newaliases.c
@@ -0,0 +1,326 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "buffer.h"
+#include "logmsg.h"
+#include "genalloc.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "open.h"
+#include "token822.h"
+#include "control.h"
+#include "auto_qmail.h"
+#include "case.h"
+#include "cdbmake.h"
+#include "byte.h"
+
+#define WHO "newaliases"
+
+int rename(const char *,const char *); // stdio.h
+
+void nomem()
+{
+ logmsg(WHO,111,FATAL,"out of memory");
+}
+void nulbyte()
+{
+ logmsg(WHO,100,FATAL,"NUL bytes are not permitted");
+}
+void longaddress()
+{
+ logmsg(WHO,100,FATAL,"addresses over 800 bytes are not permitted");
+}
+void writeerr()
+{
+ logmsg(WHO,111,FATAL,"unable to write to /etc/aliases.tmp");
+}
+void readerr()
+{
+ logmsg(WHO,111,FATAL,"unable to read /etc/aliases");
+}
+void die_control()
+{
+ logmsg(WHO,111,FATAL,"unable to read controls");
+}
+
+stralloc me = {0};
+stralloc defaulthost = {0};
+stralloc defaultdomain = {0};
+stralloc plusdomain = {0};
+
+void readcontrols()
+{
+ int r;
+ int fddir;
+
+ fddir = open_read(".");
+ if (fddir == -1)
+ logmsg(WHO,111,FATAL,"unable to open current directory");
+
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+
+ r = control_readline(&me,"control/me");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copys(&me,"me")) nomem();
+
+ r = control_readline(&defaultdomain,"control/defaultdomain");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&defaultdomain,&me)) nomem();
+
+ r = control_readline(&defaulthost,"control/defaulthost");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&defaulthost,&me)) nomem();
+
+ r = control_readline(&plusdomain,"control/plusdomain");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&plusdomain,&me)) nomem();
+
+ if (fchdir(fddir) == -1)
+ logmsg(WHO,111,FATAL,"unable to set current directory");
+}
+
+stralloc target = {0};
+stralloc fulltarget = {0};
+stralloc instr = {0};
+
+stralloc cbuf = {0};
+token822_alloc toks = {0};
+token822_alloc tokaddr = {0};
+stralloc address = {0};
+
+void gotincl()
+{
+ token822_reverse(&tokaddr);
+ if (token822_unquote(&address,&tokaddr) != 1) nomem();
+ tokaddr.len = 0;
+
+ if (!address.len)
+ logmsg(WHO,111,FATAL,"empty :include: filenames not permitted");
+ if (byte_chr(address.s,address.len,'\0') < address.len)
+ logmsg(WHO,111,FATAL,"NUL not permitted in :include: filenames");
+
+ if ((address.s[0] != '.') && (address.s[0] != '/'))
+ if (!stralloc_cats(&instr,"./")) nomem();
+ if (!stralloc_cat(&instr,&address)) nomem();
+ if (!stralloc_cats(&instr,".bin")) nomem();
+ if (!stralloc_0(&instr)) nomem();
+}
+
+void gotaddr()
+{
+ int i;
+ int j;
+ int flaghasat;
+
+ token822_reverse(&tokaddr);
+ if (token822_unquote(&address,&tokaddr) != 1) nomem();
+
+ if (!address.len)
+ logmsg(WHO,111,FATAL,"empty recipient addresses not permitted");
+
+ flaghasat = 0;
+ for (i = 0; i < tokaddr.len; ++i)
+ if (tokaddr.t[i].type == TOKEN822_AT)
+ flaghasat = 1;
+
+ tokaddr.len = 0;
+
+ if (!address.len) return;
+
+ if (!flaghasat)
+ if (address.s[0] == '/') {
+ if (!stralloc_0(&address)) nomem();
+ logmsg(WHO,111,FATAL,B("file delivery for ",address.s," not supported"));
+ }
+ if (!flaghasat)
+ if (address.s[0] == '|') {
+ if (byte_chr(address.s,address.len,'\0') < address.len)
+ logmsg(WHO,111,FATAL,"NUL not permitted in program names");
+ if (!stralloc_cats(&instr,"!")) nomem();
+ if (!stralloc_catb(&instr,address.s + 1,address.len - 1)) nomem();
+ if (!stralloc_0(&instr)) nomem();
+ return;
+ }
+
+
+ if (target.len) {
+ if (!stralloc_cats(&instr,"&")) nomem();
+ if (!stralloc_cat(&instr,&fulltarget)) nomem();
+ if (!stralloc_0(&instr)) nomem();
+ }
+
+ if (!flaghasat)
+ if (!stralloc_cats(&address,"@")) nomem();
+
+ if (!stralloc_copy(&target,&address)) nomem();
+ if (!stralloc_copy(&fulltarget,&address)) nomem();
+
+ if (fulltarget.s[fulltarget.len - 1] == '@')
+ if (!stralloc_cat(&fulltarget,&defaulthost)) nomem();
+ if (fulltarget.s[fulltarget.len - 1] == '+') {
+ fulltarget.s[fulltarget.len - 1] = '.';
+ if (!stralloc_cat(&fulltarget,&plusdomain)) nomem();
+ }
+
+ j = 0;
+ for (i = 0;i < fulltarget.len;++i) if (fulltarget.s[i] == '@') j = i;
+ for (i = j;i < fulltarget.len;++i) if (fulltarget.s[i] == '.') break;
+ if (i == fulltarget.len) {
+ if (!stralloc_cats(&fulltarget,".")) nomem();
+ if (!stralloc_cat(&fulltarget,&defaultdomain)) nomem();
+ }
+
+ if (fulltarget.len > 800) longaddress();
+ if (byte_chr(fulltarget.s,fulltarget.len,'\0') < fulltarget.len)
+ logmsg(WHO,111,FATAL,"NUL not permitted in recipient addresses");
+}
+
+stralloc line = {0};
+stralloc newline = {0};
+int match;
+
+void parseerr()
+{
+ if (!stralloc_0(&line)) nomem();
+ logmsg(WHO,111,FATAL,B("unable to parse this line: ",line.s));
+}
+
+void parseline()
+{
+ int wordok;
+ struct token822 *t;
+ struct token822 *beginning;
+
+ switch (token822_parse(&toks,&line,&cbuf)) {
+ case -1: nomem();
+ case 0: parseerr();
+ }
+
+ beginning = toks.t;
+ t = toks.t + toks.len;
+ wordok = 1;
+
+ if (!token822_readyplus(&tokaddr,1)) nomem();
+ tokaddr.len = 0;
+
+ while (t > beginning)
+ switch ((--t)->type) {
+ case TOKEN822_SEMI:
+ break; /*XXX*/
+ case TOKEN822_COLON:
+ if (t >= beginning + 2)
+ if (t[-2].type == TOKEN822_COLON)
+ if (t[-1].type == TOKEN822_ATOM)
+ if (t[-1].slen == 7)
+ if (!byte_diff(t[-1].s,7,"include")) {
+ gotincl();
+ t -= 2;
+ }
+ break; /*XXX*/
+ case TOKEN822_RIGHT:
+ if (tokaddr.len) gotaddr();
+ while ((t > beginning) && (t[-1].type != TOKEN822_LEFT))
+ if (!token822_append(&tokaddr,--t)) nomem();
+ gotaddr();
+ if (t <= beginning) parseerr();
+ --t;
+ while ((t > beginning) && ((t[-1].type == TOKEN822_COMMENT) || (t[-1].type == TOKEN822_ATOM) || (t[-1].type == TOKEN822_QUOTE) || (t[-1].type == TOKEN822_AT) || (t[-1].type == TOKEN822_DOT)))
+ --t;
+ wordok = 0;
+ continue;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+ if (!wordok) if (tokaddr.len) gotaddr();
+ wordok = 0;
+ if (!token822_append(&tokaddr,t)) nomem();
+ continue;
+ case TOKEN822_COMMENT:
+ /* comment is lexically a space; shouldn't affect wordok */
+ break;
+ case TOKEN822_COMMA:
+ if (tokaddr.len) gotaddr();
+ wordok = 1;
+ break;
+ default:
+ wordok = 1;
+ if (!token822_append(&tokaddr,t)) nomem();
+ continue;
+ }
+ if (tokaddr.len) gotaddr();
+}
+
+char inbuf[1024];
+buffer bi;
+struct cdb_make cdb;
+stralloc key = {0};
+
+void doit()
+{
+ if (!instr.len) {
+ if (target.len) parseerr();
+ return;
+ }
+
+ if (!target.len) parseerr();
+
+ if (stralloc_starts(&target,"owner-")) {
+ if (!stralloc_copys(&key,"?")) nomem();
+ if (!stralloc_catb(&key,target.s + 6,target.len - 6)) nomem();
+ case_lowerb(key.s,key.len);
+ if (cdb_make_add(&cdb,key.s,key.len,fulltarget.s,fulltarget.len) == -1) writeerr();
+ }
+
+ if (!stralloc_copys(&key,":")) nomem();
+ if (!stralloc_cat(&key,&target)) nomem();
+ case_lowerb(key.s,key.len);
+ if (cdb_make_add(&cdb,key.s,key.len,instr.s,instr.len) == -1) writeerr();
+}
+
+int main()
+{
+ int fd;
+
+ umask(033);
+ readcontrols();
+
+ fd = open_read("/etc/aliases");
+ if (fd == -1) readerr();
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ fd = open_trunc("/etc/aliases.tmp");
+ if (fd == -1) logmsg(WHO,111,FATAL,"unable to create /etc/aliases.tmp");
+ if (cdb_make_start(&cdb,fd) == -1) writeerr();
+
+ if (!stralloc_copys(&line,"")) nomem();
+
+ for (;;) {
+ if (getln(&bi,&newline,&match,'\n') != 0) readerr();
+
+ if (match && (newline.s[0] == '\n')) continue;
+
+ if (match && ((newline.s[0] == ' ') || (newline.s[0] == '\t'))) {
+ if (!stralloc_cat(&line,&newline)) nomem();
+ continue;
+ }
+
+ if (line.len)
+ if (line.s[0] != '#') {
+ if (!stralloc_copys(&target,"")) nomem();
+ if (!stralloc_copys(&fulltarget,"")) nomem();
+ if (!stralloc_copys(&instr,"")) nomem();
+ parseline();
+ doit();
+ }
+
+ if (!match) break;
+ if (!stralloc_copy(&line,&newline)) nomem();
+ }
+
+ if (cdb_make_finish(&cdb) == -1) writeerr();
+ if (fsync(fd) == -1) writeerr();
+ if (close(fd) == -1) writeerr(); /* NFS stupidity */
+
+ if (rename("/etc/aliases.tmp","/etc/aliases.cdb") == -1)
+ logmsg(WHO,111,FATAL,"unable to move /etc/aliases.tmp to /etc/aliases.cdb");
+
+ _exit(0);
+}
diff --git a/src/newfield.c b/src/newfield.c
new file mode 100644
index 0000000..6d69ec6
--- /dev/null
+++ b/src/newfield.c
@@ -0,0 +1,59 @@
+#include <unistd.h>
+#include "fmt.h"
+#include "datetime.h"
+#include "stralloc.h"
+#include "date822fmt.h"
+#include "newfield.h"
+
+/* "Date: 26 Sep 1995 04:46:53 -0000\n" */
+stralloc newfield_date = {0};
+/* "Message-ID: <19950926044653.12345.qmail@silverton.berkeley.edu>\n" */
+stralloc newfield_msgid = {0};
+
+static unsigned int datefmt(char *s, datetime_sec when)
+{
+ unsigned int i;
+ unsigned int len;
+ struct datetime dt;
+ datetime_tai(&dt,when);
+ len = 0;
+ i = fmt_str(s,"Date: "); len += i; if (s) s += i;
+ i = date822fmt(s,&dt); len += i; if (s) s += i;
+ return len;
+}
+
+static unsigned int msgidfmt(char *s, char *idhost, int idhostlen, datetime_sec when)
+{
+ unsigned int i;
+ unsigned int len;
+ struct datetime dt;
+ datetime_tai(&dt,when);
+ len = 0;
+ i = fmt_str(s,"Message-ID: <"); len += i; if (s) s += i;
+ i = fmt_uint(s,dt.year + 1900); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.mon + 1,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.mday,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.hour,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.min,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.sec,2); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_uint(s,getpid()); len += i; if (s) s += i;
+ i = fmt_str(s,".qmail@"); len += i; if (s) s += i;
+ i = fmt_strn(s,idhost,idhostlen); len += i; if (s) s += i;
+ i = fmt_str(s,">\n"); len += i; if (s) s += i;
+ return len;
+}
+
+int newfield_datemake(datetime_sec when)
+{
+ if (!stralloc_ready(&newfield_date,datefmt(FMT_LEN,when))) return 0;
+ newfield_date.len = datefmt(newfield_date.s,when);
+ return 1;
+}
+
+int newfield_msgidmake(char *idhost, int idhostlen, datetime_sec when)
+{
+ if (!stralloc_ready(&newfield_msgid,msgidfmt(FMT_LEN,idhost,idhostlen,when))) return 0;
+ newfield_msgid.len = msgidfmt(newfield_msgid.s,idhost,idhostlen,when);
+ return 1;
+}
diff --git a/src/newinclude.c b/src/newinclude.c
new file mode 100644
index 0000000..746b6a4
--- /dev/null
+++ b/src/newinclude.c
@@ -0,0 +1,317 @@
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h> // rename
+#include "buffer.h"
+#include "logmsg.h"
+#include "genalloc.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "open.h"
+#include "token822.h"
+#include "control.h"
+#include "auto_qmail.h"
+#include "byte.h"
+#include "env.h"
+
+#define WHO "newinclude"
+
+int rename(const char *,const char *); // stdio.h
+
+void nomem()
+{
+ logmsg(WHO,111,FATAL,"out of memory");
+}
+void usage()
+{
+ logmsg(WHO,100,USAGE,"newinclude list");
+}
+
+char *fnlist;
+char listbuf[1024];
+buffer bi;
+
+stralloc bin = {0};
+#define fnbin bin.s
+stralloc tmp = {0};
+#define fntmp tmp.s
+char tmpbuf[1024];
+buffer bt;
+
+
+void readerr()
+{
+ logmsg(WHO,111,FATAL,B("unable to read: ",fnlist));
+}
+void writeerr()
+{
+ logmsg(WHO,111,FATAL,B("unable to write to ",fntmp));
+}
+
+static void out(char *s,int len)
+{
+ if (buffer_put(&bt,s,len) == -1) writeerr();
+}
+
+void doincl(buf,len)
+char *buf;
+int len;
+{
+ if (!len)
+ logmsg(WHO,111,FATAL,"empty :include: filenames not permitted");
+ if (byte_chr(buf,len,'\n') != len)
+ logmsg(WHO,111,FATAL,"newlines not permitted in :include: filenames");
+ if (byte_chr(buf,len,'\0') != len)
+ logmsg(WHO,111,FATAL,"NUL not permitted in :include: filenames");
+ if ((buf[0] != '.') && (buf[0] != '/'))
+ out("./",2);
+ out(buf,len);
+ out("",1);
+}
+
+void dorecip(buf,len)
+char *buf;
+int len;
+{
+ if (!len)
+ logmsg(WHO,111,FATAL,"empty recipient addresses not permitted");
+ if (byte_chr(buf,len,'\n') != len)
+ logmsg(WHO,111,FATAL,"newlines not permitted in recipient addresses");
+ if (byte_chr(buf,len,'\0') != len)
+ logmsg(WHO,111,FATAL,"NUL not permitted in recipient addresses");
+ if (len > 800)
+ logmsg(WHO,111,FATAL,"addresses must be under 800 bytes");
+ if ((buf[len - 1] == ' ') || (buf[len - 1] == '\t'))
+ logmsg(WHO,111,FATAL,"spaces and tabs not permitted at ends of addresses");
+ out("&",1);
+ out(buf,len);
+ out("",1);
+}
+
+
+void die_control()
+{
+ logmsg(WHO,111,FATAL,"unable to read controls");
+}
+
+stralloc me = {0};
+stralloc defaulthost = {0};
+stralloc defaultdomain = {0};
+stralloc plusdomain = {0};
+
+void readcontrols()
+{
+ int r;
+ int fddir;
+ char *x;
+
+ fddir = open_read(".");
+ if (fddir == -1)
+ logmsg(WHO,111,FATAL,"unable to open current directory");
+
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+
+ r = control_readline(&me,"control/me");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copys(&me,"me")) nomem();
+
+ r = control_readline(&defaultdomain,"control/defaultdomain");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&defaultdomain,&me)) nomem();
+ x = env_get("QMAILDEFAULTDOMAIN");
+ if (x) if (!stralloc_copys(&defaultdomain,x)) nomem();
+
+ r = control_readline(&defaulthost,"control/defaulthost");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&defaulthost,&me)) nomem();
+ x = env_get("QMAILDEFAULTHOST");
+ if (x) if (!stralloc_copys(&defaulthost,x)) nomem();
+
+ r = control_readline(&plusdomain,"control/plusdomain");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&plusdomain,&me)) nomem();
+ x = env_get("QMAILPLUSDOMAIN");
+ if (x) if (!stralloc_copys(&plusdomain,x)) nomem();
+
+ if (fchdir(fddir) == -1)
+ logmsg(WHO,111,FATAL,"unable to set current directory");
+}
+
+stralloc cbuf = {0};
+token822_alloc toks = {0};
+token822_alloc tokaddr = {0};
+stralloc address = {0};
+
+void gotincl()
+{
+ token822_reverse(&tokaddr);
+ if (token822_unquote(&address,&tokaddr) != 1) nomem();
+ tokaddr.len = 0;
+ doincl(address.s,address.len);
+}
+
+void gotaddr()
+{
+ int i;
+ int j;
+ int flaghasat;
+
+ token822_reverse(&tokaddr);
+ if (token822_unquote(&address,&tokaddr) != 1) nomem();
+
+ flaghasat = 0;
+ for (i = 0;i < tokaddr.len;++i)
+ if (tokaddr.t[i].type == TOKEN822_AT)
+ flaghasat = 1;
+
+ tokaddr.len = 0;
+
+ if (!address.len) return;
+
+ if (!flaghasat)
+ if (address.s[0] == '/') {
+ if (!stralloc_0(&address)) nomem();
+ logmsg(WHO,111,FATAL,B("file delivery for ",address.s," not supported"));
+ }
+ if (!flaghasat)
+ if (address.s[0] == '|') {
+ if (!stralloc_0(&address)) nomem();
+ logmsg(WHO,111,FATAL,B("program delivery for ",address.s," not supported"));
+ }
+
+ if (!flaghasat) {
+ if (!stralloc_cats(&address,"@")) nomem();
+ if (!stralloc_cat(&address,&defaulthost)) nomem();
+ }
+ if (address.s[address.len - 1] == '+') {
+ address.s[address.len - 1] = '.';
+ if (!stralloc_cat(&address,&plusdomain)) nomem();
+ }
+ j = 0;
+ for (i = 0;i < address.len;++i) if (address.s[i] == '@') j = i;
+ for (i = j;i < address.len;++i) if (address.s[i] == '.') break;
+ if (i == address.len) {
+ if (!stralloc_cats(&address,".")) nomem();
+ if (!stralloc_cat(&address,&defaultdomain)) nomem();
+ }
+
+ dorecip(address.s,address.len);
+}
+
+
+stralloc line = {0};
+int match;
+
+void parseerr()
+{
+ if (!stralloc_0(&line)) nomem();
+ logmsg(WHO,111,FATAL,B("unable to parse this line: ",line.s));
+}
+
+void parseline()
+{
+ int wordok;
+ struct token822 *t;
+ struct token822 *beginning;
+
+ switch (token822_parse(&toks,&line,&cbuf)) {
+ case -1: nomem();
+ case 0: parseerr();
+ }
+
+ beginning = toks.t;
+ t = toks.t + toks.len;
+ wordok = 1;
+
+ if (!token822_readyplus(&tokaddr,1)) nomem();
+ tokaddr.len = 0;
+
+ while (t > beginning)
+ switch ((--t)->type) {
+ case TOKEN822_SEMI:
+ break; /*XXX*/
+ case TOKEN822_COLON:
+ if (t >= beginning + 2)
+ if (t[-2].type == TOKEN822_COLON)
+ if (t[-1].type == TOKEN822_ATOM)
+ if (t[-1].slen == 7)
+ if (!byte_diff(t[-1].s,7,"include")) {
+ gotincl();
+ t -= 2;
+ }
+ break; /*XXX*/
+ case TOKEN822_RIGHT:
+ if (tokaddr.len) gotaddr();
+ while ((t > beginning) && (t[-1].type != TOKEN822_LEFT))
+ if (!token822_append(&tokaddr,--t)) nomem();
+ gotaddr();
+ if (t <= beginning) parseerr();
+ --t;
+ while ((t > beginning) && ((t[-1].type == TOKEN822_COMMENT) || (t[-1].type == TOKEN822_ATOM) || (t[-1].type == TOKEN822_QUOTE) || (t[-1].type == TOKEN822_AT) || (t[-1].type == TOKEN822_DOT)))
+ --t;
+ wordok = 0;
+ continue;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+ if (!wordok) if (tokaddr.len) gotaddr();
+ wordok = 0;
+ if (!token822_append(&tokaddr,t)) nomem();
+ continue;
+ case TOKEN822_COMMENT:
+ /* comment is lexically a space; shouldn't affect wordok */
+ break;
+ case TOKEN822_COMMA:
+ if (tokaddr.len) gotaddr();
+ wordok = 1;
+ break;
+ default:
+ wordok = 1;
+ if (!token822_append(&tokaddr,t)) nomem();
+ continue;
+ }
+ if (tokaddr.len) gotaddr();
+}
+
+
+int main(argc,argv)
+int argc;
+char **argv;
+{
+ int fd;
+
+ umask(033);
+ readcontrols();
+
+ fnlist = argv[1]; if (!fnlist) usage();
+
+ if (!stralloc_copys(&bin,fnlist)) nomem();
+ if (!stralloc_cats(&bin,".bin")) nomem();
+ if (!stralloc_0(&bin)) nomem();
+
+ if (!stralloc_copys(&tmp,fnlist)) nomem();
+ if (!stralloc_cats(&tmp,".tmp")) nomem();
+ if (!stralloc_0(&tmp)) nomem();
+
+ fd = open_read(fnlist);
+ if (fd == -1) readerr();
+ buffer_init(&bi,read,fd,listbuf,sizeof(listbuf));
+
+ fd = open_trunc(fntmp);
+ if (fd == -1) writeerr();
+ buffer_init(&bt,write,fd,tmpbuf,sizeof(tmpbuf));
+
+ for (;;) {
+ if (getln(&bi,&line,&match,'\n') == -1) readerr();
+ if (!line.len) break;
+ if (line.s[0] != '#') parseline();
+ if (!match) break;
+ }
+
+ if (buffer_flush(&bt) == -1) writeerr();
+ if (fsync(fd) == -1) writeerr();
+ if (close(fd) == -1) writeerr(); /* NFS stupidity */
+
+ if (rename(fntmp,fnbin) == -1)
+ logmsg(WHO,111,FATAL,B("unable to move ",fntmp," to: ",fnbin));
+
+ _exit(0);
+}
diff --git a/src/now.c b/src/now.c
new file mode 100644
index 0000000..5ce4d90
--- /dev/null
+++ b/src/now.c
@@ -0,0 +1,8 @@
+#include <time.h>
+#include "datetime.h"
+#include "now.h"
+
+datetime_sec now()
+{
+ return time((long *) 0);
+}
diff --git a/src/predate.c b/src/predate.c
new file mode 100644
index 0000000..f6007b3
--- /dev/null
+++ b/src/predate.c
@@ -0,0 +1,113 @@
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include "datetime.h"
+#include "wait.h"
+#include "fd.h"
+#include "fmt.h"
+#include "logmsg.h"
+#include "buffer.h"
+#include "exit.h"
+#include "sig.h"
+
+#define WHO "predate"
+
+static char *montab[12] = {
+"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+char num[FMT_ULONG];
+char outbuf[1024];
+
+int main(int argc, char **argv)
+{
+ time_t now;
+ struct tm *tm;
+ struct datetime dt;
+ datetime_sec utc;
+ datetime_sec local;
+ int minutes;
+ int pi[2];
+ buffer bo;
+ int wstat;
+ int pid;
+
+ sig_pipeignore();
+
+ if (!argv[1])
+ logmsg(WHO,100,USAGE,"predate child");
+
+ if (pipe(pi) == -1)
+ logmsg(WHO,111,FATAL,"unable to create pipe");
+
+ switch (pid = fork()) {
+ case -1:
+ logmsg(WHO,111,FATAL,"unable to fork");
+ case 0:
+ close(pi[1]);
+ if (fd_move(0,pi[0]) == -1)
+ logmsg(WHO,111,FATAL,"unable to set up fds");
+ sig_pipedefault();
+ execvp(argv[1],argv + 1);
+ logmsg(WHO,111,FATAL,B("unable to run: ",argv[1]));
+ }
+ close(pi[0]);
+ buffer_init(&bo,write,pi[1],outbuf,sizeof(outbuf));
+
+ time(&now);
+
+ tm = gmtime(&now);
+ dt.year = tm->tm_year;
+ dt.mon = tm->tm_mon;
+ dt.mday = tm->tm_mday;
+ dt.hour = tm->tm_hour;
+ dt.min = tm->tm_min;
+ dt.sec = tm->tm_sec;
+ utc = datetime_untai(&dt); /* utc == now, if gmtime ignores leap seconds */
+
+ tm = localtime(&now);
+ dt.year = tm->tm_year;
+ dt.mon = tm->tm_mon;
+ dt.mday = tm->tm_mday;
+ dt.hour = tm->tm_hour;
+ dt.min = tm->tm_min;
+ dt.sec = tm->tm_sec;
+ local = datetime_untai(&dt);
+
+ buffer_puts(&bo,"Date: ");
+ buffer_put(&bo,num,fmt_uint(num,dt.mday));
+ buffer_puts(&bo," ");
+ buffer_puts(&bo,montab[dt.mon]);
+ buffer_puts(&bo," ");
+ buffer_put(&bo,num,fmt_uint(num,dt.year + 1900));
+ buffer_puts(&bo," ");
+ buffer_put(&bo,num,fmt_uint0(num,dt.hour,2));
+ buffer_puts(&bo,":");
+ buffer_put(&bo,num,fmt_uint0(num,dt.min,2));
+ buffer_puts(&bo,":");
+ buffer_put(&bo,num,fmt_uint0(num,dt.sec,2));
+
+ if (local < utc) {
+ minutes = (utc - local + 30) / 60;
+ buffer_puts(&bo," -");
+ buffer_put(&bo,num,fmt_uint0(num,minutes / 60,2));
+ buffer_put(&bo,num,fmt_uint0(num,minutes % 60,2));
+ }
+ else {
+ minutes = (local - utc + 30) / 60;
+ buffer_puts(&bo," +");
+ buffer_put(&bo,num,fmt_uint0(num,minutes / 60,2));
+ buffer_put(&bo,num,fmt_uint0(num,minutes % 60,2));
+ }
+
+ buffer_puts(&bo,"\n");
+ buffer_copy(&bo,buffer_0);
+ buffer_flush(&bo);
+ close(pi[1]);
+
+ if (wait_pid(&wstat,pid) == -1)
+ logmsg(WHO,111,FATAL,"wait failed");
+ if (wait_crashed(wstat))
+ logmsg(WHO,111,FATAL,"child crashed");
+ _exit(wait_exitcode(wstat));
+}
diff --git a/src/preline.c b/src/preline.c
new file mode 100644
index 0000000..fb4af06
--- /dev/null
+++ b/src/preline.c
@@ -0,0 +1,86 @@
+#include <unistd.h>
+#include "fd.h"
+#include "buffer.h"
+#include "exit.h"
+#include "wait.h"
+#include "env.h"
+#include "sig.h"
+#include "getoptb.h"
+#include "logmsg.h"
+
+#define WHO "preline"
+
+void die_usage()
+{
+ logmsg(WHO,100,USAGE,"preline cmd [ arg ... ]");
+}
+
+int flagufline = 1; char *ufline;
+int flagrpline = 1; char *rpline;
+int flagdtline = 1; char *dtline;
+
+char outbuf[BUFFER_OUTSIZE];
+buffer bo = BUFFER_INIT(write,1,outbuf,sizeof(outbuf));
+char inbuf[BUFFER_INSIZE];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+
+int main(int argc, char **argv)
+{
+ int opt;
+ int pi[2];
+ int pid;
+ int wstat;
+
+ sig_pipeignore();
+
+ if (!(ufline = env_get("UFLINE"))) die_usage();
+ if (!(rpline = env_get("RPLINE"))) die_usage();
+ if (!(dtline = env_get("DTLINE"))) die_usage();
+
+ while ((opt = getopt(argc,argv,"frdFRD")) != opteof)
+ switch (opt) {
+ case 'f': flagufline = 0; break;
+ case 'r': flagrpline = 0; break;
+ case 'd': flagdtline = 0; break;
+ case 'F': flagufline = 1; break;
+ case 'R': flagrpline = 1; break;
+ case 'D': flagdtline = 1; break;
+ default: die_usage();
+ }
+ argc -= optind;
+ argv += optind;
+ if (!*argv) die_usage();
+
+ if (pipe(pi) == -1)
+ logmsg(WHO,111,FATAL,"unable to create pipe");
+
+ pid = fork();
+ if (pid == -1)
+ logmsg(WHO,111,FATAL,"unable to fork");
+
+ if (pid == 0) {
+ close(pi[1]);
+ if (fd_move(0,pi[0]) == -1)
+ logmsg(WHO,111,FATAL,"unable to set up fds");
+ sig_pipedefault();
+ execvp(*argv,argv);
+ logmsg(WHO,errno,FATAL,B("unable to run: ",*argv));
+ }
+ close(pi[0]);
+ if (fd_move(1,pi[1]) == -1)
+ logmsg(WHO,111,FATAL,"unable to set up fds");
+
+ if (flagufline) buffer_puts(&bo,ufline);
+ if (flagrpline) buffer_puts(&bo,rpline);
+ if (flagdtline) buffer_puts(&bo,dtline);
+ if (buffer_copy(&bo,&bi) != 0)
+ logmsg(WHO,111,FATAL,"unable to copy input");
+ buffer_flush(&bo);
+ close(1);
+
+ if (wait_pid(&wstat,pid) == -1)
+ logmsg(WHO,111,FATAL,"wait failed");
+ if (wait_crashed(wstat))
+ logmsg(WHO,111,FATAL,"child crashed");
+ _exit(wait_exitcode(wstat));
+}
diff --git a/src/printforward.c b/src/printforward.c
new file mode 100644
index 0000000..0ab7ad9
--- /dev/null
+++ b/src/printforward.c
@@ -0,0 +1,142 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "logmsg.h"
+#include "stralloc.h"
+#include "cdbread.h"
+
+#define WHO "printmaillist"
+
+void badformat()
+{
+ logmsg(WHO,100,FATAL,"bad database format");
+}
+void nomem()
+{
+ logmsg(WHO,111,FATAL,"out of memory");
+}
+
+void getch(char *ch)
+{
+ int r;
+ r = buffer_get(buffer_0small,ch,1);
+ if (r == -1)
+ logmsg(WHO,111,FATAL,"unable to read input");
+ if (r == 0)
+ badformat();
+}
+
+void out(char *ch)
+{
+ if (buffer_put(buffer_1small,ch,1) == -1)
+ logmsg(WHO,111,FATAL,"unable to write output");
+}
+
+void printbuf(char *buf)
+{
+ while (*buf)
+ out(buf++);
+}
+
+void printsafe(char *buf,int len)
+{
+ char ch;
+
+ while (len) {
+ ch = *buf;
+ if ((ch <= 32) || (ch == ',') || (ch == ':') || (ch == ';') || (ch == '\\') || (ch == '#'))
+ out("\\");
+ out(&ch);
+ ++buf;
+ --len;
+ }
+}
+
+stralloc key = {0};
+stralloc data = {0};
+
+int main()
+{
+ uint32 eod;
+ uint32 pos;
+ uint32 klen;
+ uint32 dlen;
+ char buf[8];
+ char ch;
+ int i;
+ int j;
+
+ for (i = 0; i < 4; ++i)
+ getch(buf + i);
+ eod = cdb_unpack(buf);
+
+ for (i = 4; i < 2048; ++i)
+ getch(&ch);
+
+ pos = 2048;
+ while (pos < eod) {
+ if (eod - pos < 8) badformat();
+ pos += 8;
+ for (i = 0; i < 8; ++i) getch(buf + i);
+ klen = cdb_unpack(buf);
+ dlen = cdb_unpack(buf + 4);
+
+ if (!stralloc_copys(&key,"")) nomem();
+ if (eod - pos < klen) badformat();
+ pos += klen;
+ while (klen) {
+ --klen;
+ getch(&ch);
+ if (!stralloc_append(&key,&ch)) nomem();
+ }
+
+ if (eod - pos < dlen) badformat();
+ pos += dlen;
+ if (!stralloc_copys(&data,"")) nomem();
+ while (dlen) {
+ --dlen;
+ getch(&ch);
+ if (!stralloc_append(&data,&ch)) nomem();
+ }
+
+ if (!key.len) badformat();
+ if (key.s[0] == '?') {
+ printsafe(key.s + 1,key.len - 1);
+ printbuf(": ?");
+ printsafe(data.s,data.len);
+ printbuf(";\n");
+ }
+ else if (key.s[0] == ':') {
+ printsafe(key.s + 1,key.len - 1);
+ printbuf(":\n");
+
+ i = 0;
+ for (j = 0; j < data.len; ++j)
+ if (!data.s[j]) {
+ if ((data.s[i] == '.') || (data.s[i] == '/')) {
+ printbuf(", ");
+ printsafe(data.s + i,j - i);
+ printbuf("\n");
+ }
+ else if ((data.s[i] == '|') || (data.s[i] == '!')) {
+ printbuf(", ");
+ printsafe(data.s + i,j - i);
+ printbuf("\n");
+ }
+ else if ((data.s[i] == '&') && (j - i < 900)) {
+ printbuf(", ");
+ printsafe(data.s + i,j - i);
+ printbuf("\n");
+ }
+ else badformat();
+ i = j + 1;
+ }
+ if (i != j) badformat();
+ printbuf(";\n");
+ }
+ else badformat();
+ }
+
+ if (buffer_flush(buffer_1small) == -1)
+ logmsg(WHO,111,FATAL,"unable to write output");
+ _exit(0);
+}
diff --git a/src/printmaillist.c b/src/printmaillist.c
new file mode 100644
index 0000000..6edb3b3
--- /dev/null
+++ b/src/printmaillist.c
@@ -0,0 +1,53 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "logmsg.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "str.h"
+
+#define WHO "printmaillist"
+
+void badformat()
+{
+ logmsg(WHO,100,FATAL,"bad mailing list format");
+}
+
+stralloc line = {0};
+int match;
+
+int main()
+{
+ for (;;) {
+ if (getln(buffer_1small,&line,&match,'\0') == -1)
+ logmsg(WHO,111,FATAL,"unable to read input: ");
+ if (!match) {
+ if (line.len)
+ badformat();
+ if (buffer_flush(buffer_1small) == -1)
+ logmsg(WHO,111,FATAL,"unable to write output: ");
+ _exit(0);
+ }
+
+ if (line.s[str_chr(line.s,'\n')]) badformat();
+ if (line.s[line.len - 1] == ' ') badformat();
+ if (line.s[line.len - 1] == '\t') badformat();
+
+ if ((line.s[0] == '.') || (line.s[0] == '/')) {
+ if (buffer_puts(buffer_1small,line.s) == -1)
+ logmsg(WHO,111,FATAL,"unable to write output: ");
+ if (buffer_puts(buffer_1small,"\n") == -1)
+ logmsg(WHO,111,FATAL,"unable to write output: ");
+ continue;
+ }
+ if (line.s[0] == '&') {
+ if (line.len > 900) badformat();
+ if (buffer_puts(buffer_1small,line.s) == -1)
+ logmsg(WHO,111,FATAL,"unable to write output: ");
+ if (buffer_puts(buffer_1small,"\n") == -1)
+ logmsg(WHO,111,FATAL,"unable to write output: ");
+ continue;
+ }
+
+ badformat();
+ }
+}
diff --git a/src/prioq.c b/src/prioq.c
new file mode 100644
index 0000000..9559d31
--- /dev/null
+++ b/src/prioq.c
@@ -0,0 +1,54 @@
+#include "alloc.h"
+#include "genalloc.h"
+#include "prioq.h"
+
+GEN_ALLOC_readyplus(prioq,struct prioq_elt,p,len,a,i,n,x,100,prioq_readyplus)
+
+int prioq_insert(prioq *pq, struct prioq_elt *pe)
+{
+ int i;
+ int j;
+
+ if (!prioq_readyplus(pq,1)) return 0;
+ j = pq->len++;
+ while (j) {
+ i = (j - 1)/2;
+ if (pq->p[i].dt <= pe->dt) break;
+ pq->p[j] = pq->p[i];
+ j = i;
+ }
+ pq->p[j] = *pe;
+ return 1;
+}
+
+int prioq_min(prioq *pq, struct prioq_elt *pe)
+{
+ if (!pq->p) return 0;
+ if (!pq->len) return 0;
+ *pe = pq->p[0];
+ return 1;
+}
+
+void prioq_delmin(prioq *pq)
+{
+ int i;
+ int j;
+ int n;
+
+ if (!pq->p) return;
+ n = pq->len;
+ if (!n) return;
+ i = 0;
+ --n;
+
+ for (;;) {
+ j = i + i + 2;
+ if (j > n) break;
+ if (pq->p[j - 1].dt <= pq->p[j].dt) --j;
+ if (pq->p[n].dt <= pq->p[j].dt) break;
+ pq->p[i] = pq->p[j];
+ i = j;
+ }
+ pq->p[i] = pq->p[n];
+ pq->len = n;
+}
diff --git a/src/prot.c b/src/prot.c
new file mode 100644
index 0000000..5bcddd0
--- /dev/null
+++ b/src/prot.c
@@ -0,0 +1,21 @@
+#include "hasshsgr.h"
+#include "prot.h"
+
+/* XXX: there are more portability problems here waiting to leap out at me */
+
+int prot_gid(int gid)
+{
+#ifdef HASSHORTSETGROUPS
+ short x[2];
+ x[0] = gid; x[1] = 73; /* catch errors */
+ if (setgroups(1,x) == -1) return -1;
+#else
+ if (setgroups(1,&gid) == -1) return -1;
+#endif
+ return setgid(gid); /* _should_ be redundant, but on some systems it isn't */
+}
+
+int prot_uid(int uid)
+{
+ return setuid(uid);
+}
diff --git a/src/qbiff.c b/src/qbiff.c
new file mode 100644
index 0000000..b9b55bf
--- /dev/null
+++ b/src/qbiff.c
@@ -0,0 +1,141 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "hasutmp.h"
+#ifdef HASUTMP
+#include <utmp.h>
+#ifndef UTMP_FILE
+#ifdef _PATH_UTMP
+#define UTMP_FILE _PATH_UTMP
+#else
+#define UTMP_FILE "/etc/utmp"
+#endif
+#endif
+#else
+#include <utmpx.h>
+#endif
+#include "stralloc.h"
+#include "buffer.h"
+#include "open.h"
+#include "byte.h"
+#include "str.h"
+#include "headerbody.h"
+#include "hfield.h"
+#include "env.h"
+#include "exit.h"
+
+buffer b;
+#ifdef HASUTMP
+char bufutmp[sizeof(struct utmp) * 16];
+int fdutmp;
+#endif
+char buftty[1024];
+int fdtty;
+
+#ifdef HASUTMP
+struct utmp ut;
+char line[sizeof(ut.ut_line) + 1];
+#else
+struct utmpx *ut;
+char line[sizeof(ut->ut_line) + 1];
+#endif
+
+stralloc woof = {0};
+stralloc tofrom = {0};
+stralloc text = {0};
+
+void doit(char *s, int n)
+{
+ if (!stralloc_catb(&text,s,n)) _exit(0);
+ if (text.len > 78) text.len = 78;
+}
+
+void dobody(stralloc *h) { doit(h->s,h->len); }
+
+void doheader(stralloc *h)
+{
+ int i;
+
+ if (hfield_known(h->s,h->len) == H_SUBJECT) {
+ i = hfield_skipname(h->s,h->len);
+ doit(h->s + i,h->len - i);
+ }
+}
+
+void finishheader() { ; }
+
+int main()
+{
+ char *user;
+ char *sender;
+ char *userext;
+ struct stat st;
+ int i;
+
+ if (chdir("/dev") == -1) _exit(0);
+
+ if (!(user = env_get("USER"))) _exit(0);
+ if (!(sender = env_get("SENDER"))) _exit(0);
+ if (!(userext = env_get("LOCAL"))) _exit(0);
+#ifdef HASUTMP
+ if (str_len(user) > sizeof(ut.ut_name)) _exit(0);
+#else
+ if (str_len(user) > sizeof(ut->ut_user)) _exit(0);
+#endif
+
+ if (!stralloc_copys(&tofrom,"*** TO <")) _exit(0);
+ if (!stralloc_cats(&tofrom,userext)) _exit(0);
+ if (!stralloc_cats(&tofrom,"> FROM <")) _exit(0);
+ if (!stralloc_cats(&tofrom,sender)) _exit(0);
+ if (!stralloc_cats(&tofrom,">")) _exit(0);
+
+ for (i = 0; i < tofrom.len; ++i)
+ if ((tofrom.s[i] < 32) || (tofrom.s[i] > 126))
+ tofrom.s[i] = '_';
+
+ if (!stralloc_copys(&text," ")) _exit(0);
+ if (headerbody(buffer_0,doheader,finishheader,dobody) == -1) _exit(0);
+
+ for (i = 0; i < text.len; ++i)
+ if ((text.s[i] < 32) || (text.s[i] > 126))
+ text.s[i] = '/';
+
+ if (!stralloc_copys(&woof,"\015\n\007")) _exit(0);
+ if (!stralloc_cat(&woof,&tofrom)) _exit(0);
+ if (!stralloc_cats(&woof,"\015\n")) _exit(0);
+ if (!stralloc_cat(&woof,&text)) _exit(0);
+ if (!stralloc_cats(&woof,"\015\n")) _exit(0);
+
+#ifdef HASUTMP
+ fdutmp = open_read(UTMP_FILE);
+ if (fdutmp == -1) _exit(0);
+ buffer_init(&b,read,fdutmp,bufutmp,sizeof(bufutmp));
+
+ while (buffer_get(&b,&ut,sizeof(ut)) == sizeof(ut))
+ if (!str_diffn(ut.ut_name,user,sizeof(ut.ut_name))) {
+#else
+ while ((ut = getutxent()) != 0)
+ if (ut->ut_type == USER_PROCESS && !str_diffn(ut->ut_user,user,sizeof(ut->ut_user))) {
+#endif
+#ifdef HASUTMP
+ byte_copy(line,sizeof(ut.ut_line),ut.ut_line);
+ line[sizeof(ut.ut_line)] = 0;
+#else
+ byte_copy(line,sizeof(ut->ut_line),ut->ut_line);
+ line[sizeof(ut->ut_line)] = 0;
+#endif
+ if (line[0] == '/') continue;
+ if (!line[0]) continue;
+ if (line[str_chr(line,'.')]) continue;
+
+ fdtty = open_append(line);
+ if (fdtty == -1) continue;
+ if (fstat(fdtty,&st) == -1) { close(fdtty); continue; }
+ if (!(st.st_mode & 0100)) { close(fdtty); continue; }
+ if (st.st_uid != getuid()) { close(fdtty); continue; }
+ buffer_init(&b,write,fdtty,buftty,sizeof(buftty));
+ buffer_putflush(&b,woof.s,woof.len);
+ close(fdtty);
+ }
+ _exit(0);
+}
diff --git a/src/qmail-authuser.c b/src/qmail-authuser.c
new file mode 100644
index 0000000..e90468f
--- /dev/null
+++ b/src/qmail-authuser.c
@@ -0,0 +1,441 @@
+#include <stdio.h>
+#include <unistd.h>
+#include "global.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "auto_qmail.h"
+#include "case.h"
+#include "control.h"
+#include "constmap.h"
+#include "str.h"
+#include "fmt.h"
+#include "fd.h"
+#include "open.h"
+#include "byte.h"
+#include "scan.h"
+#include "md5.h"
+#include "hmac_md5.h"
+#include "sha1.h"
+#include "sha256.h"
+#include "pathexec.h"
+#include "prot.h"
+#include "wait.h"
+#include "sig.h"
+#include "error.h"
+#define FDAUTH 3
+#define FDGOSSIP 1
+#define SOCKET_CALL "-s"
+#define DOVECOT_SERVICE "-x"
+#define POP_USER "qmail-pop3d"
+
+extern char *crypt();
+#include <pwd.h>
+static struct passwd *pw;
+
+#include "hasspnam.h"
+#ifdef HASGETSPNAM
+#include <shadow.h>
+static struct spwd *spw;
+#endif
+
+#include "hasuserpw.h"
+#ifdef HASUSERPW
+#include <userpw.h>
+static struct userpw *upw;
+#endif
+
+/** @file qmail-authuser.c
+@return 0: ok
+ 1: credentials failure
+ 2: qmail-authuser is misused
+ 110: can't read controls
+ 111: temporary problem checking the password
+*/
+
+char authbuf[512];
+buffer ba = BUFFER_INIT(write,FDAUTH,authbuf,sizeof(authbuf));
+
+struct constmap mapauthuser;
+stralloc authfile = {0};
+stralloc disabled = {0};
+stralloc user = {0}; // user w/o domain appended
+stralloc homedir = {0};
+stralloc shell = {0};
+
+/** @brief Supported storage methods:
+ (1) authuser:[=]plainpasswd,
+ (2) authuser:%hashpasswd,
+ (3) authuser:?, authuser:!, *:?, *:! (! -> +environment)
+ (4) x:+ -> checkvpw; x = { user@domain, @domain, @ } vmailmgr
+ (5) x:& -> vchkpw; x = { user@domain, @domain, @ } vpopmail
+ (6) x:= -> qmail-client; x = { user@domain, @domain, @ } dovecot
+ Supported auth methods:
+ user/login/plain: (1,2,3,4,5,6),
+ cram-md5/apop: (1,5)
+*/
+
+void exit(int fail)
+{
+ int i;
+
+ for (i = 0; i < sizeof(authbuf); ++i) authbuf[i] = 0;
+ _exit(fail);
+}
+
+int dig_ascii(char *digascii,const char *digest,const int len)
+{
+ static const char hextab[] = "0123456789abcdef";
+ int j;
+
+ for (j = 0; j < len; j++) {
+ digascii[2 * j] = hextab[digest[j] >> 4];
+ digascii[2 * j + 1] = hextab[digest[j] & 0x0f];
+ }
+ digascii[2 * len] = '\0';
+
+ return (2*j); // 2*len
+}
+
+int auth_sha1(char *pwdhash,char *response)
+{
+ unsigned char digest[20];
+ unsigned char digascii[41];
+
+ sha1_hash(digest,response,str_len(response));
+ dig_ascii(digascii,digest,20);
+
+ return str_diffn(digascii,pwdhash,40);
+}
+
+int auth_sha256(char *pwdhash,char *response)
+{
+ unsigned char digest[32];
+ unsigned char digascii[65];
+
+ sha256_hash(digest,response,str_len(response));
+ dig_ascii(digascii,digest,32);
+
+ return str_diffn(digascii,pwdhash,64);
+}
+
+int auth_md5(char *pwdhash,char *response)
+{
+ MD5_CTX ctx;
+ unsigned char digest[16];
+ unsigned char digascii[33];
+
+ MD5Init(&ctx);
+ MD5Update(&ctx,response,str_len(response));
+ MD5Final(digest,&ctx);
+ dig_ascii(digascii,digest,16);
+
+ return str_diffn(digascii,pwdhash,32);
+}
+
+int auth_hash(char *password,char *response)
+{
+ switch (str_len(password)) {
+ case 32: return auth_md5(password,response);
+ case 40: return auth_sha1(password,response);
+ case 64: return auth_sha256(password,response);
+ default: return -1;
+ }
+}
+
+int auth_unix(char *user,char* response)
+{
+ char *encrypted = 0;
+ char *stored = 0;
+ int r = -1;
+
+ pw = getpwnam(user);
+ if (pw) {
+ stored = pw->pw_passwd;
+ if (!stralloc_copys(&homedir,pw->pw_dir)) exit(111);
+ if (!stralloc_copys(&shell,pw->pw_shell)) exit(111);
+ } else {
+ if (errno == ETXTBSY) exit(111);
+ exit(1);
+ }
+
+ if (response) {
+#ifdef HASUSERPW
+ upw = getuserpw(user);
+ if (upw)
+ stored = upw->upw_passwd;
+ else
+ if (errno == ETXTBSY) exit(111);
+#elif HASGETSPNAM
+ spw = getspnam(user);
+ if (spw)
+ stored = spw->sp_pwdp;
+ else
+ if (errno == ETXTBSY) exit(111);
+#endif
+ if (!stored || !*stored) exit(111);
+ encrypted = crypt(response,stored);
+ if (!encrypted) exit(111); // no password given (tx. M.B.)
+ r = str_diff(encrypted,stored);
+ }
+
+ if (r == 0 || !response) {
+ if (prot_gid((int) pw->pw_gid) == -1) exit(1);
+ if (prot_uid((int) pw->pw_uid) == -1) exit(1);
+ if (chdir(pw->pw_dir) == -1) exit(111);
+ }
+
+ return r;
+}
+
+int auth_apop(unsigned char *password,unsigned char *response,unsigned char *challenge)
+{
+ MD5_CTX context;
+ unsigned char digest[16];
+ unsigned char digascii[33];
+
+ MD5Init(&context);
+ MD5Update(&context,challenge,str_len(challenge));
+ MD5Update(&context,password,str_len(password));
+ MD5Final(digest,&context);
+ dig_ascii(digascii,digest,16);
+
+ return (str_diff(digascii,response));
+}
+
+int auth_cram(unsigned char *password,unsigned char *response,unsigned char *challenge)
+{
+ unsigned char digest[16];
+ unsigned char digascii[33];
+
+ hmac_md5(challenge,str_len(challenge),password,str_len(password),digest);
+ dig_ascii(digascii,digest,16);
+
+ return (str_diff(digascii,response) && str_diff(password,response));
+}
+
+int auth_dovecot(char *user,char *response,char *socket,char *service)
+{
+ int wstat;
+ int child;
+ char *wrapper[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ int i = 0;
+
+ close(FDGOSSIP); /* gossiping doveadm */
+
+ switch (child = fork()) {
+ case -1:
+ exit(111);
+ case 0:
+ wrapper[i] = "doveadm";
+ wrapper[++i] = "auth";
+ wrapper[++i] = "test";
+ if (socket) {
+ wrapper[++i] = "-a";
+ wrapper[++i] = socket;
+ }
+ if (service) {
+ wrapper[++i] = "-x";
+ wrapper[++i] = service;
+ }
+ wrapper[++i] = user;
+ wrapper[++i] = response;
+ wrapper[++i] = 0;
+
+ execvp(wrapper[0],wrapper);
+ exit(111);
+ }
+
+ if (wait_pid(&wstat,child) == -1) exit(111);
+ if (wait_crashed(wstat)) exit(111);
+ return wait_exitcode(wstat);
+}
+
+int auth_wrapper(char *pam,char *arg1,char *arg2,char *auth,int len)
+{
+ int wstat;
+ int child;
+ int pi[2];
+ char *wrapper[4] = {0, 0, 0, 0};
+
+ if (pipe(pi) == -1) exit(111);
+ if (pi[0] != FDAUTH) exit(111);
+
+ switch (child = fork()) {
+ case -1:
+ exit(111);
+ case 0:
+ close(pi[1]);
+ if (fd_copy(FDAUTH,pi[0]) == -1) exit(111);
+ wrapper[0] = pam;
+ wrapper[1] = arg1;
+ wrapper[2] = arg2;
+ wrapper[3] = 0;
+ sig_pipedefault();
+
+ execvp(wrapper[0],wrapper);
+ exit(111);
+ }
+ close(pi[0]);
+
+ buffer_init(&ba,write,pi[1],authbuf,sizeof(authbuf));
+ if (buffer_put(&ba,auth,len) == -1) exit(111);
+ if (buffer_flush(&ba) == -1) exit(111);
+ close(pi[1]);
+
+ if (wait_pid(&wstat,child) == -1) exit(111);
+ if (wait_crashed(wstat)) exit(111);
+ return wait_exitcode(wstat);
+}
+
+int main(int argc,char **argv)
+{
+ char *authuser;
+ char *authpass;
+ char *response = 0;
+ char *challenge = 0;
+ char *domain = 0;
+ char *authsocket = 0;
+ char *service = 0;
+ char *maildirname = 0;
+ int rc = -1; /* initialise: -1; ok: 0; !ok: > 0 */
+ int authlen = 0;
+ int buflen = 0;
+ int domlen = 0;
+ int popuser = 0;
+ int i = 0;
+ int r;
+
+ if (!argv[1])
+ exit(2);
+ else if (argv[2]) { // pop user with homedir
+ if (!case_diffs(argv[1],POP_USER)) {
+ if (!argv[3]) exit(2);
+ maildirname = argv[2];
+ popuser = 1;
+ }
+ if (!case_diffs(argv[1],SOCKET_CALL)) { // dovecot socket
+ if (!argv[3]) exit(2);
+ authsocket = argv[2];
+ if (!case_diffs(argv[3],DOVECOT_SERVICE)) { // ++ dovecot service
+ service = argv[4];
+ if (!argv[5]) exit(2);
+ }
+ }
+ if (!case_diffs(argv[1],DOVECOT_SERVICE)) { // dovecot service
+ if (!argv[3]) exit(2);
+ service = argv[2];
+ if (!case_diffs(argv[3],SOCKET_CALL)) { // ++ dovecot socket
+ if (!argv[5]) exit(2);
+ authsocket = argv[4];
+ }
+ }
+ }
+
+ /* Read input on FDAUTH */
+
+ for (;;) {
+ do
+ r = read(FDAUTH,authbuf + buflen,sizeof(authbuf) - buflen);
+ while ((r == -1) && (errno == EINTR));
+ if (r == -1) exit(111);
+ if (r == 0) break;
+ buflen += r;
+ if (buflen >= sizeof(authbuf)) exit(2);
+ }
+ close(FDAUTH);
+
+ authuser = authbuf + i; /* username */
+ if (i == buflen) exit(2);
+ while (authbuf[i++]) /* response */
+ if (i == buflen) exit(2);
+ response = authbuf + i;
+ if (i == buflen) exit(2);
+ while (authbuf[i++]) /* challenge */
+ if (i == buflen) exit(2);
+ challenge = authbuf + i;
+
+ authlen = str_len(authuser);
+ if (!stralloc_copyb(&user,authuser,authlen)) exit(111);
+
+ if ((i = byte_rchr(authuser,authlen,'@'))) /* @domain */
+ if (i < authlen && authuser[i] == '@') {
+ domain = authuser + i;
+ domlen = str_len(domain);
+ case_lowerb(domain,domlen);
+ user.len = 0;
+ if (!stralloc_copyb(&user,authuser,i)) exit(111);
+ }
+ if (!stralloc_0(&user)) exit(111);
+
+ /* Read control file users/authuser and go for checks */
+
+ if (chdir(auto_qmail) == -1) exit(110);
+
+ switch (control_readfile(&authfile,"users/authuser",0)) {
+ case -1: exit(110);
+ case 0: if (!constmap_init(&mapauthuser,"",0,1)) exit(111);
+ case 1: if (!constmap_init(&mapauthuser,authfile.s,authfile.len,1)) exit(111);
+ }
+
+ /* Check for disabled authuser/domains */
+
+ if (!stralloc_copys(&disabled,"!")) exit(111);
+ if (!stralloc_catb(&disabled,authuser,authlen)) exit(111);
+ if (constmap(&mapauthuser,disabled.s,disabled.len)) exit(1);
+
+ if (domlen) {
+ disabled.len = 0;
+ if (!stralloc_copys(&disabled,"!")) exit(111);
+ if (!stralloc_catb(&disabled,domain,domlen)) exit(111);
+ if (constmap(&mapauthuser,disabled.s,disabled.len)) exit(1);
+ }
+
+ /* Virtual and system user accounts */
+
+ authpass = constmap(&mapauthuser,authuser,authlen);
+
+ if (!authpass && domlen)
+ authpass = constmap(&mapauthuser,domain,domlen); // 1. authuser accounts
+ if (!authpass)
+ authpass = constmap(&mapauthuser,"*",1); // 2. system accounts
+ if (!authpass)
+ authpass = constmap(&mapauthuser,"@",1); // 3. virtual user accounts
+
+ if (!authpass) exit(1);
+
+ if (str_len(authpass) == 1) {
+ switch (authpass[0]) {
+ case '?': rc = auth_unix(user.s,response); break;
+ case '+': if (popuser)
+ rc = auth_wrapper("checkvpw","qmail-pop3d",maildirname,authbuf,buflen);
+ else
+ rc = auth_wrapper("checkvpw","true","Maildir",authbuf,buflen); break;
+ case '&': if (popuser)
+ rc = auth_wrapper("vchkpw","qmail-pop3d",maildirname,authbuf,buflen);
+ else
+ rc = auth_wrapper("vchkpw","true",0,authbuf,buflen); break;
+ case '=': rc = auth_dovecot(authuser,response,authsocket,service); break;
+ default: rc = 2; break;
+ }
+ } else {
+ switch (authpass[0]) {
+ case '%': rc = auth_hash(authpass + 1,response); break;
+ default: if (rc) {
+ if (popuser) {
+ if ((rc = auth_apop(authpass,response,challenge)) == 0) {
+ auth_unix(user.s,0); // Unix environment only
+ }
+ } else rc = auth_cram(authpass,response,challenge);
+ } break;
+ }
+ }
+
+ if (rc) exit(rc);
+
+ for (i = 0; i < sizeof(authbuf); ++i) authbuf[i] = 0;
+
+ if (authsocket && service) pathexec(argv + 5);
+ if (authsocket || service || popuser) pathexec(argv + 3);
+ else pathexec(argv + 1);
+ exit(111);
+}
diff --git a/src/qmail-badloadertypes.c b/src/qmail-badloadertypes.c
new file mode 100644
index 0000000..3472fd5
--- /dev/null
+++ b/src/qmail-badloadertypes.c
@@ -0,0 +1,68 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include "logmsg.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include "open.h"
+#include "auto_qmail.h"
+#include "cdbmake.h"
+
+#define WHO "qmail-badloadertypes"
+#define LOADER_LEN 5
+
+int rename(const char *,const char *); // stdio.h
+
+void die_read()
+{
+ logmsg(WHO,111,FATAL,"unable to read control/badloadertypes");
+}
+void die_write()
+{
+ logmsg(WHO,111,FATAL,"unable to write to control/badloadertypes.tmp");
+}
+
+char inbuf[1024];
+buffer b;
+
+int fd;
+int fdtemp;
+
+struct cdb_make cdb;
+stralloc line = {0};
+int match;
+
+int main()
+{
+ umask(033);
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+
+ fd = open_read("control/badloadertypes");
+ if (fd == -1) die_read();
+
+ buffer_init(&b,read,fd,inbuf,sizeof(inbuf));
+
+ fdtemp = open_trunc("control/badloadertypes.tmp");
+ if (fdtemp == -1) die_write();
+
+ if (cdb_make_start(&cdb,fdtemp) == -1) die_write();
+
+ for (;;) {
+ if (getln(&b,&line,&match,'\n') != 0) die_read();
+ if (line.s[0] != '#' && line.len > LOADER_LEN)
+ if (cdb_make_add(&cdb,line.s,LOADER_LEN,"",0) == -1)
+ die_write();
+ if (!match) break;
+ }
+
+ if (cdb_make_finish(&cdb) == -1) die_write();
+ if (fsync(fdtemp) == -1) die_write();
+ if (close(fdtemp) == -1) die_write(); /* NFS stupidity */
+ if (rename("control/badloadertypes.tmp","control/badloadertypes.cdb") == -1)
+ logmsg(WHO,111,FATAL,"unable to move control/badloadertypes.tmp to control/badloadertypes.cdb");
+
+ _exit(0);
+}
diff --git a/src/qmail-badmimetypes.c b/src/qmail-badmimetypes.c
new file mode 100644
index 0000000..a75ad4c
--- /dev/null
+++ b/src/qmail-badmimetypes.c
@@ -0,0 +1,67 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "logmsg.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include "open.h"
+#include "auto_qmail.h"
+#include "cdbmake.h"
+
+#define WHO "qmail-badmimetypes"
+#define MIMETYPE_LEN 9
+
+int rename(const char *,const char *); // stdio.h
+
+void die_read()
+{
+ logmsg(WHO,111,FATAL,"unable to read control/badmimetypes");
+}
+void die_write()
+{
+ logmsg(WHO,111,FATAL,"unable to write to control/badmimetypes.tmp");
+}
+
+char inbuf[1024];
+buffer b;
+
+int fd;
+int fdtemp;
+
+struct cdb_make cdb;
+stralloc line = {0};
+int match;
+
+int main()
+{
+ umask(033);
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+
+ fd = open_read("control/badmimetypes");
+ if (fd == -1) die_read();
+
+ buffer_init(&b,read,fd,inbuf,sizeof(inbuf));
+
+ fdtemp = open_trunc("control/badmimetypes.tmp");
+ if (fdtemp == -1) die_write();
+
+ if (cdb_make_start(&cdb,fdtemp) == -1) die_write();
+
+ for (;;) {
+ if (getln(&b,&line,&match,'\n') != 0) die_read();
+ if (line.s[0] != '#' && line.len > MIMETYPE_LEN)
+ if (cdb_make_add(&cdb,line.s,MIMETYPE_LEN,"",0) == -1)
+ die_write();
+ if (!match) break;
+ }
+
+ if (cdb_make_finish(&cdb) == -1) die_write();
+ if (fsync(fdtemp) == -1) die_write();
+ if (close(fdtemp) == -1) die_write(); /* NFS stupidity */
+ if (rename("control/badmimetypes.tmp","control/badmimetypes.cdb") == -1)
+ logmsg(WHO,111,FATAL,"unable to move control/badmimetypes.tmp to control/badmimetypes.cdb");
+
+ _exit(0);
+}
diff --git a/src/qmail-clean.c b/src/qmail-clean.c
new file mode 100644
index 0000000..df149a5
--- /dev/null
+++ b/src/qmail-clean.c
@@ -0,0 +1,100 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sig.h"
+#include "now.h"
+#include "str.h"
+#include "direntry.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "byte.h"
+#include "scan.h"
+#include "fmt.h"
+#include "exit.h"
+#include "error.h"
+#include "fmtqfn.h"
+#include "auto_qmail.h"
+
+#define OSSIFIED 129600 /* see qmail-send.c */
+
+stralloc line = {0};
+
+void cleanuppid()
+{
+ DIR *dir;
+ direntry *d;
+ struct stat st;
+ datetime_sec time;
+
+ time = now();
+ dir = opendir("pid");
+ if (!dir) return;
+
+ while ((d = readdir(dir))) {
+ if (str_equal(d->d_name,".")) continue;
+ if (str_equal(d->d_name,"..")) continue;
+ if (!stralloc_copys(&line,"pid/")) continue;
+ if (!stralloc_cats(&line,d->d_name)) continue;
+ if (!stralloc_0(&line)) continue;
+ if (stat(line.s,&st) == -1) continue;
+ if (time < st.st_atime + OSSIFIED) continue;
+ unlink(line.s);
+ }
+ closedir(dir);
+}
+
+char fnbuf[FMTQFN];
+
+void respond(char *s)
+{
+ if (buffer_putflush(buffer_1small,s,1) == -1) _exit(100);
+}
+
+int main()
+{
+ int i;
+ int match;
+ int cleanuploop;
+ unsigned long id;
+
+ if (chdir(auto_qmail) == -1) _exit(110);
+ if (chdir("queue") == -1) _exit(110);
+
+ sig_pipeignore();
+
+ if (!stralloc_ready(&line,200)) _exit(111);
+
+ cleanuploop = 0;
+
+ for (;;) {
+ if (cleanuploop) --cleanuploop; else { cleanuppid(); cleanuploop = 30; }
+ if (getln(buffer_0small,&line,&match,'\0') == -1) break;
+ if (!match) break;
+ if (line.len < 7) { respond("x"); continue; }
+ if (line.len > 100) { respond("x"); continue; }
+ if (line.s[line.len - 1]) { respond("x"); continue; } /* impossible */
+
+ for (i = line.len - 2; i > 4; --i) {
+ if (line.s[i] == '/') break;
+ if ((unsigned char) (line.s[i] - '0') > 9)
+ { respond("x"); continue; }
+ }
+ if (line.s[i] == '/')
+ if (!scan_ulong(line.s + i + 1,&id)) { respond("x"); continue; }
+ if (byte_equal(line.s,5,"foop/")) {
+#define U(prefix,flag) fmtqfn(fnbuf,prefix,id,flag); \
+ if (unlink(fnbuf) == -1) if (errno != ENOENT) { respond("!"); continue; }
+ U("intd/",1)
+ U("mess/",1)
+ respond("+");
+ } else if (byte_equal(line.s,4,"todo/")) {
+ U("intd/",1)
+ U("todo/",1)
+ respond("+");
+ }
+ else
+ respond("x");
+ }
+ _exit(0);
+}
diff --git a/src/qmail-dkim.cpp b/src/qmail-dkim.cpp
new file mode 100644
index 0000000..fba94fe
--- /dev/null
+++ b/src/qmail-dkim.cpp
@@ -0,0 +1,343 @@
+/*****************************************************************************
+* Copyright 2005 Alt-N Technologies, Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* This code incorporates intellectual property owned by Yahoo! and licensed
+* pursuant to the Yahoo! DomainKeys Patent License Agreement.
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* Changes done by ¢feh@fehcom.de obeying the above license
+*
+* Comment: Awful mixture of C and C++ making use of the worst parts of it.
+* Style: Partial Hungarian notation (see Torvalds comments)
+* C++: Obsolete classes, allocators, virtual constructors w/o destructors
+* C: Stdio interface routines
+* OpenSSL: Brain demaged EVP_Digest calls with memory leaks.
+* Network: Sigh, exchanged internal DNS routines by fehQlibs resolver
+*
+*****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "dkim.h"
+extern "C" {
+#include "dns.h"
+}
+
+// change these to your selector name, domain name, etc
+#define MYRSASELECTOR "default"
+#define MYECCSELECTOR "eddy"
+#define MYDOMAIN "" //"bardenhagen.com"
+#define MYIDENTITY "" //"dkimtest@bardenhagen.com"
+
+#define strnicmp strncasecmp
+#define FDLOG stderr /* writing to another FD requires a method */
+
+int DKIM_CALL SignThisHeader(const char* szHeader)
+{
+ if (strnicmp(szHeader,"X-",2) == 0 ) { return 0; }
+ return 1;
+}
+
+int DKIM_CALL SelectorCallback(const char* szFQDN,char* szBuffer,int nBufLen)
+{
+ return 0;
+}
+
+void usage()
+{
+ char version[] = "1.4.0";
+ fprintf(FDLOG,"qmail-dkim %s \n",version);
+ fprintf(FDLOG,"Usage: qmail-dkim [-h|-v|-s] [tags] <msgfile> [<RSAkeyfile> <outfile> <Ed25519keyfile>]\n\n");
+ fprintf(FDLOG, "Options:\n\t-h show this help\n");
+ fprintf(FDLOG, "\t-s sign the message \n");
+ fprintf(FDLOG, "\t-v verify the message\n");
+ fprintf(FDLOG, "\t-V verify the message and write result to output file (Pass/Fail)\n\n");
+ fprintf(FDLOG, "These tags are available:\n");
+ fprintf(FDLOG, "\t-c<canonicalization> - r=relaxed [DEFAULT], s=simple, t=relaxed/simple, u=simple/relaxed\n");
+ fprintf(FDLOG, "\t-d<sdid> - Signing Domain Identifier (if not provided it will be determined from the sender/from header)\n");
+ fprintf(FDLOG, "\t-i<auid> - Agent User Identifier, usually the sender's email address (optional)\n");
+ fprintf(FDLOG, "\t-l - include body length tag (optional)\n");
+ fprintf(FDLOG, "\t-q - include query method tag\n");
+ fprintf(FDLOG, "\t-t - include a timestamp tag (optional)\n");
+ fprintf(FDLOG, "\t-x<expire_time> - the expire time in seconds since epoch (optional, DEFAULT = current time + 604800)\n");
+ fprintf(FDLOG, "\t-y<selector> - set RSA selector (DEFAULT: default)\n");
+ fprintf(FDLOG, "\t-Y<selector> - set Ed25519 selector (DEFAULT: default)\n");
+ fprintf(FDLOG, "\t-z<hash> - set signature algorithm type (1=rsa-sha1, 2=rsa-sha256, 3=both, 4=ed25519, 5=hybrid)\n");
+}
+
+int main(int argc, char* argv[])
+{
+ int n;
+ const char* RSAKeyFile = "rsa.pem";
+ const char* ECCKeyFile = "ed25519.pem";
+ const char* MsgFile = "test.msg";
+ const char* OutFile = "signed.msg";
+ int nKeyLen;
+ char RSAPrivKey[4196]; // storge for private key FILE including header and DER envelope
+ char ECCPrivKey[128];
+ char Buffer[1000];
+ int BufLen;
+ char szSignature[8192];
+ time_t t;
+ DKIMContext ctxt;
+ DKIMSignOptions opts = {0};
+
+ opts.nHash = DKIM_HASH_SHA256; // default
+
+ time(&t);
+
+ opts.nCanon = DKIM_SIGN_RELAXED;
+ opts.nIncludeBodyLengthTag = 0;
+ opts.nIncludeQueryMethod = 0;
+ opts.nIncludeTimeStamp = 0;
+ opts.expireTime = t + 604800; // expires in 1 week
+ strcpy(opts.szSelector,MYRSASELECTOR);
+ strcpy(opts.szSelectorE,MYECCSELECTOR);
+ strcpy(opts.szDomain,MYDOMAIN);
+ strcpy(opts.szIdentity,MYIDENTITY);
+ opts.pfnHeaderCallback = SignThisHeader;
+ strcpy(opts.szRequiredHeaders,"NonExistant");
+ opts.nIncludeCopiedHeaders = 0;
+
+ int nArgParseState = 0;
+ bool bSign = true;
+ bool bRes = false;
+
+ if (argc < 2){
+ usage();
+ exit(1);
+ }
+
+ for (n = 1; n < argc; n++) {
+ if (argv[n][0] == '-' && strlen(argv[n]) > 1) {
+ switch (argv[n][1]) {
+ case 'c': // canonicalization
+ if (argv[n][2] == 'r') { opts.nCanon = DKIM_SIGN_RELAXED; }
+ else if (argv[n][2] == 's') { opts.nCanon = DKIM_SIGN_SIMPLE; }
+ else if (argv[n][2] == 't') { opts.nCanon = DKIM_SIGN_RELAXED_SIMPLE; }
+ else if (argv[n][2] == 'u') { opts.nCanon = DKIM_SIGN_SIMPLE_RELAXED; }
+ break;
+ case 'd':
+ strncpy(opts.szDomain,(const char*)(argv[n] + 2),sizeof(opts.szDomain) - 1);
+ break;
+ case 'l': // body length tag
+ opts.nIncludeBodyLengthTag = 1;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case 'i': // identity
+ if (argv[n][2] == '-') { opts.szIdentity[0] = '\0'; }
+ else { strncpy(opts.szIdentity, argv[n] + 2,sizeof(opts.szIdentity) - 1); }
+ break;
+ case 'q': // query method tag
+ opts.nIncludeQueryMethod = 1;
+ break;
+ case 's': // sign with and use potentially Ed25519 private key
+ bSign = true;
+ break;
+ case 't': // timestamp tag
+ opts.nIncludeTimeStamp = 1;
+ break;
+ case 'v': // verify
+ bSign = false;
+ break;
+ case 'V': // verify and write result to OutFile
+ bSign = false;
+ bRes = true;
+ break;
+ case 'x': // expire time
+ if (argv[n][2] == '-') { opts.expireTime = 0; }
+ else { opts.expireTime = t + atoi(argv[n] + 2); }
+ break;
+ case 'y':
+ strncpy(opts.szSelector,argv[n] + 2,sizeof(opts.szSelector) - 1);
+ break;
+ case 'Y':
+ strncpy(opts.szSelectorE,argv[n] + 2,sizeof(opts.szSelectorE) - 1);
+ break;
+ case 'z': // sign w/ sha1, sha256, both, ed25519, hybrid
+ opts.nHash = atoi(&argv[n][2]);
+ }
+ }
+ else {
+ switch (nArgParseState) {
+ case 0:
+ MsgFile = argv[n];
+ break;
+ case 1:
+ RSAKeyFile = argv[n];
+ break;
+ case 2:
+ OutFile = argv[n];
+ break;
+ case 3:
+ ECCKeyFile = argv[n];
+ break;
+ }
+ nArgParseState++;
+ }
+ }
+
+/** Go for DKIM signing ... **/
+
+ if (bSign) {
+ if (opts.nHash != 4) {
+ FILE* RSAPrivKeyFP = fopen(RSAKeyFile,"r");
+ if (RSAPrivKeyFP == NULL) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open private key file (%s) \n",RSAKeyFile);
+#endif
+ exit(1);
+ }
+ nKeyLen = fread(RSAPrivKey,1,sizeof(RSAPrivKey),RSAPrivKeyFP); // we read sizeof(RSAPrivKey) members with size of 1 byte each; sigh
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: private key file (%s) - length %i \n",RSAKeyFile,nKeyLen);
+#endif
+ if (nKeyLen >= sizeof(RSAPrivKey)) { /* (TC9) on return, we get the number of members read! */
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: private key buffer isn't big enough for private key length %i \n",nKeyLen);
+#endif
+ exit(1);
+ }
+ RSAPrivKey[nKeyLen] = '\0';
+ fclose(RSAPrivKeyFP);
+ }
+
+/** Ed25519 signing **/
+
+ if (opts.nHash == 4 || opts.nHash == 5) {
+ FILE* ECCPrivKeyFP = fopen(ECCKeyFile,"r");
+ if (ECCPrivKeyFP == NULL) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open Ed25519 private key file (%s) \n",ECCKeyFile);
+#endif
+ exit(1);
+ }
+ nKeyLen = fread(ECCPrivKey,1,sizeof(ECCPrivKey),ECCPrivKeyFP);
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: Ed25519 private key file (%s) - length %i \n",ECCKeyFile,nKeyLen);
+#endif
+ if (nKeyLen >= sizeof(ECCPrivKey)) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: ECC private key buffer isn't big enough for ECC private key length %i \n",nKeyLen);
+#endif
+ exit(1);
+ }
+ ECCPrivKey[nKeyLen] = '\0';
+ fclose(ECCPrivKeyFP);
+ }
+
+/** Input message for signing **/
+
+ FILE* MsgFP = fopen(MsgFile,"rb");
+ if (MsgFP == NULL) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open msg file (%s) \n",MsgFile);
+#endif
+ exit(1);
+ }
+
+ n = DKIMSignInit(&ctxt,&opts);
+
+ while (1) {
+ BufLen = fread(Buffer,1,sizeof(Buffer),MsgFP);
+ if (BufLen > 0) { DKIMSignProcess(&ctxt,Buffer,BufLen); }
+ else { break; }
+ }
+ fclose(MsgFP);
+
+ char* pSig = NULL;
+
+/** Do the actual signing **/
+
+ n = DKIMSignGetSig2(&ctxt,RSAPrivKey,ECCPrivKey,&pSig);
+
+ strcpy(szSignature,pSig);
+
+ DKIMSignFree(&ctxt);
+
+ FILE* in = fopen(MsgFile,"rb");
+ FILE* out = fopen(OutFile,"wb+");
+
+#ifdef SHOWLOG
+ fprintf(FDLOG," outfile written %s \n",OutFile);
+#endif
+
+ fwrite(szSignature,1,strlen(szSignature),out);
+ fwrite("\r\n",1,2,out);
+
+ while (1) {
+ BufLen = fread(Buffer,1,sizeof(Buffer),in);
+ if (BufLen > 0) { fwrite(Buffer,1,BufLen,out); }
+ else { break; }
+ }
+ fclose(in);
+
+ }
+
+/** Now go for verification **/
+
+ else {
+ FILE* in = fopen(MsgFile,"rb");
+ if (in == NULL) {
+//#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open input file\n");
+//#endif
+ return 0; // bad option -- no CTX set up yet
+ }
+
+ DKIMVerifyOptions vopts = {0};
+ vopts.pfnSelectorCallback = NULL; //SelectorCallback;
+
+ n = DKIMVerifyInit(&ctxt,&vopts);
+
+ while (1) {
+ BufLen = fread(Buffer,1,sizeof(Buffer),in);
+ if (BufLen > 0) { DKIMVerifyProcess(&ctxt,Buffer,BufLen); }
+ else { break; }
+ }
+
+ n = DKIMVerifyResults(&ctxt);
+
+ int nSigCount = 0;
+ DKIMVerifyDetails* pDetails;
+ char szPolicy[512];
+
+ n = DKIMVerifyGetDetails(&ctxt,&nSigCount,&pDetails,szPolicy);
+
+ for (int i = 0; i < nSigCount; i++) {
+ const char s[] = "pass";
+ const char f[] = "fail";
+ const char* error = DKIM_ErrorResult(pDetails[i].nResult);
+ if (!bRes)
+ fprintf(FDLOG," Signature #%d: ",i + 1);
+ if (pDetails[i].nResult >= 0 ) {
+ if (bRes) {
+ _DKIM_ReportResult(OutFile,s,0);
+ } else
+ printf(" Pass\n");
+ } else { // fail
+ if (bRes) {
+ _DKIM_ReportResult(OutFile,f,error);
+ } else
+ printf(" Fail %s \n",error);
+ }
+ }
+ DKIMVerifyFree(&ctxt);
+ }
+ return 0;
+}
diff --git a/src/qmail-dksign.c b/src/qmail-dksign.c
new file mode 100644
index 0000000..5135cd4
--- /dev/null
+++ b/src/qmail-dksign.c
@@ -0,0 +1,511 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include "sig.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "error.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "str.h"
+#include "exit.h"
+#include "case.h"
+#include "constmap.h"
+#include "uint_t.h"
+#include "fd.h"
+#include "logmsg.h"
+#include "open.h"
+#include "fmt.h"
+#include "fmtqfn.h"
+#include "readwrite.h"
+#include "qmail.h"
+#include "wait.h"
+#include "pathexec.h"
+#include "rcpthosts.h"
+
+#define WHO "qmail-dksign"
+
+#define DOMAINKEYS "ssl/domainkeys/"
+
+/** @file qmail-dksign.c -- generate signature and attach in DKIM header to outgoing message
+
+ Steps:
+ ------
+ a) DKIM controls: get private key for sending domain
+ b) Prepare two staging files at queue/dkim (before and after signing)
+ c) Read input at fd0 and insert CR for every line and store at dkim/x/pre
+ d) DKIM sign the message with provided private key and store at dkim/y/post
+ e) Copy signed file from fd to 0
+ f) Invoke qmail-remote (respecting the \r\n)
+ g) Remove staging files (pre/post)
+
+ Hack for hybrid signatures:
+ ---------------------------
+
+ a) selector is a link to RSA private key
+ b) selector2 is a link to Ed25519 private key
+ c) Both are provided in the 'selector' field of dkimdomains separated by colon
+ d) The coupled selector information is provided to qmail-dkim as: -yselector ,-Yselector2
+ e) The RSA privat key is given unaltered
+ f) The Ed25519 private is supplied as additional argument
+ */
+
+char bufin[1000]; // RFC 5322: 998 chars - why?
+buffer bi = BUFFER_INIT(read,0,bufin,sizeof(bufin));
+char bufout[1000];
+buffer bo = BUFFER_INIT(write,1,bufout,sizeof(bufout));
+
+void die(int e) { _exit(e); }
+void die_write(char *fn) { unlink(fn); die(53); };
+void die_read() { die(54); };
+void out(char *s) { if (buffer_puts(&bo,s) == -1) _exit(111); }
+void zero() { if (buffer_put(&bo,"\0",1) == -1) _exit(111); }
+void zerodie() { zero(); buffer_flush(&bo); _exit(111); }
+
+stralloc fndkin = {0};
+stralloc fndkout = {0};
+
+stralloc sender = {0}; // will be re-written
+stralloc senddomain = {0};
+stralloc originator = {0};
+stralloc dkimdomains = {0};
+struct constmap mapdkimdomains;
+
+stralloc ecckey = {0};
+stralloc rsakey = {0};
+char *dkimparams = 0;
+
+void temp_nomem()
+{
+ out("ZOut of memory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_chdir()
+{
+ out("ZUnable to switch to target directory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_create()
+{
+ out("ZUnable to create DKIM stage file: ");
+ out(error_str(errno));
+ out(fndkin.s); out(". (#4.3.0)\n");
+ zerodie();
+}
+void temp_unlink()
+{
+ out("ZUnable to unlink DKIM stage file. (#4.3.0)\n");
+ zerodie();
+}
+void temp_control()
+{
+ out("ZUnable to read DKIM control files. (#4.3.0)\n");
+ zerodie();
+}
+void perm_usage()
+{
+ out("Zqmail-dksign was invoked improperly. (#5.3.5)\n");
+ zerodie();
+}
+void temp_read()
+{
+ out("DUnable to read message for DKIM signing. (#4.3.0)\n");
+ zerodie();
+}
+void temp_nosignkey()
+{
+ out("DCan't read sign key: ");
+ out(rsakey.s);
+ out(" or ");
+ out(ecckey.s);
+ out(". (#4.3.0)\n");
+ zerodie();
+}
+
+int get_controls()
+{
+ int i;
+ stralloc domname = {0};
+
+ if (control_init() == -1) temp_control();
+
+ switch (control_readfile(&dkimdomains,"control/dkimdomains",0)) {
+ case -1: return 0;
+ case 0: if (!constmap_init(&mapdkimdomains,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapdkimdomains,dkimdomains.s,dkimdomains.len,1)) temp_nomem(); break;
+ }
+
+/* Check for disabled DKIM send domains */
+
+ if (!stralloc_copys(&domname,"!")) temp_nomem();
+ if (!stralloc_cats(&domname,senddomain.s)) temp_nomem();
+ if (constmap(&mapdkimdomains,domname.s,domname.len)) return 0;
+
+/* Parenting domains; senddomain 0-terminated; lowercase */
+
+ for (i = 0; i <= senddomain.len; ++i) {
+ if ((i == 0) || (senddomain.s[i] == '.'))
+ if ((dkimparams = constmap(&mapdkimdomains,senddomain.s + i,senddomain.len - i - 1))) {
+ if (!stralloc_copys(&sender,senddomain.s + i)) temp_nomem();
+ if (!stralloc_0(&sender)) temp_nomem();
+ return 3;
+ }
+ }
+
+/* We sign only senddomains we take responsibility for: rcpthosts */
+
+ if ((dkimparams = constmap(&mapdkimdomains,"=",1))) {
+ if (rcpthosts_init() == -1) temp_control();
+ if (rcpthosts(originator.s,originator.len)) {
+ if ((control_readline(&sender,"control/defaultdomain") != 1))
+ if (control_readline(&sender,"control/me") == -1) temp_control();
+ if (!stralloc_0(&sender)) temp_nomem();
+ return 2;
+ }
+ }
+
+/* Default settings for MTA: 'defaultdomain' or even 'me' */
+
+ if ((dkimparams = constmap(&mapdkimdomains,"*",1))) {
+ if ((control_readline(&sender,"control/defaultdomain") != 1))
+ if (control_readline(&sender,"control/me") == -1) temp_control();
+ if (!stralloc_0(&sender)) temp_nomem();
+ return 1;
+ }
+
+ return 0;
+}
+
+void fnmake_dkim(unsigned long id)
+{
+ fndkin.len = fmtqfn(fndkin.s,"queue/dkim/",id,1);
+ id += id;
+ fndkout.len = fmtqfn(fndkout.s,"queue/dkim/",id,1);
+}
+
+void dkim_unlink()
+{
+ if (unlink(fndkin.s) == -1)
+ if (errno != ENOENT) temp_unlink();
+ if (unlink(fndkout.s) == -1)
+ if (errno != ENOENT) temp_unlink();
+}
+
+void dkim_stage()
+{
+ int r;
+ int fd;
+ char ch;
+ struct stat st;
+
+ if (!stralloc_ready(&fndkin,FMTQFN)) temp_nomem();
+ if (!stralloc_ready(&fndkout,FMTQFN)) temp_nomem();
+
+ fnmake_dkim(getpid()); // pre-staging
+ dkim_unlink(); // duplicate, left over file
+ fd = open_excl(fndkin.s);
+ if (fd == -1) die_write(fndkin.s);
+
+ buffer_init(&bi,read,0,bufin,sizeof(bufin));
+ buffer_init(&bo,write,fd,bufout,sizeof(bufout));
+
+ for (;;) {
+ r = buffer_get(&bi,&ch,1);
+ if (r == 0) break;
+ if (r == -1) temp_read();
+ if (ch == '\r') continue;
+
+ while (ch != '\n') {
+ buffer_put(&bo,&ch,1);
+ r = buffer_get(&bi,&ch,1);
+ if (r == -1) temp_read();
+ }
+ buffer_put(&bo,"\r\n",2);
+ }
+
+ if (buffer_flush(&bo) == -1) die(51);
+ if (fstat(fd,&st) == -1) die_read();
+ if (fsync(fd) == -1) die_write(fndkin.s);
+ if (close(fd) == -1) die_write(fndkin.s);
+}
+
+/* to construct DKIM information */
+
+stralloc selector = {0};
+stralloc selectore = {0};
+stralloc sdid = {0};
+stralloc auid = {0};
+stralloc expire = {0};
+stralloc canon = {0}; // -c r = relax, s = simple, t = relaxed/simple, u = simple/realxed
+stralloc hash = {0}; // -z 1/2/3/4/5 sha1/sha2/both/ed25519/ed25519+rsa-sha256
+stralloc length = {0}; // -l
+
+/**
+
+ qmail-dkim [-h|-v|-s] [tags] <msgfile> [<RSAkeyfile> <outfile> <Ed25519keyfile>]
+ --------------------------------------------------------------------------------
+ tags:
+ ----
+ -c<canonicalization> - r=relaxed [DEFAULT], s=simple, t=relaxed/simple, u=simple/relaxed
+ -d<sdid> - Signing Domain Identifier,if not provided it will be determined from the envelope originator/from header
+ -i<auid> - Agent User Identifier, usually the sender's email address (optional)
+ -l - include body length tag (optional)
+ -q - include query method tag
+ -t - include a timestamp tag (optional)
+ -x<expire_time> - the expire time in seconds since epoch (optional, DEFAULT = current time + 604800)
+ -y<selector> - set RSA selector (DEFAULT: default)
+ -Y<selector> - set Ed25519 selector (DEFAULT: default)
+ -z<hash> - set signature type (1=sha1, 2=sha256, 3=both, 4=ed25519, 5=hybrid)
+*/
+
+int dkim_sign(const char *rsakeyfile,const char *ecckeyfile,const char *fnin,const char *fnout)
+{
+ int child;
+ int wstat;
+ char *(args[17]);
+ int i = 0;
+
+ args[i] = "qmail-dkim"; ++i;
+ args[i] = "-s"; ++i;
+ args[i] = "-q"; ++i;
+ if (sdid.len > 3) { args[i] = sdid.s; ++i; }
+ if (selector.len > 3) { args[i] = selector.s; ++i; }
+ if (selectore.len > 3) { args[i] = selectore.s; ++i; }
+ if (auid.len > 3) { args[i] = auid.s; ++i; }
+ if (expire.len > 3) { args[i] = expire.s; ++i; }
+ if (canon.len > 2) { args[i] = canon.s; ++i; }
+ if (hash.len > 2) { args[i] = hash.s; ++i; }
+ if (length.len > 2) { args[i] = length.s; ++i; }
+ args[i] = fnin; ++i;
+ args[i] = rsakeyfile; ++i;
+ args[i] = fnout; ++i;
+ if (str_len(ecckeyfile) > 3) { args[i] = ecckeyfile; ++i; }
+ args[i] = 0;
+
+ if (!(child = vfork())) {
+ pathexec(args);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat)) return 1;
+
+ switch (wait_exitcode(wstat)) {
+ case 1: return 1;
+ default: return 0;
+ }
+}
+
+int qmail_remote(char **qargs,int fd)
+{
+ int child;
+ int wstat;
+ char *(args[5]);
+
+ args[0] = "qmail-remote";
+ args[1] = qargs[1];
+ args[2] = qargs[2];
+ args[3] = qargs[3];
+ args[4] = 0;
+
+ if (!(child = vfork())) {
+ if (fd) {
+ if (fd_move(0,fd) == -1) _exit(111);
+ if (fd_copy(2,1) == -1) _exit(111);
+ }
+ pathexec(args);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat)) return 1;
+
+ switch (wait_exitcode(wstat)) {
+ case 111: return 1;
+ default: return 0;
+ }
+}
+
+void dkim_setup()
+{
+ int c, i, j, k, l;
+ char *opt, *pos;
+
+ /* defaults: selector=default, IETF format, q=dns/txt, z=2, c=r */
+
+ if (!stralloc_copys(&sdid,"-d")) temp_nomem();
+ if (!stralloc_cat(&sdid,&sender)) temp_nomem();
+ if (!stralloc_0(&sdid)) temp_nomem();
+ if (!stralloc_copys(&selector,"-ydefault")) temp_nomem();
+ if (!stralloc_0(&selector)) temp_nomem();
+ if (!stralloc_copys(&selectore,"-Yeddy")) temp_nomem();
+ if (!stralloc_0(&selectore)) temp_nomem();
+ if (!stralloc_copys(&canon,"-cr")) temp_nomem();
+ if (!stralloc_0(&canon)) temp_nomem();
+ if (!stralloc_copys(&hash,"-z2")) temp_nomem();
+ if (!stralloc_0(&hash)) temp_nomem();
+
+ /* domain:selector,selectore|sdid|[auid|~]|expire|c:z:l; c=[r|s|t|u], z=[1,2,3,4,5], l=l */
+
+ if (dkimparams && *dkimparams) {
+ i = str_chr(dkimparams,'|');
+ pos = dkimparams + i;
+ if (*pos == '|' || *pos == '\0') { // selector
+ dkimparams[i] = '\0';
+ c = str_chr(dkimparams,','); // selectore=eddy
+ if (dkimparams[c] == ',') {
+ dkimparams[c] = '\0';
+ if (str_len(dkimparams + c + 1)) {
+ if (!stralloc_copys(&selectore,"-Y")) temp_nomem();
+ if (!stralloc_cats(&selectore,dkimparams + c + 1)) temp_nomem();
+ if (!stralloc_0(&selectore)) temp_nomem();
+ }
+ } else if (str_len(dkimparams)) { // selector=default
+ if (!stralloc_copys(&selector,"-y")) temp_nomem();
+ if (!stralloc_cats(&selector,dkimparams)) temp_nomem();
+ if (!stralloc_0(&selector)) temp_nomem();
+ }
+
+ j = str_chr(dkimparams + i + 1,'|');
+ pos = dkimparams + i + j + 1;
+ if (*pos == '|' || *pos == '\0') { // sdid; domain in DKIM header
+ dkimparams[i + j + 1] = '\0';
+ if (!stralloc_copys(&sdid,"-d")) temp_nomem();
+ if (!stralloc_cats(&sdid,dkimparams + i + 1)) temp_nomem();
+ if (!stralloc_0(&sdid)) temp_nomem();
+
+ k = str_chr(dkimparams + i + j + 2,'|');
+ pos = dkimparams + i + j + k + 2;
+ if (*pos == '|' || *pos == '\0') { // auid = identifier
+ dkimparams[i + j + k + 2] = '\0';
+ if (!stralloc_copys(&auid,"-i")) temp_nomem();
+ if (dkimparams[i + j + 2] == '~') {
+ if (!stralloc_cat(&auid,&originator)) temp_nomem();
+ } else
+ if (!stralloc_cats(&auid,dkimparams + i + j + 2)) temp_nomem();
+
+ if (!stralloc_0(&auid)) temp_nomem();
+
+ l = str_chr(dkimparams + i + j + k + 3,'|');
+ pos = dkimparams + i + j + k + l + 3;
+ if (*pos == '|' || *pos == '\0') { // expire after n secs
+ dkimparams[i + j + k + l + 3] = '\0';
+ if (!stralloc_copys(&expire,"-x")) temp_nomem();
+ if (!stralloc_cats(&expire,dkimparams + i + j + k + 3)) temp_nomem();
+ if (!stralloc_0(&expire)) temp_nomem();
+
+ /* Options to follow */
+
+ opt = dkimparams + i + j + k + l + 4;
+ if (*opt == '\0') return;
+ if (*opt != ':') {
+ if (!stralloc_copys(&canon,"-c")) temp_nomem(); // canonicalization
+ if (!stralloc_catb(&canon,opt,1)) temp_nomem();
+ if (!stralloc_0(&canon)) temp_nomem();
+ ++opt; if (*opt == '\0') return; // next colon
+ }
+ if (*opt != ':' || *opt == '\0') return;
+ if (*opt == ':') ++opt;
+ if (*opt != ':') {
+ if (!stralloc_copys(&hash,"-z")) temp_nomem(); // hash
+ if (!stralloc_catb(&hash,opt,1)) temp_nomem();
+ if (!stralloc_0(&hash)) temp_nomem();
+ ++opt; if (*opt == '\0') return; // next colon
+ }
+ if (*opt != ':' || *opt == '\0') return;
+ if (*opt == ':') ++opt;
+ if (*opt != ':' && *opt == 'l') {
+ if (!stralloc_copys(&length,"-l")) temp_nomem(); // length
+ if (!stralloc_0(&length)) temp_nomem();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return;
+}
+
+int main(int argc,char **args)
+{
+ int i;
+ int fdin = 0; // initial read from FD 0
+ int nkey = 0;
+ char *(qargs[4]);
+ struct stat st;
+
+ qargs[0] = args[0];
+ qargs[1] = args[1]; // host
+ qargs[2] = args[2]; // originator
+ qargs[3] = args[3]; // recipient
+
+ umask(033);
+ sig_pipeignore();
+ if (argc < 4) perm_usage();
+ if (chdir(auto_qmail) == -1) temp_chdir();
+
+ if (str_len(args[2]) > 2) {
+ i = str_chr(args[2],'@');
+ if (*(args[2] + i) == '@')
+ if (!stralloc_copys(&senddomain,args[2] + i + 1)) temp_nomem();
+ }
+ if (!stralloc_0(&senddomain)) temp_nomem();
+ if (!stralloc_copys(&originator,args[2])) temp_nomem();
+
+ if (!get_controls()) {
+ qmail_remote(qargs,fdin);
+ _exit(0);
+ }
+
+ dkim_setup(); // sender is evaluated from originator (senddomain)
+
+ /* Setup keys: they are composed from selector */
+
+ case_lowerb(sender.s,sender.len); // needs to be lowercase
+ if (!stralloc_copys(&rsakey,DOMAINKEYS)) temp_nomem();
+ if (!stralloc_cats(&rsakey,sender.s)) temp_nomem();
+ if (!stralloc_cats(&rsakey,"/")) temp_nomem();
+
+ if (!stralloc_copys(&ecckey,DOMAINKEYS)) temp_nomem();
+ if (!stralloc_cats(&ecckey,sender.s)) temp_nomem();
+ if (!stralloc_cats(&ecckey,"/")) temp_nomem();
+
+ /* RSA key common for SHA1 and SHA256: rsakeyfile -> selector */
+
+ if (!stralloc_cats(&rsakey,selector.s + 2)) temp_nomem(); // -y prepended
+ if (!stralloc_0(&rsakey)) temp_nomem();
+ if (stat(rsakey.s,&st) != -1)
+ if (open_read(rsakey.s) > 0) ++nkey;
+
+ /* ECC key follows: ecckeyfile -> (,)selector2 */
+
+ if (!stralloc_cats(&ecckey,selectore.s + 2)) temp_nomem(); // -Y prepended
+ if (!stralloc_0(&ecckey)) temp_nomem();
+ if (stat(ecckey.s,&st) != -1)
+ if (open_read(ecckey.s) > 0) ++nkey;
+
+ /* We got keys - go for staging */
+
+ if (nkey) { // otherwise no key exists; why bother
+ dkim_stage();
+ if (!dkim_sign(rsakey.s,ecckey.s,fndkin.s,fndkout.s)) {
+ fdin = open_read(fndkout.s);
+ if (fdin == -1) die_read();
+ } else {
+ fdin = open_read(fndkin.s); // DKIM key failed to sign
+ if (fdin == -1) die_read();
+ }
+ } else
+ temp_nosignkey();
+
+ qmail_remote(qargs,fdin); // closes fdin
+ if (nkey) dkim_unlink();
+
+ _exit(0);
+}
diff --git a/src/qmail-dkverify.c b/src/qmail-dkverify.c
new file mode 100644
index 0000000..e607e08
--- /dev/null
+++ b/src/qmail-dkverify.c
@@ -0,0 +1,365 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include "sig.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "error.h"
+#include "auto_qmail.h"
+#include "str.h"
+#include "exit.h"
+#include "uint_t.h"
+#include "fd.h"
+#include "open.h"
+#include "fmt.h"
+#include "fmtqfn.h"
+#include "readwrite.h"
+#include "getln.h"
+#include "qmail.h"
+#include "wait.h"
+#include "byte.h"
+#include "case.h"
+#include "control.h"
+#include "pathexec.h"
+#include "env.h"
+#include "logmsg.h"
+
+#define WHO "qmail-dkverify"
+
+/** @file qmail-dkverify.c
+ @brief stub routine for DKIM signature verification and indication in received message
+
+ Steps:
+ ------
+ a) Store message with CRLF
+ b) Get DKIM signature from message - if given:
+ c) Call qmail-dkim for verification
+ d) Include results as appended header
+ e) Queue the message for processing
+
+ */
+
+char bufin[1024]; // RFC 5322: 998 chars - why?
+buffer bi = BUFFER_INIT(read,0,bufin,sizeof(bufin)); // read buffer
+char bufout[1024];
+buffer bo = BUFFER_INIT(write,1,bufout,sizeof(bufout)); // output message
+
+void die(int e) { _exit(e); }
+void die_pipe(char *fn) { unlink(fn); die(53); };
+void die_write(char *fn) { unlink(fn); die(53); };
+void die_read() { die(54); };
+void out(char *s) { if (buffer_puts(&bo,s) == -1) _exit(111); }
+void zero() { if (buffer_put(&bo,"\0",1) == -1) _exit(111); }
+void zerodie() { zero(); buffer_flush(&bo); _exit(111); }
+
+void temp_nomem()
+{
+ out("ZOut of memory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_chdir()
+{
+ out("ZUnable to switch to target directory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_create()
+{
+ out("ZUnable to create DKIM stage file. (#4.3.0)\n");
+ zerodie();
+}
+void temp_unlink()
+{
+ out("ZUnable to unlink DKIM stage file. (#4.3.0)\n");
+ zerodie();
+}
+void temp_read()
+{
+ out("ZUnable to read message. (#4.3.0)\n");
+ zerodie();
+}
+void temp_socket()
+{
+ out("ZUnable to crate socket pair. (#4.3.0)\n");
+ zerodie();
+}
+void temp_control()
+{
+ out("ZUnable to read control files. (#4.3.0)\n");
+ zerodie();
+}
+
+static stralloc me = {0};
+static stralloc senddomain = {0};
+static stralloc dkheader = {0};
+static stralloc fndkin = {0};
+static stralloc fndkout = {0};
+static stralloc result = {0};
+
+void fnmake_dkim(unsigned long id)
+{
+ fndkin.len = fmtqfn(fndkin.s,"queue/dkim/",id,1);
+ id += id;
+ fndkout.len = fmtqfn(fndkout.s,"queue/dkim/",id,1);
+}
+
+void dkim_stage()
+{
+ int r;
+ int fd;
+ char ch;
+ struct stat st;
+
+ if (!stralloc_ready(&fndkin,FMTQFN)) temp_nomem();
+ if (!stralloc_ready(&fndkout,FMTQFN)) temp_nomem();
+
+ fnmake_dkim(getpid()); // pre-staging
+ fd = open_excl(fndkin.s);
+ if (fd == -1) die_write(fndkin.s);
+
+ buffer_init(&bi,read,0,bufin,sizeof(bufin));
+ buffer_init(&bo,write,fd,bufout,sizeof(bufout));
+
+ for (int i = 0;;) {
+ r = buffer_get(&bi,&ch,1);
+ if (r == 0) break;
+ if (r == -1) temp_read();
+
+ while (ch != '\n') {
+ if (ch != '\r') buffer_put(&bo,&ch,1);
+ r = buffer_get(&bi,&ch,1);
+ if (r == -1) temp_read();
+ i++;
+ }
+ buffer_put(&bo,"\r\n",2);
+ }
+
+ if (buffer_flush(&bo) == -1) die(51);
+ if (fstat(fd,&st) == -1) die_write(fndkin.s);
+ if (fsync(fd) == -1) die_write(fndkin.s);
+ if (close(fd) == -1) die_write(fndkin.s);
+}
+
+int mess_dkim()
+{
+ stralloc line = {0};
+ int match;
+ int fd;
+ int at = 0;
+ int ket = 0;
+ int end = 0;
+ int len = 0;
+ int r = 0;
+ int i;
+
+ fd = open_read(fndkin.s);
+ if (fd == -1) die_read();
+ buffer_init(&bi,read,fd,bufin,sizeof(bufin));
+
+ if (!stralloc_copys(&senddomain,"")) temp_nomem();
+
+ for (;;) {
+ if (getln(&bi,&line,&match,'\n') == -1) temp_read();
+ if (case_starts(line.s,"DKIM-Signature: ")) r = 1;
+ if (r == 1) {
+ if (case_starts(line.s,"From: ")) { // fallback: From
+ at = str_chr(line.s,'@');
+ if (at < line.len) {
+ end = str_chr(line.s,'\n'); // From: user@senddomain\n
+ ket = str_chr(line.s,'>'); // From: User <user@senddomain>
+ len = (ket < end) ? ket : end;
+ len -= at - 1;
+ if (len) {
+ if (!stralloc_copyb(&senddomain,line.s + at + 1,len)) temp_nomem();
+ } else
+ if (!stralloc_copys(&senddomain,"uknown")) temp_nomem();
+ r = 2;
+ }
+ }
+ for (i = 0; i < line.len; ++i) { // d=domain.tld
+ if (*(line.s + i) == '=' && *(line.s + i - 1) == 'd') {
+ ++i; // gotcha
+ while (*(line.s + i) != ';') {
+ if (!stralloc_catb(&senddomain,line.s + i,1)) temp_nomem();
+ i++;
+ r = 3;
+ }
+ }
+ }
+ }
+ if (r >= 2 || !match) break;
+ }
+ if (senddomain.len < 2)
+ if (!stralloc_copys(&senddomain,"unknown")) temp_nomem();
+ if (!stralloc_0(&senddomain)) temp_nomem();
+
+ return r;
+}
+
+int dkim_verify()
+{
+ int child;
+ int wstat;
+ char *(args[6]);
+ int r = -1;
+
+ args[0] = "qmail-dkim";
+ args[1] = "-V";
+ args[2] = fndkin.s;
+ args[3] = "none";
+ args[4] = fndkout.s;
+ args[5] = 0;
+
+ if (!(child = fork())) {
+ pathexec(args);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat)) return 1;
+
+ switch (r = wait_exitcode(wstat)) {
+ case 10: return 1;
+ default: return 0;
+ }
+}
+
+int dkim_result(const char *me)
+{
+ int max = 64;
+ int fd;
+ int j;
+ char ch;
+ int r = 0;
+
+ if (!stralloc_copys(&result,"")) temp_nomem();
+
+ if ((fd = open_read(fndkout.s)) == -1) return 0; // nothing to read
+ while ((r = read(fd,bufin,sizeof(bufin))) > 0)
+ if (!stralloc_catb(&result,bufin,r)) temp_nomem();
+
+ if (!stralloc_0(&result)) temp_nomem();
+
+ if (result.len > 2) {
+ if (case_starts(result.s,"pass")) r = 0;
+ if (case_starts(result.s,"fail")) r = 35;
+ } else
+ if (!stralloc_copys(&result,"unknown")) temp_nomem();
+
+ if (!stralloc_copys(&dkheader,"X-Authentication-Results: ")) temp_nomem();
+ if (!stralloc_cats(&dkheader,senddomain.s)) temp_nomem();
+ if (!stralloc_cats(&dkheader,"; dkim=")) temp_nomem();
+
+ for (j = 0; j < result.len; j++) {
+ ch = result.s[j];
+ if (ch == '\r' || ch == '\n' || ch == '\0') continue;
+ if (j <= max) if (!stralloc_catb(&dkheader,&ch,1)) temp_nomem();
+ if (ch == ' ' && (j > max)) {
+ if (!stralloc_cats(&dkheader,"\n ")) temp_nomem();
+ max += max;
+ }
+ }
+
+ if (!stralloc_cats(&dkheader,"; ")) temp_nomem();
+ if (!stralloc_cats(&dkheader,me)) temp_nomem();
+ if (!stralloc_0(&dkheader)) temp_nomem();
+
+ return r;
+}
+
+int qmail_queue()
+{
+ int fd;
+ int r;
+ int child;
+ int wstat;
+ int pi[2];
+ char *(args[2]);
+ char ch;
+
+ if (pipe(pi) == -1) die_pipe(fndkin.s);
+
+ args[0] = "qmail-queue";
+ args[1] = 0;
+
+ switch (child = vfork()) {
+ case -1:
+ close(pi[0]); close(pi[1]);
+ die_write(fndkin.s);
+ case 0:
+ close(pi[1]);
+ if (fd_move(0,pi[0]) == -1) die_pipe(fndkin.s);
+ sig_pipedefault();
+ pathexec(args);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+ close(pi[0]);
+
+ buffer_init(&bo,write,pi[1],bufout,sizeof(bufout));
+
+ if (dkheader.len > 2) { // write DKIM header
+ if (buffer_put(&bo,dkheader.s,dkheader.len - 1) == -1) die_write(fndkout.s);
+ if (buffer_put(&bo,"\n",1) == -1) die_write(fndkout.s);
+ if (buffer_flush(&bo) == -1) die_write(fndkout.s);
+ }
+
+ /* read/write message byte-by-byte; we need to remove the CR (inefficient) */
+
+ if ((fd = open_read(fndkin.s)) == -1) die_read();
+ while ((r = read(fd,&ch,1)) > 0)
+ if (ch != '\r')
+ if (buffer_put(&bo,&ch,1) == -1) die_write(fndkin.s);
+
+ if (buffer_flush(&bo) == -1) die_write(fndkin.s);
+ close(pi[1]);
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat)) return 1;
+
+ switch (r = wait_exitcode(wstat)) {
+ case 10: return 1;
+ default: return 0;
+ }
+
+ return 0;
+}
+
+void dkim_unlink()
+{
+ if (unlink(fndkin.s) == -1)
+ if (errno != ENOENT) temp_unlink();
+ if (unlink(fndkout.s) == -1)
+ if (errno != ENOENT) temp_unlink();
+}
+
+int main()
+{
+ int r = 0;
+ char *mode = 0;
+
+ umask(033);
+ if (chdir(auto_qmail) == -1) temp_chdir();
+ if (control_init() == -1) temp_control();
+ if (control_readline(&me,"control/me") == -1) temp_control();
+ if (!stralloc_0(&me)) temp_nomem();
+
+ dkim_stage();
+
+ if (mess_dkim()) {
+ dkim_verify();
+ r = dkim_result(me.s);
+ }
+
+ /* we are done: call qmail-queue */
+
+ mode = env_get("DKIM");
+ if (!mode || *mode != '+') r = 0;
+
+ qmail_queue();
+ dkim_unlink();
+
+ _exit(r);
+}
diff --git a/src/qmail-getpw.c b/src/qmail-getpw.c
new file mode 100644
index 0000000..f801c3c
--- /dev/null
+++ b/src/qmail-getpw.c
@@ -0,0 +1,85 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <unistd.h>
+#include "error.h"
+#include "buffer.h"
+#include "exit.h"
+#include "byte.h"
+#include "str.h"
+#include "case.h"
+#include "fmt.h"
+#include "auto_usera.h"
+#include "auto_break.h"
+#include "qlx.h"
+
+#define GETPW_USERLEN 32
+
+char *local;
+struct passwd *pw;
+char *dash;
+char *extension;
+
+int userext()
+{
+ char username[GETPW_USERLEN];
+ struct stat st;
+
+ extension = local + str_len(local);
+ for (;;) {
+ if (extension - local < sizeof(username))
+ if (!*extension || (*extension == *auto_break)) {
+ byte_copy(username,extension - local,local);
+ username[extension - local] = 0;
+ case_lowers(username);
+ errno = 0;
+ pw = getpwnam(username);
+ if (errno == ETXTBSY) _exit(QLX_SYS);
+ if (pw)
+ if (pw->pw_uid)
+ if (stat(pw->pw_dir,&st) == 0) {
+ if (st.st_uid == pw->pw_uid) {
+ dash = "";
+ if (*extension) { ++extension; dash = "-"; }
+ return 1;
+ }
+ } else {
+ if (errno) _exit(QLX_NFS);
+ }
+ }
+ if (extension == local) return 0;
+ --extension;
+ }
+}
+
+char num[FMT_ULONG];
+
+int main(int argc,char **argv)
+{
+ local = argv[1];
+ if (!local) _exit(100);
+
+ if (!userext()) {
+ extension = local;
+ dash = "-";
+ pw = getpwnam(auto_usera);
+ }
+
+ if (!pw) _exit(QLX_NOALIAS);
+
+ buffer_puts(buffer_1small,pw->pw_name);
+ buffer_put(buffer_1small,"",1);
+ buffer_put(buffer_1small,num,fmt_ulong(num,(long) pw->pw_uid));
+ buffer_put(buffer_1small,"",1);
+ buffer_put(buffer_1small,num,fmt_ulong(num,(long) pw->pw_gid));
+ buffer_put(buffer_1small,"",1);
+ buffer_puts(buffer_1small,pw->pw_dir);
+ buffer_put(buffer_1small,"",1);
+ buffer_puts(buffer_1small,dash);
+ buffer_put(buffer_1small,"",1);
+ buffer_puts(buffer_1small,extension);
+ buffer_put(buffer_1small,"",1);
+ buffer_flush(buffer_1small);
+
+ _exit(0);
+}
diff --git a/src/qmail-inject.c b/src/qmail-inject.c
new file mode 100644
index 0000000..0071316
--- /dev/null
+++ b/src/qmail-inject.c
@@ -0,0 +1,793 @@
+#include <unistd.h>
+#include "sig.h"
+#include "buffer.h"
+#include "genalloc.h"
+#include "stralloc.h"
+#include "getoptb.h"
+#include "getln.h"
+#include "alloc.h"
+#include "str.h"
+#include "fmt.h"
+#include "hfield.h"
+#include "token822.h"
+#include "control.h"
+#include "env.h"
+#include "qmail.h"
+#include "now.h"
+#include "exit.h"
+#include "error.h"
+#include "quote.h"
+#include "headerbody.h"
+#include "auto_qmail.h"
+#include "newfield.h"
+#include "constmap.h"
+
+#define LINELEN 80
+
+datetime_sec starttime;
+
+char *qmopts;
+int flagdeletesender = 0;
+int flagdeletefrom = 0;
+int flagdeletemessid = 0;
+int flagnamecomment = 0;
+int flaghackmess = 0;
+int flaghackrecip = 0;
+char *mailhost;
+char *mailuser;
+int mailusertokentype;
+char *mailrhost;
+char *mailruser;
+
+stralloc control_idhost = {0};
+stralloc control_defaultdomain = {0};
+stralloc control_defaulthost = {0};
+stralloc control_plusdomain = {0};
+
+stralloc sender = {0};
+stralloc envsbuf = {0};
+token822_alloc envs = {0};
+int flagrh;
+
+int flagqueue;
+struct qmail qqt;
+
+void out(char *s,int len)
+{
+ if (flagqueue) qmail_put(&qqt,s,len);
+ else buffer_putflush(buffer_1,s,len);
+}
+
+void outs(char *s) { out(s,str_len(s)); }
+
+void perm() { _exit(100); }
+void temp() { _exit(111); }
+
+void die_nomem()
+{
+ buffer_putsflush(buffer_2,"qmail-inject: fatal: out of memory\n");
+ temp();
+}
+void die_invalid(stralloc *sa)
+{
+ buffer_putsflush(buffer_2,"qmail-inject: fatal: invalid header field: ");
+ buffer_putflush(buffer_2,sa->s,sa->len);
+ perm();
+}
+void die_qqt()
+{
+ buffer_putsflush(buffer_2,"qmail-inject: fatal: unable to run qmail-queue\n");
+ temp();
+}
+void die_chdir()
+{
+ buffer_putsflush(buffer_2,"qmail-inject: fatal: internal bug\n");
+ temp();
+}
+void die_read()
+{
+ if (errno == ENOMEM) die_nomem();
+ buffer_putsflush(buffer_2,"qmail-inject: fatal: read error\n");
+ temp();
+}
+void doordie(stralloc *sa,int r)
+{
+ if (r == 1) return;
+ if (r == -1) die_nomem();
+ buffer_putsflush(buffer_2,"qmail-inject: fatal: unable to parse this line:\n");
+ buffer_putflush(buffer_2,sa->s,sa->len);
+ perm();
+}
+
+GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
+GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
+
+static stralloc sauninit = {0};
+
+saa savedh = {0};
+saa hrlist = {0};
+saa tocclist = {0};
+saa hrrlist = {0};
+saa reciplist = {0};
+int flagresent;
+
+void exitnicely()
+{
+ char *qqx;
+
+ if (!flagqueue) buffer_flush(buffer_1);
+
+ if (flagqueue) {
+ int i;
+
+ if (!stralloc_0(&sender)) die_nomem();
+ qmail_from(&qqt,sender.s);
+
+ for (i = 0; i < reciplist.len; ++i) {
+ if (!stralloc_0(&reciplist.sa[i])) die_nomem();
+ qmail_to(&qqt,reciplist.sa[i].s);
+ }
+ if (flagrh) {
+ if (flagresent) {
+ for (i = 0; i < hrrlist.len; ++i) {
+ if (!stralloc_0(&hrrlist.sa[i])) die_nomem();
+ qmail_to(&qqt,hrrlist.sa[i].s);
+ }
+ } else {
+ for (i = 0; i < hrlist.len; ++i) {
+ if (!stralloc_0(&hrlist.sa[i])) die_nomem();
+ qmail_to(&qqt,hrlist.sa[i].s);
+ }
+ }
+ }
+
+ qqx = qmail_close(&qqt);
+ if (*qqx) {
+ if (*qqx == 'D') {
+ buffer_puts(buffer_2,"qmail-inject: fatal: ");
+ buffer_puts(buffer_2,qqx + 1);
+ buffer_puts(buffer_2,"\n");
+ buffer_flush(buffer_2);
+ perm();
+ } else {
+ buffer_puts(buffer_2,"qmail-inject: fatal: ");
+ buffer_puts(buffer_2,qqx + 1);
+ buffer_puts(buffer_2,"\n");
+ buffer_flush(buffer_2);
+ temp();
+ }
+ }
+ }
+
+ _exit(0);
+}
+
+void savedh_append(stralloc *h)
+{
+ if (!saa_readyplus(&savedh,1)) die_nomem();
+ savedh.sa[savedh.len] = sauninit;
+ if (!stralloc_copy(savedh.sa + savedh.len,h)) die_nomem();
+ ++savedh.len;
+}
+
+void savedh_print()
+{
+ int i;
+
+ for (i = 0; i < savedh.len; ++i)
+ out(savedh.sa[i].s,savedh.sa[i].len);
+}
+
+stralloc defaultdomainbuf = {0};
+token822_alloc defaultdomain = {0};
+stralloc defaulthostbuf = {0};
+token822_alloc defaulthost = {0};
+stralloc plusdomainbuf = {0};
+token822_alloc plusdomain = {0};
+
+void rwroute(token822_alloc *addr)
+{
+ if (addr->t[addr->len - 1].type == TOKEN822_AT)
+ while (addr->len)
+ if (addr->t[--addr->len].type == TOKEN822_COLON)
+ return;
+}
+
+void rwextraat(token822_alloc *addr)
+{
+ int i;
+
+ if (addr->t[0].type == TOKEN822_AT) {
+ --addr->len;
+ for (i = 0; i < addr->len; ++i)
+ addr->t[i] = addr->t[i + 1];
+ }
+}
+
+void rwextradot(token822_alloc *addr)
+{
+ int i;
+
+ if (addr->t[0].type == TOKEN822_DOT) {
+ --addr->len;
+ for (i = 0; i < addr->len; ++i)
+ addr->t[i] = addr->t[i + 1];
+ }
+}
+
+void rwnoat(token822_alloc *addr)
+{
+ int i;
+ int shift;
+
+ for (i = 0; i < addr->len; ++i)
+ if (addr->t[i].type == TOKEN822_AT)
+ return;
+ shift = defaulthost.len;
+ if (!token822_readyplus(addr,shift)) die_nomem();
+ for (i = addr->len - 1; i >= 0; --i)
+ addr->t[i + shift] = addr->t[i];
+
+ addr->len += shift;
+
+ for (i = 0; i < shift; ++i)
+ addr->t[i] = defaulthost.t[shift - 1 - i];
+}
+
+void rwnodot(token822_alloc *addr)
+{
+ int i;
+ int shift;
+
+ for (i = 0; i < addr->len; ++i) {
+ if (addr->t[i].type == TOKEN822_DOT)
+ return;
+ if (addr->t[i].type == TOKEN822_AT)
+ break;
+ }
+ for (i = 0; i < addr->len; ++i) {
+ if (addr->t[i].type == TOKEN822_LITERAL)
+ return;
+ if (addr->t[i].type == TOKEN822_AT)
+ break;
+ }
+ shift = defaultdomain.len;
+ if (!token822_readyplus(addr,shift)) die_nomem();
+
+ for (i = addr->len - 1; i >= 0; --i)
+ addr->t[i + shift] = addr->t[i];
+
+ addr->len += shift;
+
+ for (i = 0; i < shift; ++i)
+ addr->t[i] = defaultdomain.t[shift - 1 - i];
+}
+
+void rwplus(token822_alloc *addr)
+{
+ int i;
+ int shift;
+
+ if (addr->t[0].type != TOKEN822_ATOM) return;
+ if (!addr->t[0].slen) return;
+ if (addr->t[0].s[addr->t[0].slen - 1] != '+') return;
+
+ --addr->t[0].slen; /* remove + */
+
+ shift = plusdomain.len;
+ if (!token822_readyplus(addr,shift)) die_nomem();
+
+ for (i = addr->len - 1; i >= 0; --i)
+ addr->t[i + shift] = addr->t[i];
+
+ addr->len += shift;
+
+ for (i = 0; i < shift; ++i)
+ addr->t[i] = plusdomain.t[shift - 1 - i];
+}
+
+void rwgeneric(token822_alloc *addr)
+{
+ if (!addr->len) return; /* don't rewrite <> */
+ if (addr->len >= 2)
+ if (addr->t[1].type == TOKEN822_AT)
+ if (addr->t[0].type == TOKEN822_LITERAL)
+ if (!addr->t[0].slen) /* don't rewrite <foo@[]> */
+ return;
+
+ rwroute(addr);
+ if (!addr->len) return; /* <@foo:> -> <> */
+ rwextradot(addr);
+ if (!addr->len) return; /* <.> -> <> */
+ rwextraat(addr);
+ if (!addr->len) return; /* <@> -> <> */
+ rwnoat(addr);
+ rwplus(addr);
+ rwnodot(addr);
+}
+
+int setreturn(token822_alloc *addr)
+{
+ if (!sender.s) {
+ token822_reverse(addr);
+ if (token822_unquote(&sender,addr) != 1) die_nomem();
+ if (flaghackrecip)
+ if (!stralloc_cats(&sender,"-@[]")) die_nomem();
+ token822_reverse(addr);
+ }
+ return 1;
+}
+
+int rwreturn(token822_alloc *addr)
+{
+ rwgeneric(addr);
+ setreturn(addr);
+ return 1;
+}
+
+int rwsender(token822_alloc *addr)
+{
+ rwgeneric(addr);
+ return 1;
+}
+
+void rwappend(token822_alloc *addr,saa *xl)
+{
+ token822_reverse(addr);
+ if (!saa_readyplus(xl,1)) die_nomem();
+ xl->sa[xl->len] = sauninit;
+ if (token822_unquote(&xl->sa[xl->len],addr) != 1) die_nomem();
+ ++xl->len;
+ token822_reverse(addr);
+}
+
+int rwhrr(token822_alloc *addr)
+{
+ rwgeneric(addr);
+ rwappend(addr,&hrrlist);
+ return 1;
+}
+
+int rwhr(token822_alloc *addr)
+{
+ rwgeneric(addr);
+ rwappend(addr,&hrlist);
+ return 1;
+}
+
+int rwtocc(token822_alloc *addr)
+{
+ rwgeneric(addr);
+ rwappend(addr,&hrlist);
+ rwappend(addr,&tocclist);
+ return 1;
+}
+
+int htypeseen[H_NUM];
+stralloc hfbuf = {0};
+token822_alloc hfin = {0};
+token822_alloc hfrewrite = {0};
+token822_alloc hfaddr = {0};
+
+void doheaderfield(stralloc *h)
+{
+ int htype;
+ int (*rw)() = 0;
+
+ htype = hfield_known(h->s,h->len);
+ if (flagdeletefrom) if (htype == H_FROM) return;
+ if (flagdeletemessid) if (htype == H_MESSAGEID) return;
+ if (flagdeletesender) if (htype == H_RETURNPATH) return;
+
+ if (htype)
+ htypeseen[htype] = 1;
+ else
+ if (!hfield_valid(h->s,h->len))
+ die_invalid(h);
+
+ switch (htype) {
+ case H_TO: case H_CC:
+ rw = rwtocc; break;
+ case H_BCC: case H_APPARENTLYTO:
+ rw = rwhr; break;
+ case H_R_TO: case H_R_CC: case H_R_BCC:
+ rw = rwhrr; break;
+ case H_RETURNPATH:
+ rw = rwreturn; break;
+ case H_SENDER: case H_FROM: case H_REPLYTO:
+ case H_RETURNRECEIPTTO: case H_ERRORSTO:
+ case H_R_SENDER: case H_R_FROM: case H_R_REPLYTO:
+ rw = rwsender; break;
+ }
+
+ if (rw) {
+ doordie(h,token822_parse(&hfin,h,&hfbuf));
+ doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rw));
+ if (token822_unparse(h,&hfrewrite,LINELEN) != 1)
+ die_nomem();
+ }
+
+ if (htype == H_BCC) return;
+ if (htype == H_R_BCC) return;
+ if (htype == H_RETURNPATH) return;
+ if (htype == H_CONTENTLENGTH) return; /* some things are just too stupid */
+ savedh_append(h);
+}
+
+void dobody(stralloc *h)
+{
+ out(h->s,h->len);
+}
+
+stralloc torecip = {0};
+token822_alloc tr = {0};
+
+void dorecip(char *s)
+{
+ if (!quote2(&torecip,s)) die_nomem();
+
+ switch (token822_parse(&tr,&torecip,&hfbuf)) {
+ case -1: die_nomem();
+ case 0:
+ buffer_puts(buffer_2,"qmail-inject: fatal: unable to parse address: ");
+ buffer_puts(buffer_2,s);
+ buffer_putsflush(buffer_2,"\n");
+ perm();
+ }
+ token822_reverse(&tr);
+ rwgeneric(&tr);
+ rwappend(&tr,&reciplist);
+}
+
+stralloc defaultfrom = {0};
+token822_alloc df = {0};
+
+void defaultfrommake()
+{
+ char *fullname;
+ fullname = env_get("QMAILNAME");
+ if (!fullname) fullname = env_get("MAILNAME");
+ if (!fullname) fullname = env_get("NAME");
+ if (!token822_ready(&df,20)) die_nomem();
+
+ df.len = 0;
+ df.t[df.len].type = TOKEN822_ATOM;
+ df.t[df.len].s = "From";
+ df.t[df.len].slen = 4;
+ ++df.len;
+ df.t[df.len].type = TOKEN822_COLON;
+ ++df.len;
+
+ if (fullname && !flagnamecomment) {
+ df.t[df.len].type = TOKEN822_QUOTE;
+ df.t[df.len].s = fullname;
+ df.t[df.len].slen = str_len(fullname);
+ ++df.len;
+ df.t[df.len].type = TOKEN822_LEFT;
+ ++df.len;
+ }
+
+ df.t[df.len].type = mailusertokentype;
+ df.t[df.len].s = mailuser;
+ df.t[df.len].slen = str_len(mailuser);
+ ++df.len;
+
+ if (mailhost) {
+ df.t[df.len].type = TOKEN822_AT;
+ ++df.len;
+ df.t[df.len].type = TOKEN822_ATOM;
+ df.t[df.len].s = mailhost;
+ df.t[df.len].slen = str_len(mailhost);
+ ++df.len;
+ }
+
+ if (fullname && !flagnamecomment) {
+ df.t[df.len].type = TOKEN822_RIGHT;
+ ++df.len;
+ }
+
+ if (fullname && flagnamecomment) {
+ df.t[df.len].type = TOKEN822_COMMENT;
+ df.t[df.len].s = fullname;
+ df.t[df.len].slen = str_len(fullname);
+ ++df.len;
+ }
+
+ if (token822_unparse(&defaultfrom,&df,LINELEN) != 1) die_nomem();
+ doordie(&defaultfrom,token822_parse(&df,&defaultfrom,&hfbuf));
+ doordie(&defaultfrom,token822_addrlist(&hfrewrite,&hfaddr,&df,rwsender));
+ if (token822_unparse(&defaultfrom,&hfrewrite,LINELEN) != 1) die_nomem();
+}
+
+stralloc defaultreturnpath = {0};
+token822_alloc drp = {0};
+stralloc hackedruser = {0};
+char strnum[FMT_ULONG];
+
+void dodefaultreturnpath()
+{
+ if (!stralloc_copys(&hackedruser,mailruser)) die_nomem();
+
+ if (flaghackmess) {
+ if (!stralloc_cats(&hackedruser,"-")) die_nomem();
+ if (!stralloc_catb(&hackedruser,strnum,fmt_ulong(strnum,(unsigned long) starttime))) die_nomem();
+ if (!stralloc_cats(&hackedruser,".")) die_nomem();
+ if (!stralloc_catb(&hackedruser,strnum,fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
+ }
+ if (flaghackrecip)
+ if (!stralloc_cats(&hackedruser,"-")) die_nomem();
+
+ if (!token822_ready(&drp,10)) die_nomem();
+
+ drp.len = 0;
+ drp.t[drp.len].type = TOKEN822_ATOM;
+ drp.t[drp.len].s = "Return-Path";
+ drp.t[drp.len].slen = 11;
+ ++drp.len;
+ drp.t[drp.len].type = TOKEN822_COLON;
+ ++drp.len;
+ drp.t[drp.len].type = TOKEN822_QUOTE;
+ drp.t[drp.len].s = hackedruser.s;
+ drp.t[drp.len].slen = hackedruser.len;
+ ++drp.len;
+
+ if (mailrhost) {
+ drp.t[drp.len].type = TOKEN822_AT;
+ ++drp.len;
+ drp.t[drp.len].type = TOKEN822_ATOM;
+ drp.t[drp.len].s = mailrhost;
+ drp.t[drp.len].slen = str_len(mailrhost);
+ ++drp.len;
+ }
+
+ if (token822_unparse(&defaultreturnpath,&drp,LINELEN) != 1) die_nomem();
+ doordie(&defaultreturnpath,token822_parse(&drp,&defaultreturnpath,&hfbuf));
+ doordie(&defaultreturnpath,token822_addrlist(&hfrewrite,&hfaddr,&drp,rwreturn));
+ if (token822_unparse(&defaultreturnpath,&hfrewrite,LINELEN) != 1) die_nomem();
+}
+
+int flagmft = 0;
+stralloc mft = {0};
+struct constmap mapmft;
+
+void mft_init()
+{
+ char *x;
+ int r;
+
+ x = env_get("QMAILMFTFILE");
+ if (!x) return;
+
+ r = control_readfile(&mft,x,0);
+ if (r == -1) die_read(); /*XXX*/
+ if (!r) return;
+
+ if (!constmap_init(&mapmft,mft.s,mft.len,0)) die_nomem();
+ flagmft = 1;
+}
+
+void finishmft()
+{
+ int i;
+ static stralloc sa = {0};
+ static stralloc sa2 = {0};
+
+ if (!flagmft) return;
+ if (htypeseen[H_MAILFOLLOWUPTO]) return;
+
+ for (i = 0; i < tocclist.len; ++i)
+ if (constmap(&mapmft,tocclist.sa[i].s,tocclist.sa[i].len))
+ break;
+
+ if (i == tocclist.len) return;
+
+ outs("Mail-Followup-To: ");
+ i = tocclist.len;
+ while (i--) {
+ if (!stralloc_copy(&sa,&tocclist.sa[i])) die_nomem();
+ if (!stralloc_0(&sa)) die_nomem();
+ if (!quote2(&sa2,sa.s)) die_nomem();
+ out(sa2.s,sa2.len);
+ if (i) outs(",\n ");
+ }
+ outs("\n");
+}
+
+void finishheader()
+{
+ flagresent =
+ htypeseen[H_R_SENDER] || htypeseen[H_R_FROM] || htypeseen[H_R_REPLYTO]
+ || htypeseen[H_R_TO] || htypeseen[H_R_CC] || htypeseen[H_R_BCC]
+ || htypeseen[H_R_DATE] || htypeseen[H_R_MESSAGEID];
+
+ if (!sender.s)
+ dodefaultreturnpath();
+
+ if (!flagqueue) {
+ static stralloc sa = {0};
+ static stralloc sa2 = {0};
+
+ if (!stralloc_copy(&sa,&sender)) die_nomem();
+ if (!stralloc_0(&sa)) die_nomem();
+ if (!quote2(&sa2,sa.s)) die_nomem();
+
+ outs("Return-Path: <");
+ out(sa2.s,sa2.len);
+ outs(">\n");
+ }
+
+ /* could check at this point whether there are any recipients */
+ if (flagqueue)
+ if (qmail_open(&qqt) == -1) die_qqt();
+
+ if (flagresent) {
+ if (!htypeseen[H_R_DATE]) {
+ if (!newfield_datemake(starttime)) die_nomem();
+ outs("Resent-");
+ out(newfield_date.s,newfield_date.len);
+ }
+ if (!htypeseen[H_R_MESSAGEID]) {
+ if (!newfield_msgidmake(control_idhost.s,control_idhost.len,starttime)) die_nomem();
+ outs("Resent-");
+ out(newfield_msgid.s,newfield_msgid.len);
+ }
+ if (!htypeseen[H_R_FROM]) {
+ defaultfrommake();
+ outs("Resent-");
+ out(defaultfrom.s,defaultfrom.len);
+ }
+ if (!htypeseen[H_R_TO] && !htypeseen[H_R_CC])
+ outs("Resent-Cc: recipient list not shown: ;\n");
+ } else {
+ if (!htypeseen[H_DATE]) {
+ if (!newfield_datemake(starttime)) die_nomem();
+ out(newfield_date.s,newfield_date.len);
+ }
+ if (!htypeseen[H_MESSAGEID]) {
+ if (!newfield_msgidmake(control_idhost.s,control_idhost.len,starttime)) die_nomem();
+ out(newfield_msgid.s,newfield_msgid.len);
+ }
+ if (!htypeseen[H_FROM]) {
+ defaultfrommake();
+ out(defaultfrom.s,defaultfrom.len);
+ }
+ if (!htypeseen[H_TO] && !htypeseen[H_CC])
+ outs("Cc: recipient list not shown: ;\n");
+ finishmft();
+ }
+
+ savedh_print();
+}
+
+void getcontrols()
+{
+ static stralloc sa = {0};
+ char *x;
+
+ mft_init();
+
+ if (chdir(auto_qmail) == -1) die_chdir();
+ if (control_init() == -1) die_read();
+
+ if (control_rldef(&control_defaultdomain,"control/defaultdomain",1,"defaultdomain") != 1)
+ die_read();
+ x = env_get("QMAILDEFAULTDOMAIN");
+ if (x) if (!stralloc_copys(&control_defaultdomain,x)) die_nomem();
+ if (!stralloc_copys(&sa,".")) die_nomem();
+ if (!stralloc_cat(&sa,&control_defaultdomain)) die_nomem();
+ doordie(&sa,token822_parse(&defaultdomain,&sa,&defaultdomainbuf));
+
+ if (control_rldef(&control_defaulthost,"control/defaulthost",1,"defaulthost") != 1)
+ die_read();
+ x = env_get("QMAILDEFAULTHOST");
+ if (x) if (!stralloc_copys(&control_defaulthost,x)) die_nomem();
+ if (!stralloc_copys(&sa,"@")) die_nomem();
+ if (!stralloc_cat(&sa,&control_defaulthost)) die_nomem();
+ doordie(&sa,token822_parse(&defaulthost,&sa,&defaulthostbuf));
+
+ if (control_rldef(&control_plusdomain,"control/plusdomain",1,"plusdomain") != 1)
+ die_read();
+ x = env_get("QMAILPLUSDOMAIN");
+ if (x) if (!stralloc_copys(&control_plusdomain,x)) die_nomem();
+ if (!stralloc_copys(&sa,".")) die_nomem();
+ if (!stralloc_cat(&sa,&control_plusdomain)) die_nomem();
+ doordie(&sa,token822_parse(&plusdomain,&sa,&plusdomainbuf));
+
+ if (control_rldef(&control_idhost,"control/idhost",1,"idhost") != 1)
+ die_read();
+ x = env_get("QMAILIDHOST");
+ if (x) if (!stralloc_copys(&control_idhost,x)) die_nomem();
+}
+
+#define RECIP_DEFAULT 1
+#define RECIP_ARGS 2
+#define RECIP_HEADER 3
+#define RECIP_AH 4
+
+int main(int argc,char **argv)
+{
+ int i;
+ int opt;
+ int recipstrategy;
+
+ sig_pipeignore();
+
+ starttime = now();
+
+ qmopts = env_get("QMAILINJECT");
+ if (qmopts)
+ while (*qmopts)
+ switch (*qmopts++) {
+ case 'c': flagnamecomment = 1; break;
+ case 's': flagdeletesender = 1; break;
+ case 'f': flagdeletefrom = 1; break;
+ case 'i': flagdeletemessid = 1; break;
+ case 'r': flaghackrecip = 1; break;
+ case 'm': flaghackmess = 1; break;
+ }
+
+ mailhost = env_get("QMAILHOST");
+ if (!mailhost) mailhost = env_get("MAILHOST");
+ mailrhost = env_get("QMAILSHOST");
+ if (!mailrhost) mailrhost = mailhost;
+
+ mailuser = env_get("QMAILUSER");
+ if (!mailuser) mailuser = env_get("MAILUSER");
+ if (!mailuser) mailuser = env_get("USER");
+ if (!mailuser) mailuser = env_get("LOGNAME");
+ if (!mailuser) mailuser = "anonymous";
+ mailusertokentype = TOKEN822_ATOM;
+ if (quote_need(mailuser,str_len(mailuser))) mailusertokentype = TOKEN822_QUOTE;
+ mailruser = env_get("QMAILSUSER");
+ if (!mailruser) mailruser = mailuser;
+
+ for (i = 0; i < H_NUM; ++i) htypeseen[i] = 0;
+
+ recipstrategy = RECIP_DEFAULT;
+ flagqueue = 1;
+
+ getcontrols();
+
+ if (!saa_readyplus(&hrlist,1)) die_nomem();
+ if (!saa_readyplus(&tocclist,1)) die_nomem();
+ if (!saa_readyplus(&hrrlist,1)) die_nomem();
+ if (!saa_readyplus(&reciplist,1)) die_nomem();
+
+ while ((opt = getopt(argc,argv,"aAhHnNf:")) != opteof)
+ switch (opt) {
+ case 'a': recipstrategy = RECIP_ARGS; break;
+ case 'A': recipstrategy = RECIP_DEFAULT; break;
+ case 'h': recipstrategy = RECIP_HEADER; break;
+ case 'H': recipstrategy = RECIP_AH; break;
+ case 'n': flagqueue = 0; break;
+ case 'N': flagqueue = 1; break;
+ case 'f':
+ if (!quote2(&sender,optarg)) die_nomem();
+ doordie(&sender,token822_parse(&envs,&sender,&envsbuf));
+ token822_reverse(&envs);
+ rwgeneric(&envs);
+ token822_reverse(&envs);
+ if (token822_unquote(&sender,&envs) != 1) die_nomem();
+ break;
+ case '?':
+ default:
+ perm();
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (recipstrategy == RECIP_DEFAULT)
+ recipstrategy = (*argv ? RECIP_ARGS : RECIP_HEADER);
+
+ if (recipstrategy != RECIP_HEADER)
+ while (*argv)
+ dorecip(*argv++);
+
+ flagrh = (recipstrategy != RECIP_ARGS);
+
+ if (headerbody(buffer_0,doheaderfield,finishheader,dobody) == -1)
+ die_read();
+
+ exitnicely();
+}
diff --git a/src/qmail-local.c b/src/qmail-local.c
new file mode 100644
index 0000000..7d1e6a3
--- /dev/null
+++ b/src/qmail-local.c
@@ -0,0 +1,725 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "sig.h"
+#include "env.h"
+#include "byte.h"
+#include "exit.h"
+#include "open.h"
+#include "wait.h"
+#include "lock.h"
+#include "seek.h"
+#include "buffer.h"
+#include "getln.h"
+#include "getoptb.h"
+#include "alloc.h"
+#include "logmsg.h"
+#include "stralloc.h"
+#include "fmt.h"
+#include "str.h"
+#include "now.h"
+#include "case.h"
+#include "quote.h"
+#include "qmail.h"
+#include "readclose.h"
+#include "myctime.h"
+#include "gfrom.h"
+#include "auto_break.h"
+#include "auto_patrn.h"
+
+/**
+ @file qmail-local
+ local delivery agent to Mailbox and Maildir
+ includes patches from Matthias Andree and Toby Betts
+ @return 0 ok; > 0 failure, 111 temp failure
+ */
+
+#define WHO "qmail-local"
+
+void usage() { logmsg(WHO,100,USAGE,"qmail-local [ -nN ] user homedir local dash ext domain sender aliasempty"); }
+
+void temp_nomem() { logmsg(WHO,111,FATAL,"Out of memory. (#4.3.0)"); }
+void temp_rewind() { logmsg(WHO,111,FATAL,"Unable to rewind message. (#4.3.0)"); }
+void temp_childcrashed() { logmsg(WHO,111,FATAL,"Aack, child crashed. (#4.3.0)"); }
+void temp_fork() { logmsg(WHO,111,FATAL,B("Unable to fork: ",error_str(errno),". (#4.3.0)")); }
+void temp_read() { logmsg(WHO,111,ERROR,B("Unable to read message: ",error_str(errno),". (#4.3.0)")); }
+void temp_slowlock()
+{ logmsg(WHO,111,ERROR,"File has been locked for 30 seconds straight. (#4.3.0)"); }
+void temp_qmail(fn) char *fn;
+{ logmsg(WHO,111,FATAL,B("Unable to open: ",fn," ",error_str(errno),". (#4.3.0)")); }
+
+int flagdoit;
+int flag99;
+
+char *user;
+char *homedir;
+char *local;
+char *dash;
+char *ext;
+char *host;
+char *sender;
+char *aliasempty;
+
+stralloc safeext = {0};
+stralloc ufline = {0};
+stralloc rpline = {0};
+stralloc envrecip = {0};
+stralloc dtline = {0};
+stralloc qme = {0};
+stralloc ueo = {0};
+stralloc cmds = {0};
+stralloc messline = {0};
+stralloc foo = {0};
+stralloc hostname = {0};
+
+char bufin[1024];
+char bufout[1024];
+
+/* child process */
+
+char fntmptph[80 + FMT_ULONG * 2];
+char fnnewtph[80 + FMT_ULONG * 2];
+void tryunlinktmp() { unlink(fntmptph); }
+void sigalrm() { tryunlinktmp(); _exit(3); }
+
+void maildir_child(char *dir)
+{
+ unsigned long pid;
+ struct timeval time;
+ char host[64];
+ char *s;
+ int loop;
+ struct stat st;
+ int fd;
+ buffer bi;
+ buffer bo;
+
+ sig_alarmcatch(sigalrm);
+ if (chdir(dir) == -1) { if (errno != ENOENT) _exit(1); _exit(2); }
+ pid = getpid();
+ host[0] = 0;
+ gethostname(host,sizeof(host));
+
+ s = host;
+ for (loop = 0; loop < str_len(host); ++loop) {
+ if (host[loop] == '/') {
+ if (!stralloc_cats(&hostname,"\\057")) temp_nomem();
+ continue;
+ }
+ if (host[loop] == ':') {
+ if (!stralloc_cats(&hostname,"\\072")) temp_nomem();
+ continue;
+ }
+ if (!stralloc_append(&hostname,s+loop)) temp_nomem();
+ }
+
+ for (loop = 0 ;; ++loop) {
+ gettimeofday(&time,0);
+ s = fntmptph;
+ s += fmt_str(s,"tmp/");
+ s += fmt_ulong(s,time.tv_sec); *s++ = '.';
+ *s++ = 'M'; s += fmt_ulong(s,time.tv_usec);
+ *s++ = 'P'; s += fmt_ulong(s,pid); *s++ = '.';
+ s += fmt_strn(s,hostname.s,hostname.len); *s++ = 0;
+
+ if (stat(fntmptph,&st) == -1) if (errno == ENOENT) break;
+ /* really should never get to this point */
+ if (loop == 2) _exit(1);
+ sleep(2);
+ }
+
+ alarm(86400);
+ fd = open_excl(fntmptph);
+ if (fd == -1) _exit(1);
+
+ buffer_init(&bi,read,0,bufin,sizeof(bufin));
+ buffer_init(&bo,write,fd,bufout,sizeof(bufout));
+ if (buffer_put(&bo,rpline.s,rpline.len) == -1) goto FAIL;
+ if (buffer_put(&bo,dtline.s,dtline.len) == -1) goto FAIL;
+
+ switch (buffer_copy(&bo,&bi)) {
+ case -2: tryunlinktmp(); _exit(4);
+ case -3: goto FAIL;
+ }
+
+ if (buffer_flush(&bo) == -1) goto FAIL;
+ if (fstat(fd,&st) == -1) goto FAIL;
+ if (fsync(fd) == -1) goto FAIL;
+ if (close(fd) == -1) goto FAIL; /* NFS dorks */
+
+ s = fnnewtph;
+ s += fmt_str(s,"new/");
+ s += fmt_ulong(s,time.tv_sec); *s++ = '.';
+
+ /* in hexadecimal */
+ *s++ = 'I'; s += fmt_xlong(s,st.st_ino);
+ *s++ = 'V'; s += fmt_xlong(s,st.st_dev);
+
+ /* in decimal */
+ *s++ = 'M'; s += fmt_ulong(s,time.tv_usec);
+ *s++ = 'P'; s += fmt_ulong(s,pid); *s++ = '.';
+
+ s += fmt_strn(s,hostname.s,hostname.len); *s++ = 0;
+
+ if (link(fntmptph,fnnewtph) == -1) goto FAIL;
+ if ((fd = open(fnnewtph,O_RDONLY)) < 0 || fsync(fd) < 0 || close(fd)) goto FAIL;
+ /* DJB: if it was error_exist, almost certainly successful; i hate NFS -- FEH: Reiser patch */
+ tryunlinktmp(); _exit(0);
+
+ FAIL: tryunlinktmp(); _exit(1);
+}
+
+/* end child process */
+
+void maildir(char *fn)
+{
+ int child;
+ int wstat;
+
+ if (seek_begin(0) == -1) temp_rewind();
+
+ switch (child = fork()) {
+ case -1:
+ temp_fork();
+ case 0:
+ maildir_child(fn);
+ _exit(111);
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+ temp_childcrashed();
+
+ switch (wait_exitcode(wstat)) {
+ case 0: break;
+ case 2: logmsg(WHO,111,ERROR,"Unable to chdir to maildir. (#4.2.1)");
+ case 3: logmsg(WHO,111,ERROR,"Timeout on maildir delivery. (#4.3.0)");
+ case 4: logmsg(WHO,111,ERROR,"Unable to read message. (#4.3.0)");
+ default: logmsg(WHO,111,ERROR,"Temporary error on maildir delivery. (#4.3.0)");
+ }
+}
+
+void mailfile(char *fn)
+{
+ int fd;
+ buffer bi;
+ buffer bo;
+ int match;
+ seek_pos pos;
+ int flaglocked;
+
+ if (seek_begin(0) == -1) temp_rewind();
+
+ fd = open_append(fn);
+ if (fd == -1)
+ logmsg(WHO,111,ERROR,B("Unable to open:",fn," ",error_str(errno),". (#4.2.1)"));
+
+ sig_alarmcatch(temp_slowlock);
+ alarm(30);
+ flaglocked = (lock_ex(fd) != -1);
+ alarm(0);
+ sig_alarmdefault();
+
+ seek_end(fd);
+ pos = seek_cur(fd);
+
+ buffer_init(&bi,read,0,bufin,sizeof(bufin));
+ buffer_init(&bo,write,fd,bufout,sizeof(bufout));
+ if (buffer_put(&bo,ufline.s,ufline.len)) goto WRITERRS;
+ if (buffer_put(&bo,rpline.s,rpline.len)) goto WRITERRS;
+ if (buffer_put(&bo,dtline.s,dtline.len)) goto WRITERRS;
+
+ for (;;) {
+ if (getln(&bi,&messline,&match,'\n') != 0) {
+ logmsg(WHO,0,WARN,B("Unable to read message: ",error_str(errno),". (#4.3.0)"));
+ if (flaglocked) seek_trunc(fd,pos);
+ close(fd);
+ _exit(111);
+ }
+ if (!match && !messline.len) break;
+ if (gfrom(messline.s,messline.len))
+ if (buffer_puts(&bo,">")) goto WRITERRS;
+ if (buffer_put(&bo,messline.s,messline.len)) goto WRITERRS;
+ if (!match) {
+ if (buffer_puts(&bo,"\n")) goto WRITERRS;
+ break;
+ }
+ }
+
+ if (buffer_puts(&bo,"\n")) goto WRITERRS;
+ if (buffer_flush(&bo)) goto WRITERRS;
+ if (fsync(fd) == -1) goto WRITERRS;
+ close(fd);
+ return;
+
+ WRITERRS:
+ logmsg(WHO,0,WARN,B("Unable to write ",fn,": ",error_str(errno),". (#4.3.0)"));
+ if (flaglocked) seek_trunc(fd,pos);
+ close(fd);
+ _exit(111);
+}
+
+void mailprogram(char *prog)
+{
+ int child;
+ char *(args[4]);
+ int wstat;
+
+ if (seek_begin(0) == -1) temp_rewind();
+
+ switch (child = fork()) {
+ case -1:
+ temp_fork();
+ case 0:
+ args[0] = "/bin/sh";
+ args[1] = "-c";
+ args[2] = prog;
+ args[3] = 0;
+ sig_pipedefault();
+ execv(*args,args);
+ logmsg(WHO,0,ERROR,B("Unable to run /bin/sh: ",error_str(errno),". (#4.3.0)"));
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+ temp_childcrashed();
+
+ switch (wait_exitcode(wstat)) {
+ case 100:
+ case 64: case 65: case 70: case 76: case 77: case 78: case 112: _exit(100);
+ case 0: break;
+ case 99: flag99 = 1; break;
+ default: _exit(111);
+ }
+}
+
+unsigned long mailforward_qp = 0;
+
+void mailforward(char **recips)
+{
+ struct qmail qqt;
+ char *qqx;
+ buffer bi;
+ int match;
+
+ if (seek_begin(0) == -1) temp_rewind();
+ buffer_init(&bi,read,0,bufin,sizeof(bufin));
+
+ if (qmail_open(&qqt) == -1) temp_fork();
+ mailforward_qp = qmail_qp(&qqt);
+ qmail_put(&qqt,dtline.s,dtline.len);
+
+ do {
+ if (getln(&bi,&messline,&match,'\n') != 0) { qmail_fail(&qqt); break; }
+ qmail_put(&qqt,messline.s,messline.len);
+ } while (match);
+
+ qmail_from(&qqt,ueo.s);
+ while (*recips) qmail_to(&qqt,*recips++);
+ qqx = qmail_close(&qqt);
+ if (!*qqx) return;
+ logmsg(WHO,*qqx == 'D' ? 100 : 111,ERROR,B("Unable to forward message: ",qqx + 1,"."));
+}
+
+void bouncexf()
+{
+ int match;
+ buffer bi;
+
+ if (seek_begin(0) == -1) temp_rewind();
+ buffer_init(&bi,read,0,bufin,sizeof(bufin));
+
+ for (;;) {
+ if (getln(&bi,&messline,&match,'\n') != 0) temp_read();
+ if (!match) break;
+ if (messline.len <= 1)
+ break;
+ if (messline.len == dtline.len)
+ if (!str_diffn(messline.s,dtline.s,dtline.len))
+ logmsg(WHO,100,ERROR,"This message is looping: it already has my Delivered-To line. (#5.4.6)");
+ }
+}
+
+void checkhome()
+{
+ struct stat st;
+
+ if (stat(".",&st) == -1)
+ logmsg(WHO,111,ERROR,B("Unable to stat home directory: ",error_str(errno),". (#4.3.0)"));
+ if (st.st_mode & auto_patrn)
+ logmsg(WHO,111,ERROR,"Uh-oh: home directory is writable. (#4.7.0)");
+ if (st.st_mode & 01000)
+ if (flagdoit)
+ logmsg(WHO,111,ERROR,"Home directory is sticky: user is editing his .qmail file. (#4.2.1)");
+ else
+ logmsg(WHO,0,WARN,"Warning: home directory is sticky.");
+}
+
+int qmeox(char *dashowner)
+{
+ struct stat st;
+
+ if (!stralloc_copys(&qme,".qmail")) temp_nomem();
+ if (!stralloc_cats(&qme,dash)) temp_nomem();
+ if (!stralloc_cat(&qme,&safeext)) temp_nomem();
+ if (!stralloc_cats(&qme,dashowner)) temp_nomem();
+ if (!stralloc_0(&qme)) temp_nomem();
+
+ if (stat(qme.s,&st) == -1) {
+ if (errno != ENOENT) temp_qmail(qme.s);
+ return -1;
+ }
+ return 0;
+}
+
+int qmeexists(int *fd,int *cutable)
+{
+ struct stat st;
+
+ if (!stralloc_0(&qme)) temp_nomem();
+
+ *fd = open_read(qme.s);
+ if (*fd == -1) {
+ if (errno != ENOENT) temp_qmail(qme.s);
+ if (errno == EPERM) temp_qmail(qme.s);
+ if (errno == EACCES) temp_qmail(qme.s);
+ return 0;
+ }
+
+ if (fstat(*fd,&st) == -1) temp_qmail(qme.s);
+ if ((st.st_mode & S_IFMT) == S_IFREG) {
+ if (st.st_mode & auto_patrn)
+ logmsg(WHO,111,ERROR,"Uh-oh: .qmail file is writable. (#4.7.0)");
+ *cutable = !!(st.st_mode & 0100);
+ return 1;
+ }
+ close(*fd);
+ return 0;
+}
+
+/* "" "": "" */
+/* "-/" "": "-/" "-/default" */
+/* "-/" "a": "-/a" "-/default" */
+/* "-/" "a-": "-/a-" "-/a-default" "-/default" */
+/* "-/" "a-b": "-/a-b" "-/a-default" "-/default" */
+/* "-/" "a-b-": "-/a-b-" "-/a-b-default" "-/a-default" "-/default" */
+/* "-/" "a-b-c": "-/a-b-c" "-/a-b-default" "-/a-default" "-/default" */
+
+void qmesearch(int *fd,int *cutable)
+{
+ int i;
+
+ if (!stralloc_copys(&qme,".qmail")) temp_nomem();
+ if (!stralloc_cats(&qme,dash)) temp_nomem();
+ if (!stralloc_cat(&qme,&safeext)) temp_nomem();
+ if (qmeexists(fd,cutable)) {
+ if (safeext.len >= 7) {
+ i = safeext.len - 7;
+ if (!byte_diff("default",7,safeext.s + i))
+ if (i <= str_len(ext)) /* paranoia */
+ if (!env_put("DEFAULT",ext + i)) temp_nomem();
+ }
+ return;
+ }
+
+ for (i = safeext.len; i >= 0 ;--i)
+ if (!i || (safeext.s[i - 1] == '-')) {
+ if (!stralloc_copys(&qme,".qmail")) temp_nomem();
+ if (!stralloc_cats(&qme,dash)) temp_nomem();
+ if (!stralloc_catb(&qme,safeext.s,i)) temp_nomem();
+ if (!stralloc_cats(&qme,"default")) temp_nomem();
+ if (qmeexists(fd,cutable)) {
+ if (i <= str_len(ext)) /* paranoia */
+ if (!env_put("DEFAULT",ext + i)) temp_nomem();
+ return;
+ }
+ }
+
+ *fd = -1;
+}
+
+unsigned long count_file = 0;
+unsigned long count_forward = 0;
+unsigned long count_program = 0;
+char count_buf[FMT_ULONG];
+char buflog[256];
+buffer bl = BUFFER_INIT(write,1,buflog,sizeof(buflog));
+
+void count_print()
+{
+ buffer_puts(&bl,"did ");
+ buffer_put(&bl,count_buf,fmt_ulong(count_buf,count_file));
+ buffer_puts(&bl,"+");
+ buffer_put(&bl,count_buf,fmt_ulong(count_buf,count_forward));
+ buffer_puts(&bl,"+");
+ buffer_put(&bl,count_buf,fmt_ulong(count_buf,count_program));
+ buffer_puts(&bl,"\n");
+
+ if (mailforward_qp) {
+ buffer_puts(&bl,"qp ");
+ buffer_put(&bl,count_buf,fmt_ulong(count_buf,mailforward_qp));
+ buffer_puts(&bl,"\n");
+ }
+ buffer_flush(&bl);
+}
+
+void sayit(char *type,char *cmd,int len)
+{
+ buffer_puts(&bl,type);
+ buffer_put(&bl,cmd,len);
+ buffer_putsflush(&bl,"\n");
+}
+
+int main(int argc,char **argv)
+{
+ int opt;
+ int i, j, k;
+ int fd;
+ int numforward;
+ char **recips;
+ datetime_sec starttime;
+ int flagforwardonly;
+ char *x;
+
+ umask(077);
+ sig_pipeignore();
+
+ if (!env_init()) temp_nomem();
+
+ flagdoit = 1;
+ while ((opt = getopt(argc,argv,"nN")) != opteof)
+ switch (opt) {
+ case 'n': flagdoit = 0; break;
+ case 'N': flagdoit = 1; break;
+ default: usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!(user = *argv++)) usage();
+ if (!(homedir = *argv++)) usage();
+ if (!(local = *argv++)) usage();
+ if (!(dash = *argv++)) usage();
+ if (!(ext = *argv++)) usage();
+ if (!(host = *argv++)) usage();
+ if (!(sender = *argv++)) usage();
+ if (!(aliasempty = *argv++)) usage();
+ if (*argv) usage();
+
+ if (homedir[0] != '/') usage();
+ if (chdir(homedir) == -1)
+ logmsg(WHO,111,ERROR,B("Unable to switch to: ",homedir," ",error_str(errno),". (#4.3.0)"));
+ checkhome();
+
+ if (!env_put("HOST",host)) temp_nomem();
+ if (!env_put("HOME",homedir)) temp_nomem();
+ if (!env_put("USER",user)) temp_nomem();
+ if (!env_put("LOCAL",local)) temp_nomem();
+
+#ifdef HIDEVIRTUALUSER
+ if (str_len(ext) > 1) {
+ i = str_chr(local,*auto_break);
+ if (!stralloc_copys(&envrecip,local + i + 1)) temp_nomem();
+ } else
+#endif
+ if (!stralloc_copys(&envrecip,local)) temp_nomem();
+ if (!stralloc_cats(&envrecip,"@")) temp_nomem();
+ if (!stralloc_cats(&envrecip,host)) temp_nomem();
+
+ if (!stralloc_copy(&foo,&envrecip)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("RECIPIENT",foo.s)) temp_nomem();
+
+ if (!stralloc_copys(&dtline,"Delivered-To: ")) temp_nomem();
+ if (!stralloc_cat(&dtline,&envrecip)) temp_nomem();
+
+ for (i = 0; i < dtline.len; ++i)
+ if (dtline.s[i] == '\n') dtline.s[i] = '_';
+ if (!stralloc_cats(&dtline,"\n")) temp_nomem();
+
+ if (!stralloc_copy(&foo,&dtline)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("DTLINE",foo.s)) temp_nomem();
+
+ if (flagdoit) bouncexf();
+
+ if (!env_put("SENDER",sender)) temp_nomem();
+
+ if (!quote2(&foo,sender)) temp_nomem();
+ if (!stralloc_copys(&rpline,"Return-Path: <")) temp_nomem();
+ if (!stralloc_cat(&rpline,&foo)) temp_nomem();
+ for (i = 0;i < rpline.len;++i) if (rpline.s[i] == '\n') rpline.s[i] = '_';
+ if (!stralloc_cats(&rpline,">\n")) temp_nomem();
+
+ if (!stralloc_copy(&foo,&rpline)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("RPLINE",foo.s)) temp_nomem();
+
+ if (!stralloc_copys(&ufline,"From ")) temp_nomem();
+
+ if (*sender) {
+ int len; int i; char ch;
+
+ len = str_len(sender);
+ if (!stralloc_readyplus(&ufline,len)) temp_nomem();
+
+ for (i = 0;i < len;++i) {
+ ch = sender[i];
+ if ((ch == ' ') || (ch == '\t') || (ch == '\n')) ch = '-';
+ ufline.s[ufline.len + i] = ch;
+ }
+ ufline.len += len;
+ } else
+ if (!stralloc_cats(&ufline,"MAILER-DAEMON")) temp_nomem();
+
+ if (!stralloc_cats(&ufline," ")) temp_nomem();
+ starttime = now();
+ if (!stralloc_cats(&ufline,myctime(starttime))) temp_nomem();
+
+ if (!stralloc_copy(&foo,&ufline)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("UFLINE",foo.s)) temp_nomem();
+
+ x = ext;
+ if (!env_put("EXT",x)) temp_nomem();
+ x += str_chr(x,'-'); if (*x) ++x;
+ if (!env_put("EXT2",x)) temp_nomem();
+ x += str_chr(x,'-'); if (*x) ++x;
+ if (!env_put("EXT3",x)) temp_nomem();
+ x += str_chr(x,'-'); if (*x) ++x;
+ if (!env_put("EXT4",x)) temp_nomem();
+
+ if (!stralloc_copys(&safeext,ext)) temp_nomem();
+ case_lowerb(safeext.s,safeext.len);
+
+ for (i = 0; i < safeext.len; ++i)
+ if (safeext.s[i] == '.')
+ safeext.s[i] = ':';
+
+ i = str_len(host);
+ i = byte_rchr(host,i,'.');
+ if (!stralloc_copyb(&foo,host,i)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("HOST2",foo.s)) temp_nomem();
+ i = byte_rchr(host,i,'.');
+ if (!stralloc_copyb(&foo,host,i)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("HOST3",foo.s)) temp_nomem();
+ i = byte_rchr(host,i,'.');
+ if (!stralloc_copyb(&foo,host,i)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put("HOST4",foo.s)) temp_nomem();
+
+ flagforwardonly = 0;
+ qmesearch(&fd,&flagforwardonly);
+
+ if (fd == -1)
+ if (*dash)
+ logmsg(WHO,100,ERROR,"Sorry, no mailbox here by that name. (#5.1.1)");
+
+ if (!stralloc_copys(&ueo,sender)) temp_nomem();
+ if (str_diff(sender,""))
+ if (str_diff(sender,"#@[]"))
+ if (qmeox("-owner") == 0) {
+ if (qmeox("-owner-default") == 0) {
+ if (!stralloc_copys(&ueo,local)) temp_nomem();
+ if (!stralloc_cats(&ueo,"-owner-@")) temp_nomem();
+ if (!stralloc_cats(&ueo,host)) temp_nomem();
+ if (!stralloc_cats(&ueo,"-@[]")) temp_nomem();
+ } else {
+ if (!stralloc_copys(&ueo,local)) temp_nomem();
+ if (!stralloc_cats(&ueo,"-owner@")) temp_nomem();
+ if (!stralloc_cats(&ueo,host)) temp_nomem();
+ }
+ }
+ if (!stralloc_0(&ueo)) temp_nomem();
+ if (!env_put("NEWSENDER",ueo.s)) temp_nomem();
+
+ if (!stralloc_ready(&cmds,0)) temp_nomem();
+ cmds.len = 0;
+
+ if (fd != -1)
+ if (readclose_append(fd,&cmds,256) == -1) temp_nomem();
+
+ if (!cmds.len) {
+ if (!stralloc_copys(&cmds,aliasempty)) temp_nomem();
+ flagforwardonly = 0;
+ }
+ if (!cmds.len || (cmds.s[cmds.len - 1] != '\n'))
+ if (!stralloc_cats(&cmds,"\n")) temp_nomem();
+
+ numforward = 0;
+ i = 0;
+
+ for (j = 0; j < cmds.len; ++j)
+ if (cmds.s[j] == '\n') {
+ switch (cmds.s[i]) {
+ case '#': case '.': case '/': case '|': break;
+ default: ++numforward;
+ }
+ i = j + 1;
+ }
+
+ recips = (char **) alloc((numforward + 1) * sizeof(char *));
+ if (!recips) temp_nomem();
+ numforward = 0;
+
+ flag99 = 0;
+
+ i = 0;
+ for (j = 0; j < cmds.len; ++j)
+ if (cmds.s[j] == '\n') {
+ cmds.s[j] = 0;
+ k = j;
+ /* Patch contributed by Erik Sjolund <erik.sjolund@gmail.com>. */
+ while ((k > i) && ((cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t')))
+ cmds.s[--k] = 0;
+ switch (cmds.s[i]) {
+ case 0: /* k == i */
+ if (i) break;
+ logmsg(WHO,111,ERROR,"Uh-oh: first line of .qmail file is blank. (#4.2.1)");
+ case '#':
+ break;
+ case '.':
+ case '/':
+ ++count_file;
+ if (flagforwardonly) logmsg(WHO,111,ERROR,"Uh-oh: .qmail has file delivery but has x bit set. (#4.7.0)");
+ if (cmds.s[k - 1] == '/')
+ if (flagdoit) maildir(cmds.s + i);
+ else sayit("maildir ",cmds.s + i,k - i);
+ else
+ if (flagdoit) mailfile(cmds.s + i);
+ else sayit("mbox ",cmds.s + i,k - i);
+ break;
+ case '|':
+ ++count_program;
+ if (flagforwardonly) logmsg(WHO,111,ERROR,"Uh-oh: .qmail has prog delivery but has x bit set. (#4.7.0)");
+ if (flagdoit) mailprogram(cmds.s + i + 1);
+ else sayit("program ",cmds.s + i + 1,k - i - 1);
+ break;
+ case '+':
+ if (str_equal(cmds.s + i + 1,"list"))
+ flagforwardonly = 1;
+ break;
+ case '&':
+ ++i;
+ default:
+ ++count_forward;
+ if (flagdoit) recips[numforward++] = cmds.s + i;
+ else sayit("forward ",cmds.s + i,k - i);
+ break;
+ }
+ i = j + 1;
+ if (flag99) break;
+ }
+
+ if (numforward) if (flagdoit) {
+ recips[numforward] = 0;
+ mailforward(recips);
+ }
+
+ count_print();
+ _exit(0);
+}
diff --git a/src/qmail-lspawn.c b/src/qmail-lspawn.c
new file mode 100644
index 0000000..fed5c4c
--- /dev/null
+++ b/src/qmail-lspawn.c
@@ -0,0 +1,241 @@
+#include <unistd.h>
+#include "fd.h"
+#include "wait.h"
+#include "prot.h"
+#include "buffer.h"
+#include "stralloc.h"
+#include "scan.h"
+#include "exit.h"
+#include "cdbread.h"
+#include "case.h"
+#include "readclose.h"
+#include "auto_qmail.h"
+#include "auto_uids.h"
+#include "qlx.h"
+#include "error.h"
+#include "open.h"
+#include "byte.h"
+
+char *aliasempty;
+
+void initialize(int argc,char **argv)
+{
+ aliasempty = argv[1];
+ if (!aliasempty) _exit(100);
+}
+
+int truncreport = 3000;
+
+void report(buffer *log,int wstat,char *s,int len)
+{
+ int i;
+ if (wait_crashed(wstat)) { buffer_putsflush(log,"Zqmail-lspawn: qmail-local crashed.\n"); return; }
+
+ switch (wait_exitcode(wstat)) {
+ case QLX_CDB:
+ buffer_putsflush(log,"Zqmail-lspawn: Trouble reading users/assign.cdb.\n"); return;
+ case QLX_NOMEM:
+ buffer_putsflush(log,"Zqmail-lspawn: Out of memory.\n"); return;
+ case QLX_SYS:
+ buffer_putsflush(log,"Zqmail-lspawn: Temporary failure.\n"); return;
+ case QLX_NOALIAS:
+ buffer_putsflush(log,"Zqmail-lspawn: Unable to find alias user!\n"); return;
+ case QLX_ROOT:
+ buffer_putsflush(log,"Zqmail-spawn: Not allowed to perform deliveries as root.\n"); return;
+ case QLX_USAGE:
+ buffer_putsflush(log,"Zqmail-spawn: Internal bug.\n"); return;
+ case QLX_NFS:
+ buffer_putsflush(log,"Zqmail-spawn: NFS failure in qmail-local.\n"); return;
+ case QLX_EXECHARD:
+ buffer_putsflush(log,"Dqmail-spawn: Unable to run qmail-local.\n"); return;
+ case QLX_EXECSOFT:
+ buffer_putsflush(log,"Zqmail-spawn: Unable to run qmail-local.\n"); return;
+ case QLX_EXECPW:
+ buffer_putsflush(log,"Zqmail-spawn: Unable to run qmail-getpw.\n"); return;
+ case 111: case 71: case 74: case 75:
+ buffer_put(log,"Z",1); break;
+ case 0:
+ buffer_put(log,"K",1); break;
+ case 100:
+ default:
+ buffer_put(log,"D",1); break;
+ }
+
+ for (i = 0; i < len; ++i)
+ if (!s[i]) break;
+
+ buffer_put(log,s,i);
+}
+
+stralloc lower = {0};
+stralloc nughde = {0};
+stralloc wildchars = {0};
+
+static struct cdb c;
+
+void nughde_get(char *local)
+{
+ char *(args[3]);
+ int pi[2];
+ int gpwpid;
+ int gpwstat;
+ int r;
+ int fd;
+ int flagwild;
+
+ if (!stralloc_copys(&lower,"!")) _exit(QLX_NOMEM);
+ if (!stralloc_cats(&lower,local)) _exit(QLX_NOMEM);
+ if (!stralloc_0(&lower)) _exit(QLX_NOMEM);
+ case_lowerb(lower.s,lower.len);
+
+ if (!stralloc_copys(&nughde,"")) _exit(QLX_NOMEM);
+
+ fd = open_read("users/assign.cdb");
+ if (fd == -1)
+ if (errno != ENOENT)
+ _exit(QLX_CDB);
+
+ if (fd != -1) {
+ unsigned int i;
+
+ cdb_init(&c,fd);
+ r = cdb_find(&c,"",0);
+ if (r != 1) _exit(QLX_CDB);
+ if (!stralloc_ready(&wildchars,cdb_datalen(&c))) _exit(QLX_NOMEM);
+ wildchars.len = cdb_datalen(&c);
+ if (cdb_read(&c,wildchars.s,wildchars.len,cdb_datapos(&c)) == -1) _exit(QLX_CDB);
+
+ i = lower.len;
+ flagwild = 0;
+
+ do {
+ /* i > 0 */
+ if (!flagwild || (i == 1) || (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1]) < wildchars.len)) {
+ r = cdb_find(&c,lower.s,i);
+ if (r == -1) _exit(QLX_CDB);
+ if (r == 1) {
+ if (!stralloc_ready(&nughde,cdb_datalen(&c))) _exit(QLX_NOMEM);
+ nughde.len = cdb_datalen(&c);
+ if (cdb_read(&c,nughde.s,nughde.len,cdb_datapos(&c)) == -1) _exit(QLX_CDB);
+ if (flagwild)
+ if (!stralloc_cats(&nughde,local + i - 1)) _exit(QLX_NOMEM);
+ if (!stralloc_0(&nughde)) _exit(QLX_NOMEM);
+ close(fd);
+ return;
+ }
+ }
+ --i;
+ flagwild = 1;
+ } while (i);
+
+ close(fd);
+ }
+
+ if (pipe(pi) == -1) _exit(QLX_SYS);
+
+ args[0] = "bin/qmail-getpw";
+ args[1] = local;
+ args[2] = 0;
+ switch (gpwpid = fork()) {
+ case -1:
+ _exit(QLX_SYS);
+ case 0:
+ if (prot_gid(auto_gidn) == -1) _exit(QLX_USAGE);
+ if (prot_uid(auto_uidp) == -1) _exit(QLX_USAGE);
+ close(pi[0]);
+ if (fd_move(1,pi[1]) == -1) _exit(QLX_SYS);
+ execv(*args,args);
+ _exit(QLX_EXECPW);
+ }
+ close(pi[1]);
+
+ if (readclose_append(pi[0],&nughde,128) == -1) _exit(QLX_SYS);
+
+ if (wait_pid(&gpwstat,gpwpid) != -1) {
+ if (wait_crashed(gpwstat)) _exit(QLX_SYS);
+ if (wait_exitcode(gpwstat) != 0) _exit(wait_exitcode(gpwstat));
+ }
+}
+
+int spawn(int fdmess,int fdout,const char *s,char *r,const int at)
+{
+ int f;
+
+ if (!(f = fork())) {
+ char *(args[11]);
+ unsigned long u;
+ int n;
+ int uid;
+ int gid;
+ char *x;
+ unsigned int xlen;
+
+ r[at] = 0;
+ if (!r[0]) _exit(0); /* <> */
+
+ if (chdir(auto_qmail) == -1) _exit(QLX_USAGE);
+
+ nughde_get(r);
+
+ x = nughde.s;
+ xlen = nughde.len;
+
+ args[0] = "bin/qmail-local";
+ args[1] = "--";
+ args[2] = x;
+ n = byte_chr(x,xlen,0);
+ if (n++ == xlen) _exit(QLX_USAGE);
+ x += n;
+ xlen -= n;
+
+ scan_ulong(x,&u);
+ uid = u;
+ n = byte_chr(x,xlen,0);
+ if (n++ == xlen) _exit(QLX_USAGE);
+ x += n;
+ xlen -= n;
+
+ scan_ulong(x,&u);
+ gid = u;
+ n = byte_chr(x,xlen,0);
+ if (n++ == xlen) _exit(QLX_USAGE);
+ x += n;
+ xlen -= n;
+
+ args[3] = x;
+ n = byte_chr(x,xlen,0);
+ if (n++ == xlen) _exit(QLX_USAGE);
+ x += n;
+ xlen -= n;
+
+ args[4] = r;
+ args[5] = x;
+ n = byte_chr(x,xlen,0);
+ if (n++ == xlen) _exit(QLX_USAGE);
+ x += n;
+ xlen -= n;
+
+ args[6] = x;
+ n = byte_chr(x,xlen,0);
+ if (n++ == xlen) _exit(QLX_USAGE);
+ x += n;
+ xlen -= n;
+
+ args[7] = r + at + 1;
+ args[8] = s;
+ args[9] = aliasempty;
+ args[10] = 0;
+
+ if (fd_move(0,fdmess) == -1) _exit(QLX_SYS);
+ if (fd_move(1,fdout) == -1) _exit(QLX_SYS);
+ if (fd_copy(2,1) == -1) _exit(QLX_SYS);
+ if (prot_gid(gid) == -1) _exit(QLX_USAGE);
+ if (prot_uid(uid) == -1) _exit(QLX_USAGE);
+ if (!getuid()) _exit(QLX_ROOT);
+
+ execv(*args,args);
+ if (errno) _exit(QLX_EXECSOFT);
+ _exit(QLX_EXECHARD);
+ }
+ return f;
+}
diff --git a/src/qmail-mfrules.c b/src/qmail-mfrules.c
new file mode 100644
index 0000000..e8cfc94
--- /dev/null
+++ b/src/qmail-mfrules.c
@@ -0,0 +1,173 @@
+#include <sys/stat.h>
+#include <stdio.h> // rename
+#include "logmsg.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include <unistd.h>
+#include "open.h"
+#include "auto_qmail.h"
+#include "cdbmake.h"
+#include "fmt.h"
+#include "scan.h"
+#include "byte.h"
+#include "case.h"
+
+#define WHO "qmail-mfrules"
+
+int rename(const char *,const char *); // stdio.h
+
+stralloc address = {0};
+stralloc data = {0};
+stralloc key = {0};
+stralloc line = {0};
+
+char inbuf[1024];
+buffer bi;
+
+int fd;
+int fdtemp;
+int match = 1;
+
+struct cdb_make cdb;
+
+void die_nomem()
+{
+ logmsg(WHO,112,FATAL,"out of memory");
+}
+void die_parse()
+{
+ if (!stralloc_0(&line)) die_nomem();
+ logmsg(WHO,100,ERROR,B("unable to parse this line: ",line.s));
+}
+void die_read()
+{
+ logmsg(WHO,111,ERROR,"unable to read control/mailfromrules");
+}
+void die_write()
+{
+ logmsg(WHO,111,ERROR,"unable to write to control/mailfromrules.tmp");
+}
+
+char strnum[FMT_ULONG];
+stralloc sanum = {0};
+
+void getnum(char *buf,int len,unsigned long *u)
+{
+ if (!stralloc_copyb(&sanum,buf,len)) die_nomem();
+ if (!stralloc_0(&sanum)) die_nomem();
+ if (sanum.s[scan_ulong(sanum.s,u)]) die_parse();
+}
+
+void doaddressdata()
+{
+ int i;
+ int left;
+ int right;
+ unsigned long bot;
+ unsigned long top;
+
+ if (byte_chr(address.s,address.len,'=') == address.len)
+ if (byte_chr(address.s,address.len,'@') == address.len) {
+ i = byte_chr(address.s,address.len,'-');
+ if (i < address.len) {
+ left = byte_rchr(address.s,i,'.');
+ if (left == i) left = 0; else ++left;
+
+ ++i;
+ right = i + byte_chr(address.s + i,address.len - i,'.');
+
+ getnum(address.s + left,i - 1 - left,&bot);
+ getnum(address.s + i,right - i,&top);
+ if (top > 255) top = 255;
+
+ while (bot <= top) {
+ if (!stralloc_copyb(&key,address.s,left)) die_nomem();
+ if (!stralloc_catb(&key,strnum,fmt_ulong(strnum,bot))) die_nomem();
+ if (!stralloc_catb(&key,address.s + right,address.len - right)) die_nomem();
+ case_lowerb(key.s,key.len);
+ if (cdb_make_add(&cdb,key.s,key.len,data.s,data.len) == -1) die_write();
+ ++bot;
+ }
+
+ return;
+ }
+ }
+
+ case_lowerb(address.s,address.len);
+ case_lowerb(data.s,data.len);
+ if (cdb_make_add(&cdb,address.s,address.len,data.s,data.len) == -1) die_write();
+}
+
+int main()
+{
+ int amper;
+ int i;
+ int len;
+ char *x;
+ char ch;
+
+ umask(033);
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,ERROR,B("unable to chdir to: ",auto_qmail));
+
+ fd = open_read("control/mailfromrules");
+ if (fd == -1) die_read();
+
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ fdtemp = open_trunc("control/mailfromrules.tmp");
+ if (fdtemp == -1) die_write();
+
+ if (cdb_make_start(&cdb,fdtemp) == -1) die_write();
+
+ while (match) {
+ if (getln(&bi,&line,&match,'\n') != 0) die_read();
+
+ x = line.s; len = line.len;
+
+ if (!len) break;
+ if (x[0] == '#') continue;
+ if (x[0] == '\n') continue;
+
+ while (len) {
+ ch = x[len - 1];
+ if (ch != '\n') if (ch != ' ') if (ch != '\t') break;
+ --len;
+ }
+ line.len = len; /* for die_parse() */
+
+ amper = byte_chr(x,len,'&');
+ if (!amper) die_parse();
+ if (amper) if (amper == len || amper < 2) die_parse();
+
+ if (!stralloc_copyb(&address,x,amper)) die_nomem();
+ if (!stralloc_copys(&data,"")) die_nomem();
+
+ x = line.s + amper + 1; len = line.len - amper - 1;
+
+ while (len) {
+ if (len < 3) die_parse(); /* input checks */
+ if ( *x == ',' || *x == ' ' || *x == '\t') die_parse();
+ i = byte_chr(x,len,','); /* &addr1,addr2,.. */
+ if (i > 0 && i < len) {
+ if (!stralloc_catb(&data,"+",1)) die_nomem();
+ if (!stralloc_catb(&data,x,i)) die_nomem();
+ x += i + 1; len -= i + 1; }
+ else {
+ if (!stralloc_catb(&data,"+",1)) die_nomem();
+ if (!stralloc_catb(&data,x,len)) die_nomem();
+ len = 0; }
+ }
+ doaddressdata();
+ }
+
+ if (cdb_make_finish(&cdb) == -1) die_write();
+ if (fsync(fdtemp) == -1) die_write();
+ if (close(fdtemp) == -1) die_write(); /* NFS stupidity */
+ if (rename("control/mailfromrules.tmp","control/mailfromrules.cdb") == -1)
+ logmsg(WHO,111,ERROR,"unable to move control/mailfromrules.tmp to control/mailfromrules.cdb");
+
+ _exit(0);
+}
diff --git a/src/qmail-mrtg-queue.sh b/src/qmail-mrtg-queue.sh
new file mode 100644
index 0000000..3ac0fb1
--- /dev/null
+++ b/src/qmail-mrtg-queue.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+cd HOME
+echo `find queue/mess -type f -print | wc -l`
+echo `find queue/todo/* -type f -print | wc -l`
diff --git a/src/qmail-mrtg.c b/src/qmail-mrtg.c
new file mode 100644
index 0000000..1ccedeb
--- /dev/null
+++ b/src/qmail-mrtg.c
@@ -0,0 +1,322 @@
+#include <unistd.h>
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include "open.h"
+#include "scan.h"
+#include "fmt.h"
+#include "case.h"
+#include "now.h"
+#include "str.h"
+#include "datetime.h"
+#include "logmsg.h"
+
+#define WHO "qmail-mrtg"
+#define TAI64NLEN 24
+
+/** @file qmail-mrtg.c
+ @return 0: ok
+ 1: Error: No TAI64N timestamp available
+ 2: Warning: Not enough time left between calls
+*/
+
+/* qmail-send */
+
+int local = 0;
+int remote = 0;
+int success = 0;
+int failure = 0;
+int bytes = 0;
+int tlstrans = 0;
+int deferral = 0;
+int bounces = 0;
+int triples = 0;
+int qmtp = 0;
+int qmtps = 0;
+
+/* qmail-smtpd */
+
+int asessions = 0;
+int rsessions = 0;
+int aorig = 0;
+int arcpt = 0;
+int rsend = 0;
+int rhelo = 0;
+int rorigbad = 0;
+int rorigdns = 0;
+int rrcptbad = 0;
+int rrcptfail = 0;
+int rsize = 0;
+int rmime = 0;
+int rloader = 0;
+int rvirus = 0;
+int rspam = 0;
+int aauth = 0;
+int rauth = 0;
+int atls = 0;
+int rtls = 0;
+int spfpass = 0;
+int spfail = 0;
+
+/* qmail-pop3d */
+
+int apop = 0;
+int rpop = 0;
+int pok = 0;
+int pdeny = 0;
+
+/* *server + rblsmtpd */
+
+int sok = 0;
+int sdeny = 0;
+int greet = 0;
+int grey = 0;
+int rbl = 0;
+
+char bufsmall[64];
+buffer bo = BUFFER_INIT(write,1,bufsmall,sizeof(bufsmall));
+
+static void outs(char *s)
+{
+ if (buffer_puts(&bo,s) == -1) _exit(1);
+ if (buffer_puts(&bo,"\n") == -1) _exit(1);
+ if (buffer_flush(&bo) == -1) _exit(1);
+}
+
+static void out(int i)
+{
+ char num[FMT_ULONG];
+
+ if (buffer_put(&bo,num,fmt_ulong(num,(unsigned long) i)) == -1) _exit(1);
+ if (buffer_puts(&bo,"\n") == -1) _exit(1);
+ if (buffer_flush(&bo) == -1) _exit(1);
+}
+
+char bufspace[1024];
+buffer bi = BUFFER_INIT(read,0,bufspace,sizeof(bufspace));
+
+void mrtg_results(char flag)
+{
+ switch (flag) {
+ case '1': out(success); out(tlstrans); break;
+ case '2': bytes = bytes/1024; out(bytes); out(bytes); break;
+ case '3': out(local); out(remote); break;
+ case '4': out(failure); out(deferral); break;
+ case '5': out(bounces); out(triples); break;
+ case '6': qmtps += qmtp; out(qmtp); out(qmtps); break; /* QMTP */
+
+ case 'a': out(asessions); out(rsessions); break; /* total */
+ case 'b': out(aorig); out(arcpt); break; /* accepted */
+ case 'c': out(rsend); out(rhelo); break; /* rejected MTA */
+ case 'd': out(rorigbad); out(rorigdns); break; /* Orig */
+ case 'e': out(rrcptbad); out(rrcptfail); break; /* Recipient */
+ case 'f': out(rmime); out(rloader); break; /* Warlord */
+ case 'g': out(rvirus); out(rspam); break; /* Infected/Spam */
+ case 'h': out(aauth); out(rauth); break; /* Auth */
+ case 'i': out(atls); out(rtls); break; /* TLS */
+ case 'j': out(spfpass); out(spfail); break; /* SPF */
+ case 'k': out(grey); break; /* Greylisted */
+ case 'z': sdeny +=rbl; out(sok); out(sdeny); break; /* reject session */
+
+ case 'A': out(apop); out(rpop); break;
+ case 'B': out(pok); out(pdeny); break;
+
+ default: break;
+ }
+}
+
+void mrtg_sendlog(char *in, char flag)
+{
+ int i, j, k = 0;
+
+ switch (flag) {
+ case '1': if (case_starts(in,"delivery")) {
+ i = str_chr(in,':') + 2;
+ if (case_starts(in + i,"success:")) success++;
+ i = str_chr(in,'T');
+ if (case_starts(in + i,"TLS_")) tlstrans++;
+ }; break;
+ case '2': if (case_starts(in,"info msg")) {
+ i = str_chr(in,':') + 8;
+ if ((j = str_chr(in + i,' '))) in[i + j] = '\0';
+ bytes += atoi(in + i);
+ }; break;
+ case '3': if (case_starts(in,"status:")) {
+ i = str_rchr(in,'c') + 4;
+ k = str_rchr(in,'r') + 7;
+ if ((j = str_chr(in + i,'/'))) in[i + j] = '\0';
+ if (atoi(in + i) > local) local = atoi(in + i);
+ if ((j = str_chr(in + k,'/'))) in[k + j] = '\0';
+ if (atoi(in + k) > remote) remote = atoi(in + k);
+ }; break;
+ case '4': if (case_starts(in,"delivery")) {
+ i = str_chr(in,':') + 2;
+ if (case_starts(in + i,"failure:")) failure++;
+ if (case_starts(in + i,"deferral:")) deferral++;
+ }; break;
+ case '5': if (case_starts(in,"bounce msg")) bounces++;
+ if (case_starts(in,"triple bounce:")) triples++;
+ break;
+ case '6': if (case_starts(in,"delivery")) {
+ i = str_chr(in,'q');
+ if (case_starts(in + i,"qmtp:_ok")) qmtp++;
+ if (case_starts(in + i,"qmtps:_ok")) qmtps++;
+ }; break;
+ default: break;
+ }
+}
+
+void mrtg_smtplog(char *in, char flag)
+{
+ int i, j, k = 0;
+
+ i = str_chr(in,'A');
+ j = str_chr(in,'R');
+ k = str_chr(in,'P');
+
+ switch (flag) {
+ case 'a': if (case_starts(in + i,"Accept")) asessions++;
+ if (case_starts(in + j,"Reject")) rsessions++;
+ break;
+ case 'b': if (case_starts(in + i,"Accept::ORIG:")) aorig++;
+ if (case_starts(in + i,"Accept::RCPT:")) arcpt++;
+ break;
+ case 'c': if (case_starts(in + j,"Reject::SNDR::Invalid_Relay")) rsend++;
+ if (case_starts(in + j,"Reject::SNDR::Bad_Helo")) rhelo++;
+ if (case_starts(in + j,"Reject::SNDR::DNS_Helo")) rhelo++;
+ break;
+ case 'd': if (case_starts(in + j,"Reject::ORIG::Bad_Mailfrom")) rorigbad++;
+ if (case_starts(in + j,"Reject::ORIG::DNS_MF")) rorigdns++;
+ break;
+ case 'e': if (case_starts(in + j,"Reject::RCPT::Bad_Rcptto")) rrcptbad++;
+ if (case_starts(in + j,"Reject::RCPT::Failed_Rcptto")) rrcptfail++;
+ break;
+ case 'f': if (case_starts(in + j,"Reject::DATA::Invalid_Size")) rsize++;
+ if (case_starts(in + j,"Reject::DATA::Bad_MIME")) rmime++;
+ if (case_starts(in + j,"Reject::DATA::MIME_Attach")) rmime++;
+ if (case_starts(in + j,"Reject::DATA::Bad_Loader")) rloader++;
+ break;
+ case 'g': if (case_starts(in + j,"Reject::DATA::Spam_Message")) rspam++;
+ if (case_starts(in + j,"Reject::DATA::Virus_Infected")) rvirus++;
+ break;
+ case 'h': if (case_starts(in + i,"Accept::AUTH:")) aauth++;
+ if (case_starts(in + j,"Reject::AUTH:")) rauth++;
+ break;
+ case 'i': if (case_starts(in + k,"P:ESMTPS")) atls++;
+ if (case_starts(in + j,"Reject::TLS:")) rtls++;
+ break;
+ case 'j': if (case_starts(in + i,"Accept::SPF:")) spfpass++;
+ if (case_starts(in + j,"Reject::SPF:")) spfail++;
+ break;
+ case 'k': if (case_starts(in + i,"Deferred::SNDR::Grey_Listed")) grey++;
+ break;
+ case 'z': if (case_starts(in,"tcpserver") || case_starts(in,"sslserver") || case_starts(in,"rblsmtpd")) {
+ i = str_chr(in,':') + 2;
+ if (case_starts(in + i,"ok")) sok++;
+ if (case_starts(in + i,"deny")) sdeny++;
+ j = str_chr(in+i,':') + 2;
+ if (case_starts(in + i + j,"451")) rbl++;
+ if (case_starts(in + i + j,"553")) rbl++;
+ if (case_starts(in + i + j,"greetdelay:")) greet++;
+ } break;
+ default: break;
+ }
+}
+
+void mrtg_pop3log(char *in, char flag)
+{
+ int i, j = 0;
+
+ switch (flag) {
+ case 'A': i = str_chr(in,'A'); j = str_chr(in,'R');
+ if (case_starts(in + i,"Accept::AUTH:")) apop++;
+ if (case_starts(in + j,"Reject::AUTH:")) rpop++;
+ break;
+ case 'B': if (case_starts(in,"tcpserver:") || case_starts(in,"sslserver:")) {
+ i = str_chr(in,':') + 2;
+ if (case_starts(in + i,"ok")) pok++;
+ if (case_starts(in + i,"deny")) pdeny++;
+ }; break;
+ default: break;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ int c;
+ int match;
+ int enoughtime = 0;
+ unsigned long u;
+ unsigned long calltime;
+ unsigned long seconds;
+ unsigned long nanoseconds;
+ unsigned int history = 305;
+ char flag;
+
+ stralloc line = {0};
+ calltime = now();
+
+ if (argc < 2)
+ logmsg(WHO,100,USAGE,"qmail-mrtg [ -1 | -2 | -3 | -4 | -5 | -6 |\
+ -a | -b | -c | -d | -e | -f | -g | -h | -i | -j | -k | -z | -A | -B ] [time] \n\
+ qmail-mrtg needs to be called every [time] minutes (i.e. by crontab) - default 305 secs");
+
+ flag = *(argv[1] + 1);
+ if (argc == 3) { scan_ulong(argv[2],&u); history = 60 * u; }
+
+/* Read input lines sequentially */
+
+ buffer_init(&bi,read,0,bufspace,sizeof(bufspace));
+
+ for (;;) {
+ if (getln(&bi,&line,&match,'\n') != 0) _exit(1);
+ if (!match) break;
+ if (!stralloc_0(&line)) _exit(1);
+
+ seconds = 0;
+ nanoseconds = 0;
+
+ if (line.s[0] == '@') { /* tai64 timestamp */
+ for (i = 1; i <= TAI64NLEN; i++) {
+ c = (int)line.s[i];
+ u = c - '0';
+ if (u >= 10) {
+ u = c - 'a';
+ if (u >= 6) break;
+ u += 10;
+ }
+ seconds <<= 4;
+ seconds += nanoseconds >> 28;
+ nanoseconds &= 0xfffffff;
+ nanoseconds <<= 4;
+ nanoseconds += u;
+ }
+ seconds -= 4611686018427387914ULL;
+ seconds = seconds > 0 ? seconds : 0;
+ } else {
+ outs("Error: No TAI64N timestamp available.");
+ _exit(1);
+ }
+
+ if (enoughtime) { /* default history = 305 sec => evaluate logs every ~5 mins */
+ if (seconds <= calltime && seconds >= (calltime - history)) {
+ if (flag >= '1' && flag <= '9') mrtg_sendlog(line.s + TAI64NLEN + 2,flag);
+ else if (flag >= 'a' && flag <= 'z') mrtg_smtplog(line.s + TAI64NLEN + 2,flag);
+ else if (flag >= 'A' && flag <= 'Z') mrtg_pop3log(line.s + TAI64NLEN + 2,flag);
+ }
+ } else {
+ if (seconds) {
+ enoughtime++;
+ } else {
+ outs("Warning: Not enough time left between calls");
+ _exit(1);
+ }
+ }
+ }
+
+ mrtg_results(flag);
+
+ _exit(0);
+}
diff --git a/src/qmail-newmrh.c b/src/qmail-newmrh.c
new file mode 100644
index 0000000..4a74698
--- /dev/null
+++ b/src/qmail-newmrh.c
@@ -0,0 +1,75 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdio.h> // rename
+#include "logmsg.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include "open.h"
+#include "auto_qmail.h"
+#include "cdbmake.h"
+#include "case.h"
+
+#define WHO "qmail-newmrh"
+
+int rename(const char *,const char *); // stdio.h
+
+void die_read()
+{
+ logmsg(WHO,111,ERROR,"unable to read control/morercpthosts");
+}
+void die_write()
+{
+ logmsg(WHO,111,ERROR,"unable to write to control/morercpthosts.tmp");
+}
+
+char inbuf[1024];
+buffer bi;
+
+int fd;
+int fdtemp;
+
+struct cdb_make cdb;
+stralloc line = {0};
+int match;
+
+int main()
+{
+ umask(033);
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,ERROR,B("unable to chdir to: ",auto_qmail));
+
+ fd = open_read("control/morercpthosts");
+ if (fd == -1) die_read();
+
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ fdtemp = open_trunc("control/morercpthosts.tmp");
+ if (fdtemp == -1) die_write();
+
+ if (cdb_make_start(&cdb,fdtemp) == -1) die_write();
+
+ for (;;) {
+ if (getln(&bi,&line,&match,'\n') != 0) die_read();
+ case_lowerb(line.s,line.len);
+ while (line.len) {
+ if (line.s[line.len - 1] == ' ') { --line.len; continue; }
+ if (line.s[line.len - 1] == '\n') { --line.len; continue; }
+ if (line.s[line.len - 1] == '\t') { --line.len; continue; }
+ if (line.s[0] != '#')
+ if (cdb_make_add(&cdb,line.s,line.len,"",0) == -1)
+ die_write();
+ break;
+ }
+ if (!match) break;
+ }
+
+ if (cdb_make_finish(&cdb) == -1) die_write();
+ if (fsync(fdtemp) == -1) die_write();
+ if (close(fdtemp) == -1) die_write(); /* NFS stupidity */
+ if (rename("control/morercpthosts.tmp","control/morercpthosts.cdb") == -1)
+ logmsg(WHO,111,ERROR,"unable to move control/morercpthosts.tmp to control/morercpthosts.cdb");
+
+ _exit(0);
+}
diff --git a/src/qmail-newu.c b/src/qmail-newu.c
new file mode 100644
index 0000000..d5e9baa
--- /dev/null
+++ b/src/qmail-newu.c
@@ -0,0 +1,132 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdio.h> // rename
+#include "stralloc.h"
+#include "getln.h"
+#include "buffer.h"
+#include "cdbmake.h"
+#include "exit.h"
+#include "open.h"
+#include "logmsg.h"
+#include "case.h"
+#include "byte.h"
+#include "auto_qmail.h"
+
+#define WHO "qmail-newu"
+
+int rename(const char *,const char *); // stdio.h
+
+void die_chdir()
+{
+ logmsg(WHO,110,ERROR,"unable to chdir");
+}
+void die_nomem()
+{
+ logmsg(WHO,111,FATAL,"fatal: out of memory");
+}
+void die_opena()
+{
+ logmsg(WHO,112,ERROR,"unable to open users/assign");
+}
+void die_reada()
+{
+ logmsg(WHO,110,ERROR,"unable to read users/assign");
+}
+void die_format()
+{
+ logmsg(WHO,112,ERROR,"bad format in users/assign");
+}
+void die_opent()
+{
+ logmsg(WHO,112,ERROR,"unable to open users/assign.cdb.tmp");
+}
+void die_writet()
+{
+ logmsg(WHO,112,ERROR,"unable to write users/assign.cdb.tmp");
+}
+void die_rename()
+{
+ logmsg(WHO,112,ERROR,"unable to move users/cdb.tmp to users/assign.cdb");
+}
+
+struct cdb_make cdb;
+stralloc key = {0};
+stralloc data = {0};
+
+char inbuf[1024];
+buffer bi;
+
+int fd;
+int fdtemp;
+
+stralloc line = {0};
+int match;
+
+stralloc wildchars = {0};
+
+int main()
+{
+ int i;
+ int numcolons;
+
+ umask(033);
+ if (chdir(auto_qmail) == -1) die_chdir();
+
+ fd = open_read("users/assign");
+ if (fd == -1) die_opena();
+
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ fdtemp = open_trunc("users/assign.cdb.tmp");
+ if (fdtemp == -1) die_opent();
+
+ if (cdb_make_start(&cdb,fdtemp) == -1) die_writet();
+
+ if (!stralloc_copys(&wildchars,"")) die_nomem();
+
+ for (;;) {
+ if (getln(&bi,&line,&match,'\n') != 0) die_reada();
+ if (line.len && (line.s[0] == '.')) break;
+ if (!match) die_format();
+
+ if (byte_chr(line.s,line.len,'\0') < line.len) die_format();
+ i = byte_chr(line.s,line.len,':');
+ if (i == line.len) die_format();
+ if (i == 0) die_format();
+ if (!stralloc_copys(&key,"!")) die_nomem();
+ if (line.s[0] == '+') {
+ if (!stralloc_catb(&key,line.s + 1,i - 1)) die_nomem();
+ case_lowerb(key.s,key.len);
+ if (i >= 2)
+ if (byte_chr(wildchars.s,wildchars.len,line.s[i - 1]) == wildchars.len)
+ if (!stralloc_append(&wildchars,line.s + i - 1)) die_nomem();
+ }
+ else {
+ if (!stralloc_catb(&key,line.s + 1,i - 1)) die_nomem();
+ if (!stralloc_0(&key)) die_nomem();
+ case_lowerb(key.s,key.len);
+ }
+
+ if (!stralloc_copyb(&data,line.s + i + 1,line.len - i - 1)) die_nomem();
+
+ numcolons = 0;
+ for (i = 0; i < data.len; ++i)
+ if (data.s[i] == ':') {
+ data.s[i] = 0;
+ if (++numcolons == 6) break;
+ }
+ if (numcolons < 6) die_format();
+ data.len = i;
+
+ if (cdb_make_add(&cdb,key.s,key.len,data.s,data.len) == -1) die_writet();
+ }
+
+ if (cdb_make_add(&cdb,"",0,wildchars.s,wildchars.len) == -1) die_writet();
+
+ if (cdb_make_finish(&cdb) == -1) die_writet();
+ if (fsync(fdtemp) == -1) die_writet();
+ if (close(fdtemp) == -1) die_writet(); /* NFS stupidity */
+ if (rename("users/assign.cdb.tmp","users/assign.cdb") == -1) die_rename();
+
+ _exit(0);
+}
diff --git a/src/qmail-pop3d.c b/src/qmail-pop3d.c
new file mode 100644
index 0000000..2d26c16
--- /dev/null
+++ b/src/qmail-pop3d.c
@@ -0,0 +1,313 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "commands.h"
+#include "sig.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "alloc.h"
+#include "open.h"
+#include "prioq.h"
+#include "scan.h"
+#include "fmt.h"
+#include "str.h"
+#include "exit.h"
+#include "maildir.h"
+#include "timeout.h"
+
+#define FDIN 0
+#define FDOUT 1
+
+int rename(const char *,const char *); // stdio.h
+
+void die() { _exit(0); }
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutread(1200,fd,buf,len);
+ if (r <= 0) die();
+ return r;
+}
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutwrite(1200,fd,buf,len);
+ if (r <= 0) die();
+ return r;
+}
+
+char outbuf[1024];
+buffer bo = BUFFER_INIT(safewrite,FDOUT,outbuf,sizeof(outbuf));
+
+char inbuf[128];
+buffer bi = BUFFER_INIT(saferead,FDIN,inbuf,sizeof(inbuf));
+
+void out(char *buf,int len)
+{
+ buffer_put(&bo,buf,len);
+}
+void outs(char *s)
+{
+ buffer_puts(&bo,s);
+}
+void flush()
+{
+ buffer_flush(&bo);
+}
+void err(char *s)
+{
+ outs("-ERR ");
+ outs(s);
+ outs("\r\n");
+ flush();
+}
+
+void die_nomem() { err("out of memory"); die(); }
+void die_nomaildir() { err("this user has no $HOME/Maildir"); die(); }
+void die_scan() { err("unable to scan $HOME/Maildir"); die(); }
+
+void err_syntax() { err("syntax error"); }
+void err_unimpl() { err("unimplemented"); }
+void err_deleted() { err("already deleted"); }
+void err_nozero() { err("messages are counted from 1"); }
+void err_toobig() { err("not that many messages"); }
+void err_nosuch() { err("unable to open that message"); }
+void err_nounlink() { err("unable to unlink all deleted messages"); }
+
+void okay() { outs("+OK \r\n"); flush(); }
+
+void printfn(char *fn)
+{
+ fn += 4;
+ out(fn,str_chr(fn,':'));
+}
+
+char strnum[FMT_ULONG];
+stralloc line = {0};
+
+void blast(buffer *bf,unsigned long limit)
+{
+ int match;
+ int inheaders = 1;
+
+ for (;;) {
+ if (getln(bf,&line,&match,'\n') != 0) die();
+ if (!match && !line.len) break;
+ if (match) --line.len; /* no way to pass this info over POP */
+ if (limit) if (!inheaders) if (!--limit) break;
+ if (!line.len)
+ inheaders = 0;
+ else
+ if (line.s[0] == '.')
+ out(".",1);
+ out(line.s,line.len);
+ out("\r\n",2);
+
+ if (!match) break;
+ }
+ out("\r\n.\r\n",5);
+ flush();
+}
+
+stralloc filenames = {0};
+prioq pq = {0};
+
+struct message {
+ int flagdeleted;
+ unsigned long size;
+ char *fn;
+} *m;
+
+int numm;
+int last = 0;
+
+void getlist()
+{
+ struct prioq_elt pe;
+ struct stat st;
+ int i;
+
+ maildir_clean(&line);
+ if (maildir_scan(&pq,&filenames,1,1) == -1) die_scan();
+
+ numm = pq.p ? pq.len : 0;
+ m = (struct message *) alloc(numm * sizeof(struct message));
+ if (!m) die_nomem();
+
+ for (i = 0; i < numm; ++i) {
+ if (!prioq_min(&pq,&pe)) { numm = i; break; }
+ prioq_delmin(&pq);
+ m[i].fn = filenames.s + pe.id;
+ m[i].flagdeleted = 0;
+ if (stat(m[i].fn,&st) == -1)
+ m[i].size = 0;
+ else
+ m[i].size = st.st_size;
+ }
+}
+
+void pop3_stat()
+{
+ int i;
+ unsigned long total;
+
+ total = 0;
+ for (i = 0; i < numm; ++i)
+ if (!m[i].flagdeleted) total += m[i].size;
+
+ outs("+OK ");
+ out(strnum,fmt_uint(strnum,numm));
+ outs(" ");
+ out(strnum,fmt_ulong(strnum,total));
+ outs("\r\n");
+ flush();
+}
+
+void pop3_rset()
+{
+ int i;
+
+ for (i = 0; i < numm; ++i)
+ m[i].flagdeleted = 0;
+ last = 0;
+ okay();
+}
+
+void pop3_last()
+{
+ outs("+OK ");
+ out(strnum,fmt_uint(strnum,last));
+ outs("\r\n");
+ flush();
+}
+
+void pop3_quit()
+{
+ int i;
+
+ for (i = 0; i < numm; ++i)
+ if (m[i].flagdeleted) {
+ if (unlink(m[i].fn) == -1) err_nounlink();
+ } else {
+ if (str_start(m[i].fn,"new/")) {
+ if (!stralloc_copys(&line,"cur/")) die_nomem();
+ if (!stralloc_cats(&line,m[i].fn + 4)) die_nomem();
+ if (!stralloc_cats(&line,":2,")) die_nomem();
+ if (!stralloc_0(&line)) die_nomem();
+ rename(m[i].fn,line.s); /* if it fails, bummer */
+ }
+ }
+ okay();
+ die();
+}
+
+int msgno(char *arg)
+{
+ unsigned long u;
+
+ if (!scan_ulong(arg,&u)) { err_syntax(); return -1; }
+ if (!u) { err_nozero(); return -1; }
+ --u;
+ if (u >= numm) { err_toobig(); return -1; }
+ if (m[u].flagdeleted) { err_deleted(); return -1; }
+ return u;
+}
+
+void pop3_dele(char *arg)
+{
+ int i;
+
+ i = msgno(arg);
+ if (i == -1) return;
+ m[i].flagdeleted = 1;
+ if (i + 1 > last) last = i + 1;
+ okay();
+}
+
+void list(int i,int flaguidl)
+{
+ out(strnum,fmt_uint(strnum,i + 1));
+ outs(" ");
+ if (flaguidl) printfn(m[i].fn);
+ else out(strnum,fmt_ulong(strnum,m[i].size));
+ outs("\r\n");
+}
+
+void dolisting(char *arg,int flaguidl)
+{
+ unsigned int i;
+
+ if (*arg) {
+ i = msgno(arg);
+ if (i == -1) return;
+
+ outs("+OK ");
+ list(i,flaguidl);
+ } else {
+ okay();
+ for (i = 0; i < numm; ++i)
+ if (!m[i].flagdeleted) list(i,flaguidl);
+ outs(".\r\n");
+ }
+ flush();
+}
+
+void pop3_uidl(char *arg) { dolisting(arg,1); }
+void pop3_list(char *arg) { dolisting(arg,0); }
+
+char msgbuf[1024];
+buffer bm;
+
+void pop3_top(char *arg)
+{
+ int i;
+ unsigned long limit;
+ int fd;
+
+ i = msgno(arg);
+ if (i == -1) return;
+
+ arg += scan_ulong(arg,&limit);
+ while (*arg == ' ') ++arg;
+ if (scan_ulong(arg,&limit)) ++limit;
+ else limit = 0;
+
+ fd = open_read(m[i].fn);
+ if (fd == -1) { err_nosuch(); return; }
+ okay();
+ buffer_init(&bm,read,fd,msgbuf,sizeof(msgbuf));
+ blast(&bm,limit);
+ close(fd);
+}
+
+struct commands pop3commands[] = {
+ { "quit", pop3_quit, 0 }
+, { "stat", pop3_stat, 0 }
+, { "list", pop3_list, 0 }
+, { "uidl", pop3_uidl, 0 }
+, { "dele", pop3_dele, 0 }
+, { "retr", pop3_top, 0 }
+, { "rset", pop3_rset, 0 }
+, { "last", pop3_last, 0 }
+, { "top", pop3_top, 0 }
+, { "noop", okay, 0 }
+, { 0, err_unimpl, 0 }
+} ;
+
+int main(int argc,char **argv)
+{
+ sig_alarmcatch(die);
+ sig_pipeignore();
+
+ if (!argv[1]) die_nomaildir();
+ if (chdir(argv[1]) == -1) die_nomaildir();
+
+ getlist();
+
+ okay();
+ commands(&bi,pop3commands);
+ die();
+}
diff --git a/src/qmail-popup.c b/src/qmail-popup.c
new file mode 100644
index 0000000..522e719
--- /dev/null
+++ b/src/qmail-popup.c
@@ -0,0 +1,301 @@
+#include <unistd.h>
+#include "commands.h"
+#include "fd.h"
+#include "sig.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "alloc.h"
+#include "wait.h"
+#include "str.h"
+#include "byte.h"
+#include "now.h"
+#include "fmt.h"
+#include "case.h"
+#include "exit.h"
+#include "timeout.h"
+#include "env.h"
+#include "tls_start.h"
+#include "ip.h"
+
+#define PORT_POP3S "995"
+#define FDIN 0
+#define FDOUT 1
+#define FDAUTH 3
+#define FDLOG 5
+
+void die() { _exit(1); }
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutread(1200,fd,buf,len);
+ if (r <= 0) die();
+ return r;
+}
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutwrite(1200,fd,buf,len);
+ if (r <= 0) die();
+ return r;
+}
+
+char outbuf[128];
+buffer bo = BUFFER_INIT(safewrite,FDOUT,outbuf,sizeof(outbuf));
+
+char inbuf[128];
+buffer bi = BUFFER_INIT(saferead,FDIN,inbuf,sizeof(inbuf));
+
+void outs(char *s)
+{
+ buffer_puts(&bo,s);
+}
+void flush()
+{
+ buffer_flush(&bo);
+}
+void err(char *s)
+{
+ outs("-ERR ");
+ outs(s);
+ outs("\r\n");
+ flush();
+}
+
+/* Logging */
+
+stralloc protocol = {0};
+stralloc auth = {0};
+char *localport;
+char *remoteip;
+char *remotehost;
+
+char strnum[FMT_ULONG];
+char logbuf[512];
+buffer bl = BUFFER_INIT(safewrite,FDLOG,logbuf,sizeof(logbuf));
+
+void logs(char *s) { if (buffer_puts(&bl,s) == -1) _exit(1); }
+void logp(char *s) { logs(" P:"); logs(s); }
+void logh(char *s1, char *s2) { logs(" S:"); logs(s1); logs(":"); logs(s2); }
+void logu(char *s) { logs(" ?~ '"); logs(s); logs("'"); }
+void logn(char *s) { if (buffer_puts(&bl,s) == -1) _exit(1); if (buffer_flush(&bl) == -1) _exit(1); }
+void logpid() { strnum[fmt_ulong(strnum,getpid())] = 0; logs("qmail-popup: pid "); logs(strnum); logs(" "); }
+void log_pop(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6)
+ { logpid(); logs(s1); logs(s2); logp(s3); logh(s4,s5), logu(s6), logn("\n"); }
+
+void die_usage() { err("usage: popup hostname subprogram"); die(); }
+void die_nomem() { err("out of memory"); die(); }
+void die_pipe() { err("unable to open pipe"); die(); }
+void die_write() { err("unable to write pipe"); die(); }
+void die_fork() { err("unable to fork"); die(); }
+void die_childcrashed() { err("aack, child crashed"); }
+void die_badauth() { err("authorization failed"); }
+void die_tls() { err("TLS startup failed"); die(); }
+void die_notls() {
+ err("TLS required but not negotiated");
+ log_pop("Reject::STLS::","Any","POP3",remoteip,remotehost,"unknown");
+ die();
+}
+
+void err_syntax() { err("syntax error"); }
+void err_wantuser() { err("USER first"); }
+void err_authoriz() { err("authorization first"); }
+
+void okay() { outs("+OK \r\n"); flush(); }
+void pop3_quit() { okay(); die(); }
+
+void poplog_init()
+{
+ if (!stralloc_copys(&protocol,"POP3")) die_nomem();
+ localport = env_get("TCP6LOCALPORT");
+ if (!localport) localport = env_get("TCPLOCALPORT");
+ if (!localport) localport = "unknown";
+ if (!case_diffs(localport,PORT_POP3S))
+ if (!stralloc_cats(&protocol,"S")) die_nomem();
+ remoteip = env_get("TCP6REMOTEIP");
+ if (remoteip && byte_equal(remoteip,7,V4MAPPREFIX)) remoteip = remoteip + 7;
+ if (!remoteip) remoteip = env_get("TCPREMOTEIP");
+ if (!remoteip) remoteip = "unknown";
+ remotehost = env_get("TCP6REMOTEHOST");
+ if (!remotehost) remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+}
+
+char unique[FMT_ULONG + FMT_ULONG + 3];
+char *hostname;
+stralloc username = {0};
+int seenuser = 0;
+char **childargs;
+buffer ba;
+char authbuf[128];
+int stls = 0;
+int seenstls = 0;
+int apop = 0;
+
+void doanddie(char *user,unsigned int userlen,char *pass) /* userlen: including 0 byte */
+{
+ int child;
+ int wstat;
+ int pi[2];
+
+ if (fd_copy(2,1) == -1) die_pipe();
+ close(FDAUTH);
+ if (pipe(pi) == -1) die_pipe();
+ if (pi[0] != FDAUTH) die_pipe();
+ switch (child = fork()) {
+ case -1:
+ die_fork();
+ case 0:
+ close(pi[1]);
+ sig_pipedefault();
+ execvp(*childargs,childargs);
+ _exit(1);
+ }
+ close(pi[0]);
+ buffer_init(&ba,write,pi[1],authbuf,sizeof(authbuf));
+ if (buffer_put(&ba,user,userlen) == -1) die_write();
+ if (buffer_put(&ba,pass,str_len(pass) + 1) == -1) die_write();
+ if (buffer_puts(&ba,"<") == -1) die_write();
+ if (buffer_puts(&ba,unique) == -1) die_write();
+ if (buffer_puts(&ba,hostname) == -1) die_write();
+ if (buffer_put(&ba,">",2) == -1) die_write();
+ if (buffer_flush(&ba) == -1) die_write();
+ close(pi[1]);
+ byte_zero(pass,str_len(pass));
+ byte_zero(authbuf,sizeof(authbuf));
+ if (wait_pid(&wstat,child) == -1) die();
+ if (wait_crashed(wstat)) die_childcrashed();
+ if (!stralloc_0(&auth)) die_nomem();
+ if (!stralloc_0(&protocol)) die_nomem();
+ if (wait_exitcode(wstat)) {
+ die_badauth();
+ log_pop("Reject::AUTH::",auth.s,protocol.s,remoteip,remotehost,user);
+ }
+ else
+ log_pop("Accept::AUTH::",auth.s,protocol.s,remoteip,remotehost,user);
+ die();
+}
+
+void pop3_greet()
+{
+ char *s;
+ s = unique;
+ s += fmt_uint(s,getpid());
+ *s++ = '.';
+ s += fmt_ulong(s,(unsigned long) now());
+ *s++ = '@';
+ *s++ = 0;
+
+ if (!apop)
+ outs("+OK\r\n");
+ else {
+ outs("+OK <");
+ outs(unique);
+ outs(hostname);
+ outs(">\r\n");
+ }
+ flush();
+}
+
+void pop3_user(char *arg)
+{
+ if (stls == 2 && !seenstls) die_notls();
+ if (!*arg) { err_syntax(); return; }
+ okay();
+ seenuser = 1;
+ if (!stralloc_copys(&username,arg)) die_nomem();
+ if (!stralloc_0(&username)) die_nomem();
+}
+
+void pop3_pass(char *arg)
+{
+ if (!seenuser) { err_wantuser(); return; }
+ if (!*arg) { err_syntax(); return; }
+ if (!stralloc_copys(&auth,"User")) die_nomem();
+ doanddie(username.s,username.len,arg);
+}
+
+void pop3_apop(char *arg)
+{
+ char *space;
+
+ if (stls == 2 && !seenstls) die_notls();
+ space = arg + str_chr(arg,' ');
+ if (!*space) { err_syntax(); return; }
+ *space++ = 0;
+ if (!stralloc_copys(&auth,"Apop")) die_nomem();
+ doanddie(arg,space - arg,space);
+}
+
+void pop3_capa(char *arg)
+{
+ outs("+OK capability list follows\r\n");
+ outs("TOP\r\n");
+ outs("USER\r\n");
+ outs("UIDL\r\n");
+ if (apop)
+ outs("APOP\r\n");
+ if (stls > 0)
+ outs("STLS\r\n");
+ outs(".\r\n");
+ flush();
+}
+
+void pop3_stls(char *arg)
+{
+ if (stls == 0 || seenstls == 1)
+ return err("STLS not available");
+ outs("+OK starting TLS negotiation\r\n");
+ flush();
+
+ if (!starttls_init()) die_tls();
+ buffer_init(&bi,saferead,FDIN,inbuf,sizeof(inbuf));
+ seenstls = 1;
+
+/* reset state */
+ seenuser = 0;
+ if (!stralloc_cats(&protocol,"S")) die_nomem();
+}
+
+struct commands pop3commands[] = {
+ { "user", pop3_user, 0 }
+, { "pass", pop3_pass, 0 }
+, { "apop", pop3_apop, 0 }
+, { "quit", pop3_quit, 0 }
+, { "capa", pop3_capa, 0 }
+, { "stls", pop3_stls, 0 }
+, { "noop", okay, 0 }
+, { 0, err_authoriz, 0 }
+};
+
+int main(int argc,char **argv)
+{
+ char *pop3auth;
+ char *ucspitls;
+
+ sig_alarmcatch(die);
+ sig_pipeignore();
+
+ hostname = argv[1];
+ if (!hostname) die_usage();
+ childargs = argv + 2;
+ if (!*childargs) die_usage();
+
+ ucspitls = env_get("UCSPITLS");
+ if (ucspitls) {
+ stls = 1;
+ if (!case_diffs(ucspitls,"-")) stls = 0;
+ if (!case_diffs(ucspitls,"!")) stls = 2;
+ }
+
+ pop3auth = env_get("POP3AUTH");
+ if (pop3auth) {
+ if (case_starts(pop3auth,"apop")) apop = 2;
+ if (case_starts(pop3auth,"+apop")) apop = 1;
+ }
+ poplog_init();
+ pop3_greet();
+ commands(&bi,pop3commands);
+ die();
+}
diff --git a/src/qmail-postgrey.c b/src/qmail-postgrey.c
new file mode 100644
index 0000000..bd88389
--- /dev/null
+++ b/src/qmail-postgrey.c
@@ -0,0 +1,105 @@
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include "stralloc.h"
+#include "logmsg.h"
+#include "ip.h"
+#include "case.h"
+#include "str.h"
+#include "exit.h"
+#include "scan.h"
+#include "timeout.h"
+#include "timeoutconn.h"
+#include "socket_if.h"
+
+#define WHO "qmail-postgrey"
+
+#define CT 10 /* Connect timeout */
+#define WT 10 /* Write timeout */
+#define RT 10 /* Read timeout */
+
+unsigned int port = 60000; /* default port */
+
+int main(int argc, char **argv)
+{
+ struct ip4_address ip4;
+ struct ip6_address ip6;
+ stralloc query = {0};
+ char buf[64];
+ char *remoteip = 0;
+ char *netif = 0;
+ uint32 ifidx = 0;
+ int pgfd;
+ int i, j, r;
+
+ if (argc != 6)
+ logmsg(WHO,100,USAGE,"qmail-postgrey ip%ifidx;port sender recipient client_address client_name");
+
+ remoteip = argv[1];
+ i = str_chr(remoteip,':');
+ if (remoteip[i] == ':') {
+ j = str_chr(remoteip,'%'); /* IF index */
+ if (remoteip[j] == '%') {
+ remoteip[j] = 0;
+ netif = &remoteip[j + 1];
+ ifidx = socket_getifidx(netif);
+ }
+ if (!ip6_scan(remoteip,(char *)&ip6.d))
+ logmsg(WHO,111,FATAL,B("No valid IPv6 address provided: ",remoteip));
+ pgfd = socket(AF_INET6,SOCK_STREAM,0);
+ if (pgfd == -1)
+ logmsg(WHO,111,FATAL,"Can't bind to IPv6 socket.");
+ r = timeoutconn6(pgfd,(char *)&ip6.d,port,CT,ifidx);
+ } else {
+ if (!ip4_scan(remoteip,(char *)&ip4.d))
+ logmsg(WHO,111,FATAL,B("No valid IPv6 address provided: ",remoteip));
+ pgfd = socket(AF_INET,SOCK_STREAM,0);
+ if (pgfd == -1)
+ logmsg(WHO,111,FATAL,"Can't bind to IPv4 socket.");
+ r = timeoutconn4(pgfd,(char *)&ip4.d,port,CT);
+ }
+ if (r != 0) {
+ if (errno == ETIMEDOUT)
+ close(pgfd);
+ logmsg(WHO,111,FATAL,B("Can't communicate with postgrey server: ",remoteip));
+ _exit(1);
+ }
+
+ /* Provide SMTP connect vector to postgrey server */
+
+ if (!stralloc_copys(&query,"request=smtpd_access_policy\nclient_address=")) _exit(1);
+ if (!stralloc_cats(&query,argv[4])) _exit(1);
+ if (!stralloc_cats(&query,"\nclient_name=")) _exit(1);
+ if (!stralloc_cats(&query,argv[5])) _exit(1);
+ if (!stralloc_cats(&query,"\nsender=")) _exit(1);
+ if (!stralloc_cats(&query,argv[2])) _exit(1);
+ if (!stralloc_cats(&query,"\nrecipient=")) _exit(1);
+ if (!stralloc_cats(&query,argv[3])) _exit(1);
+ if (!stralloc_cats(&query,"\n\n")) _exit(1);
+
+ do {
+ r = timeoutwrite(WT,pgfd,query.s,query.len);
+ } while (r == -1 && errno == EINTR);
+
+ if (r != query.len) { close(pgfd); _exit(1); }
+
+ /* Read response */
+
+ do {
+ r = timeoutread(RT,pgfd,buf,sizeof(buf));
+ } while (r == -1 && errno == EINTR);
+
+ if (r == -1) { close(pgfd); _exit(1); }
+ close(pgfd);
+
+// logmsg(WHO,0,INFO,buf);
+
+ if (r >= 12)
+ if (!case_diffb(buf,12,"action=dunno")) _exit(0);
+ if (r >= 14)
+ if (!case_diffb(buf,14,"action=prepend")) _exit(0);
+ if (r >= 22)
+ if (!case_diffb(buf,22,"action=defer_if_permit")) _exit(10);
+
+ exit(1);
+}
diff --git a/src/qmail-pw2u.c b/src/qmail-pw2u.c
new file mode 100644
index 0000000..4ca7d25
--- /dev/null
+++ b/src/qmail-pw2u.c
@@ -0,0 +1,320 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "buffer.h"
+#include "getoptb.h"
+#include "control.h"
+#include "constmap.h"
+#include "stralloc.h"
+#include "fmt.h"
+#include "str.h"
+#include "scan.h"
+#include "open.h"
+#include "logmsg.h"
+#include "exit.h"
+#include "getln.h"
+#include "byte.h"
+#include "auto_break.h"
+#include "auto_qmail.h"
+#include "auto_usera.h"
+
+#define WHO "qmail-pw2u"
+
+void die_chdir()
+{
+ buffer_putsflush(buffer_2,"qmail-pw2u: fatal: unable to chdir\n");
+ _exit(111);
+}
+
+void die_nomem()
+{
+ buffer_putsflush(buffer_2,"qmail-pw2u: fatal: out of memory\n");
+ _exit(111);
+}
+
+void die_read()
+{
+ buffer_putsflush(buffer_2,"qmail-pw2u: fatal: unable to read input\n");
+ _exit(111);
+}
+
+void die_write()
+{
+ buffer_putsflush(buffer_2,"qmail-pw2u: fatal: unable to write output\n");
+ _exit(111);
+}
+
+void die_control()
+{
+ buffer_putsflush(buffer_2,"qmail-pw2u: fatal: unable to read controls\n");
+ _exit(111);
+}
+
+void die_alias()
+{
+ buffer_puts(buffer_2,"qmail-pw2u: fatal: unable to find ");
+ buffer_puts(buffer_2,auto_usera);
+ buffer_puts(buffer_2," user\n");
+ buffer_flush(buffer_2);
+ _exit(111);
+}
+
+void die_home(char *fn)
+{
+ buffer_puts(buffer_2,"qmail-pw2u: fatal: unable to stat ");
+ buffer_puts(buffer_2,fn);
+ buffer_puts(buffer_2,"\n");
+ buffer_flush(buffer_2);
+ _exit(111);
+}
+
+void die_user(char *s,unsigned int len)
+{
+ buffer_puts(buffer_2,"qmail-pw2u: fatal: unable to find ");
+ buffer_put(buffer_2,s,len);
+ buffer_puts(buffer_2," user for subuser\n");
+ buffer_flush(buffer_2);
+ _exit(111);
+}
+
+char *dashcolon = "-:";
+int flagalias = 0;
+int flagnoupper = 1;
+int homestrategy = 2;
+/* 2: skip if home does not exist; skip if home is not owned by user */
+/* 1: stop if home does not exist; skip if home is not owned by user */
+/* 0: don't worry about home */
+
+int okincl; stralloc incl = {0}; struct constmap mapincl;
+int okexcl; stralloc excl = {0}; struct constmap mapexcl;
+int okmana; stralloc mana = {0}; struct constmap mapmana;
+
+stralloc allusers = {0}; struct constmap mapuser;
+
+stralloc uugh = {0};
+stralloc user = {0};
+stralloc uidstr = {0};
+stralloc gidstr = {0};
+stralloc home = {0};
+unsigned long uid;
+
+stralloc line = {0};
+
+void doaccount()
+{
+ struct stat st;
+ int i;
+ char *mailnames;
+ char *x;
+ unsigned int xlen;
+
+ if (byte_chr(line.s,line.len,'\0') < line.len) return;
+
+ x = line.s; xlen = line.len; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ if (!stralloc_copyb(&user,x,i)) die_nomem();
+ if (!stralloc_0(&user)) die_nomem();
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ if (!stralloc_copyb(&uidstr,x,i)) die_nomem();
+ if (!stralloc_0(&uidstr)) die_nomem();
+ scan_ulong(uidstr.s,&uid);
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ if (!stralloc_copyb(&gidstr,x,i)) die_nomem();
+ if (!stralloc_0(&gidstr)) die_nomem();
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ if (!stralloc_copyb(&home,x,i)) die_nomem();
+ if (!stralloc_0(&home)) die_nomem();
+
+ if (!uid) return;
+ if (flagnoupper)
+ for (i = 0; i < user.len; ++i)
+ if ((user.s[i] >= 'A') && (user.s[i] <= 'Z'))
+ return;
+ if (okincl)
+ if (!constmap(&mapincl,user.s,user.len - 1))
+ return;
+ if (okexcl)
+ if (constmap(&mapexcl,user.s,user.len - 1))
+ return;
+ if (homestrategy) {
+ if (stat(home.s,&st) == -1) {
+ if (errno != ENOENT) die_home(home.s);
+ if (homestrategy == 1) die_home(home.s);
+ return;
+ }
+ if (st.st_uid != uid) return;
+ }
+
+ if (!stralloc_copys(&uugh,":")) die_nomem();
+ if (!stralloc_cats(&uugh,user.s)) die_nomem();
+ if (!stralloc_cats(&uugh,":")) die_nomem();
+ if (!stralloc_cats(&uugh,uidstr.s)) die_nomem();
+ if (!stralloc_cats(&uugh,":")) die_nomem();
+ if (!stralloc_cats(&uugh,gidstr.s)) die_nomem();
+ if (!stralloc_cats(&uugh,":")) die_nomem();
+ if (!stralloc_cats(&uugh,home.s)) die_nomem();
+ if (!stralloc_cats(&uugh,":")) die_nomem();
+
+ /* XXX: avoid recording in allusers unlein sub actually needs it */
+ if (!stralloc_cats(&allusers,user.s)) die_nomem();
+ if (!stralloc_cats(&allusers,":")) die_nomem();
+ if (!stralloc_catb(&allusers,uugh.s,uugh.len)) die_nomem();
+ if (!stralloc_0(&allusers)) die_nomem();
+
+ if (str_equal(user.s,auto_usera)) {
+ if (buffer_puts(buffer_1,"+") == -1) die_write();
+ if (buffer_put(buffer_1,uugh.s,uugh.len) == -1) die_write();
+ if (buffer_puts(buffer_1,dashcolon) == -1) die_write();
+ if (buffer_puts(buffer_1,":\n") == -1) die_write();
+ flagalias = 1;
+ }
+
+ mailnames = 0;
+ if (okmana)
+ mailnames = constmap(&mapmana,user.s,user.len - 1);
+ if (!mailnames)
+ mailnames = user.s;
+
+ for (;;) {
+ while (*mailnames == ':') ++mailnames;
+ if (!*mailnames) break;
+
+ i = str_chr(mailnames,':');
+
+ if (buffer_puts(buffer_1,"=") == -1) die_write();
+ if (buffer_put(buffer_1,mailnames,i) == -1) die_write();
+ if (buffer_put(buffer_1,uugh.s,uugh.len) == -1) die_write();
+ if (buffer_puts(buffer_1,"::\n") == -1) die_write();
+
+ if (*auto_break) {
+ if (buffer_puts(buffer_1,"+") == -1) die_write();
+ if (buffer_put(buffer_1,mailnames,i) == -1) die_write();
+ if (buffer_put(buffer_1,auto_break,1) == -1) die_write();
+ if (buffer_put(buffer_1,uugh.s,uugh.len) == -1) die_write();
+ if (buffer_puts(buffer_1,dashcolon) == -1) die_write();
+ if (buffer_puts(buffer_1,":\n") == -1) die_write();
+ }
+
+ mailnames += i;
+ }
+}
+
+stralloc sub = {0};
+
+void dosubuser()
+{
+ int i;
+ char *x;
+ unsigned int xlen;
+ char *uugh;
+
+ x = line.s; xlen = line.len; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ if (!stralloc_copyb(&sub,x,i)) die_nomem();
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+ uugh = constmap(&mapuser,x,i);
+ if (!uugh) die_user(x,i);
+ ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+
+ if (buffer_puts(buffer_1,"=") == -1) die_write();
+ if (buffer_put(buffer_1,sub.s,sub.len) == -1) die_write();
+ if (buffer_puts(buffer_1,uugh) == -1) die_write();
+ if (buffer_puts(buffer_1,dashcolon) == -1) die_write();
+ if (buffer_put(buffer_1,x,i) == -1) die_write();
+ if (buffer_puts(buffer_1,":\n") == -1) die_write();
+
+ if (*auto_break) {
+ if (buffer_puts(buffer_1,"+") == -1) die_write();
+ if (buffer_put(buffer_1,sub.s,sub.len) == -1) die_write();
+ if (buffer_put(buffer_1,auto_break,1) == -1) die_write();
+ if (buffer_puts(buffer_1,uugh) == -1) die_write();
+ if (buffer_puts(buffer_1,dashcolon) == -1) die_write();
+ if (buffer_put(buffer_1,x,i) == -1) die_write();
+ if (buffer_puts(buffer_1,"-:\n") == -1) die_write();
+ }
+}
+
+int fd;
+char inbuf[BUFFER_INSIZE];
+buffer in;
+
+int main(int argc,char **argv)
+{
+ int opt;
+ int match;
+
+ while ((opt = getopt(argc,argv,"/ohHuUc:C")) != opteof)
+ switch (opt) {
+ case '/': dashcolon = "-/:"; break;
+ case 'o': homestrategy = 2; break;
+ case 'h': homestrategy = 1; break;
+ case 'H': homestrategy = 0; break;
+ case 'u': flagnoupper = 0; break;
+ case 'U': flagnoupper = 1; break;
+ case 'c': *auto_break = *optarg; break;
+ case 'C': *auto_break = 0; break;
+ case '?':
+ default:
+ _exit(100);
+ }
+
+ if (chdir(auto_qmail) == -1) die_chdir();
+
+ /* no need for control_init() */
+
+ okincl = control_readfile(&incl,"users/include",0);
+ if (okincl == -1) die_control();
+ if (okincl) if (!constmap_init(&mapincl,incl.s,incl.len,0)) die_nomem();
+
+ okexcl = control_readfile(&excl,"users/exclude",0);
+ if (okexcl == -1) die_control();
+ if (okexcl) if (!constmap_init(&mapexcl,excl.s,excl.len,0)) die_nomem();
+
+ okmana = control_readfile(&mana,"users/mailnames",0);
+ if (okmana == -1) die_control();
+ if (okmana) if (!constmap_init(&mapmana,mana.s,mana.len,1)) die_nomem();
+
+ if (!stralloc_copys(&allusers,"")) die_nomem();
+
+ for (;;) {
+ if (getln(buffer_0,&line,&match,'\n') == -1) die_read();
+ doaccount();
+ if (!match) break;
+ }
+ if (!flagalias) die_alias();
+
+ fd = open_read("users/subusers");
+ if (fd == -1) {
+ if (errno != ENOENT) die_control();
+ }
+ else {
+ buffer_init(&in,read,fd,inbuf,sizeof(inbuf));
+
+ if (!constmap_init(&mapuser,allusers.s,allusers.len,1)) die_nomem();
+
+ for (;;) {
+ if (getln(&in,&line,&match,'\n') == -1) die_read();
+ dosubuser();
+ if (!match) break;
+ }
+
+ close(fd);
+ }
+
+ fd = open_read("users/append");
+ if (fd == -1) {
+ if (errno != ENOENT) die_control();
+ }
+ else {
+ buffer_init(&in,read,fd,inbuf,sizeof(inbuf));
+ for (;;) {
+ if (getln(&in,&line,&match,'\n') == -1) die_read();
+ if (buffer_put(buffer_1,line.s,line.len) == -1) die_write();
+ if (!match) break;
+ }
+ }
+
+ if (buffer_puts(buffer_1,".\n") == -1) die_write();
+ if (buffer_flush(buffer_1) == -1) die_write();
+ _exit(0);
+}
diff --git a/src/qmail-qmaint.c b/src/qmail-qmaint.c
new file mode 100644
index 0000000..e83ab6f
--- /dev/null
+++ b/src/qmail-qmaint.c
@@ -0,0 +1,594 @@
+/*
+ Based on an implementation of queue-fix 1.2 by Eric Huss
+*/
+#include <unistd.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <grp.h>
+#include "stralloc.h"
+#include "direntry.h"
+#include "fmt.h"
+#include "fmtqfn.h"
+#include "error.h"
+#include "buffer.h"
+#include "getln.h"
+#include "str.h"
+#include "open.h"
+#include "fifo.h"
+#include "scan.h"
+#include "readsubdir.h"
+#include "logmsg.h"
+#include "exit.h"
+#include "auto_qmail.h"
+#include "auto_split.h"
+#include "auto_uids.h"
+
+#define WHO "qmail-qmaint"
+
+stralloc queue_dir = {0}; /*the root queue dir with trailing slash*/
+stralloc check_dir = {0}; /*the current directory being checked*/
+stralloc temp_dirname = {0}; /*temporary used for checking directories */
+stralloc temp_filename = {0}; /*temporary used for checking individuals*/
+stralloc old_name = {0}; /*used in rename*/
+stralloc new_name = {0}; /*used in rename*/
+stralloc mess_dir = {0}; /*used for renaming in mess dir*/
+stralloc query = {0}; /*used in interactive query function*/
+
+char strnum[FMT_ULONG];
+int flag_interactive = 0;
+int flag_dircreate = 0;
+int flag_filecreate = 0;
+int flag_permfix = 0;
+int flag_namefix = 0;
+int flag_delete = 0;
+
+int qmailq_uid;
+int qmails_uid;
+int qmailr_uid;
+int qmail_gid;
+int split_num;
+
+void die_make(char *name)
+{
+ logmsg(WHO,111,ERROR,B("Failed to make: ",name));
+}
+
+void die_user(char *user)
+{
+ logmsg(WHO,111,ERROR,B("Failed to determine uid of: ",user));
+}
+
+void die_group(char *group)
+{
+ logmsg(WHO,111,ERROR,B("Failed to determine gid of: ",group));
+}
+
+void die_check()
+{
+ logmsg(WHO,111,ERROR,"Failed while checking directory structure. \nEnsure the given queue exists and you have permission to access it.");
+}
+
+void die_recon()
+{
+ logmsg(WHO,110,ERROR,"Failed to reconstruct queue. \nEnsure the queue exists and you have permission to modify it.");
+}
+
+void die_nomem()
+{
+ logmsg(WHO,110,ERROR,"Out of memory.");
+}
+
+/*returns 1==yes, 0==no*/
+
+int confirm()
+{
+ int match;
+
+ if (getln(buffer_0,&query,&match,'\n')) return 0;
+ if (!match) return 0;
+ if (query.s[0] == 'y' || query.s[0] == 'Y' || query.s[0] == '\n') return 1;
+ return 0;
+}
+
+/*gid may be -1 on files for "unknown*/
+
+#define DIRS logmsg(WHO,0,WARN,"It looks like some directories don't exist, should I create them? (Y/n)")
+#define FILES logmsg(WHO,0,WARN,"It looks like some files don't exist, should I create them? (Y/n)")
+
+#define PERMS logmsg(WHO,0,WARN,B("It looks like permissions are wrong for ",name," should I fix them? (Y/n)"))
+#define CPERMS logmsg(WHO,0,WARN,B("Changing permissions: ",name," => ",pnum))
+
+#define OWNER logmsg(WHO,0,WARN,B("It looks like ownerships are wrong for ",name," should I fix them? (Y/n)"))
+#define COWNER logmsg(WHO,0,WARN,B("Changing ownership: ",name," => ",unum,"/",gnum))
+
+int check_item(char *name,int uid,int gid,int perm,char type,int size)
+{
+ struct stat st;
+ int fd;
+ char num[12];
+ char unum[12];
+ char gnum[12];
+ char pnum[12];
+
+ /*check for existence and proper credentials*/
+
+ strnum[fmt_ulong(unum,uid)] = 0;
+ strnum[fmt_ulong(gnum,gid)] = 0;
+ strnum[fmt_ulong(pnum,perm)] = 0;
+
+ switch (type) {
+ case 'd': /*directory*/
+ if (stat(name,&st)) {
+ if (errno != ENOENT) return -1;
+ if (!flag_dircreate && flag_interactive) {
+ DIRS; if (!confirm()) return -1;
+ flag_dircreate = 1;
+ }
+ /*create it*/
+ logmsg(WHO,0,INFO,B("Creating directory: ",name));
+ if (mkdir(name,perm)) die_make(name);
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ return 0;
+ }
+ /*check the values*/
+ if (st.st_uid != uid || st.st_gid != gid) {
+ if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; }
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ }
+ if ((st.st_mode & 07777) != perm) {
+ if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; }
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ }
+ return 0;
+ case 'f': /*regular file*/
+ if (stat(name,&st)) return -1;
+ /*check the values*/
+ if (st.st_uid != uid || (st.st_gid != gid && gid != -1)) {
+ if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; }
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ }
+ if ((st.st_mode & 07777) != perm) {
+ if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; }
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ }
+ return 0;
+ case 'z': /*regular file with a size*/
+ if (stat(name,&st)) {
+ if (errno != ENOENT) return -1;
+ if (!flag_filecreate && flag_interactive) {
+ FILES; if (!confirm()) return -1;
+ flag_filecreate = 1;
+ }
+ /*create it*/
+
+ strnum[fmt_ulong(num,size)] = 0;
+ logmsg(WHO,0,INFO,B("Creating: ",name," with size ",num));
+ fd = open_trunc(name);
+ if (fd == -1) die_make(name);
+ while (size--) { if (write(fd,"",1)!=1) die_make(name); }
+ close(fd);
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ return 0;
+ }
+ /*check the values*/
+ if (st.st_uid != uid || (st.st_gid != gid && gid != -1)) {
+ if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; }
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ }
+ if ((st.st_mode & 07777) != perm) {
+ if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; }
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ }
+ if (st.st_size != size) {
+ logmsg(WHO,0,WARN,B("File ",name," has not the right size. I will not fix it, please investigate."));
+ }
+ return 0;
+ case 'p': /*a named pipe*/
+ if (stat(name,&st)) {
+ if (errno != ENOENT) return -1;
+ if (!flag_filecreate && flag_interactive) {
+ FILES; if (!confirm()) return -1;
+ flag_filecreate = 1;
+ }
+ /*create it*/
+ logmsg(WHO,INFO,0,B("Creating fifo: ",name));
+ if (fifo_make(name,perm)) die_make(name);
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ return 0;
+ }
+ /*check the values*/
+ if (st.st_uid != uid || (st.st_gid != gid && gid != -1)) {
+ if (!flag_permfix && flag_interactive) { OWNER; if (!confirm()) return -1; flag_permfix = 1; }
+ COWNER; if (chown(name,uid,gid)) die_make(name);
+ }
+ if ((st.st_mode & 07777) != perm) {
+ if (!flag_permfix && flag_interactive) { PERMS; if (!confirm()) return -1; flag_permfix = 1; }
+ CPERMS; if (chmod(name,perm)) die_make(name);
+ }
+ return 0;
+ }
+
+ return 0;
+}
+
+int check_files(char * directory,int uid,int gid,int perm)
+{
+ DIR *dir;
+ direntry *d;
+
+ dir = opendir(directory);
+
+ if (!dir) return -1;
+ while ((d = readdir(dir))) {
+ if (d->d_name[0] == '.') continue;
+ if (!stralloc_copys(&temp_filename,directory)) die_nomem();
+ if (!stralloc_append(&temp_filename,"/")) die_nomem();
+ if (!stralloc_cats(&temp_filename,d->d_name)) die_nomem();
+ if (!stralloc_0(&temp_filename)) die_nomem();
+ if (check_item(temp_filename.s,uid,gid,perm,'f',0)) { closedir(dir); return -1; }
+ }
+ closedir(dir);
+ return 0;
+}
+
+void warn_files(char * directory)
+{
+ DIR *dir;
+ direntry *d;
+ int found = 0;
+
+ dir = opendir(directory);
+ if (!dir) return;
+
+ while ((d = readdir(dir))) {
+ if (d->d_name[0] == '.') continue;
+ found = 1;
+ break;
+ }
+
+ closedir(dir);
+
+ if (found)
+ logmsg(WHO,0,WARN,B("Found files in ",directory," that shouldn't be there. I will not remove them. You should consider checking it out."));
+}
+
+int check_splits(char * directory,int dir_uid,int dir_gid,int dir_perm,int file_gid,int file_perm)
+{
+ DIR *dir;
+ direntry *d;
+ int i;
+
+ for (i = 0; i < split_num ; i++) {
+ strnum[fmt_ulong(strnum,i)] = 0;
+ if (!stralloc_copys(&temp_dirname,directory)) die_nomem();
+ if (!stralloc_append(&temp_dirname,"/")) die_nomem();
+ if (!stralloc_cats(&temp_dirname,strnum)) die_nomem();
+ if (!stralloc_0(&temp_dirname)) die_nomem();
+
+ /*check the split dir*/
+ if (check_item(temp_dirname.s,dir_uid,dir_gid,dir_perm,'d',0)) return -1;
+
+ /*check its contents*/
+ dir = opendir(temp_dirname.s);
+ if (!dir) return -1;
+ while ((d = readdir(dir))) {
+ if (d->d_name[0] == '.') continue;
+ if (!stralloc_copys(&temp_filename,temp_dirname.s)) die_nomem();
+ if (!stralloc_append(&temp_filename,"/")) die_nomem();
+ if (!stralloc_cats(&temp_filename,d->d_name)) die_nomem();
+ if (!stralloc_0(&temp_filename)) die_nomem();
+ if (check_item(temp_filename.s,dir_uid,file_gid,file_perm,'f',0)) { closedir(dir); return -1; }
+ }
+ closedir(dir);
+ }
+
+ return 0;
+}
+
+int rename_mess(char *dir, char *part, char *new_part, char *old_filename, char *new_filename)
+{
+
+ if (flag_interactive && !flag_namefix) {
+ logmsg(WHO,0,INFO,"It looks like some files need to be renamed, should I rename them? (Y/n)\n");
+ if (!confirm()) return -1;
+ flag_namefix = 1;
+ }
+
+ /*prepare the old filename*/
+ if (!stralloc_copy(&old_name,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&old_name,dir)) die_nomem();
+ if (!stralloc_cats(&old_name,part)) die_nomem();
+ if (!stralloc_append(&old_name,"/")) die_nomem();
+ if (!stralloc_cats(&old_name,old_filename)) die_nomem();
+ if (!stralloc_0(&old_name)) die_nomem();
+
+ /*prepare the new filename*/
+ if (!stralloc_copy(&new_name,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&new_name,dir)) die_nomem();
+ if (!stralloc_cats(&new_name,new_part)) die_nomem();
+ if (!stralloc_append(&new_name,"/")) die_nomem();
+ if (!stralloc_cats(&new_name,new_filename)) die_nomem();
+ if (!stralloc_0(&new_name)) die_nomem();
+
+ logmsg(WHO,0,INFO,B("Renaming ",old_name.s," to ",new_name.s));
+ if (rename(old_name.s,new_name.s)) {
+ if (errno != ENOENT) return -1;
+ }
+
+ return 0;
+}
+
+int fix_part(char *part)
+{
+ DIR *dir;
+ direntry *d;
+ struct stat st;
+ char inode[FMT_ULONG];
+ char new_part[FMT_ULONG];
+ int old_inode;
+ int part_num;
+ int correct_part_num;
+
+ scan_uint(part,&part_num);
+
+ if (!stralloc_copy(&mess_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&mess_dir,"mess/")) die_nomem();
+ if (!stralloc_cats(&mess_dir,part)) die_nomem();
+ if (!stralloc_0(&mess_dir)) die_nomem();
+
+ dir = opendir(mess_dir.s);
+ if (!dir) return -1;
+
+ while ((d = readdir(dir))) {
+ if (d->d_name[0] == '.') continue;
+ /*check from mess*/
+ if (!stralloc_copys(&temp_filename,mess_dir.s)) die_nomem();
+ if (!stralloc_append(&temp_filename,"/")) die_nomem();
+ if (!stralloc_cats(&temp_filename,d->d_name)) die_nomem();
+ if (!stralloc_0(&temp_filename)) die_nomem();
+ if (stat(temp_filename.s,&st)) { closedir(dir); return -1; }
+
+ /*check that filename == inode number*/
+ /*check that inode%auto_split == part_num*/
+ scan_uint(d->d_name,&old_inode);
+ correct_part_num = st.st_ino % split_num;
+ if (st.st_ino != old_inode || part_num != correct_part_num) {
+ /*rename*/
+ inode[fmt_ulong(inode,st.st_ino)] = 0;
+ new_part[fmt_ulong(new_part,correct_part_num)] = 0;
+ if (rename_mess("mess/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; }
+ if (rename_mess("info/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; }
+ if (rename_mess("local/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; }
+ if (rename_mess("remote/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; }
+ if (rename_mess("todo/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; }
+ if (rename_mess("intd/",part,new_part,d->d_name,inode)) { closedir(dir); return -1; }
+
+ if (rename_mess("bounce","","",d->d_name,inode)) { closedir(dir); return -1; }
+ }
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+int fix_names()
+{
+ int i;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"mess")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+
+ for (i = 0; i < split_num; i++) {
+ strnum[fmt_ulong(strnum,i)] = 0;
+ if (fix_part(strnum)) return -1;
+ }
+
+ return 0;
+}
+
+int check_dirs()
+{
+ /*check root existence*/
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0750,'d',0)) return -1;
+
+ /*check the bigtodo queue */
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"info")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmails_uid,qmail_gid,0700,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmails_uid,qmail_gid,0700,qmail_gid,0600)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"mess")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0750,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmailq_uid,qmail_gid,0750,-1,0644)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"remote")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmails_uid,qmail_gid,0700,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmails_uid,qmail_gid,0700,qmail_gid,0600)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"local")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmails_uid,qmail_gid,0700,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmails_uid,qmail_gid,0700,qmail_gid,0600)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"intd")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0700,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmailq_uid,qmail_gid,0700,qmail_gid,0600)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"todo")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0750,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmailq_uid,qmail_gid,0750,-1,0644)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"dkim")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0750,'d',0)) return -1;
+ if (check_splits(check_dir.s,qmailq_uid,qmail_gid,0750,qmail_gid,0644)) return -1;
+
+ /*check the others*/
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"bounce")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmails_uid,qmail_gid,0700,'d',0)) return -1;
+ if (check_files(check_dir.s,qmails_uid,qmail_gid,0600)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"pid")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0700,'d',0)) return -1;
+
+ warn_files(check_dir.s);
+
+ /*lock has special files that must exist*/
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"lock")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailq_uid,qmail_gid,0750,'d',0)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"lock/sendmutex")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmails_uid,qmail_gid,0600,'z',0)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"lock/tcpto")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmailr_uid,qmail_gid,0644,'z',1024)) return -1;
+
+ if (!stralloc_copy(&check_dir,&queue_dir)) die_nomem();
+ if (!stralloc_cats(&check_dir,"lock/trigger")) die_nomem();
+ if (!stralloc_0(&check_dir)) die_nomem();
+ if (check_item(check_dir.s,qmails_uid,qmail_gid,0622,'p',0)) return -1;
+
+ return 0;
+}
+
+/* stolen from qmail-send */
+
+stralloc fn = {0};
+
+void fnmake_init() { while (!stralloc_ready(&fn,FMTQFN)) die_nomem(); }
+void fnmake_local(unsigned long id) { fn.len = fmtqfn(fn.s,"local/",id,1); }
+void fnmake_remote(unsigned long id) { fn.len = fmtqfn(fn.s,"remote/",id,1); }
+void fnmake_mess(unsigned long id) { fn.len = fmtqfn(fn.s,"mess/",id,1); }
+void fnmake_dkim(unsigned long id) { fn.len = fmtqfn(fn.s,"dkim/",id,1); }
+void fnmake_info(unsigned long id) { fn.len = fmtqfn(fn.s,"info/",id,1); }
+void fnmake_bounce(unsigned long id) { fn.len = fmtqfn(fn.s,"bounce/",id,0); }
+
+void warn_unlink(unsigned long id)
+{
+ char foo[FMT_ULONG];
+ foo[fmt_ulong(foo,id)] = 0;
+ logmsg(WHO,99,WARN,B("no such file to unlink #",foo));
+}
+
+void err_unlink(unsigned long id)
+{
+ char foo[FMT_ULONG];
+ foo[fmt_ulong(foo,id)] = 0;
+ logmsg(WHO,100,ERROR,B("trouble with unlinking #",foo));
+}
+
+void err_chdir()
+{
+ logmsg(WHO,110,FATAL,"unable to chdir");
+}
+
+int delete_msg(unsigned long id)
+{
+ struct stat st;
+ int bounce = 1;
+
+ if (chdir(auto_qmail) == -1) err_chdir();
+ if (chdir("queue") == -1) err_chdir();
+ fnmake_init();
+
+ fnmake_mess(id); // regular message pre-processed
+ if (stat(fn.s,&st) == -1) err_unlink(id);
+ else bounce = 0;
+ if (!bounce && unlink(fn.s) == -1)
+ if (errno != ENOENT) err_unlink(id);
+
+ fnmake_info(id); // not delivered yet
+ if (!stat(fn.s,&st))
+ if (unlink(fn.s) == -1)
+ if (errno != ENOENT) err_unlink(id);
+
+ if (bounce) {
+ fnmake_bounce(id);
+ if (!stat(fn.s,&st)) { warn_unlink(id); return 1; }
+ if (unlink(fn.s) == -1)
+ if (errno != ENOENT) err_unlink(id);
+ }
+
+ fnmake_remote(id);
+ if (!stat(fn.s,&st))
+ if (unlink(fn.s) == -1)
+ if (errno != ENOENT) err_unlink(id);
+
+ fnmake_local(id);
+ if (!stat(fn.s,&st))
+ if (unlink(fn.s) == -1)
+ if (errno != ENOENT) err_unlink(id);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ char *mess = 0;
+ unsigned long id = 0;
+
+ if (argc > 1) {
+ if (!str_diff(argv[1],"-i")) {
+ flag_interactive = 1;
+ } else if (!str_diff(argv[1],"-d")) {
+ if (!argv[2]) logmsg(WHO,111,USAGE,"qmail-qmaint [-i] || [-d messid]");
+ mess = argv[2];
+ flag_delete = 1;
+ scan_ulong(mess,&id);
+ }
+ }
+
+ if (!stralloc_copys(&queue_dir,auto_qmail)) die_nomem();
+ if (!stralloc_cats(&queue_dir,"/queue/")) die_nomem();
+
+ logmsg(WHO,0,INFO,B("Checking s/qmail queue at: ",auto_qmail,"/queue/"));
+
+ /* get constants */
+
+ qmailq_uid = auto_uidq;
+ qmails_uid = auto_uids;
+ qmailr_uid = auto_uidr;
+ qmail_gid = auto_gidq;
+ split_num = auto_split;
+
+ /*check that all the proper directories exist with proper credentials*/
+
+ if (check_dirs()) die_check();
+
+ if (flag_delete) {
+ if (!delete_msg(id))
+ logmsg(WHO,0,INFO,B("file ",mess," from queue deleted."));
+ } else
+ if (fix_names()) die_check();
+
+ logmsg(WHO,0,INFO,"done.");
+
+ _exit (0);
+}
diff --git a/src/qmail-qmqpc.c b/src/qmail-qmqpc.c
new file mode 100644
index 0000000..c92e072
--- /dev/null
+++ b/src/qmail-qmqpc.c
@@ -0,0 +1,178 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include "stralloc.h"
+#include "readclose.h"
+#include "timeoutconn.h"
+#include "logmsg.h"
+#include "str.h"
+#include "sig.h"
+#include "ip.h"
+#include "timeout.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "fmt.h"
+#include "uint_t.h"
+#include "socket_if.h"
+
+#define PORT_QMQP 628
+
+void die_success() { _exit(0); }
+void die_perm() { _exit(31); }
+void nomem() { _exit(51); }
+void die_read() { if (errno == ENOMEM) nomem(); _exit(54); }
+void die_control() { _exit(55); }
+void die_socket() { _exit(56); }
+void die_home() { _exit(61); }
+void die_temp() { _exit(71); }
+void die_conn() { _exit(74); }
+void die_format() { _exit(91); }
+
+int lasterror = 55;
+int qmqpfd;
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutread(60,qmqpfd,buf,len);
+ if (r <= 0) die_conn();
+ return r;
+}
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutwrite(60,qmqpfd,buf,len);
+ if (r <= 0) die_conn();
+ return r;
+}
+
+char buf[1024];
+buffer bo = BUFFER_INIT(safewrite,-1,buf,sizeof(buf));
+buffer bi = BUFFER_INIT(saferead,-1,buf,sizeof(buf));
+buffer be = BUFFER_INIT(read,1,buf,sizeof(buf)); // envelope
+/* WARNING: can use only one of these at a time! */
+
+stralloc beforemessage = {0};
+stralloc message = {0};
+stralloc aftermessage = {0};
+
+char strnum[FMT_ULONG];
+stralloc line = {0};
+
+void getmess()
+{
+ int match;
+
+ if (readclose_append(0,&message,1024) == -1) die_read();
+
+ strnum[fmt_ulong(strnum,(unsigned long) message.len)] = 0;
+ if (!stralloc_copys(&beforemessage,strnum)) nomem();
+ if (!stralloc_cats(&beforemessage,":")) nomem();
+ if (!stralloc_copys(&aftermessage,",")) nomem();
+
+ if (getln(&be,&line,&match,'\0') == -1) die_read();
+ if (!match) die_format();
+ if (line.len < 2) die_format();
+ if (line.s[0] != 'F') die_format();
+
+ strnum[fmt_ulong(strnum,(unsigned long) line.len - 2)] = 0;
+ if (!stralloc_cats(&aftermessage,strnum)) nomem();
+ if (!stralloc_cats(&aftermessage,":")) nomem();
+ if (!stralloc_catb(&aftermessage,line.s + 1,line.len - 2)) nomem();
+ if (!stralloc_cats(&aftermessage,",")) nomem();
+
+ for (;;) {
+ if (getln(&be,&line,&match,'\0') == -1) die_read();
+ if (!match) die_format();
+ if (line.len < 2) break;
+ if (line.s[0] != 'T') die_format();
+
+ strnum[fmt_ulong(strnum,(unsigned long) line.len - 2)] = 0;
+ if (!stralloc_cats(&aftermessage,strnum)) nomem();
+ if (!stralloc_cats(&aftermessage,":")) nomem();
+ if (!stralloc_catb(&aftermessage,line.s + 1,line.len - 2)) nomem();
+ if (!stralloc_cats(&aftermessage,",")) nomem();
+ }
+}
+
+void doit(char *server)
+{
+ struct ip4_address ip4s;
+ struct ip6_address ip6s;
+ char *netif = 0;
+ uint32 ifidx = 0;
+ char ch;
+ int i, j, r;
+
+ i = str_chr(server,':');
+ if (server[i] == ':') {
+ j = str_chr(server,'%'); /* IF index */
+ if (server[j] == '%') {
+ server[j] = 0;
+ netif = &server[j + 1];
+ ifidx = socket_getifidx(netif);
+ }
+ if (!ip6_scan(server,(char *)&ip6s.d)) return;
+ qmqpfd = socket(AF_INET6,SOCK_STREAM,0);
+ if (qmqpfd == -1) die_socket();
+ r = timeoutconn6(qmqpfd,(char *)&ip6s.d,PORT_QMQP,10,ifidx);
+ } else {
+ if (!ip4_scan(server,(char *)&ip4s.d)) return;
+ qmqpfd = socket(AF_INET,SOCK_STREAM,0);
+ if (qmqpfd == -1) die_socket();
+ r = timeoutconn4(qmqpfd,(char *)&ip4s.d,PORT_QMQP,10);
+ }
+ if (r != 0) {
+ lasterror = 73;
+ if (errno == ETIMEDOUT) lasterror = 72;
+ close(qmqpfd);
+ return;
+ }
+
+ strnum[fmt_ulong(strnum,(unsigned long) (beforemessage.len + message.len + aftermessage.len))] = 0;
+ buffer_puts(&bo,strnum);
+ buffer_puts(&bo,":");
+ buffer_put(&bo,beforemessage.s,beforemessage.len);
+ buffer_put(&bo,message.s,message.len);
+ buffer_put(&bo,aftermessage.s,aftermessage.len);
+ buffer_puts(&bo,",");
+ buffer_flush(&bo);
+
+ for (;;) {
+ buffer_get(&bi,&ch,1);
+ if (ch == 'K') die_success();
+ if (ch == 'Z') die_temp();
+ if (ch == 'D') die_perm();
+ }
+}
+
+stralloc servers = {0};
+
+int main()
+{
+ int i;
+ int j;
+
+ sig_pipeignore();
+
+ if (chdir(auto_qmail) == -1) die_home();
+ if (control_init() == -1) die_control();
+ if (control_readfile(&servers,"control/qmqpservers",0) != 1) die_control();
+
+ getmess();
+
+ i = 0;
+ for (j = 0; j < servers.len; ++j)
+ if (!servers.s[j]) {
+ doit(servers.s + i);
+ i = j + 1;
+ }
+
+ _exit(lasterror);
+}
diff --git a/src/qmail-qmqpd.c b/src/qmail-qmqpd.c
new file mode 100644
index 0000000..86404a9
--- /dev/null
+++ b/src/qmail-qmqpd.c
@@ -0,0 +1,190 @@
+#include <unistd.h>
+#include "auto_qmail.h"
+#include "qmail.h"
+#include "received.h"
+#include "sig.h"
+#include "buffer.h"
+#include "exit.h"
+#include "now.h"
+#include "fmt.h"
+#include "env.h"
+#include "case.h"
+#include "byte.h"
+#include "ip.h"
+#include "str.h"
+
+#define PORT_QMQP "628"
+
+void resources() { _exit(111); }
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = write(fd,buf,len);
+ if (r <= 0) _exit(0);
+ return r;
+}
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ r = read(fd,buf,len);
+ if (r <= 0) _exit(0);
+ return r;
+}
+
+char inbuf[512];
+buffer bi = BUFFER_INIT(saferead,0,inbuf,sizeof(inbuf));
+char outbuf[256];
+buffer bo = BUFFER_INIT(safewrite,1,outbuf,sizeof(outbuf));
+
+unsigned long bytesleft = 100;
+
+void getbyte(char *ch)
+{
+ if (!bytesleft--) _exit(100);
+ buffer_get(&bi,ch,1);
+}
+
+unsigned long getlen()
+{
+ unsigned long len = 0;
+ char ch;
+
+ for (;;) {
+ getbyte(&ch);
+ if (ch == ':') return len;
+ if (len > 200000000) resources();
+ len = 10 * len + (ch - '0');
+ }
+}
+
+void getcomma()
+{
+ char ch;
+ getbyte(&ch);
+ if (ch != ',') _exit(100);
+}
+
+struct qmail qq;
+
+void identify()
+{
+ char *remotehost;
+ char *remoteinfo;
+ char *remoteip;
+ char *local;
+ char *localport;
+
+ remotehost = env_get("TCP6REMOTEHOST");
+ if (!remotehost) remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+ remoteinfo = env_get("TCP6REMOTEINFO");
+ if (!remoteinfo) remoteinfo = env_get("TCPREMOTEINFO");
+ remoteip = env_get("TCP6REMOTEIP");
+ if (remoteip && byte_equal(remoteip,7,V4MAPPREFIX)) remoteip=remoteip+7;
+ if (!remoteip) remoteip = env_get("TCPREMOTEIP");
+ if (!remoteip) remoteip = "unknown";
+ local = env_get("TCP6LOCALHOST");
+ if (!local) local = env_get("TCPLOCALHOST");
+ if (!local) local = env_get("TCP6LOCALIP");
+ if (!local) local = env_get("TCPLOCALIP");
+ if (!local) local = "unknown";
+ localport = env_get("TCP6LOCALPORT");
+ if (!localport) localport = env_get("TCPLOCALPORT");
+ if (!localport) localport = "0";
+
+ received(&qq,"QMQP",local,remoteip,remotehost,remoteinfo,(char *) 0,(char *) 0,(char *) 0);
+}
+
+char buf[1000];
+char strnum[FMT_ULONG];
+
+int getbuf()
+{
+ unsigned long len;
+ int i;
+
+ len = getlen();
+ if (len >= 1000) {
+ for (i = 0; i < len; ++i) getbyte(buf);
+ getcomma();
+ buf[0] = 0;
+ return 0;
+ }
+
+ for (i = 0; i < len; ++i) getbyte(buf + i);
+ getcomma();
+ buf[len] = 0;
+ return byte_chr(buf,len,'\0') == len;
+}
+
+int flagok = 1;
+
+int main()
+{
+ char *result;
+ unsigned long qp;
+ unsigned long len;
+ char ch;
+
+ sig_pipeignore();
+ sig_alarmcatch(resources);
+ alarm(3600);
+
+ bytesleft = getlen();
+
+ len = getlen();
+
+ if (chdir(auto_qmail) == -1) resources();
+ if (qmail_open(&qq) == -1) resources();
+ qp = qmail_qp(&qq);
+ identify();
+
+ while (len > 0) { /* XXX: could speed this up */
+ getbyte(&ch);
+ --len;
+ qmail_put(&qq,&ch,1);
+ }
+ getcomma();
+
+ if (getbuf())
+ qmail_from(&qq,buf);
+ else {
+ qmail_from(&qq,"");
+ qmail_fail(&qq);
+ flagok = 0;
+ }
+
+ while (bytesleft)
+ if (getbuf())
+ qmail_to(&qq,buf);
+ else {
+ qmail_fail(&qq);
+ flagok = 0;
+ }
+
+ bytesleft = 1;
+ getcomma();
+
+ result = qmail_close(&qq);
+
+ if (!*result) {
+ len = fmt_str(buf,"Kok ");
+ len += fmt_ulong(buf + len,(unsigned long) now());
+ len += fmt_str(buf + len," qp ");
+ len += fmt_ulong(buf + len,qp);
+ buf[len] = 0;
+ result = buf;
+ }
+
+ if (!flagok)
+ result = "Dsorry, I can't accept addresses like that (#5.1.3)";
+
+ buffer_put(&bo,strnum,fmt_ulong(strnum,(unsigned long) str_len(result)));
+ buffer_puts(&bo,":");
+ buffer_puts(&bo,result);
+ buffer_puts(&bo,",");
+ buffer_flush(&bo);
+ _exit(0);
+}
diff --git a/src/qmail-qmtpd.c b/src/qmail-qmtpd.c
new file mode 100644
index 0000000..fbe998b
--- /dev/null
+++ b/src/qmail-qmtpd.c
@@ -0,0 +1,354 @@
+#include <unistd.h>
+#include "stralloc.h"
+#include "buffer.h"
+#include "qmail.h"
+#include "now.h"
+#include "str.h"
+#include "fmt.h"
+#include "env.h"
+#include "sig.h"
+#include "case.h"
+#include "exit.h"
+#include "scan.h"
+#include "rcpthosts.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "received.h"
+#include "ip.h"
+#include "byte.h"
+
+#define PORT_QMTP "209"
+#define PORT_QMTPS "6209"
+
+/** @file qmail-qmtpd.c -- QMTP/QMTPS server
+ @brief requires sslserver */
+
+void badproto() { _exit(100); }
+void resources() { _exit(111); }
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = write(fd,buf,len);
+ if (r <= 0) _exit(0);
+ return r;
+}
+
+char outbuf[256];
+buffer bo = BUFFER_INIT(safewrite,1,outbuf,sizeof(outbuf));
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ buffer_flush(&bo);
+ r = read(fd,buf,len);
+ if (r <= 0) _exit(0);
+ return r;
+}
+
+char inbuf[512];
+buffer bi = BUFFER_INIT(saferead,0,inbuf,sizeof(inbuf));
+
+unsigned long getlen()
+{
+ unsigned long len = 0;
+ char ch;
+ for (;;) {
+ buffer_get(&bi,&ch,1);
+ if (ch == ':') return len;
+ if (ch < '0' || ch > '9') resources();
+ if (len > 200000000) resources();
+ len = 10 * len + (ch - '0');
+ }
+}
+
+void getcomma()
+{
+ char ch;
+ buffer_get(&bi,&ch,1);
+ if (ch != ',') badproto();
+}
+
+unsigned int databytes = 0;
+unsigned int bytestooverflow = 0;
+struct qmail qq;
+
+char buf[1000];
+char buf2[100];
+
+char *remotehost;
+char *remoteinfo;
+char *remoteip;
+char *localport;
+char *local;
+
+stralloc failure = {0};
+stralloc protocol = {0};
+stralloc tlsinfo = {0};
+
+char *relayclient;
+int relayclientlen = 0;
+
+char *ucspitls;
+char *tlsversion;
+char *cipher;
+char *cipherperm;
+char *cipherused;
+char *clientdn;
+char *clientcn;
+char *dnemail;
+
+int seentls = 0;
+
+int modssl_info()
+{
+ tlsversion = env_get("SSL_PROTOCOL");
+ if (!tlsversion) return 0;
+ seentls = 1;
+
+ cipher = env_get("SSL_CIPHER");
+ if (!cipher) cipher = "unknown";
+ cipherperm = env_get("SSL_CIPHER_ALGKEYSIZE");
+ if (!cipherperm) cipherperm = "unknown";
+ cipherused = env_get("SSL_CIPHER_USEKEYSIZE");
+ if (!cipherused) cipherused = "unknown";
+ clientdn = env_get("SSL_CLIENT_S_DN");
+ if (clientdn) seentls = 2;
+ else
+ clientdn = "none";
+
+ if (!stralloc_copys(&tlsinfo,tlsversion)) resources();
+ if (!stralloc_cats(&tlsinfo,": ")) resources();
+ if (!stralloc_cats(&tlsinfo,cipher)) resources();
+ if (!stralloc_cats(&tlsinfo," [")) resources();
+ if (!stralloc_cats(&tlsinfo,cipherused)) resources();
+ if (!stralloc_cats(&tlsinfo,"/")) resources();
+ if (!stralloc_cats(&tlsinfo,cipherperm)) resources();
+ if (!stralloc_cats(&tlsinfo,"] \n")) resources();
+ if (!stralloc_cats(&tlsinfo," DN=")) resources();
+ if (!stralloc_cats(&tlsinfo,clientdn)) resources();
+ if (!stralloc_0(&tlsinfo)) resources();
+
+ if (!stralloc_append(&protocol,"S")) resources();
+
+ if (seentls == 2) {
+ clientcn = env_get("SSL_CLIENT_S_DN_CN");
+ remoteinfo = clientcn ? clientcn : clientdn;
+ dnemail = env_get("SSL_CLIENT_S_DN_Email");
+ if (!dnemail) dnemail = "unknown";
+ if (!stralloc_append(&protocol,"A")) resources();
+ relayclient = "";
+ }
+ return 1;
+}
+
+int main()
+{
+ char ch;
+ int i;
+ unsigned long biglen;
+ unsigned long len;
+ int flagdos;
+ int flagsenderok;
+ int flagbother;
+ unsigned long qp;
+ char *result;
+ char *x;
+ unsigned long u;
+
+ sig_pipeignore();
+ sig_alarmcatch(resources);
+ alarm(3600);
+
+ if (chdir(auto_qmail) == -1) resources();
+
+ if (control_init() == -1) resources();
+ if (rcpthosts_init() == -1) resources();
+
+ if (control_readint(&databytes,"control/databytes") == -1) resources();
+ x = env_get("DATABYTES");
+ if (x) { scan_ulong(x,&u); databytes = u; }
+ if (!(databytes + 1)) --databytes;
+
+ relayclient = env_get("RELAYCLIENT");
+ remotehost = env_get("TCP6REMOTEHOST");
+ if (!remotehost) remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+ remoteinfo = env_get("TCP6REMOTEINFO");
+ if (!remoteinfo) remoteinfo = env_get("TCPREMOTEINFO");
+ remoteip = env_get("TCP6REMOTEIP");
+ if (!remoteip) remoteip = env_get("TCPREMOTEIP");
+ if (remoteip && byte_equal(remoteip,7,V4MAPPREFIX)) remoteip = remoteip + 7;
+ if (!remoteip) remoteip = "unknown";
+ local = env_get("TCP6LOCALHOST");
+ if (!local) local = env_get("TCPLOCALHOST");
+ if (!local) local = env_get("TCP6LOCALIP");
+ if (!local) local = env_get("TCPLOCALIP");
+ if (!local) local = "unknown";
+ localport = env_get("TCP6LOCALPORT");
+ if (!localport) localport = env_get("TCPLOCALPORT");
+ if (!localport) localport = "0";
+
+ if (!stralloc_copys(&protocol,"QMTP")) resources();
+ if (!case_diffs(localport,PORT_QMTPS))
+ if (!modssl_info()) resources();
+
+ if (relayclient)
+ relayclientlen = str_len(relayclient);
+
+ for (;;) {
+ if (!stralloc_copys(&failure,"")) resources();
+ flagsenderok = 1;
+
+ len = getlen();
+ if (len == 0) badproto();
+
+ if (databytes) bytestooverflow = databytes + 1;
+ if (qmail_open(&qq) == -1) resources();
+ qp = qmail_qp(&qq);
+
+ buffer_get(&bi,&ch,1);
+ --len;
+ if (ch == 10) flagdos = 0;
+ else if (ch == 13) flagdos = 1;
+ else badproto();
+
+ /* no fakehelo, no spfinfo */
+
+ received(&qq,protocol.s,local,remoteip,remotehost,remoteinfo,(char *) 0,tlsinfo.s,(char *) 0);
+
+ /* XXX: check for loops? only if len is big? */
+
+ if (flagdos)
+ while (len > 0) {
+ buffer_get(&bi,&ch,1);
+ --len;
+ while ((ch == 13) && len) {
+ buffer_get(&bi,&ch,1);
+ --len;
+ if (ch == 10) break;
+ if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qq);
+ qmail_put(&qq,"\015",1);
+ }
+ if (bytestooverflow) if (!--bytestooverflow) qmail_fail(&qq);
+ qmail_put(&qq,&ch,1);
+ }
+ else {
+ if (databytes)
+ if (len > databytes) {
+ bytestooverflow = 0;
+ qmail_fail(&qq);
+ }
+ while (len > 0) { /* XXX: could speed this up, obviously */
+ buffer_get(&bi,&ch,1);
+ --len;
+ qmail_put(&qq,&ch,1);
+ }
+ }
+ getcomma();
+
+ len = getlen();
+
+ if (len >= 1000) {
+ buf[0] = 0;
+ flagsenderok = 0;
+ for (i = 0; i < len; ++i)
+ buffer_get(&bi,&ch,1);
+ }
+ else {
+ for (i = 0; i < len; ++i) {
+ buffer_get(&bi,buf + i,1);
+ if (!buf[i]) flagsenderok = 0;
+ }
+ buf[len] = 0;
+ }
+ getcomma();
+
+ flagbother = 0;
+ qmail_from(&qq,buf);
+ if (!flagsenderok) qmail_fail(&qq);
+
+ biglen = getlen();
+ while (biglen > 0) {
+ if (!stralloc_append(&failure,"")) resources();
+
+ len = 0;
+ for (;;) {
+ if (!biglen) badproto();
+ buffer_get(&bi,&ch,1);
+ --biglen;
+ if (ch == ':') break;
+ if (ch < '0' || ch > '9') resources();
+ if (len > 200000000) resources();
+ len = 10 * len + (ch - '0');
+ }
+ if (len >= biglen) badproto();
+ if (len + relayclientlen >= 1000) {
+ failure.s[failure.len - 1] = 'L';
+ for (i = 0; i < len; ++i)
+ buffer_get(&bi,&ch,1);
+ }
+ else {
+ for (i = 0; i < len; ++i) {
+ buffer_get(&bi,buf + i,1);
+ if (!buf[i]) failure.s[failure.len - 1] = 'N';
+ }
+ buf[len] = 0;
+
+ if (relayclientlen)
+ str_copy(buf + len,relayclient);
+ if (!relayclient)
+ switch (rcpthosts(buf,len)) {
+ case -1: resources();
+ case 0: failure.s[failure.len - 1] = 'D';
+ }
+
+ if (!failure.s[failure.len - 1]) {
+ qmail_to(&qq,buf);
+ flagbother = 1;
+ }
+ }
+ getcomma();
+ biglen -= (len + 1);
+ }
+ getcomma();
+
+ if (!flagbother) qmail_fail(&qq);
+ result = qmail_close(&qq);
+ if (!flagsenderok) result = "D Unacceptable sender (#5.1.7)";
+ if (databytes) if (!bytestooverflow) result = "D Sorry, that message size exceeds my databytes limit (#5.3.4)";
+
+ if (*result)
+ len = str_len(result);
+ else {
+ /* success! */
+ len = 0;
+ len += fmt_str(buf2 + len,"K Ok ");
+ len += fmt_ulong(buf2 + len,(unsigned long) now());
+ len += fmt_str(buf2 + len," qp ");
+ len += fmt_ulong(buf2 + len,qp);
+ buf2[len] = 0;
+ result = buf2;
+ }
+
+ len = fmt_ulong(buf,len);
+ buf[len++] = ':';
+ len += fmt_str(buf + len,result);
+ buf[len++] = ',';
+
+ for (i = 0; i < failure.len; ++i)
+ switch (failure.s[i]) {
+ case 0:
+ buffer_put(&bo,buf,len);
+ break;
+ case 'D':
+ buffer_puts(&bo,"66:D Sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1),");
+ break;
+ default:
+ buffer_puts(&bo,"46:D Sorry, I can't handle that recipient (#5.1.3),");
+ break;
+ }
+
+ /* bo will be flushed when we read from the network again */
+ }
+}
diff --git a/src/qmail-qread.c b/src/qmail-qread.c
new file mode 100644
index 0000000..888c14a
--- /dev/null
+++ b/src/qmail-qread.c
@@ -0,0 +1,162 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "stralloc.h"
+#include "fmt.h"
+#include "str.h"
+#include "getln.h"
+#include "fmtqfn.h"
+#include "readsubdir.h"
+#include "auto_qmail.h"
+#include "open.h"
+#include "datetime.h"
+#include "date822fmt.h"
+#include "error.h"
+#include "exit.h"
+
+readsubdir rs;
+
+void die(int n) { buffer_flush(buffer_1); _exit(n); }
+
+void warn(char *s1,char *s2)
+{
+ char *x;
+ x = error_str(errno);
+ buffer_puts(buffer_1,s1);
+ buffer_puts(buffer_1,s2);
+ buffer_puts(buffer_1,": ");
+ buffer_puts(buffer_1,x);
+ buffer_puts(buffer_1,"\n");
+}
+
+void die_nomem() { buffer_puts(buffer_1,"fatal: out of memory\n"); die(111); }
+void die_chdir() { warn("fatal: unable to chdir",""); die(110); }
+void die_opendir(fn) char *fn; { warn("fatal: unable to opendir ",fn); die(110); }
+
+void err(unsigned long id)
+{
+ char foo[FMT_ULONG];
+ foo[fmt_ulong(foo,id)] = 0;
+ warn("warning: trouble with #",foo);
+}
+
+char fnmess[FMTQFN];
+char fninfo[FMTQFN];
+char fnlocal[FMTQFN];
+char fnremote[FMTQFN];
+char fnbounce[FMTQFN];
+
+char inbuf[1024];
+stralloc sender = {0};
+
+unsigned long id;
+datetime_sec qtime;
+int flagbounce;
+unsigned long size;
+
+unsigned int fmtstats(char *s)
+{
+ struct datetime dt;
+ unsigned int len;
+ unsigned int i;
+
+ len = 0;
+ datetime_tai(&dt,qtime);
+ i = date822fmt(s,&dt) - 7/*XXX*/; len += i; if (s) s += i;
+ i = fmt_str(s," GMT #"); len += i; if (s) s += i;
+ i = fmt_ulong(s,id); len += i; if (s) s += i;
+ i = fmt_str(s," "); len += i; if (s) s += i;
+ i = fmt_ulong(s,size); len += i; if (s) s += i;
+ i = fmt_str(s," <"); len += i; if (s) s += i;
+ i = fmt_str(s,sender.s + 1); len += i; if (s) s += i;
+ i = fmt_str(s,"> "); len += i; if (s) s += i;
+ if (flagbounce) {
+ i = fmt_str(s," bouncing"); len += i; if (s) s += i;
+ }
+
+ return len;
+}
+
+stralloc stats = {0};
+
+void out(char *s,unsigned int n)
+{
+ while (n > 0) {
+ buffer_put(buffer_1,((*s >= 32) && (*s <= 126)) ? s : "_",1);
+ --n;
+ ++s;
+ }
+}
+void outs(char *s) { out(s,str_len(s)); }
+void outok(char *s) { buffer_puts(buffer_1,s); }
+
+void putstats()
+{
+ if (!stralloc_ready(&stats,fmtstats(FMT_LEN))) die_nomem();
+ stats.len = fmtstats(stats.s);
+ out(stats.s,stats.len);
+ outok("\n");
+}
+
+stralloc line = {0};
+
+int main()
+{
+ int channel;
+ int match;
+ struct stat st;
+ int fd;
+ buffer b;
+ int x;
+
+ if (chdir(auto_qmail) == -1) die_chdir();
+ if (chdir("queue") == -1) die_chdir();
+ readsubdir_init(&rs,"info",die_opendir);
+
+ while ((x = readsubdir_next(&rs,&id)))
+ if (x > 0) {
+ fmtqfn(fnmess,"mess/",id,1);
+ fmtqfn(fninfo,"info/",id,1);
+ fmtqfn(fnlocal,"local/",id,1);
+ fmtqfn(fnremote,"remote/",id,1);
+ fmtqfn(fnbounce,"bounce/",id,0);
+
+ if (stat(fnmess,&st) == -1) { err(id); continue; }
+ size = st.st_size;
+ flagbounce = !stat(fnbounce,&st);
+
+ fd = open_read(fninfo);
+ if (fd == -1) { err(id); continue; }
+ buffer_init(&b,read,fd,inbuf,sizeof(inbuf));
+ if (getln(&b,&sender,&match,0) == -1) die_nomem();
+ if (fstat(fd,&st) == -1) { close(fd); err(id); continue; }
+ close(fd);
+ qtime = st.st_mtime;
+
+ putstats();
+
+ for (channel = 0; channel < 2; ++channel) {
+ fd = open_read(channel ? fnremote : fnlocal);
+ if (fd == -1) {
+ if (errno != ENOENT) err(id);
+ } else {
+ for (;;) {
+ if (getln(&b,&line,&match,0) == -1) die_nomem();
+ if (!match) break;
+ switch (line.s[0]) {
+ case 'D':
+ outok(" done");
+ case 'T':
+ outok(channel ? "\tremote\t" : "\tlocal\t");
+ outs(line.s + 1);
+ outok("\n");
+ break;
+ }
+ }
+ close(fd);
+ }
+ }
+ }
+
+ die(0);
+}
diff --git a/src/qmail-qstat.sh b/src/qmail-qstat.sh
new file mode 100755
index 0000000..b8971e5
--- /dev/null
+++ b/src/qmail-qstat.sh
@@ -0,0 +1,12 @@
+cd HOME
+messdirs=`echo queue/mess/* | wc -w`
+messfiles=`find queue/mess/* -print | wc -w`
+tododirs=`echo queue/todo/* | wc -w`
+todofiles=`find queue/todo/* -print 2>/dev/null | wc -w`
+echo messages in queue: `expr $messfiles - $messdirs`
+if [ $tododirs -gt 1 ]
+then
+ echo messages in queue but not yet preprocessed: `expr $todofiles - $tododirs`
+else
+ echo messages in queue but not yet preprocessed: `expr $todofiles - $tododirs + 1`
+fi
diff --git a/src/qmail-queue.c b/src/qmail-queue.c
new file mode 100644
index 0000000..b1289dc
--- /dev/null
+++ b/src/qmail-queue.c
@@ -0,0 +1,305 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "sig.h"
+#include "exit.h"
+#include "open.h"
+#include "seek.h"
+#include "fmt.h"
+#include "alloc.h"
+#include "buffer.h"
+#include "datetime.h"
+#include "now.h"
+#include "triggerpull.h"
+#include "extra.h"
+#include "auto_qmail.h"
+#include "auto_uids.h"
+#include "date822fmt.h"
+#include "fmtqfn.h"
+#include "env.h"
+#include "wait.h"
+#include "scan.h"
+
+#define DEATH 86400 /* 24 hours; _must_ be below q-s's OSSIFIED (36 hours) */
+#define ADDR 1003
+
+char inbuf[2048];
+struct buffer bi;
+char outbuf[256];
+struct buffer bo;
+
+datetime_sec starttime;
+struct datetime dt;
+unsigned long mypid;
+unsigned long uid;
+char *pidfn;
+struct stat pidst;
+unsigned long messnum;
+char *messfn;
+char *todofn;
+char *intdfn;
+int messfd;
+int intdfd;
+int flagmademess = 0;
+int flagmadeintd = 0;
+
+void cleanup()
+{
+ if (flagmadeintd) {
+ seek_trunc(intdfd,0);
+ if (unlink(intdfn) == -1) return;
+ }
+ if (flagmademess) {
+ seek_trunc(messfd,0);
+ if (unlink(messfn) == -1) return;
+ }
+}
+
+void die(int e) { _exit(e); }
+void die_qhpsi() { cleanup(); die(71); }
+void die_write() { cleanup(); die(53); }
+void die_read() { cleanup(); die(54); }
+void sigalrm() { /* thou shalt not clean up here */ die(52); }
+void sigbug() { die(81); }
+
+unsigned int receivedlen;
+char *received;
+/* "Received: (qmail-queue invoked by alias); 26 Sep 1995 04:46:54 -0000\n" */
+
+static unsigned int receivedfmt(char *s)
+{
+ unsigned int i;
+ unsigned int len;
+ len = 0;
+
+ i = fmt_str(s,"Received: (qmail "); len += i; if (s) s += i;
+ i = fmt_ulong(s,mypid); len += i; if (s) s += i;
+ i = fmt_str(s," invoked "); len += i; if (s) s += i;
+ if (uid == auto_uida) {
+ i = fmt_str(s,"by alias"); len += i; if (s) s += i;
+ } else if (uid == auto_uidd) {
+ i = fmt_str(s,"from network"); len += i; if (s) s += i;
+ } else if (uid == auto_uids) {
+ i = fmt_str(s,"for bounce"); len += i; if (s) s += i;
+ } else {
+ i = fmt_str(s,"by uid "); len += i; if (s) s += i;
+ i = fmt_ulong(s,uid); len += i; if (s) s += i;
+ }
+ i = fmt_str(s,"); "); len += i; if (s) s += i;
+ i = date822fmt(s,&dt); len += i; if (s) s += i;
+ return len;
+}
+
+void received_setup()
+{
+ receivedlen = receivedfmt((char *) 0);
+ received = alloc(receivedlen + 1);
+ if (!received) die(51);
+ receivedfmt(received);
+}
+
+unsigned int pidfmt(char *s,unsigned long seq)
+{
+ unsigned int i;
+ unsigned int len;
+
+ len = 0;
+ i = fmt_str(s,"pid/"); len += i; if (s) s += i;
+ i = fmt_ulong(s,mypid); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,starttime); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,seq); len += i; if (s) s += i;
+ ++len; if (s) *s++ = 0;
+
+ return len;
+}
+
+char *fnnum(char *dirslash,int flagsplit)
+{
+ char *s;
+
+ s = alloc(fmtqfn((char *) 0,dirslash,messnum,flagsplit));
+ if (!s) die(51);
+ fmtqfn(s,dirslash,messnum,flagsplit);
+ return s;
+}
+
+void pidopen(void)
+{
+ unsigned int len;
+ unsigned long seq;
+
+ seq = 1;
+ len = pidfmt((char *) 0,seq);
+ pidfn = alloc(len);
+ if (!pidfn) die(51);
+
+ for (seq = 1; seq < 10; ++seq) {
+ if (pidfmt((char *) 0,seq) > len) die(81); /* paranoia */
+ pidfmt(pidfn,seq);
+ messfd = open_excl(pidfn);
+ if (messfd != -1) return;
+ }
+
+ die(63);
+}
+
+char *qhpsi;
+
+void qhpsiprog(char *arg)
+{
+ int wstat;
+ int child;
+ char *qhpsiargs[6] = { 0, 0, 0, 0, 0, 0 };
+ char *x;
+ unsigned long u;
+ int childrc;
+ int qhpsirc = 1;
+ unsigned int size;
+ unsigned int qhpsiminsize = 0;
+ unsigned int qhpsimaxsize = 0;
+
+ struct stat st;
+
+ if (stat(messfn,&st) == -1) die(63);
+ size = (unsigned int) st.st_size;
+
+ x = env_get("QHPSIMINSIZE");
+ if (x) { scan_ulong(x,&u); qhpsiminsize = (int) u; }
+ if (qhpsiminsize) if (size < qhpsiminsize) return;
+ x = env_get("QHPSIMAXSIZE");
+ if (x) { scan_ulong(x,&u); qhpsimaxsize = (int) u; }
+ if (qhpsimaxsize) if (size > qhpsimaxsize) return;
+
+ if (*arg) {
+ switch (child = fork()) {
+ case -1:
+ die_qhpsi();
+ case 0:
+ qhpsiargs[0] = arg;
+ qhpsiargs[1] = messfn;
+ qhpsiargs[2] = env_get("QHPSIARG1");
+ if (!qhpsiargs[2]) qhpsiargs[2] = 0;
+ qhpsiargs[3] = env_get("QHPSIARG2");
+ if (!qhpsiargs[3]) qhpsiargs[3] = 0;
+ qhpsiargs[4] = env_get("QHPSIARG3");
+ if (!qhpsiargs[4]) qhpsiargs[4] = 0;
+ x = env_get("QHPSIRC");
+ if (x) { scan_ulong(x,&u); qhpsirc = (int) u; }
+ execvp(*qhpsiargs,qhpsiargs);
+ die_qhpsi();
+ }
+ if (wait_pid(&wstat,child) == -1) die_qhpsi();
+ if (wait_crashed(wstat)) die_qhpsi();
+ childrc = wait_exitcode(wstat);
+ if (childrc == qhpsirc) { cleanup(); die(32); }
+ else if (childrc != 0) die_qhpsi();
+ }
+}
+
+char tmp[FMT_ULONG];
+
+int main()
+{
+ unsigned int len;
+ char ch;
+ int fd;
+
+ sig_blocknone();
+ umask(033);
+ if (chdir(auto_qmail) == -1) die(61);
+ if (chdir("queue") == -1) die(62);
+
+ mypid = getpid();
+ uid = getuid();
+ starttime = now();
+ datetime_tai(&dt,starttime);
+ qhpsi = env_get("QHPSI");
+
+ received_setup();
+
+ sig_pipeignore();
+ sig_miscignore();
+ sig_alarmcatch(sigalrm);
+ sig_bugcatch(sigbug);
+
+ alarm(DEATH);
+
+ pidopen();
+ if (fstat(messfd,&pidst) == -1) die(63);
+
+ messnum = pidst.st_ino;
+ messfn = fnnum("mess/",1);
+ todofn = fnnum("todo/",1);
+ intdfn = fnnum("intd/",1);
+
+ if (link(pidfn,messfn) == -1) die(64);
+ if (unlink(pidfn) == -1) die(63);
+ flagmademess = 1;
+
+ buffer_init(&bo,write,messfd,outbuf,sizeof(outbuf));
+ buffer_init(&bi,read,0,inbuf,sizeof(inbuf));
+
+ if (buffer_put(&bo,received,receivedlen) == -1) die_write();
+
+ switch (buffer_copy(&bo,&bi)) {
+ case -2: die_read();
+ case -3: die_write();
+ }
+ if (buffer_flush(&bo) == -1) die_write();
+ if (fsync(messfd) == -1) die_write();
+
+ intdfd = open_excl(intdfn);
+ if (intdfd == -1) die(65);
+ flagmadeintd = 1;
+
+ buffer_init(&bo,write,intdfd,outbuf,sizeof(outbuf));
+ buffer_init(&bi,read,1,inbuf,sizeof(inbuf));
+
+ if (buffer_put(&bo,"u",1) == -1) die_write();
+ if (buffer_put(&bo,tmp,fmt_ulong(tmp,uid)) == -1) die_write();
+ if (buffer_put(&bo,"",1) == -1) die_write();
+
+ if (buffer_put(&bo,"p",1) == -1) die_write();
+ if (buffer_put(&bo,tmp,fmt_ulong(tmp,mypid)) == -1) die_write();
+ if (buffer_put(&bo,"",1) == -1) die_write();
+
+ if (buffer_get(&bi,&ch,1) < 1) die_read();
+ if (ch != 'F') die(91);
+ if (buffer_put(&bo,&ch,1) == -1) die_write();
+ for (len = 0; len < ADDR; ++len) {
+ if (buffer_get(&bi,&ch,1) < 1) die_read();
+ if (buffer_put(&bo,&ch,1) == -1) die_write();
+ if (!ch) break;
+ }
+ if (len >= ADDR) die(11);
+
+ if (buffer_put(&bo,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write();
+
+ for (;;) {
+ if (buffer_get(&bi,&ch,1) < 1) die_read();
+ if (!ch) break;
+ if (ch == 'Q') { qhpsi = 0; break; }
+ if (ch != 'T') die(91);
+ if (buffer_put(&bo,&ch,1) == -1) die_write();
+ for (len = 0; len < ADDR; ++len) {
+ if (buffer_get(&bi,&ch,1) < 1) die_read();
+ if (buffer_put(&bo,&ch,1) == -1) die_write();
+ if (!ch) break;
+ }
+ if (len >= ADDR) die(11);
+ }
+
+ if (qhpsi) qhpsiprog(qhpsi);
+
+ if (buffer_flush(&bo) == -1) die_write();
+ if (fsync(intdfd) == -1) die_write();
+
+ if (link(intdfn,todofn) == -1) die(66);
+ if ((fd = open(todofn,O_RDONLY)) < 0 || fsync(fd) < 0 || close(fd)) die(66);
+
+ triggerpull();
+ die(0);
+}
diff --git a/src/qmail-recipients.c b/src/qmail-recipients.c
new file mode 100644
index 0000000..058994a
--- /dev/null
+++ b/src/qmail-recipients.c
@@ -0,0 +1,77 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "logmsg.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "getln.h"
+#include "exit.h"
+#include "open.h"
+#include "case.h"
+#include "auto_qmail.h"
+#include "cdbmake.h"
+
+#define WHO "qmail-recipients"
+
+int rename(const char *,const char *); // stdio.h
+
+void die_read()
+{
+ logmsg(WHO,111,ERROR,"unable to read users/recipients");
+}
+void die_write()
+{
+ logmsg(WHO,111,ERROR,"unable to write to users/recipients.tmp");
+}
+
+char inbuf[1024];
+buffer b;
+
+int fd;
+int fdtemp;
+
+struct cdb_make cdb;
+stralloc line = {0};
+stralloc key = {0};
+int match;
+
+int main()
+{
+ umask(033);
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,110,ERROR,B("unable to chdir to: ",auto_qmail));
+
+ fd = open_read("users/recipients");
+ if (fd == -1) die_read();
+
+ buffer_init(&b,read,fd,inbuf,sizeof(inbuf));
+
+ fdtemp = open_trunc("users/recipients.tmp");
+ if (fdtemp == -1) die_write();
+
+ if (cdb_make_start(&cdb,fdtemp) == -1) die_write();
+
+ for (;;) {
+ stralloc_copys(&key,":");
+ if (getln(&b,&line,&match,'\n') != 0) die_read();
+ while (line.len) {
+ if (line.s[line.len - 1] == ' ') { --line.len; continue; }
+ if (line.s[line.len - 1] == '\n') { --line.len; continue; }
+ if (line.s[line.len - 1] == '\t') { --line.len; continue; }
+ if (line.s[0] != '#' && stralloc_cat(&key,&line)) {
+ case_lowerb(key.s,key.len);
+ if (cdb_make_add(&cdb,key.s,key.len,"",0) == -1)
+ die_write();
+ }
+ break;
+ }
+ if (!match) break;
+ }
+
+ if (cdb_make_finish(&cdb) == -1) die_write();
+ if (fsync(fdtemp) == -1) die_write();
+ if (close(fdtemp) == -1) die_write(); /* NFS stupidity */
+ if (rename("users/recipients.tmp","users/recipients.cdb") == -1)
+ logmsg(WHO,111,ERROR,"unable to move users/recipients.tmp to users/recipients.cdb");
+
+ _exit(0);
+}
diff --git a/src/qmail-remote.c b/src/qmail-remote.c
new file mode 100644
index 0000000..5ef9465
--- /dev/null
+++ b/src/qmail-remote.c
@@ -0,0 +1,1458 @@
+#ifdef IDN2
+#include <idn2.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include "sig.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "scan.h"
+#include "case.h"
+#include "byte.h"
+#include "logmsg.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "dns.h"
+#include "alloc.h"
+#include "genalloc.h"
+#include "quote.h"
+#include "fmt.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "ipme.h"
+#include "str.h"
+#include "now.h"
+#include "exit.h"
+#include "constmap.h"
+#include "tcpto.h"
+#include "timeout.h"
+#include "timeoutconn.h"
+#include "base64.h"
+#include "socket_if.h"
+#include "ucspissl.h"
+#include "hmac_md5.h"
+#include "tls_remote.h"
+#include "tls_errors.h"
+#include "tls_timeoutio.h"
+#include "uint_t.h"
+
+#define WHO "qmail-remote"
+
+#define MAX_SIZE 200000000
+#define HUGESMTPTEXT 1000 /* RFC 5322; was 5000 chars/line */
+#define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */
+#define PORT_QMTP 209
+#define PORT_SMTPS 465
+#define PORT_SUBMISSION 587
+#define PORT_QMTPS 6209
+#define VERIFYDEPTH 1
+
+unsigned long port = PORT_SMTP;
+
+/** @file qmail-remote.c -- versatile SMTP(S)/QMTP(S) client */
+
+int flagauth = 0; /* 1 = login; 2 = plain; 3 = crammd5 */
+int flagsmtps = 0; /* RFC 8314 - 'implicit TLS' */
+int flagtlsdomain = 0; /* 0 = no; 1 = yes; 2 = cert */
+int flagtls = 0; /* flagtls: XYZ
+ (mode) Z: -2 = rejected; -1 = not; 0 = no, default; Z > 0 see tls_remote.c
+ (prot) Y: 0 = StartTLS; 1 = SMTPS; 2 = QMTPS
+ (active) X: 1 = running TLS connection (after DNS lookup)
+ (done) Z: 1: CA chain; 2: Cert wildname; 3: Cert exactname;
+ 4: Cert fingerprint; 5: TLSA record */
+int flagverify = 0; /* 1 = verify Cert against CA; 2 = verify against Dir; 3 = triggerd by TLSA;
+ -2 = Cert pinning; -1 = no TLSA validation */
+int flagutf8 = 0;
+
+GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
+GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
+static stralloc sauninit = {0};
+
+stralloc helohost = {0};
+stralloc eaihost = {0};
+stralloc host = {0};
+stralloc idnhost = {0};
+stralloc sender = {0};
+stralloc canonhost = {0};
+stralloc remotehost = {0};
+stralloc canonbox = {0};
+stralloc senddomain = {0};
+stralloc sendip = {0};
+
+stralloc domainips = {0};
+struct constmap mapdomainips;
+char ip4[4];
+char ip6[16];
+uint32 ifidx = 0;
+char *authsender = 0;
+
+stralloc smtproutes = {0};
+struct constmap mapsmtproutes;
+stralloc qmtproutes = {0};
+struct constmap mapqmtproutes;
+
+saa reciplist = {0};
+stralloc recip = {0};
+
+char msgsize[FMT_ULONG];
+unsigned long msize = 0;
+struct ip_mx partner;
+
+SSL *ssl;
+SSL_CTX *ctx;
+
+char smallbuf[BUFFER_SMALL];
+buffer bs = BUFFER_INIT(write,1,smallbuf,sizeof(smallbuf));
+
+void out(char *s)
+{
+ if (buffer_puts(&bs,s) == -1)
+ _exit(0);
+ }
+void zero()
+{
+ if (buffer_put(&bs,"\0",1) == -1)
+ _exit(0);
+}
+void zerodie()
+{
+ zero();
+ buffer_flush(&bs);
+ if (ssl) tls_exit(ssl);
+ _exit(0);
+}
+
+void outsafe(stralloc *sa)
+{
+ int i;
+ char ch;
+ for (i = 0; i < sa->len; ++i) {
+ ch = sa->s[i];
+ if (ch == 0) continue;
+ if (ch < 33) ch = '?';
+ if (ch > 126) ch = '?';
+ if (buffer_put(&bs,&ch,1) == -1) _exit(0);
+ }
+}
+
+void temp_noip()
+{
+ out("ZInvalid ipaddr in control/domainips (#4.3.0)\n");
+ zerodie();
+}
+void temp_nomem()
+{
+ out("ZOut of memory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_oserr()
+{
+ out("ZSystem resources temporarily unavailable. (#4.3.0)\n");
+ zerodie();
+}
+void temp_osip()
+{
+ out("ZCan't bind to local ip address: ");
+ outsafe(&sendip);
+ out(". (#4.3.0)\n");
+ zerodie();
+}
+void temp_noconn()
+{
+ out("ZSorry, I wasn't able to establish an SMTP connection: ");
+ outsafe(&canonhost);
+ out(". (#4.3.0)\n");
+ zerodie();
+}
+void temp_qmtpnoc()
+{
+ out("ZSorry, I wasn't able to establish an QMTP connection: ");
+ outsafe(&canonhost);
+ out(". (#4.3.1)\n");
+ zerodie();
+}
+void temp_read()
+{
+ out("ZUnable to read message. (#4.3.0)\n");
+ zerodie();
+}
+void temp_dnscanon()
+{
+ out("ZCNAME lookup failed temporarily for: ");
+ outsafe(&canonhost);
+ out(". (#4.4.3)\n");
+ zerodie();
+}
+void temp_dns()
+{
+ out("ZSorry, I couldn't find any host named: ");
+ outsafe(&host);
+ out(". (#4.1.2)\n");
+ zerodie();
+}
+void temp_nomx()
+{
+ out("ZSorry, I couldn't find a mail exchanger or IP address for: ");
+ outsafe(&host);
+ out(". Will try again. (#4.1.2)\n");
+ zerodie();
+}
+void temp_chdir()
+{
+ out("ZUnable to switch to home directory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_control()
+{
+ out("ZUnable to read control files. (#4.3.0)\n");
+ zerodie();
+}
+void perm_partialline()
+{
+ out("DSMTP cannot transfer messages with partial final lines. (#5.6.2)\n");
+ zerodie();
+}
+void temp_proto()
+{
+ out("ZRecipient did not talk proper QMTP (#4.3.0)\n");
+ zerodie();
+}
+void perm_usage()
+{
+ out("Dqmail-remote was invoked improperly. (#5.3.5)\n");
+ zerodie();
+}
+void perm_dns()
+{
+ out("DSorry, I couldn't find any host named: ");
+ outsafe(&host);
+ out(". (#5.1.2)\n");
+ zerodie();
+}
+void perm_nomx()
+{
+ out("DSorry, I couldn't find a mail exchanger or IP address for: ");
+ outsafe(&host);
+ out(". (#5.4.4)\n");
+ zerodie();
+}
+void perm_ambigmx()
+{
+ out("DSorry. Although I'm listed as a best-preference MX or A for that host,\n\
+it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n");
+ zerodie();
+}
+void err_authprot()
+{
+ out("KNo supported AUTH method found, continuing without authentication.\n");
+}
+
+void outhost()
+{
+ char ipaddr[IPFMT];
+ int len;
+
+ switch (partner.af) {
+ case AF_INET:
+ len = ip4_fmt(ipaddr,(char *)&partner.addr.ip4.d); break;
+ case AF_INET6:
+ len = ip6_fmt(ipaddr,(char *)&partner.addr.ip6.d); break;
+ }
+ if (buffer_put(&bs,ipaddr,len) == -1) _exit(0);
+}
+
+int flagcritical = 0;
+
+void dropped()
+{
+ out("ZConnected to ");
+ outhost();
+ out(" but connection died. ");
+ if (flagcritical) out("Possible duplicate! ");
+ out("(#4.4.2)\n");
+ zerodie();
+}
+
+int timeoutconnect = 60;
+int smtpfd;
+int timeout = 1200;
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ if (ssl) {
+ r = tls_timeoutread(timeout,smtpfd,smtpfd,ssl,buf,len);
+ if (r < 0) temp_tlserr();
+ } else {
+ r = timeoutread(timeout,smtpfd,buf,len);
+ }
+ if (r <= 0) dropped();
+ return r;
+}
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ if (ssl) {
+ r = tls_timeoutwrite(timeout,smtpfd,smtpfd,ssl,buf,len);
+ if (r < 0) temp_tlserr();
+ } else {
+ r = timeoutwrite(timeout,smtpfd,buf,len);
+ }
+ if (r <= 0) dropped();
+ return r;
+}
+
+char inbuf[BUFFER_MTUSIZE];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+char outbuf[BUFFER_MTUSIZE];
+buffer bo = BUFFER_INIT(safewrite,-1,outbuf,sizeof(outbuf));
+char frombuf[BUFFER_SMALL];
+buffer bf = BUFFER_INIT(saferead,-1,frombuf,sizeof(frombuf));
+
+static stralloc smtptext = {0};
+static stralloc header = {0};
+
+void get(char *ch)
+{
+ buffer_get(&bf,ch,1);
+ if (*ch != '\r')
+ if (smtptext.len < HUGESMTPTEXT)
+ if (!stralloc_append(&smtptext,ch)) temp_nomem();
+}
+
+unsigned long smtpcode()
+{
+ unsigned char ch;
+ unsigned long code;
+
+ if (!stralloc_copys(&smtptext,"")) temp_nomem();
+
+ get(&ch); code = ch - '0';
+ get(&ch); code = code * 10 + (ch - '0');
+ get(&ch); code = code * 10 + (ch - '0');
+ for (;;) {
+ get(&ch);
+ if (ch != '-') break;
+ while (ch != '\n') get(&ch);
+ get(&ch);
+ get(&ch);
+ get(&ch);
+ }
+ while (ch != '\n') get(&ch);
+
+ return code;
+}
+
+void outsmtptext()
+{
+ int i;
+ if (smtptext.s) if (smtptext.len) {
+ out("Remote host said: ");
+ for (i = 0; i < smtptext.len; ++i)
+ if (!smtptext.s[i]) smtptext.s[i] = '?';
+ if (buffer_put(&bs,smtptext.s,smtptext.len) == -1) _exit(0);
+ smtptext.len = 0;
+ }
+}
+
+void quit(char *prepend,char *append)
+{
+ buffer_putsflush(&bo,"QUIT\r\n");
+ /* waiting for remote side is just too ridiculous */
+ out(prepend);
+ outhost();
+ out(append);
+ out(".\n");
+ outsmtptext();
+ zerodie();
+}
+
+void blast()
+{
+ int r;
+ char ch;
+
+ for (;;) {
+ r = buffer_get(&bi,&ch,1);
+ if (r == 0) break;
+ if (r == -1) temp_read();
+ if (ch == '.') buffer_put(&bo,".",1);
+
+ while (ch != '\n') {
+ if (ch != '\r')
+ buffer_put(&bo,&ch,1); // DKIM input
+ r = buffer_get(&bi,&ch,1);
+ if (r == 0) perm_partialline();
+ if (r == -1) temp_read();
+ }
+ buffer_put(&bo,"\r\n",2);
+ }
+
+ flagcritical = 1;
+ buffer_put(&bo,".\r\n",3);
+ buffer_flush(&bo);
+}
+
+/* this file is too long -------------------------------------- client TLS */
+
+stralloc cafile = {0};
+stralloc cadir = {0};
+stralloc certfile = {0};
+stralloc keyfile = {0};
+stralloc keypwd = {0};
+stralloc ciphers = {0};
+
+char *tlsdestinfo = 0;
+char *tlsdomaininfo = 0;
+
+stralloc domaincerts = {0};
+struct constmap mapdomaincerts;
+stralloc tlsdestinations = {0};
+struct constmap maptlsdestinations;
+unsigned long verifydepth = VERIFYDEPTH;
+
+void tls_init()
+{
+ ctx = ssl_client();
+ ssl_errstr();
+ if (!ctx) temp_tlsctx();
+
+/* Fetch CA infos for dest */
+
+ if (flagverify > 0)
+ if (cafile.len || cadir.len)
+ if (!ssl_ca(ctx,cafile.s,cadir.s,(int) verifydepth)) temp_tlsca();
+
+ if (ciphers.len)
+ if (!ssl_ciphers(ctx,ciphers.s)) temp_tlscipher();
+
+/* Prepare for Certificate Request */
+
+ if (flagtlsdomain == 2)
+ switch (tls_certkey(ctx,certfile.s,keyfile.s,keypwd.s)) {
+ case 0: break;
+ case -1: temp_tlscert();
+ case -2: temp_tlskey();
+ case -3: temp_tlschk();
+ }
+
+/* Set SSL Context */
+
+ ssl = ssl_new(ctx,smtpfd);
+ if (!ssl) temp_tlsctx();
+
+/* Setup SSL FDs */
+
+ if (!tls_conn(ssl,smtpfd)) temp_tlscon();
+
+/* Go on in none-blocking mode */
+
+ if (tls_timeoutconn(timeout,smtpfd,smtpfd,ssl) <= 0)
+ temp_tlserr();
+}
+
+int starttls_peer()
+{
+ int i = 0;
+
+ while ((i += str_chr(smtptext.s+i,'\n') + 1) &&
+ (i + 8 < smtptext.len) ) {
+ if (!str_diffn(smtptext.s + i + 4,"STARTTLS",8)) return 1; }
+
+ return 0;
+}
+
+void tls_peercheck()
+{
+ X509 *cert;
+ STACK_OF(X509) *certs;
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (!cert) { flagtls = 100; return; }
+
+ if ((certs = SSL_get_peer_cert_chain(ssl)) == NULL) {
+ certs = sk_X509_new_null();
+ sk_X509_push(certs, cert);
+ }
+
+ if (flagverify == -2) { // fingerprinting is silent
+ if (cafile.len) case_lowerb(cafile.s,cafile.len);
+ switch (tls_fingerprint(cert,cafile.s + 1,cafile.len - 2)) {
+ case -1: temp_tlspeercert();
+ case -2: temp_tlsdigest();
+ case -3: temp_invaliddigest();
+ case 0: temp_tlscertfp();
+ case 1: flagtls = 104; break;
+ }
+ }
+
+ if (flagverify >= 0) { // TLSA is default
+ switch (tlsa_check(certs,remotehost,port)) {
+ case -4: temp_tlsamissing(); break; /* FIXME */
+ case -3: temp_tlsainvalid(); break;
+ case -2: break; // unsupported type; may happen
+ case -1: break; // weird TLSA record
+ case 0: break; // no TLSA record given
+ case 1: case 2: flagtls = 107; flagverify = 3; break; // full certchain available (-PKIX)
+ case 3: flagtls = 106; flagverify = 0; break; // TA-CA; verify wont work
+ case 4: flagtls = 105; flagverify = 0; break; // Endpoint only
+ }
+ }
+
+ if (flagverify > 0) {
+ switch (tls_checkpeer(ssl,cert,remotehost,flagtls,flagverify)) {
+ case -1: temp_tlspeercert();
+ case -2: temp_tlspeerverify();
+ case -3: temp_tlspeervalid();
+ case 1: flagtls = 101; break;
+ case 2: flagtls = 102; break;
+ case 3: flagtls = 103; break;
+ }
+ }
+
+ if (flagtls < 100) flagtls = 100;
+
+ X509_free(cert);
+ X509_free(certs);
+
+ return;
+}
+
+/* this file is too long --------------------------------------- smtp UTF8 */
+
+int utf8string(unsigned char *ch,int len)
+{
+ int i = 0;
+ while (i < len)
+ if (ch[i++] > 127) return 1;
+ return 0;
+}
+
+int utf8received()
+{
+ int r;
+ int i;
+ int received = 0;
+ char ch;
+ stralloc receivedline = {0};
+
+/* we consider only our own last written header */
+
+ for (;;) {
+ r = buffer_get(&bi,&ch,1);
+ if (r == 0) break;
+ if (r == -1) temp_read();
+ if (ch == '\r') continue; // DKIM
+
+ if (ch == '\n') {
+ if (!stralloc_append(&header,"\r")) temp_nomem(); /* received.c does not add '\r' */
+ if (!stralloc_append(&header,"\n")) temp_nomem();
+ if (case_starts(receivedline.s,"Date:")) return 0; /* header to quit asap */
+ if (case_starts(receivedline.s,"Received: from")) received++; /* found Received header */
+ if (received) {
+ if (case_starts(receivedline.s," by ")) {
+ for (i = 6; i < receivedline.len - 6; ++i)
+ if (*(receivedline.s + i) == ' ')
+ if (case_starts(receivedline.s + i + 1,"with UTF8")) return 1;
+ return 0;
+ }
+ }
+ if (!stralloc_copys(&receivedline,"")) temp_nomem();
+ } else {
+ if (!stralloc_append(&header,&ch)) temp_nomem();
+ if (!stralloc_catb(&receivedline,&ch,1)) temp_nomem();
+ }
+ }
+ return 0;
+}
+
+/* this file is too long -------------------------------------- smtp client */
+
+unsigned long code;
+int flagsize = 0;
+
+int smtp_size()
+{
+ int i;
+ if (smtptext.len > 10)
+ for (i = 0; i < smtptext.len; ++i) {
+ if (case_starts(smtptext.s + i,"SIZE ")) return 1;
+ }
+ return 0;;
+}
+
+void smtp_greeting()
+{
+ buffer_puts(&bo,"EHLO ");
+ buffer_put(&bo,helohost.s,helohost.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() != 250) {
+ buffer_puts(&bo,"HELO ");
+ buffer_put(&bo,helohost.s,helohost.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ code = smtpcode();
+ authsender = 0;
+ if (code >= 500) quit("DConnected to "," but my name was rejected");
+ if (code != 250) quit("ZConnected to "," but my name was rejected");
+ }
+ flagsize = smtp_size();
+}
+
+void smtp_starttls()
+{
+ buffer_puts(&bo,"STARTTLS\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() == 220) {
+ tls_init();
+ tls_peercheck();
+ smtp_greeting();
+ }
+ else {
+ flagtls = -2;
+ quit("ZConnected to "," but STARTTLS was rejected");
+ }
+}
+
+void mailfrom()
+{
+ buffer_puts(&bo,"MAIL FROM:<");
+ buffer_put(&bo,sender.s,sender.len);
+ buffer_puts(&bo,">");
+ if (flagutf8 || utf8received())
+ buffer_puts(&bo," SMTPUTF8");
+ if (flagsize && msize) {
+ buffer_puts(&bo," SIZE=");
+ buffer_puts(&bo,msgsize);
+ }
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+}
+
+/* this file is too long -------------------------------------- client auth */
+
+stralloc authsenders = {0};
+struct constmap mapauthsenders;
+
+stralloc user = {0};
+stralloc pass = {0};
+stralloc auth = {0};
+stralloc chal = {0};
+stralloc slop = {0};
+stralloc plain = {0};
+stralloc xuser = {0};
+
+static const char hextab[] = "0123456789abcdef";
+
+int xtext(stralloc *sa,char *s,int len)
+{
+ int i;
+ unsigned char c;
+ char xch[2];
+
+ if (!stralloc_copys(sa,"")) temp_nomem();
+
+ for (i = 0; i < len; i++) {
+ c = s[i];
+ if (c < 33 || c > 126 || c == '=' || c == '+') {
+ xch[0] = hextab[(c >> 4) & 0x0f];
+ xch[1] = hextab[c & 0x0f];
+ if (!stralloc_catb(sa,xch,2)) temp_nomem();
+ } else
+ if (!stralloc_catb(sa,s + i,1)) temp_nomem();
+ }
+
+ return sa->len;
+}
+
+void mailfrom_xtext()
+{
+ if (!xtext(&xuser,user.s,user.len)) temp_nomem();
+ buffer_puts(&bo,"MAIL FROM:<");
+ buffer_put(&bo,sender.s,sender.len);
+ buffer_puts(&bo,"> AUTH=");
+ buffer_put(&bo,xuser.s,xuser.len);
+ if (flagutf8 || utf8received())
+ buffer_puts(&bo," SMTPUTF8");
+ if (flagsize && msize) {
+ buffer_puts(&bo," SIZE=");
+ buffer_puts(&bo,msgsize);
+ }
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+}
+
+int mailfrom_plain()
+{
+ buffer_puts(&bo,"AUTH PLAIN\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (AUTH PLAIN)");
+
+ if (!stralloc_cats(&plain,"")) temp_nomem(); /* RFC 4616 section 2 */
+ if (!stralloc_0(&plain)) temp_nomem();
+ if (!stralloc_cat(&plain,&user)) temp_nomem(); /* user-id */
+ if (!stralloc_0(&plain)) temp_nomem();
+ if (!stralloc_cat(&plain,&pass)) temp_nomem(); /* password */
+ if (b64encode(&plain,&auth)) quit("ZConnected to "," but unable to base64encode (plain)");
+ buffer_put(&bo,auth.s,auth.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ switch (smtpcode()) {
+ case 235: mailfrom_xtext(); break;
+ case 432: quit("DConnected to "," but password expired");
+ case 534: quit("ZConnected to "," but authentication mechamism too weak (plain)");
+ default: quit("ZConnected to "," but authentication was rejected (plain)");
+ }
+ return 0;
+}
+
+int mailfrom_login()
+{
+ buffer_puts(&bo,"AUTH LOGIN\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (AUTH LOGIN)");
+ if (!stralloc_copys(&auth,"")) temp_nomem();
+ if (b64encode(&user,&auth)) quit("ZConnected to "," but unable to base64encode user");
+
+ buffer_put(&bo,auth.s,auth.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (username)");
+
+ if (!stralloc_copys(&auth,"")) temp_nomem();
+ if (b64encode(&pass,&auth)) quit("ZConnected to "," but unable to base64encode pass");
+ buffer_put(&bo,auth.s,auth.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ switch (smtpcode()) {
+ case 235: mailfrom_xtext(); break;
+ case 432: quit("DConnected to "," but password expired");
+ case 534: quit("ZConnected to "," but authentication mechanism is too weak (login)");
+ default: quit("ZConnected to "," but authentication was rejected (login)");
+ }
+ return 0;
+}
+
+int mailfrom_cram()
+{
+ int j;
+ unsigned char digest[16];
+ unsigned char digascii[33];
+
+ buffer_puts(&bo,"AUTH CRAM-MD5\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (AUTH CRAM-MD5)");
+ if (str_chr(smtptext.s + 4,' ')) { /* Challenge */
+ if (!stralloc_copys(&slop,"")) temp_nomem();
+ if (!stralloc_copyb(&slop,smtptext.s + 4,smtptext.len - 5)) temp_nomem();
+ if (b64decode(slop.s,slop.len,&chal)) quit("ZConnected to "," but unable to base64decode challenge");
+ }
+
+ hmac_md5((unsigned char *)chal.s,chal.len,pass.s,pass.len,digest);
+
+ for (j = 0; j < 16; j++) { /* HEX => ASCII */
+ digascii[2 * j] = hextab[digest[j] >> 4];
+ digascii[2 * j + 1] = hextab[digest[j] & 0x0f];
+ }
+ digascii[32]=0;
+
+ if (!stralloc_copys(&slop,"")) temp_nomem();
+ if (!stralloc_cat(&slop,&user)) temp_nomem(); /* user-id */
+ if (!stralloc_cats(&slop," ")) temp_nomem();
+ if (!stralloc_catb(&slop,digascii,32)) temp_nomem(); /* digest */
+
+ if (!stralloc_copys(&auth,"")) temp_nomem();
+ if (b64encode(&slop,&auth)) quit("ZConnected to "," but unable to base64encode username+digest");
+
+ buffer_put(&bo,auth.s,auth.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ switch (smtpcode()) {
+ case 235: mailfrom_xtext(); break;
+ case 432: quit("DConnected to "," but password expired");
+ case 534: quit("ZConnected to "," but authentication mechamism too weak (cram)");
+ default: quit("ZConnected to "," but authentication was rejected (cram)");
+ }
+ return 0;
+}
+
+void smtp_auth()
+{
+ int i;
+
+ if (smtptext.len > 8)
+ for (i = 4; i < smtptext.len - 5; ++i) {
+ if (case_starts(smtptext.s + i,"CRAM"))
+ if (mailfrom_cram() >= 0) return;
+ if (case_starts(smtptext.s + i,"LOGIN"))
+ if (mailfrom_login() >= 0) return;
+ if (case_starts(smtptext.s + i,"PLAIN"))
+ if (mailfrom_plain() >= 0) return;
+ }
+ err_authprot();
+ mailfrom();
+}
+
+/* this file is too long ------------------------------------------- GO ON */
+
+void smtp()
+{
+ int flagbother;
+ int i;
+
+ if (flagtls > 10 && flagtls < 20) { /* SMTPS */
+ tls_init();
+ tls_peercheck();
+ }
+
+ code = smtpcode();
+ if (code >= 500) quit("DConnected to "," but sender was rejected");
+ if (code == 421 || code == 450) quit("ZConnected to "," but probably greylisted"); /* RFC 6647 */
+ if (code >= 400) quit("ZConnected to "," but sender was rejected");
+ if (code != 220) quit("ZConnected to "," but greeting failed");
+
+ smtp_greeting();
+
+ if (flagtls > 0 && flagtls < 10) { /* STARTTLS */
+ if (starttls_peer())
+ smtp_starttls();
+ else if (flagtls > 3 && flagtls != 9) {
+ if (!stralloc_0(&host)) temp_nomem();
+ temp_tlshost();
+ }
+ }
+ if (user.len && pass.len) /* AUTH */
+ smtp_auth();
+ else
+ mailfrom(); /* Mail From */
+
+ code = smtpcode();
+ if (code >= 500) quit("DConnected to "," but sender was rejected");
+ if (code >= 400) quit("ZConnected to "," but sender was probably greylisted");
+
+ flagbother = 0; /* Rcpt To */
+ for (i = 0; i < reciplist.len; ++i) {
+ buffer_puts(&bo,"RCPT TO:<");
+ buffer_put(&bo,reciplist.sa[i].s,reciplist.sa[i].len);
+ buffer_puts(&bo,">\r\n");
+ buffer_flush(&bo);
+
+ code = smtpcode(); /* Data */
+ if (flagsize) {
+ if (code == 552) quit("DConnected to "," but message size is too large");
+ if (code == 452) quit("ZConnected to "," however insufficient storage space available");
+ }
+ if (code == 421 || code == 450) { // Postfix merde ;-)
+ out("s"); outhost(); out(" sender is greylisting.\n");
+ outsmtptext(); zero();
+ } else if (code >= 500) {
+ out("h"); outhost(); out(" does not like recipient.\n");
+ outsmtptext(); zero();
+ } else if (code >= 400) {
+ out("s"); outhost(); out(" does not like recipient.\n");
+ outsmtptext(); zero();
+ } else {
+ out("r"); zero();
+ flagbother = 1;
+ }
+ }
+ if (!flagbother) quit("DGiving up on ","");
+
+ buffer_putsflush(&bo,"DATA\r\n");
+
+ code = smtpcode();
+ if (code >= 500) quit("D"," failed on DATA command");
+ if (code >= 400) quit("Z"," failed on DATA command");
+
+ buffer_putflush(&bo,header.s,header.len);
+
+ blast();
+ code = smtpcode();
+ flagcritical = 0;
+ if (code >= 500) quit("D"," failed after I sent the message");
+ if (code >= 400) quit("Z"," failed after I sent the message");
+ switch (flagtls) { // StartTLS + SMTPS
+ case 100: case 110: quit("K"," TLS transmitted message accepted"); break;
+ case 101: case 111: quit("K"," TLS (verified CA) transmitted message accepted"); break;
+ case 102: case 112: quit("K"," TLS (validated CA+DN*) transmitted message accepted"); break;
+ case 103: case 113: quit("K"," TLS (validated CA+DN) transmitted message accepted"); break;
+ case 104: case 114: quit("K"," TLS (CERT pinning) transmitted message accepted"); break;
+ case 105: case 115: quit("K"," TLS (TLSA EE validated) transmitted message accepted"); break;
+ case 106: case 116: quit("K"," TLS (TLSA TA validated) transmitted message accepted"); break;
+ case 107: case 117: quit("K"," TLS (TLSA PKIX verified) transmitted message accepted"); break;
+ default: quit("K"," accepted message"); break;
+ }
+}
+
+/* this file is too long -------------------------------------- qmtp client */
+
+int qmtpsend = 0;
+
+void qmtp()
+{
+ unsigned long len;
+ char *x;
+ int i;
+ int n;
+ unsigned char ch;
+ char num[FMT_ULONG];
+ int flagallok;
+
+ if (qmtpsend == 2) { /* QMTPS */
+ tls_init();
+ tls_peercheck();
+ }
+
+/* the following code was substantially taken from serialmail's serialqmtp.c */
+
+ scan_ulong(msgsize,&len);
+ buffer_put(&bo,num,fmt_ulong(num,len + 1));
+ buffer_put(&bo,":\n",2);
+ while (len > 0) {
+ n = buffer_feed(&bi);
+ if (n <= 0) _exit(1); /* wise guy again */
+ x = buffer_PEEK(&bi);
+ buffer_put(&bo,x,n);
+ buffer_SEEK(&bi,n);
+ len -= n;
+ }
+ buffer_put(&bo,",",1);
+
+ len = sender.len;
+ buffer_put(&bo,num,fmt_ulong(num,len));
+ buffer_put(&bo,":",1);
+ buffer_put(&bo,sender.s,sender.len);
+ buffer_put(&bo,",",1);
+
+ len = 0;
+ for (i = 0; i < reciplist.len; ++i)
+ len += fmt_ulong(num,reciplist.sa[i].len) + 1 + reciplist.sa[i].len + 1;
+ buffer_put(&bo,num,fmt_ulong(num,len));
+ buffer_put(&bo,":",1);
+ for (i = 0; i < reciplist.len; ++i) {
+ buffer_put(&bo,num,fmt_ulong(num,reciplist.sa[i].len));
+ buffer_put(&bo,":",1);
+ buffer_put(&bo,reciplist.sa[i].s,reciplist.sa[i].len);
+ buffer_put(&bo,",",1);
+ }
+ buffer_put(&bo,",",1);
+ buffer_flush(&bo);
+
+ flagallok = 1;
+
+ for (i = 0; i < reciplist.len; ++i) {
+ len = 0;
+ for (;;) {
+ get(&ch);
+ if (ch == ':') break;
+ if (len > 200000000) temp_proto();
+ if (ch - '0' > 9) temp_proto();
+ len = 10 * len + (ch - '0');
+ }
+ if (!len) temp_proto();
+ get(&ch); --len;
+ if ((ch != 'Z') && (ch != 'D') && (ch != 'K')) temp_proto();
+
+ if (!stralloc_copyb(&smtptext,&ch,1)) temp_proto();
+ if (flagtls == 100) {
+ if (!stralloc_cats(&smtptext,"qmtps:")) temp_nomem();
+ } else {
+ if (!stralloc_cats(&smtptext,"qmtp:")) temp_nomem();
+ }
+
+ while (len > 0) {
+ get(&ch);
+ --len;
+ }
+
+ for (len = 0; len < smtptext.len; ++len) {
+ ch = smtptext.s[len];
+ if ((ch < 32) || (ch > 126)) smtptext.s[len] = '?';
+ }
+ get(&ch);
+ if (ch != ',') temp_proto();
+ smtptext.s[smtptext.len - 1] = '\n';
+
+ if (smtptext.s[0] == 'K') out("r");
+ else if (smtptext.s[0] == 'D') {
+ out("h");
+ flagallok = 0;
+ }
+ else { /* if (smtptext.s[0] == 'Z') */
+ out("s");
+ flagallok = 0;
+ }
+ if (buffer_put(&bs,smtptext.s + 1,smtptext.len - 1) == -1) temp_qmtpnoc();
+ zero();
+ }
+ if (!flagallok) {
+ out("DGiving up on "); outhost(); out("\n");
+ } else {
+ out("KAll received okay by "); outhost(); out("\n");
+ }
+ zerodie();
+}
+
+/* this file is too long -------------------------------------- common */
+
+/* host has to be canonical [A/AAAA record], box has to be quoted */
+
+void addrmangle(stralloc *saout,char *address,int *flagalias,int flagcname)
+{
+ int at;
+ int r = 0;
+ stralloc cn = {0};
+
+ *flagalias = flagcname; /* saout + flagalias are output */
+ if (!flagutf8)
+ flagutf8 = utf8string(address,str_len(address));
+
+ at = str_rchr(address,'@');
+ if (!address[at]) {
+ if (!stralloc_copys(saout,address)) temp_nomem();
+ return;
+ }
+
+ if (!stralloc_copys(&canonbox,address)) temp_nomem();
+ canonbox.len = at;
+ if (!quote(saout,&canonbox)) temp_nomem(); /* saout = 'inbox' name without quotes ;-) */
+ if (!stralloc_cats(saout,"@")) temp_nomem();
+
+ if (!stralloc_copys(&canonhost,address + at + 1)) temp_nomem();
+ if (flagcname) { /* no relayhost */
+ DNS_INIT
+ switch ((r = dns_cname(&cn,&canonhost))) {
+ case DNS_MEM: temp_nomem();
+ case DNS_SOFT: temp_dnscanon();
+ case DNS_HARD: ; /* alias loop, not our problem */
+ default: if (r > 0) *flagalias = 0;
+ }
+ }
+ if (!stralloc_cat(saout,&canonhost)) temp_nomem();
+}
+
+void getcontrols()
+{
+ if (control_init() == -1) temp_control();
+ if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control();
+ if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1)
+ temp_control();
+ if (control_rldef(&helohost,"control/helohost",1,(char *) 0) != 1)
+ temp_control();
+ switch (control_readfile(&smtproutes,"control/smtproutes",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&mapsmtproutes,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapsmtproutes,smtproutes.s,smtproutes.len,1)) temp_nomem(); break;
+ }
+ switch (control_readfile(&domainips,"control/domainips",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&mapdomainips,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapdomainips,domainips.s,domainips.len,1)) temp_nomem(); break;
+ }
+ switch (control_readfile(&authsenders,"control/authsenders",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&mapauthsenders,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapauthsenders,authsenders.s,authsenders.len,1)) temp_nomem(); break;
+ }
+ switch (control_readfile(&qmtproutes,"control/qmtproutes",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&mapqmtproutes,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapqmtproutes,qmtproutes.s,qmtproutes.len,1)) temp_nomem(); break;
+ }
+ switch (control_readfile(&domaincerts,"control/domaincerts",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&mapdomaincerts,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapdomaincerts,domaincerts.s,domaincerts.len,1)) temp_nomem(); break;
+ }
+ switch (control_readfile(&tlsdestinations,"control/tlsdestinations",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&maptlsdestinations,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&maptlsdestinations,tlsdestinations.s,tlsdestinations.len,1)) temp_nomem(); break;
+ }
+}
+
+int main(int argc,char **argv)
+{
+ static ipalloc ip = {0};
+ stralloc netif = {0};
+ struct stat st;
+ int i, j, k;
+ int p; /* reserved for port */
+ int r; /* reserved for return code */
+ unsigned long random;
+ char **recips;
+ unsigned long prefme;
+ int flagallaliases;
+ int flagalias;
+ char *relayhost;
+ char *localip;
+ int ip6flag = 0;
+
+ sig_pipeignore();
+ if (argc < 4) perm_usage();
+ if (chdir(auto_qmail) == -1) temp_chdir();
+
+ getcontrols();
+ if (!stralloc_copys(&host,argv[1])) temp_nomem();
+
+ authsender = 0;
+ relayhost = 0;
+
+ addrmangle(&sender,argv[2],&flagalias,0);
+
+ if (sender.len > 1) {
+ i = str_chr(sender.s,'@');
+ if (sender.s[i] == '@')
+ if (!stralloc_copyb(&senddomain,sender.s + i + 1,sender.len - i - 1)) temp_nomem(); // un-terminated
+ }
+
+/* this file is too long -------------------------------------- set domain ip + helohost */
+
+ localip = 0;
+
+ for (i = 0; i <= senddomain.len; ++i)
+ if ((i == 0) || (senddomain.s[i] == '.'))
+ if ((localip = constmap(&mapdomainips,senddomain.s + i,senddomain.len - i)))
+ break;
+
+ if (!localip)
+ localip = constmap(&mapdomainips,"*",1); /* one for all */
+
+ if (localip) {
+ j = str_chr(localip,'%');
+ if (localip[j] != '%') j = 0;
+ k = str_chr(localip,'|');
+ if (localip[k] != '|') k = 0;
+ if (k) { /* helohost */
+ if (!stralloc_copys(&helohost,localip + k + 1)) temp_nomem();
+ if (!stralloc_0(&helohost)) temp_nomem();
+ localip[k] = 0;
+ }
+ if (j) { /* IF index */
+ localip[j] = 0;
+ if (!stralloc_copys(&netif,localip + j + 1)) temp_nomem();
+ if (!stralloc_0(&netif)) temp_nomem();
+ }
+ }
+
+/* this file is too long -------------------------------------- authsender routes */
+
+ for (i = 0; i <= sender.len; ++i)
+ if ((i == 0) || (i == sender.len) || (sender.s[i] == '.') || (sender.s[i] == '@'))
+ if ((authsender = constmap(&mapauthsenders,sender.s + i,sender.len - i)))
+ break;
+
+ if (authsender && !*authsender) authsender = 0;
+
+ if (authsender) {
+ i = str_chr(authsender,'|');
+ if (authsender[i] == '|') {
+ j = str_chr(authsender + i + 1,'|');
+ if (authsender[i + j + 1] == '|') {
+ authsender[i] = 0;
+ authsender[i + j + 1] = 0;
+ if (!stralloc_copys(&user,"")) temp_nomem();
+ if (!stralloc_copys(&user,authsender + i + 1)) temp_nomem();
+ if (!stralloc_copys(&pass,"")) temp_nomem();
+ if (!stralloc_copys(&pass,authsender + i + j + 2)) temp_nomem();
+ }
+ }
+ p = str_chr(authsender,';');
+ if (authsender[p] == ';') {
+ if (authsender[p + 1] == 's') { flagsmtps = 1, p++; }
+ scan_ulong(authsender + p + 1,&port);
+ authsender[p] = 0;
+ }
+ relayhost = authsender;
+ if (!stralloc_copys(&host,authsender)) temp_nomem();
+ }
+
+/* this file is too long -------------------------------------- standard routes */
+
+ if (!authsender) {
+ if (sender.len == 0) { /* bounce routes */
+ if ((relayhost = constmap(&mapqmtproutes,"!@",2))) {
+ qmtpsend = 1; port = PORT_QMTP;
+ } else
+ relayhost = constmap(&mapsmtproutes,"!@",2);
+ }
+
+ if (relayhost && !*relayhost) relayhost = 0;
+
+ if (!relayhost) {
+ for (i = 0; i <= host.len; ++i) { /* qmtproutes */
+ if ((i == 0) || (i == host.len) || (host.s[i] == '.'))
+ if ((relayhost = constmap(&mapqmtproutes,host.s + i,host.len - i))) {
+ qmtpsend = 1; port = PORT_QMTP;
+ break;
+ } /* default smtproutes */
+ if ((relayhost = constmap(&mapsmtproutes,host.s + i,host.len - i)))
+ break;
+ }
+ }
+ if (relayhost && !*relayhost) relayhost = 0;
+
+ if (relayhost) { /* default smtproutes -- authenticated */
+ i = str_chr(relayhost,'|');
+ if (relayhost[i] == '|') {
+ j = str_chr(relayhost + i + 1,'|'); // authenticate
+ if (relayhost[i + j + 1] == '|') {
+ relayhost[i] = 0;
+ relayhost[i + j + 1] = 0;
+ if (!stralloc_copys(&user,"")) temp_nomem();
+ if (!stralloc_copys(&user,relayhost + i + 1)) temp_nomem();
+ if (!stralloc_copys(&pass,"")) temp_nomem();
+ k = str_chr(relayhost + i + j + 2,'|'); // local ip
+ if (relayhost[i + j + k + 2] == '|') {
+ relayhost[i + j + k + 2] = 0;
+ localip = relayhost + i + j + k + 3;
+ }
+ if (!stralloc_copys(&pass,relayhost + i + j + 2)) temp_nomem();
+ }
+ }
+ p = str_chr(relayhost,';');
+ if (relayhost[p] == ';') {
+ if (relayhost[p + 1] == 's') { flagsmtps = 1; p++; } // RFC 8314
+ scan_ulong(relayhost + p + 1,&port);
+ if (qmtpsend && port == PORT_QMTPS) qmtpsend = 2;
+ relayhost[p] = 0;
+ }
+ if (!stralloc_copys(&host,relayhost)) temp_nomem();
+#ifdef IDN2
+ } else {
+ char *asciihost = 0;
+ if (!stralloc_0(&host)) temp_nomem();
+ switch (idn2_lookup_u8(host.s,(uint8_t**)&asciihost,IDN2_NFC_INPUT)) {
+ case IDN2_OK: break;
+ case IDN2_MALLOC: temp_nomem();
+ default: perm_dns();
+ }
+ if (!stralloc_copys(&idnhost,asciihost)) temp_nomem();
+#endif
+ }
+ }
+
+/* this file is too long -------------------------------------- TLS destinations */
+
+
+ flagtls = tls_destination((const stralloc) host); // host may not be 0-terminated
+
+ if (flagtls > 0) {
+ if (tlsdestinfo) {
+ i = str_chr(tlsdestinfo,'|'); /* ca file/dir or cert fingerprint */
+ if (tlsdestinfo[i] == '|') {
+ tlsdestinfo[i] = 0;
+ j = str_chr(tlsdestinfo + i + 1,'|'); /* cipher */
+ if (tlsdestinfo[i + j + 1] == '|') {
+ tlsdestinfo[i + j + 1] = 0;
+ k = str_chr(tlsdestinfo + i + j + 2,'|'); /* cone domain */
+ if (tlsdestinfo[i + j + k + 2] == '|') {
+ tlsdestinfo[i + j + k + 2] = 0;
+ if (str_diffn(tlsdestinfo + j + k + 3,canonhost.s,canonhost.len)) flagtls = 0;
+ }
+ p = str_chr(tlsdestinfo + i + j + 2,';'); /* verifydepth;port */
+ if (tlsdestinfo[i + j + p + 2] == ';') {
+ tlsdestinfo[i + j + p + 2] = 0;
+ if (p > 0) scan_ulong(tlsdestinfo + i + j + 2,&verifydepth);
+ if (tlsdestinfo[i + j + p + 3] == 's') { flagsmtps = 1; p++; } /* RFC 8314 */
+ scan_ulong(tlsdestinfo + i + j + p + 3,&port);
+ }
+ }
+ if (j)
+ if (!stralloc_copys(&ciphers,tlsdestinfo + i + 1)) temp_nomem();
+ }
+
+ /* either ':[=]cafile/cadir' -or- ':;port' */
+
+ if (tlsdestinfo[0] == ';')
+ scan_ulong(tlsdestinfo + 1,&port);
+ else
+ if (!stralloc_copys(&cafile,tlsdestinfo)) temp_nomem();
+ }
+
+/* cafile starts with '=' => it is a fingerprint
+ cafile ends with '/' => consider it as cadir
+ cafile and cadir are now 0-terminated
+ ciphers are alway 0-terminated if given */
+
+ if (cafile.len > 2) {
+ flagverify = 1;
+ if (cafile.s[cafile.len] == '/') {
+ cafile.len = 0;
+ flagverify = 2;
+ if (!stralloc_copys(&cadir,tlsdestinfo)) temp_nomem();
+ if (!stralloc_0(&cadir)) temp_nomem();
+ } else {
+ if (cafile.s[0] == '=') flagverify = -2;
+ if (!stralloc_0(&cafile)) temp_nomem();
+ }
+ } else
+ cafile.len = cadir.len = 0;
+
+ if (ciphers.len > 4) /* otherwise garbage */
+ if (!stralloc_0(&ciphers)) temp_nomem();
+ else
+ ciphers.len = 0;
+
+ if (port == PORT_SMTPS || flagsmtps) flagtls += 10;
+ if (port == PORT_QMTPS) flagtls += 20;
+ }
+
+ if (flagtls == 8) flagverify = -1;
+ if (!flagtls && qmtpsend == 2) flagtls = 20; /* QMTPS */
+
+
+/* this file is too long -------------------------------------- Our Certs - per senddomain */
+
+ if (flagtls > 0) {
+ flagtlsdomain = tls_domaincerts((const stralloc) senddomain); // senddomain un-terminated
+
+ if (flagtlsdomain && tlsdomaininfo) {
+ i = str_chr(tlsdomaininfo,'|');
+ if (tlsdomaininfo[i] == '|') {
+ tlsdomaininfo[i] = 0;
+ j = str_chr(tlsdomaininfo + i + 1,'|');
+ if (tlsdomaininfo[i + j + 1] == '|') {
+ tlsdomaininfo[i + j + 1] = 0;
+ if (!stralloc_copys(&keypwd,"")) temp_nomem();
+ if (!stralloc_copys(&keypwd,tlsdomaininfo + i + j + 2)) temp_nomem();
+ if (!stralloc_0(&keypwd)) temp_nomem();
+ }
+ if (!stralloc_copys(&keyfile,tlsdomaininfo + i + 1)) temp_nomem();
+ if (!stralloc_0(&keyfile)) temp_nomem();
+ }
+ if (!stralloc_copys(&certfile,tlsdomaininfo)) temp_nomem();
+ if (!stralloc_0(&certfile)) temp_nomem();
+ flagtlsdomain = 2;
+ }
+ }
+
+/* this file is too long -------------------------------------- work thru reciplist */
+
+ if (!saa_readyplus(&reciplist,0)) temp_nomem();
+ if (ipme_init() != 1) temp_oserr();
+
+ flagallaliases = 1;
+ recips = argv + 3;
+
+ if (fstat(0,&st) == -1) quit("Z", " unable to fstat stdin");
+ msize = st.st_size;
+ fmt_ulong(msgsize,msize);
+
+ while (*recips) {
+ if (!saa_readyplus(&reciplist,1)) temp_nomem();
+ reciplist.sa[reciplist.len] = sauninit;
+ addrmangle(reciplist.sa + reciplist.len,*recips,&flagalias,!relayhost);
+ if (!flagalias) flagallaliases = 0;
+ ++reciplist.len;
+ ++recips;
+ }
+
+ random = now() + (getpid() << 16);
+#ifdef IDN2
+ switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&idnhost,random)) {
+#else
+ switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random)) {
+#endif
+ case DNS_MEM: temp_nomem();
+ case DNS_ERR: temp_dns();
+ case DNS_COM: temp_dns();
+ case DNS_SOFT: temp_dns();
+#ifdef DEFERREDBOUNCES
+ default: if (!ip.len) temp_nomx();
+#else
+ default: if (!ip.len) perm_nomx();
+#endif
+ }
+
+ prefme = 100000;
+ for (i = 0; i < ip.len; ++i)
+ if (ipme_is(&ip.ix[i]))
+ if (ip.ix[i].pref < prefme)
+ prefme = ip.ix[i].pref;
+
+ if (relayhost) prefme = 300000;
+ if (flagallaliases) prefme = 500000;
+
+ if (localip) {
+ i = str_chr(localip,':');
+ if (localip[i] == ':') ip6flag = 1;
+ else ip6flag = -1;
+ }
+
+ for (i = 0; i < ip.len; ++i) { /* MX with smallest distance */
+ if (ip6flag == -1 && ip.ix[i].af == AF_INET6) continue;
+ if (ip6flag == 1 && ip.ix[i].af == AF_INET) continue;
+ if (ip.ix[i].pref < prefme) break;
+ }
+
+ if (i >= ip.len)
+ perm_ambigmx();
+
+ if (!stralloc_copys(&remotehost,ip.ix[i].mxh)) temp_nomem(); /* take MX hostname for TLSA */
+ if (!stralloc_0(&remotehost)) temp_nomem();
+
+ for (i = 0; i < ip.len; ++i) {
+ if (ip.ix[i].pref < prefme) {
+ if (ip6flag == -1 && ip.ix[i].af == AF_INET6) continue; /* explicit binding */
+ if (ip6flag == 1 && ip.ix[i].af == AF_INET) continue;
+ if (tcpto(&ip.ix[i])) continue;
+
+ smtpfd = socket(ip.ix[i].af,SOCK_STREAM,0);
+ if (smtpfd == -1) continue;
+
+ if (localip) { /* set domain ip */
+ if (!stralloc_copyb(&sendip,localip,str_len(localip))) temp_nomem();
+ j = str_chr(localip,':');
+ if (localip[j] == ':') {
+ if (!ip6_scan(localip,ip6)) temp_noip(); /* IPv6 */
+ if (byte_equal(ip.ix[i].addr.ip6.d,16,ip6)) continue;
+ ifidx = socket_getifidx(netif.s);
+ if (socket_bind6(smtpfd,ip6,0,ifidx) < 0) temp_osip();
+ } else {
+ if (!ip4_scan(localip,ip4)) temp_noip(); /* IPv4 */
+ if (byte_equal(ip.ix[i].addr.ip4.d,4,ip4)) continue;
+ if (socket_bind4(smtpfd,ip4,0) < 0) temp_osip();
+ }
+ }
+
+
+ AGAIN:
+ if (ip.ix[i].af == AF_INET6)
+ r = timeoutconn6(smtpfd,(char *)&ip.ix[i].addr.ip6.d,(unsigned int) port,timeoutconnect,ifidx);
+ else
+ r = timeoutconn4(smtpfd,(char *)&ip.ix[i].addr.ip4.d,(unsigned int) port,timeoutconnect);
+ if (r == 0) {
+ tcpto_err(&ip.ix[i],0);
+ partner = ip.ix[i];
+ if (qmtpsend)
+ qmtp();
+ else
+ smtp(); /* read qmail/THOUGHTS; section 6 */
+ }
+ if (flagtls == 9 && errno == EPROTO) {
+ flagtls = 0; goto AGAIN;
+ }
+ if (errno == ETIMEDOUT || errno == ECONNREFUSED || errno == EPROTO)
+ tcpto_err(&ip.ix[i],1);
+ close(smtpfd);
+ }
+ }
+ temp_noconn();
+}
diff --git a/src/qmail-rspawn.c b/src/qmail-rspawn.c
new file mode 100644
index 0000000..66a8b70
--- /dev/null
+++ b/src/qmail-rspawn.c
@@ -0,0 +1,99 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "fd.h"
+#include "wait.h"
+#include "buffer.h"
+#include "exit.h"
+#include "error.h"
+#include "ipalloc.h"
+#include "tcpto.h"
+#include "auto_qmail.h"
+#include "open.h"
+#include "pathexec.h"
+
+void initialize(int argc,char **argv) { tcpto_clean(); }
+
+int truncreport = 0;
+
+void report(buffer *log,int wstat,char *s,int len)
+{
+ int j;
+ int k;
+ int result;
+ int orr;
+
+ if (wait_crashed(wstat)) { buffer_putsflush(log,"Zqmail-spawn: qmail-remote crashed.\n"); return; }
+
+ switch (wait_exitcode(wstat)) {
+ case 0: break;
+ case 111: buffer_putsflush(log,"Zqmail-rspawn: Unable to run qmail-remote.\n"); break;
+ default: buffer_putsflush(log,"Dqmail-rspawn: Unable to run qmail-remote. \n"); return;
+ }
+
+ if (!len) { buffer_putsflush(log,"Zqmail-rspawn: qmail-remote produced no output.\n"); return; }
+
+ result = -1;
+ j = 0;
+
+ for (k = 0; k < len; ++k)
+ if (!s[k]) {
+ if (s[j] == 'K') { result = 1; break; }
+ if (s[j] == 'Z') { result = 0; break; }
+ if (s[j] == 'D') break;
+ j = k + 1;
+ }
+
+ orr = result;
+
+ switch (s[0]) {
+ case 's': orr = 0; break;
+ case 'h': orr = -1;
+ }
+
+ switch (orr) {
+ case 1: buffer_put(log,"K",1); break;
+ case 0: buffer_put(log,"Z",1); break;
+ case -1: buffer_put(log,"D",1); break;
+ }
+
+ for (k = 1; k < len;)
+ if (!s[k++]) {
+ buffer_puts(log,s + 1);
+ if (result <= orr)
+ if (k < len)
+ switch (s[k]) {
+ case 'Z': case 'D': case 'K':
+ buffer_puts(log,s + k + 1);
+ }
+ break;
+ }
+}
+
+int spawn(int fdmess,int fdout,const char *s,char *r,const int at)
+{
+ int f;
+ char *(args[5]);
+ struct stat st;
+
+ if (chdir(auto_qmail) == -1) _exit(110);
+ if (!stat("control/dkimdomains",&st))
+ args[0] = "qmail-dksign";
+ else
+ args[0] = "qmail-remote";
+ args[1] = r + at + 1;
+ args[2] = s;
+ args[3] = r;
+ args[4] = 0;
+
+ if (chdir("queue/mess") == -1) _exit(110);
+
+ if (!(f = vfork())) {
+ if (fd_move(0,fdmess) == -1) _exit(111);
+ if (fd_move(1,fdout) == -1) _exit(111);
+ if (fd_copy(2,1) == -1) _exit(111);
+ pathexec(args);
+ if (errno) _exit(111);
+ _exit(100);
+ }
+ return f;
+}
diff --git a/src/qmail-send.c b/src/qmail-send.c
new file mode 100644
index 0000000..76925d2
--- /dev/null
+++ b/src/qmail-send.c
@@ -0,0 +1,1440 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <utime.h>
+#include "error.h"
+#include "sig.h"
+#include "direntry.h"
+#include "control.h"
+#include "select.h"
+#include "open.h"
+#include "seek.h"
+#include "exit.h"
+#include "lock.h"
+#include "ndelay.h"
+#include "now.h"
+#include "getln.h"
+#include "buffer.h"
+#include "alloc.h"
+#include "genalloc.h"
+#include "stralloc.h"
+#include "logmsg.h"
+#include "str.h"
+#include "byte.h"
+#include "fmt.h"
+#include "scan.h"
+#include "case.h"
+#include "auto_qmail.h"
+#include "trigger.h"
+#include "newfield.h"
+#include "quote.h"
+#include "qmail.h"
+#include "qsutil.h"
+#include "prioq.h"
+#include "constmap.h"
+#include "fmtqfn.h"
+#include "readsubdir.h"
+#include "sendtodo.h"
+
+int lifetime = 604800;
+int bouncemaxbytes = 0;
+
+stralloc percenthack = {0};
+struct constmap mappercenthack;
+stralloc locals = {0};
+struct constmap maplocals;
+stralloc vdoms = {0};
+struct constmap mapvdoms;
+stralloc envnoathost = {0};
+stralloc bouncefrom = {0};
+stralloc bouncehost = {0};
+stralloc doublebounceto = {0};
+stralloc doublebouncehost = {0};
+
+char strnum2[FMT_ULONG];
+char strnum3[FMT_ULONG];
+
+#define CHANNELS 2
+char *chanaddr[CHANNELS] = { "local/", "remote/" };
+char *chanstatusmsg[CHANNELS] = { " local ", " remote " };
+char *tochan[CHANNELS] = { " to local ", " to remote " };
+int chanfdout[CHANNELS] = { 1, 3 };
+int chanfdin[CHANNELS] = { 2, 4 };
+int chanskip[CHANNELS] = { 10, 20 };
+
+int flagexitasap = 0; void sigterm() { flagexitasap = 1; }
+int flagrunasap = 0; void sigalrm() { flagrunasap = 1; }
+int flagreadasap = 0; void sighup() { flagreadasap = 1; }
+
+void cleandied()
+{
+ log1s("alert: lost connection to qmail-clean ... exiting\n");
+ flagexitasap = 1;
+}
+
+int flagspawnalive[CHANNELS];
+
+void spawndied(int c)
+{
+ log1s("alert: oh no! lost spawn connection! dying...\n");
+ flagspawnalive[c] = 0;
+ flagexitasap = 1;
+}
+
+#define REPORTMAX 10000
+
+datetime_sec recent;
+
+
+/* this file is too long ---------------------------------------- FILE CREATE */
+
+stralloc fn = {0};
+stralloc fn2 = {0};
+char fnmake_strnum[FMT_ULONG];
+
+void fnmake_init()
+{
+ while (!stralloc_ready(&fn,FMTQFN)) nomem();
+ while (!stralloc_ready(&fn2,FMTQFN)) nomem();
+}
+
+void fnmake_info(unsigned long id) { fn.len = fmtqfn(fn.s,"info/",id,1); }
+void fnmake_todo(unsigned long id) { fn.len = fmtqfn(fn.s,"todo/",id,1); }
+void fnmake_mess(unsigned long id) { fn.len = fmtqfn(fn.s,"mess/",id,1); }
+void fnmake_foop(unsigned long id) { fn.len = fmtqfn(fn.s,"foop/",id,0); }
+void fnmake_split(unsigned long id) { fn.len = fmtqfn(fn.s,"",id,1); }
+void fnmake2_bounce(unsigned long id) { fn2.len = fmtqfn(fn2.s,"bounce/",id,0); }
+void fnmake_chanaddr(unsigned long id,int c) { fn.len = fmtqfn(fn.s,chanaddr[c],id,1); }
+
+
+/* this file is too long ----------------------------------------- REWRITING */
+
+void senderadd(stralloc *sa,char *sender,char *recip)
+{
+ int i;
+ int j;
+ int k;
+
+ i = str_len(sender);
+ if (i >= 4)
+ if (str_equal(sender + i - 4,"-@[]")) {
+ j = byte_rchr(sender,i - 4,'@');
+ k = str_rchr(recip,'@');
+ if (recip[k] && (j + 5 <= i)) {
+ /* owner-@host-@[] -> owner-recipbox=reciphost@host */
+ while (!stralloc_catb(sa,sender,j)) nomem();
+ while (!stralloc_catb(sa,recip,k)) nomem();
+ while (!stralloc_cats(sa,"=")) nomem();
+ while (!stralloc_cats(sa,recip + k + 1)) nomem();
+ while (!stralloc_cats(sa,"@")) nomem();
+ while (!stralloc_catb(sa,sender + j + 1,i - 5 - j)) nomem();
+ return;
+ }
+ }
+ while (!stralloc_cats(sa,sender)) nomem();
+}
+
+
+/* this file is too long ---------------------------------------------- INFO */
+
+int getinfo(stralloc *sa,datetime_sec *dt,unsigned long id)
+{
+ int fdnumber;
+ struct stat st;
+ static stralloc line = {0};
+ int match;
+ char buf[128];
+ buffer b;
+
+ fnmake_info(id);
+ fdnumber = open_read(fn.s);
+ if (fdnumber == -1) return 0;
+ if (fstat(fdnumber,&st) == -1) { close(fdnumber); return 0; }
+ buffer_init(&b,read,fdnumber,buf,sizeof(buf));
+ if (getln(&b,&line,&match,'\0') == -1) { close(fdnumber); return 0; }
+ close(fdnumber);
+ if (!match) return 0;
+ if (line.s[0] != 'F') return 0;
+
+ *dt = st.st_mtime;
+ while (!stralloc_copys(sa,line.s + 1)) nomem();
+ while (!stralloc_0(sa)) nomem();
+ return 1;
+}
+
+
+/* this file is too long ------------------------------------- COMMUNICATION */
+
+buffer toqc; char toqcbuf[1024];
+buffer fromqc; char fromqcbuf[1024];
+stralloc comm_buf[CHANNELS] = { {0}, {0} };
+int comm_pos[CHANNELS];
+
+void comm_init()
+{
+ int c;
+
+ buffer_init(&toqc,write,5,toqcbuf,sizeof(toqcbuf));
+ buffer_init(&fromqc,read,6,fromqcbuf,sizeof(fromqcbuf));
+ for (c = 0; c < CHANNELS; ++c)
+ if (ndelay_on(chanfdout[c]) == -1)
+ /* this is so stupid: NDELAY semantics should be default on write */
+ spawndied(c); /* drastic, but better than risking deadlock */
+}
+
+int comm_canwrite(int c)
+{
+ /* XXX: could allow a bigger buffer; say 10 recipients */
+ if (comm_buf[c].s && comm_buf[c].len) return 0;
+ return 1;
+}
+
+void comm_write(int c,int delnum,unsigned long id,char *sender,char *recip)
+{
+ char ch;
+
+ if (comm_buf[c].s && comm_buf[c].len) return;
+ while (!stralloc_copys(&comm_buf[c],"")) nomem();
+ ch = delnum;
+ while (!stralloc_append(&comm_buf[c],&ch)) nomem();
+ fnmake_split(id);
+ while (!stralloc_cats(&comm_buf[c],fn.s)) nomem();
+ while (!stralloc_0(&comm_buf[c])) nomem();
+ senderadd(&comm_buf[c],sender,recip);
+ while (!stralloc_0(&comm_buf[c])) nomem();
+ while (!stralloc_cats(&comm_buf[c],recip)) nomem();
+ while (!stralloc_0(&comm_buf[c])) nomem();
+ comm_pos[c] = 0;
+}
+
+void comm_selprep(int *nfds,fd_set *wfds)
+{
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagspawnalive[c])
+ if (comm_buf[c].s && comm_buf[c].len) {
+ FD_SET(chanfdout[c],wfds);
+ if (*nfds <= chanfdout[c])
+ *nfds = chanfdout[c] + 1;
+ }
+}
+
+void comm_do(fd_set *wfds)
+{
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagspawnalive[c])
+ if (comm_buf[c].s && comm_buf[c].len)
+ if (FD_ISSET(chanfdout[c],wfds)) {
+ int w;
+ int len;
+ len = comm_buf[c].len;
+
+ w = write(chanfdout[c],comm_buf[c].s + comm_pos[c],len - comm_pos[c]);
+ if (w <= 0) {
+ if ((w == -1) && (errno == EPIPE))
+ spawndied(c);
+ else
+ continue; /* kernel select() bug; can't avoid busy-looping */
+ } else {
+ comm_pos[c] += w;
+ if (comm_pos[c] == len)
+ comm_buf[c].len = 0;
+ }
+ }
+}
+
+
+/* this file is too long ------------------------------------------ CLEANUPS */
+
+int flagcleanup; /* if 1, cleanupdir is initialized and ready */
+readsubdir cleanupdir;
+datetime_sec cleanuptime;
+
+void cleanup_init()
+{
+ flagcleanup = 0;
+ cleanuptime = now();
+}
+
+void cleanup_selprep(datetime_sec *wakeup)
+{
+ if (flagcleanup) *wakeup = 0;
+ if (*wakeup > cleanuptime) *wakeup = cleanuptime;
+}
+
+void cleanup_do()
+{
+ char ch;
+ struct stat st;
+ unsigned long id;
+
+ if (!flagcleanup) {
+ if (recent < cleanuptime) return;
+ readsubdir_init(&cleanupdir,"mess",pausedir);
+ flagcleanup = 1;
+ }
+
+ switch (readsubdir_next(&cleanupdir,&id)) {
+ case 1: break;
+ case 0: flagcleanup = 0; cleanuptime = recent + SLEEP_CLEANUP;
+ default: return;
+ }
+
+ fnmake_mess(id);
+ if (stat(fn.s,&st) == -1) return; /* probably qmail-queue deleted it */
+ if (recent <= st.st_atime + OSSIFIED) return;
+
+ fnmake_info(id);
+ if (stat(fn.s,&st) == 0) return;
+ if (errno != ENOENT) return;
+
+ fnmake_todo(id);
+ if (stat(fn.s,&st) == 0) return;
+ if (errno != ENOENT) return;
+
+ fnmake_foop(id);
+ if (buffer_putflush(&toqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (buffer_get(&fromqc,&ch,1) != 1) { cleandied(); return; }
+ if (ch != '+')
+ log3s("warning: qmail-clean unable to clean up ",fn.s,"\n");
+}
+
+
+/* this file is too long ----------------------------------- PRIORITY QUEUES */
+
+prioq pqdone = {0}; /* -todo +info; HOPEFULLY -local -remote */
+prioq pqchan[CHANNELS] = { {0}, {0} };
+/* pqchan 0: -todo +info +local ?remote */
+/* pqchan 1: -todo +info ?local +remote */
+prioq pqfail = {0}; /* stat() failure; has to be pqadded again */
+
+void pqadd(unsigned long id)
+{
+ struct prioq_elt pe;
+ struct prioq_elt pechan[CHANNELS];
+ int flagchan[CHANNELS];
+ struct stat st;
+ int c;
+
+#define CHECKSTAT if (errno != ENOENT) goto FAIL;
+
+ fnmake_info(id);
+ if (stat(fn.s,&st) == -1) {
+ CHECKSTAT
+ return; /* someone yanking our chain */
+ }
+
+ fnmake_todo(id);
+ if (stat(fn.s,&st) != -1) return; /* look, ma, dad crashed writing info! */
+ CHECKSTAT
+
+ for (c = 0; c < CHANNELS; ++c) {
+ fnmake_chanaddr(id,c);
+ if (stat(fn.s,&st) == -1) { flagchan[c] = 0; CHECKSTAT }
+ else { flagchan[c] = 1; pechan[c].id = id; pechan[c].dt = st.st_mtime; }
+ }
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagchan[c])
+ while (!prioq_insert(&pqchan[c],&pechan[c])) nomem();
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagchan[c]) break;
+
+ if (c == CHANNELS) {
+ pe.id = id; pe.dt = now();
+ while (!prioq_insert(&pqdone,&pe)) nomem();
+ }
+
+ return;
+
+ FAIL:
+ log3s("warning: unable to stat ",fn.s,"; will try again later\n");
+ pe.id = id; pe.dt = now() + SLEEP_SYSFAIL;
+ while (!prioq_insert(&pqfail,&pe)) nomem();
+}
+
+void pqstart()
+{
+ readsubdir rs;
+ int x;
+ unsigned long id;
+
+ readsubdir_init(&rs,"info",pausedir);
+
+ while ((x = readsubdir_next(&rs,&id)))
+ if (x > 0) pqadd(id);
+}
+
+void pqfinish()
+{
+ int c;
+ struct prioq_elt pe;
+ time_t ut[2]; /* XXX: more portable than utimbuf, but still worrisome */
+
+ for (c = 0; c < CHANNELS; ++c)
+ while (prioq_min(&pqchan[c],&pe)) {
+ prioq_delmin(&pqchan[c]);
+ fnmake_chanaddr(pe.id,c);
+ ut[0] = ut[1] = pe.dt;
+ if (utime(fn.s,ut) == -1)
+ log3s("warning: unable to utime ",fn.s,"; message will be retried too soon\n");
+ }
+}
+
+void pqrun()
+{
+ int c;
+ int i;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (pqchan[c].p)
+ if (pqchan[c].len)
+ for (i = 0; i < pqchan[c].len; ++i)
+ pqchan[c].p[i].dt = recent;
+}
+
+
+/* this file is too long ---------------------------------------------- JOBS */
+
+struct job
+{
+ int refs; /* if 0, this struct is unused */
+ unsigned long id;
+ int channel;
+ datetime_sec retry;
+ stralloc sender;
+ int numtodo;
+ int flaghiteof;
+ int flagdying;
+};
+
+int numjobs;
+struct job *jo;
+
+void job_init()
+{
+ int j;
+
+ while (!(jo = (struct job *) alloc(numjobs * sizeof(struct job)))) nomem();
+ for (j = 0; j < numjobs; ++j) {
+ jo[j].refs = 0;
+ jo[j].sender.s = 0;
+ }
+}
+
+int job_avail()
+{
+ int j;
+
+ for (j = 0; j < numjobs; ++j)
+ if (!jo[j].refs) return 1;
+ return 0;
+}
+
+int job_open(unsigned long id,int channel)
+{
+ int j;
+
+ for (j = 0; j < numjobs; ++j)
+ if (!jo[j].refs) break;
+ if (j == numjobs) return -1;
+ jo[j].refs = 1;
+ jo[j].id = id;
+ jo[j].channel = channel;
+ jo[j].numtodo = 0;
+ jo[j].flaghiteof = 0;
+ return j;
+}
+
+void job_close(int j)
+{
+ struct prioq_elt pe;
+ struct stat st;
+ int c;
+
+ if (0 < --jo[j].refs) return;
+
+ pe.id = jo[j].id;
+ pe.dt = jo[j].retry;
+
+ if (jo[j].flaghiteof && !jo[j].numtodo) {
+ fnmake_chanaddr(jo[j].id,jo[j].channel);
+ if (unlink(fn.s) == -1) {
+ log3s("warning: unable to unlink ",fn.s,"; will try again later\n");
+ pe.dt = now() + SLEEP_SYSFAIL;
+ } else {
+ for (c = 0; c < CHANNELS; ++c) if (c != jo[j].channel) {
+ fnmake_chanaddr(jo[j].id,c);
+ if (stat(fn.s,&st) == 0) return; /* more channels going */
+ if (errno != ENOENT) {
+ log3s("warning: unable to stat ",fn.s,"\n");
+ break; /* this is the only reason for HOPEFULLY */
+ }
+ }
+ pe.dt = now();
+ while (!prioq_insert(&pqdone,&pe)) nomem();
+ return;
+ }
+ }
+
+ while (!prioq_insert(&pqchan[jo[j].channel],&pe)) nomem();
+}
+
+
+/* this file is too long ------------------------------------------- BOUNCES */
+
+char *stripvdomprepend(char *recip)
+{
+ int i;
+ char *domain;
+ int domainlen;
+ char *prepend;
+
+ i = str_rchr(recip,'@');
+ if (!recip[i]) return recip;
+ domain = recip + i + 1;
+ domainlen = str_len(domain);
+
+ for (i = 0; i <= domainlen; ++i)
+ if ((i == 0) || (i == domainlen) || (domain[i] == '.'))
+ if ((prepend = constmap(&mapvdoms,domain + i,domainlen - i))) {
+ if (!*prepend) break;
+ i = str_len(prepend);
+ if (str_diffn(recip,prepend,i)) break;
+ if (recip[i] != '-') break;
+ return recip + i + 1;
+ }
+
+ return recip;
+}
+
+stralloc bouncetext = {0};
+
+void addbounce(unsigned long id,char *recip,char *report)
+{
+ int fd;
+ int pos;
+ int w;
+
+ while (!stralloc_copys(&bouncetext,"<")) nomem();
+ while (!stralloc_cats(&bouncetext,stripvdomprepend(recip))) nomem();
+
+ for (pos = 0; pos < bouncetext.len; ++pos)
+ if (bouncetext.s[pos] == '\n')
+ bouncetext.s[pos] = '_';
+
+ while (!stralloc_cats(&bouncetext,">:\n")) nomem();
+ while (!stralloc_cats(&bouncetext,report)) nomem();
+
+ if (report[0])
+ if (report[str_len(report) - 1] != '\n')
+ while (!stralloc_cats(&bouncetext,"\n")) nomem();
+
+ for (pos = bouncetext.len - 2; pos > 0; --pos)
+ if (bouncetext.s[pos] == '\n')
+ if (bouncetext.s[pos - 1] == '\n')
+ bouncetext.s[pos] = '/';
+
+ while (!stralloc_cats(&bouncetext,"\n")) nomem();
+ fnmake2_bounce(id);
+
+ for (;;) {
+ fd = open_append(fn2.s);
+ if (fd != -1) break;
+ log1s("alert: unable to append to bounce message; HELP! sleeping...\n");
+ sleep(10);
+ }
+
+ pos = 0;
+
+ while (pos < bouncetext.len) {
+ w = write(fd,bouncetext.s + pos,bouncetext.len - pos);
+ if (w <= 0) {
+ log1s("alert: unable to append to bounce message; HELP! sleeping...\n");
+ sleep(10);
+ }
+ else
+ pos += w;
+ }
+ close(fd);
+}
+
+int injectbounce(unsigned long id)
+{
+ struct qmail qqt;
+ struct stat st;
+ char *bouncesender;
+ char *bouncerecip;
+ int r;
+ int fd;
+ buffer bi;
+ char buf[128];
+ char inbuf[128];
+ static stralloc sender = {0};
+ static stralloc quoted = {0};
+ datetime_sec birth;
+ unsigned long qp;
+ int bytestogo;
+ int bytestoget;
+
+ if (!getinfo(&sender,&birth,id)) return 0; /* XXX: print warning */
+
+ /* owner-@host-@[] -> owner-@host */
+ if (sender.len >= 5)
+ if (str_equal(sender.s + sender.len - 5,"-@[]")) {
+ sender.len -= 4;
+ sender.s[sender.len - 1] = 0;
+ }
+
+ fnmake2_bounce(id);
+ fnmake_mess(id);
+
+ if (stat(fn2.s,&st) == -1) {
+ if (errno == ENOENT) return 1;
+ log3s("warning: unable to stat ",fn2.s,"\n");
+ return 0;
+ }
+
+ if (str_equal(sender.s,"#@[]"))
+ log3s("triple bounce: discarding ",fn2.s,"\n");
+ else if (!*sender.s && *doublebounceto.s == '@')
+ log3s("double bounce: discarding ",fn2.s,"\n");
+ else {
+ if (qmail_open(&qqt) == -1)
+ { log1s("warning: unable to start qmail-queue, will try later\n"); return 0; }
+ qp = qmail_qp(&qqt);
+
+ if (*sender.s) { bouncesender = ""; bouncerecip = sender.s; }
+ else { bouncesender = "#@[]"; bouncerecip = doublebounceto.s; }
+
+ while (!newfield_datemake(now())) nomem();
+ qmail_put(&qqt,newfield_date.s,newfield_date.len);
+ qmail_puts(&qqt,"From: ");
+ while (!quote(&quoted,&bouncefrom)) nomem();
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,"@");
+ qmail_put(&qqt,bouncehost.s,bouncehost.len);
+ qmail_puts(&qqt,"\nTo: ");
+ while (!quote2(&quoted,bouncerecip)) nomem();
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,"\n\
+Subject: failure notice\n\
+\n\
+Hi. This is the qmail-send program at ");
+ qmail_put(&qqt,bouncehost.s,bouncehost.len);
+ qmail_puts(&qqt,*sender.s ? ".\n\
+I'm afraid I wasn't able to deliver your message to the following addresses.\n\
+This is a permanent error; I've given up. Sorry it didn't work out.\n\
+\n\
+" : ".\n\
+I tried to deliver a bounce message to this address, but the bounce bounced!\n\
+\n\
+");
+
+ fd = open_read(fn2.s);
+ if (fd == -1)
+ qmail_fail(&qqt);
+ else {
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+ while ((r = buffer_get(&bi,buf,sizeof(buf))) > 0)
+ qmail_put(&qqt,buf,r);
+
+ close(fd);
+ if (r == -1) qmail_fail(&qqt);
+ }
+
+ qmail_puts(&qqt,*sender.s ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line is the original bounce.\n\n");
+ qmail_puts(&qqt,"Return-Path: <");
+ while (!quote2(&quoted,sender.s)) nomem();
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,">\n");
+
+ fd = open_read(fn.s);
+ if (fd == -1)
+ qmail_fail(&qqt);
+ else {
+ if (bouncemaxbytes) {
+ bytestogo = bouncemaxbytes;
+ bytestoget = (bytestogo < sizeof(buf)) ? bytestogo : sizeof(buf);
+
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ while (bytestoget > 0 && (r = buffer_get(&bi,buf,bytestoget)) > 0) {
+ qmail_put(&qqt,buf,r);
+ bytestogo -= bytestoget;
+ bytestoget = (bytestogo < sizeof(buf)) ? bytestogo : sizeof(buf);
+ }
+ if (r > 0)
+ qmail_puts(&qqt,"\n\n--- Rest of message truncated.\n");
+ } else { /* preserve default behavior */
+ buffer_init(&bi,read,fd,inbuf,sizeof(inbuf));
+
+ while ((r = buffer_get(&bi,buf,sizeof(buf))) > 0)
+ qmail_put(&qqt,buf,r);
+ }
+ close(fd);
+ if (r == -1) qmail_fail(&qqt);
+ }
+
+ qmail_from(&qqt,bouncesender);
+ qmail_to(&qqt,bouncerecip);
+
+ if (*qmail_close(&qqt)) {
+ log1s("warning: trouble injecting bounce message, will try later\n");
+ return 0;
+ }
+
+ strnum2[fmt_ulong(strnum2,id)] = 0;
+ log2s("bounce msg ",strnum2);
+ strnum2[fmt_ulong(strnum2,qp)] = 0;
+ log3s(" qp ",strnum2,"\n");
+ }
+
+ if (unlink(fn2.s) != 0) {
+ log3s("warning: unable to unlink ",fn2.s,"\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+
+/* this file is too long ---------------------------------------- DELIVERIES */
+
+struct del {
+ int used;
+ int j;
+ unsigned long delid;
+ seek_pos mpos;
+ stralloc recip;
+};
+
+unsigned long masterdelid = 1;
+unsigned int concurrency[CHANNELS] = { 10, 20 };
+unsigned int concurrencyused[CHANNELS] = { 0, 0 };
+struct del *d[CHANNELS];
+stralloc dline[CHANNELS];
+char delbuf[2048];
+
+void del_status()
+{
+ int c;
+
+ log1s("status:");
+ for (c = 0; c < CHANNELS; ++c) {
+ strnum2[fmt_ulong(strnum2,(unsigned long) concurrencyused[c])] = 0;
+ strnum3[fmt_ulong(strnum3,(unsigned long) concurrency[c])] = 0;
+ log2s(chanstatusmsg[c],strnum2);
+ log2s("/",strnum3);
+ }
+ if (flagexitasap) log1s(" exitasap");
+ log1s("\n");
+}
+
+void del_init()
+{
+ int c;
+ int i;
+
+ for (c = 0; c < CHANNELS; ++c) {
+ flagspawnalive[c] = 1;
+ while (!(d[c] = (struct del *) alloc(concurrency[c] * sizeof(struct del))))
+ nomem();
+ for (i = 0; i < concurrency[c]; ++i)
+ { d[c][i].used = 0; d[c][i].recip.s = 0; }
+ dline[c].s = 0;
+ while (!stralloc_copys(&dline[c],"")) nomem();
+ }
+
+ del_status();
+}
+
+int del_canexit()
+{
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagspawnalive[c]) /* if dead, nothing we can do about its jobs */
+ if (concurrencyused[c]) return 0;
+ return 1;
+}
+
+int del_avail(int c)
+{
+ return flagspawnalive[c] && comm_canwrite(c) && (concurrencyused[c] < concurrency[c]);
+}
+
+void del_start(int j,seek_pos mpos,char *recip)
+{
+ int i;
+ int c;
+
+ c = jo[j].channel;
+ if (!flagspawnalive[c]) return;
+ if (!comm_canwrite(c)) return;
+
+ for (i = 0; i < concurrency[c]; ++i)
+ if (!d[c][i].used) break;
+ if (i == concurrency[c]) return;
+
+ if (!stralloc_copys(&d[c][i].recip,recip)) { nomem(); return; }
+ if (!stralloc_0(&d[c][i].recip)) { nomem(); return; }
+ d[c][i].j = j; ++jo[j].refs;
+ d[c][i].delid = masterdelid++;
+ d[c][i].mpos = mpos;
+ d[c][i].used = 1; ++concurrencyused[c];
+
+ comm_write(c,i,jo[j].id,jo[j].sender.s,recip);
+
+ strnum2[fmt_ulong(strnum2,d[c][i].delid)] = 0;
+ strnum3[fmt_ulong(strnum3,jo[j].id)] = 0;
+ log2s("starting delivery ",strnum2);
+ log3s(": msg ",strnum3,tochan[c]);
+ logsafe(recip);
+ log1s("\n");
+ del_status();
+}
+
+void markdone(int c,unsigned long id,seek_pos pos)
+{
+ struct stat st;
+ int fd;
+
+ fnmake_chanaddr(id,c);
+
+ for (;;) {
+ fd = open_write(fn.s);
+ if (fd == -1) break;
+ if (fstat(fd,&st) == -1) { close(fd); break; }
+ if (seek_set(fd,pos) == -1) { close(fd); break; }
+ if (write(fd,"D",1) != 1) { close(fd); break; }
+ /* further errors -> double delivery without us knowing about it, oh well */
+ close(fd);
+ return;
+ }
+ log3s("warning: trouble marking ",fn.s,"; message will be delivered twice!\n");
+}
+
+void del_dochan(int c)
+{
+ int r;
+ char ch;
+ int i;
+ int delnum;
+
+ r = read(chanfdin[c],delbuf,sizeof(delbuf));
+ if (r == -1) return;
+ if (r == 0) { spawndied(c); return; }
+
+ for (i = 0; i < r; ++i) {
+ ch = delbuf[i];
+ while (!stralloc_append(&dline[c],&ch)) nomem();
+
+ if (dline[c].len > REPORTMAX)
+ dline[c].len = REPORTMAX;
+ /* qmail-lspawn and qmail-rspawn are responsible for keeping it short */
+ /* but from a security point of view, we don't trust rspawn */
+
+ if (!ch && (dline[c].len > 1)) {
+ delnum = (unsigned int) (unsigned char) dline[c].s[0];
+ if ((delnum < 0) || (delnum >= concurrency[c]) || !d[c][delnum].used)
+ log1s("warning: internal error: delivery report out of range\n");
+ else {
+ strnum3[fmt_ulong(strnum3,d[c][delnum].delid)] = 0;
+ if (dline[c].s[1] == 'Z')
+ if (jo[d[c][delnum].j].flagdying) {
+ dline[c].s[1] = 'D';
+ --dline[c].len;
+ while (!stralloc_cats(&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
+ while (!stralloc_0(&dline[c])) nomem();
+ }
+
+ switch (dline[c].s[1]) {
+ case 'K':
+ log3s("delivery ",strnum3,": success: ");
+ logsafe(dline[c].s + 2);
+ log1s("\n");
+ markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
+ --jo[d[c][delnum].j].numtodo;
+ break;
+ case 'Z':
+ log3s("delivery ",strnum3,": deferral: ");
+ logsafe(dline[c].s + 2);
+ log1s("\n");
+ break;
+ case 'D':
+ log3s("delivery ",strnum3,": failure: ");
+ logsafe(dline[c].s + 2);
+ log1s("\n");
+ addbounce(jo[d[c][delnum].j].id,d[c][delnum].recip.s,dline[c].s + 2);
+ markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
+ --jo[d[c][delnum].j].numtodo;
+ break;
+ default:
+ log3s("delivery ",strnum3,": report mangled, will defer\n");
+ }
+
+ job_close(d[c][delnum].j);
+ d[c][delnum].used = 0; --concurrencyused[c];
+ del_status();
+
+ }
+ dline[c].len = 0;
+ }
+ }
+}
+
+void del_selprep(int *nfds,fd_set *rfds)
+{
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagspawnalive[c]) {
+ FD_SET(chanfdin[c],rfds);
+ if (*nfds <= chanfdin[c])
+ *nfds = chanfdin[c] + 1;
+ }
+}
+
+void del_do(fd_set *rfds)
+{
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagspawnalive[c])
+ if (FD_ISSET(chanfdin[c],rfds))
+ del_dochan(c);
+}
+
+
+/* this file is too long -------------------------------------------- PASSES */
+
+struct
+{
+ unsigned long id; /* if 0, need a new pass */
+ int j; /* defined if id; job number */
+ int fd; /* defined if id; reading from {local,remote} */
+ seek_pos mpos; /* defined if id; mark position */
+ buffer b;
+ char buf[128];
+}
+pass[CHANNELS];
+
+void pass_init()
+{
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c) pass[c].id = 0;
+}
+
+void pass_selprep(datetime_sec *wakeup)
+{
+ int c;
+ struct prioq_elt pe;
+ if (flagexitasap) return;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (pass[c].id)
+ if (del_avail(c))
+ { *wakeup = 0; return; }
+
+ if (job_avail())
+ for (c = 0; c < CHANNELS; ++c)
+ if (!pass[c].id)
+ if (prioq_min(&pqchan[c],&pe))
+ if (*wakeup > pe.dt) *wakeup = pe.dt;
+
+ if (prioq_min(&pqfail,&pe))
+ if (*wakeup > pe.dt)
+ *wakeup = pe.dt;
+
+ if (prioq_min(&pqdone,&pe))
+ if (*wakeup > pe.dt)*wakeup = pe.dt;
+}
+
+static datetime_sec squareroot(datetime_sec x) /* result^2 <= x < (result + 1)^2 ; assuming: >= 0 */
+{
+ datetime_sec y;
+ datetime_sec yy;
+ datetime_sec y21;
+ int j;
+
+ y = 0; yy = 0;
+ for (j = 15; j >= 0; --j) {
+ y21 = (y << (j + 1)) + (1 << (j + j));
+ if (y21 <= x - yy) { y += (1 << j); yy += y21; }
+ }
+ return y;
+}
+
+datetime_sec nextretry(datetime_sec birth,int c)
+{
+ int n;
+
+ if (birth > recent) n = 0;
+ else n = squareroot(recent - birth); /* no need to add fuzz to recent */
+
+ n += chanskip[c];
+ return birth + n * n;
+}
+
+void pass_dochan(int c)
+{
+ datetime_sec birth;
+ struct prioq_elt pe;
+ static stralloc line = {0};
+ int match;
+
+ if (flagexitasap) return;
+
+ if (!pass[c].id) {
+ if (!job_avail()) return;
+ if (!prioq_min(&pqchan[c],&pe)) return;
+ if (pe.dt > recent) return;
+ fnmake_chanaddr(pe.id,c);
+
+ prioq_delmin(&pqchan[c]);
+ pass[c].mpos = 0;
+ pass[c].fd = open_read(fn.s);
+ if (pass[c].fd == -1) goto trouble;
+ if (!getinfo(&line,&birth,pe.id)) { close(pass[c].fd); goto trouble; }
+ pass[c].id = pe.id;
+ buffer_init(&pass[c].b,read,pass[c].fd,pass[c].buf,sizeof(pass[c].buf));
+ pass[c].j = job_open(pe.id,c);
+ jo[pass[c].j].retry = nextretry(birth,c);
+ jo[pass[c].j].flagdying = (recent > birth + lifetime);
+ while (!stralloc_copy(&jo[pass[c].j].sender,&line)) nomem();
+ }
+
+ if (!del_avail(c)) return;
+
+ if (getln(&pass[c].b,&line,&match,'\0') == -1) {
+ fnmake_chanaddr(pass[c].id,c);
+ log3s("warning: trouble reading ",fn.s,"; will try again later\n");
+ close(pass[c].fd);
+ job_close(pass[c].j);
+ pass[c].id = 0;
+ return;
+ }
+
+ if (!match) {
+ close(pass[c].fd);
+ jo[pass[c].j].flaghiteof = 1;
+ job_close(pass[c].j);
+ pass[c].id = 0;
+ return;
+ }
+
+ switch (line.s[0]) {
+ case 'T':
+ ++jo[pass[c].j].numtodo;
+ del_start(pass[c].j,pass[c].mpos,line.s + 1);
+ break;
+ case 'D':
+ break;
+ default:
+ fnmake_chanaddr(pass[c].id,c);
+ log3s("warning: unknown record type in ",fn.s,"!\n");
+ close(pass[c].fd);
+ job_close(pass[c].j);
+ pass[c].id = 0;
+ return;
+ }
+
+ pass[c].mpos += line.len;
+ return;
+
+ trouble:
+ log3s("warning: trouble opening ",fn.s,"; will try again later\n");
+ pe.dt = recent + SLEEP_SYSFAIL;
+ while (!prioq_insert(&pqchan[c],&pe)) nomem();
+}
+
+void messdone(unsigned long id)
+{
+ char ch;
+ int c;
+ struct prioq_elt pe;
+ struct stat st;
+
+ for (c = 0; c < CHANNELS; ++c) {
+ fnmake_chanaddr(id,c);
+ if (stat(fn.s,&st) == 0) return; /* false alarm; consequence of HOPEFULLY */
+ if (errno != ENOENT) {
+ log3s("warning: unable to stat ",fn.s,"; will try again later\n");
+ goto FAIL;
+ }
+ }
+
+ fnmake_todo(id);
+ if (stat(fn.s,&st) == 0) return;
+ if (errno != ENOENT) {
+ log3s("warning: unable to stat ",fn.s,"; will try again later\n");
+ goto FAIL;
+ }
+
+ fnmake_info(id);
+ if (stat(fn.s,&st) == -1) {
+ if (errno == ENOENT) return;
+ log3s("warning: unable to stat ",fn.s,"; will try again later\n");
+ goto FAIL;
+ }
+
+ /* -todo +info -local -remote ?bounce */
+ if (!injectbounce(id))
+ goto FAIL; /* injectbounce() produced error message */
+
+ strnum3[fmt_ulong(strnum3,id)] = 0;
+ log3s("end msg ",strnum3,"\n");
+
+ /* -todo +info -local -remote -bounce */
+ fnmake_info(id);
+ if (unlink(fn.s) == -1) {
+ log3s("warning: unable to unlink ",fn.s,"; will try again later\n");
+ goto FAIL;
+ }
+
+ /* -todo -info -local -remote -bounce; we can relax */
+ fnmake_foop(id);
+ if (buffer_putflush(&toqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (buffer_get(&fromqc,&ch,1) != 1) { cleandied(); return; }
+ if (ch != '+') log3s("warning: qmail-clean unable to clean up ",fn.s,"\n");
+
+ return;
+
+ FAIL:
+ pe.id = id; pe.dt = now() + SLEEP_SYSFAIL;
+ while (!prioq_insert(&pqdone,&pe)) nomem();
+}
+
+void pass_do()
+{
+ int c;
+ struct prioq_elt pe;
+
+ for (c = 0; c < CHANNELS; ++c)
+ pass_dochan(c);
+
+ if (prioq_min(&pqfail,&pe))
+ if (pe.dt <= recent) {
+ prioq_delmin(&pqfail);
+ pqadd(pe.id);
+ }
+
+ if (prioq_min(&pqdone,&pe))
+ if (pe.dt <= recent) {
+ prioq_delmin(&pqdone);
+ messdone(pe.id);
+ }
+}
+
+
+/* this file is too long ------------------------------------- EXTERNAL TODO */
+
+stralloc todoline = {0};
+char todobuf[2048];
+int todofdin;
+int todofdout;
+int flagtodoalive;
+
+void tododied() {
+ log1s("alert: lost connection to qmail-todo ... exiting\n");
+ flagexitasap = 1;
+ flagtodoalive = 0;
+}
+
+void todo_init()
+{
+ todofdout = 7;
+ todofdin = 8;
+ flagtodoalive = 1;
+ /* sync with external todo */
+ if (write(todofdout,"S",1) != 1) tododied();
+
+ return;
+}
+
+void todo_selprep(int *nfds,fd_set *rfds,datetime_sec *wakeup)
+{
+ if (flagexitasap) {
+ if (flagtodoalive) {
+ write(todofdout,"X",1);
+ }
+ }
+ if (flagtodoalive) {
+ FD_SET(todofdin,rfds);
+ if (*nfds <= todofdin)
+ *nfds = todofdin + 1;
+ }
+}
+
+void todo_del(char* s)
+{
+ int flagchan[CHANNELS];
+ struct prioq_elt pe;
+ unsigned long id;
+ unsigned int len;
+ int c;
+
+ for (c = 0; c < CHANNELS; ++c)
+ flagchan[c] = 0;
+
+ switch (*s++) {
+ case 'L':
+ flagchan[0] = 1;
+ break;
+ case 'R':
+ flagchan[1] = 1;
+ break;
+ case 'B':
+ flagchan[0] = 1;
+ flagchan[1] = 1;
+ break;
+ case 'X':
+ break;
+ default:
+ log1s("warning: qmail-send unable to understand qmail-todo\n");
+ return;
+ }
+
+ len = scan_ulong(s,&id);
+ if (!len || s[len]) {
+ log1s("warning: qmail-send unable to understand qmail-todo\n");
+ return;
+ }
+
+ pe.id = id; pe.dt = now();
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagchan[c])
+ while (!prioq_insert(&pqchan[c],&pe)) nomem();
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (flagchan[c]) break;
+
+ if (c == CHANNELS)
+ while (!prioq_insert(&pqdone,&pe)) nomem();
+
+ return;
+}
+
+void todo_do(fd_set *rfds)
+{
+ int r;
+ char ch;
+ int i;
+
+ if (!flagtodoalive) return;
+ if (!FD_ISSET(todofdin,rfds)) return;
+
+ r = read(todofdin,todobuf,sizeof(todobuf));
+ if (r == -1) return;
+ if (r == 0) {
+ if (flagexitasap)
+ flagtodoalive = 0;
+ else
+ tododied();
+ return;
+ }
+
+ for (i = 0; i < r; ++i) {
+ ch = todobuf[i];
+ while (!stralloc_append(&todoline,&ch)) nomem();
+ if (todoline.len > REPORTMAX)
+ todoline.len = REPORTMAX;
+ /* qmail-todo is responsible for keeping it short */
+ if (!ch && (todoline.len > 1)) {
+ switch (todoline.s[0]) {
+ case 'D':
+ if (flagexitasap) break;
+ todo_del(todoline.s + 1);
+ break;
+ case 'L':
+ log1s(todoline.s + 1);
+ break;
+ case 'X':
+ if (flagexitasap)
+ flagtodoalive = 0;
+ else
+ tododied();
+ break;
+ default:
+ log1s("warning: qmail-send unable to understand qmail-todo: report mangled\n");
+ break;
+ }
+ todoline.len = 0;
+ }
+ }
+}
+
+/* this file is too long ---------------------------------------------- MAIN */
+
+int getcontrols()
+{
+ if (control_init() == -1) return 0;
+ if (control_readint(&lifetime,"control/queuelifetime") == -1) return 0;
+ if (control_readint(&concurrency[0],"control/concurrencylocal") == -1) return 0;
+ if (control_readint(&concurrency[1],"control/concurrencyremote") == -1) return 0;
+ if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) return 0;
+ if (control_rldef(&bouncefrom,"control/bouncefrom",0,"MAILER-DAEMON") != 1) return 0;
+ if (control_rldef(&bouncehost,"control/bouncehost",1,"bouncehost") != 1) return 0;
+ if (control_readint(&bouncemaxbytes,"control/bouncemaxbytes") == -1) return 0;
+ if (control_rldef(&doublebouncehost,"control/doublebouncehost",1,"doublebouncehost") != 1) return 0;
+ if (control_rldef(&doublebounceto,"control/doublebounceto",0,"postmaster") != 1) return 0;
+ if (!stralloc_cats(&doublebounceto,"@")) return 0;
+ if (!stralloc_cat(&doublebounceto,&doublebouncehost)) return 0;
+ if (!stralloc_0(&doublebounceto)) return 0;
+ if (control_readfile(&locals,"control/locals",1) != 1) return 0;
+ if (!constmap_init(&maplocals,locals.s,locals.len,0)) return 0;
+ switch (control_readfile(&percenthack,"control/percenthack",0)) {
+ case -1: return 0;
+ case 0: if (!constmap_init(&mappercenthack,"",0,0)) return 0; break;
+ case 1: if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) return 0; break;
+ }
+ switch (control_readfile(&vdoms,"control/virtualdomains",0)) {
+ case -1: return 0;
+ case 0: if (!constmap_init(&mapvdoms,"",0,1)) return 0; break;
+ case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) return 0; break;
+ }
+ return 1;
+}
+
+stralloc newlocals = {0};
+stralloc newvdoms = {0};
+
+void regetcontrols()
+{
+ int r;
+
+ if (control_readfile(&newlocals,"control/locals",1) != 1) { log1s("alert: unable to reread control/locals\n"); return; }
+ if (control_readint(&concurrency[0],"control/concurrencylocal") == -1) { log1s("alert: unable to reread control/concurrencylocal\n"); return; }
+ if (control_readint(&concurrency[1],"control/concurrencyremote") == -1) { log1s("alert: unable to reread control/concurrencyremote\n"); return; }
+ if (control_readint(&lifetime,"control/queuelifetime") == -1) { log1s("alert: unable to reread control/queuelifetime\n"); return; }
+
+ r = control_readfile(&newvdoms,"control/virtualdomains",0);
+ if (r == -1) { log1s("alert: unable to reread control/virtualdomains\n"); return; }
+
+ constmap_free(&maplocals);
+ constmap_free(&mapvdoms);
+
+ while (!stralloc_copy(&locals,&newlocals)) nomem();
+ while (!constmap_init(&maplocals,locals.s,locals.len,0)) nomem();
+
+ if (r) {
+ while (!stralloc_copy(&vdoms,&newvdoms)) nomem();
+ while (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) nomem();
+ } else
+ while (!constmap_init(&mapvdoms,"",0,1)) nomem();
+}
+
+void reread()
+{
+ if (chdir(auto_qmail) == -1) {
+ log1s("alert: unable to reread controls: unable to switch to home directory\n");
+ return;
+ }
+ write(todofdout,"H",1);
+ regetcontrols();
+
+ while (chdir("queue") == -1) {
+ log1s("alert: unable to switch back to queue directory; HELP! sleeping...\n");
+ sleep(10);
+ }
+}
+
+int main()
+{
+ int fd;
+ datetime_sec wakeup;
+ fd_set rfds;
+ fd_set wfds;
+ int nfds;
+ struct timeval tv;
+ int c;
+ int u;
+ int r;
+ char ch;
+
+ if (chdir(auto_qmail) == -1) { log1s("alert: cannot start: unable to switch to home directory\n"); _exit(110); }
+ if (!getcontrols()) { log1s("alert: cannot start: unable to read controls\n"); _exit(111); }
+ if (chdir("queue") == -1) { log1s("alert: cannot start: unable to switch to queue directory\n"); _exit(110); }
+ sig_pipeignore();
+ sig_termcatch(sigterm);
+ sig_alarmcatch(sigalrm);
+ sig_hangupcatch(sighup);
+ sig_childdefault();
+ umask(077);
+
+ fd = open_write("lock/sendmutex");
+ if (fd == -1) { log1s("alert: cannot start: unable to open mutex\n"); _exit(111); }
+ if (lock_exnb(fd) == -1) { log1s("alert: cannot start: qmail-send is already running\n"); _exit(111); }
+
+ numjobs = 0;
+ for (c = 0;c < CHANNELS;++c) {
+ do
+ r = read(chanfdin[c],&ch,1);
+
+ while ((r == -1) && (errno == EINTR));
+ if (r < 1) { log1s("alert: cannot start: hath the daemon spawn no fire?\n"); _exit(111); }
+
+ u = (unsigned int) (unsigned char) ch;
+ if (concurrency[c] > u) concurrency[c] = u;
+ numjobs += concurrency[c];
+ }
+
+ fnmake_init();
+
+ comm_init();
+
+ pqstart();
+ job_init();
+ del_init();
+ pass_init();
+ todo_init();
+ cleanup_init();
+
+ while (!flagexitasap || !del_canexit() || flagtodoalive) {
+ recent = now();
+
+ if (flagrunasap) { flagrunasap = 0; pqrun(); }
+ if (flagreadasap) { flagreadasap = 0; reread(); }
+
+ wakeup = recent + SLEEP_FOREVER;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ nfds = 1;
+
+ comm_selprep(&nfds,&wfds);
+ del_selprep(&nfds,&rfds);
+ pass_selprep(&wakeup);
+ todo_selprep(&nfds,&rfds,&wakeup);
+ cleanup_selprep(&wakeup);
+
+ if (wakeup <= recent) tv.tv_sec = 0;
+ else tv.tv_sec = wakeup - recent + SLEEP_FUZZ;
+ tv.tv_usec = 0;
+
+ if (select(nfds,&rfds,&wfds,(fd_set *) 0,&tv) == -1)
+ if (errno == EINTR)
+ ;
+ else
+ log1s("warning: trouble in select\n");
+ else {
+ recent = now();
+
+ comm_do(&wfds);
+ del_do(&rfds);
+ todo_do(&rfds);
+ pass_do();
+ cleanup_do();
+ }
+ }
+
+ pqfinish();
+ log1s("status: exiting\n");
+ _exit(0);
+}
diff --git a/src/qmail-showctl.c b/src/qmail-showctl.c
new file mode 100644
index 0000000..2b4cc0a
--- /dev/null
+++ b/src/qmail-showctl.c
@@ -0,0 +1,372 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "spf.h"
+#include "buffer.h"
+#include "exit.h"
+#include "fmt.h"
+#include "str.h"
+#include "control.h"
+#include "constmap.h"
+#include "stralloc.h"
+#include "direntry.h"
+#include "auto_uids.h"
+#include "auto_qmail.h"
+#include "auto_break.h"
+#include "auto_patrn.h"
+#include "auto_spawn.h"
+#include "auto_split.h"
+
+stralloc me = {0};
+int meok;
+
+stralloc line = {0};
+char num[FMT_ULONG];
+
+void safeput(char *buf,unsigned int len)
+{
+ char ch;
+
+ while (len > 0) {
+ ch = *buf;
+ if ((ch < 32) || (ch > 126)) ch = '?';
+ buffer_put(buffer_1,&ch,1);
+ ++buf;
+ --len;
+ }
+}
+
+void do_int(char *fn,char *def,char *pre,char *post)
+{
+ int i;
+ buffer_puts(buffer_1,"\n");
+ buffer_puts(buffer_1,fn);
+ buffer_puts(buffer_1,": ");
+ switch (control_readint(&i,fn)) {
+ case 0:
+ buffer_puts(buffer_1,"(Default.) ");
+ buffer_puts(buffer_1,pre);
+ buffer_puts(buffer_1,def);
+ buffer_puts(buffer_1,post);
+ buffer_puts(buffer_1,".\n");
+ break;
+ case 1:
+ if (i < 0) i = 0;
+ buffer_puts(buffer_1,pre);
+ buffer_put(buffer_1,num,fmt_uint(num,i));
+ buffer_puts(buffer_1,post);
+ buffer_puts(buffer_1,".\n");
+ break;
+ default:
+ buffer_puts(buffer_1,"Oops! Trouble reading this file.\n");
+ break;
+ }
+}
+
+void do_str(char *fn,int flagme,char *def,char *pre)
+{
+ buffer_puts(buffer_1,"\n");
+ buffer_puts(buffer_1,fn);
+ buffer_puts(buffer_1,": ");
+ switch (control_readline(&line,fn)) {
+ case 0:
+ buffer_puts(buffer_1,"(Default.) ");
+ if (!stralloc_copys(&line,def)) {
+ buffer_puts(buffer_1,"Oops! Out of memory.\n");
+ break;
+ };
+ if (flagme && meok)
+ if (!stralloc_copy(&line,&me)) {
+ buffer_puts(buffer_1,"Oops! Out of memory.\n");
+ break;
+ };
+ case 1:
+ buffer_puts(buffer_1,pre);
+ safeput(line.s,line.len);
+ buffer_puts(buffer_1,".\n");
+ break;
+ default:
+ buffer_puts(buffer_1,"Oops! Trouble reading this file.\n");
+ break;
+ }
+}
+
+int do_lst(char *fn,char *def,char *pre,char *post)
+{
+ int i;
+ int j;
+
+ buffer_puts(buffer_1,"\n");
+ buffer_puts(buffer_1,fn);
+ buffer_puts(buffer_1,": ");
+ switch (control_readfile(&line,fn,0)) {
+ case 0:
+ buffer_puts(buffer_1,"(Default.) ");
+ buffer_puts(buffer_1,def);
+ buffer_puts(buffer_1,"\n");
+ return 0;
+ case 1:
+ buffer_puts(buffer_1,"\n");
+ i = 0;
+ for (j = 0; j < line.len; ++j)
+ if (!line.s[j]) {
+ buffer_puts(buffer_1,pre);
+ safeput(line.s + i,j - i);
+ buffer_puts(buffer_1,post);
+ buffer_puts(buffer_1,"\n");
+ i = j + 1;
+ }
+ return 1;
+ default:
+ buffer_puts(buffer_1,"Oops! Trouble reading this file.\n");
+ return -1;
+ }
+}
+
+int main()
+{
+ DIR *dir;
+ direntry *d;
+ struct stat stmrh;
+ struct stat stmrhcdb;
+
+ buffer_puts(buffer_1,"s/qmail home directory: ");
+ buffer_puts(buffer_1,auto_qmail);
+ buffer_puts(buffer_1,".\n");
+
+ buffer_puts(buffer_1,"user-ext delimiter: ");
+ buffer_puts(buffer_1,auto_break);
+ buffer_puts(buffer_1,".\n");
+
+ buffer_puts(buffer_1,"paternalism (in decimal): ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_patrn));
+ buffer_puts(buffer_1,".\n");
+
+ buffer_puts(buffer_1,"silent concurrency limit: ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_spawn));
+ buffer_puts(buffer_1,".\n");
+
+ buffer_puts(buffer_1,"subdirectory split: ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_split));
+ buffer_puts(buffer_1,".\n");
+
+ buffer_puts(buffer_1,"user ids: ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uida));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uidd));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uidl));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uido));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uidp));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uidq));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uidr));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_uids));
+ buffer_puts(buffer_1,".\n");
+
+ buffer_puts(buffer_1,"group ids: ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_gidn));
+ buffer_puts(buffer_1,", ");
+ buffer_put(buffer_1,num,fmt_ulong(num,(unsigned long) auto_gidq));
+ buffer_puts(buffer_1,".\n");
+
+ if (chdir(auto_qmail) == -1) {
+ buffer_puts(buffer_1,"Oops! Unable to chdir to ");
+ buffer_puts(buffer_1,auto_qmail);
+ buffer_puts(buffer_1,".\n");
+ buffer_flush(buffer_1);
+ _exit(110);
+ }
+ if (chdir("control") == -1) {
+ buffer_puts(buffer_1,"Oops! Unable to chdir to control.\n");
+ buffer_flush(buffer_1);
+ _exit(110);
+ }
+
+ dir = opendir(".");
+ if (!dir) {
+ buffer_puts(buffer_1,"Oops! Unable to open current directory.\n");
+ buffer_flush(buffer_1);
+ _exit(110);
+ }
+
+ meok = control_readline(&me,"me");
+ if (meok == -1) {
+ buffer_puts(buffer_1,"Oops! Trouble reading control/me.");
+ buffer_flush(buffer_1);
+ _exit(112);
+ }
+
+ do_lst("authsenders","No authenticated SMTP senders.","Authenticated SMTP senders: ","");
+ do_lst("badhelo","Any HELO/EHLO greeting is allowed.",""," not accepted in HELO/EHLO; exception token is '!'.");
+ do_lst("badmailfrom","Any MAIL FROM is allowed.",""," are rejected or treated special in MAIL FROM depending on tokens: '!', '?', '=', '~', '+'.");
+ do_lst("badloadertypes","Any loader types are accepted.",""," not accepted as loader type.");
+ /* XXX: check badloadertypes.cdb contents */
+ buffer_puts(buffer_1,"\nbadloadertypes.cdb: ");
+ if (stat("badloadertypes",&stmrh) == -1)
+ if (stat("badloadertypes.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"(Default.) No effect.\n");
+ else
+ buffer_puts(buffer_1,"Oops! badloadertypes.cdb exists but badloadertypes doesn't.\n");
+ else
+ if (stat("badloadertypes.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"Oops! badloadertypes exists but badloadertypes.cdb doesn't.\n");
+ else
+ if (stmrh.st_mtime > stmrhcdb.st_mtime)
+ buffer_puts(buffer_1,"Oops! badloadertypes.cdb is older than badloadertypes.\n");
+ else
+ buffer_puts(buffer_1,"Modified recently enough; hopefully up to date.\n");
+ do_lst("badmimetypes","Any MIME types are accepted.",""," not accepted as MIME type.");
+ /* XXX: check badmimetypes.cdb contents */
+ buffer_puts(buffer_1,"\nbadmimetypes.cdb: ");
+ if (stat("badmimetypes",&stmrh) == -1)
+ if (stat("badmimetypes.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"(Default.) No effect.\n");
+ else
+ buffer_puts(buffer_1,"Oops! badmimetypes.cdb exists but badmimetypes doesn't.\n");
+ else
+ if (stat("badmimetypes.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"Oops! badmimetypes exists but badmimetypes.cdb doesn't.\n");
+ else
+ if (stmrh.st_mtime > stmrhcdb.st_mtime)
+ buffer_puts(buffer_1,"Oops! badmimetypes.cdb is older than badmimetypes.\n");
+ else
+ buffer_puts(buffer_1,"Modified recently enough; hopefully up to date.\n");
+ do_lst("badrcptto","Any RCPT TO is allowed.",""," not accepted in RCPT TO.");
+ do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is ");
+ do_str("bouncehost",1,"bouncehost","Bounce host name is ");
+ do_int("bouncemaxbytes","0","Bounce size limit is "," bytes");
+ do_int("concurrencylocal","10","Local concurrency is ","");
+ do_int("concurrencyremote","20","Remote concurrency is ","");
+ do_int("databytes","0","SMTP DATA limit is "," bytes");
+ do_str("defaultdomain",1,"defaultdomain","Default domain name is ");
+ do_str("defaulthost",1,"defaulthost","Default host name is ");
+ do_lst("dkimdomains","No DKIM domains defined for signing.","DKIM domains: ","");
+ do_lst("domaincerts","No domain certs defined.","Domain certs: ","");
+ do_lst("domainips","No domain ip mappings defined.","Mappping sender domain part to local ip: ","");
+ do_str("doublebouncehost",1,"doublebouncehost","2B recipient host: ");
+ do_str("doublebounceto",0,"postmaster","2B recipient user: ");
+ do_str("envnoathost",1,"envnoathost","Presumed domain name is ");
+ do_str("helohost",1,"helohost","SMTP client HELO host name is ");
+ do_str("idhost",1,"idhost","Message-ID host name is ");
+ do_str("localiphost",1,"localiphost","Local IP address becomes ");
+ do_lst("locals","Messages for me are delivered locally.","Messages for "," are delivered locally.");
+ do_str("me",0,"undefined! Uh-oh","My name is ");
+
+ do_lst("mailfromrules","Any envelope sender are accepted.",""," (MAV rule).");
+ /* XXX: check mailfromrules.cdb contents */
+ buffer_puts(buffer_1,"\nmailfromrules.cdb: ");
+ if (stat("mailfromrules",&stmrh) == -1)
+ if (stat("mailfromrules.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"(Default.) No effect.\n");
+ else
+ buffer_puts(buffer_1,"Oops! mailfromrules.cdb exists but mailfromrules doesn't.\n");
+ else
+ if (stat("mailfromrules.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"Oops! mailfromrules exists but mailfromrules.cdb doesn't.\n");
+ else
+ if (stmrh.st_mtime > stmrhcdb.st_mtime)
+ buffer_puts(buffer_1,"Oops! mailfromrules.cdb is older than mailfromrules.\n");
+ else
+ buffer_puts(buffer_1,"Modified recently enough; hopefully up to date.\n");
+
+ do_lst("percenthack","The percent hack is not allowed.","The percent hack is allowed for user%host@",".");
+ do_str("plusdomain",1,"plusdomain","Plus domain name is ");
+ do_lst("qmqpservers","No QMQP servers.","QMQP server: ",".");
+ do_int("queuelifetime","604800","Message lifetime in the queue is "," seconds");
+
+ if (do_lst("rcpthosts","SMTP clients may send messages to any recipient.","SMTP clients may send messages to recipients at ","."))
+ do_lst("morercpthosts","No effect.","SMTP clients may send messages to recipients at ",".");
+ else
+ do_lst("morercpthosts","No rcpthosts; morercpthosts is irrelevant.","No rcpthosts; doesn't matter that morercpthosts has ",".");
+ /* XXX: check morercpthosts.cdb contents */
+ buffer_puts(buffer_1,"\nmorercpthosts.cdb: ");
+ if (stat("morercpthosts",&stmrh) == -1)
+ if (stat("morercpthosts.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"(Default.) No effect.\n");
+ else
+ buffer_puts(buffer_1,"Oops! morercpthosts.cdb exists but morercpthosts doesn't.\n");
+ else
+ if (stat("morercpthosts.cdb",&stmrhcdb) == -1)
+ buffer_puts(buffer_1,"Oops! morercpthosts exists but morercpthosts.cdb doesn't.\n");
+ else
+ if (stmrh.st_mtime > stmrhcdb.st_mtime)
+ buffer_puts(buffer_1,"Oops! morercpthosts.cdb is older than morercpthosts.\n");
+ else
+ buffer_puts(buffer_1,"Modified recently enough; hopefully up to date.\n");
+ do_lst("recipients","SMTP clients may send messages to any recipient.","SMTP clients may send messages to local recipients listed in ",".");
+ do_str("smtpgreeting",1,"smtpgreeting","SMTP greeting: 220 ");
+ do_lst("qmtproutes","No additional QMTP routes.","QMTP route: ","");
+ do_lst("smtproutes","No artificial SMTP routes.","SMTP route: ","");
+ do_str("spfexplain",0,SPF_DEFEXP,"SPF default explanation is: 550 ");
+ do_str("spflocalrules",0,"(None)","Defined local SPF rules are: ");
+ do_lst("srsrdomains","No SRS fowarding rules.","SRS rules: ","");
+ do_int("timeoutconnect","60","SMTP client connection timeout is "," seconds");
+ do_int("timeoutremote","1200","SMTP client data timeout is "," seconds");
+ do_int("timeoutsmtpd","1200","SMTP server data timeout is "," seconds");
+ do_lst("tlsdestinations","No TLS destinations defined.","TLS destination: ","");
+ do_lst("virtualdomains","No virtual domains.","Virtual domain: ","");
+
+ while ((d = readdir(dir))) {
+ if (str_equal(d->d_name,".")) continue;
+ if (str_equal(d->d_name,"..")) continue;
+ if (str_equal(d->d_name,"authsenders")) continue;
+ if (str_equal(d->d_name,"badhelo")) continue;
+ if (str_equal(d->d_name,"badrcptto")) continue;
+ if (str_equal(d->d_name,"badmailfrom")) continue;
+ if (str_equal(d->d_name,"badloadertypes")) continue;
+ if (str_equal(d->d_name,"badloadertypes.cdb")) continue;
+ if (str_equal(d->d_name,"badmimetypes")) continue;
+ if (str_equal(d->d_name,"badmimetypes.cdb")) continue;
+ if (str_equal(d->d_name,"bouncefrom")) continue;
+ if (str_equal(d->d_name,"bouncehost")) continue;
+ if (str_equal(d->d_name,"bouncemaxbytes")) continue;
+ if (str_equal(d->d_name,"concurrencylocal")) continue;
+ if (str_equal(d->d_name,"concurrencyremote")) continue;
+ if (str_equal(d->d_name,"databytes")) continue;
+ if (str_equal(d->d_name,"defaultdomain")) continue;
+ if (str_equal(d->d_name,"defaulthost")) continue;
+ if (str_equal(d->d_name,"dkimdomains")) continue;
+ if (str_equal(d->d_name,"domainips")) continue;
+ if (str_equal(d->d_name,"domaincerts")) continue;
+ if (str_equal(d->d_name,"doublebouncehost")) continue;
+ if (str_equal(d->d_name,"doublebounceto")) continue;
+ if (str_equal(d->d_name,"envnoathost")) continue;
+ if (str_equal(d->d_name,"helohost")) continue;
+ if (str_equal(d->d_name,"idhost")) continue;
+ if (str_equal(d->d_name,"localiphost")) continue;
+ if (str_equal(d->d_name,"locals")) continue;
+ if (str_equal(d->d_name,"me")) continue;
+ if (str_equal(d->d_name,"mailfromrules")) continue;
+ if (str_equal(d->d_name,"mailfromrules.cdb")) continue;
+ if (str_equal(d->d_name,"morercpthosts")) continue;
+ if (str_equal(d->d_name,"morercpthosts.cdb")) continue;
+ if (str_equal(d->d_name,"percenthack")) continue;
+ if (str_equal(d->d_name,"plusdomain")) continue;
+ if (str_equal(d->d_name,"qmqpservers")) continue;
+ if (str_equal(d->d_name,"queuelifetime")) continue;
+ if (str_equal(d->d_name,"rcpthosts")) continue;
+ if (str_equal(d->d_name,"recipients")) continue;
+ if (str_equal(d->d_name,"smtpgreeting")) continue;
+ if (str_equal(d->d_name,"qmtproutes")) continue;
+ if (str_equal(d->d_name,"smtproutes")) continue;
+ if (str_equal(d->d_name,"spfexplain")) continue;
+ if (str_equal(d->d_name,"spflocalrules")) continue;
+ if (str_equal(d->d_name,"srsdomains")) continue;
+ if (str_equal(d->d_name,"timeoutconnect")) continue;
+ if (str_equal(d->d_name,"timeoutremote")) continue;
+ if (str_equal(d->d_name,"timeoutsmtpd")) continue;
+ if (str_equal(d->d_name,"tlsdestinations")) continue;
+ if (str_equal(d->d_name,"virtualdomains")) continue;
+ buffer_puts(buffer_1,"\n");
+ buffer_puts(buffer_1,d->d_name);
+ buffer_puts(buffer_1,": I have no idea what this file does.\n");
+ }
+
+ buffer_flush(buffer_1);
+ _exit(0);
+}
diff --git a/src/qmail-smtpam.c b/src/qmail-smtpam.c
new file mode 100644
index 0000000..c0c6550
--- /dev/null
+++ b/src/qmail-smtpam.c
@@ -0,0 +1,631 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include "sig.h"
+#include "genalloc.h"
+#include "stralloc.h"
+#include "buffer.h"
+#include "scan.h"
+#include "case.h"
+#include "byte.h"
+#include "error.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "dns.h"
+#include "alloc.h"
+#include "quote.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "ipme.h"
+#include "str.h"
+#include "now.h"
+#include "exit.h"
+#include "constmap.h"
+#include "tcpto.h"
+#include "socket_if.h"
+#include "ucspissl.h"
+#include "timeout.h"
+#include "timeoutconn.h"
+#include "tls_remote.h"
+#include "tls_errors.h"
+#include "tls_timeoutio.h"
+#include "uint_t.h"
+
+#define MAX_SIZE 200000000
+#define HUGESMTPTEXT 5000
+#define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */
+#define PORT_SMTPS 465
+#define VERIFYDEPTH 1
+#define FDPAM 3
+
+#define WHO "qmail-smtpam"
+
+/** @file qmail-smtpam.c -- TLS enabled SMTP PAM to check mailbox at remote MX
+ */
+
+int flagauth = 0; /* 1 = login; 2 = plain; 3 =crammd5 */
+int flagsmtps = 0; /* RFC 8314 - 'implicit TLS' */
+int flagtls = 0; /* -2 = rejected; -1 = not; 0 = no, default;
+ > 0 see tls_remote.c
+ +10 = SMTPS; +20 = QMTPS; 100 = active TLS connection */
+int flagverify = 0; /* 1 = verify Cert against CA ; -1 = Cert pinning */
+int flagutf8mail = 0;
+
+unsigned long port = PORT_SMTP;
+
+GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
+GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
+
+stralloc helohost = {0};
+stralloc host = {0};
+stralloc ports = {0};
+stralloc remotehost = {0};
+stralloc sender = {0};
+stralloc canonhost = {0};
+stralloc canonbox = {0};
+stralloc sendip = {0};
+stralloc recipient = {0};
+
+stralloc domainips = {0};
+struct constmap mapdomainips;
+char ip4[4];
+char ip6[16];
+uint32 ifidx = 0;
+
+stralloc routes = {0};
+struct constmap maproutes;
+
+struct ip_mx partner;
+
+SSL *ssl;
+SSL_CTX *ctx;
+
+void out(char *s) { if (buffer_puts(buffer_1small,s) == -1) _exit(111); }
+void zero() { if (buffer_put(buffer_1small,"\0",1) == -1) _exit(111); }
+void zerodie() { zero(); buffer_flush(buffer_1small); _exit(111); }
+void outsafe(stralloc *sa)
+{
+ int i;
+ char ch;
+ for (i = 0; i < sa->len; ++i) {
+ ch = sa->s[i];
+ if (ch < 33) ch = '?';
+ if (ch > 126) ch = '?';
+ if (buffer_put(buffer_1small,&ch,1) == -1) _exit(111);
+ }
+}
+
+void temp_noip()
+{
+ out("Zinvalid ipaddr in control/domainips (#4.3.0)\n");
+ zerodie();
+}
+void temp_nomem()
+{
+ out("ZOut of memory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_oserr()
+{
+ out("ZSystem resources temporarily unavailable. (#4.3.0)\n");
+ zerodie();
+}
+void temp_osip()
+{
+ out("ZCan't bind to local ip address: ");
+ outsafe(&sendip);
+ out(". (#4.3.0)\n");
+ zerodie();
+}
+void temp_noconn()
+{
+ out("ZSorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n");
+ zerodie();
+}
+void temp_dnscanon()
+{
+ out("ZCNAME lookup failed temporarily for: ");
+ outsafe(&canonhost);
+ out(". (#4.4.3)\n");
+ zerodie();
+}
+void temp_dns()
+{
+ out("ZSorry, I couldn't find any host named: ");
+ outsafe(&host);
+ out(". (#4.1.2)\n");
+ zerodie();
+}
+void temp_chdir()
+{
+ out("ZUnable to switch to home directory. (#4.3.0)\n");
+ zerodie();
+}
+void temp_control()
+{
+ out("ZUnable to read control files. (#4.3.0)\n");
+ zerodie();
+}
+void perm_usage()
+{
+ out("Dqmail-smtpam was invoked improperly. (#5.3.5)\n");
+ zerodie();
+}
+void perm_dns()
+{
+ out("DSorry, I couldn't find any host named: ");
+ outsafe(&host);
+ out(". (#5.1.2)\n");
+ zerodie();
+}
+void outhost()
+{
+ char ipaddr[IPFMT];
+ int len;
+
+ switch (partner.af) {
+ case AF_INET:
+ len = ip4_fmt(ipaddr,(char *)&partner.addr.ip4.d); break;
+ case AF_INET6:
+ len = ip6_fmt(ipaddr,(char *)&partner.addr.ip6.d); break;
+ }
+ if (buffer_put(buffer_1small,ipaddr,len) == -1) _exit(0);
+}
+
+int flagcritical = 0;
+
+void dropped()
+{
+ out("ZConnected to ");
+ outhost();
+ out(" but connection died. ");
+ if (flagcritical) out("Possible duplicate! ");
+ out("(#4.4.2)\n");
+ zerodie();
+}
+
+int timeoutconnect = 60;
+int smtpfd;
+int timeout = 1200;
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutread(timeout,smtpfd,buf,len);
+ if (r <= 0) dropped();
+ return r;
+}
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutwrite(timeout,smtpfd,buf,len);
+ if (r <= 0) dropped();
+ return r;
+}
+
+char outbuf[1450];
+buffer bo = BUFFER_INIT(safewrite,-1,outbuf,sizeof(outbuf));
+char frombuf[128];
+buffer bi = BUFFER_INIT(saferead,-1,frombuf,sizeof(frombuf));
+
+stralloc smtptext = {0};
+
+void get(char *ch)
+{
+ buffer_get(&bi,ch,1);
+ if (*ch != '\r')
+ if (smtptext.len < HUGESMTPTEXT)
+ if (!stralloc_append(&smtptext,ch)) temp_nomem();
+}
+
+unsigned long smtpcode()
+{
+ unsigned char ch;
+ unsigned long code;
+
+ if (!stralloc_copys(&smtptext,"")) temp_nomem();
+
+ get(&ch); code = ch - '0';
+ get(&ch); code = code * 10 + (ch - '0');
+ get(&ch); code = code * 10 + (ch - '0');
+ for (;;) {
+ get(&ch);
+ if (ch != '-') break;
+ while (ch != '\n') get(&ch);
+ get(&ch);
+ get(&ch);
+ get(&ch);
+ }
+ while (ch != '\n') get(&ch);
+
+ return code;
+}
+
+void outsmtptext()
+{
+ int i;
+ if (smtptext.s) if (smtptext.len) {
+ out("Remote host said: ");
+ for (i = 0; i < smtptext.len; ++i)
+ if (!smtptext.s[i]) smtptext.s[i] = '?';
+ if (buffer_put(buffer_1small,smtptext.s,smtptext.len) == -1) _exit(111);
+ smtptext.len = 0;
+ }
+}
+
+void quit(char *prepend,char *append)
+{
+ buffer_putsflush(&bo,"QUIT\r\n");
+ /* waiting for remote side is just too ridiculous */
+ out(prepend);
+ outhost();
+ out(append);
+ out(".\n");
+ outsmtptext();
+ zerodie();
+}
+
+stralloc recip = {0};
+
+/* this file is too long -------------------------------------- client TLS */
+
+stralloc cafile = {0};
+stralloc cadir = {0};
+stralloc certfile = {0};
+stralloc keyfile = {0};
+stralloc keypwd = {0};
+stralloc ciphers = {0};
+stralloc tlsdest = {0};
+
+char *tlsdestinfo = 0;
+char *tlsdomaininfo = 0;
+
+stralloc domaincerts = {0};
+struct constmap mapdomaincerts;
+stralloc tlsdestinations = {0};
+struct constmap maptlsdestinations;
+unsigned long verifydepth = VERIFYDEPTH;
+
+void tls_init()
+{
+/* Client CTX */
+
+ ctx = ssl_client();
+ ssl_errstr();
+ if (!ctx) temp_tlsctx();
+
+/* Fetch CA infos for dest */
+
+ if (flagverify > 0)
+ if (cafile.len || cadir.len)
+ if (!ssl_ca(ctx,cafile.s,cadir.s,(int) verifydepth)) temp_tlsca();
+
+ if (ciphers.len)
+ if (!ssl_ciphers(ctx,ciphers.s)) temp_tlscipher();
+
+/* Set SSL Context */
+
+ ssl = ssl_new(ctx,smtpfd);
+ if (!ssl) temp_tlsctx();
+
+/* Setup SSL FDs */
+
+ if (!tls_conn(ssl,smtpfd)) temp_tlscon();
+
+/* Go on in none-blocking mode */
+
+ if (tls_timeoutconn(timeout,smtpfd,smtpfd,ssl) <= 0)
+ temp_tlserr();
+}
+
+int starttls_peer()
+{
+ int i = 0;
+
+ while ( (i += str_chr(smtptext.s + i,'\n') + 1) &&
+ (i < smtptext.len - 8) ) {
+ if (!str_diffn(smtptext.s + i + 4,"STARTTLS",8)) return 1; }
+
+ return 0;
+}
+
+void tls_peercheck()
+{
+ X509 *cert;
+
+ cert = SSL_get_peer_certificate(ssl);
+ if (!cert) { flagtls = 100; return; }
+
+ if (flagverify < 0) {
+ if (cafile.len) case_lowerb(cafile.s,cafile.len);
+ switch (tls_fingerprint(cert,cafile.s + 1,cafile.len - 1)) {
+ case -1: temp_tlspeercert();
+ case -2: temp_tlsdigest();
+ case -3: temp_invaliddigest();
+ case 1: temp_tlscertfp();
+ }
+ } else {
+ switch (tls_checkpeer(ssl,cert,remotehost,flagtls,flagverify)) {
+ case -1: temp_tlspeercert();
+ case -2: temp_tlspeerverify();
+ case -3: temp_tlspeervalid();
+ case 1: flagtls = 101; break;
+ case 2: flagtls = 102; break;
+ case 3: flagtls = 103; break;
+ }
+ }
+
+ if (flagtls < 100) flagtls = 100;
+
+ X509_free(cert);
+
+ return;
+}
+
+int utf8flag(unsigned char *ch,int len)
+{
+ int i = 0;
+ while (i < len)
+ if (ch[i++] > 127) return 1;
+ return 0;
+}
+
+/* this file is too long -------------------------------------- SMTP connection */
+
+unsigned long code;
+
+void smtp_greeting()
+{
+ buffer_puts(&bo,"EHLO ");
+ buffer_put(&bo,helohost.s,helohost.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+
+ if (smtpcode() != 250) {
+ buffer_puts(&bo,"HELO ");
+ buffer_put(&bo,helohost.s,helohost.len);
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+ code = smtpcode();
+ if (code >= 500) quit("DConnected to"," but my name was rejected");
+ if (code != 250) quit("ZConnected to"," but my name was rejected");
+ }
+}
+
+void smtp_starttls()
+{
+ buffer_puts(&bo,"STARTTLS\r\n");
+ buffer_flush(&bo);
+ if (smtpcode() == 220) {
+ tls_init();
+ tls_peercheck();
+ smtp_greeting();
+ } else {
+ flagtls = -2;
+ quit("ZConnected to"," but STARTTLS was rejected");
+ }
+}
+
+void smtp()
+{
+
+ if (flagtls > 10 && flagtls < 20) { /* SMTPS */
+ tls_init();
+ tls_peercheck();
+ }
+
+ code = smtpcode();
+ if (code >= 500) quit("DConnected to "," but sender was rejected");
+ if (code >= 400) quit("ZConnected to "," but sender was probably greylisted");
+
+ smtp_greeting();
+
+ if (flagutf8mail) buffer_puts(&bo," SMTPUTF8");
+
+ if (flagtls > 0 && flagtls < 10) /* STARTTLS */
+ if (starttls_peer()) {
+ smtp_starttls();
+ } else if (flagtls > 2) {
+ temp_tlshost();
+ }
+
+ buffer_puts(&bo,"MAIL FROM:<>");
+ if (flagutf8mail)
+ buffer_puts(&bo," SMTPUTF8");
+ buffer_puts(&bo,"\r\n");
+ buffer_flush(&bo);
+ code = smtpcode();
+ if (code >= 500) quit("DConnected to "," but sender was rejected");
+ if (code >= 400) quit("ZConnected to "," but sender was rejected");
+
+ buffer_puts(&bo,"RCPT TO:<");
+ buffer_put(&bo,recipient.s,recipient.len);
+ buffer_puts(&bo,">\r\n");
+ buffer_flush(&bo);
+ code = smtpcode();
+ close(smtpfd);
+ if (code == 250) _exit(0);
+ _exit(1);
+}
+
+void getcontrols()
+{
+ if (control_init() == -1) temp_control();
+ if (control_readint(&timeout,"control/timeoutremote") == -1) temp_control();
+ if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1)
+ temp_control();
+ if (control_rldef(&helohost,"control/helohost",1,(char *) 0) != 1)
+ temp_control();
+ switch (control_readfile(&domainips,"control/domainips",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&mapdomainips,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&mapdomainips,domainips.s,domainips.len,1)) temp_nomem(); break;
+ }
+ switch (control_readfile(&tlsdestinations,"control/tlsdestinations",0)) {
+ case -1: temp_control();
+ case 0: if (!constmap_init(&maptlsdestinations,"",0,1)) temp_nomem(); break;
+ case 1: if (!constmap_init(&maptlsdestinations,tlsdestinations.s,tlsdestinations.len,1)) temp_nomem(); break;
+ }
+
+}
+
+char up[513];
+int uplen;
+
+int main(int argc,char **argv)
+{
+ static ipalloc ip = {0};
+ stralloc netif = {0};
+ int i, j, k;
+ int r; /* reserved for return code */
+ int p; /* reserved for port */
+ char *localip = 0;
+ char *tlsdestinfo = 0;
+
+ sig_pipeignore();
+ if (argc < 2) perm_usage();
+ if (chdir(auto_qmail) == -1) temp_chdir();
+ getcontrols();
+
+ if (!stralloc_copys(&host,argv[1])) temp_nomem();
+
+ if (argv[2]) {
+ if (!stralloc_copys(&ports,argv[2])) temp_nomem();
+ if (*ports.s == 's') { ports.s++; flagsmtps = 1; }
+ scan_ulong(ports.s,&port);
+ }
+
+ if (ipme_init() != 1) temp_oserr();
+
+/* this file is too long -------------------------------------- set domain ip + helohost */
+
+ if (!localip)
+ localip = constmap(&mapdomainips,"*",1); /* one for all */
+
+ if (localip) {
+ j = str_chr(localip,'%');
+ if (localip[j] != '%') j = 0;
+ k = str_chr(localip,'|');
+ if (localip[k] != '|') k = 0;
+ if (k) { /* helohost */
+ if (!stralloc_copys(&helohost,localip + k + 1)) temp_nomem();
+ localip[k] = 0;
+ }
+ if (j) { /* if index */
+ localip[j] = 0;
+ if (!stralloc_copys(&netif,localip + j + 1)) temp_nomem();
+ if (!stralloc_0(&netif)) temp_nomem();
+ }
+ }
+
+
+/* this file is too long -------------------------------------- TLS destinations */
+
+ flagtls = tls_destination((const stralloc) host); // un-terminated
+
+ if (flagtls > 0) {
+ if (tlsdestinfo) {
+ i = str_chr(tlsdestinfo,'|'); /* ca file or cert fingerprint */
+ if (tlsdestinfo[i] == '|') {
+ tlsdestinfo[i] = 0;
+ j = str_chr(tlsdestinfo+i+1,'|'); /* cipher */
+ if (tlsdestinfo[i + j + 1] == '|') {
+ tlsdestinfo[i + j + 1] = 0;
+ k = str_chr(tlsdestinfo + i + j + 2,'|'); /* cone domain */
+ if (tlsdestinfo[i + j + k + 2] == '|') {
+ tlsdestinfo[i + j + k + 2] = 0;
+ if (str_diffn(tlsdestinfo + j + k + 3,canonhost.s,canonhost.len)) flagtls = 0;
+ }
+ p = str_chr(tlsdestinfo + i + j + 2,';'); /* verifydepth;port */
+ if (tlsdestinfo[i + j + p + 2] == ';') {
+ if (tlsdestinfo[i + j + p + 3] == 's') { flagsmtps = 1; p++; }
+ tlsdestinfo[i + j + p + 2] = 0;
+ if (p > 0) scan_ulong(tlsdestinfo+i+j + 2,&verifydepth);
+ scan_ulong(tlsdestinfo+i+j + p + 3,&port);
+ }
+ }
+ if (!stralloc_copys(&ciphers,tlsdestinfo + i + 1)) temp_nomem();
+ }
+ if (!stralloc_copys(&cafile,tlsdestinfo)) temp_nomem();
+ }
+
+/* cafile starts with '=' => it is a fingerprint
+ cafile ends with '/' => consider it as cadir */
+
+ if (cafile.len) {
+ flagverify = 1;
+ if (cafile.s[cafile.len] == '/') {
+ cafile.len = 0;
+ flagverify = 2;
+ if (!stralloc_copys(&cadir,tlsdestinfo)) temp_nomem();
+ if (!stralloc_0(&cadir)) temp_nomem();
+ } else {
+ if (cafile.s[0] == '%') flagverify = -1;
+ if (!stralloc_0(&cafile)) temp_nomem();
+ }
+ } else {
+ cafile.len = cadir.len = ciphers.len = p = 0;
+ }
+
+ if (port == PORT_SMTPS || flagsmtps) flagtls = flagtls + 10;
+ }
+
+/* this file is too long -------------------------------------- Setup connection */
+
+ uplen = 0;
+ for (;;) {
+ do
+ r = read(FDPAM,up + uplen,sizeof(up) - uplen);
+ while ((r == -1) && (errno == EINTR));
+ if (r == -1) _exit(111);
+ if (r == 0) break;
+ uplen += r;
+ if (uplen >= sizeof(up)) _exit(111);
+ }
+ close(FDPAM);
+
+ if (!stralloc_copyb(&recipient,up,uplen)) temp_nomem();
+ if (!stralloc_0(&recipient)) temp_nomem();
+ if (!stralloc_0(&host)) temp_nomem();
+ if (!stralloc_copys(&remotehost,host.s)) temp_nomem();
+
+ flagutf8mail = utf8flag(recipient.s,recipient.len);
+
+ switch (dns_ip(&ip,&remotehost)) {
+ case DNS_MEM: temp_nomem();
+ case DNS_ERR: temp_dns();
+ case DNS_COM: temp_dnscanon();
+ default: if (ip.len <= 0) perm_dns();
+ }
+
+ smtpfd = socket(ip.ix[i].af,SOCK_STREAM,0);
+ if (smtpfd == -1) temp_oserr();
+
+ if (localip) { /* set domain ip */
+ if (!stralloc_copyb(&sendip,localip,str_len(localip))) temp_nomem();
+ j = str_chr(localip,':');
+ if (j && localip[j] == ':') { /* IPv6 */
+ if (!ip6_scan(localip,ip6)) temp_noip();
+ ifidx = socket_getifidx(netif.s);
+ if (socket_bind6(smtpfd,ip6,0,ifidx) < 0) temp_osip();
+ } else { /* IPv4 */
+ if (!ip4_scan(localip,ip4)) temp_noip();
+ if (socket_bind4(smtpfd,ip4,0) < 0) temp_osip();
+ }
+ }
+
+ r = timeoutconn(smtpfd,&ip.ix[i].addr,(unsigned int) port,timeoutconnect,ifidx);
+ if (r == 0) {
+ tcpto_err(&ip.ix[i],0);
+ partner = ip.ix[i];
+ smtp(); /* does not return */
+ }
+ tcpto_err(&ip.ix[i],errno == ETIMEDOUT);
+ close(smtpfd);
+
+ temp_noconn();
+}
diff --git a/src/qmail-smtpd.c b/src/qmail-smtpd.c
new file mode 100644
index 0000000..f312c11
--- /dev/null
+++ b/src/qmail-smtpd.c
@@ -0,0 +1,1715 @@
+#include <unistd.h>
+#include "wildmat.h"
+#include "buffer.h"
+#include "stralloc.h"
+#include "genalloc.h"
+#include "alloc.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "received.h"
+#include "constmap.h"
+#include "logmsg.h"
+#include "ipme.h"
+#include "fd.h"
+#include "ip.h"
+#include "qmail.h"
+#include "str.h"
+#include "fmt.h"
+#include "scan.h"
+#include "byte.h"
+#include "case.h"
+#include "env.h"
+#include "now.h"
+#include "exit.h"
+#include "rcpthosts.h"
+#include "recipients.h"
+#include "mfrules.h"
+#include "tls_start.h"
+#include "smtpdlog.h"
+#include "timeout.h"
+#include "commands.h"
+#include "cdbread.h"
+#include "dns.h"
+#include "wait.h"
+#include "sig.h"
+#include "close.h"
+#include "open.h"
+#include "base64.h"
+#include "spf.h"
+
+/** @file qmail-smtpd.c -- authenticating ESMTP/ESMTPS server
+ @brief requires sslserver or tcpserver */
+
+#define PAM111421
+#define AUTHSLEEP 5
+#define PORT_SMTPS "465"
+
+#define MIMETYPE_LEN 9
+#define LOADER_LEN 5
+#define BASE64MESSAGE "content-transfer-encoding: base64"
+#define FDIN 0
+#define FDOUT 1
+#define FDLOG 2
+#define FDAUTH 3
+
+#define BUFFER_SIZE 1024
+#define MAXHOPS 100
+unsigned long databytes = 0;
+int timeout = 1200;
+
+int modssl_info();
+
+ssize_t safewrite(int fd,char *buf,int len)
+{
+ int r;
+ r = timeoutwrite(timeout,fd,buf,len);
+ if (r <= 0) _exit(1);
+ return r;
+}
+
+ssize_t saferead(int fd,char *buf,int len)
+{
+ int r;
+ flush();
+ r = timeoutread(timeout,fd,buf,len);
+ if (r == -1) if (errno == ETIMEDOUT) die_alarm();
+ if (r <= 0) die_read();
+ return r;
+}
+
+char inbuf[BUFFER_SIZE];
+buffer bi = BUFFER_INIT(saferead,FDIN,inbuf,sizeof(inbuf));
+
+char outbuf[BUFFER_SIZE/2];
+buffer bo = BUFFER_INIT(safewrite,FDOUT,outbuf,sizeof(outbuf));
+
+char logbuf[256];
+buffer bl = BUFFER_INIT(write,FDLOG,logbuf,sizeof(logbuf));
+
+void flush() { buffer_flush(&bo); } // this triggers writing to STDIO
+void out(char *s) { buffer_puts(&bo,s); }
+
+stralloc sa = {0};
+ipalloc ia = {0};
+
+int bhelocheck(void);
+
+/* this file is too long -------------------------------------- DNS helper */
+
+int dnsq(char *arg,char type)
+{
+ unsigned int random;
+ int at;
+ int r = -2;
+ int len;
+
+ len = str_len(arg);
+ if (len < 1) return r;
+
+ if (arg[len-1] == ' ') len--; /* trailing blank */
+ if (len < 1) return r;
+
+ at = byte_rchr(arg,len,'@');
+ if (at < len) {
+ if (!stralloc_copyb(&sa,arg + at + 1,len - at - 1)) die_nomem();
+ } else
+ if (!stralloc_copyb(&sa,arg,len)) die_nomem();
+
+ random = now() + (getpid() << 16);
+
+ switch (type) { /* Common for IPv4 and IPv6 */
+ case 'A': r = dns_ip(&ia,&sa); break;
+ case 'M': r = dns_mxip(&ia,&sa,random); break;
+ }
+
+ switch (r) {
+ case DNS_ERR: out("451 DNS temporary failure (#4.3.0)\r\n"); return -1;
+ case DNS_MEM: die_nomem();
+ default: if (ia.len) return 0;
+ }
+
+ return 1;
+}
+
+/* this file is too long -------------------------------------- Greeting */
+
+static stralloc greeting = {0};
+
+void smtp_greet(char *code)
+{
+ buffer_puts(&bo,code);
+ buffer_put(&bo,greeting.s,greeting.len);
+}
+void smtp_help()
+{
+ out("214 s/qmail home page: https://www.fehcom.de/sqmail.html\r\n");
+}
+void smtp_quit()
+{
+ smtp_greet("221 "); out("\r\n"); flush(); _exit(0);
+}
+
+char *remoteip;
+char *remotehost;
+char *remoteinfo;
+char *local;
+char *localport;
+char *relayclient;
+int flagutf8 = 0;
+
+stralloc protocol = {0};
+stralloc helohost = {0};
+char *fakehelo; /* pointer into helohost, or 0 */
+stralloc tlsinfo = {0};
+
+char *helocheck;
+int flagbadhelo;
+int flagdnshelo;
+int seenhelo = 0;
+
+char *badmailcond;
+char *badhelocond;
+
+void dohelo(char *helo)
+{
+ if (!stralloc_copys(&helohost,helo)) die_nomem();
+ if (!stralloc_0(&helohost)) die_nomem();
+ fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;
+ if (helocheck) {
+ if (str_len(helocheck) == 1) {
+ switch (*helocheck) {
+ case '=': flagbadhelo = bhelocheck();
+ if (fakehelo) { flagdnshelo = 1; badhelocond = "="; }
+ break;
+ case 'A': flagbadhelo = bhelocheck();
+ if (flagbadhelo == 0) { flagdnshelo = dnsq(helohost.s,'A'); badhelocond = "A"; }
+ break;
+ case 'M': flagbadhelo = bhelocheck();
+ if (flagbadhelo == 0) { flagdnshelo = dnsq(helohost.s,'M'); badhelocond = "M"; }
+ break;
+ case '.': flagbadhelo = bhelocheck();
+ if (!str_len(helo)) flagbadhelo = -2;
+ break;
+ case '!': if (!str_len(helo)) flagbadhelo = -2;
+ break;
+ }
+ } else {
+ flagbadhelo = bhelocheck();
+ }
+ if (flagbadhelo == -3) flagbadhelo = 0;
+ }
+ if (!env_put("HELOHOST",helohost.s)) die_nomem();
+}
+
+int liphostok = 0;
+stralloc liphost = {0};
+
+int bmfok = 0;
+stralloc bmf = {0};
+struct constmap mapbmf;
+
+int brtok= 0;
+stralloc brt = {0};
+struct constmap mapbrt;
+
+int badhelook = 0;
+stralloc badhelo = {0};
+struct constmap mapbhlo;
+
+static struct cdb cdbm;
+static struct cdb cdbl;
+
+static int fdbmt;
+int flagmimetype = 0; /* 1: white; 2: cdb; 3: white+cdb; 4: !relay+white; 6: !relay+white+cdb; -1: found in cdb; -2: found white */
+char *badmimeinit;
+
+static int fdblt;
+int flagloadertype = 0; /* 1: cdb; 2: !relay+cdb; -1: found in cdb */
+char *badloaderinit;
+
+static int fdmav;
+int flagmav = 0;
+int localmf = 0; /* 1: domainpart->rcpthosts; 2: ->mailformrules; 3: ->remoteinfo; 4: ->DN(Email) */
+char *localmfcheck;
+
+char *mfdnscheck;
+char *qhpsi;
+char *base64;
+
+int maxrcptcount = 0;
+int flagerrcpts = 0;
+int flagnotorious = 0;
+
+int tarpitcount = 0;
+int tarpitdelay = 0;
+
+int greylist = 0;
+stralloc pgbind = {0};
+
+char *auth;
+int smtpauth = 0; /* -1:Cert 0:none 1:login/plain 2:cram 3:login/plain/cram 11:must_login/plain 12:must_2 13:must_3 */
+int seenauth = 0; /* 1:ESMTPA 2:~CLIENTDN */
+stralloc authmethod = {0};
+
+int starttls = 0; /* -1:TLS 0:none 1:STARTTLS 2:require_STARTTLS 3:relay_if_CLIENTDN 4:require_+_relay_if_CLIENTDN */
+int seentls = 0; /* 1:~STARTTLS 2:~TLS 3:~CLIENTDN */
+char *ucspitls = 0;
+char *tlsversion;
+char *cipher;
+char *cipherperm;
+char *cipherused;
+char *clientdn;
+char *clientcn;
+char *dnemail;
+
+stralloc mailto = {0};
+stralloc deliverto = {0};
+char *delivermailto;
+stralloc rblinfo = {0};
+char *rblsmtpd;
+
+int flagspf = 0;
+static stralloc spfbounce = {0};
+
+void setup()
+{
+ char *x;
+ unsigned long u;
+ int r;
+ flagip6 = 1; // GCC 10 implicit int declarition if global var
+
+ if (control_init() == -1) die_control();
+ if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1)
+ die_control();
+ liphostok = control_rldef(&liphost,"control/localiphost",1,(char *) 0);
+ if (liphostok == -1) die_control();
+ if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
+ if (timeout <= 0) timeout = 1;
+
+ if (rcpthosts_init() == -1) die_control();
+ if (recipients_init() == -1) die_control();
+
+ bmfok = control_readfile(&bmf,"control/badmailfrom",0);
+ if (bmfok == -1) die_control();
+ if (bmfok)
+ if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem();
+
+ brtok = control_readfile(&brt,"control/badrcptto",0);
+ if (brtok == -1) die_control();
+ if (brtok)
+ if (!constmap_init(&mapbrt,brt.s,brt.len,0)) die_nomem();
+
+ if (control_readint(&databytes,"control/databytes") == -1) die_control();
+ x = env_get("DATABYTES");
+ if (x) { scan_ulong(x,&u); databytes = u; }
+ if (!(databytes + 1)) --databytes;
+
+ if (!stralloc_copys(&protocol,"ESMTP")) die_nomem(); /* RFC 3848 */
+ remoteip = env_get("TCP6REMOTEIP"); /* compactified IPv6 */
+ if (!remoteip) remoteip = env_get("TCPREMOTEIP"); /* allow other tcpserver/sslserver */
+ if (remoteip && (str_chr(remoteip,':') < str_len(remoteip))) {
+ if (byte_equal(remoteip,7,V4MAPPREFIX))
+ { remoteip = remoteip + 7; flagip6 = 0; }
+ } else flagip6 = 0;
+ if (!remoteip) { remoteip = "unknown"; flagip6 = -1; }
+ local = env_get("TCP6LOCALHOST");
+ if (!local) local = env_get("TCPLOCALHOST");
+ if (!local) local = env_get("TCP6LOCALIP");
+ if (!local) local = env_get("TCPLOCALIP");
+ if (!local) local = "unknown";
+ localport = env_get("TCP6LOCALPORT");
+ if (!localport) localport = env_get("TCPLOCALPORT");
+ if (!localport) localport = "0";
+ remotehost = env_get("TCP6REMOTEHOST");
+ if (!remotehost) remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+ remoteinfo = env_get("TCP6REMOTEINFO");
+ if (!remoteinfo) remoteinfo = env_get("TCPREMOTEINFO");
+ relayclient = env_get("RELAYCLIENT");
+
+ if (!case_diffs(localport,PORT_SMTPS)) {
+ if (!modssl_info()) die_starttls();
+ starttls = -1;
+ }
+
+ mfdnscheck = env_get("MFDNSCHECK");
+ x = env_get("MAXRECIPIENTS");
+ if (x) { scan_ulong(x,&u); maxrcptcount = u; };
+ if (!(maxrcptcount + 1)) --maxrcptcount;
+
+ helocheck = env_get("HELOCHECK");
+ if (helocheck) {
+ badhelook = control_readfile(&badhelo,"control/badhelo",0);
+ if (badhelook == -1) die_control();
+ if (badhelook)
+ if (!constmap_init(&mapbhlo,badhelo.s,badhelo.len,0)) die_nomem();
+ }
+
+ x = env_get("TARPITCOUNT");
+ if (x) { scan_ulong(x,&u); tarpitcount = u; };
+ x = env_get("TARPITDELAY");
+ if (x) { scan_ulong(x,&u); tarpitdelay = u; };
+
+ x = env_get("POSTGREY"); // RFC 6647
+ if (x) {
+ if (case_diffs(x,"-")) {
+ greylist = 1;
+ if (!stralloc_copys(&pgbind,x)) die_nomem();
+ if (!stralloc_append(&pgbind,"")) die_nomem();
+ }
+ }
+
+ localmfcheck = env_get("LOCALMFCHECK");
+ if (localmfcheck) {
+ localmf = 1;
+ if (str_len(localmfcheck) == 1 && *localmfcheck == '!') {
+ localmf = 2;
+ fdmav = open_read("control/mailfromrules.cdb");
+ if (fdmav == -1 ) localmf = 1;
+ }
+ if (str_len(localmfcheck) == 1 && *localmfcheck == '=') {
+ localmf = 3;
+ }
+ if (str_len(localmfcheck) == 1 && *localmfcheck == '?') {
+ localmf = 4;
+ }
+ }
+
+ badmimeinit = env_get("BADMIMETYPE");
+ if (badmimeinit) {
+ fdbmt = open_read("control/badmimetypes.cdb");
+ if (str_len(badmimeinit) == 1) {
+ if (*badmimeinit == '-')
+ flagmimetype = 0;
+ else {
+ if (*badmimeinit == '!') flagmimetype = 1;
+ if (*badmimeinit == '+') flagmimetype = 4;
+ }
+ }
+ if (fdbmt != -1 ) flagmimetype = flagmimetype + 2;
+ }
+
+ badloaderinit = env_get("BADLOADERTYPE");
+ if (badloaderinit) {
+ if (str_len(badloaderinit) == 1) {
+ if (*badloaderinit == '-')
+ flagloadertype = 0;
+ else {
+ flagloadertype = 1;
+ if (*badloaderinit == '+') flagloadertype = 2;
+ fdblt = open_read("control/badloadertypes.cdb");
+ if (fdblt == -1 ) flagloadertype = 0;
+ }
+ }
+ }
+
+ base64 = env_get("BASE64");
+ qhpsi = env_get("QHPSI");
+ auth = env_get("SMTPAUTH");
+ if (auth) {
+ smtpauth = 1;
+ if (!case_diffs(auth,"-")) smtpauth = 0;
+ if (!case_diffs(auth,"!")) smtpauth = 11;
+ if (case_starts(auth,"cram")) smtpauth = 2;
+ if (case_starts(auth,"+cram")) smtpauth = 3;
+ if (case_starts(auth,"!cram")) smtpauth = 12;
+ if (case_starts(auth,"!+cram")) smtpauth = 13;
+ }
+
+ if (!seentls) {
+ ucspitls = env_get("UCSPITLS");
+ if (ucspitls) {
+ starttls = 1;
+ if (!case_diffs(ucspitls,"-")) starttls = 0;
+ if (!case_diffs(ucspitls,"!")) starttls = 2;
+ if (!case_diffs(ucspitls,"?")) starttls = 3;
+ if (!case_diffs(ucspitls,"@")) starttls = 4;
+ }
+ }
+
+ delivermailto = env_get("DELIVERTO");
+ if (delivermailto) {
+ if (!stralloc_cats(&mailto,delivermailto)) die_nomem();
+ if (!stralloc_cats(&mailto," ")) die_nomem();
+ }
+
+ rblsmtpd = env_get("RBLSMTPD");
+ if (rblsmtpd) {
+ if (!stralloc_cats(&rblinfo,rblsmtpd)) die_nomem();
+ if (!stralloc_0(&rblinfo)) die_nomem();
+ }
+
+ x = env_get("SPF");
+ if (x) { scan_ulong(x,&u); flagspf = u; }
+ if (flagspf < 0 || flagspf > 6) die_control();
+ if (flagspf) {
+ r = control_readline(&spflocalrules,"control/spflocalrules");
+ if (r == -1) die_control();
+ if (!stralloc_0(&spflocalrules)) die_nomem();
+ if (control_rldef(&spfexplain,"control/spfexplain",0,SPF_DEFEXP) == -1) die_control();
+ if (!stralloc_0(&spfexplain)) die_nomem();
+ }
+
+ x = env_get("UTF8");
+ if (x) flagutf8 = 1;
+
+ if (!stralloc_copys(&helohost,"")) die_nomem(); // helohost is initially empty
+ if (!stralloc_0(&helohost)) die_nomem();
+ fakehelo = 0;
+
+}
+
+void auth_info(char *method)
+{
+ if (!env_put("AUTHPROTOCOL",method)) die_nomem();
+ if (!env_put("AUTHUSER",remoteinfo)) die_nomem();
+ if (!env_unset("TCPREMOTEINFO")) die_read();
+ if (!env_put("TCPREMOTEINFO",remoteinfo)) die_nomem();
+ if (!env_unset("TCP6REMOTEINFO")) die_read();
+ if (!env_put("TCP6REMOTEINFO",remoteinfo)) die_nomem();
+
+ if (!stralloc_append(&protocol,"A")) die_nomem();
+}
+
+int modssl_info()
+{
+ tlsversion = env_get("SSL_PROTOCOL");
+ if (!tlsversion) return 0;
+
+ cipher = env_get("SSL_CIPHER");
+ if (!cipher) cipher = "unknown";
+ cipherperm = env_get("SSL_CIPHER_ALGKEYSIZE");
+ if (!cipherperm) cipherperm = "unknown";
+ cipherused = env_get("SSL_CIPHER_USEKEYSIZE");
+ if (!cipherused) cipherused = "unknown";
+ clientdn = env_get("SSL_CLIENT_S_DN");
+ if (!clientdn) clientdn = "none";
+ else {
+ seentls = 3;
+ seenauth = 2;
+ smtpauth = -1;
+ relayclient = "";
+ }
+
+ if (!stralloc_copys(&tlsinfo,tlsversion)) die_nomem();
+ if (!stralloc_cats(&tlsinfo,": ")) die_nomem();
+ if (!stralloc_cats(&tlsinfo,cipher)) die_nomem();
+ if (!stralloc_cats(&tlsinfo," [")) die_nomem();
+ if (!stralloc_cats(&tlsinfo,cipherused)) die_nomem();
+ if (!stralloc_cats(&tlsinfo,"/")) die_nomem();
+ if (!stralloc_cats(&tlsinfo,cipherperm)) die_nomem();
+ if (!stralloc_cats(&tlsinfo,"] \n")) die_nomem();
+ if (!stralloc_cats(&tlsinfo," DN=")) die_nomem();
+ if (!stralloc_cats(&tlsinfo,clientdn)) die_nomem();
+ if (!stralloc_0(&tlsinfo)) die_nomem();
+
+ if (!stralloc_append(&protocol,"S")) die_nomem();
+
+ if (seentls == 3 && starttls == 4) {
+ clientcn = env_get("SSL_CLIENT_S_DN_CN");
+ remoteinfo = clientcn ? clientcn : clientdn;
+ dnemail = env_get("SSL_CLIENT_S_DN_Email");
+ if (!dnemail) dnemail = "unknown";
+ if (!stralloc_cats(&authmethod,"cert")) die_nomem();
+ auth_info(authmethod.s);
+ }
+ return 1;
+}
+
+/* this file is too long --------------------------------- Address checks */
+
+stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */
+stralloc eddr = {0}; /* extended address; used for smart address recognition */
+stralloc rddr = {0}; /* test anti-spoofing host name */
+stralloc mailfrom = {0};
+stralloc rcptto = {0};
+stralloc user = {0};
+stralloc fuser = {0};
+stralloc mfparms = {0};
+
+int seenmail = 0;
+int flagaddr; /* defined if seenmail */
+int flagrcpt;
+int flagdnsmf = 0;
+int flagsize;
+int rcptcount = 0;
+
+int addrparse(char *arg)
+{
+ int i;
+ char ch;
+ char terminator;
+ struct ip4_address ip4s;
+ struct ip6_address ip6s;
+ int flagesc;
+ int flagquoted;
+
+ terminator = '>';
+ i = str_chr(arg,'<');
+ if (arg[i])
+ arg += i + 1;
+ else
+ return 0;
+
+ /* strip source route */
+ if (*arg == '@') while (*arg) if (*arg++ == ':') break;
+
+ if (!stralloc_copys(&addr,"")) die_nomem();
+ flagesc = 0;
+ flagquoted = 0;
+ for (i = 0; ch = arg[i]; ++i) { /* copy arg to addr, stripping quotes */
+ if (flagesc) {
+ if (!stralloc_append(&addr,&ch)) die_nomem();
+ flagesc = 0;
+ }
+ else {
+ if (!flagquoted && (ch == terminator)) break;
+ switch (ch) {
+ case '\\': flagesc = 1; break;
+ case '"': flagquoted = !flagquoted; break;
+ default: if (!stralloc_append(&addr,&ch)) die_nomem();
+ }
+ }
+ }
+ /* could check for termination failure here, but why bother? */
+ if (!stralloc_append(&addr,"")) die_nomem();
+
+ if (liphostok) {
+ i = byte_rchr(addr.s,addr.len,'@');
+ if (i < addr.len) /* if not, partner should go read rfc 821 */
+ if (addr.s[i + 1] == '[') {
+ if (byte_rchr(addr.s + i + 2,addr.len - i - 2,':') < str_len(addr.s)) { /* @[IPv6::] */
+ if (!addr.s[i + 1 + ip6_scanbracket(addr.s + i + 1,(char *)&ip6s.d)])
+ if (ipme_is6(&ip6s)) {
+ addr.len = i + 1;
+ if (!stralloc_cat(&addr,&liphost)) die_nomem();
+ }
+ } else { /* @[IPv4] */
+ if (!addr.s[i + 1 + ip4_scanbracket(addr.s + i + 1,(char *)&ip4s.d)])
+ if (ipme_is4(&ip4s)) {
+ addr.len = i + 1;
+ if (!stralloc_cat(&addr,&liphost)) die_nomem();
+ }
+ }
+ }
+ if (!stralloc_0(&addr)) die_nomem();
+ }
+
+ if (addr.len > 900) return 0;
+ return 1;
+}
+
+int bhelocheck()
+{
+ int i;
+ int j;
+ int k = 0;
+ char subvalue;
+
+ if (badhelook && helohost.len > 1) { /* helohost! */
+ if (!stralloc_copyb(&eddr,helohost.s,helohost.len - 1)) die_nomem();
+ if (!stralloc_append(&eddr,"!")) die_nomem();
+ if (!stralloc_0(&eddr)) die_nomem();
+ if (constmap(&mapbhlo,eddr.s,eddr.len - 1)) return -3;
+
+ if (constmap(&mapbhlo,helohost.s,helohost.len - 1)) return -1;
+
+ i = 0;
+ for (j = 0; j < badhelo.len; ++j)
+ if (!badhelo.s[j]) {
+ subvalue = badhelo.s[i] != '!';
+ if (!subvalue) i++;
+ if ((k != subvalue) && wildmat(helohost.s,badhelo.s + i)) k = subvalue;
+ i = j + 1;
+ }
+ return k;
+ }
+ return 0;
+}
+
+int bmfcheck()
+{
+ int i = 0;
+ int j = 0;
+ int k = 0;
+ int at = 0;
+ int dlen;
+ int rlen;
+ char subvalue;
+
+ if (bmfok && mailfrom.len > 1) {
+ rlen = str_len(remotehost);
+ at = byte_rchr(mailfrom.s,mailfrom.len,'@');
+
+/* '?' enhanced address to skip all other tests including MFDNSCHECK */
+
+ if (!stralloc_copys(&eddr,"?")) die_nomem();
+ if (!stralloc_cat(&eddr,&mailfrom)) die_nomem();
+ case_lowerb(eddr.s,eddr.len);
+ if (constmap(&mapbmf,eddr.s,eddr.len - 1)) return -110;
+
+/* '+' extended address for none-RELAYCLIENTS */
+
+ if (at && !relayclient) {
+ if (!stralloc_copyb(&eddr,mailfrom.s,mailfrom.len - 1)) die_nomem();
+ if (!stralloc_append(&eddr,"+")) die_nomem();
+ if (!stralloc_0(&eddr)) die_nomem();
+ case_lowerb(eddr.s,eddr.len);
+ if (constmap(&mapbmf,eddr.s + at,eddr.len - at - 1)) return -5;
+ }
+
+/* '-' extended address from UNKNOWN */
+
+ if (at && !case_diffs(remotehost,"unknown")) {
+ if (!stralloc_copyb(&eddr,mailfrom.s,mailfrom.len - 1)) die_nomem();
+ if (!stralloc_append(&eddr,"-")) die_nomem();
+ if (!stralloc_0(&eddr)) die_nomem();
+ case_lowerb(eddr.s,eddr.len);
+ if (constmap(&mapbmf,eddr.s + at,eddr.len - at - 1)) return -4;
+ }
+
+/* '=' extended address for WELLKNOWN senders */
+
+ else if (at && rlen >= mailfrom.len - at - 1) {
+ dlen = mailfrom.len - at - 2;
+ if (!stralloc_copyb(&eddr,mailfrom.s,mailfrom.len - 1)) die_nomem();
+ if (!stralloc_append(&eddr,"=")) die_nomem();
+ if (!stralloc_0(&eddr)) die_nomem();
+ case_lowerb(eddr.s,eddr.len);
+ if (str_diffn(remotehost + rlen - dlen,eddr.s + at + 1,dlen))
+ if (constmap(&mapbmf,eddr.s + at,eddr.len - at - 1)) return -3;
+
+/* '~' extended address for MISMATCHED Domains */
+
+ if (case_diffrs(remotehost,mailfrom.s + at + 1)) {
+ j = 0;
+ do {
+ if (!stralloc_copys(&eddr,"~")) die_nomem();
+ if (!stralloc_cats(&eddr,remotehost + j)) die_nomem();
+ if (!stralloc_0(&eddr)) die_nomem();
+ if (constmap(&mapbmf,eddr.s,eddr.len - 1)) return -2;
+ j = byte_chr(remotehost + j,rlen - j,'.') + j + 1;
+ } while (j > 0 && rlen - j > 0);
+ }
+ }
+
+/* Standard */
+
+ if (constmap(&mapbmf,mailfrom.s,mailfrom.len - 1)) return -1;
+ if (at && at < mailfrom.len)
+ if (constmap(&mapbmf,mailfrom.s + at,mailfrom.len - at - 1)) return -1;
+
+/* Wildmating */
+
+ i = k = 0;
+ for (j = 0; j < bmf.len; ++j) {
+ if (!bmf.s[j]) {
+ subvalue = bmf.s[i] != '!';
+ if (!subvalue) i++;
+ if ((k != subvalue) && wildmat(mailfrom.s,bmf.s + i)) k = subvalue;
+ i = j + 1;
+ }
+ }
+ return k;
+ }
+
+ return 0;
+}
+
+int brtcheck()
+{
+ int i;
+ int j = 0;
+ int k = 0;
+ char subvalue;
+
+ if (brtok) {
+ if (constmap(&mapbrt,addr.s,addr.len - 1)) return -2;
+
+ int at = byte_rchr(addr.s,addr.len,'@');
+ if (at < addr.len)
+ if (constmap(&mapbrt,addr.s + at,addr.len - at - j)) return -1;
+
+/* '#' enhanced address to consider invalid rcptto addresses for none-relayclients */
+
+ if (!relayclient) {
+ if (!stralloc_copys(&eddr,"+")) die_nomem();
+ if (!stralloc_cat(&eddr,&addr)) die_nomem();
+ if (!stralloc_0(&eddr)) die_nomem();
+ case_lowerb(eddr.s,eddr.len);
+ if (constmap(&mapbmf,eddr.s,eddr.len - 1)) return 110;
+ }
+
+ i = 0;
+ for (j = 0; j < brt.len; ++j)
+ if (!brt.s[j]) {
+ subvalue = brt.s[i] != '!';
+ if (!subvalue) i++;
+ if ((k != subvalue) && wildmat(addr.s,brt.s + i)) k = subvalue;
+ i = j + 1;
+ }
+ return k;
+ }
+ return 0;
+}
+
+int addrallowed(char *arg)
+{
+ int r;
+ r = rcpthosts(arg,str_len(arg));
+ if (r == -1) die_control();
+ return r;
+}
+
+int rcptallowed()
+{
+ int r;
+ r = recipients(addr.s,str_len(addr.s));
+#ifdef PAM111421
+ if (r == 111) die_recipients();
+#endif
+ if (r == -3) die_recipients();
+ if (r == -2) die_nomem();
+ if (r == -1) die_control();
+ return r;
+}
+
+int localaddr(char *mf)
+{
+ int at;
+ int mflen;
+
+ mflen = str_len(mf);
+ if (mflen < 1 ) return 0;
+
+ if (localmf == 4) {
+ if (!case_diffs(dnemail,mf)) return 2;
+ return -4;
+ }
+ if (localmf == 3) {
+ if (!case_diffs(remoteinfo,mf)) return 2;
+ return -3;
+ }
+ else if (localmf == 2)
+ return mfrules(fdmav,remoteip,remotehost,remoteinfo,mf);
+ else {
+ if (str_len(localmfcheck) > 1) {
+ case_lowerb(localmfcheck,str_len(localmfcheck));
+ at = byte_rchr(mf,mflen,'@');
+ if (at < mflen)
+ if (!str_diffn(localmfcheck,mf + at + 1,mflen - at - 1)) return 2;
+ }
+ if (addrallowed(mf)) return 3;
+ return -2;
+ }
+}
+
+int spf_check(int flag6)
+{
+ int r;
+
+ if (mailfrom.len <= 1) { flagspf = 0; return 0; }
+
+ DNS_INIT
+ r = spf_query(remoteip,helohost.s,mailfrom.s,local,flag6);
+ if (r == SPF_NOMEM) die_nomem();
+ if (!stralloc_0(&spfinfo)) die_nomem();
+
+ switch (r) {
+ case SPF_ME:
+ case SPF_OK:
+ if (!env_put("SPFRESULT","pass")) die_nomem();
+ flagspf = 10;
+ break;
+ case SPF_LOOP:
+ case SPF_ERROR:
+ case SPF_SYNTAX:
+ case SPF_EXHAUST:
+ if (!env_put("SPFRESULT","error")) die_nomem();
+ if (flagspf < 2) { flagspf = 0; break; }
+ out("451 SPF lookup failure (#4.3.0)\r\n");
+ return -1;
+ case SPF_NONE:
+ if (!env_put("SPFRESULT","none")) die_nomem();
+ flagspf = 0;
+ break;
+ case SPF_UNKNOWN:
+ if (!env_put("SPFRESULT","unknown")) die_nomem();
+ if (flagspf < 6) break;
+ else return 4;
+ case SPF_NEUTRAL:
+ if (!env_put("SPFRESULT","neutral")) die_nomem();
+ if (flagspf < 5) break;
+ else return 3;
+ case SPF_SOFTFAIL:
+ if (!env_put("SPFRESULT","softfail")) die_nomem();
+ if (flagspf < 4) break;
+ else return 2;
+ case SPF_FAIL:
+ if (!env_put("SPFRESULT","fail")) die_nomem();
+ if (flagspf < 3) break;
+ if (!spf_parse(&spfbounce,spfexpmsg.s,expdomain.s)) die_nomem();
+ return 1;
+ }
+
+ return 0;
+}
+
+/* this file is too long --------------------------------- MF parser */
+
+int mailfrom_size(char *arg)
+{
+ unsigned long r;
+ unsigned long sizebytes = 0;
+
+ scan_ulong(arg,&r);
+ sizebytes = r;
+ if (databytes) if (sizebytes > databytes) return 1;
+ return 0;
+}
+
+void mailfrom_auth(char *arg,int len)
+{
+ if (!stralloc_copys(&fuser,"")) die_nomem();
+ if (case_starts(arg,"<>")) {
+ if (!stralloc_cats(&fuser,"unknown")) die_nomem();
+ } else {
+ while (len) {
+ if (*arg == '+') {
+ if (case_starts(arg,"+3D")) {
+ arg = arg + 2; len = len - 2;
+ if (!stralloc_cats(&fuser,"=")) die_nomem();
+ }
+ if (case_starts(arg,"+2B")) {
+ arg = arg + 2; len = len - 2;
+ if (!stralloc_cats(&fuser,"+")) die_nomem();
+ }
+ } else {
+ if (!stralloc_catb(&fuser,arg,1)) die_nomem();
+ }
+ arg++; len--;
+ }
+ }
+
+ if (!stralloc_0(&fuser)) die_nomem();
+ if (!remoteinfo) {
+ remoteinfo = fuser.s;
+ if (!env_unset("TCPREMOTEINFO")) die_read();
+ if (!env_put("TCPREMOTEINFO",remoteinfo)) die_nomem();
+ if (!env_unset("TCP6REMOTEINFO")) die_read();
+ if (!env_put("TCP6REMOTEINFO",remoteinfo)) die_nomem();
+ }
+}
+
+void mailfrom_parms(char *arg)
+{
+ int len;
+
+ if ((len = str_len(arg))) {
+ if (!stralloc_copys(&mfparms,"")) die_nomem();
+ while (len) {
+ arg++; len--;
+ if (*arg == ' ' || *arg == '\0' ) {
+ if (flagutf8) if (case_starts(mfparms.s,"SMTPUTF8")) flagutf8 = 2;
+ if (case_starts(mfparms.s,"SIZE=")) if (mailfrom_size(mfparms.s + 5)) { flagsize = 1; return; }
+ if (case_starts(mfparms.s,"AUTH=")) mailfrom_auth(mfparms.s + 5,mfparms.len - 5);
+ if (!stralloc_copys(&mfparms,"")) die_nomem();
+ }
+ else
+ if (!stralloc_catb(&mfparms,arg,1)) die_nomem();
+ }
+ }
+}
+
+/* this file is too long --------------------------------- SMTP dialog */
+
+void smtp_helo(char *arg)
+{
+ smtp_greet("250 "); out("\r\n"); flush();
+ seenmail = 0; rcptcount = 0; seenhelo++;
+ dohelo(arg);
+}
+
+void smtp_ehlo(char *arg)
+{
+ char size[FMT_ULONG];
+
+ smtp_greet("250-"); out("\r\n");
+ out("250-PIPELINING\r\n250-8BITMIME\r\n");
+ if (flagutf8) out("250-SMTPUTF8\r\n");
+ if (starttls > 0 && !seentls) out("250-STARTTLS\r\n");
+
+ switch (smtpauth) {
+ case 1: case 11: out("250-AUTH LOGIN PLAIN\r\n"); break;
+ case 2: case 12: out("250-AUTH CRAM-MD5\r\n"); break;
+ case 3: case 13: out("250-AUTH LOGIN PLAIN CRAM-MD5\r\n"); break;
+ }
+
+ size[fmt_ulong(size,(unsigned long) databytes)] = 0;
+ out("250 SIZE "); out(size); out("\r\n");
+
+ seenhelo++; seenmail = 0; rcptcount = 0;
+ dohelo(arg);
+}
+
+void smtp_rset(void)
+{
+ seenmail = 0; rcptcount = 0; /* RFC 5321: seenauth + seentls stay */
+
+ if (!stralloc_copys(&mailfrom,"")) die_nomem();
+ if (!stralloc_copys(&rcptto,"")) die_nomem();
+ out("250 flushed\r\n");
+}
+
+void smtp_starttls()
+{
+ if (starttls == 0) err_starttls();
+
+ out("220 Ready to start TLS (#5.7.0)\r\n");
+ flush();
+
+ if (!starttls_init()) die_starttls();
+ buffer_init(&bi,saferead,FDIN,inbuf,sizeof(inbuf));
+ seentls = 2;
+
+ if (!starttls_info()) die_starttls();
+ if (!modssl_info()) die_starttls();
+
+/* reset SMTP state */
+
+ seenhelo = 0; seenmail = 0; rcptcount = 0;
+ if (!stralloc_copys(&addr,"")) die_nomem();
+ if (!stralloc_copys(&helohost,"")) die_nomem();
+ if (!stralloc_copys(&mailfrom,"")) die_nomem();
+ if (!stralloc_copys(&rcptto,"")) die_nomem();
+ if (seenauth == 1) seenauth = 0; /* Otherwise Auth by client Cert */
+}
+
+void smtp_mail(char *arg)
+{
+ if (flagutf8) if (!stralloc_cats(&protocol,"UTF8")) die_nomem();
+ if (!stralloc_0(&protocol)) die_nomem();
+
+ if ((starttls > 1) && !seentls) {
+ err_tlsreq("Reject::TLS::missing",protocol.s,remoteip,remotehost,helohost.s);
+ return;
+ }
+ if (smtpauth > 10 && !seenauth) {
+ err_authreq("Reject::AUTH::missing",protocol.s,remoteip,remotehost,helohost.s);
+ return;
+ }
+ if (!addrparse(arg)) { err_syntax(); return; }
+
+ flagsize = 0;
+ rcptcount = 0;
+ mailfrom_parms(arg);
+ seenmail++;
+ if (relayclient && localmf) {
+ flagmav = localaddr(addr.s);
+ if (flagmav > 0) if (!stralloc_append(&protocol,"M")) die_nomem();
+ }
+ if (!stralloc_copys(&rcptto,"")) die_nomem();
+ if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
+ if (!stralloc_0(&mailfrom)) die_nomem();
+ if (!env_put("MAILFROM",mailfrom.s)) die_nomem();
+
+ flagaddr = bmfcheck();
+ if (flagaddr != -110)
+ if (mfdnscheck) flagdnsmf = dnsq(mailfrom.s,'M');
+
+ out("250 ok\r\n");
+}
+
+/* this file is too long --------------------------------- Greylisting */
+
+int postgrey_scanner()
+{
+ int child;
+ int wstat;
+
+ char *postgrey_scannerarg[] = {"bin/qmail-postgrey",pgbind.s,mailfrom.s,addr.s,remoteip,remotehost,0};
+
+ switch (child = fork()) {
+ case -1:
+ return err_forkgl();
+ case 0:
+ execv(*postgrey_scannerarg,postgrey_scannerarg);
+ _exit(1);
+ }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat)) return err_postgl();
+
+ switch (wait_exitcode(wstat)) {
+ case 10: return 1;
+ default: return 0;
+ }
+}
+
+void smtp_rcpt(char *arg)
+{
+ char *rcptok = 0;
+ if (!seenmail) { err_wantmail(); return; }
+ if (!addrparse(arg)) { err_syntax(); return; }
+ rcptcount++;
+
+/* this file is too long --------------------------------- Split Horizon envelope checks */
+
+ if (!relayclient) {
+ if (!seenhelo && helocheck) /* Helo rejects */
+ if (str_len(helocheck) == 1) {
+ err_helo("Reject::SNDR::Bad_Helo",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,"0");
+ return;
+ }
+ if (flagbadhelo) {
+ switch (flagbadhelo) {
+ case -2: badhelocond = "!"; break;
+ case -1: badhelocond = "."; break;
+ default: badhelocond = "*"; break;
+ }
+ err_helo("Reject::SNDR::Bad_Helo",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,badhelocond);
+ return;
+ }
+ if (flagdnshelo > 0) {
+ err_helo("Reject::SNDR::DNS_Helo",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,badhelocond);
+ return;
+ }
+
+ if (flagdnsmf > 0) { /* Mail from rejects */
+ err_mfdns("Reject::ORIG::DNS_MF",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+
+ if (!addrallowed(addr.s)) { /* Relaying rejects */
+ err_nogateway("Reject::SNDR::Invalid_Relay",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+
+ if (greylist && (postgrey_scanner() == 1)) { /* Greylisting */
+ postgrey("Deferred::SNDR::Grey_Listed",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+
+ if (tarpitcount && flagerrcpts >= tarpitcount) { /* Tarpitting et al. */
+ if (tarpitdelay == 999) flagnotorious++;
+ err_rcpts("Reject::RCPT::Toomany_Rcptto",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+ if (tarpitcount && rcptcount >= tarpitcount)
+ if (tarpitdelay > 0 && tarpitdelay < 999) sleep(tarpitdelay);
+
+ flagrcpt = rcptallowed(); /* Rcpt to rejects */
+ if (!flagrcpt) {
+ err_recipient("Reject::RCPT::Failed_Rcptto",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ flagerrcpts++;
+ return;
+ }
+
+ if (flagspf) /* SPF rejects */
+ if (spf_check(flagip6) > 0) {
+ if (!stralloc_0(&spfbounce)) die_nomem(); // spfbounce is 0-terminated in any case
+ err_spf("Reject::SPF::Fail",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,spfbounce.s);
+ return;
+ }
+ }
+
+/* this file is too long --------------------------------- Local checks */
+
+ else {
+ if (flagmimetype == 4 || flagmimetype == 6) flagmimetype = 0;
+ if (flagloadertype == 2) flagloadertype = 0;
+
+ if (flagmav < 0) {
+ err_mav("Reject::ORIG::Invalid_Mailfrom",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+ --addr.len;
+ if (!stralloc_cats(&addr,relayclient)) die_nomem();
+ if (!stralloc_0(&addr)) die_nomem();
+ }
+
+/* this file is too long --------------------------------- Common checks */
+
+ if (flagmimetype == 2 || flagmimetype == 3 || flagmimetype == 6) cdb_init(&cdbm,fdbmt);
+ if (flagloadertype == 1) cdb_init(&cdbl,fdblt);
+
+ if (flagaddr && flagaddr != -110) {
+ switch (flagaddr) {
+ case -1: badmailcond = "@"; break;
+ case -2: badmailcond = "~"; break;
+ case -3: badmailcond = "="; break;
+ case -4: badmailcond = "-"; break;
+ case -5: badmailcond = "+"; break;
+ default: badmailcond = "*"; break;
+ }
+ err_bmf("Reject::ORIG::Bad_Mailfrom",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,badmailcond);
+ return;
+ }
+
+ flagrcpt = brtcheck();
+ if (flagrcpt == 110) {
+ err_brt("Reject::RCPT::Invalid_Rcptto",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ } else if (flagrcpt > 0) {
+ err_brt("Reject::RCPT::Bad_Rcptto",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+
+ if (flagsize) {
+ err_size("Reject::DATA::Invalid_Size",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+
+ if (maxrcptcount && rcptcount > maxrcptcount) {
+ err_rcpts("Reject::RCPT::Toomany_Rcptto",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+
+/* this file is too long --------------------------------- Checks done; mailfrom/recipient accepted */
+
+ if (!stralloc_cats(&rcptto,"T")) die_nomem();
+ if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
+ if (!stralloc_0(&rcptto)) die_nomem();
+
+ if (!stralloc_cats(&mailto,addr.s)) die_nomem();
+ if (!stralloc_cats(&mailto," ")) die_nomem();
+ if (!stralloc_copys(&deliverto,mailto.s)) die_nomem();
+ if (!stralloc_0(&deliverto)) die_nomem();
+ if (!env_put("RCPTTO",deliverto.s)) die_nomem();
+
+/* this file is too long --------------------------------- Additional logging */
+
+ switch (flagrcpt) {
+ case 1: rcptok = "Recipients_Cdb"; break;
+ case 2: rcptok = "Recipients_Pam"; break;
+ case 3: rcptok = "Recipients_Users"; break;
+ case 4: rcptok = "Recipients_Wild"; break;
+ default: rcptok = "Rcpthosts_Rcptto"; break;
+ }
+ if (seenauth)
+ smtp_loga("Accept::AUTH::",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,remoteinfo,authmethod.s);
+ else if (flagmav > 0)
+ smtp_logg("Accept::ORIG::Local_Sender",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ else if (relayclient)
+ smtp_logg("Accept::SNDR::Relay_Client",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ else if (flagspf == 10)
+ smtp_logr("Accept::SPF::",rcptok,protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ else
+ smtp_logr("Accept::RCPT::",rcptok,protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+
+ out("250 ok\r\n");
+}
+
+struct qmail qqt;
+unsigned long bytestooverflow = 0;
+
+stralloc line = {0};
+stralloc base64types = {0};
+stralloc badmimetype = {0};
+stralloc badloadertype = {0};
+
+unsigned int nolines = 0;
+unsigned int flagb64 = 0; /* lineno with BASE64MESSAGE */
+unsigned int flagbase = 0; /* lineno with actual base64 content */
+unsigned int flagblank = 0;
+
+static void queue_put(char *ch)
+{
+ int i;
+
+ if (flagmimetype > 0 || flagloadertype > 0 ) {
+ if (line.len <= BUFFER_SIZE)
+ if (!stralloc_catb(&line,ch,1)) die_nomem(); /* Reassamble chars to line; prepend with 'L' */
+
+ if (*ch == '\n') {
+ nolines++;
+ if (line.len == 2) { flagblank = nolines; flagbase = 0; }
+
+ if (*(line.s + 1) == 'C' || *(line.s + 1) == 'c')
+ if (case_startb(line.s + 1,line.len - 2,BASE64MESSAGE)) flagb64 = nolines;
+ if (flagb64 && nolines == flagblank + 1 && line.len > MIMETYPE_LEN + 2) flagbase = nolines;
+ if (*(line.s + 1) == '-') { flagb64 = 0; flagbase = 0; }
+
+ if (flagmimetype > 0 && flagbase == nolines) { /* badmimetype */
+ if (!stralloc_catb(&base64types,line.s + 1,MIMETYPE_LEN)) die_nomem();
+ if (!stralloc_0(&base64types)) die_nomem();
+
+ if (flagmimetype == 2 || flagmimetype == 3 || flagmimetype == 6) {
+ if (cdb_find(&cdbm,line.s + 1,MIMETYPE_LEN)) {
+ cdb_free(&cdbm); close(fdbmt);
+ if (!stralloc_copyb(&badmimetype,line.s + 1,MIMETYPE_LEN)) die_nomem();
+ if (!stralloc_0(&badmimetype)) die_nomem();
+ if (!stralloc_cats(&rcptto,"M")) die_nomem();
+ if (!stralloc_0(&rcptto)) die_nomem();
+ qmail_fail(&qqt);
+ flagmimetype = -1;
+ }
+ }
+ }
+
+ if (flagbase && line.len > LOADER_LEN + 2) {
+ if (flagloadertype >= 1 || flagmimetype >= 1) {
+ for (i = 0; i < line.len - LOADER_LEN; ++i) {
+ if (flagloadertype == 1 && *(line.s+i) == *badloaderinit) { /* badloadertype */
+ if (cdb_find(&cdbl,line.s + i,LOADER_LEN)) {
+ cdb_free(&cdbl); close(fdbmt);
+ if (!stralloc_copyb(&badloadertype,line.s + i,LOADER_LEN)) die_nomem();
+ if (!stralloc_0(&badloadertype)) die_nomem();
+ if (!stralloc_cats(&rcptto,"L")) die_nomem();
+ if (!stralloc_0(&rcptto)) die_nomem();
+ qmail_fail(&qqt);
+ flagloadertype = -1;
+ }
+ }
+ if (flagmimetype == 1 || flagmimetype == 3 || flagmimetype == 4) {
+ if (*(line.s + i) == ' ' || *(line.s + i) == '\t') { /* white spaces */
+ if (!stralloc_copyb(&badmimetype,line.s + i - 2,MIMETYPE_LEN)) die_nomem();
+ if (!stralloc_0(&badmimetype)) die_nomem();
+ if (!stralloc_cats(&rcptto,"M")) die_nomem();
+ if (!stralloc_0(&rcptto)) die_nomem();
+ qmail_fail(&qqt);
+ flagmimetype = -2;
+ }
+ }
+ }
+ }
+ }
+ line.len = 0;
+ if (!stralloc_copys(&line,"L")) die_nomem();
+ }
+ }
+
+ if (bytestooverflow)
+ if (!--bytestooverflow)
+ qmail_fail(&qqt);
+ qmail_put(&qqt,ch,1);
+}
+
+void blast(int *hops)
+{
+ char ch;
+ int state;
+ int seencr;
+ int flaginheader;
+ int pos; /* number of bytes since most recent \n, if fih */
+ int flagmaybex; /* 1 if this line might match RECEIVED, if fih */
+ int flagmaybey; /* 1 if this line might match \r\n, if fih */
+ int flagmaybez; /* 1 if this line might match DELIVERED, if fih */
+
+ state = 1;
+ *hops = 0;
+ flaginheader = 1;
+ pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; seencr = 0;
+ for (;;) {
+ buffer_get(&bi,&ch,1);
+ if (ch == '\n') {
+ if (seencr == 0) { buffer_seek(&bi,-1); ch = '\r'; }
+ }
+ if (ch == '\r') seencr = 1; else seencr = 0;
+ if (flaginheader) {
+ if (pos < 9) {
+ if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0;
+ if (flagmaybez) if (pos == 8) ++*hops;
+ if (pos < 8)
+ if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0;
+ if (flagmaybex) if (pos == 7) ++*hops;
+ if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0;
+ if (flagmaybey) if (pos == 1) flaginheader = 0;
+ ++pos;
+ }
+ if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; }
+ }
+ switch (state) {
+ case 0:
+ if (ch == '\n') straynewline();
+ if (ch == '\r') { state = 4; continue; }
+ break;
+ case 1: /* \r\n */
+ if (ch == '\n') straynewline();
+ if (ch == '.') { state = 2; continue; }
+ if (ch == '\r') { state = 4; continue; }
+ state = 0;
+ break;
+ case 2: /* \r\n + . */
+ if (ch == '\n') straynewline();
+ if (ch == '\r') { state = 3; continue; }
+ state = 0;
+ break;
+ case 3: /* \r\n + .\r */
+ if (ch == '\n') return;
+ queue_put(".");
+ queue_put("\r");
+ if (ch == '\r') { state = 4; continue; }
+ state = 0;
+ break;
+ case 4: /* + \r */
+ if (ch == '\n') { state = 1; break; }
+ if (ch != '\r') { queue_put("\r"); state = 0; }
+ }
+ queue_put(&ch);
+ }
+}
+
+char accept_buf[FMT_ULONG];
+
+void acceptmessage(unsigned long qp)
+{
+ datetime_sec when;
+ when = now();
+ out("250 ok ");
+ accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0;
+ out(accept_buf);
+ out(" qp ");
+ accept_buf[fmt_ulong(accept_buf,qp)] = 0;
+ out(accept_buf);
+ out("\r\n");
+}
+
+void smtp_data()
+{
+ int hops;
+ unsigned long qp;
+ char *qqx;
+
+ if (!seenmail) { err_wantmail(); return; }
+ if (!rcptto.len) { err_wantrcpt(); return; }
+ if (flagnotorious) { err_notorious(); }
+ seenmail = 0;
+ if (databytes) bytestooverflow = databytes + 1;
+
+ if (!stralloc_copys(&addr,"")) die_nomem();
+ if (!stralloc_cats(&addr,rcptto.s + 1)) die_nomem();
+ if (!stralloc_0(&addr)) die_nomem();
+
+ if (qmail_open(&qqt) == -1) { err_qqt(); return; }
+ qp = qmail_qp(&qqt);
+
+ out("354 go ahead\r\n");
+
+ if (flagspf && !relayclient) spfheader(&qqt,spfinfo.s,local,remoteip,helohost.s,mailfrom.s);
+ received(&qqt,protocol.s,local,remoteip,remotehost,remoteinfo,fakehelo,tlsinfo.s,rblinfo.s);
+ blast(&hops);
+ hops = (hops >= MAXHOPS);
+ if (hops) qmail_fail(&qqt);
+ if (base64 && base64types.len == 0) {
+ if (!stralloc_cats(&rcptto,"Q")) die_nomem();
+ if (!stralloc_0(&rcptto)) die_nomem();
+ }
+ qmail_from(&qqt,mailfrom.s);
+ qmail_put(&qqt,rcptto.s,rcptto.len);
+
+ qqx = qmail_close(&qqt);
+ if (!*qqx) { acceptmessage(qp); return; }
+ if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
+ if (databytes)
+ if (!bytestooverflow) {
+ err_size("Reject::DATA::Invalid_Size",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s);
+ return;
+ }
+ if (flagmimetype < 0) {
+ err_data("Reject::DATA::Bad_MIME",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,badmimetype.s);
+ return;
+ }
+ if (flagloadertype < 0) {
+ err_data("Reject::DATA::Bad_Loader",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,badloadertype.s);
+ return;
+ }
+ if (*qqx == 'I') {
+ err_data("Reject::DKIM::Signature",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,"fail");
+ return;
+ }
+ if (*qqx == 'S') {
+ err_data("Reject::DATA::Spam_Message",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,"spam");
+ return;
+ }
+ if (*qqx == 'A') {
+ err_data("Reject::DATA::MIME_Attach",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,"MIME");
+ return;
+ }
+ if (*qqx == 'V') {
+ if (qhpsi)
+ err_data("Reject::DATA::Virus_Infected",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,qhpsi);
+ else
+ err_data("Reject::DATA::Virus_Infected",protocol.s,remoteip,remotehost,helohost.s,mailfrom.s,addr.s,"AV scanner");
+ return;
+ }
+ if (*qqx == 'D') out("554 "); else out("451 ");
+ out(qqx + 1);
+ out("\r\n");
+}
+
+/* this file is too long --------------------------------- SMTP Auth */
+
+char unique[FMT_ULONG + FMT_ULONG + 3];
+static stralloc authin = {0}; /* input from SMTP client */
+static stralloc pass = {0}; /* plain passwd or digest */
+static stralloc resp = {0}; /* b64 response */
+static stralloc chal = {0}; /* CRAM-MD5 plain challenge */
+static stralloc slop = {0}; /* CRAM-MD5 b64 challenge */
+
+char **childargs;
+char authbuf[512];
+buffer ba = BUFFER_INIT(safewrite,FDAUTH,authbuf,sizeof(authbuf));
+
+int authgetl(void)
+{
+ int i;
+
+ if (!stralloc_copys(&authin,"")) die_nomem();
+ for (;;) {
+ if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */
+ i = buffer_get(&bi,authin.s + authin.len,1);
+ if (i != 1) die_read();
+ if (authin.s[authin.len] == '\n') break;
+ ++authin.len;
+ }
+
+ if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len;
+ authin.s[authin.len] = 0;
+ if (*authin.s == '*' && *(authin.s + 1) == 0) return err_authabort();
+ if (authin.len == 0) return err_authinput();
+ return authin.len;
+}
+
+int authenticate(void)
+{
+ int child;
+ int wstat;
+ int pi[2];
+
+ if (!stralloc_0(&user)) die_nomem();
+ if (!stralloc_0(&pass)) die_nomem();
+ if (!stralloc_0(&chal)) die_nomem();
+ if (!env_put("AUTHUSER",user.s)) die_nomem();
+
+ if (pipe(pi) == -1) return err_pipe();
+ switch (child = fork()) {
+ case -1:
+ return err_fork();
+ case 0:
+ close(pi[1]);
+ if (fd_copy(FDAUTH,pi[0]) == -1) return err_pipe();
+ sig_pipedefault();
+ execvp(*childargs,childargs);
+ _exit(1);
+ }
+ close(pi[0]);
+
+ buffer_init(&ba,write,pi[1],authbuf,sizeof(authbuf));
+ if (buffer_put(&ba,user.s,user.len) == -1) return err_write();
+ if (buffer_put(&ba,pass.s,pass.len) == -1) return err_write();
+ if (smtpauth == 2 || smtpauth == 3 || smtpauth == 12 || smtpauth == 13)
+ if (buffer_put(&ba,chal.s,chal.len) == -1) return err_write();
+ if (buffer_flush(&ba) == -1) return err_write();
+
+ close(pi[1]);
+ if (!stralloc_copys(&chal,"")) die_nomem();
+ if (!stralloc_copys(&slop,"")) die_nomem();
+ byte_zero(authbuf,sizeof(authbuf));
+ if (wait_pid(&wstat,child) == -1) return err_child();
+ if (wait_crashed(wstat)) return err_child();
+ if (wait_exitcode(wstat)) { sleep(AUTHSLEEP); return 1; } /* no */
+ return 0; /* yes */
+}
+
+int auth_login(char *arg)
+{
+ int r;
+ if (smtpauth == 2 || smtpauth == 12) return 1; /* only login/plain */
+
+ if (*arg) {
+ if ((r = b64decode((unsigned char *)arg,str_len(arg),&user)) == 1) return err_authinput();
+ }
+ else {
+ out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */
+ if (authgetl() < 0) return -1;
+ if ((r = b64decode((unsigned char *)authin.s,authin.len,&user)) == 1) return err_authinput();
+ }
+ if (r == -1) die_nomem();
+
+ out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */
+
+ if (authgetl() < 0) return -1;
+ if ((r = b64decode((unsigned char *)authin.s,authin.len,&pass)) == 1) return err_authinput();
+ if (r == -1) die_nomem();
+
+ if (!user.len || !pass.len) return err_authinput();
+ return authenticate();
+}
+
+int auth_plain(char *arg)
+{
+ int r, id = 0;
+ if (smtpauth == 2 || smtpauth == 12) return 1; /* only login/plain */
+
+ if (*arg) {
+ if ((r = b64decode((unsigned char *)arg,str_len(arg),&resp)) == 1) return err_authinput();
+ }
+ else {
+ out("334 \r\n"); flush();
+ if (authgetl() < 0) return -1;
+ if ((r = b64decode((unsigned char *)authin.s,authin.len,&resp)) == 1) return err_authinput();
+ }
+ if (r == -1 || !stralloc_0(&resp)) die_nomem();
+ while (resp.s[id]) id++; /* "authorize-id\0userid\0passwd\0" */
+
+ if (resp.len > id + 1)
+ if (!stralloc_copys(&user,resp.s + id + 1)) die_nomem();
+ if (resp.len > id + user.len + 2)
+ if (!stralloc_copys(&pass,resp.s + id + user.len + 2)) die_nomem();
+
+ if (!user.len || !pass.len) return err_authinput();
+ return authenticate();
+}
+
+int auth_cram()
+{
+ int i, r;
+ char *s;
+ if (smtpauth == 1 || smtpauth == 11) return 1; /* no challenge if login/plain */
+
+ s = unique; /* generate challenge */
+ s += fmt_uint(s,getpid());
+ *s++ = '.';
+ s += fmt_ulong(s,(unsigned long) now());
+ *s++ = '@';
+ *s++ = 0;
+ if (!stralloc_copys(&chal,"<")) die_nomem();
+ if (!stralloc_cats(&chal,unique)) die_nomem();
+ if (!stralloc_cats(&chal,local)) die_nomem();
+ if (!stralloc_cats(&chal,">")) die_nomem();
+ if (b64encode(&chal,&slop) < 0) die_nomem();
+ if (!stralloc_0(&slop)) die_nomem();
+
+ out("334 "); /* "334 base64_challenge \r\n" */
+ out(slop.s);
+ out("\r\n");
+ flush();
+
+ if (authgetl() < 0) return -1; /* got response */
+ if ((r = b64decode((unsigned char *)authin.s,authin.len,&resp)) == 1) return err_authinput();
+ if (r == -1 || !stralloc_0(&resp)) die_nomem();
+
+ i = str_rchr(resp.s,' ');
+ s = resp.s + i;
+ while (*s == ' ') ++s;
+ resp.s[i] = 0;
+ if (!stralloc_copys(&user,resp.s)) die_nomem();/* userid */
+ if (!stralloc_copys(&pass,s)) die_nomem(); /* digest */
+
+ if (!user.len || !pass.len) return err_authinput();
+ return authenticate();
+}
+
+struct authcmd {
+ char *text;
+ int (*fun)();
+} authcmds[] = {
+ { "login", auth_login }
+, { "plain", auth_plain }
+, { "cram-md5", auth_cram }
+, { 0, err_noauth }
+};
+
+void smtp_auth(char *arg)
+{
+ int i;
+ char *cmd = arg;
+
+ /* prevent users to expose userid + password over unencrypted connection */
+
+ if ((starttls > 1) && !seentls) {
+ if (!stralloc_append(&protocol,"A")) die_nomem();
+ if (!stralloc_0(&protocol)) die_nomem();
+ err_authsetup("Reject::TLS::required",protocol.s,remoteip,remotehost,helohost.s);
+ return;
+ }
+
+ if ((starttls > 1) && !seenhelo) {
+ if (!stralloc_append(&protocol,"A")) die_nomem();
+ if (!stralloc_0(&protocol)) die_nomem();
+ err_tlsreq("Reject::AUTH::invalid",protocol.s,remoteip,remotehost,helohost.s);
+ return;
+ }
+
+ if (!smtpauth) { out("503 auth not available (#5.3.3)\r\n"); flush(); _exit(0); }
+ if (smtpauth && !*childargs) {
+ err_authsetup("Reject::AUTH::invalid",protocol.s,remoteip,remotehost,helohost.s);
+ flush(); _exit(1);
+ }
+ if (seenauth) { err_authd(); return; }
+ if (seenmail) { err_authmail(); return; }
+
+ if (!stralloc_copys(&user,"")) die_nomem();
+ if (!stralloc_copys(&pass,"")) die_nomem();
+ if (!stralloc_copys(&resp,"")) die_nomem();
+ if (!stralloc_copys(&chal,"")) die_nomem(); /* only needed for CRAM-MD5 */
+
+ i = str_chr(cmd,' '); /* get AUTH type */
+ arg = cmd + i;
+ while (*arg == ' ') ++arg;
+ cmd[i] = 0;
+
+ for (i = 0; authcmds[i].text; ++i)
+ if (case_equals(authcmds[i].text,cmd)) break;
+
+ if (!authcmds[i].text) { /* invalid auth cmd */
+ if (!stralloc_append(&protocol,"A")) die_nomem();
+ if (!stralloc_0(&protocol)) die_nomem();
+ err_authinvalid("Reject::AUTH::Method",protocol.s,remoteip,remotehost,helohost.s);
+ return;
+ }
+
+ if (!stralloc_copys(&authmethod,authcmds[i].text)) die_nomem();
+ if (!stralloc_0(&authmethod)) die_nomem();
+
+ switch (authcmds[i].fun(arg)) {
+ case 0:
+ seenauth = 1;
+ relayclient = "";
+ remoteinfo = user.s;
+ auth_info(authmethod.s);
+ out("235 ok, go ahead (#2.0.0)\r\n");
+ break;
+ case 1:
+ if (!stralloc_append(&protocol,"A")) die_nomem();
+ if (!stralloc_0(&protocol)) die_nomem();
+ err_authfail("Reject::AUTH::",protocol.s,remoteip,remotehost,helohost.s,user.s,authmethod.s);
+ return;
+ }
+}
+
+/* this file is too long --------------------------------- GO ON */
+
+struct commands smtpcommands[] = {
+ { "rcpt", smtp_rcpt, 0 }
+, { "mail", smtp_mail, 0 }
+, { "data", smtp_data, flush }
+, { "auth", smtp_auth, flush }
+, { "quit", smtp_quit, flush }
+, { "helo", smtp_helo, flush }
+, { "ehlo", smtp_ehlo, flush }
+, { "rset", smtp_rset, flush }
+, { "help", smtp_help, flush }
+, { "noop", err_noop, flush }
+, { "vrfy", err_vrfy, flush }
+, { "starttls", smtp_starttls, flush }
+, { 0, err_unimpl, flush }
+} ;
+
+int main(int argc, char **argv)
+{
+ childargs = argv + 1;
+ sig_pipeignore();
+ if (chdir(auto_qmail) == -1) die_control();
+ setup();
+ smtpdlog_init();
+ if (ipme_init() != 1) die_ipme();
+ smtp_greet("220 ");
+ out(" ESMTP\r\n");
+ flush();
+ if (commands(&bi,&smtpcommands) == 0) die_read();
+ die_nomem();
+
+ return 0;
+}
diff --git a/src/qmail-start.c b/src/qmail-start.c
new file mode 100644
index 0000000..7a7342c
--- /dev/null
+++ b/src/qmail-start.c
@@ -0,0 +1,165 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "fd.h"
+#include "prot.h"
+#include "exit.h"
+#include "auto_uids.h"
+
+char *(qsargs[]) = { "qmail-send", 0 };
+char *(qcargs[]) = { "qmail-clean", 0 };
+char *(qlargs[]) = { "qmail-lspawn", "./Mailbox", 0 };
+char *(qrargs[]) = { "qmail-rspawn", 0 };
+char *(qtargs[]) = { "qmail-todo", 0};
+
+void die() { _exit(111); }
+
+int pi0[2];
+int pi1[2];
+int pi2[2];
+int pi3[2];
+int pi4[2];
+int pi5[2];
+int pi6[2];
+int pi7[2];
+int pi8[2];
+int pi9[2];
+int pi10[2];
+
+void closefds()
+{
+ close(2); close(3); close(4); close(5); close(6);
+ close(7); close(8);
+}
+
+void closepipes()
+{
+ close(pi1[0]); close(pi1[1]); close(pi2[0]); close(pi2[1]);
+ close(pi3[0]); close(pi3[1]); close(pi4[0]); close(pi4[1]);
+ close(pi5[0]); close(pi5[1]); close(pi6[0]); close(pi6[1]);
+ close(pi7[0]); close(pi7[1]); close(pi8[0]); close(pi8[1]);
+ close(pi9[0]); close(pi9[1]); close(pi10[0]); close(pi10[1]);
+}
+
+int main(int argc,char **argv)
+{
+ if (chdir("/") == -1) die();
+ umask(077);
+ if (prot_gid(auto_gidq) == -1) die();
+
+ if (fd_copy(2,0) == -1) die();
+ if (fd_copy(3,0) == -1) die();
+ if (fd_copy(4,0) == -1) die();
+ if (fd_copy(5,0) == -1) die();
+ if (fd_copy(6,0) == -1) die();
+ if (fd_copy(7,0) == -1) die();
+ if (fd_copy(8,0) == -1) die();
+
+ if (argv[1]) {
+ qlargs[1] = argv[1];
+ ++argv;
+ }
+
+ if (argv[1]) {
+ if (pipe(pi0) == -1) die();
+ switch (fork()) {
+ case -1:
+ die();
+ case 0:
+ if (prot_gid(auto_gidn) == -1) die();
+ if (prot_uid(auto_uidl) == -1) die();
+ close(pi0[1]);
+ if (fd_move(0,pi0[0]) == -1) die();
+ closefds();
+ execvp(argv[1],argv + 1);
+ die();
+ }
+ close(pi0[0]);
+ if (fd_move(1,pi0[1]) == -1) die();
+ }
+
+ if (pipe(pi1) == -1) die();
+ if (pipe(pi2) == -1) die();
+ if (pipe(pi3) == -1) die();
+ if (pipe(pi4) == -1) die();
+ if (pipe(pi5) == -1) die();
+ if (pipe(pi6) == -1) die();
+ if (pipe(pi7) == -1) die();
+ if (pipe(pi8) == -1) die();
+ if (pipe(pi9) == -1) die();
+ if (pipe(pi10) == -1) die();
+
+ switch (fork()) {
+ case -1: die();
+ case 0:
+ if (fd_copy(0,pi1[0]) == -1) die();
+ if (fd_copy(1,pi2[1]) == -1) die();
+ closefds();
+ closepipes();
+ execvp(*qlargs,qlargs);
+ die();
+ }
+
+ switch (fork()) {
+ case -1: die();
+ case 0:
+ if (prot_uid(auto_uidr) == -1) die();
+ if (fd_copy(0,pi3[0]) == -1) die();
+ if (fd_copy(1,pi4[1]) == -1) die();
+ closefds();
+ closepipes();
+ execvp(*qrargs,qrargs);
+ die();
+ }
+
+ switch (fork()) {
+ case -1: die();
+ case 0:
+ if (prot_uid(auto_uidq) == -1) die();
+ if (fd_copy(0,pi5[0]) == -1) die();
+ if (fd_copy(1,pi6[1]) == -1) die();
+ closefds();
+ closepipes();
+ execvp(*qcargs,qcargs);
+ die();
+ }
+
+ switch (fork()) {
+ case -1: die();
+ case 0:
+ if (prot_uid(auto_uids) == -1) die();
+ if (fd_copy(0,pi7[0]) == -1) die();
+ if (fd_copy(1,pi8[1]) == -1) die();
+ closefds();
+ if (fd_copy(2,pi9[1]) == -1) die();
+ if (fd_copy(3,pi10[0]) == -1) die();
+ closepipes();
+ execvp(*qtargs,qtargs);
+ die();
+ }
+
+ switch (fork()) {
+ case -1: die();
+ case 0:
+ if (prot_uid(auto_uidq) == -1) die();
+ if (fd_copy(0,pi9[0]) == -1) die();
+ if (fd_copy(1,pi10[1]) == -1) die();
+ closefds();
+ closepipes();
+ execvp(*qcargs,qcargs);
+ die();
+ }
+
+ if (prot_uid(auto_uids) == -1) die();
+ if (fd_copy(0,1) == -1) die();
+ if (fd_copy(1,pi1[1]) == -1) die();
+ if (fd_copy(2,pi2[0]) == -1) die();
+ if (fd_copy(3,pi3[1]) == -1) die();
+ if (fd_copy(4,pi4[0]) == -1) die();
+ if (fd_copy(5,pi5[1]) == -1) die();
+ if (fd_copy(6,pi6[0]) == -1) die();
+ if (fd_copy(7,pi7[1]) == -1) die();
+ if (fd_copy(8,pi8[0]) == -1) die();
+ closepipes();
+ execvp(*qsargs,qsargs);
+ die();
+}
diff --git a/src/qmail-tcpok.c b/src/qmail-tcpok.c
new file mode 100644
index 0000000..2935f17
--- /dev/null
+++ b/src/qmail-tcpok.c
@@ -0,0 +1,36 @@
+#include "logmsg.h"
+#include "buffer.h"
+#include "lock.h"
+#include "open.h"
+#include <unistd.h>
+#include "auto_qmail.h"
+#include "exit.h"
+
+#define WHO "qmail-tcpok"
+
+char buf[1024]; /* XXX: must match size in tcpto_clean.c, tcpto.c */
+buffer bo;
+
+int main()
+{
+ int fd;
+ int i;
+
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+ if (chdir("queue/lock") == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to ",auto_qmail,"/queue/lock: "));
+
+ fd = open_write("tcpto");
+ if (fd == -1)
+ logmsg(WHO,111,FATAL,B("unable to write ",auto_qmail,"/queue/lock/tcpto: "));
+ if (lock_ex(fd) == -1)
+ logmsg(WHO,111,FATAL,B("unable to lock ",auto_qmail,"/queue/lock/tcpto: "));
+
+ buffer_init(&bo,write,fd,buf,sizeof(buf));
+ for (i = 0; i < sizeof(buf); ++i)
+ buffer_put(&bo,"",1);
+ if (buffer_flush(&bo) == -1)
+ logmsg(WHO,111,FATAL,B("unable to clear ",auto_qmail,"/queue/lock/tcpto: "));
+ _exit(0);
+}
diff --git a/src/qmail-tcpto.c b/src/qmail-tcpto.c
new file mode 100644
index 0000000..e148c55
--- /dev/null
+++ b/src/qmail-tcpto.c
@@ -0,0 +1,95 @@
+/* XXX: this program knows quite a bit about tcpto's internals */
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include "buffer.h"
+#include "auto_qmail.h"
+#include "fmt.h"
+#include "ip.h"
+#include "lock.h"
+#include "error.h"
+#include "exit.h"
+#include "datetime.h"
+#include "now.h"
+#include "stralloc.h"
+#include "open.h"
+#include "logmsg.h"
+
+#define WHO "qmail-tcpto"
+
+void die(n) int n; { buffer_flush(buffer_1); _exit(n); }
+
+void warn(s) char *s;
+{
+ char *x;
+ x = error_str(errno);
+ buffer_puts(buffer_1,s);
+ buffer_puts(buffer_1,": ");
+ buffer_puts(buffer_1,x);
+ buffer_puts(buffer_1,"\n");
+}
+
+void die_chdir() { logmsg(WHO,110,FATAL,"unable to chdir"); }
+void die_open() { logmsg(WHO,112,FATAL,"unable to open tcpto"); }
+void die_lock() { logmsg(WHO,112,FATAL,"unable to lock tcpto"); }
+void die_read() { logmsg(WHO,112,FATAL,"unable to read tcpto"); }
+
+char tcpto_buf[1024];
+
+char tmp[FMT_ULONG + IPFMT];
+
+int main(void)
+{
+ int fdlock;
+ int fd;
+ int r;
+ int i;
+ char *record;
+ char ip4[4];
+ char ip6[16];
+ datetime_sec when;
+ datetime_sec start;
+
+ if (chdir(auto_qmail) == -1) die_chdir();
+ if (chdir("queue/lock") == -1) die_chdir();
+
+ fdlock = open_write("tcpto");
+ if (fdlock == -1) die_open();
+ fd = open_read("tcpto");
+ if (fd == -1) die_open();
+ if (lock_ex(fdlock) == -1) die_lock();
+ r = read(fd,tcpto_buf,sizeof(tcpto_buf));
+ close(fd);
+ close(fdlock);
+
+ if (r == -1) die_read();
+ r >>= 5; /* 32 bit read */
+
+ start = now();
+ record = tcpto_buf;
+
+ for (i = 0; i < r; ++i) {
+ if (record[4] >= 1) {
+ when = (unsigned long) (unsigned char) record[11];
+ when = (when << 8) + (unsigned long) (unsigned char) record[10];
+ when = (when << 8) + (unsigned long) (unsigned char) record[9];
+ when = (when << 8) + (unsigned long) (unsigned char) record[8];
+
+ if (record[0] == AF_INET) {
+ byte_copy(&ip4,4,record + 16);
+ buffer_put(buffer_1,tmp,ip4_fmt(tmp,ip4));
+ } else {
+ byte_copy(&ip6,16,record + 16);
+ buffer_put(buffer_1,tmp,ip6_fmt(tmp,ip6));
+ }
+ buffer_puts(buffer_1," timed out ");
+ buffer_put(buffer_1,tmp,fmt_ulong(tmp,(unsigned long) (start - when)));
+ buffer_puts(buffer_1," seconds ago; # recent timeouts: ");
+ buffer_put(buffer_1,tmp,fmt_ulong(tmp,(unsigned long) (unsigned char) record[4]));
+ buffer_puts(buffer_1,"\n");
+ }
+ record += 32;
+ }
+
+ die(0);
+}
diff --git a/src/qmail-todo.c b/src/qmail-todo.c
new file mode 100644
index 0000000..6e19272
--- /dev/null
+++ b/src/qmail-todo.c
@@ -0,0 +1,641 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "alloc.h"
+#include "auto_qmail.h"
+#include "byte.h"
+#include "constmap.h"
+#include "control.h"
+#include "direntry.h"
+#include "error.h"
+#include "exit.h"
+#include "fmt.h"
+#include "fmtqfn.h"
+#include "getln.h"
+#include "open.h"
+#include "ndelay.h"
+#include "now.h"
+#include "readsubdir.h"
+#include "buffer.h"
+#include "scan.h"
+#include "select.h"
+#include "str.h"
+#include "sig.h"
+#include "stralloc.h"
+#include "trigger.h"
+#include "qsutil.h"
+#include "sendtodo.h"
+
+stralloc percenthack = {0};
+struct constmap mappercenthack;
+stralloc locals = {0};
+struct constmap maplocals;
+stralloc vdoms = {0};
+struct constmap mapvdoms;
+stralloc envnoathost = {0};
+
+char strnum[FMT_ULONG];
+
+/* XXX not good, if qmail-send.c changes this has to be updated */
+#define CHANNELS 2
+char *chanaddr[CHANNELS] = { "local/", "remote/" };
+
+datetime_sec recent;
+int flagquitasap = 0;
+
+void sendlog1(char *x);
+void sendlog3(char *x,char *y,char *z);
+
+void sigterm(void)
+{
+ if (flagquitasap == 0)
+ sendlog1("status: qmail-todo stop processing asap\n");
+ flagquitasap = 1;
+}
+
+int flagreadasap = 0; void sighup(void) { flagreadasap = 1; }
+int flagsendalive = 1; void senddied(void) { flagsendalive = 0; }
+
+void cleandied()
+{
+ sendlog1("alert: qmail-todo lost connection to qmail-clean ... exiting\n");
+ flagquitasap = 1;
+}
+
+
+/* this file is not so long ------------------------------------- FILENAMES */
+
+stralloc fn = {0};
+
+void fnmake_init(void)
+{
+ while (!stralloc_ready(&fn,FMTQFN)) nomem();
+}
+
+void fnmake_info(unsigned long id) { fn.len = fmtqfn(fn.s,"info/",id,1); }
+void fnmake_todo(unsigned long id) { fn.len = fmtqfn(fn.s,"todo/",id,1); }
+void fnmake_mess(unsigned long id) { fn.len = fmtqfn(fn.s,"mess/",id,1); }
+void fnmake_chanaddr(unsigned long id,int c) { fn.len = fmtqfn(fn.s,chanaddr[c],id,1); }
+
+
+/* this file is not so long ------------------------------------- REWRITING */
+
+stralloc rwline = {0};
+
+/* 1 if by land, 2 if by sea, 0 if out of memory. not allowed to barf. */
+/* may trash recip. must set up rwline, between a T and a \0. */
+
+int rewrite(char *recip)
+{
+ int i;
+ int j;
+ char *x;
+ static stralloc addr = {0};
+ int at;
+
+ if (!stralloc_copys(&rwline,"T")) return 0;
+ if (!stralloc_copys(&addr,recip)) return 0;
+
+ i = byte_rchr(addr.s,addr.len,'@');
+ if (i == addr.len) {
+ if (!stralloc_cats(&addr,"@")) return 0;
+ if (!stralloc_cat(&addr,&envnoathost)) return 0;
+ }
+
+ while (constmap(&mappercenthack,addr.s + i + 1,addr.len - i - 1)) {
+ j = byte_rchr(addr.s,i,'%');
+ if (j == i) break;
+ addr.len = i;
+ i = j;
+ addr.s[i] = '@';
+ }
+
+ at = byte_rchr(addr.s,addr.len,'@');
+
+ if (constmap(&maplocals,addr.s + at + 1,addr.len - at - 1)) {
+ if (!stralloc_cat(&rwline,&addr)) return 0;
+ if (!stralloc_0(&rwline)) return 0;
+ return 1;
+ }
+
+ for (i = 0; i <= addr.len; ++i)
+ if (!i || (i == at + 1) || (i == addr.len) || ((i > at) && (addr.s[i] == '.')))
+ if ((x = constmap(&mapvdoms,addr.s + i,addr.len - i))) {
+ if (!*x) break;
+ if (!stralloc_cats(&rwline,x)) return 0;
+ if (!stralloc_cats(&rwline,"-")) return 0;
+ if (!stralloc_cat(&rwline,&addr)) return 0;
+ if (!stralloc_0(&rwline)) return 0;
+ return 1;
+ }
+
+ if (!stralloc_cat(&rwline,&addr)) return 0;
+ if (!stralloc_0(&rwline)) return 0;
+ return 2;
+}
+
+/* this file is not so long --------------------------------- COMMUNICATION */
+
+buffer toqc; char toqcbuf[1024];
+buffer fromqc; char fromqcbuf[1024];
+stralloc comm_buf = {0};
+int comm_pos;
+int fdout = -1;
+int fdin = -1;
+
+void sendlog1(char* x)
+{
+ int pos;
+
+ pos = comm_buf.len;
+ if (!stralloc_cats(&comm_buf,"L")) goto FAIL;
+ if (!stralloc_cats(&comm_buf,x)) goto FAIL;
+ if (!stralloc_0(&comm_buf)) goto FAIL;
+ return;
+
+ FAIL:
+ /* either all or nothing */
+ comm_buf.len = pos;
+}
+
+void sendlog3(char* x, char *y, char *z)
+{
+ int pos;
+
+ pos = comm_buf.len;
+ if (!stralloc_cats(&comm_buf,"L")) goto FAIL;
+ if (!stralloc_cats(&comm_buf,x)) goto FAIL;
+ if (!stralloc_cats(&comm_buf,y)) goto FAIL;
+ if (!stralloc_cats(&comm_buf,z)) goto FAIL;
+ if (!stralloc_0(&comm_buf)) goto FAIL;
+ return;
+
+ FAIL:
+ /* either all or nothing */
+ comm_buf.len = pos;
+}
+
+void comm_init(void)
+{
+ buffer_init(&toqc,write,2,toqcbuf,sizeof(toqcbuf));
+ buffer_init(&fromqc,read,3,fromqcbuf,sizeof(fromqcbuf));
+
+ fdout = 1; /* stdout */
+ fdin = 0; /* stdin */
+ if (ndelay_on(fdout) == -1)
+ /* this is so stupid: NDELAY semantics should be default on write */
+ senddied(); /* drastic, but better than risking deadlock */
+
+ while (!stralloc_ready(&comm_buf,1024)) nomem();
+}
+
+int comm_canwrite(void)
+{
+ /* XXX: could allow a bigger buffer; say 10 recipients */
+ /* XXX: returns true if there is something in the buffer */
+ if (!flagsendalive) return 0;
+ if (comm_buf.s && comm_buf.len) return 1;
+ return 0;
+}
+
+void comm_write(unsigned long id, int local, int remote)
+{
+ int pos;
+ char *s;
+
+ if (local && remote) s="B";
+ else if (local) s="L";
+ else if (remote) s="R";
+ else s="X";
+
+ pos = comm_buf.len;
+ strnum[fmt_ulong(strnum,id)] = 0;
+ if (!stralloc_cats(&comm_buf,"D")) goto FAIL;
+ if (!stralloc_cats(&comm_buf,s)) goto FAIL;
+ if (!stralloc_cats(&comm_buf,strnum)) goto FAIL;
+ if (!stralloc_0(&comm_buf)) goto FAIL;
+ return;
+
+ FAIL:
+ /* either all or nothing */
+ comm_buf.len = pos;
+}
+
+void comm_info(unsigned long id, unsigned long size, char* from, unsigned long pid, unsigned long uid)
+{
+ int pos;
+ int i;
+
+ pos = comm_buf.len;
+ if (!stralloc_cats(&comm_buf,"Linfo msg ")) goto FAIL;
+ strnum[fmt_ulong(strnum,id)] = 0;
+ if (!stralloc_cats(&comm_buf,strnum)) goto FAIL;
+ if (!stralloc_cats(&comm_buf,": bytes ")) goto FAIL;
+ strnum[fmt_ulong(strnum,size)] = 0;
+ if (!stralloc_cats(&comm_buf,strnum)) goto FAIL;
+ if (!stralloc_cats(&comm_buf," from <")) goto FAIL;
+ i = comm_buf.len;
+ if (!stralloc_cats(&comm_buf,from)) goto FAIL;
+
+ for (; i < comm_buf.len; ++i)
+ if (comm_buf.s[i] == '\n')
+ comm_buf.s[i] = '/';
+ else
+ if (!issafe(comm_buf.s[i]))
+ comm_buf.s[i] = '_';
+
+ if (!stralloc_cats(&comm_buf,"> qp ")) goto FAIL;
+ strnum[fmt_ulong(strnum,pid)] = 0;
+ if (!stralloc_cats(&comm_buf,strnum)) goto FAIL;
+ if (!stralloc_cats(&comm_buf," uid ")) goto FAIL;
+ strnum[fmt_ulong(strnum,uid)] = 0;
+ if (!stralloc_cats(&comm_buf,strnum)) goto FAIL;
+ if (!stralloc_cats(&comm_buf,"\n")) goto FAIL;
+ if (!stralloc_0(&comm_buf)) goto FAIL;
+ return;
+
+ FAIL:
+ /* either all or nothing */
+ comm_buf.len = pos;
+}
+
+void comm_exit(void)
+{
+ /* if it FAILs exit, we have already stoped */
+ if (!stralloc_cats(&comm_buf,"X")) _exit(1);
+ if (!stralloc_0(&comm_buf)) _exit(1);
+}
+
+void comm_selprep(int *nfds, fd_set *wfds, fd_set *rfds)
+{
+ if (flagsendalive) {
+ if (flagquitasap && comm_canwrite() == 0)
+ comm_exit();
+ if (comm_canwrite()) {
+ FD_SET(fdout,wfds);
+ if (*nfds <= fdout)
+ *nfds = fdout + 1;
+ }
+ FD_SET(fdin,rfds);
+ if (*nfds <= fdin)
+ *nfds = fdin + 1;
+ }
+}
+
+void comm_do(fd_set *wfds, fd_set *rfds)
+{
+ /* first write then read */
+ if (flagsendalive)
+ if (comm_canwrite())
+ if (FD_ISSET(fdout,wfds)) {
+ int w;
+ int len;
+ len = comm_buf.len;
+ w = write(fdout,comm_buf.s + comm_pos,len - comm_pos);
+ if (w <= 0) {
+ if ((w == -1) && (errno == EPIPE))
+ senddied();
+ } else {
+ comm_pos += w;
+ if (comm_pos == len) {
+ comm_buf.len = 0;
+ comm_pos = 0;
+ }
+ }
+ }
+ if (flagsendalive)
+ if (FD_ISSET(fdin,rfds)) {
+ /* there are only two messages 'H' and 'X' */
+ char c;
+ int r;
+ r = read(fdin, &c, 1);
+ if (r <= 0) {
+ if ((r == -1) && (errno != EINTR))
+ senddied();
+ } else {
+ switch (c) {
+ case 'H':
+ sighup();
+ break;
+ case 'X':
+ sigterm();
+ break;
+ default:
+ sendlog1("warning: qmail-todo: qmail-send speaks an obscure dialect\n");
+ break;
+ }
+ }
+ }
+}
+
+/* this file is not so long ------------------------------------------ TODO */
+
+datetime_sec nexttodorun;
+int flagtododir; /* if 0, have to opendir again */
+readsubdir todosubdir;
+stralloc todoline = {0};
+char todobuf[BUFFER_INSIZE];
+char todobufinfo[512];
+char todobufchan[CHANNELS][1024];
+
+void todo_init(void)
+{
+ flagtododir = 0;
+ nexttodorun = now();
+ trigger_set();
+}
+
+void todo_selprep(int *nfds, fd_set *rfds, datetime_sec *wakeup)
+{
+ if (flagquitasap) return;
+ trigger_selprep(nfds,rfds);
+ if (flagtododir) *wakeup = 0;
+ if (*wakeup > nexttodorun) *wakeup = nexttodorun;
+}
+
+void todo_do(fd_set *rfds)
+{
+ struct stat st;
+ buffer bi;
+ int fd;
+ buffer bo;
+ int fdnumber;
+ buffer bchan[CHANNELS];
+ int fdchan[CHANNELS];
+ int flagchan[CHANNELS];
+ char ch;
+ int match;
+ unsigned long id;
+ int c;
+ unsigned long uid;
+ unsigned long pid;
+
+ fd = -1;
+ fdnumber = -1;
+ for (c = 0; c < CHANNELS; ++c)
+ fdchan[c] = -1;
+
+ if (flagquitasap) return;
+
+ if (!flagtododir) {
+ if (!trigger_pulled(rfds)) {
+ if (recent < nexttodorun) return;
+ }
+ trigger_set();
+ readsubdir_init(&todosubdir,"todo",pausedir);
+ flagtododir = 1;
+ nexttodorun = recent + SLEEP_TODO;
+ }
+
+ switch (readsubdir_next(&todosubdir,&id)) {
+ case 1: break;
+ case 0: flagtododir = 0;
+ default: return;
+ }
+
+ fnmake_todo(id);
+
+ fd = open_read(fn.s);
+ if (fd == -1) { sendlog3("warning: qmail-todo: unable to open ",fn.s,"\n"); return; }
+
+ fnmake_mess(id);
+ /* just for the statistics */
+ if (stat(fn.s,&st) == -1)
+ { sendlog3("warning: qmail-todo: unable to stat ",fn.s," for mess\n"); goto FAIL; }
+
+ for (c = 0; c < CHANNELS; ++c) {
+ fnmake_chanaddr(id,c);
+ if (unlink(fn.s) == -1) if (errno != ENOENT)
+ { sendlog3("warning: qmail-todo: unable to unlink ",fn.s," for mess\n"); goto FAIL; }
+ }
+
+ fnmake_info(id);
+ if (unlink(fn.s) == -1) if (errno != ENOENT)
+ { sendlog3("warning: qmail-todo: unable to unlink ",fn.s," for info\n"); goto FAIL; }
+
+ fdnumber = open_excl(fn.s);
+ if (fdnumber == -1)
+ { sendlog3("warning: qmail-todo: unable to create ",fn.s," for info\n"); goto FAIL; }
+
+ strnum[fmt_ulong(strnum,id)] = 0;
+ sendlog3("new msg ",strnum,"\n");
+
+ for (c = 0; c < CHANNELS; ++c)
+ flagchan[c] = 0;
+
+ buffer_init(&bi,read,fd,todobuf,sizeof(todobuf));
+ buffer_init(&bo,write,fdnumber,todobufinfo,sizeof(todobufinfo));
+
+ uid = 0;
+ pid = 0;
+
+ for (;;) {
+ if (getln(&bi,&todoline,&match,'\0') == -1) {
+ /* perhaps we're out of memory, perhaps an I/O error */
+ fnmake_todo(id);
+ sendlog3("warning: qmail-todo: trouble reading ",fn.s,"\n"); goto FAIL;
+ }
+ if (!match) break;
+
+ switch (todoline.s[0]) {
+ case 'u':
+ scan_ulong(todoline.s + 1,&uid); break;
+ case 'p':
+ scan_ulong(todoline.s + 1,&pid); break;
+ case 'F':
+ if (buffer_putflush(&bo,todoline.s,todoline.len) == -1) {
+ fnmake_info(id);
+ sendlog3("warning: qmail-todo: trouble writing to ",fn.s," for todo\n"); goto FAIL;
+ }
+ comm_info(id,(unsigned long) st.st_size,todoline.s + 1,pid,uid);
+ break;
+ case 'T':
+ switch (rewrite(todoline.s + 1)) {
+ case 0: nomem(); goto FAIL;
+ case 2: c = 1; break;
+ default: c = 0; break;
+ }
+ if (fdchan[c] == -1) {
+ fnmake_chanaddr(id,c);
+ fdchan[c] = open_excl(fn.s);
+ if (fdchan[c] == -1)
+ { sendlog3("warning: qmail-todo: unable to create ",fn.s," for delivery\n"); goto FAIL; }
+ buffer_init(&bchan[c],write,fdchan[c],todobufchan[c],sizeof(todobufchan[c]));
+ flagchan[c] = 1;
+ }
+ if (buffer_put(&bchan[c],rwline.s,rwline.len) == -1) {
+ fnmake_chanaddr(id,c);
+ sendlog3("warning: qmail-todo: trouble writing to ",fn.s," for delivery\n"); goto FAIL;
+ }
+ break;
+ default:
+ fnmake_todo(id);
+ sendlog3("warning: qmail-todo: unknown record type in ",fn.s,"\n"); goto FAIL;
+ }
+ }
+
+ close(fd); fd = -1;
+
+ fnmake_info(id);
+ if (buffer_flush(&bo) == -1)
+ { sendlog3("warning: qmail-todo: trouble writing to ",fn.s," for info\n"); goto FAIL; }
+ if (fsync(fdnumber) == -1)
+ { sendlog3("warning: qmail-todo: trouble fsyncing ",fn.s," for info\n"); goto FAIL; }
+ close(fdnumber); fdnumber = -1;
+
+ for (c = 0; c < CHANNELS; ++c)
+ if (fdchan[c] != -1) {
+ fnmake_chanaddr(id,c);
+ if (buffer_flush(&bchan[c]) == -1) { sendlog3("warning: qmail-todo: trouble writing to ",fn.s," in channel\n"); goto FAIL; }
+ if (fsync(fdchan[c]) == -1) { sendlog3("warning: qmail-todo: trouble fsyncing ",fn.s," in channel\n"); goto FAIL; }
+ close(fdchan[c]); fdchan[c] = -1;
+ }
+
+ fnmake_todo(id);
+ if (buffer_putflush(&toqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (buffer_get(&fromqc,&ch,1) != 1) { cleandied(); return; }
+
+ if (ch != '+') {
+ sendlog3("warning: qmail-clean unable to clean up ",fn.s,"\n");
+ return;
+ }
+
+ comm_write(id,flagchan[0],flagchan[1]);
+ return;
+
+ FAIL:
+ if (fd != -1) close(fd);
+ if (fdnumber != -1) close(fdnumber);
+ for (c = 0; c < CHANNELS; ++c)
+ if (fdchan[c] != -1) close(fdchan[c]);
+}
+
+/* this file is too long ---------------------------------------------- MAIN */
+
+int getcontrols(void)
+{
+ if (control_init() == -1) return 0;
+ if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) return 0;
+ if (control_readfile(&locals,"control/locals",1) != 1) return 0;
+ if (!constmap_init(&maplocals,locals.s,locals.len,0)) return 0;
+ switch (control_readfile(&percenthack,"control/percenthack",0)) {
+ case -1: return 0;
+ case 0: if (!constmap_init(&mappercenthack,"",0,0)) return 0; break;
+ case 1: if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) return 0; break;
+ }
+ switch (control_readfile(&vdoms,"control/virtualdomains",0)) {
+ case -1: return 0;
+ case 0: if (!constmap_init(&mapvdoms,"",0,1)) return 0; break;
+ case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) return 0; break;
+ }
+ return 1;
+}
+
+stralloc newlocals = {0};
+stralloc newvdoms = {0};
+
+void regetcontrols(void)
+{
+ int r;
+
+ if (control_readfile(&newlocals,"control/locals",1) != 1)
+ { sendlog1("alert: qmail-todo: unable to reread control/locals\n"); return; }
+ r = control_readfile(&newvdoms,"control/virtualdomains",0);
+ if (r == -1)
+ { sendlog1("alert: qmail-todo: unable to reread control/virtualdomains\n"); return; }
+
+ constmap_free(&maplocals);
+ constmap_free(&mapvdoms);
+
+ while (!stralloc_copy(&locals,&newlocals)) nomem();
+ while (!constmap_init(&maplocals,locals.s,locals.len,0)) nomem();
+
+ if (r) {
+ while (!stralloc_copy(&vdoms,&newvdoms)) nomem();
+ while (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) nomem();
+ }
+ else
+ while (!constmap_init(&mapvdoms,"",0,1)) nomem();
+}
+
+void reread(void)
+{
+ if (chdir(auto_qmail) == -1) {
+ sendlog1("alert: qmail-todo: unable to reread controls: unable to switch to home directory\n");
+ return;
+ }
+
+ regetcontrols();
+ while (chdir("queue") == -1) {
+ sendlog1("alert: qmail-todo: unable to switch back to queue directory; HELP! sleeping...\n");
+ sleep(10);
+ }
+}
+
+int main()
+{
+ datetime_sec wakeup;
+ fd_set rfds;
+ fd_set wfds;
+ int nfds;
+ struct timeval tv;
+ int r;
+ char c;
+
+ if (chdir(auto_qmail) == -1)
+ { sendlog1("alert: qmail-todo: cannot start: unable to switch to home directory\n"); _exit(110); }
+ if (!getcontrols())
+ { sendlog1("alert: qmail-todo: cannot start: unable to read controls\n"); _exit(112); }
+ if (chdir("queue") == -1)
+ { sendlog1("alert: qmail-todo: cannot start: unable to switch to queue directory\n"); _exit(110); }
+ sig_pipeignore();
+ umask(077);
+
+ fnmake_init();
+ todo_init();
+ comm_init();
+
+ do {
+ r = read(fdin, &c, 1);
+ if ((r == -1) && (errno != EINTR))
+ _exit(100); /* read failed probably qmail-send died */
+ } while ((r =! 1)); /* we assume it is a 'S' */
+
+ for (;;) {
+ recent = now();
+ if (flagreadasap) { flagreadasap = 0; reread(); }
+ if (!flagsendalive) {
+ /* qmail-send finaly exited, so do the same. */
+ if (flagquitasap) _exit(0);
+ /* qmail-send died. We can not log and we can not work therefor _exit(1). */
+ _exit(1);
+ }
+
+ wakeup = recent + SLEEP_FOREVER;
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ nfds = 1;
+
+ todo_selprep(&nfds,&rfds,&wakeup);
+ comm_selprep(&nfds,&wfds,&rfds);
+
+ if (wakeup <= recent) tv.tv_sec = 0;
+ else tv.tv_sec = wakeup - recent + SLEEP_FUZZ;
+ tv.tv_usec = 0;
+
+ if (select(nfds,&rfds,&wfds,(fd_set *) 0,&tv) == -1)
+ if (errno == EINTR)
+ ;
+ else
+ sendlog1("warning: qmail-todo: trouble in select\n");
+ else {
+ recent = now();
+
+ todo_do(&rfds);
+ comm_do(&wfds, &rfds);
+ }
+ }
+ /* NOTREACHED */
+ _exit(1);
+}
+
diff --git a/src/qmail-upq.sh b/src/qmail-upq.sh
new file mode 100755
index 0000000..f0c5dfc
--- /dev/null
+++ b/src/qmail-upq.sh
@@ -0,0 +1,14 @@
+cd QMAIL
+cd queue
+for dir in mess info local remote todo
+do
+ ( cd $dir; find . -type f -print ) | (
+ cd $dir
+ while read path
+ do
+ id=`basename "$path"`
+ sub=`expr "$id" % SPLIT`
+ mv "$path" "$sub"/"$id"
+ done
+ )
+done
diff --git a/src/qmail-vmailuser.c b/src/qmail-vmailuser.c
new file mode 100644
index 0000000..27dc85d
--- /dev/null
+++ b/src/qmail-vmailuser.c
@@ -0,0 +1,148 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "global.h"
+#include "auto_qmail.h"
+#include "stralloc.h"
+#include "case.h"
+#include "control.h"
+#include "constmap.h"
+#include "direntry.h"
+#include "error.h"
+#include "str.h"
+#include "fmt.h"
+#include "open.h"
+#include "byte.h"
+#include "scan.h"
+#include "str.h"
+
+#define FDAUTH 3
+#define RESPECT_CASE "-C"
+#define BUFFER_SIZE 128
+
+/** @file qmail-vmailuser.c
+ @return 0: virtual user exists
+ 1: virtual user dir not accessible
+ 2: qmail-vmailuser is misused
+ 110: can't read controls
+ 111: temporary problem
+*/
+
+char inputbuf[BUFFER_SIZE];
+struct constmap mapvdoms;
+stralloc vdoms = {0};
+stralloc vdomdir = {0};
+stralloc vuser = {0};
+stralloc vuserdir = {0};
+
+void pam_exit(int fail)
+{
+ int i;
+
+ close(FDAUTH);
+ for (i = 0; i < sizeof(inputbuf); ++i) inputbuf[i] = 0;
+ _exit(fail);
+}
+
+int main(int argc,char **argv)
+{
+ DIR *dir;
+ char *vdomuser;
+ char *domain = 0;
+ int buflen = 0;
+ int domlen = 0;
+ int flagrespect = 0;
+ int i, r;
+ char ch;
+ char *homedir = "/home";
+
+ if (argv[1])
+ if (!case_diffs(argv[1],RESPECT_CASE)) {
+ flagrespect = 1;
+ } else {
+ homedir = argv[1];
+ dir = opendir(homedir);
+ if (!dir) pam_exit(2);
+ }
+
+ if (argv[2])
+ if (!case_diffs(argv[2],RESPECT_CASE)) flagrespect = 1;
+
+ if (chdir(auto_qmail) == -1) pam_exit(110);
+
+ switch (control_readfile(&vdoms,"control/virtualdomains",0)) {
+ case -1: pam_exit(110);
+ case 0: if (!constmap_init(&mapvdoms,"",0,1)) pam_exit(111);
+ case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) pam_exit(111);
+ }
+
+ for (;;) { /* read input */
+ do
+ r = read(FDAUTH,inputbuf + buflen,sizeof(inputbuf) - buflen);
+ while ((r == -1) && (errno == EINTR));
+ if (r == -1) pam_exit(111);
+ if (r == 0) break;
+ buflen += r;
+ if (buflen >= sizeof(inputbuf)) pam_exit(2);
+ }
+ close(FDAUTH);
+
+ if ((r = byte_rchr(inputbuf,buflen,'@'))) /* @domain */
+ if (r < buflen && inputbuf[r] == '@') {
+ domain = inputbuf + r + 1;
+ domlen = str_len(domain);
+ if (!flagrespect)
+ case_lowerb(inputbuf,buflen);
+ else
+ case_lowerb(domain,domlen);
+ }
+ vdomuser = constmap(&mapvdoms,domain,domlen);
+ if (!vdomuser) pam_exit(1);
+
+ if (!stralloc_copys(&vuser,"")) pam_exit(111); /* user */
+ for (i = 0; i < r; ++i) {
+ ch = inputbuf[i];
+ if (ch == '.') ch = ':';
+ if (!stralloc_append(&vuser,&ch)) pam_exit(111);
+ }
+ if (!stralloc_0(&vuser)) pam_exit(111);
+
+ if (!stralloc_copys(&vdomdir,homedir)) pam_exit(111); /* vpopmail */
+ if (!stralloc_cats(&vdomdir,"/")) pam_exit(111);
+ if (!stralloc_cats(&vdomdir,"vpopmail")) pam_exit(111);
+ if (!stralloc_copy(&vuserdir,&vdomdir)) pam_exit(111);
+ if (!stralloc_cats(&vuserdir,"/domains/")) pam_exit(111);
+ if (!stralloc_cats(&vuserdir,vdomuser)) pam_exit(111);
+ if (!stralloc_copy(&vdomdir,&vuserdir)) pam_exit(111);
+ if (!stralloc_0(&vdomdir)) pam_exit(111);
+
+ dir = opendir(vdomdir.s);
+ if (dir) {
+ if (!stralloc_cats(&vuserdir,"/")) pam_exit(111);
+ if (!stralloc_cat(&vuserdir,&vuser)) pam_exit(111);
+ if (!stralloc_0(&vuserdir)) pam_exit(111);
+
+ dir = opendir(vuserdir.s);
+ if (dir) pam_exit(0);
+ }
+
+ if (!stralloc_copys(&vdomdir,homedir)) pam_exit(111); /* vmailmgr */
+ if (!stralloc_cats(&vdomdir,"/")) pam_exit(111);
+ if (!stralloc_copy(&vuserdir,&vdomdir)) pam_exit(111);
+ if (!stralloc_cats(&vuserdir,vdomuser)) pam_exit(111);
+ if (!stralloc_cats(&vuserdir,"/users")) pam_exit(111);
+ if (!stralloc_copy(&vdomdir,&vuserdir)) pam_exit(111);
+ if (!stralloc_0(&vdomdir)) pam_exit(111);
+
+ dir = opendir(vdomdir.s);
+ if (dir) {
+ if (!stralloc_cats(&vuserdir,"/")) pam_exit(111);
+ if (!stralloc_cat(&vuserdir,&vuser)) pam_exit(111);
+ if (!stralloc_0(&vuserdir)) pam_exit(111);
+
+ dir = opendir(vuserdir.s);
+ if (dir) pam_exit(0);
+ }
+
+ pam_exit(1);
+}
diff --git a/src/qmail.c b/src/qmail.c
new file mode 100644
index 0000000..7bdfd29
--- /dev/null
+++ b/src/qmail.c
@@ -0,0 +1,139 @@
+#include <unistd.h>
+#include "readwrite.h"
+#include "buffer.h"
+#include "wait.h"
+#include "exit.h"
+#include "fd.h"
+#include "qmail.h"
+#include "auto_qmail.h"
+#include "env.h"
+
+static char *binqqargs[2] = { 0, 0 } ;
+
+static void setup_qqargs()
+{
+ if (!binqqargs[0])
+ binqqargs[0] = env_get("QMAILQUEUE");
+ if (!binqqargs[0])
+ binqqargs[0] = "bin/qmail-queue";
+}
+
+int qmail_open(struct qmail *qq)
+{
+ int pim[2];
+ int pie[2];
+
+ setup_qqargs();
+
+ if (pipe(pim) == -1) return -1;
+ if (pipe(pie) == -1) { close(pim[0]); close(pim[1]); return -1; }
+
+ switch (qq->pid = vfork()) {
+ case -1:
+ close(pim[0]); close(pim[1]);
+ close(pie[0]); close(pie[1]);
+ return -1;
+ case 0:
+ close(pim[1]);
+ close(pie[1]);
+ if (fd_move(0,pim[0]) == -1) _exit(120);
+ if (fd_move(1,pie[0]) == -1) _exit(120);
+ if (chdir(auto_qmail) == -1) _exit(61);
+ execv(*binqqargs,binqqargs);
+ _exit(120);
+ }
+
+ qq->fdm = pim[1]; close(pim[0]);
+ qq->fde = pie[1]; close(pie[0]);
+ buffer_init(&qq->ss,write,qq->fdm,qq->buf,sizeof(qq->buf));
+ qq->flagerr = 0;
+ return 0;
+}
+
+unsigned long qmail_qp(struct qmail *qq)
+{
+ return qq->pid;
+}
+
+void qmail_fail(struct qmail *qq)
+{
+ qq->flagerr = 1;
+}
+
+void qmail_put(struct qmail *qq,char *s,int len)
+{
+ if (!qq->flagerr) if (buffer_put(&qq->ss,s,len) == -1) qq->flagerr = 1;
+}
+
+void qmail_puts(struct qmail *qq,char *s)
+{
+ if (!qq->flagerr) if (buffer_puts(&qq->ss,s) == -1) qq->flagerr = 1;
+}
+
+void qmail_from(struct qmail *qq,char *s)
+{
+ if (buffer_flush(&qq->ss) == -1) qq->flagerr = 1;
+ close(qq->fdm);
+ buffer_init(&qq->ss,write,qq->fde,qq->buf,sizeof(qq->buf));
+ qmail_put(qq,"F",1);
+ qmail_puts(qq,s);
+ qmail_put(qq,"",1);
+}
+
+void qmail_to(struct qmail *qq,char *s)
+{
+ qmail_put(qq,"T",1);
+ qmail_puts(qq,s);
+ qmail_put(qq,"",1);
+}
+
+char *qmail_close(struct qmail *qq)
+{
+ int wstat;
+ int exitcode;
+
+ qmail_put(qq,"",1);
+ if (!qq->flagerr)
+ if (buffer_flush(&qq->ss) == -1) qq->flagerr = 1;
+ close(qq->fde);
+
+ if (wait_pid(&wstat,qq->pid) != qq->pid)
+ return "Zqq waitpid surprise (#4.3.0)";
+ if (wait_crashed(wstat))
+ return "Zqq crashed (#4.3.0)";
+ exitcode = wait_exitcode(wstat);
+
+ switch (exitcode) {
+ case 0: if (!qq->flagerr) return ""; /* fall through */
+ case 11: return "Denvelope address too long for qq (#5.1.3)";
+ case 31: return "Dmail server permanently rejected message (#5.3.0)";
+ case 32: return "Vmail server does not accept message (#5.3.0)";
+ case 33: return "Smail server does not accept message (#5.3.0)";
+ case 34: return "Amail server does not accept message (#5.3.0)";
+ case 35: return "Imail server fails to verify DKIM signed message (#5.3.0)";
+ case 51: return "Zqq out of memory (#4.3.0)";
+ case 52: return "Zqq timeout (#4.3.0)";
+ case 53: return "Zqq write error or disk full (#4.3.0)";
+ case 54: return "Zqq read error (#4.3.0)";
+ case 55: return "Zqq unable to read configuration (#4.3.0)";
+ case 56: return "Zqq trouble making network connection (#4.3.0)";
+ case 61: return "Zqq trouble in home directory (#4.3.0)";
+ case 62: return "Zqq trouble creating files in queue (#4.3.0)";
+ case 63: /* qmail-queue: fstat/unlinking problem */
+ case 64: /* qmail-queue: linking pidfn -> messfn */
+ case 65: /* qmail-queue: exclusive open failed */
+ case 66: /* qmail-queue: linking intdfn -> todofn */
+ case 71: return "Zmail server temporarily rejected message (#4.3.0)";
+ case 72: return "Zconnection to mail server timed out (#4.4.1)";
+ case 73: return "Zconnection to mail server rejected (#4.4.1)";
+ case 74: return "Zcommunication with mail server failed (#4.4.2)";
+ case 91: /* fall through */
+ case 81: return "Zqq internal bug (#4.3.0)";
+ case 115: /* compatibility */
+ case 120: return "Zunable to exec qq (#4.3.0)";
+ default:
+ if ((exitcode >= 11) && (exitcode <= 40))
+ return "Dqq permanent problem (#5.3.0)";
+ return "Zqq temporary problem (#4.3.0)";
+ }
+}
diff --git a/src/qreceipt.c b/src/qreceipt.c
new file mode 100644
index 0000000..8dacf40
--- /dev/null
+++ b/src/qreceipt.c
@@ -0,0 +1,130 @@
+#include <unistd.h>
+#include "sig.h"
+#include "env.h"
+#include "error.h"
+#include "buffer.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "alloc.h"
+#include "str.h"
+#include "hfield.h"
+#include "token822.h"
+#include "headerbody.h"
+#include "exit.h"
+#include "open.h"
+#include "quote.h"
+#include "qmail.h"
+
+#define WHO "qreceipt"
+
+void die_noreceipt() { _exit(0); }
+void die() { _exit(100); }
+void die_temp() { _exit(111); }
+void die_nomem() {
+ buffer_putsflush(buffer_2,"qreceipt: fatal: out of memory\n"); die_temp(); }
+void die_fork() {
+ buffer_putsflush(buffer_2,"qreceipt: fatal: unable to fork\n"); die_temp(); }
+void die_qqperm() {
+ buffer_putsflush(buffer_2,"qreceipt: fatal: permanent qmail-queue error\n"); die(); }
+void die_qqtemp() {
+ buffer_putsflush(buffer_2,"qreceipt: fatal: temporary qmail-queue error\n"); die_temp(); }
+void die_usage() {
+ buffer_putsflush(buffer_2,
+ "qreceipt: usage: qreceipt deliveryaddress\n"); die(); }
+void die_read() {
+ if (errno == ENOMEM) die_nomem();
+ buffer_putsflush(buffer_2,"qreceipt: fatal: read error\n"); die_temp(); }
+void doordie(sa,r) stralloc *sa; int r; {
+ if (r == 1) return; if (r == -1) die_nomem();
+ buffer_putsflush(buffer_2,"qreceipt: fatal: unable to parse this: ");
+ buffer_putflush(buffer_2,sa->s,sa->len); die(); }
+
+char *target;
+
+int flagreceipt = 0;
+
+char *returnpath;
+stralloc messageid = {0};
+stralloc sanotice = {0};
+
+int rwnotice(token822_alloc *addr)
+{
+ token822_reverse(addr);
+ if (token822_unquote(&sanotice,addr) != 1) die_nomem();
+ if (sanotice.len == str_len(target))
+ if (!str_diffn(sanotice.s,target,sanotice.len))
+ flagreceipt = 1;
+ token822_reverse(addr);
+ return 1;
+}
+
+struct qmail qqt;
+
+stralloc quoted = {0};
+
+void finishheader()
+{
+ char *qqx;
+
+ if (!flagreceipt) die_noreceipt();
+ if (str_equal(returnpath,"")) die_noreceipt();
+ if (str_equal(returnpath,"#@[]")) die_noreceipt();
+
+ if (!quote2(&quoted,returnpath)) die_nomem();
+
+ if (qmail_open(&qqt) == -1) die_fork();
+
+ qmail_puts(&qqt,"From: DELIVERY NOTICE SYSTEM <");
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,">\n");
+ qmail_puts(&qqt,"To: <");
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,">\n");
+ qmail_puts(&qqt,"Subject: success notice\n\
+\n\
+Hi! This is the qreceipt program. Your message was delivered to the\n\
+following address: ");
+ qmail_puts(&qqt,target);
+ qmail_puts(&qqt,". Thanks for asking.\n");
+ if (messageid.s) {
+ qmail_puts(&qqt,"Your ");
+ qmail_put(&qqt,messageid.s,messageid.len);
+ }
+
+ qmail_from(&qqt,"");
+ qmail_to(&qqt,returnpath);
+ qqx = qmail_close(&qqt);
+
+ if (*qqx)
+ if (*qqx == 'D') die_qqperm();
+ else die_qqtemp();
+}
+
+stralloc hfbuf = {0};
+token822_alloc hfin = {0};
+token822_alloc hfrewrite = {0};
+token822_alloc hfaddr = {0};
+
+void doheaderfield(stralloc *h)
+{
+ switch (hfield_known(h->s,h->len)) {
+ case H_MESSAGEID:
+ if (!stralloc_copy(&messageid,h)) die_nomem();
+ break;
+ case H_NOTICEREQUESTEDUPONDELIVERYTO:
+ doordie(h,token822_parse(&hfin,h,&hfbuf));
+ doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwnotice));
+ break;
+ }
+}
+
+void dobody(stralloc *h) { ; }
+
+int main(int argc, char **argv)
+{
+ sig_pipeignore();
+ if (!(target = argv[1])) die_usage();
+ if (!(returnpath = env_get("SENDER"))) die_usage();
+ if (headerbody(buffer_0,doheaderfield,finishheader,dobody) == -1) die_read();
+ die_noreceipt();
+}
diff --git a/src/qsutil.c b/src/qsutil.c
new file mode 100644
index 0000000..9c438ea
--- /dev/null
+++ b/src/qsutil.c
@@ -0,0 +1,85 @@
+#include <unistd.h>
+#include "stralloc.h"
+#include "buffer.h"
+#include "qsutil.h"
+
+static stralloc foo = {0};
+
+static char errbuf[1];
+static struct buffer be = BUFFER_INIT(write,0,errbuf,1);
+
+
+void logsa(stralloc *sa)
+{
+ buffer_putflush(&be,sa->s,sa->len);
+}
+
+void log1s(char *s1)
+{
+ buffer_putsflush(&be,s1);
+}
+
+void log2s(char *s1,char *s2)
+{
+ buffer_putsflush(&be,s1);
+ buffer_putsflush(&be,s2);
+}
+
+void log3s(char *s1,char *s2,char *s3)
+{
+ buffer_putsflush(&be,s1);
+ buffer_putsflush(&be,s2);
+ buffer_putsflush(&be,s3);
+}
+
+void log4s(char *s1,char *s2,char *s3,char *s4)
+{
+ buffer_putsflush(&be,s1);
+ buffer_putsflush(&be,s2);
+ buffer_putsflush(&be,s3);
+ buffer_putsflush(&be,s4);
+}
+
+void log5s(char *s1,char *s2,char *s3,char *s4,char *s5)
+{
+ buffer_putsflush(&be,s1);
+ buffer_putsflush(&be,s2);
+ buffer_putsflush(&be,s3);
+ buffer_putsflush(&be,s4);
+ buffer_putsflush(&be,s5);
+}
+
+void nomem()
+{
+ log1s("alert: out of memory, sleeping...\n");
+ sleep(10);
+}
+
+void pausedir(char *dir)
+{
+ log3s("alert: unable to opendir ",dir,", sleeping...\n");
+ sleep(10);
+}
+
+int issafe(char ch)
+{
+ if (ch == '%') return 0; /* general principle: allman's code is crap */
+ if (ch < 33) return 0;
+ if (ch > 126) return 0;
+ return 1;
+}
+
+void logsafe(char *s)
+{
+ int i;
+
+ while (!stralloc_copys(&foo,s)) nomem();
+ for (i = 0; i < foo.len; ++i)
+ if (foo.s[i] == '\n')
+ foo.s[i] = '/';
+ else
+ if (!issafe(foo.s[i]))
+ foo.s[i] = '_';
+
+ logsa(&foo);
+}
diff --git a/src/quote.c b/src/quote.c
new file mode 100644
index 0000000..ef1bf45
--- /dev/null
+++ b/src/quote.c
@@ -0,0 +1,81 @@
+#include "stralloc.h"
+#include "str.h"
+#include "quote.h"
+
+/*
+quote() encodes a box as per rfc 821 and rfc 822,
+while trying to do as little quoting as possible.
+no, 821 and 822 don't have the same encoding. they're not even close.
+no special encoding here for bytes above 127.
+*/
+
+static char ok[128] = {
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+,0,7,0,7,7,7,7,7,0,0,7,7,0,7,7,7 ,7,7,7,7,7,7,7,7,7,7,0,0,0,7,0,7
+,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ,7,7,7,7,7,7,7,7,7,7,7,0,0,0,7,7
+,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0
+} ;
+
+static int doit(stralloc *saout,stralloc *sain)
+{
+ char ch;
+ int i;
+ int j;
+
+ if (!stralloc_ready(saout,sain->len * 2 + 2)) return 0;
+ j = 0;
+ saout->s[j++] = '"';
+
+ for (i = 0; i < sain->len; ++i) {
+ ch = sain->s[i];
+ if ((ch == '\r') || (ch == '\n') || (ch == '"') || (ch == '\\'))
+ saout->s[j++] = '\\';
+ saout->s[j++] = ch;
+ }
+ saout->s[j++] = '"';
+ saout->len = j;
+
+ return 1;
+}
+
+int quote_need(char *s,unsigned int n)
+{
+ unsigned char uch;
+ int i;
+ if (!n) return 1;
+
+ for (i = 0; i < n; ++i) {
+ uch = s[i];
+ if (uch >= 128) return 1;
+ if (!ok[uch]) return 1;
+ }
+ if (s[0] == '.') return 1;
+ if (s[n - 1] == '.') return 1;
+
+ for (i = 0; i < n - 1; ++i)
+ if (s[i] == '.')
+ if (s[i + 1] == '.') return 1;
+
+ return 0;
+}
+
+int quote(stralloc *saout,stralloc *sain)
+{
+ if (quote_need(sain->s,sain->len)) return doit(saout,sain);
+ return stralloc_copy(saout,sain);
+}
+
+static stralloc foo = {0};
+
+int quote2(stralloc *sa,char *s)
+{
+ int j;
+ if (!*s) return stralloc_copys(sa,s);
+ j = str_rchr(s,'@');
+ if (!stralloc_copys(&foo,s)) return 0;
+ if (!s[j]) return quote(sa,&foo);
+ foo.len = j;
+ if (!quote(sa,&foo)) return 0;
+
+ return stralloc_cats(sa,s + j);
+}
diff --git a/src/rcpthosts.c b/src/rcpthosts.c
new file mode 100644
index 0000000..2242249
--- /dev/null
+++ b/src/rcpthosts.c
@@ -0,0 +1,70 @@
+#include "cdbread.h"
+#include "byte.h"
+#include "open.h"
+#include "error.h"
+#include "exit.h"
+#include "control.h"
+#include "constmap.h"
+#include "stralloc.h"
+#include "case.h"
+#include "close.h"
+#include "fd.h"
+#include "rcpthosts.h"
+
+static int flagrh = 0;
+static int flagmrh = 0;
+static stralloc rh = {0};
+static struct constmap maprh;
+static int fdmrh;
+
+static struct cdb cdb;
+
+int rcpthosts_init()
+{
+ flagrh = control_readfile(&rh,"control/rcpthosts",0);
+ if (flagrh != 1) return flagrh;
+ if (!constmap_init(&maprh,rh.s,rh.len,0)) return flagrh = -1;
+ fdmrh = open_read("control/morercpthosts.cdb");
+ if (fdmrh == -1) if (errno != ENOENT) return flagmrh = -1;
+ if (fdmrh > 0) flagmrh = 1;
+ return 0;
+}
+
+static stralloc host = {0};
+
+int rcpthosts(char *buf, int len)
+{
+ int j;
+ int r;
+
+ if (flagrh != 1) return 1;
+
+ j = byte_rchr(buf,len,'@');
+ if (j >= len) return 1; /* presumably envnoathost is acceptable */
+
+ ++j; buf += j; len -= j;
+
+ if (!stralloc_copyb(&host,buf,len)) return -1;
+ buf = host.s;
+ case_lowerb(buf,len);
+
+ for (j = 0; j < len; ++j)
+ if (!j || (buf[j] == '.'))
+ if (constmap(&maprh,buf + j,len - j)) return 1;
+
+ if (flagmrh == 1) {
+ fdmrh = open_read("control/morercpthosts.cdb");
+ if (fdmrh == -1) if (errno == ENOENT) return 0;
+ cdb_init(&cdb,fdmrh);
+
+ for (j = 0; j < len ;++j)
+ if (!j || (buf[j] == '.')) {
+ r = cdb_find(&cdb,buf + j,len - j);
+ if (r) { cdb_free(&cdb); close(fdmrh); return r; }
+ }
+ cdb_free(&cdb);
+ close(fdmrh);
+ }
+
+ return 0;
+}
diff --git a/src/readsubdir.c b/src/readsubdir.c
new file mode 100644
index 0000000..754aa36
--- /dev/null
+++ b/src/readsubdir.c
@@ -0,0 +1,44 @@
+#include "readsubdir.h"
+#include "fmt.h"
+#include "scan.h"
+#include "str.h"
+#include "auto_split.h"
+
+void readsubdir_init(readsubdir *rs, char *name, void (*pause)())
+{
+ rs->name = name;
+ rs->pause = pause;
+ rs->dir = 0;
+ rs->pos = 0;
+}
+
+static char namepos[FMT_ULONG + 4 + READSUBDIR_NAMELEN];
+
+int readsubdir_next(readsubdir *rs, unsigned long *id)
+{
+ direntry *d;
+ unsigned int len;
+
+ if (!rs->dir) {
+ if (rs->pos >= auto_split) return 0;
+ if (str_len(rs->name) > READSUBDIR_NAMELEN) { rs->pos++; return -1; }
+ len = 0;
+ len += fmt_str(namepos + len,rs->name);
+ namepos[len++] = '/';
+ len += fmt_ulong(namepos + len,(unsigned long) rs->pos);
+ namepos[len] = 0;
+ while (!(rs->dir = opendir(namepos))) rs->pause(namepos);
+ rs->pos++;
+ return -1;
+ }
+
+ d = readdir(rs->dir);
+ if (!d) { closedir(rs->dir); rs->dir = 0; return -1; }
+
+ if (str_equal(d->d_name,".")) return -1;
+ if (str_equal(d->d_name,"..")) return -1;
+ len = scan_ulong(d->d_name,id);
+ if (!len || d->d_name[len]) return -2;
+
+ return 1;
+}
diff --git a/src/received.c b/src/received.c
new file mode 100644
index 0000000..51339a9
--- /dev/null
+++ b/src/received.c
@@ -0,0 +1,172 @@
+#include "fmt.h"
+#include "qmail.h"
+#include "now.h"
+#include "datetime.h"
+#include "date822fmt.h"
+#include "received.h"
+#include "str.h"
+#include "stralloc.h"
+#include "byte.h"
+#include "str.h"
+
+static int issafe(char ch)
+{
+ if (ch == ' ') return 1; /* accept empty spaces */
+ if (ch == '.') return 1;
+ if (ch == '@') return 1;
+ if (ch == '%') return 1;
+ if (ch == '+') return 1;
+ if (ch == '/') return 1;
+ if (ch == '=') return 1;
+ if (ch == ':') return 1;
+ if (ch == '-') return 1;
+ if ((ch >= 'a') && (ch <= 'z')) return 1;
+ if ((ch >= 'A') && (ch <= 'Z')) return 1;
+ if ((ch >= '0') && (ch <= '9')) return 1;
+ return 0;
+}
+
+void safeput(struct qmail *qqt,char *s)
+{
+ char ch;
+ while ((ch = *s++)) {
+ if (!issafe(ch)) ch = '?';
+ qmail_put(qqt,&ch,1);
+ }
+}
+
+static char buf[DATE822FMT];
+
+/* "Received: from relay1.uu.net ([E]HELO uunet.uu.net) (user@192.48.96.5)" */
+/* " de/crypted with tls-version: cipher [used/perm] DN=dn" */
+/* " by silverton.berkeley.edu with [UTF8][E]SMTP[SA]; 26 Sep 1995 04:46:54 -0000" */
+/* "X-RBL-Info: http://www.spamhaus.org/query/bl?ip=127.0.0.2 */
+
+void received(struct qmail *qqt,char *protocol,char *local,char *remoteip,char *remotehost,char *remoteinfo,char *helo,char *tlsinfo,char *rblinfo)
+{
+ struct datetime dt;
+ int i;
+
+ qmail_puts(qqt,"Received: from ");
+ safeput(qqt,remotehost);
+ if (helo) {
+ qmail_puts(qqt," (HELO ");
+ safeput(qqt,helo);
+ qmail_puts(qqt,")");
+ }
+ qmail_puts(qqt," (");
+ if (remoteinfo) {
+ safeput(qqt,remoteinfo);
+ qmail_puts(qqt,"@");
+ }
+ safeput(qqt,remoteip);
+ qmail_puts(qqt,")");
+
+ if (tlsinfo) {
+ qmail_puts(qqt,"\n de/crypted with ");
+ qmail_puts(qqt,tlsinfo);
+ }
+ qmail_puts(qqt,"\n by ");
+ safeput(qqt,local);
+ qmail_puts(qqt," with ");
+ qmail_puts(qqt,protocol);
+ qmail_puts(qqt,"; ");
+ datetime_tai(&dt,now());
+ qmail_put(qqt,buf,date822fmt(buf,&dt));
+
+ if (rblinfo) {
+ i = str_chr(rblinfo,']');
+ if (rblinfo[i] == ']') {
+ qmail_puts(qqt,"X-RBL-Info: ");
+ safeput(qqt,rblinfo + i + 2);
+ qmail_puts(qqt,"\n");
+ }
+ }
+}
+
+/* "Received-SPF: pass (Helogreeting: domain of Identity " */
+/* " designates Clientip as permitted sender) receiver=Hostname " */
+/* " client-ip=Clientip; envelope-from=Mailfrom; " */
+
+void spfheader(struct qmail *qqt,char *spfinfo,char *local,char *remoteip,char *helohost,char *mailfrom)
+{
+ char *result = 0;
+ char *identity = 0;
+ char *clientip = 0;
+ char *helo = 0;
+ char *envelopefrom = 0;
+ char *receiver = 0;
+ char *problem = 0;
+ char *mechanism = 0;
+ int i, j = 0;
+ int len;
+
+ len = str_len(spfinfo);
+ if (!len) return;
+
+ for (i = 0; i < len; i++)
+ if (spfinfo[i] == ' ') spfinfo[i] = '\0';
+
+ for (i = 0; i < len; i++) {
+ if (spfinfo[i] == '\0') {
+ switch (spfinfo[i + 1]) {
+ case 'S': clientip = spfinfo + i + 3; break;
+ case 'O': envelopefrom = spfinfo + i + 3; break;
+ case 'C': identity = spfinfo + i + 3; break;
+ case 'H': helo = spfinfo + i + 3; break;
+ case 'T': receiver = spfinfo + i + 3; break;
+ case 'P': problem = spfinfo + i + 3; break;
+ case 'M': if ((j = str_chr(spfinfo + i,'='))) spfinfo[i + j] = '\0';
+ mechanism = spfinfo + i + 1; break;
+ case 'R': result = spfinfo + i + 3; break;
+ default: break;
+ }
+ }
+ }
+
+ if (!result || *result == 0) result = "o";
+ if (!clientip || *clientip == 0) clientip = remoteip;
+ if (!helo || *helo == 0) helo = helohost;
+ if (!envelopefrom || *envelopefrom == 0) envelopefrom = mailfrom;
+ if (!receiver || *receiver == 0) receiver = local;
+ if (!problem || *problem == 0) problem = "unknown";
+ if (!mechanism || *mechanism == 0) mechanism = "unknown";
+ if (!identity || *identity == 0) {
+ j = str_rchr(envelopefrom,'@');
+ if (envelopefrom[j] == '@') identity = envelopefrom + j + 1;
+ else identity = "unknown";
+ }
+
+ qmail_puts(qqt,"Received-SPF: ");
+ switch (*result) {
+ case '+': qmail_puts(qqt," pass ("); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of "); safeput(qqt,identity); qmail_puts(qqt,"\n");
+ qmail_puts(qqt," designates "); safeput(qqt,clientip); qmail_puts(qqt," as permitted sender)\n");
+ qmail_puts(qqt," receiver="); safeput(qqt,receiver);
+ qmail_puts(qqt,"; client-ip="); safeput(qqt,clientip); qmail_puts(qqt,"\n");
+ qmail_puts(qqt," envelope-from="); safeput(qqt,envelopefrom); qmail_puts(qqt,";\n"); break;
+ case '-': qmail_puts(qqt," fail ("); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of "); safeput(qqt,identity); qmail_puts(qqt,"\n");
+ qmail_puts(qqt," does not designate "); safeput(qqt,clientip); qmail_puts(qqt," as permitted sender)\n"); break;
+ case '~': qmail_puts(qqt," softfail ("); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of transitioning "); safeput(qqt,identity); qmail_puts(qqt,"\n");
+ qmail_puts(qqt," does not designate "); safeput(qqt,clientip); qmail_puts(qqt," as permitted sender)\n"); break;
+ case '?': qmail_puts(qqt," neutral ("); safeput(qqt,helo); qmail_puts(qqt,"; client-ip="); safeput(qqt,clientip);
+ qmail_puts(qqt," is neither permitted \n"); qmail_puts(qqt," nor denied by domain of "); safeput(qqt,identity),
+ qmail_puts(qqt,")\n"); break;
+ case 'o': qmail_puts(qqt," none ("); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of "); safeput(qqt,identity); qmail_puts(qqt," does\n");
+ qmail_puts(qqt," not designate permitted sender hosts)\n"); break;
+ case 't': qmail_puts(qqt," temperror ("); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of "); safeput(qqt,identity); qmail_puts(qqt," evaluated\n");
+ qmail_puts(qqt," with error: "); safeput(qqt,problem); qmail_puts(qqt," for mechanism: "); safeput(qqt,mechanism);
+ qmail_puts(qqt,")\n"); break;
+ case 'e': qmail_puts(qqt," permerror ("); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of "); safeput(qqt,identity); qmail_puts(qqt," evaluated\n");
+ qmail_puts(qqt," with error: "); safeput(qqt,problem); qmail_puts(qqt," for mechanism: "); safeput(qqt,mechanism);
+ qmail_puts(qqt,")\n"); break;
+ default: qmail_puts(qqt," unknown (results for "); safeput(qqt,helo);
+ qmail_puts(qqt,": domain of "); safeput(qqt,identity);
+ qmail_puts(qqt," follow an unknown mechanism: "); safeput(qqt,mechanism); qmail_puts(qqt,")\n"); break;
+ }
+}
diff --git a/src/recipients.c b/src/recipients.c
new file mode 100644
index 0000000..ce29e19
--- /dev/null
+++ b/src/recipients.c
@@ -0,0 +1,290 @@
+#include <unistd.h>
+#include "cdbread.h"
+#include "byte.h"
+#include "open.h"
+#include "control.h"
+#include "constmap.h"
+#include "stralloc.h"
+#include "recipients.h"
+#include "wait.h"
+#include "str.h"
+#include "fd.h"
+#include "sig.h"
+#include "case.h"
+#include "buffer.h"
+#include "auto_break.h"
+#include "qmail.h"
+
+#define FDAUTH 3
+
+static stralloc key = {0};
+static stralloc domain = {0};
+static stralloc wildhost = {0};
+static stralloc address = {0};
+static stralloc rcptline = {0};
+static stralloc vkey = {0};
+static stralloc verp = {0};
+static stralloc user = {0};
+static stralloc ukey = {0};
+static int flagrcpts = 0;
+static int fdrcps;
+static struct cdb cdb;
+
+/** @file recipients.c
+ @brief functions recipients_init, recipients, recipients_parse, callapam
+ @param pointer to address, length of address
+ @return -3: problem with PAM
+ -2: out of memory
+ -1: error reading control file
+ 0: address not found; unsuccessful
+ 1: CDB lookup; successful
+ 2: PAM lookup; successful
+ 3: USERS lookup; successful
+ 4: Wildcarded domain; successful
+ 5: Pass-thru; neutral
+ 10: none existing control file; pass-thru
+*/
+
+int recipients_init()
+{
+ flagrcpts = control_readfile(&rcptline,"control/recipients",0);
+ if (flagrcpts != 1) return flagrcpts;
+ return 0;
+}
+
+char rcptbuf[512];
+buffer br = BUFFER_INIT(safewrite,FDAUTH,rcptbuf,sizeof(rcptbuf));
+
+int callapam(char *pam,char *addr)
+{
+ int i;
+ int j=0;
+ int wstat;
+ int pi[2];
+ int child;
+ char ch;
+ static stralloc mailaddress = {0};
+
+ char *childargs[7] = {0, 0, 0, 0, 0, 0, 0};
+ stralloc pamarg = {0};
+ stralloc pamname = {0};
+ stralloc pamarg1 = {0};
+ stralloc pamarg2 = {0};
+ stralloc pamarg3 = {0};
+ stralloc pamarg4 = {0};
+ stralloc pamarg5 = {0};
+
+ for (i = 0; (ch = pam[i]); i++) {
+ if (j < 6) {
+ if (ch != ' ')
+ if (!stralloc_append(&pamarg,&ch)) return -2;
+ if (ch == ' ' || ch == '\n' || i == str_len(pam) - 1) {
+ if (!stralloc_0(&pamarg)) return -2;
+ switch (j) {
+ case 0:
+ if (!stralloc_copy(&pamname,&pamarg)) return -2;
+ childargs[0] = pamname.s;
+ case 1:
+ if (!stralloc_copy(&pamarg1,&pamarg)) return -2;
+ childargs[1] = pamarg1.s;
+ case 2:
+ if (!stralloc_copy(&pamarg2,&pamarg)) return -2;
+ childargs[2] = pamarg2.s;
+ case 3:
+ if (!stralloc_copy(&pamarg3,&pamarg)) return -2;
+ childargs[3] = pamarg3.s;
+ case 4:
+ if (!stralloc_copy(&pamarg4,&pamarg)) return -2;
+ childargs[4] = pamarg4.s;
+ case 5:
+ if (!stralloc_copy(&pamarg5,&pamarg)) return -2;
+ childargs[5] = pamarg5.s;
+ }
+ j++;
+ if (!stralloc_copys(&pamarg,"")) return -2;
+ }
+ }
+ }
+ childargs[j] = 0;
+
+ close(FDAUTH);
+ if (pipe(pi) == -1) return -3;
+ if (pi[0] != FDAUTH) return -3;
+
+ switch (child = fork()) {
+ case -1:
+ return -3;
+ case 0:
+ close(pi[1]);
+ if (fd_copy(FDAUTH,pi[0]) == -1) return -3;
+ sig_pipedefault();
+ execvp(childargs[0],childargs);
+ return 111;
+ }
+ close(pi[0]);
+
+/* checkpassword compliant form: address\0\0\0 */
+
+ if (!stralloc_copys(&mailaddress,addr)) return -2;
+ if (!stralloc_0(&mailaddress)) return -2;
+ if (!stralloc_0(&mailaddress)) return -2;
+ if (!stralloc_0(&mailaddress)) return -2;
+
+ buffer_init(&br,write,pi[1],rcptbuf,sizeof(rcptbuf));
+ if (buffer_put(&br,mailaddress.s,mailaddress.len) == -1) return -3;
+ if (buffer_flush(&br) == -1) return -3;
+ close(pi[1]);
+
+ if (wait_pid(&wstat,child) == -1) return -3;
+ if (wait_crashed(wstat)) return -3;
+ return wait_exitcode(wstat);
+}
+
+int recipients_parse(char *rhost,int rlen,char *addr,char *rkey,int klen,char *vaddr,char *vkey,int vlen,char *ukey,int ulen)
+{
+ int i;
+ int r;
+ int j = 0;
+ int k = 0;
+ int u = 0;
+ static stralloc line = {0};
+ int seenhost = 0;
+
+ if (!stralloc_copys(&line,"")) return -2;
+ if (!stralloc_copys(&wildhost,"!")) return -2;
+ if (!stralloc_cats(&wildhost,rhost)) return -2;
+ if (!stralloc_0(&wildhost)) return -2;
+
+ for (i = 0; i < rcptline.len; ++i) {
+ if (!stralloc_append(&line,&rcptline.s[i])) return -2;
+
+ if (rcptline.s[i] == '\0') {
+ if (!stralloc_0(&line)) return -2;
+
+ j = byte_chr(line.s,line.len,':'); /* cdb */
+ k = byte_chr(line.s,line.len,'|'); /* pam */
+ u = byte_chr(line.s,line.len,'='); /* assign users */
+
+ if (!str_diffn(line.s,wildhost.s,wildhost.len - 1)) return 4; /* wilddomain */
+ if ((j && j < line.len) || (k && k < line.len) || (u && u < line.len))
+ if (!str_diffn(line.s,"@",1)) /* exact */
+ if (!str_diffn(line.s + 1,rhost,rlen - 1)) seenhost = 1;
+
+ if (!seenhost) { /* domain */
+ if (j && rlen >= j)
+ if (!str_diffn(line.s,rhost + rlen - j - 1,j - 1)) seenhost = 2;
+ if (k && rlen >= k)
+ if (!str_diffn(line.s,rhost + rlen - k - 1,k - 1)) seenhost = 3;
+ if (u && rlen >= u)
+ if (!str_diffn(line.s,rhost + rlen - u - 1,u - 1)) seenhost = 4;
+ }
+ if (!seenhost) /* pass-thru */
+ if (!str_diffn(line.s,"!*",2)) return 5;
+
+ if (k && k < line.len) /* pam */
+ if (seenhost || !str_diffn(line.s,"*",1)) {
+ r = callapam(line.s + k + 1,addr);
+ if (vlen > 0 && r != 0)
+ r = callapam(line.s + k + 1,vaddr);
+ if (r == 0) return 2;
+ if (r == 111) return r;
+ }
+
+ if (u && u < line.len) /* qmail-users */
+ if (seenhost || !str_diffn(line.s,"*",1)) {
+ fdrcps = open_read("users/assign.cdb");
+ if (fdrcps != -1) {
+ cdb_init(&cdb,fdrcps);
+ r = cdb_find(&cdb,ukey,ulen - 1);
+ cdb_free(&cdb);
+ close(fdrcps);
+ if (r) return 3;
+ }
+ }
+
+ if (j && j < line.len) /* cdb */
+ if (seenhost || !str_diffn(line.s,"*",1)) {
+ fdrcps = open_read(line.s + j + 1);
+ if (fdrcps != -1) {
+ cdb_init(&cdb,fdrcps);
+ r = cdb_find(&cdb,rkey,klen - 2);
+ if (vlen > 0 && r == 0)
+ r = cdb_find(&cdb,vkey,vlen - 2);
+ cdb_free(&cdb);
+ close(fdrcps);
+ if (r) return 1;
+ }
+ }
+
+ if (!seenhost) {
+ fdrcps = open_read(line.s); /* legacy cdb */
+ if (fdrcps != -1) {
+ cdb_init(&cdb,fdrcps);
+ r = cdb_find(&cdb,rkey,klen - 2);
+ if (vlen > 0 && r == 0)
+ r = cdb_find(&cdb,vkey,vlen - 2);
+ cdb_free(&cdb);
+ close(fdrcps);
+ if (r) return 1;
+ }
+ }
+
+ if (!stralloc_copys(&line,"")) return -2;
+ }
+ }
+ return 0;
+}
+
+int recipients(char *buf,int len)
+{
+ int at;
+ int i;
+ int r;
+
+ if (flagrcpts != 1) return 10;
+
+ at = byte_rchr(buf,len,'@');
+ if (at && at < len) {
+ if (!stralloc_copyb(&domain,buf + at + 1,len - at - 1)) return -2;
+ if (!stralloc_copyb(&address,buf,len)) return -2;
+ } else {
+ if (!stralloc_copyb(&address,buf,len)) return -2;
+ if (!stralloc_append(&address,"@")) return -2;
+ if (!stralloc_copys(&domain,"localhost")) return -2;
+ if (!stralloc_cat(&address,&domain)) return -2;
+ }
+ if (!stralloc_copyb(&user,buf,at - 1)) return -2;
+
+ if (!stralloc_0(&user)) return -2;
+ if (!stralloc_0(&address)) return -2;
+ if (!stralloc_0(&domain)) return -2;
+
+ if (!stralloc_copys(&key,":")) return -2;
+ if (!stralloc_cat(&key,&address)) return -2;
+ if (!stralloc_0(&key)) return -2; /* \0\0 terminated */
+ case_lowerb(key.s,key.len);
+ case_lowerb(domain.s,domain.len);
+
+ if (!stralloc_copys(&ukey,"!")) return -2;
+ if (!stralloc_cat(&ukey,&user)) return -2;
+ if (!stralloc_0(&ukey)) return -2; /* \0 terminated */
+ case_lowerb(ukey.s,ukey.len);
+
+
+ for (i = 0; i < at; i++) { /* VERP addresses */
+ if (buf[i] == *auto_break || buf[i] == '=' || buf[i] == '+') { /* SRS delimiter */
+ if (!stralloc_copyb(&verp,buf,i + 1)) return -2;
+ if (!stralloc_append(&verp,"@")) return -2;
+ if (!stralloc_cat(&verp,&domain)) return -2;
+ if (!stralloc_copys(&vkey,":")) return -2;
+ if (!stralloc_cat(&vkey,&verp)) return -2;
+ if (!stralloc_0(&vkey)) return -2; /* \0\0 terminated */
+ case_lowerb(vkey.s,vkey.len);
+ break;
+ }
+ }
+
+ r = recipients_parse(domain.s,domain.len,address.s,key.s,key.len,verp.s,vkey.s,vkey.len,ukey.s,ukey.len);
+ if (r) return r;
+ return 0;
+}
diff --git a/src/recipients.sh b/src/recipients.sh
new file mode 100644
index 0000000..0e520a8
--- /dev/null
+++ b/src/recipients.sh
@@ -0,0 +1,16 @@
+awk '
+ /^d/ {
+ recipient = $8
+ xdelay[recipient] += $5 - $4
+ if ($2 == "k") sbytes[recipient] += $6
+ if ($2 == "k") succ[recipient] += 1
+ if ($2 == "d") fail[recipient] += 1
+ if ($2 == "z") temp[recipient] += 1
+ }
+ END {
+ for (recipient in xdelay) {
+ str = sprintf("%.2f",xdelay[recipient])
+ print 0 + sbytes[recipient],succ[recipient] + fail[recipient],succ[recipient] + fail[recipient] + temp[recipient],str,recipient
+ }
+ }
+'
diff --git a/src/rhosts.sh b/src/rhosts.sh
new file mode 100644
index 0000000..96261e7
--- /dev/null
+++ b/src/rhosts.sh
@@ -0,0 +1,18 @@
+awk '
+ /^d/ {
+ host = $8
+ while (num = index(host,"@"))
+ host = substr(host,num + 1)
+ xdelay[host] += $5 - $4
+ if ($2 == "k") sbytes[host] += $6
+ if ($2 == "k") succ[host] += 1
+ if ($2 == "d") fail[host] += 1
+ if ($2 == "z") temp[host] += 1
+ }
+ END {
+ for (host in xdelay) {
+ str = sprintf("%.2f",xdelay[host])
+ print 0 + sbytes[host],succ[host] + fail[host],succ[host] + fail[host] + temp[host],str,host
+ }
+ }
+'
diff --git a/src/rxdelay.sh b/src/rxdelay.sh
new file mode 100644
index 0000000..643d6a4
--- /dev/null
+++ b/src/rxdelay.sh
@@ -0,0 +1,7 @@
+
+awk '
+ {
+ str = sprintf("%.2f",$4/$3)
+ print str,$3,$5
+ }
+' | sort -n
diff --git a/src/select.h1 b/src/select.h1
new file mode 100644
index 0000000..32d0968
--- /dev/null
+++ b/src/select.h1
@@ -0,0 +1,8 @@
+#ifndef SELECT_H
+#define SELECT_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+extern int select();
+
+#endif
diff --git a/src/select.h2 b/src/select.h2
new file mode 100644
index 0000000..c9bd274
--- /dev/null
+++ b/src/select.h2
@@ -0,0 +1,13 @@
+#ifndef SELECT_H
+#define SELECT_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#undef FD_SETSIZE
+#define FD_SETSIZE 1024U
+#undef __FD_SETSIZE
+#define __FD_SETSIZE 1024U
+extern int select();
+
+#endif
diff --git a/src/senders.sh b/src/senders.sh
new file mode 100644
index 0000000..f9e7338
--- /dev/null
+++ b/src/senders.sh
@@ -0,0 +1,23 @@
+
+awk '
+ /^m/ {
+ sender = $10"/"$8
+ messages[sender] += 1
+ succ[sender] += $5
+ fail[sender] += $6
+ temp[sender] += $7
+ mbytes[sender] += $4
+ sbytes[sender] += $4 * $5
+ rbytes[sender] += $4 * ($5 + $6)
+ }
+ /^d/ {
+ sender = $10"/"$7
+ xdelay[sender] += $5 - $4
+ }
+ END {
+ for (sender in messages) {
+ str = sprintf("%.6f",xdelay[sender])
+ print messages[sender],mbytes[sender],sbytes[sender],rbytes[sender],succ[sender] + fail[sender],succ[sender] + fail[sender] + temp[sender],str,sender
+ }
+ }
+'
diff --git a/src/sendmail.c b/src/sendmail.c
new file mode 100644
index 0000000..69971e3
--- /dev/null
+++ b/src/sendmail.c
@@ -0,0 +1,161 @@
+#include <unistd.h>
+#include "getoptb.h"
+#include "buffer.h"
+#include "alloc.h"
+#include "auto_qmail.h"
+#include "exit.h"
+#include "env.h"
+#include "str.h"
+#include "logmsg.h"
+
+#define WHO "sendmail"
+
+void nomem()
+{
+ logmsg(WHO,111,FATAL,"out of memory\n");
+}
+
+void die_usage()
+{
+ logmsg(WHO,100,USAGE,"sendmail [ -t ] [ -fsender ] [ -Fname ] [ -bp ] [ -bs ] [ arg ... ]\n");
+}
+
+char *smtpdarg[] = { "bin/qmail-smtpd", 0 };
+
+void smtpd()
+{
+ if (!env_get("PROTO")) {
+ if (!env_puts("RELAYCLIENT=")) nomem();
+ if (!env_puts("DATABYTES=0")) nomem();
+ if (!env_puts("PROTO=TCP")) nomem();
+ if (!env_puts("TCP6LOCALIP=::1")) nomem();
+ if (!env_puts("TCPLOCALIP=127.0.0.1")) nomem();
+ if (!env_puts("TCPLOCALHOST=localhost")) nomem();
+ if (!env_puts("TCPREMOTEIP=127.0.0.1")) nomem();
+ if (!env_puts("TCP6REMOTEIP=::1")) nomem();
+ if (!env_puts("TCPREMOTEHOST=localhost")) nomem();
+ if (!env_puts("TCPREMOTEINFO=sendmail-bs")) nomem();
+ }
+ execv(*smtpdarg,smtpdarg);
+ logmsg(WHO,111,FATAL,"unable to run qmail-smtpd\n");
+}
+
+char *qreadarg[] = { "bin/qmail-qread", 0 };
+void mailq()
+{
+ execv(*qreadarg,qreadarg);
+ logmsg(WHO,111,FATAL,"unable to run qmail-qread\n");
+}
+
+void do_sender(const char *s)
+{
+ char *x;
+ int n;
+ int a;
+ int i;
+
+ env_unset("QMAILNAME");
+ env_unset("MAILNAME");
+ env_unset("NAME");
+ env_unset("QMAILHOST");
+ env_unset("MAILHOST");
+
+ n = str_len(s);
+ a = str_rchr(s,'@');
+ if (a == n)
+ {
+ env_put("QMAILUSER",s);
+ return;
+ }
+ env_put("QMAILHOST",s + a + 1);
+
+ x = (char *) alloc((a + 1) * sizeof(char));
+ if (!x) nomem();
+ for (i = 0; i < a; i++)
+ x[i] = s[i];
+ x[i] = 0;
+ env_put("QMAILUSER",x);
+ alloc_free(x);
+}
+
+int flagh;
+char *sender;
+
+int main(int argc, char **argv)
+{
+ int opt;
+ char **qiargv;
+ char **arg;
+ int i;
+
+ if (chdir(auto_qmail) == -1) {
+ buffer_putsflush(buffer_2,"sendmail: fatal: unable to switch to qmail home directory\n");
+ _exit(111);
+ }
+
+ flagh = 0;
+ sender = 0;
+ while ((opt = getopt(argc,argv,"vimte:f:p:o:B:F:EJxb:")) != opteof) {
+ switch (opt) {
+ case 'N': break; /* ignore DSN option */
+ case 'B': break;
+ case 't': flagh = 1; break;
+ case 'f': sender = optarg; break;
+ case 'F': if (!env_put("MAILNAME",optarg)) nomem(); break;
+ case 'p': break; /* could generate a Received line from optarg */
+ case 'v': break;
+ case 'i': break; /* what an absurd concept */
+ case 'x': break; /* SVR4 stupidity */
+ case 'm': break; /* twisted-paper-path blindness, incompetent design */
+ case 'e': break; /* qmail has only one error mode */
+ case 'o':
+ switch (optarg[0]) {
+ case 'd': break; /* qmail has only one delivery mode */
+ case 'e': break; /* see 'e' above */
+ case 'i': break; /* see 'i' above */
+ case 'm': break; /* see 'm' above */
+ }
+ break;
+ case 'E': case 'J': /* Sony NEWS-OS */
+ while (argv[optind][optpos]) ++optpos; /* skip optional argument */
+ break;
+ case 'b':
+ switch (optarg[0]) {
+ case 'm': break;
+ case 'p': mailq();
+ case 's': smtpd();
+ default: die_usage();
+ }
+ break;
+ default:
+ die_usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (str_equal(optprogname,"mailq"))
+ mailq();
+
+ if (str_equal(optprogname,"newaliases")) {
+ logmsg(WHO,100,FATAL,"please use fastforward/newaliases instead\n");
+ }
+
+ qiargv = (char **) alloc((argc + 10) * sizeof(char *));
+ if (!qiargv) nomem();
+
+ arg = qiargv;
+ *arg++ = "bin/qmail-inject";
+ *arg++ = (flagh ? "-H" : "-a");
+ if (sender) {
+ *arg++ = "-f";
+ *arg++ = sender;
+ do_sender(sender);
+ }
+ *arg++ = "--";
+ for (i = 0; i < argc; ++i) *arg++ = argv[i];
+ *arg = 0;
+
+ execv(*qiargv,qiargv);
+ logmsg(WHO,111,FATAL,"unable to run qmail-inject\n");
+}
diff --git a/src/setforward.c b/src/setforward.c
new file mode 100644
index 0000000..fe17f74
--- /dev/null
+++ b/src/setforward.c
@@ -0,0 +1,173 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "logmsg.h"
+#include "stralloc.h"
+#include "open.h"
+#include "case.h"
+#include "cdbmake.h"
+#include "logmsg.h"
+
+#define WHO "setforward"
+
+int rename(const char *,const char *); // stdio.h
+
+void usage()
+{
+ logmsg(WHO,100,USAGE,"setforward data.cdb data.tmp");
+}
+void nomem()
+{
+ logmsg(WHO,111,FATAL,"out of memory");
+}
+void missingsemicolon()
+{
+ logmsg(WHO,100,FATAL,"final instruction must end with semicolon");
+}
+void extracolon()
+{
+ logmsg(WHO,100,FATAL,"double colons are not permitted");
+}
+void extracomma()
+{
+ logmsg(WHO,100,FATAL,"commas are not permitted before colons");
+}
+void nulbyte()
+{
+ logmsg(WHO,100,FATAL,"NUL bytes are not permitted");
+}
+void longaddress()
+{
+ logmsg(WHO,100,FATAL,"addresses over 800 bytes are not permitted");
+}
+
+char *fncdb;
+char *fntmp;
+int fd;
+struct cdb_make cdb;
+stralloc key = {0};
+
+stralloc target = {0}; /* always initialized; no NUL */
+stralloc command = {0}; /* always initialized; no NUL */
+stralloc instr = {0}; /* always initialized */
+
+int flagtarget = 0;
+/* 0: reading target; command is empty; instr is empty */
+/* 1: target is complete; instr still has to be written; reading command */
+
+void writeerr()
+{
+ logmsg(WHO,111,FATAL,B("unable to write to: ",fntmp));
+}
+
+void doit(prepend,data,datalen)
+char *prepend;
+char *data;
+int datalen;
+{
+ if (!stralloc_copys(&key,prepend)) nomem();
+ if (!stralloc_cat(&key,&target)) nomem();
+ case_lowerb(key.s,key.len);
+ if (cdb_make_add(&cdb,key.s,key.len,data,datalen) == -1)
+ writeerr();
+}
+
+int getch(ch)
+char *ch;
+{
+ int r;
+
+ r = buffer_get(buffer_0small,ch,1);
+ if (r == -1)
+ logmsg(WHO,111,FATAL,"unable to read input: ");
+ return r;
+}
+
+int main(int argc, char **argv)
+{
+ char ch;
+
+ if (!stralloc_copys(&target,"")) nomem();
+ if (!stralloc_copys(&command,"")) nomem();
+ if (!stralloc_copys(&instr,"")) nomem();
+
+ fncdb = argv[1]; if (!fncdb) usage();
+ fntmp = argv[2]; if (!fntmp) usage();
+
+ fd = open_trunc(fntmp);
+ if (fd == -1)
+ logmsg(WHO,111,FATAL,B("unable to create: ",fntmp));
+
+ if (cdb_make_start(&cdb,fd) == -1) writeerr();
+
+ for (;;) {
+ if (!getch(&ch)) goto EOF;
+
+ if (ch == '#') {
+ while (ch != '\n') if (!getch(&ch)) goto EOF;
+ continue;
+ }
+
+ if (ch == ' ') continue;
+ if (ch == '\n') continue;
+ if (ch == '\t') continue;
+
+ if (ch == ':') {
+ if (flagtarget) extracolon();
+ flagtarget = 1;
+ continue;
+ }
+
+ if ((ch == ',') || (ch == ';')) {
+ if (!flagtarget) extracomma();
+ if (command.len) {
+ if (command.s[0] == '?') {
+ doit("?",command.s + 1,command.len - 1);
+ }
+ else if ((command.s[0] == '|') || (command.s[0] == '!')) {
+ if (!stralloc_cat(&instr,&command)) nomem();
+ if (!stralloc_0(&instr)) nomem();
+ }
+ else if ((command.s[0] == '.') || (command.s[0] == '/')) {
+ if (!stralloc_cat(&instr,&command)) nomem();
+ if (!stralloc_0(&instr)) nomem();
+ }
+ else {
+ if (command.len > 800) longaddress();
+ if (command.s[0] != '&')
+ if (!stralloc_cats(&instr,"&")) nomem();
+ if (!stralloc_cat(&instr,&command)) nomem();
+ if (!stralloc_0(&instr)) nomem();
+ }
+ }
+
+ if (!stralloc_copys(&command,"")) nomem();
+
+ if (ch == ';') {
+ if (instr.len)
+ doit(":",instr.s,instr.len);
+
+ if (!stralloc_copys(&target,"")) nomem();
+ if (!stralloc_copys(&instr,"")) nomem();
+ flagtarget = 0;
+ }
+ continue;
+ }
+
+ if (ch == '\\') if (!getch(&ch)) goto EOF;
+ if (ch == 0) nulbyte();
+ if (!stralloc_append(flagtarget ? &command : &target,&ch)) nomem();
+ }
+
+ EOF:
+ if (flagtarget || target.len)
+ missingsemicolon();
+
+ if (cdb_make_finish(&cdb) == -1) writeerr();
+ if (fsync(fd) == -1) writeerr();
+ if (close(fd) == -1) writeerr(); /* NFS stupidity */
+
+ if (rename(fntmp,fncdb) == -1)
+ logmsg(WHO,111,FATAL,B("unable to move ",fntmp," to: ",fncdb));
+
+ _exit(0);
+}
diff --git a/src/setmaillist.c b/src/setmaillist.c
new file mode 100644
index 0000000..f7ac89b
--- /dev/null
+++ b/src/setmaillist.c
@@ -0,0 +1,93 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include "buffer.h"
+#include "logmsg.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "open.h"
+#include "byte.h"
+
+#define WHO "setmaillist"
+
+int rename(const char *,const char *); // stdio.h
+
+void usage()
+{
+ logmsg(WHO,100,USAGE,"setmaillist list.bin list.tmp");
+}
+
+stralloc line = {0};
+int match;
+
+char *fnbin;
+char *fntmp;
+int fd;
+char buf[1024];
+buffer bo;
+
+void writeerr()
+{
+ logmsg(WHO,111,FATAL,B("unable to write to: ",fntmp));
+}
+
+static void out(char *s,int len)
+{
+ if (buffer_put(&bo,s,len) == -1) writeerr();
+}
+
+int main(int argc,char **argv)
+{
+ umask(033);
+
+ fnbin = argv[1]; if (!fnbin) usage();
+ fntmp = argv[2]; if (!fntmp) usage();
+
+ fd = open_trunc(fntmp);
+ if (fd == -1)
+ logmsg(WHO,111,FATAL,B("unable to create: ",fntmp));
+
+ buffer_init(&bo,write,fd,buf,sizeof(buf));
+
+
+ do {
+ if (getln(buffer_0small,&line,&match,'\n') == -1)
+ logmsg(WHO,111,FATAL,"unable to read input: ");
+
+ while (line.len) {
+ if (line.s[line.len - 1] != '\n')
+ if (line.s[line.len - 1] != ' ')
+ if (line.s[line.len - 1] != '\t')
+ break;
+ --line.len;
+ }
+
+ if (byte_chr(line.s,line.len,'\0') != line.len)
+ logmsg(WHO,111,FATAL,"NUL in input");
+
+ if (line.len)
+ if (line.s[0] != '#') {
+ if ((line.s[0] == '.') || (line.s[0] == '/')) {
+ out(line.s,line.len);
+ out("",1);
+ }
+ else {
+ if (line.len > 800)
+ logmsg(WHO,111,FATAL,"addresses must be under 800 bytes");
+ if (line.s[0] != '&')
+ out("&",1);
+ out(line.s,line.len);
+ out("",1);
+ }
+ }
+
+ } while (match);
+
+ if (buffer_flush(&bo) == -1) writeerr();
+ if (fsync(fd) == -1) writeerr();
+ if (close(fd) == -1) writeerr(); /* NFS stupidity */
+
+ if (rename(fntmp,fnbin) == -1)
+ logmsg(WHO,111,FATAL,B("unable to move ",fntmp," to: ",fnbin));
+
+ _exit(0);
+}
diff --git a/src/sha1.c b/src/sha1.c
new file mode 100644
index 0000000..ee06e92
--- /dev/null
+++ b/src/sha1.c
@@ -0,0 +1,188 @@
+/*
+SHA-1 in C
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+-----------------
+Modified 7/98
+By James H. Brown <jbrown@burgoyne.com>
+Still 100% Public Domain
+
+-----------------
+Adopted for s/qmail 2/2020
+feh
+Still 100% Public Domain; though requiring fehQlibs-14
+
+*/
+
+#include <string.h>
+#include "sha1.h"
+#include "byte.h"
+
+// #define SHA1HANDSOFF
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+/* FIXME: can we do this in an endian-proof way? */
+#ifndef WORDS_BIGENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24) & 0xFF00FF00) \
+ | (rol(block->l[i],8) & 0x00FF00FF))
+#else
+#define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void sha1_transform(uint32_t state[5],const uint8_t buffer[SHA1_BLOCKSIZE])
+{
+ uint32_t a, b, c, d, e;
+ typedef union {
+ uint8_t c[SHA1_BLOCKSIZE];
+ uint32_t l[16];
+ } CHAR64LONG16;
+ CHAR64LONG16 *block;
+
+#ifdef SHA1HANDSOFF
+ static uint8_t workspace[SHA1_BLOCKSIZE];
+
+ block = (CHAR64LONG16 *) workspace;
+ byte_copy(block,SHA1_BLOCKSIZE,buffer);
+#else
+ block = (CHAR64LONG16 *) buffer;
+#endif
+
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+
+#ifdef SHA1HANDSOFF
+ byte_zero(block,64);
+#endif
+}
+
+/* SHA1Init - Initialize new context */
+
+void sha1_init(sha1_ctx *context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+/* Run your data through this. */
+
+void sha1_update(sha1_ctx *context,const uint8_t *data,uint32_t len)
+{
+ uint32_t i, j;
+
+ j = (context->count[0] >> 3) & 63;
+ if ((context->count[0] += len << 3) < (len << 3))
+ context->count[1]++;
+ context->count[1] += (len >> 29);
+ if ((j + len) > 63) {
+ byte_copy(&context->buffer[j],(i = 64 - j),data);
+ sha1_transform(context->state,context->buffer);
+ for (; i + 63 < len; i += 64) {
+ sha1_transform(context->state,data + i);
+ }
+ j = 0;
+ } else
+ i = 0;
+ byte_copy(&context->buffer[j],len - i,&data[i]);
+}
+
+/* Add padding and return the message digest. */
+
+void sha1_final(uint8_t digest[SHA1_DIGESTSIZE],sha1_ctx *context)
+{
+ uint32_t i;
+ uint8_t finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
+ }
+ sha1_update(context,(uint8_t *) "\200",1);
+
+ while ((context->count[0] & 504) != 448)
+ sha1_update(context,(uint8_t *) "\0",1);
+
+ sha1_update(context,finalcount,8); /* Should cause a SHA1_Transform() */
+
+ for (i = 0; i < SHA1_DIGESTSIZE; i++)
+ digest[i] = (uint8_t) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+
+ /* Wipe variables */
+ i = 0;
+ byte_zero(context->buffer,64);
+ byte_zero(context->state,20);
+ byte_zero(context->count,8);
+ byte_zero(finalcount,8);
+
+#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */
+ sha1_transform(context->state,context->buffer);
+#endif
+}
+
+void sha1_hash(char *hash,const char *str,uint32_t len)
+{
+ sha1_ctx context;
+ int i;
+
+ sha1_init(&context);
+ for (i = 0; i < len; i++)
+ sha1_update(&context,(uint8_t *)str + i,1);
+
+ sha1_final((uint8_t *)hash,&context);
+ hash[20] = '\0';
+}
diff --git a/src/sha256.c b/src/sha256.c
new file mode 100644
index 0000000..e5ba5dd
--- /dev/null
+++ b/src/sha256.c
@@ -0,0 +1,167 @@
+/*
+ * SHA256
+ *
+ * The author (Brad Conte) has released this file "into the public domain free
+ * of any restrictions". This file is unchanged except for some style
+ * clean-up and argument order for sha256_hash (feh).
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include "sha256.h"
+
+// DBL_INT_ADD treats two unsigned ints a and b as one 64-bit integer and adds c to it
+
+#define DBL_INT_ADD(a,b,c) if (a > 0xffffffff - (c)) ++b; a += c;
+#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
+#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
+
+#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
+#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
+#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
+#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
+
+uint32_t k[64] =
+{
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+static void sha256_transform(sha256_ctx *ctx, uint8_t *data)
+{
+ uint32_t a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
+
+ for (i = 0, j = 0; i < 16; ++i, j += 4)
+ m[i] = (data[j] << 24) | (data[j+1] << 16) | (data[j+2] << 8) | (data[j+3]);
+ for (; i < 64; ++i)
+ m[i] = SIG1(m[i-2]) + m[i-7] + SIG0(m[i-15]) + m[i-16];
+
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
+
+ for (i = 0; i < 64; ++i) {
+ t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
+ t2 = EP0(a) + MAJ(a,b,c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
+}
+
+static void sha256_init(sha256_ctx *ctx)
+{
+ ctx->datalen = 0;
+ ctx->bitlen[0] = 0;
+ ctx->bitlen[1] = 0;
+ ctx->state[0] = 0x6a09e667;
+ ctx->state[1] = 0xbb67ae85;
+ ctx->state[2] = 0x3c6ef372;
+ ctx->state[3] = 0xa54ff53a;
+ ctx->state[4] = 0x510e527f;
+ ctx->state[5] = 0x9b05688c;
+ ctx->state[6] = 0x1f83d9ab;
+ ctx->state[7] = 0x5be0cd19;
+}
+
+static void sha256_update(sha256_ctx *ctx, uint8_t *data, uint32_t len)
+{
+ uint32_t i;
+
+ for (i=0; i < len; ++i) {
+ ctx->data[ctx->datalen] = data[i];
+ ctx->datalen++;
+ if (ctx->datalen == 64) {
+ sha256_transform(ctx,ctx->data);
+ DBL_INT_ADD(ctx->bitlen[0],ctx->bitlen[1],512);
+ ctx->datalen = 0;
+ }
+ }
+}
+
+static void sha256_final(uint8_t *hash,sha256_ctx *ctx)
+{
+ uint32_t i;
+
+ i = ctx->datalen;
+
+// Pad whatever data is left in the buffer.
+
+ if (ctx->datalen < 56) {
+ ctx->data[i++] = 0x80;
+ while (i < 56)
+ ctx->data[i++] = 0x00;
+ }
+ else {
+ ctx->data[i++] = 0x80;
+ while (i < 64)
+ ctx->data[i++] = 0x00;
+ sha256_transform(ctx,ctx->data);
+ memset(ctx->data,0,56);
+ }
+
+// Append to the padding the total message's length in bits and transform.
+
+ DBL_INT_ADD(ctx->bitlen[0],ctx->bitlen[1],ctx->datalen * 8);
+ ctx->data[63] = ctx->bitlen[0];
+ ctx->data[62] = ctx->bitlen[0] >> 8;
+ ctx->data[61] = ctx->bitlen[0] >> 16;
+ ctx->data[60] = ctx->bitlen[0] >> 24;
+ ctx->data[59] = ctx->bitlen[1];
+ ctx->data[58] = ctx->bitlen[1] >> 8;
+ ctx->data[57] = ctx->bitlen[1] >> 16;
+ ctx->data[56] = ctx->bitlen[1] >> 24;
+ sha256_transform(ctx,ctx->data);
+
+// Since this implementation uses little endian byte ordering and SHA uses
+// big endian, reverse all the bytes when copying the final state to the output hash.
+
+ for (i = 0; i < 4; ++i) {
+ hash[i] = (ctx->state[0] >> (24-i*8)) & 0x000000ff;
+ hash[i+4] = (ctx->state[1] >> (24-i*8)) & 0x000000ff;
+ hash[i+8] = (ctx->state[2] >> (24-i*8)) & 0x000000ff;
+ hash[i+12] = (ctx->state[3] >> (24-i*8)) & 0x000000ff;
+ hash[i+16] = (ctx->state[4] >> (24-i*8)) & 0x000000ff;
+ hash[i+20] = (ctx->state[5] >> (24-i*8)) & 0x000000ff;
+ hash[i+24] = (ctx->state[6] >> (24-i*8)) & 0x000000ff;
+ hash[i+28] = (ctx->state[7] >> (24-i*8)) & 0x000000ff;
+ }
+}
+
+extern void sha256_hash(char *hash,const char *data,size_t len)
+{
+ sha256_ctx ctx;
+ sha256_init(&ctx);
+ sha256_update(&ctx,(uint8_t *)data,(int)len);
+ sha256_final((uint8_t *)hash,&ctx);
+}
diff --git a/src/smtpdlog.c b/src/smtpdlog.c
new file mode 100755
index 0000000..1b44af1
--- /dev/null
+++ b/src/smtpdlog.c
@@ -0,0 +1,271 @@
+#include <unistd.h>
+#include "buffer.h"
+#include "str.h"
+#include "byte.h"
+#include "env.h"
+#include "fmt.h"
+#include "exit.h"
+#include "smtpdlog.h"
+#define FDLOG 2
+
+char *reply421pgl;
+char *reply550hlo;
+char *reply550mbx;
+char *reply552siz;
+char *reply553bmf;
+char *reply553brt;
+char *reply553ngw;
+char *reply553env;
+char *reply553inv;
+char *reply554cnt;
+
+static char strnum[FMT_ULONG];
+static char logbuf[512];
+buffer bo2 = BUFFER_INIT(write,FDLOG,logbuf,sizeof(logbuf));
+
+void smtpdlog_init()
+{
+ reply421pgl = env_get("REPLY_GREYLISTED");
+ reply550hlo = env_get("REPLY_HELO");
+ reply550mbx = env_get("REPLY_MAILBOX");
+ reply552siz = env_get("REPLY_MAXSIZE");
+ reply553bmf = env_get("REPLY_BADMAILFROM");
+ reply553brt = env_get("REPLY_BADRCPTTO");
+ reply553env = env_get("REPLY_SENDEREXIST");
+ reply553ngw = env_get("REPLY_NOGATEWAY");
+ reply553inv = env_get("REPLY_SENDERINVALID");
+ reply554cnt = env_get("REPLY_CONTENT");
+}
+
+static void logs(char *s) { if (buffer_puts(&bo2,s) == -1) _exit(1); } /* single string */
+static void logp(char *s) { logs(" P:"); logs(s); } /* protocol */
+static void logh(char *s1,char *s2,char *s3) { logs(" S:"); logs(s1); logs(":"); logs(s2); logs(" H:"); logs(s3); } /* host */
+static void logm(char *s) { logs(" F:"); logs(s); } /* mailfrom */
+static void logt(char *s) { logs(" T:"); logs(s); } /* rcptto */
+static void logi(char *s) { logs(" '"); logs(s); logs("'"); } /* information */
+static void logn(char *s) { if (buffer_puts(&bo2,s) == -1 ) _exit(1); if (buffer_flush(&bo2) == -1) _exit(1); } /* end */
+static void logpid() { strnum[fmt_ulong(strnum,getpid())] = 0; logs("qmail-smtpd: pid "); logs(strnum); logs(" "); }
+
+void smtp_loga(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *s8,char *s9)
+ { logpid(); logs(s1); logs(s9); logp(s2); logh(s3,s4,s5); logm(s6); logt(s7), logs(" ?~"); logi(s8); logn("\n"); } /* Auth info */
+void smtp_logb(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+ { logpid(); logs(s1); logs(s7); logp(s2); logh(s3,s4,s5); logs(" ?~"); logi(s6); logn("\n"); } /* Auth info */
+void smtp_logg(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+ { logpid(); logs(s1); logp(s2); logh(s3,s4,s5); logm(s6); logt(s7); logn("\n"); } /* Generic */
+void smtp_logh(char *s1,char *s2,char *s3,char *s4,char *s5)
+ { logpid(); logs(s1); logp(s2); logh(s3,s4,s5); logn("\n"); } /* Host */
+void smtp_logi(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *s8)
+ { logpid(); logs(s1); logp(s2); logh(s3,s4,s5); logm(s6); logt(s7); logi(s8); logn("\n"); } /* Generic + Info */
+void smtp_logr(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *s8)
+ { logpid(); logs(s1); logs(s2); logp(s3); logh(s4,s5,s6); logm(s7); logt(s8); logn("\n"); } /* Recipient */
+
+void die_read() { _exit(1); }
+void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); }
+void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_starttls() { out("454 TLS not available due to temporary reason (#5.7.3)\r\n"); flush(); _exit(1); }
+void die_recipients() { out("421 unable to check recipients (#4.3.0)\r\n"); flush(); _exit(1); }
+
+void err_unimpl() { out("500 unimplemented (#5.5.1)\r\n"); }
+void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
+void err_noop() { out("250 ok\r\n"); }
+void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
+void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
+
+int err_child() { out("454 problem with child and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_fork() { out("454 child won't start and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_pipe() { out("454 unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+int err_write() { out("454 unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; }
+
+int err_postgl() { out("454 problem with child and I can't greylist (#4.3.0)\r\n"); return -1; }
+int err_forkgl() { out("454 problem with child and I can't greylist (#4.3.0)\r\n"); return -1; }
+
+/* TLS */
+
+int err_starttls()
+{
+ out("454 TLS not available due to temporary reason (#5.7.3)\r\n");
+ _exit(1);
+}
+void err_tlsreq(char *s1,char *s2,char *s3,char *s4,char *s5)
+{
+ out("535 STARTTLS required (#5.7.1)\r\n");
+ smtp_logh(s1,s2,s3,s4,s5);
+}
+
+/* Helo */
+
+void err_helo(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *s8)
+{
+ out("550 sorry, invalid HELO/EHLO greeting ");
+ if (reply550hlo) out(reply550hlo);
+ out(" (#5.7.1)\r\n");
+ smtp_logi(s1,s2,s3,s4,s5,s6,s7,s8);
+ }
+
+/* Auth */
+
+void err_authsetup(char *s1,char *s2,char *s3,char *s4,char *s5)
+{
+ out("530 Auth not available (#5.7.1)\r\n");
+ smtp_logh(s1,s2,s3,s4,s5);
+}
+void err_authd()
+{
+ out("503 you're already authenticated (#5.5.0)\r\n");
+}
+void err_authmail()
+{
+ out("503 no auth during mail transaction (#5.5.0)\r\n");
+}
+void err_authfail(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("535 authentication failed (#5.7.1)\r\n"); smtp_logb(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_authreq(char *s1,char *s2,char *s3,char *s4,char *s5)
+{
+ out("535 authentication required (#5.7.1)\r\n"); smtp_logh(s1,s2,s3,s4,s5);
+}
+void err_submission(char *s1,char *s2,char *s3,char *s4,char *s5)
+{
+ out("530 Authorization required (#5.7.1) \r\n"); smtp_logh(s1,s2,s3,s4,s5);
+}
+int err_authabort()
+{
+ out("501 auth exchange canceled (#5.0.0)\r\n");
+ return -1;
+}
+int err_authinput()
+{
+ out("501 malformed auth input (#5.5.4)\r\n");
+ return -1;
+}
+void err_authinvalid(char *s1,char *s2,char *s3,char *s4,char *s5)
+{
+ out("504 auth type unimplemented (#5.5.1)\r\n");
+ smtp_logh(s1,s2,s3,s4,s5);
+}
+int err_noauth()
+{
+ out("504 auth type unimplemented (#5.5.1)\r\n");
+ return -1;
+}
+
+/* Mail From: */
+
+void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
+
+void err_mav(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("553 sorry, invalid sender address specified ");
+ if (reply553inv) out(reply553inv);
+ out(" (#5.7.1)\r\n");
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_bmf(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *s8)
+{
+ out("553 sorry, your envelope sender is in my badmailfrom list ");
+ if (reply553bmf) out(reply553bmf);
+ out(" (#5.7.1)\r\n");
+ smtp_logi(s1,s2,s3,s4,s5,s6,s7,s8);
+}
+void err_mfdns(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("553 sorry, your envelope sender must exist ");
+ if (reply553env) out(reply553env);
+ out(" (#5.7.1)\r\n");
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+
+/* SPF */
+
+void err_spf(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *msg)
+{
+ int i, j;
+ int len = str_len(msg);
+
+ for (i = 0; i < len; i = j + 1) {
+ j = byte_chr(msg + i, len - i, '\n') + i;
+ if (j < len) {
+ out("550-");
+ msg[j] = 0;
+ out(msg);
+ msg[j] = '\n';
+ } else {
+ out("550 ");
+ out(msg);
+ }
+ }
+ out(" (#5.7.1)\r\n");
+
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+
+/* Rcpt To: */
+
+void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); }
+
+void postgrey(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("421 greylisted");
+ if (reply421pgl) out(reply421pgl);
+ out(" (#4.3.0)\r\n");
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_nogateway(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("553 sorry, that domain isn't in my list of allowed rcpthosts ");
+ if (reply553ngw) out(reply553ngw);
+ out(" (#5.7.1)\r\n");
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_brt(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("553 sorry, your envelope recipient is in my badrcptto list ");
+ if (reply553brt) out(reply553brt);
+ out(" (#5.7.1)\r\n");
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_rcpts(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("452 sorry, too many recipients (#4.5.3)\r\n"); /* RFC 5321 */
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_recipient(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ if (env_get("RECIPIENTS450"))
+ out("450 sorry, mailbox currently unavailable (#4.2.1)\r\n");
+ else {
+ out("550 sorry, no mailbox by that name ");
+ if (reply550mbx) out(reply550mbx); out(" (#5.7.1)\r\n");
+ }
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+
+/* Data */
+
+void straynewline()
+{
+ out("451 Bare Line Feeds (LF) are not accepted in SMTP; CRLF is required according to RFC 2822.\r\n");
+ flush();
+ _exit(1);
+}
+void err_notorious()
+{
+ out("503 DATA command not accepted at this time (#5.5.1)\r\n");
+ flush();
+ _exit(1);
+}
+void err_size(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7)
+{
+ out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n");
+ smtp_logg(s1,s2,s3,s4,s5,s6,s7);
+}
+void err_data(char *s1,char *s2,char *s3,char *s4,char *s5,char *s6,char *s7,char *s8)
+{
+ out("554 sorry, invalid message content ");
+ if (reply554cnt) out(reply554cnt);
+ out(" (#5.3.2)\r\n");
+ smtp_logi(s1,s2,s3,s4,s5,s6,s7,s8);
+}
diff --git a/src/spawn.c b/src/spawn.c
new file mode 100644
index 0000000..effcb26
--- /dev/null
+++ b/src/spawn.c
@@ -0,0 +1,276 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "alloc.h"
+#include "sig.h"
+#include "wait.h"
+#include "buffer.h"
+#include "byte.h"
+#include "str.h"
+#include "stralloc.h"
+#include "select.h"
+#include "exit.h"
+#include "fd.h"
+#include "open.h"
+#include "error.h"
+#include "auto_qmail.h"
+#include "auto_uids.h"
+#include "auto_spawn.h"
+
+extern int truncreport;
+extern int spawn();
+extern void report();
+extern void initialize();
+
+struct delivery
+ {
+ int used;
+ int fdin; /* pipe input */
+ int pid; /* zero if child is dead */
+ int wstat; /* if !pid: status of child */
+ int fdout; /* pipe output, -1 if !pid; delays eof until after death */
+ stralloc output;
+ }
+;
+
+struct delivery *d;
+
+void sigchld()
+{
+ int wstat;
+ int pid;
+ int i;
+
+ while ((pid = wait_nohang(&wstat)) > 0)
+ for (i = 0; i < auto_spawn; ++i)
+ if (d[i].used)
+ if (d[i].pid == pid) {
+ close(d[i].fdout); d[i].fdout = -1;
+ d[i].wstat = wstat; d[i].pid = 0;
+ }
+}
+
+int flagwriting = 1;
+
+ssize_t okwrite(int fd,char *buf,int n)
+{
+ int w;
+ if (!flagwriting) return n;
+ w = write(fd,buf,n);
+ if (w != -1) return w;
+ if (errno == EINTR) return -1;
+ flagwriting = 0; close(fd);
+ return n;
+}
+
+int flagreading = 1;
+char outbuf[1024];
+buffer bo;
+
+int stage = 0; /* reading 0:delnum 1:messid 2:sender 3:recip */
+int flagabort = 0; /* if 1, everything except delnum is garbage */
+int delnum;
+stralloc messid = {0};
+stralloc sender = {0};
+stralloc recip = {0};
+
+void err(char *s)
+{
+ char ch;
+
+ ch = delnum;
+ buffer_put(&bo,&ch,1);
+ buffer_puts(&bo,s);
+ buffer_putflush(&bo,"",1);
+}
+
+void docmd()
+{
+ int f;
+ int i;
+ int j;
+ int fdmess;
+ int pi[2];
+ struct stat st;
+
+ if (flagabort) { err("Zqmail-spawn: Out of memory. (#4.3.0)\n"); return; }
+ if (delnum < 0) { err("Zqmail-spawn: Internal error: delnum negative. (#4.3.5)\n"); return; }
+ if (delnum >= auto_spawn) { err("Zqmail-spawn: Internal error: delnum too big. (#4.3.5)\n"); return; }
+ if (d[delnum].used) { err("Zqmail-spawn: Internal error: delnum in use. (#4.3.5)\n"); return; }
+
+ for (i = 0; i < messid.len; ++i)
+ if (messid.s[i])
+ if (!i || (messid.s[i] != '/'))
+ if ((unsigned char) (messid.s[i] - '0') > 9)
+ { err("Dqmail-spawn: Internal error: messid has nonnumerics. (#5.3.5)\n"); return; }
+
+ if (messid.len > 100) { err("Dqmail-spawn: Internal error: messid too long. (#5.3.5)\n"); return; }
+ if (!messid.s[0]) { err("Dqmail-spawn: Internal error: messid too short. (#5.3.5)\n"); return; }
+
+ if (!stralloc_copys(&d[delnum].output,""))
+ { err("Zqmail-spawn: Out of memory. (#4.3.0)\n"); return; }
+
+ j = byte_rchr(recip.s,recip.len,'@');
+ if (j >= recip.len) { err("DSorry, address must include host name. (#5.1.3)\n"); return; }
+
+ fdmess = open_read(messid.s);
+ if (fdmess == -1) { err("Zqmail-spawn: Unable to open message. (#4.3.0)\n"); return; }
+
+ if (fstat(fdmess,&st) == -1)
+ { close(fdmess); err("Zqmail-spawn: Unable to fstat message. (#4.3.0)\n"); return; }
+ if ((st.st_mode & S_IFMT) != S_IFREG)
+ { close(fdmess); err("ZSorry, message has wrong type. (#4.3.5)\n"); return; }
+ if (st.st_uid != auto_uidq) /* aaack! qmailq has to be trusted! */
+ /* your security is already toast at this point. damage control... */
+ { close(fdmess); err("ZSorry, message has wrong owner. (#4.3.5)\n"); return; }
+
+ if (pipe(pi) == -1) {
+ if (errno == EFAULT) err("Zqmail-spawn: Unable to create pipe (wrong fildes). (#4.3.0)\n");
+ else if (errno == EMFILE) err("Zqmail-spawn: Unable to create pipe (too many FDS). (#4.3.0)\n");
+ else if (errno == ENFILE) err("Zqmail-spawn: Unable to create pipe (system file table full). (#4.3.0)\n");
+ else if (errno == ENOMEM) err("Zqmail-spawn: Unable to create pipe (out of memory). (#4.3.0)\n");
+ else err("Zqmail-spawn: Unable to create pipe (unkown reason). (#4.3.0)\n");
+ close(fdmess);
+ return;
+ }
+
+ fd_coe(pi[0]);
+
+ f = spawn(fdmess,pi[1],sender.s,recip.s,j);
+ close(fdmess);
+
+ if (f == -1)
+ { close(pi[0]); close(pi[1]); err("Zqmail-spawn: Unable to fork. (#4.3.0)\n"); return; }
+
+ d[delnum].fdin = pi[0];
+ d[delnum].fdout = pi[1]; fd_coe(pi[1]);
+ d[delnum].pid = f;
+ d[delnum].used = 1;
+}
+
+char cmdbuf[1024];
+
+void getcmd()
+{
+ int i;
+ int r;
+ char ch;
+
+ r = read(0,cmdbuf,sizeof(cmdbuf));
+ if (r == 0)
+ { flagreading = 0; return; }
+ if (r == -1) {
+ if (errno != EINTR)
+ flagreading = 0;
+ return;
+ }
+
+ for (i = 0; i < r; ++i) {
+ ch = cmdbuf[i];
+ switch (stage) {
+ case 0:
+ delnum = (unsigned int) (unsigned char) ch;
+ messid.len = 0; stage = 1; break;
+ case 1:
+ if (!stralloc_append(&messid,&ch)) flagabort = 1;
+ if (ch) break;
+ sender.len = 0; stage = 2; break;
+ case 2:
+ if (!stralloc_append(&sender,&ch)) flagabort = 1;
+ if (ch) break;
+ recip.len = 0; stage = 3; break;
+ case 3:
+ if (!stralloc_append(&recip,&ch)) flagabort = 1;
+ if (ch) break;
+ docmd();
+ flagabort = 0; stage = 0; break;
+ }
+ }
+}
+
+char inbuf[128];
+
+int main(int argc,char **argv)
+{
+ char ch;
+ int i;
+ int r;
+ fd_set rfds;
+ int nfds;
+
+ if (chdir(auto_qmail) == -1) _exit(110);
+ if (chdir("queue/mess") == -1) _exit(110);
+ if (!stralloc_copys(&messid,"")) _exit(111);
+ if (!stralloc_copys(&sender,"")) _exit(111);
+ if (!stralloc_copys(&recip,"")) _exit(111);
+
+ d = (struct delivery *) alloc((auto_spawn + 10) * sizeof(struct delivery));
+ if (!d) _exit(111);
+
+ buffer_init(&bo,okwrite,1,outbuf,sizeof(outbuf));
+
+ sig_pipeignore();
+ sig_childcatch(sigchld);
+
+ initialize(argc,argv);
+
+ ch = auto_spawn;
+ buffer_putflush(&bo,&ch,1);
+
+ for (i = 0; i < auto_spawn; ++i)
+ { d[i].used = 0; d[i].output.s = 0; }
+
+ for (;;) {
+ if (!flagreading) {
+ for (i = 0; i < auto_spawn; ++i) if (d[i].used) break;
+ if (i >= auto_spawn) _exit(0);
+ }
+ sig_childunblock();
+
+ FD_ZERO(&rfds);
+ if (flagreading) FD_SET(0,&rfds);
+ nfds = 1;
+
+ for (i = 0; i < auto_spawn; ++i)
+ if (d[i].used) {
+ FD_SET(d[i].fdin,&rfds);
+ if (d[i].fdin >= nfds)
+ nfds = d[i].fdin + 1;
+ }
+
+ r = select(nfds,&rfds,(fd_set *) 0,(fd_set *) 0,(struct timeval *) 0);
+ sig_childblock();
+
+ if (r != -1) {
+ if (flagreading)
+ if (FD_ISSET(0,&rfds)) getcmd();
+ for (i = 0; i < auto_spawn; ++i)
+ if (d[i].used)
+
+ if (FD_ISSET(d[i].fdin,&rfds)) {
+ r = read(d[i].fdin,inbuf,128);
+ if (r == -1)
+ continue; /* read error on a readable pipe? be serious */
+ if (r == 0) {
+ ch = i;
+ buffer_put(&bo,&ch,1);
+ report(&bo,d[i].wstat,d[i].output.s,d[i].output.len);
+ buffer_put(&bo,"",1);
+ buffer_flush(&bo);
+ close(d[i].fdin); d[i].used = 0;
+ continue;
+ }
+ while (!stralloc_readyplus(&d[i].output,r))
+ sleep(10); /*XXX*/
+ byte_copy(d[i].output.s + d[i].output.len,r,inbuf);
+ d[i].output.len += r;
+ if (truncreport > 100)
+ if (d[i].output.len > truncreport) {
+ char *truncmess = "\nError report too long, sorry.\n";
+ d[i].output.len = truncreport - str_len(truncmess) - 3;
+ stralloc_cats(&d[i].output,truncmess);
+ }
+ }
+ }
+ }
+}
diff --git a/src/spf.c b/src/spf.c
new file mode 100644
index 0000000..2b61ba1
--- /dev/null
+++ b/src/spf.c
@@ -0,0 +1,647 @@
+#include "stralloc.h"
+#include "alloc.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "ipme.h"
+#include "str.h"
+#include "fmt.h"
+#include "scan.h"
+#include "byte.h"
+#include "now.h"
+#include "dns.h"
+#include "case.h"
+#include "spf.h"
+
+/* long lived SPF variables (output) */
+
+stralloc spfinfo = {0}; /* SPF results - see spf.h */
+stralloc spfrecord = {0}; /* Used for diagnostics */
+
+/* s/qmail control SPF variables (input) */
+
+stralloc spflocalrules; /* Local rules provided here */
+stralloc spfexplain; /* Default SPF_EXPMSG in spf.h */
+
+stralloc spfmf = {0}; /* aka envelopefrom = clientid */
+stralloc spfhelo = {0}; /* helo or domain part for spfmf */
+stralloc dnsname = {0}; /* FQDN of client host in DNS */
+stralloc spflocal = {0}; /* Receiving host */
+
+stralloc spfexpmsg = {0}; /* additional explanation given as 5xx SMTP response */
+stralloc expdomain = {0}; /* the domain, for which explanation is given */
+int flagip6;
+
+stralloc domain = {0};
+stralloc identity = {0};
+
+static int recursion;
+char ip4remote[4] = {0, 0, 0, 0};
+char ip6remote[16] = {16 * 0};
+
+/* Sample SPF TXT records:
+Standard example: example.net TXT "v=spf1 mx a:pluto.example.net include:aspmx.googlemail.com -all"
+Fehcom's example: fehcom.net TXT "v=spf1 ip4:85.25.149.179/32 ip6:2001:4dd0:ff00:3d4::2/64 -all"
+Include example: mailing.com TXT "v=spf1 a:smtpout.mailing.com include:spf.nl2go.com ~all"
+Exists+Expand: exists.com TXT "v=spf1 exists:%{ir}.%{l1r+-}._spf.%{d} -all"
+*/
+
+/* Entry point: -------------------------------------- Go for SPF */
+
+/**
+ @brief spf_query
+ prepares the SPF TXT record query
+ @param input: pointer to remoteip, helo, mf, localhost, and flagIP6
+ @return int r = SPF return code
+ */
+
+
+int spf_query(const char *remoteip,const char *helo,const char *mf,const char *local,const int flagip)
+{
+ int at;
+ int r = SPF_INIT;
+ flagip6 = flagip;
+
+ if (!stralloc_copys(&spfinfo," ")) return SPF_NOMEM;
+
+ switch (flagip6) {
+ case -1: if (!spf_info("MLocal=",remoteip)) return SPF_NOMEM;
+ if (!spf_info("R:","+")) return SPF_NOMEM;
+ break;
+ case 0: if (!ip4_scan(remoteip,ip4remote)) return SPF_SYNTAX;
+ if (ipme_is4(ip4remote) == 1) {
+ if (!spf_info("MLocal=",remoteip)) return SPF_NOMEM;
+ if (!spf_info("R:","+")) return SPF_NOMEM;
+ return SPF_ME;
+ } break;
+ case 1: if (!ip6_scan(remoteip,ip6remote)) return SPF_SYNTAX;
+ if (ipme_is6(ip6remote) == 1) {
+ if (!spf_info("MLocal=",remoteip)) return SPF_NOMEM;
+ if (!spf_info("R:","+")) return SPF_NOMEM;
+ return SPF_ME;
+ } break;
+ }
+
+ if (helo && str_len(helo)) {
+ if (!stralloc_copys(&spfhelo,helo)) return SPF_NOMEM;
+ } else {
+ if (!stralloc_copys(&spfhelo,"unknown")) return SPF_NOMEM;
+ }
+ if (!stralloc_0(&spfhelo)) return SPF_NOMEM;
+
+ if (mf && str_len(mf)) {
+ if (!stralloc_copys(&spfmf,mf)) return SPF_NOMEM;
+ if (!stralloc_0(&spfmf)) return SPF_NOMEM;
+ at = str_rchr(spfmf.s,'@');
+ if (spfmf.s[at] == '@') {
+ if (!stralloc_copys(&domain,spfmf.s + at + 1)) return SPF_NOMEM;
+ } else {
+// if (!stralloc_0(&spfhelo)) return SPF_NOMEM;
+ if (!stralloc_copys(&domain,&spfhelo)) return SPF_NOMEM;
+ }
+ if (!stralloc_copy(&identity,&domain)) return SPF_NOMEM;
+ }
+ if (!stralloc_0(&identity)) return SPF_NOMEM;
+
+ if (local && str_len(local)) {
+ if (!stralloc_copys(&spflocal,local)) return SPF_NOMEM;
+ } else {
+ if (!stralloc_copys(&spflocal,"localhost")) return SPF_NOMEM;
+ }
+ if (!stralloc_0(&spflocal)) return SPF_NOMEM;
+
+ if (!spf_info("S=",remoteip)) return SPF_NOMEM;
+ if (!spf_info("O=",spfmf.s)) return SPF_NOMEM;
+ if (!spf_info("C=",identity.s)) return SPF_NOMEM;
+ if (!spf_info("H=",spfhelo.s)) return SPF_NOMEM;
+
+ if (!stralloc_copy(&spfexpmsg,&spfexplain)) return SPF_NOMEM;
+ if (!stralloc_0(&spfexpmsg)) return SPF_NOMEM;
+
+ recursion = 0;
+ dnsname.len = 0;
+
+ if (r == SPF_INIT) r = spf_lookup(&domain);
+ if (r == SPF_LOOP) {
+ if (!spf_info("P=","Maximum nesting level exceeded; possible loop")) return SPF_NOMEM;
+ if (!spf_info("R:","e")) return SPF_NOMEM;
+ }
+ if (r < 0) r = SPF_UNKNOWN; /* return 2main */
+
+ return r;
+}
+
+/* SPF Lookup: -------------------------------------- Return cases */
+
+static struct spf_aliases {
+ char *alias;
+ int defrc;
+} spf_aliases[] = {
+ { "allow", SPF_OK }
+, { "pass", SPF_OK }
+, { "deny", SPF_FAIL }
+, { "softdeny",SPF_SOFTFAIL }
+, { "fail", SPF_FAIL }
+, { "softfail",SPF_SOFTFAIL }
+, { "unknown", SPF_NEUTRAL }
+, { 0, SPF_UNKNOWN }
+};
+
+/**
+ @brief spf_lookup
+ calles the actual (recursive) SPF DNS query
+ @param input: pointer to stralloc domain (fqdn)
+ @input stralloc spflocalrules (if provided -- for artificial results)
+ @output stralloc spfdata with RDATA (+ artificial information)
+ @return int r = SPF return code
+ */
+
+int spf_lookup(stralloc *domain)
+{
+ stralloc spfdata = {0};
+ stralloc sa = {0};
+ struct spf_aliases *da;
+ int first = !recursion;
+ int local_pos = -1;
+ int localrules = 0;
+ int q = -1;
+ int i, r;
+ int begin, pos;
+ int spfrc;
+ int done;
+ char *p;
+
+ /* Fallthrough result */
+
+ REDIRECT:
+ if (++recursion > LOOKUP_LIMIT) return SPF_EXHAUST;
+
+ if (!stralloc_copys(&expdomain,domain->s)) return SPF_NOMEM; // *FIXME */
+
+ if (!stralloc_copys(&spfdata,"")) return SPF_NOMEM;
+ r = spf_records(&spfdata,domain);
+
+ if (!stralloc_0(domain)) return SPF_NOMEM;
+ if (first) if (!stralloc_copys(&spfrecord,"")) return SPF_NOMEM;
+ if (!stralloc_cats(&spfrecord,"(")) return SPF_NOMEM;
+ if (!stralloc_cat(&spfrecord,domain)) return SPF_NOMEM;
+ if (!stralloc_cats(&spfrecord,")")) return SPF_NOMEM;
+ if (!stralloc_cats(&spfrecord," => ")) return SPF_NOMEM;
+ if (!stralloc_cat(&spfrecord,&spfdata)) return SPF_NOMEM;
+ if (!stralloc_cats(&spfrecord,"\n")) return SPF_NOMEM;
+ if (!stralloc_0(&spfrecord)) return SPF_NOMEM;
+
+ /* In spite of none-existing SPF data, use local rules as substitude */
+
+ if (r == SPF_NONE) { /* No SPF records published */
+ if (!first) {
+ return r;
+ } else {
+ spfdata.len = 0;
+ }
+ if (localrules) { /* append local ruleset */
+ local_pos = spfdata.len;
+ if (!stralloc_cats(&spfdata,spflocalrules.s)) return SPF_NOMEM;
+ }
+ if (!stralloc_0(&spfdata)) return SPF_NOMEM;
+
+ if (!stralloc_copys(&expdomain,"")) return SPF_NOMEM;
+
+ } else if (r == SPF_OK) { /* SPF records published */
+ if (!stralloc_0(&spfdata)) return SPF_NOMEM;
+ r = SPF_NEUTRAL;
+
+ if (first && localrules) { /* try to add local rules before failure of all mechs */
+ pos = 0;
+ p = (char *) 0;
+ while (pos < spfdata.len) {
+ NXTOK(begin,pos,&spfdata);
+ if (!spfdata.s[begin]) continue;
+
+ if (p && spfdata.s[begin] != *p) p = (char *) 0;
+ if (!p && (spfdata.s[begin] == '-' ||
+ spfdata.s[begin] == '~' ||
+ spfdata.s[begin] == '?')) p = &spfdata.s[begin];
+
+ if (p && p > spfdata.s && case_equals(spfdata.s + begin + 1,"all")) {
+ /* ok, we can insert the local rules at p */
+ local_pos = p - spfdata.s;
+
+ if (!stralloc_readyplus(&spfdata,spflocalrules.len)) return 0;
+ p = spfdata.s + local_pos;
+ byte_copyr(p + spflocalrules.len,spfdata.len - local_pos,p);
+ byte_copy(p,spflocalrules.len,spflocalrules.s);
+ spfdata.len += spflocalrules.len;
+
+ pos += spflocalrules.len;
+ break;
+ }
+ }
+
+ if (pos >= spfdata.len) pos = spfdata.len - 1;
+ for (i = 0; i < pos; i++)
+ if (!spfdata.s[i]) spfdata.s[i] = ' ';
+ }
+
+ } else { /* Any other SPF return code */
+ return r;
+ }
+
+ /* (artificial) SPF data exist; work thru them */
+
+ pos = 0;
+ done = 0;
+ while (pos < spfdata.len) {
+ NXTOK(begin,pos,&spfdata);
+ if (!spfdata.s[begin]) continue;
+
+ if (!done && localrules) { /* in local ruleset? */
+ if (local_pos >= 0 && begin >= local_pos) {
+ if (begin < (local_pos + spflocalrules.len)) {
+ if (!stralloc_copys(&expdomain,"")) return SPF_NOMEM;
+ } else {
+ if (!stralloc_copy(&expdomain,domain)) return SPF_NOMEM;
+ }
+ }
+ }
+
+ for (p = spfdata.s + begin; *p; ++p)
+ if (*p == ':' || *p == '/' || *p == '=') break;
+
+ if (*p == '=') {
+ *p++ = 0;
+
+ if (case_equals(spfdata.s + begin,"redirect")) { /* modifiers are simply handled here */
+ if (done) continue;
+
+// if (!stralloc_0(domain)) return SPF_NOMEM;
+ if (!spf_parse(&sa,p,domain->s)) return SPF_NOMEM;
+ if (!stralloc_copy(domain,&sa)) return SPF_NOMEM;
+ if (!spf_info("D=",p)) return SPF_NOMEM;
+ r = SPF_UNKNOWN;
+
+ goto REDIRECT;
+ } else if (case_equals(spfdata.s + begin,"default")) { /* we don't need those anymore */
+ if (done) continue;
+
+ for (da = spf_aliases; da->alias; ++da)
+ if (case_equals(da->alias,p)) break;
+
+ r = da->defrc;
+ } else if (case_equals(spfdata.s + begin,"exp")) { /* exp= only on top level */
+ stralloc out = {0};
+
+ if (!first) continue;
+ if (!stralloc_copys(&sa,p)) return SPF_NOMEM;
+
+ switch (dns_txt(&out,&sa)) {
+ case -1: return SPF_NOMEM;
+ case 0: continue; /* nobody @home */
+ }
+
+ if (!stralloc_copys(&spfexpmsg,out.s)) return SPF_NOMEM;
+ if (!stralloc_append(&spfexpmsg,"\n")) return SPF_NOMEM;
+ if (!stralloc_0(&spfexpmsg)) return SPF_NOMEM;
+ }
+ } else if (!done) { /* and unknown modifiers are ignored */
+ if (!stralloc_copys(&sa,spfdata.s + begin)) return SPF_NOMEM;
+ if (!stralloc_0(&sa)) return SPF_NOMEM;
+
+ switch (spfdata.s[begin]) {
+ case '-': begin++; spfrc = SPF_FAIL; break;
+ case '~': begin++; spfrc = SPF_SOFTFAIL; break;
+ case '+': begin++; spfrc = SPF_OK; break;
+ case '?': begin++; spfrc = SPF_NEUTRAL; break;
+ default: spfrc = SPF_OK;
+ }
+
+ if (*p == '/') {
+ *p++ = 0;
+ q = spf_mechanism(spfdata.s + begin,0,p,domain->s);
+ } else {
+ if (*p) *p++ = 0;
+ i = str_chr(p,'/');
+ if (p[i] == '/') {
+ p[i++] = 0;
+ q = spf_mechanism(spfdata.s + begin,p,p + i,domain->s);
+ } else if (i > 0) {
+ q = spf_mechanism(spfdata.s + begin,p,0,domain->s);
+ } else {
+ q = spf_mechanism(spfdata.s + begin,0,0,domain->s);
+ }
+ }
+ if (q == SPF_OK) q = spfrc;
+
+ switch (q) {
+ case SPF_OK: if (!spf_info("R:","+")) return SPF_NOMEM; break;
+ case SPF_NEUTRAL: if (!spf_info("R:","?")) return SPF_NOMEM; break;
+ case SPF_SYNTAX: if (!spf_info("P=","Unknown parse error")) return SPF_NOMEM;
+ if (!spf_info("R:","e")) return SPF_NOMEM; break;
+ case SPF_SOFTFAIL: if (!spf_info("R:","~")) return SPF_NOMEM; break;
+ case SPF_FAIL: if (!spf_info("R:","-")) return SPF_NOMEM; break;
+ case SPF_EXT: if (!spf_info("P=","Unknown SPF mechanism")) return SPF_NOMEM; break;
+ case SPF_ERROR: if (localrules) if (local_pos >= 0 && begin >= local_pos) break;
+ if (!spf_info("R:","o")) return SPF_NOMEM; q = SPF_NONE; break;
+ case SPF_NONE: continue;
+ }
+
+ r = q;
+ done = 1; /* we're done, no more mechanisms */
+ }
+ }
+
+ /* we fell through, no local rule applied */
+ if (!done)
+ if (!stralloc_copy(&expdomain,domain)) return SPF_NOMEM;
+
+ return r;
+}
+
+/* Mechanisms: -------------------------------------- Lookup classes */
+
+static struct mechanisms {
+ char *mechanism;
+ int (*func)(char *spfspec,char *prefix);
+ unsigned int use_spfspec : 1;
+ unsigned int use_prefix : 1;
+ unsigned int expands : 1;
+ unsigned int filldomain : 1;
+ int defresult : 4;
+} mechanisms[] = {
+ { "all", 0, 0,0,0,0,SPF_OK }
+, { "include", spf_include,1,0,1,0,0 }
+, { "a", spf_a, 1,1,1,1,0 }
+, { "mx", spf_mx, 1,1,1,1,0 }
+, { "ptr", spf_ptr, 1,0,1,1,0 }
+, { "ip4", spf_ip4, 1,1,0,0,0 }
+, { "ip6", spf_ip6, 1,1,0,0,0 }
+, { "exists", spf_exists, 1,0,1,0,0 }
+, { "extension",0, 1,1,0,0,SPF_EXT }
+, { 0, 0, 1,1,0,0,SPF_EXT }
+};
+
+/**
+ @brief spf_mechanism
+ evaluates the provided mechanisms in the SPF record [RFC7208 Sec 5.]
+ @param input: pointer to mechanism, SPF specification from record, CIDR prefix length, domain
+ @input stralloc spflocalrules (if provided)
+ @output pointer to spfspec: data evaluated
+ @return int r
+ */
+
+int spf_mechanism(char *mechanism,char *spfspec,char *prefix,char *domain)
+{
+ struct mechanisms *mech;
+ stralloc sa = {0};
+ int r;
+ int pos;
+
+ for (mech = mechanisms; mech->mechanism; mech++)
+ if (case_equals(mech->mechanism,mechanism)) break;
+
+ if (mech->use_spfspec && !spfspec && mech->filldomain) spfspec = domain;
+ if (!mech->use_spfspec != !spfspec) return SPF_SYNTAX;
+ if (mech->use_prefix && !get_prefix(prefix)) return SPF_SYNTAX;
+
+ if (!mech->func) return mech->defresult;
+ if (!stralloc_readyplus(&sa,1)) return SPF_NOMEM;
+
+ if (mech->expands && case_diffs(spfspec,domain)) {
+ if (!spf_parse(&sa,spfspec,domain)) return SPF_NOMEM;
+ for (pos = 0; (sa.len - pos) > 255;) {
+ pos += byte_chr(sa.s + pos,sa.len - pos,'.');
+ if (pos < sa.len) pos++;
+ }
+ sa.len -= pos;
+ if (pos > 0) byte_copy(sa.s,sa.len,sa.s + pos);
+ if (!stralloc_0(&sa)) return SPF_NOMEM;
+ spfspec = sa.s;
+ }
+
+ r = mech->func(spfspec,prefix);
+ return r;
+}
+
+/**
+ @brief spf_include
+ deals with recursive evaluation of SPF record [RFC7208 Sec. 5.2]
+ @param input: pointer to included SPF specification; CIDR prefix length
+ @return int r = 1 ok; 0 failure
+ */
+
+int spf_include(char *spfspec,char *prefix)
+{
+ stralloc sa = {0};
+ int r;
+
+ if (!stralloc_copys(&sa,spfspec)) return SPF_NOMEM;
+
+ r = spf_lookup(&sa);
+ switch (r) {
+ case SPF_NONE: r = SPF_UNKNOWN; break;
+ case SPF_SYNTAX: r = SPF_UNKNOWN; break;
+ case SPF_NEUTRAL:
+ case SPF_SOFTFAIL:
+ case SPF_FAIL: r = SPF_NONE; break;
+ }
+ if (!stralloc_0(&sa)) return SPF_NOMEM;
+ if (!spf_info("I=",sa.s)) return SPF_NOMEM;
+
+ return r;
+}
+
+/**
+ @brief spf_parse
+ parses the substructure of the SPF record and calls spf_macros
+ @param input: pointer to SPF specification, pointer to domain
+ output: stralloc sa --
+ @output pointer to spfspec: with found data
+ @return int r = 1 ok; 0 failure
+ */
+
+int spf_parse(stralloc *sa,char *spfspec,char *domain)
+{
+ char *p;
+ int pos;
+ char append;
+
+ if (!stralloc_readyplus(sa,3)) return 0;
+ if (!stralloc_copys(sa,"")) return 0;
+
+ for (p = spfspec; *p; ++p) {
+ append = *p;
+ if (byte_equal(p,1,"%")) {
+ p++;
+ switch (*p) {
+ case '%': break;
+ case '_': append = ' '; break;
+ case '-': if (!stralloc_cats(sa,"%20")) return 0; continue;
+ case '{':
+ pos = str_chr(p,'}');
+ if (p[pos] != '}') { p--; break; }
+ p[pos] = '\0';
+ if (!spf_macros(sa,p + 1,domain)) return 0;
+ p += pos;
+ continue;
+ default: p--;
+ }
+ }
+ if (!stralloc_append(sa,&append)) return 0;
+ }
+
+ return 1;
+}
+
+/**
+ @brief spf_macros
+ deals with macros in the SPF specificaton [RFC7208 Sec. 7ff]
+ @param input: pointer to SPF macro, pointer to domain
+ output: pointer to stralloc expand(ed information)
+ @return int r = 1 ok; 0 failure
+ */
+
+int spf_macros(stralloc *expand,char *macro,char *domain)
+{
+ static const char hextab[] = "0123456789abcdef";
+ stralloc sa = {0};
+ int reverse = 0;
+ int ndigits = -1;
+ int urlencode;
+ unsigned long u;
+ char ch = {0};
+ char ascii;
+ int pos, i, n;
+ int start = expand->len;
+
+ /* URL encoding - hidden in RFC 7208 Sec. 7.3 */
+
+ if (*macro == 'x') { urlencode = -1; ++macro; } else urlencode = 0;
+ ch = *macro;
+ if (!ch) { return 1; }
+ if (ch >= 'A' && ch <= 'Z') { ch += 32; urlencode = 1; }
+ if (urlencode == -1) ch -= 32;
+
+ /* No. digits determine number of printed labels */
+
+ i = 0;
+ while (*macro) {
+ i++;
+ if (*macro == '}') break;
+ if (*macro >= '0' && *macro <= '9') {
+ scan_ulong(macro,&u); ndigits = u;
+ } else if (i > 1 && *macro == 'r') { reverse = 1; break; } /* Reverse representation */
+ macro++;
+ }
+
+ switch (ch) { /* see RFC7208 sec. 7.2 */
+ case 's': case 'S':
+ if (!stralloc_readyplus(&sa,spfmf.len)) return 0;
+ if (!stralloc_copys(&sa,spfmf.s)) return 0;
+ break;
+ case 'l': case 'L':
+ i = byte_rchr(spfmf.s,spfmf.len,'@');
+ if (i < spfmf.len) {
+ if (!stralloc_copyb(&sa,spfmf.s,i)) return 0;
+ } else {
+ if (!stralloc_copys(&sa,"postmaster")) return 0;
+ }
+ break;
+ case 'o': case 'O':
+ i = byte_rchr(spfmf.s,spfmf.len,'@') + 1;
+ if (i > spfmf.len) break;
+ if (!stralloc_copys(&sa,spfmf.s + i)) return 0;
+ break;
+ case 'd': case 'D':
+ if (!stralloc_copys(&sa,domain)) return 0; /* the hack for 'Z'; Russions everywhere ;-) */
+ break;
+ case 'i': case 'c': case 'I': case 'C':
+ if (!stralloc_ready(&sa,IPFMT)) return 0;
+ if (flagip6) {
+ sa.len = ip6_fmt(sa.s,ip6remote);
+ } else {
+ sa.len = ip4_fmt(sa.s,ip4remote);
+ }
+ break;
+ case 'p': case 'P':
+ if (!dnsname.len) spf_ptr(domain,0);
+ if (dnsname.len) {
+ if (!stralloc_copys(&sa,dnsname.s)) return 0;
+ } else {
+ if (!stralloc_copys(&sa,"unknown")) return 0;
+ }
+ break;
+ case 'h': case 'H':
+ if (!stralloc_copys(&sa,spfhelo.s)) return 0; /* FIXME: FQDN? */
+ break;
+ case 't': case 'T':
+ if (!stralloc_ready(&sa,FMT_ULONG)) return 0;
+ sa.len = fmt_ulong(sa.s,(unsigned long)now());
+ break;
+ case 'v': case 'V':
+ if (flagip6) {
+ if (!stralloc_copys(&sa,"ip6")) return 0;
+ } else {
+ if (!stralloc_copys(&sa,"in-addr")) return 0;
+ }
+ break;
+ case 'r': case 'R':
+ if (!stralloc_copy(&sa,&spflocal)) return 0;
+ break;
+ default: break;
+ }
+ if (!stralloc_0(&sa)) return 0; // XXX
+
+ if (reverse) {
+ n = 0;
+ for (i = 1; i <= sa.len; i++) {
+ if ((ndigits == -1) || (n < ndigits)) {
+ if (!byte_diff(sa.s + sa.len - i - 1,1,".") || (i == sa.len)) {
+ n++;
+ if (!stralloc_cats(expand,sa.s + sa.len - i)) return 0;
+ if (i < sa.len) {
+ sa.s[sa.len - i - 1] = 0;
+ if (!stralloc_cats(expand,".")) return 0;
+ }
+ }
+ }
+ }
+ } else if (ndigits != -1) {
+ n = pos = 0;
+ for (i = 1; i <= sa.len; i++) {
+ if (n < ndigits) {
+ if (!byte_diff(sa.s + i,1,".")) { n++; pos = i; }
+ }
+ }
+ if (!stralloc_catb(expand,sa.s,pos)) return 0;
+ } else
+ if (!stralloc_cats(expand,sa.s)) return 0;
+
+ if (urlencode) {
+ stralloc_copyb(&sa,expand->s + start,expand->len - start);
+ expand->len = start;
+
+ for (i = 0; i < sa.len; ++i) {
+ ch = sa.s[i];
+ if (urlchr_table[(unsigned char)ch]) {
+ if (!stralloc_readyplus(expand,3)) return 0;
+ if (!stralloc_append(expand,"%")) return 0;
+ ascii = hextab[(unsigned char)ch >> 4];
+ if (!stralloc_append(expand,&ascii)) return 0;
+ ascii = hextab[(unsigned char)ch & 0x0f];
+ if (!stralloc_append(expand,&ascii)) return 0;
+ } else {
+ if (!stralloc_append(expand,&ch)) return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+int spf_info(char *s,const char *t)
+{
+ if (!stralloc_cats(&spfinfo,s)) return 0;
+ if (!stralloc_cats(&spfinfo,t)) return 0;
+ if (!stralloc_cats(&spfinfo," ")) return 0;
+
+ return 1;
+}
diff --git a/src/spfdnsip.c b/src/spfdnsip.c
new file mode 100644
index 0000000..e9cf9ee
--- /dev/null
+++ b/src/spfdnsip.c
@@ -0,0 +1,406 @@
+#include <unistd.h>
+#include "stralloc.h"
+#include "alloc.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "ipme.h"
+#include "str.h"
+#include "fmt.h"
+#include "scan.h"
+#include "byte.h"
+#include "now.h"
+#include "dns.h"
+#include "case.h"
+#include "spf.h"
+
+// shared by spf.c + spfdnsip.c
+
+extern stralloc dnsname;
+extern char ip4remote[4];
+extern char ip6remote[16];
+extern int flagip6;
+
+/**
+ @brief match_ip
+ compares IPv4/IPv6 addreses up to prefix length
+ @param input: ip_address1,prefix length, ip_address2
+ @return 1 ok; 0 failure
+ */
+
+int match_ip4(unsigned char ip1[4],int prefix,char ip2[4])
+{
+ stralloc iptest1 = {0};
+ stralloc iptest2 = {0};
+
+ if (flagip6) return 0;
+
+ if (ip4_bytestring(&iptest1,ip1,prefix) == prefix)
+ if (ip4_bytestring(&iptest2,ip2,prefix) == prefix)
+ if (byte_diff(iptest1.s,prefix,iptest2.s)) return 0;
+
+ return 1;
+}
+
+int match_ip6(unsigned char ip1[16],int prefix,char ip2[16])
+{
+ stralloc iptest1 = {0};
+ stralloc iptest2 = {0};
+
+ if (!flagip6) return 0;
+
+ if (ip6_bytestring(&iptest1,ip1,prefix) == prefix)
+ if (ip6_bytestring(&iptest2,ip2,prefix) == prefix)
+ if (byte_diff(iptest1.s,prefix,iptest2.s)) return 0;
+
+ return 1;
+}
+
+/**
+ @brief get_prefix
+ return integer value of prefix length
+ @param input: pointer to prefix
+ @return (int) length of prefix
+ */
+
+int get_prefix(char *prefix)
+{
+ unsigned long r;
+ int pos;
+
+ if (!prefix || *prefix == '0') {
+ if (flagip6 == 0) return 32;
+ if (flagip6 == 1) return 128;
+ }
+
+ pos = scan_ulong(prefix,&r);
+ if (!pos || (prefix[pos] && !(prefix[pos] == '/'))) return SPF_SYNTAX;
+ if (flagip6 == 0 && r > 32) return SPF_SYNTAX;
+ if (flagip6 == 1 && r > 128) return SPF_SYNTAX;
+
+ return r;
+}
+
+/* DNS Record: -------------------------------------- Fetch multiple SPF TXT RRs */
+
+/**
+ @brief spf_records
+ get TXT records for domain and extract SPF information
+ @param input: pointer stralloc domain
+ output: pointer to stralloc spf records
+ @return SPF_OK, SPF_NONE; SPF_MULTIRR, SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_records(stralloc *spfrec,stralloc *domain)
+{
+ static stralloc out = {0};
+ static stralloc spf = {0};
+ int i, k;
+ int begin;
+ int r = 0;
+
+ begin = -1;
+
+ DNS_INIT
+ r = dns_txt(&out,(const stralloc *)domain);
+ switch (r) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: return SPF_DNSSOFT; /* return 2main */
+ case DNS_NXD: return SPF_NONE;
+ }
+ r = SPF_NONE;
+
+ for (k = 0; k < out.len; ++k) {
+ if (case_starts(out.s + k,"v=spf1")) {
+ begin = k;
+ break;
+ }
+ }
+
+ if (begin >= 0) {
+ if (case_starts(out.s + k + 6,"v=spf1")) return SPF_MULTIRR; /* return 2main */
+
+ if (!stralloc_copys(&spf,"")) return SPF_NOMEM;
+ for (i = begin; i < out.len; ++i) {
+ if (out.s[i] == '\r' || out.s[i] == '\n' || out.s[i] == '\0') break;
+ if (!stralloc_append(&spf,out.s + i)) return SPF_NOMEM;
+ }
+ if (!stralloc_0(&spf)) return SPF_NOMEM;
+ if (!stralloc_copys(spfrec,spf.s)) return SPF_NOMEM;
+
+ r = SPF_OK;
+ }
+
+ return r;
+}
+
+/* Mechanisms: -------------------------------------- Lookup functions */
+
+/**
+ @brief spf_a (a; a:fqdns; a:fqdns/56)
+ compares A + AAAA records for SPF info and client host
+ @param input: pointer to spfspecification, pointer to prefix
+ @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_a(char *spfspec,char *prefix)
+{
+ stralloc sa = {0};
+ stralloc ip = {0};
+ int ipprefix, r, j;
+
+ ipprefix = get_prefix(prefix);
+ if (ipprefix < 0) return SPF_SYNTAX;
+
+ if (!stralloc_copys(&sa,spfspec)) return SPF_NOMEM;
+ if (!stralloc_readyplus(&ip,0)) return SPF_NOMEM;
+ if (!spf_info("MA/AAAA=",spfspec)) return SPF_NOMEM;
+
+ DNS_INIT
+
+ switch (dns_ip4(&ip,&sa)) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: r = SPF_DNSSOFT; break;
+ case DNS_NXD: r = SPF_NONE; break;
+ default:
+ r = SPF_NONE;
+ for (j = 0; j + 4 <= ip.len; j += 4)
+ if (match_ip4(ip.s + j,ipprefix,ip4remote))
+ return SPF_OK;
+ }
+
+ switch (dns_ip6(&ip,&sa)) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: r = SPF_DNSSOFT; break;
+ case DNS_NXD: r = SPF_NONE; break;
+ default:
+ r = SPF_NONE;
+ for (j = 0; j + 16 <= ip.len; j += 16)
+ if (match_ip6(ip.s + j,ipprefix,ip6remote))
+ return SPF_OK;
+ }
+
+ return r;
+}
+
+/**
+ @brief spf_mx (mx; mx:domain; mx:domain/24)
+ compares MX records for SPF info and client host
+ @param input: pointer to spfspecification, pointer to prefix
+ @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_mx(char *spfspec,char *prefix)
+{
+ stralloc sa = {0};
+ ipalloc ia = {0};
+ unsigned long random;
+ int ipprefix;
+ int j, r;
+
+ ipprefix = get_prefix(prefix);
+ if (ipprefix < 0) return SPF_SYNTAX;
+
+ random = now() + (getpid() << 16);
+
+ if (!stralloc_copys(&sa,spfspec)) return SPF_NOMEM;
+ if (!spf_info("MMX=",spfspec)) return SPF_NOMEM;
+
+ switch (dns_mxip(&ia,&sa,random)) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: return SPF_DNSSOFT;
+ default:
+ r = SPF_NONE;
+ for (j = 0; j < ia.len; ++j) {
+ if (byte_diff(ip6remote,16,V6localnet) && !ip6_isv4mapped(ip6remote)) {
+ if (match_ip6(&ia.ix[j].addr.ip6.d,ipprefix,ip6remote))
+ return SPF_OK;
+ }
+ if (byte_diff(ip4remote,4,V4localnet)) {
+ if (match_ip4(&ia.ix[j].addr.ip4.d,ipprefix,ip4remote))
+ return SPF_OK;
+ }
+ }
+ }
+
+ return r;
+}
+
+/**
+ @brief spf_ptr (ptr; ptr:fqdn)
+ compares PTR records from SPF info and client host
+ @param input: pointer to spfspecification; prefix not used
+ @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_ptr(char *spfspec,char *prefix)
+{
+ stralloc fqdn = {0};
+ stralloc out = {0};
+ stralloc ip = {0};
+ int slen = str_len(spfspec);
+ int rc, r;
+ int k = 0;
+ int pos;
+ int l = 0;
+
+ /* we didn't find host with the matching IP before */
+ if (dnsname.len == 7 && str_equal(dnsname.s,"unknown"))
+ return SPF_NONE;
+
+ if (!spf_info("MPTR=",spfspec)) return SPF_NOMEM;
+
+ /* the hostname found will probably be the same as before */
+ while (dnsname.len) {
+ pos = dnsname.len - slen;
+ if (pos < 0) break;
+ if (pos > 0 && dnsname.s[pos - 1] != '.') break;
+ if (case_diffb(dnsname.s + pos,slen,spfspec)) break;
+ return SPF_OK;
+ }
+
+ /* ok, either it's the first test or it's a very weired setup
+ Assumptions:
+ ip -> inverse DNS name (only one!)
+ inverse DNS name -> (same) ip (only one!)
+ */
+
+
+ if (!stralloc_readyplus(&fqdn,255)) return SPF_NOMEM;
+ if (!stralloc_readyplus(&out,255)) return SPF_NOMEM;
+ if (!stralloc_readyplus(&ip,32)) return SPF_NOMEM;
+
+ if (flagip6) {
+ rc = dns_name6(&out,ip6remote); // usually: 2. . .ip6.addr => only one
+ switch (rc) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_COM: r = SPF_DNSSOFT; break;
+ case DNS_ERR: r = SPF_NONE; break;
+ case DNS_NXD: r = SPF_NONE; break;
+ default: r = SPF_NONE; l++;
+ if (l > LOOKUP_LIMIT) { r = SPF_ERROR; break; }
+ switch (dns_ip6(&ip,&out)) { // theoretical more IPs cound be retrieved
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: r = SPF_DNSSOFT; break;
+ case DNS_NXD: r = SPF_NONE; break;
+ default: r = SPF_NONE;
+ for (k = 0; k + 16 <= ip.len; k += 16) {
+ if (k > 32 * LOOKUP_LIMIT) { r = SPF_ERROR; break; }
+ if (match_ip6(ip.s + k,128,ip6remote)) {
+ if (!dnsname.len)
+ if (!stralloc_copy(&dnsname,&out)) return SPF_NOMEM;
+ pos = out.len - slen;
+ if (pos < 0) continue;
+ if (pos > 0 && out.s[pos - 1] != '.') continue;
+ if (case_diffb(out.s + pos,slen,spfspec)) continue;
+
+ if (!stralloc_copy(&dnsname,&out)) return SPF_NOMEM;
+ r = SPF_OK;
+ }
+ }
+ }
+ }
+ } else { // IP4 branch
+ rc = dns_name4(&out,ip4remote); // usual answer: d.c.b.e.in-arpa.addr for IP4 a.b.c.d => only one
+ switch (rc) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: r = SPF_DNSSOFT; break;
+ case DNS_NXD: r = SPF_NONE; break;
+ default: r = SPF_NONE; l++;
+ if (l > LOOKUP_LIMIT) { r = SPF_ERROR; break; }
+ switch (dns_ip4(&ip,&out)) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: r = SPF_DNSSOFT; break;
+ case DNS_NXD: r = SPF_NONE; break;
+ default: r = SPF_NONE;
+ for (k = 0; k + 4 <= ip.len; k += 4) {
+ if (k > 32 * LOOKUP_LIMIT) { r = SPF_ERROR; break; }
+ if (match_ip4(ip.s + k,32,ip4remote)) {
+ if (!dnsname.len)
+ if (!stralloc_copy(&dnsname,&out)) return SPF_NOMEM;
+ pos = out.len - slen;
+ if (pos < 0) continue;
+ if (pos > 0 && out.s[pos - 1] != '.') continue;
+ if (case_diffb(out.s + pos,slen,spfspec)) continue;
+
+ if (!stralloc_copy(&dnsname,&out)) return SPF_NOMEM;
+ r = SPF_OK;
+ }
+ }
+ }
+ }
+ }
+ if (!dnsname.len)
+ if (!stralloc_copys(&dnsname,"unknown")) return SPF_NOMEM;
+
+ return r;
+}
+
+/**
+ @brief spf_ip4 (ip4; ip4:fqdn; ip4:fqdn/24)
+ compares A records for SPF info and client host
+ @param input: pointer to spfspecification, pointer to prefix
+ @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_ip4(char *spfspec,char *prefix)
+{
+ char spfip[4];
+
+ if (flagip6) return SPF_NONE;
+ int ipprefix = get_prefix(prefix);
+
+ if (ipprefix < 0) return SPF_SYNTAX;
+ if (!ip4_scan(spfspec,spfip)) return SPF_SYNTAX;
+
+ if (!spf_info("MIPv4=",spfspec)) return SPF_NOMEM;
+ if (!match_ip4(spfip,ipprefix,ip4remote)) return SPF_NONE;
+
+ return SPF_OK;
+}
+
+/**
+ @brief spf_ip6 (ip6; ip6:fqdn; ip6:fqdn/56)
+ compares AAAA records for SPF info and client host
+ @param input: pointer to spfspecification, pointer to prefix
+ @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_ip6(char *spfspec,char *prefix)
+{
+ char spfip[16];
+
+ if (!flagip6) return SPF_NONE;
+ int ipprefix = get_prefix(prefix);
+
+ if (ipprefix < 0) return SPF_SYNTAX;
+ if (!ip6_scan(spfspec,spfip)) return SPF_SYNTAX;
+
+ if (!spf_info("MIPv6=",spfspec)) return SPF_NOMEM;
+ if (!match_ip6(spfip,ipprefix,ip6remote)) return SPF_NONE;
+
+ return SPF_OK;
+}
+
+/**
+ @brief spf_exists (exists; exists:fqdn)
+ simply looks for a A records only for SPF info and client host
+ @param input: pointer to spfspecification, prefix not used
+ @return SPF_OK, SPF_NONE; SPF_DNSSOFT, SPF_NOMEM
+ */
+
+int spf_exists(char *spfspec,char *prefix)
+{
+ stralloc sa = {0};
+ stralloc ip = {0};
+
+ if (!stralloc_copys(&sa,spfspec)) return SPF_NOMEM;
+ if (!spf_info("MExists=",spfspec)) return SPF_NOMEM;
+
+ switch (dns_ip4(&ip,&sa)) {
+ case DNS_MEM: return SPF_NOMEM;
+ case DNS_ERR: return SPF_DNSSOFT;
+ case DNS_NXD: return SPF_NONE;
+ default: return SPF_OK;
+ }
+
+}
diff --git a/src/spfquery.c b/src/spfquery.c
new file mode 100644
index 0000000..8c642ee
--- /dev/null
+++ b/src/spfquery.c
@@ -0,0 +1,98 @@
+#include <string.h>
+#include <unistd.h>
+#include "buffer.h"
+#include "stralloc.h"
+#include "alloc.h"
+#include "spf.h"
+#include "exit.h"
+#include "dns.h"
+#include "str.h"
+#include "byte.h"
+#include "logmsg.h"
+
+#define WHO "spfquery"
+
+void die(int e,char *s) { buffer_putsflush(buffer_2,s); _exit(e); }
+void die_nomem() { die(111,"fatal: out of memory\n"); }
+
+static stralloc heloin = {0};
+static stralloc mfin = {0};
+static stralloc spflocal = {0};
+static stralloc spfbounce = {0};
+
+int main(int argc,char **argv)
+{
+ stralloc spfip = {0};
+ int flag = 0;
+ int r;
+ int verbose = 0;
+ flagip6 = 1;
+
+ if (argc < 4)
+ logmsg(WHO,100,USAGE,"spfquery <sender-ip> <sender-helo/ehlo> <envelope-from> [<local rules>] [-v(erbose) ]\n");
+
+ if (!stralloc_copys(&spfip,argv[1])) die_nomem();
+ if (!stralloc_0(&spfip)) die_nomem();
+ r = byte_chr(spfip.s,spfip.len,':');
+ if (r < spfip.len) flag = 1;
+
+ if (!stralloc_copys(&heloin,argv[2])) die_nomem();
+ if (!stralloc_0(&heloin)) die_nomem();
+
+ if (!stralloc_copys(&mfin,argv[3])) die_nomem();
+ if (!stralloc_0(&mfin)) die_nomem();
+
+ if (argc > 4) {
+ if (!byte_diff(argv[4],2,"-v")) verbose = 1;
+ else {
+ if (!stralloc_copys(&spflocal,argv[4])) die_nomem();
+ if (spflocal.len && !stralloc_0(&spflocal)) die_nomem();
+ }
+ }
+
+ if (argc > 5) {
+ if (!byte_diff(argv[5],2,"-v")) verbose = 1;
+ }
+
+ if (!stralloc_copys(&spfexplain,SPF_DEFEXP)) die_nomem();
+ if (!stralloc_0(&spfexplain)) die_nomem();
+
+ DNS_INIT
+ r = spf_query(spfip.s,heloin.s,mfin.s,"localhost",flag);
+ if (r == SPF_NOMEM) die_nomem();
+
+ buffer_puts(buffer_1,"result=");
+ switch (r) {
+ case SPF_ME: buffer_puts(buffer_1,"loopback"); break;
+ case SPF_OK: buffer_puts(buffer_1,"pass"); break;
+ case SPF_NONE: buffer_puts(buffer_1,"none"); break;
+ case SPF_UNKNOWN: buffer_puts(buffer_1,"unknown"); break;
+ case SPF_NEUTRAL: buffer_puts(buffer_1,"neutral"); break;
+ case SPF_SOFTFAIL: buffer_puts(buffer_1,"softfail"); break;
+ case SPF_FAIL: buffer_puts(buffer_1,"fail"); break;
+ case SPF_ERROR: buffer_puts(buffer_1,"error"); break;
+ case SPF_SYNTAX: buffer_puts(buffer_1,"IP address syntax error"); break;
+ default: buffer_puts(buffer_1,"undefined"); break;
+ }
+
+ buffer_putsflush(buffer_1,"\n");
+ if (r == SPF_SYNTAX) _exit(1);
+
+ if (verbose) {
+ buffer_puts(buffer_1,"SPF records read: \n");
+ buffer_put(buffer_1,spfrecord.s,spfrecord.len);
+ }
+
+ buffer_puts(buffer_1,"SPF information evaluated: ");
+ buffer_put(buffer_1,spfinfo.s,spfinfo.len);
+ buffer_putsflush(buffer_1,"\n");
+
+ if (r == SPF_FAIL) {
+ buffer_puts(buffer_1,"SPF results returned: ");
+ if (!spf_parse(&spfbounce,spfexpmsg.s,expdomain.s)) die_nomem();
+ buffer_put(buffer_1,spfbounce.s,spfbounce.len);
+ buffer_putsflush(buffer_1,"\n");
+ }
+
+ _exit(0);
+}
diff --git a/src/splogger.c b/src/splogger.c
new file mode 100644
index 0000000..4e64590
--- /dev/null
+++ b/src/splogger.c
@@ -0,0 +1,70 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "error.h"
+#include "buffer.h"
+#include "exit.h"
+#include "str.h"
+#include "scan.h"
+#include "fmt.h"
+
+char buf[800]; /* syslog truncates long lines (or crashes); GPACIC */
+int bufpos = 0; /* 0 <= bufpos < sizeof(buf) */
+int flagcont = 0;
+int priority; /* defined if flagcont */
+char stamp[FMT_ULONG + FMT_ULONG + 3]; /* defined if flagcont */
+
+void stamp_make()
+{
+ struct timeval tv;
+ char *s;
+ gettimeofday(&tv,(struct timezone *) 0);
+ s = stamp;
+ s += fmt_ulong(s,(unsigned long) tv.tv_sec);
+ *s++ = '.';
+ s += fmt_uint0(s,(unsigned int) tv.tv_usec,6);
+ *s = 0;
+}
+
+void flush()
+{
+ if (bufpos) {
+ buf[bufpos] = 0;
+ if (flagcont)
+ syslog(priority,"%s+%s",stamp,buf); /* logger folds invisibly; GPACIC */
+ else {
+ stamp_make();
+ priority = LOG_INFO;
+ if (str_start(buf,"warning:")) priority = LOG_WARNING;
+ if (str_start(buf,"alert:")) priority = LOG_ALERT;
+ syslog(priority,"%s %s",stamp,buf);
+ flagcont = 1;
+ }
+ }
+ bufpos = 0;
+}
+
+int main(int argc,char **argv)
+{
+ char ch;
+
+ if (argv[1])
+ if (argv[2]) {
+ unsigned long facility;
+ scan_ulong(argv[2],&facility);
+ openlog(argv[1],0,facility << 3);
+ }
+ else
+ openlog(argv[1],0,LOG_MAIL);
+ else
+ openlog("splogger",0,LOG_MAIL);
+
+ for (;;) {
+ if (buffer_get(buffer_0,&ch,1) < 1) _exit(0);
+ if (ch == '\n') { flush(); flagcont = 0; continue; }
+ if (bufpos == sizeof(buf) - 1) flush();
+ if ((ch < 32) || (ch > 126)) ch = '?'; /* logger truncates at 0; GPACIC */
+ buf[bufpos++] = ch;
+ }
+}
diff --git a/src/srs2.c b/src/srs2.c
new file mode 100644
index 0000000..1bb431b
--- /dev/null
+++ b/src/srs2.c
@@ -0,0 +1,641 @@
+/* Copyright (c) 2004 Shevek (srs@anarres.org)
+ * All rights reserved.
+ *
+ * This file is a part of libsrs2 from http://www.libsrs2.org/
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, under the terms of either the GNU General Public
+ * License version 2 or the BSD license, at the discretion of the
+ * user. Copies of these licenses have been included in the libsrs2
+ * distribution. See the the file called LICENSE for more
+ * information.
+ */
+
+/* This is a minimal adapted s/qmail version; it requires complete
+ refactoring:
+
+ a) Use stralloc for addresses
+ b) Replace stdio, str*, and mem* functions
+ c) Use tai64 for timestamp function
+ d) Remove va args
+ e) Reduce code by 50%
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <time.h> /* time */
+#include <sys/types.h> /* tyepdefs */
+#include <sys/time.h> /* timeval / timezone struct */
+#include <string.h> /* memcpy, strcpy, memset */
+#include "srs2.h"
+#include "sha1.h"
+
+#ifndef HAVE_STRCASECMP
+# ifdef HAVE__STRICMP
+# define strcasecmp _stricmp
+# endif
+#endif
+
+#ifndef HAVE_STRNCASECMP
+# ifdef HAVE__STRNICMP
+# define strncasecmp _strnicmp
+# endif
+#endif
+
+ /* Use this */
+#define STRINGP(s) ((s != NULL) && (*(s) != '\0'))
+
+static const char *srs_separators = "=-+";
+
+static srs_malloc_t srs_f_malloc = malloc;
+static srs_realloc_t srs_f_realloc = realloc;
+static srs_free_t srs_f_free = free;
+
+int srs_set_malloc(srs_malloc_t m, srs_realloc_t r, srs_free_t f)
+{
+ srs_f_malloc = m;
+ srs_f_realloc = r;
+ srs_f_free = f;
+ return SRS_SUCCESS;
+}
+
+#define X(e,s) if (code == e) return s;
+
+const char *srs_strerror(int code)
+{
+ X(0,"")
+ /* Simple errors */
+ X(SRS_SUCCESS,"Success")
+ X(SRS_ENOTSRSADDRESS,"Not an SRS address.")
+
+ /* Config errors */
+ X(SRS_ENOSECRETS,"No secrets in SRS configuration.")
+ X(SRS_ESEPARATORINVALID,"Invalid separator suggested.")
+
+ /* Input errors */
+ X(SRS_ENOSENDERATSIGN,"No at sign in sender address")
+ X(SRS_EBUFTOOSMALL,"Buffer too small.")
+
+ /* Syntax errors */
+ X(SRS_ENOSRS0HOST,"No host in SRS0 address.")
+ X(SRS_ENOSRS0USER,"No user in SRS0 address.")
+ X(SRS_ENOSRS0HASH,"No hash in SRS0 address.")
+ X(SRS_ENOSRS0STAMP,"No timestamp in SRS0 address.")
+ X(SRS_ENOSRS1HOST,"No host in SRS1 address.")
+ X(SRS_ENOSRS1USER,"No user in SRS1 address.")
+ X(SRS_ENOSRS1HASH,"No hash in SRS1 address.")
+ X(SRS_EBADTIMESTAMPCHAR,"Bad base32 character in timestamp.")
+ X(SRS_EHASHTOOSHORT,"Hash too short in SRS address.")
+
+ /* SRS errors */
+ X(SRS_ETIMESTAMPOUTOFDATE,"Time stamp out of date.")
+ X(SRS_EHASHINVALID,"Hash invalid in SRS address.")
+
+ return "Unknown SRS error.";
+}
+
+srs_t *srs_new()
+{
+ srs_t *srs = (srs_t *)srs_f_malloc(sizeof(srs_t));
+ srs_init(srs);
+ return srs;
+}
+
+void srs_init(srs_t *srs)
+{
+ memset(srs, 0, sizeof(srs_t));
+ srs->secrets = NULL;
+ srs->numsecrets = 0;
+ srs->separator = '=';
+ srs->maxage = 21;
+ srs->hashlen = 4;
+ srs->hashmin = srs->hashlen;
+ srs->alwaysrewrite = FALSE;
+}
+
+void srs_free(srs_t *srs)
+{
+ int i;
+ for (i = 0; i < srs->numsecrets; i++) {
+ memset(srs->secrets[i], 0, strlen(srs->secrets[i]));
+ srs_f_free(srs->secrets[i]);
+ srs->secrets[i] = '\0';
+ }
+ srs_f_free(srs);
+}
+
+int srs_add_secret(srs_t *srs, const char *secret)
+{
+ int newlen = (srs->numsecrets + 1) * sizeof(char *);
+ srs->secrets = (char **)srs_f_realloc(srs->secrets, newlen);
+ srs->secrets[srs->numsecrets++] = strdup(secret);
+ return SRS_SUCCESS;
+}
+
+const char *srs_get_secret(srs_t *srs, int idx)
+{
+ if (idx < srs->numsecrets)
+ return srs->secrets[idx];
+ return NULL;
+}
+
+#define SRS_PARAM_DEFINE(n, t) \
+ int srs_set_ ## n (srs_t *srs, t value) { \
+ srs->n = value; \
+ return SRS_SUCCESS; \
+ } \
+ t srs_get_ ## n (srs_t *srs) { \
+ return srs->n; \
+ }
+
+int srs_set_separator(srs_t *srs, char value)
+{
+ if (strchr(srs_separators, value) == NULL)
+ return SRS_ESEPARATORINVALID;
+ srs->separator = value;
+ return SRS_SUCCESS;
+}
+
+char srs_get_separator(srs_t *srs)
+{
+ return srs->separator;
+}
+
+SRS_PARAM_DEFINE(maxage, int)
+ /* XXX Check hashlen >= hashmin */
+SRS_PARAM_DEFINE(hashlen, int)
+SRS_PARAM_DEFINE(hashmin, int)
+SRS_PARAM_DEFINE(alwaysrewrite, srs_bool)
+SRS_PARAM_DEFINE(noforward, srs_bool)
+SRS_PARAM_DEFINE(noreverse, srs_bool)
+
+/* Don't mess with these unless you know what you're doing well
+ * enough to rewrite the timestamp functions. These are based on
+ * a 2 character timestamp. Changing these in the wild is probably
+ * a bad idea. */
+#define SRS_TIME_PRECISION (60 * 60 * 24) /* One day */
+#define SRS_TIME_BASEBITS 5 /* 2^5 = 32 = strlen(CHARS) */
+/* This had better be a real variable since we do arithmethic
+ * with it. */
+const char *SRS_TIME_BASECHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+#define SRS_TIME_SIZE 2
+#define SRS_TIME_SLOTS (1<<(SRS_TIME_BASEBITS<<(SRS_TIME_SIZE-1)))
+
+int srs_timestamp_create(srs_t *srs, char *buf, time_t now)
+{
+ now = now / SRS_TIME_PRECISION;
+ buf[1] = SRS_TIME_BASECHARS[now & ((1 << SRS_TIME_BASEBITS) - 1)];
+ now = now >> SRS_TIME_BASEBITS;
+ buf[0] = SRS_TIME_BASECHARS[now & ((1 << SRS_TIME_BASEBITS) - 1)];
+ buf[2] = '\0';
+ return SRS_SUCCESS;
+}
+
+int srs_timestamp_check(srs_t *srs, const char *stamp)
+{
+ const char *sp;
+ char *bp;
+ int off;
+ time_t now;
+ time_t then;
+
+ /* We had better go around this loop exactly twice! */
+ then = 0;
+ for (sp = stamp; *sp; sp++) {
+ bp = strchr(SRS_TIME_BASECHARS, toupper(*sp));
+ if (bp == NULL)
+ return SRS_EBADTIMESTAMPCHAR;
+ off = bp - SRS_TIME_BASECHARS;
+ then = (then << SRS_TIME_BASEBITS) | off;
+ }
+
+ time(&now);
+ now = (now / SRS_TIME_PRECISION) % SRS_TIME_SLOTS;
+ while (now < then)
+ now = now + SRS_TIME_SLOTS;
+
+ if (now <= then + srs->maxage)
+ return SRS_SUCCESS;
+ return SRS_ETIMESTAMPOUTOFDATE;
+}
+
+const char *SRS_HASH_BASECHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+static void srs_hash_create_v(srs_t *srs, int idx, char *buf, int nargs, va_list ap)
+{
+ sha1_ctx ctx;
+ char srshash[SHA1_DIGESTSIZE + 1];
+ char *secret;
+ char *data;
+ int len;
+ char *lcdata;
+ unsigned char *hp;
+ char *bp;
+ int i;
+ int j;
+
+ secret = srs->secrets[idx];
+ sha1_init(&ctx);
+ sha1_update(&ctx, secret, strlen(secret));
+
+ for (i = 0; i < nargs; i++) {
+ data = va_arg(ap, char *);
+ len = strlen(data);
+ lcdata = alloca(len + 1);
+ for (j = 0; j < len; j++) {
+ if (isupper(data[j]))
+ lcdata[j] = tolower(data[j]);
+ else
+ lcdata[j] = data[j];
+ }
+ sha1_update(&ctx, lcdata, len);
+ }
+
+ sha1_final(srshash, &ctx); /* args inverted */
+ srshash[SHA1_DIGESTSIZE] = '\0';
+
+ /* A little base64 encoding. Just a little. */
+ hp = (unsigned char *)srshash;
+ bp = buf;
+ for (i = 0; i < srs->hashlen; i++) {
+ switch (i & 0x03) {
+ default: /* NOTREACHED */
+ case 0:
+ j = (*hp >> 2);
+ break;
+ case 1:
+ j = ((*hp & 0x03) << 4) |
+ ((*(hp + 1) & 0xF0) >> 4);
+ hp++;
+ break;
+ case 2:
+ j = ((*hp & 0x0F) << 2) |
+ ((*(hp + 1) & 0xC0) >> 6);
+ hp++;
+ break;
+ case 3:
+ j = (*hp++ & 0x3F);
+ break;
+ }
+ *bp++ = SRS_HASH_BASECHARS[j];
+ }
+
+ *bp = '\0';
+ buf[srs->hashlen] = '\0';
+}
+
+int srs_hash_create(srs_t *srs, char *buf, int nargs, ...)
+{
+ va_list ap;
+
+ if (srs->numsecrets == 0)
+ return SRS_ENOSECRETS;
+ if (srs->secrets == NULL)
+ return SRS_ENOSECRETS;
+ if (srs->secrets[0] == NULL)
+ return SRS_ENOSECRETS;
+
+ va_start(ap, nargs);
+ srs_hash_create_v(srs, 0, buf, nargs, ap);
+ va_end(ap);
+
+ return SRS_SUCCESS;
+}
+
+int srs_hash_check(srs_t *srs, char *hash, int nargs, ...)
+{
+ va_list ap;
+ char *srshash;
+ char *tmp;
+ int len;
+ int i;
+
+ len = strlen(hash);
+ if (len < srs->hashmin)
+ return SRS_EHASHTOOSHORT;
+ if (len < srs->hashlen) {
+ tmp = alloca(srs->hashlen + 1);
+ strncpy(tmp, hash, srs->hashlen);
+ tmp[srs->hashlen] = '\0';
+ hash = tmp;
+ len = srs->hashlen;
+ }
+
+ for (i = 0; i < srs->numsecrets; i++) {
+ va_start(ap, nargs);
+ srshash = alloca(srs->hashlen + 1);
+ srs_hash_create_v(srs, i, srshash, nargs, ap);
+ va_end(ap);
+ if (strncasecmp(hash, srshash, len) == 0)
+ return SRS_SUCCESS;
+ }
+
+ return SRS_EHASHINVALID;
+}
+
+int srs_compile_shortcut(srs_t *srs,
+ char *buf, int buflen,
+ char *sendhost, char *senduser,
+ const char *aliashost) {
+ char *srshash;
+ char srsstamp[SRS_TIME_SIZE + 1];
+ int len;
+ int ret;
+
+ /* This never happens if we get called from guarded() */
+ if ((strncasecmp(senduser, SRS0TAG, 4) == 0) &&
+ (strchr(srs_separators, senduser[4]) != NULL)) {
+ sendhost = senduser + 5;
+ if (*sendhost == '\0')
+ return SRS_ENOSRS0HOST;
+ senduser = strchr(sendhost, SRSSEP);
+ if ((senduser == NULL) || (*senduser == '\0'))
+ return SRS_ENOSRS0USER;
+ }
+
+ len = strlen(SRS0TAG) + 1 +
+ srs->hashlen + 1 +
+ SRS_TIME_SIZE + 1 +
+ strlen(sendhost) + 1 + strlen(senduser)
+ + 1 + strlen(aliashost);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+
+ ret = srs_timestamp_create(srs, srsstamp, time(NULL));
+ if (ret != SRS_SUCCESS)
+ return ret;
+ srshash = alloca(srs->hashlen + 1);
+ ret = srs_hash_create(srs, srshash,3, srsstamp, sendhost, senduser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+
+ sprintf(buf, SRS0TAG "%c%s%c%s%c%s%c%s@%s", srs->separator,
+ srshash, SRSSEP, srsstamp, SRSSEP,
+ sendhost, SRSSEP, senduser,
+ aliashost);
+
+ return SRS_SUCCESS;
+}
+
+int srs_compile_guarded(srs_t *srs,
+ char *buf, int buflen,
+ char *sendhost, char *senduser,
+ const char *aliashost) {
+ char *srshost;
+ char *srsuser;
+ char *srshash;
+ int len;
+ int ret;
+
+ if ((strncasecmp(senduser, SRS1TAG, 4) == 0) &&
+ (strchr(srs_separators, senduser[4]) != NULL)) {
+ /* Used as a temporary convenience var */
+ srshash = senduser + 5;
+ if (*srshash == '\0')
+ return SRS_ENOSRS1HASH;
+ /* Used as a temporary convenience var */
+ srshost = strchr(srshash, SRSSEP);
+ if (!STRINGP(srshost))
+ return SRS_ENOSRS1HOST;
+ *srshost++ = '\0';
+ srsuser = strchr(srshost, SRSSEP);
+ if (!STRINGP(srsuser))
+ return SRS_ENOSRS1USER;
+ *srsuser++ = '\0';
+ srshash = alloca(srs->hashlen + 1);
+ ret = srs_hash_create(srs, srshash, 2, srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ len = strlen(SRS1TAG) + 1 +
+ srs->hashlen + 1 +
+ strlen(srshost) + 1 + strlen(srsuser)
+ + 1 + strlen(aliashost);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+ sprintf(buf, SRS1TAG "%c%s%c%s%c%s@%s", srs->separator,
+ srshash, SRSSEP,
+ srshost, SRSSEP, srsuser,
+ aliashost);
+ return SRS_SUCCESS;
+ }
+ else if ((strncasecmp(senduser, SRS0TAG, 4) == 0) &&
+ (strchr(srs_separators, senduser[4]) != NULL)) {
+ srsuser = senduser + 4;
+ srshost = sendhost;
+ srshash = alloca(srs->hashlen + 1);
+ ret = srs_hash_create(srs, srshash, 2, srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ len = strlen(SRS1TAG) + 1 +
+ srs->hashlen + 1 +
+ strlen(srshost) + 1 + strlen(srsuser)
+ + 1 + strlen(aliashost);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+ sprintf(buf, SRS1TAG "%c%s%c%s%c%s@%s", srs->separator,
+ srshash, SRSSEP,
+ srshost, SRSSEP, srsuser,
+ aliashost);
+ }
+ else {
+ return srs_compile_shortcut(srs, buf, buflen,
+ sendhost, senduser, aliashost);
+ }
+
+ return SRS_SUCCESS;
+}
+
+int srs_parse_shortcut(srs_t *srs, char *buf, int buflen, char *senduser)
+{
+ char *srshash;
+ char *srsstamp;
+ char *srshost;
+ char *srsuser;
+ int ret;
+
+ if (strncasecmp(senduser, SRS0TAG, 4) == 0) {
+ srshash = senduser + 5;
+ if (!STRINGP(srshash))
+ return SRS_ENOSRS0HASH;
+ srsstamp = strchr(srshash, SRSSEP);
+ if (!STRINGP(srsstamp))
+ return SRS_ENOSRS0STAMP;
+ *srsstamp++ = '\0';
+ srshost = strchr(srsstamp, SRSSEP);
+ if (!STRINGP(srshost))
+ return SRS_ENOSRS0HOST;
+ *srshost++ = '\0';
+ srsuser = strchr(srshost, SRSSEP);
+ if (!STRINGP(srsuser))
+ return SRS_ENOSRS0USER;
+ *srsuser++ = '\0';
+ ret = srs_timestamp_check(srs, srsstamp);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ ret = srs_hash_check(srs, srshash, 3, srsstamp,
+ srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ sprintf(buf, "%s@%s", srsuser, srshost);
+ return SRS_SUCCESS;
+ }
+
+ return SRS_ENOTSRSADDRESS;
+}
+
+int srs_parse_guarded(srs_t *srs, char *buf, int buflen, char *senduser)
+{
+ char *srshash;
+ char *srshost;
+ char *srsuser;
+ int ret;
+
+ if (strncasecmp(senduser, SRS1TAG, 4) == 0) {
+ srshash = senduser + 5;
+ if (!STRINGP(srshash))
+ return SRS_ENOSRS1HASH;
+ srshost = strchr(srshash, SRSSEP);
+ if (!STRINGP(srshost))
+ return SRS_ENOSRS1HOST;
+ *srshost++ = '\0';
+ srsuser = strchr(srshost, SRSSEP);
+ if (!STRINGP(srsuser))
+ return SRS_ENOSRS1USER;
+ *srsuser++ = '\0';
+ ret = srs_hash_check(srs, srshash, 2, srshost, srsuser);
+ if (ret != SRS_SUCCESS)
+ return ret;
+ sprintf(buf, SRS0TAG "%s@%s", srsuser, srshost);
+ return SRS_SUCCESS;
+ }
+ else {
+ return srs_parse_shortcut(srs, buf, buflen, senduser);
+ }
+}
+
+int srs_forward(srs_t *srs, char *buf, int buflen,
+ const char *sender, const char *alias)
+{
+ char *senduser;
+ char *sendhost;
+ char *tmp;
+ int len;
+
+ if (srs->noforward)
+ return SRS_ENOTREWRITTEN;
+
+ /* This is allowed to be a plain domain */
+ while ((tmp = strchr(alias, '@')) != NULL)
+ alias = tmp + 1;
+
+ tmp = strchr(sender, '@');
+ if (tmp == NULL)
+ return SRS_ENOSENDERATSIGN;
+ sendhost = tmp + 1;
+
+ len = strlen(sender);
+
+ if (! srs->alwaysrewrite) {
+ if (strcasecmp(sendhost, alias) == 0) {
+ if (strlen(sender) >= buflen)
+ return SRS_EBUFTOOSMALL;
+ strcpy(buf, sender);
+ return SRS_SUCCESS;
+ }
+ }
+
+ /* Reconstruct the whole show into our alloca() buffer. */
+ senduser = alloca(len + 1);
+ strcpy(senduser, sender);
+ tmp = (senduser + (tmp - sender));
+ sendhost = tmp + 1;
+ *tmp = '\0';
+
+ return srs_compile_guarded(srs, buf, buflen,
+ sendhost, senduser, alias);
+}
+
+int srs_forward_alloc(srs_t *srs, char **sptr,
+ const char *sender, const char *alias)
+{
+ char *buf;
+ int slen;
+ int alen;
+ int len;
+ int ret;
+
+ if (srs->noforward)
+ return SRS_ENOTREWRITTEN;
+
+ slen = strlen(sender);
+ alen = strlen(alias);
+
+ /* strlen(SRSxTAG) + strlen("====+@") < 64 */
+ len = slen + alen + srs->hashlen + SRS_TIME_SIZE + 64;
+ buf = (char *)srs_f_malloc(len);
+
+ ret = srs_forward(srs, buf, len, sender, alias);
+
+ if (ret == SRS_SUCCESS)
+ *sptr = buf;
+ else
+ srs_f_free(buf);
+
+ return ret;
+}
+
+int srs_reverse(srs_t *srs, char *buf, int buflen, const char *sender)
+{
+ char *senduser;
+ char *tmp;
+ int len;
+
+ if (!SRS_IS_SRS_ADDRESS(sender))
+ return SRS_ENOTSRSADDRESS;
+
+ if (srs->noreverse)
+ return SRS_ENOTREWRITTEN;
+
+ len = strlen(sender);
+ if (len >= buflen)
+ return SRS_EBUFTOOSMALL;
+ senduser = alloca(len + 1);
+ strcpy(senduser, sender);
+
+ /* We don't really care about the host for reversal. */
+ tmp = strchr(senduser, '@');
+ if (tmp != NULL)
+ *tmp = '\0';
+ return srs_parse_guarded(srs, buf, buflen, senduser);
+}
+
+int srs_reverse_alloc(srs_t *srs, char **sptr, const char *sender)
+{
+ char *buf;
+ int len;
+ int ret;
+
+ *sptr = NULL;
+
+ if (!SRS_IS_SRS_ADDRESS(sender))
+ return SRS_ENOTSRSADDRESS;
+
+ if (srs->noreverse)
+ return SRS_ENOTREWRITTEN;
+
+ len = strlen(sender) + 1;
+ buf = (char *)srs_f_malloc(len);
+
+ ret = srs_reverse(srs, buf, len, sender);
+
+ if (ret == SRS_SUCCESS)
+ *sptr = buf;
+ else
+ srs_f_free(buf);
+
+ return ret;
+}
diff --git a/src/srsforward.c b/src/srsforward.c
new file mode 100644
index 0000000..c8aefc9
--- /dev/null
+++ b/src/srsforward.c
@@ -0,0 +1,169 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include "control.h"
+#include "sig.h"
+#include "constmap.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "env.h"
+#include "qmail.h"
+#include "auto_qmail.h"
+#include "buffer.h"
+#include "str.h"
+#include "fmt.h"
+#include "stralloc.h"
+#include "logmsg.h"
+#include "srs2.h"
+
+#define WHO "srsforward"
+
+void die_nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+void die_control() { logmsg(WHO,110,FATAL,"Unable to read control files"); }
+
+struct qmail qqt;
+char *srsdomaininfo = 0;
+stralloc srsdomains = {0};
+struct constmap mapsrsdomains;
+stralloc srshost = {0};
+stralloc srserror = {0};
+
+/** @file srsforward.c
+ @brief forwarding mails with SRS enhanced addresss
+ @return 0 on success (forwarded or not)
+ -3 SRS error with error output
+ 111 no memory / processing error
+ 110 control file not readable
+*/
+
+static int srserror_str(int code) {
+ if (!stralloc_copys(&srserror,"SRS: ")) die_nomem();
+ if (!stralloc_cats(&srserror,srs_strerror(code))) die_nomem();
+ if (!stralloc_0(&srserror)) die_nomem();
+ return -3;
+}
+
+ssize_t mywrite(int fd,char *buf,int len)
+{
+ qmail_put(&qqt,buf,len);
+ return len;
+}
+
+char inbuf[BUFFER_INSIZE];
+char outbuf[1];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+buffer bo = BUFFER_INIT(mywrite,-1,outbuf,sizeof(outbuf));
+
+char num[FMT_ULONG];
+
+int main(int argc,char **argv)
+{
+ int i, j, r;
+ char *qqx;
+ srs_t *srs;
+ stralloc cookie = {0};
+ char separator = '=';
+ char srssender[512];
+ char *host = 0;
+ char *sender = 0;
+ char *dtline = 0;
+ char *sendhost = 0;
+ int alwaysrewrite = 0;
+
+ sig_pipeignore();
+
+ sender = env_get("NEWSENDER");
+ if (!sender)
+ logmsg(WHO,100,FATAL,"NEWSENDER not set");
+ host = env_get("HOST");
+ if (!host)
+ logmsg(WHO,100,FATAL,"HOST not set");
+ dtline = env_get("DTLINE");
+ if (!dtline)
+ logmsg(WHO,100,FATAL,"DTLINE not set");
+
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+
+ if (!stralloc_cats(&srshost,"!")) die_nomem();
+ if (!stralloc_cats(&srshost,host)) die_nomem();
+
+ switch (control_readfile(&srsdomains,"control/srsdomains",0)) {
+ case -1: die_control();
+ case 0: if (!constmap_init(&mapsrsdomains,"",0,1)) die_nomem(); break;
+ case 1: if (!constmap_init(&mapsrsdomains,srsdomains.s,srsdomains.len,1)) die_nomem(); break;
+ }
+ if (constmap(&mapsrsdomains,srshost.s,srshost.len)) return 0; // domain blacklisted
+ if ((srsdomaininfo = constmap(&mapsrsdomains,host,str_len(host))) == 0) {
+ if ((srsdomaininfo = constmap(&mapsrsdomains,"*",1)) == 0) return 0; // '*' means always SRS
+ else alwaysrewrite = 1;
+ }
+
+ if (*srsdomaininfo) {
+ i = str_chr(srsdomaininfo,'|'); // multiple cookies; separated by ' '
+ if (srsdomaininfo[i] == '|') {
+ srsdomaininfo[i] = 0;
+ j = str_chr(srsdomaininfo + i + 1,'|');
+ if (srsdomaininfo[i + j + 1] == '|') {
+ srsdomaininfo[i + j + 1] = 0;
+ sendhost = srsdomaininfo + i + j + 2; // separator: - + =
+ }
+ separator = srsdomaininfo[i + 1];
+ }
+ if (!stralloc_copys(&cookie,srsdomaininfo)) die_nomem();
+ if (!stralloc_0(&cookie)) die_nomem();
+ if (!stralloc_copys(&srshost,"")) die_nomem();
+ if (*sendhost) {
+ j = str_len(sendhost);
+ if (sendhost[j - 1] == '.') {
+ if (!stralloc_copys(&srshost,sendhost)) die_nomem();
+ if (!stralloc_cats(&srshost,host)) die_nomem();
+ } else
+ if (!stralloc_copys(&srshost,sendhost)) die_nomem();
+ } else
+ if (!stralloc_copys(&srshost,host)) die_nomem();
+ if (!stralloc_0(&srshost)) die_nomem();
+ } else
+ die_control();
+
+ /* Let's go SRS rewrite */
+
+ srs = srs_new();
+
+ if (separator == '-' || separator == '+' || separator == '=') { // '=' is default
+ r = srs_set_separator(srs,separator);
+ if (r != SRS_SUCCESS) return srserror_str(r);
+ }
+ if (alwaysrewrite) {
+ r = srs_set_alwaysrewrite(srs,alwaysrewrite);
+ if (r != SRS_SUCCESS) return srserror_str(r);
+ }
+
+ for (j = 0, i = 0; j < cookie.len; j++) {
+ if (cookie.s[j] == ' ' || cookie.s[j] == '\0' ) {
+ cookie.s[j] = '\0';
+ r = srs_add_secret(srs,cookie.s + i);
+ if (r != SRS_SUCCESS) return srserror_str(r);
+ i = j + 1;
+ if (cookie.s[i] == ' ') { j++; continue; }
+ }
+ }
+
+ if ((r = srs_forward(srs,srssender,sizeof(srssender),sender,srshost.s)) != SRS_SUCCESS)
+ logmsg(WHO,100,FATAL,B("Unable to forward: ",sender," ",srs_strerror(r)));
+
+ if (qmail_open(&qqt) == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ qmail_puts(&qqt,dtline);
+ if (buffer_copy(&bo,&bi) != 0)
+ logmsg(WHO,111,FATAL,"unable to read message: ");
+ buffer_flush(&bo);
+
+ num[fmt_ulong(num,qmail_qp(&qqt))] = 0;
+
+ qmail_from(&qqt,srssender);
+ while (*++argv) qmail_to(&qqt,*argv);
+ qqx = qmail_close(&qqt);
+ if (*qqx) logmsg(WHO,*qqx == 'D' ? 100 : 111,FATAL,qqx + 1);
+ logmsg(WHO,0,LOG,B(srssender,": qp ",num));
+
+}
diff --git a/src/srsreverse.c b/src/srsreverse.c
new file mode 100644
index 0000000..dc2171d
--- /dev/null
+++ b/src/srsreverse.c
@@ -0,0 +1,172 @@
+#include <unistd.h>
+#include <sys/types.h>
+#include "control.h"
+#include "sig.h"
+#include "constmap.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "env.h"
+#include "qmail.h"
+#include "auto_qmail.h"
+#include "auto_break.h"
+#include "buffer.h"
+#include "case.h"
+#include "str.h"
+#include "fmt.h"
+#include "stralloc.h"
+#include "logmsg.h"
+#include "srs2.h"
+
+#define WHO "srsreverse"
+
+void die_nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+void die_control() { logmsg(WHO,110,FATAL,"Unable to read control files"); }
+
+struct qmail qqt;
+char *vdomainuser = 0;
+stralloc vdomains = {0};
+struct constmap mapvdomains;
+char *srsdomaininfo = 0;
+stralloc srsdomains = {0};
+struct constmap mapsrsdomains;
+stralloc srserror = {0};
+stralloc srshost = {0};
+
+/** @file srsreverse.c
+ @brief forwarding bounces with SRS enhanced addresss
+ @return 0 on success (forwarded or not)
+ -3 SRS error with error output
+ 111 no memory / processing error
+ 110 control file not readable
+*/
+
+static int srserror_str(int code) {
+ if (!stralloc_copys(&srserror,"SRS: ")) die_nomem();
+ if (!stralloc_cats(&srserror,srs_strerror(code))) die_nomem();
+ if (!stralloc_0(&srserror)) die_nomem();
+ return -3;
+}
+
+ssize_t mywrite(int fd,char *buf,int len)
+{
+ qmail_put(&qqt,buf,len);
+ return len;
+}
+
+char inbuf[BUFFER_INSIZE];
+char outbuf[1];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+buffer bo = BUFFER_INIT(mywrite,-1,outbuf,sizeof(outbuf));
+
+char num[FMT_ULONG];
+
+int main()
+{
+ int i, j, r;
+ char *recipient;
+ char *dtline;
+ char *qqx;
+ srs_t *srs;
+ stralloc cookie = {0};
+ char separator = '=';
+ char srsrecipient[512];
+ char *host = 0;
+
+ sig_pipeignore();
+
+ recipient = env_get("RECIPIENT");
+ if (!recipient)
+ logmsg(WHO,100,FATAL,"RECIPIENT not set");
+ dtline = env_get("DTLINE");
+ if (!dtline)
+ logmsg(WHO,100,FATAL,"DTLINE not set");
+ host = env_get("HOST");
+ if (!host)
+ logmsg(WHO,100,FATAL,"HOST not set");
+
+ if (chdir(auto_qmail) == -1)
+ logmsg(WHO,111,FATAL,B("unable to chdir to: ",auto_qmail));
+
+ /* Check for particular virtual SRS domain user */
+
+ switch (control_readfile(&vdomains,"control/virtualdomains",0)) {
+ case -1: die_control();
+ case 0: if (!constmap_init(&mapvdomains,"",0,1)) die_nomem(); break;
+ case 1: if (!constmap_init(&mapvdomains,vdomains.s,vdomains.len,1)) die_nomem(); break;
+ }
+
+ j = str_len(host);
+ for (i = 0; i <= j; ++i)
+ if ((i == 0) || (host[i] == '.')) {
+ if (!stralloc_copys(&srshost,"")) die_nomem();
+ if (!stralloc_catb(&srshost,host + i,j - i)) die_nomem();
+ if ((srsdomaininfo = constmap(&mapvdomains,srshost.s,srshost.len)) != 0) goto SRSDOMAINS;
+ }
+ if (!stralloc_copys(&srshost,host)) die_nomem();
+
+ SRSDOMAINS:
+
+ switch (control_readfile(&srsdomains,"control/srsdomains",0)) {
+ case -1: die_control();
+ case 0: if (!constmap_init(&mapsrsdomains,"",0,1)) die_nomem(); break;
+ case 1: if (!constmap_init(&mapsrsdomains,srsdomains.s,srsdomains.len,1)) die_nomem(); break;
+ }
+ if ((srsdomaininfo = constmap(&mapsrsdomains,srshost.s,srshost.len)) == 0)
+ if ((srsdomaininfo = constmap(&mapsrsdomains,"*",1)) == 0) return 0;
+
+ if (*srsdomaininfo) {
+ i = str_chr(srsdomaininfo,'|'); // multiple cookies; separated by ' '
+ if (srsdomaininfo[i] == '|') {
+ srsdomaininfo[i] = 0;
+ separator = srsdomaininfo[i+1];
+ }
+ if (!stralloc_copys(&cookie,srsdomaininfo)) die_nomem();
+ if (!stralloc_0(&cookie)) die_nomem();
+ }
+
+ /* strip virtual user from recipient */
+
+ if ((vdomainuser = constmap(&mapvdomains,host,j))) {
+ i = str_chr(recipient,*auto_break);
+ if (!case_diffb(recipient,i - 1,vdomainuser)) recipient += i + 1;
+ }
+
+ /* Let's go SRS reverse */
+
+ srs = srs_new();
+
+ if (separator == '-' || separator == '+' || separator == '=') { // '=' is default
+ r = srs_set_separator(srs,separator);
+ if (r != SRS_SUCCESS) return srserror_str(r);
+ }
+
+ for (j = 0, i = 0; j < cookie.len; j++) {
+ if (cookie.s[j] == ' ' || cookie.s[j] == '\0' ) {
+ cookie.s[j] = '\0';
+ r = srs_add_secret(srs,cookie.s + i);
+ if (r != SRS_SUCCESS) return srserror_str(r);
+ i = j + 1;
+ if (cookie.s[i] == ' ') { j++; continue; }
+ }
+ }
+
+ if ((r = srs_reverse(srs,srsrecipient,sizeof(srsrecipient),recipient)) != SRS_SUCCESS) {
+ logmsg(WHO,100,FATAL,B("unable to reverse: ",recipient," ",srs_strerror(r)));
+ }
+
+ if (qmail_open(&qqt) == -1)
+ logmsg(WHO,111,FATAL,"unable to fork: ");
+ qmail_puts(&qqt,dtline);
+ if (buffer_copy(&bo,&bi) != 0)
+ logmsg(WHO,111,FATAL,"unable to read message: ");
+ buffer_flush(&bo);
+
+ num[fmt_ulong(num,qmail_qp(&qqt))] = 0;
+
+ qmail_from(&qqt,"");
+ qmail_to(&qqt,srsrecipient);
+ qqx = qmail_close(&qqt);
+ if (*qqx) logmsg(WHO,*qqx == 'D' ? 100 : 111,FATAL,qqx + 1);
+ logmsg(WHO,0,LOG,B(srsrecipient,": qp ",num));
+
+}
diff --git a/src/strset.c b/src/strset.c
new file mode 100644
index 0000000..8f3ffe8
--- /dev/null
+++ b/src/strset.c
@@ -0,0 +1,125 @@
+#include "strset.h"
+#include "str.h"
+#include "byte.h"
+#include "alloc.h"
+
+uint32 strset_hash(char *s)
+{
+ unsigned char ch;
+ uint32 h;
+
+ h = 5381LL;
+
+ while ((ch = *s)) {
+ h = ((h << 5) + h) ^ ch;
+ ++s;
+ }
+ return h;
+}
+
+int strset_init(strset *set)
+{
+ int h;
+ set->mask = 15;
+ set->n = 0;
+ set->a = 10;
+
+ set->first = (int *) alloc(sizeof(int) * (set->mask + 1));
+ if (!set->first) return 0;
+ set->p = (strset_list *) alloc(sizeof(strset_list) * set->a);
+ if (!set->p) { alloc_free(set->first); return 0; }
+ set->x = (char **) alloc(sizeof(char *) * set->a);
+ if (!set->x) { alloc_free(set->p); alloc_free(set->first); return 0; }
+
+ for (h = 0; h <= set->mask; ++h)
+ set->first[h] = -1;
+
+ return 1;
+}
+
+char *strset_in(strset *set,char *s)
+{
+ uint32 h;
+ strset_list *sl;
+ int i;
+ char *xi;
+
+ h = strset_hash(s);
+ i = set->first[h & set->mask];
+
+ while (i >= 0) {
+ sl = set->p + i;
+ if (sl->h == h) {
+ xi = set->x[i];
+ if (!str_diff(xi,s)) return xi;
+ }
+ i = sl->next;
+ }
+ return 0;
+}
+
+int strset_add(strset *set,char *s)
+{
+ uint32 h;
+ int n;
+ strset_list *sl;
+
+ n = set->n;
+
+ if (n == set->a) {
+ int newa;
+ strset_list *newp;
+ char **newx;
+
+ newa = n + 10 + (n >> 3);
+ newp = (strset_list *) alloc(sizeof(strset_list) * newa);
+ if (!newp) return 0;
+ newx = (char **) alloc(sizeof(char *) * newa);
+ if (!newx) { alloc_free(newp); return 0; }
+
+ byte_copy(newp,sizeof(strset_list) * n,set->p);
+ byte_copy(newx,sizeof(char *) * n,set->x);
+ alloc_free(set->p);
+ alloc_free(set->x);
+ set->p = newp;
+ set->x = newx;
+ set->a = newa;
+
+ if (n + n + n > set->mask) {
+ int newmask;
+ int *newfirst;
+ int i;
+ uint32 h;
+
+ newmask = set->mask + set->mask + 1;
+ newfirst = (int *) alloc(sizeof(int) * (newmask + 1));
+ if (!newfirst) return 0;
+
+ for (h = 0; h <= newmask; ++h)
+ newfirst[h] = -1;
+
+ for (i = 0; i < n; ++i) {
+ sl = set->p + i;
+ h = sl->h & newmask;
+ sl->next = newfirst[h];
+ newfirst[h] = i;
+ }
+
+ alloc_free(set->first);
+ set->first = newfirst;
+ set->mask = newmask;
+ }
+ }
+
+ h = strset_hash(s);
+
+ sl = set->p + n;
+ sl->h = h;
+ h &= set->mask;
+ sl->next = set->first[h];
+ set->first[h] = n;
+ set->x[n] = s;
+ set->n = n + 1;
+
+ return 1;
+}
diff --git a/src/successes.sh b/src/successes.sh
new file mode 100644
index 0000000..ec5efd3
--- /dev/null
+++ b/src/successes.sh
@@ -0,0 +1,13 @@
+awk '
+ /^d k/ {
+ reason = $11
+ succ[reason] += 1
+ xdelay[reason] += $5 - $4
+ }
+ END {
+ for (reason in succ) {
+ str = sprintf("%.2f",xdelay[reason])
+ print succ[reason],str,reason
+ }
+ }
+'
diff --git a/src/suids.sh b/src/suids.sh
new file mode 100644
index 0000000..da2fb81
--- /dev/null
+++ b/src/suids.sh
@@ -0,0 +1,22 @@
+awk '
+ /^m/ {
+ uid = $10
+ messages[uid] += 1
+ succ[uid] += $5
+ fail[uid] += $6
+ temp[uid] += $7
+ mbytes[uid] += $4
+ sbytes[uid] += $4 * $5
+ rbytes[uid] += $4 * ($5 + $6)
+ }
+ /^d/ {
+ uid = $10
+ xdelay[uid] += $5 - $4
+ }
+ END {
+ for (uid in messages) {
+ str = sprintf("%.6f",xdelay[uid])
+ print messages[uid],mbytes[uid],sbytes[uid],rbytes[uid],succ[uid] + fail[uid],succ[uid] + fail[uid] + temp[uid],str,uid
+ }
+ }
+'
diff --git a/src/tai64nfrac.c b/src/tai64nfrac.c
new file mode 100644
index 0000000..f3db977
--- /dev/null
+++ b/src/tai64nfrac.c
@@ -0,0 +1,85 @@
+#include "buffer.h"
+#include "stralloc.h"
+#include "exit.h"
+#include "readwrite.h"
+#include "open.h"
+#include "scan.h"
+#include "fmt.h"
+#include "getln.h"
+
+#define TAI64NLEN 24
+
+/** @file tai64nfrac
+ @brief Read a TAI64N external format timestamp from stdin and
+ write fractional seconds since epoch (TAI, not UTC) to stdout.
+ Return the characters after the timestamp.
+ */
+
+char outbuf[64];
+buffer bo = BUFFER_INIT(write,1,outbuf,sizeof(outbuf));
+
+static void outs(char *s)
+{
+ if (buffer_puts(&bo,s) == -1) _exit(1);
+ if (buffer_flush(&bo) == -1) _exit(1);
+}
+
+static void outi(int i)
+{
+ char num[FMT_ULONG];
+
+ if (buffer_put(&bo,num,fmt_ulong(num,(unsigned long) i)) == -1) _exit(1);
+ if (buffer_flush(&bo) == -1) _exit(1);
+}
+
+char inbuf[1024];
+buffer bi = BUFFER_INIT(read,0,inbuf,sizeof(inbuf));
+
+int main(void)
+{
+ int c;
+ int i;
+ int match;
+ unsigned long u;
+ unsigned long seconds;
+ unsigned long nanoseconds;
+ stralloc line = {0};
+
+/* Read from stdin */
+
+ buffer_init(&bi,read,0,inbuf,sizeof(inbuf));
+
+ for (;;) {
+ if (getln(&bi,&line,&match,'\n') != 0) _exit(1);
+ if (!match) break;
+ if (!stralloc_0(&line)) _exit(1);
+
+ seconds = 0;
+ nanoseconds = 0;
+
+ if (line.s[0] == '@') { /* tai64 timestamp */
+ for (i = 1; i <= TAI64NLEN; i++) {
+ c = (int)line.s[i];
+ u = c - '0';
+ if (u >= 10) {
+ u = c - 'a';
+ if (u >= 6) break;
+ u += 10;
+ }
+ seconds <<= 4;
+ seconds += nanoseconds >> 28;
+ nanoseconds &= 0xfffffff;
+ nanoseconds <<= 4;
+ nanoseconds += u;
+ }
+ seconds -= 4611686018427387914ULL;
+ seconds = seconds > 0 ? seconds : 0;
+ outi(seconds); outs("."); outi(nanoseconds); outs(line.s + i); outs("\n");
+ } else {
+ outs("tai64nfrac: fatal: Wrong TAI64N input format."); outs("\n");
+ _exit(1);
+ }
+ }
+
+ _exit(0);
+}
diff --git a/src/tcpto.c b/src/tcpto.c
new file mode 100644
index 0000000..92c33ea
--- /dev/null
+++ b/src/tcpto.c
@@ -0,0 +1,169 @@
+#include <sys/socket.h>
+#include <unistd.h>
+#include "tcpto.h"
+#include "open.h"
+#include "lock.h"
+#include "seek.h"
+#include "now.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "byte.h"
+#include "datetime.h"
+
+char tcpto_buf[1024];
+
+static int flagwasthere;
+static int fdlock;
+
+static int getbuf()
+{
+ int r;
+ int fd;
+
+ fdlock = open_write("queue/lock/tcpto");
+ if (fdlock == -1) return 0;
+ fd = open_read("queue/lock/tcpto");
+ if (fd == -1) { close(fdlock); return 0; }
+ if (lock_ex(fdlock) == -1) { close(fdlock); close(fd); return 0; }
+ r = read(fd,tcpto_buf,sizeof(tcpto_buf));
+ close(fd);
+ if (r < 0) { close(fdlock); return 0; }
+ r >>= 5;
+ if (!r) close(fdlock);
+ return r;
+}
+
+int tcpto(struct ip_mx *ix)
+{
+ int af = ix->af;
+ struct ip_address *ip = &ix->addr;
+ int n;
+ int i;
+ char *record;
+ datetime_sec when;
+
+ flagwasthere = 0;
+
+ n = getbuf();
+ if (!n) return 0;
+ close(fdlock);
+
+ record = tcpto_buf;
+
+ for (i = 0; i < n; ++i) {
+ if (af == record[0] && byte_equal(ip->d,af == AF_INET ? 4 : 16,record + 16)) {
+ flagwasthere = 1;
+ if (record[4] >= 2) {
+ when = (unsigned long) (unsigned char) record[11];
+ when = (when << 8) + (unsigned long) (unsigned char) record[10];
+ when = (when << 8) + (unsigned long) (unsigned char) record[9];
+ when = (when << 8) + (unsigned long) (unsigned char) record[8];
+
+ if (now() - when < ((60 + (getpid() & 31)) << 6)) return 1;
+ }
+ return 0;
+ }
+ record += 32;
+ }
+ return 0;
+}
+
+void tcpto_err(struct ip_mx *ix,int flagerr)
+{
+ int af = ix->af;
+ struct ip_address *ip = &ix->addr;
+ int n;
+ int i;
+ char *record;
+ datetime_sec when;
+ datetime_sec firstwhen;
+ int firstpos;
+ datetime_sec lastwhen;
+
+ if (!flagerr)
+ if (!flagwasthere)
+ return; /* could have been added, but not worth the effort to check */
+
+ n = getbuf();
+ if (!n) return;
+
+ record = tcpto_buf;
+
+ for (i = 0; i < n; ++i) {
+ if (af == record[0] && byte_equal(ip->d,af == AF_INET ? 4 : 16,record + 16)) {
+ if (!flagerr)
+ record[4] = 0;
+ else {
+ lastwhen = (unsigned long) (unsigned char) record[11];
+ lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[10];
+ lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[9];
+ lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[8];
+ when = now();
+
+ if (record[4] && (when < 120 + lastwhen)) { close(fdlock); return; }
+
+ if (++record[4] > 10) record[4] = 10;
+ record[8] = when; when >>= 8;
+ record[9] = when; when >>= 8;
+ record[10] = when; when >>= 8;
+ record[11] = when;
+ }
+ if (seek_set(fdlock,i << 5) == 0)
+ if (write(fdlock,record,32) < 32)
+ ; /*XXX*/
+ close(fdlock);
+ return;
+ }
+ record += 32;
+ }
+
+ if (!flagerr) { close(fdlock); return; }
+
+ record = tcpto_buf;
+
+ for (i = 0; i < n; ++i) {
+ if (!record[4]) break;
+ record += 32;
+ }
+
+ if (i >= n) {
+ firstpos = -1;
+ record = tcpto_buf;
+
+ for (i = 0; i < n; ++i) {
+ when = (unsigned long) (unsigned char) record[11];
+ when = (when << 8) + (unsigned long) (unsigned char) record[10];
+ when = (when << 8) + (unsigned long) (unsigned char) record[9];
+ when = (when << 8) + (unsigned long) (unsigned char) record[8];
+ when += (record[4] << 10);
+ if ((firstpos < 0) || (when < firstwhen)) {
+ firstpos = i;
+ firstwhen = when;
+ }
+ record += 32;
+ }
+ i = firstpos;
+ }
+
+ if (i >= 0) {
+ record = tcpto_buf + (i << 5);
+ record[0] = af;
+ if (af == AF_INET6)
+ byte_copy(record + 16,16,ip->d);
+ else {
+ byte_copy(record + 16,4,ip->d);
+ byte_copy(record + 20,12,"............");
+ }
+ when = now();
+ record[8] = when; when >>= 8;
+ record[9] = when; when >>= 8;
+ record[10] = when; when >>= 8;
+ record[11] = when;
+ record[4] = 1;
+ if (seek_set(fdlock,i << 5) == 0)
+ if (write(fdlock,record,32) < 32)
+ ; /*XXX*/
+ }
+
+ close(fdlock);
+}
diff --git a/src/tcpto_clean.c b/src/tcpto_clean.c
new file mode 100644
index 0000000..e0b6969
--- /dev/null
+++ b/src/tcpto_clean.c
@@ -0,0 +1,21 @@
+#include <unistd.h>
+#include "tcpto.h"
+#include "open.h"
+#include "buffer.h"
+
+char tcpto_cleanbuf[1024];
+
+void tcpto_clean() /* running from queue/mess */
+{
+ int fd;
+ int i;
+ buffer bo;
+
+ fd = open_write("../lock/tcpto");
+ if (fd == -1) return;
+ buffer_init(&bo,write,fd,tcpto_cleanbuf,sizeof(tcpto_cleanbuf));
+ for (i = 0; i < sizeof(tcpto_cleanbuf); ++i)
+ buffer_put(&bo,"",1);
+ buffer_flush(&bo); /* if it fails, bummer */
+ close(fd);
+}
diff --git a/src/tls_errors.c b/src/tls_errors.c
new file mode 100644
index 0000000..5c30236
--- /dev/null
+++ b/src/tls_errors.c
@@ -0,0 +1,158 @@
+#include <unistd.h>
+#include "stralloc.h"
+#include "tls_errors.h"
+#include "error.h"
+
+/** @file tls_errors.c
+ @brief temp_tls* routines are used for error messges
+*/
+
+/* TLS error messages: A) Setup */
+
+void temp_tlscert()
+{
+ out("ZCan't load X.509 certificate: ");
+ outsafe(&certfile);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlskey()
+{
+ out("ZCan't load X.509 private key: ");
+ outsafe(&keyfile);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlschk()
+{
+ out("ZKeyfile does not match X.509 certificate: ");
+ outsafe(&keypwd);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlsca()
+{
+ out("ZI wasn't able to set up CAFILE: ");
+ outsafe(&cafile);
+ out(" or CADIR: ");
+ outsafe(&cadir);
+ out(" for TLS. (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlscipher()
+{
+ out("ZI wasn't able to process the TLS ciphers: ");
+ outsafe(&ciphers);
+ out(" (#4.4.1)\n");
+ zerodie();
+}
+
+/* TLS error messages: B) Connection related */
+
+void temp_tlsctx()
+{
+ out("ZI wasn't able to create TLS context for: ");
+ outsafe(&host); out(" at "); out(remotehost.s);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlscon()
+{
+ errno = EPROTO;
+ out("ZI wasn't able to establish a TLS connection with: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlserr()
+{
+ errno = EPROTO;
+ out("ZTLS connection/protocol error with: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlshost()
+{
+ out("ZI wasn't able to negotiate a StartTLS connection with: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+
+/* TLS error messages: C) Verification related */
+
+void temp_tlspeercert()
+{
+ out("ZUnable to obtain X.509 certificate from: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlspeerverify()
+{
+ out("ZUnable to verify X.509 certificate from: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlspeervalid()
+{
+ out("ZUnable to validate X.509 certificate Subject for: ");
+ outsafe(&host); out(" at "); out(remotehost.s);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlscertfp()
+{
+ out("ZReceived X.509 certificate from: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(" does not match fingerprint: ");
+ outsafe(&cafile);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_invaliddigest()
+{
+ out("ZInvalid digest length provided given for: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlsamissing()
+{
+ out("ZTLSA X.509 cert required but missing from: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlsainvalid()
+{
+ out("ZTLSA fingerprint matching error for: ");
+ out(remotehost.s);
+ out(". (#4.4.1)\n");
+ zerodie();
+}
+
+void temp_tlsdigest()
+{
+ out("ZReceived X.509 certificate from: ");
+ out(remotehost.s); out(" for "); outsafe(&host);
+ out(" posses an unknown digest method");
+ out(". (#4.4.1)\n");
+ zerodie();
+}
diff --git a/src/tls_remote.c b/src/tls_remote.c
new file mode 100644
index 0000000..1318b4e
--- /dev/null
+++ b/src/tls_remote.c
@@ -0,0 +1,387 @@
+#include <unistd.h>
+#include "ucspissl.h"
+#include "fmt.h"
+#include "stralloc.h"
+#include "str.h"
+#include "byte.h"
+#include "case.h"
+#include "dns.h"
+#include "constmap.h"
+#include "tls_remote.h"
+#include "tls_errors.h"
+
+/** @file tls_remote.c -- TLS client functions
+ @brief connection functions: tls_conn, tls_exit;
+ verification functions: tls_certkey, tls_checkpeer, tls_fingerprint, tlsa_check;
+ tls_destination, tls_domaincert
+ dummy functions: tls_crlcheck
+
+ tls_checkpeer: r = 0 -> ADH, r = 1 -> wildcard DN, r = 2 -> DN, r = 3 -> CA; r < 0 -> error
+ tls_fingerprint: r = 0 -> failed, r = 1 -> ok; r < 0 -> error
+ tlsa_check: r = 0 -> nothing, r = usage + 1, r < 0 -> error
+*/
+
+/* Caution: OpenSSL's X509_pubkey_digest() does not work as expected.
+ I've included now: X509_pkey_digest() and X509_cert_digest() (as makro) */
+
+#define X509_cert_digest X509_digest
+
+int tls_certkey(SSL_CTX *ctx,const char *cert,const char *key,char *ppwd)
+{
+ if (!cert) return 0;
+
+ if (SSL_CTX_use_certificate_chain_file(ctx,cert) != 1)
+ return -1;
+
+ if (!key) key = cert;
+
+ if (ppwd) SSL_CTX_set_default_passwd_cb_userdata(ctx,ppwd);
+
+ if (SSL_CTX_use_PrivateKey_file(ctx,key,SSL_FILETYPE_PEM) != 1)
+ return -2;
+
+ if (SSL_CTX_check_private_key(ctx) != 1)
+ return -3;
+
+ return 0;
+}
+
+int tls_conn(SSL *ssl,int smtpfd)
+{
+ SSL_set_options(ssl,SSL_OP_NO_SSLv2);
+ SSL_set_options(ssl,SSL_OP_NO_SSLv3);
+ return SSL_set_fd(ssl,smtpfd);
+}
+
+int tls_checkpeer(SSL *ssl,X509 *cert,const stralloc host,const int flag,const int verify)
+{
+ STACK_OF(GENERAL_NAME) *extensions;
+ const GENERAL_NAME *ext;
+ char buf[SSL_NAME_LEN];
+ char *dnsname = 0;
+ int dname = 0;
+ int num;
+ int len;
+ int fflag;
+ int i;
+ int rc = 0;
+
+ fflag = flag;
+ if (flag > 20) fflag = flag - 20;
+ if (flag > 10) fflag = flag - 10;
+
+ /* X.509 CA DN/SAN name validation against DNS */
+
+ if (host.len && fflag > 4) {
+ extensions = (GENERAL_NAME *)X509_get_ext_d2i(cert,NID_subject_alt_name,0,0);
+ num = sk_GENERAL_NAME_num(extensions); /* num = 0, if no SAN extensions */
+
+ for (i = 0; i < num; ++i) {
+ ext = sk_GENERAL_NAME_value(extensions,i);
+ if (ext->type == GEN_DNS) {
+ #if (OPENSSL_VERSION_NUMBER < 0x10100000L) // 0xmnnffppsL
+ if (ASN1_STRING_type(ext->d.ia5) != V_ASN1_IA5STRING) continue;
+ dnsname = (char *)ASN1_STRING_data(ext->d.ia5);
+ #else
+ if (OBJ_sn2nid((const char*)ext->d.ia5) != V_ASN1_IA5STRING) continue;
+ dnsname = (char *)ASN1_STRING_get0_data(ext->d.ia5);
+ #endif
+ len = ASN1_STRING_length(ext->d.ia5);
+ dname = 1;
+ }
+ }
+
+ if (!dname) {
+ X509_NAME_get_text_by_NID(X509_get_subject_name(cert),NID_commonName,buf,sizeof(buf));
+ buf[SSL_NAME_LEN - 1] = 0;
+ dnsname = buf;
+ len = SSL_NAME_LEN - 1;
+ }
+
+ switch (fflag) {
+ case 5: if (dnsname[0] == '*' && dnsname[1] == '.')
+ if (case_diffrs(dnsname + 1,host.s)) return -3;
+ if (case_diffrs(dnsname,host.s)) return -3;
+ rc = 3; break;
+ case 6: if (case_diffs(dnsname,host.s)) return -3;
+ rc = 2; break;
+ }
+ }
+
+ /* X.509 CA Verification: root CA must be available */
+
+ if (fflag > 3 && verify > -2) {
+ if (SSL_get_verify_result(ssl) != X509_V_OK) return -2;
+ else rc = 1;
+ }
+
+ return rc;
+}
+
+int tls_checkcrl(SSL *ssl) // not implemented yet
+{
+
+ return 0;
+}
+
+int dig_ascii(char *digascii,const char *digest,const int len)
+{
+ static const char hextab[] = "0123456789abcdef";
+ int j;
+
+ for (j = 0; j < len; j++) {
+ digascii[2 * j] = hextab[(unsigned char)digest[j] >> 4];
+ digascii[2 * j + 1] = hextab[(unsigned char)digest[j] & 0x0f];
+ }
+ digascii[2 * len] = '\0';
+
+ return (2 * j); // 2*len
+}
+
+/* X509_pkey_digest() takes the same args as X509_digest();
+ however returning the correct hash of pubkey in md.
+ Subjects keys are restricted to 2048 byte in size.
+ Return codes: 1: sucess, 0: failed. */
+
+int X509_pkey_digest(const X509 *cert,const EVP_MD *type,unsigned char *md,unsigned int *dlen)
+{
+ unsigned int len = 0;
+ unsigned int size = 2048;
+ unsigned char *buf;
+ unsigned char *buf2;
+ unsigned char buffer[size]; // avoid malloc
+
+/* Following Viktor's suggestion */
+
+ if (!X509_get0_pubkey_bitstr(cert)) return 0; // no Subject public key
+
+ len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert),0);
+ if (len > size) return 0;
+ buf2 = buf = buffer;
+ i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert),(unsigned char **)&buf2);
+ if (buf2 - buf != len) return 0;
+
+ if (!EVP_Digest(buf,len,md,dlen,type,0)) return 0; // OpenSSL voodoo
+ return 1;
+}
+
+/* Return codes: -4: no X.509 cert (fatal), -3: matching error (deferred),
+ -2: unsupported type, -1: weird TLSA record
+ 0: No X.509 cert; seen: usage++; */
+
+int tlsa_check(const STACK_OF(X509) *certs,const stralloc host,const unsigned long p)
+{
+ const EVP_MD *methodsha256 = EVP_sha256();
+ const EVP_MD *methodsha512 = EVP_sha512();
+ stralloc out = {0};
+ stralloc sa = {0};
+ stralloc cn = {0};
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ unsigned int dlen = 0;
+ unsigned int n = 0;
+ int i = 0;
+ int r;
+ char port[FMT_ULONG];
+ uint16 type;
+ uint16 selector;
+ uint16 usage;
+
+// construct TLSA FQDN -- simple procedure; returning Usage
+
+ if (host.len < 2) return 0;
+ if (!stralloc_copyb(&sa,"_",1)) temp_nomem();
+ port[fmt_ulong(port,p)] = 0;
+ if (!stralloc_cats(&sa,port)) temp_nomem();
+ if (!stralloc_cats(&sa,"._tcp.")) temp_nomem();
+ if (!stralloc_cats(&sa,host.s)) temp_nomem();
+
+ if (dns_cname(&cn,&sa) > 0) // query name could be a cname
+ { if (dns_tlsa(&out,&cn) <= 0) return 0; }
+ else
+ { if (dns_tlsa(&out,&sa) <= 0) return 0; }
+ if (out.len < 5) return -1;
+
+ /* https://www.openssl.org/docs/man3.0/man3/X509_digest.html (1.1.1):
+ "The len parameter, if not NULL, points to a place where the digest size will be stored."
+ [sigh]
+ */
+
+ do {
+ usage = (unsigned char) out.s[i]; // Usage: PKIX-TA [0], PKIX-EE [1], DANE-TA [2], DANE-EE [3]
+ selector = (unsigned char) out.s[i + 1]; // Selector: 0 = Cert, 1 = SPKI
+ type = (unsigned char) out.s[i + 2]; // Type: 0/1/2 = [Cert|SPKI]/SHA256/SHA512
+
+ unsigned len = sk_X509_num(certs);
+ for (n = 0; n < len; n++) {
+ X509 *cert = sk_X509_value(certs,n);
+ if (type == 1) {
+ if (selector == 0) r = X509_cert_digest(cert,methodsha256,digest,&dlen);
+ if (selector == 1) r = X509_pkey_digest(cert,methodsha256,digest,&dlen);
+ } else if (type == 2) {
+ if (selector == 0) r = X509_cert_digest(cert,methodsha512,digest,&dlen);
+ if (selector == 1) r = X509_pkey_digest(cert,methodsha512,digest,&dlen);
+ } else
+ return -2;
+
+ if (!byte_diff(digest,dlen,out.s + i + 3)) return ++usage;
+ }
+
+ i += (dlen + 3);
+ } while (i < out.len - 4);
+
+ return -3;
+}
+
+int tls_fingerprint(X509 *cert,const char *fingerprint,int dlen)
+{
+ const EVP_MD *methodsha1 = EVP_sha1();
+ const EVP_MD *methodsha224 = EVP_sha224();
+ const EVP_MD *methodsha256 = EVP_sha256();
+ const EVP_MD *methodsha512 = EVP_sha512();
+ unsigned char digest[EVP_MAX_MD_SIZE];
+ unsigned char digascii[257];
+ unsigned int len;
+
+ switch (dlen) { /* fetch digest from cert; len = bitlength/8 */
+ case 40: if (!X509_digest(cert,methodsha1,digest,&len)) return -2;
+ case 56: if (!X509_digest(cert,methodsha224,digest,&len)) return -2;
+ case 64: if (!X509_digest(cert,methodsha256,digest,&len)) return -2;
+ case 128: if (!X509_digest(cert,methodsha512,digest,&len)) return -2;
+ default: return -3;
+ }
+
+ len = dig_ascii(digascii,digest,len);
+ if (!str_diffn(digascii,fingerprint,len)) return 1;
+
+ return 0;
+}
+
+int tls_exit(SSL *ssl)
+{
+ if (SSL_shutdown(ssl) == 0)
+ SSL_shutdown(ssl);
+
+ return 0;
+}
+
+/** @brief tls_destination
+ @param stralloc hostname (maybe 0-terminated)
+
+ Certificate Fallthru
+
+ @return values: | ADH | Cert *DN FQDN Hash | noTLSA noTLS
+ ----------+-----+--------------------+-------------
+ optional TLS | 1 | 3 - - - | - 9
+ mandatory TLS | 2 | 4 5 6 7 | 8
+
+ no TLS -1
+ */
+
+int tls_destination(const stralloc hostname)
+{
+ int i;
+ stralloc tlshost = {0};
+ stralloc tlsdest = {0};
+
+ if (!stralloc_copy(&tlshost,&hostname)) temp_nomem();
+ if (!stralloc_0(&tlshost)) temp_nomem();
+
+// Host rules
+
+ if (!stralloc_copys(&tlsdest,"!")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return -1;
+
+ if (!stralloc_copys(&tlsdest,"?")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 9;
+
+ if (!stralloc_copys(&tlsdest,"/")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 8;
+
+ if (!stralloc_copys(&tlsdest,"%")) temp_nomem(); // CERT + hash
+ if (!stralloc_cats(&tlsdest,tlshost.s)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 7;
+
+ if (!stralloc_copys(&tlsdest,"=")) temp_nomem(); // CERT + FQDN
+ if (!stralloc_cats(&tlsdest,tlshost.s)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 6;
+
+ if (!stralloc_copys(&tlsdest,"~")) temp_nomem(); // CERT + Wild
+ if (!stralloc_cats(&tlsdest,tlshost.s)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 5;
+
+// Domain rules
+
+ for (i = 0; i < tlshost.len; ++i) // TLS fallthru
+ if ((i == 0) || (tlshost.s[i] == '.')) {
+ if (!stralloc_copys(&tlsdest,"?")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s + i)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 9;
+ }
+
+ for (i = 0; i < tlshost.len; ++i) // no TLSA
+ if ((i == 0) || (tlshost.s[i] == '.')) {
+ if (!stralloc_copys(&tlsdest,"/")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s + i)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 8;
+ }
+
+ for (i = 0; i < tlshost.len; ++i) // CERT + Wild
+ if ((i == 0) || (tlshost.s[i] == '.')) {
+ if (!stralloc_copys(&tlsdest,"~")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s + i)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 5;
+ }
+
+ for (i = 0; i < tlshost.len; ++i) // CERT - generic
+ if ((i == 0) || (tlshost.s[i] == '.')) {
+ if (!stralloc_copys(&tlsdest,"")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s + i)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 4;
+ }
+
+ for (i = 0; i < tlshost.len; ++i) // ADH per host/domain
+ if ((i == 0) || (tlshost.s[i] == '.')) {
+ if (!stralloc_copys(&tlsdest,"-")) temp_nomem();
+ if (!stralloc_cats(&tlsdest,tlshost.s + i)) temp_nomem();
+ if ((tlsdestinfo = constmap(&maptlsdestinations,tlsdest.s,tlsdest.len))) return 2;
+ }
+
+// General rules (mandatory TLS)
+
+ tlsdestinfo = 0;
+ if (constmap(&maptlsdestinations,"/*",2)) return 8; // no TLSA
+ if (constmap(&maptlsdestinations,"=*",2)) return 6; // CERT + FQDN
+ if (constmap(&maptlsdestinations,"~*",2)) return 5; // CERT + Wild
+ if (constmap(&maptlsdestinations,"+*",2)) return 4; // CERT
+ if (constmap(&maptlsdestinations,"-*",2)) return 2; // ADH
+
+// Fall thru rules (optional TLS)
+
+ if (constmap(&maptlsdestinations,"?",1)) return 9; // fallback to no TLS
+ if (constmap(&maptlsdestinations,"*",1)) return 3; // CERT
+ if (constmap(&maptlsdestinations,"-",1)) return 1; // ADH
+
+ return 0;
+}
+
+int tls_domaincerts(const stralloc domainname)
+{
+ int i;
+ tlsdomaininfo = 0; // extern
+
+/* Our Certs - per domain */
+
+ if (domainname.len)
+ for (i = 0; i < domainname.len; ++i)
+ if ((i == 0) || (domainname.s[i] == '.'))
+ if ((tlsdomaininfo = constmap(&mapdomaincerts,domainname.s + i,domainname.len - i))) return 2;
+
+/* Standard Cert (if any) */
+
+ if ((tlsdomaininfo = constmap(&mapdomaincerts,"*",1))) return 1;
+
+ return 0;
+}
diff --git a/src/tls_start.c b/src/tls_start.c
new file mode 100644
index 0000000..77c5155
--- /dev/null
+++ b/src/tls_start.c
@@ -0,0 +1,84 @@
+#include <unistd.h>
+#include "scan.h"
+#include "env.h"
+#include "open.h"
+#include "stralloc.h"
+#include "fd.h"
+#include "logmsg.h"
+
+#define WHO "tls_start"
+
+#define BUFSIZE 8192
+
+static void die_nomem() { logmsg(WHO,111,FATAL,"out of memory"); }
+static void die_tlsenv() { logmsg(WHO,111,FATAL,"no UCSPITLS environment to read"); }
+
+int starttls_init(void)
+{
+ unsigned long fd;
+ char *fdstr;
+
+ if (!(fdstr = env_get("SSLCTLFD"))) return 0;
+ if (!scan_ulong(fdstr,&fd)) return 0;
+ if (write((int)fd,"Y",1) < 1) return 0;
+
+ if (!(fdstr = env_get("SSLREADFD"))) return 0;
+ if (!scan_ulong(fdstr,&fd)) return 0;
+ if (fd_move(0,(int)fd) == -1) return 0;
+
+ if (!(fdstr = env_get("SSLWRITEFD"))) return 0;
+ if (!scan_ulong(fdstr,&fd)) return 0;
+ if (fd_move(1,(int)fd) == -1) return 0;
+
+ return 1;
+}
+
+int starttls_info(void)
+{
+ unsigned long fd;
+ char *fdstr;
+ char envbuf[BUFSIZE];
+ char *x;
+ int j;
+
+ stralloc ssl_env = {0};
+ stralloc ssl_parm = {0};
+ stralloc ssl_value = {0};
+
+ if (!(fdstr = env_get("SSLCTLFD"))) return 0;
+ if (!scan_ulong(fdstr,&fd)) return 0;
+
+ while ((j = read(fd,envbuf,BUFSIZE)) > 0 ) {
+ if (!stralloc_catb(&ssl_env,envbuf,j)) die_nomem();
+ if (ssl_env.len >= 2 && ssl_env.s[ssl_env.len - 2] == 0 && ssl_env.s[ssl_env.len - 1] == 0)
+ break;
+ }
+ if (j <= 0) { die_tlsenv(); return 0; } // nothing to read
+
+ x = ssl_env.s;
+
+ for (j = 0; j < ssl_env.len - 1; ++j) {
+ if ( *x != '=' ) {
+ if (!stralloc_catb(&ssl_parm,x,1)) die_nomem();
+ x++;
+ } else {
+ if (!stralloc_0(&ssl_parm)) die_nomem();
+ x++;
+
+ for (; j < ssl_env.len - j - 1; ++j) {
+ if ( *x != '\0' ) {
+ if (!stralloc_catb(&ssl_value,x,1)) die_nomem();
+ x++;
+ } else {
+ if (!stralloc_0(&ssl_value)) die_nomem();
+ x++;
+ if (!env_put(ssl_parm.s,ssl_value.s)) die_nomem();
+ ssl_parm.len = 0;
+ ssl_value.len = 0;
+ break;
+ }
+ }
+ }
+ }
+ return j;
+}
diff --git a/src/tls_timeoutio.c b/src/tls_timeoutio.c
new file mode 100644
index 0000000..c5f40a2
--- /dev/null
+++ b/src/tls_timeoutio.c
@@ -0,0 +1,99 @@
+/* This is essentially taken from Eric Vermeulen's TLS patch */
+#include "select.h"
+#include "error.h"
+#include "ndelay.h"
+#include "now.h"
+#include "logmsg.h"
+#include "ucspissl.h"
+#include "tls_timeoutio.h"
+
+int tls_timeoutio(int (*fun)(),
+ int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
+{
+ int n;
+ const datetime_sec end = (datetime_sec)t + now();
+
+ do {
+ fd_set fds;
+ struct timeval tv;
+
+ const int r = buf ? fun(ssl,buf,len) : fun(ssl);
+ if (r > 0) return r;
+
+ t = end - now();
+ if (t < 0) break;
+ tv.tv_sec = (time_t)t; tv.tv_usec = 0;
+
+ FD_ZERO(&fds);
+ switch (SSL_get_error(ssl,r)) {
+ default: return r; /* some other error */
+ case SSL_ERROR_WANT_READ:
+ FD_SET(rfd,&fds); n = select(rfd + 1,&fds,NULL,NULL,&tv);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ FD_SET(wfd,&fds); n = select(wfd + 1,NULL,&fds,NULL,&tv);
+ break;
+ }
+
+ /* n is the number of descriptors that changed status */
+ } while (n > 0);
+
+ if (n != -1) errno = ETIMEDOUT;
+ return -1;
+}
+
+int tls_timeoutaccept(int t,int rfd,int wfd,SSL *ssl)
+{
+ int r;
+
+ /* if connection is established, keep NDELAY */
+ if (ndelay_on(rfd) == -1 || ndelay_on(wfd) == -1) return -1;
+ r = tls_timeoutio(SSL_accept,t,rfd,wfd,ssl,NULL,0);
+
+ if (r <= 0) { ndelay_off(rfd); ndelay_off(wfd); }
+ else SSL_set_mode(ssl,SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ return r;
+}
+
+int tls_timeoutconn(int t,int rfd,int wfd,SSL *ssl)
+{
+ int r;
+
+ /* if connection is established, keep NDELAY */
+ if (ndelay_on(rfd) == -1 || ndelay_on(wfd) == -1) return -1;
+ r = tls_timeoutio(SSL_connect,t,rfd,wfd,ssl,NULL,0);
+
+ if (r <= 0) { ndelay_off(rfd); ndelay_off(wfd); }
+ else SSL_set_mode(ssl,SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ return r;
+}
+
+int tls_timeoutrehandshake(int t,int rfd,int wfd,SSL *ssl)
+{
+ int r;
+
+ SSL_renegotiate(ssl);
+ r = tls_timeoutio(SSL_do_handshake,t,rfd,wfd,ssl,NULL,0);
+ if (r <= 0) return r;
+ if (SSL_get_state(ssl) & SSL_ST_CONNECT) return -2; /* now a macro in ssl.h */
+
+ /* this is for the client only */
+ SSL_set_connect_state(ssl);
+
+ return tls_timeoutio(SSL_do_handshake,t,rfd,wfd,ssl,NULL,0);
+}
+
+int tls_timeoutread(int t,int rfd,int wfd,SSL *ssl,char *buf,int len)
+{
+ if (!buf) return 0;
+ if (SSL_pending(ssl)) return SSL_read(ssl,buf,len);
+ return tls_timeoutio(SSL_read,t,rfd,wfd,ssl,buf,len);
+}
+
+int tls_timeoutwrite(int t,int rfd,int wfd,SSL *ssl,char *buf,int len)
+{
+ if (!buf) return 0;
+ return tls_timeoutio(SSL_write,t,rfd,wfd,ssl,buf,len);
+}
diff --git a/src/token822.c b/src/token822.c
new file mode 100644
index 0000000..239887c
--- /dev/null
+++ b/src/token822.c
@@ -0,0 +1,461 @@
+#include "stralloc.h"
+#include "alloc.h"
+#include "genalloc.h"
+#include "str.h"
+#include "token822.h"
+
+static struct token822 comma = { TOKEN822_COMMA };
+
+void token822_reverse(token822_alloc *ta)
+{
+ int i;
+ int n;
+ struct token822 temp;
+
+ n = ta->len - 1;
+ for (i = 0; i + i < n; ++i) {
+ temp = ta->t[i];
+ ta->t[i] = ta->t[n - i];
+ ta->t[n - i] = temp;
+ }
+}
+
+GEN_ALLOC_ready(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_ready)
+GEN_ALLOC_readyplus(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_readyplus)
+GEN_ALLOC_append(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_readyplus,token822_append)
+
+static int needspace(int t1,int t2)
+{
+ if (!t1) return 0;
+ if (t1 == TOKEN822_COLON) return 1;
+ if (t1 == TOKEN822_COMMA) return 1;
+ if (t2 == TOKEN822_LEFT) return 1;
+
+ switch (t1) {
+ case TOKEN822_ATOM: case TOKEN822_LITERAL:
+ case TOKEN822_QUOTE: case TOKEN822_COMMENT:
+ switch (t2) {
+ case TOKEN822_ATOM: case TOKEN822_LITERAL:
+ case TOKEN822_QUOTE: case TOKEN822_COMMENT:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int atomok(char ch)
+{
+ switch (ch) {
+ case ' ': case '\t': case '\r': case '\n':
+ case '(': case '[': case '"':
+ case '<': case '>': case ';': case ':':
+ case '@': case ',': case '.':
+ return 0;
+ }
+ return 1;
+}
+
+static void atomcheck(struct token822 *t)
+{
+ int i;
+ char ch;
+
+ for (i = 0; i < t->slen; ++i) {
+ ch = t->s[i];
+ if ((ch < 32) || (ch > 126) || (ch == ')') || (ch == ']') || (ch == '\\')) {
+ t->type = TOKEN822_QUOTE;
+ return;
+ }
+ }
+}
+
+int token822_unparse(stralloc *sa,token822_alloc *ta,unsigned int linelen)
+{
+ struct token822 *t;
+ int len;
+ int ch;
+ int i;
+ int j;
+ int lasttype;
+ int newtype;
+ char *s;
+ char *lineb;
+ char *linee;
+
+ len = 0;
+ lasttype = 0;
+
+ for (i = 0; i < ta->len; ++i) {
+ t = ta->t + i;
+ newtype = t->type;
+ if (needspace(lasttype,newtype)) ++len;
+ lasttype = newtype;
+
+ switch (newtype) {
+ case TOKEN822_COMMA:
+ len += 3; break;
+ case TOKEN822_AT: case TOKEN822_DOT: case TOKEN822_LEFT: case TOKEN822_RIGHT:
+ case TOKEN822_SEMI: case TOKEN822_COLON:
+ ++len; break;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: case TOKEN822_COMMENT:
+ if (t->type != TOKEN822_ATOM) len += 2;
+ for (j = 0; j < t->slen; ++j)
+ switch (ch = t->s[j]) {
+ case '"': case '[': case ']': case '(': case ')':
+ case '\\': case '\r': case '\n': ++len;
+ default: ++len;
+ }
+ break;
+ }
+ }
+ len += 2;
+
+ if (!stralloc_ready(sa,len)) return -1;
+
+ s = sa->s;
+ lineb = s;
+ linee = 0;
+
+ lasttype = 0;
+
+ for (i = 0; i < ta->len; ++i) {
+ t = ta->t + i;
+ newtype = t->type;
+ if (needspace(lasttype,newtype)) *s++ = ' ';
+ lasttype = newtype;
+
+ switch (newtype) {
+ case TOKEN822_COMMA:
+ *s++ = ',';
+#define NSUW \
+ s[0] = '\n'; s[1] = ' '; \
+ if (linee && (!linelen || (s - lineb <= linelen))) \
+ { while (linee < s) { linee[0] = linee[2]; ++linee; } linee -= 2; } \
+ else { if (linee) lineb = linee + 1; linee = s; s += 2; }
+ NSUW
+ break;
+ case TOKEN822_AT: *s++ = '@'; break;
+ case TOKEN822_DOT: *s++ = '.'; break;
+ case TOKEN822_LEFT: *s++ = '<'; break;
+ case TOKEN822_RIGHT: *s++ = '>'; break;
+ case TOKEN822_SEMI: *s++ = ';'; break;
+ case TOKEN822_COLON: *s++ = ':'; break;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: case TOKEN822_COMMENT:
+ if (t->type == TOKEN822_QUOTE) *s++ = '"';
+ if (t->type == TOKEN822_LITERAL) *s++ = '[';
+ if (t->type == TOKEN822_COMMENT) *s++ = '(';
+
+ for (j = 0; j < t->slen; ++j)
+ switch (ch = t->s[j]) {
+ case '"': case '[': case ']': case '(': case ')':
+ case '\\': case '\r': case '\n': *s++ = '\\';
+ default: *s++ = ch;
+ }
+ if (t->type == TOKEN822_QUOTE) *s++ = '"';
+ if (t->type == TOKEN822_LITERAL) *s++ = ']';
+ if (t->type == TOKEN822_COMMENT) *s++ = ')';
+ break;
+ }
+ }
+ NSUW
+ --s;
+ sa->len = s - sa->s;
+ return 1;
+}
+
+int token822_unquote(stralloc *sa,token822_alloc *ta)
+{
+ struct token822 *t;
+ int len;
+ int i;
+ int j;
+ char *s;
+
+ len = 0;
+
+ for (i = 0; i < ta->len; ++i) {
+ t = ta->t + i;
+ switch (t->type) {
+ case TOKEN822_COMMA: case TOKEN822_AT: case TOKEN822_DOT: case TOKEN822_LEFT:
+ case TOKEN822_RIGHT: case TOKEN822_SEMI: case TOKEN822_COLON:
+ ++len; break;
+ case TOKEN822_LITERAL:
+ len += 2;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE:
+ len += t->slen;
+ }
+ }
+
+ if (!stralloc_ready(sa,len)) return -1;
+
+ s = sa->s;
+
+ for (i = 0; i < ta->len; ++i) {
+ t = ta->t + i;
+ switch (t->type) {
+ case TOKEN822_COMMA: *s++ = ','; break;
+ case TOKEN822_AT: *s++ = '@'; break;
+ case TOKEN822_DOT: *s++ = '.'; break;
+ case TOKEN822_LEFT: *s++ = '<'; break;
+ case TOKEN822_RIGHT: *s++ = '>'; break;
+ case TOKEN822_SEMI: *s++ = ';'; break;
+ case TOKEN822_COLON: *s++ = ':'; break;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+ if (t->type == TOKEN822_LITERAL) *s++ = '[';
+ for (j = 0; j < t->slen; ++j)
+ *s++ = t->s[j];
+ if (t->type == TOKEN822_LITERAL) *s++ = ']';
+ break;
+ case TOKEN822_COMMENT: break;
+ }
+ }
+ sa->len = s - sa->s;
+ return 1;
+}
+
+int token822_parse(token822_alloc *ta,stralloc *sa,stralloc *buf)
+{
+ int i;
+ int salen;
+ int level;
+ struct token822 *t;
+ int numtoks;
+ int numchars;
+ char *cbuf;
+
+ salen = sa->len;
+
+ numchars = 0;
+ numtoks = 0;
+
+ for (i = 0; i < salen; ++i)
+ switch (sa->s[i]) {
+ case '.': case ',': case '@': case '<': case '>': case ':': case ';':
+ ++numtoks; break;
+ case ' ': case '\t': case '\r': case '\n': break;
+ case ')': case ']': return 0;
+ /* other control chars and non-ASCII chars are also bad, in theory */
+ case '(':
+ level = 1;
+ while (level) {
+ if (++i >= salen) return 0;
+ switch (sa->s[i]) {
+ case '(': ++level; break;
+ case ')': --level; break;
+ case '\\': if (++i >= salen) return 0;
+ default: ++numchars;
+ }
+ }
+ ++numtoks;
+ break;
+ case '"':
+ level = 1;
+ while (level) {
+ if (++i >= salen) return 0;
+ switch (sa->s[i]) {
+ case '"': --level; break;
+ case '\\': if (++i >= salen) return 0;
+ default: ++numchars;
+ }
+ }
+ ++numtoks;
+ break;
+ case '[':
+ level = 1;
+ while (level) {
+ if (++i >= salen) return 0;
+ switch (sa->s[i]) {
+ case ']': --level; break;
+ case '\\': if (++i >= salen) return 0;
+ default: ++numchars;
+ }
+ }
+ ++numtoks;
+ break;
+ default:
+ do {
+ if (sa->s[i] == '\\') if (++i >= salen) break;
+ ++numchars;
+ if (++i >= salen) break;
+ } while (atomok(sa->s[i]));
+ --i;
+ ++numtoks;
+ }
+
+ if (!token822_ready(ta,numtoks)) return -1;
+ if (!stralloc_ready(buf,numchars)) return -1;
+ cbuf = buf->s;
+ ta->len = numtoks;
+
+ t = ta->t;
+
+ for (i = 0; i < salen; ++i)
+ switch (sa->s[i]) {
+ case '.': t->type = TOKEN822_DOT; ++t; break;
+ case ',': t->type = TOKEN822_COMMA; ++t; break;
+ case '@': t->type = TOKEN822_AT; ++t; break;
+ case '<': t->type = TOKEN822_LEFT; ++t; break;
+ case '>': t->type = TOKEN822_RIGHT; ++t; break;
+ case ':': t->type = TOKEN822_COLON; ++t; break;
+ case ';': t->type = TOKEN822_SEMI; ++t; break;
+ case ' ': case '\t': case '\r': case '\n': break;
+ case '(':
+ t->type = TOKEN822_COMMENT; t->s = cbuf; t->slen = 0;
+ level = 1;
+ while (level) {
+ ++i; /* assert: < salen */
+ switch (sa->s[i]) {
+ case '(': ++level; break;
+ case ')': --level; break;
+ case '\\': ++i; /* assert: < salen */
+ default: *cbuf++ = sa->s[i]; ++t->slen;
+ }
+ }
+ ++t;
+ break;
+ case '"':
+ t->type = TOKEN822_QUOTE; t->s = cbuf; t->slen = 0;
+ level = 1;
+ while (level) {
+ ++i; /* assert: < salen */
+ switch (sa->s[i]) {
+ case '"': --level; break;
+ case '\\': ++i; /* assert: < salen */
+ default: *cbuf++ = sa->s[i]; ++t->slen;
+ }
+ }
+ ++t;
+ break;
+ case '[':
+ t->type = TOKEN822_LITERAL; t->s = cbuf; t->slen = 0;
+ level = 1;
+ while (level) {
+ ++i; /* assert: < salen */
+ switch (sa->s[i]) {
+ case ']': --level; break;
+ case '\\': ++i; /* assert: < salen */
+ default: *cbuf++ = sa->s[i]; ++t->slen;
+ }
+ }
+ ++t;
+ break;
+ default:
+ t->type = TOKEN822_ATOM; t->s = cbuf; t->slen = 0;
+ do {
+ if (sa->s[i] == '\\') if (++i >= salen) break;
+ *cbuf++ = sa->s[i]; ++t->slen;
+ if (++i >= salen) break;
+ } while (atomok(sa->s[i]));
+ atomcheck(t);
+ --i;
+ ++t;
+ }
+ return 1;
+}
+
+static int gotaddr(token822_alloc *taout,token822_alloc *taaddr,int (*callback)())
+{
+ int i;
+
+ if (callback(taaddr) != 1)
+ return 0;
+
+ if (!token822_readyplus(taout,taaddr->len))
+ return 0;
+
+ for (i = 0; i < taaddr->len; ++i)
+ taout->t[taout->len++] = taaddr->t[i];
+
+ taaddr->len = 0;
+ return 1;
+}
+
+int token822_addrlist(token822_alloc *taout,token822_alloc *taaddr,token822_alloc *ta,int (*callback)())
+{
+ struct token822 *t;
+ struct token822 *beginning;
+ int ingroup;
+ int wordok;
+
+ taout->len = 0;
+ taaddr->len = 0;
+
+ if (!token822_readyplus(taout,1)) return -1;
+ if (!token822_readyplus(taaddr,1)) return -1;
+
+ ingroup = 0;
+ wordok = 1;
+
+ beginning = ta->t + 2;
+ t = ta->t + ta->len - 1;
+
+ /* rfc 822 address lists are easy to parse from right to left */
+
+#define FLUSH if (taaddr->len) if (!gotaddr(taout,taaddr,callback)) return -1;
+#define FLUSHCOMMA if (taaddr->len) { \
+if (!gotaddr(taout,taaddr,callback)) return -1; \
+if (!token822_append(taout,&comma)) return -1; }
+#define ADDRLEFT if (!token822_append(taaddr,t--)) return -1;
+#define OUTLEFT if (!token822_append(taout,t--)) return -1;
+
+ while (t >= beginning) {
+ switch (t->type) {
+ case TOKEN822_SEMI:
+ FLUSHCOMMA
+ if (ingroup) return 0;
+ ingroup = 1;
+ wordok = 1;
+ break;
+ case TOKEN822_COLON:
+ FLUSH
+ if (!ingroup) return 0;
+ ingroup = 0;
+ while ((t >= beginning) && (t->type != TOKEN822_COMMA))
+ OUTLEFT
+ if (t >= beginning)
+ OUTLEFT
+ wordok = 1;
+ continue;
+ case TOKEN822_RIGHT:
+ FLUSHCOMMA
+ OUTLEFT
+ while ((t >= beginning) && (t->type != TOKEN822_LEFT))
+ ADDRLEFT
+ /* important to use address here even if it's empty: <> */
+ if (!gotaddr(taout,taaddr,callback)) return -1;
+ if (t < beginning) return 0;
+ OUTLEFT
+ while ((t >= beginning) && ((t->type == TOKEN822_COMMENT) ||
+ (t->type == TOKEN822_ATOM) || (t->type == TOKEN822_QUOTE) ||
+ (t->type == TOKEN822_AT) || (t->type == TOKEN822_DOT)))
+ OUTLEFT
+ wordok = 0;
+ continue;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+ if (!wordok)
+ FLUSHCOMMA
+ wordok = 0;
+ ADDRLEFT
+ continue;
+ case TOKEN822_COMMENT:
+ /* comment is lexically a space; shouldn't affect wordok */
+ break;
+ case TOKEN822_COMMA:
+ FLUSH
+ wordok = 1;
+ break;
+ default:
+ wordok = 1;
+ ADDRLEFT
+ continue;
+ }
+ OUTLEFT
+ }
+ FLUSH
+ ++t;
+ while (t > ta->t)
+ if (!token822_append(taout,--t)) return -1;
+
+ token822_reverse(taout);
+ return 1;
+}
diff --git a/src/trigger.c b/src/trigger.c
new file mode 100644
index 0000000..ec22e11
--- /dev/null
+++ b/src/trigger.c
@@ -0,0 +1,41 @@
+#include "select.h"
+#include "ndelay.h"
+#include "open.h"
+#include "trigger.h"
+#include "close.h"
+
+static int fd = -1;
+
+void trigger_set()
+{
+ if (fd != -1) close(fd);
+ fd = open_read("lock/trigger");
+}
+
+void trigger_selprep(int *nfds,fd_set *rfds)
+{
+ if (fd != -1) {
+ FD_SET(fd,rfds);
+ if (*nfds < fd + 1) *nfds = fd + 1;
+ }
+}
+
+int trigger_pulled(fd_set *rfds)
+{
+ if (fd != -1) if (FD_ISSET(fd,rfds)) return 1;
+ return 0;
+}
+
+void write(); /* compiler warning (temp) */
+
+void triggerpull()
+{
+ int fd;
+
+ fd = open_write("lock/trigger");
+ if (fd >= 0) {
+ ndelay_on(fd);
+ write(fd,"",1); /* if it fails, bummer */
+ close(fd);
+ }
+}
diff --git a/src/triggerpull.c b/src/triggerpull.c
new file mode 100644
index 0000000..3691c5a
--- /dev/null
+++ b/src/triggerpull.c
@@ -0,0 +1,16 @@
+#include <unistd.h>
+#include "ndelay.h"
+#include "open.h"
+#include "triggerpull.h"
+
+void triggerpull(void)
+{
+ int fd;
+
+ fd = open_write("lock/trigger");
+ if (fd >= 0) {
+ ndelay_on(fd);
+ write(fd,"",1); /* if it fails, bummer */
+ close(fd);
+ }
+}
diff --git a/src/trycpp.c b/src/trycpp.c
new file mode 100644
index 0000000..690f2f3
--- /dev/null
+++ b/src/trycpp.c
@@ -0,0 +1,7 @@
+int main()
+{
+#ifdef NeXT
+ printf("nextstep\n"); exit(0);
+#endif
+ printf("unknown\n"); exit(0);
+}
diff --git a/src/trycrypt.c b/src/trycrypt.c
new file mode 100644
index 0000000..c32bd40
--- /dev/null
+++ b/src/trycrypt.c
@@ -0,0 +1,4 @@
+int main()
+{
+ ;
+}
diff --git a/src/trydnsresolv.c b/src/trydnsresolv.c
new file mode 100644
index 0000000..c32bd40
--- /dev/null
+++ b/src/trydnsresolv.c
@@ -0,0 +1,4 @@
+int main()
+{
+ ;
+}
diff --git a/src/trydrent.c b/src/trydrent.c
new file mode 100644
index 0000000..c778176
--- /dev/null
+++ b/src/trydrent.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <dirent.h>
+
+void foo()
+{
+ DIR *dir;
+ struct dirent *d;
+}
diff --git a/src/tryflock.c b/src/tryflock.c
new file mode 100644
index 0000000..b18743a
--- /dev/null
+++ b/src/tryflock.c
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <sys/file.h>
+#include <fcntl.h>
+
+int main()
+{
+ flock(0,LOCK_EX | LOCK_UN | LOCK_NB);
+}
diff --git a/src/tryidn2.c b/src/tryidn2.c
new file mode 100644
index 0000000..f35850c
--- /dev/null
+++ b/src/tryidn2.c
@@ -0,0 +1,6 @@
+#include <idn2.h>
+
+int main()
+{
+ ;
+}
diff --git a/src/trylsock.c b/src/trylsock.c
new file mode 100644
index 0000000..c32bd40
--- /dev/null
+++ b/src/trylsock.c
@@ -0,0 +1,4 @@
+int main()
+{
+ ;
+}
diff --git a/src/trymkffo.c b/src/trymkffo.c
new file mode 100644
index 0000000..e832a31
--- /dev/null
+++ b/src/trymkffo.c
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+int main()
+{
+ mkfifo("temp-trymkffo",0);
+}
diff --git a/src/trynpbg1.c b/src/trynpbg1.c
new file mode 100644
index 0000000..01a152e
--- /dev/null
+++ b/src/trynpbg1.c
@@ -0,0 +1,26 @@
+#include "select.h"
+#include "open.h"
+#include "fifo.h"
+
+#define FN "temp-trynpbg1.fifo"
+
+int main()
+{
+ int flagbug;
+ struct timeval instant;
+ fd_set rfds;
+
+ flagbug = 0;
+ if (fifo_make(FN,0600) != -1) {
+ close(0);
+ if (open_read(FN) == 0) {
+ FD_ZERO(&rfds);
+ FD_SET(0,&rfds);
+ instant.tv_sec = instant.tv_usec = 0;
+ if (select(1,&rfds,(fd_set *) 0,(fd_set *) 0,&instant) > 0)
+ flagbug = 1;
+ }
+ unlink(FN);
+ }
+ _exit(!flagbug);
+}
diff --git a/src/tryqlibs.c b/src/tryqlibs.c
new file mode 100644
index 0000000..8cc108d
--- /dev/null
+++ b/src/tryqlibs.c
@@ -0,0 +1,6 @@
+#include "stralloc.h"
+
+int main()
+{
+ ;
+}
diff --git a/src/tryrsolv.c b/src/tryrsolv.c
new file mode 100644
index 0000000..4b7857d
--- /dev/null
+++ b/src/tryrsolv.c
@@ -0,0 +1,6 @@
+#include "dnsresolv.h"
+
+int main()
+{
+ ;
+}
diff --git a/src/trysalen.c b/src/trysalen.c
new file mode 100644
index 0000000..731a109
--- /dev/null
+++ b/src/trysalen.c
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+void foo()
+{
+ struct sockaddr sa;
+ sa.sa_len = 0;
+}
diff --git a/src/trysgact.c b/src/trysgact.c
new file mode 100644
index 0000000..1471ecc
--- /dev/null
+++ b/src/trysgact.c
@@ -0,0 +1,10 @@
+#include <signal.h>
+
+int main()
+{
+ struct sigaction sa;
+ sa.sa_handler = 0;
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sigaction(0,&sa,(struct sigaction *) 0);
+}
diff --git a/src/trysgprm.c b/src/trysgprm.c
new file mode 100644
index 0000000..3d73f86
--- /dev/null
+++ b/src/trysgprm.c
@@ -0,0 +1,10 @@
+#include <signal.h>
+
+int main()
+{
+ sigset_t ss;
+
+ sigemptyset(&ss);
+ sigaddset(&ss,SIGCHLD);
+ sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0);
+}
diff --git a/src/tryshadow.c b/src/tryshadow.c
new file mode 100644
index 0000000..fbce408
--- /dev/null
+++ b/src/tryshadow.c
@@ -0,0 +1,4 @@
+main()
+{
+ ;
+}
diff --git a/src/tryshsgr.c b/src/tryshsgr.c
new file mode 100644
index 0000000..81b395c
--- /dev/null
+++ b/src/tryshsgr.c
@@ -0,0 +1,14 @@
+int main()
+{
+ short x[4];
+
+ x[0] = x[1] = 1;
+ if (getgroups(1,x) == 0) if (setgroups(1,x) == -1) _exit(1);
+
+ if (getgroups(1,x) == -1) _exit(1);
+ if (x[1] != 1) _exit(1);
+ x[1] = 2;
+ if (getgroups(1,x) == -1) _exit(1);
+ if (x[1] != 2) _exit(1);
+ _exit(0);
+}
diff --git a/src/tryslib.c b/src/tryslib.c
new file mode 100644
index 0000000..c32bd40
--- /dev/null
+++ b/src/tryslib.c
@@ -0,0 +1,4 @@
+int main()
+{
+ ;
+}
diff --git a/src/tryspnam.c b/src/tryspnam.c
new file mode 100644
index 0000000..00060cf
--- /dev/null
+++ b/src/tryspnam.c
@@ -0,0 +1,9 @@
+#include <shadow.h>
+
+int main()
+{
+ struct spwd *spw;
+
+ spw = getspnam("");
+ puts(spw->sp_pwdp);
+}
diff --git a/src/trysysel.c b/src/trysysel.c
new file mode 100644
index 0000000..abebad5
--- /dev/null
+++ b/src/trysysel.c
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/select.h> /* SVR4 silliness */
+#include <stdio.h>
+#include "select.h"
+
+int main()
+{
+ printf("FD_SETSIZE:%d\n",FD_SETSIZE);
+ return 0;
+}
diff --git a/src/trysyslog.c b/src/trysyslog.c
new file mode 100644
index 0000000..4b99afc
--- /dev/null
+++ b/src/trysyslog.c
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <syslog.h>
+
+main()
+{
+ openlog("foo",0,LOG_MAIL);
+ syslog(0,"foo");
+}
diff --git a/src/tryulong32.c b/src/tryulong32.c
new file mode 100644
index 0000000..20683d6
--- /dev/null
+++ b/src/tryulong32.c
@@ -0,0 +1,11 @@
+int main()
+{
+ unsigned long u;
+ u = 1;
+ u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+ u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+ u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+ u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+ if (!u) _exit(0);
+ _exit(1);
+}
diff --git a/src/tryuserpw.c b/src/tryuserpw.c
new file mode 100644
index 0000000..a359d27
--- /dev/null
+++ b/src/tryuserpw.c
@@ -0,0 +1,9 @@
+#include <userpw.h>
+
+int main()
+{
+ struct userpw *upw;
+
+ upw = getuserpw("");
+ puts(upw->upw_passwd);
+}
diff --git a/src/tryutmp.c b/src/tryutmp.c
new file mode 100644
index 0000000..2a25e5d
--- /dev/null
+++ b/src/tryutmp.c
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include <utmp.h>
+
+int main()
+{
+ ;
+}
diff --git a/src/tryvfork.c b/src/tryvfork.c
new file mode 100644
index 0000000..b01d2f8
--- /dev/null
+++ b/src/tryvfork.c
@@ -0,0 +1,4 @@
+int main()
+{
+ vfork();
+}
diff --git a/src/trywaitp.c b/src/trywaitp.c
new file mode 100644
index 0000000..0380358
--- /dev/null
+++ b/src/trywaitp.c
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+
+int main()
+{
+ waitpid(0,0,0);
+}
diff --git a/src/warn-auto.sh b/src/warn-auto.sh
new file mode 100644
index 0000000..64131e6
--- /dev/null
+++ b/src/warn-auto.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+# WARNING: This file was auto-generated. Do not edit!
+# POSIX compliant usage of options.
diff --git a/src/warn-shsgr b/src/warn-shsgr
new file mode 100644
index 0000000..37c351e
--- /dev/null
+++ b/src/warn-shsgr
@@ -0,0 +1,3 @@
+Oops. Your getgroups() returned 0, and setgroups() failed; this means
+that I can't reliably do my shsgr test. Please either ``make'' as root
+or ``make'' while you're in one or more supplementary groups.
diff --git a/src/wildmat.c b/src/wildmat.c
new file mode 100644
index 0000000..739f943
--- /dev/null
+++ b/src/wildmat.c
@@ -0,0 +1,109 @@
+/*** wildmat.c.orig Wed Dec 3 11:46:31 1997 */
+/* $Revision: 1.1 $
+**
+** Do shell-style pattern matching for ?, \, [], and * characters.
+** Might not be robust in face of malformed patterns; e.g., "foo[a-"
+** could cause a segmentation violation. It is 8bit clean.
+**
+** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+** Rich $alz is now <rsalz@osf.org>.
+** April, 1991: Replaced mutually-recursive calls with in-line code
+** for the star character.
+**
+** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
+** This can greatly speed up failing wildcard patterns. For example:
+** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
+** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
+** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
+** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without
+** the ABORT code, it takes 22310 calls to fail. Ugh. The following
+** explanation is from Lars:
+** The precondition that must be fulfilled is that DoMatch will consume
+** at least one character in text. This is true if *p is neither '*' nor
+** '\0'.) The last return has ABORT instead of FALSE to avoid quadratic
+** behaviour in cases like pattern "*a*b*c*d" with text "abcxxxxx". With
+** FALSE, each star-loop has to run to the end of the text; with ABORT
+** only the last one does.
+**
+** Once the control of one instance of DoMatch enters the star-loop, that
+** instance will return either TRUE or ABORT, and any calling instance
+** will therefore return immediately after (without calling recursively
+** again). In effect, only one star-loop is ever active. It would be
+** possible to modify the code to maintain this context explicitly,
+** eliminating all recursive calls at the cost of some complication and
+** loss of clarity (and the ABORT stuff seems to be unclear enough by
+** itself). I think it would be unwise to try to get this into a
+** released version unless you have a good test data base to try it out
+** on.
+*/
+
+#define TRUE 1
+#define FALSE 0
+#define ABORT -1
+
+/* What character marks an inverted character class? */
+#define NEGATE_CLASS '^'
+/* Is "*" a common pattern? */
+#define OPTIMIZE_JUST_STAR
+/* Do tar(1) matching rules, which ignore a trailing slash? */
+#undef MATCH_TAR_PATTERN
+
+/*
+** Match text and p, return TRUE, FALSE, or ABORT.
+*/
+static int DoMatch(register char *text, register char *p)
+{
+ register int last;
+ register int matched;
+ register int reverse;
+
+ for (; *p; text++, p++) {
+ if (*text == '\0' && *p != '*')
+ return ABORT;
+ switch (*p) {
+ case '\\': /* Literal match with following character. */
+ p++;
+ case '?': /* Match anything. */
+ continue;
+ case '*': /* Consecutive stars act just like one. */
+ while (*++p == '*')
+ continue;
+ if (*p == '\0') return TRUE; /* Trailing star matches everything. */
+ while (*text)
+ if ((matched = DoMatch(text++, p)) != FALSE) return matched;
+ return ABORT;
+ case '[':
+ reverse = p[1] == NEGATE_CLASS ? TRUE : FALSE;
+ if (reverse) p++; /* Inverted character class. */
+ matched = FALSE;
+ if (p[1] == ']' || p[1] == '-')
+ if (*++p == *text) matched = TRUE;
+ for (last = *p; *++p && *p != ']'; last = *p) /* This next line requires a good C compiler. */
+ if (*p == '-' && p[1] != ']' ? *text <= *++p && *text >= last : *text == *p)
+ matched = TRUE;
+ if (matched == reverse) return FALSE;
+ continue;
+ default: /* FALLTHROUGH */
+ if (*text != *p) return FALSE;
+ continue;
+ }
+ }
+
+#ifdef MATCH_TAR_PATTERN
+ if (*text == '/')
+ return TRUE;
+#endif /* MATCH_TAR_ATTERN */
+ return *text == '\0';
+}
+
+/*
+** User-level routine. Returns TRUE or FALSE.
+*/
+int wildmat(char *text,char *p)
+{
+#ifdef OPTIMIZE_JUST_STAR
+ if (p[0] == '*' && p[1] == '\0')
+ return TRUE;
+#endif /* OPTIMIZE_JUST_STAR */
+ return DoMatch(text, p) == TRUE;
+}
diff --git a/src/xqp.sh b/src/xqp.sh
new file mode 100644
index 0000000..16b3fc6
--- /dev/null
+++ b/src/xqp.sh
@@ -0,0 +1,9 @@
+
+awk '
+ /^d/ {
+ if ($9 == x) print
+ }
+ /^m/ {
+ if ($9 == x) print
+ }
+' x="$1"
diff --git a/src/xrecipient.sh b/src/xrecipient.sh
new file mode 100644
index 0000000..e65f74c
--- /dev/null
+++ b/src/xrecipient.sh
@@ -0,0 +1,6 @@
+
+awk '
+ /^d/ {
+ if ($8 == x) print
+ }
+' x="$1"
diff --git a/src/xsender.sh b/src/xsender.sh
new file mode 100644
index 0000000..333a5c0
--- /dev/null
+++ b/src/xsender.sh
@@ -0,0 +1,9 @@
+
+awk '
+ /^d/ {
+ if ($7 == x) print
+ }
+ /^m/ {
+ if ($8 == x) print
+ }
+' x="<$1>"
diff --git a/src/zddist.sh b/src/zddist.sh
new file mode 100644
index 0000000..f147549
--- /dev/null
+++ b/src/zddist.sh
@@ -0,0 +1,7 @@
+echo 'Distribution of ddelays for successful deliveries
+
+Meaning of each line: The first pct% of successful deliveries
+all happened within doneby seconds. The average ddelay was avg.
+'
+( echo doneby avg pct
+HOME/bin/ddist ) | HOME/bin/columnt
diff --git a/src/zdeferrals.sh b/src/zdeferrals.sh
new file mode 100644
index 0000000..affe4b1
--- /dev/null
+++ b/src/zdeferrals.sh
@@ -0,0 +1,8 @@
+echo 'Reasons for deferral
+
+One line per reason for deferral. Information on each line:
+* del is the number of deliveries that ended for this reason.
+* xdelay is the total xdelay on those deliveries.
+'
+( echo del xdelay reason
+HOME/bin/deferrals | sort -k2 ) | HOME/bin/columnt | tr _ ' '
diff --git a/src/zfailures.sh b/src/zfailures.sh
new file mode 100644
index 0000000..91f72ab
--- /dev/null
+++ b/src/zfailures.sh
@@ -0,0 +1,8 @@
+echo 'Reasons for failure
+
+One line per reason for delivery failure. Information on each line:
+* del is the number of deliveries that ended for this reason.
+* xdelay is the total xdelay on those deliveries.
+'
+( echo del xdelay reason
+HOME/bin/failures | sort -k2 ) | HOME/bin/columnt | tr _ ' '
diff --git a/src/zoverall.sh b/src/zoverall.sh
new file mode 100644
index 0000000..d19ec33
--- /dev/null
+++ b/src/zoverall.sh
@@ -0,0 +1,77 @@
+echo 'Basic statistics
+
+qtime is the time spent by a message in the queue.
+
+ddelay is the latency for a successful delivery to one recipient---the
+end of successful delivery, minus the time when the message was queued.
+
+xdelay is the latency for a delivery attempt---the time when the attempt
+finished, minus the time when it started. The average concurrency is the
+total xdelay for all deliveries divided by the time span; this is a good
+measure of how busy the mailer is.
+'
+
+awk '
+ BEGIN {
+ messages = 0
+ recips = 0
+ tries = 0
+ deliveries = 0
+ succ = 0
+ fail = 0
+ mbytes = 0
+ rbytes = 0
+ }
+ /^m/ {
+ ++messages
+ mbytes += $4
+ rbytes += $4 * $5
+ qtime += $3 - $2
+ recips += $5 + $6
+ tries += $5 + $6 + $7
+ if (!seen || ($2 < first)) first = $2
+ if (!seen || ($3 > last)) last = $3
+ seen = 1
+ }
+ /^d k/ { ++succ; ddelay += $5 - $3 }
+ /^d d/ { ++fail }
+ /^d/ {
+ ++deliveries
+ xdelay += $5 - $4
+ if (!seen || ($3 < first)) first = $3
+ if (!seen || ($5 > last)) last = $5
+ seen = 1
+ }
+ END {
+ print "Completed messages:", messages
+ if (messages) {
+ print "Recipients for completed messages:", recips
+ print "Total delivery attempts for completed messages:", tries
+ print "Average delivery attempts per completed message:", tries / messages
+ print "Bytes in completed messages:", mbytes
+ print "Bytes weighted by success:", rbytes
+ print "Average message qtime (s):", qtime / messages
+ }
+ print ""
+ print "Total delivery attempts:", deliveries
+ if (deliveries) {
+ print " success:", succ
+ print " failure:", fail
+ print " deferral:", deliveries - succ - fail
+ str = sprintf("%.6f",ddelay)
+ print "Total ddelay (s):", str
+ if (succ) {
+ str = sprintf("%.6f",ddelay / succ)
+ print "Average ddelay per success (s):", str
+ }
+ str = sprintf("%.6f",xdelay)
+ print "Total xdelay (s):", str
+ str = sprintf("%.6f",xdelay / deliveries)
+ print "Average xdelay per delivery attempt (s):", str
+ if (last > first) {
+ print "Time span (days):", (last - first) / 86400
+ print "Average concurrency:", xdelay / (last - first)
+ }
+ }
+ }
+'
diff --git a/src/zrecipients.sh b/src/zrecipients.sh
new file mode 100644
index 0000000..37f3078
--- /dev/null
+++ b/src/zrecipients.sh
@@ -0,0 +1,10 @@
+echo 'Recipients
+
+One line per recipient. Information on each line:
+* sbytes is the number of bytes successfully delivered to this recipient.
+* mess is the number of messages sent to this recipient (success plus failure).
+* tries is the number of delivery attempts (success, failure, deferral).
+* xdelay is the total xdelay incurred by this recipient.
+'
+( echo sbytes mess tries xdelay recipient
+HOME/bin/recipients | sort -k4 ) | HOME/bin/columnt
diff --git a/src/zrhosts.sh b/src/zrhosts.sh
new file mode 100644
index 0000000..4cd1802
--- /dev/null
+++ b/src/zrhosts.sh
@@ -0,0 +1,10 @@
+echo 'Recipient hosts
+
+One line per recipient host. Information on each line:
+* sbytes is the number of bytes successfully delivered to this host.
+* mess is the number of messages sent to this host (success plus failure).
+* tries is the number of delivery attempts (success, failure, deferral).
+* xdelay is the total xdelay incurred by this host.
+'
+( echo sbytes mess tries xdelay host
+HOME/bin/rhosts | sort -k4 ) | HOME/bin/columnt
diff --git a/src/zrxdelay.sh b/src/zrxdelay.sh
new file mode 100644
index 0000000..cf50e8d
--- /dev/null
+++ b/src/zrxdelay.sh
@@ -0,0 +1,8 @@
+echo 'Recipients in the best order for mailing lists
+
+One line per recipient, sorted by avg. Information on each line:
+* avg is the _average_ xdelay for the recipient.
+* tries is the number of deliveries that avg is based on.
+'
+( echo avg tries recipient
+HOME/bin/recipients | HOME/bin/rxdelay ) | HOME/bin/columnt
diff --git a/src/zsenders.sh b/src/zsenders.sh
new file mode 100644
index 0000000..9f52ce8
--- /dev/null
+++ b/src/zsenders.sh
@@ -0,0 +1,13 @@
+echo 'Senders
+
+One line per sender. Information on each line:
+* mess is the number of messages sent by this sender.
+* bytes is the number of bytes sent by this sender.
+* sbytes is the number of bytes successfully received from this sender.
+* rbytes is the number of bytes from this sender, weighted by recipient.
+* recips is the number of recipients (success plus failure).
+* tries is the number of delivery attempts (success, failure, deferral).
+* xdelay is the total xdelay incurred by this sender.
+'
+( echo mess bytes sbytes rbytes recips tries xdelay sender
+HOME/bin/senders | sort -n -k7 ) | HOME/bin/columnt
diff --git a/src/zsendmail.sh b/src/zsendmail.sh
new file mode 100644
index 0000000..e87715b
--- /dev/null
+++ b/src/zsendmail.sh
@@ -0,0 +1,18 @@
+
+awk '
+ /^d/ {
+ if ($2 == "k") stat="stat=Sent"
+ else if ($2 == "d") stat="stat=Failed"
+ else stat="stat=Deferred"
+ str1 = sprintf("%.6f",$5-$3)
+ str2 = sprintf("%.6f",$5-$4)
+ print $5" qp "$9": to="$8", uid="$10", ddelay="str1", xdelay="str2", "stat" ("$11")"
+ next
+ }
+ /^m/ {
+ str1 = sprintf("%.6f",$3-$2)
+ print $3" qp "$9": from="$8", uid="$10", size="$4", nrcpts="$5+$6", deferrals="$7", qtime="str1
+ next
+ }
+ { print }
+'
diff --git a/src/zsuccesses.sh b/src/zsuccesses.sh
new file mode 100644
index 0000000..93ca179
--- /dev/null
+++ b/src/zsuccesses.sh
@@ -0,0 +1,8 @@
+echo 'Reasons for success
+
+One line per reason for successful delivery. Information on each line:
+* del is the number of deliveries that ended for this reason.
+* xdelay is the total xdelay on those deliveries.
+'
+( echo del xdelay reason
+HOME/bin/successes | sort -k2 ) | HOME/bin/columnt | tr _ ' '
diff --git a/src/zsuids.sh b/src/zsuids.sh
new file mode 100644
index 0000000..ba515f3
--- /dev/null
+++ b/src/zsuids.sh
@@ -0,0 +1,13 @@
+echo 'Sender uids
+
+One line per sender uid. Information on each line:
+* mess is the number of messages sent by this uid.
+* bytes is the number of bytes sent by this uid.
+* sbytes is the number of bytes successfully received from this uid.
+* rbytes is the number of bytes from this uid, weighted by recipient.
+* recips is the number of recipients (success plus failure).
+* tries is the number of delivery attempts (success, failure, deferral).
+* xdelay is the total xdelay incurred by this uid.
+'
+( echo mess bytes sbytes rbytes recips tries xdelay uid
+HOME/bin/suids | sort -n -k7 ) | HOME/bin/columnt