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 != nil && strcmp(rv, Giveup) != 0)
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 != nil && strcmp(rv, Retry) == 0))
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 role=client server=%q user=%q", ds.host, user);
399 else
400 p = auth_getuserpasswd(nil,
401 "proto=pass service=smtp role=client 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 encrypted = 1;
471 Again:
472 if(ehlo)
473 dBprint("EHLO %s\r\n", me);
474 else
475 dBprint("HELO %s\r\n", me);
476 switch (getreply()) {
477 case 2:
478 break;
479 case 5:
480 if(ehlo){
481 ehlo = 0;
482 goto Again;
484 return Giveup;
485 default:
486 return Retry;
488 r = s_clone(reply);
489 if(r == nil)
490 return Retry; /* Out of memory or couldn't get string */
492 /* Invariant: every line has a newline, a result of getcrlf() */
493 for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){
494 *t = '\0';
495 for (t = s; *t != '\0'; t++)
496 *t = toupper(*t);
497 if(!encrypted && trysecure &&
498 (strcmp(s, "250-STARTTLS") == 0 ||
499 strcmp(s, "250 STARTTLS") == 0)){
500 s_free(r);
501 return(dotls(me));
503 if(tryauth && (encrypted || insecure) &&
504 (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
505 strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
506 ret = doauth(s + strlen("250 AUTH "));
507 s_free(r);
508 return ret;
511 s_free(r);
512 return 0;
515 /*
516 * report sender to remote
517 */
518 char *
519 mailfrom(char *from)
521 if(!returnable(from))
522 dBprint("MAIL FROM:<>\r\n");
523 else
524 if(strchr(from, '@'))
525 dBprint("MAIL FROM:<%s>\r\n", from);
526 else
527 dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
528 switch(getreply()){
529 case 2:
530 break;
531 case 5:
532 return Giveup;
533 default:
534 return Retry;
536 return 0;
539 /*
540 * report a recipient to remote
541 */
542 char *
543 rcptto(char *to)
545 String *s;
547 s = unescapespecial(bangtoat(to));
548 if(toline == 0)
549 toline = s_new();
550 else
551 s_append(toline, ", ");
552 s_append(toline, s_to_c(s));
553 if(strchr(s_to_c(s), '@'))
554 dBprint("RCPT TO:<%s>\r\n", s_to_c(s));
555 else {
556 s_append(toline, "@");
557 s_append(toline, ddomain);
558 dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain);
560 alarm(10*alarmscale);
561 switch(getreply()){
562 case 2:
563 break;
564 case 5:
565 return Giveup;
566 default:
567 return Retry;
569 return 0;
572 static char hex[] = "0123456789abcdef";
574 /*
575 * send the damn thing
576 */
577 char *
578 data(String *from, Biobuf *b)
580 char *buf, *cp;
581 int i, n, nbytes, bufsize, eof, r;
582 String *fromline;
583 char errmsg[Errlen];
584 char id[40];
586 /*
587 * input the header.
588 */
590 buf = malloc(1);
591 if(buf == 0){
592 s_append(s_restart(reply), "out of memory");
593 return Retry;
595 n = 0;
596 eof = 0;
597 for(;;){
598 cp = Brdline(b, '\n');
599 if(cp == nil){
600 eof = 1;
601 break;
603 nbytes = Blinelen(b);
604 buf = realloc(buf, n+nbytes+1);
605 if(buf == 0){
606 s_append(s_restart(reply), "out of memory");
607 return Retry;
609 strncpy(buf+n, cp, nbytes);
610 n += nbytes;
611 if(nbytes == 1) /* end of header */
612 break;
614 buf[n] = 0;
615 bufsize = n;
617 /*
618 * parse the header, turn all addresses into @ format
619 */
620 yyinit(buf, n);
621 yyparse();
623 /*
624 * print message observing '.' escapes and using \r\n for \n
625 */
626 alarm(20*alarmscale);
627 if(!filter){
628 dBprint("DATA\r\n");
629 switch(getreply()){
630 case 3:
631 break;
632 case 5:
633 free(buf);
634 return Giveup;
635 default:
636 free(buf);
637 return Retry;
640 /*
641 * send header. add a message-id, a sender, and a date if there
642 * isn't one
643 */
644 nbytes = 0;
645 fromline = convertheader(from);
646 uneaten = buf;
648 srand(truerand());
649 if(messageid == 0){
650 for(i=0; i<16; i++){
651 r = rand()&0xFF;
652 id[2*i] = hex[r&0xF];
653 id[2*i+1] = hex[(r>>4)&0xF];
655 id[2*i] = '\0';
656 nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
657 if(debug)
658 Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
661 if(originator==0){
662 nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
663 if(debug)
664 Bprint(&berr, "From: %s\r\n", s_to_c(fromline));
666 s_free(fromline);
668 if(destination == 0 && toline)
669 if(*s_to_c(toline) == '@'){ /* route addr */
670 nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline));
671 if(debug)
672 Bprint(&berr, "To: <%s>\r\n", s_to_c(toline));
673 } else {
674 nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline));
675 if(debug)
676 Bprint(&berr, "To: %s\r\n", s_to_c(toline));
679 if(date==0 && udate)
680 nbytes += printdate(udate);
681 if (usys)
682 uneaten = usys->end + 1;
683 nbytes += printheader();
684 if (*uneaten != '\n')
685 putcrnl("\n", 1);
687 /*
688 * send body
689 */
691 putcrnl(uneaten, buf+n - uneaten);
692 nbytes += buf+n - uneaten;
693 if(eof == 0){
694 for(;;){
695 n = Bread(b, buf, bufsize);
696 if(n < 0){
697 rerrstr(errmsg, sizeof(errmsg));
698 s_append(s_restart(reply), errmsg);
699 free(buf);
700 return Retry;
702 if(n == 0)
703 break;
704 alarm(10*alarmscale);
705 putcrnl(buf, n);
706 nbytes += n;
709 free(buf);
710 if(!filter){
711 if(last != '\n')
712 dBprint("\r\n.\r\n");
713 else
714 dBprint(".\r\n");
715 alarm(10*alarmscale);
716 switch(getreply()){
717 case 2:
718 break;
719 case 5:
720 return Giveup;
721 default:
722 return Retry;
724 syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from),
725 nbytes, s_to_c(toline));/**/
727 return 0;
730 /*
731 * we're leaving
732 */
733 void
734 quit(char *rv)
736 /* 60 minutes to quit */
737 quitting = 1;
738 quitrv = rv;
739 alarm(60*alarmscale);
740 dBprint("QUIT\r\n");
741 getreply();
742 Bterm(&bout);
743 Bterm(&bfile);
746 /*
747 * read a reply into a string, return the reply code
748 */
749 int
750 getreply(void)
752 char *line;
753 int rv;
755 reply = s_reset(reply);
756 for(;;){
757 line = getcrnl(reply);
758 if(debug)
759 Bflush(&berr);
760 if(line == 0)
761 return -1;
762 if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
763 return -1;
764 if(line[3] != '-')
765 break;
767 rv = atoi(line)/100;
768 return rv;
770 void
771 addhostdom(String *buf, char *host)
773 s_append(buf, "@");
774 s_append(buf, host);
777 /*
778 * Convert from `bang' to `source routing' format.
780 * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o
781 */
782 String *
783 bangtoat(char *addr)
785 String *buf;
786 register int i;
787 int j, d;
788 char *field[128];
790 /* parse the '!' format address */
791 buf = s_new();
792 for(i = 0; addr; i++){
793 field[i] = addr;
794 addr = strchr(addr, '!');
795 if(addr)
796 *addr++ = 0;
798 if (i==1) {
799 s_append(buf, field[0]);
800 return buf;
803 /*
804 * count leading domain fields (non-domains don't count)
805 */
806 for(d = 0; d<i-1; d++)
807 if(strchr(field[d], '.')==0)
808 break;
809 /*
810 * if there are more than 1 leading domain elements,
811 * put them in as source routing
812 */
813 if(d > 1){
814 addhostdom(buf, field[0]);
815 for(j=1; j<d-1; j++){
816 s_append(buf, ",");
817 s_append(buf, "@");
818 s_append(buf, field[j]);
820 s_append(buf, ":");
823 /*
824 * throw in the non-domain elements separated by '!'s
825 */
826 s_append(buf, field[d]);
827 for(j=d+1; j<=i-1; j++) {
828 s_append(buf, "!");
829 s_append(buf, field[j]);
831 if(d)
832 addhostdom(buf, field[d-1]);
833 return buf;
836 /*
837 * convert header addresses to @ format.
838 * if the address is a source address, and a domain is specified,
839 * make sure it falls in the domain.
840 */
841 String*
842 convertheader(String *from)
844 Field *f;
845 Node *p, *lastp;
846 String *a;
848 if(!returnable(s_to_c(from))){
849 from = s_new();
850 s_append(from, "Postmaster");
851 addhostdom(from, hostdomain);
852 } else
853 if(strchr(s_to_c(from), '@') == 0){
854 a = username(from);
855 if(a) {
856 s_append(a, " <");
857 s_append(a, s_to_c(from));
858 addhostdom(a, hostdomain);
859 s_append(a, ">");
860 from = a;
861 } else {
862 from = s_copy(s_to_c(from));
863 addhostdom(from, hostdomain);
865 } else
866 from = s_copy(s_to_c(from));
867 for(f = firstfield; f; f = f->next){
868 lastp = 0;
869 for(p = f->node; p; lastp = p, p = p->next){
870 if(!p->addr)
871 continue;
872 a = bangtoat(s_to_c(p->s));
873 s_free(p->s);
874 if(strchr(s_to_c(a), '@') == 0)
875 addhostdom(a, hostdomain);
876 else if(*s_to_c(a) == '@')
877 a = fixrouteaddr(a, p->next, lastp);
878 p->s = a;
881 return from;
883 /*
884 * ensure route addr has brackets around it
885 */
886 String*
887 fixrouteaddr(String *raddr, Node *next, Node *last)
889 String *a;
891 if(last && last->c == '<' && next && next->c == '>')
892 return raddr; /* properly formed already */
894 a = s_new();
895 s_append(a, "<");
896 s_append(a, s_to_c(raddr));
897 s_append(a, ">");
898 s_free(raddr);
899 return a;
902 /*
903 * print out the parsed header
904 */
905 int
906 printheader(void)
908 int n, len;
909 Field *f;
910 Node *p;
911 char *cp;
912 char c[1];
914 n = 0;
915 for(f = firstfield; f; f = f->next){
916 for(p = f->node; p; p = p->next){
917 if(p->s)
918 n += dBprint("%s", s_to_c(p->s));
919 else {
920 c[0] = p->c;
921 putcrnl(c, 1);
922 n++;
924 if(p->white){
925 cp = s_to_c(p->white);
926 len = strlen(cp);
927 putcrnl(cp, len);
928 n += len;
930 uneaten = p->end;
932 putcrnl("\n", 1);
933 n++;
934 uneaten++; /* skip newline */
936 return n;
939 /*
940 * add a domain onto an name, return the new name
941 */
942 char *
943 domainify(char *name, char *domain)
945 static String *s;
946 char *p;
948 if(domain==0 || strchr(name, '.')!=0)
949 return name;
951 s = s_reset(s);
952 s_append(s, name);
953 p = strchr(domain, '.');
954 if(p == 0){
955 s_append(s, ".");
956 p = domain;
958 s_append(s, p);
959 return s_to_c(s);
962 /*
963 * print message observing '.' escapes and using \r\n for \n
964 */
965 void
966 putcrnl(char *cp, int n)
968 int c;
970 for(; n; n--, cp++){
971 c = *cp;
972 if(c == '\n')
973 dBputc('\r');
974 else if(c == '.' && last=='\n')
975 dBputc('.');
976 dBputc(c);
977 last = c;
981 /*
982 * Get a line including a crnl into a string. Convert crnl into nl.
983 */
984 char *
985 getcrnl(String *s)
987 int c;
988 int count;
990 count = 0;
991 for(;;){
992 c = Bgetc(&bin);
993 if(debug)
994 Bputc(&berr, c);
995 switch(c){
996 case -1:
997 s_append(s, "connection closed unexpectedly by remote system");
998 s_terminate(s);
999 return 0;
1000 case '\r':
1001 c = Bgetc(&bin);
1002 if(c == '\n'){
1003 case '\n':
1004 s_putc(s, c);
1005 if(debug)
1006 Bputc(&berr, c);
1007 count++;
1008 s_terminate(s);
1009 return s->ptr - count;
1011 Bungetc(&bin);
1012 s_putc(s, '\r');
1013 if(debug)
1014 Bputc(&berr, '\r');
1015 count++;
1016 break;
1017 default:
1018 s_putc(s, c);
1019 count++;
1020 break;
1023 return 0;
1027 * print out a parsed date
1029 int
1030 printdate(Node *p)
1032 int n, sep = 0;
1034 n = dBprint("Date: %s,", s_to_c(p->s));
1035 for(p = p->next; p; p = p->next){
1036 if(p->s){
1037 if(sep == 0) {
1038 dBputc(' ');
1039 n++;
1041 if (p->next)
1042 n += dBprint("%s", s_to_c(p->s));
1043 else
1044 n += dBprint("%s", rewritezone(s_to_c(p->s)));
1045 sep = 0;
1046 } else {
1047 dBputc(p->c);
1048 n++;
1049 sep = 1;
1052 n += dBprint("\r\n");
1053 return n;
1056 char *
1057 rewritezone(char *z)
1059 int mindiff;
1060 char s;
1061 Tm *tm;
1062 static char x[7];
1064 tm = localtime(time(0));
1065 mindiff = tm->tzoff/60;
1067 /* if not in my timezone, don't change anything */
1068 if(strcmp(tm->zone, z) != 0)
1069 return z;
1071 if(mindiff < 0){
1072 s = '-';
1073 mindiff = -mindiff;
1074 } else
1075 s = '+';
1077 sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
1078 return x;
1082 * stolen from libc/port/print.c
1084 #define SIZE 4096
1085 int
1086 dBprint(char *fmt, ...)
1088 char buf[SIZE], *out;
1089 va_list arg;
1090 int n;
1092 va_start(arg, fmt);
1093 out = vseprint(buf, buf+SIZE, fmt, arg);
1094 va_end(arg);
1095 if(debug){
1096 Bwrite(&berr, buf, (long)(out-buf));
1097 Bflush(&berr);
1099 n = Bwrite(&bout, buf, (long)(out-buf));
1100 Bflush(&bout);
1101 return n;
1104 int
1105 dBputc(int x)
1107 if(debug)
1108 Bputc(&berr, x);
1109 return Bputc(&bout, x);
1112 char*
1113 expand_addr(char *addr)
1115 static char buf[256];
1116 char *p, *q, *name, *sys;
1117 Ndbtuple *t;
1118 Ndb *db;
1120 p = strchr(addr, '!');
1121 if(p){
1122 q = strchr(p+1, '!');
1123 name = p+1;
1124 }else{
1125 name = addr;
1126 q = nil;
1129 if(name[0] != '$')
1130 return addr;
1131 name++;
1132 if(q)
1133 *q = 0;
1135 sys = sysname();
1136 db = ndbopen(0);
1137 t = ndbipinfo(db, "sys", sys, &name, 1);
1138 if(t == nil){
1139 ndbclose(db);
1140 if(q)
1141 *q = '!';
1142 return addr;
1145 *(name-1) = 0;
1146 if(q)
1147 *q = '!';
1148 else
1149 q = "";
1150 snprint(buf, sizeof buf, "%s%s%s", addr, t->val, q);
1151 return buf;