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