406 lines
7.4 KiB
C
406 lines
7.4 KiB
C
#include "common.h"
|
|
#include <ctype.h>
|
|
#include <plumb.h>
|
|
#include <libsec.h>
|
|
#include "dat.h"
|
|
|
|
enum {
|
|
Buffersize = 64*1024
|
|
};
|
|
|
|
typedef struct Inbuf Inbuf;
|
|
struct Inbuf
|
|
{
|
|
int fd;
|
|
uchar *lim;
|
|
uchar *rptr;
|
|
uchar *wptr;
|
|
uchar data[Buffersize+7];
|
|
};
|
|
|
|
static void
|
|
addtomessage(Message *m, uchar *p, int n, int done)
|
|
{
|
|
int i, len;
|
|
|
|
/* add to message (+ 1 in malloc is for a trailing null) */
|
|
if(m->lim - m->end < n){
|
|
if(m->start != nil){
|
|
i = m->end-m->start;
|
|
if(done)
|
|
len = i + n;
|
|
else
|
|
len = (4*(i+n))/3;
|
|
m->start = erealloc(m->start, len + 1);
|
|
m->end = m->start + i;
|
|
} else {
|
|
if(done)
|
|
len = n;
|
|
else
|
|
len = 2*n;
|
|
m->start = emalloc(len + 1);
|
|
m->end = m->start;
|
|
}
|
|
m->lim = m->start + len;
|
|
}
|
|
|
|
memmove(m->end, p, n);
|
|
m->end += n;
|
|
}
|
|
|
|
/* */
|
|
/* read in a single message */
|
|
/* */
|
|
static int
|
|
readmessage(Message *m, Inbuf *inb)
|
|
{
|
|
int i, n, done;
|
|
uchar *p, *np;
|
|
char sdigest[SHA1dlen*2+1];
|
|
char tmp[64];
|
|
|
|
for(done = 0; !done;){
|
|
n = inb->wptr - inb->rptr;
|
|
if(n < 6){
|
|
if(n)
|
|
memmove(inb->data, inb->rptr, n);
|
|
inb->rptr = inb->data;
|
|
inb->wptr = inb->rptr + n;
|
|
i = read(inb->fd, inb->wptr, Buffersize);
|
|
if(i < 0){
|
|
/* if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
|
|
strcpy(tmp, "unknown mailbox"); jpc */
|
|
fprint(2, "error reading '%s': %r\n", tmp);
|
|
return -1;
|
|
}
|
|
if(i == 0){
|
|
if(n != 0)
|
|
addtomessage(m, inb->rptr, n, 1);
|
|
if(m->end == m->start)
|
|
return -1;
|
|
break;
|
|
}
|
|
inb->wptr += i;
|
|
}
|
|
|
|
/* look for end of message */
|
|
for(p = inb->rptr; p < inb->wptr; p = np+1){
|
|
/* first part of search for '\nFrom ' */
|
|
np = memchr(p, '\n', inb->wptr - p);
|
|
if(np == nil){
|
|
p = inb->wptr;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* if we've found a \n but there's
|
|
* not enough room for '\nFrom ', don't do
|
|
* the comparison till we've read in more.
|
|
*/
|
|
if(inb->wptr - np < 6){
|
|
p = np;
|
|
break;
|
|
}
|
|
|
|
if(strncmp((char*)np, "\nFrom ", 6) == 0){
|
|
done = 1;
|
|
p = np+1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* add to message (+ 1 in malloc is for a trailing null) */
|
|
n = p - inb->rptr;
|
|
addtomessage(m, inb->rptr, n, done);
|
|
inb->rptr += n;
|
|
}
|
|
|
|
/* if it doesn't start with a 'From ', this ain't a mailbox */
|
|
if(strncmp(m->start, "From ", 5) != 0)
|
|
return -1;
|
|
|
|
/* dump trailing newline, make sure there's a trailing null */
|
|
/* (helps in body searches) */
|
|
if(*(m->end-1) == '\n')
|
|
m->end--;
|
|
*m->end = 0;
|
|
m->bend = m->rbend = m->end;
|
|
|
|
/* digest message */
|
|
sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
|
|
for(i = 0; i < SHA1dlen; i++)
|
|
sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
|
|
m->sdigest = s_copy(sdigest);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* */
|
|
/* read in the mailbox and parse into messages. */
|
|
/* */
|
|
static char*
|
|
_readmbox(Mailbox *mb, int doplumb, Mlock *lk)
|
|
{
|
|
int fd;
|
|
String *tmp;
|
|
Dir *d;
|
|
static char err[128];
|
|
Message *m, **l;
|
|
Inbuf *inb;
|
|
char *x;
|
|
|
|
l = &mb->root->part;
|
|
|
|
/*
|
|
* open the mailbox. If it doesn't exist, try the temporary one.
|
|
*/
|
|
retry:
|
|
fd = open(mb->path, OREAD);
|
|
if(fd < 0){
|
|
errstr(err, sizeof(err));
|
|
if(strstr(err, "exist") != 0){
|
|
tmp = s_copy(mb->path);
|
|
s_append(tmp, ".tmp");
|
|
if(sysrename(s_to_c(tmp), mb->path) == 0){
|
|
s_free(tmp);
|
|
goto retry;
|
|
}
|
|
s_free(tmp);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* a new qid.path means reread the mailbox, while
|
|
* a new qid.vers means read any new messages
|
|
*/
|
|
d = dirfstat(fd);
|
|
if(d == nil){
|
|
close(fd);
|
|
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){
|
|
close(fd);
|
|
free(d);
|
|
return nil;
|
|
}
|
|
if(d->qid.path == mb->d->qid.path){
|
|
while(*l != nil)
|
|
l = &(*l)->next;
|
|
seek(fd, mb->d->length, 0);
|
|
}
|
|
free(mb->d);
|
|
}
|
|
mb->d = d;
|
|
mb->vers++;
|
|
henter(PATH(0, Qtop), mb->name,
|
|
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
|
|
|
|
inb = emalloc(sizeof(Inbuf));
|
|
inb->rptr = inb->wptr = inb->data;
|
|
inb->fd = fd;
|
|
|
|
/* read new messages */
|
|
snprint(err, sizeof err, "reading '%s'", mb->path);
|
|
logmsg(err, nil);
|
|
for(;;){
|
|
if(lk != nil)
|
|
syslockrefresh(lk);
|
|
m = newmessage(mb->root);
|
|
m->mallocd = 1;
|
|
m->inmbox = 1;
|
|
if(readmessage(m, inb) < 0){
|
|
delmessage(mb, m);
|
|
mb->root->subname--;
|
|
break;
|
|
}
|
|
|
|
/* merge mailbox versions */
|
|
while(*l != nil){
|
|
if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
|
|
/* matches mail we already read, discard */
|
|
logmsg("duplicate", *l);
|
|
delmessage(mb, m);
|
|
mb->root->subname--;
|
|
m = nil;
|
|
l = &(*l)->next;
|
|
break;
|
|
} else {
|
|
/* old mail no longer in box, mark deleted */
|
|
logmsg("disappeared", *l);
|
|
if(doplumb)
|
|
mailplumb(mb, *l, 1);
|
|
(*l)->inmbox = 0;
|
|
(*l)->deleted = 1;
|
|
l = &(*l)->next;
|
|
}
|
|
}
|
|
if(m == nil)
|
|
continue;
|
|
|
|
x = strchr(m->start, '\n');
|
|
if(x == nil)
|
|
m->header = m->end;
|
|
else
|
|
m->header = x + 1;
|
|
m->mheader = m->mhend = m->header;
|
|
parseunix(m);
|
|
parse(m, 0, mb, 0);
|
|
logmsg("new", m);
|
|
|
|
/* chain in */
|
|
*l = m;
|
|
l = &m->next;
|
|
if(doplumb)
|
|
mailplumb(mb, m, 0);
|
|
|
|
}
|
|
logmsg("mbox read", nil);
|
|
|
|
/* 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 = 1;
|
|
l = &(*l)->next;
|
|
}
|
|
|
|
close(fd);
|
|
free(inb);
|
|
return nil;
|
|
}
|
|
|
|
static void
|
|
_writembox(Mailbox *mb, Mlock *lk)
|
|
{
|
|
Dir *d;
|
|
Message *m;
|
|
String *tmp;
|
|
int mode, errs;
|
|
Biobuf *b;
|
|
|
|
tmp = s_copy(mb->path);
|
|
s_append(tmp, ".tmp");
|
|
|
|
/*
|
|
* preserve old files permissions, if possible
|
|
*/
|
|
d = dirstat(mb->path);
|
|
if(d != nil){
|
|
mode = d->mode&0777;
|
|
free(d);
|
|
} else
|
|
mode = MBOXMODE;
|
|
|
|
sysremove(s_to_c(tmp));
|
|
b = sysopen(s_to_c(tmp), "alc", mode);
|
|
if(b == 0){
|
|
fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
|
|
return;
|
|
}
|
|
|
|
logmsg("writing new mbox", nil);
|
|
errs = 0;
|
|
for(m = mb->root->part; m != nil; m = m->next){
|
|
if(lk != nil)
|
|
syslockrefresh(lk);
|
|
if(m->deleted)
|
|
continue;
|
|
logmsg("writing", m);
|
|
if(Bwrite(b, m->start, m->end - m->start) < 0)
|
|
errs = 1;
|
|
if(Bwrite(b, "\n", 1) < 0)
|
|
errs = 1;
|
|
}
|
|
logmsg("wrote new mbox", nil);
|
|
|
|
if(sysclose(b) < 0)
|
|
errs = 1;
|
|
|
|
if(errs){
|
|
fprint(2, "error writing temporary mail file\n");
|
|
s_free(tmp);
|
|
return;
|
|
}
|
|
|
|
sysremove(mb->path);
|
|
if(sysrename(s_to_c(tmp), mb->path) < 0)
|
|
fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
|
|
s_to_c(tmp), mb->path);
|
|
s_free(tmp);
|
|
if(mb->d != nil)
|
|
free(mb->d);
|
|
mb->d = dirstat(mb->path);
|
|
}
|
|
|
|
char*
|
|
plan9syncmbox(Mailbox *mb, int doplumb)
|
|
{
|
|
Mlock *lk;
|
|
char *rv;
|
|
|
|
lk = nil;
|
|
if(mb->dolock){
|
|
lk = syslock(mb->path);
|
|
if(lk == nil)
|
|
return "can't lock mailbox";
|
|
}
|
|
|
|
rv = _readmbox(mb, doplumb, lk); /* interpolate */
|
|
if(purgedeleted(mb) > 0)
|
|
_writembox(mb, lk);
|
|
|
|
if(lk != nil)
|
|
sysunlock(lk);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* */
|
|
/* look to see if we can open this mail box */
|
|
/* */
|
|
char*
|
|
plan9mbox(Mailbox *mb, char *path)
|
|
{
|
|
static char err[64];
|
|
String *tmp;
|
|
|
|
if(access(path, AEXIST) < 0){
|
|
errstr(err, sizeof(err));
|
|
tmp = s_copy(path);
|
|
s_append(tmp, ".tmp");
|
|
if(access(s_to_c(tmp), AEXIST) < 0){
|
|
s_free(tmp);
|
|
return err;
|
|
}
|
|
s_free(tmp);
|
|
}
|
|
|
|
mb->sync = plan9syncmbox;
|
|
return nil;
|
|
}
|