#include "common.h"
#include "smtp.h"
#include <ctype.h>
#include <mp.h>
#include <libsec.h>
#include <auth.h>

static  char*   connect(char*, Mx*);
static  char*   wraptls(void);
static  char*   dotls(char*);
static  char*   doauth(char*);

void    addhostdom(String*, char*);
String* bangtoat(char*);
String* convertheader(String*);
int     dBprint(char*, ...);
#pragma varargck argpos dBprint 1
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 *);

char    Retry[] = "Retry, Temporary Failure";
char    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];
Mx      *tmmx;          /* global for timeout */

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)
               return fmtstrcpy(fmt, "");
       else
               return fmtprint(fmt, "(%s:%s)", mx->host, mx->ip);
}
#pragma varargck        type    "D"     Mx*

char*
deliverytype(void)
{
       if(ping)
               return "ping";
       return "delivery";
}

void
usage(void)
{
       fprint(2, "usage: smtp [-aAdfipst] [-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 %D", deliverytype(), farend,  msg, tmmx);
       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 %D", deliverytype(), farend, tmmx);
                       _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, *p, *e, hellodomain[256], allrx[512];
       int i, ok, rcvrs, bustedmx;
       String *from, *fromm, *sender;
       Mx mx;

       alarmscale = 60*1000;   /* minutes */
       quotefmtinstall();
       mailfmtinstall();               /* 2047 encoding */
       fmtinstall('D', Dfmt);
       fmtinstall('[', encodefmt);
       fmtinstall('H', encodefmt);
       errs = malloc(argc*sizeof(char*));
       reply = s_new();
       host = 0;
       bustedmx = 0;
       ARGBEGIN{
       case 'a':
               tryauth = 1;
               if(trysecure == 0)
                       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':
               if(trysecure == 0)
                       trysecure = 1;
               break;
       case 't':
               trysecure = 2;
               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();
       if(user == nil)
               user = getuser();
       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
        */
       rcvrs = 0;
       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);

       tmmx = &mx;
       /* 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;
       /* 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:
       alarm(0);
       removenewline(s_to_c(reply));
       if(rcvrs > 0){
               p = allrx;
               e = allrx + sizeof allrx;
               seprint(p, e, "to ");
               for(i = 0; i < rcvrs - 1; i++)
                       p = seprint(p, e, "%s,", argv[i]);
               seprint(p, e, "%s ", argv[i]);
       }
       syslog(0, "smtp.fail", "%s %s at %s %D %s failed: %s",
               deliverytype(), allrx, 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 *mx)
{
       char buf[ERRMAX];
       int fd;

       fd = mxdial(net, ddomain, gdomain, mx);

       if(fd < 0){
               rerrstr(buf, sizeof buf);
               Bprint(&berr, "smtp: %s (%s) %D\n", buf, net, mx);
               syslog(0, "smtp.fail", "%s %s (%s) %D", deliverytype(), buf, net, mx);
               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";

static int
tracetls(char *fmt, ...)
{
       va_list ap;

       va_start(ap, fmt);
       Bvprint(&berr, fmt, ap);
       Bprint(&berr, "\n");
       Bflush(&berr);
       va_end(ap);
       return 0;
}

static char*
wraptls(void)
{
       TLSconn *c;
       Thumbprint *goodcerts;
       char *err;
       int fd;

       goodcerts = nil;
       err = Giveup;
       c = mallocz(sizeof(*c), 1);
       if (c == nil)
               return err;

       if (debug)
               c->trace = tracetls;

       fd = tlsClient(Bfildes(&bout), c);
       if (fd < 0) {
               syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
               goto Out;
       }
       Bterm(&bout);
       Binit(&bout, fd, OWRITE);
       fd = dup(fd, Bfildes(&bin));
       Bterm(&bin);
       Binit(&bin, fd, OREAD);

       goodcerts = initThumbprints(smtpthumbs, smtpexclthumbs, "x509");
       if (goodcerts == nil) {
               syslog(0, "smtp", "bad thumbprints in %s", smtpthumbs);
               goto Out;
       }
       if (!okCertificate(c->cert, c->certlen, goodcerts)) {
               syslog(0, "smtp", "cert for %s not recognized: %r", ddomain);
               goto Out;
       }
       syslog(0, "smtp", "started TLS to %q", ddomain);
       err = nil;
Out:
       freeThumbprints(goodcerts);
       free(c->cert);
       free(c->sessionID);
       free(c);
       return err;
}

/*
*  exchange names with remote host, attempt to
*  enable encryption and optionally authenticate.
*  not fatal if we can't.
*/
static char *
dotls(char *me)
{
       char *err;

       dBprint("STARTTLS\r\n");
       if (getreply() != 2)
               return Giveup;

       err = wraptls();
       if (err != nil)
               return err;

       return(hello(me, 1));
}

static char*
smtpcram(DS *ds)
{
       char *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
       int i, n, l;

       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));
       ch[l] = 0;
       n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
               "proto=cram role=client server=%q user=%q",
               ds->host, user);
       if(n == -1){
               if(temperror())
                       return Retry;
               syslog(0, "smtp.fail", "failed to get challenge response: %r");
               return Giveup;
       }
       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, utfnlen(rbuf, n), rbuf);
       snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf);

       dBprint("%s\r\n", ebuf);
       if(getreply() != 2)
               return Retry;
       return nil;
}

