]> git.lizzy.rs Git - plan9front.git/commitdiff
merging erik quanstros nupas
authorcinap_lenrek <cinap_lenrek@felloff.net>
Sun, 12 Mar 2017 16:15:03 +0000 (17:15 +0100)
committercinap_lenrek <cinap_lenrek@felloff.net>
Sun, 12 Mar 2017 16:15:03 +0000 (17:15 +0100)
209 files changed:
acme/mail/guide [deleted file]
acme/mail/mkbox [deleted file]
acme/mail/readme [deleted file]
acme/mail/src/dat.h [deleted file]
acme/mail/src/html.c [deleted file]
acme/mail/src/mail.c [deleted file]
acme/mail/src/mesg.c [deleted file]
acme/mail/src/mkfile [deleted file]
acme/mail/src/reply.c [deleted file]
acme/mail/src/util.c [deleted file]
acme/mail/src/win.c [deleted file]
acme/mkfile
sys/doc/mkfile
sys/doc/nupas/macros.ms [new file with mode: 0644]
sys/doc/nupas/mkfile [new file with mode: 0644]
sys/doc/nupas/nupas.ms [new file with mode: 0644]
sys/man/8/pop3
sys/src/cmd/ip/imap4d/auth.c [deleted file]
sys/src/cmd/ip/imap4d/copy.c [deleted file]
sys/src/cmd/ip/imap4d/csquery.c [deleted file]
sys/src/cmd/ip/imap4d/date.c [deleted file]
sys/src/cmd/ip/imap4d/debug.c [deleted file]
sys/src/cmd/ip/imap4d/fetch.c [deleted file]
sys/src/cmd/ip/imap4d/fns.h [deleted file]
sys/src/cmd/ip/imap4d/folder.c [deleted file]
sys/src/cmd/ip/imap4d/imap4d.c [deleted file]
sys/src/cmd/ip/imap4d/imap4d.h [deleted file]
sys/src/cmd/ip/imap4d/list.c [deleted file]
sys/src/cmd/ip/imap4d/mbox.c [deleted file]
sys/src/cmd/ip/imap4d/mkfile [deleted file]
sys/src/cmd/ip/imap4d/msg.c [deleted file]
sys/src/cmd/ip/imap4d/mutf7.c [deleted file]
sys/src/cmd/ip/imap4d/nodes.c [deleted file]
sys/src/cmd/ip/imap4d/search.c [deleted file]
sys/src/cmd/ip/imap4d/store.c [deleted file]
sys/src/cmd/ip/imap4d/utils.c [deleted file]
sys/src/cmd/ip/mkfile
sys/src/cmd/upas/Mail/dat.h [new file with mode: 0644]
sys/src/cmd/upas/Mail/html.c [new file with mode: 0644]
sys/src/cmd/upas/Mail/mail.c [new file with mode: 0644]
sys/src/cmd/upas/Mail/mesg.c [new file with mode: 0644]
sys/src/cmd/upas/Mail/mkfile [new file with mode: 0644]
sys/src/cmd/upas/Mail/reply.c [new file with mode: 0644]
sys/src/cmd/upas/Mail/util.c [new file with mode: 0644]
sys/src/cmd/upas/Mail/win.c [new file with mode: 0644]
sys/src/cmd/upas/alias/aliasmail.c
sys/src/cmd/upas/alias/mkfile
sys/src/cmd/upas/binscripts/isspam.rc [new file with mode: 0755]
sys/src/cmd/upas/binscripts/mkfile [new file with mode: 0644]
sys/src/cmd/upas/binscripts/mkfile.rc [new file with mode: 0644]
sys/src/cmd/upas/binscripts/msgcat.rc [new file with mode: 0755]
sys/src/cmd/upas/binscripts/spam.rc [new file with mode: 0755]
sys/src/cmd/upas/binscripts/tfmt.rc [new file with mode: 0755]
sys/src/cmd/upas/binscripts/unspam.rc [new file with mode: 0755]
sys/src/cmd/upas/common/appendfiletombox.c [deleted file]
sys/src/cmd/upas/common/aux.c
sys/src/cmd/upas/common/become.c
sys/src/cmd/upas/common/common.h
sys/src/cmd/upas/common/config.c
sys/src/cmd/upas/common/flags.c [new file with mode: 0644]
sys/src/cmd/upas/common/fmt.c [new file with mode: 0644]
sys/src/cmd/upas/common/folder.c [new file with mode: 0644]
sys/src/cmd/upas/common/libsys.c
sys/src/cmd/upas/common/mail.c [deleted file]
sys/src/cmd/upas/common/makefile [deleted file]
sys/src/cmd/upas/common/mkfile
sys/src/cmd/upas/common/process.c
sys/src/cmd/upas/common/sys.h
sys/src/cmd/upas/common/totm.c [new file with mode: 0644]
sys/src/cmd/upas/filterkit/deliver.c
sys/src/cmd/upas/filterkit/list.c
sys/src/cmd/upas/filterkit/mbappend.c [new file with mode: 0644]
sys/src/cmd/upas/filterkit/mbcreate.c [new file with mode: 0644]
sys/src/cmd/upas/filterkit/mbremove.c [new file with mode: 0644]
sys/src/cmd/upas/filterkit/mkfile
sys/src/cmd/upas/filterkit/pipefrom.sample [changed mode: 0644->0755]
sys/src/cmd/upas/filterkit/testemail [new file with mode: 0644]
sys/src/cmd/upas/filterkit/token.c
sys/src/cmd/upas/fs/bos.c [new file with mode: 0644]
sys/src/cmd/upas/fs/cache.c [new file with mode: 0644]
sys/src/cmd/upas/fs/chkidx [new file with mode: 0755]
sys/src/cmd/upas/fs/chkidx.c [new file with mode: 0644]
sys/src/cmd/upas/fs/dat.h
sys/src/cmd/upas/fs/extra/fd2path [new file with mode: 0755]
sys/src/cmd/upas/fs/extra/fd2path.c [new file with mode: 0644]
sys/src/cmd/upas/fs/extra/idxtst.c [new file with mode: 0644]
sys/src/cmd/upas/fs/extra/infotst.c [new file with mode: 0644]
sys/src/cmd/upas/fs/extra/paw [new file with mode: 0755]
sys/src/cmd/upas/fs/extra/paw.c [new file with mode: 0644]
sys/src/cmd/upas/fs/extra/prflags.c [new file with mode: 0644]
sys/src/cmd/upas/fs/extra/strtotmtst.c [new file with mode: 0644]
sys/src/cmd/upas/fs/extra/tokens.c [new file with mode: 0644]
sys/src/cmd/upas/fs/fs.c
sys/src/cmd/upas/fs/header.c [new file with mode: 0644]
sys/src/cmd/upas/fs/idx.c [new file with mode: 0644]
sys/src/cmd/upas/fs/imap.c [new file with mode: 0644]
sys/src/cmd/upas/fs/imap4.c [deleted file]
sys/src/cmd/upas/fs/mbox.c
sys/src/cmd/upas/fs/mdir.c [new file with mode: 0644]
sys/src/cmd/upas/fs/mkfile
sys/src/cmd/upas/fs/mtree.c [new file with mode: 0644]
sys/src/cmd/upas/fs/plan9.c
sys/src/cmd/upas/fs/planb.c
sys/src/cmd/upas/fs/pop3.c
sys/src/cmd/upas/fs/readdir.c [deleted file]
sys/src/cmd/upas/fs/ref.c [new file with mode: 0644]
sys/src/cmd/upas/fs/remove.c [new file with mode: 0644]
sys/src/cmd/upas/fs/rename.c [new file with mode: 0644]
sys/src/cmd/upas/fs/rfc2047-test [deleted file]
sys/src/cmd/upas/fs/seg.c [new file with mode: 0644]
sys/src/cmd/upas/fs/strtotm.c
sys/src/cmd/upas/fs/tester.c [deleted file]
sys/src/cmd/upas/imap4d/auth.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/capability [new file with mode: 0644]
sys/src/cmd/upas/imap4d/copy.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/csquery.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/date.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/debug.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/fetch.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/fns.h [new file with mode: 0644]
sys/src/cmd/upas/imap4d/folder.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/fsenc.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/fstree.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/imap4d.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/imap4d.h [new file with mode: 0644]
sys/src/cmd/upas/imap4d/imp.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/list.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/mbox.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/mkfile [new file with mode: 0644]
sys/src/cmd/upas/imap4d/msg.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/mutf7.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/nlisttst.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/nodes.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/print.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/quota.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/search.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/store.c [new file with mode: 0644]
sys/src/cmd/upas/imap4d/utils.c [new file with mode: 0644]
sys/src/cmd/upas/marshal/marshal.c
sys/src/cmd/upas/marshal/mkfile
sys/src/cmd/upas/misc/empty.c [new file with mode: 0644]
sys/src/cmd/upas/misc/go.fishing [deleted file]
sys/src/cmd/upas/misc/gone.fishing [deleted file]
sys/src/cmd/upas/misc/gone.msg
sys/src/cmd/upas/misc/mail [deleted file]
sys/src/cmd/upas/misc/mail.rc [new file with mode: 0755]
sys/src/cmd/upas/misc/mkfile
sys/src/cmd/upas/misc/omail.rc [new file with mode: 0755]
sys/src/cmd/upas/misc/unix/gone.fishing.sh [deleted file]
sys/src/cmd/upas/misc/unix/mail.c [deleted file]
sys/src/cmd/upas/misc/unix/mail.sh [deleted file]
sys/src/cmd/upas/misc/unix/makefile [deleted file]
sys/src/cmd/upas/mkfile
sys/src/cmd/upas/mkupas [new file with mode: 0644]
sys/src/cmd/upas/ml/common.c
sys/src/cmd/upas/ml/mkfile
sys/src/cmd/upas/ml/ml.c
sys/src/cmd/upas/ml/mlmgr.c
sys/src/cmd/upas/ml/mlowner.c
sys/src/cmd/upas/ned/mkfile
sys/src/cmd/upas/ned/nedmail.c
sys/src/cmd/upas/pop3/mkfile
sys/src/cmd/upas/pop3/pop3.c
sys/src/cmd/upas/q/mkfile
sys/src/cmd/upas/q/qer.c
sys/src/cmd/upas/q/runq.c
sys/src/cmd/upas/qfrom/mkfile [new file with mode: 0644]
sys/src/cmd/upas/qfrom/qfrom.c [new file with mode: 0644]
sys/src/cmd/upas/scanmail/mkfile
sys/src/cmd/upas/scanmail/scanmail.c
sys/src/cmd/upas/scanmail/spam.h
sys/src/cmd/upas/scanmail/testscan.c
sys/src/cmd/upas/send/authorize.c
sys/src/cmd/upas/send/bind.c
sys/src/cmd/upas/send/cat_mail.c
sys/src/cmd/upas/send/dest.c
sys/src/cmd/upas/send/filter.c
sys/src/cmd/upas/send/gateway.c
sys/src/cmd/upas/send/local.c
sys/src/cmd/upas/send/log.c
sys/src/cmd/upas/send/main.c
sys/src/cmd/upas/send/makefile [deleted file]
sys/src/cmd/upas/send/message.c
sys/src/cmd/upas/send/mkfile
sys/src/cmd/upas/send/rewrite.c
sys/src/cmd/upas/send/send.h
sys/src/cmd/upas/send/skipequiv.c
sys/src/cmd/upas/send/translate.c
sys/src/cmd/upas/send/tryit [deleted file]
sys/src/cmd/upas/smtp/greylist.c
sys/src/cmd/upas/smtp/mkfile
sys/src/cmd/upas/smtp/mxdial.c
sys/src/cmd/upas/smtp/parsetest.c [new file with mode: 0644]
sys/src/cmd/upas/smtp/rfc822.y
sys/src/cmd/upas/smtp/rmtdns.c [deleted file]
sys/src/cmd/upas/smtp/smtp.c
sys/src/cmd/upas/smtp/smtp.h
sys/src/cmd/upas/smtp/smtpd.c
sys/src/cmd/upas/smtp/spam.c
sys/src/cmd/upas/spf/dns.c [new file with mode: 0644]
sys/src/cmd/upas/spf/macro.c [new file with mode: 0644]
sys/src/cmd/upas/spf/mkfile [new file with mode: 0644]
sys/src/cmd/upas/spf/mtest.c [new file with mode: 0644]
sys/src/cmd/upas/spf/spf.c [new file with mode: 0644]
sys/src/cmd/upas/spf/spf.h [new file with mode: 0644]
sys/src/cmd/upas/spf/testsuite [new file with mode: 0644]
sys/src/cmd/upas/unesc/mkfile
sys/src/cmd/upas/vf/mkfile
sys/src/cmd/upas/vf/vf.c

diff --git a/acme/mail/guide b/acme/mail/guide
deleted file mode 100644 (file)
index 8977ac7..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Mail stored
-plumb /mail/box/$user/names
-mail -'x' someaddress
-mkbox /mail/box/$user/new_box
diff --git a/acme/mail/mkbox b/acme/mail/mkbox
deleted file mode 100755 (executable)
index 3e98afc..0000000
+++ /dev/null
@@ -1,11 +0,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
-}
diff --git a/acme/mail/readme b/acme/mail/readme
deleted file mode 100644 (file)
index b795a04..0000000
+++ /dev/null
@@ -1,57 +0,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.
diff --git a/acme/mail/src/dat.h b/acme/mail/src/dat.h
deleted file mode 100644 (file)
index 0b0b60a..0000000
+++ /dev/null
@@ -1,164 +0,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;
diff --git a/acme/mail/src/html.c b/acme/mail/src/html.c
deleted file mode 100644 (file)
index 6473085..0000000
+++ /dev/null
@@ -1,75 +0,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;
-}
diff --git a/acme/mail/src/mail.c b/acme/mail/src/mail.c
deleted file mode 100644 (file)
index 1e1ff0b..0000000
+++ /dev/null
@@ -1,550 +0,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;
-                       }
-               }
-       }
-}
-
diff --git a/acme/mail/src/mesg.c b/acme/mail/src/mesg.c
deleted file mode 100644 (file)
index 620e76e..0000000
+++ /dev/null
@@ -1,1322 +0,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);
-}
diff --git a/acme/mail/src/mkfile b/acme/mail/src/mkfile
deleted file mode 100644 (file)
index aa6545d..0000000
+++ /dev/null
@@ -1,30 +0,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
-
diff --git a/acme/mail/src/reply.c b/acme/mail/src/reply.c
deleted file mode 100644 (file)
index 65841c5..0000000
+++ /dev/null
@@ -1,567 +0,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;
-}
diff --git a/acme/mail/src/util.c b/acme/mail/src/util.c
deleted file mode 100644 (file)
index bbc8788..0000000
+++ /dev/null
@@ -1,105 +0,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");
-}
diff --git a/acme/mail/src/win.c b/acme/mail/src/win.c
deleted file mode 100644 (file)
index 53d4ed1..0000000
+++ /dev/null
@@ -1,341 +0,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;
-}
index fc6fc9abeaf842cb7ceda7131ec31e6eca7589ad..002e4de254d7fe325f2ece0e1cf2e22063f0dd87 100644 (file)
@@ -5,6 +5,5 @@ none:VQ:
 
 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}
index 0226f35c057f87d1d9a9ed36b69cfb256318b524..0775878bc69be9e53a21b4d0b541b6734a2650a8 100644 (file)
@@ -40,6 +40,7 @@ ALL=\
        spin\
        port\
        colophon\
+       nupas/nupas\
 
 ALLPS=${ALL:%=%.ps}
 HTML=${ALL:%=%.html} release3.html release4.html
diff --git a/sys/doc/nupas/macros.ms b/sys/doc/nupas/macros.ms
new file mode 100644 (file)
index 0000000..330626d
--- /dev/null
@@ -0,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
diff --git a/sys/doc/nupas/mkfile b/sys/doc/nupas/mkfile
new file mode 100644 (file)
index 0000000..85f6297
--- /dev/null
@@ -0,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
diff --git a/sys/doc/nupas/nupas.ms b/sys/doc/nupas/nupas.ms
new file mode 100644 (file)
index 0000000..a00b60e
--- /dev/null
@@ -0,0 +1,354 @@
+.EQ
+delim $$
+.EN
+.TL
+Scaling Upas
+.AU
+Erik Quanstrom
+quanstro@coraid.com
+.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.
index 1541ebc29d908e49c0c745f5b71dbf3665578e39..d9db7d77ad72577a61e7b4f2bba3f57d96dae6d0 100644 (file)
@@ -20,7 +20,7 @@ pop3, imap4d \- Internet mail servers
 .B -p
 ]
 .PP
-.B ip/imap4d
+.B upas/imap4d
 .RB [ -acpv ]
 .RB [ -d
 .IR smtpdomain ]
@@ -142,7 +142,7 @@ debugging output
 .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),
diff --git a/sys/src/cmd/ip/imap4d/auth.c b/sys/src/cmd/ip/imap4d/auth.c
deleted file mode 100644 (file)
index b8dcd75..0000000
+++ /dev/null
@@ -1,184 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/copy.c b/sys/src/cmd/ip/imap4d/copy.c
deleted file mode 100644 (file)
index a1b3f6a..0000000
+++ /dev/null
@@ -1,259 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/csquery.c b/sys/src/cmd/ip/imap4d/csquery.c
deleted file mode 100644 (file)
index d573616..0000000
+++ /dev/null
@@ -1,44 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/date.c b/sys/src/cmd/ip/imap4d/date.c
deleted file mode 100644 (file)
index 9c9c466..0000000
+++ /dev/null
@@ -1,308 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/debug.c b/sys/src/cmd/ip/imap4d/debug.c
deleted file mode 100644 (file)
index 9dbfcb2..0000000
+++ /dev/null
@@ -1,108 +0,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);
-}
diff --git a/sys/src/cmd/ip/imap4d/fetch.c b/sys/src/cmd/ip/imap4d/fetch.c
deleted file mode 100644 (file)
index 30fc0f4..0000000
+++ /dev/null
@@ -1,625 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/fns.h b/sys/src/cmd/ip/imap4d/fns.h
deleted file mode 100644 (file)
index 4f86946..0000000
+++ /dev/null
@@ -1,125 +0,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)
diff --git a/sys/src/cmd/ip/imap4d/folder.c b/sys/src/cmd/ip/imap4d/folder.c
deleted file mode 100644 (file)
index 8a74889..0000000
+++ /dev/null
@@ -1,398 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/imap4d.c b/sys/src/cmd/ip/imap4d/imap4d.c
deleted file mode 100644 (file)
index 6eafe48..0000000
+++ /dev/null
@@ -1,2102 +0,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;
-}
-
diff --git a/sys/src/cmd/ip/imap4d/imap4d.h b/sys/src/cmd/ip/imap4d/imap4d.h
deleted file mode 100644 (file)
index bc2723f..0000000
+++ /dev/null
@@ -1,378 +0,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"
diff --git a/sys/src/cmd/ip/imap4d/list.c b/sys/src/cmd/ip/imap4d/list.c
deleted file mode 100644 (file)
index 139e0f2..0000000
+++ /dev/null
@@ -1,412 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/mbox.c b/sys/src/cmd/ip/imap4d/mbox.c
deleted file mode 100644 (file)
index 8eb57df..0000000
+++ /dev/null
@@ -1,863 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/mkfile b/sys/src/cmd/ip/imap4d/mkfile
deleted file mode 100644 (file)
index afadea9..0000000
+++ /dev/null
@@ -1,31 +0,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
diff --git a/sys/src/cmd/ip/imap4d/msg.c b/sys/src/cmd/ip/imap4d/msg.c
deleted file mode 100644 (file)
index 46ccea2..0000000
+++ /dev/null
@@ -1,1782 +0,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;
-               }
-       }
-}
diff --git a/sys/src/cmd/ip/imap4d/mutf7.c b/sys/src/cmd/ip/imap4d/mutf7.c
deleted file mode 100644 (file)
index 6d84b4e..0000000
+++ /dev/null
@@ -1,174 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/nodes.c b/sys/src/cmd/ip/imap4d/nodes.c
deleted file mode 100644 (file)
index 9fe848e..0000000
+++ /dev/null
@@ -1,213 +0,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);
-}
diff --git a/sys/src/cmd/ip/imap4d/search.c b/sys/src/cmd/ip/imap4d/search.c
deleted file mode 100644 (file)
index 12f462b..0000000
+++ /dev/null
@@ -1,244 +0,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;
-}
diff --git a/sys/src/cmd/ip/imap4d/store.c b/sys/src/cmd/ip/imap4d/store.c
deleted file mode 100644 (file)
index b13c3bb..0000000
+++ /dev/null
@@ -1,127 +0,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);
-}
diff --git a/sys/src/cmd/ip/imap4d/utils.c b/sys/src/cmd/ip/imap4d/utils.c
deleted file mode 100644 (file)
index 6e081a6..0000000
+++ /dev/null
@@ -1,182 +0,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;
-}
index 71c4c6fbc0c79854b248ef9b28c1c8f247abf95b..900bb17f70d6e2c93b3d057d667e8291025fdbc7 100644 (file)
@@ -27,7 +27,7 @@ TARG =        6in4\
        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
diff --git a/sys/src/cmd/upas/Mail/dat.h b/sys/src/cmd/upas/Mail/dat.h
new file mode 100644 (file)
index 0000000..4ef95ce
--- /dev/null
@@ -0,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;
diff --git a/sys/src/cmd/upas/Mail/html.c b/sys/src/cmd/upas/Mail/html.c
new file mode 100644 (file)
index 0000000..6473085
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/Mail/mail.c b/sys/src/cmd/upas/Mail/mail.c
new file mode 100644 (file)
index 0000000..a0d9fd1
--- /dev/null
@@ -0,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;
+                       }
+               }
+       }
+}
+
diff --git a/sys/src/cmd/upas/Mail/mesg.c b/sys/src/cmd/upas/Mail/mesg.c
new file mode 100644 (file)
index 0000000..16abad8
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/Mail/mkfile b/sys/src/cmd/upas/Mail/mkfile
new file mode 100644 (file)
index 0000000..260281d
--- /dev/null
@@ -0,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
+
diff --git a/sys/src/cmd/upas/Mail/reply.c b/sys/src/cmd/upas/Mail/reply.c
new file mode 100644 (file)
index 0000000..0a5edf4
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/Mail/util.c b/sys/src/cmd/upas/Mail/util.c
new file mode 100644 (file)
index 0000000..c32de5f
--- /dev/null
@@ -0,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");
+}
diff --git a/sys/src/cmd/upas/Mail/win.c b/sys/src/cmd/upas/Mail/win.c
new file mode 100644 (file)
index 0000000..53d4ed1
--- /dev/null
@@ -0,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;
+}
index 5a8c007b7a9221827a52424dda8e2aae45b55178..e832eb558bf25c9f6138bc77d6960174fa90df1e 100644 (file)
@@ -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 int debug;
-static int from;
-static char *namefiles = "namefiles";
-#define DEBUG if(debug)
-
-/* loop through the names to be translated */
+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 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 @@ main(int argc, char *argv[])
                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 @@ main(int argc, char *argv[])
 static String *
 getdbfiles(void)
 {
-       Sinstack *sp;
-       String *files = s_new();
        char *nf;
+       Sinstack *sp;
+       String *files;
 
        if(from)
                nf = "fromfiles";
@@ -100,37 +98,34 @@ getdbfiles(void)
                nf = namefiles;
 
        /* system wide aliases */
+       files = s_new();
        if ((sp = s_allocinstack(nf)) != 0){
                while(s_rdinstack(sp, files))
                        s_append(files, " ");
                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 @@ translate(char *name,             /* name to translate */
 
        /* 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,29 +177,30 @@ attobang(String *token)
 /*  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;
 
        /* 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 @@ lookup(
        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;
 }
index 1fe8b1df695d5a9e060450047b3bac052b70cda8..9eb3f37f0c2a1876e0df293a59a6a382dcd2a912 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=aliasmail
 
@@ -10,8 +11,6 @@ HFILES=../common/common.h\
         ../common/sys.h\
 
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        $HFILES\
diff --git a/sys/src/cmd/upas/binscripts/isspam.rc b/sys/src/cmd/upas/binscripts/isspam.rc
new file mode 100755 (executable)
index 0000000..6d8107f
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /mail/lib/isspam.rc $*
diff --git a/sys/src/cmd/upas/binscripts/mkfile b/sys/src/cmd/upas/binscripts/mkfile
new file mode 100644 (file)
index 0000000..ea125f7
--- /dev/null
@@ -0,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
+
diff --git a/sys/src/cmd/upas/binscripts/mkfile.rc b/sys/src/cmd/upas/binscripts/mkfile.rc
new file mode 100644 (file)
index 0000000..c359384
--- /dev/null
@@ -0,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
diff --git a/sys/src/cmd/upas/binscripts/msgcat.rc b/sys/src/cmd/upas/binscripts/msgcat.rc
new file mode 100755 (executable)
index 0000000..f6f536b
--- /dev/null
@@ -0,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=]
diff --git a/sys/src/cmd/upas/binscripts/spam.rc b/sys/src/cmd/upas/binscripts/spam.rc
new file mode 100755 (executable)
index 0000000..88dda04
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /mail/lib/spam.rc $*
diff --git a/sys/src/cmd/upas/binscripts/tfmt.rc b/sys/src/cmd/upas/binscripts/tfmt.rc
new file mode 100755 (executable)
index 0000000..c5e6641
--- /dev/null
@@ -0,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=]
diff --git a/sys/src/cmd/upas/binscripts/unspam.rc b/sys/src/cmd/upas/binscripts/unspam.rc
new file mode 100755 (executable)
index 0000000..04213f3
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/rc
+exec /mail/lib/unspam.rc $*
diff --git a/sys/src/cmd/upas/common/appendfiletombox.c b/sys/src/cmd/upas/common/appendfiletombox.c
deleted file mode 100644 (file)
index 16bdd48..0000000
+++ /dev/null
@@ -1,164 +0,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;
-}
index 8786acb50f5dc89851ac5cc8d7448bb98277ff1e..985e3f29695046e474594fe328d9093f05cb0f9e 100644 (file)
@@ -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 @@ escapespecial(String *s)
        return ns;
 }
 
-int
+uint
 hex2uint(char x)
 {
        if(x >= '0' && x <= '9')
@@ -113,10 +76,9 @@ hex2uint(char x)
 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 @@ unescapespecial(String *s)
        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 @@ unescapespecial(String *s)
 int
 returnable(char *path)
 {
-
        return strcmp(path, "/dev/null") != 0;
 }
index 8c012d212370a2275ea84d7ddbd11cde3e2a5920..37cd119972ec86deb255e325dcd369ebac2833ea 100644 (file)
@@ -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) {
index 3f9acbd1871ad0908c9c9d1fe67130074309024f..9cefc0531e11e420d1bee49e33fa00d2c5ff52f3 100644 (file)
@@ -1,80 +1,79 @@
-#include "sys.h"
+enum
+{
+       Elemlen = 56,
+       Pathlen = 256,
+};
 
-/* format of REMOTE FROM lines */
-extern char *REMFROMRE;
-extern int REMSENDERMATCH;
-extern int REMDATEMATCH;
-extern int REMSYSMATCH;
+#include "sys.h"
+#include <String.h>
 
-/* 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{
+       Fields  = 18,
 
-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*/
 } stream;
 
 /* a child process*/
-typedef struct process{
+typedef struct{
        stream  *std[3];        /* standard fd's*/
        int     pid;            /* process identifier*/
        int     status;         /* exit status*/
        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*);
index f322783efb76e9cd7c2c5db6f93859046dbd4cbd..6bf8ef923e936daf8b1b623ea6782c238744230c 100644 (file)
@@ -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";
diff --git a/sys/src/cmd/upas/common/flags.c b/sys/src/cmd/upas/common/flags.c
new file mode 100644 (file)
index 0000000..2dc0220
--- /dev/null
@@ -0,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;
+       }
+}
diff --git a/sys/src/cmd/upas/common/fmt.c b/sys/src/cmd/upas/common/fmt.c
new file mode 100644 (file)
index 0000000..e159ad2
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/common/folder.c b/sys/src/cmd/upas/common/folder.c
new file mode 100644 (file)
index 0000000..1d5e345
--- /dev/null
@@ -0,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;
+}
index f0b8f35a411c4bd6fb2cf683d771b874ff796329..969c2ff67c7fba081a6e267262c4f56706b54124 100644 (file)
@@ -2,68 +2,43 @@
 #include <auth.h>
 #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;
 }
 
 /*
  *  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;
-
-       /*
-        *  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");
+       char *e, *q;
 
-       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 @@ static int
 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;
-               }
-
-               /* 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;
-                       }
+       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);
                }
-       } 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 @@ openlockfile(Mlock *l)
  *  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,25 +122,18 @@ syslock(char *path)
        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;
                case 1:
                        sleep(500);
                        break;
-               default:
-                       goto noway;
                }
-       }
-
-noway:
-       s_free(l->name);
        free(l);
        return nil;
 }
@@ -188,20 +141,19 @@ noway:
 /*
  *  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 @@ trylock(char *path)
                        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 @@ syslockrefresh(Mlock *l)
        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 @@ sysunlock(Mlock *l)
  *     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 @@ sysopen(char *path, char *mode, ulong perm)
        docreate = 0;
        append = 0;
        truncate = 0;
-       for(; mode && *mode; mode++)
+       for(; *mode; mode++)
                switch(*mode){
                case 'A':
                        sysmode = OWRITE;
@@ -371,15 +314,6 @@ sysclose(Biobuf *bp)
        return rv;
 }
 
-/*
- *  create a file
- */
-int
-syscreate(char *file, int mode, ulong perm)
-{
-       return create(file, mode, perm);
-}
-
 /*
  *  make a directory
  */
@@ -394,76 +328,45 @@ sysmkdir(char *file, ulong perm)
        return 0;
 }
 
-/*
- *  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;
-
-       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);
+       char *s, *c;
+
+       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 @@ sysnames_read(void)
                        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 @@ sysnames_read(void)
 /*
  *  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 @@ sysrename(char *old, char *new)
        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 nonzero if file is a directory
- */
-extern int
-sysisdir(char *file)
-{
-       Dir     *d;
-       int     rv;
-
-       d = dirstat(file);
-       if(d == nil)
-               return 0;
-       rv = d->mode & DMDIR;
-       free(d);
-       return rv;
+       return access(file, AEXIST) == 0;
 }
 
-/*
- * 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;
-       
-}
+static char yankeepig[] = "die: yankee pig dog";
 
-/*
- *  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 @@ sysdetach(void)
  */
 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 @@ pipesigoff(void)
        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,20 +517,20 @@ holdon(void)
        return fd;
 }
 
-extern int
+int
 sysopentty(void)
 {
        return open("/dev/cons", ORDWR);
 }
 
-extern void
+void
 holdoff(int fd)
 {
        write(fd, "holdoff", 7);
        close(fd);
 }
 
-extern int
+int
 sysfiles(void)
 {
        return 128;
@@ -754,142 +542,66 @@ sysfiles(void)
  *  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)
-{
-       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)
+/*
+ * 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)
 {
+       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;
-
-       if(dir == 0){
-               if(fd2path(fd, buf, sizeof(buf)) != 0)
-                       return "";
+       char *s;
+       Biobuf *b;
 
-               /* 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);
+       }
+       if(s == 0 && (b = Bopen("/adm/netkeys.who", OREAD))){
+               s = username0(b, from);
+               Bterm(b);
        }
-       return "";
+       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 @@ docreate(char *file, int perm)
        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 @@ docreate(char *file, int perm)
        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;
-
-       mailfile = s_new();
-       if(folder == 0)
-               mboxname(user, mailfile);
-       else {
-               snprint(buf, sizeof(buf), "%s/mbox", folder);
-               mboxpath(buf, user, mailfile, 0);
-       }
+       char *p, *s, buf[Pathlen];
+       int isdir, mode;
+       Dir *d;
 
-       // 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 = '/';
        }
-
-       //  create the mbox
-       if(docreate(s_to_c(mailfile), 0622|DMAPPEND|DMEXCL) < 0)
-               return -1;
+       /* must match folder.c:/^openfolder */
+       isdir = create(buf, OREAD, DMDIR|0777);
 
        /*
-        *  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");
+}
diff --git a/sys/src/cmd/upas/common/mail.c b/sys/src/cmd/upas/common/mail.c
deleted file mode 100644 (file)
index 5347c65..0000000
+++ /dev/null
@@ -1,57 +0,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;
-}
diff --git a/sys/src/cmd/upas/common/makefile b/sys/src/cmd/upas/common/makefile
deleted file mode 100644 (file)
index c7beae8..0000000
+++ /dev/null
@@ -1,18 +0,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
-
index 7422fbe3a081533bdaf86447730df2405a1f516a..5a628ae476e88213200f982eae02341bcc0bc47a 100644 (file)
@@ -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 @@ UPDATE=\
        $HFILES\
 
 </sys/src/cmd/mklib
+
+nuke:V:
+       mk clean
+       rm -f libcommon.a[$OS]
index f698c96004445bdda69b3c47e2607d53d6d3d416..b7c0df0ca54985953b1c34aecf954722ed0f0b99 100644 (file)
@@ -96,8 +96,7 @@ noshell_proc_start(char **av, stream *inp, stream *outp, stream *errp, int newpg
                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 @@ extern int
 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 @@ proc_free(process *pp)
        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);
+//}
index a50f27284af7f701a95976ceba59b985de6962fd..a860df15f5cc2d7258ddee03efa7a267a7cd7286 100644 (file)
@@ -1,85 +1,63 @@
-/*
- * 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
  */
 typedef struct Mlock   Mlock;
 struct Mlock {
-       int fd;
-       int pid;
-       String *name;
+       int     fd;
+       int     pid;
+       char    name[Pathlen];
 };
 
 /*
  *  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 */
 
-/*
- *  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*);
+enum {
+       Mboxmode        = 0622,
+};
 
 /*
- *  maximum size for a file path
+ *  files in libsys.c
  */
-#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*);
diff --git a/sys/src/cmd/upas/common/totm.c b/sys/src/cmd/upas/common/totm.c
new file mode 100644 (file)
index 0000000..8c05fb7
--- /dev/null
@@ -0,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);
+}
index b968eb6c0ea4ce3cd723f309e4cbcbad834fee7b..9040e8021a0f63ad0200d90a1b760a83b0fa32ca 100644 (file)
@@ -7,64 +7,32 @@
 void
 usage(void)
 {
-       fprint(2, "usage: %s recipient fromfile mbox\n", argv0);
+       fprint(2, "usage: deliver recipient fromaddr-file mbox\n");
        exits("usage");
 }
 
 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("");
 }
index 06259bb9ee0e53bc438406e535b201e2419f8bfc..1d78843cf95ec649966a800c1e88c395d87febfa 100644 (file)
@@ -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;
diff --git a/sys/src/cmd/upas/filterkit/mbappend.c b/sys/src/cmd/upas/filterkit/mbappend.c
new file mode 100644 (file)
index 0000000..3f9b475
--- /dev/null
@@ -0,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("");
+}
diff --git a/sys/src/cmd/upas/filterkit/mbcreate.c b/sys/src/cmd/upas/filterkit/mbcreate.c
new file mode 100644 (file)
index 0000000..36bda6a
--- /dev/null
@@ -0,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("");
+}
diff --git a/sys/src/cmd/upas/filterkit/mbremove.c b/sys/src/cmd/upas/filterkit/mbremove.c
new file mode 100644 (file)
index 0000000..94cfd80
--- /dev/null
@@ -0,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("");
+}
index c077d56411bd1392db7b4aee235e33aea2b9cd61..18415495fa2438838da9f3ad450ead6625a716ab 100644 (file)
@@ -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\
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/sys/src/cmd/upas/filterkit/testemail b/sys/src/cmd/upas/filterkit/testemail
new file mode 100644 (file)
index 0000000..296c6dd
--- /dev/null
@@ -0,0 +1,4 @@
+From: erik quanstrom <quanstro@coraid.com>
+Subject: 1 testing
+
+testing
index ba48bc310a8b4e36770c3738f79810c4f281020d..ef0e136a37d20b9a652276ef7b0b6ce59c4a2a81 100644 (file)
@@ -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 @@ check_token(char *key, char *file)
 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 @@ main(int argc, char **argv)
        switch(argc){
        case 2:
                exits(check_token(argv[0], argv[1]));
-               break;
        case 1:
                exits(create_token(argv[0]));
-               break;
        default:
                usage();
        }
diff --git a/sys/src/cmd/upas/fs/bos.c b/sys/src/cmd/upas/fs/bos.c
new file mode 100644 (file)
index 0000000..fdbf83f
--- /dev/null
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+/*
+ * assume:
+ * - the stack segment can't be resized
+ * - stacks may not be segattached (by name Stack, anyway)
+ * - no thread library
+ */
+uintptr
+absbos(void)
+{
+       char buf[64], *f[10], *s, *r;
+       int n;
+       uintptr p, q;
+       Biobuf *b;
+
+       p = 0xd0000000; /* guess pc kernel */
+       snprint(buf, sizeof buf, "/proc/%ud/segment", getpid());
+       b = Bopen(buf, OREAD);
+       if(b == nil)
+               return p;
+       for(; s = Brdstr(b, '\n', 1); free(s)){
+               if((n = tokenize(s, f, nelem(f))) < 3)
+                       continue;
+               if(strcmp(f[0], "Stack") != 0)
+                       continue;
+               /*
+                * addressing from end because segment
+                * flags could become discontiguous  if
+                * additional flags are added
+                */
+               q = strtoull(f[n - 3], &r, 16);
+               if(*r == 0 && (char*)q > end)
+                       p = q;
+       }
+       Bterm(b);
+       return p;
+}
diff --git a/sys/src/cmd/upas/fs/cache.c b/sys/src/cmd/upas/fs/cache.c
new file mode 100644 (file)
index 0000000..2d0a628
--- /dev/null
@@ -0,0 +1,552 @@
+#include "common.h"
+#include <libsec.h>
+#include "dat.h"
+
+int
+findcache(Mcache *c, Message *m)
+{
+       int i;
+
+       for(i = 0; i < c->ntab; i++)
+               if(c->ctab[i] == m)
+                       return i;
+       return -1;
+}
+
+static void
+prcache(Mcache *c, char *prefix)
+{
+       int j;
+       Message *m;
+
+       if(!debug)
+               return;
+       for(j = 0; j < c->ntab; j++){
+               m = c->ctab[j];
+               dprint("%s%d/%s\t%p\t%d\t%ld\n", prefix, j, m->name, m, m->refs, m->csize);
+       }
+}
+
+/* debugging only */
+static void
+dupchk(Mcache *c)
+{
+       int i, j;
+
+       if(!debug)
+               return;
+       for(i = 0; i < c->ntab; i++)
+               for(j = i + 1; j < c->ntab; j++)
+                       if(c->ctab[i] == c->ctab[j])
+                               goto lose;
+       return;
+lose:
+       for(j = 0; j < c->ntab; j++)
+               dprint("%d\t%p  %d\t%ld\n", j, c->ctab[j], c->ctab[j]->refs, c->ctab[j]->size);
+       abort();
+}
+       
+int
+addcache(Mcache *c, Message *m)
+{
+       int i;
+
+       if((i = findcache(c, m)) < 0){
+               if(c->ntab + 1 == nelem(c->ctab))
+                       abort();
+               i = c->ntab++;
+       }else{
+               /* rotate */
+               if(i == c->ntab - 1)
+                       return i;               /* silly shortcut to prevent excessive printage. */
+               dprint("addcache rotate %d %d\n", i, c->ntab);
+               prcache(c, "");
+               memmove(c->ctab + i, c->ctab + i + 1, (c->ntab - i - 1)*sizeof c->ctab[0]);
+               i = c->ntab - 1;
+c->ctab[i] = m;
+dupchk(c);
+       }
+       dprint("addcache %d %d  %p\n", i, c->ntab, m);
+       c->ctab[i] = m;
+       return i;
+}
+
+static void
+notecache(Mailbox *mb, Message *m, long sz)
+{
+       assert(Topmsg(mb, m));
+       assert(sz >= 0 && sz < Maxmsg);
+       m->csize += sz;
+       mb->cached += sz;
+       addcache(mb, m);
+}
+
+static long
+cachefree0(Mailbox *mb, Message *m, int force)
+{
+       long sz, i;
+       Message *s;
+
+       if(!force && !mb->fetch)
+               return 0;
+       for(s = m->part; s; s = s->next)
+               cachefree(mb, s, force);
+       dprint("cachefree: %D   %p,     %p\n", m->fileid, m, m->start);
+       if(m->mallocd){
+               free(m->start);
+               m->mallocd = 0;
+       }
+       if(m->ballocd){
+               free(m->body);
+               m->ballocd = 0;
+       }
+       if(m->hallocd){
+               free(m->header);
+               m->hallocd = 0;
+       }
+       for(i = 0; i < nelem(m->references); i++){
+               free(m->references[i]);
+               m->references[i] = 0;
+       }
+       sz = m->csize;
+       m->csize = 0;
+       m->start = 0;
+       m->end = 0;
+       m->header = 0;
+       m->hend = 0;
+       m->hlen = -1;
+       m->body = 0;
+       m->bend = 0;
+       m->mheader = 0;
+       m->mhend = 0;
+       if(mb->decache)
+               mb->decache(mb, m);
+       m->decoded = 0;
+       m->converted = 0;
+       m->badchars = 0;
+       m->cstate &= ~(Cheader|Cbody);
+       if(Topmsg(mb, m))
+               mb->cached -= sz;
+       return sz;
+}
+
+long
+cachefree(Mailbox *mb, Message *m, int force)
+{
+       long sz, i;
+
+       sz = cachefree0(mb, m, force);
+       for(i = 0; i < mb->ntab; i++)
+               if(m == mb->ctab[i]){
+                       mb->ntab--;
+                       memmove(mb->ctab + i, mb->ctab + i + 1, sizeof m*mb->ntab - i);
+                       dupchk(mb);
+                       break;
+               }
+       return sz;
+}
+
+enum{
+       Maxntab = nelem(mbl->ctab) - 10,
+};
+
+vlong
+sumcache(Mcache *c)
+{
+       int i;
+       vlong sz;
+
+       sz = 0;
+       for(i = 0; i < c->ntab; i++)
+               sz += c->ctab[i]->csize;
+       return sz;
+}
+
+int
+scancache(Mcache *c)
+{
+       int i;
+
+       for(i = 0; i < c->ntab; i++)
+               if(c->ctab[i]->csize > Maxmsg)
+                       return -1;
+       return 0;
+}
+
+/* debugging only */
+static void
+chkcsize(Mailbox *mb, vlong sz, vlong sz0)
+{
+       int j;
+       Mcache *c;
+       Message *m;
+
+       if(sumcache(mb) == mb->cached)
+       if(scancache(mb) == 0)
+               return;
+       eprint("sz0 %lld sz %lld sum %lld sumca %lld\n", sz0, sz, sumcache(mb), mb->cached);
+       eprint("%lld\n", sumcache(mb));
+       c = mb;
+       for(j = 0; j < c->ntab; j++){
+               m = c->ctab[j];
+               eprint("%d      %p      %d      %ld     %ld\n", j, m, m->refs, m->csize, m->size);
+       }
+       abort();
+}
+
+/*
+ * strategy: start with i = 0. while cache exceeds limits,
+ * find j so that all the [i:j] elements have refs == 0.
+ * uncache all the [i:j], reduce ntab by i-j.  the tail
+ * [j+1:ntab] is shifted to [i:ntab], and finally i = i+1.
+ * we may safely skip the new i, since the condition
+ * that stopped our scan there still holds.
+ */
+void
+putcache(Mailbox *mb, Message *m)
+{
+       int i, j, k;
+       vlong sz, sz0;
+       Message **p;
+
+       p = mb->ctab;
+       sz0 = mb->cached;
+       dupchk(mb);
+       for(i = 0;; i++){
+               sz = mb->cached;
+               for(j = i;; j++){
+                       if(j >= mb->ntab ||
+                       sz < cachetarg && mb->ntab - (j - i) < Maxntab){
+                               if(j != i)
+                                       break;
+chkcsize(mb, sz, sz0);
+                               return;
+                       }
+                       if(p[j]->refs > 0)
+                               break;
+                       sz -= p[j]->csize;
+               }
+               if(sz == mb->cached){
+                       if(i >= mb->ntab)
+                               break;
+                       continue;
+               }
+               for(k = i; k < j; k++)
+                       cachefree0(mb, p[k], 0);
+               mb->ntab -= j - i;
+               memmove(p + i, p + j, (mb->ntab - i)*sizeof *p);
+       }
+chkcsize(mb, sz, sz0);
+       k = 0;
+       for(i = 0; i < mb->ntab; i++)
+               k += p[i]->refs > 0;
+       if((mb->ntab > 1 || k != mb->ntab) && Topmsg(mb, m))
+               eprint("cache overflow: %D %llud bytes; %d entries\n", 
+                       m? m->fileid: 1ll, mb->cached, mb->ntab);
+       if(k == mb->ntab)
+               return;
+       debug = 1; prcache(mb, "");
+       abort();
+}
+
+static int
+squeeze(Message *m, uvlong o, long l, int c)
+{
+       char *p, *q, *e;
+       int n;
+
+       q = memchr(m->start + o, c, l);
+       if(q == nil)
+               return 0;
+       n = 0;
+       e = m->start + o + l;
+       for(p = q; q < e; q++){
+               if(*q == c){
+                       n++;
+                       continue;
+               }
+               *p++ = *q;
+       }
+       return n;
+}
+
+void
+msgrealloc(Message *m, ulong l)
+{
+       long l0, h0, m0, me, b0;
+
+       l0 = m->end - m->start;
+       m->mallocd = 1;
+       h0 = m->hend - m->start;
+       m0 = m->mheader - m->start;
+       me = m->mhend - m->start;
+       b0 = m->body - m->start;
+       assert(h0 >= 0 && m0 >= 0 && me >= 0 && b0 >= 0);
+       m->start = erealloc(m->start, l + 1);
+       m->rbody = m->start + b0;
+       m->rbend = m->end = m->start + l0;
+       if(!m->hallocd){
+               m->header = m->start;
+               m->hend = m->start + h0;
+       }
+       if(!m->ballocd){
+               m->body = m->start + b0;
+               m->bend = m->start + l0;
+       }
+       m->mheader = m->start + m0;
+       m->mhend = m->start + me;
+}
+
+/*
+ * the way we squeeze out bad characters is exceptionally sneaky.
+ */
+static int
+fetch(Mailbox *mb, Message *m, uvlong o, ulong l)
+{
+       int expand;
+       long l0, n, sz0;
+
+top:
+       l0 = m->end - m->start;
+       assert(l0 >= 0);
+       dprint("fetch %lud sz %lud o %llud l %lud badchars %d\n", l0, m->size, o, l, m->badchars);
+       assert(m->badchars < Maxmsg/10);
+       if(l0 == m->size || o > m->size)
+               return 0;
+       expand = 0;
+       if(o + l > m->size)
+               l = m->size - o;
+       if(o + l == m->size)
+               l += m->ibadchars - m->badchars;
+       if(o + l > l0){
+               expand = 1;
+               msgrealloc(m, o + m->badchars + l);
+       }
+       assert(l0 <= o);
+       sz0 = m->size;
+       if(mb->fetch(mb, m, o + m->badchars, l) == -1){
+               logmsg(m, "can't fetch %D %llud %lud", m->fileid, o, l);
+               m->deleted = Dead;
+               return -1;
+       }
+       if(m->size - sz0)
+               l += m->size - sz0;     /* awful botch for gmail */
+       if(expand){
+               /* grumble.  poor planning. */
+               if(m->badchars > 0)
+                       memmove(m->start + o, m->start + o + m->badchars, l);
+               n = squeeze(m, o, l, 0);
+               n += squeeze(m, o, l - n, '\r');
+               if(n > 0){
+                       if(m->ibadchars == 0)
+                               dprint("   %ld more badchars\n", n);
+                       l -= n;
+                       m->badchars += n;
+                       msgrealloc(m, o + l);
+               }
+               notecache(mb, m, l);
+               m->bend = m->rbend = m->end = m->start + o + l;
+               if(n)
+               if(o + l + n == m->size && m->cstate&Cidx){
+                       dprint("   redux %llud %ld\n", o + l, n);
+                       o += l;
+                       l = n;
+                       goto top;
+               }
+       }else
+               eprint("unhandled case in fetch\n");
+       *m->end = 0;
+       return 0;
+}
+
+void
+cachehash(Mailbox *mb, Message *m)
+{
+//     fprint(2, "cachehash %P\n", mpair(mb, m));
+       if(m->whole == m->whole->whole)
+               henter(PATH(mb->id, Qmbox), m->name,
+                       (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+       else
+               henter(PATH(m->whole->id, Qdir), m->name,
+                       (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
+       henter(PATH(m->id, Qdir), "xxx",
+               (Qid){PATH(m->id, Qmax), 0, QTFILE}, m, mb);    /* sleezy speedup */
+}
+
+void
+newcachehash(Mailbox *mb, Message *m, int doplumb)
+{
+       if(doplumb)
+               mailplumb(mb, m, 0);
+       else
+               if(insurecache(mb, m) == 0)
+                       msgdecref(mb, m);
+       /* avoid cachehash on error? */
+       cachehash(mb, m);
+}
+
+static char *itab[] = {
+       "idx",
+       "stale",
+       "header",
+       "body"
+};
+
+char*
+cstate(Message *m)
+{
+       char *p, *e;
+       int i, s;
+       static char buf[64];
+
+       s = m->cstate;
+       p = e = buf;
+       e += sizeof buf;
+       for(i = 0; i < 8; i++)
+               if(s & 1<<i)
+               if(i < nelem(itab))
+                       p = seprint(p, e, "%s ", itab[i]);
+       if(p > buf)
+               p--;
+       p[0] = 0;
+       return buf;
+}
+
+
+static int
+middlecache(Mailbox *mb, Message *m)
+{
+       int y;
+
+       y = 0;
+       while(!Topmsg(mb, m)){
+               m = m->whole;
+               if((m->cstate & Cbody) == 0)
+                       y = 1;
+       }
+       if(y == 0)
+               return 0;
+       dprint("middlecache %d [%D] %lud %lud\n", m->id, m->fileid, m->end - m->start, m->size);
+       return cachebody(mb, m);
+}
+
+int
+cacheheaders(Mailbox *mb, Message *m)
+{
+       char *p, *e;
+       int r;
+       ulong o;
+
+       if(!mb->fetch || m->cstate&Cheader)
+               return 0;
+       if(!Topmsg(mb, m))
+               return middlecache(mb, m);
+       dprint("cacheheaders %d %D\n", m->id, m->fileid);
+       if(m->size < 10000)
+               r = fetch(mb, m, 0, m->size);
+       else for(r = 0; (o = m->end - m->start) < m->size; ){
+               if((r = fetch(mb, m, o, 4096)) < 0)
+                       break;
+               p = m->start + o;
+               if(o)
+                       p--;
+               for(e = m->end - 2; p < e; p++){
+                       p = memchr(p, '\n', e - p);
+                       if(p == nil)
+                               break;
+                       if(p[1] == '\n' || (p[1] == '\r' && p[2] == '\n'))
+                               goto found;
+               }
+       }
+       if(r < 0)
+               return -1;
+found:
+       parseheaders(mb, m, mb->addfrom, 0);
+       return 0;
+}
+
+void
+digestmessage(Mailbox *mb, Message *m)
+{
+       assert(m->digest == 0);
+       m->digest = emalloc(SHA1dlen);
+       sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
+       if(mtreeisdup(mb, m)){
+               logmsg(m, "dup detected");
+               m->deleted = Dup;       /* no dups allowed */
+       }else
+               mtreeadd(mb, m);
+       dprint("%d %#A\n", m->id, m->digest);
+}
+
+int
+cachebody(Mailbox *mb, Message *m)
+{
+       ulong o;
+
+       while(!Topmsg(mb, m))
+               m = m->whole;
+       if(!mb->fetch || m->cstate&Cbody)
+               return 0;
+       o = m->end - m->start;
+       dprint("cachebody %d [%D] %lud %lud %s\n", m->id, m->fileid, o, m->size, cstate(m));
+       if(o < m->size)
+       if(fetch(mb, m, o, m->size - o) < 0)
+               return -1;
+       if((m->cstate&Cidx) == 0){
+               assert(m->ibadchars == 0);
+               if(m->badchars > 0)
+                       dprint("reducing size %ld %ld\n", m->size, m->size - m->badchars);
+               m->size -= m->badchars;         /* sneaky */
+               m->ibadchars = m->badchars;
+       }
+       if(m->digest == 0)
+               digestmessage(mb, m);
+       if(m->lines == 0)
+               m->lines = countlines(m);
+       parse(mb, m, mb->addfrom, 0);
+       dprint("  →%s\n", cstate(m));
+       return 0;
+}
+
+int
+cacheidx(Mailbox *mb, Message *m)
+{
+       if(m->cstate & Cidx)
+               return 0;
+       if(cachebody(mb, m) == -1)
+               return -1;
+       m->cstate |= Cidxstale|Cidx;
+       return 0;
+}
+
+static int
+countparts(Message *m)
+{
+       Message *p;
+
+       if(m->nparts == 0)
+               for(p = m->part; p; p = p->next){
+                       countparts(p);
+                       m->nparts++;
+               }
+       return m->nparts;
+}
+
+int
+insurecache(Mailbox *mb, Message *m)
+{
+       if(m->deleted || !m->inmbox)
+               return -1;
+       msgincref(m);
+       cacheidx(mb, m);
+       if((m->cstate & Cidx) == 0){
+               logmsg(m, "%s: can't cache: %s: %r", mb->path, m->name);
+               msgdecref(mb, m);
+               return -1;
+       }
+       if(m->digest == 0)
+               sysfatal("digest?");
+       countparts(m);
+       return 0;
+}
diff --git a/sys/src/cmd/upas/fs/chkidx b/sys/src/cmd/upas/fs/chkidx
new file mode 100755 (executable)
index 0000000..bac9111
Binary files /dev/null and b/sys/src/cmd/upas/fs/chkidx differ
diff --git a/sys/src/cmd/upas/fs/chkidx.c b/sys/src/cmd/upas/fs/chkidx.c
new file mode 100644 (file)
index 0000000..8b0a154
--- /dev/null
@@ -0,0 +1,416 @@
+#include "common.h"
+#include <auth.h>
+#include <libsec.h>
+#include <bin.h>
+#include "dat.h"
+
+#define idprint(...)   if(1) fprint(2, __VA_ARGS__); else {}
+enum{
+       Maxver  = 10,
+};
+static char *magictab[Maxver] = {
+[4]    "idx magic v4\n",
+[7]    "idx magic v7\n",
+};
+static int fieldstab[Maxver] = {
+[4]    19,
+[7]    21,
+};
+
+static char    *magic;
+static int     Idxfields;
+static int     lineno;
+static int     idxver;
+
+int
+newid(void)
+{
+       static int id;
+
+       return ++id;
+}
+
+void*
+emalloc(ulong n)
+{
+       void *p;
+
+       p = mallocz(n, 1);
+       if(!p)
+               sysfatal("malloc %lud: %r", n);
+       setmalloctag(p, getcallerpc(&n));
+       return p;
+}
+       
+static int
+Afmt(Fmt *f)
+{
+       char buf[SHA1dlen*2 + 1];
+       uchar *u, i;
+
+       u = va_arg(f->args, uchar*);
+       if(u == 0 && f->flags & FmtSharp)
+               return fmtstrcpy(f, "-");
+       if(u == 0)
+               return fmtstrcpy(f, "<nildigest>");
+       for(i = 0; i < SHA1dlen; i++)
+               sprint(buf + 2*i, "%2.2ux", u[i]);
+       return fmtstrcpy(f, buf);
+}
+
+static int
+Dfmt(Fmt *f)
+{
+       char buf[32];
+       int seq;
+       uvlong v;
+
+       v = va_arg(f->args, uvlong);
+       seq = v & 0xff;
+       if(seq > 99)
+               seq = 99;
+       snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq);
+       return fmtstrcpy(f, buf);
+}
+
+static Mailbox*
+shellmailbox(char *path)
+{
+       Mailbox *mb;
+
+       mb = malloc(sizeof *mb);
+       if(mb == 0)
+               sysfatal("malloc");
+       memset(mb, 0, sizeof *mb);
+       snprint(mb->path, sizeof mb->path, "%s", path);
+       mb->id = newid();
+       mb->root = newmessage(nil);
+       mb->mtree = mkavltree(mtreecmp);
+       return mb;
+}
+
+void
+shellmailboxfree(Mailbox*)
+{
+}
+
+Message*
+newmessage(Message *parent)
+{
+       static int id;
+       Message *m;
+
+//     msgallocd++;
+
+       m = mallocz(sizeof *m, 1);
+       if(m == 0)
+               sysfatal("malloc");
+       m->disposition = Dnone;
+//     m->type = newrefs("text/plain");
+//     m->charset = newrefs("iso-8859-1");
+       m->cstate = Cidxstale;
+       m->flags = Frecent;
+       m->id = newid();
+       if(parent)
+               snprint(m->name, sizeof m->name, "%d", ++(parent->subname));
+       if(parent == nil)
+               parent = m;
+       m->whole = parent;
+       m->hlen = -1;
+       return m;
+}
+
+void
+unnewmessage(Mailbox *mb, Message *parent, Message *m)
+{
+       assert(parent->subname > 0);
+//     delmessage(mb, m);
+       USED(mb, m);
+       parent->subname -= 1;
+}
+
+
+static int
+validmessage(Mailbox *mb, Message *m, int level)
+{
+       if(level){
+               if(m->digest != 0)
+                       goto lose;
+               if(m->fileid <= 1000000ull<<8)
+               if(m->fileid != 0)
+                       goto lose;
+       }else{
+               if(m->digest == 0)
+                       goto lose;
+               if(m->size == 0)
+                       goto lose;
+               if(m->fileid <= 1000000ull<<8)
+                       goto lose;
+               if(mtreefind(mb, m->digest))
+                       goto lose;
+       }
+       return 1;
+lose:
+       fprint(2, "invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid);
+       return 0;
+}
+
+static char*
+∫(char *x)
+{
+       if(x && *x)
+               return x;
+       return nil;
+}
+
+static char*
+brdstr(Biobuf *b, int c, int eat)
+{
+       char *s;
+
+       s = Brdstr(b, c, eat);
+       if(s)
+               lineno++;
+       return s;
+}
+
+static int
+nibble(int c)
+{
+       if(c >= '0' && c <= '9')
+               return c - '0';
+       if(c < 0x20)
+               c += 0x20;
+       if(c >= 'a' && c <= 'f')
+               return c - 'a'+10;
+       return 0xff;
+}
+
+static uchar*
+hackdigest(char *s)
+{
+       uchar t[SHA1dlen];
+       int i;
+
+       if(strcmp(s, "-") == 0)
+               return 0;
+       if(strlen(s) != 2*SHA1dlen){
+               fprint(2, "bad digest %s\n", s);
+               return 0;
+       }
+       for(i = 0; i < SHA1dlen; i++)
+               t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]);
+       memmove(s, t, SHA1dlen);
+       return (uchar*)s;
+}
+
+static Message*
+findmessage(Mailbox *, Message *parent, int n)
+{
+       Message *m;
+
+       for(m = parent->part; m; m = m->next)
+               if(!m->digest && n-- == 0)
+                       return m;
+       return 0;
+}
+
+static uvlong
+rdfileid(char *s, int level)
+{
+       char *p;
+       uvlong uv;
+
+       uv = strtoul(s, &p, 0);
+       if((level == 0 && uv < 1000000) || *p != '.')
+               return 0;
+       return uv<<8 | strtoul(p + 1, 0, 10);
+}
+
+static int
+rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level)
+{
+       char *f[50 + 1], *s;
+       uchar *digest;
+       int n, nparts, good, bad, redux;
+       Message *m, **ll, *l;
+
+       bad = good = redux = 0;
+       ll = &parent->part;
+       nparts = npart;
+       for(; npart != 0 && (s = brdstr(b, '\n', 1)); npart--){
+//if(lineno>18&&lineno<25)idprint("%d: %d [%s]\n", lineno, level, s);
+               n = tokenize(s, f, nelem(f));
+               if(n != Idxfields){
+                       print("%d: bad line\n", lineno);
+                       bad++;
+                       free(s);
+                       continue;
+               }
+               digest = hackdigest(f[0]);
+               if(level == 0){
+                       if(digest == 0)
+                               idprint("%d: no digest\n", lineno);
+                       m = mtreefind(mb, digest);
+               }else{
+                       m = findmessage(mb, parent, nparts - npart);
+                       if(m == 0){
+                       //      idprint("can't find message\n");
+                       }
+               }
+               if(m){
+                       /*
+                        * read in mutable information.
+                        * currently this is only flags
+                        */
+                       idprint("%d seen before %d... %.2ux", level, m->id, m->cstate);
+                       redux++;
+                       m->flags |= strtoul(f[1], 0, 16);
+                       m->cstate &= ~Cidxstale;
+                       m->cstate |= Cidx;
+                       idprint("→%.2ux\n", m->cstate);
+                       free(s);
+
+                       if(m->nparts)
+                               rdidx(b, mb, m, m->nparts, level + 1);
+                       ll = &m->next;
+                       continue;
+               }
+               m = newmessage(parent);
+//if(lineno>18&&lineno<25)idprint("%d: %d %d %A\n", lineno, level, m->id, digest);
+//             idprint("%d new %d %#A \n", level, m->id, digest);
+               m->digest = digest;
+               m->flags = strtoul(f[1], 0, 16);
+               m->fileid = rdfileid(f[2], level);
+               m->lines = atoi(f[3]);
+               m->ffrom = ∫(f[4]);
+               m->from = ∫(f[5]);
+               m->to = ∫(f[6]);
+               m->cc = ∫(f[7]);
+               m->bcc = ∫(f[8]);
+               m->replyto = ∫(f[9]);
+               m->messageid = ∫(f[10]);
+               m->subject = ∫(f[11]);
+               m->sender = ∫(f[12]);
+               m->inreplyto = ∫(f[13]);
+//             m->type = newrefs(f[14]);
+               m->disposition = atoi(f[15]);
+               m->size = strtoul(f[16], 0, 0);
+               m->rawbsize = strtoul(f[17], 0, 0);
+               switch(idxver){
+               case 4:
+                       m->nparts = strtoul(f[18], 0, 0);
+               case 7:
+                       m->ibadchars = strtoul(f[18], 0, 0);
+                       m->idxaux = ∫(f[19]);
+                       m->nparts = strtoul(f[20], 0, 0);
+               }
+               m->cstate &= ~Cidxstale;
+               m->cstate |= Cidx;
+               m->str = s;
+//             free(s);
+               if(!validmessage(mb, m, level)){
+                       /*
+                        *  if this was an okay message, and somebody
+                        * wrote garbage to the index file, we lose the msg.
+                        */
+                       print("%d: !validmessage\n", lineno);
+                       bad++;
+                       unnewmessage(mb, parent, m);
+                       continue;
+               }
+               if(level == 0)
+                       m->inmbox = 1;
+//             cachehash(mb, m);               /* hokey */
+               l = *ll;
+               *ll = m;
+               ll = &m->next;
+               *ll = l;
+               good++;
+
+               if(m->nparts){
+//                     fprint(2, "%d: %d parts [%s]\n", lineno, m->nparts, f[18]);
+                       rdidx(b, mb, m, m->nparts, level + 1);
+               }
+       }
+       if(level == 0)
+               print("idx: %d %d %d\n", good, bad, redux);
+       return 0;
+}
+
+static int
+verscmp(Biobuf *b)
+{
+       char *s;
+       int i;
+
+       if((s = brdstr(b, '\n', 0)) == 0)
+               return -1;
+       for(i = 0; i < Maxver; i++)
+               if(magictab[i])
+               if(strcmp(s, magictab[i]) == 0)
+                       break;
+       free(s);
+       if(i == Maxver)
+               return -1;
+       idxver = i;
+       magic = magictab[i];
+       Idxfields = fieldstab[i];
+       fprint(2, "version %d\n", i);
+       return 0;
+}
+
+int
+mbvers(Biobuf *b)
+{
+       char *s;
+
+       if(s = brdstr(b, '\n', 1)){
+               free(s);
+               return 0;
+       }
+       return -1;
+}
+
+int
+ckidxfile(Mailbox *mb)
+{
+       char buf[Pathlen + 4];
+       int r;
+       Biobuf *b;
+
+       snprint(buf, sizeof buf, "%s", mb->path);
+       b = Bopen(buf, OREAD);
+       if(b == nil)
+               return -1;
+       if(verscmp(b) == -1)
+               return -1;
+       if(idxver >= 7)
+               mbvers(b);
+       r = rdidx(b, mb, mb->root, -1, 0);
+       Bterm(b);
+       return r;
+}
+
+static char *bargv[] = {"/fd/0", 0};
+
+void
+main(int argc, char **argv)
+{
+       Mailbox *mb;
+
+       fmtinstall('A', Afmt);
+       fmtinstall('D', Dfmt);
+       ARGBEGIN{
+       }ARGEND
+       if(*argv == 0)
+               argv = bargv;
+       for(; *argv; argv++){
+               mb = shellmailbox(*argv);
+               lineno = 0;
+               if(ckidxfile(mb) == -1)
+                       fprint(2, "%s: %r\n", *argv);
+               shellmailboxfree(mb);
+       }
+       exits("");
+}
index d21de3ed5eb102b2337963341f8b1ac238b27999..585af992a779a5c1e1dd1ee6dc387edbbc931ac9 100644 (file)
-typedef struct Message Message;
-struct Message
-{
-       int     id;
-       int     refs;
-       int     subname;
-       char    name[Elemlen];
+#include <avl.h>
 
-       // pointers into message
-       char    *start;         // start of message
-       char    *end;           // end of message
-       char    *header;        // start of header
-       char    *hend;          // end of header
-       int     hlen;           // length of header minus ignored fields
-       char    *mheader;       // start of mime header
-       char    *mhend;         // end of mime header
-       char    *body;          // start of body
-       char    *bend;          // end of body
-       char    *rbody;         // raw (unprocessed) body
-       char    *rbend;         // end of raw (unprocessed) body
-       char    *lim;
-       char    deleted;
-       char    inmbox;
-       char    mallocd;        // message is malloc'd
-       char    ballocd;        // body is malloc'd
-       char    hallocd;        // header is malloce'd
-
-       // mail info
-       String  *unixheader;
-       String  *unixfrom;
-       String  *unixdate;
-       String  *from822;
-       String  *sender822;
-       String  *to822;
-       String  *bcc822;
-       String  *cc822;
-       String  *replyto822;
-       String  *date822;
-       String  *inreplyto822;
-       String  *subject822;
-       String  *messageid822;
-       String  *addrs;
-       String  *mimeversion;
-       String  *sdigest;
-
-       // mime info
-       String  *boundary;
-       String  *type;
-       int     encoding;
-       int     disposition;
-       String  *charset;
-       String  *filename;
-       int     converted;
-       int     decoded;
-       char    lines[10];      // number of lines in rawbody
-
-       Message *next;          // same level
-       Message *part;          // down a level
-       Message *whole;         // up a level
-
-       uchar   digest[SHA1dlen];
-
-       vlong   imapuid;        // used by imap4
-
-       char            uidl[80];       // used by pop3
-       int             mesgno;
-};
+enum {
+       /* cache states */
+       Cidx            = 1<<0,
+       Cidxstale       = 1<<1,
+       Cheader         = 1<<2,
+       Cbody           = 1<<3,
 
-enum
-{
-       // encodings
+       /* encodings */
        Enone=  0,
        Ebase64,
        Equoted,
 
-       // disposition possibilities
+       /* dispositions */
        Dnone=  0,
        Dinline,
        Dfile,
        Dignore,
 
-       PAD64=  '=',
+       /* mb create flags */
+       DMcreate        =  0x02000000,
+
+       /* rm flags */
+       Rrecur          = 1<<0,
+       Rtrunc          = 1<<1,
+
+       /* m->deleted flags */
+       Deleted         = 1<<0,
+       Dup             = 1<<1,
+       Dead            = 1<<2,
+       Disappear       = 1<<3,
+       Dmark           = 1<<4, /* temporary mark for idx scan */
+
+       /* mime flags */
+       Mtrunc          = 1<<0, /* message had no boundary */
+
+       Maxmsg          = 75*1024*1024, /* maxmessage size; debugging */
+       Maxcache        = 512*1024,     /* target cache size; set low for debugging */
+       Nctab           = 15,           /* max # of cached messages >10 */
+       Nref            = 10,
+};
+
+typedef struct Idx Idx;
+struct Idx {
+       char    *str;                   /* as read from idx file */
+       uchar   *digest;
+       uchar   flags;
+       uvlong  fileid;
+       ulong   lines;
+       ulong   size;
+       ulong   rawbsize;                       /* nasty imap4d */
+       ulong   ibadchars;
+
+       char    *ffrom;
+       char    *from;
+       char    *to;
+       char    *cc;
+       char    *bcc;
+       char    *replyto;
+       char    *messageid;
+       char    *subject;
+       char    *sender;
+       char    *inreplyto;
+       char    *idxaux;                        /* mailbox specific */
+
+       int     type;                   /* very few types: refstring */
+       int     disposition;            /* very few types: refstring */
+       int     nparts;
+};
+
+typedef struct Message Message;
+struct Message {
+       int     id;
+       int     refs;
+       int     subname;
+       char    name[12];
+
+       /* top-level indexed information */
+       Idx;
+
+       /* caching help */
+       uchar   cstate;
+       ulong   infolen;
+       ulong   csize;
+
+       /*
+        * a plethoria of pointers into message
+        * and some status.  not valid unless cached
+        */
+       char    *start;         /* start of message */
+       char    *end;           /* end of message */
+       char    *header;                /* start of header */
+       char    *hend;          /* end of header */
+       int     hlen;           /* length of header minus ignored fields */
+       char    *mheader;       /* start of mime header */
+       char    *mhend;         /* end of mime header */
+       char    *body;          /* start of body */
+       char    *bend;          /* end of body */
+       char    *rbody;         /* raw (unprocessed) body */
+       char    *rbend;         /* end of raw (unprocessed) body */
+       char    mallocd;                /* message is malloc'd */
+       char    ballocd;                /* body is malloc'd */
+       char    hallocd;                /* header is malloc'd */
+       int     badchars;       /* running count of bad chars. */
+
+       char    deleted;
+       char    inmbox;
+
+       /* mail info */
+       char    *unixheader;
+       char    *unixfrom;
+       char    *date822;
+       char    *references[Nref];
+
+       /* mime info */
+       char    *boundary;
+       int     charset;
+       char    *filename;
+       char    encoding;
+       char    converted;
+       char    decoded;
+       char    mimeflag;
+
+       Message *next;
+       Message *part;
+       Message *whole;
+
+       union{
+               char    *lim;   /* used by plan9; not compatable with cache */
+               vlong   imapuid;        /* used by imap4 */
+               void    *aux;
+       };
+};
+
+typedef struct {
+       Avl;
+       Message *m;
+} Mtree;
+
+typedef struct Mcache Mcache;
+struct Mcache {
+       uvlong  cached;
+       int     ntab;
+       Message *ctab[Nctab];
 };
 
 typedef struct Mailbox Mailbox;
-struct Mailbox
-{
+struct Mailbox {
        QLock;
+       long    idxsem;         /* abort on concurrent index access */
+       long    syncsem;                /* abort on concurrent syncs */
        int     refs;
        Mailbox *next;
        int     id;
-       int     dolock;         // lock when syncing?
-       int     std;
+       int     flags;
+       char    rmflags;
+       char    dolock;         /* lock when syncing? */
+       char    addfrom;
        char    name[Elemlen];
        char    path[Pathlen];
        Dir     *d;
        Message *root;
-       int     vers;           // goes up each time mailbox is read
+       Avltree *mtree;
+       ulong   vers;           /* goes up each time mailbox is changed */
 
-       ulong waketime;
-       char    *(*sync)(Mailbox*, int);
+       /* cache tracking */
+       Mcache;
+
+       /* index tracking */
+       Qid     qid;
+
+       ulong   waketime;
        void    (*close)(Mailbox*);
-       char    *(*fetch)(Mailbox*, Message*);
+       void    (*decache)(Mailbox*, Message*);
+       int     (*fetch)(Mailbox*, Message*, uvlong, ulong);
+       void    (*delete)(Mailbox*, Message*);
        char    *(*ctl)(Mailbox*, int, char**);
-       void    *aux;           // private to Mailbox implementation
+       char    *(*remove)(Mailbox *, int);
+       char    *(*rename)(Mailbox*, char*, int);
+       char    *(*sync)(Mailbox*, int, int*);
+       void    (*modflags)(Mailbox*, Message*, int);
+       void    (*idxwrite)(Biobuf*, Mailbox*);
+       int     (*idxread)(char*, Mailbox*);
+       void    (*idxinvalid)(Mailbox*);
+       void    *aux;           /* private to Mailbox implementation */
 };
 
+/* print argument tango; can't varargck 2 types.  should fix compiler */
+typedef struct Mpair Mpair;
+struct Mpair {
+       Mailbox *mb;
+       Message *m;
+};
+Mpair mpair(Mailbox*, Message*);
+
 typedef char *Mailboxinit(Mailbox*, char*);
 
-extern Message *root;
-extern Mailboxinit     plan9mbox;
-extern Mailboxinit     pop3mbox;
-extern Mailboxinit     imap4mbox;
-extern Mailboxinit     planbmbox;
-extern Mailboxinit     planbvmbox;
+Mailboxinit    plan9mbox;
+Mailboxinit    planbmbox;
+Mailboxinit    pop3mbox;
+Mailboxinit    imap4mbox;
+Mailboxinit    mdirmbox;
+
+void           genericidxwrite(Biobuf*, Mailbox*);
+int            genericidxread(char*, Mailbox*);
+void           genericidxinvalid(Mailbox*);
+
+void           cachehash(Mailbox*, Message*);
+void           newcachehash(Mailbox*, Message*, int);
+int            cacheheaders(Mailbox*, Message*);               /* "getcache" */
+int            cachebody(Mailbox*, Message*);
+int            cacheidx(Mailbox*, Message*);
+int            insurecache(Mailbox*, Message*);
 
+/**/
+void           putcache(Mailbox*, Message*);           /* asymmetricial */
+long           cachefree(Mailbox*, Message*, int);
+
+Message*       gettopmsg(Mailbox*, Message*);
 char*          syncmbox(Mailbox*, int);
-char*          geterrstr(void);
 void*          emalloc(ulong);
 void*          erealloc(void*, ulong);
 Message*       newmessage(Message*);
+void           unnewmessage(Mailbox*, Message*, Message*);
 void           delmessage(Mailbox*, Message*);
-void           delmessages(int, char**);
+char*          delmessages(int, char**);
+char           *flagmessages(int, char**);
+void           digestmessage(Mailbox*, Message*);
+
+uintptr                absbos(void);
+void           eprint(char*, ...);
+void           iprint(char *, ...);
 int            newid(void);
 void           mailplumb(Mailbox*, Message*, int);
-char*          newmbox(char*, char*, int);
+char*          newmbox(char*, char*, int, Mailbox**);
 void           freembox(char*);
-void           logmsg(char*, Message*);
+char*          removembox(char*, int);
+void           syncallmboxes(void);
+void           logmsg(Message*, char*, ...);
 void           msgincref(Message*);
 void           msgdecref(Mailbox*, Message*);
 void           mboxincref(Mailbox*);
 void           mboxdecref(Mailbox*);
+char           *mboxrename(char*, char*, int);
 void           convert(Message*);
 void           decode(Message*);
-int            cistrncmp(char*, char*, int);
-int            cistrcmp(char*, char*);
 int            decquoted(char*, char*, char*, int);
 int            xtoutf(char*, char**, char*, char*);
-void           countlines(Message*);
-int            headerlen(Message*);
-void           parse(Message*, int, Mailbox*, int);
-void           parseheaders(Message*, int, Mailbox*, int);
+ulong          countlines(Message*);
+void           parse(Mailbox*, Message*, int, int);
+void           parseheaders(Mailbox*, Message*, int, int);
 void           parsebody(Message*, Mailbox*);
-void           parseunix(Message*);
-String*        date822tounix(char*);
+char*          date822tounix(Message*, char*);
 int            fidmboxrefs(Mailbox*);
 int            hashmboxrefs(Mailbox*);
+void           checkmboxrefs(void);
+int            strtotm(char*, Tm*);
+char*          lowercase(char*);
 
-extern int     debug;
-extern int     fflag;
-extern int     logging;
-extern char    user[Elemlen];
-extern char    stdmbox[Pathlen];
-extern QLock   mbllock;
-extern Mailbox *mbl;
-extern char    *mntpt;
-extern int     biffing;
-extern int     plumbing;
-extern char*   Enotme;
+char*          sputc(char*, char*, int);
+char*          seappend(char*, char*, char*, int);
 
-enum
-{
-       /* mail subobjects */
-       Qbody,
+int            hdrlen(char*, char*);
+char*          rfc2047(char*, char*, char*, int, int);
+
+char*          localremove(Mailbox*, int);
+char*          localrename(Mailbox*, char*, int);
+void           rmidx(char*, int);
+int            vremove(char*);
+int            rename(char *, char*, int);
+
+int            mtreecmp(Avl*, Avl*);
+int            mtreeisdup(Mailbox *, Message *);
+Message*       mtreefind(Mailbox*, uchar*);
+void           mtreeadd(Mailbox*, Message*);
+void           mtreedelete(Mailbox*, Message*);
+
+enum {
+       /* mail sub-objects; must be sorted */
        Qbcc,
+       Qbody,
        Qcc,
        Qdate,
        Qdigest,
        Qdisposition,
+       Qffrom,
+       Qfileid,
        Qfilename,
+       Qflags,
        Qfrom,
        Qheader,
+       Qinfo,
        Qinreplyto,
        Qlines,
-       Qmimeheader,
        Qmessageid,
+       Qmimeheader,
        Qraw,
        Qrawbody,
        Qrawheader,
        Qrawunix,
+       Qreferences,
        Qreplyto,
        Qsender,
+       Qsize,
        Qsubject,
        Qto,
        Qtype,
-       Qunixheader,
-       Qinfo,
        Qunixdate,
+       Qunixheader,
        Qmax,
 
        /* other files */
@@ -196,12 +318,10 @@ enum
        Qmboxctl,
 };
 
-#define PATH(id, f)    ((((id)&0xfffff)<<10) | (f))
+#define PATH(id, f)    ((((id) & 0xfffff)<<10) | (f))
 #define FILE(p)                ((p) & 0x3ff)
 
-char *dirtab[];
-
-// hash table to aid in name lookup, all files have an entry
+/* hash table to aid in name lookup, all files have an entry */
 typedef struct Hash Hash;
 struct Hash {
        Hash    *next;
@@ -216,5 +336,50 @@ Hash       *hlook(ulong, char*);
 void   henter(ulong, char*, Qid, Message*, Mailbox*);
 void   hfree(ulong, char*);
 
-ulong msgallocd, msgfreed;
+typedef struct {
+       char    *s;
+       int     l;
+       ulong   ref;
+} Refs;
+
+int    newrefs(char*);
+void   delrefs(int);
+void   refsinit(void);
+int    prrefs(Biobuf*);
+int    rdrefs(Biobuf*);
+
+void   idxfree(Idx*);
+int    rdidxfile(Mailbox*, int);
+int    wridxfile(Mailbox*);
+
+char   *modflags(Mailbox*, Message*, char*);
+int    getmtokens(char *, char**, int, int);
+
+extern char    Enotme[];
+extern char    *mntpt;
+extern char    user[Elemlen];
+extern char    *dirtab[];
+extern int     Sflag;
+extern int     iflag;
+extern int     biffing;
+extern ulong   cachetarg;
+extern int     debug;
+extern int     lflag;
+extern int     plumbing;
+extern ulong   msgallocd;
+extern ulong   msgfreed;
+extern Mailbox *mbl;
+extern Message *root;
+extern QLock   mbllock;
+extern Refs    *rtab;
+
+#define        dprint(...)     if(debug) fprint(2, __VA_ARGS__); else {}
+#define        Topmsg(mb, m)   (m->whole == mb->root)
+#pragma        varargck        type    "A"     uchar*
+#pragma        varargck        type    "D"     uvlong
+#pragma        varargck        type    "P"     Mpair
+#pragma        varargck        type    "Δ"    uvlong
+#pragma        varargck        argpos  eprint  1
+#pragma        varargck        argpos  iprint  1
+#pragma        varargck        argpos  logmsg  2
 
diff --git a/sys/src/cmd/upas/fs/extra/fd2path b/sys/src/cmd/upas/fs/extra/fd2path
new file mode 100755 (executable)
index 0000000..d738150
Binary files /dev/null and b/sys/src/cmd/upas/fs/extra/fd2path differ
diff --git a/sys/src/cmd/upas/fs/extra/fd2path.c b/sys/src/cmd/upas/fs/extra/fd2path.c
new file mode 100644 (file)
index 0000000..5fe3ce7
--- /dev/null
@@ -0,0 +1,32 @@
+#include <u.h>
+#include <libc.h>
+
+void
+usage(void)
+{
+       fprint(2, "usage: fd2path path ...\n");
+       exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+       char buf[1024];
+       int fd;
+
+       ARGBEGIN{
+       default:
+               usage();
+       }ARGEND
+
+       if(argc == 0){
+               if(fd2path(0, buf, sizeof buf) != -1)
+                       fprint(2, "%s\n", buf);
+       }else for(; *argv; argv++){
+               fd = open(*argv, OREAD);
+               if(fd != -1 && fd2path(fd, buf, sizeof buf) != -1)
+                       fprint(2, "%s\n", buf);
+               close(fd);
+       }
+       exits("");
+}
diff --git a/sys/src/cmd/upas/fs/extra/idxtst.c b/sys/src/cmd/upas/fs/extra/idxtst.c
new file mode 100644 (file)
index 0000000..919d254
--- /dev/null
@@ -0,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+static char *etab[] = {
+       "not found",
+       "does not exist",
+       "file is locked",
+       "exclusive lock",
+};
+
+static int
+bad(int idx)
+{
+       char buf[ERRMAX];
+       int i;
+
+       rerrstr(buf, sizeof buf);
+       for(i = idx; i < nelem(etab); i++)
+               if(strstr(buf, etab[i]))
+                       return 0;
+       return 1;
+}
+
+static int
+exopen(char *s)
+{
+       int i, fd;
+
+       for(i = 0; i < 30; i++){
+               if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(0))
+                       return fd;
+               if((fd = create(s, OWRITE|OEXCL, DMEXCL|0600)) >= 0  || bad(2))
+                       return fd;
+               sleep(1000);
+       }
+       werrstr("lock timeout");
+       return -1;
+}
+
+void
+main(void)
+{
+       int fd;
+       Biobuf *b;
+
+       fd = exopen("testingex");
+       if(fd == -1)
+               sysfatal("exopen: %r");
+       b = Bopen("testingex", OREAD);
+       if(b){
+               free(b);
+               fprint(2, "print both opened at once\n");
+       }else
+               fprint(2, "bopen: %r\n");
+       close(fd);
+       exits("");
+}
diff --git a/sys/src/cmd/upas/fs/extra/infotst.c b/sys/src/cmd/upas/fs/extra/infotst.c
new file mode 100644 (file)
index 0000000..de7537a
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * simulate the read patterns of external programs for testing
+ * info file. "infotest 511 512" simulates what ned does today.
+ *
+ * here's how the new info scheme was verified:
+ *
+       ramfs
+       s=/sys/src/cmd/upas
+       unmount /mail/fs
+       $s/fs/8.out -p
+       for(f in /mail/fs/mbox/*/info){
+               for(i in `{seq 1 1026})
+                       $s/fs/infotst $i `{echo $i + 1 | hoc} > /tmp/$i < $f
+               for(i in /tmp/*)
+                       cmp $i /tmp/1
+               rm /tmp/*
+       }
+
+       # now test for differences with old scheme under
+       # ideal reading conditions
+       for(f in /mail/fs/mbox/*/info){
+               i = `{echo $f | sed 's:/mail/fs/mbox/([^/]+)/info:\1:g'}
+               $s/fs/infotst 2048 > /tmp/$i < $f
+       }
+       unmount /mail/fs
+       upas/fs -p
+       for(f in /mail/fs/mbox/*/info){
+               i = `{echo $f | sed 's:/mail/fs/mbox/([^/]+)/info:\1:g'}
+               $s/fs/infotst 2048 > /tmp/$i.o < $f
+       }
+       for(i in /tmp/*.o)
+               cmp $i `{echo $i | sed 's:\.o$::g'}
+       rm /tmp/*
+ */
+#include <u.h>
+#include <libc.h>
+
+enum{
+       Ntab    = 100,
+};
+
+int    tab[Ntab];
+int    ntab;
+int    largest;
+
+void
+usage(void)
+{
+       fprint(2, "usage: infotest n1 n2 ... nm\n");
+       exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+       char *buf;
+       int i, n;
+
+       ARGBEGIN{
+       default:
+               usage();
+       }ARGEND
+       if(argc == 0)
+               usage();
+       for(; *argv; argv++){
+               if(ntab == nelem(tab))
+                       break;
+               i = atoi(*argv);
+               if(i > largest)
+                       largest = i;
+               tab[ntab++] = i;
+       }
+       buf = malloc(largest);
+       if(!buf)
+               sysfatal("malloc: %r");
+       for(i = 0;; ){
+               switch(n = read(0, buf, tab[i])){
+               case -1:
+                       sysfatal("read: %r");
+               case 0:
+                       exits("");
+               default:
+                       write(1, buf, n);
+                       break;
+               }
+               if(i < ntab-1)
+                       i++;
+       }
+}
diff --git a/sys/src/cmd/upas/fs/extra/paw b/sys/src/cmd/upas/fs/extra/paw
new file mode 100755 (executable)
index 0000000..538f648
Binary files /dev/null and b/sys/src/cmd/upas/fs/extra/paw differ
diff --git a/sys/src/cmd/upas/fs/extra/paw.c b/sys/src/cmd/upas/fs/extra/paw.c
new file mode 100644 (file)
index 0000000..6d3a5e4
--- /dev/null
@@ -0,0 +1,23 @@
+#include<u.h>
+#include<libc.h>
+#include<bio.h>
+
+void
+main(void)
+{
+       char *f[10], *s;
+       vlong sum;
+       Biobuf b;
+
+       sum = 0;
+       Binit(&b, 0, OREAD);
+
+       while(s = Brdstr(&b, '\n', 1)){
+               if(getfields(s, f, nelem(f), 1, " ") > 2)
+                       sum += strtoul(f[2], 0, 0);
+               free(s);
+       }
+       Bterm(&b);
+       print("%lld\n", sum);
+       exits("");
+}
diff --git a/sys/src/cmd/upas/fs/extra/prflags.c b/sys/src/cmd/upas/fs/extra/prflags.c
new file mode 100644 (file)
index 0000000..6a42045
--- /dev/null
@@ -0,0 +1,37 @@
+#include "common.h"
+
+void
+usage(void)
+{
+       fprint(2, "usage: prflags\n");
+       exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+       char *f[Fields+1], buf[20], *s;
+       int n;
+       Biobuf b, o;
+
+       ARGBEGIN{
+       default:
+               usage();
+       }ARGEND
+       if(argc)
+               usage();
+       Binit(&b, 0, OREAD);
+       Binit(&o, 1, OWRITE);
+
+       for(; s = Brdstr(&b, '\n', 1); free(s)){
+               n = gettokens(s, f, nelem(f), " ");
+               if(n != Fields)
+                       continue;
+               if(!strcmp(f[0], "-"))
+                       continue;
+               Bprint(&o, "%s\n", flagbuf(buf, strtoul(f[1], 0, 16)));
+       }
+       Bterm(&b);
+       Bterm(&o);
+       exits("");
+}
diff --git a/sys/src/cmd/upas/fs/extra/strtotmtst.c b/sys/src/cmd/upas/fs/extra/strtotmtst.c
new file mode 100644 (file)
index 0000000..074e10e
--- /dev/null
@@ -0,0 +1,17 @@
+#include "strtotm.c"
+
+void
+main(int argc, char **argv)
+{
+       Tm tm;
+
+       ARGBEGIN{
+       }ARGEND
+
+       for(; *argv; argv++)
+               if(strtotm(*argv, &tm) >= 0)
+                       print("%s", asctime(&tm));
+               else
+                       print("bad\n");
+       exits("");
+}
diff --git a/sys/src/cmd/upas/fs/extra/tokens.c b/sys/src/cmd/upas/fs/extra/tokens.c
new file mode 100644 (file)
index 0000000..6192f2b
--- /dev/null
@@ -0,0 +1,62 @@
+#include <u.h>
+#include <libc.h>
+
+/* unfortunately, tokenize insists on collapsing multiple seperators */
+static char qsep[] = " \t\r\n";
+
+static char*
+qtoken(char *s, char *sep)
+{
+       int quoting;
+       char *t;
+
+       quoting = 0;
+       t = s;  /* s is output string, t is input string */
+       while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
+               if(*t != '\''){
+                       *s++ = *t++;
+                       continue;
+               }
+               /* *t is a quote */
+               if(!quoting){
+                       quoting = 1;
+                       t++;
+                       continue;
+               }
+               /* quoting and we're on a quote */
+               if(t[1] != '\''){
+                       /* end of quoted section; absorb closing quote */
+                       t++;
+                       quoting = 0;
+                       continue;
+               }
+               /* doubled quote; fold one quote into two */
+               t++;
+               *s++ = *t++;
+       }
+       if(*s != '\0'){
+               *s = '\0';
+               if(t == s)
+                       t++;
+       }
+       return t;
+}
+
+int
+getmtokens(char *s, char **args, int maxargs, int multiflag)
+{
+       int i;
+
+       for(i = 0; i < maxargs; i++){
+               if(multiflag)
+                       while(*s && utfrune(qsep, *s))
+                               s++;
+               else if(*s && utfrune(qsep, *s))
+                       s++;
+               if(*s == 0)
+                       break;
+               args[i] = s;
+               s = qtoken(s, qsep);
+       }
+       return i;
+}
index fffb290aa2be797b84a5353ad03960e2fe24172a..acfca88387e18738dc97b9e50aeb05f0602878e1 100644 (file)
@@ -1,17 +1,10 @@
 #include "common.h"
-#include <auth.h>
 #include <fcall.h>
 #include <libsec.h>
-#include <ctype.h>
+#include <pool.h>
 #include "dat.h"
 
-enum
-{
-       OPERM   = 0x3,          // mask of all permission types in open mode
-};
-
 typedef struct Fid Fid;
-
 struct Fid
 {
        Qid     qid;
@@ -21,31 +14,13 @@ struct Fid
        Fid     *next;
        Mailbox *mb;
        Message *m;
-       Message *mtop;          // top level message
+       Message *mtop;          /* top level message */
 
-       //finger pointers to speed up reads of large directories
-       long    foff;   // offset/DIRLEN of finger
-       Message *fptr;  // pointer to message at off
-       int     fvers;  // mailbox version when finger was saved
+       long    foff;           /* offset/DIRLEN of finger */
+       Message *fptr;          /* pointer to message at off */
+       int     fvers;          /* mailbox version when finger was saved */
 };
 
-ulong  path;           // incremented for each new file
-Fid    *fids;
-int    mfd[2];
-char   user[Elemlen];
-int    messagesize = 4*1024+IOHDRSZ;
-uchar  mdata[8*1024+IOHDRSZ];
-uchar  mbuf[8*1024+IOHDRSZ];
-Fcall  thdr;
-Fcall  rhdr;
-int    fflg;
-char   *mntpt;
-int    biffing;
-int    plumbing = 1;
-
-QLock  mbllock;
-Mailbox        *mbl;
-
 Fid            *newfid(int);
 void           error(char*);
 void           io(void);
@@ -54,9 +29,6 @@ void          *emalloc(ulong);
 void           usage(void);
 void           reader(void);
 int            readheader(Message*, char*, int, int);
-int            cistrncmp(char*, char*, int);
-int            tokenconvert(String*, char*, int);
-String*                stringconvert(String*, char*, int);
 void           post(char*, char*, int);
 
 char   *rflush(Fid*), *rauth(Fid*),
@@ -93,30 +65,36 @@ char        Eisopen[] =     "file already open for I/O";
 char   Excl[] =        "exclusive use file already open";
 char   Ename[] =       "illegal name";
 char   Ebadctl[] =     "unknown control message";
+char   Ebadargs[] =    "invalid arguments";
+char   Enotme[] =      "path not served by this file server";
 
-char *dirtab[] =
-{
+char *dirtab[] = {
 [Qdir]         ".",
-[Qbody]                "body",
 [Qbcc]         "bcc",
+[Qbody]                "body",
 [Qcc]          "cc",
 [Qdate]                "date",
 [Qdigest]      "digest",
 [Qdisposition] "disposition",
+[Qffrom]               "ffrom",
+[Qfileid]              "fileid",
 [Qfilename]    "filename",
+[Qflags]               "flags",
 [Qfrom]                "from",
 [Qheader]      "header",
 [Qinfo]                "info",
 [Qinreplyto]   "inreplyto",
-[Qlines]       "lines",
-[Qmimeheader]  "mimeheader",
+[Qlines]               "lines",
 [Qmessageid]   "messageid",
+[Qmimeheader]  "mimeheader",
 [Qraw]         "raw",
-[Qrawunix]     "rawunix",
 [Qrawbody]     "rawbody",
 [Qrawheader]   "rawheader",
+[Qrawunix]     "rawunix",
+[Qreferences]  "references",
 [Qreplyto]     "replyto",
 [Qsender]      "sender",
+[Qsize]                "size",
 [Qsubject]     "subject",
 [Qto]          "to",
 [Qtype]                "type",
@@ -128,61 +106,290 @@ char *dirtab[] =
 
 enum
 {
-       Hsize=  1277,
+       Hsize=  1999,
 };
 
-Hash   *htab[Hsize];
 
+char   *mntpt;
+char   user[Elemlen];
+int    Dflag;
+int    Sflag;
+int    iflag;
+int    lflag;
+int    biffing;
 int    debug;
-int    fflag;
-int    logging;
+int    plumbing = 1;
+ulong  cachetarg = Maxcache;
+Mailbox        *mbl;
+QLock  mbllock;
+
+static int     messagesize = 8*1024 + IOHDRSZ;
+static int     mfd[2];
+static char    hbuf[32*1024];
+static uchar   mbuf[16*1024 + IOHDRSZ];
+static uchar   mdata[16*1024 + IOHDRSZ];
+static ulong   path;           /* incremented for each new file */
+static Hash    *htab[Hsize];
+static Fcall   rhdr;
+static Fcall   thdr;
+static Fid     *fids;
+static uintptr bos = 0xd0000000;               /* pc kernel specific */
+
+#define onstack(x)     ((uintptr)(x) >= bos)
+#define intext(x)              ((char*)(x) <= end)
+#define validgptr(x)   assert(!onstack(x) && !intext(x))
+void
+sanemsg(Message *m)
+{
+       if(bos == 0)
+               bos = absbos();
+       assert(m->refs < 100);
+       validgptr(m->whole);
+       if(debug)
+               poolcheck(mainmem);
+       validgptr(m);
+       assert(m->next != m);
+       if(m->end < m->start)
+               abort();
+       if(m->ballocd && (m->start <= m->body && m->end >= m->body))
+               abort();
+       if(m->end - m->start > Maxmsg)
+               abort();
+       if(m->size > Maxmsg)
+               abort();
+       if(m->fileid != 0 && m->fileid <= 1000000ull<<8)
+               abort();
+}
+
+void
+sanembmsg(Mailbox *mb, Message *m)
+{
+       sanemsg(m);
+       if(Topmsg(mb, m)){
+               if(m->start > end && m->size == 0)
+                       abort();
+               if(m->fileid <= 1000000ull<<8)
+                       abort();
+       }
+}
+
+static void
+sanefid(Fid *f)
+{
+       if(f->m == 0)
+               return;
+       if(f->mtop){
+               sanemsg(f->mtop);
+               assert(f->mtop->refs > 0);
+       }
+       sanemsg(f->m);
+       if(f->m)
+       if(Topmsg(f->mb, f->m))
+               assert(f->m->refs > 0);
+}
+
+void
+sanefids(void)
+{
+       Fid *f;
+
+       for(f = fids; f; f = f->next)
+               if(f->busy)
+                       sanefid(f);
+}
+
+static int
+Afmt(Fmt *f)
+{
+       char buf[SHA1dlen*2 + 1];
+       uchar *u, i;
+
+       u = va_arg(f->args, uchar*);
+       if(u == 0 && f->flags & FmtSharp)
+               return fmtstrcpy(f, "-");
+       if(u == 0)
+               return fmtstrcpy(f, "<nildigest>");
+       for(i = 0; i < SHA1dlen; i++)
+               sprint(buf + 2*i, "%2.2ux", u[i]);
+       return fmtstrcpy(f, buf);
+}
+
+static int
+Δfmt(Fmt *f)
+{
+       char buf[32];
+       uvlong v;
+
+       v = va_arg(f->args, uvlong);
+       if(f->flags & FmtSharp)
+               if((v>>8) == 0)
+                       return fmtstrcpy(f, "");
+       strcpy(buf, ctime(v>>8));
+       buf[28] = 0;
+       return fmtstrcpy(f, buf);
+}
+
+static int
+Dfmt(Fmt *f)
+{
+       char buf[32];
+       int seq;
+       uvlong v;
+
+       v = va_arg(f->args, uvlong);
+       seq = v & 0xff;
+       if(seq > 99)
+               seq = 99;
+       snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq);
+       return fmtstrcpy(f, buf);
+}
+
+Mpair
+mpair(Mailbox *mb, Message *m)
+{
+       Mpair mp;
+
+       mp.mb = mb;
+       mp.m = m;
+       return mp;
+}
+
+static int
+Pfmt(Fmt *f)
+{
+       char buf[128], *p, *e;
+       int i, dots;
+       Mailbox *mb;
+       Message *t[32], *m;
+       Mpair mp;
+
+       mp = va_arg(f->args, Mpair);
+       mb = mp.mb;
+       m = mp.m;
+       if(m == nil || mb == nil)
+               return fmtstrcpy(f, "<P nil>");
+       i = 0;
+       for(; !Topmsg(mb, m); m = m->whole){
+               t[i++] = m;
+               if(i == nelem(t)-1)
+                       break;
+       }
+       t[i++] = m;
+       dots = 0;
+       if(i == nelem(t))
+               dots = 1;
+       e = buf + sizeof buf;
+       p = buf;
+       if(dots)
+               p = seprint(p, e, ".../");
+       while(--i >= 1)
+               p = seprint(p, e, "%s/", t[i]->name);
+       if(i == 0)
+               seprint(p, e, "%s", t[0]->name);
+       return fmtstrcpy(f, buf);
+}
 
 void
 usage(void)
 {
-       fprint(2, "usage: upas/fs [-bdlnps] [-f mboxfile] [-m mountpoint]\n");
+       fprint(2, "usage: upas/fs [-DSbdlmnps] [-c cachetarg] [-f mboxfile] [-m mountpoint]\n");
        exits("usage");
 }
 
 void
 notifyf(void *, char *s)
 {
-       if(strstr(s, "alarm") || strstr(s, "interrupt"))
+       if(strncmp(s, "interrupt", 9) == 0)
                noted(NCONT);
+       if(strncmp(s, "die: yankee pig dog", 19) != 0)
+               /* don't want to call syslog from notify handler */
+               fprint(2, "upas/fs: user: %s; note: %s\n", getuser(), s);
        noted(NDFLT);
 }
 
+void
+setname(char **v)
+{
+       char buf[128], buf2[32], *p, *e;
+       int fd, i;
+
+       e = buf + sizeof buf;
+       p = seprint(buf, e, "%s", v[0]);
+       for(i = 0; v[++i]; )
+               p = seprint(p, e, " %s", v[i]);
+       snprint(buf2, sizeof buf2, "#p/%d/args", getpid());
+       if((fd = open(buf2, OWRITE)) >= 0){
+               write(fd, buf, p - buf);
+               close(fd);
+       }
+}
+
+ulong
+ntoi(char *s)
+{
+       ulong n;
+
+       n = strtoul(s, &s, 0);
+       for(;;)
+       switch(*s++){
+       default:
+               usage();
+       case 'g':
+               n *= 1024;
+       case 'm':
+               n *= 1024;
+       case 'k':
+               n *= 1024;
+               break;
+       case 0:
+               return n;
+       }
+}
+
 void
 main(int argc, char *argv[])
 {
-       int p[2], std, nodflt;
-       char maildir[128];
-       char mbox[128];
+       char maildir[Pathlen], mbox[Pathlen], srvfile[64], **v;
        char *mboxfile, *err;
-       char srvfile[64];
-       int srvpost;
+       int p[2], nodflt, srvpost;
 
        rfork(RFNOTEG);
-       mntpt = nil;
-       fflag = 0;
        mboxfile = nil;
-       std = 0;
        nodflt = 0;
        srvpost = 0;
+       v = argv;
 
        ARGBEGIN{
+       case 'D':
+               Dflag = 1;
+               break;
+       case 'S':
+               Sflag = 1;
+               break;
        case 'b':
                biffing = 1;
                break;
+       case 'c':
+               cachetarg = ntoi(EARGF(usage()));
+               break;
+       case 'd':
+               debug = 1;
+               mainmem->flags |= POOL_PARANOIA;
+               break;
        case 'f':
-               fflag = 1;
                mboxfile = EARGF(usage());
                break;
+       case 'i':
+               iflag++;
+               break;
+       case 'l':
+               lflag = 1;
+               break;
        case 'm':
                mntpt = EARGF(usage());
                break;
-       case 'd':
-               debug = 1;
+       case 'n':
+               nodflt = 1;
                break;
        case 'p':
                plumbing = 0;
@@ -190,18 +397,19 @@ main(int argc, char *argv[])
        case 's':
                srvpost = 1;
                break;
-       case 'l':
-               logging = 1;
-               break;
-       case 'n':
-               nodflt = 1;
-               break;
        default:
                usage();
        }ARGEND
 
        if(argc)
                usage();
+       fmtinstall('A', Afmt);
+       fmtinstall('D', Dfmt);
+       fmtinstall(L'Δ', Δfmt);
+       fmtinstall('F', fcallfmt);
+       fmtinstall('H', encodefmt);             /* forces tls stuff */
+       fmtinstall('P', Pfmt);
+       quotefmtinstall();
        if(pipe(p) < 0)
                error("pipe failed");
        mfd[0] = p[0];
@@ -214,19 +422,13 @@ main(int argc, char *argv[])
                mntpt = maildir;
        }
        if(mboxfile == nil && !nodflt){
-               snprint(mbox, sizeof(mbox), "/mail/box/%s/mbox", user);
+               snprint(mbox, sizeof mbox, "/mail/box/%s/mbox", user);
                mboxfile = mbox;
-               std = 1;
        }
 
-       if(debug)
-               fmtinstall('F', fcallfmt);
-
-       if(mboxfile != nil){
-               err = newmbox(mboxfile, "mbox", std);
-               if(err != nil)
+       if(mboxfile != nil)
+               if(err = newmbox(mboxfile, "mbox", 0, 0))
                        sysfatal("opening %s: %s", mboxfile, err);
-       }
 
        switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG|RFREND)){
        case -1:
@@ -235,46 +437,67 @@ main(int argc, char *argv[])
                henter(PATH(0, Qtop), dirtab[Qctl],
                        (Qid){PATH(0, Qctl), 0, QTFILE}, nil, nil);
                close(p[1]);
+               setname(v);
                io();
-               postnote(PNGROUP, getpid(), "die yankee pig dog");
+               syncallmboxes();
+               syskillpg(getpid());
                break;
        default:
                close(p[0]);    /* don't deadlock if child fails */
                if(srvpost){
-                       sprint(srvfile, "/srv/upasfs.%s", user);
+                       snprint(srvfile, sizeof srvfile, "/srv/upasfs.%s", user);
                        post(srvfile, "upasfs", p[1]);
-               } else {
+               }else
                        if(mount(p[1], -1, mntpt, MREPL, "") < 0)
                                error("mount failed");
-               }
        }
-       exits(0);
+       exits("");
 }
 
-static int
-fileinfo(Message *m, int t, char **pp)
+char*
+sputc(char *p, char *e, int c)
 {
-       char *p;
-       int len;
+       if(p < e - 1)
+               *p++ = c;
+       return p;
+}
+
+char*
+seappend(char *s, char *e, char *a, int n)
+{
+       int l;
 
-       p = "";
-       len = 0;
+       l = e - s - 1;
+       if(l < n)
+               n = l;
+       memcpy(s, a, n);
+       s += n;
+       *s = 0;
+       return s;
+}
+
+static int
+fileinfo(Mailbox *mb, Message *m, int t, char **pp)
+{
+       char *s, *e, *p;
+       int len, i;
+       static char buf[64 + 512];
+
+       cacheidx(mb, m);
+       sanembmsg(mb, m);
+       p = nil;
+       len = -1;
        switch(t){
        case Qbody:
+               cachebody(mb, m);
                p = m->body;
-               len = m->bend - m->body;
+               len = m->bend - p;
                break;
        case Qbcc:
-               if(m->bcc822){
-                       p = s_to_c(m->bcc822);
-                       len = strlen(p);
-               }
+               p = m->bcc;
                break;
        case Qcc:
-               if(m->cc822){
-                       p = s_to_c(m->cc822);
-                       len = strlen(p);
-               }
+               p = m->cc;
                break;
        case Qdisposition:
                switch(m->disposition){
@@ -285,142 +508,131 @@ fileinfo(Message *m, int t, char **pp)
                        p = "file";
                        break;
                }
-               len = strlen(p);
                break;
        case Qdate:
-               if(m->date822){
-                       p = s_to_c(m->date822);
-                       len = strlen(p);
-               } else if(m->unixdate != nil){
-                       p = s_to_c(m->unixdate);
-                       len = strlen(p);
+               p = m->date822;
+               if(!p){
+                       p = buf;
+                       len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
                }
                break;
        case Qfilename:
-               if(m->filename){
-                       p = s_to_c(m->filename);
-                       len = strlen(p);
-               }
+               p = m->filename;
+               break;
+       case Qflags:
+               p = flagbuf(buf, m->flags);
                break;
        case Qinreplyto:
-               if(m->inreplyto822){
-                       p = s_to_c(m->inreplyto822);
-                       len = strlen(p);
-               }
+               p = m->inreplyto;
                break;
        case Qmessageid:
-               if(m->messageid822){
-                       p = s_to_c(m->messageid822);
-                       len = strlen(p);
-               }
+               p = m->messageid;
                break;
        case Qfrom:
-               if(m->from822){
-                       p = s_to_c(m->from822);
-                       len = strlen(p);
-               } else if(m->unixfrom != nil){
-                       p = s_to_c(m->unixfrom);
-                       len = strlen(p);
-               }
+               if(m->from)
+                       p = m->from;
+               else
+                       p = m->unixfrom;
                break;
-       case Qheader:
-               p = m->header;
-               len = headerlen(m);
+       case Qffrom:
+               if(m->ffrom)
+                       p = m->ffrom;
                break;
        case Qlines:
-               p = m->lines;
-               if(*p == 0)
-                       countlines(m);
-               len = strlen(m->lines);
+               len = snprint(buf, sizeof buf, "%lud", m->lines);
+               p = buf;
                break;
        case Qraw:
+               cachebody(mb, m);
                p = m->start;
-               if(strncmp(m->start, "From ", 5) == 0){
-                       p = strchr(p, '\n');
-                       if(p == nil)
-                               p = m->start;
-                       else
-                               p++;
-               }
-               len = m->end - p;
+               if(strncmp(m->start, "From ", 5) == 0)
+               if(e = strchr(p, '\n'))
+                       p = e + 1;
+               len = m->rbend - p;
                break;
        case Qrawunix:
+               cachebody(mb, m);
                p = m->start;
                len = m->end - p;
                break;
        case Qrawbody:
+               cachebody(mb, m);
                p = m->rbody;
                len = m->rbend - p;
                break;
        case Qrawheader:
+               cacheheaders(mb, m);
                p = m->header;
                len = m->hend - p;
                break;
        case Qmimeheader:
+               cacheheaders(mb, m);
                p = m->mheader;
                len = m->mhend - p;
                break;
-       case Qreplyto:
-               p = nil;
-               if(m->replyto822 != nil){
-                       p = s_to_c(m->replyto822);
-                       len = strlen(p);
-               } else if(m->from822 != nil){
-                       p = s_to_c(m->from822);
-                       len = strlen(p);
-               } else if(m->sender822 != nil){
-                       p = s_to_c(m->sender822);
-                       len = strlen(p);
-               } else if(m->unixfrom != nil){
-                       p = s_to_c(m->unixfrom);
-                       len = strlen(p);
+       case Qreferences:
+               cacheheaders(mb, m);
+               e = buf + sizeof buf;
+               s = buf;
+               for(i = 0; i < nelem(m->references); i++){
+                       if(m->references[i] == 0)
+                               break;
+                       s = seprint(s, e, "%s\n", m->references[i]);
                }
+               *s = 0;
+               p = buf;
+               len = s - buf;
+               break;
+       case Qreplyto:
+               if(m->replyto != nil)
+                       p = m->replyto;
+               else if(m->from != nil)
+                       p = m->from;
+               else if(m->sender != nil)
+                       p = m->sender;
+               else if(m->unixfrom != nil)
+                       p = m->unixfrom;
                break;
        case Qsender:
-               if(m->sender822){
-                       p = s_to_c(m->sender822);
-                       len = strlen(p);
-               }
+               p = m->sender;
                break;
        case Qsubject:
-               p = nil;
-               if(m->subject822){
-                       p = s_to_c(m->subject822);
-                       len = strlen(p);
-               }
+               p = m->subject;
+               break;
+       case Qsize:
+               len = snprint(buf, sizeof buf, "%lud", m->size);
+               p = buf;
                break;
        case Qto:
-               if(m->to822){
-                       p = s_to_c(m->to822);
-                       len = strlen(p);
-               }
+               p = m->to;
                break;
        case Qtype:
-               if(m->type){
-                       p = s_to_c(m->type);
-                       len = strlen(p);
-               }
+               p = rtab[m->type].s;
+               len = rtab[m->type].l;
                break;
        case Qunixdate:
-               if(m->unixdate){
-                       p = s_to_c(m->unixdate);
-                       len = strlen(p);
-               }
+               p = buf;
+               len = snprint(buf, sizeof buf, "%#Δ", m->fileid);
+               break;
+       case Qfileid:
+               p = buf;
+               len = snprint(buf, sizeof buf, "%D", m->fileid);
                break;
        case Qunixheader:
-               if(m->unixheader){
-                       p = s_to_c(m->unixheader);
-                       len = s_len(m->unixheader);
-               }
+               cacheheaders(mb, m);
+               p = m->unixheader;
                break;
        case Qdigest:
-               if(m->sdigest){
-                       p = s_to_c(m->sdigest);
-                       len = strlen(p);
-               }
+               p = buf;
+               len = snprint(buf, sizeof buf, "%A", m->digest);
                break;
        }
+       if(p == nil)
+               p = "";
+       if(len == -1)
+               len = strlen(p);
        *pp = p;
+       putcache(mb, m);
        return len;
 }
 
@@ -441,47 +653,53 @@ int infofields[] = {
        Qsender,
        Qmessageid,
        Qlines,
-       -1,
+       Qsize,
+       Qflags,
+       Qfileid,
+       Qffrom,
 };
 
-static int
-readinfo(Message *m, char *buf, long off, int count)
+int
+readinfo(Mailbox *mb, Message *m, char *buf, long off, int count)
 {
-       char *p;
-       int len, i, n;
-       String *s;
-
-       s = s_new();
-       len = 0;
-       for(i = 0; len < count && infofields[i] >= 0; i++){
-               n = fileinfo(m, infofields[i], &p);
-               s = stringconvert(s, p, n);
-               s_append(s, "\n");
-               p = s_to_c(s);
-               n = strlen(p);
-               if(off > 0){
-                       if(off >= n){
-                               off -= n;
-                               continue;
-                       }
-                       p += off;
+       char *s, *p, *e;
+       int i, n;
+       long off0;
+
+       if(m->infolen > 0 && off >= m->infolen)
+               return 0;
+       off0 = off;
+       s = buf;
+       e = s + count;
+       for(i = 0; s < e; i++){
+               if(i == nelem(infofields)){
+                       m->infolen = s - buf + off0;
+                       break;
+               }
+               n = fileinfo(mb, m, infofields[i], &p);
+               if(off > n){
+                       off -= n + 1;
+                       continue;
+               }
+               if(off){
                        n -= off;
+                       p += off;
                        off = 0;
                }
-               if(n > count - len)
-                       n = count - len;
-               if(buf)
-                       memmove(buf+len, p, n);
-               len += n;
+               if(s + n > e)
+                       n = e - s;
+               memcpy(s, p, n);
+               s += n;
+               if(s < e)
+                       *s++ = '\n';
        }
-       s_free(s);
-       return len;
+       return s - buf;
 }
 
 static void
 mkstat(Dir *d, Mailbox *mb, Message *m, int t)
 {
-       char *p;
+       char *p, *e;
 
        d->uid = user;
        d->gid = user;
@@ -491,13 +709,13 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t)
        d->qid.type = QTFILE;
        d->type = 0;
        d->dev = 0;
-       if(mb != nil && mb->d != nil){
-               d->atime = mb->d->atime;
-               d->mtime = mb->d->mtime;
-       } else {
+       if(m && m->fileid > 1000000ull)
+               d->atime = m->fileid >> 8;
+       else if(mb && mb->d)
+               d->atime = mb->d->mtime;
+       else
                d->atime = time(0);
-               d->mtime = d->atime;
-       }
+       d->mtime = d->atime;
 
        switch(t){
        case Qtop:
@@ -530,6 +748,12 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t)
                d->length = 0;
                d->qid.path = PATH(0, Qctl);
                break;
+       case Qheader:
+               d->name = dirtab[t];
+               cacheheaders(mb, m);
+               d->length = readheader(m, hbuf, 0, sizeof hbuf);
+               putcache(mb, m);
+               break;
        case Qmboxctl:
                d->name = dirtab[t];
                d->mode = 0222;
@@ -539,12 +763,37 @@ mkstat(Dir *d, Mailbox *mb, Message *m, int t)
                break;
        case Qinfo:
                d->name = dirtab[t];
-               d->length = readinfo(m, nil, 0, 1<<30);
+               d->length = readinfo(mb, m, hbuf, 0, sizeof hbuf);
                d->qid.path = PATH(m->id, t);
                break;
+       case Qraw:
+               cacheheaders(mb, m);
+               p = m->start;
+               if(strncmp(m->start, "From ", 5) == 0)
+               if(e = strchr(p, '\n'))
+                       p = e + 1;
+               d->name = dirtab[t];
+               d->length = m->size - (p - m->start);
+               putcache(mb, m);
+               break;
+       case Qrawbody:
+               d->name = dirtab[t];
+               d->length = m->rawbsize;
+               break;
+       case Qrawunix:
+               d->name = dirtab[t];
+               d->length = m->size;
+               if(mb->addfrom && Topmsg(mb, m)){
+                       cacheheaders(mb, m);
+                       d->length += strlen(m->unixheader);
+                       putcache(mb, m);
+               }
+               break;
+       case Qflags:
+               d->mode = 0666;
        default:
                d->name = dirtab[t];
-               d->length = fileinfo(m, t, &p);
+               d->length = fileinfo(mb, m, t, &p);
                d->qid.path = PATH(m->id, t);
                break;
        }
@@ -577,9 +826,8 @@ rauth(Fid*)
 }
 
 char*
-rflush(Fid *f)
+rflush(Fid*)
 {
-       USED(f);
        return 0;
 }
 
@@ -608,30 +856,43 @@ doclone(Fid *f, int nfid)
                return nil;
        nf->busy = 1;
        nf->open = 0;
-       nf->m = f->m;
-       nf->mtop = f->mtop;
-       nf->mb = f->mb;
-       if(f->mb != nil)
-               mboxincref(f->mb);
-       if(f->mtop != nil){
-               qlock(f->mb);
-               msgincref(f->mtop);
-               qunlock(f->mb);
+       if(nf->mb = f->mb)
+               mboxincref(nf->mb);
+       if(nf->m = f->m)
+               msgincref(gettopmsg(nf->mb, nf->m));
+       if(nf->mtop = f->mtop){
+               qlock(nf->mb);
+               msgincref(nf->mtop);
+               qunlock(nf->mb);
        }
        nf->qid = f->qid;
+sanefid(nf);
+sanefid(f);
        return nf;
 }
 
+/* slow?  binary search? */
+static int
+dindex(char *name)
+{
+       int i;
+
+       for(i = 0; i < Qmax; i++)
+               if(dirtab[i] != nil)
+               if(strcmp(dirtab[i], name) == 0)
+                       return i;
+       return -1;
+}
+
 char*
 dowalk(Fid *f, char *name)
 {
-       int t;
-       Mailbox *omb, *mb;
        char *rv, *p;
+       int t, t1;
+       Mailbox *omb, *mb;
        Hash *h;
 
        t = FILE(f->qid.path);
-
        rv = Enotexist;
 
        omb = f->mb;
@@ -640,12 +901,26 @@ dowalk(Fid *f, char *name)
        else
                qlock(&mbllock);
 
-       // this must catch everything except . and ..
+       /* this must catch everything except . and .. */
 retry:
-       h = hlook(f->qid.path, name);
+sanefid(f);
+       t1 = FILE(f->qid.path);
+       if((t1 == Qmbox || t1 == Qdir) && *name >= 'a' && *name <= 'z'){
+               h = hlook(f->qid.path, "xxx");          /* sleezy speedup */
+               t1 = dindex(name);
+               if(t1 == -1)
+                       h = nil;
+       }else
+               h = hlook(f->qid.path, name);
        if(h != nil){
+               if(f->m)
+                       msgdecref(f->mb, gettopmsg(f->mb, f->m));
+               if(f->mb && f->mb != h->mb)
+                       mboxdecref(f->mb);
                f->mb = h->mb;
                f->m = h->m;
+               if(f->m)
+                       msgincref(gettopmsg(f->mb, f->m));
                switch(t){
                case Qtop:
                        if(f->mb != nil)
@@ -659,8 +934,11 @@ retry:
                        break;
                }
                f->qid = h->qid;
+               if(t1 < Qmax)
+                       f->qid.path = PATH(f->m->id, t1);       /* sleezy speedup */
+sanefid(f);
                rv = nil;
-       } else if((p = strchr(name, '.')) != nil && *name != '.'){
+       }else if((p = strchr(name, '.')) != nil && *name != '.'){
                *p = 0;
                goto retry;
        }
@@ -697,13 +975,15 @@ retry:
                        break;
                case Qdir:
                        qlock(f->mb);
-                       if(f->m->whole == f->mb->root){
+                       if(Topmsg(f->mb, f->m)){
                                f->qid.path = PATH(f->mb->id, Qmbox);
                                f->qid.type = QTDIR;
                                f->qid.vers = f->mb->d->qid.vers;
                                msgdecref(f->mb, f->mtop);
+                               msgdecref(f->mb, f->m);
                                f->m = f->mtop = nil;
                        } else {
+                               /* refs don't change; still the same message */
                                f->m = f->m->whole;
                                f->qid.path = PATH(f->m->id, Qdir);
                                f->qid.type = QTDIR;
@@ -723,6 +1003,7 @@ rwalk(Fid *f)
        char *rv;
        int i;
 
+sanefid(f);
        if(f->open)
                return Eisopen;
 
@@ -754,32 +1035,31 @@ rwalk(Fid *f)
        }
        rhdr.nwqid = i;
 
-       /* we only error out if no walk  */
+       /* we only error out if no walk */
        if(i > 0)
                rv = nil;
-
+sanefid(f);
        return rv;
 }
 
-char *
+char*
 ropen(Fid *f)
 {
        int file;
 
        if(f->open)
                return Eisopen;
-
        file = FILE(f->qid.path);
        if(thdr.mode != OREAD)
-               if(file != Qctl && file != Qmboxctl)
+               if(file != Qctl && file != Qmboxctl && file != Qflags)
                        return Eperm;
 
-       // make sure we've decoded
+       /* make sure we've decoded */
        if(file == Qbody){
-               if(f->m->decoded == 0)
-                       decode(f->m);
-               if(f->m->converted == 0)
-                       convert(f->m);
+               cachebody(f->mb, f->m);
+               decode(f->m);
+               convert(f->m);
+               putcache(f->mb, f->m);
        }
 
        rhdr.iounit = 0;
@@ -788,7 +1068,7 @@ ropen(Fid *f)
        return 0;
 }
 
-char *
+char*
 rcreate(Fid*)
 {
        return Eperm;
@@ -816,7 +1096,7 @@ readtopdir(Fid*, uchar *buf, long off, int cnt, int blen)
                
        for(mb = mbl; mb != nil; mb = mb->next){
                mkstat(&d, mb, nil, Qmbox);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
@@ -851,22 +1131,22 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
                        off -= m;
        }
 
-       // to avoid n**2 reads of the directory, use a saved finger pointer
+       /* to avoid n**2 reads of the directory, use a saved finger pointer */
        if(f->mb->vers == f->fvers && off >= f->foff && f->fptr != nil){
                msg = f->fptr;
                pos = f->foff;
        } else {
                msg = f->mb->root->part;
                pos = 0;
-       } 
+       }
 
        for(; cnt > 0 && msg != nil; msg = msg->next){
-               // act like deleted files aren't there
+               /* act like deleted files aren't there */
                if(msg->deleted)
                        continue;
 
                mkstat(&d, f->mb, msg, Qdir);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
@@ -876,7 +1156,7 @@ readmboxdir(Fid *f, uchar *buf, long off, int cnt, int blen)
                pos += m;
        }
 
-       // save a finger pointer for next read of the mbox directory
+       /* save a finger pointer for next read of the mbox directory */
        f->foff = pos;
        f->fptr = msg;
        f->fvers = f->mb->vers;
@@ -896,7 +1176,7 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
        pos = 0;
        for(i = 0; i < Qmax; i++){
                mkstat(&d, f->mb, f->m, i);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                return n;
@@ -907,7 +1187,7 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
        }
        for(msg = f->m->part; msg != nil; msg = msg->next){
                mkstat(&d, f->mb, msg, Qdir);
-               m = convD2M(&d, &buf[n], blen-n);
+               m = convD2M(&d, &buf[n], blen - n);
                if(off <= pos){
                        if(m <= BIT16SZ || m > cnt)
                                break;
@@ -920,60 +1200,110 @@ readmsgdir(Fid *f, uchar *buf, long off, int cnt, int blen)
        return n;
 }
 
+static int
+mboxctlread(Mailbox *mb, char **p)
+{
+       static char buf[128];
+
+       *p = buf;
+       return snprint(*p, sizeof buf, "%s\n%ld\n", mb->path, mb->vers);
+}
+
 char*
 rread(Fid *f)
 {
-       long off;
-       int t, i, n, cnt;
        char *p;
+       int t, i, n, cnt;
+       long off;
 
        rhdr.count = 0;
        off = thdr.offset;
        cnt = thdr.count;
-
        if(cnt > messagesize - IOHDRSZ)
                cnt = messagesize - IOHDRSZ;
-
        rhdr.data = (char*)mbuf;
 
+sanefid(f);
        t = FILE(f->qid.path);
        if(f->qid.type & QTDIR){
-               if(t == Qtop) {
+               if(t == Qtop){
                        qlock(&mbllock);
                        n = readtopdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
                        qunlock(&mbllock);
-               } else if(t == Qmbox) {
+               }else if(t == Qmbox) {
                        qlock(f->mb);
                        if(off == 0)
                                syncmbox(f->mb, 1);
                        n = readmboxdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
                        qunlock(f->mb);
-               } else if(t == Qmboxctl) {
+               }else if(t == Qmboxctl)
                        n = 0;
-               } else {
+               else
                        n = readmsgdir(f, mbuf, off, cnt, messagesize - IOHDRSZ);
-               }
-
                rhdr.count = n;
                return nil;
        }
 
-       if(FILE(f->qid.path) == Qheader){
+       switch(t){
+       case Qctl:
+               rhdr.count = 0;
+               break;
+       case Qmboxctl:
+               i = mboxctlread(f->mb, &p);
+               goto output;
+               break;
+       case Qheader:
+               cacheheaders(f->mb, f->m);
                rhdr.count = readheader(f->m, (char*)mbuf, off, cnt);
-               return nil;
-       }
-
-       if(FILE(f->qid.path) == Qinfo){
-               rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt);
-               return nil;
+               putcache(f->mb, f->m);
+               break;
+       case Qinfo:
+               if(cnt > sizeof mbuf)
+                       cnt = sizeof mbuf;
+               rhdr.count = readinfo(f->mb, f->m, (char*)mbuf, off, cnt);
+               break;
+       case Qrawunix:
+               if(f->mb->addfrom && Topmsg(f->mb, f->m)){
+                       cacheheaders(f->mb, f->m);
+                       p = f->m->unixheader;
+                       if(off < strlen(p)){
+                               rhdr.count = strlen(p + off);
+                               memmove(mbuf, p + off, rhdr.count);
+                               break;
+                       }
+                       off -= strlen(p);
+                       putcache(f->mb, f->m);
+               }
+       default:
+               i = fileinfo(f->mb, f->m, t, &p);
+       output:
+               if(off < i){
+                       if(off + cnt > i)
+                               cnt = i - off;
+                       if(cnt > sizeof mbuf)
+                               cnt = sizeof mbuf;
+                       memmove(mbuf, p + off, cnt);
+                       rhdr.count = cnt;
+               }
+               break;
        }
+       return nil;
+}
 
-       i = fileinfo(f->m, FILE(f->qid.path), &p);
-       if(off < i){
-               if((off + cnt) > i)
-                       cnt = i - off;
-               memmove(mbuf, p + off, cnt);
-               rhdr.count = cnt;
+char*
+modflags(Mailbox *mb, Message *m, char *p)
+{
+       char *err;
+       uchar f;
+
+       f = m->flags;
+       if(err = txflags(p, &f))
+               return err;
+       if(f != m->flags){
+               if(mb->modflags != nil)
+                       mb->modflags(mb, m, f);
+               m->flags = f;
+               m->cstate |= Cidxstale;
        }
        return nil;
 }
@@ -981,87 +1311,141 @@ rread(Fid *f)
 char*
 rwrite(Fid *f)
 {
-       char *err;
-       char *token[1024];
-       int t, n;
-       String *file;
+       char *argvbuf[1024], **argv, file[Pathlen], *err, *v0;
+       int i, t, argc, flags;
+       Message *m;
 
        t = FILE(f->qid.path);
        rhdr.count = thdr.count;
+       sanefid(f);
+       if(thdr.count == 0)
+               return Ebadctl;
+       if(thdr.data[thdr.count - 1] == '\n')
+               thdr.data[thdr.count - 1] = 0;
+       else
+               thdr.data[thdr.count] = 0;
+       argv = argvbuf;
        switch(t){
        case Qctl:
-               if(thdr.count == 0)
-                       return Ebadctl;
-               if(thdr.data[thdr.count-1] == '\n')
-                       thdr.data[thdr.count-1] = 0;
-               else
-                       thdr.data[thdr.count] = 0;
-               n = tokenize(thdr.data, token, nelem(token));
-               if(n == 0)
+               memset(argvbuf, 0, sizeof argvbuf);
+               argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1);
+               if(argc == 0)
                        return Ebadctl;
-               if(strcmp(token[0], "open") == 0){
-                       file = s_new();
-                       switch(n){
-                       case 1:
-                               err = Ebadctl;
-                               break;
-                       case 2:
-                               mboxpath(token[1], getlog(), file, 0);
-                               err = newmbox(s_to_c(file), nil, 0);
-                               break;
-                       default:
-                               mboxpath(token[1], getlog(), file, 0);
-                               if(strchr(token[2], '/') != nil)
-                                       err = "/ not allowed in mailbox name";
-                               else
-                                       err = newmbox(s_to_c(file), token[2], 0);
-                               break;
-                       }
-                       s_free(file);
-                       return err;
+               if(strcmp(argv[0], "open") == 0 || strcmp(argv[0], "create") == 0){
+                       if(argc == 1 || argc > 3)
+                               return Ebadargs;
+                       mboxpathbuf(file, sizeof file, getlog(), argv[1]);
+                       if(argc == 3){
+                               if(strchr(argv[2], '/') != nil)
+                                       return "/ not allowed in mailbox name";
+                       }else
+                               argv[2] = nil;
+                       flags = 0;
+                       if(strcmp(argv[0], "create") == 0)
+                               flags |= DMcreate;
+                       return newmbox(file, argv[2], flags, 0);
                }
-               if(strcmp(token[0], "close") == 0){
-                       if(n < 2)
+               if(strcmp(argv[0], "close") == 0){
+                       if(argc < 2)
                                return nil;
-                       freembox(token[1]);
+                       for(i = 1; i < argc; i++)
+                               freembox(argv[i]);
                        return nil;
                }
-               if(strcmp(token[0], "delete") == 0){
-                       if(n < 3)
+               if(strcmp(argv[0], "delete") == 0){
+                       if(argc < 3)
                                return nil;
-                       delmessages(n-1, &token[1]);
+                       delmessages(argc - 1, argv + 1);
                        return nil;
                }
+               if(strcmp(argv[0], "flag") == 0){
+                       if(argc < 3)
+                               return nil;
+                       return flagmessages(argc - 1, argv + 1);
+               }
+               if(strcmp(argv[0], "remove") == 0){
+                       v0 = argv0;
+                       flags = 0;
+                       ARGBEGIN{
+                       default:
+                               argv0 = v0;
+                               return Ebadargs;
+                       case 'r':
+                               flags |= Rrecur;
+                               break;
+                       case 't':
+                               flags |= Rtrunc;
+                               break;
+                       }ARGEND
+                       argv0 = v0;
+                       if(argc == 0)
+                               return Ebadargs;
+                       for(; *argv; argv++){
+                               mboxpathbuf(file, sizeof file, getlog(), *argv);
+                               if(err = newmbox(file, nil, 0, 0))
+                                       return err;
+//                             if(!mb->remove)
+//                                     return "remove not implemented";
+                               if(err = removembox(file, flags))
+                                       return err;
+                       }
+                       return 0;
+               }
+               if(strcmp(argv[0], "rename") == 0){
+                       v0 = argv0;
+                       flags = 0;
+                       ARGBEGIN{
+                       case 't':
+                               flags |= Rtrunc;
+                               break;
+                       }ARGEND
+                       argv0 = v0;
+                       if(argc != 2)
+                               return Ebadargs;
+                       return mboxrename(argv[0], argv[1], flags);
+               }
                return Ebadctl;
        case Qmboxctl:
                if(f->mb && f->mb->ctl){
-                       if(thdr.count == 0)
-                               return Ebadctl;
-                       if(thdr.data[thdr.count-1] == '\n')
-                               thdr.data[thdr.count-1] = 0;
-                       else
-                               thdr.data[thdr.count] = 0;
-                       n = tokenize(thdr.data, token, nelem(token));
-                       if(n == 0)
+                       argc = tokenize(thdr.data, argv, nelem(argvbuf));
+                       if(argc == 0)
                                return Ebadctl;
-                       return (*f->mb->ctl)(f->mb, n, token);
+                       return f->mb->ctl(f->mb, argc, argv);
                }
+               break;
+       case Qflags:
+               /*
+                * modifying flags on subparts is a little strange.
+                */
+               if(!f->mb || !f->m)
+                       break;
+               m = gettopmsg(f->mb, f->m);
+               err = modflags(f->mb, m, thdr.data);
+//             premature optimization?  flags not written immediately.
+//             if(err == nil && f->m->cstate&Cidxstale)
+//                     wridxfile(f->mb);               /* syncmbox(f->mb, 1); */
+               return err;
        }
        return Eperm;
 }
 
-char *
+char*
 rclunk(Fid *f)
 {
        Mailbox *mb;
 
-       f->busy = 0;
+sanefid(f);
+       f->busy = 1;
+       /* coherence(); */
+       f->fid = -1;
        f->open = 0;
-       if(f->mtop != nil){
+       if(f->mtop){
                qlock(f->mb);
                msgdecref(f->mb, f->mtop);
                qunlock(f->mb);
        }
+       if(f->m)
+               msgdecref(f->mb, gettopmsg(f->mb, f->m));
        f->m = f->mtop = nil;
        mb = f->mb;
        if(mb != nil){
@@ -1071,17 +1455,18 @@ rclunk(Fid *f)
                mboxdecref(mb);
                qunlock(&mbllock);
        }
-       f->fid = -1;
+       f->busy = 0;
        return 0;
 }
 
 char *
 rremove(Fid *f)
 {
+sanefid(f);
        if(f->m != nil){
                if(f->m->deleted == 0)
                        mailplumb(f->mb, f->m, 1);
-               f->m->deleted = 1;
+               f->m->deleted = Deleted;
        }
        return rclunk(f);
 }
@@ -1091,6 +1476,7 @@ rstat(Fid *f)
 {
        Dir d;
 
+sanefid(f);
        if(FILE(f->qid.path) == Qmbox){
                qlock(f->mb);
                syncmbox(f->mb, 1);
@@ -1102,13 +1488,13 @@ rstat(Fid *f)
        return 0;
 }
 
-char *
+char*
 rwstat(Fid*)
 {
        return Eperm;
 }
 
-Fid *
+Fid*
 newfid(int fid)
 {
        Fid *f, *ff;
@@ -1152,33 +1538,44 @@ io(void)
        int n;
 
        /* start a process to watch the mailboxes*/
-       if(plumbing){
+       if(plumbing || biffing)
                switch(rfork(RFPROC|RFMEM)){
                case -1:
                        /* oh well */
                        break;
                case 0:
                        reader();
-                       exits(nil);
+                       exits("");
                default:
                        break;
                }
-       }
 
-       while((n = read9pmsg(mfd[0], mdata, messagesize)) != 0){
+       for(;;){
+               /*
+                * reading from a pipe or a network device
+                * will give an error after a few eof reads
+                * however, we cannot tell the difference
+                * between a zero-length read and an interrupt
+                * on the processes writing to us,
+                * so we wait for the error
+                */
+               checkmboxrefs();
+               n = read9pmsg(mfd[0], mdata, messagesize);
+               if(n == 0)
+                       continue;
                if(n < 0)
-                       error("mount read");
-               if(convM2S(mdata, n, &thdr) != n)
-                       error("convM2S format error");
+                       return;
+               if(convM2S(mdata, n, &thdr) == 0)
+                       continue;
 
-               if(debug)
+               if(Dflag)
                        fprint(2, "%s:<-%F\n", argv0, &thdr);
 
                rhdr.data = (char*)mdata + messagesize;
                if(!fcalls[thdr.type])
                        err = "bad fcall type";
                else
-                       err = (*fcalls[thdr.type])(newfid(thdr.fid));
+                       err = fcalls[thdr.type](newfid(thdr.fid));
                if(err){
                        rhdr.type = Rerror;
                        rhdr.ename = err;
@@ -1187,14 +1584,16 @@ io(void)
                        rhdr.fid = thdr.fid;
                }
                rhdr.tag = thdr.tag;
-               if(debug)
-                       fprint(2, "%s:->%F\n", argv0, &rhdr);/**/
+               if(Dflag)
+                       fprint(2, "%s:->%F\n", argv0, &rhdr);
                n = convS2M(&rhdr, mdata, messagesize);
                if(write(mfd[1], mdata, n) != n)
                        error("mount write");
        }
 }
 
+static char *readerargv[] = {"upas/fs", "plumbing", 0};
+
 void
 reader(void)
 {
@@ -1202,13 +1601,14 @@ reader(void)
        Dir *d;
        Mailbox *mb;
 
+       setname(readerargv);
        sleep(15*1000);
        for(;;){
                t = time(0);
                qlock(&mbllock);
                for(mb = mbl; mb != nil; mb = mb->next){
                        assert(mb->refs > 0);
-                       if(mb->waketime != 0 && t > mb->waketime){
+                       if(mb->waketime != 0 && t >= mb->waketime){
                                qlock(mb);
                                mb->waketime = 0;
                                break;
@@ -1256,8 +1656,8 @@ newid(void)
 void
 error(char *s)
 {
-       postnote(PNGROUP, getpid(), "die yankee pig dog");
-       fprint(2, "%s: %s: %r\n", argv0, s);
+       syskillpg(getpid());
+       eprint("upas/fs: fatal error: %s: %r\n", s);
        exits(s);
 }
 
@@ -1266,13 +1666,13 @@ typedef struct Ignorance Ignorance;
 struct Ignorance
 {
        Ignorance *next;
-       char    *str;           /* string */
-       int     partial;        /* true if not exact match */
+       char    *str;
+       int     len;
 };
 Ignorance *ignorance;
 
 /*
- *  read the file of headers to ignore 
+ *  read the file of headers to ignore
  */
 void
 readignore(void)
@@ -1288,15 +1688,13 @@ readignore(void)
        if(b == 0)
                return;
        while(p = Brdline(b, '\n')){
-               p[Blinelen(b)-1] = 0;
+               p[Blinelen(b) - 1] = 0;
                while(*p && (*p == ' ' || *p == '\t'))
                        p++;
                if(*p == '#')
                        continue;
-               i = malloc(sizeof(Ignorance));
-               if(i == 0)
-                       break;
-               i->partial = strlen(p);
+               i = emalloc(sizeof *i);
+               i->len = strlen(p);
                i->str = strdup(p);
                if(i->str == 0){
                        free(i);
@@ -1315,159 +1713,37 @@ ignore(char *p)
 
        readignore();
        for(i = ignorance; i != nil; i = i->next)
-               if(cistrncmp(i->str, p, i->partial) == 0)
+               if(cistrncmp(i->str, p, i->len) == 0)
                        return 1;
        return 0;
 }
 
-int
-hdrlen(char *p, char *e)
-{
-       char *ep;
-
-       ep = p;
-       do {
-               ep = strchr(ep, '\n');
-               if(ep == nil){
-                       ep = e;
-                       break;
-               }
-               ep++;
-               if(ep >= e){
-                       ep = e;
-                       break;
-               }
-       } while(*ep == ' ' || *ep == '\t');
-       return ep - p;
-}
-
-// rfc2047 non-ascii: =?charset?q?encoded-text?=
-int
-rfc2047convert(String *s, char *token, int len)
-{
-       char charset[100], decoded[1024], *e, *x;
-       int l;
-
-       if(len == 0)
-               return -1;
-
-       e = token+len-2;
-       token += 2;
-
-       x = memchr(token, '?', e-token);
-       if(x == nil || (l=x-token) >= sizeof charset)
-               return -1;
-       memmove(charset, token, l);
-       charset[l] = 0;
-
-       token = x+1;
-
-       // bail if it doesn't fit 
-       if(e-token > sizeof(decoded)-1)
-               return -1;
-
-       // bail if we don't understand the encoding
-       if(cistrncmp(token, "b?", 2) == 0){
-               token += 2;
-               len = dec64((uchar*)decoded, sizeof(decoded), token, e-token);
-               decoded[len] = 0;
-       } else if(cistrncmp(token, "q?", 2) == 0){
-               token += 2;
-               len = decquoted(decoded, token, e, 1);
-               if(len > 0 && decoded[len-1] == '\n')
-                       len--;
-               decoded[len] = 0;
-       } else
-               return -1;
-
-       if(xtoutf(charset, &x, decoded, decoded+len) <= 0)
-               s_append(s, decoded);
-       else {
-               s_append(s, x);
-               free(x);
-       }
-       return 0;
-}
-
-char*
-rfc2047start(char *start, char *end)
-{
-       int quests;
-
-       if(*--end != '=')
-               return nil;
-       if(*--end != '?')
-               return nil;
-
-       quests = 0;
-       for(end--; end >= start; end--){
-               switch(*end){
-               case '=':
-                       if(quests == 3 && *(end+1) == '?')
-                               return end;
-                       break;
-               case '?':
-                       ++quests;
-                       break;
-               case ' ':
-               case '\t':
-               case '\n':
-               case '\r':
-                       /* can't have white space in a token */
-                       return nil;
-               }
-       }
-       return nil;
-}
-
-// convert a header line
-String*
-stringconvert(String *s, char *uneaten, int len)
-{
-       char *token, *p, *e;
-
-       s = s_reset(s);
-       p = uneaten;
-       for(e = p+len; p < e; ){
-               while(*p++ == '=' && (token = rfc2047start(uneaten, p))){
-                       s_nappend(s, uneaten, token-uneaten);
-                       if(rfc2047convert(s, token, p - token) < 0)
-                               s_nappend(s, token, p - token);
-                       uneaten = p;
-                       for(; p<e && isspace(*p);)
-                               p++;
-                       if(p+2 < e && p[0] == '=' && p[1] == '?')
-                               uneaten = p;    // paste
-               }
-       }
-       if(p > uneaten)
-               s_nappend(s, uneaten, p-uneaten);
-       return s;
-}
-
 int
 readheader(Message *m, char *buf, int off, int cnt)
 {
-       char *p, *e;
-       int n, ns;
-       char *to = buf;
-       String *s;
+       char *s, *end, *se, *p, *e, *to;
+       int n, ns, salloc;
 
+       to = buf;
        p = m->header;
        e = m->hend;
-       s = nil;
+       s = emalloc(salloc = 2048);
+       end = s + salloc;
 
-       // copy in good headers
+       /* copy in good headers */
        while(cnt > 0 && p < e){
                n = hdrlen(p, e);
+               assert(n > 0);
                if(ignore(p)){
                        p += n;
                        continue;
                }
-
-               // rfc2047 processing
-               s = stringconvert(s, p, n);
-               ns = s_len(s);
+               if(n + 1 > salloc){
+                       s = erealloc(s, salloc = n + 1);
+                       end = s + salloc;
+               }
+               se = rfc2047(s, end, p, n, 0);
+               ns = se - s;
                if(off > 0){
                        if(ns <= off){
                                off -= ns;
@@ -1478,34 +1754,16 @@ readheader(Message *m, char *buf, int off, int cnt)
                }
                if(ns > cnt)
                        ns = cnt;
-               memmove(to, s_to_c(s)+off, ns);
+               memmove(to, s + off, ns);
                to += ns;
                p += n;
                cnt -= ns;
                off = 0;
        }
-
-       s_free(s);
+       free(s);
        return to - buf;
 }
 
-int
-headerlen(Message *m)
-{
-       char buf[1024];
-       int i, n;
-
-       if(m->hlen >= 0)
-               return m->hlen;
-       for(n = 0; ; n += i){
-               i = readheader(m, buf, n, sizeof(buf));
-               if(i <= 0)
-                       break;
-       }
-       m->hlen = n;
-       return n;
-}
-
 QLock hashlock;
 
 uint
@@ -1545,6 +1803,7 @@ henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb)
        int h;
        Hash *hp, **l;
 
+//if(m)sanemsg(m);
        qlock(&hashlock);
        h = hash(ppath, name);
        for(l = &htab[h]; *l != nil; l = &(*l)->next){
@@ -1595,25 +1854,43 @@ hashmboxrefs(Mailbox *mb)
        int refs = 0;
 
        qlock(&hashlock);
-       for(h = 0; h < Hsize; h++){
+       for(h = 0; h < Hsize; h++)
                for(hp = htab[h]; hp != nil; hp = hp->next)
                        if(hp->mb == mb)
                                refs++;
-       }
        qunlock(&hashlock);
        return refs;
 }
 
+void
+checkmboxrefs(void)
+{
+       int refs;
+       Mailbox *mb;
+
+//     qlock(&mbllock);
+       for(mb = mbl; mb; mb = mb->next){
+               qlock(mb);
+               refs = fidmboxrefs(mb) + 1;
+               if(refs != mb->refs){
+                       eprint("%s:%s ref mismatch got %d expected %d\n", mb->name, mb->path, refs, mb->refs);
+                       abort();
+               }
+               qunlock(mb);
+       }
+//     qunlock(&mbllock);
+}
+
 void
 post(char *name, char *envname, int srvfd)
 {
-       int fd;
        char buf[32];
+       int fd;
 
        fd = create(name, OWRITE, 0600);
        if(fd < 0)
                error("post failed");
-       sprint(buf, "%d",srvfd);
+       snprint(buf, sizeof buf, "%d", srvfd);
        if(write(fd, buf, strlen(buf)) != strlen(buf))
                error("srv write");
        close(fd);
diff --git a/sys/src/cmd/upas/fs/header.c b/sys/src/cmd/upas/fs/header.c
new file mode 100644 (file)
index 0000000..c6ffe82
--- /dev/null
@@ -0,0 +1,176 @@
+#include "common.h"
+#include <ctype.h>
+#include <libsec.h>
+#include "dat.h"
+
+int
+hdrlen(char *p, char *e)
+{
+       char *ep;
+
+       ep = p;
+       do {
+               ep = strchr(ep, '\n');
+               if(ep == nil){
+                       ep = e;
+                       break;
+               }
+               if(ep == p)
+                       break;
+               if(ep - p == 1 && ep[-1] == '\r')
+                       break;
+               ep++;
+               if(ep >= e){
+                       ep = e;
+                       break;
+               }
+       } while(*ep == ' ' || *ep == '\t');
+       return ep - p;
+}
+
+/* rfc2047 non-ascii: =?charset?q?encoded-text?= */
+static int
+tok(char **sp, char *se, char *token, int len)
+{
+       char charset[100], *s, *e, *x;
+       int l;
+
+       if(len == 0)
+               return -1;
+       s = *sp;
+       e = token + len - 2;
+       token += 2;
+
+       x = memchr(token, '?', e - token);
+       if(x == nil || (l = x - token) >= sizeof charset)
+               return -1;
+       memmove(charset, token, l);
+       charset[l] = 0;
+
+       /* bail if it doesn't fit */
+       token = x + 1;
+       if(e - token > se - s - 1)
+               return -1;
+
+       if(cistrncmp(token, "b?", 2) == 0){
+               token += 2;
+               len = dec64((uchar*)s, se - s - 1, token, e - token);
+               if(len == -1)
+                       return -1;
+               s[len] = 0;
+       }else if(cistrncmp(token, "q?", 2) == 0){
+               token += 2;
+               len = decquoted(s, token, e, 1);
+               if(len > 0 && s[len - 1] == '\n')
+                       len--;
+               s[len] = 0;
+       }else
+               return -1;
+
+       if(xtoutf(charset, &x, s, s + len) <= 0)
+               s += len;
+       else {
+               s = seprint(s, se, "%s", x);
+               free(x);
+       }
+       *sp = s;
+       return 0;
+}
+
+char*
+tokbegin(char *start, char *end)
+{
+       int quests;
+
+       if(*--end != '=')
+               return nil;
+       if(*--end != '?')
+               return nil;
+
+       quests = 0;
+       for(end--; end >= start; end--){
+               switch(*end){
+               case '=':
+                       if(quests == 3 && *(end + 1) == '?')
+                               return end;
+                       break;
+               case '?':
+                       ++quests;
+                       break;
+               case ' ':
+               case '\t':
+               case '\n':
+               case '\r':
+                       /* can't have white space in a token */
+                       return nil;
+               }
+       }
+       return nil;
+}
+
+static char*
+seappend822f(char *s, char *e, char *a, int n)
+{
+       int skip, c;
+
+       skip = 0;
+       for(; n--; a++){
+               c = *a;
+               if(skip && isspace(c))
+                       continue;
+               if(c == '\n'){
+                       c = ' ';
+                       skip = 1;
+               }else{
+                       if(c < 0x20)
+                               continue;
+                       skip = 0;
+               }
+               s = sputc(s, e, c);
+       }
+       return s;
+}
+
+static char*
+seappend822(char *s, char *e, char *a, int n)
+{
+       int c;
+
+       for(; n--; a++){
+               c = *a;
+               if(c < 0x20 && c != '\n' && c != '\t')
+                       continue;
+               s = sputc(s, e, c);
+       }
+       return s;
+}
+
+/* convert a header line */
+char*
+rfc2047(char *s, char *se, char *uneaten, int len, int fold)
+{
+       char *sp, *token, *p, *e;
+       char *(*f)(char*, char*, char*, int);
+
+       f = seappend822;
+       if(fold)
+               f = seappend822f;
+       sp = s;
+       p = uneaten;
+       for(e = p + len; p < e; ){
+               while(*p++ == '=' && (token = tokbegin(uneaten, p))){
+                       sp = f(sp, se, uneaten, token - uneaten);
+                       if(tok(&sp, se, token, p - token) < 0)
+                               sp = f(sp, se, token, p - token);
+                       uneaten = p;
+                       for(; p < e && isspace(*p);)
+                               p++;
+                       if(p + 2 < e && p[0] == '=' && p[1] == '?')
+                               uneaten = p;    /* paste */
+               }
+       }
+       if(p > uneaten)
+               sp = f(sp, se, uneaten, e - uneaten);
+       *sp = 0;
+       return sp;
+}
diff --git a/sys/src/cmd/upas/fs/idx.c b/sys/src/cmd/upas/fs/idx.c
new file mode 100644 (file)
index 0000000..eebe191
--- /dev/null
@@ -0,0 +1,535 @@
+#include "common.h"
+#include <libsec.h>
+#include "dat.h"
+
+#define idprint(...)   if(iflag > 1) fprint(2, __VA_ARGS__); else {}
+#define iprint(...)            if(iflag) fprint(2, __VA_ARGS__); else {}
+
+static char *magic             = "idx magic v7\n";
+static char *mbmagic   = "genericv1";
+enum {
+       Idxfields               = 21,
+
+       Idxto           = 30000,                /* index timeout in ms */
+       Idxstep         = 300,          /* sleep between tries */
+};
+
+void
+idxfree(Idx *i)
+{
+       if(i->str)
+               free(i->str);
+       else{
+               free(i->digest);
+               free(i->ffrom);
+               free(i->from);
+               free(i->to);
+               free(i->cc);
+               free(i->bcc);
+               free(i->replyto);
+               free(i->messageid);
+               free(i->subject);
+               free(i->sender);
+               free(i->inreplyto);
+               free(i->idxaux);
+       }
+       memset(i, 0, sizeof *i);
+}
+
+static char*
+∂(char *x)
+{
+       if(x)
+               return x;
+       return "";
+}
+
+static int
+pridxmsg(Biobuf *b, Idx *x)
+{
+       Bprint(b, "%#A %ux %D %lud ", x->digest, x->flags&~Frecent, x->fileid, x->lines);
+       Bprint(b, "%q %q %q %q %q ", ∂(x->ffrom), ∂(x->from), ∂(x->to), ∂(x->cc), ∂(x->bcc));
+       Bprint(b, "%q %q %q %q %q ", ∂(x->replyto), ∂(x->messageid), ∂(x->subject), ∂(x->sender), ∂(x->inreplyto));
+       Bprint(b, "%s %d %lud %lud ", rtab[x->type].s, x->disposition, x->size, x->rawbsize);
+       Bprint(b, "%lud %q %d\n", x->ibadchars, ∂(x->idxaux), x->nparts);
+       return 0;
+}
+
+static int
+pridx0(Biobuf *b, Mailbox *mb, Message *m, int l)
+{
+       for(; m; m = m->next){
+               if(l == 0)
+               if(insurecache(mb, m) == -1)
+                       continue;
+               if(pridxmsg(b, m))
+                       return -1;
+               if(m->part)
+                       pridx0(b, mb, m->part, l + 1);
+               m->cstate &= ~Cidxstale;
+               m->cstate |= Cidx;
+               if(l == 0)
+                       msgdecref(mb, m);
+       }
+       return 0;
+}
+
+void
+genericidxwrite(Biobuf *b, Mailbox*)
+{
+       Bprint(b, "%s\n", mbmagic);
+}
+
+static int
+pridx(Biobuf *b, Mailbox *mb)
+{
+       int i;
+
+       Bprint(b, magic);
+       mb->idxwrite(b, mb);
+//     prrefs(b);
+       i = pridx0(b, mb, mb->root->part, 0);
+       return i;
+}
+
+static char *eopen[] = {
+       "not found",
+       "does not exist",
+       "file is locked",
+       "file locked",
+       "exclusive lock",
+       0,
+};
+
+static char *ecreate[] = {
+       "already exists",
+       "file is locked",
+       "file locked",
+       "exclusive lock",
+       0,
+};
+
+static int
+bad(char **t)
+{
+       char buf[ERRMAX];
+       int i;
+
+       rerrstr(buf, sizeof buf);
+       for(i = 0; t[i]; i++)
+               if(strstr(buf, t[i]))
+                       return 0;
+       return 1;
+}
+
+static int
+forceexcl(int fd)
+{
+       int r;
+       Dir *d;
+
+       d = dirfstat(fd);
+       if(d == nil)
+               return 0;                       /* ignore: assume file removed */
+       if(d->mode & DMEXCL){
+               free(d);
+               return 0;
+       }
+       d->mode |= DMEXCL;
+       d->qid.type |= QTEXCL;
+       r = dirfwstat(fd, d);
+       free(d);
+       if(r == -1)
+               return 0;                       /* ignore unwritable (e.g dump) */
+       close(fd);
+       return -1;
+}
+
+static int
+exopen(char *s)
+{
+       int i, fd;
+
+       for(i = 0; i < Idxto/Idxstep; i++){
+               if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(eopen)){
+                       if(fd != -1 && forceexcl(fd) == -1)
+                               continue;
+                       return fd;
+               }
+               if((fd = create(s, OWRITE|OEXCL, DMTMP|DMEXCL|0600)) >= 0  || bad(ecreate))
+                       return fd;
+               sleep(Idxstep);
+       }
+       werrstr("lock timeout");
+       return -1;
+}
+
+static Message*
+findmessage(Mailbox *, Message *parent, int n)
+{
+       Message *m;
+
+       for(m = parent->part; m; m = m->next)
+               if(!m->digest && n-- == 0)
+                       return m;
+       return 0;
+}
+
+static int
+validmessage(Mailbox *mb, Message *m, int level)
+{
+       if(level){
+               if(m->digest != 0)
+                       goto lose;
+               if(m->fileid <= 1000000ull<<8)
+               if(m->fileid != 0)
+                       goto lose;
+       }else{
+               if(m->digest == 0)
+                       goto lose;
+               if(m->size == 0)
+                       goto lose;
+               if(m->fileid <= 1000000ull<<8)
+                       goto lose;
+               if(mtreefind(mb, m->digest))
+                       goto lose;
+       }
+       return 1;
+lose:
+       eprint("invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid);
+       return 0;
+}
+
+/*
+ * n.b.: we don't insure this is the index version we last read.
+ *
+ * we may overwrite changes.  dualing deletes should sync eventually.
+ * mboxsync should complain about missing messages but
+ * mutable information (which is not in the email itself)
+ * may be lost.
+ */
+int
+wridxfile(Mailbox *mb)
+{
+       char buf[Pathlen + 4];
+       int r, fd;
+       Biobuf b;
+       Dir *d;
+
+       assert(semacquire(&mb->idxsem, 0) != -1);
+       snprint(buf, sizeof buf, "%s.idx", mb->path);
+       iprint("wridxfile %s\n", buf);
+       if((fd = exopen(buf)) == -1){
+               rerrstr(buf, sizeof buf);
+               if(strcmp(buf, "no creates") != 0)
+               if(strstr(buf, "file system read only") == 0)
+                       eprint("wridxfile: %r\n");
+               semrelease(&mb->idxsem, 1);
+               return -1;
+       }
+       seek(fd, 0, 0);
+       Binit(&b, fd, OWRITE);
+       r = pridx(&b, mb);
+       Bterm(&b);
+       d = dirfstat(fd);
+       if(d == 0)
+               sysfatal("dirfstat: %r");
+       mb->qid = d->qid;
+       free(d);
+       close(fd);
+       semrelease(&mb->idxsem, 1);
+       return r;
+}
+
+static int
+nibble(int c)
+{
+       if(c >= '0' && c <= '9')
+               return c - '0';
+       if(c < 0x20)
+               c += 0x20;
+       if(c >= 'a' && c <= 'f')
+               return c - 'a'+10;
+       return 0xff;
+}
+
+static uchar*
+hackdigest(char *s)
+{
+       uchar t[SHA1dlen];
+       int i;
+
+       if(strcmp(s, "-") == 0)
+               return 0;
+       if(strlen(s) != 2*SHA1dlen){
+               eprint("bad digest %s\n", s);
+               return 0;
+       }
+       for(i = 0; i < SHA1dlen; i++)
+               t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]);
+       memmove(s, t, SHA1dlen);
+       return (uchar*)s;
+}
+
+static uvlong
+rdfileid(char *s, int level)
+{
+       char *p;
+       uvlong uv;
+
+       uv = strtoul(s, &p, 0);
+       if((level == 0 && uv < 1000000) || *p != '.')
+               return 0;
+       return uv<<8 | strtoul(p + 1, 0, 10);
+}
+
+static char*
+∫(char *x)
+{
+       if(x && *x)
+               return x;
+       return nil;
+}
+
+/*
+ * strategy:  use top-level avl tree to merge index with
+ * our ideas about the mailbox.  new or old messages
+ * with corrupt index entries are marked Dead.  they
+ * will be cleared out of the mailbox and are kept out
+ * of the index.  when messages are marked Dead, a
+ * reread of the mailbox is forced.
+ *
+ * side note.  if we get a new message while we are
+ * running it is added to the list in order but m->id
+ * looks out-of-order.  this is because m->id must
+ * increase monotonicly.  a new instance of the fs
+ * will result in a different ordering.
+ */
+
+static int
+rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level, int doplumb)
+{
+       char *f[Idxfields + 1], *s;
+       uchar *digest;
+       int n, flags, nparts, good, bad, redux;
+       Message *m, **ll, *l;
+
+       bad = good = redux = 0;
+       ll = &parent->part;
+       nparts = npart;
+       for(; npart != 0 && (s = Brdstr(b, '\n', 1)); npart--){
+               m = 0;
+               digest = 0;
+               n = tokenize(s, f, nelem(f));
+               if(n != Idxfields){
+dead:
+                       eprint("bad index %#A %d %d n=%d\n", digest, level, npart, n);
+                       bad++;
+                       free(s);
+                       if(level)
+                               return -1;
+                       if(m)
+                               m->deleted = Dead;
+                       continue;
+               }
+               digest = hackdigest(f[0]);
+               if(digest == 0 ^ level != 0)
+                       goto dead;
+               if(level == 0)
+                       m = mtreefind(mb, digest);
+               else
+                       m = findmessage(mb, parent, nparts - npart);
+               if(m){
+                       /*
+                        * read in mutable information.
+                        * currently this is only flags
+                        */
+                       redux++;
+                       if(level == 0)
+                               m->deleted &= ~Dmark;
+                       if(m->nparts)
+                       if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1)
+                               goto dead;
+                       ll = &m->next;
+                       idprint("%d seen before %d... %.2ux", level, m->id, m->cstate);
+                       flags = m->flags;
+                       m->flags |= strtoul(f[1], 0, 16);
+                       if(flags != m->flags)
+                               m->cstate |= Cidxstale;
+                       m->cstate |= Cidx;
+                       idprint("→%.2ux\n", m->cstate);
+                       free(s);
+                       // s = 0;
+                       continue;
+               }
+               m = newmessage(parent);
+               idprint("%d new %d %#A\n", level, m->id, digest);
+               m->digest = digest;
+               m->flags = strtoul(f[1], 0, 16);
+               m->fileid = rdfileid(f[2], level);
+               m->lines = atoi(f[3]);
+               m->ffrom = ∫(f[4]);
+               m->from = ∫(f[5]);
+               m->to = ∫(f[6]);
+               m->cc = ∫(f[7]);
+               m->bcc = ∫(f[8]);
+               m->replyto = ∫(f[9]);
+               m->messageid = ∫(f[10]);
+               m->subject = ∫(f[11]);
+               m->sender = ∫(f[12]);
+               m->inreplyto = ∫(f[13]);
+               m->type = newrefs(f[14]);
+               m->disposition = atoi(f[15]);
+               m->size = strtoul(f[16], 0, 0);
+               m->rawbsize = strtoul(f[17], 0, 0);
+               m->ibadchars = strtoul(f[18], 0, 0);
+               m->idxaux = ∫(f[19]);
+               m->nparts = strtoul(f[20], 0, 0);
+               m->cstate &= ~Cidxstale;
+               m->cstate |= Cidx;
+               m->str = s;
+               s = 0;
+               if(!validmessage(mb, m, level))
+                       goto dead;
+               if(level == 0){
+                       mtreeadd(mb, m);
+                       m->inmbox = 1;
+               }
+               cachehash(mb, m);               /* hokey */
+               l = *ll;
+               *ll = m;
+               ll = &m->next;
+               *ll = l;
+               good++;
+
+               if(m->nparts)
+               if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1)
+                       goto dead;
+               if(doplumb && level == 0)
+                       mailplumb(mb, m, 0);
+       }
+       if(level == 0 && bad + redux > 0)
+               iprint("idx: %d %d %d\n", good, bad, redux);
+       if(bad)
+               return -1;
+       return 0;
+}
+
+/* bug: should check time. */
+static int
+qidcmp(int fd, Qid *q)
+{
+       int r;
+       Dir *d;
+       Qid q0;
+
+       d = dirfstat(fd);
+       if(!d)
+               sysfatal("dirfstat: %r");
+       r = 1;
+       if(d->qid.path == q->path)
+       if(d->qid.vers == q->vers)
+               r = 0;
+       q0 = *q;
+       *q = d->qid;
+       free(d);
+       if(q0.path != 0 && r)
+               iprint("qidcmp ... index changed [%ld .. %ld]\n", q0.vers, q->vers);
+       return r;
+}
+
+static int
+verscmp(Biobuf *b, Mailbox *mb)
+{
+       char *s;
+       int n;
+
+       n = -1;
+       if(s = Brdstr(b, '\n', 0))
+               n = strcmp(s, magic);
+       free(s);
+       if(n)
+               return -1;
+       n = -1;
+       if(s = Brdstr(b, '\n', 0))
+               n = mb->idxread(s, mb);
+       free(s);
+       return n;
+}
+
+int
+genericidxread(char *s, Mailbox*)
+{
+       return strcmp(s, mbmagic);
+}
+
+void
+genericidxinvalid(Mailbox *mb)
+{
+       if(mb->d)
+               memset(&mb->d->qid, 0, sizeof mb->d->qid);
+       mb->waketime = time(0);
+}
+
+void
+mark(Mailbox *mb)
+{
+       Message *m;
+
+       for(m = mb->root->part; m != nil; m = m->next)
+               m->deleted |= Dmark;
+}
+
+int
+unmark(Mailbox *mb)
+{
+       int i;
+       Message *m;
+
+       i = 0;
+       for(m = mb->root->part; m != nil; m = m->next)
+               if(m->deleted & Dmark){
+                       i++;
+                       m->deleted &= ~Dmark;   /* let mailbox scan figure this out.  BOTCH?? */
+               }
+       return i;
+}
+
+int
+rdidxfile0(Mailbox *mb, int doplumb)
+{
+       char buf[Pathlen + 4];
+       int r, v;
+       Biobuf *b;
+
+       snprint(buf, sizeof buf, "%s.idx", mb->path);
+       b = Bopen(buf, OREAD);
+       if(b == nil)
+               return -2;
+       if(qidcmp(Bfildes(b), &mb->qid) == 0)
+               r = 0;
+       else if(verscmp(b, mb) == -1)
+               r = -1;
+       else{
+               mark(mb);
+               r = rdidx(b, mb, mb->root, -1, 0, doplumb);
+               v = unmark(mb);
+               if(r == 0 && v > 0)
+                       r = -1;
+       }
+       Bterm(b);
+       return r;
+}
+
+int
+rdidxfile(Mailbox *mb, int doplumb)
+{
+       int r;
+
+       assert(semacquire(&mb->idxsem, 0) > 0);
+       r = rdidxfile0(mb, doplumb);
+       if(r == -1 && mb->idxinvalid)
+               mb->idxinvalid(mb);
+       semrelease(&mb->idxsem, 1);
+       return r;
+}
diff --git a/sys/src/cmd/upas/fs/imap.c b/sys/src/cmd/upas/fs/imap.c
new file mode 100644 (file)
index 0000000..4a3f744
--- /dev/null
@@ -0,0 +1,1271 @@
+/*
+ * todo:
+ * 1.  sync with imap server's flags
+ * 2.  better algorithm for avoiding downloading message list.
+ * 3.  get sender — eating envelope is lots of work!
+ */
+#include "common.h"
+#include <libsec.h>
+#include <auth.h>
+#include "dat.h"
+
+#define        idprint(i, ...) if(i->flags & Fdebug) fprint(2, __VA_ARGS__); else {}
+#pragma varargck argpos        imap4cmd        2
+#pragma varargck       type    "Z"             char*
+#pragma varargck       type    "U"             uvlong
+#pragma varargck       type    "U"             vlong
+
+static char    confused[]      = "confused about fetch response";
+static char    qsep[]          = " \t\r\n";
+static char    Eimap4ctl[]     = "bad imap4 control message";
+
+enum{
+       /* cap */
+       Cnolog  = 1<<0,
+       Ccram   = 1<<1,
+       Cntlm   = 1<<2,
+
+       /* flags */
+       Fssl    = 1<<0,
+       Fdebug  = 1<<1,
+       Fgmail  = 1<<2,
+};
+
+typedef struct {
+       uvlong  uid;
+       ulong   sizes;
+       ulong   dates;
+} Fetchi;
+
+typedef struct Imap Imap;
+struct Imap {
+       long    lastread;
+
+       char    *mbox;
+       /* free this to free the strings below */
+       char    *freep;
+       char    *host;
+       char    *user;
+
+       int     refreshtime;
+       uchar   cap;
+       uchar   flags;
+
+       ulong   tag;
+       ulong   validity;
+       int     nmsg;
+       int     size;
+
+       Fetchi  *f;
+       int     nuid;
+       int     muid;
+
+       Thumbprint *thumb;
+
+       /* open network connection */
+       Biobuf  bin;
+       Biobuf  bout;
+       int     binit;
+       int     fd;
+};
+
+enum
+{
+       Qok = 0,
+       Qquote,
+       Qbackslash,
+};
+
+static int
+needtoquote(Rune r)
+{
+       if(r >= Runeself)
+               return Qquote;
+       if(r <= ' ')
+               return Qquote;
+       if(r == '\\' || r == '"')
+               return Qbackslash;
+       return Qok;
+}
+
+static int
+Zfmt(Fmt *f)
+{
+       char *s, *t;
+       int w, quotes;
+       Rune r;
+
+       s = va_arg(f->args, char*);
+       if(s == 0 || *s == 0)
+               return fmtstrcpy(f, "\"\"");
+
+       quotes = 0;
+       for(t = s; *t; t += w){
+               w = chartorune(&r, t);
+               quotes |= needtoquote(r);
+       }
+       if(quotes == 0)
+               return fmtstrcpy(f, s);
+
+       fmtrune(f, '"');
+       for(t = s; *t; t += w){
+               w = chartorune(&r, t);
+               if(needtoquote(r) == Qbackslash)
+                       fmtrune(f, '\\');
+               fmtrune(f, r);
+       }
+       return fmtrune(f, '"');
+}
+
+static int
+Ufmt(Fmt *f)
+{
+       char buf[20*2 + 2];
+       ulong a, b;
+       uvlong u;
+
+       u = va_arg(f->args, uvlong);
+       if(u == 1)
+               return fmtstrcpy(f, "nil");
+       if(u == 0)
+               return fmtstrcpy(f, "-");
+       a = u>>32;
+       b = u;
+       snprint(buf, sizeof buf, "%lud:%lud", a, b);
+       return fmtstrcpy(f, buf);
+}
+
+static void
+imap4cmd(Imap *imap, char *fmt, ...)
+{
+       char buf[256], *p;
+       va_list va;
+
+       va_start(va, fmt);
+       p = buf + sprint(buf, "9x%lud ", imap->tag);
+       vseprint(p, buf + sizeof buf, fmt, va);
+       va_end(va);
+
+       p = buf + strlen(buf);
+       if(p > buf + sizeof buf - 3)
+               sysfatal("imap4 command too long");
+       idprint(imap, "-> %s\n", buf);
+       strcpy(p, "\r\n");
+       Bwrite(&imap->bout, buf, strlen(buf));
+       Bflush(&imap->bout);
+}
+
+enum {
+       Ok,
+       No,
+       Bad,
+       Bye,
+       Exists,
+       Status,
+       Fetch,
+       Cap,
+       Auth,
+
+       Unknown,
+};
+
+static char *verblist[] = {
+[Ok]   "ok",
+[No]   "no",
+[Bad]  "bad",
+[Bye]  "bye",
+[Exists]       "exists",
+[Status]       "status",
+[Fetch]        "fetch",
+[Cap]  "capability",
+[Auth] "authenticate",
+};
+
+static int
+verbcode(char *verb)
+{
+       int i;
+       char *q;
+
+       if(q = strchr(verb, ' '))
+               *q = '\0';
+       for(i = 0; i < nelem(verblist) - 1; i++)
+               if(strcmp(verblist[i], verb) == 0)
+                       break;
+       if(q)
+               *q = ' ';
+       return i;
+}
+
+static vlong
+mkuid(Imap *i, char *id)
+{
+       vlong v;
+
+       v = (vlong)i->validity<<32;
+       return v | strtoul(id, 0, 10);
+}
+
+static vlong
+xnum(char *s, int a, int b)
+{
+       vlong v;
+
+       if(*s != a)
+               return -1;
+       v = strtoull(s + 1, &s, 10);
+       if(*s != b)
+               return -1;
+       return v;
+}
+
+static struct{
+       char    *flag;
+       int     e;
+} ftab[] = {
+       "Answered",     Fanswered,
+       "\\Deleted",    Fdeleted,
+       "\\Draft",              Fdraft,
+       "\\Flagged",    Fflagged,
+       "\\Recent",     Frecent,
+       "\\Seen",               Fseen,
+       "\\Stored",     Fstored,
+};
+
+static void
+parseflags(Message *m, char *s)
+{
+       char *f[10];
+       int i, j, j0, n;
+
+       n = tokenize(s, f, nelem(f));
+       qsort(f, n, sizeof *f, (int (*)(void*,void*))strcmp);
+       j = 0;
+       for(i = 0; i < n; i++)
+               for(j0 = j;; j++){
+                       if(j == nelem(ftab)){
+                               j = j0;         /* restart search */
+                               break;
+                       }
+                       if(strcmp(f[i], ftab[j].flag) == 0){
+                               m->flags |= ftab[j].e;
+                               break;
+                       }
+               }
+}
+
+/* "17-Jul-1996 02:44:25 -0700" */
+long
+internaltounix(char *s)
+{
+       Tm tm;
+       if(strlen(s) < 20 || s[2] != '-' || s[6] != '-')
+               return -1;
+       s[2] = ' ';
+       s[6] = ' ';
+       if(strtotm(s, &tm) == -1)
+               return -1;
+       return tm2sec(&tm);
+}
+       
+static char*
+qtoken(char *s, char *sep)
+{
+       int quoting;
+       char *t;
+
+       quoting = 0;
+       t = s;  /* s is output string, t is input string */
+       while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){
+               if(*t != '"' && *t  != '(' && *t != ')'){
+                       *s++ = *t++;
+                       continue;
+               }
+               /* *t is a quote */
+               if(!quoting || *t == '('){
+                       quoting++;
+                       t++;
+                       continue;
+               }
+               /* quoting and we're on a quote */
+               if(t[1] != '"'){
+                       /* end of quoted section; absorb closing quote */
+                       t++;
+                       if(quoting > 0)
+                               quoting--;
+                       continue;
+               }
+               /* doubled quote; fold one quote into two */
+               t++;
+               *s++ = *t++;
+       }
+       if(*s != '\0'){
+               *s = '\0';
+               if(t == s)
+                       t++;
+       }
+       return t;
+}
+
+int
+imaptokenize(char *s, char **args, int maxargs)
+{
+       int nargs;
+
+       for(nargs=0; nargs < maxargs; nargs++){
+               while(*s!='\0' && utfrune(qsep, *s)!=nil)
+                       s++;
+               if(*s == '\0')
+                       break;
+               args[nargs] = s;
+               s = qtoken(s, qsep);
+       }
+
+       return nargs;
+}
+
+static char*
+fetchrsp(Imap *imap, char *p, Mailbox *, Message *m)
+{
+       char *f[15], *s, *q;
+       int i, n, a;
+       ulong o, l;
+       uvlong v;
+       static char error[256];
+       extern void msgrealloc(Message*, ulong);
+
+redux:
+       n = imaptokenize(p, f, nelem(f));
+       if(n%2)
+               return confused;
+       for(i = 0; i < n; i += 2){
+               if(strcmp(f[i], "internaldate") == 0){
+                       l = internaltounix(f[i + 1]);
+                       if(l < 418319360)
+                               abort();
+                       if(imap->nuid < imap->muid)
+                               imap->f[imap->nuid].dates = l;
+               }else if(strcmp(f[i], "rfc822.size") == 0){
+                       l = strtoul(f[i + 1], 0, 0);
+                       if(m)
+                               m->size = l;
+                       else if(imap->nuid < imap->muid)
+                               imap->f[imap->nuid].sizes = l;
+               }else if(strcmp(f[i], "uid") == 0){
+                       v = mkuid(imap, f[1]);
+                       if(m)
+                               m->imapuid = v;
+                       if(imap->nuid < imap->muid)
+                               imap->f[imap->nuid].uid = v;
+               }else if(strcmp(f[i], "flags") == 0)
+                       parseflags(m, f[i + 1]);
+               else if(strncmp(f[i], "body[]", 6) == 0){
+                       s = f[i]+6;
+                       o = 0;
+                       if(*s == '<')
+                               o = xnum(s, '<', '>');
+                       if(o == -1)
+                               return confused;
+                       l = xnum(f[i + 1], '{', '}');
+                       a = o + l - m->ibadchars - m->size;
+                       if(a > 0){
+                               assert(imap->flags & Fgmail);
+                               m->size = o + l;
+                               msgrealloc(m, m->size);
+                               m->size -= m->ibadchars;
+                       }
+                       if(Bread(&imap->bin, m->start + o, l) != l){
+                               snprint(error, sizeof error, "read: %r");
+                               return error;
+                       }
+                       if(Bgetc(&imap->bin) == ')'){
+                               while(Bgetc(&imap->bin) != '\n')
+                                       ;
+                               return 0;
+                       }
+                       /* evil */
+                       if(!(p = Brdline(&imap->bin, '\n')))
+                               return 0;
+                       q = p + Blinelen(&imap->bin);
+                       while(q > p && (q[-1] == '\n' || q[-1] == '\r'))
+                               q--;
+                       *q = 0;
+                       lowercase(p);
+                       idprint(imap, "<- %s\n", p);
+
+                       goto redux;
+               }else
+                       return confused;
+       }
+       return 0;
+}
+
+void
+parsecap(Imap *imap, char *s)
+{
+       char *t[32], *p;
+       int n, i;
+
+       s = strdup(s);
+       n = getfields(s, t, nelem(t), 0, " ");
+       for(i = 0; i < n; i++){
+               if(strncmp(t[i], "auth=", 5) == 0){
+                       p = t[i] + 5;
+                       if(strcmp(p, "cram-md5") == 0)
+                               imap->cap |= Ccram;
+                       if(strcmp(p, "ntlm") == 0)
+                               imap->cap |= Cntlm;
+               }else if(strcmp(t[i], "logindisabled") == 0)
+                       imap->cap |= Cnolog;
+       }
+       free(s);
+}
+
+/*
+ *  get imap4 response line.  there might be various
+ *  data or other informational lines mixed in.
+ */
+static char*
+imap4resp0(Imap *imap, Mailbox *mb, Message *m)
+{
+       char *e, *line, *p, *ep, *op, *q, *verb;
+       int n, unexp;
+       static char error[256];
+
+       unexp = 0;
+       while(p = Brdline(&imap->bin, '\n')){
+               ep = p + Blinelen(&imap->bin);
+               while(ep > p && (ep[-1] == '\n' || ep[-1] == '\r'))
+                       *--ep = '\0';
+               idprint(imap, "<- %s\n", p);
+               if(unexp && p[0] != '9' && p[1] != 'x')
+               if(strtoul(p + 2, &p, 10) != imap->tag)
+                       continue;
+               if(p[0] != '+')
+                       lowercase(p);           /* botch */
+
+               switch(p[0]){
+               case '+':                               /* cram challenge */
+                       if(ep - p > 2)
+                               return p + 2;
+                       break;
+               case '*':
+                       if(p[1] != ' ')
+                               continue;
+                       p += 2;
+                       line = p;
+                       n = strtol(p, &p, 10);
+                       if(*p == ' ')
+                               p++;
+                       verb = p;
+       
+                       if(p = strchr(verb, ' '))
+                               p++;
+                       else
+                               p = verb + strlen(verb);
+
+                       switch(verbcode(verb)){
+                       case Bye:
+                               /* early disconnect */
+                               snprint(error, sizeof error, "%s", p);
+                               return error;
+                       case Ok:
+                       case No:
+                       case Bad:
+                               /* human readable text at p; */
+                               break;
+                       case Exists:
+                               imap->nmsg = n;
+                               break;
+                       case Cap:
+                               parsecap(imap, p);
+                               break;
+                       case Status:
+                               /* * status inbox (messages 2 uidvalidity 960164964) */
+                               if(q = strstr(p, "messages"))
+                                       imap->nmsg = strtoul(q + 8, 0, 10);
+                               if(q = strstr(p, "uidvalidity"))
+                                       imap->validity = strtoul(q + 11, 0, 10);
+                               break;
+                       case Fetch:
+                               if(*p == '('){
+                                       p++;
+                                       if(ep[-1] == ')')
+                                               *--ep = 0;
+                               }
+                               if(e = fetchrsp(imap, p, mb, m))
+                                       eprint("imap: fetchrsp: %s\n", e);
+                               imap->nuid++;
+                               break;
+                       case Auth:
+                               break;
+                       }
+                       if(imap->tag == 0)
+                               return line;
+                       break;
+               case '9':               /* response to our message */
+                       op = p;
+                       if(p[1] == 'x' && strtoul(p + 2, &p, 10) == imap->tag){
+                               while(*p == ' ')
+                                       p++;
+                               imap->tag++;
+                               return p;
+                       }
+                       eprint("imap: expected %lud; got %s\n", imap->tag, op);
+                       break;
+               default:
+                       if(imap->flags&Fdebug || *p){
+                               eprint("imap: unexpected line: %s\n", p);
+                               unexp = 1;
+                       }
+               }
+       }
+       snprint(error, sizeof error, "i/o error: %r\n");
+       return error;
+}
+
+static char*
+imap4resp(Imap *i)
+{
+       return imap4resp0(i, 0, 0);
+}
+
+static int
+isokay(char *resp)
+{
+       return cistrncmp(resp, "OK", 2) == 0;
+}
+
+static char*
+findflag(int idx)
+{
+       int i;
+
+       for(i = 0; i < nelem(ftab); i++)
+               if(ftab[i].e == 1<<idx)
+                       return ftab[i].flag;
+       return nil;
+}
+
+static void
+imap4modflags(Mailbox *mb, Message *m, int flags)
+{
+       char buf[128], *p, *e, *fs;
+       int i, f;
+       Imap *imap;
+
+       imap = mb->aux;
+       e = buf + sizeof buf;
+       p = buf;
+       f = flags & ~Frecent;
+       for(i = 0; i < Nflags; i++)
+               if(f & 1<<i && (fs = findflag(i)))
+                       p = seprint(p, e, "%s ", fs);
+       if(p > buf){
+               p[-1] = 0;
+               imap4cmd(imap, "uid store %lud flags (%s)", (ulong)m->imapuid, buf);
+               imap4resp(imap);
+       }
+}
+
+static char*
+imap4cram(Imap *imap)
+{
+       char *s, *p, ch[128], usr[64], rbuf[128], ubuf[128], ebuf[192];
+       int i, n, l;
+
+       fmtinstall('[', encodefmt);
+
+       imap4cmd(imap, "authenticate cram-md5");
+       p = imap4resp(imap);
+       if(p == nil)
+               return "no challenge";
+       l = dec64((uchar*)ch, sizeof ch, p, strlen(p));
+       if(l == -1)
+               return "bad base64";
+       ch[l] = 0;
+       idprint(imap, "challenge [%s]\n", ch);
+
+       if(imap->user == nil)
+               imap->user = getlog();
+       n = auth_respond(ch, l, usr, sizeof usr, rbuf, sizeof rbuf, auth_getkey,
+               "proto=cram role=client server=%q user=%s", imap->host, imap->user);
+       if(n == -1)
+               return "cannot find IMAP password";
+       for(i = 0; i < n; i++)
+               if(rbuf[i] >= 'A' && rbuf[i] <= 'Z')
+                       rbuf[i] += 'a' - 'A';
+       l = snprint(ubuf, sizeof ubuf, "%s %.*s", usr, n, rbuf);
+       idprint(imap, "raw cram [%s]\n", ubuf);
+       snprint(ebuf, sizeof ebuf, "%.*[", l, ubuf);
+
+       imap->tag = 1;
+       idprint(imap, "-> %s\n", ebuf);
+       Bprint(&imap->bout, "%s\r\n", ebuf);
+       Bflush(&imap->bout);
+
+       if(!isokay(s = imap4resp(imap)))
+               return s;
+       return nil;
+}
+
+/*
+ *  authenticate to IMAP4 server using NTLM (untested)
+ * 
+ *  http://davenport.sourceforge.net/ntlm.html#ntlmImapAuthentication
+ *  http://msdn.microsoft.com/en-us/library/cc236621%28PROT.13%29.aspx
+ */
+static uchar*
+psecb(uchar *p, uint o, int n)
+{
+       p[0] = n;
+       p[1] = n>>8;
+       p[2] = n;
+       p[3] = n>>8;
+       p[4] = o;
+       p[5] = o>>8;
+       p[6] = o>>16;
+       p[7] = o>>24;
+       return p+8;
+}
+
+static uchar*
+psecq(uchar *q, char *s, int n)
+{
+       memcpy(q, s, n);
+       return q+n;
+}
+
+static char*
+imap4ntlm(Imap *imap)
+{
+       char *s, ruser[64], enc[256];
+       uchar buf[128], *p, *ep, *q, *eq, *chal;
+       int n;
+       MSchapreply mcr;
+
+       imap4cmd(imap, "authenticate ntlm");
+       imap4resp(imap);
+
+       /* simple NtLmNegotiate blob with NTLM+OEM flags */
+       imap4cmd(imap, "TlRMTVNTUAABAAAAAgIAAA==");
+       s = imap4resp(imap);
+       n = dec64(buf, sizeof buf, s, strlen(s));
+       if(n < 32 || memcmp(buf, "NTLMSSP", 8) != 0)
+               return "bad NtLmChallenge";
+       chal = buf+24;
+
+       if(auth_respond(chal, 8, ruser, sizeof ruser,
+                       &mcr, sizeof mcr, auth_getkey,
+                       "proto=mschap role=client service=imap server=%q user?",
+                       imap->host) < 0)
+               return "auth_respond failed";
+
+       /* prepare NtLmAuthenticate blob */
+
+       memset(buf, sizeof buf, 0);
+       p = buf;
+       ep = p + 8 + 6*8 + 2*4;
+       q = ep;
+       eq = buf + sizeof buf;
+
+
+       memcpy(p, "NTLMSSP", 8);        /* magic */
+       p += 8;
+
+       *p++ = 3;
+       *p++ = 0;
+       *p++ = 0;
+       *p++ = 0;
+
+       p = psecb(p, q-buf, 24);                /* LMresp */
+       q = psecq(q, mcr.LMresp, 24);
+
+       p = psecb(p, q-buf, 24);                /* NTresp */
+       q = psecq(q, mcr.NTresp, 24);
+
+       p = psecb(p, q-buf, 0);         /* realm */
+
+       n = strlen(ruser);
+       p = psecb(p, q-buf, n);         /* user name */
+       q = psecq(q, ruser, n);
+
+       p = psecb(p, q-buf, 0);         /* workstation name */
+       p = psecb(p, q-buf, 0);         /* session key */
+
+       *p++ = 0x02;                    /* flags: oem(2)|ntlm(0x200) */
+       *p++ = 0x02;
+       *p++ = 0;
+       *p++ = 0;
+
+       if(p > ep || q > eq)
+               return "error creating NtLmAuthenticate";
+       enc64(enc, sizeof enc, buf, q-buf);
+
+       imap4cmd(imap, enc);
+       if(!isokay(s = imap4resp(imap)))
+               return s;
+       return nil;
+}
+
+static char*
+imap4passwd(Imap *imap)
+{
+       char *s;
+       UserPasswd *up;
+
+       if(imap->user != nil)
+               up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
+       else
+               up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
+       if(up == nil)
+               return "cannot find IMAP password";
+
+       imap->tag = 1;
+       imap4cmd(imap, "login %Z %Z", up->user, up->passwd);
+       free(up);
+       if(!isokay(s = imap4resp(imap)))
+               return s;
+       return nil;
+}
+
+static char*
+imap4login(Imap *imap)
+{
+       char *e;
+
+       if(imap->cap & Ccram)
+               e = imap4cram(imap);
+       else if(imap->cap & Cntlm)
+               e = imap4ntlm(imap);
+       else
+               e = imap4passwd(imap);
+       if(e)
+               return e;
+       imap4cmd(imap, "select %Z", imap->mbox);
+       if(!isokay(e = imap4resp(imap)))
+               return e;
+       return nil;
+}
+
+static char*
+imaperrstr(char *host, char *port)
+{
+       char err[ERRMAX];
+       static char buf[256];
+
+       err[0] = 0;
+       errstr(err, sizeof err);
+       snprint(buf, sizeof buf, "%s/%s:%s", host, port, err);
+       return buf;
+}
+
+static int
+starttls(Imap *imap, TLSconn *tls)
+{
+       char buf[Pathlen];
+       uchar digest[SHA1dlen];
+       int sfd, fd;
+
+       memset(tls, 0, sizeof *tls);
+       sfd = tlsClient(imap->fd, tls);
+       if(sfd < 0){
+               werrstr("tlsClient: %r");
+               return -1;
+       }
+       if(tls->cert == nil || tls->certlen <= 0){
+               close(sfd);
+               werrstr("server did not provide TLS certificate");
+               return -1;
+       }
+       sha1(tls->cert, tls->certlen, digest, nil);
+       if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
+               close(sfd);
+               werrstr("server certificate %.*H not recognized",
+                       SHA1dlen, digest);
+               return -1;
+       }
+       close(imap->fd);
+       imap->fd = sfd;
+
+       if(imap->flags & Fdebug){
+               snprint(buf, sizeof buf, "%s/ctl", tls->dir);
+               fd = open(buf, OWRITE);
+               fprint(fd, "debug");
+               close(fd);
+       }
+
+       return 1;
+}
+
+static void
+imap4disconnect(Imap *imap)
+{
+       if(imap->binit){
+               Bterm(&imap->bin);
+               Bterm(&imap->bout);
+               imap->binit = 0;
+       }
+       close(imap->fd);
+       imap->fd = -1;
+}
+
+char*
+capabilties(Imap *imap)
+{
+       char * err;
+
+       imap4cmd(imap, "capability");
+       imap4resp(imap);
+       err = imap4resp(imap);
+       if(isokay(err))
+               err = 0;
+       return err;
+}
+
+static char*
+imap4dial(Imap *imap)
+{
+       char *err, *port;
+       TLSconn conn;
+
+       if(imap->fd >= 0){
+               imap4cmd(imap, "noop");
+               if(isokay(imap4resp(imap)))
+                       return nil;
+               imap4disconnect(imap);
+       }
+       if(imap->flags & Fssl)
+               port = "imaps";
+       else
+               port = "imap";
+       if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
+               return imaperrstr(imap->host, port);
+       if(imap->flags & Fssl && starttls(imap, &conn) == -1){
+               err = imaperrstr(imap->host, port);
+               free(conn.cert);
+               imap4disconnect(imap);
+               return err;
+       }
+       assert(imap->binit == 0);
+       Binit(&imap->bin, imap->fd, OREAD);
+       Binit(&imap->bout, imap->fd, OWRITE);
+       imap->binit = 1;
+
+       imap->tag = 0;
+       err = imap4resp(imap);
+       if(!isokay(err))
+               return "error in initial IMAP handshake";
+
+       if((err = capabilties(imap)) || (err = imap4login(imap))){
+               eprint("imap: err is %s\n", err);
+               imap4disconnect(imap);
+               return err;
+       }
+       return nil;
+}
+
+static void
+imap4hangup(Imap *imap)
+{
+       imap4cmd(imap, "logout");
+       imap4resp(imap);
+       imap4disconnect(imap);
+}
+
+/* gmail lies about message sizes */
+static ulong
+gmaildiscount(Message *m, uvlong o, ulong l)
+{
+       if((m->cstate&Cidx) == 0)
+       if(o + l == m->size)
+               return l + 100 + (o + l)/5;
+       return l;
+}
+
+static int
+imap4fetch(Mailbox *mb, Message *m, uvlong o, ulong l)
+{
+       Imap *imap;
+
+       imap = mb->aux;
+       if(imap->flags & Fgmail)
+               l = gmaildiscount(m, o, l);
+       idprint(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)\n", (ulong)m->imapuid, o, l);
+       imap4cmd(imap, "uid fetch %lud (body.peek[]<%llud.%lud>)", (ulong)m->imapuid, o, l);
+       if(!isokay(imap4resp0(imap, mb, m))){
+               eprint("imap: imap fetch failed\n");
+               return -1;
+       }
+       return 0;
+}
+
+static uvlong
+datesec(Imap *imap, int i)
+{
+       int j;
+       uvlong v;
+       Fetchi *f;
+
+       f = imap->f;
+       v = (uvlong)f[i].dates << 8;
+
+       /* shifty; these sequences should be stable. */
+       for(j = i; j-- > 0; )
+               if(f[i].dates != f[j].dates)
+                       break;
+       v |= i - (j + 1);
+       return v;
+}
+
+static void
+markdel(Mailbox *mb, Message *m, int doplumb)
+{
+       if(doplumb)
+               mailplumb(mb, m, 1);
+       m->inmbox = 0;
+       m->deleted = Disappear;
+}
+
+static int
+vcmp(vlong a, vlong b)
+{
+       a -= b;
+       if(a > 0)
+               return 1;
+       if(a < 0)
+               return -1;
+       return 0;
+}
+
+static int
+fetchicmp(Fetchi *f1, Fetchi *f2)
+{
+       return vcmp(f1->uid, f2->uid);
+}
+
+static int
+setsize(Mailbox *, Message *m, Fetchi *f)
+{
+       if(f->sizes >= Maxmsg)
+               return -1;
+//     if(!gmailmbox(mb))
+       return m->size = f->sizes;
+}
+
+static char*
+imap4read(Imap *imap, Mailbox *mb, int doplumb, int *new)
+{
+       char *s;
+       int i, n, c, nnew, ndel;
+       Fetchi *f;
+       Message *m, **ll;
+
+       *new = 0;
+       if(time(0) - imap->lastread < 10)
+               return nil;
+       imap->lastread = time(0);
+       imap4cmd(imap, "status %Z (messages uidvalidity)", imap->mbox);
+       if(!isokay(s = imap4resp(imap)))
+               return s;
+
+       imap->nuid = 0;
+       imap->muid = imap->nmsg;
+       imap->f = erealloc(imap->f, imap->nmsg*sizeof imap->f[0]);
+       f = imap->f;
+       n = imap->nmsg;
+
+       if(imap->nmsg > 0){
+               imap4cmd(imap, "uid fetch 1:* (uid rfc822.size internaldate)");
+               if(!isokay(s = imap4resp(imap)))
+                       return s;
+       }
+
+       qsort(f, n, sizeof f[0], (int(*)(void*, void*))fetchicmp);
+       nnew = ndel = 0;
+       ll = &mb->root->part;
+       for(i = 0; *ll || i < n; ){
+               c = -1;
+               if(i >= n)
+                       c = 1;
+               else if(*ll){
+                       if((*ll)->imapuid == 0)
+                               (*ll)->imapuid = strtoull((*ll)->idxaux, 0, 0);
+                       c = vcmp(f[i].uid, (*ll)->imapuid);
+               }
+               idprint(imap, "consider %U and %U -> %d\n", i<n? f[i].uid: 0, *ll? (*ll)->imapuid: 1, c);
+               if(c < 0){
+                       /* new message */
+                       idprint(imap, "new: %U (%U)\n", f[i].uid, *ll? (*ll)->imapuid: 0);
+                       m = newmessage(mb->root);
+                       m->inmbox = 1;
+                       m->idxaux = smprint("%llud", f[i].uid);
+                       m->imapuid = f[i].uid;
+                       m->fileid = datesec(imap, i);
+                       if(setsize(mb, m, f + i) < 0 || m->size >= Maxmsg){
+                               /* message disappeared?  unchain */
+                               idprint(imap, "deleted → %r (%U)\n", m->imapuid);
+                               logmsg(m, "disappeared");
+                               if(doplumb)
+                                       mailplumb(mb, m, 1); /* redundant */
+                               unnewmessage(mb, mb->root, m);
+                               /* we're out of sync; here's were to signal that */
+                               break;
+                       }
+                       nnew++;
+                       logmsg(m, "new %s", m->idxaux);
+                       m->next = *ll;
+                       *ll = m;
+                       ll = &m->next;
+                       i++;
+                       newcachehash(mb, m, doplumb);
+                       putcache(mb, m);
+               }else if(c > 0){
+                       /* deleted message; */
+                       idprint(imap, "deleted: %U (%U)\n", i<n? f[i].uid: 0, *ll? (*ll)->imapuid: 0);
+                       ndel++;
+                       logmsg(*ll, "deleted");
+                       markdel(mb, *ll, doplumb);
+                       ll = &(*ll)->next;
+               }else{
+                       //logmsg(*ll, "duplicate %s", d[i].name);
+                       i++;
+                       ll = &(*ll)->next;
+               }
+       }
+
+       *new = nnew;
+       return nil;
+}
+
+static void
+imap4delete(Mailbox *mb, Message *m)
+{
+       Imap *imap;
+
+       imap = mb->aux;
+       if((ulong)(m->imapuid>>32) == imap->validity){
+               imap4cmd(imap, "uid store %lud +flags (\\Deleted)", (ulong)m->imapuid);
+               imap4resp(imap);
+               imap4cmd(imap, "expunge");
+               imap4resp(imap);
+//             if(!isokay(imap4resp(imap))
+//                     return -1;
+       }
+       m->inmbox = 0;
+}
+
+static char*
+imap4sync(Mailbox *mb, int doplumb, int *new)
+{
+       char *err;
+       Imap *imap;
+
+       imap = mb->aux;
+       if(err = imap4dial(imap))
+               goto out;
+       if((err = imap4read(imap, mb, doplumb, new)) == nil)
+               mb->d->atime = mb->d->mtime = time(0);
+out:
+       mb->waketime = time(0) + imap->refreshtime;
+       return err;
+}
+
+static char*
+imap4ctl(Mailbox *mb, int argc, char **argv)
+{
+       char *a, *b;
+       Imap *imap;
+
+       imap = mb->aux;
+       if(argc < 1)
+               return Eimap4ctl;
+
+       if(argc == 1 && strcmp(argv[0], "debug") == 0){
+               imap->flags ^= Fdebug;
+               return nil;
+       }
+       if(strcmp(argv[0], "thumbprint") == 0){
+               if(imap->thumb){
+                       freeThumbprints(imap->thumb);
+                       imap->thumb = 0;
+               }
+               a = "/sys/lib/tls/mail";
+               b = "/sys/lib/tls/mail.exclude";
+               switch(argc){
+               default:
+                       return Eimap4ctl;
+               case 4:
+                       b = argv[2];
+               case 3:
+                       a = argv[1];
+               case 2:
+                       break;
+               }
+               imap->thumb = initThumbprints(a, b);
+               return nil;
+       }
+       if(argc == 2 && strcmp(argv[0], "uid") == 0){
+               uvlong l;
+               Message *m;
+
+               for(m = mb->root->part; m; m = m->next)
+                       if(strcmp(argv[1], m->name) == 0){
+                               l = strtoull(m->idxaux, 0, 0);
+                               fprint(2, "uid %s %lud %lud %lud %lud\n", m->name, (ulong)(l>>32), (ulong)l,
+                                       (ulong)(m->imapuid>>32), (ulong)m->imapuid);
+                       }
+               return nil;
+       }
+       if(strcmp(argv[0], "refresh") == 0)
+               switch(argc){
+               case 1:
+                       imap->refreshtime = 60;
+                       return nil;
+               case 2:
+                       imap->refreshtime = atoi(argv[1]);
+                       return nil;
+               }
+
+       return Eimap4ctl;
+}
+
+static void
+imap4close(Mailbox *mb)
+{
+       Imap *imap;
+
+       imap = mb->aux;
+       imap4disconnect(imap);
+       free(imap->f);
+       free(imap);
+}
+
+static char*
+mkmbox(Imap *imap, char *p, char *e)
+{
+       p = seprint(p, e, "%s/box/%s/imap.%s", MAILROOT, getlog(), imap->host);
+       if(imap->user && strcmp(imap->user, getlog()))
+               p = seprint(p, e, ".%s", imap->user);
+       if(cistrcmp(imap->mbox, "inbox"))
+               p = seprint(p, e, ".%s", imap->mbox);
+       return p;
+}
+
+static char*
+findmbox(char *p)
+{
+       char *f[10], path[Pathlen];
+       int nf;
+
+       snprint(path, sizeof path, "%s", p);
+       nf = getfields(path, f, 5, 0, "/");
+       if(nf < 3)
+               return nil;
+       return f[nf - 1];
+}
+
+static char*
+imap4rename(Mailbox *mb, char *p2, int)
+{
+       char *r, *new;
+       Imap *imap;
+
+       imap = mb->aux;
+       new = findmbox(p2);
+       idprint(imap, "rename %s %s\n", imap->mbox, new);
+       imap4cmd(imap, "rename %s %s", imap->mbox, new);
+       r = imap4resp(imap);
+       if(!isokay(r))
+               return r;
+       free(imap->mbox);
+       imap->mbox = smprint("%s", new);
+       mkmbox(imap, mb->path, mb->path + sizeof mb->path);
+       return 0;
+}
+
+/*
+ * incomplete; when we say remove we want to get subfolders, too.
+ * so we need to to a list, and recursivly nuke folders.
+ */
+static char*
+imap4remove(Mailbox *mb, int flags)
+{
+       char *r;
+       Imap *imap;
+
+       imap = mb->aux;
+       idprint(imap, "remove %s\n", imap->mbox);
+       imap4cmd(imap, "delete %s", imap->mbox);
+       r = imap4resp(imap);
+       if(!isokay(r))
+               return r;
+       if(flags & Rtrunc){
+               imap4cmd(imap, "create %s", imap->mbox);
+               r = imap4resp(imap);
+               if(!isokay(r))
+                       return r;
+       }
+       return 0;
+}
+
+char*
+imap4mbox(Mailbox *mb, char *path)
+{
+       char *f[10];
+       uchar flags;
+       int nf;
+       Imap *imap;
+
+       fmtinstall('Z', Zfmt);
+       fmtinstall('U', Ufmt);
+       if(strncmp(path, "/imap/", 6) == 0)
+               flags = 0;
+       else if(strncmp(path, "/imaps/", 7) == 0)
+               flags = Fssl;
+       else
+               return Enotme;
+
+       path = strdup(path);
+       if(path == nil)
+               return "out of memory";
+
+       nf = getfields(path, f, 5, 0, "/");
+       if(nf < 3){
+               free(path);
+               return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
+       }
+
+       imap = emalloc(sizeof *imap);
+       imap->fd = -1;
+       imap->freep = path;
+       imap->flags = flags;
+       imap->host = f[2];
+       if(strstr(imap->host, "gmail.com"))
+               imap->flags |= Fgmail;
+       imap->refreshtime = 60;
+       if(nf < 4)
+               imap->user = nil;
+       else
+               imap->user = f[3];
+       if(nf < 5)
+               imap->mbox = strdup("inbox");
+       else
+               imap->mbox = strdup(f[4]);
+       mkmbox(imap, mb->path, mb->path + sizeof mb->path);
+       if(imap->flags & Fssl)
+               imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
+
+       mb->aux = imap;
+       mb->sync = imap4sync;
+       mb->close = imap4close;
+       mb->ctl = imap4ctl;
+       mb->fetch = imap4fetch;
+       mb->delete = imap4delete;
+       mb->rename = imap4rename;
+//     mb->remove = imap4remove;
+       mb->modflags = imap4modflags;
+       mb->d = emalloc(sizeof *mb->d);
+       mb->addfrom = 1;
+       return nil;
+}
diff --git a/sys/src/cmd/upas/fs/imap4.c b/sys/src/cmd/upas/fs/imap4.c
deleted file mode 100644 (file)
index 06e8fdd..0000000
+++ /dev/null
@@ -1,878 +0,0 @@
-#include "common.h"
-#include <ctype.h>
-#include <plumb.h>
-#include <libsec.h>
-#include <auth.h>
-#include "dat.h"
-
-#pragma varargck argpos imap4cmd 2
-#pragma varargck       type    "Z"     char*
-
-int    doublequote(Fmt*);
-
-// if pipeline == 1 and upas/fs is used with dovecot,
-// 9Xn OK responses sometimes come much later after FETCH responses, i.e.
-// <- * 1 FETCH ...
-// <- * 2 FETCH ...
-// <- * 3 FETCH ...
-// <- 9X5 OK Fetch completed.
-// <- 9X6 OK Fetch completed.
-// download 40: did not get message body
-// <- 9X7 OK Fetch completed.
-// causing multiple messages to turn into one in imap4.c:/^imap4resp.
-int    pipeline = 0;
-
-static char Eio[] = "i/o error";
-
-typedef struct Imap Imap;
-struct Imap {
-       char *freep;    // free this to free the strings below
-
-       char *host;
-       char *user;
-       char *mbox;
-
-       int mustssl;
-       int refreshtime;
-       int debug;
-
-       ulong tag;
-       ulong validity;
-       int nmsg;
-       int size;
-       char *base;
-       char *data;
-
-       vlong *uid;
-       int nuid;
-       int muid;
-
-       Thumbprint *thumb;
-
-       // open network connection
-       Biobuf bin;
-       Biobuf bout;
-       int fd;
-};
-
-static char*
-removecr(char *s)
-{
-       char *r, *w;
-
-       for(r=w=s; *r; r++)
-               if(*r != '\r')
-                       *w++ = *r;
-       *w = '\0';
-       return s;
-}
-
-//
-// send imap4 command
-//
-static void
-imap4cmd(Imap *imap, char *fmt, ...)
-{
-       char buf[128], *p;
-       va_list va;
-
-       va_start(va, fmt);
-       p = buf+sprint(buf, "9X%lud ", imap->tag);
-       vseprint(p, buf+sizeof(buf), fmt, va);
-       va_end(va);
-
-       p = buf+strlen(buf);
-       if(p > (buf+sizeof(buf)-3))
-               sysfatal("imap4 command too long");
-
-       if(imap->debug)
-               fprint(2, "-> %s\n", buf);
-       strcpy(p, "\r\n");
-       Bwrite(&imap->bout, buf, strlen(buf));
-       Bflush(&imap->bout);
-}
-
-enum {
-       OK,
-       NO,
-       BAD,
-       BYE,
-       EXISTS,
-       STATUS,
-       FETCH,
-       UNKNOWN,
-};
-
-static char *verblist[] = {
-[OK]           "OK",
-[NO]           "NO",
-[BAD]  "BAD",
-[BYE]  "BYE",
-[EXISTS]       "EXISTS",
-[STATUS]       "STATUS",
-[FETCH]        "FETCH",
-};
-
-static int
-verbcode(char *verb)
-{
-       int i;
-       char *q;
-
-       if(q = strchr(verb, ' '))
-               *q = '\0';
-
-       for(i=0; i<nelem(verblist); i++)
-               if(verblist[i] && strcmp(verblist[i], verb)==0){
-                       if(q)
-                               *q = ' ';
-                       return i;
-               }
-       if(q)
-               *q = ' ';
-       return UNKNOWN;
-}
-
-static void
-strupr(char *s)
-{
-       for(; *s; s++)
-               if('a' <= *s && *s <= 'z')
-                       *s += 'A'-'a';
-}
-
-static void
-imapgrow(Imap *imap, int n)
-{
-       int i;
-
-       if(imap->data == nil){
-               imap->base = emalloc(n+1);      
-               imap->data = imap->base;
-               imap->size = n+1;
-       }
-       if(n >= imap->size){
-               // friggin microsoft - reallocate
-               i = imap->data - imap->base;
-               imap->base = erealloc(imap->base, i+n+1);
-               imap->data = imap->base + i;
-               imap->size = n+1;
-       }
-}
-
-
-//
-// get imap4 response line.  there might be various 
-// data or other informational lines mixed in.
-//
-static char*
-imap4resp(Imap *imap)
-{
-       char *line, *p, *ep, *op, *q, *r, *en, *verb;
-       int i, n;
-       static char error[256];
-
-       while(p = Brdline(&imap->bin, '\n')){
-               ep = p+Blinelen(&imap->bin);
-               while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
-                       *--ep = '\0';
-               
-               if(imap->debug)
-                       fprint(2, "<- %s\n", p);
-               strupr(p);
-
-               switch(p[0]){
-               case '+':
-                       if(imap->tag == 0)
-                               fprint(2, "unexpected: %s\n", p);
-                       break;
-
-               // ``unsolicited'' information; everything happens here.
-               case '*':
-                       if(p[1]!=' ')
-                               continue;
-                       p += 2;
-                       line = p;
-                       n = strtol(p, &p, 10);
-                       if(*p==' ')
-                               p++;
-                       verb = p;
-                       
-                       if(p = strchr(verb, ' '))
-                               p++;
-                       else
-                               p = verb+strlen(verb);
-
-                       switch(verbcode(verb)){
-                       case OK:
-                       case NO:
-                       case BAD:
-                               // human readable text at p;
-                               break;
-                       case BYE:
-                               // early disconnect
-                               // human readable text at p;
-                               break;
-
-                       // * 32 EXISTS
-                       case EXISTS:
-                               imap->nmsg = n;
-                               break;
-
-                       // * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
-                       case STATUS:
-                               if(q = strstr(p, "MESSAGES"))
-                                       imap->nmsg = atoi(q+8);
-                               if(q = strstr(p, "UIDVALIDITY"))
-                                       imap->validity = strtoul(q+11, 0, 10);
-                               break;
-
-                       case FETCH:
-                               // * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
-                               // <3031 bytes of data>
-                               // )
-                               if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
-                                       if((q = strchr(p, '{')) 
-                                       && (n=strtol(q+1, &en, 0), *en=='}')){
-                                               if(imap->data == nil || n >= imap->size)
-                                                       imapgrow(imap, n);
-                                               if((i = Bread(&imap->bin, imap->data, n)) != n){
-                                                       snprint(error, sizeof error,
-                                                               "short read %d != %d: %r\n",
-                                                               i, n);
-                                                       return error;
-                                               }
-                                               if(imap->debug)
-                                                       fprint(2, "<- read %d bytes\n", n);
-                                               imap->data[n] = '\0';
-                                               if(imap->debug)
-                                                       fprint(2, "<- %s\n", imap->data);
-                                               imap->data += n;
-                                               imap->size -= n;
-                                               p = Brdline(&imap->bin, '\n');
-                                               if(imap->debug)
-                                                       fprint(2, "<- ignoring %.*s\n",
-                                                               Blinelen(&imap->bin), p);
-                                       }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
-                                               *r = '\0';
-                                               q++;
-                                               n = r-q;
-                                               if(imap->data == nil || n >= imap->size)
-                                                       imapgrow(imap, n);
-                                               memmove(imap->data, q, n);
-                                               imap->data[n] = '\0';
-                                               imap->data += n;
-                                               imap->size -= n;
-                                       }else
-                                               return "confused about FETCH response";
-                                       break;
-                               }
-
-                               // * 1 FETCH (UID 1 RFC822.SIZE 511)
-                               if(q=strstr(p, "RFC822.SIZE")){
-                                       imap->size = atoi(q+11);
-                                       break;
-                               }
-
-                               // * 1 FETCH (UID 1 RFC822.HEADER {496}
-                               // <496 bytes of data>
-                               // )
-                               // * 1 FETCH (UID 1 RFC822.HEADER "data")
-                               if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
-                                       if((q = strchr(p, '{')) 
-                                       && (n=strtol(q+1, &en, 0), *en=='}')){
-                                               if(imap->data == nil || n >= imap->size)
-                                                       imapgrow(imap, n);
-                                               if((i = Bread(&imap->bin, imap->data, n)) != n){
-                                                       snprint(error, sizeof error,
-                                                               "short read %d != %d: %r\n",
-                                                               i, n);
-                                                       return error;
-                                               }
-                                               if(imap->debug)
-                                                       fprint(2, "<- read %d bytes\n", n);
-                                               imap->data[n] = '\0';
-                                               if(imap->debug)
-                                                       fprint(2, "<- %s\n", imap->data);
-                                               imap->data += n;
-                                               imap->size -= n;
-                                               p = Brdline(&imap->bin, '\n');
-                                               if(imap->debug)
-                                                       fprint(2, "<- ignoring %.*s\n",
-                                                               Blinelen(&imap->bin), p);
-                                       }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
-                                               *r = '\0';
-                                               q++;
-                                               n = r-q;
-                                               if(imap->data == nil || n >= imap->size)
-                                                       imapgrow(imap, n);
-                                               memmove(imap->data, q, n);
-                                               imap->data[n] = '\0';
-                                               imap->data += n;
-                                               imap->size -= n;
-                                       }else
-                                               return "confused about FETCH response";
-                                       break;
-                               }
-
-                               // * 1 FETCH (UID 1)
-                               // * 2 FETCH (UID 6)
-                               if(q = strstr(p, "UID")){
-                                       if(imap->nuid < imap->muid)
-                                               imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
-                                       break;
-                               }
-                       }
-
-                       if(imap->tag == 0)
-                               return line;
-                       break;
-
-               case '9':               // response to our message
-                       op = p;
-                       if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
-                               while(*p==' ')
-                                       p++;
-                               imap->tag++;
-                               return p;
-                       }
-                       fprint(2, "expected %lud; got %s\n", imap->tag, op);
-                       break;
-
-               default:
-                       if(imap->debug || *p)
-                               fprint(2, "unexpected line: %s\n", p);
-               }
-       }
-       snprint(error, sizeof error, "i/o error: %r\n");
-       return error;
-}
-
-static int
-isokay(char *resp)
-{
-       return strncmp(resp, "OK", 2)==0;
-}
-
-//
-// log in to IMAP4 server, select mailbox, no SSL at the moment
-//
-static char*
-imap4login(Imap *imap)
-{
-       char *s;
-       UserPasswd *up;
-
-       imap->tag = 0;
-       s = imap4resp(imap);
-       if(!isokay(s))
-               return "error in initial IMAP handshake";
-
-       if(imap->user != nil)
-               up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
-       else
-               up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
-       if(up == nil)
-               return "cannot find IMAP password";
-
-       imap->tag = 1;
-       imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
-       free(up);
-       if(!isokay(s = imap4resp(imap)))
-               return s;
-
-       imap4cmd(imap, "SELECT %Z", imap->mbox);
-       if(!isokay(s = imap4resp(imap)))
-               return s;
-
-       return nil;
-}
-
-static char*
-imaperrstr(char *host, char *port)
-{
-       /*
-        * make mess big enough to hold a TLS certificate fingerprint
-        * plus quite a bit of slop.
-        */
-       static char mess[3 * Errlen];
-       char err[Errlen];
-
-       err[0] = '\0';
-       errstr(err, sizeof(err));
-       snprint(mess, sizeof(mess), "%s/%s:%s", host, port, err);
-       return mess;
-}
-
-static int
-starttls(Imap *imap)
-{
-       int sfd;
-       uchar digest[SHA1dlen];
-       TLSconn conn;
-
-       memset(&conn, 0, sizeof(conn));
-       sfd = tlsClient(imap->fd, &conn);
-       if(sfd < 0) {
-               werrstr("tlsClient: %r");
-               return -1;
-       }
-       imap->fd = sfd;
-       free(conn.sessionID);
-       if(conn.cert==nil || conn.certlen <= 0) {
-               werrstr("server did not provide TLS certificate");
-               return -1;
-       }
-       sha1(conn.cert, conn.certlen, digest, nil);
-       free(conn.cert);
-       if(!imap->thumb || !okThumbprint(digest, imap->thumb)){
-               fmtinstall('H', encodefmt);
-               werrstr("server certificate %.*H not recognized",
-                       SHA1dlen, digest);
-               return -1;
-       }
-       return sfd;
-}
-
-//
-// dial and handshake with the imap server
-//
-static char*
-imap4dial(Imap *imap)
-{
-       char *err, *port;
-
-       if(imap->fd >= 0){
-               imap4cmd(imap, "noop");
-               if(isokay(imap4resp(imap)))
-                       return nil;
-               close(imap->fd);
-               imap->fd = -1;
-       }
-
-       if(imap->mustssl)
-               port = "imaps";
-       else
-               port = "imap";
-
-       if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
-               return imaperrstr(imap->host, port);
-
-       if(imap->mustssl){
-               if(starttls(imap) < 0){
-                       err = imaperrstr(imap->host, port);
-                       goto Out;
-               }
-       }
-       Binit(&imap->bin, imap->fd, OREAD);
-       Binit(&imap->bout, imap->fd, OWRITE);
-       err = imap4login(imap);
-Out:
-       if(err != nil){
-               if(imap->fd >= 0){
-                       close(imap->fd);
-                       imap->fd = -1;
-               }
-       }
-       return err;
-}
-
-//
-// close connection
-//
-static void
-imap4hangup(Imap *imap)
-{
-       if(imap->fd < 0)
-               return;
-       imap4cmd(imap, "LOGOUT");
-       imap4resp(imap);
-       close(imap->fd);
-       imap->fd = -1;
-}
-
-//
-// download a single message
-//
-static char*
-imap4fetch(Mailbox *mb, Message *m)
-{
-       int i;
-       char *p, *s, sdigest[2*SHA1dlen+1];
-       Imap *imap;
-
-       imap = mb->aux;
-
-       imap->size = 0;
-
-       if(!isokay(s = imap4resp(imap)))
-               return s;
-
-       p = imap->base;
-       if(p == nil)
-               return "did not get message body";
-
-       removecr(p);
-       free(m->start);
-       m->start = p;
-       m->end = p+strlen(p);
-       m->bend = m->rbend = m->end;
-       m->header = m->start;
-
-       imap->base = nil;
-       imap->data = nil;
-
-       parse(m, 0, mb, 1);
-
-       // digest headers
-       sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
-       for(i = 0; i < SHA1dlen; i++)
-               sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
-       m->sdigest = s_copy(sdigest);
-
-       return nil;
-}
-
-//
-// check for new messages on imap4 server
-// download new messages, mark deleted messages
-//
-static char*
-imap4read(Imap *imap, Mailbox *mb, int doplumb)
-{
-       char *s;
-       int i, ignore, nnew, t;
-       Message *m, *next, **l;
-
-       imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
-       if(!isokay(s = imap4resp(imap)))
-               return s;
-
-       imap->nuid = 0;
-       imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
-       imap->muid = imap->nmsg;
-
-       if(imap->nmsg > 0){
-               imap4cmd(imap, "UID FETCH 1:* UID");
-               if(!isokay(s = imap4resp(imap)))
-                       return s;
-       }
-
-       l = &mb->root->part;
-       for(i=0; i<imap->nuid; i++){
-               ignore = 0;
-               while(*l != nil){
-                       if((*l)->imapuid == imap->uid[i]){
-                               ignore = 1;
-                               l = &(*l)->next;
-                               break;
-                       }else{
-                               // old mail, we don't have it anymore
-                               if(doplumb)
-                                       mailplumb(mb, *l, 1);
-                               (*l)->inmbox = 0;
-                               (*l)->deleted = 1;
-                               l = &(*l)->next;
-                       }
-               }
-               if(ignore)
-                       continue;
-
-               // new message
-               m = newmessage(mb->root);
-               m->mallocd = 1;
-               m->inmbox = 1;
-               m->imapuid = imap->uid[i];
-
-               // add to chain, will download soon
-               *l = m;
-               l = &m->next;
-       }
-
-       // whatever is left at the end of the chain is gone
-       while(*l != nil){
-               if(doplumb)
-                       mailplumb(mb, *l, 1);
-               (*l)->inmbox = 0;
-               (*l)->deleted = 1;
-               l = &(*l)->next;
-       }
-
-       // download new messages
-       t = imap->tag;
-       if(pipeline)
-       switch(rfork(RFPROC|RFMEM)){
-       case -1:
-               sysfatal("rfork: %r");
-       default:
-               break;
-       case 0:
-               for(m = mb->root->part; m != nil; m = m->next){
-                       if(m->start != nil)
-                               continue;
-                       if(imap->debug)
-                               fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
-                                       t, (ulong)m->imapuid);
-                       Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
-                               t++, (ulong)m->imapuid);
-               }
-               Bflush(&imap->bout);
-               _exits(nil);
-       }
-
-       nnew = 0;
-       for(m=mb->root->part; m!=nil; m=next){
-               next = m->next;
-               if(m->start != nil)
-                       continue;
-
-               if(!pipeline){
-                       Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
-                               (ulong)imap->tag, (ulong)m->imapuid);
-                       Bflush(&imap->bout);
-               }
-
-               if(s = imap4fetch(mb, m)){
-                       // message disappeared?  unchain
-                       fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
-                       delmessage(mb, m);
-                       mb->root->subname--;
-                       continue;
-               }
-               nnew++;
-               if(doplumb)
-                       mailplumb(mb, m, 0);
-       }
-       if(pipeline)
-               waitpid();
-
-       if(nnew || mb->vers == 0){
-               mb->vers++;
-               henter(PATH(0, Qtop), mb->name,
-                       (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
-       }
-       return nil;
-}
-
-//
-// sync mailbox
-//
-static void
-imap4purge(Imap *imap, Mailbox *mb)
-{
-       int ndel;
-       Message *m, *next;
-
-       ndel = 0;
-       for(m=mb->root->part; m!=nil; m=next){
-               next = m->next;
-               if(m->deleted && m->refs==0){
-                       if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
-                               imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
-                               if(isokay(imap4resp(imap))){
-                                       ndel++;
-                                       delmessage(mb, m);
-                               }
-                       }else
-                               delmessage(mb, m);
-               }
-       }
-
-       if(ndel){
-               imap4cmd(imap, "EXPUNGE");
-               imap4resp(imap);
-       }
-}
-
-//
-// connect to imap4 server, sync mailbox
-//
-static char*
-imap4sync(Mailbox *mb, int doplumb)
-{
-       char *err;
-       Imap *imap;
-
-       imap = mb->aux;
-
-       if(err = imap4dial(imap)){
-               mb->waketime = time(0) + imap->refreshtime;
-               return err;
-       }
-
-       if((err = imap4read(imap, mb, doplumb)) == nil){
-               imap4purge(imap, mb);
-               mb->d->atime = mb->d->mtime = time(0);
-       }
-       /*
-        * don't hang up; leave connection open for next time.
-        */
-       // imap4hangup(imap);
-       mb->waketime = time(0) + imap->refreshtime;
-       return err;
-}
-
-static char Eimap4ctl[] = "bad imap4 control message";
-
-static char*
-imap4ctl(Mailbox *mb, int argc, char **argv)
-{
-       int n;
-       Imap *imap;
-
-       imap = mb->aux;
-       if(argc < 1)
-               return Eimap4ctl;
-
-       if(argc==1 && strcmp(argv[0], "debug")==0){
-               imap->debug = 1;
-               return nil;
-       }
-
-       if(argc==1 && strcmp(argv[0], "nodebug")==0){
-               imap->debug = 0;
-               return nil;
-       }
-
-       if(argc==1 && strcmp(argv[0], "thumbprint")==0){
-               if(imap->thumb)
-                       freeThumbprints(imap->thumb);
-               imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
-       }
-       if(strcmp(argv[0], "refresh")==0){
-               if(argc==1){
-                       imap->refreshtime = 60;
-                       return nil;
-               }
-               if(argc==2){
-                       n = atoi(argv[1]);
-                       if(n < 15)
-                               return Eimap4ctl;
-                       imap->refreshtime = n;
-                       return nil;
-               }
-       }
-
-       return Eimap4ctl;
-}
-
-//
-// free extra memory associated with mb
-//
-static void
-imap4close(Mailbox *mb)
-{
-       Imap *imap;
-
-       imap = mb->aux;
-       free(imap->freep);
-       free(imap->base);
-       free(imap->uid);
-       if(imap->fd >= 0)
-               close(imap->fd);
-       free(imap);
-}
-
-//
-// open mailboxes of the form /imap/host/user
-//
-char*
-imap4mbox(Mailbox *mb, char *path)
-{
-       char *f[10];
-       int mustssl, nf;
-       Imap *imap;
-
-       quotefmtinstall();
-       fmtinstall('Z', doublequote);
-       if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
-               return Enotme;
-       mustssl = (strncmp(path, "/imaps/", 7) == 0);
-
-       path = strdup(path);
-       if(path == nil)
-               return "out of memory";
-
-       nf = getfields(path, f, 5, 0, "/");
-       if(nf < 3){
-               free(path);
-               return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
-       }
-
-       imap = emalloc(sizeof(*imap));
-       imap->fd = -1;
-       imap->debug = debug;
-       imap->freep = path;
-       imap->mustssl = mustssl;
-       imap->host = f[2];
-       if(nf < 4)
-               imap->user = nil;
-       else
-               imap->user = f[3];
-       if(nf < 5)
-               imap->mbox = "Inbox";
-       else
-               imap->mbox = f[4];
-       imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
-
-       mb->aux = imap;
-       mb->sync = imap4sync;
-       mb->close = imap4close;
-       mb->ctl = imap4ctl;
-       mb->d = emalloc(sizeof(*mb->d));
-       //mb->fetch = imap4fetch;
-
-       return nil;
-}
-
-//
-// Formatter for %"
-// Use double quotes to protect white space, frogs, \ and "
-//
-enum
-{
-       Qok = 0,
-       Qquote,
-       Qbackslash,
-};
-
-static int
-needtoquote(Rune r)
-{
-       if(r >= Runeself)
-               return Qquote;
-       if(r <= ' ')
-               return Qquote;
-       if(r=='\\' || r=='"')
-               return Qbackslash;
-       return Qok;
-}
-
-int
-doublequote(Fmt *f)
-{
-       char *s, *t;
-       int w, quotes;
-       Rune r;
-
-       s = va_arg(f->args, char*);
-       if(s == nil || *s == '\0')
-               return fmtstrcpy(f, "\"\"");
-
-       quotes = 0;
-       for(t=s; *t; t+=w){
-               w = chartorune(&r, t);
-               quotes |= needtoquote(r);
-       }
-       if(quotes == 0)
-               return fmtstrcpy(f, s);
-
-       fmtrune(f, '"');
-       for(t=s; *t; t+=w){
-               w = chartorune(&r, t);
-               if(needtoquote(r) == Qbackslash)
-                       fmtrune(f, '\\');
-               fmtrune(f, r);
-       }
-       return fmtrune(f, '"');
-}
index 3cd14e6cd8b0a241dfa58ee2ba92c203b623431e..3c2c1fb535810c5bf71a596ee8f3a15ea10e2c44 100644 (file)
 #include "dat.h"
 
 typedef struct Header Header;
-
 struct Header {
-       char *type;
-       void (*f)(Message*, Header*, char*);
-       int len;
+       char    *type;
+       uintptr offset;
+       char    *(*f)(Message*, Header*, char*, char*);
+       int     len;
+       int     str;
 };
 
 /* headers */
-static void    ctype(Message*, Header*, char*);
-static void    cencoding(Message*, Header*, char*);
-static void    cdisposition(Message*, Header*, char*);
-static void    date822(Message*, Header*, char*);
-static void    from822(Message*, Header*, char*);
-static void    to822(Message*, Header*, char*);
-static void    sender822(Message*, Header*, char*);
-static void    replyto822(Message*, Header*, char*);
-static void    subject822(Message*, Header*, char*);
-static void    inreplyto822(Message*, Header*, char*);
-static void    cc822(Message*, Header*, char*);
-static void    bcc822(Message*, Header*, char*);
-static void    messageid822(Message*, Header*, char*);
-static void    mimeversion(Message*, Header*, char*);
-static void    nullsqueeze(Message*);
-enum
-{
-       Mhead=  11,     /* offset of first mime header */
-};
+static char    *ctype(Message*, Header*, char*, char*);
+static char    *cencoding(Message*, Header*, char*, char*);
+static char    *cdisposition(Message*, Header*, char*, char*);
+static char    *from822(Message*, Header*, char*, char*);
+static char    *replace822(Message*, Header*, char*, char*);
+static char    *concat822(Message*, Header*, char*, char*);
+static char    *copy822(Message*, Header*, char*, char*);
+static char    *ref822(Message*, Header*, char*, char*);
 
-Header head[] =
+enum
 {
-       { "date:", date822, },
-       { "from:", from822, },
-       { "to:", to822, },
-       { "sender:", sender822, },
-       { "reply-to:", replyto822, },
-       { "subject:", subject822, },
-       { "cc:", cc822, },
-       { "bcc:", bcc822, },
-       { "in-reply-to:", inreplyto822, },
-       { "mime-version:", mimeversion, },
-       { "message-id:", messageid822, },
-
-[Mhead]        { "content-type:", ctype, },
-       { "content-transfer-encoding:", cencoding, },
-       { "content-disposition:", cdisposition, },
-       { 0, },
+       Mhead   = 11,   /* offset of first mime header */
 };
 
-static void    fatal(char *fmt, ...);
-static void    initquoted(void);
-static void    startheader(Message*);
-static void    startbody(Message*);
-static char*   skipwhite(char*);
-static char*   skiptosemi(char*);
-static char*   getstring(char*, String*, int);
-static void    setfilename(Message*, char*);
-static char*   lowercase(char*);
-static int     is8bit(Message*);
-static int     headerline(char**, String*);
-static void    initheaders(void);
-static void    parseattachments(Message*, Mailbox*);
-
-int            debug;
-
-char *Enotme = "path not served by this file server";
-
-enum
+#define O(x)   offsetof(Message, x)
+static Header head[] =
 {
-       Chunksize = 1024,
+       "date:",                O(date822),     copy822,                0,      0,
+       "from:",                O(from),                from822,        0,      1,
+       "to:",          O(to),          concat822,      0,      1,
+       "sender:",      O(sender),      replace822,     0,      1,
+       "reply-to:",    O(replyto),     replace822,     0,      1,
+       "subject:",     O(subject),     copy822,                0,      1,
+       "cc:",          O(cc),          concat822,      0,      1,
+       "bcc:",         O(bcc),         concat822,      0,      1,
+       "in-reply-to:", O(inreplyto),   replace822,     0,      1,
+       "message-id:",  O(messageid),   replace822,     0,      1,
+       "references:",  ~0,             ref822,         0,      0,
+
+[Mhead]        "content-type:",        ~0,             ctype,          0,      0,
+       "content-transfer-encoding:", ~0,       cencoding,      0,      0,
+       "content-disposition:", ~0,             cdisposition,   0,      0,
 };
 
-Mailboxinit *boxinit[] = {
+static Mailboxinit *boxinit[] = {
        imap4mbox,
        pop3mbox,
-       planbmbox,
-       planbvmbox,
+       mdirmbox,
+//     planbmbox,
        plan9mbox,
 };
 
+/*
+ * do we want to plumb flag changes?
+ */
 char*
 syncmbox(Mailbox *mb, int doplumb)
 {
-       return (*mb->sync)(mb, doplumb);
+       char *s;
+       int n, d, y, a;
+       Message *m, *next;
+
+       if(semacquire(&mb->syncsem, 0) <= 0)
+               return nil;
+       a = mb->root->subname;
+       if(rdidxfile(mb, doplumb) == -2)
+               wridxfile(mb);
+       if(s = mb->sync(mb, doplumb, &n)){
+               semrelease(&mb->syncsem, 1);
+               return s;
+       }
+       d = 0;
+       y = 0;
+       for(m = mb->root->part; m; m = next){
+               next = m->next;
+               if(m->cstate & Cidxstale)
+                       y++;
+               if(m->deleted == 0 || m->refs > 0)
+                       continue;
+               if(mb->delete && m->inmbox && m->deleted & Deleted)
+                       mb->delete(mb, m);
+               if(!m->inmbox){
+                       delmessage(mb, m);
+                       d++;
+               }
+       }
+       a = mb->root->subname - a;
+       assert(a >= 0);
+       if(n + d + y + a){
+               iprint("deleted: %d; new %d; stale %d\n", d, n, y);
+               logmsg(nil, "deleted: %d; new %d; stale %d", d, n, y);
+               wridxfile(mb);
+       }
+       if(n + d + y + a){
+               mb->vers++;
+               henter(PATH(0, Qtop), mb->name,
+                       (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+       }
+       semrelease(&mb->syncsem, 1);
+       return nil;
 }
 
-/* create a new mailbox */
+/*
+ * not entirely clear where the locking should take place, if
+ * it is required.
+ */
 char*
-newmbox(char *path, char *name, int std)
+mboxrename(char *a, char *b, int flags)
+{
+       char f0[Pathlen + 4], f1[Pathlen + 4], *err, *p0, *p1;
+       Mailbox *mb;
+
+       snprint(f0, sizeof f0, "%s", a);
+       snprint(f1, sizeof f1, "%s", b);
+       err = newmbox(f0, nil, 0, &mb);
+       dprint("mboxrename %s %s -> %s\n", f0, f1, err);
+       if(!err && !mb->rename)
+               err = "rename not supported";
+       if(err)
+               goto done;
+       err = mb->rename(mb, f1, flags);
+       if(err)
+               goto done;
+       if(flags & Rtrunc)
+               /* we're comitted, so forget bailing */
+               err = newmbox(f0, nil, DMcreate, 0);
+       p0 = f0 + strlen(f0);
+       p1 = f1 + strlen(f1);
+
+       strcat(f0, ".idx");
+       strcat(f1, ".idx");
+       rename(f0, f1, 0);
+
+       *p0 = *p1 = 0;
+       strcat(f0, ".imp");
+       strcat(f1, ".imp");
+       rename(f0, f1, 0);
+
+       snprint(mb->path, sizeof mb->path, "%s", b);
+       hfree(PATH(0, Qtop), mb->name);
+       p0 = strrchr(mb->path, '/') + 1;
+       if(p0 == (char*)1)
+               p0 = mb->path;
+       snprint(mb->name, sizeof mb->name, "%s", p0);
+       henter(PATH(0, Qtop), mb->name,
+               (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+done:
+       if(!mb)
+               return err;
+       qunlock(mb);
+//     if(err)
+//             mboxdecref(mb);
+       return err;
+}
+
+static void
+initheaders(void)
+{
+       int i;
+       static int already;
+
+       if(already)
+               return;
+       already = 1;
+       for(i = 0; i < nelem(head); i++)
+               head[i].len = strlen(head[i].type);
+}
+
+char*
+newmbox(char *path, char *name, int flags, Mailbox **r)
 {
-       Mailbox *mb, **l;
        char *p, *rv;
        int i;
+       Mailbox *mb, **l;
 
        initheaders();
-
-       mb = emalloc(sizeof(*mb));
-       strncpy(mb->path, path, sizeof(mb->path)-1);
-       if(name == nil){
+       mb = emalloc(sizeof *mb);
+       mb->idxsem = 1;
+       mb->syncsem = 1;
+       mb->flags = flags;
+       strncpy(mb->path, path, sizeof mb->path - 1);
+       p = name;
+       if(p == nil){
                p = strrchr(path, '/');
                if(p == nil)
                        p = path;
@@ -112,401 +196,539 @@ newmbox(char *path, char *name, int std)
                        free(mb);
                        return "bad mbox name";
                }
-               strncpy(mb->name, p, sizeof(mb->name)-1);
-       } else {
-               strncpy(mb->name, name, sizeof(mb->name)-1);
        }
-
-       rv = nil;
-       // check for a mailbox type
-       for(i=0; i<nelem(boxinit); i++)
-               if((rv = (*boxinit[i])(mb, path)) != Enotme)
+       strncpy(mb->name, p, sizeof mb->name - 1);
+       mb->idxread = genericidxread;
+       mb->idxwrite = genericidxwrite;
+       mb->idxinvalid = genericidxinvalid;
+
+       /* check for a mailbox type */
+       rv = Enotme;    /* can't happen; shut compiler up */
+       for(i = 0; i < nelem(boxinit); i++)
+               if((rv = boxinit[i](mb, path)) != Enotme)
                        break;
-       if(i == nelem(boxinit)){
-               free(mb);
-               return "bad path";
-       }
-
-       // on error, give up
        if(rv){
                free(mb);
                return rv;
        }
 
-       // make sure name isn't taken
+       /* make sure name isn't taken */
        qlock(&mbllock);
-       for(l = &mbl; *l != nil; l = &(*l)->next){
+       for(l = &mbl; *l != nil; l = &(*l)->next)
                if(strcmp((*l)->name, mb->name) == 0){
                        if(strcmp(path, (*l)->path) == 0)
                                rv = nil;
                        else
                                rv = "mbox name in use";
                        if(mb->close)
-                               (*mb->close)(mb);
+                               mb->close(mb);
                        free(mb);
                        qunlock(&mbllock);
                        return rv;
                }
-       }
 
-       // always try locking
+       /* always try locking */
        mb->dolock = 1;
-
        mb->refs = 1;
        mb->next = nil;
        mb->id = newid();
        mb->root = newmessage(nil);
-       mb->std = std;
+       mb->mtree = avlcreate(mtreecmp);
+
        *l = mb;
        qunlock(&mbllock);
 
        qlock(mb);
-       if(mb->ctl){
+       henter(PATH(0, Qtop), mb->name,
+               (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
+       if(mb->ctl)
                henter(PATH(mb->id, Qmbox), "ctl",
                        (Qid){PATH(mb->id, Qmboxctl), 0, QTFILE}, nil, mb);
-       }
        rv = syncmbox(mb, 0);
-       qunlock(mb);
+       if(r)
+               *r = mb;
+       else
+               qunlock(mb);
 
        return rv;
 }
 
-// close the named mailbox
+/* close the named mailbox */
 void
 freembox(char *name)
 {
        Mailbox **l, *mb;
 
        qlock(&mbllock);
-       for(l=&mbl; *l != nil; l=&(*l)->next){
+       for(l=&mbl; *l != nil; l=&(*l)->next)
                if(strcmp(name, (*l)->name) == 0){
                        mb = *l;
                        *l = mb->next;
                        mboxdecref(mb);
                        break;
                }
-       }
        hfree(PATH(0, Qtop), name);
        qunlock(&mbllock);
 }
 
-static void
-initheaders(void)
+void
+syncallmboxes(void)
 {
-       Header *h;
-       static int already;
+       char *err;
+       Mailbox *m;
 
-       if(already)
-               return;
-       already = 1;
+       qlock(&mbllock);
+       for(m = mbl; m != nil; m = m->next)
+               if(err = syncmbox(m, 1))
+                       eprint("syncmbox: %s\n", err);
+       qunlock(&mbllock);
+}
+
+
+char*
+removembox(char *name, int flags)
+{
+       int found;
+       Mailbox **l, *mb;
 
-       for(h = head; h->type != nil; h++)
-               h->len = strlen(h->type);
+       found = 0;
+       qlock(&mbllock);
+       for(l=&mbl; *l != nil; l=&(*l)->next)
+               if(strcmp(name, (*l)->path) == 0){
+                       mb = *l;
+                       *l = mb->next;
+                       mb->flags |= ORCLOSE;
+                       mb->rmflags = flags;
+                       mboxdecref(mb);
+                       found = 1;
+                       break;
+               }
+       hfree(PATH(0, Qtop), name);
+       qunlock(&mbllock);
+
+       if(found == 0)
+               return "maibox not found";
+       return 0;
 }
 
 /*
- *  parse a Unix style header
+ *  look for the date in the first Received: line.
+ *  it's likely to be the right time zone (it's
+ *  the local system) and in a convenient format.
  */
-void
-parseunix(Message *m)
+static int
+rxtotm(Message *m, Tm *tm)
 {
-       char *p;
-       String *h;
+       char *p, *q;
+       int r;
+
+       if(cistrncmp(m->header, "received:", 9))
+               return -1;
+       q = strchr(m->header, ';');
+       if(!q)
+               return -1;
+       p = q;
+       while((p = strchr(p, '\n')) != nil){
+               if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
+                       break;
+               p++;
+       }
+       if(!p)
+               return -1;
+       *p = '\0';
+       r = strtotm(q + 1, tm);
+       *p = '\n';
+       return r;
+}
 
-       h = s_new();
-       for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++)
-               s_putc(h, *p);
-       s_terminate(h);
-       s_restart(h);
+Message*
+gettopmsg(Mailbox *mb, Message *m)
+{
+       while(!Topmsg(mb, m))
+               m = m->whole;
+       return m;
+}
 
-       m->unixfrom = s_parse(h, s_reset(m->unixfrom));
-       m->unixdate = s_append(s_reset(m->unixdate), h->ptr);
+void
+datesec(Mailbox *mb, Message *m)
+{
+       char *s;
+       vlong v;
+       Tm tm;
 
-       s_free(h);
+       if(m->fileid > 1000000ull<<8)
+               return;
+       if(m->unixfrom && strtotm(m->unixfrom, &tm) >= 0)
+               v = tm2sec(&tm);
+       else if(m->date822 && strtotm(m->date822, &tm) >= 0)
+               v = tm2sec(&tm);
+       else if(rxtotm(m, &tm) >= 0)
+               v = tm2sec(&tm);
+       else{
+               s = rtab[m->type].s;
+               logmsg(gettopmsg(mb, m), "%s:%s: datasec %s %s\n", mb->path,
+                       m->whole? m->whole->name: "?",
+                       m->name, s);
+               if(Topmsg(mb, m) || strcmp(s, "message/rfc822") == 0)
+                       abort();
+               v = 0;
+       }
+       m->fileid = v<<8;
 }
 
 /*
  *  parse a message
  */
-void
-parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom)
+extern void sanemsg(Message*);
+extern void sanembmsg(Mailbox*, Message*);
+
+static Message*
+haschild(Message *m, int i)
 {
-       String *hl;
-       Header *h;
-       char *p, *q;
+       for(m = m->part; m && i; i--)
+               m = m->next;
+       if(m)
+               m->mimeflag = 0;
+       return m;
+}
+
+static void
+parseattachments(Message *m, Mailbox *mb)
+{
+       char *p, *x;
        int i;
+       Message *nm, **l;
 
-       if(m->whole == m->whole->whole){
-               henter(PATH(mb->id, Qmbox), m->name,
-                       (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
-       } else {
-               henter(PATH(m->whole->id, Qdir), m->name,
-                       (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb);
-       }
-       for(i = 0; i < Qmax; i++)
-               henter(PATH(m->id, Qdir), dirtab[i],
-                       (Qid){PATH(m->id, i), 0, QTFILE}, m, mb);
+       /* if there's a boundary, recurse... */
+sanemsg(m);
+//     dprint("parseattachments %p %ld\n", m->start, m->end - m->start);
+       if(m->boundary != nil){
+               p = m->body;
+               nm = nil;
+               l = &m->part;
+               for(i = 0;;){
+sanemsg(m);
+                       x = strstr(p, m->boundary);
+                       /* two sequential boundaries; ignore nil message */
+                       if(nm && x == p){
+                               p = strchr(x, '\n');
+                               if(p == nil){
+                                       nm->rbend = nm->bend = nm->end = x;
+                                       sanemsg(nm);
+                                       break;
+                               }
+                               p = p + 1;
+                               continue;
+                       }
+                       /* no boundary, we're done */
+                       if(x == nil){
+                               if(nm != nil){
+                                       nm->rbend = nm->bend = nm->end = m->bend;
+sanemsg(nm);
+                                       if(nm->end == nm->start)
+                                               nm->mimeflag |= Mtrunc;
+                               }
+                               break;
+                       }
+                       /* boundary must be at the start of a line */
+                       if(x != m->body && x[-1] != '\n'){
+                               p = x + 1;
+                               continue;
+                       }
 
-       // parse mime headers
-       p = m->header;
-       hl = s_new();
-       while(headerline(&p, hl)){
-               if(justmime)
-                       h = &head[Mhead];
-               else
-                       h = head;
-               for(; h->type; h++){
-                       if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){
-                               (*h->f)(m, h, s_to_c(hl));
+                       if(nm != nil)
+{
+                               nm->rbend = nm->bend = nm->end = x;
+sanemsg(nm);}
+                       x += strlen(m->boundary);
+
+                       /* is this the last part? ignore anything after it */
+                       if(strncmp(x, "--", 2) == 0)
+                               break;
+                       p = strchr(x, '\n');
+                       if(p == nil)
                                break;
+                       if((nm = haschild(m, i++)) == nil){
+                               nm = newmessage(m);
+                               *l = nm;
+                               l = &nm->next;
                        }
+                       nm->start = ++p;
+                       assert(nm->ballocd == 0);
+                       nm->mheader = nm->header = nm->body = nm->rbody = nm->start;
+               }
+               for(nm = m->part; nm != nil; nm = nm->next){
+                       parse(mb, nm, 0, 1);
+                       cachehash(mb, nm);              /* botchy place for this */
                }
-               s_reset(hl);
+               return;
        }
-       s_free(hl);
 
-       // the blank line isn't really part of the body or header
+       /* if we've got an rfc822 message, recurse... */
+       if(strcmp(rtab[m->type].s, "message/rfc822") == 0){
+               if((nm = haschild(m, 0)) == nil){
+                       nm = newmessage(m);
+                       m->part = nm;
+               }
+               assert(nm->ballocd == 0);
+               nm->start = nm->header = nm->body = nm->rbody = m->body;
+               nm->end = nm->bend = nm->rbend = m->bend;
+               if(nm->end == nm->start)
+                       nm->mimeflag |= Mtrunc;
+               parse(mb, nm, 0, 0);
+               cachehash(mb, nm);                      /* botchy place for this */
+       }
+}
+
+void
+parseheaders(Mailbox *mb, Message *m, int addfrom, int justmime)
+{
+       char *p, *e, *o, *t, *s;
+       int i, i0, n;
+       uintptr a;
+
+/*sanembmsg(mb, m);    /* fails with pop but i want this debugging for now */
+
+       /* parse mime headers */
+       p = m->header;
+       i0 = 0;
+       if(justmime)
+               i0 = Mhead;
+       s = emalloc(2048);
+       e = s + 2048 - 1;
+       while((n = hdrlen(p, m->end)) != 0){
+               if(n > e - s){
+                       s = erealloc(s, n);
+                       e = s + n - 1;
+               }
+               rfc2047(s, e, p, n, 1);
+               p += n;
+
+               for(i = i0; i < nelem(head); i++)
+                       if(!cistrncmp(s, head[i].type, head[i].len)){
+                               a = head[i].offset;
+                               if(a != ~0){
+                                       if(o = *(char**)((char*)m + a))
+                                               continue;
+                                       t = head[i].f(m, head + i, o, s);
+                                       *(char**)((char*)m + a) = t;
+                               }else
+                                       head[i].f(m, head + i, 0, s);
+                               break;
+                       }
+       }
+       free(s);
+/*sanembmsg(mb, m);    /* fails with pop but i want this debugging for now */
+       /* the blank line isn't really part of the body or header */
        if(justmime){
                m->mhend = p;
                m->hend = m->header;
-       } else {
+       } else{
                m->hend = p;
+               m->mhend = m->header;
        }
+       /*
+        * not all attachments have mime headers themselves.
+        */
+       if(!m->mheader)
+               m->mhend = 0;
        if(*p == '\n')
                p++;
        m->rbody = m->body = p;
 
-       // if type is text, get any nulls out of the body.  This is
-       // for the two seans and imap clients that get confused.
-       if(strncmp(s_to_c(m->type), "text/", 5) == 0)
-               nullsqueeze(m);
-
-       //
-       // cobble together Unix-style from line
-       // for local mailbox messages, we end up recreating the
-       // original header.
-       // for pop3 messages, the best we can do is 
-       // use the From: information and the RFC822 date.
-       //
-       if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0
-       || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){
-               if(m->unixdate){
-                       s_free(m->unixdate);
-                       m->unixdate = nil;
-               }
-               // look for the date in the first Received: line.
-               // it's likely to be the right time zone (it's
-               // the local system) and in a convenient format.
-               if(cistrncmp(m->header, "received:", 9)==0){
-                       if((q = strchr(m->header, ';')) != nil){
-                               p = q;
-                               while((p = strchr(p, '\n')) != nil){
-                                       if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n')
-                                               break;
-                                       p++;
-                               }
-                               if(p){
-                                       *p = '\0';
-                                       m->unixdate = date822tounix(q+1);
-                                       *p = '\n';
-                               }
-                       }
-               }
-
-               // fall back on the rfc822 date 
-               if(m->unixdate==nil && m->date822)
-                       m->unixdate = date822tounix(s_to_c(m->date822));
-       }
-
-       if(m->unixheader != nil)
-               s_free(m->unixheader);
-
-       // only fake header for top-level messages for pop3 and imap4
-       // clients (those protocols don't include the unix header).
-       // adding the unix header all the time screws up mime-attached
-       // rfc822 messages.
-       if(!addfrom && !m->unixfrom){
+       if(!justmime)
+               datesec(mb, m);
+
+       /*
+        *  only fake header for top-level messages for pop3 and imap4
+        *  clients (those protocols don't include the unix header).
+        *  adding the unix header all the time screws up mime-attached
+        *  rfc822 messages.
+        */
+/*sanembmsg(mb, m);    /* fails with pop but i want this debugging for now */
+       if(!addfrom && !m->unixfrom)
                m->unixheader = nil;
-               return;
+       else if(m->unixheader == nil){
+               if(m->unixfrom && strcmp(m->unixfrom, "???") != 0)
+                       p = m->unixfrom;
+               else if(m->from)
+                       p = m->from;
+               else
+                       p = "???";
+               m->unixheader = smprint("From %s %Δ\n", p, m->fileid);
        }
-
-       m->unixheader = s_copy("From ");
-       if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0)
-               s_append(m->unixheader, s_to_c(m->unixfrom));
-       else if(m->from822)
-               s_append(m->unixheader, s_to_c(m->from822));
-       else
-               s_append(m->unixheader, "???");
-
-       s_append(m->unixheader, " ");
-       if(m->unixdate)
-               s_append(m->unixheader, s_to_c(m->unixdate));
-       else
-               s_append(m->unixheader, "Thu Jan  1 00:00:00 GMT 1970");
-
-       s_append(m->unixheader, "\n");
+       m->cstate |= Cheader;
+sanembmsg(mb, m);
 }
 
-String*
-promote(String **sp)
+char*
+promote(char *s)
 {
-       String *s;
-
-       if(*sp != nil)
-               s = s_clone(*sp);
-       else
-               s = nil;
-       return s;
+       return s? strdup(s): nil;
 }
 
 void
 parsebody(Message *m, Mailbox *mb)
 {
+       char *s;
+       int l;
        Message *nm;
 
-       // recurse
-       if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){
+       /* recurse */
+       s = rtab[m->type].s;
+       l = rtab[m->type].l;
+       if(l >= 10 && strncmp(s, "multipart/", 10) == 0)
                parseattachments(m, mb);
-       } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
+       else if(l == 14 && strcmp(s, "message/rfc822") == 0){
                decode(m);
                parseattachments(m, mb);
                nm = m->part;
 
-               // promote headers
-               if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){
-                       m->from822 = promote(&nm->from822);
-                       m->to822 = promote(&nm->to822);
-                       m->date822 = promote(&nm->date822);
-                       m->sender822 = promote(&nm->sender822);
-                       m->replyto822 = promote(&nm->replyto822);
-                       m->subject822 = promote(&nm->subject822);
-                       m->unixdate = promote(&nm->unixdate);
+               /* promote headers */
+               if(m->replyto == nil && m->from == nil && m->sender == nil){
+                       m->from = promote(nm->from);
+                       m->to = promote(nm->to);
+                       m->date822 = promote(nm->date822);
+                       m->sender = promote(nm->sender);
+                       m->replyto = promote(nm->replyto);
+                       m->subject = promote(nm->subject);
                }
-       }
+       }else if(strncmp(rtab[m->type].s, "text/", 5) == 0)
+               sanemsg(m);
+       m->rawbsize = m->rbend - m->rbody;
+       m->cstate |= Cbody;
 }
 
 void
-parse(Message *m, int justmime, Mailbox *mb, int addfrom)
+parse(Mailbox *mb, Message *m, int addfrom, int justmime)
 {
-       parseheaders(m, justmime, mb, addfrom);
+       sanemsg(m);
+       assert(m->end - m->start > 0 || m->mimeflag&Mtrunc && m->end - m->start == 0);
+       if((m->cstate & Cheader) == 0)
+               parseheaders(mb, m, addfrom, justmime);
        parsebody(m, mb);
+       sanemsg(m);
 }
 
-static void
-parseattachments(Message *m, Mailbox *mb)
+static char*
+skipwhite(char *p)
 {
-       Message *nm, **l;
-       char *p, *x;
-
-       // if there's a boundary, recurse...
-       if(m->boundary != nil){
-               p = m->body;
-               nm = nil;
-               l = &m->part;
-               for(;;){
-                       x = strstr(p, s_to_c(m->boundary));
-
-                       /* no boundary, we're done */
-                       if(x == nil){
-                               if(nm != nil)
-                                       nm->rbend = nm->bend = nm->end = m->bend;
-                               break;
-                       }
+       while(isascii(*p) && isspace(*p))
+               p++;
+       return p;
+}
 
-                       /* boundary must be at the start of a line */
-                       if(x != m->body && *(x-1) != '\n'){
-                               p = x+1;
-                               continue;
-                       }
+static char*
+skiptosemi(char *p)
+{
+       while(*p && *p != ';')
+               p++;
+       while(*p == ';' || (isascii(*p) && isspace(*p)))
+               p++;
+       return p;
+}
 
-                       if(nm != nil)
-                               nm->rbend = nm->bend = nm->end = x;
-                       x += strlen(s_to_c(m->boundary));
+static char*
+getstring(char *p, char *s, char *e, int dolower)
+{
+       int c;
 
-                       /* is this the last part? ignore anything after it */
-                       if(strncmp(x, "--", 2) == 0)
+       p = skipwhite(p);
+       if(*p == '"'){
+               for(p++; (c = *p) != '"'; p++){
+                       if(c == '\\')
+                               c = *++p;
+                       /*
+                        * 821 says <x> after \ can be anything at all.
+                        * we just don't care.
+                        */
+                       if(c == 0)
                                break;
-
-                       p = strchr(x, '\n');
-                       if(p == nil)
+                       if(c < ' ')
+                               continue;
+                       if(dolower && c >= 'A' && c <= 'Z')
+                               c += 0x20;
+                       s = sputc(s, e, c);
+               }
+               if(*p == '"')
+                       p++;
+       }else{
+               for(; (c = *p) && !isspace(c) && c != ';'; p++){
+                       if(c == '\\')
+                               c = *++p;
+                       /*
+                        * 821 says <x> after \ can be anything at all.
+                        * we just don't care.
+                        */
+                       if(c == 0)
                                break;
-                       nm = newmessage(m);
-                       nm->start = nm->header = nm->body = nm->rbody = ++p;
-                       nm->mheader = nm->header;
-                       *l = nm;
-                       l = &nm->next;
+                       if(c < ' ')
+                               continue;
+                       if(dolower && c >= 'A' && c <= 'Z')
+                               c += 0x20;
+                       s = sputc(s, e, c);
                }
-               for(nm = m->part; nm != nil; nm = nm->next)
-                       parse(nm, 1, mb, 0);
-               return;
-       }
-
-       // if we've got an rfc822 message, recurse...
-       if(strcmp(s_to_c(m->type), "message/rfc822") == 0){
-               nm = newmessage(m);
-               m->part = nm;
-               nm->start = nm->header = nm->body = nm->rbody = m->body;
-               nm->end = nm->bend = nm->rbend = m->bend;
-               parse(nm, 0, mb, 0);
        }
+       *s = 0;
+       return p;
 }
 
-/*
- *  pick up a header line
- */
-static int
-headerline(char **pp, String *hl)
+static void
+setfilename(Message *m, char *p)
 {
-       char *p, *x;
+       char buf[Pathlen];
 
-       s_reset(hl);
-       p = *pp;
-       x = strpbrk(p, ":\n");
-       if(x == nil || *x == '\n')
-               return 0;
-       for(;;){
-               x = strchr(p, '\n');
-               if(x == nil)
-                       x = p + strlen(p);
-               s_nappend(hl, p, x-p);
-               p = x;
-               if(*p != '\n' || *++p != ' ' && *p != '\t')
-                       break;
-               while(*p == ' ' || *p == '\t')
-                       p++;
-               s_putc(hl, ' ');
-       }
-       *pp = p;
-       return 1;
+       free(m->filename);
+       getstring(p, buf, buf + sizeof buf - 1, 0);
+       m->filename = smprint("%s", buf);
+       for(p = m->filename; *p; p++)
+               if(*p == ' ' || *p == '\t' || *p == ';')
+                       *p = '_';
 }
 
-/* returns nil iff there are no addressees */
-static String*
-addr822(char *p)
+static char*
+rtrim(char *p)
 {
-       String *s, *list;
-       int incomment, addrdone, inanticomment, quoted;
-       int n;
-       int c;
+       char *e;
 
-       list = s_new();
-       s = s_new();
-       quoted = incomment = addrdone = inanticomment = 0;
-       n = 0;
-       for(; *p; p++){
-               c = *p;
+       if(p == 0)
+               return p;
+       e = p + strlen(p) - 1;
+       while(e > p && isascii(*e) && isspace(*e))
+               *e-- = 0;
+       return p;
+}
 
-               // whitespace is ignored
+static char*
+addr822(char *p, char **ac)
+{
+       int n, c, space, incomment, addrdone, inanticomment, quoted;
+       char s[128+1], *ps, *e, *x, *list;
+
+       list = 0;
+       s[0] = 0;
+       ps = s;
+       e = s + sizeof s;
+       space = quoted = incomment = addrdone = inanticomment = 0;
+       n = 0;
+       for(; c = *p; p++){
+               if(!inanticomment && !quoted && !space && ps != s && c == ' '){
+                       ps = sputc(ps, e, c);
+                       space = 1;
+                       continue;
+               }
+               space = 0;
                if(!quoted && isspace(c) || c == '\r')
                        continue;
-
-               // strings are always treated as atoms
+               /* strings are always treated as atoms */
                if(!quoted && c == '"'){
-                       if(!addrdone && !incomment)
-                               s_putc(s, c);
-                       for(p++; *p; p++){
+                       if(!addrdone && !incomment && !ac)
+                               ps = sputc(ps, e, c);
+                       for(p++; c = *p; p++){
+                               if(ac && c == '"')
+                                       break;
                                if(!addrdone && !incomment)
-                                       s_putc(s, *p);
+                                       ps = sputc(ps, e, c);
                                if(!quoted && *p == '"')
                                        break;
                                if(*p == '\\')
@@ -514,13 +736,13 @@ addr822(char *p)
                                else
                                        quoted = 0;
                        }
-                       if(*p == 0)
+                       if(c == 0)
                                break;
                        quoted = 0;
                        continue;
                }
 
-               // ignore everything in an expicit comment
+               /* ignore everything in an expicit comment */
                if(!quoted && c == '('){
                        incomment = 1;
                        continue;
@@ -532,10 +754,21 @@ addr822(char *p)
                        continue;
                }
 
-               // anticomments makes everything outside of them comments
+               /* anticomments makes everything outside of them comments */
                if(!quoted && c == '<' && !inanticomment){
+                       if(ac){
+                               *ps-- = 0;
+                               if(ps > s && *ps == ' ')
+                                       *ps = 0;
+                               if(*ac){
+                                       *ac = smprint("%s, %s", x=*ac, s);
+                                       free(x);
+                               }else
+                                       *ac = smprint("%s", s);
+                       }
+
                        inanticomment = 1;
-                       s = s_reset(s);
+                       ps = s;
                        continue;
                }
                if(!quoted && c == '>' && inanticomment){
@@ -544,21 +777,23 @@ addr822(char *p)
                        continue;
                }
 
-               // commas separate addresses
+               /* commas separate addresses */
                if(!quoted && c == ',' && !inanticomment){
-                       s_terminate(s);
+                       *ps = 0;
                        addrdone = 0;
-                       if(n++ != 0)
-                               s_append(list, " ");
-                       s_append(list, s_to_c(s));
-                       s = s_reset(s);
+                       if(n++ != 0){
+                               list = smprint("%s %s", x=list, s);
+                               free(x);
+                       }else
+                               list = smprint("%s", s);
+                       ps = s;
                        continue;
                }
 
-               // what's left is part of the address
-               s_putc(s, c);
+               /* what's left is part of the address */
+               ps = sputc(ps, e, c);
 
-               // quoted characters are recognized only as characters
+               /* quoted characters are recognized only as characters */
                if(c == '\\')
                        quoted = 1;
                else
@@ -566,19 +801,15 @@ addr822(char *p)
 
        }
 
-       if(*s_to_c(s) != 0){
-               s_terminate(s);
-               if(n++ != 0)
-                       s_append(list, " ");
-               s_append(list, s_to_c(s));
+       if(ps > s){
+               *ps = 0;
+               if(n != 0){
+                       list = smprint("%s %s", x=list, s);
+                       free(x);
+               }else
+                       list = smprint("%s", s);
        }
-       s_free(s);
-
-       if(n == 0){             /* no addressees given, just the keyword */
-               s_free(list);
-               return nil;
-       }
-       return list;
+       return rtrim(list);
 }
 
 /*
@@ -586,138 +817,95 @@ addr822(char *p)
  * concatenating their values.
  */
 
-static void
-to822(Message *m, Header *h, char *p)
-{
-       String *s;
-
-       p += strlen(h->type);
-       s = addr822(p);
-       if (m->to822 == nil)
-               m->to822 = s;
-       else if (s != nil) {
-               s_append(m->to822, " ");
-               s_append(m->to822, s_to_c(s));
-               s_free(s);
-       }
-}
-
-static void
-cc822(Message *m, Header *h, char *p)
-{
-       String *s;
-
-       p += strlen(h->type);
-       s = addr822(p);
-       if (m->cc822 == nil)
-               m->cc822 = s;
-       else if (s != nil) {
-               s_append(m->cc822, " ");
-               s_append(m->cc822, s_to_c(s));
-               s_free(s);
-       }
-}
-
-static void
-bcc822(Message *m, Header *h, char *p)
-{
-       String *s;
-
-       p += strlen(h->type);
-       s = addr822(p);
-       if (m->bcc822 == nil)
-               m->bcc822 = s;
-       else if (s != nil) {
-               s_append(m->bcc822, " ");
-               s_append(m->bcc822, s_to_c(s));
-               s_free(s);
-       }
-}
-
-static void
-from822(Message *m, Header *h, char *p)
+static char*
+concat822(Message*, Header *h, char *o, char *p)
 {
-       p += strlen(h->type);
-       s_free(m->from822);
-       m->from822 = addr822(p);
-}
+       char *s, *n;
 
-static void
-sender822(Message *m, Header *h, char *p)
-{
        p += strlen(h->type);
-       s_free(m->sender822);
-       m->sender822 = addr822(p);
+       s = addr822(p, 0);
+       if(o){
+               n = smprint("%s %s", o, s);
+               free(s);
+       }else
+               n = s;
+       return n;
 }
 
-static void
-replyto822(Message *m, Header *h, char *p)
-{
-       p += strlen(h->type);
-       s_free(m->replyto822);
-       m->replyto822 = addr822(p);
-}
-
-static void
-mimeversion(Message *m, Header *h, char *p)
-{
-       p += strlen(h->type);
-       s_free(m->mimeversion);
-       m->mimeversion = addr822(p);
-}
-
-static void
-killtrailingwhite(char *p)
+static char*
+from822(Message *m, Header *h, char*, char *p)
 {
-       char *e;
-
-       e = p + strlen(p) - 1;
-       while(e > p && isspace(*e))
-               *e-- = 0;
+       if(m->ffrom)
+               free(m->ffrom);
+       m->from = 0;
+       return addr822(p + h->len, &m->ffrom);
 }
 
-static void
-date822(Message *m, Header *h, char *p)
+static char*
+replace822(Message *, Header *h, char*, char *p)
 {
-       p += strlen(h->type);
-       p = skipwhite(p);
-       s_free(m->date822);
-       m->date822 = s_copy(p);
-       p = s_to_c(m->date822);
-       killtrailingwhite(p);
+       return addr822(p + h->len, 0);
 }
 
-static void
-subject822(Message *m, Header *h, char *p)
+static char*
+copy822(Message*, Header *h, char*, char *p)
 {
-       p += strlen(h->type);
-       p = skipwhite(p);
-       s_free(m->subject822);
-       m->subject822 = s_copy(p);
-       p = s_to_c(m->subject822);
-       killtrailingwhite(p);
+       return rtrim(strdup(skipwhite(p + h->len)));
 }
 
-static void
-inreplyto822(Message *m, Header *h, char *p)
+/*
+ * firefox, e.g. doesn't keep references unique
+ */
+static int
+uniqarray(char **a, int n, int allocd)
 {
-       p += strlen(h->type);
-       p = skipwhite(p);
-       s_free(m->inreplyto822);
-       m->inreplyto822 = s_copy(p);
-       p = s_to_c(m->inreplyto822);
-       killtrailingwhite(p);
+       int i, j;
+
+       for(i = 0; i < n; i++)
+               for(j = i + 1; j < n; j++)
+                       if(strcmp(a[i], a[j]) == 0){
+                               if(allocd)
+                                       free(a[j]);
+                               memmove(a + j, a + j + 1, sizeof *a*(n - (j + 1)));
+                               a[--n] = 0;
+                       }
+       return n;
 }
 
-static void
-messageid822(Message *m, Header *h, char *p)
+static char*
+ref822(Message *m, Header *h, char*, char *p)
 {
-       p += strlen(h->type);
-       p = skipwhite(p);
-       s_free(m->messageid822);
-       m->messageid822 = s_copy(p);
-       p = s_to_c(m->messageid822);
-       killtrailingwhite(p);
+       char **a, *s, *f[Nref + 1];
+       int i, j, k, n;
+
+       s = strdup(skipwhite(p + h->len));
+       n = getfields(s, f, nelem(f), 1, "<> \n\t\r,");
+       if(n > Nref)
+               n = Nref;
+       n = uniqarray(f, n, 0);
+       a = m->references;
+       for(i = 0; i < Nref; i++)
+               if(a[i] == 0)
+                       break;
+       /*
+        * if there are too many references, drop from the beginning
+        * of the list.
+        */
+       j = i + n - Nref;
+       if(j > 0){
+               if(j > Nref)
+                       j = Nref;
+               for(k = 0; k < j; k++)
+                       free(a[k]);
+               memmove(a, a + j, sizeof a[0]*(Nref - j));
+               memset(a + j, 0, Nref - j);
+               i -= j;
+       }
+       for(j = 0; j < n;)
+               a[i++] = strdup(f[j++]);
+       free(s);
+       uniqarray(a, i, 1);
+       return (char*)~0;
 }
 
 static int
@@ -741,24 +929,20 @@ isattribute(char **pp, char *attr)
        return 1;
 }
 
-static void
-ctype(Message *m, Header *h, char *p)
+static char*
+ctype(Message *m, Header *h, char*, char *p)
 {
-       String *s;
+       char buf[128], *e;
 
-       p += h->len;
-       p = skipwhite(p);
+       e = buf + sizeof buf - 1;
+       p = getstring(skipwhite(p + h->len), buf, e, 1);
+       m->type = newrefs(buf);
 
-       p = getstring(p, m->type, 1);
-       
-       while(*p){
+       for(; *p; p = skiptosemi(p))
                if(isattribute(&p, "boundary")){
-                       s = s_new();
-                       p = getstring(p, s, 0);
-                       m->boundary = s_reset(m->boundary);
-                       s_append(m->boundary, "--");
-                       s_append(m->boundary, s_to_c(s));
-                       s_free(s);
+                       p = getstring(p, buf, e, 0);
+                       free(m->boundary);
+                       m->boundary = smprint("--%s", buf);
                } else if(cistrncmp(p, "multipart", 9) == 0){
                        /*
                         *  the first unbounded part of a multipart message,
@@ -768,44 +952,41 @@ ctype(Message *m, Header *h, char *p)
                        if(m->filename == nil)
                                setfilename(m, p);
                } else if(isattribute(&p, "charset")){
-                       p = getstring(p, s_reset(m->charset), 0);
+                       p = getstring(p, buf, e, 0);
+                       lowercase(buf);
+                       m->charset = newrefs(buf);
                }
-               
-               p = skiptosemi(p);
-       }
+       return (char*)~0;
 }
 
-static void
-cencoding(Message *m, Header *h, char *p)
+static char*
+cencoding(Message *m, Header *h, char*, char *p)
 {
-       p += h->len;
-       p = skipwhite(p);
+       p = skipwhite(p + h->len);
        if(cistrncmp(p, "base64", 6) == 0)
                m->encoding = Ebase64;
        else if(cistrncmp(p, "quoted-printable", 16) == 0)
                m->encoding = Equoted;
+       return (char*)~0;
 }
 
-static void
-cdisposition(Message *m, Header *h, char *p)
+static char*
+cdisposition(Message *m, Header *h, char*, char *p)
 {
-       p += h->len;
-       p = skipwhite(p);
-       while(*p){
-               if(cistrncmp(p, "inline", 6) == 0){
+       for(p = skipwhite(p + h->len); *p; p = skiptosemi(p))
+               if(cistrncmp(p, "inline", 6) == 0)
                        m->disposition = Dinline;
-               } else if(cistrncmp(p, "attachment", 10) == 0){
+               else if(cistrncmp(p, "attachment", 10) == 0)
                        m->disposition = Dfile;
-               else if(cistrncmp(p, "filename=", 9) == 0){
+               else if(cistrncmp(p, "filename=", 9) == 0){
                        p += 9;
                        setfilename(m, p);
                }
-               p = skiptosemi(p);
-       }
-
+       return (char*)~0;
 }
 
-ulong msgallocd, msgfreed;
+ulong  msgallocd;
+ulong  msgfreed;
 
 Message*
 newmessage(Message *parent)
@@ -815,14 +996,16 @@ newmessage(Message *parent)
 
        msgallocd++;
 
-       m = emalloc(sizeof(*m));
-       memset(m, 0, sizeof(*m));
+       m = emalloc(sizeof *m);
+       dprint("newmessage %ld  %p      %p\n", msgallocd, parent, m);
        m->disposition = Dnone;
-       m->type = s_copy("text/plain");
-       m->charset = s_copy("iso-8859-1");
+       m->type = newrefs("text/plain");
+       m->charset = newrefs("iso-8859-1");
+       m->cstate = Cidxstale;
+       m->flags = Frecent;
        m->id = newid();
        if(parent)
-               sprint(m->name, "%d", ++(parent->subname));
+               snprint(m->name, sizeof m->name, "%d", ++(parent->subname));
        if(parent == nil)
                parent = m;
        m->whole = parent;
@@ -830,74 +1013,65 @@ newmessage(Message *parent)
        return m;
 }
 
-// delete a message from a mailbox
+/* delete a message from a mailbox */
 void
 delmessage(Mailbox *mb, Message *m)
 {
        Message **l;
-       int i;
 
        mb->vers++;
        msgfreed++;
 
        if(m->whole != m){
-               // unchain from parent
+               /* unchain from parent */
                for(l = &m->whole->part; *l && *l != m; l = &(*l)->next)
                        ;
                if(*l != nil)
                        *l = m->next;
 
-               // clear out of name lookup hash table
+               /* clear out of name lookup hash table */
                if(m->whole->whole == m->whole)
                        hfree(PATH(mb->id, Qmbox), m->name);
                else
                        hfree(PATH(m->whole->id, Qdir), m->name);
-               for(i = 0; i < Qmax; i++)
-                       hfree(PATH(m->id, Qdir), dirtab[i]);
+               hfree(PATH(m->id, Qdir), "xxx");                /* sleezy speedup */
        }
-
-       /* recurse through sub-parts */
+       if(Topmsg(mb, m)){
+               if(m != mb->root)
+                       mtreedelete(mb, m);
+               cachefree(mb, m, 1);
+       }
+       delrefs(m->type);
+       delrefs(m->charset);
+       idxfree(m);
        while(m->part)
                delmessage(mb, m->part);
-
-       /* free memory */
-       if(m->mallocd)
-               free(m->start);
-       if(m->hallocd)
-               free(m->header);
-       if(m->ballocd)
-               free(m->body);
-       s_free(m->unixfrom);
-       s_free(m->unixdate);
-       s_free(m->unixheader);
-       s_free(m->from822);
-       s_free(m->sender822);
-       s_free(m->to822);
-       s_free(m->bcc822);
-       s_free(m->cc822);
-       s_free(m->replyto822);
-       s_free(m->date822);
-       s_free(m->inreplyto822);
-       s_free(m->subject822);
-       s_free(m->messageid822);
-       s_free(m->addrs);
-       s_free(m->mimeversion);
-       s_free(m->sdigest);
-       s_free(m->boundary);
-       s_free(m->type);
-       s_free(m->charset);
-       s_free(m->filename);
+       free(m->unixfrom);
+       free(m->unixheader);
+       free(m->date822);
+       free(m->inreplyto);
+       free(m->boundary);
+       free(m->filename);
 
        free(m);
 }
 
-// mark messages (identified by path) for deletion
 void
+unnewmessage(Mailbox *mb, Message *parent, Message *m)
+{
+       assert(parent->subname > 0);
+       m->deleted = Dup;
+       delmessage(mb, m);
+       parent->subname -= 1;
+}
+
+/* mark messages (identified by path) for deletion */
+char*
 delmessages(int ac, char **av)
 {
+       int i, needwrite;
        Mailbox *mb;
        Message *m;
-       int i, needwrite;
 
        qlock(&mbllock);
        for(mb = mbl; mb != nil; mb = mb->next)
@@ -907,24 +1081,59 @@ delmessages(int ac, char **av)
                }
        qunlock(&mbllock);
        if(mb == nil)
-               return;
+               return "no such mailbox";
 
        needwrite = 0;
-       for(i = 1; i < ac; i++){
+       for(i = 1; i < ac; i++)
                for(m = mb->root->part; m != nil; m = m->next)
                        if(strcmp(m->name, av[i]) == 0){
                                if(!m->deleted){
                                        mailplumb(mb, m, 1);
                                        needwrite = 1;
-                                       m->deleted = 1;
-                                       logmsg("deleting", m);
+                                       m->deleted = Deleted;
+                                       logmsg(m, "deleting");
                                }
                                break;
                        }
-       }
        if(needwrite)
                syncmbox(mb, 1);
        qunlock(mb);
+       return 0;
+}
+
+char*
+flagmessages(int argc, char **argv)
+{
+       char *err, *rerr;
+       int i, needwrite;
+       Mailbox *mb;
+       Message *m;
+
+       if(argc%2)
+               return "bad flags";
+       qlock(&mbllock);
+       for(mb = mbl; mb; mb = mb->next)
+               if(strcmp(*argv, mb->name) == 0){
+                       qlock(mb);
+                       break;
+               }
+       qunlock(&mbllock);
+       if(mb == nil)
+               return "no such mailbox";
+       needwrite = 0;
+       rerr = 0;
+       for(i = 1; i < argc; i += 2)
+               for(m = mb->root->part; m; m = m->next)
+                       if(strcmp(m->name, argv[i]) == 0){
+                               if(err = modflags(mb, m, argv[i + 1]))
+                                       rerr = err;
+                               else
+                                       needwrite = 1;
+                       }
+       if(needwrite)
+               syncmbox(mb, 1);
+       qunlock(mb);
+       return rerr;
 }
 
 /*
@@ -935,12 +1144,18 @@ msgincref(Message *m)
 {
        m->refs++;
 }
+
 void
 msgdecref(Mailbox *mb, Message *m)
 {
+       assert(m->refs > 0);
        m->refs--;
-       if(m->refs == 0 && m->deleted)
-               syncmbox(mb, 1);
+       if(m->refs == 0){
+               if(m->deleted)
+                       syncmbox(mb, 1);
+               else
+                       putcache(mb, m);
+       }
 }
 
 /*
@@ -952,6 +1167,20 @@ mboxincref(Mailbox *mb)
        assert(mb->refs > 0);
        mb->refs++;
 }
+
+static void
+mbrmidx(char *path, int flags)
+{
+       char buf[Pathlen];
+
+       snprint(buf, sizeof buf, "%s.idx", path);
+       vremove(buf);
+       if((flags & Rtrunc) == 0){
+               snprint(buf, sizeof buf, "%s.imp", path);
+               vremove(buf);
+       }
+}
+
 void
 mboxdecref(Mailbox *mb)
 {
@@ -959,98 +1188,41 @@ mboxdecref(Mailbox *mb)
        qlock(mb);
        mb->refs--;
        if(mb->refs == 0){
+               syncmbox(mb, 1);
                delmessage(mb, mb->root);
                if(mb->ctl)
                        hfree(PATH(mb->id, Qmbox), "ctl");
                if(mb->close)
-                       (*mb->close)(mb);
+                       mb->close(mb);
+               if(mb->flags & ORCLOSE && mb->remove)
+               if(mb->remove(mb, mb->rmflags))
+                       rmidx(mb->path, mb->rmflags);
+               free(mb->mtree);
+               free(mb->d);
                free(mb);
        } else
                qunlock(mb);
 }
 
+/* just space over \r.  sleezy but necessary for ms email. */
 int
-cistrncmp(char *a, char *b, int n)
+deccr(char *x, int len)
 {
-       while(n-- > 0){
-               if(tolower(*a++) != tolower(*b++))
-                       return -1;
-       }
-       return 0;
-}
+       char *e;
 
-int
-cistrcmp(char *a, char *b)
-{
+       e = x + len;
        for(;;){
-               if(tolower(*a) != tolower(*b++))
-                       return -1;
-               if(*a++ == 0)
+               x = memchr(x, '\r', e - x);
+               if(x == nil)
                        break;
+               *x = ' ';
        }
-       return 0;
-}
-
-static char*
-skipwhite(char *p)
-{
-       while(isspace(*p))
-               p++;
-       return p;
+       return len;
 }
 
-static char*
-skiptosemi(char *p)
-{
-       while(*p && *p != ';')
-               p++;
-       while(*p == ';' || isspace(*p))
-               p++;
-       return p;
-}
-
-static char*
-getstring(char *p, String *s, int dolower)
-{
-       s = s_reset(s);
-       p = skipwhite(p);
-       if(*p == '"'){
-               p++;
-               for(;*p && *p != '"'; p++)
-                       if(dolower)
-                               s_putc(s, tolower(*p));
-                       else
-                               s_putc(s, *p);
-               if(*p == '"')
-                       p++;
-               s_terminate(s);
-
-               return p;
-       }
-
-       for(; *p && !isspace(*p) && *p != ';'; p++)
-               if(dolower)
-                       s_putc(s, tolower(*p));
-               else
-                       s_putc(s, *p);
-       s_terminate(s);
-
-       return p;
-}
-
-static void
-setfilename(Message *m, char *p)
-{
-       m->filename = s_reset(m->filename);
-       getstring(p, m->filename, 0);
-       for(p = s_to_c(m->filename); *p; p++)
-               if(*p == ' ' || *p == '\t' || *p == ';')
-                       *p = '_';
-}
-
-//
-// undecode message body
-//
+/*
+ *  undecode message body
+ */
 void
 decode(Message *m)
 {
@@ -1062,9 +1234,15 @@ decode(Message *m)
        switch(m->encoding){
        case Ebase64:
                len = m->bend - m->body;
-               i = (len*3)/4+1;        // room for max chars + null
+               i = (len*3)/4 + 1;      /* room for max chars + null */
                x = emalloc(i);
                len = dec64((uchar*)x, i, m->body, len);
+               if(len == -1){
+                       free(x);
+                       break;
+               }
+               if(strncmp(rtab[m->type].s, "text/", 5) == 0)
+                       len = deccr(x, len);
                if(m->ballocd)
                        free(m->body);
                m->body = x;
@@ -1073,7 +1251,7 @@ decode(Message *m)
                break;
        case Equoted:
                len = m->bend - m->body;
-               x = emalloc(len+2);     // room for null and possible extra nl
+               x = emalloc(len + 2);   /* room for null and possible extra nl */
                len = decquoted(x, m->body, m->bend, 0);
                if(m->ballocd)
                        free(m->body);
@@ -1087,22 +1265,20 @@ decode(Message *m)
        m->decoded = 1;
 }
 
-// convert latin1 to utf
+/* convert x to utf8 */
 void
 convert(Message *m)
 {
        int len;
        char *x;
 
-       // don't convert if we're not a leaf, not text, or already converted
+       /* don't convert if we're not a leaf, not text, or already converted */
        if(m->converted)
                return;
-       if(m->part != nil)
-               return;
-       if(cistrncmp(s_to_c(m->type), "text", 4) != 0)
+       m->converted = 1;
+       if(m->part != nil || cistrncmp(rtab[m->type].s, "text", 4) != 0)
                return;
-
-       len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend);
+       len = xtoutf(rtab[m->charset].s, &x, m->body, m->bend);
        if(len > 0){
                if(m->ballocd)
                        free(m->body);
@@ -1110,7 +1286,6 @@ convert(Message *m)
                m->bend = x + len;
                m->ballocd = 1;
        }
-       m->converted = 1;
 }
 
 static int
@@ -1119,18 +1294,20 @@ hex2int(int x)
        if(x >= '0' && x <= '9')
                return x - '0';
        if(x >= 'A' && x <= 'F')
-               return (x - 'A') + 10;
+               return x - 'A' + 10;
        if(x >= 'a' && x <= 'f')
-               return (x - 'a') + 10;
+               return x - 'a' + 10;
        return -1;
 }
 
-// underscores are translated in 2047 headers (uscores=1) 
-// but not in the body (uscores=0)
+/*
+ *  underscores are translated in 2047 headers (uscores=1)
+ *  but not in the body (uscores=0)
+ */
 static char*
 decquotedline(char *out, char *in, char *e, int uscores)
 {
-       int c, c2, soft;
+       int c, soft;
 
        /* dump trailing white space */
        while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n'))
@@ -1155,11 +1332,11 @@ decquotedline(char *out, char *in, char *e, int uscores)
                        *out++ = c;
                        break;
                case '=':
-                       c  = hex2int(*in++);
-                       c= hex2int(*in++);
-                       if (c != -1 && c2 != -1)
-                               *out++ = c<<4 | c2;
-                       else {
+                       c = hex2int(*in++)<<4;
+                       c |= hex2int(*in++);
+                       if(c != -1)
+                               *out++ = c;
+                       else{
                                *out++ = '=';
                                in -= 2;
                        }
@@ -1184,10 +1361,10 @@ decquoted(char *out, char *in, char *e, int uscores)
                in = nl + 1;
        }
        if(in < e)
-               p = decquotedline(p, in, e-1, uscores);
+               p = decquotedline(p, in, e - 1, uscores);
 
-       // make sure we end with a new line
-       if(*(p-1) != '\n'){
+       /* make sure we end with a new line */
+       if(*(p - 1) != '\n'){
                *p++ = '\n';
                *p = 0;
        }
@@ -1195,7 +1372,7 @@ decquoted(char *out, char *in, char *e, int uscores)
        return p - out;
 }
 
-static char*
+char*
 lowercase(char *p)
 {
        char *op;
@@ -1207,7 +1384,7 @@ lowercase(char *p)
        return op;
 }
 
-// translate latin1 directly since it fits neatly in utf
+/* translate latin1 directly since it fits neatly in utf */
 static int
 latin1toutf(char **out, char *in, char *e)
 {
@@ -1222,38 +1399,35 @@ latin1toutf(char **out, char *in, char *e)
        if(n == 0)
                return 0;
 
-       n += e-in;
-       *out = p = malloc(UTFmax*n+1);
+       n += e - in;
+       *out = p = malloc(n + 1);
        if(p == nil)
                return 0;
 
        for(; in < e; in++){
-               r = (*in) & 0xff;
+               r = (uchar)*in;
                p += runetochar(p, &r);
        }
        *p = 0;
        return p - *out;
 }
 
-// translate any thing using the tcs program
+/* translate any thing using the tcs program */
 int
 xtoutf(char *charset, char **out, char *in, char *e)
 {
-       char *av[4];
-       int totcs[2];
-       int fromtcs[2];
-       int n, len, sofar;
-       char *p;
+       char *av[4], *p;
+       int totcs[2], fromtcs[2], n, len, sofar;
 
-       // might not need to convert
+       /* might not need to convert */
        if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0)
                return 0;
        if(cistrcmp(charset, "iso-8859-1") == 0)
                return latin1toutf(out, in, e);
 
-       len = e-in+1;
+       len = e - in + 1;
        sofar = 0;
-       *out = p = malloc(len+1);
+       *out = p = malloc(len + 1);
        if(p == nil)
                return 0;
 
@@ -1279,7 +1453,7 @@ xtoutf(char *charset, char **out, char *in, char *e)
                close(fromtcs[1]); close(totcs[0]);
                dup(open("/dev/null", OWRITE), 2);
                exec("/bin/tcs", av);
-               _exits(0);
+               _exits("");
        default:
                close(fromtcs[1]); close(totcs[0]);
                switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
@@ -1289,24 +1463,24 @@ xtoutf(char *charset, char **out, char *in, char *e)
                case 0:
                        close(fromtcs[0]);
                        while(in < e){
-                               n = write(totcs[1], in, e-in);
+                               n = write(totcs[1], in, e - in);
                                if(n <= 0)
                                        break;
                                in += n;
                        }
                        close(totcs[1]);
-                       _exits(0);
+                       _exits("");
                default:
                        close(totcs[1]);
                        for(;;){
-                               n = read(fromtcs[0], &p[sofar], len-sofar);
+                               n = read(fromtcs[0], &p[sofar], len - sofar);
                                if(n <= 0)
                                        break;
                                sofar += n;
                                p[sofar] = 0;
                                if(sofar == len){
                                        len += 1024;
-                                       p = realloc(p, len+1);
+                                       p = realloc(p, len + 1);
                                        if(p == nil)
                                                goto error;
                                        *out = p;
@@ -1333,10 +1507,8 @@ emalloc(ulong n)
        void *p;
 
        p = mallocz(n, 1);
-       if(!p){
-               fprint(2, "%s: out of memory alloc %lud\n", argv0, n);
-               exits("out of memory");
-       }
+       if(!p)
+               sysfatal("malloc %lud: %r", n);
        setmalloctag(p, getcallerpc(&n));
        return p;
 }
@@ -1347,53 +1519,57 @@ erealloc(void *p, ulong n)
        if(n == 0)
                n = 1;
        p = realloc(p, n);
-       if(!p){
-               fprint(2, "%s: out of memory realloc %lud\n", argv0, n);
-               exits("out of memory");
-       }
+       if(!p)
+               sysfatal("realloc %lud: %r", n);
        setrealloctag(p, getcallerpc(&p));
        return p;
 }
 
+int
+myplumbsend(int fd, Plumbmsg *m)
+{
+       char *buf;
+       int n;
+
+       buf = plumbpack(m, &n);
+       if(buf == nil)
+               return -1;
+       n = write(fd, buf, n);
+       free(buf);
+       return n;
+}
+
 void
 mailplumb(Mailbox *mb, Message *m, int delete)
 {
+       char buf[256], dbuf[SHA1dlen*2 + 1], len[10], date[30], *from, *subject;
+       int ai, cache;
        Plumbmsg p;
        Plumbattr a[7];
-       char buf[256];
-       int ai;
-       char lenstr[10], *from, *subject, *date;
        static int fd = -1;
 
-       if(m->subject822 == nil)
+       cache = insurecache(mb, m) == 0;        /* living dangerously if deleted */
+       subject = m->subject;
+       if(subject == nil)
                subject = "";
-       else
-               subject = s_to_c(m->subject822);
 
-       if(m->from822 != nil)
-               from = s_to_c(m->from822);
+       if(m->from != nil)
+               from = m->from;
        else if(m->unixfrom != nil)
-               from = s_to_c(m->unixfrom);
+               from = m->unixfrom;
        else
                from = "";
 
-       if(m->unixdate != nil)
-               date = s_to_c(m->unixdate);
-       else
-               date = "";
-
-       sprint(lenstr, "%zd", m->end-m->start);
-
+       sprint(len, "%lud", m->size);
        if(biffing && !delete)
-               print("[ %s / %s / %s ]\n", from, subject, lenstr);
-
+               fprint(2, "[ %s / %s / %s ]\n", from, subject, len);
        if(!plumbing)
-               return;
+               goto out;
 
        if(fd < 0)
                fd = plumbopen("send", OWRITE);
        if(fd < 0)
-               return;
+               goto out;
 
        p.src = "mailfs";
        p.dst = "seemail";
@@ -1409,102 +1585,123 @@ mailplumb(Mailbox *mb, Message *m, int delete)
        a[ai-1].next = &a[ai];
 
        a[++ai].name = "length";
-       a[ai].value = lenstr;
+       a[ai].value = len;
        a[ai-1].next = &a[ai];
 
        a[++ai].name = "mailtype";
-       a[ai].value = delete?"delete":"new";
+       a[ai].value = delete? "delete": "new";
        a[ai-1].next = &a[ai];
 
+       snprint(date, sizeof date, "%Δ", m->fileid);
        a[++ai].name = "date";
        a[ai].value = date;
        a[ai-1].next = &a[ai];
 
-       if(m->sdigest){
+       if(m->digest){
+               snprint(dbuf, sizeof dbuf, "%A", m->digest);
                a[++ai].name = "digest";
-               a[ai].value = s_to_c(m->sdigest);
+               a[ai].value = dbuf;
                a[ai-1].next = &a[ai];
        }
-
        a[ai].next = nil;
-
        p.attr = a;
-       snprint(buf, sizeof(buf), "%s/%s/%s",
+       snprint(buf, sizeof buf, "%s/%s/%s",
                mntpt, mb->name, m->name);
        p.ndata = strlen(buf);
        p.data = buf;
 
-       plumbsend(fd, &p);
+       myplumbsend(fd, &p);
+out:
+       if(cache)
+               msgdecref(mb, m);
 }
 
-//
-// count the number of lines in the body (for imap4)
-//
-void
+/*
+ *  count the number of lines in the body (for imap4)
+ */
+ulong
 countlines(Message *m)
 {
-       int i;
        char *p;
+       ulong i;
 
        i = 0;
-       for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n'))
+       for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p + 1, '\n'))
                i++;
-       sprint(m->lines, "%d", i);
+       return i;
 }
 
-char *LOG = "fs";
+static char *logf = "fs";
 
 void
-logmsg(char *s, Message *m)
+logmsg(Message *m, char *fmt, ...)
 {
-       int pid;
+       char buf[256], *p, *e;
+       va_list args;
 
-       if(!logging)
+       if(!lflag)
                return;
-       pid = getpid();
-       if(m == nil)
-               syslog(0, LOG, "%s.%d: %s", user, pid, s);
-       else
-               syslog(0, LOG, "%s.%d: %s msg from %s digest %s",
-                       user, pid, s,
-                       m->from822 ? s_to_c(m->from822) : "?",
-                       s_to_c(m->sdigest));
+       e = buf + sizeof buf;
+       p = seprint(buf, e, "%s.%d: ", user, getpid());
+       if(m)
+               p = seprint(p, e, "from %s digest %A ",
+                       m->from, m->digest);
+       va_start(args, fmt);
+       vseprint(p, e, fmt, args);
+       va_end(args);
+
+       if(Sflag)
+               fprint(2, "%s\n", buf);
+       syslog(Sflag, logf, "%s", buf);
 }
 
-/*
- *  squeeze nulls out of the body
- */
-static void
-nullsqueeze(Message *m)
+void
+iprint(char *fmt, ...)
 {
-       char *p, *q;
+       char buf[256], *p, *e;
+       va_list args;
 
-       q = memchr(m->body, 0, m->end-m->body);
-       if(q == nil)
+       if(!iflag)
                return;
-
-       for(p = m->body; q < m->end; q++){
-               if(*q == 0)
-                       continue;
-               *p++ = *q;
-       }
-       m->bend = m->rbend = m->end = p;
+       e = buf + sizeof buf;
+       p = seprint(buf, e, "%s.%d: ", user, getpid());
+       va_start(args, fmt);
+       vseprint(p, e, fmt, args);
+       vfprint(2, fmt, args);
+       va_end(args);
+       syslog(Sflag, logf, "%s", buf);
 }
 
+void
+eprint(char *fmt, ...)
+{
+       char buf[256], buf2[256], *p, *e;
+       va_list args;
+
+       e = buf + sizeof buf;
+       p = seprint(buf, e, "%s.%d: ", user, getpid());
+       va_start(args, fmt);
+       vseprint(p, e, fmt, args);
+       e = buf2 + sizeof buf2;
+       p = seprint(buf2, e, "upas/fs: ");
+       vseprint(p, e, fmt, args);
+       va_end(args);
+       syslog(Sflag, logf, "%s", buf);
+       fprint(2, "%s", buf2);
+}
 
-//
-// convert an RFC822 date into a Unix style date
-// for when the Unix From line isn't there (e.g. POP3).
-// enough client programs depend on having a Unix date
-// that it's easiest to write this conversion code once, right here.
-//
-// people don't follow RFC822 particularly closely,
-// so we use strtotm, which is a bunch of heuristics.
-//
+/*
+ *  convert an RFC822 date into a Unix style date
+ *  for when the Unix From line isn't there (e.g. POP3).
+ *  enough client programs depend on having a Unix date
+ *  that it's easiest to write this conversion code once, right here.
+ *
+ *  people don't follow RFC822 particularly closely,
+ *  so we use strtotm, which is a bunch of heuristics.
+ */
 
-extern int strtotm(char*, Tm*);
-String*
-date822tounix(char *s)
+char*
+date822tounix(Message *, char *s)
 {
        char *p, *q;
        Tm tm;
@@ -1515,6 +1712,5 @@ date822tounix(char *s)
        p = asctime(&tm);
        if(q = strchr(p, '\n'))
                *q = '\0';
-       return s_copy(p);
+       return strdup(p);
 }
-
diff --git a/sys/src/cmd/upas/fs/mdir.c b/sys/src/cmd/upas/fs/mdir.c
new file mode 100644 (file)
index 0000000..69d53e4
--- /dev/null
@@ -0,0 +1,354 @@
+#include "common.h"
+#include "dat.h"
+
+typedef struct {
+       int     debug;
+} Mdir;
+
+#define        mdprint(mdir, ...)      if(mdir->debug) fprint(2, __VA_ARGS__)
+
+static int
+slurp(char *f, char *b, uvlong o, long l)
+{
+       int fd, r;
+
+       if((fd = open(f, OREAD)) == -1)
+               return -1;
+
+       seek(fd, o, 0);
+       r = readn(fd, b, l) != l;
+       close(fd);
+       return r? -1: 0;
+}
+
+static void
+parseunix(Message *m)
+{
+       char *s, *p;
+       int l;
+
+       l = m->header - m->start;
+       m->unixheader = smprint("%.*s", l, m->start);
+       s = m->start + 5;
+       if((p = strchr(s, ' ')) == nil)
+               abort();
+       *p = 0;
+       m->unixfrom = strdup(s);
+       *p = ' ';
+}
+
+static int
+mdirfetch(Mailbox *mb, Message *m, uvlong o, ulong l)
+{
+       char buf[Pathlen], *x;
+       Mdir *mdir;
+
+       mdir = mb->aux;
+       mdprint(mdir, "mdirfetch(%D) ...", m->fileid);
+
+       snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid);
+       if(slurp(buf, m->start + o, o, l) == -1){
+               logmsg(m, "fetch failed: %r");
+               mdprint(mdir, "%r\n");
+               return -1;
+       }
+       if(m->header == nil)
+               m->header = m->start;
+       if(m->header == m->start)
+       if(o + l >= 36)
+       if(strncmp(m->start, "From ", 5) == 0)
+       if(x = strchr(m->start, '\n')){
+               m->header = x + 1;
+               if(m->unixfrom == nil)
+                       parseunix(m);
+       }
+       m->mheader = m->mhend = m->header;
+       mdprint(mdir, "fetched [%llud, %llud]\n", o, o + l);
+       return 0;
+}
+
+static int
+setsize(Mailbox *mb, Message *m)
+{
+       char buf[Pathlen];
+       Dir *d;
+
+       snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid);
+       if((d = dirstat(buf)) == nil)
+               return -1;
+       m->size = d->length;
+       free(d);
+       return 0;
+}
+
+/* must be [0-9]+(\..*)? */
+int
+dirskip(Dir *a, uvlong *uv)
+{
+       char *p;
+
+       if(a->length == 0)
+               return 1;
+       *uv = strtoul(a->name, &p, 0);
+       if(*uv < 1000000 || *p != '.')
+               return 1;
+       *uv = *uv<<8 | strtoul(p + 1, &p, 10);
+       if(*p)
+               return 1;
+       return 0;
+}
+
+static int
+vcmp(vlong a, vlong b)
+{
+       a -= b;
+       if(a > 0)
+               return 1;
+       if(a < 0)
+               return -1;
+       return 0;
+}
+
+static int
+dircmp(Dir *a, Dir *b)
+{
+       uvlong x, y;
+
+       dirskip(a, &x);
+       dirskip(b, &y);
+       return vcmp(x, y);
+}
+
+static char*
+mdirread(Mdir* mdir, Mailbox* mb, int doplumb, int *new)
+{
+       int i, nnew, ndel, fd, n, c;
+       uvlong uv;
+       Dir *d;
+       Message *m, **ll;
+       static char err[ERRMAX];
+
+       mdprint(mdir, "mdirread()\n");
+       if((fd = open(mb->path, OREAD)) == -1){
+               errstr(err, sizeof err);
+               return err;
+       }
+       if((d = dirfstat(fd)) == nil){
+               errstr(err, sizeof err);
+               close(fd);
+               return err;
+       }
+       *new = nnew = 0;
+       if(mb->d){
+               if(d->qid.path == mb->d->qid.path)
+               if(d->qid.vers == mb->d->qid.vers){
+                       mdprint(mdir, "\tqids match\n");
+                       close(fd);
+                       free(d);
+                       goto finished;
+               }
+               free(mb->d);
+       }
+       logmsg(nil, "reading %s (mdir)", mb->path);
+       mb->d = d;
+
+       n = dirreadall(fd, &d);
+       close(fd);
+       if(n == -1){
+               errstr(err, sizeof err);
+               return err;
+       }
+
+       qsort(d, n, sizeof *d, (int(*)(void*, void*))dircmp);
+       ndel = 0;
+       ll = &mb->root->part;
+       for(i = 0; *ll || i < n; ){
+               if(i < n && dirskip(d + i, &uv)){
+                       i++;
+                       continue;
+               }
+               c = -1;
+               if(i >= n)
+                       c = 1;
+               else if(*ll)
+                       c = vcmp(uv, (*ll)->fileid);
+               mdprint(mdir, "consider %s and %D -> %d\n", i<n? d[i].name: 0, *ll? (*ll)->fileid: 1ull, c);
+               if(c < 0){
+                       /* new message */
+                       mdprint(mdir, "new: %s (%D)\n", d[i].name, *ll? (*ll)->fileid: 0);
+                       m = newmessage(mb->root);
+                       m->fileid = uv;
+                       if(setsize(mb, m) < 0 || m->size >= Maxmsg){
+                               /* message disappeared?  unchain */
+                               mdprint(mdir, "deleted → %r (%D)\n", m->fileid);
+                               logmsg(m, "disappeared");
+                               if(doplumb)
+                                       mailplumb(mb, m, 1); /* redundant */
+                               unnewmessage(mb, mb->root, m);
+                               /* we're out of sync; note this by dropping cached qid */
+                               mb->d->qid.path = 0;
+                               break;
+                       }
+                       m->inmbox = 1;
+                       nnew++;
+                       m->next = *ll;
+                       *ll = m;
+                       ll = &m->next;
+                       logmsg(m, "new %s", d[i].name);
+                       i++;
+                       newcachehash(mb, m, doplumb);
+                       putcache(mb, m);
+               }else if(c > 0){
+                       /* deleted message; */
+                       mdprint(mdir, "deleted: %s (%D)\n", i<n? d[i].name: 0, *ll? (*ll)->fileid: 0);
+                       ndel++;
+                       logmsg(*ll, "deleted (refs=%d)", *ll? (*ll)->refs: -42);
+                       if(doplumb)
+                               mailplumb(mb, *ll, 1);
+                       (*ll)->inmbox = 0;
+                       (*ll)->deleted = Disappear;
+                       ll = &(*ll)->next;
+               }else{
+                       //logmsg(*ll, "duplicate %s", d[i].name);
+                       i++;
+                       ll = &(*ll)->next;
+               }
+       }
+
+       free(d);
+       logmsg(nil, "mbox read: %d new %d deleted", nnew, ndel);
+finished:
+       *new = nnew;
+       return nil;
+}
+
+static void
+mdirdelete(Mailbox *mb, Message *m)
+{
+       char mpath[Pathlen];
+       Mdir* mdir;
+
+       mdir = mb->aux;
+       snprint(mpath, sizeof mpath, "%s/%D", mb->path, m->fileid);
+       mdprint(mdir, "remove: %s\n", mpath);
+       /* may have been removed by other fs.  just log the error. */
+       if(remove(mpath) == -1)
+               logmsg(m, "remove: %s: %r", mpath);
+       m->inmbox = 0;
+}
+
+static char*
+mdirsync(Mailbox* mb, int doplumb, int *new)
+{
+       Mdir *mdir;
+
+       mdir = mb->aux;
+       mdprint(mdir, "mdirsync()\n");
+       return mdirread(mdir, mb, doplumb, new);
+}
+
+static char*
+mdirctl(Mailbox *mb, int c, char **v)
+{
+       Mdir *mdir;
+
+       mdir = mb->aux;
+       if(c == 1 && strcmp(*v, "debug") == 0)
+               mdir->debug = 1;
+       else if(c == 1 && strcmp(*v, "nodebug") == 0)
+               mdir->debug = 0;
+       else
+               return "bad mdir control message";
+       return nil;
+}
+
+static void
+mdirclose(Mailbox *mb)
+{
+       free(mb->aux);
+}
+
+static int
+qidcmp(Qid *a, Qid *b)
+{
+       if(a->path != b->path)
+               return 1;
+       return a->vers - b->vers;
+}
+
+/*
+ * .idx strategy. we save the qid.path and .vers
+ * of the mdir directory and the date to the index.
+ * we accept the work done by the other upas/fs if
+ * the index is based on the same (or a newer)
+ * qid.  in any event, we recheck the directory after
+ * the directory is four hours old.
+ */
+static int
+idxr(char *s, Mailbox *mb)
+{
+       char *f[5];
+       long t, δt, n;
+       Dir d;
+
+       n = tokenize(s, f, nelem(f));
+       if(n != 4 || strcmp(f[0], "mdirv1") != 0)
+               return -1;
+       t = strtoul(f[1], 0, 0);
+       δt = time(0) - t;
+       if(δt < 0 || δt > 4*3600)
+               return 0;
+       memset(&d, 0, sizeof d);
+       d.qid.path = strtoull(f[2], 0, 0);
+       d.qid.vers = strtoull(f[3], 0, 0);
+       if(mb->d && qidcmp(&mb->d->qid, &d.qid) >= 0)
+               return 0;
+       if(mb->d == 0)
+               mb->d = emalloc(sizeof d);
+       mb->d->qid = d.qid;
+       mb->d->mtime = t;
+       return 0;
+}
+
+static void
+idxw(Biobuf *b, Mailbox *mb)
+{
+       Qid q;
+
+       memset(&q, 0, sizeof q);
+       if(mb->d)
+               q = mb->d->qid;
+       Bprint(b, "mdirv1 %lud %llud %lud\n", time(0), q.path, q.vers);
+}
+
+char*
+mdirmbox(Mailbox *mb, char *path)
+{
+       int m;
+       Dir *d;
+       Mdir *mdir;
+
+       d = dirstat(path);
+       if(!d && mb->flags & DMcreate){
+               createfolder(getuser(), path);
+               d = dirstat(path);
+       }
+       m = d && (d->mode & DMDIR);
+       free(d);
+       if(!m)
+               return Enotme;
+       snprint(mb->path, sizeof mb->path, "%s", path);
+       mdir = emalloc(sizeof *mdir);
+       mdir->debug = 0;
+       mb->aux = mdir;
+       mb->sync = mdirsync;
+       mb->close = mdirclose;
+       mb->fetch = mdirfetch;
+       mb->delete = mdirdelete;
+       mb->remove = localremove;
+       mb->rename = localrename;
+       mb->idxread = idxr;
+       mb->idxwrite = idxw;
+       mb->ctl = mdirctl;
+       return nil;
+}
index 4a7132b861dc78a827f855440ab8c5f87f624da1..814787a079b0adb083ad74279b3d10a1bc91b817 100644 (file)
@@ -1,14 +1,24 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=  fs\
 
 OFILES=\
+       bos.$O\
+       cache.$O\
        fs.$O\
-       imap4.$O\
+       header.$O\
+       idx.$O\
+       imap.$O\
        mbox.$O\
+       mdir.$O\
+       mtree.$O\
        plan9.$O\
        planb.$O\
        pop3.$O\
+       ref.$O\
+       remove.$O\
+       rename.$O\
        strtotm.$O\
 
 LIB=../common/libcommon.a$O\
@@ -16,8 +26,6 @@ LIB=../common/libcommon.a$O\
 HFILES= ../common/common.h\
        dat.h
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        $HFILES\
@@ -25,4 +33,10 @@ UPDATE=\
        ${OFILES:%.$O=%.c}\
 
 </sys/src/cmd/mkone
-CFLAGS=$CFLAGS -I/sys/include -I../common
+CFLAGS=$CFLAGS  -I../common
+
+acid:V:
+       $CC -a $CFLAGS fs.c>a$O
+
+chkidx: mtree.$O chkidx.$O
+       $LD $LDFLAGS -o $target $prereq
diff --git a/sys/src/cmd/upas/fs/mtree.c b/sys/src/cmd/upas/fs/mtree.c
new file mode 100644 (file)
index 0000000..4f2af56
--- /dev/null
@@ -0,0 +1,80 @@
+#include "common.h"
+#include <libsec.h>
+#include "dat.h"
+
+int
+mtreecmp(Avl *va, Avl *vb)
+{
+       Mtree *a, *b;
+
+       a = (Mtree*)va;
+       b = (Mtree*)vb;
+       return memcmp(a->m->digest, b->m->digest, SHA1dlen);
+}
+
+int
+mtreeisdup(Mailbox *mb, Message *m)
+{
+       Mtree t;
+
+       assert(Topmsg(mb, m) && m->digest);
+       if(!m->digest)
+               return 0;
+       memset(&t, 0, sizeof t);
+       t.m = m;
+       if(avllookup(mb->mtree, &t))
+               return 1;
+       return 0;
+}
+
+Message*
+mtreefind(Mailbox *mb, uchar *digest)
+{
+       Message m0;
+       Mtree t, *p;
+
+       m0.digest = digest;
+       memset(&t, 0, sizeof t);
+       t.m = &m0;
+       if(p = (Mtree*)avllookup(mb->mtree, &t))
+               return p->m;
+       return nil;
+}
+
+void
+mtreeadd(Mailbox *mb, Message *m)
+{
+       Avl *old;
+       Mtree *p;
+
+       assert(Topmsg(mb, m) && m->digest);
+       p = emalloc(sizeof *p);
+       p->m = m;
+       old = avlinsert(mb->mtree, p);
+       assert(old == 0);
+}
+
+void
+mtreedelete(Mailbox *mb, Message *m)
+{
+       Mtree t, *p;
+
+       assert(Topmsg(mb, m));
+       memset(&t, 0, sizeof t);
+       t.m = m;
+       if(m->deleted & ~Deleted){
+               if(m->digest == nil)
+                       return;
+               p = (Mtree*)avllookup(mb->mtree, &t);
+               if(p == nil || p->m != m)
+                       return;
+               p = (Mtree*)avldelete(mb->mtree, &t);
+               free(p);
+               return;
+       }
+       assert(m->digest);
+       p = (Mtree*)avldelete(mb->mtree, &t);
+       if(p == nil)
+               _assert("mtree delete fails");
+       free(p);
+}
index 171b156170adf1f79f655028b5b5fb2f7d3af9e6..1d65a62e0dac7ce0584a560e935fe722e61d48d5 100644 (file)
 #include "common.h"
-#include <ctype.h>
-#include <plumb.h>
 #include <libsec.h>
 #include "dat.h"
 
-enum {
-       Buffersize = 64*1024,
-};
+typedef struct {
+       Biobuf  *in;
+       char    *shift;
+} Inbuf;
 
-typedef struct Inbuf Inbuf;
-struct Inbuf
+/*
+ *  parse a Unix style header
+ */
+static int
+memtotm(char *p, int n, Tm *t)
+{
+       char buf[128];
+
+       if(n > sizeof buf - 1)
+               n = sizeof buf -1;
+       memcpy(buf, p, n);
+       buf[n] = 0;
+       return strtotm(buf, t);
+}
+
+static int
+chkunix0(char *s, int n)
+{
+       char *p;
+       Tm tm;
+
+       if(n > 256)
+               return -1;
+       if((p = memchr(s, ' ', n)) == nil)
+               return -1;
+       if(memtotm(p, n - (p - s), &tm) < 0)
+               return -1;
+       if(tm2sec(&tm) < 1000000)
+               return -1;
+       return 0;
+}
+
+static int
+chkunix(char *s, int n)
 {
-       int     fd;
-       uchar   *lim;
-       uchar   *rptr;
-       uchar   *wptr;
-       uchar   data[Buffersize+7];
-};
+       int r;
+
+       r = chkunix0(s, n);
+       if(r == -1)
+               eprint("plan9: warning naked from [%.*s]\n", n, s);
+       return r;
+}
+
+static char*
+parseunix(Message *m)
+{
+       char *s, *p, *q;
+       int l;
+       Tm tm;
+
+       l = m->header - m->start;
+       m->unixheader = smprint("%.*s", l, m->start);
+       s = m->start + 5;
+       if((p = strchr(s, ' ')) == nil)
+               return s;
+       *p = 0;
+       m->unixfrom = strdup(s);
+       *p++ = ' ';
+       if(q = strchr(p, '\n'))
+               *q = 0;
+       if(strtotm(p, &tm) < 0)
+               return p;
+       if(q)
+               *q = '\n';
+       m->fileid = (uvlong)tm2sec(&tm) << 8;
+       return 0;
+}
 
 static void
-addtomessage(Message *m, uchar *p, int n, int done)
+addtomessage(Message *m, char *p, int n)
 {
        int i, len;
 
-       // add to message (+1 in malloc is for a trailing NUL)
+       if(n == 0)
+               return;
+       /* add to message (+1 in malloc is for a trailing NUL) */
        if(m->lim - m->end < n){
                if(m->start != nil){
-                       i = m->end-m->start;
-                       if(done)
-                               len = i + n;
-                       else
-                               len = (4*(i+n))/3;
+                       i = m->end - m->start;
+                       len = (4*(i + n))/3;
                        m->start = erealloc(m->start, len + 1);
                        m->end = m->start + i;
                } else {
-                       if(done)
-                               len = n;
-                       else
-                               len = 2*n;
+                       len = 2*n;
                        m->start = emalloc(len + 1);
                        m->end = m->start;
                }
                m->lim = m->start + len;
-               *m->lim = '\0';
+               *m->lim = 0;
        }
 
        memmove(m->end, p, n);
        m->end += n;
-       *m->end = '\0';
+       *m->end = 0;
 }
 
-//
-//  read in a single message
-//
+/*
+ *   read in a single message
+ */
 static int
-readmessage(Message *m, Inbuf *inb)
+okmsg(Mailbox *mb, Message *m, Inbuf *b)
 {
-       int i, n, done;
-       uchar *p, *np;
-       char sdigest[SHA1dlen*2+1];
-       char tmp[64];
-
-       for(done = 0; !done;){
-               n = inb->wptr - inb->rptr;
-               if(n < 6){
-                       if(n)
-                               memmove(inb->data, inb->rptr, n);
-                       inb->rptr = inb->data;
-                       inb->wptr = inb->rptr + n;
-                       i = read(inb->fd, inb->wptr, Buffersize);
-                       if(i < 0){
-                               if(fd2path(inb->fd, tmp, sizeof tmp) < 0)
-                                       strcpy(tmp, "unknown mailbox");
-                               fprint(2, "error reading '%s': %r\n", tmp);
-                               return -1;
-                       }
-                       if(i == 0){
-                               if(n != 0)
-                                       addtomessage(m, inb->rptr, n, 1);
-                               if(m->end == m->start)
-                                       return -1;
-                               break;
-                       }
-                       inb->wptr += i;
-               }
-
-               // look for end of message
-               for(p = inb->rptr; p < inb->wptr; p = np+1){
-                       // first part of search for '\nFrom '
-                       np = memchr(p, '\n', inb->wptr - p);
-                       if(np == nil){
-                               p = inb->wptr;
-                               break;
-                       }
-
-                       /*
-                        *  if we've found a \n but there's
-                        *  not enough room for '\nFrom ', don't do
-                        *  the comparison till we've read in more.
-                        */
-                       if(inb->wptr - np < 6){
-                               p = np;
-                               break;
-                       }
-
-                       if(strncmp((char*)np, "\nFrom ", 6) == 0){
-                               done = 1;
-                               p = np+1;
-                               break;
-                       }
-               }
+       char e[ERRMAX], buf[128];
 
-               // add to message (+ 1 in malloc is for a trailing null)
-               n = p - inb->rptr;
-               addtomessage(m, inb->rptr, n, done);
-               inb->rptr += n;
+       rerrstr(e, sizeof e);
+       if(strlen(e)){
+               if(fd2path(Bfildes(b->in), buf, sizeof buf) < 0)
+                       strcpy(buf, "unknown mailbox");
+               eprint("plan9: error reading %s: %r\n", buf);
+               return -1;
        }
-
-       // if it doesn't start with a 'From ', this ain't a mailbox
-       if(strncmp(m->start, "From ", 5) != 0)
+       if(m->end == m->start)
                return -1;
-
-       // dump trailing newline, make sure there's a trailing null
-       // (helps in body searches)
-       if(*(m->end-1) == '\n')
+       if(m->end[-1] == '\n')
                m->end--;
        *m->end = 0;
+       m->size = m->end - m->start;
+       if(m->size >= Maxmsg)
+               return -1;
        m->bend = m->rbend = m->end;
+       if(m->digest == 0)
+               digestmessage(mb, m);
+       return 0;
+}
 
-       // digest message
-       sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
-       for(i = 0; i < SHA1dlen; i++)
-               sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
-       m->sdigest = s_copy(sdigest);
+static char*
+inbread(Inbuf *b)
+{
+       if(b->shift)
+               return b->shift;
+       return b->shift = Brdline(b->in, '\n');
+}
 
-       return 0;
+void
+inbconsume(Inbuf *b)
+{
+       b->shift = 0;
 }
 
+/*
+ * bug: very long line with From at the buffer break.
+ */
+static int
+readmessage(Mailbox *mb, Message *m, Inbuf *b)
+{
+       char *s, *n;
+       long l, state;
+
+       werrstr("");
+       state = 0;
+       for(;;){
+               s = inbread(b);
+               if(s == 0)
+                       break;
+               n = s + (l = Blinelen(b->in)) - 1;
+               if(l >= 28 + 7 && n[0] == '\n')
+               if(strncmp(s, "From ", 5) == 0)
+               if(!chkunix(s + 5, l - 5))
+               if(++state == 2)
+                       break;
+               if(state == 0)
+                       return -1;
+               addtomessage(m, s, l);
+               inbconsume(b);
+       }
+       return okmsg(mb, m, b);
+}
 
-// throw out deleted messages.  return number of freshly deleted messages
+/* throw out deleted messages.  return number of freshly deleted messages */
 int
 purgedeleted(Mailbox *mb)
 {
        Message *m, *next;
        int newdels;
 
-       // forget about what's no longer in the mailbox
+       /* forget about what's no longer in the mailbox */
        newdels = 0;
        for(m = mb->root->part; m != nil; m = next){
                next = m->next;
@@ -158,44 +195,51 @@ purgedeleted(Mailbox *mb)
        return newdels;
 }
 
-//
-//  read in the mailbox and parse into messages.
-//
+static void
+mergemsg(Message *m, Message *x)
+{
+       assert(m->start == 0);
+       m->mallocd = 1;
+       m->inmbox = 1;
+       m->lim = x->lim;
+       m->start = x->start;
+       m->end = x->end;
+       m->bend = x->bend;
+       m->rbend = x->rbend;
+       x->lim = 0;
+       x->start = 0;
+       x->end = 0;
+       x->bend = 0;
+       x->rbend = 0;
+}
+
+/*
+ *   read in the mailbox and parse into messages.
+ */
 static char*
-_readmbox(Mailbox *mb, int doplumb, Mlock *lk)
+readmbox(Mailbox *mb, int doplumb, int *new, Mlock *lk)
 {
-       int fd, n;
-       String *tmp;
+       char *p, *x, buf[Pathlen];
+       int nnew;
+       Biobuf *in;
        Dir *d;
-       static char err[Errlen];
+       Inbuf b;
        Message *m, **l;
-       Inbuf *inb;
-       char *x;
+       static char err[ERRMAX];
 
        l = &mb->root->part;
 
        /*
         *  open the mailbox.  If it doesn't exist, try the temporary one.
         */
-       n = 0;
 retry:
-       fd = open(mb->path, OREAD);
-       if(fd < 0){
-               rerrstr(err, sizeof(err));
-               if(strstr(err, "locked") != nil
-               || strstr(err, "exclusive lock") != nil)
-                       if(n++ < 20){
-                               sleep(500);     /* wait for lock to go away */
-                               goto retry;
-                       }
-               if(strstr(err, "exist") != nil){
-                       tmp = s_copy(mb->path);
-                       s_append(tmp, ".tmp");
-                       if(sysrename(s_to_c(tmp), mb->path) == 0){
-                               s_free(tmp);
+       in = Bopen(mb->path, OREAD);
+       if(in == nil){
+               errstr(err, sizeof(err));
+               if(strstr(err, "exist") != 0){
+                       snprint(buf, sizeof buf, "%s.tmp", mb->path);
+                       if(sysrename(buf, mb->path) == 0)
                                goto retry;
-                       }
-                       s_free(tmp);
                }
                return err;
        }
@@ -204,173 +248,170 @@ retry:
         *  a new qid.path means reread the mailbox, while
         *  a new qid.vers means read any new messages
         */
-       d = dirfstat(fd);
+       d = dirfstat(Bfildes(in));
        if(d == nil){
-               close(fd);
-               errstr(err, sizeof(err));
+               Bterm(in);
+               errstr(err, sizeof err);
                return err;
        }
        if(mb->d != nil){
                if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){
-                       close(fd);
+                       *new = 0;
+                       Bterm(in);
                        free(d);
                        return nil;
                }
                if(d->qid.path == mb->d->qid.path){
                        while(*l != nil)
                                l = &(*l)->next;
-                       seek(fd, mb->d->length, 0);
+                       Bseek(in, mb->d->length, 0);
                }
                free(mb->d);
        }
        mb->d = d;
-       mb->vers++;
-       henter(PATH(0, Qtop), mb->name,
-               (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
 
-       inb = emalloc(sizeof(Inbuf));
-       inb->rptr = inb->wptr = inb->data;
-       inb->fd = fd;
+       memset(&b, 0, sizeof b);
+       b.in = in;
+       b.shift = 0;
 
-       //  read new messages
-       snprint(err, sizeof err, "reading '%s'", mb->path);
-       logmsg(err, nil);
+       /*  read new messages */
+       logmsg(nil, "reading %s", mb->path);
+       nnew = 0;
        for(;;){
                if(lk != nil)
                        syslockrefresh(lk);
                m = newmessage(mb->root);
                m->mallocd = 1;
                m->inmbox = 1;
-               if(readmessage(m, inb) < 0){
-                       delmessage(mb, m);
-                       mb->root->subname--;
+               if(readmessage(mb, m, &b) < 0){
+                       unnewmessage(mb, mb->root, m);
                        break;
                }
-
-               // merge mailbox versions
+               /* merge mailbox versions */
                while(*l != nil){
                        if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){
-                               // matches mail we already read, discard
-                               logmsg("duplicate", *l);
-                               delmessage(mb, m);
-                               mb->root->subname--;
-                               m = nil;
-                               l = &(*l)->next;
+                               if((*l)->start == nil){
+                                       logmsg(*l, "read indexed");
+                                       mergemsg(*l, m);
+                                       unnewmessage(mb, mb->root, m);
+                                       m = *l;
+                               }else{
+                                       logmsg(*l, "duplicate");
+                                       m->inmbox = 1;          /* remove it */
+                                       unnewmessage(mb, mb->root, m);
+                                       m = nil;
+                                       l = &(*l)->next;
+                               }
                                break;
                        } else {
-                               // old mail no longer in box, mark deleted
-                               logmsg("disappeared", *l);
+                               /* old mail no longer in box, mark deleted */
+                               logmsg(*l, "disappeared");
                                if(doplumb)
                                        mailplumb(mb, *l, 1);
                                (*l)->inmbox = 0;
-                               (*l)->deleted = 1;
+                               (*l)->deleted = Disappear;
                                l = &(*l)->next;
                        }
                }
                if(m == nil)
                        continue;
-
-               x = strchr(m->start, '\n');
-               if(x == nil)
-                       m->header = m->end;
-               else
+               m->header = m->end;
+               if(x = strchr(m->start, '\n'))
                        m->header = x + 1;
+               if(p = parseunix(m))
+                       sysfatal("%s:%lld naked From in body? [%s]", mb->path, seek(Bfildes(in), 0, 1), p);
                m->mheader = m->mhend = m->header;
-               parseunix(m);
-               parse(m, 0, mb, 0);
-               logmsg("new", m);
-
+               parse(mb, m, 0, 0);
+               if(m != *l && m->deleted != Dup){
+                       logmsg(m, "new");
+                       newcachehash(mb, m, doplumb);
+                       putcache(mb, m);
+                       nnew++;
+               }
                /* chain in */
                *l = m;
                l = &m->next;
-               if(doplumb)
-                       mailplumb(mb, m, 0);
-
        }
-       logmsg("mbox read", nil);
+       logmsg(nil, "mbox read");
 
-       // whatever is left has been removed from the mbox, mark deleted
+       /* whatever is left has been removed from the mbox, mark deleted */
        while(*l != nil){
                if(doplumb)
                        mailplumb(mb, *l, 1);
                (*l)->inmbox = 0;
-               (*l)->deleted = 1;
+               (*l)->deleted = Deleted;
                l = &(*l)->next;
        }
 
-       close(fd);
-       free(inb);
+       Bterm(in);
+       *new = nnew;
        return nil;
 }
 
 static void
-_writembox(Mailbox *mb, Mlock *lk)
+writembox(Mailbox *mb, Mlock *lk)
 {
-       Dir *d;
-       Message *m;
-       String *tmp;
+       char buf[Pathlen];
        int mode, errs;
        Biobuf *b;
+       Dir *d;
+       Message *m;
 
-       tmp = s_copy(mb->path);
-       s_append(tmp, ".tmp");
+       snprint(buf, sizeof buf, "%s.tmp", mb->path);
 
        /*
         * preserve old files permissions, if possible
         */
-       d = dirstat(mb->path);
-       if(d != nil){
-               mode = d->mode&0777;
+       mode = Mboxmode;
+       if(d = dirstat(mb->path)){
+               mode = d->mode & 0777;
                free(d);
-       } else
-               mode = MBOXMODE;
+       }
 
-       sysremove(s_to_c(tmp));
-       b = sysopen(s_to_c(tmp), "alc", mode);
+       remove(buf);
+       b = sysopen(buf, "alc", mode);
        if(b == 0){
-               fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp));
+               eprint("plan9: can't write temporary mailbox %s: %r\n", buf);
                return;
        }
 
-       logmsg("writing new mbox", nil);
+       logmsg(nil, "writing new mbox");
        errs = 0;
        for(m = mb->root->part; m != nil; m = m->next){
                if(lk != nil)
                        syslockrefresh(lk);
                if(m->deleted)
                        continue;
-               logmsg("writing", m);
+               logmsg(m, "writing");
                if(Bwrite(b, m->start, m->end - m->start) < 0)
                        errs = 1;
                if(Bwrite(b, "\n", 1) < 0)
                        errs = 1;
        }
-       logmsg("wrote new mbox", nil);
+       logmsg(nil, "wrote new mbox");
 
        if(sysclose(b) < 0)
                errs = 1;
 
        if(errs){
-               fprint(2, "error writing temporary mail file\n");
-               s_free(tmp);
+               eprint("plan9: error writing temporary mail file\n");
                return;
        }
 
-       sysremove(mb->path);
-       if(sysrename(s_to_c(tmp), mb->path) < 0)
-               fprint(2, "%s: can't rename %s to %s: %r\n", argv0,
-                       s_to_c(tmp), mb->path);
-       s_free(tmp);
+       remove(mb->path);
+       if(sysrename(buf, mb->path) < 0)
+               eprint("plan9: can't rename %s to %s: %r\n",
+                       buf, mb->path);
        if(mb->d != nil)
                free(mb->d);
        mb->d = dirstat(mb->path);
 }
 
 char*
-plan9syncmbox(Mailbox *mb, int doplumb)
+plan9syncmbox(Mailbox *mb, int doplumb, int *new)
 {
-       Mlock *lk;
        char *rv;
+       Mlock *lk;
 
        lk = nil;
        if(mb->dolock){
@@ -379,9 +420,9 @@ plan9syncmbox(Mailbox *mb, int doplumb)
                        return "can't lock mailbox";
        }
 
-       rv = _readmbox(mb, doplumb, lk);                /* interpolate */
+       rv = readmbox(mb, doplumb, new, lk);            /* interpolate */
        if(purgedeleted(mb) > 0)
-               _writembox(mb, lk);
+               writembox(mb, lk);
 
        if(lk != nil)
                sysunlock(lk);
@@ -389,26 +430,30 @@ plan9syncmbox(Mailbox *mb, int doplumb)
        return rv;
 }
 
-//
-//  look to see if we can open this mail box
-//
+void
+plan9decache(Mailbox*, Message *m)
+{
+       m->lim = 0;
+}
+
+/*
+ *   look to see if we can open this mail box
+ */
 char*
 plan9mbox(Mailbox *mb, char *path)
 {
-       static char err[Errlen];
-       String *tmp;
+       char buf[Pathlen];
+       static char err[Pathlen];
 
        if(access(path, AEXIST) < 0){
-               errstr(err, sizeof(err));
-               tmp = s_copy(path);
-               s_append(tmp, ".tmp");
-               if(access(s_to_c(tmp), AEXIST) < 0){
-                       s_free(tmp);
+               errstr(err, sizeof err);
+               snprint(buf, sizeof buf, "%s.tmp", path);
+               if(access(buf, AEXIST) < 0)
                        return err;
-               }
-               s_free(tmp);
        }
-
        mb->sync = plan9syncmbox;
+       mb->remove = localremove;
+       mb->rename = localrename;
+       mb->decache = plan9decache;
        return nil;
 }
index 58dfe16225622d27d36617443f401d19a4e148c0..fbe2ac25108fda2de10004d6fef647aa9c25f4b2 100644 (file)
 #include <libsec.h>
 #include "dat.h"
 
+static char*
+parseunix(Message *m)
+{
+       char *s, *p, *q;
+       int l;
+       Tm tm;
+
+       l = m->header - m->start;
+       m->unixheader = smprint("%.*s", l, m->start);
+       s = m->start + 5;
+       if((p = strchr(s, ' ')) == nil)
+               return s;
+       *p = 0;
+       m->unixfrom = strdup(s);
+       *p++ = ' ';
+       if(q = strchr(p, '\n'))
+               *q = 0;
+       if(strtotm(p, &tm) < 0)
+               return p;
+       if(q)
+               *q = '\n';
+       m->fileid = (uvlong)tm2sec(&tm) << 8;
+       return 0;
+}
+
 static int
 readmessage(Message *m, char *msg)
 {
-       int fd, i, n;
+       int fd, n;
        char *buf, *name, *p;
-       char hdr[128], sdigest[SHA1dlen*2+1];
+       char hdr[128];
        Dir *d;
 
        buf = nil;
@@ -29,8 +54,10 @@ readmessage(Message *m, char *msg)
        if(name == nil)
                return -1;
        if(m->filename != nil)
-               s_free(m->filename);
-       m->filename = s_copy(name);
+               free(m->filename);
+       m->filename = strdup(name);
+       if(m->filename == nil)
+               sysfatal("malloc: %r");
        fd = open(name, OREAD);
        if(fd < 0)
                goto Fail;
@@ -75,10 +102,7 @@ readmessage(Message *m, char *msg)
                m->end--;
        *m->end = 0;
        m->bend = m->rbend = m->end;
-       sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
-       for(i = 0; i < SHA1dlen; i++)
-               sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
-       m->sdigest = s_copy(sdigest);
+
        return 0;
 Fail:
        if(fd >= 0)
@@ -98,7 +122,7 @@ archive(Message *m)
        char *dir, *p, *nname;
        Dir d;
 
-       dir = strdup(s_to_c(m->filename));
+       dir = strdup(m->filename);
        nname = nil;
        if(dir == nil)
                return;
@@ -162,21 +186,20 @@ mustshow(char* name)
 }
 
 static int
-readpbmessage(Mailbox *mb, char *msg, int doplumb)
+readpbmessage(Mailbox *mb, char *msg, int doplumb, int *nnew)
 {
        Message *m, **l;
-       char *x;
+       char *x, *p;
 
        m = newmessage(mb->root);
        m->mallocd = 1;
        m->inmbox = 1;
        if(readmessage(m, msg) < 0){
-               delmessage(mb, m);
-               mb->root->subname--;
+               unnewmessage(mb, mb->root, m);
                return -1;
        }
        for(l = &mb->root->part; *l != nil; l = &(*l)->next)
-               if(strcmp(s_to_c((*l)->filename), s_to_c(m->filename)) == 0 &&
+               if(strcmp((*l)->filename, m->filename) == 0 &&
                    *l != m){
                        if((*l)->deleted < 0)
                                (*l)->deleted = 0;
@@ -184,15 +207,19 @@ readpbmessage(Mailbox *mb, char *msg, int doplumb)
                        mb->root->subname--;
                        return -1;
                }
-       x = strchr(m->start, '\n');
-       if(x == nil)
-               m->header = m->end;
-       else
+       m->header = m->end;
+       if(x = strchr(m->start, '\n'))
                m->header = x + 1;
+       if(p = parseunix(m))
+               sysfatal("%s:%s naked From in body? [%s]", mb->path, (*l)->filename, p);
        m->mheader = m->mhend = m->header;
-       parseunix(m);
-       parse(m, 0, mb, 0);
-       logmsg("new", m);
+       parse(mb, m, 0, 0);
+       if(m != *l && m->deleted != Dup){
+               logmsg(m, "new");
+               newcachehash(mb, m, doplumb);
+               putcache(mb, m);
+               nnew[0]++;
+       }
 
        /* chain in */
        *l = m;
@@ -215,17 +242,18 @@ dcmp(Dir *a, Dir *b)
        return strcmp(an, bn);
 }
 
-static void
-readpbmbox(Mailbox *mb, int doplumb)
+static char*
+readpbmbox(Mailbox *mb, int doplumb, int *new)
 {
-       int fd, i, j, nd, nmd;
        char *month, *msg;
+       int fd, i, j, nd, nmd;
        Dir *d, *md;
+       static char err[ERRMAX];
 
        fd = open(mb->path, OREAD);
        if(fd < 0){
-               fprint(2, "%s: %s: %r\n", argv0, mb->path);
-               return;
+               errstr(err, sizeof err);
+               return err;
        }
        nd = dirreadall(fd, &d);
        close(fd);
@@ -249,7 +277,7 @@ readpbmbox(Mailbox *mb, int doplumb)
                        for(j = 0; j < nmd; j++)
                                if(mustshow(md[j].name)){
                                        msg = smprint("%s/%s", month, md[j].name);
-                                       readpbmessage(mb, msg, doplumb);
+                                       readpbmessage(mb, msg, doplumb, new);
                                        free(msg);
                                }
                }
@@ -259,45 +287,45 @@ readpbmbox(Mailbox *mb, int doplumb)
                md = nil;
        }
        free(d);
+       return nil;
 }
 
-static void
-readpbvmbox(Mailbox *mb, int doplumb)
+static char*
+readpbvmbox(Mailbox *mb, int doplumb, int *new)
 {
+       char *data, *ln, *p, *nln, *msg;
        int fd, nr;
        long sz;
-       char *data, *ln, *p, *nln, *msg;
        Dir *d;
+       static char err[ERRMAX];
 
        fd = open(mb->path, OREAD);
        if(fd < 0){
-               fprint(2, "%s: %s: %r\n", argv0, mb->path);
-               return;
+               errstr(err, sizeof err);
+               return err;
        }
        d = dirfstat(fd);
        if(d == nil){
-               fprint(2, "%s: %s: %r\n", argv0, mb->path);
-               close(fd);
-               return;
+               errstr(err, sizeof err);
+               return err;
        }
        sz = d->length;
        free(d);
        if(sz > 2 * 1024 * 1024){
                sz = 2 * 1024 * 1024;
-               fprint(2, "%s: %s: bug: folder too big\n", argv0, mb->path);
+               fprint(2, "upas/fs: %s: bug: folder too big\n", mb->path);
        }
        data = malloc(sz+1);
        if(data == nil){
-               close(fd);
-               fprint(2, "%s: no memory\n", argv0);
-               return;
+               errstr(err, sizeof err);
+               return err;
        }
        nr = readn(fd, data, sz);
        close(fd);
        if(nr < 0){
-               fprint(2, "%s: %s: %r\n", argv0, mb->path);
+               errstr(err, sizeof err);
                free(data);
-               return;
+               return err;
        }
        data[nr] = 0;
 
@@ -318,22 +346,24 @@ readpbvmbox(Mailbox *mb, int doplumb)
                        *p = 0;
                msg = smprint("/mail/box/%s/msgs/%s", user, ln);
                if(msg == nil){
-                       fprint(2, "%s: no memory\n", argv0);
+                       fprint(2, "upas/fs: malloc: %r\n");
                        continue;
                }
-               readpbmessage(mb, msg, doplumb);
+               readpbmessage(mb, msg, doplumb, new);
                free(msg);
        }
        free(data);
+       return nil;
 }
 
 static char*
-readmbox(Mailbox *mb, int doplumb, int virt)
+readmbox(Mailbox *mb, int doplumb, int virt, int *new)
 {
+       char *mberr;
        int fd;
        Dir *d;
        Message *m;
-       static char err[Errlen];
+       static char err[128];
 
        if(debug)
                fprint(2, "read mbox %s\n", mb->path);
@@ -364,45 +394,45 @@ readmbox(Mailbox *mb, int doplumb, int virt)
        henter(PATH(0, Qtop), mb->name,
                (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
        snprint(err, sizeof err, "reading '%s'", mb->path);
-       logmsg(err, nil);
+       logmsg(nil, err, nil);
 
        for(m = mb->root->part; m != nil; m = m->next)
                if(m->deleted == 0)
                        m->deleted = -1;
        if(virt == 0)
-               readpbmbox(mb, doplumb);
+               mberr = readpbmbox(mb, doplumb, new);
        else
-               readpbvmbox(mb, doplumb);
+               mberr = readpbvmbox(mb, doplumb, new);
 
        /*
         * messages removed from the mbox; flag them to go.
         */
        for(m = mb->root->part; m != nil; m = m->next)
                if(m->deleted < 0 && doplumb){
-                       m->inmbox = 0;
-                       m->deleted = 1;
-                       mailplumb(mb, m, 1);
+                       delmessage(mb, m);
+                       if(doplumb)
+                               mailplumb(mb, m, 1);
                }
-       logmsg("mbox read", nil);
-       return nil;
+       logmsg(nil, "mbox read");
+       return mberr;
 }
 
 static char*
-mbsync(Mailbox *mb, int doplumb)
+mbsync(Mailbox *mb, int doplumb, int *new)
 {
        char *rv;
 
-       rv = readmbox(mb, doplumb, 0);
+       rv = readmbox(mb, doplumb, 0, new);
        purgembox(mb, 0);
        return rv;
 }
 
 static char*
-mbvsync(Mailbox *mb, int doplumb)
+mbvsync(Mailbox *mb, int doplumb, int *new)
 {
        char *rv;
 
-       rv = readmbox(mb, doplumb, 1);
+       rv = readmbox(mb, doplumb, 1, new);
        purgembox(mb, 1);
        return rv;
 }
index ac8cb3cd002cac71794d61464980a102c6536629..750646d9bffcc2d7e48f12cbc9c9a7b7ac9c011e 100644 (file)
@@ -1,54 +1,64 @@
 #include "common.h"
-#include <ctype.h>
-#include <plumb.h>
 #include <libsec.h>
 #include <auth.h>
 #include "dat.h"
 
 #pragma varargck type "M" uchar*
 #pragma varargck argpos pop3cmd 2
+#define pdprint(p, ...)        if((p)->debug) fprint(2, __VA_ARGS__); else{}
+
+typedef struct Popm Popm;
+struct Popm{
+       int     mesgno;
+};
 
 typedef struct Pop Pop;
 struct Pop {
-       char *freep;    // free this to free the strings below
-
-       char *host;
-       char *user;
-       char *port;
-
-       int ppop;
-       int refreshtime;
-       int debug;
-       int pipeline;
-       int encrypted;
-       int needtls;
-       int notls;
-       int needssl;
-
-       // open network connection
-       Biobuf bin;
-       Biobuf bout;
-       int fd;
-       char *lastline; // from Brdstr
-
+       char    *freep;         /* free this to free the strings below */
+       char    *host;
+       char    *user;
+       char    *port;
+
+       int     ppop;
+       int     refreshtime;
+       int     debug;
+       int     pipeline;
+       int     encrypted;
+       int     needtls;
+       int     notls;
+       int     needssl;
+
+       Biobuf  bin;            /* open network connection */
+       Biobuf  bout;
+       int     fd;
+       char    *lastline;              /* from Brdstr */
        Thumbprint *thumb;
 };
 
-char*
+static int
+mesgno(Message *m)
+{
+       Popm *a;
+
+       a = m->aux;
+       return a->mesgno;
+}
+
+static char*
 geterrstr(void)
 {
-       static char err[Errlen];
+       static char err[64];
 
        err[0] = '\0';
        errstr(err, sizeof(err));
        return err;
 }
 
-//
-// get pop3 response line , without worrying
-// about multiline responses; the clients
-// will deal with that.
-//
+/*
+ *  get pop3 response line , without worrying
+ *  about multiline responses; the clients
+ *  will deal with that.
+ */
 static int
 isokay(char *s)
 {
@@ -62,15 +72,13 @@ pop3cmd(Pop *pop, char *fmt, ...)
        va_list va;
 
        va_start(va, fmt);
-       vseprint(buf, buf+sizeof(buf), fmt, va);
+       vseprint(buf, buf + sizeof buf, fmt, va);
        va_end(va);
 
-       p = buf+strlen(buf);
-       if(p > (buf+sizeof(buf)-3))
+       p = buf + strlen(buf);
+       if(p > buf + sizeof buf - 3)
                sysfatal("pop3 command too long");
-
-       if(pop->debug)
-               fprint(2, "<- %s\n", buf);
+       pdprint(pop, "<- %s\n", buf);
        strcpy(p, "\r\n");
        Bwrite(&pop->bout, buf, strlen(buf));
        Bflush(&pop->bout);
@@ -83,20 +91,19 @@ pop3resp(Pop *pop)
        char *p;
 
        alarm(60*1000);
-       s = Brdstr(&pop->bin, '\n', 0);
-       alarm(0);
-       if(s == nil){
+       if((s = Brdstr(&pop->bin, '\n', 0)) == nil){
                close(pop->fd);
                pop->fd = -1;
+               alarm(0);
                return "unexpected eof";
        }
+       alarm(0);
 
-       p = s+strlen(s)-1;
+       p = s + strlen(s) - 1;
        while(p >= s && (*p == '\r' || *p == '\n'))
                *p-- = '\0';
 
-       if(pop->debug)
-               fprint(2, "-> %s\n", s);
+       pdprint(pop, "-> %s\n", s);
        free(pop->lastline);
        pop->lastline = s;
        return s;
@@ -119,40 +126,35 @@ pop3pushtls(Pop *pop)
        int fd;
        uchar digest[SHA1dlen];
        TLSconn conn;
-       char *err;
 
-       err = nil;
        memset(&conn, 0, sizeof conn);
        // conn.trace = pop3log;
        fd = tlsClient(pop->fd, &conn);
-       if(fd < 0){
-               err = "tls error";
-               goto out;
-       }
-       pop->fd = fd;
-       Binit(&pop->bin, pop->fd, OREAD);
-       Binit(&pop->bout, pop->fd, OWRITE);
+       if(fd < 0)
+               return "tls error";
        if(conn.cert==nil || conn.certlen <= 0){
-               err = "server did not provide TLS certificate";
-               goto out;
+               close(fd);
+               return "server did not provide TLS certificate";
        }
        sha1(conn.cert, conn.certlen, digest, nil);
        if(!pop->thumb || !okThumbprint(digest, pop->thumb)){
-               fmtinstall('H', encodefmt);
-               fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
-               err = "bad server certificate";
-               goto out;
+               close(fd);
+               free(conn.cert);
+               eprint("pop3: server certificate %.*H not recognized\n", SHA1dlen, digest);
+               return "bad server certificate";
        }
-       pop->encrypted = 1;
-out:
-       free(conn.sessionID);
        free(conn.cert);
-       return err;
+       close(pop->fd);
+       pop->fd = fd;
+       pop->encrypted = 1;
+       Binit(&pop->bin, pop->fd, OREAD);
+       Binit(&pop->bout, pop->fd, OWRITE);
+       return nil;
 }
 
-//
-// get capability list, possibly start tls
-//
+/*
+ *  get capability list, possibly start tls
+ */
 static char*
 pop3capa(Pop *pop)
 {
@@ -186,9 +188,9 @@ pop3capa(Pop *pop)
        return nil;
 }
 
-//
-// log in using APOP if possible, password if allowed by user
-//
+/*
+ *  log in using APOP if possible, password if allowed by user
+ */
 static char*
 pop3login(Pop *pop)
 {
@@ -207,10 +209,10 @@ pop3login(Pop *pop)
        else
                ubuf[0] = '\0';
 
-       // look for apop banner
-       if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) {
+       /* look for apop banner */
+       if(pop->ppop == 0 && (p = strchr(s, '<')) && (q = strchr(p + 1, '>'))) {
                *++q = '\0';
-               if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
+               if((n=auth_respond(p, q - p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s",
                        pop->host, ubuf)) < 0)
                        return "factotum failed";
                if(user[0]=='\0')
@@ -253,83 +255,75 @@ pop3login(Pop *pop)
        }
 }
 
-//
-// dial and handshake with pop server
-//
+/*
+ *  dial and handshake with pop server
+ */
 static char*
 pop3dial(Pop *pop)
 {
        char *err;
 
-       if(pop->fd >= 0){
-               close(pop->fd);
-               pop->fd = -1;
-       }
        if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0)
                return geterrstr();
 
        if(pop->needssl){
                if((err = pop3pushtls(pop)) != nil)
-                       goto Out;
+                       return err;
        }else{
                Binit(&pop->bin, pop->fd, OREAD);
                Binit(&pop->bout, pop->fd, OWRITE);
        }
-       err = pop3login(pop);
-Out:
-       if(err != nil){
-               if(pop->fd >= 0){
-                       close(pop->fd);
-                       pop->fd = -1;
-               }
+
+       if(err = pop3login(pop)) {
+               close(pop->fd);
+               return err;
        }
-       return err;
+
+       return nil;
 }
 
-//
-// close connection
-//
+/*
+ *  close connection
+ */
 static void
 pop3hangup(Pop *pop)
 {
-       if(pop->fd < 0)
-               return;
        pop3cmd(pop, "QUIT");
        pop3resp(pop);
        close(pop->fd);
-       pop->fd = -1;
 }
 
-//
-// download a single message
-//
+/*
+ *  download a single message
+ */
 static char*
-pop3download(Pop *pop, Message *m)
+pop3download(Mailbox *mb, Pop *pop, Message *m)
 {
        char *s, *f[3], *wp, *ep;
-       char sdigest[SHA1dlen*2+1];
-       int i, l, sz;
+       int l, sz, pos, n;
+       Popm *a;
 
+       a = m->aux;
        if(!pop->pipeline)
-               pop3cmd(pop, "LIST %d", m->mesgno);
+               pop3cmd(pop, "LIST %d", a->mesgno);
        if(!isokay(s = pop3resp(pop)))
                return s;
 
        if(tokenize(s, f, 3) != 3)
                return "syntax error in LIST response";
 
-       if(atoi(f[1]) != m->mesgno)
+       if(atoi(f[1]) != a->mesgno)
                return "out of sync with pop3 server";
 
-       sz = atoi(f[2])+200;    /* 200 because the plan9 pop3 server lies */
+       sz = atoi(f[2]) + 200;  /* 200 because the plan9 pop3 server lies */
        if(sz == 0)
                return "invalid size in LIST response";
 
-       m->start = wp = emalloc(sz+1);
-       ep = wp+sz;
+       m->start = wp = emalloc(sz + 1);
+       ep = wp + sz;
 
        if(!pop->pipeline)
-               pop3cmd(pop, "RETR %d", m->mesgno);
+               pop3cmd(pop, "RETR %d", a->mesgno);
        if(!isokay(s = pop3resp(pop))) {
                m->start = nil;
                free(wp);
@@ -347,7 +341,7 @@ pop3download(Pop *pop, Message *m)
                if(strcmp(s, ".") == 0)
                        break;
 
-               l = strlen(s)+1;
+               l = strlen(s) + 1;
                if(s[0] == '.') {
                        s++;
                        l--;
@@ -356,14 +350,17 @@ pop3download(Pop *pop, Message *m)
                 * grow by 10%/200bytes - some servers
                 *  lie about message sizes
                 */
-               if(wp+l > ep) {
-                       int pos = wp - m->start;
-                       sz += ((sz / 10) < 200)? 200: sz/10;
-                       m->start = erealloc(m->start, sz+1);
-                       wp = m->start+pos;
-                       ep = m->start+sz;
+               if(wp + l > ep) {
+                       pos = wp - m->start;
+                       n = sz/10;
+                       if(n < 200)
+                               n = 200;
+                       sz += n;
+                       m->start = erealloc(m->start, sz + 1);
+                       wp = m->start + pos;
+                       ep = m->start + sz;
                }
-               memmove(wp, s, l-1);
+               memmove(wp, s, l - 1);
                wp[l-1] = '\n';
                wp += l;
        }
@@ -373,40 +370,41 @@ pop3download(Pop *pop, Message *m)
 
        m->end = wp;
 
-       // make sure there's a trailing null
-       // (helps in body searches)
+       /*
+        *  make sure there's a trailing null
+        *  (helps in body searches)
+        */
        *m->end = 0;
        m->bend = m->rbend = m->end;
        m->header = m->start;
-
-       // digest message
-       sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
-       for(i = 0; i < SHA1dlen; i++)
-               sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
-       m->sdigest = s_copy(sdigest);
+       m->size = m->end - m->start;
+       if(m->digest == nil)
+               digestmessage(mb, m);
 
        return nil;
 }
 
-//
-// check for new messages on pop server
-// UIDL is not required by RFC 1939, but 
-// netscape requires it, so almost every server supports it.
-// we'll use it to make our lives easier.
-//
+/*
+ *  check for new messages on pop server
+ *  UIDL is not required by RFC 1939, but
+ *  netscape requires it, so almost every server supports it.
+ *  we'll use it to make our lives easier.
+ */
 static char*
-pop3read(Pop *pop, Mailbox *mb, int doplumb)
+pop3read(Pop *pop, Mailbox *mb, int doplumb, int *new)
 {
        char *s, *p, *uidl, *f[2];
-       int mesgno, ignore, nnew;
+       int mno, ignore, nnew;
        Message *m, *next, **l;
+       Popm *a;
 
-       // Some POP servers disallow UIDL if the maildrop is empty.
+       *new = 0;
+       /* Some POP servers disallow UIDL if the maildrop is empty. */
        pop3cmd(pop, "STAT");
        if(!isokay(s = pop3resp(pop)))
                return s;
 
-       // fetch message listing; note messages to grab
+       /* fetch message listing; note messages to grab */
        l = &mb->root->part;
        if(strncmp(s, "+OK 0 ", 6) != 0) {
                pop3cmd(pop, "UIDL");
@@ -421,25 +419,32 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb)
                        if(tokenize(p, f, 2) != 2)
                                continue;
 
-                       mesgno = atoi(f[0]);
+                       mno = atoi(f[0]);
                        uidl = f[1];
-                       if(strlen(uidl) > 75)   // RFC 1939 says 70 characters max
+                       if(strlen(uidl) > 75)   /* RFC 1939 says 70 characters max */
                                continue;
 
                        ignore = 0;
                        while(*l != nil) {
-                               if(strcmp((*l)->uidl, uidl) == 0) {
-                                       // matches mail we already have, note mesgno for deletion
-                                       (*l)->mesgno = mesgno;
+                               a = (*l)->aux;
+                               if(strcmp((*l)->idxaux, uidl) == 0){
+                                       if(a == 0){
+                                               m = *l;
+                                               m->mallocd = 1;
+                                               m->inmbox = 1;
+                                               m->aux = a = emalloc(sizeof *a);
+                                       }
+                                       /* matches mail we already have, note mesgno for deletion */
+                                       a->mesgno = mno;
                                        ignore = 1;
                                        l = &(*l)->next;
                                        break;
-                               } else {
-                                       // old mail no longer in box mark deleted
+                               }else{
+                                       /* old mail no longer in box mark deleted */
                                        if(doplumb)
                                                mailplumb(mb, *l, 1);
                                        (*l)->inmbox = 0;
-                                       (*l)->deleted = 1;
+                                       (*l)->deleted = Deleted;
                                        l = &(*l)->next;
                                }
                        }
@@ -449,30 +454,31 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb)
                        m = newmessage(mb->root);
                        m->mallocd = 1;
                        m->inmbox = 1;
-                       m->mesgno = mesgno;
-                       strcpy(m->uidl, uidl);
+                       m->idxaux = strdup(uidl);
+                       m->aux = a = emalloc(sizeof *a);
+                       a->mesgno = mno;
 
-                       // chain in; will fill in message later
+                       /* chain in; will fill in message later */
                        *l = m;
                        l = &m->next;
                }
        }
 
-       // whatever is left has been removed from the mbox, mark as deleted
+       /* whatever is left has been removed from the mbox, mark as deleted */
        while(*l != nil) {
                if(doplumb)
                        mailplumb(mb, *l, 1);
                (*l)->inmbox = 0;
-               (*l)->deleted = 1;
+               (*l)->deleted = Disappear;
                l = &(*l)->next;
        }
 
-       // download new messages
+       /* download new messages */
        nnew = 0;
        if(pop->pipeline){
                switch(rfork(RFPROC|RFMEM)){
                case -1:
-                       fprint(2, "rfork: %r\n");
+                       eprint("pop3: rfork: %r\n");
                        pop->pipeline = 0;
 
                default:
@@ -480,49 +486,41 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb)
 
                case 0:
                        for(m = mb->root->part; m != nil; m = m->next){
-                               if(m->start != nil)
+                               if(m->start != nil || m->deleted)
                                        continue;
-                               Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno);
+                               Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", mesgno(m), mesgno(m));
                        }
                        Bflush(&pop->bout);
-                       _exits(nil);
+                       _exits("");
                }
        }
 
        for(m = mb->root->part; m != nil; m = next) {
                next = m->next;
 
-               if(m->start != nil)
+               if(m->start != nil || m->deleted)
                        continue;
-
-               if(s = pop3download(pop, m)) {
-                       // message disappeared? unchain
-                       fprint(2, "download %d: %s\n", m->mesgno, s);
+               if(s = pop3download(mb, pop, m)) {
+                       /* message disappeared? unchain */
+                       eprint("pop3: download %d: %s\n", mesgno(m), s);
                        delmessage(mb, m);
                        mb->root->subname--;
                        continue;
                }
                nnew++;
-               parse(m, 0, mb, 1);
-
-               if(doplumb)
-                       mailplumb(mb, m, 0);
+               parse(mb, m, 1, 0);
+               newcachehash(mb, m, doplumb);
+               putcache(mb, m);
        }
        if(pop->pipeline)
                waitpid();
-
-       if(nnew || mb->vers == 0) {
-               mb->vers++;
-               henter(PATH(0, Qtop), mb->name,
-                       (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
-       }
-
+       *new = nnew;
        return nil;     
 }
 
-//
-// delete marked messages
-//
+/*
+ *  delete marked messages
+ */
 static void
 pop3purge(Pop *pop, Mailbox *mb)
 {
@@ -531,7 +529,7 @@ pop3purge(Pop *pop, Mailbox *mb)
        if(pop->pipeline){
                switch(rfork(RFPROC|RFMEM)){
                case -1:
-                       fprint(2, "rfork: %r\n");
+                       eprint("pop3: rfork: %r\n");
                        pop->pipeline = 0;
 
                default:
@@ -542,11 +540,11 @@ pop3purge(Pop *pop, Mailbox *mb)
                                next = m->next;
                                if(m->deleted && m->refs == 0){
                                        if(m->inmbox)
-                                               Bprint(&pop->bout, "DELE %d\r\n", m->mesgno);
+                                               Bprint(&pop->bout, "DELE %d\r\n", mesgno(m));
                                }
                        }
                        Bflush(&pop->bout);
-                       _exits(nil);
+                       _exits("");
                }
        }
        for(m = mb->root->part; m != nil; m = next) {
@@ -554,7 +552,7 @@ pop3purge(Pop *pop, Mailbox *mb)
                if(m->deleted && m->refs == 0) {
                        if(m->inmbox) {
                                if(!pop->pipeline)
-                                       pop3cmd(pop, "DELE %d", m->mesgno);
+                                       pop3cmd(pop, "DELE %d", mesgno(m));
                                if(isokay(pop3resp(pop)))
                                        delmessage(mb, m);
                        } else
@@ -564,19 +562,21 @@ pop3purge(Pop *pop, Mailbox *mb)
 }
 
 
-// connect to pop3 server, sync mailbox
+/* connect to pop3 server, sync mailbox */
 static char*
-pop3sync(Mailbox *mb, int doplumb)
+pop3sync(Mailbox *mb, int doplumb, int *new)
 {
        char *err;
        Pop *pop;
 
        pop = mb->aux;
+
        if(err = pop3dial(pop)) {
                mb->waketime = time(0) + pop->refreshtime;
                return err;
        }
-       if((err = pop3read(pop, mb, doplumb)) == nil){
+
+       if((err = pop3read(pop, mb, doplumb, new)) == nil){
                pop3purge(pop, mb);
                mb->d->atime = mb->d->mtime = time(0);
        }
@@ -629,7 +629,7 @@ pop3ctl(Mailbox *mb, int argc, char **argv)
        return Epop3ctl;
 }
 
-// free extra memory associated with mb
+/* free extra memory associated with mb */
 static void
 pop3close(Mailbox *mb)
 {
@@ -640,9 +640,18 @@ pop3close(Mailbox *mb)
        free(pop);
 }
 
-//
-// open mailboxes of the form /pop/host/user or /apop/host/user
-//
+static char*
+mkmbox(Pop *pop, char *p, char *e)
+{
+       p = seprint(p, e, "%s/box/%s/pop.%s", MAILROOT, getlog(), pop->host);
+       if(pop->user && strcmp(pop->user, getlog()))
+               p = seprint(p, e, ".%s", pop->user);
+       return p;
+}
+
+/*
+ *  open mailboxes of the form /pop/host/user or /apop/host/user
+ */
 char*
 pop3mbox(Mailbox *mb, char *path)
 {
@@ -650,7 +659,6 @@ pop3mbox(Mailbox *mb, char *path)
        int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls;
        Pop *pop;
 
-       quotefmtinstall();
        popssl = strncmp(path, "/pops/", 6) == 0;
        apopssl = strncmp(path, "/apops/", 7) == 0;
        poptls = strncmp(path, "/poptls/", 8) == 0;
@@ -673,8 +681,7 @@ pop3mbox(Mailbox *mb, char *path)
                return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]";
        }
 
-       pop = emalloc(sizeof(*pop));
-       pop->fd = -1;
+       pop = emalloc(sizeof *pop);
        pop->freep = path;
        pop->host = f[2];
        if(nf < 4)
@@ -688,12 +695,13 @@ pop3mbox(Mailbox *mb, char *path)
        pop->notls = popnotls || apopnotls;
        pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
 
+       mkmbox(pop, mb->path, mb->path + sizeof mb->path);
        mb->aux = pop;
        mb->sync = pop3sync;
        mb->close = pop3close;
        mb->ctl = pop3ctl;
-       mb->d = emalloc(sizeof(*mb->d));
-
+       mb->d = emalloc(sizeof *mb->d);
+       mb->addfrom = 1;
        return nil;
 }
 
diff --git a/sys/src/cmd/upas/fs/readdir.c b/sys/src/cmd/upas/fs/readdir.c
deleted file mode 100644 (file)
index 42028d4..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <u.h>
-#include <libc.h>
-
-void
-main(void)
-{
-       Dir d;
-       int fd, n;
-
-       fd = open("/mail/fs", OREAD);
-       while((n = dirread(fd, &d, sizeof(d))) > 0){
-               print("%s\n", d.name);
-       }
-       print("n = %d\n", n);
-}
diff --git a/sys/src/cmd/upas/fs/ref.c b/sys/src/cmd/upas/fs/ref.c
new file mode 100644 (file)
index 0000000..f3cf078
--- /dev/null
@@ -0,0 +1,100 @@
+#include "common.h"
+#include <libsec.h>
+#include "dat.h"
+
+/* all the data that's fit to cache */
+
+typedef struct{
+       char    *s;
+       int     l;
+       ulong   ref;
+}Refs;
+
+Refs   *rtab;
+int    nrtab;
+int    nralloc;
+
+int
+newrefs(char *s)
+{
+       int l, i;
+       Refs *r;
+
+       l = strlen(s);
+       for(i = 0; i < nrtab; i++){
+               r = rtab + i;
+               if(r->ref == 0)
+                       goto enter;
+               if(l == r->l && strcmp(r->s, s) == 0){
+                       r->ref++;
+                       return i;
+               }
+       }
+       if(nrtab == nralloc)
+               rtab = erealloc(rtab, sizeof *rtab*(nralloc += 50));
+       nrtab = i + 1;
+enter:
+       r = rtab + i;
+       r->s = strdup(s);
+       r->l = l;
+       r->ref = 1;
+       return i;
+}
+
+void
+delrefs(int i)
+{
+       Refs *r;
+
+       r = rtab + i;
+       if(--r->ref > 0)
+               return;
+       free(r->s);
+       memset(r, 0, sizeof *r);
+}
+
+void
+refsinit(void)
+{
+       newrefs("");
+}
+
+static char *sep = "--------\n";
+
+int
+prrefs(Biobuf *b)
+{
+       int i, n;
+
+       n = 0;
+       for(i = 1; i < nrtab; i++){
+               if(rtab[i].ref == 0)
+                       continue;
+               Bprint(b, "%s ", rtab[i].s);
+               if(n++%8 == 7)
+                       Bprint(b, "\n");
+       }
+       if(n%8 != 7)
+               Bprint(b, "\n");
+       Bprint(b, sep);
+       return 0;
+}
+
+int
+rdrefs(Biobuf *b)
+{
+       char *f[10], *s;
+       int i, n;
+
+       while(s = Brdstr(b, '\n', 1)){
+               if(strcmp(s, sep) == 0){
+                       free(s);
+                       return 0;
+               }
+               n = tokenize(s, f, nelem(f));
+               for(i = 0; i < n; i++)
+                       newrefs(f[i]);
+               free(s);
+       }
+       return -1;
+}
diff --git a/sys/src/cmd/upas/fs/remove.c b/sys/src/cmd/upas/fs/remove.c
new file mode 100644 (file)
index 0000000..5bafe8a
--- /dev/null
@@ -0,0 +1,141 @@
+#include "common.h"
+#include "dat.h"
+
+#define deprint(...)   /* eprint(__VA_ARGS__) */
+
+extern int dirskip(Dir*, uvlong*);
+
+static int
+ismbox(char *path)
+{
+       char buf[512];
+       int fd, r;
+
+       fd = open(path, OREAD);
+       if(fd == -1)
+               return 0;
+       r = 1;
+       if(read(fd, buf, sizeof buf) < 28 + 5)
+               r = 0;
+       else if(strncmp(buf, "From ", 5))
+               r = 0;
+       close(fd);
+       return r;
+}
+
+static int
+isindex(Dir *d)
+{
+       char *p;
+
+       p = strrchr(d->name, '.');
+       if(!p)
+               return -1;
+       if(strcmp(p, ".idx") || strcmp(p, ".imp"))
+               return 1;
+       return 0;
+}
+
+static int
+idiotcheck(char *path, Dir *d, int getindex)
+{
+       uvlong v;
+
+       if(d->mode & DMDIR)
+               return 0;
+       if(strncmp(d->name, "L.", 2) == 0)
+               return 0;
+       if(getindex && isindex(d))
+               return 0;
+       if(!dirskip(d, &v) || ismbox(path))
+               return 0;
+       return -1;
+}
+
+int
+vremove(char *buf)
+{
+       deprint("rm %s\n", buf);
+       return remove(buf);
+}
+
+static int
+rm(char *dir, int flags, int level)
+{
+       char buf[Pathlen];
+       int i, n, r, fd, isdir, rflag;
+       Dir *d;
+
+       d = dirstat(dir);
+       isdir = d->mode & DMDIR;
+       free(d);
+       if(!isdir)
+               return 0;
+       fd = open(dir, OREAD);
+       if(fd == -1)
+               return -1;
+       n = dirreadall(fd, &d);
+       close(fd);
+       r = 0;
+       rflag = flags & Rrecur;
+       for(i = 0; i < n; i++){
+               snprint(buf, sizeof buf, "%s/%s", dir, d[i].name);
+               if(rflag)
+                       r |= rm(buf, flags, level + 1);
+               if(idiotcheck(buf, d + i, level + rflag) == -1)
+                       continue;
+               if(vremove(buf) != 0)
+                       r = -1;
+       }
+       free(d);
+       return r;
+}
+
+void
+rmidx(char *buf, int flags)
+{
+       char buf2[Pathlen];
+
+       snprint(buf2, sizeof buf2, "%s.idx", buf);
+       vremove(buf2);
+       if((flags & Rtrunc) == 0){
+               snprint(buf2, sizeof buf2, "%s.imp", buf);
+               vremove(buf2);
+       }
+}
+
+char*
+localremove(Mailbox *mb, int flags)
+{
+       char *msg, *path;
+       int r, isdir;
+       Dir *d;
+       static char err[2*Pathlen];
+
+       path = mb->path;
+       if((d = dirstat(path)) == 0){
+               snprint(err, sizeof err, "%s: doesn't exist\n", path);
+               return 0;
+       }
+       isdir = d->mode & DMDIR;
+       free(d);
+       msg = "deleting";
+       if(flags & Rtrunc)
+               msg = "truncating";
+       deprint("%s: %s\n", msg, path);
+
+       /* must match folder.c:/^openfolder */
+       r = rm(path, flags, 0);
+       if((flags & Rtrunc) == 0)
+               r = vremove(path);
+       else if(!isdir)
+               close(r = open(path, OWRITE|OTRUNC));
+
+       rmidx(path, flags);
+
+       if(r == -1){
+               snprint(err, sizeof err, "%s: can't %s\n", path, msg);
+               return err;
+       }
+       return 0;
+}
diff --git a/sys/src/cmd/upas/fs/rename.c b/sys/src/cmd/upas/fs/rename.c
new file mode 100644 (file)
index 0000000..6d53376
--- /dev/null
@@ -0,0 +1,234 @@
+#include "common.h"
+#include "dat.h"
+
+#define deprint(...)   /* eprint(__VA_ARGS__) */
+
+static int
+delivery(char *s)
+{
+       if(strncmp(s, "/mail/fs/", 9) == 0)
+       if((s = strrchr(s, '/')) && strcmp(s + 1, "mbox") == 0)
+               return 1;
+       return 0;
+}
+
+static int
+isdir(char *s)
+{
+       int isdir;
+       Dir *d;
+
+       d = dirstat(s);
+       isdir = d && d->mode & DMDIR;
+       free(d);
+       return isdir;
+}
+
+static int
+docreate(char *file, int perm)
+{
+       int fd;
+       Dir ndir;
+       Dir *d;
+
+       fd = create(file, OREAD, perm);
+       if(fd < 0)
+               return -1;
+       d = dirfstat(fd);
+       if(d == nil)
+               return -1;
+       nulldir(&ndir);
+       ndir.mode = perm;
+       ndir.gid = d->uid;
+       dirfwstat(fd, &ndir);
+       close(fd);
+       return 0;
+}
+
+static int
+rollup(char *s)
+{
+       char *p;
+       int mode;
+
+       if(access(s, 0) == 0)
+               return -1;
+
+       /*
+        * if we can deliver to this mbox, it needs
+        * to be read/execable all the way down
+        */
+       mode = 0711;
+       if(delivery(s))
+               mode = 0755;
+
+       for(p = s; p; p++) {
+               if(*p == '/')
+                       continue;
+               p = strchr(p, '/');
+               if(p == 0)
+                       break;
+               *p = 0;
+               if(access(s, 0) != 0)
+               if(docreate(s, DMDIR|mode) < 0)
+                       return -1;
+               *p = '/';
+       }
+       return 0;
+}
+
+static int
+copyfile(char *a, char *b, int flags)
+{
+       char *s;
+       int fd, fd1, mode, i, m, n, r;
+       Dir *d;
+
+       mode = 0600;
+       if(delivery(b))
+               mode = 0622;
+       fd = open(a, OREAD);
+       fd1 = create(b, OWRITE|OEXCL, DMEXCL|mode);
+       if(fd == -1 || fd1 == -1){
+               close(fd);
+               close(fd1);
+               return -1;
+       }
+       s = malloc(64*1024);
+       i = m = 0;
+       while((n = read(fd, s, sizeof s)) > 0)
+               for(i = 0; i != n; i += m)
+                       if((m = write(fd1, s + i, n - i)) == -1)
+                               goto lose;
+lose:
+       free(s);
+       close(fd);
+       close(fd1);
+       if(i != m || n != 0)
+               return -1;
+
+       if((flags & Rtrunc) == 0)
+               return vremove(a);
+
+       fd = open(a, ORDWR);
+       if(fd == -1)
+               return -1;
+       r = -1;
+       if(d = dirfstat(fd)){
+               d->length = 0;
+               r = dirfwstat(fd, d);
+               free(d);
+       }
+       return r;
+}
+
+static int
+copydir(char *a, char *b, int flags)
+{
+       char *p, buf[Pathlen], ns[Pathlen], owd[Pathlen];
+       int fd, fd1, len, i, n, r;
+       Dir *d;
+
+       fd = open(a, OREAD);
+       fd1 = create(b, OWRITE|OEXCL, DMEXCL|0777);
+       close(fd1);
+       if(fd == -1 || fd1 == -1){
+               close(fd);
+               return -1;
+       }
+
+       /* fixup mode */
+       if(delivery(b))
+       if(d = dirfstat(fd)){
+               d->mode |= 0777;
+               dirfwstat(fd, d);
+               free(d);
+       }
+
+       getwd(owd, sizeof owd);
+       if(chdir(a) == -1)
+               return -1;
+
+       p = seprint(buf, buf + sizeof buf, "%s/", b);
+       len = buf + sizeof buf - p;
+       n = dirreadall(fd, &d);
+       r = 0;
+       for(i = 0; i < n; i++){
+               snprint(p, len, "%s", d[i].name);
+               if(d->mode & DMDIR){
+                       snprint(ns, sizeof ns, "%s/%s", a, d[i].name);
+                       r |= copydir(ns, buf, 0);
+                       chdir(a);
+               }else
+                       r |= copyfile(d[i].name, buf, 0);
+               if(r)
+                       break;
+       }
+       free(d);
+
+       if((flags & Rtrunc) == 0)
+               r |= vremove(a);
+
+       chdir(owd);
+       return r;
+}
+
+int
+rename(char *a, char *b, int flags)
+{
+       char *e0, *e1;
+       int fd, r;
+       Dir *d;
+
+       e0 = strrchr(a, '/');
+       e1 = strrchr(b, '/');
+       if(!e0 || !e1 || !e1[1])
+               return -1;
+
+       if(e0 - a == e1 - b)
+       if(strncmp(a, b, e0 - a) == 0)
+       if(!delivery(a) || isdir(a)){
+               fd = open(a, OREAD);
+               if(!(d = dirfstat(fd))){
+                       close(fd);
+                       return -1;
+               }
+               d->name = e1 + 1;
+               r = dirfwstat(fd, d);
+               deprint("rename %s %s -> %d\n", a, b, r);
+               if(r != -1 && flags & Rtrunc)
+                       close(create(a, OWRITE, d->mode));
+               free(d);
+               close(fd);
+               return r;
+       }
+
+       if(rollup(b) == -1)
+               return -1;
+       if(isdir(a))
+               return copydir(a, b, flags);
+       return copyfile(a, b, flags);
+}
+
+char*
+localrename(Mailbox *mb, char *p2, int flags)
+{
+       char *path, *msg;
+       int r;
+       static char err[2*Pathlen];
+
+       path = mb->path;
+       msg = "rename";
+       if(flags & Rtrunc)
+               msg = "move";
+       deprint("localrename %s: %s %s\n", msg, path, p2);
+
+       r = rename(path, p2, flags);
+       if(r == -1){
+               snprint(err, sizeof err, "%s: can't %s\n", path, msg);
+               deprint("localrename %s\n", err);
+               return err;
+       }
+       close(r);
+       return 0;
+}
diff --git a/sys/src/cmd/upas/fs/rfc2047-test b/sys/src/cmd/upas/fs/rfc2047-test
deleted file mode 100644 (file)
index b536c4b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006
-From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>
-To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
-CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
-Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
-    =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
-
-From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006
-From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se>
-To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se
-Subject: Time for ISO 10646?
-
-From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006
-To: Dave Crocker <dcrocker@mordor.stanford.edu>
-Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se
-From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se>
-Subject: Re: RFC-HDR care and feeding
-
-From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006
-From: Nathaniel Borenstein <nsb@thumper.bellcore.com>
-       (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=)
-To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed
-      <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu>
-Subject: Test of new header generator
-MIME-Version: 1.0
-Content-type: text/plain; charset=ISO-8859-1
-
-
diff --git a/sys/src/cmd/upas/fs/seg.c b/sys/src/cmd/upas/fs/seg.c
new file mode 100644 (file)
index 0000000..394b66a
--- /dev/null
@@ -0,0 +1,164 @@
+#include "common.h"
+#include <libsec.h>
+#include "dat.h"
+
+/*
+ * unnatural acts with virtual memory
+ */
+
+typedef struct{
+       int     ref;
+       char    *va;
+       long    sz;
+}S;
+
+static S               s[15];          /* 386 only gives 4 */
+static int             nstab = nelem(s);
+static long    ssem = 1;
+//static ulong thresh = 10*1024*1024;
+static ulong   thresh = 1024;
+
+void*
+segmalloc(ulong sz)
+{
+       int i, j;
+       void *va;
+
+       if(sz < thresh)
+               return emalloc(sz);
+       semacquire(&ssem, 1);
+       for(i = 0; i < nstab; i++)
+               if(s[i].ref == 0)
+                       goto found;
+notfound:
+       /* errstr not informative; assume we hit seg limit */
+       for(j = nstab - 1; j >= i; j--)
+               if(s[j].ref)
+                       break;
+       nstab = j;
+       semrelease(&ssem, 1);
+       return emalloc(sz);
+found:
+       /*
+        * the system doesn't leave any room for expansion
+        */
+       va = segattach(SG_CEXEC, "memory", 0, sz + sz/10 + 4096);
+       if(va == 0)
+               goto notfound;
+       s[i].ref++;
+       s[i].va = va;
+       s[i].sz = sz;
+       semrelease(&ssem, 1);
+       memset(va, 0, sz);
+       return va;
+}
+
+void
+segmfree(void *va)
+{
+       char *a;
+       int i;
+
+       a = va;
+       for(i = 0; i < nstab; i++)
+               if(s[i].va == a)
+                       goto found;
+       free(va);
+       return;
+found:
+       semacquire(&ssem, 1);
+       s[i].ref--;
+       s[i].va = 0;
+       s[i].sz = 0;
+       semrelease(&ssem, 1);
+}
+
+void*
+segreallocfixup(int i, ulong sz)
+{
+       char buf[ERRMAX];
+       void *va, *ova;
+
+       rerrstr(buf, sizeof buf);
+       if(strstr(buf, "segments overlap") == 0)
+               sysfatal("segibrk: %r");
+       va = segattach(SG_CEXEC, "memory", 0, sz);
+       if(va == 0)
+               sysfatal("segattach: %r");
+       ova = s[i].va;
+fprint(2, "fix memcpy(%p, %p, %lud)\n", va, ova, s[i].sz);
+       memcpy(va, ova, s[i].sz);
+       s[i].va = va;
+       s[i].sz = sz;
+       segdetach(ova);
+       return va;
+}
+
+void*
+segrealloc(void *va, ulong sz)
+{
+       char *a;
+       int i;
+       ulong sz0;
+
+fprint(2, "segrealloc %p %lud\n", va, sz);
+       if(va == 0)
+               return segmalloc(sz);
+       a = va;
+       for(i = 0; i < nstab; i++)
+               if(s[i].va == a)
+                       goto found;
+       if(sz >= thresh)
+       if(a = segmalloc(sz)){
+               sz0 = msize(va);
+               memcpy(a, va, sz0);
+fprint(2, "memset(%p, 0, %lud)\n", a + sz0, sz - sz0);
+               memset(a + sz0, 0, sz - sz0);
+               return a;
+       }
+       return realloc(va, sz);
+found:
+       sz0 = s[i].sz;
+fprint(2, "segbrk(%p, %p)\n", s[i].va, s[i].va + sz);
+       va = segbrk(s[i].va, s[i].va + sz);
+       if(va == (void*)-1 || va < end)
+               return segreallocfixup(i, sz);
+       a = va;
+       if(sz > sz0)
+{
+fprint(2, "memset(%p, 0, %lud)\n", a + sz0, sz - sz0);
+               memset(a + sz0, 0, sz - sz0);
+}
+       s[i].va = va;
+       s[i].sz = sz;
+       return va;
+}
+
+void*
+emalloc(ulong n)
+{
+       void *p;
+fprint(2, "emalloc %lud\n", n);
+       p = mallocz(n, 1);
+       if(!p)
+               sysfatal("malloc %lud: %r", n);
+       setmalloctag(p, getcallerpc(&n));
+       return p;
+}
+
+void
+main(void)
+{
+       char *p;
+       int i;
+       ulong sz;
+
+       p = 0;
+       for(i = 0; i < 6; i++){
+               sz = i*512;
+               p = segrealloc(p, sz);
+               memset(p, 0, sz);
+       }
+       segmfree(p);
+       exits("");
+}
index bcf0bcee0ca1a9c00015d7141432f0cdf50ecbdd..6c9f7d3b6ceebc0897bbf419ca7542d43f3343c4 100644 (file)
@@ -1,11 +1,10 @@
 #include <u.h>
 #include <libc.h>
-#include <ctype.h>
 
 static char*
 skiptext(char *q)
 {
-       while(*q!='\0' && *q!=' ' && *q!='\t' && *q!='\r' && *q!='\n')
+       while(*q != '\0' && *q != ' ' && *q != '\t' && *q != '\r' && *q != '\n')
                q++;
        return q;
 }
@@ -13,36 +12,19 @@ skiptext(char *q)
 static char*
 skipwhite(char *q)
 {
-       while(*q==' ' || *q=='\t' || *q=='\r' || *q=='\n')
+       while(*q == ' ' || *q == '\t' || *q == '\r' || *q == '\n')
                q++;
        return q;
 }
 
 static char* months[] = {
        "jan", "feb", "mar", "apr",
-       "may", "jun", "jul", "aug", 
+       "may", "jun", "jul", "aug",
        "sep", "oct", "nov", "dec"
 };
 
-static int
-strcmplwr(char *a, char *b, int n)
-{
-       char *eb;
-
-       eb = b+n;
-       while(*a && *b && b<eb){
-               if(tolower(*a) != tolower(*b))
-                       return 1;
-               a++;
-               b++;
-       }
-       if(b==eb)
-               return 0;
-       return *a != *b;
-}
-
 int
-strtotm(char *p, Tm *tmp)
+strtotm(char *p, Tm *t)
 {
        char *q, *r;
        int j;
@@ -56,58 +38,60 @@ strtotm(char *p, Tm *tmp)
        tm.min = -1;
        tm.year = -1;
        tm.mday = -1;
-       for(p=skipwhite(p); *p; p=skipwhite(q)){
+       for(p = skipwhite(p); *p; p = skipwhite(q)){
                q = skiptext(p);
 
                /* look for time in hh:mm[:ss] */
-               if(r = memchr(p, ':', q-p)){
+               if(r = memchr(p, ':', q - p)){
                        tm.hour = strtol(p, 0, 10);
-                       tm.min = strtol(r+1, 0, 10);
-                       if(r = memchr(r+1, ':', q-(r+1)))
-                               tm.sec = strtol(r+1, 0, 10);
+                       tm.min = strtol(r + 1, 0, 10);
+                       if(r = memchr(r + 1, ':', q - (r + 1)))
+                               tm.sec = strtol(r + 1, 0, 10);
                        else
                                tm.sec = 0;
                        continue;
                }
 
                /* look for month */
-               for(j=0; j<12; j++)
-                       if(strcmplwr(p, months[j], 3)==0){
+               for(j = 0; j < 12; j++)
+                       if(cistrncmp(p, months[j], 3) == 0){
                                tm.mon = j;
                                break;
                        }
-
-               if(j!=12)
+               if(j != 12)
                        continue;
 
                /* look for time zone [A-Z][A-Z]T */
-               if(q-p==3 && 'A' <= p[0] && p[0] <= 'Z' 
-               && 'A' <= p[1] && p[1] <= 'Z' && p[2] == 'T'){
-                       strecpy(tm.zone, tm.zone+4, p);
+               if(q - p == 3)
+               if(p[0] >= 'A' && p[0] <= 'Z')
+               if(p[1] >= 'A' && p[1] <= 'Z')
+               if(p[2] == 'T'){
+                       strecpy(tm.zone, tm.zone + 4, p);
                        continue;
                }
 
-               if(p[0]=='+'||p[0]=='-')
-               if(q-p==5 && strspn(p+1, "0123456789") == 4){
-                       delta = (((p[1]-'0')*10+p[2]-'0')*60+(p[3]-'0')*10+p[4]-'0')*60;
+               if(p[0] == '+'||p[0] == '-')
+               if(q - p == 5 && strspn(p + 1, "0123456789") == 4){
+                       delta = (((p[1] - '0')*10 + p[2] - '0')*60 + (p[3] - '0')*10 + p[4] - '0')*60;
                        if(p[0] == '-')
                                delta = -delta;
                        continue;
                }
-               if(strspn(p, "0123456789") == q-p){
+               if(strspn(p, "0123456789") == q - p){
                        j = strtol(p, nil, 10);
-                       if(1 <= j && j <= 31)
+                       if(j >= 1 && j <= 31)
                                tm.mday = j;
                        if(j >= 1900)
-                               tm.year = j-1900;
+                               tm.year = j - 1900;
+                       continue;
                }
+               //eprint("strtotm: garbage %.*s\n", q - p, p);
        }
-
-       if(tm.mon<0 || tm.year<0
-       || tm.hour<0 || tm.min<0
-       || tm.mday<0)
+       if(tm.mon < 0 || tm.year < 0
+       || tm.hour < 0 || tm.min < 0
+       || tm.mday < 0)
                return -1;
 
-       *tmp = *localtime(tm2sec(&tm)-delta);
+       *t = *localtime(tm2sec(&tm) - delta);
        return 0;
 }
diff --git a/sys/src/cmd/upas/fs/tester.c b/sys/src/cmd/upas/fs/tester.c
deleted file mode 100644 (file)
index 3d24012..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-#include <u.h>
-#include <libc.h>
-#include <ctype.h>
-#include <String.h>
-#include "message.h"
-
-Message *root;
-
-void
-prindent(int i)
-{
-       for(; i > 0; i--)
-               print(" ");
-}
-
-void
-prstring(int indent, char *tag, String *s)
-{
-       if(s == nil)
-               return;
-       prindent(indent+1);
-       print("%s %s\n", tag, s_to_c(s));
-}
-
-void
-info(int indent, int mno, Message *m)
-{
-       int i;
-       Message *nm;
-
-       prindent(indent);
-       print("%d%c %d ", mno, m->allocated?'*':' ', m->end - m->start);
-       if(m->unixfrom != nil)
-               print("uf %s ", s_to_c(m->unixfrom));
-       if(m->unixdate != nil)
-               print("ud %s ", s_to_c(m->unixdate));
-       print("\n");
-       prstring(indent, "from:", m->from822);
-       prstring(indent, "sender:", m->sender822);
-       prstring(indent, "to:", m->to822);
-       prstring(indent, "cc:", m->cc822);
-       prstring(indent, "reply-to:", m->replyto822);
-       prstring(indent, "subject:", m->subject822);
-       prstring(indent, "date:", m->date822);
-       prstring(indent, "filename:", m->filename);
-       prstring(indent, "type:", m->type);
-       prstring(indent, "charset:", m->charset);
-
-       i = 1;
-       for(nm = m->part; nm != nil; nm = nm->next){
-               info(indent+1, i++, nm);
-       }
-}
-       
-
-void
-main(int argc, char **argv)
-{
-       char *err;
-       char *mboxfile;
-
-       ARGBEGIN{
-       }ARGEND;
-
-       if(argc > 0)
-               mboxfile = argv[0];
-       else
-               mboxfile = "./mbox";
-
-       root = newmessage(nil);
-
-       err = readmbox(mboxfile, &root->part);
-       if(err != nil){
-               fprint(2, "boom: %s\n", err);
-               exits(0);
-       }
-
-       info(0, 1, root);
-
-       exits(0);
-}
diff --git a/sys/src/cmd/upas/imap4d/auth.c b/sys/src/cmd/upas/imap4d/auth.c
new file mode 100644 (file)
index 0000000..dad825b
--- /dev/null
@@ -0,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]);
+}
diff --git a/sys/src/cmd/upas/imap4d/capability b/sys/src/cmd/upas/imap4d/capability
new file mode 100644 (file)
index 0000000..b6d1a2f
--- /dev/null
@@ -0,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]
diff --git a/sys/src/cmd/upas/imap4d/copy.c b/sys/src/cmd/upas/imap4d/copy.c
new file mode 100644 (file)
index 0000000..5d63769
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/csquery.c b/sys/src/cmd/upas/imap4d/csquery.c
new file mode 100644 (file)
index 0000000..44a86c8
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/date.c b/sys/src/cmd/upas/imap4d/date.c
new file mode 100644 (file)
index 0000000..9692203
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/debug.c b/sys/src/cmd/upas/imap4d/debug.c
new file mode 100644 (file)
index 0000000..4871eea
--- /dev/null
@@ -0,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);
+
+}
diff --git a/sys/src/cmd/upas/imap4d/fetch.c b/sys/src/cmd/upas/imap4d/fetch.c
new file mode 100644 (file)
index 0000000..d3e6ef3
--- /dev/null
@@ -0,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, ')');
+}
diff --git a/sys/src/cmd/upas/imap4d/fns.h b/sys/src/cmd/upas/imap4d/fns.h
new file mode 100644 (file)
index 0000000..1e96a4b
--- /dev/null
@@ -0,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)
diff --git a/sys/src/cmd/upas/imap4d/folder.c b/sys/src/cmd/upas/imap4d/folder.c
new file mode 100644 (file)
index 0000000..f5e41dc
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/imap4d/fsenc.c b/sys/src/cmd/upas/imap4d/fsenc.c
new file mode 100644 (file)
index 0000000..b6df1b9
--- /dev/null
@@ -0,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("");
+}
+*/
diff --git a/sys/src/cmd/upas/imap4d/fstree.c b/sys/src/cmd/upas/imap4d/fstree.c
new file mode 100644 (file)
index 0000000..13ef32b
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/imap4d/imap4d.c b/sys/src/cmd/upas/imap4d/imap4d.c
new file mode 100644 (file)
index 0000000..94fa027
--- /dev/null
@@ -0,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
diff --git a/sys/src/cmd/upas/imap4d/imap4d.h b/sys/src/cmd/upas/imap4d/imap4d.h
new file mode 100644 (file)
index 0000000..35ce88b
--- /dev/null
@@ -0,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"
diff --git a/sys/src/cmd/upas/imap4d/imp.c b/sys/src/cmd/upas/imap4d/imp.c
new file mode 100644 (file)
index 0000000..c5542ea
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/list.c b/sys/src/cmd/upas/imap4d/list.c
new file mode 100644 (file)
index 0000000..1dd3533
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/mbox.c b/sys/src/cmd/upas/imap4d/mbox.c
new file mode 100644 (file)
index 0000000..1841b68
--- /dev/null
@@ -0,0 +1,630 @@
+#include "imap4d.h"
+
+static int     fsctl           = -1;
+static char    Ecanttalk[]     = "can't talk to mail server";
+
+static void
+fsinit(void)
+{
+       if(fsctl != -1)
+               return;
+       fsctl = open("/mail/fs/ctl", ORDWR);
+       if(fsctl == -1)
+               bye(Ecanttalk);
+}
+
+static void
+boxflags(Box *box)
+{
+       Msg *m;
+
+       box->recent = 0;
+       for(m = box->msgs; m != nil; m = m->next){
+               if(m->uid == 0){
+       //              fprint(2, "unassigned uid %s\n", m->info[Idigest]);
+                       box->dirtyimp = 1;
+                       m->uid = box->uidnext++;
+               }
+               if(m->flags & Frecent)
+                       box->recent++;
+       }
+}
+
+/*
+ * try to match permissions with mbox
+ */
+static int
+createimp(Box *box, Qid *qid)
+{
+       int fd;
+       long mode;
+       Dir *d;
+
+       fd = cdcreate(mboxdir, box->imp, OREAD, 0664);
+       if(fd < 0)
+               return -1;
+       d = cddirstat(mboxdir, box->name);
+       if(d != nil){
+               mode = d->mode & 0777;
+               nulldir(d);
+               d->mode = mode;
+               dirfwstat(fd, d);
+               free(d);
+       }
+       if(fqid(fd, qid) < 0){
+               close(fd);
+               return -1;
+       }
+
+       return fd;
+}
+
+/*
+ * read in the .imp file, or make one if it doesn't exist.
+ * make sure all flags and uids are consistent.
+ * return the mailbox lock.
+ */
+static Mblock*
+openimp(Box *box, int new)
+{
+       char buf[ERRMAX];
+       int fd;
+       Biobuf b;
+       Mblock *ml;
+       Qid qid;
+
+       ml = mblock();
+       if(ml == nil)
+               return nil;
+       fd = cdopen(mboxdir, box->imp, OREAD);
+       if(fd < 0 || fqid(fd, &qid) < 0){
+               if(fd < 0){
+                       errstr(buf, sizeof buf);
+                       if(cistrstr(buf, "does not exist") == nil)
+                               ilog("imp: %s: %s", box->imp, buf);
+                       else
+                               debuglog("imp: %s: %s .. creating", box->imp, buf);
+               }else{
+                       close(fd);
+                       ilog("%s: bogus imp: bad qid: recreating", box->imp);
+               }
+               fd = createimp(box, &qid);
+               if(fd < 0){
+                       ilog("createimp fails: %r");
+                       mbunlock(ml);
+                       return nil;
+               }
+               box->dirtyimp = 1;
+               if(box->uidvalidity == 0){
+                       ilog("set uidvalidity %lud [new]\n", box->uidvalidity);
+                       box->uidvalidity = box->mtime;
+               }
+               box->impqid = qid;
+               new = 1;
+       }else if(qid.path != box->impqid.path || qid.vers != box->impqid.vers){
+               Binit(&b, fd, OREAD);
+               if(parseimp(&b, box) == -1){
+                       ilog("%s: bogus imp: parse failure", box->imp);
+                       box->dirtyimp = 1;
+                       if(box->uidvalidity == 0){
+                               ilog("set uidvalidity %lud [parseerr]\n", box->uidvalidity);
+                               box->uidvalidity = box->mtime;
+                       }
+               }
+               Bterm(&b);
+               box->impqid = qid;
+               new = 1;
+       }
+       if(new)
+               boxflags(box);
+       close(fd);
+       return ml;
+}
+
+/*
+ * mailbox is unreachable, so mark all messages expunged
+ * clean up .imp files as well.
+ */
+static void
+mboxgone(Box *box)
+{
+       char buf[ERRMAX];
+       Msg *m;
+
+       rerrstr(buf, ERRMAX);
+       if(strstr(buf, "hungup channel"))
+               bye(Ecanttalk);
+//     too smart.
+//     if(cdexists(mboxdir, box->name) < 0)
+//             cdremove(mboxdir, box->imp);
+       for(m = box->msgs; m != nil; m = m->next)
+               m->expunged = 1;
+       ilog("mboxgone");
+       box->writable = 0;
+}
+
+/*
+ * read messages in the mailbox
+ * mark message that no longer exist as expunged
+ * returns -1 for failure, 0 if no new messages, 1 if new messages.
+ */
+enum {
+       Gone    = 2,            /* don't unexpunge messages */
+};
+
+static int
+readbox(Box *box)
+{
+       char buf[ERRMAX];
+       int i, n, fd, new, id;
+       Dir *d;
+       Msg *m, *last;
+
+       fd = cdopen(box->fsdir, ".", OREAD);
+       if(fd == -1){
+goinggoinggone:
+               rerrstr(buf, ERRMAX);
+               ilog("upas/fs stat of %s/%s aka %s failed: %r",
+                       username, box->name, box->fsdir);
+               mboxgone(box);
+               return -1;
+       }
+
+       if((d = dirfstat(fd)) == nil){
+               close(fd);
+               goto goinggoinggone;
+       }
+       box->mtime = d->mtime;
+       box->qid = d->qid;
+       last = nil;
+       for(m = box->msgs; m != nil; m = m->next){
+               last = m;
+               m->expunged |= Gone;
+       }
+       new = 0;
+       free(d);
+
+       for(;;){
+               n = dirread(fd, &d);
+               if(n <= 0){
+                       close(fd);
+                       if(n == -1)
+                               goto goinggoinggone;
+                       break;
+               }
+               for(i = 0; i < n; i++){
+                       if((d[i].qid.type & QTDIR) == 0)
+                               continue;
+                       id = atoi(d[i].name);
+                       if(m = fstreefind(box, id)){
+                               m->expunged &= ~Gone;
+                               continue;
+                       }
+                       new = 1;
+                       m = MKZ(Msg);
+                       m->id = id;
+                       m->fsdir = box->fsdir;
+                       m->fs = emalloc(2 * (Filelen + 1));
+                       m->efs = seprint(m->fs, m->fs + (Filelen + 1), "%ud/", id);
+                       m->size = ~0UL;
+                       m->lines = ~0UL;
+                       m->flags = Frecent;
+                       if(!msginfo(m))
+                               freemsg(0, m);
+                       else{
+                               fstreeadd(box, m);
+                               if(last == nil)
+                                       box->msgs = m;
+                               else
+                                       last->next = m;
+                               last = m;
+                       }
+               }
+               free(d);
+       }
+
+       /* box->max is invalid here */
+       return new;
+}
+
+int
+uidcmp(void *va, void *vb)
+{
+       Msg **a, **b;
+
+       a = va;
+       b = vb;
+       return (*a)->uid - (*b)->uid;
+}
+
+static void
+sequence(Box *box)
+{
+       Msg **a, *m;
+       int n, i;
+
+       n = 0;
+       for(m = box->msgs; m; m = m->next)
+               n++;
+       a = ezmalloc(n * sizeof *a);
+       i = 0;
+       for(m = box->msgs; m; m = m->next)
+               a[i++] = m;
+       qsort(a, n, sizeof *a, uidcmp);
+       for(i = 0; i < n - 1; i++)
+               a[i]->next = a[i + 1];
+       for(i = 0; i < n; i++)
+               if(a[i]->seq && a[i]->seq != i + 1)
+                       bye("internal error assigning message numbers");
+               else
+                       a[i]->seq = i + 1;
+       box->msgs = nil;
+       if(n > 0){
+               a[n - 1]->next = nil;
+               box->msgs = a[0];
+       }
+       box->max = n;
+       memset(a, 0, n*sizeof *a);
+       free(a);
+}
+
+/*
+ * strategy:
+ * every mailbox file has an associated .imp file
+ * which maps upas/fs message digests to uids & message flags.
+ *
+ * the .imp files are locked by /mail/fs/usename/L.mbox.
+ * whenever the flags can be modified, the lock file
+ * should be opened, thereby locking the uid & flag state.
+ * for example, whenever new uids are assigned to messages,
+ * and whenever flags are changed internally, the lock file
+ * should be open and locked.  this means the file must be
+ * opened during store command, and when changing the \seen
+ * flag for the fetch command.
+ *
+ * if no .imp file exists, a null one must be created before
+ * assigning uids.
+ *
+ * the .imp file has the following format
+ * imp         : "imap internal mailbox description\n"
+ *                     uidvalidity " " uidnext "\n"
+ *                     messagelines
+ *
+ * messagelines        :
+ *             | messagelines digest " " uid " " flags "\n"
+ *
+ * uid, uidnext, and uidvalidity are 32 bit decimal numbers
+ * printed right justified in a field Nuid characters long.
+ * the 0 uid implies that no uid has been assigned to the message,
+ * but the flags are valid. note that message lines are in mailbox
+ * order, except possibly for 0 uid messages.
+ *
+ * digest is an ascii hex string Ndigest characters long.
+ *
+ * flags has a character for each of NFlag flag fields.
+ * if the flag is clear, it is represented by a "-".
+ * set flags are represented as a unique single ascii character.
+ * the currently assigned flags are, in order:
+ *     Fseen           s
+ *     Fanswered       a
+ *     Fflagged        f
+ *     Fdeleted        D
+ *     Fdraft          d
+ */
+
+Box*
+openbox(char *name, char *fsname, int writable)
+{
+       char err[ERRMAX];
+       int new;
+       Box *box;
+       Mblock *ml;
+
+       fsinit();
+if(!strcmp(name, "mbox"))ilog("open %F %q", name, fsname);
+       if(fprint(fsctl, "open %F %q", name, fsname) < 0){
+               rerrstr(err, sizeof err);
+               if(strstr(err, "file does not exist") == nil)
+                       ilog("fs open %F as %s: %s", name, fsname, err);
+               if(strstr(err, "hungup channel"))
+                       bye(Ecanttalk);
+               fprint(fsctl, "close %s", fsname);
+               return nil;
+       }
+
+       /*
+        * read box to find all messages
+        * each one has a directory, and is in numerical order
+        */
+       box = MKZ(Box);
+       box->writable = writable;
+       box->name = smprint("%s", name);
+       box->imp = smprint("%s.imp", name);
+       box->fs = smprint("%s", fsname);
+       box->fsdir = smprint("/mail/fs/%s", fsname);
+       box->uidnext = 1;
+       box->fstree = avlcreate(fstreecmp);
+       new = readbox(box);
+       if(new >= 0 && (ml = openimp(box, new))){
+               closeimp(box, ml);
+               sequence(box);
+               return box;
+       }
+       closebox(box, 0);
+       return nil;
+}
+
+/*
+ * careful: called by idle polling proc
+ */
+Mblock*
+checkbox(Box *box, int imped)
+{
+       int new;
+       Dir *d;
+       Mblock *ml;
+
+       if(box == nil)
+               return nil;
+
+       /*
+        * if stat fails, mailbox must be gone
+        */
+       d = cddirstat(box->fsdir, ".");
+       if(d == nil){
+               mboxgone(box);
+               return nil;
+       }
+       new = 0;
+       if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers
+       || box->mtime != d->mtime){
+               new = readbox(box);
+               if(new < 0){
+                       free(d);
+                       return nil;
+               }
+       }
+       free(d);
+       ml = openimp(box, new);
+       if(ml == nil){
+               ilog("openimp fails; box->writable = 0: %r");
+               box->writable = 0;
+       }else if(!imped){
+               closeimp(box, ml);
+               ml = nil;
+       }
+       if(new || box->dirtyimp)
+               sequence(box);
+       return ml;
+}
+
+/*
+ * close the .imp file, after writing out any changes
+ */
+void
+closeimp(Box *box, Mblock *ml)
+{
+       int fd;
+       Biobuf b;
+       Qid qid;
+
+       if(ml == nil)
+               return;
+       if(!box->dirtyimp){
+               mbunlock(ml);
+               return;
+       }
+       fd = cdcreate(mboxdir, box->imp, OWRITE, 0664);
+       if(fd < 0){
+               mbunlock(ml);
+               return;
+       }
+       Binit(&b, fd, OWRITE);
+       box->dirtyimp = 0;
+       wrimp(&b, box);
+       Bterm(&b);
+
+       if(fqid(fd, &qid) == 0)
+               box->impqid = qid;
+       close(fd);
+       mbunlock(ml);
+}
+
+void
+closebox(Box *box, int opened)
+{
+       Msg *m, *next;
+
+       /*
+        * make sure to leave the mailbox directory so upas/fs can close the mailbox
+        */
+       mychdir(mboxdir);
+
+       if(box->writable){
+               deletemsg(box, 0);
+               if(expungemsgs(box, 0))
+                       closeimp(box, checkbox(box, 1));
+       }
+
+       if(fprint(fsctl, "close %s", box->fs) < 0 && opened)
+               bye(Ecanttalk);
+       for(m = box->msgs; m != nil; m = next){
+               next = m->next;
+               freemsg(box, m);
+       }
+       free(box->name);
+       free(box->fs);
+       free(box->fsdir);
+       free(box->imp);
+       free(box->fstree);
+       free(box);
+}
+
+int
+deletemsg(Box *box, Msgset *ms)
+{
+       char buf[Bufsize], *p, *start;
+       int ok;
+       Msg *m;
+
+       if(!box->writable)
+               return 0;
+
+       /*
+        * first pass: delete messages; gang the writes together for speed.
+        */
+       ok = 1;
+       start = seprint(buf, buf + sizeof buf, "delete %s", box->fs);
+       p = start;
+       for(m = box->msgs; m != nil; m = m->next)
+               if(ms == 0 || ms && inmsgset(ms, m->uid))
+               if((m->flags & Fdeleted) && !m->expunged){
+                       m->expunged = 1;
+                       p = seprint(p, buf + sizeof buf, " %ud", m->id);
+                       if(p + 32 >= buf + sizeof buf){
+                               if(write(fsctl, buf, p - buf) == -1)
+                                       bye(Ecanttalk);
+                               p = start;
+                       }
+               }
+       if(p != start && write(fsctl, buf, p - buf) == -1)
+               bye(Ecanttalk);
+       return ok;
+}
+
+/*
+ * second pass: remove the message structure,
+ * and renumber message sequence numbers.
+ * update messages counts in mailbox.
+ * returns true if anything changed.
+ */
+int
+expungemsgs(Box *box, int send)
+{
+       uint n;
+       Msg *m, *next, *last;
+
+       n = 0;
+       last = nil;
+       for(m = box->msgs; m != nil; m = next){
+               m->seq -= n;
+               next = m->next;
+               if(m->expunged){
+                       if(send)
+                               Bprint(&bout, "* %ud expunge\r\n", m->seq);
+                       if(m->flags & Frecent)
+                               box->recent--;
+                       n++;
+                       if(last == nil)
+                               box->msgs = next;
+                       else
+                               last->next = next;
+                       freemsg(box, m);
+               }else
+                       last = m;
+       }
+       if(n){
+               box->max -= n;
+               box->dirtyimp = 1;
+       }
+       return n;
+}
+
+static char *stoplist[] =
+{
+       ".",
+       "dead.letter",
+       "forward",
+       "headers",
+       "imap.subscribed",
+       "mbox",
+       "names",
+       "pipefrom",
+       "pipeto",
+       0
+};
+
+/*
+ * reject bad mailboxes based on mailbox name
+ */
+int
+okmbox(char *path)
+{
+       char *name;
+       int i, c;
+
+       name = strrchr(path, '/');
+       if(name == nil)
+               name = path;
+       else
+               name++;
+       if(strlen(name) + STRLEN(".imp") >= Pathlen)
+               return 0;
+       for(i = 0; stoplist[i]; i++)
+               if(strcmp(name, stoplist[i]) == 0)
+                       return 0;
+       c = name[0];
+       if(c == 0 || c == '-' || c == '/'
+       || isdotdot(name)
+       || isprefix("L.", name)
+       || isprefix("imap-tmp.", name)
+       || issuffix("-", name)
+       || issuffix(".00", name)
+       || issuffix(".imp", name)
+       || issuffix(".idx", name))
+               return 0;
+
+       return 1;
+}
+
+int
+creatembox(char *mbox)
+{
+       fsinit();
+       if(fprint(fsctl, "create %q", mbox) > 0){
+               fprint(fsctl, "close %s", mbox);
+               return 0;
+       }
+       return -1;
+}
+
+/*
+ * rename mailbox.  truncaes or removes the source.
+ * bug? is the lock required
+ * upas/fs helpfully moves our .imp file.
+ */
+int
+renamebox(char *from, char *to, int doremove)
+{
+       char *p;
+       int r;
+       Mblock *ml;
+
+       fsinit();
+       ml = mblock();
+       if(ml == nil)
+               return 0;
+       if(doremove)
+               r = fprint(fsctl, "rename %F %F", from, to);
+       else
+               r = fprint(fsctl, "rename -t %F %F", from, to);
+       if(r > 0){
+               if(p = strrchr(to, '/'))
+                       p++;
+               else
+                       p = to;
+               fprint(fsctl, "close %s", p);
+       }
+       mbunlock(ml);
+       return r > 0;
+}
+
+/*
+ * upas/fs likes us; he removes the .imp file
+ */
+int
+removembox(char *path)
+{
+       fsinit();
+       return fprint(fsctl, "remove %s", path) > 0;
+}
diff --git a/sys/src/cmd/upas/imap4d/mkfile b/sys/src/cmd/upas/imap4d/mkfile
new file mode 100644 (file)
index 0000000..bf5b9f7
--- /dev/null
@@ -0,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
diff --git a/sys/src/cmd/upas/imap4d/msg.c b/sys/src/cmd/upas/imap4d/msg.c
new file mode 100644 (file)
index 0000000..fbd21d2
--- /dev/null
@@ -0,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;
+               }
+       }
+}
diff --git a/sys/src/cmd/upas/imap4d/mutf7.c b/sys/src/cmd/upas/imap4d/mutf7.c
new file mode 100644 (file)
index 0000000..c8872d5
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/nlisttst.c b/sys/src/cmd/upas/imap4d/nlisttst.c
new file mode 100644 (file)
index 0000000..c6a8cbf
--- /dev/null
@@ -0,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("");
+}
diff --git a/sys/src/cmd/upas/imap4d/nodes.c b/sys/src/cmd/upas/imap4d/nodes.c
new file mode 100644 (file)
index 0000000..8af84d9
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/print.c b/sys/src/cmd/upas/imap4d/print.c
new file mode 100644 (file)
index 0000000..8fd236f
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/imap4d/quota.c b/sys/src/cmd/upas/imap4d/quota.c
new file mode 100644 (file)
index 0000000..375185d
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/imap4d/search.c b/sys/src/cmd/upas/imap4d/search.c
new file mode 100644 (file)
index 0000000..42c7ad6
--- /dev/null
@@ -0,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;
+}
diff --git a/sys/src/cmd/upas/imap4d/store.c b/sys/src/cmd/upas/imap4d/store.c
new file mode 100644 (file)
index 0000000..1abe999
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/imap4d/utils.c b/sys/src/cmd/upas/imap4d/utils.c
new file mode 100644 (file)
index 0000000..b2a8bb0
--- /dev/null
@@ -0,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);
+       }
+}
index f62fc9e28d9ac97418e548828a93ed340250205a..7ddec78eedb19a9bfbb97c09354414996a681756 100644 (file)
@@ -196,7 +196,8 @@ main(int argc, char **argv)
        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 @@ main(int argc, char **argv)
                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,68 +1033,14 @@ special(String *s)
        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;
-
-       fd = -1;
-       if(rcvr != nil)
-               fd = openfolder(rcvr);
+       Biobuf *b;
 
        ac = 0;
        for(a = to; a != nil; a = a->next)
@@ -1104,7 +1049,7 @@ sendmail(Addr *to, Addr *cc, Addr *bcc, int *pid, char *rcvr)
                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 @@ sendmail(Addr *to, Addr *cc, Addr *bcc, int *pid, char *rcvr)
                                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 @@ sendmail(Addr *to, Addr *cc, Addr *bcc, int *pid, char *rcvr)
 
                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 @@ freealiases(Alias *a)
 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 @@ readaliases(void)
        }
        s_freeinstack(sp);
 out:
-       s_free(file);
        s_free(line);
        s_free(token);
        return first;
index 737fcff82c015a78c9433fa609c6a508730672b1..acbd8fe1fab6caf85360782779365fccafd9b7ca 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=marshal
 
@@ -8,8 +9,6 @@ HFILES= ../common/common.h\
 
 OFILES= marshal.$O
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        $HFILES\
diff --git a/sys/src/cmd/upas/misc/empty.c b/sys/src/cmd/upas/misc/empty.c
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sys/src/cmd/upas/misc/go.fishing b/sys/src/cmd/upas/misc/go.fishing
deleted file mode 100755 (executable)
index 8035fe0..0000000
+++ /dev/null
@@ -1,15 +0,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
diff --git a/sys/src/cmd/upas/misc/gone.fishing b/sys/src/cmd/upas/misc/gone.fishing
deleted file mode 100755 (executable)
index 66fb10b..0000000
+++ /dev/null
@@ -1,19 +0,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
-}
index ac1e7bd9e071256049a5af3f3cf37905fbe5f5fb..9eb1e5a6bba1b2c3bbdf8f066b50becce3d24c68 100644 (file)
@@ -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
diff --git a/sys/src/cmd/upas/misc/mail b/sys/src/cmd/upas/misc/mail
deleted file mode 100755 (executable)
index fdf3a0f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/rc
-switch($#*){
-case 0
-       exec upas/nedmail
-}
-
-switch($1){
-case -f* -r* -c* -m*
-       exec upas/nedmail $*
-case *
-       exec upas/marshal -8 $*
-}
diff --git a/sys/src/cmd/upas/misc/mail.rc b/sys/src/cmd/upas/misc/mail.rc
new file mode 100755 (executable)
index 0000000..fdf3a0f
--- /dev/null
@@ -0,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 $*
+}
index 0fa970e17f919c9482875aa5deef3d4a7c054f88..c3593840b57222278f5cdea1001291169f6333f3 100644 (file)
@@ -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\
diff --git a/sys/src/cmd/upas/misc/omail.rc b/sys/src/cmd/upas/misc/omail.rc
new file mode 100755 (executable)
index 0000000..ed64f38
--- /dev/null
@@ -0,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 $*
+}
diff --git a/sys/src/cmd/upas/misc/unix/gone.fishing.sh b/sys/src/cmd/upas/misc/unix/gone.fishing.sh
deleted file mode 100755 (executable)
index a304271..0000000
+++ /dev/null
@@ -1,9 +0,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
-}
diff --git a/sys/src/cmd/upas/misc/unix/mail.c b/sys/src/cmd/upas/misc/unix/mail.c
deleted file mode 100644 (file)
index 20cf239..0000000
+++ /dev/null
@@ -1,51 +0,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);
-}
-
diff --git a/sys/src/cmd/upas/misc/unix/mail.sh b/sys/src/cmd/upas/misc/unix/mail.sh
deleted file mode 100755 (executable)
index a41519a..0000000
+++ /dev/null
@@ -1,12 +0,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
diff --git a/sys/src/cmd/upas/misc/unix/makefile b/sys/src/cmd/upas/misc/unix/makefile
deleted file mode 100644 (file)
index e00c4f7..0000000
+++ /dev/null
@@ -1,44 +0,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
-
index d9bd5925281f8d202118226c80022d7f72e7ea61..0092b5b7640ba5d7bb405f6ec0cada42fc5b2743 100644 (file)
@@ -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
 
diff --git a/sys/src/cmd/upas/mkupas b/sys/src/cmd/upas/mkupas
new file mode 100644 (file)
index 0000000..66790ac
--- /dev/null
@@ -0,0 +1,5 @@
+BIN=/$objtype/bin/upas
+ABIN=/acme/bin/$objtype
+
+../common/libcommon.a$O:
+       cd ../common; mk; mk clean
index 5726b348dc87d1bdaa7fb8ea3670d8dc83132eca..670c2989c2cba591ff7be6cde495397d20cabcff 100644 (file)
@@ -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 @@ writeaddr(char *file, char *addr, int rem, char *listname)
                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 @@ addaddr(char *addr)
        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 @@ readaddrs(char *file)
        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 @@ sendnotification(char *addr, char *listname, int rem)
        int pfd[2];
        Waitmsg *w;
 
-       putenv("upasname", smprint("%s-owner", listname));
+       setsender(listname);
        if(pipe(pfd) < 0)
                sysfatal("creating pipe: %r");
        switch(fork()){
index 5142e56f45d3dd97bee0646cc3992839f0596585..7a6350e16be82da6acb2b730e14171c6703b3981 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=ml\
        mlowner\
@@ -7,7 +8,7 @@ TARG=ml\
 OFILES=\
        common.$O\
 
-LIB=../common/libcommon.av\
+LIB=../common/libcommon.a$O\
 
 UHFILES= ../common/common.h\
        ../common/sys.h\
@@ -18,8 +19,6 @@ HFILES=$UHFILES\
 
 LIB=../common/libcommon.a$O\
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        $UHFILES\
@@ -34,7 +33,4 @@ $O.ml: ../smtp/rfc822.tab.$O
 $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
index 213475434fd61bd7389f5d91997b0165ddf99e73..4d35da28d8ae38ac59ca3b354c7c2a738bfe1a68 100644 (file)
@@ -1,16 +1,89 @@
 #include "common.h"
 #include "dat.h"
 
-Biobuf in;
+Biobuf in;
+Addr   *al;
+int    na;
+String *from;
+String *sender;
+
+char*
+trim(char *s)
+{
+       while(*s == ' ' || *s == '\t')
+               s++;
+       return s;
+}
 
-Addr *al;
-int na;
-String *from;
-String *sender;
+/* add the listname to the subject */
+void
+printsubject(int fd, Field *f, char *listname)
+{
+       char *s, *e, *ln;
+       Node *p;
 
-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);
+       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)
@@ -22,16 +95,21 @@ usage(void)
 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 @@ main(int argc, char **argv)
        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 @@ main(int argc, char **argv)
                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);
@@ -93,75 +174,3 @@ main(int argc, char **argv)
        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);
-}
index a1d1b907e9d4233fe36355327fa3fabfe16fa936..4ac4435adfe4a6f948a086b3b91f1a44e0fdb4f7 100644 (file)
@@ -1,11 +1,63 @@
 #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 @@ usage(void)
 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 @@ main(int argc, char **argv)
                usage();
 
        listname = argv[0];
-       alfile = s_new();
-       mboxpath("address-list", listname, alfile, 0);
-
-       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((dom = strchr(listname, '@')) != nil)
+               *dom++ = 0;
+       mboxpathbuf(alfile, sizeof alfile, listname, "address-list");
+
+       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);
 }
index d3115978eedb2dd696be03a6cd6662d9c133251c..47761f8e2c856b54daff9b3dad8108568c090b40 100644 (file)
@@ -22,6 +22,8 @@ main(int argc, char **argv)
        char *listname;
 
        ARGBEGIN{
+       default:
+               usage();
        }ARGEND;
 
        rfork(RFENVG|RFREND);
index bc54914dfd71fa64ee06998be41569f2ec20d002..e5b8df4062e41f5a72f032c98ea4d509f13ddcde 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=nedmail
 
@@ -8,8 +9,6 @@ HFILES= ../common/common.h\
 
 OFILES=nedmail.$O
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        ${OFILES:%.$O=%.c}\
index 457f4705a8391ebcf79c3f673fee4ae1eddaac22..192d209e202d823f95af108d5feb5805a655ecff 100644 (file)
@@ -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 @@ struct Message {
        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,151 +37,281 @@ struct Message {
        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" },
        { "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 },
-};
-
-enum
-{
-       NARG=   32,
+       { "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" },
 };
 
 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,43 +326,37 @@ catchnote(void*, char *note)
        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 {
        case 'c':
                cflag = 1;
                break;
-       case 'f':
-               file = EARGF(usage());
-               break;
        case 's':
-               singleton = EARGF(usage());
+               singleton = 1;
+       case 'f':
+               mb = EARGF(usage());
                break;
        case 'r':
                reverse = 0;
@@ -242,21 +365,28 @@ main(int argc, char **argv)
                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");
 
        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,82 +400,73 @@ main(int argc, char **argv)
                system("/bin/upas/fs", av, -1);
        }
 
-       switchmb(file, singleton);
-
-       top.path = s_copy(root);
+       switchmb(mb, singleton);
+       top.path = strdup(root);
+       for(cp = ctype; cp < ctype + nelem(ctype) - 1; cp++)
+               cp->next = cp + 1;
 
-       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 = &top;
-               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;
                        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
                                                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 @@ main(int argc, char **argv)
        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 @@ file2message(Message *parent, char *name)
        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 @@ freemessage(Message *m)
                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,63 +604,171 @@ dir2message(Message *parent, int reverse)
        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)
-                       continue;
-               if(atoi(d[i].name) <= highest)
+       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;
-               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)
 {
        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 @@ dosingleton(Message *parent, char *path)
        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,101 +793,19 @@ dosingleton(Message *parent, char *path)
        return m;
 }
 
-//
-//  read a file into a string
-//
-String*
-file2string(String *dir, char *file)
-{
-       String *s;
-       int fd, n, m;
-
-       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;
-}
-
-//
-//  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)
+/*
+ *   walk the path name an element
+ */
+char*
+extendp(char *dir, char *name)
 {
-       while(n-- > 0){
-               if(tolower(*a++) != tolower(*b++))
-                       return -1;
-       }
-       return 0;
-}
+       static char buf[Pathlen];
 
-int
-cistrcmp(char *a, char *b)
-{
-       for(;;){
-               if(tolower(*a) != tolower(*b++))
-                       return -1;
-               if(*a++ == 0)
-                       break;
-       }
-       return 0;
+       if(strcmp(dir, ".") == 0)
+               snprint(buf, sizeof buf, "%s", name);
+       else
+               snprint(buf, sizeof buf, "%s/%s", dir, name);
+       return buf;
 }
 
 char*
@@ -611,7 +816,7 @@ nosecs(char *t)
        p = strchr(t, ':');
        if(p == nil)
                return t;
-       p = strchr(p+1, ':');
+       p = strchr(p + 1, ':');
        if(p != nil)
                *p = 0;
        return t;
@@ -630,47 +835,45 @@ month(char *m)
 
        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;
        }
        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]);
                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 @@ cracktime(char *d, char *out, int len)
        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 @@ findctype(Message *m)
                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)
-{
-       char timebuf[32];
-       String *id;
-       char *p, *q;
-
-       // create id
-       id = s_new();
-       mkid(id, m);
-
-       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){
+pheader0(char *buf, int len, Message *m)
+{
+       char *f, *p, *q, frombuf[40], timebuf[32], h[5];
+       int max;
+
+       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(*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;
-
-       // create id
-       id = s_new();
-       mkid(id, m);
+       char *p, *e, typeid[80];
 
        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 @@ snprintHeader(char *buf, int len, int indent, Message *m)
                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 @@ parsesearch(char **pp)
        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 @@ parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp
                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 @@ parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp
                        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 @@ parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp
                                *mp = &top;
                        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))
-                                       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))
+               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 ? nil : cur->prev; m != nil; m = m->prev){
-                               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;
                }
                if(m == nil)
                        return "search";
@@ -1008,96 +1261,54 @@ parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp
                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;
        }
 
        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 @@ parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
                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;
-                                       }
-                               }
-                       } 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;
-                                       }
+                       for(m = first; m != nil; m = m->next){
+                               if(f(m, prog, buf)){
+                                       *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 @@ parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
                }
        }
 
-       // 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 = ' ';
-       }
-
-       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;
-       }
-       return nil; 
-}
-
-// 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;
+       while(*p == ' ' || *p == '\t')
+               p++;
+       /* hack to allow all messages to start with 'd' */
+       if(*p == 'd' && p[1]){
+               cmd->delete = 1;
+               p++;
        }
-       *p = 0;
-       return line;
-}
-
-void
-messagecount(Message *m)
-{
-       int i;
-
-       i = 0;
-       for(; m != nil; m = m->next)
-               i++;
-       Bprint(&out, "%d message%s\n", i, plural(i));
+       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;
+       }
+       cmd->f = cmdtab[i].f;
+
+       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;
 }
 
 Message*
 aichcmd(Message *m, int indent)
 {
-       char    hdr[256];
-
-       if(m == &top)
-               return nil;
+       char hdr[256];
 
-       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,29 +1451,28 @@ Hcmd(Cmd*, Message *m)
 {
        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*
 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 @@ bcmd(Cmd*, Message *m)
                m = m->next;
        }
 
-       return om;
+       return m != nil? m: om;
 }
 
 Message*
@@ -1323,21 +1493,54 @@ ncmd(Cmd*, Message *m)
 }
 
 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)
@@ -1348,16 +1551,24 @@ printpart(String *s, char *part)
        return tot;
 }
 
+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 @@ Pcmd(Cmd*, Message *m)
        if(m == &top)
                return &top;
        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 @@ compress(char *p)
        *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 &top;
-       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 @@ pcmd(Cmd*, Message *m)
                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 @@ printpartindented(String *s, char *part, char *indent)
        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 &top;
@@ -1504,9 +1825,9 @@ quotecmd(Cmd*, Message *m)
        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,88 +1836,102 @@ quotecmd(Cmd*, Message *m)
                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;
 
        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;
                freemessage(m);
        }
        if(n)
-               write(fd, buf, p-buf);
+               write(fd, buf, p - buf);
 
        close(fd);
 
        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;
@@ -1606,54 +1941,83 @@ flushdeleted(Message *cur)
        return cur;
 }
 
+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 &top;
+}
+
 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*
 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,24 +2028,98 @@ ucmd(Cmd*, Message *m)
                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 *c, Message *m)
+{
+       char buf[128], *p, *e;
+       Dirstats s;
+
+       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*
-icmd(Cmd*, Message *m)
+kcmd0(Cmd *c, Message *m)
 {
-       int n;
+       char *f, *s;
+       int sticky;
 
-       n = dir2message(&top, reverse);
-       if(n > 0)
-               Bprint(&out, "%d new message%s\n", n, plural(n));
+       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)
 {
@@ -1690,38 +2128,50 @@ helpcmd(Cmd*, Message *m)
        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 @@ tomailer(char **av)
                        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 @@ tokenize822(char *str, char **args, int max)
                }
 }
 
-/* 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 != &top; 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 != &top; 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 != &top; nm = nm->parent){
+       for(nm = m; nm != &top; 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,39 +2296,44 @@ rcmd(Cmd *c, Message *m)
                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;
 }
 
 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,154 +2343,103 @@ mcmd(Cmd *c, Message *m)
                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;
 }
 
 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;
-               }
-       }
-       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
+               folder = foldername(mbpath, user, base);
+       if(folder == nil)
+               return -1;
        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 @@ scmd(Cmd *c, Message *m)
 {
        char *file;
 
-       if(m == &top){
-               Bprint(&out, "!address\n");
-               return nil;
-       }
-
        switch(c->an){
        case 1:
                file = "stored";
@@ -2054,14 +2456,12 @@ scmd(Cmd *c, Message *m)
                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,18 +2470,13 @@ wcmd(Cmd *c, Message *m)
 {
        char *file;
 
-       if(m == &top){
-               Bprint(&out, "!address\n");
-               return nil;
-       }
-
        switch(c->an){
        case 2:
                file = c->av[1];
                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 @@ wcmd(Cmd *c, Message *m)
                        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)
-{
-       char *p;
-       int i;
-
-       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;
-}
-
-// open the folder using the recipients account name
-static String*
-foldername(char *rcvr)
-{
-       char *p;
-       int c;
-       String *file;
-       Dir *d;
-       int scarey;
-
-       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;
+void
+loadxfrom(int fd)
+{
+       char *f[3], *s, *p;
+       int n, a, inc;
+       Biobuf b;
+       Xtab *x;
+
+       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++;
        }
-       free(d);
-
-       p = strrchr(rcvr, '!');
-       if(p != nil)
-               rcvr = p+1;
+}
 
-       while(*rcvr && *rcvr != '@'){
-               c = *rcvr++;
-               if(c == '/')
-                       c = '_';
-               s_putc(file, c);
+char*
+frombox(char *from)
+{
+       char *s;
+       int n, m, fd;
+       Xtab *t, *p;
+       static int once;
+
+       if(once == 0){
+               once = 1;
+               s = foldername(mbpath, user, "fromtab-");
+               fd = open(s, OREAD);
+               if(fd != -1)
+                       loadxfrom(fd);
+               close(fd);
        }
-       s_terminate(file);
-
-       if(scarey && special(file)){
-               fprint(2, "!won't overwrite %s\n", s_to_c(file));
-               s_free(file);
-               return nil;
+       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;
        }
-
-       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;
-
-       if(c->an > 1){
-               fprint(2, "!usage: f takes no arguments\n");
-               return nil;
-       }
-
-       if(m == &top){
-               Bprint(&out, "!address\n");
-               return nil;
-       }
+       char *f;
 
-       folder = foldername(m->from);
-       if(folder == nil)
+       f = frombox(m->from);
+       if(appendtofile(m, "rawunix", f, 1, 1) < 0)
                return nil;
+       return m;
+}
 
-       if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
-               s_free(folder);
-               return nil;
-       }
-       s_free(folder);
+Message*
+fqcmd(Cmd*, Message *m)
+{
+       char *f;
 
-       m->stored = 1;
+       f = frombox(m->from);
+       Bprint(&out, "! %s\n", f);
        return m;
 }
 
@@ -2219,7 +2607,7 @@ system(char *cmd, char **av, int in)
                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,68 +2625,40 @@ system(char *cmd, char **av, int in)
 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;
 }
 
 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");
-               return nil;
-       }
-
-       if(m == &top){
-               Bprint(&out, "!address\n");
+               eprint("!usage: | cmd\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 @@ closemb(void)
        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";
-
-               // close current mailbox
-               closemb();
-               didopen = 1;
+       p = strrchr(s, c);
+       if(p != nil && p > s) {
+               *p = 0;
+               return p - 1;
+       }
+       return 0;
+}
 
+/* 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)
-{
-       int i;
-
-       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)
+char*
+rooted(char *s)
 {
-       static char buf[256];
+       static char buf[Pathlen];
 
        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 @@ regerror(char*)
 {
 }
 
-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);
 }
index 3f3df413487d6c5c93d1de0306a60c0224aeb0a2..7b0348a7a3feed1e3df998fc51f127950f969394 100644 (file)
@@ -1,10 +1,10 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=pop3
 
 OFILES=pop3.$O
 
-BIN=/$objtype/bin/upas
 LIB=../common/libcommon.a$O
 
 UPDATE=\
index 1416f03ca658aa6766c69af10d3a913a0768b115..55f9d1b840a28ca946689db50c27498ac592aa01 100644 (file)
@@ -96,6 +96,7 @@ main(int argc, char **argv)
        int fd;
        char *arg, cmdbuf[1024];
        Cmd *c;
+       NetConnInfo *n;
 
        rfork(RFNAMEG);
        Binit(&in, 0, OREAD);
@@ -114,9 +115,6 @@ main(int argc, char **argv)
                        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 @@ main(int argc, char **argv)
                        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 @@ static int
 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 @@ readmbox(char *box)
                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 @@ listcmd(char *arg)
 }
 
 static int
-noopcmd(char *arg)
+noopcmd(char*)
 {
-       USED(arg);
        sendok("");
        return 0;
 }
@@ -551,31 +553,27 @@ trace(char *fmt, ...)
 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");
        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 @@ nextarg(char *p)
  * 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 @@ setuser(char *arg)
 {
        char *p;
 
+       *user = 0;
        strcpy(box, "/mail/box/");
        strecpy(box+strlen(box), box+sizeof box-7, arg);
        strcpy(cbox, box);
@@ -707,8 +706,11 @@ usercmd(char *arg)
                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 @@ enableaddr(void)
         * 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 @@ dologin(char *response)
                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 @@ passcmd(char *arg)
        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 @@ apopcmd(char *arg)
        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);
 }
 
index f21d2992a627ebb1de46e520f8f027566e7b1fcf..f58d82791e951d15a76c7ddda4e9e7f27e2ec213 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG = qer\
        runq\
@@ -10,8 +11,6 @@ HFILES=../common/common.h\
 
 LIB=../common/libcommon.a$O\
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        $HFILES\
index 53315a6aef14ffc5b502c607d214473849cf907c..9367005455d3835abc7646306355ff8257b755d4 100644 (file)
@@ -23,7 +23,7 @@ usage(void)
 void
 error(char *f, char *a)
 {
-       char err[Errlen+1];
+       char err[ERRMAX];
        char buf[256];
 
        rerrstr(err, sizeof(err));
index 60ac66d11129ef8bbd35731b3cb1daaf8e47b057..69efe4570473695dbbf730d01f1e3d69d0c8213f 100644 (file)
@@ -136,7 +136,7 @@ emptydir(char *name)
                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 @@ doalldirs(void)
                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 @@ rundir(char *name)
                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 @@ remmatch(char *name)
 
        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 @@ keeplockalive(char *path, int fd)
        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 @@ dofile(Dir *dp)
        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 @@ dofile(Dir *dp)
                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 @@ file(char *name, char type)
 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 @@ out:
 void
 warning(char *f, void *a)
 {
-       char err[65];
+       char err[ERRMAX];
        char buf[256];
 
        rerrstr(err, sizeof(err));
@@ -679,7 +666,7 @@ warning(char *f, void *a)
 void
 error(char *f, void *a)
 {
-       char err[Errlen];
+       char err[ERRMAX];
        char buf[256];
 
        rerrstr(err, sizeof(err));
diff --git a/sys/src/cmd/upas/qfrom/mkfile b/sys/src/cmd/upas/qfrom/mkfile
new file mode 100644 (file)
index 0000000..5bee6c9
--- /dev/null
@@ -0,0 +1,13 @@
+</$objtype/mkfile
+
+TARG=qfrom
+OFILES=qfrom.$O\
+
+BIN=/$objtype/bin/upas
+
+UPDATE=\
+       mkfile\
+       $HFILES\
+       ${OFILES:%.$O=%.c}\
+
+</sys/src/cmd/mkone
diff --git a/sys/src/cmd/upas/qfrom/qfrom.c b/sys/src/cmd/upas/qfrom/qfrom.c
new file mode 100644 (file)
index 0000000..df06d24
--- /dev/null
@@ -0,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("");
+}
index 5f0db8556d50d27e5ac57aebab2a7dae82927b3a..37141c53589b9b1d40e42125e53dbde586d21c6e 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=scanmail\
        testscan
@@ -10,7 +11,6 @@ HFILES=       spam.h\
 
 LIB=   ../common/libcommon.a$O\
 
-BIN=/$objtype/bin/upas
 UPDATE=\
        mkfile\
        $HFILES\
@@ -21,4 +21,4 @@ UPDATE=\
 CFLAGS=$CFLAGS -I../common
 
 scanmail.$O:   scanmail.c
-       $CC $CFLAGS -D'SPOOL="/mail"' scanmail.c
+       $CC $CFLAGS scanmail.c
index 444bbcdd20065d1b2eb19bd2b114b345b34f05a8..c84c0fa5db976fea515156cb0aea0964c9be2c4e 100644 (file)
@@ -1,4 +1,5 @@
 #include "common.h"
+#include <regexp.h>
 #include "spam.h"
 
 int    cflag;
@@ -35,7 +36,7 @@ int   optoutofspamfilter(char*);
 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 @@ Malloc(long n)
        void *p;
 
        p = malloc(n);
-       if(p == 0)
-               exits("malloc");
+       if(p == nil)
+               sysfatal("malloc: %r");
        return p;
 }
 
@@ -60,8 +61,9 @@ void*
 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 @@ main(int argc, char *argv[])
 
        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 @@ main(int argc, char *argv[])
        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,41 +163,41 @@ main(int argc, char *argv[])
                        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);
        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);
                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 @@ opendump(char *sender)
        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 @@ opendump(char *sender)
                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 @@ opencopy(char *sender)
                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;
index f1d24b2e0a6b13abe9565a47d94d30c712d76235..2f49d7581a3ccf95d4a67cee5afd1538db0aebee 100644 (file)
@@ -1,4 +1,3 @@
-
 enum{
        Dump            = 0,            /* Actions must be in order of descending importance */
        HoldHeader,
index e5ea59ad5788f5bdcf96c0d9d316f5b1d14abe38..61ea70a1555a92c9ea2c6f437369fa6fc46813ff 100644 (file)
@@ -1,4 +1,5 @@
-#include "sys.h"
+#include "common.h"
+#include <regexp.h>
 #include "spam.h"
 
 int    debug;
@@ -13,7 +14,7 @@ int   matchaction(Patterns*, char*);
 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 @@ Malloc(long n)
        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 @@ void*
 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 @@ main(int argc, char *argv[])
        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 @@ main(int argc, char *argv[])
                debug++;
                break;
        case 'p':
-               strcpy(patfile,ARGF());
+               snprint(patfile, sizeof patfile, "%s", EARGF(usage()));
                break;
        } ARGEND
 
index 6fa12c57cc5aab62cb0deda75b0c51da495e3284..98cf1220b4fcddfb74fd01c44a5d7e6617356a23 100644 (file)
@@ -12,7 +12,7 @@ authorize(dest *dp)
        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;
index 8a8fc8eacf1d390698cf7666c354847aec0ac7cd..5e97b872fb97b210bc37fbafd5907cc3e31437e2 100644 (file)
@@ -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 @@ up_bind(dest *destp, message *mp, int checkforward)
         *      - 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 @@ up_bind(dest *destp, message *mp, int checkforward)
 
        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;
-}
-
index cdd16ecedfebfda86c0769b1614f2c3d1ef43389..f4001aa09fd51d5ea269ef0d43dd0f99af76adf2 100644 (file)
 #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;
+       }
+       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);
        }
-       sysclose(fp);
-       sysunlock(l);
+       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;
index 3d7fffe45452ffe59cb9a8dff287eaeb480649f1..f5ff5b825c0092c32622691dc4e57a28f5b56788 100644 (file)
@@ -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 @@ d_new(String *addr)
        return dp;
 }
 
-extern void
+void
 d_free(dest *dp)
 {
        if (dp != 0) {
@@ -46,7 +42,7 @@ d_free(dest *dp)
  */
 
 /*  Get first element from a circular list linked via 'next'. */
-extern dest *
+dest*
 d_rm(dest **listp)
 {
        dest *dp;
@@ -63,7 +59,7 @@ d_rm(dest **listp)
 }
 
 /*  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 @@ d_insert(dest **listp, dest *new)
 }
 
 /*  Get first element from a circular list linked via 'same'. */
-extern dest *
+dest*
 d_rm_same(dest **listp)
 {
        dest *dp;
@@ -114,17 +110,20 @@ d_same_dup(dest *dp, dest *new)
        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;
        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 @@ d_same_insert(dest **listp, dest *new)
                                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 @@ d_same_insert(dest **listp, dest *new)
  *  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,33 +199,6 @@ d_to(dest *list)
        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')
 
@@ -257,3 +232,31 @@ s_parseq(String *from, String *to)
 
        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;
+}
index 83afd049ebc496e6a535ad4348a0e71ad8762df2..e5fe926e2599aa014c9c94b47c2aab797a5d3a96 100644 (file)
@@ -1,36 +1,58 @@
 #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");
 }
 
 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 @@ main(int argc, char *argv[])
                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 @@ main(int argc, char *argv[])
                        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("");
 }
index fac9f42ea0ffa23fa257b0302444287b914652b3..ceb0b0b00354f08a13033a54ac340c44fb53237f 100644 (file)
@@ -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;
index 93136b7c67c4383bdc2ac5b034c12ba386503e6a..c1014d3d84f77337c88f84844d0a1cf78cd87efa 100644 (file)
@@ -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;
@@ -15,6 +24,52 @@ mboxfile(dest *dp, String *user, String *path, char *file)
        s_append(path, file);
 }
 
+/*
+ * 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
  */
@@ -43,7 +98,7 @@ expand_local(dest *dp)
        /* 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 @@ expand_local(dest *dp)
         *  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 @@ expand_local(dest *dp)
        /*
         *  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;
index 52df3800105f2c132bc10990c9bfbde935b33ced..9324267e668a8546045f2f5789bf9ecd5206c546 100644 (file)
@@ -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 @@ logdelivery(dest *list, char *rcvr, message *mp)
 }
 
 /* log mail forwarding */
-extern void
+void
 loglist(dest *list, message *mp, char *tag)
 {
        dest *next;
@@ -57,7 +54,7 @@ loglist(dest *list, message *mp, char *tag)
 }
 
 /* log a mail refusal */
-extern void
+void
 logrefusal(dest *dp, message *mp, char *msg)
 {
        char buf[2048];
@@ -67,7 +64,7 @@ logrefusal(dest *dp, message *mp, char *msg)
        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);
index dcf5188250db6a7878f46af8b8e9860a1953f50d..77cebc782ceabd1ef05f80a9a245fc2d8cd06477 100644 (file)
@@ -2,94 +2,82 @@
 #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");
 }
 
 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 @@ main(int argc, char *argv[])
         *  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 @@ main(int argc, char *argv[])
         *  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,21 +129,21 @@ main(int argc, char *argv[])
         *  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
         *  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 @@ send(dest *destp, message *mp, int checkforward)
                        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 @@ lesstedious(void)
 
        if(debug)
                return;
-
+       if(rmail || flagn || forked)
+               return;
        switch(fork()){
        case -1:
                break;
@@ -215,9 +201,10 @@ lesstedious(void)
                for(i=0; i<3; i++)
                        close(i);
                savemail = 0;
+               forked = 1;
                break;
        default:
-               exit(0);
+               exits("");
        }
 }
 
@@ -226,26 +213,23 @@ lesstedious(void)
 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 @@ complain_mail(dest *dp, message *mp)
                msg = "unknown d_";
                break;
        }
-       if (nflg) {
+       if (flagn) {
                print("%s: %s\n", msg, s_to_c(dp->addr));
                return 0;
        }
@@ -314,12 +298,22 @@ complain_mail(dest *dp, message *mp)
 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";
@@ -329,12 +323,12 @@ pipe_mail(dest *dp, message *mp)
         *  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 @@ pipe_mail(dest *dp, message *mp)
                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,127 +369,12 @@ pipe_mail(dest *dp, message *mp)
        /*
         *  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
  */
@@ -542,30 +421,30 @@ replymsg(String *errstring, message *mp, dest *dp)
        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));
@@ -577,3 +456,113 @@ replymsg(String *errstring, message *mp, dest *dp)
        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;
+}
diff --git a/sys/src/cmd/upas/send/makefile b/sys/src/cmd/upas/send/makefile
deleted file mode 100644 (file)
index f0abcf0..0000000
+++ /dev/null
@@ -1,46 +0,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
index 69cddd87c465744905f01452f59efbdc28a422d7..02b7520f2cd4403ba3a54beaf6480c3a28f49dc6 100644 (file)
@@ -1,49 +1,51 @@
 #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;
 
        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 @@ m_new(void)
        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,34 +67,28 @@ m_free(message *mp)
        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 */
 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 @@ getstring(Node *p)
                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 @@ getstring(Node *p)
        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 @@ rfc822cruft(message *mp)
        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 @@ m_read(Biobuf *fp, int rmail, int interactive)
                 */
                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 @@ m_read(Biobuf *fp, int rmail, int interactive)
 }
 
 /* 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 @@ m_get(message *mp, long offset, char **pp)
        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 @@ m_escape(message *mp, Biobuf *fp)
 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 @@ printutf8mime(Biobuf *b)
 }
 
 /* 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 @@ m_print(message *mp, Biobuf *fp, char *remote, int mbox)
                                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);
index a895cd4a49b3984c33aa1b31b56febee19c842da..549d234f0e144a745a49008b7c30b2e5dd9b5c1f 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=send\
        filter
@@ -12,16 +13,19 @@ OFILES=\
        $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 @@ HFILES=send.h\
 
 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
index 8feebc9ad58c3f53b356380963b1767f05635f35..3dde190807b8a3ca7d5ba98287bb6912a73dd876 100644 (file)
@@ -1,5 +1,6 @@
 #include "common.h"
 #include "send.h"
+#include <regexp.h>
 
 extern int debug;
 
@@ -32,7 +33,7 @@ static rule *findrule(String *, int);
  *  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 @@ getrule(String *line, String *type, char *system)
        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 @@ getrule(String *line, String *type, char *system)
  *  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 @@ getrules(void)
                        getrule(s_restart(line), type, altthissys);
        s_free(type);
        s_free(line);
-       s_free(file);
        sysclose(rfp);
        return 0;
 }
@@ -168,8 +165,7 @@ findrule(String *addrp, int authorized)
                        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 @@ findrule(String *addrp, int authorized)
  *              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 @@ substitute(String *source, Resub *subexp, message *mp)
        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);
-}
-
-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:"...");
-       }
+       syslog(0, "mail", "error in rewrite: %s", s);
 }
 
+//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:"...");
+//     }
+//}
index 45fd61c336153b862ac9cd6b4e0bc82a3c861897..1b7d4537d5c85bf7922f7d8eb678e66d0549755f 100644 (file)
@@ -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,43 +41,35 @@ struct message {
        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;
        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 int   default_from(message*);
 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*);
index 43d7b8ac3e775046bf980b325517eeb81a994c6e..c6da2557c9b237af2338d2dca85ae7b9ecf393a8 100644 (file)
@@ -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 @@ skipequiv(char *base)
                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;
@@ -29,64 +72,3 @@ skipequiv(char *base)
        }
        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;
-}
index 0332659c0af00c7444c87c53e6318b6492b0ba6d..e2dd7382f081ef35528b1ef20417fb95c225f8f0 100644 (file)
@@ -11,7 +11,7 @@ translate(dest *dp)
        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;
diff --git a/sys/src/cmd/upas/send/tryit b/sys/src/cmd/upas/send/tryit
deleted file mode 100755 (executable)
index fed3a2a..0000000
+++ /dev/null
@@ -1,29 +0,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
index b5cee5a6ee6a1401e14cebad3d0a44dca1ca9cdb..3bbe9f5869fd3807440a37a37c872d5ff79a0a0b 100644 (file)
@@ -53,7 +53,7 @@ onwhitelist(void)
 
        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 */
index 10b312b90b7b0848e78f753313795ccc62c2f847..9f02f8ae2a4dbeaae8a95271b4db8c3a6ea58cbb 100644 (file)
@@ -1,8 +1,12 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG = smtpd\
        smtp\
 
+TEST=\
+       parsetest
+
 OFILES=
 
 LIB=../common/libcommon.a$O\
@@ -12,13 +16,11 @@ HFILES=../common/common.h\
        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 @@ UPDATE=\
        ${TARG:%=%.c}\
 
 </sys/src/cmd/mkmany
-CFLAGS=$CFLAGS -I../common -D'SPOOL="/mail"'
+CFLAGS=$CFLAGS -I../common
+
+$O.smtpd:\
+       smtpd.tab.$O\
+       spam.$O\
+       rfc822.tab.$O\
+       greylist.$O\
 
-$O.smtpd:      smtpd.tab.$O rmtdns.$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
index c17e280c3faf8bc73364d4abc77e99b193e0cfe2..74516a030eec082ae4f3797a6a48ba498a9bd201 100644 (file)
@@ -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,
-};
-
-typedef struct Mx      Mx;
-struct Mx
-{
-       char host[256];
-       char ip[24];
-       int pref;
-};
 
 char   *bustedmxs[Maxbustedmx];
-Ndb *db;
-
-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);
-
-static Mx mx[Nmx];
 
-int
-mxdial(char *addr, char *ddomain, char *gdomain)
+static void
+expand(DS *ds)
 {
-       int fd;
-       DS ds;
-
-       addr = netmkaddr(addr, 0, "smtp");
-       dial_string_parse(addr, &ds);
+       char *s;
+       Ndbtuple *t;
+
+       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);
+}
 
-       /* try connecting to destination or any of it's mail routers */
-       fd = callmx(&ds, addr, ddomain);
+/* break up an address to its component parts */
+void
+dialstringparse(char *str, DS *ds)
+{
+       char *p, *p2;
 
-       /* try our mail gateway */
-       if(fd < 0 && gdomain)
-               fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0);
+       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);
+}
 
-       return fd;
+void
+mxtabfree(Mxtab *mx)
+{
+       free(mx->mx);
+       memset(mx, 0, sizeof *mx);
 }
 
-static int
-busted(char *mx)
+static void
+mxtabrealloc(Mxtab *mx)
 {
-       char **bmp;
+       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");
+}
 
-       for (bmp = bustedmxs; *bmp != nil; bmp++)
-               if (strcmp(mx, *bmp) == 0)
-                       return 1;
-       return 0;
+static void
+mxtabadd(Mxtab *mx, char *host, char *ip, char *net, int pref)
+{
+       int i;
+       Mx *x;
+
+       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,293 +101,235 @@ timeout(void*, char *msg)
        return 0;
 }
 
-long
+static long
 timedwrite(int fd, void *buf, long len, long ms)
 {
        long n, oalarm;
 
        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];
-
-       /* 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);
-       }
+       int n;
+       char buf[1024], *f[4];
 
-       /* 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]));
+               }
+               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);
                }
-               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;
        }
-       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);
-
-       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);
-               }
-       }
+       for (bmp = bustedmxs; *bmp != nil; bmp++)
+               if (strcmp(mx, *bmp) == 0)
+                       return 1;
+       return 0;
+}
 
-       return n;
+static void
+complain(Mxtab *mx, char *domain)
+{
+       char buf[1024], *e, *p;
+       int i;
+
+       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;
+
+       /* 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;
+               }
+               if(x->valid && busted(x->host)){
+                       dprint("lookup: skipping busted mx %s\n", x->host);
+                       x->valid = 0;
+               }
+       }
+       return 0;
+}
 
-       snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir);
+static int
+lookup(Mxtab *mx, char *net, char *host, char *domain, char *type)
+{
+       char dns[128], buf[1024];
+       int fd, i;
+       Mx *x;
 
-       fd = open(dnsname, ORDWR);
-       if(fd < 0)
-               return 0;
+       snprint(dns, sizeof dns, "%s/dns", net);
+       fd = open(dns, ORDWR);
+       if(fd == -1)
+               return -1;
 
-       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);
-                       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;
+       snprint(buf, sizeof buf, "%s %s", host, type);
+       dprint("sending %s '%s'\n", dns, buf);
+       dnslookup(mx, fd, buf, domain, net, 10000);
 
-                       if(strchr(domain, '.') == 0)
-                               strcpy(domain, fields[0]);
+       for(i = 0; i < mx->nmx; i++){
+               x = mx->mx + i;
+               if(x->ip[0] != 0)
+                       continue;
+               x->valid = 0;
 
-                       strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1);
-                       mx[nmx].pref = atoi(fields[2]);
-                       nmx++;
-               }
-               if(debug)
-                       fprint(2, "dns mx; got %d entries\n", nmx);
+               snprint(buf, sizeof buf, "%s %s", x->host, "ip");
+               dprint("sending %s '%s'\n", dns, buf);
+               dnslookup(mx, fd, buf, domain, net, x->pref);
        }
 
-       /*
-        * 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++;
-       }
+       close(fd);
 
-       /*
-        * 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;
-
-       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;
-}
 
-static int
-compar(void *a, void *b)
-{
-       return ((Mx*)a)->pref - ((Mx*)b)->pref;
+       return 0;
 }
 
-/* break up an address to its component parts */
-void
-dial_string_parse(char *str, DS *ds)
+static int
+lookcall(Mxtab *mx, DS *d, char *domain, char *type)
 {
-       char *p, *p2;
-
-       strncpy(ds->buf, str, sizeof(ds->buf));
-       ds->buf[sizeof(ds->buf)-1] = 0;
+       char buf[1024];
+       int i;
+       Mx *x;
+
+       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;
+       }
 
-       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;
-
-       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;
-       }
+       int nd, i, j;
+       DS *d;
+       static char *tab[] = {"mx", "ip", };
 
-       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;
+       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;
        }
 
-       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;
+       /* 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;
+
+       /* 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;
 }
diff --git a/sys/src/cmd/upas/smtp/parsetest.c b/sys/src/cmd/upas/smtp/parsetest.c
new file mode 100644 (file)
index 0000000..38e77fb
--- /dev/null
@@ -0,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("");
+}
index b50f63eb13710169bba83d87f615e3d113f86ceb..065c153476c17a7080a1ecfc2197f9bf091401a7 100644 (file)
@@ -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,10 +52,11 @@ int messageid;
 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; }
@@ -304,14 +303,12 @@ Keyword key[] = {
  */
 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 @@ yylex(void)
                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 @@ yylex(void)
                                quoting = 0;
                                break;
                        }
-               } else {
+               }else{
                        switch(c){
                        case '\\':
                                escaping = 1;
@@ -363,7 +360,7 @@ yylex(void)
                        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 @@ yylex(void)
                                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 @@ yylex(void)
        }
 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 @@ String *
 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 @@ yywhite(void)
                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 @@ yywhite(void)
                                /*
                                 *  look for multiline fields
                                 */
-                               if(*(yylp+1)==' ' || *(yylp+1)=='\t')
+                               if(yylp[1] == ' ' || yylp[1] == '\t')
                                        break;
                                else
                                        goto out;
@@ -533,7 +526,7 @@ colon(Node *p1, Node *p2)
        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 @@ concat(Node *p1, Node *p2)
 
        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);
@@ -609,23 +602,6 @@ address(Node *p)
        return p;
 }
 
-/*
- *  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
  */
@@ -673,10 +649,10 @@ missing(Node *p)
        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 @@ missing(Node *p)
        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));
diff --git a/sys/src/cmd/upas/smtp/rmtdns.c b/sys/src/cmd/upas/smtp/rmtdns.c
deleted file mode 100644 (file)
index b74a1c9..0000000
+++ /dev/null
@@ -1,59 +0,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);
-}
-*/
index 96f2d65ab096b7c400dc37583383b3c2cd67f75f..99e5e631dce3bf73e0c94de97d07fb84ada116d2 100644 (file)
@@ -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*       bangtoat(char*);
 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 @@ int   printheader(void);
 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,13 +53,33 @@ char        *uneaten;       /* first character after rfc822 headers */
 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;
 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 @@ usage(void)
 }
 
 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 @@ timeout(void *x, char *msg)
                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 @@ timeout(void *x, char *msg)
 void
 removenewline(char *p)
 {
-       int n = strlen(p)-1;
+       int n = strlen(p) - 1;
 
        if(n < 0)
                return;
@@ -106,18 +125,21 @@ removenewline(char *p)
 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 @@ main(int argc, char **argv)
                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 @@ main(int argc, char **argv)
        /*
         *  get domain and add to host name
         */
-       if(*argv && **argv=='.') {
+       if(*argv && **argv=='.'){
                domain = *argv;
                argv++; argc--;
        } else
@@ -189,6 +211,11 @@ main(int argc, char **argv)
                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 @@ main(int argc, char **argv)
        /*
         *  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 @@ main(int argc, char **argv)
        }
 
        /* 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 @@ main(int argc, char **argv)
                exits(0);
        }
 
-       rv = data(from, &bfile);
+       rv = data(from, &bfile, &mx);
        if(rv != 0)
                goto error;
        quit(0);
@@ -266,10 +305,11 @@ main(int argc, char **argv)
        /*
         *  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 @@ main(int argc, char **argv)
         *  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 @@ error:
  *  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 @@ connect(char* net)
 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 @@ wraptls(void)
        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 @@ dotls(char *me)
        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 @@ doauth(char *methods)
        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 @@ out:
        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 @@ hello(char *me, int encrypted)
                 * 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 @@ hello(char *me, int encrypted)
                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 @@ hello(char *me, int encrypted)
        /* 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 @@ mailfrom(char *from)
 {
        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 @@ static char hex[] = "0123456789abcdef";
  *  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 @@ data(String *from, Biobuf *b)
                        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 @@ data(String *from, Biobuf *b)
 
        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 @@ data(String *from, Biobuf *b)
                        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,20 +785,20 @@ data(String *from, Biobuf *b)
                                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);
 
        /*
         *  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 @@ data(String *from, Biobuf *b)
                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 @@ addhostdom(String *buf, char *host)
 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 @@ bangtoat(char *addr)
                if(addr)
                        *addr++ = 0;
        }
-       if (i==1) {
+       if(i == 1){
                s_append(buf, field[0]);
                return buf;
        }
@@ -827,8 +913,8 @@ bangtoat(char *addr)
        /*
         *  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 @@ bangtoat(char *addr)
         */
        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 @@ bangtoat(char *addr)
         *  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 @@ bangtoat(char *addr)
 String*
 convertheader(String *from)
 {
+       char *s, buf[64];
        Field *f;
        Node *p, *lastp;
        String *a;
@@ -875,8 +962,10 @@ convertheader(String *from)
                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 @@ fixrouteaddr(String *raddr, Node *next, Node *last)
 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 @@ printheader(void)
 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 @@ putcrnl(char *cp, int n)
 char *
 getcrnl(String *s)
 {
-       int c;
-       int count;
+       int c, count;
 
        count = 0;
        for(;;){
@@ -1052,16 +1139,17 @@ getcrnl(String *s)
 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 @@ printdate(Node *p)
 char *
 rewritezone(char *z)
 {
-       int mindiff;
        char s;
+       int mindiff;
        Tm *tm;
        static char x[7];
 
@@ -1104,22 +1192,22 @@ rewritezone(char *z)
 /*
  *  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;
 }
index bfac33ed9fd3ad52de2312bbf4b4655bbd8a3e21..d420d1dc3e2d5a0b9c6b162ac23a0db04dcc9ecf 100644 (file)
@@ -24,8 +24,11 @@ struct Field {
 };
 
 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 @@ struct DS {
        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 @@ Node* address(Node*);
 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 @@ int   yylex(void);
 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)
index 85fbb098ab208293f09cd58fb2cc548f15165103..6c75e93998e296533ac513e51c9dc3c168afe3bf 100644 (file)
@@ -22,14 +22,15 @@ int logged;
 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,48 +51,35 @@ int pipemsg(int*);
 int    rejectcheck(void);
 String*        startcmd(void);
 
-static void    logmsg(char *action);
-
 static int
-catchalarm(void *a, char *msg)
+catchalarm(void*, char *msg)
 {
-       int rv;
-
-       USED(a);
+       int ign;
+       static int chattycathy;
 
-       /* 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 */
 void
 s_error(char *f, char *status)
 {
-       char errbuf[Errlen];
+       char errbuf[ERRMAX];
 
        errbuf[0] = 0;
        rerrstr(errbuf, sizeof(errbuf));
@@ -106,8 +94,7 @@ s_error(char *f, char *status)
 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 @@ main(int argc, char **argv)
        quotefmtinstall();
        fmtinstall('I', eipfmt);
        fmtinstall('[', encodefmt);
-       starttime = time(0);
        ARGBEGIN{
        case 'a':
                authenticate = 1;
@@ -135,6 +121,12 @@ main(int argc, char **argv)
        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,41 +148,39 @@ main(int argc, char **argv)
        case 'p':
                passwordinclear = 1;
                break;
+       case 'q':
+               qflag = 1;              /* don't log invalid hello */
+               break;
        case 'r':
                rflag = 1;                      /* verify sender's domain */
                break;
        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;
 
        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)
                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
                        debug = 0;
        }
        getconf();
-       if (isbadguy())
-               exits("banned");
+       if(isbadguy())
+               exits("");
        Binit(&bin, 0, OREAD);
 
        if (chdir(UPASLOG) < 0)
@@ -239,32 +229,20 @@ listadd(List *l, String *path)
        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 @@ sayhi(void)
        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 char netaspam[256] = {
 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 @@ hello(String *himp, int extended)
                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 @@ sender(String *path)
        /*
         * 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 @@ receiver(String *path)
        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 @@ receiver(String *path)
 
        /* 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 @@ void
 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 @@ verify(String *path)
 {
        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 @@ verify(String *path)
        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 @@ verify(String *path)
        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 @@ getcrnl(String *s, Biobuf *fp)
                }
                switch(c){
                case 0:
+                       /* idiot html email! */
                        break;
                case -1:
                        goto out;
@@ -774,7 +797,6 @@ getcrnl(String *s, Biobuf *fp)
                                if(debug) {
                                        seek(2, 0, 2);
                                        fprint(2, "%c", c);
-                                       stamp();
                                }
                                s_putc(s, '\n');
                                goto out;
@@ -912,15 +934,14 @@ startcmd(void)
                        dom);
                return 0;
        case ACCEPT:
+       case TRUSTED:
                /*
                 * now that all other filters have been passed,
                 * do grey-list processing.
                 */
                if(gflag)
                        vfysenderhostok();
-               /* fall through */
 
-       case TRUSTED:
                /*
                 *  set up mail command
                 */
@@ -962,34 +983,24 @@ startcmd(void)
  *  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 @@ forgedheaderwarnings(void)
        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 @@ forgedheaderwarnings(void)
        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 @@ pipemsg(int *byteswritten)
         *  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,45 +1100,152 @@ pipemsg(int *byteswritten)
         */
        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;
+       }
+
+       *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;
        }
-       nbytes += nonhdr;
+       if(status == 0)
+               parseheader(hdr, &nbytes, &status);
        s_free(hdr);
 
        /*
         *  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 @@ pipemsg(int *byteswritten)
                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 @@ firstline(char *x)
        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 @@ sendermxcheck(void)
        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 @@ sendermxcheck(void)
                                "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 @@ sendermxcheck(void)
        }
        *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 @@ sendermxcheck(void)
                 * 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,20 +1371,42 @@ sendermxcheck(void)
         * 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);
        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 @@ data(void)
                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 @@ data(void)
 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 @@ s_dec64(String *sin)
         * 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 @@ starttls(void)
        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 @@ auth(String *mech, String *resp)
        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 @@ auth(String *mech, String *resp)
                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 @@ auth(String *mech, String *resp)
                        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 @@ windup:
                }
                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");
index 4b7fe4134b0f2757f382cad47234672a6affc6d2..20ef9f944f1e8534cc5d2e2c934b29645831f70d 100644 (file)
@@ -65,10 +65,10 @@ actstr(int a)
        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 @@ getaction(char *s, char *type)
 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 @@ getconf(void)
                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 @@ getconf(void)
                                s = s_new();
                                s_append(s, p);
                                listadd(&ourdoms, s);
-                               p += strlen(p)+1;
+                               p += strlen(p) + 1;
                        }
                        break;
                default:
@@ -178,7 +178,7 @@ usermatch(char *pathuser, char *specuser)
 {
        int n;
 
-       n = strlen(specuser)-1;
+       n = strlen(specuser) - 1;
        if(specuser[n] == '*'){
                if(n == 0)              /* match everything */
                        return 0;
@@ -195,9 +195,9 @@ dommatch(char *pathdom, char *specdom)
        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 @@ getline(Biobuf *bp)
                        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 @@ found:
                        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 @@ found:
        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 @@ masquerade(String *path, char *him)
        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 @@ masquerade(String *path, char *him)
                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 @@ masquerade(String *path, char *him)
        }
 
        s_free(lpath);
-       if (debug)
-               fprint(2, "%d\n", rv);
        return rv;
 }
 
@@ -426,15 +423,15 @@ cidrcheck(char *cp)
                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 @@ dumpfile(char *sender)
                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 @@ dumpfile(char *sender)
                        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 @@ optoutofspamfilter(char *addr)
        rv = 0;
        f = smprint("/mail/box/%s/nospamfiltering", p);
        if(f != nil){
-               rv = access(f, 0)==0;
+               rv = access(f, 0) == 0;
                free(f);
        }
 
diff --git a/sys/src/cmd/upas/spf/dns.c b/sys/src/cmd/upas/spf/dns.c
new file mode 100644 (file)
index 0000000..01b2b2c
--- /dev/null
@@ -0,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;      
+}
+
diff --git a/sys/src/cmd/upas/spf/macro.c b/sys/src/cmd/upas/spf/macro.c
new file mode 100644 (file)
index 0000000..2ccc8e3
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/spf/mkfile b/sys/src/cmd/upas/spf/mkfile
new file mode 100644 (file)
index 0000000..f612714
--- /dev/null
@@ -0,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
diff --git a/sys/src/cmd/upas/spf/mtest.c b/sys/src/cmd/upas/spf/mtest.c
new file mode 100644 (file)
index 0000000..3bd0e14
--- /dev/null
@@ -0,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("");
+}
diff --git a/sys/src/cmd/upas/spf/spf.c b/sys/src/cmd/upas/spf/spf.c
new file mode 100644 (file)
index 0000000..b76db76
--- /dev/null
@@ -0,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);
+}
diff --git a/sys/src/cmd/upas/spf/spf.h b/sys/src/cmd/upas/spf/spf.h
new file mode 100644 (file)
index 0000000..b37bad8
--- /dev/null
@@ -0,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*);
diff --git a/sys/src/cmd/upas/spf/testsuite b/sys/src/cmd/upas/spf/testsuite
new file mode 100644 (file)
index 0000000..1b55f6c
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/rc
+for(i in '%{s}' '%{o}' '%{d}' '%{d4}' '%{d3}' '%{d2}' '%{d1}' '%{dr}' '%{d2r}' '%{l}' '%{l-}' '%{lr}' '%{lr-}' '%{l1r-}')
+       mtest $i 'strong-bad@email.example.com' email.example.com helounknown 192.0.2.3 
+for(i in '%{i}')
+       mtest $i 'strong-bad@email.example.com' 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 'strong-bad@email.example.com' 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 'strong-bad@email.example.com' email.example.com helounknown 192.0.2.3 
index 904d8bba94a258b4e04b7b524b740795f8956e0f..e02edc4842d50fbdefd49e1e9e7bf81c15cbc6c1 100644 (file)
@@ -1,10 +1,9 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=unesc
 OFILES=unesc.$O\
 
-BIN=/$objtype/bin/upas
-
 UPDATE=\
        mkfile\
        $HFILES\
index 5f0981a39a28dcf0a18ee2a170ab9799b5cdb8fd..3a7c1c1debf124fa615531c99e81d34267b69547 100644 (file)
@@ -1,4 +1,5 @@
 </$objtype/mkfile
+<../mkupas
 
 TARG=vf
 
@@ -9,8 +10,6 @@ LIB=../common/libcommon.a$O\
 HFILES=../common/common.h\
         ../common/sys.h\
 
-
-BIN=/$objtype/bin/upas
 UPDATE=\
        mkfile\
        $HFILES\
index d73fbe47f90396e294a5b10584463ea9b428fe77..92ec86515225c2b263baf966f86e81c70ce895e6 100644 (file)
@@ -954,7 +954,7 @@ tokenconvert(String *t)
 {
        String *s;
        char decoded[1024];
-       char utfbuf[UTFmax*1024];
+       char utfbuf[2*1024];
        int i, len;
        char *e;
        char *token;
@@ -986,6 +986,8 @@ tokenconvert(String *t)
        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;