/*
* marshal - gather mail message for transmission
*/
#include "common.h"
#include <ctype.h>
typedef struct Attach Attach;
typedef struct Alias Alias;
typedef struct Addr Addr;
typedef struct Sendermap Sendermap;
typedef struct Ctype Ctype;
struct Attach {
Attach *next;
char *path;
char *type;
int ainline;
Ctype *ctype;
};
struct Alias
{
Alias *next;
int n;
Addr *addr;
};
struct Addr
{
Addr *next;
char *v;
};
struct Sendermap
{
char *to;
char *from;
Sendermap *next;
};
enum {
Hfrom,
Hto,
Hcc,
Hbcc,
Hsender,
Hreplyto,
Hinreplyto,
Hdate,
Hsubject,
Hmime,
Hpriority,
Hmsgid,
Hcontent,
Hx,
Hprecedence,
Nhdr,
};
enum {
PGPsign = 1,
PGPencrypt = 2,
};
char *hdrs[Nhdr] = {
[Hfrom] "from:",
[Hto] "to:",
[Hcc] "cc:",
[Hbcc] "bcc:",
[Hreplyto] "reply-to:",
[Hinreplyto] "in-reply-to:",
[Hsender] "sender:",
[Hdate] "date:",
[Hsubject] "subject:",
[Hpriority] "priority:",
[Hmsgid] "message-id:",
[Hmime] "mime-",
[Hcontent] "content-",
[Hx] "x-",
[Hprecedence] "precedence",
};
struct Ctype {
char *type;
char *ext;
int display;
};
Ctype ctype[] = {
{ "text/plain", "txt", 1, },
{ "text/html", "html", 1, },
{ "text/html", "htm", 1, },
{ "text/tab-separated-values", "tsv", 1, },
{ "text/richtext", "rtx", 1, },
{ "message/rfc822", "txt", 1, },
{ "", 0, 0, },
};
Ctype *mimetypes;
int pid = -1;
int pgppid = -1;
void Bdrain(Biobuf*);
void attachment(Attach*, Biobuf*);
void body(Biobuf*, Biobuf*, int);
int cistrcmp(char*, char*);
int cistrncmp(char*, char*, int);
int doublequote(Fmt*);
void* emalloc(int);
int enc64(char*, int, uchar*, int);
void* erealloc(void*, int);
char* estrdup(char*);
Addr* expand(int, char**);
Addr* expandline(String**, Addr*);
void freeaddr(Addr*);
void freeaddr(Addr *);
void freeaddrs(Addr*);
void freealias(Alias*);
void freealiases(Alias*);
void freesendermaps(Sendermap *sm);
Attach* mkattach(char*, char*, int);
char* mkboundary(void);
char* mksubject(char*);
int pgpfilter(int*, int, int);
int pgpopts(char*);
int printcc(Biobuf*, Addr*);
int printdate(Biobuf*);
int printfrom(Biobuf*);
int printinreplyto(Biobuf*, char*);
int printsubject(Biobuf*, char*);
int printto(Biobuf*, Addr*);
Sendermap *readsendermaps(void);
char * overridesender(char *user, Sendermap *sm, Addr *to);
Alias* readaliases(void);
int readheaders(Biobuf*, int*, String**, Addr**, int);
void readmimetypes(void);
int rfc2047fmt(Fmt*);
int sendmail(Addr*, Addr*, int*, char*);
char* waitforsubprocs(void);
int rflag, lbflag, xflag, holding, nflag, Fflag, eightflag, dflag;
int pgpflag = 0;
char *user;
char *login;
Alias *aliases;
int rfc822syntaxerror;
char lastchar;
char *replymsg;
enum
{
Ok = 0,
Nomessage = 1,
Nobody = 2,
Error = -1,
};
#pragma varargck type "Z" char*
#pragma varargck type "U" char*
void
usage(void)
{
fprint(2, "usage: %s [-Fr#xn] [-s subject] [-c ccrecipient] [-t type]"
" [-aA attachment] [-p[es]] [-R replymsg] -8 | recipient-list\n",
argv0);
exits("usage");
}
void
fatal(char *fmt, ...)
{
char buf[1024];
va_list arg;
if(pid >= 0)
postnote(PNPROC, pid, "die");
if(pgppid >= 0)
postnote(PNPROC, pgppid, "die");
va_start(arg, fmt);
vseprint(buf, buf+sizeof(buf), fmt, arg);
va_end(arg);
fprint(2, "%s: %s\n", argv0, buf);
holdoff(holding);
exits(buf);
}
static void
bwritesfree(Biobuf *bp, String **str)
{
if(Bwrite(bp, s_to_c(*str), s_len(*str)) != s_len(*str))
fatal("write error");
s_free(*str);
*str = nil;
}
void
main(int argc, char **argv)
{
int ccargc, flags, fd, noinput, headersrv;
char *subject, *type, *boundary;
char *ccargv[32];
Addr *cc, *to;
Attach *first, **l, *a;
Biobuf in, out, *b;
String *file, *hdrstring;
noinput = 0;
subject = nil;
first = nil;
l = &first;
type = nil;
hdrstring = nil;
ccargc = 0;
quotefmtinstall();
fmtinstall('Z', doublequote);
fmtinstall('U', rfc2047fmt);
ARGBEGIN{
case 'a':
flags = 0;
goto aflag;
case 'A':
flags = 1;
aflag:
a = mkattach(EARGF(usage()), type, flags);
if(a == nil)
exits("bad args");
type = nil;
*l = a;
l = &a->next;
break;
case 'C':
if(ccargc >= nelem(ccargv)-1)
sysfatal("too many cc's");
ccargv[ccargc++] = EARGF(usage());
break;
case 'd':
dflag = 1; /* for sendmail */
break;
case 'F':
Fflag = 1; /* file message */
break;
case 'n': /* no standard input */
nflag = 1;
break;
case 'p': /* pgp flag: encrypt, sign, or both */
if(pgpopts(EARGF(usage())) < 0)
sysfatal("bad pgp options");
break;
case 'r':
rflag = 1; /* for sendmail */
break;
case 'R':
replymsg = EARGF(usage());
break;
case 's':
subject = EARGF(usage());
break;
case 't':
type = EARGF(usage());
break;
case 'x':
xflag = 1; /* for sendmail */
break;
case '8': /* read recipients from rfc822 header */
eightflag = 1;
break;
case '#':
lbflag = 1; /* for sendmail */
break;
default:
usage();
break;
}ARGEND;
login = getlog();
user = getenv("upasname");
if(user == nil || *user == 0)
user = login;
if(user == nil || *user == 0)
sysfatal("can't read user name");
if(Binit(&in, 0, OREAD) < 0)
sysfatal("can't Binit 0: %r");
if(nflag && eightflag)
sysfatal("can't use both -n and -8");
if(eightflag && argc >= 1)
usage();
else if(!eightflag && argc < 1)
usage();
aliases = readaliases();
if(!eightflag){
to = expand(argc, argv);
cc = expand(ccargc, ccargv);
} else
to = cc = nil;
Sendermap *sendermaps = readsendermaps();
user = overridesender(user, sendermaps, to);
freesendermaps(sendermaps);
flags = 0;
headersrv = Nomessage;
if(!nflag && !xflag && !lbflag &&!dflag) {
/*
* pass through headers, keeping track of which we've seen,
* perhaps building to list.
*/
holding = holdon();
headersrv = readheaders(&in, &flags, &hdrstring,
eightflag ? &to: nil, 1);
if(rfc822syntaxerror){
Bdrain(&in);
fatal("rfc822 syntax error, message not sent");
}
if(to == nil){
Bdrain(&in);
fatal("no addresses found, message not sent");
}
switch(headersrv){
case Error: /* error */
fatal("reading");
break;
case Nomessage: /* no message, just exit mimicking old behavior */
noinput = 1;
if(first == nil)
exits(0);
break;
}
}
fd = sendmail(to, cc, &pid, Fflag ? argv[0] : nil);
if(fd < 0)
sysfatal("execing sendmail: %r\n:");
if(xflag || lbflag || dflag){
close(fd);
exits(waitforsubprocs());
}
if(Binit(&out, fd, OWRITE) < 0)
fatal("can't Binit 1: %r");
if(!nflag)
bwritesfree(&out, &hdrstring);
/* read user's standard headers */
file = s_new();
mboxpath("headers", user, file, 0);
b = Bopen(s_to_c(file), OREAD);
if(b != nil){
if (readheaders(b, &flags, &hdrstring, nil, 0) == Error)
fatal("reading");
Bterm(b);
bwritesfree(&out, &hdrstring);
}
/* add any headers we need */
if((flags & (1<<Hdate)) == 0)
if(printdate(&out) < 0)
fatal("writing");
if((flags & (1<<Hfrom)) == 0)
if(printfrom(&out) < 0)
fatal("writing");
if((flags & (1<<Hto)) == 0)
if(printto(&out, to) < 0)
fatal("writing");
if((flags & (1<<Hcc)) == 0)
if(printcc(&out, cc) < 0)
fatal("writing");
if((flags & (1<<Hsubject)) == 0 && subject != nil)
if(printsubject(&out, subject) < 0)
fatal("writing");
if(replymsg != nil)
if(printinreplyto(&out, replymsg) < 0)
fatal("writing");
Bprint(&out, "MIME-Version: 1.0\n");
if(pgpflag){
/* interpose pgp process between us and sendmail to handle body */
Bflush(&out);
Bterm(&out);
fd = pgpfilter(&pgppid, fd, pgpflag);
if(Binit(&out, fd, OWRITE) < 0)
fatal("can't Binit 1: %r");
}
/* if attachments, stick in multipart headers */
boundary = nil;
if(first != nil){
boundary = mkboundary();
Bprint(&out, "Content-Type: multipart/mixed;\n");
Bprint(&out, "\tboundary=\"%s\"\n\n", boundary);
Bprint(&out, "This is a multi-part message in MIME format.\n");
Bprint(&out, "--%s\n", boundary);
Bprint(&out, "Content-Disposition: inline\n");
}
if(!nflag){
if(!noinput && headersrv == Ok)
body(&in, &out, 1);
} else
Bprint(&out, "\n");
holdoff(holding);
Bflush(&out);
for(a = first; a != nil; a = a->next){
if(lastchar != '\n')
Bprint(&out, "\n");
Bprint(&out, "--%s\n", boundary);
attachment(a, &out);
}
if(first != nil){
if(lastchar != '\n')
Bprint(&out, "\n");
Bprint(&out, "--%s--\n", boundary);
}
Bterm(&out);
close(fd);
exits(waitforsubprocs());
}
/* evaluate pgp option string */
int
pgpopts(char *s)
{
if(s == nil || s[0] == '\0')
return -1;
while(*s){
switch(*s++){
case 's': case 'S':
pgpflag |= PGPsign;
break;
case 'e': case 'E':
pgpflag |= PGPencrypt;
break;
default:
return -1;
}
}
return 0;
}
/*
* read headers from stdin into a String, expanding local aliases,
* keep track of which headers are there, which addresses we have
* remove Bcc: line.
*/
int
readheaders(Biobuf *in, int *fp, String **sp, Addr **top, int strict)
{
int i, seen, hdrtype;
char *p;
Addr *to;
String *s, *sline;
s = s_new();
sline = nil;
to = nil;
hdrtype = -1;
seen = 0;
for(;;) {
if((p = Brdline(in, '\n')) != nil) {
seen = 1;
p[Blinelen(in)-1] = 0;
/* coalesce multiline headers */
if((*p == ' ' || *p == '\t') && sline){
s_append(sline, "\n");
s_append(sline, p);
p[Blinelen(in)-1] = '\n';
continue;
}
}
/* process the current header, it's all been read */
if(sline) {
assert(hdrtype != -1);
if(top){
switch(hdrtype){
case Hto:
case Hcc:
case Hbcc:
to = expandline(&sline, to);
break;
}
}
if(hdrtype == Hsubject){
s_append(s, mksubject(s_to_c(sline)));
s_append(s, "\n");
}else if(top==nil || hdrtype!=Hbcc){
s_append(s, s_to_c(sline));
s_append(s, "\n");
}
s_free(sline);
sline = nil;
}
if(p == nil)
break;
/* if no :, it's not a header, seek back and break */
if(strchr(p, ':') == nil){
p[Blinelen(in)-1] = '\n';
Bseek(in, -Blinelen(in), 1);
break;
}
sline = s_copy(p);
/*
* classify the header. If we don't recognize it, break.
* This is to take care of users who start messages with
* lines that contain ':'s but that aren't headers.
* This is a bit hokey. Since I decided to let users type
* headers, I need some way to distinguish. Therefore,
* marshal tries to know all likely headers and will indeed
* screw up if the user types an unlikely one. -- presotto
*/
hdrtype = -1;
for(i = 0; i < nelem(hdrs); i++){
if(cistrncmp(hdrs[i], p, strlen(hdrs[i])) == 0){
*fp |= 1<<i;
hdrtype = i;
break;
}
}
if(strict){
if(hdrtype == -1){
p[Blinelen(in)-1] = '\n';
Bseek(in, -Blinelen(in), 1);
break;
}
} else
hdrtype = 0;
p[Blinelen(in)-1] = '\n';
}
*sp = s;
if(top)
*top = to;
if(seen == 0){
if(Blinelen(in) == 0)
return Nomessage;
else
return Ok;
}
if(p == nil)
return Nobody;
return Ok;
}
/* pass the body to sendmail, make sure body starts and ends with a newline */
void
body(Biobuf *in, Biobuf *out, int docontenttype)
{
char *buf, *p;
int i, n, len;
n = 0;
len = 16*1024;
buf = emalloc(len);
/* first char must be newline */
i = Bgetc(in);
if(i > 0){
if(i != '\n')
buf[n++] = '\n';
buf[n++] = i;
} else
buf[n++] = '\n';
/* read into memory */
if(docontenttype){
while(docontenttype){
if(n == len){
len += len >> 2;
buf = realloc(buf, len);
if(buf == nil)
sysfatal("%r");
}
p = buf+n;
i = Bread(in, p, len - n);
if(i < 0)
fatal("input error2");
if(i == 0)
break;
n += i;
for(; i > 0; i--)
if((*p++ & 0x80) && docontenttype){
Bprint(out, "Content-Type: text/plain; charset=\"UTF-8\"\n");
Bprint(out, "Content-Transfer-Encoding: 8bit\n");
docontenttype = 0;
break;
}
}
if(docontenttype){
Bprint(out, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
Bprint(out, "Content-Transfer-Encoding: 7bit\n");
}
}
/* write what we already read */
if(Bwrite(out, buf, n) < 0)
fatal("output error");
if(n > 0)
lastchar = buf[n-1];
else
lastchar = '\n';
/* pass the rest */
for(;;){
n = Bread(in, buf, len);
if(n < 0)
fatal("input error2");
if(n == 0)
break;
if(Bwrite(out, buf, n) < 0)
fatal("output error");
lastchar = buf[n-1];
}
}
/*
* pass the body to sendmail encoding with base64
*
* the size of buf is very important to enc64. Anything other than
* a multiple of 3 will cause enc64 to output a termination sequence.
* To ensure that a full buf corresponds to a multiple of complete lines,
* we make buf a multiple of 3*18 since that's how many enc64 sticks on
* a single line. This avoids short lines in the output which is pleasing
* but not necessary.
*/
void
body64(Biobuf *in, Biobuf *out)
{
int m, n;
uchar buf[3*18*54];
char obuf[3*18*54*2];
Bprint(out, "\n");
for(;;){
n = Bread(in, buf, sizeof(buf));
if(n < 0)
fatal("input error");
if(n == 0)
break;
m = enc64(obuf, sizeof(obuf), buf, n);
if(Bwrite(out, obuf, m) < 0)
fatal("output error");
}
lastchar = '\n';
}
/* pass message to sendmail, make sure body starts with a newline */
void
copy(Biobuf *in, Biobuf *out)
{
int n;
char buf[4*1024];
for(;;){
n = Bread(in, buf, sizeof(buf));
if(n < 0)
fatal("input error");
if(n == 0)
break;
if(Bwrite(out, buf, n) < 0)
fatal("output error");
}
}
void
attachment(Attach *a, Biobuf *out)
{
Biobuf *f;
char *p;
/* if it's already mime encoded, just copy */
if(strcmp(a->type, "mime") == 0){
f = Bopen(a->path, OREAD);
if(f == nil){
/*
* hack: give marshal time to stdin, before we kill it
* (for dead.letter)
*/
sleep(500);
postnote(PNPROC, pid, "interrupt");
sysfatal("opening %s: %r", a->path);
}
copy(f, out);
Bterm(f);
}
/* if it's not already mime encoded ... */
if(strcmp(a->type, "text/plain") != 0)
Bprint(out, "Content-Type: %s\n", a->type);
if(a->ainline)
Bprint(out, "Content-Disposition: inline\n");
else {
p = strrchr(a->path, '/');
if(p == nil)
p = a->path;
else
p++;
Bprint(out, "Content-Disposition: attachment; filename=%Z\n", p);
}
f = Bopen(a->path, OREAD);
if(f == nil){
/*
* hack: give marshal time to stdin, before we kill it
* (for dead.letter)
*/
sleep(500);
postnote(PNPROC, pid, "interrupt");
sysfatal("opening %s: %r", a->path);
}
/* dump our local 'From ' line when passing along mail messages */
if(strcmp(a->type, "message/rfc822") == 0){
p = Brdline(f, '\n');
if(strncmp(p, "From ", 5) != 0)
Bseek(f, 0, 0);
}
if(a->ctype->display)
body(f, out, strcmp(a->type, "text/plain") == 0);
else {
Bprint(out, "Content-Transfer-Encoding: base64\n");
body64(f, out);
}
Bterm(f);
}
char *ascwday[] =
{
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
char *ascmon[] =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
int
printdate(Biobuf *b)
{
int tz;
Tm *tm;
tm = localtime(time(0));
tz = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900 + tm->year,
tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
}
int
printfrom(Biobuf *b)
{
return Bprint(b, "From: %s\n", user);
}
int
printto(Biobuf *b, Addr *a)
{
int i;
if(Bprint(b, "To: %s", a->v) < 0)
return -1;
i = 0;
for(a = a->next; a != nil; a = a->next)
if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
return -1;
if(Bprint(b, "\n") < 0)
return -1;
return 0;
}
int
printcc(Biobuf *b, Addr *a)
{
int i;
if(a == nil)
return 0;
if(Bprint(b, "CC: %s", a->v) < 0)
return -1;
i = 0;
for(a = a->next; a != nil; a = a->next)
if(Bprint(b, "%s%s", ((i++ & 7) == 7)?",\n\t":", ", a->v) < 0)
return -1;
if(Bprint(b, "\n") < 0)
return -1;
return 0;
}
int
printsubject(Biobuf *b, char *subject)
{
return Bprint(b, "Subject: %U\n", subject);
}
int
printinreplyto(Biobuf *out, char *dir)
{
int fd, n;
char buf[256];
String *s = s_copy(dir);
s_append(s, "/messageid");
fd = open(s_to_c(s), OREAD);
s_free(s);
if(fd < 0)
return 0;
n = read(fd, buf, sizeof(buf)-1);
close(fd);
if(n <= 0)
return 0;
buf[n] = 0;
return Bprint(out, "In-Reply-To: %s\n", buf);
}
Attach*
mkattach(char *file, char *type, int ainline)
{
int n, pfd[2];
char *p;
char ftype[64];
Attach *a;
Ctype *c;
if(file == nil)
return nil;
if(access(file, 4) == -1){
fprint(2, "%s: %s can't read file\n", argv0, file);
return nil;
}
a = emalloc(sizeof(*a));
a->path = file;
a->next = nil;
a->type = type;
a->ainline = ainline;
a->ctype = nil;
if(type != nil){
for(c = ctype; ; c++)
if(strncmp(type, c->type, strlen(c->type)) == 0){
a->ctype = c;
break;
}
return a;
}
/* pick a type depending on extension */
p = strchr(file, '.');
if(p != nil)
p++;
/* check the builtin extensions */
if(p != nil){
for(c = ctype; c->ext != nil; c++)
if(strcmp(p, c->ext) == 0){
a->type = c->type;
a->ctype = c;
return a;
}
}
/* try the mime types file */
if(p != nil){
if(mimetypes == nil)
readmimetypes();
for(c = mimetypes; c != nil && c->ext != nil; c++)
if(strcmp(p, c->ext) == 0){
a->type = c->type;
a->ctype = c;
return a;
}
}
/* run file to figure out the type */
a->type = "application/octet-stream"; /* safest default */
if(pipe(pfd) < 0)
return a;
switch(fork()){
case -1:
break;
case 0:
close(pfd[1]);
close(0);
dup(pfd[0], 0);
close(1);
dup(pfd[0], 1);
execl("/bin/file", "file", "-m", file, nil);
exits(0);
default:
close(pfd[0]);
n = read(pfd[1], ftype, sizeof(ftype));
if(n > 0){
ftype[n-1] = 0;
a->type = estrdup(ftype);
}
close(pfd[1]);
waitpid();
break;
}
for(c = ctype; ; c++)
if(strncmp(a->type, c->type, strlen(c->type)) == 0){
a->ctype = c;
break;
}
return a;
}
char*
mkboundary(void)
{
int i;
char buf[32];
srand((time(0)<<16)|getpid());
strcpy(buf, "upas-");
for(i = 5; i < sizeof(buf)-1; i++)
buf[i] = 'a' + nrand(26);
buf[i] = 0;
return estrdup(buf);
}
/* copy types to two fd's */
static void
tee(int in, int out1, int out2)
{
int n;
char buf[8*1024];
while ((n = read(in, buf, sizeof buf)) > 0)
if (write(out1, buf, n) != n ||
write(out2, buf, n) != n)
break;
}
/* print the unix from line */
int
printunixfrom(int fd)
{
int tz;
Tm *tm;
tm = localtime(time(0));
tz = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
user,
ascwday[tm->wday], ascmon[tm->mon], tm->mday,
tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900 + tm->year);
}
char *specialfile[] =
{
"pipeto",
"pipefrom",
"L.mbox",
"forward",
"names"
};
/* return 1 if this is a special file */
static int
special(String *s)
{
int i;
char *p;
p = strrchr(s_to_c(s), '/');
if(p == nil)
p = s_to_c(s);
else
p++;
for(i = 0; i < nelem(specialfile); i++)
if(strcmp(p, specialfile[i]) == 0)
return 1;
return 0;
}
/* open the folder using the recipients account name */
static int
openfolder(char *rcvr)
{
int c, fd, scarey;
char *p;
Dir *d;
String *file;
file = s_new();
mboxpath("f", user, file, 0);
/* if $mail/f exists, store there, otherwise in $mail */
d = dirstat(s_to_c(file));
if(d == nil || d->qid.type != QTDIR){
scarey = 1;
file->ptr -= 1;
} else {
s_putc(file, '/');
scarey = 0;
}
free(d);
p = strrchr(rcvr, '!');
if(p != nil)
rcvr = p+1;
while(*rcvr && *rcvr != '@'){
c = *rcvr++;
if(c == '/')
c = '_';
s_putc(file, c);
}
s_terminate(file);
if(scarey && special(file)){
fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
s_free(file);
return -1;
}
fd = open(s_to_c(file), OWRITE);
if(fd < 0)
fd = create(s_to_c(file), OWRITE, 0660);
s_free(file);
return fd;
}
/* start up sendmail and return an fd to talk to it with */
int
sendmail(Addr *to, Addr *cc, int *pid, char *rcvr)
{
int ac, fd;
int pfd[2];
char **av, **v;
Addr *a;
String *cmd;
fd = -1;
if(rcvr != nil)
fd = openfolder(rcvr);
ac = 0;
for(a = to; a != nil; a = a->next)
ac++;
for(a = cc; a != nil; a = a->next)
ac++;
v = av = emalloc(sizeof(char*)*(ac+20));
ac = 0;
v[ac++] = "sendmail";
if(xflag)
v[ac++] = "-x";
if(rflag)
v[ac++] = "-r";
if(lbflag)
v[ac++] = "-#";
if(dflag)
v[ac++] = "-d";
for(a = to; a != nil; a = a->next)
v[ac++] = a->v;
for(a = cc; a != nil; a = a->next)
v[ac++] = a->v;
v[ac] = 0;
if(pipe(pfd) < 0)
fatal("%r");
switch(*pid = rfork(RFFDG|RFREND|RFPROC|RFENVG)){
case -1:
fatal("%r");
break;
case 0:
if(holding)
close(holding);
close(pfd[1]);
dup(pfd[0], 0);
close(pfd[0]);
if(rcvr != nil){
if(pipe(pfd) < 0)
fatal("%r");
switch(fork()){
case -1:
fatal("%r");
break;
case 0:
close(pfd[0]);
seek(fd, 0, 2);
printunixfrom(fd);
tee(0, pfd[1], fd);
write(fd, "\n", 1);
exits(0);
default:
close(fd);
close(pfd[1]);
dup(pfd[0], 0);
break;
}
}
if(replymsg != nil)
putenv("replymsg", replymsg);
cmd = mboxpath("pipefrom", login, s_new(), 0);
exec(s_to_c(cmd), av);
exec("/bin/myupassend", av);
exec("/bin/upas/send", av);
fatal("execing: %r");
break;
default:
if(rcvr != nil)
close(fd);
close(pfd[0]);
break;
}
return pfd[1];
}
/*
* start up pgp process and return an fd to talk to it with.
* its standard output will be the original fd, which goes to sendmail.
*/
int
pgpfilter(int *pid, int fd, int pgpflag)
{
int ac;
int pfd[2];
char **av, **v;
v = av = emalloc(sizeof(char*)*8);
ac = 0;
v[ac++] = "pgp";
v[ac++] = "-fat"; /* operate as a filter, generate text */
if(pgpflag & PGPsign)
v[ac++] = "-s";
if(pgpflag & PGPencrypt)
v[ac++] = "-e";
v[ac] = 0;
if(pipe(pfd) < 0)
fatal("%r");
switch(*pid = fork()){
case -1:
fatal("%r");
break;
case 0:
close(pfd[1]);
dup(pfd[0], 0);
close(pfd[0]);
dup(fd, 1);
close(fd);
/* add newline to avoid confusing pgp output with 822 headers */
write(1, "\n", 1);
exec("/bin/pgp", av);
fatal("execing: %r");
break;
default:
close(pfd[0]);
break;
}
close(fd);
return pfd[1];
}
/* wait for sendmail and pgp to exit; exit here if either failed */
char*
waitforsubprocs(void)
{
Waitmsg *w;
char *err;
err = nil;
while((w = wait()) != nil){
if(w->pid == pid || w->pid == pgppid)
if(w->msg[0] != 0)
err = estrdup(w->msg);
free(w);
}
if(err)
exits(err);
return nil;
}
int
cistrncmp(char *a, char *b, int n)
{
while(n-- > 0)
if(tolower(*a++) != tolower(*b++))
return -1;
return 0;
}
int
cistrcmp(char *a, char *b)
{
for(;;){
if(tolower(*a) != tolower(*b++))
return -1;
if(*a++ == 0)
break;
}
return 0;
}
static uchar t64d[256];
static char t64e[64];
static void
init64(void)
{
int c, i;
memset(t64d, 255, 256);
memset(t64e, '=', 64);
i = 0;
for(c = 'A'; c <= 'Z'; c++){
t64e[i] = c;
t64d[c] = i++;
}
for(c = 'a'; c <= 'z'; c++){
t64e[i] = c;
t64d[c] = i++;
}
for(c = '0'; c <= '9'; c++){
t64e[i] = c;
t64d[c] = i++;
}
t64e[i] = '+';
t64d['+'] = i++;
t64e[i] = '/';
t64d['/'] = i;
}
int
enc64(char *out, int lim, uchar *in, int n)
{
int i;
ulong b24;
char *start = out;
char *e = out + lim;
if(t64e[0] == 0)
init64();
for(i = 0; i < n/3; i++){
b24 = (*in++)<<16;
b24 |= (*in++)<<8;
b24 |= *in++;
if(out + 5 >= e)
goto exhausted;
*out++ = t64e[(b24>>18)];
*out++ = t64e[(b24>>12)&0x3f];
*out++ = t64e[(b24>>6)&0x3f];
*out++ = t64e[(b24)&0x3f];
if((i%18) == 17)
*out++ = '\n';
}
switch(n%3){
case 2:
b24 = (*in++)<<16;
b24 |= (*in)<<8;
if(out + 4 >= e)
goto exhausted;
*out++ = t64e[(b24>>18)];
*out++ = t64e[(b24>>12)&0x3f];
*out++ = t64e[(b24>>6)&0x3f];
break;
case 1:
b24 = (*in)<<16;
if(out + 4 >= e)
goto exhausted;
*out++ = t64e[(b24>>18)];
*out++ = t64e[(b24>>12)&0x3f];
*out++ = '=';
break;
case 0:
if((i%18) != 0)
*out++ = '\n';
*out = 0;
return out - start;
}
exhausted:
*out++ = '=';
*out++ = '\n';
*out = 0;
return out - start;
}
void
freealias(Alias *a)
{
freeaddrs(a->addr);
free(a);
}
void
freealiases(Alias *a)
{
Alias *next;
while(a != nil){
next = a->next;
freealias(a);
a = next;
}
}
void
freesendermaps(Sendermap *sm) {
Sendermap *s;
for(s = sm; s; s = sm) {
free(s->to);
free(s->from);
sm = s->next;
free(s);
}
}
/*
* map recipients on to senders i.e. when I send to 9fans@9fans.net always use mattmobile@proweb.co.uk
* (or rather look in /mail/box/maht/senders and see what maps I have set up
*/
Sendermap*
readsendermaps(void) {
Sendermap *s, *first, **l;
Sinstack *sp;
String *file, *line, *token;
first = nil;
file = s_new();
line = s_new();
token = s_new();
l = &first;
/* open and get length */
mboxpath("senders", login, file, 0);
sp = s_allocinstack(s_to_c(file));
if(sp == nil)
goto readsendermapsout;
while(s_rdinstack(sp, s_restart(line))!=nil) {
s_restart(line);
s = emalloc(sizeof(Sendermap));
s->to = s->from = nil;
if(s_parse(line, s_restart(token)) != 0)
s->to = strdup(s_to_c(token));
if(s_parse(line, s_restart(token)) != 0)
s->from = strdup(s_to_c(token));
if(s->to && s->from) {
s->next = nil;
*l = s;
l = &s->next;
} else
free(s);
}
s_freeinstack(sp);
readsendermapsout:
s_free(file);
s_free(line);
s_free(token);
return first;
}
/*
* read alias file
*/
Alias*
readaliases(void)
{
Addr *addr, **al;
Alias *a, **l, *first;
Sinstack *sp;
String *file, *line, *token;
static int already;
first = nil;
file = s_new();
line = s_new();
token = s_new();
/* open and get length */
mboxpath("names", login, file, 0);
sp = s_allocinstack(s_to_c(file));
if(sp == nil)
goto out;
l = &first;
/* read a line at a time. */
while(s_rdinstack(sp, s_restart(line))!=nil) {
s_restart(line);
a = emalloc(sizeof(Alias));
al = &a->addr;
while(s_parse(line, s_restart(token)) != 0) {
addr = emalloc(sizeof(Addr));
addr->v = strdup(s_to_c(token));
addr->next = 0;
*al = addr;
al = &addr->next;
}
if(a->addr == nil || a->addr->next == nil){
freealias(a);
continue;
}
a->next = nil;
*l = a;
l = &a->next;
}
s_freeinstack(sp);
out:
s_free(file);
s_free(line);
s_free(token);
return first;
}
Addr*
newaddr(char *name)
{
Addr *a;
a = emalloc(sizeof(*a));
a->next = nil;
a->v = estrdup(name);
if(a->v == nil)
sysfatal("%r");
return a;
}
/*
* expand personal aliases since the names are meaningless in
* other contexts
*/
Addr*
_expand(Addr *old, int *changedp)
{
Addr *first, *next, **l, *a;
Alias *al;
*changedp = 0;
first = nil;
l = &first;
for(;old != nil; old = next){
next = old->next;
for(al = aliases; al != nil; al = al->next){
if(strcmp(al->addr->v, old->v) == 0){
for(a = al->addr->next; a != nil; a = a->next){
*l = newaddr(a->v);
if(*l == nil)
sysfatal("%r");
l = &(*l)->next;
*changedp = 1;
}
break;
}
}
if(al != nil){
freeaddr(old);
continue;
}
*l = old;
old->next = nil;
l = &(*l)->next;
}
return first;
}
Addr*
rexpand(Addr *old)
{
int i, changed;
changed = 0;
for(i = 0; i < 32; i++){
old = _expand(old, &changed);
if(changed == 0)
break;
}
return old;
}
Addr*
unique(Addr *first)
{
Addr *a, **l, *x;
for(a = first; a != nil; a = a->next){
for(l = &a->next; *l != nil;){
if(strcmp(a->v, (*l)->v) == 0){
x = *l;
*l = x->next;
freeaddr(x);
} else
l = &(*l)->next;
}
}
return first;
}
Addr*
expand(int ac, char **av)
{
int i;
Addr *first, **l;
first = nil;
/* make a list of the starting addresses */
l = &first;
for(i = 0; i < ac; i++){
*l = newaddr(av[i]);
if(*l == nil)
sysfatal("%r");
l = &(*l)->next;
}
/* recurse till we don't change any more */
return unique(rexpand(first));
}
Addr*
concataddr(Addr *a, Addr *b)
{
Addr *oa;
if(a == nil)
return b;
oa = a;
for(; a->next; a=a->next)
;
a->next = b;
return oa;
}
void
freeaddr(Addr *ap)
{
free(ap->v);
free(ap);
}
void
freeaddrs(Addr *ap)
{
Addr *next;
for(; ap; ap=next) {
next = ap->next;
freeaddr(ap);
}
}
String*
s_copyn(char *s, int n)
{
return s_nappend(s_reset(nil), s, n);
}
/*
* fetch the next token from an RFC822 address string
* we assume the header is RFC822-conformant in that
* we recognize escaping anywhere even though it is only
* supposed to be in quoted-strings, domain-literals, and comments.
*
* i'd use yylex or yyparse here, but we need to preserve
* things like comments, which i think it tosses away.
*
* we're not strictly RFC822 compliant. we misparse such nonsense as
*
* To: gre @ (Grace) plan9 . (Emlin) bell-labs.com
*
* make sure there's no whitespace in your addresses and
* you'll be fine.
*/
enum {
Twhite,
Tcomment,
Twords,
Tcomma,
Tleftangle,
Trightangle,
Terror,
Tend,
};
// char *ty82[] = {"white", "comment", "words", "comma", "<", ">", "err", "end"};
#define ISWHITE(p) ((p)==' ' || (p)=='\t' || (p)=='\n' || (p)=='\r')
int
get822token(String **tok, char *p, char **pp)
{
int type, quoting;
char *op;
op = p;
switch(*p){
case '\0':
*tok = nil;
*pp = nil;
return Tend;
case ' ': /* get whitespace */
case '\t':
case '\n':
case '\r':
type = Twhite;
while(ISWHITE(*p))
p++;
break;
case '(': /* get comment */
type = Tcomment;
for(p++; *p && *p != ')'; p++)
if(*p == '\\') {
if(*(p+1) == '\0') {
*tok = nil;
return Terror;
}
p++;
}
if(*p != ')') {
*tok = nil;
return Terror;
}
p++;
break;
case ',':
type = Tcomma;
p++;
break;
case '<':
type = Tleftangle;
p++;
break;
case '>':
type = Trightangle;
p++;
break;
default: /* bunch of letters, perhaps quoted strings tossed in */
type = Twords;
quoting = 0;
for (; *p && (quoting ||
(!ISWHITE(*p) && *p != '>' && *p != '<' && *p != ',')); p++) {
if(*p == '"')
quoting = !quoting;
if(*p == '\\') {
if(*(p+1) == '\0') {
*tok = nil;
return Terror;
}
p++;
}
}
break;
}
if(pp)
*pp = p;
*tok = s_copyn(op, p-op);
return type;
}
/*
* expand local aliases in an RFC822 mail line
* add list of expanded addresses to to.
*/
Addr*
expandline(String **s, Addr *to)
{
int tok, inangle, hadangle, nword;
char *p;
Addr *na, *nto, *ap;
String *os, *ns, *stok, *lastword, *sinceword;
os = s_copy(s_to_c(*s));
p = strchr(s_to_c(*s), ':');
assert(p != nil);
p++;
ns = s_copyn(s_to_c(*s), p-s_to_c(*s));
stok = nil;
nto = nil;
/*
* the only valid mailbox namings are word
* and word* < addr >
* without comments this would be simple.
* we keep the following:
* lastword - current guess at the address
* sinceword - whitespace and comment seen since lastword
*/
lastword = s_new();
sinceword = s_new();
inangle = 0;
nword = 0;
hadangle = 0;
for(;;) {
stok = nil;
switch(tok = get822token(&stok, p, &p)){
default:
abort();
case Tcomma:
case Tend:
if(inangle)
goto Error;
if(nword != 1)
goto Error;
na = rexpand(newaddr(s_to_c(lastword)));
s_append(ns, na->v);
s_append(ns, s_to_c(sinceword));
for(ap=na->next; ap; ap=ap->next) {
s_append(ns, ", ");
s_append(ns, ap->v);
}
nto = concataddr(na, nto);
if(tok == Tcomma){
s_append(ns, ",");
s_free(stok);
}
if(tok == Tend)
goto Break2;
inangle = 0;
nword = 0;
hadangle = 0;
s_reset(sinceword);
s_reset(lastword);
break;
case Twhite:
case Tcomment:
s_append(sinceword, s_to_c(stok));
s_free(stok);
break;
case Trightangle:
if(!inangle)
goto Error;
inangle = 0;
hadangle = 1;
s_append(sinceword, s_to_c(stok));
s_free(stok);
break;
case Twords:
case Tleftangle:
if(hadangle)
goto Error;
if(tok != Tleftangle && inangle && s_len(lastword))
goto Error;
if(tok == Tleftangle) {
inangle = 1;
nword = 1;
}
s_append(ns, s_to_c(lastword));
s_append(ns, s_to_c(sinceword));
s_reset(sinceword);
if(tok == Tleftangle) {
s_append(ns, "<");
s_reset(lastword);
} else {
s_free(lastword);
lastword = stok;
}
if(!inangle)
nword++;
break;
case Terror: /* give up, use old string, addrs */
Error:
ns = os;
os = nil;
freeaddrs(nto);
nto = nil;
werrstr("rfc822 syntax error");
rfc822syntaxerror = 1;
goto Break2;
}
}
Break2:
s_free(*s);
s_free(os);
*s = ns;
nto = concataddr(nto, to);
return nto;
}
void
Bdrain(Biobuf *b)
{
char buf[8192];
while(Bread(b, buf, sizeof buf) > 0)
;
}
void
readmimetypes(void)
{
char *p;
char type[256];
char *f[6];
Biobuf *b;
static int alloced, inuse;
if(mimetypes == 0){
alloced = 256;
mimetypes = emalloc(alloced*sizeof(Ctype));
mimetypes[0].ext = "";
}
b = Bopen("/sys/lib/mimetype", OREAD);
if(b == nil)
return;
for(;;){
p = Brdline(b, '\n');
if(p == nil)
break;
p[Blinelen(b)-1] = 0;
if(tokenize(p, f, 6) < 4)
continue;
if (strcmp(f[0], "-") == 0 || strcmp(f[1], "-") == 0 ||
strcmp(f[2], "-") == 0)
continue;
if(inuse + 1 >= alloced){
alloced += 256;
mimetypes = erealloc(mimetypes, alloced*sizeof(Ctype));
}
snprint(type, sizeof(type), "%s/%s", f[1], f[2]);
mimetypes[inuse].type = estrdup(type);
mimetypes[inuse].ext = estrdup(f[0]+1);
mimetypes[inuse].display = !strcmp(type, "text/plain");
inuse++;
/* always make sure there's a terminator */
mimetypes[inuse].ext = 0;
}
Bterm(b);
}
char*
estrdup(char *x)
{
x = strdup(x);
if(x == nil)
fatal("memory");
return x;
}
void*
emalloc(int n)
{
void *x;
x = malloc(n);
if(x == nil)
fatal("%r");
return x;
}
void*
erealloc(void *x, int n)
{
x = realloc(x, n);
if(x == nil)
fatal("%r");
return x;
}
/*
* Formatter for %"
* Use double quotes to protect white space, frogs, \ and "
*/
enum
{
Qok = 0,
Qquote,
Qbackslash,
};
static int
needtoquote(Rune r)
{
if(r >= Runeself)
return Qquote;
if(r <= ' ')
return Qquote;
if(r=='\\' || r=='"')
return Qbackslash;
return Qok;
}
int
doublequote(Fmt *f)
{
int w, quotes;
char *s, *t;
Rune r;
s = va_arg(f->args, char*);
if(s == nil || *s == '\0')
return fmtstrcpy(f, "\"\"");
quotes = 0;
for(t = s; *t; t += w){
w = chartorune(&r, t);
quotes |= needtoquote(r);
}
if(quotes == 0)
return fmtstrcpy(f, s);
fmtrune(f, '"');
for(t = s; *t; t += w){
w = chartorune(&r, t);
if(needtoquote(r) == Qbackslash)
fmtrune(f, '\\');
fmtrune(f, r);
}
return fmtrune(f, '"');
}
int
rfc2047fmt(Fmt *fmt)
{
char *s, *p;
s = va_arg(fmt->args, char*);
if(s == nil)
return fmtstrcpy(fmt, "");
for(p=s; *p; p++)
if((uchar)*p >= 0x80)
goto hard;
return fmtstrcpy(fmt, s);
hard:
fmtprint(fmt, "=?utf-8?q?");
for(p = s; *p; p++){
if(*p == ' ')
fmtrune(fmt, '_');
else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' ||
(uchar)*p >= 0x80)
fmtprint(fmt, "=%.2uX", (uchar)*p);
else
fmtrune(fmt, (uchar)*p);
}
fmtprint(fmt, "?=");
return 0;
}
char*
mksubject(char *line)
{
char *p, *q;
static char buf[1024];
p = strchr(line, ':') + 1;
while(*p == ' ')
p++;
for(q = p; *q; q++)
if((uchar)*q >= 0x80)
goto hard;
return line;
hard:
snprint(buf, sizeof buf, "Subject: %U", p);
return buf;
}
char *
overridesender(char *user, Sendermap *sm, Addr *to) {
Addr* r ;
Sendermap *s;
for(r = to; r; r = to->next)
for(s = sm; s; s = s->next)
if(strcmp(r->v, s->to) == 0)
return s->from;
return user;
}
|