diff -u --unidirectional-new-file netqmail-1.05-org/Makefile netqmail-1.05/Makefile --- netqmail-1.05-org/Makefile Tue Apr 20 10:28:41 2004 +++ netqmail-1.05/Makefile Tue Apr 20 11:26:45 2004 @@ -136,6 +136,10 @@ compile auto_usera.c ./compile auto_usera.c +base64.o: \ +compile base64.c base64.h stralloc.h substdio.h str.h + ./compile base64.c + binm1: \ binm1.sh conf-qmail cat binm1.sh \ @@ -1536,12 +1540,12 @@ timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \ date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \ open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \ -fs.a auto_qmail.o socket.lib +fs.a auto_qmail.o base64.o socket.lib ./load qmail-smtpd rcpthosts.o commands.o timeoutread.o \ timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o \ received.o date822fmt.o now.o qmail.o cdb.a fd.a wait.a \ datetime.a getln.a open.a sig.a case.a env.a stralloc.a \ - alloc.a substdio.a error.a str.a fs.a auto_qmail.o `cat \ + alloc.a substdio.a error.a str.a fs.a auto_qmail.o base64.o `cat \ socket.lib` qmail-smtpd.0: \ @@ -1553,7 +1557,8 @@ substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \ error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \ substdio.h str.h fmt.h scan.h byte.h case.h env.h now.h datetime.h \ -exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h +exit.h rcpthosts.h timeoutread.h timeoutwrite.h commands.h wait.h \ +fd.h base64.h ./compile qmail-smtpd.c qmail-start: \ diff -u --unidirectional-new-file netqmail-1.05-org/README.auth netqmail-1.05/README.auth --- netqmail-1.05-org/README.auth Thu Jan 1 09:00:00 1970 +++ netqmail-1.05/README.auth Tue Apr 20 11:26:30 2004 @@ -0,0 +1,175 @@ +*** Warning! Cuidado! Vorsicht! *** +=================================== +*** Version 0.30 of the patch changes the arguments which must be +*** passed to qmail-smtpd. If you are upgrading from a previous +*** version of the patch, take care to ensure your invocation of +*** qmail-smtpd uses the correct arguments. Otherwise, your server +*** may run as an open relay! +=================================== +*** Warning! Cuidado! Vorsicht! *** + + +This patch adds ESMTP AUTH authentication protocol support to +qmail-1.03. It's originally based on Mrs. Brisby's smtp-auth patch +with many enhancements from Krzysztof Dabrowski . + +Beginning with version 0.30, the patch was completely rewritten to +use only djb's string functions by Eric M. Johnston . + +You can always get the newest version from: +http://members.elysium.pl/brush/qmail-smtpd-auth/ + +To use all of it's functionality you will also have to obtain and +install Krzysztof's cmd5checkpw utility available at: +http://members.elysium.pl/brush/cmd5checkpw/ + +If you need more information about SMTP-AUTH itself and the +client/server support and configuration, visit: +http://members.elysium.pl/brush/smtp-auth/ + +--- + +Detailed patch information: + +This patch adds the ESMTP AUTH option to qmail-1.03, allowing the +LOGIN, PLAIN, and CRAM-MD5 AUTH types. An appropriate checkpassword +tool is necessary to support the authentication. See +http://cr.yp.to/checkpwd.html for more information on the interface. +Note that the checkpassword tool should support all of the AUTH types +advertised by qmail-smtpd. + +As reflected in the modified qmail-smtpd(8) man page, qmail-smtpd +must be invoked with three arguments: hostname, checkprogram, and +subprogram. If these arguments are missing, qmail-smtpd will still +advertise availability of AUTH, but will fail with a permanent error +when AUTH is used. + +hostname is simply used to form the CRAM-MD5 challenge. qmail-smtpd +invokes checkprogram, feeding it the username and password, in the +case of LOGIN or PLAIN, or the username, challenge, and response, in +the case of CRAM-MD5. If the user is permitted, checkprogram invokes +subprogram, which just has to exit with a status of 0 for the user to +be authenticated. Otherwise, checkprogram exits with a non-zero +status. subprogram can usually be /usr/bin/true (or /bin/true, +depending on your flavor of OS). + +If the user is successfully authenticated, the RELAYCLIENT +environment variable is effectively set for the SMTP session, and +the TCPREMOTEINFO environment variable is set to the authenticated +username, overriding any value that tcpserver may have set. The +value of TCPREMOTEINFO is reflected in a Received header. + + +How to install it: + +Simply patch your qmail-1.03 distribution with the included patch +file and recompile & install like usual. + +The steps to do this are as follows (assuming your virgin +qmail-1.03 install is in "../qmail-1.03"): + + cp README.auth base64.c base64.h ../qmail-1.03 + patch -d ../qmail-1.03 < auth.patch + +Install qmail normally, with the exception of the new arguments +to qmail-smtpd described elsewhere in this file. + +Also obtain, unpack, compile and install the cmd5checkpw utility +(or some other checkpassword utility) and add a sample account to +/etc/poppasswd file. This file must be readable by the qmail-smtpd +user, usually qmaild. + + +How to use it: + +*** Warning: In version 0.30 the arguments have changed from +*** previous versions of qmail-smtpd-auth. Take care to make sure +*** you update your startup scripts if updating! + +If you're running qmail-smtpd from inetd, you'll want to do the +following: + +smtp stream tcp nowait qmaild /var/qmail/bin/tcp-env tcp-env \ +/var/qmail/bin/qmail-smtpd mail.acme.com /bin/cmd5checkpw /bin/true + +Replace mail.acme.com with your hostname. The second argument to +qmail-smtpd is your checkpassword utility (preferably cmd5checkpw +or some alternative that can handle CRAM-MD5). The third argument +is the executable that the checkpassword utility execs when +authentication is successful. (Note that the location of "true" +is OS dependent: you may need /usr/bin/true.) + +Invocations using tcpserver will require analagous changes. Give +your inetd a kill -HUP or restart tcpserver and away you go. + + +Caveats: + +Please note that as authentication needs vary wildly across +installations, no effort has been made to make this patch work ``out +of the box.'' You'll have to procure or develop your own +checkpassword program. Also note that CRAM-MD5 will require you to +keep plaintext passwords. You'll probably want to disable this AUTH +type if you're just using /etc/passwd (keeping in mind that PLAIN and +LOGIN aren't quite as safe over the wire) -- just undefine AUTHCRAM +in qmail-smtpd. + +Krzysztof Dabrowski's cmd5checkpw tool used as an example in this +document supports the three AUTH types included in this patch. +It's available at http://www.elysium.pl/members/brush/cmd5checkpw/. + +This patch has been generated against the stock qmail 1.03 +distribution. The results of combining this patch with others are +unknown. + + +Features: + +This patch supports the following auth methods: LOGIN, PLAIN and +CRAM-MD5. + + +Compatibility: + +The following MUA's are confirmed to work with this patch: + +Eudora 4.2.2 - CRAM-MD5 +Eudora 5.0.2 - CRAM-MD5 +The Bat 1.39 - LOGIN & CRAM-MD5 +Outlook Express 4 - LOGIN +Outlook Express 5 - LOGIN +Outlook 2000 - LOGIN +Netscape 4.x - LOGIN & PLAIN +Netscape 4.0x - LOGIN +Pegasus Mail 3.1x - CRAM-MD5 + + +Various compatibility issues: + +Testing with Pegasus Mail 3.1 revealed that it requires the new style +(RFC recommended) greeting message. Both styles are now enabled to +maintain the highest degree of compatibility with various clients. +This fix was suggested by David Harris , +the developer of Pegasus Mail. + + +Acknowledgments: + +This patch is based on work by Krzysztof Dabrowski at +http://members.elysium.pl/brush/qmail-smtpd-auth/ and ``Mrs. Brisby'' +at http://www.nimh.org/hacks/qmail-smtpd.c which has been further +developed by Eric M. Johnston . + +--- + +THIS SOFTWARE IS IN THE PUBLIC DOMAIN, IS PROVIDED BY THE AUTHOR +``AS IS,'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff -u --unidirectional-new-file netqmail-1.05-org/TARGETS netqmail-1.05/TARGETS --- netqmail-1.05-org/TARGETS Mon Jun 15 19:53:16 1998 +++ netqmail-1.05/TARGETS Tue Apr 20 11:26:45 2004 @@ -250,6 +250,7 @@ qmail-qmtpd.o rcpthosts.o qmail-qmtpd +base64.o qmail-smtpd.o qmail-smtpd sendmail.o diff -u --unidirectional-new-file netqmail-1.05-org/base64.c netqmail-1.05/base64.c --- netqmail-1.05-org/base64.c Thu Jan 1 09:00:00 1970 +++ netqmail-1.05/base64.c Tue Apr 20 11:26:30 2004 @@ -0,0 +1,90 @@ +#include "base64.h" +#include "stralloc.h" +#include "substdio.h" +#include "str.h" + +static char *b64alpha = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +#define B64PAD '=' + +/* returns 0 ok, 1 illegal, -1 problem */ + +int b64decode(in,l,out) +const unsigned char *in; +int l; +stralloc *out; /* not null terminated */ +{ + int i, j; + unsigned char a[4]; + unsigned char b[3]; + char *s; + + if (l == 0) + { + if (!stralloc_copys(out,"")) return -1; + return 0; + } + + if (!stralloc_ready(out,l + 2)) return -1; /* XXX generous */ + s = out->s; + + for (i = 0;i < l;i += 4) { + for (j = 0;j < 4;j++) + if ((i + j) < l && in[i + j] != B64PAD) + { + a[j] = str_chr(b64alpha,in[i + j]); + if (a[j] > 63) return 1; + } + else a[j] = 0; + + b[0] = (a[0] << 2) | (a[1] >> 4); + b[1] = (a[1] << 4) | (a[2] >> 2); + b[2] = (a[2] << 6) | (a[3]); + + *s++ = b[0]; + + if (in[i + 1] == B64PAD) break; + *s++ = b[1]; + + if (in[i + 2] == B64PAD) break; + *s++ = b[2]; + } + out->len = s - out->s; + while (out->len && !out->s[out->len - 1]) --out->len; /* XXX avoid? */ + return 0; +} + +int b64encode(in,out) +stralloc *in; +stralloc *out; /* not null terminated */ +{ + unsigned char a, b, c; + int i; + char *s; + + if (in->len == 0) + { + if (!stralloc_copys(out,"")) return -1; + return 0; + } + + if (!stralloc_ready(out,in->len / 3 * 4 + 4)) return -1; + s = out->s; + + for (i = 0;i < in->len;i += 3) { + a = in->s[i]; + b = i + 1 < in->len ? in->s[i + 1] : 0; + c = i + 2 < in->len ? in->s[i + 2] : 0; + + *s++ = b64alpha[a >> 2]; + *s++ = b64alpha[((a & 3 ) << 4) | (b >> 4)]; + + if (i + 1 >= in->len) *s++ = B64PAD; + else *s++ = b64alpha[((b & 15) << 2) | (c >> 6)]; + + if (i + 2 >= in->len) *s++ = B64PAD; + else *s++ = b64alpha[c & 63]; + } + out->len = s - out->s; + return 0; +} diff -u --unidirectional-new-file netqmail-1.05-org/base64.h netqmail-1.05/base64.h --- netqmail-1.05-org/base64.h Thu Jan 1 09:00:00 1970 +++ netqmail-1.05/base64.h Tue Apr 20 11:26:30 2004 @@ -0,0 +1,7 @@ +#ifndef BASE64_H +#define BASE64_H + +extern int b64decode(); +extern int b64encode(); + +#endif diff -u --unidirectional-new-file netqmail-1.05-org/qmail-smtpd.8 netqmail-1.05/qmail-smtpd.8 --- netqmail-1.05-org/qmail-smtpd.8 Mon Jun 15 19:53:16 1998 +++ netqmail-1.05/qmail-smtpd.8 Tue Apr 20 11:26:45 2004 @@ -3,6 +3,11 @@ qmail-smtpd \- receive mail via SMTP .SH SYNOPSIS .B qmail-smtpd +[ +.I hostname +.I checkprogram +.I subprogram +] .SH DESCRIPTION .B qmail-smtpd receives mail messages via the Simple Mail Transfer Protocol (SMTP) @@ -23,7 +28,29 @@ header fields. .B qmail-smtpd -supports ESMTP, including the 8BITMIME and PIPELINING options. +supports ESMTP, including the 8BITMIME, PIPELINING, and AUTH options. + +.B qmail-smtpd +can accept LOGIN, PLAIN, and CRAM-MD5 AUTH types. It invokes +.IR checkprogram , +which reads on file descriptor 3 the username, a 0 byte, the password +or challenge derived from +.IR hostname , +another 0 byte, a CRAM-MD5 response (if applicable to the AUTH type), +and a final 0 byte. +.I checkprogram +invokes +.I subprogram +upon successful authentication, which should in turn return 0 to +.BR qmail-smtpd , +effectively setting the environment variables RELAYCLIENT and TCPREMOTEINFO +(any supplied value replaced with the authenticated username). +.B qmail-smtpd +will reject the authentication attempt if it receives a nonzero return +value from +.I checkprogram +or +.IR subprogram . .SH TRANSPARENCY .B qmail-smtpd converts the SMTP newline convention into the UNIX newline convention @@ -177,3 +204,6 @@ qmail-newmrh(8), qmail-queue(8), qmail-remote(8) +.SH "HISTORY" +The patch enabling the ESMTP AUTH option is not part of the standard +qmail-1.03 distribution. diff -u --unidirectional-new-file netqmail-1.05-org/qmail-smtpd.c netqmail-1.05/qmail-smtpd.c --- netqmail-1.05-org/qmail-smtpd.c Tue Apr 20 10:28:41 2004 +++ netqmail-1.05/qmail-smtpd.c Tue Apr 20 11:26:45 2004 @@ -23,7 +23,10 @@ #include "timeoutread.h" #include "timeoutwrite.h" #include "commands.h" +#include "wait.h" +#include "fd.h" +#define AUTHCRAM #define MAXHOPS 100 unsigned int databytes = 0; int timeout = 1200; @@ -59,6 +62,15 @@ void err_vrfy(arg) char *arg; { out("252 send some mail, i'll try my best\r\n"); } void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); } +int err_child() { out("454 oops, problem with child and I can't auth (#4.3.0)\r\n"); return -1; } +int err_fork() { out("454 oops, child won't start and I can't auth (#4.3.0)\r\n"); return -1; } +int err_pipe() { out("454 oops, unable to open pipe and I can't auth (#4.3.0)\r\n"); return -1; } +int err_write() { out("454 oops, unable to write pipe and I can't auth (#4.3.0)\r\n"); return -1; } +void err_authd() { out("503 you're already authenticated (#5.5.0)\r\n"); } +void err_authmail() { out("503 no auth during mail transaction (#5.5.0)\r\n"); } +int err_noauth() { out("504 auth type unimplemented (#5.5.1)\r\n"); return -1; } +int err_authabrt() { out("501 auth exchange cancelled (#5.0.0)\r\n"); return -1; } +int err_input() { out("501 malformed auth input (#5.5.4)\r\n"); return -1; } stralloc greeting = {0}; @@ -229,7 +241,15 @@ } void smtp_ehlo(arg) char *arg; { - smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n"); + smtp_greet("250-"); +#ifdef AUTHCRAM + out("\r\n250-AUTH LOGIN CRAM-MD5 PLAIN"); + out("\r\n250-AUTH=LOGIN CRAM-MD5 PLAIN"); +#else + out("\r\n250-AUTH LOGIN PLAIN"); + out("\r\n250-AUTH=LOGIN PLAIN"); +#endif + out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n"); seenmail = 0; dohelo(arg); } void smtp_rset(arg) char *arg; @@ -394,10 +414,226 @@ out("\r\n"); } + +char unique[FMT_ULONG + FMT_ULONG + 3]; +static stralloc authin = {0}; +static stralloc user = {0}; +static stralloc pass = {0}; +static stralloc resp = {0}; +static stralloc slop = {0}; +char *hostname; +char **childargs; +substdio ssup; +char upbuf[128]; +int authd = 0; + +int authgetl(void) { + int i; + + if (!stralloc_copys(&authin, "")) die_nomem(); + + for (;;) { + if (!stralloc_readyplus(&authin,1)) die_nomem(); /* XXX */ + i = substdio_get(&ssin,authin.s + authin.len,1); + if (i != 1) die_read(); + if (authin.s[authin.len] == '\n') break; + ++authin.len; + } + + if (authin.len > 0) if (authin.s[authin.len - 1] == '\r') --authin.len; + authin.s[authin.len] = 0; + + if (*authin.s == '*' && *(authin.s + 1) == 0) { return err_authabrt(); } + if (authin.len == 0) { return err_input(); } + return authin.len; +} + +int authenticate(void) +{ + int child; + int wstat; + int pi[2]; + + if (!stralloc_0(&user)) die_nomem(); + if (!stralloc_0(&pass)) die_nomem(); + if (!stralloc_0(&resp)) die_nomem(); + + if (fd_copy(2,1) == -1) return err_pipe(); + close(3); + if (pipe(pi) == -1) return err_pipe(); + if (pi[0] != 3) return err_pipe(); + switch(child = fork()) { + case -1: + return err_fork(); + case 0: + close(pi[1]); + sig_pipedefault(); + execvp(*childargs, childargs); + _exit(1); + } + close(pi[0]); + + substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof upbuf); + if (substdio_put(&ssup,user.s,user.len) == -1) return err_write(); + if (substdio_put(&ssup,pass.s,pass.len) == -1) return err_write(); + if (substdio_put(&ssup,resp.s,resp.len) == -1) return err_write(); + if (substdio_flush(&ssup) == -1) return err_write(); + + close(pi[1]); + byte_zero(pass.s,pass.len); + byte_zero(upbuf,sizeof upbuf); + if (wait_pid(&wstat,child) == -1) return err_child(); + if (wait_crashed(wstat)) return err_child(); + if (wait_exitcode(wstat)) { sleep(5); return 1; } /* no */ + return 0; /* yes */ +} + +int auth_login(arg) char *arg; +{ + int r; + + if (*arg) { + if (r = b64decode(arg,str_len(arg),&user) == 1) return err_input(); + } + else { + out("334 VXNlcm5hbWU6\r\n"); flush(); /* Username: */ + if (authgetl() < 0) return -1; + if (r = b64decode(authin.s,authin.len,&user) == 1) return err_input(); + } + if (r == -1) die_nomem(); + + out("334 UGFzc3dvcmQ6\r\n"); flush(); /* Password: */ + + if (authgetl() < 0) return -1; + if (r = b64decode(authin.s,authin.len,&pass) == 1) return err_input(); + if (r == -1) die_nomem(); + + if (!user.len || !pass.len) return err_input(); + return authenticate(); +} + +int auth_plain(arg) char *arg; +{ + int r, id = 0; + + if (*arg) { + if (r = b64decode(arg,str_len(arg),&slop) == 1) return err_input(); + } + else { + out("334 \r\n"); flush(); + if (authgetl() < 0) return -1; + if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input(); + } + if (r == -1 || !stralloc_0(&slop)) die_nomem(); + while (slop.s[id]) id++; /* ignore authorize-id */ + + if (slop.len > id + 1) + if (!stralloc_copys(&user,slop.s + id + 1)) die_nomem(); + if (slop.len > id + user.len + 2) + if (!stralloc_copys(&pass,slop.s + id + user.len + 2)) die_nomem(); + + if (!user.len || !pass.len) return err_input(); + return authenticate(); +} + +#ifdef AUTHCRAM +int auth_cram() +{ + int i, r; + char *s; + + s = unique; + s += fmt_uint(s,getpid()); + *s++ = '.'; + s += fmt_ulong(s,(unsigned long) now()); + *s++ = '@'; + *s++ = 0; + + if (!stralloc_copys(&pass,"<")) die_nomem(); + if (!stralloc_cats(&pass,unique)) die_nomem(); + if (!stralloc_cats(&pass,hostname)) die_nomem(); + if (!stralloc_cats(&pass,">")) die_nomem(); + if (b64encode(&pass,&slop) < 0) die_nomem(); + if (!stralloc_0(&slop)) die_nomem(); + + out("334 "); + out(slop.s); + out("\r\n"); + flush(); + + if (authgetl() < 0) return -1; + if (r = b64decode(authin.s,authin.len,&slop) == 1) return err_input(); + if (r == -1 || !stralloc_0(&slop)) die_nomem(); + + i = str_chr(slop.s,' '); + s = slop.s + i; + while (*s == ' ') ++s; + slop.s[i] = 0; + if (!stralloc_copys(&user,slop.s)) die_nomem(); + if (!stralloc_copys(&resp,s)) die_nomem(); + + if (!user.len || !resp.len) return err_input(); + return authenticate(); +} +#endif + +struct authcmd { + char *text; + int (*fun)(); +} authcmds[] = { + { "login", auth_login } +, { "plain", auth_plain } +#ifdef AUTHCRAM +, { "cram-md5", auth_cram } +#endif +, { 0, err_noauth } +}; + +void smtp_auth(arg) +char *arg; +{ + int i; + char *cmd = arg; + + if (!hostname || !*childargs) + { + out("503 auth not available (#5.3.3)\r\n"); + return; + } + if (authd) { err_authd(); return; } + if (seenmail) { err_authmail(); return; } + + if (!stralloc_copys(&user,"")) die_nomem(); + if (!stralloc_copys(&pass,"")) die_nomem(); + if (!stralloc_copys(&resp,"")) die_nomem(); + + i = str_chr(cmd,' '); + arg = cmd + i; + while (*arg == ' ') ++arg; + cmd[i] = 0; + + for (i = 0;authcmds[i].text;++i) + if (case_equals(authcmds[i].text,cmd)) break; + + switch (authcmds[i].fun(arg)) { + case 0: + authd = 1; + relayclient = ""; + remoteinfo = user.s; + if (!env_unset("TCPREMOTEINFO")) die_read(); + if (!env_put2("TCPREMOTEINFO",remoteinfo)) die_nomem(); + out("235 ok, go ahead (#2.0.0)\r\n"); + break; + case 1: + out("535 authorization failed (#5.7.0)\r\n"); + } +} + struct commands smtpcommands[] = { { "rcpt", smtp_rcpt, 0 } , { "mail", smtp_mail, 0 } , { "data", smtp_data, flush } +, { "auth", smtp_auth, flush } , { "quit", smtp_quit, flush } , { "helo", smtp_helo, flush } , { "ehlo", smtp_ehlo, flush } @@ -408,8 +644,13 @@ , { 0, err_unimpl, flush } } ; -void main() +void main(argc,argv) +int argc; +char **argv; { + hostname = argv[1]; + childargs = argv + 2; + sig_pipeignore(); if (chdir(auto_qmail) == -1) die_control(); setup();