1
0
Fork 0

Makes qmail-smtpd with AUTH support plus SPF and DKIM.

This is the qmail that we're currently running at antartida.xyz.  We
took the notqmail repository and essentially applied patches for AUTH,
SPF and DKIM.

The DKIM patch does not come from the notqmail repository.  We took it
from the excellent notes at

  https://www.memoryhole.net/qmail/

We also applied the any-to-cname patch.  But we did not take the
oversize-dns patch.  Any mistakes here are mine---I had to merge it as
best we could and resolve various conflicts.

This is it. -- MAILER DAEMON :)

(*) Commit message from the notqmail branch

Remove FILES.auth, add new files to FILES.

Realphabetize Makefile targets.  Remove trailing whitespace variously.

Remove duplication declaration of auth in qmail-smtpd.c

Modification to Erwin Hoffmann's SMTP authentication for qmail-smtpd,
qmail-remote.

Apply Jana Saout's qmail-spf-rc5.patch

From https://www.saout.de/misc/spf/

This is an SPF implementation for qmail.

SPF is something like a DNS based "reverse MX" system to designate
permitted senders for mails depending on the domain name. The goal
is to disallow sender address forgery.

The patch is entirely written using the qmail string and DNS
functions, and thus completely integrated into qmail without
external dependencies.

It can check incoming mails inside the SMTP daemon, add Received-SPF
lines and optionally block undesired transfers. The check is
performed at the envelope level.
This commit is contained in:
MAILER-DAEMON 2024-11-23 20:13:12 -03:00 committed by MAILER DAEMON
parent 36d7678ec9
commit 69cea49034
35 changed files with 1952 additions and 227 deletions

18
FILES
View file

@ -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

View file

@ -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

View file

@ -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

197
Makefile
View file

@ -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: \

View file

@ -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

10
TARGETS
View file

@ -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

View file

@ -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;

2
byte.h
View file

@ -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();

11
byte_cspn.c Normal file
View file

@ -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;
}

17
byte_rcspn.c Normal file
View file

@ -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;
}

132
dns.c
View file

@ -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;
}

1
dns.h
View file

@ -10,5 +10,6 @@ int dns_cname();
int dns_mxip();
int dns_ip();
int dns_ptr();
int dns_txt();
#endif

13
dnsfq.c
View file

@ -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);
}

View file

@ -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);
}

32
dnstxt.c Normal file
View file

@ -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);
}

View file

@ -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

4
hier.c
View file

@ -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);

View file

@ -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

10
md5.h
View file

@ -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_

34
md5c.c
View file

@ -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;
}

View file

@ -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

308
qmail-remote-dkim Executable file
View file

@ -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)
<dkimsign>
<!-- per default sign all mails using dkim -->
<global algorithm="rsa-sha256" domain="/var/qmail/control/me" keyfile="/var/qmail/control/dkim/global.key" method="simple" selector="beta">
<types id="dkim" />
</global>
<!-- use dkim + domainkey for example.com -->
<example.com selector="beta2">
<types id="dkim" />
<types id="domainkey" method="nofws" />
</example.com>
<!-- no signing for example2.com -->
<example2.com />
</dkimsign>
=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 (<STDIN>)
{
$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 = <FH>)
{
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(/ /, <FH>))[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;
}

View file

@ -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

View file

@ -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();

View file

@ -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;

View file

@ -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),

View file

@ -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();

878
spf.c Normal file
View file

@ -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;
}

20
spf.h Normal file
View file

@ -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

84
spfquery.c Normal file
View file

@ -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 <sender-ip> <sender-helo/ehlo> <envelope-from> [<local rules>] [<best guess rules>]\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);
}

1
str.h
View file

@ -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();

18
str_cpyb.c Normal file
View file

@ -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;
}

7
strsalloc.c Normal file
View file

@ -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)

12
strsalloc.h Normal file
View file

@ -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

View file

@ -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();