diff options
author | Jannis Hoffmann <jannis@fehcom.de> | 2024-07-03 15:48:04 +0200 |
---|---|---|
committer | Jannis Hoffmann <jannis@fehcom.de> | 2024-07-03 15:48:04 +0200 |
commit | 89b7b67a13ebb7965cc7f13ad0595e2194a2d34c (patch) | |
tree | 25efd77a90ae87236e6730d8ea3846bbe0fd126f /src |
add sqmail-4.2.29asqmail-4.2
Diffstat (limited to 'src')
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("ed,&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("ed,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("ed,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("ed,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 |