aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/upas/fs')
-rw-r--r--src/cmd/upas/fs/dat.h221
-rw-r--r--src/cmd/upas/fs/fs.c1704
-rw-r--r--src/cmd/upas/fs/imap4.c876
-rw-r--r--src/cmd/upas/fs/mbox.c1601
-rw-r--r--src/cmd/upas/fs/mkfile29
-rw-r--r--src/cmd/upas/fs/mkfile.927
-rw-r--r--src/cmd/upas/fs/plan9.c405
-rw-r--r--src/cmd/upas/fs/pop3.c700
-rw-r--r--src/cmd/upas/fs/readdir.c15
-rw-r--r--src/cmd/upas/fs/strtotm.c113
-rw-r--r--src/cmd/upas/fs/tester.c81
11 files changed, 5772 insertions, 0 deletions
diff --git a/src/cmd/upas/fs/dat.h b/src/cmd/upas/fs/dat.h
new file mode 100644
index 00000000..ffcbf5b3
--- /dev/null
+++ b/src/cmd/upas/fs/dat.h
@@ -0,0 +1,221 @@
+typedef struct Message Message;
+struct Message
+{
+ int id;
+ int refs;
+ int subname;
+ char name[Elemlen];
+
+ // pointers into message
+ char *start; // start of message
+ char *end; // end of message
+ char *header; // start of header
+ char *hend; // end of header
+ int hlen; // length of header minus ignored fields
+ char *mheader; // start of mime header
+ char *mhend; // end of mime header
+ char *body; // start of body
+ char *bend; // end of body
+ char *rbody; // raw (unprocessed) body
+ char *rbend; // end of raw (unprocessed) body
+ char *lim;
+ char deleted;
+ char inmbox;
+ char mallocd; // message is malloc'd
+ char ballocd; // body is malloc'd
+ char hallocd; // header is malloce'd
+
+ // mail info
+ String *unixheader;
+ String *unixfrom;
+ String *unixdate;
+ String *from822;
+ String *sender822;
+ String *to822;
+ String *bcc822;
+ String *cc822;
+ String *replyto822;
+ String *date822;
+ String *inreplyto822;
+ String *subject822;
+ String *messageid822;
+ String *addrs;
+ String *mimeversion;
+ String *sdigest;
+
+ // mime info
+ String *boundary;
+ String *type;
+ int encoding;
+ int disposition;
+ String *charset;
+ String *filename;
+ int converted;
+ int decoded;
+ char lines[10]; // number of lines in rawbody
+
+ Message *next; // same level
+ Message *part; // down a level
+ Message *whole; // up a level
+
+ uchar digest[SHA1dlen];
+
+ vlong imapuid; // used by imap4
+
+ char uidl[80]; // used by pop3
+ int mesgno;
+};
+
+enum
+{
+ // encodings
+ Enone= 0,
+ Ebase64,
+ Equoted,
+
+ // disposition possibilities
+ Dnone= 0,
+ Dinline,
+ Dfile,
+ Dignore,
+
+ PAD64= '=',
+};
+
+typedef struct Mailbox Mailbox;
+struct Mailbox
+{
+ QLock ql; /* jpc named Qlock */
+ int refs;
+ Mailbox *next;
+ int id;
+ int dolock; // lock when syncing?
+ int std;
+ char name[Elemlen];
+ char path[Pathlen];
+ Dir *d;
+ Message *root;
+ int vers; // goes up each time mailbox is read
+
+ ulong waketime;
+ char *(*sync)(Mailbox*, int);
+ void (*close)(Mailbox*);
+ char *(*fetch)(Mailbox*, Message*);
+ char *(*ctl)(Mailbox*, int, char**);
+ void *aux; // private to Mailbox implementation
+};
+
+typedef char *Mailboxinit(Mailbox*, char*);
+
+extern Message *root;
+extern Mailboxinit plan9mbox;
+extern Mailboxinit pop3mbox;
+extern Mailboxinit imap4mbox;
+
+char* syncmbox(Mailbox*, int);
+char* geterrstr(void);
+void* emalloc(ulong);
+void* erealloc(void*, ulong);
+Message* newmessage(Message*);
+void delmessage(Mailbox*, Message*);
+void delmessages(int, char**);
+int newid(void);
+void mailplumb(Mailbox*, Message*, int);
+char* newmbox(char*, char*, int);
+void freembox(char*);
+void logmsg(char*, Message*);
+void msgincref(Message*);
+void msgdecref(Mailbox*, Message*);
+void mboxincref(Mailbox*);
+void mboxdecref(Mailbox*);
+void convert(Message*);
+void decode(Message*);
+int cistrncmp(char*, char*, int);
+int cistrcmp(char*, char*);
+int latin1toutf(char*, char*, char*);
+int windows1257toutf(char*, char*, char*);
+int decquoted(char*, char*, char*);
+int xtoutf(char*, char**, char*, char*);
+void countlines(Message*);
+int headerlen(Message*);
+void parse(Message*, int, Mailbox*, int);
+void parseheaders(Message*, int, Mailbox*, int);
+void parsebody(Message*, Mailbox*);
+void parseunix(Message*);
+String* date822tounix(char*);
+int fidmboxrefs(Mailbox*);
+int hashmboxrefs(Mailbox*);
+void checkmboxrefs(void);
+
+extern int debug;
+extern int fflag;
+extern int logging;
+extern char user[Elemlen];
+extern char stdmbox[Pathlen];
+extern QLock mbllock;
+extern Mailbox *mbl;
+extern char *mntpt;
+extern int biffing;
+extern int plumbing;
+extern char* Enotme;
+
+enum
+{
+ /* mail subobjects */
+ Qbody,
+ Qbcc,
+ Qcc,
+ Qdate,
+ Qdigest,
+ Qdisposition,
+ Qfilename,
+ Qfrom,
+ Qheader,
+ Qinreplyto,
+ Qlines,
+ Qmimeheader,
+ Qmessageid,
+ Qraw,
+ Qrawbody,
+ Qrawheader,
+ Qrawunix,
+ Qreplyto,
+ Qsender,
+ Qsubject,
+ Qto,
+ Qtype,
+ Qunixheader,
+ Qinfo,
+ Qunixdate,
+ Qmax,
+
+ /* other files */
+ Qtop,
+ Qmbox,
+ Qdir,
+ Qctl,
+ Qmboxctl,
+};
+
+#define PATH(id, f) ((((id)&0xfffff)<<10) | (f))
+#define FILE(p) ((p) & 0x3ff)
+
+/* char *dirtab[]; jpc */
+
+// hash table to aid in name lookup, all files have an entry
+typedef struct Hash Hash;
+struct Hash {
+ Hash *next;
+ char *name;
+ ulong ppath;
+ Qid qid;
+ Mailbox *mb;
+ Message *m;
+};
+
+Hash *hlook(ulong, char*);
+void henter(ulong, char*, Qid, Message*, Mailbox*);
+void hfree(ulong, char*);
+
+ulong msgallocd, msgfreed;
+
diff --git a/src/cmd/upas/fs/fs.c b/src/cmd/upas/fs/fs.c
new file mode 100644
index 00000000..b68b8d2e
--- /dev/null
+++ b/src/cmd/upas/fs/fs.c
@@ -0,0 +1,1704 @@
+#include "common.h"
+#include <auth.h>
+#include <fcall.h>
+#include <libsec.h>
+#include <9pclient.h> /* jpc */
+#include <thread.h> /* jpc */
+#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 run_io(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);
+ threadexits("usage");
+}
+
+void
+notifyf(void *a, char *s)
+{
+ USED(a);
+ if(strncmp(s, "interrupt", 9) == 0)
+ noted(NCONT);
+ noted(NDFLT);
+}
+
+void
+threadmain(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)){ /* jpc removed RFEND */
+ 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]); jpc */
+ post9pservice(p[1], "upasfs"); /* jpc */
+ } else {
+ error("tried to mount, fixme"); /* jpc */
+ /* if(mount(p[1], -1, mntpt, MREPL, "") < 0)
+ error("mount failed"); jpc */
+ }
+ }
+ threadexits(0);
+}
+
+void run_io(void *v) {
+ int *p;
+
+ p = v;
+ 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");
+}
+
+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* dummy)
+{
+ 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* dummy)
+{
+ 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->ql);
+ msgincref(f->mtop);
+ qunlock(&f->mb->ql);
+ }
+ 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->ql);
+ 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->ql);
+ 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->ql);
+ 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->ql);
+ 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* dummy)
+{
+ return Eperm;
+}
+
+int
+readtopdir(Fid* dummy, 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->ql);
+ if(off == 0)
+ syncmbox(f->mb, 1);
+ n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
+ qunlock(&f->mb->ql);
+ } 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->ql);
+ msgdecref(f->mb, f->mtop);
+ qunlock(&f->mb->ql);
+ }
+ 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->ql);
+ syncmbox(f->mb, 1);
+ qunlock(&f->mb->ql);
+ }
+ 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* dummy)
+{
+ 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, nw;
+
+ /* start a process to watch the mailboxes*/
+ if(plumbing){
+ proccreate(reader, nil, 16000);
+#if 0 /* jpc */
+ switch(rfork(RFPROC|RFMEM)){
+ case -1:
+ /* oh well */
+ break;
+ case 0:
+ reader();
+ threadexits(nil);
+ default:
+ break;
+ }
+#endif /* jpc */
+ }
+
+ 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((nw = write(mfd[1], mdata, n)) != n) {
+ fprint(2,"wrote %d bytes\n",nw);
+ error("mount write");
+ }
+ }
+}
+
+void
+reader(void *dummy)
+{
+ 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->ql);
+ mb->waketime = 0;
+ break;
+ }
+
+ d = dirstat(mb->path);
+ if(d == nil)
+ continue;
+
+ qlock(&mb->ql);
+ if(mb->d)
+ if(d->qid.path != mb->d->qid.path
+ || d->qid.vers != mb->d->qid.vers){
+ free(d);
+ break;
+ }
+ qunlock(&mb->ql);
+ free(d);
+ }
+ qunlock(&mbllock);
+ if(mb != nil){
+ syncmbox(mb, 1);
+ qunlock(&mb->ql);
+ } 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);
+ threadexits(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->ql);
+ 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->ql);
+ }
+ 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);
+}
diff --git a/src/cmd/upas/fs/imap4.c b/src/cmd/upas/fs/imap4.c
new file mode 100644
index 00000000..152d15cf
--- /dev/null
+++ b/src/cmd/upas/fs/imap4.c
@@ -0,0 +1,876 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <auth.h>
+#include "dat.h"
+
+#pragma varargck argpos imap4cmd 2
+#pragma varargck type "Z" char*
+
+int doublequote(Fmt*);
+int pipeline = 1;
+
+/* static char Eio[] = "i/o error"; jpc */
+
+typedef struct Imap Imap;
+struct Imap {
+ char *freep; // free this to free the strings below
+
+ char *host;
+ char *user;
+ char *mbox;
+
+ int mustssl;
+ int refreshtime;
+ int debug;
+
+ ulong tag;
+ ulong validity;
+ int nmsg;
+ int size;
+ char *base;
+ char *data;
+
+ vlong *uid;
+ int nuid;
+ int muid;
+
+ Thumbprint *thumb;
+
+ // open network connection
+ Biobuf bin;
+ Biobuf bout;
+ int fd;
+};
+
+static char*
+removecr(char *s)
+{
+ char *r, *w;
+
+ for(r=w=s; *r; r++)
+ if(*r != '\r')
+ *w++ = *r;
+ *w = '\0';
+ return s;
+}
+
+//
+// send imap4 command
+//
+static void
+imap4cmd(Imap *imap, char *fmt, ...)
+{
+ char buf[128], *p;
+ va_list va;
+
+ va_start(va, fmt);
+ p = buf+sprint(buf, "9X%lud ", imap->tag);
+ vseprint(p, buf+sizeof(buf), fmt, va);
+ va_end(va);
+
+ p = buf+strlen(buf);
+ if(p > (buf+sizeof(buf)-3))
+ sysfatal("imap4 command too long");
+
+ if(imap->debug)
+ fprint(2, "-> %s\n", buf);
+ strcpy(p, "\r\n");
+ Bwrite(&imap->bout, buf, strlen(buf));
+ Bflush(&imap->bout);
+}
+
+enum {
+ OK,
+ NO,
+ BAD,
+ BYE,
+ EXISTS,
+ STATUS,
+ FETCH,
+ UNKNOWN,
+};
+
+static char *verblist[] = {
+[OK] "OK",
+[NO] "NO",
+[BAD] "BAD",
+[BYE] "BYE",
+[EXISTS] "EXISTS",
+[STATUS] "STATUS",
+[FETCH] "FETCH",
+};
+
+static int
+verbcode(char *verb)
+{
+ int i;
+ char *q;
+
+ if(q = strchr(verb, ' '))
+ *q = '\0';
+
+ for(i=0; i<nelem(verblist); i++)
+ if(verblist[i] && strcmp(verblist[i], verb)==0){
+ if(q)
+ *q = ' ';
+ return i;
+ }
+ if(q)
+ *q = ' ';
+ return UNKNOWN;
+}
+
+static void
+strupr(char *s)
+{
+ for(; *s; s++)
+ if('a' <= *s && *s <= 'z')
+ *s += 'A'-'a';
+}
+
+static void
+imapgrow(Imap *imap, int n)
+{
+ int i;
+
+ if(imap->data == nil){
+ imap->base = emalloc(n+1);
+ imap->data = imap->base;
+ imap->size = n+1;
+ }
+ if(n >= imap->size){
+ // friggin microsoft - reallocate
+ i = imap->data - imap->base;
+ imap->base = erealloc(imap->base, i+n+1);
+ imap->data = imap->base + i;
+ imap->size = n+1;
+ }
+}
+
+
+//
+// get imap4 response line. there might be various
+// data or other informational lines mixed in.
+//
+static char*
+imap4resp(Imap *imap)
+{
+ char *line, *p, *ep, *op, *q, *r, *en, *verb;
+ int i, n;
+ static char error[256];
+
+ while(p = Brdline(&imap->bin, '\n')){
+ ep = p+Blinelen(&imap->bin);
+ while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
+ *--ep = '\0';
+
+ if(imap->debug)
+ fprint(2, "<- %s\n", p);
+ strupr(p);
+
+ switch(p[0]){
+ case '+':
+ if(imap->tag == 0)
+ fprint(2, "unexpected: %s\n", p);
+ break;
+
+ // ``unsolicited'' information; everything happens here.
+ case '*':
+ if(p[1]!=' ')
+ continue;
+ p += 2;
+ line = p;
+ n = strtol(p, &p, 10);
+ if(*p==' ')
+ p++;
+ verb = p;
+
+ if(p = strchr(verb, ' '))
+ p++;
+ else
+ p = verb+strlen(verb);
+
+ switch(verbcode(verb)){
+ case OK:
+ case NO:
+ case BAD:
+ // human readable text at p;
+ break;
+ case BYE:
+ // early disconnect
+ // human readable text at p;
+ break;
+
+ // * 32 EXISTS
+ case EXISTS:
+ imap->nmsg = n;
+ break;
+
+ // * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
+ case STATUS:
+ if(q = strstr(p, "MESSAGES"))
+ imap->nmsg = atoi(q+8);
+ if(q = strstr(p, "UIDVALIDITY"))
+ imap->validity = strtoul(q+11, 0, 10);
+ break;
+
+ case FETCH:
+ // * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
+ // <3031 bytes of data>
+ // )
+ if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
+ if((q = strchr(p, '{'))
+ && (n=strtol(q+1, &en, 0), *en=='}')){
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ if((i = Bread(&imap->bin, imap->data, n)) != n){
+ snprint(error, sizeof error,
+ "short read %d != %d: %r\n",
+ i, n);
+ return error;
+ }
+ if(imap->debug)
+ fprint(2, "<- read %d bytes\n", n);
+ imap->data[n] = '\0';
+ if(imap->debug)
+ fprint(2, "<- %s\n", imap->data);
+ imap->data += n;
+ imap->size -= n;
+ p = Brdline(&imap->bin, '\n');
+ if(imap->debug)
+ fprint(2, "<- ignoring %.*s\n",
+ Blinelen(&imap->bin), p);
+ }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
+ *r = '\0';
+ q++;
+ n = r-q;
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ memmove(imap->data, q, n);
+ imap->data[n] = '\0';
+ imap->data += n;
+ imap->size -= n;
+ }else
+ return "confused about FETCH response";
+ break;
+ }
+
+ // * 1 FETCH (UID 1 RFC822.SIZE 511)
+ if(q=strstr(p, "RFC822.SIZE")){
+ imap->size = atoi(q+11);
+ break;
+ }
+
+ // * 1 FETCH (UID 1 RFC822.HEADER {496}
+ // <496 bytes of data>
+ // )
+ // * 1 FETCH (UID 1 RFC822.HEADER "data")
+ if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
+ if((q = strchr(p, '{'))
+ && (n=strtol(q+1, &en, 0), *en=='}')){
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ if((i = Bread(&imap->bin, imap->data, n)) != n){
+ snprint(error, sizeof error,
+ "short read %d != %d: %r\n",
+ i, n);
+ return error;
+ }
+ if(imap->debug)
+ fprint(2, "<- read %d bytes\n", n);
+ imap->data[n] = '\0';
+ if(imap->debug)
+ fprint(2, "<- %s\n", imap->data);
+ imap->data += n;
+ imap->size -= n;
+ p = Brdline(&imap->bin, '\n');
+ if(imap->debug)
+ fprint(2, "<- ignoring %.*s\n",
+ Blinelen(&imap->bin), p);
+ }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
+ *r = '\0';
+ q++;
+ n = r-q;
+ if(imap->data == nil || n >= imap->size)
+ imapgrow(imap, n);
+ memmove(imap->data, q, n);
+ imap->data[n] = '\0';
+ imap->data += n;
+ imap->size -= n;
+ }else
+ return "confused about FETCH response";
+ break;
+ }
+
+ // * 1 FETCH (UID 1)
+ // * 2 FETCH (UID 6)
+ if(q = strstr(p, "UID")){
+ if(imap->nuid < imap->muid)
+ imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
+ break;
+ }
+ }
+
+ if(imap->tag == 0)
+ return line;
+ break;
+
+ case '9': // response to our message
+ op = p;
+ if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
+ while(*p==' ')
+ p++;
+ imap->tag++;
+ return p;
+ }
+ fprint(2, "expected %lud; got %s\n", imap->tag, op);
+ break;
+
+ default:
+ if(imap->debug || *p)
+ fprint(2, "unexpected line: %s\n", p);
+ }
+ }
+ snprint(error, sizeof error, "i/o error: %r\n");
+ return error;
+}
+
+static int
+isokay(char *resp)
+{
+ return strncmp(resp, "OK", 2)==0;
+}
+
+//
+// log in to IMAP4 server, select mailbox, no SSL at the moment
+//
+static char*
+imap4login(Imap *imap)
+{
+ char *s;
+ UserPasswd *up;
+
+ imap->tag = 0;
+ s = imap4resp(imap);
+ if(!isokay(s))
+ return "error in initial IMAP handshake";
+
+ if(imap->user != nil)
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
+ else
+ up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
+ if(up == nil)
+ return "cannot find IMAP password";
+
+ imap->tag = 1;
+ imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
+ free(up);
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ imap4cmd(imap, "SELECT %Z", imap->mbox);
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ return nil;
+}
+
+//
+// push tls onto a connection
+//
+int
+mypushtls(int fd)
+{
+ int p[2];
+ char buf[10];
+
+ if(pipe(p) < 0)
+ return -1;
+
+ switch(fork()){
+ case -1:
+ close(p[0]);
+ close(p[1]);
+ return -1;
+ case 0:
+ close(p[1]);
+ dup(p[0], 0);
+ dup(p[0], 1);
+ sprint(buf, "/fd/%d", fd);
+ execl("/bin/tlsrelay", "tlsrelay", "-f", buf, nil);
+ _exits(nil);
+ default:
+ break;
+ }
+ close(fd);
+ close(p[0]);
+ return p[1];
+}
+
+//
+// dial and handshake with the imap server
+//
+static char*
+imap4dial(Imap *imap)
+{
+ char *err, *port;
+ uchar digest[SHA1dlen];
+ int sfd;
+ TLSconn conn;
+
+ if(imap->fd >= 0){
+ imap4cmd(imap, "noop");
+ if(isokay(imap4resp(imap)))
+ return nil;
+ close(imap->fd);
+ imap->fd = -1;
+ }
+
+ if(imap->mustssl)
+ port = "imaps";
+ else
+ port = "imap4";
+
+ if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
+ return geterrstr();
+
+ if(imap->mustssl){
+ memset(&conn, 0, sizeof conn);
+ sfd = tlsClient(imap->fd, &conn);
+ if(sfd < 0)
+ sysfatal("tlsClient: %r");
+ if(conn.cert==nil || conn.certlen <= 0)
+ sysfatal("server did not provide TLS certificate");
+ sha1(conn.cert, conn.certlen, digest, nil);
+ if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
+ fmtinstall('H', encodefmt);
+ sysfatal("server certificate %.*H not recognized", SHA1dlen, digest);
+ }
+ free(conn.cert);
+ close(imap->fd);
+ imap->fd = sfd;
+
+ if(imap->debug){
+ char fn[128];
+ int fd;
+
+ snprint(fn, sizeof fn, "%s/ctl", conn.dir);
+ fd = open(fn, ORDWR);
+ if(fd < 0)
+ fprint(2, "opening ctl: %r\n");
+ if(fprint(fd, "debug") < 0)
+ fprint(2, "writing ctl: %r\n");
+ close(fd);
+ }
+ }
+ Binit(&imap->bin, imap->fd, OREAD);
+ Binit(&imap->bout, imap->fd, OWRITE);
+
+ if(err = imap4login(imap)) {
+ close(imap->fd);
+ return err;
+ }
+
+ return nil;
+}
+
+//
+// close connection
+//
+#if 0 /* jpc */
+static void
+imap4hangup(Imap *imap)
+{
+ imap4cmd(imap, "LOGOUT");
+ imap4resp(imap);
+ close(imap->fd);
+}
+#endif
+
+//
+// download a single message
+//
+static char*
+imap4fetch(Mailbox *mb, Message *m)
+{
+ int i;
+ char *p, *s, sdigest[2*SHA1dlen+1];
+ Imap *imap;
+
+ imap = mb->aux;
+
+ imap->size = 0;
+
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ p = imap->base;
+ if(p == nil)
+ return "did not get message body";
+
+ removecr(p);
+ free(m->start);
+ m->start = p;
+ m->end = p+strlen(p);
+ m->bend = m->rbend = m->end;
+ m->header = m->start;
+
+ imap->base = nil;
+ imap->data = nil;
+
+ parse(m, 0, mb, 1);
+
+ // digest headers
+ 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 nil;
+}
+
+//
+// check for new messages on imap4 server
+// download new messages, mark deleted messages
+//
+static char*
+imap4read(Imap *imap, Mailbox *mb, int doplumb)
+{
+ char *s;
+ int i, ignore, nnew, t;
+ Message *m, *next, **l;
+
+ imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+
+ imap->nuid = 0;
+ imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
+ imap->muid = imap->nmsg;
+
+ if(imap->nmsg > 0){
+ imap4cmd(imap, "UID FETCH 1:* UID");
+ if(!isokay(s = imap4resp(imap)))
+ return s;
+ }
+
+ l = &mb->root->part;
+ for(i=0; i<imap->nuid; i++){
+ ignore = 0;
+ while(*l != nil){
+ if((*l)->imapuid == imap->uid[i]){
+ ignore = 1;
+ l = &(*l)->next;
+ break;
+ }else{
+ // old mail, we don't have it anymore
+ if(doplumb)
+ mailplumb(mb, *l, 1);
+ (*l)->inmbox = 0;
+ (*l)->deleted = 1;
+ l = &(*l)->next;
+ }
+ }
+ if(ignore)
+ continue;
+
+ // new message
+ m = newmessage(mb->root);
+ m->mallocd = 1;
+ m->inmbox = 1;
+ m->imapuid = imap->uid[i];
+
+ // add to chain, will download soon
+ *l = m;
+ l = &m->next;
+ }
+
+ // whatever is left at the end of the chain is gone
+ while(*l != nil){
+ if(doplumb)
+ mailplumb(mb, *l, 1);
+ (*l)->inmbox = 0;
+ (*l)->deleted = 1;
+ l = &(*l)->next;
+ }
+
+ // download new messages
+ t = imap->tag;
+ if(pipeline)
+ switch(rfork(RFPROC|RFMEM)){
+ case -1:
+ sysfatal("rfork: %r");
+ default:
+ break;
+ case 0:
+ for(m = mb->root->part; m != nil; m = m->next){
+ if(m->start != nil)
+ continue;
+ if(imap->debug)
+ fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+ t, (ulong)m->imapuid);
+ Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+ t++, (ulong)m->imapuid);
+ }
+ Bflush(&imap->bout);
+ _exits(nil);
+ }
+
+ nnew = 0;
+ for(m=mb->root->part; m!=nil; m=next){
+ next = m->next;
+ if(m->start != nil)
+ continue;
+
+ if(!pipeline){
+ Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
+ (ulong)imap->tag, (ulong)m->imapuid);
+ Bflush(&imap->bout);
+ }
+
+ if(s = imap4fetch(mb, m)){
+ // message disappeared? unchain
+ fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
+ delmessage(mb, m);
+ mb->root->subname--;
+ continue;
+ }
+ nnew++;
+ if(doplumb)
+ mailplumb(mb, m, 0);
+ }
+ if(pipeline)
+ waitpid();
+
+ if(nnew || mb->vers == 0){
+ mb->vers++;
+ henter(PATH(0, Qtop), mb->name,
+ (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+ }
+ return nil;
+}
+
+//
+// sync mailbox
+//
+static void
+imap4purge(Imap *imap, Mailbox *mb)
+{
+ int ndel;
+ Message *m, *next;
+
+ ndel = 0;
+ for(m=mb->root->part; m!=nil; m=next){
+ next = m->next;
+ if(m->deleted && m->refs==0){
+ if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
+ imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
+ if(isokay(imap4resp(imap))){
+ ndel++;
+ delmessage(mb, m);
+ }
+ }else
+ delmessage(mb, m);
+ }
+ }
+
+ if(ndel){
+ imap4cmd(imap, "EXPUNGE");
+ imap4resp(imap);
+ }
+}
+
+//
+// connect to imap4 server, sync mailbox
+//
+static char*
+imap4sync(Mailbox *mb, int doplumb)
+{
+ char *err;
+ Imap *imap;
+
+ imap = mb->aux;
+
+ if(err = imap4dial(imap)){
+ mb->waketime = time(0) + imap->refreshtime;
+ return err;
+ }
+
+ if((err = imap4read(imap, mb, doplumb)) == nil){
+ imap4purge(imap, mb);
+ mb->d->atime = mb->d->mtime = time(0);
+ }
+ /*
+ * don't hang up; leave connection open for next time.
+ */
+ // imap4hangup(imap);
+ mb->waketime = time(0) + imap->refreshtime;
+ return err;
+}
+
+static char Eimap4ctl[] = "bad imap4 control message";
+
+static char*
+imap4ctl(Mailbox *mb, int argc, char **argv)
+{
+ int n;
+ Imap *imap;
+
+ imap = mb->aux;
+ if(argc < 1)
+ return Eimap4ctl;
+
+ if(argc==1 && strcmp(argv[0], "debug")==0){
+ imap->debug = 1;
+ return nil;
+ }
+
+ if(argc==1 && strcmp(argv[0], "nodebug")==0){
+ imap->debug = 0;
+ return nil;
+ }
+
+ if(argc==1 && strcmp(argv[0], "thumbprint")==0){
+ if(imap->thumb)
+ freeThumbprints(imap->thumb);
+ imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+ }
+ if(strcmp(argv[0], "refresh")==0){
+ if(argc==1){
+ imap->refreshtime = 60;
+ return nil;
+ }
+ if(argc==2){
+ n = atoi(argv[1]);
+ if(n < 15)
+ return Eimap4ctl;
+ imap->refreshtime = n;
+ return nil;
+ }
+ }
+
+ return Eimap4ctl;
+}
+
+//
+// free extra memory associated with mb
+//
+static void
+imap4close(Mailbox *mb)
+{
+ Imap *imap;
+
+ imap = mb->aux;
+ free(imap->freep);
+ free(imap->base);
+ free(imap->uid);
+ if(imap->fd >= 0)
+ close(imap->fd);
+ free(imap);
+}
+
+//
+// open mailboxes of the form /imap/host/user
+//
+char*
+imap4mbox(Mailbox *mb, char *path)
+{
+ char *f[10];
+ int mustssl, nf;
+ Imap *imap;
+
+ quotefmtinstall();
+ fmtinstall('Z', doublequote);
+ if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
+ return Enotme;
+ mustssl = (strncmp(path, "/imaps/", 7) == 0);
+
+ path = strdup(path);
+ if(path == nil)
+ return "out of memory";
+
+ nf = getfields(path, f, 5, 0, "/");
+ if(nf < 3){
+ free(path);
+ return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
+ }
+
+ imap = emalloc(sizeof(*imap));
+ imap->fd = -1;
+ imap->debug = debug;
+ imap->freep = path;
+ imap->mustssl = mustssl;
+ imap->host = f[2];
+ if(nf < 4)
+ imap->user = nil;
+ else
+ imap->user = f[3];
+ if(nf < 5)
+ imap->mbox = "Inbox";
+ else
+ imap->mbox = f[4];
+ imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+
+ mb->aux = imap;
+ mb->sync = imap4sync;
+ mb->close = imap4close;
+ mb->ctl = imap4ctl;
+ mb->d = emalloc(sizeof(*mb->d));
+ //mb->fetch = imap4fetch;
+
+ return nil;
+}
+
+//
+// Formatter for %"
+// Use double quotes to protect white space, frogs, \ and "
+//
+enum
+{
+ Qok = 0,
+ Qquote,
+ Qbackslash,
+};
+
+static int
+needtoquote(Rune r)
+{
+ if(r >= Runeself)
+ return Qquote;
+ if(r <= ' ')
+ return Qquote;
+ if(r=='\\' || r=='"')
+ return Qbackslash;
+ return Qok;
+}
+
+int
+doublequote(Fmt *f)
+{
+ char *s, *t;
+ int w, quotes;
+ Rune r;
+
+ s = va_arg(f->args, char*);
+ if(s == nil || *s == '\0')
+ return fmtstrcpy(f, "\"\"");
+
+ quotes = 0;
+ for(t=s; *t; t+=w){
+ w = chartorune(&r, t);
+ quotes |= needtoquote(r);
+ }
+ if(quotes == 0)
+ return fmtstrcpy(f, s);
+
+ fmtrune(f, '"');
+ for(t=s; *t; t+=w){
+ w = chartorune(&r, t);
+ if(needtoquote(r) == Qbackslash)
+ fmtrune(f, '\\');
+ fmtrune(f, r);
+ }
+ return fmtrune(f, '"');
+}
diff --git a/src/cmd/upas/fs/mbox.c b/src/cmd/upas/fs/mbox.c
new file mode 100644
index 00000000..c539df8d
--- /dev/null
+++ b/src/cmd/upas/fs/mbox.c
@@ -0,0 +1,1601 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <thread.h>
+#include "dat.h"
+
+extern char* dirtab[]; /* jpc */
+
+typedef struct Header Header;
+
+struct Header {
+ char *type;
+ void (*f)(Message*, Header*, char*);
+ int len;
+};
+
+/* headers */
+static void ctype(Message*, Header*, char*);
+static void cencoding(Message*, Header*, char*);
+static void cdisposition(Message*, Header*, char*);
+static void date822(Message*, Header*, char*);
+static void from822(Message*, Header*, char*);
+static void to822(Message*, Header*, char*);
+static void sender822(Message*, Header*, char*);
+static void replyto822(Message*, Header*, char*);
+static void subject822(Message*, Header*, char*);
+static void inreplyto822(Message*, Header*, char*);
+static void cc822(Message*, Header*, char*);
+static void bcc822(Message*, Header*, char*);
+static void messageid822(Message*, Header*, char*);
+static void mimeversion(Message*, Header*, char*);
+static void nullsqueeze(Message*);
+enum
+{
+ Mhead= 11, /* offset of first mime header */
+};
+
+Header head[] =
+{
+ { "date:", date822, },
+ { "from:", from822, },
+ { "to:", to822, },
+ { "sender:", sender822, },
+ { "reply-to:", replyto822, },
+ { "subject:", subject822, },
+ { "cc:", cc822, },
+ { "bcc:", bcc822, },
+ { "in-reply-to:", inreplyto822, },
+ { "mime-version:", mimeversion, },
+ { "message-id:", messageid822, },
+
+[Mhead] { "content-type:", ctype, },
+ { "content-transfer-encoding:", cencoding, },
+ { "content-disposition:", cdisposition, },
+ { 0, },
+};
+
+/* static void fatal(char *fmt, ...); jpc */
+static void initquoted(void);
+/* static void startheader(Message*);
+static void startbody(Message*); jpc */
+static char* skipwhite(char*);
+static char* skiptosemi(char*);
+static char* getstring(char*, String*, int);
+static void setfilename(Message*, char*);
+/* static char* lowercase(char*); jpc */
+static int is8bit(Message*);
+static int headerline(char**, String*);
+static void initheaders(void);
+static void parseattachments(Message*, Mailbox*);
+
+int debug;
+
+char *Enotme = "path not served by this file server";
+
+enum
+{
+ Chunksize = 1024,
+};
+
+Mailboxinit *boxinit[] = {
+ imap4mbox,
+ pop3mbox,
+ plan9mbox,
+};
+
+char*
+syncmbox(Mailbox *mb, int doplumb)
+{
+ return (*mb->sync)(mb, doplumb);
+}
+
+/* create a new mailbox */
+char*
+newmbox(char *path, char *name, int std)
+{
+ Mailbox *mb, **l;
+ char *p, *rv;
+ int i;
+
+ initheaders();
+
+ mb = emalloc(sizeof(*mb));
+ strncpy(mb->path, path, sizeof(mb->path)-1);
+ if(name == nil){
+ p = strrchr(path, '/');
+ if(p == nil)
+ p = path;
+ else
+ p++;
+ if(*p == 0){
+ free(mb);
+ return "bad mbox name";
+ }
+ strncpy(mb->name, p, sizeof(mb->name)-1);
+ } else {
+ strncpy(mb->name, name, sizeof(mb->name)-1);
+ }
+
+ rv = nil;
+ // check for a mailbox type
+ for(i=0; i<nelem(boxinit); i++)
+ if((rv = (*boxinit[i])(mb, path)) != Enotme)
+ break;
+ if(i == nelem(boxinit)){
+ free(mb);
+ return "bad path";
+ }
+
+ // on error, give up
+ if(rv){
+ free(mb);
+ return rv;
+ }
+
+ // make sure name isn't taken
+ qlock(&mbllock);
+ for(l = &mbl; *l != nil; l = &(*l)->next){
+ if(strcmp((*l)->name, mb->name) == 0){
+ if(strcmp(path, (*l)->path) == 0)
+ rv = nil;
+ else
+ rv = "mbox name in use";
+ if(mb->close)
+ (*mb->close)(mb);
+ free(mb);
+ qunlock(&mbllock);
+ return rv;
+ }
+ }
+
+ // always try locking
+ mb->dolock = 1;
+
+ mb->refs = 1;
+ mb->next = nil;
+ mb->id = newid();
+ mb->root = newmessage(nil);
+ mb->std = std;
+ *l = mb;
+ qunlock(&mbllock);
+
+ qlock(&mb->ql);
+ if(mb->ctl){
+ henter(PATH(mb->id, Qmbox), "ctl",
+ (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
+ }
+ rv = syncmbox(mb, 0);
+ qunlock(&mb->ql);
+
+ return rv;
+}
+
+// close the named mailbox
+void
+freembox(char *name)
+{
+ Mailbox **l, *mb;
+
+ qlock(&mbllock);
+ for(l=&mbl; *l != nil; l=&(*l)->next){
+ if(strcmp(name, (*l)->name) == 0){
+ mb = *l;
+ *l = mb->next;
+ mboxdecref(mb);
+ break;
+ }
+ }
+ hfree(PATH(0, Qtop), name);
+ qunlock(&mbllock);
+}
+
+static void
+initheaders(void)
+{
+ Header *h;
+ static int already;
+
+ if(already)
+ return;
+ already = 1;
+
+ for(h = head; h->type != nil; h++)
+ h->len = strlen(h->type);
+}
+
+/*
+ * parse a Unix style header
+ */
+void
+parseunix(Message *m)
+{
+ char *p;
+ String *h;
+
+ h = s_new();
+ for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
+ s_putc(h, *p);
+ s_terminate(h);
+ s_restart(h);
+
+ m->unixfrom = s_parse(h, s_reset(m->unixfrom));
+ m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
+
+ s_free(h);
+}
+
+/*
+ * parse a message
+ */
+void
+parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
+{
+ String *hl;
+ Header *h;
+ char *p, *q;
+ int i;
+
+ if(m->whole == m->whole->whole){
+ henter(PATH(mb->id, Qmbox), m->name,
+ (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+ } else {
+ henter(PATH(m->whole->id, Qdir), m->name,
+ (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+ }
+ for(i = 0; i < Qmax; i++)
+ henter(PATH(m->id, Qdir), dirtab[i],
+ (Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
+
+ // parse mime headers
+ p = m->header;
+ hl = s_new();
+ while(headerline(&p, hl)){
+ if(justmime)
+ h = &head[Mhead];
+ else
+ h = head;
+ for(; h->type; h++){
+ if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
+ (*h->f)(m, h, s_to_c(hl));
+ break;
+ }
+ }
+ s_reset(hl);
+ }
+ s_free(hl);
+
+ // the blank line isn't really part of the body or header
+ if(justmime){
+ m->mhend = p;
+ m->hend = m->header;
+ } else {
+ m->hend = p;
+ }
+ if(*p == '\n')
+ p++;
+ m->rbody = m->body = p;
+
+ // if type is text, get any nulls out of the body. This is
+ // for the two seans and imap clients that get confused.
+ if(strncmp(s_to_c(m->type), "text/", 5) == 0)
+ nullsqueeze(m);
+
+ //
+ // cobble together Unix-style from line
+ // for local mailbox messages, we end up recreating the
+ // original header.
+ // for pop3 messages, the best we can do is
+ // use the From: information and the RFC822 date.
+ //
+ if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
+ || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
+ if(m->unixdate){
+ s_free(m->unixdate);
+ m->unixdate = nil;
+ }
+ // look for the date in the first Received: line.
+ // it's likely to be the right time zone (it's
+ // the local system) and in a convenient format.
+ if(cistrncmp(m->header, "received:", 9)==0){
+ if((q = strchr(m->header, ';')) != nil){
+ p = q;
+ while((p = strchr(p, '\n')) != nil){
+ if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
+ break;
+ p++;
+ }
+ if(p){
+ *p = '\0';
+ m->unixdate = date822tounix(q+1);
+ *p = '\n';
+ }
+ }
+ }
+
+ // fall back on the rfc822 date
+ if(m->unixdate==nil && m->date822)
+ m->unixdate = date822tounix(s_to_c(m->date822));
+ }
+
+ if(m->unixheader != nil)
+ s_free(m->unixheader);
+
+ // only fake header for top-level messages for pop3 and imap4
+ // clients (those protocols don't include the unix header).
+ // adding the unix header all the time screws up mime-attached
+ // rfc822 messages.
+ if(!addfrom && !m->unixfrom){
+ m->unixheader = nil;
+ return;
+ }
+
+ m->unixheader = s_copy("From ");
+ if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
+ s_append(m->unixheader, s_to_c(m->unixfrom));
+ else if(m->from822)
+ s_append(m->unixheader, s_to_c(m->from822));
+ else
+ s_append(m->unixheader, "???");
+
+ s_append(m->unixheader, " ");
+ if(m->unixdate)
+ s_append(m->unixheader, s_to_c(m->unixdate));
+ else
+ s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970");
+
+ s_append(m->unixheader, "\n");
+}
+
+String*
+promote(String **sp)
+{
+ String *s;
+
+ if(*sp != nil)
+ s = s_clone(*sp);
+ else
+ s = nil;
+ return s;
+}
+
+void
+parsebody(Message *m, Mailbox *mb)
+{
+ Message *nm;
+
+ // recurse
+ if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
+ parseattachments(m, mb);
+ } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+ decode(m);
+ parseattachments(m, mb);
+ nm = m->part;
+
+ // promote headers
+ if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
+ m->from822 = promote(&nm->from822);
+ m->to822 = promote(&nm->to822);
+ m->date822 = promote(&nm->date822);
+ m->sender822 = promote(&nm->sender822);
+ m->replyto822 = promote(&nm->replyto822);
+ m->subject822 = promote(&nm->subject822);
+ m->unixdate = promote(&nm->unixdate);
+ }
+ }
+}
+
+void
+parse(Message *m, int justmime, Mailbox *mb, int addfrom)
+{
+ parseheaders(m, justmime, mb, addfrom);
+ parsebody(m, mb);
+}
+
+static void
+parseattachments(Message *m, Mailbox *mb)
+{
+ Message *nm, **l;
+ char *p, *x;
+
+ // if there's a boundary, recurse...
+ if(m->boundary != nil){
+ p = m->body;
+ nm = nil;
+ l = &m->part;
+ for(;;){
+ x = strstr(p, s_to_c(m->boundary));
+
+ /* no boundary, we're done */
+ if(x == nil){
+ if(nm != nil)
+ nm->rbend = nm->bend = nm->end = m->bend;
+ break;
+ }
+
+ /* boundary must be at the start of a line */
+ if(x != m->body && *(x-1) != '\n'){
+ p = x+1;
+ continue;
+ }
+
+ if(nm != nil)
+ nm->rbend = nm->bend = nm->end = x;
+ x += strlen(s_to_c(m->boundary));
+
+ /* is this the last part? ignore anything after it */
+ if(strncmp(x, "--", 2) == 0)
+ break;
+
+ p = strchr(x, '\n');
+ if(p == nil)
+ break;
+ nm = newmessage(m);
+ nm->start = nm->header = nm->body = nm->rbody = ++p;
+ nm->mheader = nm->header;
+ *l = nm;
+ l = &nm->next;
+ }
+ for(nm = m->part; nm != nil; nm = nm->next)
+ parse(nm, 1, mb, 0);
+ return;
+ }
+
+ // if we've got an rfc822 message, recurse...
+ if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+ nm = newmessage(m);
+ m->part = nm;
+ nm->start = nm->header = nm->body = nm->rbody = m->body;
+ nm->end = nm->bend = nm->rbend = m->bend;
+ parse(nm, 0, mb, 0);
+ }
+}
+
+/*
+ * pick up a header line
+ */
+static int
+headerline(char **pp, String *hl)
+{
+ char *p, *x;
+
+ s_reset(hl);
+ p = *pp;
+ x = strpbrk(p, ":\n");
+ if(x == nil || *x == '\n')
+ return 0;
+ for(;;){
+ x = strchr(p, '\n');
+ if(x == nil)
+ x = p + strlen(p);
+ s_nappend(hl, p, x-p);
+ p = x;
+ if(*p != '\n' || *++p != ' ' && *p != '\t')
+ break;
+ while(*p == ' ' || *p == '\t')
+ p++;
+ s_putc(hl, ' ');
+ }
+ *pp = p;
+ return 1;
+}
+
+static String*
+addr822(char *p)
+{
+ String *s, *list;
+ int incomment, addrdone, inanticomment, quoted;
+ int n;
+ int c;
+
+ list = s_new();
+ s = s_new();
+ quoted = incomment = addrdone = inanticomment = 0;
+ n = 0;
+ for(; *p; p++){
+ c = *p;
+
+ // whitespace is ignored
+ if(!quoted && isspace(c) || c == '\r')
+ continue;
+
+ // strings are always treated as atoms
+ if(!quoted && c == '"'){
+ if(!addrdone && !incomment)
+ s_putc(s, c);
+ for(p++; *p; p++){
+ if(!addrdone && !incomment)
+ s_putc(s, *p);
+ if(!quoted && *p == '"')
+ break;
+ if(*p == '\\')
+ quoted = 1;
+ else
+ quoted = 0;
+ }
+ if(*p == 0)
+ break;
+ quoted = 0;
+ continue;
+ }
+
+ // ignore everything in an expicit comment
+ if(!quoted && c == '('){
+ incomment = 1;
+ continue;
+ }
+ if(incomment){
+ if(!quoted && c == ')')
+ incomment = 0;
+ quoted = 0;
+ continue;
+ }
+
+ // anticomments makes everything outside of them comments
+ if(!quoted && c == '<' && !inanticomment){
+ inanticomment = 1;
+ s = s_reset(s);
+ continue;
+ }
+ if(!quoted && c == '>' && inanticomment){
+ addrdone = 1;
+ inanticomment = 0;
+ continue;
+ }
+
+ // commas separate addresses
+ if(!quoted && c == ',' && !inanticomment){
+ s_terminate(s);
+ addrdone = 0;
+ if(n++ != 0)
+ s_append(list, " ");
+ s_append(list, s_to_c(s));
+ s = s_reset(s);
+ continue;
+ }
+
+ // what's left is part of the address
+ s_putc(s, c);
+
+ // quoted characters are recognized only as characters
+ if(c == '\\')
+ quoted = 1;
+ else
+ quoted = 0;
+
+ }
+
+ if(*s_to_c(s) != 0){
+ s_terminate(s);
+ if(n++ != 0)
+ s_append(list, " ");
+ s_append(list, s_to_c(s));
+ }
+ s_free(s);
+
+ if(n == 0){
+ s_free(list);
+ return nil;
+ }
+ return list;
+}
+
+static void
+to822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->to822);
+ m->to822 = addr822(p);
+}
+
+static void
+cc822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->cc822);
+ m->cc822 = addr822(p);
+}
+
+static void
+bcc822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->bcc822);
+ m->bcc822 = addr822(p);
+}
+
+static void
+from822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->from822);
+ m->from822 = addr822(p);
+}
+
+static void
+sender822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->sender822);
+ m->sender822 = addr822(p);
+}
+
+static void
+replyto822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->replyto822);
+ m->replyto822 = addr822(p);
+}
+
+static void
+mimeversion(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ s_free(m->mimeversion);
+ m->mimeversion = addr822(p);
+}
+
+static void
+killtrailingwhite(char *p)
+{
+ char *e;
+
+ e = p + strlen(p) - 1;
+ while(e > p && isspace(*e))
+ *e-- = 0;
+}
+
+static void
+date822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->date822);
+ m->date822 = s_copy(p);
+ p = s_to_c(m->date822);
+ killtrailingwhite(p);
+}
+
+static void
+subject822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->subject822);
+ m->subject822 = s_copy(p);
+ p = s_to_c(m->subject822);
+ killtrailingwhite(p);
+}
+
+static void
+inreplyto822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->inreplyto822);
+ m->inreplyto822 = s_copy(p);
+ p = s_to_c(m->inreplyto822);
+ killtrailingwhite(p);
+}
+
+static void
+messageid822(Message *m, Header *h, char *p)
+{
+ p += strlen(h->type);
+ p = skipwhite(p);
+ s_free(m->messageid822);
+ m->messageid822 = s_copy(p);
+ p = s_to_c(m->messageid822);
+ killtrailingwhite(p);
+}
+
+static int
+isattribute(char **pp, char *attr)
+{
+ char *p;
+ int n;
+
+ n = strlen(attr);
+ p = *pp;
+ if(cistrncmp(p, attr, n) != 0)
+ return 0;
+ p += n;
+ while(*p == ' ')
+ p++;
+ if(*p++ != '=')
+ return 0;
+ while(*p == ' ')
+ p++;
+ *pp = p;
+ return 1;
+}
+
+static void
+ctype(Message *m, Header *h, char *p)
+{
+ String *s;
+
+ p += h->len;
+ p = skipwhite(p);
+
+ p = getstring(p, m->type, 1);
+
+ while(*p){
+ if(isattribute(&p, "boundary")){
+ s = s_new();
+ p = getstring(p, s, 0);
+ m->boundary = s_reset(m->boundary);
+ s_append(m->boundary, "--");
+ s_append(m->boundary, s_to_c(s));
+ s_free(s);
+ } else if(cistrncmp(p, "multipart", 9) == 0){
+ /*
+ * the first unbounded part of a multipart message,
+ * the preamble, is not displayed or saved
+ */
+ } else if(isattribute(&p, "name")){
+ if(m->filename == nil)
+ setfilename(m, p);
+ } else if(isattribute(&p, "charset")){
+ p = getstring(p, s_reset(m->charset), 0);
+ }
+
+ p = skiptosemi(p);
+ }
+}
+
+static void
+cencoding(Message *m, Header *h, char *p)
+{
+ p += h->len;
+ p = skipwhite(p);
+ if(cistrncmp(p, "base64", 6) == 0)
+ m->encoding = Ebase64;
+ else if(cistrncmp(p, "quoted-printable", 16) == 0)
+ m->encoding = Equoted;
+}
+
+static void
+cdisposition(Message *m, Header *h, char *p)
+{
+ p += h->len;
+ p = skipwhite(p);
+ while(*p){
+ if(cistrncmp(p, "inline", 6) == 0){
+ m->disposition = Dinline;
+ } else if(cistrncmp(p, "attachment", 10) == 0){
+ m->disposition = Dfile;
+ } else if(cistrncmp(p, "filename=", 9) == 0){
+ p += 9;
+ setfilename(m, p);
+ }
+ p = skiptosemi(p);
+ }
+
+}
+
+ulong msgallocd, msgfreed;
+
+Message*
+newmessage(Message *parent)
+{
+ /* static int id; jpc */
+ Message *m;
+
+ msgallocd++;
+
+ m = emalloc(sizeof(*m));
+ memset(m, 0, sizeof(*m));
+ m->disposition = Dnone;
+ m->type = s_copy("text/plain");
+ m->charset = s_copy("iso-8859-1");
+ m->id = newid();
+ if(parent)
+ sprint(m->name, "%d", ++(parent->subname));
+ if(parent == nil)
+ parent = m;
+ m->whole = parent;
+ m->hlen = -1;
+ return m;
+}
+
+// delete a message from a mailbox
+void
+delmessage(Mailbox *mb, Message *m)
+{
+ Message **l;
+ int i;
+
+ mb->vers++;
+ msgfreed++;
+
+ if(m->whole != m){
+ // unchain from parent
+ for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
+ ;
+ if(*l != nil)
+ *l = m->next;
+
+ // clear out of name lookup hash table
+ if(m->whole->whole == m->whole)
+ hfree(PATH(mb->id, Qmbox), m->name);
+ else
+ hfree(PATH(m->whole->id, Qdir), m->name);
+ for(i = 0; i < Qmax; i++)
+ hfree(PATH(m->id, Qdir), dirtab[i]);
+ }
+
+ /* recurse through sub-parts */
+ while(m->part)
+ delmessage(mb, m->part);
+
+ /* free memory */
+ if(m->mallocd)
+ free(m->start);
+ if(m->hallocd)
+ free(m->header);
+ if(m->ballocd)
+ free(m->body);
+ s_free(m->unixfrom);
+ s_free(m->unixdate);
+ s_free(m->unixheader);
+ s_free(m->from822);
+ s_free(m->sender822);
+ s_free(m->to822);
+ s_free(m->bcc822);
+ s_free(m->cc822);
+ s_free(m->replyto822);
+ s_free(m->date822);
+ s_free(m->inreplyto822);
+ s_free(m->subject822);
+ s_free(m->messageid822);
+ s_free(m->addrs);
+ s_free(m->mimeversion);
+ s_free(m->sdigest);
+ s_free(m->boundary);
+ s_free(m->type);
+ s_free(m->charset);
+ s_free(m->filename);
+
+ free(m);
+}
+
+// mark messages (identified by path) for deletion
+void
+delmessages(int ac, char **av)
+{
+ Mailbox *mb;
+ Message *m;
+ int i, needwrite;
+
+ qlock(&mbllock);
+ for(mb = mbl; mb != nil; mb = mb->next)
+ if(strcmp(av[0], mb->name) == 0){
+ qlock(&mb->ql);
+ break;
+ }
+ qunlock(&mbllock);
+ if(mb == nil)
+ return;
+
+ needwrite = 0;
+ for(i = 1; i < ac; i++){
+ for(m = mb->root->part; m != nil; m = m->next)
+ if(strcmp(m->name, av[i]) == 0){
+ if(!m->deleted){
+ mailplumb(mb, m, 1);
+ needwrite = 1;
+ m->deleted = 1;
+ logmsg("deleting", m);
+ }
+ break;
+ }
+ }
+ if(needwrite)
+ syncmbox(mb, 1);
+ qunlock(&mb->ql);
+}
+
+/*
+ * the following are called with the mailbox qlocked
+ */
+void
+msgincref(Message *m)
+{
+ m->refs++;
+}
+void
+msgdecref(Mailbox *mb, Message *m)
+{
+ m->refs--;
+ if(m->refs == 0 && m->deleted)
+ syncmbox(mb, 1);
+}
+
+/*
+ * the following are called with mbllock'd
+ */
+void
+mboxincref(Mailbox *mb)
+{
+ assert(mb->refs > 0);
+ mb->refs++;
+}
+void
+mboxdecref(Mailbox *mb)
+{
+ assert(mb->refs > 0);
+ qlock(&mb->ql);
+ mb->refs--;
+ if(mb->refs == 0){
+ delmessage(mb, mb->root);
+ if(mb->ctl)
+ hfree(PATH(mb->id, Qmbox), "ctl");
+ if(mb->close)
+ (*mb->close)(mb);
+ free(mb);
+ } else
+ qunlock(&mb->ql);
+}
+
+int
+cistrncmp(char *a, char *b, int n)
+{
+ while(n-- > 0){
+ if(tolower(*a++) != tolower(*b++))
+ return -1;
+ }
+ return 0;
+}
+
+int
+cistrcmp(char *a, char *b)
+{
+ for(;;){
+ if(tolower(*a) != tolower(*b++))
+ return -1;
+ if(*a++ == 0)
+ break;
+ }
+ return 0;
+}
+
+static char*
+skipwhite(char *p)
+{
+ while(isspace(*p))
+ p++;
+ return p;
+}
+
+static char*
+skiptosemi(char *p)
+{
+ while(*p && *p != ';')
+ p++;
+ while(*p == ';' || isspace(*p))
+ p++;
+ return p;
+}
+
+static char*
+getstring(char *p, String *s, int dolower)
+{
+ s = s_reset(s);
+ p = skipwhite(p);
+ if(*p == '"'){
+ p++;
+ for(;*p && *p != '"'; p++)
+ if(dolower)
+ s_putc(s, tolower(*p));
+ else
+ s_putc(s, *p);
+ if(*p == '"')
+ p++;
+ s_terminate(s);
+
+ return p;
+ }
+
+ for(; *p && !isspace(*p) && *p != ';'; p++)
+ if(dolower)
+ s_putc(s, tolower(*p));
+ else
+ s_putc(s, *p);
+ s_terminate(s);
+
+ return p;
+}
+
+static void
+setfilename(Message *m, char *p)
+{
+ m->filename = s_reset(m->filename);
+ getstring(p, m->filename, 0);
+ for(p = s_to_c(m->filename); *p; p++)
+ if(*p == ' ' || *p == '\t' || *p == ';')
+ *p = '_';
+}
+
+//
+// undecode message body
+//
+void
+decode(Message *m)
+{
+ int i, len;
+ char *x;
+
+ if(m->decoded)
+ return;
+ switch(m->encoding){
+ case Ebase64:
+ len = m->bend - m->body;
+ i = (len*3)/4+1; // room for max chars + null
+ x = emalloc(i);
+ len = dec64((uchar*)x, i, m->body, len);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ break;
+ case Equoted:
+ len = m->bend - m->body;
+ x = emalloc(len+2); // room for null and possible extra nl
+ len = decquoted(x, m->body, m->bend);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ break;
+ default:
+ break;
+ }
+ m->decoded = 1;
+}
+
+// convert latin1 to utf
+void
+convert(Message *m)
+{
+ int len;
+ char *x;
+
+ // don't convert if we're not a leaf, not text, or already converted
+ if(m->converted)
+ return;
+ if(m->part != nil)
+ return;
+ if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
+ return;
+
+ if(cistrcmp(s_to_c(m->charset), "us-ascii") == 0 ||
+ cistrcmp(s_to_c(m->charset), "iso-8859-1") == 0){
+ len = is8bit(m);
+ if(len > 0){
+ len = 2*len + m->bend - m->body + 1;
+ x = emalloc(len);
+ len = latin1toutf(x, m->body, m->bend);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "iso-8859-2") == 0){
+ len = xtoutf("8859-2", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "iso-8859-15") == 0){
+ len = xtoutf("8859-15", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "big5") == 0){
+ len = xtoutf("big5", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "iso-2022-jp") == 0){
+ len = xtoutf("jis", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "windows-1257") == 0
+ || cistrcmp(s_to_c(m->charset), "windows-1252") == 0){
+ len = is8bit(m);
+ if(len > 0){
+ len = 2*len + m->bend - m->body + 1;
+ x = emalloc(len);
+ len = windows1257toutf(x, m->body, m->bend);
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "windows-1251") == 0){
+ len = xtoutf("cp1251", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ } else if(cistrcmp(s_to_c(m->charset), "koi8-r") == 0){
+ len = xtoutf("koi8", &x, m->body, m->bend);
+ if(len != 0){
+ if(m->ballocd)
+ free(m->body);
+ m->body = x;
+ m->bend = x + len;
+ m->ballocd = 1;
+ }
+ }
+
+ m->converted = 1;
+}
+
+enum
+{
+ Self= 1,
+ Hex= 2,
+};
+uchar tableqp[256];
+
+static void
+initquoted(void)
+{
+ int c;
+
+ memset(tableqp, 0, 256);
+ for(c = ' '; c <= '<'; c++)
+ tableqp[c] = Self;
+ for(c = '>'; c <= '~'; c++)
+ tableqp[c] = Self;
+ tableqp['\t'] = Self;
+ tableqp['='] = Hex;
+}
+
+static int
+hex2int(int x)
+{
+ if(x >= '0' && x <= '9')
+ return x - '0';
+ if(x >= 'A' && x <= 'F')
+ return (x - 'A') + 10;
+ if(x >= 'a' && x <= 'f')
+ return (x - 'a') + 10;
+ return 0;
+}
+
+static char*
+decquotedline(char *out, char *in, char *e)
+{
+ int c, soft;
+
+ /* dump trailing white space */
+ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
+ e--;
+
+ /* trailing '=' means no newline */
+ if(*e == '='){
+ soft = 1;
+ e--;
+ } else
+ soft = 0;
+
+ while(in <= e){
+ c = (*in++) & 0xff;
+ switch(tableqp[c]){
+ case Self:
+ *out++ = c;
+ break;
+ case Hex:
+ c = hex2int(*in++)<<4;
+ c |= hex2int(*in++);
+ *out++ = c;
+ break;
+ }
+ }
+ if(!soft)
+ *out++ = '\n';
+ *out = 0;
+
+ return out;
+}
+
+int
+decquoted(char *out, char *in, char *e)
+{
+ char *p, *nl;
+
+ if(tableqp[' '] == 0)
+ initquoted();
+
+ p = out;
+ while((nl = strchr(in, '\n')) != nil && nl < e){
+ p = decquotedline(p, in, nl);
+ in = nl + 1;
+ }
+ if(in < e)
+ p = decquotedline(p, in, e-1);
+
+ // make sure we end with a new line
+ if(*(p-1) != '\n'){
+ *p++ = '\n';
+ *p = 0;
+ }
+
+ return p - out;
+}
+
+#if 0 /* jpc */
+static char*
+lowercase(char *p)
+{
+ char *op;
+ int c;
+
+ for(op = p; c = *p; p++)
+ if(isupper(c))
+ *p = tolower(c);
+ return op;
+}
+#endif
+
+/*
+ * return number of 8 bit characters
+ */
+static int
+is8bit(Message *m)
+{
+ int count = 0;
+ char *p;
+
+ for(p = m->body; p < m->bend; p++)
+ if(*p & 0x80)
+ count++;
+ return count;
+}
+
+// translate latin1 directly since it fits neatly in utf
+int
+latin1toutf(char *out, char *in, char *e)
+{
+ Rune r;
+ char *p;
+
+ p = out;
+ for(; in < e; in++){
+ r = (*in) & 0xff;
+ p += runetochar(p, &r);
+ }
+ *p = 0;
+ return p - out;
+}
+
+// translate any thing else using the tcs program
+int
+xtoutf(char *charset, char **out, char *in, char *e)
+{
+ char *av[4];
+ int totcs[2];
+ int fromtcs[2];
+ int n, len, sofar;
+ char *p;
+
+ len = e-in+1;
+ sofar = 0;
+ *out = p = malloc(len+1);
+ if(p == nil)
+ return 0;
+
+ av[0] = charset;
+ av[1] = "-f";
+ av[2] = charset;
+ av[3] = 0;
+ if(pipe(totcs) < 0)
+ return 0;
+ if(pipe(fromtcs) < 0){
+ close(totcs[0]); close(totcs[1]);
+ return 0;
+ }
+ switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
+ case -1:
+ close(fromtcs[0]); close(fromtcs[1]);
+ close(totcs[0]); close(totcs[1]);
+ return 0;
+ case 0:
+ close(fromtcs[0]); close(totcs[1]);
+ dup(fromtcs[1], 1);
+ dup(totcs[0], 0);
+ close(fromtcs[1]); close(totcs[0]);
+ dup(open("/dev/null", OWRITE), 2);
+ //jpc exec("/bin/tcs", av);
+ exec(unsharp("#9/bin/tcs"), av);
+ /* _exits(0); */
+ threadexits(nil);
+ default:
+ close(fromtcs[1]); close(totcs[0]);
+ switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
+ case -1:
+ close(fromtcs[0]); close(totcs[1]);
+ return 0;
+ case 0:
+ close(fromtcs[0]);
+ while(in < e){
+ n = write(totcs[1], in, e-in);
+ if(n <= 0)
+ break;
+ in += n;
+ }
+ close(totcs[1]);
+ /* _exits(0); */
+ threadexits(nil);
+ default:
+ close(totcs[1]);
+ for(;;){
+ n = read(fromtcs[0], &p[sofar], len-sofar);
+ if(n <= 0)
+ break;
+ sofar += n;
+ p[sofar] = 0;
+ if(sofar == len){
+ len += 1024;
+ *out = p = realloc(p, len+1);
+ if(p == nil)
+ return 0;
+ }
+ }
+ close(fromtcs[0]);
+ break;
+ }
+ break;
+ }
+ return sofar;
+}
+
+enum {
+ Winstart= 0x7f,
+ Winend= 0x9f,
+};
+
+Rune winchars[] = {
+ L'•',
+ L'•', L'•', L'‚', L'ƒ', L'„', L'…', L'†', L'‡',
+ L'ˆ', L'‰', L'Š', L'‹', L'Œ', L'•', L'•', L'•',
+ L'•', L'‘', L'’', L'“', L'”', L'•', L'–', L'—',
+ L'˜', L'™', L'š', L'›', L'œ', L'•', L'•', L'Ÿ',
+};
+
+int
+windows1257toutf(char *out, char *in, char *e)
+{
+ Rune r;
+ char *p;
+
+ p = out;
+ for(; in < e; in++){
+ r = (*in) & 0xff;
+ if(r >= 0x7f && r <= 0x9f)
+ r = winchars[r-0x7f];
+ p += runetochar(p, &r);
+ }
+ *p = 0;
+ return p - out;
+}
+
+void *
+emalloc(ulong n)
+{
+ void *p;
+
+ p = mallocz(n, 1);
+ if(!p){
+ fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
+ threadexits("out of memory");
+ }
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void *
+erealloc(void *p, ulong n)
+{
+ if(n == 0)
+ n = 1;
+ p = realloc(p, n);
+ if(!p){
+ fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
+ threadexits("out of memory");
+ }
+ setrealloctag(p, getcallerpc(&p));
+ return p;
+}
+
+void
+mailplumb(Mailbox *mb, Message *m, int delete)
+{
+ Plumbmsg p;
+ Plumbattr a[7];
+ char buf[256];
+ int ai;
+ char lenstr[10], *from, *subject, *date;
+ static int fd = -1;
+
+ if(m->subject822 == nil)
+ subject = "";
+ else
+ subject = s_to_c(m->subject822);
+
+ if(m->from822 != nil)
+ from = s_to_c(m->from822);
+ else if(m->unixfrom != nil)
+ from = s_to_c(m->unixfrom);
+ else
+ from = "";
+
+ if(m->unixdate != nil)
+ date = s_to_c(m->unixdate);
+ else
+ date = "";
+
+ sprint(lenstr, "%ld", m->end-m->start);
+
+ if(biffing && !delete)
+ print("[ %s / %s / %s ]\n", from, subject, lenstr);
+
+ if(!plumbing)
+ return;
+
+ if(fd < 0)
+ fd = plumbopen("send", OWRITE);
+ if(fd < 0)
+ return;
+
+ p.src = "mailfs";
+ p.dst = "seemail";
+ p.wdir = "/mail/fs";
+ p.type = "text";
+
+ ai = 0;
+ a[ai].name = "filetype";
+ a[ai].value = "mail";
+
+ a[++ai].name = "sender";
+ a[ai].value = from;
+ a[ai-1].next = &a[ai];
+
+ a[++ai].name = "length";
+ a[ai].value = lenstr;
+ a[ai-1].next = &a[ai];
+
+ a[++ai].name = "mailtype";
+ a[ai].value = delete?"delete":"new";
+ a[ai-1].next = &a[ai];
+
+ a[++ai].name = "date";
+ a[ai].value = date;
+ a[ai-1].next = &a[ai];
+
+ if(m->sdigest){
+ a[++ai].name = "digest";
+ a[ai].value = s_to_c(m->sdigest);
+ a[ai-1].next = &a[ai];
+ }
+
+ a[ai].next = nil;
+
+ p.attr = a;
+ snprint(buf, sizeof(buf), "%s/%s/%s",
+ mntpt, mb->name, m->name);
+ p.ndata = strlen(buf);
+ p.data = buf;
+
+ plumbsend(fd, &p);
+}
+
+//
+// count the number of lines in the body (for imap4)
+//
+void
+countlines(Message *m)
+{
+ int i;
+ char *p;
+
+ i = 0;
+ for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
+ i++;
+ sprint(m->lines, "%d", i);
+}
+
+char *LOG = "fs";
+
+void
+logmsg(char *s, Message *m)
+{
+ int pid;
+
+ if(!logging)
+ return;
+ pid = getpid();
+ if(m == nil)
+ syslog(0, LOG, "%s.%d: %s", user, pid, s);
+ else
+ syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
+ user, pid, s,
+ m->from822 ? s_to_c(m->from822) : "?",
+ s_to_c(m->sdigest));
+}
+
+/*
+ * squeeze nulls out of the body
+ */
+static void
+nullsqueeze(Message *m)
+{
+ char *p, *q;
+
+ q = memchr(m->body, 0, m->end-m->body);
+ if(q == nil)
+ return;
+
+ for(p = m->body; q < m->end; q++){
+ if(*q == 0)
+ continue;
+ *p++ = *q;
+ }
+ m->bend = m->rbend = m->end = p;
+}
+
+
+//
+// convert an RFC822 date into a Unix style date
+// for when the Unix From line isn't there (e.g. POP3).
+// enough client programs depend on having a Unix date
+// that it's easiest to write this conversion code once, right here.
+//
+// people don't follow RFC822 particularly closely,
+// so we use strtotm, which is a bunch of heuristics.
+//
+
+extern int strtotm(char*, Tm*);
+String*
+date822tounix(char *s)
+{
+ char *p, *q;
+ Tm tm;
+
+ if(strtotm(s, &tm) < 0)
+ return nil;
+
+ p = asctime(&tm);
+ if(q = strchr(p, '\n'))
+ *q = '\0';
+ return s_copy(p);
+}
+
diff --git a/src/cmd/upas/fs/mkfile b/src/cmd/upas/fs/mkfile
new file mode 100644
index 00000000..c596ddfc
--- /dev/null
+++ b/src/cmd/upas/fs/mkfile
@@ -0,0 +1,29 @@
+<$PLAN9/src/mkhdr
+
+TARG= fs\
+
+OFILES=\
+ fs.$O\
+ imap4.$O\
+ mbox.$O\
+ plan9.$O\
+ pop3.$O\
+ strtotm.$O\
+
+LIB=../common/libcommon.a\
+# /usr/local/plan9/lib/libthread.a
+
+HFILES= ../common/common.h\
+ dat.h
+
+BIN=$PLAN9/bin/upas
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${TARG:%=%.c}\
+ ${OFILES:%.$O=%.c}\
+
+<$PLAN9/src/mkone
+CFLAGS=$CFLAGS -I../common
+# CFLAGS=$CFLAGS -I/sys/include -I../common
diff --git a/src/cmd/upas/fs/mkfile.9 b/src/cmd/upas/fs/mkfile.9
new file mode 100644
index 00000000..05239549
--- /dev/null
+++ b/src/cmd/upas/fs/mkfile.9
@@ -0,0 +1,27 @@
+</$objtype/mkfile
+
+TARG= fs\
+
+OFILES=\
+ fs.$O\
+ imap4.$O\
+ mbox.$O\
+ plan9.$O\
+ pop3.$O\
+ strtotm.$O\
+
+LIB=../common/libcommon.a$O\
+
+HFILES= ../common/common.h\
+ dat.h
+
+BIN=/$objtype/bin/upas
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${TARG:%=%.c}\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+CFLAGS=$CFLAGS -I/sys/include -I../common
diff --git a/src/cmd/upas/fs/plan9.c b/src/cmd/upas/fs/plan9.c
new file mode 100644
index 00000000..89ee2922
--- /dev/null
+++ b/src/cmd/upas/fs/plan9.c
@@ -0,0 +1,405 @@
+#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;
+}
diff --git a/src/cmd/upas/fs/pop3.c b/src/cmd/upas/fs/pop3.c
new file mode 100644
index 00000000..4ea9adb3
--- /dev/null
+++ b/src/cmd/upas/fs/pop3.c
@@ -0,0 +1,700 @@
+#include "common.h"
+#include <ctype.h>
+#include <plumb.h>
+#include <libsec.h>
+#include <auth.h>
+#include <thread.h>
+#include "dat.h"
+
+#pragma varargck type "M" uchar*
+#pragma varargck argpos pop3cmd 2
+
+typedef struct Pop Pop;
+struct Pop {
+ char *freep; // free this to free the strings below
+
+ char *host;
+ char *user;
+ char *port;
+
+ int ppop;
+ int refreshtime;
+ int debug;
+ int pipeline;
+ int encrypted;
+ int needtls;
+ int notls;
+ int needssl;
+
+ // open network connection
+ Biobuf bin;
+ Biobuf bout;
+ int fd;
+ char *lastline; // from Brdstr
+
+ Thumbprint *thumb;
+};
+
+char*
+geterrstr(void)
+{
+ static char err[64];
+
+ err[0] = '\0';
+ errstr(err, sizeof(err));
+ return err;
+}
+
+//
+// get pop3 response line , without worrying
+// about multiline responses; the clients
+// will deal with that.
+//
+static int
+isokay(char *s)
+{
+ return s!=nil && strncmp(s, "+OK", 3)==0;
+}
+
+static void
+pop3cmd(Pop *pop, char *fmt, ...)
+{
+ char buf[128], *p;
+ va_list va;
+
+ va_start(va, fmt);
+ vseprint(buf, buf+sizeof(buf), fmt, va);
+ va_end(va);
+
+ p = buf+strlen(buf);
+ if(p > (buf+sizeof(buf)-3))
+ sysfatal("pop3 command too long");
+
+ if(pop->debug)
+ fprint(2, "<- %s\n", buf);
+ strcpy(p, "\r\n");
+ Bwrite(&pop->bout, buf, strlen(buf));
+ Bflush(&pop->bout);
+}
+
+static char*
+pop3resp(Pop *pop)
+{
+ char *s;
+ char *p;
+
+ alarm(60*1000);
+ if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
+ close(pop->fd);
+ pop->fd = -1;
+ alarm(0);
+ return "unexpected eof";
+ }
+ alarm(0);
+
+ p = s+strlen(s)-1;
+ while(p >= s && (*p == '\r' || *p == '\n'))
+ *p-- = '\0';
+
+ if(pop->debug)
+ fprint(2, "-> %s\n", s);
+ free(pop->lastline);
+ pop->lastline = s;
+ return s;
+}
+
+#if 0 /* jpc */
+static int
+pop3log(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap,fmt);
+ syslog(0, "/sys/log/pop3", fmt, ap);
+ va_end(ap);
+ return 0;
+}
+#endif
+
+static char*
+pop3pushtls(Pop *pop)
+{
+ int fd;
+ uchar digest[SHA1dlen];
+ TLSconn conn;
+
+ memset(&conn, 0, sizeof conn);
+ // conn.trace = pop3log;
+ fd = tlsClient(pop->fd, &conn);
+ if(fd < 0)
+ return "tls error";
+ if(conn.cert==nil || conn.certlen <= 0){
+ close(fd);
+ return "server did not provide TLS certificate";
+ }
+ sha1(conn.cert, conn.certlen, digest, nil);
+ if(!pop->thumb || !okThumbprint(digest, pop->thumb)){
+ fmtinstall('H', encodefmt);
+ close(fd);
+ free(conn.cert);
+ fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
+ return "bad server certificate";
+ }
+ free(conn.cert);
+ close(pop->fd);
+ pop->fd = fd;
+ pop->encrypted = 1;
+ Binit(&pop->bin, pop->fd, OREAD);
+ Binit(&pop->bout, pop->fd, OWRITE);
+ return nil;
+}
+
+//
+// get capability list, possibly start tls
+//
+static char*
+pop3capa(Pop *pop)
+{
+ char *s;
+ int hastls;
+
+ pop3cmd(pop, "CAPA");
+ if(!isokay(pop3resp(pop)))
+ return nil;
+
+ hastls = 0;
+ for(;;){
+ s = pop3resp(pop);
+ if(strcmp(s, ".") == 0 || strcmp(s, "unexpected eof") == 0)
+ break;
+ if(strcmp(s, "STLS") == 0)
+ hastls = 1;
+ if(strcmp(s, "PIPELINING") == 0)
+ pop->pipeline = 1;
+ }
+
+ if(hastls && !pop->notls){
+ pop3cmd(pop, "STLS");
+ if(!isokay(s = pop3resp(pop)))
+ return s;
+ if((s = pop3pushtls(pop)) != nil)
+ return s;
+ }
+ return nil;
+}
+
+//
+// log in using APOP if possible, password if allowed by user
+//
+static char*
+pop3login(Pop *pop)
+{
+ int n;
+ char *s, *p, *q;
+ char ubuf[128], user[128];
+ char buf[500];
+ UserPasswd *up;
+
+ s = pop3resp(pop);
+ if(!isokay(s))
+ return "error in initial handshake";
+
+ if(pop->user)
+ snprint(ubuf, sizeof ubuf, " user=%q", pop->user);
+ else
+ ubuf[0] = '\0';
+
+ // look for apop banner
+ if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
+ *++q = '\0';
+ if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
+ pop->host, ubuf)) < 0)
+ return "factotum failed";
+ if(user[0]=='\0')
+ return "factotum did not return a user name";
+
+ if(s = pop3capa(pop))
+ return s;
+
+ pop3cmd(pop, "APOP %s %.*s", user, n, buf);
+ if(!isokay(s = pop3resp(pop)))
+ return s;
+
+ return nil;
+ } else {
+ if(pop->ppop == 0)
+ return "no APOP hdr from server";
+
+ if(s = pop3capa(pop))
+ return s;
+
+ if(pop->needtls && !pop->encrypted)
+ return "could not negotiate TLS";
+
+ up = auth_getuserpasswd(auth_getkey, "role=client proto=pass service=pop dom=%q%s",
+ pop->host, ubuf);
+ /* up = auth_getuserpasswd(auth_getkey, "proto=pass service=pop dom=%q%s",
+ pop->host, ubuf); jpc */
+ if(up == nil)
+ return "no usable keys found";
+
+ pop3cmd(pop, "USER %s", up->user);
+ if(!isokay(s = pop3resp(pop))){
+ free(up);
+ return s;
+ }
+ pop3cmd(pop, "PASS %s", up->passwd);
+ free(up);
+ if(!isokay(s = pop3resp(pop)))
+ return s;
+
+ return nil;
+ }
+}
+
+//
+// dial and handshake with pop server
+//
+static char*
+pop3dial(Pop *pop)
+{
+ char *err;
+
+ if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
+ return geterrstr();
+
+ if(pop->needssl){
+ if((err = pop3pushtls(pop)) != nil)
+ return err;
+ }else{
+ Binit(&pop->bin, pop->fd, OREAD);
+ Binit(&pop->bout, pop->fd, OWRITE);
+ }
+
+ if(err = pop3login(pop)) {
+ close(pop->fd);
+ return err;
+ }
+
+ return nil;
+}
+
+//
+// close connection
+//
+static void
+pop3hangup(Pop *pop)
+{
+ pop3cmd(pop, "QUIT");
+ pop3resp(pop);
+ close(pop->fd);
+}
+
+//
+// download a single message
+//
+static char*
+pop3download(Pop *pop, Message *m)
+{
+ char *s, *f[3], *wp, *ep;
+ char sdigest[SHA1dlen*2+1];
+ int i, l, sz;
+
+ if(!pop->pipeline)
+ pop3cmd(pop, "LIST %d", m->mesgno);
+ if(!isokay(s = pop3resp(pop)))
+ return s;
+
+ if(tokenize(s, f, 3) != 3)
+ return "syntax error in LIST response";
+
+ if(atoi(f[1]) != m->mesgno)
+ return "out of sync with pop3 server";
+
+ sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */
+ if(sz == 0)
+ return "invalid size in LIST response";
+
+ m->start = wp = emalloc(sz+1);
+ ep = wp+sz;
+
+ if(!pop->pipeline)
+ pop3cmd(pop, "RETR %d", m->mesgno);
+ if(!isokay(s = pop3resp(pop))) {
+ m->start = nil;
+ free(wp);
+ return s;
+ }
+
+ s = nil;
+ while(wp <= ep) {
+ s = pop3resp(pop);
+ if(strcmp(s, "unexpected eof") == 0) {
+ free(m->start);
+ m->start = nil;
+ return "unexpected end of conversation";
+ }
+ if(strcmp(s, ".") == 0)
+ break;
+
+ l = strlen(s)+1;
+ if(s[0] == '.') {
+ s++;
+ l--;
+ }
+ /*
+ * grow by 10%/200bytes - some servers
+ * lie about message sizes
+ */
+ if(wp+l > ep) {
+ int pos = wp - m->start;
+ sz += ((sz / 10) < 200)? 200: sz/10;
+ m->start = erealloc(m->start, sz+1);
+ wp = m->start+pos;
+ ep = m->start+sz;
+ }
+ memmove(wp, s, l-1);
+ wp[l-1] = '\n';
+ wp += l;
+ }
+
+ if(s == nil || strcmp(s, ".") != 0)
+ return "out of sync with pop3 server";
+
+ m->end = wp;
+
+ // make sure there's a trailing null
+ // (helps in body searches)
+ *m->end = 0;
+ m->bend = m->rbend = m->end;
+ m->header = m->start;
+
+ // 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 nil;
+}
+
+//
+// check for new messages on pop server
+// UIDL is not required by RFC 1939, but
+// netscape requires it, so almost every server supports it.
+// we'll use it to make our lives easier.
+//
+static char*
+pop3read(Pop *pop, Mailbox *mb, int doplumb)
+{
+ char *s, *p, *uidl, *f[2];
+ int mesgno, ignore, nnew;
+ Message *m, *next, **l;
+
+ // Some POP servers disallow UIDL if the maildrop is empty.
+ pop3cmd(pop, "STAT");
+ if(!isokay(s = pop3resp(pop)))
+ return s;
+
+ // fetch message listing; note messages to grab
+ l = &mb->root->part;
+ if(strncmp(s, "+OK 0 ", 6) != 0) {
+ pop3cmd(pop, "UIDL");
+ if(!isokay(s = pop3resp(pop)))
+ return s;
+
+ for(;;){
+ p = pop3resp(pop);
+ if(strcmp(p, ".") == 0 || strcmp(p, "unexpected eof") == 0)
+ break;
+
+ if(tokenize(p, f, 2) != 2)
+ continue;
+
+ mesgno = atoi(f[0]);
+ uidl = f[1];
+ if(strlen(uidl) > 75) // RFC 1939 says 70 characters max
+ continue;
+
+ ignore = 0;
+ while(*l != nil) {
+ if(strcmp((*l)->uidl, uidl) == 0) {
+ // matches mail we already have, note mesgno for deletion
+ (*l)->mesgno = mesgno;
+ ignore = 1;
+ l = &(*l)->next;
+ break;
+ } else {
+ // old mail no longer in box mark deleted
+ if(doplumb)
+ mailplumb(mb, *l, 1);
+ (*l)->inmbox = 0;
+ (*l)->deleted = 1;
+ l = &(*l)->next;
+ }
+ }
+ if(ignore)
+ continue;
+
+ m = newmessage(mb->root);
+ m->mallocd = 1;
+ m->inmbox = 1;
+ m->mesgno = mesgno;
+ strcpy(m->uidl, uidl);
+
+ // chain in; will fill in message later
+ *l = m;
+ l = &m->next;
+ }
+ }
+
+ // whatever is left has been removed from the mbox, mark as deleted
+ while(*l != nil) {
+ if(doplumb)
+ mailplumb(mb, *l, 1);
+ (*l)->inmbox = 0;
+ (*l)->deleted = 1;
+ l = &(*l)->next;
+ }
+
+ // download new messages
+ nnew = 0;
+ if(pop->pipeline){
+ switch(rfork(RFPROC|RFMEM)){
+ case -1:
+ fprint(2, "rfork: %r\n");
+ pop->pipeline = 0;
+
+ default:
+ break;
+
+ case 0:
+ for(m = mb->root->part; m != nil; m = m->next){
+ if(m->start != nil)
+ continue;
+ Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
+ }
+ Bflush(&pop->bout);
+ threadexits(nil);
+ /* _exits(nil); jpc */
+ }
+ }
+
+ for(m = mb->root->part; m != nil; m = next) {
+ next = m->next;
+
+ if(m->start != nil)
+ continue;
+
+ if(s = pop3download(pop, m)) {
+ // message disappeared? unchain
+ fprint(2, "download %d: %s\n", m->mesgno, s);
+ delmessage(mb, m);
+ mb->root->subname--;
+ continue;
+ }
+ nnew++;
+ parse(m, 0, mb, 1);
+
+ if(doplumb)
+ mailplumb(mb, m, 0);
+ }
+ if(pop->pipeline)
+ waitpid();
+
+ if(nnew || mb->vers == 0) {
+ mb->vers++;
+ henter(PATH(0, Qtop), mb->name,
+ (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+ }
+
+ return nil;
+}
+
+//
+// delete marked messages
+//
+static void
+pop3purge(Pop *pop, Mailbox *mb)
+{
+ Message *m, *next;
+
+ if(pop->pipeline){
+ switch(rfork(RFPROC|RFMEM)){
+ case -1:
+ fprint(2, "rfork: %r\n");
+ pop->pipeline = 0;
+
+ default:
+ break;
+
+ case 0:
+ for(m = mb->root->part; m != nil; m = next){
+ next = m->next;
+ if(m->deleted && m->refs == 0){
+ if(m->inmbox)
+ Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
+ }
+ }
+ Bflush(&pop->bout);
+ /* _exits(nil); jpc */
+ threadexits(nil);
+ }
+ }
+ for(m = mb->root->part; m != nil; m = next) {
+ next = m->next;
+ if(m->deleted && m->refs == 0) {
+ if(m->inmbox) {
+ if(!pop->pipeline)
+ pop3cmd(pop, "DELE %d", m->mesgno);
+ if(isokay(pop3resp(pop)))
+ delmessage(mb, m);
+ } else
+ delmessage(mb, m);
+ }
+ }
+}
+
+
+// connect to pop3 server, sync mailbox
+static char*
+pop3sync(Mailbox *mb, int doplumb)
+{
+ char *err;
+ Pop *pop;
+
+ pop = mb->aux;
+
+ if(err = pop3dial(pop)) {
+ mb->waketime = time(0) + pop->refreshtime;
+ return err;
+ }
+
+ if((err = pop3read(pop, mb, doplumb)) == nil){
+ pop3purge(pop, mb);
+ mb->d->atime = mb->d->mtime = time(0);
+ }
+ pop3hangup(pop);
+ mb->waketime = time(0) + pop->refreshtime;
+ return err;
+}
+
+static char Epop3ctl[] = "bad pop3 control message";
+
+static char*
+pop3ctl(Mailbox *mb, int argc, char **argv)
+{
+ int n;
+ Pop *pop;
+ char *m, *me;
+
+ pop = mb->aux;
+ if(argc < 1)
+ return Epop3ctl;
+
+ if(argc==1 && strcmp(argv[0], "debug")==0){
+ pop->debug = 1;
+ return nil;
+ }
+
+ if(argc==1 && strcmp(argv[0], "nodebug")==0){
+ pop->debug = 0;
+ return nil;
+ }
+
+ if(argc==1 && strcmp(argv[0], "thumbprint")==0){
+ if(pop->thumb)
+ freeThumbprints(pop->thumb);
+ /* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
+ m = unsharp("#9/sys/lib/tls/mail");
+ me = unsharp("#9/sys/lib/tls/mail.exclude");
+ pop->thumb = initThumbprints(m, me);
+ }
+ if(strcmp(argv[0], "refresh")==0){
+ if(argc==1){
+ pop->refreshtime = 60;
+ return nil;
+ }
+ if(argc==2){
+ n = atoi(argv[1]);
+ if(n < 15)
+ return Epop3ctl;
+ pop->refreshtime = n;
+ return nil;
+ }
+ }
+
+ return Epop3ctl;
+}
+
+// free extra memory associated with mb
+static void
+pop3close(Mailbox *mb)
+{
+ Pop *pop;
+
+ pop = mb->aux;
+ free(pop->freep);
+ free(pop);
+}
+
+//
+// open mailboxes of the form /pop/host/user or /apop/host/user
+//
+char*
+pop3mbox(Mailbox *mb, char *path)
+{
+ char *f[10];
+ int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
+ Pop *pop;
+ char *m, *me;
+
+ quotefmtinstall();
+ popssl = strncmp(path, "/pops/", 6) == 0;
+ apopssl = strncmp(path, "/apops/", 7) == 0;
+ poptls = strncmp(path, "/poptls/", 8) == 0;
+ popnotls = strncmp(path, "/popnotls/", 10) == 0;
+ ppop = popssl || poptls || popnotls || strncmp(path, "/pop/", 5) == 0;
+ apoptls = strncmp(path, "/apoptls/", 9) == 0;
+ apopnotls = strncmp(path, "/apopnotls/", 11) == 0;
+ apop = apopssl || apoptls || apopnotls || strncmp(path, "/apop/", 6) == 0;
+
+ if(!ppop && !apop)
+ return Enotme;
+
+ path = strdup(path);
+ if(path == nil)
+ return "out of memory";
+
+ nf = getfields(path, f, nelem(f), 0, "/");
+ if(nf != 3 && nf != 4) {
+ free(path);
+ return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
+ }
+
+ pop = emalloc(sizeof(*pop));
+ pop->freep = path;
+ pop->host = f[2];
+ if(nf < 4)
+ pop->user = nil;
+ else
+ pop->user = f[3];
+ pop->ppop = ppop;
+ pop->needssl = popssl || apopssl;
+ pop->needtls = poptls || apoptls;
+ pop->refreshtime = 60;
+ pop->notls = popnotls || apopnotls;
+ /* pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); jpc */
+ m = unsharp("#9/sys/lib/tls/mail");
+ me = unsharp("#9/sys/lib/tls/mail.exclude");
+ pop->thumb = initThumbprints(m, me);
+
+ mb->aux = pop;
+ mb->sync = pop3sync;
+ mb->close = pop3close;
+ mb->ctl = pop3ctl;
+ mb->d = emalloc(sizeof(*mb->d));
+
+ return nil;
+}
+
diff --git a/src/cmd/upas/fs/readdir.c b/src/cmd/upas/fs/readdir.c
new file mode 100644
index 00000000..42028d4c
--- /dev/null
+++ b/src/cmd/upas/fs/readdir.c
@@ -0,0 +1,15 @@
+#include <u.h>
+#include <libc.h>
+
+void
+main(void)
+{
+ Dir d;
+ int fd, n;
+
+ fd = open("/mail/fs", OREAD);
+ while((n = dirread(fd, &d, sizeof(d))) > 0){
+ print("%s\n", d.name);
+ }
+ print("n = %d\n", n);
+}
diff --git a/src/cmd/upas/fs/strtotm.c b/src/cmd/upas/fs/strtotm.c
new file mode 100644
index 00000000..bcf0bcee
--- /dev/null
+++ b/src/cmd/upas/fs/strtotm.c
@@ -0,0 +1,113 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+
+static char*
+skiptext(char *q)
+{
+ while(*q!='\0' && *q!=' ' && *q!='\t' && *q!='\r' && *q!='\n')
+ q++;
+ return q;
+}
+
+static char*
+skipwhite(char *q)
+{
+ while(*q==' ' || *q=='\t' || *q=='\r' || *q=='\n')
+ q++;
+ return q;
+}
+
+static char* months[] = {
+ "jan", "feb", "mar", "apr",
+ "may", "jun", "jul", "aug",
+ "sep", "oct", "nov", "dec"
+};
+
+static int
+strcmplwr(char *a, char *b, int n)
+{
+ char *eb;
+
+ eb = b+n;
+ while(*a && *b && b<eb){
+ if(tolower(*a) != tolower(*b))
+ return 1;
+ a++;
+ b++;
+ }
+ if(b==eb)
+ return 0;
+ return *a != *b;
+}
+
+int
+strtotm(char *p, Tm *tmp)
+{
+ char *q, *r;
+ int j;
+ Tm tm;
+ int delta;
+
+ delta = 0;
+ memset(&tm, 0, sizeof(tm));
+ tm.mon = -1;
+ tm.hour = -1;
+ tm.min = -1;
+ tm.year = -1;
+ tm.mday = -1;
+ for(p=skipwhite(p); *p; p=skipwhite(q)){
+ q = skiptext(p);
+
+ /* look for time in hh:mm[:ss] */
+ if(r = memchr(p, ':', q-p)){
+ tm.hour = strtol(p, 0, 10);
+ tm.min = strtol(r+1, 0, 10);
+ if(r = memchr(r+1, ':', q-(r+1)))
+ tm.sec = strtol(r+1, 0, 10);
+ else
+ tm.sec = 0;
+ continue;
+ }
+
+ /* look for month */
+ for(j=0; j<12; j++)
+ if(strcmplwr(p, months[j], 3)==0){
+ tm.mon = j;
+ break;
+ }
+
+ if(j!=12)
+ continue;
+
+ /* look for time zone [A-Z][A-Z]T */
+ if(q-p==3 && 'A' <= p[0] && p[0] <= 'Z'
+ && 'A' <= p[1] && p[1] <= 'Z' && p[2] == 'T'){
+ strecpy(tm.zone, tm.zone+4, p);
+ continue;
+ }
+
+ if(p[0]=='+'||p[0]=='-')
+ if(q-p==5 && strspn(p+1, "0123456789") == 4){
+ delta = (((p[1]-'0')*10+p[2]-'0')*60+(p[3]-'0')*10+p[4]-'0')*60;
+ if(p[0] == '-')
+ delta = -delta;
+ continue;
+ }
+ if(strspn(p, "0123456789") == q-p){
+ j = strtol(p, nil, 10);
+ if(1 <= j && j <= 31)
+ tm.mday = j;
+ if(j >= 1900)
+ tm.year = j-1900;
+ }
+ }
+
+ if(tm.mon<0 || tm.year<0
+ || tm.hour<0 || tm.min<0
+ || tm.mday<0)
+ return -1;
+
+ *tmp = *localtime(tm2sec(&tm)-delta);
+ return 0;
+}
diff --git a/src/cmd/upas/fs/tester.c b/src/cmd/upas/fs/tester.c
new file mode 100644
index 00000000..3d24012e
--- /dev/null
+++ b/src/cmd/upas/fs/tester.c
@@ -0,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <String.h>
+#include "message.h"
+
+Message *root;
+
+void
+prindent(int i)
+{
+ for(; i > 0; i--)
+ print(" ");
+}
+
+void
+prstring(int indent, char *tag, String *s)
+{
+ if(s == nil)
+ return;
+ prindent(indent+1);
+ print("%s %s\n", tag, s_to_c(s));
+}
+
+void
+info(int indent, int mno, Message *m)
+{
+ int i;
+ Message *nm;
+
+ prindent(indent);
+ print("%d%c %d ", mno, m->allocated?'*':' ', m->end - m->start);
+ if(m->unixfrom != nil)
+ print("uf %s ", s_to_c(m->unixfrom));
+ if(m->unixdate != nil)
+ print("ud %s ", s_to_c(m->unixdate));
+ print("\n");
+ prstring(indent, "from:", m->from822);
+ prstring(indent, "sender:", m->sender822);
+ prstring(indent, "to:", m->to822);
+ prstring(indent, "cc:", m->cc822);
+ prstring(indent, "reply-to:", m->replyto822);
+ prstring(indent, "subject:", m->subject822);
+ prstring(indent, "date:", m->date822);
+ prstring(indent, "filename:", m->filename);
+ prstring(indent, "type:", m->type);
+ prstring(indent, "charset:", m->charset);
+
+ i = 1;
+ for(nm = m->part; nm != nil; nm = nm->next){
+ info(indent+1, i++, nm);
+ }
+}
+
+
+void
+main(int argc, char **argv)
+{
+ char *err;
+ char *mboxfile;
+
+ ARGBEGIN{
+ }ARGEND;
+
+ if(argc > 0)
+ mboxfile = argv[0];
+ else
+ mboxfile = "./mbox";
+
+ root = newmessage(nil);
+
+ err = readmbox(mboxfile, &root->part);
+ if(err != nil){
+ fprint(2, "boom: %s\n", err);
+ exits(0);
+ }
+
+ info(0, 1, root);
+
+ exits(0);
+}