diff --git a/FILES b/FILES index 0f70971..2c463e9 100644 --- a/FILES +++ b/FILES @@ -3,6 +3,7 @@ BLURB2 BLURB3 BLURB4 README +README.auth FAQ INSTALL INSTALL.alias @@ -40,6 +41,7 @@ TARGETS Makefile BIN.README BIN.Makefile +LICENSE.authentication idedit.c conf-break auto_break.h @@ -136,6 +138,8 @@ dnsfq.c dnsip.c dnsmxip.c dnsptr.c +dnstxt.c +spfquery.c hostname.c ipmeprint.c tcp-env.c @@ -336,13 +340,16 @@ envread.c byte.h byte_chr.c byte_copy.c +byte_cspn.c byte_cr.c byte_diff.c byte_rchr.c +byte_rcspn.c byte_zero.c str.h str_chr.c str_cpy.c +str_cpyb.c str_diff.c str_diffn.c str_len.c @@ -402,6 +409,8 @@ date822fmt.h date822fmt.c dns.h dns.c +spf.h +spf.c trylsock.c tryrsolv.c ip.h @@ -432,3 +441,12 @@ maildir.c tcp-environ.5 constmap.h constmap.c +base64.c +base64.h +case_startb.c +global.h +hmac_md5.h +hmac_md5.c +install_authentication.sh +md5.h +md5c.c diff --git a/FILES.auth b/FILES.auth deleted file mode 100644 index e9c4b44..0000000 --- a/FILES.auth +++ /dev/null @@ -1,27 +0,0 @@ -The qmail-smtpd Auth patch modifies the following QMAIL 1.03 files: - -= TARGETS -= Makefile -= qmail-smtpd.c -= qmail-smtpd.8 -= qmail-remote.c -= qmail-remote.8 -= qmail-showctl.c -= qmail-control.9 - -Added files: - -+ base64.c -+ base64.h -+ case_startb.c -+ hmac_md5.h -+ hmac_md5.c -+ global.h -+ md5.h -+ md5c.c - -Informational files: - -% install_auth.sh (Installation shell script) -% LICENSE.authentication (some sort of ...) -% README.auth diff --git a/LICENSE.authentication b/LICENSE.authentication index 707a870..ee95da6 100644 --- a/LICENSE.authentication +++ b/LICENSE.authentication @@ -3,9 +3,9 @@ AUTHOR Author: Dr. Erwin Hoffmann - FEHCom Germany -Web-Site: +Web-Site: http://www.fehcom.de/qmail.html -E-Mail: +E-Mail: feh@fehcom.de diff --git a/Makefile b/Makefile index ca58373..a9b6dcb 100644 --- a/Makefile +++ b/Makefile @@ -140,14 +140,6 @@ base64.o: \ compile base64.c base64.h stralloc.h substdio.h str.h ./compile base64.c -md5c.o : \ -compile md5c.c md5.h - ./compile md5c.c - -hmac_md5.o : \ -compile hmac_md5.c hmac_md5.h global.h - ./compile hmac_md5.c - binm1: \ binm1.sh conf-qmail cat binm1.sh \ @@ -215,6 +207,10 @@ byte_cr.o: \ compile byte_cr.c byte.h ./compile byte_cr.c +byte_cspn.o: \ +compile byte_cspn.c byte.h + ./compile byte_cspn.c + byte_diff.o: \ compile byte_diff.c byte.h ./compile byte_diff.c @@ -223,6 +219,10 @@ byte_rchr.o: \ compile byte_rchr.c byte.h ./compile byte_rchr.c +byte_rcspn.o: \ +compile byte_rcspn.c byte.h + ./compile byte_rcspn.c + byte_zero.o: \ compile byte_zero.c byte.h ./compile byte_zero.c @@ -405,84 +405,96 @@ compile trydrent.c direntry.h1 direntry.h2 rm -f trydrent.o dns.lib: \ -tryrsolv.c compile load socket.lib dns.o ipalloc.o ip.o stralloc.a \ -alloc.a error.a fs.a str.a +tryrsolv.c compile load socket.lib dns.o ipalloc.o strsalloc.o ip.o \ +stralloc.a alloc.a error.a fs.a str.a ( ( ./compile tryrsolv.c && ./load tryrsolv dns.o \ - ipalloc.o ip.o stralloc.a alloc.a error.a fs.a str.a \ + ipalloc.o strsalloc.o ip.o stralloc.a alloc.a error.a fs.a str.a \ -lresolv `cat socket.lib` ) >/dev/null 2>&1 \ && echo -lresolv || exit 0 ) > dns.lib rm -f tryrsolv.o tryrsolv dns.o: \ -compile dns.c ip.h ipalloc.h ip.h gen_alloc.h fmt.h alloc.h str.h \ -stralloc.h gen_alloc.h dns.h case.h +compile dns.c ip.h ipalloc.h strsalloc.h gen_alloc.h fmt.h alloc.h \ +str.h stralloc.h dns.h case.h ./compile dns.c dnscname: \ -load dnscname.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +load dnscname.o dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a alloc.a \ substdio.a error.a str.a fs.a dns.lib socket.lib - ./load dnscname dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + ./load dnscname dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a \ alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ socket.lib` dnscname.o: \ -compile dnscname.c substdio.h subfd.h substdio.h stralloc.h \ +compile dnscname.c substdio.h subfd.h stralloc.h \ gen_alloc.h dns.h dnsdoe.h readwrite.h exit.h ./compile dnscname.c dnsdoe.o: \ -compile dnsdoe.c substdio.h subfd.h substdio.h exit.h dns.h dnsdoe.h +compile dnsdoe.c substdio.h subfd.h exit.h dns.h dnsdoe.h ./compile dnsdoe.c dnsfq: \ -load dnsfq.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +load dnsfq.o dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a alloc.a \ substdio.a error.a str.a fs.a dns.lib socket.lib - ./load dnsfq dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + ./load dnsfq dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a \ alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ socket.lib` dnsfq.o: \ -compile dnsfq.c substdio.h subfd.h substdio.h stralloc.h gen_alloc.h \ -dns.h dnsdoe.h ip.h ipalloc.h ip.h gen_alloc.h exit.h +compile dnsfq.c substdio.h subfd.h stralloc.h gen_alloc.h \ +dns.h dnsdoe.h ip.h ipalloc.h strsalloc.h exit.h ./compile dnsfq.c dnsip: \ -load dnsip.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +load dnsip.o dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a alloc.a \ substdio.a error.a str.a fs.a dns.lib socket.lib - ./load dnsip dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + ./load dnsip dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a \ alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ socket.lib` dnsip.o: \ -compile dnsip.c substdio.h subfd.h substdio.h stralloc.h gen_alloc.h \ -dns.h dnsdoe.h ip.h ipalloc.h ip.h gen_alloc.h exit.h +compile dnsip.c substdio.h subfd.h stralloc.h gen_alloc.h \ +dns.h dnsdoe.h ip.h ipalloc.h strsalloc.h exit.h ./compile dnsip.c dnsmxip: \ -load dnsmxip.o dns.o dnsdoe.o ip.o ipalloc.o now.o stralloc.a alloc.a \ -substdio.a error.a str.a fs.a dns.lib socket.lib - ./load dnsmxip dns.o dnsdoe.o ip.o ipalloc.o now.o \ +load dnsmxip.o dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o now.o stralloc.a \ +alloc.a substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnsmxip dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o now.o \ stralloc.a alloc.a substdio.a error.a str.a fs.a `cat \ dns.lib` `cat socket.lib` dnsmxip.o: \ -compile dnsmxip.c substdio.h subfd.h substdio.h stralloc.h \ -gen_alloc.h fmt.h dns.h dnsdoe.h ip.h ipalloc.h ip.h gen_alloc.h \ +compile dnsmxip.c substdio.h subfd.h stralloc.h \ +gen_alloc.h fmt.h dns.h dnsdoe.h ip.h ipalloc.h strsalloc.h \ now.h datetime.h exit.h ./compile dnsmxip.c dnsptr: \ -load dnsptr.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \ +load dnsptr.o dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a alloc.a \ substdio.a error.a str.a fs.a dns.lib socket.lib - ./load dnsptr dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \ + ./load dnsptr dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a \ alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ socket.lib` dnsptr.o: \ -compile dnsptr.c substdio.h subfd.h substdio.h stralloc.h gen_alloc.h \ +compile dnsptr.c substdio.h subfd.h stralloc.h gen_alloc.h \ str.h scan.h dns.h dnsdoe.h ip.h exit.h ./compile dnsptr.c +dnstxt: \ +load dnstxt.o dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a dns.lib socket.lib + ./load dnstxt dns.o dnsdoe.o ip.o ipalloc.o strsalloc.o stralloc.a \ + alloc.a substdio.a error.a str.a fs.a `cat dns.lib` `cat \ + socket.lib` + +dnstxt.o: \ +compile dnstxt.c substdio.h subfd.h stralloc.h gen_alloc.h \ +str.h scan.h dns.h dnsdoe.h ip.h exit.h + ./compile dnstxt.c + dot-qmail.0: \ dot-qmail.5 nroff -man dot-qmail.5 > dot-qmail.0 @@ -717,6 +729,10 @@ hier.o: \ compile hier.c auto_qmail.h auto_split.h auto_uids.h fmt.h fifo.h ./compile hier.c +hmac_md5.o : \ +compile hmac_md5.c hmac_md5.h global.h + ./compile hmac_md5.c + home: \ home.sh conf-qmail cat home.sh \ @@ -789,24 +805,24 @@ compile ip.c fmt.h scan.h ip.h ./compile ip.c ipalloc.o: \ -compile ipalloc.c alloc.h gen_allocdefs.h ip.h ipalloc.h ip.h \ +compile ipalloc.c alloc.h gen_allocdefs.h ip.h ipalloc.h \ gen_alloc.h ./compile ipalloc.c ipme.o: \ -compile ipme.c hassalen.h byte.h ip.h ipalloc.h ip.h gen_alloc.h \ -stralloc.h gen_alloc.h ipme.h ip.h ipalloc.h +compile ipme.c hassalen.h byte.h ip.h ipalloc.h strsalloc.h ip.h gen_alloc.h \ +stralloc.h gen_alloc.h ipme.h ip.h ipalloc.h strsalloc.h ./compile ipme.c ipmeprint: \ -load ipmeprint.o ipme.o ip.o ipalloc.o stralloc.a alloc.a substdio.a \ -error.a str.a fs.a socket.lib - ./load ipmeprint ipme.o ip.o ipalloc.o stralloc.a alloc.a \ - substdio.a error.a str.a fs.a `cat socket.lib` +load ipmeprint.o ipme.o ip.o ipalloc.o strsalloc.o stralloc.a alloc.a \ +substdio.a error.a str.a fs.a socket.lib + ./load ipmeprint ipme.o ip.o ipalloc.o strsalloc.o stralloc.a \ + alloc.a substdio.a error.a str.a fs.a `cat socket.lib` ipmeprint.o: \ compile ipmeprint.c subfd.h substdio.h substdio.h ip.h ipme.h ip.h \ -ipalloc.h ip.h gen_alloc.h exit.h +ipalloc.h strsalloc.h ip.h gen_alloc.h exit.h ./compile ipmeprint.c it: \ @@ -816,11 +832,11 @@ predate datemail mailsubj qmail-upq qmail-showctl qmail-newu \ qmail-pw2u qmail-qread qmail-qstat qmail-tcpto qmail-tcpok \ qmail-pop3d qmail-popup qmail-qmqpc qmail-qmqpd qmail-qmtpd \ qmail-smtpd sendmail tcp-env qmail-newmrh config config-fast dnscname \ -dnsptr dnsip dnsmxip dnsfq hostname ipmeprint qreceipt qsmhook qbiff \ +dnsptr dnsip dnsmxip dnsfq dnstxt hostname ipmeprint qreceipt qsmhook \ forward preline condredirect bouncesaying except maildirmake \ maildir2mbox maildirwatch qail elq pinq idedit install-big install \ instcheck home home+df proc proc+df binm1 binm1+df binm2 binm2+df \ -binm3 binm3+df +binm3 binm3+df spfquery load: \ make-load warn-auto.sh systype @@ -953,6 +969,10 @@ mbox.0: \ mbox.5 nroff -man mbox.5 > mbox.0 +md5c.o : \ +compile md5c.c md5.h + ./compile md5c.c + myctime.o: \ compile myctime.c datetime.h fmt.h myctime.h ./compile myctime.c @@ -1451,14 +1471,14 @@ auto_qmail.h auto_uids.h date822fmt.h fmtqfn.h qmail-remote: \ load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \ -timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \ +timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o strsalloc.o ipme.o quote.o \ ndelay.a case.a sig.a open.a lock.a seek.a getln.a stralloc.a alloc.a \ substdio.a error.a str.a fs.a auto_qmail.o \ base64.o md5c.o hmac_md5.o \ dns.lib socket.lib ./load qmail-remote control.o constmap.o timeoutread.o \ timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \ - ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \ + ipalloc.o strsalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \ lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \ base64.o md5c.o hmac_md5.o \ str.a fs.a auto_qmail.o `cat dns.lib` `cat socket.lib` @@ -1470,7 +1490,7 @@ qmail-remote.8 qmail-remote.o: \ compile qmail-remote.c sig.h stralloc.h gen_alloc.h substdio.h \ subfd.h substdio.h scan.h case.h error.h auto_qmail.h control.h dns.h \ -alloc.h quote.h ip.h ipalloc.h ip.h gen_alloc.h ipme.h ip.h ipalloc.h \ +alloc.h quote.h ip.h ipalloc.h strsalloc.h ip.h gen_alloc.h ipme.h ip.h ipalloc.h strsalloc.h \ gen_alloc.h gen_allocdefs.h str.h now.h datetime.h exit.h constmap.h \ tcpto.h readwrite.h timeoutconn.h timeoutread.h timeoutwrite.h ./compile qmail-remote.c @@ -1543,21 +1563,21 @@ qmail-showctl.o: \ compile qmail-showctl.c substdio.h subfd.h substdio.h exit.h fmt.h \ str.h control.h constmap.h stralloc.h gen_alloc.h direntry.h \ auto_uids.h auto_qmail.h auto_break.h auto_patrn.h auto_spawn.h \ -auto_split.h +auto_split.h spf.h ./compile qmail-showctl.c qmail-smtpd: \ load qmail-smtpd.o rcpthosts.o commands.o timeoutread.o \ -timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \ -date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \ -open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \ -fs.a auto_qmail.o base64.o socket.lib +timeoutwrite.o ip.o ipme.o ipalloc.o strsalloc.o control.o constmap.o \ +received.o date822fmt.o now.o qmail.o spf.o dns.o cdb.a fd.a wait.a \ +datetime.a getln.a open.a sig.a case.a env.a stralloc.a alloc.a substdio.a \ +error.a str.a fs.a auto_qmail.o base64.o socket.lib dns.lib ./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \ - timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \ - received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ - datetime.a getln.a open.a sig.a case.a env.a stralloc.a \ - alloc.a substdio.a error.a str.a fs.a auto_qmail.o base64.o `cat \ - socket.lib` + timeoutwrite.o ip.o ipme.o ipalloc.o strsalloc.o control.o \ + constmap.o received.o date822fmt.o now.o qmail.o spf.o dns.o cdb.a \ + fd.a wait.a datetime.a getln.a open.a sig.a case.a env.a stralloc.a \ + alloc.a substdio.a error.a str.a fs.a auto_qmail.o base64.o \ + str.a `cat socket.lib` `cat dns.lib` qmail-smtpd.0: \ qmail-smtpd.8 @@ -1566,9 +1586,9 @@ qmail-smtpd.8 qmail-smtpd.o: \ compile qmail-smtpd.c sig.h readwrite.h stralloc.h gen_alloc.h \ substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \ -error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \ +error.h ipme.h ip.h ipalloc.h strsalloc.h ip.h gen_alloc.h ip.h qmail.h \ substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \ -exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h base64.h +exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h spf.h base64.h ./compile qmail-smtpd.c qmail-start: \ @@ -1760,6 +1780,12 @@ substdio.h alloc.h auto_qmail.h exit.h env.h str.h setup: \ it man ./install + mv "`head -1 conf-qmail`/bin/qmail-remote" "`head -1 conf-qmail`/bin/qmail-remote-orig" + cp ./qmail-remote-dkim "`head -1 conf-qmail`/bin/qmail-remote" + ## It seems with qmail-remote being a Perl program, it cannot + ## have the same permissions as qmail-remote-orig. I could + ## get it work with 0755 and did not investigate further. + chmod 0755 "`head -1 conf-qmail`/bin/qmail-remote" sgetopt.o: \ compile sgetopt.c substdio.h subfd.h substdio.h sgetopt.h subgetopt.h \ @@ -1794,7 +1820,7 @@ qmail-popup.c qmail-pw2u.c qmail-qmqpc.c qmail-qmqpd.c qmail-qmtpd.c \ qmail-qread.c qmail-qstat.sh qmail-queue.c qmail-remote.c \ qmail-rspawn.c qmail-send.c qmail-showctl.c qmail-smtpd.c \ qmail-start.c qmail-tcpok.c qmail-tcpto.c spawn.c dnscname.c dnsfq.c \ -dnsip.c dnsmxip.c dnsptr.c hostname.c ipmeprint.c tcp-env.c \ +dnsip.c dnsmxip.c dnsptr.c dnstxt.c hostname.c ipmeprint.c tcp-env.c \ sendmail.c qreceipt.c qsmhook.c qbiff.c forward.c preline.c predate.c \ except.c bouncesaying.c condredirect.c maildirmake.c maildir2mbox.c \ maildirwatch.c splogger.c qail.sh elq.sh pinq.sh qmail-upq.sh \ @@ -1828,8 +1854,9 @@ uint32.h2 tryulong32.c wait.3 wait.h wait_pid.c wait_nohang.c \ trywaitp.c sig.h sig_alarm.c sig_block.c sig_catch.c sig_pause.c \ sig_pipe.c sig_child.c sig_term.c sig_hup.c sig_misc.c sig_bug.c \ trysgact.c trysgprm.c env.3 env.h env.c envread.c byte.h byte_chr.c \ -byte_copy.c byte_cr.c byte_diff.c byte_rchr.c byte_zero.c str.h \ -str_chr.c str_cpy.c str_diff.c str_diffn.c str_len.c str_rchr.c \ +byte_copy.c byte_cr.c byte_cspn.c byte_diff.c byte_rchr.c byte_rcspn.c \ +byte_zero.c str.h spf.c spf.h spfquery.c \ +str_chr.c str_cpy.c str_cpyb.c str_diff.c str_diffn.c str_len.c str_rchr.c \ str_start.c lock.h lock_ex.c lock_exnb.c lock_un.c tryflock.c getln.3 \ getln.h getln.c getln2.3 getln2.c sgetopt.3 sgetopt.h sgetopt.c \ subgetopt.3 subgetopt.h subgetopt.c error.3 error_str.3 error_temp.3 \ @@ -1839,7 +1866,7 @@ slurpclose.h slurpclose.c quote.h quote.c hfield.h hfield.c \ headerbody.h headerbody.c token822.h token822.c control.h control.c \ datetime.3 datetime.h datetime.c datetime_un.c prioq.h prioq.c \ date822fmt.h date822fmt.c dns.h dns.c trylsock.c tryrsolv.c ip.h ip.c \ -ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h ndelay.c \ +ipalloc.h strsalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h ndelay.c \ ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c prot.h \ prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c trysalen.c \ maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c @@ -1912,6 +1939,23 @@ auto_qmail.h auto_uids.h auto_spawn.h ./chkspawn ./compile spawn.c +spf.o: \ +compile spf.c stralloc.h gen_alloc.h alloc.h ipme.h ip.h ipalloc.h \ +strsalloc.h str.h fmt.h scan.h byte.h now.h case.h + ./compile spf.c + +spfquery: \ +load spfquery.o spf.o ip.o ipme.o ipalloc.o strsalloc.o now.o dns.o \ +datetime.a stralloc.a alloc.a str.a substdio.a error.a fs.a case.a dns.lib + ./load spfquery spf.o ip.o ipme.o ipalloc.o strsalloc.o \ + now.o dns.o datetime.a stralloc.a alloc.a str.a substdio.a \ + case.a error.a fs.a `cat dns.lib` `cat socket.lib` + +spfquery.o: \ +compile spfquery.c substdio.h subfd.h stralloc.h gen_alloc.h alloc.h \ +spf.h exit.h + ./compile spfquery.c + splogger: \ load splogger.o substdio.a error.a str.a fs.a syslog.lib socket.lib ./load splogger substdio.a error.a str.a fs.a `cat \ @@ -1927,12 +1971,12 @@ scan.h fmt.h ./compile splogger.c str.a: \ -makelib str_len.o str_diff.o str_diffn.o str_cpy.o str_chr.o \ -str_rchr.o str_start.o byte_chr.o byte_rchr.o byte_diff.o byte_copy.o \ -byte_cr.o byte_zero.o - ./makelib str.a str_len.o str_diff.o str_diffn.o str_cpy.o \ - str_chr.o str_rchr.o str_start.o byte_chr.o byte_rchr.o \ - byte_diff.o byte_copy.o byte_cr.o byte_zero.o +makelib str_len.o str_diff.o str_diffn.o str_cpy.o str_cpyb.o str_chr.o \ +str_rchr.o str_start.o byte_chr.o byte_rchr.o byte_cspn.o byte_rcspn.o \ +byte_diff.o byte_copy.o byte_cr.o byte_zero.o + ./makelib str.a str_len.o str_diff.o str_diffn.o str_cpy.o str_cpyb.o \ + str_chr.o str_rchr.o str_start.o byte_chr.o byte_rchr.o byte_cspn.o \ + byte_rcspn.o byte_diff.o byte_copy.o byte_cr.o byte_zero.o str_chr.o: \ compile str_chr.c str.h @@ -1942,6 +1986,10 @@ str_cpy.o: \ compile str_cpy.c str.h ./compile str_cpy.c +str_cpyb.o: \ +compile str_cpyb.c str.h + ./compile str_cpyb.c + str_diff.o: \ compile str_diff.c str.h ./compile str_diff.c @@ -2021,6 +2069,11 @@ strerr_sys.o: \ compile strerr_sys.c error.h strerr.h ./compile strerr_sys.c +strsalloc.o: \ +compile strsalloc.c alloc.h gen_allocdefs.h stralloc.h strsalloc.h \ +gen_alloc.h + ./compile strsalloc.c + subfderr.o: \ compile subfderr.c readwrite.h substdio.h subfd.h substdio.h ./compile subfderr.c @@ -2081,11 +2134,11 @@ find-systype trycpp.c tcp-env: \ load tcp-env.o dns.o remoteinfo.o timeoutread.o timeoutwrite.o \ -timeoutconn.o ip.o ipalloc.o case.a ndelay.a sig.a env.a getopt.a \ -stralloc.a alloc.a substdio.a error.a str.a fs.a dns.lib socket.lib +timeoutconn.o ip.o ipalloc.o strsalloc.o case.a ndelay.a sig.a env.a \ +getopt.a stralloc.a alloc.a substdio.a error.a str.a fs.a dns.lib socket.lib ./load tcp-env dns.o remoteinfo.o timeoutread.o \ - timeoutwrite.o timeoutconn.o ip.o ipalloc.o case.a ndelay.a \ - sig.a env.a getopt.a stralloc.a alloc.a substdio.a error.a \ + timeoutwrite.o timeoutconn.o ip.o ipalloc.o strsalloc.o case.a \ + ndelay.a sig.a env.a getopt.a stralloc.a alloc.a substdio.a error.a \ str.a fs.a `cat dns.lib` `cat socket.lib` tcp-env.0: \ diff --git a/README.auth b/README.auth index 0a014b5..551d336 100644 --- a/README.auth +++ b/README.auth @@ -4,8 +4,8 @@ README qmail SMTP Authentication Scope: ------ -This patch supports RFC 2554 "SMTP Service Extension for Authentication" and -RFC 4409 "Message Submission for Mail" for +This patch supports RFC 2554 "SMTP Service Extension for Authentication" and +RFC 4409 "Message Submission for Mail" for * qmail-smtpd and * qmail-remote @@ -48,7 +48,7 @@ In order activate SMTP authentication, you need to provide the environment variable 'SMTPAUTH' to qmail-smtpd. Possible choices: - + a) SMTPAUTH=""; qmail-smtpd supports auth of type PLAIN and/or LOGIN. b) SMTPAUTH="+cram"; qmail-smtpd will additionally annonce CRAM-MD5, this requires a CRAM-MD5 supporting PAM. @@ -74,7 +74,7 @@ but with additional authentication information (username + password): Note: The choice of the AUTH method depends on the capabilities of the server. -b) SMTP Authentication by recipient domain: +b) SMTP Authentication by recipient domain: The control file 'smtproutes' is enhanced with the authentication information: @@ -125,7 +125,7 @@ Version: Notes: Date: 0.7.1 cosmetics for qmail-remote 5.8.2010 0.7.2 authorization-id = authentication-id for qmail-remote; - added SMTPAUTH environment variable + added SMTPAUTH environment variable for qmail-smtpd; backport from SC 2.7 29.4.2012 0.7.3 Fixed missing AUTH for qmai-smtpd announcement. Improved SUBMISSION port handling. 2.5.2012 @@ -134,7 +134,7 @@ Version: Notes: Date: 0.7.5 Fixed bug in qmail-remote not respecting announced AUTH types. Tx. Callum Gibson. Added '432' server code evaluation for - AUTH password expired in qmail-remote. 23.10.2012 + AUTH password expired in qmail-remote. 23.10.2012 0.7.6 Fixed order of SMTP commands (tx roberto). 02.02.2013 0.8.0 Added authentication by recipient domain diff --git a/TARGETS b/TARGETS index 4cba994..2b64490 100644 --- a/TARGETS +++ b/TARGETS @@ -101,11 +101,14 @@ str_len.o str_diff.o str_diffn.o str_cpy.o +str_cpyb.o str_chr.o str_rchr.o str_start.o byte_chr.o byte_rchr.o +byte_cspn.o +byte_rcspn.o byte_diff.o byte_copy.o byte_cr.o @@ -172,8 +175,10 @@ timeoutwrite.o timeoutconn.o tcpto.o dns.o +spf.o ip.o ipalloc.o +strsalloc.o hassalen.h ipme.o ndelay.o @@ -215,6 +220,9 @@ qmail-inject.o headerbody.o hfield.o token822.o +spf.o +spfquery.o +spfquery qmail-inject predate.o predate @@ -273,6 +281,8 @@ dnsip.o dnsip dnsmxip.o dnsmxip +dnstxt.o +dnstxt dnsfq.o dnsfq hostname.o diff --git a/base64.c b/base64.c index 9784755..53da4ea 100644 --- a/base64.c +++ b/base64.c @@ -101,7 +101,7 @@ stralloc *out; /* not null terminated */ return 0; } - i = in->len / 3 * 4 + 4; + i = in->len / 3 * 4 + 4; if (!stralloc_ready(out,i)) return -1; s = out->s; diff --git a/byte.h b/byte.h index de06c69..927a5de 100644 --- a/byte.h +++ b/byte.h @@ -3,6 +3,8 @@ extern unsigned int byte_chr(); extern unsigned int byte_rchr(); +extern unsigned int byte_cspn(); +extern unsigned int byte_rcspn(); extern void byte_copy(); extern void byte_copyr(); extern int byte_diff(); diff --git a/byte_cspn.c b/byte_cspn.c new file mode 100644 index 0000000..ed40ebe --- /dev/null +++ b/byte_cspn.c @@ -0,0 +1,11 @@ +#include "byte.h" + +unsigned int byte_cspn(s,n,c) +register char *s; +register unsigned int n; +register char *c; +{ + while(*c) + n = byte_chr(s,n,*c++); + return n; +} diff --git a/byte_rcspn.c b/byte_rcspn.c new file mode 100644 index 0000000..e2bb0ec --- /dev/null +++ b/byte_rcspn.c @@ -0,0 +1,17 @@ +#include "byte.h" + +unsigned int byte_rcspn(s,n,c) +register char *s; +register unsigned int n; +register char *c; +{ + unsigned int ret,pos,i; + + for(ret = n,pos = 0;*c;++c) { + i = byte_rchr(s + pos,n - pos,*c) + pos; + if (i < n) ret = pos = i; + } + + return ret; +} + diff --git a/dns.c b/dns.c index e9faad7..f70ce29 100644 --- a/dns.c +++ b/dns.c @@ -9,6 +9,7 @@ extern int res_query(); extern int res_search(); #include "ip.h" #include "ipalloc.h" +#include "strsalloc.h" #include "fmt.h" #include "alloc.h" #include "str.h" @@ -27,6 +28,7 @@ static unsigned char *responsepos; static int numanswers; static char name[MAXDNAME]; static struct ip_address ip; +static stralloc txt = {0}; unsigned short pref; static stralloc glue = {0}; @@ -177,6 +179,49 @@ int wanttype; return 0; } +static int findtxt(wanttype) +int wanttype; +{ + unsigned short rrtype; + unsigned short rrdlen; + int i; + + if (numanswers <= 0) return 2; + --numanswers; + if (responsepos == responseend) return DNS_SOFT; + + i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME); + if (i < 0) return DNS_SOFT; + responsepos += i; + + i = responseend - responsepos; + if (i < 4 + 3 * 2) return DNS_SOFT; + + rrtype = getshort(responsepos); + rrdlen = getshort(responsepos + 8); + responsepos += 10; + + if (rrtype == wanttype) + { + unsigned short txtpos; + unsigned char txtlen; + + txt.len = 0; + for (txtpos = 0;txtpos < rrdlen;txtpos += txtlen) + { + txtlen = responsepos[txtpos++]; + if (txtlen > rrdlen-txtpos) txtlen = rrdlen-txtpos; + if (!stralloc_catb(&txt,&responsepos[txtpos],txtlen)) return DNS_MEM; + } + + responsepos += rrdlen; + return 1; + } + + responsepos += rrdlen; + return 0; +} + void dns_init(flagsearch) int flagsearch; { @@ -194,7 +239,7 @@ stralloc *sa; if (!sa->len) return loop; if (sa->s[sa->len - 1] == ']') return loop; if (sa->s[sa->len - 1] == '.') { --sa->len; continue; } - switch(resolve(sa,T_ANY)) + switch(resolve(sa,T_CNAME)) { case DNS_MEM: return DNS_MEM; case DNS_SOFT: return DNS_SOFT; @@ -235,15 +280,18 @@ struct ip_address *ip; return len; } -int dns_ptr(sa,ip) -stralloc *sa; +static int dns_ptrplus(ssa,ip) +strsalloc *ssa; struct ip_address *ip; { + stralloc sa = {0}; int r; - if (!stralloc_ready(sa,iaafmt((char *) 0,ip))) return DNS_MEM; - sa->len = iaafmt(sa->s,ip); - switch(resolve(sa,T_PTR)) + if (!stralloc_ready(&sa,iaafmt((char *) 0,ip))) return DNS_MEM; + sa.len = iaafmt(sa.s,ip); + r = resolve(&sa,T_PTR); + alloc_free(sa.s); + switch(r) { case DNS_MEM: return DNS_MEM; case DNS_SOFT: return DNS_SOFT; @@ -254,13 +302,35 @@ struct ip_address *ip; if (r == DNS_SOFT) return DNS_SOFT; if (r == 1) { - if (!stralloc_copys(sa,name)) return DNS_MEM; - return 0; + stralloc sa2 = {0}; + if (!stralloc_copys(&sa2,name)) return DNS_MEM; + if (!strsalloc_append(ssa,&sa2)) return DNS_MEM; } } + if (ssa->len) return 0; return DNS_HARD; } +int dns_ptr(ssa,ip) +strsalloc *ssa; +struct ip_address *ip; +{ + int r; + int j; + + if (!strsalloc_readyplus(ssa,0)) return DNS_MEM; + ssa->len = 0; + r = dns_ptrplus(ssa,ip); + if (r < 0) + { + for (j = 0;j < ssa->len;++j) + alloc_free(ssa->sa[j].s); + ssa->len = 0; + } + return r; +} + + static int dns_ipplus(ia,sa,pref) ipalloc *ia; stralloc *sa; @@ -396,3 +466,49 @@ unsigned long random; alloc_free(mx); return flagsoft; } + + +static int dns_txtplus(ssa,sa) +strsalloc *ssa; +stralloc *sa; +{ + int r; + + switch(resolve(sa,T_TXT)) + { + case DNS_MEM: return DNS_MEM; + case DNS_SOFT: return DNS_SOFT; + case DNS_HARD: return DNS_HARD; + } + while ((r = findtxt(T_TXT)) != 2) + { + if (r == DNS_SOFT) return DNS_SOFT; + if (r == 1) + { + stralloc sa = {0}; + if (!stralloc_copy(&sa,&txt)) return DNS_MEM; + if (!strsalloc_append(ssa,&sa)) return DNS_MEM; + } + } + if (ssa->len) return 0; + return DNS_HARD; +} + +int dns_txt(ssa,sa) +strsalloc *ssa; +stralloc *sa; +{ + int r; + int j; + + if (!strsalloc_readyplus(ssa,0)) return DNS_MEM; + ssa->len = 0; + r = dns_txtplus(ssa,sa); + if (r < 0) + { + for (j = 0;j < ssa->len;++j) + alloc_free(ssa->sa[j].s); + ssa->len = 0; + } + return r; +} diff --git a/dns.h b/dns.h index bca9490..b5992be 100644 --- a/dns.h +++ b/dns.h @@ -10,5 +10,6 @@ int dns_cname(); int dns_mxip(); int dns_ip(); int dns_ptr(); +int dns_txt(); #endif diff --git a/dnsfq.c b/dnsfq.c index b7619b9..a0ae90c 100644 --- a/dnsfq.c +++ b/dnsfq.c @@ -5,15 +5,19 @@ #include "dnsdoe.h" #include "ip.h" #include "ipalloc.h" +#include "strsalloc.h" #include "exit.h" stralloc sa = {0}; +strsalloc ssa = {0}; ipalloc ia = {0}; void main(argc,argv) int argc; char **argv; { + int j; + if (!argv[1]) _exit(100); if (!stralloc_copys(&sa,argv[1])) @@ -25,8 +29,11 @@ char **argv; { substdio_putsflush(subfderr,"no IP addresses\n"); _exit(100); } - dnsdoe(dns_ptr(&sa,&ia.ix[0].ip)); - substdio_putflush(subfdout,sa.s,sa.len); - substdio_putsflush(subfdout,"\n"); + dnsdoe(dns_ptr(&ssa,&ia.ix[0].ip)); + for(j = 0;j < ssa.len;++j) + { + substdio_putflush(subfdout,ssa.sa[j].s,ssa.sa[j].len); + substdio_putsflush(subfdout,"\n"); + } _exit(0); } diff --git a/dnsptr.c b/dnsptr.c index 6a92fe0..270a33f 100644 --- a/dnsptr.c +++ b/dnsptr.c @@ -6,22 +6,28 @@ #include "dns.h" #include "dnsdoe.h" #include "ip.h" +#include "strsalloc.h" #include "exit.h" -stralloc sa = {0}; +strsalloc ssa = {0}; struct ip_address ip; void main(argc,argv) int argc; char **argv; { + int j; + if (!argv[1]) _exit(100); ip_scan(argv[1],&ip); dns_init(0); - dnsdoe(dns_ptr(&sa,&ip)); - substdio_putflush(subfdout,sa.s,sa.len); - substdio_putsflush(subfdout,"\n"); + dnsdoe(dns_ptr(&ssa,&ip)); + for(j = 0;j < ssa.len;++j) + { + substdio_putflush(subfdout,ssa.sa[j].s,ssa.sa[j].len); + substdio_putsflush(subfdout,"\n"); + } _exit(0); } diff --git a/dnstxt.c b/dnstxt.c new file mode 100644 index 0000000..247527f --- /dev/null +++ b/dnstxt.c @@ -0,0 +1,32 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "str.h" +#include "scan.h" +#include "dns.h" +#include "dnsdoe.h" +#include "strsalloc.h" +#include "exit.h" + +strsalloc ssa = {0}; +stralloc sa = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + int j; + + if (!argv[1]) _exit(100); + + if (!stralloc_copys(&sa, argv[1])) + { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); } + dns_init(0); + dnsdoe(dns_txt(&ssa,&sa)); + for (j = 0;j < ssa.len;++j) + { + substdio_put(subfdout,ssa.sa[j].s,ssa.sa[j].len); + substdio_putsflush(subfdout,"\n"); + } + _exit(0); +} diff --git a/global.h b/global.h index e0cf2dc..fa084b7 100644 --- a/global.h +++ b/global.h @@ -40,7 +40,7 @@ typedef unsigned short int UINT2; /* 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. + returns an empty list. */ #if PROTOTYPES #define PROTO_LIST(list) list diff --git a/hier.c b/hier.c index 28e568d..a1af469 100644 --- a/hier.c +++ b/hier.c @@ -104,7 +104,7 @@ void hier() 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-remote",auto_uido,auto_gidq,0755); 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); @@ -131,7 +131,7 @@ void hier() c(auto_qmail,"bin","tcp-env",auto_uido,auto_gidq,0755); c(auto_qmail,"bin","qreceipt",auto_uido,auto_gidq,0755); c(auto_qmail,"bin","qsmhook",auto_uido,auto_gidq,0755); - c(auto_qmail,"bin","qbiff",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); diff --git a/install_authentication.sh b/install_authentication.sh index 49f3786..4d2fce3 100755 --- a/install_authentication.sh +++ b/install_authentication.sh @@ -15,7 +15,7 @@ # # Return Codes: 0 - Patches applied successfully # 1 - Original QMAIL files not found (Patch not extracted in QMAIL source directory) -# 2 - Patch files not found +# 2 - Patch files not found # # Output: install_auth.log # @@ -40,7 +40,7 @@ BUILD=20150823180830 if [ $# -eq 0 ] ; then - echo "Installing qmail AUTH $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 + echo "Installing qmail AUTH $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 for FILE in $(grep "^= " ${TARGETS} | awk '{print $2}'); do echo "Targeting file $FILE ..." | tee -a $LOGFILE 2>&1 @@ -63,27 +63,27 @@ if [ $# -eq 0 ] ; then echo "!! ${FILE}.patch not found !" exit 2 fi - done + done - echo "Copying documentation and samples to ${QMAILHOME}/doc/ ..." | tee -a $LOGFILE 2>&1 + echo "Copying documentation and samples to ${QMAILHOME}/doc/ ..." | tee -a $LOGFILE 2>&1 cp README.auth* ${QMAILHOME}/doc/ | tee -a $LOGFILE 2>&1 echo "" echo "If you dont wont CRAM-MD5 suport disable '#define CRAM_MD5' in qmail-smtpd !" - echo "Installation of qmail authentication $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 + echo "Installation of qmail authentication $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 # Now go for the uninstallation.... elif [ "$1" = "-u" ] ; then -# Get the Version Number from INPUT +# Get the Version Number from INPUT if [ $# -eq 2 ] ; then REL=$2 fi - echo "De-installing qmail authentication $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 + echo "De-installing qmail authentication $REL (Build $BUILD) at $DATE <<<" | tee -a $LOGFILE 2>&1 for FILE in $(grep "^= " ${TARGETS} | awk '{print $2}'); do echo "Targeting file $FILE ..." | tee -a $LOGFILE 2>&1 @@ -95,7 +95,7 @@ elif [ "$1" = "-u" ] ; then echo "!! ${FILE}.$REL not found !" fi done - echo "De-installation of qmail authentication $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 + echo "De-installation of qmail authentication $REL (Build $BUILD) finished at $DATE <<<" | tee -a $LOGFILE 2>&1 fi exit 0 diff --git a/md5.h b/md5.h index 94774ba..9826d62 100644 --- a/md5.h +++ b/md5.h @@ -12,15 +12,15 @@ 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. - + 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. - + 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. + documentation and/or software. */ #ifndef _MD5_H_ diff --git a/md5c.c b/md5c.c index 8b0c856..c07ce23 100644 --- a/md5c.c +++ b/md5c.c @@ -12,15 +12,15 @@ 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. - + 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. - + 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. + documentation and/or software. */ #include "global.h" @@ -128,26 +128,26 @@ unsigned int inputLen; /* length of input block */ < ((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 + MD5_memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); } @@ -170,13 +170,13 @@ MD5_CTX *context; /* context */ 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)); @@ -189,7 +189,7 @@ 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 */ @@ -268,7 +268,7 @@ unsigned char block[64]; state[1] += b; state[2] += c; state[3] += d; - + /* Zeroize sensitive information. */ MD5_memset ((POINTER)x, 0, sizeof (x)); @@ -315,7 +315,7 @@ POINTER input; unsigned int len; { unsigned int i; - + for (i = 0; i < len; i++) output[i] = input[i]; } @@ -328,7 +328,7 @@ int value; unsigned int len; { unsigned int i; - + for (i = 0; i < len; i++) ((char *)output)[i] = (char)value; } diff --git a/qmail-control.9 b/qmail-control.9 index 2857813..ac4f832 100644 --- a/qmail-control.9 +++ b/qmail-control.9 @@ -64,6 +64,10 @@ control default used by .I rcpthosts \fR(none) \fRqmail-smtpd .I smtpgreeting \fIme \fRqmail-smtpd .I smtproutes \fR(none) \fRqmail-remote +.I spfbehavior \fR0 \fRqmail-smtpd +.I spfexp \fR(default) \fRqmail-smtpd +.I spfguess \fR(none) \fRqmail-smtpd +.I spfrules \fR(none) \fRqmail-smtpd .I timeoutconnect \fR60 \fRqmail-remote .I timeoutremote \fR1200 \fRqmail-remote .I timeoutsmtpd \fR1200 \fRqmail-smtpd diff --git a/qmail-remote-dkim b/qmail-remote-dkim new file mode 100755 index 0000000..63b053f --- /dev/null +++ b/qmail-remote-dkim @@ -0,0 +1,308 @@ +#!/usr/local/bin/perl +# +# Copyright (C) 2007 Manuel Mausz (manuel@mausz.at) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +use strict; +use warnings; +our $VERSION = '0.3'; + +use Mail::DKIM 0.29; +use Mail::DKIM::Signer; + +# enable support for "pretty" signatures, if available +eval 'require Mail::DKIM::TextWrap'; + +=head +config file structure + - missing settings will be merged from the global-node + - domain-entry will also match its subdomains + - create empty domain-node to omit signing (or specify "none" as id) + + + + + + + + + + + + + + + + +=cut + +my $configfile = undef; +$configfile = '/var/qmail/control/dkim/signconf.xml'; +my $debugfile = undef; +#$debugfile = '/tmp/dkim.debug'; +my $qremote = '/var/qmail/bin/qmail-remote-orig'; +my $rcpthosts = '/var/qmail/control/rcpthosts'; +my $binary = 0; +our $config; +$config->{'global'} = { + types => { dkim => {} }, + keyfile => '/var/qmail/control/dkim/global.key', + algorithm => 'rsa-sha256', + method => 'simple', + selector => 'beta', + # either undefined (=sender), string or file (first line of file will be used) + #domain => '/var/qmail/control/me' +}; + +#------------------------------------------------------------------------------- + +# read config file. safely +if (defined($configfile) && -r $configfile) +{ + eval 'use XML::Simple'; + if (!$@) + { + my $xmlconf; + eval { $xmlconf = XMLin($configfile, ForceArray => ['types'], KeyAttr => ['id']); }; + qexit_deferral('Unable to read config file: ', $@) + if ($@); + ConfigMerge::merge($config, $xmlconf); + } +} + +# open debug file +my $debugfh = undef; +if (defined($debugfile)) +{ + open($debugfh, '>', $debugfile) + or qexit_deferral('Unable to open ', $debugfile, ' to writing: ', $!); +} + +# generate signatures +my $dkim; +my $mailbuf = ''; +eval +{ + my $conf = $config->{'global'}; + $dkim = Mail::DKIM::Signer->new( + Policy => 'MySignerPolicy', + Debug_Canonicalization => $debugfh + ); + + if ($binary) + { + binmode STDIN; + } + + while () + { + $mailbuf .= $_; + unless ($binary) + { + chomp $_; + s/\015?$/\015\012/s; + } + $dkim->PRINT($_); + } + $dkim->CLOSE(); +}; +qexit_deferral('Error while signing: ', $@) + if ($@); + +# close debug file +close($debugfh) + if (defined($debugfh)); + +# execute qmail-remote +unshift(@ARGV, $qremote); +open(QR, '|-') || exec { $ARGV[0] } @ARGV + or qexit_deferral('Unable to run qmail-remote: ', $!); +foreach my $dkim_signature ($dkim->signatures) +{ + my $sig = $dkim_signature->as_string; + $sig =~ s/\015\012\t/\012\t/g; + print QR $sig."\012"; +} +print QR $mailbuf; +close(QR); + +#------------------------------------------------------------------------------- + +sub qexit +{ + print @_, "\0"; + exit(0); +} + +sub qexit_deferral +{ + return qexit('Z', @_); +} + +sub qexit_failure +{ + return qexit('D', @_); +} + +sub qexit_success +{ + return qexit('K', @_); +} + +#------------------------------------------------------------------------------- + +package ConfigMerge; + +# merge config hashes. arrays and scalars will be copied. +sub merge +{ + my ($left, $right) = @_; + foreach my $rkey (keys(%$right)) + { + my $rtype = ref($right->{$rkey}) eq 'HASH' ? 'HASH' + : ref($right->{$rkey}) eq 'ARRAY' ? 'ARRAY' + : defined($right->{$rkey}) ? 'SCALAR' + : ''; + my $ltype = ref($left->{$rkey}) eq 'HASH' ? 'HASH' + : ref($left->{$rkey}) eq 'ARRAY' ? 'ARRAY' + : defined($left->{$rkey}) ? 'SCALAR' + : ''; + if ($rtype ne 'HASH' || $ltype ne 'HASH') + { + $left->{$rkey} = $right->{$rkey}; + } + else + { + merge($left->{$rkey}, $right->{$rkey}); + } + } + return; +} + +#------------------------------------------------------------------------------- + +package MySignerPolicy; +use Mail::DKIM::SignerPolicy; +use base 'Mail::DKIM::SignerPolicy'; +use Mail::DKIM::Signature; +use Mail::DKIM::DkSignature; +use Carp; +use strict; +use warnings; + +sub apply +{ + my ($self, $signer) = @_; + my $domain = undef; + $domain = lc($signer->message_sender->host) + if (defined($signer) && defined($signer->message_sender) + && defined($signer->message_sender->host)); + my $sender = $domain; + + # merge configs + while($domain) + { + if (defined($config->{$domain})) + { + $config->{'global'}->{'types'} = undef; + ConfigMerge::merge($config->{'global'}, $config->{$domain}); + last; + } + (undef, $domain) = split(/\./, $domain, 2); + } + + my $conf = $config->{'global'}; + return 0 + if (!defined($conf->{'types'}) || defined($conf->{'types'}->{'none'})); + + # set key file + $signer->key_file($conf->{'keyfile'}); + + # parse (signature) domain + if (!defined($conf->{'domain'}) || $conf->{'domain'} eq 'sender') + { + return 0 + if (!defined($sender)); + + $conf->{'domain'} = undef; + open(FH, '<', $rcpthosts) + or croak('Unable to open rcpthosts: '.$!); + while (my $row = ) + { + chomp($row); + if ($row eq $sender) + { + $conf->{'domain'} = $sender; + last; + } + } + close(FH); + return 0 + if (!defined($conf->{'domain'})); + } + elsif (substr($conf->{'domain'}, 0, 1) eq '/') + { + open(FH, '<', $conf->{'domain'}) + or croak('Unable to open domain-file: '.$!); + my $newdom = (split(/ /, ))[0]; + close(FH); + croak("Unable to read domain-file. Maybe empty file.") + if (!$newdom); + chomp($newdom); + $conf->{'domain'} = $newdom; + } + + # generate signatures + my $sigdone = 0; + foreach my $type (keys(%{$conf->{'types'}})) + { + my $sigconf = $conf->{'types'}->{$type}; + + if ($type eq 'dkim') + { + $signer->add_signature( + new Mail::DKIM::Signature( + Algorithm => $sigconf->{'algorithm'} || $conf->{'algorithm'} || $signer->algorithm, + Method => $sigconf->{'method'} || $conf->{'method'} || $signer->method, + Headers => $sigconf->{'headers'} || $conf->{'headers'} || $signer->headers, + Domain => $sigconf->{'domain'} || $conf->{'domain'} || $signer->domain, + Selector => $sigconf->{'selector'} || $conf->{'selector'} || $signer->selector, + Query => $sigconf->{'query'} || $conf->{'query'}, + Identity => $sigconf->{'identity'} || $conf->{'identity'}, + Expiration => $sigconf->{'expiration'} || $conf->{'expiration'} + ) + ); + $sigdone = 1; + } + elsif ($type eq 'domainkey') + { + $signer->add_signature( + new Mail::DKIM::DkSignature( + Algorithm => 'rsa-sha1', # only rsa-sha1 supported + Method => $sigconf->{'method'} || $conf->{'method'} || $signer->method, + Headers => $sigconf->{'selector'} || $conf->{'headers'} || $signer->headers, + Domain => $sigconf->{'domain'} || $conf->{'domain'} || $signer->domain, + Selector => $sigconf->{'selector'} || $conf->{'selector'} || $signer->selector, + Query => $sigconf->{'query'} || $conf->{'query'} + ) + ); + $sigdone = 1; + } + } + + return $sigdone; +} diff --git a/qmail-remote.8 b/qmail-remote.8 index 8ab9b93..9a714a6 100644 --- a/qmail-remote.8 +++ b/qmail-remote.8 @@ -53,7 +53,6 @@ does not take options and does not follow the .B getopt standard. - .SH TRANSPARENCY End-of-file in SMTP is encoded as dot CR LF. A dot at the beginning of a line is encoded as dot dot. @@ -110,17 +109,17 @@ K no supported AUTH method found, continuing without authentication. .TP 5 Z -Connected to +Connected to .I host but authentication was rejected (AUTH PLAIN). .TP 5 Z -Connected to -.I host +Connected to +.I host but unable to base64encode (plain). .TP 5 Z -Connected to +Connected to .I host but authentication was rejected (plain)." .TP 5 @@ -130,18 +129,18 @@ Connected to but authentication was rejected (AUTH LOGIN). .TP 5 Z -Connected to +Connected to .I host but unable to base64encode user. .TP 5 Z -Connected to -.I host +Connected to +.I host but authentication was rejected (username). .TP 5 Z -Connected to -.I host +Connected to +.I host but unable to base64encode pass. .TP 5 Z @@ -149,23 +148,23 @@ Connected to .I host but authentication was rejected (AUTH CRAM-MD5). Z -Connected to +Connected to .I host but unable to base64decode challenge. .TP 5 Z -Connected to +Connected to .I host but unable to base64encode username+digest. .TP 5 Z -Connected to -.I host +Connected to +.I host but password expired. .TP 5 Z -Connected to -.I host +Connected to +.I host but authentication was rejected (username+digest). .PP The recipient reports will always be printed in the same order as @@ -185,36 +184,36 @@ always exits zero. .I authsenders Authenticated sender. For each -.I sender -included in +.I sender +included in .IR authsenders : -.I sender\fB:\fIrelay\fB:\fIport\fB|\fIuser\fB|\fIpassword +.I sender\fB:\fIrelay\fB:\fIport\fB|\fIuser\fB|\fIpassword .B qmail-remote -will try SMTP Authentication -of type CRAM-MD5, LOGIN, or PLAIN +will try SMTP Authentication +of type CRAM-MD5, LOGIN, or PLAIN with the provided user name -.I user +.I user and password -.I password -(the authentication information) -and eventually relay the +.I password +(the authentication information) +and eventually relay the mail through .I relay on port .IR port . -The use of +The use of .I relay -and -.I port +and +.I port follows the same rules as for -.IR smtproutes +.IR smtproutes Note: In case .I sender -is empty, +is empty, .B qmail-remote -will try to deliver each outgoing mail +will try to deliver each outgoing mail SMTP authenticated. If the authentication -information is missing, the mail is +information is missing, the mail is delivered none-authenticated. .I authsenders can be constructed as follows: @@ -241,7 +240,7 @@ refuses to run. Artificial SMTP routes. Each route has the form .IR domain\fB:\fIrelay , -or +or .IR domain\fB:\fIrelay\fB|\fIuser\fB|\fIpassword in case of authenticated routes without any extra spaces. If @@ -293,13 +292,13 @@ any other address is artificially routed to The .B qmail system does not protect you if you create an artificial -mail loop between machines. +mail loop between machines. However, you are always safe using .I smtproutes if you do not accept mail from the network. -Note: -.I authsender +Note: +.I authsender routes have precedence over .IR smtproutes . .TP 5 diff --git a/qmail-remote.c b/qmail-remote.c index ddc7317..b89e728 100644 --- a/qmail-remote.c +++ b/qmail-remote.c @@ -251,12 +251,12 @@ int len; int i; if(!stralloc_copys(sa,"")) temp_nomem(); - + for (i = 0; i < len; i++) { if (s[i] == '=') { if (!stralloc_cats(sa,"+3D")) temp_nomem(); - } else if (s[i] == '+') { - if (!stralloc_cats(sa,"+2B")) temp_nomem(); + } else if (s[i] == '+') { + if (!stralloc_cats(sa,"+2B")) temp_nomem(); } else if ((int) s[i] < 33 || (int) s[i] > 126) { if (!stralloc_cats(sa,"+3F")) temp_nomem(); /* ok. not correct */ } else if (!stralloc_catb(sa,s+i,1)) { @@ -296,7 +296,7 @@ int mailfrom_plain() if (smtpcode() == 235) { mailfrom_xtext(); return 0; } else if (smtpcode() == 432) { quit("ZConnected to "," but password expired."); return 1; } else { quit("ZConnected to "," but authentication was rejected (plain)."); return 1; } - + return 0; } @@ -340,13 +340,13 @@ int mailfrom_cram() 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(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] & 0xf]; + digascii[2*j] = hextab[digest[j] >> 4]; + digascii[2*j+1] = hextab[digest[j] & 0xf]; } digascii[32]=0; @@ -354,7 +354,7 @@ int mailfrom_cram() 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_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."); @@ -363,25 +363,25 @@ int mailfrom_cram() substdio_flush(&smtpto); if (smtpcode() == 235) { mailfrom_xtext(); return 0; } else if (smtpcode() == 432) { quit("ZConnected to "," but password expired."); return 1; } - else { quit("ZConnected to "," but authentication was rejected (username+digest)."); return 1; } + else { quit("ZConnected to "," but authentication was rejected (username+digest)."); return 1; } } void smtp_auth() { - int i, j; + int i, j; for (i = 0; i + 8 < smtptext.len; i += str_chr(smtptext.s+i,'\n')+1) - if (!str_diffn(smtptext.s+i+4,"AUTH",4)) { + if (!str_diffn(smtptext.s+i+4,"AUTH",4)) { if (j = str_chr(smtptext.s+i+8,'C') > 0) if (case_starts(smtptext.s+i+8+j,"CRAM")) if (mailfrom_cram() >= 0) return; if (j = str_chr(smtptext.s+i+8,'P') > 0) - if (case_starts(smtptext.s+i+8+j,"PLAIN")) + if (case_starts(smtptext.s+i+8+j,"PLAIN")) if (mailfrom_plain() >= 0) return; if (j = str_chr(smtptext.s+i+8,'L') > 0) - if (case_starts(smtptext.s+i+8+j,"LOGIN")) + if (case_starts(smtptext.s+i+8+j,"LOGIN")) if (mailfrom_login() >= 0) return; err_authprot(); @@ -416,7 +416,7 @@ void smtp() if (user.len && pass.len) smtp_auth(); - else + else mailfrom(); code = smtpcode(); @@ -509,7 +509,7 @@ void getcontrols() case 1: if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break; } - + switch(control_readfile(&authsenders,"control/authsenders",0)) { case -1: temp_control(); @@ -532,7 +532,7 @@ char **argv; int flagallaliases; int flagalias; char *relayhost; - + sig_pipeignore(); if (argc < 4) perm_usage(); if (chdir(auto_qmail) == -1) temp_chdir(); diff --git a/qmail-showctl.c b/qmail-showctl.c index dbd93fd..e25475b 100644 --- a/qmail-showctl.c +++ b/qmail-showctl.c @@ -15,6 +15,7 @@ #include "auto_patrn.h" #include "auto_spawn.h" #include "auto_split.h" +#include "spf.h" stralloc me = {0}; int meok; @@ -258,6 +259,10 @@ void main() do_str("smtpgreeting",1,"smtpgreeting","SMTP greeting: 220 "); do_lst("smtproutes","No artificial SMTP routes.","SMTP route: ",""); + do_int("spfbehavior","0","The SPF behavior is ",""); + do_str("spfexp",0,SPF_DEFEXP,"The SPF default explanation is: 550 "); + do_str("spfguess",0,"","The guess SPF rules are: "); + do_str("spfrules",0,"","The local SPF rules are: "); 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"); @@ -292,6 +297,10 @@ void main() if (str_equal(d->d_name,"rcpthosts")) continue; if (str_equal(d->d_name,"smtpgreeting")) continue; if (str_equal(d->d_name,"smtproutes")) continue; + if (str_equal(d->d_name,"spfbehavior")) continue; + if (str_equal(d->d_name,"spfexp")) continue; + if (str_equal(d->d_name,"spfguess")) continue; + if (str_equal(d->d_name,"spfrules")) 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; diff --git a/qmail-smtpd.8 b/qmail-smtpd.8 index 785d5ba..acc9840 100644 --- a/qmail-smtpd.8 +++ b/qmail-smtpd.8 @@ -229,15 +229,15 @@ In case is defined, .B qmail-smtpd honors LOGIN, PLAIN, and additionally CRAM-MD5 authentication. -Simply +Simply .TP 5 .IR SMTPAUTH='cram' restricts authentication just to CRAM-MD5. If however .TP 5 .IR SMTPAUTH='!' -starts with an exclamation mark, AUTH is required. -You can enforce 'Submission' using this option +starts with an exclamation mark, AUTH is required. +You can enforce 'Submission' using this option and binding .B qmail-smtpd to the SUBMISSION port \'587'\. @@ -253,6 +253,42 @@ connections. Note: The use of 'cram' requires a CRAM-MD5 enabled PAM. +.TP 5 +.I spfbehavior +Set to a value between 1 and 6 to enable SPF checks; 0 to disable. +1 selects 'annotate-only' mode, where +.B qmail-smtpd +will annotate incoming email with +.B Received-SPF +fields, but will not reject any messages. 2 will produce temporary +failures on DNS lookup problems so you can make sure you always have +meaningful Received-SPF headers. 3 selects 'reject' mode, +where incoming mail will be rejected if the SPF record says 'fail'. 4 +selects a more stricter rejection mode, which is like 'reject' mode, +except that incoming mail will also be rejected when the SPF record +says 'softfail'. 5 will also reject when the SPF record says 'neutral', +and 6 if no SPF records are available at all (or a syntax error was +encountered). The contents of this file are overridden by the value of +the +.B SPFBEHAVIOR +environment variable, if set. +Default: 0. +.TP 5 +.I spfexp +You can add a line with a an SPF explanation that will be shown to the +sender in case of a reject. It will override the default one. You can +use SPF macro expansion. +.TP 5 +.I spfguess +You can add a line with SPF rules that will be checked if a sender +domain doesn't have a SPF record. The local rules will also be used +in this case. +.TP 5 +.I spfrules +You can add a line with SPF rules that will be checked before other SPF +rules would fail. This can be used to always allow certain machines to +send certain mails. + .SH "SEE ALSO" tcp-env(1), tcp-environ(5), diff --git a/qmail-smtpd.c b/qmail-smtpd.c index 4ca7293..1283dbd 100644 --- a/qmail-smtpd.c +++ b/qmail-smtpd.c @@ -24,12 +24,13 @@ #include "timeoutwrite.h" #include "commands.h" #include "wait.h" +#include "spf.h" #define AUTHSLEEP 5 - #define MAXHOPS 100 unsigned int databytes = 0; int timeout = 1200; +unsigned int spfbehavior = 0; int safewrite(fd,buf,len) int fd; char *buf; int len; { @@ -50,7 +51,7 @@ 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 straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); } +void straynewline() { out("451 See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); } void err_size() { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); } void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } @@ -76,6 +77,9 @@ void err_authfail() { out("535 authentication failed (#5.7.1)\r\n"); } void err_submission() { out("530 Authorization required (#5.7.1) \r\n"); } stralloc greeting = {0}; +stralloc spflocal = {0}; +stralloc spfguess = {0}; +stralloc spfexp = {0}; void smtp_greet(code) char *code; { @@ -84,7 +88,7 @@ void smtp_greet(code) char *code; } void smtp_help(arg) char *arg; { - out("214 netqmail home page: http://qmail.org/netqmail\r\n"); + out("214 Omg, you didn't take ``SMTP 101'' in college.\r\n"); } void smtp_quit(arg) char *arg; { @@ -128,6 +132,7 @@ void setup() 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(); bmfok = control_readfile(&bmf,"control/badmailfrom",0); @@ -141,6 +146,19 @@ void setup() if (!(databytes + 1)) --databytes; protocol = "SMTP"; + if (control_readint(&spfbehavior,"control/spfbehavior") == -1) + die_control(); + x = env_get("SPFBEHAVIOR"); + if (x) { scan_ulong(x,&u); spfbehavior = u; } + + if (control_readline(&spflocal,"control/spfrules") == -1) die_control(); + if (spflocal.len && !stralloc_0(&spflocal)) die_nomem(); + if (control_readline(&spfguess,"control/spfguess") == -1) die_control(); + if (spfguess.len && !stralloc_0(&spfguess)) die_nomem(); + if (control_rldef(&spfexp,"control/spfexp",0,SPF_DEFEXP) == -1) + die_control(); + if (!stralloc_0(&spfexp)) die_nomem(); + remoteip = env_get("TCPREMOTEIP"); if (!remoteip) remoteip = "unknown"; local = env_get("TCPLOCALHOST"); @@ -166,6 +184,7 @@ void setup() dohelo(remotehost); } + stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ int addrparse(arg) @@ -247,11 +266,13 @@ int addrallowed() return r; } -char *auth; + int seenauth = 0; int seenmail = 0; int flagbarf; /* defined if seenmail */ int flagsize; +int flagbarfspf; +stralloc spfbarfmsg = {0}; stralloc mailfrom = {0}; stralloc rcptto = {0}; stralloc fuser = {0}; @@ -268,13 +289,13 @@ int mailfrom_size(arg) char *arg; return 0; } -void mailfrom_auth(arg,len) -char *arg; +void mailfrom_auth(arg,len) +char *arg; int len; { if (!stralloc_copys(&fuser,"")) die_nomem(); if (case_starts(arg,"<>")) { if (!stralloc_cats(&fuser,"unknown")) die_nomem(); } - else + else while (len) { if (*arg == '+') { if (case_starts(arg,"+3D")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"=")) die_nomem(); } @@ -302,14 +323,14 @@ void mailfrom_parms(arg) char *arg; i = byte_chr(arg,len,'>'); if (i > 4 && i < len) { while (len) { - arg++; len--; + arg++; len--; if (*arg == ' ' || *arg == '\0' ) { 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 (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(); + if (!stralloc_catb(&mfparms,arg,1)) die_nomem(); } } } @@ -323,8 +344,7 @@ void smtp_ehlo(arg) char *arg; { char size[FMT_ULONG]; size[fmt_ulong(size,(unsigned int) databytes)] = 0; - smtp_greet("250-"); - out("\r\n250-PIPELINING\r\n250-8BITMIME\r\n"); + smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250-8BITMIME\r\n"); if (smtpauth == 1 || smtpauth == 11) out("250-AUTH LOGIN PLAIN\r\n"); if (smtpauth == 2 || smtpauth == 12) out("250-AUTH CRAM-MD5\r\n"); if (smtpauth == 3 || smtpauth == 13) out("250-AUTH LOGIN PLAIN CRAM-MD5\r\n"); @@ -333,29 +353,88 @@ void smtp_ehlo(arg) char *arg; } void smtp_rset(arg) char *arg; { - seenmail = 0; seenauth = 0; + seenmail = 0; seenauth = 0; mailfrom.len = 0; rcptto.len = 0; out("250 flushed\r\n"); } + void smtp_mail(arg) char *arg; { + int r; if (smtpauth) if (smtpauth > 10 && !seenauth) { err_submission(); return; } + if (!addrparse(arg)) { err_syntax(); return; } flagsize = 0; mailfrom_parms(arg); if (flagsize) { err_size(); return; } flagbarf = bmfcheck(); + flagbarfspf = 0; + if (spfbehavior && !relayclient) + { + switch(r = spfcheck()) { + case SPF_OK: env_put2("SPFRESULT","pass"); break; + case SPF_NONE: env_put2("SPFRESULT","none"); break; + case SPF_UNKNOWN: env_put2("SPFRESULT","unknown"); break; + case SPF_NEUTRAL: env_put2("SPFRESULT","neutral"); break; + case SPF_SOFTFAIL: env_put2("SPFRESULT","softfail"); break; + case SPF_FAIL: env_put2("SPFRESULT","fail"); break; + case SPF_ERROR: env_put2("SPFRESULT","error"); break; + } + switch (r) { + case SPF_NOMEM: + die_nomem(); + case SPF_ERROR: + if (spfbehavior < 2) break; + out("451 SPF lookup failure (#4.3.0)\r\n"); + return; + case SPF_NONE: + case SPF_UNKNOWN: + if (spfbehavior < 6) break; + case SPF_NEUTRAL: + if (spfbehavior < 5) break; + case SPF_SOFTFAIL: + if (spfbehavior < 4) break; + case SPF_FAIL: + if (spfbehavior < 3) break; + if (!spfexplanation(&spfbarfmsg)) die_nomem(); + if (!stralloc_0(&spfbarfmsg)) die_nomem(); + flagbarfspf = 1; + } + } + else + env_unset("SPFRESULT"); seenmail = 1; if (!stralloc_copys(&rcptto,"")) die_nomem(); if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); if (!stralloc_0(&mailfrom)) die_nomem(); out("250 ok\r\n"); } + +void err_spf() { + int i,j; + + for(i = 0; i < spfbarfmsg.len; i = j + 1) { + j = byte_chr(spfbarfmsg.s + i, spfbarfmsg.len - i, '\n') + i; + if (j < spfbarfmsg.len) { + out("550-"); + spfbarfmsg.s[j] = 0; + out(spfbarfmsg.s); + spfbarfmsg.s[j] = '\n'; + out("\r\n"); + } else { + out("550 "); + out(spfbarfmsg.s); + out(" (#5.7.1)\r\n"); + } + } +} + void smtp_rcpt(arg) char *arg; { if (!seenmail) { err_wantmail(); return; } if (!addrparse(arg)) { err_syntax(); return; } if (flagbarf) { err_bmf(); return; } + if (flagbarfspf) { err_spf(); return; } if (relayclient) { --addr.len; if (!stralloc_cats(&addr,relayclient)) die_nomem(); @@ -456,6 +535,25 @@ int *hops; } } +void spfreceived() +{ + stralloc sa = {0}; + stralloc rcvd_spf = {0}; + + if (!spfbehavior || relayclient) return; + + if (!stralloc_copys(&rcvd_spf, "Received-SPF: ")) die_nomem(); + if (!spfinfo(&sa)) die_nomem(); + if (!stralloc_cat(&rcvd_spf, &sa)) die_nomem(); + if (!stralloc_append(&rcvd_spf, "\n")) die_nomem(); + if (bytestooverflow) { + bytestooverflow -= rcvd_spf.len; + if (bytestooverflow <= 0) qmail_fail(&qqt); + } + qmail_put(&qqt,rcvd_spf.s,rcvd_spf.len); +} + + char accept_buf[FMT_ULONG]; void acceptmessage(qp) unsigned long qp; { @@ -484,6 +582,7 @@ void smtp_data(arg) char *arg; { out("354 go ahead\r\n"); received(&qqt,protocol,local,remoteip,remotehost,remoteinfo,fakehelo); + spfreceived(); blast(&hops); hops = (hops >= MAXHOPS); if (hops) qmail_fail(&qqt); @@ -558,7 +657,7 @@ int authenticate(void) substdio_fdbuf(&ssauth,write,pi[1],ssauthbuf,sizeof ssauthbuf); if (substdio_put(&ssauth,user.s,user.len) == -1) return err_write(); if (substdio_put(&ssauth,pass.s,pass.len) == -1) return err_write(); - if (smtpauth == 2 || smtpauth == 3 || smtpauth == 12 || smtpauth == 13) + if (smtpauth == 2 || smtpauth == 3 || smtpauth == 12 || smtpauth == 13) if (substdio_put(&ssauth,chal.s,chal.len) == -1) return err_write(); if (substdio_flush(&ssauth) == -1) return err_write(); diff --git a/spf.c b/spf.c new file mode 100644 index 0000000..d2a5f27 --- /dev/null +++ b/spf.c @@ -0,0 +1,878 @@ +#include "stralloc.h" +#include "strsalloc.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" + +#define SPF_EXT -1 +#define SPF_SYNTAX -2 + +#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) + +/* 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, +}; + + +extern stralloc addr; +extern stralloc helohost; +extern char *remoteip; +extern char *local; + +extern stralloc spflocal; +extern stralloc spfguess; +extern stralloc spfexp; + +static stralloc sender_fqdn = {0}; +static stralloc explanation = {0}; +static stralloc expdomain = {0}; +static stralloc errormsg = {0}; +static char *received; + +static int recursion; +static struct ip_address ip; + +static void hdr_pass() { received = "pass (%{xr}: %{xs} designates %{i} as permitted sender)"; }; +static void hdr_softfail() { received = "softfail (%{xr}: transitioning %{xs} does not designate %{i} as permitted sender)"; }; +static void hdr_fail() { received = "fail (%{xr}: %{xs} does not designate %{i} as permitted sender)"; }; +static void hdr_unknown() { received = "unknown (%{xr}: domain at %{d} does not designate permitted sender hosts)"; }; +static void hdr_neutral() { received = "neutral (%{xr}: %{i} is neither permitted nor denied by %{xs})"; }; +static void hdr_none() { received = "none (%{xr}: domain at %{d} does not designate permitted sender hosts)"; }; +static void hdr_unknown_msg(e) char *e; { stralloc_copys(&errormsg, e); received = "unknown (%{xr}: %{xe})"; }; +static void hdr_ext(e) char *e; { stralloc_copys(&errormsg, e); received = "unknown %{xe} (%{xr}: %{xs} uses mechanism not recognized by this client)"; }; +static void hdr_syntax() { received = "unknown (%{xr}: parse error in %{xs})"; }; +static void hdr_error(e) char *e; { stralloc_copys(&errormsg, e); received = "error (%{xr}: error in processing during lookup of %{d}: %{xe})"; }; +static void hdr_dns() { hdr_error("DNS problem"); } + + +static int matchip(struct ip_address *net, int mask, struct ip_address *ip) +{ + int j; + int bytemask; + + for (j = 0; j < 4 && mask > 0; ++j) { + if (mask > 8) bytemask = 8; else bytemask = mask; + mask -= bytemask; + + if ((net->d[j] ^ ip->d[j]) & (0x100 - (1 << (8 - bytemask)))) + return 0; + } + return 1; +} + +static int getipmask(char *mask, int ipv6) { + unsigned long r; + int pos; + + if (!mask) return 32; + + pos = scan_ulong(mask, &r); + if (!pos || (mask[pos] && !(mask[pos] == '/' && ipv6))) return -1; + if (r > 32) return -1; + + return r; +} + +int spfget(stralloc *spf, stralloc *domain) +{ + strsalloc ssa = {0}; + int j; + int begin, pos, i; + int r = SPF_NONE; + + spf->len = 0; + + switch(dns_txt(&ssa, domain)) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: hdr_dns(); return SPF_ERROR; + case DNS_HARD: return SPF_NONE; + } + + for (j = 0;j < ssa.len;++j) { + pos = 0; + + NXTOK(begin, pos, &ssa.sa[j]); + if (str_len(ssa.sa[j].s + begin) < 6) continue; + if (!byte_equal(ssa.sa[j].s + begin,6,"v=spf1")) continue; + if (ssa.sa[j].s[begin + 6]) { + /* check for subversion */ + if (ssa.sa[j].s[begin + 6] != '.') continue; + for(i = begin + 7;;++i) + if (!(ssa.sa[j].s[i] >= '0' && ssa.sa[j].s[i] <= '9')) break; + if (i == (begin + 7)) continue; + if (ssa.sa[j].s[i]) continue; + } + + if (spf->len > 0) { + spf->len = 0; + hdr_unknown_msg("Multiple SPF records returned"); + r = SPF_UNKNOWN; + break; + } + if (!stralloc_0(&ssa.sa[j])) return SPF_NOMEM; + if (!stralloc_copys(spf,ssa.sa[j].s + pos)) return SPF_NOMEM; + r = SPF_OK; + } + + for (j = 0;j < ssa.len;++j) + alloc_free(ssa.sa[j].s); + alloc_free(ssa.sa); + return r; +} + +static int spf_ptr(char *spec, char *mask); + +int spfsubst(stralloc *expand, char *spec, char *domain) +{ + static char hexdigits[] = "0123456789abcdef"; + stralloc sa = {0}; + char ch; + int digits = -1; + int urlencode = 0; + int reverse = 0; + int start = expand->len; + int i, pos; + char *split = "."; + + if (!stralloc_readyplus(&sa,0)) return 0; + + if (*spec == 'x') { i = 1; ++spec; } else i = 0; + ch = *spec++; + if (!ch) { alloc_free(sa.s); return 1; } + if (ch >= 'A' && ch <= 'Z') { ch += 32; urlencode = 1; } + if (i) ch -= 32; + while(*spec >= '0' && *spec <= '9') { + if (digits < 0) digits = 0; + if (digits >= 1000000) { digits = 10000000; continue; } + digits = (digits * 10) + (*spec - '0'); + spec++; + } + + while((*spec >= 'a' && *spec <= 'z') || (*spec >= 'A' && *spec <= 'Z')) { + if (*spec == 'r') reverse = 1; + spec++; + } + + if (*spec) split = spec; + + switch(ch) { + case 'l': + pos = byte_rchr(addr.s, addr.len, '@'); + if (pos < addr.len) { + if (!stralloc_copyb(&sa, addr.s, pos)) return 0; + } else + if (!stralloc_copys(&sa, "postmaster")) return 0; + break; + case 's': + if (!stralloc_copys(&sa, addr.s)) return 0; + break; + case 'o': + pos = byte_rchr(addr.s, addr.len, '@') + 1; + if (pos > addr.len) break; + if (!stralloc_copys(&sa, addr.s + pos)) return 0; + break; + case 'd': + if (!stralloc_copys(&sa, domain)) return 0; + break; + case 'i': + if (!stralloc_ready(&sa, IPFMT)) return 0; + sa.len = ip_fmt(sa.s, &ip); + break; + case 't': + if (!stralloc_ready(&sa, FMT_ULONG)) return 0; + sa.len = fmt_ulong(sa.s, (unsigned long)now()); + break; + case 'p': + if (!sender_fqdn.len) + spf_ptr(domain, 0); + if (sender_fqdn.len) { + if (!stralloc_copy(&sa, &sender_fqdn)) return 0; + } else + if (!stralloc_copys(&sa, "unknown")) return 0; + break; + case 'v': + if (!stralloc_copys(&sa, "in-addr")) return 0; + break; + case 'h': + if (!stralloc_copys(&sa, helohost.s)) return 0; /* FIXME: FQDN? */ + break; + case 'E': + if (errormsg.len && !stralloc_copy(&sa, &errormsg)) return 0; + break; + case 'R': + if (!stralloc_copys(&sa, local)) return 0; + break; + case 'S': + if (expdomain.len > 0) { + if (!stralloc_copys(&sa, "SPF record at ")) return 0; + if (!stralloc_cats(&sa, expdomain.s)) return 0; + } else { + if (!stralloc_copys(&sa, "local policy")) return 0; + } + break; + } + + if (reverse) { + for(pos = 0; digits; ++pos) { + pos += byte_cspn(sa.s + pos, sa.len - pos, split); + if (pos >= sa.len) break; + if (!--digits) break; + } + + for(; pos > 0; pos = i - 1) { + i = byte_rcspn(sa.s, pos, split) + 1; + if (i > pos) i = 0; + if (!stralloc_catb(expand, sa.s + i, pos - i)) return 0; + if (i > 0 && !stralloc_append(expand, ".")) return 0; + } + } else { + for(pos = sa.len; digits; --pos) { + i = byte_rcspn(sa.s, pos, split) + 1; + if (i > pos) { pos = 0; break; } + pos = i; + if (!--digits) break; + } + + if (!stralloc_catb(expand, sa.s + pos, sa.len - pos)) return 0; + if (split[0] != '.' || split[1]) + for(pos = 0; pos < expand->len; pos++) { + pos += byte_cspn(expand->s + pos, expand->len - pos, split); + if (pos < expand->len) + expand->s[pos] = '.'; + } + } + + if (urlencode) { + stralloc_copyb(&sa, expand->s + start, expand->len - start); + expand->len = start; + + for(pos = 0; pos < sa.len; ++pos) { + ch = sa.s[pos]; + if (urlchr_table[(unsigned char)ch]) { + if (!stralloc_readyplus(expand, 3)) return 0; + expand->s[expand->len++] = '%'; + expand->s[expand->len++] = hexdigits[(unsigned char)ch >> 4]; + expand->s[expand->len++] = hexdigits[(unsigned char)ch & 15]; + } else + if (!stralloc_append(expand, &ch)) return 0; + } + } + + alloc_free(sa.s); + return 1; +} + +int spfexpand(stralloc *sa, char *spec, char *domain) +{ + char *p; + char append; + int pos; + + if (!stralloc_readyplus(sa, 0)) return 0; + sa->len = 0; + + for(p = spec; *p; p++) { + append = *p; + if (*p == '%') { + 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 (!spfsubst(sa, p + 1, domain)) return 0; + p += pos; + continue; + default: p--; + } + } + if (!stralloc_append(sa, &append)) return 0; + } + + return 1; +} + +static int spflookup(stralloc *domain); + +static int spf_include(char *spec, char *mask) +{ + stralloc sa = {0}; + int r; + + if (!stralloc_copys(&sa, spec)) return SPF_NOMEM; + r = spflookup(&sa); + alloc_free(sa.s); + + switch(r) { + case SPF_NONE: + hdr_unknown(); + r = SPF_UNKNOWN; + break; + case SPF_SYNTAX: + r = SPF_UNKNOWN; + break; + case SPF_NEUTRAL: + case SPF_SOFTFAIL: + case SPF_FAIL: + r = SPF_NONE; + break; + } + + return r; +} + +static int spf_a(char *spec, char *mask) +{ + stralloc sa = {0}; + ipalloc ia = {0}; + int ipmask = getipmask(mask, 1); + int r; + int j; + + if (ipmask < 0) return SPF_SYNTAX; + + if (!stralloc_copys(&sa, spec)) return SPF_NOMEM; + if (!stralloc_readyplus(&ia, 0)) return SPF_NOMEM; + + switch(dns_ip(&ia, &sa)) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: hdr_dns(); r = SPF_ERROR; break; + case DNS_HARD: r = SPF_NONE; break; + default: + r = SPF_NONE; + for(j = 0; j < ia.len; ++j) + if (matchip(&ia.ix[j].ip, ipmask, &ip)) { + r = SPF_OK; + break; + } + } + + alloc_free(sa.s); + alloc_free(ia.ix); + return r; +} + +static int spf_mx(char *spec, char *mask) +{ + stralloc sa = {0}; + ipalloc ia = {0}; + int ipmask = getipmask(mask, 1); + int random = now() + (getpid() << 16); + int r; + int j; + + if (ipmask < 0) return SPF_SYNTAX; + + if (!stralloc_copys(&sa, spec)) return SPF_NOMEM; + if (!stralloc_readyplus(&ia, 0)) return SPF_NOMEM; + + switch(dns_mxip(&ia, &sa, random)) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: hdr_dns(); r = SPF_ERROR; break; + case DNS_HARD: r = SPF_NONE; break; + default: + r = SPF_NONE; + for(j = 0; j < ia.len; ++j) + if (matchip(&ia.ix[j].ip, ipmask, &ip)) { + r = SPF_OK; + break; + } + } + + alloc_free(sa.s); + alloc_free(ia.ix); + return r; +} + +static int spf_ptr(char *spec, char *mask) +{ + strsalloc ssa = {0}; + ipalloc ia = {0}; + int len = str_len(spec); + int r; + int j, k; + int pos; + + /* we didn't find host with the matching ip before */ + if (sender_fqdn.len == 7 && str_equal(sender_fqdn.s, "unknown")) + return SPF_NONE; + + /* the hostname found will probably be the same as before */ + while (sender_fqdn.len) { + pos = sender_fqdn.len - len; + if (pos < 0) break; + if (pos > 0 && sender_fqdn.s[pos - 1] != '.') break; + if (case_diffb(sender_fqdn.s + pos, len, spec)) break; + + return SPF_OK; + } + + /* ok, either it's the first test or it's a very weird setup */ + + if (!stralloc_readyplus(&ssa, 0)) return SPF_NOMEM; + if (!stralloc_readyplus(&ia, 0)) return SPF_NOMEM; + + switch(dns_ptr(&ssa, &ip)) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: hdr_dns(); r = SPF_ERROR; break; + case DNS_HARD: r = SPF_NONE; break; + default: + r = SPF_NONE; + for(j = 0; j < ssa.len; ++j) { + switch(dns_ip(&ia, &ssa.sa[j])) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: hdr_dns(); r = SPF_ERROR; break; + case DNS_HARD: break; + default: + for(k = 0; k < ia.len; ++k) + if (matchip(&ia.ix[k].ip, 32, &ip)) { + if (!sender_fqdn.len) + if (!stralloc_copy(&sender_fqdn, &ssa.sa[j])) return SPF_NOMEM; + + pos = ssa.sa[j].len - len; + if (pos < 0) continue; + if (pos > 0 && ssa.sa[j].s[pos - 1] != '.') continue; + if (case_diffb(ssa.sa[j].s + pos, len, spec)) continue; + + stralloc_copy(&sender_fqdn, &ssa.sa[j]); + r = SPF_OK; + break; + } + } + + if (r == SPF_ERROR) break; + } + } + + for(j = 0;j < ssa.len;++j) + alloc_free(ssa.sa[j].s); + + alloc_free(ssa.sa); + alloc_free(ia.ix); + + if (!sender_fqdn.len) + if (!stralloc_copys(&sender_fqdn, "unknown")) return SPF_NOMEM; + + return r; +} + +static int spf_ip(char *spec, char *mask) +{ + struct ip_address net; + int ipmask = getipmask(mask, 0); + + if (ipmask < 0) return SPF_SYNTAX; + if (!ip_scan(spec, &net)) return SPF_SYNTAX; + + if (matchip(&net, ipmask, &ip)) return SPF_OK; + + return SPF_NONE; +} + +static int spf_exists(char *spec, char *mask) +{ + stralloc sa = {0}; + ipalloc ia = {0}; + int r; + + if (!stralloc_copys(&sa, spec)) return SPF_NOMEM; + if (!stralloc_readyplus(&ia, 0)) return SPF_NOMEM; + + switch(dns_ip(&ia, &sa)) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: hdr_dns(); r = SPF_ERROR; break; + case DNS_HARD: r = SPF_NONE; break; + default: r = SPF_OK; + } + + alloc_free(sa.s); + alloc_free(ia.ix); + return r; +} + +static struct mechanisms { + char *mechanism; + int (*func)(char *spec, char *mask); + unsigned int takes_spec : 1; + unsigned int takes_mask : 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_ip, 1,1,0,0,0 } +, { "ip6", 0, 1,1,0,0,SPF_NONE } +, { "exists", spf_exists, 1,0,1,0,0 } +, { "extension",0, 1,1,0,0,SPF_EXT } +, { 0, 0, 1,1,0,0,SPF_EXT } +}; + +static int spfmech(char *mechanism, char *spec, char *mask, char *domain) +{ + struct mechanisms *mech; + stralloc sa = {0}; + int r; + int pos; + + for(mech = mechanisms; mech->mechanism; mech++) + if (str_equal(mech->mechanism, mechanism)) break; + + if (mech->takes_spec && !spec && mech->filldomain) spec = domain; + if (!mech->takes_spec != !spec) return SPF_SYNTAX; + if (!mech->takes_mask && mask) return SPF_SYNTAX; + if (!mech->func) return mech->defresult; + + if (!stralloc_readyplus(&sa, 0)) return SPF_NOMEM; + if (mech->expands && spec != domain) { + if (!spfexpand(&sa, spec, 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); + stralloc_0(&sa); + spec = sa.s; + } + + r = mech->func(spec, mask); + + alloc_free(sa.s); + return r; +} + +static struct default_aliases { + char *alias; + int defret; +} default_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 } +}; + +static int spflookup(stralloc *domain) +{ + stralloc spf = {0}; + stralloc sa = {0}; + struct default_aliases *da; + int main = !recursion; + int local_pos = -1; + int r, q; + int begin, pos; + int i; + int prefix; + int done; + int guessing = 0; + char *p; + + if (!stralloc_readyplus(&spf, 0)) return SPF_NOMEM; + if (!stralloc_readyplus(&sa, 0)) return SPF_NOMEM; + + /* fallthrough result */ + if (main) hdr_none(); + +redirect: + if (++recursion > 20) { + alloc_free(spf.s); + alloc_free(sa.s); + hdr_unknown_msg("Maximum nesting level exceeded, possible loop"); + return SPF_SYNTAX; + } + + if (!stralloc_0(domain)) return SPF_NOMEM; + if (!stralloc_copy(&expdomain, domain)) return SPF_NOMEM; + + r = spfget(&spf, domain); + if (r == SPF_NONE) { + if (!main) { alloc_free(spf.s); return r; } + + if (spfguess.len) { + /* try to guess */ + guessing = 1; + if (!stralloc_copys(&spf, spfguess.s)) return SPF_NOMEM; + if (!stralloc_append(&spf, " ")) return SPF_NOMEM; + } else + spf.len = 0; + + /* append local rulest */ + if (spflocal.len) { + local_pos = spf.len; + if (!stralloc_cats(&spf, spflocal.s)) return SPF_NOMEM; + } + if (!stralloc_0(&spf)) return SPF_NOMEM; + + expdomain.len = 0; + } else if (r == SPF_OK) { + if (!stralloc_0(&spf)) return SPF_NOMEM; + if (main) hdr_neutral(); + r = SPF_NEUTRAL; + + /* try to add local rules before fail all mechs */ + if (main && spflocal.len) { + pos = 0; + p = (char *) 0; + while(pos < spf.len) { + NXTOK(begin, pos, &spf); + if (!spf.s[begin]) continue; + + if (p && spf.s[begin] != *p) p = (char *) 0; + if (!p && (spf.s[begin] == '-' || spf.s[begin] == '~' || + spf.s[begin] == '?')) p = &spf.s[begin]; + + if (p && p > spf.s && str_equal(spf.s + begin + 1, "all")) { + /* ok, we can insert the local rules at p */ + local_pos = p - spf.s; + + stralloc_readyplus(&spf, spflocal.len); + p = spf.s + local_pos; + byte_copyr(p + spflocal.len, spf.len - local_pos, p); + byte_copy(p, spflocal.len, spflocal.s); + spf.len += spflocal.len; + + pos += spflocal.len; + break; + } + } + + if (pos >= spf.len) pos = spf.len - 1; + for(i = 0; i < pos; i++) + if (!spf.s[i]) spf.s[i] = ' '; + } + } else { + alloc_free(spf.s); + return r; + } + + pos = 0; + done = 0; + while(pos < spf.len) { + NXTOK(begin, pos, &spf); + if (!spf.s[begin]) continue; + + /* in local ruleset? */ + if (!done && local_pos >= 0 && begin >= local_pos) { + if (begin < (local_pos + spflocal.len)) + expdomain.len = 0; + else + if (!stralloc_copy(&expdomain, domain)) + return SPF_NOMEM; + } + + for (p = spf.s + begin;*p;++p) + if (*p == ':' || *p == '/' || *p == '=') break; + + if (*p == '=') { + *p++ = 0; + + /* modifiers are simply handled here */ + if (str_equal(spf.s + begin, "redirect")) { + if (done) continue; + + if (!spfexpand(&sa, p, domain->s)) return SPF_NOMEM; + stralloc_copy(domain, &sa); + + hdr_unknown(); + r = SPF_UNKNOWN; + + goto redirect; + } else if (str_equal(spf.s + begin, "default")) { + if (done) continue; + + for(da = default_aliases; da->alias; ++da) + if (str_equal(da->alias, p)) break; + + r = da->defret; + } else if (str_equal(spf.s + begin, "exp")) { + strsalloc ssa = {0}; + + if (!main) continue; + + if (!stralloc_copys(&sa, p)) return SPF_NOMEM; + switch(dns_txt(&ssa, &sa)) { + case DNS_MEM: return SPF_NOMEM; + case DNS_SOFT: continue; /* FIXME... */ + case DNS_HARD: continue; + } + + explanation.len = 0; + for(i = 0; i < ssa.len; i++) { + if (!stralloc_cat(&explanation, &ssa.sa[i])) return SPF_NOMEM; + if (i < (ssa.len - 1)) + if (!stralloc_append(&explanation, "\n")) return SPF_NOMEM; + + alloc_free(ssa.sa[i].s); + } + if (!stralloc_0(&explanation)) return SPF_NOMEM; + } /* and unknown modifiers are ignored */ + } else if (!done) { + if (!stralloc_copys(&sa, spf.s + begin)) return SPF_NOMEM; + if (!stralloc_0(&sa)) return SPF_NOMEM; + + switch(spf.s[begin]) { + case '-': begin++; prefix = SPF_FAIL; break; + case '~': begin++; prefix = SPF_SOFTFAIL; break; + case '+': begin++; prefix = SPF_OK; break; + case '?': begin++; prefix = SPF_NEUTRAL; break; + default: prefix = SPF_OK; + } + + if (*p == '/') { + *p++ = 0; + q = spfmech(spf.s + begin, 0, p, domain->s); + } else { + if (*p) *p++ = 0; + i = str_chr(p, '/'); + if (p[i] == '/') { + p[i++] = 0; + q = spfmech(spf.s + begin, p, p + i, domain->s); + } else if (i > 0) + q = spfmech(spf.s + begin, p, 0, domain->s); + else + q = spfmech(spf.s + begin, 0, 0, domain->s); + } + + if (q == SPF_OK) q = prefix; + + switch(q) { + case SPF_OK: hdr_pass(); break; + case SPF_NEUTRAL: hdr_neutral(); break; + case SPF_SYNTAX: hdr_syntax(); break; + case SPF_SOFTFAIL: hdr_softfail(); break; + case SPF_FAIL: hdr_fail(); break; + case SPF_EXT: hdr_ext(sa.s); break; + case SPF_ERROR: + if (!guessing) + break; + if (local_pos >= 0 && begin >= local_pos) + break; + hdr_none(); + 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 && !stralloc_copy(&expdomain, domain)) return SPF_NOMEM; + + alloc_free(spf.s); + alloc_free(sa.s); + return r; +} + +int spfcheck() +{ + stralloc domain = {0}; + int pos; + int r; + + pos = byte_rchr(addr.s, addr.len, '@') + 1; + if (pos < addr.len) { + if (!stralloc_copys(&domain, addr.s + pos)) return SPF_NOMEM; + } else { + pos = str_rchr(helohost.s, '@'); + if (helohost.s[pos]) { + if (!stralloc_copys(&domain, helohost.s + pos + 1)) return SPF_NOMEM; + } else + if (!stralloc_copys(&domain, helohost.s)) return SPF_NOMEM; + } + if (!stralloc_copys(&explanation, spfexp.s)) return SPF_NOMEM; + if (!stralloc_0(&explanation)) return SPF_NOMEM; + recursion = 0; + + if (!remoteip || !ip_scan(remoteip, &ip)) { + hdr_unknown_msg("No IP address in conversation"); + return SPF_UNKNOWN; + } + + if (!stralloc_readyplus(&expdomain, 0)) return SPF_NOMEM; + if (!stralloc_readyplus(&errormsg, 0)) return SPF_NOMEM; + expdomain.len = 0; + errormsg.len = 0; + sender_fqdn.len = 0; + received = (char *) 0; + + if ((ip.d[0] == 127 && ip.d[1] == 0 && ip.d[2] == 0 && ip.d[3] == 1) || ipme_is(&ip)) + { hdr_pass(); r = SPF_OK; } + else + r = spflookup(&domain); + + if (r < 0) r = SPF_UNKNOWN; + + alloc_free(domain.s); + return r; +} + +int spfexplanation(sa) +stralloc *sa; +{ + return spfexpand(sa, explanation.s, expdomain.s); +} + +int spfinfo(sa) +stralloc *sa; +{ + stralloc tmp = {0}; + if (!stralloc_copys(&tmp, received)) return 0; + if (!stralloc_0(&tmp)) return 0; + if (!spfexpand(sa, tmp.s, expdomain.s)) return 0; + alloc_free(tmp.s); + return 1; +} diff --git a/spf.h b/spf.h new file mode 100644 index 0000000..e1cfd1b --- /dev/null +++ b/spf.h @@ -0,0 +1,20 @@ +#ifndef SPF_H +#define SPF_H + +#define SPF_OK 0 +#define SPF_NONE 1 +#define SPF_UNKNOWN 2 +#define SPF_NEUTRAL 3 +#define SPF_SOFTFAIL 4 +#define SPF_FAIL 5 +#define SPF_ERROR 6 +#define SPF_NOMEM 7 + +#define SPF_DEFEXP "See http://spf.pobox.com/" \ + "why.html?sender=%{S}&ip=%{I}&receiver=%{xR}" + +extern int spfcheck(); +extern int spfexplanation(); +extern int spfinfo(); + +#endif diff --git a/spfquery.c b/spfquery.c new file mode 100644 index 0000000..b5dfd5e --- /dev/null +++ b/spfquery.c @@ -0,0 +1,84 @@ +#include "substdio.h" +#include "subfd.h" +#include "stralloc.h" +#include "alloc.h" +#include "spf.h" +#include "exit.h" + +void die(e,s) int e; char *s; { substdio_putsflush(subfderr,s); _exit(e); } +void die_usage() { die(100,"fatal: invalid usage\nusage: spfquery [] []\n"); } +void die_nomem() { die(111,"fatal: out of memory\n"); } + +stralloc addr = {0}; +stralloc helohost = {0}; +char *remoteip; +char *local; + +stralloc spflocal = {0}; +stralloc spfguess = {0}; +stralloc spfexp = {0}; + +void main(argc,argv) +int argc; +char **argv; +{ + stralloc sa = {0}; + int r; + + if (argc < 4) die_usage(); + + remoteip = (char *)strdup(argv[1]); + local = "localhost"; + + if (!stralloc_copys(&helohost, argv[2])) die_nomem(); + if (!stralloc_0(&helohost)) die_nomem(); + + if (!stralloc_copys(&addr, argv[3])) die_nomem(); + if (!stralloc_0(&addr)) die_nomem(); + + if (argc > 4) { + if (!stralloc_copys(&spflocal, argv[4])) die_nomem(); + if (spflocal.len && !stralloc_0(&spflocal)) die_nomem(); + } + + if (argc > 5) { + if (!stralloc_copys(&spfguess, argv[5])) die_nomem(); + if (spfguess.len && !stralloc_0(&spfguess)) die_nomem(); + } + + if (argc > 6) { + if (!stralloc_copys(&spfexp, argv[6])) die_nomem(); + } else + if (!stralloc_copys(&spfexp, SPF_DEFEXP)) die_nomem(); + if (spfexp.len && !stralloc_0(&spfexp)) die_nomem(); + + dns_init(0); + r = spfcheck(); + if (r == SPF_NOMEM) die_nomem(); + + substdio_puts(subfdout,"result="); + switch(r) { + case SPF_OK: substdio_puts(subfdout,"pass"); break; + case SPF_NONE: substdio_puts(subfdout,"none"); break; + case SPF_UNKNOWN: substdio_puts(subfdout,"unknown"); break; + case SPF_NEUTRAL: substdio_puts(subfdout,"neutral"); break; + case SPF_SOFTFAIL: substdio_puts(subfdout,"softfail"); break; + case SPF_FAIL: substdio_puts(subfdout,"fail"); break; + case SPF_ERROR: substdio_puts(subfdout,"error"); break; + } + + if (r == SPF_FAIL) { + substdio_puts(subfdout,": "); + if (!spfexplanation(&sa)) die_nomem(); + substdio_put(subfdout,sa.s,sa.len); + } + + substdio_putsflush(subfdout,"\n"); + + substdio_puts(subfdout,"Received-SPF: "); + if (!spfinfo(&sa)) die_nomem(); + substdio_put(subfdout,sa.s,sa.len); + substdio_putsflush(subfdout,"\n"); + + _exit(0); +} diff --git a/str.h b/str.h index e00773c..00307f4 100644 --- a/str.h +++ b/str.h @@ -2,6 +2,7 @@ #define STR_H extern unsigned int str_copy(); +extern unsigned int str_copyb(); extern int str_diff(); extern int str_diffn(); extern unsigned int str_len(); diff --git a/str_cpyb.c b/str_cpyb.c new file mode 100644 index 0000000..9afcfc4 --- /dev/null +++ b/str_cpyb.c @@ -0,0 +1,18 @@ +#include "str.h" + +unsigned int str_copyb(s,t,max) +register char *s; +register char *t; +unsigned int max; +{ + register int len; + + len = 0; + while (max-- > 0) { + if (!(*s = *t)) return len; ++s; ++t; ++len; + if (!(*s = *t)) return len; ++s; ++t; ++len; + if (!(*s = *t)) return len; ++s; ++t; ++len; + if (!(*s = *t)) return len; ++s; ++t; ++len; + } + return len; +} diff --git a/strsalloc.c b/strsalloc.c new file mode 100644 index 0000000..482e489 --- /dev/null +++ b/strsalloc.c @@ -0,0 +1,7 @@ +#include "alloc.h" +#include "gen_allocdefs.h" +#include "stralloc.h" +#include "strsalloc.h" + +GEN_ALLOC_readyplus(strsalloc,stralloc,sa,len,a,i,n,x,10,strsalloc_readyplus) +GEN_ALLOC_append(strsalloc,stralloc,sa,len,a,i,n,x,10,strsalloc_readyplus,strsalloc_append) diff --git a/strsalloc.h b/strsalloc.h new file mode 100644 index 0000000..c42804e --- /dev/null +++ b/strsalloc.h @@ -0,0 +1,12 @@ +#ifndef STRSALLOC_H +#define STRSALLOC_H + +#include "stralloc.h" + +#include "gen_alloc.h" + +GEN_ALLOC_typedef(strsalloc,stralloc,sa,len,a) +extern int strsalloc_readyplus(); +extern int strsalloc_append(); + +#endif diff --git a/tcp-env.c b/tcp-env.c index feb85cc..db64808 100644 --- a/tcp-env.c +++ b/tcp-env.c @@ -10,6 +10,7 @@ #include "scan.h" #include "subgetopt.h" #include "ip.h" +#include "strsalloc.h" #include "dns.h" #include "byte.h" #include "remoteinfo.h" @@ -34,6 +35,7 @@ void main(argc,argv) int argc; char *argv[]; { + strsalloc ssa = {0}; int dummy; char *proto; int opt; @@ -74,12 +76,13 @@ char *argv[]; temp[ip_fmt(temp,&iplocal)] = 0; if (!env_put2("TCPLOCALIP",temp)) die(); - switch(dns_ptr(&localname,&iplocal)) + switch(dns_ptr(&ssa,&iplocal)) { case DNS_MEM: die(); case DNS_SOFT: if (!stralloc_copys(&localname,"softdnserror")) die(); case 0: + if (!stralloc_copy(&localname,&ssa.sa[0])) die(); if (!stralloc_0(&localname)) die(); case_lowers(localname.s); if (!env_put2("TCPLOCALHOST",localname.s)) die(); @@ -99,12 +102,13 @@ char *argv[]; temp[ip_fmt(temp,&ipremote)] = 0; if (!env_put2("TCPREMOTEIP",temp)) die(); - switch(dns_ptr(&remotename,&ipremote)) + switch(dns_ptr(&ssa,&ipremote)) { case DNS_MEM: die(); case DNS_SOFT: if (!stralloc_copys(&remotename,"softdnserror")) die(); case 0: + if (!stralloc_copy(&remotename,&ssa.sa[0])) die(); if (!stralloc_0(&remotename)) die(); case_lowers(remotename.s); if (!env_put2("TCPREMOTEHOST",remotename.s)) die();