#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* 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; i<mx->nmx; 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);
}
|