8 static char* connect(char*, Mx*);
9 static char* wraptls(void);
10 static char* dotls(char*);
11 static char* doauth(char*);
13 void addhostdom(String*, char*);
14 String* bangtoat(char*);
15 String* convertheader(String*);
16 int dBprint(char*, ...);
17 #pragma varargck argpos dBprint 1
19 char* data(String*, Biobuf*, Mx*);
20 char* domainify(char*, char*);
21 String* fixrouteaddr(String*, Node*, Node*);
22 char* getcrnl(String*);
24 char* hello(char*, int);
25 char* mailfrom(char*);
27 int printheader(void);
28 void putcrnl(char*, int);
31 char *rewritezone(char *);
33 char Retry[] = "Retry, Temporary Failure";
34 char Giveup[] = "Permanent Failure";
36 String *reply; /* last reply */
41 int debug; /* true if we're debugging */
44 int last = 'n'; /* last character sent by putcrnl() */
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 */
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 */
57 Mx *tmmx; /* global for timeout */
69 mx = va_arg(fmt->args, Mx*);
70 if(mx == nil || mx->host[0] == 0)
71 return fmtstrcpy(fmt, "");
73 return fmtprint(fmt, "(%s:%s)", mx->host, mx->ip);
75 #pragma varargck type "D" Mx*
88 fprint(2, "usage: smtp [-aAdfipst] [-b busted-mx] [-g gw] [-h host] "
89 "[-u user] [.domain] net!host[!service] sender rcpt-list\n");
94 timeout(void *, char *msg)
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);
103 if(strstr(msg, "closed pipe")){
104 fprint(2, "smtp timeout: connection closed to %s\n", farend);
106 syslog(0, "smtp.fail", "%s closed pipe to %s %D", deliverytype(), farend, tmmx);
109 /* call _exits() to prevent Bio from trying to flush closed pipe */
116 removenewline(char *p)
118 int n = strlen(p) - 1;
127 main(int argc, char **argv)
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;
136 alarmscale = 60*1000; /* minutes */
139 mailfmtinstall(); /* 2047 encoding */
140 fmtinstall('D', Dfmt);
141 fmtinstall('[', encodefmt);
142 fmtinstall('H', encodefmt);
143 errs = malloc(argc*sizeof(char*));
153 case 'A': /* autistic: won't talk to us until we talk (Verizon) */
157 if(bustedmx >= Maxbustedmx)
158 sysfatal("more than %d busted mxs given", Maxbustedmx);
159 bustedmxs[bustedmx++] = EARGF(usage());
168 gdomain = EARGF(usage());
171 host = EARGF(usage());
177 alarmscale = 10*1000; /* tens of seconds */
188 user = EARGF(usage());
195 Binit(&berr, 2, OWRITE);
196 Binit(&bfile, 0, OREAD);
199 * get domain and add to host name
201 if(*argv && **argv=='.'){
205 domain = domainname_read();
207 host = sysname_read();
210 strcpy(hostdomain, domainify(host, domain));
211 strcpy(hellodomain, domainify(sysname_read(), domain));
214 * get destination address
218 addr = *argv++; argc--;
220 if((rv = strrchr(addr, '!')) && rv[1] == '['){
221 syslog(0, "smtp.fail", "%s to %s failed: illegal address",
222 deliverytype(), addr);
227 * get sender's machine.
228 * get sender in internet style. domainify if necessary.
232 sender = unescapespecial(s_copy(*argv++));
234 fromm = s_clone(sender);
235 rv = strrchr(s_to_c(fromm), '!');
240 from = bangtoat(s_to_c(sender));
247 USED(phase); /* just in case */
249 Binit(&bout, 1, OWRITE);
250 rv = data(from, &bfile, nil);
258 /* mxdial uses its own timeout handler */
259 if((rv = connect(addr, &mx)) != 0)
263 /* 10 minutes to get through the initial handshake */
264 atnotify(timeout, 1);
265 alarm(10*alarmscale);
266 if((rv = hello(hellodomain, 0)) != 0){
270 alarm(10*alarmscale);
271 if((rv = mailfrom(s_to_c(from))) != 0){
277 /* if any rcvrs are ok, we try to send the message */
279 for(i = 0; i < argc; i++){
280 if((trv = rcptto(argv[i])) != 0){
281 /* remember worst error */
284 errs[rcvrs] = strdup(s_to_c(reply));
285 removenewline(errs[rcvrs]);
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)
304 rv = data(from, &bfile, &mx);
312 * here when some but not all rcvrs failed
314 fprint(2, "%Ï„ connect to %s: %D %s:\n", thedate(&tm), addr, &mx, phase);
315 for(i = 0; i < rcvrs; 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]);
325 * here when all rcvrs failed
329 removenewline(s_to_c(reply));
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]);
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));
347 * connect to the remote host
350 connect(char* net, Mx *mx)
355 fd = mxdial(net, ddomain, gdomain, mx);
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"))
368 Binit(&bin, fd, OREAD);
370 Binit(&bout, fd, OWRITE);
374 static char smtpthumbs[] = "/sys/lib/tls/smtp";
375 static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude";
378 tracetls(char *fmt, ...)
383 Bvprint(&berr, fmt, ap);
394 Thumbprint *goodcerts;
400 c = mallocz(sizeof(*c), 1);
407 fd = tlsClient(Bfildes(&bout), c);
409 syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
413 Binit(&bout, fd, OWRITE);
414 fd = dup(fd, Bfildes(&bin));
416 Binit(&bin, fd, OREAD);
418 goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs, "x509");
419 if (goodcerts == nil) {
420 syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
423 if (!okCertificate(c->cert, c->certlen, goodcerts)) {
424 syslog(0, "smtp", "cert for %s not recognized: %r", ddomain);
427 syslog(0, "smtp", "started TLS to %q", ddomain);
430 freeThumbprints(goodcerts);
438 * exchange names with remote host, attempt to
439 * enable encryption and optionally authenticate.
440 * not fatal if we can't.
447 dBprint("STARTTLS\r\n");
455 return(hello(me, 1));
461 char *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
464 dBprint("AUTH CRAM-MD5\r\n");
467 p = s_to_c(reply) + 4;
468 l = dec64((uchar*)ch, sizeof ch, p, strlen(p));
470 n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
471 "proto=cram role=client server=%q user=%q",
476 syslog(0, "smtp.fail", "failed to get challenge response: %r");
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);
486 dBprint("%s\r\n", ebuf);
493 doauth(char *methods)
495 char buf[1024], *err;
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",
509 syslog(0, "smtp.fail", "failed to get userpasswd: %r");
513 if (strstr(methods, "LOGIN")){
514 dBprint("AUTH LOGIN\r\n");
518 dBprint("%.*[\r\n", (int)strlen(p->user), p->user);
522 dBprint("%.*[\r\n", (int)strlen(p->passwd), p->passwd);
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));
536 err = "No supported AUTH method";
538 memset(p->user, 0, strlen(p->user));
539 memset(p->passwd, 0, strlen(p->passwd));
545 hello(char *me, int encrypted)
553 if((ret = wraptls()) != nil)
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
565 getreply(); /* consume the smtp greeting */
566 /* next reply will be response to noop */
581 dBprint("EHLO %s\r\n", me);
583 dBprint("HELO %s\r\n", me);
598 return Retry; /* Out of memory or couldn't get string */
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){
603 if(!encrypted && trysecure &&
604 (cistrcmp(s, "250-STARTTLS") == 0 ||
605 cistrcmp(s, "250 STARTTLS") == 0)){
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 "));
622 * report sender to remote
627 if(!returnable(from))
628 dBprint("MAIL FROM:<>\r\n");
629 else if(strchr(from, '@'))
630 dBprint("MAIL FROM:<%s>\r\n", from);
632 dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
644 * report a recipient to remote
651 s = unescapespecial(bangtoat(to));
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));
660 s_append(toline, "@");
661 s_append(toline, ddomain);
662 dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain);
664 alarm(10*alarmscale);
677 * send the damn thing
680 data(String *from, Biobuf *b, Mx *mx)
682 char *buf, *cp, errmsg[ERRMAX];
683 int n, nbytes, bufsize, eof;
692 s_append(s_restart(reply), "out of memory");
698 cp = Brdline(b, '\n');
703 nbytes = Blinelen(b);
704 buf = realloc(buf, n + nbytes + 1);
706 s_append(s_restart(reply), "out of memory");
709 strncpy(buf + n, cp, nbytes);
711 if(nbytes == 1) /* end of header */
718 * parse the header, turn all addresses into @ format
724 * print message observing '.' escapes and using \r\n for \n
726 alarm(20*alarmscale);
741 * send header. add a message-id, a sender, and a date if there
745 fromline = convertheader(from);
751 genrandom(id, sizeof(id));
752 nbytes += dBprint("Message-ID: <%.*H@%s>\r\n",
753 sizeof(id), id, hostdomain);
757 nbytes += dBprint("From: %s\r\n", s_to_c(fromline));
760 if(destination == 0 && toline){
761 if(*s_to_c(toline) == '@') /* route addr */
762 nbytes += dBprint("To: <%s>\r\n", s_to_c(toline));
764 nbytes += dBprint("To: %s\r\n", s_to_c(toline));
767 if(date == 0 && udate)
768 nbytes += printdate(udate);
770 uneaten = usys->end + 1;
771 nbytes += printheader();
779 putcrnl(uneaten, buf + n - uneaten);
780 nbytes += buf + n - uneaten;
783 n = Bread(b, buf, bufsize);
785 rerrstr(errmsg, sizeof(errmsg));
786 s_append(s_restart(reply), errmsg);
792 alarm(10*alarmscale);
800 dBprint("\r\n.\r\n");
803 alarm(10*alarmscale);
812 syslog(0, "smtp", "%s sent %d bytes to %s %D", s_to_c(from),
813 nbytes, s_to_c(toline), mx);
824 /* 60 minutes to quit */
827 alarm(60*alarmscale);
835 * read a reply into a string, return the reply code
843 reply = s_reset(reply);
845 line = getcrnl(reply);
850 if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2]))
861 addhostdom(String *buf, char *host)
868 * Convert from `bang' to `source routing' format.
870 * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o
879 /* parse the '!' format address */
881 for(i = 0; addr; i++){
883 addr = strchr(addr, '!');
888 s_append(buf, field[0]);
893 * count leading domain fields (non-domains don't count)
895 for(d = 0; d < i - 1; d++)
896 if(strchr(field[d], '.') == 0)
899 * if there are more than 1 leading domain elements,
900 * put them in as source routing
903 addhostdom(buf, field[0]);
904 for(j = 1; j< d - 1; j++){
907 s_append(buf, field[j]);
913 * throw in the non-domain elements separated by '!'s
915 s_append(buf, field[d]);
916 for(j = d + 1; j <= i - 1; j++){
918 s_append(buf, field[j]);
921 addhostdom(buf, field[d-1]);
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.
931 convertheader(String *from)
938 if(!returnable(s_to_c(from))){
940 s_append(from, "Postmaster");
941 addhostdom(from, hostdomain);
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);
949 s_append(a, s_to_c(from));
950 addhostdom(a, hostdomain);
954 from = s_copy(s_to_c(from));
955 addhostdom(from, hostdomain);
958 from = s_copy(s_to_c(from));
959 for(f = firstfield; f; f = f->next){
961 for(p = f->node; p; lastp = p, p = p->next){
964 a = bangtoat(s_to_c(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);
976 * ensure route addr has brackets around it
979 fixrouteaddr(String *raddr, Node *next, Node *last)
983 if(last && last->c == '<' && next && next->c == '>')
984 return raddr; /* properly formed already */
988 s_append(a, s_to_c(raddr));
995 * print out the parsed header
1006 for(f = firstfield; f; f = f->next){
1007 for(p = f->node; p; p = p->next){
1009 n += dBprint("%s", s_to_c(p->s));
1016 cp = s_to_c(p->white);
1025 uneaten++; /* skip newline */
1031 * add a domain onto an name, return the new name
1034 domainify(char *name, char *domain)
1039 if(domain == 0 || strchr(name, '.') != 0)
1044 p = strchr(domain, '.');
1054 * print message observing '.' escapes and using \r\n for \n
1057 putcrnl(char *cp, int n)
1061 for(; n; n--, cp++){
1065 else if(c == '.' && last=='\n')
1073 * Get a line including a crnl into a string. Convert crnl into nl.
1087 s_append(s, "connection closed unexpectedly by remote system");
1099 return s->ptr - count;
1116 * print out a parsed date
1123 n = dBprint("Date: %s,", s_to_c(p->s));
1125 for(p = p->next; p; p = p->next){
1132 n += dBprint("%s", s_to_c(p->s));
1134 n += dBprint("%s", rewritezone(s_to_c(p->s)));
1142 n += dBprint("\r\n");
1147 rewritezone(char *z)
1154 tm = localtime(time(0));
1155 mindiff = tm->tzoff/60;
1157 /* if not in my timezone, don't change anything */
1158 if(strcmp(tm->zone, z) != 0)
1167 sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
1172 * stolen from libc/port/print.c
1176 dBprint(char *fmt, ...)
1178 char buf[4096], *out;
1183 out = vseprint(buf, buf + sizeof buf, fmt, arg);
1186 Bwrite(&berr, buf, out - buf);
1189 n = Bwrite(&bout, buf,out - buf);
1199 return Bputc(&bout, x);