summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJannis Hoffmann <jannis@fehcom.de>2024-07-03 15:48:04 +0200
committerJannis Hoffmann <jannis@fehcom.de>2024-07-03 15:48:04 +0200
commit89b7b67a13ebb7965cc7f13ad0595e2194a2d34c (patch)
tree25efd77a90ae87236e6730d8ea3846bbe0fd126f /src
add sqmail-4.2.29asqmail-4.2
Diffstat (limited to 'src')
-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
291 files changed, 33842 insertions, 0 deletions
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