static char *
doauth(char *methods)
{
       char buf[1024], *err;
       UserPasswd *p;
       DS ds;
       int n;

       dialstringparse(farend, &ds);
       if(strstr(methods, "CRAM-MD5"))
               return smtpcram(&ds);
       p = auth_getuserpasswd(nil,
               "proto=pass service=smtp server=%q user=%q",
               ds.host, user);
       if (p == nil) {
               if(temperror())
                       return Retry;
               syslog(0, "smtp.fail", "failed to get userpasswd: %r");
               return Giveup;
       }
       err = Retry;
       if (strstr(methods, "LOGIN")){
               dBprint("AUTH LOGIN\r\n");
               if (getreply() != 3)
                       goto out;

               dBprint("%.*[\r\n", (int)strlen(p->user), p->user);
               if (getreply() != 3)
                       goto out;

               dBprint("%.*[\r\n", (int)strlen(p->passwd), p->passwd);
               if (getreply() != 2)
                       goto out;

               err = nil;
       }
       else if (strstr(methods, "PLAIN")){
               n = snprint(buf, sizeof(buf), "%c%s%c%s", 0, p->user, 0, p->passwd);
               dBprint("AUTH PLAIN %.*[\r\n", n, buf);
               memset(buf, 0, sizeof(buf));
               if (getreply() != 2)
                       goto out;
               err = nil;
       } else
               err = "No supported AUTH method";
out:
       memset(p->user, 0, strlen(p->user));
       memset(p->passwd, 0, strlen(p->passwd));
       free(p);
       return err;
}

char*
hello(char *me, int encrypted)
{
       char *ret, *s, *t;
       int ehlo;
       String *r;

       if(!encrypted){
               if(trysecure > 1){
                       if((ret = wraptls()) != nil)
                               return ret;
                       encrypted = 1;
               }

               /*
                * 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;
}

/*
*  send the damn thing
*/
char *
data(String *from, Biobuf *b, Mx *mx)
{
       char *buf, *cp, errmsg[ERRMAX];
       int n, nbytes, bufsize, eof;
       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;

       if(messageid == 0){
               uchar id[16];

               genrandom(id, sizeof(id));
               nbytes += dBprint("Message-ID: <%.*H@%s>\r\n",
                       sizeof(id), id, hostdomain);
       }

       if(originator == 0)
               nbytes += dBprint("From: %s\r\n", s_to_c(fromline));
       s_free(fromline);

       if(destination == 0 && toline){
               if(*s_to_c(toline) == '@')      /* route addr */
                       nbytes += dBprint("To: <%s>\r\n", s_to_c(toline));
               else
                       nbytes += dBprint("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:[email protected]
*/
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);
}