diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-03-12 17:15:03 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-03-12 17:15:03 +0100 |
commit | 963cfc9a6f6e721f52aa949e6d1af0c3e8dc2ecc (patch) | |
tree | 749b74875dbc49bcf6ed0776648b8f0ef9417407 /sys/src/cmd/upas/imap4d/mbox.c | |
parent | 8177d20fb2709ba9290dfd41308b8e5bee4e00f8 (diff) |
merging erik quanstros nupas
Diffstat (limited to 'sys/src/cmd/upas/imap4d/mbox.c')
-rw-r--r-- | sys/src/cmd/upas/imap4d/mbox.c | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/sys/src/cmd/upas/imap4d/mbox.c b/sys/src/cmd/upas/imap4d/mbox.c new file mode 100644 index 000000000..1841b6832 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/mbox.c @@ -0,0 +1,630 @@ +#include "imap4d.h" + +static int fsctl = -1; +static char Ecanttalk[] = "can't talk to mail server"; + +static void +fsinit(void) +{ + if(fsctl != -1) + return; + fsctl = open("/mail/fs/ctl", ORDWR); + if(fsctl == -1) + bye(Ecanttalk); +} + +static void +boxflags(Box *box) +{ + Msg *m; + + box->recent = 0; + for(m = box->msgs; m != nil; m = m->next){ + if(m->uid == 0){ + // fprint(2, "unassigned uid %s\n", m->info[Idigest]); + box->dirtyimp = 1; + m->uid = box->uidnext++; + } + if(m->flags & Frecent) + box->recent++; + } +} + +/* + * try to match permissions with mbox + */ +static int +createimp(Box *box, Qid *qid) +{ + int fd; + long mode; + Dir *d; + + fd = cdcreate(mboxdir, box->imp, OREAD, 0664); + if(fd < 0) + return -1; + d = cddirstat(mboxdir, box->name); + if(d != nil){ + mode = d->mode & 0777; + nulldir(d); + d->mode = mode; + dirfwstat(fd, d); + free(d); + } + if(fqid(fd, qid) < 0){ + close(fd); + return -1; + } + + return fd; +} + +/* + * read in the .imp file, or make one if it doesn't exist. + * make sure all flags and uids are consistent. + * return the mailbox lock. + */ +static Mblock* +openimp(Box *box, int new) +{ + char buf[ERRMAX]; + int fd; + Biobuf b; + Mblock *ml; + Qid qid; + + ml = mblock(); + if(ml == nil) + return nil; + fd = cdopen(mboxdir, box->imp, OREAD); + if(fd < 0 || fqid(fd, &qid) < 0){ + if(fd < 0){ + errstr(buf, sizeof buf); + if(cistrstr(buf, "does not exist") == nil) + ilog("imp: %s: %s", box->imp, buf); + else + debuglog("imp: %s: %s .. creating", box->imp, buf); + }else{ + close(fd); + ilog("%s: bogus imp: bad qid: recreating", box->imp); + } + fd = createimp(box, &qid); + if(fd < 0){ + ilog("createimp fails: %r"); + mbunlock(ml); + return nil; + } + box->dirtyimp = 1; + if(box->uidvalidity == 0){ + ilog("set uidvalidity %lud [new]\n", box->uidvalidity); + box->uidvalidity = box->mtime; + } + box->impqid = qid; + new = 1; + }else if(qid.path != box->impqid.path || qid.vers != box->impqid.vers){ + Binit(&b, fd, OREAD); + if(parseimp(&b, box) == -1){ + ilog("%s: bogus imp: parse failure", box->imp); + box->dirtyimp = 1; + if(box->uidvalidity == 0){ + ilog("set uidvalidity %lud [parseerr]\n", box->uidvalidity); + box->uidvalidity = box->mtime; + } + } + Bterm(&b); + box->impqid = qid; + new = 1; + } + if(new) + boxflags(box); + close(fd); + return ml; +} + +/* + * mailbox is unreachable, so mark all messages expunged + * clean up .imp files as well. + */ +static void +mboxgone(Box *box) +{ + char buf[ERRMAX]; + Msg *m; + + rerrstr(buf, ERRMAX); + if(strstr(buf, "hungup channel")) + bye(Ecanttalk); +// too smart. +// if(cdexists(mboxdir, box->name) < 0) +// cdremove(mboxdir, box->imp); + for(m = box->msgs; m != nil; m = m->next) + m->expunged = 1; + ilog("mboxgone"); + box->writable = 0; +} + +/* + * read messages in the mailbox + * mark message that no longer exist as expunged + * returns -1 for failure, 0 if no new messages, 1 if new messages. + */ +enum { + Gone = 2, /* don't unexpunge messages */ +}; + +static int +readbox(Box *box) +{ + char buf[ERRMAX]; + int i, n, fd, new, id; + Dir *d; + Msg *m, *last; + + fd = cdopen(box->fsdir, ".", OREAD); + if(fd == -1){ +goinggoinggone: + rerrstr(buf, ERRMAX); + ilog("upas/fs stat of %s/%s aka %s failed: %r", + username, box->name, box->fsdir); + mboxgone(box); + return -1; + } + + if((d = dirfstat(fd)) == nil){ + close(fd); + goto goinggoinggone; + } + box->mtime = d->mtime; + box->qid = d->qid; + last = nil; + for(m = box->msgs; m != nil; m = m->next){ + last = m; + m->expunged |= Gone; + } + new = 0; + free(d); + + for(;;){ + n = dirread(fd, &d); + if(n <= 0){ + close(fd); + if(n == -1) + goto goinggoinggone; + break; + } + for(i = 0; i < n; i++){ + if((d[i].qid.type & QTDIR) == 0) + continue; + id = atoi(d[i].name); + if(m = fstreefind(box, id)){ + m->expunged &= ~Gone; + continue; + } + new = 1; + m = MKZ(Msg); + m->id = id; + m->fsdir = box->fsdir; + m->fs = emalloc(2 * (Filelen + 1)); + m->efs = seprint(m->fs, m->fs + (Filelen + 1), "%ud/", id); + m->size = ~0UL; + m->lines = ~0UL; + m->flags = Frecent; + if(!msginfo(m)) + freemsg(0, m); + else{ + fstreeadd(box, m); + if(last == nil) + box->msgs = m; + else + last->next = m; + last = m; + } + } + free(d); + } + + /* box->max is invalid here */ + return new; +} + +int +uidcmp(void *va, void *vb) +{ + Msg **a, **b; + + a = va; + b = vb; + return (*a)->uid - (*b)->uid; +} + +static void +sequence(Box *box) +{ + Msg **a, *m; + int n, i; + + n = 0; + for(m = box->msgs; m; m = m->next) + n++; + a = ezmalloc(n * sizeof *a); + i = 0; + for(m = box->msgs; m; m = m->next) + a[i++] = m; + qsort(a, n, sizeof *a, uidcmp); + for(i = 0; i < n - 1; i++) + a[i]->next = a[i + 1]; + for(i = 0; i < n; i++) + if(a[i]->seq && a[i]->seq != i + 1) + bye("internal error assigning message numbers"); + else + a[i]->seq = i + 1; + box->msgs = nil; + if(n > 0){ + a[n - 1]->next = nil; + box->msgs = a[0]; + } + box->max = n; + memset(a, 0, n*sizeof *a); + free(a); +} + +/* + * strategy: + * every mailbox file has an associated .imp file + * which maps upas/fs message digests to uids & message flags. + * + * the .imp files are locked by /mail/fs/usename/L.mbox. + * whenever the flags can be modified, the lock file + * should be opened, thereby locking the uid & flag state. + * for example, whenever new uids are assigned to messages, + * and whenever flags are changed internally, the lock file + * should be open and locked. this means the file must be + * opened during store command, and when changing the \seen + * flag for the fetch command. + * + * if no .imp file exists, a null one must be created before + * assigning uids. + * + * the .imp file has the following format + * imp : "imap internal mailbox description\n" + * uidvalidity " " uidnext "\n" + * messagelines + * + * messagelines : + * | messagelines digest " " uid " " flags "\n" + * + * uid, uidnext, and uidvalidity are 32 bit decimal numbers + * printed right justified in a field Nuid characters long. + * the 0 uid implies that no uid has been assigned to the message, + * but the flags are valid. note that message lines are in mailbox + * order, except possibly for 0 uid messages. + * + * digest is an ascii hex string Ndigest characters long. + * + * flags has a character for each of NFlag flag fields. + * if the flag is clear, it is represented by a "-". + * set flags are represented as a unique single ascii character. + * the currently assigned flags are, in order: + * Fseen s + * Fanswered a + * Fflagged f + * Fdeleted D + * Fdraft d + */ + +Box* +openbox(char *name, char *fsname, int writable) +{ + char err[ERRMAX]; + int new; + Box *box; + Mblock *ml; + + fsinit(); +if(!strcmp(name, "mbox"))ilog("open %F %q", name, fsname); + if(fprint(fsctl, "open %F %q", name, fsname) < 0){ + rerrstr(err, sizeof err); + if(strstr(err, "file does not exist") == nil) + ilog("fs open %F as %s: %s", name, fsname, err); + if(strstr(err, "hungup channel")) + bye(Ecanttalk); + fprint(fsctl, "close %s", fsname); + return nil; + } + + /* + * read box to find all messages + * each one has a directory, and is in numerical order + */ + box = MKZ(Box); + box->writable = writable; + box->name = smprint("%s", name); + box->imp = smprint("%s.imp", name); + box->fs = smprint("%s", fsname); + box->fsdir = smprint("/mail/fs/%s", fsname); + box->uidnext = 1; + box->fstree = avlcreate(fstreecmp); + new = readbox(box); + if(new >= 0 && (ml = openimp(box, new))){ + closeimp(box, ml); + sequence(box); + return box; + } + closebox(box, 0); + return nil; +} + +/* + * careful: called by idle polling proc + */ +Mblock* +checkbox(Box *box, int imped) +{ + int new; + Dir *d; + Mblock *ml; + + if(box == nil) + return nil; + + /* + * if stat fails, mailbox must be gone + */ + d = cddirstat(box->fsdir, "."); + if(d == nil){ + mboxgone(box); + return nil; + } + new = 0; + if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers + || box->mtime != d->mtime){ + new = readbox(box); + if(new < 0){ + free(d); + return nil; + } + } + free(d); + ml = openimp(box, new); + if(ml == nil){ + ilog("openimp fails; box->writable = 0: %r"); + box->writable = 0; + }else if(!imped){ + closeimp(box, ml); + ml = nil; + } + if(new || box->dirtyimp) + sequence(box); + return ml; +} + +/* + * close the .imp file, after writing out any changes + */ +void +closeimp(Box *box, Mblock *ml) +{ + int fd; + Biobuf b; + Qid qid; + + if(ml == nil) + return; + if(!box->dirtyimp){ + mbunlock(ml); + return; + } + fd = cdcreate(mboxdir, box->imp, OWRITE, 0664); + if(fd < 0){ + mbunlock(ml); + return; + } + Binit(&b, fd, OWRITE); + box->dirtyimp = 0; + wrimp(&b, box); + Bterm(&b); + + if(fqid(fd, &qid) == 0) + box->impqid = qid; + close(fd); + mbunlock(ml); +} + +void +closebox(Box *box, int opened) +{ + Msg *m, *next; + + /* + * make sure to leave the mailbox directory so upas/fs can close the mailbox + */ + mychdir(mboxdir); + + if(box->writable){ + deletemsg(box, 0); + if(expungemsgs(box, 0)) + closeimp(box, checkbox(box, 1)); + } + + if(fprint(fsctl, "close %s", box->fs) < 0 && opened) + bye(Ecanttalk); + for(m = box->msgs; m != nil; m = next){ + next = m->next; + freemsg(box, m); + } + free(box->name); + free(box->fs); + free(box->fsdir); + free(box->imp); + free(box->fstree); + free(box); +} + +int +deletemsg(Box *box, Msgset *ms) +{ + char buf[Bufsize], *p, *start; + int ok; + Msg *m; + + if(!box->writable) + return 0; + + /* + * first pass: delete messages; gang the writes together for speed. + */ + ok = 1; + start = seprint(buf, buf + sizeof buf, "delete %s", box->fs); + p = start; + for(m = box->msgs; m != nil; m = m->next) + if(ms == 0 || ms && inmsgset(ms, m->uid)) + if((m->flags & Fdeleted) && !m->expunged){ + m->expunged = 1; + p = seprint(p, buf + sizeof buf, " %ud", m->id); + if(p + 32 >= buf + sizeof buf){ + if(write(fsctl, buf, p - buf) == -1) + bye(Ecanttalk); + p = start; + } + } + if(p != start && write(fsctl, buf, p - buf) == -1) + bye(Ecanttalk); + return ok; +} + +/* + * second pass: remove the message structure, + * and renumber message sequence numbers. + * update messages counts in mailbox. + * returns true if anything changed. + */ +int +expungemsgs(Box *box, int send) +{ + uint n; + Msg *m, *next, *last; + + n = 0; + last = nil; + for(m = box->msgs; m != nil; m = next){ + m->seq -= n; + next = m->next; + if(m->expunged){ + if(send) + Bprint(&bout, "* %ud expunge\r\n", m->seq); + if(m->flags & Frecent) + box->recent--; + n++; + if(last == nil) + box->msgs = next; + else + last->next = next; + freemsg(box, m); + }else + last = m; + } + if(n){ + box->max -= n; + box->dirtyimp = 1; + } + return n; +} + +static char *stoplist[] = +{ + ".", + "dead.letter", + "forward", + "headers", + "imap.subscribed", + "mbox", + "names", + "pipefrom", + "pipeto", + 0 +}; + +/* + * reject bad mailboxes based on mailbox name + */ +int +okmbox(char *path) +{ + char *name; + int i, c; + + name = strrchr(path, '/'); + if(name == nil) + name = path; + else + name++; + if(strlen(name) + STRLEN(".imp") >= Pathlen) + return 0; + for(i = 0; stoplist[i]; i++) + if(strcmp(name, stoplist[i]) == 0) + return 0; + c = name[0]; + if(c == 0 || c == '-' || c == '/' + || isdotdot(name) + || isprefix("L.", name) + || isprefix("imap-tmp.", name) + || issuffix("-", name) + || issuffix(".00", name) + || issuffix(".imp", name) + || issuffix(".idx", name)) + return 0; + + return 1; +} + +int +creatembox(char *mbox) +{ + fsinit(); + if(fprint(fsctl, "create %q", mbox) > 0){ + fprint(fsctl, "close %s", mbox); + return 0; + } + return -1; +} + +/* + * rename mailbox. truncaes or removes the source. + * bug? is the lock required + * upas/fs helpfully moves our .imp file. + */ +int +renamebox(char *from, char *to, int doremove) +{ + char *p; + int r; + Mblock *ml; + + fsinit(); + ml = mblock(); + if(ml == nil) + return 0; + if(doremove) + r = fprint(fsctl, "rename %F %F", from, to); + else + r = fprint(fsctl, "rename -t %F %F", from, to); + if(r > 0){ + if(p = strrchr(to, '/')) + p++; + else + p = to; + fprint(fsctl, "close %s", p); + } + mbunlock(ml); + return r > 0; +} + +/* + * upas/fs likes us; he removes the .imp file + */ +int +removembox(char *path) +{ + fsinit(); + return fprint(fsctl, "remove %s", path) > 0; +} |