|  | #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 <thread.h> | 
|  | #include "../smtp/rfc822.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); | 
|  | threadexitsall(status); | 
|  | } | 
|  |  | 
|  | void | 
|  | threadmain(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': | 
|  | fprint(2, "tls is not available\n"); | 
|  | threadexitsall("no tls"); | 
|  | 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"); | 
|  | threadexitsall("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.db", 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(); | 
|  | threadexitsall(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"); | 
|  | threadexitsall("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); | 
|  | threadexitsall(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; | 
|  | static char *validate; | 
|  |  | 
|  | 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(validate == nil) | 
|  | validate = unsharp("#9/mail/lib/validatesender"); | 
|  | if(access(validate, 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(validate, "validatesender", | 
|  | "-n", nci->root, senddom, user, nil); | 
|  | threadexitsall("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 a message appeared on stderr, despite good status, | 
|  | * log it.  this can happen if rewrite.in contains a bad | 
|  | * r.e., for example. | 
|  | */ | 
|  | if(*s_to_c(err)) | 
|  | syslog(0, "smtpd", | 
|  | "%s returned good status, but said: %s", | 
|  | s_to_c(mailer), s_to_c(err)); | 
|  |  | 
|  | if(filterstate == BLOCKED) | 
|  | reply("554 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"); | 
|  | threadexitsall("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); | 
|  | threadexitsall("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); | 
|  | } |