#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
typedef struct Header Header;
struct Header {
char *type;
uintptr offset;
char *(*f)(Message*, Header*, char*, char*);
int len;
int str;
};
/* headers */
static char *ctype(Message*, Header*, char*, char*);
static char *cencoding(Message*, Header*, char*, char*);
static char *cdisposition(Message*, Header*, char*, char*);
static char *from822(Message*, Header*, char*, char*);
static char *replace822(Message*, Header*, char*, char*);
static char *concat822(Message*, Header*, char*, char*);
static char *copy822(Message*, Header*, char*, char*);
static char *ref822(Message*, Header*, char*, char*);
enum
{
Mhead = 11, /* offset of first mime header */
};
#define O(x) offsetof(Message, x)
static Header head[] =
{
"date:", O(date822), copy822, 0, 0,
"from:", O(from), from822, 0, 1,
"to:", O(to), concat822, 0, 1,
"sender:", O(sender), replace822, 0, 1,
"reply-to:", O(replyto), replace822, 0, 1,
"subject:", O(subject), copy822, 0, 1,
"cc:", O(cc), concat822, 0, 1,
"bcc:", O(bcc), concat822, 0, 1,
"in-reply-to:", O(inreplyto), replace822, 0, 1,
"message-id:", O(messageid), replace822, 0, 1,
"references:", ~0, ref822, 0, 0,
[Mhead] "content-type:", ~0, ctype, 0, 0,
"content-transfer-encoding:", ~0, cencoding, 0, 0,
"content-disposition:", ~0, cdisposition, 0, 0,
};
static Mailboxinit *boxinit[] = {
imap4mbox,
pop3mbox,
mdirmbox,
// planbmbox,
plan9mbox,
};
/*
* do we want to plumb flag changes?
*/
char*
syncmbox(Mailbox *mb, int doplumb)
{
char *s;
int n, d, y, a;
Message *m, *next;
if(semacquire(&mb->syncsem, 0) <= 0)
return nil;
a = mb->root->subname;
if(rdidxfile(mb, doplumb) == -2)
wridxfile(mb);
if(s = mb->sync(mb, doplumb, &n)){
semrelease(&mb->syncsem, 1);
return s;
}
d = 0;
y = 0;
for(m = mb->root->part; m; m = next){
next = m->next;
if(m->cstate & Cidxstale)
y++;
if(m->deleted == 0 || m->refs > 0)
continue;
if(mb->delete && m->inmbox && m->deleted & Deleted)
mb->delete(mb, m);
if(!m->inmbox){
delmessage(mb, m);
d++;
}
}
a = mb->root->subname - a;
assert(a >= 0);
if(n + d + y + a){
iprint("deleted: %d; new %d; stale %d\n", d, n, y);
logmsg(nil, "deleted: %d; new %d; stale %d", d, n, y);
wridxfile(mb);
}
if(n + d + y + a){
mb->vers++;
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
}
semrelease(&mb->syncsem, 1);
return nil;
}
/*
* not entirely clear where the locking should take place, if
* it is required.
*/
char*
mboxrename(char *a, char *b, int flags)
{
char f0[Pathlen + 4], f1[Pathlen + 4], *err, *p0, *p1;
Mailbox *mb;
snprint(f0, sizeof f0, "%s", a);
snprint(f1, sizeof f1, "%s", b);
err = newmbox(f0, nil, 0, &mb);
dprint("mboxrename %s %s -> %s\n", f0, f1, err);
if(!err && !mb->rename)
err = "rename not supported";
if(err)
goto done;
err = mb->rename(mb, f1, flags);
if(err)
goto done;
if(flags & Rtrunc)
/* we're comitted, so forget bailing */
err = newmbox(f0, nil, DMcreate, 0);
p0 = f0 + strlen(f0);
p1 = f1 + strlen(f1);
strcat(f0, ".idx");
strcat(f1, ".idx");
rename(f0, f1, 0);
*p0 = *p1 = 0;
strcat(f0, ".imp");
strcat(f1, ".imp");
rename(f0, f1, 0);
snprint(mb->path, sizeof mb->path, "%s", b);
hfree(PATH(0, Qtop), mb->name);
p0 = strrchr(mb->path, '/') + 1;
if(p0 == (char*)1)
p0 = mb->path;
snprint(mb->name, sizeof mb->name, "%s", p0);
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
done:
if(!mb)
return err;
qunlock(mb);
// if(err)
// mboxdecref(mb);
return err;
}
static void
initheaders(void)
{
int i;
static int already;
if(already)
return;
already = 1;
for(i = 0; i < nelem(head); i++)
head[i].len = strlen(head[i].type);
}
char*
newmbox(char *path, char *name, int flags, Mailbox **r)
{
char *p, *rv;
int i;
Mailbox *mb, **l;
initheaders();
mb = emalloc(sizeof *mb);
mb->idxsem = 1;
mb->syncsem = 1;
mb->flags = flags;
strncpy(mb->path, path, sizeof mb->path - 1);
p = name;
if(p == nil){
p = strrchr(path, '/');
if(p == nil)
p = path;
else
p++;
if(*p == 0){
free(mb);
return "bad mbox name";
}
}
strncpy(mb->name, p, sizeof mb->name - 1);
mb->idxread = genericidxread;
mb->idxwrite = genericidxwrite;
mb->idxinvalid = genericidxinvalid;
/* check for a mailbox type */
rv = Enotme; /* can't happen; shut compiler up */
for(i = 0; i < nelem(boxinit); i++)
if((rv = boxinit[i](mb, path)) != Enotme)
break;
if(rv){
free(mb);
return rv;
}
/* make sure name isn't taken */
qlock(&mbllock);
for(l = &mbl; *l != nil; l = &(*l)->next)
if(strcmp((*l)->name, mb->name) == 0){
if(strcmp(path, (*l)->path) == 0)
rv = nil;
else
rv = "mbox name in use";
if(mb->close)
mb->close(mb);
free(mb);
qunlock(&mbllock);
return rv;
}
/* always try locking */
mb->dolock = 1;
mb->refs = 1;
mb->next = nil;
mb->id = newid();
mb->root = newmessage(nil);
mb->mtree = mkavltree(mtreecmp);
*l = mb;
qunlock(&mbllock);
qlock(mb);
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
if(mb->ctl)
henter(PATH(mb->id, Qmbox), "ctl",
(Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
rv = syncmbox(mb, 0);
if(r)
*r = mb;
else
qunlock(mb);
return rv;
}
/* close the named mailbox */
void
freembox(char *name)
{
Mailbox **l, *mb;
qlock(&mbllock);
for(l=&mbl; *l != nil; l=&(*l)->next)
if(strcmp(name, (*l)->name) == 0){
mb = *l;
*l = mb->next;
mboxdecref(mb);
break;
}
hfree(PATH(0, Qtop), name);
qunlock(&mbllock);
}
void
syncallmboxes(void)
{
char *err;
Mailbox *m;
qlock(&mbllock);
for(m = mbl; m != nil; m = m->next)
if(err = syncmbox(m, 1))
eprint("syncmbox: %s\n", err);
qunlock(&mbllock);
}
char*
removembox(char *name, int flags)
{
int found;
Mailbox **l, *mb;
found = 0;
qlock(&mbllock);
for(l=&mbl; *l != nil; l=&(*l)->next)
if(strcmp(name, (*l)->path) == 0){
mb = *l;
*l = mb->next;
mb->flags |= ORCLOSE;
mb->rmflags = flags;
mboxdecref(mb);
found = 1;
break;
}
hfree(PATH(0, Qtop), name);
qunlock(&mbllock);
if(found == 0)
return "maibox not found";
return 0;
}
/*
* look for the date in the first Received: line.
* it's likely to be the right time zone (it's
* the local system) and in a convenient format.
*/
static int
rxtotm(Message *m, Tm *tm)
{
char *p, *q;
int r;
if(cistrncmp(m->header, "received:", 9))
return -1;
q = strchr(m->header, ';');
if(!q)
return -1;
p = q;
while((p = strchr(p, '\n')) != nil){
if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
break;
p++;
}
if(!p)
return -1;
*p = '\0';
r = strtotm(q + 1, tm);
*p = '\n';
return r;
}
Message*
gettopmsg(Mailbox *mb, Message *m)
{
while(!Topmsg(mb, m))
m = m->whole;
return m;
}
void
datesec(Mailbox *mb, Message *m)
{
char *s;
vlong v;
Tm tm;
if(m->fileid > 1000000ull<<8)
return;
if(m->unixfrom && strtotm(m->unixfrom, &tm) >= 0)
v = tm2sec(&tm);
else if(m->date822 && strtotm(m->date822, &tm) >= 0)
v = tm2sec(&tm);
else if(rxtotm(m, &tm) >= 0)
v = tm2sec(&tm);
else{
s = rtab[m->type].s;
logmsg(gettopmsg(mb, m), "%s:%s: datasec %s %s\n", mb->path,
m->whole? m->whole->name: "?",
m->name, s);
if(Topmsg(mb, m) || strcmp(s, "message/rfc822") == 0)
abort();
v = 0;
}
m->fileid = v<<8;
}
/*
* parse a message
*/
extern void sanemsg(Message*);
extern void sanembmsg(Mailbox*, Message*);
static Message*
haschild(Message *m, int i)
{
for(m = m->part; m && i; i--)
m = m->next;
if(m)
m->mimeflag = 0;
return m;
}
static void
parseattachments(Message *m, Mailbox *mb)
{
char *p, *x;
int i;
Message *nm, **l;
/* if there's a boundary, recurse... */
sanemsg(m);
// dprint("parseattachments %p %ld\n", m->start, m->end - m->start);
if(m->boundary != nil){
p = m->body;
nm = nil;
l = &m->part;
for(i = 0;;){
sanemsg(m);
x = strstr(p, m->boundary);
/* two sequential boundaries; ignore nil message */
if(nm && x == p){
p = strchr(x, '\n');
if(p == nil){
nm->rbend = nm->bend = nm->end = x;
sanemsg(nm);
break;
}
p = p + 1;
continue;
}
/* no boundary, we're done */
if(x == nil){
if(nm != nil){
nm->rbend = nm->bend = nm->end = m->bend;
sanemsg(nm);
if(nm->end == nm->start)
nm->mimeflag |= Mtrunc;
}
break;
}
/* boundary must be at the start of a line */
if(x != m->body && x[-1] != '\n'){
p = x + 1;
continue;
}
if(nm != nil)
{
nm->rbend = nm->bend = nm->end = x;
sanemsg(nm);}
x += strlen(m->boundary);
/* is this the last part? ignore anything after it */
if(strncmp(x, "--", 2) == 0)
break;
p = strchr(x, '\n');
if(p == nil)
break;
if((nm = haschild(m, i++)) == nil){
nm = newmessage(m);
*l = nm;
l = &nm->next;
}
nm->start = ++p;
assert(nm->ballocd == 0);
nm->mheader = nm->header = nm->body = nm->rbody = nm->start;
}
for(nm = m->part; nm != nil; nm = nm->next){
parse(mb, nm, 0, 1);
cachehash(mb, nm); /* botchy place for this */
}
return;
}
/* if we've got an rfc822 message, recurse... */
if(strcmp(rtab[m->type].s, "message/rfc822") == 0){
if((nm = haschild(m, 0)) == nil){
nm = newmessage(m);
m->part = nm;
}
assert(nm->ballocd == 0);
nm->start = nm->header = nm->body = nm->rbody = m->body;
nm->end = nm->bend = nm->rbend = m->bend;
if(nm->end == nm->start)
nm->mimeflag |= Mtrunc;
parse(mb, nm, 0, 0);
cachehash(mb, nm); /* botchy place for this */
}
}
void
parseheaders(Mailbox *mb, Message *m, int addfrom, int justmime)
{
char *p, *e, *o, *t, *s;
int i, i0, n;
uintptr a;
sanembmsg(mb, m); /* fails with pop but i want this debugging for now */
/* parse mime headers */
p = m->header;
i0 = 0;
if(justmime)
i0 = Mhead;
s = emalloc(2048);
e = s + 2048 - 1;
while((n = hdrlen(p, m->end)) != 0){
if(n > e - s){
s = erealloc(s, n);
e = s + n - 1;
}
rfc2047(s, e, p, n, 1);
p += n;
for(i = i0; i < nelem(head); i++)
if(!cistrncmp(s, head[i].type, head[i].len)){
a = head[i].offset;
if(a != ~0){
if(o = *(char**)((char*)m + a))
continue;
t = head[i].f(m, head + i, o, s);
*(char**)((char*)m + a) = t;
}else
head[i].f(m, head + i, 0, s);
break;
}
}
free(s);
sanembmsg(mb, m); /* fails with pop but i want this debugging for now */
/* the blank line isn't really part of the body or header */
if(justmime){
m->mhend = p;
m->hend = m->header;
} else{
m->hend = p;
m->mhend = m->header;
}
/*
* not all attachments have mime headers themselves.
*/
if(!m->mheader)
m->mhend = 0;
if(*p == '\n')
p++;
m->rbody = m->body = p;
if(!justmime)
datesec(mb, m);
/*
* only fake header for top-level messages for pop3 and imap4
* clients (those protocols don't include the unix header).
* adding the unix header all the time screws up mime-attached
* rfc822 messages.
*/
sanembmsg(mb, m); /* fails with pop but i want this debugging for now */
if(!addfrom && !m->unixfrom)
m->unixheader = nil;
else if(m->unixheader == nil){
if(m->unixfrom && strcmp(m->unixfrom, "???") != 0)
p = m->unixfrom;
else if(m->from)
p = m->from;
else
p = "???";
m->unixheader = smprint("From %s %Δ\n", p, m->fileid);
}
m->cstate |= Cheader;
sanembmsg(mb, m);
}
char*
promote(char *s)
{
return s? strdup(s): nil;
}
void
parsebody(Message *m, Mailbox *mb)
{
char *s;
int l;
Message *nm;
/* recurse */
s = rtab[m->type].s;
l = rtab[m->type].l;
if(l >= 10 && strncmp(s, "multipart/", 10) == 0)
parseattachments(m, mb);
else if(l == 14 && strcmp(s, "message/rfc822") == 0){
decode(m);
parseattachments(m, mb);
nm = m->part;
/* promote headers */
if(m->replyto == nil && m->from == nil && m->sender == nil){
m->from = promote(nm->from);
m->to = promote(nm->to);
m->date822 = promote(nm->date822);
m->sender = promote(nm->sender);
m->replyto = promote(nm->replyto);
m->subject = promote(nm->subject);
}
}else if(strncmp(rtab[m->type].s, "text/", 5) == 0)
sanemsg(m);
m->rawbsize = m->rbend - m->rbody;
m->cstate |= Cbody;
}
void
parse(Mailbox *mb, Message *m, int addfrom, int justmime)
{
sanemsg(m);
assert(m->end - m->start > 0 || m->mimeflag&Mtrunc && m->end - m->start == 0);
if((m->cstate & Cheader) == 0)
parseheaders(mb, m, addfrom, justmime);
parsebody(m, mb);
sanemsg(m);
}
static char*
skipwhite(char *p)
{
while(isascii(*p) && isspace(*p))
p++;
return p;
}
static char*
skiptosemi(char *p)
{
while(*p && *p != ';')
p++;
while(*p == ';' || (isascii(*p) && isspace(*p)))
p++;
return p;
}
static char*
getstring(char *p, char *s, char *e, int dolower)
{
int c;
p = skipwhite(p);
if(*p == '"'){
for(p++; (c = *p) != '"'; p++){
if(c == '\\')
c = *++p;
/*
* 821 says <x> after \ can be anything at all.
* we just don't care.
*/
if(c == 0)
break;
if(c < ' ')
continue;
if(dolower && c >= 'A' && c <= 'Z')
c += 0x20;
s = sputc(s, e, c);
}
if(*p == '"')
p++;
}else{
for(; (c = *p) && !isspace(c) && c != ';'; p++){
if(c == '\\')
c = *++p;
/*
* 821 says <x> after \ can be anything at all.
* we just don't care.
*/
if(c == 0)
break;
if(c < ' ')
continue;
if(dolower && c >= 'A' && c <= 'Z')
c += 0x20;
s = sputc(s, e, c);
}
}
*s = 0;
return p;
}
static void
setfilename(Message *m, char *p)
{
char buf[Pathlen];
free(m->filename);
getstring(p, buf, buf + sizeof buf - 1, 0);
m->filename = smprint("%s", buf);
for(p = m->filename; *p; p++)
if(*p == ' ' || *p == '\t' || *p == ';')
*p = '_';
}
static char*
rtrim(char *p)
{
char *e;
if(p == 0)
return p;
e = p + strlen(p) - 1;
while(e > p && isascii(*e) && isspace(*e))
*e-- = 0;
return p;
}
static char*
addr822(char *p, char **ac)
{
int n, c, space, incomment, addrdone, inanticomment, quoted;
char s[128+1], *ps, *e, *x, *list;
list = 0;
s[0] = 0;
ps = s;
e = s + sizeof s;
space = quoted = incomment = addrdone = inanticomment = 0;
n = 0;
for(; c = *p; p++){
if(!inanticomment && !quoted && !space && ps != s && c == ' '){
ps = sputc(ps, e, c);
space = 1;
continue;
}
space = 0;
if(!quoted && isspace(c) || c == '\r')
continue;
/* strings are always treated as atoms */
if(!quoted && c == '"'){
if(!addrdone && !incomment && !ac)
ps = sputc(ps, e, c);
for(p++; c = *p; p++){
if(ac && c == '"')
break;
if(!addrdone && !incomment)
ps = sputc(ps, e, c);
if(!quoted && *p == '"')
break;
if(*p == '\\')
quoted = 1;
else
quoted = 0;
}
if(c == 0)
break;
quoted = 0;
continue;
}
/* ignore everything in an expicit comment */
if(!quoted && c == '('){
incomment = 1;
continue;
}
if(incomment){
if(!quoted && c == ')')
incomment = 0;
quoted = 0;
continue;
}
/* anticomments makes everything outside of them comments */
if(!quoted && c == '<' && !inanticomment){
if(ac){
*ps-- = 0;
if(ps > s && *ps == ' ')
*ps = 0;
if(*ac){
*ac = smprint("%s, %s", x=*ac, s);
free(x);
}else
*ac = smprint("%s", s);
}
inanticomment = 1;
ps = s;
continue;
}
if(!quoted && c == '>' && inanticomment){
addrdone = 1;
inanticomment = 0;
continue;
}
/* commas separate addresses */
if(!quoted && c == ',' && !inanticomment){
*ps = 0;
addrdone = 0;
if(n++ != 0){
list = smprint("%s %s", x=list, s);
free(x);
}else
list = smprint("%s", s);
ps = s;
continue;
}
/* what's left is part of the address */
ps = sputc(ps, e, c);
/* quoted characters are recognized only as characters */
if(c == '\\')
quoted = 1;
else
quoted = 0;
}
if(ps > s){
*ps = 0;
if(n != 0){
list = smprint("%s %s", x=list, s);
free(x);
}else
list = smprint("%s", s);
}
return rtrim(list);
}
/*
* per rfc2822 §4.5.3, permit multiple to, cc and bcc headers by
* concatenating their values.
*/
static char*
concat822(Message*, Header *h, char *o, char *p)
{
char *s, *n;
p += strlen(h->type);
s = addr822(p, 0);
if(o){
n = smprint("%s %s", o, s);
free(s);
}else
n = s;
return n;
}
static char*
from822(Message *m, Header *h, char*, char *p)
{
if(m->ffrom)
free(m->ffrom);
m->from = 0;
return addr822(p + h->len, &m->ffrom);
}
static char*
replace822(Message *, Header *h, char*, char *p)
{
return addr822(p + h->len, 0);
}
static char*
copy822(Message*, Header *h, char*, char *p)
{
return rtrim(strdup(skipwhite(p + h->len)));
}
/*
* firefox, e.g. doesn't keep references unique
*/
static int
uniqarray(char **a, int n, int allocd)
{
int i, j;
for(i = 0; i < n; i++)
for(j = i + 1; j < n; j++)
if(strcmp(a[i], a[j]) == 0){
if(allocd)
free(a[j]);
memmove(a + j, a + j + 1, sizeof *a*(n - (j + 1)));
a[--n] = 0;
}
return n;
}
static char*
ref822(Message *m, Header *h, char*, char *p)
{
char **a, *s, *f[Nref + 1];
int i, j, k, n;
s = strdup(skipwhite(p + h->len));
n = getfields(s, f, nelem(f), 1, "<> \n\t\r,");
if(n > Nref)
n = Nref;
n = uniqarray(f, n, 0);
a = m->references;
for(i = 0; i < Nref; i++)
if(a[i] == 0)
break;
/*
* if there are too many references, drop from the beginning
* of the list.
*/
j = i + n - Nref;
if(j > 0){
if(j > Nref)
j = Nref;
for(k = 0; k < j; k++)
free(a[k]);
memmove(a, a + j, sizeof a[0]*(Nref - j));
memset(a + j, 0, Nref - j);
i -= j;
}
for(j = 0; j < n;)
a[i++] = strdup(f[j++]);
free(s);
uniqarray(a, i, 1);
return (char*)~0;
}
static int
isattribute(char **pp, char *attr)
{
char *p;
int n;
n = strlen(attr);
p = *pp;
if(cistrncmp(p, attr, n) != 0)
return 0;
p += n;
while(*p == ' ')
p++;
if(*p++ != '=')
return 0;
while(*p == ' ')
p++;
*pp = p;
return 1;
}
static char*
ctype(Message *m, Header *h, char*, char *p)
{
char buf[128], *e;
e = buf + sizeof buf - 1;
p = getstring(skipwhite(p + h->len), buf, e, 1);
m->type = newrefs(buf);
for(; *p; p = skiptosemi(p))
if(isattribute(&p, "boundary")){
p = getstring(p, buf, e, 0);
free(m->boundary);
m->boundary = smprint("--%s", buf);
} else if(cistrncmp(p, "multipart", 9) == 0){
/*
* the first unbounded part of a multipart message,
* the preamble, is not displayed or saved
*/
} else if(isattribute(&p, "name")){
if(m->filename == nil)
setfilename(m, p);
} else if(isattribute(&p, "charset")){
p = getstring(p, buf, e, 0);
lowercase(buf);
m->charset = newrefs(buf);
}
return (char*)~0;
}
static char*
cencoding(Message *m, Header *h, char*, char *p)
{
p = skipwhite(p + h->len);
if(cistrncmp(p, "base64", 6) == 0)
m->encoding = Ebase64;
else if(cistrncmp(p, "quoted-printable", 16) == 0)
m->encoding = Equoted;
return (char*)~0;
}
static char*
cdisposition(Message *m, Header *h, char*, char *p)
{
for(p = skipwhite(p + h->len); *p; p = skiptosemi(p))
if(cistrncmp(p, "inline", 6) == 0)
m->disposition = Dinline;
else if(cistrncmp(p, "attachment", 10) == 0)
m->disposition = Dfile;
else if(cistrncmp(p, "filename=", 9) == 0){
p += 9;
setfilename(m, p);
}
return (char*)~0;
}
ulong msgallocd;
ulong msgfreed;
Message*
newmessage(Message *parent)
{
static int id;
Message *m;
msgallocd++;
m = emalloc(sizeof *m);
dprint("newmessage %ld %p %p\n", msgallocd, parent, m);
m->disposition = Dnone;
m->type = newrefs("text/plain");
m->charset = newrefs("iso-8859-1");
m->cstate = Cidxstale;
m->flags = Frecent;
m->id = newid();
if(parent)
snprint(m->name, sizeof m->name, "%d", ++(parent->subname));
if(parent == nil)
parent = m;
m->whole = parent;
m->hlen = -1;
return m;
}
/* delete a message from a mailbox */
void
delmessage(Mailbox *mb, Message *m)
{
Message **l;
mb->vers++;
msgfreed++;
if(m->whole != m){
/* unchain from parent */
for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
;
if(*l != nil)
*l = m->next;
/* clear out of name lookup hash table */
if(m->whole->whole == m->whole)
hfree(PATH(mb->id, Qmbox), m->name);
else
hfree(PATH(m->whole->id, Qdir), m->name);
hfree(PATH(m->id, Qdir), "xxx"); /* sleezy speedup */
}
if(Topmsg(mb, m)){
if(m != mb->root)
mtreedelete(mb, m);
cachefree(mb, m, 1);
}
delrefs(m->type);
delrefs(m->charset);
idxfree(m);
while(m->part)
delmessage(mb, m->part);
free(m->unixfrom);
free(m->unixheader);
free(m->date822);
free(m->inreplyto);
free(m->boundary);
free(m->filename);
free(m);
}
void
unnewmessage(Mailbox *mb, Message *parent, Message *m)
{
assert(parent->subname > 0);
m->deleted = Dup;
delmessage(mb, m);
parent->subname -= 1;
}
/* mark messages (identified by path) for deletion */
char*
delmessages(int ac, char **av)
{
int i, needwrite;
Mailbox *mb;
Message *m;
qlock(&mbllock);
for(mb = mbl; mb != nil; mb = mb->next)
if(strcmp(av[0], mb->name) == 0){
qlock(mb);
break;
}
qunlock(&mbllock);
if(mb == nil)
return "no such mailbox";
needwrite = 0;
for(i = 1; i < ac; i++)
for(m = mb->root->part; m != nil; m = m->next)
if(strcmp(m->name, av[i]) == 0){
if(!m->deleted){
mailplumb(mb, m, 1);
needwrite = 1;
m->deleted = Deleted;
logmsg(m, "deleting");
}
break;
}
if(needwrite)
syncmbox(mb, 1);
qunlock(mb);
return 0;
}
char*
flagmessages(int argc, char **argv)
{
char *err, *rerr;
int i, needwrite;
Mailbox *mb;
Message *m;
if(argc%2)
return "bad flags";
qlock(&mbllock);
for(mb = mbl; mb; mb = mb->next)
if(strcmp(*argv, mb->name) == 0){
qlock(mb);
break;
}
qunlock(&mbllock);
if(mb == nil)
return "no such mailbox";
needwrite = 0;
rerr = 0;
for(i = 1; i < argc; i += 2)
for(m = mb->root->part; m; m = m->next)
if(strcmp(m->name, argv[i]) == 0){
if(err = modflags(mb, m, argv[i + 1]))
rerr = err;
else
needwrite = 1;
}
if(needwrite)
syncmbox(mb, 1);
qunlock(mb);
return rerr;
}
/*
* the following are called with the mailbox qlocked
*/
void
msgincref(Message *m)
{
m->refs++;
}
void
msgdecref(Mailbox *mb, Message *m)
{
assert(m->refs > 0);
m->refs--;
if(m->refs == 0){
if(m->deleted)
syncmbox(mb, 1);
else
putcache(mb, m);
}
}
/*
* the following are called with mbllock'd
*/
void
mboxincref(Mailbox *mb)
{
assert(mb->refs > 0);
mb->refs++;
}
static void
mbrmidx(char *path, int flags)
{
char buf[Pathlen];
snprint(buf, sizeof buf, "%s.idx", path);
vremove(buf);
if((flags & Rtrunc) == 0){
snprint(buf, sizeof buf, "%s.imp", path);
vremove(buf);
}
}
void
mboxdecref(Mailbox *mb)
{
assert(mb->refs > 0);
qlock(mb);
mb->refs--;
if(mb->refs == 0){
syncmbox(mb, 1);
delmessage(mb, mb->root);
if(mb->ctl)
hfree(PATH(mb->id, Qmbox), "ctl");
if(mb->close)
mb->close(mb);
if(mb->flags & ORCLOSE && mb->remove)
if(mb->remove(mb, mb->rmflags))
rmidx(mb->path, mb->rmflags);
free(mb->mtree);
free(mb->d);
free(mb);
} else
qunlock(mb);
}
/* just space over \r. sleezy but necessary for ms email. */
int
deccr(char *x, int len)
{
char *e;
e = x + len;
for(;;){
x = memchr(x, '\r', e - x);
if(x == nil)
break;
*x = ' ';
}
return len;
}
/*
* undecode message body
*/
void
decode(Message *m)
{
int i, len;
char *x;
if(m->decoded)
return;
switch(m->encoding){
case Ebase64:
len = m->bend - m->body;
i = (len*3)/4 + 1; /* room for max chars + null */
x = emalloc(i);
len = dec64((uchar*)x, i, m->body, len);
if(len == -1){
free(x);
break;
}
if(strncmp(rtab[m->type].s, "text/", 5) == 0)
len = deccr(x, len);
if(m->ballocd)
free(m->body);
m->body = x;
m->bend = x + len;
m->ballocd = 1;
break;
case Equoted:
len = m->bend - m->body;
x = emalloc(len + 2); /* room for null and possible extra nl */
len = decquoted(x, m->body, m->bend, 0);
if(m->ballocd)
free(m->body);
m->body = x;
m->bend = x + len;
m->ballocd = 1;
break;
default:
break;
}
m->decoded = 1;
}
/* convert x to utf8 */
void
convert(Message *m)
{
int len;
char *x;
/* don't convert if we're not a leaf, not text, or already converted */
if(m->converted)
return;
m->converted = 1;
if(m->part != nil || cistrncmp(rtab[m->type].s, "text", 4) != 0)
return;
len = xtoutf(rtab[m->charset].s, &x, m->body, m->bend);
if(len > 0){
if(m->ballocd)
free(m->body);
m->body = x;
m->bend = x + len;
m->ballocd = 1;
}
}
static int
hex2int(int x)
{
if(x >= '0' && x <= '9')
return x - '0';
if(x >= 'A' && x <= 'F')
return x - 'A' + 10;
if(x >= 'a' && x <= 'f')
return x - 'a' + 10;
return -1;
}
/*
* underscores are translated in 2047 headers (uscores=1)
* but not in the body (uscores=0)
*/
static char*
decquotedline(char *out, char *in, char *e, int uscores)
{
int c, soft;
/* dump trailing white space */
while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
e--;
/* trailing '=' means no newline */
if(*e == '='){
soft = 1;
e--;
} else
soft = 0;
while(in <= e){
c = (*in++) & 0xff;
switch(c){
case '_':
if(uscores){
*out++ = ' ';
break;
}
default:
*out++ = c;
break;
case '=':
c = hex2int(*in++)<<4;
c |= hex2int(*in++);
if(c != -1)
*out++ = c;
else{
*out++ = '=';
in -= 2;
}
break;
}
}
if(!soft)
*out++ = '\n';
*out = 0;
return out;
}
int
decquoted(char *out, char *in, char *e, int uscores)
{
char *p, *nl;
p = out;
while((nl = strchr(in, '\n')) != nil && nl < e){
p = decquotedline(p, in, nl, uscores);
in = nl + 1;
}
if(in < e)
p = decquotedline(p, in, e - 1, uscores);
/* make sure we end with a new line */
if(*(p - 1) != '\n'){
*p++ = '\n';
*p = 0;
}
return p - out;
}
char*
lowercase(char *p)
{
char *op;
int c;
for(op = p; c = *p; p++)
if(isupper(c))
*p = tolower(c);
return op;
}
/* translate latin1 directly since it fits neatly in utf */
static int
latin1toutf(char **out, char *in, char *e)
{
int n;
char *p;
Rune r;
n = 0;
for(p = in; p < e; p++)
if(*p & 0x80)
n++;
if(n == 0)
return 0;
n += e - in;
*out = p = malloc(n + 1);
if(p == nil)
return 0;
for(; in < e; in++){
r = (uchar)*in;
p += runetochar(p, &r);
}
*p = 0;
return p - *out;
}
/* translate any thing using the tcs program */
int
xtoutf(char *charset, char **out, char *in, char *e)
{
char *av[4], *p;
int totcs[2], fromtcs[2], n, len, sofar;
/* might not need to convert */
if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0)
return 0;
if(cistrcmp(charset, "iso-8859-1") == 0)
return latin1toutf(out, in, e);
len = e - in + 1;
sofar = 0;
*out = p = malloc(len + 1);
if(p == nil)
return 0;
av[0] = charset;
av[1] = "-f";
av[2] = charset;
av[3] = 0;
if(pipe(totcs) < 0)
goto error;
if(pipe(fromtcs) < 0){
close(totcs[0]); close(totcs[1]);
goto error;
}
switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
case -1:
close(fromtcs[0]); close(fromtcs[1]);
close(totcs[0]); close(totcs[1]);
goto error;
case 0:
close(fromtcs[0]); close(totcs[1]);
dup(fromtcs[1], 1);
dup(totcs[0], 0);
close(fromtcs[1]); close(totcs[0]);
dup(open("/dev/null", OWRITE), 2);
exec("/bin/tcs", av);
_exits("");
default:
close(fromtcs[1]); close(totcs[0]);
switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
case -1:
close(fromtcs[0]); close(totcs[1]);
goto error;
case 0:
close(fromtcs[0]);
while(in < e){
n = write(totcs[1], in, e - in);
if(n <= 0)
break;
in += n;
}
close(totcs[1]);
_exits("");
default:
close(totcs[1]);
for(;;){
n = read(fromtcs[0], &p[sofar], len - sofar);
if(n <= 0)
break;
sofar += n;
p[sofar] = 0;
if(sofar == len){
len += 1024;
p = realloc(p, len + 1);
if(p == nil)
goto error;
*out = p;
}
}
close(fromtcs[0]);
break;
}
break;
}
if(sofar == 0)
goto error;
return sofar;
error:
free(*out);
*out = nil;
return 0;
}
void *
emalloc(ulong n)
{
void *p;
p = mallocz(n, 1);
if(!p)
sysfatal("malloc %lud: %r", n);
setmalloctag(p, getcallerpc(&n));
return p;
}
void *
erealloc(void *p, ulong n)
{
if(n == 0)
n = 1;
p = realloc(p, n);
if(!p)
sysfatal("realloc %lud: %r", n);
setrealloctag(p, getcallerpc(&p));
return p;
}
int
myplumbsend(int fd, Plumbmsg *m)
{
char *buf;
int n;
buf = plumbpack(m, &n);
if(buf == nil)
return -1;
n = write(fd, buf, n);
free(buf);
return n;
}
void
mailplumb(Mailbox *mb, Message *m, int delete)
{
char buf[256], dbuf[SHA1dlen*2 + 1], len[10], date[30], *from, *subject;
int ai, cache;
Plumbmsg p;
Plumbattr a[7];
static int fd = -1;
cache = insurecache(mb, m) == 0; /* living dangerously if deleted */
subject = m->subject;
if(subject == nil)
subject = "";
if(m->from != nil)
from = m->from;
else if(m->unixfrom != nil)
from = m->unixfrom;
else
from = "";
sprint(len, "%lud", m->size);
if(biffing && !delete)
fprint(2, "[ %s / %s / %s ]\n", from, subject, len);
if(!plumbing)
goto out;
if(fd < 0)
fd = plumbopen("send", OWRITE);
if(fd < 0)
goto out;
p.src = "mailfs";
p.dst = "seemail";
p.wdir = "/mail/fs";
p.type = "text";
ai = 0;
a[ai].name = "filetype";
a[ai].value = "mail";
a[++ai].name = "sender";
a[ai].value = from;
a[ai-1].next = &a[ai];
a[++ai].name = "length";
a[ai].value = len;
a[ai-1].next = &a[ai];
a[++ai].name = "mailtype";
a[ai].value = delete? "delete": "new";
a[ai-1].next = &a[ai];
snprint(date, sizeof date, "%Δ", m->fileid);
a[++ai].name = "date";
a[ai].value = date;
a[ai-1].next = &a[ai];
if(m->digest){
snprint(dbuf, sizeof dbuf, "%A", m->digest);
a[++ai].name = "digest";
a[ai].value = dbuf;
a[ai-1].next = &a[ai];
}
a[ai].next = nil;
p.attr = a;
snprint(buf, sizeof buf, "%s/%s/%s",
mntpt, mb->name, m->name);
p.ndata = strlen(buf);
p.data = buf;
myplumbsend(fd, &p);
out:
if(cache)
msgdecref(mb, m);
}
/*
* count the number of lines in the body (for imap4)
*/
ulong
countlines(Message *m)
{
char *p;
ulong i;
i = 0;
for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p + 1, '\n'))
i++;
return i;
}
static char *logf = "fs";
void
logmsg(Message *m, char *fmt, ...)
{
char buf[256], *p, *e;
va_list args;
if(!lflag)
return;
e = buf + sizeof buf;
p = seprint(buf, e, "%s.%d: ", user, getpid());
if(m)
p = seprint(p, e, "from %s digest %A ",
m->from, m->digest);
va_start(args, fmt);
vseprint(p, e, fmt, args);
va_end(args);
if(Sflag)
fprint(2, "%s\n", buf);
syslog(Sflag, logf, "%s", buf);
}
void
iprint(char *fmt, ...)
{
char buf[256], *p, *e;
va_list args;
if(!iflag)
return;
e = buf + sizeof buf;
p = seprint(buf, e, "%s.%d: ", user, getpid());
va_start(args, fmt);
vseprint(p, e, fmt, args);
vfprint(2, fmt, args);
va_end(args);
syslog(Sflag, logf, "%s", buf);
}
void
eprint(char *fmt, ...)
{
char buf[256], buf2[256], *p, *e;
va_list args;
e = buf + sizeof buf;
p = seprint(buf, e, "%s.%d: ", user, getpid());
va_start(args, fmt);
vseprint(p, e, fmt, args);
e = buf2 + sizeof buf2;
p = seprint(buf2, e, "upas/fs: ");
vseprint(p, e, fmt, args);
va_end(args);
syslog(Sflag, logf, "%s", buf);
fprint(2, "%s", buf2);
}
/*
* convert an RFC822 date into a Unix style date
* for when the Unix From line isn't there (e.g. POP3).
* enough client programs depend on having a Unix date
* that it's easiest to write this conversion code once, right here.
*
* people don't follow RFC822 particularly closely,
* so we use strtotm, which is a bunch of heuristics.
*/
char*
date822tounix(Message *, char *s)
{
char *p, *q;
Tm tm;
if(strtotm(s, &tm) < 0)
return nil;
p = asctime(&tm);
if(q = strchr(p, '\n'))
*q = '\0';
return strdup(p);
}
|