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