ref: dff78ef64a0f064e74108394b76a92889f8a2d54
parent: 4bcd247b909898766d549c3990c230cbeb3d17a3
parent: 963cfc9a6f6e721f52aa949e6d1af0c3e8dc2ecc
author: cinap_lenrek <[email protected]>
date: Sun Mar 12 13:20:13 EDT 2017
merge
--- a/acme/mail/guide
+++ /dev/null
@@ -1,4 +1,0 @@
-Mail stored
-plumb /mail/box/$user/names
-mail -'x' someaddress
-mkbox /mail/box/$user/new_box
--- a/acme/mail/mkbox
+++ /dev/null
@@ -1,11 +1,0 @@
-#!/bin/rc
-
-for(i){
- if(! test -e $i){
- if(cp /dev/null $i){
- chmod 600 $i
- chmod +al $i
- }
- }
- if not echo $i already exists
-}
--- a/acme/mail/readme
+++ /dev/null
@@ -1,57 +1,0 @@
-The Acme Mail program uses upas/fs to parse the mail box, and then
-presents a file-browser-like user interface to reading and sending
-messages. The Mail window presents each numbered message like the
-contents of a directory presented one per line. If a message has a
-Subject: line, that is shown indented on the following line.
-Multipart MIME-encoded messages are presented in the obvious
-hierarchical format.
-
-Mail uses upas/fs to access the mail box. By default it reads "mbox",
-the standard user mail box. If Mail is given an argument, it is
-passed to upas/fs as the name of the mail box (or upas/fs directory)
-to open.
-
-Although Mail works if the plumber is not running, it's designed to be
-run with plumbing enabled and many of its features work best if it is.
-
-The mailbox window has a few commands: Put writes back the mailbox;
-Mail creates a new window in which to compose a message; and Delmesg
-deletes messages by number. The number may be given as argument or
-indicated by selecting the header line in the mailbox window.
-(Delmesg does not expand null selections, in the interest of safety.)
-
-Clicking the right button on a message number opens it; clicking on
-any of the subparts of a message opens that (and also opens the
-message itself). Each message window has a few commands in the tag
-with obvious names: Reply, Delmsg, etc. "Reply" replies to the single
-sender of the message, "Reply all" or "Replyall" replies to everyone
-in the From:, To:, and CC: lines.
-
-Message parts with recognized MIME types such as image/jpeg are sent
-to the plumber for further dispatch. Acme Mail also listens to
-messages on the seemail and showmail plumbing ports, to report the
-arrival of new messages (highlighting the entry; right-click on the
-entry to open the message) and open them if you right-click on the
-face in the faces window.
-
-When composing a mail message or replying to a message, the first line
-of the text is a list of recipients of the message. To:, and CC:, and BCC:
-lines are interpreted in the usual way. Two other header lines are
-special to Acme Mail:
- Include: file places a copy of file in the message as an
- inline MIME attachment.
- Attach: file places a copy of file in the message as a regular
- MIME attachment.
-
-Acme Mail uses these conventions when replying to messages,
-constructing headers for the default behavior. You may edit these to
-change behavior. Most important, when replying to a message Mail will
-always Include: the original message; delete that line if you don't
-want to include it.
-
-If the mailbox
- /mail/box/$user/outgoing
-exists, Acme Mail will save your a copy of your outgoing messages
-there. Attachments are described in the copy but not included.
-
-The -m mntpoint flag specifies a different mount point for /upas/fs.
--- a/acme/mail/src/dat.h
+++ /dev/null
@@ -1,164 +1,0 @@
-typedef struct Event Event;
-typedef struct Exec Exec;
-typedef struct Message Message;
-typedef struct Window Window;
-
-enum
-{
- STACK = 8192,
- EVENTSIZE = 256,
- NEVENT = 5,
-};
-
-struct Event
-{
- int c1;
- int c2;
- int q0;
- int q1;
- int flag;
- int nb;
- int nr;
- char b[EVENTSIZE*UTFmax+1];
- Rune r[EVENTSIZE+1];
-};
-
-struct Window
-{
- /* file descriptors */
- int ctl;
- int event;
- int addr;
- int data;
- Biobuf *body;
-
- /* event input */
- char buf[512];
- char *bufp;
- int nbuf;
- Event e[NEVENT];
-
- int id;
- int open;
- Channel *cevent;
-};
-
-struct Message
-{
- Window *w;
- int ctlfd;
- char *name;
- char *replyname;
- uchar opened;
- uchar dirty;
- uchar isreply;
- uchar deleted;
- uchar writebackdel;
- uchar tagposted;
- uchar recursed;
- uchar level;
-
- /* header info */
- char *fromcolon; /* from header file; all rest are from info file */
- char *from;
- char *to;
- char *cc;
- char *replyto;
- char *date;
- char *subject;
- char *type;
- char *disposition;
- char *filename;
- char *digest;
-
- Message *next; /* next in this mailbox */
- Message *prev; /* prev in this mailbox */
- Message *head; /* first subpart */
- Message *tail; /* last subpart */
-};
-
-enum
-{
- NARGS = 100,
- NARGCHAR = 8*1024,
- EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
-};
-
-struct Exec
-{
- char *prog;
- char **argv;
- int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
- int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
- Channel *sync;
-};
-
-extern Window* newwindow(void);
-extern int winopenfile(Window*, char*);
-extern void winopenbody(Window*, int);
-extern void winclosebody(Window*);
-extern void wintagwrite(Window*, char*, int);
-extern void winname(Window*, char*);
-extern void winwriteevent(Window*, Event*);
-extern void winread(Window*, uint, uint, char*);
-extern int windel(Window*, int);
-extern void wingetevent(Window*, Event*);
-extern void wineventproc(void*);
-extern void winwritebody(Window*, char*, int);
-extern void winclean(Window*);
-extern int winselect(Window*, char*, int);
-extern char* winselection(Window*);
-extern int winsetaddr(Window*, char*, int);
-extern char* winreadbody(Window*, int*);
-extern void windormant(Window*);
-extern void winsetdump(Window*, char*, char*);
-
-extern void readmbox(Message*, char*, char*);
-extern void rewritembox(Window*, Message*);
-
-extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
-extern void delreply(Message*);
-
-extern int mesgadd(Message*, char*, Dir*, char*);
-extern void mesgmenu(Window*, Message*);
-extern void mesgmenunew(Window*, Message*);
-extern int mesgopen(Message*, char*, char*, Message*, int, char*);
-extern void mesgctl(void*);
-extern void mesgsend(Message*);
-extern void mesgdel(Message*, Message*);
-extern void mesgmenudel(Window*, Message*, Message*);
-extern void mesgmenumark(Window*, char*, char*);
-extern void mesgmenumarkdel(Window*, Message*, Message*, int);
-extern Message* mesglookup(Message*, char*, char*);
-extern Message* mesglookupfile(Message*, char*, char*);
-extern void mesgfreeparts(Message*);
-
-extern char* readfile(char*, char*, int*);
-extern char* readbody(char*, char*, int*);
-extern void ctlprint(int, char*, ...);
-extern void* emalloc(uint);
-extern void* erealloc(void*, uint);
-extern char* estrdup(char*);
-extern char* estrstrdup(char*, char*);
-extern char* egrow(char*, char*, char*);
-extern char* eappend(char*, char*, char*);
-extern void error(char*, ...);
-extern int tokenizec(char*, char**, int, char*);
-extern void execproc(void*);
-
-#pragma varargck argpos error 1
-#pragma varargck argpos ctlprint 2
-
-extern Window *wbox;
-extern Message mbox;
-extern Message replies;
-extern char *fsname;
-extern int plumbsendfd;
-extern int plumbseemailfd;
-extern char *home;
-extern char *outgoing;
-extern char *mailboxdir;
-extern char *user;
-extern char deleted[];
-extern int wctlfd;
-extern int shortmenu;
--- a/acme/mail/src/html.c
+++ /dev/null
@@ -1,75 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include <ctype.h>
-#include <plumb.h>
-#include "dat.h"
-
-
-char*
-formathtml(char *body, int *np)
-{
- int i, j, p[2], q[2];
- Exec *e;
- char buf[1024];
- Channel *sync;
-
- e = emalloc(sizeof(struct Exec));
- if(pipe(p) < 0 || pipe(q) < 0)
- error("can't create pipe: %r");
-
- e->p[0] = p[0];
- e->p[1] = p[1];
- e->q[0] = q[0];
- e->q[1] = q[1];
- e->argv = emalloc(3*sizeof(char*));
- e->argv[0] = estrdup("htmlfmt");
- e->argv[1] = estrdup("-cutf-8");
- e->argv[2] = nil;
- e->prog = "/bin/htmlfmt";
- sync = chancreate(sizeof(int), 0);
- e->sync = sync;
- proccreate(execproc, e, EXECSTACK);
- recvul(sync);
- close(p[0]);
- close(q[1]);
-
- if((i=write(p[1], body, *np)) != *np){
- fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
- close(p[1]);
- close(q[0]);
- return body;
- }
- close(p[1]);
-
- free(body);
- body = nil;
- i = 0;
- for(;;){
- j = read(q[0], buf, sizeof buf);
- if(j <= 0)
- break;
- body = realloc(body, i+j+1);
- if(body == nil)
- error("realloc failed: %r");
- memmove(body+i, buf, j);
- i += j;
- body[i] = '\0';
- }
- close(q[0]);
-
- *np = i;
- return body;
-}
-
-char*
-readbody(char *type, char *dir, int *np)
-{
- char *body;
-
- body = readfile(dir, "body", np);
- if(body != nil && strcmp(type, "text/html") == 0)
- return formathtml(body, np);
- return body;
-}
--- a/acme/mail/src/mail.c
+++ /dev/null
@@ -1,550 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include <plumb.h>
-#include <ctype.h>
-#include "dat.h"
-
-char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
-char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
-char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
-char *mailboxdir = nil; /* nil == /mail/box/$user */
-char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
-char *user;
-char *outgoing;
-
-Window *wbox;
-Message mbox;
-Message replies;
-char *home;
-int plumbsendfd;
-int plumbseemailfd;
-int plumbshowmailfd;
-int plumbsendmailfd;
-Channel *cplumb;
-Channel *cplumbshow;
-Channel *cplumbsend;
-int wctlfd;
-void mainctl(void*);
-void plumbproc(void*);
-void plumbshowproc(void*);
-void plumbsendproc(void*);
-void plumbthread(void);
-void plumbshowthread(void*);
-void plumbsendthread(void*);
-
-int shortmenu;
-
-void
-usage(void)
-{
- fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
- threadexitsall("usage");
-}
-
-void
-removeupasfs(void)
-{
- char buf[256];
-
- if(strcmp(mboxname, "mbox") == 0)
- return;
- snprint(buf, sizeof buf, "close %s", mboxname);
- write(mbox.ctlfd, buf, strlen(buf));
-}
-
-int
-ismaildir(char *s)
-{
- char buf[256];
- Dir *d;
- int ret;
-
- snprint(buf, sizeof buf, "%s%s", maildir, s);
- d = dirstat(buf);
- if(d == nil)
- return 0;
- ret = d->qid.type & QTDIR;
- free(d);
- return ret;
-}
-
-void
-threadmain(int argc, char *argv[])
-{
- char *s, *name;
- char err[ERRMAX], *cmd;
- int i, newdir;
- Fmt fmt;
-
- doquote = needsrcquote;
- quotefmtinstall();
-
- /* open these early so we won't miss notification of new mail messages while we read mbox */
- plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
- plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
- plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
-
- shortmenu = 0;
- ARGBEGIN{
- case 's':
- shortmenu = 1;
- break;
- case 'S':
- shortmenu = 2;
- break;
- case 'o':
- outgoing = EARGF(usage());
- break;
- case 'm':
- smprint(maildir, "%s/", EARGF(usage()));
- break;
- default:
- usage();
- }ARGEND
-
- name = "mbox";
-
- /* bind the terminal /mail/fs directory over the local one */
- if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
- bind(mailtermdir, maildir, MAFTER);
-
- newdir = 1;
- if(argc > 0){
- i = strlen(argv[0]);
- if(argc>2 || i==0)
- usage();
- /* see if the name is that of an existing /mail/fs directory */
- if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
- name = argv[0];
- mboxname = eappend(estrdup(maildir), "", name);
- newdir = 0;
- }else{
- if(argv[0][i-1] == '/')
- argv[0][i-1] = '\0';
- s = strrchr(argv[0], '/');
- if(s == nil)
- mboxname = estrdup(argv[0]);
- else{
- *s++ = '\0';
- if(*s == '\0')
- usage();
- mailboxdir = argv[0];
- mboxname = estrdup(s);
- }
- if(argc > 1)
- name = argv[1];
- else
- name = mboxname;
- }
- }
-
- user = getenv("user");
- if(user == nil)
- user = "none";
- if(mailboxdir == nil)
- mailboxdir = estrstrdup("/mail/box/", user);
- if(outgoing == nil)
- outgoing = estrstrdup(mailboxdir, "/outgoing");
-
- s = estrstrdup(maildir, "ctl");
- mbox.ctlfd = open(s, ORDWR|OCEXEC);
- if(mbox.ctlfd < 0)
- error("can't open %s: %r", s);
-
- fsname = estrdup(name);
- if(newdir && argc > 0){
- s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
- for(i=0; i<10; i++){
- sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
- if(write(mbox.ctlfd, s, strlen(s)) >= 0)
- break;
- err[0] = '\0';
- errstr(err, sizeof err);
- if(strstr(err, "mbox name in use") == nil)
- error("can't create directory %s for mail: %s", name, err);
- free(fsname);
- fsname = emalloc(strlen(name)+10);
- sprint(fsname, "%s-%d", name, i);
- }
- if(i == 10)
- error("can't open %s/%s: %r", mailboxdir, mboxname);
- free(s);
- }
-
- s = estrstrdup(fsname, "/");
- mbox.name = estrstrdup(maildir, s);
- mbox.level= 0;
- readmbox(&mbox, maildir, s);
- home = getenv("home");
- if(home == nil)
- home = "/";
-
- wbox = newwindow();
- winname(wbox, mbox.name);
- wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1);
- threadcreate(mainctl, wbox, STACK);
-
- fmtstrinit(&fmt);
- fmtprint(&fmt, "Mail");
- if(shortmenu)
- fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
- if(outgoing)
- fmtprint(&fmt, " -o %s", outgoing);
- fmtprint(&fmt, " %s", name);
- cmd = fmtstrflush(&fmt);
- if(cmd == nil)
- sysfatal("out of memory");
- winsetdump(wbox, "/acme/mail", cmd);
- mbox.w = wbox;
-
- mesgmenu(wbox, &mbox);
- winclean(wbox);
-
- wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
- cplumb = chancreate(sizeof(Plumbmsg*), 0);
- cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
- if(strcmp(name, "mbox") == 0){
- /*
- * Avoid creating multiple windows to send mail by only accepting
- * sendmail plumb messages if we're reading the main mailbox.
- */
- plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
- cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
- proccreate(plumbsendproc, nil, STACK);
- threadcreate(plumbsendthread, nil, STACK);
- }
- /* start plumb reader as separate proc ... */
- proccreate(plumbproc, nil, STACK);
- proccreate(plumbshowproc, nil, STACK);
- threadcreate(plumbshowthread, nil, STACK);
- /* ... and use this thread to read the messages */
- plumbthread();
-}
-
-void
-plumbproc(void*)
-{
- Plumbmsg *m;
-
- threadsetname("plumbproc");
- for(;;){
- m = plumbrecv(plumbseemailfd);
- sendp(cplumb, m);
- if(m == nil)
- threadexits(nil);
- }
-}
-
-void
-plumbshowproc(void*)
-{
- Plumbmsg *m;
-
- threadsetname("plumbshowproc");
- for(;;){
- m = plumbrecv(plumbshowmailfd);
- sendp(cplumbshow, m);
- if(m == nil)
- threadexits(nil);
- }
-}
-
-void
-plumbsendproc(void*)
-{
- Plumbmsg *m;
-
- threadsetname("plumbsendproc");
- for(;;){
- m = plumbrecv(plumbsendmailfd);
- sendp(cplumbsend, m);
- if(m == nil)
- threadexits(nil);
- }
-}
-
-void
-newmesg(char *name, char *digest)
-{
- Dir *d;
-
- if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
- return; /* message is about another mailbox */
- if(mesglookupfile(&mbox, name, digest) != nil)
- return;
- d = dirstat(name);
- if(d == nil)
- return;
- if(mesgadd(&mbox, mbox.name, d, digest))
- mesgmenunew(wbox, &mbox);
- free(d);
-}
-
-void
-showmesg(char *name, char *digest)
-{
- char *n;
-
- if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
- return; /* message is about another mailbox */
- n = estrdup(name+strlen(mbox.name));
- if(n[strlen(n)-1] != '/')
- n = egrow(n, "/", nil);
- mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
- free(n);
-}
-
-void
-delmesg(char *name, char *digest, int dodel)
-{
- Message *m;
-
- m = mesglookupfile(&mbox, name, digest);
- if(m != nil){
- mesgmenumarkdel(wbox, &mbox, m, 0);
- if(dodel)
- m->writebackdel = 1;
- }
-}
-
-void
-plumbthread(void)
-{
- Plumbmsg *m;
- Plumbattr *a;
- char *type, *digest;
-
- threadsetname("plumbthread");
- while((m = recvp(cplumb)) != nil){
- a = m->attr;
- digest = plumblookup(a, "digest");
- type = plumblookup(a, "mailtype");
- if(type == nil)
- fprint(2, "Mail: plumb message with no mailtype attribute\n");
- else if(strcmp(type, "new") == 0)
- newmesg(m->data, digest);
- else if(strcmp(type, "delete") == 0)
- delmesg(m->data, digest, 0);
- else
- fprint(2, "Mail: unknown plumb attribute %s\n", type);
- plumbfree(m);
- }
- threadexits(nil);
-}
-
-void
-plumbshowthread(void*)
-{
- Plumbmsg *m;
-
- threadsetname("plumbshowthread");
- while((m = recvp(cplumbshow)) != nil){
- showmesg(m->data, plumblookup(m->attr, "digest"));
- plumbfree(m);
- }
- threadexits(nil);
-}
-
-void
-plumbsendthread(void*)
-{
- Plumbmsg *m;
-
- threadsetname("plumbsendthread");
- while((m = recvp(cplumbsend)) != nil){
- mkreply(nil, "Mail", m->data, m->attr, nil);
- plumbfree(m);
- }
- threadexits(nil);
-}
-
-int
-mboxcommand(Window *w, char *s)
-{
- char *args[10], **targs;
- Message *m, *next;
- int ok, nargs, i, j;
- char buf[128];
-
- nargs = tokenize(s, args, nelem(args));
- if(nargs == 0)
- return 0;
- if(strcmp(args[0], "Mail") == 0){
- if(nargs == 1)
- mkreply(nil, "Mail", "", nil, nil);
- else
- mkreply(nil, "Mail", args[1], nil, nil);
- return 1;
- }
- if(strcmp(s, "Del") == 0){
- if(mbox.dirty){
- mbox.dirty = 0;
- fprint(2, "mail: mailbox not written\n");
- return 1;
- }
- ok = 1;
- for(m=mbox.head; m!=nil; m=next){
- next = m->next;
- if(m->w){
- if(windel(m->w, 0))
- m->w = nil;
- else
- ok = 0;
- }
- }
- for(m=replies.head; m!=nil; m=next){
- next = m->next;
- if(m->w){
- if(windel(m->w, 0))
- m->w = nil;
- else
- ok = 0;
- }
- }
- if(ok){
- windel(w, 1);
- removeupasfs();
- threadexitsall(nil);
- }
- return 1;
- }
- if(strcmp(s, "Put") == 0){
- rewritembox(wbox, &mbox);
- return 1;
- }
- if(strcmp(s, "Delmesg") == 0){
- if(nargs > 1){
- for(i=1; i<nargs; i++){
- snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
- delmesg(buf, nil, 1);
- }
- }
- s = winselection(w);
- if(s == nil)
- return 1;
- nargs = 1;
- for(i=0; s[i]; i++)
- if(s[i] == '\n')
- nargs++;
- targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
- nargs = getfields(s, targs, nargs, 1, "\n");
- for(i=0; i<nargs; i++){
- if(!isdigit(targs[i][0]))
- continue;
- j = atoi(targs[i]); /* easy way to parse the number! */
- if(j == 0)
- continue;
- snprint(buf, sizeof buf, "%s%d", mbox.name, j);
- delmesg(buf, nil, 1);
- }
- free(s);
- free(targs);
- return 1;
- }
- return 0;
-}
-
-void
-mainctl(void *v)
-{
- Window *w;
- Event *e, *e2, *eq, *ea;
- int na, nopen;
- char *s, *t, *buf;
-
- w = v;
- proccreate(wineventproc, w, STACK);
-
- for(;;){
- e = recvp(w->cevent);
- switch(e->c1){
- default:
- Unknown:
- print("unknown message %c%c\n", e->c1, e->c2);
- break;
-
- case 'E': /* write to body; can't affect us */
- break;
-
- case 'F': /* generated by our actions; ignore */
- break;
-
- case 'K': /* type away; we don't care */
- break;
-
- case 'M':
- switch(e->c2){
- case 'x':
- case 'X':
- ea = nil;
- e2 = nil;
- if(e->flag & 2)
- e2 = recvp(w->cevent);
- if(e->flag & 8){
- ea = recvp(w->cevent);
- na = ea->nb;
- recvp(w->cevent);
- }else
- na = 0;
- s = e->b;
- /* if it's a known command, do it */
- if((e->flag&2) && e->nb==0)
- s = e2->b;
- if(na){
- t = emalloc(strlen(s)+1+na+1);
- sprint(t, "%s %s", s, ea->b);
- s = t;
- }
- /* if it's a long message, it can't be for us anyway */
- if(!mboxcommand(w, s)) /* send it back */
- winwriteevent(w, e);
- if(na)
- free(s);
- break;
-
- case 'l':
- case 'L':
- buf = nil;
- eq = e;
- if(e->flag & 2){
- e2 = recvp(w->cevent);
- eq = e2;
- }
- s = eq->b;
- if(eq->q1>eq->q0 && eq->nb==0){
- buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
- winread(w, eq->q0, eq->q1, buf);
- s = buf;
- }
- nopen = 0;
- do{
- /* skip 'deleted' string if present' */
- if(strncmp(s, deleted, strlen(deleted)) == 0)
- s += strlen(deleted);
- /* skip mail box name if present */
- if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
- s += strlen(mbox.name);
- nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
- while(*s!='\0' && *s++!='\n')
- ;
- }while(*s);
- if(nopen == 0) /* send it back */
- winwriteevent(w, e);
- free(buf);
- break;
-
- case 'I': /* modify away; we don't care */
- case 'D':
- case 'd':
- case 'i':
- break;
-
- default:
- goto Unknown;
- }
- }
- }
-}
-
--- a/acme/mail/src/mesg.c
+++ /dev/null
@@ -1,1322 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include <ctype.h>
-#include <plumb.h>
-#include "dat.h"
-
-enum
-{
- DIRCHUNK = 32*sizeof(Dir)
-};
-
-char regexchars[] = "\\/[].+?()*^$";
-char deleted[] = "(deleted)-";
-char deletedrx[] = "\\(deleted\\)-";
-char deletedrx01[] = "(\\(deleted\\)-)?";
-char deletedaddr[] = "-#0;/^\\(deleted\\)-/";
-
-struct{
- char *type;
- char *port;
- char *suffix;
-} ports[] = {
- "text/", "edit", ".txt",
- /* text must be first for plumbport() */
- "image/gif", "image", ".gif",
- "image/jpeg", "image", ".jpg",
- "image/jpeg", "image", ".jpeg",
- "image/png", "image", ".png",
- "image/tiff", "image", ".tif",
- "application/postscript", "postscript", ".ps",
- "application/pdf", "postscript", ".pdf",
- "application/msword", "msword", ".doc",
- "application/rtf", "msword", ".rtf",
- "audio/x-wav", "wav", ".wav",
- nil, nil
-};
-
-char *goodtypes[] = {
- "text",
- "text/plain",
- "message/rfc822",
- "text/richtext",
- "text/tab-separated-values",
- "application/octet-stream",
- nil,
-};
-
-struct{
- char *type;
- char *ext;
-} exts[] = {
- "image/gif", ".gif",
- "image/jpeg", ".jpg",
- nil, nil
-};
-
-char *okheaders[] =
-{
- "From:",
- "Date:",
- "To:",
- "CC:",
- "Subject:",
- nil
-};
-
-char *extraheaders[] =
-{
- "Resent-From:",
- "Resent-To:",
- "Sort:",
- nil,
-};
-
-char*
-line(char *data, char **pp)
-{
- char *p, *q;
-
- for(p=data; *p!='\0' && *p!='\n'; p++)
- ;
- if(*p == '\n')
- *pp = p+1;
- else
- *pp = p;
- q = emalloc(p-data + 1);
- memmove(q, data, p-data);
- return q;
-}
-
-void
-scanheaders(Message *m, char *dir)
-{
- char *s, *t, *u, *f;
-
- s = f = readfile(dir, "header", nil);
- if(s != nil)
- while(*s){
- t = line(s, &s);
- if(strncmp(t, "From: ", 6) == 0){
- m->fromcolon = estrdup(t+6);
- /* remove all quotes; they're ugly and irregular */
- for(u=m->fromcolon; *u; u++)
- if(*u == '"')
- memmove(u, u+1, strlen(u));
- }
- if(strncmp(t, "Subject: ", 9) == 0)
- m->subject = estrdup(t+9);
- free(t);
- }
- if(m->fromcolon == nil)
- m->fromcolon = estrdup(m->from);
- free(f);
-}
-
-int
-loadinfo(Message *m, char *dir)
-{
- int n;
- char *data, *p, *s;
-
- data = readfile(dir, "info", &n);
- if(data == nil)
- return 0;
- m->from = line(data, &p);
- scanheaders(m, dir); /* depends on m->from being set */
- m->to = line(p, &p);
- m->cc = line(p, &p);
- m->replyto = line(p, &p);
- m->date = line(p, &p);
- s = line(p, &p);
- if(m->subject == nil)
- m->subject = s;
- else
- free(s);
- m->type = line(p, &p);
- m->disposition = line(p, &p);
- m->filename = line(p, &p);
- m->digest = line(p, &p);
- free(data);
- return 1;
-}
-
-int
-isnumeric(char *s)
-{
- while(*s){
- if(!isdigit(*s))
- return 0;
- s++;
- }
- return 1;
-}
-
-Dir*
-loaddir(char *name, int *np)
-{
- int fd;
- Dir *dp;
-
- fd = open(name, OREAD);
- if(fd < 0)
- return nil;
- *np = dirreadall(fd, &dp);
- close(fd);
- return dp;
-}
-
-void
-readmbox(Message *mbox, char *dir, char *subdir)
-{
- char *name;
- Dir *d, *dirp;
- int i, n;
-
- name = estrstrdup(dir, subdir);
- dirp = loaddir(name, &n);
- mbox->recursed = 1;
- if(dirp)
- for(i=0; i<n; i++){
- d = &dirp[i];
- if(isnumeric(d->name))
- mesgadd(mbox, name, d, nil);
- }
- free(dirp);
- free(name);
-}
-
-/* add message to box, in increasing numerical order */
-int
-mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
-{
- Message *m;
- char *name;
- int loaded;
-
- m = emalloc(sizeof(Message));
- m->name = estrstrdup(d->name, "/");
- m->next = nil;
- m->prev = mbox->tail;
- m->level= mbox->level+1;
- m->recursed = 0;
- name = estrstrdup(dir, m->name);
- loaded = loadinfo(m, name);
- free(name);
- /* if two upas/fs are running, we can get misled, so check digest before accepting message */
- if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
- mesgfreeparts(m);
- free(m);
- return 0;
- }
- if(mbox->tail != nil)
- mbox->tail->next = m;
- mbox->tail = m;
- if(mbox->head == nil)
- mbox->head = m;
-
- if (m->level != 1){
- m->recursed = 1;
- readmbox(m, dir, m->name);
- }
- return 1;
-}
-
-int
-thisyear(char *year)
-{
- static char now[10];
- char *s;
-
- if(now[0] == '\0'){
- s = ctime(time(nil));
- strcpy(now, s+24);
- }
- return strncmp(year, now, 4) == 0;
-}
-
-char*
-stripdate(char *as)
-{
- int n;
- char *s, *fld[10];
-
- as = estrdup(as);
- s = estrdup(as);
- n = tokenize(s, fld, 10);
- if(n > 5){
- sprint(as, "%.3s ", fld[0]); /* day */
- /* some dates have 19 Apr, some Apr 19 */
- if(strlen(fld[1])<4 && isnumeric(fld[1]))
- sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]); /* date, month */
- else
- sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]); /* date, month */
- /* do we use time or year? depends on whether year matches this one */
- if(thisyear(fld[5])){
- if(strchr(fld[3], ':') != nil)
- sprint(as+strlen(as), "%.5s ", fld[3]); /* time */
- else if(strchr(fld[4], ':') != nil)
- sprint(as+strlen(as), "%.5s ", fld[4]); /* time */
- }else
- sprint(as+strlen(as), "%.4s ", fld[5]); /* year */
- }
- free(s);
- return as;
-}
-
-char*
-readfile(char *dir, char *name, int *np)
-{
- char *file, *data;
- int fd, len;
- Dir *d;
-
- if(np != nil)
- *np = 0;
- file = estrstrdup(dir, name);
- fd = open(file, OREAD);
- if(fd < 0)
- return nil;
- d = dirfstat(fd);
- free(file);
- len = 0;
- if(d != nil)
- len = d->length;
- free(d);
- data = emalloc(len+1);
- read(fd, data, len);
- close(fd);
- if(np != nil)
- *np = len;
- return data;
-}
-
-char*
-info(Message *m, int ind, int ogf)
-{
- char *i;
- int j, len, lens;
- char *p;
- char fmt[80], s[80];
-
- if (ogf)
- p=m->to;
- else
- p=m->fromcolon;
-
- if(ind==0 && shortmenu){
- len = 30;
- lens = 30;
- if(shortmenu > 1){
- len = 10;
- lens = 25;
- }
- if(ind==0 && m->subject[0]=='\0'){
- snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
- snprint(s, sizeof s, fmt, p);
- }else{
- snprint(fmt, sizeof fmt, " %%-%d.%ds %%-%d.%ds", len, len, lens, lens);
- snprint(s, sizeof s, fmt, p, m->subject);
- }
- i = estrdup(s);
-
- return i;
- }
-
- i = estrdup("");
- i = eappend(i, "\t", p);
- i = egrow(i, "\t", stripdate(m->date));
- if(ind == 0){
- if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 &&
- strncmp(m->type, "multipart/", 10)!=0)
- i = egrow(i, "\t(", estrstrdup(m->type, ")"));
- }else if(strncmp(m->type, "multipart/", 10) != 0)
- i = egrow(i, "\t(", estrstrdup(m->type, ")"));
- if(m->subject[0] != '\0'){
- i = eappend(i, "\n", nil);
- for(j=0; j<ind; j++)
- i = eappend(i, "\t", nil);
- i = eappend(i, "\t", m->subject);
- }
- return i;
-}
-
-void
-mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
-{
- int i;
- Message *m;
- char *name, *tmp;
- int ogf=0;
-
- if(strstr(realdir, "outgoing") != nil)
- ogf=1;
-
- /* show mail box in reverse order, pieces in forward order */
- if(ind > 0)
- m = mbox->head;
- else
- m = mbox->tail;
- while(m != nil){
- for(i=0; i<ind; i++)
- Bprint(fd, "\t");
- if(ind != 0)
- Bprint(fd, " ");
- name = estrstrdup(dir, m->name);
- tmp = info(m, ind, ogf);
- Bprint(fd, "%s%s\n", name, tmp);
- free(tmp);
- if(dotail && m->tail)
- mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
- free(name);
- if(ind)
- m = m->next;
- else
- m = m->prev;
- if(onlyone)
- m = nil;
- }
-}
-
-void
-mesgmenu(Window *w, Message *mbox)
-{
- winopenbody(w, OWRITE);
- mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
- winclosebody(w);
-}
-
-/* one new message has arrived, as mbox->tail */
-void
-mesgmenunew(Window *w, Message *mbox)
-{
- Biobuf *b;
-
- winselect(w, "0", 0);
- w->data = winopenfile(w, "data");
- b = emalloc(sizeof(Biobuf));
- Binit(b, w->data, OWRITE);
- mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
- Bterm(b);
- free(b);
- if(!mbox->dirty)
- winclean(w);
- /* select tag line plus following indented lines, but not final newline (it's distinctive) */
- winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
- close(w->addr);
- close(w->data);
- w->addr = -1;
- w->data = -1;
-}
-
-char*
-name2regexp(char *prefix, char *s)
-{
- char *buf, *p, *q;
-
- buf = emalloc(strlen(prefix)+2*strlen(s)+50); /* leave room to append more */
- p = buf;
- *p++ = '0';
- *p++ = '/';
- *p++ = '^';
- strcpy(p, prefix);
- p += strlen(prefix);
- for(q=s; *q!='\0'; q++){
- if(strchr(regexchars, *q) != nil)
- *p++ = '\\';
- *p++ = *q;
- }
- *p++ = '/';
- *p = '\0';
- return buf;
-}
-
-void
-mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
-{
- char *buf;
-
-
- if(m->deleted)
- return;
- m->writebackdel = writeback;
- if(w->data < 0)
- w->data = winopenfile(w, "data");
- buf = name2regexp("", m->name);
- strcat(buf, "-#0");
- if(winselect(w, buf, 1))
- write(w->data, deleted, 10);
- free(buf);
- close(w->data);
- close(w->addr);
- w->addr = w->data = -1;
- mbox->dirty = 1;
- m->deleted = 1;
-}
-
-void
-mesgmenumarkundel(Window *w, Message*, Message *m)
-{
- char *buf;
-
- if(m->deleted == 0)
- return;
- if(w->data < 0)
- w->data = winopenfile(w, "data");
- buf = name2regexp(deletedrx, m->name);
- if(winselect(w, buf, 1))
- if(winsetaddr(w, deletedaddr, 1))
- write(w->data, "", 0);
- free(buf);
- close(w->data);
- close(w->addr);
- w->addr = w->data = -1;
- m->deleted = 0;
-}
-
-void
-mesgmenudel(Window *w, Message *mbox, Message *m)
-{
- char *buf;
-
- if(w->data < 0)
- w->data = winopenfile(w, "data");
- buf = name2regexp(deletedrx, m->name);
- if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
- write(w->data, "", 0);
- free(buf);
- close(w->data);
- close(w->addr);
- w->addr = w->data = -1;
- mbox->dirty = 1;
- m->deleted = 1;
-}
-
-void
-mesgmenumark(Window *w, char *which, char *mark)
-{
- char *buf;
-
- if(w->data < 0)
- w->data = winopenfile(w, "data");
- buf = name2regexp(deletedrx01, which);
- if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) /* go to end of line */
- write(w->data, mark, strlen(mark));
- free(buf);
- close(w->data);
- close(w->addr);
- w->addr = w->data = -1;
- if(!mbox.dirty)
- winclean(w);
-}
-
-void
-mesgfreeparts(Message *m)
-{
- free(m->name);
- free(m->replyname);
- free(m->fromcolon);
- free(m->from);
- free(m->to);
- free(m->cc);
- free(m->replyto);
- free(m->date);
- free(m->subject);
- free(m->type);
- free(m->disposition);
- free(m->filename);
- free(m->digest);
-}
-
-void
-mesgdel(Message *mbox, Message *m)
-{
- Message *n, *next;
-
- if(m->opened)
- error("internal error: deleted message still open in mesgdel");
- /* delete subparts */
- for(n=m->head; n!=nil; n=next){
- next = n->next;
- mesgdel(m, n);
- }
- /* remove this message from list */
- if(m->next)
- m->next->prev = m->prev;
- else
- mbox->tail = m->prev;
- if(m->prev)
- m->prev->next = m->next;
- else
- mbox->head = m->next;
-
- mesgfreeparts(m);
-}
-
-int
-mesgsave(Message *m, char *s)
-{
- int ofd, n, k, ret;
- char *t, *raw, *unixheader, *all;
-
- t = estrstrdup(mbox.name, m->name);
- raw = readfile(t, "raw", &n);
- unixheader = readfile(t, "unixheader", &k);
- if(raw==nil || unixheader==nil){
- fprint(2, "Mail: can't read %s: %r\n", t);
- free(t);
- return 0;
- }
- free(t);
-
- all = emalloc(n+k+1);
- memmove(all, unixheader, k);
- memmove(all+k, raw, n);
- memmove(all+k+n, "\n", 1);
- n = k+n+1;
- free(unixheader);
- free(raw);
- ret = 1;
- s = estrdup(s);
- if(s[0] != '/')
- s = egrow(estrdup(mailboxdir), "/", s);
- ofd = open(s, OWRITE);
- if(ofd < 0){
- fprint(2, "Mail: can't open %s: %r\n", s);
- ret = 0;
- }else if(seek(ofd, 0LL, 2)<0 || write(ofd, all, n)!=n){
- fprint(2, "Mail: save failed: can't write %s: %r\n", s);
- ret = 0;
- }
- free(all);
- close(ofd);
- free(s);
- return ret;
-}
-
-int
-mesgcommand(Message *m, char *cmd)
-{
- char *s;
- char *args[10];
- int ok, ret, nargs;
-
- s = cmd;
- ret = 1;
- nargs = tokenize(s, args, nelem(args));
- if(nargs == 0)
- return 0;
- if(strcmp(args[0], "Post") == 0){
- mesgsend(m);
- goto Return;
- }
- if(strncmp(args[0], "Save", 4) == 0){
- if(m->isreply)
- goto Return;
- s = estrdup("\t[saved");
- if(nargs==1 || strcmp(args[1], "")==0){
- ok = mesgsave(m, "stored");
- }else{
- ok = mesgsave(m, args[1]);
- s = eappend(s, " ", args[1]);
- }
- if(ok){
- s = egrow(s, "]", nil);
- mesgmenumark(mbox.w, m->name, s);
- }
- free(s);
- goto Return;
- }
- if(strcmp(args[0], "Reply")==0){
- if(nargs>=2 && strcmp(args[1], "all")==0)
- mkreply(m, "Replyall", nil, nil, nil);
- else
- mkreply(m, "Reply", nil, nil, nil);
- goto Return;
- }
- if(strcmp(args[0], "Q") == 0){
- s = winselection(m->w); /* will be freed by mkreply */
- if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
- mkreply(m, "QReplyall", nil, nil, s);
- else
- mkreply(m, "QReply", nil, nil, s);
- goto Return;
- }
- if(strcmp(args[0], "Del") == 0){
- if(windel(m->w, 0)){
- chanfree(m->w->cevent);
- free(m->w);
- m->w = nil;
- if(m->isreply)
- delreply(m);
- else{
- m->opened = 0;
- m->tagposted = 0;
- }
- free(cmd);
- threadexits(nil);
- }
- goto Return;
- }
- if(strcmp(args[0], "Delmesg") == 0){
- if(!m->isreply){
- mesgmenumarkdel(wbox, &mbox, m, 1);
- free(cmd); /* mesgcommand might not return */
- mesgcommand(m, estrdup("Del"));
- return 1;
- }
- goto Return;
- }
- if(strcmp(args[0], "UnDelmesg") == 0){
- if(!m->isreply && m->deleted)
- mesgmenumarkundel(wbox, &mbox, m);
- goto Return;
- }
-// if(strcmp(args[0], "Headers") == 0){
-// m->showheaders();
-// return True;
-// }
-
- ret = 0;
-
- Return:
- free(cmd);
- return ret;
-}
-
-void
-mesgtagpost(Message *m)
-{
- if(m->tagposted)
- return;
- wintagwrite(m->w, " Post", 5);
- m->tagposted = 1;
-}
-
-/* need to expand selection more than default word */
-#pragma varargck argpos eval 2
-
-long
-eval(Window *w, char *s, ...)
-{
- char buf[64];
- va_list arg;
-
- va_start(arg, s);
- vsnprint(buf, sizeof buf, s, arg);
- va_end(arg);
-
- if(winsetaddr(w, buf, 1)==0)
- return -1;
-
- if(pread(w->addr, buf, 24, 0) != 24)
- return -1;
- return strtol(buf, 0, 10);
-}
-
-int
-isemail(char *s)
-{
- int nat;
-
- nat = 0;
- for(; *s; s++)
- if(*s == '@')
- nat++;
- else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
- return 0;
- return nat==1;
-}
-
-char addrdelim[] = "/[ \t\\n<>()\\[\\]]/";
-char*
-expandaddr(Window *w, Event *e)
-{
- char *s;
- long q0, q1;
-
- if(e->q0 != e->q1) /* cannot happen */
- return nil;
-
- q0 = eval(w, "#%d-%s", e->q0, addrdelim);
- if(q0 == -1) /* bad char not found */
- q0 = 0;
- else /* increment past bad char */
- q0++;
-
- q1 = eval(w, "#%d+%s", e->q0, addrdelim);
- if(q1 < 0){
- q1 = eval(w, "$");
- if(q1 < 0)
- return nil;
- }
- if(q0 >= q1)
- return nil;
- s = emalloc((q1-q0)*UTFmax+1);
- winread(w, q0, q1, s);
- return s;
-}
-
-int
-replytoaddr(Window *w, Message *m, Event *e, char *s)
-{
- int did;
- char *buf;
- Plumbmsg *pm;
-
- buf = nil;
- did = 0;
- if(e->flag & 2){
- /* autoexpanded; use our own bigger expansion */
- buf = expandaddr(w, e);
- if(buf == nil)
- return 0;
- s = buf;
- }
- if(isemail(s)){
- did = 1;
- pm = emalloc(sizeof(Plumbmsg));
- pm->src = estrdup("Mail");
- pm->dst = estrdup("sendmail");
- pm->data = estrdup(s);
- pm->ndata = -1;
- if(m->subject && m->subject[0]){
- pm->attr = emalloc(sizeof(Plumbattr));
- pm->attr->name = estrdup("Subject");
- if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
- pm->attr->value = estrstrdup("Re: ", m->subject);
- else
- pm->attr->value = estrdup(m->subject);
- pm->attr->next = nil;
- }
- if(plumbsend(plumbsendfd, pm) < 0)
- fprint(2, "error writing plumb message: %r\n");
- plumbfree(pm);
- }
- free(buf);
- return did;
-}
-
-
-void
-mesgctl(void *v)
-{
- Message *m;
- Window *w;
- Event *e, *eq, *e2, *ea;
- int na, nopen, i, j;
- char *os, *s, *t, *buf;
-
- m = v;
- w = m->w;
- threadsetname("mesgctl");
- proccreate(wineventproc, w, STACK);
- for(;;){
- e = recvp(w->cevent);
- switch(e->c1){
- default:
- Unk:
- print("unknown message %c%c\n", e->c1, e->c2);
- break;
-
- case 'E': /* write to body; can't affect us */
- break;
-
- case 'F': /* generated by our actions; ignore */
- break;
-
- case 'K': /* type away; we don't care */
- case 'M':
- switch(e->c2){
- case 'x': /* mouse only */
- case 'X':
- ea = nil;
- eq = e;
- if(e->flag & 2){
- e2 = recvp(w->cevent);
- eq = e2;
- }
- if(e->flag & 8){
- ea = recvp(w->cevent);
- recvp(w->cevent);
- na = ea->nb;
- }else
- na = 0;
- if(eq->q1>eq->q0 && eq->nb==0){
- s = emalloc((eq->q1-eq->q0)*UTFmax+1);
- winread(w, eq->q0, eq->q1, s);
- }else
- s = estrdup(eq->b);
- if(na){
- t = emalloc(strlen(s)+1+na+1);
- sprint(t, "%s %s", s, ea->b);
- free(s);
- s = t;
- }
- if(!mesgcommand(m, s)) /* send it back */
- winwriteevent(w, e);
- break;
-
- case 'l': /* mouse only */
- case 'L':
- buf = nil;
- eq = e;
- if(e->flag & 2){
- e2 = recvp(w->cevent);
- eq = e2;
- }
- s = eq->b;
- if(eq->q1>eq->q0 && eq->nb==0){
- buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
- winread(w, eq->q0, eq->q1, buf);
- s = buf;
- }
- os = s;
- nopen = 0;
- do{
- /* skip mail box name if present */
- if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
- s += strlen(mbox.name);
- if(strstr(s, "body") != nil){
- /* strip any known extensions */
- for(i=0; exts[i].ext!=nil; i++){
- j = strlen(exts[i].ext);
- if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
- s[strlen(s)-j] = '\0';
- break;
- }
- }
- if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
- s[strlen(s)-4] = '\0'; /* leave / in place */
- }
- nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
- while(*s!=0 && *s++!='\n')
- ;
- }while(*s);
- if(nopen == 0 && e->c1 == 'L')
- nopen += replytoaddr(w, m, e, os);
- if(nopen == 0)
- winwriteevent(w, e);
- free(buf);
- break;
-
- case 'I': /* modify away; we don't care */
- case 'D':
- mesgtagpost(m);
- /* fall through */
- case 'd':
- case 'i':
- break;
-
- default:
- goto Unk;
- }
- }
- }
-}
-
-void
-mesgline(Message *m, char *header, char *value)
-{
- if(strlen(value) > 0)
- Bprint(m->w->body, "%s: %s\n", header, value);
-}
-
-int
-isprintable(char *type)
-{
- int i;
-
- for(i=0; goodtypes[i]!=nil; i++)
- if(strcmp(type, goodtypes[i])==0)
- return 1;
- return 0;
-}
-
-char*
-ext(char *type)
-{
- int i;
-
- for(i=0; exts[i].type!=nil; i++)
- if(strcmp(type, exts[i].type)==0)
- return exts[i].ext;
- return "";
-}
-
-void
-mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
-{
- char *dest;
-
- if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
- if(strlen(m->filename) == 0){
- dest = estrdup(m->name);
- dest[strlen(dest)-1] = '\0';
- }else
- dest = estrdup(m->filename);
- if(m->filename[0] != '/')
- dest = egrow(estrdup(home), "/", dest);
- Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
- free(dest);
- }else if(!fileonly)
- Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
-}
-
-void
-printheader(char *dir, Biobuf *b, char **okheaders)
-{
- char *s;
- char *lines[100];
- int i, j, n;
-
- s = readfile(dir, "header", nil);
- if(s == nil)
- return;
- n = getfields(s, lines, nelem(lines), 0, "\n");
- for(i=0; i<n; i++)
- for(j=0; okheaders[j]; j++)
- if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
- Bprint(b, "%s\n", lines[i]);
- free(s);
-}
-
-void
-mesgload(Message *m, char *rootdir, char *file, Window *w)
-{
- char *s, *subdir, *name, *dir;
- Message *mp, *thisone;
- int n;
-
- dir = estrstrdup(rootdir, file);
-
- if(strcmp(m->type, "message/rfc822") != 0){ /* suppress headers of envelopes */
- if(strlen(m->from) > 0){
- Bprint(w->body, "From: %s\n", m->from);
- mesgline(m, "Date", m->date);
- mesgline(m, "To", m->to);
- mesgline(m, "CC", m->cc);
- mesgline(m, "Subject", m->subject);
- printheader(dir, w->body, extraheaders);
- }else{
- printheader(dir, w->body, okheaders);
- printheader(dir, w->body, extraheaders);
- }
- Bprint(w->body, "\n");
- }
-
- if(m->level == 1 && m->recursed == 0){
- m->recursed = 1;
- readmbox(m, rootdir, m->name);
- }
- if(m->head == nil){ /* single part message */
- if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
- mimedisplay(m, m->name, rootdir, w, 1);
- s = readbody(m->type, dir, &n);
- winwritebody(w, s, n);
- free(s);
- }else
- mimedisplay(m, m->name, rootdir, w, 0);
- }else{
- /* multi-part message, either multipart/* or message/rfc822 */
- thisone = nil;
- if(strcmp(m->type, "multipart/alternative") == 0){
- thisone = m->head; /* in case we can't find a good one */
- for(mp=m->head; mp!=nil; mp=mp->next)
- if(isprintable(mp->type)){
- thisone = mp;
- break;
- }
- }
- for(mp=m->head; mp!=nil; mp=mp->next){
- if(thisone!=nil && mp!=thisone)
- continue;
- subdir = estrstrdup(dir, mp->name);
- name = estrstrdup(file, mp->name);
- /* skip first element in name because it's already in window name */
- if(mp != m->head)
- Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
- if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
- mimedisplay(mp, name, rootdir, w, 1);
- printheader(subdir, w->body, okheaders);
- printheader(subdir, w->body, extraheaders);
- winwritebody(w, "\n", 1);
- s = readbody(mp->type, subdir, &n);
- winwritebody(w, s, n);
- free(s);
- }else{
- if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
- mp->w = w;
- mesgload(mp, rootdir, name, w);
- mp->w = nil;
- }else
- mimedisplay(mp, name, rootdir, w, 0);
- }
- free(name);
- free(subdir);
- }
- }
- free(dir);
-}
-
-int
-tokenizec(char *str, char **args, int max, char *splitc)
-{
- int na;
- int intok = 0;
-
- if(max <= 0)
- return 0;
- for(na=0; *str != '\0';str++){
- if(strchr(splitc, *str) == nil){
- if(intok)
- continue;
- args[na++] = str;
- intok = 1;
- }else{
- /* it's a separator/skip character */
- *str = '\0';
- if(intok){
- intok = 0;
- if(na >= max)
- break;
- }
- }
- }
- return na;
-}
-
-Message*
-mesglookup(Message *mbox, char *name, char *digest)
-{
- int n;
- Message *m;
- char *t;
-
- if(digest){
- /* can find exactly */
- for(m=mbox->head; m!=nil; m=m->next)
- if(strcmp(digest, m->digest) == 0)
- break;
- return m;
- }
-
- n = strlen(name);
- if(n == 0)
- return nil;
- if(name[n-1] == '/')
- t = estrdup(name);
- else
- t = estrstrdup(name, "/");
- for(m=mbox->head; m!=nil; m=m->next)
- if(strcmp(t, m->name) == 0)
- break;
- free(t);
- return m;
-}
-
-/*
- * Find plumb port, knowing type is text, given file name (by extension)
- */
-int
-plumbportbysuffix(char *file)
-{
- char *suf;
- int i, nsuf, nfile;
-
- nfile = strlen(file);
- for(i=0; ports[i].type!=nil; i++){
- suf = ports[i].suffix;
- nsuf = strlen(suf);
- if(nfile > nsuf)
- if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
- return i;
- }
- return 0;
-}
-
-/*
- * Find plumb port using type and file name (by extension)
- */
-int
-plumbport(char *type, char *file)
-{
- int i;
-
- for(i=0; ports[i].type!=nil; i++)
- if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
- return i;
- /* see if it's a text type */
- for(i=0; goodtypes[i]!=nil; i++)
- if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
- return plumbportbysuffix(file);
- return -1;
-}
-
-void
-plumb(Message *m, char *dir)
-{
- int i;
- char *port;
- Plumbmsg *pm;
-
- if(strlen(m->type) == 0)
- return;
- i = plumbport(m->type, m->filename);
- if(i < 0)
- fprint(2, "can't find destination for message subpart\n");
- else{
- port = ports[i].port;
- pm = emalloc(sizeof(Plumbmsg));
- pm->src = estrdup("Mail");
- if(port)
- pm->dst = estrdup(port);
- else
- pm->dst = nil;
- pm->wdir = nil;
- pm->type = estrdup("text");
- pm->ndata = -1;
- pm->data = estrstrdup(dir, "body");
- pm->data = eappend(pm->data, "", ports[i].suffix);
- if(plumbsend(plumbsendfd, pm) < 0)
- fprint(2, "error writing plumb message: %r\n");
- plumbfree(pm);
- }
-}
-
-int
-mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
-{
- char *t, *u, *v;
- Message *m;
- char *direlem[10];
- int i, ndirelem, reuse;
-
- /* find white-space-delimited first word */
- for(t=s; *t!='\0' && !isspace(*t); t++)
- ;
- u = emalloc(t-s+1);
- memmove(u, s, t-s);
- /* separate it on slashes */
- ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
- if(ndirelem <= 0){
- Error:
- free(u);
- return 0;
- }
- if(plumbed){
- write(wctlfd, "top", 3);
- write(wctlfd, "current", 7);
- }
- /* open window for message */
- m = mesglookup(mbox, direlem[0], digest);
- if(m == nil)
- goto Error;
- if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */
- goto Error;
- if(m->opened == 0){
- if(m->w == nil){
- reuse = 0;
- m->w = newwindow();
- }else{
- reuse = 1;
- /* re-use existing window */
- if(winsetaddr(m->w, "0,$", 1)){
- if(m->w->data < 0)
- m->w->data = winopenfile(m->w, "data");
- write(m->w->data, "", 0);
- }
- }
- v = estrstrdup(mbox->name, m->name);
- winname(m->w, v);
- free(v);
- if(!reuse){
- if(m->deleted)
- wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
- else
- wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
- }
- threadcreate(mesgctl, m, STACK);
- winopenbody(m->w, OWRITE);
- mesgload(m, dir, m->name, m->w);
- winclosebody(m->w);
- winclean(m->w);
- m->opened = 1;
- if(ndirelem == 1){
- free(u);
- return 1;
- }
- }
- if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
- /* make sure dot is visible */
- ctlprint(m->w->ctl, "show\n");
- return 0;
- }
- /* walk to subpart */
- dir = estrstrdup(dir, m->name);
- for(i=1; i<ndirelem; i++){
- m = mesglookup(m, direlem[i], digest);
- if(m == nil)
- break;
- dir = egrow(dir, m->name, nil);
- }
- if(m != nil && plumbport(m->type, m->filename) > 0)
- plumb(m, dir);
- free(dir);
- free(u);
- return 1;
-}
-
-void
-rewritembox(Window *w, Message *mbox)
-{
- Message *m, *next;
- char *deletestr, *t;
- int nopen;
-
- deletestr = estrstrdup("delete ", fsname);
-
- nopen = 0;
- for(m=mbox->head; m!=nil; m=next){
- next = m->next;
- if(m->deleted == 0)
- continue;
- if(m->opened){
- nopen++;
- continue;
- }
- if(m->writebackdel){
- /* messages deleted by plumb message are not removed again */
- t = estrdup(m->name);
- if(strlen(t) > 0)
- t[strlen(t)-1] = '\0';
- deletestr = egrow(deletestr, " ", t);
- }
- mesgmenudel(w, mbox, m);
- mesgdel(mbox, m);
- }
- if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
- fprint(2, "Mail: warning: error removing mail message files: %r\n");
- free(deletestr);
- winselect(w, "0", 0);
- if(nopen == 0)
- winclean(w);
- mbox->dirty = 0;
-}
-
-/* name is a full file name, but it might not belong to us */
-Message*
-mesglookupfile(Message *mbox, char *name, char *digest)
-{
- int k, n;
-
- k = strlen(name);
- n = strlen(mbox->name);
- if(k==0 || strncmp(name, mbox->name, n) != 0){
-// fprint(2, "Mail: message %s not in this mailbox\n", name);
- return nil;
- }
- return mesglookup(mbox, name+n, digest);
-}
--- a/acme/mail/src/mkfile
+++ /dev/null
@@ -1,30 +1,0 @@
-</$objtype/mkfile
-
-TARG=Mail
-OFILES=\
- html.$O\
- mail.$O\
- mesg.$O\
- reply.$O\
- util.$O\
- win.$O
-
-HFILES=dat.h
-LIB=
-
-BIN=/acme/bin/$objtype
-
-UPDATE=\
- mkfile\
- $HFILES\
- ${OFILES:%.$O=%.c}\
-
-</sys/src/cmd/mkone
-
-$O.out: $OFILES
- $LD -o $target $LDFLAGS $OFILES
-
-syms:V:
- 8c -a mail.c >syms
- 8c -aa mesg.c reply.c util.c win.c >>syms
-
--- a/acme/mail/src/reply.c
+++ /dev/null
@@ -1,567 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include <ctype.h>
-#include <plumb.h>
-#include "dat.h"
-
-static int replyid;
-
-int
-quote(Message *m, Biobuf *b, char *dir, char *quotetext)
-{
- char *body, *type;
- int i, n, nlines;
- char **lines;
-
- if(quotetext){
- body = quotetext;
- n = strlen(body);
- type = nil;
- }else{
- /* look for first textual component to quote */
- type = readfile(dir, "type", &n);
- if(type == nil){
- print("no type in %s\n", dir);
- return 0;
- }
- if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
- dir = estrstrdup(dir, "1/");
- if(quote(m, b, dir, nil)){
- free(type);
- free(dir);
- return 1;
- }
- free(dir);
- }
- if(strncmp(type, "text", 4) != 0){
- free(type);
- return 0;
- }
- body = readbody(m->type, dir, &n);
- if(body == nil)
- return 0;
- }
- nlines = 0;
- for(i=0; i<n; i++)
- if(body[i] == '\n')
- nlines++;
- nlines++;
- lines = emalloc(nlines*sizeof(char*));
- nlines = getfields(body, lines, nlines, 0, "\n");
- /* delete leading and trailing blank lines */
- i = 0;
- while(i<nlines && lines[i][0]=='\0')
- i++;
- while(i<nlines && lines[nlines-1][0]=='\0')
- nlines--;
- while(i < nlines){
- Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
- i++;
- }
- free(lines);
- free(body); /* will free quotetext if non-nil */
- free(type);
- return 1;
-}
-
-void
-mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
-{
- Message *r;
- char *dir, *t;
- int quotereply;
- Plumbattr *a;
-
- quotereply = (label[0] == 'Q');
- r = emalloc(sizeof(Message));
- r->isreply = 1;
- if(m != nil)
- r->replyname = estrdup(m->name);
- r->next = replies.head;
- r->prev = nil;
- if(replies.head != nil)
- replies.head->prev = r;
- replies.head = r;
- if(replies.tail == nil)
- replies.tail = r;
- r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
- sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
- r->w = newwindow();
- winname(r->w, r->name);
- ctlprint(r->w->ctl, "cleartag");
- wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
- r->tagposted = 1;
- threadcreate(mesgctl, r, STACK);
- winopenbody(r->w, OWRITE);
- if(to!=nil && to[0]!='\0')
- Bprint(r->w->body, "%s\n", to);
- for(a=attr; a; a=a->next)
- Bprint(r->w->body, "%s: %s\n", a->name, a->value);
- dir = nil;
- if(m != nil){
- dir = estrstrdup(mbox.name, m->name);
- if(to == nil && attr == nil){
- /* Reply goes to replyto; Reply all goes to From and To and CC */
- if(strstr(label, "all") == nil)
- Bprint(r->w->body, "To: %s\n", m->replyto);
- else{ /* Replyall */
- if(strlen(m->from) > 0)
- Bprint(r->w->body, "To: %s\n", m->from);
- if(strlen(m->to) > 0)
- Bprint(r->w->body, "To: %s\n", m->to);
- if(strlen(m->cc) > 0)
- Bprint(r->w->body, "CC: %s\n", m->cc);
- }
- }
- if(strlen(m->subject) > 0){
- t = "Subject: Re: ";
- if(strlen(m->subject) >= 3)
- if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
- t = "Subject: ";
- Bprint(r->w->body, "%s%s\n", t, m->subject);
- }
- if(!quotereply){
- Bprint(r->w->body, "Include: %sraw\n", dir);
- free(dir);
- }
- }
- Bprint(r->w->body, "\n");
- if(m == nil)
- Bprint(r->w->body, "\n");
- else if(quotereply){
- quote(m, r->w->body, dir, quotetext);
- free(dir);
- }
- winclosebody(r->w);
- if(m==nil && (to==nil || to[0]=='\0'))
- winselect(r->w, "0", 0);
- else
- winselect(r->w, "$", 0);
- winclean(r->w);
- windormant(r->w);
-}
-
-void
-delreply(Message *m)
-{
- if(m->next == nil)
- replies.tail = m->prev;
- else
- m->next->prev = m->prev;
- if(m->prev == nil)
- replies.head = m->next;
- else
- m->prev->next = m->next;
- mesgfreeparts(m);
- free(m);
-}
-
-/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
-void
-buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
-{
- int i, n;
- char *s, *a;
-
- s = args;
- for(i=0; i<NARGS; i++){
- a = inargv[i];
- if(a == nil)
- break;
- n = strlen(a)+1;
- if((s-args)+n >= NARGCHAR) /* too many characters */
- break;
- argv[i] = s;
- memmove(s, a, n);
- s += n;
- free(a);
- }
- argv[i] = nil;
-}
-
-void
-execproc(void *v)
-{
- struct Exec *e;
- int p[2], q[2];
- char *prog;
- char *argv[NARGS+1], args[NARGCHAR];
-
- e = v;
- p[0] = e->p[0];
- p[1] = e->p[1];
- q[0] = e->q[0];
- q[1] = e->q[1];
- prog = e->prog; /* known not to be malloc'ed */
- rfork(RFFDG);
- sendul(e->sync, 1);
- buildargv(e->argv, argv, args);
- free(e->argv);
- chanfree(e->sync);
- free(e);
- dup(p[0], 0);
- close(p[0]);
- close(p[1]);
- if(q[0]){
- dup(q[1], 1);
- close(q[0]);
- close(q[1]);
- }
- procexec(nil, prog, argv);
-//fprint(2, "exec: %s", e->prog);
-//{int i;
-//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
-//print("\n");
-//}
-//argv[0] = "cat";
-//argv[1] = nil;
-//procexec(nil, "/bin/cat", argv);
- fprint(2, "Mail: can't exec %s: %r\n", prog);
- threadexits("can't exec");
-}
-
-enum{
- ATTACH,
- BCC,
- CC,
- FROM,
- INCLUDE,
- TO,
-};
-
-char *headers[] = {
- "attach:",
- "bcc:",
- "cc:",
- "from:",
- "include:",
- "to:",
- nil,
-};
-
-int
-whichheader(char *h)
-{
- int i;
-
- for(i=0; headers[i]!=nil; i++)
- if(cistrcmp(h, headers[i]) == 0)
- return i;
- return -1;
-}
-
-char *tolist[200];
-char *cclist[200];
-char *bcclist[200];
-int ncc, nbcc, nto;
-char *attlist[200];
-char included[200];
-
-int
-addressed(char *name)
-{
- int i;
-
- for(i=0; i<nto; i++)
- if(strcmp(name, tolist[i]) == 0)
- return 1;
- for(i=0; i<ncc; i++)
- if(strcmp(name, cclist[i]) == 0)
- return 1;
- for(i=0; i<nbcc; i++)
- if(strcmp(name, bcclist[i]) == 0)
- return 1;
- return 0;
-}
-
-char*
-skipbl(char *s, char *e)
-{
- while(s < e){
- if(*s!=' ' && *s!='\t' && *s!=',')
- break;
- s++;
- }
- return s;
-}
-
-char*
-findbl(char *s, char *e)
-{
- while(s < e){
- if(*s==' ' || *s=='\t' || *s==',')
- break;
- s++;
- }
- return s;
-}
-
-/*
- * comma-separate possibly blank-separated strings in line; e points before newline
- */
-void
-commas(char *s, char *e)
-{
- char *t;
-
- /* may have initial blanks */
- s = skipbl(s, e);
- while(s < e){
- s = findbl(s, e);
- if(s == e)
- break;
- t = skipbl(s, e);
- if(t == e) /* no more words */
- break;
- /* patch comma */
- *s++ = ',';
- while(s < t)
- *s++ = ' ';
- }
-}
-
-int
-print2(int fd, int ofd, char *fmt, ...)
-{
- int m, n;
- char *s;
- va_list arg;
-
- va_start(arg, fmt);
- s = vsmprint(fmt, arg);
- va_end(arg);
- if(s == nil)
- return -1;
- m = strlen(s);
- n = write(fd, s, m);
- if(ofd > 0)
- write(ofd, s, m);
- return n;
-}
-
-void
-write2(int fd, int ofd, char *buf, int n, int nofrom)
-{
- char *from, *p;
- int m;
-
- write(fd, buf, n);
-
- if(ofd <= 0)
- return;
-
- if(nofrom == 0){
- write(ofd, buf, n);
- return;
- }
-
- /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
- for(p=buf; *p; p+=m){
- from = cistrstr(p, "from");
- if(from == nil)
- m = n;
- else
- m = from - p;
- if(m > 0)
- write(ofd, p, m);
- if(from){
- if(p==buf || from[-1]=='\n')
- write(ofd, " ", 1); /* escape with space if From is at start of line */
- write(ofd, from, 4);
- m += 4;
- }
- n -= m;
- }
-}
-
-void
-mesgsend(Message *m)
-{
- char *s, *body, *to;
- int i, j, h, n, natt, p[2];
- struct Exec *e;
- Channel *sync;
- int first, nfld, delit, ofd;
- char *copy, *fld[100], *now;
-
- body = winreadbody(m->w, &n);
- /* assemble to: list from first line, to: line, and cc: line */
- nto = 0;
- natt = 0;
- ncc = 0;
- nbcc = 0;
- first = 1;
- to = body;
- for(;;){
- for(s=to; *s!='\n'; s++)
- if(*s == '\0'){
- free(body);
- return;
- }
- if(s++ == to) /* blank line */
- break;
- /* make copy of line to tokenize */
- copy = emalloc(s-to);
- memmove(copy, to, s-to);
- copy[s-to-1] = '\0';
- nfld = tokenizec(copy, fld, nelem(fld), ", \t");
- if(nfld == 0){
- free(copy);
- break;
- }
- n -= s-to;
- switch(h = whichheader(fld[0])){
- case TO:
- case FROM:
- delit = 1;
- commas(to+strlen(fld[0]), s-1);
- for(i=1; i<nfld && nto<nelem(tolist); i++)
- if(!addressed(fld[i]))
- tolist[nto++] = estrdup(fld[i]);
- break;
- case BCC:
- delit = 1;
- commas(to+strlen(fld[0]), s-1);
- for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
- if(!addressed(fld[i]))
- bcclist[nbcc++] = estrdup(fld[i]);
- break;
- case CC:
- delit = 1;
- commas(to+strlen(fld[0]), s-1);
- for(i=1; i<nfld && ncc<nelem(cclist); i++)
- if(!addressed(fld[i]))
- cclist[ncc++] = estrdup(fld[i]);
- break;
- case ATTACH:
- case INCLUDE:
- delit = 1;
- for(i=1; i<nfld && natt<nelem(attlist); i++){
- attlist[natt] = estrdup(fld[i]);
- included[natt++] = (h == INCLUDE);
- }
- break;
- default:
- if(first){
- delit = 1;
- for(i=0; i<nfld && nto<nelem(tolist); i++)
- tolist[nto++] = estrdup(fld[i]);
- }else /* ignore it */
- delit = 0;
- break;
- }
- if(delit){
- /* delete line from body */
- memmove(to, s, n+1);
- }else
- to = s;
- free(copy);
- first = 0;
- }
-
- ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
- if(ofd > 0){
- /* From dhog Fri Aug 24 22:13:00 EDT 2001 */
- now = ctime(time(0));
- fprint(ofd, "From %s %s", user, now);
- fprint(ofd, "From: %s\n", user);
- fprint(ofd, "Date: %s", now);
- for(i=0; i<natt; i++)
- if(included[i])
- fprint(ofd, "Include: %s\n", attlist[i]);
- else
- fprint(ofd, "Attach: %s\n", attlist[i]);
- /* needed because mail is by default Latin-1 */
- fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
- fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
- }
-
- e = emalloc(sizeof(struct Exec));
- if(pipe(p) < 0)
- error("can't create pipe: %r");
- e->p[0] = p[0];
- e->p[1] = p[1];
- e->prog = "/bin/upas/marshal";
- e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
- e->argv[0] = estrdup("marshal");
- e->argv[1] = estrdup("-8");
- j = 2;
- if(m->replyname){
- e->argv[j++] = estrdup("-R");
- e->argv[j++] = estrstrdup(mbox.name, m->replyname);
- }
- for(i=0; i<natt; i++){
- if(included[i])
- e->argv[j++] = estrdup("-A");
- else
- e->argv[j++] = estrdup("-a");
- e->argv[j++] = estrdup(attlist[i]);
- }
- sync = chancreate(sizeof(int), 0);
- e->sync = sync;
- proccreate(execproc, e, EXECSTACK);
- recvul(sync);
- close(p[0]);
-
- /* using marshal -8, so generate rfc822 headers */
- if(nto > 0){
- print2(p[1], ofd, "To: ");
- for(i=0; i<nto-1; i++)
- print2(p[1], ofd, "%s, ", tolist[i]);
- print2(p[1], ofd, "%s\n", tolist[i]);
- }
- if(ncc > 0){
- print2(p[1], ofd, "CC: ");
- for(i=0; i<ncc-1; i++)
- print2(p[1], ofd, "%s, ", cclist[i]);
- print2(p[1], ofd, "%s\n", cclist[i]);
- }
- if(nbcc > 0){
- print2(p[1], ofd, "BCC: ");
- for(i=0; i<nbcc-1; i++)
- print2(p[1], ofd, "%s, ", bcclist[i]);
- print2(p[1], ofd, "%s\n", bcclist[i]);
- }
-
- i = strlen(body);
- if(i > 0)
- write2(p[1], ofd, body, i, 1);
-
- /* guarantee a blank line, to ensure attachments are separated from body */
- if(i==0 || body[i-1]!='\n')
- write2(p[1], ofd, "\n\n", 2, 0);
- else if(i>1 && body[i-2]!='\n')
- write2(p[1], ofd, "\n", 1, 0);
-
- /* these look like pseudo-attachments in the "outgoing" box */
- if(ofd>0 && natt>0){
- for(i=0; i<natt; i++)
- if(included[i])
- fprint(ofd, "=====> Include: %s\n", attlist[i]);
- else
- fprint(ofd, "=====> Attach: %s\n", attlist[i]);
- }
- if(ofd > 0)
- write(ofd, "\n", 1);
-
- for(i=0; i<natt; i++)
- free(attlist[i]);
- close(ofd);
- close(p[1]);
- free(body);
-
- if(m->replyname != nil)
- mesgmenumark(mbox.w, m->replyname, "\t[replied]");
- if(m->name[0] == '/')
- s = estrdup(m->name);
- else
- s = estrstrdup(mbox.name, m->name);
- s = egrow(s, "-R", nil);
- winname(m->w, s);
- free(s);
- winclean(m->w);
- /* mark message unopened because it's no longer the original message */
- m->opened = 0;
-}
--- a/acme/mail/src/util.c
+++ /dev/null
@@ -1,105 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include <plumb.h>
-#include "dat.h"
-
-void*
-emalloc(uint n)
-{
- void *p;
-
- p = malloc(n);
- if(p == nil)
- error("can't malloc: %r");
- memset(p, 0, n);
- setmalloctag(p, getcallerpc(&n));
- return p;
-}
-
-void*
-erealloc(void *p, uint n)
-{
- p = realloc(p, n);
- if(p == nil)
- error("can't realloc: %r");
- setmalloctag(p, getcallerpc(&n));
- return p;
-}
-
-char*
-estrdup(char *s)
-{
- char *t;
-
- t = emalloc(strlen(s)+1);
- strcpy(t, s);
- return t;
-}
-
-char*
-estrstrdup(char *s, char *t)
-{
- char *u;
-
- u = emalloc(strlen(s)+strlen(t)+1);
- strcpy(u, s);
- strcat(u, t);
- return u;
-}
-
-char*
-eappend(char *s, char *sep, char *t)
-{
- char *u;
-
- if(t == nil)
- u = estrstrdup(s, sep);
- else{
- u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
- strcpy(u, s);
- strcat(u, sep);
- strcat(u, t);
- }
- free(s);
- return u;
-}
-
-char*
-egrow(char *s, char *sep, char *t)
-{
- s = eappend(s, sep, t);
- free(t);
- return s;
-}
-
-void
-error(char *fmt, ...)
-{
- Fmt f;
- char buf[64];
- va_list arg;
-
- fmtfdinit(&f, 2, buf, sizeof buf);
- fmtprint(&f, "Mail: ");
- va_start(arg, fmt);
- fmtvprint(&f, fmt, arg);
- va_end(arg);
- fmtprint(&f, "\n");
- fmtfdflush(&f);
- threadexitsall(buf);
-}
-
-void
-ctlprint(int fd, char *fmt, ...)
-{
- int n;
- va_list arg;
-
- va_start(arg, fmt);
- n = vfprint(fd, fmt, arg);
- va_end(arg);
- if(n <= 0)
- error("control file write error: %r");
-}
--- a/acme/mail/src/win.c
+++ /dev/null
@@ -1,341 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <thread.h>
-#include <plumb.h>
-#include "dat.h"
-
-Window*
-newwindow(void)
-{
- char buf[12];
- Window *w;
-
- w = emalloc(sizeof(Window));
- w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
- if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
- error("can't open window ctl file: %r");
- ctlprint(w->ctl, "noscroll\n");
- w->id = atoi(buf);
- w->event = winopenfile(w, "event");
- w->addr = -1; /* will be opened when needed */
- w->body = nil;
- w->data = -1;
- w->cevent = chancreate(sizeof(Event*), 0);
- return w;
-}
-
-void
-winsetdump(Window *w, char *dir, char *cmd)
-{
- if(dir != nil)
- ctlprint(w->ctl, "dumpdir %s\n", dir);
- if(cmd != nil)
- ctlprint(w->ctl, "dump %s\n", cmd);
-}
-
-void
-wineventproc(void *v)
-{
- Window *w;
- int i;
-
- w = v;
- for(i=0; ; i++){
- if(i >= NEVENT)
- i = 0;
- wingetevent(w, &w->e[i]);
- sendp(w->cevent, &w->e[i]);
- }
-}
-
-static int
-winopenfile1(Window *w, char *f, int m)
-{
- char buf[64];
- int fd;
-
- sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
- fd = open(buf, m|OCEXEC);
- if(fd < 0)
- error("can't open window file %s: %r", f);
- return fd;
-}
-
-int
-winopenfile(Window *w, char *f)
-{
- return winopenfile1(w, f, ORDWR);
-}
-
-void
-wintagwrite(Window *w, char *s, int n)
-{
- int fd;
-
- fd = winopenfile(w, "tag");
- if(write(fd, s, n) != n)
- error("tag write: %r");
- close(fd);
-}
-
-void
-winname(Window *w, char *s)
-{
- ctlprint(w->ctl, "name %s\n", s);
-}
-
-void
-winopenbody(Window *w, int mode)
-{
- char buf[256];
-
- sprint(buf, "/mnt/wsys/%d/body", w->id);
- w->body = Bopen(buf, mode|OCEXEC);
- if(w->body == nil)
- error("can't open window body file: %r");
-}
-
-void
-winclosebody(Window *w)
-{
- if(w->body != nil){
- Bterm(w->body);
- w->body = nil;
- }
-}
-
-void
-winwritebody(Window *w, char *s, int n)
-{
- if(w->body == nil)
- winopenbody(w, OWRITE);
- if(Bwrite(w->body, s, n) != n)
- error("write error to window: %r");
-}
-
-int
-wingetec(Window *w)
-{
- if(w->nbuf == 0){
- w->nbuf = read(w->event, w->buf, sizeof w->buf);
- if(w->nbuf <= 0){
- /* probably because window has exited, and only called by wineventproc, so just shut down */
- threadexits(nil);
- }
- w->bufp = w->buf;
- }
- w->nbuf--;
- return *w->bufp++;
-}
-
-int
-wingeten(Window *w)
-{
- int n, c;
-
- n = 0;
- while('0'<=(c=wingetec(w)) && c<='9')
- n = n*10+(c-'0');
- if(c != ' ')
- error("event number syntax");
- return n;
-}
-
-int
-wingeter(Window *w, char *buf, int *nb)
-{
- Rune r;
- int n;
-
- r = wingetec(w);
- buf[0] = r;
- n = 1;
- if(r >= Runeself) {
- while(!fullrune(buf, n))
- buf[n++] = wingetec(w);
- chartorune(&r, buf);
- }
- *nb = n;
- return r;
-}
-
-void
-wingetevent(Window *w, Event *e)
-{
- int i, nb;
-
- e->c1 = wingetec(w);
- e->c2 = wingetec(w);
- e->q0 = wingeten(w);
- e->q1 = wingeten(w);
- e->flag = wingeten(w);
- e->nr = wingeten(w);
- if(e->nr > EVENTSIZE)
- error("event string too long");
- e->nb = 0;
- for(i=0; i<e->nr; i++){
- e->r[i] = wingeter(w, e->b+e->nb, &nb);
- e->nb += nb;
- }
- e->r[e->nr] = 0;
- e->b[e->nb] = 0;
- if(wingetec(w) != '\n')
- error("event syntax error");
-}
-
-void
-winwriteevent(Window *w, Event *e)
-{
- fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
-}
-
-void
-winread(Window *w, uint q0, uint q1, char *data)
-{
- int m, n, nr;
- char buf[256];
-
- if(w->addr < 0)
- w->addr = winopenfile(w, "addr");
- if(w->data < 0)
- w->data = winopenfile(w, "data");
- m = q0;
- while(m < q1){
- n = sprint(buf, "#%d", m);
- if(write(w->addr, buf, n) != n)
- error("error writing addr: %r");
- n = read(w->data, buf, sizeof buf);
- if(n <= 0)
- error("reading data: %r");
- nr = utfnlen(buf, n);
- while(m+nr >q1){
- do; while(n>0 && (buf[--n]&0xC0)==0x80);
- --nr;
- }
- if(n == 0)
- break;
- memmove(data, buf, n);
- data += n;
- *data = 0;
- m += nr;
- }
-}
-
-void
-windormant(Window *w)
-{
- if(w->addr >= 0){
- close(w->addr);
- w->addr = -1;
- }
- if(w->body != nil){
- Bterm(w->body);
- w->body = nil;
- }
- if(w->data >= 0){
- close(w->data);
- w->data = -1;
- }
-}
-
-
-int
-windel(Window *w, int sure)
-{
- if(sure)
- write(w->ctl, "delete\n", 7);
- else if(write(w->ctl, "del\n", 4) != 4)
- return 0;
- /* event proc will die due to read error from event file */
- windormant(w);
- close(w->ctl);
- w->ctl = -1;
- close(w->event);
- w->event = -1;
- return 1;
-}
-
-void
-winclean(Window *w)
-{
- if(w->body)
- Bflush(w->body);
- ctlprint(w->ctl, "clean\n");
-}
-
-int
-winsetaddr(Window *w, char *addr, int errok)
-{
- if(w->addr < 0)
- w->addr = winopenfile(w, "addr");
- if(write(w->addr, addr, strlen(addr)) < 0){
- if(!errok)
- error("error writing addr(%s): %r", addr);
- return 0;
- }
- return 1;
-}
-
-int
-winselect(Window *w, char *addr, int errok)
-{
- if(winsetaddr(w, addr, errok)){
- ctlprint(w->ctl, "dot=addr\n");
- return 1;
- }
- return 0;
-}
-
-char*
-winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
-{
- char *s;
- int m, na, n;
-
- if(w->body != nil)
- winclosebody(w);
- winopenbody(w, OREAD);
- s = nil;
- na = 0;
- n = 0;
- for(;;){
- if(na < n+512){
- na += 1024;
- s = realloc(s, na+1);
- }
- m = Bread(w->body, s+n, na-n);
- if(m <= 0)
- break;
- n += m;
- }
- s[n] = 0;
- winclosebody(w);
- *np = n;
- return s;
-}
-
-char*
-winselection(Window *w)
-{
- int fd, m, n;
- char *buf;
- char tmp[256];
-
- fd = winopenfile1(w, "rdsel", OREAD);
- if(fd < 0)
- error("can't open rdsel: %r");
- n = 0;
- buf = nil;
- for(;;){
- m = read(fd, tmp, sizeof tmp);
- if(m <= 0)
- break;
- buf = erealloc(buf, n+m+1);
- memmove(buf+n, tmp, m);
- n += m;
- buf[n] = '\0';
- }
- close(fd);
- return buf;
-}
--- a/acme/mkfile
+++ b/acme/mkfile
@@ -5,6 +5,5 @@
all install clean nuke installall update:V:
@{cd bin/source; mk $target}
- @{cd mail/src; mk $target}
@{cd news/src; mk $target}
@{cd wiki/src; mk $target}
--- a/sys/doc/mkfile
+++ b/sys/doc/mkfile
@@ -40,6 +40,7 @@
spin\
port\
colophon\
+ nupas/nupas\
ALLPS=${ALL:%=%.ps}
HTML=${ALL:%=%.html} release3.html release4.html
--- /dev/null
+++ b/sys/doc/nupas/macros.ms
@@ -1,0 +1,92 @@
+.de F1
+.nr OI \\n(.iu
+.nr PW 1v
+.KF
+.sp 0.3v
+..
+.de T1
+.F1
+..
+.de F2
+.ds Fp Figure\ \\n(Fi
+.ds Fn Figure\ \\n+(Fi
+.ds Fq \\*(Fp
+.F0
+..
+.de T2
+.ds Tp Table\ \\n(Ti
+.ds Tn Table\ \\n+(Ti
+.ds Tq \\*(Tp
+.T0
+..
+.de F0
+.nr BD 1
+.if t .ps \\n(PS-1
+.ie \\n(VS>=41 .vs \\n(VSu-1p
+.el .vs \\n(VSp-1p
+.ft 1
+.di DD
+.ll \\n(.lu*3u/4u
+.in 0
+.fi
+.ad b
+.sp 0.5v
+\f3\\*(Fq\f1\ \ \c
+..
+.de T0
+.nr BD 1
+.if t .ps \\n(PS-1
+.ie \\n(VS>=41 .vs \\n(VSu-1p
+.el .vs \\n(VSp-1p
+.ft 1
+.di DD
+.ll \\n(.lu*3u/4u
+.in 0
+.fi
+.ad b
+.sp 0.5v
+\f3\\*(Tq\f1\ \ \c
+..
+.de F3
+.sp 0.5v
+.di
+.br
+.ll \\n(.lu*4u/3u
+.if \\n(dl>\\n(BD .nr BD \\n(dl
+.if \\n(BD<\\n(.l .in (\\n(.lu-\\n(BDu)/2u
+.nf
+.DD
+.in \\n(OIu
+.nr BD 0
+.fi
+.KE
+.ie \\n(VS>=41 .vs \\n(VSu
+.el .vs \\n(VSp
+..
+.de T3
+.F3
+..
+.de EX
+.P1
+\s-4
+..
+.de EE
+\s+4
+.P2
+..
+.nr Fi 1 +1
+.nr Ti 1 +1
+.ds Fn Figure\ \\n(Fi
+.ds Tn Table\ \\n(Ti
+.nr XP 2 \" delta point size for program
+.nr XV 2p \" delta vertical for programs
+.nr XT 4 \" delta tab stop for programs
+.nr DV .5v \" space before start of program
+.FP lucidasans
+.nr PS 11
+.nr VS 13
+.nr LL 6.6i
+.nr PI 0 \" paragraph indent
+.nr PD 4p \" extra space between paragraphs
+.pl 11i
+.rm CH
--- /dev/null
+++ b/sys/doc/nupas/mkfile
@@ -1,0 +1,20 @@
+TARG=nupas.ps nupas.pdf
+
+all:V: $TARG
+
+%.ps:DQ: %.ms
+ eval `{doctype macros.ms $stem.ms} | \
+ lp -m.9 -dstdout >$target
+
+%.pdf:DQ: %.ps
+ cat ../docfonts $stem.ps >_$stem.ps
+ ps2pdf _$stem.ps $stem.pdf && rm -f _$stem.ps
+
+%.show:VQ: %.ps
+ page -w $stem.ps
+
+#install:VQ: fs4.man fs.man fsrecover.man fsconfig.man
+# cp fs4.man /sys/man/4/fs
+# cp fs.man /sys/man/8/fs
+# cp fsconfig.man /sys/man/8/fsconfig
+# cp fsrecover.man /sys/man/8/fsrecover
--- /dev/null
+++ b/sys/doc/nupas/nupas.ms
@@ -1,0 +1,354 @@
+.EQ
+delim $$
+.EN
+.TL
+Scaling Upas
+.AU
+Erik Quanstrom
[email protected]
+.AB
+The Plan 9 email system, Upas, uses traditional methods of delivery to
+.UX
+mail boxes while using a user-level file system, Upas/fs, to
+translate mail boxes of various formats into a single, convenient format for access.
+Unfortunately, it does not do so efficiently. Upas/fs
+reads entire folders into core. When deleting email from mail boxes,
+the entire mail box is rewritten. I describe how Upas/fs has been
+adapted to use caching, indexing and a new mail box format (mdir) to
+limit I/O, reduce core size and eliminate the need to rewrite mail
+boxes.
+.AE
+.NH
+Introduction
+.LP
+.DS I
+Chained at his root two scion demons dwell
+.br
+ – Erasmus Darwin, The Botanic Garden
+.DE
+.LP
+At Coraid, email is the largest resource user in the system by orders
+of magnitude. As of July, 2007, rewriting mail boxes was using
+300MB/day on the WORM and several users required more than 400MB of
+core. As of July, 2008, rewriting mail boxes was using 800MB/day on
+the WORM and several users required more than 1.2GB of core to read
+email. Clearly these are difficult to sustain levels of growth, even
+without growth of the company. We needed to limit the amount of disk
+space used and, more urgently, reduce Upas/fs' core size.
+.LP
+The techniques employed are simple. Mail is now stored in a directory
+with one message per file. This eliminates the need to rewrite mail
+boxes. Upas/fs now maintains an index which allows it to present
+complete message summaries without reading indexed messages.
+Combining the two techniques allows Upas/fs to read only new or
+referenced messages. Finally, caching limits both the total number of
+in-core messages and their total size.
+.NH
+Mdir Format
+.LP
+In addition to meeting our urgent operational requirements of reducing
+memory and disk footprint, to meet the expectations of our users we
+require a solution that is able to handle folders up to ten thousand
+messages, open folders quickly, list the contents of folders quickly
+and support the current set of mail readers.
+.LP
+There are several potential styles of mail boxes. The maildir[1] format
+has some attractive properties. Mail can be delivered to or deleted
+from a mail box without locking. New mail or deleted mail may be
+detected with a directory scan. When used with WORM storage, the
+amount of storage required is no more than the size of new mail
+received. Mbox format can require that a new copy of the inbox be
+stored every day. Even with storage that coalesces duplicate blocks
+such as Venti, deleting a message will generally require new storage
+since messages are not disk-block aligned. Maildir does not reduce
+the cost of the common task of a summary listing of mail such as
+generated by acme Mail.
+.LP
+The mails[2] format proposes a directory per mail. A copy of
+the mail as delivered is stored and each mime part is decoded
+in such a way that a mail reader could display the file directly.
+Command line tools in the style of MH[3] are used to display and
+process mail. Upas/fs is not necessary for reading local mail.
+Mails has the potential to reduce memory footprint below that
+offered by mdirs for native email reading. However all of the
+largest mail boxes at our site are served exclusively through IMAP.
+The preformatting by mails would be unnecessary for such accounts.
+.LP
+Other mail servers such as Lotus Notes[4] store email in a custom
+database format which allows for fielded and full-text searching
+of mail folders. Such a format provides very quick mail
+listings and good search capabilities. Such a solution would not
+lend itself well to a tool-based environment, nor would it be simple.
+.LP
+Maildir format seemed the best basic format but its particulars are
+tied to the
+.UX
+environment; mdir is a descendant. A mdir folder
+is a directory with the name of the folder. Messages in the mdir
+folder are stored in a file named
+.I "utime.seq" .
+.I Utime
+is defined as the decimal
+.UX
+seconds when the message was added to
+the folder. For the inbox, this time will correspond to the
+.UX
+“From ” line.
+.I Seq
+is a two-digit sequence number starting with
+.CW "00."
+The lowest available sequence number is used to store the message.
+Thus, the first email possible would be named
+.CW "0.00."
+To prevent accidents, message files are stored with
+the append-only and exclusive-access bits turned on.
+The message is stored in the same format it would be in mbox
+format; each message is a valid mbox folder with a single message.
+.NH
+Indexing
+.LP
+When upas/fs finds an unindexed message, it is added to the index.
+The index is a file named
+.I "foldername" .idx
+and consists a signature and one line per MIME part. Each line
+contains the SHA1 checksum of the message (or a place holder for
+subparts), one field per entry in the
+.I "messageid/info"
+file, flags and the number of subparts. The flags are currently a
+superset of the standard IMAP flags. They provide the similar
+functionality to maildir's modified file names. Thus the `S'
+(answered) flag remains set between invocations of mail readers.
+Other mutable information about a message may be stored in a similar
+way.
+.LP
+Since the
+.I info
+file is read by all the mail readers to produce mail listings,
+mail boxes may be listed without opening any mail files when no new
+mail has arrived. Similarly, opening a new mail box requires reading
+the index and checking new mail. Index files are typically between
+0.5% and 5% the size of the full mail box. Each time the index is
+generated, it is fully rewritten.
+.NH
+Caching
+.LP
+Upas/fs stores each message in a
+.CW "Message"
+structure. To enable caching, this structure was split
+into four parts: The
+.CW "Idx"
+(or index), message subparts, information on the cache state of the
+message and a set of pointers into the processed header and body.
+Only the pointers to the processed header and body are subject to
+caching. The available cache states are
+.CW "Cidx" ,
+.CW "Cheader"
+and
+.CW "Cbody" .
+.LP
+When the header and body are not present, the average message with
+subparts takes roughly 3KB of memory. Thus a 10,000 message mail box
+would require roughly 30MB of core in addition to any cached
+messages. Reads of the
+.CW "info"
+or
+.CW "subject"
+files can be satisfied from the information in the
+.CW "Idx"
+structure.
+.LP
+Since there are a fair number of very large messages, requests that
+can be satisfied by reading the message headers do not result in the
+full message being read. Reads of the
+.CW "header"
+or
+.CW "rawheader"
+files of top-level messages are satisfied in this way. Reading the
+same files for subparts, however, results in the entire message being
+read. Caching the header results in the
+.CW "Cheader"
+cache state.
+.LP
+Similarly, reading the
+.CW "body"
+requires the body to be read, processed and results in
+the
+.CW "Cbody"
+cache state. Reading from MIME subparts also results
+in the
+.CW "Cbody"
+cache state.
+.LP
+The cache has a simple LRU replacement policy. Each time a cached
+member of a message is accessed, it is moved to the head of the list.
+The cache contains a maximum number of messages and a maximum size.
+While the maximum number of messages may not be exceeded, the maximum
+cache size may be exceeded if the sum of all the currently referenced
+messages is greater than the size of the cache. In this case all
+unreferenced messages will be freed. When removing a message
+from the cache all of the cacheable information is freed.
+.NH
+Collateral damage
+.LP
+.DS I
+Each new user of a new system uncovers a new class of bugs.
+.br
+ — Brian Kernighan
+.DE
+.LP
+In addition to upas/fs, programs that have assumptions about how
+mail boxes are structured needed to be modified. Programs which
+deliver mail to mail boxes (deliver, marshal, ml, smtp) and append messages to
+folders were given a common (nedmail) function to call. Since this
+was done by modifying functions in the Upas common library, this
+presented a problem for programs not traditionally part of Upas
+such as acme Mail and imap4d. Rather than fold these programs
+into Upas, a new program, mbappend, was added to Upas.
+.LP
+Imap4d also requires the ability to rename and remove folders.
+While an external program would work for this as well, that
+approach has some drawbacks. Most importantly, IMAP folders
+can't be moved or renamed in the same way without reimplementing
+functionality that is already in upas/fs. It also emphasises the
+asymmetry between reading and deleting email and other folder
+actions. Folder renaming and removal were added to upas/fs.
+It is intended that mbappend will be removed soon
+and replaced with equivalent upas/fs functionality —
+at least for non-delivery programs.
+.LP
+Mdirs also expose an oddity about file permissions. An append-only
+file that is mode
+.CW 0622
+may be appended to by anyone, but is readable only by the owner.
+With a directory, such a setup is not directly possible as write permission
+to a directory implies permission to remove. There are a number of
+solutions to this problem. Delivery could be made asymmetrical—incoming
+files could be written to a mbox. Or, following the example of the outbound
+mail queue, each user could deliver to a directory owned by that user.
+In many BSD-derived
+.UX
+systems, the “sticky bit” on directories is used to modify
+the meaning of the
+.CW w
+bit for users matching only the other bits. For them, the
+.CW w
+bit gives permission to create but not to remove.
+.LP
+While this is somewhat of a one-off situation, I chose to implement
+a version of the “sticky bit” using the existing append-only bit on our
+file server. This was implemented as an extra permission check when
+removing files. Fewer than 10 lines of code were required.
+.NH
+Performance
+.LP
+A representative local mail box was used to generate some rough
+performance numbers. The mail box is 110MB and contains 868 messages.
+These figures are shown in table 1. In the worse case—an unindexed
+mail box—the new upas/fs uses 18% of the memory of the original while
+using 13% more cpu. In the best case, it uses only 5% of the memory
+while using only 13% of the cpu. Clearly, a larger mail box will make
+these ratios more attractive. In the two months since the snapshot was
+taken, that same mail box has grown to 220MB and contains 1814
+messages.
+.ps -2
+.DS C
+.TS
+box, tab(:);
+c s s s s
+c | c | c | c | c
+a | n | n | n | n.
+Table 1 – Performance
+_
+action:user:system:real:core size:
+:s:s:s:MB:
+_
+old fs read:1.69:0.84:6.07:135
+_
+initial read:1.65:0.90:6.90:25
+_
+indexed read:0.64:0.03:0.77:6.5
+.TE
+.DE
+.NL
+.NH
+Future Work
+.LP
+While Upas' memory usage has been drastically reduced,
+it is still a work-in-progress. Caching and indexing are
+adequate but primitive. Upas/fs is still inconsistently
+bypassed for appending messages to mail boxes. There
+are also some features which remain incomplete. Finally,
+the small increase in scale brings some new questions about
+the organization of email.
+.LP
+It may be useful for mail boxes with very large numbers
+of messages to divide the index into fixed-size chunks.
+Then messages could be read into a fixed-sized pool of
+structures as needed. However it is currently hard to
+see how clients could easily interface a mail box large
+enough for this technique to be useful. Currently, all
+clients assume that it is reasonable to allocate an
+in-core data structure for each message in a mail box.
+To take advantage of a chunked index, clients (or the
+server) would need a way of limiting the number of
+messages considered at a time. Also, for such large
+mail boxes, it would be important to separate the
+incoming messages from older messages to limit the work
+required to scan for new messages.
+.LP
+Caching is particularly unsatisfactory. Files should
+be read in fixed-sized buffers so maximum memory usage
+does not depend on the size of the largest file in the
+mail box. Unfortunately, current data structures do not readily
+support this. In practice, this limitation has not yet
+been noticeable.
+.LP
+There are also a few features that need to be completed.
+Tracking of references has been added to marshal and
+upas/fs. In addition, the index provides a place to store
+mutable information about a message. These capabilities
+should be built upon to provide general threading and
+tagging capabilities.
+.NH
+Speculation
+.LP
+Freed from the limitation that all messages in a
+mail box must be read and stored in memory before a
+single message may be accessed, it is interesting to
+speculate on a few further possibilites.
+.LP
+For example, it may be
+useful to replace separate mail boxes with a single
+collection of messages assigned to one or more virtual
+mail boxes. The association between a message and a
+mail box would be a “tag.” A message could be added to
+or removed from one or more mail boxes without modifying
+the mdir file. If threads were implemented by tagging
+each message with its references, it would be possible
+to follow threads across mail boxes, even to messages
+removed from all mail boxes, provided the underlying
+file were not also removed. If a facility for adding
+arbitrary, automatic tags were enabled, it would be
+possible to tag messages with the email address in
+the SMTP From line.
+.NH
+References
+.IP [1]
+D. Bernstein, “Using maildir format”,
+published online at
+.br
+http://cr.yp.to/proto/maildir.html
+.IP [2]
+F. Ballesteros
+.IR mails (1),
+published online at
+http://lsub.org/magic/man2html/1/mails
+.IP [3]
+MH Wikipedia entry,
+http://en.wikipedia.org/wiki/MH_Message_Handling_System
+.IP [4]
+Lotus Notes Wikipedia entry,
+http://en.wikipedia.org/wiki/Lotus_Notes
+.IP [5]
+D. Presotto, “Upas—a Simpler Approach to Network Mail”,
+Proceedings of the 10th Usenix conference, 1985.
--- a/sys/man/8/pop3
+++ b/sys/man/8/pop3
@@ -20,7 +20,7 @@
.B -p
]
.PP
-.B ip/imap4d
+.B upas/imap4d
.RB [ -acpv ]
.RB [ -d
.IR smtpdomain ]
@@ -142,7 +142,7 @@
.SH SOURCE
.B /sys/src/cmd/upas/pop3
.br
-.B /sys/src/cmd/ip/imap4d
+.B /sys/src/cmd/upas/imap4d
.SH "SEE ALSO"
.IR aliasmail (8),
.IR faces (1),
--- a/sys/src/cmd/ip/imap4d/auth.c
+++ /dev/null
@@ -1,184 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <auth.h>
-#include <libsec.h>
-#include <bio.h>
-#include "imap4d.h"
-
-/*
- * hack to allow smtp forwarding.
- * hide the peer IP address under a rock in the ratifier FS.
- */
-void
-enableForwarding(void)
-{
- char buf[64], peer[64], *p;
- static ulong last;
- ulong now;
- int fd;
-
- if(remote == nil)
- return;
-
- now = time(0);
- if(now < last + 5*60)
- return;
- last = now;
-
- fd = open("/srv/ratify", ORDWR);
- if(fd < 0)
- return;
- if(!mount(fd, -1, "/mail/ratify", MBEFORE, "")){
- close(fd);
- return;
- }
- close(fd);
-
- strncpy(peer, remote, sizeof(peer));
- peer[sizeof(peer) - 1] = '\0';
- p = strchr(peer, '!');
- if(p != nil)
- *p = '\0';
-
- snprint(buf, sizeof(buf), "/mail/ratify/trusted/%s#32", peer);
-
- /*
- * if the address is already there and the user owns it,
- * remove it and recreate it to give him a new time quanta.
- */
- if(access(buf, 0) >= 0 && remove(buf) < 0)
- return;
-
- fd = create(buf, OREAD, 0666);
- if(fd >= 0)
- close(fd);
-}
-
-void
-setupuser(AuthInfo *ai)
-{
- Waitmsg *w;
- int pid;
-
- if(ai){
- strecpy(username, username+sizeof username, ai->cuid);
-
- if(auth_chuid(ai, nil) < 0)
- bye("user auth failed: %r");
- auth_freeAI(ai);
- }else
- strecpy(username, username+sizeof username, getuser());
-
- if(newns(username, 0) < 0)
- bye("user login failed: %r");
-
- /*
- * hack to allow access to outgoing smtp forwarding
- */
- enableForwarding();
-
- snprint(mboxDir, MboxNameLen, "/mail/box/%s", username);
- if(myChdir(mboxDir) < 0)
- bye("can't open user's mailbox");
-
- switch(pid = fork()){
- case -1:
- bye("can't initialize mail system");
- break;
- case 0:
- execl("/bin/upas/fs", "upas/fs", "-np", nil);
-_exits("rob1");
- _exits(0);
- break;
- default:
- break;
- }
- if((w=wait()) == nil || w->pid != pid || w->msg[0] != '\0')
- bye("can't initialize mail system");
- free(w);
-}
-
-static char*
-authresp(void)
-{
- char *s, *t;
- int n;
-
- t = Brdline(&bin, '\n');
- n = Blinelen(&bin);
- if(n < 2)
- return nil;
- n--;
- if(t[n-1] == '\r')
- n--;
- t[n] = '\0';
- if(n == 0 || strcmp(t, "*") == 0)
- return nil;
-
- s = binalloc(&parseBin, n + 1, 0);
- n = dec64((uchar*)s, n, t, n);
- s[n] = '\0';
- return s;
-}
-
-/*
- * rfc 2195 cram-md5 authentication
- */
-char*
-cramauth(void)
-{
- AuthInfo *ai;
- Chalstate *cs;
- char *s, *t;
-
- if((cs = auth_challenge("proto=cram role=server")) == nil)
- return "couldn't get cram challenge";
-
- Bprint(&bout, "+ %.*[\r\n", cs->nchal, cs->chal);
- if(Bflush(&bout) < 0)
- writeErr();
-
- s = authresp();
- if(s == nil)
- return "client cancelled authentication";
-
- t = strchr(s, ' ');
- if(t == nil)
- bye("bad auth response");
- *t++ = '\0';
- strncpy(username, s, UserNameLen);
- username[UserNameLen-1] = '\0';
-
- cs->user = username;
- cs->resp = t;
- cs->nresp = strlen(t);
- if((ai = auth_response(cs)) == nil)
- return "login failed";
- auth_freechal(cs);
- setupuser(ai);
- return nil;
-}
-
-AuthInfo*
-passLogin(char *user, char *secret)
-{
- AuthInfo *ai;
- Chalstate *cs;
- uchar digest[MD5dlen];
- char response[2*MD5dlen+1];
-
- if((cs = auth_challenge("proto=cram role=server")) == nil)
- return nil;
-
- hmac_md5((uchar*)cs->chal, strlen(cs->chal),
- (uchar*)secret, strlen(secret), digest,
- nil);
- snprint(response, sizeof(response), "%.*H", MD5dlen, digest);
-
- cs->user = user;
- cs->resp = response;
- cs->nresp = strlen(response);
- ai = auth_response(cs);
- auth_freechal(cs);
- return ai;
-}
--- a/sys/src/cmd/ip/imap4d/copy.c
+++ /dev/null
@@ -1,259 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include <libsec.h>
-#include "imap4d.h"
-
-static int saveMsg(char *dst, char *digest, int flags, char *head, int nhead, Biobuf *b, long n);
-static int saveb(int fd, DigestState *dstate, char *buf, int nr, int nw);
-static long appSpool(Biobuf *bout, Biobuf *bin, long n);
-
-/*
- * check if the message exists
- */
-int
-copyCheck(Box *box, Msg *m, int uids, void *v)
-{
- int fd;
-
- USED(box);
- USED(uids);
- USED(v);
-
- if(m->expunged)
- return 0;
- fd = msgFile(m, "raw");
- if(fd < 0){
- msgDead(m);
- return 0;
- }
- close(fd);
- return 1;
-}
-
-int
-copySave(Box *box, Msg *m, int uids, void *vs)
-{
- Dir *d;
- Biobuf b;
- vlong length;
- char *head;
- int ok, hfd, bfd, nhead;
-
- USED(box);
- USED(uids);
-
- if(m->expunged)
- return 0;
-
- hfd = msgFile(m, "unixheader");
- if(hfd < 0){
- msgDead(m);
- return 0;
- }
- head = readFile(hfd);
- if(head == nil){
- close(hfd);
- return 0;
- }
-
- /*
- * clear out the header if it doesn't end in a newline,
- * since it is a runt and the "header" will show up in the raw file.
- */
- nhead = strlen(head);
- if(nhead > 0 && head[nhead-1] != '\n')
- nhead = 0;
-
- bfd = msgFile(m, "raw");
- close(hfd);
- if(bfd < 0){
- msgDead(m);
- return 0;
- }
-
- d = dirfstat(bfd);
- if(d == nil){
- close(bfd);
- return 0;
- }
- length = d->length;
- free(d);
-
- Binit(&b, bfd, OREAD);
- ok = saveMsg(vs, m->info[IDigest], m->flags, head, nhead, &b, length);
- Bterm(&b);
- close(bfd);
- return ok;
-}
-
-/*
- * first spool the input into a temorary file,
- * and massage the input in the process.
- * then save to real box.
- */
-int
-appendSave(char *mbox, int flags, char *head, Biobuf *b, long n)
-{
- Biobuf btmp;
- int fd, ok;
-
- fd = imapTmp();
- if(fd < 0)
- return 0;
- Bprint(&bout, "+ Ready for literal data\r\n");
- if(Bflush(&bout) < 0)
- writeErr();
- Binit(&btmp, fd, OWRITE);
- n = appSpool(&btmp, b, n);
- Bterm(&btmp);
- if(n < 0){
- close(fd);
- return 0;
- }
-
- seek(fd, 0, 0);
- Binit(&btmp, fd, OREAD);
- ok = saveMsg(mbox, nil, flags, head, strlen(head), &btmp, n);
- Bterm(&btmp);
- close(fd);
- return ok;
-}
-
-/*
- * copy from bin to bout,
- * mapping "\r\n" to "\n" and "\nFrom " to "\n From "
- * return the number of bytes in the mapped file.
- *
- * exactly n bytes must be read from the input,
- * unless an input error occurs.
- */
-static long
-appSpool(Biobuf *bout, Biobuf *bin, long n)
-{
- int i, c;
-
- c = '\n';
- while(n > 0){
- if(c == '\n' && n >= STRLEN("From ")){
- for(i = 0; i < STRLEN("From "); i++){
- c = Bgetc(bin);
- if(c != "From "[i]){
- if(c < 0)
- return -1;
- Bungetc(bin);
- break;
- }
- n--;
- }
- if(i == STRLEN("From "))
- Bputc(bout, ' ');
- Bwrite(bout, "From ", i);
- }
- c = Bgetc(bin);
- n--;
- if(c == '\r' && n-- > 0){
- c = Bgetc(bin);
- if(c != '\n')
- Bputc(bout, '\r');
- }
- if(c < 0)
- return -1;
- if(Bputc(bout, c) < 0)
- return -1;
- }
- if(c != '\n')
- Bputc(bout, '\n');
- if(Bflush(bout) < 0)
- return -1;
- return Boffset(bout);
-}
-
-static int
-saveMsg(char *dst, char *digest, int flags, char *head, int nhead, Biobuf *b, long n)
-{
- DigestState *dstate;
- MbLock *ml;
- uchar shadig[SHA1dlen];
- char buf[BufSize + 1], digbuf[NDigest + 1];
- int i, fd, nr, nw, ok;
-
- ml = mbLock();
- if(ml == nil)
- return 0;
- fd = openLocked(mboxDir, dst, OWRITE);
- if(fd < 0){
- mbUnlock(ml);
- return 0;
- }
- seek(fd, 0, 2);
-
- dstate = nil;
- if(digest == nil)
- dstate = sha1(nil, 0, nil, nil);
- if(!saveb(fd, dstate, head, nhead, nhead)){
- if(dstate != nil)
- sha1(nil, 0, shadig, dstate);
- mbUnlock(ml);
- close(fd);
- return 0;
- }
- ok = 1;
- if(n == 0)
- ok = saveb(fd, dstate, "\n", 0, 1);
- while(n > 0){
- nr = n;
- if(nr > BufSize)
- nr = BufSize;
- nr = Bread(b, buf, nr);
- if(nr <= 0){
- saveb(fd, dstate, "\n\n", 0, 2);
- ok = 0;
- break;
- }
- n -= nr;
- nw = nr;
- if(n == 0){
- if(buf[nw - 1] != '\n')
- buf[nw++] = '\n';
- buf[nw++] = '\n';
- }
- if(!saveb(fd, dstate, buf, nr, nw)){
- ok = 0;
- break;
- }
- mbLockRefresh(ml);
- }
- close(fd);
-
- if(dstate != nil){
- digest = digbuf;
- sha1(nil, 0, shadig, dstate);
- for(i = 0; i < SHA1dlen; i++)
- snprint(digest+2*i, NDigest+1-2*i, "%2.2ux", shadig[i]);
- }
- if(ok){
- fd = cdOpen(mboxDir, impName(dst), OWRITE);
- if(fd < 0)
- fd = emptyImp(dst);
- if(fd >= 0){
- seek(fd, 0, 2);
- wrImpFlags(buf, flags, 1);
- fprint(fd, "%.*s %.*lud %s\n", NDigest, digest, NUid, 0UL, buf);
- close(fd);
- }
- }
- mbUnlock(ml);
- return 1;
-}
-
-static int
-saveb(int fd, DigestState *dstate, char *buf, int nr, int nw)
-{
- if(dstate != nil)
- sha1((uchar*)buf, nr, nil, dstate);
- if(write(fd, buf, nw) != nw)
- return 0;
- return 1;
-}
--- a/sys/src/cmd/ip/imap4d/csquery.c
+++ /dev/null
@@ -1,44 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-/*
- * query the connection server
- */
-char*
-csquery(char *attr, char *val, char *rattr)
-{
- char token[64+4];
- char buf[256], *p, *sp;
- int fd, n;
-
- if(val == nil || val[0] == 0)
- return nil;
- fd = open("/net/cs", ORDWR);
- if(fd < 0)
- return nil;
- fprint(fd, "!%s=%s", attr, val);
- seek(fd, 0, 0);
- snprint(token, sizeof(token), "%s=", rattr);
- for(;;){
- n = read(fd, buf, sizeof(buf)-1);
- if(n <= 0)
- break;
- buf[n] = 0;
- p = strstr(buf, token);
- if(p != nil && (p == buf || *(p-1) == 0)){
- close(fd);
- sp = strchr(p, ' ');
- if(sp)
- *sp = 0;
- p = strchr(p, '=');
- if(p == nil)
- return nil;
- return strdup(p+1);
- }
- }
- close(fd);
- return nil;
-}
--- a/sys/src/cmd/ip/imap4d/date.c
+++ /dev/null
@@ -1,308 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-char *
-wdayname[7] =
-{
- "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-
-char *
-monname[12] =
-{
- "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-static void time2tm(Tm *tm, char *s);
-static void zone2tm(Tm *tm, char *s);
-static int dateindex(char *d, char **tab, int n);
-
-int
-rfc822date(char *s, int n, Tm *tm)
-{
- char *plus;
- int m;
-
- plus = "+";
- if(tm->tzoff < 0)
- plus = "";
- m = 0;
- if(0 <= tm->wday && tm->wday < 7){
- m = snprint(s, n, "%s, ", wdayname[tm->wday]);
- if(m < 0)
- return m;
- }
- return snprint(s+m, n-m, "%.2d %s %.4d %.2d:%.2d:%.2d %s%.4d",
- tm->mday, monname[tm->mon], tm->year+1900, tm->hour, tm->min, tm->sec,
- plus, (tm->tzoff/3600)*100 + (tm->tzoff/60)%60);
-}
-
-int
-imap4date(char *s, int n, Tm *tm)
-{
- char *plus;
-
- plus = "+";
- if(tm->tzoff < 0)
- plus = "";
- return snprint(s, n, "%2d-%s-%.4d %2.2d:%2.2d:%2.2d %s%4.4d",
- tm->mday, monname[tm->mon], tm->year+1900, tm->hour, tm->min, tm->sec, plus, (tm->tzoff/3600)*100 + (tm->tzoff/60)%60);
-}
-
-int
-imap4Date(Tm *tm, char *date)
-{
- char *flds[4];
-
- if(getfields(date, flds, 3, 0, "-") != 3)
- return 0;
-
- tm->mday = strtol(flds[0], nil, 10);
- tm->mon = dateindex(flds[1], monname, 12);
- tm->year = strtol(flds[2], nil, 10) - 1900;
- return 1;
-}
-
-/*
- * parse imap4 dates
- */
-ulong
-imap4DateTime(char *date)
-{
- Tm tm;
- char *flds[4], *sflds[4];
- ulong t;
-
- if(getfields(date, flds, 4, 0, " ") != 3)
- return ~0;
-
- if(!imap4Date(&tm, flds[0]))
- return ~0;
-
- if(getfields(flds[1], sflds, 3, 0, ":") != 3)
- return ~0;
-
- tm.hour = strtol(sflds[0], nil, 10);
- tm.min = strtol(sflds[1], nil, 10);
- tm.sec = strtol(sflds[2], nil, 10);
-
- strcpy(tm.zone, "GMT");
- tm.yday = 0;
- t = tm2sec(&tm);
- zone2tm(&tm, flds[2]);
- t -= tm.tzoff;
- return t;
-}
-
-/*
- * parse dates of formats
- * [Wkd[,]] DD Mon YYYY HH:MM:SS zone
- * [Wkd] Mon ( D|DD) HH:MM:SS zone YYYY
- * plus anything similar
- * return nil for a failure
- */
-Tm*
-date2tm(Tm *tm, char *date)
-{
- Tm gmt, *atm;
- char *flds[7], *s, dstr[64];
- int n;
-
- /*
- * default date is Thu Jan 1 00:00:00 GMT 1970
- */
- tm->wday = 4;
- tm->mday = 1;
- tm->mon = 1;
- tm->hour = 0;
- tm->min = 0;
- tm->sec = 0;
- tm->year = 70;
- strcpy(tm->zone, "GMT");
- tm->tzoff = 0;
-
- strncpy(dstr, date, sizeof(dstr));
- dstr[sizeof(dstr)-1] = '\0';
- n = tokenize(dstr, flds, 7);
- if(n != 6 && n != 5)
- return nil;
-
- if(n == 5){
- for(n = 5; n >= 1; n--)
- flds[n] = flds[n - 1];
- n = 5;
- }else{
- /*
- * Wday[,]
- */
- s = strchr(flds[0], ',');
- if(s != nil)
- *s = '\0';
- tm->wday = dateindex(flds[0], wdayname, 7);
- if(tm->wday < 0)
- return nil;
- }
-
- /*
- * check for the two major formats:
- * Month first or day first
- */
- tm->mon = dateindex(flds[1], monname, 12);
- if(tm->mon >= 0){
- tm->mday = strtoul(flds[2], nil, 10);
- time2tm(tm, flds[3]);
- zone2tm(tm, flds[4]);
- tm->year = strtoul(flds[5], nil, 10);
- if(strlen(flds[5]) > 2)
- tm->year -= 1900;
- }else{
- tm->mday = strtoul(flds[1], nil, 10);
- tm->mon = dateindex(flds[2], monname, 12);
- tm->year = strtoul(flds[3], nil, 10);
- if(strlen(flds[3]) > 2)
- tm->year -= 1900;
- time2tm(tm, flds[4]);
- zone2tm(tm, flds[5]);
- }
-
- if(n == 5){
- gmt = *tm;
- strncpy(gmt.zone, "", 4);
- gmt.tzoff = 0;
- atm = gmtime(tm2sec(&gmt));
- tm->wday = atm->wday;
- }else{
- /*
- * Wday[,]
- */
- s = strchr(flds[0], ',');
- if(s != nil)
- *s = '\0';
- tm->wday = dateindex(flds[0], wdayname, 7);
- if(tm->wday < 0)
- return nil;
- }
- return tm;
-}
-
-/*
- * zone : [A-Za-z][A-Za-z][A-Za-z] some time zone names
- * | [A-IK-Z] military time; rfc1123 says the rfc822 spec is wrong.
- * | "UT" universal time
- * | [+-][0-9][0-9][0-9][0-9]
- * zones is the rfc-822 list of time zone names
- */
-static NamedInt zones[] =
-{
- {"A", -1 * 3600},
- {"B", -2 * 3600},
- {"C", -3 * 3600},
- {"CDT", -5 * 3600},
- {"CST", -6 * 3600},
- {"D", -4 * 3600},
- {"E", -5 * 3600},
- {"EDT", -4 * 3600},
- {"EST", -5 * 3600},
- {"F", -6 * 3600},
- {"G", -7 * 3600},
- {"GMT", 0},
- {"H", -8 * 3600},
- {"I", -9 * 3600},
- {"K", -10 * 3600},
- {"L", -11 * 3600},
- {"M", -12 * 3600},
- {"MDT", -6 * 3600},
- {"MST", -7 * 3600},
- {"N", +1 * 3600},
- {"O", +2 * 3600},
- {"P", +3 * 3600},
- {"PDT", -7 * 3600},
- {"PST", -8 * 3600},
- {"Q", +4 * 3600},
- {"R", +5 * 3600},
- {"S", +6 * 3600},
- {"T", +7 * 3600},
- {"U", +8 * 3600},
- {"UT", 0},
- {"V", +9 * 3600},
- {"W", +10 * 3600},
- {"X", +11 * 3600},
- {"Y", +12 * 3600},
- {"Z", 0},
- {nil, 0}
-};
-
-static void
-zone2tm(Tm *tm, char *s)
-{
- Tm aux, *atm;
- int i;
-
- if(*s == '+' || *s == '-'){
- i = strtol(s, &s, 10);
- tm->tzoff = (i / 100) * 3600 + i % 100;
- strncpy(tm->zone, "", 4);
- return;
- }
-
- /*
- * look it up in the standard rfc822 table
- */
- strncpy(tm->zone, s, 3);
- tm->zone[3] = '\0';
- tm->tzoff = 0;
- for(i = 0; zones[i].name != nil; i++){
- if(cistrcmp(zones[i].name, s) == 0){
- tm->tzoff = zones[i].v;
- return;
- }
- }
-
- /*
- * one last try: look it up in the current local timezone
- * probe a couple of times to get daylight/standard time change.
- */
- aux = *tm;
- memset(aux.zone, 0, 4);
- aux.hour--;
- for(i = 0; i < 2; i++){
- atm = localtime(tm2sec(&aux));
- if(cistrcmp(tm->zone, atm->zone) == 0){
- tm->tzoff = atm->tzoff;
- return;
- }
- aux.hour++;
- }
-
- strncpy(tm->zone, "GMT", 4);
- tm->tzoff = 0;
-}
-
-/*
- * hh[:mm[:ss]]
- */
-static void
-time2tm(Tm *tm, char *s)
-{
- tm->hour = strtoul(s, &s, 10);
- if(*s++ != ':')
- return;
- tm->min = strtoul(s, &s, 10);
- if(*s++ != ':')
- return;
- tm->sec = strtoul(s, &s, 10);
-}
-
-static int
-dateindex(char *d, char **tab, int n)
-{
- int i;
-
- for(i = 0; i < n; i++)
- if(cistrcmp(d, tab[i]) == 0)
- return i;
- return -1;
-}
--- a/sys/src/cmd/ip/imap4d/debug.c
+++ /dev/null
@@ -1,108 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <auth.h>
-#include <bio.h>
-#include "imap4d.h"
-
-void
-debuglog(char *fmt, ...)
-{
- va_list arg;
- static int logfd;
-
- if(debug == 0)
- return;
- if(logfd == 0)
- logfd = open("/sys/log/imap4d", OWRITE);
- if(logfd > 0){
- va_start(arg, fmt);
- fprint(logfd, "%s: ", username);
- vfprint(logfd, fmt, arg);
- va_end(arg);
- }
-}
-
-void
-boxVerify(Box *box)
-{
- Msg *m;
- ulong seq, uid, recent;
-
- if(box == nil)
- return;
- recent = 0;
- seq = 0;
- uid = 0;
- for(m = box->msgs; m != nil; m = m->next){
- if(m->seq == 0)
- fprint(2, "m->seq == 0: m->seq=%lud\n", m->seq);
- else if(m->seq <= seq)
- fprint(2, "m->seq=%lud out of order: last=%lud\n", m->seq, seq);
- seq = m->seq;
-
- if(m->uid == 0)
- fprint(2, "m->uid == 0: m->seq=%lud\n", m->seq);
- else if(m->uid <= uid)
- fprint(2, "m->uid=%lud out of order: last=%lud\n", m->uid, uid);
- uid = m->uid;
-
- if(m->flags & MRecent)
- recent++;
- }
- if(seq != box->max)
- fprint(2, "max=%lud, should be %lud\n", box->max, seq);
- if(uid >= box->uidnext)
- fprint(2, "uidnext=%lud, maxuid=%lud\n", box->uidnext, uid);
- if(recent != box->recent)
- fprint(2, "recent=%lud, should be %lud\n", box->recent, recent);
-}
-
-void
-openfiles(void)
-{
- Dir *d;
- int i;
-
- for(i = 0; i < 20; i++){
- d = dirfstat(i);
- if(d != nil){
- fprint(2, "fd[%d]='%s' type=%c dev=%d user='%s group='%s'\n", i, d->name, d->type, d->dev, d->uid, d->gid);
- free(d);
- }
- }
-}
-
-void
-ls(char *file)
-{
- Dir *d;
- int fd, i, nd;
-
- fd = open(file, OREAD);
- if(fd < 0)
- return;
-
- /*
- * read box to find all messages
- * each one has a directory, and is in numerical order
- */
- d = dirfstat(fd);
- if(d == nil){
- close(fd);
- return;
- }
- if(!(d->mode & DMDIR)){
- fprint(2, "file %s\n", file);
- free(d);
- close(fd);
- return;
- }
- free(d);
- while((nd = dirread(fd, &d)) > 0){
- for(i = 0; i < nd; i++){
- fprint(2, "%s/%s %c\n", file, d[i].name, "-d"[(d[i].mode & DMDIR) == DMDIR]);
- }
- free(d);
- }
- close(fd);
-}
--- a/sys/src/cmd/ip/imap4d/fetch.c
+++ /dev/null
@@ -1,625 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-char *fetchPartNames[FPMax] =
-{
- "",
- "HEADER",
- "HEADER.FIELDS",
- "HEADER.FIELDS.NOT",
- "MIME",
- "TEXT",
-};
-
-/*
- * implicitly set the \seen flag. done in a separate pass
- * so the .imp file doesn't need to be open while the
- * messages are sent to the client.
- */
-int
-fetchSeen(Box *box, Msg *m, int uids, void *vf)
-{
- Fetch *f;
-
- USED(uids);
-
- if(m->expunged)
- return uids;
- for(f = vf; f != nil; f = f->next){
- switch(f->op){
- case FRfc822:
- case FRfc822Text:
- case FBodySect:
- msgSeen(box, m);
- goto breakout;
- }
- }
-breakout:
-
- return 1;
-}
-
-/*
- * fetch messages
- *
- * imap4 body[] requestes get translated to upas/fs files as follows
- * body[id.header] == id/rawheader file + extra \r\n
- * body[id.text] == id/rawbody
- * body[id.mime] == id/mimeheader + extra \r\n
- * body[id] === body[id.header] + body[id.text]
-*/
-int
-fetchMsg(Box *, Msg *m, int uids, void *vf)
-{
- Tm tm;
- Fetch *f;
- char *sep;
- int todo;
-
- if(m->expunged)
- return uids;
-
- todo = 0;
- for(f = vf; f != nil; f = f->next){
- switch(f->op){
- case FFlags:
- todo = 1;
- break;
- case FUid:
- todo = 1;
- break;
- case FInternalDate:
- case FEnvelope:
- case FRfc822:
- case FRfc822Head:
- case FRfc822Size:
- case FRfc822Text:
- case FBodySect:
- case FBodyPeek:
- case FBody:
- case FBodyStruct:
- todo = 1;
- if(!msgStruct(m, 1)){
- msgDead(m);
- return uids;
- }
- break;
- default:
- bye("bad implementation of fetch");
- return 0;
- }
- }
-
- if(m->expunged)
- return uids;
- if(!todo)
- return 1;
-
- /*
- * note: it is allowed to send back the responses one at a time
- * rather than all together. this is exploited to send flags elsewhere.
- */
- Bprint(&bout, "* %lud FETCH (", m->seq);
- sep = "";
- if(uids){
- Bprint(&bout, "UID %lud", m->uid);
- sep = " ";
- }
- for(f = vf; f != nil; f = f->next){
- switch(f->op){
- default:
- bye("bad implementation of fetch");
- break;
- case FFlags:
- Bprint(&bout, "%sFLAGS (", sep);
- writeFlags(&bout, m, 1);
- Bprint(&bout, ")");
- break;
- case FUid:
- if(uids)
- continue;
- Bprint(&bout, "%sUID %lud", sep, m->uid);
- break;
- case FEnvelope:
- Bprint(&bout, "%sENVELOPE ", sep);
- fetchEnvelope(m);
- break;
- case FInternalDate:
- Bprint(&bout, "%sINTERNALDATE ", sep);
- Bimapdate(&bout, date2tm(&tm, m->unixDate));
- break;
- case FBody:
- Bprint(&bout, "%sBODY ", sep);
- fetchBodyStruct(m, &m->head, 0);
- break;
- case FBodyStruct:
- Bprint(&bout, "%sBODYSTRUCTURE ", sep);
- fetchBodyStruct(m, &m->head, 1);
- break;
- case FRfc822Size:
- Bprint(&bout, "%sRFC822.SIZE %lud", sep, msgSize(m));
- break;
- case FRfc822:
- f->part = FPAll;
- Bprint(&bout, "%sRFC822", sep);
- fetchBody(m, f);
- break;
- case FRfc822Head:
- f->part = FPHead;
- Bprint(&bout, "%sRFC822.HEADER", sep);
- fetchBody(m, f);
- break;
- case FRfc822Text:
- f->part = FPText;
- Bprint(&bout, "%sRFC822.TEXT", sep);
- fetchBody(m, f);
- break;
- case FBodySect:
- case FBodyPeek:
- Bprint(&bout, "%sBODY", sep);
- fetchBody(fetchSect(m, f), f);
- break;
- }
- sep = " ";
- }
- Bprint(&bout, ")\r\n");
-
- return 1;
-}
-
-/*
- * print out section, part, headers;
- * find and return message section
- */
-Msg *
-fetchSect(Msg *m, Fetch *f)
-{
- Bputc(&bout, '[');
- BNList(&bout, f->sect, ".");
- if(f->part != FPAll){
- if(f->sect != nil)
- Bputc(&bout, '.');
- Bprint(&bout, "%s", fetchPartNames[f->part]);
- if(f->hdrs != nil){
- Bprint(&bout, " (");
- BSList(&bout, f->hdrs, " ");
- Bputc(&bout, ')');
- }
- }
- Bprint(&bout, "]");
- return findMsgSect(m, f->sect);
-}
-
-/*
- * actually return the body pieces
- */
-void
-fetchBody(Msg *m, Fetch *f)
-{
- Pair p;
- char *s, *t, *e, buf[BufSize + 2];
- ulong n, start, stop, pos;
- int fd, nn;
-
- if(m == nil){
- fetchBodyStr(f, "", 0);
- return;
- }
- switch(f->part){
- case FPHeadFields:
- case FPHeadFieldsNot:
- n = m->head.size + 3;
- s = emalloc(n);
- n = selectFields(s, n, m->head.buf, f->hdrs, f->part == FPHeadFields);
- fetchBodyStr(f, s, n);
- free(s);
- return;
- case FPHead:
- fetchBodyStr(f, m->head.buf, m->head.size);
- return;
- case FPMime:
- fetchBodyStr(f, m->mime.buf, m->mime.size);
- return;
- case FPAll:
- fd = msgFile(m, "rawbody");
- if(fd < 0){
- msgDead(m);
- fetchBodyStr(f, "", 0);
- return;
- }
- p = fetchBodyPart(f, msgSize(m));
- start = p.start;
- if(start < m->head.size){
- stop = p.stop;
- if(stop > m->head.size)
- stop = m->head.size;
- Bwrite(&bout, &m->head.buf[start], stop - start);
- start = 0;
- stop = p.stop;
- if(stop <= m->head.size){
- close(fd);
- return;
- }
- }else
- start -= m->head.size;
- stop = p.stop - m->head.size;
- break;
- case FPText:
- fd = msgFile(m, "rawbody");
- if(fd < 0){
- msgDead(m);
- fetchBodyStr(f, "", 0);
- return;
- }
- p = fetchBodyPart(f, m->size);
- start = p.start;
- stop = p.stop;
- break;
- default:
- fetchBodyStr(f, "", 0);
- return;
- }
-
- /*
- * read in each block, convert \n without \r to \r\n.
- * this means partial fetch requires fetching everything
- * through stop, since we don't know how many \r's will be added
- */
- buf[0] = ' ';
- for(pos = 0; pos < stop; ){
- n = BufSize;
- if(n > stop - pos)
- n = stop - pos;
- n = read(fd, &buf[1], n);
- if(n <= 0){
- fetchBodyFill(stop - pos);
- break;
- }
- e = &buf[n + 1];
- *e = '\0';
- for(s = &buf[1]; s < e && pos < stop; s = t + 1){
- t = memchr(s, '\n', e - s);
- if(t == nil)
- t = e;
- n = t - s;
- if(pos < start){
- if(pos + n <= start){
- s = t;
- pos += n;
- }else{
- s += start - pos;
- pos = start;
- }
- n = t - s;
- }
- nn = n;
- if(pos + nn > stop)
- nn = stop - pos;
- if(Bwrite(&bout, s, nn) != nn)
- writeErr();
- pos += n;
- if(*t == '\n'){
- if(t[-1] != '\r'){
- if(pos >= start && pos < stop)
- Bputc(&bout, '\r');
- pos++;
- }
- if(pos >= start && pos < stop)
- Bputc(&bout, '\n');
- pos++;
- }
- }
- buf[0] = e[-1];
- }
- close(fd);
-}
-
-/*
- * resolve the actual bounds of any partial fetch,
- * and print out the bounds & size of string returned
- */
-Pair
-fetchBodyPart(Fetch *f, ulong size)
-{
- Pair p;
- ulong start, stop;
-
- start = 0;
- stop = size;
- if(f->partial){
- start = f->start;
- if(start > size)
- start = size;
- stop = start + f->size;
- if(stop > size)
- stop = size;
- Bprint(&bout, "<%lud>", start);
- }
- Bprint(&bout, " {%lud}\r\n", stop - start);
- p.start = start;
- p.stop = stop;
- return p;
-}
-
-/*
- * something went wrong fetching data
- * produce fill bytes for what we've committed to produce
- */
-void
-fetchBodyFill(ulong n)
-{
- while(n-- > 0)
- if(Bputc(&bout, ' ') < 0)
- writeErr();
-}
-
-/*
- * return a simple string
- */
-void
-fetchBodyStr(Fetch *f, char *buf, ulong size)
-{
- Pair p;
-
- p = fetchBodyPart(f, size);
- Bwrite(&bout, &buf[p.start], p.stop-p.start);
-}
-
-char*
-printnlist(NList *sect)
-{
- static char buf[100];
- char *p;
-
- for(p= buf; sect; sect=sect->next){
- p += sprint(p, "%ld", sect->n);
- if(sect->next)
- *p++ = '.';
- }
- *p = '\0';
- return buf;
-}
-
-/*
- * find the numbered sub-part of the message
- */
-Msg*
-findMsgSect(Msg *m, NList *sect)
-{
- ulong id;
-
- for(; sect != nil; sect = sect->next){
- id = sect->n;
-#ifdef HACK
- /* HACK to solve extra level of structure not visible from upas/fs */
- if(m->kids == 0 && id == 1 && sect->next == nil){
- if(m->mime.type->s && strcmp(m->mime.type->s, "message")==0)
- if(m->mime.type->t && strcmp(m->mime.type->t, "rfc822")==0)
- if(m->head.type->s && strcmp(m->head.type->s, "text")==0)
- if(m->head.type->t && strcmp(m->head.type->t, "plain")==0)
- break;
- }
- /* end of HACK */
-#endif HACK
- for(m = m->kids; m != nil; m = m->next)
- if(m->id == id)
- break;
- if(m == nil)
- return nil;
- }
- return m;
-}
-
-void
-fetchEnvelope(Msg *m)
-{
- Tm tm;
-
- Bputc(&bout, '(');
- Brfc822date(&bout, date2tm(&tm, m->info[IDate]));
- Bputc(&bout, ' ');
- Bimapstr(&bout, m->info[ISubject]);
- Bputc(&bout, ' ');
- Bimapaddr(&bout, m->from);
- Bputc(&bout, ' ');
- Bimapaddr(&bout, m->sender);
- Bputc(&bout, ' ');
- Bimapaddr(&bout, m->replyTo);
- Bputc(&bout, ' ');
- Bimapaddr(&bout, m->to);
- Bputc(&bout, ' ');
- Bimapaddr(&bout, m->cc);
- Bputc(&bout, ' ');
- Bimapaddr(&bout, m->bcc);
- Bputc(&bout, ' ');
- Bimapstr(&bout, m->info[IInReplyTo]);
- Bputc(&bout, ' ');
- Bimapstr(&bout, m->info[IMessageId]);
- Bputc(&bout, ')');
-}
-
-void
-fetchBodyStruct(Msg *m, Header *h, int extensions)
-{
- Msg *k;
- ulong len;
-
- if(msgIsMulti(h)){
- Bputc(&bout, '(');
- for(k = m->kids; k != nil; k = k->next)
- fetchBodyStruct(k, &k->mime, extensions);
-
- Bputc(&bout, ' ');
- Bimapstr(&bout, h->type->t);
-
- if(extensions){
- Bputc(&bout, ' ');
- BimapMimeParams(&bout, h->type->next);
- fetchStructExt(h);
- }
-
- Bputc(&bout, ')');
- return;
- }
-
- Bputc(&bout, '(');
- if(h->type != nil){
- Bimapstr(&bout, h->type->s);
- Bputc(&bout, ' ');
- Bimapstr(&bout, h->type->t);
- Bputc(&bout, ' ');
- BimapMimeParams(&bout, h->type->next);
- }else
- Bprint(&bout, "\"text\" \"plain\" NIL");
-
- Bputc(&bout, ' ');
- if(h->id != nil)
- Bimapstr(&bout, h->id->s);
- else
- Bprint(&bout, "NIL");
-
- Bputc(&bout, ' ');
- if(h->description != nil)
- Bimapstr(&bout, h->description->s);
- else
- Bprint(&bout, "NIL");
-
- Bputc(&bout, ' ');
- if(h->encoding != nil)
- Bimapstr(&bout, h->encoding->s);
- else
- Bprint(&bout, "NIL");
-
- /*
- * this is so strange: return lengths for a body[text] response,
- * except in the case of a multipart message, when return lengths for a body[] response
- */
- len = m->size;
- if(h == &m->mime)
- len += m->head.size;
- Bprint(&bout, " %lud", len);
-
- len = m->lines;
- if(h == &m->mime)
- len += m->head.lines;
-
- if(h->type == nil || cistrcmp(h->type->s, "text") == 0){
- Bprint(&bout, " %lud", len);
- }else if(msgIsRfc822(h)){
- Bputc(&bout, ' ');
- k = m;
- if(h != &m->mime)
- k = m->kids;
- if(k == nil)
- Bprint(&bout, "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"text\" \"plain\" NIL NIL NIL NIL 0 0) 0");
- else{
- fetchEnvelope(k);
- Bputc(&bout, ' ');
- fetchBodyStruct(k, &k->head, extensions);
- Bprint(&bout, " %lud", len);
- }
- }
-
- if(extensions){
- Bputc(&bout, ' ');
-
- /*
- * don't have the md5 laying around,
- * since the header & body have added newlines.
- */
- Bprint(&bout, "NIL");
-
- fetchStructExt(h);
- }
- Bputc(&bout, ')');
-}
-
-/*
- * common part of bodystructure extensions
- */
-void
-fetchStructExt(Header *h)
-{
- Bputc(&bout, ' ');
- if(h->disposition != nil){
- Bputc(&bout, '(');
- Bimapstr(&bout, h->disposition->s);
- Bputc(&bout, ' ');
- BimapMimeParams(&bout, h->disposition->next);
- Bputc(&bout, ')');
- }else
- Bprint(&bout, "NIL");
- Bputc(&bout, ' ');
- if(h->language != nil){
- if(h->language->next != nil)
- BimapMimeParams(&bout, h->language->next);
- else
- Bimapstr(&bout, h->language->s);
- }else
- Bprint(&bout, "NIL");
-}
-
-int
-BimapMimeParams(Biobuf *b, MimeHdr *mh)
-{
- char *sep;
- int n;
-
- if(mh == nil)
- return Bprint(b, "NIL");
-
- n = Bputc(b, '(');
-
- sep = "";
- for(; mh != nil; mh = mh->next){
- n += Bprint(b, sep);
- n += Bimapstr(b, mh->s);
- n += Bputc(b, ' ');
- n += Bimapstr(b, mh->t);
- sep = " ";
- }
-
- n += Bputc(b, ')');
- return n;
-}
-
-/*
- * print a list of addresses;
- * each address is printed as '(' personalName AtDomainList mboxName hostName ')'
- * the AtDomainList is always NIL
- */
-int
-Bimapaddr(Biobuf *b, MAddr *a)
-{
- char *host, *sep;
- int n;
-
- if(a == nil)
- return Bprint(b, "NIL");
-
- n = Bputc(b, '(');
- sep = "";
- for(; a != nil; a = a->next){
- n += Bprint(b, "%s(", sep);
- n += Bimapstr(b, a->personal);
- n += Bprint(b," NIL ");
- n += Bimapstr(b, a->box);
- n += Bputc(b, ' ');
-
- /*
- * can't send NIL as hostName, since that is code for a group
- */
- host = a->host;
- if(host == nil)
- host = "";
- n += Bimapstr(b, host);
-
- n += Bputc(b, ')');
- sep = " ";
- }
- n += Bputc(b, ')');
- return n;
-}
--- a/sys/src/cmd/ip/imap4d/fns.h
+++ /dev/null
@@ -1,125 +1,0 @@
-/*
- * sorted by 4,/^$/|sort -bd +1
- */
-int fqid(int fd, Qid *qid);
-int BNList(Biobuf *b, NList *nl, char *sep);
-int BSList(Biobuf *b, SList *sl, char *sep);
-int BimapMimeParams(Biobuf *b, MimeHdr *mh);
-int Bimapaddr(Biobuf *b, MAddr *a);
-int Bimapdate(Biobuf *b, Tm *tm);
-int Bimapstr(Biobuf *b, char *s);
-int Brfc822date(Biobuf *b, Tm *tm);
-int appendSave(char *mbox, int flags, char *head, Biobuf *b, long n);
-void bye(char *fmt, ...);
-int cdCreate(char *dir, char *file, int mode, ulong perm);
-int cdExists(char *dir, char *file);
-Dir *cdDirstat(char *dir, char *file);
-int cdDirwstat(char *dir, char *file, Dir *d);
-int cdOpen(char *dir, char *file, int mode);
-int cdRemove(char *dir, char *file);
-MbLock *checkBox(Box *box, int imped);
-int ciisprefix(char *pre, char *s);
-int cistrcmp(char*, char*);
-int cistrncmp(char*, char*, int);
-char *cistrstr(char *s, char *sub);
-void closeBox(Box *box, int opened);
-void closeImp(Box *box, MbLock *ml);
-int copyBox(char *from, char *to, int doremove);
-int copyCheck(Box *box, Msg *m, int uids, void *v);
-int copySave(Box *box, Msg *m, int uids, void *vs);
-char *cramauth(void);
-int createBox(char *mbox, int dir);
-Tm *date2tm(Tm *tm, char *date);
-int decmutf7(char *out, int nout, char *in);
-int deleteMsgs(Box *box);
-void debuglog(char *fmt, ...);
-void *emalloc(ulong);
-int emptyImp(char *mbox);
-void enableForwarding(void);
-int encmutf7(char *out, int nout, char *in);
-void *erealloc(void*, ulong);
-char *estrdup(char*);
-int expungeMsgs(Box *box, int send);
-void *ezmalloc(ulong);
-void fetchBodyFill(ulong n);
-void fetchBody(Msg *m, Fetch *f);
-Pair fetchBodyPart(Fetch *f, ulong size);
-void fetchBodyStr(Fetch *f, char *buf, ulong size);
-void fetchBodyStruct(Msg *m, Header *h, int extensions);
-void fetchEnvelope(Msg *m);
-int fetchMsg(Box *box, Msg *m, int uids, void *fetch);
-Msg *fetchSect(Msg *m, Fetch *f);
-int fetchSeen(Box *box, Msg *m, int uids, void *vf);
-void fetchStructExt(Header *h);
-Msg *findMsgSect(Msg *m, NList *sect);
-int forMsgs(Box *box, MsgSet *ms, ulong max, int uids, int (*f)(Box*, Msg*, int, void*), void *rock);
-void freeMsg(Msg *m);
-ulong imap4DateTime(char *date);
-int imap4Date(Tm *tm, char *date);
-int imap4date(char *s, int n, Tm *tm);
-int imapTmp(void);
-char *impName(char *name);
-int infoIsNil(char *s);
-int isdotdot(char*);
-int isprefix(char *pre, char *s);
-int issuffix(char *suf, char *s);
-int listBoxes(char *cmd, char *ref, char *pat);
-char *loginauth(void);
-int lsubBoxes(char *cmd, char *ref, char *pat);
-char *maddrStr(MAddr *a);
-ulong mapFlag(char *name);
-ulong mapInt(NamedInt *map, char *name);
-void mbLockRefresh(MbLock *ml);
-int mbLocked(void);
-MbLock *mbLock(void);
-void mbUnlock(MbLock *ml);
-char *mboxName(char*);
-Fetch *mkFetch(int op, Fetch *next);
-NList *mkNList(ulong n, NList *next);
-SList *mkSList(char *s, SList *next);
-Store *mkStore(int sign, int op, int flags);
-int moveBox(char *from, char *to);
-void msgDead(Msg *m);
-int msgFile(Msg *m, char *f);
-int msgInfo(Msg *m);
-int msgIsMulti(Header *h);
-int msgIsRfc822(Header *h);
-int msgSeen(Box *box, Msg *m);
-ulong msgSize(Msg *m);
-int msgStruct(Msg *m, int top);
-char *mutf7str(char*);
-int myChdir(char *dir);
-int okMbox(char *mbox);
-Box *openBox(char *name, char *fsname, int writable);
-int openLocked(char *dir, char *file, int mode);
-void parseErr(char *msg);
-AuthInfo *passLogin(char*, char*);
-char *readFile(int fd);
-void resetCurDir(void);
-Fetch *revFetch(Fetch *f);
-NList *revNList(NList *s);
-SList *revSList(SList *s);
-int rfc822date(char *s, int n, Tm *tm);
-int searchMsg(Msg *m, Search *s);
-long selectFields(char *dst, long n, char *hdr, SList *fields, int matches);
-void sendFlags(Box *box, int uids);
-void setFlags(Box *box, Msg *m, int f);
-void setupuser(AuthInfo*);
-int storeMsg(Box *box, Msg *m, int uids, void *fetch);
-char *strmutf7(char*);
-void strrev(char *s, char *e);
-int subscribe(char *mbox, int how);
-void wrImpFlags(char *buf, int flags, int killRecent);
-void writeErr(void);
-void writeFlags(Biobuf *b, Msg *m, int recentOk);
-
-#pragma varargck argpos bye 1
-#pragma varargck argpos debuglog 1
-
-#define MK(t) ((t*)emalloc(sizeof(t)))
-#define MKZ(t) ((t*)ezmalloc(sizeof(t)))
-#define MKN(t,n) ((t*)emalloc((n)*sizeof(t)))
-#define MKNZ(t,n) ((t*)ezmalloc((n)*sizeof(t)))
-#define MKNA(t,at,n) ((t*)emalloc(sizeof(t) + (n)*sizeof(at)))
-
-#define STRLEN(cs) (sizeof(cs)-1)
--- a/sys/src/cmd/ip/imap4d/folder.c
+++ /dev/null
@@ -1,398 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-static int copyData(int ffd, int tfd, MbLock *ml);
-static MbLock mLock =
-{
- .fd = -1
-};
-
-static char curDir[MboxNameLen];
-
-void
-resetCurDir(void)
-{
- curDir[0] = '\0';
-}
-
-int
-myChdir(char *dir)
-{
- if(strcmp(dir, curDir) == 0)
- return 0;
- if(dir[0] != '/' || strlen(dir) > MboxNameLen)
- return -1;
- strcpy(curDir, dir);
- if(chdir(dir) < 0){
- werrstr("mychdir failed: %r");
- return -1;
- }
- return 0;
-}
-
-int
-cdCreate(char *dir, char *file, int mode, ulong perm)
-{
- if(myChdir(dir) < 0)
- return -1;
- return create(file, mode, perm);
-}
-
-Dir*
-cdDirstat(char *dir, char *file)
-{
- if(myChdir(dir) < 0)
- return nil;
- return dirstat(file);
-}
-
-int
-cdExists(char *dir, char *file)
-{
- Dir *d;
-
- d = cdDirstat(dir, file);
- if(d == nil)
- return 0;
- free(d);
- return 1;
-}
-
-int
-cdDirwstat(char *dir, char *file, Dir *d)
-{
- if(myChdir(dir) < 0)
- return -1;
- return dirwstat(file, d);
-}
-
-int
-cdOpen(char *dir, char *file, int mode)
-{
- if(myChdir(dir) < 0)
- return -1;
- return open(file, mode);
-}
-
-int
-cdRemove(char *dir, char *file)
-{
- if(myChdir(dir) < 0)
- return -1;
- return remove(file);
-}
-
-/*
- * open the one true mail lock file
- */
-MbLock*
-mbLock(void)
-{
- int i;
-
- if(mLock.fd >= 0)
- bye("mail lock deadlock");
- for(i = 0; i < 5; i++){
- mLock.fd = openLocked(mboxDir, "L.mbox", OREAD);
- if(mLock.fd >= 0)
- return &mLock;
- sleep(1000);
- }
- return nil;
-}
-
-void
-mbUnlock(MbLock *ml)
-{
- if(ml != &mLock)
- bye("bad mail unlock");
- if(ml->fd < 0)
- bye("mail unlock when not locked");
- close(ml->fd);
- ml->fd = -1;
-}
-
-void
-mbLockRefresh(MbLock *ml)
-{
- char buf[1];
-
- seek(ml->fd, 0, 0);
- read(ml->fd, buf, 1);
-}
-
-int
-mbLocked(void)
-{
- return mLock.fd >= 0;
-}
-
-char*
-impName(char *name)
-{
- char *s;
- int n;
-
- if(cistrcmp(name, "inbox") == 0)
- if(access("msgs", AEXIST) == 0)
- name = "msgs";
- else
- name = "mbox";
- n = strlen(name) + STRLEN(".imp") + 1;
- s = binalloc(&parseBin, n, 0);
- if(s == nil)
- return nil;
- snprint(s, n, "%s.imp", name);
- return s;
-}
-
-/*
- * massage the mailbox name into something valid
- * eliminates all .', and ..',s, redundatant and trailing /'s.
- */
-char *
-mboxName(char *s)
-{
- char *ss;
-
- ss = mutf7str(s);
- if(ss == nil)
- return nil;
- cleanname(ss);
- return ss;
-}
-
-char *
-strmutf7(char *s)
-{
- char *m;
- int n;
-
- n = strlen(s) * MUtf7Max + 1;
- m = binalloc(&parseBin, n, 0);
- if(m == nil)
- return nil;
- if(encmutf7(m, n, s) < 0)
- return nil;
- return m;
-}
-
-char *
-mutf7str(char *s)
-{
- char *m;
- int n;
-
- /*
- * n = strlen(s) * UTFmax / (2.67) + 1
- * UTFMax / 2.67 == 3 / (8/3) == 9 / 8
- */
- n = strlen(s);
- n = (n * 9 + 7) / 8 + 1;
- m = binalloc(&parseBin, n, 0);
- if(m == nil)
- return nil;
- if(decmutf7(m, n, s) < 0)
- return nil;
- return m;
-}
-
-void
-splitr(char *s, int c, char **left, char **right)
-{
- char *d;
- int n;
-
- n = strlen(s);
- d = binalloc(&parseBin, n + 1, 0);
- if(d == nil)
- parseErr("out of memory");
- strcpy(d, s);
- s = strrchr(d, c);
- if(s != nil){
- *left = d;
- *s++ = '\0';
- *right = s;
- }else{
- *right = d;
- *left = d + n;
- }
-}
-
-/*
- * create the mailbox and all intermediate components
- * a trailing / implies the new mailbox is a directory;
- * otherwise, it's a file.
- *
- * return with the file open for write, or directory open for read.
- */
-int
-createBox(char *mbox, int dir)
-{
- char *m;
- int fd;
-
- fd = -1;
- for(m = mbox; *m; m++){
- if(*m == '/'){
- *m = '\0';
- if(access(mbox, AEXIST) < 0){
- if(fd >= 0)
- close(fd);
- fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
- if(fd < 0)
- return -1;
- }
- *m = '/';
- }
- }
- if(dir)
- fd = cdCreate(mboxDir, mbox, OREAD, DMDIR|0775);
- else
- fd = cdCreate(mboxDir, mbox, OWRITE, 0664);
- return fd;
-}
-
-/*
- * move one mail folder to another
- * destination mailbox doesn't exist.
- * the source folder may be a directory or a mailbox,
- * and may be in the same directory as the destination,
- * or a completely different directory.
- */
-int
-moveBox(char *from, char *to)
-{
- Dir *d;
- char *fd, *fe, *td, *te, *fimp;
-
- splitr(from, '/', &fd, &fe);
- splitr(to, '/', &td, &te);
-
- /*
- * in the same directory: try rename
- */
- d = cdDirstat(mboxDir, from);
- if(d == nil)
- return 0;
- if(strcmp(fd, td) == 0){
- nulldir(d);
- d->name = te;
- if(cdDirwstat(mboxDir, from, d) >= 0){
- fimp = impName(from);
- d->name = impName(te);
- cdDirwstat(mboxDir, fimp, d);
- free(d);
- return 1;
- }
- }
-
- /*
- * directory copy is too hard for now
- */
- if(d->mode & DMDIR)
- return 0;
- free(d);
-
- return copyBox(from, to, 1);
-}
-
-/*
- * copy the contents of one mailbox to another
- * either truncates or removes the source box if it succeeds.
- */
-int
-copyBox(char *from, char *to, int doremove)
-{
- MbLock *ml;
- char *fimp, *timp;
- int ffd, tfd, ok;
-
- if(cistrcmp(from, "inbox") == 0)
- if(access("msgs", AEXIST) == 0)
- from = "msgs";
- else
- from = "mbox";
-
- ml = mbLock();
- if(ml == nil)
- return 0;
- ffd = openLocked(mboxDir, from, OREAD);
- if(ffd < 0){
- mbUnlock(ml);
- return 0;
- }
- tfd = createBox(to, 0);
- if(tfd < 0){
- mbUnlock(ml);
- close(ffd);
- return 0;
- }
-
- ok = copyData(ffd, tfd, ml);
- close(ffd);
- close(tfd);
- if(!ok){
- mbUnlock(ml);
- return 0;
- }
-
- fimp = impName(from);
- timp = impName(to);
- if(fimp != nil && timp != nil){
- ffd = cdOpen(mboxDir, fimp, OREAD);
- if(ffd >= 0){
- tfd = cdCreate(mboxDir, timp, OWRITE, 0664);
- if(tfd >= 0){
- copyData(ffd, tfd, ml);
- close(tfd);
- }
- close(ffd);
- }
- }
- cdRemove(mboxDir, fimp);
- if(doremove)
- cdRemove(mboxDir, from);
- else
- close(cdOpen(mboxDir, from, OWRITE|OTRUNC));
- mbUnlock(ml);
- return 1;
-}
-
-/*
- * copies while holding the mail lock,
- * then tries to copy permissions and group ownership
- */
-static int
-copyData(int ffd, int tfd, MbLock *ml)
-{
- Dir *fd, td;
- char buf[BufSize];
- int n;
-
- for(;;){
- n = read(ffd, buf, BufSize);
- if(n <= 0){
- if(n < 0)
- return 0;
- break;
- }
- if(write(tfd, buf, n) != n)
- return 0;
- mbLockRefresh(ml);
- }
- fd = dirfstat(ffd);
- if(fd != nil){
- nulldir(&td);
- td.mode = fd->mode;
- if(dirfwstat(tfd, &td) >= 0){
- nulldir(&td);
- td.gid = fd->gid;
- dirfwstat(tfd, &td);
- }
- }
- return 1;
-}
--- a/sys/src/cmd/ip/imap4d/imap4d.c
+++ /dev/null
@@ -1,2102 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <auth.h>
-#include <bio.h>
-#include "imap4d.h"
-
-/*
- * these should be in libraries
- */
-char *csquery(char *attr, char *val, char *rattr);
-
-/*
- * /lib/rfc/rfc2060 imap4rev1
- * /lib/rfc/rfc2683 is implementation advice
- * /lib/rfc/rfc2342 is namespace capability
- * /lib/rfc/rfc2222 is security protocols
- * /lib/rfc/rfc1731 is security protocols
- * /lib/rfc/rfc2221 is LOGIN-REFERRALS
- * /lib/rfc/rfc2193 is MAILBOX-REFERRALS
- * /lib/rfc/rfc2177 is IDLE capability
- * /lib/rfc/rfc2195 is CRAM-MD5 authentication
- * /lib/rfc/rfc2088 is LITERAL+ capability
- * /lib/rfc/rfc1760 is S/Key authentication
- *
- * outlook uses "Secure Password Authentication" aka ntlm authentication
- *
- * capabilities from nslocum
- * CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT
- */
-
-typedef struct ParseCmd ParseCmd;
-
-enum
-{
- UlongMax = 4294967295,
-};
-
-struct ParseCmd
-{
- char *name;
- void (*f)(char *tg, char *cmd);
-};
-
-static void appendCmd(char *tg, char *cmd);
-static void authenticateCmd(char *tg, char *cmd);
-static void capabilityCmd(char *tg, char *cmd);
-static void closeCmd(char *tg, char *cmd);
-static void copyCmd(char *tg, char *cmd);
-static void createCmd(char *tg, char *cmd);
-static void deleteCmd(char *tg, char *cmd);
-static void expungeCmd(char *tg, char *cmd);
-static void fetchCmd(char *tg, char *cmd);
-static void idleCmd(char *tg, char *cmd);
-static void listCmd(char *tg, char *cmd);
-static void loginCmd(char *tg, char *cmd);
-static void logoutCmd(char *tg, char *cmd);
-static void namespaceCmd(char *tg, char *cmd);
-static void noopCmd(char *tg, char *cmd);
-static void renameCmd(char *tg, char *cmd);
-static void searchCmd(char *tg, char *cmd);
-static void selectCmd(char *tg, char *cmd);
-static void statusCmd(char *tg, char *cmd);
-static void storeCmd(char *tg, char *cmd);
-static void subscribeCmd(char *tg, char *cmd);
-static void uidCmd(char *tg, char *cmd);
-static void unsubscribeCmd(char *tg, char *cmd);
-
-static void copyUCmd(char *tg, char *cmd, int uids);
-static void fetchUCmd(char *tg, char *cmd, int uids);
-static void searchUCmd(char *tg, char *cmd, int uids);
-static void storeUCmd(char *tg, char *cmd, int uids);
-
-static void imap4(int);
-static void status(int expungeable, int uids);
-static void cleaner(void);
-static void check(void);
-static int catcher(void*, char*);
-
-static Search *searchKey(int first);
-static Search *searchKeys(int first, Search *tail);
-static char *astring(void);
-static char *atomString(char *disallowed, char *initial);
-static char *atom(void);
-static void badsyn(void);
-static void clearcmd(void);
-static char *command(void);
-static void crnl(void);
-static Fetch *fetchAtt(char *s, Fetch *f);
-static Fetch *fetchWhat(void);
-static int flagList(void);
-static int flags(void);
-static int getc(void);
-static char *listmbox(void);
-static char *literal(void);
-static ulong litlen(void);
-static MsgSet *msgSet(int);
-static void mustBe(int c);
-static ulong number(int nonzero);
-static int peekc(void);
-static char *quoted(void);
-static void sectText(Fetch *f, int mimeOk);
-static ulong seqNo(void);
-static Store *storeWhat(void);
-static char *tag(void);
-static ulong uidNo(void);
-static void ungetc(void);
-
-static ParseCmd SNonAuthed[] =
-{
- {"capability", capabilityCmd},
- {"logout", logoutCmd},
- {"x-exit", logoutCmd},
- {"noop", noopCmd},
-
- {"login", loginCmd},
- {"authenticate", authenticateCmd},
-
- nil
-};
-
-static ParseCmd SAuthed[] =
-{
- {"capability", capabilityCmd},
- {"logout", logoutCmd},
- {"x-exit", logoutCmd},
- {"noop", noopCmd},
-
- {"append", appendCmd},
- {"create", createCmd},
- {"delete", deleteCmd},
- {"examine", selectCmd},
- {"select", selectCmd},
- {"idle", idleCmd},
- {"list", listCmd},
- {"lsub", listCmd},
- {"namespace", namespaceCmd},
- {"rename", renameCmd},
- {"status", statusCmd},
- {"subscribe", subscribeCmd},
- {"unsubscribe", unsubscribeCmd},
-
- nil
-};
-
-static ParseCmd SSelected[] =
-{
- {"capability", capabilityCmd},
- {"logout", logoutCmd},
- {"x-exit", logoutCmd},
- {"noop", noopCmd},
-
- {"append", appendCmd},
- {"create", createCmd},
- {"delete", deleteCmd},
- {"examine", selectCmd},
- {"select", selectCmd},
- {"idle", idleCmd},
- {"list", listCmd},
- {"lsub", listCmd},
- {"namespace", namespaceCmd},
- {"rename", renameCmd},
- {"status", statusCmd},
- {"subscribe", subscribeCmd},
- {"unsubscribe", unsubscribeCmd},
-
- {"check", noopCmd},
- {"close", closeCmd},
- {"copy", copyCmd},
- {"expunge", expungeCmd},
- {"fetch", fetchCmd},
- {"search", searchCmd},
- {"store", storeCmd},
- {"uid", uidCmd},
-
- nil
-};
-
-static char *atomStop = "(){%*\"\\";
-static Chalstate *chal;
-static int chaled;
-static ParseCmd *imapState;
-static jmp_buf parseJmp;
-static char *parseMsg;
-static int allowPass;
-static int allowCR;
-static int exiting;
-static QLock imaplock;
-static int idlepid = -1;
-
-Biobuf bout;
-Biobuf bin;
-char username[UserNameLen];
-char mboxDir[MboxNameLen];
-char *servername;
-char *site;
-char *remote;
-Box *selected;
-Bin *parseBin;
-int debug;
-
-void
-main(int argc, char *argv[])
-{
- char *s, *t;
- int preauth, n;
-
- Binit(&bin, 0, OREAD);
- Binit(&bout, 1, OWRITE);
-
- /* for auth */
- fmtinstall('H', encodefmt);
- fmtinstall('[', encodefmt);
-
- preauth = 0;
- allowPass = 0;
- allowCR = 0;
- ARGBEGIN{
- case 'a':
- preauth = 1;
- break;
- case 'd':
- site = ARGF();
- break;
- case 'c':
- allowCR = 1;
- break;
- case 'p':
- allowPass = 1;
- break;
- case 'r':
- remote = ARGF();
- break;
- case 's':
- servername = ARGF();
- break;
- case 'v':
- debug = 1;
- debuglog("imap4d debugging enabled\n");
- break;
- default:
- fprint(2, "usage: ip/imap4d [-acpv] [-d site] [-r remotehost] [-s servername]\n");
- bye("usage");
- break;
- }ARGEND
-
- if(allowPass && allowCR){
- fprint(2, "%s: -c and -p are mutually exclusive\n", argv0);
- bye("usage");
- }
-
- if(preauth)
- setupuser(nil);
-
- if(servername == nil){
- servername = csquery("sys", sysname(), "dom");
- if(servername == nil)
- servername = sysname();
- if(servername == nil){
- fprint(2, "ip/imap4d can't find server name: %r\n");
- bye("can't find system name");
- }
- }
- if(site == nil){
- t = getenv("site");
- if(t == nil)
- site = servername;
- else{
- n = strlen(t);
- s = strchr(servername, '.');
- if(s == nil)
- s = servername;
- else
- s++;
- n += strlen(s) + 2;
- site = emalloc(n);
- snprint(site, n, "%s.%s", t, s);
- }
- }
-
- rfork(RFNOTEG|RFREND);
-
- atnotify(catcher, 1);
- qlock(&imaplock);
- atexit(cleaner);
- imap4(preauth);
-}
-
-static void
-imap4(int preauth)
-{
- char *volatile tg;
- char *volatile cmd;
- ParseCmd *st;
-
- if(preauth){
- Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
- imapState = SAuthed;
- }else{
- Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername);
- imapState = SNonAuthed;
- }
- if(Bflush(&bout) < 0)
- writeErr();
-
- chaled = 0;
-
- tg = nil;
- cmd = nil;
- if(setjmp(parseJmp)){
- if(tg == nil)
- Bprint(&bout, "* bad empty command line: %s\r\n", parseMsg);
- else if(cmd == nil)
- Bprint(&bout, "%s BAD no command: %s\r\n", tg, parseMsg);
- else
- Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parseMsg);
- clearcmd();
- if(Bflush(&bout) < 0)
- writeErr();
- binfree(&parseBin);
- }
- for(;;){
- if(mbLocked())
- bye("internal error: mailbox lock held");
- tg = nil;
- cmd = nil;
- tg = tag();
- mustBe(' ');
- cmd = atom();
-
- /*
- * note: outlook express is broken: it requires echoing the
- * command as part of matching response
- */
- for(st = imapState; st->name != nil; st++){
- if(cistrcmp(cmd, st->name) == 0){
- (*st->f)(tg, cmd);
- break;
- }
- }
- if(st->name == nil){
- clearcmd();
- Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd);
- }
-
- if(Bflush(&bout) < 0)
- writeErr();
- binfree(&parseBin);
- }
-}
-
-void
-bye(char *fmt, ...)
-{
- va_list arg;
-
- va_start(arg, fmt);
- Bprint(&bout, "* bye ");
- Bvprint(&bout, fmt, arg);
- Bprint(&bout, "\r\n");
- Bflush(&bout);
-exits("rob2");
- exits(0);
-}
-
-void
-parseErr(char *msg)
-{
- parseMsg = msg;
- longjmp(parseJmp, 1);
-}
-
-/*
- * an error occured while writing to the client
- */
-void
-writeErr(void)
-{
- cleaner();
- _exits("connection closed");
-}
-
-static int
-catcher(void *v, char *msg)
-{
- USED(v);
- if(strstr(msg, "closed pipe") != nil)
- return 1;
- return 0;
-}
-
-/*
- * wipes out the idleCmd backgroung process if it is around.
- * this can only be called if the current proc has qlocked imaplock.
- * it must be the last piece of imap4d code executed.
- */
-static void
-cleaner(void)
-{
- int i;
-
- if(idlepid < 0)
- return;
- exiting = 1;
- close(0);
- close(1);
- close(2);
-
- /*
- * the other proc is either stuck in a read, a sleep,
- * or is trying to lock imap4lock.
- * get him out of it so he can exit cleanly
- */
- qunlock(&imaplock);
- for(i = 0; i < 4; i++)
- postnote(PNGROUP, getpid(), "die");
-}
-
-/*
- * send any pending status updates to the client
- * careful: shouldn't exit, because called by idle polling proc
- *
- * can't always send pending info
- * in particular, can't send expunge info
- * in response to a fetch, store, or search command.
- *
- * rfc2060 5.2: server must send mailbox size updates
- * rfc2060 5.2: server may send flag updates
- * rfc2060 5.5: servers prohibited from sending expunge while fetch, store, search in progress
- * rfc2060 7: in selected state, server checks mailbox for new messages as part of every command
- * sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
- * should also send appropriate untagged FETCH and EXPUNGE messages if another agent
- * changes the state of any message flags or expunges any messages
- * rfc2060 7.4.1 expunge server response must not be sent when no command is in progress,
- * nor while responding to a fetch, stort, or search command (uid versions are ok)
- * command only "in progress" after entirely parsed.
- *
- * strategy for third party deletion of messages or of a mailbox
- *
- * deletion of a selected mailbox => act like all message are expunged
- * not strictly allowed by rfc2180, but close to method 3.2.
- *
- * renaming same as deletion
- *
- * copy
- * reject iff a deleted message is in the request
- *
- * search, store, fetch operations on expunged messages
- * ignore the expunged messages
- * return tagged no if referenced
- */
-static void
-status(int expungeable, int uids)
-{
- int tell;
-
- if(!selected)
- return;
- tell = 0;
- if(expungeable)
- tell = expungeMsgs(selected, 1);
- if(selected->sendFlags)
- sendFlags(selected, uids);
- if(tell || selected->toldMax != selected->max){
- Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
- selected->toldMax = selected->max;
- }
- if(tell || selected->toldRecent != selected->recent){
- Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
- selected->toldRecent = selected->recent;
- }
- if(tell)
- closeImp(selected, checkBox(selected, 1));
-}
-
-/*
- * careful: can't exit, because called by idle polling proc
- */
-static void
-check(void)
-{
- if(!selected)
- return;
- checkBox(selected, 0);
- status(1, 0);
-}
-
-static void
-appendCmd(char *tg, char *cmd)
-{
- char *mbox, head[128];
- ulong t, n, now;
- int flags, ok;
-
- mustBe(' ');
- mbox = astring();
- mustBe(' ');
- flags = 0;
- if(peekc() == '('){
- flags = flagList();
- mustBe(' ');
- }
- now = time(nil);
- if(peekc() == '"'){
- t = imap4DateTime(quoted());
- if(t == ~0)
- parseErr("illegal date format");
- mustBe(' ');
- if(t > now)
- t = now;
- }else
- t = now;
- n = litlen();
-
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox)){
- check();
- Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
- return;
- }
- if(!cdExists(mboxDir, mbox)){
- check();
- Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
- return;
- }
-
- snprint(head, sizeof(head), "From %s %s", username, ctime(t));
- ok = appendSave(mbox, flags, head, &bin, n);
- crnl();
- check();
- if(ok)
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
- else
- Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd);
-}
-
-static void
-authenticateCmd(char *tg, char *cmd)
-{
- char *s, *t;
-
- mustBe(' ');
- s = atom();
- crnl();
- auth_freechal(chal);
- chal = nil;
- if(cistrcmp(s, "cram-md5") == 0){
- t = cramauth();
- if(t == nil){
- Bprint(&bout, "%s OK %s\r\n", tg, cmd);
- imapState = SAuthed;
- }else
- Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t);
- }else
- Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd);
-}
-
-static void
-capabilityCmd(char *tg, char *cmd)
-{
- crnl();
- check();
-// nslocum's capabilities
-// Bprint(&bout, "* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE IDLE SCAN SORT MAILBOX-REFERRALS LOGIN-REFERRALS AUTH=LOGIN THREAD=ORDEREDSUBJECT\r\n");
- Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE AUTH=CRAM-MD5\r\n");
- Bprint(&bout, "%s OK %s\r\n", tg, cmd);
-}
-
-static void
-closeCmd(char *tg, char *cmd)
-{
- crnl();
- imapState = SAuthed;
- closeBox(selected, 1);
- selected = nil;
- Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd);
-}
-
-/*
- * note: message id's are before any pending expunges
- */
-static void
-copyCmd(char *tg, char *cmd)
-{
- copyUCmd(tg, cmd, 0);
-}
-
-static void
-copyUCmd(char *tg, char *cmd, int uids)
-{
- MsgSet *ms;
- char *uid, *mbox;
- ulong max;
- int ok;
-
- mustBe(' ');
- ms = msgSet(uids);
- mustBe(' ');
- mbox = astring();
- crnl();
-
- uid = "";
- if(uids)
- uid = "uid ";
-
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox)){
- status(1, uids);
- Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd);
- return;
- }
- if(!cdExists(mboxDir, mbox)){
- check();
- Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
- return;
- }
-
- max = selected->max;
- checkBox(selected, 0);
- ok = forMsgs(selected, ms, max, uids, copyCheck, nil);
- if(ok)
- ok = forMsgs(selected, ms, max, uids, copySave, mbox);
-
- status(1, uids);
- if(ok)
- Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
- else
- Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
-}
-
-static void
-createCmd(char *tg, char *cmd)
-{
- char *mbox, *m;
- int fd, slash;
-
- mustBe(' ');
- mbox = astring();
- crnl();
- check();
-
- m = strchr(mbox, '\0');
- slash = m != mbox && m[-1] == '/';
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox)){
- Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
- return;
- }
- if(cistrcmp(mbox, "inbox") == 0){
- Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
- return;
- }
- if(access(mbox, AEXIST) >= 0){
- Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
- return;
- }
-
- fd = createBox(mbox, slash);
- close(fd);
- if(fd < 0)
- Bprint(&bout, "%s NO %s cannot create mailbox %s\r\n", tg, cmd, mbox);
- else
- Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
-}
-
-static void
-deleteCmd(char *tg, char *cmd)
-{
- char *mbox, *imp;
-
- mustBe(' ');
- mbox = astring();
- crnl();
- check();
-
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox)){
- Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
- return;
- }
-
- imp = impName(mbox);
- if(cistrcmp(mbox, "inbox") == 0
- || imp != nil && cdRemove(mboxDir, imp) < 0 && cdExists(mboxDir, imp)
- || cdRemove(mboxDir, mbox) < 0)
- Bprint(&bout, "%s NO %s cannot delete mailbox %s\r\n", tg, cmd, mbox);
- else
- Bprint(&bout, "%s OK %s %s completed\r\n", tg, mbox, cmd);
-}
-
-static void
-expungeCmd(char *tg, char *cmd)
-{
- int ok;
-
- crnl();
- ok = deleteMsgs(selected);
- check();
- if(ok)
- Bprint(&bout, "%s OK %s messages erased\r\n", tg, cmd);
- else
- Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
-}
-
-static void
-fetchCmd(char *tg, char *cmd)
-{
- fetchUCmd(tg, cmd, 0);
-}
-
-static void
-fetchUCmd(char *tg, char *cmd, int uids)
-{
- Fetch *f;
- MsgSet *ms;
- MbLock *ml;
- char *uid;
- ulong max;
- int ok;
-
- mustBe(' ');
- ms = msgSet(uids);
- mustBe(' ');
- f = fetchWhat();
- crnl();
- uid = "";
- if(uids)
- uid = "uid ";
- max = selected->max;
- ml = checkBox(selected, 1);
- if(ml != nil)
- forMsgs(selected, ms, max, uids, fetchSeen, f);
- closeImp(selected, ml);
- ok = ml != nil && forMsgs(selected, ms, max, uids, fetchMsg, f);
- status(uids, uids);
- if(ok)
- Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
- else
- Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
-}
-
-static void
-idleCmd(char *tg, char *cmd)
-{
- int c, pid;
-
- crnl();
- Bprint(&bout, "+ idling, waiting for done\r\n");
- if(Bflush(&bout) < 0)
- writeErr();
-
- if(idlepid < 0){
- pid = rfork(RFPROC|RFMEM|RFNOWAIT);
- if(pid == 0){
- for(;;){
- qlock(&imaplock);
- if(exiting)
- break;
-
- /*
- * parent may have changed curDir, but it doesn't change our .
- */
- resetCurDir();
-
- check();
- if(Bflush(&bout) < 0)
- writeErr();
- qunlock(&imaplock);
- sleep(15*1000);
- enableForwarding();
- }
-_exits("rob3");
- _exits(0);
- }
- idlepid = pid;
- }
-
- qunlock(&imaplock);
-
- /*
- * clear out the next line, which is supposed to contain (case-insensitive)
- * done\n
- * this is special code since it has to dance with the idle polling proc
- * and handle exiting correctly.
- */
- for(;;){
- c = getc();
- if(c < 0){
- qlock(&imaplock);
- if(!exiting)
- cleaner();
-_exits("rob4");
- _exits(0);
- }
- if(c == '\n')
- break;
- }
-
- qlock(&imaplock);
- if(exiting)
-{_exits("rob5");
- _exits(0);
-}
-
- /*
- * child may have changed curDir, but it doesn't change our .
- */
- resetCurDir();
-
- check();
- Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
-}
-
-static void
-listCmd(char *tg, char *cmd)
-{
- char *s, *t, *ss, *ref, *mbox;
- int n;
-
- mustBe(' ');
- s = astring();
- mustBe(' ');
- t = listmbox();
- crnl();
- check();
- ref = mutf7str(s);
- mbox = mutf7str(t);
- if(ref == nil || mbox == nil){
- Bprint(&bout, "%s BAD %s mailbox name not in modified utf-7\r\n", tg, cmd);
- return;
- }
-
- /*
- * special request for hierarchy delimiter and root name
- * root name appears to be name up to and including any delimiter,
- * or the empty string, if there is no delimiter.
- *
- * this must change if the # namespace convention is supported.
- */
- if(*mbox == '\0'){
- s = strchr(ref, '/');
- if(s == nil)
- ref = "";
- else
- s[1] = '\0';
- Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
- Bprint(&bout, "%s OK %s\r\n", tg, cmd);
- return;
- }
-
-
- /*
- * massage the listing name:
- * clean up the components individually,
- * then rip off componenets from the ref to
- * take care of leading ..'s in the mbox.
- *
- * the cleanup can wipe out * followed by a ..
- * tough luck if such a stupid pattern is given.
- */
- cleanname(mbox);
- if(strcmp(mbox, ".") == 0)
- *mbox = '\0';
- if(mbox[0] == '/')
- *ref = '\0';
- else if(*ref != '\0'){
- cleanname(ref);
- if(strcmp(ref, ".") == 0)
- *ref = '\0';
- }else
- *ref = '\0';
- while(*ref && isdotdot(mbox)){
- s = strrchr(ref, '/');
- if(s == nil)
- s = ref;
- if(isdotdot(s))
- break;
- *s = '\0';
- mbox += 2;
- if(*mbox == '/')
- mbox++;
- }
- if(*ref == '\0'){
- s = mbox;
- ss = s;
- }else{
- n = strlen(ref) + strlen(mbox) + 2;
- t = binalloc(&parseBin, n, 0);
- if(t == nil)
- parseErr("out of memory");
- snprint(t, n, "%s/%s", ref, mbox);
- s = t;
- ss = s + strlen(ref);
- }
-
- /*
- * only allow activity in /mail/box
- */
- if(s[0] == '/' || isdotdot(s)){
- Bprint(&bout, "%s NO illegal mailbox pattern\r\n", tg);
- return;
- }
-
- if(cistrcmp(cmd, "lsub") == 0)
- lsubBoxes(cmd, s, ss);
- else
- listBoxes(cmd, s, ss);
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
-}
-
-static char*
-passCR(char*u, char*p)
-{
- static char Ebadch[] = "can't get challenge";
- static char nchall[64];
- static char response[64];
- static Chalstate *ch = nil;
- AuthInfo *ai;
-
-again:
- if (ch == nil){
- if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
- return Ebadch;
- snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
- return nchall;
- } else {
- strncpy(response, p, 64);
- ch->resp = response;
- ch->nresp = strlen(response);
- ai = auth_response(ch);
- auth_freechal(ch);
- ch = nil;
- if (ai == nil)
- goto again;
- setupuser(ai);
- return nil;
- }
-
-}
-
-static void
-loginCmd(char *tg, char *cmd)
-{
- char *s, *t;
- AuthInfo *ai;
- char*r;
- mustBe(' ');
- s = astring(); /* uid */
- mustBe(' ');
- t = astring(); /* password */
- crnl();
- if(allowCR){
- if ((r = passCR(s, t)) == nil){
- Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
- imapState = SAuthed;
- } else {
- Bprint(&bout, "* NO [ALERT] %s\r\n", r);
- Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
- }
- return;
- }
- else if(allowPass){
- if(ai = passLogin(s, t)){
- setupuser(ai);
- Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
- imapState = SAuthed;
- }else
- Bprint(&bout, "%s NO %s failed check\r\n", tg, cmd);
- return;
- }
- Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
-}
-
-/*
- * logout or x-exit, which doesn't expunge the mailbox
- */
-static void
-logoutCmd(char *tg, char *cmd)
-{
- crnl();
-
- if(cmd[0] != 'x' && selected){
- closeBox(selected, 1);
- selected = nil;
- }
- Bprint(&bout, "* bye\r\n");
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
-exits("rob6");
- exits(0);
-}
-
-static void
-namespaceCmd(char *tg, char *cmd)
-{
- crnl();
- check();
-
- /*
- * personal, other users, shared namespaces
- * send back nil or descriptions of (prefix heirarchy-delim) for each case
- */
- Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n");
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
-}
-
-static void
-noopCmd(char *tg, char *cmd)
-{
- crnl();
- check();
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
- enableForwarding();
-}
-
-/*
- * this is only a partial implementation
- * should copy files to other directories,
- * and copy & truncate inbox
- */
-static void
-renameCmd(char *tg, char *cmd)
-{
- char *from, *to;
- int ok;
-
- mustBe(' ');
- from = astring();
- mustBe(' ');
- to = astring();
- crnl();
- check();
-
- to = mboxName(to);
- if(to == nil || !okMbox(to) || cistrcmp(to, "inbox") == 0){
- Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
- return;
- }
- if(access(to, AEXIST) >= 0){
- Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
- return;
- }
- from = mboxName(from);
- if(from == nil || !okMbox(from)){
- Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
- return;
- }
- if(cistrcmp(from, "inbox") == 0)
- ok = copyBox(from, to, 0);
- else
- ok = moveBox(from, to);
-
- if(ok)
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
- else
- Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
-}
-
-static void
-searchCmd(char *tg, char *cmd)
-{
- searchUCmd(tg, cmd, 0);
-}
-
-static void
-searchUCmd(char *tg, char *cmd, int uids)
-{
- Search rock;
- Msg *m;
- char *uid;
- ulong id;
-
- mustBe(' ');
- rock.next = nil;
- searchKeys(1, &rock);
- crnl();
- uid = "";
- if(uids)
- uid = "uid ";
- if(rock.next != nil && rock.next->key == SKCharset){
- if(cistrcmp(rock.next->s, "utf-8") != 0
- && cistrcmp(rock.next->s, "us-ascii") != 0){
- Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
- checkBox(selected, 0);
- status(uids, uids);
- return;
- }
- rock.next = rock.next->next;
- }
- Bprint(&bout, "* search");
- for(m = selected->msgs; m != nil; m = m->next)
- m->matched = searchMsg(m, rock.next);
- for(m = selected->msgs; m != nil; m = m->next){
- if(m->matched){
- if(uids)
- id = m->uid;
- else
- id = m->seq;
- Bprint(&bout, " %lud", id);
- }
- }
- Bprint(&bout, "\r\n");
- checkBox(selected, 0);
- status(uids, uids);
- Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
-}
-
-static void
-selectCmd(char *tg, char *cmd)
-{
- Msg *m;
- char *s, *mbox;
-
- mustBe(' ');
- mbox = astring();
- crnl();
-
- if(selected){
- imapState = SAuthed;
- closeBox(selected, 1);
- selected = nil;
- }
-
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox)){
- Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
- return;
- }
-
- selected = openBox(mbox, "imap", cistrcmp(cmd, "select") == 0);
- if(selected == nil){
- Bprint(&bout, "%s NO %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
- return;
- }
-
- imapState = SSelected;
-
- Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
- Bprint(&bout, "* %lud EXISTS\r\n", selected->max);
- selected->toldMax = selected->max;
- Bprint(&bout, "* %lud RECENT\r\n", selected->recent);
- selected->toldRecent = selected->recent;
- for(m = selected->msgs; m != nil; m = m->next){
- if(!m->expunged && (m->flags & MSeen) != MSeen){
- Bprint(&bout, "* OK [UNSEEN %ld]\r\n", m->seq);
- break;
- }
- }
- Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
- Bprint(&bout, "* OK [UIDNEXT %ld]\r\n", selected->uidnext);
- Bprint(&bout, "* OK [UIDVALIDITY %ld]\r\n", selected->uidvalidity);
- s = "READ-ONLY";
- if(selected->writable)
- s = "READ-WRITE";
- Bprint(&bout, "%s OK [%s] %s %s completed\r\n", tg, s, cmd, mbox);
-}
-
-static NamedInt statusItems[] =
-{
- {"MESSAGES", SMessages},
- {"RECENT", SRecent},
- {"UIDNEXT", SUidNext},
- {"UIDVALIDITY", SUidValidity},
- {"UNSEEN", SUnseen},
- {nil, 0}
-};
-
-static void
-statusCmd(char *tg, char *cmd)
-{
- Box *box;
- Msg *m;
- char *s, *mbox;
- ulong v;
- int si, i;
-
- mustBe(' ');
- mbox = astring();
- mustBe(' ');
- mustBe('(');
- si = 0;
- for(;;){
- s = atom();
- i = mapInt(statusItems, s);
- if(i == 0)
- parseErr("illegal status item");
- si |= i;
- if(peekc() == ')')
- break;
- mustBe(' ');
- }
- mustBe(')');
- crnl();
-
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox)){
- check();
- Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
- return;
- }
-
- box = openBox(mbox, "status", 1);
- if(box == nil){
- check();
- Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %s: %r\r\n", tg, cmd, mbox);
- return;
- }
-
- Bprint(&bout, "* STATUS %s (", mbox);
- s = "";
- for(i = 0; statusItems[i].name != nil; i++){
- if(si & statusItems[i].v){
- v = 0;
- switch(statusItems[i].v){
- case SMessages:
- v = box->max;
- break;
- case SRecent:
- v = box->recent;
- break;
- case SUidNext:
- v = box->uidnext;
- break;
- case SUidValidity:
- v = box->uidvalidity;
- break;
- case SUnseen:
- v = 0;
- for(m = box->msgs; m != nil; m = m->next)
- if((m->flags & MSeen) != MSeen)
- v++;
- break;
- default:
- Bprint(&bout, ")");
- bye("internal error: status item not implemented");
- break;
- }
- Bprint(&bout, "%s%s %lud", s, statusItems[i].name, v);
- s = " ";
- }
- }
- Bprint(&bout, ")\r\n");
- closeBox(box, 1);
-
- check();
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
-}
-
-static void
-storeCmd(char *tg, char *cmd)
-{
- storeUCmd(tg, cmd, 0);
-}
-
-static void
-storeUCmd(char *tg, char *cmd, int uids)
-{
- Store *st;
- MsgSet *ms;
- MbLock *ml;
- char *uid;
- ulong max;
- int ok;
-
- mustBe(' ');
- ms = msgSet(uids);
- mustBe(' ');
- st = storeWhat();
- crnl();
- uid = "";
- if(uids)
- uid = "uid ";
- max = selected->max;
- ml = checkBox(selected, 1);
- ok = ml != nil && forMsgs(selected, ms, max, uids, storeMsg, st);
- closeImp(selected, ml);
- status(uids, uids);
- if(ok)
- Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
- else
- Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
-}
-
-/*
- * minimal implementation of subscribe
- * all folders are automatically subscribed,
- * and can't be unsubscribed
- */
-static void
-subscribeCmd(char *tg, char *cmd)
-{
- Box *box;
- char *mbox;
- int ok;
-
- mustBe(' ');
- mbox = astring();
- crnl();
- check();
- mbox = mboxName(mbox);
- ok = 0;
- if(mbox != nil && okMbox(mbox)){
- box = openBox(mbox, "subscribe", 0);
- if(box != nil){
- ok = subscribe(mbox, 's');
- closeBox(box, 1);
- }
- }
- if(!ok)
- Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
- else
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
-}
-
-static void
-uidCmd(char *tg, char *cmd)
-{
- char *sub;
-
- mustBe(' ');
- sub = atom();
- if(cistrcmp(sub, "copy") == 0)
- copyUCmd(tg, sub, 1);
- else if(cistrcmp(sub, "fetch") == 0)
- fetchUCmd(tg, sub, 1);
- else if(cistrcmp(sub, "search") == 0)
- searchUCmd(tg, sub, 1);
- else if(cistrcmp(sub, "store") == 0)
- storeUCmd(tg, sub, 1);
- else{
- clearcmd();
- Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
- }
-}
-
-static void
-unsubscribeCmd(char *tg, char *cmd)
-{
- char *mbox;
-
- mustBe(' ');
- mbox = astring();
- crnl();
- check();
- mbox = mboxName(mbox);
- if(mbox == nil || !okMbox(mbox) || !subscribe(mbox, 'u'))
- Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
- else
- Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
-}
-
-static void
-badsyn(void)
-{
- parseErr("bad syntax");
-}
-
-static void
-clearcmd(void)
-{
- int c;
-
- for(;;){
- c = getc();
- if(c < 0)
- bye("end of input");
- if(c == '\n')
- return;
- }
-}
-
-static void
-crnl(void)
-{
- int c;
-
- c = getc();
- if(c == '\n')
- return;
- if(c != '\r' || getc() != '\n')
- badsyn();
-}
-
-static void
-mustBe(int c)
-{
- if(getc() != c){
- ungetc();
- badsyn();
- }
-}
-
-/*
- * flaglist : '(' ')' | '(' flags ')'
- */
-static int
-flagList(void)
-{
- int f;
-
- mustBe('(');
- f = 0;
- if(peekc() != ')')
- f = flags();
-
- mustBe(')');
- return f;
-}
-
-/*
- * flags : flag | flags ' ' flag
- * flag : '\' atom | atom
- */
-static int
-flags(void)
-{
- int ff, flags;
- char *s;
- int c;
-
- flags = 0;
- for(;;){
- c = peekc();
- if(c == '\\'){
- mustBe('\\');
- s = atomString(atomStop, "\\");
- }else if(strchr(atomStop, c) != nil)
- s = atom();
- else
- break;
- ff = mapFlag(s);
- if(ff == 0)
- parseErr("flag not supported");
- flags |= ff;
- if(peekc() != ' ')
- break;
- mustBe(' ');
- }
- if(flags == 0)
- parseErr("no flags given");
- return flags;
-}
-
-/*
- * storeWhat : osign 'FLAGS' ' ' storeflags
- * | osign 'FLAGS.SILENT' ' ' storeflags
- * osign :
- * | '+' | '-'
- * storeflags : flagList | flags
- */
-static Store*
-storeWhat(void)
-{
- int f;
- char *s;
- int c, w;
-
- c = peekc();
- if(c == '+' || c == '-')
- mustBe(c);
- else
- c = 0;
- s = atom();
- w = 0;
- if(cistrcmp(s, "flags") == 0)
- w = STFlags;
- else if(cistrcmp(s, "flags.silent") == 0)
- w = STFlagsSilent;
- else
- parseErr("illegal store attribute");
- mustBe(' ');
- if(peekc() == '(')
- f = flagList();
- else
- f = flags();
- return mkStore(c, w, f);
-}
-
-/*
- * fetchWhat : "ALL" | "FULL" | "FAST" | fetchAtt | '(' fetchAtts ')'
- * fetchAtts : fetchAtt | fetchAtts ' ' fetchAtt
- */
-static char *fetchAtom = "(){}%*\"\\[]";
-static Fetch*
-fetchWhat(void)
-{
- Fetch *f;
- char *s;
-
- if(peekc() == '('){
- getc();
- f = nil;
- for(;;){
- s = atomString(fetchAtom, "");
- f = fetchAtt(s, f);
- if(peekc() == ')')
- break;
- mustBe(' ');
- }
- getc();
- return revFetch(f);
- }
-
- s = atomString(fetchAtom, "");
- if(cistrcmp(s, "all") == 0)
- f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, nil))));
- else if(cistrcmp(s, "fast") == 0)
- f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, nil)));
- else if(cistrcmp(s, "full") == 0)
- f = mkFetch(FFlags, mkFetch(FInternalDate, mkFetch(FRfc822Size, mkFetch(FEnvelope, mkFetch(FBody, nil)))));
- else
- f = fetchAtt(s, nil);
- return f;
-}
-
-/*
- * fetchAtt : "ENVELOPE" | "FLAGS" | "INTERNALDATE"
- * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
- * | "BODYSTRUCTURE"
- * | "UID"
- * | "BODY"
- * | "BODY" bodysubs
- * | "BODY.PEEK" bodysubs
- * bodysubs : sect
- * | sect '<' number '.' nz-number '>'
- * sect : '[' sectSpec ']'
- * sectSpec : sectMsgText
- * | sectPart
- * | sectPart '.' sectText
- * sectPart : nz-number
- * | sectPart '.' nz-number
- */
-static Fetch*
-fetchAtt(char *s, Fetch *f)
-{
- NList *sect;
- int c;
-
- if(cistrcmp(s, "envelope") == 0)
- return mkFetch(FEnvelope, f);
- if(cistrcmp(s, "flags") == 0)
- return mkFetch(FFlags, f);
- if(cistrcmp(s, "internaldate") == 0)
- return mkFetch(FInternalDate, f);
- if(cistrcmp(s, "RFC822") == 0)
- return mkFetch(FRfc822, f);
- if(cistrcmp(s, "RFC822.header") == 0)
- return mkFetch(FRfc822Head, f);
- if(cistrcmp(s, "RFC822.size") == 0)
- return mkFetch(FRfc822Size, f);
- if(cistrcmp(s, "RFC822.text") == 0)
- return mkFetch(FRfc822Text, f);
- if(cistrcmp(s, "bodystructure") == 0)
- return mkFetch(FBodyStruct, f);
- if(cistrcmp(s, "uid") == 0)
- return mkFetch(FUid, f);
-
- if(cistrcmp(s, "body") == 0){
- if(peekc() != '[')
- return mkFetch(FBody, f);
- f = mkFetch(FBodySect, f);
- }else if(cistrcmp(s, "body.peek") == 0)
- f = mkFetch(FBodyPeek, f);
- else
- parseErr("illegal fetch attribute");
-
- mustBe('[');
- c = peekc();
- if(c >= '1' && c <= '9'){
- sect = mkNList(number(1), nil);
- while(peekc() == '.'){
- getc();
- c = peekc();
- if(c >= '1' && c <= '9'){
- sect = mkNList(number(1), sect);
- }else{
- break;
- }
- }
- f->sect = revNList(sect);
- }
- if(peekc() != ']')
- sectText(f, f->sect != nil);
- mustBe(']');
-
- if(peekc() != '<')
- return f;
-
- f->partial = 1;
- mustBe('<');
- f->start = number(0);
- mustBe('.');
- f->size = number(1);
- mustBe('>');
- return f;
-}
-
-/*
- * sectText : sectMsgText | "MIME"
- * sectMsgText : "HEADER"
- * | "TEXT"
- * | "HEADER.FIELDS" ' ' hdrList
- * | "HEADER.FIELDS.NOT" ' ' hdrList
- * hdrList : '(' hdrs ')'
- * hdrs: : astring
- * | hdrs ' ' astring
- */
-static void
-sectText(Fetch *f, int mimeOk)
-{
- SList *h;
- char *s;
-
- s = atomString(fetchAtom, "");
- if(cistrcmp(s, "header") == 0){
- f->part = FPHead;
- return;
- }
- if(cistrcmp(s, "text") == 0){
- f->part = FPText;
- return;
- }
- if(mimeOk && cistrcmp(s, "mime") == 0){
- f->part = FPMime;
- return;
- }
- if(cistrcmp(s, "header.fields") == 0)
- f->part = FPHeadFields;
- else if(cistrcmp(s, "header.fields.not") == 0)
- f->part = FPHeadFieldsNot;
- else
- parseErr("illegal fetch section text");
- mustBe(' ');
- mustBe('(');
- h = nil;
- for(;;){
- h = mkSList(astring(), h);
- if(peekc() == ')')
- break;
- mustBe(' ');
- }
- mustBe(')');
- f->hdrs = revSList(h);
-}
-
-/*
- * searchWhat : "CHARSET" ' ' astring searchkeys | searchkeys
- * searchkeys : searchkey | searchkeys ' ' searchkey
- * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
- * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
- * | astrkey ' ' astring
- * | datekey ' ' date
- * | "KEYWORD" ' ' flag | "UNKEYWORD" flag
- * | "LARGER" ' ' number | "SMALLER" ' ' number
- * | "HEADER" astring ' ' astring
- * | set | "UID" ' ' set
- * | "NOT" ' ' searchkey
- * | "OR" ' ' searchkey ' ' searchkey
- * | '(' searchkeys ')'
- * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
- * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
- */
-static NamedInt searchMap[] =
-{
- {"ALL", SKAll},
- {"ANSWERED", SKAnswered},
- {"DELETED", SKDeleted},
- {"FLAGGED", SKFlagged},
- {"NEW", SKNew},
- {"OLD", SKOld},
- {"RECENT", SKRecent},
- {"SEEN", SKSeen},
- {"UNANSWERED", SKUnanswered},
- {"UNDELETED", SKUndeleted},
- {"UNFLAGGED", SKUnflagged},
- {"DRAFT", SKDraft},
- {"UNDRAFT", SKUndraft},
- {"UNSEEN", SKUnseen},
- {nil, 0}
-};
-
-static NamedInt searchMapStr[] =
-{
- {"CHARSET", SKCharset},
- {"BCC", SKBcc},
- {"BODY", SKBody},
- {"CC", SKCc},
- {"FROM", SKFrom},
- {"SUBJECT", SKSubject},
- {"TEXT", SKText},
- {"TO", SKTo},
- {nil, 0}
-};
-
-static NamedInt searchMapDate[] =
-{
- {"BEFORE", SKBefore},
- {"ON", SKOn},
- {"SINCE", SKSince},
- {"SENTBEFORE", SKSentBefore},
- {"SENTON", SKSentOn},
- {"SENTSINCE", SKSentSince},
- {nil, 0}
-};
-
-static NamedInt searchMapFlag[] =
-{
- {"KEYWORD", SKKeyword},
- {"UNKEYWORD", SKUnkeyword},
- {nil, 0}
-};
-
-static NamedInt searchMapNum[] =
-{
- {"SMALLER", SKSmaller},
- {"LARGER", SKLarger},
- {nil, 0}
-};
-
-static Search*
-searchKeys(int first, Search *tail)
-{
- Search *s;
-
- for(;;){
- if(peekc() == '('){
- getc();
- tail = searchKeys(0, tail);
- mustBe(')');
- }else{
- s = searchKey(first);
- tail->next = s;
- tail = s;
- }
- first = 0;
- if(peekc() != ' ')
- break;
- getc();
- }
- return tail;
-}
-
-static Search*
-searchKey(int first)
-{
- Search *sr, rock;
- Tm tm;
- char *a;
- int i, c;
-
- sr = binalloc(&parseBin, sizeof(Search), 1);
- if(sr == nil)
- parseErr("out of memory");
-
- c = peekc();
- if(c >= '0' && c <= '9'){
- sr->key = SKSet;
- sr->set = msgSet(0);
- return sr;
- }
-
- a = atom();
- if(i = mapInt(searchMap, a))
- sr->key = i;
- else if(i = mapInt(searchMapStr, a)){
- if(!first && i == SKCharset)
- parseErr("illegal search key");
- sr->key = i;
- mustBe(' ');
- sr->s = astring();
- }else if(i = mapInt(searchMapDate, a)){
- sr->key = i;
- mustBe(' ');
- c = peekc();
- if(c == '"')
- getc();
- a = atom();
- if(!imap4Date(&tm, a))
- parseErr("bad date format");
- sr->year = tm.year;
- sr->mon = tm.mon;
- sr->mday = tm.mday;
- if(c == '"')
- mustBe('"');
- }else if(i = mapInt(searchMapFlag, a)){
- sr->key = i;
- mustBe(' ');
- c = peekc();
- if(c == '\\'){
- mustBe('\\');
- a = atomString(atomStop, "\\");
- }else
- a = atom();
- i = mapFlag(a);
- if(i == 0)
- parseErr("flag not supported");
- sr->num = i;
- }else if(i = mapInt(searchMapNum, a)){
- sr->key = i;
- mustBe(' ');
- sr->num = number(0);
- }else if(cistrcmp(a, "HEADER") == 0){
- sr->key = SKHeader;
- mustBe(' ');
- sr->hdr = astring();
- mustBe(' ');
- sr->s = astring();
- }else if(cistrcmp(a, "UID") == 0){
- sr->key = SKUid;
- mustBe(' ');
- sr->set = msgSet(0);
- }else if(cistrcmp(a, "NOT") == 0){
- sr->key = SKNot;
- mustBe(' ');
- rock.next = nil;
- searchKeys(0, &rock);
- sr->left = rock.next;
- }else if(cistrcmp(a, "OR") == 0){
- sr->key = SKOr;
- mustBe(' ');
- rock.next = nil;
- searchKeys(0, &rock);
- sr->left = rock.next;
- mustBe(' ');
- rock.next = nil;
- searchKeys(0, &rock);
- sr->right = rock.next;
- }else
- parseErr("illegal search key");
- return sr;
-}
-
-/*
- * set : seqno
- * | seqno ':' seqno
- * | set ',' set
- * seqno: nz-number
- * | '*'
- *
- */
-static MsgSet*
-msgSet(int uids)
-{
- MsgSet head, *last, *ms;
- ulong from, to;
-
- last = &head;
- head.next = nil;
- for(;;){
- from = uids ? uidNo() : seqNo();
- to = from;
- if(peekc() == ':'){
- getc();
- to = uids ? uidNo() : seqNo();
- }
- ms = binalloc(&parseBin, sizeof(MsgSet), 0);
- if(ms == nil)
- parseErr("out of memory");
- ms->from = from;
- ms->to = to;
- ms->next = nil;
- last->next = ms;
- last = ms;
- if(peekc() != ',')
- break;
- getc();
- }
- return head.next;
-}
-
-static ulong
-seqNo(void)
-{
- if(peekc() == '*'){
- getc();
- return ~0UL;
- }
- return number(1);
-}
-
-static ulong
-uidNo(void)
-{
- if(peekc() == '*'){
- getc();
- return ~0UL;
- }
- return number(0);
-}
-
-/*
- * 7 bit, non-ctl chars, no (){%*"\
- * NIL is special case for nstring or parenlist
- */
-static char *
-atom(void)
-{
- return atomString(atomStop, "");
-}
-
-/*
- * like an atom, but no +
- */
-static char *
-tag(void)
-{
- return atomString("+(){%*\"\\", "");
-}
-
-/*
- * string or atom allowing %*
- */
-static char *
-listmbox(void)
-{
- int c;
-
- c = peekc();
- if(c == '{')
- return literal();
- if(c == '"')
- return quoted();
- return atomString("(){\"\\", "");
-}
-
-/*
- * string or atom
- */
-static char *
-astring(void)
-{
- int c;
-
- c = peekc();
- if(c == '{')
- return literal();
- if(c == '"')
- return quoted();
- return atom();
-}
-
-/*
- * 7 bit, non-ctl chars, none from exception list
- */
-static char *
-atomString(char *disallowed, char *initial)
-{
- char *s;
- int c, ns, as;
-
- ns = strlen(initial);
- s = binalloc(&parseBin, ns + StrAlloc, 0);
- if(s == nil)
- parseErr("out of memory");
- strcpy(s, initial);
- as = ns + StrAlloc;
- for(;;){
- c = getc();
- if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
- ungetc();
- break;
- }
- s[ns++] = c;
- if(ns >= as){
- s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
- if(s == nil)
- parseErr("out of memory");
- as += StrAlloc;
- }
- }
- if(ns == 0)
- badsyn();
- s[ns] = '\0';
- return s;
-}
-
-/*
- * quoted: '"' chars* '"'
- * chars: 1-128 except \r and \n
- */
-static char *
-quoted(void)
-{
- char *s;
- int c, ns, as;
-
- mustBe('"');
- s = binalloc(&parseBin, StrAlloc, 0);
- if(s == nil)
- parseErr("out of memory");
- as = StrAlloc;
- ns = 0;
- for(;;){
- c = getc();
- if(c == '"')
- break;
- if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
- badsyn();
- if(c == '\\'){
- c = getc();
- if(c != '\\' && c != '"')
- badsyn();
- }
- s[ns++] = c;
- if(ns >= as){
- s = bingrow(&parseBin, s, as, as + StrAlloc, 0);
- if(s == nil)
- parseErr("out of memory");
- as += StrAlloc;
- }
- }
- s[ns] = '\0';
- return s;
-}
-
-/*
- * litlen: {number}\r\n
- */
-static ulong
-litlen(void)
-{
- ulong v;
-
- mustBe('{');
- v = number(0);
- mustBe('}');
- crnl();
- return v;
-}
-
-/*
- * literal: litlen data<0:litlen>
- */
-static char *
-literal(void)
-{
- char *s;
- ulong v;
-
- v = litlen();
- s = binalloc(&parseBin, v+1, 0);
- if(s == nil)
- parseErr("out of memory");
- Bprint(&bout, "+ Ready for literal data\r\n");
- if(Bflush(&bout) < 0)
- writeErr();
- if(v != 0 && Bread(&bin, s, v) != v)
- badsyn();
- s[v] = '\0';
- return s;
-}
-
-/*
- * digits; number is 32 bits
- */
-static ulong
-number(int nonzero)
-{
- ulong v;
- int c, first;
-
- v = 0;
- first = 1;
- for(;;){
- c = getc();
- if(c < '0' || c > '9'){
- ungetc();
- if(first)
- badsyn();
- break;
- }
- if(nonzero && first && c == '0')
- badsyn();
- c -= '0';
- first = 0;
- if(v > UlongMax/10 || v == UlongMax/10 && c > UlongMax%10)
- parseErr("number out of range\r\n");
- v = v * 10 + c;
- }
- return v;
-}
-
-static int
-getc(void)
-{
- return Bgetc(&bin);
-}
-
-static void
-ungetc(void)
-{
- Bungetc(&bin);
-}
-
-static int
-peekc(void)
-{
- int c;
-
- c = Bgetc(&bin);
- Bungetc(&bin);
- return c;
-}
-
--- a/sys/src/cmd/ip/imap4d/imap4d.h
+++ /dev/null
@@ -1,378 +1,0 @@
-/*
- * mailbox and message representations
- *
- * these structures are allocated with emalloc and must be explicitly freed
- */
-typedef struct Box Box;
-typedef struct Header Header;
-typedef struct MAddr MAddr;
-typedef struct MbLock MbLock;
-typedef struct MimeHdr MimeHdr;
-typedef struct Msg Msg;
-typedef struct NamedInt NamedInt;
-typedef struct Pair Pair;
-
-enum
-{
- StrAlloc = 32, /* characters allocated at a time */
- BufSize = 8*1024, /* size of transfer block */
- NDigest = 40, /* length of digest string */
- NUid = 10, /* length of .imp uid string */
- NFlags = 8, /* length of .imp flag string */
- LockSecs = 5 * 60, /* seconds to wait for acquiring a locked file */
- MboxNameLen = 256, /* max. length of upas/fs mbox name */
- MsgNameLen = 32, /* max. length of a file in a upas/fs mbox */
- UserNameLen = 64, /* max. length of user's name */
-
- MUtf7Max = 6, /* max length for a modified utf7 character: &bbbb- */
-
- /*
- * message flags
- */
- MSeen = 1 << 0,
- MAnswered = 1 << 1,
- MFlagged = 1 << 2,
- MDeleted = 1 << 3,
- MDraft = 1 << 4,
- MRecent = 1 << 5,
-
- /*
- * message bogus flags
- */
- NotBogus = 0, /* the message is displayable */
- BogusHeader = 1, /* the header had bad characters */
- BogusBody = 2, /* the body had bad characters */
- BogusTried = 4, /* attempted to open the fake message */
-};
-
-struct Box
-{
- char *name; /* path name of mailbox */
- char *fs; /* fs name of mailbox */
- char *fsDir; /* /mail/fs/box->fs */
- char *imp; /* path name of .imp file */
- uchar writable; /* can write back messages? */
- uchar dirtyImp; /* .imp file needs to be written? */
- uchar sendFlags; /* need flags update */
- Qid qid; /* qid of fs mailbox */
- Qid impQid; /* qid of .imp when last synched */
- long mtime; /* file mtime when last read */
- ulong max; /* maximum msgs->seq, same as number of messages */
- ulong toldMax; /* last value sent to client */
- ulong recent; /* number of recently received messaged */
- ulong toldRecent; /* last value sent to client */
- ulong uidnext; /* next uid value assigned to a message */
- ulong uidvalidity; /* uid of mailbox */
- Msg *msgs;
-};
-
-/*
- * fields of Msg->info
- */
-enum
-{
- /*
- * read from upasfs
- */
- IFrom,
- ITo,
- ICc,
- IReplyTo,
- IUnixDate,
- ISubject,
- IType,
- IDisposition,
- IFilename,
- IDigest,
- IBcc,
- IInReplyTo, /* aka internal date */
- IDate,
- ISender,
- IMessageId,
- ILines, /* number of lines of raw body */
-
- IMax
-};
-
-struct Header
-{
- char *buf; /* header, including terminating \r\n */
- ulong size; /* strlen(buf) */
- ulong lines; /* number of \n characters in buf */
-
- /*
- * pre-parsed mime headers
- */
- MimeHdr *type; /* content-type */
- MimeHdr *id; /* content-id */
- MimeHdr *description; /* content-description */
- MimeHdr *encoding; /* content-transfer-encoding */
- MimeHdr *md5; /* content-md5 */
- MimeHdr *disposition; /* content-disposition */
- MimeHdr *language; /* content-language */
-};
-
-struct Msg
-{
- Msg *next;
- Msg *prev;
- Msg *kids;
- Msg *parent;
- char *fsDir; /* box->fsDir of enclosing message */
- Header head; /* message header */
- Header mime; /* mime header from enclosing multipart spec */
- int flags;
- uchar sendFlags; /* flags value needs to be sent to client */
- uchar expunged; /* message actually expunged, but not yet reported to client */
- uchar matched; /* search succeeded? */
- uchar bogus; /* implies the message is invalid, ie contains nulls; see flags above */
- ulong uid; /* imap unique identifier */
- ulong seq; /* position in box; 1 is oldest */
- ulong id; /* number of message directory in upas/fs */
- char *fs; /* name of message directory */
- char *efs; /* pointer after / in fs; enough space for file name */
-
- ulong size; /* size of fs/rawbody, in bytes, with \r added before \n */
- ulong lines; /* number of lines in rawbody */
-
- char *iBuf;
- char *info[IMax]; /* all info about message */
-
- char *unixDate;
- MAddr *unixFrom;
-
- MAddr *to; /* parsed out address lines */
- MAddr *from;
- MAddr *replyTo;
- MAddr *sender;
- MAddr *cc;
- MAddr *bcc;
-};
-
-/*
- * pre-parsed header lines
- */
-struct MAddr
-{
- char *personal;
- char *box;
- char *host;
- MAddr *next;
-};
-
-struct MimeHdr
-{
- char *s;
- char *t;
- MimeHdr *next;
-};
-
-/*
- * mapping of integer & names
- */
-struct NamedInt
-{
- char *name;
- int v;
-};
-
-/*
- * lock for all mail file operations
- */
-struct MbLock
-{
- int fd;
-};
-
-/*
- * parse nodes for imap4rev1 protocol
- *
- * important: all of these items are allocated
- * in one can, so they can be tossed out at the same time.
- * this allows leakless parse error recovery by simply tossing the can away.
- * however, it means these structures cannot be mixed with the mailbox structures
- */
-
-typedef struct Fetch Fetch;
-typedef struct NList NList;
-typedef struct SList SList;
-typedef struct MsgSet MsgSet;
-typedef struct Store Store;
-typedef struct Search Search;
-
-/*
- * parse tree for fetch command
- */
-enum
-{
- FEnvelope,
- FFlags,
- FInternalDate,
- FRfc822,
- FRfc822Head,
- FRfc822Size,
- FRfc822Text,
- FBodyStruct,
- FUid,
- FBody, /* BODY */
- FBodySect, /* BODY [...] */
- FBodyPeek,
-
- FMax
-};
-
-enum
-{
- FPAll,
- FPHead,
- FPHeadFields,
- FPHeadFieldsNot,
- FPMime,
- FPText,
-
- FPMax
-};
-
-struct Fetch
-{
- uchar op; /* F.* operator */
- uchar part; /* FP.* subpart for body[] & body.peek[]*/
- uchar partial; /* partial fetch? */
- long start; /* partial fetch amounts */
- long size;
- NList *sect;
- SList *hdrs;
- Fetch *next;
-};
-
-/*
- * status items
- */
-enum{
- SMessages = 1 << 0,
- SRecent = 1 << 1,
- SUidNext = 1 << 2,
- SUidValidity = 1 << 3,
- SUnseen = 1 << 4,
-};
-
-/*
- * parse tree for store command
- */
-enum
-{
- STFlags,
- STFlagsSilent,
-
- STMax
-};
-
-struct Store
-{
- uchar sign;
- uchar op;
- int flags;
-};
-
-/*
- * parse tree for search command
- */
-enum
-{
- SKNone,
-
- SKCharset,
-
- SKAll,
- SKAnswered,
- SKBcc,
- SKBefore,
- SKBody,
- SKCc,
- SKDeleted,
- SKDraft,
- SKFlagged,
- SKFrom,
- SKHeader,
- SKKeyword,
- SKLarger,
- SKNew,
- SKNot,
- SKOld,
- SKOn,
- SKOr,
- SKRecent,
- SKSeen,
- SKSentBefore,
- SKSentOn,
- SKSentSince,
- SKSet,
- SKSince,
- SKSmaller,
- SKSubject,
- SKText,
- SKTo,
- SKUid,
- SKUnanswered,
- SKUndeleted,
- SKUndraft,
- SKUnflagged,
- SKUnkeyword,
- SKUnseen,
-
- SKMax
-};
-
-struct Search
-{
- int key;
- char *s;
- char *hdr;
- ulong num;
- int year;
- int mon;
- int mday;
- MsgSet *set;
- Search *left;
- Search *right;
- Search *next;
-};
-
-struct NList
-{
- ulong n;
- NList *next;
-};
-
-struct SList
-{
- char *s;
- SList *next;
-};
-
-struct MsgSet
-{
- ulong from;
- ulong to;
- MsgSet *next;
-};
-
-struct Pair
-{
- ulong start;
- ulong stop;
-};
-
-#include "bin.h"
-
-extern Bin *parseBin;
-extern Biobuf bout;
-extern Biobuf bin;
-extern char username[UserNameLen];
-extern char mboxDir[MboxNameLen];
-extern char *fetchPartNames[FPMax];
-extern char *site;
-extern char *remote;
-extern int debug;
-
-#include "fns.h"
--- a/sys/src/cmd/ip/imap4d/list.c
+++ /dev/null
@@ -1,412 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-#define SUBSCRIBED "imap.subscribed"
-
-static int matches(char *ref, char *pat, char *name);
-static int mayMatch(char *pat, char *name, int star);
-static int checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir);
-static int listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime);
-static int listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm);
-static int mkSubscribed(void);
-
-static long
-listMtime(char *file)
-{
- Dir *d;
- long mtime;
-
- d = cdDirstat(mboxDir, file);
- if(d == nil)
- return 0;
- mtime = d->mtime;
- free(d);
- return mtime;
-}
-
-/*
- * check for subscribed mailboxes
- * each line is either a comment starting with #
- * or is a subscribed mailbox name
- */
-int
-lsubBoxes(char *cmd, char *ref, char *pat)
-{
- MbLock *mb;
- Dir *d;
- Biobuf bin;
- char *s;
- long mtime;
- int fd, ok, isdir;
-
- mb = mbLock();
- if(mb == nil)
- return 0;
- fd = cdOpen(mboxDir, SUBSCRIBED, OREAD);
- if(fd < 0)
- fd = mkSubscribed();
- if(fd < 0){
- mbUnlock(mb);
- return 0;
- }
- ok = 0;
- Binit(&bin, fd, OREAD);
- while(s = Brdline(&bin, '\n')){
- s[Blinelen(&bin) - 1] = '\0';
- if(s[0] == '#')
- continue;
- isdir = 1;
- if(cistrcmp(s, "INBOX") == 0){
- if(access("msgs", AEXIST) == 0)
- mtime = listMtime("msgs");
- else
- mtime = listMtime("mbox");
- isdir = 0;
- }else{
- d = cdDirstat(mboxDir, s);
- if(d != nil){
- mtime = d->mtime;
- if(!(d->mode & DMDIR))
- isdir = 0;
- free(d);
- }else
- mtime = 0;
- }
- ok |= checkMatch(cmd, ref, pat, s, mtime, isdir);
- }
- Bterm(&bin);
- close(fd);
- mbUnlock(mb);
- return ok;
-}
-
-static int
-mkSubscribed(void)
-{
- int fd;
-
- fd = cdCreate(mboxDir, SUBSCRIBED, ORDWR, 0664);
- if(fd < 0)
- return -1;
- fprint(fd, "#imap4 subscription list\nINBOX\n");
- seek(fd, 0, 0);
- return fd;
-}
-
-/*
- * either subscribe or unsubscribe to a mailbox
- */
-int
-subscribe(char *mbox, int how)
-{
- MbLock *mb;
- char *s, *in, *ein;
- int fd, tfd, ok, nmbox;
-
- if(cistrcmp(mbox, "inbox") == 0)
- mbox = "INBOX";
- mb = mbLock();
- if(mb == nil)
- return 0;
- fd = cdOpen(mboxDir, SUBSCRIBED, ORDWR);
- if(fd < 0)
- fd = mkSubscribed();
- if(fd < 0){
- mbUnlock(mb);
- return 0;
- }
- in = readFile(fd);
- if(in == nil){
- mbUnlock(mb);
- return 0;
- }
- nmbox = strlen(mbox);
- s = strstr(in, mbox);
- while(s != nil && (s != in && s[-1] != '\n' || s[nmbox] != '\n'))
- s = strstr(s+1, mbox);
- ok = 0;
- if(how == 's' && s == nil){
- if(fprint(fd, "%s\n", mbox) > 0)
- ok = 1;
- }else if(how == 'u' && s != nil){
- ein = strchr(s, '\0');
- memmove(s, &s[nmbox+1], ein - &s[nmbox+1]);
- ein -= nmbox+1;
- tfd = cdOpen(mboxDir, SUBSCRIBED, OWRITE|OTRUNC);
- if(tfd >= 0 && seek(fd, 0, 0) >= 0 && write(fd, in, ein-in) == ein-in)
- ok = 1;
- if(tfd > 0)
- close(tfd);
- }else
- ok = 1;
- close(fd);
- mbUnlock(mb);
- return ok;
-}
-
-/*
- * stupidly complicated so that % doesn't read entire directory structure
- * yet * works
- * note: in most places, inbox is case-insensitive,
- * but here INBOX is checked for a case-sensitve match.
- */
-int
-listBoxes(char *cmd, char *ref, char *pat)
-{
- int ok;
-
- ok = checkMatch(cmd, ref, pat, "INBOX", listMtime("mbox"), 0);
- return ok | listMatch(cmd, ref, pat, ref, pat);
-}
-
-/*
- * look for all messages which may match the pattern
- * punt when a * is reached
- */
-static int
-listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm)
-{
- Dir *dir, *dirs;
- char *mdir, *m, *mb, *wc;
- long mode;
- int c, i, nmb, nmdir, nd, ok, fd;
-
- mdir = nil;
- for(m = mm; c = *m; m++){
- if(c == '%' || c == '*'){
- if(mdir == nil){
- fd = cdOpen(mboxDir, ".", OREAD);
- if(fd < 0)
- return 0;
- mbox = "";
- nmdir = 0;
- }else{
- *mdir = '\0';
- fd = cdOpen(mboxDir, mbox, OREAD);
- *mdir = '/';
- nmdir = mdir - mbox + 1;
- if(fd < 0)
- return 0;
- dir = dirfstat(fd);
- if(dir == nil){
- close(fd);
- return 0;
- }
- mode = dir->mode;
- free(dir);
- if(!(mode & DMDIR))
- break;
- }
- wc = m;
- for(; c = *m; m++)
- if(c == '/')
- break;
- nmb = nmdir + strlen(m) + MboxNameLen + 3;
- mb = emalloc(nmb);
- strncpy(mb, mbox, nmdir);
- ok = 0;
- while((nd = dirread(fd, &dirs)) > 0){
- for(i = 0; i < nd; i++){
- if(strcmp(mbox, "") == 0 &&
- !okMbox(dirs[i].name))
- continue;
- /* Safety: ignore message dirs */
- if(strstr(dirs[i].name, "mails") != 0 ||
- strcmp(dirs[i].name, "out") == 0 ||
- strcmp(dirs[i].name, "obox") == 0 ||
- strcmp(dirs[i].name, "ombox") == 0)
- continue;
- if(strcmp(dirs[i].name, "msgs") == 0)
- dirs[i].mode &= ~DMDIR;
- if(*wc == '*' && dirs[i].mode & DMDIR &&
- mayMatch(mm, dirs[i].name, 1)){
- snprint(mb+nmdir, nmb-nmdir,
- "%s", dirs[i].name);
- ok |= listAll(cmd, ref, pat, mb,
- dirs[i].mtime);
- }else if(mayMatch(mm, dirs[i].name, 0)){
- snprint(mb+nmdir, nmb-nmdir,
- "%s%s", dirs[i].name, m);
- if(*m == '\0')
- ok |= checkMatch(cmd,
- ref, pat, mb,
- dirs[i].mtime,
- dirs[i].mode &
- DMDIR);
- else if(dirs[i].mode & DMDIR)
- ok |= listMatch(cmd,
- ref, pat, mb, mb
- + nmdir + strlen(
- dirs[i].name));
- }
- }
- free(dirs);
- }
- close(fd);
- free(mb);
- return ok;
- }
- if(c == '/'){
- mdir = m;
- mm = m + 1;
- }
- }
- m = mbox;
- if(*mbox == '\0')
- m = ".";
- dir = cdDirstat(mboxDir, m);
- if(dir == nil)
- return 0;
- ok = checkMatch(cmd, ref, pat, mbox, dir->mtime, (dir->mode & DMDIR) == DMDIR);
- free(dir);
- return ok;
-}
-
-/*
- * too hard: recursively list all files rooted at mbox,
- * and list checkMatch figure it out
- */
-static int
-listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime)
-{
- Dir *dirs;
- char *mb;
- int i, nmb, nd, ok, fd;
-
- ok = checkMatch(cmd, ref, pat, mbox, mtime, 1);
- fd = cdOpen(mboxDir, mbox, OREAD);
- if(fd < 0)
- return ok;
-
- nmb = strlen(mbox) + MboxNameLen + 2;
- mb = emalloc(nmb);
- while((nd = dirread(fd, &dirs)) > 0){
- for(i = 0; i < nd; i++){
- snprint(mb, nmb, "%s/%s", mbox, dirs[i].name);
- /* safety: do not recurr */
- if(0 && dirs[i].mode & DMDIR)
- ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime);
- else
- ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, 0);
- }
- free(dirs);
- }
- close(fd);
- free(mb);
- return ok;
-}
-
-static int
-mayMatch(char *pat, char *name, int star)
-{
- Rune r;
- int i, n;
-
- for(; *pat && *pat != '/'; pat += n){
- r = *(uchar*)pat;
- if(r < Runeself)
- n = 1;
- else
- n = chartorune(&r, pat);
-
- if(r == '*' || r == '%'){
- pat += n;
- if(r == '*' && star || *pat == '\0' || *pat == '/')
- return 1;
- while(*name){
- if(mayMatch(pat, name, star))
- return 1;
- name += chartorune(&r, name);
- }
- return 0;
- }
- for(i = 0; i < n; i++)
- if(name[i] != pat[i])
- return 0;
- name += n;
- }
- if(*name == '\0')
- return 1;
- return 0;
-}
-
-/*
- * mbox is a mailbox name which might match pat.
- * verify the match
- * generates response
- */
-static int
-checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir)
-{
- char *s, *flags;
-
- if(!matches(ref, pat, mbox) || !okMbox(mbox))
- return 0;
- if(strcmp(mbox, ".") == 0)
- mbox = "";
-
- if(isdir)
- flags = "(\\Noselect)";
- else{
- s = impName(mbox);
- if(s != nil && listMtime(s) < mtime)
- flags = "(\\Noinferiors \\Marked)";
- else
- flags = "(\\Noinferiors)";
- }
-
- s = strmutf7(mbox);
- if(s != nil)
- Bprint(&bout, "* %s %s \"/\" \"%s\"\r\n", cmd, flags, s);
- return 1;
-}
-
-static int
-matches(char *ref, char *pat, char *name)
-{
- Rune r;
- int i, n;
-
- while(ref != pat)
- if(*name++ != *ref++)
- return 0;
- for(; *pat; pat += n){
- r = *(uchar*)pat;
- if(r < Runeself)
- n = 1;
- else
- n = chartorune(&r, pat);
-
- if(r == '*'){
- pat += n;
- if(*pat == '\0')
- return 1;
- while(*name){
- if(matches(pat, pat, name))
- return 1;
- name += chartorune(&r, name);
- }
- return 0;
- }
- if(r == '%'){
- pat += n;
- while(*name && *name != '/'){
- if(matches(pat, pat, name))
- return 1;
- name += chartorune(&r, name);
- }
- pat -= n;
- continue;
- }
- for(i = 0; i < n; i++)
- if(name[i] != pat[i])
- return 0;
- name += n;
- }
- if(*name == '\0')
- return 1;
- return 0;
-}
--- a/sys/src/cmd/ip/imap4d/mbox.c
+++ /dev/null
@@ -1,863 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-static NamedInt flagChars[NFlags] =
-{
- {"s", MSeen},
- {"a", MAnswered},
- {"f", MFlagged},
- {"D", MDeleted},
- {"d", MDraft},
- {"r", MRecent},
-};
-
-static int fsCtl = -1;
-
-static void boxFlags(Box *box);
-static int createImp(Box *box, Qid *qid);
-static void fsInit(void);
-static void mboxGone(Box *box);
-static MbLock *openImp(Box *box, int new);
-static int parseImp(Biobuf *b, Box *box);
-static int readBox(Box *box);
-static ulong uidRenumber(Msg *m, ulong uid, int force);
-static int impFlags(Box *box, Msg *m, char *flags);
-
-/*
- * strategy:
- * every mailbox file has an associated .imp file
- * which maps upas/fs message digests to uids & message flags.
- *
- * the .imp files are locked by /mail/fs/usename/L.mbox.
- * whenever the flags can be modified, the lock file
- * should be opened, thereby locking the uid & flag state.
- * for example, whenever new uids are assigned to messages,
- * and whenever flags are changed internally, the lock file
- * should be open and locked. this means the file must be
- * opened during store command, and when changing the \seen
- * flag for the fetch command.
- *
- * if no .imp file exists, a null one must be created before
- * assigning uids.
- *
- * the .imp file has the following format
- * imp : "imap internal mailbox description\n"
- * uidvalidity " " uidnext "\n"
- * messageLines
- *
- * messageLines :
- * | messageLines digest " " uid " " flags "\n"
- *
- * uid, uidnext, and uidvalidity are 32 bit decimal numbers
- * printed right justified in a field NUid characters long.
- * the 0 uid implies that no uid has been assigned to the message,
- * but the flags are valid. note that message lines are in mailbox
- * order, except possibly for 0 uid messages.
- *
- * digest is an ascii hex string NDigest characters long.
- *
- * flags has a character for each of NFlag flag fields.
- * if the flag is clear, it is represented by a "-".
- * set flags are represented as a unique single ascii character.
- * the currently assigned flags are, in order:
- * MSeen s
- * MAnswered a
- * MFlagged f
- * MDeleted D
- * MDraft d
- */
-Box*
-openBox(char *name, char *fsname, int writable)
-{
- Box *box;
- MbLock *ml;
- int n, new;
-
- if(cistrcmp(name, "inbox") == 0)
- if(access("msgs", AEXIST) == 0)
- name = "msgs";
- else
- name = "mbox";
- fsInit();
- debuglog("imap4d open %s %s\n", name, fsname);
-
- if(fprint(fsCtl, "open '/mail/box/%s/%s' %s", username, name, fsname) < 0){
-//ZZZ
- char err[ERRMAX];
-
- rerrstr(err, sizeof err);
- if(strstr(err, "file does not exist") == nil)
- fprint(2,
- "imap4d at %lud: upas/fs open %s/%s as %s failed: '%s' %s",
- time(nil), username, name, fsname, err,
- ctime(time(nil))); /* NB: ctime result ends with \n */
- fprint(fsCtl, "close %s", fsname);
- return nil;
- }
-
- /*
- * read box to find all messages
- * each one has a directory, and is in numerical order
- */
- box = MKZ(Box);
- box->writable = writable;
-
- n = strlen(name) + 1;
- box->name = emalloc(n);
- strcpy(box->name, name);
-
- n += STRLEN(".imp");
- box->imp = emalloc(n);
- snprint(box->imp, n, "%s.imp", name);
-
- n = strlen(fsname) + 1;
- box->fs = emalloc(n);
- strcpy(box->fs, fsname);
-
- n = STRLEN("/mail/fs/") + strlen(fsname) + 1;
- box->fsDir = emalloc(n);
- snprint(box->fsDir, n, "/mail/fs/%s", fsname);
-
- box->uidnext = 1;
- new = readBox(box);
- if(new >= 0){
- ml = openImp(box, new);
- if(ml != nil){
- closeImp(box, ml);
- return box;
- }
- }
- closeBox(box, 0);
- return nil;
-}
-
-/*
- * check mailbox
- * returns fd of open .imp file if imped.
- * otherwise, return value is insignificant
- *
- * careful: called by idle polling proc
- */
-MbLock*
-checkBox(Box *box, int imped)
-{
- MbLock *ml;
- Dir *d;
- int new;
-
- if(box == nil)
- return nil;
-
- /*
- * if stat fails, mailbox must be gone
- */
- d = cdDirstat(box->fsDir, ".");
- if(d == nil){
- mboxGone(box);
- return nil;
- }
- new = 0;
- if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers
- || box->mtime != d->mtime){
- new = readBox(box);
- if(new < 0){
- free(d);
- return nil;
- }
- }
- free(d);
- ml = openImp(box, new);
- if(ml == nil)
- box->writable = 0;
- else if(!imped){
- closeImp(box, ml);
- ml = nil;
- }
- return ml;
-}
-
-/*
- * mailbox is unreachable, so mark all messages expunged
- * clean up .imp files as well.
- */
-static void
-mboxGone(Box *box)
-{
- Msg *m;
-
- if(cdExists(mboxDir, box->name) < 0)
- cdRemove(mboxDir, box->imp);
- for(m = box->msgs; m != nil; m = m->next)
- m->expunged = 1;
- box->writable = 0;
-}
-
-/*
- * read messages in the mailbox
- * mark message that no longer exist as expunged
- * returns -1 for failure, 0 if no new messages, 1 if new messages.
- */
-static int
-readBox(Box *box)
-{
- Msg *msgs, *m, *last;
- Dir *d;
- char *s;
- long max, id;
- int i, nd, fd, new;
-
- fd = cdOpen(box->fsDir, ".", OREAD);
- if(fd < 0){
- syslog(0, "mail",
- "imap4d at %lud: upas/fs stat of %s/%s aka %s failed: %r",
- time(nil), username, box->name, box->fsDir);
- mboxGone(box);
- return -1;
- }
-
- /*
- * read box to find all messages
- * each one has a directory, and is in numerical order
- */
- d = dirfstat(fd);
- if(d == nil){
- close(fd);
- return -1;
- }
- box->mtime = d->mtime;
- box->qid = d->qid;
- last = nil;
- msgs = box->msgs;
- max = 0;
- new = 0;
- free(d);
- while((nd = dirread(fd, &d)) > 0){
- for(i = 0; i < nd; i++){
- s = d[i].name;
- id = strtol(s, &s, 10);
- if(id <= max || *s != '\0'
- || (d[i].mode & DMDIR) != DMDIR)
- continue;
-
- max = id;
-
- while(msgs != nil){
- last = msgs;
- msgs = msgs->next;
- if(last->id == id)
- goto continueDir;
- last->expunged = 1;
- }
-
- new = 1;
- m = MKZ(Msg);
- m->id = id;
- m->fsDir = box->fsDir;
- m->fs = emalloc(2 * (MsgNameLen + 1));
- m->efs = seprint(m->fs, m->fs + (MsgNameLen + 1), "%lud/", id);
- m->size = ~0UL;
- m->lines = ~0UL;
- m->prev = last;
- m->flags = MRecent;
- if(!msgInfo(m))
- freeMsg(m);
- else{
- if(last == nil)
- box->msgs = m;
- else
- last->next = m;
- last = m;
- }
- continueDir:;
- }
- free(d);
- }
- close(fd);
- for(; msgs != nil; msgs = msgs->next)
- msgs->expunged = 1;
-
- /*
- * make up the imap message sequence numbers
- */
- id = 1;
- for(m = box->msgs; m != nil; m = m->next){
- if(m->seq && m->seq != id)
- bye("internal error assigning message numbers");
- m->seq = id++;
- }
- box->max = id - 1;
-
- return new;
-}
-
-/*
- * read in the .imp file, or make one if it doesn't exist.
- * make sure all flags and uids are consistent.
- * return the mailbox lock.
- */
-#define IMPMAGIC "imap internal mailbox description\n"
-static MbLock*
-openImp(Box *box, int new)
-{
- Qid qid;
- Biobuf b;
- MbLock *ml;
- int fd;
-//ZZZZ
- int once;
-
- ml = mbLock();
- if(ml == nil)
- return nil;
- fd = cdOpen(mboxDir, box->imp, OREAD);
- once = 0;
-ZZZhack:
- if(fd < 0 || fqid(fd, &qid) < 0){
- if(fd < 0){
- char buf[ERRMAX];
-
- errstr(buf, sizeof buf);
- if(cistrstr(buf, "does not exist") == nil)
- fprint(2, "imap4d at %lud: imp open failed: %s\n", time(nil), buf);
- if(!once && cistrstr(buf, "locked") != nil){
- once = 1;
- fprint(2, "imap4d at %lud: imp %s/%s %s locked when it shouldn't be; spinning\n", time(nil), username, box->name, box->imp);
- fd = openLocked(mboxDir, box->imp, OREAD);
- goto ZZZhack;
- }
- }
- if(fd >= 0)
- close(fd);
- fd = createImp(box, &qid);
- if(fd < 0){
- mbUnlock(ml);
- return nil;
- }
- box->dirtyImp = 1;
- if(box->uidvalidity == 0)
- box->uidvalidity = box->mtime;
- box->impQid = qid;
- new = 1;
- }else if(qid.path != box->impQid.path || qid.vers != box->impQid.vers){
- Binit(&b, fd, OREAD);
- if(!parseImp(&b, box)){
- box->dirtyImp = 1;
- if(box->uidvalidity == 0)
- box->uidvalidity = box->mtime;
- }
- Bterm(&b);
- box->impQid = qid;
- new = 1;
- }
- if(new)
- boxFlags(box);
- close(fd);
- return ml;
-}
-
-/*
- * close the .imp file, after writing out any changes
- */
-void
-closeImp(Box *box, MbLock *ml)
-{
- Msg *m;
- Qid qid;
- Biobuf b;
- char buf[NFlags+1];
- int fd;
-
- if(ml == nil)
- return;
- if(!box->dirtyImp){
- mbUnlock(ml);
- return;
- }
-
- fd = cdCreate(mboxDir, box->imp, OWRITE, 0664);
- if(fd < 0){
- mbUnlock(ml);
- return;
- }
- Binit(&b, fd, OWRITE);
-
- box->dirtyImp = 0;
- Bprint(&b, "%s", IMPMAGIC);
- Bprint(&b, "%.*lud %.*lud\n", NUid, box->uidvalidity, NUid, box->uidnext);
- for(m = box->msgs; m != nil; m = m->next){
- if(m->expunged)
- continue;
- wrImpFlags(buf, m->flags, strcmp(box->fs, "imap") == 0);
- Bprint(&b, "%.*s %.*lud %s\n", NDigest, m->info[IDigest], NUid, m->uid, buf);
- }
- Bterm(&b);
-
- if(fqid(fd, &qid) >= 0)
- box->impQid = qid;
- close(fd);
- mbUnlock(ml);
-}
-
-void
-wrImpFlags(char *buf, int flags, int killRecent)
-{
- int i;
-
- for(i = 0; i < NFlags; i++){
- if((flags & flagChars[i].v)
- && (flagChars[i].v != MRecent || !killRecent))
- buf[i] = flagChars[i].name[0];
- else
- buf[i] = '-';
- }
- buf[i] = '\0';
-}
-
-int
-emptyImp(char *mbox)
-{
- Dir *d;
- long mode;
- int fd;
-
- fd = cdCreate(mboxDir, impName(mbox), OWRITE, 0664);
- if(fd < 0)
- return -1;
- d = cdDirstat(mboxDir, mbox);
- if(d == nil){
- close(fd);
- return -1;
- }
- fprint(fd, "%s%.*lud %.*lud\n", IMPMAGIC, NUid, d->mtime, NUid, 1UL);
- mode = d->mode & 0777;
- nulldir(d);
- d->mode = mode;
- dirfwstat(fd, d);
- free(d);
- return fd;
-}
-
-/*
- * try to match permissions with mbox
- */
-static int
-createImp(Box *box, Qid *qid)
-{
- Dir *d;
- long mode;
- int fd;
-
- fd = cdCreate(mboxDir, box->imp, OREAD, 0664);
- if(fd < 0)
- return -1;
- d = cdDirstat(mboxDir, box->name);
- if(d != nil){
- mode = d->mode & 0777;
- nulldir(d);
- d->mode = mode;
- dirfwstat(fd, d);
- free(d);
- }
- if(fqid(fd, qid) < 0){
- close(fd);
- return -1;
- }
-
- return fd;
-}
-
-/*
- * read or re-read a .imp file.
- * this is tricky:
- * messages can be deleted by another agent
- * we might still have a Msg for an expunged message,
- * because we haven't told the client yet.
- * we can have a Msg without a .imp entry.
- * flag information is added at the end of the .imp by copy & append
- * there can be duplicate messages (same digests).
- *
- * look up existing messages based on uid.
- * look up new messages based on in order digest matching.
- *
- * note: in the face of duplicate messages, one of which is deleted,
- * two active servers may decide different ones are valid, and so return
- * different uids for the messages. this situation will stablize when the servers exit.
- */
-static int
-parseImp(Biobuf *b, Box *box)
-{
- Msg *m, *mm;
- char *s, *t, *toks[3];
- ulong uid, u;
- int match, n;
-
- m = box->msgs;
- s = Brdline(b, '\n');
- if(s == nil || Blinelen(b) != STRLEN(IMPMAGIC)
- || strncmp(s, IMPMAGIC, STRLEN(IMPMAGIC)) != 0)
- return 0;
-
- s = Brdline(b, '\n');
- if(s == nil || Blinelen(b) != 2*NUid + 2)
- return 0;
- s[2*NUid + 1] = '\0';
- u = strtoul(s, &t, 10);
- if(u != box->uidvalidity && box->uidvalidity != 0)
- return 0;
- box->uidvalidity = u;
- if(*t != ' ' || t != s + NUid)
- return 0;
- t++;
- u = strtoul(t, &t, 10);
- if(box->uidnext > u)
- return 0;
- box->uidnext = u;
- if(t != s + 2*NUid+1 || box->uidnext == 0)
- return 0;
-
- uid = ~0;
- while(m != nil){
- s = Brdline(b, '\n');
- if(s == nil)
- break;
- n = Blinelen(b) - 1;
- if(n != NDigest + NUid + NFlags + 2
- || s[NDigest] != ' ' || s[NDigest + NUid + 1] != ' ')
- return 0;
- toks[0] = s;
- s[NDigest] = '\0';
- toks[1] = s + NDigest + 1;
- s[NDigest + NUid + 1] = '\0';
- toks[2] = s + NDigest + NUid + 2;
- s[n] = '\0';
- t = toks[1];
- u = strtoul(t, &t, 10);
- if(*t != '\0' || uid != ~0 && (uid >= u && u || u && !uid))
- return 0;
- uid = u;
-
- /*
- * zero uid => added by append or copy, only flags valid
- * can only match messages without uids, but this message
- * may not be the next one, and may have been deleted.
- */
- if(!uid){
- for(; m != nil && m->uid; m = m->next)
- ;
- for(mm = m; mm != nil; mm = mm->next){
- if(mm->info[IDigest] != nil &&
- strcmp(mm->info[IDigest], toks[0]) == 0){
- if(!mm->uid)
- mm->flags = 0;
- if(!impFlags(box, mm, toks[2]))
- return 0;
- m = mm->next;
- break;
- }
- }
- continue;
- }
-
- /*
- * ignore expunged messages,
- * and messages already assigned uids which don't match this uid.
- * such messages must have been deleted by another imap server,
- * which updated the mailbox and .imp file since we read the mailbox,
- * or because upas/fs got confused by consecutive duplicate messages,
- * the first of which was deleted by another imap server.
- */
- for(; m != nil && (m->expunged || m->uid && m->uid < uid); m = m->next)
- ;
- if(m == nil)
- break;
-
- /*
- * only check for digest match on the next message,
- * since it comes before all other messages, and therefore
- * must be in the .imp file if they should be.
- */
- match = m->info[IDigest] != nil &&
- strcmp(m->info[IDigest], toks[0]) == 0;
- if(uid && (m->uid == uid || !m->uid && match)){
- if(!match)
- bye("inconsistent uid");
-
- /*
- * wipe out recent flag if some other server saw this new message.
- * it will be read from the .imp file if is really should be set,
- * ie the message was only seen by a status command.
- */
- if(!m->uid)
- m->flags = 0;
-
- if(!impFlags(box, m, toks[2]))
- return 0;
- m->uid = uid;
- m = m->next;
- }
- }
- return 1;
-}
-
-/*
- * parse .imp flags
- */
-static int
-impFlags(Box *box, Msg *m, char *flags)
-{
- int i, f;
-
- f = 0;
- for(i = 0; i < NFlags; i++){
- if(flags[i] == '-')
- continue;
- if(flags[i] != flagChars[i].name[0])
- return 0;
- f |= flagChars[i].v;
- }
-
- /*
- * recent flags are set until the first time message's box is selected or examined.
- * it may be stored in the file as a side effect of a status or subscribe command;
- * if so, clear it out.
- */
- if((f & MRecent) && strcmp(box->fs, "imap") == 0)
- box->dirtyImp = 1;
- f |= m->flags & MRecent;
-
- /*
- * all old messages with changed flags should be reported to the client
- */
- if(m->uid && m->flags != f){
- box->sendFlags = 1;
- m->sendFlags = 1;
- }
- m->flags = f;
- return 1;
-}
-
-/*
- * assign uids to any new messages
- * which aren't already in the .imp file.
- * sum up totals for flag values.
- */
-static void
-boxFlags(Box *box)
-{
- Msg *m;
-
- box->recent = 0;
- for(m = box->msgs; m != nil; m = m->next){
- if(m->uid == 0){
- box->dirtyImp = 1;
- box->uidnext = uidRenumber(m, box->uidnext, 0);
- }
- if(m->flags & MRecent)
- box->recent++;
- }
-}
-
-static ulong
-uidRenumber(Msg *m, ulong uid, int force)
-{
- for(; m != nil; m = m->next){
- if(!force && m->uid != 0)
- bye("uid renumbering with a valid uid");
- m->uid = uid++;
- }
- return uid;
-}
-
-void
-closeBox(Box *box, int opened)
-{
- Msg *m, *next;
-
- /*
- * make sure to leave the mailbox directory so upas/fs can close the mailbox
- */
- myChdir(mboxDir);
-
- if(box->writable){
- deleteMsgs(box);
- if(expungeMsgs(box, 0))
- closeImp(box, checkBox(box, 1));
- }
-
- if(fprint(fsCtl, "close %s", box->fs) < 0 && opened)
- bye("can't talk to mail server");
- for(m = box->msgs; m != nil; m = next){
- next = m->next;
- freeMsg(m);
- }
- free(box->name);
- free(box->fs);
- free(box->fsDir);
- free(box->imp);
- free(box);
-}
-
-int
-deleteMsgs(Box *box)
-{
- Msg *m;
- char buf[BufSize], *p, *start;
- int ok;
-
- if(!box->writable)
- return 0;
-
- /*
- * first pass: delete messages; gang the writes together for speed.
- */
- ok = 1;
- start = seprint(buf, buf + sizeof(buf), "delete %s", box->fs);
- p = start;
- for(m = box->msgs; m != nil; m = m->next){
- if((m->flags & MDeleted) && !m->expunged){
- m->expunged = 1;
- p = seprint(p, buf + sizeof(buf), " %lud", m->id);
- if(p + 32 >= buf + sizeof(buf)){
- if(write(fsCtl, buf, p - buf) < 0)
- bye("can't talk to mail server");
- p = start;
- }
- }
- }
- if(p != start && write(fsCtl, buf, p - buf) < 0)
- bye("can't talk to mail server");
-
- return ok;
-}
-
-/*
- * second pass: remove the message structure,
- * and renumber message sequence numbers.
- * update messages counts in mailbox.
- * returns true if anything changed.
- */
-int
-expungeMsgs(Box *box, int send)
-{
- Msg *m, *next, *last;
- ulong n;
-
- n = 0;
- last = nil;
- for(m = box->msgs; m != nil; m = next){
- m->seq -= n;
- next = m->next;
- if(m->expunged){
- if(send)
- Bprint(&bout, "* %lud expunge\r\n", m->seq);
- if(m->flags & MRecent)
- box->recent--;
- n++;
- if(last == nil)
- box->msgs = next;
- else
- last->next = next;
- freeMsg(m);
- }else
- last = m;
- }
- if(n){
- box->max -= n;
- box->dirtyImp = 1;
- }
- return n;
-}
-
-static void
-fsInit(void)
-{
- if(fsCtl >= 0)
- return;
- fsCtl = open("/mail/fs/ctl", ORDWR);
- if(fsCtl < 0)
- bye("can't open mail file system");
- if(fprint(fsCtl, "close mbox") < 0)
- bye("can't initialize mail file system");
-}
-
-static char *stoplist[] =
-{
- "mbox",
- "pipeto",
- "forward",
- "names",
- "pipefrom",
- "headers",
- "imap.ok",
- 0
-};
-
-enum {
- Maxokbytes = 4096,
- Maxfolders = Maxokbytes / 4,
-};
-
-static char *folders[Maxfolders];
-static char *folderbuff;
-
-static void
-readokfolders(void)
-{
- int fd, nr;
-
- fd = open("imap.ok", OREAD);
- if(fd < 0)
- return;
- folderbuff = malloc(Maxokbytes);
- if(folderbuff == nil) {
- close(fd);
- return;
- }
- nr = read(fd, folderbuff, Maxokbytes-1); /* once is ok */
- close(fd);
- if(nr < 0){
- free(folderbuff);
- folderbuff = nil;
- return;
- }
- folderbuff[nr] = 0;
- tokenize(folderbuff, folders, nelem(folders));
-}
-
-/*
- * reject bad mailboxes based on mailbox name
- */
-int
-okMbox(char *path)
-{
- char *name;
- int i;
-
- if(folderbuff == nil && access("imap.ok", AREAD) == 0)
- readokfolders();
- name = strrchr(path, '/');
- if(name == nil)
- name = path;
- else
- name++;
- if(folderbuff != nil){
- for(i = 0; i < nelem(folders) && folders[i] != nil; i++)
- if(cistrcmp(folders[i], name) == 0)
- return 1;
- return 0;
- }
- if(strlen(name) + STRLEN(".imp") >= MboxNameLen)
- return 0;
- for(i = 0; stoplist[i]; i++)
- if(strcmp(name, stoplist[i]) == 0)
- return 0;
- if(isprefix("L.", name) || isprefix("imap-tmp.", name)
- || issuffix(".imp", name)
- || strcmp("imap.subscribed", name) == 0
- || isdotdot(name) || name[0] == '/')
- return 0;
- return 1;
-}
--- a/sys/src/cmd/ip/imap4d/mkfile
+++ /dev/null
@@ -1,31 +1,0 @@
-</$objtype/mkfile
-
-OFILES=\
- auth.$O\
- copy.$O\
- csquery.$O\
- date.$O\
- fetch.$O\
- imap4d.$O\
- list.$O\
- mbox.$O\
- msg.$O\
- mutf7.$O\
- nodes.$O\
- folder.$O\
- search.$O\
- store.$O\
- utils.$O\
- debug.$O\
-
-HFILES=imap4d.h\
- fns.h\
-
-TARG=imap4d
-BIN=/$objtype/bin/ip
-UPDATE=\
- mkfile\
- $HFILES\
- ${OFILES:%.$O=%.c}\
-
-</sys/src/cmd/mkone
--- a/sys/src/cmd/ip/imap4d/msg.c
+++ /dev/null
@@ -1,1782 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <libsec.h>
-#include <auth.h>
-#include <fcall.h>
-#include "imap4d.h"
-
-static void body64(int in, int out);
-static void bodystrip(int in, int out);
-static void cleanupHeader(Header *h);
-static char *domBang(char *s);
-static void freeMAddr(MAddr *a);
-static void freeMimeHdr(MimeHdr *mh);
-static char *headAddrSpec(char *e, char *w);
-static MAddr *headAddresses(void);
-static MAddr *headAddress(void);
-static char *headAtom(char *disallowed);
-static int headChar(int eat);
-static char *headDomain(char *e);
-static MAddr *headMAddr(MAddr *old);
-static char *headPhrase(char *e, char *w);
-static char *headQuoted(int start, int stop);
-static char *headSkipWhite(int);
-static void headSkip(void);
-static char *headSubDomain(void);
-static char *headText(void);
-static void headToEnd(void);
-static char *headWord(void);
-static void mimeDescription(Header *h);
-static void mimeDisposition(Header *h);
-static void mimeEncoding(Header *h);
-static void mimeId(Header *h);
-static void mimeLanguage(Header *h);
-static void mimeMd5(Header *h);
-static MimeHdr *mimeParams(void);
-static void mimeType(Header *h);
-static MimeHdr *mkMimeHdr(char *s, char *t, MimeHdr *next);
-static void msgAddDate(Msg *m);
-static void msgAddHead(Msg *m, char *head, char *body);
-static int msgBodySize(Msg *m);
-static int msgHeader(Msg *m, Header *h, char *file);
-static long msgReadFile(Msg *m, char *file, char **ss);
-static int msgUnix(Msg *m, int top);
-static void stripQuotes(char *q);
-static MAddr *unixFrom(char *s);
-
-
-static char bogusBody[] =
- "This message contains null characters, so it cannot be displayed correctly.\r\n"
- "Most likely you were sent a bogus message or a binary file.\r\n"
- "\r\n"
- "Each of the following attachments has a different version of the message.\r\n"
- "The first is inlined with all non-printable characters stripped.\r\n"
- "The second contains the message as it was stored in your mailbox.\r\n"
- "The third has the initial header stripped.\r\n";
-
-static char bogusMimeText[] =
- "Content-Disposition: inline\r\n"
- "Content-Type: text/plain; charset=\"US-ASCII\"\r\n"
- "Content-Transfer-Encoding: 7bit\r\n";
-
-static char bogusMimeBinary[] =
- "Content-Disposition: attachment\r\n"
- "Content-Type: application/octet-stream\r\n"
- "Content-Transfer-Encoding: base64\r\n";
-
-/*
- * stop list for header fields
- */
-static char *headFieldStop = ":";
-static char *mimeTokenStop = "()<>@,;:\\\"/[]?=";
-static char *headAtomStop = "()<>@,;:\\\".[]";
-static uchar *headStr;
-static uchar *lastWhite;
-
-long
-selectFields(char *dst, long n, char *hdr, SList *fields, int matches)
-{
- SList *f;
- uchar *start;
- char *s;
- long m, nf;
-
- headStr = (uchar*)hdr;
- m = 0;
- for(;;){
- start = headStr;
- s = headAtom(headFieldStop);
- if(s == nil)
- break;
- headSkip();
- for(f = fields; f != nil; f = f->next){
- if(cistrcmp(s, f->s) == !matches){
- nf = headStr - start;
- if(m + nf > n)
- return 0;
- memmove(&dst[m], start, nf);
- m += nf;
- }
- }
- free(s);
- }
- if(m + 3 > n)
- return 0;
- dst[m++] = '\r';
- dst[m++] = '\n';
- dst[m] = '\0';
- return m;
-}
-
-void
-freeMsg(Msg *m)
-{
- Msg *k, *last;
-
- free(m->iBuf);
- freeMAddr(m->to);
- if(m->replyTo != m->from)
- freeMAddr(m->replyTo);
- if(m->sender != m->from)
- freeMAddr(m->sender);
- if(m->from != m->unixFrom)
- freeMAddr(m->from);
- freeMAddr(m->unixFrom);
- freeMAddr(m->cc);
- freeMAddr(m->bcc);
- free(m->unixDate);
- cleanupHeader(&m->head);
- cleanupHeader(&m->mime);
- for(k = m->kids; k != nil; ){
- last = k;
- k = k->next;
- freeMsg(last);
- }
- free(m->fs);
- free(m);
-}
-
-ulong
-msgSize(Msg *m)
-{
- return m->head.size + m->size;
-}
-
-int
-infoIsNil(char *s)
-{
- return s == nil || s[0] == '\0';
-}
-
-char*
-maddrStr(MAddr *a)
-{
- char *host, *addr;
- int n;
-
- host = a->host;
- if(host == nil)
- host = "";
- n = strlen(a->box) + strlen(host) + 2;
- if(a->personal != nil)
- n += strlen(a->personal) + 3;
- addr = emalloc(n);
- if(a->personal != nil)
- snprint(addr, n, "%s <%s@%s>", a->personal, a->box, host);
- else
- snprint(addr, n, "%s@%s", a->box, host);
- return addr;
-}
-
-/*
- * return actual name of f in m's fs directory
- * this is special cased when opening m/rawbody, m/mimeheader, or m/rawheader,
- * if the message was corrupted. in that case,
- * a temporary file is made to hold the base64 encoding of m/raw.
- */
-int
-msgFile(Msg *m, char *f)
-{
- Msg *parent, *p;
- Dir d;
- Tm tm;
- char buf[64], nbuf[2];
- uchar dbuf[64];
- int i, n, fd, fd1, fd2;
-
- if(!m->bogus
- || strcmp(f, "") != 0 && strcmp(f, "rawbody") != 0
- && strcmp(f, "rawheader") != 0 && strcmp(f, "mimeheader") != 0
- && strcmp(f, "info") != 0 && strcmp(f, "unixheader") != 0){
- if(strlen(f) > MsgNameLen)
- bye("internal error: msgFile name too long");
- strcpy(m->efs, f);
- return cdOpen(m->fsDir, m->fs, OREAD);
- }
-
- /*
- * walk up the stupid runt message parts for non-multipart messages
- */
- parent = m->parent;
- if(parent != nil && parent->parent != nil){
- m = parent;
- parent = m->parent;
- }
- p = m;
- if(parent != nil)
- p = parent;
-
- if(strcmp(f, "info") == 0 || strcmp(f, "unixheader") == 0){
- strcpy(p->efs, f);
- return cdOpen(p->fsDir, p->fs, OREAD);
- }
-
- fd = imapTmp();
- if(fd < 0)
- return -1;
-
- /*
- * craft the message parts for bogus messages
- */
- if(strcmp(f, "") == 0){
- /*
- * make a fake directory for each kid
- * all we care about is the name
- */
- if(parent == nil){
- nulldir(&d);
- d.mode = DMDIR|0600;
- d.qid.type = QTDIR;
- d.name = nbuf;
- nbuf[1] = '\0';
- for(i = '1'; i <= '4'; i++){
- nbuf[0] = i;
- n = convD2M(&d, dbuf, sizeof(dbuf));
- if(n <= BIT16SZ)
- fprint(2, "bad convD2M %d\n", n);
- write(fd, dbuf, n);
- }
- }
- }else if(strcmp(f, "mimeheader") == 0){
- if(parent != nil){
- switch(m->id){
- case 1:
- case 2:
- fprint(fd, "%s", bogusMimeText);
- break;
- case 3:
- case 4:
- fprint(fd, "%s", bogusMimeBinary);
- break;
- }
- }
- }else if(strcmp(f, "rawheader") == 0){
- if(parent == nil){
- date2tm(&tm, m->unixDate);
- rfc822date(buf, sizeof(buf), &tm);
- fprint(fd,
- "Date: %s\r\n"
- "From: imap4 daemon <%s@%s>\r\n"
- "To: <%s@%s>\r\n"
- "Subject: This message was illegal or corrupted\r\n"
- "MIME-Version: 1.0\r\n"
- "Content-Type: multipart/mixed;\r\n\tboundary=\"upas-%s\"\r\n",
- buf, username, site, username, site, m->info[IDigest]);
- }
- }else if(strcmp(f, "rawbody") == 0){
- fd1 = msgFile(p, "raw");
- strcpy(p->efs, "rawbody");
- fd2 = cdOpen(p->fsDir, p->fs, OREAD);
- if(fd1 < 0 || fd2 < 0){
- close(fd);
- close(fd1);
- close(fd2);
- return -1;
- }
- if(parent == nil){
- fprint(fd,
- "This is a multi-part message in MIME format.\r\n"
- "--upas-%s\r\n"
- "%s"
- "\r\n"
- "%s"
- "\r\n",
- m->info[IDigest], bogusMimeText, bogusBody);
-
- fprint(fd,
- "--upas-%s\r\n"
- "%s"
- "\r\n",
- m->info[IDigest], bogusMimeText);
- bodystrip(fd1, fd);
-
- fprint(fd,
- "--upas-%s\r\n"
- "%s"
- "\r\n",
- m->info[IDigest], bogusMimeBinary);
- seek(fd1, 0, 0);
- body64(fd1, fd);
-
- fprint(fd,
- "--upas-%s\r\n"
- "%s"
- "\r\n",
- m->info[IDigest], bogusMimeBinary);
- body64(fd2, fd);
-
- fprint(fd, "--upas-%s--\r\n", m->info[IDigest]);
- }else{
- switch(m->id){
- case 1:
- fprint(fd, "%s", bogusBody);
- break;
- case 2:
- bodystrip(fd1, fd);
- break;
- case 3:
- body64(fd1, fd);
- break;
- case 4:
- body64(fd2, fd);
- break;
- }
- }
- close(fd1);
- close(fd2);
- }
- seek(fd, 0, 0);
- return fd;
-}
-
-int
-msgIsMulti(Header *h)
-{
- return h->type != nil && cistrcmp("multipart", h->type->s) == 0;
-}
-
-int
-msgIsRfc822(Header *h)
-{
- return h->type != nil && cistrcmp("message", h->type->s) == 0 && cistrcmp("rfc822", h->type->t) == 0;
-}
-
-/*
- * check if a message has been deleted by someone else
- */
-void
-msgDead(Msg *m)
-{
- if(m->expunged)
- return;
- *m->efs = '\0';
- if(!cdExists(m->fsDir, m->fs))
- m->expunged = 1;
-}
-
-/*
- * make sure the message has valid associated info
- * used for ISubject, IDigest, IInReplyTo, IMessageId.
- */
-int
-msgInfo(Msg *m)
-{
- char *s;
- int i;
-
- if(m->info[0] != nil)
- return 1;
-
- i = msgReadFile(m, "info", &m->iBuf);
- if(i < 0)
- return 0;
-
- s = m->iBuf;
- for(i = 0; i < IMax; i++){
- m->info[i] = s;
- s = strchr(s, '\n');
- if(s == nil)
- break;
- *s++ = '\0';
- }
- for(; i < IMax; i++)
- m->info[i] = nil;
-
- for(i = 0; i < IMax; i++)
- if(infoIsNil(m->info[i]))
- m->info[i] = nil;
-
- return 1;
-}
-
-/*
- * make sure the message has valid mime structure
- * and sub-messages
- */
-int
-msgStruct(Msg *m, int top)
-{
- Msg *k, head, *last;
- Dir *d;
- char *s;
- ulong max, id;
- int i, nd, fd, ns;
-
- if(m->kids != nil)
- return 1;
-
- if(m->expunged
- || !msgInfo(m)
- || !msgUnix(m, top)
- || !msgBodySize(m)
- || !msgHeader(m, &m->mime, "mimeheader")
- || (top || msgIsRfc822(&m->mime) || msgIsMulti(&m->mime)) && !msgHeader(m, &m->head, "rawheader")){
- if(top && m->bogus && !(m->bogus & BogusTried)){
- m->bogus |= BogusTried;
- return msgStruct(m, top);
- }
- msgDead(m);
- return 0;
- }
-
- /*
- * if a message has no kids, it has a kid which is just the body of the real message
- */
- if(!msgIsMulti(&m->head) && !msgIsMulti(&m->mime) && !msgIsRfc822(&m->head) && !msgIsRfc822(&m->mime)){
- k = MKZ(Msg);
- k->id = 1;
- k->fsDir = m->fsDir;
- k->bogus = m->bogus;
- k->parent = m->parent;
- ns = m->efs - m->fs;
- k->fs = emalloc(ns + (MsgNameLen + 1));
- memmove(k->fs, m->fs, ns);
- k->efs = k->fs + ns;
- *k->efs = '\0';
- k->size = m->size;
- m->kids = k;
- return 1;
- }
-
- /*
- * read in all child messages messages
- */
- fd = msgFile(m, "");
- if(fd < 0){
- msgDead(m);
- return 0;
- }
-
- max = 0;
- head.next = nil;
- last = &head;
- while((nd = dirread(fd, &d)) > 0){
- for(i = 0; i < nd; i++){
- s = d[i].name;
- id = strtol(s, &s, 10);
- if(id <= max || *s != '\0'
- || (d[i].mode & DMDIR) != DMDIR)
- continue;
-
- max = id;
-
- k = MKZ(Msg);
- k->id = id;
- k->fsDir = m->fsDir;
- k->bogus = m->bogus;
- k->parent = m;
- ns = strlen(m->fs);
- k->fs = emalloc(ns + 2 * (MsgNameLen + 1));
- k->efs = seprint(k->fs, k->fs + ns + (MsgNameLen + 1), "%s%lud/", m->fs, id);
- k->prev = last;
- k->size = ~0UL;
- k->lines = ~0UL;
- last->next = k;
- last = k;
- }
- }
- close(fd);
- m->kids = head.next;
-
- /*
- * if kids fail, just whack them
- */
- top = top && (msgIsRfc822(&m->head) || msgIsMulti(&m->head));
- for(k = m->kids; k != nil; k = k->next){
- if(!msgStruct(k, top)){
- for(k = m->kids; k != nil; ){
- last = k;
- k = k->next;
- freeMsg(last);
- }
- m->kids = nil;
- break;
- }
- }
- return 1;
-}
-
-static long
-msgReadFile(Msg *m, char *file, char **ss)
-{
- Dir *d;
- char *s, buf[BufSize];
- vlong length;
- long n, nn;
- int fd;
-
- fd = msgFile(m, file);
- if(fd < 0){
- msgDead(m);
- return -1;
- }
-
- n = read(fd, buf, BufSize);
- if(n < BufSize){
- close(fd);
- if(n < 0){
- *ss = nil;
- return -1;
- }
- s = emalloc(n + 1);
- memmove(s, buf, n);
- s[n] = '\0';
- *ss = s;
- return n;
- }
-
- d = dirfstat(fd);
- if(d == nil){
- close(fd);
- return -1;
- }
- length = d->length;
- free(d);
- nn = length;
- s = emalloc(nn + 1);
- memmove(s, buf, n);
- if(nn > n)
- nn = readn(fd, s+n, nn-n) + n;
- close(fd);
- if(nn != length){
- free(s);
- return -1;
- }
- s[nn] = '\0';
- *ss = s;
- return nn;
-}
-
-static void
-freeMAddr(MAddr *a)
-{
- MAddr *p;
-
- while(a != nil){
- p = a;
- a = a->next;
- free(p->personal);
- free(p->box);
- free(p->host);
- free(p);
- }
-}
-
-/*
- * the message is corrupted or illegal.
- * reset message fields. msgStruct will reparse the message,
- * relying on msgFile to make up corrected body parts.
- */
-static int
-msgBogus(Msg *m, int flags)
-{
- if(!(m->bogus & flags))
- m->bogus |= flags;
- m->lines = ~0;
- free(m->head.buf);
- free(m->mime.buf);
- memset(&m->head, 0, sizeof(Header));
- memset(&m->mime, 0, sizeof(Header));
- return 0;
-}
-
-/*
- * stolen from upas/marshal; base64 encodes from one fd to another.
- *
- * the size of buf is very important to enc64. Anything other than
- * a multiple of 3 will cause enc64 to output a termination sequence.
- * To ensure that a full buf corresponds to a multiple of complete lines,
- * we make buf a multiple of 3*18 since that's how many enc64 sticks on
- * a single line. This avoids short lines in the output which is pleasing
- * but not necessary.
- */
-static int
-enc64x18(char *out, int lim, uchar *in, int n)
-{
- int m, mm, nn;
-
- for(nn = 0; n > 0; n -= m, nn += mm){
- m = 18 * 3;
- if(m > n)
- m = n;
- nn += 2; /* \r\n */
- assert(nn < lim);
- mm = enc64(out, lim - nn, in, m);
- assert(mm > 0);
- in += m;
- out += mm;
- *out++ = '\r';
- *out++ = '\n';
- }
- return nn;
-}
-
-static void
-body64(int in, int out)
-{
- uchar buf[3*18*54];
- char obuf[3*18*54*2];
- int m, n;
-
- for(;;){
- n = readn(in, buf, sizeof(buf));
- if(n < 0)
- return;
- if(n == 0)
- break;
- m = enc64x18(obuf, sizeof(obuf), buf, n);
- if(write(out, obuf, m) < 0)
- return;
- }
-}
-
-/*
- * strip all non-printable characters from a file
- */
-static void
-bodystrip(int in, int out)
-{
- uchar buf[3*18*54];
- int m, n, i, c;
-
- for(;;){
- n = read(in, buf, sizeof(buf));
- if(n < 0)
- return;
- if(n == 0)
- break;
- m = 0;
- for(i = 0; i < n; i++){
- c = buf[i];
- if(c > 0x1f && c < 0x7f /* normal characters */
- || c >= 0x9 && c <= 0xd) /* \t, \n, vertical tab, form feed, \r */
- buf[m++] = c;
- }
-
- if(m && write(out, buf, m) < 0)
- return;
- }
-}
-
-/*
- * read in the message body to count \n without a preceding \r
- */
-static int
-msgBodySize(Msg *m)
-{
- Dir *d;
- char buf[BufSize + 2], *s, *se;
- vlong length;
- ulong size, lines, bad;
- int n, fd, c;
-
- if(m->lines != ~0UL)
- return 1;
- fd = msgFile(m, "rawbody");
- if(fd < 0)
- return 0;
- d = dirfstat(fd);
- if(d == nil){
- close(fd);
- return 0;
- }
- length = d->length;
- free(d);
-
- size = 0;
- lines = 0;
- bad = 0;
- buf[0] = ' ';
- for(;;){
- n = read(fd, &buf[1], BufSize);
- if(n <= 0)
- break;
- size += n;
- se = &buf[n + 1];
- for(s = &buf[1]; s < se; s++){
- c = *s;
- if(c == '\0'){
- close(fd);
- return msgBogus(m, BogusBody);
- }
- if(c != '\n')
- continue;
- if(s[-1] != '\r')
- bad++;
- lines++;
- }
- buf[0] = buf[n];
- }
- if(size != length)
- bye("bad length reading rawbody");
- size += bad;
- m->size = size;
- m->lines = lines;
- close(fd);
- return 1;
-}
-
-/*
- * retrieve information from the unixheader file
- */
-static int
-msgUnix(Msg *m, int top)
-{
- Tm tm;
- char *s, *ss;
-
- if(m->unixDate != nil)
- return 1;
-
- if(!top){
-bogus:
- m->unixDate = estrdup("");
- m->unixFrom = unixFrom(nil);
- return 1;
- }
-
- if(msgReadFile(m, "unixheader", &ss) < 0)
- return 0;
- s = ss;
- s = strchr(s, ' ');
- if(s == nil){
- free(ss);
- goto bogus;
- }
- s++;
- m->unixFrom = unixFrom(s);
- s = (char*)headStr;
- if(date2tm(&tm, s) == nil)
- s = m->info[IUnixDate];
- if(s == nil){
- free(ss);
- goto bogus;
- }
- m->unixDate = estrdup(s);
- free(ss);
- return 1;
-}
-
-/*
- * parse the address in the unix header
- * last line of defence, so must return something
- */
-static MAddr *
-unixFrom(char *s)
-{
- MAddr *a;
- char *e, *t;
-
- if(s == nil)
- return nil;
- headStr = (uchar*)s;
- t = emalloc(strlen(s) + 2);
- e = headAddrSpec(t, nil);
- if(e == nil)
- a = nil;
- else{
- if(*e != '\0')
- *e++ = '\0';
- else
- e = site;
- a = MKZ(MAddr);
-
- a->box = estrdup(t);
- a->host = estrdup(e);
- }
- free(t);
- return a;
-}
-
-/*
- * read in the entire header,
- * and parse out any existing mime headers
- */
-static int
-msgHeader(Msg *m, Header *h, char *file)
-{
- char *s, *ss, *t, *te;
- ulong lines, n, nn;
- long ns;
- int dated, c;
-
- if(h->buf != nil)
- return 1;
-
- ns = msgReadFile(m, file, &ss);
- if(ns < 0)
- return 0;
- s = ss;
- n = ns;
-
- /*
- * count lines ending with \n and \r\n
- * add an extra line at the end, since upas/fs headers
- * don't have a terminating \r\n
- */
- lines = 1;
- te = s + ns;
- for(t = s; t < te; t++){
- c = *t;
- if(c == '\0')
- return msgBogus(m, BogusHeader);
- if(c != '\n')
- continue;
- if(t == s || t[-1] != '\r')
- n++;
- lines++;
- }
- if(t > s && t[-1] != '\n'){
- if(t[-1] != '\r')
- n++;
- n++;
- }
- n += 2;
- h->buf = emalloc(n + 1);
- h->size = n;
- h->lines = lines;
-
- /*
- * make sure all headers end in \r\n
- */
- nn = 0;
- for(t = s; t < te; t++){
- c = *t;
- if(c == '\n'){
- if(!nn || h->buf[nn - 1] != '\r')
- h->buf[nn++] = '\r';
- lines++;
- }
- h->buf[nn++] = c;
- }
- if(nn && h->buf[nn-1] != '\n'){
- if(h->buf[nn-1] != '\r')
- h->buf[nn++] = '\r';
- h->buf[nn++] = '\n';
- }
- h->buf[nn++] = '\r';
- h->buf[nn++] = '\n';
- h->buf[nn] = '\0';
- if(nn != n)
- bye("misconverted header %ld %ld", nn, n);
- free(s);
-
- /*
- * and parse some mime headers
- */
- headStr = (uchar*)h->buf;
- dated = 0;
- while(s = headAtom(headFieldStop)){
- if(cistrcmp(s, "content-type") == 0)
- mimeType(h);
- else if(cistrcmp(s, "content-transfer-encoding") == 0)
- mimeEncoding(h);
- else if(cistrcmp(s, "content-id") == 0)
- mimeId(h);
- else if(cistrcmp(s, "content-description") == 0)
- mimeDescription(h);
- else if(cistrcmp(s, "content-disposition") == 0)
- mimeDisposition(h);
- else if(cistrcmp(s, "content-md5") == 0)
- mimeMd5(h);
- else if(cistrcmp(s, "content-language") == 0)
- mimeLanguage(h);
- else if(h == &m->head && cistrcmp(s, "from") == 0)
- m->from = headMAddr(m->from);
- else if(h == &m->head && cistrcmp(s, "to") == 0)
- m->to = headMAddr(m->to);
- else if(h == &m->head && cistrcmp(s, "reply-to") == 0)
- m->replyTo = headMAddr(m->replyTo);
- else if(h == &m->head && cistrcmp(s, "sender") == 0)
- m->sender = headMAddr(m->sender);
- else if(h == &m->head && cistrcmp(s, "cc") == 0)
- m->cc = headMAddr(m->cc);
- else if(h == &m->head && cistrcmp(s, "bcc") == 0)
- m->bcc = headMAddr(m->bcc);
- else if(h == &m->head && cistrcmp(s, "date") == 0)
- dated = 1;
- headSkip();
- free(s);
- }
-
- if(h == &m->head){
- if(m->from == nil){
- m->from = m->unixFrom;
- if(m->from != nil){
- s = maddrStr(m->from);
- msgAddHead(m, "From", s);
- free(s);
- }
- }
- if(m->sender == nil)
- m->sender = m->from;
- if(m->replyTo == nil)
- m->replyTo = m->from;
-
- if(infoIsNil(m->info[IDate]))
- m->info[IDate] = m->unixDate;
- if(!dated && m->from != nil)
- msgAddDate(m);
- }
- return 1;
-}
-
-/*
- * prepend head: body to the cached header
- */
-static void
-msgAddHead(Msg *m, char *head, char *body)
-{
- char *s;
- long size, n;
-
- n = strlen(head) + strlen(body) + 4;
- size = m->head.size + n;
- s = emalloc(size + 1);
- snprint(s, size + 1, "%s: %s\r\n%s", head, body, m->head.buf);
- free(m->head.buf);
- m->head.buf = s;
- m->head.size = size;
- m->head.lines++;
-}
-
-static void
-msgAddDate(Msg *m)
-{
- Tm tm;
- char buf[64];
-
- /* don't bother if we don't have a date */
- if(infoIsNil(m->info[IDate]))
- return;
-
- date2tm(&tm, m->info[IDate]);
- rfc822date(buf, sizeof(buf), &tm);
- msgAddHead(m, "Date", buf);
-}
-
-static MimeHdr*
-mkMimeHdr(char *s, char *t, MimeHdr *next)
-{
- MimeHdr *mh;
-
- mh = MK(MimeHdr);
- mh->s = s;
- mh->t = t;
- mh->next = next;
- return mh;
-}
-
-static void
-freeMimeHdr(MimeHdr *mh)
-{
- MimeHdr *last;
-
- while(mh != nil){
- last = mh;
- mh = mh->next;
- free(last->s);
- free(last->t);
- free(last);
- }
-}
-
-static void
-cleanupHeader(Header *h)
-{
- freeMimeHdr(h->type);
- freeMimeHdr(h->id);
- freeMimeHdr(h->description);
- freeMimeHdr(h->encoding);
- freeMimeHdr(h->md5);
- freeMimeHdr(h->disposition);
- freeMimeHdr(h->language);
-}
-
-/*
- * parser for rfc822 & mime header fields
- */
-
-/*
- * type : 'content-type' ':' token '/' token params
- */
-static void
-mimeType(Header *h)
-{
- char *s, *t;
-
- if(headChar(1) != ':')
- return;
- s = headAtom(mimeTokenStop);
- if(s == nil || headChar(1) != '/'){
- free(s);
- return;
- }
- t = headAtom(mimeTokenStop);
- if(t == nil){
- free(s);
- return;
- }
- h->type = mkMimeHdr(s, t, mimeParams());
-}
-
-/*
- * params :
- * | params ';' token '=' token
- * | params ';' token '=' quoted-str
- */
-static MimeHdr*
-mimeParams(void)
-{
- MimeHdr head, *last;
- char *s, *t;
-
- head.next = nil;
- last = &head;
- for(;;){
- if(headChar(1) != ';')
- break;
- s = headAtom(mimeTokenStop);
- if(s == nil || headChar(1) != '='){
- free(s);
- break;
- }
- if(headChar(0) == '"'){
- t = headQuoted('"', '"');
- stripQuotes(t);
- }else
- t = headAtom(mimeTokenStop);
- if(t == nil){
- free(s);
- break;
- }
- last->next = mkMimeHdr(s, t, nil);
- last = last->next;
- }
- return head.next;
-}
-
-/*
- * encoding : 'content-transfer-encoding' ':' token
- */
-static void
-mimeEncoding(Header *h)
-{
- char *s;
-
- if(headChar(1) != ':')
- return;
- s = headAtom(mimeTokenStop);
- if(s == nil)
- return;
- h->encoding = mkMimeHdr(s, nil, nil);
-}
-
-/*
- * mailaddr : ':' addresses
- */
-static MAddr*
-headMAddr(MAddr *old)
-{
- MAddr *a;
-
- if(headChar(1) != ':')
- return old;
-
- if(headChar(0) == '\n')
- return old;
-
- a = headAddresses();
- if(a == nil)
- return old;
-
- freeMAddr(old);
- return a;
-}
-
-/*
- * addresses : address | addresses ',' address
- */
-static MAddr*
-headAddresses(void)
-{
- MAddr *addr, *tail, *a;
-
- addr = headAddress();
- if(addr == nil)
- return nil;
- tail = addr;
- while(headChar(0) == ','){
- headChar(1);
- a = headAddress();
- if(a == nil){
- freeMAddr(addr);
- return nil;
- }
- tail->next = a;
- tail = a;
- }
- return addr;
-}
-
-/*
- * address : mailbox | group
- * group : phrase ':' mboxes ';' | phrase ':' ';'
- * mailbox : addr-spec
- * | optphrase '<' addr-spec '>'
- * | optphrase '<' route ':' addr-spec '>'
- * optphrase : | phrase
- * route : '@' domain
- * | route ',' '@' domain
- * personal names are the phrase before '<',
- * or a comment before or after a simple addr-spec
- */
-static MAddr*
-headAddress(void)
-{
- MAddr *addr;
- uchar *hs;
- char *s, *e, *w, *personal;
- int c;
-
- s = emalloc(strlen((char*)headStr) + 2);
- e = s;
- personal = headSkipWhite(1);
- c = headChar(0);
- if(c == '<')
- w = nil;
- else{
- w = headWord();
- c = headChar(0);
- }
- if(c == '.' || c == '@' || c == ',' || c == '\n' || c == '\0'){
- lastWhite = headStr;
- e = headAddrSpec(s, w);
- if(personal == nil){
- hs = headStr;
- headStr = lastWhite;
- personal = headSkipWhite(1);
- headStr = hs;
- }
- }else{
- if(c != '<' || w != nil){
- free(personal);
- if(!headPhrase(e, w)){
- free(s);
- return nil;
- }
-
- /*
- * ignore addresses with groups,
- * so the only thing left if <
- */
- c = headChar(1);
- if(c != '<'){
- free(s);
- return nil;
- }
- personal = estrdup(s);
- }else
- headChar(1);
-
- /*
- * after this point, we need to free personal before returning.
- * set e to nil to everything afterwards fails.
- *
- * ignore routes, they are useless, and heavily discouraged in rfc1123.
- * imap4 reports them up to, but not including, the terminating :
- */
- e = s;
- c = headChar(0);
- if(c == '@'){
- for(;;){
- c = headChar(1);
- if(c != '@'){
- e = nil;
- break;
- }
- headDomain(e);
- c = headChar(1);
- if(c != ','){
- e = s;
- break;
- }
- }
- if(c != ':')
- e = nil;
- }
-
- if(e != nil)
- e = headAddrSpec(s, nil);
- if(headChar(1) != '>')
- e = nil;
- }
-
- /*
- * e points to @host, or nil if an error occured
- */
- if(e == nil){
- free(personal);
- addr = nil;
- }else{
- if(*e != '\0')
- *e++ = '\0';
- else
- e = site;
- addr = MKZ(MAddr);
-
- addr->personal = personal;
- addr->box = estrdup(s);
- addr->host = estrdup(e);
- }
- free(s);
- return addr;
-}
-
-/*
- * phrase : word
- * | phrase word
- * w is the optional initial word of the phrase
- * returns the end of the phrase, or nil if a failure occured
- */
-static char*
-headPhrase(char *e, char *w)
-{
- int c;
-
- for(;;){
- if(w == nil){
- w = headWord();
- if(w == nil)
- return nil;
- }
- if(w[0] == '"')
- stripQuotes(w);
- strcpy(e, w);
- free(w);
- w = nil;
- e = strchr(e, '\0');
- c = headChar(0);
- if(c <= ' ' || strchr(headAtomStop, c) != nil && c != '"')
- break;
- *e++ = ' ';
- *e = '\0';
- }
- return e;
-}
-
-/*
- * addr-spec : local-part '@' domain
- * | local-part extension to allow ! and local names
- * local-part : word
- * | local-part '.' word
- *
- * if no '@' is present, rewrite d!e!f!u as @d,@e:u@f,
- * where d, e, f are valid domain components.
- * the @d,@e: is ignored, since routes are ignored.
- * perhaps they should be rewritten as e!f!u@d, but that is inconsistent with upas.
- *
- * returns a pointer to '@', the end if none, or nil if there was an error
- */
-static char*
-headAddrSpec(char *e, char *w)
-{
- char *s, *at, *b, *bang, *dom;
- int c;
-
- s = e;
- for(;;){
- if(w == nil){
- w = headWord();
- if(w == nil)
- return nil;
- }
- strcpy(e, w);
- free(w);
- w = nil;
- e = strchr(e, '\0');
- lastWhite = headStr;
- c = headChar(0);
- if(c != '.')
- break;
- headChar(1);
- *e++ = '.';
- *e = '\0';
- }
-
- if(c != '@'){
- /*
- * extenstion: allow name without domain
- * check for domain!xxx
- */
- bang = domBang(s);
- if(bang == nil)
- return e;
-
- /*
- * if dom1!dom2!xxx, ignore dom1!
- */
- dom = s;
- for(; b = domBang(bang + 1); bang = b)
- dom = bang + 1;
-
- /*
- * convert dom!mbox into mbox@dom
- */
- *bang = '@';
- strrev(dom, bang);
- strrev(bang+1, e);
- strrev(dom, e);
- bang = &dom[e - bang - 1];
- if(dom > s){
- bang -= dom - s;
- for(e = s; *e = *dom; e++)
- dom++;
- }
-
- /*
- * eliminate a trailing '.'
- */
- if(e[-1] == '.')
- e[-1] = '\0';
- return bang;
- }
- headChar(1);
-
- at = e;
- *e++ = '@';
- *e = '\0';
- if(!headDomain(e))
- return nil;
- return at;
-}
-
-/*
- * find the ! in domain!rest, where domain must have at least
- * one internal '.'
- */
-static char*
-domBang(char *s)
-{
- int dot, c;
-
- dot = 0;
- for(; c = *s; s++){
- if(c == '!'){
- if(!dot || dot == 1 && s[-1] == '.' || s[1] == '\0')
- return nil;
- return s;
- }
- if(c == '"')
- break;
- if(c == '.')
- dot++;
- }
- return nil;
-}
-
-/*
- * domain : sub-domain
- * | domain '.' sub-domain
- * returns the end of the domain, or nil if a failure occured
- */
-static char*
-headDomain(char *e)
-{
- char *w;
-
- for(;;){
- w = headSubDomain();
- if(w == nil)
- return nil;
- strcpy(e, w);
- free(w);
- e = strchr(e, '\0');
- lastWhite = headStr;
- if(headChar(0) != '.')
- break;
- headChar(1);
- *e++ = '.';
- *e = '\0';
- }
- return e;
-}
-
-/*
- * id : 'content-id' ':' msg-id
- * msg-id : '<' addr-spec '>'
- */
-static void
-mimeId(Header *h)
-{
- char *s, *e, *w;
-
- if(headChar(1) != ':')
- return;
- if(headChar(1) != '<')
- return;
-
- s = emalloc(strlen((char*)headStr) + 3);
- e = s;
- *e++ = '<';
- e = headAddrSpec(e, nil);
- if(e == nil || headChar(1) != '>'){
- free(s);
- return;
- }
- e = strchr(e, '\0');
- *e++ = '>';
- e[0] = '\0';
- w = strdup(s);
- free(s);
- h->id = mkMimeHdr(w, nil, nil);
-}
-
-/*
- * description : 'content-description' ':' *text
- */
-static void
-mimeDescription(Header *h)
-{
- if(headChar(1) != ':')
- return;
- headSkipWhite(0);
- h->description = mkMimeHdr(headText(), nil, nil);
-}
-
-/*
- * disposition : 'content-disposition' ':' token params
- */
-static void
-mimeDisposition(Header *h)
-{
- char *s;
-
- if(headChar(1) != ':')
- return;
- s = headAtom(mimeTokenStop);
- if(s == nil)
- return;
- h->disposition = mkMimeHdr(s, nil, mimeParams());
-}
-
-/*
- * md5 : 'content-md5' ':' token
- */
-static void
-mimeMd5(Header *h)
-{
- char *s;
-
- if(headChar(1) != ':')
- return;
- s = headAtom(mimeTokenStop);
- if(s == nil)
- return;
- h->md5 = mkMimeHdr(s, nil, nil);
-}
-
-/*
- * language : 'content-language' ':' langs
- * langs : token
- * | langs commas token
- * commas : ','
- * | commas ','
- */
-static void
-mimeLanguage(Header *h)
-{
- MimeHdr head, *last;
- char *s;
-
- head.next = nil;
- last = &head;
- for(;;){
- s = headAtom(mimeTokenStop);
- if(s == nil)
- break;
- last->next = mkMimeHdr(s, nil, nil);
- last = last->next;
- while(headChar(0) != ',')
- headChar(1);
- }
- h->language = head.next;
-}
-
-/*
- * token : 1*<char 33-255, except "()<>@,;:\\\"/[]?=" aka mimeTokenStop>
- * atom : 1*<chars 33-255, except "()<>@,;:\\\".[]" aka headAtomStop>
- * note this allows 8 bit characters, which occur in utf.
- */
-static char*
-headAtom(char *disallowed)
-{
- char *s;
- int c, ns, as;
-
- headSkipWhite(0);
-
- s = emalloc(StrAlloc);
- as = StrAlloc;
- ns = 0;
- for(;;){
- c = *headStr++;
- if(c <= ' ' || strchr(disallowed, c) != nil){
- headStr--;
- break;
- }
- s[ns++] = c;
- if(ns >= as){
- as += StrAlloc;
- s = erealloc(s, as);
- }
- }
- if(ns == 0){
- free(s);
- return 0;
- }
- s[ns] = '\0';
- return s;
-}
-
-/*
- * sub-domain : atom | domain-lit
- */
-static char *
-headSubDomain(void)
-{
- if(headChar(0) == '[')
- return headQuoted('[', ']');
- return headAtom(headAtomStop);
-}
-
-/*
- * word : atom | quoted-str
- */
-static char *
-headWord(void)
-{
- if(headChar(0) == '"')
- return headQuoted('"', '"');
- return headAtom(headAtomStop);
-}
-
-/*
- * q is a quoted string. remove enclosing " and and \ escapes
- */
-static void
-stripQuotes(char *q)
-{
- char *s;
- int c;
-
- if(q == nil)
- return;
- s = q++;
- while(c = *q++){
- if(c == '\\'){
- c = *q++;
- if(!c)
- return;
- }
- *s++ = c;
- }
- s[-1] = '\0';
-}
-
-/*
- * quoted-str : '"' *(any char but '"\\\r', or '\' any char, or linear-white-space) '"'
- * domain-lit : '[' *(any char but '[]\\\r', or '\' any char, or linear-white-space) ']'
- */
-static char *
-headQuoted(int start, int stop)
-{
- char *s;
- int c, ns, as;
-
- if(headChar(1) != start)
- return nil;
- s = emalloc(StrAlloc);
- as = StrAlloc;
- ns = 0;
- s[ns++] = start;
- for(;;){
- c = *headStr;
- if(c == stop){
- headStr++;
- break;
- }
- if(c == '\0'){
- free(s);
- return nil;
- }
- if(c == '\r'){
- headStr++;
- continue;
- }
- if(c == '\n'){
- headStr++;
- while(*headStr == ' ' || *headStr == '\t' || *headStr == '\r' || *headStr == '\n')
- headStr++;
- c = ' ';
- }else if(c == '\\'){
- headStr++;
- s[ns++] = c;
- c = *headStr;
- if(c == '\0'){
- free(s);
- return nil;
- }
- headStr++;
- }else
- headStr++;
- s[ns++] = c;
- if(ns + 1 >= as){ /* leave room for \c or "0 */
- as += StrAlloc;
- s = erealloc(s, as);
- }
- }
- s[ns++] = stop;
- s[ns] = '\0';
- return s;
-}
-
-/*
- * headText : contents of rest of header line
- */
-static char *
-headText(void)
-{
- uchar *v;
- char *s;
-
- v = headStr;
- headToEnd();
- s = emalloc(headStr - v + 1);
- memmove(s, v, headStr - v);
- s[headStr - v] = '\0';
- return s;
-}
-
-/*
- * white space is ' ' '\t' or nested comments.
- * skip white space.
- * if com and a comment is seen,
- * return it's contents and stop processing white space.
- */
-static char*
-headSkipWhite(int com)
-{
- char *s;
- int c, incom, as, ns;
-
- s = nil;
- as = StrAlloc;
- ns = 0;
- if(com)
- s = emalloc(StrAlloc);
- incom = 0;
- for(; c = *headStr; headStr++){
- switch(c){
- case ' ':
- case '\t':
- case '\r':
- c = ' ';
- break;
- case '\n':
- c = headStr[1];
- if(c != ' ' && c != '\t')
- goto breakout;
- c = ' ';
- break;
- case '\\':
- if(com && incom)
- s[ns++] = c;
- c = headStr[1];
- if(c == '\0')
- goto breakout;
- headStr++;
- break;
- case '(':
- incom++;
- if(incom == 1)
- continue;
- break;
- case ')':
- incom--;
- if(com && !incom){
- s[ns] = '\0';
- return s;
- }
- break;
- default:
- if(!incom)
- goto breakout;
- break;
- }
- if(com && incom && (c != ' ' || ns > 0 && s[ns-1] != ' ')){
- s[ns++] = c;
- if(ns + 1 >= as){ /* leave room for \c or 0 */
- as += StrAlloc;
- s = erealloc(s, as);
- }
- }
- }
-breakout:;
- free(s);
- return nil;
-}
-
-/*
- * return the next non-white character
- */
-static int
-headChar(int eat)
-{
- int c;
-
- headSkipWhite(0);
- c = *headStr;
- if(eat && c != '\0' && c != '\n')
- headStr++;
- return c;
-}
-
-static void
-headToEnd(void)
-{
- uchar *s;
- int c;
-
- for(;;){
- s = headStr;
- c = *s++;
- while(c == '\r')
- c = *s++;
- if(c == '\n'){
- c = *s++;
- if(c != ' ' && c != '\t')
- return;
- }
- if(c == '\0')
- return;
- headStr = s;
- }
-}
-
-static void
-headSkip(void)
-{
- int c;
-
- while(c = *headStr){
- headStr++;
- if(c == '\n'){
- c = *headStr;
- if(c == ' ' || c == '\t')
- continue;
- return;
- }
- }
-}
--- a/sys/src/cmd/ip/imap4d/mutf7.c
+++ /dev/null
@@ -1,174 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-/*
- * modified utf-7, as per imap4 spec
- * like utf-7, but substitues , for / in base 64,
- * does not allow escaped ascii characters.
- *
- * /lib/rfc/rfc2152 is utf-7
- * /lib/rfc/rfc1642 is obsolete utf-7
- *
- * test sequences from rfc1642
- * 'A≢Α.' 'A&ImIDkQ-.'
- * 'Hi Mom ☺!" 'Hi Mom &Jjo-!'
- * '日本語' '&ZeVnLIqe-'
- */
-
-static uchar mt64d[256];
-static char mt64e[64];
-
-static void
-initm64(void)
-{
- int c, i;
-
- memset(mt64d, 255, 256);
- memset(mt64e, '=', 64);
- i = 0;
- for(c = 'A'; c <= 'Z'; c++){
- mt64e[i] = c;
- mt64d[c] = i++;
- }
- for(c = 'a'; c <= 'z'; c++){
- mt64e[i] = c;
- mt64d[c] = i++;
- }
- for(c = '0'; c <= '9'; c++){
- mt64e[i] = c;
- mt64d[c] = i++;
- }
- mt64e[i] = '+';
- mt64d['+'] = i++;
- mt64e[i] = ',';
- mt64d[','] = i;
-}
-
-int
-encmutf7(char *out, int lim, char *in)
-{
- Rune rr;
- ulong r, b;
- char *start = out;
- char *e = out + lim;
- int nb;
-
- if(mt64e[0] == 0)
- initm64();
- for(;;){
- r = *(uchar*)in;
-
- if(r < ' ' || r >= Runeself){
- if(r == '\0')
- break;
- if(out + 1 >= e)
- return -1;
- *out++ = '&';
- b = 0;
- nb = 0;
- for(;;){
- in += chartorune(&rr, in);
- r = rr;
- if(r == '\0' || r >= ' ' && r < Runeself)
- break;
- b = (b << 16) | r;
- for(nb += 16; nb >= 6; nb -= 6){
- if(out + 1 >= e)
- return -1;
- *out++ = mt64e[(b>>(nb-6))&0x3f];
- }
- }
- for(; nb >= 6; nb -= 6){
- if(out + 1 >= e)
- return -1;
- *out++ = mt64e[(b>>(nb-6))&0x3f];
- }
- if(nb){
- if(out + 1 >= e)
- return -1;
- *out++ = mt64e[(b<<(6-nb))&0x3f];
- }
-
- if(out + 1 >= e)
- return -1;
- *out++ = '-';
- if(r == '\0')
- break;
- }else
- in++;
- if(out + 1 >= e)
- return -1;
- *out = r;
- out++;
- if(r == '&')
- *out++ = '-';
- }
-
- if(out >= e)
- return -1;
- *out = '\0';
- return out - start;
-}
-
-int
-decmutf7(char *out, int lim, char *in)
-{
- Rune rr;
- char *start = out;
- char *e = out + lim;
- int c, b, nb;
-
- if(mt64e[0] == 0)
- initm64();
- for(;;){
- c = *in;
-
- if(c < ' ' || c >= Runeself){
- if(c == '\0')
- break;
- return -1;
- }
- if(c != '&'){
- if(out + 1 >= e)
- return -1;
- *out++ = c;
- in++;
- continue;
- }
- in++;
- if(*in == '-'){
- if(out + 1 >= e)
- return -1;
- *out++ = '&';
- in++;
- continue;
- }
-
- b = 0;
- nb = 0;
- while((c = *in++) != '-'){
- c = mt64d[c];
- if(c >= 64)
- return -1;
- b = (b << 6) | c;
- nb += 6;
- if(nb >= 16){
- rr = b >> (nb - 16);
- nb -= 16;
- if(out + UTFmax + 1 >= e && out + runelen(rr) + 1 >= e)
- return -1;
- out += runetochar(out, &rr);
- }
- }
- if(b & ((1 << nb) - 1))
- return -1;
- }
-
- if(out >= e)
- return -1;
- *out = '\0';
- return out - start;
-}
--- a/sys/src/cmd/ip/imap4d/nodes.c
+++ /dev/null
@@ -1,213 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-/*
- * iterated over all of the items in the message set.
- * errors are accumulated, but processing continues.
- * if uids, then ignore non-existent messages.
- * otherwise, that's an error
- */
-int
-forMsgs(Box *box, MsgSet *ms, ulong max, int uids, int (*f)(Box*, Msg*, int, void*), void *rock)
-{
- Msg *m;
- ulong id;
- int ok, rok;
-
- ok = 1;
- for(; ms != nil; ms = ms->next){
- id = ms->from;
- rok = 0;
- for(m = box->msgs; m != nil && m->seq <= max; m = m->next){
- if(!uids && m->seq > id
- || uids && m->uid > ms->to)
- break;
- if(!uids && m->seq == id
- || uids && m->uid >= id){
- if(!(*f)(box, m, uids, rock))
- ok = 0;
- if(uids)
- id = m->uid;
- if(id >= ms->to){
- rok = 1;
- break;
- }
- if(ms->to == ~0UL)
- rok = 1;
- id++;
- }
- }
- if(!uids && !rok)
- ok = 0;
- }
- return ok;
-}
-
-Store *
-mkStore(int sign, int op, int flags)
-{
- Store *st;
-
- st = binalloc(&parseBin, sizeof(Store), 1);
- if(st == nil)
- parseErr("out of memory");
- st->sign = sign;
- st->op = op;
- st->flags = flags;
- return st;
-}
-
-Fetch *
-mkFetch(int op, Fetch *next)
-{
- Fetch *f;
-
- f = binalloc(&parseBin, sizeof(Fetch), 1);
- if(f == nil)
- parseErr("out of memory");
- f->op = op;
- f->next = next;
- return f;
-}
-
-Fetch*
-revFetch(Fetch *f)
-{
- Fetch *last, *next;
-
- last = nil;
- for(; f != nil; f = next){
- next = f->next;
- f->next = last;
- last = f;
- }
- return last;
-}
-
-NList*
-mkNList(ulong n, NList *next)
-{
- NList *nl;
-
- nl = binalloc(&parseBin, sizeof(NList), 0);
- if(nl == nil)
- parseErr("out of memory");
- nl->n = n;
- nl->next = next;
- return nl;
-}
-
-NList*
-revNList(NList *nl)
-{
- NList *last, *next;
-
- last = nil;
- for(; nl != nil; nl = next){
- next = nl->next;
- nl->next = last;
- last = nl;
- }
- return last;
-}
-
-SList*
-mkSList(char *s, SList *next)
-{
- SList *sl;
-
- sl = binalloc(&parseBin, sizeof(SList), 0);
- if(sl == nil)
- parseErr("out of memory");
- sl->s = s;
- sl->next = next;
- return sl;
-}
-
-SList*
-revSList(SList *sl)
-{
- SList *last, *next;
-
- last = nil;
- for(; sl != nil; sl = next){
- next = sl->next;
- sl->next = last;
- last = sl;
- }
- return last;
-}
-
-int
-BNList(Biobuf *b, NList *nl, char *sep)
-{
- char *s;
- int n;
-
- s = "";
- n = 0;
- for(; nl != nil; nl = nl->next){
- n += Bprint(b, "%s%lud", s, nl->n);
- s = sep;
- }
- return n;
-}
-
-int
-BSList(Biobuf *b, SList *sl, char *sep)
-{
- char *s;
- int n;
-
- s = "";
- n = 0;
- for(; sl != nil; sl = sl->next){
- n += Bprint(b, "%s", s);
- n += Bimapstr(b, sl->s);
- s = sep;
- }
- return n;
-}
-
-int
-Bimapdate(Biobuf *b, Tm *tm)
-{
- char buf[64];
-
- if(tm == nil)
- tm = localtime(time(nil));
- imap4date(buf, sizeof(buf), tm);
- return Bimapstr(b, buf);
-}
-
-int
-Brfc822date(Biobuf *b, Tm *tm)
-{
- char buf[64];
-
- if(tm == nil)
- tm = localtime(time(nil));
- rfc822date(buf, sizeof(buf), tm);
- return Bimapstr(b, buf);
-}
-
-int
-Bimapstr(Biobuf *b, char *s)
-{
- char *t;
- int c;
-
- if(s == nil)
- return Bprint(b, "NIL");
- for(t = s; ; t++){
- c = *t;
- if(c == '\0')
- return Bprint(b, "\"%s\"", s);
- if(t - s > 64 || c >= 0x7f || strchr("\"\\\r\n", c) != nil)
- break;
- }
- return Bprint(b, "{%lud}\r\n%s", strlen(s), s);
-}
--- a/sys/src/cmd/ip/imap4d/search.c
+++ /dev/null
@@ -1,244 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-static int dateCmp(char *date, Search *s);
-static int addrSearch(MAddr *a, char *s);
-static int fileSearch(Msg *m, char *file, char *pat);
-static int headerSearch(Msg *m, char *hdr, char *pat);
-
-/*
- * free to exit, parseErr, since called before starting any client reply
- *
- * the header and envelope searches should convert mime character set escapes.
- */
-int
-searchMsg(Msg *m, Search *s)
-{
- MsgSet *ms;
- int ok;
-
- if(!msgStruct(m, 1) || m->expunged)
- return 0;
- for(ok = 1; ok && s != nil; s = s->next){
- switch(s->key){
- default:
- ok = 0;
- break;
- case SKNot:
- ok = !searchMsg(m, s->left);
- break;
- case SKOr:
- ok = searchMsg(m, s->left) || searchMsg(m, s->right);
- break;
- case SKAll:
- ok = 1;
- break;
- case SKAnswered:
- ok = (m->flags & MAnswered) == MAnswered;
- break;
- case SKDeleted:
- ok = (m->flags & MDeleted) == MDeleted;
- break;
- case SKDraft:
- ok = (m->flags & MDraft) == MDraft;
- break;
- case SKFlagged:
- ok = (m->flags & MFlagged) == MFlagged;
- break;
- case SKKeyword:
- ok = (m->flags & s->num) == s->num;
- break;
- case SKNew:
- ok = (m->flags & (MRecent|MSeen)) == MRecent;
- break;
- case SKOld:
- ok = (m->flags & MRecent) != MRecent;
- break;
- case SKRecent:
- ok = (m->flags & MRecent) == MRecent;
- break;
- case SKSeen:
- ok = (m->flags & MSeen) == MSeen;
- break;
- case SKUnanswered:
- ok = (m->flags & MAnswered) != MAnswered;
- break;
- case SKUndeleted:
- ok = (m->flags & MDeleted) != MDeleted;
- break;
- case SKUndraft:
- ok = (m->flags & MDraft) != MDraft;
- break;
- case SKUnflagged:
- ok = (m->flags & MFlagged) != MFlagged;
- break;
- case SKUnkeyword:
- ok = (m->flags & s->num) != s->num;
- break;
- case SKUnseen:
- ok = (m->flags & MSeen) != MSeen;
- break;
-
- case SKLarger:
- ok = msgSize(m) > s->num;
- break;
- case SKSmaller:
- ok = msgSize(m) < s->num;
- break;
-
- case SKBcc:
- ok = addrSearch(m->bcc, s->s);
- break;
- case SKCc:
- ok = addrSearch(m->cc, s->s);
- break;
- case SKFrom:
- ok = addrSearch(m->from, s->s);
- break;
- case SKTo:
- ok = addrSearch(m->to, s->s);
- break;
- case SKSubject:
- ok = 0;
- if(m->info[ISubject])
- ok = cistrstr(m->info[ISubject], s->s) != nil;
- break;
-
- case SKBefore:
- ok = dateCmp(m->unixDate, s) < 0;
- break;
- case SKOn:
- ok = dateCmp(m->unixDate, s) == 0;
- break;
- case SKSince:
- ok = dateCmp(m->unixDate, s) > 0;
- break;
- case SKSentBefore:
- ok = dateCmp(m->info[IDate], s) < 0;
- break;
- case SKSentOn:
- ok = dateCmp(m->info[IDate], s) == 0;
- break;
- case SKSentSince:
- ok = dateCmp(m->info[IDate], s) > 0;
- break;
-
- case SKUid:
- case SKSet:
- for(ms = s->set; ms != nil; ms = ms->next)
- if(s->key == SKUid && m->uid >= ms->from && m->uid <= ms->to
- || s->key == SKSet && m->seq >= ms->from && m->seq <= ms->to)
- break;
- ok = ms != nil;
- break;
-
- case SKHeader:
- ok = headerSearch(m, s->hdr, s->s);
- break;
-
- case SKBody:
- case SKText:
- if(s->key == SKText && cistrstr(m->head.buf, s->s)){
- ok = 1;
- break;
- }
- ok = fileSearch(m, "body", s->s);
- break;
- }
- }
- return ok;
-}
-
-static int
-fileSearch(Msg *m, char *file, char *pat)
-{
- char buf[BufSize + 1];
- int n, nbuf, npat, fd, ok;
-
- npat = strlen(pat);
- if(npat >= BufSize / 2)
- return 0;
-
- fd = msgFile(m, file);
- if(fd < 0)
- return 0;
- ok = 0;
- nbuf = 0;
- for(;;){
- n = read(fd, &buf[nbuf], BufSize - nbuf);
- if(n <= 0)
- break;
- nbuf += n;
- buf[nbuf] = '\0';
- if(cistrstr(buf, pat) != nil){
- ok = 1;
- break;
- }
- if(nbuf > npat){
- memmove(buf, &buf[nbuf - npat], npat);
- nbuf = npat;
- }
- }
- close(fd);
- return ok;
-}
-
-static int
-headerSearch(Msg *m, char *hdr, char *pat)
-{
- SList hdrs;
- char *s, *t;
- int ok, n;
-
- n = m->head.size + 3;
- s = emalloc(n);
- hdrs.next = nil;
- hdrs.s = hdr;
- ok = 0;
- if(selectFields(s, n, m->head.buf, &hdrs, 1) > 0){
- t = strchr(s, ':');
- if(t != nil && cistrstr(t+1, pat) != nil)
- ok = 1;
- }
- free(s);
- return ok;
-}
-
-static int
-addrSearch(MAddr *a, char *s)
-{
- char *ok, *addr;
-
- for(; a != nil; a = a->next){
- addr = maddrStr(a);
- ok = cistrstr(addr, s);
- free(addr);
- if(ok != nil)
- return 1;
- }
- return 0;
-}
-
-static int
-dateCmp(char *date, Search *s)
-{
- Tm tm;
-
- date2tm(&tm, date);
- if(tm.year < s->year)
- return -1;
- if(tm.year > s->year)
- return 1;
- if(tm.mon < s->mon)
- return -1;
- if(tm.mon > s->mon)
- return 1;
- if(tm.mday < s->mday)
- return -1;
- if(tm.mday > s->mday)
- return 1;
- return 0;
-}
--- a/sys/src/cmd/ip/imap4d/store.c
+++ /dev/null
@@ -1,127 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-static NamedInt flagMap[] =
-{
- {"\\Seen", MSeen},
- {"\\Answered", MAnswered},
- {"\\Flagged", MFlagged},
- {"\\Deleted", MDeleted},
- {"\\Draft", MDraft},
- {"\\Recent", MRecent},
- {nil, 0}
-};
-
-int
-storeMsg(Box *box, Msg *m, int uids, void *vst)
-{
- Store *st;
- int f, flags;
-
- USED(uids);
-
- if(m->expunged)
- return uids;
-
- st = vst;
- flags = st->flags;
-
- f = m->flags;
- if(st->sign == '+')
- f |= flags;
- else if(st->sign == '-')
- f &= ~flags;
- else
- f = flags;
-
- /*
- * not allowed to change the recent flag
- */
- f = (f & ~MRecent) | (m->flags & MRecent);
- setFlags(box, m, f);
-
- if(st->op != STFlagsSilent){
- m->sendFlags = 1;
- box->sendFlags = 1;
- }
-
- return 1;
-}
-
-/*
- * update flags & global flag counts in box
- */
-void
-setFlags(Box *box, Msg *m, int f)
-{
- if(f == m->flags)
- return;
-
- box->dirtyImp = 1;
- if((f & MRecent) != (m->flags & MRecent)){
- if(f & MRecent)
- box->recent++;
- else
- box->recent--;
- }
- m->flags = f;
-}
-
-void
-sendFlags(Box *box, int uids)
-{
- Msg *m;
-
- if(!box->sendFlags)
- return;
-
- box->sendFlags = 0;
- for(m = box->msgs; m != nil; m = m->next){
- if(!m->expunged && m->sendFlags){
- Bprint(&bout, "* %lud FETCH (", m->seq);
- if(uids)
- Bprint(&bout, "uid %lud ", m->uid);
- Bprint(&bout, "FLAGS (");
- writeFlags(&bout, m, 1);
- Bprint(&bout, "))\r\n");
- m->sendFlags = 0;
- }
- }
-}
-
-void
-writeFlags(Biobuf *b, Msg *m, int recentOk)
-{
- char *sep;
- int f;
-
- sep = "";
- for(f = 0; flagMap[f].name != nil; f++){
- if((m->flags & flagMap[f].v)
- && (flagMap[f].v != MRecent || recentOk)){
- Bprint(b, "%s%s", sep, flagMap[f].name);
- sep = " ";
- }
- }
-}
-
-int
-msgSeen(Box *box, Msg *m)
-{
- if(m->flags & MSeen)
- return 0;
- m->flags |= MSeen;
- box->sendFlags = 1;
- m->sendFlags = 1;
- box->dirtyImp = 1;
- return 1;
-}
-
-ulong
-mapFlag(char *name)
-{
- return mapInt(flagMap, name);
-}
--- a/sys/src/cmd/ip/imap4d/utils.c
+++ /dev/null
@@ -1,182 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-#include <auth.h>
-#include "imap4d.h"
-
-/*
- * reverse string [s:e) in place
- */
-void
-strrev(char *s, char *e)
-{
- int c;
-
- while(--e > s){
- c = *s;
- *s++ = *e;
- *e = c;
- }
-}
-
-int
-isdotdot(char *s)
-{
- return s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == '\0');
-}
-
-int
-issuffix(char *suf, char *s)
-{
- int n;
-
- n = strlen(s) - strlen(suf);
- if(n < 0)
- return 0;
- return strcmp(s + n, suf) == 0;
-}
-
-int
-isprefix(char *pre, char *s)
-{
- return strncmp(pre, s, strlen(pre)) == 0;
-}
-
-int
-ciisprefix(char *pre, char *s)
-{
- return cistrncmp(pre, s, strlen(pre)) == 0;
-}
-
-char*
-readFile(int fd)
-{
- Dir *d;
- long length;
- char *s;
-
- d = dirfstat(fd);
- if(d == nil)
- return nil;
- length = d->length;
- free(d);
- s = binalloc(&parseBin, length + 1, 0);
- if(s == nil || read(fd, s, length) != length)
- return nil;
- s[length] = '\0';
- return s;
-}
-
-/*
- * create the imap tmp file.
- * it just happens that we don't need multiple temporary files.
- */
-int
-imapTmp(void)
-{
- char buf[ERRMAX], name[MboxNameLen];
- int tries, fd;
-
- snprint(name, sizeof(name), "/mail/box/%s/mbox.tmp.imp", username);
- for(tries = 0; tries < LockSecs*2; tries++){
- fd = create(name, ORDWR|ORCLOSE|OCEXEC, DMEXCL|0600);
- if(fd >= 0)
- return fd;
- errstr(buf, sizeof buf);
- if(cistrstr(buf, "locked") == nil)
- break;
- sleep(500);
- }
- return -1;
-}
-
-/*
- * open a file which might be locked.
- * if it is, spin until available
- */
-int
-openLocked(char *dir, char *file, int mode)
-{
- char buf[ERRMAX];
- int tries, fd;
-
- for(tries = 0; tries < LockSecs*2; tries++){
- fd = cdOpen(dir, file, mode);
- if(fd >= 0)
- return fd;
- errstr(buf, sizeof buf);
- if(cistrstr(buf, "locked") == nil)
- break;
- sleep(500);
- }
- return -1;
-}
-
-int
-fqid(int fd, Qid *qid)
-{
- Dir *d;
-
- d = dirfstat(fd);
- if(d == nil)
- return -1;
- *qid = d->qid;
- free(d);
- return 0;
-}
-
-ulong
-mapInt(NamedInt *map, char *name)
-{
- int i;
-
- for(i = 0; map[i].name != nil; i++)
- if(cistrcmp(map[i].name, name) == 0)
- break;
- return map[i].v;
-}
-
-char*
-estrdup(char *s)
-{
- char *t;
-
- t = emalloc(strlen(s) + 1);
- strcpy(t, s);
- return t;
-}
-
-void*
-emalloc(ulong n)
-{
- void *p;
-
- p = malloc(n);
- if(p == nil)
- bye("server out of memory");
- setmalloctag(p, getcallerpc(&n));
- return p;
-}
-
-void*
-ezmalloc(ulong n)
-{
- void *p;
-
- p = malloc(n);
- if(p == nil)
- bye("server out of memory");
- setmalloctag(p, getcallerpc(&n));
- memset(p, 0, n);
- return p;
-}
-
-void*
-erealloc(void *p, ulong n)
-{
- p = realloc(p, n);
- if(p == nil)
- bye("server out of memory");
- setrealloctag(p, getcallerpc(&p));
- return p;
-}
--- a/sys/src/cmd/ip/mkfile
+++ b/sys/src/cmd/ip/mkfile
@@ -27,7 +27,7 @@
socksd\
wol\
-DIRS=ftpfs cifsd dhcpd httpd ipconfig ppp imap4d snoopy
+DIRS=ftpfs cifsd dhcpd httpd ipconfig ppp snoopy
BIN=/$objtype/bin/ip
HFILES=dhcp.h arp.h glob.h icmp.h telnet.h
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/dat.h
@@ -1,0 +1,166 @@
+typedef struct Event Event;
+typedef struct Exec Exec;
+typedef struct Message Message;
+typedef struct Window Window;
+
+enum
+{
+ STACK = 8192,
+ EVENTSIZE = 256,
+ NEVENT = 5,
+};
+
+struct Event
+{
+ int c1;
+ int c2;
+ int q0;
+ int q1;
+ int flag;
+ int nb;
+ int nr;
+ char b[EVENTSIZE*UTFmax+1];
+ Rune r[EVENTSIZE+1];
+};
+
+struct Window
+{
+ /* file descriptors */
+ int ctl;
+ int event;
+ int addr;
+ int data;
+ Biobuf *body;
+
+ /* event input */
+ char buf[512];
+ char *bufp;
+ int nbuf;
+ Event e[NEVENT];
+
+ int id;
+ int open;
+ Channel *cevent;
+};
+
+struct Message
+{
+ Window *w;
+ int ctlfd;
+ char *name;
+ char *replyname;
+ uchar opened;
+ uchar dirty;
+ uchar isreply;
+ uchar deleted;
+ uchar writebackdel;
+ uchar tagposted;
+ uchar recursed;
+ uchar level;
+
+ /* header info */
+ char *fromcolon; /* from header file; all rest are from info file */
+ char *from;
+ char *to;
+ char *cc;
+ char *replyto;
+ char *date;
+ char *subject;
+ char *type;
+ char *disposition;
+ char *filename;
+ char *digest;
+
+ Message *next; /* next in this mailbox */
+ Message *prev; /* prev in this mailbox */
+ Message *head; /* first subpart */
+ Message *tail; /* last subpart */
+};
+
+enum
+{
+ NARGS = 100,
+ NARGCHAR = 8*1024,
+ EXECSTACK = STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
+};
+
+struct Exec
+{
+ char *prog;
+ char **argv;
+ int p[2]; /* p[1] is write to program; p[0] set to prog fd 0*/
+ int q[2]; /* q[0] is read from program; q[1] set to prog fd 1 */
+ Channel *sync;
+};
+
+extern Window* newwindow(void);
+extern int winopenfile(Window*, char*);
+extern void winopenbody(Window*, int);
+extern void winclosebody(Window*);
+extern void wintagwrite(Window*, char*, int);
+extern void winname(Window*, char*);
+extern void winwriteevent(Window*, Event*);
+extern void winread(Window*, uint, uint, char*);
+extern int windel(Window*, int);
+extern void wingetevent(Window*, Event*);
+extern void wineventproc(void*);
+extern void winwritebody(Window*, char*, int);
+extern void winclean(Window*);
+extern int winselect(Window*, char*, int);
+extern char* winselection(Window*);
+extern int winsetaddr(Window*, char*, int);
+extern char* winreadbody(Window*, int*);
+extern void windormant(Window*);
+extern void winsetdump(Window*, char*, char*);
+
+extern void readmbox(Message*, char*, char*);
+extern void rewritembox(Window*, Message*);
+
+extern void mkreply(Message*, char*, char*, Plumbattr*, char*);
+extern void delreply(Message*);
+extern int write2(int, int, char*, int, int);
+
+extern int mesgadd(Message*, char*, Dir*, char*);
+extern void mesgmenu(Window*, Message*);
+extern void mesgmenunew(Window*, Message*);
+extern int mesgopen(Message*, char*, char*, Message*, int, char*);
+extern void mesgctl(void*);
+extern void mesgsend(Message*);
+extern void mesgdel(Message*, Message*);
+extern void mesgmenudel(Window*, Message*, Message*);
+extern void mesgmenumark(Window*, char*, char*);
+extern void mesgmenumarkdel(Window*, Message*, Message*, int);
+extern Message* mesglookup(Message*, char*, char*);
+extern Message* mesglookupfile(Message*, char*, char*);
+extern void mesgfreeparts(Message*);
+
+extern char* readfile(char*, char*, int*);
+extern char* readbody(char*, char*, int*);
+extern void ctlprint(int, char*, ...);
+extern void* emalloc(uint);
+extern void* erealloc(void*, uint);
+extern char* estrdup(char*);
+extern char* estrstrdup(char*, char*);
+extern char* egrow(char*, char*, char*);
+extern char* eappend(char*, char*, char*);
+extern void error(char*, ...);
+extern int tokenizec(char*, char**, int, char*);
+extern void execproc(void*);
+
+#pragma varargck argpos error 1
+#pragma varargck argpos ctlprint 2
+
+extern Window *wbox;
+extern Message mbox;
+extern Message replies;
+extern char *fsname;
+extern int plumbsendfd;
+extern int plumbseemailfd;
+extern char *home;
+extern char *outgoing;
+extern char *mailboxdir;
+extern char *user;
+extern char deleted[];
+extern int wctlfd;
+extern int shortmenu;
+extern int altmenu;
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/html.c
@@ -1,0 +1,75 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+
+char*
+formathtml(char *body, int *np)
+{
+ int i, j, p[2], q[2];
+ Exec *e;
+ char buf[1024];
+ Channel *sync;
+
+ e = emalloc(sizeof(struct Exec));
+ if(pipe(p) < 0 || pipe(q) < 0)
+ error("can't create pipe: %r");
+
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->q[0] = q[0];
+ e->q[1] = q[1];
+ e->argv = emalloc(3*sizeof(char*));
+ e->argv[0] = estrdup("htmlfmt");
+ e->argv[1] = estrdup("-cutf-8");
+ e->argv[2] = nil;
+ e->prog = "/bin/htmlfmt";
+ sync = chancreate(sizeof(int), 0);
+ e->sync = sync;
+ proccreate(execproc, e, EXECSTACK);
+ recvul(sync);
+ close(p[0]);
+ close(q[1]);
+
+ if((i=write(p[1], body, *np)) != *np){
+ fprint(2, "Mail: warning: htmlfmt failed: wrote %d of %d: %r\n", i, *np);
+ close(p[1]);
+ close(q[0]);
+ return body;
+ }
+ close(p[1]);
+
+ free(body);
+ body = nil;
+ i = 0;
+ for(;;){
+ j = read(q[0], buf, sizeof buf);
+ if(j <= 0)
+ break;
+ body = realloc(body, i+j+1);
+ if(body == nil)
+ error("realloc failed: %r");
+ memmove(body+i, buf, j);
+ i += j;
+ body[i] = '\0';
+ }
+ close(q[0]);
+
+ *np = i;
+ return body;
+}
+
+char*
+readbody(char *type, char *dir, int *np)
+{
+ char *body;
+
+ body = readfile(dir, "body", np);
+ if(body != nil && strcmp(type, "text/html") == 0)
+ return formathtml(body, np);
+ return body;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/mail.c
@@ -1,0 +1,620 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include <ctype.h>
+#include "dat.h"
+
+char *maildir = "/mail/fs/"; /* mountpoint of mail file system */
+char *mailtermdir = "/mnt/term/mail/fs/"; /* alternate mountpoint */
+char *mboxname = "mbox"; /* mailboxdir/mboxname is mail spool file */
+char *mailboxdir = nil; /* nil == /mail/box/$user */
+char *fsname; /* filesystem for mailboxdir/mboxname is at maildir/fsname */
+char *user;
+char *outgoing;
+
+Window *wbox;
+Message mbox;
+Message replies;
+char *home;
+int plumbsendfd;
+int plumbseemailfd;
+int plumbshowmailfd;
+int plumbsendmailfd;
+Channel *cplumb;
+Channel *cplumbshow;
+Channel *cplumbsend;
+int wctlfd;
+void mainctl(void*);
+void plumbproc(void*);
+void plumbshowproc(void*);
+void plumbsendproc(void*);
+void plumbthread(void);
+void plumbshowthread(void*);
+void plumbsendthread(void*);
+
+int shortmenu;
+int altmenu;
+
+void
+usage(void)
+{
+ fprint(2, "usage: Mail [-sS] [-o outgoing] [mailboxname [directoryname]]\n");
+ threadexitsall("usage");
+}
+
+void
+removeupasfs(void)
+{
+ char buf[256];
+
+ if(strcmp(mboxname, "mbox") == 0)
+ return;
+ snprint(buf, sizeof buf, "close %s", mboxname);
+ write(mbox.ctlfd, buf, strlen(buf));
+}
+
+int
+ismaildir(char *s)
+{
+ char *path;
+ Dir *d;
+ int ret;
+
+ path = smprint("%s%s", maildir, s);
+ d = dirstat(path);
+ free(path);
+ if(d == nil)
+ return 0;
+ ret = d->qid.type & QTDIR;
+ free(d);
+ return ret;
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ char *s, *name;
+ char err[ERRMAX], *cmd;
+ int i, newdir;
+ Fmt fmt;
+
+ doquote = needsrcquote;
+ quotefmtinstall();
+
+ /* open these early so we won't miss notification of new mail messages while we read mbox */
+ plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
+ plumbseemailfd = plumbopen("seemail", OREAD|OCEXEC);
+ plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC);
+
+ shortmenu = 0;
+ altmenu = 0;
+ ARGBEGIN{
+ case 's':
+ shortmenu = 1;
+ break;
+ case 'S':
+ shortmenu = 2;
+ break;
+ case 'A':
+ altmenu = 1;
+ break;
+ case 'o':
+ outgoing = EARGF(usage());
+ break;
+ case 'm':
+ smprint(maildir, "%s/", EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ name = "mbox";
+
+ /* bind the terminal /mail/fs directory over the local one */
+ if(access(maildir, 0)<0 && access(mailtermdir, 0)==0)
+ bind(mailtermdir, maildir, MAFTER);
+
+ newdir = 1;
+ if(argc > 0){
+ i = strlen(argv[0]);
+ if(argc>2 || i==0)
+ usage();
+ /* see if the name is that of an existing /mail/fs directory */
+ if(argc==1 && strchr(argv[0], '/')==0 && ismaildir(argv[0])){
+ name = argv[0];
+ mboxname = eappend(estrdup(maildir), "", name);
+ newdir = 0;
+ }else{
+ if(argv[0][i-1] == '/')
+ argv[0][i-1] = '\0';
+ s = strrchr(argv[0], '/');
+ if(s == nil)
+ mboxname = estrdup(argv[0]);
+ else{
+ *s++ = '\0';
+ if(*s == '\0')
+ usage();
+ mailboxdir = argv[0];
+ mboxname = estrdup(s);
+ }
+ if(argc > 1)
+ name = argv[1];
+ else
+ name = mboxname;
+ }
+ }
+
+ user = getenv("user");
+ if(user == nil)
+ user = "none";
+ if(mailboxdir == nil)
+ mailboxdir = estrstrdup("/mail/box/", user);
+ if(outgoing == nil)
+ outgoing = estrstrdup(mailboxdir, "/outgoing");
+
+ s = estrstrdup(maildir, "ctl");
+ mbox.ctlfd = open(s, ORDWR|OCEXEC);
+ if(mbox.ctlfd < 0)
+ error("can't open %s: %r", s);
+
+ fsname = estrdup(name);
+ if(newdir && argc > 0){
+ s = emalloc(5+strlen(mailboxdir)+strlen(mboxname)+strlen(name)+10+1);
+ for(i=0; i<10; i++){
+ sprint(s, "open %s/%s %s", mailboxdir, mboxname, fsname);
+ if(write(mbox.ctlfd, s, strlen(s)) >= 0)
+ break;
+ err[0] = '\0';
+ errstr(err, sizeof err);
+ if(strstr(err, "mbox name in use") == nil)
+ error("can't create directory %s for mail: %s", name, err);
+ free(fsname);
+ fsname = emalloc(strlen(name)+10);
+ sprint(fsname, "%s-%d", name, i);
+ }
+ if(i == 10)
+ error("can't open %s/%s: %r", mailboxdir, mboxname);
+ free(s);
+ }
+
+ s = estrstrdup(fsname, "/");
+ mbox.name = estrstrdup(maildir, s);
+ mbox.level= 0;
+ readmbox(&mbox, maildir, s);
+ home = getenv("home");
+ if(home == nil)
+ home = "/";
+
+ wbox = newwindow();
+ winname(wbox, mbox.name);
+ wintagwrite(wbox, "Put Mail Delmesg Save ", 3+1+4+1+7+1+4+1);
+ threadcreate(mainctl, wbox, STACK);
+
+ fmtstrinit(&fmt);
+ fmtprint(&fmt, "Mail");
+ if(shortmenu)
+ fmtprint(&fmt, " -%c", "sS"[shortmenu-1]);
+ if(altmenu)
+ fmtprint(&fmt, " -A");
+ if(outgoing)
+ fmtprint(&fmt, " -o %s", outgoing);
+ fmtprint(&fmt, " %s", name);
+ cmd = fmtstrflush(&fmt);
+ if(cmd == nil)
+ sysfatal("out of memory");
+ winsetdump(wbox, "/acme/mail", cmd);
+ mbox.w = wbox;
+
+ mesgmenu(wbox, &mbox);
+ winclean(wbox);
+
+ wctlfd = open("/dev/wctl", OWRITE|OCEXEC); /* for acme window */
+ cplumb = chancreate(sizeof(Plumbmsg*), 0);
+ cplumbshow = chancreate(sizeof(Plumbmsg*), 0);
+ if(strcmp(name, "mbox") == 0){
+ /*
+ * Avoid creating multiple windows to send mail by only accepting
+ * sendmail plumb messages if we're reading the main mailbox.
+ */
+ plumbsendmailfd = plumbopen("sendmail", OREAD|OCEXEC);
+ cplumbsend = chancreate(sizeof(Plumbmsg*), 0);
+ proccreate(plumbsendproc, nil, STACK);
+ threadcreate(plumbsendthread, nil, STACK);
+ }
+ /* start plumb reader as separate proc ... */
+ proccreate(plumbproc, nil, STACK);
+ proccreate(plumbshowproc, nil, STACK);
+ threadcreate(plumbshowthread, nil, STACK);
+ /* ... and use this thread to read the messages */
+ plumbthread();
+}
+
+void
+plumbproc(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbproc");
+ for(;;){
+ m = plumbrecv(plumbseemailfd);
+ sendp(cplumb, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+plumbshowproc(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbshowproc");
+ for(;;){
+ m = plumbrecv(plumbshowmailfd);
+ sendp(cplumbshow, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+plumbsendproc(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbsendproc");
+ for(;;){
+ m = plumbrecv(plumbsendmailfd);
+ sendp(cplumbsend, m);
+ if(m == nil)
+ threadexits(nil);
+ }
+}
+
+void
+newmesg(char *name, char *digest)
+{
+ Dir *d;
+
+ if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+ return; /* message is about another mailbox */
+ if(mesglookupfile(&mbox, name, digest) != nil)
+ return;
+ d = dirstat(name);
+ if(d == nil)
+ return;
+ if(mesgadd(&mbox, mbox.name, d, digest))
+ mesgmenunew(wbox, &mbox);
+ free(d);
+}
+
+void
+showmesg(char *name, char *digest)
+{
+ char *n;
+
+ if(strncmp(name, mbox.name, strlen(mbox.name)) != 0)
+ return; /* message is about another mailbox */
+ n = estrdup(name+strlen(mbox.name));
+ if(n[strlen(n)-1] != '/')
+ n = egrow(n, "/", nil);
+ mesgopen(&mbox, mbox.name, name+strlen(mbox.name), nil, 1, digest);
+ free(n);
+}
+
+void
+delmesg(char *name, char *digest, int dodel)
+{
+ Message *m;
+
+ m = mesglookupfile(&mbox, name, digest);
+ if(m != nil){
+ mesgmenumarkdel(wbox, &mbox, m, 0);
+ if(dodel)
+ m->writebackdel = 1;
+ }
+}
+
+extern int mesgsave(Message*, char*);
+void
+savemesg(char *box, char *name, char *digest)
+{
+ char *s;
+ int ok;
+ Message *m;
+
+ m = mesglookupfile(&mbox, name, digest);
+ if(!m || m->isreply)
+ return;
+ s = estrdup("\t[saved");
+ if(!box[0])
+ ok = mesgsave(m, "stored");
+ else{
+ ok = mesgsave(m, box);
+ s = eappend(s, " ", box);
+ }
+ if(ok){
+ s = egrow(s, "]", nil);
+ mesgmenumark(mbox.w, m->name, s);
+ }
+ free(s);
+
+}
+
+void
+plumbthread(void)
+{
+ Plumbmsg *m;
+ Plumbattr *a;
+ char *type, *digest;
+
+ threadsetname("plumbthread");
+ while((m = recvp(cplumb)) != nil){
+ a = m->attr;
+ digest = plumblookup(a, "digest");
+ type = plumblookup(a, "mailtype");
+ if(type == nil)
+ fprint(2, "Mail: plumb message with no mailtype attribute\n");
+ else if(strcmp(type, "new") == 0)
+ newmesg(m->data, digest);
+ else if(strcmp(type, "delete") == 0)
+ delmesg(m->data, digest, 0);
+ else
+ fprint(2, "Mail: unknown plumb attribute %s\n", type);
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+void
+plumbshowthread(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbshowthread");
+ while((m = recvp(cplumbshow)) != nil){
+ showmesg(m->data, plumblookup(m->attr, "digest"));
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+void
+plumbsendthread(void*)
+{
+ Plumbmsg *m;
+
+ threadsetname("plumbsendthread");
+ while((m = recvp(cplumbsend)) != nil){
+ mkreply(nil, "Mail", m->data, m->attr, nil);
+ plumbfree(m);
+ }
+ threadexits(nil);
+}
+
+int
+mboxcommand(Window *w, char *s)
+{
+ char *args[10], **targs, *r, *box;
+ Message *m, *next;
+ int ok, nargs, i, j;
+ char buf[128];
+
+ nargs = tokenize(s, args, nelem(args));
+ if(nargs == 0)
+ return 0;
+ if(strcmp(args[0], "Mail") == 0){
+ if(nargs == 1)
+ mkreply(nil, "Mail", "", nil, nil);
+ else
+ mkreply(nil, "Mail", args[1], nil, nil);
+ return 1;
+ }
+ if(strcmp(s, "Del") == 0){
+ if(mbox.dirty){
+ mbox.dirty = 0;
+ fprint(2, "mail: mailbox not written\n");
+ return 1;
+ }
+ ok = 1;
+ for(m=mbox.head; m!=nil; m=next){
+ next = m->next;
+ if(m->w){
+ if(windel(m->w, 0))
+ m->w = nil;
+ else
+ ok = 0;
+ }
+ }
+ for(m=replies.head; m!=nil; m=next){
+ next = m->next;
+ if(m->w){
+ if(windel(m->w, 0))
+ m->w = nil;
+ else
+ ok = 0;
+ }
+ }
+ if(ok){
+ windel(w, 1);
+ removeupasfs();
+ threadexitsall(nil);
+ }
+ return 1;
+ }
+ if(strcmp(s, "Put") == 0){
+ rewritembox(wbox, &mbox);
+ return 1;
+ }
+ if(strcmp(s, "Delmesg") == 0){
+ if(nargs > 1){
+ for(i=1; i<nargs; i++){
+ snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
+ delmesg(buf, nil, 1);
+ }
+ }
+ s = winselection(w);
+ if(s == nil)
+ return 1;
+ nargs = 1;
+ for(i=0; s[i]; i++)
+ if(s[i] == '\n')
+ nargs++;
+ targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
+ nargs = getfields(s, targs, nargs, 1, "\n");
+ for(i=0; i<nargs; i++){
+ if(!isdigit(targs[i][0]))
+ continue;
+ j = atoi(targs[i]); /* easy way to parse the number! */
+ if(j == 0)
+ continue;
+ snprint(buf, sizeof buf, "%s%d", mbox.name, j);
+ delmesg(buf, nil, 1);
+ }
+ free(s);
+ free(targs);
+ return 1;
+ }
+ if(strncmp(args[0], "Save", 4) == 0){
+ box = "";
+ i = 1;
+ if(nargs > 1 && !mesglookupfile(&mbox, args[1], nil)){
+ box = args[1];
+ i++;
+ nargs--;
+ }
+ if(nargs > 1){
+ for(; i<nargs; i++){
+ snprint(buf, sizeof buf, "%s%s", mbox.name, args[i]);
+ savemesg(box, buf, nil);
+ }
+ }
+ s = winselection(w);
+ if(s == nil)
+ return 1;
+ nargs = 1;
+ for(i=0; s[i]; i++)
+ if(s[i] == '\n')
+ nargs++;
+ targs = emalloc(nargs*sizeof(char*)); /* could be too many for a local array */
+ nargs = getfields(s, targs, nargs, 1, "\n");
+ for(i=0; i<nargs; i++){
+ if(!isdigit(targs[i][0]))
+ continue;
+ j = strtoul(targs[i], &r, 10);
+ if(j == 0 || *r != '/')
+ continue;
+ snprint(buf, sizeof buf, "%s%d", mbox.name, j);
+ savemesg(box, buf, nil);
+ }
+ free(s);
+ free(targs);
+ return 1;
+ }
+ return 0;
+}
+
+void
+mainctl(void *v)
+{
+ Window *w;
+ Event *e, *e2, *eq, *ea;
+ int na, nopen;
+ char *s, *t, *buf;
+
+ w = v;
+ proccreate(wineventproc, w, STACK);
+
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unknown:
+ print("unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ break;
+
+ case 'M':
+ switch(e->c2){
+ case 'x':
+ case 'X':
+ ea = nil;
+ e2 = nil;
+ if(e->flag & 2)
+ e2 = recvp(w->cevent);
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ na = ea->nb;
+ recvp(w->cevent);
+ }else
+ na = 0;
+ s = e->b;
+ /* if it's a known command, do it */
+ if((e->flag&2) && e->nb==0)
+ s = e2->b;
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ s = t;
+ }
+ /* if it's a long message, it can't be for us anyway */
+ if(!mboxcommand(w, s)) /* send it back */
+ winwriteevent(w, e);
+ if(na)
+ free(s);
+ break;
+
+ case 'l':
+ case 'L':
+ buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ nopen = 0;
+ do{
+ /* skip 'deleted' string if present' */
+ if(strncmp(s, deleted, strlen(deleted)) == 0)
+ s += strlen(deleted);
+ /* skip mail box name if present */
+ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+ s += strlen(mbox.name);
+ nopen += mesgopen(&mbox, mbox.name, s, nil, 0, nil);
+ while(*s!='\0' && *s++!='\n')
+ ;
+ }while(*s);
+ if(nopen == 0) /* send it back */
+ winwriteevent(w, e);
+ free(buf);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'D':
+ case 'd':
+ case 'i':
+ break;
+
+ default:
+ goto Unknown;
+ }
+ }
+ }
+}
+
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/mesg.c
@@ -1,0 +1,1390 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+enum
+{
+ DIRCHUNK = 32*sizeof(Dir)
+};
+
+char regexchars[] = "\\/[].+?()*^$";
+char deleted[] = "(deleted)-";
+char deletedrx[] = "\\(deleted\\)-";
+char deletedrx01[] = "(\\(deleted\\)-)?";
+char deletedaddr[] = "-#0;/^\\(deleted\\)-/";
+
+struct{
+ char *type;
+ char *port;
+ char *suffix;
+} ports[] = {
+ "text/", "edit", ".txt", /* must be first for plumbport() */
+ /* text must be first for plumbport() */
+ "image/gif", "image", ".gif",
+ "image/jpeg", "image", ".jpg",
+ "image/jpeg", "image", ".jpeg",
+ "image/png", "image", ".png",
+ "image/tiff", "image", ".tif",
+ "application/postscript", "postscript", ".ps",
+ "application/pdf", "postscript", ".pdf",
+ "application/msword", "msword", ".doc",
+ "application/rtf", "msword", ".rtf",
+ "audio/x-wav", "wav", ".wav",
+ nil, nil
+};
+
+char *goodtypes[] = {
+ "text",
+ "text/plain",
+ "message/rfc822",
+ "text/richtext",
+ "text/tab-separated-values",
+ "application/octet-stream",
+ nil,
+};
+
+struct{
+ char *type;
+ char *ext;
+} exts[] = {
+ "image/gif", ".gif",
+ "image/jpeg", ".jpg",
+ nil, nil
+};
+
+char *okheaders[] =
+{
+ "From:",
+ "Date:",
+ "To:",
+ "CC:",
+ "Subject:",
+ nil
+};
+
+char *extraheaders[] =
+{
+ "Resent-From:",
+ "Resent-To:",
+ "Sort:",
+ nil,
+};
+
+char*
+line(char *data, char **pp)
+{
+ char *p, *q;
+
+ for(p=data; *p!='\0' && *p!='\n'; p++)
+ ;
+ if(*p == '\n')
+ *pp = p+1;
+ else
+ *pp = p;
+ q = emalloc(p-data + 1);
+ memmove(q, data, p-data);
+ return q;
+}
+
+char*
+fc(Message *m, char *s)
+{
+ char *r;
+
+ if(*s && *m->from){
+ r = smprint("%s <%s>", s, m->from);
+ free(s);
+ return r;
+ }else if(*s)
+ return s;
+ else if(*m->from)
+ return estrdup(m->from);
+ return estrdup("??");
+}
+
+int
+loadinfo(Message *m, char *dir)
+{
+ int n;
+ char *data, *p;
+
+ data = readfile(dir, "info", &n);
+ if(data == nil)
+ return 0;
+ m->from = line(data, &p);
+ m->to = line(p, &p);
+ m->cc = line(p, &p);
+ m->replyto = line(p, &p);
+ m->date = line(p, &p);
+ m->subject = line(p, &p);
+ m->type = line(p, &p);
+ m->disposition = line(p, &p);
+ m->filename = line(p, &p);
+ m->digest = line(p, &p);
+ /* m->bcc = */ free(line(p, &p));
+ /* m->inreplyto = */ free(line(p, &p));
+ /* m->date = */ free(line(p, &p));
+ /* m->sender = */ free(line(p, &p));
+ /* m->messageid = */ free(line(p, &p));
+ /* m->lines = */ free(line(p, &p));
+ /* m->size = */ free(line(p, &p));
+ /* m->flags = */ free(line(p, &p));
+ /* m->fileid = */ free(line(p, &p));
+ m->fromcolon = fc(m, line(p, &p));
+
+ free(data);
+ return 1;
+}
+
+int
+isnumeric(char *s)
+{
+ while(*s){
+ if(!isdigit(*s))
+ return 0;
+ s++;
+ }
+ return 1;
+}
+
+Dir*
+loaddir(char *name, int *np)
+{
+ int fd;
+ Dir *dp;
+
+ fd = open(name, OREAD);
+ if(fd < 0)
+ return nil;
+ *np = dirreadall(fd, &dp);
+ close(fd);
+ return dp;
+}
+
+void
+readmbox(Message *mbox, char *dir, char *subdir)
+{
+ char *name;
+ Dir *d, *dirp;
+ int i, n;
+
+ name = estrstrdup(dir, subdir);
+ dirp = loaddir(name, &n);
+ mbox->recursed = 1;
+ if(dirp)
+ for(i=0; i<n; i++){
+ d = &dirp[i];
+ if(isnumeric(d->name))
+ mesgadd(mbox, name, d, nil);
+ }
+ free(dirp);
+ free(name);
+}
+
+/* add message to box, in increasing numerical order */
+int
+mesgadd(Message *mbox, char *dir, Dir *d, char *digest)
+{
+ Message *m;
+ char *name;
+ int loaded;
+
+ m = emalloc(sizeof(Message));
+ m->name = estrstrdup(d->name, "/");
+ m->next = nil;
+ m->prev = mbox->tail;
+ m->level= mbox->level+1;
+ m->recursed = 0;
+ name = estrstrdup(dir, m->name);
+ loaded = loadinfo(m, name);
+ free(name);
+ /* if two upas/fs are running, we can get misled, so check digest before accepting message */
+ if(loaded==0 || (digest!=nil && m->digest!=nil && strcmp(digest, m->digest)!=0)){
+ mesgfreeparts(m);
+ free(m);
+ return 0;
+ }
+ if(mbox->tail != nil)
+ mbox->tail->next = m;
+ mbox->tail = m;
+ if(mbox->head == nil)
+ mbox->head = m;
+
+ if (m->level != 1){
+ m->recursed = 1;
+ readmbox(m, dir, m->name);
+ }
+ return 1;
+}
+
+int
+thisyear(char *year)
+{
+ static char now[10];
+ char *s;
+
+ if(now[0] == '\0'){
+ s = ctime(time(nil));
+ strcpy(now, s+24);
+ }
+ return strncmp(year, now, 4) == 0;
+}
+
+char*
+stripdate(char *as)
+{
+ int n;
+ char *s, *fld[10];
+
+ as = estrdup(as);
+ s = estrdup(as);
+ n = tokenize(s, fld, 10);
+ if(n > 5){
+ sprint(as, "%.3s ", fld[0]); /* day */
+ /* some dates have 19 Apr, some Apr 19 */
+ if(strlen(fld[1])<4 && isnumeric(fld[1]))
+ sprint(as+strlen(as), "%.3s %.3s ", fld[1], fld[2]); /* date, month */
+ else
+ sprint(as+strlen(as), "%.3s %.3s ", fld[2], fld[1]); /* date, month */
+ /* do we use time or year? depends on whether year matches this one */
+ if(thisyear(fld[5])){
+ if(strchr(fld[3], ':') != nil)
+ sprint(as+strlen(as), "%.5s ", fld[3]); /* time */
+ else if(strchr(fld[4], ':') != nil)
+ sprint(as+strlen(as), "%.5s ", fld[4]); /* time */
+ }else
+ sprint(as+strlen(as), "%.4s ", fld[5]); /* year */
+ }
+ free(s);
+ return as;
+}
+
+char*
+readfile(char *dir, char *name, int *np)
+{
+ char *file, *data;
+ int fd, len;
+ Dir *d;
+
+ if(np != nil)
+ *np = 0;
+ file = estrstrdup(dir, name);
+ fd = open(file, OREAD);
+ if(fd < 0)
+ return nil;
+ d = dirfstat(fd);
+ free(file);
+ len = 0;
+ if(d != nil)
+ len = d->length;
+ free(d);
+ data = emalloc(len+1);
+ read(fd, data, len);
+ close(fd);
+ if(np != nil)
+ *np = len;
+ return data;
+}
+
+int
+writefile(char *dir, char *name, char *s)
+{
+ char *e, *file;
+ int fd, n;
+
+ file = estrstrdup(dir, name);
+// fprint(2, "writefile %s [%s]\n", file, s);
+ fd = open(file, OWRITE);
+ if(fd < 0)
+ return -1;
+ for(e = s + strlen(s); e - s > 0; s += n)
+ if((n = write(fd, s, e - s)) <= 0)
+ break;
+ close(fd);
+ return s == e? 0: -1;
+}
+
+void
+setflags(Message *m, char *f)
+{
+ char *t;
+
+ t = smprint("%s/%s", mbox.name, m->name);
+ writefile(t, "flags", f);
+ free(t);
+}
+
+char*
+info(Message *m, int ind, int ogf)
+{
+ char *i;
+ int j, len, lens;
+ char *p;
+ char fmt[80], s[80];
+
+ if (ogf)
+ p=m->to;
+ else
+ p=m->fromcolon;
+
+ if(ind==0 && altmenu){
+ len = 12;
+ lens = 20;
+
+ if(ind==0 && m->subject[0]=='\0'){
+ snprint(fmt, sizeof fmt,
+ "\t%%-%d.%ds\t%%-12.12s\t", len, len);
+ snprint(s, sizeof s, fmt, p, stripdate(m->date) + 4);
+ }else{
+ snprint(fmt, sizeof fmt,
+ "\t%%-%d.%ds\t%%-12.12s\t%%-%d.%ds", len, len, lens, lens);
+ snprint(s, sizeof s, fmt, p, stripdate(m->date) + 4, m->subject);
+ }
+ i = estrdup(s);
+
+ return i;
+ }
+
+ if(ind==0 && shortmenu){
+ len = 30;
+ lens = 30;
+ if(shortmenu > 1){
+ len = 10;
+ lens = 25;
+ }
+ if(ind==0 && m->subject[0]=='\0'){
+ snprint(fmt, sizeof fmt, " %%-%d.%ds", len, len);
+ snprint(s, sizeof s, fmt, p);
+ }else{
+ snprint(fmt, sizeof fmt, " %%-%d.%ds %%-%d.%ds", len, len, lens, lens);
+ snprint(s, sizeof s, fmt, p, m->subject);
+ }
+ i = estrdup(s);
+
+ return i;
+ }
+
+ i = estrdup("");
+ i = eappend(i, "\t", p);
+ i = egrow(i, "\t", stripdate(m->date));
+ if(ind == 0){
+ if(strcmp(m->type, "text")!=0 && strncmp(m->type, "text/", 5)!=0 &&
+ strncmp(m->type, "multipart/", 10)!=0)
+ i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+ }else if(strncmp(m->type, "multipart/", 10) != 0)
+ i = egrow(i, "\t(", estrstrdup(m->type, ")"));
+ if(m->subject[0] != '\0'){
+ i = eappend(i, "\n", nil);
+ for(j=0; j<ind; j++)
+ i = eappend(i, "\t", nil);
+ i = eappend(i, "\t", m->subject);
+ }
+ return i;
+}
+
+void
+mesgmenu0(Window *w, Message *mbox, char *realdir, char *dir, int ind, Biobuf *fd, int onlyone, int dotail)
+{
+ int i;
+ Message *m;
+ char *name, *tmp;
+ int ogf=0;
+
+ if(strstr(realdir, "outgoing") != nil)
+ ogf=1;
+
+ /* show mail box in reverse order, pieces in forward order */
+ if(ind > 0)
+ m = mbox->head;
+ else
+ m = mbox->tail;
+ while(m != nil){
+ for(i=0; i<ind; i++)
+ Bprint(fd, "\t");
+ if(ind != 0)
+ Bprint(fd, " ");
+ name = estrstrdup(dir, m->name);
+ tmp = info(m, ind, ogf);
+ Bprint(fd, "%s%s\n", name, tmp);
+ free(tmp);
+ if(dotail && m->tail)
+ mesgmenu0(w, m, realdir, name, ind+1, fd, 0, dotail);
+ free(name);
+ if(ind)
+ m = m->next;
+ else
+ m = m->prev;
+ if(onlyone)
+ m = nil;
+ }
+}
+
+void
+mesgmenu(Window *w, Message *mbox)
+{
+ winopenbody(w, OWRITE);
+ mesgmenu0(w, mbox, mbox->name, "", 0, w->body, 0, !shortmenu);
+ winclosebody(w);
+}
+
+/* one new message has arrived, as mbox->tail */
+void
+mesgmenunew(Window *w, Message *mbox)
+{
+ Biobuf *b;
+
+ winselect(w, "0", 0);
+ w->data = winopenfile(w, "data");
+ b = emalloc(sizeof(Biobuf));
+ Binit(b, w->data, OWRITE);
+ mesgmenu0(w, mbox, mbox->name, "", 0, b, 1, !shortmenu);
+ Bterm(b);
+ free(b);
+ if(!mbox->dirty)
+ winclean(w);
+ /* select tag line plus following indented lines, but not final newline (it's distinctive) */
+ winselect(w, "0/.*\\n((\t.*\\n)*\t.*)?/", 1);
+ close(w->addr);
+ close(w->data);
+ w->addr = -1;
+ w->data = -1;
+}
+
+char*
+name2regexp(char *prefix, char *s)
+{
+ char *buf, *p, *q;
+
+ buf = emalloc(strlen(prefix)+2*strlen(s)+50); /* leave room to append more */
+ p = buf;
+ *p++ = '0';
+ *p++ = '/';
+ *p++ = '^';
+ strcpy(p, prefix);
+ p += strlen(prefix);
+ for(q=s; *q!='\0'; q++){
+ if(strchr(regexchars, *q) != nil)
+ *p++ = '\\';
+ *p++ = *q;
+ }
+ *p++ = '/';
+ *p = '\0';
+ return buf;
+}
+
+void
+mesgmenumarkdel(Window *w, Message *mbox, Message *m, int writeback)
+{
+ char *buf;
+
+
+ if(m->deleted)
+ return;
+ m->writebackdel = writeback;
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp("", m->name);
+ strcat(buf, "-#0");
+ if(winselect(w, buf, 1))
+ write(w->data, deleted, 10);
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ mbox->dirty = 1;
+ m->deleted = 1;
+}
+
+void
+mesgmenumarkundel(Window *w, Message*, Message *m)
+{
+ char *buf;
+
+ if(m->deleted == 0)
+ return;
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx, m->name);
+ if(winselect(w, buf, 1))
+ if(winsetaddr(w, deletedaddr, 1))
+ write(w->data, "", 0);
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ m->deleted = 0;
+}
+
+void
+mesgmenudel(Window *w, Message *mbox, Message *m)
+{
+ char *buf;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx, m->name);
+ if(winsetaddr(w, buf, 1) && winsetaddr(w, ".,./.*\\n(\t.*\\n)*/", 1))
+ write(w->data, "", 0);
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ mbox->dirty = 1;
+ m->deleted = 1;
+}
+
+void
+mesgmenumark(Window *w, char *which, char *mark)
+{
+ char *buf;
+
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ buf = name2regexp(deletedrx01, which);
+ if(winsetaddr(w, buf, 1) && winsetaddr(w, "+0-#1", 1)) /* go to end of line */
+ write(w->data, mark, strlen(mark));
+ free(buf);
+ close(w->data);
+ close(w->addr);
+ w->addr = w->data = -1;
+ if(!mbox.dirty)
+ winclean(w);
+}
+
+void
+mesgfreeparts(Message *m)
+{
+ free(m->name);
+ free(m->replyname);
+ free(m->fromcolon);
+ free(m->from);
+ free(m->to);
+ free(m->cc);
+ free(m->replyto);
+ free(m->date);
+ free(m->subject);
+ free(m->type);
+ free(m->disposition);
+ free(m->filename);
+ free(m->digest);
+}
+
+void
+mesgdel(Message *mbox, Message *m)
+{
+ Message *n, *next;
+
+ if(m->opened)
+ error("internal error: deleted message still open in mesgdel");
+ /* delete subparts */
+ for(n=m->head; n!=nil; n=next){
+ next = n->next;
+ mesgdel(m, n);
+ }
+ /* remove this message from list */
+ if(m->next)
+ m->next->prev = m->prev;
+ else
+ mbox->tail = m->prev;
+ if(m->prev)
+ m->prev->next = m->next;
+ else
+ mbox->head = m->next;
+
+ mesgfreeparts(m);
+}
+
+int
+deliver(char *folder, char *file)
+{
+ char *av[4];
+ int pid, wpid, nz;
+ Waitmsg *w;
+
+ pid = fork();
+ switch(pid){
+ case -1:
+ return -1;
+ case 0:
+ av[0] = "mbappend";
+ av[1] = folder;
+ av[2] = file;
+ av[3] = 0;
+ exec("/bin/upas/mbappend", av);
+ _exits("b0rked");
+ return -1;
+ default:
+ while(w = wait()){
+ nz = !w->msg || !w->msg[0];
+ if(!nz)
+ werrstr("%s", w->msg);
+ wpid = w->pid;
+ free(w);
+ if(wpid == pid)
+ return nz? 0: -1;
+ }
+ return -1;
+ }
+}
+
+int
+mesgsave(Message *m, char *s)
+{
+ char *t;
+ int ret;
+
+ t = smprint("%s/%s/rawunix", mbox.name, m->name);
+ if(s[0] != '/')
+ s = estrdup(s);
+ else
+ s = smprint("%s/%s", mailboxdir, s);
+ ret = 1;
+ if(deliver(s, t) == -1){
+ fprint(2, "Mail: save failed: can't write %s: %r\n", s);
+ ret = 0;
+ }
+ setflags(m, "S");
+ free(s);
+ free(t);
+ return ret;
+}
+
+int
+mesgcommand(Message *m, char *cmd)
+{
+ char *s;
+ char *args[10];
+ int ok, ret, nargs;
+
+ s = cmd;
+ ret = 1;
+ nargs = tokenize(s, args, nelem(args));
+ if(nargs == 0)
+ return 0;
+ if(strcmp(args[0], "Post") == 0){
+ mesgsend(m);
+ goto Return;
+ }
+ if(strncmp(args[0], "Save", 4) == 0){
+ if(m->isreply)
+ goto Return;
+ s = estrdup("\t[saved");
+ if(nargs==1 || strcmp(args[1], "")==0){
+ ok = mesgsave(m, "stored");
+ }else{
+ ok = mesgsave(m, args[1]);
+ s = eappend(s, " ", args[1]);
+ }
+ if(ok){
+ s = egrow(s, "]", nil);
+ mesgmenumark(mbox.w, m->name, s);
+ }
+ free(s);
+ setflags(m, "S");
+ goto Return;
+ }
+ if(strcmp(args[0], "Reply")==0){
+ if(nargs>=2 && strcmp(args[1], "all")==0)
+ mkreply(m, "Replyall", nil, nil, nil);
+ else
+ mkreply(m, "Reply", nil, nil, nil);
+// setflags(m, "a");
+ goto Return;
+ }
+ if(strcmp(args[0], "Q") == 0){
+ s = winselection(m->w); /* will be freed by mkreply */
+ if(nargs>=3 && strcmp(args[1], "Reply")==0 && strcmp(args[2], "all")==0)
+ mkreply(m, "QReplyall", nil, nil, s);
+ else
+ mkreply(m, "QReply", nil, nil, s);
+// setflags(m, "a");
+ goto Return;
+ }
+ if(strcmp(args[0], "Del") == 0){
+ if(windel(m->w, 0)){
+ chanfree(m->w->cevent);
+ free(m->w);
+ m->w = nil;
+ if(m->isreply)
+ delreply(m);
+ else{
+ m->opened = 0;
+ m->tagposted = 0;
+ }
+ free(cmd);
+ threadexits(nil);
+ }
+ goto Return;
+ }
+ if(strcmp(args[0], "Delmesg") == 0){
+ if(!m->isreply){
+ mesgmenumarkdel(wbox, &mbox, m, 1);
+ free(cmd); /* mesgcommand might not return */
+ mesgcommand(m, estrdup("Del"));
+ return 1;
+ }
+// setflags(m, "d");
+ goto Return;
+ }
+ if(strcmp(args[0], "UnDelmesg") == 0){
+ if(!m->isreply && m->deleted)
+ mesgmenumarkundel(wbox, &mbox, m);
+// setflags(m, "-d");
+ goto Return;
+ }
+// if(strcmp(args[0], "Headers") == 0){
+// m->showheaders();
+// return True;
+// }
+
+ ret = 0;
+
+ Return:
+ free(cmd);
+ return ret;
+}
+
+void
+mesgtagpost(Message *m)
+{
+ if(m->tagposted)
+ return;
+ wintagwrite(m->w, " Post", 5);
+ m->tagposted = 1;
+}
+
+/* need to expand selection more than default word */
+#pragma varargck argpos eval 2
+
+long
+eval(Window *w, char *s, ...)
+{
+ char buf[64];
+ va_list arg;
+
+ va_start(arg, s);
+ vsnprint(buf, sizeof buf, s, arg);
+ va_end(arg);
+
+ if(winsetaddr(w, buf, 1)==0)
+ return -1;
+
+ if(pread(w->addr, buf, 24, 0) != 24)
+ return -1;
+ return strtol(buf, 0, 10);
+}
+
+int
+isemail(char *s)
+{
+ int nat;
+
+ nat = 0;
+ for(; *s; s++)
+ if(*s == '@')
+ nat++;
+ else if(!isalpha(*s) && !isdigit(*s) && !strchr("_.-+/", *s))
+ return 0;
+ return nat==1;
+}
+
+char addrdelim[] = "/[ \t\\n<>()\\[\\]]/";
+char*
+expandaddr(Window *w, Event *e)
+{
+ char *s;
+ long q0, q1;
+
+ if(e->q0 != e->q1) /* cannot happen */
+ return nil;
+
+ q0 = eval(w, "#%d-%s", e->q0, addrdelim);
+ if(q0 == -1) /* bad char not found */
+ q0 = 0;
+ else /* increment past bad char */
+ q0++;
+
+ q1 = eval(w, "#%d+%s", e->q0, addrdelim);
+ if(q1 < 0){
+ q1 = eval(w, "$");
+ if(q1 < 0)
+ return nil;
+ }
+ if(q0 >= q1)
+ return nil;
+ s = emalloc((q1-q0)*UTFmax+1);
+ winread(w, q0, q1, s);
+ return s;
+}
+
+int
+replytoaddr(Window *w, Message *m, Event *e, char *s)
+{
+ int did;
+ char *buf;
+ Plumbmsg *pm;
+
+ buf = nil;
+ did = 0;
+ if(e->flag & 2){
+ /* autoexpanded; use our own bigger expansion */
+ buf = expandaddr(w, e);
+ if(buf == nil)
+ return 0;
+ s = buf;
+ }
+ if(isemail(s)){
+ did = 1;
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("Mail");
+ pm->dst = estrdup("sendmail");
+ pm->data = estrdup(s);
+ pm->ndata = -1;
+ if(m->subject && m->subject[0]){
+ pm->attr = emalloc(sizeof(Plumbattr));
+ pm->attr->name = estrdup("Subject");
+ if(tolower(m->subject[0]) != 'r' || tolower(m->subject[1]) != 'e' || m->subject[2] != ':')
+ pm->attr->value = estrstrdup("Re: ", m->subject);
+ else
+ pm->attr->value = estrdup(m->subject);
+ pm->attr->next = nil;
+ }
+ if(plumbsend(plumbsendfd, pm) < 0)
+ fprint(2, "error writing plumb message: %r\n");
+ plumbfree(pm);
+ }
+ free(buf);
+ return did;
+}
+
+
+void
+mesgctl(void *v)
+{
+ Message *m;
+ Window *w;
+ Event *e, *eq, *e2, *ea;
+ int na, nopen, i, j;
+ char *os, *s, *t, *buf;
+
+ m = v;
+ w = m->w;
+ threadsetname("mesgctl");
+ proccreate(wineventproc, w, STACK);
+ for(;;){
+ e = recvp(w->cevent);
+ switch(e->c1){
+ default:
+ Unk:
+ print("unknown message %c%c\n", e->c1, e->c2);
+ break;
+
+ case 'E': /* write to body; can't affect us */
+ break;
+
+ case 'F': /* generated by our actions; ignore */
+ break;
+
+ case 'K': /* type away; we don't care */
+ case 'M':
+ switch(e->c2){
+ case 'x': /* mouse only */
+ case 'X':
+ ea = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ if(e->flag & 8){
+ ea = recvp(w->cevent);
+ recvp(w->cevent);
+ na = ea->nb;
+ }else
+ na = 0;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ s = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, s);
+ }else
+ s = estrdup(eq->b);
+ if(na){
+ t = emalloc(strlen(s)+1+na+1);
+ sprint(t, "%s %s", s, ea->b);
+ free(s);
+ s = t;
+ }
+ if(!mesgcommand(m, s)) /* send it back */
+ winwriteevent(w, e);
+ break;
+
+ case 'l': /* mouse only */
+ case 'L':
+ buf = nil;
+ eq = e;
+ if(e->flag & 2){
+ e2 = recvp(w->cevent);
+ eq = e2;
+ }
+ s = eq->b;
+ if(eq->q1>eq->q0 && eq->nb==0){
+ buf = emalloc((eq->q1-eq->q0)*UTFmax+1);
+ winread(w, eq->q0, eq->q1, buf);
+ s = buf;
+ }
+ os = s;
+ nopen = 0;
+ do{
+ /* skip mail box name if present */
+ if(strncmp(s, mbox.name, strlen(mbox.name)) == 0)
+ s += strlen(mbox.name);
+ if(strstr(s, "body") != nil){
+ /* strip any known extensions */
+ for(i=0; exts[i].ext!=nil; i++){
+ j = strlen(exts[i].ext);
+ if(strlen(s)>j && strcmp(s+strlen(s)-j, exts[i].ext)==0){
+ s[strlen(s)-j] = '\0';
+ break;
+ }
+ }
+ if(strlen(s)>5 && strcmp(s+strlen(s)-5, "/body")==0)
+ s[strlen(s)-4] = '\0'; /* leave / in place */
+ }
+ nopen += mesgopen(&mbox, mbox.name, s, m, 0, nil);
+ while(*s!=0 && *s++!='\n')
+ ;
+ }while(*s);
+ if(nopen == 0 && e->c1 == 'L')
+ nopen += replytoaddr(w, m, e, os);
+ if(nopen == 0)
+ winwriteevent(w, e);
+ free(buf);
+ break;
+
+ case 'I': /* modify away; we don't care */
+ case 'D':
+ mesgtagpost(m);
+ /* fall through */
+ case 'd':
+ case 'i':
+ break;
+
+ default:
+ goto Unk;
+ }
+ }
+ }
+}
+
+void
+mesgline(Message *m, char *header, char *value)
+{
+ if(strlen(value) > 0)
+ Bprint(m->w->body, "%s: %s\n", header, value);
+}
+
+int
+isprintable(char *type)
+{
+ int i;
+
+ for(i=0; goodtypes[i]!=nil; i++)
+ if(strcmp(type, goodtypes[i])==0)
+ return 1;
+ return 0;
+}
+
+char*
+ext(char *type)
+{
+ int i;
+
+ for(i=0; exts[i].type!=nil; i++)
+ if(strcmp(type, exts[i].type)==0)
+ return exts[i].ext;
+ return "";
+}
+
+void
+mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly)
+{
+ char *dest, *maildest;
+
+ if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){
+ if(strlen(m->filename) == 0){
+ dest = estrdup(m->name);
+ dest[strlen(dest)-1] = '\0';
+ }else
+ dest = estrdup(m->filename);
+ if(maildest = getenv("maildest")){
+ maildest = eappend(maildest, "/", dest);
+ Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), maildest);
+ free(maildest);
+ }
+ if(m->filename[0] != '/')
+ dest = egrow(estrdup(home), "/", dest);
+ Bprint(w->body, "\tcp %s%sbody%s %q\n", rootdir, name, ext(m->type), dest);
+ free(dest);
+ }else if(!fileonly)
+ Bprint(w->body, "\tfile is %s%sbody%s\n", rootdir, name, ext(m->type));
+}
+
+void
+printheader(char *dir, Biobuf *b, char **okheaders)
+{
+ char *s;
+ char *lines[100];
+ int i, j, n;
+
+ s = readfile(dir, "header", nil);
+ if(s == nil)
+ return;
+ n = getfields(s, lines, nelem(lines), 0, "\n");
+ for(i=0; i<n; i++)
+ for(j=0; okheaders[j]; j++)
+ if(cistrncmp(lines[i], okheaders[j], strlen(okheaders[j])) == 0)
+ Bprint(b, "%s\n", lines[i]);
+ free(s);
+}
+
+void
+mesgload(Message *m, char *rootdir, char *file, Window *w)
+{
+ char *s, *subdir, *name, *dir;
+ Message *mp, *thisone;
+ int n;
+
+ dir = estrstrdup(rootdir, file);
+
+ if(strcmp(m->type, "message/rfc822") != 0){ /* suppress headers of envelopes */
+ if(strlen(m->from) > 0){
+ Bprint(w->body, "From: %s\n", m->from);
+ mesgline(m, "Date", m->date);
+ mesgline(m, "To", m->to);
+ mesgline(m, "CC", m->cc);
+ mesgline(m, "Subject", m->subject);
+ printheader(dir, w->body, extraheaders);
+ }else{
+ printheader(dir, w->body, okheaders);
+ printheader(dir, w->body, extraheaders);
+ }
+ Bprint(w->body, "\n");
+ }
+
+ if(m->level == 1 && m->recursed == 0){
+ m->recursed = 1;
+ readmbox(m, rootdir, m->name);
+ }
+ if(m->head == nil){ /* single part message */
+ if(strcmp(m->type, "text")==0 || strncmp(m->type, "text/", 5)==0){
+ mimedisplay(m, m->name, rootdir, w, 1);
+ s = readbody(m->type, dir, &n);
+ winwritebody(w, s, n);
+ free(s);
+ }else
+ mimedisplay(m, m->name, rootdir, w, 0);
+ }else{
+ /* multi-part message, either multipart/* or message/rfc822 */
+ thisone = nil;
+ if(strcmp(m->type, "multipart/alternative") == 0){
+ thisone = m->head; /* in case we can't find a good one */
+ for(mp=m->head; mp!=nil; mp=mp->next)
+ if(isprintable(mp->type)){
+ thisone = mp;
+ break;
+ }
+ }
+ for(mp=m->head; mp!=nil; mp=mp->next){
+ if(thisone!=nil && mp!=thisone)
+ continue;
+ subdir = estrstrdup(dir, mp->name);
+ name = estrstrdup(file, mp->name);
+ /* skip first element in name because it's already in window name */
+ if(mp != m->head)
+ Bprint(w->body, "\n===> %s (%s) [%s]\n", strchr(name, '/')+1, mp->type, mp->disposition);
+ if(strcmp(mp->type, "text")==0 || strncmp(mp->type, "text/", 5)==0){
+ mimedisplay(mp, name, rootdir, w, 1);
+ printheader(subdir, w->body, okheaders);
+ printheader(subdir, w->body, extraheaders);
+ winwritebody(w, "\n", 1);
+ s = readbody(mp->type, subdir, &n);
+ winwritebody(w, s, n);
+ free(s);
+ }else{
+ if(strncmp(mp->type, "multipart/", 10)==0 || strcmp(mp->type, "message/rfc822")==0){
+ mp->w = w;
+ mesgload(mp, rootdir, name, w);
+ mp->w = nil;
+ }else
+ mimedisplay(mp, name, rootdir, w, 0);
+ }
+ free(name);
+ free(subdir);
+ }
+ }
+ free(dir);
+}
+
+int
+tokenizec(char *str, char **args, int max, char *splitc)
+{
+ int na;
+ int intok = 0;
+
+ if(max <= 0)
+ return 0;
+ for(na=0; *str != '\0';str++){
+ if(strchr(splitc, *str) == nil){
+ if(intok)
+ continue;
+ args[na++] = str;
+ intok = 1;
+ }else{
+ /* it's a separator/skip character */
+ *str = '\0';
+ if(intok){
+ intok = 0;
+ if(na >= max)
+ break;
+ }
+ }
+ }
+ return na;
+}
+
+Message*
+mesglookup(Message *mbox, char *name, char *digest)
+{
+ int n;
+ Message *m;
+ char *t;
+
+ if(digest){
+ /* can find exactly */
+ for(m=mbox->head; m!=nil; m=m->next)
+ if(strcmp(digest, m->digest) == 0)
+ break;
+ return m;
+ }
+
+ n = strlen(name);
+ if(n == 0)
+ return nil;
+ if(name[n-1] == '/')
+ t = estrdup(name);
+ else
+ t = estrstrdup(name, "/");
+ for(m=mbox->head; m!=nil; m=m->next)
+ if(strcmp(t, m->name) == 0)
+ break;
+ free(t);
+ return m;
+}
+
+/*
+ * Find plumb port, knowing type is text, given file name (by extension)
+ */
+int
+plumbportbysuffix(char *file)
+{
+ char *suf;
+ int i, nsuf, nfile;
+
+ nfile = strlen(file);
+ for(i=0; ports[i].type!=nil; i++){
+ suf = ports[i].suffix;
+ nsuf = strlen(suf);
+ if(nfile > nsuf)
+ if(cistrncmp(file+nfile-nsuf, suf, nsuf) == 0)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * Find plumb port using type and file name (by extension)
+ */
+int
+plumbport(char *type, char *file)
+{
+ int i;
+
+ for(i=0; ports[i].type!=nil; i++)
+ if(strncmp(type, ports[i].type, strlen(ports[i].type)) == 0)
+ return i;
+ /* see if it's a text type */
+ for(i=0; goodtypes[i]!=nil; i++)
+ if(strncmp(type, goodtypes[i], strlen(goodtypes[i])) == 0)
+ return plumbportbysuffix(file);
+ return -1;
+}
+
+void
+plumb(Message *m, char *dir)
+{
+ int i;
+ char *port;
+ Plumbmsg *pm;
+
+ if(strlen(m->type) == 0)
+ return;
+ i = plumbport(m->type, m->filename);
+ if(i < 0)
+ fprint(2, "can't find destination for message subpart\n");
+ else{
+ port = ports[i].port;
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = estrdup("Mail");
+ if(port)
+ pm->dst = estrdup(port);
+ else
+ pm->dst = nil;
+ pm->wdir = nil;
+ pm->type = estrdup("text");
+ pm->ndata = -1;
+ pm->data = estrstrdup(dir, "body");
+ pm->data = eappend(pm->data, "", ports[i].suffix);
+ if(plumbsend(plumbsendfd, pm) < 0)
+ fprint(2, "error writing plumb message: %r\n");
+ plumbfree(pm);
+ }
+}
+
+int
+mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *digest)
+{
+ char *t, *u, *v;
+ Message *m;
+ char *direlem[10];
+ int i, ndirelem, reuse;
+
+ /* find white-space-delimited first word */
+ for(t=s; *t!='\0' && !isspace(*t); t++)
+ ;
+ u = emalloc(t-s+1);
+ memmove(u, s, t-s);
+ /* separate it on slashes */
+ ndirelem = tokenizec(u, direlem, nelem(direlem), "/");
+ if(ndirelem <= 0){
+ Error:
+ free(u);
+ return 0;
+ }
+ if(plumbed){
+ write(wctlfd, "top", 3);
+ write(wctlfd, "current", 7);
+ }
+ /* open window for message */
+ m = mesglookup(mbox, direlem[0], digest);
+ if(m == nil)
+ goto Error;
+ if(mesg!=nil && m!=mesg) /* string looked like subpart but isn't part of this message */
+ goto Error;
+ if(m->opened == 0){
+ if(m->w == nil){
+ reuse = 0;
+ m->w = newwindow();
+ }else{
+ reuse = 1;
+ /* re-use existing window */
+ if(winsetaddr(m->w, "0,$", 1)){
+ if(m->w->data < 0)
+ m->w->data = winopenfile(m->w, "data");
+ write(m->w->data, "", 0);
+ }
+ }
+ v = estrstrdup(mbox->name, m->name);
+ winname(m->w, v);
+ free(v);
+ if(!reuse){
+ if(m->deleted)
+ wintagwrite(m->w, "Q Reply all UnDelmesg Save ", 2+6+4+10+5);
+ else
+ wintagwrite(m->w, "Q Reply all Delmesg Save ", 2+6+4+8+5);
+ }
+ threadcreate(mesgctl, m, STACK);
+ winopenbody(m->w, OWRITE);
+ mesgload(m, dir, m->name, m->w);
+ winclosebody(m->w);
+ winclean(m->w);
+ m->opened = 1;
+ setflags(m, "s");
+ if(ndirelem == 1){
+ free(u);
+ return 1;
+ }
+ }
+ if(ndirelem == 1 && plumbport(m->type, m->filename) <= 0){
+ /* make sure dot is visible */
+ ctlprint(m->w->ctl, "show\n");
+ return 0;
+ }
+ /* walk to subpart */
+ dir = estrstrdup(dir, m->name);
+ for(i=1; i<ndirelem; i++){
+ m = mesglookup(m, direlem[i], digest);
+ if(m == nil)
+ break;
+ dir = egrow(dir, m->name, nil);
+ }
+ if(m != nil && plumbport(m->type, m->filename) > 0)
+ plumb(m, dir);
+ free(dir);
+ free(u);
+ return 1;
+}
+
+void
+rewritembox(Window *w, Message *mbox)
+{
+ Message *m, *next;
+ char *deletestr, *t;
+ int nopen;
+
+ deletestr = estrstrdup("delete ", fsname);
+
+ nopen = 0;
+ for(m=mbox->head; m!=nil; m=next){
+ next = m->next;
+ if(m->deleted == 0)
+ continue;
+ if(m->opened){
+ nopen++;
+ continue;
+ }
+ if(m->writebackdel){
+ /* messages deleted by plumb message are not removed again */
+ t = estrdup(m->name);
+ if(strlen(t) > 0)
+ t[strlen(t)-1] = '\0';
+ deletestr = egrow(deletestr, " ", t);
+ }
+ mesgmenudel(w, mbox, m);
+ mesgdel(mbox, m);
+ }
+ if(write(mbox->ctlfd, deletestr, strlen(deletestr)) < 0)
+ fprint(2, "Mail: warning: error removing mail message files: %r\n");
+ free(deletestr);
+ winselect(w, "0", 0);
+ if(nopen == 0)
+ winclean(w);
+ mbox->dirty = 0;
+}
+
+/* name is a full file name, but it might not belong to us */
+Message*
+mesglookupfile(Message *mbox, char *name, char *digest)
+{
+ int k, n;
+
+ k = strlen(name);
+ n = strlen(mbox->name);
+ if(k==0 || strncmp(name, mbox->name, n) != 0){
+// fprint(2, "Mail: message %s not in this mailbox\n", name);
+ return nil;
+ }
+ return mesglookup(mbox, name+n, digest);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/mkfile
@@ -1,0 +1,32 @@
+</$objtype/mkfile
+<../mkupas
+
+TARG=Mail
+OFILES=\
+ html.$O\
+ mail.$O\
+ mesg.$O\
+ reply.$O\
+ util.$O\
+ win.$O
+
+HFILES=dat.h
+LIB=
+
+# BIN=/acme/bin/$objtype
+BIN=$ABIN
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
+
+$O.out: $OFILES
+ $LD -o $target $LDFLAGS $OFILES
+
+syms:V:
+ $CC -a mail.c >syms
+ $CC -aa mesg.c reply.c util.c win.c >>syms
+
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/reply.c
@@ -1,0 +1,568 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <ctype.h>
+#include <plumb.h>
+#include "dat.h"
+
+static int replyid;
+
+int
+quote(Message *m, Biobuf *b, char *dir, char *quotetext)
+{
+ char *body, *type;
+ int i, n, nlines;
+ char **lines;
+
+ if(quotetext){
+ body = quotetext;
+ n = strlen(body);
+ type = nil;
+ }else{
+ /* look for first textual component to quote */
+ type = readfile(dir, "type", &n);
+ if(type == nil){
+ print("no type in %s\n", dir);
+ return 0;
+ }
+ if(strncmp(type, "multipart/", 10)==0 || strncmp(type, "message/", 8)==0){
+ dir = estrstrdup(dir, "1/");
+ if(quote(m, b, dir, nil)){
+ free(type);
+ free(dir);
+ return 1;
+ }
+ free(dir);
+ }
+ if(strncmp(type, "text", 4) != 0){
+ free(type);
+ return 0;
+ }
+ body = readbody(m->type, dir, &n);
+ if(body == nil)
+ return 0;
+ }
+ nlines = 0;
+ for(i=0; i<n; i++)
+ if(body[i] == '\n')
+ nlines++;
+ nlines++;
+ lines = emalloc(nlines*sizeof(char*));
+ nlines = getfields(body, lines, nlines, 0, "\n");
+ /* delete leading and trailing blank lines */
+ i = 0;
+ while(i<nlines && lines[i][0]=='\0')
+ i++;
+ while(i<nlines && lines[nlines-1][0]=='\0')
+ nlines--;
+ while(i < nlines){
+ Bprint(b, ">%s%s\n", lines[i][0]=='>'? "" : " ", lines[i]);
+ i++;
+ }
+ free(lines);
+ free(body); /* will free quotetext if non-nil */
+ free(type);
+ return 1;
+}
+
+void
+mkreply(Message *m, char *label, char *to, Plumbattr *attr, char *quotetext)
+{
+ Message *r;
+ char *dir, *t;
+ int quotereply;
+ Plumbattr *a;
+
+ quotereply = (label[0] == 'Q');
+ r = emalloc(sizeof(Message));
+ r->isreply = 1;
+ if(m != nil)
+ r->replyname = estrdup(m->name);
+ r->next = replies.head;
+ r->prev = nil;
+ if(replies.head != nil)
+ replies.head->prev = r;
+ replies.head = r;
+ if(replies.tail == nil)
+ replies.tail = r;
+ r->name = emalloc(strlen(mbox.name)+strlen(label)+10);
+ sprint(r->name, "%s%s%d", mbox.name, label, ++replyid);
+ r->w = newwindow();
+ winname(r->w, r->name);
+ ctlprint(r->w->ctl, "cleartag");
+ wintagwrite(r->w, "fmt Look Post Undo", 4+5+5+4);
+ r->tagposted = 1;
+ threadcreate(mesgctl, r, STACK);
+ winopenbody(r->w, OWRITE);
+ if(to!=nil && to[0]!='\0')
+ Bprint(r->w->body, "%s\n", to);
+ for(a=attr; a; a=a->next)
+ Bprint(r->w->body, "%s: %s\n", a->name, a->value);
+ dir = nil;
+ if(m != nil){
+ dir = estrstrdup(mbox.name, m->name);
+ if(to == nil && attr == nil){
+ /* Reply goes to replyto; Reply all goes to From and To and CC */
+ if(strstr(label, "all") == nil)
+ Bprint(r->w->body, "To: %s\n", m->replyto);
+ else{ /* Replyall */
+ if(strlen(m->from) > 0)
+ Bprint(r->w->body, "To: %s\n", m->from);
+ if(strlen(m->to) > 0)
+ Bprint(r->w->body, "To: %s\n", m->to);
+ if(strlen(m->cc) > 0)
+ Bprint(r->w->body, "CC: %s\n", m->cc);
+ }
+ }
+ if(strlen(m->subject) > 0){
+ t = "Subject: Re: ";
+ if(strlen(m->subject) >= 3)
+ if(tolower(m->subject[0])=='r' && tolower(m->subject[1])=='e' && m->subject[2]==':')
+ t = "Subject: ";
+ Bprint(r->w->body, "%s%s\n", t, m->subject);
+ }
+ if(!quotereply){
+ Bprint(r->w->body, "Include: %sraw\n", dir);
+ free(dir);
+ }
+ }
+ Bprint(r->w->body, "\n");
+ if(m == nil)
+ Bprint(r->w->body, "\n");
+ else if(quotereply){
+ quote(m, r->w->body, dir, quotetext);
+ free(dir);
+ }
+ winclosebody(r->w);
+ if(m==nil && (to==nil || to[0]=='\0'))
+ winselect(r->w, "0", 0);
+ else
+ winselect(r->w, "$", 0);
+ winclean(r->w);
+ windormant(r->w);
+}
+
+void
+delreply(Message *m)
+{
+ if(m->next == nil)
+ replies.tail = m->prev;
+ else
+ m->next->prev = m->prev;
+ if(m->prev == nil)
+ replies.head = m->next;
+ else
+ m->prev->next = m->next;
+ mesgfreeparts(m);
+ free(m);
+}
+
+/* copy argv to stack and free the incoming strings, so we don't leak argument vectors */
+void
+buildargv(char **inargv, char *argv[NARGS+1], char args[NARGCHAR])
+{
+ int i, n;
+ char *s, *a;
+
+ s = args;
+ for(i=0; i<NARGS; i++){
+ a = inargv[i];
+ if(a == nil)
+ break;
+ n = strlen(a)+1;
+ if((s-args)+n >= NARGCHAR) /* too many characters */
+ break;
+ argv[i] = s;
+ memmove(s, a, n);
+ s += n;
+ free(a);
+ }
+ argv[i] = nil;
+}
+
+void
+execproc(void *v)
+{
+ struct Exec *e;
+ int p[2], q[2];
+ char *prog;
+ char *argv[NARGS+1], args[NARGCHAR];
+
+ e = v;
+ p[0] = e->p[0];
+ p[1] = e->p[1];
+ q[0] = e->q[0];
+ q[1] = e->q[1];
+ prog = e->prog; /* known not to be malloc'ed */
+ rfork(RFFDG);
+ sendul(e->sync, 1);
+ buildargv(e->argv, argv, args);
+ free(e->argv);
+ chanfree(e->sync);
+ free(e);
+ dup(p[0], 0);
+ close(p[0]);
+ close(p[1]);
+ if(q[0]){
+ dup(q[1], 1);
+ close(q[0]);
+ close(q[1]);
+ }
+ procexec(nil, prog, argv);
+//fprint(2, "exec: %s", e->prog);
+//{int i;
+//for(i=0; argv[i]; i++) print(" '%s'", argv[i]);
+//print("\n");
+//}
+//argv[0] = "cat";
+//argv[1] = nil;
+//procexec(nil, "/bin/cat", argv);
+ fprint(2, "Mail: can't exec %s: %r\n", prog);
+ threadexits("can't exec");
+}
+
+enum{
+ ATTACH,
+ BCC,
+ CC,
+ FROM,
+ INCLUDE,
+ TO,
+};
+
+char *headers[] = {
+ "attach:",
+ "bcc:",
+ "cc:",
+ "from:",
+ "include:",
+ "to:",
+ nil,
+};
+
+int
+whichheader(char *h)
+{
+ int i;
+
+ for(i=0; headers[i]!=nil; i++)
+ if(cistrcmp(h, headers[i]) == 0)
+ return i;
+ return -1;
+}
+
+char *tolist[200];
+char *cclist[200];
+char *bcclist[200];
+int ncc, nbcc, nto;
+char *attlist[200];
+char included[200];
+
+int
+addressed(char *name)
+{
+ int i;
+
+ for(i=0; i<nto; i++)
+ if(strcmp(name, tolist[i]) == 0)
+ return 1;
+ for(i=0; i<ncc; i++)
+ if(strcmp(name, cclist[i]) == 0)
+ return 1;
+ for(i=0; i<nbcc; i++)
+ if(strcmp(name, bcclist[i]) == 0)
+ return 1;
+ return 0;
+}
+
+char*
+skipbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s!=' ' && *s!='\t' && *s!=',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+char*
+findbl(char *s, char *e)
+{
+ while(s < e){
+ if(*s==' ' || *s=='\t' || *s==',')
+ break;
+ s++;
+ }
+ return s;
+}
+
+/*
+ * comma-separate possibly blank-separated strings in line; e points before newline
+ */
+void
+commas(char *s, char *e)
+{
+ char *t;
+
+ /* may have initial blanks */
+ s = skipbl(s, e);
+ while(s < e){
+ s = findbl(s, e);
+ if(s == e)
+ break;
+ t = skipbl(s, e);
+ if(t == e) /* no more words */
+ break;
+ /* patch comma */
+ *s++ = ',';
+ while(s < t)
+ *s++ = ' ';
+ }
+}
+
+int
+print2(int fd, int ofd, char *fmt, ...)
+{
+ int m, n;
+ char *s;
+ va_list arg;
+
+ va_start(arg, fmt);
+ s = vsmprint(fmt, arg);
+ va_end(arg);
+ if(s == nil)
+ return -1;
+ m = strlen(s);
+ n = write(fd, s, m);
+ if(ofd > 0)
+ write(ofd, s, m);
+ return n;
+}
+
+int
+write2(int fd, int ofd, char *buf, int n, int nofrom)
+{
+ char *from, *p;
+ int m = 0;
+
+ if(fd >= 0)
+ m = write(fd, buf, n);
+
+ if(ofd <= 0)
+ return m;
+
+ if(nofrom == 0)
+ return write(ofd, buf, n);
+
+ /* need to escape leading From lines to avoid corrupting 'outgoing' mailbox */
+ for(p=buf; *p; p+=m){
+ from = cistrstr(p, "from");
+ if(from == nil)
+ m = n;
+ else
+ m = from - p;
+ if(m > 0)
+ write(ofd, p, m);
+ if(from){
+ /* escape with space if From is at start of line */
+ if(p==buf || from[-1]=='\n')
+ write(ofd, " ", 1);
+ write(ofd, from, 4);
+ m += 4;
+ }
+ n -= m;
+ }
+ return p - buf;
+}
+
+void
+mesgsend(Message *m)
+{
+ char *s, *body, *to;
+ int i, j, h, n, natt, p[2];
+ struct Exec *e;
+ Channel *sync;
+ int first, nfld, delit, ofd;
+ char *copy, *fld[100], *now;
+
+ body = winreadbody(m->w, &n);
+ /* assemble to: list from first line, to: line, and cc: line */
+ nto = 0;
+ natt = 0;
+ ncc = 0;
+ nbcc = 0;
+ first = 1;
+ to = body;
+ for(;;){
+ for(s=to; *s!='\n'; s++)
+ if(*s == '\0'){
+ free(body);
+ return;
+ }
+ if(s++ == to) /* blank line */
+ break;
+ /* make copy of line to tokenize */
+ copy = emalloc(s-to);
+ memmove(copy, to, s-to);
+ copy[s-to-1] = '\0';
+ nfld = tokenizec(copy, fld, nelem(fld), ", \t");
+ if(nfld == 0){
+ free(copy);
+ break;
+ }
+ n -= s-to;
+ switch(h = whichheader(fld[0])){
+ case TO:
+ case FROM:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && nto<nelem(tolist); i++)
+ if(!addressed(fld[i]))
+ tolist[nto++] = estrdup(fld[i]);
+ break;
+ case BCC:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && nbcc<nelem(bcclist); i++)
+ if(!addressed(fld[i]))
+ bcclist[nbcc++] = estrdup(fld[i]);
+ break;
+ case CC:
+ delit = 1;
+ commas(to+strlen(fld[0]), s-1);
+ for(i=1; i<nfld && ncc<nelem(cclist); i++)
+ if(!addressed(fld[i]))
+ cclist[ncc++] = estrdup(fld[i]);
+ break;
+ case ATTACH:
+ case INCLUDE:
+ delit = 1;
+ for(i=1; i<nfld && natt<nelem(attlist); i++){
+ attlist[natt] = estrdup(fld[i]);
+ included[natt++] = (h == INCLUDE);
+ }
+ break;
+ default:
+ if(first){
+ delit = 1;
+ for(i=0; i<nfld && nto<nelem(tolist); i++)
+ tolist[nto++] = estrdup(fld[i]);
+ }else /* ignore it */
+ delit = 0;
+ break;
+ }
+ if(delit){
+ /* delete line from body */
+ memmove(to, s, n+1);
+ }else
+ to = s;
+ free(copy);
+ first = 0;
+ }
+
+ ofd = open(outgoing, OWRITE|OCEXEC); /* no error check necessary */
+ if(ofd > 0){
+ /* From dhog Fri Aug 24 22:13:00 EDT 2001 */
+ now = ctime(time(0));
+ fprint(ofd, "From %s %s", user, now);
+ fprint(ofd, "From: %s\n", user);
+ fprint(ofd, "Date: %s", now);
+ for(i=0; i<natt; i++)
+ if(included[i])
+ fprint(ofd, "Include: %s\n", attlist[i]);
+ else
+ fprint(ofd, "Attach: %s\n", attlist[i]);
+ /* needed because mail is by default Latin-1 */
+ fprint(ofd, "Content-Type: text/plain; charset=\"UTF-8\"\n");
+ fprint(ofd, "Content-Transfer-Encoding: 8bit\n");
+ }
+
+ e = emalloc(sizeof(struct Exec));
+ if(pipe(p) < 0)
+ error("can't create pipe: %r");
+ e->p[0] = p[0];
+ e->p[1] = p[1];
+ e->prog = "/bin/upas/marshal";
+ e->argv = emalloc((1+1+2+4*natt+1)*sizeof(char*));
+ e->argv[0] = estrdup("marshal");
+ e->argv[1] = estrdup("-8");
+ j = 2;
+ if(m->replyname){
+ e->argv[j++] = estrdup("-R");
+ e->argv[j++] = estrstrdup(mbox.name, m->replyname);
+ }
+ for(i=0; i<natt; i++){
+ if(included[i])
+ e->argv[j++] = estrdup("-A");
+ else
+ e->argv[j++] = estrdup("-a");
+ e->argv[j++] = estrdup(attlist[i]);
+ }
+ sync = chancreate(sizeof(int), 0);
+ e->sync = sync;
+ proccreate(execproc, e, EXECSTACK);
+ recvul(sync);
+ close(p[0]);
+
+ /* using marshal -8, so generate rfc822 headers */
+ if(nto > 0){
+ print2(p[1], ofd, "To: ");
+ for(i=0; i<nto-1; i++)
+ print2(p[1], ofd, "%s, ", tolist[i]);
+ print2(p[1], ofd, "%s\n", tolist[i]);
+ }
+ if(ncc > 0){
+ print2(p[1], ofd, "CC: ");
+ for(i=0; i<ncc-1; i++)
+ print2(p[1], ofd, "%s, ", cclist[i]);
+ print2(p[1], ofd, "%s\n", cclist[i]);
+ }
+ if(nbcc > 0){
+ print2(p[1], ofd, "BCC: ");
+ for(i=0; i<nbcc-1; i++)
+ print2(p[1], ofd, "%s, ", bcclist[i]);
+ print2(p[1], ofd, "%s\n", bcclist[i]);
+ }
+
+ i = strlen(body);
+ if(i > 0)
+ write2(p[1], ofd, body, i, 1);
+
+ /* guarantee a blank line, to ensure attachments are separated from body */
+ if(i==0 || body[i-1]!='\n')
+ write2(p[1], ofd, "\n\n", 2, 0);
+ else if(i>1 && body[i-2]!='\n')
+ write2(p[1], ofd, "\n", 1, 0);
+
+ /* these look like pseudo-attachments in the "outgoing" box */
+ if(ofd>0 && natt>0){
+ for(i=0; i<natt; i++)
+ if(included[i])
+ fprint(ofd, "=====> Include: %s\n", attlist[i]);
+ else
+ fprint(ofd, "=====> Attach: %s\n", attlist[i]);
+ }
+ if(ofd > 0)
+ write(ofd, "\n", 1);
+
+ for(i=0; i<natt; i++)
+ free(attlist[i]);
+ close(ofd);
+ close(p[1]);
+ free(body);
+
+ if(m->replyname != nil)
+ mesgmenumark(mbox.w, m->replyname, "\t[replied]");
+ if(m->name[0] == '/')
+ s = estrdup(m->name);
+ else
+ s = estrstrdup(mbox.name, m->name);
+ s = egrow(s, "-R", nil);
+ winname(m->w, s);
+ free(s);
+ winclean(m->w);
+ /* mark message unopened because it's no longer the original message */
+ m->opened = 0;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/util.c
@@ -1,0 +1,105 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include "dat.h"
+
+void*
+emalloc(uint n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ error("can't malloc: %r");
+ memset(p, 0, n);
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void*
+erealloc(void *p, uint n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ error("can't realloc: %r");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s)+1);
+ strcpy(t, s);
+ return t;
+}
+
+char*
+estrstrdup(char *s, char *t)
+{
+ char *u;
+
+ u = emalloc(strlen(s)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, t);
+ return u;
+}
+
+char*
+eappend(char *s, char *sep, char *t)
+{
+ char *u;
+
+ if(t == nil)
+ u = estrstrdup(s, sep);
+ else{
+ u = emalloc(strlen(s)+strlen(sep)+strlen(t)+1);
+ strcpy(u, s);
+ strcat(u, sep);
+ strcat(u, t);
+ }
+ free(s);
+ return u;
+}
+
+char*
+egrow(char *s, char *sep, char *t)
+{
+ s = eappend(s, sep, t);
+ free(t);
+ return s;
+}
+
+void
+error(char *fmt, ...)
+{
+ Fmt f;
+ char buf[64];
+ va_list arg;
+
+ fmtfdinit(&f, 2, buf, sizeof buf);
+ fmtprint(&f, "Mail: ");
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\n");
+ fmtfdflush(&f);
+ threadexitsall(fmt);
+}
+
+void
+ctlprint(int fd, char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = vfprint(fd, fmt, arg);
+ va_end(arg);
+ if(n <= 0)
+ error("control file write error: %r");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/Mail/win.c
@@ -1,0 +1,341 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <plumb.h>
+#include "dat.h"
+
+Window*
+newwindow(void)
+{
+ char buf[12];
+ Window *w;
+
+ w = emalloc(sizeof(Window));
+ w->ctl = open("/mnt/wsys/new/ctl", ORDWR|OCEXEC);
+ if(w->ctl<0 || read(w->ctl, buf, 12)!=12)
+ error("can't open window ctl file: %r");
+ ctlprint(w->ctl, "noscroll\n");
+ w->id = atoi(buf);
+ w->event = winopenfile(w, "event");
+ w->addr = -1; /* will be opened when needed */
+ w->body = nil;
+ w->data = -1;
+ w->cevent = chancreate(sizeof(Event*), 0);
+ return w;
+}
+
+void
+winsetdump(Window *w, char *dir, char *cmd)
+{
+ if(dir != nil)
+ ctlprint(w->ctl, "dumpdir %s\n", dir);
+ if(cmd != nil)
+ ctlprint(w->ctl, "dump %s\n", cmd);
+}
+
+void
+wineventproc(void *v)
+{
+ Window *w;
+ int i;
+
+ w = v;
+ for(i=0; ; i++){
+ if(i >= NEVENT)
+ i = 0;
+ wingetevent(w, &w->e[i]);
+ sendp(w->cevent, &w->e[i]);
+ }
+}
+
+static int
+winopenfile1(Window *w, char *f, int m)
+{
+ char buf[64];
+ int fd;
+
+ sprint(buf, "/mnt/wsys/%d/%s", w->id, f);
+ fd = open(buf, m|OCEXEC);
+ if(fd < 0)
+ error("can't open window file %s: %r", f);
+ return fd;
+}
+
+int
+winopenfile(Window *w, char *f)
+{
+ return winopenfile1(w, f, ORDWR);
+}
+
+void
+wintagwrite(Window *w, char *s, int n)
+{
+ int fd;
+
+ fd = winopenfile(w, "tag");
+ if(write(fd, s, n) != n)
+ error("tag write: %r");
+ close(fd);
+}
+
+void
+winname(Window *w, char *s)
+{
+ ctlprint(w->ctl, "name %s\n", s);
+}
+
+void
+winopenbody(Window *w, int mode)
+{
+ char buf[256];
+
+ sprint(buf, "/mnt/wsys/%d/body", w->id);
+ w->body = Bopen(buf, mode|OCEXEC);
+ if(w->body == nil)
+ error("can't open window body file: %r");
+}
+
+void
+winclosebody(Window *w)
+{
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+}
+
+void
+winwritebody(Window *w, char *s, int n)
+{
+ if(w->body == nil)
+ winopenbody(w, OWRITE);
+ if(Bwrite(w->body, s, n) != n)
+ error("write error to window: %r");
+}
+
+int
+wingetec(Window *w)
+{
+ if(w->nbuf == 0){
+ w->nbuf = read(w->event, w->buf, sizeof w->buf);
+ if(w->nbuf <= 0){
+ /* probably because window has exited, and only called by wineventproc, so just shut down */
+ threadexits(nil);
+ }
+ w->bufp = w->buf;
+ }
+ w->nbuf--;
+ return *w->bufp++;
+}
+
+int
+wingeten(Window *w)
+{
+ int n, c;
+
+ n = 0;
+ while('0'<=(c=wingetec(w)) && c<='9')
+ n = n*10+(c-'0');
+ if(c != ' ')
+ error("event number syntax");
+ return n;
+}
+
+int
+wingeter(Window *w, char *buf, int *nb)
+{
+ Rune r;
+ int n;
+
+ r = wingetec(w);
+ buf[0] = r;
+ n = 1;
+ if(r >= Runeself) {
+ while(!fullrune(buf, n))
+ buf[n++] = wingetec(w);
+ chartorune(&r, buf);
+ }
+ *nb = n;
+ return r;
+}
+
+void
+wingetevent(Window *w, Event *e)
+{
+ int i, nb;
+
+ e->c1 = wingetec(w);
+ e->c2 = wingetec(w);
+ e->q0 = wingeten(w);
+ e->q1 = wingeten(w);
+ e->flag = wingeten(w);
+ e->nr = wingeten(w);
+ if(e->nr > EVENTSIZE)
+ error("event string too long");
+ e->nb = 0;
+ for(i=0; i<e->nr; i++){
+ e->r[i] = wingeter(w, e->b+e->nb, &nb);
+ e->nb += nb;
+ }
+ e->r[e->nr] = 0;
+ e->b[e->nb] = 0;
+ if(wingetec(w) != '\n')
+ error("event syntax error");
+}
+
+void
+winwriteevent(Window *w, Event *e)
+{
+ fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
+}
+
+void
+winread(Window *w, uint q0, uint q1, char *data)
+{
+ int m, n, nr;
+ char buf[256];
+
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(w->data < 0)
+ w->data = winopenfile(w, "data");
+ m = q0;
+ while(m < q1){
+ n = sprint(buf, "#%d", m);
+ if(write(w->addr, buf, n) != n)
+ error("error writing addr: %r");
+ n = read(w->data, buf, sizeof buf);
+ if(n <= 0)
+ error("reading data: %r");
+ nr = utfnlen(buf, n);
+ while(m+nr >q1){
+ do; while(n>0 && (buf[--n]&0xC0)==0x80);
+ --nr;
+ }
+ if(n == 0)
+ break;
+ memmove(data, buf, n);
+ data += n;
+ *data = 0;
+ m += nr;
+ }
+}
+
+void
+windormant(Window *w)
+{
+ if(w->addr >= 0){
+ close(w->addr);
+ w->addr = -1;
+ }
+ if(w->body != nil){
+ Bterm(w->body);
+ w->body = nil;
+ }
+ if(w->data >= 0){
+ close(w->data);
+ w->data = -1;
+ }
+}
+
+
+int
+windel(Window *w, int sure)
+{
+ if(sure)
+ write(w->ctl, "delete\n", 7);
+ else if(write(w->ctl, "del\n", 4) != 4)
+ return 0;
+ /* event proc will die due to read error from event file */
+ windormant(w);
+ close(w->ctl);
+ w->ctl = -1;
+ close(w->event);
+ w->event = -1;
+ return 1;
+}
+
+void
+winclean(Window *w)
+{
+ if(w->body)
+ Bflush(w->body);
+ ctlprint(w->ctl, "clean\n");
+}
+
+int
+winsetaddr(Window *w, char *addr, int errok)
+{
+ if(w->addr < 0)
+ w->addr = winopenfile(w, "addr");
+ if(write(w->addr, addr, strlen(addr)) < 0){
+ if(!errok)
+ error("error writing addr(%s): %r", addr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+winselect(Window *w, char *addr, int errok)
+{
+ if(winsetaddr(w, addr, errok)){
+ ctlprint(w->ctl, "dot=addr\n");
+ return 1;
+ }
+ return 0;
+}
+
+char*
+winreadbody(Window *w, int *np) /* can't use readfile because acme doesn't report the length */
+{
+ char *s;
+ int m, na, n;
+
+ if(w->body != nil)
+ winclosebody(w);
+ winopenbody(w, OREAD);
+ s = nil;
+ na = 0;
+ n = 0;
+ for(;;){
+ if(na < n+512){
+ na += 1024;
+ s = realloc(s, na+1);
+ }
+ m = Bread(w->body, s+n, na-n);
+ if(m <= 0)
+ break;
+ n += m;
+ }
+ s[n] = 0;
+ winclosebody(w);
+ *np = n;
+ return s;
+}
+
+char*
+winselection(Window *w)
+{
+ int fd, m, n;
+ char *buf;
+ char tmp[256];
+
+ fd = winopenfile1(w, "rdsel", OREAD);
+ if(fd < 0)
+ error("can't open rdsel: %r");
+ n = 0;
+ buf = nil;
+ for(;;){
+ m = read(fd, tmp, sizeof tmp);
+ if(m <= 0)
+ break;
+ buf = erealloc(buf, n+m+1);
+ memmove(buf+n, tmp, m);
+ n += m;
+ buf[n] = '\0';
+ }
+ close(fd);
+ return buf;
+}
--- a/sys/src/cmd/upas/alias/aliasmail.c
+++ b/sys/src/cmd/upas/alias/aliasmail.c
@@ -5,28 +5,30 @@
* local ones.
*/
-/* predeclared */
-static String *getdbfiles(void);
-static int translate(char*, char**, String*, String*);
-static int lookup(String**, String*, String*);
-static int compare(String*, char*);
-static char* mklower(char*);
+static String *getdbfiles(void);
+static int translate(char*, char**, String*, String*);
+static int lookup(String**, String*, String*);
+static char *mklower(char*);
-static int debug;
-static int from;
-static char *namefiles = "namefiles";
-#define DEBUG if(debug)
+static int debug;
+static int from;
+static char *namefiles = "namefiles";
-/* loop through the names to be translated */
+#define dprint(...) if(debug)fprint(2, __VA_ARGS__); else {}
+
void
+usage(void)
+{
+ fprint(2, "usage: aliasmail [-df] [-n namefile] [names ...]\n");
+ exits("usage");
+}
+
+void
main(int argc, char *argv[])
{
- String *s;
- String *alias; /* the alias for the name */
- char **names; /* names of this system */
- String *files; /* list of files to search */
+ char *alias, **names, *p; /* names of this system */
int i, rv;
- char *p;
+ String *s, *salias, *files;
ARGBEGIN {
case 'd':
@@ -36,50 +38,46 @@
from = 1;
break;
case 'n':
- namefiles = ARGF();
+ namefiles = EARGF(usage());
break;
+ default:
+ usage();
} ARGEND
- if (chdir(UPASLIB) < 0) {
- perror("translate(chdir):");
- exit(1);
- }
+ if (chdir(UPASLIB) < 0)
+ sysfatal("chdir: %r");
- /* get environmental info */
names = sysnames_read();
files = getdbfiles();
- alias = s_new();
+ salias = s_new();
/* loop through the names to be translated (from standard input) */
for(i=0; i<argc; i++) {
s = unescapespecial(s_copy(mklower(argv[i])));
if(strchr(s_to_c(s), '!') == 0)
- rv = translate(s_to_c(s), names, files, alias);
+ rv = translate(s_to_c(s), names, files, salias);
else
rv = -1;
+ alias = s_to_c(salias);
if(from){
- if (rv >= 0 && *s_to_c(alias) != '\0'){
- p = strchr(s_to_c(alias), '\n');
- if(p)
+ if (rv >= 0 && *alias != '\0'){
+ if(p = strchr(alias, '\n'))
*p = 0;
- p = strchr(s_to_c(alias), '!');
- if(p) {
+ if(p = strchr(alias, '!')) {
*p = 0;
- print("%s", s_to_c(alias));
+ print("%s", alias);
} else {
- p = strchr(s_to_c(alias), '@');
- if(p)
+ if(p = strchr(alias, '@'))
print("%s", p+1);
else
- print("%s", s_to_c(alias));
+ print("%s", alias);
}
}
} else {
- if (rv < 0 || *s_to_c(alias) == '\0')
+ if (rv < 0 || *alias == '\0')
print("local!%s\n", s_to_c(s));
- else {
+ else
/* this must be a write, not a print */
- write(1, s_to_c(alias), strlen(s_to_c(alias)));
- }
+ write(1, alias, strlen(alias));
}
s_free(s);
}
@@ -90,9 +88,9 @@
static String *
getdbfiles(void)
{
- Sinstack *sp;
- String *files = s_new();
char *nf;
+ Sinstack *sp;
+ String *files;
if(from)
nf = "fromfiles";
@@ -100,6 +98,7 @@
nf = namefiles;
/* system wide aliases */
+ files = s_new();
if ((sp = s_allocinstack(nf)) != 0){
while(s_rdinstack(sp, files))
s_append(files, " ");
@@ -106,31 +105,27 @@
s_freeinstack(sp);
}
-
- DEBUG print("files are %s\n", s_to_c(files));
-
+ dprint("files are %s\n", s_to_c(files));
return files;
}
/* loop through the translation files */
static int
-translate(char *name, /* name to translate */
- char **namev, /* names of this system */
- String *files, /* names of system alias files */
- String *alias) /* where to put the alias */
+translate(char *name, char **namev, String *files, String *alias)
{
- String *file = s_new();
- String **fullnamev;
int n, rv;
+ String *file, **fullnamev;
rv = -1;
+ file = s_new();
- DEBUG print("translate(%s, %s, %s)\n", name,
+ dprint("translate(%s, %s, %s)\n", name,
s_to_c(files), s_to_c(alias));
/* create the full name to avoid loops (system!name) */
for(n = 0; namev[n]; n++)
;
+
fullnamev = (String**)malloc(sizeof(String*)*(n+2));
n = 0;
fullnamev[n++] = s_copy(name);
@@ -144,12 +139,11 @@
/* look at system-wide names */
s_restart(files);
- while (s_parse(files, s_restart(file)) != 0) {
+ while (s_parse(files, s_restart(file)) != 0)
if (lookup(fullnamev, file, alias)==0) {
rv = 0;
goto out;
}
- }
out:
for(n = 0; fullnamev[n]; n++)
@@ -183,22 +177,23 @@
/* Loop through the entries in a translation file looking for a match.
* Return 0 if found, -1 otherwise.
*/
+#define compare(a, b) cistrcmp(s_to_c(a), b)
+
static int
-lookup(
- String **namev,
- String *file,
- String *alias) /* returned String */
+lookup(String **namev, String *file, String *alias)
{
- String *line = s_new();
- String *token = s_new();
- String *bangtoken;
- int i, rv = -1;
- char *name = s_to_c(namev[0]);
+ char *name;
+ int i, rv;
+ String *line, *token, *bangtoken;
Sinstack *sp;
- DEBUG print("lookup(%s, %s, %s, %s)\n", s_to_c(namev[0]), s_to_c(namev[1]),
+ dprint("lookup(%s, %s, %s, %s)\n", s_to_c(namev[0]), s_to_c(namev[1]),
s_to_c(file), s_to_c(alias));
+ rv = -1;
+ name = s_to_c(namev[0]);
+ line = s_new();
+ token = s_new();
s_reset(alias);
if ((sp = s_allocinstack(s_to_c(file))) == 0)
return -1;
@@ -205,7 +200,7 @@
/* look for a match */
while (s_rdinstack(sp, s_restart(line))!=0) {
- DEBUG print("line is %s\n", s_to_c(line));
+ dprint("line is %s\n", s_to_c(line));
s_restart(token);
if (s_parse(s_restart(line), token)==0)
continue;
@@ -247,35 +242,13 @@
return rv;
}
-#define lower(c) ((c)>='A' && (c)<='Z' ? (c)-('A'-'a'):(c))
-
-/* compare two Strings (case insensitive) */
-static int
-compare(String *s1,
- char *p2)
-{
- char *p1 = s_to_c(s1);
- int rv;
-
- DEBUG print("comparing %s to %s\n", p1, p2);
- while((rv = lower(*p1) - lower(*p2)) == 0) {
- if (*p1 == '\0')
- break;
- p1++;
- p2++;
- }
- return rv;
-}
-
static char*
mklower(char *name)
{
- char *p;
- char c;
+ char c, *p;
- for(p = name; *p; p++){
- c = *p;
- *p = lower(c);
- }
+ for(p = name; c = *p; p++)
+ if(c >= 'A' && c <= 'Z')
+ *p = c + 0x20;
return name;
}
--- a/sys/src/cmd/upas/alias/mkfile
+++ b/sys/src/cmd/upas/alias/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=aliasmail
@@ -9,8 +10,6 @@
HFILES=../common/common.h\
../common/sys.h\
-
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/isspam.rc
@@ -1,0 +1,2 @@
+#!/bin/rc
+exec /mail/lib/isspam.rc $*
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/mkfile
@@ -1,0 +1,34 @@
+</$objtype/mkfile
+<../mkupas
+
+RCFILES=isspam\
+ msgcat\
+ spam\
+ tfmt\
+ unspam\
+
+all:Q:
+ ;
+
+installall:Q: install
+ ;
+
+install:V: ${RCFILES:%=$BIN/%}
+
+safeinstall:V: install
+
+safeinstallall:V: install
+
+clean:Q:
+ ;
+nuke:V:
+ rm $BIN/^($RCFILES)
+
+UPDATE=$RCFILES
+
+update:V:
+ update $UPDATEFLAGS $UPDATE
+
+$BIN/%: %.rc
+ cp $stem.rc $BIN/$stem
+
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/mkfile.rc
@@ -1,0 +1,38 @@
+
+RCFILES=mail.rc\
+
+all:Q:
+ ;
+
+installall:Q: install
+ ;
+
+install:V:
+ cp mail.rc /rc/bin/mail
+
+safeinstall:V:
+ cp mail.rc /rc/bin/mail
+
+safeinstallall:V:
+ cp mail.rc /rc/bin/mail
+
+clean:Q:
+ ;
+nuke:V:
+ rm /rc/bin/mail
+
+UPDATE=\
+ gone.fishing\
+ gone.msg\
+ mail.rc\
+ mail.sh\
+ makefile\
+ mkfile\
+ namefiles\
+ omail.rc\
+ qmail\
+ remotemail\
+ rewrite\
+
+update:V:
+ update $UPDATEFLAGS $UPDATE
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/msgcat.rc
@@ -1,0 +1,11 @@
+#!/bin/rc
+
+f=$*
+if(~ $#f 0)
+ f=/mail/fs/mbox/[0-9]*
+f=`{echo $f|sed s:/mail/fs/mbox/::g}
+
+{
+ for(i in $f)
+ echo $i p
+} | upas/nedmail >[2=]
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/spam.rc
@@ -1,0 +1,2 @@
+#!/bin/rc
+exec /mail/lib/spam.rc $*
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/tfmt.rc
@@ -1,0 +1,25 @@
+#!/bin/rc
+# anti-topposting defense
+
+# sed '/^[ ]*>[ ]*>[ ]*>/q'
+
+awk '
+{
+ if(l[i] ~ /^[ ]*>[ ]*>[ ]*>/)
+ q = 1
+ if(q == 0)
+ l[i = NR] = $0;
+}
+END{
+ for(; i > 1; i--)
+ if(l[i] !~ /^([ ]*>)*[ ]*$/)
+ break;
+ for(; i > 1; i--)
+ if(l[i] !~ /^[ ]*>[ ]*>/)
+ break;
+ for(; i > 1; i--)
+ if(l[i] !~ /^([ ]*>)*[ ]*$/)
+ break;
+ for(j = 1; j <= i; j++)
+ print l[j]
+}' |dd -conv block >[2=]
--- /dev/null
+++ b/sys/src/cmd/upas/binscripts/unspam.rc
@@ -1,0 +1,2 @@
+#!/bin/rc
+exec /mail/lib/unspam.rc $*
--- a/sys/src/cmd/upas/common/appendfiletombox.c
+++ /dev/null
@@ -1,164 +1,0 @@
-#include "common.h"
-
-enum {
- Buffersize = 64*1024,
-};
-
-typedef struct Inbuf Inbuf;
-struct Inbuf
-{
- char buf[Buffersize];
- char *wp;
- char *rp;
- int eof;
- int in;
- int out;
- int last;
- ulong bytes;
-};
-
-static Inbuf*
-allocinbuf(int in, int out)
-{
- Inbuf *b;
-
- b = mallocz(sizeof(Inbuf), 1);
- if(b == nil)
- sysfatal("reading mailbox: %r");
- b->rp = b->wp = b->buf;
- b->in = in;
- b->out = out;
- return b;
-}
-
-/* should only be called at start of file or when b->rp[-1] == '\n' */
-static int
-fill(Inbuf *b, int addspace)
-{
- int i, n;
-
- if(b->eof && b->wp - b->rp == 0)
- return 0;
-
- n = b->rp - b->buf;
- if(n > 0){
- i = write(b->out, b->buf, n);
- if(i != n)
- return -1;
- b->last = b->buf[n-1];
- b->bytes += n;
- }
- if(addspace){
- if(write(b->out, " ", 1) != 1)
- return -1;
- b->last = ' ';
- b->bytes++;
- }
-
- n = b->wp - b->rp;
- memmove(b->buf, b->rp, n);
- b->rp = b->buf;
- b->wp = b->rp + n;
-
- i = read(b->in, b->buf+n, sizeof(b->buf)-n);
- if(i < 0)
- return -1;
- b->wp += i;
-
- return b->wp - b->rp;
-}
-
-enum { Fromlen = sizeof "From " - 1, };
-
-/* code to escape ' '*From' ' at the beginning of a line */
-int
-appendfiletombox(int in, int out)
-{
- int addspace, n, sol;
- char *p;
- Inbuf *b;
-
- seek(out, 0, 2);
-
- b = allocinbuf(in, out);
- addspace = 0;
- sol = 1;
-
- for(;;){
- if(b->wp - b->rp < Fromlen){
- /*
- * not enough unread bytes in buffer to match "From ",
- * so get some more. We must only inject a space at
- * the start of a line (one that begins with "From ").
- */
- if (b->rp == b->buf || b->rp[-1] == '\n') {
- n = fill(b, addspace);
- addspace = 0;
- } else
- n = fill(b, 0);
- if(n < 0)
- goto error;
- if(n == 0)
- break;
- if(n < Fromlen){ /* still can't match? */
- b->rp = b->wp;
- continue;
- }
- }
-
- /* state machine looking for ' '*From' ' */
- if(!sol){
- p = memchr(b->rp, '\n', b->wp - b->rp);
- if(p == nil)
- b->rp = b->wp;
- else{
- b->rp = p+1;
- sol = 1;
- }
- continue;
- } else {
- if(*b->rp == ' ' || strncmp(b->rp, "From ", Fromlen) != 0){
- b->rp++;
- continue;
- }
- addspace = 1;
- sol = 0;
- }
- }
-
- /* mailbox entries always terminate with two newlines */
- n = b->last == '\n' ? 1 : 2;
- if(write(out, "\n\n", n) != n)
- goto error;
- n += b->bytes;
- free(b);
- return n;
-error:
- free(b);
- return -1;
-}
-
-int
-appendfiletofile(int in, int out)
-{
- int n;
- Inbuf *b;
-
- seek(out, 0, 2);
-
- b = allocinbuf(in, out);
- for(;;){
- n = fill(b, 0);
- if(n < 0)
- goto error;
- if(n == 0)
- break;
- b->rp = b->wp;
- }
- n = b->bytes;
- free(b);
- return n;
-error:
- free(b);
- return -1;
-}
--- a/sys/src/cmd/upas/common/aux.c
+++ b/sys/src/cmd/upas/common/aux.c
@@ -1,42 +1,5 @@
#include "common.h"
-/* expand a path relative to some `.' */
-extern String *
-abspath(char *path, char *dot, String *to)
-{
- if (*path == '/') {
- to = s_append(to, path);
- } else {
- to = s_append(to, dot);
- to = s_append(to, "/");
- to = s_append(to, path);
- }
- return to;
-}
-
-/* return a pointer to the base component of a pathname */
-extern char *
-basename(char *path)
-{
- char *cp;
-
- cp = strrchr(path, '/');
- return cp==0 ? path : cp+1;
-}
-
-/* append a sub-expression match onto a String */
-extern void
-append_match(Resub *subexp, String *sp, int se)
-{
- char *cp, *ep;
-
- cp = subexp[se].sp;
- ep = subexp[se].ep;
- for (; cp < ep; cp++)
- s_putc(sp, *cp);
- s_terminate(sp);
-}
-
/*
* check for shell characters in a String
*/
@@ -95,7 +58,7 @@
return ns;
}
-int
+uint
hex2uint(char x)
{
if(x >= '0' && x <= '9')
@@ -113,10 +76,9 @@
extern String*
unescapespecial(String *s)
{
- int c;
- String *ns;
char *sp;
- uint n;
+ uint c, n;
+ String *ns;
if(strstr(s_to_c(s), escape) == 0)
return s;
@@ -126,7 +88,7 @@
for(sp = s_to_c(s); *sp; sp++){
if(strncmp(sp, escape, n) == 0){
c = (hex2uint(sp[n])<<4) | hex2uint(sp[n+1]);
- if(c < 0)
+ if(c & 0x80)
s_putc(ns, *sp);
else {
s_putc(ns, c);
@@ -144,6 +106,5 @@
int
returnable(char *path)
{
-
return strcmp(path, "/dev/null") != 0;
}
--- a/sys/src/cmd/upas/common/become.c
+++ b/sys/src/cmd/upas/common/become.c
@@ -6,11 +6,10 @@
* become powerless user
*/
int
-become(char **cmd, char *who)
+become(char **, char *who)
{
int fd;
- USED(cmd);
if(strcmp(who, "none") == 0) {
fd = open("#c/user", OWRITE);
if(fd < 0 || write(fd, "none", strlen("none")) < 0) {
--- a/sys/src/cmd/upas/common/common.h
+++ b/sys/src/cmd/upas/common/common.h
@@ -1,59 +1,61 @@
+enum
+{
+ Elemlen = 56,
+ Pathlen = 256,
+};
+
#include "sys.h"
+#include <String.h>
-/* format of REMOTE FROM lines */
-extern char *REMFROMRE;
-extern int REMSENDERMATCH;
-extern int REMDATEMATCH;
-extern int REMSYSMATCH;
+enum{
+ Fields = 18,
-/* format of mailbox FROM lines */
-#define IS_HEADER(p) ((p)[0]=='F'&&(p)[1]=='r'&&(p)[2]=='o'&&(p)[3]=='m'&&(p)[4]==' ')
-#define IS_TRAILER(p) ((p)[0]=='m'&&(p)[1]=='o'&&(p)[2]=='r'&&(p)[3]=='F'&&(p)[4]=='\n')
-extern char *FROMRE;
-extern int SENDERMATCH;
-extern int DATEMATCH;
-
-enum
-{
- Elemlen= 28,
- Errlen= ERRMAX,
- Pathlen= 256,
+ /* flags */
+ Fanswered = 1<<0, /* a */
+ Fdeleted = 1<<1, /* D */
+ Fdraft = 1<<2, /* d */
+ Fflagged = 1<<3, /* f */
+ Frecent = 1<<4, /* r we are the first fs to see this */
+ Fseen = 1<<5, /* s */
+ Fstored = 1<<6, /* S */
+ Nflags = 7,
};
-enum { Atnoteunknown, Atnoterecog };
/*
- * routines in mail.c
+ * flag.c
*/
-extern int print_header(Biobuf*, char*, char*);
-extern int print_remote_header(Biobuf*, char*, char*, char*);
-extern int parse_header(char*, String*, String*);
+char *flagbuf(char*, int);
+int buftoflags(char*);
+char *txflags(char*, uchar*);
/*
* routines in aux.c
*/
-extern String *abspath(char*, char*, String*);
-extern String *mboxpath(char*, char*, String*, int);
-extern char *basename(char*);
-extern int delivery_status(String*);
-extern void append_match(Resub*, String*, int);
-extern int shellchars(char*);
-extern String* escapespecial(String*);
-extern String* unescapespecial(String*);
-extern int returnable(char*);
+char *mboxpathbuf(char*, int, char*, char*);
+char *basename(char*);
+int shellchars(char*);
+String *escapespecial(String*);
+String *unescapespecial(String*);
+int returnable(char*);
-/* in copymessage */
-extern int appendfiletombox(int, int);
-extern int appendfiletofile(int, int);
+/* folder.c */
+Biobuf *openfolder(char*, long);
+int closefolder(Biobuf*);
+int appendfolder(Biobuf*, char*, long*, int);
+int fappendfolder(char*, long, char *, int);
+int fappendfile(char*, char*, int);
+char* foldername(char*, char*, char*);
+char* ffoldername(char*, char*, char*);
-/* mailbox types */
-#define MF_NORMAL 0
-#define MF_PIPE 1
-#define MF_FORWARD 2
-#define MF_NOMBOX 3
-#define MF_NOTMBOX 4
+/* fmt.c */
+void mailfmtinstall(void); /* 'U' = 2047fmt */
+#pragma varargck type "U" char*
+/* totm.c */
+int fromtotm(char*, Tm*);
+
/* a pipe between parent and child*/
-typedef struct {
+typedef struct{
Biobuf bb;
Biobuf *fp; /* parent process end*/
int fd; /* child process end*/
@@ -60,7 +62,7 @@
} stream;
/* a child process*/
-typedef struct process{
+typedef struct{
stream *std[3]; /* standard fd's*/
int pid; /* process identifier*/
int status; /* exit status*/
@@ -67,14 +69,11 @@
Waitmsg *waitmsg;
} process;
-extern stream *instream(void);
-extern stream *outstream(void);
-extern void stream_free(stream*);
-extern process *noshell_proc_start(char**, stream*, stream*, stream*, int, char*);
-extern process *proc_start(char*, stream*, stream*, stream*, int, char*);
-extern int proc_wait(process*);
-extern int proc_free(process*);
-extern int proc_kill(process*);
-
-/* tell compiler we're using a value so it won't complain */
-#define USE(x) if(x)
+stream *instream(void);
+stream *outstream(void);
+void stream_free(stream*);
+process *noshell_proc_start(char**, stream*, stream*, stream*, int, char*);
+process *proc_start(char*, stream*, stream*, stream*, int, char*);
+int proc_wait(process*);
+int proc_free(process*);
+//int proc_kill(process*);
--- a/sys/src/cmd/upas/common/config.c
+++ b/sys/src/cmd/upas/common/config.c
@@ -1,11 +1,9 @@
#include "common.h"
-char *MAILROOT = "/mail";
-char *UPASLOG = "/sys/log";
-char *UPASLIB = "/mail/lib";
-char *UPASBIN= "/bin/upas";
-char *UPASTMP = "/mail/tmp";
-char *SHELL = "/bin/rc";
-char *POST = "/sys/lib/post/dispatch";
-
-int MBOXMODE = 0662;
+char *MAILROOT = "/mail";
+char *SPOOL = "/mail";
+char *UPASLOG = "/sys/log";
+char *UPASLIB = "/mail/lib";
+char *UPASBIN = "/bin/upas";
+char *UPASTMP = "/mail/tmp";
+char *SHELL = "/bin/rc";
--- /dev/null
+++ b/sys/src/cmd/upas/common/flags.c
@@ -1,0 +1,73 @@
+#include "common.h"
+
+static uchar flagtab[] = {
+ 'a', Fanswered,
+ 'D', Fdeleted,
+ 'd', Fdraft,
+ 'f', Fflagged,
+ 'r', Frecent,
+ 's', Fseen,
+ 'S', Fstored,
+};
+
+char*
+flagbuf(char *buf, int flags)
+{
+ char *p, c;
+ int i;
+
+ p = buf;
+ for(i = 0; i < nelem(flagtab); i += 2){
+ c = '-';
+ if(flags & flagtab[i+1])
+ c = flagtab[i];
+ *p++ = c;
+ }
+ *p = 0;
+ return buf;
+}
+
+int
+buftoflags(char *p)
+{
+ uchar flags;
+ int i;
+
+ flags = 0;
+ for(i = 0; i < nelem(flagtab); i += 2)
+ if(p[i>>1] == flagtab[i])
+ flags |= flagtab[i + 1];
+ return flags;
+}
+
+char*
+txflags(char *p, uchar *flags)
+{
+ uchar neg, f, c, i;
+
+ for(;;){
+ neg = 0;
+ again:
+ if((c = *p++) == '-'){
+ neg = 1;
+ goto again;
+ }else if(c == '+'){
+ neg = 0;
+ goto again;
+ }
+ if(c == 0)
+ return nil;
+ for(i = 0;; i += 2){
+ if(i == nelem(flagtab))
+ return "bad flag";
+ if(c == flagtab[i]){
+ f = flagtab[i+1];
+ break;
+ }
+ }
+ if(neg)
+ *flags &= ~f;
+ else
+ *flags |= f;
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/upas/common/fmt.c
@@ -1,0 +1,35 @@
+#include "common.h"
+
+int
+rfc2047fmt(Fmt *fmt)
+{
+ char *s, *p;
+
+ s = va_arg(fmt->args, char*);
+ if(s == nil)
+ return fmtstrcpy(fmt, "");
+ for(p=s; *p; p++)
+ if((uchar)*p >= 0x80)
+ goto hard;
+ return fmtstrcpy(fmt, s);
+
+hard:
+ fmtprint(fmt, "=?utf-8?q?");
+ for(p = s; *p; p++){
+ if(*p == ' ')
+ fmtrune(fmt, '_');
+ else if(*p == '_' || *p == '\t' || *p == '=' || *p == '?' ||
+ (uchar)*p >= 0x80)
+ fmtprint(fmt, "=%.2uX", (uchar)*p);
+ else
+ fmtrune(fmt, (uchar)*p);
+ }
+ fmtprint(fmt, "?=");
+ return 0;
+}
+
+void
+mailfmtinstall(void)
+{
+ fmtinstall('U', rfc2047fmt);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/common/folder.c
@@ -1,0 +1,361 @@
+#include "common.h"
+
+enum{
+ Mbox = 1,
+ Mdir,
+};
+
+typedef struct Folder Folder;
+struct Folder{
+ int open;
+ int ofd;
+ int type;
+ Biobuf *out;
+ Mlock *l;
+};
+static Folder ftab[5];
+
+static Folder*
+getfolder(Biobuf *out)
+{
+ int i;
+ Folder *f;
+
+ for(i = 0; i < nelem(ftab); i++){
+ f = ftab+i;
+ if(f->open == 0){
+ f->open = 1;
+ f->ofd = -1;
+ f->type = 0;
+ return f;
+ }
+ if(f->out == out)
+ return f;
+ }
+ sysfatal("folder.c:ftab too small");
+ return 0;
+}
+
+static int
+putfolder(Folder *f)
+{
+ int r;
+
+ r = 0;
+ if(f->l)
+ sysunlock(f->l);
+ if(f->out)
+ r |= Bterm(f->out);
+ if(f->ofd > 0){
+ close(f->ofd);
+ free(f->out);
+ }
+ memset(f, 0, sizeof *f);
+ return r;
+}
+
+static Biobuf*
+mboxopen(char *s)
+{
+ Folder *f;
+
+ f = getfolder(0);
+ f->l = syslock(s); /* traditional botch: ignore failure */
+ if((f->ofd = open(s, OWRITE)) == -1)
+ if((f->ofd = create(s, OWRITE|OEXCL, DMAPPEND|0600)) == -1){
+ putfolder(f);
+ return nil;
+ }
+ seek(f->ofd, 0, 2);
+ f->out = malloc(sizeof *f->out);
+ Binit(f->out, f->ofd, OWRITE);
+ f->type = Mbox;
+ return f->out;
+}
+
+/*
+ * sync with send/cat_mail.c:/^mdir
+ */
+static Biobuf*
+mdiropen(char *s, long t)
+{
+ char buf[64];
+ long i;
+ Folder *f;
+
+ f = getfolder(0);
+ for(i = 0; i < 100; i++){
+ snprint(buf, sizeof buf, "%s/%lud.%.2ld", s, t, i);
+ if((f->ofd = create(buf, OWRITE|OEXCL, DMAPPEND|0660)) != -1)
+ goto found;
+ }
+ putfolder(f);
+ return nil;
+found:
+ werrstr("");
+ f->out = malloc(sizeof *f->out);
+ Binit(f->out, f->ofd, OWRITE);
+ f->type = Mdir;
+ return f->out;
+}
+
+Biobuf*
+openfolder(char *s, long t)
+{
+ int isdir;
+ Dir *d;
+
+ if(d = dirstat(s)){
+ isdir = d->mode&DMDIR;
+ free(d);
+ }else{
+ isdir = create(s, OREAD, DMDIR|0777);
+ if(isdir == -1)
+ return nil;
+ close(isdir);
+ isdir = 1;
+ }
+ if(isdir)
+ return mdiropen(s, t);
+ else
+ return mboxopen(s);
+}
+
+int
+renamefolder(Biobuf *b, long t)
+{
+ char buf[32];
+ int i;
+ Dir d;
+ Folder *f;
+
+ f = getfolder(b);
+ if(f->type != Mdir)
+ return 0;
+ for(i = 0; i < 100; i++){
+ nulldir(&d);
+ snprint(buf, sizeof buf, "%lud.%.2d", t, i);
+ d.name = buf;
+ if(dirfwstat(Bfildes(b), &d) > 0)
+ return 0;
+ }
+ return -1;
+}
+
+int
+closefolder(Biobuf *b)
+{
+ return putfolder(getfolder(b));
+}
+
+/*
+ * escape "From " at the beginning of a line;
+ * translate \r\n to \n for imap
+ */
+static int
+mboxesc(Biobuf *in, Biobuf *out, int type)
+{
+ char *s;
+ int n;
+
+ for(; s = Brdstr(in, '\n', 0); free(s)){
+ if(!strncmp(s, "From ", 5))
+ Bputc(out, ' ');
+ n = strlen(s);
+ if(n > 1 && s[n-2] == '\r'){
+ s[n-2] = '\n';
+ n--;
+ }
+ if(Bwrite(out, s, n) == Beof){
+ free(s);
+ return -1;
+ }
+ if(s[n-1] != '\n')
+ Bputc(out, '\n');
+ }
+ if(type == Mbox)
+ Bputc(out, '\n');
+ if(Bflush(out) == Beof)
+ return -1;
+ return 0;
+}
+
+int
+appendfolder(Biobuf *b, char *addr, long *t, int fd)
+{
+ char *s;
+ int r;
+ Biobuf bin;
+ Folder *f;
+ Tm tm;
+
+ f = getfolder(b);
+ Bseek(f->out, 0, 2);
+ Binit(&bin, fd, OREAD);
+ s = Brdstr(&bin, '\n', 0);
+ if(!s || strncmp(s, "From ", 5))
+ Bprint(f->out, "From %s %.28s\n", addr, ctime(*t));
+ else if(fromtotm(s, &tm) >= 0)
+ *t = tm2sec(&tm);
+ if(s)
+ Bwrite(f->out, s, strlen(s));
+ free(s);
+ r = mboxesc(&bin, f->out, f->type);
+ return r | Bterm(&bin);
+}
+
+int
+fappendfolder(char *addr, long t, char *s, int fd)
+{
+ long t0, r;
+ Biobuf *b;
+
+ b = openfolder(s, t);
+ if(b == nil)
+ return -1;
+ t0 = t;
+ r = appendfolder(b, addr, &t, fd);
+ if(t != t0)
+ renamefolder(b, t);
+ r |= closefolder(b);
+ return r;
+}
+
+/*
+ * BOTCH sync with ../imap4d/mbox.c:/^okmbox
+ */
+
+static char *specialfile[] =
+{
+ "L.mbox",
+ "forward",
+ "headers",
+ "imap.subscribed",
+ "names",
+ "pipefrom",
+ "pipeto",
+};
+
+static int
+special(char *s)
+{
+ char *p;
+ int i;
+
+ p = strrchr(s, '/');
+ if(p == nil)
+ p = s;
+ else
+ p++;
+ for(i = 0; i < nelem(specialfile); i++)
+ if(strcmp(p, specialfile[i]) == 0)
+ return 1;
+ return 0;
+}
+
+static char*
+mkmbpath(char *s, int n, char *user, char *mb, char *path)
+{
+ char *p, *e, *r, buf[Pathlen];
+
+ if(!mb)
+ return mboxpathbuf(s, n, user, path);
+ e = buf+sizeof buf;
+ p = seprint(buf, e, "%s", mb);
+ if(r = strrchr(buf, '/'))
+ p = r;
+ seprint(p, e, "/%s", path);
+ return mboxpathbuf(s, n, user, buf);
+}
+
+
+/*
+ * fancy processing for ned:
+ * we default to storing in $mail/f then just in $mail.
+ */
+char*
+ffoldername(char *mb, char *user, char *rcvr)
+{
+ char *p;
+ int c, n;
+ Dir *d;
+ static char buf[Pathlen];
+
+ d = dirstat(mkmbpath(buf, sizeof buf, user, mb, "f/"));
+ n = strlen(buf);
+ if(!d || d->qid.type != QTDIR)
+ buf[n -= 2] = 0;
+ free(d);
+
+ if(p = strrchr(rcvr, '!'))
+ rcvr = p+1;
+ while(n < sizeof buf-1 && (c = *rcvr++)){
+ if(c== '@')
+ break;
+ if(c == '/')
+ c = '_';
+ buf[n++] = c;
+ }
+ buf[n] = 0;
+
+ if(special(buf)){
+ fprint(2, "!won't overwrite %s\n", buf);
+ return nil;
+ }
+ return buf;
+}
+
+char*
+foldername(char *mb, char *user, char *path)
+{
+ static char buf[Pathlen];
+
+ mkmbpath(buf, sizeof buf, user, mb, path);
+ if(special(buf)){
+ fprint(2, "!won't overwrite %s\n", buf);
+ return nil;
+ }
+ return buf;
+}
+
+static int
+append(Biobuf *in, Biobuf *out)
+{
+ char *buf;
+ int n, m;
+
+ buf = malloc(8192);
+ for(;;){
+ m = 0;
+ n = Bread(in, buf, 8192);
+ if(n <= 0)
+ break;
+ m = Bwrite(out, buf, n);
+ if(m != n)
+ break;
+ }
+ if(m != n)
+ n = -1;
+ else
+ n = 1;
+ free(buf);
+ return n;
+}
+
+/* symmetry for nedmail; misnamed */
+int
+fappendfile(char*, char *target, int in)
+{
+ int fd, r;
+ Biobuf bin, out;
+
+ if((fd = create(target, ORDWR|OEXCL, 0666)) == -1)
+ return -1;
+ Binit(&out, fd, OWRITE);
+ Binit(&bin, in, OREAD);
+ r = append(&bin, &out);
+ Bterm(&bin);
+ Bterm(&out);
+ close(fd);
+ return r;
+}
--- a/sys/src/cmd/upas/common/libsys.c
+++ b/sys/src/cmd/upas/common/libsys.c
@@ -3,25 +3,15 @@
#include <ndb.h>
/*
- * number of predefined fd's
- */
-int nsysfile=3;
-
-static char err[Errlen];
-
-/*
* return the date
*/
-extern char *
+char*
thedate(void)
{
static char now[64];
- char *cp;
strcpy(now, ctime(time(0)));
- cp = strchr(now, '\n');
- if(cp)
- *cp = 0;
+ now[28] = 0;
return now;
}
@@ -28,42 +18,27 @@
/*
* return the user id of the current user
*/
-extern char *
+char *
getlog(void)
{
- static char user[64];
- int fd;
- int n;
-
- fd = open("/dev/user", 0);
- if(fd < 0)
- return nil;
- if((n=read(fd, user, sizeof(user)-1)) <= 0)
- return nil;
- close(fd);
- user[n] = 0;
- return user;
+ return getuser();
}
/*
* return the lock name (we use one lock per directory)
*/
-static String *
-lockname(char *path)
+static void
+lockname(Mlock *l, char *path)
{
- String *lp;
- char *cp;
+ char *e, *q;
- /*
- * get the name of the lock file
- */
- lp = s_new();
- cp = strrchr(path, '/');
- if(cp)
- s_nappend(lp, path, cp - path + 1);
- s_append(lp, "L.mbox");
-
- return lp;
+ seprint(l->name, e = l->name+sizeof l->name, "%s", path);
+ q = strrchr(l->name, '/');
+ if(q == nil)
+ q = l->name;
+ else
+ q++;
+ seprint(q, e, "%s", "L.mbox");
}
int
@@ -92,58 +67,43 @@
openlockfile(Mlock *l)
{
int fd;
- Dir *d;
- Dir nd;
+ Dir *d, nd;
char *p;
- fd = open(s_to_c(l->name), OREAD);
- if(fd >= 0){
- l->fd = fd;
+ l->fd = open(l->name, OREAD);
+ if(l->fd >= 0)
return 0;
+ if(d = dirstat(l->name)){
+ free(d);
+ return 1; /* try again later */
}
-
- d = dirstat(s_to_c(l->name));
- if(d == nil){
- /* file doesn't exist */
- /* try creating it */
- fd = create(s_to_c(l->name), OREAD, DMEXCL|0666);
- if(fd >= 0){
- nulldir(&nd);
- nd.mode = DMEXCL|0666;
- if(dirfwstat(fd, &nd) < 0){
- /* if we can't chmod, don't bother */
- /* live without the lock but log it */
- syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
- remove(s_to_c(l->name));
- }
- l->fd = fd;
- return 0;
+ l->fd = create(l->name, OREAD, DMEXCL|0666);
+ if(l->fd >= 0){
+ nulldir(&nd);
+ nd.mode = DMEXCL|0666;
+ if(dirfwstat(l->fd, &nd) < 0){
+ /* if we can't chmod, don't bother */
+ /* live without the lock but log it */
+ close(l->fd);
+ l->fd = -1;
+ syslog(0, "mail", "lock error: %s: %r", l->name);
+ remove(l->name);
}
-
- /* couldn't create */
- /* do we have write access to the directory? */
- p = strrchr(s_to_c(l->name), '/');
- if(p != 0){
- *p = 0;
- fd = access(s_to_c(l->name), 2);
- *p = '/';
- if(fd < 0){
- /* live without the lock but log it */
- syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
- return 0;
- }
- } else {
- fd = access(".", 2);
- if(fd < 0){
- /* live without the lock but log it */
- syslog(0, "mail", "lock error: %s: %r", s_to_c(l->name));
- return 0;
- }
- }
- } else
- free(d);
-
- return 1; /* try again later */
+ return 0;
+ }
+ /* couldn't create; let's see what we can whine about */
+ p = strrchr(l->name, '/');
+ if(p != 0){
+ *p = 0;
+ fd = access(l->name, 2);
+ *p = '/';
+ }else
+ fd = access(".", 2);
+ if(fd < 0)
+ /* live without the lock but log it */
+ syslog(0, "mail", "lock error: %s: %r", l->name);
+ close(fd);
+ return 0;
}
#define LSECS 5*60
@@ -152,7 +112,7 @@
* Set a lock for a particular file. The lock is a file in the same directory
* and has L. prepended to the name of the last element of the file name.
*/
-extern Mlock *
+Mlock*
syslock(char *path)
{
Mlock *l;
@@ -162,12 +122,11 @@
if(l == 0)
return nil;
- l->name = lockname(path);
-
+ lockname(l, path);
/*
* wait LSECS seconds for it to unlock
*/
- for(tries = 0; tries < LSECS*2; tries++){
+ for(tries = 0; tries < LSECS*2; tries++)
switch(openlockfile(l)){
case 0:
return l;
@@ -174,13 +133,7 @@
case 1:
sleep(500);
break;
- default:
- goto noway;
}
- }
-
-noway:
- s_free(l->name);
free(l);
return nil;
}
@@ -188,20 +141,19 @@
/*
* like lock except don't wait
*/
-extern Mlock *
+Mlock *
trylock(char *path)
{
- Mlock *l;
char buf[1];
int fd;
+ Mlock *l;
- l = malloc(sizeof(Mlock));
+ l = mallocz(sizeof(Mlock), 1);
if(l == 0)
return 0;
- l->name = lockname(path);
+ lockname(l, path);
if(openlockfile(l) != 0){
- s_free(l->name);
free(l);
return 0;
}
@@ -217,12 +169,12 @@
if(pread(fd, buf, 1, 0) < 0)
break;
}
- _exits(0);
+ _exits(nil);
}
return l;
}
-extern void
+void
syslockrefresh(Mlock *l)
{
char buf[1];
@@ -230,16 +182,12 @@
pread(l->fd, buf, 1, 0);
}
-extern void
+void
sysunlock(Mlock *l)
{
if(l == 0)
return;
- if(l->name){
- s_free(l->name);
- }
- if(l->fd >= 0)
- close(l->fd);
+ close(l->fd);
if(l->pid > 0)
postnote(PNPROC, l->pid, "time to die");
free(l);
@@ -254,15 +202,10 @@
* w - writable
* A - append only (doesn't exist in Bio)
*/
-extern Biobuf *
+Biobuf*
sysopen(char *path, char *mode, ulong perm)
{
- int sysperm;
- int sysmode;
- int fd;
- int docreate;
- int append;
- int truncate;
+ int sysperm, sysmode, fd, docreate, append, truncate;
Dir *d, nd;
Biobuf *bp;
@@ -274,7 +217,7 @@
docreate = 0;
append = 0;
truncate = 0;
- for(; mode && *mode; mode++)
+ for(; *mode; mode++)
switch(*mode){
case 'A':
sysmode = OWRITE;
@@ -372,15 +315,6 @@
}
/*
- * create a file
- */
-int
-syscreate(char *file, int mode, ulong perm)
-{
- return create(file, mode, perm);
-}
-
-/*
* make a directory
*/
int
@@ -395,75 +329,44 @@
}
/*
- * change the group of a file
- */
-int
-syschgrp(char *file, char *group)
-{
- Dir nd;
-
- if(group == 0)
- return -1;
- nulldir(&nd);
- nd.gid = group;
- return dirwstat(file, &nd);
-}
-
-extern int
-sysdirreadall(int fd, Dir **d)
-{
- return dirreadall(fd, d);
-}
-
-/*
* read in the system name
*/
-extern char *
+char *
sysname_read(void)
{
static char name[128];
- char *cp;
+ char *s, *c;
- cp = getenv("site");
- if(cp == 0 || *cp == 0)
- cp = alt_sysname_read();
- if(cp == 0 || *cp == 0)
- cp = "kremvax";
- strecpy(name, name+sizeof name, cp);
+ c = s = getenv("site");
+ if(!c)
+ c = alt_sysname_read();
+ if(!c)
+ c = "kremvax";
+ strecpy(name, name+sizeof name, c);
+ free(s);
return name;
}
-extern char *
+
+char *
alt_sysname_read(void)
{
- static char name[128];
- int n, fd;
-
- fd = open("/dev/sysname", OREAD);
- if(fd < 0)
- return 0;
- n = read(fd, name, sizeof(name)-1);
- close(fd);
- if(n <= 0)
- return 0;
- name[n] = 0;
- return name;
+ return sysname();
}
/*
* get all names
*/
-extern char**
+char**
sysnames_read(void)
{
- static char **namev;
- Ndbtuple *t, *nt;
int n;
- char *cp;
+ Ndbtuple *t, *nt;
+ static char **namev;
if(namev)
return namev;
- free(csgetvalue(0, "sys", alt_sysname_read(), "dom", &t));
+ free(csgetvalue(0, "sys", sysname(), "dom", &t));
n = 0;
for(nt = t; nt; nt = nt->entry)
@@ -471,13 +374,10 @@
n++;
namev = (char**)malloc(sizeof(char *)*(n+3));
-
if(namev){
- n = 0;
- namev[n++] = strdup(sysname_read());
- cp = alt_sysname_read();
- if(cp)
- namev[n++] = strdup(cp);
+ namev[0] = strdup(sysname_read());
+ namev[1] = strdup(alt_sysname_read());
+ n = 2;
for(nt = t; nt; nt = nt->entry)
if(strcmp(nt->attr, "dom") == 0)
namev[n++] = strdup(nt->val);
@@ -492,69 +392,21 @@
/*
* read in the domain name
*/
-extern char *
+char*
domainname_read(void)
{
- char **namev;
+ char **p;
- for(namev = sysnames_read(); *namev; namev++)
- if(strchr(*namev, '.'))
- return *namev;
+ for(p = sysnames_read(); *p; p++)
+ if(strchr(*p, '.'))
+ return *p;
return 0;
}
/*
- * return true if the last error message meant file
- * did not exist.
- */
-extern int
-e_nonexistent(void)
-{
- rerrstr(err, sizeof(err));
- return strcmp(err, "file does not exist") == 0;
-}
-
-/*
- * return true if the last error message meant file
- * was locked.
- */
-extern int
-e_locked(void)
-{
- rerrstr(err, sizeof(err));
- return strcmp(err, "open/create -- file is locked") == 0;
-}
-
-/*
- * return the length of a file
- */
-extern long
-sysfilelen(Biobuf *fp)
-{
- Dir *d;
- long rv;
-
- d = dirfstat(Bfildes(fp));
- if(d == nil)
- return -1;
- rv = d->length;
- free(d);
- return rv;
-}
-
-/*
- * remove a file
- */
-extern int
-sysremove(char *path)
-{
- return remove(path);
-}
-
-/*
* rename a file, fails unless both are in the same directory
*/
-extern int
+int
sysrename(char *old, char *new)
{
Dir d;
@@ -579,81 +431,27 @@
return dirwstat(old, &d);
}
-/*
- * see if a file exists
- */
-extern int
+int
sysexist(char *file)
{
- Dir *d;
-
- d = dirstat(file);
- if(d == nil)
- return 0;
- free(d);
- return 1;
+ return access(file, AEXIST) == 0;
}
-/*
- * return nonzero if file is a directory
- */
-extern int
-sysisdir(char *file)
-{
- Dir *d;
- int rv;
+static char yankeepig[] = "die: yankee pig dog";
- d = dirstat(file);
- if(d == nil)
- return 0;
- rv = d->mode & DMDIR;
- free(d);
- return rv;
-}
-
-/*
- * kill a process or process group
- */
-
-static int
-stomp(int pid, char *file)
-{
- char name[64];
- int fd;
-
- snprint(name, sizeof(name), "/proc/%d/%s", pid, file);
- fd = open(name, 1);
- if(fd < 0)
- return -1;
- if(write(fd, "die: yankee pig dog\n", sizeof("die: yankee pig dog\n") - 1) <= 0){
- close(fd);
- return -1;
- }
- close(fd);
- return 0;
-
-}
-
-/*
- * kill a process
- */
-extern int
+int
syskill(int pid)
{
- return stomp(pid, "note");
-
+ return postnote(PNPROC, pid, yankeepig);
}
-/*
- * kill a process group
- */
-extern int
+int
syskillpg(int pid)
{
- return stomp(pid, "notepg");
+ return postnote(PNGROUP, pid, yankeepig);
}
-extern int
+int
sysdetach(void)
{
if(rfork(RFENVG|RFNAMEG|RFNOTEG) < 0) {
@@ -668,11 +466,10 @@
*/
static int *closedflag;
static int
-catchpipe(void *a, char *msg)
+catchpipe(void *, char *msg)
{
static char *foo = "sys: write on closed pipe";
- USED(a);
if(strncmp(msg, foo, strlen(foo)) == 0){
if(closedflag)
*closedflag = 1;
@@ -692,30 +489,21 @@
atnotify(catchpipe, 0);
}
-void
-exit(int i)
-{
- char buf[32];
-
- if(i == 0)
- exits(0);
- snprint(buf, sizeof(buf), "%d", i);
- exits(buf);
-}
-
-static int
+int
islikeatty(int fd)
{
char buf[64];
+ int l;
if(fd2path(fd, buf, sizeof buf) != 0)
return 0;
/* might be /mnt/term/dev/cons */
- return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
+ l = strlen(buf);
+ return l >= 9 && strcmp(buf+l-9, "/dev/cons") == 0;
}
-extern int
+int
holdon(void)
{
int fd;
@@ -729,13 +517,13 @@
return fd;
}
-extern int
+int
sysopentty(void)
{
return open("/dev/cons", ORDWR);
}
-extern void
+void
holdoff(int fd)
{
write(fd, "holdoff", 7);
@@ -742,7 +530,7 @@
close(fd);
}
-extern int
+int
sysfiles(void)
{
return 128;
@@ -754,142 +542,66 @@
* if the path starts with / or ./, don't change it
*
*/
-extern String *
-mboxpath(char *path, char *user, String *to, int dot)
+char*
+mboxpathbuf(char *to, int n, char *user, char *path)
{
- if (dot || *path=='/' || strncmp(path, "./", 2) == 0
- || strncmp(path, "../", 3) == 0) {
- to = s_append(to, path);
- } else {
- to = s_append(to, MAILROOT);
- to = s_append(to, "/box/");
- to = s_append(to, user);
- to = s_append(to, "/");
- to = s_append(to, path);
- }
+ if(*path == '/' || !strncmp(path, "./", 2) || !strncmp(path, "../", 3))
+ snprint(to, n, "%s", path);
+ else
+ snprint(to, n, "%s/box/%s/%s", MAILROOT, user, path);
return to;
}
-extern String *
-mboxname(char *user, String *to)
+/*
+ * warning: we're not quoting bad characters. we're not encoding
+ * non-ascii characters. basically this function sucks. don't use.
+ */
+char*
+username0(Biobuf *b, char *from)
{
- return mboxpath("mbox", user, to, 0);
-}
-
-extern String *
-deadletter(String *to) /* pass in sender??? */
-{
- char *cp;
-
- cp = getlog();
- if(cp == 0)
- return 0;
- return mboxpath("dead.letter", cp, to, 0);
-}
-
-char *
-homedir(char *user)
-{
- USED(user);
- return getenv("home");
-}
-
-String *
-readlock(String *file)
-{
- char *cp;
-
- cp = getlog();
- if(cp == 0)
- return 0;
- return mboxpath("reading", cp, file, 0);
-}
-
-String *
-username(String *from)
-{
+ char *p, *f[6];
int n;
- Biobuf *bp;
- char *p, *q;
- String *s;
+ static char buf[32];
- bp = Bopen("/adm/keys.who", OREAD);
- if(bp == 0)
- bp = Bopen("/adm/netkeys.who", OREAD);
- if(bp == 0)
- return 0;
-
- s = 0;
- n = strlen(s_to_c(from));
- for(;;) {
- p = Brdline(bp, '\n');
+ n = strlen(from);
+ buf[0] = 0;
+ for(;; free(p)) {
+ p = Brdstr(b, '\n', 1);
if(p == 0)
break;
- p[Blinelen(bp)-1] = 0;
- if(strncmp(p, s_to_c(from), n))
+ if(strncmp(p, from, n) || p[n] != '|')
continue;
- p += n;
- if(*p != ' ' && *p != '\t') /* must be full match */
+ if(getfields(p, f, nelem(f), 0, "|") < 3)
continue;
- while(*p && (*p == ' ' || *p == '\t'))
- p++;
- if(*p == 0)
- continue;
- for(q = p; *q; q++)
- if(('0' <= *q && *q <= '9') || *q == '<')
- break;
- while(q > p && q[-1] != ' ' && q[-1] != '\t')
- q--;
- while(q > p && (q[-1] == ' ' || q[-1] == '\t'))
- q--;
- *q = 0;
- s = s_new();
- s_append(s, "\"");
- s_append(s, p);
- s_append(s, "\"");
- break;
+ snprint(buf, sizeof buf, "\"%s\"", f[2]);
+ /* no break; last match wins */
}
- Bterm(bp);
- return s;
+ return buf[0]? buf: 0;
}
-char *
-remoteaddr(int fd, char *dir)
+char*
+username(char *from)
{
- char buf[128], *p;
- int n;
+ char *s;
+ Biobuf *b;
- if(dir == 0){
- if(fd2path(fd, buf, sizeof(buf)) != 0)
- return "";
-
- /* parse something of the form /net/tcp/nnnn/data */
- p = strrchr(buf, '/');
- if(p == 0)
- return "";
- strncpy(p+1, "remote", sizeof(buf)-(p-buf)-2);
- } else
- snprint(buf, sizeof buf, "%s/remote", dir);
- buf[sizeof(buf)-1] = 0;
-
- fd = open(buf, OREAD);
- if(fd < 0)
- return "";
- n = read(fd, buf, sizeof(buf)-1);
- close(fd);
- if(n > 0){
- buf[n] = 0;
- p = strchr(buf, '!');
- if(p)
- *p = 0;
- return strdup(buf);
+ s = 0;
+ if(b = Bopen("/adm/keys.who", OREAD)){
+ s = username0(b, from);
+ Bterm(b);
}
- return "";
+ if(s == 0 && (b = Bopen("/adm/netkeys.who", OREAD))){
+ s = username0(b, from);
+ Bterm(b);
+ }
+ return s;
}
-// create a file and
-// 1) ensure the modes we asked for
-// 2) make gid == uid
+/*
+ * create a file and
+ * 1) ensure the modes we asked for
+ * 2) make gid == uid
+ */
static int
docreate(char *file, int perm)
{
@@ -897,7 +609,7 @@
Dir ndir;
Dir *d;
- // create the mbox
+ /* create the mbox */
fd = create(file, OREAD, perm);
if(fd < 0){
fprint(2, "couldn't create %s\n", file);
@@ -917,55 +629,77 @@
return 0;
}
-// create a mailbox
-int
-creatembox(char *user, char *folder)
+static int
+createfolder0(char *user, char *folder, char *ftype)
{
- char *p;
- String *mailfile;
- char buf[512];
- Mlock *ml;
+ char *p, *s, buf[Pathlen];
+ int isdir, mode;
+ Dir *d;
- mailfile = s_new();
- if(folder == 0)
- mboxname(user, mailfile);
- else {
- snprint(buf, sizeof(buf), "%s/mbox", folder);
- mboxpath(buf, user, mailfile, 0);
- }
-
- // don't destroy existing mailbox
- if(access(s_to_c(mailfile), 0) == 0){
- fprint(2, "mailbox already exists\n");
+ assert(folder != 0);
+ mboxpathbuf(buf, sizeof buf, user, folder);
+ if(access(buf, 0) == 0){
+ fprint(2, "%s already exists\n", ftype);
return -1;
}
- fprint(2, "creating new mbox: %s\n", s_to_c(mailfile));
+ fprint(2, "creating new %s: %s\n", ftype, buf);
- // make sure preceding levels exist
- for(p = s_to_c(mailfile); p; p++) {
- if(*p == '/') /* skip leading or consecutive slashes */
+ /*
+ * if we can deliver to this mbox, it needs
+ * to be read/execable all the way down
+ */
+ mode = 0711;
+ if(!strncmp(buf, "/mail/box/", 10))
+ if((s = strrchr(buf, '/')) && !strcmp(s+1, "mbox"))
+ mode = 0755;
+ for(p = buf; p; p++) {
+ if(*p == '/')
continue;
p = strchr(p, '/');
if(p == 0)
break;
*p = 0;
- if(access(s_to_c(mailfile), 0) != 0){
- if(docreate(s_to_c(mailfile), DMDIR|0711) < 0)
- return -1;
- }
+ if(access(buf, 0) != 0)
+ if(docreate(buf, DMDIR|mode) < 0)
+ return -1;
*p = '/';
}
+ /* must match folder.c:/^openfolder */
+ isdir = create(buf, OREAD, DMDIR|0777);
- // create the mbox
- if(docreate(s_to_c(mailfile), 0622|DMAPPEND|DMEXCL) < 0)
- return -1;
-
/*
- * create the lock file if it doesn't exist
+ * make sure everyone can write here if it's a mailbox
+ * rather than a folder
*/
- ml = trylock(s_to_c(mailfile));
- if(ml != nil)
- sysunlock(ml);
+ if(mode == 0755)
+ if(isdir >= 0 && (d = dirfstat(isdir))){
+ d->mode |= 0773;
+ dirfwstat(isdir, d);
+ free(d);
+ }
+ if(isdir == -1){
+ fprint(2, "can't create %s: %s\n", ftype, buf);
+ return -1;
+ }
+ close(isdir);
return 0;
+}
+
+int
+createfolder(char *user, char *folder)
+{
+ return createfolder0(user, folder, "folder");
+}
+
+int
+creatembox(char *user, char *mbox)
+{
+ char buf[Pathlen];
+
+ if(mbox == 0)
+ snprint(buf, sizeof buf, "mbox");
+ else
+ snprint(buf, sizeof buf, "%s/mbox", mbox);
+ return createfolder0(user, buf, "mbox");
}
--- a/sys/src/cmd/upas/common/mail.c
+++ /dev/null
@@ -1,57 +1,0 @@
-#include "common.h"
-
-/* format of REMOTE FROM lines */
-char *REMFROMRE =
- "^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)[ \t]+remote[ \t]+from[ \t]+(.*)\n$";
-int REMSENDERMATCH = 1;
-int REMDATEMATCH = 4;
-int REMSYSMATCH = 5;
-
-/* format of LOCAL FROM lines */
-char *FROMRE =
- "^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)\n$";
-int SENDERMATCH = 1;
-int DATEMATCH = 4;
-
-/* output a unix style local header */
-int
-print_header(Biobuf *fp, char *sender, char *date)
-{
- return Bprint(fp, "From %s %s\n", sender, date);
-}
-
-/* output a unix style remote header */
-int
-print_remote_header(Biobuf *fp, char *sender, char *date, char *system)
-{
- return Bprint(fp, "From %s %s remote from %s\n", sender, date, system);
-}
-
-/* parse a mailbox style header */
-int
-parse_header(char *line, String *sender, String *date)
-{
- if (!IS_HEADER(line))
- return -1;
- line += sizeof("From ") - 1;
- s_restart(sender);
- while(*line==' '||*line=='\t')
- line++;
- if(*line == '"'){
- s_putc(sender, *line++);
- while(*line && *line != '"')
- s_putc(sender, *line++);
- s_putc(sender, *line++);
- } else {
- while(*line && *line != ' ' && *line != '\t')
- s_putc(sender, *line++);
- }
- s_terminate(sender);
- s_restart(date);
- while(*line==' '||*line=='\t')
- line++;
- while(*line)
- s_putc(date, *line++);
- s_terminate(date);
- return 0;
-}
--- a/sys/src/cmd/upas/common/makefile
+++ /dev/null
@@ -1,18 +1,0 @@
-CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
-OBJS=mail.o aux.o string.o ${SYSOBJ}
-AR=ar
-.c.o: ; ${CC} -c ${CFLAGS} $*.c
-
-common.a: ${OBJS}
- ${AR} cr common.a ${OBJS}
- -ranlib common.a
-
-aux.o: aux.h string.h mail.h
-string.o: string.h mail.h
-mail.o: mail.h
-syslog.o: sys.h
-mail.h: sys.h
-
-clean:
- -rm -f *.[oO] core a.out *.a *.sL common.a
-
--- a/sys/src/cmd/upas/common/mkfile
+++ b/sys/src/cmd/upas/common/mkfile
@@ -1,13 +1,17 @@
</$objtype/mkfile
+<../mkupas
LIB=libcommon.a$O
-OFILES=aux.$O\
+OFILES=\
+ aux.$O\
become.$O\
- mail.$O\
- process.$O\
- libsys.$O\
config.$O\
- appendfiletombox.$O\
+ folder.$O\
+ flags.$O\
+ fmt.$O\
+ libsys.$O\
+ process.$O\
+ totm.$O\
HFILES=common.h\
sys.h\
@@ -18,3 +22,7 @@
$HFILES\
</sys/src/cmd/mklib
+
+nuke:V:
+ mk clean
+ rm -f libcommon.a[$OS]
--- a/sys/src/cmd/upas/common/process.c
+++ b/sys/src/cmd/upas/common/process.c
@@ -96,8 +96,7 @@
if(who)
become(av, who);
exec(av[0], av);
- perror("proc_start");
- exits("proc_start");
+ sysfatal("proc_start");
default:
for (i=0; i<3; i++)
if (pp->std[i] != 0) {
@@ -126,12 +125,12 @@
proc_wait(process *pp)
{
Waitmsg *status;
- char err[Errlen];
+ char err[ERRMAX];
for(;;){
status = wait();
if(status == nil){
- rerrstr(err, sizeof(err));
+ errstr(err, sizeof(err));
if(strstr(err, "interrupt") == 0)
break;
}
@@ -161,13 +160,13 @@
if (pp->pid >= 0)
proc_wait(pp);
free(pp->waitmsg);
- free((char *)pp);
+ free(pp);
return 0;
}
/* kill a process */
-extern int
-proc_kill(process *pp)
-{
- return syskill(pp->pid);
-}
+//extern int
+//proc_kill(process *pp)
+//{
+// return syskill(pp->pid);
+//}
--- a/sys/src/cmd/upas/common/sys.h
+++ b/sys/src/cmd/upas/common/sys.h
@@ -1,12 +1,6 @@
-/*
- * System dependent header files for research
- */
-
#include <u.h>
#include <libc.h>
-#include <regexp.h>
#include <bio.h>
-#include "String.h"
/*
* for the lock routines in libsys.c
@@ -13,9 +7,9 @@
*/
typedef struct Mlock Mlock;
struct Mlock {
- int fd;
- int pid;
- String *name;
+ int fd;
+ int pid;
+ char name[Pathlen];
};
/*
@@ -22,64 +16,48 @@
* from config.c
*/
extern char *MAILROOT; /* root of mail system */
+extern char *SPOOL; /* spool directory; for spam ctl */
extern char *UPASLOG; /* log directory */
extern char *UPASLIB; /* upas library directory */
extern char *UPASBIN; /* upas binary directory */
extern char *UPASTMP; /* temporary directory */
extern char *SHELL; /* path name of shell */
-extern char *POST; /* path name of post server addresses */
-extern int MBOXMODE; /* default mailbox protection mode */
+enum {
+ Mboxmode = 0622,
+};
+
/*
* files in libsys.c
*/
-extern char *sysname_read(void);
-extern char *alt_sysname_read(void);
-extern char *domainname_read(void);
-extern char **sysnames_read(void);
-extern char *getlog(void);
-extern char *thedate(void);
-extern Biobuf *sysopen(char*, char*, ulong);
-extern int sysopentty(void);
-extern int sysclose(Biobuf*);
-extern int sysmkdir(char*, ulong);
-extern int syschgrp(char*, char*);
-extern Mlock *syslock(char *);
-extern void sysunlock(Mlock *);
-extern void syslockrefresh(Mlock *);
-extern int e_nonexistent(void);
-extern int e_locked(void);
-extern long sysfilelen(Biobuf*);
-extern int sysremove(char*);
-extern int sysrename(char*, char*);
-extern int sysexist(char*);
-extern int sysisdir(char*);
-extern int syskill(int);
-extern int syskillpg(int);
-extern int syscreate(char*, int, ulong);
-extern Mlock *trylock(char *);
-extern void exit(int);
-extern void pipesig(int*);
-extern void pipesigoff(void);
-extern int holdon(void);
-extern void holdoff(int);
-extern int syscreatelocked(char*, int, int);
-extern int sysopenlocked(char*, int);
-extern int sysunlockfile(int);
-extern int sysfiles(void);
-extern int become(char**, char*);
-extern int sysdetach(void);
-extern int sysdirreadall(int, Dir**);
-extern String *username(String*);
-extern char* remoteaddr(int, char*);
-extern int creatembox(char*, char*);
-
-extern String *readlock(String*);
-extern char *homedir(char*);
-extern String *mboxname(char*, String*);
-extern String *deadletter(String*);
-
-/*
- * maximum size for a file path
- */
-#define MAXPATHLEN 128
+char *sysname_read(void);
+char *alt_sysname_read(void);
+char *domainname_read(void);
+char **sysnames_read(void);
+char *getlog(void);
+char *thedate(void);
+Biobuf *sysopen(char*, char*, ulong);
+int sysopentty(void);
+int sysclose(Biobuf*);
+int sysmkdir(char*, ulong);
+Mlock *syslock(char *);
+void sysunlock(Mlock *);
+void syslockrefresh(Mlock *);
+int sysrename(char*, char*);
+int sysexist(char*);
+int syskill(int);
+int syskillpg(int);
+Mlock *trylock(char *);
+void pipesig(int*);
+void pipesigoff(void);
+int holdon(void);
+void holdoff(int);
+int syscreatelocked(char*, int, int);
+int sysopenlocked(char*, int);
+int sysunlockfile(int);
+int sysfiles(void);
+int become(char**, char*);
+int sysdetach(void);
+char *username(char*);
+int creatembox(char*, char*);
+int createfolder(char*, char*);
--- /dev/null
+++ b/sys/src/cmd/upas/common/totm.c
@@ -1,0 +1,36 @@
+#include <common.h>
+
+static char mtab[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
+
+int
+ctimetotm(char *s, Tm *tm)
+{
+ char buf[32];
+
+ if(strlen(s) < 28)
+ return -1;
+ snprint(buf, sizeof buf, "%s", s);
+ memset(tm, 0, sizeof *tm);
+ buf[7] = 0;
+ tm->mon = (strstr(mtab, buf+4) - mtab)/3;
+ tm->mday = atoi(buf+8);
+ tm->hour = atoi(buf+11);
+ tm->min = atoi(buf+14);
+ tm->sec = atoi(buf+17);
+ tm->zone[0] = buf[20];
+ tm->zone[1] = buf[21];
+ tm->zone[2] = buf[22];
+ tm->year = atoi(buf+24) - 1900;
+ return 0;
+}
+
+int
+fromtotm(char *s, Tm *tm)
+{
+ char buf[256], *f[3];
+
+ snprint(buf, sizeof buf, "%s", s);
+ if(getfields(buf, f, nelem(f), 0, " ") != 3)
+ return -1;
+ return ctimetotm(f[2], tm);
+}
--- a/sys/src/cmd/upas/filterkit/deliver.c
+++ b/sys/src/cmd/upas/filterkit/deliver.c
@@ -7,7 +7,7 @@
void
usage(void)
{
- fprint(2, "usage: %s recipient fromfile mbox\n", argv0);
+ fprint(2, "usage: deliver recipient fromaddr-file mbox\n");
exits("usage");
}
@@ -14,57 +14,25 @@
void
main(int argc, char **argv)
{
- int bytes, fd, i;
- char now[30];
- char *deliveredto;
+ char *to, *s;
+ int r;
+ long l;
Addr *a;
- Mlock *l;
ARGBEGIN{
}ARGEND;
-
if(argc != 3)
usage();
-
- deliveredto = strrchr(argv[0], '!');
- if(deliveredto == nil)
- deliveredto = argv[0];
+ if(to = strrchr(argv[0], '!'))
+ to++;
else
- deliveredto++;
+ to = argv[0];
a = readaddrs(argv[1], nil);
if(a == nil)
sysfatal("missing from address");
-
- l = syslock(argv[2]);
-
- /* append to mbox */
- i = 0;
-retry:
- fd = open(argv[2], OWRITE);
- if(fd < 0){
- rerrstr(now, sizeof(now));
- if(strstr(now, "exclusive lock") && i++ < 20){
- sleep(500); /* wait for lock to go away */
- goto retry;
- }
- sysfatal("opening mailbox: %r");
- }
- seek(fd, 0, 2);
- strncpy(now, ctime(time(0)), sizeof(now));
- now[28] = 0;
- if(fprint(fd, "From %s %s\n", a->val, now) < 0)
- sysfatal("writing mailbox: %r");
-
- /* copy message handles escapes and any needed new lines */
- bytes = appendfiletombox(0, fd);
- if(bytes < 0)
- sysfatal("writing mailbox: %r");
-
- close(fd);
- sysunlock(l);
-
- /* log it */
- syslog(0, "mail", "delivered %s From %s %s (%s) %d", deliveredto,
- a->val, now, argv[0], bytes);
- exits(0);
+ s = ctime(l = time(0));
+ werrstr("");
+ r = fappendfolder(a->val, l, argv[2], 0);
+ syslog(0, "mail", "delivered %s From %s %.28s (%s) %d %r", to, a->val, s, argv[0], r);
+ exits("");
}
--- a/sys/src/cmd/upas/filterkit/list.c
+++ b/sys/src/cmd/upas/filterkit/list.c
@@ -2,8 +2,8 @@
#include <libc.h>
#include <regexp.h>
#include <libsec.h>
-#include <String.h>
#include <bio.h>
+#include <String.h>
#include "dat.h"
int debug;
--- /dev/null
+++ b/sys/src/cmd/upas/filterkit/mbappend.c
@@ -1,0 +1,63 @@
+/*
+ * deliver to one's own folder with locking & logging
+ */
+#include "dat.h"
+#include "common.h"
+
+void
+append(int fd, char *mb, char *from, long t)
+{
+ char *folder, *s;
+ int r;
+
+ s = ctime(t);
+ folder = foldername(from, getuser(), mb);
+ r = fappendfolder(0, t, folder, fd);
+ if(r == 0)
+ werrstr("");
+ syslog(0, "mail", "mbappend %s %.28s (%s) %r", mb, s, folder);
+ if(r)
+ exits("fail");
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: mbappend [-t time] [-f from] mbox [file ...]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *mb, *from;
+ int fd;
+ long t;
+
+ from = nil;
+ t = time(0);
+ ARGBEGIN{
+ case 't':
+ t = strtoul(EARGF(usage()), 0, 0);
+ break;
+ case 'f':
+ from = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+ if(*argv == 0)
+ usage();
+ werrstr("");
+ mb = *argv++;
+ if(*argv == 0)
+ append(0, mb, from, t);
+ else for(; *argv; argv++){
+ fd = open(*argv, OREAD);
+ if(fd < 0)
+ sysfatal("open: %r");
+ append(fd, mb, from, t);
+ close(fd);
+ }
+ exits("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/filterkit/mbcreate.c
@@ -1,0 +1,32 @@
+#include "dat.h"
+#include "common.h"
+
+void
+usage(void)
+{
+ fprint(2, "usage: mbcreate [-f] ...\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int r;
+ int (*f)(char*, char*);
+
+ f = creatembox;
+ ARGBEGIN{
+ case 'f':
+ f = createfolder;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ r = 0;
+ for(; *argv; argv++)
+ r |= f(getuser(), *argv);
+ if(r)
+ exits("errors");
+ exits("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/filterkit/mbremove.c
@@ -1,0 +1,243 @@
+/*
+ * why did i write this and not use upas/fs?
+ */
+#include "dat.h"
+#include "common.h"
+
+int qflag;
+int pflag;
+int rflag;
+int tflag;
+int vflag;
+
+/* must be [0-9]+(\..*)? */
+static 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
+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;
+}
+
+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;
+}
+
+int
+idiotcheck(char *path, Dir *d, int getindex)
+{
+ uvlong v;
+
+ if(d->mode & DMDIR)
+ return 0;
+ if(!strncmp(d->name, "L.", 2))
+ return 0;
+ if(getindex && isindex(d))
+ return 0;
+ if(!dirskip(d, &v) || ismbox(path))
+ return 0;
+ return -1;
+}
+
+int
+vremove(char *buf)
+{
+ if(vflag)
+ fprint(2, "rm %s\n", buf);
+ if(!pflag)
+ return remove(buf);
+ return 0;
+}
+
+int
+rm(char *dir, int level)
+{
+ char buf[Pathlen];
+ int i, n, r, fd, isdir;
+ 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;
+ for(i = 0; i < n; i++){
+ snprint(buf, sizeof buf, "%s/%s", dir, d[i].name);
+ if(rflag)
+ r |= rm(buf, level+1);
+ if(idiotcheck(buf, d+i, level+rflag) == -1)
+ continue;
+ if(vremove(buf) != 0)
+ r = -1;
+ }
+ free(d);
+ return r;
+}
+
+void
+nukeidx(char *buf)
+{
+ char buf2[Pathlen];
+
+ snprint(buf2, sizeof buf2, "%s.idx", buf);
+ vremove(buf2);
+ snprint(buf2, sizeof buf2, "%s.imp", buf);
+ vremove(buf2);
+}
+
+void
+truncidx(char *buf)
+{
+ char buf2[Pathlen];
+
+ snprint(buf2, sizeof buf2, "%s.idx", buf);
+ vremove(buf2);
+// snprint(buf2, sizeof buf2, "%s.imp", buf);
+// vremove(buf2);
+}
+
+static int
+removefolder0(char *user, char *folder, char *ftype)
+{
+ char *msg, buf[Pathlen];
+ int r, isdir;
+ Dir *d;
+
+ assert(folder != 0);
+ mboxpathbuf(buf, sizeof buf, user, folder);
+ if((d = dirstat(buf)) == 0){
+ fprint(2, "%s: %s doesn't exist\n", buf, ftype);
+ return 0;
+ }
+ isdir = d->mode & DMDIR;
+ free(d);
+ msg = "deleting";
+ if(tflag)
+ msg = "truncating";
+ fprint(2, "%s %s: %s\n", msg, ftype, buf);
+
+ /* must match folder.c:/^openfolder */
+ r = rm(buf, 0);
+ if(!tflag)
+ r = vremove(buf);
+ else if(!isdir)
+ r = open(buf, OWRITE|OTRUNC);
+
+ if(tflag)
+ truncidx(buf);
+ else
+ nukeidx(buf);
+
+ if(r == -1){
+ fprint(2, "%s: can't %s %s\n", buf, msg, ftype);
+ return -1;
+ }
+ close(r);
+ return 0;
+}
+
+int
+removefolder(char *user, char *folder)
+{
+ return removefolder0(user, folder, "folder");
+}
+
+int
+removembox(char *user, char *mbox)
+{
+ char buf[Pathlen];
+
+ if(mbox == 0)
+ snprint(buf, sizeof buf, "mbox");
+ else
+ snprint(buf, sizeof buf, "%s/mbox", mbox);
+ return removefolder0(user, buf, "mbox");
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: mbremove [-fpqrtv] ...\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int r;
+ int (*f)(char*, char*);
+
+ f = removembox;
+ ARGBEGIN{
+ case 'f':
+ f = removefolder;
+ break;
+ case 'p':
+ pflag++;
+ break;
+ case 'q':
+ qflag++;
+ close(2);
+ open("/dev/null", OWRITE);
+ break;
+ case 'r':
+ rflag++;
+ break;
+ case 't':
+ tflag++;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ r = 0;
+ for(; *argv; argv++)
+ r |= f(getuser(), *argv);
+ if(r)
+ exits("errors");
+ exits("");
+}
--- a/sys/src/cmd/upas/filterkit/mkfile
+++ b/sys/src/cmd/upas/filterkit/mkfile
@@ -1,13 +1,14 @@
</$objtype/mkfile
+<../mkupas
TARG=\
- token\
- list\
deliver\
+ list\
+ mbappend\
+ token\
LIB=../common/libcommon.a$O\
-BIN=/$objtype/bin/upas
OFILES=readaddrs.$O
UPDATE=\
mkfile\
--- /dev/null
+++ b/sys/src/cmd/upas/filterkit/testemail
@@ -1,0 +1,4 @@
+From: erik quanstrom <[email protected]>
+Subject: 1 testing
+
+testing
--- a/sys/src/cmd/upas/filterkit/token.c
+++ b/sys/src/cmd/upas/filterkit/token.c
@@ -1,60 +1,50 @@
#include <u.h>
#include <libc.h>
#include <libsec.h>
-#include <String.h>
#include "dat.h"
void
usage(void)
{
- fprint(2, "usage: %s key [token [file]]\n", argv0);
+ fprint(2, "usage: token key [token]\n");
exits("usage");
}
-static String*
-mktoken(char *key, long thetime)
+static char*
+mktoken(char *key, long t)
{
- char *now;
+ char *now, token[64];
uchar digest[SHA1dlen];
- char token[64];
- String *s;
-
- now = ctime(thetime);
+
+ now = ctime(t);
memset(now+11, ':', 8);
hmac_sha1((uchar*)now, strlen(now), (uchar*)key, strlen(key), digest, nil);
enc64(token, sizeof token, digest, sizeof digest);
- s = s_new();
- s_nappend(s, token, 5);
- return s;
+ return smprint("%.5s", token);
}
static char*
check_token(char *key, char *file)
{
- String *s;
+ char *s, buf[1024];
+ int i, fd, m;
long now;
- int i;
- char buf[1024];
- int fd;
fd = open(file, OREAD);
if(fd < 0)
return "no match";
- i = read(fd, buf, sizeof(buf)-1);
+ i = read(fd, buf, sizeof buf-1);
close(fd);
if(i < 0)
return "no match";
buf[i] = 0;
-
now = time(0);
-
for(i = 0; i < 14; i++){
s = mktoken(key, now-24*60*60*i);
- if(strstr(buf, s_to_c(s)) != nil){
- s_free(s);
+ m = s != nil && strstr(buf, s) != nil;
+ free(s);
+ if(m)
return nil;
- }
- s_free(s);
}
return "no match";
}
@@ -62,10 +52,7 @@
static char*
create_token(char *key)
{
- String *s;
-
- s = mktoken(key, time(0));
- print("%s", s_to_c(s));
+ print("%s", mktoken(key, time(0)));
return nil;
}
@@ -78,10 +65,8 @@
switch(argc){
case 2:
exits(check_token(argv[0], argv[1]));
- break;
case 1:
exits(create_token(argv[0]));
- break;
default:
usage();
}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/bos.c
@@ -1,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;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/cache.c
@@ -1,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;
+}
binary files /dev/null b/sys/src/cmd/upas/fs/chkidx differ
--- /dev/null
+++ b/sys/src/cmd/upas/fs/chkidx.c
@@ -1,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("");
+}
--- a/sys/src/cmd/upas/fs/dat.h
+++ b/sys/src/cmd/upas/fs/dat.h
@@ -1,191 +1,313 @@
+#include <avl.h>
+
+enum {
+ /* cache states */
+ Cidx = 1<<0,
+ Cidxstale = 1<<1,
+ Cheader = 1<<2,
+ Cbody = 1<<3,
+
+ /* encodings */
+ Enone= 0,
+ Ebase64,
+ Equoted,
+
+ /* dispositions */
+ Dnone= 0,
+ Dinline,
+ Dfile,
+ Dignore,
+
+ /* 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
-{
+struct Message {
int id;
int refs;
int subname;
- char name[Elemlen];
+ char name[12];
- // 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
+ /* top-level indexed information */
+ Idx;
- // 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;
+ /* caching help */
+ uchar cstate;
+ ulong infolen;
+ ulong csize;
- // 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
+ /*
+ * 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. */
- Message *next; // same level
- Message *part; // down a level
- Message *whole; // up a level
+ char deleted;
+ char inmbox;
- uchar digest[SHA1dlen];
+ /* mail info */
+ char *unixheader;
+ char *unixfrom;
+ char *date822;
+ char *references[Nref];
- vlong imapuid; // used by imap4
+ /* mime info */
+ char *boundary;
+ int charset;
+ char *filename;
+ char encoding;
+ char converted;
+ char decoded;
+ char mimeflag;
- char uidl[80]; // used by pop3
- int mesgno;
+ Message *next;
+ Message *part;
+ Message *whole;
+
+ union{
+ char *lim; /* used by plan9; not compatable with cache */
+ vlong imapuid; /* used by imap4 */
+ void *aux;
+ };
};
-enum
-{
- // encodings
- Enone= 0,
- Ebase64,
- Equoted,
+typedef struct {
+ Avl;
+ Message *m;
+} Mtree;
- // disposition possibilities
- Dnone= 0,
- Dinline,
- Dfile,
- Dignore,
-
- PAD64= '=',
+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 @@
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 @@
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
binary files /dev/null b/sys/src/cmd/upas/fs/extra/fd2path differ
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/fd2path.c
@@ -1,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("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/idxtst.c
@@ -1,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("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/infotst.c
@@ -1,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++;
+ }
+}
binary files /dev/null b/sys/src/cmd/upas/fs/extra/paw differ
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/paw.c
@@ -1,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("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/prflags.c
@@ -1,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("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/strtotmtst.c
@@ -1,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("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/extra/tokens.c
@@ -1,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;
+}
--- 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 @@
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 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 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,19 +106,193 @@
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");
}
@@ -147,42 +299,97 @@
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,12 +397,6 @@
case 's':
srvpost = 1;
break;
- case 'l':
- logging = 1;
- break;
- case 'n':
- nodflt = 1;
- break;
default:
usage();
}ARGEND
@@ -202,6 +403,13 @@
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 @@
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 @@
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("");
}
+char*
+sputc(char *p, char *e, int c)
+{
+ if(p < e - 1)
+ *p++ = c;
+ return p;
+}
+
+char*
+seappend(char *s, char *e, char *a, int n)
+{
+ int l;
+
+ l = e - s - 1;
+ if(l < n)
+ n = l;
+ memcpy(s, a, n);
+ s += n;
+ *s = 0;
+ return s;
+}
+
static int
-fileinfo(Message *m, int t, char **pp)
+fileinfo(Mailbox *mb, Message *m, int t, char **pp)
{
- char *p;
- int len;
+ char *s, *e, *p;
+ int len, i;
+ static char buf[64 + 512];
- p = "";
- len = 0;
+ 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 @@
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 @@
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;
+ char *s, *p, *e;
+ int i, n;
+ long off0;
- 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;
+ 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 @@
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 @@
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 @@
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 @@
}
char*
-rflush(Fid *f)
+rflush(Fid*)
{
- USED(f);
return 0;
}
@@ -608,30 +856,43 @@
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 @@
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 @@
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 @@
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 @@
char *rv;
int i;
+sanefid(f);
if(f->open)
return Eisopen;
@@ -754,14 +1035,14 @@
}
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;
@@ -768,18 +1049,17 @@
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 @@
return 0;
}
-char *
+char*
rcreate(Fid*)
{
return Eperm;
@@ -816,7 +1096,7 @@
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,7 +1131,7 @@
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;
@@ -858,15 +1138,15 @@
} 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 @@
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 @@
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 @@
}
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 @@
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;
+ 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;
+}
- if(FILE(f->qid.path) == Qinfo){
- rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
- return nil;
- }
+char*
+modflags(Mailbox *mb, Message *m, char *p)
+{
+ char *err;
+ uchar f;
- 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;
+ 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 @@
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)
+ memset(argvbuf, 0, sizeof argvbuf);
+ argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1);
+ if(argc == 0)
return Ebadctl;
- if(thdr.data[thdr.count-1] == '\n')
- thdr.data[thdr.count-1] = 0;
- else
- thdr.data[thdr.count] = 0;
- n = tokenize(thdr.data, token, nelem(token));
- if(n == 0)
- return Ebadctl;
- if(strcmp(token[0], "open") == 0){
- file = s_new();
- switch(n){
- case 1:
- err = Ebadctl;
- break;
- case 2:
- mboxpath(token[1], getlog(), file, 0);
- err = newmbox(s_to_c(file), nil, 0);
- break;
- default:
- mboxpath(token[1], getlog(), file, 0);
- if(strchr(token[2], '/') != nil)
- err = "/ not allowed in mailbox name";
- else
- err = newmbox(s_to_c(file), token[2], 0);
- break;
- }
- s_free(file);
- return err;
+ if(strcmp(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)
+ argc = tokenize(thdr.data, argv, nelem(argvbuf));
+ if(argc == 0)
return Ebadctl;
- if(thdr.data[thdr.count-1] == '\n')
- thdr.data[thdr.count-1] = 0;
- else
- thdr.data[thdr.count] = 0;
- n = tokenize(thdr.data, token, nelem(token));
- if(n == 0)
- return Ebadctl;
- return (*f->mb->ctl)(f->mb, n, token);
+ return 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,7 +1455,7 @@
mboxdecref(mb);
qunlock(&mbllock);
}
- f->fid = -1;
+ f->busy = 0;
return 0;
}
@@ -1078,10 +1462,11 @@
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 @@
{
Dir d;
+sanefid(f);
if(FILE(f->qid.path) == Qmbox){
qlock(f->mb);
syncmbox(f->mb, 1);
@@ -1102,13 +1488,13 @@
return 0;
}
-char *
+char*
rwstat(Fid*)
{
return Eperm;
}
-Fid *
+Fid*
newfid(int fid)
{
Fid *f, *ff;
@@ -1152,7 +1538,7 @@
int n;
/* start a process to watch the mailboxes*/
- if(plumbing){
+ if(plumbing || biffing)
switch(rfork(RFPROC|RFMEM)){
case -1:
/* oh well */
@@ -1159,19 +1545,30 @@
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;
@@ -1178,7 +1575,7 @@
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,8 +1584,8 @@
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");
@@ -1195,6 +1592,8 @@
}
}
+static char *readerargv[] = {"upas/fs", "plumbing", 0};
+
void
reader(void)
{
@@ -1202,6 +1601,7 @@
Dir *d;
Mailbox *mb;
+ setname(readerargv);
sleep(15*1000);
for(;;){
t = time(0);
@@ -1208,7 +1608,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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);
--- /dev/null
+++ b/sys/src/cmd/upas/fs/header.c
@@ -1,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;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/idx.c
@@ -1,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;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/imap.c
@@ -1,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;
+}
--- a/sys/src/cmd/upas/fs/imap4.c
+++ /dev/null
@@ -1,878 +1,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, '"');
-}
--- 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*);
+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*);
+
enum
{
- Mhead= 11, /* offset of first mime header */
+ Mhead = 11, /* offset of first mime header */
};
-Header head[] =
+#define O(x) offsetof(Message, x)
+static Header head[] =
{
- { "date:", date822, },
- { "from:", from822, },
- { "to:", to822, },
- { "sender:", sender822, },
- { "reply-to:", replyto822, },
- { "subject:", subject822, },
- { "cc:", cc822, },
- { "bcc:", bcc822, },
- { "in-reply-to:", inreplyto822, },
- { "mime-version:", mimeversion, },
- { "message-id:", messageid822, },
+ "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:", ctype, },
- { "content-transfer-encoding:", cencoding, },
- { "content-disposition:", cdisposition, },
- { 0, },
+[Mhead] "content-type:", ~0, ctype, 0, 0,
+ "content-transfer-encoding:", ~0, cencoding, 0, 0,
+ "content-disposition:", ~0, cdisposition, 0, 0,
};
-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
-{
- Chunksize = 1024,
-};
-
-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)
{
- Mailbox *mb, **l;
+ 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)
+{
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,30 +196,25 @@
free(mb);
return "bad mbox name";
}
- strncpy(mb->name, p, sizeof(mb->name)-1);
- } else {
- strncpy(mb->name, name, sizeof(mb->name)-1);
}
+ strncpy(mb->name, p, sizeof mb->name - 1);
+ mb->idxread = genericidxread;
+ mb->idxwrite = genericidxwrite;
+ mb->idxinvalid = genericidxinvalid;
- rv = nil;
- // check for a mailbox type
- for(i=0; i<nelem(boxinit); i++)
- if((rv = (*boxinit[i])(mb, path)) != Enotme)
+ /* 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;
@@ -142,36 +221,39 @@
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)
{
@@ -178,7 +260,7 @@
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;
@@ -185,328 +267,468 @@
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);
+}
- for(h = head; h->type != nil; h++)
- h->len = strlen(h->type);
+
+char*
+removembox(char *name, int flags)
+{
+ int found;
+ Mailbox **l, *mb;
+
+ 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;
- h = s_new();
- for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
- s_putc(h, *p);
- s_terminate(h);
- s_restart(h);
+ 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;
+}
- m->unixfrom = s_parse(h, s_reset(m->unixfrom));
- m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
+Message*
+gettopmsg(Mailbox *mb, Message *m)
+{
+ while(!Topmsg(mb, m))
+ m = m->whole;
+ return m;
+}
- s_free(h);
+void
+datesec(Mailbox *mb, Message *m)
+{
+ char *s;
+ vlong v;
+ Tm tm;
+
+ 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;
}
- s_reset(hl);
+ for(nm = m->part; nm != nil; nm = nm->next){
+ parse(mb, nm, 0, 1);
+ cachehash(mb, nm); /* botchy place for this */
+ }
+ 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);
+ if(!justmime)
+ datesec(mb, 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){
+ /*
+ * 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;
+ while(isascii(*p) && isspace(*p))
+ p++;
+ return p;
+}
- // 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));
+static char*
+skiptosemi(char *p)
+{
+ while(*p && *p != ';')
+ p++;
+ while(*p == ';' || (isascii(*p) && isspace(*p)))
+ p++;
+ return p;
+}
- /* no boundary, we're done */
- if(x == nil){
- if(nm != nil)
- nm->rbend = nm->bend = nm->end = m->bend;
- break;
- }
+static char*
+getstring(char *p, char *s, char *e, int dolower)
+{
+ int c;
- /* boundary must be at the start of a line */
- if(x != m->body && *(x-1) != '\n'){
- p = x+1;
+ 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;
+ if(c < ' ')
continue;
- }
-
- if(nm != nil)
- nm->rbend = nm->bend = nm->end = x;
- x += strlen(s_to_c(m->boundary));
-
- /* is this the last part? ignore anything after it */
- if(strncmp(x, "--", 2) == 0)
+ 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;
-
- p = strchr(x, '\n');
- if(p == nil)
- break;
- nm = newmessage(m);
- nm->start = nm->header = nm->body = nm->rbody = ++p;
- nm->mheader = nm->header;
- *l = nm;
- l = &nm->next;
+ 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;
}
+ *s = 0;
+ return p;
+}
- // 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);
- }
+static void
+setfilename(Message *m, char *p)
+{
+ char buf[Pathlen];
+
+ 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 = '_';
}
-/*
- * pick up a header line
- */
-static int
-headerline(char **pp, String *hl)
+static char*
+rtrim(char *p)
{
- char *p, *x;
+ char *e;
- 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;
+ if(p == 0)
+ return p;
+ e = p + strlen(p) - 1;
+ while(e > p && isascii(*e) && isspace(*e))
+ *e-- = 0;
+ return p;
}
-/* returns nil iff there are no addressees */
-static String*
-addr822(char *p)
+static char*
+addr822(char *p, char **ac)
{
- String *s, *list;
- int incomment, addrdone, inanticomment, quoted;
- int n;
- int c;
+ int n, c, space, incomment, addrdone, inanticomment, quoted;
+ char s[128+1], *ps, *e, *x, *list;
- list = s_new();
- s = s_new();
- quoted = incomment = addrdone = inanticomment = 0;
+ list = 0;
+ s[0] = 0;
+ ps = s;
+ e = s + sizeof s;
+ space = quoted = incomment = addrdone = inanticomment = 0;
n = 0;
- for(; *p; p++){
- c = *p;
-
- // whitespace is ignored
+ 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 @@
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 @@
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 @@
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 @@
}
- 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,140 +817,97 @@
* concatenating their values.
*/
-static void
-to822(Message *m, Header *h, char *p)
+static char*
+concat822(Message*, Header *h, char *o, char *p)
{
- String *s;
+ char *s, *n;
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);
- }
+ s = addr822(p, 0);
+ if(o){
+ n = smprint("%s %s", o, s);
+ free(s);
+ }else
+ n = s;
+ return n;
}
-static void
-cc822(Message *m, Header *h, char *p)
+static char*
+from822(Message *m, Header *h, char*, 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);
- }
+ if(m->ffrom)
+ free(m->ffrom);
+ m->from = 0;
+ return addr822(p + h->len, &m->ffrom);
}
-static void
-bcc822(Message *m, Header *h, char *p)
+static char*
+replace822(Message *, Header *h, char*, 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);
- }
+ return addr822(p + h->len, 0);
}
-static void
-from822(Message *m, Header *h, char *p)
+static char*
+copy822(Message*, Header *h, char*, char *p)
{
- p += strlen(h->type);
- s_free(m->from822);
- m->from822 = addr822(p);
+ return rtrim(strdup(skipwhite(p + h->len)));
}
-static void
-sender822(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);
- s_free(m->sender822);
- m->sender822 = addr822(p);
-}
+ int i, j;
-static void
-replyto822(Message *m, Header *h, char *p)
-{
- p += strlen(h->type);
- s_free(m->replyto822);
- m->replyto822 = addr822(p);
+ 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
-mimeversion(Message *m, Header *h, char *p)
+static char*
+ref822(Message *m, Header *h, char*, char *p)
{
- p += strlen(h->type);
- s_free(m->mimeversion);
- m->mimeversion = addr822(p);
-}
+ char **a, *s, *f[Nref + 1];
+ int i, j, k, n;
-static void
-killtrailingwhite(char *p)
-{
- char *e;
-
- e = p + strlen(p) - 1;
- while(e > p && isspace(*e))
- *e-- = 0;
+ 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 void
-date822(Message *m, Header *h, char *p)
-{
- p += strlen(h->type);
- p = skipwhite(p);
- s_free(m->date822);
- m->date822 = s_copy(p);
- p = s_to_c(m->date822);
- killtrailingwhite(p);
-}
-
-static void
-subject822(Message *m, Header *h, char *p)
-{
- p += strlen(h->type);
- p = skipwhite(p);
- s_free(m->subject822);
- m->subject822 = s_copy(p);
- p = s_to_c(m->subject822);
- killtrailingwhite(p);
-}
-
-static void
-inreplyto822(Message *m, Header *h, char *p)
-{
- p += strlen(h->type);
- p = skipwhite(p);
- s_free(m->inreplyto822);
- m->inreplyto822 = s_copy(p);
- p = s_to_c(m->inreplyto822);
- killtrailingwhite(p);
-}
-
-static void
-messageid822(Message *m, Header *h, char *p)
-{
- p += strlen(h->type);
- p = skipwhite(p);
- s_free(m->messageid822);
- m->messageid822 = s_copy(p);
- p = s_to_c(m->messageid822);
- killtrailingwhite(p);
-}
-
static int
isattribute(char **pp, char *attr)
{
@@ -741,24 +929,20 @@
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 @@
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 @@
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 @@
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(m->unixfrom);
+ free(m->unixheader);
+ free(m->date822);
+ free(m->inreplyto);
+ free(m->boundary);
+ free(m->filename);
- /* free memory */
- if(m->mallocd)
- free(m->start);
- if(m->hallocd)
- free(m->header);
- if(m->ballocd)
- free(m->body);
- s_free(m->unixfrom);
- s_free(m->unixdate);
- s_free(m->unixheader);
- s_free(m->from822);
- s_free(m->sender822);
- s_free(m->to822);
- s_free(m->bcc822);
- s_free(m->cc822);
- s_free(m->replyto822);
- s_free(m->date822);
- s_free(m->inreplyto822);
- s_free(m->subject822);
- s_free(m->messageid822);
- s_free(m->addrs);
- s_free(m->mimeversion);
- s_free(m->sdigest);
- s_free(m->boundary);
- s_free(m->type);
- s_free(m->charset);
- s_free(m->filename);
-
free(m);
}
-// mark messages (identified by path) for deletion
void
+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,26 +1081,61 @@
}
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;
+}
+
/*
* the following are called with the mailbox qlocked
*/
@@ -935,12 +1144,18 @@
{
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 @@
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 @@
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;
+ return len;
}
-static char*
-skipwhite(char *p)
-{
- while(isspace(*p))
- p++;
- return p;
-}
-
-static char*
-skiptosemi(char *p)
-{
- while(*p && *p != ';')
- p++;
- while(*p == ';' || isspace(*p))
- p++;
- return p;
-}
-
-static char*
-getstring(char *p, String *s, int dolower)
-{
- s = s_reset(s);
- p = skipwhite(p);
- if(*p == '"'){
- p++;
- for(;*p && *p != '"'; p++)
- if(dolower)
- s_putc(s, tolower(*p));
- else
- s_putc(s, *p);
- if(*p == '"')
- p++;
- s_terminate(s);
-
- return p;
- }
-
- for(; *p && !isspace(*p) && *p != ';'; p++)
- if(dolower)
- s_putc(s, tolower(*p));
- else
- s_putc(s, *p);
- s_terminate(s);
-
- return p;
-}
-
-static void
-setfilename(Message *m, char *p)
-{
- m->filename = s_reset(m->filename);
- getstring(p, m->filename, 0);
- for(p = s_to_c(m->filename); *p; p++)
- if(*p == ' ' || *p == '\t' || *p == ';')
- *p = '_';
-}
-
-//
-// undecode message body
-//
+/*
+ * undecode message body
+ */
void
decode(Message *m)
{
@@ -1062,9 +1234,15 @@
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 @@
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,7 +1265,7 @@
m->decoded = 1;
}
-// convert latin1 to utf
+/* convert x to utf8 */
void
convert(Message *m)
{
@@ -1094,15 +1272,13 @@
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)
+ m->converted = 1;
+ if(m->part != nil || cistrncmp(rtab[m->type].s, "text", 4) != 0)
return;
- if(cistrncmp(s_to_c(m->type), "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 @@
m->bend = x + len;
m->ballocd = 1;
}
- m->converted = 1;
}
static int
@@ -1119,18 +1294,20 @@
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 @@
*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 @@
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 @@
return p - out;
}
-static char*
+char*
lowercase(char *p)
{
char *op;
@@ -1207,7 +1384,7 @@
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,13 +1399,13 @@
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;
@@ -1235,25 +1412,22 @@
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 @@
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,17 +1463,17 @@
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;
@@ -1306,7 +1480,7 @@
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 @@
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 @@
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 @@
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;
-//
-// 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.
-//
+ 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);
+}
-extern int strtotm(char*, Tm*);
-String*
-date822tounix(char *s)
+/*
+ * 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.
+ */
+
+char*
+date822tounix(Message *, char *s)
{
char *p, *q;
Tm tm;
@@ -1515,6 +1712,5 @@
p = asctime(&tm);
if(q = strchr(p, '\n'))
*q = '\0';
- return s_copy(p);
+ return strdup(p);
}
-
--- /dev/null
+++ b/sys/src/cmd/upas/fs/mdir.c
@@ -1,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;
+}
--- 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 @@
HFILES= ../common/common.h\
dat.h
-BIN=/$objtype/bin/upas
-
UPDATE=\
mkfile\
$HFILES\
@@ -25,4 +33,10 @@
${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
--- /dev/null
+++ b/sys/src/cmd/upas/fs/mtree.c
@@ -1,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);
+}
--- a/sys/src/cmd/upas/fs/plan9.c
+++ b/sys/src/cmd/upas/fs/plan9.c
@@ -1,144 +1,181 @@
#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)
{
- int fd;
- uchar *lim;
- uchar *rptr;
- uchar *wptr;
- uchar data[Buffersize+7];
-};
+ 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 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];
+ char e[ERRMAX], buf[128];
- 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;
- }
- }
-
- // 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;
-// throw out deleted messages. return number of freshly deleted messages
+ 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 */
int
purgedeleted(Mailbox *mb)
{
@@ -145,7 +182,7 @@
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,19 +195,37 @@
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;
@@ -177,25 +232,14 @@
/*
* 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 */
+ 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;
- }
- 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);
- goto retry;
- }
- s_free(tmp);
}
return err;
}
@@ -204,15 +248,16 @@
* 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;
}
@@ -219,22 +264,19 @@
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);
@@ -241,98 +283,99 @@
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)
@@ -339,28 +382,26 @@
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);
@@ -367,10 +408,10 @@
}
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 @@
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 @@
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;
}
--- 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
*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,15 +394,15 @@
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.
@@ -379,30 +409,30 @@
*/
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;
}
--- a/sys/src/cmd/upas/fs/pop3.c
+++ b/sys/src/cmd/upas/fs/pop3.c
@@ -1,6 +1,4 @@
#include "common.h"
-#include <ctype.h>
-#include <plumb.h>
#include <libsec.h>
#include <auth.h>
#include "dat.h"
@@ -7,37 +5,49 @@
#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 *freep; /* free this to free the strings below */
+ char *host;
+ char *user;
+ char *port;
- char *host;
- char *user;
- char *port;
+ int ppop;
+ int refreshtime;
+ int debug;
+ int pipeline;
+ int encrypted;
+ int needtls;
+ int notls;
+ int needssl;
- 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
-
+ 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));
@@ -44,11 +54,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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,65 +255,57 @@
}
}
-//
-// 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;
@@ -318,18 +312,18 @@
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 @@
if(strcmp(s, ".") == 0)
break;
- l = strlen(s)+1;
+ l = strlen(s) + 1;
if(s[0] == '.') {
s++;
l--;
@@ -356,14 +350,17 @@
* 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 @@
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;
+ m->size = m->end - m->start;
+ if(m->digest == nil)
+ digestmessage(mb, m);
- // digest message
- sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
- for(i = 0; i < SHA1dlen; i++)
- sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
- m->sdigest = s_copy(sdigest);
-
return nil;
}
-//
-// check for new messages on pop server
-// UIDL is not required by RFC 1939, but
-// netscape requires it, so almost every server supports it.
-// we'll use it to make our lives easier.
-//
+/*
+ * 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 @@
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 @@
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,12 +486,12 @@
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("");
}
}
@@ -492,37 +498,29 @@
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 @@
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 @@
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 @@
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 @@
}
-// 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 @@
return Epop3ctl;
}
-// free extra memory associated with mb
+/* free extra memory associated with mb */
static void
pop3close(Mailbox *mb)
{
@@ -640,9 +640,18 @@
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 @@
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 @@
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 @@
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;
}
--- a/sys/src/cmd/upas/fs/readdir.c
+++ /dev/null
@@ -1,15 +1,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);
-}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/ref.c
@@ -1,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;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/remove.c
@@ -1,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;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/fs/rename.c
@@ -1,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;
+}
--- a/sys/src/cmd/upas/fs/rfc2047-test
+++ /dev/null
@@ -1,28 +1,0 @@
-From [email protected] Tue Mar 28 21:58:10 CST 2006
-From: =?US-ASCII?Q?Keith_Moore?= <[email protected]>
-To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <[email protected]>
-CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <[email protected]>
-Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
- =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
-
-From [email protected] Tue Mar 28 21:58:10 CST 2006
-From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <[email protected]>
-To: [email protected], [email protected]
-Subject: Time for ISO 10646?
-
-From [email protected] Tue Mar 28 21:58:10 CST 2006
-To: Dave Crocker <[email protected]>
-Cc: [email protected], [email protected]
-From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <[email protected]>
-Subject: Re: RFC-HDR care and feeding
-
-From [email protected] Tue Mar 28 21:58:10 CST 2006
-From: Nathaniel Borenstein <[email protected]>
- (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=)
-To: Greg Vaudreuil <[email protected]>, Ned Freed
- <[email protected]>, Keith Moore <[email protected]>
-Subject: Test of new header generator
-MIME-Version: 1.0
-Content-type: text/plain; charset=ISO-8859-1
-
-
--- /dev/null
+++ b/sys/src/cmd/upas/fs/seg.c
@@ -1,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("");
+}
--- 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,7 +12,7 @@
static char*
skipwhite(char *q)
{
- while(*q==' ' || *q=='\t' || *q=='\r' || *q=='\n')
+ while(*q == ' ' || *q == '\t' || *q == '\r' || *q == '\n')
q++;
return q;
}
@@ -20,29 +19,12 @@
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,15 +38,15 @@
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;
@@ -71,43 +53,45 @@
}
/* 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;
}
--- a/sys/src/cmd/upas/fs/tester.c
+++ /dev/null
@@ -1,81 +1,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);
-}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/auth.c
@@ -1,0 +1,280 @@
+#include "imap4d.h"
+#include <libsec.h>
+
+static char Ebadch[] = "can't get challenge";
+static char Ecantstart[] = "can't initialize mail system: %r";
+static char Ecancel[] = "client cancelled authentication";
+static char Ebadau[] = "login failed";
+
+/*
+ * hack to allow smtp forwarding.
+ * hide the peer IP address under a rock in the ratifier FS.
+ */
+void
+enableforwarding(void)
+{
+ char buf[64], peer[64], *p;
+ int fd;
+ ulong now;
+ static ulong last;
+
+ if(remote == nil)
+ return;
+
+ now = time(0);
+ if(now < last + 5*60)
+ return;
+ last = now;
+
+ fd = open("/srv/ratify", ORDWR);
+ if(fd < 0)
+ return;
+ if(!mount(fd, -1, "/mail/ratify", MBEFORE, "")){
+ close(fd);
+ return;
+ }
+ close(fd);
+
+ strncpy(peer, remote, sizeof peer);
+ peer[sizeof peer - 1] = 0;
+ p = strchr(peer, '!');
+ if(p != nil)
+ *p = 0;
+
+ snprint(buf, sizeof buf, "/mail/ratify/trusted/%s#32", peer);
+
+ /*
+ * if the address is already there and the user owns it,
+ * remove it and recreate it to give him a new time quanta.
+ */
+ if(access(buf, 0) >= 0 && remove(buf) < 0)
+ return;
+
+ fd = create(buf, OREAD, 0666);
+ if(fd >= 0)
+ close(fd);
+}
+
+void
+setupuser(AuthInfo *ai)
+{
+ int pid;
+ Waitmsg *w;
+
+ if(ai){
+ strecpy(username, username + sizeof username, ai->cuid);
+
+ if(auth_chuid(ai, nil) == -1)
+ bye("user auth failed: %r");
+ auth_freeAI(ai);
+ }else
+ strecpy(username, username + sizeof username, getuser());
+
+ if(strcmp(username, "none") == 0 || newns(username, 0) == -1)
+ bye("user login failed: %r");
+ if(binupas){
+ if(bind(binupas, "/bin/upas", MREPL) > 0)
+ ilog("bound %s on /bin/upas", binupas);
+ else
+ bye("bind %s failed: %r", binupas);
+ }
+
+ /*
+ * hack to allow access to outgoing smtp forwarding
+ */
+ enableforwarding();
+
+ snprint(mboxdir, Pathlen, "/mail/box/%s", username);
+ if(mychdir(mboxdir) < 0)
+ bye("can't open user's mailbox");
+
+ switch(pid = fork()){
+ case -1:
+ bye(Ecantstart);
+ break;
+ case 0:
+if(!strstr(argv0, "8.out"))
+ execl("/bin/upas/fs", "upas/fs", "-np", nil);
+else{
+ilog("using /sys/src/cmd/upas/fs/8.out");
+execl("/sys/src/cmd/upas/fs/8.out", "upas/fs", "-np", nil);
+}
+ _exits(0);
+ break;
+ default:
+ break;
+ }
+ if((w = wait()) == nil || w->pid != pid || w->msg[0] != 0)
+ bye(Ecantstart);
+ free(w);
+}
+
+static char*
+authread(int *len)
+{
+ char *t;
+ int n;
+
+ t = Brdline(&bin, '\n');
+ n = Blinelen(&bin);
+ if(n < 2)
+ return nil;
+ n--;
+ if(t[n-1] == '\r')
+ n--;
+ t[n] = 0;
+ if(n == 0 || strcmp(t, "*") == 0)
+ return nil;
+ *len = n;
+ return t;
+}
+
+static char*
+authresp(void)
+{
+ char *s, *t;
+ int n;
+
+ t = authread(&n);
+ if(t == nil)
+ return nil;
+ s = binalloc(&parsebin, n + 1, 1);
+ n = dec64((uchar*)s, n, t, n);
+ s[n] = 0;
+ return s;
+}
+
+/*
+ * rfc 2195 cram-md5 authentication
+ */
+char*
+cramauth(void)
+{
+ char *s, *t;
+ int n;
+ AuthInfo *ai;
+ Chalstate *cs;
+
+ if((cs = auth_challenge("proto=cram role=server")) == nil)
+ return Ebadch;
+
+ n = cs->nchal;
+ s = binalloc(&parsebin, n * 2, 0);
+ n = enc64(s, n * 2, (uchar*)cs->chal, n);
+ Bprint(&bout, "+ ");
+ Bwrite(&bout, s, n);
+ Bprint(&bout, "\r\n");
+ if(Bflush(&bout) < 0)
+ writeerr();
+
+ s = authresp();
+ if(s == nil)
+ return Ecancel;
+
+ t = strchr(s, ' ');
+ if(t == nil)
+ return Ebadch;
+ *t++ = 0;
+ strncpy(username, s, Userlen);
+ username[Userlen - 1] = 0;
+
+ cs->user = username;
+ cs->resp = t;
+ cs->nresp = strlen(t);
+ if((ai = auth_response(cs)) == nil)
+ return Ebadau;
+ auth_freechal(cs);
+ setupuser(ai);
+ return nil;
+}
+
+char*
+crauth(char *u, char *p)
+{
+ char response[64];
+ AuthInfo *ai;
+ static char nchall[64];
+ static Chalstate *ch;
+
+again:
+ if(ch == nil){
+ if(!(ch = auth_challenge("proto=p9cr role=server user=%q", u)))
+ return Ebadch;
+ snprint(nchall, 64, " encrypt challenge: %s", ch->chal);
+ return nchall;
+ } else {
+ strncpy(response, p, 64);
+ ch->resp = response;
+ ch->nresp = strlen(response);
+ ai = auth_response(ch);
+ auth_freechal(ch);
+ ch = nil;
+ if(ai == nil)
+ goto again;
+ setupuser(ai);
+ return nil;
+ }
+}
+
+char*
+passauth(char *u, char *secret)
+{
+ char response[2*MD5dlen + 1];
+ uchar digest[MD5dlen];
+ int i;
+ AuthInfo *ai;
+ Chalstate *cs;
+
+ if((cs = auth_challenge("proto=cram role=server")) == nil)
+ return Ebadch;
+ hmac_md5((uchar*)cs->chal, strlen(cs->chal),
+ (uchar*)secret, strlen(secret), digest, nil);
+ for(i = 0; i < MD5dlen; i++)
+ snprint(response + 2*i, sizeof response - 2*i, "%2.2ux", digest[i]);
+ cs->user = u;
+ cs->resp = response;
+ cs->nresp = strlen(response);
+ ai = auth_response(cs);
+ if(ai == nil)
+ return Ebadau;
+ auth_freechal(cs);
+ setupuser(ai);
+ return nil;
+}
+
+static int
+niltokenize(char *buf, int n, char **f, int nelemf)
+{
+ int i, nf;
+
+ f[0] = buf;
+ nf = 1;
+ for(i = 0; i < n - 1; i++)
+ if(buf[i] == 0){
+ f[nf++] = buf + i + 1;
+ if(nf == nelemf)
+ break;
+ }
+ return nf;
+}
+
+char*
+plainauth(char *ch)
+{
+ char buf[256*3 + 2], *f[4];
+ int n, nf;
+
+ if(ch == nil){
+ Bprint(&bout, "+ \r\n");
+ if(Bflush(&bout) < 0)
+ writeerr();
+ ch = authread(&n);
+ }
+ if(ch == nil || strlen(ch) == 0)
+ return Ecancel;
+ n = dec64((uchar*)buf, sizeof buf, ch, strlen(ch));
+ nf = niltokenize(buf, n, f, nelem(f));
+ if(nf != 3)
+ return Ebadau;
+ return passauth(f[1], f[2]);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/capability
@@ -1,0 +1,37 @@
+status
+u acl [rfc2086][rfc4314]
+u annotate-experiment-1 [rfc5257]
+u binary [rfc3516]
+ catenate [rfc4469]
+ children [rfc3348]
+u compress=deflate [rfc4978]
+ condstore [rfc4551]
+ context=search [rfc5267]
+ context=sort [rfc5267]
+u convert [rfc-ietf-lemonade-convert-20.txt]
+ enable [rfc5161]
+* esearch [rfc4731]
+ esort [rfc5267]
+u i18nlevel=1 [rfc5255]
+u i18nlevel=2 [rfc5255]
+u id [rfc2971]
+y idle [rfc2177]
+u language [rfc5255]
+ literal+ [rfc2088]
+ login-referrals [rfc2221]
+y logindisabled [rfc2595][rfc3501]
+ mailbox-referrals [rfc2193]
+ multiappend [rfc3502]
+y namespace [rfc2342]
+ qresync [rfc5162]
+y quota [rfc2087]
+u rights= [rfc4314]
+ sasl-ir [rfc4959]
+* searchres [rfc5182]
+* sort [rfc5256]
+ starttls [rfc2595][rfc3501]
+n thread [rfc5256]
+y uidplus [rfc4315]
+n unselect [rfc3691]
+u urlauth [rfc4467]
+ within [rfc5032]
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/copy.c
@@ -1,0 +1,248 @@
+#include "imap4d.h"
+#include <libsec.h>
+
+int
+copycheck(Box*, Msg *m, int, void *)
+{
+ int fd;
+
+ if(m->expunged)
+ return 0;
+ fd = msgfile(m, "rawunix");
+ if(fd < 0){
+ msgdead(m);
+ return 0;
+ }
+ close(fd);
+ return 1;
+}
+
+static int
+opendeliver(int *pip, char *folder, char *from, long t)
+{
+ char *av[7], buf[32];
+ int i, pid, fd[2];
+
+ if(pipe(fd) != 0)
+ sysfatal("pipe: %r");
+ pid = fork();
+ switch(pid){
+ case -1:
+ return -1;
+ case 0:
+ av[0] = "mbappend";
+ av[1] = folder;
+ i = 2;
+ if(from){
+ av[i++] = "-f";
+ av[i++] = from;
+ }
+ if(t != 0){
+ snprint(buf, sizeof buf, "%ld", t);
+ av[i++] = "-t";
+ av[i++] = buf;
+ }
+ av[i] = 0;
+ close(0);
+ dup(fd[1], 0);
+ if(fd[1] != 0)
+ close(fd[1]);
+ close(fd[0]);
+ exec("/bin/upas/mbappend", av);
+ ilog("exec: %r");
+ _exits("b0rked");
+ return -1;
+ default:
+ *pip = fd[0];
+ close(fd[1]);
+ return pid;
+ }
+}
+
+static int
+closedeliver(int pid, int fd)
+{
+ int nz, wpid;
+ Waitmsg *w;
+
+ close(fd);
+ while(w = wait()){
+ nz = !w->msg || !w->msg[0];
+ wpid = w->pid;
+ free(w);
+ if(wpid == pid)
+ return nz? 0: -1;
+ }
+ return -1;
+}
+
+/*
+ * we're going to all this trouble of fiddling the .imp file for
+ * the target mailbox because we wish to save the flags. we
+ * should be using upas/fs's flags instead.
+ *
+ * note. appendmb for mbox fmt wants to lock the directory.
+ * since the locking is intentionally broken, we could get by
+ * with aquiring the lock before we fire up appendmb and
+ * trust that he doesn't worry if he does acquire the lock.
+ * instead, we'll just do locking around the .imp file.
+ */
+static int
+savemsg(char *dst, int flags, char *head, int nhead, Biobuf *b, long n, Uidplus *u)
+{
+ char *digest, buf[Bufsize + 1], digbuf[Ndigest + 1], folder[Pathlen];
+ uchar shadig[SHA1dlen];
+ int i, fd, pid, nr, ok;
+ DigestState *dstate;
+ Mblock *ml;
+
+ snprint(folder, sizeof folder, "%s/%s", mboxdir, dst);
+ pid = opendeliver(&fd, folder, 0, 0);
+ if(pid == -1)
+ return 0;
+ ok = 1;
+ dstate = sha1(nil, 0, nil, nil);
+ if(nhead){
+ sha1((uchar*)head, nhead, nil, dstate);
+ if(write(fd, head, nhead) != nhead){
+ ok = 0;
+ goto loose;
+ }
+ }
+ while(n > 0){
+ nr = n;
+ if(nr > Bufsize)
+ nr = Bufsize;
+ nr = Bread(b, buf, nr);
+ if(nr <= 0){
+ ok = 0;
+ break;
+ }
+ n -= nr;
+ sha1((uchar*)buf, nr, nil, dstate);
+ if(write(fd, buf, nr) != nr){
+ ok = 0;
+ break;
+ }
+ }
+loose:
+ closedeliver(pid, fd);
+ sha1(nil, 0, shadig, dstate);
+ if(ok){
+ digest = digbuf;
+ for(i = 0; i < SHA1dlen; i++)
+ sprint(digest + 2*i, "%2.2ux", shadig[i]);
+ ml = mblock();
+ if(ml == nil)
+ return 0;
+ ok = appendimp(dst, digest, flags, u) == 0;
+ mbunlock(ml);
+ }
+ return ok;
+}
+
+static int
+copysave(Box*, Msg *m, int, void *vs, Uidplus *u)
+{
+ int ok, fd;
+ vlong length;
+ Biobuf b;
+ Dir *d;
+
+ if(m->expunged)
+ return 0;
+ if((fd = msgfile(m, "rawunix")) == -1){
+ msgdead(m);
+ return 0;
+ }
+ if((d = dirfstat(fd)) == nil){
+ close(fd);
+ return 0;
+ }
+ length = d->length;
+ free(d);
+
+ Binit(&b, fd, OREAD);
+ ok = savemsg(vs, m->flags, 0, 0, &b, length, u);
+ Bterm(&b);
+ close(fd);
+ return ok;
+}
+
+int
+copysaveu(Box *box, Msg *m, int i, void *vs)
+{
+ int ok;
+ Uidplus *u;
+
+ u = binalloc(&parsebin, sizeof *u, 1);
+ ok = copysave(box, m, i, vs, u);
+ *uidtl = u;
+ uidtl = &u->next;
+ return ok;
+}
+
+
+/*
+ * first spool the input into a temorary file,
+ * and massage the input in the process.
+ * then save to real box.
+ */
+/*
+ * copy from bin to bout,
+ * map "\r\n" to "\n" and
+ * return the number of bytes in the mapped file.
+ *
+ * exactly n bytes must be read from the input,
+ * unless an input error occurs.
+ */
+static long
+spool(Biobuf *bout, Biobuf *bin, long n)
+{
+ int c;
+
+ while(n > 0){
+ c = Bgetc(bin);
+ n--;
+ if(c == '\r' && n-- > 0){
+ c = Bgetc(bin);
+ if(c != '\n')
+ Bputc(bout, '\r');
+ }
+ if(c < 0)
+ return -1;
+ if(Bputc(bout, c) < 0)
+ return -1;
+ }
+ if(Bflush(bout) < 0)
+ return -1;
+ return Boffset(bout);
+}
+
+int
+appendsave(char *mbox, int flags, char *head, Biobuf *b, long n, Uidplus *u)
+{
+ int fd, ok;
+ Biobuf btmp;
+
+ fd = imaptmp();
+ if(fd < 0)
+ return 0;
+ Bprint(&bout, "+ Ready for literal data\r\n");
+ if(Bflush(&bout) < 0)
+ writeerr();
+ Binit(&btmp, fd, OWRITE);
+ n = spool(&btmp, b, n);
+ Bterm(&btmp);
+ if(n < 0){
+ close(fd);
+ return 0;
+ }
+
+ seek(fd, 0, 0);
+ Binit(&btmp, fd, OREAD);
+ ok = savemsg(mbox, flags, head, strlen(head), &btmp, n, u);
+ Bterm(&btmp);
+ close(fd);
+ return ok;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/csquery.c
@@ -1,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+
+/*
+ * query the connection server
+ */
+char*
+csquery(char *attr, char *val, char *rattr)
+{
+ char token[64 + 4], buf[256], *p, *sp;
+ int fd, n;
+
+ if(val == nil || val[0] == 0)
+ return nil;
+ fd = open("/net/cs", ORDWR);
+ if(fd < 0)
+ return nil;
+ fprint(fd, "!%s=%s", attr, val);
+ seek(fd, 0, 0);
+ snprint(token, sizeof token, "%s=", rattr);
+ for(;;){
+ n = read(fd, buf, sizeof buf - 1);
+ if(n <= 0)
+ break;
+ buf[n] = 0;
+ p = strstr(buf, token);
+ if(p != nil && (p == buf || p[-1] == 0)){
+ close(fd);
+ sp = strchr(p, ' ');
+ if(sp)
+ *sp = 0;
+ p = strchr(p, '=');
+ if(p == nil)
+ return nil;
+ return strdup(p + 1);
+ }
+ }
+ close(fd);
+ return nil;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/date.c
@@ -1,0 +1,263 @@
+#include "imap4d.h"
+
+static char *wdayname[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static char *monname[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+/*
+ * zone : [A-Za-z][A-Za-z][A-Za-z] some time zone names
+ * | [A-IK-Z] military time; rfc1123 says the rfc822 spec is wrong.
+ * | "UT" universal time
+ * | [+-][0-9][0-9][0-9][0-9]
+ * zones is the rfc-822 list of time zone names
+ */
+static Namedint zones[] =
+{
+ {"A", -1 * 3600},
+ {"B", -2 * 3600},
+ {"C", -3 * 3600},
+ {"CDT", -5 * 3600},
+ {"CST", -6 * 3600},
+ {"D", -4 * 3600},
+ {"E", -5 * 3600},
+ {"EDT", -4 * 3600},
+ {"EST", -5 * 3600},
+ {"F", -6 * 3600},
+ {"G", -7 * 3600},
+ {"GMT", 0},
+ {"H", -8 * 3600},
+ {"I", -9 * 3600},
+ {"K", -10 * 3600},
+ {"L", -11 * 3600},
+ {"M", -12 * 3600},
+ {"MDT", -6 * 3600},
+ {"MST", -7 * 3600},
+ {"N", +1 * 3600},
+ {"O", +2 * 3600},
+ {"P", +3 * 3600},
+ {"PDT", -7 * 3600},
+ {"PST", -8 * 3600},
+ {"Q", +4 * 3600},
+ {"R", +5 * 3600},
+ {"S", +6 * 3600},
+ {"T", +7 * 3600},
+ {"U", +8 * 3600},
+ {"UT", 0},
+ {"V", +9 * 3600},
+ {"W", +10 * 3600},
+ {"X", +11 * 3600},
+ {"Y", +12 * 3600},
+ {"Z", 0},
+};
+
+static void
+zone2tm(Tm *tm, char *s)
+{
+ int i;
+ Tm aux, *atm;
+
+ if(*s == '+' || *s == '-'){
+ i = strtol(s, &s, 10);
+ tm->tzoff = (i/100)*3600 + i%100;
+ strncpy(tm->zone, "", 4);
+ return;
+ }
+
+ /*
+ * look it up in the standard rfc822 table
+ */
+ strncpy(tm->zone, s, 3);
+ tm->zone[3] = 0;
+ tm->tzoff = 0;
+ for(i = 0; i < nelem(zones); i++){
+ if(cistrcmp(zones[i].name, s) == 0){
+ tm->tzoff = zones[i].v;
+ return;
+ }
+ }
+
+ /*
+ * one last try: look it up in the current local timezone
+ * probe a couple of times to get daylight/standard time change.
+ */
+ aux = *tm;
+ memset(aux.zone, 0, 4);
+ aux.hour--;
+ for(i = 0; i < 2; i++){
+ atm = localtime(tm2sec(&aux));
+ if(cistrcmp(tm->zone, atm->zone) == 0){
+ tm->tzoff = atm->tzoff;
+ return;
+ }
+ aux.hour++;
+ }
+
+ strncpy(tm->zone, "GMT", 4);
+ tm->tzoff = 0;
+}
+
+/*
+ * hh[:mm[:ss]]
+ */
+static void
+time2tm(Tm *tm, char *s)
+{
+ tm->hour = strtoul(s, &s, 10);
+ if(*s++ != ':')
+ return;
+ tm->min = strtoul(s, &s, 10);
+ if(*s++ != ':')
+ return;
+ tm->sec = strtoul(s, &s, 10);
+}
+
+static int
+dateindex(char *d, char **tab, int n)
+{
+ int i;
+
+ for(i = 0; i < n; i++)
+ if(cistrcmp(d, tab[i]) == 0)
+ return i;
+ return -1;
+}
+
+int
+imap4date(Tm *tm, char *date)
+{
+ char *flds[4];
+
+ if(getfields(date, flds, 3, 0, "-") != 3)
+ return 0;
+
+ tm->mday = strtol(flds[0], nil, 10);
+ tm->mon = dateindex(flds[1], monname, 12);
+ tm->year = strtol(flds[2], nil, 10) - 1900;
+ return 1;
+}
+
+/*
+ * parse imap4 dates
+ */
+ulong
+imap4datetime(char *date)
+{
+ char *flds[4], *sflds[4];
+ ulong t;
+ Tm tm;
+
+ if(getfields(date, flds, 4, 0, " ") != 3)
+ return ~0;
+
+ if(!imap4date(&tm, flds[0]))
+ return ~0;
+
+ if(getfields(flds[1], sflds, 3, 0, ":") != 3)
+ return ~0;
+
+ tm.hour = strtol(sflds[0], nil, 10);
+ tm.min = strtol(sflds[1], nil, 10);
+ tm.sec = strtol(sflds[2], nil, 10);
+
+ strcpy(tm.zone, "GMT");
+ tm.yday = 0;
+ t = tm2sec(&tm);
+ zone2tm(&tm, flds[2]);
+ t -= tm.tzoff;
+ return t;
+}
+
+/*
+ * parse dates of formats
+ * [Wkd[,]] DD Mon YYYY HH:MM:SS zone
+ * [Wkd] Mon ( D|DD) HH:MM:SS zone YYYY
+ * plus anything similar
+ * return nil for a failure
+ */
+Tm*
+date2tm(Tm *tm, char *date)
+{
+ char *flds[7], *s, dstr[64];
+ int n;
+ Tm gmt, *atm;
+
+ /*
+ * default date is Thu Jan 1 00:00:00 GMT 1970
+ */
+ tm->wday = 4;
+ tm->mday = 1;
+ tm->mon = 1;
+ tm->hour = 0;
+ tm->min = 0;
+ tm->sec = 0;
+ tm->year = 70;
+ strcpy(tm->zone, "GMT");
+ tm->tzoff = 0;
+
+ strncpy(dstr, date, sizeof dstr);
+ dstr[sizeof dstr - 1] = 0;
+ n = tokenize(dstr, flds, 7);
+ if(n != 6 && n != 5)
+ return nil;
+
+ if(n == 5){
+ for(n = 5; n >= 1; n--)
+ flds[n] = flds[n - 1];
+ n = 5;
+ }else{
+ /*
+ * Wday[,]
+ */
+ s = strchr(flds[0], ',');
+ if(s != nil)
+ *s = 0;
+ tm->wday = dateindex(flds[0], wdayname, 7);
+ if(tm->wday < 0)
+ return nil;
+ }
+
+ /*
+ * check for the two major formats:
+ * Month first or day first
+ */
+ tm->mon = dateindex(flds[1], monname, 12);
+ if(tm->mon >= 0){
+ tm->mday = strtoul(flds[2], nil, 10);
+ time2tm(tm, flds[3]);
+ zone2tm(tm, flds[4]);
+ tm->year = strtoul(flds[5], nil, 10);
+ if(strlen(flds[5]) > 2)
+ tm->year -= 1900;
+ }else{
+ tm->mday = strtoul(flds[1], nil, 10);
+ tm->mon = dateindex(flds[2], monname, 12);
+ tm->year = strtoul(flds[3], nil, 10);
+ if(strlen(flds[3]) > 2)
+ tm->year -= 1900;
+ time2tm(tm, flds[4]);
+ zone2tm(tm, flds[5]);
+ }
+
+ if(n == 5){
+ gmt = *tm;
+ strncpy(gmt.zone, "", 4);
+ gmt.tzoff = 0;
+ atm = gmtime(tm2sec(&gmt));
+ tm->wday = atm->wday;
+ }else{
+ /*
+ * Wday[,]
+ */
+ s = strchr(flds[0], ',');
+ if(s != nil)
+ *s = 0;
+ tm->wday = dateindex(flds[0], wdayname, 7);
+ if(tm->wday < 0)
+ return nil;
+ }
+ return tm;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/debug.c
@@ -1,0 +1,30 @@
+#include "imap4d.h"
+
+char logfile[28] = "imap4";
+
+void
+debuglog(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ if(debug == 0)
+ return;
+ va_start(arg, fmt);
+ vseprint(buf, buf + sizeof buf, fmt, arg);
+ va_end(arg);
+ syslog(0, logfile, "[%s:%d] %s", username, getpid(), buf);
+}
+
+void
+ilog(char *fmt, ...)
+{
+ char buf[1024];
+ va_list arg;
+
+ va_start(arg, fmt);
+ vseprint(buf, buf + sizeof buf, fmt, arg);
+ va_end(arg);
+ syslog(0, logfile, "[%s:%d] %s", username, getpid(), buf);
+
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/fetch.c
@@ -1,0 +1,559 @@
+#include "imap4d.h"
+
+char *fetchpartnames[FPmax] =
+{
+ "",
+ "HEADER",
+ "HEADER.FIELDS",
+ "HEADER.FIELDS.NOT",
+ "MIME",
+ "TEXT",
+};
+
+/*
+ * implicitly set the \seen flag. done in a separate pass
+ * so the .imp file doesn't need to be open while the
+ * messages are sent to the client.
+ */
+int
+fetchseen(Box *box, Msg *m, int uids, void *vf)
+{
+ Fetch *f;
+
+ if(m->expunged)
+ return uids;
+ for(f = vf; f != nil; f = f->next){
+ switch(f->op){
+ case Frfc822:
+ case Frfc822text:
+ case Fbodysect:
+ msgseen(box, m);
+ return 1;
+ }
+ }
+ return 1;
+}
+
+/*
+ * fetch messages
+ *
+ * imap4 body[] requests get translated to upas/fs files as follows
+ * body[id.header] == id/rawheader file + extra \r\n
+ * body[id.text] == id/rawbody
+ * body[id.mime] == id/mimeheader + extra \r\n
+ * body[id] === body[id.header] + body[id.text]
+*/
+int
+fetchmsg(Box *, Msg *m, int uids, void *vf)
+{
+ char *sep;
+ Fetch *f;
+ Tm tm;
+
+ if(m->expunged)
+ return uids;
+ for(f = vf; f != nil; f = f->next)
+ switch(f->op){
+ case Fflags:
+ break;
+ case Fuid:
+ break;
+ case Finternaldate:
+ case Fenvelope:
+ case Frfc822:
+ case Frfc822head:
+ case Frfc822text:
+ case Frfc822size:
+ case Fbodysect:
+ case Fbodypeek:
+ case Fbody:
+ case Fbodystruct:
+ if(!msgstruct(m, 1)){
+ msgdead(m);
+ return uids;
+ }
+ break;
+ default:
+ bye("bad implementation of fetch");
+ return 0;
+ }
+ if(m->expunged)
+ return uids;
+ if(vf == 0)
+ return 1;
+
+ /*
+ * note: it is allowed to send back the responses one at a time
+ * rather than all together. this is exploited to send flags elsewhere.
+ */
+ Bprint(&bout, "* %ud FETCH (", m->seq);
+ sep = "";
+ if(uids){
+ Bprint(&bout, "UID %ud", m->uid);
+ sep = " ";
+ }
+ for(f = vf; f != nil; f = f->next){
+ switch(f->op){
+ default:
+ bye("bad implementation of fetch");
+ break;
+ case Fflags:
+ Bprint(&bout, "%sFLAGS (", sep);
+ writeflags(&bout, m, 1);
+ Bprint(&bout, ")");
+ break;
+ case Fuid:
+ if(uids)
+ continue;
+ Bprint(&bout, "%sUID %ud", sep, m->uid);
+ break;
+ case Fenvelope:
+ Bprint(&bout, "%sENVELOPE ", sep);
+ fetchenvelope(m);
+ break;
+ case Finternaldate:
+ Bprint(&bout, "%sINTERNALDATE %#D", sep, date2tm(&tm, m->unixdate));
+ break;
+ case Fbody:
+ Bprint(&bout, "%sBODY ", sep);
+ fetchbodystruct(m, &m->head, 0);
+ break;
+ case Fbodystruct:
+ Bprint(&bout, "%sBODYSTRUCTURE ", sep);
+ fetchbodystruct(m, &m->head, 1);
+ break;
+ case Frfc822size:
+ Bprint(&bout, "%sRFC822.SIZE %ud", sep, msgsize(m));
+ break;
+ case Frfc822:
+ f->part = FPall;
+ Bprint(&bout, "%sRFC822", sep);
+ fetchbody(m, f);
+ break;
+ case Frfc822head:
+ f->part = FPhead;
+ Bprint(&bout, "%sRFC822.HEADER", sep);
+ fetchbody(m, f);
+ break;
+ case Frfc822text:
+ f->part = FPtext;
+ Bprint(&bout, "%sRFC822.TEXT", sep);
+ fetchbody(m, f);
+ break;
+ case Fbodysect:
+ case Fbodypeek:
+ Bprint(&bout, "%sBODY", sep);
+ fetchbody(fetchsect(m, f), f);
+ break;
+ }
+ sep = " ";
+ }
+ Bprint(&bout, ")\r\n");
+
+ return 1;
+}
+
+/*
+ * print out section, part, headers;
+ * find and return message section
+ */
+Msg *
+fetchsect(Msg *m, Fetch *f)
+{
+ Bputc(&bout, '[');
+ Bnlist(&bout, f->sect, ".");
+ if(f->part != FPall){
+ if(f->sect != nil)
+ Bputc(&bout, '.');
+ Bprint(&bout, "%s", fetchpartnames[f->part]);
+ if(f->hdrs != nil){
+ Bprint(&bout, " (");
+ Bslist(&bout, f->hdrs, " ");
+ Bputc(&bout, ')');
+ }
+ }
+ Bprint(&bout, "]");
+ return findmsgsect(m, f->sect);
+}
+
+/*
+ * actually return the body pieces
+ */
+void
+fetchbody(Msg *m, Fetch *f)
+{
+ char *s, *t, *e, buf[Bufsize + 2];
+ uint n, start, stop, pos;
+ int fd, nn;
+ Pair p;
+
+ if(m == nil){
+ fetchbodystr(f, "", 0);
+ return;
+ }
+ switch(f->part){
+ case FPheadfields:
+ case FPheadfieldsnot:
+ n = m->head.size + 3;
+ s = emalloc(n);
+ n = selectfields(s, n, m->head.buf, f->hdrs, f->part == FPheadfields);
+ fetchbodystr(f, s, n);
+ free(s);
+ return;
+ case FPhead:
+//ilog("head.size %d", m->head.size);
+ fetchbodystr(f, m->head.buf, m->head.size);
+ return;
+ case FPmime:
+ fetchbodystr(f, m->mime.buf, m->mime.size);
+ return;
+ case FPall:
+ fd = msgfile(m, "rawbody");
+ if(fd < 0){
+ msgdead(m);
+ fetchbodystr(f, "", 0);
+ return;
+ }
+ p = fetchbodypart(f, msgsize(m));
+ start = p.start;
+//ilog("head.size %d", m->head.size);
+ if(start < m->head.size){
+ stop = p.stop;
+ if(stop > m->head.size)
+ stop = m->head.size;
+//ilog("fetch header %ld.%ld (%ld)", start, stop, m->head.size);
+ Bwrite(&bout, m->head.buf + start, stop - start);
+ start = 0;
+ stop = p.stop;
+ if(stop <= m->head.size){
+ close(fd);
+ return;
+ }
+ }else
+ start -= m->head.size;
+ stop = p.stop - m->head.size;
+ break;
+ case FPtext:
+ fd = msgfile(m, "rawbody");
+ if(fd < 0){
+ msgdead(m);
+ fetchbodystr(f, "", 0);
+ return;
+ }
+ p = fetchbodypart(f, m->size);
+ start = p.start;
+ stop = p.stop;
+ break;
+ default:
+ fetchbodystr(f, "", 0);
+ return;
+ }
+
+ /*
+ * read in each block, convert \n without \r to \r\n.
+ * this means partial fetch requires fetching everything
+ * through stop, since we don't know how many \r's will be added
+ */
+ buf[0] = ' ';
+ for(pos = 0; pos < stop; ){
+ n = Bufsize;
+ if(n > stop - pos)
+ n = stop - pos;
+ n = read(fd, &buf[1], n);
+//ilog("read %ld at %d stop %ld\n", n, pos, stop);
+ if(n <= 0){
+ilog("must fill %ld bytes\n", stop - pos);
+fprint(2, "must fill %d bytes\n", stop - pos);
+ fetchbodyfill(stop - pos);
+ break;
+ }
+ e = &buf[n + 1];
+ *e = 0;
+ for(s = &buf[1]; s < e && pos < stop; s = t + 1){
+ t = memchr(s, '\n', e - s);
+ if(t == nil)
+ t = e;
+ n = t - s;
+ if(pos < start){
+ if(pos + n <= start){
+ s = t;
+ pos += n;
+ }else{
+ s += start - pos;
+ pos = start;
+ }
+ n = t - s;
+ }
+ nn = n;
+ if(pos + nn > stop)
+ nn = stop - pos;
+ if(Bwrite(&bout, s, nn) != nn)
+ writeerr();
+//ilog("w %ld at %ld->%ld stop %ld\n", nn, pos, pos + nn, stop);
+ pos += n;
+ if(*t == '\n'){
+ if(t[-1] != '\r'){
+ if(pos >= start && pos < stop)
+ Bputc(&bout, '\r');
+ pos++;
+ }
+ if(pos >= start && pos < stop)
+ Bputc(&bout, '\n');
+ pos++;
+ }
+ }
+ buf[0] = e[-1];
+ }
+ close(fd);
+}
+
+/*
+ * resolve the actual bounds of any partial fetch,
+ * and print out the bounds & size of string returned
+ */
+Pair
+fetchbodypart(Fetch *f, uint size)
+{
+ uint start, stop;
+ Pair p;
+
+ start = 0;
+ stop = size;
+ if(f->partial){
+ start = f->start;
+ if(start > size)
+ start = size;
+ stop = start + f->size;
+ if(stop > size)
+ stop = size;
+ Bprint(&bout, "<%ud>", start);
+ }
+ Bprint(&bout, " {%ud}\r\n", stop - start);
+ p.start = start;
+ p.stop = stop;
+ return p;
+}
+
+/*
+ * something went wrong fetching data
+ * produce fill bytes for what we've committed to produce
+ */
+void
+fetchbodyfill(uint n)
+{
+ while(n-- > 0)
+ if(Bputc(&bout, ' ') < 0)
+ writeerr();
+}
+
+/*
+ * return a simple string
+ */
+void
+fetchbodystr(Fetch *f, char *buf, uint size)
+{
+ Pair p;
+
+ p = fetchbodypart(f, size);
+ Bwrite(&bout, buf + p.start, p.stop - p.start);
+}
+
+char*
+printnlist(Nlist *sect)
+{
+ static char buf[100];
+ char *p;
+
+ for(p = buf; sect; sect = sect->next){
+ p += sprint(p, "%ud", sect->n);
+ if(sect->next)
+ *p++ = '.';
+ }
+ *p = 0;
+ return buf;
+}
+
+/*
+ * find the numbered sub-part of the message
+ */
+Msg*
+findmsgsect(Msg *m, Nlist *sect)
+{
+ uint id;
+
+ for(; sect != nil; sect = sect->next){
+ id = sect->n;
+ for(m = m->kids; m != nil; m = m->next)
+ if(m->id == id)
+ break;
+ if(m == nil)
+ return nil;
+ }
+ return m;
+}
+
+void
+fetchenvelope(Msg *m)
+{
+ Tm tm;
+
+ Bprint(&bout, "(%#D %Z ", date2tm(&tm, m->info[Idate]), m->info[Isubject]);
+ Bimapaddr(&bout, m->from);
+ Bputc(&bout, ' ');
+ Bimapaddr(&bout, m->sender);
+ Bputc(&bout, ' ');
+ Bimapaddr(&bout, m->replyto);
+ Bputc(&bout, ' ');
+ Bimapaddr(&bout, m->to);
+ Bputc(&bout, ' ');
+ Bimapaddr(&bout, m->cc);
+ Bputc(&bout, ' ');
+ Bimapaddr(&bout, m->bcc);
+ Bprint(&bout, " %Z %Z)", m->info[Iinreplyto], m->info[Imessageid]);
+}
+
+static int
+Bmime(Biobuf *b, Mimehdr *mh)
+{
+ char *sep;
+
+ if(mh == nil)
+ return Bprint(b, "NIL");
+ sep = "(";
+ for(; mh != nil; mh = mh->next){
+ Bprint(b, "%s%Z %Z", sep, mh->s, mh->t);
+ sep = " ";
+ }
+ Bputc(b, ')');
+ return 0;
+}
+
+static void
+fetchext(Biobuf *b, Header *h)
+{
+ Bputc(b, ' ');
+ if(h->disposition != nil){
+ Bprint(b, "(%Z ", h->disposition->s);
+ Bmime(b, h->disposition->next);
+ Bputc(b, ')');
+ }else
+ Bprint(b, "NIL");
+ Bputc(b, ' ');
+ if(h->language != nil){
+ if(h->language->next != nil)
+ Bmime(b, h->language->next);
+ else
+ Bprint(&bout, "%Z", h->language->s);
+ }else
+ Bprint(b, "NIL");
+}
+
+void
+fetchbodystruct(Msg *m, Header *h, int extensions)
+{
+ uint len;
+ Msg *k;
+
+ if(msgismulti(h)){
+ Bputc(&bout, '(');
+ for(k = m->kids; k != nil; k = k->next)
+ fetchbodystruct(k, &k->mime, extensions);
+ if(m->kids)
+ Bputc(&bout, ' ');
+ Bprint(&bout, "%Z", h->type->t);
+ if(extensions){
+ Bputc(&bout, ' ');
+ Bmime(&bout, h->type->next);
+ fetchext(&bout, h);
+ }
+
+ Bputc(&bout, ')');
+ return;
+ }
+
+ Bputc(&bout, '(');
+ if(h->type != nil){
+ Bprint(&bout, "%Z %Z ", h->type->s, h->type->t);
+ Bmime(&bout, h->type->next);
+ }else
+ Bprint(&bout, "\"text\" \"plain\" NIL");
+
+ Bputc(&bout, ' ');
+ if(h->id != nil)
+ Bprint(&bout, "%Z", h->id->s);
+ else
+ Bprint(&bout, "NIL");
+
+ Bputc(&bout, ' ');
+ if(h->description != nil)
+ Bprint(&bout, "%Z", h->description->s);
+ else
+ Bprint(&bout, "NIL");
+
+ Bputc(&bout, ' ');
+ if(h->encoding != nil)
+ Bprint(&bout, "%Z", h->encoding->s);
+ else
+ Bprint(&bout, "NIL");
+
+ /*
+ * this is so strange: return lengths for a body[text] response,
+ * except in the case of a multipart message, when return lengths for a body[] response
+ */
+ len = m->size;
+ if(h == &m->mime)
+ len += m->head.size;
+ Bprint(&bout, " %ud", len);
+
+ len = m->lines;
+ if(h == &m->mime)
+ len += m->head.lines;
+
+ if(h->type == nil || cistrcmp(h->type->s, "text") == 0)
+ Bprint(&bout, " %ud", len);
+ else if(msgis822(h)){
+ Bputc(&bout, ' ');
+ k = m;
+ if(h != &m->mime)
+ k = m->kids;
+ if(k == nil)
+ Bprint(&bout, "(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) (\"text\" \"plain\" NIL NIL NIL NIL 0 0) 0");
+ else{
+ fetchenvelope(k);
+ Bputc(&bout, ' ');
+ fetchbodystruct(k, &k->head, extensions);
+ Bprint(&bout, " %ud", len);
+ }
+ }
+
+ if(extensions){
+ Bprint(&bout, " NIL"); /* md5 */
+ fetchext(&bout, h);
+ }
+ Bputc(&bout, ')');
+}
+
+/*
+ * print a list of addresses;
+ * each address is printed as '(' personalname atdomainlist mboxname hostname ')'
+ * the atdomainlist is always NIL
+ */
+int
+Bimapaddr(Biobuf *b, Maddr *a)
+{
+ char *host, *sep;
+
+ if(a == nil)
+ return Bprint(b, "NIL");
+ Bputc(b, '(');
+ sep = "";
+ for(; a != nil; a = a->next){
+ /*
+ * can't send NIL as hostname, since that is code for a group
+ */
+ host = a->host? a->host: "";
+ Bprint(b, "%s(%Z NIL %Z %Z)", sep, a->personal, a->box, host);
+ sep = " ";
+ }
+ return Bputc(b, ')');
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/fns.h
@@ -1,0 +1,138 @@
+/*
+ * sorted by Edit 4,/^$/|sort -bd +1
+ */
+int Bimapaddr(Biobuf*, Maddr*);
+int Bimapmimeparams(Biobuf*, Mimehdr*);
+int Bnlist(Biobuf*, Nlist*, char*);
+int Bslist(Biobuf*, Slist*, char*);
+int Dfmt(Fmt*);
+int δfmt(Fmt*);
+int Ffmt(Fmt*);
+int Xfmt(Fmt*);
+int Zfmt(Fmt*);
+int appendsave(char*, int , char*, Biobuf*, long, Uidplus*);
+void bye(char*, ...);
+int cdcreate(char*, char*, int, ulong);
+Dir *cddirstat(char*, char*);
+int cddirwstat(char*, char*, Dir*);
+int cdexists(char*, char*);
+int cdopen(char*, char*, int);
+int cdremove(char*, char*);
+Mblock *checkbox(Box*, int );
+void closebox(Box*, int opened);
+void closeimp(Box*, Mblock*);
+int copycheck(Box*, Msg*, int uids, void*);
+int copysaveu(Box*, Msg*, int uids, void*);
+char *cramauth(void);
+char *crauth(char*, char*);
+int creatembox(char*);
+Tm *date2tm(Tm*, char*);
+void debuglog(char*, ...);
+char *decfs(char*, int, char*);
+char *decmutf7(char*, int, char*);
+int deletemsg(Box *, Msgset*);
+void *emalloc(ulong);
+int emptyimp(char*);
+void enableforwarding(void);
+char *encfs(char*, int, char*);
+char *encmutf7(char*, int nout, char*);
+void *erealloc(void*, ulong);
+char *estrdup(char*);
+int expungemsgs(Box*, int);
+void *ezmalloc(ulong);
+void fetchbody(Msg*, Fetch*);
+void fetchbodyfill(uint);
+Pair fetchbodypart(Fetch*, uint);
+void fetchbodystr(Fetch*, char*, uint);
+void fetchbodystruct(Msg*, Header*, int);
+void fetchenvelope(Msg*);
+int fetchmsg(Box*, Msg *, int, void*);
+Msg *fetchsect(Msg*, Fetch*);
+int fetchseen(Box*, Msg*, int, void*);
+void fetchstructext(Header*);
+Msg *findmsgsect(Msg*, Nlist*);
+int formsgs(Box*, Msgset*, uint, int, int (*)(Box*, Msg*, int, void*), void*);
+int fqid(int, Qid*);
+void freemsg(Box*, Msg*);
+vlong getquota(void);
+void ilog(char*, ...);
+int imap4date(Tm*, char*);
+ulong imap4datetime(char*);
+int imaptmp(void);
+char *impname(char*);
+int inmsgset(Msgset*, uint);
+int isdotdot(char*);
+int isprefix(char*, char*);
+int issuffix(char*, char*);
+int listboxes(char*, char*, char*);
+char *loginauth(char*, char*);
+int lsubboxes(char*, char*, char*);
+char *maddrstr(Maddr*);
+uint mapflag(char*);
+uint mapint(Namedint*, char*);
+int mblocked(void);
+void mblockrefresh(Mblock*);
+Mblock *mblock(void);
+char *mboxname(char*);
+void mbunlock(Mblock*);
+Fetch *mkfetch(int, Fetch*);
+Slist *mkslist(char*, Slist*);
+Store *mkstore(int, int, int);
+int movebox(char*, char*);
+void msgdead(Msg*);
+int msgfile(Msg*, char*);
+int msginfo(Msg*);
+int msgis822(Header*);
+int msgismulti(Header*);
+int msgseen(Box*, Msg*);
+uint msgsize(Msg*);
+int msgstruct(Msg*, int top);
+char *mutf7str(char*);
+int mychdir(char*);
+int okmbox(char*);
+Box *openbox(char*, char*, int);
+int openlocked(char*, char*, int);
+void parseerr(char*);
+int parseimp(Biobuf*, Box*);
+char *passauth(char*, char*);
+char *plainauth(char*);
+char *readfile(int);
+int removembox(char*);
+int renamebox(char*, char*, int);
+void resetcurdir(void);
+Fetch *revfetch(Fetch*);
+Slist *revslist(Slist*);
+int searchmsg(Msg*, Search*, int);
+int searchld(Search*);
+long selectfields(char*, long n, char*, Slist*, int);
+void sendflags(Box*, int uids);
+void setflags(Box*, Msg*, int f);
+void setname(char*, ...);
+void setupuser(AuthInfo*);
+int storemsg(Box*, Msg*, int, void*);
+char *strmutf7(char*);
+void strrev(char*, char*);
+int subscribe(char*, int);
+int wrimp(Biobuf*, Box*);
+int appendimp(char*, char*, int, Uidplus*);
+void writeerr(void);
+void writeflags(Biobuf*, Msg*, int);
+
+void fstreeadd(Box*, Msg*);
+void fstreedelete(Box*, Msg*);
+Msg *fstreefind(Box*, int);
+int fstreecmp(Avl*, Avl*);
+
+#pragma varargck argpos bye 1
+#pragma varargck argpos debuglog 1
+#pragma varargck argpos imap4cmd 2
+#pragma varargck type "F" char*
+#pragma varargck type "D" Tm*
+#pragma varargck type "δ" Tm*
+#pragma varargck type "X" char*
+#pragma varargck type "Y" char*
+#pragma varargck type "Z" char*
+
+#define MK(t) ((t*)emalloc(sizeof(t)))
+#define MKZ(t) ((t*)ezmalloc(sizeof(t)))
+#define STRLEN(cs) (sizeof(cs)-1)
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/folder.c
@@ -1,0 +1,186 @@
+#include "imap4d.h"
+
+static Mblock mblck = {
+.fd = -1
+};
+
+static char curdir[Pathlen];
+
+void
+resetcurdir(void)
+{
+ curdir[0] = 0;
+}
+
+int
+mychdir(char *dir)
+{
+ if(strcmp(dir, curdir) == 0)
+ return 0;
+ if(dir[0] != '/' || strlen(dir) > Pathlen)
+ return -1;
+ strcpy(curdir, dir);
+ if(chdir(dir) < 0){
+ werrstr("mychdir failed: %r");
+ return -1;
+ }
+ return 0;
+}
+
+int
+cdcreate(char *dir, char *file, int mode, ulong perm)
+{
+ if(mychdir(dir) < 0)
+ return -1;
+ return create(file, mode, perm);
+}
+
+Dir*
+cddirstat(char *dir, char *file)
+{
+ if(mychdir(dir) < 0)
+ return nil;
+ return dirstat(file);
+}
+
+int
+cdexists(char *dir, char *file)
+{
+ Dir *d;
+
+ d = cddirstat(dir, file);
+ if(d == nil)
+ return 0;
+ free(d);
+ return 1;
+}
+
+int
+cddirwstat(char *dir, char *file, Dir *d)
+{
+ if(mychdir(dir) < 0)
+ return -1;
+ return dirwstat(file, d);
+}
+
+int
+cdopen(char *dir, char *file, int mode)
+{
+ if(mychdir(dir) < 0)
+ return -1;
+ return open(file, mode);
+}
+
+int
+cdremove(char *dir, char *file)
+{
+ if(mychdir(dir) < 0)
+ return -1;
+ return remove(file);
+}
+
+/*
+ * open the one true mail lock file
+ */
+Mblock*
+mblock(void)
+{
+ if(mblck.fd >= 0)
+ bye("mail lock deadlock");
+ mblck.fd = openlocked(mboxdir, "L.mbox", OREAD);
+ if(mblck.fd >= 0)
+ return &mblck;
+ ilog("mblock: %r");
+ return nil;
+}
+
+void
+mbunlock(Mblock *ml)
+{
+ if(ml != &mblck)
+ bye("bad mail unlock");
+ if(ml->fd < 0)
+ bye("mail unlock when not locked");
+ close(ml->fd);
+ ml->fd = -1;
+}
+
+void
+mblockrefresh(Mblock *ml)
+{
+ char buf[1];
+
+ seek(ml->fd, 0, 0);
+ read(ml->fd, buf, 1);
+}
+
+int
+mblocked(void)
+{
+ return mblck.fd >= 0;
+}
+
+char*
+impname(char *name)
+{
+ char *s, buf[Pathlen];
+ int n;
+
+ encfs(buf, sizeof buf, name);
+ n = strlen(buf) + STRLEN(".imp") + 1;
+ s = binalloc(&parsebin, n, 0);
+ if(s == nil)
+ return nil;
+ snprint(s, n, "%s.imp", name);
+ return s;
+}
+
+/*
+ * massage the mailbox name into something valid
+ * eliminates all .', and ..',s, redundatant and trailing /'s.
+ */
+char *
+mboxname(char *s)
+{
+ char *ss, *p;
+
+ ss = mutf7str(s);
+ if(ss == nil)
+ return nil;
+ cleanname(ss);
+ if(!okmbox(ss))
+ return nil;
+ p = binalloc(&parsebin, Pathlen, 0);
+ return encfs(p, Pathlen, ss);
+}
+
+char*
+strmutf7(char *s)
+{
+ char *m;
+ int n;
+
+ n = strlen(s) * Mutf7max + 1;
+ m = binalloc(&parsebin, n, 0);
+ if(m == nil)
+ return nil;
+ return encmutf7(m, n, s);
+}
+
+char*
+mutf7str(char *s)
+{
+ char *m;
+ int n;
+
+ /*
+ * n = strlen(s) * UTFmax / (2.67) + 1
+ * UTFmax / 2.67 == 3 / (8/3) == 9 / 8
+ */
+ n = strlen(s);
+ n = (n * 9 + 7) / 8 + 1;
+ m = binalloc(&parsebin, n, 0);
+ if(m == nil)
+ return nil;
+ return decmutf7(m, n, s);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/fsenc.c
@@ -1,0 +1,98 @@
+/*
+ * more regrettable, goofy processing
+ */
+#include "imap4d.h"
+
+char tab[0x7f] = {
+['\t'] '0',
+[' '] '#',
+['#'] '1',
+};
+
+char itab[0x7f] = {
+['0'] '\t',
+['#'] ' ',
+['1'] '#',
+};
+
+char*
+encfs(char *buf, int n, char *s)
+{
+ char *p, c;
+
+ if(!s){
+ *buf = 0;
+ return 0;
+ }
+ if(!cistrcmp(s, "inbox"))
+ s = "mbox";
+ for(p = buf; n > 0 && (c = *s++); n--){
+ if(tab[c & 0x7f]){
+ if(n < 1)
+ break;
+ if((c = tab[c]) == 0)
+ break;
+ *p++ = '#';
+ }
+ *p++ = c;
+ }
+ *p = 0;
+ return buf;
+}
+
+char*
+decfs(char *buf, int n, char *s)
+{
+ char *p, c;
+
+ if(!s){
+ *buf = 0;
+ return 0;
+ }
+ if(!cistrcmp(s, "mbox"))
+ s = "INBOX";
+ for(p = buf; n > 0 && (c = *s++); n--){
+ if(c == '#'){
+ c = *s++;
+ if((c = itab[c]) == 0)
+ break;
+ }
+ *p++ = c;
+ }
+ *p = 0;
+ return buf;
+}
+
+/*
+void
+usage(void)
+{
+ fprint(2, "usage: encfs [-d] ...\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char buf[1024];
+ int dflag;
+ char *(*f)(char*, int, char*);
+
+ dflag = 0;
+ ARGBEGIN{
+ case 'd':
+ dflag ^= 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+ f = encfs;
+ if(dflag)
+ f = decfs;
+ while(*argv){
+ f(buf, sizeof buf, *argv++);
+ print("%s\n", buf);
+ }
+ exits("");
+}
+*/
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/fstree.c
@@ -1,0 +1,58 @@
+#include "imap4d.h"
+
+int
+fstreecmp(Avl *va, Avl *vb)
+{
+ int i;
+ Fstree *a, *b;
+
+ a = (Fstree*)va;
+ b = (Fstree*)vb;
+ i = a->m->id - b->m->id;
+ if(i > 0)
+ i = 1;
+ if(i < 0)
+ i = -1;
+ return i;
+}
+
+Msg*
+fstreefind(Box *mb, int id)
+{
+ Msg m0;
+ Fstree t, *p;
+
+ memset(&t, 0, sizeof t);
+ m0.id = id;
+ t.m = &m0;
+ if(p = (Fstree*)avllookup(mb->fstree, &t))
+ return p->m;
+ return nil;
+}
+
+void
+fstreeadd(Box *mb, Msg *m)
+{
+ Avl *old;
+ Fstree *p;
+
+ assert(m->id > 0);
+ p = ezmalloc(sizeof *p);
+ p->m = m;
+ old = avlinsert(mb->fstree, p);
+ assert(old == 0);
+}
+
+void
+fstreedelete(Box *mb, Msg *m)
+{
+ Fstree t, *p;
+
+ memset(&t, 0, sizeof t);
+ t.m = m;
+ assert(m->id > 0);
+ p = (Fstree*)avldelete(mb->fstree, &t);
+ if(p == nil)
+ _assert("fstree delete fails");
+ free(p);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/imap4d.c
@@ -1,0 +1,2292 @@
+#include "imap4d.h"
+
+/*
+ * these should be in libraries
+ */
+char *csquery(char *attr, char *val, char *rattr);
+
+/*
+ * implemented:
+ * /lib/rfc/rfc3501 imap4rev1
+ * /lib/rfc/rfc2683 implementation advice
+ * /lib/rfc/rfc2342 namespace capability
+ * /lib/rfc/rfc2222 security protocols
+ * /lib/rfc/rfc1731 security protocols
+ * /lib/rfc/rfc2177 idle capability
+ * /lib/rfc/rfc2195 cram-md5 authentication
+ * /lib/rfc/rfc4315 uidplus capability
+ *
+ * not implemented, priority:
+ * /lib/rfc/rfc5256 sort and thread
+ * requires missing support from upas/fs.
+ *
+ * not implemented, low priority:
+ * /lib/rfc/rfc2088 literal+ capability
+ * /lib/rfc/rfc2221 login-referrals
+ * /lib/rfc/rfc2193 mailbox-referrals
+ * /lib/rfc/rfc1760 s/key authentication
+ *
+ */
+
+typedef struct Parsecmd Parsecmd;
+struct Parsecmd
+{
+ char *name;
+ void (*f)(char*, char*);
+};
+
+static void appendcmd(char*, char*);
+static void authenticatecmd(char*, char*);
+static void capabilitycmd(char*, char*);
+static void closecmd(char*, char*);
+static void copycmd(char*, char*);
+static void createcmd(char*, char*);
+static void deletecmd(char*, char*);
+static void expungecmd(char*, char*);
+static void fetchcmd(char*, char*);
+static void getquotacmd(char*, char*);
+static void getquotarootcmd(char*, char*);
+static void idlecmd(char*, char*);
+static void listcmd(char*, char*);
+static void logincmd(char*, char*);
+static void logoutcmd(char*, char*);
+static void namespacecmd(char*, char*);
+static void noopcmd(char*, char*);
+static void renamecmd(char*, char*);
+static void searchcmd(char*, char*);
+static void selectcmd(char*, char*);
+static void setquotacmd(char*, char*);
+static void statuscmd(char*, char*);
+static void storecmd(char*, char*);
+static void subscribecmd(char*, char*);
+static void uidcmd(char*, char*);
+static void unsubscribecmd(char*, char*);
+static void xdebugcmd(char*, char*);
+static void copyucmd(char*, char*, int);
+static void fetchucmd(char*, char*, int);
+static void searchucmd(char*, char*, int);
+static void storeucmd(char*, char*, int);
+
+static void imap4(int);
+static void status(int expungeable, int uids);
+static void cleaner(void);
+static void check(void);
+static int catcher(void*, char*);
+
+static Search *searchkey(int first);
+static Search *searchkeys(int first, Search *tail);
+static char *astring(void);
+static char *atomstring(char *disallowed, char *initial);
+static char *atom(void);
+static void clearcmd(void);
+static char *command(void);
+static void crnl(void);
+static Fetch *fetchatt(char *s, Fetch *f);
+static Fetch *fetchwhat(void);
+static int flaglist(void);
+static int flags(void);
+static int getc(void);
+static char *listmbox(void);
+static char *literal(void);
+static uint litlen(void);
+static Msgset *msgset(int);
+static void mustbe(int c);
+static uint number(int nonzero);
+static int peekc(void);
+static char *quoted(void);
+static void secttext(Fetch *, int);
+static uint seqno(void);
+static Store *storewhat(void);
+static char *tag(void);
+static uint uidno(void);
+static void ungetc(void);
+
+static Parsecmd Snonauthed[] =
+{
+ {"capability", capabilitycmd},
+ {"logout", logoutcmd},
+ {"noop", noopcmd},
+ {"x-exit", logoutcmd},
+
+ {"authenticate", authenticatecmd},
+ {"login", logincmd},
+
+ nil
+};
+
+static Parsecmd Sauthed[] =
+{
+ {"capability", capabilitycmd},
+ {"logout", logoutcmd},
+ {"noop", noopcmd},
+ {"x-exit", logoutcmd},
+ {"xdebug", xdebugcmd},
+
+ {"append", appendcmd},
+ {"create", createcmd},
+ {"delete", deletecmd},
+ {"examine", selectcmd},
+ {"select", selectcmd},
+ {"idle", idlecmd},
+ {"list", listcmd},
+ {"lsub", listcmd},
+ {"namespace", namespacecmd},
+ {"rename", renamecmd},
+ {"setquota", setquotacmd},
+ {"getquota", getquotacmd},
+ {"getquotaroot", getquotarootcmd},
+ {"status", statuscmd},
+ {"subscribe", subscribecmd},
+ {"unsubscribe", unsubscribecmd},
+
+ nil
+};
+
+static Parsecmd Sselected[] =
+{
+ {"capability", capabilitycmd},
+ {"xdebug", xdebugcmd},
+ {"logout", logoutcmd},
+ {"x-exit", logoutcmd},
+ {"noop", noopcmd},
+
+ {"append", appendcmd},
+ {"create", createcmd},
+ {"delete", deletecmd},
+ {"examine", selectcmd},
+ {"select", selectcmd},
+ {"idle", idlecmd},
+ {"list", listcmd},
+ {"lsub", listcmd},
+ {"namespace", namespacecmd},
+ {"rename", renamecmd},
+ {"status", statuscmd},
+ {"subscribe", subscribecmd},
+ {"unsubscribe", unsubscribecmd},
+
+ {"check", noopcmd},
+ {"close", closecmd},
+ {"copy", copycmd},
+ {"expunge", expungecmd},
+ {"fetch", fetchcmd},
+ {"search", searchcmd},
+ {"store", storecmd},
+ {"uid", uidcmd},
+
+ nil
+};
+
+static char *atomstop = "(){%*\"\\";
+static Parsecmd *imapstate;
+static jmp_buf parsejmp;
+static char *parsemsg;
+static int allowpass;
+static int allowcr;
+static int exiting;
+static QLock imaplock;
+static int idlepid = -1;
+
+Biobuf bout;
+Biobuf bin;
+char username[Userlen];
+char mboxdir[Pathlen];
+char *servername;
+char *site;
+char *remote;
+char *binupas;
+Box *selected;
+Bin *parsebin;
+int debug;
+Uidplus *uidlist;
+Uidplus **uidtl;
+
+void
+usage(void)
+{
+ fprint(2, "usage: upas/imap4d [-acpv] [-l logfile] [-b binupas] [-d site] [-r remotehost] [-s servername]\n");
+ bye("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ int preauth;
+
+ Binit(&bin, dup(0, -1), OREAD);
+ close(0);
+ Binit(&bout, 1, OWRITE);
+ quotefmtinstall();
+ fmtinstall('F', Ffmt);
+ fmtinstall('D', Dfmt); /* rfc822; # imap date %Z */
+ fmtinstall(L'δ', Dfmt); /* rfc822; # imap date %s */
+ fmtinstall('X', Xfmt);
+ fmtinstall('Y', Zfmt);
+ fmtinstall('Z', Zfmt);
+
+ preauth = 0;
+ allowpass = 0;
+ allowcr = 0;
+ ARGBEGIN{
+ case 'a':
+ preauth = 1;
+ break;
+ case 'b':
+ binupas = EARGF(usage());
+ break;
+ case 'c':
+ allowcr = 1;
+ break;
+ case 'd':
+ site = EARGF(usage());
+ break;
+ case 'l':
+ snprint(logfile, sizeof logfile, "%s", EARGF(usage()));
+ break;
+ case 'p':
+ allowpass = 1;
+ break;
+ case 'r':
+ remote = EARGF(usage());
+ break;
+ case 's':
+ servername = EARGF(usage());
+ break;
+ case 'v':
+ debug ^= 1;
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND
+
+ if(allowpass && allowcr){
+ fprint(2, "imap4d: -c and -p are mutually exclusive\n");
+ usage();
+ }
+
+ if(preauth)
+ setupuser(nil);
+
+ if(servername == nil){
+ servername = csquery("sys", sysname(), "dom");
+ if(servername == nil)
+ servername = sysname();
+ if(servername == nil){
+ fprint(2, "ip/imap4d can't find server name: %r\n");
+ bye("can't find system name");
+ }
+ }
+ if(site == nil)
+ site = getenv("site");
+ if(site == nil){
+ site = strchr(servername, '.');
+ if(site)
+ site++;
+ else
+ site = servername;
+ }
+
+ rfork(RFNOTEG|RFREND);
+
+ atnotify(catcher, 1);
+ qlock(&imaplock);
+ atexit(cleaner);
+ imap4(preauth);
+}
+
+static void
+imap4(int preauth)
+{
+ char *volatile tg;
+ char *volatile cmd;
+ Parsecmd *st;
+
+ if(preauth){
+ Bprint(&bout, "* preauth %s IMAP4rev1 server ready user %s authenticated\r\n", servername, username);
+ imapstate = Sauthed;
+ }else{
+ Bprint(&bout, "* OK %s IMAP4rev1 server ready\r\n", servername);
+ imapstate = Snonauthed;
+ }
+ if(Bflush(&bout) < 0)
+ writeerr();
+
+ tg = nil;
+ cmd = nil;
+ if(setjmp(parsejmp)){
+ if(tg == nil)
+ Bprint(&bout, "* bad empty command line: %s\r\n", parsemsg);
+ else if(cmd == nil)
+ Bprint(&bout, "%s BAD no command: %s\r\n", tg, parsemsg);
+ else
+ Bprint(&bout, "%s BAD %s %s\r\n", tg, cmd, parsemsg);
+ clearcmd();
+ if(Bflush(&bout) < 0)
+ writeerr();
+ binfree(&parsebin);
+ }
+ for(;;){
+ if(mblocked())
+ bye("internal error: mailbox lock held");
+ tg = nil;
+ cmd = nil;
+ tg = tag();
+ mustbe(' ');
+ cmd = atom();
+
+ /*
+ * note: outlook express is broken: it requires echoing the
+ * command as part of matching response
+ */
+ for(st = imapstate; st->name != nil; st++)
+ if(cistrcmp(cmd, st->name) == 0){
+ st->f(tg, cmd);
+ break;
+ }
+ if(st->name == nil){
+ clearcmd();
+ Bprint(&bout, "%s BAD %s illegal command\r\n", tg, cmd);
+ }
+
+ if(Bflush(&bout) < 0)
+ writeerr();
+ binfree(&parsebin);
+ }
+}
+
+void
+bye(char *fmt, ...)
+{
+ va_list arg;
+
+ va_start(arg, fmt);
+ Bprint(&bout, "* bye ");
+ Bvprint(&bout, fmt, arg);
+ Bprint(&bout, "\r\n");
+ Bflush(&bout);
+ exits(0);
+}
+
+void
+parseerr(char *msg)
+{
+ debuglog("parse error: %s", msg);
+ parsemsg = msg;
+ longjmp(parsejmp, 1);
+}
+
+/*
+ * an error occured while writing to the client
+ */
+void
+writeerr(void)
+{
+ cleaner();
+ _exits("connection closed");
+}
+
+static int
+catcher(void *, char *msg)
+{
+ if(strstr(msg, "closed pipe") != nil)
+ return 1;
+ return 0;
+}
+
+/*
+ * wipes out the idlecmd backgroung process if it is around.
+ * this can only be called if the current proc has qlocked imaplock.
+ * it must be the last piece of imap4d code executed.
+ */
+static void
+cleaner(void)
+{
+ int i;
+
+ debuglog("cleaner");
+ if(idlepid < 0)
+ return;
+ exiting = 1;
+ close(0);
+ close(1);
+ close(2);
+ close(bin.fid);
+ bin.fid = -1;
+ /*
+ * the other proc is either stuck in a read, a sleep,
+ * or is trying to lock imap4lock.
+ * get him out of it so he can exit cleanly
+ */
+ qunlock(&imaplock);
+ for(i = 0; i < 4; i++)
+ postnote(PNGROUP, getpid(), "die");
+}
+
+/*
+ * send any pending status updates to the client
+ * careful: shouldn't exit, because called by idle polling proc
+ *
+ * can't always send pending info
+ * in particular, can't send expunge info
+ * in response to a fetch, store, or search command.
+ *
+ * rfc2060 5.2: server must send mailbox size updates
+ * rfc2060 5.2: server may send flag updates
+ * rfc2060 5.5: servers prohibited from sending expunge while fetch, store, search in progress
+ * rfc2060 7: in selected state, server checks mailbox for new messages as part of every command
+ * sends untagged EXISTS and RECENT respsonses reflecting new size of the mailbox
+ * should also send appropriate untagged FETCH and EXPUNGE messages if another agent
+ * changes the state of any message flags or expunges any messages
+ * rfc2060 7.4.1 expunge server response must not be sent when no command is in progress,
+ * nor while responding to a fetch, stort, or search command (uid versions are ok)
+ * command only "in progress" after entirely parsed.
+ *
+ * strategy for third party deletion of messages or of a mailbox
+ *
+ * deletion of a selected mailbox => act like all message are expunged
+ * not strictly allowed by rfc2180, but close to method 3.2.
+ *
+ * renaming same as deletion
+ *
+ * copy
+ * reject iff a deleted message is in the request
+ *
+ * search, store, fetch operations on expunged messages
+ * ignore the expunged messages
+ * return tagged no if referenced
+ */
+static void
+status(int expungeable, int uids)
+{
+ int tell;
+
+ if(!selected)
+ return;
+ tell = 0;
+ if(expungeable)
+ tell = expungemsgs(selected, 1);
+ if(selected->sendflags)
+ sendflags(selected, uids);
+ if(tell || selected->toldmax != selected->max){
+ Bprint(&bout, "* %ud EXISTS\r\n", selected->max);
+ selected->toldmax = selected->max;
+ }
+ if(tell || selected->toldrecent != selected->recent){
+ Bprint(&bout, "* %ud RECENT\r\n", selected->recent);
+ selected->toldrecent = selected->recent;
+ }
+ if(tell)
+ closeimp(selected, checkbox(selected, 1));
+}
+
+/*
+ * careful: can't exit, because called by idle polling proc
+ */
+static void
+check(void)
+{
+ if(!selected)
+ return;
+ checkbox(selected, 0);
+ status(1, 0);
+}
+
+static void
+appendcmd(char *tg, char *cmd)
+{
+ char *mbox, head[128];
+ uint t, n, now;
+ int flags, ok;
+ Uidplus u;
+
+ mustbe(' ');
+ mbox = astring();
+ mustbe(' ');
+ flags = 0;
+ if(peekc() == '('){
+ flags = flaglist();
+ mustbe(' ');
+ }
+ now = time(nil);
+ if(peekc() == '"'){
+ t = imap4datetime(quoted());
+ if(t == ~0)
+ parseerr("illegal date format");
+ mustbe(' ');
+ if(t > now)
+ t = now;
+ }else
+ t = now;
+ n = litlen();
+
+ mbox = mboxname(mbox);
+ if(mbox == nil){
+ check();
+ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
+ return;
+ }
+ /* bug. this is upas/fs's job */
+ if(!cdexists(mboxdir, mbox)){
+ check();
+ Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
+ return;
+ }
+
+ snprint(head, sizeof head, "From %s %s", username, ctime(t));
+ ok = appendsave(mbox, flags, head, &bin, n, &u);
+ crnl();
+ check();
+ if(ok)
+ Bprint(&bout, "%s OK [APPENDUID %ud %ud] %s completed\r\n",
+ tg, u.uidvalidity, u.uid, cmd);
+ else
+ Bprint(&bout, "%s NO %s message save failed\r\n", tg, cmd);
+}
+
+static void
+authenticatecmd(char *tg, char *cmd)
+{
+ char *s, *t;
+
+ mustbe(' ');
+ s = atom();
+ if(cistrcmp(s, "cram-md5") == 0){
+ crnl();
+ t = cramauth();
+ if(t == nil){
+ Bprint(&bout, "%s OK %s\r\n", tg, cmd);
+ imapstate = Sauthed;
+ }else
+ Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t);
+ }else if(cistrcmp(s, "plain") == 0){
+ s = nil;
+ if(peekc() == ' '){
+ mustbe(' ');
+ s = astring();
+ }
+ crnl();
+ if(!allowpass)
+ Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
+ else if(t = plainauth(s))
+ Bprint(&bout, "%s NO %s failed %s\r\n", tg, cmd, t);
+ else{
+ Bprint(&bout, "%s OK %s\r\n", tg, cmd);
+ imapstate = Sauthed;
+ }
+ }else
+ Bprint(&bout, "%s NO %s unsupported authentication protocol\r\n", tg, cmd);
+}
+
+static void
+capabilitycmd(char *tg, char *cmd)
+{
+ crnl();
+ check();
+ Bprint(&bout, "* CAPABILITY IMAP4REV1 IDLE NAMESPACE QUOTA XDEBUG");
+ Bprint(&bout, " UIDPLUS");
+ if(allowpass || allowcr)
+ Bprint(&bout, " AUTH=CRAM-MD5 AUTH=PLAIN");
+ else
+ Bprint(&bout, " LOGINDISABLED AUTH=CRAM-MD5");
+ Bprint(&bout, "\r\n%s OK %s\r\n", tg, cmd);
+}
+
+static void
+closecmd(char *tg, char *cmd)
+{
+ crnl();
+ imapstate = Sauthed;
+ closebox(selected, 1);
+ selected = nil;
+ Bprint(&bout, "%s OK %s mailbox closed, now in authenticated state\r\n", tg, cmd);
+}
+
+/*
+ * note: message id's are before any pending expunges
+ */
+static void
+copycmd(char *tg, char *cmd)
+{
+ copyucmd(tg, cmd, 0);
+}
+
+static char *uidpsep;
+static int
+printuid(Box*, Msg *m, int, void*)
+{
+ Bprint(&bout, "%s%ud", uidpsep, m->uid);
+ uidpsep = ",";
+ return 1;
+}
+
+static void
+copyucmd(char *tg, char *cmd, int uids)
+{
+ char *uid, *mbox;
+ int ok;
+ uint max;
+ Msgset *ms;
+ Uidplus *u;
+
+ mustbe(' ');
+ ms = msgset(uids);
+ mustbe(' ');
+ mbox = astring();
+ crnl();
+
+ uid = "";
+ if(uids)
+ uid = "UID ";
+
+ mbox = mboxname(mbox);
+ if(mbox == nil){
+ status(1, uids);
+ Bprint(&bout, "%s NO %s%s bad mailbox\r\n", tg, uid, cmd);
+ return;
+ }
+ if(!cdexists(mboxdir, mbox)){
+ check();
+ Bprint(&bout, "%s NO [TRYCREATE] %s mailbox does not exist\r\n", tg, cmd);
+ return;
+ }
+
+ uidlist = 0;
+ uidtl = &uidlist;
+
+ max = selected->max;
+ checkbox(selected, 0);
+ ok = formsgs(selected, ms, max, uids, copycheck, nil);
+ if(ok)
+ ok = formsgs(selected, ms, max, uids, copysaveu, mbox);
+ status(1, uids);
+ if(ok && uidlist){
+ u = uidlist;
+ Bprint(&bout, "%s OK [COPYUID %ud", tg, u->uidvalidity);
+ uidpsep = " ";
+ formsgs(selected, ms, max, uids, printuid, mbox);
+ Bprint(&bout, " %ud", u->uid);
+ for(u = u->next; u; u = u->next)
+ Bprint(&bout, ",%ud", u->uid);
+ Bprint(&bout, "] %s%s completed\r\n", uid, cmd);
+ }else if(ok)
+ Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
+ else
+ Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
+}
+
+static void
+createcmd(char *tg, char *cmd)
+{
+ char *mbox;
+
+ mustbe(' ');
+ mbox = astring();
+ crnl();
+ check();
+
+ mbox = mboxname(mbox);
+ if(mbox == nil){
+ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
+ return;
+ }
+ if(cistrcmp(mbox, "mbox") == 0){
+ Bprint(&bout, "%s NO %s cannot remotely create INBOX\r\n", tg, cmd);
+ return;
+ }
+ if(creatembox(mbox) == -1)
+ Bprint(&bout, "%s NO %s cannot create mailbox %#Y\r\n", tg, cmd, mbox);
+ else
+ Bprint(&bout, "%s OK %#Y %s completed\r\n", tg, mbox, cmd);
+}
+
+static void
+xdebugcmd(char *tg, char *)
+{
+ char *s, *t;
+
+ mustbe(' ');
+ s = astring();
+ t = 0;
+ if(!cistrcmp(s, "file")){
+ mustbe(' ');
+ t = astring();
+ }
+ crnl();
+ check();
+ if(!cistrcmp(s, "on") || !cistrcmp(s, "1")){
+ Bprint(&bout, "%s OK debug on\r\n", tg);
+ debug = 1;
+ }else if(!cistrcmp(s, "file")){
+ if(!strstr(t, ".."))
+ snprint(logfile, sizeof logfile, "%s", t);
+ Bprint(&bout, "%s OK debug file %#Z\r\n", tg, logfile);
+ }else{
+ Bprint(&bout, "%s OK debug off\r\n", tg);
+ debug = 0;
+ }
+}
+
+static void
+deletecmd(char *tg, char *cmd)
+{
+ char *mbox;
+
+ mustbe(' ');
+ mbox = astring();
+ crnl();
+ check();
+
+ mbox = mboxname(mbox);
+ if(mbox == nil){
+ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
+ return;
+ }
+
+ /*
+ * i don't know if this is a hack or not. a delete of the
+ * currently-selected box seems fishy. the standard doesn't
+ * specify any behavior.
+ */
+ if(selected && strcmp(selected->name, mbox) == 0){
+ ilog("delete: client bug? close of selected mbox %s", selected->fs);
+ imapstate = Sauthed;
+ closebox(selected, 1);
+ selected = nil;
+ setname("[none]");
+ }
+
+ if(!cistrcmp(mbox, "mbox") || !removembox(mbox) == -1)
+ Bprint(&bout, "%s NO %s cannot delete mailbox %#Y\r\n", tg, cmd, mbox);
+ else
+ Bprint(&bout, "%s OK %#Y %s completed\r\n", tg, mbox, cmd);
+}
+
+static void
+expungeucmd(char *tg, char *cmd, int uids)
+{
+ int ok;
+ Msgset *ms;
+
+ ms = 0;
+ if(uids){
+ mustbe(' ');
+ ms = msgset(uids);
+ }
+ crnl();
+ ok = deletemsg(selected, ms);
+ check();
+ if(ok)
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+ else
+ Bprint(&bout, "%s NO %s some messages not expunged\r\n", tg, cmd);
+}
+
+static void
+expungecmd(char *tg, char *cmd)
+{
+ expungeucmd(tg, cmd, 0);
+}
+
+static void
+fetchcmd(char *tg, char *cmd)
+{
+ fetchucmd(tg, cmd, 0);
+}
+
+static void
+fetchucmd(char *tg, char *cmd, int uids)
+{
+ char *uid;
+ int ok;
+ uint max;
+ Fetch *f;
+ Msgset *ms;
+ Mblock *ml;
+
+ mustbe(' ');
+ ms = msgset(uids);
+ mustbe(' ');
+ f = fetchwhat();
+ crnl();
+ uid = "";
+ if(uids)
+ uid = "uid ";
+ max = selected->max;
+ ml = checkbox(selected, 1);
+ if(ml != nil)
+ formsgs(selected, ms, max, uids, fetchseen, f);
+ closeimp(selected, ml);
+ ok = ml != nil && formsgs(selected, ms, max, uids, fetchmsg, f);
+ status(uids, uids);
+ if(ok)
+ Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
+ else{
+ if(ml == nil)
+ ilog("nil maillock\n");
+ Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
+ }
+}
+
+static void
+idlecmd(char *tg, char *cmd)
+{
+ int c, pid;
+
+ crnl();
+ Bprint(&bout, "+ idling, waiting for done\r\n");
+ if(Bflush(&bout) < 0)
+ writeerr();
+
+ if(idlepid < 0){
+ pid = rfork(RFPROC|RFMEM|RFNOWAIT);
+ if(pid == 0){
+ setname("imap idle");
+ for(;;){
+ qlock(&imaplock);
+ if(exiting)
+ break;
+
+ /*
+ * parent may have changed curdir, but it doesn't change our .
+ */
+ resetcurdir();
+
+ check();
+ if(Bflush(&bout) < 0)
+ writeerr();
+ qunlock(&imaplock);
+ sleep(15*1000);
+ enableforwarding();
+ }
+ _exits(0);
+ }
+ idlepid = pid;
+ }
+
+ qunlock(&imaplock);
+
+ /*
+ * clear out the next line, which is supposed to contain (case-insensitive)
+ * done\n
+ * this is special code since it has to dance with the idle polling proc
+ * and handle exiting correctly.
+ */
+ for(;;){
+ c = getc();
+ if(c < 0){
+ qlock(&imaplock);
+ if(!exiting)
+ cleaner();
+ _exits(0);
+ }
+ if(c == '\n')
+ break;
+ }
+
+ qlock(&imaplock);
+ if(exiting)
+ _exits(0);
+
+ /*
+ * child may have changed curdir, but it doesn't change our .
+ */
+ resetcurdir();
+ check();
+ Bprint(&bout, "%s OK %s terminated\r\n", tg, cmd);
+}
+
+static void
+listcmd(char *tg, char *cmd)
+{
+ char *s, *t, *ref, *mbox;
+
+ mustbe(' ');
+ s = astring();
+ mustbe(' ');
+ t = listmbox();
+ crnl();
+ check();
+ ref = mutf7str(s);
+ mbox = mutf7str(t);
+ if(ref == nil || mbox == nil){
+ Bprint(&bout, "%s BAD %s modified utf-7\r\n", tg, cmd);
+ return;
+ }
+
+ /*
+ * special request for hierarchy delimiter and root name
+ * root name appears to be name up to and including any delimiter,
+ * or the empty string, if there is no delimiter.
+ *
+ * this must change if the # namespace convention is supported.
+ */
+ if(*mbox == '\0'){
+ s = strchr(ref, '/');
+ if(s == nil)
+ ref = "";
+ else
+ s[1] = '\0';
+ Bprint(&bout, "* %s (\\Noselect) \"/\" \"%s\"\r\n", cmd, ref);
+ Bprint(&bout, "%s OK %s\r\n", tg, cmd);
+ return;
+ }
+
+ /*
+ * hairy exception: these take non-fsencoded strings. BUG?
+ */
+ if(cistrcmp(cmd, "lsub") == 0)
+ lsubboxes(cmd, ref, mbox);
+ else
+ listboxes(cmd, ref, mbox);
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+}
+
+static void
+logincmd(char *tg, char *cmd)
+{
+ char *r, *s, *t;
+
+ mustbe(' ');
+ s = astring(); /* uid */
+ mustbe(' ');
+ t = astring(); /* password */
+ crnl();
+ if(allowcr){
+ if(r = crauth(s, t)){
+ Bprint(&bout, "* NO [ALERT] %s\r\n", r);
+ Bprint(&bout, "%s NO %s succeeded\r\n", tg, cmd);
+ }else{
+ Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
+ imapstate = Sauthed;
+ }
+ return;
+ }else if(allowpass){
+ if(r = passauth(s, t))
+ Bprint(&bout, "%s NO %s failed check [%s]\r\n", tg, cmd, r);
+ else{
+ Bprint(&bout, "%s OK %s succeeded\r\n", tg, cmd);
+ imapstate = Sauthed;
+ }
+ return;
+ }
+ Bprint(&bout, "%s NO %s plaintext passwords disallowed\r\n", tg, cmd);
+}
+
+/*
+ * logout or x-exit, which doesn't expunge the mailbox
+ */
+static void
+logoutcmd(char *tg, char *cmd)
+{
+ crnl();
+
+ if(cmd[0] != 'x' && selected){
+ closebox(selected, 1);
+ selected = nil;
+ }
+ Bprint(&bout, "* bye\r\n");
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+ exits(0);
+}
+
+static void
+namespacecmd(char *tg, char *cmd)
+{
+ crnl();
+ check();
+
+ /*
+ * personal, other users, shared namespaces
+ * send back nil or descriptions of (prefix heirarchy-delim) for each case
+ */
+ Bprint(&bout, "* NAMESPACE ((\"\" \"/\")) nil nil\r\n");
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+}
+
+static void
+noopcmd(char *tg, char *cmd)
+{
+ crnl();
+ check();
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+ enableforwarding();
+}
+
+static void
+getquota0(char *tg, char *cmd, char *r)
+{
+extern vlong getquota(void);
+ vlong v;
+
+ if(r[0]){
+ Bprint(&bout, "%s NO %s no such quota root\r\n", tg, cmd);
+ return;
+ }
+ v = getquota();
+ if(v == -1){
+ Bprint(&bout, "%s NO %s bad [%r]\r\n", tg, cmd);
+ return;
+ }
+ Bprint(&bout, "* %s "" (storage %llud %d)\r\n", cmd, v/1024, 256*1024);
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+}
+
+static void
+getquotacmd(char *tg, char *cmd)
+{
+ char *r;
+
+ mustbe(' ');
+ r = astring();
+ crnl();
+ check();
+ getquota0(tg, cmd, r);
+}
+
+static void
+getquotarootcmd(char *tg, char *cmd)
+{
+ char *r;
+
+ mustbe(' ');
+ r = astring();
+ crnl();
+ check();
+
+ Bprint(&bout, "* %s %s \"\"\r\n", cmd, r);
+ getquota0(tg, cmd, "");
+}
+
+static void
+setquotacmd(char *tg, char *cmd)
+{
+ mustbe(' ');
+ astring();
+ mustbe(' ');
+ mustbe('(');
+ for(;;){
+ astring();
+ mustbe(' ');
+ number(0);
+ if(peekc() == ')')
+ break;
+ }
+ getc();
+ crnl();
+ check();
+ Bprint(&bout, "%s NO %s error: can't set that data\r\n", tg, cmd);
+}
+
+/*
+ * this is only a partial implementation
+ * should copy files to other directories,
+ * and copy & truncate inbox
+ */
+static void
+renamecmd(char *tg, char *cmd)
+{
+ char *from, *to;
+
+ mustbe(' ');
+ from = astring();
+ mustbe(' ');
+ to = astring();
+ crnl();
+ check();
+
+ to = mboxname(to);
+ if(to == nil || cistrcmp(to, "mbox") == 0){
+ Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
+ return;
+ }
+ if(access(to, AEXIST) >= 0){
+ Bprint(&bout, "%s NO %s mailbox already exists\r\n", tg, cmd);
+ return;
+ }
+ from = mboxname(from);
+ if(from == nil){
+ Bprint(&bout, "%s NO %s bad mailbox destination name\r\n", tg, cmd);
+ return;
+ }
+ if(renamebox(from, to, strcmp(from, "mbox")))
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+ else
+ Bprint(&bout, "%s NO %s failed\r\n", tg, cmd);
+}
+
+static void
+searchcmd(char *tg, char *cmd)
+{
+ searchucmd(tg, cmd, 0);
+}
+
+/*
+ * mail.app has a vicious habit of appending a message to
+ * a folder and then immediately searching for it by message-id.
+ * for a 10,000 message sent folder, this can be quite painful.
+ *
+ * evil strategy. for message-id searches, check the last
+ * message in the mailbox! if that fails, use the normal algorithm.
+ */
+static Msg*
+mailappsucks(Search *s)
+{
+ Msg *m;
+
+ if(s->key == SKuid)
+ s = s->next;
+ if(s && s->next == nil)
+ if(s->key == SKheader && cistrcmp(s->hdr, "message-id") == 0){
+ for(m = selected->msgs; m && m->next; m = m->next)
+ ;
+ if(m != nil)
+ if(m->matched = searchmsg(m, s, 0))
+ return m;
+ }
+ return 0;
+}
+
+static void
+searchucmd(char *tg, char *cmd, int uids)
+{
+ char *uid;
+ uint id, ld;
+ Msg *m;
+ Search rock;
+
+ mustbe(' ');
+ rock.next = nil;
+ searchkeys(1, &rock);
+ crnl();
+ uid = "";
+ if(uids)
+ uid = "UID "; /* android needs caps */
+ if(rock.next != nil && rock.next->key == SKcharset){
+ if(cistrcmp(rock.next->s, "utf-8") != 0
+ && cistrcmp(rock.next->s, "us-ascii") != 0){
+ Bprint(&bout, "%s NO [BADCHARSET] (\"US-ASCII\" \"UTF-8\") %s%s failed\r\n", tg, uid, cmd);
+ checkbox(selected, 0);
+ status(uids, uids);
+ return;
+ }
+ rock.next = rock.next->next;
+ }
+ Bprint(&bout, "* search");
+ if(m = mailappsucks(rock.next))
+ goto cheat;
+ ld = searchld(rock.next);
+ for(m = selected->msgs; m != nil; m = m->next)
+ m->matched = searchmsg(m, rock.next, ld);
+ for(m = selected->msgs; m != nil; m = m->next){
+cheat:
+ if(m->matched){
+ if(uids)
+ id = m->uid;
+ else
+ id = m->seq;
+ Bprint(&bout, " %ud", id);
+ }
+ }
+ Bprint(&bout, "\r\n");
+ checkbox(selected, 0);
+ status(uids, uids);
+ Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
+}
+
+static void
+selectcmd(char *tg, char *cmd)
+{
+ char *s, *m0, *mbox, buf[Pathlen];
+ Msg *m;
+
+ mustbe(' ');
+ m0 = astring();
+ crnl();
+
+ if(selected){
+ imapstate = Sauthed;
+ closebox(selected, 1);
+ selected = nil;
+ setname("[none]");
+ }
+ debuglog("select %s", m0);
+
+ mbox = mboxname(m0);
+ if(mbox == nil){
+ debuglog("select %s [%s] -> no bad", mbox, m0);
+ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
+ return;
+ }
+
+ selected = openbox(mbox, "imap", cistrcmp(cmd, "select") == 0);
+ if(selected == nil){
+ Bprint(&bout, "%s NO %s can't open mailbox %#Y: %r\r\n", tg, cmd, mbox);
+ return;
+ }
+
+ setname("%s", decfs(buf, sizeof buf, selected->name));
+ imapstate = Sselected;
+
+ Bprint(&bout, "* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft)\r\n");
+ Bprint(&bout, "* %ud EXISTS\r\n", selected->max);
+ selected->toldmax = selected->max;
+ Bprint(&bout, "* %ud RECENT\r\n", selected->recent);
+ selected->toldrecent = selected->recent;
+ for(m = selected->msgs; m != nil; m = m->next){
+ if(!m->expunged && (m->flags & Fseen) != Fseen){
+ Bprint(&bout, "* OK [UNSEEN %ud]\r\n", m->seq);
+ break;
+ }
+ }
+ Bprint(&bout, "* OK [PERMANENTFLAGS (\\Seen \\Answered \\Flagged \\Draft \\Deleted)]\r\n");
+ Bprint(&bout, "* OK [UIDNEXT %ud]\r\n", selected->uidnext);
+ Bprint(&bout, "* OK [UIDVALIDITY %ud]\r\n", selected->uidvalidity);
+ s = "READ-ONLY";
+ if(selected->writable)
+ s = "READ-WRITE";
+ Bprint(&bout, "%s OK [%s] %s %#Y completed\r\n", tg, s, cmd, mbox);
+}
+
+static Namedint statusitems[] =
+{
+ {"MESSAGES", Smessages},
+ {"RECENT", Srecent},
+ {"UIDNEXT", Suidnext},
+ {"UIDVALIDITY", Suidvalidity},
+ {"UNSEEN", Sunseen},
+ {nil, 0}
+};
+
+static void
+statuscmd(char *tg, char *cmd)
+{
+ char *s, *mbox;
+ int si, i, opened;
+ uint v;
+ Box *box;
+ Msg *m;
+
+ mustbe(' ');
+ mbox = astring();
+ mustbe(' ');
+ mustbe('(');
+ si = 0;
+ for(;;){
+ s = atom();
+ i = mapint(statusitems, s);
+ if(i == 0)
+ parseerr("illegal status item");
+ si |= i;
+ if(peekc() == ')')
+ break;
+ mustbe(' ');
+ }
+ mustbe(')');
+ crnl();
+
+ mbox = mboxname(mbox);
+ if(mbox == nil){
+ check();
+ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
+ return;
+ }
+
+ opened = 0;
+ if(selected && !strcmp(mbox, selected->name))
+ box = selected;
+ else{
+ box = openbox(mbox, "status", 1);
+ if(box == nil){
+ check();
+ Bprint(&bout, "%s NO [TRYCREATE] %s can't open mailbox %#Y: %r\r\n", tg, cmd, mbox);
+ return;
+ }
+ opened = 1;
+ }
+
+ Bprint(&bout, "* STATUS %#Y (", mbox);
+ s = "";
+ for(i = 0; statusitems[i].name != nil; i++)
+ if(si & statusitems[i].v){
+ v = 0;
+ switch(statusitems[i].v){
+ case Smessages:
+ v = box->max;
+ break;
+ case Srecent:
+ v = box->recent;
+ break;
+ case Suidnext:
+ v = box->uidnext;
+ break;
+ case Suidvalidity:
+ v = box->uidvalidity;
+ break;
+ case Sunseen:
+ v = 0;
+ for(m = box->msgs; m != nil; m = m->next)
+ if((m->flags & Fseen) != Fseen)
+ v++;
+ break;
+ default:
+ Bprint(&bout, ")");
+ bye("internal error: status item not implemented");
+ break;
+ }
+ Bprint(&bout, "%s%s %ud", s, statusitems[i].name, v);
+ s = " ";
+ }
+ Bprint(&bout, ")\r\n");
+ if(opened)
+ closebox(box, 1);
+
+ check();
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+}
+
+static void
+storecmd(char *tg, char *cmd)
+{
+ storeucmd(tg, cmd, 0);
+}
+
+static void
+storeucmd(char *tg, char *cmd, int uids)
+{
+ char *uid;
+ int ok;
+ uint max;
+ Mblock *ml;
+ Msgset *ms;
+ Store *st;
+
+ mustbe(' ');
+ ms = msgset(uids);
+ mustbe(' ');
+ st = storewhat();
+ crnl();
+ uid = "";
+ if(uids)
+ uid = "uid ";
+ max = selected->max;
+ ml = checkbox(selected, 1);
+ ok = ml != nil && formsgs(selected, ms, max, uids, storemsg, st);
+ closeimp(selected, ml);
+ status(uids, uids);
+ if(ok)
+ Bprint(&bout, "%s OK %s%s completed\r\n", tg, uid, cmd);
+ else
+ Bprint(&bout, "%s NO %s%s failed\r\n", tg, uid, cmd);
+}
+
+/*
+ * minimal implementation of subscribe
+ * all folders are automatically subscribed,
+ * and can't be unsubscribed
+ */
+static void
+subscribecmd(char *tg, char *cmd)
+{
+ char *mbox;
+ int ok;
+ Box *box;
+
+ mustbe(' ');
+ mbox = astring();
+ crnl();
+ check();
+ mbox = mboxname(mbox);
+ ok = 0;
+ if(mbox != nil && (box = openbox(mbox, "subscribe", 0))){
+ ok = subscribe(mbox, 's');
+ closebox(box, 1);
+ }
+ if(!ok)
+ Bprint(&bout, "%s NO %s bad mailbox\r\n", tg, cmd);
+ else
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+}
+
+static void
+uidcmd(char *tg, char *cmd)
+{
+ char *sub;
+
+ mustbe(' ');
+ sub = atom();
+ if(cistrcmp(sub, "copy") == 0)
+ copyucmd(tg, sub, 1);
+ else if(cistrcmp(sub, "fetch") == 0)
+ fetchucmd(tg, sub, 1);
+ else if(cistrcmp(sub, "search") == 0)
+ searchucmd(tg, sub, 1);
+ else if(cistrcmp(sub, "store") == 0)
+ storeucmd(tg, sub, 1);
+ else if(cistrcmp(sub, "expunge") == 0)
+ expungeucmd(tg, sub, 1);
+ else{
+ clearcmd();
+ Bprint(&bout, "%s BAD %s illegal uid command %s\r\n", tg, cmd, sub);
+ }
+}
+
+static void
+unsubscribecmd(char *tg, char *cmd)
+{
+ char *mbox;
+
+ mustbe(' ');
+ mbox = astring();
+ crnl();
+ check();
+ mbox = mboxname(mbox);
+ if(mbox == nil || !subscribe(mbox, 'u'))
+ Bprint(&bout, "%s NO %s can't unsubscribe\r\n", tg, cmd);
+ else
+ Bprint(&bout, "%s OK %s completed\r\n", tg, cmd);
+}
+
+static char *gbuf;
+static void
+badsyn(void)
+{
+ debuglog("syntax error [%s]", gbuf);
+ parseerr("bad syntax");
+}
+
+static void
+clearcmd(void)
+{
+ int c;
+
+ for(;;){
+ c = getc();
+ if(c < 0)
+ bye("end of input");
+ if(c == '\n')
+ return;
+ }
+}
+
+static void
+crnl(void)
+{
+ int c;
+
+ c = getc();
+ if(c == '\n')
+ return;
+ if(c != '\r' || getc() != '\n')
+ badsyn();
+}
+
+static void
+mustbe(int c)
+{
+ int x;
+
+ if((x = getc()) != c){
+ ungetc();
+ ilog("must be '%c' got %c", c, x);
+ badsyn();
+ }
+}
+
+/*
+ * flaglist : '(' ')' | '(' flags ')'
+ */
+static int
+flaglist(void)
+{
+ int f;
+
+ mustbe('(');
+ f = 0;
+ if(peekc() != ')')
+ f = flags();
+
+ mustbe(')');
+ return f;
+}
+
+/*
+ * flags : flag | flags ' ' flag
+ * flag : '\' atom | atom
+ */
+static int
+flags(void)
+{
+ char *s;
+ int ff, flags, c;
+
+ flags = 0;
+ for(;;){
+ c = peekc();
+ if(c == '\\'){
+ mustbe('\\');
+ s = atomstring(atomstop, "\\");
+ }else if(strchr(atomstop, c) != nil)
+ s = atom();
+ else
+ break;
+ ff = mapflag(s);
+ if(ff == 0)
+ parseerr("flag not supported");
+ flags |= ff;
+ if(peekc() != ' ')
+ break;
+ mustbe(' ');
+ }
+ if(flags == 0)
+ parseerr("no flags given");
+ return flags;
+}
+
+/*
+ * storewhat : osign 'FLAGS' ' ' storeflags
+ * | osign 'FLAGS.SILENT' ' ' storeflags
+ * osign :
+ * | '+' | '-'
+ * storeflags : flaglist | flags
+ */
+static Store*
+storewhat(void)
+{
+ char *s;
+ int c, f, w;
+
+ c = peekc();
+ if(c == '+' || c == '-')
+ mustbe(c);
+ else
+ c = 0;
+ s = atom();
+ w = 0;
+ if(cistrcmp(s, "flags") == 0)
+ w = Stflags;
+ else if(cistrcmp(s, "flags.silent") == 0)
+ w = Stflagssilent;
+ else
+ parseerr("illegal store attribute");
+ mustbe(' ');
+ if(peekc() == '(')
+ f = flaglist();
+ else
+ f = flags();
+ return mkstore(c, w, f);
+}
+
+/*
+ * fetchwhat : "ALL" | "FULL" | "FAST" | fetchatt | '(' fetchatts ')'
+ * fetchatts : fetchatt | fetchatts ' ' fetchatt
+ */
+static char *fetchatom = "(){}%*\"\\[]";
+static Fetch*
+fetchwhat(void)
+{
+ char *s;
+ Fetch *f;
+
+ if(peekc() == '('){
+ getc();
+ f = nil;
+ for(;;){
+ s = atomstring(fetchatom, "");
+ f = fetchatt(s, f);
+ if(peekc() == ')')
+ break;
+ mustbe(' ');
+ }
+ getc();
+ return revfetch(f);
+ }
+
+ s = atomstring(fetchatom, "");
+ if(cistrcmp(s, "all") == 0)
+ f = mkfetch(Fflags, mkfetch(Finternaldate, mkfetch(Frfc822size, mkfetch(Fenvelope, nil))));
+ else if(cistrcmp(s, "fast") == 0)
+ f = mkfetch(Fflags, mkfetch(Finternaldate, mkfetch(Frfc822size, nil)));
+ else if(cistrcmp(s, "full") == 0)
+ f = mkfetch(Fflags, mkfetch(Finternaldate, mkfetch(Frfc822size, mkfetch(Fenvelope, mkfetch(Fbody, nil)))));
+ else
+ f = fetchatt(s, nil);
+ return f;
+}
+
+/*
+ * fetchatt : "ENVELOPE" | "FLAGS" | "INTERNALDATE"
+ * | "RFC822" | "RFC822.HEADER" | "RFC822.SIZE" | "RFC822.TEXT"
+ * | "BODYSTRUCTURE"
+ * | "UID"
+ * | "BODY"
+ * | "BODY" bodysubs
+ * | "BODY.PEEK" bodysubs
+ * bodysubs : sect
+ * | sect '<' number '.' nz-number '>'
+ * sect : '[' sectspec ']'
+ * sectspec : sectmsgtext
+ * | sectpart
+ * | sectpart '.' secttext
+ * sectpart : nz-number
+ * | sectpart '.' nz-number
+ */
+Nlist*
+mknlist(void)
+{
+ Nlist *nl;
+
+ nl = binalloc(&parsebin, sizeof *nl, 1);
+ if(nl == nil)
+ parseerr("out of memory");
+ nl->n = number(1);
+ return nl;
+}
+
+static Fetch*
+fetchatt(char *s, Fetch *f)
+{
+ int c;
+ Nlist *n;
+
+ if(cistrcmp(s, "envelope") == 0)
+ return mkfetch(Fenvelope, f);
+ if(cistrcmp(s, "flags") == 0)
+ return mkfetch(Fflags, f);
+ if(cistrcmp(s, "internaldate") == 0)
+ return mkfetch(Finternaldate, f);
+ if(cistrcmp(s, "RFC822") == 0)
+ return mkfetch(Frfc822, f);
+ if(cistrcmp(s, "RFC822.header") == 0)
+ return mkfetch(Frfc822head, f);
+ if(cistrcmp(s, "RFC822.size") == 0)
+ return mkfetch(Frfc822size, f);
+ if(cistrcmp(s, "RFC822.text") == 0)
+ return mkfetch(Frfc822text, f);
+ if(cistrcmp(s, "bodystructure") == 0)
+ return mkfetch(Fbodystruct, f);
+ if(cistrcmp(s, "uid") == 0)
+ return mkfetch(Fuid, f);
+
+ if(cistrcmp(s, "body") == 0){
+ if(peekc() != '[')
+ return mkfetch(Fbody, f);
+ f = mkfetch(Fbodysect, f);
+ }else if(cistrcmp(s, "body.peek") == 0)
+ f = mkfetch(Fbodypeek, f);
+ else
+ parseerr("illegal fetch attribute");
+
+ mustbe('[');
+ c = peekc();
+ if(c >= '1' && c <= '9'){
+ n = f->sect = mknlist();
+ while(peekc() == '.'){
+ getc();
+ c = peekc();
+ if(c < '1' || c > '9')
+ break;
+ n->next = mknlist();
+ n = n->next;
+ }
+ }
+ if(peekc() != ']')
+ secttext(f, f->sect != nil);
+ mustbe(']');
+
+ if(peekc() != '<')
+ return f;
+
+ f->partial = 1;
+ mustbe('<');
+ f->start = number(0);
+ mustbe('.');
+ f->size = number(1);
+ mustbe('>');
+ return f;
+}
+
+/*
+ * secttext : sectmsgtext | "MIME"
+ * sectmsgtext : "HEADER"
+ * | "TEXT"
+ * | "HEADER.FIELDS" ' ' hdrlist
+ * | "HEADER.FIELDS.NOT" ' ' hdrlist
+ * hdrlist : '(' hdrs ')'
+ * hdrs: : astring
+ * | hdrs ' ' astring
+ */
+static void
+secttext(Fetch *f, int mimeok)
+{
+ char *s;
+ Slist *h;
+
+ s = atomstring(fetchatom, "");
+ if(cistrcmp(s, "header") == 0){
+ f->part = FPhead;
+ return;
+ }
+ if(cistrcmp(s, "text") == 0){
+ f->part = FPtext;
+ return;
+ }
+ if(mimeok && cistrcmp(s, "mime") == 0){
+ f->part = FPmime;
+ return;
+ }
+ if(cistrcmp(s, "header.fields") == 0)
+ f->part = FPheadfields;
+ else if(cistrcmp(s, "header.fields.not") == 0)
+ f->part = FPheadfieldsnot;
+ else
+ parseerr("illegal fetch section text");
+ mustbe(' ');
+ mustbe('(');
+ h = nil;
+ for(;;){
+ h = mkslist(astring(), h);
+ if(peekc() == ')')
+ break;
+ mustbe(' ');
+ }
+ mustbe(')');
+ f->hdrs = revslist(h);
+}
+
+/*
+ * searchwhat : "CHARSET" ' ' astring searchkeys | searchkeys
+ * searchkeys : searchkey | searchkeys ' ' searchkey
+ * searchkey : "ALL" | "ANSWERED" | "DELETED" | "FLAGGED" | "NEW" | "OLD" | "RECENT"
+ * | "SEEN" | "UNANSWERED" | "UNDELETED" | "UNFLAGGED" | "DRAFT" | "UNDRAFT"
+ * | astrkey ' ' astring
+ * | datekey ' ' date
+ * | "KEYWORD" ' ' flag | "UNKEYWORD" flag
+ * | "LARGER" ' ' number | "SMALLER" ' ' number
+ * | "HEADER" astring ' ' astring
+ * | set | "UID" ' ' set
+ * | "NOT" ' ' searchkey
+ * | "OR" ' ' searchkey ' ' searchkey
+ * | '(' searchkeys ')'
+ * astrkey : "BCC" | "BODY" | "CC" | "FROM" | "SUBJECT" | "TEXT" | "TO"
+ * datekey : "BEFORE" | "ON" | "SINCE" | "SENTBEFORE" | "SENTON" | "SENTSINCE"
+ */
+static Namedint searchmap[] =
+{
+ {"ALL", SKall},
+ {"ANSWERED", SKanswered},
+ {"DELETED", SKdeleted},
+ {"FLAGGED", SKflagged},
+ {"NEW", SKnew},
+ {"OLD", SKold},
+ {"RECENT", SKrecent},
+ {"SEEN", SKseen},
+ {"UNANSWERED", SKunanswered},
+ {"UNDELETED", SKundeleted},
+ {"UNFLAGGED", SKunflagged},
+ {"DRAFT", SKdraft},
+ {"UNDRAFT", SKundraft},
+ {"UNSEEN", SKunseen},
+ {nil, 0}
+};
+
+static Namedint searchmapstr[] =
+{
+ {"CHARSET", SKcharset},
+ {"BCC", SKbcc},
+ {"BODY", SKbody},
+ {"CC", SKcc},
+ {"FROM", SKfrom},
+ {"SUBJECT", SKsubject},
+ {"TEXT", SKtext},
+ {"TO", SKto},
+ {nil, 0}
+};
+
+static Namedint searchmapdate[] =
+{
+ {"BEFORE", SKbefore},
+ {"ON", SKon},
+ {"SINCE", SKsince},
+ {"SENTBEFORE", SKsentbefore},
+ {"SENTON", SKsenton},
+ {"SENTSINCE", SKsentsince},
+ {nil, 0}
+};
+
+static Namedint searchmapflag[] =
+{
+ {"KEYWORD", SKkeyword},
+ {"UNKEYWORD", SKunkeyword},
+ {nil, 0}
+};
+
+static Namedint searchmapnum[] =
+{
+ {"SMALLER", SKsmaller},
+ {"LARGER", SKlarger},
+ {nil, 0}
+};
+
+static Search*
+searchkeys(int first, Search *tail)
+{
+ Search *s;
+
+ for(;;){
+ if(peekc() == '('){
+ getc();
+ tail = searchkeys(0, tail);
+ mustbe(')');
+ }else{
+ s = searchkey(first);
+ tail->next = s;
+ tail = s;
+ }
+ first = 0;
+ if(peekc() != ' ')
+ break;
+ getc();
+ }
+ return tail;
+}
+
+static Search*
+searchkey(int first)
+{
+ char *a;
+ int i, c;
+ Search *sr, rock;
+ Tm tm;
+
+ sr = binalloc(&parsebin, sizeof *sr, 1);
+ if(sr == nil)
+ parseerr("out of memory");
+
+ c = peekc();
+ if(c >= '0' && c <= '9'){
+ sr->key = SKset;
+ sr->set = msgset(0);
+ return sr;
+ }
+
+ a = atom();
+ if(i = mapint(searchmap, a))
+ sr->key = i;
+ else if(i = mapint(searchmapstr, a)){
+ if(!first && i == SKcharset)
+ parseerr("illegal search key");
+ sr->key = i;
+ mustbe(' ');
+ sr->s = astring();
+ }else if(i = mapint(searchmapdate, a)){
+ sr->key = i;
+ mustbe(' ');
+ c = peekc();
+ if(c == '"')
+ getc();
+ a = atom();
+ if(a == nil || !imap4date(&tm, a))
+ parseerr("bad date format");
+ sr->year = tm.year;
+ sr->mon = tm.mon;
+ sr->mday = tm.mday;
+ if(c == '"')
+ mustbe('"');
+ }else if(i = mapint(searchmapflag, a)){
+ sr->key = i;
+ mustbe(' ');
+ c = peekc();
+ if(c == '\\'){
+ mustbe('\\');
+ a = atomstring(atomstop, "\\");
+ }else
+ a = atom();
+ i = mapflag(a);
+ if(i == 0)
+ parseerr("flag not supported");
+ sr->num = i;
+ }else if(i = mapint(searchmapnum, a)){
+ sr->key = i;
+ mustbe(' ');
+ sr->num = number(0);
+ }else if(cistrcmp(a, "HEADER") == 0){
+ sr->key = SKheader;
+ mustbe(' ');
+ sr->hdr = astring();
+ mustbe(' ');
+ sr->s = astring();
+ }else if(cistrcmp(a, "UID") == 0){
+ sr->key = SKuid;
+ mustbe(' ');
+ sr->set = msgset(0);
+ }else if(cistrcmp(a, "NOT") == 0){
+ sr->key = SKnot;
+ mustbe(' ');
+ rock.next = nil;
+ searchkeys(0, &rock);
+ sr->left = rock.next;
+ }else if(cistrcmp(a, "OR") == 0){
+ sr->key = SKor;
+ mustbe(' ');
+ rock.next = nil;
+ searchkeys(0, &rock);
+ sr->left = rock.next;
+ mustbe(' ');
+ rock.next = nil;
+ searchkeys(0, &rock);
+ sr->right = rock.next;
+ }else
+ parseerr("illegal search key");
+ return sr;
+}
+
+/*
+ * set : seqno
+ * | seqno ':' seqno
+ * | set ',' set
+ * seqno: nz-number
+ * | '*'
+ *
+ */
+static Msgset*
+msgset(int uids)
+{
+ uint from, to;
+ Msgset head, *last, *ms;
+
+ last = &head;
+ head.next = nil;
+ for(;;){
+ from = uids ? uidno() : seqno();
+ to = from;
+ if(peekc() == ':'){
+ getc();
+ to = uids? uidno(): seqno();
+ }
+ ms = binalloc(&parsebin, sizeof *ms, 0);
+ if(ms == nil)
+ parseerr("out of memory");
+ if(to < from){
+ ms->from = to;
+ ms->to = from;
+ }else{
+ ms->from = from;
+ ms->to = to;
+ }
+ ms->next = nil;
+ last->next = ms;
+ last = ms;
+ if(peekc() != ',')
+ break;
+ getc();
+ }
+ return head.next;
+}
+
+static uint
+seqno(void)
+{
+ if(peekc() == '*'){
+ getc();
+ return ~0UL;
+ }
+ return number(1);
+}
+
+static uint
+uidno(void)
+{
+ if(peekc() == '*'){
+ getc();
+ return ~0UL;
+ }
+ return number(0);
+}
+
+/*
+ * 7 bit, non-ctl chars, no (){%*"\
+ * NIL is special case for nstring or parenlist
+ */
+static char *
+atom(void)
+{
+ return atomstring(atomstop, "");
+}
+
+/*
+ * like an atom, but no +
+ */
+static char *
+tag(void)
+{
+ return atomstring("+(){%*\"\\", "");
+}
+
+/*
+ * string or atom allowing %*
+ */
+static char *
+listmbox(void)
+{
+ int c;
+
+ c = peekc();
+ if(c == '{')
+ return literal();
+ if(c == '"')
+ return quoted();
+ return atomstring("(){\"\\", "");
+}
+
+/*
+ * string or atom
+ */
+static char *
+astring(void)
+{
+ int c;
+
+ c = peekc();
+ if(c == '{')
+ return literal();
+ if(c == '"')
+ return quoted();
+ return atom();
+}
+
+/*
+ * 7 bit, non-ctl chars, none from exception list
+ */
+static char *
+atomstring(char *disallowed, char *initial)
+{
+ char *s;
+ int c, ns, as;
+
+ ns = strlen(initial);
+ s = binalloc(&parsebin, ns + Stralloc, 0);
+ if(s == nil)
+ parseerr("out of memory");
+ strcpy(s, initial);
+ as = ns + Stralloc;
+ for(;;){
+ c = getc();
+ if(c <= ' ' || c >= 0x7f || strchr(disallowed, c) != nil){
+ ungetc();
+ break;
+ }
+ s[ns++] = c;
+ if(ns >= as){
+ s = bingrow(&parsebin, s, as, as + Stralloc, 0);
+ if(s == nil)
+ parseerr("out of memory");
+ as += Stralloc;
+ }
+ }
+ if(ns == 0)
+ badsyn();
+ s[ns] = '\0';
+ return s;
+}
+
+/*
+ * quoted: '"' chars* '"'
+ * chars: 1-128 except \r and \n
+ */
+static char *
+quoted(void)
+{
+ char *s;
+ int c, ns, as;
+
+ mustbe('"');
+ s = binalloc(&parsebin, Stralloc, 0);
+ if(s == nil)
+ parseerr("out of memory");
+ as = Stralloc;
+ ns = 0;
+ for(;;){
+ c = getc();
+ if(c == '"')
+ break;
+ if(c < 1 || c > 0x7f || c == '\r' || c == '\n')
+ badsyn();
+ if(c == '\\'){
+ c = getc();
+ if(c != '\\' && c != '"')
+ badsyn();
+ }
+ s[ns++] = c;
+ if(ns >= as){
+ s = bingrow(&parsebin, s, as, as + Stralloc, 0);
+ if(s == nil)
+ parseerr("out of memory");
+ as += Stralloc;
+ }
+ }
+ s[ns] = '\0';
+ return s;
+}
+
+/*
+ * litlen: {number}\r\n
+ */
+static uint
+litlen(void)
+{
+ uint v;
+
+ mustbe('{');
+ v = number(0);
+ mustbe('}');
+ crnl();
+ return v;
+}
+
+/*
+ * literal: litlen data<0:litlen>
+ */
+static char*
+literal(void)
+{
+ char *s;
+ uint v;
+
+ v = litlen();
+ s = binalloc(&parsebin, v + 1, 0);
+ if(s == nil)
+ parseerr("out of memory");
+ Bprint(&bout, "+ Ready for literal data\r\n");
+ if(Bflush(&bout) < 0)
+ writeerr();
+ if(v != 0 && Bread(&bin, s, v) != v)
+ badsyn();
+ s[v] = '\0';
+ return s;
+}
+
+/*
+ * digits; number is 32 bits
+ */
+enum{
+ Max = 0xffffffff/10,
+};
+
+static uint
+number(int nonzero)
+{
+ uint n, nn;
+ int c, first, ovfl;
+
+ n = 0;
+ first = 1;
+ ovfl = 0;
+ for(;;){
+ c = getc();
+ if(c < '0' || c > '9'){
+ ungetc();
+ if(first)
+ badsyn();
+ break;
+ }
+ c -= '0';
+ first = 0;
+ if(n > Max)
+ ovfl = 1;
+ nn = n*10 + c;
+ if(nn < n)
+ ovfl = 1;
+ n = nn;
+ }
+ if(nonzero && n == 0)
+ badsyn();
+ if(ovfl)
+ parseerr("number out of range\r\n");
+ return n;
+}
+
+static void
+logit(char *o)
+{
+ char *s, *p, *q;
+
+ if(!debug)
+ return;
+ s = strdup(o);
+ p = strchr(s, ' ');
+ if(!p)
+ goto emit;
+ q = strchr(++p, ' ');
+ if(!q)
+ goto emit;
+ if(!cistrncmp(p, "login", 5)){
+ q = strchr(++q, ' ');
+ if(!q)
+ goto emit;
+ for(q = q + 1; *q != ' ' && *q; q++)
+ *q = '*';
+ }
+emit:
+ for(p = s + strlen(s) - 1; p >= s && (/**p == '\r' ||*/ *p == '\n'); )
+ *p-- = 0;
+ ilog("%s", s);
+ free(s);
+}
+
+static char *gbuf;
+static char *gbufp = "";
+
+static int
+getc(void)
+{
+ if(*gbufp == 0){
+ free(gbuf);
+ werrstr("");
+ gbufp = gbuf = Brdstr(&bin, '\n', 0);
+ if(gbuf == 0){
+ ilog("empty line [%d]: %r", bin.fid);
+ gbufp = "";
+ return -1;
+ }
+ logit(gbuf);
+ }
+ return *gbufp++;
+}
+
+static void
+ungetc(void)
+{
+ if(gbufp > gbuf)
+ gbufp--;
+}
+
+static int
+peekc(void)
+{
+ return *gbufp;
+}
+
+#ifdef normal
+
+static int
+getc(void)
+{
+ return Bgetc(&bin);
+}
+
+static void
+ungetc(void)
+{
+ Bungetc(&bin);
+}
+
+static int
+peekc(void)
+{
+ int c;
+
+ c = Bgetc(&bin);
+ Bungetc(&bin);
+ return c;
+}
+#endif
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/imap4d.h
@@ -1,0 +1,395 @@
+/*
+ * mailbox and message representations
+ *
+ * these structures are allocated with emalloc and must be explicitly freed
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <avl.h>
+#include <bin.h>
+
+typedef struct Box Box;
+typedef struct Header Header;
+typedef struct Maddr Maddr;
+typedef struct Mblock Mblock;
+typedef struct Mimehdr Mimehdr;
+typedef struct Msg Msg;
+typedef struct Namedint Namedint;
+typedef struct Pair Pair;
+typedef struct Uidplus Uidplus;
+
+enum
+{
+ Stralloc = 32, /* characters allocated at a time */
+ Bufsize = 8*1024, /* size of transfer block */
+ Ndigest = 40, /* length of digest string */
+ Nuid = 10, /* length of .imp uid string */
+ Nflags = 8, /* length of .imp flag string */
+ Locksecs = 5 * 60, /* seconds to wait for acquiring a locked file */
+ Pathlen = 256, /* max. length of upas/fs mbox name */
+ Filelen = 32, /* max. length of a file in a upas/fs mbox */
+ Userlen = 64, /* max. length of user's name */
+
+ Mutf7max = 6, /* max bytes for a mutf7 character: &bbbb- */
+
+ /*
+ * message flags
+ */
+ Fseen = 1 << 0,
+ Fanswered = 1 << 1,
+ Fflagged = 1 << 2,
+ Fdeleted = 1 << 3,
+ Fdraft = 1 << 4,
+ Frecent = 1 << 5,
+};
+
+typedef struct Fstree Fstree;
+struct Fstree {
+ Avl;
+ Msg *m;
+};
+
+struct Box
+{
+ char *name; /* path name of mailbox */
+ char *fs; /* fs name of mailbox */
+ char *fsdir; /* /mail/fs/box->fs */
+ char *imp; /* path name of .imp file */
+ uchar writable; /* can write back messages? */
+ uchar dirtyimp; /* .imp file needs to be written? */
+ uchar sendflags; /* need flags update */
+ Qid qid; /* qid of fs mailbox */
+ Qid impqid; /* qid of .imp when last synched */
+ long mtime; /* file mtime when last read */
+ uint max; /* maximum msgs->seq, same as number of messages */
+ uint toldmax; /* last value sent to client */
+ uint recent; /* number of recently received messaged */
+ uint toldrecent; /* last value sent to client */
+ uint uidnext; /* next uid value assigned to a message */
+ uint uidvalidity; /* uid of mailbox */
+ Msg *msgs; /* msgs in uid order */
+ Avltree *fstree; /* msgs in upas/fs order */
+};
+
+/*
+ * fields of Msg->info
+ */
+enum
+{
+ /*
+ * read from upasfs
+ */
+ Ifrom,
+ Ito,
+ Icc,
+ Ireplyto,
+ Iunixdate,
+ Isubject,
+ Itype,
+ Idisposition,
+ Ifilename,
+ Idigest,
+ Ibcc,
+ Iinreplyto,
+ Idate,
+ Isender,
+ Imessageid,
+ Ilines, /* number of lines of raw body */
+ Isize,
+// Iflags,
+// Idatesec
+
+ Imax
+};
+
+struct Header
+{
+ char *buf; /* header, including terminating \r\n */
+ uint size; /* strlen(buf) */
+ uint lines; /* number of \n characters in buf */
+
+ /*
+ * pre-parsed mime headers
+ */
+ Mimehdr *type; /* content-type */
+ Mimehdr *id; /* content-id */
+ Mimehdr *description; /* content-description */
+ Mimehdr *encoding; /* content-transfer-encoding */
+// Mimehdr *md5; /* content-md5 */
+ Mimehdr *disposition; /* content-disposition */
+ Mimehdr *language; /* content-language */
+};
+
+struct Msg
+{
+ Msg *next;
+ Msg *kids;
+ Msg *parent;
+ char *fsdir; /* box->fsdir of enclosing message */
+ Header head; /* message header */
+ Header mime; /* mime header from enclosing multipart spec */
+ int flags;
+ uchar sendflags; /* flags value needs to be sent to client */
+ uchar expunged; /* message actually expunged, but not yet reported to client */
+ uchar matched; /* search succeeded? */
+ uint uid; /* imap unique identifier */
+ uint seq; /* position in box; 1 is oldest */
+ uint id; /* number of message directory in upas/fs */
+ char *fs; /* name of message directory */
+ char *efs; /* pointer after / in fs; enough space for file name */
+
+ uint size; /* size of fs/rawbody, in bytes, with \r added before \n */
+ uint lines; /* number of lines in rawbody */
+
+ char *ibuf;
+ char *info[Imax]; /* all info about message */
+
+ char *unixdate;
+ Maddr *unixfrom;
+
+ Maddr *to; /* parsed out address lines */
+ Maddr *from;
+ Maddr *replyto;
+ Maddr *sender;
+ Maddr *cc;
+ Maddr *bcc;
+};
+
+/*
+ * pre-parsed header lines
+ */
+struct Maddr
+{
+ char *personal;
+ char *box;
+ char *host;
+ Maddr *next;
+};
+
+struct Mimehdr
+{
+ char *s;
+ char *t;
+ Mimehdr *next;
+};
+
+/*
+ * mapping of integer & names
+ */
+struct Namedint
+{
+ char *name;
+ int v;
+};
+
+/*
+ * lock for all mail file operations
+ */
+struct Mblock
+{
+ int fd;
+};
+
+/*
+ * parse nodes for imap4rev1 protocol
+ *
+ * important: all of these items are allocated
+ * in one can, so they can be tossed out at the same time.
+ * this allows leakless parse error recovery by simply tossing the can away.
+ * however, it means these structures cannot be mixed with the mailbox structures
+ */
+
+typedef struct Fetch Fetch;
+typedef struct Nlist Nlist;
+typedef struct Slist Slist;
+typedef struct Msgset Msgset;
+typedef struct Store Store;
+typedef struct Search Search;
+
+/*
+ * parse tree for fetch command
+ */
+enum
+{
+ Fenvelope,
+ Fflags,
+ Finternaldate,
+ Frfc822,
+ Frfc822head,
+ Frfc822size,
+ Frfc822text,
+ Fbodystruct,
+ Fuid,
+ Fbody, /* BODY */
+ Fbodysect, /* BODY [...] */
+ Fbodypeek,
+
+ Fmax
+};
+
+enum
+{
+ FPall,
+ FPhead,
+ FPheadfields,
+ FPheadfieldsnot,
+ FPmime,
+ FPtext,
+
+ FPmax
+};
+
+struct Fetch
+{
+ uchar op; /* F.* operator */
+ uchar part; /* FP.* subpart for body[] & body.peek[]*/
+ uchar partial; /* partial fetch? */
+ long start; /* partial fetch amounts */
+ long size;
+ Nlist *sect;
+ Slist *hdrs;
+ Fetch *next;
+};
+
+/*
+ * status items
+ */
+enum{
+ Smessages = 1 << 0,
+ Srecent = 1 << 1,
+ Suidnext = 1 << 2,
+ Suidvalidity = 1 << 3,
+ Sunseen = 1 << 4,
+};
+
+/*
+ * parse tree for store command
+ */
+enum
+{
+ Stflags,
+ Stflagssilent,
+
+ Stmax
+};
+
+struct Store
+{
+ uchar sign;
+ uchar op;
+ int flags;
+};
+
+/*
+ * parse tree for search command
+ */
+enum
+{
+ SKnone,
+
+ SKcharset,
+
+ SKall,
+ SKanswered,
+ SKbcc,
+ SKbefore,
+ SKbody,
+ SKcc,
+ SKdeleted,
+ SKdraft,
+ SKflagged,
+ SKfrom,
+ SKheader,
+ SKkeyword,
+ SKlarger,
+ SKnew,
+ SKnot,
+ SKold,
+ SKon,
+ SKor,
+ SKrecent,
+ SKseen,
+ SKsentbefore,
+ SKsenton,
+ SKsentsince,
+ SKset,
+ SKsince,
+ SKsmaller,
+ SKsubject,
+ SKtext,
+ SKto,
+ SKuid,
+ SKunanswered,
+ SKundeleted,
+ SKundraft,
+ SKunflagged,
+ SKunkeyword,
+ SKunseen,
+
+ SKmax
+};
+
+struct Search
+{
+ int key;
+ char *s;
+ char *hdr;
+ uint num;
+ int year;
+ int mon;
+ int mday;
+ Msgset *set;
+ Search *left;
+ Search *right;
+ Search *next;
+};
+
+struct Nlist
+{
+ uint n;
+ Nlist *next;
+};
+
+struct Slist
+{
+ char *s;
+ Slist *next;
+};
+
+struct Msgset
+{
+ uint from;
+ uint to;
+ Msgset *next;
+};
+
+struct Pair
+{
+ uint start;
+ uint stop;
+};
+
+struct Uidplus
+{
+ uint uid;
+ uint uidvalidity;
+ Uidplus *next;
+};
+
+extern Bin *parsebin;
+extern Biobuf bout;
+extern Biobuf bin;
+extern char username[Userlen];
+extern char mboxdir[Pathlen];
+extern char *fetchpartnames[FPmax];
+extern char *binupas;
+extern char *site;
+extern char *remote;
+extern int debug;
+extern char logfile[28];
+extern Uidplus *uidlist;
+extern Uidplus **uidtl;
+
+#include "fns.h"
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/imp.c
@@ -1,0 +1,315 @@
+#include "imap4d.h"
+
+static char magic[] = "imap internal mailbox description\n";
+
+/* another appearance of this nasty hack. */
+typedef struct{
+ Avl;
+ Msg *m;
+}Mtree;
+
+static Avltree *mtree;
+static Bin *mbin;
+
+static int
+mtreecmp(Avl *va, Avl *vb)
+{
+ Mtree *a, *b;
+
+ a = (Mtree*)va;
+ b = (Mtree*)vb;
+ return strcmp(a->m->info[Idigest], b->m->info[Idigest]);
+}
+
+static Namedint flagcmap[Nflags] =
+{
+ {"s", Fseen},
+ {"a", Fanswered},
+ {"f", Fflagged},
+ {"D", Fdeleted},
+ {"d", Fdraft},
+ {"r", Frecent},
+};
+
+static int
+parseflags(char *flags)
+{
+ int i, f;
+
+ f = 0;
+ for(i = 0; i < Nflags; i++){
+ if(flags[i] == '-')
+ continue;
+ if(flags[i] != flagcmap[i].name[0])
+ return 0;
+ f |= flagcmap[i].v;
+ }
+ return f;
+}
+
+static int
+impflags(Box *box, Msg *m, char *flags)
+{
+ int f;
+
+ f = parseflags(flags);
+ /*
+ * recent flags are set until the first time message's box is selected or examined.
+ * it may be stored in the file as a side effect of a status or subscribe command;
+ * if so, clear it out.
+ */
+ if((f & Frecent) && strcmp(box->fs, "imap") == 0)
+ box->dirtyimp = 1;
+ f |= m->flags & Frecent;
+
+ /*
+ * all old messages with changed flags should be reported to the client
+ */
+ if(m->uid && m->flags != f){
+ box->sendflags = 1;
+ m->sendflags = 1;
+ }
+ m->flags = f;
+ return 1;
+}
+
+/*
+ * considerations:
+ * . messages can be deleted by another agent
+ * . we might still have a Msg for an expunged message,
+ * because we haven't told the client yet.
+ * . we can have a Msg without a .imp entry.
+ * . flag information is added at the end of the .imp by copy & append
+ */
+
+static int
+rdimp(Biobuf *b, Box *box)
+{
+ char *s, *f[4];
+ uint u;
+ Msg *m, m0;
+ Mtree t, *p;
+
+ memset(&m0, 0, sizeof m0);
+ for(; s = Brdline(b, '\n'); ){
+ s[Blinelen(b) - 1] = 0;
+ if(tokenize(s, f, nelem(f)) != 3)
+ return -1;
+ u = strtoul(f[1], 0, 10);
+
+ memset(&t, 0, sizeof t);
+ m0.info[Idigest] = f[0];
+ t.m = &m0;
+ p = (Mtree*)avllookup(mtree, &t);
+ if(p){
+ m = p->m;
+ if(m->uid && m->uid != u){
+ ilog("dup? %ud %ud %s", u, m->uid, f[0]);
+ continue;
+ }
+ if(m->uid >= box->uidnext){
+ ilog("uid %ud >= %ud\n", m->uid, box->uidnext);
+ box->uidnext = m->uid;
+ }
+ if(m->uid == 0)
+ m->flags = 0;
+ if(impflags(box, m, f[2]) == -1)
+ return -1;
+ m->uid = u;
+ }else{
+ /*
+ * message has been deleted.
+ */
+// ilog("flags, uid dropped on floor [%s, %ud]", m0.info[Idigest], u);
+ }
+ }
+ return 0;
+}
+
+enum{
+ Rmagic,
+ Rrdstr,
+ Rtok,
+ Rvalidity,
+ Ruidnext,
+};
+
+static char *rtab[] = {
+ "magic",
+ "rdstr",
+ "tok",
+ "val",
+ "uidnext"
+};
+
+char*
+sreason(int r)
+{
+ if(r >= 0 && r <= nelem(rtab))
+ return rtab[r];
+ return "*GOK*";
+}
+
+static int
+verscmp(Biobuf *b, Box *box, int *reason)
+{
+ char *s, *f[3];
+ int n;
+ uint u, v;
+
+ n = -1;
+ *reason = Rmagic;
+ if(s = Brdstr(b, '\n', 0))
+ n = strcmp(s, magic);
+ free(s);
+ if(n == -1)
+ return -1;
+ n = -1;
+ v = box->uidvalidity;
+ if((s = Brdstr(b, '\n', 1)) && ++*reason)
+ if(tokenize(s, f, nelem(f)) == 2 && ++*reason)
+ if((u = strtoul(f[0], 0, 10)) == v || v == 0 && ++*reason)
+ if((v = strtoul(f[1], 0, 10)) >= box->uidnext && ++*reason){
+ box->uidvalidity = u;
+ box->uidnext = v;
+ n = 0;
+ }
+ free(s);
+ return n;
+}
+
+int
+parseimp(Biobuf *b, Box *box)
+{
+ int r, reason;
+ Msg *m;
+ Mtree *p;
+
+ if(verscmp(b, box, &reason) == -1)
+ return -1;
+ mtree = avlcreate(mtreecmp);
+ r = 0;
+ for(m = box->msgs; m; m = m->next)
+ r++;
+ p = binalloc(&mbin, r*sizeof *p, 1);
+ if(p == nil)
+ bye("no memory");
+ for(m = box->msgs; m; m = m->next){
+ p->m = m;
+ avlinsert(mtree, p);
+ p++;
+ }
+ r = rdimp(b, box);
+ binfree(&mbin);
+ free(mtree);
+ return r;
+}
+
+static void
+wrimpflags(char *buf, int flags, int killrecent)
+{
+ int i;
+
+ if(killrecent)
+ flags &= ~Frecent;
+ memset(buf, '-', Nflags);
+ for(i = 0; i < Nflags; i++)
+ if(flags & flagcmap[i].v)
+ buf[i] = flagcmap[i].name[0];
+ buf[i] = 0;
+}
+
+int
+wrimp(Biobuf *b, Box *box)
+{
+ char buf[16];
+ int i;
+ Msg *m;
+
+ box->dirtyimp = 0;
+ Bprint(b, "%s", magic);
+ Bprint(b, "%.*ud %.*ud\n", Nuid, box->uidvalidity, Nuid, box->uidnext);
+ i = strcmp(box->fs, "imap") == 0;
+ for(m = box->msgs; m != nil; m = m->next){
+ if(m->expunged)
+ continue;
+ wrimpflags(buf, m->flags, i);
+ Bprint(b, "%.*s %.*ud %s\n", Ndigest, m->info[Idigest], Nuid, m->uid, buf);
+ }
+ return 0;
+}
+
+static uint
+scanferdup(Biobuf *b, char *digest, int *flags, vlong *pos)
+{
+ char *s, *f[4];
+ uint uid;
+
+ uid = 0;
+ for(; s = Brdline(b, '\n'); ){
+ s[Blinelen(b) - 1] = 0;
+ if(tokenize(s, f, nelem(f)) != 3)
+ return ~0;
+ if(strcmp(f[0], digest) == 0){
+ uid = strtoul(f[1], 0, 10);
+// fprint(2, "digest %s matches uid %ud\n", f[0], uid);
+ *flags |= parseflags(f[2]);
+ break;
+ }
+ *pos += Blinelen(b);
+ }
+ return uid;
+}
+
+int
+appendimp(char *bname, char *digest, int flags, Uidplus *u)
+{
+ char buf[16], *iname;
+ int fd, reason;
+ uint dup;
+ vlong pos;
+ Biobuf b;
+ Box box;
+
+ dup = 0;
+ pos = 0;
+ memset(&box, 0, sizeof box);
+ iname = impname(bname);
+ fd = cdopen(mboxdir, iname, ORDWR);
+ if(fd == -1){
+ fd = cdcreate(mboxdir, iname, OWRITE, 0664);
+ if(fd == -1)
+ return -1;
+ box.uidvalidity = time(0);
+ box.uidnext = 1;
+ }else{
+ dup = ~0;
+ Binit(&b, fd, OREAD);
+ if(verscmp(&b, &box, &reason) == -1)
+ ilog("bad verscmp %s", sreason(reason));
+ else{
+ pos = Bseek(&b, 0, 1);
+ dup = scanferdup(&b, digest, &flags, &pos);
+ }
+ Bterm(&b);
+ }
+ if(dup == ~0){
+ close(fd);
+ return -1;
+ }
+ Binit(&b, fd, OWRITE);
+ if(dup == 0){
+ Bseek(&b, 0, 0);
+ Bprint(&b, "%s", magic);
+ Bprint(&b, "%.*ud %.*ud\n", Nuid, box.uidvalidity, Nuid, box.uidnext + 1);
+ Bseek(&b, 0, 2);
+ }else
+ Bseek(&b, pos, 0);
+ wrimpflags(buf, flags, 0);
+ Bprint(&b, "%.*s %.*ud %s\n", Ndigest, digest, Nuid, dup? dup: box.uidnext, buf);
+ Bterm(&b);
+ close(fd);
+ u->uidvalidity = box.uidvalidity;
+ u->uid = box.uidnext;
+ return 0;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/list.c
@@ -1,0 +1,425 @@
+#include "imap4d.h"
+
+enum{
+ Mfolder = 0,
+ Mbox,
+ Mdir,
+};
+
+ char subscribed[] = "imap.subscribed";
+static int ldebug;
+
+#define dprint(...) if(ldebug)fprint(2, __VA_ARGS__); else {}
+
+static int lmatch(char*, char*, char*);
+
+static int
+mopen(char *box, int mode)
+{
+ char buf[Pathlen];
+
+ if(!strcmp(box, "..") || strstr(box, "/.."))
+ return -1;
+ return cdopen(mboxdir, encfs(buf, sizeof buf, box), mode);
+}
+
+static Dir*
+mdirstat(char *box)
+{
+ char buf[Pathlen];
+
+ return cddirstat(mboxdir, encfs(buf, sizeof buf, box));
+}
+
+static long
+mtime(char *box)
+{
+ long mtime;
+ Dir *d;
+
+ mtime = 0;
+ if(d = mdirstat(box))
+ mtime = d->mtime;
+ free(d);
+ return mtime;
+}
+
+static int
+mokmbox(char *s)
+{
+ char *p;
+
+ if(p = strrchr(s, '/'))
+ s = p + 1;
+ if(!strcmp(s, "mbox"))
+ return 1;
+ return okmbox(s);
+}
+
+/*
+ * paranoid check to prevent accidents
+ */
+/*
+ * BOTCH: we're taking it upon ourselves to
+ * identify mailboxes. this is a bad idea.
+ * keep in sync with ../fs/mdir.c
+ */
+static 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
+chkmbox(char *path, int mode)
+{
+ char buf[32];
+ int i, r, n, fd, type;
+ uvlong uv;
+ Dir *d;
+
+ type = Mbox;
+ if(mode & DMDIR)
+ type = Mdir;
+ fd = mopen(path, OREAD);
+ if(fd == -1)
+ return -1;
+ r = -1;
+ if(type == Mdir && (n = dirread(fd, &d)) > 0){
+ r = Mfolder;
+ for(i = 0; i < n; i++)
+ if(!dirskip(d + i, &uv)){
+ r = Mdir;
+ break;
+ }
+ free(d);
+ }else if(type == Mdir)
+ r = Mdir;
+ else if(type == Mbox){
+ if(pread(fd, buf, sizeof buf, 0) == sizeof buf)
+ if(!strncmp(buf, "From ", 5))
+ r = Mbox;
+ }
+ close(fd);
+ return r;
+}
+
+static int
+chkmboxpath(char *f)
+{
+ int r;
+ Dir *d;
+
+ r = -1;
+ if(d = mdirstat(f))
+ r = chkmbox(f, d->mode);
+ free(d);
+ return r;
+}
+
+static char*
+appendwd(char *nwd, int n, char *wd, char *a)
+{
+ if(wd[0] && a[0] != '/')
+ snprint(nwd, n, "%s/%s", wd, a);
+ else
+ snprint(nwd, n, "%s", a);
+ return nwd;
+}
+
+static int
+output(char *cmd, char *wd, Dir *d, int term)
+{
+ char path[Pathlen], dec[Pathlen], *s, *flags;
+
+ appendwd(path, sizeof path, wd, d->name);
+ dprint("Xoutput %s %s %d\n", wd, d->name, term);
+ switch(chkmbox(path, d->mode)){
+ default:
+ return 0;
+ case Mfolder:
+ flags = "(\\Noselect)";
+ break;
+ case Mdir:
+ case Mbox:
+ s = impname(path);
+ if(s != nil && mtime(s) < d->mtime)
+ flags = "(\\Noinferiors \\Marked)";
+ else
+ flags = "(\\Noinferiors)";
+ break;
+ }
+
+ if(!term)
+ return 1;
+
+ if(s = strmutf7(decfs(dec, sizeof dec, path)))
+ Bprint(&bout, "* %s %s \"/\" %#Z\r\n", cmd, flags, s);
+ return 1;
+}
+
+static int
+rematch(char *cmd, char *wd, char *pat, Dir *d)
+{
+ char nwd[Pathlen];
+
+ appendwd(nwd, sizeof nwd, wd, d->name);
+ if(d->mode & DMDIR)
+ if(chkmbox(nwd, d->mode) == Mfolder)
+ if(lmatch(cmd, pat, nwd))
+ return 1;
+ return 0;
+}
+
+static int
+match(char *cmd, char *wd, char *pat, Dir *d, int i)
+{
+ char *p, *p1;
+ int m, n;
+ Rune r, r1;
+
+ m = 0;
+ for(p = pat; ; p = p1){
+ n = chartorune(&r, p);
+ p1 = p + n;
+ dprint("r = %C [%.2ux]\n", r, r);
+ switch(r){
+ case '*':
+ case '%':
+ for(r1 = 1; r1;){
+ if(match(cmd, wd, p1, d, i))
+ if(output(cmd, wd, d, 0)){
+ m++;
+ break;
+ }
+ i += chartorune(&r1, d->name + i);
+ }
+ if(r == '*' && rematch(cmd, wd, p, d))
+ return 1;
+ if(m > 0)
+ return 1;
+ break;
+ case '/':
+ return rematch(cmd, wd, p1, d);
+ default:
+ chartorune(&r1, d->name + i);
+ if(r1 != r)
+ return 0;
+ if(r == 0)
+ return output(cmd, wd, d, 1);
+ dprint(" r %C ~ %C [%.2ux]\n", r, r1, r1);
+ i += n;
+ break;
+ }
+ }
+}
+
+static int
+lmatch(char *cmd, char *pat, char *wd)
+{
+ char dec[Pathlen];
+ int fd, n, m, i;
+ Dir *d;
+
+ if((fd = mopen(wd[0]? wd: ".", OREAD)) == -1)
+ return -1;
+ if(wd[0])
+ dprint("wd %s\n", wd);
+ m = 0;
+ for(;;){
+ n = dirread(fd, &d);
+ if(n <= 0)
+ break;
+ for(i = 0; i < n; i++)
+ if(mokmbox(d[i].name)){
+ d[i].name = decfs(dec, sizeof dec, d[i].name);
+ m += match(cmd, wd, pat, d + i, 0);
+ }
+ free(d);
+ }
+ close(fd);
+ return m;
+}
+
+int
+listboxes(char *cmd, char *ref, char *pat)
+{
+ char buf[Pathlen];
+
+ pat = appendwd(buf, sizeof buf, ref, pat);
+ return lmatch(cmd, pat, "") > 0;
+}
+
+static int
+opensubscribed(void)
+{
+ int fd;
+
+ fd = cdopen(mboxdir, subscribed, ORDWR);
+ if(fd >= 0)
+ return fd;
+ fd = cdcreate(mboxdir, subscribed, ORDWR, 0664);
+ if(fd < 0)
+ return -1;
+ fprint(fd, "#imap4 subscription list\nINBOX\n");
+ seek(fd, 0, 0);
+ return fd;
+}
+
+/*
+ * resistance to hand-edits
+ */
+static char*
+trim(char *s, int l)
+{
+ int c;
+
+ for(;; l--){
+ if(l == 0)
+ return 0;
+ c = s[l - 1];
+ if(c != '\t' && c != ' ')
+ break;
+ }
+ for(s[l] = 0; c = *s; s++)
+ if(c != '\t' && c != ' ')
+ break;
+ if(c == 0 || c == '#')
+ return 0;
+ return s;
+}
+
+static int
+poutput(char *cmd, char *f, int term)
+{
+ char *p, *wd;
+ int r;
+ Dir *d;
+
+ if(!mokmbox(f) || !(d = mdirstat(f)))
+ return 0;
+ wd = "";
+ if(p = strrchr(f, '/')){
+ *p = 0;
+ wd = f;
+ }
+ r = output(cmd, wd, d, term);
+ if(p)
+ *p = '/';
+ free(d);
+ return r;
+}
+
+static int
+pmatch(char *cmd, char *pat, char *f, int i)
+{
+ char *p, *p1;
+ int m, n;
+ Rune r, r1;
+
+ dprint("pmatch pat[%s] f[%s]\n", pat, f + i);
+ m = 0;
+ for(p = pat; ; p = p1){
+ n = chartorune(&r, p);
+ p1 = p + n;
+ switch(r){
+ case '*':
+ case '%':
+ for(r1 = 1; r1;){
+ if(pmatch(cmd, p1, f, i))
+ if(poutput(cmd, f, 0)){
+ m++;
+ break;
+ }
+ i += chartorune(&r1, f + i);
+ if(r == '%' && r1 == '/')
+ break;
+ }
+ if(m > 0)
+ return 1;
+ break;
+ default:
+ chartorune(&r1, f + i);
+ if(r1 != r)
+ return 0;
+ if(r == 0)
+ return poutput(cmd, f, 1);
+ i += n;
+ break;
+ }
+ }
+}
+
+int
+lsubboxes(char *cmd, char *ref, char *pat)
+{
+ char *s, buf[Pathlen];
+ int r, fd;
+ Biobuf b;
+ Mblock *l;
+
+ pat = appendwd(buf, sizeof buf, ref, pat);
+ if((l = mblock()) == nil)
+ return 0;
+ fd = opensubscribed();
+ r = 0;
+ Binit(&b, fd, OREAD);
+ while(s = Brdline(&b, '\n'))
+ if(s = trim(s, Blinelen(&b) - 1))
+ r += pmatch(cmd, pat, s, 0);
+ Bterm(&b);
+ close(fd);
+ mbunlock(l);
+ return r;
+}
+
+int
+subscribe(char *mbox, int how)
+{
+ char *s, *in, *ein;
+ int fd, tfd, ok, l;
+ Mblock *mb;
+
+ if(cistrcmp(mbox, "inbox") == 0)
+ mbox = "INBOX";
+ if((mb = mblock()) == nil)
+ return 0;
+ fd = opensubscribed();
+ if(fd < 0 || (in = readfile(fd)) == nil){
+ close(fd);
+ mbunlock(mb);
+ return 0;
+ }
+ l = strlen(mbox);
+ s = strstr(in, mbox);
+ while(s != nil && (s != in && s[-1] != '\n' || s[l] != '\n'))
+ s = strstr(s + 1, mbox);
+ ok = 0;
+ if(how == 's' && s == nil){
+ if(chkmboxpath(mbox) > 0)
+ if(fprint(fd, "%s\n", mbox) > 0)
+ ok = 1;
+ }else if(how == 'u' && s != nil){
+ ein = strchr(s, 0);
+ memmove(s, &s[l+1], ein - &s[l+1]);
+ ein -= l + 1;
+ tfd = cdopen(mboxdir, subscribed, OWRITE|OTRUNC);
+ if(tfd >= 0 && pwrite(fd, in, ein - in, 0) == ein - in)
+ ok = 1;
+ close(tfd);
+ }else
+ ok = 1;
+ close(fd);
+ mbunlock(mb);
+ return ok;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/mbox.c
@@ -1,0 +1,630 @@
+#include "imap4d.h"
+
+static int fsctl = -1;
+static char Ecanttalk[] = "can't talk to mail server";
+
+static void
+fsinit(void)
+{
+ if(fsctl != -1)
+ return;
+ fsctl = open("/mail/fs/ctl", ORDWR);
+ if(fsctl == -1)
+ bye(Ecanttalk);
+}
+
+static void
+boxflags(Box *box)
+{
+ Msg *m;
+
+ box->recent = 0;
+ for(m = box->msgs; m != nil; m = m->next){
+ if(m->uid == 0){
+ // fprint(2, "unassigned uid %s\n", m->info[Idigest]);
+ box->dirtyimp = 1;
+ m->uid = box->uidnext++;
+ }
+ if(m->flags & Frecent)
+ box->recent++;
+ }
+}
+
+/*
+ * try to match permissions with mbox
+ */
+static int
+createimp(Box *box, Qid *qid)
+{
+ int fd;
+ long mode;
+ Dir *d;
+
+ fd = cdcreate(mboxdir, box->imp, OREAD, 0664);
+ if(fd < 0)
+ return -1;
+ d = cddirstat(mboxdir, box->name);
+ if(d != nil){
+ mode = d->mode & 0777;
+ nulldir(d);
+ d->mode = mode;
+ dirfwstat(fd, d);
+ free(d);
+ }
+ if(fqid(fd, qid) < 0){
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+/*
+ * read in the .imp file, or make one if it doesn't exist.
+ * make sure all flags and uids are consistent.
+ * return the mailbox lock.
+ */
+static Mblock*
+openimp(Box *box, int new)
+{
+ char buf[ERRMAX];
+ int fd;
+ Biobuf b;
+ Mblock *ml;
+ Qid qid;
+
+ ml = mblock();
+ if(ml == nil)
+ return nil;
+ fd = cdopen(mboxdir, box->imp, OREAD);
+ if(fd < 0 || fqid(fd, &qid) < 0){
+ if(fd < 0){
+ errstr(buf, sizeof buf);
+ if(cistrstr(buf, "does not exist") == nil)
+ ilog("imp: %s: %s", box->imp, buf);
+ else
+ debuglog("imp: %s: %s .. creating", box->imp, buf);
+ }else{
+ close(fd);
+ ilog("%s: bogus imp: bad qid: recreating", box->imp);
+ }
+ fd = createimp(box, &qid);
+ if(fd < 0){
+ ilog("createimp fails: %r");
+ mbunlock(ml);
+ return nil;
+ }
+ box->dirtyimp = 1;
+ if(box->uidvalidity == 0){
+ ilog("set uidvalidity %lud [new]\n", box->uidvalidity);
+ box->uidvalidity = box->mtime;
+ }
+ box->impqid = qid;
+ new = 1;
+ }else if(qid.path != box->impqid.path || qid.vers != box->impqid.vers){
+ Binit(&b, fd, OREAD);
+ if(parseimp(&b, box) == -1){
+ ilog("%s: bogus imp: parse failure", box->imp);
+ box->dirtyimp = 1;
+ if(box->uidvalidity == 0){
+ ilog("set uidvalidity %lud [parseerr]\n", box->uidvalidity);
+ box->uidvalidity = box->mtime;
+ }
+ }
+ Bterm(&b);
+ box->impqid = qid;
+ new = 1;
+ }
+ if(new)
+ boxflags(box);
+ close(fd);
+ return ml;
+}
+
+/*
+ * mailbox is unreachable, so mark all messages expunged
+ * clean up .imp files as well.
+ */
+static void
+mboxgone(Box *box)
+{
+ char buf[ERRMAX];
+ Msg *m;
+
+ rerrstr(buf, ERRMAX);
+ if(strstr(buf, "hungup channel"))
+ bye(Ecanttalk);
+// too smart.
+// if(cdexists(mboxdir, box->name) < 0)
+// cdremove(mboxdir, box->imp);
+ for(m = box->msgs; m != nil; m = m->next)
+ m->expunged = 1;
+ ilog("mboxgone");
+ box->writable = 0;
+}
+
+/*
+ * read messages in the mailbox
+ * mark message that no longer exist as expunged
+ * returns -1 for failure, 0 if no new messages, 1 if new messages.
+ */
+enum {
+ Gone = 2, /* don't unexpunge messages */
+};
+
+static int
+readbox(Box *box)
+{
+ char buf[ERRMAX];
+ int i, n, fd, new, id;
+ Dir *d;
+ Msg *m, *last;
+
+ fd = cdopen(box->fsdir, ".", OREAD);
+ if(fd == -1){
+goinggoinggone:
+ rerrstr(buf, ERRMAX);
+ ilog("upas/fs stat of %s/%s aka %s failed: %r",
+ username, box->name, box->fsdir);
+ mboxgone(box);
+ return -1;
+ }
+
+ if((d = dirfstat(fd)) == nil){
+ close(fd);
+ goto goinggoinggone;
+ }
+ box->mtime = d->mtime;
+ box->qid = d->qid;
+ last = nil;
+ for(m = box->msgs; m != nil; m = m->next){
+ last = m;
+ m->expunged |= Gone;
+ }
+ new = 0;
+ free(d);
+
+ for(;;){
+ n = dirread(fd, &d);
+ if(n <= 0){
+ close(fd);
+ if(n == -1)
+ goto goinggoinggone;
+ break;
+ }
+ for(i = 0; i < n; i++){
+ if((d[i].qid.type & QTDIR) == 0)
+ continue;
+ id = atoi(d[i].name);
+ if(m = fstreefind(box, id)){
+ m->expunged &= ~Gone;
+ continue;
+ }
+ new = 1;
+ m = MKZ(Msg);
+ m->id = id;
+ m->fsdir = box->fsdir;
+ m->fs = emalloc(2 * (Filelen + 1));
+ m->efs = seprint(m->fs, m->fs + (Filelen + 1), "%ud/", id);
+ m->size = ~0UL;
+ m->lines = ~0UL;
+ m->flags = Frecent;
+ if(!msginfo(m))
+ freemsg(0, m);
+ else{
+ fstreeadd(box, m);
+ if(last == nil)
+ box->msgs = m;
+ else
+ last->next = m;
+ last = m;
+ }
+ }
+ free(d);
+ }
+
+ /* box->max is invalid here */
+ return new;
+}
+
+int
+uidcmp(void *va, void *vb)
+{
+ Msg **a, **b;
+
+ a = va;
+ b = vb;
+ return (*a)->uid - (*b)->uid;
+}
+
+static void
+sequence(Box *box)
+{
+ Msg **a, *m;
+ int n, i;
+
+ n = 0;
+ for(m = box->msgs; m; m = m->next)
+ n++;
+ a = ezmalloc(n * sizeof *a);
+ i = 0;
+ for(m = box->msgs; m; m = m->next)
+ a[i++] = m;
+ qsort(a, n, sizeof *a, uidcmp);
+ for(i = 0; i < n - 1; i++)
+ a[i]->next = a[i + 1];
+ for(i = 0; i < n; i++)
+ if(a[i]->seq && a[i]->seq != i + 1)
+ bye("internal error assigning message numbers");
+ else
+ a[i]->seq = i + 1;
+ box->msgs = nil;
+ if(n > 0){
+ a[n - 1]->next = nil;
+ box->msgs = a[0];
+ }
+ box->max = n;
+ memset(a, 0, n*sizeof *a);
+ free(a);
+}
+
+/*
+ * strategy:
+ * every mailbox file has an associated .imp file
+ * which maps upas/fs message digests to uids & message flags.
+ *
+ * the .imp files are locked by /mail/fs/usename/L.mbox.
+ * whenever the flags can be modified, the lock file
+ * should be opened, thereby locking the uid & flag state.
+ * for example, whenever new uids are assigned to messages,
+ * and whenever flags are changed internally, the lock file
+ * should be open and locked. this means the file must be
+ * opened during store command, and when changing the \seen
+ * flag for the fetch command.
+ *
+ * if no .imp file exists, a null one must be created before
+ * assigning uids.
+ *
+ * the .imp file has the following format
+ * imp : "imap internal mailbox description\n"
+ * uidvalidity " " uidnext "\n"
+ * messagelines
+ *
+ * messagelines :
+ * | messagelines digest " " uid " " flags "\n"
+ *
+ * uid, uidnext, and uidvalidity are 32 bit decimal numbers
+ * printed right justified in a field Nuid characters long.
+ * the 0 uid implies that no uid has been assigned to the message,
+ * but the flags are valid. note that message lines are in mailbox
+ * order, except possibly for 0 uid messages.
+ *
+ * digest is an ascii hex string Ndigest characters long.
+ *
+ * flags has a character for each of NFlag flag fields.
+ * if the flag is clear, it is represented by a "-".
+ * set flags are represented as a unique single ascii character.
+ * the currently assigned flags are, in order:
+ * Fseen s
+ * Fanswered a
+ * Fflagged f
+ * Fdeleted D
+ * Fdraft d
+ */
+
+Box*
+openbox(char *name, char *fsname, int writable)
+{
+ char err[ERRMAX];
+ int new;
+ Box *box;
+ Mblock *ml;
+
+ fsinit();
+if(!strcmp(name, "mbox"))ilog("open %F %q", name, fsname);
+ if(fprint(fsctl, "open %F %q", name, fsname) < 0){
+ rerrstr(err, sizeof err);
+ if(strstr(err, "file does not exist") == nil)
+ ilog("fs open %F as %s: %s", name, fsname, err);
+ if(strstr(err, "hungup channel"))
+ bye(Ecanttalk);
+ fprint(fsctl, "close %s", fsname);
+ return nil;
+ }
+
+ /*
+ * read box to find all messages
+ * each one has a directory, and is in numerical order
+ */
+ box = MKZ(Box);
+ box->writable = writable;
+ box->name = smprint("%s", name);
+ box->imp = smprint("%s.imp", name);
+ box->fs = smprint("%s", fsname);
+ box->fsdir = smprint("/mail/fs/%s", fsname);
+ box->uidnext = 1;
+ box->fstree = avlcreate(fstreecmp);
+ new = readbox(box);
+ if(new >= 0 && (ml = openimp(box, new))){
+ closeimp(box, ml);
+ sequence(box);
+ return box;
+ }
+ closebox(box, 0);
+ return nil;
+}
+
+/*
+ * careful: called by idle polling proc
+ */
+Mblock*
+checkbox(Box *box, int imped)
+{
+ int new;
+ Dir *d;
+ Mblock *ml;
+
+ if(box == nil)
+ return nil;
+
+ /*
+ * if stat fails, mailbox must be gone
+ */
+ d = cddirstat(box->fsdir, ".");
+ if(d == nil){
+ mboxgone(box);
+ return nil;
+ }
+ new = 0;
+ if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers
+ || box->mtime != d->mtime){
+ new = readbox(box);
+ if(new < 0){
+ free(d);
+ return nil;
+ }
+ }
+ free(d);
+ ml = openimp(box, new);
+ if(ml == nil){
+ ilog("openimp fails; box->writable = 0: %r");
+ box->writable = 0;
+ }else if(!imped){
+ closeimp(box, ml);
+ ml = nil;
+ }
+ if(new || box->dirtyimp)
+ sequence(box);
+ return ml;
+}
+
+/*
+ * close the .imp file, after writing out any changes
+ */
+void
+closeimp(Box *box, Mblock *ml)
+{
+ int fd;
+ Biobuf b;
+ Qid qid;
+
+ if(ml == nil)
+ return;
+ if(!box->dirtyimp){
+ mbunlock(ml);
+ return;
+ }
+ fd = cdcreate(mboxdir, box->imp, OWRITE, 0664);
+ if(fd < 0){
+ mbunlock(ml);
+ return;
+ }
+ Binit(&b, fd, OWRITE);
+ box->dirtyimp = 0;
+ wrimp(&b, box);
+ Bterm(&b);
+
+ if(fqid(fd, &qid) == 0)
+ box->impqid = qid;
+ close(fd);
+ mbunlock(ml);
+}
+
+void
+closebox(Box *box, int opened)
+{
+ Msg *m, *next;
+
+ /*
+ * make sure to leave the mailbox directory so upas/fs can close the mailbox
+ */
+ mychdir(mboxdir);
+
+ if(box->writable){
+ deletemsg(box, 0);
+ if(expungemsgs(box, 0))
+ closeimp(box, checkbox(box, 1));
+ }
+
+ if(fprint(fsctl, "close %s", box->fs) < 0 && opened)
+ bye(Ecanttalk);
+ for(m = box->msgs; m != nil; m = next){
+ next = m->next;
+ freemsg(box, m);
+ }
+ free(box->name);
+ free(box->fs);
+ free(box->fsdir);
+ free(box->imp);
+ free(box->fstree);
+ free(box);
+}
+
+int
+deletemsg(Box *box, Msgset *ms)
+{
+ char buf[Bufsize], *p, *start;
+ int ok;
+ Msg *m;
+
+ if(!box->writable)
+ return 0;
+
+ /*
+ * first pass: delete messages; gang the writes together for speed.
+ */
+ ok = 1;
+ start = seprint(buf, buf + sizeof buf, "delete %s", box->fs);
+ p = start;
+ for(m = box->msgs; m != nil; m = m->next)
+ if(ms == 0 || ms && inmsgset(ms, m->uid))
+ if((m->flags & Fdeleted) && !m->expunged){
+ m->expunged = 1;
+ p = seprint(p, buf + sizeof buf, " %ud", m->id);
+ if(p + 32 >= buf + sizeof buf){
+ if(write(fsctl, buf, p - buf) == -1)
+ bye(Ecanttalk);
+ p = start;
+ }
+ }
+ if(p != start && write(fsctl, buf, p - buf) == -1)
+ bye(Ecanttalk);
+ return ok;
+}
+
+/*
+ * second pass: remove the message structure,
+ * and renumber message sequence numbers.
+ * update messages counts in mailbox.
+ * returns true if anything changed.
+ */
+int
+expungemsgs(Box *box, int send)
+{
+ uint n;
+ Msg *m, *next, *last;
+
+ n = 0;
+ last = nil;
+ for(m = box->msgs; m != nil; m = next){
+ m->seq -= n;
+ next = m->next;
+ if(m->expunged){
+ if(send)
+ Bprint(&bout, "* %ud expunge\r\n", m->seq);
+ if(m->flags & Frecent)
+ box->recent--;
+ n++;
+ if(last == nil)
+ box->msgs = next;
+ else
+ last->next = next;
+ freemsg(box, m);
+ }else
+ last = m;
+ }
+ if(n){
+ box->max -= n;
+ box->dirtyimp = 1;
+ }
+ return n;
+}
+
+static char *stoplist[] =
+{
+ ".",
+ "dead.letter",
+ "forward",
+ "headers",
+ "imap.subscribed",
+ "mbox",
+ "names",
+ "pipefrom",
+ "pipeto",
+ 0
+};
+
+/*
+ * reject bad mailboxes based on mailbox name
+ */
+int
+okmbox(char *path)
+{
+ char *name;
+ int i, c;
+
+ name = strrchr(path, '/');
+ if(name == nil)
+ name = path;
+ else
+ name++;
+ if(strlen(name) + STRLEN(".imp") >= Pathlen)
+ return 0;
+ for(i = 0; stoplist[i]; i++)
+ if(strcmp(name, stoplist[i]) == 0)
+ return 0;
+ c = name[0];
+ if(c == 0 || c == '-' || c == '/'
+ || isdotdot(name)
+ || isprefix("L.", name)
+ || isprefix("imap-tmp.", name)
+ || issuffix("-", name)
+ || issuffix(".00", name)
+ || issuffix(".imp", name)
+ || issuffix(".idx", name))
+ return 0;
+
+ return 1;
+}
+
+int
+creatembox(char *mbox)
+{
+ fsinit();
+ if(fprint(fsctl, "create %q", mbox) > 0){
+ fprint(fsctl, "close %s", mbox);
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * rename mailbox. truncaes or removes the source.
+ * bug? is the lock required
+ * upas/fs helpfully moves our .imp file.
+ */
+int
+renamebox(char *from, char *to, int doremove)
+{
+ char *p;
+ int r;
+ Mblock *ml;
+
+ fsinit();
+ ml = mblock();
+ if(ml == nil)
+ return 0;
+ if(doremove)
+ r = fprint(fsctl, "rename %F %F", from, to);
+ else
+ r = fprint(fsctl, "rename -t %F %F", from, to);
+ if(r > 0){
+ if(p = strrchr(to, '/'))
+ p++;
+ else
+ p = to;
+ fprint(fsctl, "close %s", p);
+ }
+ mbunlock(ml);
+ return r > 0;
+}
+
+/*
+ * upas/fs likes us; he removes the .imp file
+ */
+int
+removembox(char *path)
+{
+ fsinit();
+ return fprint(fsctl, "remove %s", path) > 0;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/mkfile
@@ -1,0 +1,36 @@
+</$objtype/mkfile
+<../mkupas
+
+OFILES=\
+ auth.$O\
+ copy.$O\
+ csquery.$O\
+ date.$O\
+ debug.$O\
+ fetch.$O\
+ folder.$O\
+ fsenc.$O\
+ fstree.$O\
+ imp.$O\
+ imap4d.$O\
+ list.$O\
+ mbox.$O\
+ msg.$O\
+ mutf7.$O\
+ nodes.$O\
+ print.$O\
+ quota.$O\
+ search.$O\
+ store.$O\
+ utils.$O\
+
+HFILES=imap4d.h\
+ fns.h\
+
+TARG=imap4d
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/msg.c
@@ -1,0 +1,1507 @@
+#include "imap4d.h"
+
+static char *headaddrspec(char*, char*);
+static Maddr *headaddresses(void);
+static Maddr *headaddress(void);
+static char *headatom(char*);
+static int headchar(int eat);
+static char *headdomain(char*);
+static Maddr *headmaddr(Maddr*);
+static char *headphrase(char*, char*);
+static char *headquoted(int start, int stop);
+static char *headskipwhite(int);
+static void headskip(void);
+static char *headsubdomain(void);
+static char *headtext(void);
+static void headtoend(void);
+static char *headword(void);
+static void mimedescription(Header*);
+static void mimedisposition(Header*);
+static void mimeencoding(Header*);
+static void mimeid(Header*);
+static void mimelanguage(Header*);
+//static void mimemd5(Header*);
+static void mimetype(Header*);
+static int msgbodysize(Msg*);
+static int msgheader(Msg*, Header*, char*);
+
+/*
+ * stop list for header fields
+ */
+static char *headfieldstop = ":";
+static char *mimetokenstop = "()<>@,;:\\\"/[]?=";
+static char *headatomstop = "()<>@,;:\\\".[]";
+static uchar *headstr;
+static uchar *lastwhite;
+
+long
+selectfields(char *dst, long n, char *hdr, Slist *fields, int matches)
+{
+ char *s;
+ uchar *start;
+ long m, nf;
+ Slist *f;
+
+ headstr = (uchar*)hdr;
+ m = 0;
+ for(;;){
+ start = headstr;
+ s = headatom(headfieldstop);
+ if(s == nil)
+ break;
+ headskip();
+ for(f = fields; f != nil; f = f->next){
+ if(cistrcmp(s, f->s) == !matches){
+ nf = headstr - start;
+ if(m + nf > n)
+ return 0;
+ memmove(&dst[m], start, nf);
+ m += nf;
+ }
+ }
+ free(s);
+ }
+ if(m + 3 > n)
+ return 0;
+ dst[m++] = '\r';
+ dst[m++] = '\n';
+ dst[m] = '\0';
+ return m;
+}
+
+static Mimehdr*
+mkmimehdr(char *s, char *t, Mimehdr *next)
+{
+ Mimehdr *mh;
+
+ mh = MK(Mimehdr);
+ mh->s = s;
+ mh->t = t;
+ mh->next = next;
+ return mh;
+}
+
+static void
+freemimehdr(Mimehdr *mh)
+{
+ Mimehdr *last;
+
+ while(mh != nil){
+ last = mh;
+ mh = mh->next;
+ free(last->s);
+ free(last->t);
+ free(last);
+ }
+}
+
+static void
+freeheader(Header *h)
+{
+ freemimehdr(h->type);
+ freemimehdr(h->id);
+ freemimehdr(h->description);
+ freemimehdr(h->encoding);
+// freemimehdr(h->md5);
+ freemimehdr(h->disposition);
+ freemimehdr(h->language);
+ free(h->buf);
+}
+
+static void
+freemaddr(Maddr *a)
+{
+ Maddr *p;
+
+ while(a != nil){
+ p = a;
+ a = a->next;
+ free(p->personal);
+ free(p->box);
+ free(p->host);
+ free(p);
+ }
+}
+
+void
+freemsg(Box *box, Msg *m)
+{
+ Msg *k, *last;
+
+ if(box != nil)
+ fstreedelete(box, m);
+ free(m->ibuf);
+ freemaddr(m->to);
+ if(m->replyto != m->from)
+ freemaddr(m->replyto);
+ if(m->sender != m->from)
+ freemaddr(m->sender);
+ if(m->from != m->unixfrom)
+ freemaddr(m->from);
+ freemaddr(m->unixfrom);
+ freemaddr(m->cc);
+ freemaddr(m->bcc);
+ free(m->unixdate);
+ freeheader(&m->head);
+ freeheader(&m->mime);
+ for(k = m->kids; k != nil; ){
+ last = k;
+ k = k->next;
+ freemsg(0, last);
+ }
+ free(m->fs);
+ free(m);
+}
+
+uint
+msgsize(Msg *m)
+{
+ return m->head.size + m->size;
+}
+
+char*
+maddrstr(Maddr *a)
+{
+ char *host, *addr;
+
+ host = a->host;
+ if(host == nil)
+ host = "";
+ if(a->personal != nil)
+ addr = smprint("%s <%s@%s>", a->personal, a->box, host);
+ else
+ addr = smprint("%s@%s", a->box, host);
+ return addr;
+}
+
+int
+msgfile(Msg *m, char *f)
+{
+ if(strlen(f) > Filelen)
+ bye("internal error: msgfile name too long");
+ strcpy(m->efs, f);
+ return cdopen(m->fsdir, m->fs, OREAD);
+}
+
+int
+msgismulti(Header *h)
+{
+ return h->type != nil && cistrcmp("multipart", h->type->s) == 0;
+}
+
+int
+msgis822(Header *h)
+{
+ Mimehdr *t;
+
+ t = h->type;
+ return t != nil && cistrcmp("message", t->s) == 0 && cistrcmp("rfc822", t->t) == 0;
+}
+
+/*
+ * check if a message has been deleted by someone else
+ */
+void
+msgdead(Msg *m)
+{
+ if(m->expunged)
+ return;
+ *m->efs = '\0';
+ if(!cdexists(m->fsdir, m->fs))
+ m->expunged = 1;
+}
+
+static long
+msgreadfile(Msg *m, char *file, char **ss)
+{
+ char *s, buf[Bufsize];
+ int fd;
+ long n, nn;
+ vlong length;
+ Dir *d;
+
+ fd = msgfile(m, file);
+ if(fd < 0){
+ msgdead(m);
+ return -1;
+ }
+
+ n = read(fd, buf, Bufsize);
+ if(n < Bufsize){
+ close(fd);
+ if(n < 0){
+ *ss = nil;
+ return -1;
+ }
+ s = emalloc(n + 1);
+ memmove(s, buf, n);
+ s[n] = '\0';
+ *ss = s;
+ return n;
+ }
+
+ d = dirfstat(fd);
+ if(d == nil){
+ close(fd);
+ return -1;
+ }
+ length = d->length;
+ free(d);
+ nn = length;
+ s = emalloc(nn + 1);
+ memmove(s, buf, n);
+ if(nn > n)
+ nn = readn(fd, s + n, nn - n) + n;
+ close(fd);
+ if(nn != length){
+ free(s);
+ return -1;
+ }
+ s[nn] = '\0';
+ *ss = s;
+ return nn;
+}
+
+/*
+ * parse the address in the unix header
+ * last line of defence, so must return something
+ */
+static Maddr *
+unixfrom(char *s)
+{
+ char *e, *t;
+ Maddr *a;
+
+ if(s == nil)
+ return nil;
+ headstr = (uchar*)s;
+ t = emalloc(strlen(s) + 2);
+ e = headaddrspec(t, nil);
+ if(e == nil)
+ a = nil;
+ else{
+ if(*e != '\0')
+ *e++ = '\0';
+ else
+ e = site;
+ a = MKZ(Maddr);
+ a->box = estrdup(t);
+ a->host = estrdup(e);
+ }
+ free(t);
+ return a;
+}
+
+/*
+ * retrieve information from the unixheader file
+ */
+static int
+msgunix(Msg *m, int top)
+{
+ char *s, *ss;
+ Tm tm;
+
+ if(m->unixdate != nil)
+ return 1;
+ if(!top){
+bogus:
+ m->unixdate = estrdup("");
+ m->unixfrom = unixfrom(nil);
+ return 1;
+ }
+
+ if(msgreadfile(m, "unixheader", &ss) < 0)
+ goto bogus;
+ s = ss;
+ s = strchr(s, ' ');
+ if(s == nil){
+ free(ss);
+ goto bogus;
+ }
+ s++;
+ m->unixfrom = unixfrom(s);
+ s = (char*)headstr;
+ if(date2tm(&tm, s) == nil)
+ s = m->info[Iunixdate];
+ if(s == nil){
+ free(ss);
+ goto bogus;
+ }
+ m->unixdate = estrdup(s);
+ free(ss);
+ return 1;
+}
+
+/*
+ * make sure the message has valid associated info
+ * used for Isubject, Idigest, Iinreplyto, Imessageid.
+ */
+int
+msginfo(Msg *m)
+{
+ char *s;
+ int i;
+
+ if(m->info[0] != nil)
+ return 1;
+ if(msgreadfile(m, "info", &m->ibuf) < 0)
+ return 0;
+ s = m->ibuf;
+ for(i = 0; i < Imax; i++){
+ m->info[i] = s;
+ s = strchr(s, '\n');
+ if(s == nil)
+ return 0;
+ if(s == m->info[i])
+ m->info[i] = 0;
+ *s++ = '\0';
+ }
+// m->lines = strtoul(m->info[Ilines], 0, 0);
+// m->size = strtoull(m->info[Isize], 0, 0);
+// m->size += m->lines; /* BOTCH: this hack belongs elsewhere */
+ return 1;
+}
+
+/*
+ * make sure the message has valid mime structure
+ * and sub-messages
+ */
+int
+msgstruct(Msg *m, int top)
+{
+ char buf[12];
+ int fd, ns, max;
+ Msg *k, head, *last;
+
+ if(m->kids != nil)
+ return 1;
+ if(m->expunged
+ || !msginfo(m)
+ || !msgheader(m, &m->mime, "mimeheader")){
+ msgdead(m);
+ return 0;
+ }
+ /* gack. we need to get the header from the subpart here. */
+ if(msgis822(&m->mime)){
+ free(m->ibuf);
+ m->info[0] = 0;
+ m->efs = seprint(m->efs, m->efs + 5, "/1/");
+ if(!msginfo(m)){
+ msgdead(m);
+ return 0;
+ }
+ }
+ if(!msgunix(m, top)
+ || !msgbodysize(m)
+ || (top || msgis822(&m->mime) || msgismulti(&m->mime)) && !msgheader(m, &m->head, "rawheader")){
+ msgdead(m);
+ return 0;
+ }
+
+ /*
+ * if a message has no kids, it has a kid which is just the body of the real message
+ */
+ if(!msgismulti(&m->head) && !msgismulti(&m->mime) && !msgis822(&m->head) && !msgis822(&m->mime)){
+ k = MKZ(Msg);
+ k->id = 1;
+ k->fsdir = m->fsdir;
+ k->parent = m->parent;
+ ns = m->efs - m->fs;
+ k->fs = emalloc(ns + (Filelen + 1));
+ memmove(k->fs, m->fs, ns);
+ k->efs = k->fs + ns;
+ *k->efs = '\0';
+ k->size = m->size;
+ m->kids = k;
+ return 1;
+ }
+
+ /*
+ * read in all child messages messages
+ */
+ head.next = nil;
+ last = &head;
+ for(max = 1;; max++){
+ snprint(buf, sizeof buf, "%d", max);
+ fd = msgfile(m, buf);
+ if(fd == -1)
+ break;
+ close(fd);
+ m->efs[0] = 0; /* BOTCH! */
+
+ k = MKZ(Msg);
+ k->id = max;
+ k->fsdir = m->fsdir;
+ k->parent = m;
+ ns = strlen(m->fs) + 2*(Filelen + 1);
+ k->fs = emalloc(ns);
+ k->efs = seprint(k->fs, k->fs + ns, "%s%d/", m->fs, max);
+ k->size = ~0UL;
+ k->lines = ~0UL;
+ last->next = k;
+ last = k;
+ }
+
+ m->kids = head.next;
+
+ /*
+ * if kids fail, just whack them
+ */
+ top = top && (msgis822(&m->head) || msgismulti(&m->head));
+ for(k = m->kids; k != nil; k = k->next)
+ if(!msgstruct(k, top)){
+ debuglog("kid fail %p %s", k, k->fs);
+ for(k = m->kids; k != nil; ){
+ last = k;
+ k = k->next;
+ freemsg(0, last);
+ }
+ m->kids = nil;
+ break;
+ }
+ return 1;
+}
+
+/*
+ * stolen from upas/marshal; base64 encodes from one fd to another.
+ *
+ * the size of buf is very important to enc64. Anything other than
+ * a multiple of 3 will cause enc64 to output a termination sequence.
+ * To ensure that a full buf corresponds to a multiple of complete lines,
+ * we make buf a multiple of 3*18 since that's how many enc64 sticks on
+ * a single line. This avoids short lines in the output which is pleasing
+ * but not necessary.
+ */
+static int
+enc64x18(char *out, int lim, uchar *in, int n)
+{
+ int m, mm, nn;
+
+ nn = 0;
+ for(; n > 0; n -= m){
+ m = 18 * 3;
+ if(m > n)
+ m = n;
+ mm = enc64(out, lim - nn, in, m);
+ in += m;
+ out += mm;
+ *out++ = '\r';
+ *out++ = '\n';
+ nn += mm + 2;
+ }
+ return nn;
+}
+
+/*
+ * read in the message body to count \n without a preceding \r
+ */
+static int
+msgbodysize(Msg *m)
+{
+ char buf[Bufsize + 2], *s, *se;
+ uint length, size, lines, needr;
+ int n, fd, c;
+ Dir *d;
+
+ if(m->lines != ~0UL)
+ return 1;
+ fd = msgfile(m, "rawbody");
+ if(fd < 0)
+ return 0;
+ d = dirfstat(fd);
+ if(d == nil){
+ close(fd);
+ return 0;
+ }
+ length = d->length;
+ free(d);
+
+ size = 0;
+ lines = 0;
+ needr = 0;
+ buf[0] = ' ';
+ for(;;){
+ n = read(fd, &buf[1], Bufsize);
+ if(n <= 0)
+ break;
+ size += n;
+ se = &buf[n + 1];
+ for(s = &buf[1]; s < se; s++){
+ c = *s;
+ if(c == '\0')
+ *s = ' ';
+ if(c != '\n')
+ continue;
+ if(s[-1] != '\r')
+ needr++;
+ lines++;
+ }
+ buf[0] = buf[n];
+ }
+ if(size != length)
+ bye("bad length reading rawbody %d != %d; n %d %s", size, length, n, m->fs);
+ size += needr;
+ m->size = size;
+ m->lines = lines;
+ close(fd);
+ return 1;
+}
+
+/*
+ * prepend hdrname: val to the cached header
+ */
+static void
+msgaddhead(Msg *m, char *hdrname, char *val)
+{
+ char *s;
+ long size, n;
+
+ n = strlen(hdrname) + strlen(val) + 4;
+ size = m->head.size + n;
+ s = emalloc(size + 1);
+ snprint(s, size + 1, "%s: %s\r\n%s", hdrname, val, m->head.buf);
+ free(m->head.buf);
+ m->head.buf = s;
+ m->head.size = size;
+ m->head.lines++;
+}
+
+static void
+msgadddate(Msg *m)
+{
+ char buf[64];
+ Tm tm;
+
+ /* don't bother if we don't have a date */
+ if(m->info[Idate] == 0)
+ return;
+
+ date2tm(&tm, m->info[Idate]);
+ snprint(buf, sizeof buf, "%δ", &tm);
+ msgaddhead(m, "Date", buf);
+}
+
+/*
+ * read in the entire header,
+ * and parse out any existing mime headers
+ */
+static int
+msgheader(Msg *m, Header *h, char *file)
+{
+ char *s, *ss, *t, *te;
+ int dated, c;
+ long ns;
+ uint lines, n, nn;
+
+ if(h->buf != nil)
+ return 1;
+
+ ns = msgreadfile(m, file, &ss);
+ if(ns < 0)
+ return 0;
+ s = ss;
+ n = ns;
+
+ /*
+ * count lines ending with \n and \r\n
+ * add an extra line at the end, since upas/fs headers
+ * don't have a terminating \r\n
+ */
+ lines = 1;
+ te = s + ns;
+ for(t = s; t < te; t++){
+ c = *t;
+ if(c == '\0')
+ *t = ' ';
+ if(c != '\n')
+ continue;
+ if(t == s || t[-1] != '\r')
+ n++;
+ lines++;
+ }
+ if(t > s && t[-1] != '\n'){
+ if(t[-1] != '\r')
+ n++;
+ n++;
+ }
+ if(n > 0)
+ n += 2;
+ h->buf = emalloc(n + 1);
+ h->size = n;
+ h->lines = lines;
+
+ /*
+ * make sure all headers end in \r\n
+ */
+ nn = 0;
+ for(t = s; t < te; t++){
+ c = *t;
+ if(c == '\n'){
+ if(!nn || h->buf[nn - 1] != '\r')
+ h->buf[nn++] = '\r';
+ lines++;
+ }
+ h->buf[nn++] = c;
+ }
+ if(nn && h->buf[nn-1] != '\n'){
+ if(h->buf[nn-1] != '\r')
+ h->buf[nn++] = '\r';
+ h->buf[nn++] = '\n';
+ }
+ if(nn > 0){
+ h->buf[nn++] = '\r';
+ h->buf[nn++] = '\n';
+ }
+ h->buf[nn] = '\0';
+ if(nn != n)
+ bye("misconverted header %d %d", nn, n);
+ free(s);
+
+ /*
+ * and parse some mime headers
+ */
+ headstr = (uchar*)h->buf;
+ dated = 0;
+ while(s = headatom(headfieldstop)){
+ if(cistrcmp(s, "content-type") == 0)
+ mimetype(h);
+ else if(cistrcmp(s, "content-transfer-encoding") == 0)
+ mimeencoding(h);
+ else if(cistrcmp(s, "content-id") == 0)
+ mimeid(h);
+ else if(cistrcmp(s, "content-description") == 0)
+ mimedescription(h);
+ else if(cistrcmp(s, "content-disposition") == 0)
+ mimedisposition(h);
+// else if(cistrcmp(s, "content-md5") == 0)
+// mimemd5(h);
+ else if(cistrcmp(s, "content-language") == 0)
+ mimelanguage(h);
+ else if(h == &m->head){
+ if(cistrcmp(s, "from") == 0)
+ m->from = headmaddr(m->from);
+ else if(cistrcmp(s, "to") == 0)
+ m->to = headmaddr(m->to);
+ else if(cistrcmp(s, "reply-to") == 0)
+ m->replyto = headmaddr(m->replyto);
+ else if(cistrcmp(s, "sender") == 0)
+ m->sender = headmaddr(m->sender);
+ else if(cistrcmp(s, "cc") == 0)
+ m->cc = headmaddr(m->cc);
+ else if(cistrcmp(s, "bcc") == 0)
+ m->bcc = headmaddr(m->bcc);
+ else if(cistrcmp(s, "date") == 0)
+ dated = 1;
+ }
+ headskip();
+ free(s);
+ }
+
+ if(h == &m->head){
+ if(m->from == nil){
+ m->from = m->unixfrom;
+ if(m->from != nil){
+ s = maddrstr(m->from);
+ msgaddhead(m, "From", s);
+ free(s);
+ }
+ }
+ if(m->sender == nil)
+ m->sender = m->from;
+ if(m->replyto == nil)
+ m->replyto = m->from;
+
+ if(m->info[Idate] == 0)
+ m->info[Idate] = m->unixdate;
+ if(!dated && m->from != nil)
+ msgadddate(m);
+ }
+ return 1;
+}
+
+/*
+ * q is a quoted string. remove enclosing " and and \ escapes
+ */
+static void
+stripquotes(char *q)
+{
+ char *s;
+ int c;
+
+ if(q == nil)
+ return;
+ s = q++;
+ while(c = *q++){
+ if(c == '\\'){
+ c = *q++;
+ if(!c)
+ return;
+ }
+ *s++ = c;
+ }
+ s[-1] = '\0';
+}
+
+/*
+ * parser for rfc822 & mime header fields
+ */
+
+/*
+ * params :
+ * | params ';' token '=' token
+ * | params ';' token '=' quoted-str
+ */
+static Mimehdr*
+mimeparams(void)
+{
+ char *s, *t;
+ Mimehdr head, *last;
+
+ head.next = nil;
+ last = &head;
+ for(;;){
+ if(headchar(1) != ';')
+ break;
+ s = headatom(mimetokenstop);
+ if(s == nil || headchar(1) != '='){
+ free(s);
+ break;
+ }
+ if(headchar(0) == '"'){
+ t = headquoted('"', '"');
+ stripquotes(t);
+ }else
+ t = headatom(mimetokenstop);
+ if(t == nil){
+ free(s);
+ break;
+ }
+ last->next = mkmimehdr(s, t, nil);
+ last = last->next;
+ }
+ return head.next;
+}
+
+/*
+ * type : 'content-type' ':' token '/' token params
+ */
+static void
+mimetype(Header *h)
+{
+ char *s, *t;
+
+ if(headchar(1) != ':')
+ return;
+ s = headatom(mimetokenstop);
+ if(s == nil || headchar(1) != '/'){
+ free(s);
+ return;
+ }
+ t = headatom(mimetokenstop);
+ if(t == nil){
+ free(s);
+ return;
+ }
+ h->type = mkmimehdr(s, t, mimeparams());
+}
+
+/*
+ * encoding : 'content-transfer-encoding' ':' token
+ */
+static void
+mimeencoding(Header *h)
+{
+ char *s;
+
+ if(headchar(1) != ':')
+ return;
+ s = headatom(mimetokenstop);
+ if(s == nil)
+ return;
+ h->encoding = mkmimehdr(s, nil, nil);
+}
+
+/*
+ * mailaddr : ':' addresses
+ */
+static Maddr*
+headmaddr(Maddr *old)
+{
+ Maddr *a;
+
+ if(headchar(1) != ':')
+ return old;
+
+ if(headchar(0) == '\n')
+ return old;
+
+ a = headaddresses();
+ if(a == nil)
+ return old;
+
+ freemaddr(old);
+ return a;
+}
+
+/*
+ * addresses : address | addresses ',' address
+ */
+static Maddr*
+headaddresses(void)
+{
+ Maddr *addr, *tail, *a;
+
+ addr = headaddress();
+ if(addr == nil)
+ return nil;
+ tail = addr;
+ while(headchar(0) == ','){
+ headchar(1);
+ a = headaddress();
+ if(a == nil){
+ freemaddr(addr);
+ return nil;
+ }
+ tail->next = a;
+ tail = a;
+ }
+ return addr;
+}
+
+/*
+ * address : mailbox | group
+ * group : phrase ':' mboxes ';' | phrase ':' ';'
+ * mailbox : addr-spec
+ * | optphrase '<' addr-spec '>'
+ * | optphrase '<' route ':' addr-spec '>'
+ * optphrase : | phrase
+ * route : '@' domain
+ * | route ',' '@' domain
+ * personal names are the phrase before '<',
+ * or a comment before or after a simple addr-spec
+ */
+static Maddr*
+headaddress(void)
+{
+ char *s, *e, *w, *personal;
+ uchar *hs;
+ int c;
+ Maddr *addr;
+
+ s = emalloc(strlen((char*)headstr) + 2);
+ e = s;
+ personal = headskipwhite(1);
+ c = headchar(0);
+ if(c == '<')
+ w = nil;
+ else{
+ w = headword();
+ c = headchar(0);
+ }
+ if(c == '.' || c == '@' || c == ',' || c == '\n' || c == '\0'){
+ lastwhite = headstr;
+ e = headaddrspec(s, w);
+ if(personal == nil){
+ hs = headstr;
+ headstr = lastwhite;
+ personal = headskipwhite(1);
+ headstr = hs;
+ }
+ }else{
+ if(c != '<' || w != nil){
+ free(personal);
+ if(!headphrase(e, w)){
+ free(s);
+ return nil;
+ }
+
+ /*
+ * ignore addresses with groups,
+ * so the only thing left if <
+ */
+ c = headchar(1);
+ if(c != '<'){
+ free(s);
+ return nil;
+ }
+ personal = estrdup(s);
+ }else
+ headchar(1);
+
+ /*
+ * after this point, we need to free personal before returning.
+ * set e to nil to everything afterwards fails.
+ *
+ * ignore routes, they are useless, and heavily discouraged in rfc1123.
+ * imap4 reports them up to, but not including, the terminating :
+ */
+ e = s;
+ c = headchar(0);
+ if(c == '@'){
+ for(;;){
+ c = headchar(1);
+ if(c != '@'){
+ e = nil;
+ break;
+ }
+ headdomain(e);
+ c = headchar(1);
+ if(c != ','){
+ e = s;
+ break;
+ }
+ }
+ if(c != ':')
+ e = nil;
+ }
+
+ if(e != nil)
+ e = headaddrspec(s, nil);
+ if(headchar(1) != '>')
+ e = nil;
+ }
+
+ /*
+ * e points to @host, or nil if an error occured
+ */
+ if(e == nil){
+ free(personal);
+ addr = nil;
+ }else{
+ if(*e != '\0')
+ *e++ = '\0';
+ else
+ e = site;
+ addr = MKZ(Maddr);
+
+ addr->personal = personal;
+ addr->box = estrdup(s);
+ addr->host = estrdup(e);
+ }
+ free(s);
+ return addr;
+}
+
+/*
+ * phrase : word
+ * | phrase word
+ * w is the optional initial word of the phrase
+ * returns the end of the phrase, or nil if a failure occured
+ */
+static char*
+headphrase(char *e, char *w)
+{
+ int c;
+
+ for(;;){
+ if(w == nil){
+ w = headword();
+ if(w == nil)
+ return nil;
+ }
+ if(w[0] == '"')
+ stripquotes(w);
+ strcpy(e, w);
+ free(w);
+ w = nil;
+ e = strchr(e, '\0');
+ c = headchar(0);
+ if(c <= ' ' || strchr(headatomstop, c) != nil && c != '"')
+ break;
+ *e++ = ' ';
+ *e = '\0';
+ }
+ return e;
+}
+
+/*
+ * find the ! in domain!rest, where domain must have at least
+ * one internal '.'
+ */
+static char*
+dombang(char *s)
+{
+ int dot, c;
+
+ dot = 0;
+ for(; c = *s; s++){
+ if(c == '!'){
+ if(!dot || dot == 1 && s[-1] == '.' || s[1] == '\0')
+ return nil;
+ return s;
+ }
+ if(c == '"')
+ break;
+ if(c == '.')
+ dot++;
+ }
+ return nil;
+}
+
+/*
+ * addr-spec : local-part '@' domain
+ * | local-part extension to allow ! and local names
+ * local-part : word
+ * | local-part '.' word
+ *
+ * if no '@' is present, rewrite d!e!f!u as @d,@e:u@f,
+ * where d, e, f are valid domain components.
+ * the @d,@e: is ignored, since routes are ignored.
+ * perhaps they should be rewritten as e!f!u@d, but that is inconsistent with upas.
+ *
+ * returns a pointer to '@', the end if none, or nil if there was an error
+ */
+static char*
+headaddrspec(char *e, char *w)
+{
+ char *s, *at, *b, *bang, *dom;
+ int c;
+
+ s = e;
+ for(;;){
+ if(w == nil){
+ w = headword();
+ if(w == nil)
+ return nil;
+ }
+ strcpy(e, w);
+ free(w);
+ w = nil;
+ e = strchr(e, '\0');
+ lastwhite = headstr;
+ c = headchar(0);
+ if(c != '.')
+ break;
+ headchar(1);
+ *e++ = '.';
+ *e = '\0';
+ }
+
+ if(c != '@'){
+ /*
+ * extenstion: allow name without domain
+ * check for domain!xxx
+ */
+ bang = dombang(s);
+ if(bang == nil)
+ return e;
+
+ /*
+ * if dom1!dom2!xxx, ignore dom1!
+ */
+ dom = s;
+ for(; b = dombang(bang + 1); bang = b)
+ dom = bang + 1;
+
+ /*
+ * convert dom!mbox into mbox@dom
+ */
+ *bang = '@';
+ strrev(dom, bang);
+ strrev(bang + 1, e);
+ strrev(dom, e);
+ bang = &dom[e - bang - 1];
+ if(dom > s){
+ bang -= dom - s;
+ for(e = s; *e = *dom; e++)
+ dom++;
+ }
+
+ /*
+ * eliminate a trailing '.'
+ */
+ if(e[-1] == '.')
+ e[-1] = '\0';
+ return bang;
+ }
+ headchar(1);
+
+ at = e;
+ *e++ = '@';
+ *e = '\0';
+ if(!headdomain(e))
+ return nil;
+ return at;
+}
+
+/*
+ * domain : sub-domain
+ * | domain '.' sub-domain
+ * returns the end of the domain, or nil if a failure occured
+ */
+static char*
+headdomain(char *e)
+{
+ char *w;
+
+ for(;;){
+ w = headsubdomain();
+ if(w == nil)
+ return nil;
+ strcpy(e, w);
+ free(w);
+ e = strchr(e, '\0');
+ lastwhite = headstr;
+ if(headchar(0) != '.')
+ break;
+ headchar(1);
+ *e++ = '.';
+ *e = '\0';
+ }
+ return e;
+}
+
+/*
+ * id : 'content-id' ':' msg-id
+ * msg-id : '<' addr-spec '>'
+ */
+static void
+mimeid(Header *h)
+{
+ char *s, *e, *w;
+
+ if(headchar(1) != ':')
+ return;
+ if(headchar(1) != '<')
+ return;
+
+ s = emalloc(strlen((char*)headstr) + 3);
+ e = s;
+ *e++ = '<';
+ e = headaddrspec(e, nil);
+ if(e == nil || headchar(1) != '>'){
+ free(s);
+ return;
+ }
+ e = strchr(e, '\0');
+ *e++ = '>';
+ e[0] = '\0';
+ w = strdup(s);
+ free(s);
+ h->id = mkmimehdr(w, nil, nil);
+}
+
+/*
+ * description : 'content-description' ':' *text
+ */
+static void
+mimedescription(Header *h)
+{
+ if(headchar(1) != ':')
+ return;
+ headskipwhite(0);
+ h->description = mkmimehdr(headtext(), nil, nil);
+}
+
+/*
+ * disposition : 'content-disposition' ':' token params
+ */
+static void
+mimedisposition(Header *h)
+{
+ char *s;
+
+ if(headchar(1) != ':')
+ return;
+ s = headatom(mimetokenstop);
+ if(s == nil)
+ return;
+ h->disposition = mkmimehdr(s, nil, mimeparams());
+}
+
+/*
+ * md5 : 'content-md5' ':' token
+ */
+//static void
+//mimemd5(Header *h)
+//{
+// char *s;
+//
+// if(headchar(1) != ':')
+// return;
+// s = headatom(mimetokenstop);
+// if(s == nil)
+// return;
+// h->md5 = mkmimehdr(s, nil, nil);
+//}
+
+/*
+ * language : 'content-language' ':' langs
+ * langs : token
+ * | langs commas token
+ * commas : ','
+ * | commas ','
+ */
+static void
+mimelanguage(Header *h)
+{
+ char *s;
+ Mimehdr head, *last;
+
+ head.next = nil;
+ last = &head;
+ for(;;){
+ s = headatom(mimetokenstop);
+ if(s == nil)
+ break;
+ last->next = mkmimehdr(s, nil, nil);
+ last = last->next;
+ while(headchar(0) != ',')
+ headchar(1);
+ }
+ h->language = head.next;
+}
+
+/*
+ * token : 1*<char 33-255, except "()<>@,;:\\\"/[]?=" aka mimetokenstop>
+ * atom : 1*<chars 33-255, except "()<>@,;:\\\".[]" aka headatomstop>
+ * note this allows 8 bit characters, which occur in utf.
+ */
+static char*
+headatom(char *disallowed)
+{
+ char *s;
+ int c, ns, as;
+
+ headskipwhite(0);
+
+ s = emalloc(Stralloc);
+ as = Stralloc;
+ ns = 0;
+ for(;;){
+ c = *headstr++;
+ if(c <= ' ' || strchr(disallowed, c) != nil){
+ headstr--;
+ break;
+ }
+ s[ns++] = c;
+ if(ns >= as){
+ as += Stralloc;
+ s = erealloc(s, as);
+ }
+ }
+ if(ns == 0){
+ free(s);
+ return 0;
+ }
+ s[ns] = '\0';
+ return s;
+}
+
+/*
+ * sub-domain : atom | domain-lit
+ */
+static char *
+headsubdomain(void)
+{
+ if(headchar(0) == '[')
+ return headquoted('[', ']');
+ return headatom(headatomstop);
+}
+
+/*
+ * word : atom | quoted-str
+ */
+static char *
+headword(void)
+{
+ if(headchar(0) == '"')
+ return headquoted('"', '"');
+ return headatom(headatomstop);
+}
+
+/*
+ * quoted-str : '"' *(any char but '"\\\r', or '\' any char, or linear-white-space) '"'
+ * domain-lit : '[' *(any char but '[]\\\r', or '\' any char, or linear-white-space) ']'
+ */
+static char *
+headquoted(int start, int stop)
+{
+ char *s;
+ int c, ns, as;
+
+ if(headchar(1) != start)
+ return nil;
+ s = emalloc(Stralloc);
+ as = Stralloc;
+ ns = 0;
+ s[ns++] = start;
+ for(;;){
+ c = *headstr;
+ if(c == stop){
+ headstr++;
+ break;
+ }
+ if(c == '\0'){
+ free(s);
+ return nil;
+ }
+ if(c == '\r'){
+ headstr++;
+ continue;
+ }
+ if(c == '\n'){
+ headstr++;
+ while(*headstr == ' ' || *headstr == '\t' || *headstr == '\r' || *headstr == '\n')
+ headstr++;
+ c = ' ';
+ }else if(c == '\\'){
+ headstr++;
+ s[ns++] = c;
+ c = *headstr;
+ if(c == '\0'){
+ free(s);
+ return nil;
+ }
+ headstr++;
+ }else
+ headstr++;
+ s[ns++] = c;
+ if(ns + 1 >= as){ /* leave room for \c or "0 */
+ as += Stralloc;
+ s = erealloc(s, as);
+ }
+ }
+ s[ns++] = stop;
+ s[ns] = '\0';
+ return s;
+}
+
+/*
+ * headtext : contents of rest of header line
+ */
+static char *
+headtext(void)
+{
+ uchar *v;
+ char *s;
+
+ v = headstr;
+ headtoend();
+ s = emalloc(headstr - v + 1);
+ memmove(s, v, headstr - v);
+ s[headstr - v] = '\0';
+ return s;
+}
+
+/*
+ * white space is ' ' '\t' or nested comments.
+ * skip white space.
+ * if com and a comment is seen,
+ * return it's contents and stop processing white space.
+ */
+static char*
+headskipwhite(int com)
+{
+ char *s;
+ int c, incom, as, ns;
+
+ s = nil;
+ as = Stralloc;
+ ns = 0;
+ if(com)
+ s = emalloc(Stralloc);
+ incom = 0;
+ for(; c = *headstr; headstr++){
+ switch(c){
+ case ' ':
+ case '\t':
+ case '\r':
+ c = ' ';
+ break;
+ case '\n':
+ c = headstr[1];
+ if(c != ' ' && c != '\t')
+ goto done;
+ c = ' ';
+ break;
+ case '\\':
+ if(com && incom)
+ s[ns++] = c;
+ c = headstr[1];
+ if(c == '\0')
+ goto done;
+ headstr++;
+ break;
+ case '(':
+ incom++;
+ if(incom == 1)
+ continue;
+ break;
+ case ')':
+ incom--;
+ if(com && !incom){
+ s[ns] = '\0';
+ return s;
+ }
+ break;
+ default:
+ if(!incom)
+ goto done;
+ break;
+ }
+ if(com && incom && (c != ' ' || ns > 0 && s[ns-1] != ' ')){
+ s[ns++] = c;
+ if(ns + 1 >= as){ /* leave room for \c or 0 */
+ as += Stralloc;
+ s = erealloc(s, as);
+ }
+ }
+ }
+done:
+ free(s);
+ return nil;
+}
+
+/*
+ * return the next non-white character
+ */
+static int
+headchar(int eat)
+{
+ int c;
+
+ headskipwhite(0);
+ c = *headstr;
+ if(eat && c != '\0' && c != '\n')
+ headstr++;
+ return c;
+}
+
+static void
+headtoend(void)
+{
+ uchar *s;
+ int c;
+
+ for(;;){
+ s = headstr;
+ c = *s++;
+ while(c == '\r')
+ c = *s++;
+ if(c == '\n'){
+ c = *s++;
+ if(c != ' ' && c != '\t')
+ return;
+ }
+ if(c == '\0')
+ return;
+ headstr = s;
+ }
+}
+
+static void
+headskip(void)
+{
+ int c;
+
+ while(c = *headstr){
+ headstr++;
+ if(c == '\n'){
+ c = *headstr;
+ if(c == ' ' || c == '\t')
+ continue;
+ return;
+ }
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/mutf7.c
@@ -1,0 +1,174 @@
+#include "imap4d.h"
+
+/* not compatable with characters outside the basic plane */
+
+/*
+ * modified utf-7, as per imap4 spec
+ * like utf-7, but substitues , for / in base 64,
+ * does not allow escaped ascii characters.
+ *
+ * /lib/rfc/rfc2152 is utf-7
+ * /lib/rfc/rfc1642 is obsolete utf-7
+ *
+ * test sequences from rfc1642
+ * 'A≢Α.' 'A&ImIDkQ-.'
+ * 'Hi Mom ☺!" 'Hi Mom &Jjo-!'
+ * '日本語' '&ZeVnLIqe-'
+ */
+
+static uchar mt64d[256];
+static char mt64e[64];
+
+static void
+initm64(void)
+{
+ int c, i;
+
+ memset(mt64d, 255, 256);
+ memset(mt64e, '=', 64);
+ i = 0;
+ for(c = 'A'; c <= 'Z'; c++){
+ mt64e[i] = c;
+ mt64d[c] = i++;
+ }
+ for(c = 'a'; c <= 'z'; c++){
+ mt64e[i] = c;
+ mt64d[c] = i++;
+ }
+ for(c = '0'; c <= '9'; c++){
+ mt64e[i] = c;
+ mt64d[c] = i++;
+ }
+ mt64e[i] = '+';
+ mt64d['+'] = i++;
+ mt64e[i] = ',';
+ mt64d[','] = i;
+}
+
+char*
+encmutf7(char *out, int lim, char *in)
+{
+ char *start, *e;
+ int nb;
+ ulong r, b;
+ Rune rr;
+
+ start = out;
+ e = out + lim;
+ if(mt64e[0] == 0)
+ initm64();
+ if(in)
+ for(;;){
+ r = *(uchar*)in;
+
+ if(r < ' ' || r >= Runeself){
+ if(r == 0)
+ break;
+ if(out + 1 >= e)
+ return 0;
+ *out++ = '&';
+ b = 0;
+ nb = 0;
+ for(;;){
+ in += chartorune(&rr, in);
+ r = rr;
+ if(r == 0 || r >= ' ' && r < Runeself)
+ break;
+ b = (b << 16) | r;
+ for(nb += 16; nb >= 6; nb -= 6){
+ if(out + 1 >= e)
+ return 0;
+ *out++ = mt64e[(b >> nb - 6) & 0x3f];
+ }
+ }
+ for(; nb >= 6; nb -= 6){
+ if(out + 1 >= e)
+ return 0;
+ *out++ = mt64e[(b >> nb - 6) & 0x3f];
+ }
+ if(nb){
+ if(out + 1 >= e)
+ return 0;
+ *out++ = mt64e[(b << 6 - nb) & 0x3f];
+ }
+
+ if(out + 1 >= e)
+ return 0;
+ *out++ = '-';
+ if(r == 0)
+ break;
+ }else
+ in++;
+ if(out + 1 >= e)
+ return 0;
+ *out = r;
+ out++;
+ if(r == '&')
+ *out++ = '-';
+ }
+ *out = 0;
+ if(!in || out >= e)
+ return 0;
+ return start;
+}
+
+char*
+decmutf7(char *out, int lim, char *in)
+{
+ char *start, *e;
+ int c, b, nb;
+ Rune rr;
+
+ start = out;
+ e = out + lim;
+ if(mt64e[0] == 0)
+ initm64();
+ if(in)
+ for(;;){
+ c = *in;
+
+ if(c < ' ' || c >= Runeself){
+ if(c == 0)
+ break;
+ return 0;
+ }
+ if(c != '&'){
+ if(out + 1 >= e)
+ return 0;
+ *out++ = c;
+ in++;
+ continue;
+ }
+ in++;
+ if(*in == '-'){
+ if(out + 1 >= e)
+ return 0;
+ *out++ = '&';
+ in++;
+ continue;
+ }
+
+ b = 0;
+ nb = 0;
+ while((c = *in++) != '-'){
+ c = mt64d[c];
+ if(c >= 64)
+ return 0;
+ b = (b << 6) | c;
+ nb += 6;
+ if(nb >= 16){
+ rr = b >> (nb - 16);
+ nb -= 16;
+ if(out + UTFmax + 1 >= e && out + runelen(rr) + 1 >= e)
+ return 0;
+ out += runetochar(out, &rr);
+ }
+ }
+ if(b & ((1 << nb) - 1))
+ return 0;
+ }
+ *out = 0;
+ if(!in || out >= e)
+ return 0;
+ return start;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/nlisttst.c
@@ -1,0 +1,94 @@
+#include "nlist.c"
+
+char username[] = "quanstro";
+char mboxdir[] = "/mail/box/quanstro/";
+Biobuf bout;
+Bin *parsebin;
+
+void
+bye(char *fmt, ...)
+{
+ va_list arg;
+
+ va_start(arg, fmt);
+ Bprint(&bout, "* bye ");
+ Bvprint(&bout, fmt, arg);
+ Bprint(&bout, "\r\n");
+ Bflush(&bout);
+ exits(0);
+}
+
+static char *stoplist[] =
+{
+ ".",
+ "dead.letter",
+ "forward",
+ "headers",
+ "imap.subscribed",
+ "mbox",
+ "names",
+ "pipefrom",
+ "pipeto",
+ 0
+};
+int
+okmbox(char *path)
+{
+ char *name;
+ int i, c;
+
+ name = strrchr(path, '/');
+ if(name == nil)
+ name = path;
+ else
+ name++;
+ if(strlen(name) + STRLEN(".imp") >= Pathlen)
+ return 0;
+ for(i = 0; stoplist[i]; i++)
+ if(strcmp(name, stoplist[i]) == 0)
+ return 0;
+ c = name[0];
+ if(c == 0 || c == '-' || c == '/'
+ || isdotdot(name)
+ || isprefix("L.", name)
+ || isprefix("imap-tmp.", name)
+ || issuffix("-", name)
+ || issuffix(".00", name)
+ || issuffix(".imp", name)
+ || issuffix(".idx", name))
+ return 0;
+
+ return 1;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: nlist ref pat\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int lsub;
+
+ lsub = 0;
+ ARGBEGIN{
+ case 'l':
+ lsub = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+ if(argc != 2)
+ usage();
+ Binit(&bout, 1, OWRITE);
+ quotefmtinstall();
+ if(lsub)
+ Bprint(&bout, "lsub→%d\n", lsubboxes("lsub", argv[0], argv[1]));
+ else
+ Bprint(&bout, "→%d\n", listboxes("list", argv[0], argv[1]));
+ Bterm(&bout);
+ exits("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/nodes.c
@@ -1,0 +1,184 @@
+#include "imap4d.h"
+
+int
+inmsgset(Msgset *ms, uint id)
+{
+ for(; ms; ms = ms->next)
+ if(ms->from <= id && ms->to >= id)
+ return 1;
+ return 0;
+}
+
+/*
+ * we can't rely on uids being in order, but short-circuting saves us
+ * very little. we have a few tens of thousands of messages at most.
+ * also use the msg list as the outer loop to avoid 1:5,3:7 returning
+ * duplicates. this is allowed, but silly. and could be a problem for
+ * internal uses that aren't idempotent, like (re)moving messages.
+ */
+static int
+formsgsu(Box *box, Msgset *s, uint max, int (*f)(Box*, Msg*, int, void*), void *rock)
+{
+ int ok;
+ Msg *m;
+ Msgset *ms;
+
+ ok = 1;
+ for(m = box->msgs; m != nil && m->seq <= max; m = m->next)
+ for(ms = s; ms != nil; ms = ms->next)
+ if(m->uid >= ms->from && m->uid <= ms->to){
+ if(!f(box, m, 1, rock))
+ ok = 0;
+ break;
+ }
+ return ok;
+}
+
+int
+formsgsi(Box *box, Msgset *ms, uint max, int (*f)(Box*, Msg*, int, void*), void *rock)
+{
+ int ok, rok;
+ uint id;
+ Msg *m;
+
+ ok = 1;
+ for(; ms != nil; ms = ms->next){
+ id = ms->from;
+ rok = 0;
+ for(m = box->msgs; m != nil && m->seq <= max; m = m->next){
+ if(m->seq > id)
+ break; /* optimization */
+ if(m->seq == id){
+ if(!f(box, m, 0, rock))
+ ok = 0;
+ if(id >= ms->to){
+ rok = 1;
+ break; /* optimization */
+ }
+ if(ms->to == ~0UL)
+ rok = 1;
+ id++;
+ }
+ }
+ if(!rok)
+ ok = 0;
+ }
+ return ok;
+}
+
+/*
+ * iterated over all of the items in the message set.
+ * errors are accumulated, but processing continues.
+ * if uids, then ignore non-existent messages.
+ * otherwise, that's an error. additional note from the
+ * rfc:
+ *
+ * “Servers MAY coalesce overlaps and/or execute the
+ * sequence in any order.”
+ */
+int
+formsgs(Box *box, Msgset *ms, uint max, int uids, int (*f)(Box*, Msg*, int, void*), void *rock)
+{
+ if(uids)
+ return formsgsu(box, ms, max, f, rock);
+ else
+ return formsgsi(box, ms, max, f, rock);
+}
+
+Store*
+mkstore(int sign, int op, int flags)
+{
+ Store *st;
+
+ st = binalloc(&parsebin, sizeof *st, 1);
+ if(st == nil)
+ parseerr("out of memory");
+ st->sign = sign;
+ st->op = op;
+ st->flags = flags;
+ return st;
+}
+
+Fetch *
+mkfetch(int op, Fetch *next)
+{
+ Fetch *f;
+
+ f = binalloc(&parsebin, sizeof *f, 1);
+ if(f == nil)
+ parseerr("out of memory");
+ f->op = op;
+ f->next = next;
+ return f;
+}
+
+Fetch*
+revfetch(Fetch *f)
+{
+ Fetch *last, *next;
+
+ last = nil;
+ for(; f != nil; f = next){
+ next = f->next;
+ f->next = last;
+ last = f;
+ }
+ return last;
+}
+
+Slist*
+mkslist(char *s, Slist *next)
+{
+ Slist *sl;
+
+ sl = binalloc(&parsebin, sizeof *sl, 0);
+ if(sl == nil)
+ parseerr("out of memory");
+ sl->s = s;
+ sl->next = next;
+ return sl;
+}
+
+Slist*
+revslist(Slist *sl)
+{
+ Slist *last, *next;
+
+ last = nil;
+ for(; sl != nil; sl = next){
+ next = sl->next;
+ sl->next = last;
+ last = sl;
+ }
+ return last;
+}
+
+int
+Bnlist(Biobuf *b, Nlist *nl, char *sep)
+{
+ char *s;
+ int n;
+
+ s = "";
+ n = 0;
+ for(; nl != nil; nl = nl->next){
+ n += Bprint(b, "%s%ud", s, nl->n);
+ s = sep;
+ }
+ return n;
+}
+
+int
+Bslist(Biobuf *b, Slist *sl, char *sep)
+{
+ char *s;
+ int n;
+
+ s = "";
+ n = 0;
+ for(; sl != nil; sl = sl->next){
+ n += Bprint(b, "%s%Z", s, sl->s);
+ s = sep;
+ }
+ return n;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/print.c
@@ -1,0 +1,129 @@
+#include "imap4d.h"
+
+int
+Ffmt(Fmt *f)
+{
+ char *s, buf[128], buf2[128];
+
+ s = va_arg(f->args, char*);
+ if(strncmp("/imap", s, 5) && strncmp("/pop", s, 4)){
+ snprint(buf, sizeof buf, "/mail/box/%s/%s", username, s);
+ s = buf;
+ }
+ snprint(buf2, sizeof buf2, "%q", s);
+ return fmtstrcpy(f, buf2);
+}
+
+enum {
+ Qok = 0,
+ Qquote = 1<<0,
+ Qbackslash = 1<<1,
+ Qliteral = 1<<2,
+};
+
+static int
+needtoquote(Rune r)
+{
+ if(r >= 0x7f || r == '\n' || r == '\r')
+ return Qliteral;
+ if(r <= ' ')
+ return Qquote;
+ if(r == '\\' || r == '"')
+ return Qbackslash;
+ return Qok;
+}
+
+int
+Zfmt(Fmt *f)
+{
+ char *s, *t, buf[Pathlen], buf2[Pathlen];
+ int w, quotes, alt;
+ Rune r;
+
+ s = va_arg(f->args, char*);
+ alt = f->flags & FmtSharp;
+ if(s == 0 && !alt)
+ return fmtstrcpy(f, "NIL");
+ if(s == 0 || *s == 0)
+ return fmtstrcpy(f, "\"\"");
+ switch(f->r){
+ case 'Y':
+ s = decfs(buf, sizeof buf, s);
+ s = encmutf7(buf2, sizeof buf2, s);
+ break;
+ }
+ quotes = 0;
+ for(t = s; *t; t += w){
+ w = chartorune(&r, t);
+ quotes |= needtoquote(r);
+ if(quotes & Qliteral && alt)
+ ilog("[%s] bad at [%s] %.2ux\n", s, t, r);
+ }
+ if(alt){
+ if(!quotes)
+ return fmtstrcpy(f, s);
+ if(quotes & Qliteral)
+ return fmtstrcpy(f, "GOK");
+ }else if(quotes & Qliteral)
+ return fmtprint(f, "{%lud}\r\n%s", strlen(s), 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, '"');
+}
+
+int
+Xfmt(Fmt *f)
+{
+ char *s, buf[Pathlen], buf2[Pathlen];
+
+ s = va_arg(f->args, char*);
+ if(s == 0)
+ return fmtstrcpy(f, "NIL");
+ s = decmutf7(buf2, sizeof buf2, s);
+ cleanname(s);
+ return fmtstrcpy(f, encfs(buf, sizeof buf, s));
+}
+
+static char *day[] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+};
+
+static char *mon[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+int
+Dfmt(Fmt *f)
+{
+ char buf[128], *p, *e, *sgn, *fmt;
+ int off;
+ Tm *tm;
+
+ tm = va_arg(f->args, Tm*);
+ if(tm == nil)
+ tm = localtime(time(0));
+ sgn = "+";
+ if(tm->tzoff < 0)
+ sgn = "";
+ e = buf + sizeof buf;
+ p = buf;
+ off = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
+ if((f->flags & FmtSharp) == 0){
+ /* rfc822 style */
+ fmt = "%.2d %s %.4d %.2d:%.2d:%.2d %s%.4d";
+ p = seprint(p, e, "%s, ", day[tm->wday]);
+ }else
+ fmt = "%2d-%s-%.4d %2.2d:%2.2d:%2.2d %s%4.4d";
+ seprint(p, e, fmt,
+ tm->mday, mon[tm->mon], tm->year + 1900, tm->hour, tm->min, tm->sec,
+ sgn, off);
+ if(f->r == L'δ')
+ fmtprint(f, "%s", buf);
+ return fmtprint(f, "%Z", buf);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/quota.c
@@ -1,0 +1,73 @@
+#include "imap4d.h"
+
+static int
+openpipe(int *pip, char *cmd, char *av[])
+{
+ int pid, fd[2];
+
+ if(pipe(fd) != 0)
+ sysfatal("pipe: %r");
+ pid = fork();
+ switch(pid){
+ case -1:
+ return -1;
+ case 0:
+ close(1);
+ dup(fd[1], 1);
+ if(fd[1] != 1)
+ close(fd[1]);
+ if(fd[0] != 0)
+ close(fd[0]);
+ exec(cmd, av);
+ ilog("exec: %r");
+ _exits("b0rked");
+ return -1;
+ default:
+ *pip = fd[0];
+ close(fd[1]);
+ return pid;
+ }
+}
+
+static int
+closepipe(int pid, int fd)
+{
+ int nz, wpid;
+ Waitmsg *w;
+
+ close(fd);
+ while(w = wait()){
+ nz = !w->msg || !w->msg[0];
+ wpid = w->pid;
+ free(w);
+ if(wpid == pid)
+ return nz? 0: -1;
+ }
+ return -1;
+}
+
+static char dupath[Pathlen];
+static char *duav[] = { "du", "-s", dupath, 0};
+
+vlong
+getquota(void)
+{
+ char buf[Pathlen + 128], *f[3];
+ int fd, pid;
+
+ werrstr("");
+ memset(buf, 0, sizeof buf);
+ snprint(dupath, sizeof dupath, "%s", mboxdir);
+ pid = openpipe(&fd, "/bin/du", duav);
+ if(pid == -1)
+ return -1;
+ if(read(fd, buf, sizeof buf) < 4){
+ closepipe(pid, fd);
+ return -1;
+ }
+ if(closepipe(pid, fd) == -1)
+ return -1;
+ if(getfields(buf, f, 2, 1, "\t") != 2)
+ return -1;
+ return strtoull(f[0], 0, 0);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/search.c
@@ -1,0 +1,329 @@
+#include "imap4d.h"
+
+static int
+filesearch(Msg *m, char *file, char *pat)
+{
+ char buf[Bufsize + 1];
+ int n, nbuf, npat, fd, ok;
+
+ npat = strlen(pat);
+ if(npat >= Bufsize/2)
+ return 0;
+
+ fd = msgfile(m, file);
+ if(fd < 0)
+ return 0;
+ ok = 0;
+ nbuf = 0;
+ for(;;){
+ n = read(fd, &buf[nbuf], Bufsize - nbuf);
+ if(n <= 0)
+ break;
+ nbuf += n;
+ buf[nbuf] = '\0';
+ if(cistrstr(buf, pat) != nil){
+ ok = 1;
+ break;
+ }
+ if(nbuf > npat){
+ memmove(buf, &buf[nbuf - npat], npat);
+ nbuf = npat;
+ }
+ }
+ close(fd);
+ return ok;
+}
+
+static int
+headersearch(Msg *m, char *hdr, char *pat)
+{
+ char *s, *t;
+ int ok, n;
+ Slist hdrs;
+
+ n = m->head.size + 3;
+ s = emalloc(n);
+ hdrs.next = nil;
+ hdrs.s = hdr;
+ ok = 0;
+ if(selectfields(s, n, m->head.buf, &hdrs, 1) > 0){
+ t = strchr(s, ':');
+ if(t != nil && cistrstr(t + 1, pat) != nil)
+ ok = 1;
+ }
+ free(s);
+ return ok;
+}
+
+static int
+addrsearch(Maddr *a, char *s)
+{
+ char *ok, *addr;
+
+ for(; a != nil; a = a->next){
+ addr = maddrstr(a);
+ ok = cistrstr(addr, s);
+ free(addr);
+ if(ok != nil)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+datecmp(char *date, Search *s)
+{
+ Tm tm;
+
+ date2tm(&tm, date);
+ if(tm.year < s->year)
+ return -1;
+ if(tm.year > s->year)
+ return 1;
+ if(tm.mon < s->mon)
+ return -1;
+ if(tm.mon > s->mon)
+ return 1;
+ if(tm.mday < s->mday)
+ return -1;
+ if(tm.mday > s->mday)
+ return 1;
+ return 0;
+}
+
+enum{
+ Simp = 0,
+ Sinfo = 1<<0,
+ Sbody = 1<<2,
+};
+
+int
+searchld(Search *s)
+{
+ int r;
+
+ for(r = 0; (r & Sbody) == 0 && s; s = s->next)
+ switch(s->key){
+ case SKall:
+ case SKanswered:
+ case SKdeleted:
+ case SKdraft:
+ case SKflagged:
+ case SKkeyword:
+ case SKnew:
+ case SKold:
+ case SKrecent:
+ case SKseen:
+ case SKunanswered:
+ case SKundeleted:
+ case SKundraft:
+ case SKunflagged:
+ case SKunkeyword:
+ case SKunseen:
+ case SKuid:
+ case SKset:
+ break;
+ case SKlarger:
+ case SKsmaller:
+ case SKbcc:
+ case SKcc:
+ case SKfrom:
+ case SKto:
+ case SKsubject:
+ case SKbefore:
+ case SKon:
+ case SKsince:
+ case SKsentbefore:
+ case SKsenton:
+ case SKsentsince:
+ r = Sinfo;
+ break;
+ case SKheader:
+ if(cistrcmp(s->hdr, "message-id") == 0)
+ r = Sinfo;
+ else
+ r = Sbody;
+ break;
+ case SKbody:
+ break; /* msgstruct doesn't do us any good */
+ case SKtext:
+ default:
+ r = Sbody;
+ break;
+ case SKnot:
+ r = searchld(s->left);
+ break;
+ case SKor:
+ r = searchld(s->left) | searchld(s->right);
+ break;
+ }
+ return 0;
+}
+
+/* important speed hack for apple mail */
+int
+msgidsearch(char *s, char *hdr)
+{
+ char c;
+ int l, r;
+
+ l = strlen(s);
+ c = 0;
+ if(s[0] == '<' && s[l-1] == '>'){
+ l -= 2;
+ s += 1;
+ c = s[l-1];
+ }
+ r = hdr && strstr(s, hdr) != nil;
+ if(c)
+ s[l-1] = c;
+ return r;
+}
+
+/*
+ * free to exit, parseerr, since called before starting any client reply
+ *
+ * the header and envelope searches should convert mime character set escapes.
+ */
+int
+searchmsg(Msg *m, Search *s, int ld)
+{
+ uint ok, id;
+ Msgset *ms;
+
+ if(m->expunged)
+ return 0;
+ if(ld & Sbody){
+ if(!msgstruct(m, 1))
+ return 0;
+ }else if (ld & Sinfo){
+ if(!msginfo(m))
+ return 0;
+ }
+ for(ok = 1; ok && s != nil; s = s->next){
+ switch(s->key){
+ default:
+ ok = 0;
+ break;
+ case SKnot:
+ ok = !searchmsg(m, s->left, ld);
+ break;
+ case SKor:
+ ok = searchmsg(m, s->left, ld) || searchmsg(m, s->right, ld);
+ break;
+ case SKall:
+ ok = 1;
+ break;
+ case SKanswered:
+ ok = (m->flags & Fanswered) == Fanswered;
+ break;
+ case SKdeleted:
+ ok = (m->flags & Fdeleted) == Fdeleted;
+ break;
+ case SKdraft:
+ ok = (m->flags & Fdraft) == Fdraft;
+ break;
+ case SKflagged:
+ ok = (m->flags & Fflagged) == Fflagged;
+ break;
+ case SKkeyword:
+ ok = (m->flags & s->num) == s->num;
+ break;
+ case SKnew:
+ ok = (m->flags & (Frecent|Fseen)) == Frecent;
+ break;
+ case SKold:
+ ok = (m->flags & Frecent) != Frecent;
+ break;
+ case SKrecent:
+ ok = (m->flags & Frecent) == Frecent;
+ break;
+ case SKseen:
+ ok = (m->flags & Fseen) == Fseen;
+ break;
+ case SKunanswered:
+ ok = (m->flags & Fanswered) != Fanswered;
+ break;
+ case SKundeleted:
+ ok = (m->flags & Fdeleted) != Fdeleted;
+ break;
+ case SKundraft:
+ ok = (m->flags & Fdraft) != Fdraft;
+ break;
+ case SKunflagged:
+ ok = (m->flags & Fflagged) != Fflagged;
+ break;
+ case SKunkeyword:
+ ok = (m->flags & s->num) != s->num;
+ break;
+ case SKunseen:
+ ok = (m->flags & Fseen) != Fseen;
+ break;
+ case SKlarger:
+ ok = msgsize(m) > s->num;
+ break;
+ case SKsmaller:
+ ok = msgsize(m) < s->num;
+ break;
+ case SKbcc:
+ ok = addrsearch(m->bcc, s->s);
+ break;
+ case SKcc:
+ ok = addrsearch(m->cc, s->s);
+ break;
+ case SKfrom:
+ ok = addrsearch(m->from, s->s);
+ break;
+ case SKto:
+ ok = addrsearch(m->to, s->s);
+ break;
+ case SKsubject:
+ ok = cistrstr(m->info[Isubject], s->s) != nil;
+ break;
+ case SKbefore:
+ ok = datecmp(m->info[Iunixdate], s) < 0;
+ break;
+ case SKon:
+ ok = datecmp(m->info[Iunixdate], s) == 0;
+ break;
+ case SKsince:
+ ok = datecmp(m->info[Iunixdate], s) > 0;
+ break;
+ case SKsentbefore:
+ ok = datecmp(m->info[Idate], s) < 0;
+ break;
+ case SKsenton:
+ ok = datecmp(m->info[Idate], s) == 0;
+ break;
+ case SKsentsince:
+ ok = datecmp(m->info[Idate], s) > 0;
+ break;
+ case SKuid:
+ id = m->uid;
+ goto set;
+ case SKset:
+ id = m->seq;
+ set:
+ for(ms = s->set; ms != nil; ms = ms->next)
+ if(id >= ms->from && id <= ms->to)
+ break;
+ ok = ms != nil;
+ break;
+ case SKheader:
+ if(cistrcmp(s->hdr, "message-id") == 0)
+ ok = msgidsearch(s->s, m->info[Imessageid]);
+ else
+ ok = headersearch(m, s->hdr, s->s);
+ break;
+ case SKbody:
+ case SKtext:
+ if(s->key == SKtext && cistrstr(m->head.buf, s->s)){
+ ok = 1;
+ break;
+ }
+ ok = filesearch(m, "body", s->s);
+ break;
+ }
+ }
+ return ok;
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/store.c
@@ -1,0 +1,119 @@
+#include "imap4d.h"
+
+static Namedint flagmap[] =
+{
+ {"\\Seen", Fseen},
+ {"\\Answered", Fanswered},
+ {"\\Flagged", Fflagged},
+ {"\\Deleted", Fdeleted},
+ {"\\Draft", Fdraft},
+ {"\\Recent", Frecent},
+ {nil, 0}
+};
+
+int
+storemsg(Box *box, Msg *m, int uids, void *vst)
+{
+ int f, flags;
+ Store *st;
+
+ if(m->expunged)
+ return uids;
+ st = vst;
+ flags = st->flags;
+
+ f = m->flags;
+ if(st->sign == '+')
+ f |= flags;
+ else if(st->sign == '-')
+ f &= ~flags;
+ else
+ f = flags;
+
+ /*
+ * not allowed to change the recent flag
+ */
+ f = (f & ~Frecent) | (m->flags & Frecent);
+ setflags(box, m, f);
+
+ if(st->op != Stflagssilent){
+ m->sendflags = 1;
+ box->sendflags = 1;
+ }
+
+ return 1;
+}
+
+/*
+ * update flags & global flag counts in box
+ */
+void
+setflags(Box *box, Msg *m, int f)
+{
+ if(f == m->flags)
+ return;
+ box->dirtyimp = 1;
+ if((f & Frecent) != (m->flags & Frecent)){
+ if(f & Frecent)
+ box->recent++;
+ else
+ box->recent--;
+ }
+ m->flags = f;
+}
+
+void
+sendflags(Box *box, int uids)
+{
+ Msg *m;
+
+ if(!box->sendflags)
+ return;
+
+ box->sendflags = 0;
+ for(m = box->msgs; m != nil; m = m->next){
+ if(!m->expunged && m->sendflags){
+ Bprint(&bout, "* %ud FETCH (", m->seq);
+ if(uids)
+ Bprint(&bout, "uid %ud ", m->uid);
+ Bprint(&bout, "FLAGS (");
+ writeflags(&bout, m, 1);
+ Bprint(&bout, "))\r\n");
+ m->sendflags = 0;
+ }
+ }
+}
+
+void
+writeflags(Biobuf *b, Msg *m, int recentok)
+{
+ char *sep;
+ int f;
+
+ sep = "";
+ for(f = 0; flagmap[f].name != nil; f++){
+ if((m->flags & flagmap[f].v)
+ && (flagmap[f].v != Frecent || recentok)){
+ Bprint(b, "%s%s", sep, flagmap[f].name);
+ sep = " ";
+ }
+ }
+}
+
+int
+msgseen(Box *box, Msg *m)
+{
+ if(m->flags & Fseen)
+ return 0;
+ m->flags |= Fseen;
+ box->sendflags = 1;
+ m->sendflags = 1;
+ box->dirtyimp = 1;
+ return 1;
+}
+
+uint
+mapflag(char *name)
+{
+ return mapint(flagmap, name);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/imap4d/utils.c
@@ -1,0 +1,209 @@
+#include "imap4d.h"
+
+/*
+ * reverse string [s:e) in place
+ */
+void
+strrev(char *s, char *e)
+{
+ int c;
+
+ while(--e > s){
+ c = *s;
+ *s++ = *e;
+ *e = c;
+ }
+}
+
+int
+isdotdot(char *s)
+{
+ return s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == 0);
+}
+
+int
+issuffix(char *suf, char *s)
+{
+ int n;
+
+ n = strlen(s) - strlen(suf);
+ if(n < 0)
+ return 0;
+ return strcmp(s + n, suf) == 0;
+}
+
+int
+isprefix(char *pre, char *s)
+{
+ return strncmp(pre, s, strlen(pre)) == 0;
+}
+
+char*
+readfile(int fd)
+{
+ char *s;
+ long length;
+ Dir *d;
+
+ d = dirfstat(fd);
+ if(d == nil)
+ return nil;
+ length = d->length;
+ free(d);
+ s = binalloc(&parsebin, length + 1, 0);
+ if(s == nil || readn(fd, s, length) != length)
+ return nil;
+ s[length] = 0;
+ return s;
+}
+
+/*
+ * create the imap tmp file.
+ * it just happens that we don't need multiple temporary files.
+ */
+int
+imaptmp(void)
+{
+ char buf[ERRMAX], name[Pathlen];
+ int tries, fd;
+
+ snprint(name, sizeof name, "/mail/box/%s/mbox.tmp.imp", username);
+ for(tries = 0; tries < Locksecs*2; tries++){
+ fd = create(name, ORDWR|ORCLOSE|OCEXEC, DMEXCL|0600);
+ if(fd >= 0)
+ return fd;
+ errstr(buf, sizeof buf);
+ if(cistrstr(buf, "locked") == nil)
+ break;
+ sleep(500);
+ }
+ return -1;
+}
+
+/*
+ * open a file which might be locked.
+ * if it is, spin until available
+ */
+static char *etab[] = {
+ "not found",
+ "does not exist",
+ "file is locked",
+ "exclusive lock",
+ "already exists",
+};
+
+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;
+}
+
+int
+openlocked(char *dir, char *file, int mode)
+{
+ int i, fd;
+
+ for(i = 0; i < 30; i++){
+ if((fd = cdopen(dir, file, mode)) >= 0 || bad(0))
+ return fd;
+ if((fd = cdcreate(dir, file, mode|OEXCL, DMEXCL|0600)) >= 0 || bad(2))
+ return fd;
+ sleep(1000);
+ }
+ werrstr("lock timeout");
+ return -1;
+}
+
+int
+fqid(int fd, Qid *qid)
+{
+ Dir *d;
+
+ d = dirfstat(fd);
+ if(d == nil)
+ return -1;
+ *qid = d->qid;
+ free(d);
+ return 0;
+}
+
+uint
+mapint(Namedint *map, char *name)
+{
+ int i;
+
+ for(i = 0; map[i].name != nil; i++)
+ if(cistrcmp(map[i].name, name) == 0)
+ break;
+ return map[i].v;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+
+ t = emalloc(strlen(s) + 1);
+ strcpy(t, s);
+ return t;
+}
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ bye("server out of memory");
+ setmalloctag(p, getcallerpc(&n));
+ return p;
+}
+
+void*
+ezmalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == nil)
+ bye("server out of memory");
+ setmalloctag(p, getcallerpc(&n));
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+ p = realloc(p, n);
+ if(p == nil)
+ bye("server out of memory");
+ setrealloctag(p, getcallerpc(&p));
+ return p;
+}
+
+void
+setname(char *fmt, ...)
+{
+ char buf[128], buf2[32], *p;
+ int fd;
+ va_list arg;
+
+ va_start(arg, fmt);
+ p = vseprint(buf, buf + sizeof buf, fmt, arg);
+ va_end(arg);
+
+ snprint(buf2, sizeof buf2, "#p/%d/args", getpid());
+ if((fd = open(buf2, OWRITE)) >= 0){
+ write(fd, buf, p - buf);
+ close(fd);
+ }
+}
--- a/sys/src/cmd/upas/marshal/marshal.c
+++ b/sys/src/cmd/upas/marshal/marshal.c
@@ -196,7 +196,8 @@
Addr *to, *cc, *bcc;
Attach *first, **l, *a;
Biobuf in, out, *b;
- String *file, *hdrstring;
+ String *hdrstring;
+ char file[Pathlen];
noinput = 0;
subject = nil;
@@ -343,10 +344,8 @@
bwritesfree(&out, &hdrstring);
/* read user's standard headers */
- file = s_new();
- mboxpath("headers", user, file, 0);
- b = Bopen(s_to_c(file), OREAD);
- if(b != nil){
+ mboxpathbuf(file, sizeof file, user, "headers");
+ if(b = Bopen(file, OREAD)){
if (readheaders(b, &flags, &hdrstring, nil, nil, nil, nil, 0) == Error)
fatal("reading");
Bterm(b);
@@ -1034,69 +1033,15 @@
return 0;
}
-/* open the folder using the recipients account name */
-static int
-openfolder(char *rcvr)
-{
- int c, fd, scarey;
- char *p;
- Dir *d;
- String *file;
-
- file = s_new();
- mboxpath("f", user, file, 0);
-
- /* if $mail/f exists, store there, otherwise in $mail */
- d = dirstat(s_to_c(file));
- if(d == nil || d->qid.type != QTDIR){
- scarey = 1;
- file->ptr -= 1;
- } else {
- s_putc(file, '/');
- scarey = 0;
- }
- free(d);
-
- p = strrchr(rcvr, '!');
- if(p != nil)
- rcvr = p+1;
-
- while(*rcvr && *rcvr != '@'){
- c = *rcvr++;
- if(c == '/')
- c = '_';
- s_putc(file, c);
- }
- s_terminate(file);
-
- if(scarey && special(file)){
- fprint(2, "%s: won't overwrite %s\n", argv0, s_to_c(file));
- s_free(file);
- return -1;
- }
-
- fd = open(s_to_c(file), OWRITE);
- if(fd < 0)
- fd = create(s_to_c(file), OWRITE, 0660);
-
- s_free(file);
- return fd;
-}
-
/* start up sendmail and return an fd to talk to it with */
int
sendmail(Addr *to, Addr *cc, Addr *bcc, int *pid, char *rcvr)
{
- int ac, fd;
- int pfd[2];
- char **av, **v;
+ int ac, fd, pfd[2];
+ char **v, cmd[Pathlen];
Addr *a;
- String *cmd;
+ Biobuf *b;
- fd = -1;
- if(rcvr != nil)
- fd = openfolder(rcvr);
-
ac = 0;
for(a = to; a != nil; a = a->next)
ac++;
@@ -1104,7 +1049,7 @@
ac++;
for(a = bcc; a != nil; a = a->next)
ac++;
- v = av = emalloc(sizeof(char*)*(ac+20));
+ v = emalloc(sizeof(char*)*(ac+20));
ac = 0;
v[ac++] = "sendmail";
if(xflag)
@@ -1145,13 +1090,17 @@
break;
case 0:
close(pfd[0]);
- seek(fd, 0, 2);
+ b = 0;
+ /* BOTCH; "From " time gets changed */
+ if(rcvr)
+ b = openfolder(foldername(nil, user, rcvr), time(0));
+ fd = b? Bfildes(b): -1;
printunixfrom(fd);
tee(0, pfd[1], fd);
write(fd, "\n", 1);
+ closefolder(b);
exits(0);
default:
- close(fd);
close(pfd[1]);
dup(pfd[0], 0);
break;
@@ -1160,16 +1109,14 @@
if(replymsg != nil)
putenv("replymsg", replymsg);
-
- cmd = mboxpath("pipefrom", login, s_new(), 0);
- exec(s_to_c(cmd), av);
- exec("/bin/myupassend", av);
- exec("/bin/upas/send", av);
+ mboxpathbuf(cmd, sizeof cmd, login, "pipefrom");
+ exec(cmd, v);
+ exec("/bin/myupassend", v);
+ exec("/bin/upas/send", v);
fatal("execing: %r");
break;
default:
- if(rcvr != nil)
- close(fd);
+ free(v);
close(pfd[0]);
break;
}
@@ -1267,20 +1214,20 @@
Alias*
readaliases(void)
{
+ char file[Pathlen];
Addr *addr, **al;
Alias *a, **l, *first;
Sinstack *sp;
- String *file, *line, *token;
+ String *line, *token;
static int already;
first = nil;
- file = s_new();
line = s_new();
token = s_new();
/* open and get length */
- mboxpath("names", login, file, 0);
- sp = s_allocinstack(s_to_c(file));
+ mboxpathbuf(file, Pathlen, login, "names");
+ sp = s_allocinstack(file);
if(sp == nil)
goto out;
@@ -1308,7 +1255,6 @@
}
s_freeinstack(sp);
out:
- s_free(file);
s_free(line);
s_free(token);
return first;
--- a/sys/src/cmd/upas/marshal/mkfile
+++ b/sys/src/cmd/upas/marshal/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=marshal
@@ -7,8 +8,6 @@
HFILES= ../common/common.h\
OFILES= marshal.$O
-
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
--- a/sys/src/cmd/upas/misc/go.fishing
+++ /dev/null
@@ -1,15 +1,0 @@
-#!/bin/rc
-# go.fishing - set up vacation responder
-rfork e
-
-cd /mail/box/$user
-if (test -e gone.fishing) {
- echo $0: /mail/box/$user/gone.fishing already exists >[1=2]
- exit 'already fishing'
-}
-
->gone.addrs
-chmod -a gone.addrs
->gone.addrs
-chmod +arw gone.addrs
->>gone.fishing
--- a/sys/src/cmd/upas/misc/gone.fishing
+++ /dev/null
@@ -1,19 +1,0 @@
-#!/bin/rc
-# gone.fishing local!$user /mail/box/$user/mbox - vacation responder
-# as pipeto script
-
-# standard library. saves the message on standard input in $TMP.msg and
-# parses it into /mail/fs/mbox/1.
-. /mail/lib/pipeto.lib $*
-
-{cat $TMP.msg; echo} >>/mail/box/$USER/gone.mail
-
-message=/mail/box/$USER/gone.msg
-if (! test -e $message)
- message=/mail/lib/gone.msg
-
-MAILTO=`{cat $D/replyto}
-grep '^'$"MAILTO'$' /mail/box/$USER/gone.addrs >/dev/null >[2=1] || {
- echo $MAILTO >>/mail/box/$USER/gone.addrs
- mail $MAILTO <$message
-}
--- a/sys/src/cmd/upas/misc/gone.msg
+++ b/sys/src/cmd/upas/misc/gone.msg
@@ -1,5 +1,3 @@
-subject: away from my computer
-
This is a recorded message. I am currently out of contact with my
computer system. Your message to me has been saved and will be
read upon my return. This is the last time you will receive this
--- a/sys/src/cmd/upas/misc/mail
+++ /dev/null
@@ -1,12 +1,0 @@
-#!/bin/rc
-switch($#*){
-case 0
- exec upas/nedmail
-}
-
-switch($1){
-case -f* -r* -c* -m*
- exec upas/nedmail $*
-case *
- exec upas/marshal -8 $*
-}
--- /dev/null
+++ b/sys/src/cmd/upas/misc/mail.rc
@@ -1,0 +1,12 @@
+#!/bin/rc
+switch($#*){
+case 0
+ exec upas/nedmail
+}
+
+switch($1){
+case -f* -r* -c* -m*
+ exec upas/nedmail $*
+case *
+ exec upas/marshal -8 $*
+}
--- a/sys/src/cmd/upas/misc/mkfile
+++ b/sys/src/cmd/upas/misc/mkfile
@@ -1,32 +1,35 @@
-RCFILES=mail\
- go.fishing\
-all:VQ:
+RCFILES=mail.rc\
+
+all:Q:
;
-installall:VQ: install
+installall:Q: install
;
-install:V: mail go.fishing gone.msg gone.fishing
- cp mail go.fishing /rc/bin
- cp gone.msg gone.fishing /mail/lib
+install:V:
+ cp mail.rc /rc/bin/mail
-safeinstall:V: install
+safeinstall:V:
+ cp mail.rc /rc/bin/mail
-safeinstallall:V: install
+safeinstallall:V:
+ cp mail.rc /rc/bin/mail
-clean:VQ:
+clean:Q:
;
nuke:V:
- ; # rm /rc/bin/^(mail gone.fishing)
+ rm /rc/bin/mail
UPDATE=\
- go.fishing\
gone.fishing\
gone.msg\
- mail\
+ mail.rc\
+ mail.sh\
+ makefile\
mkfile\
namefiles\
+ omail.rc\
qmail\
remotemail\
rewrite\
--- /dev/null
+++ b/sys/src/cmd/upas/misc/omail.rc
@@ -1,0 +1,14 @@
+#!/bin/rc
+switch($#*){
+case 0
+ exec upas/edmail -m
+}
+
+switch($1){
+case -F* -m* -f* -r* -p* -e* -c* -D*
+ exec upas/edmail -m $*
+case '-#'* -a*
+ exec upas/sendmail $*
+case *
+ exec upas/sendmail $*
+}
--- a/sys/src/cmd/upas/misc/unix/gone.fishing.sh
+++ /dev/null
@@ -1,9 +1,0 @@
-#!/bin/sh
-PATH=/bin:/usr/bin
-message=${1-/usr/lib/upas/gone.msg}
-return=`sed '2,$s/^From[ ]/>&/'|tee -a $HOME/gone.mail|sed -n '1s/^From[ ]\([^ ]*\)[ ].*$/\1/p'`
-echo '' >>$HOME/gone.mail
-grep "^$return" $HOME/gone.addrs >/dev/null 2>/dev/null || {
- echo $return >>$HOME/gone.addrs
- mail $return < $message
-}
--- a/sys/src/cmd/upas/misc/unix/mail.c
+++ /dev/null
@@ -1,51 +1,0 @@
-/*
- * #!/bin/sh
- * case $1 in
- * -n)
- * exit 0 ;;
- * -m*|-f*|-r*|-p*|-e*|"")
- * exec /usr/lib/upas/edmail $*
- * exit $? ;;
- * *)
- * exec /usr/lib/upas/send $*
- * exit $? ;;
- * esac
- */
-
-
-extern *UPASROOT;
-
-#define EDMAIL "edmail"
-#define SEND "send"
-
-main (argc, argv)
- int argc;
- char **argv;
-{
- char *progname = SEND;
- char realprog[500];
-
- if (argc > 1) {
- if (argv[1][0] == '-') {
- switch (argv[1][1]) {
- case 'n':
- exit (0);
-
- case 'm':
- case 'f':
- case 'r':
- case 'p':
- case 'e':
- case '\0':
- progname = EDMAIL;
- }
- }
- } else
- progname = EDMAIL;
-
- sprint(realprog, "%s/%s", UPASROOT, progname);
- execv (realprog, argv);
- perror (realprog);
- exit (1);
-}
-
--- a/sys/src/cmd/upas/misc/unix/mail.sh
+++ /dev/null
@@ -1,12 +1,0 @@
-#!/bin/sh
-case $1 in
--n)
- exec LIBDIR/notify
- exit $? ;;
--m*|-f*|-r*|-p*|-e*|"")
- exec LIBDIR/edmail $*
- exit $? ;;
-*)
- exec LIBDIR/send $*
- exit $? ;;
-esac
--- a/sys/src/cmd/upas/misc/unix/makefile
+++ /dev/null
@@ -1,44 +1,0 @@
-LIB=/usr/lib/upas
-CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include -I/usr/include/sys
-LFLAGS=-g
-HOSTNAME=cat /etc/whoami
-
-.c.o: ; $(CC) -c $(CFLAGS) $*.c
-all: mail
-
-sedfile:
- echo 's+LIBDIR+$(LIB)+g' >sed.file
- echo 's+HOSTNAME+$(HOSTNAME)+g' >>sed.file
-
-install: sedfile install.fish install.mail.sh
-
-install.fish:
- cp gone.msg $(LIB)
- sed -f sed.file gone.fishing >$(LIB)/gone.fishing
- -chmod 775 $(LIB)/gone.fishing
- -chown bin $(LIB)/gone.fishing $(LIB)/gone.msg
-
-install.mail.sh:
- sed -f sed.file mail.sh >/bin/mail
- -chown bin /bin/mail
- -chmod 775 /bin/mail
-
-install.notify: notify
- cp notify $(LIB)/notify
- -chmod 775 $(LIB)/notify
- -chown bin $(LIB)/notify
-
-install.mail: mail
- cp mail /bin
- strip /bin/mail
-
-notify: notify.o
- cc $(LFLAGS) notify.o -o notify
-
-mail: mail.o ../config/config.o
- cc $(LFLAGS) mail.o ../config/config.o -o mail
-
-clean:
- -rm -f *.[oOa] core a.out *.sL notify
- -rm -f sed.file mail
-
--- a/sys/src/cmd/upas/mkfile
+++ b/sys/src/cmd/upas/mkfile
@@ -1,7 +1,27 @@
</$objtype/mkfile
LIBS=common
-PROGS=smtp alias fs ned misc q send scanmail pop3 ml marshal vf filterkit unesc bayes
+PROGS=\
+ Mail\
+ alias\
+ bayes\
+ binscripts\
+ filterkit\
+ fs\
+ imap4d\
+ marshal\
+ misc\
+ ml\
+ ned\
+ pop3\
+ q\
+ scanmail\
+ send\
+ smtp\
+ spf\
+ unesc\
+ vf\
+
#libs must be made first
DIRS=$LIBS $PROGS
--- /dev/null
+++ b/sys/src/cmd/upas/mkupas
@@ -1,0 +1,5 @@
+BIN=/$objtype/bin/upas
+ABIN=/acme/bin/$objtype
+
+../common/libcommon.a$O:
+ cd ../common; mk; mk clean
--- a/sys/src/cmd/upas/ml/common.c
+++ b/sys/src/cmd/upas/ml/common.c
@@ -4,10 +4,9 @@
String*
getaddr(Node *p)
{
- for(; p; p = p->next){
+ for(; p; p = p->next)
if(p->s && p->addr)
return p->s;
- }
return nil;
}
@@ -42,14 +41,15 @@
dirwstat(file, &nd);
} else
seek(fd, 0, 2);
- if(rem)
+ if(rem){
+ sendnotification(addr, listname, rem);
fprint(fd, "!%s\n", addr);
- else
+ }else{
fprint(fd, "%s\n", addr);
+ if(*addr != '#')
+ sendnotification(addr, listname, rem);
+ }
close(fd);
-
- if(*addr != '#')
- sendnotification(addr, listname, rem);
}
void
@@ -75,10 +75,9 @@
Addr **l;
Addr *a;
- for(l = &al; *l; l = &(*l)->next){
+ for(l = &al; *l; l = &(*l)->next)
if(strcmp(addr, (*l)->addr) == 0)
return 0;
- }
na++;
*l = a = malloc(sizeof(*a)+strlen(addr)+1);
if(a == nil)
@@ -113,27 +112,25 @@
Bterm(b);
}
-/* start a mailer sending to all the receivers for list `name' */
+static void
+setsender(char *name)
+{
+ char *s;
+
+ s = smprint("%s-bounces", name);
+ putenv("upasname", s);
+ free(s);
+}
+
+/* start a mailer sending to all the receivers */
int
startmailer(char *name)
{
- int pfd[2];
char **av;
- int ac;
+ int pfd[2], ac;
Addr *a;
- /*
- * we used to send mail to the list from /dev/null,
- * which is equivalent to an smtp return address of <>,
- * but such a return address should only be used when
- * sending a bounce to a single address. our smtpd lets
- * such mail through, but refuses mail from <> to multiple
- * addresses, since that's not allowed and is likely spam.
- * thus mailing list mail to another upas system with
- * multiple addressees was being rejected.
- */
- putenv("upasname", smprint("%s-owner", name));
-
+ setsender(name);
if(pipe(pfd) < 0)
sysfatal("creating pipe: %r");
switch(fork()){
@@ -171,7 +168,7 @@
int pfd[2];
Waitmsg *w;
- putenv("upasname", smprint("%s-owner", listname));
+ setsender(listname);
if(pipe(pfd) < 0)
sysfatal("creating pipe: %r");
switch(fork()){
--- a/sys/src/cmd/upas/ml/mkfile
+++ b/sys/src/cmd/upas/ml/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=ml\
mlowner\
@@ -7,7 +8,7 @@
OFILES=\
common.$O\
-LIB=../common/libcommon.av\
+LIB=../common/libcommon.a$O\
UHFILES= ../common/common.h\
../common/sys.h\
@@ -18,8 +19,6 @@
LIB=../common/libcommon.a$O\
-BIN=/$objtype/bin/upas
-
UPDATE=\
mkfile\
$UHFILES\
@@ -34,7 +33,4 @@
$O.mlowner: ../smtp/rfc822.tab.$O
../smtp/y.tab.h ../smtp/rfc822.tab.$O:
- @{
- cd ../smtp
- mk rfc822.tab.$O
- }
+ cd ../smtp && mk rfc822.tab.$O
--- a/sys/src/cmd/upas/ml/ml.c
+++ b/sys/src/cmd/upas/ml/ml.c
@@ -1,18 +1,91 @@
#include "common.h"
#include "dat.h"
-Biobuf in;
+Biobuf in;
+Addr *al;
+int na;
+String *from;
+String *sender;
-Addr *al;
-int na;
-String *from;
-String *sender;
+char*
+trim(char *s)
+{
+ while(*s == ' ' || *s == '\t')
+ s++;
+ return s;
+}
-void printmsg(int fd, String *msg, char *replyto, char *listname);
-void appendtoarchive(char* listname, String *firstline, String *msg);
-void printsubject(int fd, Field *f, char *listname);
+/* add the listname to the subject */
+void
+printsubject(int fd, Field *f, char *listname)
+{
+ char *s, *e, *ln;
+ Node *p;
+ if(f == nil || f->node == nil){
+ fprint(fd, "Subject: [%s]\n", listname);
+ return;
+ }
+ s = e = f->node->end + 1;
+ for(p = f->node; p; p = p->next)
+ e = p->end;
+ *e = 0;
+ ln = smprint("[%s]", listname);
+ if(ln != nil && strstr(s, ln) == nil)
+ fprint(fd, "Subject: %s %s\n", ln, trim(s));
+ else
+ fprint(fd, "Subject: %s\n", trim(s));
+ free(ln);
+ *e = '\n';
+}
+
+/* send message filtering Reply-to out of messages */
void
+printmsg(int fd, String *msg, char *replyto, char *listname)
+{
+ Field *f, *subject;
+ Node *p;
+ char *cp, *ocp;
+
+ subject = nil;
+ cp = s_to_c(msg);
+ for(f = firstfield; f; f = f->next){
+ ocp = cp;
+ for(p = f->node; p; p = p->next)
+ cp = p->end+1;
+ switch(f->node->c){
+ case SUBJECT:
+ subject = f;
+ case REPLY_TO:
+ case PRECEDENCE:
+ continue;
+ }
+ write(fd, ocp, cp-ocp);
+ }
+ printsubject(fd, subject, listname);
+ fprint(fd, "Reply-To: %s\nPrecedence: bulk\n", replyto);
+ write(fd, cp, s_len(msg) - (cp - s_to_c(msg)));
+}
+
+/* if the mailbox exists, cat the mail to the end of it */
+void
+appendtoarchive(char* listname, String *firstline, String *msg)
+{
+ char *f;
+ Biobuf *b;
+
+ f = foldername(nil, listname, "mbox");
+ if(access(f, 0) < 0)
+ return;
+ if((b = openfolder(f, time(0))) == nil)
+ return;
+ Bwrite(b, s_to_c(firstline), s_len(firstline));
+ Bwrite(b, s_to_c(msg), s_len(msg));
+ Bwrite(b, "\n", 1);
+ closefolder(b);
+}
+
+void
usage(void)
{
fprint(2, "usage: %s address-list-file listname\n", argv0);
@@ -22,16 +95,21 @@
void
main(int argc, char **argv)
{
- String *msg;
- String *firstline;
- char *listname, *alfile;
+ char *listname, *alfile, *replytoname;
+ int fd, private;
+ String *msg, *firstline;
Waitmsg *w;
- int fd;
- char *replytoname = nil;
+ private = 0;
+ replytoname = nil;
ARGBEGIN{
+ default:
+ usage();
+ case 'p':
+ private = 1;
+ break;
case 'r':
- replytoname = ARGF();
+ replytoname = EARGF(usage());
break;
}ARGEND;
@@ -56,8 +134,10 @@
if(s_read_line(&in, firstline) == nil)
sysfatal("reading input: %r");
- /* read up to the first 128k of the message. more is ridiculous.
- Not if word documents are distributed. Upped it to 2MB (pb) */
+ /*
+ * read up to the first 128k of the message. more is ridiculous.
+ * Not if word documents are distributed. Upped it to 2MB (pb)
+ */
if(s_read(&in, msg, 2*1024*1024) <= 0)
sysfatal("reading input: %r");
@@ -73,7 +153,8 @@
sysfatal("message must contain From: or Sender:");
if(strcmp(listname, s_to_c(from)) == 0)
sysfatal("can't remail messages from myself");
- addaddr(s_to_c(from));
+ if(addaddr(s_to_c(from)) != 0 && private)
+ sysfatal("not a list member");
/* start the mailer up and return a pipe to it */
fd = startmailer(listname);
@@ -92,76 +173,4 @@
/* if the mailbox exists, cat the mail to the end of it */
appendtoarchive(listname, firstline, msg);
exits(0);
-}
-
-/* send message filtering Reply-to out of messages */
-void
-printmsg(int fd, String *msg, char *replyto, char *listname)
-{
- Field *f, *subject;
- Node *p;
- char *cp, *ocp;
-
- subject = nil;
- cp = s_to_c(msg);
- for(f = firstfield; f; f = f->next){
- ocp = cp;
- for(p = f->node; p; p = p->next)
- cp = p->end+1;
- if(f->node->c == REPLY_TO)
- continue;
- if(f->node->c == PRECEDENCE)
- continue;
- if(f->node->c == SUBJECT){
- subject = f;
- continue;
- }
- write(fd, ocp, cp-ocp);
- }
- printsubject(fd, subject, listname);
- fprint(fd, "Reply-To: %s\nPrecedence: bulk\n", replyto);
- write(fd, cp, s_len(msg) - (cp - s_to_c(msg)));
-}
-
-/* if the mailbox exists, cat the mail to the end of it */
-void
-appendtoarchive(char* listname, String *firstline, String *msg)
-{
- String *mbox;
- int fd;
-
- mbox = s_new();
- mboxpath("mbox", listname, mbox, 0);
- if(access(s_to_c(mbox), 0) < 0)
- return;
- fd = open(s_to_c(mbox), OWRITE);
- if(fd < 0)
- return;
- s_append(msg, "\n");
- write(fd, s_to_c(firstline), s_len(firstline));
- write(fd, s_to_c(msg), s_len(msg));
-}
-
-/* add the listname to the subject */
-void
-printsubject(int fd, Field *f, char *listname)
-{
- char *s, *e;
- Node *p;
- char *ln;
-
- if(f == nil || f->node == nil){
- fprint(fd, "Subject: [%s]\n", listname);
- return;
- }
- s = e = f->node->end + 1;
- for(p = f->node; p; p = p->next)
- e = p->end;
- *e = 0;
- ln = smprint("[%s]", listname);
- if(ln != nil && strstr(s, ln) == nil)
- fprint(fd, "Subject: %s%s\n", ln, s);
- else
- fprint(fd, "Subject:%s\n", s);
- free(ln);
}
--- a/sys/src/cmd/upas/ml/mlmgr.c
+++ b/sys/src/cmd/upas/ml/mlmgr.c
@@ -1,12 +1,64 @@
#include "common.h"
#include "dat.h"
-int cflag;
-int aflag;
-int rflag;
+enum {
+ Bounces,
+ Owner,
+ List,
+ Nboxes,
+};
-int createpipeto(char *alfile, char *user, char *listname, int owner);
+char *suffix[Nboxes] = {
+[Bounces] "-bounces",
+[Owner] "-owner",
+[List] "",
+};
+int
+createpipeto(char *alfile, char *user, char *listname, char *dom, int which)
+{
+ char buf[Pathlen], rflag[64];
+ int fd;
+ Dir *d;
+
+ mboxpathbuf(buf, sizeof buf, user, "pipeto");
+
+ fprint(2, "creating new pipeto: %s\n", buf);
+ fd = create(buf, OWRITE, 0775);
+ if(fd < 0)
+ return -1;
+ d = dirfstat(fd);
+ if(d == nil){
+ fprint(fd, "Couldn't stat %s: %r\n", buf);
+ return -1;
+ }
+ d->mode |= 0775;
+ if(dirfwstat(fd, d) < 0)
+ fprint(fd, "Couldn't wstat %s: %r\n", buf);
+ free(d);
+
+ if(dom != nil)
+ snprint(rflag, sizeof rflag, "-r%s@%s ", listname, dom);
+ else
+ rflag[0] = 0;
+
+ fprint(fd, "#!/bin/rc\n");
+ switch(which){
+ case Owner:
+ fprint(fd, "/bin/upas/mlowner %s %s\n", alfile, listname);
+ break;
+ case List:
+ fprint(fd, "/bin/upas/ml %s%s %s\n", rflag, alfile, user);
+ break;
+ case Bounces:
+ fprint(fd, "exit ''\n");
+ break;
+ }
+ close(fd);
+
+ return 0;
+}
+
void
usage(void)
{
@@ -18,24 +70,24 @@
void
main(int argc, char **argv)
{
- char *listname, *addr;
- String *owner, *alfile;
+ char *listname, *dom, *addr, alfile[Pathlen], buf[64], flag[127];
+ int i;
+
rfork(RFENVG|RFREND);
+ memset(flag, 0, sizeof flag);
ARGBEGIN{
case 'c':
- cflag = 1;
- break;
case 'r':
- rflag = 1;
- break;
case 'a':
- aflag = 1;
+ flag[ARGC()] = 1;
break;
+ default:
+ usage();
}ARGEND;
- if(aflag + rflag + cflag > 1){
+ if(flag['a'] + flag['r'] + flag['c'] > 1){
fprint(2, "%s: -a, -r, and -c are mutually exclusive\n", argv0);
exits("usage");
}
@@ -44,67 +96,31 @@
usage();
listname = argv[0];
- alfile = s_new();
- mboxpath("address-list", listname, alfile, 0);
+ if((dom = strchr(listname, '@')) != nil)
+ *dom++ = 0;
+ mboxpathbuf(alfile, sizeof alfile, listname, "address-list");
- if(cflag){
- owner = s_copy(listname);
- s_append(owner, "-owner");
- if(creatembox(listname, nil) < 0)
- sysfatal("creating %s's mbox: %r", listname);
- if(creatembox(s_to_c(owner), nil) < 0)
- sysfatal("creating %s's mbox: %r", s_to_c(owner));
- if(createpipeto(s_to_c(alfile), listname, listname, 0) < 0)
- sysfatal("creating %s's pipeto: %r", s_to_c(owner));
- if(createpipeto(s_to_c(alfile), s_to_c(owner), listname, 1) < 0)
- sysfatal("creating %s's pipeto: %r", s_to_c(owner));
- writeaddr(s_to_c(alfile), "# mlmgr c flag", 0, listname);
- } else if(rflag){
+ if(flag['c']){
+ for(i = 0; i < Nboxes; i++){
+ snprint(buf, sizeof buf, "%s%s", listname, suffix[i]);
+ if(creatembox(buf, nil) < 0)
+ sysfatal("creating %s's mbox: %r", buf);
+ if(createpipeto(alfile, buf, listname, dom, i) < 0)
+ sysfatal("creating %s's pipeto: %r", buf);
+ }
+ writeaddr(alfile, "# mlmgr c flag", 0, listname);
+ } else if(flag['r']){
if(argc != 2)
usage();
addr = argv[1];
- writeaddr(s_to_c(alfile), "# mlmgr r flag", 0, listname);
- writeaddr(s_to_c(alfile), addr, 1, listname);
- } else if(aflag){
+ writeaddr(alfile, "# mlmgr r flag", 0, listname);
+ writeaddr(alfile, addr, 1, listname);
+ } else if(flag['a']){
if(argc != 2)
usage();
addr = argv[1];
- writeaddr(s_to_c(alfile), "# mlmgr a flag", 0, listname);
- writeaddr(s_to_c(alfile), addr, 0, listname);
- } else
- usage();
- exits(0);
-}
-
-int
-createpipeto(char *alfile, char *user, char *listname, int owner)
-{
- String *f;
- int fd;
- Dir *d;
-
- f = s_new();
- mboxpath("pipeto", user, f, 0);
- fprint(2, "creating new pipeto: %s\n", s_to_c(f));
- fd = create(s_to_c(f), OWRITE, 0775);
- if(fd < 0)
- return -1;
- d = dirfstat(fd);
- if(d == nil){
- fprint(fd, "Couldn't stat %s: %r\n", s_to_c(f));
- return -1;
+ writeaddr(alfile, "# mlmgr a flag", 0, listname);
+ writeaddr(alfile, addr, 0, listname);
}
- d->mode |= 0775;
- if(dirfwstat(fd, d) < 0)
- fprint(fd, "Couldn't wstat %s: %r\n", s_to_c(f));
- free(d);
-
- fprint(fd, "#!/bin/rc\n");
- if(owner)
- fprint(fd, "/bin/upas/mlowner %s %s\n", alfile, listname);
- else
- fprint(fd, "/bin/upas/ml %s %s\n", alfile, user);
- close(fd);
-
- return 0;
+ exits(0);
}
--- a/sys/src/cmd/upas/ml/mlowner.c
+++ b/sys/src/cmd/upas/ml/mlowner.c
@@ -22,6 +22,8 @@
char *listname;
ARGBEGIN{
+ default:
+ usage();
}ARGEND;
rfork(RFENVG|RFREND);
--- a/sys/src/cmd/upas/ned/mkfile
+++ b/sys/src/cmd/upas/ned/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=nedmail
@@ -7,8 +8,6 @@
HFILES= ../common/common.h\
OFILES=nedmail.$O
-
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
--- a/sys/src/cmd/upas/ned/nedmail.c
+++ b/sys/src/cmd/upas/ned/nedmail.c
@@ -1,22 +1,21 @@
#include "common.h"
#include <ctype.h>
#include <plumb.h>
+#include <regexp.h>
-typedef struct Message Message;
-typedef struct Ctype Ctype;
typedef struct Cmd Cmd;
+typedef struct Ctype Ctype;
+typedef struct Dirstats Dirstats;
+typedef struct Message Message;
+typedef Message* (Mfn)(Cmd*,Message*);
-char root[Pathlen];
-char mbname[Elemlen];
-int rootlen;
-int didopen;
-char *user;
-char wd[2048];
-String *mbpath;
-int natural;
-int doflush;
+enum{
+ /* nflags */
+ Nmissing = 1<<0,
+ Nnoflags = 1<<1,
-int interrupted;
+ Narg = 32,
+};
struct Message {
Message *next;
@@ -24,11 +23,11 @@
Message *cmd;
Message *child;
Message *parent;
- String *path;
+ char *path;
int id;
int len;
- int fileno; // number of directory
- String *info;
+ int fileno; /* number of directory */
+ char *info;
char *from;
char *to;
char *cc;
@@ -38,28 +37,34 @@
char *type;
char *disposition;
char *filename;
- char deleted;
- char stored;
+ uchar flags;
+ uchar nflags;
};
+#pragma varargck type "D" Message*
-Message top;
+enum{
+ Display = 1<<0,
+ Rechk = 1<<1, /* always use file to check content type */
+};
struct Ctype {
char *type;
char *ext;
- int display;
+ uchar flag;
char *plumbdest;
Ctype *next;
};
+/* first element is the default return value */
Ctype ctype[] = {
- { "text/plain", "txt", 1, 0 },
- { "text/html", "htm", 1, 0 },
- { "text/html", "html", 1, 0 },
- { "text/tab-separated-values", "tsv", 1, 0 },
- { "text/richtext", "rtx", 1, 0 },
- { "text/rtf", "rtf", 1, 0 },
- { "text", "txt", 1, 0 },
+ { "application/octet-stream", "bin", Rechk, 0, 0, },
+ { "text/plain", "txt", Display, 0 },
+ { "text/html", "htm", Display, 0 },
+ { "text/html", "html", Display, 0 },
+ { "text/tab-separated-values", "tsv", Display, 0 },
+ { "text/richtext", "rtx", Display, 0 },
+ { "text/rtf", "rtf", Display, 0 },
+ { "text", "txt", Display, 0 },
{ "message/rfc822", "msg", 0, 0 },
{ "image/bmp", "bmp", 0, "image" },
{ "image/jpg", "jpg", 0, "image" },
@@ -66,123 +71,247 @@
{ "image/jpeg", "jpg", 0, "image" },
{ "image/gif", "gif", 0, "image" },
{ "image/png", "png", 0, "image" },
+ { "image/x-png", "png", 0, "image" },
+ { "image/tiff", "tif", 0, "image" },
{ "application/pdf", "pdf", 0, "postscript" },
- { "application/postscript", "ps", 0, "postscript" },
- { "application/", 0, 0, 0 },
+ { "application/postscript", "ps", 0, "postscript" },
+ { "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", 0, "docx" },
+ { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", 0, "xlsx" },
+ { "application/", 0, 0, 0 },
{ "image/", 0, 0, 0 },
{ "multipart/", "mul", 0, 0 },
};
-Message* acmd(Cmd*, Message*);
-Message* bcmd(Cmd*, Message*);
-Message* dcmd(Cmd*, Message*);
-Message* eqcmd(Cmd*, Message*);
-Message* hcmd(Cmd*, Message*);
-Message* Hcmd(Cmd*, Message*);
-Message* helpcmd(Cmd*, Message*);
-Message* icmd(Cmd*, Message*);
-Message* pcmd(Cmd*, Message*);
-Message* qcmd(Cmd*, Message*);
-Message* rcmd(Cmd*, Message*);
-Message* scmd(Cmd*, Message*);
-Message* ucmd(Cmd*, Message*);
-Message* wcmd(Cmd*, Message*);
-Message* xcmd(Cmd*, Message*);
-Message* ycmd(Cmd*, Message*);
-Message* pipecmd(Cmd*, Message*);
-Message* rpipecmd(Cmd*, Message*);
-Message* bangcmd(Cmd*, Message*);
-Message* Pcmd(Cmd*, Message*);
-Message* mcmd(Cmd*, Message*);
-Message* fcmd(Cmd*, Message*);
-Message* quotecmd(Cmd*, Message*);
+struct Dirstats {
+ int new;
+ int del;
+ int old;
+ int unread;
+};
+Mfn acmd;
+Mfn bangcmd;
+Mfn bcmd;
+Mfn dcmd;
+Mfn eqcmd;
+Mfn Fcmd;
+Mfn fcmd;
+Mfn fqcmd;
+Mfn Hcmd;
+Mfn hcmd;
+Mfn helpcmd;
+Mfn icmd;
+Mfn Kcmd;
+Mfn kcmd;
+Mfn mbcmd;
+Mfn mcmd;
+Mfn Pcmd;
+Mfn pcmd;
+Mfn pipecmd;
+Mfn qcmd;
+Mfn quotecmd;
+Mfn rcmd;
+Mfn rpipecmd;
+Mfn scmd;
+Mfn tcmd;
+Mfn ucmd;
+Mfn wcmd;
+Mfn xcmd;
+Mfn ycmd;
+
struct {
- char *cmd;
- int args;
- Message* (*f)(Cmd*, Message*);
- char *help;
+ char *cmd;
+ int args;
+ int addr;
+ Mfn *f;
+ char *help;
} cmdtab[] = {
- { "a", 1, acmd, "a reply to sender and recipients" },
- { "A", 1, acmd, "A reply to sender and recipients with copy" },
- { "b", 0, bcmd, "b print the next 10 headers" },
- { "d", 0, dcmd, "d mark for deletion" },
- { "f", 0, fcmd, "f file message by from address" },
- { "h", 0, hcmd, "h print elided message summary (,h for all)" },
- { "help", 0, helpcmd, "help print this info" },
- { "H", 0, Hcmd, "H print message's MIME structure " },
- { "i", 0, icmd, "i incorporate new mail" },
- { "m", 1, mcmd, "m addr forward mail" },
- { "M", 1, mcmd, "M addr forward mail with message" },
- { "p", 0, pcmd, "p print the processed message" },
- { "P", 0, Pcmd, "P print the raw message" },
- { "\"", 0, quotecmd, "\" print a quoted version of msg" },
- { "q", 0, qcmd, "q exit and remove all deleted mail" },
- { "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" },
- { "rf", 1, rcmd, "rf [addr]file message and reply" },
- { "R", 1, rcmd, "R [addr] reply including copy of message" },
- { "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" },
- { "s", 1, scmd, "s file append raw message to file" },
- { "u", 0, ucmd, "u remove deletion mark" },
- { "w", 1, wcmd, "w file store message contents as file" },
- { "x", 0, xcmd, "x exit without flushing deleted messages" },
- { "y", 0, ycmd, "y synchronize with mail box" },
- { "=", 1, eqcmd, "= print current message number" },
- { "|", 1, pipecmd, "|cmd pipe message body to a command" },
- { "||", 1, rpipecmd, "||cmd pipe raw message to a command" },
- { "!", 1, bangcmd, "!cmd run a command" },
- { nil, 0, nil, nil },
+ { "a", 1, 1, acmd, "a\t" "reply to sender and recipients" },
+ { "A", 1, 0, acmd, "A\t" "reply to sender and recipients with copy" },
+ { "b", 0, 0, bcmd, "b\t" "print the next 10 headers" },
+ { "d", 0, 1, dcmd, "d\t" "mark for deletion" },
+ { "F", 1, 1, Fcmd, "f\t" "set message flags [+-][aDdfrSs]" },
+ { "f", 0, 1, fcmd, "f\t" "file message by from address" },
+ { "fq", 0, 1, fqcmd, "fq\t" "print mailbox f appends" },
+ { "H", 0, 0, Hcmd, "H\t" "print message's MIME structure" },
+ { "h", 0, 0, hcmd, "h\t" "print message summary (,h for all)" },
+ { "help", 0, 0, helpcmd, "help\t" "print this info" },
+ { "i", 0, 0, icmd, "i\t" "incorporate new mail" },
+ { "k", 1, 1, kcmd, "k [flags]\t" "mark mail" },
+ { "K", 1, 1, Kcmd, "K [flags]\t" "unmark mail" },
+ { "m", 1, 1, mcmd, "m addr\t" "forward mail" },
+ { "M", 1, 0, mcmd, "M addr\t" "forward mail with message" },
+ { "mb", 1, 0, mbcmd, "mb mbox\t" "switch mailboxes" },
+ { "p", 1, 0, pcmd, "p\t" "print the processed message" },
+ { "P", 0, 0, Pcmd, "P\t" "print the raw message" },
+ { "\"", 0, 0, quotecmd, "\"\t" "print a quoted version of msg" },
+ { "\"\"", 0, 0, quotecmd, "\"\"\t" "format and quote message" },
+ { "q", 0, 0, qcmd, "q\t" "exit and remove all deleted mail" },
+ { "r", 1, 1, rcmd, "r [addr]\t" "reply to sender plus any addrs specified" },
+ { "rf", 1, 0, rcmd, "rf [addr]\t" "file message and reply" },
+ { "R", 1, 0, rcmd, "R [addr]\t" "reply including copy of message" },
+ { "Rf", 1, 0, rcmd, "Rf [addr]\t" "file message and reply with copy" },
+ { "s", 1, 1, scmd, "s file\t" "append raw message to file" },
+ { "t", 1, 0, tcmd, "t\t" "text formatter" },
+ { "u", 0, 0, ucmd, "u\t" "remove deletion mark" },
+ { "w", 1, 1, wcmd, "w file\t" "store message contents as file" },
+ { "x", 0, 0, xcmd, "x\t" "exit without flushing deleted messages" },
+ { "y", 0, 0, ycmd, "y\t" "synchronize with mail box" },
+ { "=", 1, 0, eqcmd, "=\t" "print current message number" },
+ { "|", 1, 1, pipecmd, "|cmd\t" "pipe message body to a command" },
+ { "||", 1, 1, rpipecmd, "||cmd\t" "pipe raw message to a command" },
+ { "!", 1, 0, bangcmd, "!cmd\t" "run a command" },
};
-enum
-{
- NARG= 32,
-};
-
struct Cmd {
Message *msgs;
- Message *(*f)(Cmd*, Message*);
+ Mfn *f;
int an;
- char *av[NARG];
+ char *av[Narg];
+ char cmdline[2*1024];
int delete;
};
-Biobuf out;
-int startedfs;
-int reverse;
-int longestfrom = 12;
-
-String* file2string(String*, char*);
-int dir2message(Message*, int);
-int filelen(String*, char*);
-String* extendpath(String*, char*);
-void snprintheader(char*, int, Message*);
-void cracktime(char*, char*, int);
-int cistrncmp(char*, char*, int);
-int cistrcmp(char*, char*);
-Reprog* parsesearch(char**);
-char* parseaddr(char**, Message*, Message*, Message*, Message**);
+int dir2message(Message*, int, Dirstats*);
+int mdir2message(Message*);
+char* extendp(char*, char*);
char* parsecmd(char*, Cmd*, Message*, Message*);
-char* readline(char*, char*, int);
-void messagecount(Message*);
void system(char*, char**, int);
-void mkid(String*, Message*);
-int switchmb(char*, char*);
+int switchmb(char *, int);
void closemb(void);
-int lineize(char*, char**, int);
-int rawsearch(Message*, Reprog*);
Message* dosingleton(Message*, char*);
-String* rooted(String*);
+char* rooted(char*);
int plumb(Message*, Ctype*);
-String* addrecolon(char*);
void exitfs(char*);
Message* flushdeleted(Message*);
+int didopen;
+int doflush;
+int interrupted;
+int longestfrom = 12;
+int longestto = 12;
+int hcmdfmt;
+Qid mbqid;
+int mbvers;
+char mbname[Pathlen];
+char mbpath[Pathlen];
+int natural;
+Biobuf out;
+int reverse;
+char root[Pathlen];
+int rootlen;
+int startedfs;
+Message top;
+char *user;
+char homewd[Pathlen];
+char wd[Pathlen];
+char textfmt[Pathlen];
+
+char*
+idfmt(char *p, char *e, Message *m)
+{
+ char buf[32];
+ int sz, l;
+
+ for(; (sz = e - p) > 0; ){
+ l = snprint(buf, sizeof buf, "%d", m->id);
+ if(l + 1 > sz)
+ return "*GOK*";
+ e -= l;
+ memcpy(e, buf, l);
+ if((m = m->parent) == &top)
+ break;
+ e--;
+ *e = '.';
+ }
+ return e;
+}
+
+int
+eprint(char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ Bflush(&out);
+
+ va_start(args, fmt);
+ n = vfprint(2, fmt, args);
+ va_end(args);
+ return n;
+}
+
void
+dissappeared(void)
+{
+ char buf[ERRMAX];
+
+ rerrstr(buf, sizeof buf);
+ if(strstr(buf, "hungup channel")){
+ eprint("\n!she's dead, jim\n");
+ exits(buf);
+ }
+ eprint("!message dissappeared\n");
+}
+
+int
+Dfmt(Fmt *f)
+{
+ char *e, buf[128];
+ Message *m;
+
+ m = va_arg(f->args, Message*);
+ if(m == nil)
+ return fmtstrcpy(f, "*GOK*");
+ if(m == &top)
+ return 0;
+ e = buf + sizeof buf - 1;
+ *e = 0;
+ return fmtstrcpy(f, idfmt(buf, e, m));
+}
+
+char*
+readline(char *prompt, char *line, int len)
+{
+ char *p, *e, *q;
+ int n, dump;
+
+ e = line + len;
+retry:
+ dump = 0;
+ interrupted = 0;
+ eprint("%s", prompt);
+ for(p = line;; p += n){
+ if(p == e){
+ dump = 1;
+ p = line;
+ }
+ n = read(0, p, e - p);
+ if(n < 0){
+ if(interrupted)
+ goto retry;
+ return nil;
+ }
+ if(n == 0)
+ return nil;
+ if(q = memchr(p, '\n', n)){
+ if(dump){
+ eprint("!line too long\n");
+ goto retry;
+ }
+ p = q;
+ break;
+ }
+ }
+ *p = 0;
+ return line;
+}
+
+void
usage(void)
{
- fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
+ fprint(2, "usage: %s [-nrt] [-f mailfile] [-s mailfile]\n", argv0);
fprint(2, " %s -c dir\n", argv0);
exits("usage");
}
@@ -197,32 +326,27 @@
noted(NDFLT);
}
-char *
+char*
plural(int n)
{
if (n == 1)
return "";
-
- return "s";
+ return "s";
}
void
main(int argc, char **argv)
{
- Message *cur, *m, *x;
- char cmdline[4*1024];
+ char cmdline[2*1024], prompt[64], *err, *av[4], *mb;
+ int n, cflag, singleton;
Cmd cmd;
Ctype *cp;
- char *err;
- int n, cflag;
- char *av[4];
- String *prompt;
- char *file, *singleton;
+ Message *cur, *m, *x;
Binit(&out, 1, OWRITE);
- file = nil;
- singleton = nil;
+ mb = nil;
+ singleton = 0;
reverse = 1;
cflag = 0;
ARGBEGIN {
@@ -229,12 +353,11 @@
case 'c':
cflag = 1;
break;
+ case 's':
+ singleton = 1;
case 'f':
- file = EARGF(usage());
+ mb = EARGF(usage());
break;
- case 's':
- singleton = EARGF(usage());
- break;
case 'r':
reverse = 0;
break;
@@ -242,11 +365,18 @@
natural = 1;
reverse = 0;
break;
+ case 't':
+ hcmdfmt = 1;
+ break;
default:
usage();
break;
} ARGEND;
+ fmtinstall('D', Dfmt);
+ quotefmtinstall();
+ doquote = needsrcquote;
+ getwd(homewd, sizeof homewd);
user = getlog();
if(user == nil || *user == 0)
sysfatal("can't read user name");
@@ -253,10 +383,10 @@
if(cflag){
if(argc > 0)
- creatembox(user, argv[0]);
+ n = creatembox(user, argv[0]);
else
- creatembox(user, nil);
- exits(0);
+ n = creatembox(user, nil);
+ exits(n? 0: "fail");
}
if(argc)
@@ -270,57 +400,46 @@
system("/bin/upas/fs", av, -1);
}
- switchmb(file, singleton);
+ switchmb(mb, singleton);
+ top.path = strdup(root);
+ for(cp = ctype; cp < ctype + nelem(ctype) - 1; cp++)
+ cp->next = cp + 1;
- top.path = s_copy(root);
-
- for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
- cp->next = cp+1;
-
- if(singleton != nil){
- cur = dosingleton(&top, singleton);
+ if(singleton){
+ cur = dosingleton(&top, mb);
if(cur == nil){
- Bprint(&out, "no message\n");
+ eprint("no message\n");
exitfs(0);
}
pcmd(nil, cur);
} else {
cur = ⊤
- n = dir2message(&top, reverse);
- if(n < 0)
- sysfatal("can't read %s", s_to_c(top.path));
- Bprint(&out, "%d message%s\n", n, plural(n));
+ if(icmd(nil, cur) == nil)
+ sysfatal("can't read %s", top.path);
}
-
notify(catchnote);
- prompt = s_new();
for(;;){
- s_reset(prompt);
- if(cur == &top)
- s_append(prompt, ": ");
- else {
- mkid(prompt, cur);
- s_append(prompt, ": ");
- }
+ snprint(prompt, sizeof prompt, "%D: ", cur);
- // leave space at the end of cmd line in case parsecmd needs to
- // add a space after a '|' or '!'
- if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
+ /*
+ * leave space at the end of cmd line in case parsecmd needs to
+ * add a space after a '|' or '!'
+ */
+ if(readline(prompt, cmdline, sizeof cmdline - 1) == nil)
break;
err = parsecmd(cmdline, &cmd, top.child, cur);
if(err != nil){
- Bprint(&out, "!%s\n", err);
+ eprint("!%s\n", err);
continue;
}
- if(singleton != nil && cmd.f == icmd){
- Bprint(&out, "!illegal command\n");
+ if(singleton && (cmd.f == icmd || cmd.f == ycmd)){
+ eprint("!illegal command\n");
continue;
}
interrupted = 0;
if(cmd.msgs == nil || cmd.msgs == &top){
- x = (*cmd.f)(&cmd, &top);
- if(x != nil)
+ if(x = cmd.f(&cmd, &top))
cur = x;
} else for(m = cmd.msgs; m != nil; m = m->cmd){
x = m;
@@ -327,13 +446,15 @@
if(cmd.delete){
dcmd(&cmd, x);
- // dp acts differently than all other commands
- // since its an old lesk idiom that people love.
- // it deletes the current message, moves the current
- // pointer ahead one and prints.
+ /*
+ * dp acts differently than all other commands
+ * since its an old lesk idiom that people love.
+ * it deletes the current message, moves the current
+ * pointer ahead one and prints.
+ */
if(cmd.f == pcmd){
if(x->next == nil){
- Bprint(&out, "!address\n");
+ eprint("!address\n");
cur = x;
break;
} else
@@ -340,12 +461,12 @@
x = x->next;
}
}
- x = (*cmd.f)(&cmd, x);
+ x = cmd.f(&cmd, x);
if(x != nil)
cur = x;
if(interrupted)
break;
- if(singleton != nil && (cmd.delete || cmd.f == dcmd))
+ if(singleton && (cmd.delete || cmd.f == dcmd))
qcmd(nil, nil);
}
if(doflush)
@@ -354,23 +475,76 @@
qcmd(nil, nil);
}
-//
-// read the message info
-//
+char*
+file2string(char *dir, char *file)
+{
+ int fd, n;
+ char *s, *p, *e;
+
+ p = s = malloc(512);
+ e = p + 511;
+
+ fd = open(extendp(dir, file), OREAD);
+ while((n = read(fd, p, e - p)) > 0){
+ p += n;
+ if(p == e){
+ s = realloc(s, (n = p - s) + 512 + 1);
+ if(s == nil)
+ sysfatal("malloc: %r");
+ p = s + n;
+ e = p + 512;
+ }
+ }
+ close(fd);
+ *p = 0;
+ return s;
+}
+
+#define Fields 18 /* terrible hack; worth 10% */
+#define Minfields 17
+
+void
+updateinfo(Message *m)
+{
+ char *s, *f[Fields + 1];
+ int i, n, sticky;
+
+ s = file2string(m->path, "info");
+ if(s == nil)
+ return;
+ if((n = getfields(s, f, nelem(f), 0, "\n")) < Minfields){
+ for(i = 0; i < n; i++)
+ fprint(2, "info: %s\n", f[i]);
+ sysfatal("info file invalid %s %D: %d fields", m->path, m, n);
+ }
+ if((m->nflags & Nnoflags) == 0){
+ sticky = m->flags & Fdeleted;
+ m->flags = buftoflags(f[17]) | sticky;
+ }
+ m->nflags &= ~Nmissing;
+ free(s);
+}
+
Message*
file2message(Message *parent, char *name)
{
+ char *path, *f[Fields + 1];
+ int i, n;
Message *m;
- String *path;
- char *f[10];
- m = mallocz(sizeof(Message), 1);
+ m = mallocz(sizeof *m, 1);
if(m == nil)
return nil;
- m->path = path = extendpath(parent->path, name);
+ m->path = path = strdup(extendp(parent->path, name));
m->fileno = atoi(name);
m->info = file2string(path, "info");
- lineize(s_to_c(m->info), f, nelem(f));
+ m->parent = parent;
+ n = getfields(m->info, f, nelem(f), 0, "\n");
+ if(n < Minfields){
+ for(i = 0; i < n; i++)
+ fprint(2, "info: [%s]\n", f[i]);
+ sysfatal("info file invalid %s %D: %d fields", path, m, n);
+ }
m->from = f[0];
m->to = f[1];
m->cc = f[2];
@@ -380,11 +554,15 @@
m->type = f[6];
m->disposition = f[7];
m->filename = f[8];
- m->len = filelen(path, "raw");
- if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
- dir2message(m, 0);
- m->parent = parent;
-
+ m->len = strtoul(f[16], 0, 0);
+ if(n > 17)
+ m->flags = buftoflags(f[17]);
+ else
+ m->nflags |= Nnoflags;
+
+ if(m->type)
+ if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
+ mdir2message(m);
return m;
}
@@ -397,26 +575,27 @@
next = nm->next;
freemessage(nm);
}
- s_free(m->path);
- s_free(m->info);
+ free(m->path);
+ free(m->info);
free(m);
}
-//
-// read a directory into a list of messages
-//
+/*
+ * read a directory into a list of messages. at the top level, there may be
+ * large gaps in message numbers. so we need to read the whole directory.
+ * and pick out the messages we're interested in. within a message, subparts
+ * are contiguous and if we don't read the header/body/rawbody, we can avoid forcing
+ * upas/fs to read the whole message.
+ */
int
-dir2message(Message *parent, int reverse)
+mdir2message(Message *parent)
{
- int i, n, fd, highest, newmsgs;
+ char buf[Pathlen];
+ int i, highest, newmsgs;
Dir *d;
Message *first, *last, *m;
- fd = open(s_to_c(parent->path), OREAD);
- if(fd < 0)
- return -1;
-
- // count current entries
+ /* count current entries */
first = parent->child;
highest = newmsgs = 0;
for(last = parent->child; last != nil && last->next != nil; last = last->next)
@@ -425,51 +604,159 @@
if(last != nil)
if(last->fileno > highest)
highest = last->fileno;
-
- n = dirreadall(fd, &d);
- for(i = 0; i < n; i++){
- if((d[i].qid.type & QTDIR) == 0)
+ for(i = highest + 1;; i++){
+ snprint(buf, sizeof buf, "%s/%d", parent->path, i);
+ if((d = dirstat(buf)) == nil)
+ break;
+ if((d->qid.type & QTDIR) == 0){
+ free(d);
continue;
- if(atoi(d[i].name) <= highest)
- continue;
- m = file2message(parent, d[i].name);
+ }
+ free(d);
+ snprint(buf, sizeof buf, "%d", i);
+ m = file2message(parent, buf);
if(m == nil)
break;
+ m->id = m->fileno;
newmsgs++;
- if(reverse){
- m->next = first;
- if(first != nil)
- first->prev = m;
+ if(first == nil)
first = m;
- } else {
- if(first == nil)
+ else
+ last->next = m;
+ m->prev = last;
+ last = m;
+ }
+ parent->child = first;
+ return newmsgs;
+}
+
+/*
+ * 99.9% of the time, we don't need to sort this list.
+ * however, sometimes email is added to a mailbox
+ * out of order. or, sape copies it back in from the
+ * dump. in this case, we've got to sort.
+ *
+ * BOTCH. we're not observing any sort of stable
+ * order. if an old message comes in while upas/fs
+ * is running, it will appear out of order. restarting
+ * upas/fs will reorder things.
+ */
+int
+dcmp(Dir *a, Dir *b)
+{
+ return atoi(a->name) - atoi(b->name);
+}
+
+void
+itsallsapesfault(Dir *d, int n)
+{
+ int c, i, r, t;
+
+ /* evade qsort suck */
+ r = -1;
+ for(i = 0; i < n; i++){
+ t = atol(d[i].name);
+ if(t > r){
+ c = d[i].name[0];
+ if(c >= '0' && c <= 9)
+ break;
+ }
+ r = t;
+ }
+ if(i != n)
+ qsort(d, n, sizeof *d, (int (*)(void*, void*))dcmp);
+}
+
+int
+dir2message(Message *parent, int reverse, Dirstats *s)
+{
+ int i, c, n, fd;
+ Dir *d;
+ Message *first, *last, *m, **ll;
+
+ memset(s, 0, sizeof *s);
+ fd = open(parent->path, OREAD);
+ if(fd < 0)
+ return -1;
+ first = parent->child;
+ last = nil;
+ if(first)
+ for(last = first; last->next; last = last->next)
+ ;
+ n = dirreadall(fd, &d);
+ itsallsapesfault(d, n);
+ if(reverse)
+ ll = &last;
+ else
+ ll = &parent->child;
+ for(i = 0; *ll || i < n; ){
+ if(i < n && (d[i].qid.type & QTDIR) == 0){
+ i++;
+ continue;
+ }
+ c = -1;
+ if(i >= n)
+ c = 1;
+ else if(*ll)
+ c = atoi(d[i].name) - (*ll)->fileno;
+ if(c < 0){
+ m = file2message(parent, d[i].name);
+ if(m == nil)
+ break;
+ if(reverse){
+ m->next = first;
+ if(first != nil)
+ first->prev = m;
first = m;
- else
- last->next = m;
- m->prev = last;
- last = m;
+ }else{
+ if(first == nil)
+ first = m;
+ else
+ last->next = m;
+ m->prev = last;
+ last = m;
+ }
+ *ll = m;
+ s->new++;
+ s->unread += (m->flags & Fseen) == 0;
+ i++;
+ }else if(c > 0){
+ (*ll)->nflags |= Nmissing;
+ s->del++;
+ }else{
+ updateinfo(*ll);
+ s->old++;
+ i++;
}
+
+ if(reverse)
+ ll = &(*ll)->prev;
+ else
+ ll = &(*ll)->next;
}
free(d);
close(fd);
parent->child = first;
- // renumber and file longest from
+ /* renumber and file longest from */
i = 1;
longestfrom = 12;
+ longestto = 12;
for(m = first; m != nil; m = m->next){
m->id = natural ? m->fileno : i++;
n = strlen(m->from);
if(n > longestfrom)
longestfrom = n;
+ n = strlen(m->to);
+ if(n > longestto)
+ longestto = n;
}
-
- return newmsgs;
+ return 0;
}
-//
-// point directly to a message
-//
+/*
+ * point directly to a message
+ */
Message*
dosingleton(Message *parent, char *path)
{
@@ -476,12 +763,12 @@
char *p, *np;
Message *m;
- // walk down to message and read it
+ /* walk down to message and read it */
if(strlen(path) < rootlen)
return nil;
if(path[rootlen] != '/')
return nil;
- p = path+rootlen+1;
+ p = path + rootlen + 1;
np = strchr(p, '/');
if(np != nil)
*np = 0;
@@ -491,14 +778,14 @@
parent->child = m;
m->id = 1;
- // walk down to requested component
+ /* walk down to requested component */
while(np != nil){
*np = '/';
- np = strchr(np+1, '/');
+ np = strchr(np + 1, '/');
if(np != nil)
*np = 0;
for(m = m->child; m != nil; m = m->next)
- if(strcmp(path, s_to_c(m->path)) == 0)
+ if(strcmp(path, m->path) == 0)
return m;
if(m == nil)
return nil;
@@ -506,103 +793,21 @@
return m;
}
-//
-// read a file into a string
-//
-String*
-file2string(String *dir, char *file)
+/*
+ * walk the path name an element
+ */
+char*
+extendp(char *dir, char *name)
{
- String *s;
- int fd, n, m;
+ static char buf[Pathlen];
- s = extendpath(dir, file);
- fd = open(s_to_c(s), OREAD);
- s_grow(s, 512); /* avoid multiple reads on info files */
- s_reset(s);
- if(fd < 0)
- return s;
-
- for(;;){
- n = s->end - s->ptr;
- if(n == 0){
- s_grow(s, 128);
- continue;
- }
- m = read(fd, s->ptr, n);
- if(m <= 0)
- break;
- s->ptr += m;
- if(m < n)
- break;
- }
- s_terminate(s);
- close(fd);
-
- return s;
+ if(strcmp(dir, ".") == 0)
+ snprint(buf, sizeof buf, "%s", name);
+ else
+ snprint(buf, sizeof buf, "%s/%s", dir, name);
+ return buf;
}
-//
-// get the length of a file
-//
-int
-filelen(String *dir, char *file)
-{
- String *path;
- Dir *d;
- int rv;
-
- path = extendpath(dir, file);
- d = dirstat(s_to_c(path));
- if(d == nil){
- s_free(path);
- return -1;
- }
- s_free(path);
- rv = d->length;
- free(d);
- return rv;
-}
-
-//
-// walk the path name an element
-//
-String*
-extendpath(String *dir, char *name)
-{
- String *path;
-
- if(strcmp(s_to_c(dir), ".") == 0)
- path = s_new();
- else {
- path = s_copy(s_to_c(dir));
- s_append(path, "/");
- }
- s_append(path, name);
- return path;
-}
-
-int
-cistrncmp(char *a, char *b, int n)
-{
- while(n-- > 0){
- if(tolower(*a++) != tolower(*b++))
- return -1;
- }
- return 0;
-}
-
-int
-cistrcmp(char *a, char *b)
-{
- for(;;){
- if(tolower(*a) != tolower(*b++))
- return -1;
- if(*a++ == 0)
- break;
- }
- return 0;
-}
-
char*
nosecs(char *t)
{
@@ -611,7 +816,7 @@
p = strchr(t, ':');
if(p == nil)
return t;
- p = strchr(p+1, ':');
+ p = strchr(p + 1, ':');
if(p != nil)
*p = 0;
return t;
@@ -630,33 +835,31 @@
for(i = 0; i < 12; i++)
if(cistrcmp(m, months[i]) == 0)
- return i+1;
+ return i + 1;
return 1;
}
enum
{
- Yearsecs= 365*24*60*60
+ Yearsecs = 365*24*60*60,
};
void
cracktime(char *d, char *out, int len)
{
- char in[64];
- char *f[6];
+ char in[64], *f[6], *dtime;
int n;
- Tm tm;
long now, then;
- char *dtime;
+ Tm tm;
*out = 0;
if(d == nil)
return;
- strncpy(in, d, sizeof(in));
- in[sizeof(in)-1] = 0;
+ strncpy(in, d, sizeof in);
+ in[sizeof in - 1] = 0;
n = getfields(in, f, 6, 1, " \t\r\n");
if(n != 6){
- // unknown style
+ /* unknown style */
snprint(out, 16, "%10.10s", d);
return;
}
@@ -663,7 +866,7 @@
now = time(0);
memset(&tm, 0, sizeof tm);
if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
- // 822 style
+ /* 822 style */
tm.year = atoi(f[3])-1900;
tm.mon = month(f[2]);
tm.mday = atoi(f[1]);
@@ -670,7 +873,7 @@
dtime = nosecs(f[4]);
then = tm2sec(&tm);
} else if(strchr(f[3], ':') != nil){
- // unix style
+ /* unix style */
tm.year = atoi(f[5])-1900;
tm.mon = month(f[1]);
tm.mday = atoi(f[2]);
@@ -685,31 +888,31 @@
if(now - then < Yearsecs/2)
snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
else
- snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
+ snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year + 1900);
}
+int
+matchtype(char *s, Ctype *t)
+{
+ return strncmp(t->type, s, strlen(t->type)) == 0;
+}
+
Ctype*
findctype(Message *m)
{
- char *p;
- char ftype[128];
+ char *p, ftype[256];
int n, pfd[2];
Ctype *a, *cp;
- static Ctype nulltype = { "", 0, 0, 0 };
- static Ctype bintype = { "application/octet-stream", "bin", 0, 0 };
for(cp = ctype; cp; cp = cp->next)
- if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
- return cp;
+ if(matchtype(m->type, cp))
+ if((cp->flag & Rechk) == 0)
+ return cp;
+ else
+ break;
-/* use file(1) for any unknown mimetypes
- *
- * if (strcmp(m->type, bintype.type) != 0)
- * return &nulltype;
- */
if(pipe(pfd) < 0)
- return &bintype;
-
+ return ctype;
*ftype = 0;
switch(fork()){
case -1:
@@ -720,112 +923,102 @@
dup(pfd[0], 0);
close(1);
dup(pfd[0], 1);
- execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
+ execl("/bin/file", "file", "-m", extendp(m->path, "body"), nil);
exits(0);
default:
close(pfd[0]);
- n = read(pfd[1], ftype, sizeof(ftype));
- if(n > 0)
- ftype[n] = 0;
+ n = read(pfd[1], ftype, sizeof ftype - 1);
+ while(n > 0 && isspace(ftype[n - 1]))
+ n--;
+ ftype[n] = 0;
close(pfd[1]);
waitpid();
break;
}
-
- if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
- return &bintype;
+ for(cp = ctype; cp; cp = cp->next)
+ if(matchtype(ftype, cp))
+ return cp;
+ if(*ftype == 0 || (p = strchr(ftype, '/')) == nil)
+ return ctype;
*p++ = 0;
- a = mallocz(sizeof(Ctype), 1);
+ a = mallocz(sizeof *a, 1);
a->type = strdup(ftype);
a->ext = strdup(p);
- a->display = 0;
+ a->flag = 0;
a->plumbdest = strdup(ftype);
for(cp = ctype; cp->next; cp = cp->next)
- continue;
+ ;
cp->next = a;
a->next = nil;
return a;
}
+/*
+ * traditional
+ */
void
-mkid(String *s, Message *m)
+hds(char *buf, Message *m)
{
- char buf[32];
-
- if(m->parent != &top){
- mkid(s, m->parent);
- s_append(s, ".");
- }
- sprint(buf, "%d", m->id);
- s_append(s, buf);
+ buf[0] = m->child? 'H': ' ';
+ buf[1] = m->flags & Fdeleted ? 'd' : ' ';
+ buf[2] = m->flags & Fstored? 's': ' ';
+ buf[3] = m->flags & Fseen? ' ': '*';
+ if(m->flags & Fanswered)
+ buf[3] = 'a';
+ if(m->flags & Fflagged)
+ buf[3] = '\'';
+ buf[4] = 0;
}
void
-snprintheader(char *buf, int len, Message *m)
+pheader0(char *buf, int len, Message *m)
{
- char timebuf[32];
- String *id;
- char *p, *q;
+ char *f, *p, *q, frombuf[40], timebuf[32], h[5];
+ int max;
- // create id
- id = s_new();
- mkid(id, m);
+ hds(h, m);
+ if(hcmdfmt == 0){
+ f = m->from;
+ max = longestfrom;
+ }else{
+ snprint(frombuf, sizeof frombuf-5, "%s", m->to);
+ p = strchr(frombuf, ' ');
+ if(p != nil)
+ snprint(p, 5, " ...");
+ f = frombuf;
+ max = longestto;
+ if(max > sizeof frombuf)
+ max = sizeof frombuf;
+ }
- if(*m->from == 0){
- // no from
- snprint(buf, len, "%-3s %s %6d %s",
- s_to_c(id),
- m->type,
- m->len,
- m->filename);
- } else if(*m->subject){
+ if(*f == 0)
+ snprint(buf, len, "%3D %s %6d %s",
+ m, m->type, m->len, m->filename);
+ else if(*m->subject){
q = p = strdup(m->subject);
while(*p == ' ')
p++;
if(strlen(p) > 50)
p[50] = 0;
- cracktime(m->date, timebuf, sizeof(timebuf));
- snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s",
- s_to_c(id),
- m->child ? 'H' : ' ',
- m->deleted ? 'd' : ' ',
- m->stored ? 's' : ' ',
- m->len,
- timebuf,
- longestfrom, longestfrom, m->from,
- p);
+ cracktime(m->date, timebuf, sizeof timebuf);
+ snprint(buf, len, "%3D %s %6d %11.11s %-*.*s %s",
+ m, h, m->len, timebuf, max, max, f, p);
free(q);
} else {
- cracktime(m->date, timebuf, sizeof(timebuf));
- snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s",
- s_to_c(id),
- m->child ? 'H' : ' ',
- m->deleted ? 'd' : ' ',
- m->stored ? 's' : ' ',
- m->len,
- timebuf,
- m->from);
+ cracktime(m->date, timebuf, sizeof timebuf);
+ snprint(buf, len, "%3D %s %6d %11.11s %s",
+ m, h, m->len, timebuf, f);
}
- s_free(id);
}
-char *spaces = " ";
-
void
-snprintHeader(char *buf, int len, int indent, Message *m)
+pheader(char *buf, int len, int indent, Message *m)
{
- String *id;
- char typeid[64];
- char *p, *e;
+ char *p, *e, typeid[80];
- // create id
- id = s_new();
- mkid(id, m);
-
e = buf + len;
-
- snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
+ snprint(typeid, sizeof typeid, "%D %s", m, m->type);
if(indent < 6)
p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
else
@@ -836,32 +1029,55 @@
p = seprint(p, e, "(from,%s)", m->from);
if(m->subject && *m->subject)
seprint(p, e, "(subj,%s)", m->subject);
-
- s_free(id);
}
char sstring[256];
-// cmd := range cmd ' ' arg-list ;
-// range := address
-// | address ',' address
-// | 'g' search ;
-// address := msgno
-// | search ;
-// msgno := number
-// | number '/' msgno ;
-// search := '/' string '/'
-// | '%' string '%' ;
-//
+/*
+ * cmd := range cmd ' ' arg-list ;
+ * range := address
+ * | address ',' address
+ * | 'g' search ;
+ * address := msgno
+ * | search ;
+ * msgno := number
+ * | number '/' msgno ;
+ * search := '/' string '/'
+ * | '%' string '%'
+ * | '#' (field '#')? re '#'
+ *
+ */
+static char*
+qstrchr(char *s, int c)
+{
+ for(;; s++){
+ if(*s == '\\')
+ s++;
+ else if(*s == c)
+ return s;
+ if(*s == 0)
+ return nil;
+ }
+}
+
Reprog*
-parsesearch(char **pp)
+parsesearch(char **pp, char *buf, int bufl)
{
- char *p, *np;
+ char *p, *np, *e;
int c, n;
+ buf[0] = 0;
p = *pp;
c = *p++;
- np = strchr(p, c);
+ if(c == '#')
+ snprint(buf, bufl, "from");
+ np = qstrchr(p, c);
+ if(c == '#' && np)
+ if(e = qstrchr(np + 1, c)){
+ snprint(buf, bufl, "%.*s", (int)(np - p), p);
+ p = np + 1;
+ np = e;
+ }
if(np != nil){
*np++ = 0;
*pp = np;
@@ -872,57 +1088,94 @@
if(*p == 0)
p = sstring;
else{
- strncpy(sstring, p, sizeof(sstring));
- sstring[sizeof(sstring)-1] = 0;
+ strncpy(sstring, p, sizeof sstring);
+ sstring[sizeof sstring - 1] = 0;
}
return regcomp(p);
}
-static char *
-num2msg(Message **mp, int sign, int n, Message *first, Message *cur)
+enum{
+ Comma = 1,
+};
+
+/*
+ * search a message for a regular expression match
+ */
+int
+fsearch(Message *m, Reprog *prog, char *field)
{
- Message *m;
+ char buf[4096 + 1];
+ int i, fd, rv;
+ uvlong o;
- m = nil;
- switch(sign){
- case 0:
- for(m = first; m != nil; m = m->next)
- if(m->id == n)
- break;
- break;
- case -1:
- if(cur != &top)
- for(m = cur; m != nil && n > 0; n--)
- m = m->prev;
- break;
- case 1:
- if(cur == &top){
- n--;
- cur = first;
+ rv = 0;
+ fd = open(extendp(m->path, field), OREAD);
+ /*
+ * march through raw message 4096 bytes at a time
+ * with a 128 byte overlap to chain the re search.
+ */
+ for(o = 0;; o += i - 128){
+ i = pread(fd, buf, sizeof buf - 1, o);
+ if(i <= 0)
+ break;
+ buf[i] = 0;
+ if(regexec(prog, buf, nil, 0)){
+ rv = 1;
+ break;
}
- for(m = cur; m != nil && n > 0; n--)
- m = m->next;
- break;
+ if(i < sizeof buf - 1)
+ break;
}
- if(m == nil)
- return "address";
- *mp = m;
- return nil;
+ close(fd);
+ return rv;
}
+int
+rsearch(Message *m, Reprog *prog, char*)
+{
+ return fsearch(m, prog, "raw");
+}
+
+int
+hsearch(Message *m, Reprog *prog, char*)
+{
+ char buf[256];
+
+ pheader0(buf, sizeof buf, m);
+ return regexec(prog, buf, nil, 0);
+}
+
+/*
+ * ack: returns int (*)(Message*, Reprog*, char*)
+ */
+int (*
+chartosearch(int c))(Message*, Reprog*, char*)
+{
+ switch(c){
+ case '%':
+ return rsearch;
+ case '/':
+ case '?':
+ return hsearch;
+ case '#':
+ return fsearch;
+ }
+ return 0;
+}
+
char*
-parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
+parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp, int f)
{
- int n;
- Message *m;
- char *p, *err;
+ char *p, buf[256];
+ int n, c, sign;
+ Message *m, *m0;
Reprog *prog;
- int c, sign;
- char buf[256];
+ int (*fn)(Message*, Reprog*, char*);
*mp = nil;
p = *pp;
+ sign = 0;
if(*p == '+'){
sign = 1;
p++;
@@ -931,18 +1184,6 @@
sign = -1;
p++;
*pp = p;
- } else
- sign = 0;
-
- /*
- * TODO: verify & install this.
- * make + and - mean +1 and -1, as in ed. then -,.d won't
- * delete all messages up to the current one. - geoff
- */
- if(sign && (!isascii(*p) || !isdigit(*p))) {
- err = num2msg(mp, sign, 1, first, cur);
- if (err != nil)
- return err;
}
switch(*p){
@@ -952,7 +1193,7 @@
goto number;
}
*mp = unspec;
- break;
+ break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
n = strtoul(p, pp, 10);
@@ -963,41 +1204,53 @@
*mp = ⊤
break;
}
- /* fall through */
number:
- err = num2msg(mp, sign, n, first, cur);
- if (err != nil)
- return err;
+ m0 = m = nil;
+ switch(sign){
+ case 0:
+ for(m = first; m != nil; m0 = m, m = m->next)
+ if(m->id == n)
+ break;
+ break;
+ case -1:
+ if(cur != &top)
+ for(m = cur; m0 = m, m != nil && n > 0; n--)
+ m = m->prev;
+ break;
+ case 1:
+ if(cur == &top){
+ n--;
+ cur = first;
+ }
+ for(m = cur; m != nil && n > 0; m0 = m, n--)
+ m = m->next;
+ break;
+ }
+ if(m == nil && f&Comma)
+ m = m0;
+ if(m == nil)
+ return "address";
+ *mp = m;
break;
+ case '?':
+ /* legacy behavior. no longer needed */
+ sign = -1;
case '%':
case '/':
- case '?':
+ case '#':
c = *p;
- prog = parsesearch(pp);
+ fn= chartosearch(c);
+ prog = parsesearch(pp, buf, sizeof buf);
if(prog == nil)
return "badly formed regular expression";
- m = nil;
- switch(c){
- case '%':
- for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
- if(rawsearch(m, prog))
+ if(sign == -1){
+ for(m = cur == &top ? nil : cur->prev; m; m = m->prev)
+ if(fn(m, prog, buf))
break;
- }
- break;
- case '/':
- for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
- snprintheader(buf, sizeof(buf), m);
- if(regexec(prog, buf, nil, 0))
+ }else{
+ for(m = cur == &top ? first : cur->next; m; m = m->next)
+ if(fn(m, prog, buf))
break;
- }
- break;
- case '?':
- for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
- snprintheader(buf, sizeof(buf), m);
- if(regexec(prog, buf, nil, 0))
- break;
- }
- break;
}
if(m == nil)
return "search";
@@ -1008,15 +1261,14 @@
for(m = first; m != nil && m->next != nil; m = m->next)
;
*mp = m;
- *pp = p+1;
+ *pp = p + 1;
break;
case '.':
*mp = cur;
- *pp = p+1;
+ *pp = p + 1;
break;
case ',':
- if (*mp == nil)
- *mp = first;
+ *mp = first;
*pp = p;
break;
}
@@ -1023,81 +1275,40 @@
if(*mp != nil && **pp == '.'){
(*pp)++;
- if((*mp)->child == nil)
+ if((m = (*mp)->child) == nil)
return "no sub parts";
- return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
+ return parseaddr(pp, m, m, m, mp, 0);
}
- if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
- return parseaddr(pp, first, *mp, *mp, mp);
+ c = **pp;
+ if(c == '+' || c == '-' || c == '/' || c == '%' || c == '#')
+ return parseaddr(pp, first, *mp, *mp, mp, 0);
return nil;
}
-//
-// search a message for a regular expression match
-//
-int
-rawsearch(Message *m, Reprog *prog)
-{
- char buf[4096+1];
- int i, fd, rv;
- String *path;
-
- path = extendpath(m->path, "raw");
- fd = open(s_to_c(path), OREAD);
- if(fd < 0)
- return 0;
-
- // march through raw message 4096 bytes at a time
- // with a 128 byte overlap to chain the re search.
- rv = 0;
- for(;;){
- i = read(fd, buf, sizeof(buf)-1);
- if(i <= 0)
- break;
- buf[i] = 0;
- if(regexec(prog, buf, nil, 0)){
- rv = 1;
- break;
- }
- if(i < sizeof(buf)-1)
- break;
- if(seek(fd, -128LL, 1) < 0)
- break;
- }
-
- close(fd);
- s_free(path);
- return rv;
-}
-
-
char*
parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
{
+ char buf[256], *err;
+ int i, c, r;
Reprog *prog;
Message *m, *s, *e, **l, *last;
- char buf[256];
- char *err;
- int i, c;
- char *q;
- static char errbuf[Errlen];
+ int (*f)(Message*, Reprog*, char*);
+ static char errbuf[ERRMAX];
cmd->delete = 0;
l = &cmd->msgs;
*l = nil;
- // eat white space
- while(*p == ' ')
+ while(*p == ' ' || *p == '\t')
p++;
- // null command is a special case (advance and print)
+ /* null command is a special case (advance and print) */
if(*p == 0){
- if(cur == &top){
- // special case
+ if(cur == &top)
m = first;
- } else {
- // walk to the next message even if we have to go up
+ else {
+ /* walk to the next message even if we have to go up */
m = cur->next;
while(m == nil && cur->parent != nil){
cur = cur->parent;
@@ -1113,73 +1324,76 @@
return nil;
}
- // global search ?
+ /* global search ? */
if(*p == 'g'){
p++;
- // no search string means all messages
- if(*p != '/' && *p != '%'){
+ /* no search string means all messages */
+ if(*p == 'k'){
+ for(m = first; m != nil; m = m->next)
+ if(m->flags & Fflagged){
+ *l = m;
+ l = &m->cmd;
+ *l = nil;
+ }
+ p++;
+ }else if(*p != '/' && *p != '%' && *p != '#'){
for(m = first; m != nil; m = m->next){
*l = m;
l = &m->cmd;
*l = nil;
}
- } else {
- // mark all messages matching this search string
+ }else{
+ /* mark all messages matching this search string */
c = *p;
- prog = parsesearch(&p);
+ f = chartosearch(c);
+ prog = parsesearch(&p, buf, sizeof buf);
if(prog == nil)
return "badly formed regular expression";
- if(c == '%'){
- for(m = first; m != nil; m = m->next){
- if(rawsearch(m, prog)){
- *l = m;
- l = &m->cmd;
- *l = nil;
- }
+ for(m = first; m != nil; m = m->next){
+ if(f(m, prog, buf)){
+ *l = m;
+ l = &m->cmd;
+ *l = nil;
}
- } else {
- for(m = first; m != nil; m = m->next){
- snprintheader(buf, sizeof(buf), m);
- if(regexec(prog, buf, nil, 0)){
- *l = m;
- l = &m->cmd;
- *l = nil;
- }
- }
}
free(prog);
}
- } else {
-
- // parse an address
+ }else{
+ /* parse an address */
s = e = nil;
- err = parseaddr(&p, first, cur, cur, &s);
+ err = parseaddr(&p, first, cur, cur, &s, 0);
if(err != nil)
return err;
if(*p == ','){
- // this is an address range
+ /* this is an address range */
if(s == &top)
s = first;
p++;
for(last = s; last != nil && last->next != nil; last = last->next)
;
- err = parseaddr(&p, first, cur, last, &e);
+ err = parseaddr(&p, first, cur, last, &e, Comma);
if(err != nil)
return err;
-
- // select all messages in the range
- for(; s != nil; s = s->next){
+ /* select all messages in the range */
+ r = 0;
+ if(s != nil && e != nil && s->id > e->id)
+ r = 1;
+ while(s != nil){
*l = s;
l = &s->cmd;
*l = nil;
if(s == e)
break;
+ if(r)
+ s = s->prev;
+ else
+ s = s->next;
}
if(s == nil)
return "null address range";
} else {
- // single address
+ /* single address */
if(s != &top){
*l = s;
s->cmd = nil;
@@ -1187,92 +1401,49 @@
}
}
- // insert a space after '!'s and '|'s
- for(q = p; *q; q++)
- if(*q != '!' && *q != '|')
- break;
- if(q != p && *q != ' '){
- memmove(q+1, q, strlen(q)+1);
- *q = ' ';
+ while(*p == ' ' || *p == '\t')
+ p++;
+ /* hack to allow all messages to start with 'd' */
+ if(*p == 'd' && p[1]){
+ cmd->delete = 1;
+ p++;
}
-
- cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
- if(cmd->an == 0 || *cmd->av[0] == 0)
- cmd->f = pcmd;
- else {
- // hack to allow all messages to start with 'd'
- if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
- cmd->delete = 1;
- cmd->av[0]++;
- }
-
- // search command table
- for(i = 0; cmdtab[i].cmd != nil; i++)
- if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
- break;
- if(cmdtab[i].cmd == nil)
- return "illegal command";
- if(cmdtab[i].args == 0 && cmd->an > 1){
- snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
- return errbuf;
- }
- cmd->f = cmdtab[i].f;
+ while(*p == ' ' || *p == '\t')
+ p++;
+ if(*p == 0)
+ p = "p";
+ for(i = nelem(cmdtab) - 1; i >= 0; i--)
+ if(strncmp(p, cmdtab[i].cmd, strlen(cmdtab[i].cmd)) == 0)
+ goto found;
+ return "illegal command";
+found:
+ p += strlen(cmdtab[i].cmd);
+ snprint(cmd->cmdline, sizeof cmd->cmdline, "%s", p);
+ cmd->av[0] = cmdtab[i].cmd;
+ cmd->an = 1 + tokenize(p, cmd->av + 1, nelem(cmd->av) - 2);
+ if(cmdtab[i].args == 0 && cmd->an > 1){
+ snprint(errbuf, sizeof errbuf, "%s doesn't take an argument", cmdtab[i].cmd);
+ return errbuf;
}
- return nil;
-}
+ cmd->f = cmdtab[i].f;
-// inefficient read from standard input
-char*
-readline(char *prompt, char *line, int len)
-{
- char *p, *e;
- int n;
-
-retry:
- interrupted = 0;
- Bprint(&out, "%s", prompt);
- Bflush(&out);
- e = line + len;
- for(p = line; p < e; p++){
- n = read(0, p, 1);
- if(n < 0){
- if(interrupted)
- goto retry;
- return nil;
- }
- if(n == 0)
- return nil;
- if(*p == '\n')
- break;
- }
- *p = 0;
- return line;
+ if(cmdtab[i].addr && (cmd->msgs == nil || cmd->msgs == &top)){
+ snprint(errbuf, sizeof errbuf, "%s requires an address", cmdtab[i].cmd);
+ return errbuf;
+ }
+ return nil;
}
-void
-messagecount(Message *m)
-{
- int i;
-
- i = 0;
- for(; m != nil; m = m->next)
- i++;
- Bprint(&out, "%d message%s\n", i, plural(i));
-}
-
Message*
aichcmd(Message *m, int indent)
{
- char hdr[256];
+ char hdr[256];
- if(m == &top)
- return nil;
-
- snprintHeader(hdr, sizeof(hdr), indent, m);
+ pheader(hdr, sizeof hdr, indent, m);
Bprint(&out, "%s\n", hdr);
for(m = m->child; m != nil; m = m->next)
- aichcmd(m, indent+1);
- return nil;
+ aichcmd(m, indent + 1);
+ return m;
}
Message*
@@ -1280,21 +1451,19 @@
{
if(m == &top)
return nil;
- aichcmd(m, 0);
- return nil;
+ return aichcmd(m, 0);
}
Message*
hcmd(Cmd*, Message *m)
{
- char hdr[256];
+ char hdr[256];
if(m == &top)
return nil;
-
- snprintheader(hdr, sizeof(hdr), m);
+ pheader0(hdr, sizeof hdr, m);
Bprint(&out, "%s\n", hdr);
- return nil;
+ return m;
}
Message*
@@ -1301,8 +1470,9 @@
bcmd(Cmd*, Message *m)
{
int i;
- Message *om = m;
+ Message *om;
+ om = m;
if(m == &top)
m = top.child;
for(i = 0; i < 10 && m != nil; i++){
@@ -1311,7 +1481,7 @@
m = m->next;
}
- return om;
+ return m != nil? m: om;
}
Message*
@@ -1323,21 +1493,54 @@
}
int
-printpart(String *s, char *part)
+writepart(char *m, char *part, char *s)
{
+ char *e;
+ int fd, n;
+
+ fd = open(extendp(m, part), OWRITE);
+ if(fd < 0){
+ dissappeared();
+ return -1;
+ }
+ for(e = s + strlen(s); e - s > 0; s += n){
+ if((n = write(fd, s, e - s)) <= 0){
+ eprint("!writepart:%s: %r\n", part);
+ break;
+ }
+ if(interrupted)
+ break;
+ }
+ close(fd);
+ return s == e? 0: -1;
+}
+
+Message *xpipecmd(Cmd*, Message*, char*);
+
+Message*
+printfmt(Message *m, char *part, char *cmd)
+{
+ Cmd c;
+
+ c.an = 2;
+ snprint(c.cmdline, sizeof c.cmdline, "%s", cmd);
+ Bflush(&out);
+ return xpipecmd(&c, m, part);
+}
+
+int
+printpart0(Message *m, char *part)
+{
char buf[4096];
int n, fd, tot;
- String *path;
- path = extendpath(s, part);
- fd = open(s_to_c(path), OREAD);
- s_free(path);
+ fd = open(extendp(m->path, part), OREAD);
if(fd < 0){
- fprint(2, "!message disappeared\n");
+ dissappeared();
return 0;
}
tot = 0;
- while((n = read(fd, buf, sizeof(buf))) > 0){
+ while((n = read(fd, buf, sizeof buf)) > 0){
if(interrupted)
break;
if(Bwrite(&out, buf, n) <= 0)
@@ -1349,15 +1552,23 @@
}
int
+printpart(Message *m, char *part, char *cmd)
+{
+ if(cmd == nil || cmd[0] == 0)
+ return printpart0(m, part);
+ printfmt(m, part, cmd);
+ return 1;
+}
+
+int
printhtml(Message *m)
{
Cmd c;
+ memset(&c, 0, sizeof c);
c.an = 3;
- c.av[1] = "/bin/htmlfmt";
- c.av[2] = "-l 40 -cutf-8";
- Bprint(&out, "!%s\n", c.av[1]);
- Bflush(&out);
+ snprint(c.cmdline, sizeof c.cmdline, "/bin/htmlfmt -l60 -cutf8");
+ eprint("!/bin/htmlfmt\n");
pipecmd(&c, m);
return 0;
}
@@ -1368,8 +1579,8 @@
if(m == &top)
return ⊤
if(m->parent == &top)
- printpart(m->path, "unixheader");
- printpart(m->path, "raw");
+ printpart(m, "unixheader", nil);
+ printpart(m, "raw", nil);
return m;
}
@@ -1389,27 +1600,101 @@
*np = 0;
}
+void
+setflags(Message *m, char *f)
+{
+ uchar f0;
+
+ f0 = m->flags;
+ txflags(f, &m->flags);
+ if(f0 != m->flags)
+ if((m->nflags & Nnoflags) == 0)
+ writepart(m->path, "flags", f);
+}
+
Message*
-pcmd(Cmd*, Message *m)
+Fcmd(Cmd *c, Message *m)
{
- Message *nm;
+ int i;
+
+ for(i = 1; i < c->an; i++)
+ setflags(m, c->av[i]);
+ return m;
+}
+
+void
+seen(Message *m)
+{
+ setflags(m, "s");
+}
+
+/*
+ * sleeze
+ */
+int
+magicpart(Message *m, char *s, char *part)
+{
+ char buf[4096];
+ int n, fd, c;
+
+ fd = open(extendp(s, part), OREAD);
+ if(fd < 0){
+ if(strcmp(part, "id") == 0)
+ Bprint(&out, "%D ", m);
+ else if(strcmp(part, "fpath") == 0)
+ Bprint(&out, "%s ", rooted(m->path));
+ else
+ Bprint(&out, "%s ", part);
+ return 0;
+ }
+
+ c = 0;
+ while((n = read(fd, buf, sizeof buf)) > 0){
+ c = -1;
+ if(interrupted)
+ break;
+ if(Bwrite(&out, buf, n) <= 0)
+ break;
+ c = buf[n - 1];
+ }
+ close(fd);
+ if(!interrupted && n != -1 && c != -1)
+ if(strstr(part, "body") != nil || strcmp(part, "rawunix") == 0)
+ seen(m);
+ return c;
+}
+
+Message*
+pcmd0(Cmd *c, Message *m, int mayplumb, char *tfmt)
+{
+ char *s, buf[128];
+ int i, ch;
Ctype *cp;
- String *s;
- char buf[128];
+ Message *nm;
if(m == &top)
return ⊤
- if(m->parent == &top)
- printpart(m->path, "unixheader");
- if(printpart(m->path, "header") > 0)
+ if(c && c->an >= 2){
+ ch = 0;
+ for(i = 1; i < c->an; i++)
+ ch = magicpart(m, m->path, c->av[i]);
+ if(ch != '\n')
+ Bprint(&out, "\n");
+ return m;
+ }
+ if(m->parent == &top){
+ seen(m);
+ printpart(m, "unixheader", nil);
+ }
+ if(printpart(m, "header", nil) > 0)
Bprint(&out, "\n");
cp = findctype(m);
- if(cp->display){
+ if(cp->flag & Display){
if(strcmp(m->type, "text/html") == 0)
printhtml(m);
else
- printpart(m->path, "body");
- } else if(strcmp(m->type, "multipart/alternative") == 0){
+ printpart(m, "body", tfmt);
+ }else if(strcmp(m->type, "multipart/alternative") == 0){
for(nm = m->child; nm != nil; nm = nm->next){
cp = findctype(nm);
if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
@@ -1418,67 +1703,91 @@
if(nm == nil)
for(nm = m->child; nm != nil; nm = nm->next){
cp = findctype(nm);
- if(cp->display)
+ if(cp->flag & Display)
break;
}
if(nm != nil)
- pcmd(nil, nm);
+ pcmd0(nil, nm, mayplumb, tfmt);
else
hcmd(nil, m);
- } else if(strncmp(m->type, "multipart/", 10) == 0){
+ }else if(strncmp(m->type, "multipart/", 10) == 0){
nm = m->child;
if(nm != nil){
- // always print first part
- pcmd(nil, nm);
+ /* always print first part */
+ pcmd0(nil, nm, mayplumb, tfmt);
for(nm = nm->next; nm != nil; nm = nm->next){
- s = rooted(s_clone(nm->path));
+ s = rooted(nm->path);
cp = findctype(nm);
- snprintHeader(buf, sizeof buf, -1, nm);
+ pheader(buf, sizeof buf, -1, nm);
compress(buf);
if(strcmp(nm->disposition, "inline") == 0){
if(cp->ext != nil)
Bprint(&out, "\n--- %s %s/body.%s\n\n",
- buf, s_to_c(s), cp->ext);
+ buf, s, cp->ext);
else
Bprint(&out, "\n--- %s %s/body\n\n",
- buf, s_to_c(s));
- // pcmd(nil, nm);
+ buf, s);
+ pcmd0(nil, nm, 0, tfmt);
} else {
if(cp->ext != nil)
Bprint(&out, "\n!--- %s %s/body.%s\n",
- buf, s_to_c(s), cp->ext);
+ buf, s, cp->ext);
else
Bprint(&out, "\n!--- %s %s/body\n",
- buf, s_to_c(s));
+ buf, s);
}
- s_free(s);
}
} else {
hcmd(nil, m);
}
- } else if(strcmp(m->type, "message/rfc822") == 0){
+ }else if(strcmp(m->type, "message/rfc822") == 0)
pcmd(nil, m->child);
- } else if(plumb(m, cp) >= 0)
- Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
- else
- Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
-
+ else if(!mayplumb){
+ }else if(plumb(m, cp) >= 0){
+ Bprint(&out, "\n!--- using plumber to type %s", cp->type);
+ if(strcmp(cp->type, m->type) != 0)
+ Bprint(&out, " (was %s)", m->type);
+ Bprint(&out, "\n");
+ }else
+ Bprint(&out, "\n!--- cannot display %s\n", cp->type);
+
return m;
}
+Message*
+pcmd(Cmd *c, Message *m)
+{
+ return pcmd0(c, m, 1, textfmt);
+}
+
+Message*
+tcmd(Cmd *c, Message *m)
+{
+ switch(c->an){
+ case 1:
+ if(textfmt[0] != 0)
+ textfmt[0] = 0;
+ else
+ snprint(textfmt, sizeof textfmt, "%s", "upas/tfmt");
+ break;
+ default:
+ snprint(textfmt, sizeof textfmt, "%s", c->cmdline);
+ break;
+ }
+ eprint("!textfmt %s\n", textfmt);
+ return m;
+}
+
void
-printpartindented(String *s, char *part, char *indent)
+printpartindented(char *s, char *part, char *indent)
{
char *p;
- String *path;
Biobuf *b;
- path = extendpath(s, part);
- b = Bopen(s_to_c(path), OREAD);
- s_free(path);
+ b = Bopen(extendp(s, part), OREAD);
if(b == nil){
- fprint(2, "!message disappeared\n");
+ dissappeared();
return;
}
while((p = Brdline(b, '\n')) != nil){
@@ -1492,11 +1801,23 @@
Bterm(b);
}
+void
+printpartindent2(char *s, char *part, char *indent)
+{
+ Cmd c;
+
+ memset(&c, 0, sizeof c);
+ snprint(c.cmdline, sizeof c.cmdline, "fmt -q '> ' %s | sed 's/^/%s/g' ",
+ rooted(extendp(s, part)), indent);
+ Bflush(&out);
+ bangcmd(&c, nil);
+}
+
Message*
-quotecmd(Cmd*, Message *m)
+quotecmd0(Cmd *c, Message *m, void (*p)(char*, char*, char*))
{
- Message *nm;
Ctype *cp;
+ Message *nm;
if(m == &top)
return ⊤
@@ -1504,9 +1825,9 @@
if(m->from != nil && *m->from)
Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
cp = findctype(m);
- if(cp->display){
- printpartindented(m->path, "body", "> ");
- } else if(strcmp(m->type, "multipart/alternative") == 0){
+ if(cp->flag & Display)
+ p(m->path, "body", "> ");
+ else if(strcmp(m->type, "multipart/alternative") == 0){
for(nm = m->child; nm != nil; nm = nm->next){
cp = findctype(nm);
if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
@@ -1515,30 +1836,41 @@
if(nm == nil)
for(nm = m->child; nm != nil; nm = nm->next){
cp = findctype(nm);
- if(cp->display)
+ if(cp->flag & Display)
break;
}
if(nm != nil)
- quotecmd(nil, nm);
- } else if(strncmp(m->type, "multipart/", 10) == 0){
+ quotecmd(c, nm);
+ }else if(strncmp(m->type, "multipart/", 10) == 0){
nm = m->child;
if(nm != nil){
cp = findctype(nm);
- if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
- quotecmd(nil, nm);
+ if(cp->flag & Display || strncmp(m->type, "multipart/", 10) == 0)
+ quotecmd(c, nm);
}
}
return m;
}
-// really delete messages
Message*
+quotecmd(Cmd *c, Message *m)
+{
+ void (*p)(char*, char*, char*);
+
+ p = printpartindented;
+ if(strstr(c->av[0], "\"\"") != nil)
+ p = printpartindent2;
+ return quotecmd0(c, m, p);
+}
+
+
+/* really delete messages */
+Message*
flushdeleted(Message *cur)
{
- Message *m, **l;
char buf[1024], *p, *e, *msg;
- int deld, n, fd;
- int i;
+ int i, deld, n, fd;
+ Message *m, **l;
doflush = 0;
deld = 0;
@@ -1545,38 +1877,39 @@
fd = open("/mail/fs/ctl", ORDWR);
if(fd < 0){
- fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
+ eprint("!can't delete mail, opening /mail/fs/ctl: %r\n");
exitfs(0);
}
- e = &buf[sizeof(buf)];
+ e = buf + sizeof buf;
p = seprint(buf, e, "delete %s", mbname);
n = 0;
for(l = &top.child; *l != nil;){
m = *l;
- if(!m->deleted){
+ if((m->nflags & Nmissing) == 0)
+ if((m->flags & Fdeleted) == 0){
l = &(*l)->next;
continue;
}
- // don't return a pointer to a deleted message
+ /* don't return a pointer to a deleted message */
if(m == cur)
cur = m->next;
-
deld++;
- msg = strrchr(s_to_c(m->path), '/');
- if(msg == nil)
- msg = s_to_c(m->path);
- else
- msg++;
- if(e-p < 10){
- write(fd, buf, p-buf);
- n = 0;
- p = seprint(buf, e, "delete %s", mbname);
+ if(m->flags & Fdeleted){
+ msg = strrchr(m->path, '/');
+ if(msg == nil)
+ msg = m->path;
+ else
+ msg++;
+ if(e - p < 10){
+ write(fd, buf, p - buf);
+ n = 0;
+ p = seprint(buf, e, "delete %s", mbname);
+ }
+ p = seprint(p, e, " %s", msg);
+ n++;
}
- p = seprint(p, e, " %s", msg);
- n++;
-
- // unchain and free
+ /* unchain and free */
*l = m->next;
if(m->next)
m->next->prev = m->prev;
@@ -1583,7 +1916,7 @@
freemessage(m);
}
if(n)
- write(fd, buf, p-buf);
+ write(fd, buf, p - buf);
close(fd);
@@ -1590,13 +1923,15 @@
if(deld)
Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
- // renumber
+ /* renumber */
i = 1;
for(m = top.child; m != nil; m = m->next)
m->id = natural ? m->fileno : i++;
- // if we're out of messages, go back to first
- // if no first, return the fake first
+ /*
+ * if we're out of messages, go back to first
+ * if no first, return the fake first
+ */
if(cur == nil){
if(top.child)
return top.child;
@@ -1607,24 +1942,60 @@
}
Message*
+mbcmd(Cmd *c, Message*)
+{
+ char *mb, oldmb[Pathlen];
+ Message *m, **l;
+
+ switch(c->an){
+ case 1:
+ mb = "mbox";
+ break;
+ case 2:
+ mb = c->av[1];
+ break;
+ default:
+ eprint("!usage: mbcmd [mbox]\n");
+ return nil;
+ }
+
+ /* flushdeleted(nil); ? */
+ for(l = &top.child; *l; ){
+ m = *l;
+ *l = m->next;
+ freemessage(m);
+ }
+ top.child = nil;
+
+ strcpy(oldmb, mbpath);
+ if(switchmb(mb, 0) < 0){
+ eprint("!no mb\n");
+ if(switchmb(oldmb, 0) < 0){
+ eprint("!mb disappeared\n");
+ exits("fail");
+ }
+ }
+ icmd(nil, nil);
+ interrupted = 1; /* no looping */
+ return ⊤
+}
+
+Message*
qcmd(Cmd*, Message*)
{
flushdeleted(nil);
-
if(didopen)
closemb();
Bflush(&out);
-
exitfs(0);
- return nil; // not reached
+ return nil;
}
Message*
-ycmd(Cmd*, Message *m)
+ycmd(Cmd *c, Message *m)
{
doflush = 1;
-
- return icmd(nil, m);
+ return icmd(c, m);
}
Message*
@@ -1631,29 +2002,22 @@
xcmd(Cmd*, Message*)
{
exitfs(0);
- return nil; // not reached
+ return nil;
}
Message*
eqcmd(Cmd*, Message *m)
{
- if(m == &top)
- Bprint(&out, "0\n");
- else
- Bprint(&out, "%d\n", m->id);
- return nil;
+ Bprint(&out, "%D\n", m);
+ return m;
}
Message*
dcmd(Cmd*, Message *m)
{
- if(m == &top){
- Bprint(&out, "!address\n");
- return nil;
- }
while(m->parent != &top)
m = m->parent;
- m->deleted = 1;
+ m->flags |= Fdeleted;
return m;
}
@@ -1664,25 +2028,99 @@
return nil;
while(m->parent != &top)
m = m->parent;
- if(m->deleted < 0)
- Bprint(&out, "!can't undelete, already flushed\n");
- m->deleted = 0;
+ m->flags &= ~Fdeleted;
return m;
}
+int
+skipscan(void)
+{
+ int r;
+ Dir *d;
+ static int lastvers = -1;
+ d = dirstat(top.path);
+ r = d && d->qid.path == mbqid.path && d->qid.vers == mbqid.vers;
+ r = r && mbvers == lastvers;
+ if(d != nil){
+ mbqid = d->qid;
+ lastvers = mbvers;
+ }
+ free(d);
+ return r;
+}
+
Message*
-icmd(Cmd*, Message *m)
+icmd(Cmd *c, Message *m)
{
- int n;
+ char buf[128], *p, *e;
+ Dirstats s;
- n = dir2message(&top, reverse);
- if(n > 0)
- Bprint(&out, "%d new message%s\n", n, plural(n));
+ if(skipscan())
+ return m;
+ if(dir2message(&top, reverse, &s) < 0)
+ return nil;
+ p = buf;
+ e = buf + sizeof buf;
+ if(s.new > 0 && c == nil){
+ p = seprint(p, e, "%d message%s", s.new, plural(s.new));
+ if(s.unread > 0)
+ p = seprint(p, e, ", %d unread", s.unread);
+ }
+ else if(s.new > 0)
+ Bprint(&out, "%d new message%s", s.new, plural(s.new));
+ if(s.new && s.del)
+ p = seprint(p, e, "; ");
+ if(s.del > 0)
+ p = seprint(p, e, "%d deleted message%s", s.del, plural(s.del));
+ if(s.new + s.del)
+ p = seprint(p, e, "\n");
+ if(p > buf){
+ Bflush(&out);
+ eprint("%s", buf);
+ }
return m;
}
Message*
+kcmd0(Cmd *c, Message *m)
+{
+ char *f, *s;
+ int sticky;
+
+ if(c->an > 2){
+ eprint("!usage k [flags]\n");
+ return nil;
+ }
+ if(c->f == kcmd)
+ f = "f";
+ else
+ f = "-f";
+ if(c->an == 2)
+ f = c->av[1];
+ setflags(m, f);
+ if(c->an == 2 && (m->nflags & Nnoflags) == 0){
+ sticky = m->flags & Fdeleted;
+ s = file2string(m->path, "flags");
+ m->flags = buftoflags(s) | sticky;
+ free(s);
+ }
+ return m;
+}
+
+Message*
+kcmd(Cmd *c, Message *m)
+{
+ return kcmd0(c, m);
+}
+
+Message*
+Kcmd(Cmd *c, Message *m)
+{
+ return kcmd0(c, m);
+}
+
+Message*
helpcmd(Cmd*, Message *m)
{
int i;
@@ -1690,38 +2128,50 @@
Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
- Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
+ Bprint(&out, "<search> := 'k' | '/'<re>'/' | '?'<re>'?' | '%%'<re>'%%' | '#' <field> '#' <re> '#' \n");
Bprint(&out, "<command> :=\n");
- for(i = 0; cmdtab[i].cmd != nil; i++)
+ for(i = 0; i < nelem(cmdtab); i++)
Bprint(&out, "%s\n", cmdtab[i].help);
return m;
}
+/* ed thinks this is a good idea */
+void
+marshal(char **path, char **argv0)
+{
+ char *s;
+
+ s = getenv("marshal");
+ if(s == nil || *s == 0)
+ s = "/bin/upas/marshal";
+ *path = s;
+ *argv0 = strrchr(s, '/') + 1;
+ if(*argv0 == (char*)1)
+ *argv0 = s;
+}
+
int
tomailer(char **av)
{
- Waitmsg *w;
int pid, i;
+ char *p, *a;
+ Waitmsg *w;
- // start the mailer and get out of the way
switch(pid = fork()){
case -1:
- fprint(2, "can't fork: %r\n");
+ eprint("can't fork: %r\n");
return -1;
case 0:
- Bprint(&out, "!/bin/upas/marshal");
- for(i = 1; av[i]; i++){
- if(strchr(av[i], ' ') != nil)
- Bprint(&out, " '%s'", av[i]);
- else
- Bprint(&out, " %s", av[i]);
- }
+ marshal(&p, &a);
+ Bprint(&out, "!%s", p);
+ for(i = 1; av[i]; i++)
+ Bprint(&out, " %q", av[i]);
Bprint(&out, "\n");
Bflush(&out);
- av[0] = "marshal";
+ av[0] = a;
chdir(wd);
- exec("/bin/upas/marshal", av);
- fprint(2, "couldn't exec /bin/upas/marshal\n");
+ exec(p, av);
+ eprint("couldn't exec %s\n", p);
exits(0);
default:
w = wait();
@@ -1732,28 +2182,28 @@
return -1;
}
if(w->msg[0]){
- fprint(2, "mailer failed: %s\n", w->msg);
+ eprint("mailer failed: %s\n", w->msg);
free(w);
return -1;
}
free(w);
- Bprint(&out, "!\n");
+// Bprint(&out, "!\n");
break;
}
return 0;
}
-//
-// like tokenize but obey "" quoting
-//
+/*
+ * like tokenize but obey "" quoting
+ */
int
tokenize822(char *str, char **args, int max)
{
- int na;
- int intok = 0, inquote = 0;
+ int na, intok, inquote;
if(max <= 0)
- return 0;
+ return 0;
+ intok = inquote = 0;
for(na=0; ;str++)
switch(*str) {
case ' ':
@@ -1783,56 +2233,59 @@
}
}
-/* return reply-to address & set *nmp to corresponding Message */
-static char *
-getreplyto(Message *m, Message **nmp)
+static char *rec[] = {"Re: ", "AW:", };
+static char *fwc[] = {"Fwd: ", };
+
+char*
+addrecolon(char **tab, int n, char *s)
{
- Message *nm;
+ char *prefix;
+ int i;
- for(nm = m; nm != ⊤ nm = nm->parent)
- if(*nm->replyto != 0)
+ prefix = "";
+ for(i = 0; i < n; i++)
+ if(cistrncmp(s, tab[i], strlen(tab[i]) - 1) == 0)
break;
- *nmp = nm;
- return nm? nm->replyto: nil;
+ if(i == n)
+ prefix = tab[0];
+ return smprint("%s%s", prefix, s);
}
Message*
rcmd(Cmd *c, Message *m)
{
- char *addr;
- char *av[128];
- int i, ai = 1;
- String *from, *rpath, *path = nil, *subject = nil;
+ char *from, *path, *subject, *rpath, *addr, *av[128];
+ int i, ai;
Message *nm;
- if(m == &top){
- Bprint(&out, "!address\n");
- return nil;
- }
-
- addr = getreplyto(m, &nm);
+ ai = 1;
+ av[ai++] = "-8";
+ addr = path = subject = nil;
+ for(nm = m; nm != ⊤ nm = nm->parent)
+ if(*nm->replyto != 0){
+ addr = nm->replyto;
+ break;
+ }
if(addr == nil){
- Bprint(&out, "!no reply address\n");
+ eprint("!no reply address\n");
return nil;
}
+
if(nm == &top){
print("!noone to reply to\n");
return nil;
}
- av[ai++] = "-8";
- for(nm = m; nm != ⊤ nm = nm->parent){
+ for(nm = m; nm != ⊤ nm = nm->parent)
if(*nm->subject){
av[ai++] = "-s";
- subject = addrecolon(nm->subject);
- av[ai++] = s_to_c(subject);
+ subject = addrecolon(rec, nelem(rec), nm->subject);
+ av[ai++] = subject;
break;
}
- }
av[ai++] = "-R";
- rpath = rooted(s_clone(m->path));
- av[ai++] = s_to_c(rpath);
+ av[ai++] = rpath = strdup(rooted(m->path));
if(strchr(c->av[0], 'f') != nil){
fcmd(c, m);
@@ -1843,21 +2296,22 @@
av[ai++] = "-t";
av[ai++] = "message/rfc822";
av[ai++] = "-A";
- path = rooted(extendpath(m->path, "raw"));
- av[ai++] = s_to_c(path);
+ path = strdup(rooted(extendp(m->path, "raw")));
+ av[ai++] = path;
}
for(i = 1; i < c->an && ai < nelem(av)-1; i++)
av[ai++] = c->av[i];
- from = s_copy(addr);
- ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
+ ai += tokenize822(from = strdup(addr), &av[ai], nelem(av) - ai);
av[ai] = 0;
- if(tomailer(av) < 0)
+ if(tomailer(av) == -1)
m = nil;
- s_free(path);
- s_free(rpath);
- s_free(subject);
- s_free(from);
+ else
+ m->flags |= Fanswered;
+ free(path);
+ free(rpath);
+ free(subject);
+ free(from);
return m;
}
@@ -1864,18 +2318,22 @@
Message*
mcmd(Cmd *c, Message *m)
{
- char *av[128];
- int i, ai = 1;
- String *path;
+ char *subject, *av[128];
+ int i, ai;
- if(m == &top){
- Bprint(&out, "!address\n");
+ if(c->an < 2){
+ eprint("!usage: M list-of addresses\n");
return nil;
}
- if(c->an < 2){
- fprint(2, "!usage: M list-of addresses\n");
- return nil;
+ ai = 1;
+ av[ai++] = "-8";
+
+ subject = nil;
+ if(m->subject){
+ av[ai++] = "-s";
+ subject = addrecolon(fwc, nelem(fwc), m->subject);
+ av[ai++] = subject;
}
av[ai++] = "-t";
@@ -1885,22 +2343,18 @@
av[ai++] = "mime";
av[ai++] = "-A";
- path = rooted(extendpath(m->path, "raw"));
- av[ai++] = s_to_c(path);
-
+ av[ai++] = rooted(extendp(m->path, "raw"));
if(strchr(c->av[0], 'M') == nil)
av[ai++] = "-n";
- else
- av[ai++] = "-8";
-
for(i = 1; i < c->an && ai < nelem(av)-1; i++)
av[ai++] = c->av[i];
av[ai] = 0;
- if(tomailer(av) < 0)
+ if(tomailer(av) == -1)
m = nil;
- if(path != nil)
- s_free(path);
+ else
+ m->flags |= Fanswered;
+ free(subject);
return m;
}
@@ -1907,132 +2361,85 @@
Message*
acmd(Cmd *c, Message *m)
{
- char *av[128];
- int i, ai = 1;
- String *from, *rpath, *path = nil, *subject = nil;
- String *to, *cc;
+ char *av[128], *rpath, *subject, *from, *to, *cc;
+ int i, ai;
- if(m == &top){
- Bprint(&out, "!address\n");
+ if(m->from == nil || m->to == nil || m->cc == nil){
+ eprint("!bad message\n");
return nil;
}
+ ai = 1;
av[ai++] = "-8";
+ av[ai++] = "-R";
+ av[ai++] = rpath = strdup(rooted(m->path));
- if(*m->subject){
+ subject = nil;
+ if(m->subject && *m->subject){
av[ai++] = "-s";
- subject = addrecolon(m->subject);
- av[ai++] = s_to_c(subject);
+ subject = addrecolon(rec, nelem(rec), m->subject);
+ av[ai++] = subject;
}
- av[ai++] = "-R";
- rpath = rooted(s_clone(m->path));
- av[ai++] = s_to_c(rpath);
-
if(strchr(c->av[0], 'A') != nil){
av[ai++] = "-t";
av[ai++] = "message/rfc822";
av[ai++] = "-A";
- path = rooted(extendpath(m->path, "raw"));
- av[ai++] = s_to_c(path);
+ av[ai++] = rooted(extendp(m->path, "raw"));
}
for(i = 1; i < c->an && ai < nelem(av)-1; i++)
av[ai++] = c->av[i];
- from = s_copy(m->from);
- ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
- to = s_copy(m->to);
- ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
- cc = s_copy(m->cc);
- ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
+ ai += tokenize822(from = strdup(m->from), &av[ai], nelem(av) - ai);
+ ai += tokenize822(to = strdup(m->to), &av[ai], nelem(av) - ai);
+ ai += tokenize822(cc = strdup(m->cc), &av[ai], nelem(av) - ai);
av[ai] = 0;
- if(tomailer(av) < 0)
+ if(tomailer(av) == -1)
m = nil;
- s_free(path);
- s_free(rpath);
- s_free(subject);
- s_free(from);
- s_free(to);
- s_free(cc);
+ else
+ m->flags |= Fanswered;
+ free(from);
+ free(to);
+ free(cc);
+ free(subject);
+ free(rpath);
return m;
}
-String *
-relpath(char *path, String *to)
-{
- if (*path=='/' || strncmp(path, "./", 2) == 0
- || strncmp(path, "../", 3) == 0) {
- to = s_append(to, path);
- } else if(mbpath) {
- to = s_append(to, s_to_c(mbpath));
- to->ptr = strrchr(to->base, '/')+1;
- s_append(to, path);
- }
- return to;
-}
-
int
-appendtofile(Message *m, char *part, char *base, int mbox)
+appendtofile(Message *m, char *part, char *base, int mbox, int f)
{
- String *file, *h;
- int in, out, rv;
+ char *folder, path[Pathlen];
+ int in, rv, rp;
- file = extendpath(m->path, part);
- in = open(s_to_c(file), OREAD);
- if(in < 0){
- fprint(2, "!message disappeared\n");
+ in = open(extendp(m->path, part), OREAD);
+ if(in == -1){
+ dissappeared();
return -1;
}
-
- s_reset(file);
-
- relpath(base, file);
- if(sysisdir(s_to_c(file))){
- s_append(file, "/");
- if(m->filename && strchr(m->filename, '/') == nil)
- s_append(file, m->filename);
- else {
- s_append(file, "att.XXXXXXXXXXX");
- mktemp(s_to_c(file));
- }
- }
- if(mbox)
- out = open(s_to_c(file), OWRITE);
+ rp = 0;
+ if(*base == '/')
+ folder = base;
+ else if(!mbox){
+ snprint(path, sizeof path, "%s/%s", wd, base);
+ folder = path;
+ rp = 1;
+ }else if(f)
+ folder = ffoldername(mbpath, user, base);
else
- out = open(s_to_c(file), OWRITE|OTRUNC);
- if(out < 0){
- out = create(s_to_c(file), OWRITE, 0666);
- if(out < 0){
- fprint(2, "!can't open %s: %r\n", s_to_c(file));
- close(in);
- s_free(file);
- return -1;
- }
- }
+ folder = foldername(mbpath, user, base);
+ if(folder == nil)
+ return -1;
if(mbox)
- seek(out, 0, 2);
-
- // put on a 'From ' line
- if(mbox){
- while(m->parent != &top)
- m = m->parent;
- h = file2string(m->path, "unixheader");
- fprint(out, "%s", s_to_c(h));
- s_free(h);
- }
-
- // copy the message escaping what we have to ad adding newlines if we have to
- if(mbox)
- rv = appendfiletombox(in, out);
+ rv = fappendfolder(0, 0, folder, in);
else
- rv = appendfiletofile(in, out);
-
+ rv = fappendfile(m->from, folder, in);
close(in);
- close(out);
-
- if(rv >= 0)
- print("!saved in %s\n", s_to_c(file));
- s_free(file);
+ if(rv >= 0){
+ eprint("!saved in %s\n", rp? base: folder);
+ setflags(m, "S");
+ }else
+ eprint("!error %r\n");
return rv;
}
@@ -2041,11 +2448,6 @@
{
char *file;
- if(m == &top){
- Bprint(&out, "!address\n");
- return nil;
- }
-
switch(c->an){
case 1:
file = "stored";
@@ -2054,14 +2456,12 @@
file = c->av[1];
break;
default:
- fprint(2, "!usage: s filename\n");
+ eprint("!usage: s filename\n");
return nil;
}
- if(appendtofile(m, "raw", file, 1) < 0)
+ if(appendtofile(m, "rawunix", file, 1, 0) < 0)
return nil;
-
- m->stored = 1;
return m;
}
@@ -2070,11 +2470,6 @@
{
char *file;
- if(m == &top){
- Bprint(&out, "!address\n");
- return nil;
- }
-
switch(c->an){
case 2:
file = c->av[1];
@@ -2081,7 +2476,7 @@
break;
case 1:
if(*m->filename == 0){
- fprint(2, "!usage: w filename\n");
+ eprint("!usage: w filename\n");
return nil;
}
file = strrchr(m->filename, '/');
@@ -2091,114 +2486,107 @@
file = m->filename;
break;
default:
- fprint(2, "!usage: w filename\n");
+ eprint("!usage: w filename\n");
return nil;
}
- if(appendtofile(m, "body", file, 0) < 0)
+ if(appendtofile(m, "body", file, 0, 0) < 0)
return nil;
- m->stored = 1;
return m;
}
-char *specialfile[] =
-{
- "pipeto",
- "pipefrom",
- "L.mbox",
- "forward",
- "names"
+typedef struct Xtab Xtab;
+struct Xtab {
+ char *a;
+ char *b;
};
+Xtab *xtab;
+int nxtab;
-// return 1 if this is a special file
-static int
-special(String *s)
+void
+loadxfrom(int fd)
{
- char *p;
- int i;
+ char *f[3], *s, *p;
+ int n, a, inc;
+ Biobuf b;
+ Xtab *x;
- p = strrchr(s_to_c(s), '/');
- if(p == nil)
- p = s_to_c(s);
- else
- p++;
- for(i = 0; i < nelem(specialfile); i++)
- if(strcmp(p, specialfile[i]) == 0)
- return 1;
- return 0;
+ Binit(&b, fd, OREAD);
+ a = 0;
+ inc = 100;
+ for(; s = Brdstr(&b, '\n', 1);){
+ if(p = strchr(s, '#'))
+ *p = 0;
+ n = tokenize(s, f, nelem(f));
+ if(n != 2){
+ free(s);
+ continue;
+ }
+ if(nxtab == a){
+ a += inc;
+ xtab = realloc(xtab, a*sizeof *xtab);
+ if(xtab == nil)
+ sysfatal("realloc: %r");
+ inc *= 2;
+ }
+ for(x = xtab+nxtab; x > xtab && strcmp(x[-1].a, f[0]) > 0; x--)
+ x[0] = x[-1];
+ x->a = f[0];
+ x->b = f[1];
+ nxtab++;
+ }
}
-// open the folder using the recipients account name
-static String*
-foldername(char *rcvr)
+char*
+frombox(char *from)
{
- char *p;
- int c;
- String *file;
- Dir *d;
- int scarey;
+ char *s;
+ int n, m, fd;
+ Xtab *t, *p;
+ static int once;
- file = s_new();
- mboxpath("f", user, file, 0);
- d = dirstat(s_to_c(file));
-
- // if $mail/f exists, store there, otherwise in $mail
- s_restart(file);
- if(d && d->qid.type == QTDIR){
- scarey = 0;
- s_append(file, "f/");
- } else {
- scarey = 1;
+ if(once == 0){
+ once = 1;
+ s = foldername(mbpath, user, "fromtab-");
+ fd = open(s, OREAD);
+ if(fd != -1)
+ loadxfrom(fd);
+ close(fd);
}
- free(d);
-
- p = strrchr(rcvr, '!');
- if(p != nil)
- rcvr = p+1;
-
- while(*rcvr && *rcvr != '@'){
- c = *rcvr++;
- if(c == '/')
- c = '_';
- s_putc(file, c);
+ t = xtab;
+ n = nxtab;
+ while(n > 1) {
+ m = n/2;
+ p = t + m;
+ if(strcmp(from, p->a) > 0){
+ t = p;
+ n = n - m;
+ } else
+ n = m;
}
- s_terminate(file);
-
- if(scarey && special(file)){
- fprint(2, "!won't overwrite %s\n", s_to_c(file));
- s_free(file);
- return nil;
- }
-
- return file;
+ if(n && strcmp(from, t->a) == 0)
+ return t->b;
+ return from;
}
Message*
-fcmd(Cmd *c, Message *m)
+fcmd(Cmd*, Message *m)
{
- String *folder;
+ char *f;
- if(c->an > 1){
- fprint(2, "!usage: f takes no arguments\n");
+ f = frombox(m->from);
+ if(appendtofile(m, "rawunix", f, 1, 1) < 0)
return nil;
- }
+ return m;
+}
- if(m == &top){
- Bprint(&out, "!address\n");
- return nil;
- }
+Message*
+fqcmd(Cmd*, Message *m)
+{
+ char *f;
- folder = foldername(m->from);
- if(folder == nil)
- return nil;
-
- if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
- s_free(folder);
- return nil;
- }
- s_free(folder);
-
- m->stored = 1;
+ f = frombox(m->from);
+ Bprint(&out, "! %s\n", f);
return m;
}
@@ -2219,7 +2607,7 @@
if(wd[0] != 0)
chdir(wd);
exec(cmd, av);
- fprint(2, "!couldn't exec %s\n", cmd);
+ eprint("!couldn't exec %s\n", cmd);
exits(0);
default:
if(in >= 0)
@@ -2237,22 +2625,14 @@
Message*
bangcmd(Cmd *c, Message *m)
{
- char cmd[4*1024];
- char *p, *e;
char *av[4];
- int i;
- cmd[0] = 0;
- p = cmd;
- e = cmd+sizeof(cmd);
- for(i = 1; i < c->an; i++)
- p = seprint(p, e, "%s ", c->av[i]);
av[0] = "rc";
av[1] = "-c";
- av[2] = cmd;
+ av[2] = c->cmdline;
av[3] = 0;
system("/bin/rc", av, -1);
- Bprint(&out, "!\n");
+// Bprint(&out, "!\n");
return m;
}
@@ -2259,46 +2639,26 @@
Message*
xpipecmd(Cmd *c, Message *m, char *part)
{
- char cmd[128];
- char *p, *e;
char *av[4];
- String *path;
- int i, fd;
+ int fd;
if(c->an < 2){
- Bprint(&out, "!usage: | cmd\n");
+ eprint("!usage: | cmd\n");
return nil;
}
- if(m == &top){
- Bprint(&out, "!address\n");
- return nil;
- }
-
- path = extendpath(m->path, part);
- fd = open(s_to_c(path), OREAD);
- s_free(path);
- if(fd < 0){ // compatibility with older upas/fs
- path = extendpath(m->path, "raw");
- fd = open(s_to_c(path), OREAD);
- s_free(path);
- }
+ fd = open(extendp(m->path, part), OREAD);
if(fd < 0){
- fprint(2, "!message disappeared\n");
+ dissappeared();
return nil;
}
- p = cmd;
- e = cmd+sizeof(cmd);
- cmd[0] = 0;
- for(i = 1; i < c->an; i++)
- p = seprint(p, e, "%s ", c->av[i]);
av[0] = "rc";
av[1] = "-c";
- av[2] = cmd;
+ av[2] = c->cmdline;
av[3] = 0;
system("/bin/rc", av, fd); /* system closes fd */
- Bprint(&out, "!\n");
+// Bprint(&out, "!\n");
return m;
}
@@ -2323,172 +2683,128 @@
if(fd < 0)
sysfatal("can't open /mail/fs/ctl: %r");
- // close current mailbox
+ /* close current mailbox */
if(*mbname && strcmp(mbname, "mbox") != 0)
- fprint(fd, "close %s", mbname);
+ if(fprint(fd, "close %q", mbname) == -1)
+ eprint("!close %q: %r", mbname);
close(fd);
}
-int
-switchmb(char *file, char *singleton)
+static char*
+chop(char *s, int c)
{
char *p;
- int n, fd;
- String *path;
- char buf[256];
- // if the user didn't say anything and there
- // is an mbox mounted already, use that one
- // so that the upas/fs -fdefault default is honored.
- if(file
- || (singleton && access(singleton, 0)<0)
- || (!singleton && access("/mail/fs/mbox", 0)<0)){
- if(file == nil)
- file = "mbox";
+ p = strrchr(s, c);
+ if(p != nil && p > s) {
+ *p = 0;
+ return p - 1;
+ }
+ return 0;
+}
- // close current mailbox
- closemb();
- didopen = 1;
+/* sometimes opens the file (or open mbox) intended. */
+int
+switchmb(char *mb, int singleton)
+{
+ char *p, *e, pbuf[Pathlen], buf[Pathlen], file[Pathlen];
+ int fd, abs;
+ closemb();
+ abs = 0;
+ if(mb == nil)
+ mb = "mbox";
+ if(strcmp(mb, ".") == 0) /* botch */
+ mb = homewd;
+ if(*mb == '/' || strncmp(mb, "./", 2) == 0 || strncmp(mb, "../", 3) == 0){
+ snprint(file, sizeof file, "%s", mb);
+ abs = 1;
+ }else
+ snprint(file, sizeof file, "/mail/fs/%s", mb);
+ if(singleton){
+ if(chop(file, '/') == nil || (p = strrchr(file, '/')) == nil || p - file < 2){
+ eprint("!bad mbox name\n");
+ return -1;
+ }
+ mboxpathbuf(pbuf, sizeof pbuf, user, "mbox");
+ snprint(mbname, sizeof mbname, "%s", p + 1);
+ }else if(abs || access(file, 0) < 0){
fd = open("/mail/fs/ctl", ORDWR);
if(fd < 0)
sysfatal("can't open /mail/fs/ctl: %r");
-
- path = s_new();
-
- // get an absolute path to the mail box
- if(strncmp(file, "./", 2) == 0){
- // resolve path here since upas/fs doesn't know
- // our working directory
- if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
- fprint(2, "!can't get working directory: %s\n", buf);
- return -1;
- }
- s_append(path, buf);
- s_append(path, file+1);
- } else {
- mboxpath(file, user, path, 0);
- }
-
- // make up a handle to use when talking to fs
- p = strrchr(file, '/');
- if(p == nil){
- // if its in the mailbox directory, just use the name
- strncpy(mbname, file, sizeof(mbname));
- mbname[sizeof(mbname)-1] = 0;
- } else {
- // make up a mailbox name
- p = strrchr(s_to_c(path), '/');
- p++;
- if(*p == 0){
- fprint(2, "!bad mbox name");
- return -1;
- }
- strncpy(mbname, p, sizeof(mbname));
- mbname[sizeof(mbname)-1] = 0;
- n = strlen(mbname);
- if(n > Elemlen-12)
- n = Elemlen-12;
- sprint(mbname+n, "%ld", time(0));
- }
-
- if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
- fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
- s_free(path);
+ p = pbuf;
+ e = pbuf + sizeof pbuf;
+ if(abs && *file != '/')
+ seprint(p, e, "%s/%s", getwd(buf, sizeof buf), mb);
+ else if(abs)
+ seprint(p, e, "%s", mb);
+ else
+ mboxpathbuf(pbuf, sizeof pbuf, user, mb);
+ /* make up a handle to use when talking to fs */
+ if((p = strrchr(mb, '/')) == nil)
+ p = mb - 1;
+ snprint(mbname, sizeof mbname, "%s%ld", p + 1, time(0));
+ if(fprint(fd, "open %q %q", pbuf, mbname) < 0){
+ eprint("!can't open %q %q: %r\n", pbuf, mbname);
return -1;
}
close(fd);
- }else
- if (singleton && access(singleton, 0)==0
- && strncmp(singleton, "/mail/fs/", 9) == 0){
- if ((p = strchr(singleton +10, '/')) == nil){
- fprint(2, "!bad mbox name");
- return -1;
- }
- n = p-(singleton+9);
- strncpy(mbname, singleton+9, n);
- mbname[n+1] = 0;
- path = s_reset(nil);
- mboxpath(mbname, user, path, 0);
+ didopen = 1;
}else{
- path = s_reset(nil);
- mboxpath("mbox", user, path, 0);
- strcpy(mbname, "mbox");
+ mboxpathbuf(pbuf, sizeof pbuf, user, mb);
+ strcpy(mbname, mb);
}
snprint(root, sizeof root, "/mail/fs/%s", mbname);
- if(getwd(wd, sizeof(wd)) == 0)
+ if(getwd(wd, sizeof wd) == nil)
wd[0] = 0;
- if(singleton == nil && chdir(root) >= 0)
+ if(!singleton && chdir(root) >= 0)
strcpy(root, ".");
rootlen = strlen(root);
+ snprint(mbpath, sizeof mbpath, "%s", pbuf);
+ memset(&mbqid, 0, sizeof mbqid);
+ mbvers++;
- if(mbpath != nil)
- s_free(mbpath);
- mbpath = path;
return 0;
}
-// like tokenize but for into lines
-int
-lineize(char *s, char **f, int n)
+char*
+rooted(char *s)
{
- int i;
+ static char buf[Pathlen];
- for(i = 0; *s && i < n; i++){
- f[i] = s;
- s = strchr(s, '\n');
- if(s == nil)
- break;
- *s++ = 0;
- }
- return i;
-}
-
-
-
-String*
-rooted(String *s)
-{
- static char buf[256];
-
if(strcmp(root, ".") != 0)
return s;
- snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
- s_free(s);
- return s_copy(buf);
+ snprint(buf, sizeof buf, "/mail/fs/%s/%s", mbname, s);
+ return buf;
}
int
plumb(Message *m, Ctype *cp)
{
- String *s;
+ char *s;
Plumbmsg *pm;
static int fd = -2;
if(cp->plumbdest == nil)
return -1;
-
if(fd < -1)
fd = plumbopen("send", OWRITE);
if(fd < 0)
return -1;
- pm = mallocz(sizeof(Plumbmsg), 1);
+ pm = mallocz(sizeof *pm, 1);
pm->src = strdup("mail");
if(*cp->plumbdest)
pm->dst = strdup(cp->plumbdest);
- pm->wdir = nil;
pm->type = strdup("text");
pm->ndata = -1;
- s = rooted(extendpath(m->path, "body"));
- if(cp->ext != nil){
- s_append(s, ".");
- s_append(s, cp->ext);
- }
- pm->data = strdup(s_to_c(s));
- s_free(s);
+ s = rooted(extendp(m->path, "body"));
+ if(cp->ext != nil)
+ pm->data = smprint("%s.%s", s, cp->ext);
+ else
+ pm->data = strdup(s);
plumbsend(fd, pm);
plumbfree(pm);
return 0;
@@ -2499,24 +2815,11 @@
{
}
-String*
-addrecolon(char *s)
-{
- String *str;
-
- if(cistrncmp(s, "re:", 3) != 0){
- str = s_copy("Re: ");
- s_append(str, s);
- } else
- str = s_copy(s);
- return str;
-}
-
void
exitfs(char *rv)
{
if(startedfs)
unmount(nil, "/mail/fs");
- chdir("/sys/src/cmd/upas/ned"); /* for profiling? */
+ chdir(homewd); /* prof */
exits(rv);
}
--- a/sys/src/cmd/upas/pop3/mkfile
+++ b/sys/src/cmd/upas/pop3/mkfile
@@ -1,10 +1,10 @@
</$objtype/mkfile
+<../mkupas
TARG=pop3
OFILES=pop3.$O
-BIN=/$objtype/bin/upas
LIB=../common/libcommon.a$O
UPDATE=\
--- a/sys/src/cmd/upas/pop3/pop3.c
+++ b/sys/src/cmd/upas/pop3/pop3.c
@@ -96,6 +96,7 @@
int fd;
char *arg, cmdbuf[1024];
Cmd *c;
+ NetConnInfo *n;
rfork(RFNAMEG);
Binit(&in, 0, OREAD);
@@ -114,9 +115,6 @@
close(fd);
}
break;
- case 'p':
- passwordinclear = 1;
- break;
case 'r':
strecpy(tmpaddr, tmpaddr+sizeof tmpaddr, EARGF(usage()));
if(arg = strchr(tmpaddr, '!'))
@@ -130,11 +128,17 @@
exits(nil);
}
break;
+ case 'p':
+ passwordinclear = 1;
+ break;
}ARGEND
/* do before TLS */
if(peeraddr == nil)
- peeraddr = remoteaddr(0,0);
+ if(n = getnetconninfo(0, 0)){
+ peeraddr = strdup(n->rsys);
+ freenetconninfo(n);
+ }
hello();
@@ -167,7 +171,7 @@
readmbox(char *box)
{
int fd, i, n, nd, lines, pid;
- char buf[100], err[Errlen];
+ char buf[100], err[ERRMAX];
char *p;
Biobuf *b;
Dir *d, *draw;
@@ -245,9 +249,8 @@
for(;;){
p = Brdline(b, '\n');
if(p == nil){
- if((n = Blinelen(b)) == 0)
+ if(Blinelen(b) == 0)
break;
- Bseek(b, n, 1);
}else
lines++;
}
@@ -434,9 +437,8 @@
}
static int
-noopcmd(char *arg)
+noopcmd(char*)
{
- USED(arg);
sendok("");
return 0;
}
@@ -551,23 +553,21 @@
static int
stlscmd(char*)
{
- TLSconn conn;
int fd;
+ TLSconn conn;
if(didtls)
return senderr("tls already started");
if(!tlscert)
return senderr("don't have any tls credentials");
+ sendok("");
+ Bflush(&out);
+
memset(&conn, 0, sizeof conn);
+ conn.cert = tlscert;
conn.certlen = ntlscert;
- conn.cert = malloc(ntlscert);
- if(conn.cert == nil)
- return senderr("out of memory");
- memmove(conn.cert, tlscert, ntlscert);
if(debug)
conn.trace = trace;
- sendok("");
- Bflush(&out);
fd = tlsServer(0, &conn);
if(fd < 0)
sysfatal("tlsServer: %r");
@@ -574,8 +574,6 @@
dup(fd, 0);
dup(fd, 1);
close(fd);
- free(conn.cert);
- free(conn.sessionID);
Binit(&in, 0, OREAD);
Binit(&out, 1, OWRITE);
didtls = 1;
@@ -665,9 +663,9 @@
* authentication
*/
Chalstate *chs;
-char user[256];
-char box[256];
-char cbox[256];
+char user[Pathlen];
+char box[Pathlen];
+char cbox[Pathlen];
static void
hello(void)
@@ -686,6 +684,7 @@
{
char *p;
+ *user = 0;
strcpy(box, "/mail/box/");
strecpy(box+strlen(box), box+sizeof box-7, arg);
strcpy(cbox, box);
@@ -707,8 +706,11 @@
return senderr("already authenticated");
if(*arg == 0)
return senderr("USER requires argument");
- if(setuser(arg) < 0)
- return -1;
+ if(setuser(arg) < 0){
+ sleep(15*1000);
+ senderr("you are not expected to understand this"); /* pop3 attack. */
+ exits("");
+ }
return sendok("");
}
@@ -728,7 +730,7 @@
* if the address is already there and the user owns it,
* remove it and recreate it to give him a new time quanta.
*/
- if(access(buf, 0) >= 0 && remove(buf) < 0)
+ if(access(buf, 0) >= 0 && remove(buf) < 0)
return;
fd = create(buf, OREAD, 0666);
@@ -776,7 +778,7 @@
senderr("newns failed: %r; server exiting");
exits(nil);
}
- syslog(0, "pop3", "user %s logged in", user);
+
enableaddr();
if(readmbox(box) < 0)
exits(nil);
@@ -790,6 +792,12 @@
uchar digest[MD5dlen];
char response[2*MD5dlen+1];
+ if(*user == 0){
+ senderr("inscrutable phase error"); // pop3 attack.
+ sleep(15*1000);
+ exits("");
+ }
+
if(passwordinclear==0 && didtls==0)
return senderr("password in the clear disallowed");
@@ -810,8 +818,11 @@
char *resp;
resp = nextarg(arg);
- if(setuser(arg) < 0)
- return -1;
+ if(setuser(arg) < 0){
+ senderr("i before e except after c"); // pop3 attack.
+ sleep(15*1000);
+ exits("");
+ }
return dologin(resp);
}
--- a/sys/src/cmd/upas/q/mkfile
+++ b/sys/src/cmd/upas/q/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG = qer\
runq\
@@ -9,8 +10,6 @@
../common/sys.h\
LIB=../common/libcommon.a$O\
-
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
--- a/sys/src/cmd/upas/q/qer.c
+++ b/sys/src/cmd/upas/q/qer.c
@@ -23,7 +23,7 @@
void
error(char *f, char *a)
{
- char err[Errlen+1];
+ char err[ERRMAX];
char buf[256];
rerrstr(err, sizeof(err));
--- a/sys/src/cmd/upas/q/runq.c
+++ b/sys/src/cmd/upas/q/runq.c
@@ -136,7 +136,7 @@
if(debug)
fprint(2, "removing directory %s\n", name);
syslog(0, runqlog, "rmdir %s", name);
- sysremove(name);
+ remove(name);
return 1;
}
return 0;
@@ -184,7 +184,7 @@
warning("reading %s", root);
return;
}
- n = sysdirreadall(fd, &db);
+ n = dirreadall(fd, &db);
if(n > 0){
for(i=0; i<n; i++){
if(db[i].qid.type & QTDIR){
@@ -245,7 +245,7 @@
warning("reading %s", name);
return;
}
- nfiles = sysdirreadall(fd, &dirbuf);
+ nfiles = dirreadall(fd, &dirbuf);
if(nfiles > 0){
for(i=0; i<nfiles; i++){
if(dirbuf[i].name[0]!='C' || dirbuf[i].name[1]!='.')
@@ -272,13 +272,13 @@
for(i=0; i<nfiles; i++){
if(strcmp(&dirbuf[i].name[1], &name[1]) == 0)
- sysremove(dirbuf[i].name);
+ remove(dirbuf[i].name);
}
/* error file (may have) appeared after we read the directory */
/* stomp on data file in case of phase error */
- sysremove(file(name, 'D'));
- sysremove(file(name, 'E'));
+ remove(file(name, 'D'));
+ remove(file(name, 'E'));
}
/*
@@ -295,8 +295,7 @@
if(l == 0)
return 0;
l->fd = fd;
- l->name = s_new();
- s_append(l->name, path);
+ snprint(l->name, sizeof l->name, "%s", path);
/* fork process to keep lock alive until sysunlock(l) */
switch(l->pid = rfork(RFPROC)){
@@ -356,11 +355,7 @@
if(!Eflag && (d = dirstat(file(dp->name, 'E'))) != nil){
etime = d->mtime;
free(d);
- if(etime - dtime < 15*60){
- /* up to the first 15 minutes, every 30 seconds */
- if(time(0) - etime < 30)
- return;
- } else if(etime - dtime < 60*60){
+ if(etime - dtime < 60*60){
/* up to the first hour, try every 15 minutes */
if(time(0) - etime < 15*60)
return;
@@ -517,11 +512,7 @@
if(wm->msg[0]){
if(debug)
fprint(2, "[%d] wm->msg == %s\n", getpid(), wm->msg);
- syslog(0, runqlog, "message: %s", wm->msg);
- if(strstr(wm->msg, "Ignore") != nil){
- /* fix for fish/chips, leave message alone */
- logit("ignoring", dp->name, av);
- }else if(!Rflag && strstr(wm->msg, "Retry")==0){
+ if(!Rflag && strstr(wm->msg, "Retry")==0){
/* return the message and remove it */
if(returnmail(av, dp->name, wm->msg) != 0)
logit("returnmail failed", dp->name, av);
@@ -572,15 +563,11 @@
int
returnmail(char **av, char *name, char *msg)
{
- int pfd[2];
- Waitmsg *wm;
- int fd;
- char buf[256];
- char attachment[256];
- int i;
+ char buf[256], attachment[Pathlen], *sender;
+ int i, fd, pfd[2];
long n;
+ Waitmsg *wm;
String *s;
- char *sender;
if(av[1] == 0 || av[2] == 0){
logit("runq - dumping bad file", name, av);
@@ -665,7 +652,7 @@
void
warning(char *f, void *a)
{
- char err[65];
+ char err[ERRMAX];
char buf[256];
rerrstr(err, sizeof(err));
@@ -679,7 +666,7 @@
void
error(char *f, void *a)
{
- char err[Errlen];
+ char err[ERRMAX];
char buf[256];
rerrstr(err, sizeof(err));
--- /dev/null
+++ b/sys/src/cmd/upas/qfrom/mkfile
@@ -1,0 +1,13 @@
+</$objtype/mkfile
+
+TARG=qfrom
+OFILES=qfrom.$O\
+
+BIN=/$objtype/bin/upas
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/cmd/upas/qfrom/qfrom.c
@@ -1,0 +1,63 @@
+/*
+ * quote from lines without messing with character encoding.
+ * (might rather just undo the character encoding and use sed.)
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+void
+qfrom(int fd)
+{
+ Biobuf b, bo;
+ char *s;
+ int l;
+
+ if(Binit(&b, fd, OREAD) == -1)
+ sysfatal("Binit: %r");
+ if(Binit(&bo, 1, OWRITE) == -1)
+ sysfatal("Binit: %r");
+
+ while(s = Brdstr(&b, '\n', 0)){
+ l = Blinelen(&b);
+ if(l >= 5)
+ if(memcmp(s, "From ", 5) == 0)
+ Bputc(&bo, ' ');
+ Bwrite(&bo, s, l);
+ free(s);
+ }
+ Bterm(&b);
+ Bterm(&bo);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: qfrom [files...]\n");
+ exits("");
+}
+
+void
+main(int argc, char **argv)
+{
+ int fd;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(*argv == 0){
+ qfrom(0);
+ exits("");
+ }
+ for(; *argv; argv++){
+ fd = open(*argv, OREAD);
+ if(fd == -1)
+ sysfatal("open: %r");
+ qfrom(fd);
+ close(fd);
+ }
+ exits("");
+}
--- a/sys/src/cmd/upas/scanmail/mkfile
+++ b/sys/src/cmd/upas/scanmail/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=scanmail\
testscan
@@ -10,7 +11,6 @@
LIB= ../common/libcommon.a$O\
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
$HFILES\
@@ -21,4 +21,4 @@
CFLAGS=$CFLAGS -I../common
scanmail.$O: scanmail.c
- $CC $CFLAGS -D'SPOOL="/mail"' scanmail.c
+ $CC $CFLAGS scanmail.c
--- a/sys/src/cmd/upas/scanmail/scanmail.c
+++ b/sys/src/cmd/upas/scanmail/scanmail.c
@@ -1,4 +1,5 @@
#include "common.h"
+#include <regexp.h>
#include "spam.h"
int cflag;
@@ -35,7 +36,7 @@
void
usage(void)
{
- fprint(2, "missing or bad arguments to qer\n");
+ fprint(2, "usage: scanmail [-cdhnstv] [-p pattern] [-q queuename] sender dest sys\n");
exits("usage");
}
@@ -51,8 +52,8 @@
void *p;
p = malloc(n);
- if(p == 0)
- exits("malloc");
+ if(p == nil)
+ sysfatal("malloc: %r");
return p;
}
@@ -60,8 +61,9 @@
Realloc(void *p, ulong n)
{
p = realloc(p, n);
- if(p == 0)
- exits("realloc");
+ if(p == nil)
+ sysfatal("malloc: %r");
+ setrealloctag(p, getcallerpc(&p));
return p;
}
@@ -76,10 +78,10 @@
optout = 1;
a = args = Malloc((argc+1)*sizeof(char*));
- sprint(patfile, "%s/patterns", UPASLIB);
- sprint(linefile, "%s/lines", UPASLOG);
- sprint(holdqueue, "%s/queue.hold", SPOOL);
- sprint(copydir, "%s/copy", SPOOL);
+ snprint(patfile, sizeof patfile, "%s/patterns", UPASLIB);
+ snprint(linefile, sizeof linefile, "%s/lines", UPASLOG);
+ snprint(holdqueue, sizeof holdqueue, "%s/queue.hold", SPOOL);
+ snprint(copydir, sizeof copydir, "%s/copy", SPOOL);
*a++ = argv[0];
for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){
@@ -142,7 +144,7 @@
qdir = a;
sender = argv[2];
- /* copy the rest of argv, acummulating the recipients as we go */
+ /* copy the rest of argv, acummulating the recipients as we go */
for(i = 0; argv[i]; i++){
*a++ = argv[i];
if(i < 4) /* skip queue, 'mail', sender, dest sys */
@@ -161,17 +163,17 @@
optout = 0;
}
*a = 0;
- /* construct a command string for matching */
+ /* construct a command string for matching */
snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips));
cmd[sizeof(cmd)-1] = 0;
for(cp = cmd; *cp; cp++)
*cp = tolower(*cp);
- /* canonicalize a copy of the header and body.
- * buf points to orginal message and n contains
- * number of bytes of original message read during
- * canonicalization.
- */
+ /* canonicalize a copy of the header and body.
+ * buf points to orginal message and n contains
+ * number of bytes of original message read during
+ * canonicalization.
+ */
*body = 0;
*header = 0;
buf = canon(&bin, header+1, body+1, &n);
@@ -178,7 +180,7 @@
if (buf == 0)
exits("read");
- /* if all users opt out, don't try matches */
+ /* if all users opt out, don't try matches */
if(optout){
if(cflag)
cout = opencopy(sender);
@@ -185,17 +187,17 @@
exits(qmail(args, buf, n, cout));
}
- /* Turn off line logging, if command line matches */
+ /* Turn off line logging, if command line matches */
nolines = matchaction(Lineoff, cmd, match);
for(i = 0; patterns[i].action; i++){
- /* Lineoff patterns were already done above */
+ /* Lineoff patterns were already done above */
if(i == Lineoff)
continue;
- /* don't apply "Line" patterns if excluded above */
+ /* don't apply "Line" patterns if excluded above */
if(nolines && i == SaveLine)
continue;
- /* apply patterns to the sender/recips, header and body */
+ /* apply patterns to the sender/recips, header and body */
if(matchaction(i, cmd, match))
break;
if(matchaction(i, header+1, match))
@@ -407,9 +409,9 @@
cp[7] = 0;
cp[10] = 0;
if(cp[8] == ' ')
- sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
+ snprint(buf, sizeof buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
else
- sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
+ snprint(buf, sizeof buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
cp = buf+strlen(buf);
if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){
syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender);
@@ -421,7 +423,7 @@
h = h*257 + *sender++;
for(i = 0; i < 50; i++){
h += lrand();
- sprint(cp, "/%lud", h);
+ seprint(cp, buf+sizeof buf, "/%lud", h);
b = sysopen(buf, "wlc", 0644);
if(b){
if(vflag)
@@ -445,7 +447,7 @@
h = h*257 + *sender++;
for(i = 0; i < 50; i++){
h += lrand();
- sprint(buf, "%s/%lud", copydir, h);
+ snprint(buf, sizeof buf, "%s/%lud", copydir, h);
b = sysopen(buf, "wlc", 0600);
if(b)
return b;
--- a/sys/src/cmd/upas/scanmail/spam.h
+++ b/sys/src/cmd/upas/scanmail/spam.h
@@ -1,4 +1,3 @@
-
enum{
Dump = 0, /* Actions must be in order of descending importance */
HoldHeader,
--- a/sys/src/cmd/upas/scanmail/testscan.c
+++ b/sys/src/cmd/upas/scanmail/testscan.c
@@ -1,4 +1,5 @@
-#include "sys.h"
+#include "common.h"
+#include <regexp.h>
#include "spam.h"
int debug;
@@ -13,7 +14,7 @@
void
usage(void)
{
- fprint(2, "missing or bad arguments to qer\n");
+ fprint(2, "usage: testscan -avd [-p pattern] ...\n");
exits("usage");
}
@@ -23,10 +24,9 @@
void *p;
p = malloc(n);
- if(p == 0){
- fprint(2, "malloc error");
- exits("malloc");
- }
+ if(p == nil)
+ sysfatal("malloc: %r");
+ setmalloctag(p, getcallerpc(&n));
return p;
}
@@ -34,10 +34,9 @@
Realloc(void *p, ulong n)
{
p = realloc(p, n);
- if(p == 0){
- fprint(2, "realloc error");
- exits("realloc");
- }
+ if(p == nil)
+ sysfatal("malloc: %r");
+ setrealloctag(p, getcallerpc(&p));
return p;
}
@@ -79,7 +78,7 @@
char body[Bodysize+2], *raw, *ret;
Biobuf *bp;
- sprint(patfile, "%s/patterns", UPASLIB);
+ snprint(patfile, sizeof patfile, "%s/patterns", UPASLIB);
aflag = -1;
vflag = 0;
ARGBEGIN {
@@ -93,7 +92,7 @@
debug++;
break;
case 'p':
- strcpy(patfile,ARGF());
+ snprint(patfile, sizeof patfile, "%s", EARGF(usage()));
break;
} ARGEND
--- a/sys/src/cmd/upas/send/authorize.c
+++ b/sys/src/cmd/upas/send/authorize.c
@@ -12,7 +12,7 @@
String *errstr;
dp->authorized = 1;
- pp = proc_start(s_to_c(dp->repl1), (stream *)0, (stream *)0, outstream(), 1, 0);
+ pp = proc_start(s_to_c(dp->repl1), 0, 0, outstream(), 1, 0);
if (pp == 0){
dp->status = d_noforward;
return;
--- a/sys/src/cmd/upas/send/bind.c
+++ b/sys/src/cmd/upas/send/bind.c
@@ -1,20 +1,34 @@
#include "common.h"
#include "send.h"
-static int forward_loop(char *, char *);
+/* Return TRUE if a forwarding loop exists, i.e., the String `system'
+ * is found more than 4 times in the return address.
+ */
+static int
+forward_loop(char *addr, char *system)
+{
+ int len, found;
+ found = 0;
+ len = strlen(system);
+ while(addr = strchr(addr, '!'))
+ if (!strncmp(++addr, system, len)
+ && addr[len] == '!' && ++found == 4)
+ return 1;
+ return 0;
+}
+
+
/* bind the destinations to the commands to be executed */
-extern dest *
+dest *
up_bind(dest *destp, message *mp, int checkforward)
{
- dest *list[2]; /* lists of unbound destinations */
- int li; /* index into list[2] */
- dest *bound=0; /* bound destinations */
- dest *dp;
- int i;
+ int i, li;
+ dest *list[2], *bound, *dp;
+ bound = nil;
list[0] = destp;
- list[1] = 0;
+ list[1] = nil;
/*
* loop once to check for:
@@ -24,7 +38,7 @@
* - characters that need escaping
*/
for (dp = d_rm(&list[0]); dp != 0; dp = d_rm(&list[0])) {
- if (!checkforward)
+ if(!checkforward)
dp->authorized = 1;
dp->addr = escapespecial(dp->addr);
if (forward_loop(s_to_c(dp->addr), thissys)) {
@@ -115,19 +129,3 @@
return bound;
}
-
-/* Return TRUE if a forwarding loop exists, i.e., the String `system'
- * is found more than 4 times in the return address.
- */
-static int
-forward_loop(char *addr, char *system)
-{
- int len = strlen(system), found = 0;
-
- while (addr = strchr(addr, '!'))
- if (!strncmp(++addr, system, len)
- && addr[len] == '!' && ++found == 4)
- return 1;
- return 0;
-}
-
--- a/sys/src/cmd/upas/send/cat_mail.c
+++ b/sys/src/cmd/upas/send/cat_mail.c
@@ -1,57 +1,121 @@
#include "common.h"
#include "send.h"
+/*
+ * warning will robinson
+ *
+ * mbox and mdir should likely be merged with ../common/folder.c
+ * at a minimum, changes need to done in sync.
+ */
-/* dispose of local addresses */
-int
-cat_mail(dest *dp, message *mp)
+static int
+mbox(dest *dp, message *mp, char *s)
{
- Biobuf *fp;
- char *rcvr, *cp;
+ char *tmp;
+ int i, n, e;
+ Biobuf *b;
Mlock *l;
- String *tmp, *s;
- int i, n;
- s = unescapespecial(s_clone(dp->repl1));
- if (nflg) {
- if(!xflg)
- print("cat >> %s\n", s_to_c(s));
- else
- print("%s\n", s_to_c(dp->addr));
- s_free(s);
- return 0;
- }
for(i = 0;; i++){
- l = syslock(s_to_c(s));
+ l = syslock(s);
if(l == 0)
return refuse(dp, mp, "can't lock mail file", 0, 0);
-
- fp = sysopen(s_to_c(s), "al", MBOXMODE);
- if(fp)
+ b = sysopen(s, "al", Mboxmode);
+ if(b)
break;
- tmp = s_append(0, s_to_c(s));
- s_append(tmp, ".tmp");
- fp = sysopen(s_to_c(tmp), "al", MBOXMODE);
- if(fp){
- syslog(0, "mail", "error: used %s", s_to_c(tmp));
- s_free(tmp);
+ b = sysopen(tmp = smprint("%s.tmp", s), "al", Mboxmode);
+ free(tmp);
+ sysunlock(l);
+ if(b){
+ syslog(0, "mail", "error: used %s.tmp", s);
break;
}
- s_free(tmp);
- sysunlock(l);
if(i >= 5)
return refuse(dp, mp, "mail file cannot be opened", 0, 0);
sleep(1000);
}
- s_free(s);
- n = m_print(mp, fp, (char *)0, 1);
- if (Bprint(fp, "\n") < 0 || Bflush(fp) < 0 || n < 0){
- sysclose(fp);
- sysunlock(l);
+ e = 0;
+ n = m_print(mp, b, 0, 1);
+ if(n == -1 || Bprint(b, "\n") == -1 || Bflush(b) == -1)
+ e = 1;
+ sysclose(b);
+ sysunlock(l);
+ if(e)
return refuse(dp, mp, "error writing mail file", 0, 0);
+ return 0;
+}
+
+static int
+mdir(dest *dp, message *mp, char *s)
+{
+ char buf[100];
+ int fd, i, n, e;
+ ulong t;
+ Biobuf b;
+
+ t = time(0);
+ for(i = 0; i < 100; i++){
+ snprint(buf, sizeof buf, "%s/%lud.%.2d", s, t, i);
+ if((fd = create(buf, OWRITE|OEXCL, DMAPPEND|0660)) != -1)
+ goto found;
}
- sysclose(fp);
- sysunlock(l);
+ return refuse(dp, mp, "mdir file cannot be opened", 0, 0);
+found:
+ e = 0;
+ Binit(&b, fd, OWRITE);
+ n = m_print(mp, &b, 0, 1);
+ if(n == -1 || Bprint(&b, "\n") == -1 || Bflush(&b) == -1)
+ e = 1;
+ Bterm(&b);
+ close(fd);
+ if(e){
+ remove(buf);
+ return refuse(dp, mp, "error writing mail file", 0, 0);
+ }
+ return 0;
+}
+
+/* dispose of local addresses */
+int
+cat_mail(dest *dp, message *mp)
+{
+ char *rcvr, *cp, *s;
+ int e, isdir;
+ Dir *d;
+ String *ss;
+
+ ss = unescapespecial(s_clone(dp->repl1));
+ s = s_to_c(ss);
+ if (flagn) {
+ if(!flagx)
+ print("upas/mbappend %s\n", s);
+ else
+ print("%s\n", s_to_c(dp->addr));
+ s_free(ss);
+ return 0;
+ }
+ /* avoid lock errors */
+ if(strcmp(s, "/dev/null") == 0){
+ s_free(ss);
+ return(0);
+ }
+ if(d = dirstat(s)){
+ isdir = d->mode&DMDIR;
+ free(d);
+ }else{
+ isdir = create(s, OREAD, DMDIR|0777);
+ if(isdir == -1)
+ return refuse(dp, mp, "mdir cannot be created", 0, 0);
+ close(isdir);
+ isdir = 1;
+ }
+ if(isdir)
+ e = mdir(dp, mp, s);
+ else
+ e = mbox(dp, mp, s);
+ s_free(ss);
+ if(e != 0)
+ return e;
rcvr = s_to_c(dp->addr);
if(cp = strrchr(rcvr, '!'))
rcvr = cp+1;
--- a/sys/src/cmd/upas/send/dest.c
+++ b/sys/src/cmd/upas/send/dest.c
@@ -1,21 +1,17 @@
#include "common.h"
#include "send.h"
-static String* s_parseq(String*, String*);
-
/* exports */
dest *dlist;
-extern dest*
+dest*
d_new(String *addr)
{
dest *dp;
dp = (dest *)mallocz(sizeof(dest), 1);
- if (dp == 0) {
- perror("d_new");
- exit(1);
- }
+ if (dp == 0)
+ sysfatal("malloc: %r");
dp->same = dp;
dp->nsame = 1;
dp->nchar = 0;
@@ -27,7 +23,7 @@
return dp;
}
-extern void
+void
d_free(dest *dp)
{
if (dp != 0) {
@@ -46,7 +42,7 @@
*/
/* Get first element from a circular list linked via 'next'. */
-extern dest *
+dest*
d_rm(dest **listp)
{
dest *dp;
@@ -63,7 +59,7 @@
}
/* Insert a new entry at the end of the list linked via 'next'. */
-extern void
+void
d_insert(dest **listp, dest *new)
{
dest *head;
@@ -82,7 +78,7 @@
}
/* Get first element from a circular list linked via 'same'. */
-extern dest *
+dest*
d_rm_same(dest **listp)
{
dest *dp;
@@ -114,10 +110,11 @@
return 0;
}
-/* Insert an entry into the corresponding list linked by 'same'. Note that
+/*
+ * Insert an entry into the corresponding list linked by 'same'. Note that
* the basic structure is a list of lists.
*/
-extern void
+void
d_same_insert(dest **listp, dest *new)
{
dest *dp;
@@ -124,7 +121,9 @@
int len;
if(new->status == d_pipe || new->status == d_cat) {
- len = new->repl2 ? strlen(s_to_c(new->repl2)) : 0;
+ len = 0;
+ if(new->repl2)
+ len = strlen(s_to_c(new->repl2));
if(*listp != 0){
dp = (*listp)->next;
do {
@@ -146,7 +145,10 @@
dp = dp->next;
} while (dp != (*listp)->next);
}
- new->nchar = strlen(s_to_c(new->repl1)) + len + 1;
+ if(s_to_c(new->repl1))
+ new->nchar = strlen(s_to_c(new->repl1)) + len + 1;
+ else
+ new->nchar = 0;
}
new->next = new;
d_insert(listp, new);
@@ -157,7 +159,7 @@
* The local! and !local! checks are artificial intelligence,
* there should be a better way.
*/
-extern String*
+String*
d_to(dest *list)
{
dest *np, *sp;
@@ -197,34 +199,7 @@
return unescapespecial(s);
}
-/* expand a String of destinations into a linked list of destiniations */
-extern dest *
-s_to_dest(String *sp, dest *parent)
-{
- String *addr;
- dest *list=0;
- dest *new;
- if (sp == 0)
- return 0;
- addr = s_new();
- while (s_parseq(sp, addr)!=0) {
- addr = escapespecial(addr);
- if(shellchars(s_to_c(addr))){
- while(new = d_rm(&list))
- d_free(new);
- break;
- }
- new = d_new(addr);
- new->parent = parent;
- new->authorized = parent->authorized;
- d_insert(&list, new);
- addr = s_new();
- }
- s_free(addr);
- return list;
-}
-
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
/* Get the next field from a String. The field is delimited by white space.
@@ -256,4 +231,32 @@
from->ptr++;
return to;
+}
+
+/* expand a String of destinations into a linked list of destiniations */
+dest*
+s_to_dest(String *sp, dest *parent)
+{
+ String *addr;
+ dest *list=0;
+ dest *new;
+
+ if (sp == 0)
+ return 0;
+ addr = s_new();
+ while (s_parseq(sp, addr)!=0) {
+ addr = escapespecial(addr);
+ if(shellchars(s_to_c(addr))){
+ while(new = d_rm(&list))
+ d_free(new);
+ break;
+ }
+ new = d_new(addr);
+ new->parent = parent;
+ new->authorized = parent->authorized;
+ d_insert(&list, new);
+ addr = s_new();
+ }
+ s_free(addr);
+ return list;
}
--- a/sys/src/cmd/upas/send/filter.c
+++ b/sys/src/cmd/upas/send/filter.c
@@ -1,16 +1,40 @@
#include "common.h"
#include "send.h"
+#include <regexp.h>
Biobuf bin;
-int rmail, tflg;
-char *subjectarg;
+int flagn;
+int flagx;
+int rmail;
+int tflg;
+char *subjectarg;
-char *findbody(char*);
+char*
+findbody(char *p)
+{
+ if(*p == '\n')
+ return p;
+ while(*p){
+ if(*p == '\n' && *(p+1) == '\n')
+ return p+1;
+ p++;
+ }
+ return p;
+}
+
+int
+refuse(dest*, message *, char *cp, int, int)
+{
+ fprint(2, "%s", cp);
+ exits("error");
+ return 0;
+}
+
void
usage(void)
{
- fprint(2, "usage: upas/filter [-bh] rcvr mailbox [regexp file] ...\n");
+ fprint(2, "usage: upas/filter [-nbh] rcvr mailbox [regexp file] ...\n");
exits("usage");
}
@@ -17,20 +41,18 @@
void
main(int argc, char *argv[])
{
+ char *cp, file[Pathlen];
+ int i, header, body;
message *mp;
dest *dp;
Reprog *p;
Resub match[10];
- char file[MAXPATHLEN];
- Biobuf *fp;
- char *rcvr, *cp;
- Mlock *l;
- String *tmp;
- int i;
- int header, body;
header = body = 0;
ARGBEGIN {
+ case 'n':
+ flagn = 1;
+ break;
case 'h':
header = 1;
break;
@@ -54,7 +76,6 @@
mp->sender = s_copy(cp);
}
- dp = d_new(s_copy(argv[0]));
strecpy(file, file+sizeof file, argv[1]);
cp = findbody(s_to_c(mp->body));
for(i = 2; i < argc; i += 2){
@@ -74,62 +95,9 @@
break;
}
}
-
- /*
- * always lock the normal mail file to avoid too many lock files
- * lying about. This isn't right but it's what the majority prefers.
- */
- l = syslock(argv[1]);
- if(l == 0){
- fprint(2, "can't lock mail file %s\n", argv[1]);
- exit(1);
- }
-
- /*
- * open the destination mail file
- */
- fp = sysopen(file, "ca", MBOXMODE);
- if (fp == 0){
- tmp = s_append(0, file);
- s_append(tmp, ".tmp");
- fp = sysopen(s_to_c(tmp), "cal", MBOXMODE);
- if(fp == 0){
- sysunlock(l);
- fprint(2, "can't open mail file %s\n", file);
- exit(1);
- }
- syslog(0, "mail", "error: used %s", s_to_c(tmp));
- s_free(tmp);
- }
- Bseek(fp, 0, 2);
- if(m_print(mp, fp, (char *)0, 1) < 0
- || Bprint(fp, "\n") < 0
- || Bflush(fp) < 0){
- sysclose(fp);
- sysunlock(l);
- fprint(2, "can't write mail file %s\n", file);
- exit(1);
- }
- sysclose(fp);
-
- sysunlock(l);
- rcvr = argv[0];
- if(cp = strrchr(rcvr, '!'))
- rcvr = cp+1;
- logdelivery(dp, rcvr, mp);
- exit(0);
-}
-
-char*
-findbody(char *p)
-{
- if(*p == '\n')
- return p;
-
- while(*p){
- if(*p == '\n' && *(p+1) == '\n')
- return p+1;
- p++;
- }
- return p;
+ dp = d_new(s_copy(argv[0]));
+ dp->repl1 = s_copy(file);
+ if(cat_mail(dp, mp) != 0)
+ exits("fail");
+ exits("");
}
--- a/sys/src/cmd/upas/send/gateway.c
+++ b/sys/src/cmd/upas/send/gateway.c
@@ -1,13 +1,11 @@
#include "common.h"
#include "send.h"
-#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
-
/*
* Translate the last component of the sender address. If the translation
* yields the same address, replace the sender with its last component.
*/
-extern void
+void
gateway(message *mp)
{
char *base;
--- a/sys/src/cmd/upas/send/local.c
+++ b/sys/src/cmd/upas/send/local.c
@@ -1,12 +1,21 @@
#include "common.h"
#include "send.h"
+static String*
+mboxpath(char *path, char *user, String *to)
+{
+ char buf[Pathlen];
+
+ mboxpathbuf(buf, sizeof buf, user, path);
+ return s_append(to, buf);
+}
+
static void
mboxfile(dest *dp, String *user, String *path, char *file)
{
char *cp;
- mboxpath(s_to_c(user), s_to_c(dp->addr), path, 0);
+ mboxpath(s_to_c(user), s_to_c(dp->addr), path);
cp = strrchr(s_to_c(path), '/');
if(cp)
path->ptr = cp+1;
@@ -16,6 +25,52 @@
}
/*
+ * BOTCH, BOTCH
+ * the problem is that we don't want to say a user exists
+ * just because the user has a mail box directory. that
+ * precludes using mode bits to disable mailboxes.
+ *
+ * botch #1: we pretend like we know that there must be a
+ * corresponding file or directory /mail/box/$user[/folder]/mbox
+ * this is wrong, but we get away with this for local mail boxes.
+ *
+ * botch #2: since the file server and not the auth server owns
+ * groups, it's not possible to get groups right. this means that
+ * a mailbox that only allows members of a group to post but
+ * not read wouldn't work.
+ */
+static uint accesstx[] = {
+[OREAD] 1<<2,
+[OWRITE] 1<<1,
+[ORDWR] 3<<1,
+[OEXEC] 1<<0
+};
+
+static int
+accessmbox(char *f, int m)
+{
+ int r, n;
+ Dir *d;
+
+ d = dirstat(f);
+ if(d == nil)
+ return -1;
+ n = 0;
+ if(m < nelem(accesstx))
+ n = accesstx[m];
+ if(d->mode & DMDIR)
+ n |= OEXEC;
+ r = (d->mode & n<<0) == n<<0;
+// if(r == 0 && inlist(mygids(), d->gid) == 0)
+// r = (d->mode & n<<3) == n<<3;
+ if(r == 0 && strcmp(getlog(), d->uid) == 0)
+ r = (d->mode & n<<6) == n<<6;
+ r--;
+ free(d);
+ return r;
+}
+
+/*
* Check forwarding requests
*/
extern dest*
@@ -43,7 +98,7 @@
/* if no replacement string, plug in user's name */
if(dp->repl1 == 0){
dp->repl1 = s_new();
- mboxname(user, dp->repl1);
+ mboxpath("mbox", user, dp->repl1);
}
s = unescapespecial(s_clone(dp->repl1));
@@ -95,7 +150,7 @@
* name passes through a shell. tdb.
*/
mboxfile(dp, dp->repl1, s_reset(file), "pipeto");
- if(sysexist(s_to_c(file))){
+ if(access(s_to_c(file), AEXEC) == 0){
if(debug)
fprint(2, "found a pipeto file\n");
dp->status = d_pipeto;
@@ -118,8 +173,8 @@
/*
* see if the mailbox directory exists
*/
- mboxfile(dp, s, s_reset(file), ".");
- if(sysexist(s_to_c(file)))
+ mboxfile(dp, s, s_reset(file), "mbox");
+ if(accessmbox(s_to_c(file), OWRITE) != -1)
dp->status = d_cat;
else
dp->status = d_unknown;
--- a/sys/src/cmd/upas/send/log.c
+++ b/sys/src/cmd/upas/send/log.c
@@ -1,11 +1,8 @@
#include "common.h"
#include "send.h"
-/* configuration */
-#define LOGBiobuf "log/status"
-
/* log mail delivery */
-extern void
+void
logdelivery(dest *list, char *rcvr, message *mp)
{
dest *parent;
@@ -29,7 +26,7 @@
}
/* log mail forwarding */
-extern void
+void
loglist(dest *list, message *mp, char *tag)
{
dest *next;
@@ -57,7 +54,7 @@
}
/* log a mail refusal */
-extern void
+void
logrefusal(dest *dp, message *mp, char *msg)
{
char buf[2048];
@@ -67,7 +64,7 @@
srcvr = unescapespecial(s_clone(dp->addr));
sender = unescapespecial(s_clone(mp->sender));
- sprint(buf, "error %.256s From %.256s %.256s\nerror+ ", s_to_c(srcvr),
+ snprint(buf, sizeof buf, "error %.256s From %.256s %.256s\nerror+ ", s_to_c(srcvr),
s_to_c(sender), s_to_c(mp->date));
s_free(srcvr);
s_free(sender);
--- a/sys/src/cmd/upas/send/main.c
+++ b/sys/src/cmd/upas/send/main.c
@@ -2,40 +2,37 @@
#include "send.h"
/* globals to all files */
-int rmail;
-char *thissys, *altthissys;
-int nflg;
-int xflg;
-int debug;
-int rflg;
-int iflg = 1;
-int nosummary;
+int flagn;
+int flagx;
+int debug;
+int flagi = 1;
+int rmail;
+int nosummary;
+char *thissys;
+char *altthissys;
/* global to this file */
-static String *errstring;
-static message *mp;
-static int interrupt;
-static int savemail;
-static Biobuf in;
-static int forked;
-static int add822headers = 1;
-static String *arglist;
+static String *errstring;
+static message *mp;
+static int interrupt;
+static int savemail;
+static Biobuf in;
+static int forked;
+static int add822headers = 1;
+static String *arglist;
/* predeclared */
-static int send(dest *, message *, int);
-static void lesstedious(void);
-static void save_mail(message *);
-static int complain_mail(dest *, message *);
-static int pipe_mail(dest *, message *);
-static void appaddr(String *, dest *);
-static void mkerrstring(String *, message *, dest *, dest *, char *, int);
-static int replymsg(String *, message *, dest *);
-static int catchint(void*, char*);
+static int send(dest*, message*, int);
+static void lesstedious(void);
+static void save_mail(message*);
+static int complain_mail(dest*, message*);
+static int pipe_mail(dest*, message*);
+static int catchint(void*, char*);
void
usage(void)
{
- fprint(2, "usage: mail [-birtx] list-of-addresses\n");
+ fprint(2, "usage: send [-#bdirx] list-of-addresses\n");
exits("usage");
}
@@ -42,54 +39,45 @@
void
main(int argc, char *argv[])
{
- dest *dp=0;
- int checkforward;
- char *base;
int rv;
+ dest *dp;
- /* process args */
ARGBEGIN{
case '#':
- nflg = 1;
+ flagn = 1;
break;
case 'b':
add822headers = 0;
break;
- case 'x':
- nflg = 1;
- xflg = 1;
- break;
case 'd':
debug = 1;
break;
case 'i':
- iflg = 0;
+ flagi = 0;
break;
case 'r':
- rflg = 1;
+ rmail++;
break;
+ case 'x':
+ flagn = 1;
+ flagx = 1;
+ break;
default:
usage();
}ARGEND
- while(*argv){
+ if(*argv == 0)
+ usage();
+ dp = 0;
+ for(; *argv; argv++){
if(shellchars(*argv)){
fprint(2, "illegal characters in destination\n");
exits("syntax");
}
- d_insert(&dp, d_new(s_copy(*argv++)));
+ d_insert(&dp, d_new(s_copy(*argv)));
}
-
- if (dp == 0)
- usage();
arglist = d_to(dp);
- /*
- * get context:
- * - whether we're rmail or mail
- */
- base = basename(argv0);
- checkforward = rmail = (strcmp(base, "rmail")==0) | rflg;
thissys = sysname_read();
altthissys = alt_sysname_read();
if(rmail)
@@ -99,22 +87,22 @@
* read the mail. If an interrupt occurs while reading, save in
* dead.letter
*/
- if (!nflg) {
+ if (!flagn) {
Binit(&in, 0, OREAD);
if(!rmail)
atnotify(catchint, 1);
- mp = m_read(&in, rmail, !iflg);
+ mp = m_read(&in, rmail, !flagi);
if (mp == 0)
- exit(0);
+ exits(0);
if (interrupt != 0) {
save_mail(mp);
- exit(1);
+ exits("interrupt");
}
} else {
mp = m_new();
if(default_from(mp) < 0){
fprint(2, "%s: can't determine login name\n", argv0);
- exit(1);
+ exits("fail");
}
}
errstring = s_new();
@@ -132,7 +120,7 @@
* security reasons.
*/
mp->sender = escapespecial(mp->sender);
- if (shellchars(s_to_c(mp->sender)))
+ if(shellchars(s_to_c(mp->sender)))
mp->replyaddr = s_copy("postmaster");
else
mp->replyaddr = s_clone(mp->sender);
@@ -141,7 +129,7 @@
* reject messages that have been looping for too long
*/
if(mp->received > 32)
- exit(refuse(dp, mp, "possible forward loop", 0, 0));
+ exits(refuse(dp, mp, "possible forward loop", 0, 0)? "refuse": "");
/*
* reject messages that are too long. We don't do it earlier
@@ -148,14 +136,14 @@
* in m_read since we haven't set up enough things yet.
*/
if(mp->size < 0)
- exit(refuse(dp, mp, "message too long", 0, 0));
+ exits(refuse(dp, mp, "message too long", 0, 0)? "refuse": "");
- rv = send(dp, mp, checkforward);
+ rv = send(dp, mp, rmail);
if(savemail)
save_mail(mp);
if(mp)
m_free(mp);
- exit(rv);
+ exits(rv? "fail": "");
}
/* send a message to a list of sites */
@@ -183,10 +171,7 @@
break;
case d_pipeto:
case d_pipe:
- if (!rmail && !nflg && !forked) {
- forked = 1;
- lesstedious();
- }
+ lesstedious();
errors += pipe_mail(dp, mp);
break;
default:
@@ -206,7 +191,8 @@
if(debug)
return;
-
+ if(rmail || flagn || forked)
+ return;
switch(fork()){
case -1:
break;
@@ -215,9 +201,10 @@
for(i=0; i<3; i++)
close(i);
savemail = 0;
+ forked = 1;
break;
default:
- exit(0);
+ exits("");
}
}
@@ -226,26 +213,23 @@
static void
save_mail(message *mp)
{
+ char buf[Pathlen];
Biobuf *fp;
- String *file;
- file = s_new();
- deadletter(file);
- fp = sysopen(s_to_c(file), "cAt", 0660);
+ mboxpathbuf(buf, sizeof buf, getlog(), "dead.letter");
+ fp = sysopen(buf, "cAt", 0660);
if (fp == 0)
return;
m_bprint(mp, fp);
sysclose(fp);
- fprint(2, "saved in %s\n", s_to_c(file));
- s_free(file);
+ fprint(2, "saved in %s\n", buf);
}
/* remember the interrupt happened */
static int
-catchint(void *a, char *msg)
+catchint(void*, char *msg)
{
- USED(a);
if(strstr(msg, "interrupt") || strstr(msg, "hangup")) {
interrupt = 1;
return 1;
@@ -303,7 +287,7 @@
msg = "unknown d_";
break;
}
- if (nflg) {
+ if (flagn) {
print("%s: %s\n", msg, s_to_c(dp->addr));
return 0;
}
@@ -314,13 +298,23 @@
static int
pipe_mail(dest *dp, message *mp)
{
- dest *next, *list=0;
- String *cmd;
- process *pp;
- int status, r;
+ int status;
char *none;
- String *errstring=s_new();
+ dest *next, *list;
+ process *pp;
+ String *cmd;
+ String *errstring;
+ errstring = s_new();
+ list = 0;
+
+ /*
+ * we're just protecting users from their own
+ * pipeto scripts with this none business.
+ * this depends on none being able to append
+ * to a mail file.
+ */
+
if (dp->status == d_pipeto)
none = "none";
else
@@ -329,12 +323,12 @@
* collect the arguments
*/
next = d_rm_same(&dp);
- if(xflg)
+ if(flagx)
cmd = s_new();
else
cmd = s_clone(next->repl1);
for(; next != 0; next = d_rm_same(&dp)){
- if(xflg){
+ if(flagx){
s_append(cmd, s_to_c(next->addr));
s_append(cmd, "\n");
} else {
@@ -346,8 +340,8 @@
d_insert(&list, next);
}
- if (nflg) {
- if(xflg)
+ if (flagn) {
+ if(flagx)
print("%s", s_to_c(cmd));
else
print("%s\n", s_to_c(cmd));
@@ -375,128 +369,13 @@
/*
* return status
*/
- if (status != 0) {
- r = refuse(list, mp, s_to_c(errstring), status, 0);
- s_free(errstring);
- return r;
- }
- s_free(errstring);
+ if (status != 0)
+ return refuse(list, mp, s_to_c(errstring), status, 0);
loglist(list, mp, "remote");
return 0;
}
-static void
-appaddr(String *sp, dest *dp)
-{
- dest *parent;
- String *s;
-
- if (dp->parent != 0) {
- for(parent=dp->parent; parent->parent!=0; parent=parent->parent)
- ;
- s = unescapespecial(s_clone(parent->addr));
- s_append(sp, s_to_c(s));
- s_free(s);
- s_append(sp, "' alias `");
- }
- s = unescapespecial(s_clone(dp->addr));
- s_append(sp, s_to_c(s));
- s_free(s);
-}
-
/*
- * reject delivery
- *
- * returns 0 - if mail has been disposed of
- * other - if mail has not been disposed
- */
-int
-refuse(dest *list, message *mp, char *cp, int status, int outofresources)
-{
- String *errstring=s_new();
- dest *dp;
- int rv;
-
- dp = d_rm(&list);
- mkerrstring(errstring, mp, dp, list, cp, status);
-
- /*
- * log first in case we get into trouble
- */
- logrefusal(dp, mp, s_to_c(errstring));
-
- /*
- * bulk mail is never replied to, if we're out of resources,
- * let the sender try again
- */
- if(rmail){
- /* accept it or request a retry */
- if(outofresources){
- fprint(2, "Mail %s\n", s_to_c(errstring));
- rv = 1; /* try again later */
- } else if(mp->bulk)
- rv = 0; /* silently discard bulk */
- else
- rv = replymsg(errstring, mp, dp); /* try later if we can't reply */
- } else {
- /* aysnchronous delivery only happens if !rmail */
- if(forked){
- /*
- * if spun off for asynchronous delivery, we own the mail now.
- * return it or dump it on the floor. rv really doesn't matter.
- */
- rv = 0;
- if(!outofresources && !mp->bulk)
- replymsg(errstring, mp, dp);
- } else {
- fprint(2, "Mail %s\n", s_to_c(errstring));
- savemail = 1;
- rv = 1;
- }
- }
-
- s_free(errstring);
- return rv;
-}
-
-/* make the error message */
-static void
-mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
-{
- dest *next;
- char smsg[64];
- String *sender;
-
- sender = unescapespecial(s_clone(mp->sender));
-
- /* list all aliases */
- s_append(errstring, " from '");
- s_append(errstring, s_to_c(sender));
- s_append(errstring, "'\nto '");
- appaddr(errstring, dp);
- for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
- s_append(errstring, "'\nand '");
- appaddr(errstring, next);
- d_insert(&dp, next);
- }
- s_append(errstring, "'\nfailed with error '");
- s_append(errstring, cp);
- s_append(errstring, "'.\n");
-
- /* >> and | deserve different flavored messages */
- switch(dp->status) {
- case d_pipe:
- s_append(errstring, "The mailer `");
- s_append(errstring, s_to_c(dp->repl1));
- sprint(smsg, "' returned error status %x.\n\n", status);
- s_append(errstring, smsg);
- break;
- }
-
- s_free(sender);
-}
-
-/*
* create a new boundary
*/
static String*
@@ -542,30 +421,30 @@
refp->haveto = 1;
s_append(refp->body, "To: ");
s_append(refp->body, rcvr);
- s_append(refp->body, "\n");
- s_append(refp->body, "Subject: bounced mail\n");
- s_append(refp->body, "MIME-Version: 1.0\n");
- s_append(refp->body, "Content-Type: multipart/mixed;\n");
- s_append(refp->body, "\tboundary=\"");
+ s_append(refp->body, "\n"
+ "Subject: bounced mail\n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/mixed;\n"
+ "\tboundary=\"");
s_append(refp->body, s_to_c(boundary));
- s_append(refp->body, "\"\n");
- s_append(refp->body, "Content-Disposition: inline\n");
- s_append(refp->body, "\n");
- s_append(refp->body, "This is a multi-part message in MIME format.\n");
- s_append(refp->body, "--");
+ s_append(refp->body, "\"\n"
+ "Content-Disposition: inline\n"
+ "\n"
+ "This is a multi-part message in MIME format.\n"
+ "--");
s_append(refp->body, s_to_c(boundary));
- s_append(refp->body, "\n");
- s_append(refp->body, "Content-Disposition: inline\n");
- s_append(refp->body, "Content-Type: text/plain; charset=\"US-ASCII\"\n");
- s_append(refp->body, "Content-Transfer-Encoding: 7bit\n");
- s_append(refp->body, "\n");
- s_append(refp->body, "The attached mail");
+ s_append(refp->body, "\n"
+ "Content-Disposition: inline\n"
+ "Content-Type: text/plain; charset=\"US-ASCII\"\n"
+ "Content-Transfer-Encoding: 7bit\n"
+ "\n"
+ "The attached mail");
s_append(refp->body, s_to_c(errstring));
s_append(refp->body, "--");
s_append(refp->body, s_to_c(boundary));
- s_append(refp->body, "\n");
- s_append(refp->body, "Content-Type: message/rfc822\n");
- s_append(refp->body, "Content-Disposition: inline\n\n");
+ s_append(refp->body, "\n"
+ "Content-Type: message/rfc822\n"
+ "Content-Disposition: inline\n\n");
s_append(refp->body, s_to_c(mp->body));
s_append(refp->body, "--");
s_append(refp->body, s_to_c(boundary));
@@ -575,5 +454,115 @@
rv = send(ndp, refp, 0);
m_free(refp);
d_free(ndp);
+ return rv;
+}
+
+static void
+appaddr(String *sp, dest *dp)
+{
+ dest *p;
+ String *s;
+
+ if (dp->parent != 0) {
+ for(p = dp->parent; p->parent; p = p->parent)
+ ;
+ s = unescapespecial(s_clone(p->addr));
+ s_append(sp, s_to_c(s));
+ s_free(s);
+ s_append(sp, "' alias `");
+ }
+ s = unescapespecial(s_clone(dp->addr));
+ s_append(sp, s_to_c(s));
+ s_free(s);
+}
+
+/* make the error message */
+static void
+mkerrstring(String *errstring, message *mp, dest *dp, dest *list, char *cp, int status)
+{
+ dest *next;
+ char smsg[64];
+ String *sender;
+
+ sender = unescapespecial(s_clone(mp->sender));
+
+ /* list all aliases */
+ s_append(errstring, " from '");
+ s_append(errstring, s_to_c(sender));
+ s_append(errstring, "'\nto '");
+ appaddr(errstring, dp);
+ for(next = d_rm(&list); next != 0; next = d_rm(&list)) {
+ s_append(errstring, "'\nand '");
+ appaddr(errstring, next);
+ d_insert(&dp, next);
+ }
+ s_append(errstring, "'\nfailed with error '");
+ s_append(errstring, cp);
+ s_append(errstring, "'.\n");
+
+ /* >> and | deserve different flavored messages */
+ switch(dp->status) {
+ case d_pipe:
+ s_append(errstring, "The mailer `");
+ s_append(errstring, s_to_c(dp->repl1));
+ sprint(smsg, "' returned error status %x.\n\n", status);
+ s_append(errstring, smsg);
+ break;
+ }
+
+ s_free(sender);
+}
+
+/*
+ * reject delivery
+ *
+ * returns 0 - if mail has been disposed of
+ * other - if mail has not been disposed
+ */
+int
+refuse(dest *list, message *mp, char *cp, int status, int outofresources)
+{
+ int rv;
+ String *errstring;
+ dest *dp;
+
+ errstring = s_new();
+ dp = d_rm(&list);
+ mkerrstring(errstring, mp, dp, list, cp, status);
+
+ /*
+ * log first in case we get into trouble
+ */
+ logrefusal(dp, mp, s_to_c(errstring));
+
+ rv = 1;
+ if(rmail){
+ /* accept it or request a retry */
+ if(outofresources){
+ fprint(2, "Mail %s\n", s_to_c(errstring));
+ } else {
+ /*
+ * reject without generating a reply, smtpd returns
+ * 5.0.0 status when it sees "mail refused"
+ */
+ fprint(2, "mail refused: %s\n", s_to_c(errstring));
+ }
+ } else {
+ /* aysnchronous delivery only happens if !rmail */
+ if(forked){
+ /*
+ * if spun off for asynchronous delivery, we own the mail now.
+ * return it or dump it on the floor. rv really doesn't matter.
+ */
+ rv = 0;
+ if(!outofresources && !mp->bulk)
+ replymsg(errstring, mp, dp);
+ } else {
+ fprint(2, "Mail %s\n", s_to_c(errstring));
+ savemail = 1;
+ }
+ }
+
+ s_free(errstring);
return rv;
}
--- a/sys/src/cmd/upas/send/makefile
+++ /dev/null
@@ -1,46 +1,0 @@
-SSRC= message.c main.c bind.c rewrite.c local.c dest.c process.c translate.c\
- log.c chkfwd.c notify.c gateway.c authorize.o ../common/*.c
-SOBJ= message.o main.o bind.o rewrite.o local.o dest.o process.o translate.o\
- log.o chkfwd.o notify.o gateway.o authorize.o\
- ../config/config.o ../common/common.a ../libc/libc.a
-SINC= ../common/mail.h ../common/string.h ../common/aux.h
-CFLAGS=${UNIX} -g -I. -I../libc -I../common -I/usr/include ${SCFLAGS}
-LFLAGS=-g
-.c.o: ; $(CC) -c $(CFLAGS) $*.c
-LIB=/usr/lib/upas
-
-all: send
-
-send: $(SOBJ)
- $(CC) $(SOBJ) $(LFLAGS) -o send
-
-chkfwd.o: $(SINC) message.h dest.h
-dest.o: $(SINC) dest.h
-local.o: $(SINC) dest.h process.h
-log.o: $(SINC) message.h
-main.o: $(SINC) message.h dest.h process.h
-bind.o: $(SINC) dest.h message.h
-process.o: $(SINC) process.h
-rewrite.o: $(SINC) dest.h
-translate.o: $(SINC) dest.h process.h
-message.o: $(SINC) message.h
-notify.o: $(SINC) message.h
-gateway.o: $(SINC) dest.h message.h
-
-prcan:
- prcan $(SSRC)
-
-clean:
- -rm -f send *.[oO] a.out core *.sL rmail
-
-cyntax:
- cyntax $(CFLAGS) $(SSRC)
-
-install: send
- rm -f $(LIB)/send /bin/rmail
- cp send $(LIB)/send
- cp send /bin/rmail
- strip /bin/rmail
- strip $(LIB)/send
- chown root $(LIB)/send /bin/rmail
- chmod 4755 $(LIB)/send /bin/rmail
--- a/sys/src/cmd/upas/send/message.c
+++ b/sys/src/cmd/upas/send/message.c
@@ -1,22 +1,24 @@
#include "common.h"
#include "send.h"
-
+#include <regexp.h>
#include "../smtp/smtp.h"
#include "../smtp/y.tab.h"
+enum{
+ VMLIMIT = 64*1024,
+ MSGLIMIT = 128*1024*1024,
+};
+
/* global to this file */
static Reprog *rfprog;
static Reprog *fprog;
-#define VMLIMIT (64*1024)
-#define MSGLIMIT (128*1024*1024)
-
int received; /* from rfc822.y */
static String* getstring(Node *p);
static String* getaddr(Node *p);
-extern int
+int
default_from(message *mp)
{
char *cp, *lp;
@@ -23,27 +25,27 @@
cp = getenv("upasname");
lp = getlog();
- if(lp == nil)
+ if(lp == nil){
+ free(cp);
return -1;
-
+ }
if(cp && *cp)
s_append(mp->sender, cp);
else
s_append(mp->sender, lp);
+ free(cp);
s_append(mp->date, thedate());
return 0;
}
-extern message *
+message*
m_new(void)
{
message *mp;
- mp = (message *)mallocz(sizeof(message), 1);
- if (mp == 0) {
- perror("message:");
- exit(1);
- }
+ mp = (message*)mallocz(sizeof(message), 1);
+ if (mp == 0)
+ sysfatal("m_new: %r");
mp->sender = s_new();
mp->replyaddr = s_new();
mp->date = s_new();
@@ -53,14 +55,11 @@
return mp;
}
-extern void
+void
m_free(message *mp)
{
- if(mp->fd >= 0){
+ if(mp->fd >= 0)
close(mp->fd);
- sysremove(s_to_c(mp->tmp));
- s_free(mp->tmp);
- }
s_free(mp->sender);
s_free(mp->date);
s_free(mp->body);
@@ -68,7 +67,7 @@
s_free(mp->havesender);
s_free(mp->havereplyto);
s_free(mp->havesubject);
- free((char *)mp);
+ free(mp);
}
/* read a message into a temp file, return an open fd to it in mp->fd */
@@ -75,27 +74,21 @@
static int
m_read_to_file(Biobuf *fp, message *mp)
{
- int fd;
- int n;
- String *file;
- char buf[4*1024];
+ char buf[4*1024], file[Pathlen];
+ int fd, n;
- file = s_new();
+ snprint(file, sizeof file, "%s/mtXXXXXX", UPASTMP);
/*
* create temp file to be removed on close
*/
- abspath("mtXXXXXX", UPASTMP, file);
- mktemp(s_to_c(file));
- if((fd = syscreate(s_to_c(file), ORDWR|ORCLOSE, 0600))<0){
- s_free(file);
+ mktemp(file);
+ if((fd = create(file, ORDWR|ORCLOSE, 0600))<0)
return -1;
- }
- mp->tmp = file;
/*
* read the rest into the temp file
*/
- while((n = Bread(fp, buf, sizeof(buf))) > 0){
+ while((n = Bread(fp, buf, sizeof buf)) > 0){
if(write(fd, buf, n) != n){
close(fd);
return -1;
@@ -132,9 +125,9 @@
return s;
for(p = p->next; p; p = p->next){
- if(p->s){
+ if(p->s)
s_append(s, s_to_c(p->s));
- }else{
+ else{
s_putc(s, p->c);
s_terminate(s);
}
@@ -144,35 +137,6 @@
return s;
}
-static char *fieldname[] =
-{
-[WORD-WORD] "WORD",
-[DATE-WORD] "DATE",
-[RESENT_DATE-WORD] "RESENT_DATE",
-[RETURN_PATH-WORD] "RETURN_PATH",
-[FROM-WORD] "FROM",
-[SENDER-WORD] "SENDER",
-[REPLY_TO-WORD] "REPLY_TO",
-[RESENT_FROM-WORD] "RESENT_FROM",
-[RESENT_SENDER-WORD] "RESENT_SENDER",
-[RESENT_REPLY_TO-WORD] "RESENT_REPLY_TO",
-[SUBJECT-WORD] "SUBJECT",
-[TO-WORD] "TO",
-[CC-WORD] "CC",
-[BCC-WORD] "BCC",
-[RESENT_TO-WORD] "RESENT_TO",
-[RESENT_CC-WORD] "RESENT_CC",
-[RESENT_BCC-WORD] "RESENT_BCC",
-[REMOTE-WORD] "REMOTE",
-[PRECEDENCE-WORD] "PRECEDENCE",
-[MIMEVERSION-WORD] "MIMEVERSION",
-[CONTENTTYPE-WORD] "CONTENTTYPE",
-[MESSAGEID-WORD] "MESSAGEID",
-[RECEIVED-WORD] "RECEIVED",
-[MAILER-WORD] "MAILER",
-[BADTOKEN-WORD] "BADTOKEN",
-};
-
/* fix 822 addresses */
static void
rfc822cruft(message *mp)
@@ -251,8 +215,35 @@
mp->body = body;
}
+/* append a sub-expression match onto a String */
+static void
+append_match(Resub *subexp, String *sp, int se)
+{
+ char *cp, *ep;
+
+ cp = subexp[se].sp;
+ ep = subexp[se].ep;
+ for (; cp < ep; cp++)
+ s_putc(sp, *cp);
+ s_terminate(sp);
+}
+
+
+static char *REMFROMRE =
+ "^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)[ \t]+remote[ \t]+from[ \t]+(.*)\n$";
+static char *FROMRE =
+ "^>?From[ \t]+((\".*\")?[^\" \t]+?(\".*\")?[^\" \t]+?)[ \t]+(.+)\n$";
+
+enum{
+ REMSENDERMATCH = 1,
+ SENDERMATCH = 1,
+ REMDATEMATCH = 4,
+ DATEMATCH = 4,
+ REMSYSMATCH = 5,
+};
+
/* read in a message, interpret the 'From' header */
-extern message *
+message*
m_read(Biobuf *fp, int rmail, int interactive)
{
message *mp;
@@ -326,18 +317,12 @@
*/
mp->size = mp->body->ptr - mp->body->base;
n = s_read(fp, mp->body, VMLIMIT);
- if(n < 0){
- perror("m_read");
- exit(1);
- }
+ if(n < 0)
+ sysfatal("m_read: %r");
mp->size += n;
- if(n == VMLIMIT){
- if(m_read_to_file(fp, mp) < 0){
- perror("m_read");
- exit(1);
- }
- }
-
+ if(n == VMLIMIT)
+ if(m_read_to_file(fp, mp) < 0)
+ sysfatal("m_read: %r");
}
/*
@@ -352,8 +337,8 @@
}
/* return a piece of message starting at `offset' */
-extern int
-m_get(message *mp, long offset, char **pp)
+int
+m_get(message *mp, vlong offset, char **pp)
{
static char buf[4*1024];
@@ -377,10 +362,8 @@
offset -= s_len(mp->body);
if(mp->fd < 0)
return -1;
- if(seek(mp->fd, offset, 0)<0)
- return -1;
*pp = buf;
- return read(mp->fd, buf, sizeof buf);
+ return pread(mp->fd, buf, sizeof buf, offset);
}
/* output the message body without ^From escapes */
@@ -444,20 +427,22 @@
static int
printfrom(message *mp, Biobuf *fp)
{
- String *s;
+// char *p;
int rv;
+ String *s;
if(!returnable(s_to_c(mp->sender)))
return Bprint(fp, "From: Postmaster\n");
- s = username(mp->sender);
- if(s) {
- s_append(s, " <");
- s_append(s, s_to_c(mp->sender));
- s_append(s, ">");
- } else {
+// p = username(s_to_c(mp->sender));
+// if(p) {
+// s_append(s = s_new(), p);
+// s_append(s, " <");
+// s_append(s, s_to_c(mp->sender));
+// s_append(s, ">");
+// } else {
s = s_copy(s_to_c(mp->sender));
- }
+// }
s = unescapespecial(s);
rv = Bprint(fp, "From: %s\n", s_to_c(s));
s_free(s);
@@ -509,34 +494,30 @@
}
/* output a message */
-extern int
+int
m_print(message *mp, Biobuf *fp, char *remote, int mbox)
{
- String *date, *sender;
- char *f[6];
- int n;
+ char *date, *d, *f[6];
+ int n, r;
+ String *sender;
sender = unescapespecial(s_clone(mp->sender));
-
- if (remote != 0){
- if(print_remote_header(fp,s_to_c(sender),s_to_c(mp->date),remote) < 0){
- s_free(sender);
- return -1;
- }
- } else {
- if(print_header(fp, s_to_c(sender), s_to_c(mp->date)) < 0){
- s_free(sender);
- return -1;
- }
- }
+ date = s_to_c(mp->date);
+ if(remote)
+ r = Bprint(fp, "From %s %s remote from %s\n", s_to_c(sender), date, remote);
+ else
+ r = Bprint(fp, "From %s %s\n", s_to_c(sender), date);
s_free(sender);
+ if(r < 0)
+ return -1;
if(!rmail && !mp->havedate){
/* add a date: line Date: Sun, 19 Apr 1998 12:27:52 -0400 */
- date = s_copy(s_to_c(mp->date));
- n = getfields(s_to_c(date), f, 6, 1, " \t");
+ d = strdup(date);
+ n = getfields(date, f, 6, 1, " \t");
if(n == 6)
Bprint(fp, "Date: %s, %s %s %s %s %s\n", f[0], f[2], f[1],
f[5], f[3], rewritezone(f[4]));
+ free(d);
}
if(!rmail && !mp->havemime && isutf8(mp->body))
printutf8mime(fp);
@@ -559,13 +540,13 @@
return -1;
}
- if (!mbox)
+ if(!mbox)
return m_noescape(mp, fp);
return m_escape(mp, fp);
}
/* print just the message body */
-extern int
+int
m_bprint(message *mp, Biobuf *fp)
{
return m_noescape(mp, fp);
--- a/sys/src/cmd/upas/send/mkfile
+++ b/sys/src/cmd/upas/send/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=send\
filter
@@ -12,16 +13,19 @@
$UOFILES\
../smtp/rfc822.tab.$O\
-SMOBJ=main.$O\
+SOBJ=\
+ authorize.$O\
bind.$O\
- rewrite.$O\
+ cat_mail.$O\
+ gateway.$O\
local.$O\
+ main.$O\
+ rewrite.$O\
translate.$O\
- authorize.$O\
- gateway.$O\
+
+FOBJ=\
cat_mail.$O\
-LIB=../common/libcommon.av\
HFILES=send.h\
../common/common.h\
@@ -29,24 +33,22 @@
LIB=../common/libcommon.a$O\
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
$HFILES\
${UOFILES:%.$O=%.c}\
- ${SMOBJ:%.$O=%.c}\
+ ${SOBJ:%.$O=%.c}\
${TARG:%=%.c}\
</sys/src/cmd/mkmany
CFLAGS=$CFLAGS -I../common
-$O.send: $SMOBJ $OFILES
+$O.send: $SOBJ $OFILES
$LD $LDFLAGS -o $target $prereq $LIB
+$O.filter: $FOBJ
+
message.$O: ../smtp/y.tab.h
../smtp/y.tab.h ../smtp/rfc822.tab.$O: ../smtp/rfc822.y
- @{
- cd ../smtp
- mk rfc822.tab.$O
- }
+ cd ../smtp && mk rfc822.tab.$O
--- a/sys/src/cmd/upas/send/rewrite.c
+++ b/sys/src/cmd/upas/send/rewrite.c
@@ -1,5 +1,6 @@
#include "common.h"
#include "send.h"
+#include <regexp.h>
extern int debug;
@@ -32,7 +33,7 @@
* Get the next token from `line'. The symbol `\l' is replaced by
* the name of the local system.
*/
-extern String *
+String*
rule_parse(String *line, char *system, int *backl)
{
String *token;
@@ -80,10 +81,8 @@
if(re == 0)
return 0;
rp = (rule *)malloc(sizeof(rule));
- if(rp == 0) {
- perror("getrules:");
- exit(1);
- }
+ if(rp == 0)
+ sysfatal("malloc: %r");
rp->next = 0;
s_tolower(re);
rp->matchre = s_new();
@@ -123,16 +122,15 @@
* rules are of the form:
* <reg exp> <String> <repl exp> [<repl exp>]
*/
-extern int
+int
getrules(void)
{
- Biobuf *rfp;
- String *line;
- String *type;
- String *file;
+ char file[Pathlen];
+ Biobuf *rfp;
+ String *line, *type;
- file = abspath("rewrite", UPASLIB, (String *)0);
- rfp = sysopen(s_to_c(file), "r", 0);
+ snprint(file, sizeof file, "%s/rewrite", UPASLIB);
+ rfp = sysopen(file, "r", 0);
if(rfp == 0) {
rulep = 0;
return -1;
@@ -145,7 +143,6 @@
getrule(s_restart(line), type, altthissys);
s_free(type);
s_free(line);
- s_free(file);
sysclose(rfp);
return 0;
}
@@ -168,8 +165,7 @@
continue;
memset(rp->subexp, 0, sizeof(rp->subexp));
if(debug)
- fprint(2, "matching %s against %s\n", s_to_c(addrp),
- rp->matchre->base);
+ fprint(2, "matching %s aginst %s\n", s_to_c(addrp), rp->matchre->base);
if(regexec(rp->program, s_to_c(addrp), rp->subexp, NSUBEXP))
if(s_to_c(addrp) == rp->subexp[0].sp)
if((s_to_c(addrp) + strlen(s_to_c(addrp))) == rp->subexp[0].ep)
@@ -183,7 +179,7 @@
* 0 ifaddress matched and ok to forward
* 1 ifaddress matched and not ok to forward
*/
-extern int
+int
rewrite(dest *dp, message *mp)
{
rule *rp; /* rewriting rule */
@@ -279,40 +275,39 @@
return s_restart(stp);
}
-extern void
+void
regerror(char* s)
{
fprint(2, "rewrite: %s\n", s);
/* make sure the message is seen locally */
- syslog(0, "mail", "regexp error in rewrite: %s", s);
+ syslog(0, "mail", "error in rewrite: %s", s);
}
-extern void
-dumprules(void)
-{
- rule *rp;
-
- for (rp = rulep; rp != 0; rp = rp->next) {
- fprint(2, "'%s'", rp->matchre->base);
- switch (rp->type) {
- case d_pipe:
- fprint(2, " |");
- break;
- case d_cat:
- fprint(2, " >>");
- break;
- case d_alias:
- fprint(2, " alias");
- break;
- case d_translate:
- fprint(2, " translate");
- break;
- default:
- fprint(2, " UNKNOWN");
- break;
- }
- fprint(2, " '%s'", rp->repl1 ? rp->repl1->base:"...");
- fprint(2, " '%s'\n", rp->repl2 ? rp->repl2->base:"...");
- }
-}
-
+//void
+//dumprules(void)
+//{
+// rule *rp;
+//
+// for (rp = rulep; rp != 0; rp = rp->next) {
+// fprint(2, "'%s'", rp->matchre->base);
+// switch (rp->type) {
+// case d_pipe:
+// fprint(2, " |");
+// break;
+// case d_cat:
+// fprint(2, " >>");
+// break;
+// case d_alias:
+// fprint(2, " alias");
+// break;
+// case d_translate:
+// fprint(2, " translate");
+// break;
+// default:
+// fprint(2, " UNKNOWN");
+// break;
+// }
+// fprint(2, " '%s'", rp->repl1 ? rp->repl1->base:"...");
+// fprint(2, " '%s'\n", rp->repl2 ? rp->repl2->base:"...");
+// }
+//}
--- a/sys/src/cmd/upas/send/send.h
+++ b/sys/src/cmd/upas/send/send.h
@@ -1,11 +1,5 @@
-/*
- * these limits are intended to stay within those imposed by SMTP
- * and avoid tickling bugs in other mail systems.
- * they both pertain to attempts to group recipients for the same
- * destination together in a single copy of a message.
- */
-#define MAXSAME 32 /* max recipients; was 16 */
-#define MAXSAMECHAR 1024 /* max chars in the list of recipients */
+#define MAXSAME 16
+#define MAXSAMECHAR 1024
/* status of a destination*/
typedef enum {
@@ -47,11 +41,9 @@
String *replyaddr;
String *date;
String *body;
- String *tmp; /* name of temp file */
String *to;
int size;
int fd; /* if >= 0, the file the message is stored in*/
- char haveto;
String *havefrom;
String *havesender;
String *havereplyto;
@@ -58,32 +50,26 @@
char havedate;
char havemime;
String *havesubject;
- char bulk; /* if Precedence: Bulk in header */
char rfc822headers;
- int received; /* number of received lines */
char *boundary; /* bondary marker for attachments */
+ char haveto;
+ char bulk; /* if Precedence: Bulk in header */
+ char received; /* number of received lines */
};
-/*
- * exported variables
- */
-extern int rmail;
-extern int onatty;
-extern char *thissys, *altthissys;
-extern int xflg;
-extern int nflg;
-extern int tflg;
-extern int debug;
-extern int nosummary;
+extern int rmail;
+extern int onatty;
+extern char *thissys;
+extern char *altthissys;
+extern int debug;
+extern int nosummary;
+extern int flagn;
+extern int flagx;
-/*
- * exported procedures
- */
extern void authorize(dest*);
extern int cat_mail(dest*, message*);
extern dest *up_bind(dest*, message*, int);
extern int ok_to_forward(char*);
-extern int lookup(char*, char*, Biobuf**, char*, Biobuf**);
extern dest *d_new(String*);
extern void d_free(dest*);
extern dest *d_rm(dest**);
@@ -101,7 +87,7 @@
extern message *m_new(void);
extern void m_free(message*);
extern message *m_read(Biobuf*, int, int);
-extern int m_get(message*, long, char**);
+extern int m_get(message*, vlong, char**);
extern int m_print(message*, Biobuf*, char*, int);
extern int m_bprint(message*, Biobuf*);
extern String *rule_parse(String*, char*, int*);
--- a/sys/src/cmd/upas/send/skipequiv.c
+++ b/sys/src/cmd/upas/send/skipequiv.c
@@ -3,10 +3,53 @@
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
+static int
+okfile(char *s, Biobuf *b)
+{
+ char *buf, *p, *e;
+ int len, c;
+
+ len = strlen(s);
+ Bseek(b, 0, 0);
+
+ /* one iteration per system name in the file */
+ while(buf = Brdline(b, '\n')) {
+ e = buf + Blinelen(b);
+ for(p = buf; p < e;){
+ while(isspace(*p) || *p==',')
+ p++;
+ if(strncmp(p, s, len) == 0) {
+ c = p[len];
+ if(isspace(c) || c==',')
+ return 1;
+ }
+ while(p < e && (!isspace(*p)) && *p!=',')
+ p++;
+ }
+ }
+ /* didn't find it, prohibit forwarding */
+ return 0;
+}
+
+/* return 1 if name found in file
+ * 0 if name not found
+ * -1 if
+ */
+static int
+lookup(char *s, char *local, Biobuf **b)
+{
+ char file[Pathlen];
+
+ snprint(file, sizeof file, "%s/%s", UPASLIB, local);
+ if(*b != nil || (*b = sysopen(file, "r", 0)) != nil)
+ return okfile(s, *b);
+ return 0;
+}
+
/*
* skip past all systems in equivlist
*/
-extern char*
+char*
skipequiv(char *base)
{
char *sp;
@@ -17,7 +60,7 @@
if(sp==0)
break;
*sp = '\0';
- if(lookup(base, "equivlist", &fp, 0, 0)==1){
+ if(lookup(base, "equivlist", &fp)==1){
/* found or us, forget this system */
*sp='!';
base=sp+1;
@@ -28,65 +71,4 @@
}
}
return base;
-}
-
-static int
-okfile(char *cp, Biobuf *fp)
-{
- char *buf;
- int len;
- char *bp, *ep;
- int c;
-
- len = strlen(cp);
- Bseek(fp, 0, 0);
-
- /* one iteration per system name in the file */
- while(buf = Brdline(fp, '\n')) {
- ep = &buf[Blinelen(fp)];
- for(bp=buf; bp < ep;){
- while(isspace(*bp) || *bp==',')
- bp++;
- if(strncmp(bp, cp, len) == 0) {
- c = *(bp+len);
- if(isspace(c) || c==',')
- return 1;
- }
- while(bp < ep && (!isspace(*bp)) && *bp!=',')
- bp++;
- }
- }
-
- /* didn't find it, prohibit forwarding */
- return 0;
-}
-
-/* return 1 if name found in one of the files
- * 0 if name not found in one of the files
- * -1 if neither file exists
- */
-extern int
-lookup(char *cp, char *local, Biobuf **lfpp, char *global, Biobuf **gfpp)
-{
- static String *file = 0;
-
- if (local) {
- if (file == 0)
- file = s_new();
- abspath(local, UPASLIB, s_restart(file));
- if (*lfpp != 0 || (*lfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
- if (okfile(cp, *lfpp))
- return 1;
- } else
- local = 0;
- }
- if (global) {
- abspath(global, UPASLIB, s_restart(file));
- if (*gfpp != 0 || (*gfpp = sysopen(s_to_c(file), "r", 0)) != 0) {
- if (okfile(cp, *gfpp))
- return 1;
- } else
- global = 0;
- }
- return (local || global)? 0 : -1;
}
--- a/sys/src/cmd/upas/send/translate.c
+++ b/sys/src/cmd/upas/send/translate.c
@@ -11,7 +11,7 @@
char *cp;
int n;
- pp = proc_start(s_to_c(dp->repl1), (stream *)0, outstream(), outstream(), 1, 0);
+ pp = proc_start(s_to_c(dp->repl1), 0, outstream(), outstream(), 1, 0);
if (pp == 0) {
dp->status = d_resource;
return 0;
--- a/sys/src/cmd/upas/send/tryit
+++ /dev/null
@@ -1,29 +1,0 @@
-#!/bin/sh
-set -x
-
-> /usr/spool/mail/test.local
-echo "Forward to test.local" > /usr/spool/mail/test.forward
-echo "Pipe to cat > /tmp/test.mail" > /usr/spool/mail/test.pipe
-chmod 644 /usr/spool/mail/test.pipe
-
-mail test.local <<EOF
-mailed to test.local
-EOF
-mail test.forward <<EOF
-mailed to test.forward
-EOF
-mail test.pipe <<EOF
-mailed to test.pipe
-EOF
-mail dutoit!bowell!test.local <<EOF
-mailed to dutoit!bowell!test.local
-EOF
-
-sleep 60
-
-ls -l /usr/spool/mail/test.*
-ls -l /tmp/test.mail
-echo ">>>test.local<<<"
-cat /usr/spool/mail/test.local
-echo ">>>test.mail<<<"
-cat /tmp/test.mail
--- a/sys/src/cmd/upas/smtp/greylist.c
+++ b/sys/src/cmd/upas/smtp/greylist.c
@@ -53,7 +53,7 @@
wl = Bopen(whitelist, OREAD);
if (wl == nil)
- return 1;
+ return 0;
while ((line = Brdline(wl, '\n')) != nil) {
lnlen = Blinelen(wl);
line[lnlen-1] = '\0'; /* clobber newline */
--- a/sys/src/cmd/upas/smtp/mkfile
+++ b/sys/src/cmd/upas/smtp/mkfile
@@ -1,8 +1,12 @@
</$objtype/mkfile
+<../mkupas
TARG = smtpd\
smtp\
+TEST=\
+ parsetest
+
OFILES=
LIB=../common/libcommon.a$O\
@@ -12,13 +16,11 @@
smtpd.h\
smtp.h\
-BIN=/$objtype/bin/upas
UPDATE=\
greylist.c\
mkfile\
mxdial.c\
rfc822.y\
- rmtdns.c\
smtpd.y\
spam.c\
$HFILES\
@@ -26,24 +28,30 @@
${TARG:%=%.c}\
</sys/src/cmd/mkmany
-CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"'
+CFLAGS=$CFLAGS -I../common
-$O.smtpd: smtpd.tab.$O rmtdns.$O spam.$O rfc822.tab.$O greylist.$O
+$O.smtpd:\
+ smtpd.tab.$O\
+ spam.$O\
+ rfc822.tab.$O\
+ greylist.$O\
+
$O.smtp: rfc822.tab.$O mxdial.$O
-smtpd.tab.c: smtpd.y
+smtpd.tab.c: smtpd.y
yacc -o xxx smtpd.y
sed 's/yy/zz/g' < xxx > $target
rm xxx
-rfc822.tab.c: rfc822.y
+rfc822.tab.c: rfc822.y
yacc -d -o $target rfc822.y
+$O.parsetest: rfc822.tab.$O
+
+parsetest.$O: rfc822.tab.$O
+
clean:V:
- rm -f *.[$OS] [$OS].$TARG smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG
+ rm -f *.[$OS] [$OS].^($TARG $TEST) smtpd.tab.c rfc822.tab.c y.tab.? y.debug $TARG
../common/libcommon.a$O:
- @{
- cd ../common
- mk
- }
+ cd ../common && mk
--- a/sys/src/cmd/upas/smtp/mxdial.c
+++ b/sys/src/cmd/upas/smtp/mxdial.c
@@ -1,60 +1,96 @@
#include "common.h"
+#include "smtp.h"
#include <ndb.h>
-#include <smtp.h> /* to publish dial_string_parse */
-enum
-{
- Nmx= 16,
- Maxstring= 256,
-};
+char *bustedmxs[Maxbustedmx];
-typedef struct Mx Mx;
-struct Mx
+static void
+expand(DS *ds)
{
- char host[256];
- char ip[24];
- int pref;
-};
+ char *s;
+ Ndbtuple *t;
-char *bustedmxs[Maxbustedmx];
-Ndb *db;
+ s = ds->host + 1;
+ t = csipinfo(ds->netdir, "sys", sysname(), &s, 1);
+ if(t != nil){
+ strecpy(ds->expand, ds->expand+sizeof ds->expand, t->val);
+ ds->host = ds->expand;
+ }
+ ndbfree(t);
+}
-static int mxlookup(DS*, char*);
-static int mxlookup1(DS*, char*);
-static int compar(void*, void*);
-static int callmx(DS*, char*, char*);
-static void expand_meta(DS *ds);
+/* break up an address to its component parts */
+void
+dialstringparse(char *str, DS *ds)
+{
+ char *p, *p2;
-static Mx mx[Nmx];
+ strecpy(ds->buf, ds->buf + sizeof ds->buf, str);
+ p = strchr(ds->buf, '!');
+ if(p == 0) {
+ ds->netdir = 0;
+ ds->proto = "net";
+ ds->host = ds->buf;
+ } else {
+ if(*ds->buf != '/'){
+ ds->netdir = 0;
+ ds->proto = ds->buf;
+ } else {
+ for(p2 = p; *p2 != '/'; p2--)
+ ;
+ *p2++ = 0;
+ ds->netdir = ds->buf;
+ ds->proto = p2;
+ }
+ *p = 0;
+ ds->host = p + 1;
+ }
+ ds->service = strchr(ds->host, '!');
+ if(ds->service)
+ *ds->service++ = 0;
+ if(*ds->host == '$')
+ expand(ds);
+}
-int
-mxdial(char *addr, char *ddomain, char *gdomain)
+void
+mxtabfree(Mxtab *mx)
{
- int fd;
- DS ds;
+ free(mx->mx);
+ memset(mx, 0, sizeof *mx);
+}
- addr = netmkaddr(addr, 0, "smtp");
- dial_string_parse(addr, &ds);
-
- /* try connecting to destination or any of it's mail routers */
- fd = callmx(&ds, addr, ddomain);
-
- /* try our mail gateway */
- if(fd < 0 && gdomain)
- fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
-
- return fd;
+static void
+mxtabrealloc(Mxtab *mx)
+{
+ if(mx->nmx < mx->amx)
+ return;
+ if(mx->amx == 0)
+ mx->amx = 1;
+ mx->amx <<= 1;
+ mx->mx = realloc(mx->mx, sizeof mx->mx[0] * mx->amx);
+ if(mx->mx == nil)
+ sysfatal("no memory for mx");
}
-static int
-busted(char *mx)
+static void
+mxtabadd(Mxtab *mx, char *host, char *ip, char *net, int pref)
{
- char **bmp;
+ int i;
+ Mx *x;
- for (bmp = bustedmxs; *bmp != nil; bmp++)
- if (strcmp(mx, *bmp) == 0)
- return 1;
- return 0;
+ mxtabrealloc(mx);
+ x = mx->mx;
+ for(i = mx->nmx; i>0 && x[i-1].pref>pref && x[i-1].netdir == net; i--)
+ x[i] = x[i-1];
+ strecpy(x[i].host, x[i].host + sizeof x[i].host, host);
+ if(ip != nil)
+ strecpy(x[i].ip, x[i].ip + sizeof x[i].ip, ip);
+ else
+ x[i].ip[0] = 0;
+ x[i].netdir = net;
+ x[i].pref = pref;
+ x[i].valid = 1;
+ mx->nmx++;
}
static int
@@ -65,7 +101,7 @@
return 0;
}
-long
+static long
timedwrite(int fd, void *buf, long len, long ms)
{
long n, oalarm;
@@ -72,286 +108,228 @@
atnotify(timeout, 1);
oalarm = alarm(ms);
- n = write(fd, buf, len);
+ n = pwrite(fd, buf, len, 0);
alarm(oalarm);
atnotify(timeout, 0);
return n;
}
-/*
- * take an address and return all the mx entries for it,
- * most preferred first
- */
static int
-callmx(DS *ds, char *dest, char *domain)
+dnslookup(Mxtab *mx, int fd, char *query, char *domain, char *net, int pref0)
{
- int fd, i, nmx;
- char addr[Maxstring];
+ int n;
+ char buf[1024], *f[4];
- /* get a list of mx entries */
- nmx = mxlookup(ds, domain);
- if(nmx < 0){
- /* dns isn't working, don't just dial */
- return -1;
- }
- if(nmx == 0){
- if(debug)
- fprint(2, "mxlookup returns nothing\n");
- return dial(dest, 0, 0, 0);
- }
-
- /* refuse to honor loopback addresses given by dns */
- for(i = 0; i < nmx; i++)
- if(strcmp(mx[i].ip, "127.0.0.1") == 0){
- if(debug)
- fprint(2, "mxlookup returns loopback\n");
- werrstr("illegal: domain lists 127.0.0.1 as mail server");
+ n = timedwrite(fd, query, strlen(query), 60*1000);
+ if(n < 0){
+ rerrstr(buf, sizeof buf);
+ dprint("dns: %s\n", buf);
+ if(strstr(buf, "dns failure")){
+ /* if dns fails for the mx lookup, we have to stop */
+ close(fd);
return -1;
}
+ return 0;
+ }
- /* sort by preference */
- if(nmx > 1)
- qsort(mx, nmx, sizeof(Mx), compar);
-
- /* dial each one in turn */
- for(i = 0; i < nmx; i++){
- if (busted(mx[i].host)) {
- if (debug)
- fprint(2, "mxdial skipping busted mx %s\n",
- mx[i].host);
+ seek(fd, 0, 0);
+ for(;;){
+ if((n = read(fd, buf, sizeof buf - 1)) < 1)
+ break;
+ buf[n] = 0;
+ // chat("dns: %s\n", buf);
+ n = tokenize(buf, f, nelem(f));
+ if(n < 2)
continue;
+ if(strcmp(f[1], "mx") == 0 && n == 4){
+ if(strchr(domain, '.') == 0)
+ strcpy(domain, f[0]);
+ mxtabadd(mx, f[3], nil, net, atoi(f[2]));
}
- snprint(addr, sizeof(addr), "%s/%s!%s!%s", ds->netdir, ds->proto,
- mx[i].host, ds->service);
- if(debug)
- fprint(2, "mxdial trying %s\n", addr);
- atnotify(timeout, 1);
- alarm(10*1000);
- fd = dial(addr, 0, 0, 0);
- alarm(0);
- atnotify(timeout, 0);
- if(fd >= 0)
- return fd;
+ else if (strcmp(f[1], "ip") == 0 && n == 3){
+ if(strchr(domain, '.') == 0)
+ strcpy(domain, f[0]);
+ mxtabadd(mx, f[0], f[2], net, pref0);
+ }
}
- return -1;
+
+ return 0;
}
-/*
- * call the dns process and have it try to resolve the mx request
- *
- * this routine knows about the firewall and tries inside and outside
- * dns's seperately.
- */
static int
-mxlookup(DS *ds, char *domain)
+busted(char *mx)
{
- int n;
+ char **bmp;
- /* just in case we find no domain name */
- strcpy(domain, ds->host);
+ for (bmp = bustedmxs; *bmp != nil; bmp++)
+ if (strcmp(mx, *bmp) == 0)
+ return 1;
+ return 0;
+}
- if(ds->netdir)
- n = mxlookup1(ds, domain);
- else {
- ds->netdir = "/net";
- n = mxlookup1(ds, domain);
- if(n <= 0) {
- ds->netdir = "/net.alt";
- n = mxlookup1(ds, domain);
- }
- }
+static void
+complain(Mxtab *mx, char *domain)
+{
+ char buf[1024], *e, *p;
+ int i;
- return n;
+ p = buf;
+ e = buf + sizeof buf;
+ for(i = 0; i < mx->nmx; i++)
+ p = seprint(p, e, "%s ", mx->mx[i].ip);
+ syslog(0, "smtpd.mx", "loopback for %s %s", domain, buf);
}
static int
-mxlookup1(DS *ds, char *domain)
+okaymx(Mxtab *mx, char *domain)
{
- int i, n, fd, nmx;
- char buf[1024], dnsname[Maxstring];
- char *fields[4];
+ int i;
+ Mx *x;
- snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
-
- fd = open(dnsname, ORDWR);
- if(fd < 0)
- return 0;
-
- nmx = 0;
- snprint(buf, sizeof buf, "%s mx", ds->host);
- if(debug)
- fprint(2, "sending %s '%s'\n", dnsname, buf);
- /*
- * don't hang indefinitely in the write to /net/dns.
- */
- n = timedwrite(fd, buf, strlen(buf), 60*1000);
- if(n < 0){
- rerrstr(buf, sizeof buf);
- if(debug)
- fprint(2, "dns: %s\n", buf);
- if(strstr(buf, "dns failure")){
- /* if dns fails for the mx lookup, we have to stop */
- close(fd);
+ /* look for malicious dns entries; TODO use badcidr in ../spf/ to catch more than ip4 */
+ for(i = 0; i < mx->nmx; i++){
+ x = mx->mx + i;
+ if(x->valid && strcmp(x->ip, "127.0.0.1") == 0){
+ dprint("illegal: domain %s lists 127.0.0.1 as mail server", domain);
+ complain(mx, domain);
+ werrstr("illegal: domain %s lists 127.0.0.1 as mail server", domain);
return -1;
}
- } else {
- /*
- * get any mx entries
- */
- seek(fd, 0, 0);
- while(nmx < Nmx && (n = read(fd, buf, sizeof buf-1)) > 0){
- buf[n] = 0;
- if(debug)
- fprint(2, "dns mx: %s\n", buf);
- n = getfields(buf, fields, 4, 1, " \t");
- if(n < 4)
- continue;
-
- if(strchr(domain, '.') == 0)
- strcpy(domain, fields[0]);
-
- strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1);
- mx[nmx].pref = atoi(fields[2]);
- nmx++;
+ if(x->valid && busted(x->host)){
+ dprint("lookup: skipping busted mx %s\n", x->host);
+ x->valid = 0;
}
- if(debug)
- fprint(2, "dns mx; got %d entries\n", nmx);
}
+ return 0;
+}
- /*
- * no mx record? try name itself.
- */
- /*
- * BUG? If domain has no dots, then we used to look up ds->host
- * but return domain instead of ds->host in the list. Now we return
- * ds->host. What will this break?
- */
- if(nmx == 0){
- mx[0].pref = 1;
- strncpy(mx[0].host, ds->host, sizeof(mx[0].host));
- nmx++;
+static int
+lookup(Mxtab *mx, char *net, char *host, char *domain, char *type)
+{
+ char dns[128], buf[1024];
+ int fd, i;
+ Mx *x;
+
+ snprint(dns, sizeof dns, "%s/dns", net);
+ fd = open(dns, ORDWR);
+ if(fd == -1)
+ return -1;
+
+ snprint(buf, sizeof buf, "%s %s", host, type);
+ dprint("sending %s '%s'\n", dns, buf);
+ dnslookup(mx, fd, buf, domain, net, 10000);
+
+ for(i = 0; i < mx->nmx; i++){
+ x = mx->mx + i;
+ if(x->ip[0] != 0)
+ continue;
+ x->valid = 0;
+
+ snprint(buf, sizeof buf, "%s %s", x->host, "ip");
+ dprint("sending %s '%s'\n", dns, buf);
+ dnslookup(mx, fd, buf, domain, net, x->pref);
}
- /*
- * look up all ip addresses
- */
- for(i = 0; i < nmx; i++){
- seek(fd, 0, 0);
- snprint(buf, sizeof buf, "%s ip", mx[i].host);
- mx[i].ip[0] = 0;
- /*
- * don't hang indefinitely in the write to /net/dns.
- */
- if(timedwrite(fd, buf, strlen(buf), 60*1000) < 0)
- goto no;
- seek(fd, 0, 0);
- if((n = read(fd, buf, sizeof buf-1)) < 0)
- goto no;
- buf[n] = 0;
- if(getfields(buf, fields, 4, 1, " \t") < 3)
- goto no;
- strncpy(mx[i].ip, fields[2], sizeof(mx[i].ip)-1);
- continue;
+ close(fd);
- no:
- /* remove mx[i] and go around again */
- nmx--;
- mx[i] = mx[nmx];
- i--;
+ if(strcmp(type, "mx") == 0){
+ if(okaymx(mx, domain) == -1)
+ return -1;
+ for(i = 0; i < mx->nmx; i++){
+ x = mx->mx + i;
+ dprint("mx list: %s %d %s\n", x->host, x->pref, x->ip);
+ }
+ dprint("\n");
}
- return nmx;
+
+ return 0;
}
static int
-compar(void *a, void *b)
+lookcall(Mxtab *mx, DS *d, char *domain, char *type)
{
- return ((Mx*)a)->pref - ((Mx*)b)->pref;
-}
+ char buf[1024];
+ int i;
+ Mx *x;
-/* break up an address to its component parts */
-void
-dial_string_parse(char *str, DS *ds)
-{
- char *p, *p2;
+ if(lookup(mx, d->netdir, d->host, domain, type) == -1){
+ for(i = 0; i < mx->nmx; i++)
+ if(mx->mx[i].netdir == d->netdir)
+ mx->mx[i].valid = 0;
+ return -1;
+ }
- strncpy(ds->buf, str, sizeof(ds->buf));
- ds->buf[sizeof(ds->buf)-1] = 0;
-
- p = strchr(ds->buf, '!');
- if(p == 0) {
- ds->netdir = 0;
- ds->proto = "net";
- ds->host = ds->buf;
- } else {
- if(*ds->buf != '/'){
- ds->netdir = 0;
- ds->proto = ds->buf;
- } else {
- for(p2 = p; *p2 != '/'; p2--)
- ;
- *p2++ = 0;
- ds->netdir = ds->buf;
- ds->proto = p2;
+ for(i = 0; i < mx->nmx; i++){
+ x = mx->mx + i;
+ if(x->ip[0] == 0 || x->valid == 0){
+ x->valid = 0;
+ continue;
}
- *p = 0;
- ds->host = p + 1;
+ snprint(buf, sizeof buf, "%s/%s!%s!%s", d->netdir, d->proto,
+ x->ip /*x->host*/, d->service);
+ dprint("mxdial trying %s [%s]\n", x->host, buf);
+ atnotify(timeout, 1);
+ alarm(10*1000);
+ mx->fd = dial(buf, 0, 0, 0);
+ alarm(0);
+ atnotify(timeout, 0);
+ if(mx->fd >= 0){
+ mx->pmx = i;
+ return mx->fd;
+ }
+ dprint(" failed %r\n");
+ x->valid = 0;
}
- ds->service = strchr(ds->host, '!');
- if(ds->service)
- *ds->service++ = 0;
- if(*ds->host == '$')
- expand_meta(ds);
+
+ return -1;
}
-static void
-expand_meta(DS *ds)
+int
+mxdial0(char *addr, char *ddomain, char *gdomain, Mxtab *mx)
{
- char buf[128], cs[128], *net, *p;
- int fd, n;
+ int nd, i, j;
+ DS *d;
+ static char *tab[] = {"mx", "ip", };
- net = ds->netdir;
- if(!net)
- net = "/net";
-
- if(debug)
- fprint(2, "expanding %s!%s\n", net, ds->host);
- snprint(cs, sizeof(cs), "%s/cs", net);
- if((fd = open(cs, ORDWR)) == -1){
- if(debug)
- fprint(2, "open %s: %r\n", cs);
- syslog(0, "smtp", "cannot open %s: %r", cs);
- return;
+ dprint("mxdial(%s, %s, %s, mx)\n", addr, ddomain, gdomain);
+ memset(mx, 0, sizeof *mx);
+ addr = netmkaddr(addr, 0, "smtp");
+ d = mx->ds;
+ dialstringparse(addr, d + 0);
+ nd = 1;
+ if(d[0].netdir == nil){
+ d[1] = d[0];
+ d[0].netdir = "/net";
+ d[1].netdir = "/net.alt";
+ nd = 2;
}
- snprint(buf, sizeof buf, "!ipinfo %s", ds->host+1); // +1 to skip $
- if(write(fd, buf, strlen(buf)) <= 0){
- if(debug)
- fprint(2, "write %s: %r\n", cs);
- syslog(0, "smtp", "%s to %s - write failed: %r", buf, cs);
- close(fd);
- return;
- }
+ /* search all networks for mx records; then ip records */
+ for(j = 0; j < nelem(tab); j++)
+ for(i = 0; i < nd; i++)
+ if(lookcall(mx, d + i, ddomain, tab[j]) != -1)
+ return mx->fd;
- seek(fd, 0, 0);
- if((n = read(fd, ds->expand, sizeof(ds->expand)-1)) < 0){
- if(debug)
- fprint(2, "read %s: %r\n", cs);
- syslog(0, "smtp", "%s - read failed: %r", cs);
- close(fd);
- return;
+ /* grotty: try gateway machine by ip only (fixme: try cs lookup) */
+ if(gdomain != nil){
+ dialstringparse(netmkaddr(gdomain, 0, "smtp"), d + 0);
+ if(lookcall(mx, d + 0, gdomain, "ip") != -1)
+ return mx->fd;
}
- close(fd);
- ds->expand[n] = 0;
- if((p = strchr(ds->expand, '=')) == nil){
- if(debug)
- fprint(2, "response %s: %s\n", cs, ds->expand);
- syslog(0, "smtp", "%q from %s - bad response: %r", ds->expand, cs);
- return;
- }
- ds->host = p+1;
+ return -1;
+}
- /* take only first one returned (quasi-bug) */
- if((p = strchr(ds->host, ' ')) != nil)
- *p = 0;
+int
+mxdial(char *addr, char *ddomain, char *gdomain, Mx *x)
+{
+ int fd;
+ Mxtab mx;
+
+ memset(x, 0, sizeof *x);
+ fd = mxdial0(addr, ddomain, gdomain, &mx);
+ if(fd >= 0 && mx.pmx >= 0)
+ *x = mx.mx[mx.pmx];
+ mxtabfree(&mx);
+ return fd;
}
--- /dev/null
+++ b/sys/src/cmd/upas/smtp/parsetest.c
@@ -1,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <String.h>
+#include <bio.h>
+#include "smtp.h"
+
+Biobuf o;
+
+void
+freefields(void)
+{
+ Field *f, *fn;
+ Node *n, *nn;
+
+ for(f = firstfield; f != nil; f = fn){
+ fn = f->next;
+ for(n = f->node; n != nil; n = nn){
+ nn = n->next;
+ s_free(n->s);
+ s_free(n->white);
+ free(n);
+ }
+ free(f);
+ }
+ firstfield = nil;
+}
+
+void
+printhdr(void)
+{
+ Field *f;
+ Node *n;
+
+ for(f = firstfield; f != nil; f = f->next){
+ for(n = f->node; n != nil; n = n->next){
+ if(n->s != nil)
+ Bprint(&o, "%s", s_to_c(n->s));
+ else
+ Bprint(&o, "%c", n->c);
+ if(n->white != nil)
+ Bprint(&o, "%s", s_to_c(n->white));
+ }
+ Bprint(&o, "\n");
+ }
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: parsetest file ...\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int i, fd, nbuf;
+ char *buf;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(Binit(&o, 1, OWRITE) == -1)
+ sysfatal("Binit: %r");
+ for(i = 0; i < argc; i++){
+ fd = open(argv[i], OREAD);
+ if(fd == -1)
+ sysfatal("open: %r");
+ buf = malloc(128*1024);
+ if(buf == nil)
+ sysfatal("malloc: %r");
+ nbuf = read(fd, buf, 128*1024);
+ if(nbuf == -1)
+ sysfatal("read: %r");
+ close(fd);
+ yyinit(buf, nbuf);
+ yyparse();
+ printhdr();
+ freefields();
+ free(buf);
+ Bflush(&o);
+ }
+ exits("");
+}
--- a/sys/src/cmd/upas/smtp/rfc822.y
+++ b/sys/src/cmd/upas/smtp/rfc822.y
@@ -3,8 +3,6 @@
#include "smtp.h"
#include <ctype.h>
-#define YYMAXDEPTH 500 /* was default 150 */
-
char *yylp; /* next character to be lex'd */
int yydone; /* tell yylex to give up */
char *yybuffer; /* first parsed character */
@@ -54,11 +52,12 @@
msg : fields
| unixfrom '\n' fields
;
-fields : '\n'
+fields : fieldlist '\n'
{ yydone = 1; }
- | field '\n'
- | field '\n' fields
;
+fieldlist : field '\n'
+ | fieldlist field '\n'
+ ;
field : dates
{ date = 1; }
| originator
@@ -304,14 +303,12 @@
*/
yylex(void)
{
- String *t;
- int quoting;
- int escaping;
char *start;
+ int quoting, escaping, c, d;
+ String *t;
Keyword *kp;
- int c, d;
-/* print("lexing\n"); /**/
+// print("lexing\n");
if(yylp >= yyend)
return 0;
if(yydone)
@@ -331,15 +328,15 @@
if(c == 0)
continue;
- if(escaping) {
+ if(escaping)
escaping = 0;
- } else if(quoting) {
+ else if(quoting){
switch(c){
case '\\':
escaping = 1;
break;
case '\n':
- d = (*(yylp+1))&0xff;
+ d = yylp[1] & 0xff;
if(d != ' ' && d != '\t'){
quoting = 0;
yylp--;
@@ -350,7 +347,7 @@
quoting = 0;
break;
}
- } else {
+ }else{
switch(c){
case '\\':
escaping = 1;
@@ -363,7 +360,7 @@
case '\n':
if(yylp == start){
yylp++;
-/* print("lex(c %c)\n", c); /**/
+// print("lex(c %c)\n", c);
yylval->end = yylp;
return yylval->c = c;
}
@@ -377,7 +374,7 @@
if(yylp == start){
yylp++;
yylval->white = yywhite();
-/* print("lex(c %c)\n", c); /**/
+// print("lex(c %c)\n", c);
yylval->end = yylp;
return yylval->c = c;
}
@@ -395,25 +392,23 @@
}
out:
yylval->white = yywhite();
- if(t) {
+ if(t)
s_terminate(t);
- } else /* message begins with white-space! */
+ else /* message begins with white-space! */
return yylval->c = '\n';
yylval->s = t;
for(kp = key; kp->val != WORD; kp++)
- if(cistrcmp(s_to_c(t), kp->rep)==0)
+ if(cistrcmp(s_to_c(t), kp->rep) == 0)
break;
-/* print("lex(%d) %s\n", kp->val-WORD, s_to_c(t)); /**/
+// print("lex(%d) %s\n", kp->val - WORD, s_to_c(t));
yylval->end = yylp;
return yylval->c = kp->val;
}
void
-yyerror(char *x)
+yyerror(char*)
{
- USED(x);
-
- /*fprint(2, "parse err: %s\n", x);/**/
+// fprint(2, "parse err: %s\n", x);
}
/*
@@ -423,9 +418,7 @@
yywhite(void)
{
String *w;
- int clevel;
- int c;
- int escaping;
+ int clevel, c, escaping;
escaping = clevel = 0;
for(w = 0; yylp < yyend; yylp++){
@@ -435,15 +428,15 @@
if(c == 0)
continue;
- if(escaping){
+ if(escaping)
escaping = 0;
- } else if(clevel) {
+ else if(clevel){
switch(c){
case '\n':
/*
* look for multiline fields
*/
- if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ if(yylp[1] == ' ' || yylp[1] == '\t')
break;
else
goto out;
@@ -473,7 +466,7 @@
/*
* look for multiline fields
*/
- if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+ if(yylp[1] == ' ' || yylp[1] == '\t')
break;
else
goto out;
@@ -533,7 +526,7 @@
if(p1->white){
if(p2->white)
s_append(p1->white, s_to_c(p2->white));
- } else {
+ }else{
p1->white = p2->white;
p2->white = 0;
}
@@ -573,7 +566,7 @@
if(p2->s)
s_append(p1->s, s_to_c(p2->s));
- else {
+ else{
buf[0] = p2->c;
buf[1] = 0;
s_append(p1->s, buf);
@@ -610,23 +603,6 @@
}
/*
- * case independent string compare
- */
-int
-cistrcmp(char *s1, char *s2)
-{
- int c1, c2;
-
- for(; *s1; s1++, s2++){
- c1 = isupper(*s1) ? tolower(*s1) : *s1;
- c2 = isupper(*s2) ? tolower(*s2) : *s2;
- if (c1 != c2)
- return -1;
- }
- return *s2;
-}
-
-/*
* free a node
*/
void
@@ -673,10 +649,10 @@
start = yybuffer;
if(lastfield != nil){
for(np = lastfield->node; np; np = np->next)
- start = np->end+1;
+ start = np->end + 1;
}
- end = p->start-1;
+ end = p->start - 1;
if(end <= start)
return;
@@ -689,7 +665,7 @@
np->end = end;
np->white = nil;
s = s_copy("BadHeader: ");
- np->s = s_nappend(s, start, end-start);
+ np->s = s_nappend(s, start, end - start);
np->next = nil;
f = malloc(sizeof(Field));
--- a/sys/src/cmd/upas/smtp/rmtdns.c
+++ /dev/null
@@ -1,59 +1,0 @@
-#include "common.h"
-#include <ndb.h>
-
-int
-rmtdns(char *net, char *path)
-{
- int fd, n, nb, r;
- char *domain, *cp, buf[1024];
-
- if(net == 0 || path == 0)
- return 0;
-
- domain = strdup(path);
- cp = strchr(domain, '!');
- if(cp){
- *cp = 0;
- n = cp-domain;
- } else
- n = strlen(domain);
-
- if(*domain == '[' && domain[n-1] == ']'){ /* accept [nnn.nnn.nnn.nnn] */
- domain[n-1] = 0;
- r = strcmp(ipattr(domain+1), "ip");
- domain[n-1] = ']';
- } else
- r = strcmp(ipattr(domain), "ip"); /* accept nnn.nnn.nnn.nnn */
- if(r == 0){
- free(domain);
- return 0;
- }
-
- snprint(buf, sizeof buf, "%s/dns", net);
- fd = open(buf, ORDWR); /* look up all others */
- if(fd < 0){ /* dns screw up - can't check */
- free(domain);
- return 0;
- }
-
- n = snprint(buf, sizeof buf, "%s all", domain);
- free(domain);
- seek(fd, 0, 0);
- nb = write(fd, buf, n);
- close(fd);
- if(nb != n){
- rerrstr(buf, sizeof buf);
- if (strcmp(buf, "dns: name does not exist") == 0)
- return -1;
- }
- return 0;
-}
-
-/*
-void
-main(int, char *argv[])
-{
- print("return = %d\n", rmtdns("/net.alt", argv[1]));
- exits(0);
-}
-*/
--- a/sys/src/cmd/upas/smtp/smtp.c
+++ b/sys/src/cmd/upas/smtp/smtp.c
@@ -5,7 +5,7 @@
#include <libsec.h>
#include <auth.h>
-static char* connect(char*);
+static char* connect(char*, Mx*);
static char* wraptls(void);
static char* dotls(char*);
static char* doauth(char*);
@@ -15,7 +15,7 @@
String* convertheader(String*);
int dBprint(char*, ...);
int dBputc(int);
-char* data(String*, Biobuf*);
+char* data(String*, Biobuf*, Mx*);
char* domainify(char*, char*);
String* fixrouteaddr(String*, Node*, Node*);
char* getcrnl(String*);
@@ -27,7 +27,7 @@
void putcrnl(char*, int);
void quit(char*);
char* rcptto(char*);
-char* rewritezone(char *);
+char *rewritezone(char *);
#define Retry "Retry, Temporary Failure"
#define Giveup "Permanent Failure"
@@ -53,6 +53,7 @@
char *farend; /* system we are trying to send to */
char *user; /* user we are authenticating as, if authenticating */
char hostdomain[256];
+Mx *tmmx; /* global for timeout */
Biobuf bin;
Biobuf bout;
@@ -59,8 +60,27 @@
Biobuf berr;
Biobuf bfile;
-static int bustedmx;
+int
+Dfmt(Fmt *fmt)
+{
+ Mx *mx;
+ mx = va_arg(fmt->args, Mx*);
+ if(mx == nil || mx->host[0] == 0)
+ return fmtstrcpy(fmt, "");
+ else
+ return fmtprint(fmt, "(%s:%s)", mx->host, mx->ip);
+}
+#pragma varargck type "D" Mx*
+
+char*
+deliverytype(void)
+{
+ if(ping)
+ return "ping";
+ return "delivery";
+}
+
void
usage(void)
{
@@ -70,10 +90,9 @@
}
int
-timeout(void *x, char *msg)
+timeout(void *, char *msg)
{
- USED(x);
- syslog(0, "smtp.fail", "interrupt: %s: %s", farend, msg);
+ syslog(0, "smtp.fail", "%s interrupt: %s: %s %D", deliverytype(), farend, msg, tmmx);
if(strstr(msg, "alarm")){
fprint(2, "smtp timeout: connection to %s timed out\n", farend);
if(quitting)
@@ -81,12 +100,12 @@
exits(Retry);
}
if(strstr(msg, "closed pipe")){
- /* call _exits() to prevent Bio from trying to flush closed pipe */
fprint(2, "smtp timeout: connection closed to %s\n", farend);
if(quitting){
- syslog(0, "smtp.fail", "closed pipe to %s", farend);
+ syslog(0, "smtp.fail", "%s closed pipe to %s %D", deliverytype(), farend, tmmx);
_exits(quitrv);
}
+ /* call _exits() to prevent Bio from trying to flush closed pipe */
_exits(Retry);
}
return 0;
@@ -95,7 +114,7 @@
void
removenewline(char *p)
{
- int n = strlen(p)-1;
+ int n = strlen(p) - 1;
if(n < 0)
return;
@@ -106,18 +125,21 @@
void
main(int argc, char **argv)
{
- int i, ok, rcvrs;
- char *addr, *rv, *trv, *host, *domain;
- char **errs;
- char hellodomain[256];
+ char *phase, *addr, *rv, *trv, *host, *domain;
+ char **errs, *p, *e, hellodomain[256], allrx[512];
+ int i, ok, rcvrs, bustedmx;
String *from, *fromm, *sender;
+ Mx mx;
alarmscale = 60*1000; /* minutes */
quotefmtinstall();
+ mailfmtinstall(); /* 2047 encoding */
+ fmtinstall('D', Dfmt);
fmtinstall('[', encodefmt);
errs = malloc(argc*sizeof(char*));
reply = s_new();
host = 0;
+ bustedmx = 0;
ARGBEGIN{
case 'a':
tryauth = 1;
@@ -128,7 +150,7 @@
autistic = 1;
break;
case 'b':
- if (bustedmx >= Maxbustedmx)
+ if(bustedmx >= Maxbustedmx)
sysfatal("more than %d busted mxs given", Maxbustedmx);
bustedmxs[bustedmx++] = EARGF(usage());
break;
@@ -172,7 +194,7 @@
/*
* get domain and add to host name
*/
- if(*argv && **argv=='.') {
+ if(*argv && **argv=='.'){
domain = *argv;
argv++; argc--;
} else
@@ -189,6 +211,11 @@
usage();
addr = *argv++; argc--;
farend = addr;
+ if((rv = strrchr(addr, '!')) && rv[1] == '['){
+ syslog(0, "smtp.fail", "%s to %s failed: illegal address",
+ deliverytype(), addr);
+ exits(Giveup);
+ }
/*
* get sender's machine.
@@ -209,30 +236,40 @@
/*
* send the mail
*/
+ rcvrs = 0;
+ phase = "";
+ USED(phase); /* just in case */
if(filter){
Binit(&bout, 1, OWRITE);
- rv = data(from, &bfile);
- if(rv != 0)
+ rv = data(from, &bfile, nil);
+ if(rv != 0){
+ phase = "filter";
goto error;
+ }
exits(0);
}
/* mxdial uses its own timeout handler */
- if((rv = connect(addr)) != 0)
+ if((rv = connect(addr, &mx)) != 0)
exits(rv);
+ tmmx = &mx;
/* 10 minutes to get through the initial handshake */
atnotify(timeout, 1);
alarm(10*alarmscale);
- if((rv = hello(hellodomain, 0)) != 0)
+ if((rv = hello(hellodomain, 0)) != 0){
+ phase = "hello";
goto error;
+ }
alarm(10*alarmscale);
- if((rv = mailfrom(s_to_c(from))) != 0)
+ if((rv = mailfrom(s_to_c(from))) != 0){
+ phase = "mailfrom";
goto error;
+ }
ok = 0;
- rcvrs = 0;
/* if any rcvrs are ok, we try to send the message */
+ phase = "rcptto";
for(i = 0; i < argc; i++){
if((trv = rcptto(argv[i])) != 0){
/* remember worst error */
@@ -248,6 +285,8 @@
}
/* if no ok rcvrs or worst error is retry, give up */
+ if(ok == 0 && rcvrs == 0)
+ phase = "rcptto; no addresses";
if(ok == 0 || rv == Retry)
goto error;
@@ -256,7 +295,7 @@
exits(0);
}
- rv = data(from, &bfile);
+ rv = data(from, &bfile, &mx);
if(rv != 0)
goto error;
quit(0);
@@ -266,10 +305,11 @@
/*
* here when some but not all rcvrs failed
*/
- fprint(2, "%s connect to %s:\n", thedate(), addr);
+ fprint(2, "%s connect to %s: %D %s:\n", thedate(), addr, &mx, phase);
for(i = 0; i < rcvrs; i++){
if(errs[i]){
- syslog(0, "smtp.fail", "delivery to %s at %s failed: %s", argv[i], addr, errs[i]);
+ syslog(0, "smtp.fail", "delivery to %s at %s %D %s, failed: %s",
+ argv[i], addr, &mx, phase, errs[i]);
fprint(2, " mail to %s failed: %s", argv[i], errs[i]);
}
}
@@ -279,11 +319,19 @@
* here when all rcvrs failed
*/
error:
+ alarm(0);
removenewline(s_to_c(reply));
- syslog(0, "smtp.fail", "%s to %s failed: %s",
- ping ? "ping" : "delivery",
- addr, s_to_c(reply));
- fprint(2, "%s connect to %s:\n%s\n", thedate(), addr, s_to_c(reply));
+ if(rcvrs > 0){
+ p = allrx;
+ e = allrx + sizeof allrx;
+ seprint(p, e, "to ");
+ for(i = 0; i < rcvrs - 1; i++)
+ p = seprint(p, e, "%s,", argv[i]);
+ seprint(p, e, "%s ", argv[i]);
+ }
+ syslog(0, "smtp.fail", "%s %s at %s %D %s failed: %s",
+ deliverytype(), allrx, addr, &mx, phase, s_to_c(reply));
+ fprint(2, "%s connect to %s %D %s:\n%s\n", thedate(), addr, &mx, phase, s_to_c(reply));
if(!filter)
quit(rv);
exits(rv);
@@ -293,17 +341,17 @@
* connect to the remote host
*/
static char *
-connect(char* net)
+connect(char* net, Mx *mx)
{
- char buf[Errlen];
+ char buf[ERRMAX];
int fd;
- fd = mxdial(net, ddomain, gdomain);
+ fd = mxdial(net, ddomain, gdomain, mx);
if(fd < 0){
- rerrstr(buf, sizeof(buf));
- Bprint(&berr, "smtp: %s (%s)\n", buf, net);
- syslog(0, "smtp.fail", "%s (%s)", buf, net);
+ rerrstr(buf, sizeof buf);
+ Bprint(&berr, "smtp: %s (%s) %D\n", buf, net, mx);
+ syslog(0, "smtp.fail", "%s %s (%s) %D", deliverytype(), buf, net, mx);
if(strstr(buf, "illegal")
|| strstr(buf, "unknown")
|| strstr(buf, "can't translate"))
@@ -320,7 +368,20 @@
static char smtpthumbs[] = "/sys/lib/tls/smtp";
static char smtpexclthumbs[] = "/sys/lib/tls/smtp.exclude";
-static char *
+static int
+tracetls(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ Bvprint(&berr, fmt, ap);
+ Bprint(&berr, "\n");
+ Bflush(&berr);
+ va_end(ap);
+ return 0;
+}
+
+static char*
wraptls(void)
{
TLSconn *c;
@@ -335,6 +396,9 @@
if (c == nil)
return err;
+ if (debug)
+ c->trace = tracetls;
+
fd = tlsClient(Bfildes(&bout), c);
if (fd < 0) {
syslog(0, "smtp", "tlsClient to %q: %r", ddomain);
@@ -396,6 +460,36 @@
return(hello(me, 1));
}
+static char*
+smtpcram(DS *ds)
+{
+ char *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
+ int i, n, l;
+
+ dBprint("AUTH CRAM-MD5\r\n");
+ if(getreply() != 3)
+ return Retry;
+ p = s_to_c(reply) + 4;
+ l = dec64((uchar*)ch, sizeof ch, p, strlen(p));
+ ch[l] = 0;
+ n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
+ user?"proto=cram role=client server=%q user=%q":"proto=cram role=client server=%q",
+ ds->host, user);
+ if(n == -1)
+ return "cannot find SMTP password";
+ if(usr[0] == 0)
+ return "cannot find user name";
+ for(i = 0; i < n; i++)
+ rbuf[i] = tolower(rbuf[i]);
+ l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf);
+ snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf);
+
+ dBprint("%s\r\n", ch);
+ if(getreply() != 2)
+ return Retry;
+ return nil;
+}
+
static char *
doauth(char *methods)
{
@@ -404,14 +498,13 @@
int n;
DS ds;
- dial_string_parse(ddomain, &ds);
+ dialstringparse(ddomain, &ds);
+ if(strstr(methods, "CRAM-MD5"))
+ return smtpcram(&ds);
- if(user != nil)
- p = auth_getuserpasswd(nil,
- "proto=pass service=smtp server=%q user=%q", ds.host, user);
- else
- p = auth_getuserpasswd(nil,
- "proto=pass service=smtp server=%q", ds.host);
+ p = auth_getuserpasswd(nil,
+ user?"proto=pass service=smtp server=%q user=%q":"proto=pass service=smtp server=%q",
+ ds.host, user);
if (p == nil)
return Giveup;
@@ -455,14 +548,14 @@
return err;
}
-char *
+char*
hello(char *me, int encrypted)
{
+ char *ret, *s, *t;
int ehlo;
String *r;
- char *ret, *s, *t;
- if (!encrypted) {
+ if(!encrypted){
if(trysecure > 1){
if((ret = wraptls()) != nil)
return ret;
@@ -474,7 +567,7 @@
* answers a call. Send a no-op in the hope of making it
* talk.
*/
- if (autistic) {
+ if(autistic){
dBprint("NOOP\r\n");
getreply(); /* consume the smtp greeting */
/* next reply will be response to noop */
@@ -495,7 +588,7 @@
dBprint("EHLO %s\r\n", me);
else
dBprint("HELO %s\r\n", me);
- switch (getreply()) {
+ switch(getreply()){
case 2:
break;
case 5:
@@ -514,17 +607,15 @@
/* Invariant: every line has a newline, a result of getcrlf() */
for(s = s_to_c(r); (t = strchr(s, '\n')) != nil; s = t + 1){
*t = '\0';
- for (t = s; *t != '\0'; t++)
- *t = toupper(*t);
if(!encrypted && trysecure &&
- (strcmp(s, "250-STARTTLS") == 0 ||
- strcmp(s, "250 STARTTLS") == 0)){
+ (cistrcmp(s, "250-STARTTLS") == 0 ||
+ cistrcmp(s, "250 STARTTLS") == 0)){
s_free(r);
return dotls(me);
}
if(tryauth && (encrypted || insecure) &&
- (strncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
- strncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
+ (cistrncmp(s, "250 AUTH", strlen("250 AUTH")) == 0 ||
+ cistrncmp(s, "250-AUTH", strlen("250 AUTH")) == 0)){
ret = doauth(s + strlen("250 AUTH "));
s_free(r);
return ret;
@@ -542,20 +633,18 @@
{
if(!returnable(from))
dBprint("MAIL FROM:<>\r\n");
- else
- if(strchr(from, '@'))
+ else if(strchr(from, '@'))
dBprint("MAIL FROM:<%s>\r\n", from);
else
dBprint("MAIL FROM:<%s@%s>\r\n", from, hostdomain);
switch(getreply()){
case 2:
- break;
+ return 0;
case 5:
return Giveup;
default:
return Retry;
}
- return 0;
}
/*
@@ -597,13 +686,11 @@
* send the damn thing
*/
char *
-data(String *from, Biobuf *b)
+data(String *from, Biobuf *b, Mx *mx)
{
- char *buf, *cp;
+ char *buf, *cp, errmsg[ERRMAX], id[40];
int i, n, nbytes, bufsize, eof, r;
String *fromline;
- char errmsg[Errlen];
- char id[40];
/*
* input the header.
@@ -623,12 +710,12 @@
break;
}
nbytes = Blinelen(b);
- buf = realloc(buf, n+nbytes+1);
+ buf = realloc(buf, n + nbytes + 1);
if(buf == 0){
s_append(s_restart(reply), "out of memory");
return Retry;
}
- strncpy(buf+n, cp, nbytes);
+ strncpy(buf + n, cp, nbytes);
n += nbytes;
if(nbytes == 1) /* end of header */
break;
@@ -669,10 +756,10 @@
srand(truerand());
if(messageid == 0){
- for(i=0; i<16; i++){
- r = rand()&0xFF;
- id[2*i] = hex[r&0xF];
- id[2*i+1] = hex[(r>>4)&0xF];
+ for(i = 0; i < 16; i++){
+ r = rand() & 0xff;
+ id[2*i] = hex[r & 0xf];
+ id[2*i + 1] = hex[(r>>4) & 0xf];
}
id[2*i] = '\0';
nbytes += Bprint(&bout, "Message-ID: <%s@%s>\r\n", id, hostdomain);
@@ -680,7 +767,7 @@
Bprint(&berr, "Message-ID: <%s@%s>\r\n", id, hostdomain);
}
- if(originator==0){
+ if(originator == 0){
nbytes += Bprint(&bout, "From: %s\r\n", s_to_c(fromline));
if(debug)
Bprint(&berr, "From: %s\r\n", s_to_c(fromline));
@@ -698,12 +785,12 @@
Bprint(&berr, "To: %s\r\n", s_to_c(toline));
}
- if(date==0 && udate)
+ if(date == 0 && udate)
nbytes += printdate(udate);
- if (usys)
+ if(usys)
uneaten = usys->end + 1;
nbytes += printheader();
- if (*uneaten != '\n')
+ if(*uneaten != '\n')
putcrnl("\n", 1);
/*
@@ -710,8 +797,8 @@
* send body
*/
- putcrnl(uneaten, buf+n - uneaten);
- nbytes += buf+n - uneaten;
+ putcrnl(uneaten, buf + n - uneaten);
+ nbytes += buf + n - uneaten;
if(eof == 0){
for(;;){
n = Bread(b, buf, bufsize);
@@ -743,8 +830,8 @@
default:
return Retry;
}
- syslog(0, "smtp", "%s sent %d bytes to %s", s_to_c(from),
- nbytes, s_to_c(toline));/**/
+ syslog(0, "smtp", "%s sent %d bytes to %s %D", s_to_c(from),
+ nbytes, s_to_c(toline), mx);
}
return 0;
}
@@ -806,10 +893,9 @@
String *
bangtoat(char *addr)
{
- String *buf;
- register int i;
- int j, d;
char *field[128];
+ int i, j, d;
+ String *buf;
/* parse the '!' format address */
buf = s_new();
@@ -819,7 +905,7 @@
if(addr)
*addr++ = 0;
}
- if (i==1) {
+ if(i == 1){
s_append(buf, field[0]);
return buf;
}
@@ -827,8 +913,8 @@
/*
* count leading domain fields (non-domains don't count)
*/
- for(d = 0; d<i-1; d++)
- if(strchr(field[d], '.')==0)
+ for(d = 0; d < i - 1; d++)
+ if(strchr(field[d], '.') == 0)
break;
/*
* if there are more than 1 leading domain elements,
@@ -836,7 +922,7 @@
*/
if(d > 1){
addhostdom(buf, field[0]);
- for(j=1; j<d-1; j++){
+ for(j = 1; j< d - 1; j++){
s_append(buf, ",");
s_append(buf, "@");
s_append(buf, field[j]);
@@ -848,7 +934,7 @@
* throw in the non-domain elements separated by '!'s
*/
s_append(buf, field[d]);
- for(j=d+1; j<=i-1; j++) {
+ for(j = d + 1; j <= i - 1; j++){
s_append(buf, "!");
s_append(buf, field[j]);
}
@@ -865,6 +951,7 @@
String*
convertheader(String *from)
{
+ char *s, buf[64];
Field *f;
Node *p, *lastp;
String *a;
@@ -875,8 +962,10 @@
addhostdom(from, hostdomain);
} else
if(strchr(s_to_c(from), '@') == 0){
- a = username(from);
- if(a) {
+ if(s = username(s_to_c(from))){
+ /* this has always been here, but username() was broken */
+ snprint(buf, sizeof buf, "%U", s);
+ s_append(a = s_new(), buf);
s_append(a, " <");
s_append(a, s_to_c(from));
addhostdom(a, hostdomain);
@@ -929,11 +1018,10 @@
int
printheader(void)
{
+ char *cp, c[1];
int n, len;
Field *f;
Node *p;
- char *cp;
- char c[1];
n = 0;
for(f = firstfield; f; f = f->next){
@@ -966,10 +1054,10 @@
char *
domainify(char *name, char *domain)
{
- static String *s;
char *p;
+ static String *s;
- if(domain==0 || strchr(name, '.')!=0)
+ if(domain == 0 || strchr(name, '.') != 0)
return name;
s = s_reset(s);
@@ -1008,8 +1096,7 @@
char *
getcrnl(String *s)
{
- int c;
- int count;
+ int c, count;
count = 0;
for(;;){
@@ -1052,16 +1139,17 @@
int
printdate(Node *p)
{
- int n, sep = 0;
+ int n, sep;
n = dBprint("Date: %s,", s_to_c(p->s));
+ sep = 0;
for(p = p->next; p; p = p->next){
if(p->s){
- if(sep == 0) {
+ if(sep == 0){
dBputc(' ');
n++;
}
- if (p->next)
+ if(p->next)
n += dBprint("%s", s_to_c(p->s));
else
n += dBprint("%s", rewritezone(s_to_c(p->s)));
@@ -1079,8 +1167,8 @@
char *
rewritezone(char *z)
{
- int mindiff;
char s;
+ int mindiff;
Tm *tm;
static char x[7];
@@ -1104,22 +1192,22 @@
/*
* stolen from libc/port/print.c
*/
-#define SIZE 4096
+
int
dBprint(char *fmt, ...)
{
- char buf[SIZE], *out;
- va_list arg;
+ char buf[4096], *out;
int n;
+ va_list arg;
va_start(arg, fmt);
- out = vseprint(buf, buf+SIZE, fmt, arg);
+ out = vseprint(buf, buf + sizeof buf, fmt, arg);
va_end(arg);
if(debug){
- Bwrite(&berr, buf, (long)(out-buf));
+ Bwrite(&berr, buf, out - buf);
Bflush(&berr);
}
- n = Bwrite(&bout, buf, (long)(out-buf));
+ n = Bwrite(&bout, buf,out - buf);
Bflush(&bout);
return n;
}
--- a/sys/src/cmd/upas/smtp/smtp.h
+++ b/sys/src/cmd/upas/smtp/smtp.h
@@ -24,8 +24,11 @@
};
typedef struct DS DS;
+typedef struct Mx Mx;
+typedef struct Mxtab Mxtab;
+
struct DS {
- /* dist string */
+ /* dial string */
char buf[128];
char expand[128];
char *netdir;
@@ -34,6 +37,25 @@
char *service;
};
+struct Mx
+{
+ char *netdir;
+ char host[256];
+ char ip[24];
+ int pref;
+ int valid;
+};
+
+struct Mxtab
+{
+ DS ds[2];
+ int nmx;
+ int amx;
+ int pmx;
+ int fd;
+ Mx *mx;
+};
+
extern Field *firstfield;
extern Field *lastfield;
extern Node *usender;
@@ -51,7 +73,6 @@
int badfieldname(Node*);
Node* bang(Node*, Node*);
Node* colon(Node*, Node*);
-int cistrcmp(char*, char*);
Node* link2(Node*, Node*);
Node* link3(Node*, Node*, Node*);
void freenode(Node*);
@@ -63,5 +84,9 @@
String* yywhite(void);
Node* whiten(Node*);
void yycleanup(void);
-int mxdial(char*, char*, char*);
-void dial_string_parse(char*, DS*);
+int mxdial0(char*, char*, char*, Mxtab*);
+int mxdial(char*, char*, char*, Mx*);
+void mxtabfree(Mxtab*);
+void dialstringparse(char*, DS*);
+
+#define dprint(...) do if(debug)print(__VA_ARGS__); while(0)
--- a/sys/src/cmd/upas/smtp/smtpd.c
+++ b/sys/src/cmd/upas/smtp/smtpd.c
@@ -22,14 +22,15 @@
int rejectcount;
int hardreject;
-ulong starttime;
-
Biobuf bin;
int debug;
int Dflag;
+int Eflag;
+int eflag;
int fflag;
int gflag;
+int qflag;
int rflag;
int sflag;
int authenticate;
@@ -50,41 +51,28 @@
int rejectcheck(void);
String* startcmd(void);
-static void logmsg(char *action);
-
static int
-catchalarm(void *a, char *msg)
+catchalarm(void*, char *msg)
{
- int rv;
+ int ign;
+ static int chattycathy;
- USED(a);
-
- /* log alarms but continue */
- if(strstr(msg, "alarm") != nil){
- if(senders.first && senders.first->p &&
- rcvers.first && rcvers.first->p)
+ ign = strstr(msg, "closed pipe") != nil;
+ if(ign)
+ return 0;
+ if(chattycathy++ < 5){
+ if(senders.first && rcvers.first)
syslog(0, "smtpd", "note: %s->%s: %s",
s_to_c(senders.first->p),
s_to_c(rcvers.first->p), msg);
else
syslog(0, "smtpd", "note: %s", msg);
- rv = Atnoterecog;
- } else
- rv = Atnoteunknown;
- if (debug) {
- seek(2, 0, 2);
- fprint(2, "caught note: %s\n", msg);
}
-
- /* kill the children if there are any */
- if(pp && pp->pid > 0) {
- syskillpg(pp->pid);
- /* none can't syskillpg, so try a variant */
- sleep(500);
+ if(pp){
syskill(pp->pid);
+ // pp = 0;
}
-
- return rv;
+ return strstr(msg, "alarm") != nil;
}
/* override string error functions to do something reasonable */
@@ -91,7 +79,7 @@
void
s_error(char *f, char *status)
{
- char errbuf[Errlen];
+ char errbuf[ERRMAX];
errbuf[0] = 0;
rerrstr(errbuf, sizeof(errbuf));
@@ -106,8 +94,7 @@
static void
usage(void)
{
- fprint(2,
- "usage: smtpd [-adDfghprs] [-c cert] [-k ip] [-m mailer] [-n net]\n");
+ fprint(2, "usage: smtpd [-DEadefghpqrs] [-c cert] [-k ip] [-m mailer] [-n net]\n");
exits("usage");
}
@@ -121,7 +108,6 @@
quotefmtinstall();
fmtinstall('I', eipfmt);
fmtinstall('[', encodefmt);
- starttime = time(0);
ARGBEGIN{
case 'a':
authenticate = 1;
@@ -135,6 +121,12 @@
case 'd':
debug++;
break;
+ case 'E':
+ Eflag = 1;
+ break; /* if you fail extra helo checks, you must authenticate */
+ case 'e':
+ eflag = 1; /* disable extra helo checks */
+ break;
case 'f': /* disallow relaying */
fflag = 1;
break;
@@ -156,6 +148,9 @@
case 'p':
passwordinclear = 1;
break;
+ case 'q':
+ qflag = 1; /* don't log invalid hello */
+ break;
case 'r':
rflag = 1; /* verify sender's domain */
break;
@@ -162,11 +157,6 @@
case 's': /* save blocked messages */
sflag = 1;
break;
- case 't':
- fprint(2, "%s: the -t option is no longer supported, see -c\n",
- argv0);
- tlscert = "/sys/lib/ssl/smtpd-cert.pem";
- break;
default:
usage();
}ARGEND;
@@ -173,7 +163,7 @@
nci = getnetconninfo(netdir, 0);
if(nci == nil)
- sysfatal("can't get remote system's address: %r");
+ sysfatal("can't get remote system's address");
parseip(rsysip, nci->rsys);
if(mailer == nil)
@@ -180,9 +170,9 @@
mailer = mailerpath("send");
if(debug){
- snprint(buf, sizeof buf, "%s/smtpdb/%ld", UPASLOG, time(0));
close(2);
- if (create(buf, OWRITE | OEXCL, 0662) >= 0) {
+ snprint(buf, sizeof(buf), "%s/smtpd.db", UPASLOG);
+ if (open(buf, OWRITE) >= 0) {
seek(2, 0, 2);
fprint(2, "%d smtpd %s\n", getpid(), thedate());
} else
@@ -189,8 +179,8 @@
debug = 0;
}
getconf();
- if (isbadguy())
- exits("banned");
+ if(isbadguy())
+ exits("");
Binit(&bin, 0, OREAD);
if (chdir(UPASLOG) < 0)
@@ -239,32 +229,20 @@
l->last = lp;
}
-void
-stamp(void)
-{
- if(debug) {
- seek(2, 0, 2);
- fprint(2, "%3lud ", time(0) - starttime);
- }
-}
-
-#define SIZE 4096
-
int
reply(char *fmt, ...)
{
- long n;
- char buf[SIZE], *out;
+ char buf[4096], *out;
+ int n;
va_list arg;
va_start(arg, fmt);
- out = vseprint(buf, buf+SIZE, fmt, arg);
+ out = vseprint(buf, buf + 4096, fmt, arg);
va_end(arg);
n = out - buf;
if(debug) {
seek(2, 0, 2);
- stamp();
write(2, buf, n);
}
write(1, buf, n);
@@ -291,6 +269,36 @@
reply("220 %s ESMTP\r\n", dom);
}
+Ndbtuple*
+rquery(char *d)
+{
+ Ndbtuple *t, *p;
+
+ t = dnsquery(nci->root, nci->rsys, "ptr");
+ for(p = t; p != nil; p = p->entry)
+ if(strcmp(p->attr, "dom") == 0
+ && strcmp(p->val, d) == 0){
+ syslog(0, "smtpd", "ptr only from %s as %s",
+ nci->rsys, d);
+ return t;
+ }
+ ndbfree(t);
+ return nil;
+}
+
+int
+dnsexists(char *d)
+{
+ int r;
+ Ndbtuple *t;
+
+ r = -1;
+ if((t = dnsquery(nci->root, d, "any")) != nil || (t = rquery(d)) != nil)
+ r = 0;
+ ndbfree(t);
+ return r;
+}
+
/*
* make callers from class A networks infested by spammers
* wait longer.
@@ -324,102 +332,153 @@
static int
delaysecs(void)
{
- if (trusted)
+ if (netaspam[rsysip[0]])
+ return 60;
+ return 15;
+}
+
+static char *badtld[] = {
+ "localdomain",
+ "localhost",
+ "local",
+ "example",
+ "invalid",
+ "lan",
+ "test",
+};
+
+static char *bad2ld[] = {
+ "example.com",
+ "example.net",
+ "example.org"
+};
+
+int
+badname(void)
+{
+ char *p;
+
+ /*
+ * similarly, if the claimed domain is not an address-literal,
+ * require at least one letter, which there will be in
+ * at least the last component (e.g., .com, .net) if it's real.
+ * this rejects non-address-literal IP addresses,
+ * among other bogosities.
+ */
+ for (p = him; *p; p++)
+ if(isascii(*p) && isalpha(*p))
+ return 0;
+ return -1;
+}
+
+int
+ckhello(void)
+{
+ char *ldot, *rdot;
+ int i;
+
+ /*
+ * it is unacceptable to claim any string that doesn't look like
+ * a domain name (e.g., has at least one dot in it), but
+ * Microsoft mail client software gets this wrong, so let trusted
+ * (local) clients omit the dot.
+ */
+ rdot = strrchr(him, '.');
+ if(rdot && rdot[1] == '\0') {
+ *rdot = '\0'; /* clobber trailing dot */
+ rdot = strrchr(him, '.'); /* try again */
+ }
+ if(rdot == nil)
+ return -1;
+ /*
+ * Reject obviously bogus domains and those reserved by RFC 2606.
+ */
+ if(rdot == nil)
+ rdot = him;
+ else
+ rdot++;
+ for(i = 0; i < nelem(badtld); i++)
+ if(!cistrcmp(rdot, badtld[i]))
+ return -1;
+ /* check second-level RFC 2606 domains: example\.(com|net|org) */
+ if(rdot != him)
+ *--rdot = '\0';
+ ldot = strrchr(him, '.');
+ if(rdot != him)
+ *rdot = '.';
+ if(ldot == nil)
+ ldot = him;
+ else
+ ldot++;
+ for(i = 0; i < nelem(bad2ld); i++)
+ if(!cistrcmp(ldot, bad2ld[i]))
+ return -1;
+ if(badname() == -1)
+ return -1;
+ if(dnsexists(him) == -1)
+ return -1;
+ return 0;
+}
+
+int
+heloclaims(void)
+{
+ char **s;
+
+ /*
+ * We don't care if he lies about who he is, but it is
+ * not okay to pretend to be us. Many viruses do this,
+ * just parroting back what we say in the greeting.
+ */
+ if(strcmp(nci->rsys, nci->lsys) == 0)
return 0;
- if (0 && netaspam[rsysip[0]])
- return 20;
- return 12;
+ if(strcmp(him, dom) == 0)
+ return -1;
+ for(s = sysnames_read(); s && *s; s++)
+ if(cistrcmp(*s, him) == 0)
+ return -1;
+ if(him[0] != '[' && badname() == -1)
+ return -1;
+
+ return 0;
}
void
hello(String *himp, int extended)
{
- char **mynames;
- char *ldot, *rdot;
- char *p;
+ int ck;
him = s_to_c(himp);
- syslog(0, "smtpd", "%s from %s as %s", extended? "ehlo": "helo",
- nci->rsys, him);
+ if(!qflag)
+ syslog(0, "smtpd", "%s from %s as %s", extended? "ehlo": "helo",
+ nci->rsys, him);
if(rejectcheck())
return;
- if (him[0] == '[') {
- /*
- * reject literal ip addresses when not trusted.
- */
- if (!trusted)
- goto Liarliar;
- him = nci->rsys;
- } else {
- if (!trusted && fflag && nci && strcmp(nci->rsys, nci->lsys) != 0){
- /*
- * We don't care if he lies about who he is, but it is
- * not okay to pretend to be us. Many viruses do this,
- * just parroting back what we say in the greeting.
- */
- if(cistrcmp(him, dom) == 0)
- goto Liarliar;
- for(mynames = sysnames_read(); mynames && *mynames; mynames++)
- if(cistrcmp(*mynames, him) == 0)
- goto Liarliar;
- }
-
- /*
- * require at least one letter, which there will be in
- * at least the last component (e.g., .com, .net) if it's real.
- * this rejects non-address-literal IP addresses,
- * among other bogosities.
- */
- for (p = him; *p != '\0'; p++)
- if (isascii(*p) && isalpha(*p))
- break;
- if (*p == '\0')
- goto Liarliar;
-
- /*
- * it is unacceptable to claim any string that doesn't look like
- * a domain name (e.g., has at least one dot in it), but
- * Microsoft mail client software gets this wrong, so let trusted
- * (local) clients omit the dot.
- */
- rdot = strrchr(him, '.');
- if (rdot && rdot[1] == '\0') {
- *rdot = '\0'; /* clobber trailing dot */
- rdot = strrchr(him, '.'); /* try again */
- }
- if (!trusted && rdot == nil)
- goto Liarliar;
-
- /*
- * Reject obviously bogus domains and those reserved by RFC 2606.
- */
- if (rdot == nil)
- rdot = him;
- else
- rdot++;
- if (cistrcmp(rdot, "localdomain") == 0 ||
- cistrcmp(rdot, "localhost") == 0 ||
- cistrcmp(rdot, "example") == 0 ||
- cistrcmp(rdot, "invalid") == 0 ||
- cistrcmp(rdot, "test") == 0)
- goto Liarliar; /* bad top-level domain */
- /* check second-level RFC 2606 domains: example\.(com|net|org) */
- if (rdot != him)
- *--rdot = '\0';
- ldot = strrchr(him, '.');
- if (rdot != him)
- *rdot = '.';
- if (ldot == nil)
- ldot = him;
- else
- ldot++;
- if (cistrcmp(ldot, "example.com") == 0 ||
- cistrcmp(ldot, "example.net") == 0 ||
- cistrcmp(ldot, "example.org") == 0)
- goto Liarliar;
+ ck = -1;
+ if(!trusted && nci)
+ if(heloclaims() || (!eflag && (ck = ckhello())))
+ if(ck && Eflag){
+ reply("250-you lie. authentication required.\r\n");
+ authenticate = 1;
+ }else{
+ if(Dflag)
+ sleep(delaysecs()*1000);
+ if(!qflag)
+ syslog(0, "smtpd", "Hung up on %s; claimed to be %s",
+ nci->rsys, him);
+ rejectcount++;
+ reply("554 5.7.0 Liar!\r\n");
+ exits("client pretended to be us");
+ return;
}
+ if(strchr(him, '.') == 0 && nci != nil && strchr(nci->rsys, '.') != nil)
+ him = nci->rsys;
+
+ if(qflag)
+ syslog(0, "smtpd", "%s from %s as %s", extended? "ehlo": "helo",
+ nci->rsys, him);
if(Dflag)
sleep(delaysecs()*1000);
reply("250%c%s you are %s\r\n", extended ? '-' : ' ', dom, him);
@@ -432,15 +491,6 @@
else
reply("250 AUTH CRAM-MD5\r\n");
}
- return;
-
-Liarliar:
- syslog(0, "smtpd", "Hung up on %s; claimed to be %s",
- nci->rsys, him);
- if(Dflag)
- sleep(delaysecs()*1000);
- reply("554 5.7.0 Liar!\r\n");
- exits("client pretended to be us");
}
void
@@ -481,33 +531,9 @@
/*
* see if this ip address, domain name, user name or account is blocked
*/
- logged = 0;
filterstate = blocked(path);
- /*
- * permanently reject what we can before trying smtp ping, which
- * often leads to merely temporary rejections.
- */
- switch (filterstate){
- case DENIED:
- syslog(0, "smtpd", "Denied %s (%s/%s)",
- s_to_c(path), him, nci->rsys);
- rejectcount++;
- logged++;
- reply("554-5.7.1 We don't accept mail from %s.\r\n",
- s_to_c(path));
- reply("554 5.7.1 Contact postmaster@%s for more information.\r\n",
- dom);
- return;
- case REFUSED:
- syslog(0, "smtpd", "Refused %s (%s/%s)",
- s_to_c(path), him, nci->rsys);
- rejectcount++;
- logged++;
- reply("554 5.7.1 Sender domain must exist: %s\r\n",
- s_to_c(path));
- return;
- }
+ logged = 0;
listadd(&senders, path);
reply("250 2.0.0 sender is %s\r\n", s_to_c(path));
}
@@ -642,7 +668,7 @@
if(!recipok(s_to_c(path))){
rejectcount++;
syslog(0, "smtpd",
- "Disallowed %s (%s/%s) to blocked, unknown or invalid name %s",
+ "Disallowed %s (%s/%s) to blocked name %s",
sender, him, nci->rsys, s_to_c(path));
reply("550 5.1.1 %s ... user unknown\r\n", s_to_c(path));
return;
@@ -660,11 +686,9 @@
/* forwarding() can modify 'path' on loopback request */
if(filterstate == ACCEPT && fflag && !authenticated && forwarding(path)) {
- syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)",
- senders.last && senders.last->p?
- s_to_c(senders.last->p): sender,
- him, nci->rsys, path? s_to_c(path): rcpt);
rejectcount++;
+ syslog(0, "smtpd", "Bad Forward %s (%s/%s) (%s)",
+ sender, him, nci->rsys, rcpt);
reply("550 5.7.1 we don't relay. send to your-path@[] for "
"loopback.\r\n");
return;
@@ -677,12 +701,6 @@
quit(void)
{
reply("221 2.0.0 Successful termination\r\n");
- if(debug){
- seek(2, 0, 2);
- stamp();
- fprint(2, "# %d sent 221 reply to QUIT %s\n",
- getpid(), thedate());
- }
close(0);
exits(0);
}
@@ -710,10 +728,14 @@
{
char *p, *q;
char *av[4];
+ static uint nverify;
if(rejectcheck())
return;
+ if(nverify++ >= 2)
+ sleep(1000 * (4 << nverify - 2));
if(shellchars(s_to_c(path))){
+ rejectcount++;
reply("503 5.1.3 Bad character in address %s.\r\n", s_to_c(path));
return;
}
@@ -722,7 +744,7 @@
av[2] = s_to_c(path);
av[3] = 0;
- pp = noshell_proc_start(av, (stream *)0, outstream(), (stream *)0, 1, 0);
+ pp = noshell_proc_start(av, 0, outstream(), 0, 1, 0);
if (pp == 0) {
reply("450 4.3.2 We're busy right now, try later\r\n");
return;
@@ -732,13 +754,13 @@
if(p == 0){
reply("550 5.1.0 String does not match anything.\r\n");
} else {
- p[Blinelen(pp->std[1]->fp)-1] = 0;
+ p[Blinelen(pp->std[1]->fp) - 1] = 0;
if(strchr(p, ':'))
reply("550 5.1.0 String does not match anything.\r\n");
else{
q = strrchr(p, '!');
if(q)
- p = q+1;
+ p = q + 1;
reply("250 2.0.0 %s <%s@%s>\r\n", s_to_c(path), p, dom);
}
}
@@ -765,6 +787,7 @@
}
switch(c){
case 0:
+ /* idiot html email! */
break;
case -1:
goto out;
@@ -774,7 +797,6 @@
if(debug) {
seek(2, 0, 2);
fprint(2, "%c", c);
- stamp();
}
s_putc(s, '\n');
goto out;
@@ -912,6 +934,7 @@
dom);
return 0;
case ACCEPT:
+ case TRUSTED:
/*
* now that all other filters have been passed,
* do grey-list processing.
@@ -918,9 +941,7 @@
*/
if(gflag)
vfysenderhostok();
- /* fall through */
- case TRUSTED:
/*
* set up mail command
*/
@@ -962,34 +983,24 @@
* address@him
*/
char*
-bprintnode(Biobuf *b, Node *p, int *cntp)
+bprintnode(Biobuf *b, Node *p, int *nbytes)
{
- int len;
+ int n, m;
- *cntp = 0;
if(p->s){
- if(p->addr && strchr(s_to_c(p->s), '@') == nil){
- if(Bprint(b, "%s@%s", s_to_c(p->s), him) < 0)
- return nil;
- *cntp += s_len(p->s) + 1 + strlen(him);
- } else {
- len = s_len(p->s);
- if(Bwrite(b, s_to_c(p->s), len) < 0)
- return nil;
- *cntp += len;
- }
- }else{
- if(Bputc(b, p->c) < 0)
- return nil;
- ++*cntp;
- }
- if(p->white) {
- len = s_len(p->white);
- if(Bwrite(b, s_to_c(p->white), len) < 0)
- return nil;
- *cntp += len;
- }
- return p->end+1;
+ if(p->addr && strchr(s_to_c(p->s), '@') == nil)
+ n = Bprint(b, "%s@%s", s_to_c(p->s), him);
+ else
+ n = Bwrite(b, s_to_c(p->s), s_len(p->s));
+ }else
+ n = Bputc(b, p->c) == -1? -1: 1;
+ m = 0;
+ if(n != -1 && p->white)
+ m = Bwrite(b, s_to_c(p->white), s_len(p->white));
+ if(n == -1 || m == -1)
+ return nil;
+ *nbytes += n + m;
+ return p->end + 1;
}
static String*
@@ -1017,8 +1028,7 @@
nbytes = 0;
/* warn about envelope sender */
- if(senders.last != nil && senders.last->p != nil &&
- strcmp(s_to_c(senders.last->p), "/dev/null") != 0 &&
+ if(strcmp(s_to_c(senders.last->p), "/dev/null") != 0 &&
masquerade(senders.last->p, nil))
nbytes += Bprint(pp->std[0]->fp,
"X-warning: suspect envelope domain\n");
@@ -1044,68 +1054,16 @@
return nbytes;
}
-/*
- * pipe message to mailer with the following transformations:
- * - change \r\n into \n.
- * - add sender's domain to any addrs with no domain
- * - add a From: if none of From:, Sender:, or Replyto: exists
- * - add a Received: line
- */
-int
-pipemsg(int *byteswritten)
+static int
+parseheader(String *hdr, int *nbytesp, int *status)
{
- int n, nbytes, sawdot, status, nonhdr, bpr;
char *cp;
+ int nbytes, n;
Field *f;
Link *l;
Node *p;
- String *hdr, *line;
- pipesig(&status); /* set status to 1 on write to closed pipe */
- sawdot = 0;
- status = 0;
-
- /*
- * add a 'From ' line as envelope
- */
- nbytes = 0;
- nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n",
- s_to_c(senders.first->p), thedate());
-
- /*
- * add our own Received: stamp
- */
- nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him);
- if(nci->rsys)
- nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys);
- nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate());
-
- /*
- * read first 16k obeying '.' escape. we're assuming
- * the header will all be there.
- */
- line = s_new();
- hdr = s_new();
- while(sawdot == 0 && s_len(hdr) < 16*1024){
- n = getcrnl(s_reset(line), &bin);
-
- /* eof or error ends the message */
- if(n <= 0)
- break;
-
- /* a line with only a '.' ends the message */
- cp = s_to_c(line);
- if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
- sawdot = 1;
- break;
- }
-
- s_append(hdr, *cp == '.' ? cp+1 : cp);
- }
-
- /*
- * parse header
- */
+ nbytes = *nbytesp;
yyinit(s_to_c(hdr), s_len(hdr));
yyparse();
@@ -1120,9 +1078,8 @@
* add an orginator and/or destination if either is missing
*/
if(originator == 0){
- if(senders.last == nil || senders.last->p == nil)
- nbytes += Bprint(pp->std[0]->fp, "From: /dev/null@%s\n",
- him);
+ if(senders.last == nil)
+ nbytes += Bprint(pp->std[0]->fp, "From: /dev/null@%s\n", him);
else
nbytes += Bprint(pp->std[0]->fp, "From: %s\n",
s_to_c(senders.last->p));
@@ -1143,29 +1100,132 @@
*/
cp = s_to_c(hdr);
for(f = firstfield; cp != nil && f; f = f->next){
- for(p = f->node; cp != 0 && p; p = p->next) {
- bpr = 0;
- cp = bprintnode(pp->std[0]->fp, p, &bpr);
- nbytes += bpr;
- }
- if(status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){
+ for(p = f->node; cp != 0 && p; p = p->next)
+ cp = bprintnode(pp->std[0]->fp, p, &nbytes);
+ if(*status == 0 && Bprint(pp->std[0]->fp, "\n") < 0){
piperror = "write error";
- status = 1;
+ *status = 1;
}
- nbytes++; /* for newline */
+ nbytes++;
}
if(cp == nil){
piperror = "sender domain";
- status = 1;
+ *status = 1;
}
-
/* write anything we read following the header */
- nonhdr = s_to_c(hdr) + s_len(hdr) - cp;
- if(status == 0 && Bwrite(pp->std[0]->fp, cp, nonhdr) < 0){
- piperror = "write error 2";
- status = 1;
+ if(*status == 0){
+ n = Bwrite(pp->std[0]->fp, cp, s_to_c(hdr) + s_len(hdr) - cp);
+ if(n == -1){
+ piperror = "write error 2";
+ *status = 1;
+ }
+ nbytes += n;
}
- nbytes += nonhdr;
+
+ *nbytesp = nbytes;
+ return *status;
+}
+
+static int
+chkhdr(char *s, int n)
+{
+ int i;
+ Rune r;
+
+ for(i = 0; i < n; ){
+ if(!fullrune(s + i, n - i))
+ return -1;
+ i += chartorune(&r, s + i);
+ if(r == Runeerror)
+ return -1;
+ }
+ return 0;
+}
+
+static void
+fancymsg(int status)
+{
+ static char msg[2*ERRMAX], *p, *e;
+
+ if(!status)
+ return;
+
+ p = seprint(msg, msg+ERRMAX, "%s: ", piperror);
+ rerrstr(p, ERRMAX);
+ piperror = msg;
+}
+
+/*
+ * pipe message to mailer with the following transformations:
+ * - change \r\n into \n.
+ * - add sender's domain to any addrs with no domain
+ * - add a From: if none of From:, Sender:, or Replyto: exists
+ * - add a Received: line
+ * - elide leading dot
+ */
+int
+pipemsg(int *byteswritten)
+{
+ char *cp;
+ int n, nbytes, sawdot, status;
+ String *hdr, *line;
+
+ pipesig(&status); /* set status to 1 on write to closed pipe */
+ sawdot = 0;
+ status = 0;
+ werrstr("");
+ piperror = nil;
+
+ /*
+ * add a 'From ' line as envelope and Received: stamp
+ */
+ nbytes = 0;
+ nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n",
+ s_to_c(senders.first->p), thedate());
+ nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him);
+ if(nci->rsys)
+ nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys);
+ nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate());
+
+ /*
+ * read first 16k obeying '.' escape. we're assuming
+ * the header will all be there.
+ */
+ line = s_new();
+ hdr = s_new();
+ while(s_len(hdr) < 16*1024){
+ n = getcrnl(s_reset(line), &bin);
+
+ /* eof or error ends the message */
+ if(n <= 0){
+ piperror = "header read error";
+ status = 1;
+ break;
+ }
+
+ cp = s_to_c(line);
+ if(chkhdr(cp, s_len(line)) == -1){
+ status = 1;
+ piperror = "mail refused: illegal header chars";
+ break;
+ }
+
+ /* a line with only a '.' ends the message */
+ if(cp[0] == '.' && cp[1] == '\n'){
+ sawdot = 1;
+ break;
+ }
+ if(cp[0] == '.'){
+ cp++;
+ n--;
+ }
+ s_append(hdr, cp);
+ nbytes += n;
+ if(*cp == '\n')
+ break;
+ }
+ if(status == 0)
+ parseheader(hdr, &nbytes, &status);
s_free(hdr);
/*
@@ -1172,16 +1232,20 @@
* pass rest of message to mailer. take care of '.'
* escapes.
*/
- while(sawdot == 0){
+ for(;;){
n = getcrnl(s_reset(line), &bin);
/* eof or error ends the message */
+ if(n < 0){
+ piperror = "body read error";
+ status = 1;
+ }
if(n <= 0)
break;
/* a line with only a '.' ends the message */
cp = s_to_c(line);
- if(n == 2 && *cp == '.' && *(cp+1) == '\n'){
+ if(cp[0] == '.' && cp[1] == '\n'){
sawdot = 1;
break;
}
@@ -1193,37 +1257,29 @@
if(status == 0 && Bwrite(pp->std[0]->fp, cp, n) < 0){
piperror = "write error 3";
status = 1;
+ break;
}
}
s_free(line);
- if(sawdot == 0){
+ if(status == 0 && sawdot == 0){
/* message did not terminate normally */
- snprint(pipbuf, sizeof pipbuf, "network eof: %r");
+ snprint(pipbuf, sizeof pipbuf, "network eof no dot: %r");
piperror = pipbuf;
- if (pp->pid > 0) {
- syskillpg(pp->pid);
- /* none can't syskillpg, so try a variant */
- sleep(500);
- syskill(pp->pid);
- }
status = 1;
}
-
if(status == 0 && Bflush(pp->std[0]->fp) < 0){
piperror = "write error 4";
status = 1;
}
- if (debug) {
- stamp();
- fprint(2, "at end of message; %s .\n",
- (sawdot? "saw": "didn't see"));
- }
+ if(status != 0)
+ syskill(pp->pid);
stream_free(pp->std[0]);
pp->std[0] = 0;
*byteswritten = nbytes;
pipesigoff();
- if(status && !piperror)
+ if(status && piperror == nil)
piperror = "write on closed pipe";
+ fancymsg(status);
return status;
}
@@ -1233,8 +1289,8 @@
char *p;
static char buf[128];
- strncpy(buf, x, sizeof(buf));
- buf[sizeof(buf)-1] = 0;
+ strncpy(buf, x, sizeof buf);
+ buf[sizeof buf - 1] = 0;
p = strchr(buf, '\n');
if(p)
*p = 0;
@@ -1248,6 +1304,7 @@
char *cp, *senddom, *user, *who;
Waitmsg *w;
+ senddom = 0;
who = s_to_c(senders.first->p);
if(strcmp(who, "/dev/null") == 0){
/* /dev/null can only send to one rcpt at a time */
@@ -1256,13 +1313,14 @@
"recipients");
return -1;
}
- return 0;
+ /* 4408 spf §2.2 notes that 2821 says /dev/null == postmaster@domain */
+ senddom = smprint("%s!postmaster", him);
}
if(access("/mail/lib/validatesender", AEXEC) < 0)
return 0;
-
- senddom = strdup(who);
+ if(!senddom)
+ senddom = strdup(who);
if((cp = strchr(senddom, '!')) == nil){
werrstr("rejected: domainless sender %s", who);
free(senddom);
@@ -1270,6 +1328,12 @@
}
*cp++ = 0;
user = cp;
+ /* shellchars isn't restrictive. should probablly disallow specialchars */
+ if(shellchars(senddom) || shellchars(user) || shellchars(him)){
+ werrstr("rejected: evil sender/domain/helo");
+ free(senddom);
+ return -1;
+ }
switch(pid = fork()){
case -1:
@@ -1281,7 +1345,7 @@
* to allow validatesender to implement SPF eventually.
*/
execl("/mail/lib/validatesender", "validatesender",
- "-n", nci->root, senddom, user, nil);
+ "-n", nci->root, senddom, user, nci->rsys, him, nil);
_exits("exec validatesender: %r");
default:
break;
@@ -1307,8 +1371,8 @@
* skip over validatesender 143123132: prefix from rc.
*/
cp = strchr(w->msg, ':');
- if(cp && *(cp+1) == ' ')
- werrstr("%s", cp+2);
+ if(cp && cp[1] == ' ')
+ werrstr("%s", cp + 2);
else
werrstr("%s", w->msg);
free(w);
@@ -1315,12 +1379,34 @@
return -1;
}
+int
+refused(char *e)
+{
+ return e && strstr(e, "mail refused") != nil;
+}
+
+/*
+ * if a message appeared on stderr, despite good status,
+ * log it. this can happen if rewrite.in contains a bad
+ * r.e., for example.
+ */
void
+logerrors(String *err)
+{
+ char *s;
+
+ s = s_to_c(err);
+ if(*s == 0)
+ return;
+ syslog(0, "smtpd", "%s returned good status, but said: %s",
+ s_to_c(mailer), s);
+}
+
+void
data(void)
{
+ char *cp, *ep, *e, buf[ERRMAX];
int status, nbytes;
- char *cp, *ep;
- char errx[ERRMAX];
Link *l;
String *cmd, *err;
@@ -1337,122 +1423,82 @@
return;
}
if(!trusted && sendermxcheck()){
- rerrstr(errx, sizeof errx);
- if(strncmp(errx, "rejected:", 9) == 0)
- reply("554 5.7.1 %s\r\n", errx);
+ rerrstr(buf, sizeof buf);
+ if(strncmp(buf, "rejected:", 9) == 0)
+ reply("554 5.7.1 %s\r\n", buf);
else
- reply("450 4.7.0 %s\r\n", errx);
+ reply("450 4.7.0 %s\r\n", buf);
for(l=rcvers.first; l; l=l->next)
syslog(0, "smtpd", "[%s/%s] %s -> %s sendercheck: %s",
him, nci->rsys, s_to_c(senders.first->p),
- s_to_c(l->p), errx);
+ s_to_c(l->p), buf);
rejectcount++;
return;
}
+ /*
+ * allow 145 more minutes to move the data
+ */
cmd = startcmd();
if(cmd == 0)
return;
-
reply("354 Input message; end with <CRLF>.<CRLF>\r\n");
- if(debug){
- seek(2, 0, 2);
- stamp();
- fprint(2, "# sent 354; accepting DATA %s\n", thedate());
- }
-
-
- /*
- * allow 145 more minutes to move the data
- */
alarm(145*60*1000);
-
+ piperror = nil;
status = pipemsg(&nbytes);
-
- /*
- * read any error messages
- */
err = s_new();
- if (debug) {
- stamp();
- fprint(2, "waiting for upas/send to close stderr\n");
- }
while(s_read_line(pp->std[2]->fp, err))
;
-
alarm(0);
atnotify(catchalarm, 0);
- if (debug) {
- stamp();
- fprint(2, "waiting for upas/send to exit\n");
- }
status |= proc_wait(pp);
if(debug){
seek(2, 0, 2);
- stamp();
- fprint(2, "# %d upas/send status %#ux at %s\n",
- getpid(), status, thedate());
+ fprint(2, "%d status %ux\n", getpid(), status);
if(*s_to_c(err))
- fprint(2, "# %d error %s\n", getpid(), s_to_c(err));
+ fprint(2, "%d error %s\n", getpid(), s_to_c(err));
}
/*
* if process terminated abnormally, send back error message
*/
+ if(status && (refused(piperror) || refused(s_to_c(err)))){
+ filterstate = BLOCKED;
+ status = 0;
+ }
if(status){
- int code;
- char *ecode;
-
- if(strstr(s_to_c(err), "mail refused")){
- syslog(0, "smtpd", "++[%s/%s] %s %s refused: %s",
- him, nci->rsys, s_to_c(senders.first->p),
- s_to_c(cmd), firstline(s_to_c(err)));
- code = 554;
- ecode = "5.0.0";
- } else {
- syslog(0, "smtpd", "++[%s/%s] %s %s %s%s%sreturned %#q %s",
- him, nci->rsys,
- s_to_c(senders.first->p), s_to_c(cmd),
- piperror? "error during pipemsg: ": "",
- piperror? piperror: "",
- piperror? "; ": "",
- pp->waitmsg->msg, firstline(s_to_c(err)));
- code = 450;
- ecode = "4.0.0";
- }
+ buf[0] = 0;
+ if(piperror != nil)
+ snprint(buf, sizeof buf, "pipemesg: %s; ", piperror);
+ syslog(0, "smtpd", "++[%s/%s] %s %s %sreturned %#q %s",
+ him, nci->rsys, s_to_c(senders.first->p),
+ s_to_c(cmd), buf,
+ pp->waitmsg->msg, firstline(s_to_c(err)));
for(cp = s_to_c(err); ep = strchr(cp, '\n'); cp = ep){
*ep++ = 0;
- reply("%d-%s %s\r\n", code, ecode, cp);
+ reply("450-4.0.0 %s\r\n", cp);
}
- reply("%d %s mail process terminated abnormally\r\n",
- code, ecode);
+ reply("450 4.0.0 mail process terminated abnormally\r\n");
+ rejectcount++;
} else {
- /*
- * if a message appeared on stderr, despite good status,
- * log it. this can happen if rewrite.in contains a bad
- * r.e., for example.
- */
- if(*s_to_c(err))
- syslog(0, "smtpd",
- "%s returned good status, but said: %s",
- s_to_c(mailer), s_to_c(err));
-
- if(filterstate == BLOCKED)
- reply("554 5.7.1 we believe this is spam. "
- "we don't accept it.\r\n");
- else if(filterstate == DELAY)
+ if(filterstate == BLOCKED){
+ e = firstline(s_to_c(err));
+ if(e[0] == 0)
+ e = piperror;
+ if(e == nil)
+ e = "we believe this is spam.";
+ syslog(0, "smtpd", "++[%s/%s] blocked: %s", him, nci->rsys, e);
+ reply("554 5.7.1 %s\r\n", e);
+ rejectcount++;
+ }else if(filterstate == DELAY){
+ logerrors(err);
reply("450 4.3.0 There will be a delay in delivery "
"of this message.\r\n");
- else {
+ }else{
+ logerrors(err);
reply("250 2.5.0 sent\r\n");
logcall(nbytes);
- if(debug){
- seek(2, 0, 2);
- stamp();
- fprint(2, "# %d sent 250 reply %s\n",
- getpid(), thedate());
- }
}
}
proc_free(pp);
@@ -1475,6 +1521,8 @@
int
rejectcheck(void)
{
+ if(rejectcount)
+ sleep(1000 * (4<<rejectcount));
if(rejectcount > MAXREJECTS){
syslog(0, "smtpd", "Rejected (%s/%s)", him, nci->rsys);
reply("554 5.5.0 too many errors. transaction failed.\r\n");
@@ -1518,9 +1566,9 @@
* if the string is coming from smtpd.y, it will have no nl.
* if it is coming from getcrnl below, it will have an nl.
*/
- if (*(s_to_c(sin)+lin-1) == '\n')
+ if (*(s_to_c(sin) + lin - 1) == '\n')
lin--;
- sout = s_newalloc(lin+1);
+ sout = s_newalloc(lin + 1);
lout = dec64((uchar *)s_to_c(sout), lin, s_to_c(sin), lin);
if (lout < 0) {
s_free(sout);
@@ -1567,6 +1615,32 @@
syslog(0, "smtpd", "started TLS with %s", him);
}
+int
+passauth(char *u, char *secret)
+{
+ char response[2*MD5dlen + 1];
+ uchar digest[MD5dlen];
+ int i;
+ AuthInfo *ai;
+ Chalstate *cs;
+
+ if((cs = auth_challenge("proto=cram role=server")) == nil)
+ return -1;
+ hmac_md5((uchar*)cs->chal, strlen(cs->chal),
+ (uchar*)secret, strlen(secret), digest, nil);
+ for(i = 0; i < MD5dlen; i++)
+ snprint(response + 2*i, sizeof response - 2*i, "%2.2ux", digest[i]);
+ cs->user = u;
+ cs->resp = response;
+ cs->nresp = strlen(response);
+ ai = auth_response(cs);
+ if(ai == nil)
+ return -1;
+ auth_freechal(cs);
+ auth_freeAI(ai);
+ return 0;
+}
+
void
auth(String *mech, String *resp)
{
@@ -1576,19 +1650,19 @@
String *s_resp1_64 = nil, *s_resp2_64 = nil, *s_resp1 = nil;
String *s_resp2 = nil;
- if (rejectcheck())
+ if(rejectcheck())
goto bomb_out;
syslog(0, "smtpd", "auth(%s, %s) from %s", s_to_c(mech),
"(protected)", him);
- if (authenticated) {
+ if(authenticated) {
bad_sequence:
rejectcount++;
reply("503 5.5.2 Bad sequence of commands\r\n");
goto bomb_out;
}
- if (cistrcmp(s_to_c(mech), "plain") == 0) {
+ if(cistrcmp(s_to_c(mech), "plain") == 0){
if (!passwordinclear) {
rejectcount++;
reply("538 5.7.1 Encryption required for requested "
@@ -1611,12 +1685,13 @@
memset(s_to_c(s_resp1_64), 'X', s_len(s_resp1_64));
user = s_to_c(s_resp1) + strlen(s_to_c(s_resp1)) + 1;
pass = user + strlen(user) + 1;
- ai = auth_userpasswd(user, pass);
- authenticated = ai != nil;
+// ai = auth_userpasswd(user, pass);
+// authenticated = ai != nil;
+authenticated = passauth(user, pass) != -1;
memset(pass, 'X', strlen(pass));
goto windup;
}
- else if (cistrcmp(s_to_c(mech), "login") == 0) {
+ else if(cistrcmp(s_to_c(mech), "login") == 0){
if (!passwordinclear) {
rejectcount++;
reply("538 5.7.1 Encryption required for requested "
@@ -1628,7 +1703,8 @@
s_resp1_64 = s_new();
if (getcrnl(s_resp1_64, &bin) <= 0)
goto bad_sequence;
- }
+ }else
+ s_resp1_64 = resp;
reply("334 UGFzc3dvcmQ6\r\n");
s_resp2_64 = s_new();
if (getcrnl(s_resp2_64, &bin) <= 0)
@@ -1656,7 +1732,7 @@
}
goto bomb_out;
}
- else if (cistrcmp(s_to_c(mech), "cram-md5") == 0) {
+ else if(cistrcmp(s_to_c(mech), "cram-md5") == 0){
char *resp, *t;
chs = auth_challenge("proto=cram role=server");
--- a/sys/src/cmd/upas/smtp/spam.c
+++ b/sys/src/cmd/upas/smtp/spam.c
@@ -65,10 +65,10 @@
static char buf[32];
Keyword *p;
- for(p=actions; p->name; p++)
+ for(p = actions; p->name; p++)
if(p->code == a)
return p->name;
- if(a==NONE)
+ if(a == NONE)
return "none";
sprint(buf, "%d", a);
return buf;
@@ -94,13 +94,13 @@
int
istrusted(char *s)
{
- char buf[1024];
+ char buf[Pathlen];
if(s == nil || *s == 0)
return 0;
snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s);
- return access(buf,0) >= 0;
+ return access(buf, 0) >= 0;
}
void
@@ -130,7 +130,7 @@
cp = getline(bp);
if(cp == 0)
break;
- p = cp+strlen(cp)+1;
+ p = cp + strlen(cp) + 1;
switch(findkey(cp, options)){
case NORELAY:
if(fflag == 0 && strcmp(p, "on") == 0)
@@ -157,7 +157,7 @@
s = s_new();
s_append(s, p);
listadd(&ourdoms, s);
- p += strlen(p)+1;
+ p += strlen(p) + 1;
}
break;
default:
@@ -178,7 +178,7 @@
{
int n;
- n = strlen(specuser)-1;
+ n = strlen(specuser) - 1;
if(specuser[n] == '*'){
if(n == 0) /* match everything */
return 0;
@@ -195,9 +195,9 @@
if (*specdom == '*'){
if (specdom[1] == '.' && specdom[2]){
specdom += 2;
- n = strlen(pathdom)-strlen(specdom);
+ n = strlen(pathdom) - strlen(specdom);
if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
- return strcmp(pathdom+n, specdom);
+ return strcmp(pathdom + n, specdom);
return n;
}
}
@@ -261,10 +261,10 @@
return 0;
n = Blinelen(bp);
cp[n-1] = 0;
- if(buf == 0 || bufsize < n+1){
+ if(buf == 0 || bufsize < n + 1){
bufsize += 512;
- if(bufsize < n+1)
- bufsize = n+1;
+ if(bufsize < n + 1)
+ bufsize = n + 1;
buf = realloc(buf, bufsize);
if(buf == 0)
break;
@@ -328,7 +328,7 @@
s_append(path, "[");
s_append(path, nci->rsys);
s_append(path, "]!");
- s_append(path, cp+3);
+ s_append(path, cp + 3);
s_terminate(path);
s_free(lpath);
return 0;
@@ -348,7 +348,7 @@
for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */
*cp = tolower(*cp);
- for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
+ for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp + 1){
*cp = 0;
if(!isourdom(s)){
s_free(lpath);
@@ -367,13 +367,12 @@
int rv = 0;
if(debug)
- fprint(2, "masquerade(%s) ", s_to_c(path));
+ fprint(2, "masquerade(%s)\n", s_to_c(path));
- if(trusted || path == nil) {
- if(debug)
- fprint(2, "0\n");
+ if(trusted)
return 0;
- }
+ if(path == nil)
+ return 0;
lpath = s_copy(s_to_c(path));
@@ -388,7 +387,7 @@
if(isourdom(s))
rv = 1;
} else if((cp = strrchr(s, '@')) != nil){
- if(isourdom(cp+1))
+ if(isourdom(cp + 1))
rv = 1;
} else {
if(isourdom(him))
@@ -396,8 +395,6 @@
}
s_free(lpath);
- if (debug)
- fprint(2, "%d\n", rv);
return rv;
}
@@ -426,15 +423,15 @@
if(strchr(cp, '/') == 0){
m = 0xff000000;
p = cp;
- for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
- m = (m>>8)|0xff000000;
+ for(p = strchr(p, '.'); p && p[1]; p = strchr(p + 1, '.'))
+ m = (m>>8)|0xff000000;
/* force at least a class B */
m |= 0xffff0000;
}
- if((v4peerip&m) == a)
+ if((v4peerip & m) == a)
return 1;
- cp += strlen(cp)+1;
+ cp += strlen(cp) + 1;
}
return 0;
}
@@ -470,10 +467,10 @@
cp = ctime(time(0));
cp[7] = 0;
if(cp[8] == ' ')
- sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
+ sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp + 4, cp[9]);
else
- sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
- cp = buf+strlen(buf);
+ sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp + 4, cp[8], cp[9]);
+ cp = buf + strlen(buf);
if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0)
return "/dev/null";
h = 0;
@@ -484,7 +481,7 @@
sprint(cp, "/%lud", h);
if(access(buf, 0) >= 0)
continue;
- fd = syscreate(buf, ORDWR, 0666);
+ fd = create(buf, ORDWR, 0666);
if(fd >= 0){
if(debug)
fprint(2, "saving in %s\n", buf);
@@ -586,7 +583,7 @@
rv = 0;
f = smprint("/mail/box/%s/nospamfiltering", p);
if(f != nil){
- rv = access(f, 0)==0;
+ rv = access(f, 0) == 0;
free(f);
}
--- /dev/null
+++ b/sys/src/cmd/upas/spf/dns.c
@@ -1,0 +1,81 @@
+#include "spf.h"
+
+extern char dflag;
+extern char vflag;
+extern char *netroot;
+
+static int
+timeout(void*, char *msg)
+{
+ if(strstr(msg, "alarm")){
+ fprint(2, "deferred: dns timeout");
+ exits("deferred: dns timeout");
+ }
+ return 0;
+}
+
+static Ndbtuple*
+tdnsquery(char *r, char *s, char *v)
+{
+ long a;
+ Ndbtuple *t;
+
+ atnotify(timeout, 1);
+ a = alarm(15*1000);
+ t = dnsquery(r, s, v);
+ alarm(a);
+ atnotify(timeout, 0);
+ return t;
+}
+
+Ndbtuple*
+vdnsquery(char *s, char *v, int recur)
+{
+ Ndbtuple *n, *t;
+ static int nquery;
+
+ /* conflicts with standard: must limit to 10 and -> fail */
+ if(recur > 5 || ++nquery == 25){
+ fprint(2, "dns query limited %d %d\n", recur, nquery);
+ return 0;
+ }
+ if(dflag)
+ fprint(2, "dnsquery(%s, %s, %s) ->\n", netroot, s, v);
+ t = tdnsquery(netroot, s, v);
+ if(dflag)
+ for(n = t; n; n = n->entry)
+ fprint(2, "\t%s\t%s\n", n->attr, n->val);
+ return t;
+}
+
+void
+dnreverse(char *s, int l, char *d)
+{
+ char *p, *e, buf[100], *f[15];
+ int i, n;
+
+ n = getfields(d, f, nelem(f), 0, ".");
+ p = e = buf;
+ if(l < sizeof buf)
+ e += l;
+ else
+ e += sizeof buf;
+ for(i = 1; i <= n; i++)
+ p = seprint(p, e, "%s.", f[n-i]);
+ if(p > buf)
+ p = seprint(p-1, e, ".in-addr.arpa");
+ memmove(s, buf, p-buf+1);
+}
+
+int
+dncontains(char *d, char *s)
+{
+loop:
+ if(!strcmp(d, s))
+ return 1;
+ if(!(s = strchr(s, '.')))
+ return 0;
+ s++;
+ goto loop;
+}
+
--- /dev/null
+++ b/sys/src/cmd/upas/spf/macro.c
@@ -1,0 +1,304 @@
+#include "spf.h"
+
+#define mrprint(...) snprint(m->mreg, sizeof m->mreg, __VA_ARGS__)
+
+typedef struct Mfmt Mfmt;
+typedef struct Macro Macro;
+
+struct Mfmt{
+ char buf[0xff];
+ char *p;
+ char *e;
+
+ char mreg[0xff];
+ int f1;
+ int f2;
+ int f3;
+
+ char *sender;
+ char *domain;
+ char *ip;
+ char *helo;
+ uchar ipa[IPaddrlen];
+};
+
+struct Macro{
+ char c;
+ void (*f)(Mfmt*);
+};
+
+static void
+ms(Mfmt *m)
+{
+ mrprint("%s", m->sender);
+}
+
+static void
+ml(Mfmt *m)
+{
+ char *p;
+
+ mrprint("%s", m->sender);
+ if(p = strchr(m->mreg, '@'))
+ *p = 0;
+}
+
+static void
+mo(Mfmt *m)
+{
+ mrprint("%s", m->domain);
+}
+
+static void
+md(Mfmt *m)
+{
+ mrprint("%s", m->domain);
+}
+
+static void
+mi(Mfmt *m)
+{
+ uint i, c;
+
+ if(isv4(m->ipa))
+ mrprint("%s", m->ip);
+ else{
+ for(i = 0; i < 32; i++){
+ c = m->ipa[i / 2];
+ if((i & 1) == 0)
+ c >>= 4;
+ sprint(m->mreg+2*i, "%ux.", c & 0xf);
+ }
+ m->mreg[2*32 - 1] = 0;
+ }
+}
+
+static int
+maquery(Mfmt *m, char *d, char *match, int recur)
+{
+ int r;
+ Ndbtuple *t, *n;
+
+ r = 0;
+ t = vdnsquery(d, "any", recur);
+ for(n = t; n; n = n->entry)
+ if(!strcmp(n->attr, "ip") || !strcmp(n->attr, "ipv6")){
+ if(!strcmp(n->val, match)){
+ r = 1;
+ break;
+ }
+ }else if(!strcmp(n->attr, "cname"))
+ maquery(m, d, match, recur+1);
+ ndbfree(t);
+ return r;
+}
+
+static int
+lrcmp(char *a, char *b)
+{
+ return strlen(b) - strlen(a);
+}
+
+static void
+mptrquery(Mfmt *m, char *d, int recur)
+{
+ char *s, buf[64], *a, *list[11];
+ int nlist, i;
+ Ndbtuple *t, *n;
+
+ nlist = 0;
+ dnreverse(buf, sizeof buf, s = strdup(m->ip));
+ t = vdnsquery(buf, "ptr", recur);
+ for(n = t; n; n = n->entry){
+ if(!strcmp(n->attr, "dom") || !strcmp(n->attr, "cname"))
+ if(dncontains(n->val, d) && maquery(m, n->val, m->ip, recur+1))
+ list[nlist++] = strdup(n->val);
+ }
+ ndbfree(t);
+ free(s);
+ qsort(list, nlist, sizeof *list, (int(*)(void*,void*))lrcmp);
+ a = "unknown";
+ for(i = 0; i < nlist; i++)
+ if(!strcmp(list[i], d)){
+ a = list[i];
+ break;
+ }else if(dncontains(list[i], d))
+ a = list[i];
+ mrprint("%s", a);
+ for(i = 0; i < nlist; i++)
+ free(list[i]);
+}
+
+static void
+mp(Mfmt *m)
+{
+ /*
+ * we're supposed to do a reverse lookup on the ip & compare.
+ * this is a very bad idea.
+ */
+// mrprint("unknown); /* simulate dns failure */
+ mptrquery(m, m->domain, 0);
+}
+
+static void
+mv(Mfmt *m)
+{
+ if(isv4(m->ipa))
+ mrprint("in-addr");
+ else
+ mrprint("ip6");
+}
+
+static void
+mh(Mfmt *m)
+{
+ mrprint("%s", m->helo);
+}
+
+static Macro tab[] = {
+'s', ms, /* sender */
+'l', ml, /* local part of sender */
+'o', mo, /* domain of sender */
+'d', md, /* domain */
+'i', mi, /* ip */
+'p', mp, /* validated domain name of ip */
+'v', mv, /* "in-addr" if ipv4, or "ip6" if ipv6 */
+'h', mh, /* helo/ehol domain */
+};
+
+static void
+reverse(Mfmt *m)
+{
+ char *p, *e, buf[100], *f[32], sep[2];
+ int i, n;
+
+ sep[0] = m->f2;
+ sep[1] = 0;
+ n = getfields(m->mreg, f, nelem(f), 0, sep);
+ p = e = buf;
+ e += sizeof buf-1;
+ for(i = 0; i < n; i++)
+ p = seprint(p, e, "%s.", f[n-i-1]);
+ if(p > buf)
+ p--;
+ *p = 0;
+ memmove(m->mreg, buf, p-buf+1);
+ m->f2 = '.';
+}
+
+static void
+chop(Mfmt *m)
+{
+ char *p, *e, buf[100], *f[32], sep[2];
+ int i, n;
+
+ sep[0] = m->f2;
+ sep[1] = 0;
+ n = getfields(m->mreg, f, nelem(f), 0, sep);
+ p = e = buf;
+ e += sizeof buf-1;
+ if(m->f1 == 0)
+ i = 0;
+ else
+ i = n-m->f1;
+ if(i < 0)
+ i = 0;
+ for(; i < n; i++)
+ p = seprint(p, e, "%s.", f[i]);
+ if(p > buf)
+ p--;
+ *p = 0;
+ memmove(m->mreg, buf, p-buf+1);
+ m->f2 = '.';
+}
+
+static void
+mfmtinit(Mfmt *m, char *s, char *d, char *h, char *i)
+{
+ memset(m, 0, sizeof *m);
+ m->p = m->buf;
+ m->e = m->p + sizeof m->buf-1;
+ m->sender = s? s: "Unsets";
+ m->domain = d? d: "Unsetd";
+ m->helo = h? h: "Unseth";
+ m->ip = i? i: "127.0.0.2";
+ parseip(m->ipa, m->ip);
+}
+
+/* url escaping? rfc3986 */
+static void
+mputc(Mfmt *m, int c)
+{
+ if(m->p < m->e)
+ *m->p++ = c;
+}
+
+static void
+mputs(Mfmt *m, char *s)
+{
+ int c;
+
+ while(c = *s++)
+ mputc(m, c);
+}
+
+char*
+macro(char *f, char *sender, char *dom, char *hdom, char *ip)
+{
+ char *p;
+ int i, c;
+ Mfmt m;
+
+ mfmtinit(&m, sender, dom, hdom, ip);
+ while(*f){
+ while((c = *f++) && c != '%')
+ mputc(&m, c);
+ if(c == 0)
+ break;
+ switch(*f++){
+ case '%':
+ mputc(&m, '%');
+ break;
+ case '-':
+ mputs(&m, "%20");
+ break;
+ case '_':
+ mputc(&m, ' ');
+ break;
+ case '{':
+ m.f1 = 0;
+ m.f2 = '.';
+ m.f3 = 0;
+ c = *f++;
+ if(c >= 'A' && c <= 'Z')
+ c += 0x20;
+ for(i = 0; i < nelem(tab); i++)
+ if(tab[i].c == c)
+ break;
+ if(i == nelem(tab))
+ return 0;
+ for(c = *f++; c >= '0' && c <= '9'; c = *f++)
+ m.f1 = m.f1*10 + c-'0';
+ if(c == 'R' || c == 'r'){
+ m.f3 = 'r';
+ c = *f++;
+ }
+ for(; p = strchr(".-+,_=", c); c = *f++)
+ m.f2 = *p;
+ if(c == '}'){
+ tab[i].f(&m);
+ if(m.f1 || m.f2 != '.')
+ chop(&m);
+ if(m.f3 == 'r')
+ reverse(&m);
+ mputs(&m, m.mreg);
+ m.mreg[0] = 0;
+ break;
+ }
+ default:
+ return 0;
+ }
+ }
+ mputc(&m, 0);
+ return strdup(m.buf);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/spf/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+<../mkupas
+
+TARG=spf
+
+OFILES=\
+ dns.$O\
+ macro.$O\
+ spf.$O\
+
+</sys/src/cmd/mkone
+
+mtest: dns.$O macro.$O mtest.$O
+ $LD $LDFLAGS -o $target $prereq
--- /dev/null
+++ b/sys/src/cmd/upas/spf/mtest.c
@@ -1,0 +1,39 @@
+#include "spf.h"
+
+char dflag;
+char vflag;
+char *netroot = "/net";
+
+void
+usage(void)
+{
+ fprint(2, "usage: mtest [-dv] sender dom hello ip\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *a[5], *s;
+ int i;
+
+ ARGBEGIN{
+ case 'd':
+ dflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ fmtinstall('I', eipfmt);
+ memset(a, 0, sizeof a);
+ for(i = 0; i < argc && i < nelem(a); i++)
+ a[i] = argv[i];
+ s = macro(a[0], a[1], a[2], a[3], a[4]);
+ print("%s\n", s);
+ free(s);
+ exits("");
+}
--- /dev/null
+++ b/sys/src/cmd/upas/spf/spf.c
@@ -1,0 +1,800 @@
+#include "spf.h"
+
+#define vprint(...) if(vflag) fprint(2, __VA_ARGS__)
+
+enum{
+ Traw,
+ Tip4,
+ Tip6,
+ Texists,
+ Tall,
+ Tbegin,
+ Tend,
+};
+
+char *typetab[] = {
+ "raw",
+ "ip4",
+ "ip6",
+ "exists",
+ "all",
+ "begin",
+ "end",
+};
+
+typedef struct Squery Squery;
+struct Squery{
+ char ver;
+ char sabort;
+ char mod;
+ char *cidrtail;
+ char *ptrmatch;
+ char *ip;
+ char *domain;
+ char *sender;
+ char *hello;
+};
+
+typedef struct Spf Spf;
+struct Spf{
+ char mod;
+ char type;
+ char s[100];
+};
+#pragma varargck type "§" Spf*
+
+char *txt;
+char *netroot = "/net";
+char dflag;
+char eflag;
+char mflag;
+char pflag;
+char rflag;
+char vflag;
+
+char *vtab[] = {0, "v=spf1", "spf2.0/"};
+
+char*
+isvn(Squery *q, char *s, int i)
+{
+ char *p, *t;
+
+ t = vtab[i];
+ if(cistrncmp(s, t, strlen(t)))
+ return 0;
+ p = s + strlen(t);
+ if(i == 2){
+ p = strchr(p, ' ');
+ if(p == nil)
+ return 0;
+ }
+ if(*p && *p++ != ' ')
+ return 0;
+ q->ver = i;
+ return p;
+}
+
+char*
+pickspf(Squery *s, char *v1, char *v2)
+{
+ switch(s->ver){
+ default:
+ case 0:
+ if(v1)
+ return v1;
+ return v2;
+ case 1:
+ if(v1)
+ return v1;
+ return 0;
+ case 2:
+ if(v2)
+ return v2;
+ return v1; /* spf2.0/pra,mfrom */
+ }
+}
+
+char *ftab[] = {"txt", "spf"}; /* p. 9 */
+
+char*
+spffetch(Squery *s, char *d)
+{
+ char *p, *v1, *v2;
+ int i;
+ Ndbtuple *t, *n;
+
+ if(txt){
+ p = strdup(txt);
+ txt = 0;
+ return p;
+ }
+ v1 = v2 = 0;
+ for(i = 0; i < nelem(ftab); i++){
+ t = vdnsquery(d, ftab[i], 0);
+ for(n = t; n; n = n->entry){
+ if(strcmp(n->attr, ftab[i]))
+ continue;
+ v1 = isvn(s, n->val, 1);
+ v2 = isvn(s, n->val, 2);
+ }
+ if(p = pickspf(s, v1, v2))
+ p = strdup(p);
+ ndbfree(t);
+ if(p)
+ return p;
+ }
+ return 0;
+}
+
+Spf spftab[200];
+int nspf;
+int mod;
+
+Spf*
+spfadd(int type, char *s)
+{
+ Spf *p;
+
+ if(nspf >= nelem(spftab))
+ return 0;
+ p = spftab+nspf;
+ p->s[0] = 0;
+ if(s)
+ snprint(p->s, sizeof p->s, "%s", s);
+ p->type = type;
+ p->mod = mod;
+ nspf++;
+ return p;
+}
+
+char *badcidr[] = {
+ "0.0.0.0/8",
+ "1.0.0.0/8",
+ "2.0.0.0/8",
+ "5.0.0.0/8",
+ "10.0.0.0/8",
+ "127.0.0.0/8",
+ "255.0.0.0/8",
+ "192.168.0.0/16",
+ "169.254.0.0/16",
+ "172.16.0.0/20",
+ "224.0.0.0/24", /*rfc 3330 says this is /4. not sure */
+ "fc00::/7",
+};
+
+char *okcidr[] = {
+ "17.0.0.0/8", /* apple. seems dubious. */
+};
+
+int
+parsecidr(uchar *addr, uchar *mask, char *from)
+{
+ char *p, buf[50];
+ int i, bits, z;
+ vlong v;
+ uchar *a;
+
+ strecpy(buf, buf+sizeof buf, from);
+ if(p = strchr(buf, '/'))
+ *p = 0;
+ v = parseip(addr, buf);
+ if(v == -1)
+ return -1;
+ switch((ulong)v){
+ default:
+ bits = 32;
+ z = 96;
+ break;
+ case 6:
+ bits = 128;
+ z = 0;
+ break;
+ }
+
+ if(p){
+ i = strtoul(p+1, &p, 0);
+ if(i > bits)
+ i = bits;
+ i += z;
+ memset(mask, 0, 128/8);
+ for(a = mask; i >= 8; i -= 8)
+ *a++ = 0xff;
+ if(i > 0)
+ *a = ~((1<<(8-i))-1);
+ }else
+ memset(mask, 0xff, IPaddrlen);
+ return 0;
+}
+
+/*
+ * match x.y.z.w to x1.y1.z1.w1/m
+ */
+int
+cidrmatch(char *x, char *y)
+{
+ uchar a[IPaddrlen], b[IPaddrlen], m[IPaddrlen];
+
+ if(parseip(a, x) == -1)
+ return 0;
+ parsecidr(b, m, y);
+ maskip(a, m, a);
+ maskip(b, m, b);
+ if(!memcmp(a, b, IPaddrlen))
+ return 1;
+ return 0;
+}
+
+int
+cidrmatchtab(char *addr, char **tab, int ntab)
+{
+ int i;
+
+ for(i = 0; i < ntab; i++)
+ if(cidrmatch(addr, tab[i]))
+ return 1;
+ return 0;
+}
+
+int
+okcidrlen(char *cidr, int i)
+{
+ if(i >= 14 && i <= 128)
+ return 1;
+ if(cidrmatchtab(cidr, okcidr, nelem(okcidr)))
+ return 1;
+ return 0;
+}
+
+int
+cidrokay0(char *cidr)
+{
+ char *p, buf[40];
+ uchar addr[IPaddrlen];
+ int l, i;
+
+ p = strchr(cidr, '/');
+ if(p)
+ l = p-cidr;
+ else
+ l = strlen(cidr);
+ if(l > 39)
+ return 0;
+ if(p){
+ i = atoi(p+1);
+ if(!okcidrlen(cidr, i))
+ return 0;
+ }
+ memcpy(buf, cidr, l);
+ buf[l] = 0;
+ if(parseip(addr, buf) == -1)
+ return 0;
+ if(cidrmatchtab(cidr, badcidr, nelem(badcidr)))
+ return 0;
+ return 1;
+}
+
+int
+cidrokay(char *cidr)
+{
+ if(!cidrokay0(cidr)){
+ fprint(2, "spf: naughty cidr %s\n", cidr);
+ return 0;
+ }
+ return 1;
+}
+
+int
+ptrmatch(Squery *q, char *s)
+{
+ if(!q->ptrmatch || !strcmp(q->ptrmatch, s))
+ return 1;
+ return 0;
+}
+
+Spf*
+spfaddcidr(Squery *q, int type, char *s)
+{
+ char buf[64];
+
+ if(q->cidrtail){
+ snprint(buf, sizeof buf, "%s/%s", s, q->cidrtail);
+ s = buf;
+ }
+ if(cidrokay(s) && ptrmatch(q, s))
+ return spfadd(type, s);
+ return 0;
+}
+
+char*
+qpluscidr(Squery *q, char *d, int recur, int *y)
+{
+ char *p;
+
+ *y = 0;
+ if(!recur && (p = strchr(d, '/'))){
+ q->cidrtail = p + 1;
+ *p = 0;
+ *y = 1;
+ }
+ return d;
+}
+
+void
+cidrtail(Squery *q, char *, int y)
+{
+ if(!y)
+ return;
+ q->cidrtail[-1] = '/';
+ q->cidrtail = 0;
+}
+
+void
+aquery(Squery *q, char *d, int recur)
+{
+ int y;
+ Ndbtuple *t, *n;
+
+ d = qpluscidr(q, d, recur, &y);
+ t = vdnsquery(d, "any", recur);
+ for(n = t; n; n = n->entry){
+ if(!strcmp(n->attr, "ip"))
+ spfaddcidr(q, Tip4, n->val);
+ else if(!strcmp(n->attr, "ipv6"))
+ spfaddcidr(q, Tip6, n->val);
+ else if(!strcmp(n->attr, "cname"))
+ aquery(q, d, recur+1);
+ }
+ cidrtail(q, d, y);
+ ndbfree(t);
+}
+
+void
+mxquery(Squery *q, char *d, int recur)
+{
+ int i, y;
+ Ndbtuple *t, *n;
+
+ d = qpluscidr(q, d, recur, &y);
+ i = 0;
+ t = vdnsquery(d, "mx", recur);
+ for(n = t; n; n = n->entry)
+ if(i++ < 10 && !strcmp(n->attr, "mx"))
+ aquery(q, n->val, recur+1);
+ ndbfree(t);
+ cidrtail(q, d, y);
+}
+
+void
+ptrquery(Squery *q, char *d, int recur)
+{
+ char *s, buf[64];
+ int i, y;
+ Ndbtuple *t, *n;
+
+ if(!q->ip){
+ fprint(2, "spf: ptr query; no ip\n");
+ return;
+ }
+ d = qpluscidr(q, d, recur, &y);
+ i = 0;
+ dnreverse(buf, sizeof buf, s = strdup(q->ip));
+ t = vdnsquery(buf, "ptr", recur);
+ for(n = t; n; n = n->entry){
+ if(!strcmp(n->attr, "dom") || !strcmp(n->attr, "cname"))
+ if(i++ < 10 && dncontains(d, n->val)){
+ q->ptrmatch = q->ip;
+ aquery(q, n->val, recur+1);
+ q->ptrmatch = 0;
+ }
+ }
+ ndbfree(t);
+ free(s);
+ cidrtail(q, d, y);
+}
+
+/*
+ * this looks very wrong; see §5.7 which says only a records match.
+ */
+void
+exists(Squery*, char *d, int recur)
+{
+ Ndbtuple *t;
+
+ if(t = vdnsquery(d, "ip", recur))
+ spfadd(Texists, "1");
+ else
+ spfadd(Texists, 0);
+ ndbfree(t);
+}
+
+void
+addfail(void)
+{
+ mod = '-';
+ spfadd(Tall, 0);
+}
+
+void
+addend(char *s)
+{
+ spfadd(Tend, s);
+ spftab[nspf-1].mod = 0;
+}
+
+Spf*
+includeloop(char *s1, int n)
+{
+ char *s, *p;
+ int i;
+
+ for(i = 0; i < n; i++){
+ s = spftab[i].s;
+ if(s)
+ if(p = strstr(s, " -> "))
+ if(!strcmp(p+4, s1))
+ return spftab+i;
+ }
+ return nil;
+}
+
+void
+addbegin(int c, char *s0, char *s1)
+{
+ char buf[0xff];
+
+ snprint(buf, sizeof buf, "%s -> %s", s0, s1);
+ spfadd(Tbegin, buf);
+ spftab[nspf-1].mod = c;
+}
+
+void
+ditch(void)
+{
+ if(nspf > 0)
+ nspf--;
+}
+
+static void
+lower(char *s)
+{
+ int c;
+
+ for(; c = *s; s++)
+ if(c >= 'A' && c <= 'Z')
+ *s = c + 0x20;
+}
+
+int
+spfquery(Squery *x, char *d, int include)
+{
+ char *s, **t, *r, *p, *q, buf[10];
+ int i, n, c;
+ Spf *inc;
+
+ if(include)
+ if(inc = includeloop(d, nspf-1)){
+ fprint(2, "spf: include loop: %s (%s)\n", d, inc->s);
+ return -1;
+ }
+ s = spffetch(x, d);
+ if(!s)
+ return -1;
+ t = malloc(500*sizeof *t);
+ n = getfields(s, t, 500, 1, " ");
+ x->sabort = 0;
+ for(i = 0; i < n && !x->sabort; i++){
+ if(!strncmp(t[i], "v=", 2))
+ continue;
+ c = *t[i];
+ r = t[i]+1;
+ switch(c){
+ default:
+ mod = '+';
+ r--;
+ break;
+ case '-':
+ case '~':
+ case '+':
+ case '?':
+ mod = c;
+ break;
+ }
+ if(!strcmp(r, "all")){
+ spfadd(Tall, 0);
+ continue;
+ }
+ strecpy(buf, buf+sizeof buf, r);
+ p = strchr(buf, ':');
+ if(p == 0)
+ p = strchr(buf, '=');
+ q = d;
+ if(p){
+ *p = 0;
+ q = p+1;
+ q = r+(q-buf);
+ }
+ if(!mflag)
+ q = macro(q, x->sender, x->domain, x->hello, x->ip);
+ else
+ q = strdup(q);
+ lower(buf);
+ if(!strcmp(buf, "ip4"))
+ spfaddcidr(x, Tip4, q);
+ else if(!strcmp(buf, "ip6"))
+ spfaddcidr(x, Tip6, q);
+ else if(!strcmp(buf, "a"))
+ aquery(x, q, 0);
+ else if(!strcmp(buf, "mx"))
+ mxquery(x, d, 0);
+ else if(!strcmp(buf, "ptr"))
+ ptrquery(x, d, 0);
+ else if(!strcmp(buf, "exists"))
+ exists(x, q, 0);
+ else if(!strcmp(buf, "include") || !strcmp(buf, "redirect")){
+ if(q && *q){
+ if(rflag)
+ fprint(2, "I> %s\n", q);
+ addbegin(mod, r, q);
+ if(spfquery(x, q, 1) == -1){
+ ditch();
+ addfail();
+ }else
+ addend(r);
+ }
+ }
+ free(q);
+ }
+ free(t);
+ free(s);
+ return 0;
+}
+
+char*
+url(char *s)
+{
+ char buf[64], *p, *e;
+ int c;
+
+ p = buf;
+ e = p + sizeof buf;
+ *p = 0;
+ while(c = *s++){
+ if(c >= 'A' && c <= 'Z')
+ c += 0x20;
+ if(c <= ' ' || c == '%' || c & 0x80)
+ p = seprint(p, e, "%%%2.2X", c);
+ else
+ p = seprint(p, e, "%c", c);
+ }
+ return strdup(buf);
+}
+
+void
+spfinit(Squery *q, char *dom, int argc, char **argv)
+{
+ uchar a[IPaddrlen];
+
+ memset(q, 0, sizeof q);
+ q->ip = argc>0? argv[1]: 0;
+ if(q->ip && parseip(a, q->ip) == -1)
+ sysfatal("bogus ip");
+ q->domain = url(dom);
+ q->sender = argc>2? url(argv[2]): 0;
+ q->hello = argc>3? url(argv[3]): 0;
+ mod = 0; /* BOTCH */
+}
+
+int
+§fmt(Fmt *f)
+{
+ char *p, *e, buf[115];
+ Spf *spf;
+
+ spf = va_arg(f->args, Spf*);
+ if(!spf)
+ return fmtstrcpy(f, "<nil>");
+ e = buf+sizeof buf;
+ p = buf;
+ if(spf->mod && spf->mod != '+')
+ *p++ = spf->mod;
+ p = seprint(p, e, "%s", typetab[spf->type]);
+ if(spf->s[0])
+ seprint(p, e, " : %s", spf->s);
+ return fmtstrcpy(f, buf);
+}
+
+static Spf head;
+
+struct{
+ int i;
+}walk;
+
+int
+invertmod(int c)
+{
+ switch(c){
+ case '?':
+ return '?';
+ case '+':
+ return '-';
+ case '-':
+ return '+';
+ case '~':
+ return '?';
+ }
+ return 0;
+}
+
+#define reprint(...) if(vflag && recur == 0) fprint(2, __VA_ARGS__)
+
+int
+spfwalk(int all, int recur, char *ip)
+{
+ int match, bias, mod, r;
+ Spf *s;
+
+ r = 0;
+ bias = 0;
+ if(recur == 0)
+ walk.i = 0;
+ for(; walk.i < nspf; walk.i++){
+ s = spftab+walk.i;
+ mod = s->mod;
+ switch(s->type){
+ default:
+ abort();
+ case Tbegin:
+ walk.i++;
+ match = spfwalk(s->s[0] == 'r', recur+1, ip);
+ if(match < 0)
+ mod = invertmod(mod);
+ break;
+ case Tend:
+ return r;
+ case Tall:
+ match = 1;
+ break;
+ case Texists:
+ match = s->s[0];
+ break;
+ case Tip4:
+ case Tip6:
+ match = cidrmatch(ip, s->s);
+ break;
+ }
+ if(!r && match)
+ switch(mod){
+ case '~':
+ reprint("bias %§\n", s);
+ bias = '~';
+ case '?':
+ break;
+ case '-':
+ if(all || s->type !=Tall){
+ vprint("fail %§\n", s);
+ r = -1;
+ }
+ break;
+ case '+':
+ default:
+ vprint("match %§\n", s);
+ r = 1;
+ }
+ }
+ /* recur == 0 */
+ if(r == 0 && bias == '~')
+ r = -1;
+ return r;
+}
+
+/* ad hoc and noncomprehensive */
+char *tccld[] = {"au", "ca", "gt", "id", "pk", "uk", "ve", };
+int
+is3cctld(char *s)
+{
+ int i;
+
+ if(strlen(s) != 2)
+ return 0;
+ for(i = 0; i < nelem(tccld); i++)
+ if(!strcmp(tccld[i], s))
+ return 1;
+ return 0;
+}
+
+char*
+rootify(char *d)
+{
+ char *p, *q;
+
+ if(!(p = strchr(d, '.')))
+ return 0;
+ p++;
+ if(!(q = strchr(p, '.')))
+ return 0;
+ q++;
+ if(!strchr(q, '.') && is3cctld(q))
+ return 0;
+ return p;
+}
+
+void
+usage(void)
+{
+ fprint(2, "spf [-demrpv] [-n netroot] dom [ip sender helo]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ char *s, *d, *e;
+ int i, j, t[] = {0, 3};
+ Squery q;
+
+ ARGBEGIN{
+ case 'd':
+ dflag = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ break;
+ case 'm':
+ mflag = 1;
+ break;
+ case 'n':
+ netroot = EARGF(usage());
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 't':
+ txt = EARGF(usage());
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc < 1 || argc > 4)
+ usage();
+ if(argc == 1)
+ pflag = 1;
+ fmtinstall(L'§', §fmt);
+ fmtinstall('I', eipfmt);
+ fmtinstall('M', eipfmt);
+
+ e = "none";
+ for(i = 0; i < nelem(t); i++){
+ if(argc <= t[i])
+ break;
+ d = argv[t[i]];
+ for(j = 0; j < i; j++)
+ if(!strcmp(argv[t[j]], d))
+ goto loop;
+ for(s = d; ; s = rootify(s)){
+ if(!s)
+ goto loop;
+ spfinit(&q, d, argc, argv); /* or s? */
+ addbegin('+', ".", s);
+ if(spfquery(&q, s, 0) != -1)
+ break;
+ }
+ if(eflag && nspf)
+ addfail();
+ e = "";
+ if(pflag)
+ for(j = 0; j < nspf; j++)
+ print("%§\n", spftab+j);
+ if(argc >= t[i] && argc > 1)
+ if(spfwalk(1, 0, argv[1]) == -1)
+ exits("fail");
+loop:;
+ }
+ exits(e);
+}
--- /dev/null
+++ b/sys/src/cmd/upas/spf/spf.h
@@ -1,0 +1,12 @@
+/* © 2008 erik quanstrom; plan 9 license */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ndb.h>
+#include <ip.h>
+
+char *macro(char*, char*, char*, char*, char*);
+
+Ndbtuple* vdnsquery(char*, char*, int);
+int dncontains(char*, char *);
+void dnreverse(char*, int, char*);
--- /dev/null
+++ b/sys/src/cmd/upas/spf/testsuite
@@ -1,0 +1,9 @@
+#!/bin/rc
+for(i in '%{s}' '%{o}' '%{d}' '%{d4}' '%{d3}' '%{d2}' '%{d1}' '%{dr}' '%{d2r}' '%{l}' '%{l-}' '%{lr}' '%{lr-}' '%{l1r-}')
+ mtest $i '[email protected]' email.example.com helounknown 192.0.2.3
+for(i in '%{i}')
+ mtest $i '[email protected]' email.example.com helounknown 2001:db8::cb01
+for(i in '%{ir}.%{v}._spf.%{d2}' '%{lr-}.lp._spf.%{d2}' '%{lr-}.lp.%{ir}.%{v}._spf.%{d2}' '%{ir}.%{v}.%{lr-}.lp._spf.%{d2}')
+ mtest $i '[email protected]' email.example.com helounknown 2001:db8::cb01
+for(i in '%{ir}.%{v}._spf.%{d2}' '%{lr-}.lp._spf.%{d2}' '%{lr-}.lp.%{ir}.%{v}._spf.%{d2}' '%{ir}.%{v}.%{lr-}.lp._spf.%{d2}')
+ mtest $i '[email protected]' email.example.com helounknown 192.0.2.3
--- a/sys/src/cmd/upas/unesc/mkfile
+++ b/sys/src/cmd/upas/unesc/mkfile
@@ -1,9 +1,8 @@
</$objtype/mkfile
+<../mkupas
TARG=unesc
OFILES=unesc.$O\
-
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
--- a/sys/src/cmd/upas/vf/mkfile
+++ b/sys/src/cmd/upas/vf/mkfile
@@ -1,4 +1,5 @@
</$objtype/mkfile
+<../mkupas
TARG=vf
@@ -9,8 +10,6 @@
HFILES=../common/common.h\
../common/sys.h\
-
-BIN=/$objtype/bin/upas
UPDATE=\
mkfile\
$HFILES\
--- a/sys/src/cmd/upas/vf/vf.c
+++ b/sys/src/cmd/upas/vf/vf.c
@@ -954,7 +954,7 @@
{
String *s;
char decoded[1024];
- char utfbuf[UTFmax*1024];
+ char utfbuf[2*1024];
int i, len;
char *e;
char *token;
@@ -986,6 +986,8 @@
if(cistrncmp(token, "b?", 2) == 0){
token += 2;
len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
+ if(len == -1)
+ goto err;
decoded[len] = 0;
} else if(cistrncmp(token, "q?", 2) == 0){
token += 2;