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