10 static char* connect(char*);
11 static char* dotls(char*);
12 static char* doauth(char*);
13 char* hello(char*, int);
14 char* mailfrom(char*);
16 char* data(String*, Biobuf*);
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*);
27 char *rewritezone(char *);
28 int dBprint(char*, ...);
30 String* fixrouteaddr(String*, Node*, Node*);
31 char* expand_addr(char* a);
35 #define Retry "Retry, Temporary Failure"
36 #define Giveup "Permanent Failure"
38 int debug; /* true if we're debugging */
39 String *reply; /* last reply */
42 int last = 'n'; /* last character sent by putcrnl() */
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 */
62 fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n");
63 threadexitsall(Giveup);
67 timeout(void *x, char *msg)
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);
74 threadexitsall(quitrv);
75 threadexitsall(Retry);
77 if(strstr(msg, "closed pipe")){
78 fprint(2, "smtp timeout: connection closed to %s\n", farend);
80 syslog(0, "smtp.fail", "closed pipe to %s", farend);
81 threadexitsall(quitrv);
83 threadexitsall(Retry);
89 removenewline(char *p)
102 if(strstr(s, "Retry")) /* known to runq */
108 threadmain(int argc, char **argv)
110 char hellodomain[256];
120 alarmscale = 60*1000; /* minutes */
122 errs = malloc(argc*sizeof(char*));
146 alarmscale = 10*1000; /* tens of seconds */
160 Binit(&berr, 2, OWRITE);
161 Binit(&bfile, 0, OREAD);
164 * get domain and add to host name
166 if(*argv && **argv=='.') {
170 domain = domainname_read();
172 host = sysname_read();
173 strcpy(hostdomain, domainify(host, domain));
174 strcpy(hellodomain, domainify(sysname_read(), domain));
177 * get destination address
181 addr = *argv++; argc--;
182 /* expand $smtp if necessary */
183 addr = expand_addr(addr);
187 * get sender's machine.
188 * get sender in internet style. domainify if necessary.
192 sender = unescapespecial(s_copy(*argv++));
194 fromm = s_clone(sender);
195 rv = strrchr(s_to_c(fromm), '!');
200 from = bangtoat(s_to_c(sender));
206 Binit(&bout, 1, OWRITE);
207 rv = data(from, &bfile);
213 /* mxdial uses its own timeout handler */
214 if((rv = connect(addr)) != 0)
217 /* 10 minutes to get through the initial handshake */
218 atnotify(timeout, 1);
219 alarm(10*alarmscale);
220 if((rv = hello(hellodomain, 0)) != 0)
222 alarm(10*alarmscale);
223 if((rv = mailfrom(s_to_c(from))) != 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 */
234 errs[rcvrs] = strdup(s_to_c(reply));
235 removenewline(errs[rcvrs]);
243 /* if no ok rcvrs or worst error is retry, give up */
244 if(ok == 0 || rv == Retry)
252 rv = data(from, &bfile);
260 * here when some but not all rcvrs failed
262 fprint(2, "%s connect to %s:\n", thedate(), addr);
263 for(i = 0; i < rcvrs; 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);
272 * here when all rcvrs failed
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));
286 * connect to the remote host
294 fd = mxdial(net, ddomain, gdomain);
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"))
307 Binit(&bin, fd, OREAD);
309 Binit(&bout, fd, OWRITE);
313 static char smtpthumbs[] = "/sys/lib/tls/smtp";
314 static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude";
317 * exchange names with remote host, attempt to
318 * enable encryption and optionally authenticate.
319 * not fatal if we can't.
325 Thumbprint *goodcerts;
328 uchar hash[SHA1dlen];
332 c = mallocz(sizeof(*c), 1); /* Note: not freed on success */
336 dBprint("STARTTLS\r\n");
340 fd = tlsClient(Bfildes(&bout), c);
342 syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
345 goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs);
346 if (goodcerts == nil) {
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 */
359 h = malloc(2*sizeof hash + 1);
361 enc16(h, 2*sizeof hash + 1, hash, sizeof hash);
362 /* print("x509 sha1=%s", h); */
364 "remote cert. has bad thumbprint: x509 sha1=%s server=%q",
368 return Giveup; /* how to recover? TLS is started */
370 freeThumbprints(goodcerts);
375 * set up bin & bout to use the TLS fd, i/o upon which generates
376 * i/o on the original, underlying fd.
378 Binit(&bin, fd, OREAD);
380 Binit(&bout, fd, OWRITE);
382 syslog(0, "smtp", "started TLS to %q", ddomain);
383 return(hello(me, 1));
387 doauth(char *methods)
394 dial_string_parse(ddomain, &ds);
397 p = auth_getuserpasswd(nil,
398 "proto=pass service=smtp server=%q user=%q", ds.host, user);
400 p = auth_getuserpasswd(nil,
401 "proto=pass service=smtp server=%q", ds.host);
405 if (strstr(methods, "LOGIN")){
406 dBprint("AUTH LOGIN\r\n");
411 base64 = malloc(2*n);
413 return Retry; /* Out of memory */
414 enc64(base64, 2*n, (uchar *)p->user, n);
415 dBprint("%s\r\n", base64);
419 n = strlen(p->passwd);
420 base64 = malloc(2*n);
422 return Retry; /* Out of memory */
423 enc64(base64, 2*n, (uchar *)p->passwd, n);
424 dBprint("%s\r\n", base64);
431 if (strstr(methods, "PLAIN")){
432 n = strlen(p->user) + strlen(p->passwd) + 3;
434 base64 = malloc(2 * n);
435 if (buf == nil || base64 == nil) {
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);
442 dBprint("AUTH PLAIN %s\r\n", base64);
448 return "No supported AUTH method";
453 hello(char *me, int encrypted)
472 dBprint("EHLO %s\r\n", me);
474 dBprint("HELO %s\r\n", me);
475 switch (getreply()) {
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){
494 for (t = s; *t != '\0'; t++)
496 if(!encrypted && trysecure &&
497 (strcmp(s, "250-STARTTLS") == 0 ||
498 strcmp(s, "250 STARTTLS") == 0)){
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 "));
515 * report sender to remote
520 if(!returnable(from))
521 dBprint("MAIL FROM:<>\r\n");
523 if(strchr(from, '@'))
524 dBprint("MAIL FROM:<%s>\r\n", from);
526 dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
539 * report a recipient to remote
546 s = unescapespecial(bangtoat(to));
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));
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);
571 static char hex[] = "0123456789abcdef";
574 * send the damn thing
577 data(String *from, Biobuf *b)
580 int i, n, nbytes, bufsize, eof, r;
591 s_append(s_restart(reply), "out of memory");
597 cp = Brdline(b, '\n');
602 nbytes = Blinelen(b);
603 buf = realloc(buf, n+nbytes+1);
605 s_append(s_restart(reply), "out of memory");
608 strncpy(buf+n, cp, nbytes);
610 if(nbytes == 1) /* end of header */
617 * parse the header, turn all addresses into @ format
623 * print message observing '.' escapes and using \r\n for \n
625 alarm(20*alarmscale);
640 * send header. add a message-id, a sender, and a date if there
644 fromline = convertheader(from);
651 id[2*i] = hex[r&0xF];
652 id[2*i+1] = hex[(r>>4)&0xF];
655 nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
657 Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
661 nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
663 Bprint(&berr, "From: %s\r\n", s_to_c(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));
671 Bprint(&berr, "To: <%s>\r\n", s_to_c(toline));
673 nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline));
675 Bprint(&berr, "To: %s\r\n", s_to_c(toline));
679 nbytes += printdate(udate);
681 uneaten = usys->end + 1;
682 nbytes += printheader();
683 if (*uneaten != '\n')
690 putcrnl(uneaten, buf+n - uneaten);
691 nbytes += buf+n - uneaten;
694 n = Bread(b, buf, bufsize);
696 rerrstr(errmsg, sizeof(errmsg));
697 s_append(s_restart(reply), errmsg);
703 alarm(10*alarmscale);
711 dBprint("\r\n.\r\n");
714 alarm(10*alarmscale);
723 syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from),
724 nbytes, s_to_c(toline));/**/
735 /* 60 minutes to quit */
738 alarm(60*alarmscale);
746 * read a reply into a string, return the reply code
754 reply = s_reset(reply);
756 line = getcrnl(reply);
761 if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
770 addhostdom(String *buf, char *host)
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
789 /* parse the '!' format address */
791 for(i = 0; addr; i++){
793 addr = strchr(addr, '!');
798 s_append(buf, field[0]);
803 * count leading domain fields (non-domains don't count)
805 for(d = 0; d<i-1; d++)
806 if(strchr(field[d], '.')==0)
809 * if there are more than 1 leading domain elements,
810 * put them in as source routing
813 addhostdom(buf, field[0]);
814 for(j=1; j<d-1; j++){
817 s_append(buf, field[j]);
823 * throw in the non-domain elements separated by '!'s
825 s_append(buf, field[d]);
826 for(j=d+1; j<=i-1; j++) {
828 s_append(buf, field[j]);
831 addhostdom(buf, field[d-1]);
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.
841 convertheader(String *from)
847 if(!returnable(s_to_c(from))){
849 s_append(from, "Postmaster");
850 addhostdom(from, hostdomain);
852 if(strchr(s_to_c(from), '@') == 0){
856 s_append(a, s_to_c(from));
857 addhostdom(a, hostdomain);
861 from = s_copy(s_to_c(from));
862 addhostdom(from, hostdomain);
865 from = s_copy(s_to_c(from));
866 for(f = firstfield; f; f = f->next){
868 for(p = f->node; p; lastp = p, p = p->next){
871 a = bangtoat(s_to_c(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);
883 * ensure route addr has brackets around it
886 fixrouteaddr(String *raddr, Node *next, Node *last)
890 if(last && last->c == '<' && next && next->c == '>')
891 return raddr; /* properly formed already */
895 s_append(a, s_to_c(raddr));
902 * print out the parsed header
914 for(f = firstfield; f; f = f->next){
915 for(p = f->node; p; p = p->next){
917 n += dBprint("%s", s_to_c(p->s));
924 cp = s_to_c(p->white);
933 uneaten++; /* skip newline */
939 * add a domain onto an name, return the new name
942 domainify(char *name, char *domain)
947 if(domain==0 || strchr(name, '.')!=0)
952 p = strchr(domain, '.');
962 * print message observing '.' escapes and using \r\n for \n
965 putcrnl(char *cp, int n)
973 else if(c == '.' && last=='\n')
981 * Get a line including a crnl into a string. Convert crnl into nl.
996 s_append(s, "connection closed unexpectedly by remote system");
1008 return s->ptr - count;
1026 * print out a parsed date
1033 n = dBprint("Date: %s,", s_to_c(p->s));
1034 for(p = p->next; p; p = p->next){
1041 n += dBprint("%s", s_to_c(p->s));
1043 n += dBprint("%s", rewritezone(s_to_c(p->s)));
1051 n += dBprint("\r\n");
1056 rewritezone(char *z)
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)
1076 sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
1081 * stolen from libc/port/print.c
1085 dBprint(char *fmt, ...)
1087 char buf[SIZE], *out;
1092 out = vseprint(buf, buf+SIZE, fmt, arg);
1095 Bwrite(&berr, buf, (long)(out-buf));
1098 n = Bwrite(&bout, buf, (long)(out-buf));
1108 return Bputc(&bout, x);
1112 expand_addr(char *addr)
1114 static char buf[256];
1115 char *p, *q, *name, *sys;
1119 p = strchr(addr, '!');
1121 q = strchr(p+1, '!');
1136 t = ndbipinfo(db, "sys", sys, &name, 1);
1149 snprint(buf, sizeof buf, "%s%s%s", addr, t->val, q);