Blob


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