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