#include "common.h"
#include <libsec.h>
#include "dat.h"
typedef struct {
Biobuf *in;
char *shift;
} Inbuf;
/*
* parse a Unix style header
*/
static int
memtotm(char *p, int n, Tm *t)
{
char buf[128];
if(n > sizeof buf - 1)
n = sizeof buf -1;
memcpy(buf, p, n);
buf[n] = 0;
return strtotm(buf, t);
}
static int
chkunix0(char *s, int n)
{
char *p;
Tm tm;
if(n > 256)
return -1;
if((p = memchr(s, ' ', n)) == nil)
return -1;
if(memtotm(p, n - (p - s), &tm) < 0)
return -1;
if(tm2sec(&tm) < 1000000)
return -1;
return 0;
}
static int
chkunix(char *s, int n)
{
int r;
r = chkunix0(s, n);
if(r == -1)
eprint("plan9: warning naked from [%.*s]\n", n, s);
return r;
}
static char*
parseunix(Message *m)
{
char *s, *p, *q;
int l;
Tm tm;
l = m->header - m->start;
m->unixheader = smprint("%.*s", l, m->start);
s = m->start + 5;
if((p = strchr(s, ' ')) == nil)
return s;
*p = 0;
m->unixfrom = strdup(s);
*p++ = ' ';
if(q = strchr(p, '\n'))
*q = 0;
if(strtotm(p, &tm) < 0)
return p;
if(q)
*q = '\n';
m->fileid = (uvlong)tm2sec(&tm) << 8;
return 0;
}
static void
addtomessage(Message *m, char *p, int n)
{
int i, len;
if(n == 0)
return;
/* add to message (+1 in malloc is for a trailing NUL) */
if(m->lim - m->end < n){
if(m->start != nil){
i = m->end - m->start;
len = (4*(i + n))/3;
m->start = erealloc(m->start, len + 1);
m->end = m->start + i;
} else {
len = 2*n;
m->start = emalloc(len + 1);
m->end = m->start;
}
m->lim = m->start + len;
*m->lim = 0;
}
memmove(m->end, p, n);
m->end += n;
*m->end = 0;
}
/*
* read in a single message
*/
static int
okmsg(Mailbox *mb, Message *m, Inbuf *b)
{
char e[ERRMAX], buf[128];
rerrstr(e, sizeof e);
if(strlen(e)){
if(fd2path(Bfildes(b->in), buf, sizeof buf) < 0)
strcpy(buf, "unknown mailbox");
eprint("plan9: error reading %s: %r\n", buf);
return -1;
}
if(m->end == m->start)
return -1;
if(m->end[-1] == '\n')
m->end--;
*m->end = 0;
m->size = m->end - m->start;
if(m->size >= Maxmsg)
return -1;
m->bend = m->rbend = m->end;
if(m->digest == 0)
digestmessage(mb, m);
return 0;
}
static char*
inbread(Inbuf *b)
{
if(b->shift)
return b->shift;
return b->shift = Brdline(b->in, '\n');
}
void
inbconsume(Inbuf *b)
{
b->shift = 0;
}
/*
* bug: very long line with From at the buffer break.
*/
static int
readmessage(Mailbox *mb, Message *m, Inbuf *b)
{
char *s, *n;
long l, state;
werrstr("");
state = 0;
for(;;){
s = inbread(b);
if(s == 0)
break;
n = s + (l = Blinelen(b->in)) - 1;
if(l >= 28 + 7 && n[0] == '\n')
if(strncmp(s, "From ", 5) == 0)
if(!chkunix(s + 5, l - 5))
if(++state == 2)
break;
if(state == 0)
return -1;
addtomessage(m, s, l);
inbconsume(b);
}
return okmsg(mb, m, b);
}
/* throw out deleted messages. return number of freshly deleted messages */
int
purgedeleted(Mailbox *mb)
{
Message *m, *next;
int newdels;
/* forget about what's no longer in the mailbox */
newdels = 0;
for(m = mb->root->part; m != nil; m = next){
next = m->next;
if(m->deleted && m->refs == 0){
if(m->inmbox)
newdels++;
delmessage(mb, m);
}
}
return newdels;
}
static void
mergemsg(Message *m, Message *x)
{
assert(m->start == 0);
m->mallocd = 1;
m->inmbox = 1;
m->lim = x->lim;
m->start = x->start;
m->end = x->end;
m->bend = x->bend;
m->rbend = x->rbend;
x->lim = 0;
x->start = 0;
x->end = 0;
x->bend = 0;
x->rbend = 0;
}
/*
* read in the mailbox and parse into messages.
*/
static char*
readmbox(Mailbox *mb, int doplumb, int *new, Mlock *lk)
{
char *p, *x, buf[Pathlen];
int nnew;
Biobuf *in;
Dir *d;
Inbuf b;
Message *m, **l;
static char err[ERRMAX];
l = &mb->root->part;
/*
* open the mailbox. If it doesn't exist, try the temporary one.
*/
retry:
in = Bopen(mb->path, OREAD);
if(in == nil){
errstr(err, sizeof(err));
if(strstr(err, "exist") != 0){
snprint(buf, sizeof buf, "%s.tmp", mb->path);
if(sysrename(buf, mb->path) == 0)
goto retry;
}
return err;
}
/*
* a new qid.path means reread the mailbox, while
* a new qid.vers means read any new messages
*/
d = dirfstat(Bfildes(in));
if(d == nil){
Bterm(in);
errstr(err, sizeof err);
return err;
}
if(mb->d != nil){
if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
*new = 0;
Bterm(in);
free(d);
return nil;
}
if(d->qid.path == mb->d->qid.path){
while(*l != nil)
l = &(*l)->next;
Bseek(in, mb->d->length, 0);
}
free(mb->d);
}
mb->d = d;
memset(&b, 0, sizeof b);
b.in = in;
b.shift = 0;
/* read new messages */
logmsg(nil, "reading %s", mb->path);
nnew = 0;
for(;;){
if(lk != nil)
syslockrefresh(lk);
m = newmessage(mb->root);
m->mallocd = 1;
m->inmbox = 1;
if(readmessage(mb, m, &b) < 0){
unnewmessage(mb, mb->root, m);
break;
}
/* merge mailbox versions */
while(*l != nil){
if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
if((*l)->start == nil){
logmsg(*l, "read indexed");
mergemsg(*l, m);
unnewmessage(mb, mb->root, m);
m = *l;
}else{
logmsg(*l, "duplicate");
m->inmbox = 1; /* remove it */
unnewmessage(mb, mb->root, m);
m = nil;
l = &(*l)->next;
}
break;
} else {
/* old mail no longer in box, mark deleted */
logmsg(*l, "disappeared");
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = Disappear;
l = &(*l)->next;
}
}
if(m == nil)
continue;
m->header = m->end;
if(x = strchr(m->start, '\n'))
m->header = x + 1;
if(p = parseunix(m))
sysfatal("%s:%lld naked From in body? [%s]", mb->path, seek(Bfildes(in), 0, 1), p);
m->mheader = m->mhend = m->header;
parse(mb, m, 0, 0);
if(m != *l && m->deleted != Dup){
logmsg(m, "new");
newcachehash(mb, m, doplumb);
putcache(mb, m);
nnew++;
}
/* chain in */
*l = m;
l = &m->next;
}
logmsg(nil, "mbox read");
/* whatever is left has been removed from the mbox, mark deleted */
while(*l != nil){
if(doplumb)
mailplumb(mb, *l, 1);
(*l)->inmbox = 0;
(*l)->deleted = Deleted;
l = &(*l)->next;
}
Bterm(in);
*new = nnew;
return nil;
}
static void
writembox(Mailbox *mb, Mlock *lk)
{
char buf[Pathlen];
int mode, errs;
Biobuf *b;
Dir *d;
Message *m;
snprint(buf, sizeof buf, "%s.tmp", mb->path);
/*
* preserve old files permissions, if possible
*/
mode = Mboxmode;
if(d = dirstat(mb->path)){
mode = d->mode & 0777;
free(d);
}
remove(buf);
b = sysopen(buf, "alc", mode);
if(b == 0){
eprint("plan9: can't write temporary mailbox %s: %r\n", buf);
return;
}
logmsg(nil, "writing new mbox");
errs = 0;
for(m = mb->root->part; m != nil; m = m->next){
if(lk != nil)
syslockrefresh(lk);
if(m->deleted)
continue;
logmsg(m, "writing");
if(Bwrite(b, m->start, m->end - m->start) < 0)
errs = 1;
if(Bwrite(b, "\n", 1) < 0)
errs = 1;
}
logmsg(nil, "wrote new mbox");
if(sysclose(b) < 0)
errs = 1;
if(errs){
eprint("plan9: error writing temporary mail file\n");
return;
}
remove(mb->path);
if(sysrename(buf, mb->path) < 0)
eprint("plan9: can't rename %s to %s: %r\n",
buf, mb->path);
if(mb->d != nil)
free(mb->d);
mb->d = dirstat(mb->path);
}
char*
plan9syncmbox(Mailbox *mb, int doplumb, int *new)
{
char *rv;
Mlock *lk;
lk = nil;
if(mb->dolock){
lk = syslock(mb->path);
if(lk == nil)
return "can't lock mailbox";
}
rv = readmbox(mb, doplumb, new, lk); /* interpolate */
if(purgedeleted(mb) > 0)
writembox(mb, lk);
if(lk != nil)
sysunlock(lk);
return rv;
}
void
plan9decache(Mailbox*, Message *m)
{
m->lim = 0;
}
/*
* look to see if we can open this mail box
*/
char*
plan9mbox(Mailbox *mb, char *path)
{
char buf[Pathlen];
static char err[Pathlen];
if(access(path, AEXIST) < 0){
errstr(err, sizeof err);
snprint(buf, sizeof buf, "%s.tmp", path);
if(access(buf, AEXIST) < 0)
return err;
}
mb->sync = plan9syncmbox;
mb->remove = localremove;
mb->rename = localrename;
mb->decache = plan9decache;
return nil;
}
|