📄 smtp.c
字号:
#include "common.h"#include "smtp.h"#include <ctype.h>#include <mp.h>#include <libsec.h>#include <auth.h>static char* connect(char*);static char* dotls(char*);static char* doauth(char*);char* hello(char*, int);char* mailfrom(char*);char* rcptto(char*);char* data(String*, Biobuf*);void quit(char*);int getreply(void);void addhostdom(String*, char*);String* bangtoat(char*);String* convertheader(String*);int printheader(void);char* domainify(char*, char*);void putcrnl(char*, int);char* getcrnl(String*);int printdate(Node*);char *rewritezone(char *);int dBprint(char*, ...);int dBputc(int);String* fixrouteaddr(String*, Node*, Node*);int ping;int insecure;#define Retry "Retry, Temporary Failure"#define Giveup "Permanent Failure"int debug; /* true if we're debugging */String *reply; /* last reply */String *toline;int alarmscale;int last = 'n'; /* last character sent by putcrnl() */int filter;int trysecure; /* Try to use TLS if the other side supports it */int tryauth; /* Try to authenticate, if supported */int quitting; /* when error occurs in quit */char *quitrv; /* deferred return value when in quit */char ddomain[1024]; /* domain name of destination machine */char *gdomain; /* domain name of gateway */char *uneaten; /* first character after rfc822 headers */char *farend; /* system we are trying to send to */char *user; /* user we are authenticating as, if authenticating */char hostdomain[256];Biobuf bin;Biobuf bout;Biobuf berr;Biobuf bfile;voidusage(void){ fprint(2, "usage: smtp [-adips] [-uuser] [-hhost] [.domain] net!host[!service] sender rcpt-list\n"); exits(Giveup); }inttimeout(void *x, char *msg){ USED(x); syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg); if(strstr(msg, "alarm")){ fprint(2, "smtp timeout: connection to %s timed out\n", farend); if(quitting) exits(quitrv); exits(Retry); } if(strstr(msg, "closed pipe")){ /* call _exits() to prevent Bio from trying to flush closed pipe */ fprint(2, "smtp timeout: connection closed to %s\n", farend); if(quitting){ syslog(0, "smtp.fail", "closed pipe to %s", farend); _exits(quitrv); } _exits(Retry); } return 0;}voidremovenewline(char *p){ int n = strlen(p)-1; if(n < 0) return; if(p[n] == '\n') p[n] = 0;}voidmain(int argc, char **argv){ char hellodomain[256]; char *host, *domain; String *from; String *fromm; String *sender; char *addr; char *rv, *trv; int i, ok, rcvrs; char **errs; alarmscale = 60*1000; /* minutes */ quotefmtinstall(); errs = malloc(argc*sizeof(char*)); reply = s_new(); host = 0; ARGBEGIN{ case 'a': tryauth = 1; trysecure = 1; break; case 'f': filter = 1; break; case 'd': debug = 1; break; case 'g': gdomain = ARGF(); break; case 'h': host = ARGF(); break; case 'i': insecure = 1; break; case 'p': alarmscale = 10*1000; /* tens of seconds */ ping = 1; break; case 's': trysecure = 1; break; case 'u': user = ARGF(); break; default: usage(); break; }ARGEND; Binit(&berr, 2, OWRITE); Binit(&bfile, 0, OREAD); /* * get domain and add to host name */ if(*argv && **argv=='.') { domain = *argv; argv++; argc--; } else domain = domainname_read(); if(host == 0) host = sysname_read(); strcpy(hostdomain, domainify(host, domain)); strcpy(hellodomain, domainify(sysname_read(), domain)); /* * get destination address */ if(*argv == 0) usage(); addr = *argv++; argc--; farend = addr; /* * get sender's machine. * get sender in internet style. domainify if necessary. */ if(*argv == 0) usage(); sender = unescapespecial(s_copy(*argv++)); argc--; fromm = s_clone(sender); rv = strrchr(s_to_c(fromm), '!'); if(rv) *rv = 0; else *s_to_c(fromm) = 0; from = bangtoat(s_to_c(sender)); /* * send the mail */ if(filter){ Binit(&bout, 1, OWRITE); rv = data(from, &bfile); if(rv != 0) goto error; exits(0); } /* mxdial uses its own timeout handler */ if((rv = connect(addr)) != 0) exits(rv); /* 10 minutes to get through the initial handshake */ atnotify(timeout, 1); alarm(10*alarmscale); if((rv = hello(hellodomain, 0)) != 0) goto error; alarm(10*alarmscale); if((rv = mailfrom(s_to_c(from))) != 0) goto error; ok = 0; rcvrs = 0; /* if any rcvrs are ok, we try to send the message */ for(i = 0; i < argc; i++){ if((trv = rcptto(argv[i])) != 0){ /* remember worst error */ if(rv != Giveup) rv = trv; errs[rcvrs] = strdup(s_to_c(reply)); removenewline(errs[rcvrs]); } else { ok++; errs[rcvrs] = 0; } rcvrs++; } /* if no ok rcvrs or worst error is retry, give up */ if(ok == 0 || rv == Retry) goto error; if(ping){ quit(0); exits(0); } rv = data(from, &bfile); if(rv != 0) goto error; quit(0); if(rcvrs == ok) exits(0); /* * here when some but not all rcvrs failed */ fprint(2, "%s connect to %s:\n", thedate(), addr); for(i = 0; i < rcvrs; i++){ if(errs[i]){ syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]); fprint(2, " mail to %s failed: %s", argv[i], errs[i]); } } exits(Giveup); /* * here when all rcvrs failed */error: removenewline(s_to_c(reply)); syslog(0, "smtp.fail", "%s to %s failed: %s", ping ? "ping" : "delivery", addr, s_to_c(reply)); fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply)); if(!filter) quit(rv); exits(rv);}/* * connect to the remote host */static char *connect(char* net){ char buf[256]; int fd; fd = mxdial(net, ddomain, gdomain); if(fd < 0){ rerrstr(buf, sizeof(buf)); Bprint(&berr, "smtp: %s (%s)\n", buf, net); syslog(0, "smtp.fail", "%s (%s)", buf, net); if(strstr(buf, "illegal") || strstr(buf, "unknown") || strstr(buf, "can't translate")) return Giveup; else return Retry; } Binit(&bin, fd, OREAD); fd = dup(fd, -1); Binit(&bout, fd, OWRITE); return 0;}static char smtpthumbs[] = "/sys/lib/tls/smtp";static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude";/* * exchange names with remote host, attempt to * enable encryption and optionally authenticate. * not fatal if we can't. */static char *dotls(char *me){ TLSconn *c; Thumbprint *goodcerts; char *h; int fd; uchar hash[SHA1dlen]; c = mallocz(sizeof(*c), 1); /* Note: not freed on success */ if (c == nil) return Giveup; dBprint("STARTTLS\r\n"); if (getreply() != 2) return Giveup; fd = tlsClient(Bfildes(&bout), c); if (fd < 0) { syslog(0, "smtp", "tlsClient to %q: %r", ddomain); return Giveup; } goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs); if (goodcerts == nil) { free(c); close(fd); syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs); return Giveup; /* how to recover? TLS is started */ } /* compute sha1 hash of remote's certificate, see if we know it */ sha1(c->cert, c->certlen, hash, nil); if (!okThumbprint(hash, goodcerts)) { /* TODO? if not excluded, add hash to thumb list */ free(c); close(fd); h = malloc(2*sizeof hash + 1); if (h != nil) { enc16(h, 2*sizeof hash + 1, hash, sizeof hash); // print("x509 sha1=%s", h); syslog(0, "smtp", "remote cert. has bad thumbprint: x509 sha1=%s server=%q", h, ddomain); free(h); } return Giveup; /* how to recover? TLS is started */ } freeThumbprints(goodcerts); Bterm(&bin); Bterm(&bout); /* * set up bin & bout to use the TLS fd, i/o upon which generates * i/o on the original, underlying fd. */ Binit(&bin, fd, OREAD); fd = dup(fd, -1); Binit(&bout, fd, OWRITE); syslog(0, "smtp", "started TLS to %q", ddomain); return(hello(me, 1));}static char *doauth(char *methods){ char *buf, *base64; int n; DS ds; UserPasswd *p; dial_string_parse(ddomain, &ds); if(user != nil) p = auth_getuserpasswd(nil, "proto=pass service=smtp server=%q user=%q", ds.host, user); else p = auth_getuserpasswd(nil, "proto=pass service=smtp server=%q", ds.host); if (p == nil) return Giveup; if (strstr(methods, "LOGIN")){ dBprint("AUTH LOGIN\r\n"); if (getreply() != 3) return Retry; n = strlen(p->user); base64 = malloc(2*n); if (base64 == nil) return Retry; /* Out of memory */ enc64(base64, 2*n, (uchar *)p->user, n); dBprint("%s\r\n", base64); if (getreply() != 3) return Retry; n = strlen(p->passwd); base64 = malloc(2*n); if (base64 == nil) return Retry; /* Out of memory */ enc64(base64, 2*n, (uchar *)p->passwd, n); dBprint("%s\r\n", base64); if (getreply() != 2) return Retry; free(base64); } else if (strstr(methods, "PLAIN")){ n = strlen(p->user) + strlen(p->passwd) + 3; buf = malloc(n); base64 = malloc(2 * n); if (buf == nil || base64 == nil) { free(buf); return Retry; /* Out of memory */ } snprint(buf, n, "%c%s%c%s", 0, p->user, 0, p->passwd); enc64(base64, 2 * n, (uchar *)buf, n - 1); free(buf); dBprint("AUTH PLAIN %s\r\n", base64); free(base64); if (getreply() != 2) return Retry; } else return "No supported AUTH method"; return(0);}char *hello(char *me, int encrypted){ int ehlo; String *r; char *ret, *s, *t; if (!encrypted) switch(getreply()){ case 2: break; case 5: return Giveup; default: return Retry; } ehlo = 1; Again: if(ehlo) dBprint("EHLO %s\r\n", me); else dBprint("HELO %s\r\n", me); switch (getreply()) { case 2: break; case 5: if(ehlo){ ehlo = 0; goto Again; } return Giveup; default: return Retry; } r = s_clone(reply); if(r == nil) return Retry; /* Out of memory or couldn't get string */ /* Invariant: every line has a newline, a result of getcrlf() */ for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){ *t = '\0'; for (t = s; *t != '\0'; t++) *t = toupper(*t); if(!encrypted && trysecure && (strcmp(s, "250-STARTTLS") == 0 || strcmp(s, "250 STARTTLS") == 0)){ s_free(r); return(dotls(me)); } if(tryauth && (encrypted || insecure) && (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 || strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){ ret = doauth(s + strlen("250 AUTH ")); s_free(r); return ret; } } s_free(r); return 0;}/* * report sender to remote */char *mailfrom(char *from){ if(!returnable(from)) dBprint("MAIL FROM:<>\r\n"); else if(strchr(from, '@')) dBprint("MAIL FROM:<%s>\r\n", from); else dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain); switch(getreply()){ case 2: break; case 5: return Giveup; default: return Retry; } return 0;}/* * report a recipient to remote */char *rcptto(char *to){ String *s; s = unescapespecial(bangtoat(to)); if(toline == 0) toline = s_new(); else s_append(toline, ", "); s_append(toline, s_to_c(s)); if(strchr(s_to_c(s), '@')) dBprint("RCPT TO:<%s>\r\n", s_to_c(s)); else { s_append(toline, "@"); s_append(toline, ddomain); dBprint("RCPT TO:<%s@%s>\r\n", s_to_c(s), ddomain); } alarm(10*alarmscale); switch(getreply()){ case 2: break; case 5:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -