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/fs | |
parent | 8177d20fb2709ba9290dfd41308b8e5bee4e00f8 (diff) |
merging erik quanstros nupas
Diffstat (limited to 'sys/src/cmd/upas/fs')
34 files changed, 6992 insertions, 2894 deletions
diff --git a/sys/src/cmd/upas/fs/bos.c b/sys/src/cmd/upas/fs/bos.c new file mode 100644 index 000000000..fdbf83f52 --- /dev/null +++ b/sys/src/cmd/upas/fs/bos.c @@ -0,0 +1,40 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> + +/* + * assume: + * - the stack segment can't be resized + * - stacks may not be segattached (by name Stack, anyway) + * - no thread library + */ +uintptr +absbos(void) +{ + char buf[64], *f[10], *s, *r; + int n; + uintptr p, q; + Biobuf *b; + + p = 0xd0000000; /* guess pc kernel */ + snprint(buf, sizeof buf, "/proc/%ud/segment", getpid()); + b = Bopen(buf, OREAD); + if(b == nil) + return p; + for(; s = Brdstr(b, '\n', 1); free(s)){ + if((n = tokenize(s, f, nelem(f))) < 3) + continue; + if(strcmp(f[0], "Stack") != 0) + continue; + /* + * addressing from end because segment + * flags could become discontiguous if + * additional flags are added + */ + q = strtoull(f[n - 3], &r, 16); + if(*r == 0 && (char*)q > end) + p = q; + } + Bterm(b); + return p; +} diff --git a/sys/src/cmd/upas/fs/cache.c b/sys/src/cmd/upas/fs/cache.c new file mode 100644 index 000000000..2d0a6280d --- /dev/null +++ b/sys/src/cmd/upas/fs/cache.c @@ -0,0 +1,552 @@ +#include "common.h" +#include <libsec.h> +#include "dat.h" + +int +findcache(Mcache *c, Message *m) +{ + int i; + + for(i = 0; i < c->ntab; i++) + if(c->ctab[i] == m) + return i; + return -1; +} + +static void +prcache(Mcache *c, char *prefix) +{ + int j; + Message *m; + + if(!debug) + return; + for(j = 0; j < c->ntab; j++){ + m = c->ctab[j]; + dprint("%s%d/%s\t%p\t%d\t%ld\n", prefix, j, m->name, m, m->refs, m->csize); + } +} + +/* debugging only */ +static void +dupchk(Mcache *c) +{ + int i, j; + + if(!debug) + return; + for(i = 0; i < c->ntab; i++) + for(j = i + 1; j < c->ntab; j++) + if(c->ctab[i] == c->ctab[j]) + goto lose; + return; +lose: + for(j = 0; j < c->ntab; j++) + dprint("%d\t%p %d\t%ld\n", j, c->ctab[j], c->ctab[j]->refs, c->ctab[j]->size); + abort(); +} + +int +addcache(Mcache *c, Message *m) +{ + int i; + + if((i = findcache(c, m)) < 0){ + if(c->ntab + 1 == nelem(c->ctab)) + abort(); + i = c->ntab++; + }else{ + /* rotate */ + if(i == c->ntab - 1) + return i; /* silly shortcut to prevent excessive printage. */ + dprint("addcache rotate %d %d\n", i, c->ntab); + prcache(c, ""); + memmove(c->ctab + i, c->ctab + i + 1, (c->ntab - i - 1)*sizeof c->ctab[0]); + i = c->ntab - 1; +c->ctab[i] = m; +dupchk(c); + } + dprint("addcache %d %d %p\n", i, c->ntab, m); + c->ctab[i] = m; + return i; +} + +static void +notecache(Mailbox *mb, Message *m, long sz) +{ + assert(Topmsg(mb, m)); + assert(sz >= 0 && sz < Maxmsg); + m->csize += sz; + mb->cached += sz; + addcache(mb, m); +} + +static long +cachefree0(Mailbox *mb, Message *m, int force) +{ + long sz, i; + Message *s; + + if(!force && !mb->fetch) + return 0; + for(s = m->part; s; s = s->next) + cachefree(mb, s, force); + dprint("cachefree: %D %p, %p\n", m->fileid, m, m->start); + if(m->mallocd){ + free(m->start); + m->mallocd = 0; + } + if(m->ballocd){ + free(m->body); + m->ballocd = 0; + } + if(m->hallocd){ + free(m->header); + m->hallocd = 0; + } + for(i = 0; i < nelem(m->references); i++){ + free(m->references[i]); + m->references[i] = 0; + } + sz = m->csize; + m->csize = 0; + m->start = 0; + m->end = 0; + m->header = 0; + m->hend = 0; + m->hlen = -1; + m->body = 0; + m->bend = 0; + m->mheader = 0; + m->mhend = 0; + if(mb->decache) + mb->decache(mb, m); + m->decoded = 0; + m->converted = 0; + m->badchars = 0; + m->cstate &= ~(Cheader|Cbody); + if(Topmsg(mb, m)) + mb->cached -= sz; + return sz; +} + +long +cachefree(Mailbox *mb, Message *m, int force) +{ + long sz, i; + + sz = cachefree0(mb, m, force); + for(i = 0; i < mb->ntab; i++) + if(m == mb->ctab[i]){ + mb->ntab--; + memmove(mb->ctab + i, mb->ctab + i + 1, sizeof m*mb->ntab - i); + dupchk(mb); + break; + } + return sz; +} + +enum{ + Maxntab = nelem(mbl->ctab) - 10, +}; + +vlong +sumcache(Mcache *c) +{ + int i; + vlong sz; + + sz = 0; + for(i = 0; i < c->ntab; i++) + sz += c->ctab[i]->csize; + return sz; +} + +int +scancache(Mcache *c) +{ + int i; + + for(i = 0; i < c->ntab; i++) + if(c->ctab[i]->csize > Maxmsg) + return -1; + return 0; +} + +/* debugging only */ +static void +chkcsize(Mailbox *mb, vlong sz, vlong sz0) +{ + int j; + Mcache *c; + Message *m; + + if(sumcache(mb) == mb->cached) + if(scancache(mb) == 0) + return; + eprint("sz0 %lld sz %lld sum %lld sumca %lld\n", sz0, sz, sumcache(mb), mb->cached); + eprint("%lld\n", sumcache(mb)); + c = mb; + for(j = 0; j < c->ntab; j++){ + m = c->ctab[j]; + eprint("%d %p %d %ld %ld\n", j, m, m->refs, m->csize, m->size); + } + abort(); +} + +/* + * strategy: start with i = 0. while cache exceeds limits, + * find j so that all the [i:j] elements have refs == 0. + * uncache all the [i:j], reduce ntab by i-j. the tail + * [j+1:ntab] is shifted to [i:ntab], and finally i = i+1. + * we may safely skip the new i, since the condition + * that stopped our scan there still holds. + */ +void +putcache(Mailbox *mb, Message *m) +{ + int i, j, k; + vlong sz, sz0; + Message **p; + + p = mb->ctab; + sz0 = mb->cached; + dupchk(mb); + for(i = 0;; i++){ + sz = mb->cached; + for(j = i;; j++){ + if(j >= mb->ntab || + sz < cachetarg && mb->ntab - (j - i) < Maxntab){ + if(j != i) + break; +chkcsize(mb, sz, sz0); + return; + } + if(p[j]->refs > 0) + break; + sz -= p[j]->csize; + } + if(sz == mb->cached){ + if(i >= mb->ntab) + break; + continue; + } + for(k = i; k < j; k++) + cachefree0(mb, p[k], 0); + mb->ntab -= j - i; + memmove(p + i, p + j, (mb->ntab - i)*sizeof *p); + } +chkcsize(mb, sz, sz0); + k = 0; + for(i = 0; i < mb->ntab; i++) + k += p[i]->refs > 0; + if((mb->ntab > 1 || k != mb->ntab) && Topmsg(mb, m)) + eprint("cache overflow: %D %llud bytes; %d entries\n", + m? m->fileid: 1ll, mb->cached, mb->ntab); + if(k == mb->ntab) + return; + debug = 1; prcache(mb, ""); + abort(); +} + +static int +squeeze(Message *m, uvlong o, long l, int c) +{ + char *p, *q, *e; + int n; + + q = memchr(m->start + o, c, l); + if(q == nil) + return 0; + n = 0; + e = m->start + o + l; + for(p = q; q < e; q++){ + if(*q == c){ + n++; + continue; + } + *p++ = *q; + } + return n; +} + +void +msgrealloc(Message *m, ulong l) +{ + long l0, h0, m0, me, b0; + + l0 = m->end - m->start; + m->mallocd = 1; + h0 = m->hend - m->start; + m0 = m->mheader - m->start; + me = m->mhend - m->start; + b0 = m->body - m->start; + assert(h0 >= 0 && m0 >= 0 && me >= 0 && b0 >= 0); + m->start = erealloc(m->start, l + 1); + m->rbody = m->start + b0; + m->rbend = m->end = m->start + l0; + if(!m->hallocd){ + m->header = m->start; + m->hend = m->start + h0; + } + if(!m->ballocd){ + m->body = m->start + b0; + m->bend = m->start + l0; + } + m->mheader = m->start + m0; + m->mhend = m->start + me; +} + +/* + * the way we squeeze out bad characters is exceptionally sneaky. + */ +static int +fetch(Mailbox *mb, Message *m, uvlong o, ulong l) +{ + int expand; + long l0, n, sz0; + +top: + l0 = m->end - m->start; + assert(l0 >= 0); + dprint("fetch %lud sz %lud o %llud l %lud badchars %d\n", l0, m->size, o, l, m->badchars); + assert(m->badchars < Maxmsg/10); + if(l0 == m->size || o > m->size) + return 0; + expand = 0; + if(o + l > m->size) + l = m->size - o; + if(o + l == m->size) + l += m->ibadchars - m->badchars; + if(o + l > l0){ + expand = 1; + msgrealloc(m, o + m->badchars + l); + } + assert(l0 <= o); + sz0 = m->size; + if(mb->fetch(mb, m, o + m->badchars, l) == -1){ + logmsg(m, "can't fetch %D %llud %lud", m->fileid, o, l); + m->deleted = Dead; + return -1; + } + if(m->size - sz0) + l += m->size - sz0; /* awful botch for gmail */ + if(expand){ + /* grumble. poor planning. */ + if(m->badchars > 0) + memmove(m->start + o, m->start + o + m->badchars, l); + n = squeeze(m, o, l, 0); + n += squeeze(m, o, l - n, '\r'); + if(n > 0){ + if(m->ibadchars == 0) + dprint(" %ld more badchars\n", n); + l -= n; + m->badchars += n; + msgrealloc(m, o + l); + } + notecache(mb, m, l); + m->bend = m->rbend = m->end = m->start + o + l; + if(n) + if(o + l + n == m->size && m->cstate&Cidx){ + dprint(" redux %llud %ld\n", o + l, n); + o += l; + l = n; + goto top; + } + }else + eprint("unhandled case in fetch\n"); + *m->end = 0; + return 0; +} + +void +cachehash(Mailbox *mb, Message *m) +{ +// fprint(2, "cachehash %P\n", mpair(mb, m)); + 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); + henter(PATH(m->id, Qdir), "xxx", + (Qid){PATH(m->id, Qmax), 0, QTFILE}, m, mb); /* sleezy speedup */ +} + +void +newcachehash(Mailbox *mb, Message *m, int doplumb) +{ + if(doplumb) + mailplumb(mb, m, 0); + else + if(insurecache(mb, m) == 0) + msgdecref(mb, m); + /* avoid cachehash on error? */ + cachehash(mb, m); +} + +static char *itab[] = { + "idx", + "stale", + "header", + "body" +}; + +char* +cstate(Message *m) +{ + char *p, *e; + int i, s; + static char buf[64]; + + s = m->cstate; + p = e = buf; + e += sizeof buf; + for(i = 0; i < 8; i++) + if(s & 1<<i) + if(i < nelem(itab)) + p = seprint(p, e, "%s ", itab[i]); + if(p > buf) + p--; + p[0] = 0; + return buf; +} + + +static int +middlecache(Mailbox *mb, Message *m) +{ + int y; + + y = 0; + while(!Topmsg(mb, m)){ + m = m->whole; + if((m->cstate & Cbody) == 0) + y = 1; + } + if(y == 0) + return 0; + dprint("middlecache %d [%D] %lud %lud\n", m->id, m->fileid, m->end - m->start, m->size); + return cachebody(mb, m); +} + +int +cacheheaders(Mailbox *mb, Message *m) +{ + char *p, *e; + int r; + ulong o; + + if(!mb->fetch || m->cstate&Cheader) + return 0; + if(!Topmsg(mb, m)) + return middlecache(mb, m); + dprint("cacheheaders %d %D\n", m->id, m->fileid); + if(m->size < 10000) + r = fetch(mb, m, 0, m->size); + else for(r = 0; (o = m->end - m->start) < m->size; ){ + if((r = fetch(mb, m, o, 4096)) < 0) + break; + p = m->start + o; + if(o) + p--; + for(e = m->end - 2; p < e; p++){ + p = memchr(p, '\n', e - p); + if(p == nil) + break; + if(p[1] == '\n' || (p[1] == '\r' && p[2] == '\n')) + goto found; + } + } + if(r < 0) + return -1; +found: + parseheaders(mb, m, mb->addfrom, 0); + return 0; +} + +void +digestmessage(Mailbox *mb, Message *m) +{ + assert(m->digest == 0); + m->digest = emalloc(SHA1dlen); + sha1((uchar*)m->start, m->end - m->start, m->digest, nil); + if(mtreeisdup(mb, m)){ + logmsg(m, "dup detected"); + m->deleted = Dup; /* no dups allowed */ + }else + mtreeadd(mb, m); + dprint("%d %#A\n", m->id, m->digest); +} + +int +cachebody(Mailbox *mb, Message *m) +{ + ulong o; + + while(!Topmsg(mb, m)) + m = m->whole; + if(!mb->fetch || m->cstate&Cbody) + return 0; + o = m->end - m->start; + dprint("cachebody %d [%D] %lud %lud %s\n", m->id, m->fileid, o, m->size, cstate(m)); + if(o < m->size) + if(fetch(mb, m, o, m->size - o) < 0) + return -1; + if((m->cstate&Cidx) == 0){ + assert(m->ibadchars == 0); + if(m->badchars > 0) + dprint("reducing size %ld %ld\n", m->size, m->size - m->badchars); + m->size -= m->badchars; /* sneaky */ + m->ibadchars = m->badchars; + } + if(m->digest == 0) + digestmessage(mb, m); + if(m->lines == 0) + m->lines = countlines(m); + parse(mb, m, mb->addfrom, 0); + dprint(" →%s\n", cstate(m)); + return 0; +} + +int +cacheidx(Mailbox *mb, Message *m) +{ + if(m->cstate & Cidx) + return 0; + if(cachebody(mb, m) == -1) + return -1; + m->cstate |= Cidxstale|Cidx; + return 0; +} + +static int +countparts(Message *m) +{ + Message *p; + + if(m->nparts == 0) + for(p = m->part; p; p = p->next){ + countparts(p); + m->nparts++; + } + return m->nparts; +} + +int +insurecache(Mailbox *mb, Message *m) +{ + if(m->deleted || !m->inmbox) + return -1; + msgincref(m); + cacheidx(mb, m); + if((m->cstate & Cidx) == 0){ + logmsg(m, "%s: can't cache: %s: %r", mb->path, m->name); + msgdecref(mb, m); + return -1; + } + if(m->digest == 0) + sysfatal("digest?"); + countparts(m); + return 0; +} diff --git a/sys/src/cmd/upas/fs/chkidx b/sys/src/cmd/upas/fs/chkidx Binary files differnew file mode 100755 index 000000000..bac911139 --- /dev/null +++ b/sys/src/cmd/upas/fs/chkidx diff --git a/sys/src/cmd/upas/fs/chkidx.c b/sys/src/cmd/upas/fs/chkidx.c new file mode 100644 index 000000000..8b0a1545b --- /dev/null +++ b/sys/src/cmd/upas/fs/chkidx.c @@ -0,0 +1,416 @@ +#include "common.h" +#include <auth.h> +#include <libsec.h> +#include <bin.h> +#include "dat.h" + +#define idprint(...) if(1) fprint(2, __VA_ARGS__); else {} +enum{ + Maxver = 10, +}; +static char *magictab[Maxver] = { +[4] "idx magic v4\n", +[7] "idx magic v7\n", +}; +static int fieldstab[Maxver] = { +[4] 19, +[7] 21, +}; + +static char *magic; +static int Idxfields; +static int lineno; +static int idxver; + +int +newid(void) +{ + static int id; + + return ++id; +} + +void* +emalloc(ulong n) +{ + void *p; + + p = mallocz(n, 1); + if(!p) + sysfatal("malloc %lud: %r", n); + setmalloctag(p, getcallerpc(&n)); + return p; +} + +static int +Afmt(Fmt *f) +{ + char buf[SHA1dlen*2 + 1]; + uchar *u, i; + + u = va_arg(f->args, uchar*); + if(u == 0 && f->flags & FmtSharp) + return fmtstrcpy(f, "-"); + if(u == 0) + return fmtstrcpy(f, "<nildigest>"); + for(i = 0; i < SHA1dlen; i++) + sprint(buf + 2*i, "%2.2ux", u[i]); + return fmtstrcpy(f, buf); +} + +static int +Dfmt(Fmt *f) +{ + char buf[32]; + int seq; + uvlong v; + + v = va_arg(f->args, uvlong); + seq = v & 0xff; + if(seq > 99) + seq = 99; + snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq); + return fmtstrcpy(f, buf); +} + +static Mailbox* +shellmailbox(char *path) +{ + Mailbox *mb; + + mb = malloc(sizeof *mb); + if(mb == 0) + sysfatal("malloc"); + memset(mb, 0, sizeof *mb); + snprint(mb->path, sizeof mb->path, "%s", path); + mb->id = newid(); + mb->root = newmessage(nil); + mb->mtree = mkavltree(mtreecmp); + return mb; +} + +void +shellmailboxfree(Mailbox*) +{ +} + +Message* +newmessage(Message *parent) +{ + static int id; + Message *m; + +// msgallocd++; + + m = mallocz(sizeof *m, 1); + if(m == 0) + sysfatal("malloc"); + m->disposition = Dnone; +// m->type = newrefs("text/plain"); +// m->charset = newrefs("iso-8859-1"); + m->cstate = Cidxstale; + m->flags = Frecent; + m->id = newid(); + if(parent) + snprint(m->name, sizeof m->name, "%d", ++(parent->subname)); + if(parent == nil) + parent = m; + m->whole = parent; + m->hlen = -1; + return m; +} + +void +unnewmessage(Mailbox *mb, Message *parent, Message *m) +{ + assert(parent->subname > 0); +// delmessage(mb, m); + USED(mb, m); + parent->subname -= 1; +} + + +static int +validmessage(Mailbox *mb, Message *m, int level) +{ + if(level){ + if(m->digest != 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + if(m->fileid != 0) + goto lose; + }else{ + if(m->digest == 0) + goto lose; + if(m->size == 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + goto lose; + if(mtreefind(mb, m->digest)) + goto lose; + } + return 1; +lose: + fprint(2, "invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid); + return 0; +} + +static char* +∫(char *x) +{ + if(x && *x) + return x; + return nil; +} + +static char* +brdstr(Biobuf *b, int c, int eat) +{ + char *s; + + s = Brdstr(b, c, eat); + if(s) + lineno++; + return s; +} + +static int +nibble(int c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c < 0x20) + c += 0x20; + if(c >= 'a' && c <= 'f') + return c - 'a'+10; + return 0xff; +} + +static uchar* +hackdigest(char *s) +{ + uchar t[SHA1dlen]; + int i; + + if(strcmp(s, "-") == 0) + return 0; + if(strlen(s) != 2*SHA1dlen){ + fprint(2, "bad digest %s\n", s); + return 0; + } + for(i = 0; i < SHA1dlen; i++) + t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]); + memmove(s, t, SHA1dlen); + return (uchar*)s; +} + +static Message* +findmessage(Mailbox *, Message *parent, int n) +{ + Message *m; + + for(m = parent->part; m; m = m->next) + if(!m->digest && n-- == 0) + return m; + return 0; +} + +static uvlong +rdfileid(char *s, int level) +{ + char *p; + uvlong uv; + + uv = strtoul(s, &p, 0); + if((level == 0 && uv < 1000000) || *p != '.') + return 0; + return uv<<8 | strtoul(p + 1, 0, 10); +} + +static int +rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level) +{ + char *f[50 + 1], *s; + uchar *digest; + int n, nparts, good, bad, redux; + Message *m, **ll, *l; + + bad = good = redux = 0; + ll = &parent->part; + nparts = npart; + for(; npart != 0 && (s = brdstr(b, '\n', 1)); npart--){ +//if(lineno>18&&lineno<25)idprint("%d: %d [%s]\n", lineno, level, s); + n = tokenize(s, f, nelem(f)); + if(n != Idxfields){ + print("%d: bad line\n", lineno); + bad++; + free(s); + continue; + } + digest = hackdigest(f[0]); + if(level == 0){ + if(digest == 0) + idprint("%d: no digest\n", lineno); + m = mtreefind(mb, digest); + }else{ + m = findmessage(mb, parent, nparts - npart); + if(m == 0){ + // idprint("can't find message\n"); + } + } + if(m){ + /* + * read in mutable information. + * currently this is only flags + */ + idprint("%d seen before %d... %.2ux", level, m->id, m->cstate); + redux++; + m->flags |= strtoul(f[1], 0, 16); + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + idprint("→%.2ux\n", m->cstate); + free(s); + + if(m->nparts) + rdidx(b, mb, m, m->nparts, level + 1); + ll = &m->next; + continue; + } + m = newmessage(parent); +//if(lineno>18&&lineno<25)idprint("%d: %d %d %A\n", lineno, level, m->id, digest); +// idprint("%d new %d %#A \n", level, m->id, digest); + m->digest = digest; + m->flags = strtoul(f[1], 0, 16); + m->fileid = rdfileid(f[2], level); + m->lines = atoi(f[3]); + m->ffrom = ∫(f[4]); + m->from = ∫(f[5]); + m->to = ∫(f[6]); + m->cc = ∫(f[7]); + m->bcc = ∫(f[8]); + m->replyto = ∫(f[9]); + m->messageid = ∫(f[10]); + m->subject = ∫(f[11]); + m->sender = ∫(f[12]); + m->inreplyto = ∫(f[13]); +// m->type = newrefs(f[14]); + m->disposition = atoi(f[15]); + m->size = strtoul(f[16], 0, 0); + m->rawbsize = strtoul(f[17], 0, 0); + switch(idxver){ + case 4: + m->nparts = strtoul(f[18], 0, 0); + case 7: + m->ibadchars = strtoul(f[18], 0, 0); + m->idxaux = ∫(f[19]); + m->nparts = strtoul(f[20], 0, 0); + } + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + m->str = s; +// free(s); + if(!validmessage(mb, m, level)){ + /* + * if this was an okay message, and somebody + * wrote garbage to the index file, we lose the msg. + */ + print("%d: !validmessage\n", lineno); + bad++; + unnewmessage(mb, parent, m); + continue; + } + if(level == 0) + m->inmbox = 1; +// cachehash(mb, m); /* hokey */ + l = *ll; + *ll = m; + ll = &m->next; + *ll = l; + good++; + + if(m->nparts){ +// fprint(2, "%d: %d parts [%s]\n", lineno, m->nparts, f[18]); + rdidx(b, mb, m, m->nparts, level + 1); + } + } + if(level == 0) + print("idx: %d %d %d\n", good, bad, redux); + return 0; +} + +static int +verscmp(Biobuf *b) +{ + char *s; + int i; + + if((s = brdstr(b, '\n', 0)) == 0) + return -1; + for(i = 0; i < Maxver; i++) + if(magictab[i]) + if(strcmp(s, magictab[i]) == 0) + break; + free(s); + if(i == Maxver) + return -1; + idxver = i; + magic = magictab[i]; + Idxfields = fieldstab[i]; + fprint(2, "version %d\n", i); + return 0; +} + +int +mbvers(Biobuf *b) +{ + char *s; + + if(s = brdstr(b, '\n', 1)){ + free(s); + return 0; + } + return -1; +} + +int +ckidxfile(Mailbox *mb) +{ + char buf[Pathlen + 4]; + int r; + Biobuf *b; + + snprint(buf, sizeof buf, "%s", mb->path); + b = Bopen(buf, OREAD); + if(b == nil) + return -1; + if(verscmp(b) == -1) + return -1; + if(idxver >= 7) + mbvers(b); + r = rdidx(b, mb, mb->root, -1, 0); + Bterm(b); + return r; +} + +static char *bargv[] = {"/fd/0", 0}; + +void +main(int argc, char **argv) +{ + Mailbox *mb; + + fmtinstall('A', Afmt); + fmtinstall('D', Dfmt); + ARGBEGIN{ + }ARGEND + if(*argv == 0) + argv = bargv; + for(; *argv; argv++){ + mb = shellmailbox(*argv); + lineno = 0; + if(ckidxfile(mb) == -1) + fprint(2, "%s: %r\n", *argv); + shellmailboxfree(mb); + } + exits(""); +} diff --git a/sys/src/cmd/upas/fs/dat.h b/sys/src/cmd/upas/fs/dat.h index d21de3ed5..585af992a 100644 --- a/sys/src/cmd/upas/fs/dat.h +++ b/sys/src/cmd/upas/fs/dat.h @@ -1,191 +1,313 @@ -typedef struct Message Message; -struct Message -{ - int id; - int refs; - int subname; - char name[Elemlen]; +#include <avl.h> - // 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 { + /* cache states */ + Cidx = 1<<0, + Cidxstale = 1<<1, + Cheader = 1<<2, + Cbody = 1<<3, -enum -{ - // encodings + /* encodings */ Enone= 0, Ebase64, Equoted, - // disposition possibilities + /* dispositions */ Dnone= 0, Dinline, Dfile, Dignore, - PAD64= '=', + /* mb create flags */ + DMcreate = 0x02000000, + + /* rm flags */ + Rrecur = 1<<0, + Rtrunc = 1<<1, + + /* m->deleted flags */ + Deleted = 1<<0, + Dup = 1<<1, + Dead = 1<<2, + Disappear = 1<<3, + Dmark = 1<<4, /* temporary mark for idx scan */ + + /* mime flags */ + Mtrunc = 1<<0, /* message had no boundary */ + + Maxmsg = 75*1024*1024, /* maxmessage size; debugging */ + Maxcache = 512*1024, /* target cache size; set low for debugging */ + Nctab = 15, /* max # of cached messages >10 */ + Nref = 10, +}; + +typedef struct Idx Idx; +struct Idx { + char *str; /* as read from idx file */ + uchar *digest; + uchar flags; + uvlong fileid; + ulong lines; + ulong size; + ulong rawbsize; /* nasty imap4d */ + ulong ibadchars; + + char *ffrom; + char *from; + char *to; + char *cc; + char *bcc; + char *replyto; + char *messageid; + char *subject; + char *sender; + char *inreplyto; + char *idxaux; /* mailbox specific */ + + int type; /* very few types: refstring */ + int disposition; /* very few types: refstring */ + int nparts; +}; + +typedef struct Message Message; +struct Message { + int id; + int refs; + int subname; + char name[12]; + + /* top-level indexed information */ + Idx; + + /* caching help */ + uchar cstate; + ulong infolen; + ulong csize; + + /* + * a plethoria of pointers into message + * and some status. not valid unless cached + */ + 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 mallocd; /* message is malloc'd */ + char ballocd; /* body is malloc'd */ + char hallocd; /* header is malloc'd */ + int badchars; /* running count of bad chars. */ + + char deleted; + char inmbox; + + /* mail info */ + char *unixheader; + char *unixfrom; + char *date822; + char *references[Nref]; + + /* mime info */ + char *boundary; + int charset; + char *filename; + char encoding; + char converted; + char decoded; + char mimeflag; + + Message *next; + Message *part; + Message *whole; + + union{ + char *lim; /* used by plan9; not compatable with cache */ + vlong imapuid; /* used by imap4 */ + void *aux; + }; +}; + +typedef struct { + Avl; + Message *m; +} Mtree; + +typedef struct Mcache Mcache; +struct Mcache { + uvlong cached; + int ntab; + Message *ctab[Nctab]; }; typedef struct Mailbox Mailbox; -struct Mailbox -{ +struct Mailbox { QLock; + long idxsem; /* abort on concurrent index access */ + long syncsem; /* abort on concurrent syncs */ int refs; Mailbox *next; int id; - int dolock; // lock when syncing? - int std; + int flags; + char rmflags; + char dolock; /* lock when syncing? */ + char addfrom; char name[Elemlen]; char path[Pathlen]; Dir *d; Message *root; - int vers; // goes up each time mailbox is read + Avltree *mtree; + ulong vers; /* goes up each time mailbox is changed */ - ulong waketime; - char *(*sync)(Mailbox*, int); + /* cache tracking */ + Mcache; + + /* index tracking */ + Qid qid; + + ulong waketime; void (*close)(Mailbox*); - char *(*fetch)(Mailbox*, Message*); + void (*decache)(Mailbox*, Message*); + int (*fetch)(Mailbox*, Message*, uvlong, ulong); + void (*delete)(Mailbox*, Message*); char *(*ctl)(Mailbox*, int, char**); - void *aux; // private to Mailbox implementation + char *(*remove)(Mailbox *, int); + char *(*rename)(Mailbox*, char*, int); + char *(*sync)(Mailbox*, int, int*); + void (*modflags)(Mailbox*, Message*, int); + void (*idxwrite)(Biobuf*, Mailbox*); + int (*idxread)(char*, Mailbox*); + void (*idxinvalid)(Mailbox*); + void *aux; /* private to Mailbox implementation */ }; +/* print argument tango; can't varargck 2 types. should fix compiler */ +typedef struct Mpair Mpair; +struct Mpair { + Mailbox *mb; + Message *m; +}; +Mpair mpair(Mailbox*, Message*); + typedef char *Mailboxinit(Mailbox*, char*); -extern Message *root; -extern Mailboxinit plan9mbox; -extern Mailboxinit pop3mbox; -extern Mailboxinit imap4mbox; -extern Mailboxinit planbmbox; -extern Mailboxinit planbvmbox; +Mailboxinit plan9mbox; +Mailboxinit planbmbox; +Mailboxinit pop3mbox; +Mailboxinit imap4mbox; +Mailboxinit mdirmbox; + +void genericidxwrite(Biobuf*, Mailbox*); +int genericidxread(char*, Mailbox*); +void genericidxinvalid(Mailbox*); + +void cachehash(Mailbox*, Message*); +void newcachehash(Mailbox*, Message*, int); +int cacheheaders(Mailbox*, Message*); /* "getcache" */ +int cachebody(Mailbox*, Message*); +int cacheidx(Mailbox*, Message*); +int insurecache(Mailbox*, Message*); +/**/ +void putcache(Mailbox*, Message*); /* asymmetricial */ +long cachefree(Mailbox*, Message*, int); + +Message* gettopmsg(Mailbox*, Message*); char* syncmbox(Mailbox*, int); -char* geterrstr(void); void* emalloc(ulong); void* erealloc(void*, ulong); Message* newmessage(Message*); +void unnewmessage(Mailbox*, Message*, Message*); void delmessage(Mailbox*, Message*); -void delmessages(int, char**); +char* delmessages(int, char**); +char *flagmessages(int, char**); +void digestmessage(Mailbox*, Message*); + +uintptr absbos(void); +void eprint(char*, ...); +void iprint(char *, ...); int newid(void); void mailplumb(Mailbox*, Message*, int); -char* newmbox(char*, char*, int); +char* newmbox(char*, char*, int, Mailbox**); void freembox(char*); -void logmsg(char*, Message*); +char* removembox(char*, int); +void syncallmboxes(void); +void logmsg(Message*, char*, ...); void msgincref(Message*); void msgdecref(Mailbox*, Message*); void mboxincref(Mailbox*); void mboxdecref(Mailbox*); +char *mboxrename(char*, char*, int); void convert(Message*); void decode(Message*); -int cistrncmp(char*, char*, int); -int cistrcmp(char*, char*); int decquoted(char*, char*, char*, int); int xtoutf(char*, char**, char*, char*); -void countlines(Message*); -int headerlen(Message*); -void parse(Message*, int, Mailbox*, int); -void parseheaders(Message*, int, Mailbox*, int); +ulong countlines(Message*); +void parse(Mailbox*, Message*, int, int); +void parseheaders(Mailbox*, Message*, int, int); void parsebody(Message*, Mailbox*); -void parseunix(Message*); -String* date822tounix(char*); +char* date822tounix(Message*, char*); int fidmboxrefs(Mailbox*); int hashmboxrefs(Mailbox*); +void checkmboxrefs(void); +int strtotm(char*, Tm*); +char* lowercase(char*); -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; +char* sputc(char*, char*, int); +char* seappend(char*, char*, char*, int); -enum -{ - /* mail subobjects */ - Qbody, +int hdrlen(char*, char*); +char* rfc2047(char*, char*, char*, int, int); + +char* localremove(Mailbox*, int); +char* localrename(Mailbox*, char*, int); +void rmidx(char*, int); +int vremove(char*); +int rename(char *, char*, int); + +int mtreecmp(Avl*, Avl*); +int mtreeisdup(Mailbox *, Message *); +Message* mtreefind(Mailbox*, uchar*); +void mtreeadd(Mailbox*, Message*); +void mtreedelete(Mailbox*, Message*); + +enum { + /* mail sub-objects; must be sorted */ Qbcc, + Qbody, Qcc, Qdate, Qdigest, Qdisposition, + Qffrom, + Qfileid, Qfilename, + Qflags, Qfrom, Qheader, + Qinfo, Qinreplyto, Qlines, - Qmimeheader, Qmessageid, + Qmimeheader, Qraw, Qrawbody, Qrawheader, Qrawunix, + Qreferences, Qreplyto, Qsender, + Qsize, Qsubject, Qto, Qtype, - Qunixheader, - Qinfo, Qunixdate, + Qunixheader, Qmax, /* other files */ @@ -196,12 +318,10 @@ enum Qmboxctl, }; -#define PATH(id, f) ((((id)&0xfffff)<<10) | (f)) +#define PATH(id, f) ((((id) & 0xfffff)<<10) | (f)) #define FILE(p) ((p) & 0x3ff) -char *dirtab[]; - -// hash table to aid in name lookup, all files have an entry +/* hash table to aid in name lookup, all files have an entry */ typedef struct Hash Hash; struct Hash { Hash *next; @@ -216,5 +336,50 @@ Hash *hlook(ulong, char*); void henter(ulong, char*, Qid, Message*, Mailbox*); void hfree(ulong, char*); -ulong msgallocd, msgfreed; +typedef struct { + char *s; + int l; + ulong ref; +} Refs; + +int newrefs(char*); +void delrefs(int); +void refsinit(void); +int prrefs(Biobuf*); +int rdrefs(Biobuf*); + +void idxfree(Idx*); +int rdidxfile(Mailbox*, int); +int wridxfile(Mailbox*); + +char *modflags(Mailbox*, Message*, char*); +int getmtokens(char *, char**, int, int); + +extern char Enotme[]; +extern char *mntpt; +extern char user[Elemlen]; +extern char *dirtab[]; +extern int Sflag; +extern int iflag; +extern int biffing; +extern ulong cachetarg; +extern int debug; +extern int lflag; +extern int plumbing; +extern ulong msgallocd; +extern ulong msgfreed; +extern Mailbox *mbl; +extern Message *root; +extern QLock mbllock; +extern Refs *rtab; + +#define dprint(...) if(debug) fprint(2, __VA_ARGS__); else {} +#define Topmsg(mb, m) (m->whole == mb->root) +#pragma varargck type "A" uchar* +#pragma varargck type "D" uvlong +#pragma varargck type "P" Mpair +#pragma varargck type "Δ" uvlong +#pragma varargck argpos eprint 1 +#pragma varargck argpos iprint 1 +#pragma varargck argpos logmsg 2 diff --git a/sys/src/cmd/upas/fs/extra/fd2path b/sys/src/cmd/upas/fs/extra/fd2path Binary files differnew file mode 100755 index 000000000..d73815007 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/fd2path diff --git a/sys/src/cmd/upas/fs/extra/fd2path.c b/sys/src/cmd/upas/fs/extra/fd2path.c new file mode 100644 index 000000000..5fe3ce708 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/fd2path.c @@ -0,0 +1,32 @@ +#include <u.h> +#include <libc.h> + +void +usage(void) +{ + fprint(2, "usage: fd2path path ...\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char buf[1024]; + int fd; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + if(argc == 0){ + if(fd2path(0, buf, sizeof buf) != -1) + fprint(2, "%s\n", buf); + }else for(; *argv; argv++){ + fd = open(*argv, OREAD); + if(fd != -1 && fd2path(fd, buf, sizeof buf) != -1) + fprint(2, "%s\n", buf); + close(fd); + } + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/idxtst.c b/sys/src/cmd/upas/fs/extra/idxtst.c new file mode 100644 index 000000000..919d25476 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/idxtst.c @@ -0,0 +1,58 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> + +static char *etab[] = { + "not found", + "does not exist", + "file is locked", + "exclusive lock", +}; + +static int +bad(int idx) +{ + char buf[ERRMAX]; + int i; + + rerrstr(buf, sizeof buf); + for(i = idx; i < nelem(etab); i++) + if(strstr(buf, etab[i])) + return 0; + return 1; +} + +static int +exopen(char *s) +{ + int i, fd; + + for(i = 0; i < 30; i++){ + if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(0)) + return fd; + if((fd = create(s, OWRITE|OEXCL, DMEXCL|0600)) >= 0 || bad(2)) + return fd; + sleep(1000); + } + werrstr("lock timeout"); + return -1; +} + +void +main(void) +{ + int fd; + Biobuf *b; + + fd = exopen("testingex"); + if(fd == -1) + sysfatal("exopen: %r"); + b = Bopen("testingex", OREAD); + if(b){ + free(b); + fprint(2, "print both opened at once\n"); + }else + fprint(2, "bopen: %r\n"); + close(fd); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/infotst.c b/sys/src/cmd/upas/fs/extra/infotst.c new file mode 100644 index 000000000..de7537a94 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/infotst.c @@ -0,0 +1,89 @@ +/* + * simulate the read patterns of external programs for testing + * info file. "infotest 511 512" simulates what ned does today. + * + * here's how the new info scheme was verified: + * + ramfs + s=/sys/src/cmd/upas + unmount /mail/fs + $s/fs/8.out -p + for(f in /mail/fs/mbox/*/info){ + for(i in `{seq 1 1026}) + $s/fs/infotst $i `{echo $i + 1 | hoc} > /tmp/$i < $f + for(i in /tmp/*) + cmp $i /tmp/1 + rm /tmp/* + } + + # now test for differences with old scheme under + # ideal reading conditions + for(f in /mail/fs/mbox/*/info){ + i = `{echo $f | sed 's:/mail/fs/mbox/([^/]+)/info:\1:g'} + $s/fs/infotst 2048 > /tmp/$i < $f + } + unmount /mail/fs + upas/fs -p + for(f in /mail/fs/mbox/*/info){ + i = `{echo $f | sed 's:/mail/fs/mbox/([^/]+)/info:\1:g'} + $s/fs/infotst 2048 > /tmp/$i.o < $f + } + for(i in /tmp/*.o) + cmp $i `{echo $i | sed 's:\.o$::g'} + rm /tmp/* + */ +#include <u.h> +#include <libc.h> + +enum{ + Ntab = 100, +}; + +int tab[Ntab]; +int ntab; +int largest; + +void +usage(void) +{ + fprint(2, "usage: infotest n1 n2 ... nm\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *buf; + int i, n; + + ARGBEGIN{ + default: + usage(); + }ARGEND + if(argc == 0) + usage(); + for(; *argv; argv++){ + if(ntab == nelem(tab)) + break; + i = atoi(*argv); + if(i > largest) + largest = i; + tab[ntab++] = i; + } + buf = malloc(largest); + if(!buf) + sysfatal("malloc: %r"); + for(i = 0;; ){ + switch(n = read(0, buf, tab[i])){ + case -1: + sysfatal("read: %r"); + case 0: + exits(""); + default: + write(1, buf, n); + break; + } + if(i < ntab-1) + i++; + } +} diff --git a/sys/src/cmd/upas/fs/extra/paw b/sys/src/cmd/upas/fs/extra/paw Binary files differnew file mode 100755 index 000000000..538f648f3 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/paw diff --git a/sys/src/cmd/upas/fs/extra/paw.c b/sys/src/cmd/upas/fs/extra/paw.c new file mode 100644 index 000000000..6d3a5e484 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/paw.c @@ -0,0 +1,23 @@ +#include<u.h> +#include<libc.h> +#include<bio.h> + +void +main(void) +{ + char *f[10], *s; + vlong sum; + Biobuf b; + + sum = 0; + Binit(&b, 0, OREAD); + + while(s = Brdstr(&b, '\n', 1)){ + if(getfields(s, f, nelem(f), 1, " ") > 2) + sum += strtoul(f[2], 0, 0); + free(s); + } + Bterm(&b); + print("%lld\n", sum); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/prflags.c b/sys/src/cmd/upas/fs/extra/prflags.c new file mode 100644 index 000000000..6a42045d1 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/prflags.c @@ -0,0 +1,37 @@ +#include "common.h" + +void +usage(void) +{ + fprint(2, "usage: prflags\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *f[Fields+1], buf[20], *s; + int n; + Biobuf b, o; + + ARGBEGIN{ + default: + usage(); + }ARGEND + if(argc) + usage(); + Binit(&b, 0, OREAD); + Binit(&o, 1, OWRITE); + + for(; s = Brdstr(&b, '\n', 1); free(s)){ + n = gettokens(s, f, nelem(f), " "); + if(n != Fields) + continue; + if(!strcmp(f[0], "-")) + continue; + Bprint(&o, "%s\n", flagbuf(buf, strtoul(f[1], 0, 16))); + } + Bterm(&b); + Bterm(&o); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/strtotmtst.c b/sys/src/cmd/upas/fs/extra/strtotmtst.c new file mode 100644 index 000000000..074e10e2c --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/strtotmtst.c @@ -0,0 +1,17 @@ +#include "strtotm.c" + +void +main(int argc, char **argv) +{ + Tm tm; + + ARGBEGIN{ + }ARGEND + + for(; *argv; argv++) + if(strtotm(*argv, &tm) >= 0) + print("%s", asctime(&tm)); + else + print("bad\n"); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/tokens.c b/sys/src/cmd/upas/fs/extra/tokens.c new file mode 100644 index 000000000..6192f2b93 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/tokens.c @@ -0,0 +1,62 @@ +#include <u.h> +#include <libc.h> + +/* unfortunately, tokenize insists on collapsing multiple seperators */ +static char qsep[] = " \t\r\n"; + +static char* +qtoken(char *s, char *sep) +{ + int quoting; + char *t; + + quoting = 0; + t = s; /* s is output string, t is input string */ + while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){ + if(*t != '\''){ + *s++ = *t++; + continue; + } + /* *t is a quote */ + if(!quoting){ + quoting = 1; + t++; + continue; + } + /* quoting and we're on a quote */ + if(t[1] != '\''){ + /* end of quoted section; absorb closing quote */ + t++; + quoting = 0; + continue; + } + /* doubled quote; fold one quote into two */ + t++; + *s++ = *t++; + } + if(*s != '\0'){ + *s = '\0'; + if(t == s) + t++; + } + return t; +} + +int +getmtokens(char *s, char **args, int maxargs, int multiflag) +{ + int i; + + for(i = 0; i < maxargs; i++){ + if(multiflag) + while(*s && utfrune(qsep, *s)) + s++; + else if(*s && utfrune(qsep, *s)) + s++; + if(*s == 0) + break; + args[i] = s; + s = qtoken(s, qsep); + } + return i; +} diff --git a/sys/src/cmd/upas/fs/fs.c b/sys/src/cmd/upas/fs/fs.c index fffb290aa..acfca8838 100644 --- a/sys/src/cmd/upas/fs/fs.c +++ b/sys/src/cmd/upas/fs/fs.c @@ -1,17 +1,10 @@ #include "common.h" -#include <auth.h> #include <fcall.h> #include <libsec.h> -#include <ctype.h> +#include <pool.h> #include "dat.h" -enum -{ - OPERM = 0x3, // mask of all permission types in open mode -}; - typedef struct Fid Fid; - struct Fid { Qid qid; @@ -21,31 +14,13 @@ struct Fid Fid *next; Mailbox *mb; Message *m; - Message *mtop; // top level message + 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 + 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); @@ -54,9 +29,6 @@ void *emalloc(ulong); void usage(void); void reader(void); int readheader(Message*, char*, int, int); -int cistrncmp(char*, char*, int); -int tokenconvert(String*, char*, int); -String* stringconvert(String*, char*, int); void post(char*, char*, int); char *rflush(Fid*), *rauth(Fid*), @@ -93,30 +65,36 @@ 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 Ebadargs[] = "invalid arguments"; +char Enotme[] = "path not served by this file server"; -char *dirtab[] = -{ +char *dirtab[] = { [Qdir] ".", -[Qbody] "body", [Qbcc] "bcc", +[Qbody] "body", [Qcc] "cc", [Qdate] "date", [Qdigest] "digest", [Qdisposition] "disposition", +[Qffrom] "ffrom", +[Qfileid] "fileid", [Qfilename] "filename", +[Qflags] "flags", [Qfrom] "from", [Qheader] "header", [Qinfo] "info", [Qinreplyto] "inreplyto", -[Qlines] "lines", -[Qmimeheader] "mimeheader", +[Qlines] "lines", [Qmessageid] "messageid", +[Qmimeheader] "mimeheader", [Qraw] "raw", -[Qrawunix] "rawunix", [Qrawbody] "rawbody", [Qrawheader] "rawheader", +[Qrawunix] "rawunix", +[Qreferences] "references", [Qreplyto] "replyto", [Qsender] "sender", +[Qsize] "size", [Qsubject] "subject", [Qto] "to", [Qtype] "type", @@ -128,61 +106,290 @@ char *dirtab[] = enum { - Hsize= 1277, + Hsize= 1999, }; -Hash *htab[Hsize]; +char *mntpt; +char user[Elemlen]; +int Dflag; +int Sflag; +int iflag; +int lflag; +int biffing; int debug; -int fflag; -int logging; +int plumbing = 1; +ulong cachetarg = Maxcache; +Mailbox *mbl; +QLock mbllock; + +static int messagesize = 8*1024 + IOHDRSZ; +static int mfd[2]; +static char hbuf[32*1024]; +static uchar mbuf[16*1024 + IOHDRSZ]; +static uchar mdata[16*1024 + IOHDRSZ]; +static ulong path; /* incremented for each new file */ +static Hash *htab[Hsize]; +static Fcall rhdr; +static Fcall thdr; +static Fid *fids; +static uintptr bos = 0xd0000000; /* pc kernel specific */ + +#define onstack(x) ((uintptr)(x) >= bos) +#define intext(x) ((char*)(x) <= end) +#define validgptr(x) assert(!onstack(x) && !intext(x)) +void +sanemsg(Message *m) +{ + if(bos == 0) + bos = absbos(); + assert(m->refs < 100); + validgptr(m->whole); + if(debug) + poolcheck(mainmem); + validgptr(m); + assert(m->next != m); + if(m->end < m->start) + abort(); + if(m->ballocd && (m->start <= m->body && m->end >= m->body)) + abort(); + if(m->end - m->start > Maxmsg) + abort(); + if(m->size > Maxmsg) + abort(); + if(m->fileid != 0 && m->fileid <= 1000000ull<<8) + abort(); +} + +void +sanembmsg(Mailbox *mb, Message *m) +{ + sanemsg(m); + if(Topmsg(mb, m)){ + if(m->start > end && m->size == 0) + abort(); + if(m->fileid <= 1000000ull<<8) + abort(); + } +} + +static void +sanefid(Fid *f) +{ + if(f->m == 0) + return; + if(f->mtop){ + sanemsg(f->mtop); + assert(f->mtop->refs > 0); + } + sanemsg(f->m); + if(f->m) + if(Topmsg(f->mb, f->m)) + assert(f->m->refs > 0); +} + +void +sanefids(void) +{ + Fid *f; + + for(f = fids; f; f = f->next) + if(f->busy) + sanefid(f); +} + +static int +Afmt(Fmt *f) +{ + char buf[SHA1dlen*2 + 1]; + uchar *u, i; + + u = va_arg(f->args, uchar*); + if(u == 0 && f->flags & FmtSharp) + return fmtstrcpy(f, "-"); + if(u == 0) + return fmtstrcpy(f, "<nildigest>"); + for(i = 0; i < SHA1dlen; i++) + sprint(buf + 2*i, "%2.2ux", u[i]); + return fmtstrcpy(f, buf); +} + +static int +Δfmt(Fmt *f) +{ + char buf[32]; + uvlong v; + + v = va_arg(f->args, uvlong); + if(f->flags & FmtSharp) + if((v>>8) == 0) + return fmtstrcpy(f, ""); + strcpy(buf, ctime(v>>8)); + buf[28] = 0; + return fmtstrcpy(f, buf); +} + +static int +Dfmt(Fmt *f) +{ + char buf[32]; + int seq; + uvlong v; + + v = va_arg(f->args, uvlong); + seq = v & 0xff; + if(seq > 99) + seq = 99; + snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq); + return fmtstrcpy(f, buf); +} + +Mpair +mpair(Mailbox *mb, Message *m) +{ + Mpair mp; + + mp.mb = mb; + mp.m = m; + return mp; +} + +static int +Pfmt(Fmt *f) +{ + char buf[128], *p, *e; + int i, dots; + Mailbox *mb; + Message *t[32], *m; + Mpair mp; + + mp = va_arg(f->args, Mpair); + mb = mp.mb; + m = mp.m; + if(m == nil || mb == nil) + return fmtstrcpy(f, "<P nil>"); + i = 0; + for(; !Topmsg(mb, m); m = m->whole){ + t[i++] = m; + if(i == nelem(t)-1) + break; + } + t[i++] = m; + dots = 0; + if(i == nelem(t)) + dots = 1; + e = buf + sizeof buf; + p = buf; + if(dots) + p = seprint(p, e, ".../"); + while(--i >= 1) + p = seprint(p, e, "%s/", t[i]->name); + if(i == 0) + seprint(p, e, "%s", t[0]->name); + return fmtstrcpy(f, buf); +} void usage(void) { - fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n"); + fprint(2, "usage: upas/fs [-DSbdlmnps] [-c cachetarg] [-f mboxfile] [-m mountpoint]\n"); exits("usage"); } void notifyf(void *, char *s) { - if(strstr(s, "alarm") || strstr(s, "interrupt")) + if(strncmp(s, "interrupt", 9) == 0) noted(NCONT); + if(strncmp(s, "die: yankee pig dog", 19) != 0) + /* don't want to call syslog from notify handler */ + fprint(2, "upas/fs: user: %s; note: %s\n", getuser(), s); noted(NDFLT); } void +setname(char **v) +{ + char buf[128], buf2[32], *p, *e; + int fd, i; + + e = buf + sizeof buf; + p = seprint(buf, e, "%s", v[0]); + for(i = 0; v[++i]; ) + p = seprint(p, e, " %s", v[i]); + snprint(buf2, sizeof buf2, "#p/%d/args", getpid()); + if((fd = open(buf2, OWRITE)) >= 0){ + write(fd, buf, p - buf); + close(fd); + } +} + +ulong +ntoi(char *s) +{ + ulong n; + + n = strtoul(s, &s, 0); + for(;;) + switch(*s++){ + default: + usage(); + case 'g': + n *= 1024; + case 'm': + n *= 1024; + case 'k': + n *= 1024; + break; + case 0: + return n; + } +} + +void main(int argc, char *argv[]) { - int p[2], std, nodflt; - char maildir[128]; - char mbox[128]; + char maildir[Pathlen], mbox[Pathlen], srvfile[64], **v; char *mboxfile, *err; - char srvfile[64]; - int srvpost; + int p[2], nodflt, srvpost; rfork(RFNOTEG); - mntpt = nil; - fflag = 0; mboxfile = nil; - std = 0; nodflt = 0; srvpost = 0; + v = argv; ARGBEGIN{ + case 'D': + Dflag = 1; + break; + case 'S': + Sflag = 1; + break; case 'b': biffing = 1; break; + case 'c': + cachetarg = ntoi(EARGF(usage())); + break; + case 'd': + debug = 1; + mainmem->flags |= POOL_PARANOIA; + break; case 'f': - fflag = 1; mboxfile = EARGF(usage()); break; + case 'i': + iflag++; + break; + case 'l': + lflag = 1; + break; case 'm': mntpt = EARGF(usage()); break; - case 'd': - debug = 1; + case 'n': + nodflt = 1; break; case 'p': plumbing = 0; @@ -190,18 +397,19 @@ main(int argc, char *argv[]) case 's': srvpost = 1; break; - case 'l': - logging = 1; - break; - case 'n': - nodflt = 1; - break; default: usage(); }ARGEND if(argc) usage(); + fmtinstall('A', Afmt); + fmtinstall('D', Dfmt); + fmtinstall(L'Δ', Δfmt); + fmtinstall('F', fcallfmt); + fmtinstall('H', encodefmt); /* forces tls stuff */ + fmtinstall('P', Pfmt); + quotefmtinstall(); if(pipe(p) < 0) error("pipe failed"); mfd[0] = p[0]; @@ -214,19 +422,13 @@ main(int argc, char *argv[]) mntpt = maildir; } if(mboxfile == nil && !nodflt){ - snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user); + 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) + if(mboxfile != nil) + if(err = newmbox(mboxfile, "mbox", 0, 0)) sysfatal("opening %s: %s", mboxfile, err); - } switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){ case -1: @@ -235,46 +437,67 @@ main(int argc, char *argv[]) henter(PATH(0, Qtop), dirtab[Qctl], (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil); close(p[1]); + setname(v); io(); - postnote(PNGROUP, getpid(), "die yankee pig dog"); + syncallmboxes(); + syskillpg(getpid()); break; default: close(p[0]); /* don't deadlock if child fails */ if(srvpost){ - sprint(srvfile, "/srv/upasfs.%s", user); + snprint(srvfile, sizeof srvfile, "/srv/upasfs.%s", user); post(srvfile, "upasfs", p[1]); - } else { + }else if(mount(p[1], -1, mntpt, MREPL, "") < 0) error("mount failed"); - } } - exits(0); + exits(""); } -static int -fileinfo(Message *m, int t, char **pp) +char* +sputc(char *p, char *e, int c) { - char *p; - int len; + if(p < e - 1) + *p++ = c; + return p; +} + +char* +seappend(char *s, char *e, char *a, int n) +{ + int l; - p = ""; - len = 0; + l = e - s - 1; + if(l < n) + n = l; + memcpy(s, a, n); + s += n; + *s = 0; + return s; +} + +static int +fileinfo(Mailbox *mb, Message *m, int t, char **pp) +{ + char *s, *e, *p; + int len, i; + static char buf[64 + 512]; + + cacheidx(mb, m); + sanembmsg(mb, m); + p = nil; + len = -1; switch(t){ case Qbody: + cachebody(mb, m); p = m->body; - len = m->bend - m->body; + len = m->bend - p; break; case Qbcc: - if(m->bcc822){ - p = s_to_c(m->bcc822); - len = strlen(p); - } + p = m->bcc; break; case Qcc: - if(m->cc822){ - p = s_to_c(m->cc822); - len = strlen(p); - } + p = m->cc; break; case Qdisposition: switch(m->disposition){ @@ -285,142 +508,131 @@ fileinfo(Message *m, int t, char **pp) 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); + p = m->date822; + if(!p){ + p = buf; + len = snprint(buf, sizeof buf, "%#Δ", m->fileid); } break; case Qfilename: - if(m->filename){ - p = s_to_c(m->filename); - len = strlen(p); - } + p = m->filename; + break; + case Qflags: + p = flagbuf(buf, m->flags); break; case Qinreplyto: - if(m->inreplyto822){ - p = s_to_c(m->inreplyto822); - len = strlen(p); - } + p = m->inreplyto; break; case Qmessageid: - if(m->messageid822){ - p = s_to_c(m->messageid822); - len = strlen(p); - } + p = m->messageid; 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); - } + if(m->from) + p = m->from; + else + p = m->unixfrom; break; - case Qheader: - p = m->header; - len = headerlen(m); + case Qffrom: + if(m->ffrom) + p = m->ffrom; break; case Qlines: - p = m->lines; - if(*p == 0) - countlines(m); - len = strlen(m->lines); + len = snprint(buf, sizeof buf, "%lud", m->lines); + p = buf; break; case Qraw: + cachebody(mb, m); 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; + if(strncmp(m->start, "From ", 5) == 0) + if(e = strchr(p, '\n')) + p = e + 1; + len = m->rbend - p; break; case Qrawunix: + cachebody(mb, m); p = m->start; len = m->end - p; break; case Qrawbody: + cachebody(mb, m); p = m->rbody; len = m->rbend - p; break; case Qrawheader: + cacheheaders(mb, m); p = m->header; len = m->hend - p; break; case Qmimeheader: + cacheheaders(mb, m); 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); + case Qreferences: + cacheheaders(mb, m); + e = buf + sizeof buf; + s = buf; + for(i = 0; i < nelem(m->references); i++){ + if(m->references[i] == 0) + break; + s = seprint(s, e, "%s\n", m->references[i]); } + *s = 0; + p = buf; + len = s - buf; + break; + case Qreplyto: + if(m->replyto != nil) + p = m->replyto; + else if(m->from != nil) + p = m->from; + else if(m->sender != nil) + p = m->sender; + else if(m->unixfrom != nil) + p = m->unixfrom; break; case Qsender: - if(m->sender822){ - p = s_to_c(m->sender822); - len = strlen(p); - } + p = m->sender; break; case Qsubject: - p = nil; - if(m->subject822){ - p = s_to_c(m->subject822); - len = strlen(p); - } + p = m->subject; + break; + case Qsize: + len = snprint(buf, sizeof buf, "%lud", m->size); + p = buf; break; case Qto: - if(m->to822){ - p = s_to_c(m->to822); - len = strlen(p); - } + p = m->to; break; case Qtype: - if(m->type){ - p = s_to_c(m->type); - len = strlen(p); - } + p = rtab[m->type].s; + len = rtab[m->type].l; break; case Qunixdate: - if(m->unixdate){ - p = s_to_c(m->unixdate); - len = strlen(p); - } + p = buf; + len = snprint(buf, sizeof buf, "%#Δ", m->fileid); + break; + case Qfileid: + p = buf; + len = snprint(buf, sizeof buf, "%D", m->fileid); break; case Qunixheader: - if(m->unixheader){ - p = s_to_c(m->unixheader); - len = s_len(m->unixheader); - } + cacheheaders(mb, m); + p = m->unixheader; break; case Qdigest: - if(m->sdigest){ - p = s_to_c(m->sdigest); - len = strlen(p); - } + p = buf; + len = snprint(buf, sizeof buf, "%A", m->digest); break; } + if(p == nil) + p = ""; + if(len == -1) + len = strlen(p); *pp = p; + putcache(mb, m); return len; } @@ -441,47 +653,53 @@ int infofields[] = { Qsender, Qmessageid, Qlines, - -1, + Qsize, + Qflags, + Qfileid, + Qffrom, }; -static int -readinfo(Message *m, char *buf, long off, int count) +int +readinfo(Mailbox *mb, 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; + char *s, *p, *e; + int i, n; + long off0; + + if(m->infolen > 0 && off >= m->infolen) + return 0; + off0 = off; + s = buf; + e = s + count; + for(i = 0; s < e; i++){ + if(i == nelem(infofields)){ + m->infolen = s - buf + off0; + break; + } + n = fileinfo(mb, m, infofields[i], &p); + if(off > n){ + off -= n + 1; + continue; + } + if(off){ n -= off; + p += off; off = 0; } - if(n > count - len) - n = count - len; - if(buf) - memmove(buf+len, p, n); - len += n; + if(s + n > e) + n = e - s; + memcpy(s, p, n); + s += n; + if(s < e) + *s++ = '\n'; } - s_free(s); - return len; + return s - buf; } static void mkstat(Dir *d, Mailbox *mb, Message *m, int t) { - char *p; + char *p, *e; d->uid = user; d->gid = user; @@ -491,13 +709,13 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t) 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 { + if(m && m->fileid > 1000000ull) + d->atime = m->fileid >> 8; + else if(mb && mb->d) + d->atime = mb->d->mtime; + else d->atime = time(0); - d->mtime = d->atime; - } + d->mtime = d->atime; switch(t){ case Qtop: @@ -530,6 +748,12 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t) d->length = 0; d->qid.path = PATH(0, Qctl); break; + case Qheader: + d->name = dirtab[t]; + cacheheaders(mb, m); + d->length = readheader(m, hbuf, 0, sizeof hbuf); + putcache(mb, m); + break; case Qmboxctl: d->name = dirtab[t]; d->mode = 0222; @@ -539,12 +763,37 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t) break; case Qinfo: d->name = dirtab[t]; - d->length = readinfo(m, nil, 0, 1<<30); + d->length = readinfo(mb, m, hbuf, 0, sizeof hbuf); d->qid.path = PATH(m->id, t); break; + case Qraw: + cacheheaders(mb, m); + p = m->start; + if(strncmp(m->start, "From ", 5) == 0) + if(e = strchr(p, '\n')) + p = e + 1; + d->name = dirtab[t]; + d->length = m->size - (p - m->start); + putcache(mb, m); + break; + case Qrawbody: + d->name = dirtab[t]; + d->length = m->rawbsize; + break; + case Qrawunix: + d->name = dirtab[t]; + d->length = m->size; + if(mb->addfrom && Topmsg(mb, m)){ + cacheheaders(mb, m); + d->length += strlen(m->unixheader); + putcache(mb, m); + } + break; + case Qflags: + d->mode = 0666; default: d->name = dirtab[t]; - d->length = fileinfo(m, t, &p); + d->length = fileinfo(mb, m, t, &p); d->qid.path = PATH(m->id, t); break; } @@ -577,9 +826,8 @@ rauth(Fid*) } char* -rflush(Fid *f) +rflush(Fid*) { - USED(f); return 0; } @@ -608,30 +856,43 @@ doclone(Fid *f, int nfid) return nil; nf->busy = 1; nf->open = 0; - nf->m = f->m; - nf->mtop = f->mtop; - nf->mb = f->mb; - if(f->mb != nil) - mboxincref(f->mb); - if(f->mtop != nil){ - qlock(f->mb); - msgincref(f->mtop); - qunlock(f->mb); + if(nf->mb = f->mb) + mboxincref(nf->mb); + if(nf->m = f->m) + msgincref(gettopmsg(nf->mb, nf->m)); + if(nf->mtop = f->mtop){ + qlock(nf->mb); + msgincref(nf->mtop); + qunlock(nf->mb); } nf->qid = f->qid; +sanefid(nf); +sanefid(f); return nf; } +/* slow? binary search? */ +static int +dindex(char *name) +{ + int i; + + for(i = 0; i < Qmax; i++) + if(dirtab[i] != nil) + if(strcmp(dirtab[i], name) == 0) + return i; + return -1; +} + char* dowalk(Fid *f, char *name) { - int t; - Mailbox *omb, *mb; char *rv, *p; + int t, t1; + Mailbox *omb, *mb; Hash *h; t = FILE(f->qid.path); - rv = Enotexist; omb = f->mb; @@ -640,12 +901,26 @@ dowalk(Fid *f, char *name) else qlock(&mbllock); - // this must catch everything except . and .. + /* this must catch everything except . and .. */ retry: - h = hlook(f->qid.path, name); +sanefid(f); + t1 = FILE(f->qid.path); + if((t1 == Qmbox || t1 == Qdir) && *name >= 'a' && *name <= 'z'){ + h = hlook(f->qid.path, "xxx"); /* sleezy speedup */ + t1 = dindex(name); + if(t1 == -1) + h = nil; + }else + h = hlook(f->qid.path, name); if(h != nil){ + if(f->m) + msgdecref(f->mb, gettopmsg(f->mb, f->m)); + if(f->mb && f->mb != h->mb) + mboxdecref(f->mb); f->mb = h->mb; f->m = h->m; + if(f->m) + msgincref(gettopmsg(f->mb, f->m)); switch(t){ case Qtop: if(f->mb != nil) @@ -659,8 +934,11 @@ retry: break; } f->qid = h->qid; + if(t1 < Qmax) + f->qid.path = PATH(f->m->id, t1); /* sleezy speedup */ +sanefid(f); rv = nil; - } else if((p = strchr(name, '.')) != nil && *name != '.'){ + }else if((p = strchr(name, '.')) != nil && *name != '.'){ *p = 0; goto retry; } @@ -697,13 +975,15 @@ retry: break; case Qdir: qlock(f->mb); - if(f->m->whole == f->mb->root){ + if(Topmsg(f->mb, f->m)){ 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); + msgdecref(f->mb, f->m); f->m = f->mtop = nil; } else { + /* refs don't change; still the same message */ f->m = f->m->whole; f->qid.path = PATH(f->m->id, Qdir); f->qid.type = QTDIR; @@ -723,6 +1003,7 @@ rwalk(Fid *f) char *rv; int i; +sanefid(f); if(f->open) return Eisopen; @@ -754,32 +1035,31 @@ rwalk(Fid *f) } rhdr.nwqid = i; - /* we only error out if no walk */ + /* we only error out if no walk */ if(i > 0) rv = nil; - +sanefid(f); return rv; } -char * +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) + if(file != Qctl && file != Qmboxctl && file != Qflags) return Eperm; - // make sure we've decoded + /* make sure we've decoded */ if(file == Qbody){ - if(f->m->decoded == 0) - decode(f->m); - if(f->m->converted == 0) - convert(f->m); + cachebody(f->mb, f->m); + decode(f->m); + convert(f->m); + putcache(f->mb, f->m); } rhdr.iounit = 0; @@ -788,7 +1068,7 @@ ropen(Fid *f) return 0; } -char * +char* rcreate(Fid*) { return Eperm; @@ -816,7 +1096,7 @@ readtopdir(Fid*, uchar *buf, long off, int cnt, int blen) for(mb = mbl; mb != nil; mb = mb->next){ mkstat(&d, mb, nil, Qmbox); - m = convD2M(&d, &buf[n], blen-n); + m = convD2M(&d, &buf[n], blen - n); if(off <= pos){ if(m <= BIT16SZ || m > cnt) break; @@ -851,22 +1131,22 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen) off -= m; } - // to avoid n**2 reads of the directory, use a saved finger pointer + /* 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 + /* act like deleted files aren't there */ if(msg->deleted) continue; mkstat(&d, f->mb, msg, Qdir); - m = convD2M(&d, &buf[n], blen-n); + m = convD2M(&d, &buf[n], blen - n); if(off <= pos){ if(m <= BIT16SZ || m > cnt) break; @@ -876,7 +1156,7 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen) pos += m; } - // save a finger pointer for next read of the mbox directory + /* save a finger pointer for next read of the mbox directory */ f->foff = pos; f->fptr = msg; f->fvers = f->mb->vers; @@ -896,7 +1176,7 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen) pos = 0; for(i = 0; i < Qmax; i++){ mkstat(&d, f->mb, f->m, i); - m = convD2M(&d, &buf[n], blen-n); + m = convD2M(&d, &buf[n], blen - n); if(off <= pos){ if(m <= BIT16SZ || m > cnt) return n; @@ -907,7 +1187,7 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen) } for(msg = f->m->part; msg != nil; msg = msg->next){ mkstat(&d, f->mb, msg, Qdir); - m = convD2M(&d, &buf[n], blen-n); + m = convD2M(&d, &buf[n], blen - n); if(off <= pos){ if(m <= BIT16SZ || m > cnt) break; @@ -920,60 +1200,110 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen) return n; } +static int +mboxctlread(Mailbox *mb, char **p) +{ + static char buf[128]; + + *p = buf; + return snprint(*p, sizeof buf, "%s\n%ld\n", mb->path, mb->vers); +} + char* rread(Fid *f) { - long off; - int t, i, n, cnt; char *p; + int t, i, n, cnt; + long off; rhdr.count = 0; off = thdr.offset; cnt = thdr.count; - if(cnt > messagesize - IOHDRSZ) cnt = messagesize - IOHDRSZ; - rhdr.data = (char*)mbuf; +sanefid(f); t = FILE(f->qid.path); if(f->qid.type & QTDIR){ - if(t == Qtop) { + if(t == Qtop){ qlock(&mbllock); n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); qunlock(&mbllock); - } else if(t == Qmbox) { + }else if(t == Qmbox) { qlock(f->mb); if(off == 0) syncmbox(f->mb, 1); n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); qunlock(f->mb); - } else if(t == Qmboxctl) { + }else if(t == Qmboxctl) n = 0; - } else { + else n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ); - } - rhdr.count = n; return nil; } - if(FILE(f->qid.path) == Qheader){ + switch(t){ + case Qctl: + rhdr.count = 0; + break; + case Qmboxctl: + i = mboxctlread(f->mb, &p); + goto output; + break; + case Qheader: + cacheheaders(f->mb, f->m); 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; + putcache(f->mb, f->m); + break; + case Qinfo: + if(cnt > sizeof mbuf) + cnt = sizeof mbuf; + rhdr.count = readinfo(f->mb, f->m, (char*)mbuf, off, cnt); + break; + case Qrawunix: + if(f->mb->addfrom && Topmsg(f->mb, f->m)){ + cacheheaders(f->mb, f->m); + p = f->m->unixheader; + if(off < strlen(p)){ + rhdr.count = strlen(p + off); + memmove(mbuf, p + off, rhdr.count); + break; + } + off -= strlen(p); + putcache(f->mb, f->m); + } + default: + i = fileinfo(f->mb, f->m, t, &p); + output: + if(off < i){ + if(off + cnt > i) + cnt = i - off; + if(cnt > sizeof mbuf) + cnt = sizeof mbuf; + memmove(mbuf, p + off, cnt); + rhdr.count = cnt; + } + break; } + 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; +char* +modflags(Mailbox *mb, Message *m, char *p) +{ + char *err; + uchar f; + + f = m->flags; + if(err = txflags(p, &f)) + return err; + if(f != m->flags){ + if(mb->modflags != nil) + mb->modflags(mb, m, f); + m->flags = f; + m->cstate |= Cidxstale; } return nil; } @@ -981,87 +1311,141 @@ rread(Fid *f) char* rwrite(Fid *f) { - char *err; - char *token[1024]; - int t, n; - String *file; + char *argvbuf[1024], **argv, file[Pathlen], *err, *v0; + int i, t, argc, flags; + Message *m; t = FILE(f->qid.path); rhdr.count = thdr.count; + sanefid(f); + 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; + argv = argvbuf; 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) + memset(argvbuf, 0, sizeof argvbuf); + argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1); + if(argc == 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(argv[0], "open") == 0 || strcmp(argv[0], "create") == 0){ + if(argc == 1 || argc > 3) + return Ebadargs; + mboxpathbuf(file, sizeof file, getlog(), argv[1]); + if(argc == 3){ + if(strchr(argv[2], '/') != nil) + return "/ not allowed in mailbox name"; + }else + argv[2] = nil; + flags = 0; + if(strcmp(argv[0], "create") == 0) + flags |= DMcreate; + return newmbox(file, argv[2], flags, 0); } - if(strcmp(token[0], "close") == 0){ - if(n < 2) + if(strcmp(argv[0], "close") == 0){ + if(argc < 2) return nil; - freembox(token[1]); + for(i = 1; i < argc; i++) + freembox(argv[i]); return nil; } - if(strcmp(token[0], "delete") == 0){ - if(n < 3) + if(strcmp(argv[0], "delete") == 0){ + if(argc < 3) return nil; - delmessages(n-1, &token[1]); + delmessages(argc - 1, argv + 1); return nil; } + if(strcmp(argv[0], "flag") == 0){ + if(argc < 3) + return nil; + return flagmessages(argc - 1, argv + 1); + } + if(strcmp(argv[0], "remove") == 0){ + v0 = argv0; + flags = 0; + ARGBEGIN{ + default: + argv0 = v0; + return Ebadargs; + case 'r': + flags |= Rrecur; + break; + case 't': + flags |= Rtrunc; + break; + }ARGEND + argv0 = v0; + if(argc == 0) + return Ebadargs; + for(; *argv; argv++){ + mboxpathbuf(file, sizeof file, getlog(), *argv); + if(err = newmbox(file, nil, 0, 0)) + return err; +// if(!mb->remove) +// return "remove not implemented"; + if(err = removembox(file, flags)) + return err; + } + return 0; + } + if(strcmp(argv[0], "rename") == 0){ + v0 = argv0; + flags = 0; + ARGBEGIN{ + case 't': + flags |= Rtrunc; + break; + }ARGEND + argv0 = v0; + if(argc != 2) + return Ebadargs; + return mboxrename(argv[0], argv[1], flags); + } 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) + argc = tokenize(thdr.data, argv, nelem(argvbuf)); + if(argc == 0) return Ebadctl; - return (*f->mb->ctl)(f->mb, n, token); + return f->mb->ctl(f->mb, argc, argv); } + break; + case Qflags: + /* + * modifying flags on subparts is a little strange. + */ + if(!f->mb || !f->m) + break; + m = gettopmsg(f->mb, f->m); + err = modflags(f->mb, m, thdr.data); +// premature optimization? flags not written immediately. +// if(err == nil && f->m->cstate&Cidxstale) +// wridxfile(f->mb); /* syncmbox(f->mb, 1); */ + return err; } return Eperm; } -char * +char* rclunk(Fid *f) { Mailbox *mb; - f->busy = 0; +sanefid(f); + f->busy = 1; + /* coherence(); */ + f->fid = -1; f->open = 0; - if(f->mtop != nil){ + if(f->mtop){ qlock(f->mb); msgdecref(f->mb, f->mtop); qunlock(f->mb); } + if(f->m) + msgdecref(f->mb, gettopmsg(f->mb, f->m)); f->m = f->mtop = nil; mb = f->mb; if(mb != nil){ @@ -1071,17 +1455,18 @@ rclunk(Fid *f) mboxdecref(mb); qunlock(&mbllock); } - f->fid = -1; + f->busy = 0; return 0; } char * rremove(Fid *f) { +sanefid(f); if(f->m != nil){ if(f->m->deleted == 0) mailplumb(f->mb, f->m, 1); - f->m->deleted = 1; + f->m->deleted = Deleted; } return rclunk(f); } @@ -1091,6 +1476,7 @@ rstat(Fid *f) { Dir d; +sanefid(f); if(FILE(f->qid.path) == Qmbox){ qlock(f->mb); syncmbox(f->mb, 1); @@ -1102,13 +1488,13 @@ rstat(Fid *f) return 0; } -char * +char* rwstat(Fid*) { return Eperm; } -Fid * +Fid* newfid(int fid) { Fid *f, *ff; @@ -1152,33 +1538,44 @@ io(void) int n; /* start a process to watch the mailboxes*/ - if(plumbing){ + if(plumbing || biffing) switch(rfork(RFPROC|RFMEM)){ case -1: /* oh well */ break; case 0: reader(); - exits(nil); + exits(""); default: break; } - } - while((n = read9pmsg(mfd[0], mdata, messagesize)) != 0){ + 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) - error("mount read"); - if(convM2S(mdata, n, &thdr) != n) - error("convM2S format error"); + return; + if(convM2S(mdata, n, &thdr) == 0) + continue; - if(debug) + if(Dflag) 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)); + err = fcalls[thdr.type](newfid(thdr.fid)); if(err){ rhdr.type = Rerror; rhdr.ename = err; @@ -1187,14 +1584,16 @@ io(void) rhdr.fid = thdr.fid; } rhdr.tag = thdr.tag; - if(debug) - fprint(2, "%s:->%F\n", argv0, &rhdr);/**/ + if(Dflag) + fprint(2, "%s:->%F\n", argv0, &rhdr); n = convS2M(&rhdr, mdata, messagesize); if(write(mfd[1], mdata, n) != n) error("mount write"); } } +static char *readerargv[] = {"upas/fs", "plumbing", 0}; + void reader(void) { @@ -1202,13 +1601,14 @@ reader(void) Dir *d; Mailbox *mb; + setname(readerargv); 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){ + if(mb->waketime != 0 && t >= mb->waketime){ qlock(mb); mb->waketime = 0; break; @@ -1256,8 +1656,8 @@ newid(void) void error(char *s) { - postnote(PNGROUP, getpid(), "die yankee pig dog"); - fprint(2, "%s: %s: %r\n", argv0, s); + syskillpg(getpid()); + eprint("upas/fs: fatal error: %s: %r\n", s); exits(s); } @@ -1266,13 +1666,13 @@ typedef struct Ignorance Ignorance; struct Ignorance { Ignorance *next; - char *str; /* string */ - int partial; /* true if not exact match */ + char *str; + int len; }; Ignorance *ignorance; /* - * read the file of headers to ignore + * read the file of headers to ignore */ void readignore(void) @@ -1288,15 +1688,13 @@ readignore(void) if(b == 0) return; while(p = Brdline(b, '\n')){ - p[Blinelen(b)-1] = 0; + 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 = emalloc(sizeof *i); + i->len = strlen(p); i->str = strdup(p); if(i->str == 0){ free(i); @@ -1315,159 +1713,37 @@ ignore(char *p) readignore(); for(i = ignorance; i != nil; i = i->next) - if(cistrncmp(i->str, p, i->partial) == 0) + if(cistrncmp(i->str, p, i->len) == 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: =?charset?q?encoded-text?= -int -rfc2047convert(String *s, char *token, int len) -{ - char charset[100], decoded[1024], *e, *x; - int l; - - if(len == 0) - return -1; - - e = token+len-2; - token += 2; - - x = memchr(token, '?', e-token); - if(x == nil || (l=x-token) >= sizeof charset) - return -1; - memmove(charset, token, l); - charset[l] = 0; - - token = x+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, 1); - if(len > 0 && decoded[len-1] == '\n') - len--; - decoded[len] = 0; - } else - return -1; - - if(xtoutf(charset, &x, decoded, decoded+len) <= 0) - s_append(s, decoded); - else { - s_append(s, x); - free(x); - } - 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, *p, *e; - - s = s_reset(s); - p = uneaten; - for(e = p+len; p < e; ){ - while(*p++ == '=' && (token = rfc2047start(uneaten, p))){ - s_nappend(s, uneaten, token-uneaten); - if(rfc2047convert(s, token, p - token) < 0) - s_nappend(s, token, p - token); - uneaten = p; - for(; p<e && isspace(*p);) - p++; - if(p+2 < e && p[0] == '=' && p[1] == '?') - uneaten = p; // paste - } - } - 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; + char *s, *end, *se, *p, *e, *to; + int n, ns, salloc; + to = buf; p = m->header; e = m->hend; - s = nil; + s = emalloc(salloc = 2048); + end = s + salloc; - // copy in good headers + /* copy in good headers */ while(cnt > 0 && p < e){ n = hdrlen(p, e); + assert(n > 0); if(ignore(p)){ p += n; continue; } - - // rfc2047 processing - s = stringconvert(s, p, n); - ns = s_len(s); + if(n + 1 > salloc){ + s = erealloc(s, salloc = n + 1); + end = s + salloc; + } + se = rfc2047(s, end, p, n, 0); + ns = se - s; if(off > 0){ if(ns <= off){ off -= ns; @@ -1478,34 +1754,16 @@ readheader(Message *m, char *buf, int off, int cnt) } if(ns > cnt) ns = cnt; - memmove(to, s_to_c(s)+off, ns); + memmove(to, s + off, ns); to += ns; p += n; cnt -= ns; off = 0; } - - s_free(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 @@ -1545,6 +1803,7 @@ henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb) int h; Hash *hp, **l; +//if(m)sanemsg(m); qlock(&hashlock); h = hash(ppath, name); for(l = &htab[h]; *l != nil; l = &(*l)->next){ @@ -1595,25 +1854,43 @@ hashmboxrefs(Mailbox *mb) int refs = 0; qlock(&hashlock); - for(h = 0; h < Hsize; h++){ + 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 refs; + Mailbox *mb; + +// qlock(&mbllock); + for(mb = mbl; mb; mb = mb->next){ + qlock(mb); + refs = fidmboxrefs(mb) + 1; + if(refs != mb->refs){ + eprint("%s:%s ref mismatch got %d expected %d\n", mb->name, mb->path, refs, mb->refs); + abort(); + } + qunlock(mb); + } +// qunlock(&mbllock); +} + +void post(char *name, char *envname, int srvfd) { - int fd; char buf[32]; + int fd; fd = create(name, OWRITE, 0600); if(fd < 0) error("post failed"); - sprint(buf, "%d",srvfd); + snprint(buf, sizeof buf, "%d", srvfd); if(write(fd, buf, strlen(buf)) != strlen(buf)) error("srv write"); close(fd); diff --git a/sys/src/cmd/upas/fs/header.c b/sys/src/cmd/upas/fs/header.c new file mode 100644 index 000000000..c6ffe8216 --- /dev/null +++ b/sys/src/cmd/upas/fs/header.c @@ -0,0 +1,176 @@ +#include "common.h" +#include <ctype.h> +#include <libsec.h> +#include "dat.h" + +int +hdrlen(char *p, char *e) +{ + char *ep; + + ep = p; + do { + ep = strchr(ep, '\n'); + if(ep == nil){ + ep = e; + break; + } + if(ep == p) + break; + if(ep - p == 1 && ep[-1] == '\r') + break; + ep++; + if(ep >= e){ + ep = e; + break; + } + } while(*ep == ' ' || *ep == '\t'); + return ep - p; +} + +/* rfc2047 non-ascii: =?charset?q?encoded-text?= */ +static int +tok(char **sp, char *se, char *token, int len) +{ + char charset[100], *s, *e, *x; + int l; + + if(len == 0) + return -1; + s = *sp; + e = token + len - 2; + token += 2; + + x = memchr(token, '?', e - token); + if(x == nil || (l = x - token) >= sizeof charset) + return -1; + memmove(charset, token, l); + charset[l] = 0; + + /* bail if it doesn't fit */ + token = x + 1; + if(e - token > se - s - 1) + return -1; + + if(cistrncmp(token, "b?", 2) == 0){ + token += 2; + len = dec64((uchar*)s, se - s - 1, token, e - token); + if(len == -1) + return -1; + s[len] = 0; + }else if(cistrncmp(token, "q?", 2) == 0){ + token += 2; + len = decquoted(s, token, e, 1); + if(len > 0 && s[len - 1] == '\n') + len--; + s[len] = 0; + }else + return -1; + + if(xtoutf(charset, &x, s, s + len) <= 0) + s += len; + else { + s = seprint(s, se, "%s", x); + free(x); + } + *sp = s; + return 0; +} + +char* +tokbegin(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; +} + +static char* +seappend822f(char *s, char *e, char *a, int n) +{ + int skip, c; + + skip = 0; + for(; n--; a++){ + c = *a; + if(skip && isspace(c)) + continue; + if(c == '\n'){ + c = ' '; + skip = 1; + }else{ + if(c < 0x20) + continue; + skip = 0; + } + s = sputc(s, e, c); + } + return s; +} + +static char* +seappend822(char *s, char *e, char *a, int n) +{ + int c; + + for(; n--; a++){ + c = *a; + if(c < 0x20 && c != '\n' && c != '\t') + continue; + s = sputc(s, e, c); + } + return s; +} + +/* convert a header line */ +char* +rfc2047(char *s, char *se, char *uneaten, int len, int fold) +{ + char *sp, *token, *p, *e; + char *(*f)(char*, char*, char*, int); + + f = seappend822; + if(fold) + f = seappend822f; + sp = s; + p = uneaten; + for(e = p + len; p < e; ){ + while(*p++ == '=' && (token = tokbegin(uneaten, p))){ + sp = f(sp, se, uneaten, token - uneaten); + if(tok(&sp, se, token, p - token) < 0) + sp = f(sp, se, token, p - token); + uneaten = p; + for(; p < e && isspace(*p);) + p++; + if(p + 2 < e && p[0] == '=' && p[1] == '?') + uneaten = p; /* paste */ + } + } + if(p > uneaten) + sp = f(sp, se, uneaten, e - uneaten); + *sp = 0; + return sp; +} diff --git a/sys/src/cmd/upas/fs/idx.c b/sys/src/cmd/upas/fs/idx.c new file mode 100644 index 000000000..eebe191c7 --- /dev/null +++ b/sys/src/cmd/upas/fs/idx.c @@ -0,0 +1,535 @@ +#include "common.h" +#include <libsec.h> +#include "dat.h" + +#define idprint(...) if(iflag > 1) fprint(2, __VA_ARGS__); else {} +#define iprint(...) if(iflag) fprint(2, __VA_ARGS__); else {} + +static char *magic = "idx magic v7\n"; +static char *mbmagic = "genericv1"; +enum { + Idxfields = 21, + + Idxto = 30000, /* index timeout in ms */ + Idxstep = 300, /* sleep between tries */ +}; + +void +idxfree(Idx *i) +{ + if(i->str) + free(i->str); + else{ + free(i->digest); + free(i->ffrom); + free(i->from); + free(i->to); + free(i->cc); + free(i->bcc); + free(i->replyto); + free(i->messageid); + free(i->subject); + free(i->sender); + free(i->inreplyto); + free(i->idxaux); + } + memset(i, 0, sizeof *i); +} + +static char* +∂(char *x) +{ + if(x) + return x; + return ""; +} + +static int +pridxmsg(Biobuf *b, Idx *x) +{ + Bprint(b, "%#A %ux %D %lud ", x->digest, x->flags&~Frecent, x->fileid, x->lines); + Bprint(b, "%q %q %q %q %q ", ∂(x->ffrom), ∂(x->from), ∂(x->to), ∂(x->cc), ∂(x->bcc)); + Bprint(b, "%q %q %q %q %q ", ∂(x->replyto), ∂(x->messageid), ∂(x->subject), ∂(x->sender), ∂(x->inreplyto)); + Bprint(b, "%s %d %lud %lud ", rtab[x->type].s, x->disposition, x->size, x->rawbsize); + Bprint(b, "%lud %q %d\n", x->ibadchars, ∂(x->idxaux), x->nparts); + return 0; +} + +static int +pridx0(Biobuf *b, Mailbox *mb, Message *m, int l) +{ + for(; m; m = m->next){ + if(l == 0) + if(insurecache(mb, m) == -1) + continue; + if(pridxmsg(b, m)) + return -1; + if(m->part) + pridx0(b, mb, m->part, l + 1); + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + if(l == 0) + msgdecref(mb, m); + } + return 0; +} + +void +genericidxwrite(Biobuf *b, Mailbox*) +{ + Bprint(b, "%s\n", mbmagic); +} + +static int +pridx(Biobuf *b, Mailbox *mb) +{ + int i; + + Bprint(b, magic); + mb->idxwrite(b, mb); +// prrefs(b); + i = pridx0(b, mb, mb->root->part, 0); + return i; +} + +static char *eopen[] = { + "not found", + "does not exist", + "file is locked", + "file locked", + "exclusive lock", + 0, +}; + +static char *ecreate[] = { + "already exists", + "file is locked", + "file locked", + "exclusive lock", + 0, +}; + +static int +bad(char **t) +{ + char buf[ERRMAX]; + int i; + + rerrstr(buf, sizeof buf); + for(i = 0; t[i]; i++) + if(strstr(buf, t[i])) + return 0; + return 1; +} + +static int +forceexcl(int fd) +{ + int r; + Dir *d; + + d = dirfstat(fd); + if(d == nil) + return 0; /* ignore: assume file removed */ + if(d->mode & DMEXCL){ + free(d); + return 0; + } + d->mode |= DMEXCL; + d->qid.type |= QTEXCL; + r = dirfwstat(fd, d); + free(d); + if(r == -1) + return 0; /* ignore unwritable (e.g dump) */ + close(fd); + return -1; +} + +static int +exopen(char *s) +{ + int i, fd; + + for(i = 0; i < Idxto/Idxstep; i++){ + if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(eopen)){ + if(fd != -1 && forceexcl(fd) == -1) + continue; + return fd; + } + if((fd = create(s, OWRITE|OEXCL, DMTMP|DMEXCL|0600)) >= 0 || bad(ecreate)) + return fd; + sleep(Idxstep); + } + werrstr("lock timeout"); + return -1; +} + +static Message* +findmessage(Mailbox *, Message *parent, int n) +{ + Message *m; + + for(m = parent->part; m; m = m->next) + if(!m->digest && n-- == 0) + return m; + return 0; +} + +static int +validmessage(Mailbox *mb, Message *m, int level) +{ + if(level){ + if(m->digest != 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + if(m->fileid != 0) + goto lose; + }else{ + if(m->digest == 0) + goto lose; + if(m->size == 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + goto lose; + if(mtreefind(mb, m->digest)) + goto lose; + } + return 1; +lose: + eprint("invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid); + return 0; +} + +/* + * n.b.: we don't insure this is the index version we last read. + * + * we may overwrite changes. dualing deletes should sync eventually. + * mboxsync should complain about missing messages but + * mutable information (which is not in the email itself) + * may be lost. + */ +int +wridxfile(Mailbox *mb) +{ + char buf[Pathlen + 4]; + int r, fd; + Biobuf b; + Dir *d; + + assert(semacquire(&mb->idxsem, 0) != -1); + snprint(buf, sizeof buf, "%s.idx", mb->path); + iprint("wridxfile %s\n", buf); + if((fd = exopen(buf)) == -1){ + rerrstr(buf, sizeof buf); + if(strcmp(buf, "no creates") != 0) + if(strstr(buf, "file system read only") == 0) + eprint("wridxfile: %r\n"); + semrelease(&mb->idxsem, 1); + return -1; + } + seek(fd, 0, 0); + Binit(&b, fd, OWRITE); + r = pridx(&b, mb); + Bterm(&b); + d = dirfstat(fd); + if(d == 0) + sysfatal("dirfstat: %r"); + mb->qid = d->qid; + free(d); + close(fd); + semrelease(&mb->idxsem, 1); + return r; +} + +static int +nibble(int c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c < 0x20) + c += 0x20; + if(c >= 'a' && c <= 'f') + return c - 'a'+10; + return 0xff; +} + +static uchar* +hackdigest(char *s) +{ + uchar t[SHA1dlen]; + int i; + + if(strcmp(s, "-") == 0) + return 0; + if(strlen(s) != 2*SHA1dlen){ + eprint("bad digest %s\n", s); + return 0; + } + for(i = 0; i < SHA1dlen; i++) + t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]); + memmove(s, t, SHA1dlen); + return (uchar*)s; +} + +static uvlong +rdfileid(char *s, int level) +{ + char *p; + uvlong uv; + + uv = strtoul(s, &p, 0); + if((level == 0 && uv < 1000000) || *p != '.') + return 0; + return uv<<8 | strtoul(p + 1, 0, 10); +} + +static char* +∫(char *x) +{ + if(x && *x) + return x; + return nil; +} + +/* + * strategy: use top-level avl tree to merge index with + * our ideas about the mailbox. new or old messages + * with corrupt index entries are marked Dead. they + * will be cleared out of the mailbox and are kept out + * of the index. when messages are marked Dead, a + * reread of the mailbox is forced. + * + * side note. if we get a new message while we are + * running it is added to the list in order but m->id + * looks out-of-order. this is because m->id must + * increase monotonicly. a new instance of the fs + * will result in a different ordering. + */ + +static int +rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level, int doplumb) +{ + char *f[Idxfields + 1], *s; + uchar *digest; + int n, flags, nparts, good, bad, redux; + Message *m, **ll, *l; + + bad = good = redux = 0; + ll = &parent->part; + nparts = npart; + for(; npart != 0 && (s = Brdstr(b, '\n', 1)); npart--){ + m = 0; + digest = 0; + n = tokenize(s, f, nelem(f)); + if(n != Idxfields){ +dead: + eprint("bad index %#A %d %d n=%d\n", digest, level, npart, n); + bad++; + free(s); + if(level) + return -1; + if(m) + m->deleted = Dead; + continue; + } + digest = hackdigest(f[0]); + if(digest == 0 ^ level != 0) + goto dead; + if(level == 0) + m = mtreefind(mb, digest); + else + m = findmessage(mb, parent, nparts - npart); + if(m){ + /* + * read in mutable information. + * currently this is only flags + */ + redux++; + if(level == 0) + m->deleted &= ~Dmark; + if(m->nparts) + if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1) + goto dead; + ll = &m->next; + idprint("%d seen before %d... %.2ux", level, m->id, m->cstate); + flags = m->flags; + m->flags |= strtoul(f[1], 0, 16); + if(flags != m->flags) + m->cstate |= Cidxstale; + m->cstate |= Cidx; + idprint("→%.2ux\n", m->cstate); + free(s); + // s = 0; + continue; + } + m = newmessage(parent); + idprint("%d new %d %#A\n", level, m->id, digest); + m->digest = digest; + m->flags = strtoul(f[1], 0, 16); + m->fileid = rdfileid(f[2], level); + m->lines = atoi(f[3]); + m->ffrom = ∫(f[4]); + m->from = ∫(f[5]); + m->to = ∫(f[6]); + m->cc = ∫(f[7]); + m->bcc = ∫(f[8]); + m->replyto = ∫(f[9]); + m->messageid = ∫(f[10]); + m->subject = ∫(f[11]); + m->sender = ∫(f[12]); + m->inreplyto = ∫(f[13]); + m->type = newrefs(f[14]); + m->disposition = atoi(f[15]); + m->size = strtoul(f[16], 0, 0); + m->rawbsize = strtoul(f[17], 0, 0); + m->ibadchars = strtoul(f[18], 0, 0); + m->idxaux = ∫(f[19]); + m->nparts = strtoul(f[20], 0, 0); + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + m->str = s; + s = 0; + if(!validmessage(mb, m, level)) + goto dead; + if(level == 0){ + mtreeadd(mb, m); + m->inmbox = 1; + } + cachehash(mb, m); /* hokey */ + l = *ll; + *ll = m; + ll = &m->next; + *ll = l; + good++; + + if(m->nparts) + if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1) + goto dead; + if(doplumb && level == 0) + mailplumb(mb, m, 0); + } + if(level == 0 && bad + redux > 0) + iprint("idx: %d %d %d\n", good, bad, redux); + if(bad) + return -1; + return 0; +} + +/* bug: should check time. */ +static int +qidcmp(int fd, Qid *q) +{ + int r; + Dir *d; + Qid q0; + + d = dirfstat(fd); + if(!d) + sysfatal("dirfstat: %r"); + r = 1; + if(d->qid.path == q->path) + if(d->qid.vers == q->vers) + r = 0; + q0 = *q; + *q = d->qid; + free(d); + if(q0.path != 0 && r) + iprint("qidcmp ... index changed [%ld .. %ld]\n", q0.vers, q->vers); + return r; +} + +static int +verscmp(Biobuf *b, Mailbox *mb) +{ + char *s; + int n; + + n = -1; + if(s = Brdstr(b, '\n', 0)) + n = strcmp(s, magic); + free(s); + if(n) + return -1; + n = -1; + if(s = Brdstr(b, '\n', 0)) + n = mb->idxread(s, mb); + free(s); + return n; +} + +int +genericidxread(char *s, Mailbox*) +{ + return strcmp(s, mbmagic); +} + +void +genericidxinvalid(Mailbox *mb) +{ + if(mb->d) + memset(&mb->d->qid, 0, sizeof mb->d->qid); + mb->waketime = time(0); +} + +void +mark(Mailbox *mb) +{ + Message *m; + + for(m = mb->root->part; m != nil; m = m->next) + m->deleted |= Dmark; +} + +int +unmark(Mailbox *mb) +{ + int i; + Message *m; + + i = 0; + for(m = mb->root->part; m != nil; m = m->next) + if(m->deleted & Dmark){ + i++; + m->deleted &= ~Dmark; /* let mailbox scan figure this out. BOTCH?? */ + } + return i; +} + +int +rdidxfile0(Mailbox *mb, int doplumb) +{ + char buf[Pathlen + 4]; + int r, v; + Biobuf *b; + + snprint(buf, sizeof buf, "%s.idx", mb->path); + b = Bopen(buf, OREAD); + if(b == nil) + return -2; + if(qidcmp(Bfildes(b), &mb->qid) == 0) + r = 0; + else if(verscmp(b, mb) == -1) + r = -1; + else{ + mark(mb); + r = rdidx(b, mb, mb->root, -1, 0, doplumb); + v = unmark(mb); + if(r == 0 && v > 0) + r = -1; + } + Bterm(b); + return r; +} + +int +rdidxfile(Mailbox *mb, int doplumb) +{ + int r; + + assert(semacquire(&mb->idxsem, 0) > 0); + r = rdidxfile0(mb, doplumb); + if(r == -1 && mb->idxinvalid) + mb->idxinvalid(mb); + semrelease(&mb->idxsem, 1); + return r; +} diff --git a/sys/src/cmd/upas/fs/imap.c b/sys/src/cmd/upas/fs/imap.c new file mode 100644 index 000000000..4a3f74467 --- /dev/null +++ b/sys/src/cmd/upas/fs/imap.c @@ -0,0 +1,1271 @@ +/* + * todo: + * 1. sync with imap server's flags + * 2. better algorithm for avoiding downloading message list. + * 3. get sender — eating envelope is lots of work! + */ +#include "common.h" +#include <libsec.h> +#include <auth.h> +#include "dat.h" + +#define idprint(i, ...) if(i->flags & Fdebug) fprint(2, __VA_ARGS__); else {} +#pragma varargck argpos imap4cmd 2 +#pragma varargck type "Z" char* +#pragma varargck type "U" uvlong +#pragma varargck type "U" vlong + +static char confused[] = "confused about fetch response"; +static char qsep[] = " \t\r\n"; +static char Eimap4ctl[] = "bad imap4 control message"; + +enum{ + /* cap */ + Cnolog = 1<<0, + Ccram = 1<<1, + Cntlm = 1<<2, + + /* flags */ + Fssl = 1<<0, + Fdebug = 1<<1, + Fgmail = 1<<2, +}; + +typedef struct { + uvlong uid; + ulong sizes; + ulong dates; +} Fetchi; + +typedef struct Imap Imap; +struct Imap { + long lastread; + + char *mbox; + /* free this to free the strings below */ + char *freep; + char *host; + char *user; + + int refreshtime; + uchar cap; + uchar flags; + + ulong tag; + ulong validity; + int nmsg; + int size; + + Fetchi *f; + int nuid; + int muid; + + Thumbprint *thumb; + + /* open network connection */ + Biobuf bin; + Biobuf bout; + int binit; + int fd; +}; + +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; +} + +static int +Zfmt(Fmt *f) +{ + char *s, *t; + int w, quotes; + Rune r; + + s = va_arg(f->args, char*); + if(s == 0 || *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, '"'); +} + +static int +Ufmt(Fmt *f) +{ + char buf[20*2 + 2]; + ulong a, b; + uvlong u; + + u = va_arg(f->args, uvlong); + if(u == 1) + return fmtstrcpy(f, "nil"); + if(u == 0) + return fmtstrcpy(f, "-"); + a = u>>32; + b = u; + snprint(buf, sizeof buf, "%lud:%lud", a, b); + return fmtstrcpy(f, buf); +} + +static void +imap4cmd(Imap *imap, char *fmt, ...) +{ + char buf[256], *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"); + idprint(imap, "-> %s\n", buf); + strcpy(p, "\r\n"); + Bwrite(&imap->bout, buf, strlen(buf)); + Bflush(&imap->bout); +} + +enum { + Ok, + No, + Bad, + Bye, + Exists, + Status, + Fetch, + Cap, + Auth, + + Unknown, +}; + +static char *verblist[] = { +[Ok] "ok", +[No] "no", +[Bad] "bad", +[Bye] "bye", +[Exists] "exists", +[Status] "status", +[Fetch] "fetch", +[Cap] "capability", +[Auth] "authenticate", +}; + +static int +verbcode(char *verb) +{ + int i; + char *q; + + if(q = strchr(verb, ' ')) + *q = '\0'; + for(i = 0; i < nelem(verblist) - 1; i++) + if(strcmp(verblist[i], verb) == 0) + break; + if(q) + *q = ' '; + return i; +} + +static vlong +mkuid(Imap *i, char *id) +{ + vlong v; + + v = (vlong)i->validity<<32; + return v | strtoul(id, 0, 10); +} + +static vlong +xnum(char *s, int a, int b) +{ + vlong v; + + if(*s != a) + return -1; + v = strtoull(s + 1, &s, 10); + if(*s != b) + return -1; + return v; +} + +static struct{ + char *flag; + int e; +} ftab[] = { + "Answered", Fanswered, + "\\Deleted", Fdeleted, + "\\Draft", Fdraft, + "\\Flagged", Fflagged, + "\\Recent", Frecent, + "\\Seen", Fseen, + "\\Stored", Fstored, +}; + +static void +parseflags(Message *m, char *s) +{ + char *f[10]; + int i, j, j0, n; + + n = tokenize(s, f, nelem(f)); + qsort(f, n, sizeof *f, (int (*)(void*,void*))strcmp); + j = 0; + for(i = 0; i < n; i++) + for(j0 = j;; j++){ + if(j == nelem(ftab)){ + j = j0; /* restart search */ + break; + } + if(strcmp(f[i], ftab[j].flag) == 0){ + m->flags |= ftab[j].e; + break; + } + } +} + +/* "17-Jul-1996 02:44:25 -0700" */ +long +internaltounix(char *s) +{ + Tm tm; + if(strlen(s) < 20 || s[2] != '-' || s[6] != '-') + return -1; + s[2] = ' '; + s[6] = ' '; + if(strtotm(s, &tm) == -1) + return -1; + return tm2sec(&tm); +} + +static char* +qtoken(char *s, char *sep) +{ + int quoting; + char *t; + + quoting = 0; + t = s; /* s is output string, t is input string */ + while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){ + if(*t != '"' && *t != '(' && *t != ')'){ + *s++ = *t++; + continue; + } + /* *t is a quote */ + if(!quoting || *t == '('){ + quoting++; + t++; + continue; + } + /* quoting and we're on a quote */ + if(t[1] != '"'){ + /* end of quoted section; absorb closing quote */ + t++; + if(quoting > 0) + quoting--; + continue; + } + /* doubled quote; fold one quote into two */ + t++; + *s++ = *t++; + } + if(*s != '\0'){ + *s = '\0'; + if(t == s) + t++; + } + return t; +} + +int +imaptokenize(char *s, char **args, int maxargs) +{ + int nargs; + + for(nargs=0; nargs < maxargs; nargs++){ + while(*s!='\0' && utfrune(qsep, *s)!=nil) + s++; + if(*s == '\0') + break; + args[nargs] = s; + s = qtoken(s, qsep); + } + + return nargs; +} + +static char* +fetchrsp(Imap *imap, char *p, Mailbox *, Message *m) +{ + char *f[15], *s, *q; + int i, n, a; + ulong o, l; + uvlong v; + static char error[256]; + extern void msgrealloc(Message*, ulong); + +redux: + n = imaptokenize(p, f, nelem(f)); + if(n%2) + return confused; + for(i = 0; i < n; i += 2){ + if(strcmp(f[i], "internaldate") == 0){ + l = internaltounix(f[i + 1]); + if(l < 418319360) + abort(); + if(imap->nuid < imap->muid) + imap->f[imap->nuid].dates = l; + }else if(strcmp(f[i], "rfc822.size") == 0){ + l = strtoul(f[i + 1], 0, 0); + if(m) + m->size = l; + else if(imap->nuid < imap->muid) + imap->f[imap->nuid].sizes = l; + }else if(strcmp(f[i], "uid") == 0){ + v = mkuid(imap, f[1]); + if(m) + m->imapuid = v; + if(imap->nuid < imap->muid) + imap->f[imap->nuid].uid = v; + }else if(strcmp(f[i], "flags") == 0) + parseflags(m, f[i + 1]); + else if(strncmp(f[i], "body[]", 6) == 0){ + s = f[i]+6; + o = 0; + if(*s == '<') + o = xnum(s, '<', '>'); + if(o == -1) + return confused; + l = xnum(f[i + 1], '{', '}'); + a = o + l - m->ibadchars - m->size; + if(a > 0){ + assert(imap->flags & Fgmail); + m->size = o + l; + msgrealloc(m, m->size); + m->size -= m->ibadchars; + } + if(Bread(&imap->bin, m->start + o, l) != l){ + snprint(error, sizeof error, "read: %r"); + return error; + } + if(Bgetc(&imap->bin) == ')'){ + while(Bgetc(&imap->bin) != '\n') + ; + return 0; + } + /* evil */ + if(!(p = Brdline(&imap->bin, '\n'))) + return 0; + q = p + Blinelen(&imap->bin); + while(q > p && (q[-1] == '\n' || q[-1] == '\r')) + q--; + *q = 0; + lowercase(p); + idprint(imap, "<- %s\n", p); + + goto redux; + }else + return confused; + } + return 0; +} + +void +parsecap(Imap *imap, char *s) +{ + char *t[32], *p; + int n, i; + + s = strdup(s); + n = getfields(s, t, nelem(t), 0, " "); + for(i = 0; i < n; i++){ + if(strncmp(t[i], "auth=", 5) == 0){ + p = t[i] + 5; + if(strcmp(p, "cram-md5") == 0) + imap->cap |= Ccram; + if(strcmp(p, "ntlm") == 0) + imap->cap |= Cntlm; + }else if(strcmp(t[i], "logindisabled") == 0) + imap->cap |= Cnolog; + } + free(s); +} + +/* + * get imap4 response line. there might be various + * data or other informational lines mixed in. + */ +static char* +imap4resp0(Imap *imap, Mailbox *mb, Message *m) +{ + char *e, *line, *p, *ep, *op, *q, *verb; + int n, unexp; + static char error[256]; + + unexp = 0; + while(p = Brdline(&imap->bin, '\n')){ + ep = p + Blinelen(&imap->bin); + while(ep > p && (ep[-1] == '\n' || ep[-1] == '\r')) + *--ep = '\0'; + idprint(imap, "<- %s\n", p); + if(unexp && p[0] != '9' && p[1] != 'x') + if(strtoul(p + 2, &p, 10) != imap->tag) + continue; + if(p[0] != '+') + lowercase(p); /* botch */ + + switch(p[0]){ + case '+': /* cram challenge */ + if(ep - p > 2) + return p + 2; + break; + 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 Bye: + /* early disconnect */ + snprint(error, sizeof error, "%s", p); + return error; + case Ok: + case No: + case Bad: + /* human readable text at p; */ + break; + case Exists: + imap->nmsg = n; + break; + case Cap: + parsecap(imap, p); + break; + case Status: + /* * status inbox (messages 2 uidvalidity 960164964) */ + if(q = strstr(p, "messages")) + imap->nmsg = strtoul(q + 8, 0, 10); + if(q = strstr(p, "uidvalidity")) + imap->validity = strtoul(q + 11, 0, 10); + break; + case Fetch: + if(*p == '('){ + p++; + if(ep[-1] == ')') + *--ep = 0; + } + if(e = fetchrsp(imap, p, mb, m)) + eprint("imap: fetchrsp: %s\n", e); + imap->nuid++; + break; + case Auth: + 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; + } + eprint("imap: expected %lud; got %s\n", imap->tag, op); + break; + default: + if(imap->flags&Fdebug || *p){ + eprint("imap: unexpected line: %s\n", p); + unexp = 1; + } + } + } + snprint(error, sizeof error, "i/o error: %r\n"); + return error; +} + +static char* +imap4resp(Imap *i) +{ + return imap4resp0(i, 0, 0); +} + +static int +isokay(char *resp) +{ + return cistrncmp(resp, "OK", 2) == 0; +} + +static char* +findflag(int idx) +{ + int i; + + for(i = 0; i < nelem(ftab); i++) + if(ftab[i].e == 1<<idx) + return ftab[i].flag; + return nil; +} + +static void +imap4modflags(Mailbox *mb, Message *m, int flags) +{ + char buf[128], *p, *e, *fs; + int i, f; + Imap *imap; + + imap = mb->aux; + e = buf + sizeof buf; + p = buf; + f = flags & ~Frecent; + for(i = 0; i < Nflags; i++) + if(f & 1<<i && (fs = findflag(i))) + p = seprint(p, e, "%s ", fs); + if(p > buf){ + p[-1] = 0; + imap4cmd(imap, "uid store %lud flags (%s)", (ulong)m->imapuid, buf); + imap4resp(imap); + } +} + +static char* +imap4cram(Imap *imap) +{ + char *s, *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192]; + int i, n, l; + + fmtinstall('[', encodefmt); + + imap4cmd(imap, "authenticate cram-md5"); + p = imap4resp(imap); + if(p == nil) + return "no challenge"; + l = dec64((uchar*)ch, sizeof ch, p, strlen(p)); + if(l == -1) + return "bad base64"; + ch[l] = 0; + idprint(imap, "challenge [%s]\n", ch); + + if(imap->user == nil) + imap->user = getlog(); + n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey, + "proto=cram role=client server=%q user=%s", imap->host, imap->user); + if(n == -1) + return "cannot find IMAP password"; + for(i = 0; i < n; i++) + if(rbuf[i] >= 'A' && rbuf[i] <= 'Z') + rbuf[i] += 'a' - 'A'; + l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf); + idprint(imap, "raw cram [%s]\n", ubuf); + snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf); + + imap->tag = 1; + idprint(imap, "-> %s\n", ebuf); + Bprint(&imap->bout, "%s\r\n", ebuf); + Bflush(&imap->bout); + + if(!isokay(s = imap4resp(imap))) + return s; + return nil; +} + +/* + * authenticate to IMAP4 server using NTLM (untested) + * + * http://davenport.sourceforge.net/ntlm.html#ntlmImapAuthentication + * http://msdn.microsoft.com/en-us/library/cc236621%28PROT.13%29.aspx + */ +static uchar* +psecb(uchar *p, uint o, int n) +{ + p[0] = n; + p[1] = n>>8; + p[2] = n; + p[3] = n>>8; + p[4] = o; + p[5] = o>>8; + p[6] = o>>16; + p[7] = o>>24; + return p+8; +} + +static uchar* +psecq(uchar *q, char *s, int n) +{ + memcpy(q, s, n); + return q+n; +} + +static char* +imap4ntlm(Imap *imap) +{ + char *s, ruser[64], enc[256]; + uchar buf[128], *p, *ep, *q, *eq, *chal; + int n; + MSchapreply mcr; + + imap4cmd(imap, "authenticate ntlm"); + imap4resp(imap); + + /* simple NtLmNegotiate blob with NTLM+OEM flags */ + imap4cmd(imap, "TlRMTVNTUAABAAAAAgIAAA=="); + s = imap4resp(imap); + n = dec64(buf, sizeof buf, s, strlen(s)); + if(n < 32 || memcmp(buf, "NTLMSSP", 8) != 0) + return "bad NtLmChallenge"; + chal = buf+24; + + if(auth_respond(chal, 8, ruser, sizeof ruser, + &mcr, sizeof mcr, auth_getkey, + "proto=mschap role=client service=imap server=%q user?", + imap->host) < 0) + return "auth_respond failed"; + + /* prepare NtLmAuthenticate blob */ + + memset(buf, sizeof buf, 0); + p = buf; + ep = p + 8 + 6*8 + 2*4; + q = ep; + eq = buf + sizeof buf; + + + memcpy(p, "NTLMSSP", 8); /* magic */ + p += 8; + + *p++ = 3; + *p++ = 0; + *p++ = 0; + *p++ = 0; + + p = psecb(p, q-buf, 24); /* LMresp */ + q = psecq(q, mcr.LMresp, 24); + + p = psecb(p, q-buf, 24); /* NTresp */ + q = psecq(q, mcr.NTresp, 24); + + p = psecb(p, q-buf, 0); /* realm */ + + n = strlen(ruser); + p = psecb(p, q-buf, n); /* user name */ + q = psecq(q, ruser, n); + + p = psecb(p, q-buf, 0); /* workstation name */ + p = psecb(p, q-buf, 0); /* session key */ + + *p++ = 0x02; /* flags: oem(2)|ntlm(0x200) */ + *p++ = 0x02; + *p++ = 0; + *p++ = 0; + + if(p > ep || q > eq) + return "error creating NtLmAuthenticate"; + enc64(enc, sizeof enc, buf, q-buf); + + imap4cmd(imap, enc); + if(!isokay(s = imap4resp(imap))) + return s; + return nil; +} + +static char* +imap4passwd(Imap *imap) +{ + char *s; + UserPasswd *up; + + 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; + return nil; +} + +static char* +imap4login(Imap *imap) +{ + char *e; + + if(imap->cap & Ccram) + e = imap4cram(imap); + else if(imap->cap & Cntlm) + e = imap4ntlm(imap); + else + e = imap4passwd(imap); + if(e) + return e; + imap4cmd(imap, "select %Z", imap->mbox); + if(!isokay(e = imap4resp(imap))) + return e; + return nil; +} + +static char* +imaperrstr(char *host, char *port) +{ + char err[ERRMAX]; + static char buf[256]; + + err[0] = 0; + errstr(err, sizeof err); + snprint(buf, sizeof buf, "%s/%s:%s", host, port, err); + return buf; +} + +static int +starttls(Imap *imap, TLSconn *tls) +{ + char buf[Pathlen]; + uchar digest[SHA1dlen]; + int sfd, fd; + + memset(tls, 0, sizeof *tls); + sfd = tlsClient(imap->fd, tls); + if(sfd < 0){ + werrstr("tlsClient: %r"); + return -1; + } + if(tls->cert == nil || tls->certlen <= 0){ + close(sfd); + werrstr("server did not provide TLS certificate"); + return -1; + } + sha1(tls->cert, tls->certlen, digest, nil); + if(!imap->thumb || !okThumbprint(digest, imap->thumb)){ + close(sfd); + werrstr("server certificate %.*H not recognized", + SHA1dlen, digest); + return -1; + } + close(imap->fd); + imap->fd = sfd; + + if(imap->flags & Fdebug){ + snprint(buf, sizeof buf, "%s/ctl", tls->dir); + fd = open(buf, OWRITE); + fprint(fd, "debug"); + close(fd); + } + + return 1; +} + +static void +imap4disconnect(Imap *imap) +{ + if(imap->binit){ + Bterm(&imap->bin); + Bterm(&imap->bout); + imap->binit = 0; + } + close(imap->fd); + imap->fd = -1; +} + +char* +capabilties(Imap *imap) +{ + char * err; + + imap4cmd(imap, "capability"); + imap4resp(imap); + err = imap4resp(imap); + if(isokay(err)) + err = 0; + return err; +} + +static char* +imap4dial(Imap *imap) +{ + char *err, *port; + TLSconn conn; + + if(imap->fd >= 0){ + imap4cmd(imap, "noop"); + if(isokay(imap4resp(imap))) + return nil; + imap4disconnect(imap); + } + if(imap->flags & Fssl) + port = "imaps"; + else + port = "imap"; + if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0) + return imaperrstr(imap->host, port); + if(imap->flags & Fssl && starttls(imap, &conn) == -1){ + err = imaperrstr(imap->host, port); + free(conn.cert); + imap4disconnect(imap); + return err; + } + assert(imap->binit == 0); + Binit(&imap->bin, imap->fd, OREAD); + Binit(&imap->bout, imap->fd, OWRITE); + imap->binit = 1; + + imap->tag = 0; + err = imap4resp(imap); + if(!isokay(err)) + return "error in initial IMAP handshake"; + + if((err = capabilties(imap)) || (err = imap4login(imap))){ + eprint("imap: err is %s\n", err); + imap4disconnect(imap); + return err; + } + return nil; +} + +static void +imap4hangup(Imap *imap) +{ + imap4cmd(imap, "logout"); + imap4resp(imap); + imap4disconnect(imap); +} + +/* gmail lies about message sizes */ +static ulong +gmaildiscount(Message *m, uvlong o, ulong l) +{ + if((m->cstate&Cidx) == 0) + if(o + l == m->size) + return l + 100 + (o + l)/5; + return l; +} + +static int +imap4fetch(Mailbox *mb, Message *m, uvlong o, ulong l) +{ + Imap *imap; + + imap = mb->aux; + if(imap->flags & Fgmail) + l = gmaildiscount(m, o, l); + idprint(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)\n", (ulong)m->imapuid, o, l); + imap4cmd(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)", (ulong)m->imapuid, o, l); + if(!isokay(imap4resp0(imap, mb, m))){ + eprint("imap: imap fetch failed\n"); + return -1; + } + return 0; +} + +static uvlong +datesec(Imap *imap, int i) +{ + int j; + uvlong v; + Fetchi *f; + + f = imap->f; + v = (uvlong)f[i].dates << 8; + + /* shifty; these sequences should be stable. */ + for(j = i; j-- > 0; ) + if(f[i].dates != f[j].dates) + break; + v |= i - (j + 1); + return v; +} + +static void +markdel(Mailbox *mb, Message *m, int doplumb) +{ + if(doplumb) + mailplumb(mb, m, 1); + m->inmbox = 0; + m->deleted = Disappear; +} + +static int +vcmp(vlong a, vlong b) +{ + a -= b; + if(a > 0) + return 1; + if(a < 0) + return -1; + return 0; +} + +static int +fetchicmp(Fetchi *f1, Fetchi *f2) +{ + return vcmp(f1->uid, f2->uid); +} + +static int +setsize(Mailbox *, Message *m, Fetchi *f) +{ + if(f->sizes >= Maxmsg) + return -1; +// if(!gmailmbox(mb)) + return m->size = f->sizes; +} + +static char* +imap4read(Imap *imap, Mailbox *mb, int doplumb, int *new) +{ + char *s; + int i, n, c, nnew, ndel; + Fetchi *f; + Message *m, **ll; + + *new = 0; + if(time(0) - imap->lastread < 10) + return nil; + imap->lastread = time(0); + imap4cmd(imap, "status %Z (messages uidvalidity)", imap->mbox); + if(!isokay(s = imap4resp(imap))) + return s; + + imap->nuid = 0; + imap->muid = imap->nmsg; + imap->f = erealloc(imap->f, imap->nmsg*sizeof imap->f[0]); + f = imap->f; + n = imap->nmsg; + + if(imap->nmsg > 0){ + imap4cmd(imap, "uid fetch 1:* (uid rfc822.size internaldate)"); + if(!isokay(s = imap4resp(imap))) + return s; + } + + qsort(f, n, sizeof f[0], (int(*)(void*, void*))fetchicmp); + nnew = ndel = 0; + ll = &mb->root->part; + for(i = 0; *ll || i < n; ){ + c = -1; + if(i >= n) + c = 1; + else if(*ll){ + if((*ll)->imapuid == 0) + (*ll)->imapuid = strtoull((*ll)->idxaux, 0, 0); + c = vcmp(f[i].uid, (*ll)->imapuid); + } + idprint(imap, "consider %U and %U -> %d\n", i<n? f[i].uid: 0, *ll? (*ll)->imapuid: 1, c); + if(c < 0){ + /* new message */ + idprint(imap, "new: %U (%U)\n", f[i].uid, *ll? (*ll)->imapuid: 0); + m = newmessage(mb->root); + m->inmbox = 1; + m->idxaux = smprint("%llud", f[i].uid); + m->imapuid = f[i].uid; + m->fileid = datesec(imap, i); + if(setsize(mb, m, f + i) < 0 || m->size >= Maxmsg){ + /* message disappeared? unchain */ + idprint(imap, "deleted → %r (%U)\n", m->imapuid); + logmsg(m, "disappeared"); + if(doplumb) + mailplumb(mb, m, 1); /* redundant */ + unnewmessage(mb, mb->root, m); + /* we're out of sync; here's were to signal that */ + break; + } + nnew++; + logmsg(m, "new %s", m->idxaux); + m->next = *ll; + *ll = m; + ll = &m->next; + i++; + newcachehash(mb, m, doplumb); + putcache(mb, m); + }else if(c > 0){ + /* deleted message; */ + idprint(imap, "deleted: %U (%U)\n", i<n? f[i].uid: 0, *ll? (*ll)->imapuid: 0); + ndel++; + logmsg(*ll, "deleted"); + markdel(mb, *ll, doplumb); + ll = &(*ll)->next; + }else{ + //logmsg(*ll, "duplicate %s", d[i].name); + i++; + ll = &(*ll)->next; + } + } + + *new = nnew; + return nil; +} + +static void +imap4delete(Mailbox *mb, Message *m) +{ + Imap *imap; + + imap = mb->aux; + if((ulong)(m->imapuid>>32) == imap->validity){ + imap4cmd(imap, "uid store %lud +flags (\\Deleted)", (ulong)m->imapuid); + imap4resp(imap); + imap4cmd(imap, "expunge"); + imap4resp(imap); +// if(!isokay(imap4resp(imap)) +// return -1; + } + m->inmbox = 0; +} + +static char* +imap4sync(Mailbox *mb, int doplumb, int *new) +{ + char *err; + Imap *imap; + + imap = mb->aux; + if(err = imap4dial(imap)) + goto out; + if((err = imap4read(imap, mb, doplumb, new)) == nil) + mb->d->atime = mb->d->mtime = time(0); +out: + mb->waketime = time(0) + imap->refreshtime; + return err; +} + +static char* +imap4ctl(Mailbox *mb, int argc, char **argv) +{ + char *a, *b; + Imap *imap; + + imap = mb->aux; + if(argc < 1) + return Eimap4ctl; + + if(argc == 1 && strcmp(argv[0], "debug") == 0){ + imap->flags ^= Fdebug; + return nil; + } + if(strcmp(argv[0], "thumbprint") == 0){ + if(imap->thumb){ + freeThumbprints(imap->thumb); + imap->thumb = 0; + } + a = "/sys/lib/tls/mail"; + b = "/sys/lib/tls/mail.exclude"; + switch(argc){ + default: + return Eimap4ctl; + case 4: + b = argv[2]; + case 3: + a = argv[1]; + case 2: + break; + } + imap->thumb = initThumbprints(a, b); + return nil; + } + if(argc == 2 && strcmp(argv[0], "uid") == 0){ + uvlong l; + Message *m; + + for(m = mb->root->part; m; m = m->next) + if(strcmp(argv[1], m->name) == 0){ + l = strtoull(m->idxaux, 0, 0); + fprint(2, "uid %s %lud %lud %lud %lud\n", m->name, (ulong)(l>>32), (ulong)l, + (ulong)(m->imapuid>>32), (ulong)m->imapuid); + } + return nil; + } + if(strcmp(argv[0], "refresh") == 0) + switch(argc){ + case 1: + imap->refreshtime = 60; + return nil; + case 2: + imap->refreshtime = atoi(argv[1]); + return nil; + } + + return Eimap4ctl; +} + +static void +imap4close(Mailbox *mb) +{ + Imap *imap; + + imap = mb->aux; + imap4disconnect(imap); + free(imap->f); + free(imap); +} + +static char* +mkmbox(Imap *imap, char *p, char *e) +{ + p = seprint(p, e, "%s/box/%s/imap.%s", MAILROOT, getlog(), imap->host); + if(imap->user && strcmp(imap->user, getlog())) + p = seprint(p, e, ".%s", imap->user); + if(cistrcmp(imap->mbox, "inbox")) + p = seprint(p, e, ".%s", imap->mbox); + return p; +} + +static char* +findmbox(char *p) +{ + char *f[10], path[Pathlen]; + int nf; + + snprint(path, sizeof path, "%s", p); + nf = getfields(path, f, 5, 0, "/"); + if(nf < 3) + return nil; + return f[nf - 1]; +} + +static char* +imap4rename(Mailbox *mb, char *p2, int) +{ + char *r, *new; + Imap *imap; + + imap = mb->aux; + new = findmbox(p2); + idprint(imap, "rename %s %s\n", imap->mbox, new); + imap4cmd(imap, "rename %s %s", imap->mbox, new); + r = imap4resp(imap); + if(!isokay(r)) + return r; + free(imap->mbox); + imap->mbox = smprint("%s", new); + mkmbox(imap, mb->path, mb->path + sizeof mb->path); + return 0; +} + +/* + * incomplete; when we say remove we want to get subfolders, too. + * so we need to to a list, and recursivly nuke folders. + */ +static char* +imap4remove(Mailbox *mb, int flags) +{ + char *r; + Imap *imap; + + imap = mb->aux; + idprint(imap, "remove %s\n", imap->mbox); + imap4cmd(imap, "delete %s", imap->mbox); + r = imap4resp(imap); + if(!isokay(r)) + return r; + if(flags & Rtrunc){ + imap4cmd(imap, "create %s", imap->mbox); + r = imap4resp(imap); + if(!isokay(r)) + return r; + } + return 0; +} + +char* +imap4mbox(Mailbox *mb, char *path) +{ + char *f[10]; + uchar flags; + int nf; + Imap *imap; + + fmtinstall('Z', Zfmt); + fmtinstall('U', Ufmt); + if(strncmp(path, "/imap/", 6) == 0) + flags = 0; + else if(strncmp(path, "/imaps/", 7) == 0) + flags = Fssl; + else + return Enotme; + + 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->freep = path; + imap->flags = flags; + imap->host = f[2]; + if(strstr(imap->host, "gmail.com")) + imap->flags |= Fgmail; + imap->refreshtime = 60; + if(nf < 4) + imap->user = nil; + else + imap->user = f[3]; + if(nf < 5) + imap->mbox = strdup("inbox"); + else + imap->mbox = strdup(f[4]); + mkmbox(imap, mb->path, mb->path + sizeof mb->path); + if(imap->flags & Fssl) + 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->fetch = imap4fetch; + mb->delete = imap4delete; + mb->rename = imap4rename; +// mb->remove = imap4remove; + mb->modflags = imap4modflags; + mb->d = emalloc(sizeof *mb->d); + mb->addfrom = 1; + return nil; +} diff --git a/sys/src/cmd/upas/fs/imap4.c b/sys/src/cmd/upas/fs/imap4.c deleted file mode 100644 index 06e8fdd84..000000000 --- a/sys/src/cmd/upas/fs/imap4.c +++ /dev/null @@ -1,878 +0,0 @@ -#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*); - -// if pipeline == 1 and upas/fs is used with dovecot, -// 9Xn OK responses sometimes come much later after FETCH responses, i.e. -// <- * 1 FETCH ... -// <- * 2 FETCH ... -// <- * 3 FETCH ... -// <- 9X5 OK Fetch completed. -// <- 9X6 OK Fetch completed. -// download 40: did not get message body -// <- 9X7 OK Fetch completed. -// causing multiple messages to turn into one in imap4.c:/^imap4resp. -int pipeline = 0; - -static char Eio[] = "i/o error"; - -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; -} - -static char* -imaperrstr(char *host, char *port) -{ - /* - * make mess big enough to hold a TLS certificate fingerprint - * plus quite a bit of slop. - */ - static char mess[3 * Errlen]; - char err[Errlen]; - - err[0] = '\0'; - errstr(err, sizeof(err)); - snprint(mess, sizeof(mess), "%s/%s:%s", host, port, err); - return mess; -} - -static int -starttls(Imap *imap) -{ - int sfd; - uchar digest[SHA1dlen]; - TLSconn conn; - - memset(&conn, 0, sizeof(conn)); - sfd = tlsClient(imap->fd, &conn); - if(sfd < 0) { - werrstr("tlsClient: %r"); - return -1; - } - imap->fd = sfd; - free(conn.sessionID); - if(conn.cert==nil || conn.certlen <= 0) { - werrstr("server did not provide TLS certificate"); - return -1; - } - sha1(conn.cert, conn.certlen, digest, nil); - free(conn.cert); - if(!imap->thumb || !okThumbprint(digest, imap->thumb)){ - fmtinstall('H', encodefmt); - werrstr("server certificate %.*H not recognized", - SHA1dlen, digest); - return -1; - } - return sfd; -} - -// -// dial and handshake with the imap server -// -static char* -imap4dial(Imap *imap) -{ - char *err, *port; - - 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 = "imap"; - - if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0) - return imaperrstr(imap->host, port); - - if(imap->mustssl){ - if(starttls(imap) < 0){ - err = imaperrstr(imap->host, port); - goto Out; - } - } - Binit(&imap->bin, imap->fd, OREAD); - Binit(&imap->bout, imap->fd, OWRITE); - err = imap4login(imap); -Out: - if(err != nil){ - if(imap->fd >= 0){ - close(imap->fd); - imap->fd = -1; - } - } - return err; -} - -// -// close connection -// -static void -imap4hangup(Imap *imap) -{ - if(imap->fd < 0) - return; - imap4cmd(imap, "LOGOUT"); - imap4resp(imap); - close(imap->fd); - imap->fd = -1; -} - -// -// 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/sys/src/cmd/upas/fs/mbox.c b/sys/src/cmd/upas/fs/mbox.c index 3cd14e6cd..3c2c1fb53 100644 --- a/sys/src/cmd/upas/fs/mbox.c +++ b/sys/src/cmd/upas/fs/mbox.c @@ -5,104 +5,188 @@ #include "dat.h" typedef struct Header Header; - struct Header { - char *type; - void (*f)(Message*, Header*, char*); - int len; + char *type; + uintptr offset; + char *(*f)(Message*, Header*, char*, char*); + int len; + int str; }; /* 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 */ -}; +static char *ctype(Message*, Header*, char*, char*); +static char *cencoding(Message*, Header*, char*, char*); +static char *cdisposition(Message*, Header*, char*, char*); +static char *from822(Message*, Header*, char*, char*); +static char *replace822(Message*, Header*, char*, char*); +static char *concat822(Message*, Header*, char*, char*); +static char *copy822(Message*, Header*, char*, char*); +static char *ref822(Message*, Header*, char*, char*); -Header head[] = +enum { - { "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, }, + Mhead = 11, /* offset of first mime header */ }; -static void fatal(char *fmt, ...); -static void initquoted(void); -static void startheader(Message*); -static void startbody(Message*); -static char* skipwhite(char*); -static char* skiptosemi(char*); -static char* getstring(char*, String*, int); -static void setfilename(Message*, char*); -static char* lowercase(char*); -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 +#define O(x) offsetof(Message, x) +static Header head[] = { - Chunksize = 1024, + "date:", O(date822), copy822, 0, 0, + "from:", O(from), from822, 0, 1, + "to:", O(to), concat822, 0, 1, + "sender:", O(sender), replace822, 0, 1, + "reply-to:", O(replyto), replace822, 0, 1, + "subject:", O(subject), copy822, 0, 1, + "cc:", O(cc), concat822, 0, 1, + "bcc:", O(bcc), concat822, 0, 1, + "in-reply-to:", O(inreplyto), replace822, 0, 1, + "message-id:", O(messageid), replace822, 0, 1, + "references:", ~0, ref822, 0, 0, + +[Mhead] "content-type:", ~0, ctype, 0, 0, + "content-transfer-encoding:", ~0, cencoding, 0, 0, + "content-disposition:", ~0, cdisposition, 0, 0, }; -Mailboxinit *boxinit[] = { +static Mailboxinit *boxinit[] = { imap4mbox, pop3mbox, - planbmbox, - planbvmbox, + mdirmbox, +// planbmbox, plan9mbox, }; +/* + * do we want to plumb flag changes? + */ char* syncmbox(Mailbox *mb, int doplumb) { - return (*mb->sync)(mb, doplumb); + char *s; + int n, d, y, a; + Message *m, *next; + + if(semacquire(&mb->syncsem, 0) <= 0) + return nil; + a = mb->root->subname; + if(rdidxfile(mb, doplumb) == -2) + wridxfile(mb); + if(s = mb->sync(mb, doplumb, &n)){ + semrelease(&mb->syncsem, 1); + return s; + } + d = 0; + y = 0; + for(m = mb->root->part; m; m = next){ + next = m->next; + if(m->cstate & Cidxstale) + y++; + if(m->deleted == 0 || m->refs > 0) + continue; + if(mb->delete && m->inmbox && m->deleted & Deleted) + mb->delete(mb, m); + if(!m->inmbox){ + delmessage(mb, m); + d++; + } + } + a = mb->root->subname - a; + assert(a >= 0); + if(n + d + y + a){ + iprint("deleted: %d; new %d; stale %d\n", d, n, y); + logmsg(nil, "deleted: %d; new %d; stale %d", d, n, y); + wridxfile(mb); + } + if(n + d + y + a){ + mb->vers++; + henter(PATH(0, Qtop), mb->name, + (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); + } + semrelease(&mb->syncsem, 1); + return nil; } -/* create a new mailbox */ +/* + * not entirely clear where the locking should take place, if + * it is required. + */ char* -newmbox(char *path, char *name, int std) +mboxrename(char *a, char *b, int flags) +{ + char f0[Pathlen + 4], f1[Pathlen + 4], *err, *p0, *p1; + Mailbox *mb; + + snprint(f0, sizeof f0, "%s", a); + snprint(f1, sizeof f1, "%s", b); + err = newmbox(f0, nil, 0, &mb); + dprint("mboxrename %s %s -> %s\n", f0, f1, err); + if(!err && !mb->rename) + err = "rename not supported"; + if(err) + goto done; + err = mb->rename(mb, f1, flags); + if(err) + goto done; + if(flags & Rtrunc) + /* we're comitted, so forget bailing */ + err = newmbox(f0, nil, DMcreate, 0); + p0 = f0 + strlen(f0); + p1 = f1 + strlen(f1); + + strcat(f0, ".idx"); + strcat(f1, ".idx"); + rename(f0, f1, 0); + + *p0 = *p1 = 0; + strcat(f0, ".imp"); + strcat(f1, ".imp"); + rename(f0, f1, 0); + + snprint(mb->path, sizeof mb->path, "%s", b); + hfree(PATH(0, Qtop), mb->name); + p0 = strrchr(mb->path, '/') + 1; + if(p0 == (char*)1) + p0 = mb->path; + snprint(mb->name, sizeof mb->name, "%s", p0); + henter(PATH(0, Qtop), mb->name, + (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); +done: + if(!mb) + return err; + qunlock(mb); +// if(err) +// mboxdecref(mb); + return err; +} + +static void +initheaders(void) +{ + int i; + static int already; + + if(already) + return; + already = 1; + for(i = 0; i < nelem(head); i++) + head[i].len = strlen(head[i].type); +} + +char* +newmbox(char *path, char *name, int flags, Mailbox **r) { - Mailbox *mb, **l; char *p, *rv; int i; + Mailbox *mb, **l; initheaders(); - - mb = emalloc(sizeof(*mb)); - strncpy(mb->path, path, sizeof(mb->path)-1); - if(name == nil){ + mb = emalloc(sizeof *mb); + mb->idxsem = 1; + mb->syncsem = 1; + mb->flags = flags; + strncpy(mb->path, path, sizeof mb->path - 1); + p = name; + if(p == nil){ p = strrchr(path, '/'); if(p == nil) p = path; @@ -112,401 +196,539 @@ newmbox(char *path, char *name, int std) 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) + strncpy(mb->name, p, sizeof mb->name - 1); + mb->idxread = genericidxread; + mb->idxwrite = genericidxwrite; + mb->idxinvalid = genericidxinvalid; + + /* check for a mailbox type */ + rv = Enotme; /* can't happen; shut compiler up */ + 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 + /* make sure name isn't taken */ qlock(&mbllock); - for(l = &mbl; *l != nil; l = &(*l)->next){ + 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); + mb->close(mb); free(mb); qunlock(&mbllock); return rv; } - } - // always try locking + /* always try locking */ mb->dolock = 1; - mb->refs = 1; mb->next = nil; mb->id = newid(); mb->root = newmessage(nil); - mb->std = std; + mb->mtree = avlcreate(mtreecmp); + *l = mb; qunlock(&mbllock); qlock(mb); - if(mb->ctl){ + henter(PATH(0, Qtop), mb->name, + (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); + if(mb->ctl) henter(PATH(mb->id, Qmbox), "ctl", (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb); - } rv = syncmbox(mb, 0); - qunlock(mb); + if(r) + *r = mb; + else + qunlock(mb); return rv; } -// close the named mailbox +/* close the named mailbox */ void freembox(char *name) { Mailbox **l, *mb; qlock(&mbllock); - for(l=&mbl; *l != nil; l=&(*l)->next){ + 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) +void +syncallmboxes(void) { - Header *h; - static int already; + char *err; + Mailbox *m; - if(already) - return; - already = 1; + qlock(&mbllock); + for(m = mbl; m != nil; m = m->next) + if(err = syncmbox(m, 1)) + eprint("syncmbox: %s\n", err); + qunlock(&mbllock); +} + + +char* +removembox(char *name, int flags) +{ + int found; + Mailbox **l, *mb; - for(h = head; h->type != nil; h++) - h->len = strlen(h->type); + found = 0; + qlock(&mbllock); + for(l=&mbl; *l != nil; l=&(*l)->next) + if(strcmp(name, (*l)->path) == 0){ + mb = *l; + *l = mb->next; + mb->flags |= ORCLOSE; + mb->rmflags = flags; + mboxdecref(mb); + found = 1; + break; + } + hfree(PATH(0, Qtop), name); + qunlock(&mbllock); + + if(found == 0) + return "maibox not found"; + return 0; } /* - * parse a Unix style header + * 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. */ -void -parseunix(Message *m) +static int +rxtotm(Message *m, Tm *tm) { - char *p; - String *h; + char *p, *q; + int r; + + if(cistrncmp(m->header, "received:", 9)) + return -1; + q = strchr(m->header, ';'); + if(!q) + return -1; + p = q; + while((p = strchr(p, '\n')) != nil){ + if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') + break; + p++; + } + if(!p) + return -1; + *p = '\0'; + r = strtotm(q + 1, tm); + *p = '\n'; + return r; +} - h = s_new(); - for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++) - s_putc(h, *p); - s_terminate(h); - s_restart(h); +Message* +gettopmsg(Mailbox *mb, Message *m) +{ + while(!Topmsg(mb, m)) + m = m->whole; + return m; +} - m->unixfrom = s_parse(h, s_reset(m->unixfrom)); - m->unixdate = s_append(s_reset(m->unixdate), h->ptr); +void +datesec(Mailbox *mb, Message *m) +{ + char *s; + vlong v; + Tm tm; - s_free(h); + if(m->fileid > 1000000ull<<8) + return; + if(m->unixfrom && strtotm(m->unixfrom, &tm) >= 0) + v = tm2sec(&tm); + else if(m->date822 && strtotm(m->date822, &tm) >= 0) + v = tm2sec(&tm); + else if(rxtotm(m, &tm) >= 0) + v = tm2sec(&tm); + else{ + s = rtab[m->type].s; + logmsg(gettopmsg(mb, m), "%s:%s: datasec %s %s\n", mb->path, + m->whole? m->whole->name: "?", + m->name, s); + if(Topmsg(mb, m) || strcmp(s, "message/rfc822") == 0) + abort(); + v = 0; + } + m->fileid = v<<8; } /* * parse a message */ -void -parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom) +extern void sanemsg(Message*); +extern void sanembmsg(Mailbox*, Message*); + +static Message* +haschild(Message *m, int i) { - String *hl; - Header *h; - char *p, *q; + for(m = m->part; m && i; i--) + m = m->next; + if(m) + m->mimeflag = 0; + return m; +} + +static void +parseattachments(Message *m, Mailbox *mb) +{ + char *p, *x; int i; + Message *nm, **l; - 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); + /* if there's a boundary, recurse... */ +sanemsg(m); +// dprint("parseattachments %p %ld\n", m->start, m->end - m->start); + if(m->boundary != nil){ + p = m->body; + nm = nil; + l = &m->part; + for(i = 0;;){ +sanemsg(m); + x = strstr(p, m->boundary); + /* two sequential boundaries; ignore nil message */ + if(nm && x == p){ + p = strchr(x, '\n'); + if(p == nil){ + nm->rbend = nm->bend = nm->end = x; + sanemsg(nm); + break; + } + p = p + 1; + continue; + } + /* no boundary, we're done */ + if(x == nil){ + if(nm != nil){ + nm->rbend = nm->bend = nm->end = m->bend; +sanemsg(nm); + if(nm->end == nm->start) + nm->mimeflag |= Mtrunc; + } + break; + } + /* boundary must be at the start of a line */ + if(x != m->body && x[-1] != '\n'){ + p = x + 1; + continue; + } - // 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)); + if(nm != nil) +{ + nm->rbend = nm->bend = nm->end = x; +sanemsg(nm);} + x += strlen(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; + if((nm = haschild(m, i++)) == nil){ + nm = newmessage(m); + *l = nm; + l = &nm->next; } + nm->start = ++p; + assert(nm->ballocd == 0); + nm->mheader = nm->header = nm->body = nm->rbody = nm->start; + } + for(nm = m->part; nm != nil; nm = nm->next){ + parse(mb, nm, 0, 1); + cachehash(mb, nm); /* botchy place for this */ } - s_reset(hl); + return; } - s_free(hl); - // the blank line isn't really part of the body or header + /* if we've got an rfc822 message, recurse... */ + if(strcmp(rtab[m->type].s, "message/rfc822") == 0){ + if((nm = haschild(m, 0)) == nil){ + nm = newmessage(m); + m->part = nm; + } + assert(nm->ballocd == 0); + nm->start = nm->header = nm->body = nm->rbody = m->body; + nm->end = nm->bend = nm->rbend = m->bend; + if(nm->end == nm->start) + nm->mimeflag |= Mtrunc; + parse(mb, nm, 0, 0); + cachehash(mb, nm); /* botchy place for this */ + } +} + +void +parseheaders(Mailbox *mb, Message *m, int addfrom, int justmime) +{ + char *p, *e, *o, *t, *s; + int i, i0, n; + uintptr a; + +/*sanembmsg(mb, m); /* fails with pop but i want this debugging for now */ + + /* parse mime headers */ + p = m->header; + i0 = 0; + if(justmime) + i0 = Mhead; + s = emalloc(2048); + e = s + 2048 - 1; + while((n = hdrlen(p, m->end)) != 0){ + if(n > e - s){ + s = erealloc(s, n); + e = s + n - 1; + } + rfc2047(s, e, p, n, 1); + p += n; + + for(i = i0; i < nelem(head); i++) + if(!cistrncmp(s, head[i].type, head[i].len)){ + a = head[i].offset; + if(a != ~0){ + if(o = *(char**)((char*)m + a)) + continue; + t = head[i].f(m, head + i, o, s); + *(char**)((char*)m + a) = t; + }else + head[i].f(m, head + i, 0, s); + break; + } + } + free(s); +/*sanembmsg(mb, m); /* fails with pop but i want this debugging for now */ + /* the blank line isn't really part of the body or header */ if(justmime){ m->mhend = p; m->hend = m->header; - } else { + } else{ m->hend = p; + m->mhend = m->header; } + /* + * not all attachments have mime headers themselves. + */ + if(!m->mheader) + m->mhend = 0; 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){ + if(!justmime) + datesec(mb, m); + + /* + * 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. + */ +/*sanembmsg(mb, m); /* fails with pop but i want this debugging for now */ + if(!addfrom && !m->unixfrom) m->unixheader = nil; - return; + else if(m->unixheader == nil){ + if(m->unixfrom && strcmp(m->unixfrom, "???") != 0) + p = m->unixfrom; + else if(m->from) + p = m->from; + else + p = "???"; + m->unixheader = smprint("From %s %Δ\n", p, m->fileid); } - - 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"); + m->cstate |= Cheader; +sanembmsg(mb, m); } -String* -promote(String **sp) +char* +promote(char *s) { - String *s; - - if(*sp != nil) - s = s_clone(*sp); - else - s = nil; - return s; + return s? strdup(s): nil; } void parsebody(Message *m, Mailbox *mb) { + char *s; + int l; Message *nm; - // recurse - if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){ + /* recurse */ + s = rtab[m->type].s; + l = rtab[m->type].l; + if(l >= 10 && strncmp(s, "multipart/", 10) == 0) parseattachments(m, mb); - } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ + else if(l == 14 && strcmp(s, "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); + /* promote headers */ + if(m->replyto == nil && m->from == nil && m->sender == nil){ + m->from = promote(nm->from); + m->to = promote(nm->to); + m->date822 = promote(nm->date822); + m->sender = promote(nm->sender); + m->replyto = promote(nm->replyto); + m->subject = promote(nm->subject); } - } + }else if(strncmp(rtab[m->type].s, "text/", 5) == 0) + sanemsg(m); + m->rawbsize = m->rbend - m->rbody; + m->cstate |= Cbody; } void -parse(Message *m, int justmime, Mailbox *mb, int addfrom) +parse(Mailbox *mb, Message *m, int addfrom, int justmime) { - parseheaders(m, justmime, mb, addfrom); + sanemsg(m); + assert(m->end - m->start > 0 || m->mimeflag&Mtrunc && m->end - m->start == 0); + if((m->cstate & Cheader) == 0) + parseheaders(mb, m, addfrom, justmime); parsebody(m, mb); + sanemsg(m); } -static void -parseattachments(Message *m, Mailbox *mb) +static char* +skipwhite(char *p) { - 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; - } + while(isascii(*p) && isspace(*p)) + p++; + return p; +} - /* boundary must be at the start of a line */ - if(x != m->body && *(x-1) != '\n'){ - p = x+1; - continue; - } +static char* +skiptosemi(char *p) +{ + while(*p && *p != ';') + p++; + while(*p == ';' || (isascii(*p) && isspace(*p))) + p++; + return p; +} - if(nm != nil) - nm->rbend = nm->bend = nm->end = x; - x += strlen(s_to_c(m->boundary)); +static char* +getstring(char *p, char *s, char *e, int dolower) +{ + int c; - /* is this the last part? ignore anything after it */ - if(strncmp(x, "--", 2) == 0) + p = skipwhite(p); + if(*p == '"'){ + for(p++; (c = *p) != '"'; p++){ + if(c == '\\') + c = *++p; + /* + * 821 says <x> after \ can be anything at all. + * we just don't care. + */ + if(c == 0) break; - - p = strchr(x, '\n'); - if(p == nil) + if(c < ' ') + continue; + if(dolower && c >= 'A' && c <= 'Z') + c += 0x20; + s = sputc(s, e, c); + } + if(*p == '"') + p++; + }else{ + for(; (c = *p) && !isspace(c) && c != ';'; p++){ + if(c == '\\') + c = *++p; + /* + * 821 says <x> after \ can be anything at all. + * we just don't care. + */ + if(c == 0) break; - nm = newmessage(m); - nm->start = nm->header = nm->body = nm->rbody = ++p; - nm->mheader = nm->header; - *l = nm; - l = &nm->next; + if(c < ' ') + continue; + if(dolower && c >= 'A' && c <= 'Z') + c += 0x20; + s = sputc(s, e, c); } - 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); } + *s = 0; + return p; } -/* - * pick up a header line - */ -static int -headerline(char **pp, String *hl) +static void +setfilename(Message *m, char *p) { - char *p, *x; + char buf[Pathlen]; - 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; + free(m->filename); + getstring(p, buf, buf + sizeof buf - 1, 0); + m->filename = smprint("%s", buf); + for(p = m->filename; *p; p++) + if(*p == ' ' || *p == '\t' || *p == ';') + *p = '_'; } -/* returns nil iff there are no addressees */ -static String* -addr822(char *p) +static char* +rtrim(char *p) { - String *s, *list; - int incomment, addrdone, inanticomment, quoted; - int n; - int c; + char *e; - list = s_new(); - s = s_new(); - quoted = incomment = addrdone = inanticomment = 0; - n = 0; - for(; *p; p++){ - c = *p; + if(p == 0) + return p; + e = p + strlen(p) - 1; + while(e > p && isascii(*e) && isspace(*e)) + *e-- = 0; + return p; +} - // whitespace is ignored +static char* +addr822(char *p, char **ac) +{ + int n, c, space, incomment, addrdone, inanticomment, quoted; + char s[128+1], *ps, *e, *x, *list; + + list = 0; + s[0] = 0; + ps = s; + e = s + sizeof s; + space = quoted = incomment = addrdone = inanticomment = 0; + n = 0; + for(; c = *p; p++){ + if(!inanticomment && !quoted && !space && ps != s && c == ' '){ + ps = sputc(ps, e, c); + space = 1; + continue; + } + space = 0; if(!quoted && isspace(c) || c == '\r') continue; - - // strings are always treated as atoms + /* strings are always treated as atoms */ if(!quoted && c == '"'){ - if(!addrdone && !incomment) - s_putc(s, c); - for(p++; *p; p++){ + if(!addrdone && !incomment && !ac) + ps = sputc(ps, e, c); + for(p++; c = *p; p++){ + if(ac && c == '"') + break; if(!addrdone && !incomment) - s_putc(s, *p); + ps = sputc(ps, e, c); if(!quoted && *p == '"') break; if(*p == '\\') @@ -514,13 +736,13 @@ addr822(char *p) else quoted = 0; } - if(*p == 0) + if(c == 0) break; quoted = 0; continue; } - // ignore everything in an expicit comment + /* ignore everything in an expicit comment */ if(!quoted && c == '('){ incomment = 1; continue; @@ -532,10 +754,21 @@ addr822(char *p) continue; } - // anticomments makes everything outside of them comments + /* anticomments makes everything outside of them comments */ if(!quoted && c == '<' && !inanticomment){ + if(ac){ + *ps-- = 0; + if(ps > s && *ps == ' ') + *ps = 0; + if(*ac){ + *ac = smprint("%s, %s", x=*ac, s); + free(x); + }else + *ac = smprint("%s", s); + } + inanticomment = 1; - s = s_reset(s); + ps = s; continue; } if(!quoted && c == '>' && inanticomment){ @@ -544,21 +777,23 @@ addr822(char *p) continue; } - // commas separate addresses + /* commas separate addresses */ if(!quoted && c == ',' && !inanticomment){ - s_terminate(s); + *ps = 0; addrdone = 0; - if(n++ != 0) - s_append(list, " "); - s_append(list, s_to_c(s)); - s = s_reset(s); + if(n++ != 0){ + list = smprint("%s %s", x=list, s); + free(x); + }else + list = smprint("%s", s); + ps = s; continue; } - // what's left is part of the address - s_putc(s, c); + /* what's left is part of the address */ + ps = sputc(ps, e, c); - // quoted characters are recognized only as characters + /* quoted characters are recognized only as characters */ if(c == '\\') quoted = 1; else @@ -566,19 +801,15 @@ addr822(char *p) } - if(*s_to_c(s) != 0){ - s_terminate(s); - if(n++ != 0) - s_append(list, " "); - s_append(list, s_to_c(s)); + if(ps > s){ + *ps = 0; + if(n != 0){ + list = smprint("%s %s", x=list, s); + free(x); + }else + list = smprint("%s", s); } - s_free(s); - - if(n == 0){ /* no addressees given, just the keyword */ - s_free(list); - return nil; - } - return list; + return rtrim(list); } /* @@ -586,138 +817,95 @@ addr822(char *p) * concatenating their values. */ -static void -to822(Message *m, Header *h, char *p) -{ - String *s; - - p += strlen(h->type); - s = addr822(p); - if (m->to822 == nil) - m->to822 = s; - else if (s != nil) { - s_append(m->to822, " "); - s_append(m->to822, s_to_c(s)); - s_free(s); - } -} - -static void -cc822(Message *m, Header *h, char *p) -{ - String *s; - - p += strlen(h->type); - s = addr822(p); - if (m->cc822 == nil) - m->cc822 = s; - else if (s != nil) { - s_append(m->cc822, " "); - s_append(m->cc822, s_to_c(s)); - s_free(s); - } -} - -static void -bcc822(Message *m, Header *h, char *p) -{ - String *s; - - p += strlen(h->type); - s = addr822(p); - if (m->bcc822 == nil) - m->bcc822 = s; - else if (s != nil) { - s_append(m->bcc822, " "); - s_append(m->bcc822, s_to_c(s)); - s_free(s); - } -} - -static void -from822(Message *m, Header *h, char *p) +static char* +concat822(Message*, Header *h, char *o, char *p) { - p += strlen(h->type); - s_free(m->from822); - m->from822 = addr822(p); -} + char *s, *n; -static void -sender822(Message *m, Header *h, char *p) -{ p += strlen(h->type); - s_free(m->sender822); - m->sender822 = addr822(p); + s = addr822(p, 0); + if(o){ + n = smprint("%s %s", o, s); + free(s); + }else + n = s; + return n; } -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) +static char* +from822(Message *m, Header *h, char*, char *p) { - char *e; - - e = p + strlen(p) - 1; - while(e > p && isspace(*e)) - *e-- = 0; + if(m->ffrom) + free(m->ffrom); + m->from = 0; + return addr822(p + h->len, &m->ffrom); } -static void -date822(Message *m, Header *h, char *p) +static char* +replace822(Message *, Header *h, char*, 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); + return addr822(p + h->len, 0); } -static void -subject822(Message *m, Header *h, char *p) +static char* +copy822(Message*, Header *h, char*, 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); + return rtrim(strdup(skipwhite(p + h->len))); } -static void -inreplyto822(Message *m, Header *h, char *p) +/* + * firefox, e.g. doesn't keep references unique + */ +static int +uniqarray(char **a, int n, int allocd) { - p += strlen(h->type); - p = skipwhite(p); - s_free(m->inreplyto822); - m->inreplyto822 = s_copy(p); - p = s_to_c(m->inreplyto822); - killtrailingwhite(p); + int i, j; + + for(i = 0; i < n; i++) + for(j = i + 1; j < n; j++) + if(strcmp(a[i], a[j]) == 0){ + if(allocd) + free(a[j]); + memmove(a + j, a + j + 1, sizeof *a*(n - (j + 1))); + a[--n] = 0; + } + return n; } -static void -messageid822(Message *m, Header *h, char *p) +static char* +ref822(Message *m, Header *h, char*, 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); + char **a, *s, *f[Nref + 1]; + int i, j, k, n; + + s = strdup(skipwhite(p + h->len)); + n = getfields(s, f, nelem(f), 1, "<> \n\t\r,"); + if(n > Nref) + n = Nref; + n = uniqarray(f, n, 0); + a = m->references; + for(i = 0; i < Nref; i++) + if(a[i] == 0) + break; + /* + * if there are too many references, drop from the beginning + * of the list. + */ + j = i + n - Nref; + if(j > 0){ + if(j > Nref) + j = Nref; + for(k = 0; k < j; k++) + free(a[k]); + memmove(a, a + j, sizeof a[0]*(Nref - j)); + memset(a + j, 0, Nref - j); + i -= j; + } + for(j = 0; j < n;) + a[i++] = strdup(f[j++]); + free(s); + uniqarray(a, i, 1); + return (char*)~0; } static int @@ -741,24 +929,20 @@ isattribute(char **pp, char *attr) return 1; } -static void -ctype(Message *m, Header *h, char *p) +static char* +ctype(Message *m, Header *h, char*, char *p) { - String *s; + char buf[128], *e; - p += h->len; - p = skipwhite(p); + e = buf + sizeof buf - 1; + p = getstring(skipwhite(p + h->len), buf, e, 1); + m->type = newrefs(buf); - p = getstring(p, m->type, 1); - - while(*p){ + for(; *p; p = skiptosemi(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); + p = getstring(p, buf, e, 0); + free(m->boundary); + m->boundary = smprint("--%s", buf); } else if(cistrncmp(p, "multipart", 9) == 0){ /* * the first unbounded part of a multipart message, @@ -768,44 +952,41 @@ ctype(Message *m, Header *h, char *p) if(m->filename == nil) setfilename(m, p); } else if(isattribute(&p, "charset")){ - p = getstring(p, s_reset(m->charset), 0); + p = getstring(p, buf, e, 0); + lowercase(buf); + m->charset = newrefs(buf); } - - p = skiptosemi(p); - } + return (char*)~0; } -static void -cencoding(Message *m, Header *h, char *p) +static char* +cencoding(Message *m, Header *h, char*, char *p) { - p += h->len; - p = skipwhite(p); + p = skipwhite(p + h->len); if(cistrncmp(p, "base64", 6) == 0) m->encoding = Ebase64; else if(cistrncmp(p, "quoted-printable", 16) == 0) m->encoding = Equoted; + return (char*)~0; } -static void -cdisposition(Message *m, Header *h, char *p) +static char* +cdisposition(Message *m, Header *h, char*, char *p) { - p += h->len; - p = skipwhite(p); - while(*p){ - if(cistrncmp(p, "inline", 6) == 0){ + for(p = skipwhite(p + h->len); *p; p = skiptosemi(p)) + if(cistrncmp(p, "inline", 6) == 0) m->disposition = Dinline; - } else if(cistrncmp(p, "attachment", 10) == 0){ + else if(cistrncmp(p, "attachment", 10) == 0) m->disposition = Dfile; - } else if(cistrncmp(p, "filename=", 9) == 0){ + else if(cistrncmp(p, "filename=", 9) == 0){ p += 9; setfilename(m, p); } - p = skiptosemi(p); - } - + return (char*)~0; } -ulong msgallocd, msgfreed; +ulong msgallocd; +ulong msgfreed; Message* newmessage(Message *parent) @@ -815,14 +996,16 @@ newmessage(Message *parent) msgallocd++; - m = emalloc(sizeof(*m)); - memset(m, 0, sizeof(*m)); + m = emalloc(sizeof *m); + dprint("newmessage %ld %p %p\n", msgallocd, parent, m); m->disposition = Dnone; - m->type = s_copy("text/plain"); - m->charset = s_copy("iso-8859-1"); + m->type = newrefs("text/plain"); + m->charset = newrefs("iso-8859-1"); + m->cstate = Cidxstale; + m->flags = Frecent; m->id = newid(); if(parent) - sprint(m->name, "%d", ++(parent->subname)); + snprint(m->name, sizeof m->name, "%d", ++(parent->subname)); if(parent == nil) parent = m; m->whole = parent; @@ -830,74 +1013,65 @@ newmessage(Message *parent) return m; } -// delete a message from a mailbox +/* 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 + /* 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 + /* 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]); + hfree(PATH(m->id, Qdir), "xxx"); /* sleezy speedup */ } - - /* recurse through sub-parts */ + if(Topmsg(mb, m)){ + if(m != mb->root) + mtreedelete(mb, m); + cachefree(mb, m, 1); + } + delrefs(m->type); + delrefs(m->charset); + idxfree(m); 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->unixfrom); + free(m->unixheader); + free(m->date822); + free(m->inreplyto); + free(m->boundary); + free(m->filename); free(m); } -// mark messages (identified by path) for deletion void +unnewmessage(Mailbox *mb, Message *parent, Message *m) +{ + assert(parent->subname > 0); + m->deleted = Dup; + delmessage(mb, m); + parent->subname -= 1; +} + +/* mark messages (identified by path) for deletion */ +char* delmessages(int ac, char **av) { + int i, needwrite; Mailbox *mb; Message *m; - int i, needwrite; qlock(&mbllock); for(mb = mbl; mb != nil; mb = mb->next) @@ -907,24 +1081,59 @@ delmessages(int ac, char **av) } qunlock(&mbllock); if(mb == nil) - return; + return "no such mailbox"; needwrite = 0; - for(i = 1; i < ac; i++){ + 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); + m->deleted = Deleted; + logmsg(m, "deleting"); } break; } - } if(needwrite) syncmbox(mb, 1); qunlock(mb); + return 0; +} + +char* +flagmessages(int argc, char **argv) +{ + char *err, *rerr; + int i, needwrite; + Mailbox *mb; + Message *m; + + if(argc%2) + return "bad flags"; + qlock(&mbllock); + for(mb = mbl; mb; mb = mb->next) + if(strcmp(*argv, mb->name) == 0){ + qlock(mb); + break; + } + qunlock(&mbllock); + if(mb == nil) + return "no such mailbox"; + needwrite = 0; + rerr = 0; + for(i = 1; i < argc; i += 2) + for(m = mb->root->part; m; m = m->next) + if(strcmp(m->name, argv[i]) == 0){ + if(err = modflags(mb, m, argv[i + 1])) + rerr = err; + else + needwrite = 1; + } + if(needwrite) + syncmbox(mb, 1); + qunlock(mb); + return rerr; } /* @@ -935,12 +1144,18 @@ msgincref(Message *m) { m->refs++; } + void msgdecref(Mailbox *mb, Message *m) { + assert(m->refs > 0); m->refs--; - if(m->refs == 0 && m->deleted) - syncmbox(mb, 1); + if(m->refs == 0){ + if(m->deleted) + syncmbox(mb, 1); + else + putcache(mb, m); + } } /* @@ -952,6 +1167,20 @@ mboxincref(Mailbox *mb) assert(mb->refs > 0); mb->refs++; } + +static void +mbrmidx(char *path, int flags) +{ + char buf[Pathlen]; + + snprint(buf, sizeof buf, "%s.idx", path); + vremove(buf); + if((flags & Rtrunc) == 0){ + snprint(buf, sizeof buf, "%s.imp", path); + vremove(buf); + } +} + void mboxdecref(Mailbox *mb) { @@ -959,98 +1188,41 @@ mboxdecref(Mailbox *mb) qlock(mb); mb->refs--; if(mb->refs == 0){ + syncmbox(mb, 1); delmessage(mb, mb->root); if(mb->ctl) hfree(PATH(mb->id, Qmbox), "ctl"); if(mb->close) - (*mb->close)(mb); + mb->close(mb); + if(mb->flags & ORCLOSE && mb->remove) + if(mb->remove(mb, mb->rmflags)) + rmidx(mb->path, mb->rmflags); + free(mb->mtree); + free(mb->d); free(mb); } else qunlock(mb); } +/* just space over \r. sleezy but necessary for ms email. */ int -cistrncmp(char *a, char *b, int n) +deccr(char *x, int len) { - while(n-- > 0){ - if(tolower(*a++) != tolower(*b++)) - return -1; - } - return 0; -} + char *e; -int -cistrcmp(char *a, char *b) -{ + e = x + len; for(;;){ - if(tolower(*a) != tolower(*b++)) - return -1; - if(*a++ == 0) + x = memchr(x, '\r', e - x); + if(x == nil) break; + *x = ' '; } - return 0; -} - -static char* -skipwhite(char *p) -{ - while(isspace(*p)) - p++; - return p; + return len; } -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 -// +/* + * undecode message body + */ void decode(Message *m) { @@ -1062,9 +1234,15 @@ decode(Message *m) switch(m->encoding){ case Ebase64: len = m->bend - m->body; - i = (len*3)/4+1; // room for max chars + null + i = (len*3)/4 + 1; /* room for max chars + null */ x = emalloc(i); len = dec64((uchar*)x, i, m->body, len); + if(len == -1){ + free(x); + break; + } + if(strncmp(rtab[m->type].s, "text/", 5) == 0) + len = deccr(x, len); if(m->ballocd) free(m->body); m->body = x; @@ -1073,7 +1251,7 @@ decode(Message *m) break; case Equoted: len = m->bend - m->body; - x = emalloc(len+2); // room for null and possible extra nl + x = emalloc(len + 2); /* room for null and possible extra nl */ len = decquoted(x, m->body, m->bend, 0); if(m->ballocd) free(m->body); @@ -1087,22 +1265,20 @@ decode(Message *m) m->decoded = 1; } -// convert latin1 to utf +/* convert x to utf8 */ void convert(Message *m) { int len; char *x; - // don't convert if we're not a leaf, not text, or already converted + /* 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) + m->converted = 1; + if(m->part != nil || cistrncmp(rtab[m->type].s, "text", 4) != 0) return; - - len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend); + len = xtoutf(rtab[m->charset].s, &x, m->body, m->bend); if(len > 0){ if(m->ballocd) free(m->body); @@ -1110,7 +1286,6 @@ convert(Message *m) m->bend = x + len; m->ballocd = 1; } - m->converted = 1; } static int @@ -1119,18 +1294,20 @@ hex2int(int x) if(x >= '0' && x <= '9') return x - '0'; if(x >= 'A' && x <= 'F') - return (x - 'A') + 10; + return x - 'A' + 10; if(x >= 'a' && x <= 'f') - return (x - 'a') + 10; + return x - 'a' + 10; return -1; } -// underscores are translated in 2047 headers (uscores=1) -// but not in the body (uscores=0) +/* + * underscores are translated in 2047 headers (uscores=1) + * but not in the body (uscores=0) + */ static char* decquotedline(char *out, char *in, char *e, int uscores) { - int c, c2, soft; + int c, soft; /* dump trailing white space */ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) @@ -1155,11 +1332,11 @@ decquotedline(char *out, char *in, char *e, int uscores) *out++ = c; break; case '=': - c = hex2int(*in++); - c2 = hex2int(*in++); - if (c != -1 && c2 != -1) - *out++ = c<<4 | c2; - else { + c = hex2int(*in++)<<4; + c |= hex2int(*in++); + if(c != -1) + *out++ = c; + else{ *out++ = '='; in -= 2; } @@ -1184,10 +1361,10 @@ decquoted(char *out, char *in, char *e, int uscores) in = nl + 1; } if(in < e) - p = decquotedline(p, in, e-1, uscores); + p = decquotedline(p, in, e - 1, uscores); - // make sure we end with a new line - if(*(p-1) != '\n'){ + /* make sure we end with a new line */ + if(*(p - 1) != '\n'){ *p++ = '\n'; *p = 0; } @@ -1195,7 +1372,7 @@ decquoted(char *out, char *in, char *e, int uscores) return p - out; } -static char* +char* lowercase(char *p) { char *op; @@ -1207,7 +1384,7 @@ lowercase(char *p) return op; } -// translate latin1 directly since it fits neatly in utf +/* translate latin1 directly since it fits neatly in utf */ static int latin1toutf(char **out, char *in, char *e) { @@ -1222,38 +1399,35 @@ latin1toutf(char **out, char *in, char *e) if(n == 0) return 0; - n += e-in; - *out = p = malloc(UTFmax*n+1); + n += e - in; + *out = p = malloc(n + 1); if(p == nil) return 0; for(; in < e; in++){ - r = (*in) & 0xff; + r = (uchar)*in; p += runetochar(p, &r); } *p = 0; return p - *out; } -// translate any thing using the tcs program +/* translate any thing 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; + char *av[4], *p; + int totcs[2], fromtcs[2], n, len, sofar; - // might not need to convert + /* might not need to convert */ if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0) return 0; if(cistrcmp(charset, "iso-8859-1") == 0) return latin1toutf(out, in, e); - len = e-in+1; + len = e - in + 1; sofar = 0; - *out = p = malloc(len+1); + *out = p = malloc(len + 1); if(p == nil) return 0; @@ -1279,7 +1453,7 @@ xtoutf(char *charset, char **out, char *in, char *e) close(fromtcs[1]); close(totcs[0]); dup(open("/dev/null", OWRITE), 2); exec("/bin/tcs", av); - _exits(0); + _exits(""); default: close(fromtcs[1]); close(totcs[0]); switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ @@ -1289,24 +1463,24 @@ xtoutf(char *charset, char **out, char *in, char *e) case 0: close(fromtcs[0]); while(in < e){ - n = write(totcs[1], in, e-in); + n = write(totcs[1], in, e - in); if(n <= 0) break; in += n; } close(totcs[1]); - _exits(0); + _exits(""); default: close(totcs[1]); for(;;){ - n = read(fromtcs[0], &p[sofar], len-sofar); + n = read(fromtcs[0], &p[sofar], len - sofar); if(n <= 0) break; sofar += n; p[sofar] = 0; if(sofar == len){ len += 1024; - p = realloc(p, len+1); + p = realloc(p, len + 1); if(p == nil) goto error; *out = p; @@ -1333,10 +1507,8 @@ emalloc(ulong n) void *p; p = mallocz(n, 1); - if(!p){ - fprint(2, "%s: out of memory alloc %lud\n", argv0, n); - exits("out of memory"); - } + if(!p) + sysfatal("malloc %lud: %r", n); setmalloctag(p, getcallerpc(&n)); return p; } @@ -1347,53 +1519,57 @@ 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); - exits("out of memory"); - } + if(!p) + sysfatal("realloc %lud: %r", n); setrealloctag(p, getcallerpc(&p)); return p; } +int +myplumbsend(int fd, Plumbmsg *m) +{ + char *buf; + int n; + + buf = plumbpack(m, &n); + if(buf == nil) + return -1; + n = write(fd, buf, n); + free(buf); + return n; +} + void mailplumb(Mailbox *mb, Message *m, int delete) { + char buf[256], dbuf[SHA1dlen*2 + 1], len[10], date[30], *from, *subject; + int ai, cache; Plumbmsg p; Plumbattr a[7]; - char buf[256]; - int ai; - char lenstr[10], *from, *subject, *date; static int fd = -1; - if(m->subject822 == nil) + cache = insurecache(mb, m) == 0; /* living dangerously if deleted */ + subject = m->subject; + if(subject == nil) subject = ""; - else - subject = s_to_c(m->subject822); - if(m->from822 != nil) - from = s_to_c(m->from822); + if(m->from != nil) + from = m->from; else if(m->unixfrom != nil) - from = s_to_c(m->unixfrom); + from = m->unixfrom; else from = ""; - if(m->unixdate != nil) - date = s_to_c(m->unixdate); - else - date = ""; - - sprint(lenstr, "%zd", m->end-m->start); - + sprint(len, "%lud", m->size); if(biffing && !delete) - print("[ %s / %s / %s ]\n", from, subject, lenstr); - + fprint(2, "[ %s / %s / %s ]\n", from, subject, len); if(!plumbing) - return; + goto out; if(fd < 0) fd = plumbopen("send", OWRITE); if(fd < 0) - return; + goto out; p.src = "mailfs"; p.dst = "seemail"; @@ -1409,102 +1585,123 @@ mailplumb(Mailbox *mb, Message *m, int delete) a[ai-1].next = &a[ai]; a[++ai].name = "length"; - a[ai].value = lenstr; + a[ai].value = len; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; - a[ai].value = delete?"delete":"new"; + a[ai].value = delete? "delete": "new"; a[ai-1].next = &a[ai]; + snprint(date, sizeof date, "%Δ", m->fileid); a[++ai].name = "date"; a[ai].value = date; a[ai-1].next = &a[ai]; - if(m->sdigest){ + if(m->digest){ + snprint(dbuf, sizeof dbuf, "%A", m->digest); a[++ai].name = "digest"; - a[ai].value = s_to_c(m->sdigest); + a[ai].value = dbuf; a[ai-1].next = &a[ai]; } - a[ai].next = nil; - p.attr = a; - snprint(buf, sizeof(buf), "%s/%s/%s", + snprint(buf, sizeof buf, "%s/%s/%s", mntpt, mb->name, m->name); p.ndata = strlen(buf); p.data = buf; - plumbsend(fd, &p); + myplumbsend(fd, &p); +out: + if(cache) + msgdecref(mb, m); } -// -// count the number of lines in the body (for imap4) -// -void +/* + * count the number of lines in the body (for imap4) + */ +ulong countlines(Message *m) { - int i; char *p; + ulong i; i = 0; - for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n')) + for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p + 1, '\n')) i++; - sprint(m->lines, "%d", i); + return i; } -char *LOG = "fs"; +static char *logf = "fs"; void -logmsg(char *s, Message *m) +logmsg(Message *m, char *fmt, ...) { - int pid; + char buf[256], *p, *e; + va_list args; - if(!logging) + if(!lflag) 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)); + e = buf + sizeof buf; + p = seprint(buf, e, "%s.%d: ", user, getpid()); + if(m) + p = seprint(p, e, "from %s digest %A ", + m->from, m->digest); + va_start(args, fmt); + vseprint(p, e, fmt, args); + va_end(args); + + if(Sflag) + fprint(2, "%s\n", buf); + syslog(Sflag, logf, "%s", buf); } -/* - * squeeze nulls out of the body - */ -static void -nullsqueeze(Message *m) +void +iprint(char *fmt, ...) { - char *p, *q; + char buf[256], *p, *e; + va_list args; - q = memchr(m->body, 0, m->end-m->body); - if(q == nil) + if(!iflag) return; - - for(p = m->body; q < m->end; q++){ - if(*q == 0) - continue; - *p++ = *q; - } - m->bend = m->rbend = m->end = p; + e = buf + sizeof buf; + p = seprint(buf, e, "%s.%d: ", user, getpid()); + va_start(args, fmt); + vseprint(p, e, fmt, args); + vfprint(2, fmt, args); + va_end(args); + syslog(Sflag, logf, "%s", buf); } +void +eprint(char *fmt, ...) +{ + char buf[256], buf2[256], *p, *e; + va_list args; + + e = buf + sizeof buf; + p = seprint(buf, e, "%s.%d: ", user, getpid()); + va_start(args, fmt); + vseprint(p, e, fmt, args); + e = buf2 + sizeof buf2; + p = seprint(buf2, e, "upas/fs: "); + vseprint(p, e, fmt, args); + va_end(args); + syslog(Sflag, logf, "%s", buf); + fprint(2, "%s", buf2); +} -// -// 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. -// +/* + * 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* +date822tounix(Message *, char *s) { char *p, *q; Tm tm; @@ -1515,6 +1712,5 @@ date822tounix(char *s) p = asctime(&tm); if(q = strchr(p, '\n')) *q = '\0'; - return s_copy(p); + return strdup(p); } - diff --git a/sys/src/cmd/upas/fs/mdir.c b/sys/src/cmd/upas/fs/mdir.c new file mode 100644 index 000000000..69d53e465 --- /dev/null +++ b/sys/src/cmd/upas/fs/mdir.c @@ -0,0 +1,354 @@ +#include "common.h" +#include "dat.h" + +typedef struct { + int debug; +} Mdir; + +#define mdprint(mdir, ...) if(mdir->debug) fprint(2, __VA_ARGS__) + +static int +slurp(char *f, char *b, uvlong o, long l) +{ + int fd, r; + + if((fd = open(f, OREAD)) == -1) + return -1; + + seek(fd, o, 0); + r = readn(fd, b, l) != l; + close(fd); + return r? -1: 0; +} + +static void +parseunix(Message *m) +{ + char *s, *p; + int l; + + l = m->header - m->start; + m->unixheader = smprint("%.*s", l, m->start); + s = m->start + 5; + if((p = strchr(s, ' ')) == nil) + abort(); + *p = 0; + m->unixfrom = strdup(s); + *p = ' '; +} + +static int +mdirfetch(Mailbox *mb, Message *m, uvlong o, ulong l) +{ + char buf[Pathlen], *x; + Mdir *mdir; + + mdir = mb->aux; + mdprint(mdir, "mdirfetch(%D) ...", m->fileid); + + snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid); + if(slurp(buf, m->start + o, o, l) == -1){ + logmsg(m, "fetch failed: %r"); + mdprint(mdir, "%r\n"); + return -1; + } + if(m->header == nil) + m->header = m->start; + if(m->header == m->start) + if(o + l >= 36) + if(strncmp(m->start, "From ", 5) == 0) + if(x = strchr(m->start, '\n')){ + m->header = x + 1; + if(m->unixfrom == nil) + parseunix(m); + } + m->mheader = m->mhend = m->header; + mdprint(mdir, "fetched [%llud, %llud]\n", o, o + l); + return 0; +} + +static int +setsize(Mailbox *mb, Message *m) +{ + char buf[Pathlen]; + Dir *d; + + snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid); + if((d = dirstat(buf)) == nil) + return -1; + m->size = d->length; + free(d); + return 0; +} + +/* must be [0-9]+(\..*)? */ +int +dirskip(Dir *a, uvlong *uv) +{ + char *p; + + if(a->length == 0) + return 1; + *uv = strtoul(a->name, &p, 0); + if(*uv < 1000000 || *p != '.') + return 1; + *uv = *uv<<8 | strtoul(p + 1, &p, 10); + if(*p) + return 1; + return 0; +} + +static int +vcmp(vlong a, vlong b) +{ + a -= b; + if(a > 0) + return 1; + if(a < 0) + return -1; + return 0; +} + +static int +dircmp(Dir *a, Dir *b) +{ + uvlong x, y; + + dirskip(a, &x); + dirskip(b, &y); + return vcmp(x, y); +} + +static char* +mdirread(Mdir* mdir, Mailbox* mb, int doplumb, int *new) +{ + int i, nnew, ndel, fd, n, c; + uvlong uv; + Dir *d; + Message *m, **ll; + static char err[ERRMAX]; + + mdprint(mdir, "mdirread()\n"); + if((fd = open(mb->path, OREAD)) == -1){ + errstr(err, sizeof err); + return err; + } + if((d = dirfstat(fd)) == nil){ + errstr(err, sizeof err); + close(fd); + return err; + } + *new = nnew = 0; + if(mb->d){ + if(d->qid.path == mb->d->qid.path) + if(d->qid.vers == mb->d->qid.vers){ + mdprint(mdir, "\tqids match\n"); + close(fd); + free(d); + goto finished; + } + free(mb->d); + } + logmsg(nil, "reading %s (mdir)", mb->path); + mb->d = d; + + n = dirreadall(fd, &d); + close(fd); + if(n == -1){ + errstr(err, sizeof err); + return err; + } + + qsort(d, n, sizeof *d, (int(*)(void*, void*))dircmp); + ndel = 0; + ll = &mb->root->part; + for(i = 0; *ll || i < n; ){ + if(i < n && dirskip(d + i, &uv)){ + i++; + continue; + } + c = -1; + if(i >= n) + c = 1; + else if(*ll) + c = vcmp(uv, (*ll)->fileid); + mdprint(mdir, "consider %s and %D -> %d\n", i<n? d[i].name: 0, *ll? (*ll)->fileid: 1ull, c); + if(c < 0){ + /* new message */ + mdprint(mdir, "new: %s (%D)\n", d[i].name, *ll? (*ll)->fileid: 0); + m = newmessage(mb->root); + m->fileid = uv; + if(setsize(mb, m) < 0 || m->size >= Maxmsg){ + /* message disappeared? unchain */ + mdprint(mdir, "deleted → %r (%D)\n", m->fileid); + logmsg(m, "disappeared"); + if(doplumb) + mailplumb(mb, m, 1); /* redundant */ + unnewmessage(mb, mb->root, m); + /* we're out of sync; note this by dropping cached qid */ + mb->d->qid.path = 0; + break; + } + m->inmbox = 1; + nnew++; + m->next = *ll; + *ll = m; + ll = &m->next; + logmsg(m, "new %s", d[i].name); + i++; + newcachehash(mb, m, doplumb); + putcache(mb, m); + }else if(c > 0){ + /* deleted message; */ + mdprint(mdir, "deleted: %s (%D)\n", i<n? d[i].name: 0, *ll? (*ll)->fileid: 0); + ndel++; + logmsg(*ll, "deleted (refs=%d)", *ll? (*ll)->refs: -42); + if(doplumb) + mailplumb(mb, *ll, 1); + (*ll)->inmbox = 0; + (*ll)->deleted = Disappear; + ll = &(*ll)->next; + }else{ + //logmsg(*ll, "duplicate %s", d[i].name); + i++; + ll = &(*ll)->next; + } + } + + free(d); + logmsg(nil, "mbox read: %d new %d deleted", nnew, ndel); +finished: + *new = nnew; + return nil; +} + +static void +mdirdelete(Mailbox *mb, Message *m) +{ + char mpath[Pathlen]; + Mdir* mdir; + + mdir = mb->aux; + snprint(mpath, sizeof mpath, "%s/%D", mb->path, m->fileid); + mdprint(mdir, "remove: %s\n", mpath); + /* may have been removed by other fs. just log the error. */ + if(remove(mpath) == -1) + logmsg(m, "remove: %s: %r", mpath); + m->inmbox = 0; +} + +static char* +mdirsync(Mailbox* mb, int doplumb, int *new) +{ + Mdir *mdir; + + mdir = mb->aux; + mdprint(mdir, "mdirsync()\n"); + return mdirread(mdir, mb, doplumb, new); +} + +static char* +mdirctl(Mailbox *mb, int c, char **v) +{ + Mdir *mdir; + + mdir = mb->aux; + if(c == 1 && strcmp(*v, "debug") == 0) + mdir->debug = 1; + else if(c == 1 && strcmp(*v, "nodebug") == 0) + mdir->debug = 0; + else + return "bad mdir control message"; + return nil; +} + +static void +mdirclose(Mailbox *mb) +{ + free(mb->aux); +} + +static int +qidcmp(Qid *a, Qid *b) +{ + if(a->path != b->path) + return 1; + return a->vers - b->vers; +} + +/* + * .idx strategy. we save the qid.path and .vers + * of the mdir directory and the date to the index. + * we accept the work done by the other upas/fs if + * the index is based on the same (or a newer) + * qid. in any event, we recheck the directory after + * the directory is four hours old. + */ +static int +idxr(char *s, Mailbox *mb) +{ + char *f[5]; + long t, δt, n; + Dir d; + + n = tokenize(s, f, nelem(f)); + if(n != 4 || strcmp(f[0], "mdirv1") != 0) + return -1; + t = strtoul(f[1], 0, 0); + δt = time(0) - t; + if(δt < 0 || δt > 4*3600) + return 0; + memset(&d, 0, sizeof d); + d.qid.path = strtoull(f[2], 0, 0); + d.qid.vers = strtoull(f[3], 0, 0); + if(mb->d && qidcmp(&mb->d->qid, &d.qid) >= 0) + return 0; + if(mb->d == 0) + mb->d = emalloc(sizeof d); + mb->d->qid = d.qid; + mb->d->mtime = t; + return 0; +} + +static void +idxw(Biobuf *b, Mailbox *mb) +{ + Qid q; + + memset(&q, 0, sizeof q); + if(mb->d) + q = mb->d->qid; + Bprint(b, "mdirv1 %lud %llud %lud\n", time(0), q.path, q.vers); +} + +char* +mdirmbox(Mailbox *mb, char *path) +{ + int m; + Dir *d; + Mdir *mdir; + + d = dirstat(path); + if(!d && mb->flags & DMcreate){ + createfolder(getuser(), path); + d = dirstat(path); + } + m = d && (d->mode & DMDIR); + free(d); + if(!m) + return Enotme; + snprint(mb->path, sizeof mb->path, "%s", path); + mdir = emalloc(sizeof *mdir); + mdir->debug = 0; + mb->aux = mdir; + mb->sync = mdirsync; + mb->close = mdirclose; + mb->fetch = mdirfetch; + mb->delete = mdirdelete; + mb->remove = localremove; + mb->rename = localrename; + mb->idxread = idxr; + mb->idxwrite = idxw; + mb->ctl = mdirctl; + return nil; +} diff --git a/sys/src/cmd/upas/fs/mkfile b/sys/src/cmd/upas/fs/mkfile index 4a7132b86..814787a07 100644 --- a/sys/src/cmd/upas/fs/mkfile +++ b/sys/src/cmd/upas/fs/mkfile @@ -1,14 +1,24 @@ </$objtype/mkfile +<../mkupas TARG= fs\ OFILES=\ + bos.$O\ + cache.$O\ fs.$O\ - imap4.$O\ + header.$O\ + idx.$O\ + imap.$O\ mbox.$O\ + mdir.$O\ + mtree.$O\ plan9.$O\ planb.$O\ pop3.$O\ + ref.$O\ + remove.$O\ + rename.$O\ strtotm.$O\ LIB=../common/libcommon.a$O\ @@ -16,8 +26,6 @@ LIB=../common/libcommon.a$O\ HFILES= ../common/common.h\ dat.h -BIN=/$objtype/bin/upas - UPDATE=\ mkfile\ $HFILES\ @@ -25,4 +33,10 @@ UPDATE=\ ${OFILES:%.$O=%.c}\ </sys/src/cmd/mkone -CFLAGS=$CFLAGS -I/sys/include -I../common +CFLAGS=$CFLAGS -I../common + +acid:V: + $CC -a $CFLAGS fs.c>a$O + +chkidx: mtree.$O chkidx.$O + $LD $LDFLAGS -o $target $prereq diff --git a/sys/src/cmd/upas/fs/mtree.c b/sys/src/cmd/upas/fs/mtree.c new file mode 100644 index 000000000..4f2af5655 --- /dev/null +++ b/sys/src/cmd/upas/fs/mtree.c @@ -0,0 +1,80 @@ +#include "common.h" +#include <libsec.h> +#include "dat.h" + +int +mtreecmp(Avl *va, Avl *vb) +{ + Mtree *a, *b; + + a = (Mtree*)va; + b = (Mtree*)vb; + return memcmp(a->m->digest, b->m->digest, SHA1dlen); +} + +int +mtreeisdup(Mailbox *mb, Message *m) +{ + Mtree t; + + assert(Topmsg(mb, m) && m->digest); + if(!m->digest) + return 0; + memset(&t, 0, sizeof t); + t.m = m; + if(avllookup(mb->mtree, &t)) + return 1; + return 0; +} + +Message* +mtreefind(Mailbox *mb, uchar *digest) +{ + Message m0; + Mtree t, *p; + + m0.digest = digest; + memset(&t, 0, sizeof t); + t.m = &m0; + if(p = (Mtree*)avllookup(mb->mtree, &t)) + return p->m; + return nil; +} + +void +mtreeadd(Mailbox *mb, Message *m) +{ + Avl *old; + Mtree *p; + + assert(Topmsg(mb, m) && m->digest); + p = emalloc(sizeof *p); + p->m = m; + old = avlinsert(mb->mtree, p); + assert(old == 0); +} + +void +mtreedelete(Mailbox *mb, Message *m) +{ + Mtree t, *p; + + assert(Topmsg(mb, m)); + memset(&t, 0, sizeof t); + t.m = m; + if(m->deleted & ~Deleted){ + if(m->digest == nil) + return; + p = (Mtree*)avllookup(mb->mtree, &t); + if(p == nil || p->m != m) + return; + p = (Mtree*)avldelete(mb->mtree, &t); + free(p); + return; + } + assert(m->digest); + p = (Mtree*)avldelete(mb->mtree, &t); + if(p == nil) + _assert("mtree delete fails"); + free(p); +} diff --git a/sys/src/cmd/upas/fs/plan9.c b/sys/src/cmd/upas/fs/plan9.c index 171b15617..1d65a62e0 100644 --- a/sys/src/cmd/upas/fs/plan9.c +++ b/sys/src/cmd/upas/fs/plan9.c @@ -1,151 +1,188 @@ #include "common.h" -#include <ctype.h> -#include <plumb.h> #include <libsec.h> #include "dat.h" -enum { - Buffersize = 64*1024, -}; +typedef struct { + Biobuf *in; + char *shift; +} Inbuf; -typedef struct Inbuf Inbuf; -struct Inbuf +/* + * parse a Unix style header + */ +static int +memtotm(char *p, int n, Tm *t) +{ + char buf[128]; + + if(n > sizeof buf - 1) + n = sizeof buf -1; + memcpy(buf, p, n); + buf[n] = 0; + return strtotm(buf, t); +} + +static int +chkunix0(char *s, int n) +{ + char *p; + Tm tm; + + if(n > 256) + return -1; + if((p = memchr(s, ' ', n)) == nil) + return -1; + if(memtotm(p, n - (p - s), &tm) < 0) + return -1; + if(tm2sec(&tm) < 1000000) + return -1; + return 0; +} + +static int +chkunix(char *s, int n) { - int fd; - uchar *lim; - uchar *rptr; - uchar *wptr; - uchar data[Buffersize+7]; -}; + int r; + + r = chkunix0(s, n); + if(r == -1) + eprint("plan9: warning naked from [%.*s]\n", n, s); + return r; +} + +static char* +parseunix(Message *m) +{ + char *s, *p, *q; + int l; + Tm tm; + + l = m->header - m->start; + m->unixheader = smprint("%.*s", l, m->start); + s = m->start + 5; + if((p = strchr(s, ' ')) == nil) + return s; + *p = 0; + m->unixfrom = strdup(s); + *p++ = ' '; + if(q = strchr(p, '\n')) + *q = 0; + if(strtotm(p, &tm) < 0) + return p; + if(q) + *q = '\n'; + m->fileid = (uvlong)tm2sec(&tm) << 8; + return 0; +} static void -addtomessage(Message *m, uchar *p, int n, int done) +addtomessage(Message *m, char *p, int n) { int i, len; - // add to message (+1 in malloc is for a trailing NUL) + if(n == 0) + return; + /* add to message (+1 in malloc is for a trailing NUL) */ 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; + i = m->end - m->start; + 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; + len = 2*n; m->start = emalloc(len + 1); m->end = m->start; } m->lim = m->start + len; - *m->lim = '\0'; + *m->lim = 0; } memmove(m->end, p, n); m->end += n; - *m->end = '\0'; + *m->end = 0; } -// -// read in a single message -// +/* + * read in a single message + */ static int -readmessage(Message *m, Inbuf *inb) +okmsg(Mailbox *mb, Message *m, Inbuf *b) { - 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"); - 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; - } - } + char e[ERRMAX], buf[128]; - // add to message (+ 1 in malloc is for a trailing null) - n = p - inb->rptr; - addtomessage(m, inb->rptr, n, done); - inb->rptr += n; + rerrstr(e, sizeof e); + if(strlen(e)){ + if(fd2path(Bfildes(b->in), buf, sizeof buf) < 0) + strcpy(buf, "unknown mailbox"); + eprint("plan9: error reading %s: %r\n", buf); + return -1; } - - // if it doesn't start with a 'From ', this ain't a mailbox - if(strncmp(m->start, "From ", 5) != 0) + if(m->end == m->start) return -1; - - // dump trailing newline, make sure there's a trailing null - // (helps in body searches) - if(*(m->end-1) == '\n') + if(m->end[-1] == '\n') m->end--; *m->end = 0; + m->size = m->end - m->start; + if(m->size >= Maxmsg) + return -1; m->bend = m->rbend = m->end; + if(m->digest == 0) + digestmessage(mb, m); + return 0; +} - // 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); +static char* +inbread(Inbuf *b) +{ + if(b->shift) + return b->shift; + return b->shift = Brdline(b->in, '\n'); +} - return 0; +void +inbconsume(Inbuf *b) +{ + b->shift = 0; } +/* + * bug: very long line with From at the buffer break. + */ +static int +readmessage(Mailbox *mb, Message *m, Inbuf *b) +{ + char *s, *n; + long l, state; + + werrstr(""); + state = 0; + for(;;){ + s = inbread(b); + if(s == 0) + break; + n = s + (l = Blinelen(b->in)) - 1; + if(l >= 28 + 7 && n[0] == '\n') + if(strncmp(s, "From ", 5) == 0) + if(!chkunix(s + 5, l - 5)) + if(++state == 2) + break; + if(state == 0) + return -1; + addtomessage(m, s, l); + inbconsume(b); + } + return okmsg(mb, m, b); +} -// throw out deleted messages. return number of freshly deleted messages +/* 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 + /* forget about what's no longer in the mailbox */ newdels = 0; for(m = mb->root->part; m != nil; m = next){ next = m->next; @@ -158,44 +195,51 @@ purgedeleted(Mailbox *mb) return newdels; } -// -// read in the mailbox and parse into messages. -// +static void +mergemsg(Message *m, Message *x) +{ + assert(m->start == 0); + m->mallocd = 1; + m->inmbox = 1; + m->lim = x->lim; + m->start = x->start; + m->end = x->end; + m->bend = x->bend; + m->rbend = x->rbend; + x->lim = 0; + x->start = 0; + x->end = 0; + x->bend = 0; + x->rbend = 0; +} + +/* + * read in the mailbox and parse into messages. + */ static char* -_readmbox(Mailbox *mb, int doplumb, Mlock *lk) +readmbox(Mailbox *mb, int doplumb, int *new, Mlock *lk) { - int fd, n; - String *tmp; + char *p, *x, buf[Pathlen]; + int nnew; + Biobuf *in; Dir *d; - static char err[Errlen]; + Inbuf b; Message *m, **l; - Inbuf *inb; - char *x; + static char err[ERRMAX]; l = &mb->root->part; /* * open the mailbox. If it doesn't exist, try the temporary one. */ - n = 0; retry: - fd = open(mb->path, OREAD); - if(fd < 0){ - rerrstr(err, sizeof(err)); - if(strstr(err, "locked") != nil - || strstr(err, "exclusive lock") != nil) - if(n++ < 20){ - sleep(500); /* wait for lock to go away */ - goto retry; - } - if(strstr(err, "exist") != nil){ - tmp = s_copy(mb->path); - s_append(tmp, ".tmp"); - if(sysrename(s_to_c(tmp), mb->path) == 0){ - s_free(tmp); + in = Bopen(mb->path, OREAD); + if(in == nil){ + errstr(err, sizeof(err)); + if(strstr(err, "exist") != 0){ + snprint(buf, sizeof buf, "%s.tmp", mb->path); + if(sysrename(buf, mb->path) == 0) goto retry; - } - s_free(tmp); } return err; } @@ -204,173 +248,170 @@ retry: * a new qid.path means reread the mailbox, while * a new qid.vers means read any new messages */ - d = dirfstat(fd); + d = dirfstat(Bfildes(in)); if(d == nil){ - close(fd); - errstr(err, sizeof(err)); + Bterm(in); + 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); + *new = 0; + Bterm(in); free(d); return nil; } if(d->qid.path == mb->d->qid.path){ while(*l != nil) l = &(*l)->next; - seek(fd, mb->d->length, 0); + Bseek(in, 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; + memset(&b, 0, sizeof b); + b.in = in; + b.shift = 0; - // read new messages - snprint(err, sizeof err, "reading '%s'", mb->path); - logmsg(err, nil); + /* read new messages */ + logmsg(nil, "reading %s", mb->path); + nnew = 0; 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--; + if(readmessage(mb, m, &b) < 0){ + unnewmessage(mb, mb->root, m); break; } - - // merge mailbox versions + /* 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; + if((*l)->start == nil){ + logmsg(*l, "read indexed"); + mergemsg(*l, m); + unnewmessage(mb, mb->root, m); + m = *l; + }else{ + logmsg(*l, "duplicate"); + m->inmbox = 1; /* remove it */ + unnewmessage(mb, mb->root, m); + m = nil; + l = &(*l)->next; + } break; } else { - // old mail no longer in box, mark deleted - logmsg("disappeared", *l); + /* old mail no longer in box, mark deleted */ + logmsg(*l, "disappeared"); if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; - (*l)->deleted = 1; + (*l)->deleted = Disappear; l = &(*l)->next; } } if(m == nil) continue; - - x = strchr(m->start, '\n'); - if(x == nil) - m->header = m->end; - else + m->header = m->end; + if(x = strchr(m->start, '\n')) m->header = x + 1; + if(p = parseunix(m)) + sysfatal("%s:%lld naked From in body? [%s]", mb->path, seek(Bfildes(in), 0, 1), p); m->mheader = m->mhend = m->header; - parseunix(m); - parse(m, 0, mb, 0); - logmsg("new", m); - + parse(mb, m, 0, 0); + if(m != *l && m->deleted != Dup){ + logmsg(m, "new"); + newcachehash(mb, m, doplumb); + putcache(mb, m); + nnew++; + } /* chain in */ *l = m; l = &m->next; - if(doplumb) - mailplumb(mb, m, 0); - } - logmsg("mbox read", nil); + logmsg(nil, "mbox read"); - // whatever is left has been removed from the mbox, mark deleted + /* 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)->deleted = Deleted; l = &(*l)->next; } - close(fd); - free(inb); + Bterm(in); + *new = nnew; return nil; } static void -_writembox(Mailbox *mb, Mlock *lk) +writembox(Mailbox *mb, Mlock *lk) { - Dir *d; - Message *m; - String *tmp; + char buf[Pathlen]; int mode, errs; Biobuf *b; + Dir *d; + Message *m; - tmp = s_copy(mb->path); - s_append(tmp, ".tmp"); + snprint(buf, sizeof buf, "%s.tmp", mb->path); /* * preserve old files permissions, if possible */ - d = dirstat(mb->path); - if(d != nil){ - mode = d->mode&0777; + mode = Mboxmode; + if(d = dirstat(mb->path)){ + mode = d->mode & 0777; free(d); - } else - mode = MBOXMODE; + } - sysremove(s_to_c(tmp)); - b = sysopen(s_to_c(tmp), "alc", mode); + remove(buf); + b = sysopen(buf, "alc", mode); if(b == 0){ - fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp)); + eprint("plan9: can't write temporary mailbox %s: %r\n", buf); return; } - logmsg("writing new mbox", nil); + logmsg(nil, "writing new mbox"); errs = 0; for(m = mb->root->part; m != nil; m = m->next){ if(lk != nil) syslockrefresh(lk); if(m->deleted) continue; - logmsg("writing", m); + logmsg(m, "writing"); 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); + logmsg(nil, "wrote new mbox"); if(sysclose(b) < 0) errs = 1; if(errs){ - fprint(2, "error writing temporary mail file\n"); - s_free(tmp); + eprint("plan9: error writing temporary mail file\n"); 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); + remove(mb->path); + if(sysrename(buf, mb->path) < 0) + eprint("plan9: can't rename %s to %s: %r\n", + buf, mb->path); if(mb->d != nil) free(mb->d); mb->d = dirstat(mb->path); } char* -plan9syncmbox(Mailbox *mb, int doplumb) +plan9syncmbox(Mailbox *mb, int doplumb, int *new) { - Mlock *lk; char *rv; + Mlock *lk; lk = nil; if(mb->dolock){ @@ -379,9 +420,9 @@ plan9syncmbox(Mailbox *mb, int doplumb) return "can't lock mailbox"; } - rv = _readmbox(mb, doplumb, lk); /* interpolate */ + rv = readmbox(mb, doplumb, new, lk); /* interpolate */ if(purgedeleted(mb) > 0) - _writembox(mb, lk); + writembox(mb, lk); if(lk != nil) sysunlock(lk); @@ -389,26 +430,30 @@ plan9syncmbox(Mailbox *mb, int doplumb) return rv; } -// -// look to see if we can open this mail box -// +void +plan9decache(Mailbox*, Message *m) +{ + m->lim = 0; +} + +/* + * look to see if we can open this mail box + */ char* plan9mbox(Mailbox *mb, char *path) { - static char err[Errlen]; - String *tmp; + char buf[Pathlen]; + static char err[Pathlen]; 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); + errstr(err, sizeof err); + snprint(buf, sizeof buf, "%s.tmp", path); + if(access(buf, AEXIST) < 0) return err; - } - s_free(tmp); } - mb->sync = plan9syncmbox; + mb->remove = localremove; + mb->rename = localrename; + mb->decache = plan9decache; return nil; } diff --git a/sys/src/cmd/upas/fs/planb.c b/sys/src/cmd/upas/fs/planb.c index 58dfe1622..fbe2ac251 100644 --- a/sys/src/cmd/upas/fs/planb.c +++ b/sys/src/cmd/upas/fs/planb.c @@ -15,12 +15,37 @@ #include <libsec.h> #include "dat.h" +static char* +parseunix(Message *m) +{ + char *s, *p, *q; + int l; + Tm tm; + + l = m->header - m->start; + m->unixheader = smprint("%.*s", l, m->start); + s = m->start + 5; + if((p = strchr(s, ' ')) == nil) + return s; + *p = 0; + m->unixfrom = strdup(s); + *p++ = ' '; + if(q = strchr(p, '\n')) + *q = 0; + if(strtotm(p, &tm) < 0) + return p; + if(q) + *q = '\n'; + m->fileid = (uvlong)tm2sec(&tm) << 8; + return 0; +} + static int readmessage(Message *m, char *msg) { - int fd, i, n; + int fd, n; char *buf, *name, *p; - char hdr[128], sdigest[SHA1dlen*2+1]; + char hdr[128]; Dir *d; buf = nil; @@ -29,8 +54,10 @@ readmessage(Message *m, char *msg) if(name == nil) return -1; if(m->filename != nil) - s_free(m->filename); - m->filename = s_copy(name); + free(m->filename); + m->filename = strdup(name); + if(m->filename == nil) + sysfatal("malloc: %r"); fd = open(name, OREAD); if(fd < 0) goto Fail; @@ -75,10 +102,7 @@ readmessage(Message *m, char *msg) m->end--; *m->end = 0; m->bend = m->rbend = m->end; - 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; Fail: if(fd >= 0) @@ -98,7 +122,7 @@ archive(Message *m) char *dir, *p, *nname; Dir d; - dir = strdup(s_to_c(m->filename)); + dir = strdup(m->filename); nname = nil; if(dir == nil) return; @@ -162,21 +186,20 @@ mustshow(char* name) } static int -readpbmessage(Mailbox *mb, char *msg, int doplumb) +readpbmessage(Mailbox *mb, char *msg, int doplumb, int *nnew) { Message *m, **l; - char *x; + char *x, *p; m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; if(readmessage(m, msg) < 0){ - delmessage(mb, m); - mb->root->subname--; + unnewmessage(mb, mb->root, m); return -1; } for(l = &mb->root->part; *l != nil; l = &(*l)->next) - if(strcmp(s_to_c((*l)->filename), s_to_c(m->filename)) == 0 && + if(strcmp((*l)->filename, m->filename) == 0 && *l != m){ if((*l)->deleted < 0) (*l)->deleted = 0; @@ -184,15 +207,19 @@ readpbmessage(Mailbox *mb, char *msg, int doplumb) mb->root->subname--; return -1; } - x = strchr(m->start, '\n'); - if(x == nil) - m->header = m->end; - else + m->header = m->end; + if(x = strchr(m->start, '\n')) m->header = x + 1; + if(p = parseunix(m)) + sysfatal("%s:%s naked From in body? [%s]", mb->path, (*l)->filename, p); m->mheader = m->mhend = m->header; - parseunix(m); - parse(m, 0, mb, 0); - logmsg("new", m); + parse(mb, m, 0, 0); + if(m != *l && m->deleted != Dup){ + logmsg(m, "new"); + newcachehash(mb, m, doplumb); + putcache(mb, m); + nnew[0]++; + } /* chain in */ *l = m; @@ -215,17 +242,18 @@ dcmp(Dir *a, Dir *b) return strcmp(an, bn); } -static void -readpbmbox(Mailbox *mb, int doplumb) +static char* +readpbmbox(Mailbox *mb, int doplumb, int *new) { - int fd, i, j, nd, nmd; char *month, *msg; + int fd, i, j, nd, nmd; Dir *d, *md; + static char err[ERRMAX]; fd = open(mb->path, OREAD); if(fd < 0){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); - return; + errstr(err, sizeof err); + return err; } nd = dirreadall(fd, &d); close(fd); @@ -249,7 +277,7 @@ readpbmbox(Mailbox *mb, int doplumb) for(j = 0; j < nmd; j++) if(mustshow(md[j].name)){ msg = smprint("%s/%s", month, md[j].name); - readpbmessage(mb, msg, doplumb); + readpbmessage(mb, msg, doplumb, new); free(msg); } } @@ -259,45 +287,45 @@ readpbmbox(Mailbox *mb, int doplumb) md = nil; } free(d); + return nil; } -static void -readpbvmbox(Mailbox *mb, int doplumb) +static char* +readpbvmbox(Mailbox *mb, int doplumb, int *new) { + char *data, *ln, *p, *nln, *msg; int fd, nr; long sz; - char *data, *ln, *p, *nln, *msg; Dir *d; + static char err[ERRMAX]; fd = open(mb->path, OREAD); if(fd < 0){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); - return; + errstr(err, sizeof err); + return err; } d = dirfstat(fd); if(d == nil){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); - close(fd); - return; + errstr(err, sizeof err); + return err; } sz = d->length; free(d); if(sz > 2 * 1024 * 1024){ sz = 2 * 1024 * 1024; - fprint(2, "%s: %s: bug: folder too big\n", argv0, mb->path); + fprint(2, "upas/fs: %s: bug: folder too big\n", mb->path); } data = malloc(sz+1); if(data == nil){ - close(fd); - fprint(2, "%s: no memory\n", argv0); - return; + errstr(err, sizeof err); + return err; } nr = readn(fd, data, sz); close(fd); if(nr < 0){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); + errstr(err, sizeof err); free(data); - return; + return err; } data[nr] = 0; @@ -318,22 +346,24 @@ readpbvmbox(Mailbox *mb, int doplumb) *p = 0; msg = smprint("/mail/box/%s/msgs/%s", user, ln); if(msg == nil){ - fprint(2, "%s: no memory\n", argv0); + fprint(2, "upas/fs: malloc: %r\n"); continue; } - readpbmessage(mb, msg, doplumb); + readpbmessage(mb, msg, doplumb, new); free(msg); } free(data); + return nil; } static char* -readmbox(Mailbox *mb, int doplumb, int virt) +readmbox(Mailbox *mb, int doplumb, int virt, int *new) { + char *mberr; int fd; Dir *d; Message *m; - static char err[Errlen]; + static char err[128]; if(debug) fprint(2, "read mbox %s\n", mb->path); @@ -364,45 +394,45 @@ readmbox(Mailbox *mb, int doplumb, int virt) henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); snprint(err, sizeof err, "reading '%s'", mb->path); - logmsg(err, nil); + logmsg(nil, err, nil); for(m = mb->root->part; m != nil; m = m->next) if(m->deleted == 0) m->deleted = -1; if(virt == 0) - readpbmbox(mb, doplumb); + mberr = readpbmbox(mb, doplumb, new); else - readpbvmbox(mb, doplumb); + mberr = readpbvmbox(mb, doplumb, new); /* * messages removed from the mbox; flag them to go. */ for(m = mb->root->part; m != nil; m = m->next) if(m->deleted < 0 && doplumb){ - m->inmbox = 0; - m->deleted = 1; - mailplumb(mb, m, 1); + delmessage(mb, m); + if(doplumb) + mailplumb(mb, m, 1); } - logmsg("mbox read", nil); - return nil; + logmsg(nil, "mbox read"); + return mberr; } static char* -mbsync(Mailbox *mb, int doplumb) +mbsync(Mailbox *mb, int doplumb, int *new) { char *rv; - rv = readmbox(mb, doplumb, 0); + rv = readmbox(mb, doplumb, 0, new); purgembox(mb, 0); return rv; } static char* -mbvsync(Mailbox *mb, int doplumb) +mbvsync(Mailbox *mb, int doplumb, int *new) { char *rv; - rv = readmbox(mb, doplumb, 1); + rv = readmbox(mb, doplumb, 1, new); purgembox(mb, 1); return rv; } diff --git a/sys/src/cmd/upas/fs/pop3.c b/sys/src/cmd/upas/fs/pop3.c index ac8cb3cd0..750646d9b 100644 --- a/sys/src/cmd/upas/fs/pop3.c +++ b/sys/src/cmd/upas/fs/pop3.c @@ -1,54 +1,64 @@ #include "common.h" -#include <ctype.h> -#include <plumb.h> #include <libsec.h> #include <auth.h> #include "dat.h" #pragma varargck type "M" uchar* #pragma varargck argpos pop3cmd 2 +#define pdprint(p, ...) if((p)->debug) fprint(2, __VA_ARGS__); else{} + +typedef struct Popm Popm; +struct Popm{ + int mesgno; +}; 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 - + 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; + + Biobuf bin; /* open network connection */ + Biobuf bout; + int fd; + char *lastline; /* from Brdstr */ Thumbprint *thumb; }; -char* +static int +mesgno(Message *m) +{ + Popm *a; + + a = m->aux; + return a->mesgno; +} + +static char* geterrstr(void) { - static char err[Errlen]; + 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. -// +/* + * get pop3 response line , without worrying + * about multiline responses; the clients + * will deal with that. + */ static int isokay(char *s) { @@ -62,15 +72,13 @@ pop3cmd(Pop *pop, char *fmt, ...) va_list va; va_start(va, fmt); - vseprint(buf, buf+sizeof(buf), fmt, va); + vseprint(buf, buf + sizeof buf, fmt, va); va_end(va); - p = buf+strlen(buf); - if(p > (buf+sizeof(buf)-3)) + p = buf + strlen(buf); + if(p > buf + sizeof buf - 3) sysfatal("pop3 command too long"); - - if(pop->debug) - fprint(2, "<- %s\n", buf); + pdprint(pop, "<- %s\n", buf); strcpy(p, "\r\n"); Bwrite(&pop->bout, buf, strlen(buf)); Bflush(&pop->bout); @@ -83,20 +91,19 @@ pop3resp(Pop *pop) char *p; alarm(60*1000); - s = Brdstr(&pop->bin, '\n', 0); - alarm(0); - if(s == nil){ + 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; + p = s + strlen(s) - 1; while(p >= s && (*p == '\r' || *p == '\n')) *p-- = '\0'; - if(pop->debug) - fprint(2, "-> %s\n", s); + pdprint(pop, "-> %s\n", s); free(pop->lastline); pop->lastline = s; return s; @@ -119,40 +126,35 @@ pop3pushtls(Pop *pop) int fd; uchar digest[SHA1dlen]; TLSconn conn; - char *err; - err = nil; memset(&conn, 0, sizeof conn); // conn.trace = pop3log; fd = tlsClient(pop->fd, &conn); - if(fd < 0){ - err = "tls error"; - goto out; - } - pop->fd = fd; - Binit(&pop->bin, pop->fd, OREAD); - Binit(&pop->bout, pop->fd, OWRITE); + if(fd < 0) + return "tls error"; if(conn.cert==nil || conn.certlen <= 0){ - err = "server did not provide TLS certificate"; - goto out; + 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); - fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest); - err = "bad server certificate"; - goto out; + close(fd); + free(conn.cert); + eprint("pop3: server certificate %.*H not recognized\n", SHA1dlen, digest); + return "bad server certificate"; } - pop->encrypted = 1; -out: - free(conn.sessionID); free(conn.cert); - return err; + 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 -// +/* + * get capability list, possibly start tls + */ static char* pop3capa(Pop *pop) { @@ -186,9 +188,9 @@ pop3capa(Pop *pop) return nil; } -// -// log in using APOP if possible, password if allowed by user -// +/* + * log in using APOP if possible, password if allowed by user + */ static char* pop3login(Pop *pop) { @@ -207,10 +209,10 @@ pop3login(Pop *pop) else ubuf[0] = '\0'; - // look for apop banner - if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) { + /* 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", + 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') @@ -253,83 +255,75 @@ pop3login(Pop *pop) } } -// -// dial and handshake with pop server -// +/* + * dial and handshake with pop server + */ static char* pop3dial(Pop *pop) { char *err; - if(pop->fd >= 0){ - close(pop->fd); - pop->fd = -1; - } 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) - goto Out; + return err; }else{ Binit(&pop->bin, pop->fd, OREAD); Binit(&pop->bout, pop->fd, OWRITE); } - err = pop3login(pop); -Out: - if(err != nil){ - if(pop->fd >= 0){ - close(pop->fd); - pop->fd = -1; - } + + if(err = pop3login(pop)) { + close(pop->fd); + return err; } - return err; + + return nil; } -// -// close connection -// +/* + * close connection + */ static void pop3hangup(Pop *pop) { - if(pop->fd < 0) - return; pop3cmd(pop, "QUIT"); pop3resp(pop); close(pop->fd); - pop->fd = -1; } -// -// download a single message -// +/* + * download a single message + */ static char* -pop3download(Pop *pop, Message *m) +pop3download(Mailbox *mb, Pop *pop, Message *m) { char *s, *f[3], *wp, *ep; - char sdigest[SHA1dlen*2+1]; - int i, l, sz; + int l, sz, pos, n; + Popm *a; + a = m->aux; if(!pop->pipeline) - pop3cmd(pop, "LIST %d", m->mesgno); + pop3cmd(pop, "LIST %d", a->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) + if(atoi(f[1]) != a->mesgno) return "out of sync with pop3 server"; - sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */ + 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; + m->start = wp = emalloc(sz + 1); + ep = wp + sz; if(!pop->pipeline) - pop3cmd(pop, "RETR %d", m->mesgno); + pop3cmd(pop, "RETR %d", a->mesgno); if(!isokay(s = pop3resp(pop))) { m->start = nil; free(wp); @@ -347,7 +341,7 @@ pop3download(Pop *pop, Message *m) if(strcmp(s, ".") == 0) break; - l = strlen(s)+1; + l = strlen(s) + 1; if(s[0] == '.') { s++; l--; @@ -356,14 +350,17 @@ pop3download(Pop *pop, Message *m) * 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; + if(wp + l > ep) { + pos = wp - m->start; + n = sz/10; + if(n < 200) + n = 200; + sz += n; + m->start = erealloc(m->start, sz + 1); + wp = m->start + pos; + ep = m->start + sz; } - memmove(wp, s, l-1); + memmove(wp, s, l - 1); wp[l-1] = '\n'; wp += l; } @@ -373,40 +370,41 @@ pop3download(Pop *pop, Message *m) m->end = wp; - // make sure there's a trailing null - // (helps in body searches) + /* + * 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); + m->size = m->end - m->start; + if(m->digest == nil) + digestmessage(mb, m); 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. -// +/* + * 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) +pop3read(Pop *pop, Mailbox *mb, int doplumb, int *new) { char *s, *p, *uidl, *f[2]; - int mesgno, ignore, nnew; + int mno, ignore, nnew; Message *m, *next, **l; + Popm *a; - // Some POP servers disallow UIDL if the maildrop is empty. + *new = 0; + /* 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 + /* fetch message listing; note messages to grab */ l = &mb->root->part; if(strncmp(s, "+OK 0 ", 6) != 0) { pop3cmd(pop, "UIDL"); @@ -421,25 +419,32 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb) if(tokenize(p, f, 2) != 2) continue; - mesgno = atoi(f[0]); + mno = atoi(f[0]); uidl = f[1]; - if(strlen(uidl) > 75) // RFC 1939 says 70 characters max + 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; + a = (*l)->aux; + if(strcmp((*l)->idxaux, uidl) == 0){ + if(a == 0){ + m = *l; + m->mallocd = 1; + m->inmbox = 1; + m->aux = a = emalloc(sizeof *a); + } + /* matches mail we already have, note mesgno for deletion */ + a->mesgno = mno; ignore = 1; l = &(*l)->next; break; - } else { - // old mail no longer in box mark deleted + }else{ + /* old mail no longer in box mark deleted */ if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; - (*l)->deleted = 1; + (*l)->deleted = Deleted; l = &(*l)->next; } } @@ -449,30 +454,31 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb) m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; - m->mesgno = mesgno; - strcpy(m->uidl, uidl); + m->idxaux = strdup(uidl); + m->aux = a = emalloc(sizeof *a); + a->mesgno = mno; - // chain in; will fill in message later + /* chain in; will fill in message later */ *l = m; l = &m->next; } } - // whatever is left has been removed from the mbox, mark as deleted + /* 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)->deleted = Disappear; l = &(*l)->next; } - // download new messages + /* download new messages */ nnew = 0; if(pop->pipeline){ switch(rfork(RFPROC|RFMEM)){ case -1: - fprint(2, "rfork: %r\n"); + eprint("pop3: rfork: %r\n"); pop->pipeline = 0; default: @@ -480,49 +486,41 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb) case 0: for(m = mb->root->part; m != nil; m = m->next){ - if(m->start != nil) + if(m->start != nil || m->deleted) continue; - Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno); + Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", mesgno(m), mesgno(m)); } Bflush(&pop->bout); - _exits(nil); + _exits(""); } } for(m = mb->root->part; m != nil; m = next) { next = m->next; - if(m->start != nil) + if(m->start != nil || m->deleted) continue; - - if(s = pop3download(pop, m)) { - // message disappeared? unchain - fprint(2, "download %d: %s\n", m->mesgno, s); + if(s = pop3download(mb, pop, m)) { + /* message disappeared? unchain */ + eprint("pop3: download %d: %s\n", mesgno(m), s); delmessage(mb, m); mb->root->subname--; continue; } nnew++; - parse(m, 0, mb, 1); - - if(doplumb) - mailplumb(mb, m, 0); + parse(mb, m, 1, 0); + newcachehash(mb, m, doplumb); + putcache(mb, m); } 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); - } - + *new = nnew; return nil; } -// -// delete marked messages -// +/* + * delete marked messages + */ static void pop3purge(Pop *pop, Mailbox *mb) { @@ -531,7 +529,7 @@ pop3purge(Pop *pop, Mailbox *mb) if(pop->pipeline){ switch(rfork(RFPROC|RFMEM)){ case -1: - fprint(2, "rfork: %r\n"); + eprint("pop3: rfork: %r\n"); pop->pipeline = 0; default: @@ -542,11 +540,11 @@ pop3purge(Pop *pop, Mailbox *mb) next = m->next; if(m->deleted && m->refs == 0){ if(m->inmbox) - Bprint(&pop->bout, "DELE %d\r\n", m->mesgno); + Bprint(&pop->bout, "DELE %d\r\n", mesgno(m)); } } Bflush(&pop->bout); - _exits(nil); + _exits(""); } } for(m = mb->root->part; m != nil; m = next) { @@ -554,7 +552,7 @@ pop3purge(Pop *pop, Mailbox *mb) if(m->deleted && m->refs == 0) { if(m->inmbox) { if(!pop->pipeline) - pop3cmd(pop, "DELE %d", m->mesgno); + pop3cmd(pop, "DELE %d", mesgno(m)); if(isokay(pop3resp(pop))) delmessage(mb, m); } else @@ -564,19 +562,21 @@ pop3purge(Pop *pop, Mailbox *mb) } -// connect to pop3 server, sync mailbox +/* connect to pop3 server, sync mailbox */ static char* -pop3sync(Mailbox *mb, int doplumb) +pop3sync(Mailbox *mb, int doplumb, int *new) { 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){ + + if((err = pop3read(pop, mb, doplumb, new)) == nil){ pop3purge(pop, mb); mb->d->atime = mb->d->mtime = time(0); } @@ -629,7 +629,7 @@ pop3ctl(Mailbox *mb, int argc, char **argv) return Epop3ctl; } -// free extra memory associated with mb +/* free extra memory associated with mb */ static void pop3close(Mailbox *mb) { @@ -640,9 +640,18 @@ pop3close(Mailbox *mb) free(pop); } -// -// open mailboxes of the form /pop/host/user or /apop/host/user -// +static char* +mkmbox(Pop *pop, char *p, char *e) +{ + p = seprint(p, e, "%s/box/%s/pop.%s", MAILROOT, getlog(), pop->host); + if(pop->user && strcmp(pop->user, getlog())) + p = seprint(p, e, ".%s", pop->user); + return p; +} + +/* + * open mailboxes of the form /pop/host/user or /apop/host/user + */ char* pop3mbox(Mailbox *mb, char *path) { @@ -650,7 +659,6 @@ pop3mbox(Mailbox *mb, char *path) int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls; Pop *pop; - quotefmtinstall(); popssl = strncmp(path, "/pops/", 6) == 0; apopssl = strncmp(path, "/apops/", 7) == 0; poptls = strncmp(path, "/poptls/", 8) == 0; @@ -673,8 +681,7 @@ pop3mbox(Mailbox *mb, char *path) return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]"; } - pop = emalloc(sizeof(*pop)); - pop->fd = -1; + pop = emalloc(sizeof *pop); pop->freep = path; pop->host = f[2]; if(nf < 4) @@ -688,12 +695,13 @@ pop3mbox(Mailbox *mb, char *path) pop->notls = popnotls || apopnotls; pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); + mkmbox(pop, mb->path, mb->path + sizeof mb->path); mb->aux = pop; mb->sync = pop3sync; mb->close = pop3close; mb->ctl = pop3ctl; - mb->d = emalloc(sizeof(*mb->d)); - + mb->d = emalloc(sizeof *mb->d); + mb->addfrom = 1; return nil; } diff --git a/sys/src/cmd/upas/fs/readdir.c b/sys/src/cmd/upas/fs/readdir.c deleted file mode 100644 index 42028d4c1..000000000 --- a/sys/src/cmd/upas/fs/readdir.c +++ /dev/null @@ -1,15 +0,0 @@ -#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/sys/src/cmd/upas/fs/ref.c b/sys/src/cmd/upas/fs/ref.c new file mode 100644 index 000000000..f3cf078b3 --- /dev/null +++ b/sys/src/cmd/upas/fs/ref.c @@ -0,0 +1,100 @@ +#include "common.h" +#include <libsec.h> +#include "dat.h" + +/* all the data that's fit to cache */ + +typedef struct{ + char *s; + int l; + ulong ref; +}Refs; + +Refs *rtab; +int nrtab; +int nralloc; + +int +newrefs(char *s) +{ + int l, i; + Refs *r; + + l = strlen(s); + for(i = 0; i < nrtab; i++){ + r = rtab + i; + if(r->ref == 0) + goto enter; + if(l == r->l && strcmp(r->s, s) == 0){ + r->ref++; + return i; + } + } + if(nrtab == nralloc) + rtab = erealloc(rtab, sizeof *rtab*(nralloc += 50)); + nrtab = i + 1; +enter: + r = rtab + i; + r->s = strdup(s); + r->l = l; + r->ref = 1; + return i; +} + +void +delrefs(int i) +{ + Refs *r; + + r = rtab + i; + if(--r->ref > 0) + return; + free(r->s); + memset(r, 0, sizeof *r); +} + +void +refsinit(void) +{ + newrefs(""); +} + +static char *sep = "--------\n"; + +int +prrefs(Biobuf *b) +{ + int i, n; + + n = 0; + for(i = 1; i < nrtab; i++){ + if(rtab[i].ref == 0) + continue; + Bprint(b, "%s ", rtab[i].s); + if(n++%8 == 7) + Bprint(b, "\n"); + } + if(n%8 != 7) + Bprint(b, "\n"); + Bprint(b, sep); + return 0; +} + +int +rdrefs(Biobuf *b) +{ + char *f[10], *s; + int i, n; + + while(s = Brdstr(b, '\n', 1)){ + if(strcmp(s, sep) == 0){ + free(s); + return 0; + } + n = tokenize(s, f, nelem(f)); + for(i = 0; i < n; i++) + newrefs(f[i]); + free(s); + } + return -1; +} diff --git a/sys/src/cmd/upas/fs/remove.c b/sys/src/cmd/upas/fs/remove.c new file mode 100644 index 000000000..5bafe8a70 --- /dev/null +++ b/sys/src/cmd/upas/fs/remove.c @@ -0,0 +1,141 @@ +#include "common.h" +#include "dat.h" + +#define deprint(...) /* eprint(__VA_ARGS__) */ + +extern int dirskip(Dir*, uvlong*); + +static int +ismbox(char *path) +{ + char buf[512]; + int fd, r; + + fd = open(path, OREAD); + if(fd == -1) + return 0; + r = 1; + if(read(fd, buf, sizeof buf) < 28 + 5) + r = 0; + else if(strncmp(buf, "From ", 5)) + r = 0; + close(fd); + return r; +} + +static int +isindex(Dir *d) +{ + char *p; + + p = strrchr(d->name, '.'); + if(!p) + return -1; + if(strcmp(p, ".idx") || strcmp(p, ".imp")) + return 1; + return 0; +} + +static int +idiotcheck(char *path, Dir *d, int getindex) +{ + uvlong v; + + if(d->mode & DMDIR) + return 0; + if(strncmp(d->name, "L.", 2) == 0) + return 0; + if(getindex && isindex(d)) + return 0; + if(!dirskip(d, &v) || ismbox(path)) + return 0; + return -1; +} + +int +vremove(char *buf) +{ + deprint("rm %s\n", buf); + return remove(buf); +} + +static int +rm(char *dir, int flags, int level) +{ + char buf[Pathlen]; + int i, n, r, fd, isdir, rflag; + Dir *d; + + d = dirstat(dir); + isdir = d->mode & DMDIR; + free(d); + if(!isdir) + return 0; + fd = open(dir, OREAD); + if(fd == -1) + return -1; + n = dirreadall(fd, &d); + close(fd); + r = 0; + rflag = flags & Rrecur; + for(i = 0; i < n; i++){ + snprint(buf, sizeof buf, "%s/%s", dir, d[i].name); + if(rflag) + r |= rm(buf, flags, level + 1); + if(idiotcheck(buf, d + i, level + rflag) == -1) + continue; + if(vremove(buf) != 0) + r = -1; + } + free(d); + return r; +} + +void +rmidx(char *buf, int flags) +{ + char buf2[Pathlen]; + + snprint(buf2, sizeof buf2, "%s.idx", buf); + vremove(buf2); + if((flags & Rtrunc) == 0){ + snprint(buf2, sizeof buf2, "%s.imp", buf); + vremove(buf2); + } +} + +char* +localremove(Mailbox *mb, int flags) +{ + char *msg, *path; + int r, isdir; + Dir *d; + static char err[2*Pathlen]; + + path = mb->path; + if((d = dirstat(path)) == 0){ + snprint(err, sizeof err, "%s: doesn't exist\n", path); + return 0; + } + isdir = d->mode & DMDIR; + free(d); + msg = "deleting"; + if(flags & Rtrunc) + msg = "truncating"; + deprint("%s: %s\n", msg, path); + + /* must match folder.c:/^openfolder */ + r = rm(path, flags, 0); + if((flags & Rtrunc) == 0) + r = vremove(path); + else if(!isdir) + close(r = open(path, OWRITE|OTRUNC)); + + rmidx(path, flags); + + if(r == -1){ + snprint(err, sizeof err, "%s: can't %s\n", path, msg); + return err; + } + return 0; +} diff --git a/sys/src/cmd/upas/fs/rename.c b/sys/src/cmd/upas/fs/rename.c new file mode 100644 index 000000000..6d5337643 --- /dev/null +++ b/sys/src/cmd/upas/fs/rename.c @@ -0,0 +1,234 @@ +#include "common.h" +#include "dat.h" + +#define deprint(...) /* eprint(__VA_ARGS__) */ + +static int +delivery(char *s) +{ + if(strncmp(s, "/mail/fs/", 9) == 0) + if((s = strrchr(s, '/')) && strcmp(s + 1, "mbox") == 0) + return 1; + return 0; +} + +static int +isdir(char *s) +{ + int isdir; + Dir *d; + + d = dirstat(s); + isdir = d && d->mode & DMDIR; + free(d); + return isdir; +} + +static int +docreate(char *file, int perm) +{ + int fd; + Dir ndir; + Dir *d; + + fd = create(file, OREAD, perm); + if(fd < 0) + return -1; + d = dirfstat(fd); + if(d == nil) + return -1; + nulldir(&ndir); + ndir.mode = perm; + ndir.gid = d->uid; + dirfwstat(fd, &ndir); + close(fd); + return 0; +} + +static int +rollup(char *s) +{ + char *p; + int mode; + + if(access(s, 0) == 0) + return -1; + + /* + * if we can deliver to this mbox, it needs + * to be read/execable all the way down + */ + mode = 0711; + if(delivery(s)) + mode = 0755; + + for(p = s; p; p++) { + if(*p == '/') + continue; + p = strchr(p, '/'); + if(p == 0) + break; + *p = 0; + if(access(s, 0) != 0) + if(docreate(s, DMDIR|mode) < 0) + return -1; + *p = '/'; + } + return 0; +} + +static int +copyfile(char *a, char *b, int flags) +{ + char *s; + int fd, fd1, mode, i, m, n, r; + Dir *d; + + mode = 0600; + if(delivery(b)) + mode = 0622; + fd = open(a, OREAD); + fd1 = create(b, OWRITE|OEXCL, DMEXCL|mode); + if(fd == -1 || fd1 == -1){ + close(fd); + close(fd1); + return -1; + } + s = malloc(64*1024); + i = m = 0; + while((n = read(fd, s, sizeof s)) > 0) + for(i = 0; i != n; i += m) + if((m = write(fd1, s + i, n - i)) == -1) + goto lose; +lose: + free(s); + close(fd); + close(fd1); + if(i != m || n != 0) + return -1; + + if((flags & Rtrunc) == 0) + return vremove(a); + + fd = open(a, ORDWR); + if(fd == -1) + return -1; + r = -1; + if(d = dirfstat(fd)){ + d->length = 0; + r = dirfwstat(fd, d); + free(d); + } + return r; +} + +static int +copydir(char *a, char *b, int flags) +{ + char *p, buf[Pathlen], ns[Pathlen], owd[Pathlen]; + int fd, fd1, len, i, n, r; + Dir *d; + + fd = open(a, OREAD); + fd1 = create(b, OWRITE|OEXCL, DMEXCL|0777); + close(fd1); + if(fd == -1 || fd1 == -1){ + close(fd); + return -1; + } + + /* fixup mode */ + if(delivery(b)) + if(d = dirfstat(fd)){ + d->mode |= 0777; + dirfwstat(fd, d); + free(d); + } + + getwd(owd, sizeof owd); + if(chdir(a) == -1) + return -1; + + p = seprint(buf, buf + sizeof buf, "%s/", b); + len = buf + sizeof buf - p; + n = dirreadall(fd, &d); + r = 0; + for(i = 0; i < n; i++){ + snprint(p, len, "%s", d[i].name); + if(d->mode & DMDIR){ + snprint(ns, sizeof ns, "%s/%s", a, d[i].name); + r |= copydir(ns, buf, 0); + chdir(a); + }else + r |= copyfile(d[i].name, buf, 0); + if(r) + break; + } + free(d); + + if((flags & Rtrunc) == 0) + r |= vremove(a); + + chdir(owd); + return r; +} + +int +rename(char *a, char *b, int flags) +{ + char *e0, *e1; + int fd, r; + Dir *d; + + e0 = strrchr(a, '/'); + e1 = strrchr(b, '/'); + if(!e0 || !e1 || !e1[1]) + return -1; + + if(e0 - a == e1 - b) + if(strncmp(a, b, e0 - a) == 0) + if(!delivery(a) || isdir(a)){ + fd = open(a, OREAD); + if(!(d = dirfstat(fd))){ + close(fd); + return -1; + } + d->name = e1 + 1; + r = dirfwstat(fd, d); + deprint("rename %s %s -> %d\n", a, b, r); + if(r != -1 && flags & Rtrunc) + close(create(a, OWRITE, d->mode)); + free(d); + close(fd); + return r; + } + + if(rollup(b) == -1) + return -1; + if(isdir(a)) + return copydir(a, b, flags); + return copyfile(a, b, flags); +} + +char* +localrename(Mailbox *mb, char *p2, int flags) +{ + char *path, *msg; + int r; + static char err[2*Pathlen]; + + path = mb->path; + msg = "rename"; + if(flags & Rtrunc) + msg = "move"; + deprint("localrename %s: %s %s\n", msg, path, p2); + + r = rename(path, p2, flags); + if(r == -1){ + snprint(err, sizeof err, "%s: can't %s\n", path, msg); + deprint("localrename %s\n", err); + return err; + } + close(r); + return 0; +} diff --git a/sys/src/cmd/upas/fs/rfc2047-test b/sys/src/cmd/upas/fs/rfc2047-test deleted file mode 100644 index b536c4b3e..000000000 --- a/sys/src/cmd/upas/fs/rfc2047-test +++ /dev/null @@ -1,28 +0,0 @@ -From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006 -From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu> -To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk> -CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be> -Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= - =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= - -From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006 -From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se> -To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se -Subject: Time for ISO 10646? - -From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006 -To: Dave Crocker <dcrocker@mordor.stanford.edu> -Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se -From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se> -Subject: Re: RFC-HDR care and feeding - -From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006 -From: Nathaniel Borenstein <nsb@thumper.bellcore.com> - (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) -To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed - <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu> -Subject: Test of new header generator -MIME-Version: 1.0 -Content-type: text/plain; charset=ISO-8859-1 - - diff --git a/sys/src/cmd/upas/fs/seg.c b/sys/src/cmd/upas/fs/seg.c new file mode 100644 index 000000000..394b66ad3 --- /dev/null +++ b/sys/src/cmd/upas/fs/seg.c @@ -0,0 +1,164 @@ +#include "common.h" +#include <libsec.h> +#include "dat.h" + +/* + * unnatural acts with virtual memory + */ + +typedef struct{ + int ref; + char *va; + long sz; +}S; + +static S s[15]; /* 386 only gives 4 */ +static int nstab = nelem(s); +static long ssem = 1; +//static ulong thresh = 10*1024*1024; +static ulong thresh = 1024; + +void* +segmalloc(ulong sz) +{ + int i, j; + void *va; + + if(sz < thresh) + return emalloc(sz); + semacquire(&ssem, 1); + for(i = 0; i < nstab; i++) + if(s[i].ref == 0) + goto found; +notfound: + /* errstr not informative; assume we hit seg limit */ + for(j = nstab - 1; j >= i; j--) + if(s[j].ref) + break; + nstab = j; + semrelease(&ssem, 1); + return emalloc(sz); +found: + /* + * the system doesn't leave any room for expansion + */ + va = segattach(SG_CEXEC, "memory", 0, sz + sz/10 + 4096); + if(va == 0) + goto notfound; + s[i].ref++; + s[i].va = va; + s[i].sz = sz; + semrelease(&ssem, 1); + memset(va, 0, sz); + return va; +} + +void +segmfree(void *va) +{ + char *a; + int i; + + a = va; + for(i = 0; i < nstab; i++) + if(s[i].va == a) + goto found; + free(va); + return; +found: + semacquire(&ssem, 1); + s[i].ref--; + s[i].va = 0; + s[i].sz = 0; + semrelease(&ssem, 1); +} + +void* +segreallocfixup(int i, ulong sz) +{ + char buf[ERRMAX]; + void *va, *ova; + + rerrstr(buf, sizeof buf); + if(strstr(buf, "segments overlap") == 0) + sysfatal("segibrk: %r"); + va = segattach(SG_CEXEC, "memory", 0, sz); + if(va == 0) + sysfatal("segattach: %r"); + ova = s[i].va; +fprint(2, "fix memcpy(%p, %p, %lud)\n", va, ova, s[i].sz); + memcpy(va, ova, s[i].sz); + s[i].va = va; + s[i].sz = sz; + segdetach(ova); + return va; +} + +void* +segrealloc(void *va, ulong sz) +{ + char *a; + int i; + ulong sz0; + +fprint(2, "segrealloc %p %lud\n", va, sz); + if(va == 0) + return segmalloc(sz); + a = va; + for(i = 0; i < nstab; i++) + if(s[i].va == a) + goto found; + if(sz >= thresh) + if(a = segmalloc(sz)){ + sz0 = msize(va); + memcpy(a, va, sz0); +fprint(2, "memset(%p, 0, %lud)\n", a + sz0, sz - sz0); + memset(a + sz0, 0, sz - sz0); + return a; + } + return realloc(va, sz); +found: + sz0 = s[i].sz; +fprint(2, "segbrk(%p, %p)\n", s[i].va, s[i].va + sz); + va = segbrk(s[i].va, s[i].va + sz); + if(va == (void*)-1 || va < end) + return segreallocfixup(i, sz); + a = va; + if(sz > sz0) +{ +fprint(2, "memset(%p, 0, %lud)\n", a + sz0, sz - sz0); + memset(a + sz0, 0, sz - sz0); +} + s[i].va = va; + s[i].sz = sz; + return va; +} + +void* +emalloc(ulong n) +{ + void *p; +fprint(2, "emalloc %lud\n", n); + p = mallocz(n, 1); + if(!p) + sysfatal("malloc %lud: %r", n); + setmalloctag(p, getcallerpc(&n)); + return p; +} + +void +main(void) +{ + char *p; + int i; + ulong sz; + + p = 0; + for(i = 0; i < 6; i++){ + sz = i*512; + p = segrealloc(p, sz); + memset(p, 0, sz); + } + segmfree(p); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/strtotm.c b/sys/src/cmd/upas/fs/strtotm.c index bcf0bcee0..6c9f7d3b6 100644 --- a/sys/src/cmd/upas/fs/strtotm.c +++ b/sys/src/cmd/upas/fs/strtotm.c @@ -1,11 +1,10 @@ #include <u.h> #include <libc.h> -#include <ctype.h> static char* skiptext(char *q) { - while(*q!='\0' && *q!=' ' && *q!='\t' && *q!='\r' && *q!='\n') + while(*q != '\0' && *q != ' ' && *q != '\t' && *q != '\r' && *q != '\n') q++; return q; } @@ -13,36 +12,19 @@ skiptext(char *q) static char* skipwhite(char *q) { - while(*q==' ' || *q=='\t' || *q=='\r' || *q=='\n') + while(*q == ' ' || *q == '\t' || *q == '\r' || *q == '\n') q++; return q; } static char* months[] = { "jan", "feb", "mar", "apr", - "may", "jun", "jul", "aug", + "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) +strtotm(char *p, Tm *t) { char *q, *r; int j; @@ -56,58 +38,60 @@ strtotm(char *p, Tm *tmp) tm.min = -1; tm.year = -1; tm.mday = -1; - for(p=skipwhite(p); *p; p=skipwhite(q)){ + for(p = skipwhite(p); *p; p = skipwhite(q)){ q = skiptext(p); /* look for time in hh:mm[:ss] */ - if(r = memchr(p, ':', q-p)){ + 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); + 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){ + for(j = 0; j < 12; j++) + if(cistrncmp(p, months[j], 3) == 0){ tm.mon = j; break; } - - if(j!=12) + 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); + if(q - p == 3) + if(p[0] >= 'A' && p[0] <= 'Z') + if(p[1] >= 'A' && p[1] <= 'Z') + if(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] == '+'||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){ + if(strspn(p, "0123456789") == q - p){ j = strtol(p, nil, 10); - if(1 <= j && j <= 31) + if(j >= 1 && j <= 31) tm.mday = j; if(j >= 1900) - tm.year = j-1900; + tm.year = j - 1900; + continue; } + //eprint("strtotm: garbage %.*s\n", q - p, p); } - - if(tm.mon<0 || tm.year<0 - || tm.hour<0 || tm.min<0 - || tm.mday<0) + if(tm.mon < 0 || tm.year < 0 + || tm.hour < 0 || tm.min < 0 + || tm.mday < 0) return -1; - *tmp = *localtime(tm2sec(&tm)-delta); + *t = *localtime(tm2sec(&tm) - delta); return 0; } diff --git a/sys/src/cmd/upas/fs/tester.c b/sys/src/cmd/upas/fs/tester.c deleted file mode 100644 index 3d24012ef..000000000 --- a/sys/src/cmd/upas/fs/tester.c +++ /dev/null @@ -1,81 +0,0 @@ -#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); -} |