diff options
author | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2005-10-29 16:26:44 +0000 |
commit | 5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch) | |
tree | 8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/smtp | |
parent | cd3745196389579fb78b9b01ef1daefb5a57aa71 (diff) | |
download | plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2 plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip |
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/smtp')
-rw-r--r-- | src/cmd/upas/smtp/greylist.c | 274 | ||||
-rw-r--r-- | src/cmd/upas/smtp/mkfile | 54 | ||||
-rw-r--r-- | src/cmd/upas/smtp/mxdial.c | 333 | ||||
-rw-r--r-- | src/cmd/upas/smtp/rfc822.tab.c | 1260 | ||||
-rw-r--r-- | src/cmd/upas/smtp/rfc822.tab.h | 98 | ||||
-rw-r--r-- | src/cmd/upas/smtp/rfc822.y | 778 | ||||
-rw-r--r-- | src/cmd/upas/smtp/rmtdns.c | 58 | ||||
-rw-r--r-- | src/cmd/upas/smtp/smtp.c | 1122 | ||||
-rw-r--r-- | src/cmd/upas/smtp/smtp.h | 61 | ||||
-rw-r--r-- | src/cmd/upas/smtp/smtpd.c | 1494 | ||||
-rw-r--r-- | src/cmd/upas/smtp/smtpd.h | 68 | ||||
-rw-r--r-- | src/cmd/upas/smtp/smtpd.y | 317 | ||||
-rw-r--r-- | src/cmd/upas/smtp/spam.c | 591 | ||||
-rw-r--r-- | src/cmd/upas/smtp/y.tab.h | 25 |
14 files changed, 6533 insertions, 0 deletions
diff --git a/src/cmd/upas/smtp/greylist.c b/src/cmd/upas/smtp/greylist.c new file mode 100644 index 00000000..915e688e --- /dev/null +++ b/src/cmd/upas/smtp/greylist.c @@ -0,0 +1,274 @@ +#include "common.h" +#include "smtpd.h" +#include "smtp.h" +#include <ctype.h> +#include <ip.h> +#include <ndb.h> + +typedef struct { + int existed; /* these two are distinct to cope with errors */ + int created; + int noperm; + long mtime; /* mod time, iff it already existed */ +} Greysts; + +/* + * There's a bit of a problem with yahoo; they apparently have a vast + * pool of machines that all run the same queue(s), so a 451 retry can + * come from a different IP address for many, many retries, and it can + * take ~5 hours for the same IP to call us back. Various other goofballs, + * notably the IEEE, try to send mail just before 9 AM, then refuse to try + * again until after 5 PM. Doh! + */ +enum { + Nonspammax = 14*60*60, /* must call back within this time if real */ +}; +static char whitelist[] = "/mail/lib/whitelist"; + +/* + * matches ip addresses or subnets in whitelist against nci->rsys. + * ignores comments and blank lines in /mail/lib/whitelist. + */ +static int +onwhitelist(void) +{ + int lnlen; + char *line, *parse; + char input[128]; + uchar ip[IPaddrlen], ipmasked[IPaddrlen]; + uchar mask4[IPaddrlen], addr4[IPaddrlen]; + uchar mask[IPaddrlen], addr[IPaddrlen], addrmasked[IPaddrlen]; + Biobuf *wl; + static int beenhere; + static allzero[IPaddrlen]; + + if (!beenhere) { + beenhere = 1; + fmtinstall('I', eipfmt); + } + + parseip(ip, nci->rsys); + wl = Bopen(whitelist, OREAD); + if (wl == nil) + return 1; + while ((line = Brdline(wl, '\n')) != nil) { + if (line[0] == '#' || line[0] == '\n') + continue; + lnlen = Blinelen(wl); + line[lnlen-1] = '\0'; /* clobber newline */ + + /* default mask is /32 (v4) or /128 (v6) for bare IP */ + parse = line; + if (strchr(line, '/') == nil) { + strncpy(input, line, sizeof input - 5); + if (strchr(line, '.') != nil) + strcat(input, "/32"); + else + strcat(input, "/128"); + parse = input; + } + /* sorry, dave; where's parsecidr for v4 or v6? */ + v4parsecidr(addr4, mask4, parse); + v4tov6(addr, addr4); + v4tov6(mask, mask4); + + maskip(addr, mask, addrmasked); + maskip(ip, mask, ipmasked); + if (memcmp(ipmasked, addrmasked, IPaddrlen) == 0) + break; + } + Bterm(wl); + return line != nil; +} + +static int mkdirs(char *); + +/* + * if any directories leading up to path don't exist, create them. + * modifies but restores path. + */ +static int +mkpdirs(char *path) +{ + int rv = 0; + char *sl = strrchr(path, '/'); + + if (sl != nil) { + *sl = '\0'; + rv = mkdirs(path); + *sl = '/'; + } + return rv; +} + +/* + * if path or any directories leading up to it don't exist, create them. + * modifies but restores path. + */ +static int +mkdirs(char *path) +{ + int fd; + + if (access(path, AEXIST) >= 0) + return 0; + + /* make presumed-missing intermediate directories */ + if (mkpdirs(path) < 0) + return -1; + + /* make final directory */ + fd = create(path, OREAD, 0777|DMDIR); + if (fd < 0) + /* + * we may have lost a race; if the directory now exists, + * it's okay. + */ + return access(path, AEXIST) < 0? -1: 0; + close(fd); + return 0; +} + +static long +getmtime(char *file) +{ + long mtime = -1; + Dir *ds = dirstat(file); + + if (ds != nil) { + mtime = ds->mtime; + free(ds); + } + return mtime; +} + +static void +tryaddgrey(char *file, Greysts *gsp) +{ + int fd = create(file, OWRITE|OEXCL, 0444|DMEXCL); + + gsp->created = (fd >= 0); + if (fd >= 0) { + close(fd); + gsp->existed = 0; /* just created; couldn't have existed */ + } else { + /* + * why couldn't we create file? it must have existed + * (or we were denied perm on parent dir.). + * if it existed, fill in gsp->mtime; otherwise + * make presumed-missing intermediate directories. + */ + gsp->existed = access(file, AEXIST) >= 0; + if (gsp->existed) + gsp->mtime = getmtime(file); + else if (mkpdirs(file) < 0) + gsp->noperm = 1; + } +} + +static void +addgreylist(char *file, Greysts *gsp) +{ + tryaddgrey(file, gsp); + if (!gsp->created && !gsp->existed && !gsp->noperm) + /* retry the greylist entry with parent dirs created */ + tryaddgrey(file, gsp); +} + +static int +recentcall(Greysts *gsp) +{ + long delay = time(0) - gsp->mtime; + + if (!gsp->existed) + return 0; + /* reject immediate call-back; spammers are doing that now */ + return delay >= 30 && delay <= Nonspammax; +} + +/* + * policy: if (caller-IP, my-IP, rcpt) is not on the greylist, + * reject this message as "451 temporary failure". if the caller is real, + * he'll retry soon, otherwise he's a spammer. + * at the first rejection, create a greylist entry for (my-ip, caller-ip, + * rcpt, time), where time is the file's mtime. if they call back and there's + * already a greylist entry, and it's within the allowed interval, + * add their IP to the append-only whitelist. + * + * greylist files can be removed at will; at worst they'll cause a few + * extra retries. + */ + +static int +isrcptrecent(char *rcpt) +{ + char *user; + char file[256]; + Greysts gs; + Greysts *gsp = &gs; + + if (rcpt[0] == '\0' || strchr(rcpt, '/') != nil || + strcmp(rcpt, ".") == 0 || strcmp(rcpt, "..") == 0) + return 0; + + /* shorten names to fit pre-fossil or pre-9p2000 file servers */ + user = strrchr(rcpt, '!'); + if (user == nil) + user = rcpt; + else + user++; + + /* check & try to update the grey list entry */ + snprint(file, sizeof file, "/mail/grey/%s/%s/%s", + nci->lsys, nci->rsys, user); + memset(gsp, 0, sizeof *gsp); + addgreylist(file, gsp); + + /* if on greylist already and prior call was recent, add to whitelist */ + if (gsp->existed && recentcall(gsp)) { + syslog(0, "smtpd", + "%s/%s was grey; adding IP to white", nci->rsys, rcpt); + return 1; + } else if (gsp->existed) + syslog(0, "smtpd", "call for %s/%s was seconds ago or long ago", + nci->rsys, rcpt); + else + syslog(0, "smtpd", "no call registered for %s/%s; registering", + nci->rsys, rcpt); + return 0; +} + +void +vfysenderhostok(void) +{ + char *fqdn; + int recent = 0; + Link *l; + + if (onwhitelist()) + return; + + for (l = rcvers.first; l; l = l->next) + if (isrcptrecent(s_to_c(l->p))) + recent = 1; + + /* if on greylist already and prior call was recent, add to whitelist */ + if (recent) { + int fd = create(whitelist, OWRITE, 0666|DMAPPEND); + + if (fd >= 0) { + seek(fd, 0, 2); /* paranoia */ + if ((fqdn = csgetvalue(nil, "ip", nci->rsys, "dom", nil)) != nil) + fprint(fd, "# %s\n%s\n\n", fqdn, nci->rsys); + else + fprint(fd, "# unknown\n%s\n\n", nci->rsys); + close(fd); + } + } else { + syslog(0, "smtpd", + "no recent call from %s for a rcpt; rejecting with temporary failure", + nci->rsys); + reply("451 please try again soon from the same IP.\r\n"); + exits("no recent call for a rcpt"); + } +} diff --git a/src/cmd/upas/smtp/mkfile b/src/cmd/upas/smtp/mkfile new file mode 100644 index 00000000..722f1357 --- /dev/null +++ b/src/cmd/upas/smtp/mkfile @@ -0,0 +1,54 @@ +<$PLAN9/src/mkhdr + +TARG = # smtpd\ + smtp\ + +OFILES= + +LIB=../common/libcommon.a\ + $PLAN9/lib/libthread.a # why do i have to explicitly put this? + +HFILES=../common/common.h\ + ../common/sys.h\ + smtpd.h\ + smtp.h\ + +BIN=$PLAN9/bin/upas +UPDATE=\ + greylist.c\ + mkfile\ + mxdial.c\ + rfc822.y\ + rmtdns.c\ + smtpd.y\ + spam.c\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + ${TARG:%=%.c}\ + +<$PLAN9/src/mkmany +CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"' + +$O.smtpd: smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O +$O.smtp: rfc822.tab.$O mxdial.$O + +smtpd.$O: smtpd.h + +smtp.$O to.$O: smtp.h + +smtpd.tab.c: smtpd.y smtpd.h + yacc -o xxx smtpd.y + sed 's/yy/zz/g' < xxx > $target + rm xxx + +rfc822.tab.c: rfc822.y smtp.h + 9 yacc -d -o $target rfc822.y + +clean:V: + rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG + +../common/libcommon.a$O: + @{ + cd ../common + mk + } diff --git a/src/cmd/upas/smtp/mxdial.c b/src/cmd/upas/smtp/mxdial.c new file mode 100644 index 00000000..aea5f256 --- /dev/null +++ b/src/cmd/upas/smtp/mxdial.c @@ -0,0 +1,333 @@ +#include "common.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; +}; +static Mx mx[Nmx]; + +Ndb *db; +extern int debug; + +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); +extern int cistrcmp(char*, char*); + +int +mxdial(char *addr, char *ddomain, char *gdomain) +{ + int fd; + DS ds; + char err[Errlen]; + + addr = netmkaddr(addr, 0, "smtp"); + dial_string_parse(addr, &ds); + + /* try connecting to destination or any of it's mail routers */ + fd = callmx(&ds, addr, ddomain); + + /* try our mail gateway */ + rerrstr(err, sizeof(err)); + if(fd < 0 && gdomain && strstr(err, "can't translate") != 0) { + fprint(2,"dialing %s\n",gdomain); + fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); + } + + return fd; +} + +/* + * take an address and return all the mx entries for it, + * most preferred first + */ +static int +callmx(DS *ds, char *dest, char *domain) +{ + 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); + } + + /* 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"); + return -1; + } + } + + /* sort by preference */ + if(nmx > 1) + qsort(mx, nmx, sizeof(Mx), compar); + + /* dial each one in turn */ + for(i = 0; i < nmx; i++){ + 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); + fd = dial(addr, 0, 0, 0); + if(fd >= 0) + return fd; + } + return -1; +} + +/* + * 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) +{ + int n; + + /* 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); + } + } + + return n; +} + +static int +mxlookup1(DS *ds, char *domain) +{ + char buf[1024]; + char dnsname[Maxstring]; + char *fields[4]; + int i, n, fd, nmx; + + snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir); + + fd = open(dnsname, ORDWR); + if(fd < 0) + return 0; + + nmx = 0; + snprint(buf, sizeof(buf), "%s mx", ds->host); + if(debug) + fprint(2, "sending %s '%s'\n", dnsname, buf); + n = write(fd, buf, strlen(buf)); + 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; + + if(strchr(domain, '.') == 0) + strcpy(domain, fields[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); + } + + /* + * 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++; + } + + /* + * 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; + if(write(fd, buf, strlen(buf)) < 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--; + } + return nmx; +} + +static int +compar(void *a, void *b) +{ + return ((Mx*)a)->pref - ((Mx*)b)->pref; +} + +/* break up an address to its component parts */ +void +dial_string_parse(char *str, DS *ds) +{ + char *p, *p2; + + strncpy(ds->buf, str, sizeof(ds->buf)); + ds->buf[sizeof(ds->buf)-1] = 0; + + 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_meta(ds); +} + +#if 0 /* jpc */ +static void +expand_meta(DS *ds) +{ + 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; + } + + 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; + } + + 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; + } + 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; + + /* take only first one returned (quasi-bug) */ + if((p = strchr(ds->host, ' ')) != nil) + *p = 0; +} +#endif /* jpc */ + +static void +expand_meta(DS *ds) +{ + Ndb *db; + Ndbs s; + char *sys, *smtpserver; + + sys = sysname(); + db = ndbopen(unsharp("#9/ndb/local")); + fprint(2,"%s",ds->host); + smtpserver = ndbgetvalue(db, &s, "sys", sys, "smtp", nil); + snprint(ds->host,128,"%s",smtpserver); + fprint(2," exanded to %s\n",ds->host); + +} diff --git a/src/cmd/upas/smtp/rfc822.tab.c b/src/cmd/upas/smtp/rfc822.tab.c new file mode 100644 index 00000000..1f12e48e --- /dev/null +++ b/src/cmd/upas/smtp/rfc822.tab.c @@ -0,0 +1,1260 @@ + +#line 2 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +#include "common.h" +#include "smtp.h" +#include <ctype.h> + +char *yylp; /* next character to be lex'd */ +int yydone; /* tell yylex to give up */ +char *yybuffer; /* first parsed character */ +char *yyend; /* end of buffer to be parsed */ +Node *root; +Field *firstfield; +Field *lastfield; +Node *usender; +Node *usys; +Node *udate; +char *startfield, *endfield; +int originator; +int destination; +int date; +int received; +int messageid; +extern int yyerrflag; +#ifndef YYMAXDEPTH +#define YYMAXDEPTH 150 +#endif +#ifndef YYSTYPE +#define YYSTYPE int +#endif +YYSTYPE yylval; +YYSTYPE yyval; +#define WORD 57346 +#define DATE 57347 +#define RESENT_DATE 57348 +#define RETURN_PATH 57349 +#define FROM 57350 +#define SENDER 57351 +#define REPLY_TO 57352 +#define RESENT_FROM 57353 +#define RESENT_SENDER 57354 +#define RESENT_REPLY_TO 57355 +#define SUBJECT 57356 +#define TO 57357 +#define CC 57358 +#define BCC 57359 +#define RESENT_TO 57360 +#define RESENT_CC 57361 +#define RESENT_BCC 57362 +#define REMOTE 57363 +#define PRECEDENCE 57364 +#define MIMEVERSION 57365 +#define CONTENTTYPE 57366 +#define MESSAGEID 57367 +#define RECEIVED 57368 +#define MAILER 57369 +#define BADTOKEN 57370 +#define YYEOFCODE 1 +#define YYERRCODE 2 + +#line 246 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" + + +/* + * Initialize the parsing. Done once for each header field. + */ +void +yyinit(char *p, int len) +{ + yybuffer = p; + yylp = p; + yyend = p + len; + firstfield = lastfield = 0; + received = 0; +} + +/* + * keywords identifying header fields we care about + */ +typedef struct Keyword Keyword; +struct Keyword { + char *rep; + int val; +}; + +/* field names that we need to recognize */ +Keyword key[] = { + { "date", DATE }, + { "resent-date", RESENT_DATE }, + { "return_path", RETURN_PATH }, + { "from", FROM }, + { "sender", SENDER }, + { "reply-to", REPLY_TO }, + { "resent-from", RESENT_FROM }, + { "resent-sender", RESENT_SENDER }, + { "resent-reply-to", RESENT_REPLY_TO }, + { "to", TO }, + { "cc", CC }, + { "bcc", BCC }, + { "resent-to", RESENT_TO }, + { "resent-cc", RESENT_CC }, + { "resent-bcc", RESENT_BCC }, + { "remote", REMOTE }, + { "subject", SUBJECT }, + { "precedence", PRECEDENCE }, + { "mime-version", MIMEVERSION }, + { "content-type", CONTENTTYPE }, + { "message-id", MESSAGEID }, + { "received", RECEIVED }, + { "mailer", MAILER }, + { "who-the-hell-cares", WORD } +}; + +/* + * Lexical analysis for an rfc822 header field. Continuation lines + * are handled in yywhite() when skipping over white space. + * + */ +int +yylex(void) +{ + String *t; + int quoting; + int escaping; + char *start; + Keyword *kp; + int c, d; + +/* print("lexing\n"); /**/ + if(yylp >= yyend) + return 0; + if(yydone) + return 0; + + quoting = escaping = 0; + start = yylp; + yylval = malloc(sizeof(Node)); + yylval->white = yylval->s = 0; + yylval->next = 0; + yylval->addr = 0; + yylval->start = yylp; + for(t = 0; yylp < yyend; yylp++){ + c = *yylp & 0xff; + + /* dump nulls, they can't be in header */ + if(c == 0) + continue; + + if(escaping) { + escaping = 0; + } else if(quoting) { + switch(c){ + case '\\': + escaping = 1; + break; + case '\n': + d = (*(yylp+1))&0xff; + if(d != ' ' && d != '\t'){ + quoting = 0; + yylp--; + continue; + } + break; + case '"': + quoting = 0; + break; + } + } else { + switch(c){ + case '\\': + escaping = 1; + break; + case '(': + case ' ': + case '\t': + case '\r': + goto out; + case '\n': + if(yylp == start){ + yylp++; +/* print("lex(c %c)\n", c); /**/ + yylval->end = yylp; + return yylval->c = c; + } + goto out; + case '@': + case '>': + case '<': + case ':': + case ',': + case ';': + if(yylp == start){ + yylp++; + yylval->white = yywhite(); +/* print("lex(c %c)\n", c); /**/ + yylval->end = yylp; + return yylval->c = c; + } + goto out; + case '"': + quoting = 1; + break; + default: + break; + } + } + if(t == 0) + t = s_new(); + s_putc(t, c); + } +out: + yylval->white = yywhite(); + if(t) { + s_terminate(t); + } 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) + break; +/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/ + yylval->end = yylp; + return yylval->c = kp->val; +} + +void +yyerror(char *x) +{ + USED(x); + + /*fprint(2, "parse err: %s\n", x);/**/ +} + +/* + * parse white space and comments + */ +String * +yywhite(void) +{ + String *w; + int clevel; + int c; + int escaping; + + escaping = clevel = 0; + for(w = 0; yylp < yyend; yylp++){ + c = *yylp & 0xff; + + /* dump nulls, they can't be in header */ + if(c == 0) + continue; + + if(escaping){ + escaping = 0; + } else if(clevel) { + switch(c){ + case '\n': + /* + * look for multiline fields + */ + if(*(yylp+1)==' ' || *(yylp+1)=='\t') + break; + else + goto out; + case '\\': + escaping = 1; + break; + case '(': + clevel++; + break; + case ')': + clevel--; + break; + } + } else { + switch(c){ + case '\\': + escaping = 1; + break; + case '(': + clevel++; + break; + case ' ': + case '\t': + case '\r': + break; + case '\n': + /* + * look for multiline fields + */ + if(*(yylp+1)==' ' || *(yylp+1)=='\t') + break; + else + goto out; + default: + goto out; + } + } + if(w == 0) + w = s_new(); + s_putc(w, c); + } +out: + if(w) + s_terminate(w); + return w; +} + +/* + * link two parsed entries together + */ +Node* +link2(Node *p1, Node *p2) +{ + Node *p; + + for(p = p1; p->next; p = p->next) + ; + p->next = p2; + return p1; +} + +/* + * link three parsed entries together + */ +Node* +link3(Node *p1, Node *p2, Node *p3) +{ + Node *p; + + for(p = p2; p->next; p = p->next) + ; + p->next = p3; + + for(p = p1; p->next; p = p->next) + ; + p->next = p2; + + return p1; +} + +/* + * make a:b, move all white space after both + */ +Node* +colon(Node *p1, Node *p2) +{ + if(p1->white){ + if(p2->white) + s_append(p1->white, s_to_c(p2->white)); + } else { + p1->white = p2->white; + p2->white = 0; + } + + s_append(p1->s, ":"); + if(p2->s) + s_append(p1->s, s_to_c(p2->s)); + + if(p1->end < p2->end) + p1->end = p2->end; + freenode(p2); + return p1; +} + +/* + * concatenate two fields, move all white space after both + */ +Node* +concat(Node *p1, Node *p2) +{ + char buf[2]; + + if(p1->white){ + if(p2->white) + s_append(p1->white, s_to_c(p2->white)); + } else { + p1->white = p2->white; + p2->white = 0; + } + + if(p1->s == nil){ + buf[0] = p1->c; + buf[1] = 0; + p1->s = s_new(); + s_append(p1->s, buf); + } + + if(p2->s) + s_append(p1->s, s_to_c(p2->s)); + else { + buf[0] = p2->c; + buf[1] = 0; + s_append(p1->s, buf); + } + + if(p1->end < p2->end) + p1->end = p2->end; + freenode(p2); + return p1; +} + +/* + * look for disallowed chars in the field name + */ +int +badfieldname(Node *p) +{ + for(; p; p = p->next){ + /* field name can't contain white space */ + if(p->white && p->next) + return 1; + } + return 0; +} + +/* + * mark as an address + */ +Node * +address(Node *p) +{ + p->addr = 1; + return 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 +freenode(Node *p) +{ + Node *tp; + + while(p){ + tp = p->next; + if(p->s) + s_free(p->s); + if(p->white) + s_free(p->white); + free(p); + p = tp; + } +} + + +/* + * an anonymous user + */ +Node* +nobody(Node *p) +{ + if(p->s) + s_free(p->s); + p->s = s_copy("pOsTmAsTeR"); + p->addr = 1; + return p; +} + +/* + * add anything that was dropped because of a parse error + */ +void +missing(Node *p) +{ + Node *np; + char *start, *end; + Field *f; + String *s; + + start = yybuffer; + if(lastfield != nil){ + for(np = lastfield->node; np; np = np->next) + start = np->end+1; + } + + end = p->start-1; + + if(end <= start) + return; + + if(strncmp(start, "From ", 5) == 0) + return; + + np = malloc(sizeof(Node)); + np->start = start; + np->end = end; + np->white = nil; + s = s_copy("BadHeader: "); + np->s = s_nappend(s, start, end-start); + np->next = nil; + + f = malloc(sizeof(Field)); + f->next = 0; + f->node = np; + f->source = 0; + if(firstfield) + lastfield->next = f; + else + firstfield = f; + lastfield = f; +} + +/* + * create a new field + */ +void +newfield(Node *p, int source) +{ + Field *f; + + missing(p); + + f = malloc(sizeof(Field)); + f->next = 0; + f->node = p; + f->source = source; + if(firstfield) + lastfield->next = f; + else + firstfield = f; + lastfield = f; + endfield = startfield; + startfield = yylp; +} + +/* + * fee a list of fields + */ +void +freefield(Field *f) +{ + Field *tf; + + while(f){ + tf = f->next; + freenode(f->node); + free(f); + f = tf; + } +} + +/* + * add some white space to a node + */ +Node* +whiten(Node *p) +{ + Node *tp; + + for(tp = p; tp->next; tp = tp->next) + ; + if(tp->white == 0) + tp->white = s_copy(" "); + return p; +} + +void +yycleanup(void) +{ + Field *f, *fnext; + Node *np, *next; + + for(f = firstfield; f; f = fnext){ + for(np = f->node; np; np = next){ + if(np->s) + s_free(np->s); + if(np->white) + s_free(np->white); + next = np->next; + free(np); + } + fnext = f->next; + free(f); + } + firstfield = lastfield = 0; +} +static const short yyexca[] = +{-1, 1, + 1, -1, + -2, 0, +-1, 47, + 1, 4, + -2, 0, +-1, 112, + 29, 72, + 31, 72, + 32, 72, + 35, 72, + -2, 74, +}; +#define YYNPROD 122 +#define YYPRIVATE 57344 +#define YYLAST 608 +static const short yyact[] = +{ + 112, 133, 136, 53, 121, 111, 134, 55, 109, 118, + 119, 116, 162, 171, 35, 48, 166, 54, 5, 166, + 179, 114, 115, 155, 49, 101, 100, 99, 95, 94, + 93, 92, 98, 91, 132, 90, 123, 89, 122, 88, + 87, 86, 85, 84, 83, 82, 97, 81, 80, 106, + 47, 46, 110, 117, 153, 168, 108, 2, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 65, 73, 66, + 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, + 78, 79, 124, 124, 49, 55, 177, 131, 110, 52, + 110, 110, 138, 137, 140, 141, 124, 124, 51, 120, + 124, 124, 124, 50, 102, 104, 135, 154, 31, 32, + 107, 157, 105, 14, 55, 55, 156, 13, 161, 117, + 117, 139, 158, 124, 142, 143, 144, 145, 146, 147, + 163, 164, 160, 12, 148, 149, 11, 157, 150, 151, + 152, 10, 156, 9, 8, 7, 3, 1, 0, 124, + 124, 124, 124, 124, 0, 169, 0, 0, 110, 165, + 0, 0, 170, 117, 0, 0, 0, 0, 173, 176, + 178, 0, 0, 0, 172, 0, 0, 0, 180, 0, + 0, 182, 183, 0, 0, 165, 165, 165, 165, 165, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 174, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 73, 66, 67, 68, 69, 70, 71, + 72, 74, 75, 76, 77, 78, 79, 0, 0, 128, + 130, 129, 125, 126, 127, 15, 0, 36, 16, 17, + 19, 103, 20, 18, 23, 22, 21, 30, 24, 26, + 28, 25, 27, 29, 0, 34, 37, 38, 39, 33, + 40, 0, 4, 0, 45, 44, 41, 42, 43, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 73, + 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, + 77, 78, 79, 0, 0, 96, 45, 44, 41, 42, + 43, 15, 0, 36, 16, 17, 19, 6, 20, 18, + 23, 22, 21, 30, 24, 26, 28, 25, 27, 29, + 0, 34, 37, 38, 39, 33, 40, 0, 4, 0, + 45, 44, 41, 42, 43, 15, 0, 36, 16, 17, + 19, 103, 20, 18, 23, 22, 21, 30, 24, 26, + 28, 25, 27, 29, 0, 34, 37, 38, 39, 33, + 40, 0, 0, 0, 45, 44, 41, 42, 43, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 73, + 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, + 77, 78, 79, 0, 0, 0, 0, 175, 113, 0, + 52, 56, 57, 58, 59, 60, 61, 62, 63, 64, + 65, 73, 66, 67, 68, 69, 70, 71, 72, 74, + 75, 76, 77, 78, 79, 0, 0, 0, 0, 0, + 113, 0, 52, 56, 57, 58, 59, 60, 61, 62, + 63, 64, 65, 73, 66, 67, 68, 69, 70, 71, + 72, 74, 75, 76, 77, 78, 79, 0, 0, 0, + 0, 0, 0, 159, 52, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 73, 66, 67, 68, 69, + 70, 71, 72, 74, 75, 76, 77, 78, 79, 0, + 0, 0, 0, 0, 0, 0, 52, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 73, 66, 67, + 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, + 79, 0, 0, 167, 0, 0, 113, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 73, 66, 67, + 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, + 79, 0, 0, 0, 0, 0, 113, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 73, 66, 67, + 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, + 79, 0, 0, 181, 56, 57, 58, 59, 60, 61, + 62, 63, 64, 65, 73, 66, 67, 68, 69, 70, + 71, 72, 74, 75, 76, 77, 78, 79 +}; +static const short yypact[] = +{ + 299,-1000,-1000, 22,-1000, 21, 54,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000, 19, 17, 15, 14, 13, + 12, 11, 10, 9, 7, 5, 3, 1, 0, -1, + -2, 265, -3, -4, -5,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000, 233, 233, 580, 397, + -9,-1000, 580, -26, -25,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000, + 333, 199, 199, 397, 461, 397, 397, 397, 397, 397, + 397, 397, 397, 397, 397, 199, 199,-1000,-1000, 199, + 199, 199,-1000, -6,-1000, 33, 580, -8,-1000,-1000, + 523,-1000,-1000, 429, 580, -23,-1000,-1000, 580, 580, +-1000,-1000, 199,-1000,-1000,-1000,-1000,-1000,-1000,-1000, +-1000,-1000, -15,-1000,-1000,-1000, 493,-1000,-1000, -15, +-1000,-1000, -15, -15, -15, -15, -15, -15, 199, 199, + 199, 199, 199, 47, 580, 397,-1000,-1000, -21,-1000, + -25, -26, 580,-1000,-1000,-1000, 397, 365, 580, 580, +-1000,-1000,-1000,-1000, -12,-1000,-1000, 553,-1000,-1000, + 580, 580,-1000,-1000 +}; +static const short yypgo[] = +{ + 0, 147, 57, 146, 18, 145, 144, 143, 141, 136, + 133, 117, 113, 8, 112, 0, 34, 110, 6, 4, + 38, 109, 108, 1, 106, 2, 5, 103, 17, 98, + 11, 3, 36, 86, 14 +}; +static const short yyr1[] = +{ + 0, 1, 1, 2, 2, 2, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 3, 6, 6, 6, 6, + 6, 6, 6, 5, 5, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 8, 8, 11, + 11, 12, 12, 10, 10, 21, 21, 21, 21, 9, + 9, 16, 16, 23, 23, 24, 24, 17, 17, 18, + 18, 18, 26, 26, 13, 13, 27, 27, 29, 29, + 28, 28, 31, 30, 25, 25, 20, 20, 32, 32, + 32, 32, 32, 32, 32, 19, 14, 33, 33, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 22, 22, 22, 22, 34, 34, 34, + 34, 34 +}; +static const short yyr2[] = +{ + 0, 1, 3, 1, 2, 3, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 6, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 2, 3, 2, 3, 2, + 3, 2, 3, 2, 3, 2, 3, 3, 2, 3, + 2, 3, 2, 3, 2, 1, 1, 1, 1, 3, + 2, 1, 3, 1, 1, 4, 3, 1, 3, 1, + 2, 1, 3, 2, 3, 1, 2, 4, 1, 1, + 3, 3, 1, 1, 1, 2, 1, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 6, 1, 3, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, + 1, 1 +}; +static const short yychk[] = +{ +-1000, -1, -2, -3, 29, -4, 8, -5, -6, -7, + -8, -9, -10, -11, -12, 2, 5, 6, 10, 7, + 9, 13, 12, 11, 15, 18, 16, 19, 17, 20, + 14, -22, -21, 26, 22, -34, 4, 23, 24, 25, + 27, 33, 34, 35, 32, 31, 29, 29, -13, 30, + -27, -29, 35, -31, -28, -15, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, + 19, 20, 21, 14, 22, 23, 24, 25, 26, 27, + 29, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, -34, -15, 30, + 30, 30, -2, 8, -2, -14, -15, -17, -18, -13, + -25, -26, -15, 33, 30, 31, -30, -15, 35, 35, + -4, -19, -20, -32, -15, 33, 34, 35, 30, 32, + 31, -19, -16, -23, -18, -24, -25, -13, -18, -16, + -18, -18, -16, -16, -16, -16, -16, -16, -20, -20, + -20, -20, -20, 21, -15, 31, -26, -15, -13, 34, + -28, -31, 35, -30, -30, -32, 31, 30, 8, -15, + -18, 34, -30, -23, -16, 32, -15, -33, -15, 32, + -15, 30, -15, -15 +}; +static const short yydef[] = +{ + 0, -2, 1, 0, 3, 0, 0, 6, 7, 8, + 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 113, 114, 45, 46, 47, + 48, 117, 118, 119, 120, 121, 0, -2, 0, 0, + 0, 65, 0, 68, 69, 72, 89, 90, 91, 92, + 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, + 27, 29, 31, 33, 35, 38, 50, 115, 116, 44, + 40, 42, 2, 0, 5, 0, 0, 18, 57, 59, + 0, 61, -2, 0, 0, 0, 66, 73, 0, 0, + 14, 23, 85, 76, 78, 79, 80, 81, 82, 83, + 84, 24, 16, 51, 53, 54, 0, 17, 19, 20, + 21, 22, 26, 28, 30, 32, 34, 36, 37, 49, + 43, 39, 41, 0, 0, 0, 60, 75, 0, 63, + 64, 0, 0, 70, 71, 77, 0, 0, 0, 0, + 58, 62, 67, 52, 0, 56, 15, 0, 87, 55, + 0, 0, 86, 88 +}; +static const short yytok1[] = +{ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 30, 32, + 33, 0, 34, 0, 35 +}; +static const short yytok2[] = +{ + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28 +}; +static const long yytok3[] = +{ + 0 +}; +#define YYFLAG -1000 +#define YYERROR goto yyerrlab +#define YYACCEPT return(0) +#define YYABORT return(1) +#define yyclearin yychar = -1 +#define yyerrok yyerrflag = 0 + +#ifdef yydebug +#include "y.debug" +#else +#define yydebug 0 +static const char* yytoknames[1]; /* for debugging */ +static const char* yystates[1]; /* for debugging */ +#endif + +/* parser for yacc output */ +#ifdef YYARG +#define yynerrs yyarg->yynerrs +#define yyerrflag yyarg->yyerrflag +#define yyval yyarg->yyval +#define yylval yyarg->yylval +#else +int yynerrs = 0; /* number of errors */ +int yyerrflag = 0; /* error recovery flag */ +#endif + +extern int fprint(int, char*, ...); +extern int sprint(char*, char*, ...); + +static const char* +yytokname(int yyc) +{ + static char x[10]; + + if(yyc > 0 && yyc <= sizeof(yytoknames)/sizeof(yytoknames[0])) + if(yytoknames[yyc-1]) + return yytoknames[yyc-1]; + sprint(x, "<%d>", yyc); + return x; +} + +static const char* +yystatname(int yys) +{ + static char x[10]; + + if(yys >= 0 && yys < sizeof(yystates)/sizeof(yystates[0])) + if(yystates[yys]) + return yystates[yys]; + sprint(x, "<%d>\n", yys); + return x; +} + +static long +#ifdef YYARG +yylex1(struct Yyarg *yyarg) +#else +yylex1(void) +#endif +{ + long yychar; + const long *t3p; + int c; + +#ifdef YYARG + yychar = yylex(yyarg); +#else + yychar = yylex(); +#endif + if(yychar <= 0) { + c = yytok1[0]; + goto out; + } + if(yychar < sizeof(yytok1)/sizeof(yytok1[0])) { + c = yytok1[yychar]; + goto out; + } + if(yychar >= YYPRIVATE) + if(yychar < YYPRIVATE+sizeof(yytok2)/sizeof(yytok2[0])) { + c = yytok2[yychar-YYPRIVATE]; + goto out; + } + for(t3p=yytok3;; t3p+=2) { + c = t3p[0]; + if(c == yychar) { + c = t3p[1]; + goto out; + } + if(c == 0) + break; + } + c = 0; + +out: + if(c == 0) + c = yytok2[1]; /* unknown char */ + if(yydebug >= 3) + fprint(2, "lex %.4lux %s\n", yychar, yytokname(c)); + return c; +} + +int +#ifdef YYARG +yyparse(struct Yyarg *yyarg) +#else +yyparse(void) +#endif +{ + struct + { + YYSTYPE yyv; + int yys; + } yys[YYMAXDEPTH], *yyp, *yypt; + const short *yyxi; + int yyj, yym, yystate, yyn, yyg; + long yychar; +#ifndef YYARG + YYSTYPE save1, save2; + int save3, save4; + + save1 = yylval; + save2 = yyval; + save3 = yynerrs; + save4 = yyerrflag; +#endif + + yystate = 0; + yychar = -1; + yynerrs = 0; + yyerrflag = 0; + yyp = &yys[-1]; + goto yystack; + +ret0: + yyn = 0; + goto ret; + +ret1: + yyn = 1; + goto ret; + +ret: +#ifndef YYARG + yylval = save1; + yyval = save2; + yynerrs = save3; + yyerrflag = save4; +#endif + return yyn; + +yystack: + /* put a state and value onto the stack */ + if(yydebug >= 4) + fprint(2, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= &yys[YYMAXDEPTH]) { + yyerror("yacc stack overflow"); + goto ret1; + } + yyp->yys = yystate; + yyp->yyv = yyval; + +yynewstate: + yyn = yypact[yystate]; + if(yyn <= YYFLAG) + goto yydefault; /* simple state */ + if(yychar < 0) +#ifdef YYARG + yychar = yylex1(yyarg); +#else + yychar = yylex1(); +#endif + yyn += yychar; + if(yyn < 0 || yyn >= YYLAST) + goto yydefault; + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { /* valid shift */ + yychar = -1; + yyval = yylval; + yystate = yyn; + if(yyerrflag > 0) + yyerrflag--; + goto yystack; + } + +yydefault: + /* default state action */ + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) +#ifdef YYARG + yychar = yylex1(yyarg); +#else + yychar = yylex1(); +#endif + + /* look through exception table */ + for(yyxi=yyexca;; yyxi+=2) + if(yyxi[0] == -1 && yyxi[1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyxi[0]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyxi[1]; + if(yyn < 0) + goto ret0; + } + if(yyn == 0) { + /* error ... attempt to resume parsing */ + switch(yyerrflag) { + case 0: /* brand new error */ + yyerror("syntax error"); + if(yydebug >= 1) { + fprint(2, "%s", yystatname(yystate)); + fprint(2, "saw %s\n", yytokname(yychar)); + } + goto yyerrlab; + yyerrlab: + yynerrs++; + + case 1: + case 2: /* incompletely recovered error ... try again */ + yyerrflag = 3; + + /* find a state where "error" is a legal shift action */ + while(yyp >= yys) { + yyn = yypact[yyp->yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; /* simulate a shift of "error" */ + if(yychk[yystate] == YYERRCODE) + goto yystack; + } + + /* the current yyp has no shift onn "error", pop stack */ + if(yydebug >= 2) + fprint(2, "error recovery pops state %d, uncovers %d\n", + yyp->yys, (yyp-1)->yys ); + yyp--; + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1; + + case 3: /* no shift yet; clobber input char */ + if(yydebug >= 2) + fprint(2, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) + goto ret1; + yychar = -1; + goto yynewstate; /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if(yydebug >= 2) + fprint(2, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt = yyp; + yyp -= yyr2[yyn]; + yyval = (yyp+1)->yyv; + yym = yyn; + + /* consult goto table to find next state */ + yyn = yyr1[yyn]; + yyg = yypgo[yyn]; + yyj = yyg + yyp->yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + switch(yym) { + +case 3: +#line 56 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yydone = 1; } break; +case 6: +#line 61 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ date = 1; } break; +case 7: +#line 63 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ originator = 1; } break; +case 8: +#line 65 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ destination = 1; } break; +case 15: +#line 74 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ freenode(yypt[-5].yyv); freenode(yypt[-2].yyv); freenode(yypt[-1].yyv); + usender = yypt[-4].yyv; udate = yypt[-3].yyv; usys = yypt[-0].yyv; + } break; +case 16: +#line 79 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 17: +#line 81 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 18: +#line 83 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 19: +#line 85 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 20: +#line 87 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 21: +#line 89 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 22: +#line 91 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 1); } break; +case 23: +#line 94 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 24: +#line 96 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 25: +#line 99 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 26: +#line 101 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 27: +#line 103 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 28: +#line 105 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 29: +#line 107 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 30: +#line 109 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 31: +#line 111 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 32: +#line 113 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 33: +#line 115 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 34: +#line 117 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 35: +#line 119 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 36: +#line 121 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 37: +#line 124 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 38: +#line 126 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 39: +#line 129 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break; +case 40: +#line 131 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); received++; } break; +case 41: +#line 134 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 42: +#line 136 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 43: +#line 139 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 44: +#line 141 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); } break; +case 47: +#line 143 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ messageid = 1; } break; +case 49: +#line 146 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ /* hack to allow same lex for field names and the rest */ + if(badfieldname(yypt[-2].yyv)){ + freenode(yypt[-2].yyv); + freenode(yypt[-1].yyv); + freenode(yypt[-0].yyv); + return 1; + } + newfield(link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv), 0); + } break; +case 50: +#line 156 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ /* hack to allow same lex for field names and the rest */ + if(badfieldname(yypt[-1].yyv)){ + freenode(yypt[-1].yyv); + freenode(yypt[-0].yyv); + return 1; + } + newfield(link2(yypt[-1].yyv, yypt[-0].yyv), 0); + } break; +case 52: +#line 167 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 55: +#line 173 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-3].yyv, link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv)); } break; +case 56: +#line 175 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 58: +#line 179 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 60: +#line 183 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 62: +#line 187 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 63: +#line 189 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = nobody(yypt[-0].yyv); freenode(yypt[-1].yyv); } break; +case 64: +#line 192 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break; +case 66: +#line 196 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = concat(yypt[-1].yyv, yypt[-0].yyv); } break; +case 67: +#line 198 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = concat(yypt[-3].yyv, concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv))); } break; +case 68: +#line 201 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(yypt[-0].yyv); } break; +case 70: +#line 205 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break; +case 71: +#line 207 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = address(concat(yypt[-2].yyv, concat(yypt[-1].yyv, yypt[-0].yyv)));} break; +case 75: +#line 215 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 77: +#line 219 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 86: +#line 226 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-5].yyv, yypt[-3].yyv, link3(yypt[-4].yyv, yypt[-0].yyv, link2(yypt[-2].yyv, yypt[-1].yyv))); } break; +case 88: +#line 230 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link3(yypt[-2].yyv, yypt[-1].yyv, yypt[-0].yyv); } break; +case 115: +#line 240 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; +case 116: +#line 242 "/usr/local/plan9/src/cmd/upas/smtp/rfc822.y" +{ yyval = link2(yypt[-1].yyv, yypt[-0].yyv); } break; + } + goto yystack; /* stack new state and value */ +} diff --git a/src/cmd/upas/smtp/rfc822.tab.h b/src/cmd/upas/smtp/rfc822.tab.h new file mode 100644 index 00000000..cddbbc0f --- /dev/null +++ b/src/cmd/upas/smtp/rfc822.tab.h @@ -0,0 +1,98 @@ +/* A Bison parser, made by GNU Bison 2.0. */ + +/* Skeleton parser for Yacc-like parsing with Bison, + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + WORD = 258, + DATE = 259, + RESENT_DATE = 260, + RETURN_PATH = 261, + FROM = 262, + SENDER = 263, + REPLY_TO = 264, + RESENT_FROM = 265, + RESENT_SENDER = 266, + RESENT_REPLY_TO = 267, + SUBJECT = 268, + TO = 269, + CC = 270, + BCC = 271, + RESENT_TO = 272, + RESENT_CC = 273, + RESENT_BCC = 274, + REMOTE = 275, + PRECEDENCE = 276, + MIMEVERSION = 277, + CONTENTTYPE = 278, + MESSAGEID = 279, + RECEIVED = 280, + MAILER = 281, + BADTOKEN = 282 + }; +#endif +#define WORD 258 +#define DATE 259 +#define RESENT_DATE 260 +#define RETURN_PATH 261 +#define FROM 262 +#define SENDER 263 +#define REPLY_TO 264 +#define RESENT_FROM 265 +#define RESENT_SENDER 266 +#define RESENT_REPLY_TO 267 +#define SUBJECT 268 +#define TO 269 +#define CC 270 +#define BCC 271 +#define RESENT_TO 272 +#define RESENT_CC 273 +#define RESENT_BCC 274 +#define REMOTE 275 +#define PRECEDENCE 276 +#define MIMEVERSION 277 +#define CONTENTTYPE 278 +#define MESSAGEID 279 +#define RECEIVED 280 +#define MAILER 281 +#define BADTOKEN 282 + + + + +#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED) +typedef int YYSTYPE; +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + +extern YYSTYPE yylval; + + + diff --git a/src/cmd/upas/smtp/rfc822.y b/src/cmd/upas/smtp/rfc822.y new file mode 100644 index 00000000..be77d2bd --- /dev/null +++ b/src/cmd/upas/smtp/rfc822.y @@ -0,0 +1,778 @@ +%{ +#include "common.h" +#include "smtp.h" +#include <ctype.h> + +char *yylp; /* next character to be lex'd */ +int yydone; /* tell yylex to give up */ +char *yybuffer; /* first parsed character */ +char *yyend; /* end of buffer to be parsed */ +Node *root; +Field *firstfield; +Field *lastfield; +Node *usender; +Node *usys; +Node *udate; +char *startfield, *endfield; +int originator; +int destination; +int date; +int received; +int messageid; +%} + +%term WORD +%term DATE +%term RESENT_DATE +%term RETURN_PATH +%term FROM +%term SENDER +%term REPLY_TO +%term RESENT_FROM +%term RESENT_SENDER +%term RESENT_REPLY_TO +%term SUBJECT +%term TO +%term CC +%term BCC +%term RESENT_TO +%term RESENT_CC +%term RESENT_BCC +%term REMOTE +%term PRECEDENCE +%term MIMEVERSION +%term CONTENTTYPE +%term MESSAGEID +%term RECEIVED +%term MAILER +%term BADTOKEN +%start msg +%% + +msg : fields + | unixfrom '\n' fields + ; +fields : '\n' + { yydone = 1; } + | field '\n' + | field '\n' fields + ; +field : dates + { date = 1; } + | originator + { originator = 1; } + | destination + { destination = 1; } + | subject + | optional + | ignored + | received + | precedence + | error '\n' field + ; +unixfrom : FROM route_addr unix_date_time REMOTE FROM word + { freenode($1); freenode($4); freenode($5); + usender = $2; udate = $3; usys = $6; + } + ; +originator : REPLY_TO ':' address_list + { newfield(link3($1, $2, $3), 1); } + | RETURN_PATH ':' route_addr + { newfield(link3($1, $2, $3), 1); } + | FROM ':' mailbox_list + { newfield(link3($1, $2, $3), 1); } + | SENDER ':' mailbox + { newfield(link3($1, $2, $3), 1); } + | RESENT_REPLY_TO ':' address_list + { newfield(link3($1, $2, $3), 1); } + | RESENT_SENDER ':' mailbox + { newfield(link3($1, $2, $3), 1); } + | RESENT_FROM ':' mailbox + { newfield(link3($1, $2, $3), 1); } + ; +dates : DATE ':' date_time + { newfield(link3($1, $2, $3), 0); } + | RESENT_DATE ':' date_time + { newfield(link3($1, $2, $3), 0); } + ; +destination : TO ':' + { newfield(link2($1, $2), 0); } + | TO ':' address_list + { newfield(link3($1, $2, $3), 0); } + | RESENT_TO ':' + { newfield(link2($1, $2), 0); } + | RESENT_TO ':' address_list + { newfield(link3($1, $2, $3), 0); } + | CC ':' + { newfield(link2($1, $2), 0); } + | CC ':' address_list + { newfield(link3($1, $2, $3), 0); } + | RESENT_CC ':' + { newfield(link2($1, $2), 0); } + | RESENT_CC ':' address_list + { newfield(link3($1, $2, $3), 0); } + | BCC ':' + { newfield(link2($1, $2), 0); } + | BCC ':' address_list + { newfield(link3($1, $2, $3), 0); } + | RESENT_BCC ':' + { newfield(link2($1, $2), 0); } + | RESENT_BCC ':' address_list + { newfield(link3($1, $2, $3), 0); } + ; +subject : SUBJECT ':' things + { newfield(link3($1, $2, $3), 0); } + | SUBJECT ':' + { newfield(link2($1, $2), 0); } + ; +received : RECEIVED ':' things + { newfield(link3($1, $2, $3), 0); received++; } + | RECEIVED ':' + { newfield(link2($1, $2), 0); received++; } + ; +precedence : PRECEDENCE ':' things + { newfield(link3($1, $2, $3), 0); } + | PRECEDENCE ':' + { newfield(link2($1, $2), 0); } + ; +ignored : ignoredhdr ':' things + { newfield(link3($1, $2, $3), 0); } + | ignoredhdr ':' + { newfield(link2($1, $2), 0); } + ; +ignoredhdr : MIMEVERSION | CONTENTTYPE | MESSAGEID { messageid = 1; } | MAILER + ; +optional : fieldwords ':' things + { /* hack to allow same lex for field names and the rest */ + if(badfieldname($1)){ + freenode($1); + freenode($2); + freenode($3); + return 1; + } + newfield(link3($1, $2, $3), 0); + } + | fieldwords ':' + { /* hack to allow same lex for field names and the rest */ + if(badfieldname($1)){ + freenode($1); + freenode($2); + return 1; + } + newfield(link2($1, $2), 0); + } + ; +address_list : address + | address_list ',' address + { $$ = link3($1, $2, $3); } + ; +address : mailbox + | group + ; +group : phrase ':' address_list ';' + { $$ = link2($1, link3($2, $3, $4)); } + | phrase ':' ';' + { $$ = link3($1, $2, $3); } + ; +mailbox_list : mailbox + | mailbox_list ',' mailbox + { $$ = link3($1, $2, $3); } + ; +mailbox : route_addr + | phrase brak_addr + { $$ = link2($1, $2); } + | brak_addr + ; +brak_addr : '<' route_addr '>' + { $$ = link3($1, $2, $3); } + | '<' '>' + { $$ = nobody($2); freenode($1); } + ; +route_addr : route ':' at_addr + { $$ = address(concat($1, concat($2, $3))); } + | addr_spec + ; +route : '@' domain + { $$ = concat($1, $2); } + | route ',' '@' domain + { $$ = concat($1, concat($2, concat($3, $4))); } + ; +addr_spec : local_part + { $$ = address($1); } + | at_addr + ; +at_addr : local_part '@' domain + { $$ = address(concat($1, concat($2, $3)));} + | at_addr '@' domain + { $$ = address(concat($1, concat($2, $3)));} + ; +local_part : word + ; +domain : word + ; +phrase : word + | phrase word + { $$ = link2($1, $2); } + ; +things : thing + | things thing + { $$ = link2($1, $2); } + ; +thing : word | '<' | '>' | '@' | ':' | ';' | ',' + ; +date_time : things + ; +unix_date_time : word word word unix_time word word + { $$ = link3($1, $3, link3($2, $6, link2($4, $5))); } + ; +unix_time : word + | unix_time ':' word + { $$ = link3($1, $2, $3); } + ; +word : WORD | DATE | RESENT_DATE | RETURN_PATH | FROM | SENDER + | REPLY_TO | RESENT_FROM | RESENT_SENDER | RESENT_REPLY_TO + | TO | CC | BCC | RESENT_TO | RESENT_CC | RESENT_BCC | REMOTE | SUBJECT + | PRECEDENCE | MIMEVERSION | CONTENTTYPE | MESSAGEID | RECEIVED | MAILER + ; +fieldwords : fieldword + | WORD + | fieldwords fieldword + { $$ = link2($1, $2); } + | fieldwords word + { $$ = link2($1, $2); } + ; +fieldword : '<' | '>' | '@' | ';' | ',' + ; +%% + +/* + * Initialize the parsing. Done once for each header field. + */ +void +yyinit(char *p, int len) +{ + yybuffer = p; + yylp = p; + yyend = p + len; + firstfield = lastfield = 0; + received = 0; +} + +/* + * keywords identifying header fields we care about + */ +typedef struct Keyword Keyword; +struct Keyword { + char *rep; + int val; +}; + +/* field names that we need to recognize */ +Keyword key[] = { + { "date", DATE }, + { "resent-date", RESENT_DATE }, + { "return_path", RETURN_PATH }, + { "from", FROM }, + { "sender", SENDER }, + { "reply-to", REPLY_TO }, + { "resent-from", RESENT_FROM }, + { "resent-sender", RESENT_SENDER }, + { "resent-reply-to", RESENT_REPLY_TO }, + { "to", TO }, + { "cc", CC }, + { "bcc", BCC }, + { "resent-to", RESENT_TO }, + { "resent-cc", RESENT_CC }, + { "resent-bcc", RESENT_BCC }, + { "remote", REMOTE }, + { "subject", SUBJECT }, + { "precedence", PRECEDENCE }, + { "mime-version", MIMEVERSION }, + { "content-type", CONTENTTYPE }, + { "message-id", MESSAGEID }, + { "received", RECEIVED }, + { "mailer", MAILER }, + { "who-the-hell-cares", WORD } +}; + +/* + * Lexical analysis for an rfc822 header field. Continuation lines + * are handled in yywhite() when skipping over white space. + * + */ +int +yylex(void) +{ + String *t; + int quoting; + int escaping; + char *start; + Keyword *kp; + int c, d; + +/* print("lexing\n"); /**/ + if(yylp >= yyend) + return 0; + if(yydone) + return 0; + + quoting = escaping = 0; + start = yylp; + yylval = malloc(sizeof(Node)); + yylval->white = yylval->s = 0; + yylval->next = 0; + yylval->addr = 0; + yylval->start = yylp; + for(t = 0; yylp < yyend; yylp++){ + c = *yylp & 0xff; + + /* dump nulls, they can't be in header */ + if(c == 0) + continue; + + if(escaping) { + escaping = 0; + } else if(quoting) { + switch(c){ + case '\\': + escaping = 1; + break; + case '\n': + d = (*(yylp+1))&0xff; + if(d != ' ' && d != '\t'){ + quoting = 0; + yylp--; + continue; + } + break; + case '"': + quoting = 0; + break; + } + } else { + switch(c){ + case '\\': + escaping = 1; + break; + case '(': + case ' ': + case '\t': + case '\r': + goto out; + case '\n': + if(yylp == start){ + yylp++; +/* print("lex(c %c)\n", c); /**/ + yylval->end = yylp; + return yylval->c = c; + } + goto out; + case '@': + case '>': + case '<': + case ':': + case ',': + case ';': + if(yylp == start){ + yylp++; + yylval->white = yywhite(); +/* print("lex(c %c)\n", c); /**/ + yylval->end = yylp; + return yylval->c = c; + } + goto out; + case '"': + quoting = 1; + break; + default: + break; + } + } + if(t == 0) + t = s_new(); + s_putc(t, c); + } +out: + yylval->white = yywhite(); + if(t) { + s_terminate(t); + } 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) + break; +/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/ + yylval->end = yylp; + return yylval->c = kp->val; +} + +void +yyerror(char *x) +{ + USED(x); + + /*fprint(2, "parse err: %s\n", x);/**/ +} + +/* + * parse white space and comments + */ +String * +yywhite(void) +{ + String *w; + int clevel; + int c; + int escaping; + + escaping = clevel = 0; + for(w = 0; yylp < yyend; yylp++){ + c = *yylp & 0xff; + + /* dump nulls, they can't be in header */ + if(c == 0) + continue; + + if(escaping){ + escaping = 0; + } else if(clevel) { + switch(c){ + case '\n': + /* + * look for multiline fields + */ + if(*(yylp+1)==' ' || *(yylp+1)=='\t') + break; + else + goto out; + case '\\': + escaping = 1; + break; + case '(': + clevel++; + break; + case ')': + clevel--; + break; + } + } else { + switch(c){ + case '\\': + escaping = 1; + break; + case '(': + clevel++; + break; + case ' ': + case '\t': + case '\r': + break; + case '\n': + /* + * look for multiline fields + */ + if(*(yylp+1)==' ' || *(yylp+1)=='\t') + break; + else + goto out; + default: + goto out; + } + } + if(w == 0) + w = s_new(); + s_putc(w, c); + } +out: + if(w) + s_terminate(w); + return w; +} + +/* + * link two parsed entries together + */ +Node* +link2(Node *p1, Node *p2) +{ + Node *p; + + for(p = p1; p->next; p = p->next) + ; + p->next = p2; + return p1; +} + +/* + * link three parsed entries together + */ +Node* +link3(Node *p1, Node *p2, Node *p3) +{ + Node *p; + + for(p = p2; p->next; p = p->next) + ; + p->next = p3; + + for(p = p1; p->next; p = p->next) + ; + p->next = p2; + + return p1; +} + +/* + * make a:b, move all white space after both + */ +Node* +colon(Node *p1, Node *p2) +{ + if(p1->white){ + if(p2->white) + s_append(p1->white, s_to_c(p2->white)); + } else { + p1->white = p2->white; + p2->white = 0; + } + + s_append(p1->s, ":"); + if(p2->s) + s_append(p1->s, s_to_c(p2->s)); + + if(p1->end < p2->end) + p1->end = p2->end; + freenode(p2); + return p1; +} + +/* + * concatenate two fields, move all white space after both + */ +Node* +concat(Node *p1, Node *p2) +{ + char buf[2]; + + if(p1->white){ + if(p2->white) + s_append(p1->white, s_to_c(p2->white)); + } else { + p1->white = p2->white; + p2->white = 0; + } + + if(p1->s == nil){ + buf[0] = p1->c; + buf[1] = 0; + p1->s = s_new(); + s_append(p1->s, buf); + } + + if(p2->s) + s_append(p1->s, s_to_c(p2->s)); + else { + buf[0] = p2->c; + buf[1] = 0; + s_append(p1->s, buf); + } + + if(p1->end < p2->end) + p1->end = p2->end; + freenode(p2); + return p1; +} + +/* + * look for disallowed chars in the field name + */ +int +badfieldname(Node *p) +{ + for(; p; p = p->next){ + /* field name can't contain white space */ + if(p->white && p->next) + return 1; + } + return 0; +} + +/* + * mark as an address + */ +Node * +address(Node *p) +{ + p->addr = 1; + return 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 +freenode(Node *p) +{ + Node *tp; + + while(p){ + tp = p->next; + if(p->s) + s_free(p->s); + if(p->white) + s_free(p->white); + free(p); + p = tp; + } +} + + +/* + * an anonymous user + */ +Node* +nobody(Node *p) +{ + if(p->s) + s_free(p->s); + p->s = s_copy("pOsTmAsTeR"); + p->addr = 1; + return p; +} + +/* + * add anything that was dropped because of a parse error + */ +void +missing(Node *p) +{ + Node *np; + char *start, *end; + Field *f; + String *s; + + start = yybuffer; + if(lastfield != nil){ + for(np = lastfield->node; np; np = np->next) + start = np->end+1; + } + + end = p->start-1; + + if(end <= start) + return; + + if(strncmp(start, "From ", 5) == 0) + return; + + np = malloc(sizeof(Node)); + np->start = start; + np->end = end; + np->white = nil; + s = s_copy("BadHeader: "); + np->s = s_nappend(s, start, end-start); + np->next = nil; + + f = malloc(sizeof(Field)); + f->next = 0; + f->node = np; + f->source = 0; + if(firstfield) + lastfield->next = f; + else + firstfield = f; + lastfield = f; +} + +/* + * create a new field + */ +void +newfield(Node *p, int source) +{ + Field *f; + + missing(p); + + f = malloc(sizeof(Field)); + f->next = 0; + f->node = p; + f->source = source; + if(firstfield) + lastfield->next = f; + else + firstfield = f; + lastfield = f; + endfield = startfield; + startfield = yylp; +} + +/* + * fee a list of fields + */ +void +freefield(Field *f) +{ + Field *tf; + + while(f){ + tf = f->next; + freenode(f->node); + free(f); + f = tf; + } +} + +/* + * add some white space to a node + */ +Node* +whiten(Node *p) +{ + Node *tp; + + for(tp = p; tp->next; tp = tp->next) + ; + if(tp->white == 0) + tp->white = s_copy(" "); + return p; +} + +void +yycleanup(void) +{ + Field *f, *fnext; + Node *np, *next; + + for(f = firstfield; f; f = fnext){ + for(np = f->node; np; np = next){ + if(np->s) + s_free(np->s); + if(np->white) + s_free(np->white); + next = np->next; + free(np); + } + fnext = f->next; + free(f); + } + firstfield = lastfield = 0; +} diff --git a/src/cmd/upas/smtp/rmtdns.c b/src/cmd/upas/smtp/rmtdns.c new file mode 100644 index 00000000..54b679bb --- /dev/null +++ b/src/cmd/upas/smtp/rmtdns.c @@ -0,0 +1,58 @@ +#include "common.h" +#include <ndb.h> + +int +rmtdns(char *net, char *path) +{ + + int fd, n, 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); + n = write(fd, buf, n); + close(fd); + if(n < 0){ + 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/tcp/109", argv[1]));} + +*/ diff --git a/src/cmd/upas/smtp/smtp.c b/src/cmd/upas/smtp/smtp.c new file mode 100644 index 00000000..e88154f7 --- /dev/null +++ b/src/cmd/upas/smtp/smtp.c @@ -0,0 +1,1122 @@ +#include "common.h" +#include "smtp.h" +#include <ctype.h> +#include <mp.h> +#include <libsec.h> +#include <auth.h> +#include <ndb.h> + +static char* connect(char*); +static char* dotls(char*); +static char* doauth(char*); +char* hello(char*, int); +char* mailfrom(char*); +char* rcptto(char*); +char* data(String*, Biobuf*); +void quit(char*); +int getreply(void); +void addhostdom(String*, char*); +String* bangtoat(char*); +String* convertheader(String*); +int printheader(void); +char* domainify(char*, char*); +void putcrnl(char*, int); +char* getcrnl(String*); +int printdate(Node*); +char *rewritezone(char *); +int dBprint(char*, ...); +int dBputc(int); +String* fixrouteaddr(String*, Node*, Node*); +char* expand_addr(char* a); +int ping; +int insecure; + +#define Retry "Retry, Temporary Failure" +#define Giveup "Permanent Failure" + +int debug; /* true if we're debugging */ +String *reply; /* last reply */ +String *toline; +int alarmscale; +int last = 'n'; /* last character sent by putcrnl() */ +int filter; +int trysecure; /* Try to use TLS if the other side supports it */ +int tryauth; /* Try to authenticate, if supported */ +int quitting; /* when error occurs in quit */ +char *quitrv; /* deferred return value when in quit */ +char ddomain[1024]; /* domain name of destination machine */ +char *gdomain; /* domain name of gateway */ +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]; +Biobuf bin; +Biobuf bout; +Biobuf berr; +Biobuf bfile; + +void +usage(void) +{ + fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n"); + exits(Giveup); +} + +int +timeout(void *x, char *msg) +{ + USED(x); + syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg); + if(strstr(msg, "alarm")){ + fprint(2, "smtp timeout: connection to %s timed out\n", farend); + if(quitting) + exits(quitrv); + 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); + _exits(quitrv); + } + _exits(Retry); + } + return 0; +} + +void +removenewline(char *p) +{ + int n = strlen(p)-1; + + if(n < 0) + return; + if(p[n] == '\n') + p[n] = 0; +} + +void +threadmain(int argc, char **argv) +{ + char hellodomain[256]; + char *host, *domain; + String *from; + String *fromm; + String *sender; + char *addr; + char *rv, *trv; + int i, ok, rcvrs; + char **errs; + + alarmscale = 60*1000; /* minutes */ + quotefmtinstall(); + errs = malloc(argc*sizeof(char*)); + reply = s_new(); + host = 0; + ARGBEGIN{ + case 'a': + tryauth = 1; + trysecure = 1; + break; + case 'f': + filter = 1; + break; + case 'd': + debug = 1; + break; + case 'g': + gdomain = ARGF(); + break; + case 'h': + host = ARGF(); + break; + case 'i': + insecure = 1; + break; + case 'p': + alarmscale = 10*1000; /* tens of seconds */ + ping = 1; + break; + case 's': + trysecure = 1; + break; + case 'u': + user = ARGF(); + break; + default: + usage(); + break; + }ARGEND; + + Binit(&berr, 2, OWRITE); + Binit(&bfile, 0, OREAD); + + /* + * get domain and add to host name + */ + if(*argv && **argv=='.') { + domain = *argv; + argv++; argc--; + } else + domain = domainname_read(); + if(host == 0) + host = sysname_read(); + strcpy(hostdomain, domainify(host, domain)); + strcpy(hellodomain, domainify(sysname_read(), domain)); + + /* + * get destination address + */ + if(*argv == 0) + usage(); + addr = *argv++; argc--; + // expand $smtp if necessary + addr = expand_addr(addr); + farend = addr; + + /* + * get sender's machine. + * get sender in internet style. domainify if necessary. + */ + if(*argv == 0) + usage(); + sender = unescapespecial(s_copy(*argv++)); + argc--; + fromm = s_clone(sender); + rv = strrchr(s_to_c(fromm), '!'); + if(rv) + *rv = 0; + else + *s_to_c(fromm) = 0; + from = bangtoat(s_to_c(sender)); + + /* + * send the mail + */ + if(filter){ + Binit(&bout, 1, OWRITE); + rv = data(from, &bfile); + if(rv != 0) + goto error; + exits(0); + } + + /* 10 minutes to get through the initial handshake */ + atnotify(timeout, 1); + + alarm(10*alarmscale); + if((rv = connect(addr)) != 0) + exits(rv); + alarm(10*alarmscale); + if((rv = hello(hellodomain, 0)) != 0) + goto error; + alarm(10*alarmscale); + if((rv = mailfrom(s_to_c(from))) != 0) + goto error; + + ok = 0; + rcvrs = 0; + /* if any rcvrs are ok, we try to send the message */ + for(i = 0; i < argc; i++){ + if((trv = rcptto(argv[i])) != 0){ + /* remember worst error */ + if(rv != Giveup) + rv = trv; + errs[rcvrs] = strdup(s_to_c(reply)); + removenewline(errs[rcvrs]); + } else { + ok++; + errs[rcvrs] = 0; + } + rcvrs++; + } + + /* if no ok rcvrs or worst error is retry, give up */ + if(ok == 0 || rv == Retry) + goto error; + + if(ping){ + quit(0); + exits(0); + } + + rv = data(from, &bfile); + if(rv != 0) + goto error; + quit(0); + if(rcvrs == ok) + exits(0); + + /* + * here when some but not all rcvrs failed + */ + fprint(2, "%s connect to %s:\n", thedate(), addr); + 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]); + fprint(2, " mail to %s failed: %s", argv[i], errs[i]); + } + } + exits(Giveup); + + /* + * here when all rcvrs failed + */ +error: + 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(!filter) + quit(rv); + exits(rv); +} + +/* + * connect to the remote host + */ +static char * +connect(char* net) +{ + char buf[256]; + int fd; + + fd = mxdial(net, ddomain, gdomain); + + if(fd < 0){ + rerrstr(buf, sizeof(buf)); + Bprint(&berr, "smtp: %s (%s)\n", buf, net); + syslog(0, "smtp.fail", "%s (%s)", buf, net); + if(strstr(buf, "illegal") + || strstr(buf, "unknown") + || strstr(buf, "can't translate")) + return Giveup; + else + return Retry; + } + Binit(&bin, fd, OREAD); + fd = dup(fd, -1); + Binit(&bout, fd, OWRITE); + return 0; +} + +static char smtpthumbs[] = "/sys/lib/tls/smtp"; +static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude"; + +/* + * exchange names with remote host, attempt to + * enable encryption and optionally authenticate. + * not fatal if we can't. + */ +static char * +dotls(char *me) +{ + TLSconn *c; + Thumbprint *goodcerts; + char *h; + int fd; + uchar hash[SHA1dlen]; + + c = mallocz(sizeof(*c), 1); /* Note: not freed on success */ + if (c == nil) + return Giveup; + + dBprint("STARTTLS\r\n"); + if (getreply() != 2) + return Giveup; + + fd = tlsClient(Bfildes(&bout), c); + if (fd < 0) { + syslog(0, "smtp", "tlsClient to %q: %r", ddomain); + return Giveup; + } + goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs); + if (goodcerts == nil) { + free(c); + close(fd); + syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs); + return Giveup; /* how to recover? TLS is started */ + } + + /* compute sha1 hash of remote's certificate, see if we know it */ + sha1(c->cert, c->certlen, hash, nil); + if (!okThumbprint(hash, goodcerts)) { + /* TODO? if not excluded, add hash to thumb list */ + free(c); + close(fd); + h = malloc(2*sizeof hash + 1); + if (h != nil) { + enc16(h, 2*sizeof hash + 1, hash, sizeof hash); + // print("x509 sha1=%s", h); + syslog(0, "smtp", + "remote cert. has bad thumbprint: x509 sha1=%s server=%q", + h, ddomain); + free(h); + } + return Giveup; /* how to recover? TLS is started */ + } + freeThumbprints(goodcerts); + Bterm(&bin); + Bterm(&bout); + + /* + * set up bin & bout to use the TLS fd, i/o upon which generates + * i/o on the original, underlying fd. + */ + Binit(&bin, fd, OREAD); + fd = dup(fd, -1); + Binit(&bout, fd, OWRITE); + + syslog(0, "smtp", "started TLS to %q", ddomain); + return(hello(me, 1)); +} + +static char * +doauth(char *methods) +{ + char *buf, *base64; + int n; + DS ds; + UserPasswd *p; + + dial_string_parse(ddomain, &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); + if (p == nil) + return Giveup; + + if (strstr(methods, "LOGIN")){ + dBprint("AUTH LOGIN\r\n"); + if (getreply() != 3) + return Retry; + + n = strlen(p->user); + base64 = malloc(2*n); + if (base64 == nil) + return Retry; /* Out of memory */ + enc64(base64, 2*n, (uchar *)p->user, n); + dBprint("%s\r\n", base64); + if (getreply() != 3) + return Retry; + + n = strlen(p->passwd); + base64 = malloc(2*n); + if (base64 == nil) + return Retry; /* Out of memory */ + enc64(base64, 2*n, (uchar *)p->passwd, n); + dBprint("%s\r\n", base64); + if (getreply() != 2) + return Retry; + + free(base64); + } + else + if (strstr(methods, "PLAIN")){ + n = strlen(p->user) + strlen(p->passwd) + 3; + buf = malloc(n); + base64 = malloc(2 * n); + if (buf == nil || base64 == nil) { + free(buf); + return Retry; /* Out of memory */ + } + snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd); + enc64(base64, 2 * n, (uchar *)buf, n - 1); + free(buf); + dBprint("AUTH PLAIN %s\r\n", base64); + free(base64); + if (getreply() != 2) + return Retry; + } + else + return "No supported AUTH method"; + return(0); +} + +char * +hello(char *me, int encrypted) +{ + int ehlo; + String *r; + char *ret, *s, *t; + + if (!encrypted) + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + + ehlo = 1; + Again: + if(ehlo) + dBprint("EHLO %s\r\n", me); + else + dBprint("HELO %s\r\n", me); + switch (getreply()) { + case 2: + break; + case 5: + if(ehlo){ + ehlo = 0; + goto Again; + } + return Giveup; + default: + return Retry; + } + r = s_clone(reply); + if(r == nil) + return Retry; /* Out of memory or couldn't get string */ + + /* 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)){ + 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)){ + ret = doauth(s + strlen("250 AUTH ")); + s_free(r); + return ret; + } + } + s_free(r); + return 0; +} + +/* + * report sender to remote + */ +char * +mailfrom(char *from) +{ + if(!returnable(from)) + dBprint("MAIL FROM:<>\r\n"); + 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; + case 5: + return Giveup; + default: + return Retry; + } + return 0; +} + +/* + * report a recipient to remote + */ +char * +rcptto(char *to) +{ + String *s; + + s = unescapespecial(bangtoat(to)); + if(toline == 0) + toline = s_new(); + else + s_append(toline, ", "); + s_append(toline, s_to_c(s)); + if(strchr(s_to_c(s), '@')) + dBprint("RCPT TO:<%s>\r\n", s_to_c(s)); + else { + s_append(toline, "@"); + s_append(toline, ddomain); + dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain); + } + alarm(10*alarmscale); + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + return 0; +} + +static char hex[] = "0123456789abcdef"; + +/* + * send the damn thing + */ +char * +data(String *from, Biobuf *b) +{ + char *buf, *cp; + int i, n, nbytes, bufsize, eof, r; + String *fromline; + char errmsg[Errlen]; + char id[40]; + + /* + * input the header. + */ + + buf = malloc(1); + if(buf == 0){ + s_append(s_restart(reply), "out of memory"); + return Retry; + } + n = 0; + eof = 0; + for(;;){ + cp = Brdline(b, '\n'); + if(cp == nil){ + eof = 1; + break; + } + nbytes = Blinelen(b); + buf = realloc(buf, n+nbytes+1); + if(buf == 0){ + s_append(s_restart(reply), "out of memory"); + return Retry; + } + strncpy(buf+n, cp, nbytes); + n += nbytes; + if(nbytes == 1) /* end of header */ + break; + } + buf[n] = 0; + bufsize = n; + + /* + * parse the header, turn all addresses into @ format + */ + yyinit(buf, n); + yyparse(); + + /* + * print message observing '.' escapes and using \r\n for \n + */ + alarm(20*alarmscale); + if(!filter){ + dBprint("DATA\r\n"); + switch(getreply()){ + case 3: + break; + case 5: + free(buf); + return Giveup; + default: + free(buf); + return Retry; + } + } + /* + * send header. add a message-id, a sender, and a date if there + * isn't one + */ + nbytes = 0; + fromline = convertheader(from); + uneaten = buf; + + 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]; + } + id[2*i] = '\0'; + nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain); + if(debug) + Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain); + } + + 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)); + } + s_free(fromline); + + if(destination == 0 && toline) + if(*s_to_c(toline) == '@'){ /* route addr */ + nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline)); + if(debug) + Bprint(&berr, "To: <%s>\r\n", s_to_c(toline)); + } else { + nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline)); + if(debug) + Bprint(&berr, "To: %s\r\n", s_to_c(toline)); + } + + if(date==0 && udate) + nbytes += printdate(udate); + if (usys) + uneaten = usys->end + 1; + nbytes += printheader(); + if (*uneaten != '\n') + putcrnl("\n", 1); + + /* + * send body + */ + + putcrnl(uneaten, buf+n - uneaten); + nbytes += buf+n - uneaten; + if(eof == 0){ + for(;;){ + n = Bread(b, buf, bufsize); + if(n < 0){ + rerrstr(errmsg, sizeof(errmsg)); + s_append(s_restart(reply), errmsg); + free(buf); + return Retry; + } + if(n == 0) + break; + alarm(10*alarmscale); + putcrnl(buf, n); + nbytes += n; + } + } + free(buf); + if(!filter){ + if(last != '\n') + dBprint("\r\n.\r\n"); + else + dBprint(".\r\n"); + alarm(10*alarmscale); + switch(getreply()){ + case 2: + break; + case 5: + return Giveup; + default: + return Retry; + } + syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from), + nbytes, s_to_c(toline));/**/ + } + return 0; +} + +/* + * we're leaving + */ +void +quit(char *rv) +{ + /* 60 minutes to quit */ + quitting = 1; + quitrv = rv; + alarm(60*alarmscale); + dBprint("QUIT\r\n"); + getreply(); + Bterm(&bout); + Bterm(&bfile); +} + +/* + * read a reply into a string, return the reply code + */ +int +getreply(void) +{ + char *line; + int rv; + + reply = s_reset(reply); + for(;;){ + line = getcrnl(reply); + if(line == 0) + return -1; + if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2])) + return -1; + if(line[3] != '-') + break; + } + if(debug) + Bflush(&berr); + rv = atoi(line)/100; + return rv; +} +void +addhostdom(String *buf, char *host) +{ + s_append(buf, "@"); + s_append(buf, host); +} + +/* + * Convert from `bang' to `source routing' format. + * + * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o + */ +String * +bangtoat(char *addr) +{ + String *buf; + register int i; + int j, d; + char *field[128]; + + /* parse the '!' format address */ + buf = s_new(); + for(i = 0; addr; i++){ + field[i] = addr; + addr = strchr(addr, '!'); + if(addr) + *addr++ = 0; + } + if (i==1) { + s_append(buf, field[0]); + return buf; + } + + /* + * count leading domain fields (non-domains don't count) + */ + for(d = 0; d<i-1; d++) + if(strchr(field[d], '.')==0) + break; + /* + * if there are more than 1 leading domain elements, + * put them in as source routing + */ + if(d > 1){ + addhostdom(buf, field[0]); + for(j=1; j<d-1; j++){ + s_append(buf, ","); + s_append(buf, "@"); + s_append(buf, field[j]); + } + s_append(buf, ":"); + } + + /* + * throw in the non-domain elements separated by '!'s + */ + s_append(buf, field[d]); + for(j=d+1; j<=i-1; j++) { + s_append(buf, "!"); + s_append(buf, field[j]); + } + if(d) + addhostdom(buf, field[d-1]); + return buf; +} + +/* + * convert header addresses to @ format. + * if the address is a source address, and a domain is specified, + * make sure it falls in the domain. + */ +String* +convertheader(String *from) +{ + Field *f; + Node *p, *lastp; + String *a; + + if(!returnable(s_to_c(from))){ + from = s_new(); + s_append(from, "Postmaster"); + addhostdom(from, hostdomain); + } else + if(strchr(s_to_c(from), '@') == 0){ + a = username(from); + if(a) { + s_append(a, " <"); + s_append(a, s_to_c(from)); + addhostdom(a, hostdomain); + s_append(a, ">"); + from = a; + } else { + from = s_copy(s_to_c(from)); + addhostdom(from, hostdomain); + } + } else + from = s_copy(s_to_c(from)); + for(f = firstfield; f; f = f->next){ + lastp = 0; + for(p = f->node; p; lastp = p, p = p->next){ + if(!p->addr) + continue; + a = bangtoat(s_to_c(p->s)); + s_free(p->s); + if(strchr(s_to_c(a), '@') == 0) + addhostdom(a, hostdomain); + else if(*s_to_c(a) == '@') + a = fixrouteaddr(a, p->next, lastp); + p->s = a; + } + } + return from; +} +/* + * ensure route addr has brackets around it + */ +String* +fixrouteaddr(String *raddr, Node *next, Node *last) +{ + String *a; + + if(last && last->c == '<' && next && next->c == '>') + return raddr; /* properly formed already */ + + a = s_new(); + s_append(a, "<"); + s_append(a, s_to_c(raddr)); + s_append(a, ">"); + s_free(raddr); + return a; +} + +/* + * print out the parsed header + */ +int +printheader(void) +{ + int n, len; + Field *f; + Node *p; + char *cp; + char c[1]; + + n = 0; + for(f = firstfield; f; f = f->next){ + for(p = f->node; p; p = p->next){ + if(p->s) + n += dBprint("%s", s_to_c(p->s)); + else { + c[0] = p->c; + putcrnl(c, 1); + n++; + } + if(p->white){ + cp = s_to_c(p->white); + len = strlen(cp); + putcrnl(cp, len); + n += len; + } + uneaten = p->end; + } + putcrnl("\n", 1); + n++; + uneaten++; /* skip newline */ + } + return n; +} + +/* + * add a domain onto an name, return the new name + */ +char * +domainify(char *name, char *domain) +{ + static String *s; + char *p; + + if(domain==0 || strchr(name, '.')!=0) + return name; + + s = s_reset(s); + s_append(s, name); + p = strchr(domain, '.'); + if(p == 0){ + s_append(s, "."); + p = domain; + } + s_append(s, p); + return s_to_c(s); +} + +/* + * print message observing '.' escapes and using \r\n for \n + */ +void +putcrnl(char *cp, int n) +{ + int c; + + for(; n; n--, cp++){ + c = *cp; + if(c == '\n') + dBputc('\r'); + else if(c == '.' && last=='\n') + dBputc('.'); + dBputc(c); + last = c; + } +} + +/* + * Get a line including a crnl into a string. Convert crnl into nl. + */ +char * +getcrnl(String *s) +{ + int c; + int count; + + count = 0; + for(;;){ + c = Bgetc(&bin); + if(debug) + Bputc(&berr, c); + switch(c){ + case -1: + s_append(s, "connection closed unexpectedly by remote system"); + s_terminate(s); + return 0; + case '\r': + c = Bgetc(&bin); + if(c == '\n'){ + s_putc(s, c); + if(debug) + Bputc(&berr, c); + count++; + s_terminate(s); + return s->ptr - count; + } + Bungetc(&bin); + s_putc(s, '\r'); + if(debug) + Bputc(&berr, '\r'); + count++; + break; + default: + s_putc(s, c); + count++; + break; + } + } + return 0; +} + +/* + * print out a parsed date + */ +int +printdate(Node *p) +{ + int n, sep = 0; + + n = dBprint("Date: %s,", s_to_c(p->s)); + for(p = p->next; p; p = p->next){ + if(p->s){ + if(sep == 0) { + dBputc(' '); + n++; + } + if (p->next) + n += dBprint("%s", s_to_c(p->s)); + else + n += dBprint("%s", rewritezone(s_to_c(p->s))); + sep = 0; + } else { + dBputc(p->c); + n++; + sep = 1; + } + } + n += dBprint("\r\n"); + return n; +} + +char * +rewritezone(char *z) +{ + int mindiff; + char s; + Tm *tm; + static char x[7]; + + tm = localtime(time(0)); + mindiff = tm->tzoff/60; + + /* if not in my timezone, don't change anything */ + if(strcmp(tm->zone, z) != 0) + return z; + + if(mindiff < 0){ + s = '-'; + mindiff = -mindiff; + } else + s = '+'; + + sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60); + return x; +} + +/* + * stolen from libc/port/print.c + */ +#define SIZE 4096 +int +dBprint(char *fmt, ...) +{ + char buf[SIZE], *out; + va_list arg; + int n; + + va_start(arg, fmt); + out = vseprint(buf, buf+SIZE, fmt, arg); + va_end(arg); + if(debug){ + Bwrite(&berr, buf, (long)(out-buf)); + Bflush(&berr); + } + n = Bwrite(&bout, buf, (long)(out-buf)); + Bflush(&bout); + return n; +} + +int +dBputc(int x) +{ + if(debug) + Bputc(&berr, x); + return Bputc(&bout, x); +} + +char* +expand_addr(char* a) +{ + Ndb *db; + Ndbs s; + char *sys, *ret, *proto, *host; + + proto = strtok(a,"!"); + if ( strcmp(proto,"net") != 0 ) { + fprint(2,"unknown proto %s\n",proto); + } + host = strtok(0,"!"); + if ( strcmp(host,"$smtp") == 0 ) { + sys = sysname(); + db = ndbopen(unsharp("#9/ndb/local")); + host = ndbgetvalue(db, &s, "sys", sys, "smtp", nil); + } + ret = malloc(strlen(proto)+strlen(host)+2); + sprint(ret,"%s!%s",proto,host); + + return ret; + +} diff --git a/src/cmd/upas/smtp/smtp.h b/src/cmd/upas/smtp/smtp.h new file mode 100644 index 00000000..83619223 --- /dev/null +++ b/src/cmd/upas/smtp/smtp.h @@ -0,0 +1,61 @@ +typedef struct Node Node; +typedef struct Field Field; +typedef Node *Nodeptr; +#define YYSTYPE Nodeptr + +struct Node { + Node *next; + int c; /* token type */ + char addr; /* true if this is an address */ + String *s; /* string representing token */ + String *white; /* white space following token */ + char *start; /* first byte for this token */ + char *end; /* next byte in input */ +}; + +struct Field { + Field *next; + Node *node; + int source; +}; + +typedef struct DS DS; +struct DS { + /* dist string */ + char buf[128]; + char expand[128]; + char *netdir; + char *proto; + char *host; + char *service; +}; + +extern Field *firstfield; +extern Field *lastfield; +extern Node *usender; +extern Node *usys; +extern Node *udate; +extern int originator; +extern int destination; +extern int date; +extern int messageid; + +Node* anonymous(Node*); +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*); +void newfield(Node*, int); +void freefield(Field*); +void yyinit(char*, int); +int yyparse(void); +int yylex(void); +String* yywhite(void); +Node* whiten(Node*); +void yycleanup(void); +int mxdial(char*, char*, char*); +void dial_string_parse(char*, DS*); diff --git a/src/cmd/upas/smtp/smtpd.c b/src/cmd/upas/smtp/smtpd.c new file mode 100644 index 00000000..7042c37c --- /dev/null +++ b/src/cmd/upas/smtp/smtpd.c @@ -0,0 +1,1494 @@ +#include "common.h" +#include "smtpd.h" +#include "smtp.h" +#include <ctype.h> +#include <ip.h> +#include <ndb.h> +#include <mp.h> +#include <libsec.h> +#include <auth.h> +#include "../smtp/y.tab.h" + +#define DBGMX 1 + +char *me; +char *him=""; +char *dom; +process *pp; +String *mailer; +NetConnInfo *nci; + +int filterstate = ACCEPT; +int trusted; +int logged; +int rejectcount; +int hardreject; + +Biobuf bin; + +int debug; +int Dflag; +int fflag; +int gflag; +int rflag; +int sflag; +int authenticate; +int authenticated; +int passwordinclear; +char *tlscert; + +List senders; +List rcvers; + +char pipbuf[ERRMAX]; +char *piperror; +int pipemsg(int*); +String* startcmd(void); +int rejectcheck(void); +String* mailerpath(char*); + +static int +catchalarm(void *a, char *msg) +{ + int rv = 1; + + USED(a); + + /* log alarms but continue */ + if(strstr(msg, "alarm")){ + 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 = 0; + } + + /* kill the children if there are any */ + if(pp) + syskillpg(pp->pid); + + return rv; +} + + /* override string error functions to do something reasonable */ +void +s_error(char *f, char *status) +{ + char errbuf[Errlen]; + + errbuf[0] = 0; + rerrstr(errbuf, sizeof(errbuf)); + if(f && *f) + reply("452 out of memory %s: %s\r\n", f, errbuf); + else + reply("452 out of memory %s\r\n", errbuf); + syslog(0, "smtpd", "++Malloc failure %s [%s]", him, nci->rsys); + exits(status); +} + +void +main(int argc, char **argv) +{ + char *p, buf[1024]; + char *netdir; + + netdir = nil; + quotefmtinstall(); + ARGBEGIN{ + case 'D': + Dflag++; + break; + case 'd': + debug++; + break; + case 'n': /* log peer ip address */ + netdir = ARGF(); + break; + case 'f': /* disallow relaying */ + fflag = 1; + break; + case 'g': + gflag = 1; + break; + case 'h': /* default domain name */ + dom = ARGF(); + break; + case 'k': /* prohibited ip address */ + p = ARGF(); + if (p) + addbadguy(p); + break; + case 'm': /* set mail command */ + p = ARGF(); + if(p) + mailer = mailerpath(p); + break; + case 'r': + rflag = 1; /* verify sender's domain */ + break; + case 's': /* save blocked messages */ + sflag = 1; + break; + case 'a': + authenticate = 1; + break; + case 'p': + passwordinclear = 1; + break; + case 'c': + tlscert = ARGF(); + 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: + fprint(2, "usage: smtpd [-dfhrs] [-n net] [-c cert]\n"); + exits("usage"); + }ARGEND; + + nci = getnetconninfo(netdir, 0); + if(nci == nil) + sysfatal("can't get remote system's address"); + + if(mailer == nil) + mailer = mailerpath("send"); + + if(debug){ + close(2); + snprint(buf, sizeof(buf), "%s/smtpd", UPASLOG); + if (open(buf, OWRITE) >= 0) { + seek(2, 0, 2); + fprint(2, "%d smtpd %s\n", getpid(), thedate()); + } else + debug = 0; + } + getconf(); + Binit(&bin, 0, OREAD); + + chdir(UPASLOG); + me = sysname_read(); + if(dom == 0 || dom[0] == 0) + dom = domainname_read(); + if(dom == 0 || dom[0] == 0) + dom = me; + sayhi(); + parseinit(); + /* allow 45 minutes to parse the header */ + atnotify(catchalarm, 1); + alarm(45*60*1000); + zzparse(); + exits(0); +} + +void +listfree(List *l) +{ + Link *lp; + Link *next; + + for(lp = l->first; lp; lp = next){ + next = lp->next; + s_free(lp->p); + free(lp); + } + l->first = l->last = 0; +} + +void +listadd(List *l, String *path) +{ + Link *lp; + + lp = (Link *)malloc(sizeof(Link)); + lp->p = path; + lp->next = 0; + + if(l->last) + l->last->next = lp; + else + l->first = lp; + l->last = lp; +} + +#define SIZE 4096 +int +reply(char *fmt, ...) +{ + char buf[SIZE], *out; + va_list arg; + int n; + + va_start(arg, fmt); + out = vseprint(buf, buf+SIZE, fmt, arg); + va_end(arg); + n = (long)(out-buf); + if(debug) { + seek(2, 0, 2); + write(2, buf, n); + } + write(1, buf, n); + return n; +} + +void +reset(void) +{ + if(rejectcheck()) + return; + listfree(&rcvers); + listfree(&senders); + if(filterstate != DIALUP){ + logged = 0; + filterstate = ACCEPT; + } + reply("250 ok\r\n"); +} + +void +sayhi(void) +{ + reply("220 %s SMTP\r\n", dom); +} + +void +hello(String *himp, int extended) +{ + char **mynames; + + him = s_to_c(himp); + syslog(0, "smtpd", "%s from %s as %s", extended ? "ehlo" : "helo", nci->rsys, him); + if(rejectcheck()) + return; + + if(strchr(him, '.') && nci && !trusted && fflag && 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(strcmp(him, dom) == 0) + goto Liarliar; + for(mynames=sysnames_read(); mynames && *mynames; mynames++){ + if(cistrcmp(*mynames, him) == 0){ + Liarliar: + syslog(0, "smtpd", "Hung up on %s; claimed to be %s", + nci->rsys, him); + reply("554 Liar!\r\n"); + exits("client pretended to be us"); + return; + } + } + } + /* + * it is never acceptable to claim to be "localhost", + * "localhost.localdomain" or "localhost.example.com"; only spammers + * do this. it should be 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 software gets this wrong. + */ + if (strcmp(him, "localhost") == 0 || + strcmp(him, "localhost.localdomain") == 0 || + strcmp(him, "localhost.example.com") == 0) + goto Liarliar; + if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil) + him = nci->rsys; + + if(Dflag) + sleep(15*1000); + reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him); + if (extended) { + if(tlscert != nil) + reply("250-STARTTLS\r\n"); + if (passwordinclear) + reply("250 AUTH CRAM-MD5 PLAIN LOGIN\r\n"); + else + reply("250 AUTH CRAM-MD5\r\n"); + } +} + +void +sender(String *path) +{ + String *s; + static char *lastsender; + + if(rejectcheck()) + return; + if (authenticate && !authenticated) { + rejectcount++; + reply("530 Authentication required\r\n"); + return; + } + if(him == 0 || *him == 0){ + rejectcount++; + reply("503 Start by saying HELO, please.\r\n", s_to_c(path)); + return; + } + + /* don't add the domain onto black holes or we will loop */ + if(strchr(s_to_c(path), '!') == 0 && strcmp(s_to_c(path), "/dev/null") != 0){ + s = s_new(); + s_append(s, him); + s_append(s, "!"); + s_append(s, s_to_c(path)); + s_terminate(s); + s_free(path); + path = s; + } + if(shellchars(s_to_c(path))){ + rejectcount++; + reply("503 Bad character in sender address %s.\r\n", s_to_c(path)); + return; + } + + /* + * if the last sender address resulted in a rejection because the sending + * domain didn't exist and this sender has the same domain, reject immediately. + */ + if(lastsender){ + if (strncmp(lastsender, s_to_c(path), strlen(lastsender)) == 0){ + filterstate = REFUSED; + rejectcount++; + reply("554 Sender domain must exist: %s\r\n", s_to_c(path)); + return; + } + free(lastsender); /* different sender domain */ + lastsender = 0; + } + + /* + * see if this ip address, domain name, user name or account is blocked + */ + filterstate = blocked(path); + + logged = 0; + listadd(&senders, path); + reply("250 sender is %s\r\n", s_to_c(path)); +} + +enum { Rcpt, Domain, Ntoks }; + +typedef struct Sender Sender; +struct Sender { + Sender *next; + char *rcpt; + char *domain; +}; +static Sender *sendlist, *sendlast; +static uchar rsysip[IPaddrlen]; + +static int +rdsenders(void) +{ + int lnlen, nf, ok = 1; + char *line, *senderfile; + char *toks[Ntoks]; + Biobuf *sf; + Sender *snd; + static int beenhere = 0; + + if (beenhere) + return 1; + beenhere = 1; + + fmtinstall('I', eipfmt); + parseip(rsysip, nci->rsys); + + /* + * we're sticking with a system-wide sender list because + * per-user lists would require fully resolving recipient + * addresses to determine which users they correspond to + * (barring syntactic conventions). + */ + senderfile = smprint("%s/senders", UPASLIB); + sf = Bopen(senderfile, OREAD); + free(senderfile); + if (sf == nil) + return 1; + while ((line = Brdline(sf, '\n')) != nil) { + if (line[0] == '#' || line[0] == '\n') + continue; + lnlen = Blinelen(sf); + line[lnlen-1] = '\0'; /* clobber newline */ + nf = tokenize(line, toks, nelem(toks)); + if (nf != nelem(toks)) + continue; /* malformed line */ + + snd = malloc(sizeof *snd); + if (snd == nil) + sysfatal("out of memory: %r"); + memset(snd, 0, sizeof *snd); + snd->next = nil; + + if (sendlast == nil) + sendlist = snd; + else + sendlast->next = snd; + sendlast = snd; + snd->rcpt = strdup(toks[Rcpt]); + snd->domain = strdup(toks[Domain]); + } + Bterm(sf); + return ok; +} + +/* + * read (recipient, sender's DNS) pairs from /mail/lib/senders. + * Only allow mail to recipient from any of sender's IPs. + * A recipient not mentioned in the file is always permitted. + */ +static int +senderok(char *rcpt) +{ + int mentioned = 0, matched = 0; + uchar dnsip[IPaddrlen]; + Sender *snd; + Ndbtuple *nt, *next, *first; + + rdsenders(); + for (snd = sendlist; snd != nil; snd = snd->next) { + if (strcmp(rcpt, snd->rcpt) != 0) + continue; + /* + * see if this domain's ips match nci->rsys. + * if not, perhaps a later entry's domain will. + */ + mentioned = 1; + if (parseip(dnsip, snd->domain) != -1 && + memcmp(rsysip, dnsip, IPaddrlen) == 0) + return 1; + /* + * NB: nt->line links form a circular list(!). + * we need to make one complete pass over it to free it all. + */ + first = nt = dnsquery(nci->root, snd->domain, "ip"); + if (first == nil) + continue; + do { + if (strcmp(nt->attr, "ip") == 0 && + parseip(dnsip, nt->val) != -1 && + memcmp(rsysip, dnsip, IPaddrlen) == 0) + matched = 1; + next = nt->line; + free(nt); + nt = next; + } while (nt != first); + } + if (matched) + return 1; + else + return !mentioned; +} + +void +receiver(String *path) +{ + char *sender, *rcpt; + + if(rejectcheck()) + return; + if(him == 0 || *him == 0){ + rejectcount++; + reply("503 Start by saying HELO, please\r\n"); + return; + } + if(senders.last) + sender = s_to_c(senders.last->p); + else + sender = "<unknown>"; + + if(!recipok(s_to_c(path))){ + rejectcount++; + syslog(0, "smtpd", "Disallowed %s (%s/%s) to blocked name %s", + sender, him, nci->rsys, s_to_c(path)); + reply("550 %s ... user unknown\r\n", s_to_c(path)); + return; + } + rcpt = s_to_c(path); + if (!senderok(rcpt)) { + rejectcount++; + syslog(0, "smtpd", "Disallowed sending IP of %s (%s/%s) to %s", + sender, him, nci->rsys, rcpt); + reply("550 %s ... sending system not allowed\r\n", rcpt); + return; + } + + logged = 0; + /* forwarding() can modify 'path' on loopback request */ + if(filterstate == ACCEPT && (fflag && !authenticated) && forwarding(path)) { + syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)", + s_to_c(senders.last->p), him, nci->rsys, s_to_c(path)); + rejectcount++; + reply("550 we don't relay. send to your-path@[] for loopback.\r\n"); + return; + } + listadd(&rcvers, path); + reply("250 receiver is %s\r\n", s_to_c(path)); +} + +void +quit(void) +{ + reply("221 Successful termination\r\n"); + close(0); + exits(0); +} + +void +turn(void) +{ + if(rejectcheck()) + return; + reply("502 TURN unimplemented\r\n"); +} + +void +noop(void) +{ + if(rejectcheck()) + return; + reply("250 Stop wasting my time!\r\n"); +} + +void +help(String *cmd) +{ + if(rejectcheck()) + return; + if(cmd) + s_free(cmd); + reply("250 Read rfc821 and stop wasting my time\r\n"); +} + +void +verify(String *path) +{ + char *p, *q; + char *av[4]; + + if(rejectcheck()) + return; + if(shellchars(s_to_c(path))){ + reply("503 Bad character in address %s.\r\n", s_to_c(path)); + return; + } + av[0] = s_to_c(mailer); + av[1] = "-x"; + av[2] = s_to_c(path); + av[3] = 0; + + pp = noshell_proc_start(av, (stream *)0, outstream(), (stream *)0, 1, 0); + if (pp == 0) { + reply("450 We're busy right now, try later\r\n"); + return; + } + + p = Brdline(pp->std[1]->fp, '\n'); + if(p == 0){ + reply("550 String does not match anything.\r\n"); + } else { + p[Blinelen(pp->std[1]->fp)-1] = 0; + if(strchr(p, ':')) + reply("550 String does not match anything.\r\n"); + else{ + q = strrchr(p, '!'); + if(q) + p = q+1; + reply("250 %s <%s@%s>\r\n", s_to_c(path), p, dom); + } + } + proc_wait(pp); + proc_free(pp); + pp = 0; +} + +/* + * get a line that ends in crnl or cr, turn terminating crnl into a nl + * + * return 0 on EOF + */ +static int +getcrnl(String *s, Biobuf *fp) +{ + int c; + + for(;;){ + c = Bgetc(fp); + if(debug) { + seek(2, 0, 2); + fprint(2, "%c", c); + } + switch(c){ + case -1: + goto out; + case '\r': + c = Bgetc(fp); + if(c == '\n'){ + if(debug) { + seek(2, 0, 2); + fprint(2, "%c", c); + } + s_putc(s, '\n'); + goto out; + } + Bungetc(fp); + s_putc(s, '\r'); + break; + case '\n': + s_putc(s, c); + goto out; + default: + s_putc(s, c); + break; + } + } +out: + s_terminate(s); + return s_len(s); +} + +void +logcall(int nbytes) +{ + Link *l; + String *to, *from; + + to = s_new(); + from = s_new(); + for(l = senders.first; l; l = l->next){ + if(l != senders.first) + s_append(from, ", "); + s_append(from, s_to_c(l->p)); + } + for(l = rcvers.first; l; l = l->next){ + if(l != rcvers.first) + s_append(to, ", "); + s_append(to, s_to_c(l->p)); + } + syslog(0, "smtpd", "[%s/%s] %s sent %d bytes to %s", him, nci->rsys, + s_to_c(from), nbytes, s_to_c(to)); + s_free(to); + s_free(from); +} + +static void +logmsg(char *action) +{ + Link *l; + + if(logged) + return; + + logged = 1; + for(l = rcvers.first; l; l = l->next) + syslog(0, "smtpd", "%s %s (%s/%s) (%s)", action, + s_to_c(senders.last->p), him, nci->rsys, s_to_c(l->p)); +} + +static int +optoutall(int filterstate) +{ + Link *l; + + switch(filterstate){ + case ACCEPT: + case TRUSTED: + return filterstate; + } + + for(l = rcvers.first; l; l = l->next) + if(!optoutofspamfilter(s_to_c(l->p))) + return filterstate; + + return ACCEPT; +} + +String* +startcmd(void) +{ + int n; + Link *l; + char **av; + String *cmd; + char *filename; + + /* + * ignore the filterstate if the all the receivers prefer it. + */ + filterstate = optoutall(filterstate); + + switch (filterstate){ + case BLOCKED: + case DELAY: + rejectcount++; + logmsg("Blocked"); + filename = dumpfile(s_to_c(senders.last->p)); + cmd = s_new(); + s_append(cmd, "cat > "); + s_append(cmd, filename); + pp = proc_start(s_to_c(cmd), instream(), 0, outstream(), 0, 0); + break; + case DIALUP: + logmsg("Dialup"); + rejectcount++; + reply("554 We don't accept mail from dial-up ports.\r\n"); + /* + * we could exit here, because we're never going to accept mail from this + * ip address, but it's unclear that RFC821 allows that. Instead we set + * the hardreject flag and go stupid. + */ + hardreject = 1; + return 0; + case DENIED: + logmsg("Denied"); + rejectcount++; + reply("554-We don't accept mail from %s.\r\n", s_to_c(senders.last->p)); + reply("554 Contact postmaster@%s for more information.\r\n", dom); + return 0; + case REFUSED: + logmsg("Refused"); + rejectcount++; + reply("554 Sender domain must exist: %s\r\n", s_to_c(senders.last->p)); + return 0; + default: + case NONE: + logmsg("Confused"); + rejectcount++; + reply("554-We have had an internal mailer error classifying your message.\r\n"); + reply("554-Filterstate is %d\r\n", filterstate); + reply("554 Contact postmaster@%s for more information.\r\n", dom); + return 0; + case ACCEPT: + case TRUSTED: + /* + * now that all other filters have been passed, + * do grey-list processing. + */ + if(gflag) + vfysenderhostok(); + + /* + * set up mail command + */ + cmd = s_clone(mailer); + n = 3; + for(l = rcvers.first; l; l = l->next) + n++; + av = malloc(n*sizeof(char*)); + if(av == nil){ + reply("450 We're busy right now, try later\n"); + s_free(cmd); + return 0; + } + + n = 0; + av[n++] = s_to_c(cmd); + av[n++] = "-r"; + for(l = rcvers.first; l; l = l->next) + av[n++] = s_to_c(l->p); + av[n] = 0; + /* + * start mail process + */ + pp = noshell_proc_start(av, instream(), outstream(), outstream(), 0, 0); + free(av); + break; + } + if(pp == 0) { + reply("450 We're busy right now, try later\n"); + s_free(cmd); + return 0; + } + return cmd; +} + +/* + * print out a header line, expanding any domainless addresses into + * address@him + */ +char* +bprintnode(Biobuf *b, Node *p) +{ + 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; + } else { + if(Bwrite(b, s_to_c(p->s), s_len(p->s)) < 0) + return nil; + } + }else{ + if(Bputc(b, p->c) < 0) + return nil; + } + if(p->white) + if(Bwrite(b, s_to_c(p->white), s_len(p->white)) < 0) + return nil; + return p->end+1; +} + +static String* +getaddr(Node *p) +{ + for(; p; p = p->next) + if(p->s && p->addr) + return p->s; + return nil; +} + +/* + * add waring headers of the form + * X-warning: <reason> + * for any headers that looked like they might be forged. + * + * return byte count of new headers + */ +static int +forgedheaderwarnings(void) +{ + int nbytes; + Field *f; + + nbytes = 0; + + /* warn about envelope sender */ + 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"); + + /* + * check Sender: field. If it's OK, ignore the others because this is an + * exploded mailing list. + */ + for(f = firstfield; f; f = f->next){ + if(f->node->c == SENDER){ + if(masquerade(getaddr(f->node), him)) + nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect Sender: domain\n"); + else + return nbytes; + } + } + + /* check From: */ + for(f = firstfield; f; f = f->next){ + if(f->node->c == FROM && masquerade(getaddr(f->node), him)) + nbytes += Bprint(pp->std[0]->fp, "X-warning: suspect From: domain\n"); + } + 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) +{ + int status; + char *cp; + String *line; + String *hdr; + int n, nbytes; + int sawdot; + Field *f; + Node *p; + Link *l; + + 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 + */ + yyinit(s_to_c(hdr), s_len(hdr)); + yyparse(); + + /* + * Look for masquerades. Let Sender: trump From: to allow mailing list + * forwarded messages. + */ + if(fflag) + nbytes += forgedheaderwarnings(); + + /* + * add an orginator and/or destination if either is missing + */ + if(originator == 0){ + if(senders.last == nil) + Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him); + else + Bprint(pp->std[0]->fp, "From: %s\n", s_to_c(senders.last->p)); + } + if(destination == 0){ + Bprint(pp->std[0]->fp, "To: "); + for(l = rcvers.first; l; l = l->next){ + if(l != rcvers.first) + Bprint(pp->std[0]->fp, ", "); + Bprint(pp->std[0]->fp, "%s", s_to_c(l->p)); + } + Bprint(pp->std[0]->fp, "\n"); + } + + /* + * add sender's domain to any domainless addresses + * (to avoid forging local addresses) + */ + cp = s_to_c(hdr); + for(f = firstfield; cp != nil && f; f = f->next){ + for(p = f->node; cp != 0 && p; p = p->next) + cp = bprintnode(pp->std[0]->fp, p); + if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){ + piperror = "write error"; + status = 1; + } + } + if(cp == nil){ + piperror = "sender domain"; + status = 1; + } + + /* write anything we read following the header */ + if(status == 0 && Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp) < 0){ + piperror = "write error 2"; + status = 1; + } + s_free(hdr); + + /* + * pass rest of message to mailer. take care of '.' + * escapes. + */ + while(sawdot == 0){ + 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; + } + nbytes += n; + if(status == 0 && Bwrite(pp->std[0]->fp, *cp == '.' ? cp+1 : cp, n) < 0){ + piperror = "write error 3"; + status = 1; + } + } + s_free(line); + if(sawdot == 0){ + /* message did not terminate normally */ + snprint(pipbuf, sizeof pipbuf, "network eof: %r"); + piperror = pipbuf; + syskillpg(pp->pid); + status = 1; + } + + if(status == 0 && Bflush(pp->std[0]->fp) < 0){ + piperror = "write error 4"; + status = 1; + } + stream_free(pp->std[0]); + pp->std[0] = 0; + *byteswritten = nbytes; + pipesigoff(); + if(status && !piperror) + piperror = "write on closed pipe"; + return status; +} + +char* +firstline(char *x) +{ + static char buf[128]; + char *p; + + strncpy(buf, x, sizeof(buf)); + buf[sizeof(buf)-1] = 0; + p = strchr(buf, '\n'); + if(p) + *p = 0; + return buf; +} + +int +sendermxcheck(void) +{ + char *cp, *senddom, *user; + char *who; + int pid; + Waitmsg *w; + + who = s_to_c(senders.first->p); + if(strcmp(who, "/dev/null") == 0){ + /* /dev/null can only send to one rcpt at a time */ + if(rcvers.first != rcvers.last){ + werrstr("rejected: /dev/null sending to multiple recipients"); + return -1; + } + return 0; + } + + if(access("/mail/lib/validatesender", AEXEC) < 0) + return 0; + + senddom = strdup(who); + if((cp = strchr(senddom, '!')) == nil){ + werrstr("rejected: domainless sender %s", who); + free(senddom); + return -1; + } + *cp++ = 0; + user = cp; + + switch(pid = fork()){ + case -1: + werrstr("deferred: fork: %r"); + return -1; + case 0: + /* + * Could add an option with the remote IP address + * to allow validatesender to implement SPF eventually. + */ + execl("/mail/lib/validatesender", "validatesender", + "-n", nci->root, senddom, user, nil); + _exits("exec validatesender: %r"); + default: + break; + } + + free(senddom); + w = wait(); + if(w == nil){ + werrstr("deferred: wait failed: %r"); + return -1; + } + if(w->pid != pid){ + werrstr("deferred: wait returned wrong pid %d != %d", w->pid, pid); + free(w); + return -1; + } + if(w->msg[0] == 0){ + free(w); + return 0; + } + /* + * skip over validatesender 143123132: prefix from rc. + */ + cp = strchr(w->msg, ':'); + if(cp && *(cp+1) == ' ') + werrstr("%s", cp+2); + else + werrstr("%s", w->msg); + free(w); + return -1; +} + +void +data(void) +{ + String *cmd; + String *err; + int status, nbytes; + char *cp, *ep; + char errx[ERRMAX]; + Link *l; + + if(rejectcheck()) + return; + if(senders.last == 0){ + reply("503 Data without MAIL FROM:\r\n"); + rejectcount++; + return; + } + if(rcvers.last == 0){ + reply("503 Data without RCPT TO:\r\n"); + rejectcount++; + return; + } + if(sendermxcheck()){ + rerrstr(errx, sizeof errx); + if(strncmp(errx, "rejected:", 9) == 0) + reply("554 %s\r\n", errx); + else + reply("450 %s\r\n", errx); + 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); + rejectcount++; + return; + } + + cmd = startcmd(); + if(cmd == 0) + return; + + reply("354 Input message; end with <CRLF>.<CRLF>\r\n"); + + /* + * allow 145 more minutes to move the data + */ + alarm(145*60*1000); + + status = pipemsg(&nbytes); + + /* + * read any error messages + */ + err = s_new(); + while(s_read_line(pp->std[2]->fp, err)) + ; + + alarm(0); + atnotify(catchalarm, 0); + + status |= proc_wait(pp); + if(debug){ + seek(2, 0, 2); + fprint(2, "%d status %ux\n", getpid(), status); + if(*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){ + int code; + + 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; + } 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; + } + for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){ + *ep++ = 0; + reply("%d-%s\r\n", code, cp); + } + reply("%d mail process terminated abnormally\r\n", code); + } else { + if(filterstate == BLOCKED) + reply("554 we believe this is spam. we don't accept it.\r\n"); + else + if(filterstate == DELAY) + reply("554 There will be a delay in delivery of this message.\r\n"); + else { + reply("250 sent\r\n"); + logcall(nbytes); + } + } + proc_free(pp); + pp = 0; + s_free(cmd); + s_free(err); + + listfree(&senders); + listfree(&rcvers); +} + +/* + * when we have blocked a transaction based on IP address, there is nothing + * that the sender can do to convince us to take the message. after the + * first rejection, some spammers continually RSET and give a new MAIL FROM: + * filling our logs with rejections. rejectcheck() limits the retries and + * swiftly rejects all further commands after the first 500-series message + * is issued. + */ +int +rejectcheck(void) +{ + + if(rejectcount > MAXREJECTS){ + syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys); + reply("554 too many errors. transaction failed.\r\n"); + exits("errcount"); + } + if(hardreject){ + rejectcount++; + reply("554 We don't accept mail from dial-up ports.\r\n"); + } + return hardreject; +} + +/* + * create abs path of the mailer + */ +String* +mailerpath(char *p) +{ + String *s; + + if(p == nil) + return nil; + if(*p == '/') + return s_copy(p); + s = s_new(); + s_append(s, UPASBIN); + s_append(s, "/"); + s_append(s, p); + return s; +} + +String * +s_dec64(String *sin) +{ + String *sout; + int lin, lout; + lin = s_len(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') + lin--; + sout = s_newalloc(lin+1); + lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin); + if (lout < 0) { + s_free(sout); + return nil; + } + sout->ptr = sout->base + lout; + s_terminate(sout); + return sout; +} + +void +starttls(void) +{ + uchar *cert; + int certlen, fd; + TLSconn *conn; + + conn = mallocz(sizeof *conn, 1); + cert = readcert(tlscert, &certlen); + if (conn == nil || cert == nil) { + if (conn != nil) + free(conn); + reply("454 TLS not available\r\n"); + return; + } + reply("220 Go ahead make my day\r\n"); + conn->cert = cert; + conn->certlen = certlen; + fd = tlsServer(Bfildes(&bin), conn); + if (fd < 0) { + free(cert); + free(conn); + syslog(0, "smtpd", "TLS start-up failed with %s", him); + + /* force the client to hang up */ + close(Bfildes(&bin)); /* probably fd 0 */ + close(1); + exits("tls failed"); + } + Bterm(&bin); + Binit(&bin, fd, OREAD); + if (dup(fd, 1) < 0) + fprint(2, "dup of %d failed: %r\n", fd); + passwordinclear = 1; + syslog(0, "smtpd", "started TLS with %s", him); +} + +void +auth(String *mech, String *resp) +{ + Chalstate *chs = nil; + AuthInfo *ai = nil; + String *s_resp1_64 = nil; + String *s_resp2_64 = nil; + String *s_resp1 = nil; + String *s_resp2 = nil; + char *scratch = nil; + char *user, *pass; + + if (rejectcheck()) + goto bomb_out; + + syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech), + "(protected)", him); + + if (authenticated) { + bad_sequence: + rejectcount++; + reply("503 Bad sequence of commands\r\n"); + goto bomb_out; + } + if (cistrcmp(s_to_c(mech), "plain") == 0) { + + if (!passwordinclear) { + rejectcount++; + reply("538 Encryption required for requested authentication mechanism\r\n"); + goto bomb_out; + } + s_resp1_64 = resp; + if (s_resp1_64 == nil) { + reply("334 \r\n"); + s_resp1_64 = s_new(); + if (getcrnl(s_resp1_64, &bin) <= 0) { + goto bad_sequence; + } + } + s_resp1 = s_dec64(s_resp1_64); + if (s_resp1 == nil) { + rejectcount++; + reply("501 Cannot decode base64\r\n"); + goto bomb_out; + } + 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; + memset(pass, 'X', strlen(pass)); + goto windup; + } + else if (cistrcmp(s_to_c(mech), "login") == 0) { + + if (!passwordinclear) { + rejectcount++; + reply("538 Encryption required for requested authentication mechanism\r\n"); + goto bomb_out; + } + if (resp == nil) { + reply("334 VXNlcm5hbWU6\r\n"); + s_resp1_64 = s_new(); + if (getcrnl(s_resp1_64, &bin) <= 0) + goto bad_sequence; + } + reply("334 UGFzc3dvcmQ6\r\n"); + s_resp2_64 = s_new(); + if (getcrnl(s_resp2_64, &bin) <= 0) + goto bad_sequence; + s_resp1 = s_dec64(s_resp1_64); + s_resp2 = s_dec64(s_resp2_64); + memset(s_to_c(s_resp2_64), 'X', s_len(s_resp2_64)); + if (s_resp1 == nil || s_resp2 == nil) { + rejectcount++; + reply("501 Cannot decode base64\r\n"); + goto bomb_out; + } + ai = auth_userpasswd(s_to_c(s_resp1), s_to_c(s_resp2)); + authenticated = ai != nil; + memset(s_to_c(s_resp2), 'X', s_len(s_resp2)); + windup: + if (authenticated) + reply("235 Authentication successful\r\n"); + else { + rejectcount++; + reply("535 Authentication failed\r\n"); + } + goto bomb_out; + } + else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) { + char *resp; + int chal64n; + char *t; + + chs = auth_challenge("proto=cram role=server"); + if (chs == nil) { + rejectcount++; + reply("501 Couldn't get CRAM-MD5 challenge\r\n"); + goto bomb_out; + } + scratch = malloc(chs->nchal * 2 + 1); + chal64n = enc64(scratch, chs->nchal * 2, (uchar *)chs->chal, chs->nchal); + scratch[chal64n] = 0; + reply("334 %s\r\n", scratch); + s_resp1_64 = s_new(); + if (getcrnl(s_resp1_64, &bin) <= 0) + goto bad_sequence; + s_resp1 = s_dec64(s_resp1_64); + if (s_resp1 == nil) { + rejectcount++; + reply("501 Cannot decode base64\r\n"); + goto bomb_out; + } + /* should be of form <user><space><response> */ + resp = s_to_c(s_resp1); + t = strchr(resp, ' '); + if (t == nil) { + rejectcount++; + reply("501 Poorly formed CRAM-MD5 response\r\n"); + goto bomb_out; + } + *t++ = 0; + chs->user = resp; + chs->resp = t; + chs->nresp = strlen(t); + ai = auth_response(chs); + authenticated = ai != nil; + goto windup; + } + rejectcount++; + reply("501 Unrecognised authentication type %s\r\n", s_to_c(mech)); +bomb_out: + if (ai) + auth_freeAI(ai); + if (chs) + auth_freechal(chs); + if (scratch) + free(scratch); + if (s_resp1) + s_free(s_resp1); + if (s_resp2) + s_free(s_resp2); + if (s_resp1_64) + s_free(s_resp1_64); + if (s_resp2_64) + s_free(s_resp2_64); +} diff --git a/src/cmd/upas/smtp/smtpd.h b/src/cmd/upas/smtp/smtpd.h new file mode 100644 index 00000000..3a8e60e2 --- /dev/null +++ b/src/cmd/upas/smtp/smtpd.h @@ -0,0 +1,68 @@ +enum { + ACCEPT = 0, + REFUSED, + DENIED, + DIALUP, + BLOCKED, + DELAY, + TRUSTED, + NONE, + + MAXREJECTS = 100, +}; + + +typedef struct Link Link; +typedef struct List List; + +struct Link { + Link *next; + String *p; +}; + +struct List { + Link *first; + Link *last; +}; + +extern int fflag; +extern int rflag; +extern int sflag; + +extern int debug; +extern NetConnInfo *nci; +extern char *dom; +extern char* me; +extern int trusted; +extern List senders; +extern List rcvers; + +void addbadguy(char*); +void auth(String *, String *); +int blocked(String*); +void data(void); +char* dumpfile(char*); +int forwarding(String*); +void getconf(void); +void hello(String*, int extended); +void help(String *); +int isbadguy(void); +void listadd(List*, String*); +void listfree(List*); +int masquerade(String*, char*); +void noop(void); +int optoutofspamfilter(char*); +void quit(void); +void parseinit(void); +void receiver(String*); +int recipok(char*); +int reply(char*, ...); +void reset(void); +int rmtdns(char*, char*); +void sayhi(void); +void sender(String*); +void starttls(void); +void turn(void); +void verify(String*); +void vfysenderhostok(void); +int zzparse(void); diff --git a/src/cmd/upas/smtp/smtpd.y b/src/cmd/upas/smtp/smtpd.y new file mode 100644 index 00000000..57b89a02 --- /dev/null +++ b/src/cmd/upas/smtp/smtpd.y @@ -0,0 +1,317 @@ +%{ +#include "common.h" +#include <ctype.h> +#include "smtpd.h" + +#define YYSTYPE yystype +typedef struct quux yystype; +struct quux { + String *s; + int c; +}; +Biobuf *yyfp; +YYSTYPE *bang; +extern Biobuf bin; +extern int debug; + +YYSTYPE cat(YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*, YYSTYPE*); +int yyparse(void); +int yylex(void); +YYSTYPE anonymous(void); +%} + +%term SPACE +%term CNTRL +%term CRLF +%start conversation +%% + +conversation : cmd + | conversation cmd + ; +cmd : error + | 'h' 'e' 'l' 'o' spaces sdomain CRLF + { hello($6.s, 0); } + | 'e' 'h' 'l' 'o' spaces sdomain CRLF + { hello($6.s, 1); } + | 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF + { sender($11.s); } + | 'm' 'a' 'i' 'l' spaces 'f' 'r' 'o' 'm' ':' spath spaces 'a' 'u' 't' 'h' '=' sauth CRLF + { sender($11.s); } + | 'r' 'c' 'p' 't' spaces 't' 'o' ':' spath CRLF + { receiver($9.s); } + | 'd' 'a' 't' 'a' CRLF + { data(); } + | 'r' 's' 'e' 't' CRLF + { reset(); } + | 's' 'e' 'n' 'd' spaces 'f' 'r' 'o' 'm' ':' spath CRLF + { sender($11.s); } + | 's' 'o' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF + { sender($11.s); } + | 's' 'a' 'm' 'l' spaces 'f' 'r' 'o' 'm' ':' spath CRLF + { sender($11.s); } + | 'v' 'r' 'f' 'y' spaces string CRLF + { verify($6.s); } + | 'e' 'x' 'p' 'n' spaces string CRLF + { verify($6.s); } + | 'h' 'e' 'l' 'p' CRLF + { help(0); } + | 'h' 'e' 'l' 'p' spaces string CRLF + { help($6.s); } + | 'n' 'o' 'o' 'p' CRLF + { noop(); } + | 'q' 'u' 'i' 't' CRLF + { quit(); } + | 't' 'u' 'r' 'n' CRLF + { turn(); } + | 's' 't' 'a' 'r' 't' 't' 'l' 's' CRLF + { starttls(); } + | 'a' 'u' 't' 'h' spaces name spaces string CRLF + { auth($6.s, $8.s); } + | 'a' 'u' 't' 'h' spaces name CRLF + { auth($6.s, nil); } + | CRLF + { reply("501 illegal command or bad syntax\r\n"); } + ; +path : '<' '>' ={ $$ = anonymous(); } + | '<' mailbox '>' ={ $$ = $2; } + | '<' a_d_l ':' mailbox '>' ={ $$ = cat(&$2, bang, &$4, 0, 0 ,0, 0); } + ; +spath : path ={ $$ = $1; } + | spaces path ={ $$ = $2; } + ; +auth : path ={ $$ = $1; } + | mailbox ={ $$ = $1; } + ; +sauth : auth ={ $$ = $1; } + | spaces auth ={ $$ = $2; } + ; + ; +a_d_l : at_domain ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | at_domain ',' a_d_l ={ $$ = cat(&$1, bang, &$3, 0, 0, 0, 0); } + ; +at_domain : '@' domain ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); } + ; +sdomain : domain ={ $$ = $1; } + | domain spaces ={ $$ = $1; } + ; +domain : element ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | element '.' ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | element '.' domain ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); } + ; +element : name ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | '#' number ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + | '[' ']' ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + | '[' dotnum ']' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); } + ; +mailbox : local_part ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | local_part '@' domain ={ $$ = cat(&$3, bang, &$1, 0, 0 ,0, 0); } + ; +local_part : dot_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | quoted_string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + ; +name : let_dig ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + | let_dig ldh_str ld_str ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); } + ; +ld_str : let_dig + | let_dig ld_str ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + ; +ldh_str : hunder + | ld_str hunder ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + | ldh_str ld_str hunder ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); } + ; +let_dig : a + | d + ; +dot_string : string ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | string '.' dot_string ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); } + ; + +string : char ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | string char ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + ; + +quoted_string : '"' qtext '"' ={ $$ = cat(&$1, &$2, &$3, 0, 0 ,0, 0); } + ; +qtext : '\\' x ={ $$ = cat(&$2, 0, 0, 0, 0 ,0, 0); } + | qtext '\\' x ={ $$ = cat(&$1, &$3, 0, 0, 0 ,0, 0); } + | q + | qtext q ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + ; +char : c + | '\\' x ={ $$ = $2; } + ; +dotnum : snum '.' snum '.' snum '.' snum ={ $$ = cat(&$1, &$2, &$3, &$4, &$5, &$6, &$7); } + ; +number : d ={ $$ = cat(&$1, 0, 0, 0, 0 ,0, 0); } + | number d ={ $$ = cat(&$1, &$2, 0, 0, 0 ,0, 0); } + ; +snum : number ={ if(atoi(s_to_c($1.s)) > 255) print("bad snum\n"); } + ; +spaces : SPACE ={ $$ = $1; } + | SPACE spaces ={ $$ = $1; } + ; +hunder : '-' | '_' + ; +special1 : CNTRL + | '(' | ')' | ',' | '.' + | ':' | ';' | '<' | '>' | '@' + ; +special : special1 | '\\' | '"' + ; +notspecial : '!' | '#' | '$' | '%' | '&' | '\'' + | '*' | '+' | '-' | '/' + | '=' | '?' + | '[' | ']' | '^' | '_' | '`' | '{' | '|' | '}' | '~' + ; + +a : 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' + | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' + | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' + ; +d : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' + ; +c : a | d | notspecial + ; +q : a | d | special1 | notspecial | SPACE + ; +x : a | d | special | notspecial | SPACE + ; +%% + +void +parseinit(void) +{ + bang = (YYSTYPE*)malloc(sizeof(YYSTYPE)); + bang->c = '!'; + bang->s = 0; + yyfp = &bin; +} + +yylex(void) +{ + int c; + + for(;;){ + c = Bgetc(yyfp); + if(c == -1) + return 0; + if(debug) + fprint(2, "%c", c); + yylval.c = c = c & 0x7F; + if(c == '\n'){ + return CRLF; + } + if(c == '\r'){ + c = Bgetc(yyfp); + if(c != '\n'){ + Bungetc(yyfp); + c = '\r'; + } else { + if(debug) + fprint(2, "%c", c); + return CRLF; + } + } + if(isalpha(c)) + return tolower(c); + if(isspace(c)) + return SPACE; + if(iscntrl(c)) + return CNTRL; + return c; + } +} + +YYSTYPE +cat(YYSTYPE *y1, YYSTYPE *y2, YYSTYPE *y3, YYSTYPE *y4, YYSTYPE *y5, YYSTYPE *y6, YYSTYPE *y7) +{ + YYSTYPE rv; + + if(y1->s) + rv.s = y1->s; + else { + rv.s = s_new(); + s_putc(rv.s, y1->c); + s_terminate(rv.s); + } + if(y2){ + if(y2->s){ + s_append(rv.s, s_to_c(y2->s)); + s_free(y2->s); + } else { + s_putc(rv.s, y2->c); + s_terminate(rv.s); + } + } else + return rv; + if(y3){ + if(y3->s){ + s_append(rv.s, s_to_c(y3->s)); + s_free(y3->s); + } else { + s_putc(rv.s, y3->c); + s_terminate(rv.s); + } + } else + return rv; + if(y4){ + if(y4->s){ + s_append(rv.s, s_to_c(y4->s)); + s_free(y4->s); + } else { + s_putc(rv.s, y4->c); + s_terminate(rv.s); + } + } else + return rv; + if(y5){ + if(y5->s){ + s_append(rv.s, s_to_c(y5->s)); + s_free(y5->s); + } else { + s_putc(rv.s, y5->c); + s_terminate(rv.s); + } + } else + return rv; + if(y6){ + if(y6->s){ + s_append(rv.s, s_to_c(y6->s)); + s_free(y6->s); + } else { + s_putc(rv.s, y6->c); + s_terminate(rv.s); + } + } else + return rv; + if(y7){ + if(y7->s){ + s_append(rv.s, s_to_c(y7->s)); + s_free(y7->s); + } else { + s_putc(rv.s, y7->c); + s_terminate(rv.s); + } + } else + return rv; +} + +void +yyerror(char *x) +{ + USED(x); +} + +/* + * an anonymous user + */ +YYSTYPE +anonymous(void) +{ + YYSTYPE rv; + + rv.s = s_copy("/dev/null"); + return rv; +} diff --git a/src/cmd/upas/smtp/spam.c b/src/cmd/upas/smtp/spam.c new file mode 100644 index 00000000..84a8fccc --- /dev/null +++ b/src/cmd/upas/smtp/spam.c @@ -0,0 +1,591 @@ +#include "common.h" +#include "smtpd.h" +#include <ip.h> + +enum { + NORELAY = 0, + DNSVERIFY, + SAVEBLOCK, + DOMNAME, + OURNETS, + OURDOMS, + + IP = 0, + STRING, +}; + + +typedef struct Keyword Keyword; + +struct Keyword { + char *name; + int code; +}; + +static Keyword options[] = { + "norelay", NORELAY, + "verifysenderdom", DNSVERIFY, + "saveblockedmsg", SAVEBLOCK, + "defaultdomain", DOMNAME, + "ournets", OURNETS, + "ourdomains", OURDOMS, + 0, NONE, +}; + +static Keyword actions[] = { + "allow", ACCEPT, + "block", BLOCKED, + "deny", DENIED, + "dial", DIALUP, + "delay", DELAY, + 0, NONE, +}; + +static int hisaction; +static List ourdoms; +static List badguys; +static ulong v4peerip; + +static char* getline(Biobuf*); +static int cidrcheck(char*); + +static int +findkey(char *val, Keyword *p) +{ + + for(; p->name; p++) + if(strcmp(val, p->name) == 0) + break; + return p->code; +} + +char* +actstr(int a) +{ + char buf[32]; + Keyword *p; + + for(p=actions; p->name; p++) + if(p->code == a) + return p->name; + if(a==NONE) + return "none"; + sprint(buf, "%d", a); + return buf; +} + +int +getaction(char *s, char *type) +{ + char buf[1024]; + Keyword *k; + + if(s == nil || *s == 0) + return ACCEPT; + + for(k = actions; k->name != 0; k++){ + snprint(buf, sizeof buf, "/mail/ratify/%s/%s/%s", k->name, type, s); + if(access(buf,0) >= 0) + return k->code; + } + return ACCEPT; +} + +int +istrusted(char *s) +{ + char buf[1024]; + + if(s == nil || *s == 0) + return 0; + + snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s); + return access(buf,0) >= 0; +} + +void +getconf(void) +{ + Biobuf *bp; + char *cp, *p; + String *s; + char buf[512]; + uchar addr[4]; + + v4parseip(addr, nci->rsys); + v4peerip = nhgetl(addr); + + trusted = istrusted(nci->rsys); + hisaction = getaction(nci->rsys, "ip"); + if(debug){ + fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted); + fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction)); + } + snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB); + bp = sysopen(buf, "r", 0); + if(bp == 0) + return; + + for(;;){ + cp = getline(bp); + if(cp == 0) + break; + p = cp+strlen(cp)+1; + switch(findkey(cp, options)){ + case NORELAY: + if(fflag == 0 && strcmp(p, "on") == 0) + fflag++; + break; + case DNSVERIFY: + if(rflag == 0 && strcmp(p, "on") == 0) + rflag++; + break; + case SAVEBLOCK: + if(sflag == 0 && strcmp(p, "on") == 0) + sflag++; + break; + case DOMNAME: + if(dom == 0) + dom = strdup(p); + break; + case OURNETS: + if (trusted == 0) + trusted = cidrcheck(p); + break; + case OURDOMS: + while(*p){ + s = s_new(); + s_append(s, p); + listadd(&ourdoms, s); + p += strlen(p)+1; + } + break; + default: + break; + } + } + sysclose(bp); +} + +/* + * match a user name. the only meta-char is '*' which matches all + * characters. we only allow it as "*", which matches anything or + * an * at the end of the name (e.g., "username*") which matches + * trailing characters. + */ +static int +usermatch(char *pathuser, char *specuser) +{ + int n; + + n = strlen(specuser)-1; + if(specuser[n] == '*'){ + if(n == 0) /* match everything */ + return 0; + return strncmp(pathuser, specuser, n); + } + return strcmp(pathuser, specuser); +} + +static int +dommatch(char *pathdom, char *specdom) +{ + int n; + + if (*specdom == '*'){ + if (specdom[1] == '.' && specdom[2]){ + specdom += 2; + n = strlen(pathdom)-strlen(specdom); + if(n == 0 || (n > 0 && pathdom[n-1] == '.')) + return strcmp(pathdom+n, specdom); + return n; + } + } + return strcmp(pathdom, specdom); +} + +/* + * figure out action for this sender + */ +int +blocked(String *path) +{ + String *lpath; + int action; + + if(debug) + fprint(2, "blocked(%s)\n", s_to_c(path)); + + /* if the sender's IP address is blessed, ignore sender email address */ + if(trusted){ + if(debug) + fprint(2, "\ttrusted => trusted\n"); + return TRUSTED; + } + + /* if sender's IP address is blocked, ignore sender email address */ + if(hisaction != ACCEPT){ + if(debug) + fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction)); + return hisaction; + } + + /* convert to lower case */ + lpath = s_copy(s_to_c(path)); + s_tolower(lpath); + + /* classify */ + action = getaction(s_to_c(lpath), "account"); + if(debug) + fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action)); + s_free(lpath); + return action; +} + +/* + * get a canonicalized line: a string of null-terminated lower-case + * tokens with a two null bytes at the end. + */ +static char* +getline(Biobuf *bp) +{ + char c, *cp, *p, *q; + int n; + + static char *buf; + static int bufsize; + + for(;;){ + cp = Brdline(bp, '\n'); + if(cp == 0) + return 0; + n = Blinelen(bp); + cp[n-1] = 0; + if(buf == 0 || bufsize < n+1){ + bufsize += 512; + if(bufsize < n+1) + bufsize = n+1; + buf = realloc(buf, bufsize); + if(buf == 0) + break; + } + q = buf; + for (p = cp; *p; p++){ + c = *p; + if(c == '\\' && p[1]) /* we don't allow \<newline> */ + c = *++p; + else + if(c == '#') + break; + else + if(c == ' ' || c == '\t' || c == ',') + if(q == buf || q[-1] == 0) + continue; + else + c = 0; + *q++ = tolower(c); + } + if(q != buf){ + if(q[-1]) + *q++ = 0; + *q = 0; + break; + } + } + return buf; +} + +static int +isourdom(char *s) +{ + Link *l; + + if(strchr(s, '.') == nil) + return 1; + + for(l = ourdoms.first; l; l = l->next){ + if(dommatch(s, s_to_c(l->p)) == 0) + return 1; + } + return 0; +} + +int +forwarding(String *path) +{ + char *cp, *s; + String *lpath; + + if(debug) + fprint(2, "forwarding(%s)\n", s_to_c(path)); + + /* first check if they want loopback */ + lpath = s_copy(s_to_c(s_restart(path))); + if(nci->rsys && *nci->rsys){ + cp = s_to_c(lpath); + if(strncmp(cp, "[]!", 3) == 0){ +found: + s_append(path, "["); + s_append(path, nci->rsys); + s_append(path, "]!"); + s_append(path, cp+3); + s_terminate(path); + s_free(lpath); + return 0; + } + cp = strchr(cp,'!'); /* skip our domain and check next */ + if(cp++ && strncmp(cp, "[]!", 3) == 0) + goto found; + } + + /* if mail is from a trusted IP addr, allow it to forward */ + if(trusted) { + s_free(lpath); + return 0; + } + + /* sender is untrusted; ensure receiver is in one of our domains */ + 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){ + *cp = 0; + if(!isourdom(s)){ + s_free(lpath); + return 1; + } + } + s_free(lpath); + return 0; +} + +int +masquerade(String *path, char *him) +{ + char *cp, *s; + String *lpath; + int rv = 0; + + if(debug) + fprint(2, "masquerade(%s)\n", s_to_c(path)); + + if(trusted) + return 0; + if(path == nil) + return 0; + + lpath = s_copy(s_to_c(path)); + + /* sender is untrusted; ensure receiver is in one of our domains */ + for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */ + *cp = tolower(*cp); + s = s_to_c(lpath); + + /* scan first element of ! or last element of @ paths */ + if((cp = strchr(s, '!')) != nil){ + *cp = 0; + if(isourdom(s)) + rv = 1; + } else if((cp = strrchr(s, '@')) != nil){ + if(isourdom(cp+1)) + rv = 1; + } else { + if(isourdom(him)) + rv = 1; + } + + s_free(lpath); + return rv; +} + +/* this is a v4 only check */ +static int +cidrcheck(char *cp) +{ + char *p; + ulong a, m; + uchar addr[IPv4addrlen]; + uchar mask[IPv4addrlen]; + + if(v4peerip == 0) + return 0; + + /* parse a list of CIDR addresses comparing each to the peer IP addr */ + while(cp && *cp){ + v4parsecidr(addr, mask, cp); + a = nhgetl(addr); + m = nhgetl(mask); + /* + * if a mask isn't specified, we build a minimal mask + * instead of using the default mask for that net. in this + * case we never allow a class A mask (0xff000000). + */ + if(strchr(cp, '/') == 0){ + m = 0xff000000; + p = cp; + 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) + return 1; + cp += strlen(cp)+1; + } + return 0; +} + +int +isbadguy(void) +{ + Link *l; + + /* check if this IP address is banned */ + for(l = badguys.first; l; l = l->next) + if(cidrcheck(s_to_c(l->p))) + return 1; + + return 0; +} + +void +addbadguy(char *p) +{ + listadd(&badguys, s_copy(p)); +}; + +char* +dumpfile(char *sender) +{ + int i, fd; + ulong h; + static char buf[512]; + char *cp; + + if (sflag == 1){ + cp = ctime(time(0)); + cp[7] = 0; + if(cp[8] == ' ') + 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); + if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0) + return "/dev/null"; + h = 0; + while(*sender) + h = h*257 + *sender++; + for(i = 0; i < 50; i++){ + h += lrand(); + sprint(cp, "/%lud", h); + if(access(buf, 0) >= 0) + continue; + fd = syscreate(buf, ORDWR, 0666); + if(fd >= 0){ + if(debug) + fprint(2, "saving in %s\n", buf); + close(fd); + return buf; + } + } + } + return "/dev/null"; +} + +char *validator = "/mail/lib/validateaddress"; + +int +recipok(char *user) +{ + char *cp, *p, c; + char buf[512]; + int n; + Biobuf *bp; + int pid; + Waitmsg *w; + + if(shellchars(user)){ + syslog(0, "smtpd", "shellchars in user name"); + return 0; + } + + if(access(validator, AEXEC) == 0) + switch(pid = fork()) { + case -1: + break; + case 0: + execl(validator, "validateaddress", user, nil); + exits(0); + default: + while(w = wait()) { + if(w->pid != pid) + continue; + if(w->msg[0] != 0){ + /* + syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg); + */ + return 0; + } + break; + } + } + + snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB); + bp = sysopen(buf, "r", 0); + if(bp == 0) + return 1; + for(;;){ + cp = Brdline(bp, '\n'); + if(cp == 0) + break; + n = Blinelen(bp); + cp[n-1] = 0; + + while(*cp == ' ' || *cp == '\t') + cp++; + for(p = cp; c = *p; p++){ + if(c == '#') + break; + if(c == ' ' || c == '\t') + break; + } + if(p > cp){ + *p = 0; + if(cistrcmp(user, cp) == 0){ + syslog(0, "smtpd", "names.blocked blocks %s", user); + Bterm(bp); + return 0; + } + } + } + Bterm(bp); + return 1; +} + +/* + * a user can opt out of spam filtering by creating + * a file in his mail directory named 'nospamfiltering'. + */ +int +optoutofspamfilter(char *addr) +{ + char *p, *f; + int rv; + + p = strchr(addr, '!'); + if(p) + p++; + else + p = addr; + + + rv = 0; + f = smprint("/mail/box/%s/nospamfiltering", p); + if(f != nil){ + rv = access(f, 0)==0; + free(f); + } + + return rv; +} diff --git a/src/cmd/upas/smtp/y.tab.h b/src/cmd/upas/smtp/y.tab.h new file mode 100644 index 00000000..a31a0e67 --- /dev/null +++ b/src/cmd/upas/smtp/y.tab.h @@ -0,0 +1,25 @@ +#define WORD 57346 +#define DATE 57347 +#define RESENT_DATE 57348 +#define RETURN_PATH 57349 +#define FROM 57350 +#define SENDER 57351 +#define REPLY_TO 57352 +#define RESENT_FROM 57353 +#define RESENT_SENDER 57354 +#define RESENT_REPLY_TO 57355 +#define SUBJECT 57356 +#define TO 57357 +#define CC 57358 +#define BCC 57359 +#define RESENT_TO 57360 +#define RESENT_CC 57361 +#define RESENT_BCC 57362 +#define REMOTE 57363 +#define PRECEDENCE 57364 +#define MIMEVERSION 57365 +#define CONTENTTYPE 57366 +#define MESSAGEID 57367 +#define RECEIVED 57368 +#define MAILER 57369 +#define BADTOKEN 57370 |