summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile11
-rw-r--r--TARGETS1
-rw-r--r--base64.c90
-rw-r--r--base64.h7
-rw-r--r--qmail-smtpd.832
-rw-r--r--qmail-smtpd.c265
6 files changed, 390 insertions, 16 deletions
diff --git a/Makefile b/Makefile
index 0f0e31a..65e9e9c 100644
--- a/Makefile
+++ b/Makefile
@@ -136,6 +136,10 @@ auto_usera.o: \
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 @@ load qmail-smtpd.o rcpthosts.o commands.o timeoutread.o \
timeoutwrite.o ip.o ipme.o ipalloc.o control.o constmap.o received.o \
date822fmt.o now.o qmail.o cdb.a fd.a wait.a datetime.a getln.a \
open.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a str.a \
-fs.a auto_qmail.o 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 @@ compile qmail-smtpd.c sig.h readwrite.h stralloc.h gen_alloc.h \
substdio.h alloc.h auto_qmail.h control.h received.h constmap.h \
error.h ipme.h ip.h ipalloc.h ip.h gen_alloc.h ip.h qmail.h \
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 --git a/TARGETS b/TARGETS
index facdad7..75ec741 100644
--- a/TARGETS
+++ b/TARGETS
@@ -250,6 +250,7 @@ qmail-qmqpd
qmail-qmtpd.o
rcpthosts.o
qmail-qmtpd
+base64.o
qmail-smtpd.o
qmail-smtpd
sendmail.o
diff --git a/base64.c b/base64.c
new file mode 100644
index 0000000..fbce145
--- /dev/null
+++ b/base64.c
@@ -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 --git a/base64.h b/base64.h
new file mode 100644
index 0000000..a9164c0
--- /dev/null
+++ b/base64.h
@@ -0,0 +1,7 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+extern int b64decode();
+extern int b64encode();
+
+#endif
diff --git a/qmail-smtpd.8 b/qmail-smtpd.8
index c4640b8..3e6cce2 100644
--- a/qmail-smtpd.8
+++ b/qmail-smtpd.8
@@ -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 @@ or
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-inject(8),
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 --git a/qmail-smtpd.c b/qmail-smtpd.c
index 54df00c..7390eb4 100644
--- a/qmail-smtpd.c
+++ b/qmail-smtpd.c
@@ -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_noop(arg) char *arg; { out("250 ok\r\n"); }
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};
@@ -86,8 +98,8 @@ stralloc helohost = {0};
char *fakehelo; /* pointer into helohost, or 0 */
void dohelo(arg) char *arg; {
- if (!stralloc_copys(&helohost,arg)) die_nomem();
- if (!stralloc_0(&helohost)) die_nomem();
+ if (!stralloc_copys(&helohost,arg)) die_nomem();
+ if (!stralloc_0(&helohost)) die_nomem();
fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;
}
@@ -101,7 +113,7 @@ void setup()
{
char *x;
unsigned long u;
-
+
if (control_init() == -1) die_control();
if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1)
die_control();
@@ -116,12 +128,12 @@ void setup()
if (bmfok == -1) die_control();
if (bmfok)
if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem();
-
+
if (control_readint(&databytes,"control/databytes") == -1) die_control();
x = env_get("DATABYTES");
if (x) { scan_ulong(x,&u); databytes = u; }
if (!(databytes + 1)) --databytes;
-
+
remoteip = env_get("TCPREMOTEIP");
if (!remoteip) remoteip = "unknown";
local = env_get("TCPLOCALHOST");
@@ -146,7 +158,7 @@ char *arg;
struct ip_address ip;
int flagesc;
int flagquoted;
-
+
terminator = '>';
i = str_chr(arg,'<');
if (arg[i])
@@ -229,7 +241,15 @@ void smtp_helo(arg) char *arg;
}
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;
@@ -300,7 +320,7 @@ int *hops;
int flagmaybex; /* 1 if this line might match RECEIVED, if fih */
int flagmaybey; /* 1 if this line might match \r\n, if fih */
int flagmaybez; /* 1 if this line might match DELIVERED, if fih */
-
+
state = 1;
*hops = 0;
flaginheader = 1;
@@ -369,7 +389,7 @@ void smtp_data(arg) char *arg; {
int hops;
unsigned long qp;
char *qqx;
-
+
if (!seenmail) { err_wantmail(); return; }
if (!rcptto.len) { err_wantrcpt(); return; }
seenmail = 0;
@@ -377,14 +397,14 @@ void smtp_data(arg) char *arg; {
if (qmail_open(&qqt) == -1) { err_qqt(); return; }
qp = qmail_qp(&qqt);
out("354 go ahead\r\n");
-
+
received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
blast(&hops);
hops = (hops >= MAXHOPS);
if (hops) qmail_fail(&qqt);
qmail_from(&qqt,mailfrom.s);
qmail_put(&qqt,rcptto.s,rcptto.len);
-
+
qqx = qmail_close(&qqt);
if (!*qqx) { acceptmessage(qp); return; }
if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
@@ -394,10 +414,226 @@ void smtp_data(arg) char *arg; {
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 @@ struct commands smtpcommands[] = {
, { 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();