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