#include "common.h"
#include <auth.h>
#include <fcall.h>
#include <libsec.h>
#include "dat.h"
enum
{
OPERM = 0x3, // mask of all permission types in open mode
};
typedef struct Fid Fid;
struct Fid
{
Qid qid;
short busy;
short open;
int fid;
Fid *next;
Mailbox *mb;
Message *m;
Message *mtop; // top level message
//finger pointers to speed up reads of large directories
long foff; // offset/DIRLEN of finger
Message *fptr; // pointer to message at off
int fvers; // mailbox version when finger was saved
};
ulong path; // incremented for each new file
Fid *fids;
int mfd[2];
char user[Elemlen];
int messagesize = 4*1024*IOHDRSZ;
uchar mdata[8*1024*IOHDRSZ];
uchar mbuf[8*1024*IOHDRSZ];
Fcall thdr;
Fcall rhdr;
int fflg;
char *mntpt;
int biffing;
int plumbing = 1;
QLock mbllock;
Mailbox *mbl;
Fid *newfid(int);
void error(char*);
void io(void);
void *erealloc(void*, ulong);
void *emalloc(ulong);
void usage(void);
void reader(void);
int readheader(Message*, char*, int, int);
int cistrncmp(char*, char*, int);
int tokenconvert(String*, char*, int);
String* stringconvert(String*, char*, int);
void post(char*, char*, int);
char *rflush(Fid*), *rauth(Fid*),
*rattach(Fid*), *rwalk(Fid*),
*ropen(Fid*), *rcreate(Fid*),
*rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
*rremove(Fid*), *rstat(Fid*), *rwstat(Fid*),
*rversion(Fid*);
char *(*fcalls[])(Fid*) = {
[Tflush] rflush,
[Tversion] rversion,
[Tauth] rauth,
[Tattach] rattach,
[Twalk] rwalk,
[Topen] ropen,
[Tcreate] rcreate,
[Tread] rread,
[Twrite] rwrite,
[Tclunk] rclunk,
[Tremove] rremove,
[Tstat] rstat,
[Twstat] rwstat,
};
char Eperm[] = "permission denied";
char Enotdir[] = "not a directory";
char Enoauth[] = "upas/fs: authentication not required";
char Enotexist[] = "file does not exist";
char Einuse[] = "file in use";
char Eexist[] = "file exists";
char Enotowner[] = "not owner";
char Eisopen[] = "file already open for I/O";
char Excl[] = "exclusive use file already open";
char Ename[] = "illegal name";
char Ebadctl[] = "unknown control message";
char *dirtab[] =
{
[Qdir] ".",
[Qbody] "body",
[Qbcc] "bcc",
[Qcc] "cc",
[Qdate] "date",
[Qdigest] "digest",
[Qdisposition] "disposition",
[Qfilename] "filename",
[Qfrom] "from",
[Qheader] "header",
[Qinfo] "info",
[Qinreplyto] "inreplyto",
[Qlines] "lines",
[Qmimeheader] "mimeheader",
[Qmessageid] "messageid",
[Qraw] "raw",
[Qrawunix] "rawunix",
[Qrawbody] "rawbody",
[Qrawheader] "rawheader",
[Qreplyto] "replyto",
[Qsender] "sender",
[Qsubject] "subject",
[Qto] "to",
[Qtype] "type",
[Qunixdate] "unixdate",
[Qunixheader] "unixheader",
[Qctl] "ctl",
[Qmboxctl] "ctl",
};
enum
{
Hsize= 1277,
};
Hash *htab[Hsize];
int debug;
int fflag;
int logging;
void
usage(void)
{
fprint(2, "usage: %s [-b -m mountpoint]\n", argv0);
exits("usage");
}
void
notifyf(void *a, char *s)
{
USED(a);
if(strncmp(s, "interrupt", 9) == 0)
noted(NCONT);
noted(NDFLT);
}
void
main(int argc, char *argv[])
{
int p[2], std, nodflt;
char maildir[128];
char mbox[128];
char *mboxfile, *err;
char srvfile[64];
int srvpost;
rfork(RFNOTEG);
mntpt = nil;
fflag = 0;
mboxfile = nil;
std = 0;
nodflt = 0;
srvpost = 0;
ARGBEGIN{
case 'b':
biffing = 1;
break;
case 'f':
fflag = 1;
mboxfile = ARGF();
break;
case 'm':
mntpt = ARGF();
break;
case 'd':
debug = 1;
break;
case 'p':
plumbing = 0;
break;
case 's':
srvpost = 1;
break;
case 'l':
logging = 1;
break;
case 'n':
nodflt = 1;
break;
default:
usage();
}ARGEND
if(pipe(p) < 0)
error("pipe failed");
mfd[0] = p[0];
mfd[1] = p[0];
notify(notifyf);
strcpy(user, getuser());
if(mntpt == nil){
snprint(maildir, sizeof(maildir), "/mail/fs");
mntpt = maildir;
}
if(mboxfile == nil && !nodflt){
snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user);
mboxfile = mbox;
std = 1;
}
if(debug)
fmtinstall('F', fcallfmt);
if(mboxfile != nil){
err = newmbox(mboxfile, "mbox", std);
if(err != nil)
sysfatal("opening mailbox: %s", err);
}
switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
case -1:
error("fork");
case 0:
henter(PATH(0, Qtop), dirtab[Qctl],
(Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
close(p[1]);
io();
postnote(PNGROUP, getpid(), "die yankee pig dog");
break;
default:
close(p[0]); /* don't deadlock if child fails */
if(srvpost){
sprint(srvfile, "/srv/upasfs.%s", user);
post(srvfile, "upasfs", p[1]);
} else {
if(mount(p[1], -1, mntpt, MREPL, "") < 0)
error("mount failed");
}
}
exits(0);
}
static int
fileinfo(Message *m, int t, char **pp)
{
char *p;
int len;
p = "";
len = 0;
switch(t){
case Qbody:
p = m->body;
len = m->bend - m->body;
break;
case Qbcc:
if(m->bcc822){
p = s_to_c(m->bcc822);
len = strlen(p);
}
break;
case Qcc:
if(m->cc822){
p = s_to_c(m->cc822);
len = strlen(p);
}
break;
case Qdisposition:
switch(m->disposition){
case Dinline:
p = "inline";
break;
case Dfile:
p = "file";
break;
}
len = strlen(p);
break;
case Qdate:
if(m->date822){
p = s_to_c(m->date822);
len = strlen(p);
} else if(m->unixdate != nil){
p = s_to_c(m->unixdate);
len = strlen(p);
}
break;
case Qfilename:
if(m->filename){
p = s_to_c(m->filename);
len = strlen(p);
}
break;
case Qinreplyto:
if(m->inreplyto822){
p = s_to_c(m->inreplyto822);
len = strlen(p);
}
break;
case Qmessageid:
if(m->messageid822){
p = s_to_c(m->messageid822);
len = strlen(p);
}
break;
case Qfrom:
if(m->from822){
p = s_to_c(m->from822);
len = strlen(p);
} else if(m->unixfrom != nil){
p = s_to_c(m->unixfrom);
len = strlen(p);
}
break;
case Qheader:
p = m->header;
len = headerlen(m);
break;
case Qlines:
p = m->lines;
if(*p == 0)
countlines(m);
len = strlen(m->lines);
break;
case Qraw:
p = m->start;
if(strncmp(m->start, "From ", 5) == 0){
p = strchr(p, '\n');
if(p == nil)
p = m->start;
else
p++;
}
len = m->end - p;
break;
case Qrawunix:
p = m->start;
len = m->end - p;
break;
case Qrawbody:
p = m->rbody;
len = m->rbend - p;
break;
case Qrawheader:
p = m->header;
len = m->hend - p;
break;
case Qmimeheader:
p = m->mheader;
len = m->mhend - p;
break;
case Qreplyto:
p = nil;
if(m->replyto822 != nil){
p = s_to_c(m->replyto822);
len = strlen(p);
} else if(m->from822 != nil){
p = s_to_c(m->from822);
len = strlen(p);
} else if(m->sender822 != nil){
p = s_to_c(m->sender822);
len = strlen(p);
} else if(m->unixfrom != nil){
p = s_to_c(m->unixfrom);
len = strlen(p);
}
break;
case Qsender:
if(m->sender822){
p = s_to_c(m->sender822);
len = strlen(p);
}
break;
case Qsubject:
p = nil;
if(m->subject822){
p = s_to_c(m->subject822);
len = strlen(p);
}
break;
case Qto:
if(m->to822){
p = s_to_c(m->to822);
len = strlen(p);
}
break;
case Qtype:
if(m->type){
p = s_to_c(m->type);
len = strlen(p);
}
break;
case Qunixdate:
if(m->unixdate){
p = s_to_c(m->unixdate);
len = strlen(p);
}
break;
case Qunixheader:
if(m->unixheader){
p = s_to_c(m->unixheader);
len = s_len(m->unixheader);
}
break;
case Qdigest:
if(m->sdigest){
p = s_to_c(m->sdigest);
len = strlen(p);
}
break;
}
*pp = p;
return len;
}
int infofields[] = {
Qfrom,
Qto,
Qcc,
Qreplyto,
Qunixdate,
Qsubject,
Qtype,
Qdisposition,
Qfilename,
Qdigest,
Qbcc,
Qinreplyto,
Qdate,
Qsender,
Qmessageid,
Qlines,
-1,
};
static int
readinfo(Message *m, char *buf, long off, int count)
{
char *p;
int len, i, n;
String *s;
s = s_new();
len = 0;
for(i = 0; len < count && infofields[i] >= 0; i++){
n = fileinfo(m, infofields[i], &p);
s = stringconvert(s, p, n);
s_append(s, "\n");
p = s_to_c(s);
n = strlen(p);
if(off > 0){
if(off >= n){
off -= n;
continue;
}
p += off;
n -= off;
off = 0;
}
if(n > count - len)
n = count - len;
if(buf)
memmove(buf+len, p, n);
len += n;
}
s_free(s);
return len;
}
static void
mkstat(Dir *d, Mailbox *mb, Message *m, int t)
{
char *p;
d->uid = user;
d->gid = user;
d->muid = user;
d->mode = 0444;
d->qid.vers = 0;
d->qid.type = QTFILE;
d->type = 0;
d->dev = 0;
if(mb != nil && mb->d != nil){
d->atime = mb->d->atime;
d->mtime = mb->d->mtime;
} else {
d->atime = time(0);
d->mtime = d->atime;
}
switch(t){
case Qtop:
d->name = ".";
d->mode = DMDIR|0555;
d->atime = d->mtime = time(0);
d->length = 0;
d->qid.path = PATH(0, Qtop);
d->qid.type = QTDIR;
break;
case Qmbox:
d->name = mb->name;
d->mode = DMDIR|0555;
d->length = 0;
d->qid.path = PATH(mb->id, Qmbox);
d->qid.type = QTDIR;
d->qid.vers = mb->vers;
break;
case Qdir:
d->name = m->name;
d->mode = DMDIR|0555;
d->length = 0;
d->qid.path = PATH(m->id, Qdir);
d->qid.type = QTDIR;
break;
case Qctl:
d->name = dirtab[t];
d->mode = 0666;
d->atime = d->mtime = time(0);
d->length = 0;
d->qid.path = PATH(0, Qctl);
break;
case Qmboxctl:
d->name = dirtab[t];
d->mode = 0222;
d->atime = d->mtime = time(0);
d->length = 0;
d->qid.path = PATH(mb->id, Qmboxctl);
break;
case Qinfo:
d->name = dirtab[t];
d->length = readinfo(m, nil, 0, 1<<30);
d->qid.path = PATH(m->id, t);
break;
default:
d->name = dirtab[t];
d->length = fileinfo(m, t, &p);
d->qid.path = PATH(m->id, t);
break;
}
}
char*
rversion(Fid*)
{
Fid *f;
if(thdr.msize < 256)
return "max messagesize too small";
if(thdr.msize < messagesize)
messagesize = thdr.msize;
rhdr.msize = messagesize;
if(strncmp(thdr.version, "9P2000", 6) != 0)
return "unknown 9P version";
else
rhdr.version = "9P2000";
for(f = fids; f; f = f->next)
if(f->busy)
rclunk(f);
return nil;
}
char*
rauth(Fid*)
{
return Enoauth;
}
char*
rflush(Fid *f)
{
USED(f);
return 0;
}
char*
rattach(Fid *f)
{
f->busy = 1;
f->m = nil;
f->mb = nil;
f->qid.path = PATH(0, Qtop);
f->qid.type = QTDIR;
f->qid.vers = 0;
rhdr.qid = f->qid;
if(strcmp(thdr.uname, user) != 0)
return Eperm;
return 0;
}
static Fid*
doclone(Fid *f, int nfid)
{
Fid *nf;
nf = newfid(nfid);
if(nf->busy)
return nil;
nf->busy = 1;
nf->open = 0;
nf->m = f->m;
nf->mtop = f->mtop;
nf->mb = f->mb;
if(f->mb != nil)
mboxincref(f->mb);
if(f->mtop != nil){
qlock(f->mb);
msgincref(f->mtop);
qunlock(f->mb);
}
nf->qid = f->qid;
return nf;
}
char*
dowalk(Fid *f, char *name)
{
int t;
Mailbox *omb, *mb;
char *rv, *p;
Hash *h;
t = FILE(f->qid.path);
rv = Enotexist;
omb = f->mb;
if(omb)
qlock(omb);
else
qlock(&mbllock);
// this must catch everything except . and ..
retry:
h = hlook(f->qid.path, name);
if(h != nil){
f->mb = h->mb;
f->m = h->m;
switch(t){
case Qtop:
if(f->mb != nil)
mboxincref(f->mb);
break;
case Qmbox:
if(f->m){
msgincref(f->m);
f->mtop = f->m;
}
break;
}
f->qid = h->qid;
rv = nil;
} else if((p = strchr(name, '.')) != nil && *name != '.'){
*p = 0;
goto retry;
}
if(omb)
qunlock(omb);
else
qunlock(&mbllock);
if(rv == nil)
return rv;
if(strcmp(name, ".") == 0)
return nil;
if(f->qid.type != QTDIR)
return Enotdir;
if(strcmp(name, "..") == 0){
switch(t){
case Qtop:
f->qid.path = PATH(0, Qtop);
f->qid.type = QTDIR;
f->qid.vers = 0;
break;
case Qmbox:
f->qid.path = PATH(0, Qtop);
f->qid.type = QTDIR;
f->qid.vers = 0;
qlock(&mbllock);
mb = f->mb;
f->mb = nil;
mboxdecref(mb);
qunlock(&mbllock);
break;
case Qdir:
qlock(f->mb);
if(f->m->whole == f->mb->root){
f->qid.path = PATH(f->mb->id, Qmbox);
f->qid.type = QTDIR;
f->qid.vers = f->mb->d->qid.vers;
msgdecref(f->mb, f->mtop);
f->m = f->mtop = nil;
} else {
f->m = f->m->whole;
f->qid.path = PATH(f->m->id, Qdir);
f->qid.type = QTDIR;
}
qunlock(f->mb);
break;
}
rv = nil;
}
return rv;
}
char*
rwalk(Fid *f)
{
Fid *nf;
char *rv;
int i;
if(f->open)
return Eisopen;
rhdr.nwqid = 0;
nf = nil;
/* clone if requested */
if(thdr.newfid != thdr.fid){
nf = doclone(f, thdr.newfid);
if(nf == nil)
return "new fid in use";
f = nf;
}
/* if it's just a clone, return */
if(thdr.nwname == 0 && nf != nil)
return nil;
/* walk each element */
rv = nil;
for(i = 0; i < thdr.nwname; i++){
rv = dowalk(f, thdr.wname[i]);
if(rv != nil){
if(nf != nil)
rclunk(nf);
break;
}
rhdr.wqid[i] = f->qid;
}
rhdr.nwqid = i;
/* we only error out if no walk */
if(i > 0)
rv = nil;
return rv;
}
char *
ropen(Fid *f)
{
int file;
if(f->open)
return Eisopen;
file = FILE(f->qid.path);
if(thdr.mode != OREAD)
if(file != Qctl && file != Qmboxctl)
return Eperm;
// make sure we've decoded
if(file == Qbody){
if(f->m->decoded == 0)
decode(f->m);
if(f->m->converted == 0)
convert(f->m);
}
rhdr.iounit = 0;
rhdr.qid = f->qid;
f->open = 1;
return 0;
}
char *
rcreate(Fid*)
{
return Eperm;
}
int
readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
{
Dir d;
int m, n;
long pos;
Mailbox *mb;
n = 0;
pos = 0;
mkstat(&d, nil, nil, Qctl);
m = convD2M(&d, &buf[n], blen);
if(off <= pos){
if(m <= BIT16SZ || m > cnt)
return 0;
n += m;
cnt -= m;
}
pos += m;
for(mb = mbl; mb != nil; mb = mb->next){
mkstat(&d, mb, nil, Qmbox);
m = convD2M(&d, &buf[n], blen-n);
if(off <= pos){
if(m <= BIT16SZ || m > cnt)
break;
n += m;
cnt -= m;
}
pos += m;
}
return n;
}
int
readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
{
Dir d;
int n, m;
long pos;
Message *msg;
n = 0;
if(f->mb->ctl){
mkstat(&d, f->mb, nil, Qmboxctl);
m = convD2M(&d, &buf[n], blen);
if(off == 0){
if(m <= BIT16SZ || m > cnt){
f->fptr = nil;
return 0;
}
n += m;
cnt -= m;
} else
off -= m;
}
// to avoid n**2 reads of the directory, use a saved finger pointer
if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
msg = f->fptr;
pos = f->foff;
} else {
msg = f->mb->root->part;
pos = 0;
}
for(; cnt > 0 && msg != nil; msg = msg->next){
// act like deleted files aren't there
if(msg->deleted)
continue;
mkstat(&d, f->mb, msg, Qdir);
m = convD2M(&d, &buf[n], blen-n);
if(off <= pos){
if(m <= BIT16SZ || m > cnt)
break;
n += m;
cnt -= m;
}
pos += m;
}
// save a finger pointer for next read of the mbox directory
f->foff = pos;
f->fptr = msg;
f->fvers = f->mb->vers;
return n;
}
int
readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
{
Dir d;
int i, n, m;
long pos;
Message *msg;
n = 0;
pos = 0;
for(i = 0; i < Qmax; i++){
mkstat(&d, f->mb, f->m, i);
m = convD2M(&d, &buf[n], blen-n);
if(off <= pos){
if(m <= BIT16SZ || m > cnt)
return n;
n += m;
cnt -= m;
}
pos += m;
}
for(msg = f->m->part; msg != nil; msg = msg->next){
mkstat(&d, f->mb, msg, Qdir);
m = convD2M(&d, &buf[n], blen-n);
if(off <= pos){
if(m <= BIT16SZ || m > cnt)
break;
n += m;
cnt -= m;
}
pos += m;
}
return n;
}
char*
rread(Fid *f)
{
long off;
int t, i, n, cnt;
char *p;
rhdr.count = 0;
off = thdr.offset;
cnt = thdr.count;
if(cnt > messagesize - IOHDRSZ)
cnt = messagesize - IOHDRSZ;
rhdr.data = (char*)mbuf;
t = FILE(f->qid.path);
if(f->qid.type & QTDIR){
if(t == Qtop) {
qlock(&mbllock);
n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
qunlock(&mbllock);
} else if(t == Qmbox) {
qlock(f->mb);
if(off == 0)
syncmbox(f->mb, 1);
n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
qunlock(f->mb);
} else if(t == Qmboxctl) {
n = 0;
} else {
n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
}
rhdr.count = n;
return nil;
}
if(FILE(f->qid.path) == Qheader){
rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
return nil;
}
if(FILE(f->qid.path) == Qinfo){
rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
return nil;
}
i = fileinfo(f->m, FILE(f->qid.path), &p);
if(off < i){
if((off + cnt) > i)
cnt = i - off;
memmove(mbuf, p + off, cnt);
rhdr.count = cnt;
}
return nil;
}
char*
rwrite(Fid *f)
{
char *err;
char *token[1024];
int t, n;
String *file;
t = FILE(f->qid.path);
rhdr.count = thdr.count;
switch(t){
case Qctl:
if(thdr.count == 0)
return Ebadctl;
if(thdr.data[thdr.count-1] == '\n')
thdr.data[thdr.count-1] = 0;
else
thdr.data[thdr.count] = 0;
n = tokenize(thdr.data, token, nelem(token));
if(n == 0)
return Ebadctl;
if(strcmp(token[0], "open") == 0){
file = s_new();
switch(n){
case 1:
err = Ebadctl;
break;
case 2:
mboxpath(token[1], getlog(), file, 0);
err = newmbox(s_to_c(file), nil, 0);
break;
default:
mboxpath(token[1], getlog(), file, 0);
if(strchr(token[2], '/') != nil)
err = "/ not allowed in mailbox name";
else
err = newmbox(s_to_c(file), token[2], 0);
break;
}
s_free(file);
return err;
}
if(strcmp(token[0], "close") == 0){
if(n < 2)
return nil;
freembox(token[1]);
return nil;
}
if(strcmp(token[0], "delete") == 0){
if(n < 3)
return nil;
delmessages(n-1, &token[1]);
return nil;
}
return Ebadctl;
case Qmboxctl:
if(f->mb && f->mb->ctl){
if(thdr.count == 0)
return Ebadctl;
if(thdr.data[thdr.count-1] == '\n')
thdr.data[thdr.count-1] = 0;
else
thdr.data[thdr.count] = 0;
n = tokenize(thdr.data, token, nelem(token));
if(n == 0)
return Ebadctl;
return (*f->mb->ctl)(f->mb, n, token);
}
}
return Eperm;
}
char *
rclunk(Fid *f)
{
Mailbox *mb;
f->busy = 0;
f->open = 0;
if(f->mtop != nil){
qlock(f->mb);
msgdecref(f->mb, f->mtop);
qunlock(f->mb);
}
f->m = f->mtop = nil;
mb = f->mb;
if(mb != nil){
f->mb = nil;
assert(mb->refs > 0);
qlock(&mbllock);
mboxdecref(mb);
qunlock(&mbllock);
}
f->fid = -1;
return 0;
}
char *
rremove(Fid *f)
{
if(f->m != nil){
if(f->m->deleted == 0)
mailplumb(f->mb, f->m, 1);
f->m->deleted = 1;
}
return rclunk(f);
}
char *
rstat(Fid *f)
{
Dir d;
if(FILE(f->qid.path) == Qmbox){
qlock(f->mb);
syncmbox(f->mb, 1);
qunlock(f->mb);
}
mkstat(&d, f->mb, f->m, FILE(f->qid.path));
rhdr.nstat = convD2M(&d, mbuf, messagesize - IOHDRSZ);
rhdr.stat = mbuf;
return 0;
}
char *
rwstat(Fid*)
{
return Eperm;
}
Fid *
newfid(int fid)
{
Fid *f, *ff;
ff = 0;
for(f = fids; f; f = f->next)
if(f->fid == fid)
return f;
else if(!ff && !f->busy)
ff = f;
if(ff){
ff->fid = fid;
ff->fptr = nil;
return ff;
}
f = emalloc(sizeof *f);
f->fid = fid;
f->fptr = nil;
f->next = fids;
fids = f;
return f;
}
int
fidmboxrefs(Mailbox *mb)
{
Fid *f;
int refs = 0;
for(f = fids; f; f = f->next){
if(f->mb == mb)
refs++;
}
return refs;
}
void
io(void)
{
char *err;
int n;
/* start a process to watch the mailboxes*/
if(plumbing){
switch(rfork(RFPROC|RFMEM)){
case -1:
/* oh well */
break;
case 0:
reader();
exits(nil);
default:
break;
}
}
for(;;){
/*
* reading from a pipe or a network device
* will give an error after a few eof reads
* however, we cannot tell the difference
* between a zero-length read and an interrupt
* on the processes writing to us,
* so we wait for the error
*/
checkmboxrefs();
n = read9pmsg(mfd[0], mdata, messagesize);
if(n == 0)
continue;
if(n < 0)
return;
if(convM2S(mdata, n, &thdr) == 0)
continue;
if(debug)
fprint(2, "%s:<-%F\n", argv0, &thdr);
rhdr.data = (char*)mdata + messagesize;
if(!fcalls[thdr.type])
err = "bad fcall type";
else
err = (*fcalls[thdr.type])(newfid(thdr.fid));
if(err){
rhdr.type = Rerror;
rhdr.ename = err;
}else{
rhdr.type = thdr.type + 1;
rhdr.fid = thdr.fid;
}
rhdr.tag = thdr.tag;
if(debug)
fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
n = convS2M(&rhdr, mdata, messagesize);
if(write(mfd[1], mdata, n) != n)
error("mount write");
}
}
void
reader(void)
{
ulong t;
Dir *d;
Mailbox *mb;
sleep(15*1000);
for(;;){
t = time(0);
qlock(&mbllock);
for(mb = mbl; mb != nil; mb = mb->next){
assert(mb->refs > 0);
if(mb->waketime != 0 && t > mb->waketime){
qlock(mb);
mb->waketime = 0;
break;
}
d = dirstat(mb->path);
if(d == nil)
continue;
qlock(mb);
if(d->qid.path != mb->d->qid.path
|| mb->d && d->qid.vers != mb->d->qid.vers){
free(d);
break;
}
qunlock(mb);
free(d);
}
qunlock(&mbllock);
if(mb != nil){
syncmbox(mb, 1);
qunlock(mb);
} else
sleep(15*1000);
}
}
int
newid(void)
{
int rv;
static int id;
static Lock idlock;
lock(&idlock);
rv = ++id;
unlock(&idlock);
return rv;
}
void
error(char *s)
{
postnote(PNGROUP, getpid(), "die yankee pig dog");
fprint(2, "%s: %s: %r\n", argv0, s);
exits(s);
}
typedef struct Ignorance Ignorance;
struct Ignorance
{
Ignorance *next;
char *str; /* string */
int partial; /* true if not exact match */
};
Ignorance *ignorance;
/*
* read the file of headers to ignore
*/
void
readignore(void)
{
char *p;
Ignorance *i;
Biobuf *b;
if(ignorance != nil)
return;
b = Bopen("/mail/lib/ignore", OREAD);
if(b == 0)
return;
while(p = Brdline(b, '\n')){
p[Blinelen(b)-1] = 0;
while(*p && (*p == ' ' || *p == '\t'))
p++;
if(*p == '#')
continue;
i = malloc(sizeof(Ignorance));
if(i == 0)
break;
i->partial = strlen(p);
i->str = strdup(p);
if(i->str == 0){
free(i);
break;
}
i->next = ignorance;
ignorance = i;
}
Bterm(b);
}
int
ignore(char *p)
{
Ignorance *i;
readignore();
for(i = ignorance; i != nil; i = i->next)
if(cistrncmp(i->str, p, i->partial) == 0)
return 1;
return 0;
}
int
hdrlen(char *p, char *e)
{
char *ep;
ep = p;
do {
ep = strchr(ep, '\n');
if(ep == nil){
ep = e;
break;
}
ep++;
if(ep >= e){
ep = e;
break;
}
} while(*ep == ' ' || *ep == '\t');
return ep - p;
}
// rfc2047 non-ascii
typedef struct Charset Charset;
struct Charset {
char *name;
int len;
int convert;
char *tcsname;
} charsets[] =
{
{ "us-ascii", 8, 1, nil, },
{ "utf-8", 5, 0, nil, },
{ "iso-8859-1", 10, 1, nil, },
{ "iso-8859-2", 10, 2, "8859-2", },
{ "big5", 4, 2, "big5", },
{ "iso-2022-jp", 11, 2, "jis", },
{ "windows-1251", 12, 2, "cp1251"},
{ "koi8-r", 6, 2, "koi8"},
};
int
rfc2047convert(String *s, char *token, int len)
{
char decoded[1024];
char utfbuf[2*1024];
int i;
char *e, *x;
if(len == 0)
return -1;
e = token+len-2;
token += 2;
// bail if we don't understand the character set
for(i = 0; i < nelem(charsets); i++)
if(cistrncmp(charsets[i].name, token, charsets[i].len) == 0)
if(token[charsets[i].len] == '?'){
token += charsets[i].len + 1;
break;
}
if(i >= nelem(charsets))
return -1;
// bail if it doesn't fit
if(e-token > sizeof(decoded)-1)
return -1;
// bail if we don't understand the encoding
if(cistrncmp(token, "b?", 2) == 0){
token += 2;
len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
decoded[len] = 0;
} else if(cistrncmp(token, "q?", 2) == 0){
token += 2;
len = decquoted(decoded, token, e);
if(len > 0 && decoded[len-1] == '\n')
len--;
decoded[len] = 0;
} else
return -1;
switch(charsets[i].convert){
case 0:
s_append(s, decoded);
break;
case 1:
latin1toutf(utfbuf, decoded, decoded+len);
s_append(s, utfbuf);
break;
case 2:
if(xtoutf(charsets[i].tcsname, &x, decoded, decoded+len) <= 0){
s_append(s, decoded);
} else {
s_append(s, x);
free(x);
}
break;
}
return 0;
}
char*
rfc2047start(char *start, char *end)
{
int quests;
if(*--end != '=')
return nil;
if(*--end != '?')
return nil;
quests = 0;
for(end--; end >= start; end--){
switch(*end){
case '=':
if(quests == 3 && *(end+1) == '?')
return end;
break;
case '?':
++quests;
break;
case ' ':
case '\t':
case '\n':
case '\r':
/* can't have white space in a token */
return nil;
}
}
return nil;
}
// convert a header line
String*
stringconvert(String *s, char *uneaten, int len)
{
char *token;
char *p;
int i;
s = s_reset(s);
p = uneaten;
for(i = 0; i < len; i++){
if(*p++ == '='){
token = rfc2047start(uneaten, p);
if(token != nil){
s_nappend(s, uneaten, token-uneaten);
if(rfc2047convert(s, token, p - token) < 0)
s_nappend(s, token, p - token);
uneaten = p;
}
}
}
if(p > uneaten)
s_nappend(s, uneaten, p-uneaten);
return s;
}
int
readheader(Message *m, char *buf, int off, int cnt)
{
char *p, *e;
int n, ns;
char *to = buf;
String *s;
p = m->header;
e = m->hend;
s = nil;
// copy in good headers
while(cnt > 0 && p < e){
n = hdrlen(p, e);
if(ignore(p)){
p += n;
continue;
}
// rfc2047 processing
s = stringconvert(s, p, n);
ns = s_len(s);
if(off > 0){
if(ns <= off){
off -= ns;
p += n;
continue;
}
ns -= off;
}
if(ns > cnt)
ns = cnt;
memmove(to, s_to_c(s)+off, ns);
to += ns;
p += n;
cnt -= ns;
off = 0;
}
s_free(s);
return to - buf;
}
int
headerlen(Message *m)
{
char buf[1024];
int i, n;
if(m->hlen >= 0)
return m->hlen;
for(n = 0; ; n += i){
i = readheader(m, buf, n, sizeof(buf));
if(i <= 0)
break;
}
m->hlen = n;
return n;
}
QLock hashlock;
uint
hash(ulong ppath, char *name)
{
uchar *p;
uint h;
h = 0;
for(p = (uchar*)name; *p; p++)
h = h*7 + *p;
h += ppath;
return h % Hsize;
}
Hash*
hlook(ulong ppath, char *name)
{
int h;
Hash *hp;
qlock(&hashlock);
h = hash(ppath, name);
for(hp = htab[h]; hp != nil; hp = hp->next)
if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
qunlock(&hashlock);
return hp;
}
qunlock(&hashlock);
return nil;
}
void
henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
{
int h;
Hash *hp, **l;
qlock(&hashlock);
h = hash(ppath, name);
for(l = &htab[h]; *l != nil; l = &(*l)->next){
hp = *l;
if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
hp->m = m;
hp->mb = mb;
hp->qid = qid;
qunlock(&hashlock);
return;
}
}
*l = hp = emalloc(sizeof(*hp));
hp->m = m;
hp->mb = mb;
hp->qid = qid;
hp->name = name;
hp->ppath = ppath;
qunlock(&hashlock);
}
void
hfree(ulong ppath, char *name)
{
int h;
Hash *hp, **l;
qlock(&hashlock);
h = hash(ppath, name);
for(l = &htab[h]; *l != nil; l = &(*l)->next){
hp = *l;
if(ppath == hp->ppath && strcmp(name, hp->name) == 0){
hp->mb = nil;
*l = hp->next;
free(hp);
break;
}
}
qunlock(&hashlock);
}
int
hashmboxrefs(Mailbox *mb)
{
int h;
Hash *hp;
int refs = 0;
qlock(&hashlock);
for(h = 0; h < Hsize; h++){
for(hp = htab[h]; hp != nil; hp = hp->next)
if(hp->mb == mb)
refs++;
}
qunlock(&hashlock);
return refs;
}
void
checkmboxrefs(void)
{
int f, refs;
Mailbox *mb;
qlock(&mbllock);
for(mb=mbl; mb; mb=mb->next){
qlock(mb);
refs = (f=fidmboxrefs(mb))+1;
if(refs != mb->refs){
fprint(2, "mbox %s %s ref mismatch actual %d (%d+1) expected %d\n", mb->name, mb->path, refs, f, mb->refs);
abort();
}
qunlock(mb);
}
qunlock(&mbllock);
}
void
post(char *name, char *envname, int srvfd)
{
int fd;
char buf[32];
fd = create(name, OWRITE, 0600);
if(fd < 0)
error("post failed");
sprint(buf, "%d",srvfd);
if(write(fd, buf, strlen(buf)) != strlen(buf))
error("srv write");
close(fd);
putenv(envname, name);
}
|