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");
}
/* 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, Addr **ccp, Addr **bccp, Attach **att, int strict)
{
int i, seen, hdrtype;
Addr *to, *cc, *bcc;
String *s, *sline;
char *p;
s = s_new();
to = cc = bcc = nil;
sline = nil;
hdrtype = -1;
seen = 0;
for(;;) {
if((p = Brdline(in, '\n')) != nil) {
seen = 1;
p[Blinelen(in)-1] = 0;
/* process the current header, it's all been read */
if(sline) {
switch(hdrtype){
default:
Addhdr:
s_append(s, s_to_c(sline));
s_append(s, "\n");
break;
case Hto:
if(top)
to = expandline(&sline, to);
goto Addhdr;
case Hcc:
if(ccp)
cc = expandline(&sline, cc);
goto Addhdr;
case Hbcc:
if(bccp)
bcc = expandline(&sline, bcc);
break;
case Hsubject:
s_append(s, mksubject(s_to_c(sline)));
s_append(s, "\n");
break;
case Hattach:
case Hinclude:
if(att == nil)
break;
*att = mkattach(hdrval(s_to_c(sline)), nil, hdrtype == Hinclude);
if(*att != nil)
att = &(*att)->next;
break;
}
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';
}
/* 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");
}
}
/*
* 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.
*/
static int
enc64x18(char *out, int lim, uchar *in, int n)
{
int m, mm, nn;
for(nn = 0; n > 0; n -= m, nn += mm){
m = 18 * 3;
if(m > n)
m = n;
nn++; /* \n */
assert(nn < lim);
mm = enc64(out, lim - nn, in, m);
assert(mm > 0);
in += m;
out += mm;
*out++ = '\n';
}
return nn;
}
void
body64(Biobuf *in, Biobuf *out)
{
int m, n;
uchar buf[3*18*54];
char obuf[3*18*54*2];
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);
}
/* 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;
}
/* start up sendmail and return an fd to talk to it with */
int
sendmail(Addr *to, Addr *cc, Addr *bcc, int *pid, char *rcvr)
{
int ac, fd, pfd[2];
char **v, cmd[Pathlen];
Addr *a;
Biobuf *b;
ac = 0;
for(a = to; a != nil; a = a->next)
ac++;
for(a = cc; a != nil; a = a->next)
ac++;
for(a = bcc; a != nil; a = a->next)
ac++;
v = 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;
for(a = bcc; 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]);
/*
* 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);
/*
* 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;
}
/*
* 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,
};
/*
* 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;
}