#include "common.h" #include "smtp.h" #include #include #include #include static char* connect(char*, Mx*); static char* dotls(char*); static char* doauth(char*); void addhostdom(String*, char*); String* bangtoat(char*); String* convertheader(String*); int dBprint(char*, ...); int dBputc(int); char* data(String*, Biobuf*, Mx*); char* domainify(char*, char*); String* fixrouteaddr(String*, Node*, Node*); char* getcrnl(String*); int getreply(void); char* hello(char*, int); char* mailfrom(char*); int printdate(Node*); int printheader(void); void putcrnl(char*, int); void quit(char*); char* rcptto(char*); char *rewritezone(char *); #define Retry "Retry, Temporary Failure" #define Giveup "Permanent Failure" String *reply; /* last reply */ String *toline; int alarmscale; int autistic; int debug; /* true if we're debugging */ int filter; int insecure; int last = 'n'; /* last character sent by putcrnl() */ int ping; int quitting; /* when error occurs in quit */ int tryauth; /* Try to authenticate, if supported */ int trysecure; /* Try to use TLS if the other side supports it */ 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; int Dfmt(Fmt *fmt) { Mx *mx; mx = va_arg(fmt->args, Mx*); if(mx == nil || mx->host[0] == 0 && mx->ip[0] == 0) return fmtstrcpy(fmt, ""); if((fmt->flags & FmtSharp) == 0) fmtstrcpy(fmt, "("); if(mx->valid == 0) fmtstrcpy(fmt, "!"); fmtprint(fmt, "%s:%s;pref=%d", mx->host, mx->ip, mx->pref); if((fmt->flags & FmtSharp) == 0) fmtstrcpy(fmt, ")"); return 0; } #pragma varargck type "D" Mx* int Lfmt(Fmt *fmt) { int i; Mxtab *mx; mx = va_arg(fmt->args, Mxtab*); if(mx == nil || mx->nmx == 0) return fmtstrcpy(fmt, ""); fmtstrcpy(fmt, "("); for(i = 0; inmx; i++){ if(i == mx->pmx) fmtprint(fmt, "*"); fmtprint(fmt, "%#D", mx->mx+i); if(++i == mx->nmx) break; fmtprint(fmt, " "); } return fmtstrcpy(fmt, ")"); } #pragma varargck type "L" Mxtab* char* deliverytype(void) { if(ping) return "ping"; return "delivery"; } void usage(void) { fprint(2, "usage: smtp [-aAdfips] [-b busted-mx] [-g gw] [-h host] " "[-u user] [.domain] net!host[!service] sender rcpt-list\n"); exits(Giveup); } int timeout(void *, char *msg) { syslog(0, "smtp.fail", "%s interrupt: %s: %s", deliverytype(), 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")){ fprint(2, "smtp timeout: connection closed to %s\n", farend); if(quitting){ syslog(0, "smtp.fail", "%s closed pipe to %s", deliverytype(), farend); _exits(quitrv); } /* call _exits() to prevent Bio from trying to flush closed pipe */ _exits(Retry); } return 0; } void removenewline(char *p) { int n = strlen(p) - 1; if(n < 0) return; if(p[n] == '\n') p[n] = 0; } void main(int argc, char **argv) { char *phase, *addr, *rv, *trv, *host, *domain; char **errs, hellodomain[256]; int i, ok, rcvrs, bustedmx; String *from, *fromm, *sender; Mx mx; alarmscale = 60*1000; /* minutes */ quotefmtinstall(); mailfmtinstall(); /* 2047 encoding */ fmtinstall('D', Dfmt); fmtinstall('L', Lfmt); errs = malloc(argc*sizeof(char*)); reply = s_new(); host = 0; bustedmx = 0; ARGBEGIN{ case 'a': tryauth = 1; trysecure = 1; break; case 'A': /* autistic: won't talk to us until we talk (Verizon) */ autistic = 1; break; case 'b': if(bustedmx >= Maxbustedmx) sysfatal("more than %d busted mxs given", Maxbustedmx); bustedmxs[bustedmx++] = EARGF(usage()); break; case 'd': debug = 1; break; case 'f': filter = 1; break; case 'g': gdomain = EARGF(usage()); break; case 'h': host = EARGF(usage()); 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 = EARGF(usage()); 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; if((rv = strrchr(addr, '!')) && rv[1] == '['){ syslog(0, "smtp.fail", "%s to %s failed: illegal address", deliverytype(), addr); exits(Giveup); } /* * 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 */ phase = ""; USED(phase); /* just in case */ if(filter){ Binit(&bout, 1, OWRITE); rv = data(from, &bfile, nil); if(rv != 0){ phase = "filter"; goto error; } exits(0); } /* mxdial uses its own timeout handler */ if((rv = connect(addr, &mx)) != 0) exits(rv); /* 10 minutes to get through the initial handshake */ atnotify(timeout, 1); alarm(10*alarmscale); if((rv = hello(hellodomain, 0)) != 0){ phase = "hello"; goto error; } alarm(10*alarmscale); if((rv = mailfrom(s_to_c(from))) != 0){ phase = "mailfrom"; goto error; } ok = 0; rcvrs = 0; /* if any rcvrs are ok, we try to send the message */ phase = "rcptto"; 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 && rcvrs == 0) phase = "rcptto; no addresses"; if(ok == 0 || rv == Retry) goto error; if(ping){ quit(0); exits(0); } rv = data(from, &bfile, &mx); 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: %D %s:\n", thedate(), addr, &mx, phase); for(i = 0; i < rcvrs; i++){ if(errs[i]){ syslog(0, "smtp.fail", "delivery to %s at %s %D %s, failed: %s", argv[i], addr, &mx, phase, 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 %D %s failed: %s", deliverytype(), addr, &mx, phase, s_to_c(reply)); fprint(2, "%s connect to %s %D %s:\n%s\n", thedate(), addr, &mx, phase, s_to_c(reply)); if(!filter) quit(rv); exits(rv); } /* * connect to the remote host */ static char * connect(char* net, Mx *x) { char buf[1024]; int fd; Mxtab mx; memset(x, 0, sizeof *x); fd = mxdial0(net, ddomain, gdomain, &mx); if(fd < 0){ rerrstr(buf, sizeof buf); Bprint(&berr, "smtp: %s (%s) %L\n", buf, net, &mx); syslog(0, "smtp.fail", "%s %s (%s) %L", deliverytype(), buf, net, &mx); if(strstr(buf, "illegal") || strstr(buf, "unknown") || strstr(buf, "can't translate")) return Giveup; else return Retry; } *x = mx.mx[mx.pmx]; /* mxtabfree(&mx); but for .netdir */ 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) { char *h; int fd; uchar hash[SHA1dlen]; TLSconn *c; Thumbprint *goodcerts; 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); // fprint(2, "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* smtpcram(DS *ds) { char *p, *e, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192], abuf[128]; int i, n, l; fmtinstall('[', encodefmt); dBprint("AUTH CRAM-MD5\r\n"); if(getreply() != 3) return Retry; p = s_to_c(reply) + 4; l = dec64((uchar*)ch, sizeof ch, p, strlen(p)); if(l == -1) return Retry; ch[l] = 0; e = abuf + sizeof abuf; p = seprint(abuf, e, "proto=cram role=client server=%q", ds->host); if(user != nil) seprint(p, e, " user=%q", user); n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey, abuf); if(n == -1) return "cannot find SMTP password"; if(usr[0] == 0) return "cannot find user name"; for(i = 0; i < n; i++) rbuf[i] = tolower(rbuf[i]); l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf); snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf); dBprint("%s\r\n", ch); if(getreply() != 2) return Retry; return nil; } static char * doauth(char *methods) { char *buf, *base64; int n; DS ds; UserPasswd *p; dialstringparse(ddomain, &ds); if(strstr(methods, "CRAM-MD5")) return smtpcram(&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); free(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); free(base64); if(getreply() != 2) return Retry; }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 nil; } char* hello(char *me, int encrypted) { char *ret, *s, *t; int ehlo; String *r; if(!encrypted){ /* * Verizon fails to print the smtp greeting banner when it * answers a call. Send a no-op in the hope of making it * talk. */ if(autistic){ dBprint("NOOP\r\n"); getreply(); /* consume the smtp greeting */ /* next reply will be response to noop */ } 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'; if(!encrypted && trysecure && (cistrcmp(s, "250-STARTTLS") == 0 || cistrcmp(s, "250 STARTTLS") == 0)){ s_free(r); return dotls(me); } if(tryauth && (encrypted || insecure) && (cistrncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 || cistrncmp(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: return 0; case 5: return Giveup; default: return Retry; } } /* * 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: return Giveup; default: return Retry; } return 0; } static char hex[] = "0123456789abcdef"; /* * send the damn thing */ char * data(String *from, Biobuf *b, Mx *mx) { char *buf, *cp, errmsg[Errlen], id[40]; int i, n, nbytes, bufsize, eof, r; String *fromline; /* * input the header. */ buf = malloc(1); if(buf == 0){ s_append(s_restart(reply), "out of memory"); return Retry; } n = 0; eof = 0; for(;;){ cp = Brdline(b, '\n'); if(cp == nil){ eof = 1; break; } nbytes = Blinelen(b); buf = realloc(buf, n + nbytes + 1); if(buf == 0){ s_append(s_restart(reply), "out of memory"); return Retry; } strncpy(buf + n, cp, nbytes); n += nbytes; if(nbytes == 1) /* end of header */ break; } buf[n] = 0; bufsize = n; /* * parse the header, turn all addresses into @ format */ yyinit(buf, n); yyparse(); /* * print message observing '.' escapes and using \r\n for \n */ alarm(20*alarmscale); if(!filter){ dBprint("DATA\r\n"); switch(getreply()){ case 3: break; case 5: free(buf); return Giveup; default: free(buf); return Retry; } } /* * send header. add a message-id, a sender, and a date if there * isn't one */ nbytes = 0; fromline = convertheader(from); uneaten = buf; srand(truerand()); if(messageid == 0){ for(i = 0; i < 16; i++){ r = rand() & 0xff; id[2*i] = hex[r & 0xf]; id[2*i + 1] = hex[(r>>4) & 0xf]; } id[2*i] = '\0'; nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain); if(debug) Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain); } if(originator == 0){ nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline)); if(debug) Bprint(&berr, "From: %s\r\n", s_to_c(fromline)); } s_free(fromline); if(destination == 0 && toline) if(*s_to_c(toline) == '@'){ /* route addr */ nbytes += Bprint(&bout, "To: <%s>\r\n", s_to_c(toline)); if(debug) Bprint(&berr, "To: <%s>\r\n", s_to_c(toline)); } else { nbytes += Bprint(&bout, "To: %s\r\n", s_to_c(toline)); if(debug) Bprint(&berr, "To: %s\r\n", s_to_c(toline)); } if(date == 0 && udate) nbytes += printdate(udate); if(usys) uneaten = usys->end + 1; nbytes += printheader(); if(*uneaten != '\n') putcrnl("\n", 1); /* * send body */ putcrnl(uneaten, buf + n - uneaten); nbytes += buf + n - uneaten; if(eof == 0){ for(;;){ n = Bread(b, buf, bufsize); if(n < 0){ rerrstr(errmsg, sizeof(errmsg)); s_append(s_restart(reply), errmsg); free(buf); return Retry; } if(n == 0) break; alarm(10*alarmscale); putcrnl(buf, n); nbytes += n; } } free(buf); if(!filter){ if(last != '\n') dBprint("\r\n.\r\n"); else dBprint(".\r\n"); alarm(10*alarmscale); switch(getreply()){ case 2: break; case 5: return Giveup; default: return Retry; } syslog(0, "smtp", "%s sent %d bytes to %s %D", s_to_c(from), nbytes, s_to_c(toline), mx); } return 0; } /* * we're leaving */ void quit(char *rv) { /* 60 minutes to quit */ quitting = 1; quitrv = rv; alarm(60*alarmscale); dBprint("QUIT\r\n"); getreply(); Bterm(&bout); Bterm(&bfile); } /* * read a reply into a string, return the reply code */ int getreply(void) { char *line; int rv; reply = s_reset(reply); for(;;){ line = getcrnl(reply); if(debug) Bflush(&berr); if(line == 0) return -1; if(!isdigit(line[0]) || !isdigit(line[1]) || !isdigit(line[2])) return -1; if(line[3] != '-') break; } if(debug) Bflush(&berr); rv = atoi(line)/100; return rv; } void addhostdom(String *buf, char *host) { s_append(buf, "@"); s_append(buf, host); } /* * Convert from `bang' to `source routing' format. * * a.x.y!b.p.o!c!d -> @a.x.y:c!d@b.p.o */ String * bangtoat(char *addr) { char *field[128]; int i, j, d; String *buf; /* parse the '!' format address */ buf = s_new(); for(i = 0; addr; i++){ field[i] = addr; addr = strchr(addr, '!'); if(addr) *addr++ = 0; } if(i == 1){ s_append(buf, field[0]); return buf; } /* * count leading domain fields (non-domains don't count) */ for(d = 0; d < i - 1; d++) if(strchr(field[d], '.') == 0) break; /* * if there are more than 1 leading domain elements, * put them in as source routing */ if(d > 1){ addhostdom(buf, field[0]); for(j = 1; j< d - 1; j++){ s_append(buf, ","); s_append(buf, "@"); s_append(buf, field[j]); } s_append(buf, ":"); } /* * throw in the non-domain elements separated by '!'s */ s_append(buf, field[d]); for(j = d + 1; j <= i - 1; j++){ s_append(buf, "!"); s_append(buf, field[j]); } if(d) addhostdom(buf, field[d-1]); return buf; } /* * convert header addresses to @ format. * if the address is a source address, and a domain is specified, * make sure it falls in the domain. */ String* convertheader(String *from) { char *s, buf[64]; Field *f; Node *p, *lastp; String *a; if(!returnable(s_to_c(from))){ from = s_new(); s_append(from, "Postmaster"); addhostdom(from, hostdomain); } else if(strchr(s_to_c(from), '@') == 0){ if(s = username(s_to_c(from))){ /* this has always been here, but username() was broken */ snprint(buf, sizeof buf, "%U", s); s_append(a = s_new(), buf); s_append(a, " <"); s_append(a, s_to_c(from)); addhostdom(a, hostdomain); s_append(a, ">"); from = a; } else { from = s_copy(s_to_c(from)); addhostdom(from, hostdomain); } } else from = s_copy(s_to_c(from)); for(f = firstfield; f; f = f->next){ lastp = 0; for(p = f->node; p; lastp = p, p = p->next){ if(!p->addr) continue; a = bangtoat(s_to_c(p->s)); s_free(p->s); if(strchr(s_to_c(a), '@') == 0) addhostdom(a, hostdomain); else if(*s_to_c(a) == '@') a = fixrouteaddr(a, p->next, lastp); p->s = a; } } return from; } /* * ensure route addr has brackets around it */ String* fixrouteaddr(String *raddr, Node *next, Node *last) { String *a; if(last && last->c == '<' && next && next->c == '>') return raddr; /* properly formed already */ a = s_new(); s_append(a, "<"); s_append(a, s_to_c(raddr)); s_append(a, ">"); s_free(raddr); return a; } /* * print out the parsed header */ int printheader(void) { char *cp, c[1]; int n, len; Field *f; Node *p; n = 0; for(f = firstfield; f; f = f->next){ for(p = f->node; p; p = p->next){ if(p->s) n += dBprint("%s", s_to_c(p->s)); else { c[0] = p->c; putcrnl(c, 1); n++; } if(p->white){ cp = s_to_c(p->white); len = strlen(cp); putcrnl(cp, len); n += len; } uneaten = p->end; } putcrnl("\n", 1); n++; uneaten++; /* skip newline */ } return n; } /* * add a domain onto an name, return the new name */ char * domainify(char *name, char *domain) { char *p; static String *s; if(domain == 0 || strchr(name, '.') != 0) return name; s = s_reset(s); s_append(s, name); p = strchr(domain, '.'); if(p == 0){ s_append(s, "."); p = domain; } s_append(s, p); return s_to_c(s); } /* * print message observing '.' escapes and using \r\n for \n */ void putcrnl(char *cp, int n) { int c; for(; n; n--, cp++){ c = *cp; if(c == '\n') dBputc('\r'); else if(c == '.' && last=='\n') dBputc('.'); dBputc(c); last = c; } } /* * Get a line including a crnl into a string. Convert crnl into nl. */ char * getcrnl(String *s) { int c, count; count = 0; for(;;){ c = Bgetc(&bin); if(debug) Bputc(&berr, c); switch(c){ case -1: s_append(s, "connection closed unexpectedly by remote system"); s_terminate(s); return 0; case '\r': c = Bgetc(&bin); if(c == '\n'){ case '\n': s_putc(s, c); if(debug) Bputc(&berr, c); count++; s_terminate(s); return s->ptr - count; } Bungetc(&bin); s_putc(s, '\r'); if(debug) Bputc(&berr, '\r'); count++; break; default: s_putc(s, c); count++; break; } } } /* * print out a parsed date */ int printdate(Node *p) { int n, sep; n = dBprint("Date: %s,", s_to_c(p->s)); sep = 0; for(p = p->next; p; p = p->next){ if(p->s){ if(sep == 0){ dBputc(' '); n++; } if(p->next) n += dBprint("%s", s_to_c(p->s)); else n += dBprint("%s", rewritezone(s_to_c(p->s))); sep = 0; } else { dBputc(p->c); n++; sep = 1; } } n += dBprint("\r\n"); return n; } char * rewritezone(char *z) { char s; int mindiff; Tm *tm; static char x[7]; tm = localtime(time(0)); mindiff = tm->tzoff/60; /* if not in my timezone, don't change anything */ if(strcmp(tm->zone, z) != 0) return z; if(mindiff < 0){ s = '-'; mindiff = -mindiff; } else s = '+'; sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60); return x; } /* * stolen from libc/port/print.c */ int dBprint(char *fmt, ...) { char buf[4096], *out; int n; va_list arg; va_start(arg, fmt); out = vseprint(buf, buf + sizeof buf, fmt, arg); va_end(arg); if(debug){ Bwrite(&berr, buf, out - buf); Bflush(&berr); } n = Bwrite(&bout, buf,out - buf); Bflush(&bout); return n; } int dBputc(int x) { if(debug) Bputc(&berr, x); return Bputc(&bout, x); }