/*
* Use upas/fs to convert a plan 9 mail box into
* a file tree with a plan b mail box (similar to Mh).
*
* Implementation is not as clean as it should be,
* A large part of the code is taken as-is from nedmail, trying to
* keep it the same, so that bugs are easy to fix both
* here and in nedmail (should they show up).
* We should get rid of Strings, for example.
*
*/
#include "common.h"
#include <ctype.h>
#include <libsec.h>
#include "util.h"
enum {
Defperm = 0666, /* none needs access for delivery, else could be 0620 */
};
typedef struct Message Message;
typedef struct Ctype Ctype;
typedef struct Cmd Cmd;
struct Message {
Message *next;
Message *prev;
Message *cmd;
Message *child;
Message *parent;
String *path;
int id;
int len;
int fileno; /* number of directory */
String *info;
char *from;
char *to;
char *cc;
char *replyto;
char *date;
char *subject;
char *type;
char *disposition;
char *filename;
char *sdigest;
char deleted;
char stored;
int saveit;
};
struct Ctype {
char *type;
char *ext;
int display;
char *plumbdest;
Ctype *next;
};
Ctype ctype[] = {
{ "text/plain", "txt", 1, 0 },
{ "text/html", "htm", 1, 0 },
{ "text/html", "html", 1, 0 },
{ "text/tab-separated-values", "tsv", 1, 0 },
{ "text/richtext", "rtx", 1, 0 },
{ "text/rtf", "rtf", 1, 0 },
{ "text", "txt", 1, 0 },
{ "message/rfc822", "msg", 0, 0 },
{ "image/bmp", "bmp", 0, "image" },
{ "image/jpeg", "jpg", 0, "image" },
{ "image/gif", "gif", 0, "image" },
{ "image/png", "png", 0, "image" },
{ "application/pdf", "pdf", 0, "postscript" },
{ "application/postscript", "ps", 0, "postscript" },
{ "application/", 0, 0, 0 },
{ "image/", 0, 0, 0 },
{ "multipart/", "mul", 0, 0 },
};
Rune altspc = L'·';
Rune altlparen = L'«';
Rune altrparen = L'»';
Rune altquote = L'´';
Rune altamp = L'';
Rune altslash = L'.';
enum
{
NARG= 32,
};
struct Cmd {
Message *msgs;
Message *(*f)(Cmd*, Message*);
int an;
char *av[NARG];
int delete;
};
Message top; /* top-level of upas mailbox */
int mboxfd; /* lock/seq fd for plan b mail box */
int debug;
int dry;
int cflag; /* create */
int aflag; /* archive */
int oflag; /* old mail */
int rflag; /* save complete raw message */
int mboxisfile;
char* digests; /* in-memory copy of digests */
char* mboxnm; /* name for mbox in upas */
void
tabs(int n)
{
static char t[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
if(n >= sizeof t)
n = sizeof t - 1; /* don't run off the end, truncate */
write(2, t, n);
}
void
printmessage(Message* m, int t)
{
Message* l;
tabs(t);
fprint(2, "message %d file %d %d bytes\n", m->id, m->fileno, m->len);
if(m->info != nil){
tabs(t);
fprint(2, "info %s\n", s_to_c(m->info));
}
if(m->from != nil){
tabs(t);
fprint(2, "from %s\n", m->from);
}
if(m->subject != nil){
tabs(t);
fprint(2, "subject %s\n", m->subject);
}
if(m->filename != nil){
tabs(t);
fprint(2, "file %s\n", m->filename);
}
for(l = m->child; l != nil ; l = l->next)
printmessage(l, t+1);
fprint(2, "\n");
}
String*
extendpath(String *dir, char *name)
{
String *path;
if(strcmp(s_to_c(dir), ".") == 0)
path = s_new();
else {
path = s_copy(s_to_c(dir));
s_append(path, "/");
}
s_append(path, name);
return path;
}
String*
file2string(String *dir, char *file)
{
String *s;
int fd, n, m;
s = extendpath(dir, file);
fd = open(s_to_c(s), OREAD);
s_grow(s, 512); /* avoid multiple reads on info files */
s_reset(s);
if(fd < 0)
return s;
for(;;){
n = s->end - s->ptr;
if(n == 0){
s_grow(s, 128);
continue;
}
m = read(fd, s->ptr, n);
if(m <= 0)
break;
s->ptr += m;
if(m < n)
break;
}
s_terminate(s);
close(fd);
return s;
}
int
lineize(char *s, char **f, int n)
{
int i;
for(i = 0; *s && i < n; i++){
f[i] = s;
s = strchr(s, '\n');
if(s == nil)
break;
*s++ = 0;
}
return i;
}
int
filelen(String *dir, char *file)
{
String *path;
Dir *d;
int rv;
path = extendpath(dir, file);
d = dirstat(s_to_c(path));
if(d == nil){
s_free(path);
return -1;
}
s_free(path);
rv = d->length;
free(d);
return rv;
}
int dir2message(Message *parent, int reverse);
Message*
file2message(Message *parent, char *name)
{
Message *m;
String *path;
char *f[10];
m = mallocz(sizeof(Message), 1);
if(m == nil)
return nil;
m->path = path = extendpath(parent->path, name);
m->fileno = atoi(name);
m->info = file2string(path, "info");
lineize(s_to_c(m->info), f, nelem(f));
m->from = f[0];
m->to = f[1];
m->cc = f[2];
m->replyto = f[3];
m->date = f[4];
m->subject = f[5];
m->type = f[6];
m->disposition = f[7];
m->filename = f[8];
m->len = filelen(path, "raw");
if(strstr(m->type, "multipart") != nil ||
strcmp(m->type, "message/rfc822") == 0)
dir2message(m, 0);
m->parent = parent;
return m;
}
int
dir2message(Message *parent, int reverse)
{
int i, n, fd, highest, newmsgs;
Dir *d;
Message *first, *last, *m;
fd = open(s_to_c(parent->path), OREAD);
if(fd < 0)
return -1;
/* count current entries */
first = parent->child;
highest = newmsgs = 0;
for(last = parent->child; last != nil && last->next != nil;
last = last->next)
if(last->fileno > highest)
highest = last->fileno;
if(last != nil)
if(last->fileno > highest)
highest = last->fileno;
n = dirreadall(fd, &d);
for(i = 0; i < n; i++){
if((d[i].qid.type & QTDIR) == 0)
continue;
if(atoi(d[i].name) <= highest)
continue;
m = file2message(parent, d[i].name);
if(m == nil)
break;
newmsgs++;
if(reverse){
m->next = first;
if(first != nil)
first->prev = m;
first = m;
} else {
if(first == nil)
first = m;
else
last->next = m;
m->prev = last;
last = m;
}
}
free(d);
close(fd);
parent->child = first;
/* renumber */
for(m = first; m != nil; m = m->next)
m->id = m->fileno;
return newmsgs;
}
void
compress(char *p)
{
char *np;
int last;
last = ' ';
for(np = p; *p; p++)
if(*p != '
' && (*p != ' ' || last != ' ')){
last = *p;
*np++ = last;
}
*np = 0;
}
int
printheader(Biobuf* bout, Message* m)
{
if(m->from == nil || *m->from == 0)
return 0;
Bprint(bout, "From: %s\n", m->from);
if(m->to != nil && *m->to)
Bprint(bout, "To: %s\n", m->to);
if(m->cc != nil && *m->cc)
Bprint(bout, "Cc: %s\n", m->cc);
if(m->replyto != nil && *m->replyto && m->from != nil && strcmp(m->replyto, m->from))
Bprint(bout, "Reply-To: %s\n", m->replyto);
if(m->date != nil && *m->date)
Bprint(bout, "Date: %s\n", m->date);
if(m->subject != nil && *m->subject)
Bprint(bout, "Subject: %s\n", m->subject);
return 1;
}
int
printpart(Biobuf* bout, String *s, char *part)
{
char buf[4096];
int n, fd, tot;
String *path;
path = extendpath(s, part);
fd = open(s_to_c(path), OREAD);
s_free(path);
if(fd < 0){
fprint(2, "!message disappeared\n");
return 0;
}
tot = 0;
while((n = read(fd, buf, sizeof(buf)-1)) > 0){
buf[n] = 0;
compress(buf);
n = strlen(buf);
if(Bwrite(bout, buf, n) <= 0)
break;
tot += n;
}
close(fd);
return tot;
}
/*
* Mail box format:
* dir/ (mail box)
* seq (file with last msg nb, +l)
* digest (file with mail digests)
* yyyymm/ (per month folder)
* raw (raw message; just headers)
* text (text as in reader)
* 1.x (first attach)
* 2.x (second attach)
* 3/ (third attach, a mail)
*/
int
mkmdir(char* mdir)
{
int fd, seqfd, nattempts;
char buf[200];
char* s;
Dir* d;
digests = strdup("");
d = dirstat(mdir);
s = smprint("%s/seq", mdir);
if(d == nil){
if(!cflag)
sysfatal("%s: not a mail dir", mdir);
if(debug)
fprint(2, "create mdir %s\n", mdir);
fd = create(mdir, OREAD, DMDIR|Defperm|0111);
if(fd < 0)
sysfatal("%s: %r", mdir);
close(fd);
fd = seqfd = create(s, ORDWR, Defperm|DMEXCL);
if(fd < 0)
sysfatal("%s: %r", s);
fprint(fd, "%11.11d", 0);
seek(fd, 0, 0);
free(s);
s = smprint("%s/digest", mdir);
fd = create(s, OREAD, Defperm|DMAPPEND|DMEXCL);
if(fd < 0)
sysfatal("%s: %r", s);
close(fd);
} else {
if((d->qid.type&QTDIR) == 0)
sysfatal("%s: not a directory", mdir);
free(d);
nattempts = 0;
while ((seqfd = open(s, ORDWR)) < 0){
rerrstr(buf, sizeof buf);
if(strstr(buf, "exclusive") == nil &&
strstr(buf, "already open") == nil)
break;
if(nattempts++ >= 10)
sysfatal("%s: can't get lock: %r", mdir);
sleep(500);
}
if (seqfd < 0)
seqfd = create(s, ORDWR, Defperm|DMEXCL);
if (seqfd < 0)
sysfatal("can't create %s: %r", s);
}
free(s);
return seqfd;
}
void
closemdir(int fd)
{
close(fd);
}
int
newseq(int startover)
{
int i, nr;
char buf[128];
if(mboxfd < 0)
return -1;
seek(mboxfd, 0, 0);
nr = read(mboxfd, buf, sizeof(buf)-1);
if(nr < 0)
return -1;
if(nr == 0)
i = 0;
else {
buf[nr] = 0;
i = atoi(buf);
}
i++;
if(startover && !oflag) /* start sequencing each month */
i = 1; /* unless we are adding old msgs */
seek(mboxfd, 0, 0);
fprint(mboxfd, "%11d ", i);
if(debug)
fprint(2, "newmsg %d\n", i);
return i;
}
char*
mksubmsg(int id, char* mdir)
{
char* fname;
int fd;
if(aflag)
fname = smprint("%s/a.%d", mdir, id);
else
fname = smprint("%s/%d", mdir, id);
if(debug)
fprint(2, "create %s\n", fname);
fd = create(fname, OREAD, Defperm|0111|DMDIR);
if(fd < 0){
fprint(2, "%s: %r\n", fname);
free(fname);
fname = nil;
} else
close(fd);
return fname;
}
static char*
datedir(void)
{
Tm *tm;
static char datebuf[30];
if(datebuf[0] == 0){
tm = localtime(time(0));
seprint(datebuf, datebuf+30, "%.4d%.2d",
tm->year+1900, tm->mon+1);
}
return datebuf;
}
static struct{
char *m;
char *n;
} mnames[] = {
{ "jan", "01"}, {"feb", "02"}, {"mar", "03"}, {"apr", "04"}, {"may", "05"}, {"jun", "06"},
{ "jul", "07"}, {"aug", "08"}, {"sep", "09"}, {"oct", "10"}, {"nov", "11"}, {"dec", "12"}
};
static char*
mname(char *m)
{
int i;
for(i = 0; i < nelem(mnames); i++)
if(cistrcmp(m, mnames[i].m))
return mnames[i].n;
return "00";
}
int
isnumeric(char *s)
{
while(*s){
if(!isdigit(*s))
return 0;
s++;
}
return 1;
}
static char*
mdatedir(Message *m)
{
int n;
char *s, *fld[10];
static char datebuf[30];
char *month, *year;
n = 0;
s= nil;
if(m->date != nil){
s = strdup(m->date);
n = tokenize(s, fld, 10);
}
if(n >= 5){
/* some dates have 19 Apr, some Apr 19 */
if(strlen(fld[1])<4 && isnumeric(fld[1]))
month = fld[2];
else
month = fld[1];
month = mname(month);
if(n > 5)
year = fld[5];
else
year = fld[4];
seprint(datebuf, datebuf+30, "%.4s%.2s", year, month);
}else
fprint(2, "%d fields [%s]\n", n, m->date);
free(s);
if(datebuf[0] != 0){
if(debug)
fprint(2, "olddir: %s\n", datebuf);
return datebuf; /* may be an old one if we failed. that's ok. */
}else
return datedir();
}
/*
* Create directory for the message, including the per-month
* directory.
* Date is today or that from the mail if oflag (old mail convert)
*/
char*
mkmsg(Message *m, char* mdir)
{
int fd;
char *d, *fname;
int newmonth;
d = smprint("%s/%s", mdir, oflag ? mdatedir(m) : datedir());
newmonth = 0;
if(access(d, AEXIST) < 0){
fd = create(d, OREAD, Defperm|0111|DMDIR);
if(fd < 0){
fprint(2, "%s: %s: %r\n", argv0, d);
free(d);
return nil;
}
close(fd);
newmonth=1;
}
fname = mksubmsg(newseq(newmonth), d);
free(d);
return fname;
}
Ctype*
findctype(Message *m)
{
int n, kid, pid, pfd[2];
char *p;
char ftype[128];
Ctype *a, *cp;
static Ctype nulltype = { "", 0, 0, 0 };
static Ctype bintype = { "application/octet-stream", "bin", 0, 0 };
for(cp = ctype; cp; cp = cp->next)
if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
return cp;
/*
* use file(1) for any unknown mime types
*/
if(pipe(pfd) < 0) {
fprint(2, "%s: out of pipes: %r\n", argv0);
return &bintype;
}
*ftype = 0;
switch(kid = fork()){
case -1:
break;
case 0:
close(pfd[1]);
close(0);
dup(pfd[0], 0);
close(1);
dup(pfd[0], 1);
execl("/bin/file", "file", "-m",
s_to_c(extendpath(m->path, "body")), nil);
sysfatal("no /bin/file: %r");
default:
close(pfd[0]);
n = read(pfd[1], ftype, sizeof(ftype));
if(n > 0)
ftype[n] = 0;
close(pfd[1]);
while ((pid = waitpid()) != -1 && pid != kid)
;
break;
}
if(*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
return &bintype;
*p++ = 0;
a = mallocz(sizeof(Ctype), 1);
a->type = strdup(ftype);
a->ext = strdup(p);
a->display = 0;
a->plumbdest = strdup(ftype);
for(cp = ctype; cp->next; cp = cp->next)
continue;
cp->next = a;
a->next = nil;
return a;
}
void
pipecmd(Cmd *c, Biobuf* bout, Message *m, char* part)
{
int i, fd, nr, kid, pid;
int pfd[2];
char *p, *e;
char cmd[128], buf[512];
char *av[4];
String *path;
path = extendpath(m->path, part);
fd = open(s_to_c(path), OREAD);
s_free(path);
if(fd < 0){ /* compatibility with older upas/fs */
path = extendpath(m->path, "raw");
fd = open(s_to_c(path), OREAD);
s_free(path);
}
if(fd < 0){
fprint(2, "!message disappeared\n");
return;
}
p = cmd;
e = cmd + sizeof cmd;
cmd[0] = 0;
for(i = 1; i < c->an; i++)
p = seprint(p, e, "%s ", c->av[i]);
av[0] = "rc";
av[1] = "-c";
av[2] = cmd;
av[3] = 0;
if(pipe(pfd)<0){
fprint(2, "%s: pipe: %r\n", argv0);
return;
}
kid = fork();
switch(kid){
case -1:
fprint(2, "%s: fork: %r\n", argv0);
break;
case 0:
dup(fd, 0);
close(fd);
close(pfd[0]);
dup(pfd[1], 1);
close(pfd[1]);
exec("/bin/rc", av);
sysfatal("no /bin/rc: %r");
default:
close(fd);
close(pfd[1]);
while ((nr = read(pfd[0], buf, sizeof buf)) > 0)
Bwrite(bout, buf, nr);
close(pfd[0]);
while ((pid = waitpid()) != -1 && pid != kid)
;
break;
}
}
int
printhtml(Biobuf* bout, Message *m)
{
Cmd c;
c.an = 3;
c.av[1] = "/bin/htmlfmt";
c.av[2] = "-a -l 80 -cutf-8";
pipecmd(&c, bout, m, "body");
return 0;
}
void
fcopy(char* d, char* s, int append)
{
int dfd, sfd, nr;
char buf[1024];
if(debug)
fprint(2, "create %s\n", d);
if(append){
dfd = open(d, OWRITE);
if(dfd >= 0)
seek(dfd, 0, 2);
}else
dfd = create(d, OWRITE, 0660 /* TODO 0640 */);
sfd = open(s, OREAD);
if(dfd < 0 || sfd < 0){
fprint(2, "%s: copying %s to %s: %r\n", argv0, s, d);
goto fail;
}
while ((nr = read(sfd, buf, sizeof buf)) > 0)
if(write(dfd, buf, nr) != nr){
fprint(2, "%s: copying %s to %s: %r\n", argv0, s, d);
break;
}
fail:
close(dfd);
close(sfd);
}
char*
importname(char* name)
{
int nr;
Rune r;
char *up, *p9name, *uname;
uname = name;
if(name == 0 ||
(strchr(name, ' ') == 0 && strchr(name, '(') == 0 &&
strchr(name, ')') == 0 && strchr(name, '&') == 0 &&
strchr(name, '\'') == 0 && strchr(name, '/') == 0))
return name;
p9name = malloc(strlen(name) * 3 + 1); /* worst case: all blanks + 0 */
up = p9name;
while(*name != 0){
nr = chartorune(&r, name);
if(r == ' ' || r == '\n' || r == '\r' || r == '\t')
r = altspc;
else if(r == '(')
r = altlparen;
else if(r == ')')
r = altrparen;
else if(r == '&')
r = altamp;
else if(r == '\'')
r = altquote;
else if(r == '/')
r = altslash;
else if(!isalpharune(r)){
free(p9name);
free(uname);
return nil;
}
up += runetochar(up, &r);
name += nr;
}
*up = 0;
free(uname);
return p9name;
}
char*
mfname(Message* m, Ctype* cp)
{
char *s, *r;
if(m->filename == nil || *m->filename == 0)
s = smprint("%d", m->id);
else
s = strdup(m->filename);
s = importname(s);
if(s == nil)
s = smprint("%d", m->id);
if(cp->ext != nil)
if(strstr(s, cp->ext) != s + strlen(s) - strlen(cp->ext)){
r = smprint("%s.%s",s, cp->ext);
free(s);
return r;
}
return s;
}
static void
addnl(char *f)
{
int fd;
fd = open(f, OWRITE);
if(fd < 0)
return;
seek(fd, 0, 2);
write(fd, "\n\n", 2);
close(fd);
}
static char*
xtabs(int n)
{
static char t[20];
memset(t, '\t', sizeof(t));
t[n] = 0;
return t;
}
/*
* Main conversion routine.
* Convert message m into a directory.
* It may recur for compound msgs, bout
* is the message text, nil in the first call.
*/
int
msg2fs(Biobuf* bout, Message* m, char* mdir)
{
char *dfname, *f, *fname, *msgdir;
Ctype *cp;
Message *nm;
String *s;
static int nest = 0;
nest++;
msgdir = nil;
if(m->parent == &top || strcmp(m->type, "message/rfc822") == 0){
if(m->parent == &top)
msgdir = mkmsg(m, mdir);
else
msgdir = mksubmsg(m->id, mdir);
if(msgdir == nil){
nest--;
return -1;
}
mdir = msgdir;
fname = smprint("%s/text", mdir);
if(debug){
if(m->parent == &top)
fprint(2, "%stoplevel\n", xtabs(nest));
fprint(2, "%screate %s\n", xtabs(nest), fname);
}
if(oflag)
print("%s\n", fname);
close(create(fname, OWRITE, 0666 /* TODO 0640 */));
bout = Bopen(fname, OWRITE);
if(bout == nil){
fprint(2, "%s: %s: %r\n", argv0, fname);
free(fname);
free(msgdir);
nest--;
return -1;
}
free(fname);
dfname = smprint("%s/raw", mdir);
fname = smprint("%s/unixheader", s_to_c(m->path));
fcopy(dfname, fname, 0);
free(fname);
/* save entire original in raw or save just headers? */
if(rflag){
fname = smprint("%s/raw", s_to_c(m->path));
fcopy(dfname, fname, 1);
free(fname);
}else{
fname = smprint("%s/rawheader", s_to_c(m->path));
fcopy(dfname, fname, 1);
addnl(dfname);
free(fname);
}
free(dfname);
}
if(bout != nil && printheader(bout, m) > 0)
Bprint(bout, "\n");
cp = findctype(m);
if(cp == nil)
fprint(2, "nil ctype\n");
else if(cp->display){
if(debug)
fprint(2, "%sbody\n", xtabs(nest));
if(strcmp(m->type, "text/html") == 0)
printhtml(bout, m);
else
printpart(bout, m->path, "body");
/* experiment: save also inline attachments */
if(m->saveit)
goto Save;
} else if(strcmp(m->type, "multipart/alternative") == 0){
if(debug){
fprint(2, "%smultipart/alternative\n", xtabs(nest));
for(nm = m->child; nm != nil; nm = nm->next)
fprint(2, "%s->%s\n", xtabs(nest+1), nm->type);
}
/* all parts should be equivalent, according to mime. however,
* Apple mail dares to place attachs into a part that is multipart,
* which is not equivalent to the first text part we find. So, we must
* prefer a multipart subpart to any other plain text or displayed part.
*/
for(nm = m->child; nm != nil; nm = nm->next)
if(strncmp(nm->type, "multipart/", 10) == 0)
break;
if(nm == nil)
for(nm = m->child; nm != nil; nm = nm->next){
cp = findctype(nm);
if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
break;
}
if(nm == nil)
for(nm = m->child; nm != nil; nm = nm->next){
cp = findctype(nm);
if(cp->display)
break;
}
if(nm != nil)
msg2fs(bout, nm, mdir);
} else if(strncmp(m->type, "multipart/", 10) == 0){
if(debug)
fprint(2, "%smultipart\n", xtabs(nest));
nm = m->child;
if(nm != nil){
/* always print first part */
msg2fs(bout, nm, mdir);
for(nm = nm->next; nm != nil; nm = nm->next){
fname = smprint("%s", s_to_c(nm->path));
s = s_copy(fname);
free(fname);
cp = findctype(nm);
if(nm->type != nil &&
strcmp(nm->type, "message/rfc822") == 0){
Bprint(bout, "\n— %d/\n", nm->id);
msg2fs(bout, nm, mdir);
} else {
f = mfname(nm, cp);
Bprint(bout,"\n!— %s\n\n",f);
free(f);
/* experiment: save also inline attachments */
nm->saveit = 1;
msg2fs(bout, nm, mdir);
}
s_free(s);
}
}
} else if(strcmp(m->type, "message/rfc822") == 0){
if(debug)
fprint(2, "%smessage/rfc822\n", xtabs(nest));
msg2fs(bout, m->child, mdir);
} else {
Save:
if(debug)
fprint(2, "%sattach\n", xtabs(nest));
f = mfname(m, cp);
dfname = smprint("%s/%s", mdir, f);
free(f);
if(cp->ext)
fname = smprint("%s/body.%s", s_to_c(m->path), cp->ext);
else
fname = smprint("%s/body", s_to_c(m->path));
fcopy(dfname, fname, 0);
free(dfname);
free(fname);
}
free(msgdir);
if(m->parent == &top || strcmp(m->type, "message/rfc822") == 0)
Bterm(bout);
nest--;
return 0;
}
/*
* Compute signature for mail.
* Msgs with same signature are considered dups and not
* added to the mail box.
* Returns 0 if message is already there.
*/
static char sdigest[MD5dlen*2+1];
char*
lastsigned(void)
{
return sdigest;
}
int
sign(Message* m)
{
int fd, i, n;
char *edigest, *odigests, *p;
uchar buf[256], digest[MD5dlen];
DigestState *s;
String *path;
s = md5((uchar*)"mail2fs", 7, nil, nil);
path = extendpath(m->path, "subject");
fd = open(s_to_c(path), OREAD);
s_free(path);
if(fd < 0)
goto fail;
n = read(fd, buf, sizeof(buf));
if(n < 0)
goto fail;
if(n > 0)
md5(buf, n, nil, s);
close(fd);
path = extendpath(m->path, "body");
fd = open(s_to_c(path), OREAD);
s_free(path);
if(fd < 0)
goto fail;
while((n = read(fd, buf, 256)) > 0)
md5(buf, n, nil, s);
md5((uchar*)"mail2fs", 7, digest, s);
close(fd);
p = sdigest;
edigest = sdigest + sizeof(sdigest);
for(i = 0; i < MD5dlen; i++)
p = seprint(p, edigest, "%02x", digest[i]);
*p = 0;
if(strstr(digests, sdigest)){
if(debug)
fprint(2, "%s: %s: message already in digests\n",
argv0, s_to_c(m->path));
return 0;
}
odigests = digests;
digests = smprint("%s%s\n", odigests, sdigest);
if(digests == nil)
sysfatal("not enough memory");
if(debug)
fprint(2, "%s: %s: digest %s\n",
argv0, s_to_c(m->path), sdigest);
free(odigests);
return 1;
fail:
close(fd);
fprint(2, "!message disappeared\n");
md5((uchar*)"mail2fs", 7, digest, s);
return 1;
}
void
upasfs(char* mbox)
{
int kid, pid;
rfork(RFNAMEG);
unmount(nil, "/mail/fs");
kid = fork();
switch(kid){
case -1:
break;
case 0:
execl("/bin/upas/fs", "upas/fs", "-p", "-f", mbox, nil);
sysfatal("no upas/fs: %r");
default:
while ((pid = waitpid()) != -1 && pid != kid)
;
break;
}
}
char*
openmbox(char* mbox)
{
char* d;
int fd;
mboxnm = strrchr(mbox, '/');
if (mboxnm != nil)
mboxnm++;
else
mboxnm = mbox;
d = smprint("/mail/fs/%s", mboxnm);
upasfs(mbox); /* run upas/fs on mbox file to produce a tree */
fd = open("/mail/fs/ctl", OWRITE);
if(fd < 0)
sysfatal("/mail/fs/ctl: %r");
if(fprint(fd, "open %s\n", mbox) < 0){
close(fd);
sysfatal("%s: open: %r", mbox);
}
close(fd);
return d;
}
void
closembox(void)
{
int fd, some;
char *s, *e;
char buf[1024];
Message *l;
fd = open("/mail/fs/ctl", OWRITE);
if(fd < 0)
sysfatal("/mail/fs/ctl: %r");
e = buf + sizeof buf;
s = seprint(buf, e, "delete %s", mboxnm);
some = 0;
for(l = top.child; l != nil; l = l->next)
if(l->deleted){
if(e - s < 20){
if(write(fd, buf, s-buf) != s-buf)
fprint(2, "%s: delete: %r\n", mboxnm);
s = seprint(buf, e, "delete %s", mboxnm);
some = 0;
}
some++;
s = seprint(s, e, " %d", l->id);
}
if(some && write(fd, buf, s-buf) != s-buf)
fprint(2, "%s: delete: %r\n", mboxnm);
close(fd);
}
int
mbox2fs(char* mbox, char* mdir)
{
int n, sfd;
char* fname;
Message *l;
String *ddir, *s;
if(chdir(mbox) < 0){
fprint(2, "%s: %s: not a directory: %r\n", argv0, mbox);
return -1;
}
top.path = s_copy(mbox);
ddir = s_copy(mdir);
s = file2string(ddir, "digest");
digests = strdup(s_to_c(s));
s_free(s);
s_free(ddir);
n = dir2message(&top, 0);
if(n < 0) {
fprint(2, "%s: dirmessage failed\n", argv0);
return -1;
}
fname = smprint("%s/digest", mdir);
sfd = open(fname, OWRITE);
free(fname);
if(sfd >= 0)
seek(sfd, 0, 2);
for(l = top.child; l != nil; l = l->next){
if(debug)
fprint(2, "converting %s/%d\n", mbox, l->id);
if(sign(l))
if(msg2fs(nil, l, mdir) < 0){
fprint(2, "mbox2fs: msg2fs errors\n");
return -1;
} else {
if(sfd >= 0)
fprint(sfd, "%s\n", lastsigned());
}
if(!dry)
l->deleted = 1;
}
if(sfd >= 0)
close(sfd);
return 0;
}
static void
usage(void)
{
fprint(2, "usage: %s [-acDnor] [-d mdir] [mbox]\n", argv0);
exits("usage");
}
void
main(int argc, char*argv[])
{
char *mbox = "mbox";
char *mdir, *top;
Ctype *cp;
top = smprint("/mail/box/%s", getuser());
mdir = smprint("%s/msgs", top);
ARGBEGIN{
case 'a':
aflag++;
break;
case 'c':
cflag++;
break;
case 'd':
free(mdir);
mdir = EARGF(usage());
mdir = cleanpath(mdir, top);
break;
case 'D':
debug++;
break;
case 'n':
dry++;
break;
case 'o':
oflag++;
break;
case 'r':
rflag++;
break;
default:
usage();
}ARGEND;
switch(argc){
case 1:
mbox = cleanpath(argv[0], top);
break;
case 0:
mbox = cleanpath(mbox, top);
break;
default:
usage();
}
for(cp = ctype; cp < ctype + nelem(ctype) - 1; cp++)
cp->next = cp + 1;
mbox = openmbox(mbox);
mboxfd = mkmdir(mdir);
mbox2fs(mbox, mdir);
closembox();
closemdir(mboxfd);
exits(nil);
}
|