|  | #include "common.h" | 
|  | #include "smtp.h" | 
|  | #include <ctype.h> | 
|  | #include <mp.h> | 
|  | #include <libsec.h> | 
|  | #include <auth.h> | 
|  | #include <ndb.h> | 
|  | #include <thread.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"); | 
|  | threadexitsall(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) | 
|  | threadexitsall(quitrv); | 
|  | threadexitsall(Retry); | 
|  | } | 
|  | if(strstr(msg, "closed pipe")){ | 
|  | fprint(2, "smtp timeout: connection closed to %s\n", farend); | 
|  | if(quitting){ | 
|  | syslog(0, "smtp.fail", "closed pipe to %s", farend); | 
|  | threadexitsall(quitrv); | 
|  | } | 
|  | threadexitsall(Retry); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void | 
|  | removenewline(char *p) | 
|  | { | 
|  | int n = strlen(p)-1; | 
|  |  | 
|  | if(n < 0) | 
|  | return; | 
|  | if(p[n] == '\n') | 
|  | p[n] = 0; | 
|  | } | 
|  |  | 
|  | int | 
|  | exitcode(char *s) | 
|  | { | 
|  | if(strstr(s, "Retry"))	/* known to runq */ | 
|  | return RetryCode; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | threadexitsall(0); | 
|  | } | 
|  |  | 
|  | /* mxdial uses its own timeout handler */ | 
|  | if((rv = connect(addr)) != 0) | 
|  | threadexitsall(rv); | 
|  |  | 
|  | /* 10 minutes to get through the initial handshake */ | 
|  | atnotify(timeout, 1); | 
|  | 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 != nil && strcmp(rv, Giveup) != 0) | 
|  | 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 != nil && strcmp(rv, Retry) == 0)) | 
|  | goto error; | 
|  |  | 
|  | if(ping){ | 
|  | quit(0); | 
|  | threadexitsall(0); | 
|  | } | 
|  |  | 
|  | rv = data(from, &bfile); | 
|  | if(rv != 0) | 
|  | goto error; | 
|  | quit(0); | 
|  | if(rcvrs == ok) | 
|  | threadexitsall(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]); | 
|  | } | 
|  | } | 
|  | threadexitsall(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); | 
|  | threadexitsall(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]; | 
|  |  | 
|  | return Giveup; | 
|  |  | 
|  | 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 role=client server=%q user=%q", ds.host, user); | 
|  | else | 
|  | p = auth_getuserpasswd(nil, | 
|  | "proto=pass service=smtp role=client 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(debug) | 
|  | Bflush(&berr); | 
|  | if(line == 0) | 
|  | return -1; | 
|  | if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2])) | 
|  | return -1; | 
|  | if(line[3] != '-') | 
|  | break; | 
|  | } | 
|  | 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'){ | 
|  | case '\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 *addr) | 
|  | { | 
|  | static char buf[256]; | 
|  | char *p, *q, *name, *sys; | 
|  | Ndbtuple *t; | 
|  | Ndb *db; | 
|  |  | 
|  | p = strchr(addr, '!'); | 
|  | if(p){ | 
|  | q = strchr(p+1, '!'); | 
|  | name = p+1; | 
|  | }else{ | 
|  | name = addr; | 
|  | q = nil; | 
|  | } | 
|  |  | 
|  | if(name[0] != '$') | 
|  | return addr; | 
|  | name++; | 
|  | if(q) | 
|  | *q = 0; | 
|  |  | 
|  | sys = sysname(); | 
|  | db = ndbopen(0); | 
|  | t = ndbipinfo(db, "sys", sys, &name, 1); | 
|  | if(t == nil){ | 
|  | ndbclose(db); | 
|  | if(q) | 
|  | *q = '!'; | 
|  | return addr; | 
|  | } | 
|  |  | 
|  | *(name-1) = 0; | 
|  | if(q) | 
|  | *q = '!'; | 
|  | else | 
|  | q = ""; | 
|  | snprint(buf, sizeof buf, "%s%s%s", addr, t->val, q); | 
|  | return buf; | 
|  | } |