diff options
Diffstat (limited to 'src/cmd/upas/fs')
-rw-r--r-- | src/cmd/upas/fs/dat.h | 221 | ||||
-rw-r--r-- | src/cmd/upas/fs/fs.c | 1704 | ||||
-rw-r--r-- | src/cmd/upas/fs/imap4.c | 876 | ||||
-rw-r--r-- | src/cmd/upas/fs/mbox.c | 1601 | ||||
-rw-r--r-- | src/cmd/upas/fs/mkfile | 29 | ||||
-rw-r--r-- | src/cmd/upas/fs/mkfile.9 | 27 | ||||
-rw-r--r-- | src/cmd/upas/fs/plan9.c | 405 | ||||
-rw-r--r-- | src/cmd/upas/fs/pop3.c | 700 | ||||
-rw-r--r-- | src/cmd/upas/fs/readdir.c | 15 | ||||
-rw-r--r-- | src/cmd/upas/fs/strtotm.c | 113 | ||||
-rw-r--r-- | src/cmd/upas/fs/tester.c | 81 |
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); +} |