plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

smtp.c (21018B)


      1 #include "common.h"
      2 #include "smtp.h"
      3 #include <ctype.h>
      4 #include <mp.h>
      5 #include <libsec.h>
      6 #include <auth.h>
      7 #include <ndb.h>
      8 #include <thread.h>
      9 
     10 static	char*	connect(char*);
     11 static	char*	dotls(char*);
     12 static	char*	doauth(char*);
     13 char*	hello(char*, int);
     14 char*	mailfrom(char*);
     15 char*	rcptto(char*);
     16 char*	data(String*, Biobuf*);
     17 void	quit(char*);
     18 int	getreply(void);
     19 void	addhostdom(String*, char*);
     20 String*	bangtoat(char*);
     21 String*	convertheader(String*);
     22 int	printheader(void);
     23 char*	domainify(char*, char*);
     24 void	putcrnl(char*, int);
     25 char*	getcrnl(String*);
     26 int	printdate(Node*);
     27 char	*rewritezone(char *);
     28 int	dBprint(char*, ...);
     29 int	dBputc(int);
     30 String*	fixrouteaddr(String*, Node*, Node*);
     31 char* expand_addr(char* a);
     32 int	ping;
     33 int	insecure;
     34 
     35 #define Retry	"Retry, Temporary Failure"
     36 #define Giveup	"Permanent Failure"
     37 
     38 int	debug;		/* true if we're debugging */
     39 String	*reply;		/* last reply */
     40 String	*toline;
     41 int	alarmscale;
     42 int	last = 'n';	/* last character sent by putcrnl() */
     43 int	filter;
     44 int	trysecure;	/* Try to use TLS if the other side supports it */
     45 int	tryauth;	/* Try to authenticate, if supported */
     46 int	quitting;	/* when error occurs in quit */
     47 char	*quitrv;	/* deferred return value when in quit */
     48 char	ddomain[1024];	/* domain name of destination machine */
     49 char	*gdomain;	/* domain name of gateway */
     50 char	*uneaten;	/* first character after rfc822 headers */
     51 char	*farend;	/* system we are trying to send to */
     52 char	*user;		/* user we are authenticating as, if authenticating */
     53 char	hostdomain[256];
     54 Biobuf	bin;
     55 Biobuf	bout;
     56 Biobuf	berr;
     57 Biobuf	bfile;
     58 
     59 void
     60 usage(void)
     61 {
     62 	fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n");
     63 	threadexitsall(Giveup);
     64 }
     65 
     66 int
     67 timeout(void *x, char *msg)
     68 {
     69 	USED(x);
     70 	syslog(0, "smtp.fail", "interrupt: %s: %s", farend,  msg);
     71 	if(strstr(msg, "alarm")){
     72 		fprint(2, "smtp timeout: connection to %s timed out\n", farend);
     73 		if(quitting)
     74 			threadexitsall(quitrv);
     75 		threadexitsall(Retry);
     76 	}
     77 	if(strstr(msg, "closed pipe")){
     78 		fprint(2, "smtp timeout: connection closed to %s\n", farend);
     79 		if(quitting){
     80 			syslog(0, "smtp.fail", "closed pipe to %s", farend);
     81 			threadexitsall(quitrv);
     82 		}
     83 		threadexitsall(Retry);
     84 	}
     85 	return 0;
     86 }
     87 
     88 void
     89 removenewline(char *p)
     90 {
     91 	int n = strlen(p)-1;
     92 
     93 	if(n < 0)
     94 		return;
     95 	if(p[n] == '\n')
     96 		p[n] = 0;
     97 }
     98 
     99 int
    100 exitcode(char *s)
    101 {
    102 	if(strstr(s, "Retry"))	/* known to runq */
    103 		return RetryCode;
    104 	return 1;
    105 }
    106 
    107 void
    108 threadmain(int argc, char **argv)
    109 {
    110 	char hellodomain[256];
    111 	char *host, *domain;
    112 	String *from;
    113 	String *fromm;
    114 	String *sender;
    115 	char *addr;
    116 	char *rv, *trv;
    117 	int i, ok, rcvrs;
    118 	char **errs;
    119 
    120 	alarmscale = 60*1000;	/* minutes */
    121 	quotefmtinstall();
    122 	errs = malloc(argc*sizeof(char*));
    123 	reply = s_new();
    124 	host = 0;
    125 	ARGBEGIN{
    126 	case 'a':
    127 		tryauth = 1;
    128 		trysecure = 1;
    129 		break;
    130 	case 'f':
    131 		filter = 1;
    132 		break;
    133 	case 'd':
    134 		debug = 1;
    135 		break;
    136 	case 'g':
    137 		gdomain = ARGF();
    138 		break;
    139 	case 'h':
    140 		host = ARGF();
    141 		break;
    142 	case 'i':
    143 		insecure = 1;
    144 		break;
    145 	case 'p':
    146 		alarmscale = 10*1000;	/* tens of seconds */
    147 		ping = 1;
    148 		break;
    149 	case 's':
    150 		trysecure = 1;
    151 		break;
    152 	case 'u':
    153 		user = ARGF();
    154 		break;
    155 	default:
    156 		usage();
    157 		break;
    158 	}ARGEND;
    159 
    160 	Binit(&berr, 2, OWRITE);
    161 	Binit(&bfile, 0, OREAD);
    162 
    163 	/*
    164 	 *  get domain and add to host name
    165 	 */
    166 	if(*argv && **argv=='.') {
    167 		domain = *argv;
    168 		argv++; argc--;
    169 	} else
    170 		domain = domainname_read();
    171 	if(host == 0)
    172 		host = sysname_read();
    173 	strcpy(hostdomain, domainify(host, domain));
    174 	strcpy(hellodomain, domainify(sysname_read(), domain));
    175 
    176 	/*
    177 	 *  get destination address
    178 	 */
    179 	if(*argv == 0)
    180 		usage();
    181 	addr = *argv++; argc--;
    182 	/* expand $smtp if necessary */
    183 	addr = expand_addr(addr);
    184 	farend = addr;
    185 
    186 	/*
    187 	 *  get sender's machine.
    188 	 *  get sender in internet style.  domainify if necessary.
    189 	 */
    190 	if(*argv == 0)
    191 		usage();
    192 	sender = unescapespecial(s_copy(*argv++));
    193 	argc--;
    194 	fromm = s_clone(sender);
    195 	rv = strrchr(s_to_c(fromm), '!');
    196 	if(rv)
    197 		*rv = 0;
    198 	else
    199 		*s_to_c(fromm) = 0;
    200 	from = bangtoat(s_to_c(sender));
    201 
    202 	/*
    203 	 *  send the mail
    204 	 */
    205 	if(filter){
    206 		Binit(&bout, 1, OWRITE);
    207 		rv = data(from, &bfile);
    208 		if(rv != 0)
    209 			goto error;
    210 		threadexitsall(0);
    211 	}
    212 
    213 	/* mxdial uses its own timeout handler */
    214 	if((rv = connect(addr)) != 0)
    215 		threadexitsall(rv);
    216 
    217 	/* 10 minutes to get through the initial handshake */
    218 	atnotify(timeout, 1);
    219 	alarm(10*alarmscale);
    220 	if((rv = hello(hellodomain, 0)) != 0)
    221 		goto error;
    222 	alarm(10*alarmscale);
    223 	if((rv = mailfrom(s_to_c(from))) != 0)
    224 		goto error;
    225 
    226 	ok = 0;
    227 	rcvrs = 0;
    228 	/* if any rcvrs are ok, we try to send the message */
    229 	for(i = 0; i < argc; i++){
    230 		if((trv = rcptto(argv[i])) != 0){
    231 			/* remember worst error */
    232 			if(rv != nil && strcmp(rv, Giveup) != 0)
    233 				rv = trv;
    234 			errs[rcvrs] = strdup(s_to_c(reply));
    235 			removenewline(errs[rcvrs]);
    236 		} else {
    237 			ok++;
    238 			errs[rcvrs] = 0;
    239 		}
    240 		rcvrs++;
    241 	}
    242 
    243 	/* if no ok rcvrs or worst error is retry, give up */
    244 	if(ok == 0 || (rv != nil && strcmp(rv, Retry) == 0))
    245 		goto error;
    246 
    247 	if(ping){
    248 		quit(0);
    249 		threadexitsall(0);
    250 	}
    251 
    252 	rv = data(from, &bfile);
    253 	if(rv != 0)
    254 		goto error;
    255 	quit(0);
    256 	if(rcvrs == ok)
    257 		threadexitsall(0);
    258 
    259 	/*
    260 	 *  here when some but not all rcvrs failed
    261 	 */
    262 	fprint(2, "%s connect to %s:\n", thedate(), addr);
    263 	for(i = 0; i < rcvrs; i++){
    264 		if(errs[i]){
    265 			syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]);
    266 			fprint(2, "  mail to %s failed: %s", argv[i], errs[i]);
    267 		}
    268 	}
    269 	threadexitsall(Giveup);
    270 
    271 	/*
    272 	 *  here when all rcvrs failed
    273 	 */
    274 error:
    275 	removenewline(s_to_c(reply));
    276 	syslog(0, "smtp.fail", "%s to %s failed: %s",
    277 		ping ? "ping" : "delivery",
    278 		addr, s_to_c(reply));
    279 	fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply));
    280 	if(!filter)
    281 		quit(rv);
    282 	threadexitsall(rv);
    283 }
    284 
    285 /*
    286  *  connect to the remote host
    287  */
    288 static char *
    289 connect(char* net)
    290 {
    291 	char buf[256];
    292 	int fd;
    293 
    294 	fd = mxdial(net, ddomain, gdomain);
    295 
    296 	if(fd < 0){
    297 		rerrstr(buf, sizeof(buf));
    298 		Bprint(&berr, "smtp: %s (%s)\n", buf, net);
    299 		syslog(0, "smtp.fail", "%s (%s)", buf, net);
    300 		if(strstr(buf, "illegal")
    301 		|| strstr(buf, "unknown")
    302 		|| strstr(buf, "can't translate"))
    303 			return Giveup;
    304 		else
    305 			return Retry;
    306 	}
    307 	Binit(&bin, fd, OREAD);
    308 	fd = dup(fd, -1);
    309 	Binit(&bout, fd, OWRITE);
    310 	return 0;
    311 }
    312 
    313 static char smtpthumbs[] =	"/sys/lib/tls/smtp";
    314 static char smtpexclthumbs[] =	"/sys/lib/tls/smtp.exclude";
    315 
    316 /*
    317  *  exchange names with remote host, attempt to
    318  *  enable encryption and optionally authenticate.
    319  *  not fatal if we can't.
    320  */
    321 static char *
    322 dotls(char *me)
    323 {
    324 	TLSconn *c;
    325 	Thumbprint *goodcerts;
    326 	char *h;
    327 	int fd;
    328 	uchar hash[SHA1dlen];
    329 
    330 	return Giveup;
    331 
    332 	c = mallocz(sizeof(*c), 1);	/* Note: not freed on success */
    333 	if (c == nil)
    334 		return Giveup;
    335 
    336 	dBprint("STARTTLS\r\n");
    337 	if (getreply() != 2)
    338 		return Giveup;
    339 
    340 	fd = tlsClient(Bfildes(&bout), c);
    341 	if (fd < 0) {
    342 		syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
    343 		return Giveup;
    344 	}
    345 	goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs);
    346 	if (goodcerts == nil) {
    347 		free(c);
    348 		close(fd);
    349 		syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
    350 		return Giveup;		/* how to recover? TLS is started */
    351 	}
    352 
    353 	/* compute sha1 hash of remote's certificate, see if we know it */
    354 	sha1(c->cert, c->certlen, hash, nil);
    355 	if (!okThumbprint(hash, goodcerts)) {
    356 		/* TODO? if not excluded, add hash to thumb list */
    357 		free(c);
    358 		close(fd);
    359 		h = malloc(2*sizeof hash + 1);
    360 		if (h != nil) {
    361 			enc16(h, 2*sizeof hash + 1, hash, sizeof hash);
    362 			/* print("x509 sha1=%s", h); */
    363 			syslog(0, "smtp",
    364 		"remote cert. has bad thumbprint: x509 sha1=%s server=%q",
    365 				h, ddomain);
    366 			free(h);
    367 		}
    368 		return Giveup;		/* how to recover? TLS is started */
    369 	}
    370 	freeThumbprints(goodcerts);
    371 	Bterm(&bin);
    372 	Bterm(&bout);
    373 
    374 	/*
    375 	 * set up bin & bout to use the TLS fd, i/o upon which generates
    376 	 * i/o on the original, underlying fd.
    377 	 */
    378 	Binit(&bin, fd, OREAD);
    379 	fd = dup(fd, -1);
    380 	Binit(&bout, fd, OWRITE);
    381 
    382 	syslog(0, "smtp", "started TLS to %q", ddomain);
    383 	return(hello(me, 1));
    384 }
    385 
    386 static char *
    387 doauth(char *methods)
    388 {
    389 	char *buf, *base64;
    390 	int n;
    391 	DS ds;
    392 	UserPasswd *p;
    393 
    394 	dial_string_parse(ddomain, &ds);
    395 
    396 	if(user != nil)
    397 		p = auth_getuserpasswd(nil,
    398 	  	  "proto=pass service=smtp role=client server=%q user=%q", ds.host, user);
    399 	else
    400 		p = auth_getuserpasswd(nil,
    401 	  	  "proto=pass service=smtp role=client server=%q", ds.host);
    402 	if (p == nil)
    403 		return Giveup;
    404 
    405 	if (strstr(methods, "LOGIN")){
    406 		dBprint("AUTH LOGIN\r\n");
    407 		if (getreply() != 3)
    408 			return Retry;
    409 
    410 		n = strlen(p->user);
    411 		base64 = malloc(2*n);
    412 		if (base64 == nil)
    413 			return Retry;	/* Out of memory */
    414 		enc64(base64, 2*n, (uchar *)p->user, n);
    415 		dBprint("%s\r\n", base64);
    416 		if (getreply() != 3)
    417 			return Retry;
    418 
    419 		n = strlen(p->passwd);
    420 		base64 = malloc(2*n);
    421 		if (base64 == nil)
    422 			return Retry;	/* Out of memory */
    423 		enc64(base64, 2*n, (uchar *)p->passwd, n);
    424 		dBprint("%s\r\n", base64);
    425 		if (getreply() != 2)
    426 			return Retry;
    427 
    428 		free(base64);
    429 	}
    430 	else
    431 	if (strstr(methods, "PLAIN")){
    432 		n = strlen(p->user) + strlen(p->passwd) + 3;
    433 		buf = malloc(n);
    434 		base64 = malloc(2 * n);
    435 		if (buf == nil || base64 == nil) {
    436 			free(buf);
    437 			return Retry;	/* Out of memory */
    438 		}
    439 		snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd);
    440 		enc64(base64, 2 * n, (uchar *)buf, n - 1);
    441 		free(buf);
    442 		dBprint("AUTH PLAIN %s\r\n", base64);
    443 		free(base64);
    444 		if (getreply() != 2)
    445 			return Retry;
    446 	}
    447 	else
    448 		return "No supported AUTH method";
    449 	return(0);
    450 }
    451 
    452 char *
    453 hello(char *me, int encrypted)
    454 {
    455 	int ehlo;
    456 	String *r;
    457 	char *ret, *s, *t;
    458 
    459 	if (!encrypted)
    460 		switch(getreply()){
    461 		case 2:
    462 			break;
    463 		case 5:
    464 			return Giveup;
    465 		default:
    466 			return Retry;
    467 		}
    468 
    469 	ehlo = 1;
    470 	encrypted = 1;
    471   Again:
    472 	if(ehlo)
    473 		dBprint("EHLO %s\r\n", me);
    474 	else
    475 		dBprint("HELO %s\r\n", me);
    476 	switch (getreply()) {
    477 	case 2:
    478 		break;
    479 	case 5:
    480 		if(ehlo){
    481 			ehlo = 0;
    482 			goto Again;
    483 		}
    484 		return Giveup;
    485 	default:
    486 		return Retry;
    487 	}
    488 	r = s_clone(reply);
    489 	if(r == nil)
    490 		return Retry;	/* Out of memory or couldn't get string */
    491 
    492 	/* Invariant: every line has a newline, a result of getcrlf() */
    493 	for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){
    494 		*t = '\0';
    495 		for (t = s; *t != '\0'; t++)
    496 			*t = toupper(*t);
    497 		if(!encrypted && trysecure &&
    498 		    (strcmp(s, "250-STARTTLS") == 0 ||
    499 		     strcmp(s, "250 STARTTLS") == 0)){
    500 			s_free(r);
    501 			return(dotls(me));
    502 		}
    503 		if(tryauth && (encrypted || insecure) &&
    504 		    (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
    505 		     strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
    506 			ret = doauth(s + strlen("250 AUTH "));
    507 			s_free(r);
    508 			return ret;
    509 		}
    510 	}
    511 	s_free(r);
    512 	return 0;
    513 }
    514 
    515 /*
    516  *  report sender to remote
    517  */
    518 char *
    519 mailfrom(char *from)
    520 {
    521 	if(!returnable(from))
    522 		dBprint("MAIL FROM:<>\r\n");
    523 	else
    524 	if(strchr(from, '@'))
    525 		dBprint("MAIL FROM:<%s>\r\n", from);
    526 	else
    527 		dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
    528 	switch(getreply()){
    529 	case 2:
    530 		break;
    531 	case 5:
    532 		return Giveup;
    533 	default:
    534 		return Retry;
    535 	}
    536 	return 0;
    537 }
    538 
    539 /*
    540  *  report a recipient to remote
    541  */
    542 char *
    543 rcptto(char *to)
    544 {
    545 	String *s;
    546 
    547 	s = unescapespecial(bangtoat(to));
    548 	if(toline == 0)
    549 		toline = s_new();
    550 	else
    551 		s_append(toline, ", ");
    552 	s_append(toline, s_to_c(s));
    553 	if(strchr(s_to_c(s), '@'))
    554 		dBprint("RCPT TO:<%s>\r\n", s_to_c(s));
    555 	else {
    556 		s_append(toline, "@");
    557 		s_append(toline, ddomain);
    558 		dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain);
    559 	}
    560 	alarm(10*alarmscale);
    561 	switch(getreply()){
    562 	case 2:
    563 		break;
    564 	case 5:
    565 		return Giveup;
    566 	default:
    567 		return Retry;
    568 	}
    569 	return 0;
    570 }
    571 
    572 static char hex[] = "0123456789abcdef";
    573 
    574 /*
    575  *  send the damn thing
    576  */
    577 char *
    578 data(String *from, Biobuf *b)
    579 {
    580 	char *buf, *cp;
    581 	int i, n, nbytes, bufsize, eof, r;
    582 	String *fromline;
    583 	char errmsg[Errlen];
    584 	char id[40];
    585 
    586 	/*
    587 	 *  input the header.
    588 	 */
    589 
    590 	buf = malloc(1);
    591 	if(buf == 0){
    592 		s_append(s_restart(reply), "out of memory");
    593 		return Retry;
    594 	}
    595 	n = 0;
    596 	eof = 0;
    597 	for(;;){
    598 		cp = Brdline(b, '\n');
    599 		if(cp == nil){
    600 			eof = 1;
    601 			break;
    602 		}
    603 		nbytes = Blinelen(b);
    604 		buf = realloc(buf, n+nbytes+1);
    605 		if(buf == 0){
    606 			s_append(s_restart(reply), "out of memory");
    607 			return Retry;
    608 		}
    609 		strncpy(buf+n, cp, nbytes);
    610 		n += nbytes;
    611 		if(nbytes == 1)		/* end of header */
    612 			break;
    613 	}
    614 	buf[n] = 0;
    615 	bufsize = n;
    616 
    617 	/*
    618 	 *  parse the header, turn all addresses into @ format
    619 	 */
    620 	yyinit(buf, n);
    621 	yyparse();
    622 
    623 	/*
    624 	 *  print message observing '.' escapes and using \r\n for \n
    625 	 */
    626 	alarm(20*alarmscale);
    627 	if(!filter){
    628 		dBprint("DATA\r\n");
    629 		switch(getreply()){
    630 		case 3:
    631 			break;
    632 		case 5:
    633 			free(buf);
    634 			return Giveup;
    635 		default:
    636 			free(buf);
    637 			return Retry;
    638 		}
    639 	}
    640 	/*
    641 	 *  send header.  add a message-id, a sender, and a date if there
    642 	 *  isn't one
    643 	 */
    644 	nbytes = 0;
    645 	fromline = convertheader(from);
    646 	uneaten = buf;
    647 
    648 	srand(truerand());
    649 	if(messageid == 0){
    650 		for(i=0; i<16; i++){
    651 			r = rand()&0xFF;
    652 			id[2*i] = hex[r&0xF];
    653 			id[2*i+1] = hex[(r>>4)&0xF];
    654 		}
    655 		id[2*i] = '\0';
    656 		nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
    657 		if(debug)
    658 			Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
    659 	}
    660 
    661 	if(originator==0){
    662 		nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
    663 		if(debug)
    664 			Bprint(&berr, "From: %s\r\n", s_to_c(fromline));
    665 	}
    666 	s_free(fromline);
    667 
    668 	if(destination == 0 && toline)
    669 		if(*s_to_c(toline) == '@'){	/* route addr */
    670 			nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline));
    671 			if(debug)
    672 				Bprint(&berr, "To: <%s>\r\n", s_to_c(toline));
    673 		} else {
    674 			nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline));
    675 			if(debug)
    676 				Bprint(&berr, "To: %s\r\n", s_to_c(toline));
    677 		}
    678 
    679 	if(date==0 && udate)
    680 		nbytes += printdate(udate);
    681 	if (usys)
    682 		uneaten = usys->end + 1;
    683 	nbytes += printheader();
    684 	if (*uneaten != '\n')
    685 		putcrnl("\n", 1);
    686 
    687 	/*
    688 	 *  send body
    689 	 */
    690 
    691 	putcrnl(uneaten, buf+n - uneaten);
    692 	nbytes += buf+n - uneaten;
    693 	if(eof == 0){
    694 		for(;;){
    695 			n = Bread(b, buf, bufsize);
    696 			if(n < 0){
    697 				rerrstr(errmsg, sizeof(errmsg));
    698 				s_append(s_restart(reply), errmsg);
    699 				free(buf);
    700 				return Retry;
    701 			}
    702 			if(n == 0)
    703 				break;
    704 			alarm(10*alarmscale);
    705 			putcrnl(buf, n);
    706 			nbytes += n;
    707 		}
    708 	}
    709 	free(buf);
    710 	if(!filter){
    711 		if(last != '\n')
    712 			dBprint("\r\n.\r\n");
    713 		else
    714 			dBprint(".\r\n");
    715 		alarm(10*alarmscale);
    716 		switch(getreply()){
    717 		case 2:
    718 			break;
    719 		case 5:
    720 			return Giveup;
    721 		default:
    722 			return Retry;
    723 		}
    724 		syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from),
    725 				nbytes, s_to_c(toline));/**/
    726 	}
    727 	return 0;
    728 }
    729 
    730 /*
    731  *  we're leaving
    732  */
    733 void
    734 quit(char *rv)
    735 {
    736 		/* 60 minutes to quit */
    737 	quitting = 1;
    738 	quitrv = rv;
    739 	alarm(60*alarmscale);
    740 	dBprint("QUIT\r\n");
    741 	getreply();
    742 	Bterm(&bout);
    743 	Bterm(&bfile);
    744 }
    745 
    746 /*
    747  *  read a reply into a string, return the reply code
    748  */
    749 int
    750 getreply(void)
    751 {
    752 	char *line;
    753 	int rv;
    754 
    755 	reply = s_reset(reply);
    756 	for(;;){
    757 		line = getcrnl(reply);
    758 		if(debug)
    759 			Bflush(&berr);
    760 		if(line == 0)
    761 			return -1;
    762 		if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
    763 			return -1;
    764 		if(line[3] != '-')
    765 			break;
    766 	}
    767 	rv = atoi(line)/100;
    768 	return rv;
    769 }
    770 void
    771 addhostdom(String *buf, char *host)
    772 {
    773 	s_append(buf, "@");
    774 	s_append(buf, host);
    775 }
    776 
    777 /*
    778  *	Convert from `bang' to `source routing' format.
    779  *
    780  *	   a.x.y!b.p.o!c!d ->	@a.x.y:c!d@b.p.o
    781  */
    782 String *
    783 bangtoat(char *addr)
    784 {
    785 	String *buf;
    786 	register int i;
    787 	int j, d;
    788 	char *field[128];
    789 
    790 	/* parse the '!' format address */
    791 	buf = s_new();
    792 	for(i = 0; addr; i++){
    793 		field[i] = addr;
    794 		addr = strchr(addr, '!');
    795 		if(addr)
    796 			*addr++ = 0;
    797 	}
    798 	if (i==1) {
    799 		s_append(buf, field[0]);
    800 		return buf;
    801 	}
    802 
    803 	/*
    804 	 *  count leading domain fields (non-domains don't count)
    805 	 */
    806 	for(d = 0; d<i-1; d++)
    807 		if(strchr(field[d], '.')==0)
    808 			break;
    809 	/*
    810 	 *  if there are more than 1 leading domain elements,
    811 	 *  put them in as source routing
    812 	 */
    813 	if(d > 1){
    814 		addhostdom(buf, field[0]);
    815 		for(j=1; j<d-1; j++){
    816 			s_append(buf, ",");
    817 			s_append(buf, "@");
    818 			s_append(buf, field[j]);
    819 		}
    820 		s_append(buf, ":");
    821 	}
    822 
    823 	/*
    824 	 *  throw in the non-domain elements separated by '!'s
    825 	 */
    826 	s_append(buf, field[d]);
    827 	for(j=d+1; j<=i-1; j++) {
    828 		s_append(buf, "!");
    829 		s_append(buf, field[j]);
    830 	}
    831 	if(d)
    832 		addhostdom(buf, field[d-1]);
    833 	return buf;
    834 }
    835 
    836 /*
    837  *  convert header addresses to @ format.
    838  *  if the address is a source address, and a domain is specified,
    839  *  make sure it falls in the domain.
    840  */
    841 String*
    842 convertheader(String *from)
    843 {
    844 	Field *f;
    845 	Node *p, *lastp;
    846 	String *a;
    847 
    848 	if(!returnable(s_to_c(from))){
    849 		from = s_new();
    850 		s_append(from, "Postmaster");
    851 		addhostdom(from, hostdomain);
    852 	} else
    853 	if(strchr(s_to_c(from), '@') == 0){
    854 		a = username(from);
    855 		if(a) {
    856 			s_append(a, " <");
    857 			s_append(a, s_to_c(from));
    858 			addhostdom(a, hostdomain);
    859 			s_append(a, ">");
    860 			from = a;
    861 		} else {
    862 			from = s_copy(s_to_c(from));
    863 			addhostdom(from, hostdomain);
    864 		}
    865 	} else
    866 		from = s_copy(s_to_c(from));
    867 	for(f = firstfield; f; f = f->next){
    868 		lastp = 0;
    869 		for(p = f->node; p; lastp = p, p = p->next){
    870 			if(!p->addr)
    871 				continue;
    872 			a = bangtoat(s_to_c(p->s));
    873 			s_free(p->s);
    874 			if(strchr(s_to_c(a), '@') == 0)
    875 				addhostdom(a, hostdomain);
    876 			else if(*s_to_c(a) == '@')
    877 				a = fixrouteaddr(a, p->next, lastp);
    878 			p->s = a;
    879 		}
    880 	}
    881 	return from;
    882 }
    883 /*
    884  *	ensure route addr has brackets around it
    885  */
    886 String*
    887 fixrouteaddr(String *raddr, Node *next, Node *last)
    888 {
    889 	String *a;
    890 
    891 	if(last && last->c == '<' && next && next->c == '>')
    892 		return raddr;			/* properly formed already */
    893 
    894 	a = s_new();
    895 	s_append(a, "<");
    896 	s_append(a, s_to_c(raddr));
    897 	s_append(a, ">");
    898 	s_free(raddr);
    899 	return a;
    900 }
    901 
    902 /*
    903  *  print out the parsed header
    904  */
    905 int
    906 printheader(void)
    907 {
    908 	int n, len;
    909 	Field *f;
    910 	Node *p;
    911 	char *cp;
    912 	char c[1];
    913 
    914 	n = 0;
    915 	for(f = firstfield; f; f = f->next){
    916 		for(p = f->node; p; p = p->next){
    917 			if(p->s)
    918 				n += dBprint("%s", s_to_c(p->s));
    919 			else {
    920 				c[0] = p->c;
    921 				putcrnl(c, 1);
    922 				n++;
    923 			}
    924 			if(p->white){
    925 				cp = s_to_c(p->white);
    926 				len = strlen(cp);
    927 				putcrnl(cp, len);
    928 				n += len;
    929 			}
    930 			uneaten = p->end;
    931 		}
    932 		putcrnl("\n", 1);
    933 		n++;
    934 		uneaten++;		/* skip newline */
    935 	}
    936 	return n;
    937 }
    938 
    939 /*
    940  *  add a domain onto an name, return the new name
    941  */
    942 char *
    943 domainify(char *name, char *domain)
    944 {
    945 	static String *s;
    946 	char *p;
    947 
    948 	if(domain==0 || strchr(name, '.')!=0)
    949 		return name;
    950 
    951 	s = s_reset(s);
    952 	s_append(s, name);
    953 	p = strchr(domain, '.');
    954 	if(p == 0){
    955 		s_append(s, ".");
    956 		p = domain;
    957 	}
    958 	s_append(s, p);
    959 	return s_to_c(s);
    960 }
    961 
    962 /*
    963  *  print message observing '.' escapes and using \r\n for \n
    964  */
    965 void
    966 putcrnl(char *cp, int n)
    967 {
    968 	int c;
    969 
    970 	for(; n; n--, cp++){
    971 		c = *cp;
    972 		if(c == '\n')
    973 			dBputc('\r');
    974 		else if(c == '.' && last=='\n')
    975 			dBputc('.');
    976 		dBputc(c);
    977 		last = c;
    978 	}
    979 }
    980 
    981 /*
    982  *  Get a line including a crnl into a string.  Convert crnl into nl.
    983  */
    984 char *
    985 getcrnl(String *s)
    986 {
    987 	int c;
    988 	int count;
    989 
    990 	count = 0;
    991 	for(;;){
    992 		c = Bgetc(&bin);
    993 		if(debug)
    994 			Bputc(&berr, c);
    995 		switch(c){
    996 		case -1:
    997 			s_append(s, "connection closed unexpectedly by remote system");
    998 			s_terminate(s);
    999 			return 0;
   1000 		case '\r':
   1001 			c = Bgetc(&bin);
   1002 			if(c == '\n'){
   1003 		case '\n':
   1004 				s_putc(s, c);
   1005 				if(debug)
   1006 					Bputc(&berr, c);
   1007 				count++;
   1008 				s_terminate(s);
   1009 				return s->ptr - count;
   1010 			}
   1011 			Bungetc(&bin);
   1012 			s_putc(s, '\r');
   1013 			if(debug)
   1014 				Bputc(&berr, '\r');
   1015 			count++;
   1016 			break;
   1017 		default:
   1018 			s_putc(s, c);
   1019 			count++;
   1020 			break;
   1021 		}
   1022 	}
   1023 	return 0;
   1024 }
   1025 
   1026 /*
   1027  *  print out a parsed date
   1028  */
   1029 int
   1030 printdate(Node *p)
   1031 {
   1032 	int n, sep = 0;
   1033 
   1034 	n = dBprint("Date: %s,", s_to_c(p->s));
   1035 	for(p = p->next; p; p = p->next){
   1036 		if(p->s){
   1037 			if(sep == 0) {
   1038 				dBputc(' ');
   1039 				n++;
   1040 			}
   1041 			if (p->next)
   1042 				n += dBprint("%s", s_to_c(p->s));
   1043 			else
   1044 				n += dBprint("%s", rewritezone(s_to_c(p->s)));
   1045 			sep = 0;
   1046 		} else {
   1047 			dBputc(p->c);
   1048 			n++;
   1049 			sep = 1;
   1050 		}
   1051 	}
   1052 	n += dBprint("\r\n");
   1053 	return n;
   1054 }
   1055 
   1056 char *
   1057 rewritezone(char *z)
   1058 {
   1059 	int mindiff;
   1060 	char s;
   1061 	Tm *tm;
   1062 	static char x[7];
   1063 
   1064 	tm = localtime(time(0));
   1065 	mindiff = tm->tzoff/60;
   1066 
   1067 	/* if not in my timezone, don't change anything */
   1068 	if(strcmp(tm->zone, z) != 0)
   1069 		return z;
   1070 
   1071 	if(mindiff < 0){
   1072 		s = '-';
   1073 		mindiff = -mindiff;
   1074 	} else
   1075 		s = '+';
   1076 
   1077 	sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
   1078 	return x;
   1079 }
   1080 
   1081 /*
   1082  *  stolen from libc/port/print.c
   1083  */
   1084 #define	SIZE	4096
   1085 int
   1086 dBprint(char *fmt, ...)
   1087 {
   1088 	char buf[SIZE], *out;
   1089 	va_list arg;
   1090 	int n;
   1091 
   1092 	va_start(arg, fmt);
   1093 	out = vseprint(buf, buf+SIZE, fmt, arg);
   1094 	va_end(arg);
   1095 	if(debug){
   1096 		Bwrite(&berr, buf, (long)(out-buf));
   1097 		Bflush(&berr);
   1098 	}
   1099 	n = Bwrite(&bout, buf, (long)(out-buf));
   1100 	Bflush(&bout);
   1101 	return n;
   1102 }
   1103 
   1104 int
   1105 dBputc(int x)
   1106 {
   1107 	if(debug)
   1108 		Bputc(&berr, x);
   1109 	return Bputc(&bout, x);
   1110 }
   1111 
   1112 char*
   1113 expand_addr(char *addr)
   1114 {
   1115 	static char buf[256];
   1116 	char *p, *q, *name, *sys;
   1117 	Ndbtuple *t;
   1118 	Ndb *db;
   1119 
   1120 	p = strchr(addr, '!');
   1121 	if(p){
   1122 		q = strchr(p+1, '!');
   1123 		name = p+1;
   1124 	}else{
   1125 		name = addr;
   1126 		q = nil;
   1127 	}
   1128 
   1129 	if(name[0] != '$')
   1130 		return addr;
   1131 	name++;
   1132 	if(q)
   1133 		*q = 0;
   1134 
   1135 	sys = sysname();
   1136 	db = ndbopen(0);
   1137 	t = ndbipinfo(db, "sys", sys, &name, 1);
   1138 	if(t == nil){
   1139 		ndbclose(db);
   1140 		if(q)
   1141 			*q = '!';
   1142 		return addr;
   1143 	}
   1144 
   1145 	*(name-1) = 0;
   1146 	if(q)
   1147 		*q = '!';
   1148 	else
   1149 		q = "";
   1150 	snprint(buf, sizeof buf, "%s%s%s", addr, t->val, q);
   1151 	return buf;
   1152 }