diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-03-12 17:15:03 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-03-12 17:15:03 +0100 |
commit | 963cfc9a6f6e721f52aa949e6d1af0c3e8dc2ecc (patch) | |
tree | 749b74875dbc49bcf6ed0776648b8f0ef9417407 /sys/src/cmd/upas/smtp | |
parent | 8177d20fb2709ba9290dfd41308b8e5bee4e00f8 (diff) |
merging erik quanstros nupas
Diffstat (limited to 'sys/src/cmd/upas/smtp')
-rw-r--r-- | sys/src/cmd/upas/smtp/greylist.c | 2 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/mkfile | 30 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/mxdial.c | 534 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/parsetest.c | 86 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/rfc822.y | 82 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/rmtdns.c | 59 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/smtp.c | 292 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/smtp.h | 33 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/smtpd.c | 886 | ||||
-rw-r--r-- | sys/src/cmd/upas/smtp/spam.c | 59 |
10 files changed, 1119 insertions, 944 deletions
diff --git a/sys/src/cmd/upas/smtp/greylist.c b/sys/src/cmd/upas/smtp/greylist.c index b5cee5a6e..3bbe9f586 100644 --- a/sys/src/cmd/upas/smtp/greylist.c +++ b/sys/src/cmd/upas/smtp/greylist.c @@ -53,7 +53,7 @@ onwhitelist(void) wl = Bopen(whitelist, OREAD); if (wl == nil) - return 1; + return 0; while ((line = Brdline(wl, '\n')) != nil) { lnlen = Blinelen(wl); line[lnlen-1] = '\0'; /* clobber newline */ diff --git a/sys/src/cmd/upas/smtp/mkfile b/sys/src/cmd/upas/smtp/mkfile index 10b312b90..9f02f8ae2 100644 --- a/sys/src/cmd/upas/smtp/mkfile +++ b/sys/src/cmd/upas/smtp/mkfile @@ -1,8 +1,12 @@ </$objtype/mkfile +<../mkupas TARG = smtpd\ smtp\ +TEST=\ + parsetest + OFILES= LIB=../common/libcommon.a$O\ @@ -12,13 +16,11 @@ HFILES=../common/common.h\ smtpd.h\ smtp.h\ -BIN=/$objtype/bin/upas UPDATE=\ greylist.c\ mkfile\ mxdial.c\ rfc822.y\ - rmtdns.c\ smtpd.y\ spam.c\ $HFILES\ @@ -26,24 +28,30 @@ UPDATE=\ ${TARG:%=%.c}\ </sys/src/cmd/mkmany -CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"' +CFLAGS=$CFLAGS -I../common + +$O.smtpd:\ + smtpd.tab.$O\ + spam.$O\ + rfc822.tab.$O\ + greylist.$O\ -$O.smtpd: smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O $O.smtp: rfc822.tab.$O mxdial.$O -smtpd.tab.c: smtpd.y +smtpd.tab.c: smtpd.y yacc -o xxx smtpd.y sed 's/yy/zz/g' < xxx > $target rm xxx -rfc822.tab.c: rfc822.y +rfc822.tab.c: rfc822.y yacc -d -o $target rfc822.y +$O.parsetest: rfc822.tab.$O + +parsetest.$O: rfc822.tab.$O + clean:V: - rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG + rm -f *.[$OS] [$OS].^($TARG $TEST) smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG ../common/libcommon.a$O: - @{ - cd ../common - mk - } + cd ../common && mk diff --git a/sys/src/cmd/upas/smtp/mxdial.c b/sys/src/cmd/upas/smtp/mxdial.c index c17e280c3..74516a030 100644 --- a/sys/src/cmd/upas/smtp/mxdial.c +++ b/sys/src/cmd/upas/smtp/mxdial.c @@ -1,60 +1,96 @@ #include "common.h" +#include "smtp.h" #include <ndb.h> -#include <smtp.h> /* to publish dial_string_parse */ - -enum -{ - Nmx= 16, - Maxstring= 256, -}; - -typedef struct Mx Mx; -struct Mx -{ - char host[256]; - char ip[24]; - int pref; -}; char *bustedmxs[Maxbustedmx]; -Ndb *db; - -static int mxlookup(DS*, char*); -static int mxlookup1(DS*, char*); -static int compar(void*, void*); -static int callmx(DS*, char*, char*); -static void expand_meta(DS *ds); - -static Mx mx[Nmx]; -int -mxdial(char *addr, char *ddomain, char *gdomain) +static void +expand(DS *ds) { - int fd; - DS ds; - - addr = netmkaddr(addr, 0, "smtp"); - dial_string_parse(addr, &ds); + char *s; + Ndbtuple *t; + + s = ds->host + 1; + t = csipinfo(ds->netdir, "sys", sysname(), &s, 1); + if(t != nil){ + strecpy(ds->expand, ds->expand+sizeof ds->expand, t->val); + ds->host = ds->expand; + } + ndbfree(t); +} - /* try connecting to destination or any of it's mail routers */ - fd = callmx(&ds, addr, ddomain); +/* break up an address to its component parts */ +void +dialstringparse(char *str, DS *ds) +{ + char *p, *p2; - /* try our mail gateway */ - if(fd < 0 && gdomain) - fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); + strecpy(ds->buf, ds->buf + sizeof ds->buf, str); + p = strchr(ds->buf, '!'); + if(p == 0) { + ds->netdir = 0; + ds->proto = "net"; + ds->host = ds->buf; + } else { + if(*ds->buf != '/'){ + ds->netdir = 0; + ds->proto = ds->buf; + } else { + for(p2 = p; *p2 != '/'; p2--) + ; + *p2++ = 0; + ds->netdir = ds->buf; + ds->proto = p2; + } + *p = 0; + ds->host = p + 1; + } + ds->service = strchr(ds->host, '!'); + if(ds->service) + *ds->service++ = 0; + if(*ds->host == '$') + expand(ds); +} - return fd; +void +mxtabfree(Mxtab *mx) +{ + free(mx->mx); + memset(mx, 0, sizeof *mx); } -static int -busted(char *mx) +static void +mxtabrealloc(Mxtab *mx) { - char **bmp; + if(mx->nmx < mx->amx) + return; + if(mx->amx == 0) + mx->amx = 1; + mx->amx <<= 1; + mx->mx = realloc(mx->mx, sizeof mx->mx[0] * mx->amx); + if(mx->mx == nil) + sysfatal("no memory for mx"); +} - for (bmp = bustedmxs; *bmp != nil; bmp++) - if (strcmp(mx, *bmp) == 0) - return 1; - return 0; +static void +mxtabadd(Mxtab *mx, char *host, char *ip, char *net, int pref) +{ + int i; + Mx *x; + + mxtabrealloc(mx); + x = mx->mx; + for(i = mx->nmx; i>0 && x[i-1].pref>pref && x[i-1].netdir == net; i--) + x[i] = x[i-1]; + strecpy(x[i].host, x[i].host + sizeof x[i].host, host); + if(ip != nil) + strecpy(x[i].ip, x[i].ip + sizeof x[i].ip, ip); + else + x[i].ip[0] = 0; + x[i].netdir = net; + x[i].pref = pref; + x[i].valid = 1; + mx->nmx++; } static int @@ -65,293 +101,235 @@ timeout(void*, char *msg) return 0; } -long +static long timedwrite(int fd, void *buf, long len, long ms) { long n, oalarm; atnotify(timeout, 1); oalarm = alarm(ms); - n = write(fd, buf, len); + n = pwrite(fd, buf, len, 0); alarm(oalarm); atnotify(timeout, 0); return n; } -/* - * take an address and return all the mx entries for it, - * most preferred first - */ static int -callmx(DS *ds, char *dest, char *domain) +dnslookup(Mxtab *mx, int fd, char *query, char *domain, char *net, int pref0) { - int fd, i, nmx; - char addr[Maxstring]; - - /* get a list of mx entries */ - nmx = mxlookup(ds, domain); - if(nmx < 0){ - /* dns isn't working, don't just dial */ - return -1; - } - if(nmx == 0){ - if(debug) - fprint(2, "mxlookup returns nothing\n"); - return dial(dest, 0, 0, 0); - } + int n; + char buf[1024], *f[4]; - /* refuse to honor loopback addresses given by dns */ - for(i = 0; i < nmx; i++) - if(strcmp(mx[i].ip, "127.0.0.1") == 0){ - if(debug) - fprint(2, "mxlookup returns loopback\n"); - werrstr("illegal: domain lists 127.0.0.1 as mail server"); + n = timedwrite(fd, query, strlen(query), 60*1000); + if(n < 0){ + rerrstr(buf, sizeof buf); + dprint("dns: %s\n", buf); + if(strstr(buf, "dns failure")){ + /* if dns fails for the mx lookup, we have to stop */ + close(fd); return -1; } + return 0; + } - /* sort by preference */ - if(nmx > 1) - qsort(mx, nmx, sizeof(Mx), compar); - - /* dial each one in turn */ - for(i = 0; i < nmx; i++){ - if (busted(mx[i].host)) { - if (debug) - fprint(2, "mxdial skipping busted mx %s\n", - mx[i].host); + seek(fd, 0, 0); + for(;;){ + if((n = read(fd, buf, sizeof buf - 1)) < 1) + break; + buf[n] = 0; + // chat("dns: %s\n", buf); + n = tokenize(buf, f, nelem(f)); + if(n < 2) continue; + if(strcmp(f[1], "mx") == 0 && n == 4){ + if(strchr(domain, '.') == 0) + strcpy(domain, f[0]); + mxtabadd(mx, f[3], nil, net, atoi(f[2])); + } + else if (strcmp(f[1], "ip") == 0 && n == 3){ + if(strchr(domain, '.') == 0) + strcpy(domain, f[0]); + mxtabadd(mx, f[0], f[2], net, pref0); } - snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto, - mx[i].host, ds->service); - if(debug) - fprint(2, "mxdial trying %s\n", addr); - atnotify(timeout, 1); - alarm(10*1000); - fd = dial(addr, 0, 0, 0); - alarm(0); - atnotify(timeout, 0); - if(fd >= 0) - return fd; } - return -1; + + return 0; } -/* - * call the dns process and have it try to resolve the mx request - * - * this routine knows about the firewall and tries inside and outside - * dns's seperately. - */ static int -mxlookup(DS *ds, char *domain) +busted(char *mx) { - int n; + char **bmp; - /* just in case we find no domain name */ - strcpy(domain, ds->host); - - if(ds->netdir) - n = mxlookup1(ds, domain); - else { - ds->netdir = "/net"; - n = mxlookup1(ds, domain); - if(n <= 0) { - ds->netdir = "/net.alt"; - n = mxlookup1(ds, domain); - } - } + for (bmp = bustedmxs; *bmp != nil; bmp++) + if (strcmp(mx, *bmp) == 0) + return 1; + return 0; +} - return n; +static void +complain(Mxtab *mx, char *domain) +{ + char buf[1024], *e, *p; + int i; + + p = buf; + e = buf + sizeof buf; + for(i = 0; i < mx->nmx; i++) + p = seprint(p, e, "%s ", mx->mx[i].ip); + syslog(0, "smtpd.mx", "loopback for %s %s", domain, buf); } static int -mxlookup1(DS *ds, char *domain) +okaymx(Mxtab *mx, char *domain) { - int i, n, fd, nmx; - char buf[1024], dnsname[Maxstring]; - char *fields[4]; + int i; + Mx *x; + + /* look for malicious dns entries; TODO use badcidr in ../spf/ to catch more than ip4 */ + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->valid && strcmp(x->ip, "127.0.0.1") == 0){ + dprint("illegal: domain %s lists 127.0.0.1 as mail server", domain); + complain(mx, domain); + werrstr("illegal: domain %s lists 127.0.0.1 as mail server", domain); + return -1; + } + if(x->valid && busted(x->host)){ + dprint("lookup: skipping busted mx %s\n", x->host); + x->valid = 0; + } + } + return 0; +} - snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir); +static int +lookup(Mxtab *mx, char *net, char *host, char *domain, char *type) +{ + char dns[128], buf[1024]; + int fd, i; + Mx *x; - fd = open(dnsname, ORDWR); - if(fd < 0) - return 0; + snprint(dns, sizeof dns, "%s/dns", net); + fd = open(dns, ORDWR); + if(fd == -1) + return -1; - nmx = 0; - snprint(buf, sizeof buf, "%s mx", ds->host); - if(debug) - fprint(2, "sending %s '%s'\n", dnsname, buf); - /* - * don't hang indefinitely in the write to /net/dns. - */ - n = timedwrite(fd, buf, strlen(buf), 60*1000); - if(n < 0){ - rerrstr(buf, sizeof buf); - if(debug) - fprint(2, "dns: %s\n", buf); - if(strstr(buf, "dns failure")){ - /* if dns fails for the mx lookup, we have to stop */ - close(fd); - return -1; - } - } else { - /* - * get any mx entries - */ - seek(fd, 0, 0); - while(nmx < Nmx && (n = read(fd, buf, sizeof buf-1)) > 0){ - buf[n] = 0; - if(debug) - fprint(2, "dns mx: %s\n", buf); - n = getfields(buf, fields, 4, 1, " \t"); - if(n < 4) - continue; + snprint(buf, sizeof buf, "%s %s", host, type); + dprint("sending %s '%s'\n", dns, buf); + dnslookup(mx, fd, buf, domain, net, 10000); - if(strchr(domain, '.') == 0) - strcpy(domain, fields[0]); + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->ip[0] != 0) + continue; + x->valid = 0; - strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1); - mx[nmx].pref = atoi(fields[2]); - nmx++; - } - if(debug) - fprint(2, "dns mx; got %d entries\n", nmx); + snprint(buf, sizeof buf, "%s %s", x->host, "ip"); + dprint("sending %s '%s'\n", dns, buf); + dnslookup(mx, fd, buf, domain, net, x->pref); } - /* - * no mx record? try name itself. - */ - /* - * BUG? If domain has no dots, then we used to look up ds->host - * but return domain instead of ds->host in the list. Now we return - * ds->host. What will this break? - */ - if(nmx == 0){ - mx[0].pref = 1; - strncpy(mx[0].host, ds->host, sizeof(mx[0].host)); - nmx++; - } + close(fd); - /* - * look up all ip addresses - */ - for(i = 0; i < nmx; i++){ - seek(fd, 0, 0); - snprint(buf, sizeof buf, "%s ip", mx[i].host); - mx[i].ip[0] = 0; - /* - * don't hang indefinitely in the write to /net/dns. - */ - if(timedwrite(fd, buf, strlen(buf), 60*1000) < 0) - goto no; - seek(fd, 0, 0); - if((n = read(fd, buf, sizeof buf-1)) < 0) - goto no; - buf[n] = 0; - if(getfields(buf, fields, 4, 1, " \t") < 3) - goto no; - strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1); - continue; - - no: - /* remove mx[i] and go around again */ - nmx--; - mx[i] = mx[nmx]; - i--; + if(strcmp(type, "mx") == 0){ + if(okaymx(mx, domain) == -1) + return -1; + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + dprint("mx list: %s %d %s\n", x->host, x->pref, x->ip); + } + dprint("\n"); } - return nmx; -} -static int -compar(void *a, void *b) -{ - return ((Mx*)a)->pref - ((Mx*)b)->pref; + return 0; } -/* break up an address to its component parts */ -void -dial_string_parse(char *str, DS *ds) +static int +lookcall(Mxtab *mx, DS *d, char *domain, char *type) { - char *p, *p2; - - strncpy(ds->buf, str, sizeof(ds->buf)); - ds->buf[sizeof(ds->buf)-1] = 0; + char buf[1024]; + int i; + Mx *x; + + if(lookup(mx, d->netdir, d->host, domain, type) == -1){ + for(i = 0; i < mx->nmx; i++) + if(mx->mx[i].netdir == d->netdir) + mx->mx[i].valid = 0; + return -1; + } - p = strchr(ds->buf, '!'); - if(p == 0) { - ds->netdir = 0; - ds->proto = "net"; - ds->host = ds->buf; - } else { - if(*ds->buf != '/'){ - ds->netdir = 0; - ds->proto = ds->buf; - } else { - for(p2 = p; *p2 != '/'; p2--) - ; - *p2++ = 0; - ds->netdir = ds->buf; - ds->proto = p2; + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->ip[0] == 0 || x->valid == 0){ + x->valid = 0; + continue; } - *p = 0; - ds->host = p + 1; + snprint(buf, sizeof buf, "%s/%s!%s!%s", d->netdir, d->proto, + x->ip /*x->host*/, d->service); + dprint("mxdial trying %s [%s]\n", x->host, buf); + atnotify(timeout, 1); + alarm(10*1000); + mx->fd = dial(buf, 0, 0, 0); + alarm(0); + atnotify(timeout, 0); + if(mx->fd >= 0){ + mx->pmx = i; + return mx->fd; + } + dprint(" failed %r\n"); + x->valid = 0; } - ds->service = strchr(ds->host, '!'); - if(ds->service) - *ds->service++ = 0; - if(*ds->host == '$') - expand_meta(ds); + + return -1; } -static void -expand_meta(DS *ds) +int +mxdial0(char *addr, char *ddomain, char *gdomain, Mxtab *mx) { - char buf[128], cs[128], *net, *p; - int fd, n; - - net = ds->netdir; - if(!net) - net = "/net"; - - if(debug) - fprint(2, "expanding %s!%s\n", net, ds->host); - snprint(cs, sizeof(cs), "%s/cs", net); - if((fd = open(cs, ORDWR)) == -1){ - if(debug) - fprint(2, "open %s: %r\n", cs); - syslog(0, "smtp", "cannot open %s: %r", cs); - return; - } + int nd, i, j; + DS *d; + static char *tab[] = {"mx", "ip", }; - snprint(buf, sizeof buf, "!ipinfo %s", ds->host+1); // +1 to skip $ - if(write(fd, buf, strlen(buf)) <= 0){ - if(debug) - fprint(2, "write %s: %r\n", cs); - syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs); - close(fd); - return; + dprint("mxdial(%s, %s, %s, mx)\n", addr, ddomain, gdomain); + memset(mx, 0, sizeof *mx); + addr = netmkaddr(addr, 0, "smtp"); + d = mx->ds; + dialstringparse(addr, d + 0); + nd = 1; + if(d[0].netdir == nil){ + d[1] = d[0]; + d[0].netdir = "/net"; + d[1].netdir = "/net.alt"; + nd = 2; } - seek(fd, 0, 0); - if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){ - if(debug) - fprint(2, "read %s: %r\n", cs); - syslog(0, "smtp", "%s - read failed: %r", cs); - close(fd); - return; + /* search all networks for mx records; then ip records */ + for(j = 0; j < nelem(tab); j++) + for(i = 0; i < nd; i++) + if(lookcall(mx, d + i, ddomain, tab[j]) != -1) + return mx->fd; + + /* grotty: try gateway machine by ip only (fixme: try cs lookup) */ + if(gdomain != nil){ + dialstringparse(netmkaddr(gdomain, 0, "smtp"), d + 0); + if(lookcall(mx, d + 0, gdomain, "ip") != -1) + return mx->fd; } - close(fd); - ds->expand[n] = 0; - if((p = strchr(ds->expand, '=')) == nil){ - if(debug) - fprint(2, "response %s: %s\n", cs, ds->expand); - syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs); - return; - } - ds->host = p+1; + return -1; +} - /* take only first one returned (quasi-bug) */ - if((p = strchr(ds->host, ' ')) != nil) - *p = 0; +int +mxdial(char *addr, char *ddomain, char *gdomain, Mx *x) +{ + int fd; + Mxtab mx; + + memset(x, 0, sizeof *x); + fd = mxdial0(addr, ddomain, gdomain, &mx); + if(fd >= 0 && mx.pmx >= 0) + *x = mx.mx[mx.pmx]; + mxtabfree(&mx); + return fd; } diff --git a/sys/src/cmd/upas/smtp/parsetest.c b/sys/src/cmd/upas/smtp/parsetest.c new file mode 100644 index 000000000..38e77fb9a --- /dev/null +++ b/sys/src/cmd/upas/smtp/parsetest.c @@ -0,0 +1,86 @@ +#include <u.h> +#include <libc.h> +#include <String.h> +#include <bio.h> +#include "smtp.h" + +Biobuf o; + +void +freefields(void) +{ + Field *f, *fn; + Node *n, *nn; + + for(f = firstfield; f != nil; f = fn){ + fn = f->next; + for(n = f->node; n != nil; n = nn){ + nn = n->next; + s_free(n->s); + s_free(n->white); + free(n); + } + free(f); + } + firstfield = nil; +} + +void +printhdr(void) +{ + Field *f; + Node *n; + + for(f = firstfield; f != nil; f = f->next){ + for(n = f->node; n != nil; n = n->next){ + if(n->s != nil) + Bprint(&o, "%s", s_to_c(n->s)); + else + Bprint(&o, "%c", n->c); + if(n->white != nil) + Bprint(&o, "%s", s_to_c(n->white)); + } + Bprint(&o, "\n"); + } +} + +void +usage(void) +{ + fprint(2, "usage: parsetest file ...\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int i, fd, nbuf; + char *buf; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + if(Binit(&o, 1, OWRITE) == -1) + sysfatal("Binit: %r"); + for(i = 0; i < argc; i++){ + fd = open(argv[i], OREAD); + if(fd == -1) + sysfatal("open: %r"); + buf = malloc(128*1024); + if(buf == nil) + sysfatal("malloc: %r"); + nbuf = read(fd, buf, 128*1024); + if(nbuf == -1) + sysfatal("read: %r"); + close(fd); + yyinit(buf, nbuf); + yyparse(); + printhdr(); + freefields(); + free(buf); + Bflush(&o); + } + exits(""); +} diff --git a/sys/src/cmd/upas/smtp/rfc822.y b/sys/src/cmd/upas/smtp/rfc822.y index b50f63eb1..065c15347 100644 --- a/sys/src/cmd/upas/smtp/rfc822.y +++ b/sys/src/cmd/upas/smtp/rfc822.y @@ -3,8 +3,6 @@ #include "smtp.h" #include <ctype.h> -#define YYMAXDEPTH 500 /* was default 150 */ - char *yylp; /* next character to be lex'd */ int yydone; /* tell yylex to give up */ char *yybuffer; /* first parsed character */ @@ -54,10 +52,11 @@ int messageid; msg : fields | unixfrom '\n' fields ; -fields : '\n' +fields : fieldlist '\n' { yydone = 1; } - | field '\n' - | field '\n' fields + ; +fieldlist : field '\n' + | fieldlist field '\n' ; field : dates { date = 1; } @@ -304,14 +303,12 @@ Keyword key[] = { */ yylex(void) { - String *t; - int quoting; - int escaping; char *start; + int quoting, escaping, c, d; + String *t; Keyword *kp; - int c, d; -/* print("lexing\n"); /**/ +// print("lexing\n"); if(yylp >= yyend) return 0; if(yydone) @@ -331,15 +328,15 @@ yylex(void) if(c == 0) continue; - if(escaping) { + if(escaping) escaping = 0; - } else if(quoting) { + else if(quoting){ switch(c){ case '\\': escaping = 1; break; case '\n': - d = (*(yylp+1))&0xff; + d = yylp[1] & 0xff; if(d != ' ' && d != '\t'){ quoting = 0; yylp--; @@ -350,7 +347,7 @@ yylex(void) quoting = 0; break; } - } else { + }else{ switch(c){ case '\\': escaping = 1; @@ -363,7 +360,7 @@ yylex(void) case '\n': if(yylp == start){ yylp++; -/* print("lex(c %c)\n", c); /**/ +// print("lex(c %c)\n", c); yylval->end = yylp; return yylval->c = c; } @@ -377,7 +374,7 @@ yylex(void) if(yylp == start){ yylp++; yylval->white = yywhite(); -/* print("lex(c %c)\n", c); /**/ +// print("lex(c %c)\n", c); yylval->end = yylp; return yylval->c = c; } @@ -395,25 +392,23 @@ yylex(void) } out: yylval->white = yywhite(); - if(t) { + if(t) s_terminate(t); - } else /* message begins with white-space! */ + else /* message begins with white-space! */ return yylval->c = '\n'; yylval->s = t; for(kp = key; kp->val != WORD; kp++) - if(cistrcmp(s_to_c(t), kp->rep)==0) + if(cistrcmp(s_to_c(t), kp->rep) == 0) break; -/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/ +// print("lex(%d) %s\n", kp->val - WORD, s_to_c(t)); yylval->end = yylp; return yylval->c = kp->val; } void -yyerror(char *x) +yyerror(char*) { - USED(x); - - /*fprint(2, "parse err: %s\n", x);/**/ +// fprint(2, "parse err: %s\n", x); } /* @@ -423,9 +418,7 @@ String * yywhite(void) { String *w; - int clevel; - int c; - int escaping; + int clevel, c, escaping; escaping = clevel = 0; for(w = 0; yylp < yyend; yylp++){ @@ -435,15 +428,15 @@ yywhite(void) if(c == 0) continue; - if(escaping){ + if(escaping) escaping = 0; - } else if(clevel) { + else if(clevel){ switch(c){ case '\n': /* * look for multiline fields */ - if(*(yylp+1)==' ' || *(yylp+1)=='\t') + if(yylp[1] == ' ' || yylp[1] == '\t') break; else goto out; @@ -473,7 +466,7 @@ yywhite(void) /* * look for multiline fields */ - if(*(yylp+1)==' ' || *(yylp+1)=='\t') + if(yylp[1] == ' ' || yylp[1] == '\t') break; else goto out; @@ -533,7 +526,7 @@ colon(Node *p1, Node *p2) if(p1->white){ if(p2->white) s_append(p1->white, s_to_c(p2->white)); - } else { + }else{ p1->white = p2->white; p2->white = 0; } @@ -573,7 +566,7 @@ concat(Node *p1, Node *p2) if(p2->s) s_append(p1->s, s_to_c(p2->s)); - else { + else{ buf[0] = p2->c; buf[1] = 0; s_append(p1->s, buf); @@ -610,23 +603,6 @@ address(Node *p) } /* - * case independent string compare - */ -int -cistrcmp(char *s1, char *s2) -{ - int c1, c2; - - for(; *s1; s1++, s2++){ - c1 = isupper(*s1) ? tolower(*s1) : *s1; - c2 = isupper(*s2) ? tolower(*s2) : *s2; - if (c1 != c2) - return -1; - } - return *s2; -} - -/* * free a node */ void @@ -673,10 +649,10 @@ missing(Node *p) start = yybuffer; if(lastfield != nil){ for(np = lastfield->node; np; np = np->next) - start = np->end+1; + start = np->end + 1; } - end = p->start-1; + end = p->start - 1; if(end <= start) return; @@ -689,7 +665,7 @@ missing(Node *p) np->end = end; np->white = nil; s = s_copy("BadHeader: "); - np->s = s_nappend(s, start, end-start); + np->s = s_nappend(s, start, end - start); np->next = nil; f = malloc(sizeof(Field)); diff --git a/sys/src/cmd/upas/smtp/rmtdns.c b/sys/src/cmd/upas/smtp/rmtdns.c deleted file mode 100644 index b74a1c90b..000000000 --- a/sys/src/cmd/upas/smtp/rmtdns.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "common.h" -#include <ndb.h> - -int -rmtdns(char *net, char *path) -{ - int fd, n, nb, r; - char *domain, *cp, buf[1024]; - - if(net == 0 || path == 0) - return 0; - - domain = strdup(path); - cp = strchr(domain, '!'); - if(cp){ - *cp = 0; - n = cp-domain; - } else - n = strlen(domain); - - if(*domain == '[' && domain[n-1] == ']'){ /* accept [nnn.nnn.nnn.nnn] */ - domain[n-1] = 0; - r = strcmp(ipattr(domain+1), "ip"); - domain[n-1] = ']'; - } else - r = strcmp(ipattr(domain), "ip"); /* accept nnn.nnn.nnn.nnn */ - if(r == 0){ - free(domain); - return 0; - } - - snprint(buf, sizeof buf, "%s/dns", net); - fd = open(buf, ORDWR); /* look up all others */ - if(fd < 0){ /* dns screw up - can't check */ - free(domain); - return 0; - } - - n = snprint(buf, sizeof buf, "%s all", domain); - free(domain); - seek(fd, 0, 0); - nb = write(fd, buf, n); - close(fd); - if(nb != n){ - rerrstr(buf, sizeof buf); - if (strcmp(buf, "dns: name does not exist") == 0) - return -1; - } - return 0; -} - -/* -void -main(int, char *argv[]) -{ - print("return = %d\n", rmtdns("/net.alt", argv[1])); - exits(0); -} -*/ diff --git a/sys/src/cmd/upas/smtp/smtp.c b/sys/src/cmd/upas/smtp/smtp.c index 96f2d65ab..99e5e631d 100644 --- a/sys/src/cmd/upas/smtp/smtp.c +++ b/sys/src/cmd/upas/smtp/smtp.c @@ -5,7 +5,7 @@ #include <libsec.h> #include <auth.h> -static char* connect(char*); +static char* connect(char*, Mx*); static char* wraptls(void); static char* dotls(char*); static char* doauth(char*); @@ -15,7 +15,7 @@ String* bangtoat(char*); String* convertheader(String*); int dBprint(char*, ...); int dBputc(int); -char* data(String*, Biobuf*); +char* data(String*, Biobuf*, Mx*); char* domainify(char*, char*); String* fixrouteaddr(String*, Node*, Node*); char* getcrnl(String*); @@ -27,7 +27,7 @@ int printheader(void); void putcrnl(char*, int); void quit(char*); char* rcptto(char*); -char* rewritezone(char *); +char *rewritezone(char *); #define Retry "Retry, Temporary Failure" #define Giveup "Permanent Failure" @@ -53,13 +53,33 @@ char *uneaten; /* first character after rfc822 headers */ char *farend; /* system we are trying to send to */ char *user; /* user we are authenticating as, if authenticating */ char hostdomain[256]; +Mx *tmmx; /* global for timeout */ Biobuf bin; Biobuf bout; Biobuf berr; Biobuf bfile; -static int bustedmx; +int +Dfmt(Fmt *fmt) +{ + Mx *mx; + + mx = va_arg(fmt->args, Mx*); + if(mx == nil || mx->host[0] == 0) + return fmtstrcpy(fmt, ""); + else + return fmtprint(fmt, "(%s:%s)", mx->host, mx->ip); +} +#pragma varargck type "D" Mx* + +char* +deliverytype(void) +{ + if(ping) + return "ping"; + return "delivery"; +} void usage(void) @@ -70,10 +90,9 @@ usage(void) } int -timeout(void *x, char *msg) +timeout(void *, char *msg) { - USED(x); - syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg); + syslog(0, "smtp.fail", "%s interrupt: %s: %s %D", deliverytype(), farend, msg, tmmx); if(strstr(msg, "alarm")){ fprint(2, "smtp timeout: connection to %s timed out\n", farend); if(quitting) @@ -81,12 +100,12 @@ timeout(void *x, char *msg) exits(Retry); } if(strstr(msg, "closed pipe")){ - /* call _exits() to prevent Bio from trying to flush closed pipe */ fprint(2, "smtp timeout: connection closed to %s\n", farend); if(quitting){ - syslog(0, "smtp.fail", "closed pipe to %s", farend); + syslog(0, "smtp.fail", "%s closed pipe to %s %D", deliverytype(), farend, tmmx); _exits(quitrv); } + /* call _exits() to prevent Bio from trying to flush closed pipe */ _exits(Retry); } return 0; @@ -95,7 +114,7 @@ timeout(void *x, char *msg) void removenewline(char *p) { - int n = strlen(p)-1; + int n = strlen(p) - 1; if(n < 0) return; @@ -106,18 +125,21 @@ removenewline(char *p) void main(int argc, char **argv) { - int i, ok, rcvrs; - char *addr, *rv, *trv, *host, *domain; - char **errs; - char hellodomain[256]; + char *phase, *addr, *rv, *trv, *host, *domain; + char **errs, *p, *e, hellodomain[256], allrx[512]; + int i, ok, rcvrs, bustedmx; String *from, *fromm, *sender; + Mx mx; alarmscale = 60*1000; /* minutes */ quotefmtinstall(); + mailfmtinstall(); /* 2047 encoding */ + fmtinstall('D', Dfmt); fmtinstall('[', encodefmt); errs = malloc(argc*sizeof(char*)); reply = s_new(); host = 0; + bustedmx = 0; ARGBEGIN{ case 'a': tryauth = 1; @@ -128,7 +150,7 @@ main(int argc, char **argv) autistic = 1; break; case 'b': - if (bustedmx >= Maxbustedmx) + if(bustedmx >= Maxbustedmx) sysfatal("more than %d busted mxs given", Maxbustedmx); bustedmxs[bustedmx++] = EARGF(usage()); break; @@ -172,7 +194,7 @@ main(int argc, char **argv) /* * get domain and add to host name */ - if(*argv && **argv=='.') { + if(*argv && **argv=='.'){ domain = *argv; argv++; argc--; } else @@ -189,6 +211,11 @@ main(int argc, char **argv) usage(); addr = *argv++; argc--; farend = addr; + if((rv = strrchr(addr, '!')) && rv[1] == '['){ + syslog(0, "smtp.fail", "%s to %s failed: illegal address", + deliverytype(), addr); + exits(Giveup); + } /* * get sender's machine. @@ -209,30 +236,40 @@ main(int argc, char **argv) /* * send the mail */ + rcvrs = 0; + phase = ""; + USED(phase); /* just in case */ if(filter){ Binit(&bout, 1, OWRITE); - rv = data(from, &bfile); - if(rv != 0) + rv = data(from, &bfile, nil); + if(rv != 0){ + phase = "filter"; goto error; + } exits(0); } /* mxdial uses its own timeout handler */ - if((rv = connect(addr)) != 0) + if((rv = connect(addr, &mx)) != 0) exits(rv); + tmmx = &mx; /* 10 minutes to get through the initial handshake */ atnotify(timeout, 1); alarm(10*alarmscale); - if((rv = hello(hellodomain, 0)) != 0) + if((rv = hello(hellodomain, 0)) != 0){ + phase = "hello"; goto error; + } alarm(10*alarmscale); - if((rv = mailfrom(s_to_c(from))) != 0) + if((rv = mailfrom(s_to_c(from))) != 0){ + phase = "mailfrom"; goto error; + } ok = 0; - rcvrs = 0; /* if any rcvrs are ok, we try to send the message */ + phase = "rcptto"; for(i = 0; i < argc; i++){ if((trv = rcptto(argv[i])) != 0){ /* remember worst error */ @@ -248,6 +285,8 @@ main(int argc, char **argv) } /* if no ok rcvrs or worst error is retry, give up */ + if(ok == 0 && rcvrs == 0) + phase = "rcptto; no addresses"; if(ok == 0 || rv == Retry) goto error; @@ -256,7 +295,7 @@ main(int argc, char **argv) exits(0); } - rv = data(from, &bfile); + rv = data(from, &bfile, &mx); if(rv != 0) goto error; quit(0); @@ -266,10 +305,11 @@ main(int argc, char **argv) /* * here when some but not all rcvrs failed */ - fprint(2, "%s connect to %s:\n", thedate(), addr); + fprint(2, "%s connect to %s: %D %s:\n", thedate(), addr, &mx, phase); for(i = 0; i < rcvrs; i++){ if(errs[i]){ - syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]); + syslog(0, "smtp.fail", "delivery to %s at %s %D %s, failed: %s", + argv[i], addr, &mx, phase, errs[i]); fprint(2, " mail to %s failed: %s", argv[i], errs[i]); } } @@ -279,11 +319,19 @@ main(int argc, char **argv) * here when all rcvrs failed */ error: + alarm(0); removenewline(s_to_c(reply)); - syslog(0, "smtp.fail", "%s to %s failed: %s", - ping ? "ping" : "delivery", - addr, s_to_c(reply)); - fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply)); + if(rcvrs > 0){ + p = allrx; + e = allrx + sizeof allrx; + seprint(p, e, "to "); + for(i = 0; i < rcvrs - 1; i++) + p = seprint(p, e, "%s,", argv[i]); + seprint(p, e, "%s ", argv[i]); + } + syslog(0, "smtp.fail", "%s %s at %s %D %s failed: %s", + deliverytype(), allrx, addr, &mx, phase, s_to_c(reply)); + fprint(2, "%s connect to %s %D %s:\n%s\n", thedate(), addr, &mx, phase, s_to_c(reply)); if(!filter) quit(rv); exits(rv); @@ -293,17 +341,17 @@ error: * connect to the remote host */ static char * -connect(char* net) +connect(char* net, Mx *mx) { - char buf[Errlen]; + char buf[ERRMAX]; int fd; - fd = mxdial(net, ddomain, gdomain); + fd = mxdial(net, ddomain, gdomain, mx); if(fd < 0){ - rerrstr(buf, sizeof(buf)); - Bprint(&berr, "smtp: %s (%s)\n", buf, net); - syslog(0, "smtp.fail", "%s (%s)", buf, net); + rerrstr(buf, sizeof buf); + Bprint(&berr, "smtp: %s (%s) %D\n", buf, net, mx); + syslog(0, "smtp.fail", "%s %s (%s) %D", deliverytype(), buf, net, mx); if(strstr(buf, "illegal") || strstr(buf, "unknown") || strstr(buf, "can't translate")) @@ -320,7 +368,20 @@ connect(char* net) static char smtpthumbs[] = "/sys/lib/tls/smtp"; static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude"; -static char * +static int +tracetls(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + Bvprint(&berr, fmt, ap); + Bprint(&berr, "\n"); + Bflush(&berr); + va_end(ap); + return 0; +} + +static char* wraptls(void) { TLSconn *c; @@ -335,6 +396,9 @@ wraptls(void) if (c == nil) return err; + if (debug) + c->trace = tracetls; + fd = tlsClient(Bfildes(&bout), c); if (fd < 0) { syslog(0, "smtp", "tlsClient to %q: %r", ddomain); @@ -396,6 +460,36 @@ dotls(char *me) return(hello(me, 1)); } +static char* +smtpcram(DS *ds) +{ + char *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192]; + int i, n, l; + + dBprint("AUTH CRAM-MD5\r\n"); + if(getreply() != 3) + return Retry; + p = s_to_c(reply) + 4; + l = dec64((uchar*)ch, sizeof ch, p, strlen(p)); + ch[l] = 0; + n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey, + user?"proto=cram role=client server=%q user=%q":"proto=cram role=client server=%q", + ds->host, user); + if(n == -1) + return "cannot find SMTP password"; + if(usr[0] == 0) + return "cannot find user name"; + for(i = 0; i < n; i++) + rbuf[i] = tolower(rbuf[i]); + l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf); + snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf); + + dBprint("%s\r\n", ch); + if(getreply() != 2) + return Retry; + return nil; +} + static char * doauth(char *methods) { @@ -404,14 +498,13 @@ doauth(char *methods) int n; DS ds; - dial_string_parse(ddomain, &ds); + dialstringparse(ddomain, &ds); + if(strstr(methods, "CRAM-MD5")) + return smtpcram(&ds); - if(user != nil) - p = auth_getuserpasswd(nil, - "proto=pass service=smtp server=%q user=%q", ds.host, user); - else - p = auth_getuserpasswd(nil, - "proto=pass service=smtp server=%q", ds.host); + p = auth_getuserpasswd(nil, + user?"proto=pass service=smtp server=%q user=%q":"proto=pass service=smtp server=%q", + ds.host, user); if (p == nil) return Giveup; @@ -455,14 +548,14 @@ out: return err; } -char * +char* hello(char *me, int encrypted) { + char *ret, *s, *t; int ehlo; String *r; - char *ret, *s, *t; - if (!encrypted) { + if(!encrypted){ if(trysecure > 1){ if((ret = wraptls()) != nil) return ret; @@ -474,7 +567,7 @@ hello(char *me, int encrypted) * answers a call. Send a no-op in the hope of making it * talk. */ - if (autistic) { + if(autistic){ dBprint("NOOP\r\n"); getreply(); /* consume the smtp greeting */ /* next reply will be response to noop */ @@ -495,7 +588,7 @@ hello(char *me, int encrypted) dBprint("EHLO %s\r\n", me); else dBprint("HELO %s\r\n", me); - switch (getreply()) { + switch(getreply()){ case 2: break; case 5: @@ -514,17 +607,15 @@ hello(char *me, int encrypted) /* Invariant: every line has a newline, a result of getcrlf() */ for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){ *t = '\0'; - for (t = s; *t != '\0'; t++) - *t = toupper(*t); if(!encrypted && trysecure && - (strcmp(s, "250-STARTTLS") == 0 || - strcmp(s, "250 STARTTLS") == 0)){ + (cistrcmp(s, "250-STARTTLS") == 0 || + cistrcmp(s, "250 STARTTLS") == 0)){ s_free(r); return dotls(me); } if(tryauth && (encrypted || insecure) && - (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 || - strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){ + (cistrncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 || + cistrncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){ ret = doauth(s + strlen("250 AUTH ")); s_free(r); return ret; @@ -542,20 +633,18 @@ mailfrom(char *from) { if(!returnable(from)) dBprint("MAIL FROM:<>\r\n"); - else - if(strchr(from, '@')) + else if(strchr(from, '@')) dBprint("MAIL FROM:<%s>\r\n", from); else dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain); switch(getreply()){ case 2: - break; + return 0; case 5: return Giveup; default: return Retry; } - return 0; } /* @@ -597,13 +686,11 @@ static char hex[] = "0123456789abcdef"; * send the damn thing */ char * -data(String *from, Biobuf *b) +data(String *from, Biobuf *b, Mx *mx) { - char *buf, *cp; + char *buf, *cp, errmsg[ERRMAX], id[40]; int i, n, nbytes, bufsize, eof, r; String *fromline; - char errmsg[Errlen]; - char id[40]; /* * input the header. @@ -623,12 +710,12 @@ data(String *from, Biobuf *b) break; } nbytes = Blinelen(b); - buf = realloc(buf, n+nbytes+1); + buf = realloc(buf, n + nbytes + 1); if(buf == 0){ s_append(s_restart(reply), "out of memory"); return Retry; } - strncpy(buf+n, cp, nbytes); + strncpy(buf + n, cp, nbytes); n += nbytes; if(nbytes == 1) /* end of header */ break; @@ -669,10 +756,10 @@ data(String *from, Biobuf *b) srand(truerand()); if(messageid == 0){ - for(i=0; i<16; i++){ - r = rand()&0xFF; - id[2*i] = hex[r&0xF]; - id[2*i+1] = hex[(r>>4)&0xF]; + for(i = 0; i < 16; i++){ + r = rand() & 0xff; + id[2*i] = hex[r & 0xf]; + id[2*i + 1] = hex[(r>>4) & 0xf]; } id[2*i] = '\0'; nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain); @@ -680,7 +767,7 @@ data(String *from, Biobuf *b) Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain); } - if(originator==0){ + if(originator == 0){ nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline)); if(debug) Bprint(&berr, "From: %s\r\n", s_to_c(fromline)); @@ -698,20 +785,20 @@ data(String *from, Biobuf *b) Bprint(&berr, "To: %s\r\n", s_to_c(toline)); } - if(date==0 && udate) + if(date == 0 && udate) nbytes += printdate(udate); - if (usys) + if(usys) uneaten = usys->end + 1; nbytes += printheader(); - if (*uneaten != '\n') + if(*uneaten != '\n') putcrnl("\n", 1); /* * send body */ - putcrnl(uneaten, buf+n - uneaten); - nbytes += buf+n - uneaten; + putcrnl(uneaten, buf + n - uneaten); + nbytes += buf + n - uneaten; if(eof == 0){ for(;;){ n = Bread(b, buf, bufsize); @@ -743,8 +830,8 @@ data(String *from, Biobuf *b) default: return Retry; } - syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from), - nbytes, s_to_c(toline));/**/ + syslog(0, "smtp", "%s sent %d bytes to %s %D", s_to_c(from), + nbytes, s_to_c(toline), mx); } return 0; } @@ -806,10 +893,9 @@ addhostdom(String *buf, char *host) String * bangtoat(char *addr) { - String *buf; - register int i; - int j, d; char *field[128]; + int i, j, d; + String *buf; /* parse the '!' format address */ buf = s_new(); @@ -819,7 +905,7 @@ bangtoat(char *addr) if(addr) *addr++ = 0; } - if (i==1) { + if(i == 1){ s_append(buf, field[0]); return buf; } @@ -827,8 +913,8 @@ bangtoat(char *addr) /* * count leading domain fields (non-domains don't count) */ - for(d = 0; d<i-1; d++) - if(strchr(field[d], '.')==0) + for(d = 0; d < i - 1; d++) + if(strchr(field[d], '.') == 0) break; /* * if there are more than 1 leading domain elements, @@ -836,7 +922,7 @@ bangtoat(char *addr) */ if(d > 1){ addhostdom(buf, field[0]); - for(j=1; j<d-1; j++){ + for(j = 1; j< d - 1; j++){ s_append(buf, ","); s_append(buf, "@"); s_append(buf, field[j]); @@ -848,7 +934,7 @@ bangtoat(char *addr) * throw in the non-domain elements separated by '!'s */ s_append(buf, field[d]); - for(j=d+1; j<=i-1; j++) { + for(j = d + 1; j <= i - 1; j++){ s_append(buf, "!"); s_append(buf, field[j]); } @@ -865,6 +951,7 @@ bangtoat(char *addr) String* convertheader(String *from) { + char *s, buf[64]; Field *f; Node *p, *lastp; String *a; @@ -875,8 +962,10 @@ convertheader(String *from) addhostdom(from, hostdomain); } else if(strchr(s_to_c(from), '@') == 0){ - a = username(from); - if(a) { + if(s = username(s_to_c(from))){ + /* this has always been here, but username() was broken */ + snprint(buf, sizeof buf, "%U", s); + s_append(a = s_new(), buf); s_append(a, " <"); s_append(a, s_to_c(from)); addhostdom(a, hostdomain); @@ -929,11 +1018,10 @@ fixrouteaddr(String *raddr, Node *next, Node *last) int printheader(void) { + char *cp, c[1]; int n, len; Field *f; Node *p; - char *cp; - char c[1]; n = 0; for(f = firstfield; f; f = f->next){ @@ -966,10 +1054,10 @@ printheader(void) char * domainify(char *name, char *domain) { - static String *s; char *p; + static String *s; - if(domain==0 || strchr(name, '.')!=0) + if(domain == 0 || strchr(name, '.') != 0) return name; s = s_reset(s); @@ -1008,8 +1096,7 @@ putcrnl(char *cp, int n) char * getcrnl(String *s) { - int c; - int count; + int c, count; count = 0; for(;;){ @@ -1052,16 +1139,17 @@ getcrnl(String *s) int printdate(Node *p) { - int n, sep = 0; + int n, sep; n = dBprint("Date: %s,", s_to_c(p->s)); + sep = 0; for(p = p->next; p; p = p->next){ if(p->s){ - if(sep == 0) { + if(sep == 0){ dBputc(' '); n++; } - if (p->next) + if(p->next) n += dBprint("%s", s_to_c(p->s)); else n += dBprint("%s", rewritezone(s_to_c(p->s))); @@ -1079,8 +1167,8 @@ printdate(Node *p) char * rewritezone(char *z) { - int mindiff; char s; + int mindiff; Tm *tm; static char x[7]; @@ -1104,22 +1192,22 @@ rewritezone(char *z) /* * stolen from libc/port/print.c */ -#define SIZE 4096 + int dBprint(char *fmt, ...) { - char buf[SIZE], *out; - va_list arg; + char buf[4096], *out; int n; + va_list arg; va_start(arg, fmt); - out = vseprint(buf, buf+SIZE, fmt, arg); + out = vseprint(buf, buf + sizeof buf, fmt, arg); va_end(arg); if(debug){ - Bwrite(&berr, buf, (long)(out-buf)); + Bwrite(&berr, buf, out - buf); Bflush(&berr); } - n = Bwrite(&bout, buf, (long)(out-buf)); + n = Bwrite(&bout, buf,out - buf); Bflush(&bout); return n; } diff --git a/sys/src/cmd/upas/smtp/smtp.h b/sys/src/cmd/upas/smtp/smtp.h index bfac33ed9..d420d1dc3 100644 --- a/sys/src/cmd/upas/smtp/smtp.h +++ b/sys/src/cmd/upas/smtp/smtp.h @@ -24,8 +24,11 @@ struct Field { }; typedef struct DS DS; +typedef struct Mx Mx; +typedef struct Mxtab Mxtab; + struct DS { - /* dist string */ + /* dial string */ char buf[128]; char expand[128]; char *netdir; @@ -34,6 +37,25 @@ struct DS { char *service; }; +struct Mx +{ + char *netdir; + char host[256]; + char ip[24]; + int pref; + int valid; +}; + +struct Mxtab +{ + DS ds[2]; + int nmx; + int amx; + int pmx; + int fd; + Mx *mx; +}; + extern Field *firstfield; extern Field *lastfield; extern Node *usender; @@ -51,7 +73,6 @@ Node* address(Node*); int badfieldname(Node*); Node* bang(Node*, Node*); Node* colon(Node*, Node*); -int cistrcmp(char*, char*); Node* link2(Node*, Node*); Node* link3(Node*, Node*, Node*); void freenode(Node*); @@ -63,5 +84,9 @@ int yylex(void); String* yywhite(void); Node* whiten(Node*); void yycleanup(void); -int mxdial(char*, char*, char*); -void dial_string_parse(char*, DS*); +int mxdial0(char*, char*, char*, Mxtab*); +int mxdial(char*, char*, char*, Mx*); +void mxtabfree(Mxtab*); +void dialstringparse(char*, DS*); + +#define dprint(...) do if(debug)print(__VA_ARGS__); while(0) diff --git a/sys/src/cmd/upas/smtp/smtpd.c b/sys/src/cmd/upas/smtp/smtpd.c index 85fbb098a..6c75e9399 100644 --- a/sys/src/cmd/upas/smtp/smtpd.c +++ b/sys/src/cmd/upas/smtp/smtpd.c @@ -22,14 +22,15 @@ int logged; int rejectcount; int hardreject; -ulong starttime; - Biobuf bin; int debug; int Dflag; +int Eflag; +int eflag; int fflag; int gflag; +int qflag; int rflag; int sflag; int authenticate; @@ -50,48 +51,35 @@ int pipemsg(int*); int rejectcheck(void); String* startcmd(void); -static void logmsg(char *action); - static int -catchalarm(void *a, char *msg) +catchalarm(void*, char *msg) { - int rv; - - USED(a); + int ign; + static int chattycathy; - /* log alarms but continue */ - if(strstr(msg, "alarm") != nil){ - if(senders.first && senders.first->p && - rcvers.first && rcvers.first->p) + ign = strstr(msg, "closed pipe") != nil; + if(ign) + return 0; + if(chattycathy++ < 5){ + if(senders.first && rcvers.first) syslog(0, "smtpd", "note: %s->%s: %s", s_to_c(senders.first->p), s_to_c(rcvers.first->p), msg); else syslog(0, "smtpd", "note: %s", msg); - rv = Atnoterecog; - } else - rv = Atnoteunknown; - if (debug) { - seek(2, 0, 2); - fprint(2, "caught note: %s\n", msg); } - - /* kill the children if there are any */ - if(pp && pp->pid > 0) { - syskillpg(pp->pid); - /* none can't syskillpg, so try a variant */ - sleep(500); + if(pp){ syskill(pp->pid); + // pp = 0; } - - return rv; + return strstr(msg, "alarm") != nil; } /* override string error functions to do something reasonable */ void s_error(char *f, char *status) { - char errbuf[Errlen]; + char errbuf[ERRMAX]; errbuf[0] = 0; rerrstr(errbuf, sizeof(errbuf)); @@ -106,8 +94,7 @@ s_error(char *f, char *status) static void usage(void) { - fprint(2, - "usage: smtpd [-adDfghprs] [-c cert] [-k ip] [-m mailer] [-n net]\n"); + fprint(2, "usage: smtpd [-DEadefghpqrs] [-c cert] [-k ip] [-m mailer] [-n net]\n"); exits("usage"); } @@ -121,7 +108,6 @@ main(int argc, char **argv) quotefmtinstall(); fmtinstall('I', eipfmt); fmtinstall('[', encodefmt); - starttime = time(0); ARGBEGIN{ case 'a': authenticate = 1; @@ -135,6 +121,12 @@ main(int argc, char **argv) case 'd': debug++; break; + case 'E': + Eflag = 1; + break; /* if you fail extra helo checks, you must authenticate */ + case 'e': + eflag = 1; /* disable extra helo checks */ + break; case 'f': /* disallow relaying */ fflag = 1; break; @@ -156,41 +148,39 @@ main(int argc, char **argv) case 'p': passwordinclear = 1; break; + case 'q': + qflag = 1; /* don't log invalid hello */ + break; case 'r': rflag = 1; /* verify sender's domain */ break; case 's': /* save blocked messages */ sflag = 1; break; - case 't': - fprint(2, "%s: the -t option is no longer supported, see -c\n", - argv0); - tlscert = "/sys/lib/ssl/smtpd-cert.pem"; - break; default: usage(); }ARGEND; nci = getnetconninfo(netdir, 0); if(nci == nil) - sysfatal("can't get remote system's address: %r"); + sysfatal("can't get remote system's address"); parseip(rsysip, nci->rsys); if(mailer == nil) mailer = mailerpath("send"); if(debug){ - snprint(buf, sizeof buf, "%s/smtpdb/%ld", UPASLOG, time(0)); close(2); - if (create(buf, OWRITE | OEXCL, 0662) >= 0) { + snprint(buf, sizeof(buf), "%s/smtpd.db", UPASLOG); + if (open(buf, OWRITE) >= 0) { seek(2, 0, 2); fprint(2, "%d smtpd %s\n", getpid(), thedate()); } else debug = 0; } getconf(); - if (isbadguy()) - exits("banned"); + if(isbadguy()) + exits(""); Binit(&bin, 0, OREAD); if (chdir(UPASLOG) < 0) @@ -239,32 +229,20 @@ listadd(List *l, String *path) l->last = lp; } -void -stamp(void) -{ - if(debug) { - seek(2, 0, 2); - fprint(2, "%3lud ", time(0) - starttime); - } -} - -#define SIZE 4096 - int reply(char *fmt, ...) { - long n; - char buf[SIZE], *out; + char buf[4096], *out; + int n; va_list arg; va_start(arg, fmt); - out = vseprint(buf, buf+SIZE, fmt, arg); + out = vseprint(buf, buf + 4096, fmt, arg); va_end(arg); n = out - buf; if(debug) { seek(2, 0, 2); - stamp(); write(2, buf, n); } write(1, buf, n); @@ -291,6 +269,36 @@ sayhi(void) reply("220 %s ESMTP\r\n", dom); } +Ndbtuple* +rquery(char *d) +{ + Ndbtuple *t, *p; + + t = dnsquery(nci->root, nci->rsys, "ptr"); + for(p = t; p != nil; p = p->entry) + if(strcmp(p->attr, "dom") == 0 + && strcmp(p->val, d) == 0){ + syslog(0, "smtpd", "ptr only from %s as %s", + nci->rsys, d); + return t; + } + ndbfree(t); + return nil; +} + +int +dnsexists(char *d) +{ + int r; + Ndbtuple *t; + + r = -1; + if((t = dnsquery(nci->root, d, "any")) != nil || (t = rquery(d)) != nil) + r = 0; + ndbfree(t); + return r; +} + /* * make callers from class A networks infested by spammers * wait longer. @@ -324,102 +332,153 @@ static char netaspam[256] = { static int delaysecs(void) { - if (trusted) + if (netaspam[rsysip[0]]) + return 60; + return 15; +} + +static char *badtld[] = { + "localdomain", + "localhost", + "local", + "example", + "invalid", + "lan", + "test", +}; + +static char *bad2ld[] = { + "example.com", + "example.net", + "example.org" +}; + +int +badname(void) +{ + char *p; + + /* + * similarly, if the claimed domain is not an address-literal, + * require at least one letter, which there will be in + * at least the last component (e.g., .com, .net) if it's real. + * this rejects non-address-literal IP addresses, + * among other bogosities. + */ + for (p = him; *p; p++) + if(isascii(*p) && isalpha(*p)) + return 0; + return -1; +} + +int +ckhello(void) +{ + char *ldot, *rdot; + int i; + + /* + * it is unacceptable to claim any string that doesn't look like + * a domain name (e.g., has at least one dot in it), but + * Microsoft mail client software gets this wrong, so let trusted + * (local) clients omit the dot. + */ + rdot = strrchr(him, '.'); + if(rdot && rdot[1] == '\0') { + *rdot = '\0'; /* clobber trailing dot */ + rdot = strrchr(him, '.'); /* try again */ + } + if(rdot == nil) + return -1; + /* + * Reject obviously bogus domains and those reserved by RFC 2606. + */ + if(rdot == nil) + rdot = him; + else + rdot++; + for(i = 0; i < nelem(badtld); i++) + if(!cistrcmp(rdot, badtld[i])) + return -1; + /* check second-level RFC 2606 domains: example\.(com|net|org) */ + if(rdot != him) + *--rdot = '\0'; + ldot = strrchr(him, '.'); + if(rdot != him) + *rdot = '.'; + if(ldot == nil) + ldot = him; + else + ldot++; + for(i = 0; i < nelem(bad2ld); i++) + if(!cistrcmp(ldot, bad2ld[i])) + return -1; + if(badname() == -1) + return -1; + if(dnsexists(him) == -1) + return -1; + return 0; +} + +int +heloclaims(void) +{ + char **s; + + /* + * We don't care if he lies about who he is, but it is + * not okay to pretend to be us. Many viruses do this, + * just parroting back what we say in the greeting. + */ + if(strcmp(nci->rsys, nci->lsys) == 0) return 0; - if (0 && netaspam[rsysip[0]]) - return 20; - return 12; + if(strcmp(him, dom) == 0) + return -1; + for(s = sysnames_read(); s && *s; s++) + if(cistrcmp(*s, him) == 0) + return -1; + if(him[0] != '[' && badname() == -1) + return -1; + + return 0; } void hello(String *himp, int extended) { - char **mynames; - char *ldot, *rdot; - char *p; + int ck; him = s_to_c(himp); - syslog(0, "smtpd", "%s from %s as %s", extended? "ehlo": "helo", - nci->rsys, him); + if(!qflag) + syslog(0, "smtpd", "%s from %s as %s", extended? "ehlo": "helo", + nci->rsys, him); if(rejectcheck()) return; - if (him[0] == '[') { - /* - * reject literal ip addresses when not trusted. - */ - if (!trusted) - goto Liarliar; - him = nci->rsys; - } else { - if (!trusted && fflag && nci && strcmp(nci->rsys, nci->lsys) != 0){ - /* - * We don't care if he lies about who he is, but it is - * not okay to pretend to be us. Many viruses do this, - * just parroting back what we say in the greeting. - */ - if(cistrcmp(him, dom) == 0) - goto Liarliar; - for(mynames = sysnames_read(); mynames && *mynames; mynames++) - if(cistrcmp(*mynames, him) == 0) - goto Liarliar; - } - - /* - * require at least one letter, which there will be in - * at least the last component (e.g., .com, .net) if it's real. - * this rejects non-address-literal IP addresses, - * among other bogosities. - */ - for (p = him; *p != '\0'; p++) - if (isascii(*p) && isalpha(*p)) - break; - if (*p == '\0') - goto Liarliar; - - /* - * it is unacceptable to claim any string that doesn't look like - * a domain name (e.g., has at least one dot in it), but - * Microsoft mail client software gets this wrong, so let trusted - * (local) clients omit the dot. - */ - rdot = strrchr(him, '.'); - if (rdot && rdot[1] == '\0') { - *rdot = '\0'; /* clobber trailing dot */ - rdot = strrchr(him, '.'); /* try again */ - } - if (!trusted && rdot == nil) - goto Liarliar; - - /* - * Reject obviously bogus domains and those reserved by RFC 2606. - */ - if (rdot == nil) - rdot = him; - else - rdot++; - if (cistrcmp(rdot, "localdomain") == 0 || - cistrcmp(rdot, "localhost") == 0 || - cistrcmp(rdot, "example") == 0 || - cistrcmp(rdot, "invalid") == 0 || - cistrcmp(rdot, "test") == 0) - goto Liarliar; /* bad top-level domain */ - /* check second-level RFC 2606 domains: example\.(com|net|org) */ - if (rdot != him) - *--rdot = '\0'; - ldot = strrchr(him, '.'); - if (rdot != him) - *rdot = '.'; - if (ldot == nil) - ldot = him; - else - ldot++; - if (cistrcmp(ldot, "example.com") == 0 || - cistrcmp(ldot, "example.net") == 0 || - cistrcmp(ldot, "example.org") == 0) - goto Liarliar; + ck = -1; + if(!trusted && nci) + if(heloclaims() || (!eflag && (ck = ckhello()))) + if(ck && Eflag){ + reply("250-you lie. authentication required.\r\n"); + authenticate = 1; + }else{ + if(Dflag) + sleep(delaysecs()*1000); + if(!qflag) + syslog(0, "smtpd", "Hung up on %s; claimed to be %s", + nci->rsys, him); + rejectcount++; + reply("554 5.7.0 Liar!\r\n"); + exits("client pretended to be us"); + return; } + if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil) + him = nci->rsys; + + if(qflag) + syslog(0, "smtpd", "%s from %s as %s", extended? "ehlo": "helo", + nci->rsys, him); if(Dflag) sleep(delaysecs()*1000); reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him); @@ -432,15 +491,6 @@ hello(String *himp, int extended) else reply("250 AUTH CRAM-MD5\r\n"); } - return; - -Liarliar: - syslog(0, "smtpd", "Hung up on %s; claimed to be %s", - nci->rsys, him); - if(Dflag) - sleep(delaysecs()*1000); - reply("554 5.7.0 Liar!\r\n"); - exits("client pretended to be us"); } void @@ -481,33 +531,9 @@ sender(String *path) /* * see if this ip address, domain name, user name or account is blocked */ - logged = 0; filterstate = blocked(path); - /* - * permanently reject what we can before trying smtp ping, which - * often leads to merely temporary rejections. - */ - switch (filterstate){ - case DENIED: - syslog(0, "smtpd", "Denied %s (%s/%s)", - s_to_c(path), him, nci->rsys); - rejectcount++; - logged++; - reply("554-5.7.1 We don't accept mail from %s.\r\n", - s_to_c(path)); - reply("554 5.7.1 Contact postmaster@%s for more information.\r\n", - dom); - return; - case REFUSED: - syslog(0, "smtpd", "Refused %s (%s/%s)", - s_to_c(path), him, nci->rsys); - rejectcount++; - logged++; - reply("554 5.7.1 Sender domain must exist: %s\r\n", - s_to_c(path)); - return; - } + logged = 0; listadd(&senders, path); reply("250 2.0.0 sender is %s\r\n", s_to_c(path)); } @@ -642,7 +668,7 @@ receiver(String *path) if(!recipok(s_to_c(path))){ rejectcount++; syslog(0, "smtpd", - "Disallowed %s (%s/%s) to blocked, unknown or invalid name %s", + "Disallowed %s (%s/%s) to blocked name %s", sender, him, nci->rsys, s_to_c(path)); reply("550 5.1.1 %s ... user unknown\r\n", s_to_c(path)); return; @@ -660,11 +686,9 @@ receiver(String *path) /* forwarding() can modify 'path' on loopback request */ if(filterstate == ACCEPT && fflag && !authenticated && forwarding(path)) { - syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)", - senders.last && senders.last->p? - s_to_c(senders.last->p): sender, - him, nci->rsys, path? s_to_c(path): rcpt); rejectcount++; + syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)", + sender, him, nci->rsys, rcpt); reply("550 5.7.1 we don't relay. send to your-path@[] for " "loopback.\r\n"); return; @@ -677,12 +701,6 @@ void quit(void) { reply("221 2.0.0 Successful termination\r\n"); - if(debug){ - seek(2, 0, 2); - stamp(); - fprint(2, "# %d sent 221 reply to QUIT %s\n", - getpid(), thedate()); - } close(0); exits(0); } @@ -710,10 +728,14 @@ verify(String *path) { char *p, *q; char *av[4]; + static uint nverify; if(rejectcheck()) return; + if(nverify++ >= 2) + sleep(1000 * (4 << nverify - 2)); if(shellchars(s_to_c(path))){ + rejectcount++; reply("503 5.1.3 Bad character in address %s.\r\n", s_to_c(path)); return; } @@ -722,7 +744,7 @@ verify(String *path) av[2] = s_to_c(path); av[3] = 0; - pp = noshell_proc_start(av, (stream *)0, outstream(), (stream *)0, 1, 0); + pp = noshell_proc_start(av, 0, outstream(), 0, 1, 0); if (pp == 0) { reply("450 4.3.2 We're busy right now, try later\r\n"); return; @@ -732,13 +754,13 @@ verify(String *path) if(p == 0){ reply("550 5.1.0 String does not match anything.\r\n"); } else { - p[Blinelen(pp->std[1]->fp)-1] = 0; + p[Blinelen(pp->std[1]->fp) - 1] = 0; if(strchr(p, ':')) reply("550 5.1.0 String does not match anything.\r\n"); else{ q = strrchr(p, '!'); if(q) - p = q+1; + p = q + 1; reply("250 2.0.0 %s <%s@%s>\r\n", s_to_c(path), p, dom); } } @@ -765,6 +787,7 @@ getcrnl(String *s, Biobuf *fp) } switch(c){ case 0: + /* idiot html email! */ break; case -1: goto out; @@ -774,7 +797,6 @@ getcrnl(String *s, Biobuf *fp) if(debug) { seek(2, 0, 2); fprint(2, "%c", c); - stamp(); } s_putc(s, '\n'); goto out; @@ -912,15 +934,14 @@ startcmd(void) dom); return 0; case ACCEPT: + case TRUSTED: /* * now that all other filters have been passed, * do grey-list processing. */ if(gflag) vfysenderhostok(); - /* fall through */ - case TRUSTED: /* * set up mail command */ @@ -962,34 +983,24 @@ startcmd(void) * address@him */ char* -bprintnode(Biobuf *b, Node *p, int *cntp) +bprintnode(Biobuf *b, Node *p, int *nbytes) { - int len; + int n, m; - *cntp = 0; if(p->s){ - if(p->addr && strchr(s_to_c(p->s), '@') == nil){ - if(Bprint(b, "%s@%s", s_to_c(p->s), him) < 0) - return nil; - *cntp += s_len(p->s) + 1 + strlen(him); - } else { - len = s_len(p->s); - if(Bwrite(b, s_to_c(p->s), len) < 0) - return nil; - *cntp += len; - } - }else{ - if(Bputc(b, p->c) < 0) - return nil; - ++*cntp; - } - if(p->white) { - len = s_len(p->white); - if(Bwrite(b, s_to_c(p->white), len) < 0) - return nil; - *cntp += len; - } - return p->end+1; + if(p->addr && strchr(s_to_c(p->s), '@') == nil) + n = Bprint(b, "%s@%s", s_to_c(p->s), him); + else + n = Bwrite(b, s_to_c(p->s), s_len(p->s)); + }else + n = Bputc(b, p->c) == -1? -1: 1; + m = 0; + if(n != -1 && p->white) + m = Bwrite(b, s_to_c(p->white), s_len(p->white)); + if(n == -1 || m == -1) + return nil; + *nbytes += n + m; + return p->end + 1; } static String* @@ -1017,8 +1028,7 @@ forgedheaderwarnings(void) nbytes = 0; /* warn about envelope sender */ - if(senders.last != nil && senders.last->p != nil && - strcmp(s_to_c(senders.last->p), "/dev/null") != 0 && + if(strcmp(s_to_c(senders.last->p), "/dev/null") != 0 && masquerade(senders.last->p, nil)) nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect envelope domain\n"); @@ -1044,68 +1054,16 @@ forgedheaderwarnings(void) return nbytes; } -/* - * pipe message to mailer with the following transformations: - * - change \r\n into \n. - * - add sender's domain to any addrs with no domain - * - add a From: if none of From:, Sender:, or Replyto: exists - * - add a Received: line - */ -int -pipemsg(int *byteswritten) +static int +parseheader(String *hdr, int *nbytesp, int *status) { - int n, nbytes, sawdot, status, nonhdr, bpr; char *cp; + int nbytes, n; Field *f; Link *l; Node *p; - String *hdr, *line; - pipesig(&status); /* set status to 1 on write to closed pipe */ - sawdot = 0; - status = 0; - - /* - * add a 'From ' line as envelope - */ - nbytes = 0; - nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n", - s_to_c(senders.first->p), thedate()); - - /* - * add our own Received: stamp - */ - nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him); - if(nci->rsys) - nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys); - nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate()); - - /* - * read first 16k obeying '.' escape. we're assuming - * the header will all be there. - */ - line = s_new(); - hdr = s_new(); - while(sawdot == 0 && s_len(hdr) < 16*1024){ - n = getcrnl(s_reset(line), &bin); - - /* eof or error ends the message */ - if(n <= 0) - break; - - /* a line with only a '.' ends the message */ - cp = s_to_c(line); - if(n == 2 && *cp == '.' && *(cp+1) == '\n'){ - sawdot = 1; - break; - } - - s_append(hdr, *cp == '.' ? cp+1 : cp); - } - - /* - * parse header - */ + nbytes = *nbytesp; yyinit(s_to_c(hdr), s_len(hdr)); yyparse(); @@ -1120,9 +1078,8 @@ pipemsg(int *byteswritten) * add an orginator and/or destination if either is missing */ if(originator == 0){ - if(senders.last == nil || senders.last->p == nil) - nbytes += Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", - him); + if(senders.last == nil) + nbytes += Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him); else nbytes += Bprint(pp->std[0]->fp, "From: %s\n", s_to_c(senders.last->p)); @@ -1143,45 +1100,152 @@ pipemsg(int *byteswritten) */ cp = s_to_c(hdr); for(f = firstfield; cp != nil && f; f = f->next){ - for(p = f->node; cp != 0 && p; p = p->next) { - bpr = 0; - cp = bprintnode(pp->std[0]->fp, p, &bpr); - nbytes += bpr; - } - if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){ + for(p = f->node; cp != 0 && p; p = p->next) + cp = bprintnode(pp->std[0]->fp, p, &nbytes); + if(*status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){ piperror = "write error"; - status = 1; + *status = 1; } - nbytes++; /* for newline */ + nbytes++; } if(cp == nil){ piperror = "sender domain"; - status = 1; + *status = 1; } - /* write anything we read following the header */ - nonhdr = s_to_c(hdr) + s_len(hdr) - cp; - if(status == 0 && Bwrite(pp->std[0]->fp, cp, nonhdr) < 0){ - piperror = "write error 2"; - status = 1; + if(*status == 0){ + n = Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp); + if(n == -1){ + piperror = "write error 2"; + *status = 1; + } + nbytes += n; + } + + *nbytesp = nbytes; + return *status; +} + +static int +chkhdr(char *s, int n) +{ + int i; + Rune r; + + for(i = 0; i < n; ){ + if(!fullrune(s + i, n - i)) + return -1; + i += chartorune(&r, s + i); + if(r == Runeerror) + return -1; + } + return 0; +} + +static void +fancymsg(int status) +{ + static char msg[2*ERRMAX], *p, *e; + + if(!status) + return; + + p = seprint(msg, msg+ERRMAX, "%s: ", piperror); + rerrstr(p, ERRMAX); + piperror = msg; +} + +/* + * pipe message to mailer with the following transformations: + * - change \r\n into \n. + * - add sender's domain to any addrs with no domain + * - add a From: if none of From:, Sender:, or Replyto: exists + * - add a Received: line + * - elide leading dot + */ +int +pipemsg(int *byteswritten) +{ + char *cp; + int n, nbytes, sawdot, status; + String *hdr, *line; + + pipesig(&status); /* set status to 1 on write to closed pipe */ + sawdot = 0; + status = 0; + werrstr(""); + piperror = nil; + + /* + * add a 'From ' line as envelope and Received: stamp + */ + nbytes = 0; + nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n", + s_to_c(senders.first->p), thedate()); + nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him); + if(nci->rsys) + nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys); + nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate()); + + /* + * read first 16k obeying '.' escape. we're assuming + * the header will all be there. + */ + line = s_new(); + hdr = s_new(); + while(s_len(hdr) < 16*1024){ + n = getcrnl(s_reset(line), &bin); + + /* eof or error ends the message */ + if(n <= 0){ + piperror = "header read error"; + status = 1; + break; + } + + cp = s_to_c(line); + if(chkhdr(cp, s_len(line)) == -1){ + status = 1; + piperror = "mail refused: illegal header chars"; + break; + } + + /* a line with only a '.' ends the message */ + if(cp[0] == '.' && cp[1] == '\n'){ + sawdot = 1; + break; + } + if(cp[0] == '.'){ + cp++; + n--; + } + s_append(hdr, cp); + nbytes += n; + if(*cp == '\n') + break; } - nbytes += nonhdr; + if(status == 0) + parseheader(hdr, &nbytes, &status); s_free(hdr); /* * pass rest of message to mailer. take care of '.' * escapes. */ - while(sawdot == 0){ + for(;;){ n = getcrnl(s_reset(line), &bin); /* eof or error ends the message */ + if(n < 0){ + piperror = "body read error"; + status = 1; + } if(n <= 0) break; /* a line with only a '.' ends the message */ cp = s_to_c(line); - if(n == 2 && *cp == '.' && *(cp+1) == '\n'){ + if(cp[0] == '.' && cp[1] == '\n'){ sawdot = 1; break; } @@ -1193,37 +1257,29 @@ pipemsg(int *byteswritten) if(status == 0 && Bwrite(pp->std[0]->fp, cp, n) < 0){ piperror = "write error 3"; status = 1; + break; } } s_free(line); - if(sawdot == 0){ + if(status == 0 && sawdot == 0){ /* message did not terminate normally */ - snprint(pipbuf, sizeof pipbuf, "network eof: %r"); + snprint(pipbuf, sizeof pipbuf, "network eof no dot: %r"); piperror = pipbuf; - if (pp->pid > 0) { - syskillpg(pp->pid); - /* none can't syskillpg, so try a variant */ - sleep(500); - syskill(pp->pid); - } status = 1; } - if(status == 0 && Bflush(pp->std[0]->fp) < 0){ piperror = "write error 4"; status = 1; } - if (debug) { - stamp(); - fprint(2, "at end of message; %s .\n", - (sawdot? "saw": "didn't see")); - } + if(status != 0) + syskill(pp->pid); stream_free(pp->std[0]); pp->std[0] = 0; *byteswritten = nbytes; pipesigoff(); - if(status && !piperror) + if(status && piperror == nil) piperror = "write on closed pipe"; + fancymsg(status); return status; } @@ -1233,8 +1289,8 @@ firstline(char *x) char *p; static char buf[128]; - strncpy(buf, x, sizeof(buf)); - buf[sizeof(buf)-1] = 0; + strncpy(buf, x, sizeof buf); + buf[sizeof buf - 1] = 0; p = strchr(buf, '\n'); if(p) *p = 0; @@ -1248,6 +1304,7 @@ sendermxcheck(void) char *cp, *senddom, *user, *who; Waitmsg *w; + senddom = 0; who = s_to_c(senders.first->p); if(strcmp(who, "/dev/null") == 0){ /* /dev/null can only send to one rcpt at a time */ @@ -1256,13 +1313,14 @@ sendermxcheck(void) "recipients"); return -1; } - return 0; + /* 4408 spf ยง2.2 notes that 2821 says /dev/null == postmaster@domain */ + senddom = smprint("%s!postmaster", him); } if(access("/mail/lib/validatesender", AEXEC) < 0) return 0; - - senddom = strdup(who); + if(!senddom) + senddom = strdup(who); if((cp = strchr(senddom, '!')) == nil){ werrstr("rejected: domainless sender %s", who); free(senddom); @@ -1270,6 +1328,12 @@ sendermxcheck(void) } *cp++ = 0; user = cp; + /* shellchars isn't restrictive. should probablly disallow specialchars */ + if(shellchars(senddom) || shellchars(user) || shellchars(him)){ + werrstr("rejected: evil sender/domain/helo"); + free(senddom); + return -1; + } switch(pid = fork()){ case -1: @@ -1281,7 +1345,7 @@ sendermxcheck(void) * to allow validatesender to implement SPF eventually. */ execl("/mail/lib/validatesender", "validatesender", - "-n", nci->root, senddom, user, nil); + "-n", nci->root, senddom, user, nci->rsys, him, nil); _exits("exec validatesender: %r"); default: break; @@ -1307,20 +1371,42 @@ sendermxcheck(void) * skip over validatesender 143123132: prefix from rc. */ cp = strchr(w->msg, ':'); - if(cp && *(cp+1) == ' ') - werrstr("%s", cp+2); + if(cp && cp[1] == ' ') + werrstr("%s", cp + 2); else werrstr("%s", w->msg); free(w); return -1; } +int +refused(char *e) +{ + return e && strstr(e, "mail refused") != nil; +} + +/* + * if a message appeared on stderr, despite good status, + * log it. this can happen if rewrite.in contains a bad + * r.e., for example. + */ +void +logerrors(String *err) +{ + char *s; + + s = s_to_c(err); + if(*s == 0) + return; + syslog(0, "smtpd", "%s returned good status, but said: %s", + s_to_c(mailer), s); +} + void data(void) { + char *cp, *ep, *e, buf[ERRMAX]; int status, nbytes; - char *cp, *ep; - char errx[ERRMAX]; Link *l; String *cmd, *err; @@ -1337,122 +1423,82 @@ data(void) return; } if(!trusted && sendermxcheck()){ - rerrstr(errx, sizeof errx); - if(strncmp(errx, "rejected:", 9) == 0) - reply("554 5.7.1 %s\r\n", errx); + rerrstr(buf, sizeof buf); + if(strncmp(buf, "rejected:", 9) == 0) + reply("554 5.7.1 %s\r\n", buf); else - reply("450 4.7.0 %s\r\n", errx); + reply("450 4.7.0 %s\r\n", buf); for(l=rcvers.first; l; l=l->next) syslog(0, "smtpd", "[%s/%s] %s -> %s sendercheck: %s", him, nci->rsys, s_to_c(senders.first->p), - s_to_c(l->p), errx); + s_to_c(l->p), buf); rejectcount++; return; } + /* + * allow 145 more minutes to move the data + */ cmd = startcmd(); if(cmd == 0) return; - reply("354 Input message; end with <CRLF>.<CRLF>\r\n"); - if(debug){ - seek(2, 0, 2); - stamp(); - fprint(2, "# sent 354; accepting DATA %s\n", thedate()); - } - - - /* - * allow 145 more minutes to move the data - */ alarm(145*60*1000); - + piperror = nil; status = pipemsg(&nbytes); - - /* - * read any error messages - */ err = s_new(); - if (debug) { - stamp(); - fprint(2, "waiting for upas/send to close stderr\n"); - } while(s_read_line(pp->std[2]->fp, err)) ; - alarm(0); atnotify(catchalarm, 0); - if (debug) { - stamp(); - fprint(2, "waiting for upas/send to exit\n"); - } status |= proc_wait(pp); if(debug){ seek(2, 0, 2); - stamp(); - fprint(2, "# %d upas/send status %#ux at %s\n", - getpid(), status, thedate()); + fprint(2, "%d status %ux\n", getpid(), status); if(*s_to_c(err)) - fprint(2, "# %d error %s\n", getpid(), s_to_c(err)); + fprint(2, "%d error %s\n", getpid(), s_to_c(err)); } /* * if process terminated abnormally, send back error message */ + if(status && (refused(piperror) || refused(s_to_c(err)))){ + filterstate = BLOCKED; + status = 0; + } if(status){ - int code; - char *ecode; - - if(strstr(s_to_c(err), "mail refused")){ - syslog(0, "smtpd", "++[%s/%s] %s %s refused: %s", - him, nci->rsys, s_to_c(senders.first->p), - s_to_c(cmd), firstline(s_to_c(err))); - code = 554; - ecode = "5.0.0"; - } else { - syslog(0, "smtpd", "++[%s/%s] %s %s %s%s%sreturned %#q %s", - him, nci->rsys, - s_to_c(senders.first->p), s_to_c(cmd), - piperror? "error during pipemsg: ": "", - piperror? piperror: "", - piperror? "; ": "", - pp->waitmsg->msg, firstline(s_to_c(err))); - code = 450; - ecode = "4.0.0"; - } + buf[0] = 0; + if(piperror != nil) + snprint(buf, sizeof buf, "pipemesg: %s; ", piperror); + syslog(0, "smtpd", "++[%s/%s] %s %s %sreturned %#q %s", + him, nci->rsys, s_to_c(senders.first->p), + s_to_c(cmd), buf, + pp->waitmsg->msg, firstline(s_to_c(err))); for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){ *ep++ = 0; - reply("%d-%s %s\r\n", code, ecode, cp); + reply("450-4.0.0 %s\r\n", cp); } - reply("%d %s mail process terminated abnormally\r\n", - code, ecode); + reply("450 4.0.0 mail process terminated abnormally\r\n"); + rejectcount++; } else { - /* - * if a message appeared on stderr, despite good status, - * log it. this can happen if rewrite.in contains a bad - * r.e., for example. - */ - if(*s_to_c(err)) - syslog(0, "smtpd", - "%s returned good status, but said: %s", - s_to_c(mailer), s_to_c(err)); - - if(filterstate == BLOCKED) - reply("554 5.7.1 we believe this is spam. " - "we don't accept it.\r\n"); - else if(filterstate == DELAY) + if(filterstate == BLOCKED){ + e = firstline(s_to_c(err)); + if(e[0] == 0) + e = piperror; + if(e == nil) + e = "we believe this is spam."; + syslog(0, "smtpd", "++[%s/%s] blocked: %s", him, nci->rsys, e); + reply("554 5.7.1 %s\r\n", e); + rejectcount++; + }else if(filterstate == DELAY){ + logerrors(err); reply("450 4.3.0 There will be a delay in delivery " "of this message.\r\n"); - else { + }else{ + logerrors(err); reply("250 2.5.0 sent\r\n"); logcall(nbytes); - if(debug){ - seek(2, 0, 2); - stamp(); - fprint(2, "# %d sent 250 reply %s\n", - getpid(), thedate()); - } } } proc_free(pp); @@ -1475,6 +1521,8 @@ data(void) int rejectcheck(void) { + if(rejectcount) + sleep(1000 * (4<<rejectcount)); if(rejectcount > MAXREJECTS){ syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys); reply("554 5.5.0 too many errors. transaction failed.\r\n"); @@ -1518,9 +1566,9 @@ s_dec64(String *sin) * if the string is coming from smtpd.y, it will have no nl. * if it is coming from getcrnl below, it will have an nl. */ - if (*(s_to_c(sin)+lin-1) == '\n') + if (*(s_to_c(sin) + lin - 1) == '\n') lin--; - sout = s_newalloc(lin+1); + sout = s_newalloc(lin + 1); lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin); if (lout < 0) { s_free(sout); @@ -1567,6 +1615,32 @@ starttls(void) syslog(0, "smtpd", "started TLS with %s", him); } +int +passauth(char *u, char *secret) +{ + char response[2*MD5dlen + 1]; + uchar digest[MD5dlen]; + int i; + AuthInfo *ai; + Chalstate *cs; + + if((cs = auth_challenge("proto=cram role=server")) == nil) + return -1; + hmac_md5((uchar*)cs->chal, strlen(cs->chal), + (uchar*)secret, strlen(secret), digest, nil); + for(i = 0; i < MD5dlen; i++) + snprint(response + 2*i, sizeof response - 2*i, "%2.2ux", digest[i]); + cs->user = u; + cs->resp = response; + cs->nresp = strlen(response); + ai = auth_response(cs); + if(ai == nil) + return -1; + auth_freechal(cs); + auth_freeAI(ai); + return 0; +} + void auth(String *mech, String *resp) { @@ -1576,19 +1650,19 @@ auth(String *mech, String *resp) String *s_resp1_64 = nil, *s_resp2_64 = nil, *s_resp1 = nil; String *s_resp2 = nil; - if (rejectcheck()) + if(rejectcheck()) goto bomb_out; syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech), "(protected)", him); - if (authenticated) { + if(authenticated) { bad_sequence: rejectcount++; reply("503 5.5.2 Bad sequence of commands\r\n"); goto bomb_out; } - if (cistrcmp(s_to_c(mech), "plain") == 0) { + if(cistrcmp(s_to_c(mech), "plain") == 0){ if (!passwordinclear) { rejectcount++; reply("538 5.7.1 Encryption required for requested " @@ -1611,12 +1685,13 @@ auth(String *mech, String *resp) memset(s_to_c(s_resp1_64), 'X', s_len(s_resp1_64)); user = s_to_c(s_resp1) + strlen(s_to_c(s_resp1)) + 1; pass = user + strlen(user) + 1; - ai = auth_userpasswd(user, pass); - authenticated = ai != nil; +// ai = auth_userpasswd(user, pass); +// authenticated = ai != nil; +authenticated = passauth(user, pass) != -1; memset(pass, 'X', strlen(pass)); goto windup; } - else if (cistrcmp(s_to_c(mech), "login") == 0) { + else if(cistrcmp(s_to_c(mech), "login") == 0){ if (!passwordinclear) { rejectcount++; reply("538 5.7.1 Encryption required for requested " @@ -1628,7 +1703,8 @@ auth(String *mech, String *resp) s_resp1_64 = s_new(); if (getcrnl(s_resp1_64, &bin) <= 0) goto bad_sequence; - } + }else + s_resp1_64 = resp; reply("334 UGFzc3dvcmQ6\r\n"); s_resp2_64 = s_new(); if (getcrnl(s_resp2_64, &bin) <= 0) @@ -1656,7 +1732,7 @@ windup: } goto bomb_out; } - else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) { + else if(cistrcmp(s_to_c(mech), "cram-md5") == 0){ char *resp, *t; chs = auth_challenge("proto=cram role=server"); diff --git a/sys/src/cmd/upas/smtp/spam.c b/sys/src/cmd/upas/smtp/spam.c index 4b7fe4134..20ef9f944 100644 --- a/sys/src/cmd/upas/smtp/spam.c +++ b/sys/src/cmd/upas/smtp/spam.c @@ -65,10 +65,10 @@ actstr(int a) static char buf[32]; Keyword *p; - for(p=actions; p->name; p++) + for(p = actions; p->name; p++) if(p->code == a) return p->name; - if(a==NONE) + if(a == NONE) return "none"; sprint(buf, "%d", a); return buf; @@ -94,13 +94,13 @@ getaction(char *s, char *type) int istrusted(char *s) { - char buf[1024]; + char buf[Pathlen]; if(s == nil || *s == 0) return 0; snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s); - return access(buf,0) >= 0; + return access(buf, 0) >= 0; } void @@ -130,7 +130,7 @@ getconf(void) cp = getline(bp); if(cp == 0) break; - p = cp+strlen(cp)+1; + p = cp + strlen(cp) + 1; switch(findkey(cp, options)){ case NORELAY: if(fflag == 0 && strcmp(p, "on") == 0) @@ -157,7 +157,7 @@ getconf(void) s = s_new(); s_append(s, p); listadd(&ourdoms, s); - p += strlen(p)+1; + p += strlen(p) + 1; } break; default: @@ -178,7 +178,7 @@ usermatch(char *pathuser, char *specuser) { int n; - n = strlen(specuser)-1; + n = strlen(specuser) - 1; if(specuser[n] == '*'){ if(n == 0) /* match everything */ return 0; @@ -195,9 +195,9 @@ dommatch(char *pathdom, char *specdom) if (*specdom == '*'){ if (specdom[1] == '.' && specdom[2]){ specdom += 2; - n = strlen(pathdom)-strlen(specdom); + n = strlen(pathdom) - strlen(specdom); if(n == 0 || (n > 0 && pathdom[n-1] == '.')) - return strcmp(pathdom+n, specdom); + return strcmp(pathdom + n, specdom); return n; } } @@ -261,10 +261,10 @@ getline(Biobuf *bp) return 0; n = Blinelen(bp); cp[n-1] = 0; - if(buf == 0 || bufsize < n+1){ + if(buf == 0 || bufsize < n + 1){ bufsize += 512; - if(bufsize < n+1) - bufsize = n+1; + if(bufsize < n + 1) + bufsize = n + 1; buf = realloc(buf, bufsize); if(buf == 0) break; @@ -328,7 +328,7 @@ found: s_append(path, "["); s_append(path, nci->rsys); s_append(path, "]!"); - s_append(path, cp+3); + s_append(path, cp + 3); s_terminate(path); s_free(lpath); return 0; @@ -348,7 +348,7 @@ found: for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */ *cp = tolower(*cp); - for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){ + for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp + 1){ *cp = 0; if(!isourdom(s)){ s_free(lpath); @@ -367,13 +367,12 @@ masquerade(String *path, char *him) int rv = 0; if(debug) - fprint(2, "masquerade(%s) ", s_to_c(path)); + fprint(2, "masquerade(%s)\n", s_to_c(path)); - if(trusted || path == nil) { - if(debug) - fprint(2, "0\n"); + if(trusted) + return 0; + if(path == nil) return 0; - } lpath = s_copy(s_to_c(path)); @@ -388,7 +387,7 @@ masquerade(String *path, char *him) if(isourdom(s)) rv = 1; } else if((cp = strrchr(s, '@')) != nil){ - if(isourdom(cp+1)) + if(isourdom(cp + 1)) rv = 1; } else { if(isourdom(him)) @@ -396,8 +395,6 @@ masquerade(String *path, char *him) } s_free(lpath); - if (debug) - fprint(2, "%d\n", rv); return rv; } @@ -426,15 +423,15 @@ cidrcheck(char *cp) if(strchr(cp, '/') == 0){ m = 0xff000000; p = cp; - for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.')) - m = (m>>8)|0xff000000; + for(p = strchr(p, '.'); p && p[1]; p = strchr(p + 1, '.')) + m = (m>>8)|0xff000000; /* force at least a class B */ m |= 0xffff0000; } - if((v4peerip&m) == a) + if((v4peerip & m) == a) return 1; - cp += strlen(cp)+1; + cp += strlen(cp) + 1; } return 0; } @@ -470,10 +467,10 @@ dumpfile(char *sender) cp = ctime(time(0)); cp[7] = 0; if(cp[8] == ' ') - sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]); + sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp + 4, cp[9]); else - sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]); - cp = buf+strlen(buf); + sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp + 4, cp[8], cp[9]); + cp = buf + strlen(buf); if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0) return "/dev/null"; h = 0; @@ -484,7 +481,7 @@ dumpfile(char *sender) sprint(cp, "/%lud", h); if(access(buf, 0) >= 0) continue; - fd = syscreate(buf, ORDWR, 0666); + fd = create(buf, ORDWR, 0666); if(fd >= 0){ if(debug) fprint(2, "saving in %s\n", buf); @@ -586,7 +583,7 @@ optoutofspamfilter(char *addr) rv = 0; f = smprint("/mail/box/%s/nospamfiltering", p); if(f != nil){ - rv = access(f, 0)==0; + rv = access(f, 0) == 0; free(f); } |