/*
* Plan B (mail2fs) mail box format.
*
* BUG: this does not reconstruct the
* raw text for attachments. So imap and others
* will be unable to access any attachment using upas/fs.
* unless all message has been saved into the mailbox by
* using -r flag with mail2fs.
*
* As an aid, we add the path to the message directory
* to the message body, so the user could build the path
* for any attachment and open it.
*/
#include "common.h"
#include <ctype.h>
#include <plumb.h>
#include <libsec.h>
#include "dat.h"
static int
hasbody(char *m)
{
char *p;
int i;
p = strstr(m, "\n\n");
if(p == nil)
return 0;
p += 2;
for(i = 0; p[i] != 0 && i < 5; i++)
if(p[i] != '\n')
return 1;
return 0;
}
static int
readmessage(Message *m, char *msg)
{
int i, n;
int fd;
char sdigest[SHA1dlen*2+1];
Dir *d;
char *raw;
char *buf;
char *name;
char *p;
buf = nil;
d = nil;
raw = nil;
name = smprint("%s/raw", msg);
if(name == nil)
return -1;
if(m->filename != nil)
s_free(m->filename);
m->filename = s_copy(name);
fd = open(name, OREAD);
if(fd < 0)
goto Fail;
d = dirfstat(fd);
if(d == nil)
goto Fail;
raw = malloc(d->length + 1);
if(raw == nil)
goto Fail;
n = readn(fd, raw, d->length);
free(d);
d = nil;
if(n <= 0)
goto Fail;
raw[n] = 0;
close(fd);
fd = -1;
if(hasbody(raw)){ /* assume raw has everything */
m->start = raw;
m->lim = m->end = raw+n;
}else{ /* assume raw has just headers */
p = strchr(raw, '\n');
if(p != nil)
*++p = 0;
if(strncmp(raw, "From ", 5) != 0)
goto Fail;
free(name);
name = smprint("%s/text", msg);
if(name == nil)
goto Fail;
fd = open(name, OREAD);
if(fd < 0)
goto Fail;
d = dirfstat(fd);
if(d == nil)
goto Fail;
/* allocate a few extra chars */
buf = malloc(strlen(raw) + d->length + strlen(msg) + 80);
if(buf == nil)
goto Fail;
strcpy(buf, raw);
p = buf+strlen(raw);
n = readn(fd, p, d->length);
if(n < 0)
goto Fail;
sprint(p+n, "\n[%s]\n", msg);
n += 2 + strlen(msg) + 2;
close(fd);
free(raw);
free(name);
free(d);
free(m->start);
m->start = buf;
m->lim = m->end = p+n;
}
*m->end = 0;
m->bend = m->rbend = m->end;
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;
Fail:
if(fd >= 0)
close(fd);
free(raw);
free(name);
free(buf);
free(d);
return -1;
}
/*
* Deleted messages are kept as spam instead.
*/
static void
archive(Message *m)
{
char *dir;
char *p;
char *nname;
Dir d;
dir = strdup(s_to_c(m->filename));
nname = nil;
if(dir == nil)
return;
p = strrchr(dir, '/');
if(p == nil)
goto Fail;
*p = 0;
p = strrchr(dir, '/');
if(p == nil)
goto Fail;
p++;
if(*p < '0' || *p > '9')
goto Fail;
nname = smprint("s.%s", p);
if(nname == nil)
goto Fail;
nulldir(&d);
d.name = nname;
dirwstat(dir, &d);
Fail:
free(dir);
free(nname);
}
int
purgembox(Mailbox *mb, int virtual)
{
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 > 0 && m->refs == 0){
if(m->inmbox){
newdels++;
/* virtual folders are
* virtual, we do not archive
*/
if(virtual == 0)
archive(m);
}
delmessage(mb, m);
}
}
return newdels;
}
static int
mustshow(char* name)
{
if(isdigit(name[0]))
return 1;
if(0 && name[0] == 'a' && name[1] == '.')
return 1;
if(0 && name[0] == 's' && name[1] == '.')
return 1;
return 0;
}
static int
readpbmessage(Mailbox *mb, char *msg, int doplumb)
{
Message *m, **l;
char *x;
m = newmessage(mb->root);
m->mallocd = 1;
m->inmbox = 1;
if(readmessage(m, msg) < 0){
delmessage(mb, m);
mb->root->subname--;
return -1;
}
for(l = &mb->root->part; *l != nil; l = &(*l)->next)
if(strcmp(s_to_c((*l)->filename), s_to_c(m->filename)) == 0)
if(*l != m){
if((*l)->deleted < 0)
(*l)->deleted = 0;
delmessage(mb, m);
mb->root->subname--;
return -1;
}
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;
if(doplumb)
mailplumb(mb, m, 0);
return 0;
}
static int
dcmp(Dir *a, Dir *b)
{
char *an;
char *bn;
an = a->name;
bn = b->name;
if(an[0] != 0 && an[1] == '.')
an += 2;
if(bn[0] != 0 && bn[1] == '.')
bn += 2;
return strcmp(an, bn);
}
static void
readpbvmbox(Mailbox *mb, int doplumb)
{
Dir *d;
char *data;
long sz;
char *ln, *p, *nln;
char *msg;
int fd;
int nr;
fd = open(mb->path, OREAD);
if(fd < 0){
fprint(2, "%s: %s: %r\n", argv0, mb->path);
return;
}
d = dirfstat(fd);
if(d == nil){
fprint(2, "%s: %s: %r\n", argv0, mb->path);
close(fd);
return;
}
sz = d->length;
free(d);
if(sz > 32 * 1024 * 1024){
sz = 32 * 1024 * 1024;
fprint(2, "%s: %s: bug: folder too big (>32M)\n", argv0, mb->path);
}
data = malloc(sz+1);
if(data == nil){
close(fd);
fprint(2, "%s: no memory\n", argv0);
return;
}
nr = readn(fd, data, sz);
close(fd);
if(nr < 0){
fprint(2, "%s: %s: %r\n", argv0, mb->path);
free(data);
return;
}
data[nr] = 0;
for(ln = data; *ln != 0; ln = nln){
nln = strchr(ln, '\n');
if(nln != nil)
*nln++ = 0;
else
nln = ln + strlen(ln);
p = strchr(ln , ' ');
if(p != nil)
*p = 0;
p = strchr(ln, '\t');
if(p != nil)
*p = 0;
p = strstr(ln, "/text");
if(p != nil)
*p = 0;
msg = smprint("/mail/box/%s/msgs/%s", user, ln);
if(msg == nil){
fprint(2, "%s: no memory\n", argv0);
continue;
}
readpbmessage(mb, msg, doplumb);
free(msg);
}
free(data);
}
static void
readpbmbox(Mailbox *mb, int doplumb)
{
int fd;
Dir *md;
Dir *cd;
char *cf;
int nd;
Dir *d;
int nmd;
char *msg;
char *month;
int i, j;
fd = open(mb->path, OREAD);
if(fd < 0){
fprint(2, "%s: %s: %r\n", argv0, mb->path);
return;
}
nd = dirreadall(fd, &d);
close(fd);
if(nd > 0)
qsort(d, nd, sizeof d[0], (int (*)(void*, void*))dcmp);
for(i = 0; i < nd; i++){
month = smprint("%s/%s", mb->path, d[i].name);
cf = smprint("%s.l", month);
if(month == nil || cf == nil) /* what do we do? */
break;
fd = open(month, OREAD);
if(fd < 0){
fprint(2, "%s: %s: %r\n", argv0, month);
free(month);
continue;
}
md = dirfstat(fd);
cd = dirstat(cf);
if(md != nil && (md->qid.type & QTDIR) != 0){
/*
* A 200909.l file is a cached listing for month 200909.
* use it instead if it's there and not out of date.
*
* But upas/fs is not ready for this. We can handle
* huge folders, but upas/fs is eager to read everything.
* Also, we are ignoring archived messages on the main
* mailbox but not on the virtual mboxes.
* Thus, we don't readpbvmbox for path cf.
*/
free(md);
md = nil;
nmd = dirreadall(fd, &md);
for(j = 0; j < nmd; j++)
if(mustshow(md[j].name)){
msg = smprint("%s/%s",month,md[j].name);
readpbmessage(mb, msg, doplumb);
free(msg);
}
}
close(fd);
free(month);
free(md);
free(cd);
free(cf);
md = nil;
}
free(d);
}
static char*
readmbox(Mailbox *mb, int doplumb, int virt)
{
int fd;
Dir *d;
static char err[128];
Message *m;
if(debug)
fprint(2, "read mbox %s\n", mb->path);
fd = open(mb->path, OREAD);
if(fd < 0){
errstr(err, sizeof(err));
return err;
}
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;
}
free(mb->d);
}
close(fd);
mb->d = d;
mb->vers++;
henter(PATH(0, Qtop), mb->name,
(Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
snprint(err, sizeof err, "reading '%s'", mb->path);
logmsg(err, nil);
for(m = mb->root->part; m != nil; m = m->next)
if(m->deleted == 0)
m->deleted = -1;
if(virt == 0)
readpbmbox(mb, doplumb);
else
readpbvmbox(mb, doplumb);
/*
* messages removed from the mbox; flag them to go.
*/
for(m = mb->root->part; m != nil; m = m->next)
if(m->deleted < 0 && doplumb){
m->inmbox = 0;
m->deleted = 1;
mailplumb(mb, m, 1);
}
logmsg("mbox read", nil);
return nil;
}
static char*
mbsync(Mailbox *mb, int doplumb)
{
char *rv;
rv = readmbox(mb, doplumb, 0);
purgembox(mb, 0);
return rv;
}
static char*
mbvsync(Mailbox *mb, int doplumb)
{
char *rv;
rv = readmbox(mb, doplumb, 1);
purgembox(mb, 1);
return rv;
}
char*
planbmbox(Mailbox *mb, char *path)
{
static char err[64];
char *list;
if(access(path, AEXIST) < 0)
return Enotme;
list = smprint("%s/list", path);
if(access(list, AEXIST) < 0){
free(list);
return Enotme;
}
free(list);
mb->sync = mbsync;
if(debug)
fprint(2, "planb mbox %s\n", path);
return nil;
}
char*
planbvmbox(Mailbox *mb, char *path)
{
static char err[64];
char buf[64];
int fd;
int nr;
int i;
fd = open(path, OREAD);
if(fd < 0)
return Enotme;
nr = read(fd, buf, sizeof(buf)-1);
close(fd);
if(nr < 7)
return Enotme;
buf[nr] = 0;
for(i = 0; i < 6; i++)
if(buf[i] < '0' || buf[i] > '9')
return Enotme;
if(buf[6] != '/')
return Enotme;
mb->sync = mbvsync;
if(debug)
fprint(2, "planb virtual mbox %s\n", path);
return nil;
}
|