summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2013-02-04 02:32:40 +0100
committermanuel <manuel@mausz.at>2013-02-04 02:32:40 +0100
commit8514473287c9594137c6fbc39f5619672ebc2430 (patch)
treea5b965d8c7b60dee396bf8ebe25dd3eddfaa6753
parent35ddb916045abafaa4ae2c778b9383059fa06726 (diff)
downloadqmail-8514473287c9594137c6fbc39f5619672ebc2430.tar.gz
qmail-8514473287c9594137c6fbc39f5619672ebc2430.tar.bz2
qmail-8514473287c9594137c6fbc39f5619672ebc2430.zip
[PATCH] qregex-starttls-2way-auth-20060423-mm
-rw-r--r--Makefile67
-rw-r--r--Makefile-cert.mk21
-rw-r--r--README.auth66
-rw-r--r--README.qregex203
-rw-r--r--README.remote-auth31
-rw-r--r--README.starttls103
-rw-r--r--TARGETS6
-rw-r--r--base64.c122
-rw-r--r--base64.h7
-rw-r--r--case_startb.c21
-rw-r--r--conf-cc2
-rw-r--r--dns.c15
-rw-r--r--hier.c7
-rw-r--r--install-big.c4
-rw-r--r--ipalloc.h8
-rw-r--r--qmail-control.918
-rw-r--r--qmail-remote.843
-rw-r--r--qmail-remote.c444
-rw-r--r--qmail-showctl.c10
-rw-r--r--qmail-smtpd.8133
-rw-r--r--qmail-smtpd.c758
-rw-r--r--qregex.c57
-rw-r--r--qregex.h5
-rw-r--r--ssl_timeoutio.c95
-rw-r--r--ssl_timeoutio.h21
-rw-r--r--tls.c25
-rw-r--r--tls.h16
-rw-r--r--update_tmprsadh.sh25
28 files changed, 2286 insertions, 47 deletions
diff --git a/Makefile b/Makefile
index 9230887..15a0e54 100644
--- a/Makefile
+++ b/Makefile
@@ -136,6 +136,10 @@ auto_usera.o: \
136compile auto_usera.c 136compile auto_usera.c
137 ./compile auto_usera.c 137 ./compile auto_usera.c
138 138
139base64.o: \
140compile base64.c base64.h stralloc.h substdio.h str.h
141 ./compile base64.c
142
139binm1: \ 143binm1: \
140binm1.sh conf-qmail 144binm1.sh conf-qmail
141 cat binm1.sh \ 145 cat binm1.sh \
@@ -808,7 +812,7 @@ dnsptr dnsip dnsmxip dnsfq hostname ipmeprint qreceipt qsmhook qbiff \
808forward preline condredirect bouncesaying except maildirmake \ 812forward preline condredirect bouncesaying except maildirmake \
809maildir2mbox maildirwatch qail elq pinq idedit install-big install \ 813maildir2mbox maildirwatch qail elq pinq idedit install-big install \
810instcheck home home+df proc proc+df binm1 binm1+df binm2 binm2+df \ 814instcheck home home+df proc proc+df binm1 binm1+df binm2 binm2+df \
811binm3 binm3+df 815binm3 binm3+df update_tmprsadh
812 816
813load: \ 817load: \
814make-load warn-auto.sh systype 818make-load warn-auto.sh systype
@@ -1441,12 +1445,13 @@ qmail-remote: \
1441load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \ 1445load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \
1442timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \ 1446timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \
1443ndelay.a case.a sig.a open.a lock.a seek.a getln.a stralloc.a alloc.a \ 1447ndelay.a case.a sig.a open.a lock.a seek.a getln.a stralloc.a alloc.a \
1444substdio.a error.a str.a fs.a auto_qmail.o dns.lib socket.lib 1448substdio.a error.a str.a fs.a auto_qmail.o base64.o dns.lib socket.lib
1445 ./load qmail-remote control.o constmap.o timeoutread.o \ 1449 ./load qmail-remote control.o constmap.o timeoutread.o \
1446 timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \ 1450 timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \
1451 tls.o ssl_timeoutio.o -L/usr/local/ssl/lib -lssl -lcrypto \
1447 ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \ 1452 ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \
1448 lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \ 1453 lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \
1449 str.a fs.a auto_qmail.o `cat dns.lib` `cat socket.lib` 1454 str.a fs.a auto_qmail.o base64.o `cat dns.lib` `cat socket.lib`
1450 1455
1451qmail-remote.0: \ 1456qmail-remote.0: \
1452qmail-remote.8 1457qmail-remote.8
@@ -1532,16 +1537,17 @@ auto_split.h
1532 ./compile qmail-showctl.c 1537 ./compile qmail-showctl.c
1533 1538
1534qmail-smtpd: \ 1539qmail-smtpd: \
1535load qmail-smtpd.o rcpthosts.o commands.o timeoutread.o \ 1540load qmail-smtpd.o rcpthosts.o qregex.o commands.o timeoutread.o \
1536timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \ 1541timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \
1537date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \ 1542date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \
1538open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \ 1543open.a sig.a case.a env.a stralloc.a alloc.a strerr.a substdio.a error.a str.a \
1539fs.a auto_qmail.o socket.lib 1544fs.a auto_qmail.o base64.o socket.lib
1540 ./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \ 1545 ./load qmail-smtpd qregex.o rcpthosts.o commands.o timeoutread.o \
1541 timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \ 1546 timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \
1547 tls.o ssl_timeoutio.o ndelay.a -L/usr/local/ssl/lib -lssl -lcrypto \
1542 received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ 1548 received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \
1543 datetime.a getln.a open.a sig.a case.a env.a stralloc.a \ 1549 datetime.a getln.a open.a sig.a case.a env.a stralloc.a \
1544 alloc.a substdio.a error.a str.a fs.a auto_qmail.o `cat \ 1550 alloc.a strerr.a substdio.a error.a str.a fs.a auto_qmail.o base64.o `cat \
1545 socket.lib` 1551 socket.lib`
1546 1552
1547qmail-smtpd.0: \ 1553qmail-smtpd.0: \
@@ -1553,7 +1559,7 @@ compile qmail-smtpd.c sig.h readwrite.h stralloc.h gen_alloc.h \
1553substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \ 1559substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \
1554error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \ 1560error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \
1555substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \ 1561substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \
1556exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h 1562exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h base64.h
1557 ./compile qmail-smtpd.c 1563 ./compile qmail-smtpd.c
1558 1564
1559qmail-start: \ 1565qmail-start: \
@@ -1681,6 +1687,10 @@ compile rcpthosts.c cdb.h uint32.h byte.h open.h error.h control.h \
1681constmap.h stralloc.h gen_alloc.h rcpthosts.h 1687constmap.h stralloc.h gen_alloc.h rcpthosts.h
1682 ./compile rcpthosts.c 1688 ./compile rcpthosts.c
1683 1689
1690qregex.o: \
1691compile qregex.c qregex.h
1692 ./compile qregex.c
1693
1684readsubdir.o: \ 1694readsubdir.o: \
1685compile readsubdir.c readsubdir.h direntry.h fmt.h scan.h str.h \ 1695compile readsubdir.c readsubdir.h direntry.h fmt.h scan.h str.h \
1686auto_split.h 1696auto_split.h
@@ -1827,7 +1837,8 @@ date822fmt.h date822fmt.c dns.h dns.c trylsock.c tryrsolv.c ip.h ip.c \
1827ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h ndelay.c \ 1837ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h ndelay.c \
1828ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c prot.h \ 1838ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c prot.h \
1829prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c trysalen.c \ 1839prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c trysalen.c \
1830maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c 1840maildir.5 maildir.h maildir.c tcp-environ.5 constmap.h constmap.c \
1841update_tmprsadh
1831 shar -m `cat FILES` > shar 1842 shar -m `cat FILES` > shar
1832 chmod 400 shar 1843 chmod 400 shar
1833 1844
@@ -2108,6 +2119,19 @@ timeoutwrite.o: \
2108compile timeoutwrite.c timeoutwrite.h select.h error.h readwrite.h 2119compile timeoutwrite.c timeoutwrite.h select.h error.h readwrite.h
2109 ./compile timeoutwrite.c 2120 ./compile timeoutwrite.c
2110 2121
2122qmail-smtpd: tls.o ssl_timeoutio.o ndelay.a
2123qmail-remote: tls.o ssl_timeoutio.o
2124qmail-smtpd.o: tls.h ssl_timeoutio.h
2125qmail-remote.o: tls.h ssl_timeoutio.h
2126
2127tls.o: \
2128compile tls.c exit.h error.h
2129 ./compile tls.c
2130
2131ssl_timeoutio.o: \
2132compile ssl_timeoutio.c ssl_timeoutio.h select.h error.h ndelay.h
2133 ./compile ssl_timeoutio.c
2134
2111token822.o: \ 2135token822.o: \
2112compile token822.c stralloc.h gen_alloc.h alloc.h str.h token822.h \ 2136compile token822.c stralloc.h gen_alloc.h alloc.h str.h token822.h \
2113gen_alloc.h gen_allocdefs.h 2137gen_alloc.h gen_allocdefs.h
@@ -2139,3 +2163,26 @@ compile wait_nohang.c haswaitp.h
2139wait_pid.o: \ 2163wait_pid.o: \
2140compile wait_pid.c error.h haswaitp.h 2164compile wait_pid.c error.h haswaitp.h
2141 ./compile wait_pid.c 2165 ./compile wait_pid.c
2166
2167cert cert-req: \
2168Makefile-cert
2169 @$(MAKE) -sf $< $@
2170
2171Makefile-cert: \
2172conf-qmail conf-users conf-groups Makefile-cert.mk
2173 @cat Makefile-cert.mk \
2174 | sed s}QMAIL}"`head -1 conf-qmail`"}g \
2175 > $@
2176
2177update_tmprsadh: \
2178conf-qmail conf-users conf-groups update_tmprsadh.sh
2179 @cat update_tmprsadh.sh\
2180 | sed s}UGQMAILD}"`head -2 conf-users|tail -1`:`head -1 conf-groups`"}g \
2181 | sed s}QMAIL}"`head -1 conf-qmail`"}g \
2182 > $@
2183 chmod 755 update_tmprsadh
2184
2185tmprsadh: \
2186update_tmprsadh
2187 echo "Creating new temporary RSA and DH parameters"
2188 ./update_tmprsadh
diff --git a/Makefile-cert.mk b/Makefile-cert.mk
new file mode 100644
index 0000000..d869999
--- /dev/null
+++ b/Makefile-cert.mk
@@ -0,0 +1,21 @@
1cert-req: req.pem
2cert cert-req: QMAIL/control/clientcert.pem
3 @:
4
5QMAIL/control/clientcert.pem: QMAIL/control/servercert.pem
6 ln -s $< $@
7
8QMAIL/control/servercert.pem:
9 PATH=$$PATH:/usr/local/ssl/bin \
10 openssl req -new -x509 -nodes -days 366 -out $@ -keyout $@
11 chmod 640 $@
12 chown `head -2 conf-users | tail -1`:`head -1 conf-groups` $@
13
14req.pem:
15 PATH=$$PATH:/usr/local/ssl/bin openssl req \
16 -new -nodes -out $@ -keyout QMAIL/control/servercert.pem
17 chmod 640 QMAIL/control/servercert.pem
18 chown `head -2 conf-users | tail -1`:`head -1 conf-groups` QMAIL/control/servercert.pem
19 @echo
20 @echo "Send req.pem to your CA to obtain signed_req.pem, and do:"
21 @echo "cat signed_req.pem >> QMAIL/control/servercert.pem"
diff --git a/README.auth b/README.auth
new file mode 100644
index 0000000..435fe3b
--- /dev/null
+++ b/README.auth
@@ -0,0 +1,66 @@
1README qmail-smtpd SMTP Authentication
2======================================
3
4
5History:
6--------
7
8This patch is based on Krzysztof Dabrowski's qmail-smtpd-auth-0.31 patch
9which itself uses "Mrs. Brisby's" initial code.
10Version 0.41 of this patch fixes the "CAPS-LOCK" typo announcing
11'CRAM_MD5' instead of 'CRAM-MD5' (german keyboard) - tx to Mike Garrison.
12Version 0.42 fixes the '421 unable to read controls (#4.3.0)' problem
13(can't read control/morercpthosts.cdb) because FD 3 was already closed - tx Richard Lyons.
14Version 0.43 fixes the ba64decode() failure in case CRAM_MD5 is not enabled - tx Vladimir Zidar.
15Version 0.51 includes the evaluation of the 'Auth' and the 'Size' parameter in the 'Mail From:' command.
16Version 0.52 uses DJB functions to copy FDs.
17Version 0.56 corrects some minor mistakes displaying the 'Auth' userid.
18Version 0.57 uses keyword "ESMTPA" in Received header in case of authentication to comply with RFC 3848.
19
20
21Scope:
22------
23
24This patch supports RFC 2554 "SMTP Service Extension for Authentication" for qmail-smtpd.
25Additionally, RFC 1870 is honoured ("SMTP Service Extension for Message Size Declaration").
26For more technical details see: http://www.fehcom.de/qmail/docu/smtpauth.html.
27
28
29Installation:
30-------------
31
32* Untar the source in the qmail-1.03 home direcotry.
33* Run ./install_auth.
34* Modify the compile time option "#define CRAM_MD5" to your needs.
35* Re-make qmail.
36
37
38Setup:
39------
40
41In order to use SMTP Authentication you have to use a 'Pluggable Authentication Module'
42PAM to be called by qmail-smtpd; typically
43
44 /var/qmail/bin/qmail-smtpd /bin/checkpassword true 2>&1
45
46Since qmail-smtpd does not run as root, checkpassword has to be made sticky.
47There is no need to include additionally the hostname in the call.
48In order to compute the CRAM-MD5 challenge, qmail-smtpd uses the 'tcplocalhost' information.
49
50
51Changes wrt. Krysztof Dabrowski's patch:
52----------------------------------------
53
54* Avoid the 'hostname' in the call of the PAM.
55* Confirm to Dan Bernstein's checkpassword interface even for CRAM-MD5.
56* Doesn't close FD 2; thus not inhibiting logging to STDERR.
57* Fixed bugs in base64.c.
58* Modified unconditional close of FD 3 in order to sustain reading of 'control/morecpthosts.cdb'.
59* Evaluation of the (informational) Mail From: < > Auth=username.
60* Additional support for the advertised "Size" via 'Mail From: <return-path> SIZE=123456780' (RFC 1870).
61* RFC 3848 conformance for Received header in case of SMTP Auth.
62
63
64Erwin Hoffmann - Cologne 2005-01-23 (www.fehcom.de)
65
66
diff --git a/README.qregex b/README.qregex
new file mode 100644
index 0000000..60c1aeb
--- /dev/null
+++ b/README.qregex
@@ -0,0 +1,203 @@
1QREGEX (v2) 20060423 - README April 23, 2006
2A Regular Expression matching patch for qmail 1.03 and netqmail
3
4
5OVERVIEW:
6
7qregex adds the ability to match address evelopes via Regular Expressions (REs)
8in the qmail-smtpd process. It has the abiltiy to match `helo/ehlo` (host name),
9`mail from` (envelope sender), and `rcpt to` (envelope recipient) commands.
10It follows all the base rules that are set out with qmail (ie using control
11files) so it makes for easy integretion into an existing setup (see the
12install instructions for more info). The v2 is specified because qregex was
13re-written to better conform to the security guarantee set forth by the author
14of qmail. The original version used stdio.h and stdlib.h for reading the
15control files whereas v2 now uses all stralloc functions which are much more
16regulated against buffer overruns and the like.
17See: http://cr.yp.to/qmail/guarantee.html
18
19
20FEATURES:
21
22Features of qregex include:
23
241. Performs pattern matching on envelope senders and envelope
25 recipients against REs in the badmailfrom and badmailto control
26 files. Two additional control files, badmailfromnorelay and
27 badmailtonorelay, are used for pattern matching when the
28 RELAYCLIENT environment variable is not set.
29
302. Performs pattern matching on the helo/ehlo host name. Setting the
31 NOBADHELO environment variable prevents the host name from being
32 compared to the patterns in the badhelo control file.
33
343. Matches to patterns are logged. Setting the LOGREGEX environment
35 variable causes the matched regex pattern to be included in the log.
36
374. Matching is case insensitive.
38
395. qregex ignores empty envelope senders. An empty envelope sender is not
40 compared to the patterns in the badmailfrom and badmailfromnorelay
41 control files and is always accepted.
42
43
44PLATFORMS:
45
46qregex has been built and tested on the following platforms. I'm sure it won't
47have any problems on any platform that qmail will run on (providing they have
48a regex interface) but if you run into problems let me know.
49
50 - OpenBSD 3.x
51 - FreeBSD 4.x, 5.x
52 - Mandrake Linux 9.x
53 - SuSE Linux 8.x
54
55
56
57INSTALLATION INSTRUCTIONS:
58
59Installation is very simple, there is only one requirement. You need to use the
60GNU version of the patch utility (http://www.gnu.org/software/patch/patch.html).
61(For Solaris 8 users it is installed as 'gpatch')
62
63- If this is a new setup.
64Unpack the qmail archive, cd into the qmail-1.03 directory and run
65"patch < /path/to/qregex-<version>.patch". Follow the instructions as per the
66included qmail INSTALL file. Once you are done come back to this file and read
67the section on the control files.
68
69If you are using netqmail, then unpack the netqmail archive. Run the collate.sh
70script and cd into the resulting netqmail-<version> directory. From there, run
71"patch < /path/to/qregex-<version>.patch". Complete the netqmail installation
72normally. Once you are done, come back to this file and read the section on the
73control files.
74
75- If this is an existing setup.
76FIRST: create your control files (see below).
77cd into your existing qmail or netqmail source directory. Run
78"patch < /path/to/qregex-<version>.patch" then "make qmail-smtpd". Now run
79./qmail-smtpd and test your new rules to make sure they work as expected.
80
81Install the new binary by cd'ing to /var/qmail/bin and as root (in one command)
82copy the existing binary to 'qmail-smtpd.old' and copy the new binary from the
83source directory to 'qmail-smtpd'.
84(ex. cp qmail-smtpd qmail-smtpd.old && cp ~/qmail-1.03/qmail-smtpd qmail-smtpd)
85
86You can also optionally just run "make setup check" as it will install the
87updated documentation and man pages provided with this patch. Stopping qmail
88before doing the "make setup check" is always a good idea.
89
90
91LOGGING:
92
93qregex will log matches to the patterns in the various control files. Log
94messages will take these three forms depending on which control file was
95matched:
96
97badhelo
98qmail-smtpd: badhelo: <host> at <remote IP>
99
100badmailfrom and badmailfromnorelay
101qmail-smtpd: badmailfrom: <sender address> at <remote IP>
102
103badmailto and badmailtonorelay
104qmail-smtpd: badmailto: <rcpt address> at <remote IP>
105
106When the LOGREGEX environment variable is set, the matched pattern will
107be included in the log. Log messages will have the regex pattern appended
108to them. For example, a badhelo log message will look like this:
109
110qmail-smtpd: badhelo: <host> at <remote IP> matches pattern: <regex>
111
112
113CONTROL FILES:
114
115qregex provides you with five control files. None of these control files
116is mandatory and you can use them in any combination you choose in your setup.
117
118The "control/badmailfrom" and "control/badmailto" files contain your REs for
119matching against the 'mail from' (envelope sender) and 'rcpt to' (envelope
120recipient) smtp commands respectively.
121The "control/badmailfromnorelay" and "control/badmailtonorelay" match against
122the same commands but are read only when the RELAYCLIENT environment variable
123is not set.
124The "control/badhelo" file matches against the 'helo/ehlo' smtp command.
125
126If you prefer you can symlink the badmailfrom and badmailto control files
127(ln -s badmailfrom badmailto) and maintain fewer sets of rules. Beware
128this might cause problems in certain setups.
129
130 Here's an example "badhelo" file.
131 -----------------------------------
132 # block host strings with no dot (not a FQDN)
133 !\.
134 -----------------------------------
135
136 An example "badmailfrom" file.
137 -----------------------------------
138 # this will drop everything containing the string
139 # bad.domain.com or Bad.Domain.Com or BAD.domain.COM
140 bad\.domain\.com
141 # force users to fully qualify themselves
142 # (i.e. deny "user", accept "user@domain")
143 !@
144 -----------------------------------
145
146 And "badmailto" (a little more interesting)
147 -----------------------------------
148 # must not contain invalid characters, brakets or multiple @'s
149 [!%#:*^(){}]
150 @.*@
151 -----------------------------------
152
153You can use the non-RE character '!' to start an RE as a signal to qregex to
154negate the action. As used above in the badmailfrom file, by negating the '@'
155symbol qregex will signal qmail-smtpd to deny the 'mail from' command whenever
156the address doesn't contain an @ symbol. When used inside a bracket expression,
157the '!' character looses this special meaning. This is shown in the badmailto
158example.
159
160The norelay control files follow the same rules as the other control files but
161are intended to address two specific scenarios.
162The badmailfromnorelay file can be used to block mail trying to spoof a domain
163hosted on your mail server. It prevents a mail client that is not allowed to
164relay email through your server from using one of your hosted domains as its
165envelope sender.
166The badmailtonorelay file can be used to create email addresses that cannot
167receive mail from any source not allowed to relay email through your server.
168This is handy for creating email addresses for use only within your own
169domain(s) that can't receive spam from the world at large.
170
171
172INTERNALS:
173
174qregex (or regexmatch as the function is called) will be called during the
175`helo/ehlo`, `rcpt to` and `mail from` handling routines in "qmail-smtpd.c".
176When called, it will read the proper control file then one by one compile and
177execute the regex on the string passed into qmail-smtpd. If the regex matches
178it returns TRUE (1) and the qmail-smtpd process will deny the user the ability
179to continue. If you change anything and think it betters this patch please
180send me a new diff file so I can take a peek.
181
182
183CONTACT:
184qregex is maintained by:
185 Andrew St. Jean
186 andrew@arda.homeunix.net
187 www.arda.homeunix.net/store/qmail/
188
189Contributers to qregex:
190 Jeremy Kitchen
191 kitchen at scriptkitchen dot com
192 http://www.scriptkitchen.com/qmail
193
194 Alex Pleiner
195 alex@zeitform.de
196 zeitform Internet Dienste
197 http://www.zeitform.de/
198
199 Thanos Massias
200
201Original qregex patch written by:
202 Evan Borgstrom
203 evan at unixpimps dot org
diff --git a/README.remote-auth b/README.remote-auth
new file mode 100644
index 0000000..ab91f23
--- /dev/null
+++ b/README.remote-auth
@@ -0,0 +1,31 @@
1Docs for http://www.qmail.org/qmail-remote_authenticated_smtp.patch
2a patch to convince qmail-remote to use Authenticated SMTP when
3talking to certain hosts.
4
5This version of the patch has been modified by Robert Sanders so that
6base64 encoding of usernames and passwords is done automatically by
7qmail-remote.
8
9After applying the patch, the username password go into
10/var/qmail/control/smtproutes like so:
11
12domain:relay username password
13
14The domain:relay part is identical to a normal smtproutes entry (see
15the qmail-remote man page). For sites that require authentication, you
16simply add two space separated fields to the line, where the first
17field is the username, the second is the password. For example:
18
19inside.af.mil:firewall.af.mil:26 johndoe secret
20
21would send mail for inside.af.mil to firewall.af.mil, port 26 using
22username 'johndoe' and password 'secret'
23
24Since smtproutes will contain plain text usernames and passwords
25you might want to make the file owned by qmailr and set its permissions
26to 0600. Of course, the entries are going to go over the wire in the clear
27anyway as part of the SMTP transaction.
28
29Good luck.
30
31j.
diff --git a/README.starttls b/README.starttls
new file mode 100644
index 0000000..0286632
--- /dev/null
+++ b/README.starttls
@@ -0,0 +1,103 @@
1Frederik Vermeulen <qmail-tls akrul inoa.net> 20060104
2http://inoa.net/qmail-tls/
3
4This patch implements RFC 3207 (was RFC 2487) in qmail.
5This means you can get SSL or TLS encrypted and
6authenticated SMTP between the MTAs and from MUA to MTA.
7The code is considered experimental (but has worked for
8many since its first release on 1999-03-21).
9
10Usage: - install OpenSSL-0.9.8 http://www.openssl.org/
11 (any 0.9.6 to 0.9.8 version is presumed to work)
12 - apply patch to netqmail-1.05 http://qmail.org/netqmail
13 (should work on qmail-1.03 too). The patches to
14 qmail-remote.c and qmail-smtpd.c can be applied separately.
15 - provide a server certificate in /var/qmail/control/servercert.pem.
16 "make cert" makes a self-signed certificate.
17 "make cert-req" makes a certificate request.
18 Note: you can add the CA certificate and intermediate
19 certs to the end of servercert.pem.
20 - replace qmail-smtpd and/or qmail-remote binary
21 - verify operation (header information should show
22 something like
23 "Received [..] with (DHE-RSA-AES256-SHA encrypted) SMTP;")
24 If you don't have a server to test with, you can test
25 by sending mail to tag-ping@tbs-internet.com,
26 which will bounce your mail.
27
28Optional: - when DEBUG is defined, some extra TLS info will be logged
29 - qmail-remote will authenticate with the certificate in
30 /var/qmail/control/clientcert.pem. By preference this is
31 the same as servercert.pem, where nsCertType should be
32 == server,client or be a generic certificate (no usage specified).
33 - when a 512 bit RSA key is provided in /var/qmail/control/rsa512.pem,
34 this key will be used instead of (slow) on-the-fly generation by
35 qmail-smtpd. Idem for 512 and 1024 DH params in control/dh512.pem
36 and control/dh1024.pem. `make tmprsadh` does this.
37 Periodical replacement can be done by crontab:
38 01 01 * * * /var/qmail/bin/update_tmprsadh > /dev/null 2>&1
39 - server authentication:
40 qmail-remote requires authentication from servers for which
41 /var/qmail/control/tlshosts/host.dom.ain.pem exists.
42 The .pem file contains the validating CA certificates
43 (or self-signed server certificate). CommonName has to match.
44 WARNING: this option may cause mail to be delayed, bounced,
45 doublebounced, and lost.
46 If /var/qmail/control/tlshosts/exhaustivelist is present,
47 the lists of hosts in /var/qmail/control/tlshosts is
48 an exhaustive list of hosts TLS is tried on.
49 If /var/qmail/control/notlshosts/host.dom.ain is present,
50 no TLS is tried on this host.
51 - client authentication:
52 when relay rules would reject an incoming mail,
53 qmail-smtpd can allow the mail based on a presented cert.
54 Certs are verified against a CA list in
55 /var/qmail/control/clientca.pem (eg. http://www.modssl.org/
56 source/cvs/exp/mod_ssl/pkg.mod_ssl/pkg.sslcfg/ca-bundle.crt)
57 and the cert email-address has to match a line in
58 /var/qmail/control/tlsclients. This email-address is logged
59 in the headers. CRLs can be provided through
60 /var/qmail/control/clientcrl.pem.
61 - cipher selection:
62 qmail-remote:
63 openssl cipher string (`man ciphers`) read from
64 /var/qmail/control/tlsclientciphers
65 qmail-smtpd:
66 openssl cipher string read from TLSCIPHERS environment variable
67 (can vary based on client IP address e.g.)
68 or if that is not available /var/qmail/control/tlsserverciphers
69 - smtps (deprecated SMTP over TLS via port 465):
70 qmail-remote: when connecting to port 465
71 qmail-smtpd: when SMTPS environment variable is not empty
72
73Caveats: - do a `make clean` after patching
74 - binaries dynamically linked with current openssl versions need
75 recompilation when the shared openssl libs are upgraded.
76 - this patch could conflict with other patches (notably those
77 replacing \n with \r\n, which is a bad idea on encrypted links).
78 - some broken servers have a problem with TLSv1 compatibility.
79 Uncomment the line where we set the SSL_OP_NO_TLSv1 option.
80 - needs working /dev/urandom (or EGD for openssl versions >0.9.7)
81 for seeding random number generator.
82 - packagers should make sure that installing without a valid
83 servercert is impossible
84 - when applied in combination with AUTH patch, AUTH patch
85 should be applied first and first part of this patch
86 will fail. This error can be ignored. Packagers should
87 cut the first 12 lines of this patch to make a happy
88 patch
89 - `make tmprsadh` is recommended (or should I say required),
90 otherwise DH generation can be unpredictably slow
91 - some need "-I/usr/kerberos/include" to be added in conf-cc
92
93Copyright: GPL
94 Links with OpenSSL
95 Inspiration and code from examples in SSLeay (E. Young
96 <eay@cryptsoft.com> and T. Hudson <tjh@cryptsoft.com>),
97 stunnel (M. Trojnara <mtrojnar@ddc.daewoo.com.pl>),
98 Postfix/TLS (L. Jaenicke <Lutz.Jaenicke@aet.tu-cottbus.de>),
99 modssl (R. Engelschall <rse@engelschall.com>),
100 openssl examples of E. Rescorla <ekr@rtfm.com>.
101
102Bug reports: mailto:<qmail-tls akrul inoa.net>
103
diff --git a/TARGETS b/TARGETS
index facdad7..63c8d4c 100644
--- a/TARGETS
+++ b/TARGETS
@@ -10,6 +10,7 @@ qmail-local.o
10qmail.o 10qmail.o
11quote.o 11quote.o
12now.o 12now.o
13base64.o
13gfrom.o 14gfrom.o
14myctime.o 15myctime.o
15slurpclose.o 16slurpclose.o
@@ -168,6 +169,8 @@ control.o
168constmap.o 169constmap.o
169timeoutread.o 170timeoutread.o
170timeoutwrite.o 171timeoutwrite.o
172tls.o
173ssl_timeoutio.o
171timeoutconn.o 174timeoutconn.o
172tcpto.o 175tcpto.o
173dns.o 176dns.o
@@ -252,6 +255,7 @@ rcpthosts.o
252qmail-qmtpd 255qmail-qmtpd
253qmail-smtpd.o 256qmail-smtpd.o
254qmail-smtpd 257qmail-smtpd
258qregex.o
255sendmail.o 259sendmail.o
256sendmail 260sendmail
257tcp-env.o 261tcp-env.o
@@ -320,6 +324,7 @@ binm2
320binm2+df 324binm2+df
321binm3 325binm3
322binm3+df 326binm3+df
327Makefile-cert
323it 328it
324qmail-local.0 329qmail-local.0
325qmail-lspawn.0 330qmail-lspawn.0
@@ -385,3 +390,4 @@ forgeries.0
385man 390man
386setup 391setup
387check 392check
393update_tmprsadh
diff --git a/base64.c b/base64.c
new file mode 100644
index 0000000..ea8b777
--- /dev/null
+++ b/base64.c
@@ -0,0 +1,122 @@
1#include "base64.h"
2#include "stralloc.h"
3#include "substdio.h"
4#include "str.h"
5
6static char *b64alpha =
7 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
8#define B64PAD '='
9
10/* returns 0 ok, 1 illegal, -1 problem */
11
12int b64decode(in,l,out)
13const unsigned char *in;
14int l;
15stralloc *out; /* not null terminated */
16{
17 int p = 0;
18 int n;
19 unsigned int x;
20 int i, j;
21 char *s;
22 unsigned char b[3];
23
24 if (l == 0)
25 {
26 if (!stralloc_copys(out,"")) return -1;
27 return 0;
28 }
29
30 while(in[l-1] == B64PAD) {
31 p ++;
32 l--;
33 }
34
35 n = (l + p) / 4;
36 out->len = (n * 3) - p;
37 if (!stralloc_ready(out,out->len)) return -1;
38 s = out->s;
39
40 for(i = 0; i < n - 1 ; i++) {
41 x = 0;
42 for(j = 0; j < 4; j++) {
43 if(in[j] >= 'A' && in[j] <= 'Z')
44 x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
45 else if(in[j] >= 'a' && in[j] <= 'z')
46 x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
47 else if(in[j] >= '0' && in[j] <= '9')
48 x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
49 else if(in[j] == '+')
50 x = (x << 6) + 62;
51 else if(in[j] == '/')
52 x = (x << 6) + 63;
53 else if(in[j] == '=')
54 x = (x << 6);
55 }
56
57 s[2] = (unsigned char)(x & 255); x >>= 8;
58 s[1] = (unsigned char)(x & 255); x >>= 8;
59 s[0] = (unsigned char)(x & 255); x >>= 8;
60 s += 3; in += 4;
61 }
62
63 x = 0;
64 for(j = 0; j < 4; j++) {
65 if(in[j] >= 'A' && in[j] <= 'Z')
66 x = (x << 6) + (unsigned int)(in[j] - 'A' + 0);
67 else if(in[j] >= 'a' && in[j] <= 'z')
68 x = (x << 6) + (unsigned int)(in[j] - 'a' + 26);
69 else if(in[j] >= '0' && in[j] <= '9')
70 x = (x << 6) + (unsigned int)(in[j] - '0' + 52);
71 else if(in[j] == '+')
72 x = (x << 6) + 62;
73 else if(in[j] == '/')
74 x = (x << 6) + 63;
75 else if(in[j] == '=')
76 x = (x << 6);
77 }
78
79 b[2] = (unsigned char)(x & 255); x >>= 8;
80 b[1] = (unsigned char)(x & 255); x >>= 8;
81 b[0] = (unsigned char)(x & 255); x >>= 8;
82
83 for(i = 0; i < 3 - p; i++)
84 s[i] = b[i];
85
86 return 0;
87}
88
89int b64encode(in,out)
90stralloc *in;
91stralloc *out; /* not null terminated */
92{
93 unsigned char a, b, c;
94 int i;
95 char *s;
96
97 if (in->len == 0)
98 {
99 if (!stralloc_copys(out,"")) return -1;
100 return 0;
101 }
102
103 if (!stralloc_ready(out,in->len / 3 * 4 + 4)) return -1;
104 s = out->s;
105
106 for (i = 0;i < in->len;i += 3) {
107 a = in->s[i];
108 b = i + 1 < in->len ? in->s[i + 1] : 0;
109 c = i + 2 < in->len ? in->s[i + 2] : 0;
110
111 *s++ = b64alpha[a >> 2];
112 *s++ = b64alpha[((a & 3 ) << 4) | (b >> 4)];
113
114 if (i + 1 >= in->len) *s++ = B64PAD;
115 else *s++ = b64alpha[((b & 15) << 2) | (c >> 6)];
116
117 if (i + 2 >= in->len) *s++ = B64PAD;
118 else *s++ = b64alpha[c & 63];
119 }
120 out->len = s - out->s;
121 return 0;
122}
diff --git a/base64.h b/base64.h
new file mode 100644
index 0000000..a9164c0
--- /dev/null
+++ b/base64.h
@@ -0,0 +1,7 @@
1#ifndef BASE64_H
2#define BASE64_H
3
4extern int b64decode();
5extern int b64encode();
6
7#endif
diff --git a/case_startb.c b/case_startb.c
new file mode 100644
index 0000000..ee88efe
--- /dev/null
+++ b/case_startb.c
@@ -0,0 +1,21 @@
1#include "case.h"
2
3int case_startb(s,len,t)
4register char *s;
5unsigned int len;
6register char *t;
7{
8 register unsigned char x;
9 register unsigned char y;
10
11 for (;;) {
12 y = *t++ - 'A';
13 if (y <= 'Z' - 'A') y += 'a'; else y += 'A';
14 if (!y) return 1;
15 if (!len) return 0;
16 --len;
17 x = *s++ - 'A';
18 if (x <= 'Z' - 'A') x += 'a'; else x += 'A';
19 if (x != y) return 0;
20 }
21}
diff --git a/conf-cc b/conf-cc
index e58fb9b..782611a 100644
--- a/conf-cc
+++ b/conf-cc
@@ -1,3 +1,3 @@
1cc -O2 1cc -O2 -DTLS=20070408 -I/usr/local/ssl/include
2 2
3This will be used to compile .c files. 3This will be used to compile .c files.
diff --git a/dns.c b/dns.c
index e9faad7..055ab76 100644
--- a/dns.c
+++ b/dns.c
@@ -267,12 +267,11 @@ stralloc *sa;
267int pref; 267int pref;
268{ 268{
269 int r; 269 int r;
270 struct ip_mx ix; 270 struct ip_mx ix = {0};
271 271
272 if (!stralloc_copy(&glue,sa)) return DNS_MEM; 272 if (!stralloc_copy(&glue,sa)) return DNS_MEM;
273 if (!stralloc_0(&glue)) return DNS_MEM; 273 if (!stralloc_0(&glue)) return DNS_MEM;
274 if (glue.s[0]) { 274 if (glue.s[0]) {
275 ix.pref = 0;
276 if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)]) 275 if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)])
277 { 276 {
278 if (!ipalloc_append(ia,&ix)) return DNS_MEM; 277 if (!ipalloc_append(ia,&ix)) return DNS_MEM;
@@ -291,9 +290,16 @@ int pref;
291 ix.ip = ip; 290 ix.ip = ip;
292 ix.pref = pref; 291 ix.pref = pref;
293 if (r == DNS_SOFT) return DNS_SOFT; 292 if (r == DNS_SOFT) return DNS_SOFT;
294 if (r == 1) 293 if (r == 1) {
294#ifdef IX_FQDN
295 ix.fqdn = glue.s;
296#endif
295 if (!ipalloc_append(ia,&ix)) return DNS_MEM; 297 if (!ipalloc_append(ia,&ix)) return DNS_MEM;
296 } 298 }
299 }
300#ifdef IX_FQDN
301 glue.s = 0;
302#endif
297 return 0; 303 return 0;
298} 304}
299 305
@@ -313,7 +319,7 @@ unsigned long random;
313{ 319{
314 int r; 320 int r;
315 struct mx { stralloc sa; unsigned short p; } *mx; 321 struct mx { stralloc sa; unsigned short p; } *mx;
316 struct ip_mx ix; 322 struct ip_mx ix = {0};
317 int nummx; 323 int nummx;
318 int i; 324 int i;
319 int j; 325 int j;
@@ -325,7 +331,6 @@ unsigned long random;
325 if (!stralloc_copy(&glue,sa)) return DNS_MEM; 331 if (!stralloc_copy(&glue,sa)) return DNS_MEM;
326 if (!stralloc_0(&glue)) return DNS_MEM; 332 if (!stralloc_0(&glue)) return DNS_MEM;
327 if (glue.s[0]) { 333 if (glue.s[0]) {
328 ix.pref = 0;
329 if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)]) 334 if (!glue.s[ip_scan(glue.s,&ix.ip)] || !glue.s[ip_scanbracket(glue.s,&ix.ip)])
330 { 335 {
331 if (!ipalloc_append(ia,&ix)) return DNS_MEM; 336 if (!ipalloc_append(ia,&ix)) return DNS_MEM;
diff --git a/hier.c b/hier.c
index 28e568d..5e5db8f 100644
--- a/hier.c
+++ b/hier.c
@@ -76,6 +76,10 @@ void hier()
76 c(auto_qmail,"boot","binm3+df",auto_uido,auto_gidq,0755); 76 c(auto_qmail,"boot","binm3+df",auto_uido,auto_gidq,0755);
77 77
78 c(auto_qmail,"doc","FAQ",auto_uido,auto_gidq,0644); 78 c(auto_qmail,"doc","FAQ",auto_uido,auto_gidq,0644);
79 c(auto_qmail,"doc","README.qregex",auto_uido,auto_gidq,0644);
80 c(auto_qmail,"doc","README.auth",auto_uido,auto_gidq,0644);
81 c(auto_qmail,"doc","README.remote-auth",auto_uido,auto_gidq,0644);
82 c(auto_qmail,"doc","README.starttls",auto_uido,auto_gidq,0644);
79 c(auto_qmail,"doc","UPGRADE",auto_uido,auto_gidq,0644); 83 c(auto_qmail,"doc","UPGRADE",auto_uido,auto_gidq,0644);
80 c(auto_qmail,"doc","SENDMAIL",auto_uido,auto_gidq,0644); 84 c(auto_qmail,"doc","SENDMAIL",auto_uido,auto_gidq,0644);
81 c(auto_qmail,"doc","INSTALL",auto_uido,auto_gidq,0644); 85 c(auto_qmail,"doc","INSTALL",auto_uido,auto_gidq,0644);
@@ -143,6 +147,9 @@ void hier()
143 c(auto_qmail,"bin","qail",auto_uido,auto_gidq,0755); 147 c(auto_qmail,"bin","qail",auto_uido,auto_gidq,0755);
144 c(auto_qmail,"bin","elq",auto_uido,auto_gidq,0755); 148 c(auto_qmail,"bin","elq",auto_uido,auto_gidq,0755);
145 c(auto_qmail,"bin","pinq",auto_uido,auto_gidq,0755); 149 c(auto_qmail,"bin","pinq",auto_uido,auto_gidq,0755);
150#ifdef TLS
151 c(auto_qmail,"bin","update_tmprsadh",auto_uido,auto_gidq,0755);
152#endif
146 153
147 c(auto_qmail,"man/man5","addresses.5",auto_uido,auto_gidq,0644); 154 c(auto_qmail,"man/man5","addresses.5",auto_uido,auto_gidq,0644);
148 c(auto_qmail,"man/cat5","addresses.0",auto_uido,auto_gidq,0644); 155 c(auto_qmail,"man/cat5","addresses.0",auto_uido,auto_gidq,0644);
diff --git a/install-big.c b/install-big.c
index df813df..a058ad2 100644
--- a/install-big.c
+++ b/install-big.c
@@ -76,6 +76,10 @@ void hier()
76 c(auto_qmail,"boot","binm3+df",auto_uido,auto_gidq,0755); 76 c(auto_qmail,"boot","binm3+df",auto_uido,auto_gidq,0755);
77 77
78 c(auto_qmail,"doc","FAQ",auto_uido,auto_gidq,0644); 78 c(auto_qmail,"doc","FAQ",auto_uido,auto_gidq,0644);
79 c(auto_qmail,"doc","README.qregex",auto_uido,auto_gidq,0644);
80 c(auto_qmail,"doc","README.auth",auto_uido,auto_gidq,0644);
81 c(auto_qmail,"doc","README.remote-auth",auto_uido,auto_gidq,0644);
82 c(auto_qmail,"doc","README.starttls",auto_uido,auto_gidq,0644);
79 c(auto_qmail,"doc","UPGRADE",auto_uido,auto_gidq,0644); 83 c(auto_qmail,"doc","UPGRADE",auto_uido,auto_gidq,0644);
80 c(auto_qmail,"doc","SENDMAIL",auto_uido,auto_gidq,0644); 84 c(auto_qmail,"doc","SENDMAIL",auto_uido,auto_gidq,0644);
81 c(auto_qmail,"doc","INSTALL",auto_uido,auto_gidq,0644); 85 c(auto_qmail,"doc","INSTALL",auto_uido,auto_gidq,0644);
diff --git a/ipalloc.h b/ipalloc.h
index ad61475..bf9d060 100644
--- a/ipalloc.h
+++ b/ipalloc.h
@@ -3,7 +3,15 @@
3 3
4#include "ip.h" 4#include "ip.h"
5 5
6#ifdef TLS
7# define IX_FQDN 1
8#endif
9
10#ifdef IX_FQDN
11struct ip_mx { struct ip_address ip; int pref; char *fqdn; } ;
12#else
6struct ip_mx { struct ip_address ip; int pref; } ; 13struct ip_mx { struct ip_address ip; int pref; } ;
14#endif
7 15
8#include "gen_alloc.h" 16#include "gen_alloc.h"
9 17
diff --git a/qmail-control.9 b/qmail-control.9
index 503ce93..149c328 100644
--- a/qmail-control.9
+++ b/qmail-control.9
@@ -20,7 +20,11 @@ other hostname-related control files.
20 20
21Comments are allowed 21Comments are allowed
22in 22in
23.IR badhelo ,
23.IR badmailfrom , 24.IR badmailfrom ,
25.IR badmailfromnorelay ,
26.IR badmailto ,
27.IR badmailtonorelay ,
24.IR locals , 28.IR locals ,
25.IR percenthack , 29.IR percenthack ,
26.IR qmqpservers , 30.IR qmqpservers ,
@@ -40,14 +44,22 @@ See the corresponding man pages for further details.
40.ta 5c 10c 44.ta 5c 10c
41control default used by 45control default used by
42 46
47.I badhelo \fR(none) \fRqmail-smtpd
43.I badmailfrom \fR(none) \fRqmail-smtpd 48.I badmailfrom \fR(none) \fRqmail-smtpd
49.I badmailfromnorelay \fR(none) \fRqmail-smtpd
50.I badmailto \fR(none) \fRqmail-smtpd
51.I badmailtonorelay \fR(none) \fRqmail-smtpd
44.I bouncefrom \fRMAILER-DAEMON \fRqmail-send 52.I bouncefrom \fRMAILER-DAEMON \fRqmail-send
45.I bouncehost \fIme \fRqmail-send 53.I bouncehost \fIme \fRqmail-send
54.I clientca.pem \fR(none) \fRqmail-smtpd
55.I clientcert.pem \fR(none) \fRqmail-remote
46.I concurrencylocal \fR10 \fRqmail-send 56.I concurrencylocal \fR10 \fRqmail-send
47.I concurrencyremote \fR20 \fRqmail-send 57.I concurrencyremote \fR20 \fRqmail-send
48.I defaultdomain \fIme \fRqmail-inject 58.I defaultdomain \fIme \fRqmail-inject
49.I defaulthost \fIme \fRqmail-inject 59.I defaulthost \fIme \fRqmail-inject
50.I databytes \fR0 \fRqmail-smtpd 60.I databytes \fR0 \fRqmail-smtpd
61.I dh1024.pem \fR(none) \fRqmail-smtpd
62.I dh512.pem \fR(none) \fRqmail-smtpd
51.I doublebouncehost \fIme \fRqmail-send 63.I doublebouncehost \fIme \fRqmail-send
52.I doublebounceto \fRpostmaster \fRqmail-send 64.I doublebounceto \fRpostmaster \fRqmail-send
53.I envnoathost \fIme \fRqmail-send 65.I envnoathost \fIme \fRqmail-send
@@ -61,11 +73,17 @@ control default used by
61.I qmqpservers \fR(none) \fRqmail-qmqpc 73.I qmqpservers \fR(none) \fRqmail-qmqpc
62.I queuelifetime \fR604800 \fRqmail-send 74.I queuelifetime \fR604800 \fRqmail-send
63.I rcpthosts \fR(none) \fRqmail-smtpd 75.I rcpthosts \fR(none) \fRqmail-smtpd
76.I rsa512.pem \fR(none) \fRqmail-smtpd
77.I servercert.pem \fR(none) \fRqmail-smtpd
64.I smtpgreeting \fIme \fRqmail-smtpd 78.I smtpgreeting \fIme \fRqmail-smtpd
65.I smtproutes \fR(none) \fRqmail-remote 79.I smtproutes \fR(none) \fRqmail-remote
66.I timeoutconnect \fR60 \fRqmail-remote 80.I timeoutconnect \fR60 \fRqmail-remote
67.I timeoutremote \fR1200 \fRqmail-remote 81.I timeoutremote \fR1200 \fRqmail-remote
68.I timeoutsmtpd \fR1200 \fRqmail-smtpd 82.I timeoutsmtpd \fR1200 \fRqmail-smtpd
83.I tlsclients \fR(none) \fRqmail-smtpd
84.I tlsclientciphers \fR(none) \fRqmail-remote
85.I tlshosts/FQDN.pem \fR(none) \fRqmail-remote
86.I tlsserverciphers \fR(none) \fRqmail-smtpd
69.I virtualdomains \fR(none) \fRqmail-send 87.I virtualdomains \fR(none) \fRqmail-send
70.fi 88.fi
71.RE 89.RE
diff --git a/qmail-remote.8 b/qmail-remote.8
index 08bae85..995443b 100644
--- a/qmail-remote.8
+++ b/qmail-remote.8
@@ -114,6 +114,10 @@ arguments.
114always exits zero. 114always exits zero.
115.SH "CONTROL FILES" 115.SH "CONTROL FILES"
116.TP 5 116.TP 5
117.I clientcert.pem
118SSL certificate that is used to authenticate with the remote server
119during a TLS session.
120.TP 5
117.I helohost 121.I helohost
118Current host name, 122Current host name,
119for use solely in saying hello to the remote SMTP server. 123for use solely in saying hello to the remote SMTP server.
@@ -123,6 +127,16 @@ if that is supplied;
123otherwise 127otherwise
124.B qmail-remote 128.B qmail-remote
125refuses to run. 129refuses to run.
130
131.TP 5
132.I notlshosts/<FQDN>
133.B qmail-remote
134will not try TLS on servers for which this file exists
135.RB ( <FQDN>
136is the fully-qualified domain name of the server).
137.IR (tlshosts/<FQDN>.pem
138takes precedence over this file however).
139
126.TP 5 140.TP 5
127.I smtproutes 141.I smtproutes
128Artificial SMTP routes. 142Artificial SMTP routes.
@@ -156,6 +170,8 @@ may be empty;
156this tells 170this tells
157.B qmail-remote 171.B qmail-remote
158to look up MX records as usual. 172to look up MX records as usual.
173.I port
174value of 465 (deprecated smtps port) causes TLS session to be started.
159.I smtproutes 175.I smtproutes
160may include wildcards: 176may include wildcards:
161 177
@@ -195,6 +211,33 @@ Number of seconds
195.B qmail-remote 211.B qmail-remote
196will wait for each response from the remote SMTP server. 212will wait for each response from the remote SMTP server.
197Default: 1200. 213Default: 1200.
214
215.TP 5
216.I tlsclientciphers
217A set of OpenSSL client cipher strings. Multiple ciphers
218contained in a string should be separated by a colon.
219
220.TP 5
221.I tlshosts/<FQDN>.pem
222.B qmail-remote
223requires TLS authentication from servers for which this file exists
224.RB ( <FQDN>
225is the fully-qualified domain name of the server). One of the
226.I dNSName
227or the
228.I CommonName
229attributes have to match. The file contains the trusted CA certificates.
230
231.B WARNING:
232this option may cause mail to be delayed, bounced, doublebounced, or lost.
233
234.TP 5
235.I tlshosts/exhaustivelist
236if this file exists
237no TLS will be tried on hosts other than those for which a file
238.B tlshosts/<FQDN>.pem
239exists.
240
198.SH "SEE ALSO" 241.SH "SEE ALSO"
199addresses(5), 242addresses(5),
200envelopes(5), 243envelopes(5),
diff --git a/qmail-remote.c b/qmail-remote.c
index 7d65473..dae884d 100644
--- a/qmail-remote.c
+++ b/qmail-remote.c
@@ -28,6 +28,7 @@
28#include "timeoutconn.h" 28#include "timeoutconn.h"
29#include "timeoutread.h" 29#include "timeoutread.h"
30#include "timeoutwrite.h" 30#include "timeoutwrite.h"
31#include "base64.h"
31 32
32#define HUGESMTPTEXT 5000 33#define HUGESMTPTEXT 5000
33 34
@@ -43,11 +44,27 @@ stralloc routes = {0};
43struct constmap maproutes; 44struct constmap maproutes;
44stralloc host = {0}; 45stralloc host = {0};
45stralloc sender = {0}; 46stralloc sender = {0};
47stralloc auth_smtp_user = {0};
48stralloc auth_smtp_pass = {0};
49stralloc auth_b64_user = {0};
50stralloc auth_b64_pass = {0};
51stralloc auth_status = {0};
46 52
47saa reciplist = {0}; 53saa reciplist = {0};
48 54
49struct ip_address partner; 55struct ip_address partner;
50 56
57#ifdef TLS
58# include <sys/stat.h>
59# include "tls.h"
60# include "ssl_timeoutio.h"
61# include <openssl/x509v3.h>
62# define EHLO 1
63
64int tls_init();
65const char *ssl_err_str = 0;
66#endif
67
51void out(s) char *s; { if (substdio_puts(subfdoutsmall,s) == -1) _exit(0); } 68void out(s) char *s; { if (substdio_puts(subfdoutsmall,s) == -1) _exit(0); }
52void zero() { if (substdio_put(subfdoutsmall,"\0",1) == -1) _exit(0); } 69void zero() { if (substdio_put(subfdoutsmall,"\0",1) == -1) _exit(0); }
53void zerodie() { zero(); substdio_flush(subfdoutsmall); _exit(0); } 70void zerodie() { zero(); substdio_flush(subfdoutsmall); _exit(0); }
@@ -85,6 +102,18 @@ void perm_ambigmx() { out("D\
85Sorry. Although I'm listed as a best-preference MX or A for that host,\n\ 102Sorry. Although I'm listed as a best-preference MX or A for that host,\n\
86it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n"); 103it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n");
87zerodie(); } 104zerodie(); }
105void auth_user_not_set() {
106 if(!stralloc_copys(&auth_status, \
107 "User and password not set, continuing without authentication.\n"))
108 temp_nomem();
109 if(!stralloc_0(&auth_status)) temp_nomem();
110}
111void no_supported_auth() {
112 if(!stralloc_copys(&auth_status, \
113 "No supported AUTH method found, continuing without authentication.\n"))
114 temp_nomem();
115 if(!stralloc_0(&auth_status)) temp_nomem();
116}
88 117
89void outhost() 118void outhost()
90{ 119{
@@ -99,6 +128,9 @@ void dropped() {
99 outhost(); 128 outhost();
100 out(" but connection died. "); 129 out(" but connection died. ");
101 if (flagcritical) out("Possible duplicate! "); 130 if (flagcritical) out("Possible duplicate! ");
131#ifdef TLS
132 if (ssl_err_str) { out(ssl_err_str); out(" "); }
133#endif
102 out("(#4.4.2)\n"); 134 out("(#4.4.2)\n");
103 zerodie(); 135 zerodie();
104} 136}
@@ -110,6 +142,12 @@ int timeout = 1200;
110int saferead(fd,buf,len) int fd; char *buf; int len; 142int saferead(fd,buf,len) int fd; char *buf; int len;
111{ 143{
112 int r; 144 int r;
145#ifdef TLS
146 if (ssl) {
147 r = ssl_timeoutread(timeout, smtpfd, smtpfd, ssl, buf, len);
148 if (r < 0) ssl_err_str = ssl_error_str();
149 } else
150#endif
113 r = timeoutread(timeout,smtpfd,buf,len); 151 r = timeoutread(timeout,smtpfd,buf,len);
114 if (r <= 0) dropped(); 152 if (r <= 0) dropped();
115 return r; 153 return r;
@@ -117,6 +155,12 @@ int saferead(fd,buf,len) int fd; char *buf; int len;
117int safewrite(fd,buf,len) int fd; char *buf; int len; 155int safewrite(fd,buf,len) int fd; char *buf; int len;
118{ 156{
119 int r; 157 int r;
158#ifdef TLS
159 if (ssl) {
160 r = ssl_timeoutwrite(timeout, smtpfd, smtpfd, ssl, buf, len);
161 if (r < 0) ssl_err_str = ssl_error_str();
162 } else
163#endif
120 r = timeoutwrite(timeout,smtpfd,buf,len); 164 r = timeoutwrite(timeout,smtpfd,buf,len);
121 if (r <= 0) dropped(); 165 if (r <= 0) dropped();
122 return r; 166 return r;
@@ -163,6 +207,65 @@ unsigned long smtpcode()
163 return code; 207 return code;
164} 208}
165 209
210#ifdef EHLO
211saa ehlokw = {0}; /* list of EHLO keywords and parameters */
212int maxehlokwlen = 0;
213
214unsigned long ehlo()
215{
216 stralloc *sa;
217 char *s, *e, *p;
218 unsigned long code;
219
220 if (ehlokw.len > maxehlokwlen) maxehlokwlen = ehlokw.len;
221 ehlokw.len = 0;
222
223# ifdef MXPS
224 if (type == 's') return 0;
225# endif
226
227 substdio_puts(&smtpto, "EHLO ");
228 substdio_put(&smtpto, helohost.s, helohost.len);
229 substdio_puts(&smtpto, "\r\n");
230 substdio_flush(&smtpto);
231
232 code = smtpcode();
233 if (code != 250) return code;
234
235 s = smtptext.s;
236 while (*s++ != '\n') ; /* skip the first line: contains the domain */
237
238 e = smtptext.s + smtptext.len - 6; /* 250-?\n */
239 while (s <= e)
240 {
241 int wasspace = 0;
242
243 if (!saa_readyplus(&ehlokw, 1)) temp_nomem();
244 sa = ehlokw.sa + ehlokw.len++;
245 if (ehlokw.len > maxehlokwlen) *sa = sauninit; else sa->len = 0;
246
247 /* smtptext is known to end in a '\n' */
248 for (p = (s += 4); ; ++p)
249 if (*p == '\n' || *p == ' ' || *p == '\t') {
250 if (!wasspace)
251 if (!stralloc_catb(sa, s, p - s) || !stralloc_0(sa)) temp_nomem();
252 if (*p == '\n') break;
253 wasspace = 1;
254 } else if (wasspace == 1) {
255 wasspace = 0;
256 s = p;
257 }
258 s = ++p;
259
260 /* keyword should consist of alpha-num and '-'
261 * broken AUTH might use '=' instead of space */
262 for (p = sa->s; *p; ++p) if (*p == '=') { *p = 0; break; }
263 }
264
265 return 250;
266}
267#endif
268
166void outsmtptext() 269void outsmtptext()
167{ 270{
168 int i; 271 int i;
@@ -179,6 +282,11 @@ void quit(prepend,append)
179char *prepend; 282char *prepend;
180char *append; 283char *append;
181{ 284{
285#ifdef TLS
286 /* shouldn't talk to the client unless in an appropriate state */
287 int state = ssl ? ssl->state : SSL_ST_BEFORE;
288 if (state & SSL_ST_OK || (!smtps && state & SSL_ST_BEFORE))
289#endif
182 substdio_putsflush(&smtpto,"QUIT\r\n"); 290 substdio_putsflush(&smtpto,"QUIT\r\n");
183 /* waiting for remote side is just too ridiculous */ 291 /* waiting for remote side is just too ridiculous */
184 out(prepend); 292 out(prepend);
@@ -186,6 +294,30 @@ char *append;
186 out(append); 294 out(append);
187 out(".\n"); 295 out(".\n");
188 outsmtptext(); 296 outsmtptext();
297
298#if defined(TLS) && defined(DEBUG)
299 if (ssl) {
300 X509 *peercert;
301
302 out("STARTTLS proto="); out(SSL_get_version(ssl));
303 out("; cipher="); out(SSL_get_cipher(ssl));
304
305 /* we want certificate details */
306 if (peercert = SSL_get_peer_certificate(ssl)) {
307 char *str;
308
309 str = X509_NAME_oneline(X509_get_subject_name(peercert), NULL, 0);
310 out("; subject="); out(str); OPENSSL_free(str);
311
312 str = X509_NAME_oneline(X509_get_issuer_name(peercert), NULL, 0);
313 out("; issuer="); out(str); OPENSSL_free(str);
314
315 X509_free(peercert);
316 }
317 out(";\n");
318 }
319#endif
320
189 zerodie(); 321 zerodie();
190} 322}
191 323
@@ -214,26 +346,297 @@ void blast()
214 substdio_flush(&smtpto); 346 substdio_flush(&smtpto);
215} 347}
216 348
349#ifdef TLS
350char *partner_fqdn = 0;
351
352# define TLS_QUIT quit(ssl ? "; connected to " : "; connecting to ", "")
353void tls_quit(const char *s1, const char *s2)
354{
355 out(s1); if (s2) { out(": "); out(s2); } TLS_QUIT;
356}
357# define tls_quit_error(s) tls_quit(s, ssl_error())
358
359int match_partner(const char *s, int len)
360{
361 if (!case_diffb(partner_fqdn, len, s) && !partner_fqdn[len]) return 1;
362 /* we also match if the name is *.domainname */
363 if (*s == '*') {
364 const char *domain = partner_fqdn + str_chr(partner_fqdn, '.');
365 if (!case_diffb(domain, --len, ++s) && !domain[len]) return 1;
366 }
367 return 0;
368}
369
370/* don't want to fail handshake if certificate can't be verified */
371int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
372
373int tls_init()
374{
375 int i;
376 SSL *myssl;
377 SSL_CTX *ctx;
378 stralloc saciphers = {0};
379 const char *ciphers, *servercert = 0;
380
381 if (partner_fqdn) {
382 struct stat st;
383 stralloc tmp = {0};
384 if (!stralloc_copys(&tmp, "control/tlshosts/")
385 || !stralloc_catb(&tmp, partner_fqdn, str_len(partner_fqdn))
386 || !stralloc_catb(&tmp, ".pem", 5)) temp_nomem();
387 if (stat(tmp.s, &st) == 0)
388 servercert = tmp.s;
389 else {
390 if (!stralloc_copys(&tmp, "control/notlshosts/")
391 || !stralloc_catb(&tmp, partner_fqdn, str_len(partner_fqdn)+1))
392 temp_nomem();
393 if ((stat("control/tlshosts/exhaustivelist", &st) == 0) ||
394 (stat(tmp.s, &st) == 0)) {
395 alloc_free(tmp.s);
396 return 0;
397 }
398 alloc_free(tmp.s);
399 }
400 }
401
402 if (!smtps) {
403 stralloc *sa = ehlokw.sa;
404 unsigned int len = ehlokw.len;
405 /* look for STARTTLS among EHLO keywords */
406 for ( ; len && case_diffs(sa->s, "STARTTLS"); ++sa, --len) ;
407 if (!len) {
408 if (!servercert) return 0;
409 out("ZNo TLS achieved while "); out(servercert);
410 out(" exists"); smtptext.len = 0; TLS_QUIT;
411 }
412 }
413
414 SSL_library_init();
415 ctx = SSL_CTX_new(SSLv23_client_method());
416 if (!ctx) {
417 if (!smtps && !servercert) return 0;
418 smtptext.len = 0;
419 tls_quit_error("ZTLS error initializing ctx");
420 }
421
422 if (servercert) {
423 if (!SSL_CTX_load_verify_locations(ctx, servercert, NULL)) {
424 SSL_CTX_free(ctx);
425 smtptext.len = 0;
426 out("ZTLS unable to load "); tls_quit_error(servercert);
427 }
428 /* set the callback here; SSL_set_verify didn't work before 0.9.6c */
429 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb);
430 }
431
432 /* let the other side complain if it needs a cert and we don't have one */
433# define CLIENTCERT "control/clientcert.pem"
434 if (SSL_CTX_use_certificate_chain_file(ctx, CLIENTCERT))
435 SSL_CTX_use_RSAPrivateKey_file(ctx, CLIENTCERT, SSL_FILETYPE_PEM);
436# undef CLIENTCERT
437
438 myssl = SSL_new(ctx);
439 SSL_CTX_free(ctx);
440 if (!myssl) {
441 if (!smtps && !servercert) return 0;
442 smtptext.len = 0;
443 tls_quit_error("ZTLS error initializing ssl");
444 }
445
446 if (!smtps) substdio_putsflush(&smtpto, "STARTTLS\r\n");
447
448 /* while the server is preparing a responce, do something else */
449 if (control_readfile(&saciphers, "control/tlsclientciphers", 0) == -1)
450 { SSL_free(myssl); temp_control(); }
451 if (saciphers.len) {
452 for (i = 0; i < saciphers.len - 1; ++i)
453 if (!saciphers.s[i]) saciphers.s[i] = ':';
454 ciphers = saciphers.s;
455 }
456 else ciphers = "DEFAULT";
457 SSL_set_cipher_list(myssl, ciphers);
458 alloc_free(saciphers.s);
459
460 /* SSL_set_options(myssl, SSL_OP_NO_TLSv1); */
461 SSL_set_fd(myssl, smtpfd);
462
463 /* read the responce to STARTTLS */
464 if (!smtps) {
465 if (smtpcode() != 220) {
466 SSL_free(myssl);
467 if (!servercert) return 0;
468 out("ZSTARTTLS rejected while ");
469 out(servercert); out(" exists"); TLS_QUIT;
470 }
471 smtptext.len = 0;
472 }
473
474 ssl = myssl;
475 if (ssl_timeoutconn(timeout, smtpfd, smtpfd, ssl) <= 0)
476 tls_quit("ZTLS connect failed", ssl_error_str());
477
478 if (servercert) {
479 X509 *peercert;
480 STACK_OF(GENERAL_NAME) *gens;
481
482 int r = SSL_get_verify_result(ssl);
483 if (r != X509_V_OK) {
484 out("ZTLS unable to verify server with ");
485 tls_quit(servercert, X509_verify_cert_error_string(r));
486 }
487 alloc_free(servercert);
488
489 peercert = SSL_get_peer_certificate(ssl);
490 if (!peercert) {
491 out("ZTLS unable to verify server ");
492 tls_quit(partner_fqdn, "no certificate provided");
493 }
494
495 /* RFC 2595 section 2.4: find a matching name
496 * first find a match among alternative names */
497 gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0);
498 if (gens) {
499 for (i = 0, r = sk_GENERAL_NAME_num(gens); i < r; ++i)
500 {
501 const GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i);
502 if (gn->type == GEN_DNS)
503 if (match_partner(gn->d.ia5->data, gn->d.ia5->length)) break;
504 }
505 sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
506 }
507
508 /* no alternative name matched, look up commonName */
509 if (!gens || i >= r) {
510 stralloc peer = {0};
511 X509_NAME *subj = X509_get_subject_name(peercert);
512 i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
513 if (i >= 0) {
514 const ASN1_STRING *s = X509_NAME_get_entry(subj, i)->value;
515 if (s) { peer.len = s->length; peer.s = s->data; }
516 }
517 if (peer.len <= 0) {
518 out("ZTLS unable to verify server ");
519 tls_quit(partner_fqdn, "certificate contains no valid commonName");
520 }
521 if (!match_partner(peer.s, peer.len)) {
522 out("ZTLS unable to verify server "); out(partner_fqdn);
523 out(": received certificate for "); outsafe(&peer); TLS_QUIT;
524 }
525 }
526
527 X509_free(peercert);
528 }
529
530 if (smtps) if (smtpcode() != 220)
531 quit("ZTLS Connected to "," but greeting failed");
532
533 return 1;
534}
535#endif
536
217stralloc recip = {0}; 537stralloc recip = {0};
218 538
539void mail_without_auth()
540{
541 substdio_puts(&smtpto,"MAIL FROM:<");
542 substdio_put(&smtpto,sender.s,sender.len);
543 substdio_puts(&smtpto,">\r\n");
544 substdio_flush(&smtpto);
545}
546
219void smtp() 547void smtp()
220{ 548{
221 unsigned long code; 549 unsigned long code;
222 int flagbother; 550 int flagbother;
223 int i; 551 int i, j;
552
553#ifndef PORT_SMTP
554 /* the qmtpc patch uses smtp_port and undefines PORT_SMTP */
555# define port smtp_port
556#endif
557
558#ifdef TLS
559# ifdef MXPS
560 if (type == 'S') smtps = 1;
561 else if (type != 's')
562# endif
563 if (port == 465) smtps = 1;
564 if (!smtps)
565#endif
224 566
225 if (smtpcode() != 220) quit("ZConnected to "," but greeting failed"); 567 if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
226 568
569#ifdef EHLO
570# ifdef TLS
571 if (!smtps)
572# endif
573 code = ehlo();
574
575# ifdef TLS
576 if (tls_init())
577 /* RFC2487 says we should issue EHLO (even if we might not need
578 * extensions); at the same time, it does not prohibit a server
579 * to reject the EHLO and make us fallback to HELO */
580 code = ehlo();
581# endif
582
583 if (code == 250) {
584 /* add EHLO response checks here */
585
586 /* and if EHLO failed, use HELO */
587 } else {
588#endif
589
227 substdio_puts(&smtpto,"HELO "); 590 substdio_puts(&smtpto,"HELO ");
228 substdio_put(&smtpto,helohost.s,helohost.len); 591 substdio_put(&smtpto,helohost.s,helohost.len);
229 substdio_puts(&smtpto,"\r\n"); 592 substdio_puts(&smtpto,"\r\n");
230 substdio_flush(&smtpto); 593 substdio_flush(&smtpto);
231 if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected"); 594 if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
232 595
233 substdio_puts(&smtpto,"MAIL FROM:<"); 596#ifdef EHLO
234 substdio_put(&smtpto,sender.s,sender.len); 597 }
235 substdio_puts(&smtpto,">\r\n"); 598#endif
236 substdio_flush(&smtpto); 599 i = 0;
600 if (auth_smtp_user.len && auth_smtp_pass.len) {
601 while((i += str_chr(smtptext.s+i,'\n') + 1) &&
602 (i+8 < smtptext.len) &&
603 str_diffn(smtptext.s+i+4,"AUTH",4));
604 if (((i+9 < smtptext.len) &&
605 (str_diffn(smtptext.s+i+9," ",1) ||
606 str_diffn(smtptext.s+i+9,"=",1))) &&
607 ( i += str_chr(smtptext.s+i,'L') + 1 ) &&
608 str_diffn(smtptext.s+i+1,"OGIN",4)) {
609
610 if (b64encode(&auth_smtp_user,&auth_b64_user)) quit("ZConnected to "," but unable to base64encode user");
611 if (b64encode(&auth_smtp_pass,&auth_b64_pass)) quit("ZConnected to "," but unable to base64encode pass");
612
613 substdio_puts(&smtpto,"AUTH LOGIN\r\n");
614 substdio_flush(&smtpto);
615 if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (AUTH LOGIN)");
616 substdio_put(&smtpto,auth_b64_user.s,auth_b64_user.len);
617 substdio_puts(&smtpto,"\r\n");
618 substdio_flush(&smtpto);
619 if (smtpcode() != 334) quit("ZConnected to "," but authentication was rejected (username)");
620 substdio_put(&smtpto,auth_b64_pass.s,auth_b64_pass.len);
621 substdio_puts(&smtpto,"\r\n");
622 substdio_flush(&smtpto);
623 if (smtpcode() != 235) quit("ZConnected to "," but authentication was rejected (password)");
624 substdio_puts(&smtpto,"MAIL FROM:<");
625 substdio_put(&smtpto,sender.s,sender.len);
626 substdio_puts(&smtpto,"> AUTH=<");
627 substdio_put(&smtpto,sender.s,sender.len);
628 substdio_puts(&smtpto,">\r\n");
629 substdio_flush(&smtpto);
630 if(!stralloc_copys(&auth_status, "Delivered with authenticated connection to \n")) temp_nomem();
631 if(!stralloc_0(&auth_status)) temp_nomem();
632 } else {
633 no_supported_auth();
634 mail_without_auth();
635 }
636 } else {
637 auth_user_not_set();
638 mail_without_auth();
639 }
237 code = smtpcode(); 640 code = smtpcode();
238 if (code >= 500) quit("DConnected to "," but sender was rejected"); 641 if (code >= 500) quit("DConnected to "," but sender was rejected");
239 if (code >= 400) quit("ZConnected to "," but sender was rejected"); 642 if (code >= 400) quit("ZConnected to "," but sender was rejected");
@@ -246,15 +649,22 @@ void smtp()
246 substdio_flush(&smtpto); 649 substdio_flush(&smtpto);
247 code = smtpcode(); 650 code = smtpcode();
248 if (code >= 500) { 651 if (code >= 500) {
249 out("h"); outhost(); out(" does not like recipient.\n"); 652 out("h"); out(auth_status.s); outhost();
653 out(" does not like recipient.\n");
250 outsmtptext(); zero(); 654 outsmtptext(); zero();
251 } 655 }
252 else if (code >= 400) { 656 else if (code >= 400) {
253 out("s"); outhost(); out(" does not like recipient.\n"); 657 out("s"); out(auth_status.s); outhost();
658 out(" does not like recipient.\n");
254 outsmtptext(); zero(); 659 outsmtptext(); zero();
255 } 660 }
256 else { 661 else {
257 out("r"); zero(); 662 /*
663 * James Raftery <james@now.ie>
664 * Log _real_ envelope recipient, post canonicalisation.
665 */
666 out("r"); out(auth_status.s);
667 out("<"); outsafe(&reciplist.sa[i]); out("> "); zero();
258 flagbother = 1; 668 flagbother = 1;
259 } 669 }
260 } 670 }
@@ -331,7 +741,7 @@ int argc;
331char **argv; 741char **argv;
332{ 742{
333 static ipalloc ip = {0}; 743 static ipalloc ip = {0};
334 int i; 744 int i,j;
335 unsigned long random; 745 unsigned long random;
336 char **recips; 746 char **recips;
337 unsigned long prefme; 747 unsigned long prefme;
@@ -347,6 +757,9 @@ char **argv;
347 757
348 if (!stralloc_copys(&host,argv[1])) temp_nomem(); 758 if (!stralloc_copys(&host,argv[1])) temp_nomem();
349 759
760 if (!stralloc_copys(&auth_smtp_user,"")) temp_nomem();
761 if (!stralloc_copys(&auth_smtp_pass,"")) temp_nomem();
762
350 relayhost = 0; 763 relayhost = 0;
351 for (i = 0;i <= host.len;++i) 764 for (i = 0;i <= host.len;++i)
352 if ((i == 0) || (i == host.len) || (host.s[i] == '.')) 765 if ((i == 0) || (i == host.len) || (host.s[i] == '.'))
@@ -355,6 +768,16 @@ char **argv;
355 if (relayhost && !*relayhost) relayhost = 0; 768 if (relayhost && !*relayhost) relayhost = 0;
356 769
357 if (relayhost) { 770 if (relayhost) {
771 i = str_chr(relayhost,' ');
772 if (relayhost[i]) {
773 j = str_chr(relayhost + i + 1,' ');
774 if (relayhost[j]) {
775 relayhost[i] = 0;
776 relayhost[i + j + 1] = 0;
777 if (!stralloc_copys(&auth_smtp_user,relayhost + i + 1)) temp_nomem();
778 if (!stralloc_copys(&auth_smtp_pass,relayhost + i + j + 2)) temp_nomem();
779 }
780 }
358 i = str_chr(relayhost,':'); 781 i = str_chr(relayhost,':');
359 if (relayhost[i]) { 782 if (relayhost[i]) {
360 scan_ulong(relayhost + i + 1,&port); 783 scan_ulong(relayhost + i + 1,&port);
@@ -417,6 +840,9 @@ char **argv;
417 if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) { 840 if (timeoutconn(smtpfd,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0) {
418 tcpto_err(&ip.ix[i].ip,0); 841 tcpto_err(&ip.ix[i].ip,0);
419 partner = ip.ix[i].ip; 842 partner = ip.ix[i].ip;
843#ifdef TLS
844 partner_fqdn = ip.ix[i].fqdn;
845#endif
420 smtp(); /* does not return */ 846 smtp(); /* does not return */
421 } 847 }
422 tcpto_err(&ip.ix[i].ip,errno == error_timeout); 848 tcpto_err(&ip.ix[i].ip,errno == error_timeout);
diff --git a/qmail-showctl.c b/qmail-showctl.c
index a24aa63..bbaa2bc 100644
--- a/qmail-showctl.c
+++ b/qmail-showctl.c
@@ -214,7 +214,11 @@ void main()
214 _exit(111); 214 _exit(111);
215 } 215 }
216 216
217 do_lst("badmailfrom","Any MAIL FROM is allowed.",""," not accepted in MAIL FROM."); 217 do_lst("badhelo","Any HELO host name is allowed.",""," HELO host name denied if it matches this pattern.");
218 do_lst("badmailfrom","Any MAIL FROM is allowed.",""," MAIL FROM denied if it matches this pattern.");
219 do_lst("badmailfromnorelay","Any MAIL FROM is allowed.",""," MAIL FROM denied if it matches this pattern and RELAYCLIENT is not set.");
220 do_lst("badmailto","No RCPT TO are specifically denied.",""," RCPT TO denied if it matches this pattern.");
221 do_lst("badmailtonorelay","No RCPT TO are specifically denied.",""," RCPT TO denied if it matches this pattern and RELAYCLIENT is not set.");
218 do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is "); 222 do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is ");
219 do_str("bouncehost",1,"bouncehost","Bounce host name is "); 223 do_str("bouncehost",1,"bouncehost","Bounce host name is ");
220 do_int("concurrencylocal","10","Local concurrency is ",""); 224 do_int("concurrencylocal","10","Local concurrency is ","");
@@ -267,7 +271,11 @@ void main()
267 if (str_equal(d->d_name,"..")) continue; 271 if (str_equal(d->d_name,"..")) continue;
268 if (str_equal(d->d_name,"bouncefrom")) continue; 272 if (str_equal(d->d_name,"bouncefrom")) continue;
269 if (str_equal(d->d_name,"bouncehost")) continue; 273 if (str_equal(d->d_name,"bouncehost")) continue;
274 if (str_equal(d->d_name,"badhelo")) continue;
270 if (str_equal(d->d_name,"badmailfrom")) continue; 275 if (str_equal(d->d_name,"badmailfrom")) continue;
276 if (str_equal(d->d_name,"badmailfromnorelay")) continue;
277 if (str_equal(d->d_name,"badmailto")) continue;
278 if (str_equal(d->d_name,"badmailtonorelay")) continue;
271 if (str_equal(d->d_name,"bouncefrom")) continue; 279 if (str_equal(d->d_name,"bouncefrom")) continue;
272 if (str_equal(d->d_name,"bouncehost")) continue; 280 if (str_equal(d->d_name,"bouncehost")) continue;
273 if (str_equal(d->d_name,"concurrencylocal")) continue; 281 if (str_equal(d->d_name,"concurrencylocal")) continue;
diff --git a/qmail-smtpd.8 b/qmail-smtpd.8
index c4640b8..ce0dc02 100644
--- a/qmail-smtpd.8
+++ b/qmail-smtpd.8
@@ -14,6 +14,15 @@ must be supplied several environment variables;
14see 14see
15.BR tcp-environ(5) . 15.BR tcp-environ(5) .
16 16
17If the environment variable
18.B SMTPS
19is non-empty,
20.B qmail-smtpd
21starts a TLS session (to support the deprecated SMTPS protocol,
22normally on port 465). Otherwise,
23.B qmail-smtpd
24offers the STARTTLS extension to ESMTP.
25
17.B qmail-smtpd 26.B qmail-smtpd
18is responsible for counting hops. 27is responsible for counting hops.
19It rejects any message with 100 or more 28It rejects any message with 100 or more
@@ -23,7 +32,30 @@ or
23header fields. 32header fields.
24 33
25.B qmail-smtpd 34.B qmail-smtpd
26supports ESMTP, including the 8BITMIME and PIPELINING options. 35supports ESMTP, including the 8BITMIME, DATA, PIPELINING, SIZE, and AUTH options.
36.B qmail-smtpd
37includes a \'MAIL FROM:\' parameter parser and obeys \'Auth\' and \'Size\' advertisements.
38.B qmail-smtpd
39can accept LOGIN, PLAIN, and CRAM-MD5 AUTH types. It invokes
40.IR checkprogram ,
41which reads on file descriptor 3 the username, a 0 byte, the password
42or CRAM-MD5 digest/response derived from the SMTP client,
43another 0 byte, a CRAM-MD5 challenge (if applicable to the AUTH type),
44and a final 0 byte.
45.I checkprogram
46invokes
47.I subprogram
48upon successful authentication, which should in turn return 0 to
49.BR qmail-smtpd ,
50effectively setting the environment variables $RELAYCLIENT and $TCPREMOTEINFO
51(any supplied value replaced with the authenticated username).
52.B qmail-smtpd
53will reject the authentication attempt if it receives a nonzero return
54value from
55.I checkprogram
56or
57.IR subprogram .
58
27.SH TRANSPARENCY 59.SH TRANSPARENCY
28.B qmail-smtpd 60.B qmail-smtpd
29converts the SMTP newline convention into the UNIX newline convention 61converts the SMTP newline convention into the UNIX newline convention
@@ -37,11 +69,26 @@ accepts messages that contain long lines or non-ASCII characters,
37even though such messages violate the SMTP protocol. 69even though such messages violate the SMTP protocol.
38.SH "CONTROL FILES" 70.SH "CONTROL FILES"
39.TP 5 71.TP 5
72.I badhelo
73Unacceptable HELO/EHLO host names.
74.B qmail-smtpd
75will reject every recipient address for a message if
76the host name is listed in,
77or matches a POSIX regular expression pattern listed in,
78.IR badhelo .
79If the
80.B NOBADHELO
81environment variable is set, then the contents of
82.IR badhelo
83will be ignored.
84For more information, please have a look at doc/README.qregex.
85.TP 5
40.I badmailfrom 86.I badmailfrom
41Unacceptable envelope sender addresses. 87Unacceptable envelope sender addresses.
42.B qmail-smtpd 88.B qmail-smtpd
43will reject every recipient address for a message 89will reject every recipient address for a message
44if the envelope sender address is listed in 90if the envelope sender address is listed in, or matches a POSIX regular expression
91pattern listed in,
45.IR badmailfrom . 92.IR badmailfrom .
46A line in 93A line in
47.I badmailfrom 94.I badmailfrom
@@ -49,6 +96,45 @@ may be of the form
49.BR @\fIhost , 96.BR @\fIhost ,
50meaning every address at 97meaning every address at
51.IR host . 98.IR host .
99For more information, please have a look at doc/README.qregex.
100.TP 5
101.I badmailfromnorelay
102Functions the same as the
103.IR badmailfrom
104control file but is read only if the
105.B RELAYCLIENT
106environment variable is not set.
107For more information, please have a look at doc/README.qregex.
108.TP 5
109.I badmailto
110Unacceptable envelope recipient addresses.
111.B qmail-smtpd
112will reject every recipient address for a message if the recipient address
113is listed in,
114or matches a POSIX regular expression pattern listed in,
115.IR badmailto .
116For more information, please have a look at doc/README.qregex.
117.TP 5
118.I badmailtonorelay
119Functions the same as the
120.IR badmailto
121control file but is read only if the
122.B RELAYCLIENT
123environment variable is not set.
124For more information, please have a look at doc/README.qregex.
125
126.TP 5
127.I clientca.pem
128A list of Certifying Authority (CA) certificates that are used to verify
129the client-presented certificates during a TLS-encrypted session.
130
131.TP 5
132.I clientcrl.pem
133A list of Certificate Revocation Lists (CRLs). If present it
134should contain the CRLs of the CAs in
135.I clientca.pem
136and client certs will be checked for revocation.
137
52.TP 5 138.TP 5
53.I databytes 139.I databytes
54Maximum number of bytes allowed in a message, 140Maximum number of bytes allowed in a message,
@@ -76,6 +162,18 @@ If the environment variable
76.B DATABYTES 162.B DATABYTES
77is set, it overrides 163is set, it overrides
78.IR databytes . 164.IR databytes .
165
166.TP 5
167.I dh1024.pem
168If these 1024 bit DH parameters are provided,
169.B qmail-smtpd
170will use them for TLS sessions instead of generating one on-the-fly
171(which is very timeconsuming).
172.TP 5
173.I dh512.pem
174512 bit counterpart for
175.B dh1024.pem.
176
79.TP 5 177.TP 5
80.I localiphost 178.I localiphost
81Replacement host name for local IP addresses. 179Replacement host name for local IP addresses.
@@ -151,6 +249,19 @@ may include wildcards:
151 249
152Envelope recipient addresses without @ signs are 250Envelope recipient addresses without @ signs are
153always allowed through. 251always allowed through.
252
253.TP 5
254.I rsa512.pem
255If this 512 bit RSA key is provided,
256.B qmail-smtpd
257will use it for TLS sessions instead of generating one on-the-fly.
258
259.TP 5
260.I servercert.pem
261SSL certificate to be presented to clients in TLS-encrypted sessions.
262Should contain both the certificate and the private key. Certifying Authority
263(CA) and intermediate certificates can be added at the end of the file.
264
154.TP 5 265.TP 5
155.I smtpgreeting 266.I smtpgreeting
156SMTP greeting message. 267SMTP greeting message.
@@ -169,6 +280,24 @@ Number of seconds
169.B qmail-smtpd 280.B qmail-smtpd
170will wait for each new buffer of data from the remote SMTP client. 281will wait for each new buffer of data from the remote SMTP client.
171Default: 1200. 282Default: 1200.
283
284.TP 5
285.I tlsclients
286A list of email addresses. When relay rules would reject an incoming message,
287.B qmail-smtpd
288can allow it if the client presents a certificate that can be verified against
289the CA list in
290.I clientca.pem
291and the certificate email address is in
292.IR tlsclients .
293
294.TP 5
295.I tlsserverciphers
296A set of OpenSSL cipher strings. Multiple ciphers contained in a
297string should be separated by a colon. If the environment variable
298.B TLSCIPHERS
299is set to such a string, it takes precedence.
300
172.SH "SEE ALSO" 301.SH "SEE ALSO"
173tcp-env(1), 302tcp-env(1),
174tcp-environ(5), 303tcp-environ(5),
diff --git a/qmail-smtpd.c b/qmail-smtpd.c
index 1e28c88..c77c6cc 100644
--- a/qmail-smtpd.c
+++ b/qmail-smtpd.c
@@ -23,14 +23,44 @@
23#include "timeoutread.h" 23#include "timeoutread.h"
24#include "timeoutwrite.h" 24#include "timeoutwrite.h"
25#include "commands.h" 25#include "commands.h"
26#include "wait.h"
27#include "qregex.h"
28#include "strerr.h"
29
30#define BMCHECK_BMF 0
31#define BMCHECK_BMFNR 1
32#define BMCHECK_BMT 2
33#define BMCHECK_BMTNR 3
34#define BMCHECK_BHELO 4
35
36#define CRAM_MD5
37#define AUTHSLEEP 5
26 38
27#define MAXHOPS 100 39#define MAXHOPS 100
28unsigned int databytes = 0; 40unsigned int databytes = 0;
29int timeout = 1200; 41int timeout = 1200;
30 42
43const char *protocol = "SMTP";
44
45#ifdef TLS
46#include <sys/stat.h>
47#include "tls.h"
48#include "ssl_timeoutio.h"
49
50void tls_init();
51int tls_verify();
52void tls_nogateway();
53int ssl_rfd = -1, ssl_wfd = -1; /* SSL_get_Xfd() are broken */
54#endif
55
31int safewrite(fd,buf,len) int fd; char *buf; int len; 56int safewrite(fd,buf,len) int fd; char *buf; int len;
32{ 57{
33 int r; 58 int r;
59#ifdef TLS
60 if (ssl && fd == ssl_wfd)
61 r = ssl_timeoutwrite(timeout, ssl_rfd, ssl_wfd, ssl, buf, len);
62 else
63#endif
34 r = timeoutwrite(timeout,fd,buf,len); 64 r = timeoutwrite(timeout,fd,buf,len);
35 if (r <= 0) _exit(1); 65 if (r <= 0) _exit(1);
36 return r; 66 return r;
@@ -49,8 +79,20 @@ void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _
49void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); } 79void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
50void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); } 80void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); }
51 81
52void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); } 82void err_size() { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); }
83void err_bmf() { out("553 sorry, your envelope sender has been denied (#5.7.1)\r\n"); }
84void err_bmt() { out("553 sorry, your envelope recipient has been denied (#5.7.1)\r\n"); }
85void err_bhelo() { out("553 sorry, your HELO host name has been denied (#5.7.1)\r\n"); }
86#ifndef TLS
53void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); } 87void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
88#else
89void err_nogateway()
90{
91 out("553 sorry, that domain isn't in my list of allowed rcpthosts");
92 tls_nogateway();
93 out(" (#5.7.1)\r\n");
94}
95#endif
54void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); } 96void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
55void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); } 97void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
56void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); } 98void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
@@ -59,6 +101,16 @@ void err_noop() { out("250 ok\r\n"); }
59void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); } 101void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
60void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } 102void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
61 103
104int err_child() { out("454 oops, problem with child and I can't auth (#4.3.0)\r\n"); return -1; }
105int err_fork() { out("454 oops, child won't start and I can't auth (#4.3.0)\r\n"); return -1; }
106int err_pipe() { out("454 oops, unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; }
107int err_write() { out("454 oops, unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; }
108void err_authd() { out("503 you're already authenticated (#5.5.0)\r\n"); }
109void err_authmail() { out("503 no auth during mail transaction (#5.5.0)\r\n"); }
110int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); return -1; }
111int err_authabrt() { out("501 auth exchange canceled (#5.0.0)\r\n"); return -1; }
112int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; }
113void err_authfail() { out("535 authentication failed (#5.7.1)\r\n"); }
62 114
63stralloc greeting = {0}; 115stralloc greeting = {0};
64 116
@@ -93,9 +145,24 @@ void dohelo(arg) char *arg; {
93 145
94int liphostok = 0; 146int liphostok = 0;
95stralloc liphost = {0}; 147stralloc liphost = {0};
148
96int bmfok = 0; 149int bmfok = 0;
97stralloc bmf = {0}; 150stralloc bmf = {0};
98struct constmap mapbmf; 151
152int bmfnrok = 0;
153stralloc bmfnr = {0};
154
155int bmtok = 0;
156stralloc bmt = {0};
157
158int bmtnrok = 0;
159stralloc bmtnr = {0};
160
161int bhelook = 0;
162stralloc bhelo = {0};
163
164int logregex = 0;
165stralloc matchedregex = {0};
99 166
100void setup() 167void setup()
101{ 168{
@@ -109,13 +176,25 @@ void setup()
109 if (liphostok == -1) die_control(); 176 if (liphostok == -1) die_control();
110 if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); 177 if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
111 if (timeout <= 0) timeout = 1; 178 if (timeout <= 0) timeout = 1;
112
113 if (rcpthosts_init() == -1) die_control(); 179 if (rcpthosts_init() == -1) die_control();
114 180
115 bmfok = control_readfile(&bmf,"control/badmailfrom",0); 181 bmfok = control_readfile(&bmf,"control/badmailfrom",0);
116 if (bmfok == -1) die_control(); 182 if (bmfok == -1) die_control();
117 if (bmfok) 183
118 if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); 184 bmfnrok = control_readfile(&bmfnr,"control/badmailfromnorelay",0);
185 if (bmfnrok == -1) die_control();
186
187 bmtok = control_readfile(&bmt,"control/badmailto",0);
188 if (bmtok == -1) die_control();
189
190 bmtnrok = control_readfile(&bmtnr,"control/badmailtonorelay",0);
191 if (bmtnrok == -1) die_control();
192
193 bhelook = control_readfile(&bhelo, "control/badhelo",0);
194 if (bhelook == -1) die_control();
195 if (env_get("NOBADHELO")) bhelook = 0;
196
197 if (env_get("LOGREGEX")) logregex = 1;
119 198
120 if (control_readint(&databytes,"control/databytes") == -1) die_control(); 199 if (control_readint(&databytes,"control/databytes") == -1) die_control();
121 x = env_get("DATABYTES"); 200 x = env_get("DATABYTES");
@@ -131,6 +210,11 @@ void setup()
131 if (!remotehost) remotehost = "unknown"; 210 if (!remotehost) remotehost = "unknown";
132 remoteinfo = env_get("TCPREMOTEINFO"); 211 remoteinfo = env_get("TCPREMOTEINFO");
133 relayclient = env_get("RELAYCLIENT"); 212 relayclient = env_get("RELAYCLIENT");
213
214#ifdef TLS
215 if (env_get("SMTPS")) { smtps = 1; tls_init(); }
216 else
217#endif
134 dohelo(remotehost); 218 dohelo(remotehost);
135} 219}
136 220
@@ -197,14 +281,56 @@ char *arg;
197 return 1; 281 return 1;
198} 282}
199 283
200int bmfcheck() 284int bmcheck(which) int which;
201{ 285{
202 int j; 286 int i = 0;
203 if (!bmfok) return 0; 287 int j = 0;
204 if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; 288 int x = 0;
205 j = byte_rchr(addr.s,addr.len,'@'); 289 int negate = 0;
206 if (j < addr.len) 290 static stralloc bmb = {0};
207 if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1; 291 static stralloc curregex = {0};
292
293 if (which == BMCHECK_BMF) {
294 if (!stralloc_copy(&bmb,&bmf)) die_nomem();
295 } else if (which == BMCHECK_BMFNR) {
296 if (!stralloc_copy(&bmb,&bmfnr)) die_nomem();
297 } else if (which == BMCHECK_BMT) {
298 if (!stralloc_copy(&bmb,&bmt)) die_nomem();
299 } else if (which == BMCHECK_BMTNR) {
300 if (!stralloc_copy(&bmb,&bmtnr)) die_nomem();
301 } else if (which == BMCHECK_BHELO) {
302 if (!stralloc_copy(&bmb,&bhelo)) die_nomem();
303 } else {
304 die_control();
305 }
306
307 while (j < bmb.len) {
308 i = j;
309 while ((bmb.s[i] != '\0') && (i < bmb.len)) i++;
310 if (bmb.s[j] == '!') {
311 negate = 1;
312 j++;
313 }
314 if (!stralloc_copyb(&curregex,bmb.s + j,(i - j))) die_nomem();
315 if (!stralloc_0(&curregex)) die_nomem();
316 if (which == BMCHECK_BHELO) {
317 x = matchregex(helohost.s, curregex.s);
318 } else {
319 x = matchregex(addr.s, curregex.s);
320 }
321 if ((negate) && (x == 0)) {
322 if (!stralloc_copyb(&matchedregex,bmb.s + j - 1,(i - j + 1))) die_nomem();
323 if (!stralloc_0(&matchedregex)) die_nomem();
324 return 1;
325 }
326 if (!(negate) && (x > 0)) {
327 if (!stralloc_copyb(&matchedregex,bmb.s + j,(i - j))) die_nomem();
328 if (!stralloc_0(&matchedregex)) die_nomem();
329 return 1;
330 }
331 j = i + 1;
332 negate = 0;
333 }
208 return 0; 334 return 0;
209} 335}
210 336
@@ -213,24 +339,110 @@ int addrallowed()
213 int r; 339 int r;
214 r = rcpthosts(addr.s,str_len(addr.s)); 340 r = rcpthosts(addr.s,str_len(addr.s));
215 if (r == -1) die_control(); 341 if (r == -1) die_control();
342#ifdef TLS
343 if (r == 0) if (tls_verify()) r = -2;
344#endif
216 return r; 345 return r;
217} 346}
218 347
219 348
220int seenmail = 0; 349int seenmail = 0;
221int flagbarf; /* defined if seenmail */ 350int flagbarfbmf; /* defined if seenmail */
351int flagbarfbmt;
352int flagbarfbhelo;
353int flagsize;
222stralloc mailfrom = {0}; 354stralloc mailfrom = {0};
223stralloc rcptto = {0}; 355stralloc rcptto = {0};
356stralloc fuser = {0};
357stralloc mfparms = {0};
358
359int mailfrom_size(arg) char *arg;
360{
361 long r;
362 unsigned long sizebytes = 0;
363
364 scan_ulong(arg,&r);
365 sizebytes = r;
366 if (databytes) if (sizebytes > databytes) return 1;
367 return 0;
368}
369
370void mailfrom_auth(arg,len)
371char *arg;
372int len;
373{
374 int j;
375
376 if (!stralloc_copys(&fuser,"")) die_nomem();
377 if (case_starts(arg,"<>")) { if (!stralloc_cats(&fuser,"unknown")) die_nomem(); }
378 else
379 while (len) {
380 if (*arg == '+') {
381 if (case_starts(arg,"+3D")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"=")) die_nomem(); }
382 if (case_starts(arg,"+2B")) { arg=arg+2; len=len-2; if (!stralloc_cats(&fuser,"+")) die_nomem(); }
383 }
384 else
385 if (!stralloc_catb(&fuser,arg,1)) die_nomem();
386 arg++; len--;
387 }
388 if(!stralloc_0(&fuser)) die_nomem();
389 if (!remoteinfo) {
390 remoteinfo = fuser.s;
391 if (!env_unset("TCPREMOTEINFO")) die_read();
392 if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
393 }
394}
395
396void mailfrom_parms(arg) char *arg;
397{
398 int i;
399 int len;
400
401 len = str_len(arg);
402 if (!stralloc_copys(&mfparms,"")) die_nomem;
403 i = byte_chr(arg,len,'>');
404 if (i > 4 && i < len) {
405 while (len) {
406 arg++; len--;
407 if (*arg == ' ' || *arg == '\0' ) {
408 if (case_starts(mfparms.s,"SIZE=")) if (mailfrom_size(mfparms.s+5)) { flagsize = 1; return; }
409 if (case_starts(mfparms.s,"AUTH=")) mailfrom_auth(mfparms.s+5,mfparms.len-5);
410 if (!stralloc_copys(&mfparms,"")) die_nomem;
411 }
412 else
413 if (!stralloc_catb(&mfparms,arg,1)) die_nomem;
414 }
415 }
416}
224 417
225void smtp_helo(arg) char *arg; 418void smtp_helo(arg) char *arg;
226{ 419{
227 smtp_greet("250 "); out("\r\n"); 420 smtp_greet("250 "); out("\r\n");
228 seenmail = 0; dohelo(arg); 421 seenmail = 0; dohelo(arg);
422 if (bhelook) flagbarfbhelo = bmcheck(BMCHECK_BHELO);
229} 423}
424/* ESMTP extensions are published here */
230void smtp_ehlo(arg) char *arg; 425void smtp_ehlo(arg) char *arg;
231{ 426{
232 smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n"); 427#ifdef TLS
428 struct stat st;
429#endif
430 char size[FMT_ULONG];
431 size[fmt_ulong(size,(unsigned int) databytes)] = 0;
432 smtp_greet("250-");
433#ifdef TLS
434 if (!ssl && (stat("control/servercert.pem",&st) == 0))
435 out("\r\n250-STARTTLS");
436#endif
437 out("\r\n250-PIPELINING\r\n250-8BITMIME\r\n");
438 out("250-SIZE "); out(size); out("\r\n");
439#ifdef CRAM_MD5
440 out("250 AUTH LOGIN PLAIN CRAM-MD5\r\n");
441#else
442 out("250 AUTH LOGIN PLAIN\r\n");
443#endif
233 seenmail = 0; dohelo(arg); 444 seenmail = 0; dohelo(arg);
445 if (bhelook) flagbarfbhelo = bmcheck(BMCHECK_BHELO);
234} 446}
235void smtp_rset() 447void smtp_rset()
236{ 448{
@@ -240,7 +452,14 @@ void smtp_rset()
240void smtp_mail(arg) char *arg; 452void smtp_mail(arg) char *arg;
241{ 453{
242 if (!addrparse(arg)) { err_syntax(); return; } 454 if (!addrparse(arg)) { err_syntax(); return; }
243 flagbarf = bmfcheck(); 455 flagsize = 0;
456 mailfrom_parms(arg);
457 if (flagsize) { err_size(); return; }
458 flagbarfbmf = 0; /* bmcheck is skipped for empty envelope senders */
459 if ((bmfok) && (addr.len != 1)) flagbarfbmf = bmcheck(BMCHECK_BMF);
460 if ((!flagbarfbmf) && (bmfnrok) && (addr.len != 1) && (!relayclient)) {
461 flagbarfbmf = bmcheck(BMCHECK_BMFNR);
462 }
244 seenmail = 1; 463 seenmail = 1;
245 if (!stralloc_copys(&rcptto,"")) die_nomem(); 464 if (!stralloc_copys(&rcptto,"")) die_nomem();
246 if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); 465 if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
@@ -250,7 +469,37 @@ void smtp_mail(arg) char *arg;
250void smtp_rcpt(arg) char *arg; { 469void smtp_rcpt(arg) char *arg; {
251 if (!seenmail) { err_wantmail(); return; } 470 if (!seenmail) { err_wantmail(); return; }
252 if (!addrparse(arg)) { err_syntax(); return; } 471 if (!addrparse(arg)) { err_syntax(); return; }
253 if (flagbarf) { err_bmf(); return; } 472 if (flagbarfbhelo) {
473 if (logregex) {
474 strerr_warn6("qmail-smtpd: badhelo: <",helohost.s,"> at ",remoteip," matches pattern: ",matchedregex.s,0);
475 } else {
476 strerr_warn4("qmail-smtpd: badhelo: <",helohost.s,"> at ",remoteip,0);
477 }
478 err_bhelo();
479 return;
480 }
481 if (flagbarfbmf) {
482 if (logregex) {
483 strerr_warn6("qmail-smtpd: badmailfrom: <",mailfrom.s,"> at ",remoteip," matches pattern: ",matchedregex.s,0);
484 } else {
485 strerr_warn4("qmail-smtpd: badmailfrom: <",mailfrom.s,"> at ",remoteip,0);
486 }
487 err_bmf();
488 return;
489 }
490 if (bmtok) flagbarfbmt = bmcheck(BMCHECK_BMT);
491 if ((!flagbarfbmt) && (bmtnrok) && (!relayclient)) {
492 flagbarfbmt = bmcheck(BMCHECK_BMTNR);
493 }
494 if (flagbarfbmt) {
495 if (logregex) {
496 strerr_warn6("qmail-smtpd: badmailto: <",addr.s,"> at ",remoteip," matches pattern: ",matchedregex.s,0);
497 } else {
498 strerr_warn4("qmail-smtpd: badmailto: <",addr.s,"> at ",remoteip,0);
499 }
500 err_bmt();
501 return;
502 }
254 if (relayclient) { 503 if (relayclient) {
255 --addr.len; 504 --addr.len;
256 if (!stralloc_cats(&addr,relayclient)) die_nomem(); 505 if (!stralloc_cats(&addr,relayclient)) die_nomem();
@@ -269,6 +518,11 @@ int saferead(fd,buf,len) int fd; char *buf; int len;
269{ 518{
270 int r; 519 int r;
271 flush(); 520 flush();
521#ifdef TLS
522 if (ssl && fd == ssl_rfd)
523 r = ssl_timeoutread(timeout, ssl_rfd, ssl_wfd, ssl, buf, len);
524 else
525#endif
272 r = timeoutread(timeout,fd,buf,len); 526 r = timeoutread(timeout,fd,buf,len);
273 if (r == -1) if (errno == error_timeout) die_alarm(); 527 if (r == -1) if (errno == error_timeout) die_alarm();
274 if (r <= 0) die_read(); 528 if (r <= 0) die_read();
@@ -378,7 +632,7 @@ void smtp_data() {
378 qp = qmail_qp(&qqt); 632 qp = qmail_qp(&qqt);
379 out("354 go ahead\r\n"); 633 out("354 go ahead\r\n");
380 634
381 received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo); 635 received(&qqt,protocol,local,remoteip,remotehost,remoteinfo,fakehelo);
382 blast(&hops); 636 blast(&hops);
383 hops = (hops >= MAXHOPS); 637 hops = (hops >= MAXHOPS);
384 if (hops) qmail_fail(&qqt); 638 if (hops) qmail_fail(&qqt);
@@ -388,28 +642,494 @@ void smtp_data() {
388 qqx = qmail_close(&qqt); 642 qqx = qmail_close(&qqt);
389 if (!*qqx) { acceptmessage(qp); return; } 643 if (!*qqx) { acceptmessage(qp); return; }
390 if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; } 644 if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
391 if (databytes) if (!bytestooverflow) { out("552 sorry, that message size exceeds my databytes limit (#5.3.4)\r\n"); return; } 645 if (databytes) if (!bytestooverflow) { err_size(); return; }
392 if (*qqx == 'D') out("554 "); else out("451 "); 646 if (*qqx == 'D') out("554 "); else out("451 ");
393 out(qqx + 1); 647 out(qqx + 1);
394 out("\r\n"); 648 out("\r\n");
395} 649}
396 650
651/* this file is too long ----------------------------------------- SMTP AUTH */
652
653char unique[FMT_ULONG + FMT_ULONG + 3];
654static stralloc authin = {0}; /* input from SMTP client */
655static stralloc user = {0}; /* authorization user-id */
656static stralloc pass = {0}; /* plain passwd or digest */
657static stralloc resp = {0}; /* b64 response */
658#ifdef CRAM_MD5
659static stralloc chal = {0}; /* plain challenge */
660static stralloc slop = {0}; /* b64 challenge */
661#endif
662
663int flagauth = 0;
664char **childargs;
665char ssauthbuf[512];
666substdio ssauth = SUBSTDIO_FDBUF(safewrite,3,ssauthbuf,sizeof(ssauthbuf));
667
668int authgetl(void) {
669 int i;
670
671 if (!stralloc_copys(&authin,"")) die_nomem();
672 for (;;) {
673 if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */
674 i = substdio_get(&ssin,authin.s + authin.len,1);
675 if (i != 1) die_read();
676 if (authin.s[authin.len] == '\n') break;
677 ++authin.len;
678 }
679
680 if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len;
681 authin.s[authin.len] = 0;
682 if (*authin.s == '*' && *(authin.s + 1) == 0) { return err_authabrt(); }
683 if (authin.len == 0) { return err_input(); }
684 return authin.len;
685}
686
687int authenticate(void)
688{
689 int child;
690 int wstat;
691 int pi[2];
692
693 if (!stralloc_0(&user)) die_nomem();
694 if (!stralloc_0(&pass)) die_nomem();
695#ifdef CRAM_MD5
696 if (!stralloc_0(&chal)) die_nomem();
697#endif
698
699 if (pipe(pi) == -1) return err_pipe();
700 switch(child = fork()) {
701 case -1:
702 return err_fork();
703 case 0:
704 close(pi[1]);
705 if(fd_copy(3,pi[0]) == -1) return err_pipe();
706 sig_pipedefault();
707 execvp(*childargs, childargs);
708 _exit(1);
709 }
710 close(pi[0]);
711
712 substdio_fdbuf(&ssauth,write,pi[1],ssauthbuf,sizeof ssauthbuf);
713 if (substdio_put(&ssauth,user.s,user.len) == -1) return err_write();
714 if (substdio_put(&ssauth,pass.s,pass.len) == -1) return err_write();
715#ifdef CRAM_MD5
716 if (substdio_put(&ssauth,chal.s,chal.len) == -1) return err_write();
717#endif
718 if (substdio_flush(&ssauth) == -1) return err_write();
719
720 close(pi[1]);
721#ifdef CRAM_MD5
722 if (!stralloc_copys(&chal,"")) die_nomem();
723 if (!stralloc_copys(&slop,"")) die_nomem();
724#endif
725 byte_zero(ssauthbuf,sizeof ssauthbuf);
726 if (wait_pid(&wstat,child) == -1) return err_child();
727 if (wait_crashed(wstat)) return err_child();
728 if (wait_exitcode(wstat)) { sleep(AUTHSLEEP); return 1; } /* no */
729 return 0; /* yes */
730}
731
732int auth_login(arg) char *arg;
733{
734 int r;
735
736 if (*arg) {
737 if (r = b64decode(arg,str_len(arg),&user) == 1) return err_input();
738 }
739 else {
740 out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */
741 if (authgetl() < 0) return -1;
742 if (r = b64decode(authin.s,authin.len,&user) == 1) return err_input();
743 }
744 if (r == -1) die_nomem();
745
746 out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */
747
748 if (authgetl() < 0) return -1;
749 if (r = b64decode(authin.s,authin.len,&pass) == 1) return err_input();
750 if (r == -1) die_nomem();
751
752 if (!user.len || !pass.len) return err_input();
753 return authenticate();
754}
755
756int auth_plain(arg) char *arg;
757{
758 int r, id = 0;
759
760 if (*arg) {
761 if (r = b64decode(arg,str_len(arg),&resp) == 1) return err_input();
762 }
763 else {
764 out("334 \r\n"); flush();
765 if (authgetl() < 0) return -1;
766 if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input();
767 }
768 if (r == -1 || !stralloc_0(&resp)) die_nomem();
769 while (resp.s[id]) id++; /* "authorize-id\0userid\0passwd\0" */
770
771 if (resp.len > id + 1)
772 if (!stralloc_copys(&user,resp.s + id + 1)) die_nomem();
773 if (resp.len > id + user.len + 2)
774 if (!stralloc_copys(&pass,resp.s + id + user.len + 2)) die_nomem();
775
776 if (!user.len || !pass.len) return err_input();
777 return authenticate();
778}
779
780#ifdef CRAM_MD5
781int auth_cram()
782{
783 int i, r;
784 char *s;
785
786 s = unique; /* generate challenge */
787 s += fmt_uint(s,getpid());
788 *s++ = '.';
789 s += fmt_ulong(s,(unsigned long) now());
790 *s++ = '@';
791 *s++ = 0;
792 if (!stralloc_copys(&chal,"<")) die_nomem();
793 if (!stralloc_cats(&chal,unique)) die_nomem();
794 if (!stralloc_cats(&chal,local)) die_nomem();
795 if (!stralloc_cats(&chal,">")) die_nomem();
796 if (b64encode(&chal,&slop) < 0) die_nomem();
797 if (!stralloc_0(&slop)) die_nomem();
798
799 out("334 "); /* "334 base64_challenge \r\n" */
800 out(slop.s);
801 out("\r\n");
802 flush();
803
804 if (authgetl() < 0) return -1; /* got response */
805 if (r = b64decode(authin.s,authin.len,&resp) == 1) return err_input();
806 if (r == -1 || !stralloc_0(&resp)) die_nomem();
807
808 i = str_chr(resp.s,' ');
809 s = resp.s + i;
810 while (*s == ' ') ++s;
811 resp.s[i] = 0;
812 if (!stralloc_copys(&user,resp.s)) die_nomem(); /* userid */
813 if (!stralloc_copys(&pass,s)) die_nomem(); /* digest */
814
815 if (!user.len || !pass.len) return err_input();
816 return authenticate();
817}
818#endif
819
820struct authcmd {
821 char *text;
822 int (*fun)();
823} authcmds[] = {
824 { "login",auth_login }
825, { "plain",auth_plain }
826#ifdef CRAM_MD5
827, { "cram-md5",auth_cram }
828#endif
829, { 0,err_noauth }
830};
831
832void smtp_auth(arg)
833char *arg;
834{
835 int i;
836 char *cmd = arg;
837
838 if (!*childargs) { out("503 auth not available (#5.3.3)\r\n"); return; }
839 if (flagauth) { err_authd(); return; }
840 if (seenmail) { err_authmail(); return; }
841
842 if (!stralloc_copys(&user,"")) die_nomem();
843 if (!stralloc_copys(&pass,"")) die_nomem();
844 if (!stralloc_copys(&resp,"")) die_nomem();
845#ifdef CRAM_MD5
846 if (!stralloc_copys(&chal,"")) die_nomem();
847#endif
848
849 i = str_chr(cmd,' ');
850 arg = cmd + i;
851 while (*arg == ' ') ++arg;
852 cmd[i] = 0;
853
854 for (i = 0;authcmds[i].text;++i)
855 if (case_equals(authcmds[i].text,cmd)) break;
856
857 switch (authcmds[i].fun(arg)) {
858 case 0:
859 flagauth = 1;
860 protocol = "ESMTPA";
861 relayclient = "";
862 remoteinfo = user.s;
863 if (!env_unset("TCPREMOTEINFO")) die_read();
864 if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem();
865 if (!env_put2("RELAYCLIENT",relayclient)) die_nomem();
866 out("235 ok, go ahead (#2.0.0)\r\n");
867 break;
868 case 1:
869 err_authfail(user.s,authcmds[i].text);
870 }
871}
872
873
874/* this file is too long --------------------------------------------- GO ON */
875
876#ifdef TLS
877stralloc proto = {0};
878int ssl_verified = 0;
879const char *ssl_verify_err = 0;
880
881void smtp_tls(char *arg)
882{
883 if (ssl) err_unimpl();
884 else if (*arg) out("501 Syntax error (no parameters allowed) (#5.5.4)\r\n");
885 else tls_init();
886}
887
888RSA *tmp_rsa_cb(SSL *ssl, int export, int keylen)
889{
890 if (!export) keylen = 512;
891 if (keylen == 512) {
892 FILE *in = fopen("control/rsa512.pem", "r");
893 if (in) {
894 RSA *rsa = PEM_read_RSAPrivateKey(in, NULL, NULL, NULL);
895 fclose(in);
896 if (rsa) return rsa;
897 }
898 }
899 return RSA_generate_key(keylen, RSA_F4, NULL, NULL);
900}
901
902DH *tmp_dh_cb(SSL *ssl, int export, int keylen)
903{
904 if (!export) keylen = 1024;
905 if (keylen == 512) {
906 FILE *in = fopen("control/dh512.pem", "r");
907 if (in) {
908 DH *dh = PEM_read_DHparams(in, NULL, NULL, NULL);
909 fclose(in);
910 if (dh) return dh;
911 }
912 }
913 if (keylen == 1024) {
914 FILE *in = fopen("control/dh1024.pem", "r");
915 if (in) {
916 DH *dh = PEM_read_DHparams(in, NULL, NULL, NULL);
917 fclose(in);
918 if (dh) return dh;
919 }
920 }
921 return DH_generate_parameters(keylen, DH_GENERATOR_2, NULL, NULL);
922}
923
924/* don't want to fail handshake if cert isn't verifiable */
925int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { return 1; }
926
927void tls_nogateway()
928{
929 /* there may be cases when relayclient is set */
930 if (!ssl || relayclient) return;
931 out("; no valid cert for gatewaying");
932 if (ssl_verify_err) { out(": "); out(ssl_verify_err); }
933}
934void tls_out(const char *s1, const char *s2)
935{
936 out("454 TLS "); out(s1);
937 if (s2) { out(": "); out(s2); }
938 out(" (#4.3.0)\r\n"); flush();
939}
940void tls_err(const char *s) { tls_out(s, ssl_error()); if (smtps) die_read(); }
941
942# define CLIENTCA "control/clientca.pem"
943# define CLIENTCRL "control/clientcrl.pem"
944# define SERVERCERT "control/servercert.pem"
945
946int tls_verify()
947{
948 stralloc clients = {0};
949 struct constmap mapclients;
950
951 if (!ssl || relayclient || ssl_verified) return 0;
952 ssl_verified = 1; /* don't do this twice */
953
954 /* request client cert to see if it can be verified by one of our CAs
955 * and the associated email address matches an entry in tlsclients */
956 switch (control_readfile(&clients, "control/tlsclients", 0))
957 {
958 case 1:
959 if (constmap_init(&mapclients, clients.s, clients.len, 0)) {
960 /* if CLIENTCA contains all the standard root certificates, a
961 * 0.9.6b client might fail with SSL_R_EXCESSIVE_MESSAGE_SIZE;
962 * it is probably due to 0.9.6b supporting only 8k key exchange
963 * data while the 0.9.6c release increases that limit to 100k */
964 STACK_OF(X509_NAME) *sk = SSL_load_client_CA_file(CLIENTCA);
965 if (sk) {
966 SSL_set_client_CA_list(ssl, sk);
967 SSL_set_verify(ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, NULL);
968 break;
969 }
970 constmap_free(&mapclients);
971 }
972 case 0: alloc_free(clients.s); return 0;
973 case -1: die_control();
974 }
975
976 if (ssl_timeoutrehandshake(timeout, ssl_rfd, ssl_wfd, ssl) <= 0) {
977 const char *err = ssl_error_str();
978 tls_out("rehandshake failed", err); die_read();
979 }
980
981 do { /* one iteration */
982 X509 *peercert;
983 X509_NAME *subj;
984 stralloc email = {0};
985
986 int n = SSL_get_verify_result(ssl);
987 if (n != X509_V_OK)
988 { ssl_verify_err = X509_verify_cert_error_string(n); break; }
989 peercert = SSL_get_peer_certificate(ssl);
990 if (!peercert) break;
991
992 subj = X509_get_subject_name(peercert);
993 n = X509_NAME_get_index_by_NID(subj, NID_pkcs9_emailAddress, -1);
994 if (n >= 0) {
995 const ASN1_STRING *s = X509_NAME_get_entry(subj, n)->value;
996 if (s) { email.len = s->length; email.s = s->data; }
997 }
998
999 if (email.len <= 0)
1000 ssl_verify_err = "contains no email address";
1001 else if (!constmap(&mapclients, email.s, email.len))
1002 ssl_verify_err = "email address not in my list of tlsclients";
1003 else {
1004 /* add the cert email to the proto if it helped allow relaying */
1005 --proto.len;
1006 if (!stralloc_cats(&proto, "\n (cert ") /* continuation line */
1007 || !stralloc_catb(&proto, email.s, email.len)
1008 || !stralloc_cats(&proto, ")")
1009 || !stralloc_0(&proto)) die_nomem();
1010 relayclient = "";
1011 protocol = proto.s;
1012 }
1013
1014 X509_free(peercert);
1015 } while (0);
1016 constmap_free(&mapclients); alloc_free(clients.s);
1017
1018 /* we are not going to need this anymore: free the memory */
1019 SSL_set_client_CA_list(ssl, NULL);
1020 SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
1021
1022 return relayclient ? 1 : 0;
1023}
1024
1025void tls_init()
1026{
1027 SSL *myssl;
1028 SSL_CTX *ctx;
1029 const char *ciphers;
1030 stralloc saciphers = {0};
1031 X509_STORE *store;
1032 X509_LOOKUP *lookup;
1033
1034 SSL_library_init();
1035
1036 /* a new SSL context with the bare minimum of options */
1037 ctx = SSL_CTX_new(SSLv23_server_method());
1038 if (!ctx) { tls_err("unable to initialize ctx"); return; }
1039
1040 if (!SSL_CTX_use_certificate_chain_file(ctx, SERVERCERT))
1041 { SSL_CTX_free(ctx); tls_err("missing certificate"); return; }
1042 SSL_CTX_load_verify_locations(ctx, CLIENTCA, NULL);
1043
1044#if OPENSSL_VERSION_NUMBER >= 0x00907000L
1045 /* crl checking */
1046 store = SSL_CTX_get_cert_store(ctx);
1047 if ((lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) &&
1048 (X509_load_crl_file(lookup, CLIENTCRL, X509_FILETYPE_PEM) == 1))
1049 X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
1050 X509_V_FLAG_CRL_CHECK_ALL);
1051#endif
1052
1053 /* set the callback here; SSL_set_verify didn't work before 0.9.6c */
1054 SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, verify_cb);
1055
1056 /* a new SSL object, with the rest added to it directly to avoid copying */
1057 myssl = SSL_new(ctx);
1058 SSL_CTX_free(ctx);
1059 if (!myssl) { tls_err("unable to initialize ssl"); return; }
1060
1061 /* this will also check whether public and private keys match */
1062 if (!SSL_use_RSAPrivateKey_file(myssl, SERVERCERT, SSL_FILETYPE_PEM))
1063 { SSL_free(myssl); tls_err("no valid RSA private key"); return; }
1064
1065 ciphers = env_get("TLSCIPHERS");
1066 if (!ciphers) {
1067 if (control_readfile(&saciphers, "control/tlsserverciphers", 0) == -1)
1068 { SSL_free(myssl); die_control(); }
1069 if (saciphers.len) { /* convert all '\0's except the last one to ':' */
1070 int i;
1071 for (i = 0; i < saciphers.len - 1; ++i)
1072 if (!saciphers.s[i]) saciphers.s[i] = ':';
1073 ciphers = saciphers.s;
1074 }
1075 }
1076 if (!ciphers || !*ciphers) ciphers = "DEFAULT";
1077 SSL_set_cipher_list(myssl, ciphers);
1078 alloc_free(saciphers.s);
1079
1080 SSL_set_tmp_rsa_callback(myssl, tmp_rsa_cb);
1081 SSL_set_tmp_dh_callback(myssl, tmp_dh_cb);
1082 SSL_set_rfd(myssl, ssl_rfd = substdio_fileno(&ssin));
1083 SSL_set_wfd(myssl, ssl_wfd = substdio_fileno(&ssout));
1084
1085 if (!smtps) { out("220 ready for tls\r\n"); flush(); }
1086
1087 if (ssl_timeoutaccept(timeout, ssl_rfd, ssl_wfd, myssl) <= 0) {
1088 /* neither cleartext nor any other response here is part of a standard */
1089 const char *err = ssl_error_str();
1090 ssl_free(myssl); tls_out("connection failed", err); die_read();
1091 }
1092 ssl = myssl;
1093
1094 /* populate the protocol string, used in Received */
1095 if (!stralloc_copys(&proto, "ESMTPS (")
1096 || !stralloc_cats(&proto, SSL_get_cipher(ssl))
1097 || !stralloc_cats(&proto, " encrypted)")) die_nomem();
1098 if (!stralloc_0(&proto)) die_nomem();
1099 protocol = proto.s;
1100
1101 /* have to discard the pre-STARTTLS HELO/EHLO argument, if any */
1102 dohelo(remotehost);
1103}
1104
1105# undef SERVERCERT
1106# undef CLIENTCA
1107
1108#endif
1109
397struct commands smtpcommands[] = { 1110struct commands smtpcommands[] = {
398 { "rcpt", smtp_rcpt, 0 } 1111 { "rcpt", smtp_rcpt, 0 }
399, { "mail", smtp_mail, 0 } 1112, { "mail", smtp_mail, 0 }
400, { "data", smtp_data, flush } 1113, { "data", smtp_data, flush }
1114, { "auth", smtp_auth, flush }
401, { "quit", smtp_quit, flush } 1115, { "quit", smtp_quit, flush }
402, { "helo", smtp_helo, flush } 1116, { "helo", smtp_helo, flush }
403, { "ehlo", smtp_ehlo, flush } 1117, { "ehlo", smtp_ehlo, flush }
404, { "rset", smtp_rset, 0 } 1118, { "rset", smtp_rset, 0 }
405, { "help", smtp_help, flush } 1119, { "help", smtp_help, flush }
1120#ifdef TLS
1121, { "starttls", smtp_tls, flush }
1122#endif
406, { "noop", err_noop, flush } 1123, { "noop", err_noop, flush }
407, { "vrfy", err_vrfy, flush } 1124, { "vrfy", err_vrfy, flush }
408, { 0, err_unimpl, flush } 1125, { 0, err_unimpl, flush }
409} ; 1126} ;
410 1127
411void main() 1128void main(argc,argv)
1129int argc;
1130char **argv;
412{ 1131{
1132 childargs = argv + 1;
413 sig_pipeignore(); 1133 sig_pipeignore();
414 if (chdir(auto_qmail) == -1) die_control(); 1134 if (chdir(auto_qmail) == -1) die_control();
415 setup(); 1135 setup();
diff --git a/qregex.c b/qregex.c
new file mode 100644
index 0000000..59c0f76
--- /dev/null
+++ b/qregex.c
@@ -0,0 +1,57 @@
1/*
2 * qregex (v2)
3 * $Id: qregex.c,v 2.1 2001/12/28 07:05:21 evan Exp $
4 *
5 * Author : Evan Borgstrom (evan at unixpimps dot org)
6 * Created : 2001/12/14 23:08:16
7 * Modified: $Date: 2001/12/28 07:05:21 $
8 * Revision: $Revision: 2.1 $
9 *
10 * Do POSIX regex matching on addresses for anti-relay / spam control.
11 * It logs to the maillog
12 * See the qregex-readme file included with this tarball.
13 * If you didn't get this file in a tarball please see the following URL:
14 * http://www.unixpimps.org/software/qregex
15 *
16 * qregex.c is released under a BSD style copyright.
17 * See http://www.unixpimps.org/software/qregex/copyright.html
18 *
19 * Note: this revision follows the coding guidelines set forth by the rest of
20 * the qmail code and that described at the following URL.
21 * http://cr.yp.to/qmail/guarantee.html
22 *
23 */
24
25#include <sys/types.h>
26#include <regex.h>
27#include "qregex.h"
28
29#define REGCOMP(X,Y) regcomp(&X, Y, REG_EXTENDED|REG_ICASE)
30#define REGEXEC(X,Y) regexec(&X, Y, (size_t)0, (regmatch_t *)0, (int)0)
31
32int matchregex(char *text, char *regex) {
33 regex_t qreg;
34 int retval = 0;
35
36
37 /* build the regex */
38 if ((retval = REGCOMP(qreg, regex)) != 0) {
39 regfree(&qreg);
40 return(-retval);
41 }
42
43 /* execute the regex */
44 if ((retval = REGEXEC(qreg, text)) != 0) {
45 /* did we just not match anything? */
46 if (retval == REG_NOMATCH) {
47 regfree(&qreg);
48 return(0);
49 }
50 regfree(&qreg);
51 return(-retval);
52 }
53
54 /* signal the match */
55 regfree(&qreg);
56 return(1);
57}
diff --git a/qregex.h b/qregex.h
new file mode 100644
index 0000000..e333a2d
--- /dev/null
+++ b/qregex.h
@@ -0,0 +1,5 @@
1/* simple header file for the matchregex prototype */
2#ifndef _QREGEX_H_
3#define _QREGEX_H_
4int matchregex(char *text, char *regex);
5#endif
diff --git a/ssl_timeoutio.c b/ssl_timeoutio.c
new file mode 100644
index 0000000..5b2dc9d
--- /dev/null
+++ b/ssl_timeoutio.c
@@ -0,0 +1,95 @@
1#include "select.h"
2#include "error.h"
3#include "ndelay.h"
4#include "now.h"
5#include "ssl_timeoutio.h"
6
7int ssl_timeoutio(int (*fun)(),
8 int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
9{
10 int n;
11 const datetime_sec end = (datetime_sec)t + now();
12
13 do {
14 fd_set fds;
15 struct timeval tv;
16
17 const int r = buf ? fun(ssl, buf, len) : fun(ssl);
18 if (r > 0) return r;
19
20 t = end - now();
21 if (t < 0) break;
22 tv.tv_sec = (time_t)t; tv.tv_usec = 0;
23
24 FD_ZERO(&fds);
25 switch (SSL_get_error(ssl, r))
26 {
27 default: return r; /* some other error */
28 case SSL_ERROR_WANT_READ:
29 FD_SET(rfd, &fds); n = select(rfd + 1, &fds, NULL, NULL, &tv);
30 break;
31 case SSL_ERROR_WANT_WRITE:
32 FD_SET(wfd, &fds); n = select(wfd + 1, NULL, &fds, NULL, &tv);
33 break;
34 }
35
36 /* n is the number of descriptors that changed status */
37 } while (n > 0);
38
39 if (n != -1) errno = error_timeout;
40 return -1;
41}
42
43int ssl_timeoutaccept(int t, int rfd, int wfd, SSL *ssl)
44{
45 int r;
46
47 /* if connection is established, keep NDELAY */
48 if (ndelay_on(rfd) == -1 || ndelay_on(wfd) == -1) return -1;
49 r = ssl_timeoutio(SSL_accept, t, rfd, wfd, ssl, NULL, 0);
50
51 if (r <= 0) { ndelay_off(rfd); ndelay_off(wfd); }
52 else SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
53
54 return r;
55}
56
57int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl)
58{
59 int r;
60
61 /* if connection is established, keep NDELAY */
62 if (ndelay_on(rfd) == -1 || ndelay_on(wfd) == -1) return -1;
63 r = ssl_timeoutio(SSL_connect, t, rfd, wfd, ssl, NULL, 0);
64
65 if (r <= 0) { ndelay_off(rfd); ndelay_off(wfd); }
66 else SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
67
68 return r;
69}
70
71int ssl_timeoutrehandshake(int t, int rfd, int wfd, SSL *ssl)
72{
73 int r;
74
75 SSL_renegotiate(ssl);
76 r = ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
77 if (r <= 0 || ssl->type == SSL_ST_CONNECT) return r;
78
79 /* this is for the server only */
80 ssl->state = SSL_ST_ACCEPT;
81 return ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
82}
83
84int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
85{
86 if (!buf) return 0;
87 if (SSL_pending(ssl)) return SSL_read(ssl, buf, len);
88 return ssl_timeoutio(SSL_read, t, rfd, wfd, ssl, buf, len);
89}
90
91int ssl_timeoutwrite(int t, int rfd, int wfd, SSL *ssl, char *buf, int len)
92{
93 if (!buf) return 0;
94 return ssl_timeoutio(SSL_write, t, rfd, wfd, ssl, buf, len);
95}
diff --git a/ssl_timeoutio.h b/ssl_timeoutio.h
new file mode 100644
index 0000000..073cb67
--- /dev/null
+++ b/ssl_timeoutio.h
@@ -0,0 +1,21 @@
1#ifndef SSL_TIMEOUTIO_H
2#define SSL_TIMEOUTIO_H
3
4#include <openssl/ssl.h>
5
6/* the version is like this: 0xMNNFFPPS: major minor fix patch status */
7#if OPENSSL_VERSION_NUMBER < 0x00906000L
8# error "Need OpenSSL version at least 0.9.6"
9#endif
10
11int ssl_timeoutconn(int t, int rfd, int wfd, SSL *ssl);
12int ssl_timeoutaccept(int t, int rfd, int wfd, SSL *ssl);
13int ssl_timeoutrehandshake(int t, int rfd, int wfd, SSL *ssl);
14
15int ssl_timeoutread(int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
16int ssl_timeoutwrite(int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
17
18int ssl_timeoutio(
19 int (*fun)(), int t, int rfd, int wfd, SSL *ssl, char *buf, int len);
20
21#endif
diff --git a/tls.c b/tls.c
new file mode 100644
index 0000000..aed5d57
--- /dev/null
+++ b/tls.c
@@ -0,0 +1,25 @@
1#include "exit.h"
2#include "error.h"
3#include <openssl/ssl.h>
4#include <openssl/err.h>
5
6int smtps = 0;
7SSL *ssl = NULL;
8
9void ssl_free(SSL *myssl) { SSL_shutdown(myssl); SSL_free(myssl); }
10void ssl_exit(int status) { if (ssl) ssl_free(ssl); _exit(status); }
11
12const char *ssl_error()
13{
14 int r = ERR_get_error();
15 if (!r) return NULL;
16 SSL_load_error_strings();
17 return ERR_error_string(r, NULL);
18}
19const char *ssl_error_str()
20{
21 const char *err = ssl_error();
22 if (err) return err;
23 if (!errno) return 0;
24 return (errno == error_timeout) ? "timed out" : error_str(errno);
25}
diff --git a/tls.h b/tls.h
new file mode 100644
index 0000000..a4650af
--- /dev/null
+++ b/tls.h
@@ -0,0 +1,16 @@
1#ifndef TLS_H
2#define TLS_H
3
4#include <openssl/ssl.h>
5
6extern int smtps;
7extern SSL *ssl;
8
9void ssl_free(SSL *myssl);
10void ssl_exit(int status);
11# define _exit ssl_exit
12
13const char *ssl_error();
14const char *ssl_error_str();
15
16#endif
diff --git a/update_tmprsadh.sh b/update_tmprsadh.sh
new file mode 100644
index 0000000..563fcfb
--- /dev/null
+++ b/update_tmprsadh.sh
@@ -0,0 +1,25 @@
1#!/bin/sh
2
3# Update temporary RSA and DH keys
4# Frederik Vermeulen 2004-05-31 GPL
5
6umask 0077 || exit 0
7
8export PATH="$PATH:/usr/local/bin/ssl:/usr/sbin"
9
10openssl genrsa -out QMAIL/control/rsa512.new 512 &&
11chmod 600 QMAIL/control/rsa512.new &&
12chown UGQMAILD QMAIL/control/rsa512.new &&
13mv -f QMAIL/control/rsa512.new QMAIL/control/rsa512.pem
14echo
15
16openssl dhparam -2 -out QMAIL/control/dh512.new 512 &&
17chmod 600 QMAIL/control/dh512.new &&
18chown UGQMAILD QMAIL/control/dh512.new &&
19mv -f QMAIL/control/dh512.new QMAIL/control/dh512.pem
20echo
21
22openssl dhparam -2 -out QMAIL/control/dh1024.new 1024 &&
23chmod 600 QMAIL/control/dh1024.new &&
24chown UGQMAILD QMAIL/control/dh1024.new &&
25mv -f QMAIL/control/dh1024.new QMAIL/control/dh1024.pem