From 963cfc9a6f6e721f52aa949e6d1af0c3e8dc2ecc Mon Sep 17 00:00:00 2001 From: cinap_lenrek Date: Sun, 12 Mar 2017 17:15:03 +0100 Subject: [PATCH] merging erik quanstros nupas --- acme/mail/guide | 4 - acme/mail/mkbox | 11 - acme/mail/readme | 57 - acme/mkfile | 1 - sys/doc/mkfile | 1 + sys/doc/nupas/macros.ms | 92 + sys/doc/nupas/mkfile | 20 + sys/doc/nupas/nupas.ms | 354 +++ sys/man/8/pop3 | 4 +- sys/src/cmd/ip/imap4d/auth.c | 184 -- sys/src/cmd/ip/imap4d/copy.c | 259 -- sys/src/cmd/ip/imap4d/debug.c | 108 - sys/src/cmd/ip/imap4d/fns.h | 125 - sys/src/cmd/ip/imap4d/folder.c | 398 --- sys/src/cmd/ip/imap4d/imap4d.c | 2102 ------------- sys/src/cmd/ip/imap4d/imap4d.h | 378 --- sys/src/cmd/ip/imap4d/list.c | 412 --- sys/src/cmd/ip/imap4d/mbox.c | 863 ----- sys/src/cmd/ip/imap4d/msg.c | 1782 ----------- sys/src/cmd/ip/imap4d/nodes.c | 213 -- sys/src/cmd/ip/imap4d/search.c | 244 -- sys/src/cmd/ip/imap4d/store.c | 127 - sys/src/cmd/ip/mkfile | 2 +- .../mail/src => sys/src/cmd/upas/Mail}/dat.h | 2 + .../mail/src => sys/src/cmd/upas/Mail}/html.c | 0 .../mail/src => sys/src/cmd/upas/Mail}/mail.c | 82 +- .../mail/src => sys/src/cmd/upas/Mail}/mesg.c | 192 +- .../mail/src => sys/src/cmd/upas/Mail}/mkfile | 8 +- .../src => sys/src/cmd/upas/Mail}/reply.c | 19 +- .../mail/src => sys/src/cmd/upas/Mail}/util.c | 2 +- .../mail/src => sys/src/cmd/upas/Mail}/win.c | 0 sys/src/cmd/upas/alias/aliasmail.c | 149 +- sys/src/cmd/upas/alias/mkfile | 3 +- sys/src/cmd/upas/binscripts/isspam.rc | 2 + sys/src/cmd/upas/binscripts/mkfile | 34 + sys/src/cmd/upas/binscripts/mkfile.rc | 38 + sys/src/cmd/upas/binscripts/msgcat.rc | 11 + sys/src/cmd/upas/binscripts/spam.rc | 2 + sys/src/cmd/upas/binscripts/tfmt.rc | 25 + sys/src/cmd/upas/binscripts/unspam.rc | 2 + sys/src/cmd/upas/common/appendfiletombox.c | 164 - sys/src/cmd/upas/common/aux.c | 47 +- sys/src/cmd/upas/common/become.c | 3 +- sys/src/cmd/upas/common/common.h | 107 +- sys/src/cmd/upas/common/config.c | 16 +- sys/src/cmd/upas/common/flags.c | 73 + sys/src/cmd/upas/common/fmt.c | 35 + sys/src/cmd/upas/common/folder.c | 361 +++ sys/src/cmd/upas/common/libsys.c | 676 ++-- sys/src/cmd/upas/common/mail.c | 57 - sys/src/cmd/upas/common/makefile | 18 - sys/src/cmd/upas/common/mkfile | 18 +- sys/src/cmd/upas/common/process.c | 19 +- sys/src/cmd/upas/common/sys.h | 100 +- sys/src/cmd/upas/common/totm.c | 36 + sys/src/cmd/upas/filterkit/deliver.c | 56 +- sys/src/cmd/upas/filterkit/list.c | 2 +- sys/src/cmd/upas/filterkit/mbappend.c | 63 + sys/src/cmd/upas/filterkit/mbcreate.c | 32 + sys/src/cmd/upas/filterkit/mbremove.c | 243 ++ sys/src/cmd/upas/filterkit/mkfile | 7 +- sys/src/cmd/upas/filterkit/pipefrom.sample | 0 sys/src/cmd/upas/filterkit/testemail | 4 + sys/src/cmd/upas/filterkit/token.c | 43 +- sys/src/cmd/upas/fs/bos.c | 40 + sys/src/cmd/upas/fs/cache.c | 552 ++++ sys/src/cmd/upas/fs/chkidx | Bin 0 -> 75510 bytes sys/src/cmd/upas/fs/chkidx.c | 416 +++ sys/src/cmd/upas/fs/dat.h | 407 ++- sys/src/cmd/upas/fs/extra/fd2path | Bin 0 -> 36947 bytes sys/src/cmd/upas/fs/extra/fd2path.c | 32 + sys/src/cmd/upas/fs/extra/idxtst.c | 58 + sys/src/cmd/upas/fs/extra/infotst.c | 89 + sys/src/cmd/upas/fs/extra/paw | Bin 0 -> 62365 bytes sys/src/cmd/upas/fs/extra/paw.c | 23 + sys/src/cmd/upas/fs/extra/prflags.c | 37 + sys/src/cmd/upas/fs/extra/strtotmtst.c | 17 + sys/src/cmd/upas/fs/extra/tokens.c | 62 + sys/src/cmd/upas/fs/fs.c | 1291 +++++--- sys/src/cmd/upas/fs/header.c | 176 ++ sys/src/cmd/upas/fs/idx.c | 535 ++++ sys/src/cmd/upas/fs/imap.c | 1271 ++++++++ sys/src/cmd/upas/fs/imap4.c | 878 ------ sys/src/cmd/upas/fs/mbox.c | 1796 ++++++----- sys/src/cmd/upas/fs/mdir.c | 354 +++ sys/src/cmd/upas/fs/mkfile | 22 +- sys/src/cmd/upas/fs/mtree.c | 80 + sys/src/cmd/upas/fs/plan9.c | 481 +-- sys/src/cmd/upas/fs/planb.c | 144 +- sys/src/cmd/upas/fs/pop3.c | 370 +-- sys/src/cmd/upas/fs/readdir.c | 15 - sys/src/cmd/upas/fs/ref.c | 100 + sys/src/cmd/upas/fs/remove.c | 141 + sys/src/cmd/upas/fs/rename.c | 234 ++ sys/src/cmd/upas/fs/rfc2047-test | 28 - sys/src/cmd/upas/fs/seg.c | 164 + sys/src/cmd/upas/fs/strtotm.c | 74 +- sys/src/cmd/upas/fs/tester.c | 81 - sys/src/cmd/upas/imap4d/auth.c | 280 ++ sys/src/cmd/upas/imap4d/capability | 37 + sys/src/cmd/upas/imap4d/copy.c | 248 ++ sys/src/cmd/{ip => upas}/imap4d/csquery.c | 14 +- sys/src/cmd/{ip => upas}/imap4d/date.c | 331 +- sys/src/cmd/upas/imap4d/debug.c | 30 + sys/src/cmd/{ip => upas}/imap4d/fetch.c | 432 ++- sys/src/cmd/upas/imap4d/fns.h | 138 + sys/src/cmd/upas/imap4d/folder.c | 186 ++ sys/src/cmd/upas/imap4d/fsenc.c | 98 + sys/src/cmd/upas/imap4d/fstree.c | 58 + sys/src/cmd/upas/imap4d/imap4d.c | 2292 ++++++++++++++ sys/src/cmd/upas/imap4d/imap4d.h | 395 +++ sys/src/cmd/upas/imap4d/imp.c | 315 ++ sys/src/cmd/upas/imap4d/list.c | 425 +++ sys/src/cmd/upas/imap4d/mbox.c | 630 ++++ sys/src/cmd/{ip => upas}/imap4d/mkfile | 11 +- sys/src/cmd/upas/imap4d/msg.c | 1507 +++++++++ sys/src/cmd/{ip => upas}/imap4d/mutf7.c | 84 +- sys/src/cmd/upas/imap4d/nlisttst.c | 94 + sys/src/cmd/upas/imap4d/nodes.c | 184 ++ sys/src/cmd/upas/imap4d/print.c | 129 + sys/src/cmd/upas/imap4d/quota.c | 73 + sys/src/cmd/upas/imap4d/search.c | 329 ++ sys/src/cmd/upas/imap4d/store.c | 119 + sys/src/cmd/{ip => upas}/imap4d/utils.c | 93 +- sys/src/cmd/upas/marshal/marshal.c | 100 +- sys/src/cmd/upas/marshal/mkfile | 3 +- sys/src/cmd/upas/misc/empty.c | 0 sys/src/cmd/upas/misc/go.fishing | 15 - sys/src/cmd/upas/misc/gone.fishing | 19 - sys/src/cmd/upas/misc/gone.msg | 2 - sys/src/cmd/upas/misc/{mail => mail.rc} | 0 sys/src/cmd/upas/misc/mkfile | 29 +- sys/src/cmd/upas/misc/omail.rc | 14 + sys/src/cmd/upas/misc/unix/gone.fishing.sh | 9 - sys/src/cmd/upas/misc/unix/mail.c | 51 - sys/src/cmd/upas/misc/unix/mail.sh | 12 - sys/src/cmd/upas/misc/unix/makefile | 44 - sys/src/cmd/upas/mkfile | 22 +- sys/src/cmd/upas/mkupas | 5 + sys/src/cmd/upas/ml/common.c | 51 +- sys/src/cmd/upas/ml/mkfile | 10 +- sys/src/cmd/upas/ml/ml.c | 187 +- sys/src/cmd/upas/ml/mlmgr.c | 150 +- sys/src/cmd/upas/ml/mlowner.c | 2 + sys/src/cmd/upas/ned/mkfile | 3 +- sys/src/cmd/upas/ned/nedmail.c | 2775 +++++++++-------- sys/src/cmd/upas/pop3/mkfile | 2 +- sys/src/cmd/upas/pop3/pop3.c | 69 +- sys/src/cmd/upas/q/mkfile | 3 +- sys/src/cmd/upas/q/qer.c | 2 +- sys/src/cmd/upas/q/runq.c | 41 +- sys/src/cmd/upas/qfrom/mkfile | 13 + sys/src/cmd/upas/qfrom/qfrom.c | 63 + sys/src/cmd/upas/scanmail/mkfile | 4 +- sys/src/cmd/upas/scanmail/scanmail.c | 52 +- sys/src/cmd/upas/scanmail/spam.h | 1 - sys/src/cmd/upas/scanmail/testscan.c | 23 +- sys/src/cmd/upas/send/authorize.c | 2 +- sys/src/cmd/upas/send/bind.c | 48 +- sys/src/cmd/upas/send/cat_mail.c | 142 +- sys/src/cmd/upas/send/dest.c | 89 +- sys/src/cmd/upas/send/filter.c | 108 +- sys/src/cmd/upas/send/gateway.c | 4 +- sys/src/cmd/upas/send/local.c | 65 +- sys/src/cmd/upas/send/log.c | 11 +- sys/src/cmd/upas/send/main.c | 425 ++- sys/src/cmd/upas/send/makefile | 46 - sys/src/cmd/upas/send/message.c | 195 +- sys/src/cmd/upas/send/mkfile | 28 +- sys/src/cmd/upas/send/rewrite.c | 89 +- sys/src/cmd/upas/send/send.h | 42 +- sys/src/cmd/upas/send/skipequiv.c | 108 +- sys/src/cmd/upas/send/translate.c | 2 +- sys/src/cmd/upas/send/tryit | 29 - sys/src/cmd/upas/smtp/greylist.c | 2 +- sys/src/cmd/upas/smtp/mkfile | 30 +- sys/src/cmd/upas/smtp/mxdial.c | 586 ++-- sys/src/cmd/upas/smtp/parsetest.c | 86 + sys/src/cmd/upas/smtp/rfc822.y | 82 +- sys/src/cmd/upas/smtp/rmtdns.c | 59 - sys/src/cmd/upas/smtp/smtp.c | 292 +- sys/src/cmd/upas/smtp/smtp.h | 33 +- sys/src/cmd/upas/smtp/smtpd.c | 888 +++--- sys/src/cmd/upas/smtp/spam.c | 59 +- sys/src/cmd/upas/spf/dns.c | 81 + sys/src/cmd/upas/spf/macro.c | 304 ++ sys/src/cmd/upas/spf/mkfile | 14 + sys/src/cmd/upas/spf/mtest.c | 39 + sys/src/cmd/upas/spf/spf.c | 800 +++++ sys/src/cmd/upas/spf/spf.h | 12 + sys/src/cmd/upas/spf/testsuite | 9 + sys/src/cmd/upas/unesc/mkfile | 3 +- sys/src/cmd/upas/vf/mkfile | 3 +- sys/src/cmd/upas/vf/vf.c | 4 +- 194 files changed, 22221 insertions(+), 15366 deletions(-) delete mode 100644 acme/mail/guide delete mode 100755 acme/mail/mkbox delete mode 100644 acme/mail/readme create mode 100644 sys/doc/nupas/macros.ms create mode 100644 sys/doc/nupas/mkfile create mode 100644 sys/doc/nupas/nupas.ms delete mode 100644 sys/src/cmd/ip/imap4d/auth.c delete mode 100644 sys/src/cmd/ip/imap4d/copy.c delete mode 100644 sys/src/cmd/ip/imap4d/debug.c delete mode 100644 sys/src/cmd/ip/imap4d/fns.h delete mode 100644 sys/src/cmd/ip/imap4d/folder.c delete mode 100644 sys/src/cmd/ip/imap4d/imap4d.c delete mode 100644 sys/src/cmd/ip/imap4d/imap4d.h delete mode 100644 sys/src/cmd/ip/imap4d/list.c delete mode 100644 sys/src/cmd/ip/imap4d/mbox.c delete mode 100644 sys/src/cmd/ip/imap4d/msg.c delete mode 100644 sys/src/cmd/ip/imap4d/nodes.c delete mode 100644 sys/src/cmd/ip/imap4d/search.c delete mode 100644 sys/src/cmd/ip/imap4d/store.c rename {acme/mail/src => sys/src/cmd/upas/Mail}/dat.h (98%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/html.c (100%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/mail.c (87%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/mesg.c (90%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/mkfile (69%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/reply.c (98%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/util.c (98%) rename {acme/mail/src => sys/src/cmd/upas/Mail}/win.c (100%) create mode 100755 sys/src/cmd/upas/binscripts/isspam.rc create mode 100644 sys/src/cmd/upas/binscripts/mkfile create mode 100644 sys/src/cmd/upas/binscripts/mkfile.rc create mode 100755 sys/src/cmd/upas/binscripts/msgcat.rc create mode 100755 sys/src/cmd/upas/binscripts/spam.rc create mode 100755 sys/src/cmd/upas/binscripts/tfmt.rc create mode 100755 sys/src/cmd/upas/binscripts/unspam.rc delete mode 100644 sys/src/cmd/upas/common/appendfiletombox.c create mode 100644 sys/src/cmd/upas/common/flags.c create mode 100644 sys/src/cmd/upas/common/fmt.c create mode 100644 sys/src/cmd/upas/common/folder.c delete mode 100644 sys/src/cmd/upas/common/mail.c delete mode 100644 sys/src/cmd/upas/common/makefile create mode 100644 sys/src/cmd/upas/common/totm.c create mode 100644 sys/src/cmd/upas/filterkit/mbappend.c create mode 100644 sys/src/cmd/upas/filterkit/mbcreate.c create mode 100644 sys/src/cmd/upas/filterkit/mbremove.c mode change 100644 => 100755 sys/src/cmd/upas/filterkit/pipefrom.sample create mode 100644 sys/src/cmd/upas/filterkit/testemail create mode 100644 sys/src/cmd/upas/fs/bos.c create mode 100644 sys/src/cmd/upas/fs/cache.c create mode 100755 sys/src/cmd/upas/fs/chkidx create mode 100644 sys/src/cmd/upas/fs/chkidx.c create mode 100755 sys/src/cmd/upas/fs/extra/fd2path create mode 100644 sys/src/cmd/upas/fs/extra/fd2path.c create mode 100644 sys/src/cmd/upas/fs/extra/idxtst.c create mode 100644 sys/src/cmd/upas/fs/extra/infotst.c create mode 100755 sys/src/cmd/upas/fs/extra/paw create mode 100644 sys/src/cmd/upas/fs/extra/paw.c create mode 100644 sys/src/cmd/upas/fs/extra/prflags.c create mode 100644 sys/src/cmd/upas/fs/extra/strtotmtst.c create mode 100644 sys/src/cmd/upas/fs/extra/tokens.c create mode 100644 sys/src/cmd/upas/fs/header.c create mode 100644 sys/src/cmd/upas/fs/idx.c create mode 100644 sys/src/cmd/upas/fs/imap.c delete mode 100644 sys/src/cmd/upas/fs/imap4.c create mode 100644 sys/src/cmd/upas/fs/mdir.c create mode 100644 sys/src/cmd/upas/fs/mtree.c delete mode 100644 sys/src/cmd/upas/fs/readdir.c create mode 100644 sys/src/cmd/upas/fs/ref.c create mode 100644 sys/src/cmd/upas/fs/remove.c create mode 100644 sys/src/cmd/upas/fs/rename.c delete mode 100644 sys/src/cmd/upas/fs/rfc2047-test create mode 100644 sys/src/cmd/upas/fs/seg.c delete mode 100644 sys/src/cmd/upas/fs/tester.c create mode 100644 sys/src/cmd/upas/imap4d/auth.c create mode 100644 sys/src/cmd/upas/imap4d/capability create mode 100644 sys/src/cmd/upas/imap4d/copy.c rename sys/src/cmd/{ip => upas}/imap4d/csquery.c (67%) rename sys/src/cmd/{ip => upas}/imap4d/date.c (77%) create mode 100644 sys/src/cmd/upas/imap4d/debug.c rename sys/src/cmd/{ip => upas}/imap4d/fetch.c (51%) create mode 100644 sys/src/cmd/upas/imap4d/fns.h create mode 100644 sys/src/cmd/upas/imap4d/folder.c create mode 100644 sys/src/cmd/upas/imap4d/fsenc.c create mode 100644 sys/src/cmd/upas/imap4d/fstree.c create mode 100644 sys/src/cmd/upas/imap4d/imap4d.c create mode 100644 sys/src/cmd/upas/imap4d/imap4d.h create mode 100644 sys/src/cmd/upas/imap4d/imp.c create mode 100644 sys/src/cmd/upas/imap4d/list.c create mode 100644 sys/src/cmd/upas/imap4d/mbox.c rename sys/src/cmd/{ip => upas}/imap4d/mkfile (82%) create mode 100644 sys/src/cmd/upas/imap4d/msg.c rename sys/src/cmd/{ip => upas}/imap4d/mutf7.c (74%) create mode 100644 sys/src/cmd/upas/imap4d/nlisttst.c create mode 100644 sys/src/cmd/upas/imap4d/nodes.c create mode 100644 sys/src/cmd/upas/imap4d/print.c create mode 100644 sys/src/cmd/upas/imap4d/quota.c create mode 100644 sys/src/cmd/upas/imap4d/search.c create mode 100644 sys/src/cmd/upas/imap4d/store.c rename sys/src/cmd/{ip => upas}/imap4d/utils.c (59%) create mode 100644 sys/src/cmd/upas/misc/empty.c delete mode 100755 sys/src/cmd/upas/misc/go.fishing delete mode 100755 sys/src/cmd/upas/misc/gone.fishing rename sys/src/cmd/upas/misc/{mail => mail.rc} (100%) create mode 100755 sys/src/cmd/upas/misc/omail.rc delete mode 100755 sys/src/cmd/upas/misc/unix/gone.fishing.sh delete mode 100644 sys/src/cmd/upas/misc/unix/mail.c delete mode 100755 sys/src/cmd/upas/misc/unix/mail.sh delete mode 100644 sys/src/cmd/upas/misc/unix/makefile create mode 100644 sys/src/cmd/upas/mkupas create mode 100644 sys/src/cmd/upas/qfrom/mkfile create mode 100644 sys/src/cmd/upas/qfrom/qfrom.c delete mode 100644 sys/src/cmd/upas/send/makefile delete mode 100755 sys/src/cmd/upas/send/tryit create mode 100644 sys/src/cmd/upas/smtp/parsetest.c delete mode 100644 sys/src/cmd/upas/smtp/rmtdns.c create mode 100644 sys/src/cmd/upas/spf/dns.c create mode 100644 sys/src/cmd/upas/spf/macro.c create mode 100644 sys/src/cmd/upas/spf/mkfile create mode 100644 sys/src/cmd/upas/spf/mtest.c create mode 100644 sys/src/cmd/upas/spf/spf.c create mode 100644 sys/src/cmd/upas/spf/spf.h create mode 100644 sys/src/cmd/upas/spf/testsuite diff --git a/acme/mail/guide b/acme/mail/guide deleted file mode 100644 index 8977ac7c8..000000000 --- a/acme/mail/guide +++ /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 index 3e98afc7b..000000000 --- a/acme/mail/mkbox +++ /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 index b795a04e7..000000000 --- a/acme/mail/readme +++ /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/mkfile b/acme/mkfile index fc6fc9abe..002e4de25 100644 --- a/acme/mkfile +++ b/acme/mkfile @@ -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} diff --git a/sys/doc/mkfile b/sys/doc/mkfile index 0226f35c0..0775878bc 100644 --- a/sys/doc/mkfile +++ b/sys/doc/mkfile @@ -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 index 000000000..330626da3 --- /dev/null +++ b/sys/doc/nupas/macros.ms @@ -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 index 000000000..85f629785 --- /dev/null +++ b/sys/doc/nupas/mkfile @@ -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 index 000000000..a00b60eca --- /dev/null +++ b/sys/doc/nupas/nupas.ms @@ -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. diff --git a/sys/man/8/pop3 b/sys/man/8/pop3 index 1541ebc29..d9db7d77a 100644 --- a/sys/man/8/pop3 +++ b/sys/man/8/pop3 @@ -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 index b8dcd7581..000000000 --- a/sys/src/cmd/ip/imap4d/auth.c +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include -#include -#include -#include -#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 index a1b3f6a5d..000000000 --- a/sys/src/cmd/ip/imap4d/copy.c +++ /dev/null @@ -1,259 +0,0 @@ -#include -#include -#include -#include -#include -#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/debug.c b/sys/src/cmd/ip/imap4d/debug.c deleted file mode 100644 index 9dbfcb204..000000000 --- a/sys/src/cmd/ip/imap4d/debug.c +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include -#include -#include -#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/fns.h b/sys/src/cmd/ip/imap4d/fns.h deleted file mode 100644 index 4f8694689..000000000 --- a/sys/src/cmd/ip/imap4d/fns.h +++ /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 index 8a74889e6..000000000 --- a/sys/src/cmd/ip/imap4d/folder.c +++ /dev/null @@ -1,398 +0,0 @@ -#include -#include -#include -#include -#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 index 6eafe481e..000000000 --- a/sys/src/cmd/ip/imap4d/imap4d.c +++ /dev/null @@ -1,2102 +0,0 @@ -#include -#include -#include -#include -#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 index bc2723fea..000000000 --- a/sys/src/cmd/ip/imap4d/imap4d.h +++ /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 index 139e0f240..000000000 --- a/sys/src/cmd/ip/imap4d/list.c +++ /dev/null @@ -1,412 +0,0 @@ -#include -#include -#include -#include -#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 index 8eb57dfde..000000000 --- a/sys/src/cmd/ip/imap4d/mbox.c +++ /dev/null @@ -1,863 +0,0 @@ -#include -#include -#include -#include -#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/msg.c b/sys/src/cmd/ip/imap4d/msg.c deleted file mode 100644 index 46ccea2e5..000000000 --- a/sys/src/cmd/ip/imap4d/msg.c +++ /dev/null @@ -1,1782 +0,0 @@ -#include -#include -#include -#include -#include -#include -#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*@,;:\\\"/[]?=" aka mimeTokenStop> - * atom : 1*@,;:\\\".[]" 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/nodes.c b/sys/src/cmd/ip/imap4d/nodes.c deleted file mode 100644 index 9fe848e88..000000000 --- a/sys/src/cmd/ip/imap4d/nodes.c +++ /dev/null @@ -1,213 +0,0 @@ -#include -#include -#include -#include -#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 index 12f462b91..000000000 --- a/sys/src/cmd/ip/imap4d/search.c +++ /dev/null @@ -1,244 +0,0 @@ -#include -#include -#include -#include -#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 index b13c3bb72..000000000 --- a/sys/src/cmd/ip/imap4d/store.c +++ /dev/null @@ -1,127 +0,0 @@ -#include -#include -#include -#include -#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/mkfile b/sys/src/cmd/ip/mkfile index 71c4c6fbc..900bb17f7 100644 --- a/sys/src/cmd/ip/mkfile +++ b/sys/src/cmd/ip/mkfile @@ -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/acme/mail/src/dat.h b/sys/src/cmd/upas/Mail/dat.h similarity index 98% rename from acme/mail/src/dat.h rename to sys/src/cmd/upas/Mail/dat.h index 0b0b60a70..4ef95ce26 100644 --- a/acme/mail/src/dat.h +++ b/sys/src/cmd/upas/Mail/dat.h @@ -118,6 +118,7 @@ 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*); @@ -162,3 +163,4 @@ extern char *user; extern char deleted[]; extern int wctlfd; extern int shortmenu; +extern int altmenu; diff --git a/acme/mail/src/html.c b/sys/src/cmd/upas/Mail/html.c similarity index 100% rename from acme/mail/src/html.c rename to sys/src/cmd/upas/Mail/html.c diff --git a/acme/mail/src/mail.c b/sys/src/cmd/upas/Mail/mail.c similarity index 87% rename from acme/mail/src/mail.c rename to sys/src/cmd/upas/Mail/mail.c index 1e1ff0b8b..a0d9fd18e 100644 --- a/acme/mail/src/mail.c +++ b/sys/src/cmd/upas/Mail/mail.c @@ -34,7 +34,8 @@ void plumbthread(void); void plumbshowthread(void*); void plumbsendthread(void*); -int shortmenu; +int shortmenu; +int altmenu; void usage(void) @@ -57,12 +58,13 @@ removeupasfs(void) int ismaildir(char *s) { - char buf[256]; + char *path; Dir *d; int ret; - snprint(buf, sizeof buf, "%s%s", maildir, s); - d = dirstat(buf); + path = smprint("%s%s", maildir, s); + d = dirstat(path); + free(path); if(d == nil) return 0; ret = d->qid.type & QTDIR; @@ -87,6 +89,7 @@ threadmain(int argc, char *argv[]) plumbshowmailfd = plumbopen("showmail", OREAD|OCEXEC); shortmenu = 0; + altmenu = 0; ARGBEGIN{ case 's': shortmenu = 1; @@ -94,6 +97,9 @@ threadmain(int argc, char *argv[]) case 'S': shortmenu = 2; break; + case 'A': + altmenu = 1; + break; case 'o': outgoing = EARGF(usage()); break; @@ -183,13 +189,15 @@ threadmain(int argc, char *argv[]) wbox = newwindow(); winname(wbox, mbox.name); - wintagwrite(wbox, "Put Mail Delmesg ", 3+1+4+1+7+1); + 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); @@ -309,6 +317,32 @@ delmesg(char *name, char *digest, int dodel) } } +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) { @@ -363,7 +397,7 @@ plumbsendthread(void*) int mboxcommand(Window *w, char *s) { - char *args[10], **targs; + char *args[10], **targs, *r, *box; Message *m, *next; int ok, nargs, i, j; char buf[128]; @@ -443,6 +477,42 @@ mboxcommand(Window *w, char *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(; ifromcolon = 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); + 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, *s; + char *data, *p; 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->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; } @@ -293,6 +290,34 @@ readfile(char *dir, char *name, int *np) 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) { @@ -306,6 +331,24 @@ info(Message *m, int ind, int ogf) 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; @@ -555,44 +598,58 @@ mesgdel(Message *mbox, Message *m) 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) { - int ofd, n, k, ret; - char *t, *raw, *unixheader, *all; + char *t; + int ret; - 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); + t = smprint("%s/%s/rawunix", mbox.name, m->name); 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){ + 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; } - free(all); - close(ofd); + setflags(m, "S"); free(s); + free(t); return ret; } @@ -627,6 +684,7 @@ mesgcommand(Message *m, char *cmd) mesgmenumark(mbox.w, m->name, s); } free(s); + setflags(m, "S"); goto Return; } if(strcmp(args[0], "Reply")==0){ @@ -634,6 +692,7 @@ mesgcommand(Message *m, char *cmd) mkreply(m, "Replyall", nil, nil, nil); else mkreply(m, "Reply", nil, nil, nil); +// setflags(m, "a"); goto Return; } if(strcmp(args[0], "Q") == 0){ @@ -642,6 +701,7 @@ mesgcommand(Message *m, char *cmd) mkreply(m, "QReplyall", nil, nil, s); else mkreply(m, "QReply", nil, nil, s); +// setflags(m, "a"); goto Return; } if(strcmp(args[0], "Del") == 0){ @@ -667,11 +727,13 @@ mesgcommand(Message *m, char *cmd) 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){ @@ -949,7 +1011,7 @@ ext(char *type) void mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly) { - char *dest; + char *dest, *maildest; if(strcmp(m->disposition, "file")==0 || strlen(m->filename)!=0){ if(strlen(m->filename) == 0){ @@ -957,6 +1019,11 @@ mimedisplay(Message *m, char *name, char *rootdir, Window *w, int fileonly) 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); @@ -1244,6 +1311,7 @@ mesgopen(Message *mbox, char *dir, char *s, Message *mesg, int plumbed, char *di winclosebody(m->w); winclean(m->w); m->opened = 1; + setflags(m, "s"); if(ndirelem == 1){ free(u); return 1; diff --git a/acme/mail/src/mkfile b/sys/src/cmd/upas/Mail/mkfile similarity index 69% rename from acme/mail/src/mkfile rename to sys/src/cmd/upas/Mail/mkfile index aa6545d7d..260281de8 100644 --- a/acme/mail/src/mkfile +++ b/sys/src/cmd/upas/Mail/mkfile @@ -1,4 +1,5 @@ syms - 8c -aa mesg.c reply.c util.c win.c >>syms + $CC -a mail.c >syms + $CC -aa mesg.c reply.c util.c win.c >>syms diff --git a/acme/mail/src/reply.c b/sys/src/cmd/upas/Mail/reply.c similarity index 98% rename from acme/mail/src/reply.c rename to sys/src/cmd/upas/Mail/reply.c index 65841c570..0a5edf46c 100644 --- a/acme/mail/src/reply.c +++ b/sys/src/cmd/upas/Mail/reply.c @@ -341,21 +341,20 @@ print2(int fd, int ofd, char *fmt, ...) return n; } -void +int write2(int fd, int ofd, char *buf, int n, int nofrom) { char *from, *p; - int m; + int m = 0; - write(fd, buf, n); + if(fd >= 0) + m = write(fd, buf, n); if(ofd <= 0) - return; + return m; - if(nofrom == 0){ - write(ofd, buf, n); - return; - } + 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){ @@ -367,13 +366,15 @@ write2(int fd, int ofd, char *buf, int n, int nofrom) 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); /* escape with space if From is at start of line */ + write(ofd, " ", 1); write(ofd, from, 4); m += 4; } n -= m; } + return p - buf; } void diff --git a/acme/mail/src/util.c b/sys/src/cmd/upas/Mail/util.c similarity index 98% rename from acme/mail/src/util.c rename to sys/src/cmd/upas/Mail/util.c index bbc87883c..c32de5feb 100644 --- a/acme/mail/src/util.c +++ b/sys/src/cmd/upas/Mail/util.c @@ -88,7 +88,7 @@ error(char *fmt, ...) va_end(arg); fmtprint(&f, "\n"); fmtfdflush(&f); - threadexitsall(buf); + threadexitsall(fmt); } void diff --git a/acme/mail/src/win.c b/sys/src/cmd/upas/Mail/win.c similarity index 100% rename from acme/mail/src/win.c rename to sys/src/cmd/upas/Mail/win.c diff --git a/sys/src/cmd/upas/alias/aliasmail.c b/sys/src/cmd/upas/alias/aliasmail.c index 5a8c007b7..e832eb558 100644 --- a/sys/src/cmd/upas/alias/aliasmail.c +++ b/sys/src/cmd/upas/alias/aliasmail.c @@ -5,28 +5,30 @@ * local ones. */ -/* predeclared */ -static String *getdbfiles(void); -static int translate(char*, char**, String*, String*); -static int lookup(String**, String*, String*); -static int compare(String*, char*); -static char* mklower(char*); +static String *getdbfiles(void); +static int translate(char*, char**, String*, String*); +static int lookup(String**, String*, String*); +static char *mklower(char*); -static int debug; -static int from; -static char *namefiles = "namefiles"; -#define DEBUG if(debug) +static int debug; +static int from; +static char *namefiles = "namefiles"; + +#define dprint(...) if(debug)fprint(2, __VA_ARGS__); else {} + +void +usage(void) +{ + fprint(2, "usage: aliasmail [-df] [-n namefile] [names ...]\n"); + exits("usage"); +} -/* loop through the names to be translated */ 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= 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; } diff --git a/sys/src/cmd/upas/alias/mkfile b/sys/src/cmd/upas/alias/mkfile index 1fe8b1df6..9eb3f37f0 100644 --- a/sys/src/cmd/upas/alias/mkfile +++ b/sys/src/cmd/upas/alias/mkfile @@ -1,4 +1,5 @@ [2=] diff --git a/sys/src/cmd/upas/binscripts/spam.rc b/sys/src/cmd/upas/binscripts/spam.rc new file mode 100755 index 000000000..88dda04b6 --- /dev/null +++ b/sys/src/cmd/upas/binscripts/spam.rc @@ -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 index 000000000..c5e664128 --- /dev/null +++ b/sys/src/cmd/upas/binscripts/tfmt.rc @@ -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 index 000000000..04213f3f7 --- /dev/null +++ b/sys/src/cmd/upas/binscripts/unspam.rc @@ -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 index 16bdd48aa..000000000 --- a/sys/src/cmd/upas/common/appendfiletombox.c +++ /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; -} diff --git a/sys/src/cmd/upas/common/aux.c b/sys/src/cmd/upas/common/aux.c index 8786acb50..985e3f296 100644 --- a/sys/src/cmd/upas/common/aux.c +++ b/sys/src/cmd/upas/common/aux.c @@ -1,42 +1,5 @@ #include "common.h" -/* expand a path relative to some `.' */ -extern String * -abspath(char *path, char *dot, String *to) -{ - if (*path == '/') { - to = s_append(to, path); - } else { - to = s_append(to, dot); - to = s_append(to, "/"); - to = s_append(to, path); - } - return to; -} - -/* return a pointer to the base component of a pathname */ -extern char * -basename(char *path) -{ - char *cp; - - cp = strrchr(path, '/'); - return cp==0 ? path : cp+1; -} - -/* append a sub-expression match onto a String */ -extern void -append_match(Resub *subexp, String *sp, int se) -{ - char *cp, *ep; - - cp = subexp[se].sp; - ep = subexp[se].ep; - for (; cp < ep; cp++) - s_putc(sp, *cp); - s_terminate(sp); -} - /* * check for shell characters in a String */ @@ -95,7 +58,7 @@ 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; } diff --git a/sys/src/cmd/upas/common/become.c b/sys/src/cmd/upas/common/become.c index 8c012d212..37cd11997 100644 --- a/sys/src/cmd/upas/common/become.c +++ b/sys/src/cmd/upas/common/become.c @@ -6,11 +6,10 @@ * become powerless user */ int -become(char **cmd, char *who) +become(char **, char *who) { int fd; - USED(cmd); if(strcmp(who, "none") == 0) { fd = open("#c/user", OWRITE); if(fd < 0 || write(fd, "none", strlen("none")) < 0) { diff --git a/sys/src/cmd/upas/common/common.h b/sys/src/cmd/upas/common/common.h index 3f9acbd18..9cefc0531 100644 --- a/sys/src/cmd/upas/common/common.h +++ b/sys/src/cmd/upas/common/common.h @@ -1,80 +1,79 @@ -#include "sys.h" - -/* format of REMOTE FROM lines */ -extern char *REMFROMRE; -extern int REMSENDERMATCH; -extern int REMDATEMATCH; -extern int REMSYSMATCH; - -/* format of mailbox FROM lines */ -#define IS_HEADER(p) ((p)[0]=='F'&&(p)[1]=='r'&&(p)[2]=='o'&&(p)[3]=='m'&&(p)[4]==' ') -#define IS_TRAILER(p) ((p)[0]=='m'&&(p)[1]=='o'&&(p)[2]=='r'&&(p)[3]=='F'&&(p)[4]=='\n') -extern char *FROMRE; -extern int SENDERMATCH; -extern int DATEMATCH; - enum { - Elemlen= 28, - Errlen= ERRMAX, - Pathlen= 256, + Elemlen = 56, + Pathlen = 256, +}; + +#include "sys.h" +#include + +enum{ + Fields = 18, + + /* 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*); diff --git a/sys/src/cmd/upas/common/config.c b/sys/src/cmd/upas/common/config.c index f322783ef..6bf8ef923 100644 --- a/sys/src/cmd/upas/common/config.c +++ b/sys/src/cmd/upas/common/config.c @@ -1,11 +1,9 @@ #include "common.h" -char *MAILROOT = "/mail"; -char *UPASLOG = "/sys/log"; -char *UPASLIB = "/mail/lib"; -char *UPASBIN= "/bin/upas"; -char *UPASTMP = "/mail/tmp"; -char *SHELL = "/bin/rc"; -char *POST = "/sys/lib/post/dispatch"; - -int MBOXMODE = 0662; +char *MAILROOT = "/mail"; +char *SPOOL = "/mail"; +char *UPASLOG = "/sys/log"; +char *UPASLIB = "/mail/lib"; +char *UPASBIN = "/bin/upas"; +char *UPASTMP = "/mail/tmp"; +char *SHELL = "/bin/rc"; diff --git a/sys/src/cmd/upas/common/flags.c b/sys/src/cmd/upas/common/flags.c new file mode 100644 index 000000000..2dc022032 --- /dev/null +++ b/sys/src/cmd/upas/common/flags.c @@ -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 index 000000000..e159ad269 --- /dev/null +++ b/sys/src/cmd/upas/common/fmt.c @@ -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 index 000000000..1d5e34542 --- /dev/null +++ b/sys/src/cmd/upas/common/folder.c @@ -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; +} diff --git a/sys/src/cmd/upas/common/libsys.c b/sys/src/cmd/upas/common/libsys.c index f0b8f35a4..969c2ff67 100644 --- a/sys/src/cmd/upas/common/libsys.c +++ b/sys/src/cmd/upas/common/libsys.c @@ -2,68 +2,43 @@ #include #include -/* - * 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; + char *e, *q; - /* - * get the name of the lock file - */ - lp = s_new(); - cp = strrchr(path, '/'); - if(cp) - s_nappend(lp, path, cp - path + 1); - s_append(lp, "L.mbox"); - - return lp; + seprint(l->name, e = l->name+sizeof l->name, "%s", path); + q = strrchr(l->name, '/'); + if(q == nil) + q = l->name; + else + q++; + seprint(q, e, "%s", "L.mbox"); } int @@ -92,58 +67,43 @@ 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 */ + } + 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); + } return 0; } - - 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; - } - } - } else - free(d); - - return 1; /* try again later */ + /* 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; + char *s, *c; - cp = getenv("site"); - if(cp == 0 || *cp == 0) - cp = alt_sysname_read(); - if(cp == 0 || *cp == 0) - cp = "kremvax"; - strecpy(name, name+sizeof name, cp); + c = s = getenv("site"); + if(!c) + c = alt_sysname_read(); + if(!c) + c = "kremvax"; + strecpy(name, name+sizeof name, c); + free(s); return name; } -extern char * + +char * alt_sysname_read(void) { - static char name[128]; - int n, fd; - - fd = open("/dev/sysname", OREAD); - if(fd < 0) - return 0; - n = read(fd, name, sizeof(name)-1); - close(fd); - if(n <= 0) - return 0; - name[n] = 0; - return name; + return sysname(); } /* * get all names */ -extern char** +char** sysnames_read(void) { - static char **namev; - Ndbtuple *t, *nt; int n; - char *cp; + Ndbtuple *t, *nt; + static char **namev; if(namev) return namev; - free(csgetvalue(0, "sys", alt_sysname_read(), "dom", &t)); + free(csgetvalue(0, "sys", sysname(), "dom", &t)); n = 0; for(nt = t; nt; nt = nt->entry) @@ -471,13 +374,10 @@ 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 access(file, AEXIST) == 0; } -/* - * return nonzero if file is a directory - */ -extern int -sysisdir(char *file) -{ - Dir *d; - int rv; +static char yankeepig[] = "die: yankee pig dog"; - d = dirstat(file); - if(d == nil) - return 0; - rv = d->mode & DMDIR; - free(d); - return rv; -} - -/* - * kill a process or process group - */ - -static int -stomp(int pid, char *file) -{ - char name[64]; - int fd; - - snprint(name, sizeof(name), "/proc/%d/%s", pid, file); - fd = open(name, 1); - if(fd < 0) - return -1; - if(write(fd, "die: yankee pig dog\n", sizeof("die: yankee pig dog\n") - 1) <= 0){ - close(fd); - return -1; - } - close(fd); - return 0; - -} - -/* - * kill a process - */ -extern int +int syskill(int pid) { - return stomp(pid, "note"); - + return postnote(PNPROC, pid, yankeepig); } -/* - * kill a process group - */ -extern int +int syskillpg(int pid) { - return stomp(pid, "notepg"); + return postnote(PNGROUP, pid, yankeepig); } -extern int +int sysdetach(void) { if(rfork(RFENVG|RFNAMEG|RFNOTEG) < 0) { @@ -668,11 +466,10 @@ 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 */ + } + return buf[0]? buf: 0; +} + +char* +username(char *from) +{ + char *s; + Biobuf *b; + + 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); } - Bterm(bp); return s; } -char * -remoteaddr(int fd, char *dir) -{ - char buf[128], *p; - int n; - - if(dir == 0){ - if(fd2path(fd, buf, sizeof(buf)) != 0) - return ""; - - /* parse something of the form /net/tcp/nnnn/data */ - p = strrchr(buf, '/'); - if(p == 0) - return ""; - strncpy(p+1, "remote", sizeof(buf)-(p-buf)-2); - } else - snprint(buf, sizeof buf, "%s/remote", dir); - buf[sizeof(buf)-1] = 0; - - fd = open(buf, OREAD); - if(fd < 0) - return ""; - n = read(fd, buf, sizeof(buf)-1); - close(fd); - if(n > 0){ - buf[n] = 0; - p = strchr(buf, '!'); - if(p) - *p = 0; - return strdup(buf); - } - return ""; -} - -// 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; + char *p, *s, buf[Pathlen]; + int isdir, mode; + Dir *d; - mailfile = s_new(); - if(folder == 0) - mboxname(user, mailfile); - else { - snprint(buf, sizeof(buf), "%s/mbox", folder); - mboxpath(buf, user, mailfile, 0); - } - - // don't destroy existing mailbox - if(access(s_to_c(mailfile), 0) == 0){ - fprint(2, "mailbox already exists\n"); + assert(folder != 0); + mboxpathbuf(buf, sizeof buf, user, folder); + if(access(buf, 0) == 0){ + fprint(2, "%s already exists\n", ftype); return -1; } - fprint(2, "creating new mbox: %s\n", s_to_c(mailfile)); + fprint(2, "creating new %s: %s\n", ftype, buf); - // make sure preceding levels exist - for(p = s_to_c(mailfile); p; p++) { - if(*p == '/') /* skip leading or consecutive slashes */ + /* + * if we can deliver to this mbox, it needs + * to be read/execable all the way down + */ + mode = 0711; + if(!strncmp(buf, "/mail/box/", 10)) + if((s = strrchr(buf, '/')) && !strcmp(s+1, "mbox")) + mode = 0755; + for(p = buf; p; p++) { + if(*p == '/') continue; p = strchr(p, '/'); if(p == 0) break; *p = 0; - if(access(s_to_c(mailfile), 0) != 0){ - if(docreate(s_to_c(mailfile), DMDIR|0711) < 0) - return -1; - } + if(access(buf, 0) != 0) + if(docreate(buf, DMDIR|mode) < 0) + return -1; *p = '/'; } - - // 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 index 5347c6550..000000000 --- a/sys/src/cmd/upas/common/mail.c +++ /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 index c7beae817..000000000 --- a/sys/src/cmd/upas/common/makefile +++ /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 - diff --git a/sys/src/cmd/upas/common/mkfile b/sys/src/cmd/upas/common/mkfile index 7422fbe3a..5a628ae47 100644 --- a/sys/src/cmd/upas/common/mkfile +++ b/sys/src/cmd/upas/common/mkfile @@ -1,13 +1,17 @@ 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); +//} diff --git a/sys/src/cmd/upas/common/sys.h b/sys/src/cmd/upas/common/sys.h index a50f27284..a860df15f 100644 --- a/sys/src/cmd/upas/common/sys.h +++ b/sys/src/cmd/upas/common/sys.h @@ -1,85 +1,63 @@ -/* - * System dependent header files for research - */ - #include #include -#include #include -#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 */ + +enum { + Mboxmode = 0622, +}; /* * files in libsys.c */ -extern char *sysname_read(void); -extern char *alt_sysname_read(void); -extern char *domainname_read(void); -extern char **sysnames_read(void); -extern char *getlog(void); -extern char *thedate(void); -extern Biobuf *sysopen(char*, char*, ulong); -extern int sysopentty(void); -extern int sysclose(Biobuf*); -extern int sysmkdir(char*, ulong); -extern int syschgrp(char*, char*); -extern Mlock *syslock(char *); -extern void sysunlock(Mlock *); -extern void syslockrefresh(Mlock *); -extern int e_nonexistent(void); -extern int e_locked(void); -extern long sysfilelen(Biobuf*); -extern int sysremove(char*); -extern int sysrename(char*, char*); -extern int sysexist(char*); -extern int sysisdir(char*); -extern int syskill(int); -extern int syskillpg(int); -extern int syscreate(char*, int, ulong); -extern Mlock *trylock(char *); -extern void exit(int); -extern void pipesig(int*); -extern void pipesigoff(void); -extern int holdon(void); -extern void holdoff(int); -extern int syscreatelocked(char*, int, int); -extern int sysopenlocked(char*, int); -extern int sysunlockfile(int); -extern int sysfiles(void); -extern int become(char**, char*); -extern int sysdetach(void); -extern int sysdirreadall(int, Dir**); -extern String *username(String*); -extern char* remoteaddr(int, char*); -extern int creatembox(char*, char*); - -extern String *readlock(String*); -extern char *homedir(char*); -extern String *mboxname(char*, String*); -extern String *deadletter(String*); - -/* - * maximum size for a file path - */ -#define MAXPATHLEN 128 +char *sysname_read(void); +char *alt_sysname_read(void); +char *domainname_read(void); +char **sysnames_read(void); +char *getlog(void); +char *thedate(void); +Biobuf *sysopen(char*, char*, ulong); +int sysopentty(void); +int sysclose(Biobuf*); +int sysmkdir(char*, ulong); +Mlock *syslock(char *); +void sysunlock(Mlock *); +void syslockrefresh(Mlock *); +int sysrename(char*, char*); +int sysexist(char*); +int syskill(int); +int syskillpg(int); +Mlock *trylock(char *); +void pipesig(int*); +void pipesigoff(void); +int holdon(void); +void holdoff(int); +int syscreatelocked(char*, int, int); +int sysopenlocked(char*, int); +int sysunlockfile(int); +int sysfiles(void); +int become(char**, char*); +int sysdetach(void); +char *username(char*); +int creatembox(char*, char*); +int createfolder(char*, char*); diff --git a/sys/src/cmd/upas/common/totm.c b/sys/src/cmd/upas/common/totm.c new file mode 100644 index 000000000..8c05fb758 --- /dev/null +++ b/sys/src/cmd/upas/common/totm.c @@ -0,0 +1,36 @@ +#include + +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); +} diff --git a/sys/src/cmd/upas/filterkit/deliver.c b/sys/src/cmd/upas/filterkit/deliver.c index b968eb6c0..9040e8021 100644 --- a/sys/src/cmd/upas/filterkit/deliver.c +++ b/sys/src/cmd/upas/filterkit/deliver.c @@ -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(""); } diff --git a/sys/src/cmd/upas/filterkit/list.c b/sys/src/cmd/upas/filterkit/list.c index 06259bb9e..1d78843cf 100644 --- a/sys/src/cmd/upas/filterkit/list.c +++ b/sys/src/cmd/upas/filterkit/list.c @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include #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 index 000000000..3f9b47596 --- /dev/null +++ b/sys/src/cmd/upas/filterkit/mbappend.c @@ -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 index 000000000..36bda6ad0 --- /dev/null +++ b/sys/src/cmd/upas/filterkit/mbcreate.c @@ -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 index 000000000..94cfd80ba --- /dev/null +++ b/sys/src/cmd/upas/filterkit/mbremove.c @@ -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(""); +} diff --git a/sys/src/cmd/upas/filterkit/mkfile b/sys/src/cmd/upas/filterkit/mkfile index c077d5641..18415495f 100644 --- a/sys/src/cmd/upas/filterkit/mkfile +++ b/sys/src/cmd/upas/filterkit/mkfile @@ -1,13 +1,14 @@ +Subject: 1 testing + +testing diff --git a/sys/src/cmd/upas/filterkit/token.c b/sys/src/cmd/upas/filterkit/token.c index ba48bc310..ef0e136a3 100644 --- a/sys/src/cmd/upas/filterkit/token.c +++ b/sys/src/cmd/upas/filterkit/token.c @@ -1,60 +1,50 @@ #include #include #include -#include #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 index 000000000..fdbf83f52 --- /dev/null +++ b/sys/src/cmd/upas/fs/bos.c @@ -0,0 +1,40 @@ +#include +#include +#include + +/* + * assume: + * - the stack segment can't be resized + * - stacks may not be segattached (by name Stack, anyway) + * - no thread library + */ +uintptr +absbos(void) +{ + char buf[64], *f[10], *s, *r; + int n; + uintptr p, q; + Biobuf *b; + + p = 0xd0000000; /* guess pc kernel */ + snprint(buf, sizeof buf, "/proc/%ud/segment", getpid()); + b = Bopen(buf, OREAD); + if(b == nil) + return p; + for(; s = Brdstr(b, '\n', 1); free(s)){ + if((n = tokenize(s, f, nelem(f))) < 3) + continue; + if(strcmp(f[0], "Stack") != 0) + continue; + /* + * addressing from end because segment + * flags could become discontiguous if + * additional flags are added + */ + q = strtoull(f[n - 3], &r, 16); + if(*r == 0 && (char*)q > end) + p = q; + } + Bterm(b); + return p; +} diff --git a/sys/src/cmd/upas/fs/cache.c b/sys/src/cmd/upas/fs/cache.c new file mode 100644 index 000000000..2d0a6280d --- /dev/null +++ b/sys/src/cmd/upas/fs/cache.c @@ -0,0 +1,552 @@ +#include "common.h" +#include +#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< 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 index 0000000000000000000000000000000000000000..bac91113986d2cf95d87ddfef44546f7b24d3870 GIT binary patch literal 75510 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 Ic*6Ss0f7o>W&i*H literal 0 HcmV?d00001 diff --git a/sys/src/cmd/upas/fs/chkidx.c b/sys/src/cmd/upas/fs/chkidx.c new file mode 100644 index 000000000..8b0a1545b --- /dev/null +++ b/sys/src/cmd/upas/fs/chkidx.c @@ -0,0 +1,416 @@ +#include "common.h" +#include +#include +#include +#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, ""); + for(i = 0; i < SHA1dlen; i++) + sprint(buf + 2*i, "%2.2ux", u[i]); + return fmtstrcpy(f, buf); +} + +static int +Dfmt(Fmt *f) +{ + char buf[32]; + int seq; + uvlong v; + + v = va_arg(f->args, uvlong); + seq = v & 0xff; + if(seq > 99) + seq = 99; + snprint(buf, sizeof buf, "%llud.%.2d", v>>8, seq); + return fmtstrcpy(f, buf); +} + +static Mailbox* +shellmailbox(char *path) +{ + Mailbox *mb; + + mb = malloc(sizeof *mb); + if(mb == 0) + sysfatal("malloc"); + memset(mb, 0, sizeof *mb); + snprint(mb->path, sizeof mb->path, "%s", path); + mb->id = newid(); + mb->root = newmessage(nil); + mb->mtree = mkavltree(mtreecmp); + return mb; +} + +void +shellmailboxfree(Mailbox*) +{ +} + +Message* +newmessage(Message *parent) +{ + static int id; + Message *m; + +// msgallocd++; + + m = mallocz(sizeof *m, 1); + if(m == 0) + sysfatal("malloc"); + m->disposition = Dnone; +// m->type = newrefs("text/plain"); +// m->charset = newrefs("iso-8859-1"); + m->cstate = Cidxstale; + m->flags = Frecent; + m->id = newid(); + if(parent) + snprint(m->name, sizeof m->name, "%d", ++(parent->subname)); + if(parent == nil) + parent = m; + m->whole = parent; + m->hlen = -1; + return m; +} + +void +unnewmessage(Mailbox *mb, Message *parent, Message *m) +{ + assert(parent->subname > 0); +// delmessage(mb, m); + USED(mb, m); + parent->subname -= 1; +} + + +static int +validmessage(Mailbox *mb, Message *m, int level) +{ + if(level){ + if(m->digest != 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + if(m->fileid != 0) + goto lose; + }else{ + if(m->digest == 0) + goto lose; + if(m->size == 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + goto lose; + if(mtreefind(mb, m->digest)) + goto lose; + } + return 1; +lose: + fprint(2, "invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid); + return 0; +} + +static char* +∫(char *x) +{ + if(x && *x) + return x; + return nil; +} + +static char* +brdstr(Biobuf *b, int c, int eat) +{ + char *s; + + s = Brdstr(b, c, eat); + if(s) + lineno++; + return s; +} + +static int +nibble(int c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c < 0x20) + c += 0x20; + if(c >= 'a' && c <= 'f') + return c - 'a'+10; + return 0xff; +} + +static uchar* +hackdigest(char *s) +{ + uchar t[SHA1dlen]; + int i; + + if(strcmp(s, "-") == 0) + return 0; + if(strlen(s) != 2*SHA1dlen){ + fprint(2, "bad digest %s\n", s); + return 0; + } + for(i = 0; i < SHA1dlen; i++) + t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]); + memmove(s, t, SHA1dlen); + return (uchar*)s; +} + +static Message* +findmessage(Mailbox *, Message *parent, int n) +{ + Message *m; + + for(m = parent->part; m; m = m->next) + if(!m->digest && n-- == 0) + return m; + return 0; +} + +static uvlong +rdfileid(char *s, int level) +{ + char *p; + uvlong uv; + + uv = strtoul(s, &p, 0); + if((level == 0 && uv < 1000000) || *p != '.') + return 0; + return uv<<8 | strtoul(p + 1, 0, 10); +} + +static int +rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level) +{ + char *f[50 + 1], *s; + uchar *digest; + int n, nparts, good, bad, redux; + Message *m, **ll, *l; + + bad = good = redux = 0; + ll = &parent->part; + nparts = npart; + for(; npart != 0 && (s = brdstr(b, '\n', 1)); npart--){ +//if(lineno>18&&lineno<25)idprint("%d: %d [%s]\n", lineno, level, s); + n = tokenize(s, f, nelem(f)); + if(n != Idxfields){ + print("%d: bad line\n", lineno); + bad++; + free(s); + continue; + } + digest = hackdigest(f[0]); + if(level == 0){ + if(digest == 0) + idprint("%d: no digest\n", lineno); + m = mtreefind(mb, digest); + }else{ + m = findmessage(mb, parent, nparts - npart); + if(m == 0){ + // idprint("can't find message\n"); + } + } + if(m){ + /* + * read in mutable information. + * currently this is only flags + */ + idprint("%d seen before %d... %.2ux", level, m->id, m->cstate); + redux++; + m->flags |= strtoul(f[1], 0, 16); + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + idprint("→%.2ux\n", m->cstate); + free(s); + + if(m->nparts) + rdidx(b, mb, m, m->nparts, level + 1); + ll = &m->next; + continue; + } + m = newmessage(parent); +//if(lineno>18&&lineno<25)idprint("%d: %d %d %A\n", lineno, level, m->id, digest); +// idprint("%d new %d %#A \n", level, m->id, digest); + m->digest = digest; + m->flags = strtoul(f[1], 0, 16); + m->fileid = rdfileid(f[2], level); + m->lines = atoi(f[3]); + m->ffrom = ∫(f[4]); + m->from = ∫(f[5]); + m->to = ∫(f[6]); + m->cc = ∫(f[7]); + m->bcc = ∫(f[8]); + m->replyto = ∫(f[9]); + m->messageid = ∫(f[10]); + m->subject = ∫(f[11]); + m->sender = ∫(f[12]); + m->inreplyto = ∫(f[13]); +// m->type = newrefs(f[14]); + m->disposition = atoi(f[15]); + m->size = strtoul(f[16], 0, 0); + m->rawbsize = strtoul(f[17], 0, 0); + switch(idxver){ + case 4: + m->nparts = strtoul(f[18], 0, 0); + case 7: + m->ibadchars = strtoul(f[18], 0, 0); + m->idxaux = ∫(f[19]); + m->nparts = strtoul(f[20], 0, 0); + } + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + m->str = s; +// free(s); + if(!validmessage(mb, m, level)){ + /* + * if this was an okay message, and somebody + * wrote garbage to the index file, we lose the msg. + */ + print("%d: !validmessage\n", lineno); + bad++; + unnewmessage(mb, parent, m); + continue; + } + if(level == 0) + m->inmbox = 1; +// cachehash(mb, m); /* hokey */ + l = *ll; + *ll = m; + ll = &m->next; + *ll = l; + good++; + + if(m->nparts){ +// fprint(2, "%d: %d parts [%s]\n", lineno, m->nparts, f[18]); + rdidx(b, mb, m, m->nparts, level + 1); + } + } + if(level == 0) + print("idx: %d %d %d\n", good, bad, redux); + return 0; +} + +static int +verscmp(Biobuf *b) +{ + char *s; + int i; + + if((s = brdstr(b, '\n', 0)) == 0) + return -1; + for(i = 0; i < Maxver; i++) + if(magictab[i]) + if(strcmp(s, magictab[i]) == 0) + break; + free(s); + if(i == Maxver) + return -1; + idxver = i; + magic = magictab[i]; + Idxfields = fieldstab[i]; + fprint(2, "version %d\n", i); + return 0; +} + +int +mbvers(Biobuf *b) +{ + char *s; + + if(s = brdstr(b, '\n', 1)){ + free(s); + return 0; + } + return -1; +} + +int +ckidxfile(Mailbox *mb) +{ + char buf[Pathlen + 4]; + int r; + Biobuf *b; + + snprint(buf, sizeof buf, "%s", mb->path); + b = Bopen(buf, OREAD); + if(b == nil) + return -1; + if(verscmp(b) == -1) + return -1; + if(idxver >= 7) + mbvers(b); + r = rdidx(b, mb, mb->root, -1, 0); + Bterm(b); + return r; +} + +static char *bargv[] = {"/fd/0", 0}; + +void +main(int argc, char **argv) +{ + Mailbox *mb; + + fmtinstall('A', Afmt); + fmtinstall('D', Dfmt); + ARGBEGIN{ + }ARGEND + if(*argv == 0) + argv = bargv; + for(; *argv; argv++){ + mb = shellmailbox(*argv); + lineno = 0; + if(ckidxfile(mb) == -1) + fprint(2, "%s: %r\n", *argv); + shellmailboxfree(mb); + } + exits(""); +} diff --git a/sys/src/cmd/upas/fs/dat.h b/sys/src/cmd/upas/fs/dat.h index d21de3ed5..585af992a 100644 --- a/sys/src/cmd/upas/fs/dat.h +++ b/sys/src/cmd/upas/fs/dat.h @@ -1,191 +1,313 @@ -typedef struct Message Message; -struct Message -{ - int id; - int refs; - int subname; - char name[Elemlen]; +#include - // 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 +enum { + /* cache states */ + Cidx = 1<<0, + Cidxstale = 1<<1, + Cheader = 1<<2, + Cbody = 1<<3, - // mail info - String *unixheader; - String *unixfrom; - String *unixdate; - String *from822; - String *sender822; - String *to822; - String *bcc822; - String *cc822; - String *replyto822; - String *date822; - String *inreplyto822; - String *subject822; - String *messageid822; - String *addrs; - String *mimeversion; - String *sdigest; - - // mime info - String *boundary; - String *type; - int encoding; - int disposition; - String *charset; - String *filename; - int converted; - int decoded; - char lines[10]; // number of lines in rawbody - - Message *next; // same level - Message *part; // down a level - Message *whole; // up a level - - uchar digest[SHA1dlen]; - - vlong imapuid; // used by imap4 - - char uidl[80]; // used by pop3 - int mesgno; -}; - -enum -{ - // encodings + /* 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 index 0000000000000000000000000000000000000000..d73815007a47fc0b6f832ad20f5de3f012a0cbda GIT binary patch literal 36947 zcmc(I3tW^{`v3dRyx@SNGb$=771davXn=UZOK}+#6))vIH6$*|hF~ybx<3$ycLsPF zP%F#YtgP*_+kLl-m&z3(vD!+LTD5GQGMZ^@qq0OT`G22t-Wg^Tx7^*&=Z_!eocEmP zexCE3=bZBn3WDergsek?5SAwh+6X}y)+z{ndkE~u5F?+|@6e#GNTwZGD^QwElDk6^ zgdO?xB$?bB@gO(rn`|dds5!Tq;;3Pgunbt%UQVqNLRGm#unKaM)LCzeAuuf(EWMpt z!)|nqFxbBUwEPC^6RdMvBom&6hAfjw$udbh1)!j!b3aeCJ31{_`h~hf6{^7uSTjHt z17t^;D%|&4L9jKGU`?FmmkJlq*;CP*TA&Q^anD4d>?Ok2DRWKHN`&cR)`@^|q7zWo z2tc@FsXb$x&M4;r(rwjxJRQF z0Pg)$0F(oL^2tmkM7InkhY6LCNYq+FjQBJXAG0YFBaN;sHvD~ou*0lccoV$C`<;9; z!nwp0Xdnc_2UPZ40WuCA@L{)B%XfnK(E{>*lrE%yUYJ*;ef|d;7mrg&7 z?d);%R9t5}OAzHQOWN9DQ$1xkj%46RbsaEeVniq;owXWNOyQz|Oq zA0FD-DF~B@$hr-T0tST1B%jnlZaqwBG;HrHpRKIyS=&#QM%QFJODL7sf>cLnFOsNM zs~nVFH`(4^PY=^YRTNzlf#`c3=O6?wAHS&=14z-TXD}In+;G7%S{@?b}~q%A(gjUzgG6sEM!VnhRBT? z?*X|{>wT9rgm!R^bunP4H@qhB!3p!LDW4VB=_irwwB|Hs%S<$BE&ee5GM2{6f8o4Qv`l>?i(?>G;lk5gUov=O*;mvB?TIK*6~t483!_v zVCV}%I}m$GVx4;qDm&*E;wd0RJxdz#25+lG!;|o~?Ti~*((qR#`anKItn!1X;oH3!jR4T6c43siAA~>3hyO?*{*F8{ z1=JYa4T1N3D0grai(v;72jJfR82#hihjhePf$(M(>_V?5C0oD;O9QF8#1xV6VZt|t z-B_8T4R#}p#_C}PyX325ojM43);`=|f6G^or?{O?VcnX*I^)ZAtV~|lOa4W$Xigth z4v<(4(mmoH-~T*<&HI$SYBCZiB?Ix#mcdjZz_j@AT&q^IL2|B z{kyeFqudc+9wMJfUY8)B$)Q@oxG<$>7LKnQsFmkGd&_wV7_5;L^;!R&S_HYehN}1 z=QMI1JX1VlK1;5BGn|}2C%{jw2I`%rQzJrT6HWg}Rih z6iVF=fj8*gwU8D73+dTz8i%5xo+E;Hy=gABQ#`#7aAYtd6S*$33&@=U-JF;yBy^av z-_}grn|7eJQ>{}3Reh-+S^-!Y#`;Nhc#+(;*JUIjwrdVu~@m z)nFf+@h52#?*PNp4?#15rTAv6H2Nl!QG%f&tJc zIAu-G^TB3-)uS*7o_6@F z?L5=d0zKy{hg0B)QC!6(P!WppJo8mlAX=A-%il?Z1B_(&; zPY~TVx`1#E?MIz=nqyLfL4{BvtG7nP@WD&qE1JSQ0=v zGX;^PR_Fb~Z)0*+teX_!k%&?1IMbP~Q#`7dq(>@u=$z?BvRoe}%7kj?0Rl#YRqvdx zQ>N?r>W?}tcnwmxbGklx|6N~z4D)ipd<{d-iS)iApNs}G6Gq25L!a;=20dcJTvJTN zO)zZo{?*s3N_Ls`HX2Jn5f=C4>wyVv_6gBxKJ@a&pur?@qWOfR3h^Z0o1=qV3EwEl zjwTd=X9o9I;u>{C?vBg(5Xohc^rOcKi@_ZyuBK&}Sb|!>Jb&pI&Xd{9U@s>ZWtuWpzImQv*q&t? zU!dqZkrFY!m9-qqZ%U#rrbbe$GFyiNb|Ft3RFX6cF+5v3qX8}Y~VQ!ZtCnYQ#4ma z>wXk^sup3R@JHV$ETrI+MA|u1$Tab&28_a;-Ht*8u=yw)miK5e3h}G2WtN1jZkJEa zEK`#5uGlys%M@>@`7;KfYaq*tcoaxtShWG>-^fIP&7D(uP@K(8zc9e`2Kys4E4uNt z>wjkEH#AfPT88muTNTn3Qw(P1Ta}&Pt2RZu*hIaaJIPs9kD?%lwa1-%j)U=OZw#=O zCkEhwL(u);j`G0@HAKYz$DsYxDkY={0f|NX&f*EC|X0&1Xw^({)$rqGG*B+3(MA5t*{XX<~pYTeoO_ zcB}9T5*WMK0%f0a7z)SmX<%AC(y;yg0xXI*8n(Z!U^29}cXk$FUA&Qv7G}FTBTR;x zdNw2WvA|Y#E2Xj9-><#E91jGoCS6Jx?BP)LLUY7I5mKVzn=aQ$X)zp7#)h+KmqR*ouOw8v1cOxgMi-@vRS2#Am{82A|Rtu*hGu)W)f%_ua9 zgkL3*d#plUi{}VD$K#nbg9yq<5*K9zO1(Q(<;jn#BIR^gHk~XBqQIBjlvv3)r;<-n z+7|<+aoD`U3KLB`Z@IJ;Jji0@W>b@Miq8teop;3~e3JY_MNj2x!*=Ob_!CYHC$c37 zX3)8-5E=+2-FsB=7`BTAfUxLsW~0`vrjllXw3m#hu3{Dv7IZzSv39fFK{a zV4TAlU)o}A!(ck!Fg<~y1qlEO0rJBPaS5NGA(I+VW~n7*rvQkSJ60JSAJP%W6j*Nx zv!2;xZkZCj1^~7!Q`~x#I~J0)5M~)ho?+t}U>NNAgx9gqV4ut?w_EgV3fP*r0jCMv z#?cM~6EtqC#{~J&Hnk3mtP4enMo4XJp5ys6@w+3rHUi$9z1nJp|6y_6hO3iHdVpO!E5` zdTc?LCckB6fuLE@eN5$yJGg6`cXC0m`y#Y4PXmGqsI_;w{4M<8)EVKxs9!gmD zTH?W;8P*jH%c_F}&RNljX+*?w8x=as0u&PkOAHflguFcN9C?=~l_n3=)8SX|z8T4o zYFkbhqbFF1;67ov_Z{BGHUhg@TeSBzR>pfDbIvkBR~^^&E`7@s6ND2yODSG5i%jagyhk27(4Z%#ae0zaV0Px)3F0JG||F34zR|?GgqOA za65W|26u55ONvZ$N#~&g;ssfvuhRaY{JB;BQ zb(6_qJckgInIdtMy@#Q@8AEo2vO&0i!iT3V;C(Qlj@DmcW&|~21SWmJ#QUs|`d8$f zjw!STFg6VnRW_#&Lrt^Kt35-}Op}IA=yw#VGtToSLOHdD(}2CiQn?b#0Guxw92?Xw zLh#(DKEN1)rxp)fT#4Ie`s>W)$z1CJK<0{4&SKK*oU@{wsT~j`)0z4+iY!lWAYZEQ zd&+^B(J|ME3gtUpH2VdI5l<7vr=ZBA@s)TCO0nreG`Td7G^2xo7R1pwQ_p?6|FNY2z~ zoa$9hvW&)|re&DiajoSlxnsB`LhgvOgtCLTCfn3Fni9CfnHm@E_2N_K{Su!>%<-vl z8t)OgV}%8gtq756`sW-#hQhPI@}U}>OZZ-kyf7)^;?mY|~ZRrKz)NC(M8IAiYvV4SJb3P>kB zEUrXj@>5))V|g8Bf*x^*W`vPScViUnttLmm5#@drWykZMeccOTL8{T>q;e;bV<+n0 zzo2vshp4h!gd{P?u#-E6vl+x3ldTQGQLw#F*Jwy7qT#xxMt20e&3b(l%oW>22@@uK z`D*!Gbj5tstcl7m%J&306xyvP=jcgi$OG>?efYl9ACmVgvn5u<$jvcLr;mEQ$`PgG zkb7q|5Mqxg-yixs>;P7~BTD-rH;#8-25GCL{OEl~K9^GQq;f=VJkGk_@;;y(J$+O@ z8(w*hau6hr1M48Vj&o86m9vMw2C2QCl~9%x`!vM7Ob|YS5o|^5>U2GvTJp?9H=Us) z3sWT15hQ1@faLxcOQ`5Vhjt)FzdlP4*z|&BDjM}HsOhQu^Z|@B(J<1$e}dmndo4lK zCn#SFtWgpAX$Uoc*7)g&Cj;TpGO{=hHA}pXBz_Ruu#-6s>^2v$7oQ=VzH8g7i;_O`!zM3@= zwM?|7td5b-fNV=D)4m0B9piJB7-gBv$~2mmDN4B9Jh2G^WMKvU8352djsQV~ucTQ@ zBc^}6!iI#c_QVN8qr;S@C0_4-co2h6@(aRh*53m#o(ZxFwG>XlOoNI?qC|d3lqij( zy_OMWG|C9Kfqb>xG+VIr2h40p8lw<2)#A9t=JLL|Xo+ThR5MW@6awft5QLP< zWKtCB^zIjNl>m$w*wI9G^o+)U`Hbpqq*#k^1l?i;YY!Chd=)k+OiHo>NlCfCUrKCJ zjG#all7d@cvpA}$C7q2bU`Vr)79#SH7m@u@sMMEu40i{HmNQl^Y zj(_kpW~QQbY9XVN4iAd?buHw7qs7MF-j^6TlP2&UdIDdj!+1AGQAOL%ArI&i3iTnI7v7ooBj5+8WF zQR&G6SZ}(-7a|Xyjw>F}HL?YXW3d+-I71xn%V(p@hpIj#pG_$bZ=qCzEf0AA0_@ya z3_0RiVs}R%yMwIP$Bv$kBc1;@?C?HCboq5E3vlb{<4oGA?2fa6g?fzNaA+5yOvg|Y z1mmFfEwdyVv5R0UH@O2Y7Rm21zpo`U7CLpP8kKN=PtUF%R0DfPb@iaidaxdYBHhnI z24}d^vs1%om9{et6Y8RFU*|Nb6CBmIY>LJFXe!}b z6XlLj!^YECD-8B$S;D9_Y}B&HzXC$;5G}cKhsHA4y}BO|aL5CsC`H6)w2}%_#o@&w zEzM~Vk0GL%+4Ld@08Hvkr65N$l|nN4GlgVhC*@K}AQ*)Uw5RDp9SDSkD1a(O^2;T| zcF2am{nwOmr0eNR(V|1L+#+{`7#y86hyQE3Ap1H98Mgm-_9F2p8gPOBW3z+G2#P5k zJq-4z*ih>X_J>&|%rZ;v2sb!%{}V1Pw1NI#7;ha2`P_o&;Q&!W(Vc7I&;q{H<)@xB zTILY4cziS}{Q@F}uN_{g_*VqVP5;;6jmM3R9hc%^`<&jAyOdy!$0Hj%A}=8xP%&;7 zp<=cTr4EkEq8g0GnwWB|9{^meu6{JpDv7uh{DBoo5^&5^j&~WXTD(2McCg+KOkxRF zWO{2jYC6=6mhO5f4Q0I-;Ig)IIzZ;V^p*yrSEg=}6ZUw|`Fgv9yQ>?x%5Lbb{*7>@ z-N2Q0L$5pex`XRZKIHEg>D!%r-N6OQNBJLKZABDd>7wB^4ZDr0h(e}irI~>jQKFjC zu-n0#ExfP%1d`Xj3cGHO#)d<3zxfenD5!{gy&uz#%)(v~Fd{Knx7hZJfK!8tIdmW! zvfGNig0oiM%d#g|=O}nT_K3W#))Zq0%ygEwn99n&^=-?!!R7yT-)pdptmNJYV zugH6K-gnvgv&^=75$-8hvgJiRo}^<($-N3e7!%y+zL7dnk=gwKB^^=0QtaKyq&Qd> z&)<7!WiZJ zxN|KTOn;96$oo$pz8~B)41`%;k4H(d#&}$`49$oW6|2ef5$gPS$q$Gv|BSd4wsa=^ z#7q@ms$*sH>Ru2YuVXcx_dY${`$U8Enz#o>;V<{?qrRSgb9_o*1$AdOV^;(*|4yf&^J5gYMxV5P1$oV)x!O*}i+otFG{*+21;S^|rn zJXi~{Q@NZ!Len)Dq>io7STZGhv5m8A_T5 z18|frH?L{xg7@8Z>fC?n1~JBmcr5H9MA)tqQs~@kxmN0&Xi~f9__?rFueA*;Weq3xd4(JXFeARqU;ivy5Z|? z7nl(^+B2cL-z-Z^bHNCo#MEFv6VQd1FVy1fgX2N0%4E&P$mS-ra*6hfK21Y$}S!$I5tg z7sCcS5|?pbB|7q+b^z(z`!O;Mz3UBcw_eA^AVmUP%HVhzdsPH!oRVS8QZN8?w`fAh z%|wT?pDy;y&U({&bcl`n#1ViRhdNtI((n+^9w^`BM@KmIFEH3&W{3034faG-5L=hR z->}>!(UR-C#n-uCq#F(F%@AngvG682pEIo|(o0PA>WW38p}`imLW5C2h}9(ggiy0C zTsbYbe7l=Y;L*A6I^}@aA+}Edduuqom*gQiNBV2=^p1VyQ4m;{q_m427Xp%{Fd`{n zmLl%Q+s=4NTKQUhr&co)IF)}#BwZIhq5M+RXQ99HILwd9hoD0@sJJ#J*?@wK(J#kH z8u5IKfr{l8-9BkJi!FGJ0md`H@PlT+7@1&hXrM6VZyv$ck@I=w@jI?^cyB)aFQpwD z#>TTD_zDi*n2q-fj_XB?Ohg`#ao7`~e7s|MK|z5ZUULa}y0~NTLBu?_P~`I2b-3sM z0w41|H{+43)RT%81_xcgDxZ<@aPsoKVYf6A5!@1sQNvf!TrAl`o!F!#z*%gF6mJcjE^8-DtWPG}^Nk?W&|Z>AN17qFry}xNoJGO~A!O zv&R=E#w-IEmbAby@fZ#@E({Z2V$xHzx-d-Gk6@~*z)p3gWSJtSH)xtc+s|u!H@rr7 z!)vXN7hjEi5qWj2Pd7TK4c2WPz3>9vHdBcAZa5Zqscm+ zsVLz9W-tm^{jxmw!_90bDJ6xnYfvcc{+|wKBrq*U3#_Db*V1d`970|erv>Y)5a`?s zPhjB8rbyp&CV!5&m>6sq2}rhCL8*5SXr&y`)=a#yQDqw(T-)mKCf2c(4lt$f|6z0@ zP?g*ZAwQ+E{H>Y>I+wzlu%7HjqjMW7kby1sAAIkZi9Nsa1$&Ng*0Wb}AU1=9D`9>G zzrIw3Tv&abW5+0v#J%T^L|)oXZbk(wQUtjYUBPv6e-IHG)E@u{CwxWiYx=Az! zOl#HW)OI0eHZX0?M7asy=A<9%=_ZIp?yEbUhocHnIi%JjMB1wBsTDc;B5d3*h70aB z0-l{n;?)Vg(#Ma0Wk;{YMynqgSqvmaJ;Tv6#NQKe3#-E|?789a21Eu4fg3MC;tNeb zJc+L~;R{Vq5HmKAOK`-8O;YmF(ojTe3>pR!vc`~y!w0cG!%jzH58_$xjw0IuntgG^ zdNtro$p=>(ry?O9x(9%4qiZ?2Ps9>Uw5EDbliu`YoSyQT-&cmBdC&1CWk1u7zL|sO zTX}OdHQP>_Dd~E{l!cGis!3`!;U~OX_navck9gw-8(+%FaT}Rz*zhIV zBJz*sYhcpKJX2Me;tYJs~Wosr4n?QrW_L-$f zn#`mpr)TZ0b}4of>FMueFRMzl){3vN9@`tN*ooz-{Au-J4Cd#$v3?wuU`Mb`y@Jg2 zcRk608Gp7vp|<&5Z7^~?IMNR!W=38&wt+$c!VH&}O%g5`syeh12CDE4xQY+0@@-$u zP@jW%YS;}b*v1*^=io_Ikm(M&iGmhuhoRvOa8^5^{Y121%zX!e4)^obxlF59Br}r&Jfuk~3`y^P2`@ykcEkrt7)d+y6XW52eRb}ST39%xW{5@f zR3c0R)oLVG!Z&OaNXdCvg%L_eM6$2s7#yiskd)_45|M~A?F26)O)z}hS)e8g+F=j| z8&c6%ej?!$l1WoB z(P`gIJzD!_f>rFB`NY7c+{Wv`HafTR<}bv9&#@Xd37Ya|3^qZ-mhLDxHCY$}Hq`T0 zXyd+#d>tvm20G&On?h4@9oQ1T37m(;nD7Z&7%|?#7#wA<5m4d=UG)H$T|Hve+8L>Q zploNYQ`Y7S>5KcIQDZ#e0*Ujn~J$wsWF?bdLjrF+kG8kB7f(4q7;weC$av#xYU4kIw zlUSgQi3LsQ6$^Y(k?v5niq3rv2>Ax#Y=|XXJ{t-+kexBZGad!@T?U5{l?=~5dNcf- z-f~ht*URAe8Bbo9d~T#+<9ax-B~?B*9B&Gq8)0y`2oQfuz$Di*KN0Dn$1X%$!Mj5~ zi~Fd*0b&<83*JAeFdFYswdM~xJaPip5{a*vTl!&M85|D6#LN*qcR-n4aB0DlLu#GN zuwXIEH#l+`cMYD-!c*oEJoFbl6A0?8RC$MGG0|K~G#7%VWe%1y%M|~zgD*$MTjCIE zX~dras-b~`133*0PmP^Cu8I3=BRyZj)!-cz|2;=gXR>+!x(gvp@IDJ=nMrZPceaOh z>n=oJWKTHc6Y*`01Ij+nQ$V5PI2^7de8_?T=JmR0^yCGrbyy|9K?jL1;S6PiCC1^`A)*|-MMm8q+os8A4Pk~<@IQ|pM}jI z=xi^#DY+2{G}8Q`q6-_zy2-N~9OzXqI-$oQxbh>u_IHFX>&f?5DyJtKcHaa)x6Y&? zGV<>`?^BMje!PW<#u(nXmm2Zak0XbU$L>|!8OnEZV|*>;;E|nlq+mTv-=mfBrD^4R z?_U{?*g-bm%RqGATFzH#=^}B4k?0aarRJ>-MEACk{6*NlB9TOT7b*V|hPQsB){Rp5 z7YBFXM2;3mox2~|HvrP9*(YfQ`I|5mrSd(xp)jg=Afj_0Cx2jn#2^}S#yRn#KzMn` zE=Cf(8uYg`x-<<^uemspf-1y^-i0(_ZE;Sc7n~%zIj3cB2|N-avUi|i+;!q~A{ZNq z&v=p2XOq(P1`oE%I=6|&hJ8g+k2E-v#wg*FeWAiE9Gn=-65)6_064~vA-u|Ac5Fe% z5Qms~V&9rcF(tmXHvp~wAon!v?g5KKyr@IR#ly;59Q7N;eMwaBz4S5;-K!hfi-ldP zNBdR(d$8(p7ptCwNed15mr;uF@)A}TafY*ZgJe)>qcM&|ilxy_5@T@6gqPpHZjc63 zLPN;~+UX!n@BV=e2EAHCFRMktfurbXi7$UzoMr$ANAryd7#^C+ovk z@OhBpeO)&g7*lp`ZlrT_tGU^)^hG>u-C>q30q$Gy*T-M_^b;(NG z!+1t9hEWV9S|*|u<1?9$7P4RD3k{!uAI1K#}=l=tY}UHQC-Q`7d$*tgjC zNhorJpv|(D-Et~k`Im@wA+O`y8ynC=MQ{n2O*z6A-9w)%Gz)c^}Q$c{@{Q`3xupALc%;p29q@%UBU zQak8Is;(0Vk)1Ot`^EQd=jjjQlt0;Jm(_jz`>yYip%Hoirpu)^@UL#iv0DNYyGxnO z2=Mwy)bmUxB%N%7@BzFRI>*7tk;g>-uJ5ry3A_~d@oUnhQbmaLegwg>69njakK6i~vi-aP^?$pf%%N5nD=Bxd=myT}^sWe$Iv?RDO^BNlStAO~_w zI6KkNSqOh9!b3Sd;5b9+paTso1^AmSLZxK6CgG>#^OfP2!OHjI(d4t0{gfj=tAD-a zNlOSFcQiGL{}ey_MgCTj|0VhVw9Atgx7?zY_puWZ@24t3%Mjv09De3!CQl=UWv3=j zvDYN2r@+}jXg@esL>fzThtFw6L6E1~?_&&-^e!tklZ1;PmW$RC>L z{|&@E-pHTYZ{npXg_nzXnX-|WwN1SIT?Z@I+efAf_&LxwmzDN^Y++^od+$*hoxM$y zhSI${b1apQZyvl7Wy+UZ;~z)4>B{CeK1P{z-H&(Z(}nfN$F6NkO&7Kub}UF*oi6O3 zxqM~gv+2U5yO*6<=t@tie|60jFZMUj{`T{aPYj-JzUQMEp&zubH~%!v)uI{ks`-_N zkE~5!^R@YIO-$S{XLQDBZQ}M*&(F=!*w3H1^N5mR>wDD=UtPZ^#UZc0y5ru-Ti>31%@!+Dn6(20x_idl4k1R=V-+ssBsryFFJ^5s4`P3!bUU~U? z`(LJ(?SE`r!k1r8-8lXo-MZU`O#4ILy0>}{xp~^2UyiN*Fvl@1zT&=!DZ6$}yRrCK zQPC^y)82nOqPE6mnx6Xepx=20-8%ixlV*ROFniPVelt$odShPm^aaZv{%%IandvKc zNX3tRdPA1^hrK6GcyhD)zx2Zux1D$(Ysl7BPg<||AnWd&f||lo?TjDK+~m>D8aJb~ zW8)Xiw-?WtaxS^|>%TlcW6HfJ$mBV{m!(RlUj~_yldoL zGxf>$)fb+9ZsybJ$4=X#KA(B_=2q+De;hFD)tyuOIkU26^@-?x^5Yk6vj#la=jp!> zd2QC>&W{fK?Q!p{2mj{ye0#>#vny}7{*$3w=FQGN*M9T2KiAD3^6;2@`+v4)_U&e= z;}89Roc(|ELS7RW54(Bgyrw>`HOp?^KP+kfpe>tk{{862XaD)e+cy_AE_}D=0O6LF z??&$4FeK@g=^U&v~{&nt>Q)h}JKW&>^cIS?|EANe(=brrXfFC}&dEVoZ%Rc?~mg;%$T)F1Z zvtPYF@1}mk^WI(c-Mrq1M^8Hc_o4Iq&XoT?DP{5eca0xDdY$g>`R6z9n-V|mt@)G2 z?^yK4*0b{qO#OEJ_*ndc_1_G1;^bFe{1;V!3Fhy+52PcjRzKdyL439D>I~p zcZ?P`U7eG>@U1lARN>B&g|El1ANTp8KQ0`Wv~Azf{f8HhPR(4?=UneajWHv#Y5Qetg zRdwq+Q~B5-{kPxx$cV)uw;w%uYt_#S(<^k>EdJw!8#ZsPo4>d|e8SH35%r6=v^CF} zwzYBbr0Cb&OSC^Np7EzIu1T^FUlKp{{rk2h<}4}yB=z;o%KMfqzNPQiga7%?lC@>i zKV0>$uyn!f&q8OMPg=V2#C@5^C;e_|^9xHyJXf%7sdezH;})!MUD`M2^U#&s!ft!E ze1^#^rQG)MC(B063$fhx_r5#VJQMoo+bS1)HDF%mKW}^S{@Ei(H}uP1{Kkp3RjV?y zOT9}Y|5(2+`}&v1fBWUYm$Sdi4!h%@$G*z`X8XGt3qHDH*{NF|beQsTI=!njf6#*K*w zz}SR%)J6#jqmmL6#*R%Ki{r1v(L%!LG4z={dK7y~OpG5J&#R*oM~{OX@w`8N?5HH5 zCyrxa3CW4^$%*5VM~{vl%UhC?;}a6bBqSt{8kaP73?n)=X>|PPaq***$0R3?8pkmq zzHrO`JvzIe`Or<#zMmNS@jY~vna1EByZ=XRx3M3wn;#BP#eOv1{7`>1$@2gIr)%94 zRbd+PaombJJ#Rog6r~2gn^31cF!kMwI#No3Qi(07(;Z<5ez&5I8%l=rIO=$TLO_-& z{2BGZC}|V%7u1KKBwD*rCqDGsj5^V#-?vaFz9IO18+C#uIv=1;eCdY$AE*;w94!l< zqfUJ37WZ4!i7#$`g!8BqU+Oc$Q;9FtO{lNTDJ(`+u$JX4&mSLMke9eB$Fd@teT*DA zGMsk^L(9V1uMiE`qr(N^TD2UCaxXs6#M}&-o|`52CVixmvD3Nqmabl9X71=Mt1eZqMQ!=GL+vV*|MS} zyftb+ih!S&Qm@UuVO1e9XFu(!g0SR=)_hC;8VfauZ8uP%X=64U*H&gJwUp$IB%y^H z+X_}%ib`_tV2_1)dqONtX%Dj23}}w~ZP%hAIhdt4i~8 z@;F;FvCU1$kEb7WF3UP$$)VB9&kAH!v=`RMDS|zS!r->tqLQ+FE@5I@X?|%bq~eNS z)#f*vpl`E|Br$~0w%nCW>HcI@Y6)p8D`m8#wz9R%eqn8@w{kjz{Ew{ninitX77Qit z9NJb^l%Kzf3vfLIfI5ttsV%Pr9N5D$sX4FYPHTyU4OwJc$tnyEBbLTYzmntUmj@`i zOxK)kDPaSmKT%SgzlM|{c?Y&7L(W{701tPp+DmEZ#F3*4@v%t)R%_vqpirIh!GobQ9VNO7qvO;__TW z5@22ALuG8E7?R8+SkAm-R6&8yJA@%^+4*Yu1tXCbIIirKx6sesYY)P2;)N=bnG+3Y zD+s{8iP)2SGp(*}TOHUrw#`yvU9~EoJO2vHi93o*?ka{L#9~xi&g~`YU=(r#B;4*d zrGZUTf*VbN#}^0+=LWc=&=;Yjlo9LEW?hxTM2IDD3N-YzEV4N}!0Lrdn4J^4Moc_Q zE0IRfVqFWt`qP8XOo*$XVLjnOPqx}1rsJ7@CbM=4?X)ThS|e}Qj^w2=x|OXi)Mxx= zWI<`pN*aITv{rTTkz%@9CCODl6JHWsw_7RbOFCCJLK2BC^=4IOa-kiKJRVSOHM#{Z zIZ186+DNOSAPnF#^zG4_?OW=yy13zxsj!%#<7D7xUS~uDN%dum<>jmYlFE8YYwn61 ztP?b$NuOR&)auzD9KC}><= zNTgi9M;S`7iW4v-Fb88PwHK=>{amdD)}kUGfk0V&5(t1M-oYk|pr*_Am}%T(5vEo? z*N8?$fKEa?xr89O3aR5>6)LZ!n0b7#CIlJrQ7el+gcKD-hfNGlN6@*{B|lJw{sR8o zE`Bc8>7%#`{S#XGv?m&Y!=iJk-V9?iPlx@~=^w~`puo=dgbh@zSYgLy=YP>YkVCwcGt!QNnH8outh4RNPlvS<%_tdk9nC_q%nMj>9`-0*U?+lWnzi>4{UA7c`Ace@59)r+dxQ0k*3%F!j2QMQt zTC*`_Cg)8qveN&L5Inaf7n>$LR7Bp>sxH5P5@&b0j+n&3{F6^d%Lu6`#;Yrje}cwy zFAJP({n-ZSf2&;;*KW|Ytx&EYufV5Vus@K)51P(F==1@PK@JUtSmLa2FDv{#TW{2$ zscYv8>II`@A_D)*Q3@$m^;qA^QfneVuFLR36gPaNi-q6_7t^3?jWW9Us^=Nh!1mR`aeHYa0TcM)Y( zqbL{4r38%j@2NG?wUP>{eKnQQ=a?{*DpIRCK$`2iq=D_JHO^uH2GhmIg0z z(IJPh(6ySC7S(a`T*dix3ZNQg_zH|xiPbOT#mUofj8j2T&T>`0;AMyG7H4hEzC9;T z?GH?Hw1MLo6mSL#LzxU}UK965E8Cb;TOfnLSQ{l=$51Xec~DdTxaL;2RU;aK1L0r! z)vyR(VF4kS_4WT1>(i}t((s>f4i{n==N>$mju_UOjXfzf1)2%VKQ#r494^FClR)bz zenyOGg;T3xFc|fsR3}^*MkpGuiaN73J1>#BR9t{>3hgYIn7VvZ$46UgCrw9Ah1YPb z!1Mvj64B%vj!7;>O_hg+zPL&mv+?7{?;}4=7IAbID zKE~L7C-p?QaHz9_B`wi>VntF*5uFtZy7oMtR}1aMRtlQ&*PZgyObO6Ciib%xz0h-= zL7Is8aJ#5M0%l)`KxKjpwk%xDXJp_^(KDkV#HdLNkxNX)X0;^m*MTW+Cq8y+wK}nb zhnI|!*v#3YDg=(9&XwoGaTTkz?j#vi$0fNCA4a$Y7KxMu7bq=Tk;8^1Xn@<H0wusMo(#$-@0d`)tmLb9fVTz148@AF>LT%iVayn+mA?X$3tl;8B}}5g zpaMobBFF%f*~7pf!Qw&#%+Exix$FuhYV~EdO;{NH>d0epULnDK7ph)OW4f zEBIm=I2ae`IB^i5ew9{G&e4}$|6TqrQazlRnA*y3!YJ+pE{@3Q)iYC_Z&S}wpul{m zzYs@g|H~b$G=e8U6Yn`i=M06#WptB8OoFozHX(UpGbxm|5CM>ZyA15)cy#qAL4-MJ z)G45-$#Y<0D)Ag&U`JAu_OUHoUSS!oWOLap4YW{1SIX42g$5gbpP+6Mb?t>^YnV&Rbl*Y0_6FGP!m}GYx23e3mp8q7YE4caOZ@_kscUD>i&K3f$g`o8 zo~PE7S=q^6AgG!u1!1JjI<+RxQo@kS16EP9D>sn{X1Q{csLXWbj-oQfl}p!0!Z@A| z4sn&S$2b==aFUD3x1!6oCKuZ|#k<(jxX_img00}QUDjN6`!&~9R=8YU8p{|@ES1D! zi3^vfYS`z)lKCj@t4eazu@~2L2`nz_;>n#woX7X$7)my#Q?EhHEAZ{f0$l+)9r07g zJb0Mdt@VFA%yge};Za-(AI_=MTlrlZaeksp5uX_KUHYu|?O6jw8z{sRrtkcD>MO1M z29w$XV^lB>8!SK08?Eem`rn~M32aC%+X6#WEt|2AV|RMQ<(@LlOG_EOenC`%re}LWX$ed7g?0a+I@eJ)%ptK z2nRW6^&rhpl*x;3^%EWGn5JvZqT&4=6Bs0SZ=aP~oSV(zI^ z{}$NCg3Y6O3kf)XxHy&<4r-s7q1vU!_T-e0xSi}&~mv^B1N(Xvp}T3Dv` z1_cP81JjIIIJK4D*S~-OuG!QiLnT!PQr*DuUPNG)5$OC_!5U5=@|UgQU0lP3@3pcw zf`~-0j_OIj$~vDMYSC4#MR|CAh=2o+>7u%Z!st6OR`K%ki!3=5A8##c#aS3LMWFAi zXO4aq3mNf11^Wy9k9}NjBv&u+7Y}XauOpGr!JNA;@YN;XpKu5BzmZTc+rU?H123M( z68~;4g%=mHo~{ewz+HWy1Oo;Bbq@8ceQ^clY6!|Cl@u_W198HI6zUdwsZuyvt^5r$ zY79iu@f&k>-tyVJ_@36^4jeS!!i7{~AY`g+AEoPhdK-=!gW0M{Heb4ZOL?#gX@n)j ztF+D%Ej0oKlJYJ8J{szUKrpFvMrsTvwPYeArLOLQw7O`pRNqHhe{p2#FPIt0%XLdN zsadkF)%UI%kue7hBQ_j-+o$(kWee)fwcxw@m3$YX+P+nq`@~IBeaI^ziujoLaF2T@ zSBrlTpV4eE+qI90wyf_oo70>jc6{Od=i^lxMdJ_^Q5LH;vS`=Z#VXOBW&?li;JPro zSdE_A9(AH}l|!t(twyZTI7K@kS(~Ot+{Ex|_0_s+(JtE1UL!ii29!>5Bb8EBNUgX< zs*`LYumO`^6Yj{U7ON)PbXA$Q-qjK@sTOO|<R|z z+A2CV8wv6&u~s8%t28#PO=A=7p*GPWiXl}Zlmvq2UsWNSL|M}SO=WRodVMIbZ9;96 zSf6SWWvNQD2{^T(b-C3cHn82I*%G>`|3(dQcckB)YMXm^AJ`(*HoFRKiWdK%>U3L( zGt@TK8D^VX2YK!lQ*Gu{`_L-w#^Kdcjrb=FOKqAX)Dij&?C_AdO;eldoKLK3#kw9f zkgf{<6quB(SGAz7N417*RxNG_0|G`7rmMQ*L6o&c7_*S3DfS+<>3z7mOG(^aeUfaXhwi9h61GQE4ki`vAefMZ^5 zMy>gI@nNlfdTrles1`uo4XBsk2+wBPb=!S#^I^E*)$lmd=K-kyoQ7n2K-`tOf$@LB z=$KV&#=mgt0Sr`rKW&3V@;(P@Po~*4uVmEDgzsKiEj0k*$&hMkgSa)MF2pWA1zBz4 zX3dt==TfVrCiE!cMt={q!$91dStZ@CsnOWfUK+Y;;MeP@v_VUn4+7SDVe@Ly0nCRpoT5W(OT(vV%XDaL z(>7?THTHBzYE@<(DA+^n=`fo}EsjO6iTlJyCAfH|U2=#T8|Dm6$VV}wGHtBo)_cis zwDx&5Vr@_REj4}ZX$>=K4EBtg5c{eII81|hf24i>{R8at8c=WOXTQ0jk9{`PH}$q> zZPMGPvwE0)%BCLn4Ado1(AY6ugluvFxVKmr?wAU<)7hrrGt@>ZNVYUP+h(z* zzwH+E^|8&uyyyh3dQiw=|3VrN z@qiP8aYM*LTu848twC9h$YFy)NCH{JM-?2^mQ`oK;9@ee&m44V>@#XY?ZfIq9tIh- zLRk8gwROye{wO{yz6>0N;V9yUIW^+5dfQaEB>pP%(+JEMKMmrkwjtzYb2S*sp@DT7 z&QwEYsgqn6h;GaxHZsC-SDzgaF9OEbS0{0+*derxxdD-Bj-#v<|9}Wj zy%;F)xKuCsIpIP$;F4`*#FmF7f(L%UXN?wAI0XJPE2<;NiB+f*7IC$sPBn_xc5uh) zLJpYrY$bEV)Xl3&;XjGWC|gA}<`rt=?IpZc7YB#E8Rn-}qkRR3tQ%_2a&Vk5d)lK5 z)%rktjtY6D-Tb7uNo$7%Nwzw*RQ}B~JO#r;k%PkrKUYau!g?rJ*jU-a&i{pO34Iv< zp6(I!Z&)?FnW7kZrdFPUYRH2kE!9From5ZGt)al`&|+Q#P=;Y_TE`4~M(uzq%%LrW z2vHae{H5AljlY&mL50@CIUBU5Fzg*wD3w(A;}+pd2Q zzYWVN4e7jlLx=(=BGoXQkljAT9)=`iy2DTxQjgXP+NJ6qYfz>T&BeAZxr z!L)~K)lr}oH^L%yXn6oj$QFe82Q*t~eu!JrY!ZbBl;PFlBY@eG?g&9Rg(Rdi;S%ph zL3^`_+@Vgqrx&%s*#_M=e}^a!A<6hJAVZPEP>2xCRHe#;yhEG}&xCQO;P>Xci5<-dFsKnFduCO7Rrwo`_I`#`W3*@O!V{Kc zvRMy%@dojpp7v??i1&oqOCLwiqtJ4*20HX0`8SDZGuwJ>7M~L3srKK4I8s3TG&;f_ z1CxC$Ff@qwVm!`2=fDE;9);EWNw+lm0I0T?1Ag=nBGE&W6&7@P@Z+Hkv5Jiyw4#5$ z#wz$KYoI1pMVvuAR@;3KW&Xe&3UFkLvWTRtD-EOY;t-#Nt!43EO_k(;S3!hd1X+s= zHWhK0mn7!cdStB3a^OQl*+7bBym-wviaMAik7*QYDQ3y$X5=}D*&9H(TCCUBQ%~Ur zxD}bAb1TM4d@fbdJfv|V<7*I~2jxG(hz+z_Lr%MRzw~GE89;$!nnOn%*@Qw%H4@hi zS{g8Nsf{o|101ZjFM7}dH)T%$Vk#0^^!`z7xRu zhiWkp_?>T$s$=PN9R`q~fE~eSCe0>ChP_A4BAeN!Lj=pR8EOE9RxAY$jm_+wZ0k?P z-G-K`^jgg5d;3;}a60#(?I{r-4y-&hf>j#Pfvt{Bq{)dfvxmr72Nd-&({>Hg2qpnx zJ_88G$R=(>+`bp%bq}5?9WXoL9la;#ivs{sbW5y(8rYLp{V zE6*B<=iThrv7EARNC^CU2!dcjsfC8+J{=_o_@_~r*i?7GCn*>r8vcdaScx_b>SSgSqlkHY +#include + +void +usage(void) +{ + fprint(2, "usage: fd2path path ...\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char buf[1024]; + int fd; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + if(argc == 0){ + if(fd2path(0, buf, sizeof buf) != -1) + fprint(2, "%s\n", buf); + }else for(; *argv; argv++){ + fd = open(*argv, OREAD); + if(fd != -1 && fd2path(fd, buf, sizeof buf) != -1) + fprint(2, "%s\n", buf); + close(fd); + } + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/idxtst.c b/sys/src/cmd/upas/fs/extra/idxtst.c new file mode 100644 index 000000000..919d25476 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/idxtst.c @@ -0,0 +1,58 @@ +#include +#include +#include + +static char *etab[] = { + "not found", + "does not exist", + "file is locked", + "exclusive lock", +}; + +static int +bad(int idx) +{ + char buf[ERRMAX]; + int i; + + rerrstr(buf, sizeof buf); + for(i = idx; i < nelem(etab); i++) + if(strstr(buf, etab[i])) + return 0; + return 1; +} + +static int +exopen(char *s) +{ + int i, fd; + + for(i = 0; i < 30; i++){ + if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(0)) + return fd; + if((fd = create(s, OWRITE|OEXCL, DMEXCL|0600)) >= 0 || bad(2)) + return fd; + sleep(1000); + } + werrstr("lock timeout"); + return -1; +} + +void +main(void) +{ + int fd; + Biobuf *b; + + fd = exopen("testingex"); + if(fd == -1) + sysfatal("exopen: %r"); + b = Bopen("testingex", OREAD); + if(b){ + free(b); + fprint(2, "print both opened at once\n"); + }else + fprint(2, "bopen: %r\n"); + close(fd); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/infotst.c b/sys/src/cmd/upas/fs/extra/infotst.c new file mode 100644 index 000000000..de7537a94 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/infotst.c @@ -0,0 +1,89 @@ +/* + * simulate the read patterns of external programs for testing + * info file. "infotest 511 512" simulates what ned does today. + * + * here's how the new info scheme was verified: + * + ramfs + s=/sys/src/cmd/upas + unmount /mail/fs + $s/fs/8.out -p + for(f in /mail/fs/mbox/*/info){ + for(i in `{seq 1 1026}) + $s/fs/infotst $i `{echo $i + 1 | hoc} > /tmp/$i < $f + for(i in /tmp/*) + cmp $i /tmp/1 + rm /tmp/* + } + + # now test for differences with old scheme under + # ideal reading conditions + for(f in /mail/fs/mbox/*/info){ + i = `{echo $f | sed 's:/mail/fs/mbox/([^/]+)/info:\1:g'} + $s/fs/infotst 2048 > /tmp/$i < $f + } + unmount /mail/fs + upas/fs -p + for(f in /mail/fs/mbox/*/info){ + i = `{echo $f | sed 's:/mail/fs/mbox/([^/]+)/info:\1:g'} + $s/fs/infotst 2048 > /tmp/$i.o < $f + } + for(i in /tmp/*.o) + cmp $i `{echo $i | sed 's:\.o$::g'} + rm /tmp/* + */ +#include +#include + +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 index 0000000000000000000000000000000000000000..538f648f30571ee6650bae490b30602ab040adfb GIT binary patch literal 62365 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 +#include +#include + +void +main(void) +{ + char *f[10], *s; + vlong sum; + Biobuf b; + + sum = 0; + Binit(&b, 0, OREAD); + + while(s = Brdstr(&b, '\n', 1)){ + if(getfields(s, f, nelem(f), 1, " ") > 2) + sum += strtoul(f[2], 0, 0); + free(s); + } + Bterm(&b); + print("%lld\n", sum); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/prflags.c b/sys/src/cmd/upas/fs/extra/prflags.c new file mode 100644 index 000000000..6a42045d1 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/prflags.c @@ -0,0 +1,37 @@ +#include "common.h" + +void +usage(void) +{ + fprint(2, "usage: prflags\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *f[Fields+1], buf[20], *s; + int n; + Biobuf b, o; + + ARGBEGIN{ + default: + usage(); + }ARGEND + if(argc) + usage(); + Binit(&b, 0, OREAD); + Binit(&o, 1, OWRITE); + + for(; s = Brdstr(&b, '\n', 1); free(s)){ + n = gettokens(s, f, nelem(f), " "); + if(n != Fields) + continue; + if(!strcmp(f[0], "-")) + continue; + Bprint(&o, "%s\n", flagbuf(buf, strtoul(f[1], 0, 16))); + } + Bterm(&b); + Bterm(&o); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/strtotmtst.c b/sys/src/cmd/upas/fs/extra/strtotmtst.c new file mode 100644 index 000000000..074e10e2c --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/strtotmtst.c @@ -0,0 +1,17 @@ +#include "strtotm.c" + +void +main(int argc, char **argv) +{ + Tm tm; + + ARGBEGIN{ + }ARGEND + + for(; *argv; argv++) + if(strtotm(*argv, &tm) >= 0) + print("%s", asctime(&tm)); + else + print("bad\n"); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/extra/tokens.c b/sys/src/cmd/upas/fs/extra/tokens.c new file mode 100644 index 000000000..6192f2b93 --- /dev/null +++ b/sys/src/cmd/upas/fs/extra/tokens.c @@ -0,0 +1,62 @@ +#include +#include + +/* unfortunately, tokenize insists on collapsing multiple seperators */ +static char qsep[] = " \t\r\n"; + +static char* +qtoken(char *s, char *sep) +{ + int quoting; + char *t; + + quoting = 0; + t = s; /* s is output string, t is input string */ + while(*t!='\0' && (quoting || utfrune(sep, *t)==nil)){ + if(*t != '\''){ + *s++ = *t++; + continue; + } + /* *t is a quote */ + if(!quoting){ + quoting = 1; + t++; + continue; + } + /* quoting and we're on a quote */ + if(t[1] != '\''){ + /* end of quoted section; absorb closing quote */ + t++; + quoting = 0; + continue; + } + /* doubled quote; fold one quote into two */ + t++; + *s++ = *t++; + } + if(*s != '\0'){ + *s = '\0'; + if(t == s) + t++; + } + return t; +} + +int +getmtokens(char *s, char **args, int maxargs, int multiflag) +{ + int i; + + for(i = 0; i < maxargs; i++){ + if(multiflag) + while(*s && utfrune(qsep, *s)) + s++; + else if(*s && utfrune(qsep, *s)) + s++; + if(*s == 0) + break; + args[i] = s; + s = qtoken(s, qsep); + } + return i; +} diff --git a/sys/src/cmd/upas/fs/fs.c b/sys/src/cmd/upas/fs/fs.c index fffb290aa..acfca8838 100644 --- a/sys/src/cmd/upas/fs/fs.c +++ b/sys/src/cmd/upas/fs/fs.c @@ -1,17 +1,10 @@ #include "common.h" -#include #include #include -#include +#include #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, ""); + 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, "

"); + 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(""); +} + +char* +sputc(char *p, char *e, int c) +{ + if(p < e - 1) + *p++ = c; + return p; +} + +char* +seappend(char *s, char *e, char *a, int n) +{ + int l; + + l = e - s - 1; + if(l < n) + n = l; + memcpy(s, a, n); + s += n; + *s = 0; + return s; } static int -fileinfo(Message *m, int t, char **pp) +fileinfo(Mailbox *mb, Message *m, int t, char **pp) { - char *p; - int len; + char *s, *e, *p; + int len, i; + static char buf[64 + 512]; - p = ""; - len = 0; + cacheidx(mb, m); + sanembmsg(mb, m); + p = nil; + len = -1; switch(t){ case Qbody: + cachebody(mb, m); p = m->body; - len = m->bend - m->body; + len = m->bend - p; break; case Qbcc: - if(m->bcc822){ - p = s_to_c(m->bcc822); - len = strlen(p); - } + p = m->bcc; break; case Qcc: - if(m->cc822){ - p = s_to_c(m->cc822); - len = strlen(p); - } + p = m->cc; break; case Qdisposition: switch(m->disposition){ @@ -285,142 +508,131 @@ 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; + char *s, *p, *e; + int i, n; + long off0; - s = s_new(); - len = 0; - for(i = 0; len < count && infofields[i] >= 0; i++){ - n = fileinfo(m, infofields[i], &p); - s = stringconvert(s, p, n); - s_append(s, "\n"); - p = s_to_c(s); - n = strlen(p); - if(off > 0){ - if(off >= n){ - off -= n; - continue; - } - p += off; + if(m->infolen > 0 && off >= m->infolen) + return 0; + off0 = off; + s = buf; + e = s + count; + for(i = 0; s < e; i++){ + if(i == nelem(infofields)){ + m->infolen = s - buf + off0; + break; + } + n = fileinfo(mb, m, infofields[i], &p); + if(off > n){ + off -= n + 1; + continue; + } + if(off){ n -= off; + p += off; off = 0; } - if(n > count - len) - n = count - len; - if(buf) - memmove(buf+len, p, n); - len += n; + if(s + n > e) + n = e - s; + memcpy(s, p, n); + s += n; + if(s < e) + *s++ = '\n'; } - s_free(s); - return len; + return s - buf; } static void mkstat(Dir *d, Mailbox *mb, Message *m, int t) { - char *p; + char *p, *e; d->uid = user; d->gid = user; @@ -491,13 +709,13 @@ 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; + putcache(f->mb, f->m); + break; + case Qinfo: + if(cnt > sizeof mbuf) + cnt = sizeof mbuf; + rhdr.count = readinfo(f->mb, f->m, (char*)mbuf, off, cnt); + break; + case Qrawunix: + if(f->mb->addfrom && Topmsg(f->mb, f->m)){ + cacheheaders(f->mb, f->m); + p = f->m->unixheader; + if(off < strlen(p)){ + rhdr.count = strlen(p + off); + memmove(mbuf, p + off, rhdr.count); + break; + } + off -= strlen(p); + putcache(f->mb, f->m); + } + default: + i = fileinfo(f->mb, f->m, t, &p); + output: + if(off < i){ + if(off + cnt > i) + cnt = i - off; + if(cnt > sizeof mbuf) + cnt = sizeof mbuf; + memmove(mbuf, p + off, cnt); + rhdr.count = cnt; + } + break; } + return nil; +} - if(FILE(f->qid.path) == Qinfo){ - rhdr.count = readinfo(f->m, (char*)mbuf, off, cnt); - return nil; - } +char* +modflags(Mailbox *mb, Message *m, char *p) +{ + char *err; + uchar f; - i = fileinfo(f->m, FILE(f->qid.path), &p); - if(off < i){ - if((off + cnt) > i) - cnt = i - off; - memmove(mbuf, p + off, cnt); - rhdr.count = cnt; + f = m->flags; + if(err = txflags(p, &f)) + return err; + if(f != m->flags){ + if(mb->modflags != nil) + mb->modflags(mb, m, f); + m->flags = f; + m->cstate |= Cidxstale; } return nil; } @@ -981,87 +1311,141 @@ 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) + memset(argvbuf, 0, sizeof argvbuf); + argc = tokenize(thdr.data, argv, nelem(argvbuf) - 1); + if(argc == 0) return Ebadctl; - if(thdr.data[thdr.count-1] == '\n') - thdr.data[thdr.count-1] = 0; - else - thdr.data[thdr.count] = 0; - n = tokenize(thdr.data, token, nelem(token)); - if(n == 0) - return Ebadctl; - if(strcmp(token[0], "open") == 0){ - file = s_new(); - switch(n){ - case 1: - err = Ebadctl; - break; - case 2: - mboxpath(token[1], getlog(), file, 0); - err = newmbox(s_to_c(file), nil, 0); - break; + 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(argv[0], "close") == 0){ + if(argc < 2) + return nil; + for(i = 1; i < argc; i++) + freembox(argv[i]); + return nil; + } + if(strcmp(argv[0], "delete") == 0){ + if(argc < 3) + return nil; + 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: - 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); + 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; } - s_free(file); - return err; + return 0; } - if(strcmp(token[0], "close") == 0){ - if(n < 2) - return nil; - freembox(token[1]); - return nil; - } - if(strcmp(token[0], "delete") == 0){ - if(n < 3) - return nil; - delmessages(n-1, &token[1]); - return nil; + if(strcmp(argv[0], "rename") == 0){ + v0 = argv0; + flags = 0; + ARGBEGIN{ + case 't': + flags |= Rtrunc; + break; + }ARGEND + argv0 = v0; + if(argc != 2) + return Ebadargs; + return mboxrename(argv[0], argv[1], flags); } return Ebadctl; case Qmboxctl: if(f->mb && f->mb->ctl){ - if(thdr.count == 0) + argc = tokenize(thdr.data, argv, nelem(argvbuf)); + if(argc == 0) return Ebadctl; - if(thdr.data[thdr.count-1] == '\n') - thdr.data[thdr.count-1] = 0; - else - thdr.data[thdr.count] = 0; - n = tokenize(thdr.data, token, nelem(token)); - if(n == 0) - return Ebadctl; - return (*f->mb->ctl)(f->mb, n, token); + return f->mb->ctl(f->mb, argc, argv); } + break; + case Qflags: + /* + * modifying flags on subparts is a little strange. + */ + if(!f->mb || !f->m) + break; + m = gettopmsg(f->mb, f->m); + err = modflags(f->mb, m, thdr.data); +// premature optimization? flags not written immediately. +// if(err == nil && f->m->cstate&Cidxstale) +// wridxfile(f->mb); /* syncmbox(f->mb, 1); */ + return err; } return Eperm; } -char * +char* rclunk(Fid *f) { Mailbox *mb; - f->busy = 0; +sanefid(f); + f->busy = 1; + /* coherence(); */ + f->fid = -1; f->open = 0; - if(f->mtop != nil){ + if(f->mtop){ qlock(f->mb); msgdecref(f->mb, f->mtop); qunlock(f->mb); } + if(f->m) + msgdecref(f->mb, gettopmsg(f->mb, f->m)); f->m = f->mtop = nil; mb = f->mb; if(mb != nil){ @@ -1071,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 uneaten) - s_nappend(s, uneaten, p-uneaten); - return s; -} - int readheader(Message *m, char *buf, int off, int cnt) { - char *p, *e; - int n, ns; - char *to = buf; - String *s; + char *s, *end, *se, *p, *e, *to; + int n, ns, salloc; + to = buf; p = m->header; e = m->hend; - s = nil; + s = emalloc(salloc = 2048); + end = s + salloc; - // copy in good headers + /* copy in good headers */ while(cnt > 0 && p < e){ n = hdrlen(p, e); + assert(n > 0); if(ignore(p)){ p += n; continue; } - - // rfc2047 processing - s = stringconvert(s, p, n); - ns = s_len(s); + if(n + 1 > salloc){ + s = erealloc(s, salloc = n + 1); + end = s + salloc; + } + se = rfc2047(s, end, p, n, 0); + ns = se - s; if(off > 0){ if(ns <= off){ off -= ns; @@ -1478,34 +1754,16 @@ readheader(Message *m, char *buf, int off, int cnt) } if(ns > cnt) ns = cnt; - memmove(to, s_to_c(s)+off, ns); + memmove(to, s + off, ns); to += ns; p += n; cnt -= ns; off = 0; } - - s_free(s); + free(s); return to - buf; } -int -headerlen(Message *m) -{ - char buf[1024]; - int i, n; - - if(m->hlen >= 0) - return m->hlen; - for(n = 0; ; n += i){ - i = readheader(m, buf, n, sizeof(buf)); - if(i <= 0) - break; - } - m->hlen = n; - return n; -} - QLock hashlock; uint @@ -1545,6 +1803,7 @@ henter(ulong ppath, char *name, Qid qid, Message *m, Mailbox *mb) int h; Hash *hp, **l; +//if(m)sanemsg(m); qlock(&hashlock); h = hash(ppath, name); for(l = &htab[h]; *l != nil; l = &(*l)->next){ @@ -1595,25 +1854,43 @@ hashmboxrefs(Mailbox *mb) int refs = 0; qlock(&hashlock); - for(h = 0; h < Hsize; h++){ + for(h = 0; h < Hsize; h++) for(hp = htab[h]; hp != nil; hp = hp->next) if(hp->mb == mb) refs++; - } qunlock(&hashlock); return refs; } +void +checkmboxrefs(void) +{ + int refs; + Mailbox *mb; + +// qlock(&mbllock); + for(mb = mbl; mb; mb = mb->next){ + qlock(mb); + refs = fidmboxrefs(mb) + 1; + if(refs != mb->refs){ + eprint("%s:%s ref mismatch got %d expected %d\n", mb->name, mb->path, refs, mb->refs); + abort(); + } + qunlock(mb); + } +// qunlock(&mbllock); +} + void post(char *name, char *envname, int srvfd) { - int fd; char buf[32]; + int fd; fd = create(name, OWRITE, 0600); if(fd < 0) error("post failed"); - sprint(buf, "%d",srvfd); + snprint(buf, sizeof buf, "%d", srvfd); if(write(fd, buf, strlen(buf)) != strlen(buf)) error("srv write"); close(fd); diff --git a/sys/src/cmd/upas/fs/header.c b/sys/src/cmd/upas/fs/header.c new file mode 100644 index 000000000..c6ffe8216 --- /dev/null +++ b/sys/src/cmd/upas/fs/header.c @@ -0,0 +1,176 @@ +#include "common.h" +#include +#include +#include "dat.h" + +int +hdrlen(char *p, char *e) +{ + char *ep; + + ep = p; + do { + ep = strchr(ep, '\n'); + if(ep == nil){ + ep = e; + break; + } + if(ep == p) + break; + if(ep - p == 1 && ep[-1] == '\r') + break; + ep++; + if(ep >= e){ + ep = e; + break; + } + } while(*ep == ' ' || *ep == '\t'); + return ep - p; +} + +/* rfc2047 non-ascii: =?charset?q?encoded-text?= */ +static int +tok(char **sp, char *se, char *token, int len) +{ + char charset[100], *s, *e, *x; + int l; + + if(len == 0) + return -1; + s = *sp; + e = token + len - 2; + token += 2; + + x = memchr(token, '?', e - token); + if(x == nil || (l = x - token) >= sizeof charset) + return -1; + memmove(charset, token, l); + charset[l] = 0; + + /* bail if it doesn't fit */ + token = x + 1; + if(e - token > se - s - 1) + return -1; + + if(cistrncmp(token, "b?", 2) == 0){ + token += 2; + len = dec64((uchar*)s, se - s - 1, token, e - token); + if(len == -1) + return -1; + s[len] = 0; + }else if(cistrncmp(token, "q?", 2) == 0){ + token += 2; + len = decquoted(s, token, e, 1); + if(len > 0 && s[len - 1] == '\n') + len--; + s[len] = 0; + }else + return -1; + + if(xtoutf(charset, &x, s, s + len) <= 0) + s += len; + else { + s = seprint(s, se, "%s", x); + free(x); + } + *sp = s; + return 0; +} + +char* +tokbegin(char *start, char *end) +{ + int quests; + + if(*--end != '=') + return nil; + if(*--end != '?') + return nil; + + quests = 0; + for(end--; end >= start; end--){ + switch(*end){ + case '=': + if(quests == 3 && *(end + 1) == '?') + return end; + break; + case '?': + ++quests; + break; + case ' ': + case '\t': + case '\n': + case '\r': + /* can't have white space in a token */ + return nil; + } + } + return nil; +} + +static char* +seappend822f(char *s, char *e, char *a, int n) +{ + int skip, c; + + skip = 0; + for(; n--; a++){ + c = *a; + if(skip && isspace(c)) + continue; + if(c == '\n'){ + c = ' '; + skip = 1; + }else{ + if(c < 0x20) + continue; + skip = 0; + } + s = sputc(s, e, c); + } + return s; +} + +static char* +seappend822(char *s, char *e, char *a, int n) +{ + int c; + + for(; n--; a++){ + c = *a; + if(c < 0x20 && c != '\n' && c != '\t') + continue; + s = sputc(s, e, c); + } + return s; +} + +/* convert a header line */ +char* +rfc2047(char *s, char *se, char *uneaten, int len, int fold) +{ + char *sp, *token, *p, *e; + char *(*f)(char*, char*, char*, int); + + f = seappend822; + if(fold) + f = seappend822f; + sp = s; + p = uneaten; + for(e = p + len; p < e; ){ + while(*p++ == '=' && (token = tokbegin(uneaten, p))){ + sp = f(sp, se, uneaten, token - uneaten); + if(tok(&sp, se, token, p - token) < 0) + sp = f(sp, se, token, p - token); + uneaten = p; + for(; p < e && isspace(*p);) + p++; + if(p + 2 < e && p[0] == '=' && p[1] == '?') + uneaten = p; /* paste */ + } + } + if(p > uneaten) + sp = f(sp, se, uneaten, e - uneaten); + *sp = 0; + return sp; +} diff --git a/sys/src/cmd/upas/fs/idx.c b/sys/src/cmd/upas/fs/idx.c new file mode 100644 index 000000000..eebe191c7 --- /dev/null +++ b/sys/src/cmd/upas/fs/idx.c @@ -0,0 +1,535 @@ +#include "common.h" +#include +#include "dat.h" + +#define idprint(...) if(iflag > 1) fprint(2, __VA_ARGS__); else {} +#define iprint(...) if(iflag) fprint(2, __VA_ARGS__); else {} + +static char *magic = "idx magic v7\n"; +static char *mbmagic = "genericv1"; +enum { + Idxfields = 21, + + Idxto = 30000, /* index timeout in ms */ + Idxstep = 300, /* sleep between tries */ +}; + +void +idxfree(Idx *i) +{ + if(i->str) + free(i->str); + else{ + free(i->digest); + free(i->ffrom); + free(i->from); + free(i->to); + free(i->cc); + free(i->bcc); + free(i->replyto); + free(i->messageid); + free(i->subject); + free(i->sender); + free(i->inreplyto); + free(i->idxaux); + } + memset(i, 0, sizeof *i); +} + +static char* +∂(char *x) +{ + if(x) + return x; + return ""; +} + +static int +pridxmsg(Biobuf *b, Idx *x) +{ + Bprint(b, "%#A %ux %D %lud ", x->digest, x->flags&~Frecent, x->fileid, x->lines); + Bprint(b, "%q %q %q %q %q ", ∂(x->ffrom), ∂(x->from), ∂(x->to), ∂(x->cc), ∂(x->bcc)); + Bprint(b, "%q %q %q %q %q ", ∂(x->replyto), ∂(x->messageid), ∂(x->subject), ∂(x->sender), ∂(x->inreplyto)); + Bprint(b, "%s %d %lud %lud ", rtab[x->type].s, x->disposition, x->size, x->rawbsize); + Bprint(b, "%lud %q %d\n", x->ibadchars, ∂(x->idxaux), x->nparts); + return 0; +} + +static int +pridx0(Biobuf *b, Mailbox *mb, Message *m, int l) +{ + for(; m; m = m->next){ + if(l == 0) + if(insurecache(mb, m) == -1) + continue; + if(pridxmsg(b, m)) + return -1; + if(m->part) + pridx0(b, mb, m->part, l + 1); + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + if(l == 0) + msgdecref(mb, m); + } + return 0; +} + +void +genericidxwrite(Biobuf *b, Mailbox*) +{ + Bprint(b, "%s\n", mbmagic); +} + +static int +pridx(Biobuf *b, Mailbox *mb) +{ + int i; + + Bprint(b, magic); + mb->idxwrite(b, mb); +// prrefs(b); + i = pridx0(b, mb, mb->root->part, 0); + return i; +} + +static char *eopen[] = { + "not found", + "does not exist", + "file is locked", + "file locked", + "exclusive lock", + 0, +}; + +static char *ecreate[] = { + "already exists", + "file is locked", + "file locked", + "exclusive lock", + 0, +}; + +static int +bad(char **t) +{ + char buf[ERRMAX]; + int i; + + rerrstr(buf, sizeof buf); + for(i = 0; t[i]; i++) + if(strstr(buf, t[i])) + return 0; + return 1; +} + +static int +forceexcl(int fd) +{ + int r; + Dir *d; + + d = dirfstat(fd); + if(d == nil) + return 0; /* ignore: assume file removed */ + if(d->mode & DMEXCL){ + free(d); + return 0; + } + d->mode |= DMEXCL; + d->qid.type |= QTEXCL; + r = dirfwstat(fd, d); + free(d); + if(r == -1) + return 0; /* ignore unwritable (e.g dump) */ + close(fd); + return -1; +} + +static int +exopen(char *s) +{ + int i, fd; + + for(i = 0; i < Idxto/Idxstep; i++){ + if((fd = open(s, OWRITE|OTRUNC)) >= 0 || bad(eopen)){ + if(fd != -1 && forceexcl(fd) == -1) + continue; + return fd; + } + if((fd = create(s, OWRITE|OEXCL, DMTMP|DMEXCL|0600)) >= 0 || bad(ecreate)) + return fd; + sleep(Idxstep); + } + werrstr("lock timeout"); + return -1; +} + +static Message* +findmessage(Mailbox *, Message *parent, int n) +{ + Message *m; + + for(m = parent->part; m; m = m->next) + if(!m->digest && n-- == 0) + return m; + return 0; +} + +static int +validmessage(Mailbox *mb, Message *m, int level) +{ + if(level){ + if(m->digest != 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + if(m->fileid != 0) + goto lose; + }else{ + if(m->digest == 0) + goto lose; + if(m->size == 0) + goto lose; + if(m->fileid <= 1000000ull<<8) + goto lose; + if(mtreefind(mb, m->digest)) + goto lose; + } + return 1; +lose: + eprint("invalid cache[%d] %#A size %ld %D\n", level, m->digest, m->size, m->fileid); + return 0; +} + +/* + * n.b.: we don't insure this is the index version we last read. + * + * we may overwrite changes. dualing deletes should sync eventually. + * mboxsync should complain about missing messages but + * mutable information (which is not in the email itself) + * may be lost. + */ +int +wridxfile(Mailbox *mb) +{ + char buf[Pathlen + 4]; + int r, fd; + Biobuf b; + Dir *d; + + assert(semacquire(&mb->idxsem, 0) != -1); + snprint(buf, sizeof buf, "%s.idx", mb->path); + iprint("wridxfile %s\n", buf); + if((fd = exopen(buf)) == -1){ + rerrstr(buf, sizeof buf); + if(strcmp(buf, "no creates") != 0) + if(strstr(buf, "file system read only") == 0) + eprint("wridxfile: %r\n"); + semrelease(&mb->idxsem, 1); + return -1; + } + seek(fd, 0, 0); + Binit(&b, fd, OWRITE); + r = pridx(&b, mb); + Bterm(&b); + d = dirfstat(fd); + if(d == 0) + sysfatal("dirfstat: %r"); + mb->qid = d->qid; + free(d); + close(fd); + semrelease(&mb->idxsem, 1); + return r; +} + +static int +nibble(int c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c < 0x20) + c += 0x20; + if(c >= 'a' && c <= 'f') + return c - 'a'+10; + return 0xff; +} + +static uchar* +hackdigest(char *s) +{ + uchar t[SHA1dlen]; + int i; + + if(strcmp(s, "-") == 0) + return 0; + if(strlen(s) != 2*SHA1dlen){ + eprint("bad digest %s\n", s); + return 0; + } + for(i = 0; i < SHA1dlen; i++) + t[i] = nibble(s[2*i])<<4 | nibble(s[2*i + 1]); + memmove(s, t, SHA1dlen); + return (uchar*)s; +} + +static uvlong +rdfileid(char *s, int level) +{ + char *p; + uvlong uv; + + uv = strtoul(s, &p, 0); + if((level == 0 && uv < 1000000) || *p != '.') + return 0; + return uv<<8 | strtoul(p + 1, 0, 10); +} + +static char* +∫(char *x) +{ + if(x && *x) + return x; + return nil; +} + +/* + * strategy: use top-level avl tree to merge index with + * our ideas about the mailbox. new or old messages + * with corrupt index entries are marked Dead. they + * will be cleared out of the mailbox and are kept out + * of the index. when messages are marked Dead, a + * reread of the mailbox is forced. + * + * side note. if we get a new message while we are + * running it is added to the list in order but m->id + * looks out-of-order. this is because m->id must + * increase monotonicly. a new instance of the fs + * will result in a different ordering. + */ + +static int +rdidx(Biobuf *b, Mailbox *mb, Message *parent, int npart, int level, int doplumb) +{ + char *f[Idxfields + 1], *s; + uchar *digest; + int n, flags, nparts, good, bad, redux; + Message *m, **ll, *l; + + bad = good = redux = 0; + ll = &parent->part; + nparts = npart; + for(; npart != 0 && (s = Brdstr(b, '\n', 1)); npart--){ + m = 0; + digest = 0; + n = tokenize(s, f, nelem(f)); + if(n != Idxfields){ +dead: + eprint("bad index %#A %d %d n=%d\n", digest, level, npart, n); + bad++; + free(s); + if(level) + return -1; + if(m) + m->deleted = Dead; + continue; + } + digest = hackdigest(f[0]); + if(digest == 0 ^ level != 0) + goto dead; + if(level == 0) + m = mtreefind(mb, digest); + else + m = findmessage(mb, parent, nparts - npart); + if(m){ + /* + * read in mutable information. + * currently this is only flags + */ + redux++; + if(level == 0) + m->deleted &= ~Dmark; + if(m->nparts) + if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1) + goto dead; + ll = &m->next; + idprint("%d seen before %d... %.2ux", level, m->id, m->cstate); + flags = m->flags; + m->flags |= strtoul(f[1], 0, 16); + if(flags != m->flags) + m->cstate |= Cidxstale; + m->cstate |= Cidx; + idprint("→%.2ux\n", m->cstate); + free(s); + // s = 0; + continue; + } + m = newmessage(parent); + idprint("%d new %d %#A\n", level, m->id, digest); + m->digest = digest; + m->flags = strtoul(f[1], 0, 16); + m->fileid = rdfileid(f[2], level); + m->lines = atoi(f[3]); + m->ffrom = ∫(f[4]); + m->from = ∫(f[5]); + m->to = ∫(f[6]); + m->cc = ∫(f[7]); + m->bcc = ∫(f[8]); + m->replyto = ∫(f[9]); + m->messageid = ∫(f[10]); + m->subject = ∫(f[11]); + m->sender = ∫(f[12]); + m->inreplyto = ∫(f[13]); + m->type = newrefs(f[14]); + m->disposition = atoi(f[15]); + m->size = strtoul(f[16], 0, 0); + m->rawbsize = strtoul(f[17], 0, 0); + m->ibadchars = strtoul(f[18], 0, 0); + m->idxaux = ∫(f[19]); + m->nparts = strtoul(f[20], 0, 0); + m->cstate &= ~Cidxstale; + m->cstate |= Cidx; + m->str = s; + s = 0; + if(!validmessage(mb, m, level)) + goto dead; + if(level == 0){ + mtreeadd(mb, m); + m->inmbox = 1; + } + cachehash(mb, m); /* hokey */ + l = *ll; + *ll = m; + ll = &m->next; + *ll = l; + good++; + + if(m->nparts) + if(rdidx(b, mb, m, m->nparts, level + 1, 0) == -1) + goto dead; + if(doplumb && level == 0) + mailplumb(mb, m, 0); + } + if(level == 0 && bad + redux > 0) + iprint("idx: %d %d %d\n", good, bad, redux); + if(bad) + return -1; + return 0; +} + +/* bug: should check time. */ +static int +qidcmp(int fd, Qid *q) +{ + int r; + Dir *d; + Qid q0; + + d = dirfstat(fd); + if(!d) + sysfatal("dirfstat: %r"); + r = 1; + if(d->qid.path == q->path) + if(d->qid.vers == q->vers) + r = 0; + q0 = *q; + *q = d->qid; + free(d); + if(q0.path != 0 && r) + iprint("qidcmp ... index changed [%ld .. %ld]\n", q0.vers, q->vers); + return r; +} + +static int +verscmp(Biobuf *b, Mailbox *mb) +{ + char *s; + int n; + + n = -1; + if(s = Brdstr(b, '\n', 0)) + n = strcmp(s, magic); + free(s); + if(n) + return -1; + n = -1; + if(s = Brdstr(b, '\n', 0)) + n = mb->idxread(s, mb); + free(s); + return n; +} + +int +genericidxread(char *s, Mailbox*) +{ + return strcmp(s, mbmagic); +} + +void +genericidxinvalid(Mailbox *mb) +{ + if(mb->d) + memset(&mb->d->qid, 0, sizeof mb->d->qid); + mb->waketime = time(0); +} + +void +mark(Mailbox *mb) +{ + Message *m; + + for(m = mb->root->part; m != nil; m = m->next) + m->deleted |= Dmark; +} + +int +unmark(Mailbox *mb) +{ + int i; + Message *m; + + i = 0; + for(m = mb->root->part; m != nil; m = m->next) + if(m->deleted & Dmark){ + i++; + m->deleted &= ~Dmark; /* let mailbox scan figure this out. BOTCH?? */ + } + return i; +} + +int +rdidxfile0(Mailbox *mb, int doplumb) +{ + char buf[Pathlen + 4]; + int r, v; + Biobuf *b; + + snprint(buf, sizeof buf, "%s.idx", mb->path); + b = Bopen(buf, OREAD); + if(b == nil) + return -2; + if(qidcmp(Bfildes(b), &mb->qid) == 0) + r = 0; + else if(verscmp(b, mb) == -1) + r = -1; + else{ + mark(mb); + r = rdidx(b, mb, mb->root, -1, 0, doplumb); + v = unmark(mb); + if(r == 0 && v > 0) + r = -1; + } + Bterm(b); + return r; +} + +int +rdidxfile(Mailbox *mb, int doplumb) +{ + int r; + + assert(semacquire(&mb->idxsem, 0) > 0); + r = rdidxfile0(mb, doplumb); + if(r == -1 && mb->idxinvalid) + mb->idxinvalid(mb); + semrelease(&mb->idxsem, 1); + return r; +} diff --git a/sys/src/cmd/upas/fs/imap.c b/sys/src/cmd/upas/fs/imap.c new file mode 100644 index 000000000..4a3f74467 --- /dev/null +++ b/sys/src/cmd/upas/fs/imap.c @@ -0,0 +1,1271 @@ +/* + * todo: + * 1. sync with imap server's flags + * 2. better algorithm for avoiding downloading message list. + * 3. get sender — eating envelope is lots of work! + */ +#include "common.h" +#include +#include +#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<aux; + e = buf + sizeof buf; + p = buf; + f = flags & ~Frecent; + for(i = 0; i < Nflags; i++) + if(f & 1< 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", iimapuid: 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", iimapuid: 0); + ndel++; + logmsg(*ll, "deleted"); + markdel(mb, *ll, doplumb); + ll = &(*ll)->next; + }else{ + //logmsg(*ll, "duplicate %s", d[i].name); + i++; + ll = &(*ll)->next; + } + } + + *new = nnew; + return nil; +} + +static void +imap4delete(Mailbox *mb, Message *m) +{ + Imap *imap; + + imap = mb->aux; + if((ulong)(m->imapuid>>32) == imap->validity){ + imap4cmd(imap, "uid store %lud +flags (\\Deleted)", (ulong)m->imapuid); + imap4resp(imap); + imap4cmd(imap, "expunge"); + imap4resp(imap); +// if(!isokay(imap4resp(imap)) +// return -1; + } + m->inmbox = 0; +} + +static char* +imap4sync(Mailbox *mb, int doplumb, int *new) +{ + char *err; + Imap *imap; + + imap = mb->aux; + if(err = imap4dial(imap)) + goto out; + if((err = imap4read(imap, mb, doplumb, new)) == nil) + mb->d->atime = mb->d->mtime = time(0); +out: + mb->waketime = time(0) + imap->refreshtime; + return err; +} + +static char* +imap4ctl(Mailbox *mb, int argc, char **argv) +{ + char *a, *b; + Imap *imap; + + imap = mb->aux; + if(argc < 1) + return Eimap4ctl; + + if(argc == 1 && strcmp(argv[0], "debug") == 0){ + imap->flags ^= Fdebug; + return nil; + } + if(strcmp(argv[0], "thumbprint") == 0){ + if(imap->thumb){ + freeThumbprints(imap->thumb); + imap->thumb = 0; + } + a = "/sys/lib/tls/mail"; + b = "/sys/lib/tls/mail.exclude"; + switch(argc){ + default: + return Eimap4ctl; + case 4: + b = argv[2]; + case 3: + a = argv[1]; + case 2: + break; + } + imap->thumb = initThumbprints(a, b); + return nil; + } + if(argc == 2 && strcmp(argv[0], "uid") == 0){ + uvlong l; + Message *m; + + for(m = mb->root->part; m; m = m->next) + if(strcmp(argv[1], m->name) == 0){ + l = strtoull(m->idxaux, 0, 0); + fprint(2, "uid %s %lud %lud %lud %lud\n", m->name, (ulong)(l>>32), (ulong)l, + (ulong)(m->imapuid>>32), (ulong)m->imapuid); + } + return nil; + } + if(strcmp(argv[0], "refresh") == 0) + switch(argc){ + case 1: + imap->refreshtime = 60; + return nil; + case 2: + imap->refreshtime = atoi(argv[1]); + return nil; + } + + return Eimap4ctl; +} + +static void +imap4close(Mailbox *mb) +{ + Imap *imap; + + imap = mb->aux; + imap4disconnect(imap); + free(imap->f); + free(imap); +} + +static char* +mkmbox(Imap *imap, char *p, char *e) +{ + p = seprint(p, e, "%s/box/%s/imap.%s", MAILROOT, getlog(), imap->host); + if(imap->user && strcmp(imap->user, getlog())) + p = seprint(p, e, ".%s", imap->user); + if(cistrcmp(imap->mbox, "inbox")) + p = seprint(p, e, ".%s", imap->mbox); + return p; +} + +static char* +findmbox(char *p) +{ + char *f[10], path[Pathlen]; + int nf; + + snprint(path, sizeof path, "%s", p); + nf = getfields(path, f, 5, 0, "/"); + if(nf < 3) + return nil; + return f[nf - 1]; +} + +static char* +imap4rename(Mailbox *mb, char *p2, int) +{ + char *r, *new; + Imap *imap; + + imap = mb->aux; + new = findmbox(p2); + idprint(imap, "rename %s %s\n", imap->mbox, new); + imap4cmd(imap, "rename %s %s", imap->mbox, new); + r = imap4resp(imap); + if(!isokay(r)) + return r; + free(imap->mbox); + imap->mbox = smprint("%s", new); + mkmbox(imap, mb->path, mb->path + sizeof mb->path); + return 0; +} + +/* + * incomplete; when we say remove we want to get subfolders, too. + * so we need to to a list, and recursivly nuke folders. + */ +static char* +imap4remove(Mailbox *mb, int flags) +{ + char *r; + Imap *imap; + + imap = mb->aux; + idprint(imap, "remove %s\n", imap->mbox); + imap4cmd(imap, "delete %s", imap->mbox); + r = imap4resp(imap); + if(!isokay(r)) + return r; + if(flags & Rtrunc){ + imap4cmd(imap, "create %s", imap->mbox); + r = imap4resp(imap); + if(!isokay(r)) + return r; + } + return 0; +} + +char* +imap4mbox(Mailbox *mb, char *path) +{ + char *f[10]; + uchar flags; + int nf; + Imap *imap; + + fmtinstall('Z', Zfmt); + fmtinstall('U', Ufmt); + if(strncmp(path, "/imap/", 6) == 0) + flags = 0; + else if(strncmp(path, "/imaps/", 7) == 0) + flags = Fssl; + else + return Enotme; + + path = strdup(path); + if(path == nil) + return "out of memory"; + + nf = getfields(path, f, 5, 0, "/"); + if(nf < 3){ + free(path); + return "bad imap path syntax /imap[s]/system[/user[/mailbox]]"; + } + + imap = emalloc(sizeof *imap); + imap->fd = -1; + imap->freep = path; + imap->flags = flags; + imap->host = f[2]; + if(strstr(imap->host, "gmail.com")) + imap->flags |= Fgmail; + imap->refreshtime = 60; + if(nf < 4) + imap->user = nil; + else + imap->user = f[3]; + if(nf < 5) + imap->mbox = strdup("inbox"); + else + imap->mbox = strdup(f[4]); + mkmbox(imap, mb->path, mb->path + sizeof mb->path); + if(imap->flags & Fssl) + imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); + + mb->aux = imap; + mb->sync = imap4sync; + mb->close = imap4close; + mb->ctl = imap4ctl; + mb->fetch = imap4fetch; + mb->delete = imap4delete; + mb->rename = imap4rename; +// mb->remove = imap4remove; + mb->modflags = imap4modflags; + mb->d = emalloc(sizeof *mb->d); + mb->addfrom = 1; + return nil; +} diff --git a/sys/src/cmd/upas/fs/imap4.c b/sys/src/cmd/upas/fs/imap4.c deleted file mode 100644 index 06e8fdd84..000000000 --- a/sys/src/cmd/upas/fs/imap4.c +++ /dev/null @@ -1,878 +0,0 @@ -#include "common.h" -#include -#include -#include -#include -#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; idata == 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; inuid; i++){ - ignore = 0; - while(*l != nil){ - if((*l)->imapuid == imap->uid[i]){ - ignore = 1; - l = &(*l)->next; - break; - }else{ - // old mail, we don't have it anymore - if(doplumb) - mailplumb(mb, *l, 1); - (*l)->inmbox = 0; - (*l)->deleted = 1; - l = &(*l)->next; - } - } - if(ignore) - continue; - - // new message - m = newmessage(mb->root); - m->mallocd = 1; - m->inmbox = 1; - m->imapuid = imap->uid[i]; - - // add to chain, will download soon - *l = m; - l = &m->next; - } - - // whatever is left at the end of the chain is gone - while(*l != nil){ - if(doplumb) - mailplumb(mb, *l, 1); - (*l)->inmbox = 0; - (*l)->deleted = 1; - l = &(*l)->next; - } - - // download new messages - t = imap->tag; - if(pipeline) - switch(rfork(RFPROC|RFMEM)){ - case -1: - sysfatal("rfork: %r"); - default: - break; - case 0: - for(m = mb->root->part; m != nil; m = m->next){ - if(m->start != nil) - continue; - if(imap->debug) - fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n", - t, (ulong)m->imapuid); - Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n", - t++, (ulong)m->imapuid); - } - Bflush(&imap->bout); - _exits(nil); - } - - nnew = 0; - for(m=mb->root->part; m!=nil; m=next){ - next = m->next; - if(m->start != nil) - continue; - - if(!pipeline){ - Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n", - (ulong)imap->tag, (ulong)m->imapuid); - Bflush(&imap->bout); - } - - if(s = imap4fetch(mb, m)){ - // message disappeared? unchain - fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s); - delmessage(mb, m); - mb->root->subname--; - continue; - } - nnew++; - if(doplumb) - mailplumb(mb, m, 0); - } - if(pipeline) - waitpid(); - - if(nnew || mb->vers == 0){ - mb->vers++; - henter(PATH(0, Qtop), mb->name, - (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); - } - return nil; -} - -// -// sync mailbox -// -static void -imap4purge(Imap *imap, Mailbox *mb) -{ - int ndel; - Message *m, *next; - - ndel = 0; - for(m=mb->root->part; m!=nil; m=next){ - next = m->next; - if(m->deleted && m->refs==0){ - if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){ - imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid); - if(isokay(imap4resp(imap))){ - ndel++; - delmessage(mb, m); - } - }else - delmessage(mb, m); - } - } - - if(ndel){ - imap4cmd(imap, "EXPUNGE"); - imap4resp(imap); - } -} - -// -// connect to imap4 server, sync mailbox -// -static char* -imap4sync(Mailbox *mb, int doplumb) -{ - char *err; - Imap *imap; - - imap = mb->aux; - - if(err = imap4dial(imap)){ - mb->waketime = time(0) + imap->refreshtime; - return err; - } - - if((err = imap4read(imap, mb, doplumb)) == nil){ - imap4purge(imap, mb); - mb->d->atime = mb->d->mtime = time(0); - } - /* - * don't hang up; leave connection open for next time. - */ - // imap4hangup(imap); - mb->waketime = time(0) + imap->refreshtime; - return err; -} - -static char Eimap4ctl[] = "bad imap4 control message"; - -static char* -imap4ctl(Mailbox *mb, int argc, char **argv) -{ - int n; - Imap *imap; - - imap = mb->aux; - if(argc < 1) - return Eimap4ctl; - - if(argc==1 && strcmp(argv[0], "debug")==0){ - imap->debug = 1; - return nil; - } - - if(argc==1 && strcmp(argv[0], "nodebug")==0){ - imap->debug = 0; - return nil; - } - - if(argc==1 && strcmp(argv[0], "thumbprint")==0){ - if(imap->thumb) - freeThumbprints(imap->thumb); - imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); - } - if(strcmp(argv[0], "refresh")==0){ - if(argc==1){ - imap->refreshtime = 60; - return nil; - } - if(argc==2){ - n = atoi(argv[1]); - if(n < 15) - return Eimap4ctl; - imap->refreshtime = n; - return nil; - } - } - - return Eimap4ctl; -} - -// -// free extra memory associated with mb -// -static void -imap4close(Mailbox *mb) -{ - Imap *imap; - - imap = mb->aux; - free(imap->freep); - free(imap->base); - free(imap->uid); - if(imap->fd >= 0) - close(imap->fd); - free(imap); -} - -// -// open mailboxes of the form /imap/host/user -// -char* -imap4mbox(Mailbox *mb, char *path) -{ - char *f[10]; - int mustssl, nf; - Imap *imap; - - quotefmtinstall(); - fmtinstall('Z', doublequote); - if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0) - return Enotme; - mustssl = (strncmp(path, "/imaps/", 7) == 0); - - path = strdup(path); - if(path == nil) - return "out of memory"; - - nf = getfields(path, f, 5, 0, "/"); - if(nf < 3){ - free(path); - return "bad imap path syntax /imap[s]/system[/user[/mailbox]]"; - } - - imap = emalloc(sizeof(*imap)); - imap->fd = -1; - imap->debug = debug; - imap->freep = path; - imap->mustssl = mustssl; - imap->host = f[2]; - if(nf < 4) - imap->user = nil; - else - imap->user = f[3]; - if(nf < 5) - imap->mbox = "Inbox"; - else - imap->mbox = f[4]; - imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); - - mb->aux = imap; - mb->sync = imap4sync; - mb->close = imap4close; - mb->ctl = imap4ctl; - mb->d = emalloc(sizeof(*mb->d)); - //mb->fetch = imap4fetch; - - return nil; -} - -// -// Formatter for %" -// Use double quotes to protect white space, frogs, \ and " -// -enum -{ - Qok = 0, - Qquote, - Qbackslash, -}; - -static int -needtoquote(Rune r) -{ - if(r >= Runeself) - return Qquote; - if(r <= ' ') - return Qquote; - if(r=='\\' || r=='"') - return Qbackslash; - return Qok; -} - -int -doublequote(Fmt *f) -{ - char *s, *t; - int w, quotes; - Rune r; - - s = va_arg(f->args, char*); - if(s == nil || *s == '\0') - return fmtstrcpy(f, "\"\""); - - quotes = 0; - for(t=s; *t; t+=w){ - w = chartorune(&r, t); - quotes |= needtoquote(r); - } - if(quotes == 0) - return fmtstrcpy(f, s); - - fmtrune(f, '"'); - for(t=s; *t; t+=w){ - w = chartorune(&r, t); - if(needtoquote(r) == Qbackslash) - fmtrune(f, '\\'); - fmtrune(f, r); - } - return fmtrune(f, '"'); -} diff --git a/sys/src/cmd/upas/fs/mbox.c b/sys/src/cmd/upas/fs/mbox.c index 3cd14e6cd..3c2c1fb53 100644 --- a/sys/src/cmd/upas/fs/mbox.c +++ b/sys/src/cmd/upas/fs/mbox.c @@ -5,104 +5,188 @@ #include "dat.h" typedef struct Header Header; - struct Header { - char *type; - void (*f)(Message*, Header*, char*); - int len; + char *type; + uintptr offset; + char *(*f)(Message*, Header*, char*, char*); + int len; + int str; }; /* headers */ -static void ctype(Message*, Header*, char*); -static void cencoding(Message*, Header*, char*); -static void cdisposition(Message*, Header*, char*); -static void date822(Message*, Header*, char*); -static void from822(Message*, Header*, char*); -static void to822(Message*, Header*, char*); -static void sender822(Message*, Header*, char*); -static void replyto822(Message*, Header*, char*); -static void subject822(Message*, Header*, char*); -static void inreplyto822(Message*, Header*, char*); -static void cc822(Message*, Header*, char*); -static void bcc822(Message*, Header*, char*); -static void messageid822(Message*, Header*, char*); -static void mimeversion(Message*, Header*, char*); -static void nullsqueeze(Message*); -enum -{ - Mhead= 11, /* offset of first mime header */ -}; - -Header head[] = -{ - { "date:", date822, }, - { "from:", from822, }, - { "to:", to822, }, - { "sender:", sender822, }, - { "reply-to:", replyto822, }, - { "subject:", subject822, }, - { "cc:", cc822, }, - { "bcc:", bcc822, }, - { "in-reply-to:", inreplyto822, }, - { "mime-version:", mimeversion, }, - { "message-id:", messageid822, }, - -[Mhead] { "content-type:", ctype, }, - { "content-transfer-encoding:", cencoding, }, - { "content-disposition:", cdisposition, }, - { 0, }, -}; - -static void fatal(char *fmt, ...); -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"; +static char *ctype(Message*, Header*, char*, char*); +static char *cencoding(Message*, Header*, char*, char*); +static char *cdisposition(Message*, Header*, char*, char*); +static char *from822(Message*, Header*, char*, char*); +static char *replace822(Message*, Header*, char*, char*); +static char *concat822(Message*, Header*, char*, char*); +static char *copy822(Message*, Header*, char*, char*); +static char *ref822(Message*, Header*, char*, char*); enum { - Chunksize = 1024, + Mhead = 11, /* offset of first mime header */ }; -Mailboxinit *boxinit[] = { +#define O(x) offsetof(Message, x) +static Header head[] = +{ + "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, +}; + +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); } + strncpy(mb->name, p, sizeof mb->name - 1); + mb->idxread = genericidxread; + mb->idxwrite = genericidxwrite; + mb->idxinvalid = genericidxinvalid; - rv = nil; - // check for a mailbox type - for(i=0; inext){ + 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); +} - for(h = head; h->type != nil; h++) - h->len = strlen(h->type); + +char* +removembox(char *name, int flags) +{ + int found; + Mailbox **l, *mb; + + found = 0; + qlock(&mbllock); + for(l=&mbl; *l != nil; l=&(*l)->next) + if(strcmp(name, (*l)->path) == 0){ + mb = *l; + *l = mb->next; + mb->flags |= ORCLOSE; + mb->rmflags = flags; + mboxdecref(mb); + found = 1; + break; + } + hfree(PATH(0, Qtop), name); + qunlock(&mbllock); + + if(found == 0) + return "maibox not found"; + return 0; } /* - * parse a Unix style header + * look for the date in the first Received: line. + * it's likely to be the right time zone (it's + * the local system) and in a convenient format. */ -void -parseunix(Message *m) +static int +rxtotm(Message *m, Tm *tm) { - char *p; - String *h; + char *p, *q; + int r; - h = s_new(); - for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++) - s_putc(h, *p); - s_terminate(h); - s_restart(h); + if(cistrncmp(m->header, "received:", 9)) + return -1; + q = strchr(m->header, ';'); + if(!q) + return -1; + p = q; + while((p = strchr(p, '\n')) != nil){ + if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') + break; + p++; + } + if(!p) + return -1; + *p = '\0'; + r = strtotm(q + 1, tm); + *p = '\n'; + return r; +} - m->unixfrom = s_parse(h, s_reset(m->unixfrom)); - m->unixdate = s_append(s_reset(m->unixdate), h->ptr); +Message* +gettopmsg(Mailbox *mb, Message *m) +{ + while(!Topmsg(mb, m)) + m = m->whole; + return m; +} - s_free(h); +void +datesec(Mailbox *mb, Message *m) +{ + char *s; + vlong v; + Tm tm; + + if(m->fileid > 1000000ull<<8) + return; + if(m->unixfrom && strtotm(m->unixfrom, &tm) >= 0) + v = tm2sec(&tm); + else if(m->date822 && strtotm(m->date822, &tm) >= 0) + v = tm2sec(&tm); + else if(rxtotm(m, &tm) >= 0) + v = tm2sec(&tm); + else{ + s = rtab[m->type].s; + logmsg(gettopmsg(mb, m), "%s:%s: datasec %s %s\n", mb->path, + m->whole? m->whole->name: "?", + m->name, s); + if(Topmsg(mb, m) || strcmp(s, "message/rfc822") == 0) + abort(); + v = 0; + } + m->fileid = v<<8; } /* * parse a message */ -void -parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom) +extern void sanemsg(Message*); +extern void sanembmsg(Mailbox*, Message*); + +static Message* +haschild(Message *m, int i) { - String *hl; - Header *h; - char *p, *q; - int i; - - if(m->whole == m->whole->whole){ - henter(PATH(mb->id, Qmbox), m->name, - (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb); - } else { - henter(PATH(m->whole->id, Qdir), m->name, - (Qid){PATH(m->id, Qdir), 0, QTDIR}, m, mb); - } - for(i = 0; i < Qmax; i++) - henter(PATH(m->id, Qdir), dirtab[i], - (Qid){PATH(m->id, i), 0, QTFILE}, m, mb); - - // parse mime headers - p = m->header; - hl = s_new(); - while(headerline(&p, hl)){ - if(justmime) - h = &head[Mhead]; - else - h = head; - for(; h->type; h++){ - if(cistrncmp(s_to_c(hl), h->type, h->len) == 0){ - (*h->f)(m, h, s_to_c(hl)); - break; - } - } - s_reset(hl); - } - s_free(hl); - - // the blank line isn't really part of the body or header - if(justmime){ - m->mhend = p; - m->hend = m->header; - } else { - m->hend = p; - } - if(*p == '\n') - p++; - m->rbody = m->body = p; - - // if type is text, get any nulls out of the body. This is - // for the two seans and imap clients that get confused. - if(strncmp(s_to_c(m->type), "text/", 5) == 0) - nullsqueeze(m); - - // - // cobble together Unix-style from line - // for local mailbox messages, we end up recreating the - // original header. - // for pop3 messages, the best we can do is - // use the From: information and the RFC822 date. - // - if(m->unixdate == nil || strcmp(s_to_c(m->unixdate), "???") == 0 - || strcmp(s_to_c(m->unixdate), "Thu Jan 1 00:00:00 GMT 1970") == 0){ - if(m->unixdate){ - s_free(m->unixdate); - m->unixdate = nil; - } - // look for the date in the first Received: line. - // it's likely to be the right time zone (it's - // the local system) and in a convenient format. - if(cistrncmp(m->header, "received:", 9)==0){ - if((q = strchr(m->header, ';')) != nil){ - p = q; - while((p = strchr(p, '\n')) != nil){ - if(p[1] != ' ' && p[1] != '\t' && p[1] != '\n') - break; - p++; - } - if(p){ - *p = '\0'; - m->unixdate = date822tounix(q+1); - *p = '\n'; - } - } - } - - // fall back on the rfc822 date - if(m->unixdate==nil && m->date822) - m->unixdate = date822tounix(s_to_c(m->date822)); - } - - if(m->unixheader != nil) - s_free(m->unixheader); - - // only fake header for top-level messages for pop3 and imap4 - // clients (those protocols don't include the unix header). - // adding the unix header all the time screws up mime-attached - // rfc822 messages. - if(!addfrom && !m->unixfrom){ - m->unixheader = nil; - return; - } - - m->unixheader = s_copy("From "); - if(m->unixfrom && strcmp(s_to_c(m->unixfrom), "???") != 0) - s_append(m->unixheader, s_to_c(m->unixfrom)); - else if(m->from822) - s_append(m->unixheader, s_to_c(m->from822)); - else - s_append(m->unixheader, "???"); - - s_append(m->unixheader, " "); - if(m->unixdate) - s_append(m->unixheader, s_to_c(m->unixdate)); - else - s_append(m->unixheader, "Thu Jan 1 00:00:00 GMT 1970"); - - s_append(m->unixheader, "\n"); -} - -String* -promote(String **sp) -{ - String *s; - - if(*sp != nil) - s = s_clone(*sp); - else - s = nil; - return s; -} - -void -parsebody(Message *m, Mailbox *mb) -{ - Message *nm; - - // recurse - if(strncmp(s_to_c(m->type), "multipart/", 10) == 0){ - parseattachments(m, mb); - } else if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ - decode(m); - parseattachments(m, mb); - nm = m->part; - - // promote headers - if(m->replyto822 == nil && m->from822 == nil && m->sender822 == nil){ - m->from822 = promote(&nm->from822); - m->to822 = promote(&nm->to822); - m->date822 = promote(&nm->date822); - m->sender822 = promote(&nm->sender822); - m->replyto822 = promote(&nm->replyto822); - m->subject822 = promote(&nm->subject822); - m->unixdate = promote(&nm->unixdate); - } - } -} - -void -parse(Message *m, int justmime, Mailbox *mb, int addfrom) -{ - parseheaders(m, justmime, mb, addfrom); - parsebody(m, mb); + for(m = m->part; m && i; i--) + m = m->next; + if(m) + m->mimeflag = 0; + return m; } static void parseattachments(Message *m, Mailbox *mb) { - Message *nm, **l; char *p, *x; + int i; + Message *nm, **l; - // if there's a boundary, recurse... + /* 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(;;){ - x = strstr(p, s_to_c(m->boundary)); - + 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) + 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; + if(x != m->body && x[-1] != '\n'){ + p = x + 1; continue; } if(nm != nil) +{ nm->rbend = nm->bend = nm->end = x; - x += strlen(s_to_c(m->boundary)); +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; - nm = newmessage(m); - nm->start = nm->header = nm->body = nm->rbody = ++p; - nm->mheader = nm->header; - *l = nm; - l = &nm->next; + 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 */ } - 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; + /* 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; - parse(nm, 0, mb, 0); + if(nm->end == nm->start) + nm->mimeflag |= Mtrunc; + parse(mb, nm, 0, 0); + cachehash(mb, nm); /* botchy place for this */ } } -/* - * pick up a header line - */ -static int -headerline(char **pp, String *hl) +void +parseheaders(Mailbox *mb, Message *m, int addfrom, int justmime) { - char *p, *x; + char *p, *e, *o, *t, *s; + int i, i0, n; + uintptr a; - 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, ' '); +/*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; + } } - *pp = p; - return 1; + 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{ + 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(!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; + 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->cstate |= Cheader; +sanembmsg(mb, m); } -/* returns nil iff there are no addressees */ -static String* -addr822(char *p) +char* +promote(char *s) +{ + return s? strdup(s): nil; +} + +void +parsebody(Message *m, Mailbox *mb) +{ + char *s; + int l; + Message *nm; + + /* recurse */ + s = rtab[m->type].s; + l = rtab[m->type].l; + if(l >= 10 && strncmp(s, "multipart/", 10) == 0) + parseattachments(m, mb); + else if(l == 14 && strcmp(s, "message/rfc822") == 0){ + decode(m); + parseattachments(m, mb); + nm = m->part; + + /* 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(Mailbox *mb, Message *m, int addfrom, int justmime) +{ + 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 char* +skipwhite(char *p) +{ + while(isascii(*p) && isspace(*p)) + p++; + return p; +} + +static char* +skiptosemi(char *p) +{ + while(*p && *p != ';') + p++; + while(*p == ';' || (isascii(*p) && isspace(*p))) + p++; + return p; +} + +static char* +getstring(char *p, char *s, char *e, int dolower) { - String *s, *list; - int incomment, addrdone, inanticomment, quoted; - int n; int c; - list = s_new(); - s = s_new(); - quoted = incomment = addrdone = inanticomment = 0; - n = 0; - for(; *p; p++){ - c = *p; + p = skipwhite(p); + if(*p == '"'){ + for(p++; (c = *p) != '"'; p++){ + if(c == '\\') + c = *++p; + /* + * 821 says after \ can be anything at all. + * we just don't care. + */ + if(c == 0) + break; + 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 after \ can be anything at all. + * we just don't care. + */ + if(c == 0) + break; + if(c < ' ') + continue; + if(dolower && c >= 'A' && c <= 'Z') + c += 0x20; + s = sputc(s, e, c); + } + } + *s = 0; + return p; +} - // whitespace is ignored +static void +setfilename(Message *m, char *p) +{ + char buf[Pathlen]; + + free(m->filename); + getstring(p, buf, buf + sizeof buf - 1, 0); + m->filename = smprint("%s", buf); + for(p = m->filename; *p; p++) + if(*p == ' ' || *p == '\t' || *p == ';') + *p = '_'; +} + +static char* +rtrim(char *p) +{ + char *e; + + if(p == 0) + return p; + e = p + strlen(p) - 1; + while(e > p && isascii(*e) && isspace(*e)) + *e-- = 0; + return p; +} + +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) +static char* +concat822(Message*, Header *h, char *o, char *p) { - String *s; + char *s, *n; p += strlen(h->type); - s = addr822(p); - if (m->to822 == nil) - m->to822 = s; - else if (s != nil) { - s_append(m->to822, " "); - s_append(m->to822, s_to_c(s)); - s_free(s); + s = addr822(p, 0); + if(o){ + n = smprint("%s %s", o, s); + free(s); + }else + n = s; + return n; +} + +static char* +from822(Message *m, Header *h, char*, char *p) +{ + if(m->ffrom) + free(m->ffrom); + m->from = 0; + return addr822(p + h->len, &m->ffrom); +} + +static char* +replace822(Message *, Header *h, char*, char *p) +{ + return addr822(p + h->len, 0); +} + +static char* +copy822(Message*, Header *h, char*, char *p) +{ + return rtrim(strdup(skipwhite(p + h->len))); +} + +/* + * firefox, e.g. doesn't keep references unique + */ +static int +uniqarray(char **a, int n, int allocd) +{ + 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 char* +ref822(Message *m, Header *h, char*, char *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; } -} - -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) -{ - p += strlen(h->type); - s_free(m->from822); - m->from822 = addr822(p); -} - -static void -sender822(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - s_free(m->sender822); - m->sender822 = addr822(p); -} - -static void -replyto822(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - s_free(m->replyto822); - m->replyto822 = addr822(p); -} - -static void -mimeversion(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - s_free(m->mimeversion); - m->mimeversion = addr822(p); -} - -static void -killtrailingwhite(char *p) -{ - char *e; - - e = p + strlen(p) - 1; - while(e > p && isspace(*e)) - *e-- = 0; -} - -static void -date822(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - p = skipwhite(p); - s_free(m->date822); - m->date822 = s_copy(p); - p = s_to_c(m->date822); - killtrailingwhite(p); -} - -static void -subject822(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - p = skipwhite(p); - s_free(m->subject822); - m->subject822 = s_copy(p); - p = s_to_c(m->subject822); - killtrailingwhite(p); -} - -static void -inreplyto822(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - p = skipwhite(p); - s_free(m->inreplyto822); - m->inreplyto822 = s_copy(p); - p = s_to_c(m->inreplyto822); - killtrailingwhite(p); -} - -static void -messageid822(Message *m, Header *h, char *p) -{ - p += strlen(h->type); - p = skipwhite(p); - s_free(m->messageid822); - m->messageid822 = s_copy(p); - p = s_to_c(m->messageid822); - killtrailingwhite(p); + 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; + return len; } -static char* -skipwhite(char *p) -{ - while(isspace(*p)) - p++; - return p; -} - -static char* -skiptosemi(char *p) -{ - while(*p && *p != ';') - p++; - while(*p == ';' || isspace(*p)) - p++; - return p; -} - -static char* -getstring(char *p, String *s, int dolower) -{ - s = s_reset(s); - p = skipwhite(p); - if(*p == '"'){ - p++; - for(;*p && *p != '"'; p++) - if(dolower) - s_putc(s, tolower(*p)); - else - s_putc(s, *p); - if(*p == '"') - p++; - s_terminate(s); - - return p; - } - - for(; *p && !isspace(*p) && *p != ';'; p++) - if(dolower) - s_putc(s, tolower(*p)); - else - s_putc(s, *p); - s_terminate(s); - - return p; -} - -static void -setfilename(Message *m, char *p) -{ - m->filename = s_reset(m->filename); - getstring(p, m->filename, 0); - for(p = s_to_c(m->filename); *p; p++) - if(*p == ' ' || *p == '\t' || *p == ';') - *p = '_'; -} - -// -// undecode message body -// +/* + * undecode message body + */ void decode(Message *m) { @@ -1062,9 +1234,15 @@ 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) + m->converted = 1; + if(m->part != nil || cistrncmp(rtab[m->type].s, "text", 4) != 0) return; - if(cistrncmp(s_to_c(m->type), "text", 4) != 0) - return; - - len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend); + len = xtoutf(rtab[m->charset].s, &x, m->body, m->bend); if(len > 0){ if(m->ballocd) free(m->body); @@ -1110,7 +1286,6 @@ convert(Message *m) m->bend = x + len; m->ballocd = 1; } - m->converted = 1; } static int @@ -1119,18 +1294,20 @@ hex2int(int x) if(x >= '0' && x <= '9') return x - '0'; if(x >= 'A' && x <= 'F') - return (x - 'A') + 10; + return x - 'A' + 10; if(x >= 'a' && x <= 'f') - return (x - 'a') + 10; + return x - 'a' + 10; return -1; } -// underscores are translated in 2047 headers (uscores=1) -// but not in the body (uscores=0) +/* + * underscores are translated in 2047 headers (uscores=1) + * but not in the body (uscores=0) + */ static char* decquotedline(char *out, char *in, char *e, int uscores) { - int c, c2, soft; + int c, soft; /* dump trailing white space */ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) @@ -1155,11 +1332,11 @@ decquotedline(char *out, char *in, char *e, int uscores) *out++ = c; break; case '=': - c = hex2int(*in++); - c2 = hex2int(*in++); - if (c != -1 && c2 != -1) - *out++ = c<<4 | c2; - else { + c = hex2int(*in++)<<4; + c |= hex2int(*in++); + if(c != -1) + *out++ = c; + else{ *out++ = '='; in -= 2; } @@ -1184,10 +1361,10 @@ decquoted(char *out, char *in, char *e, int uscores) in = nl + 1; } if(in < e) - p = decquotedline(p, in, e-1, uscores); + p = decquotedline(p, in, e - 1, uscores); - // make sure we end with a new line - if(*(p-1) != '\n'){ + /* make sure we end with a new line */ + if(*(p - 1) != '\n'){ *p++ = '\n'; *p = 0; } @@ -1195,7 +1372,7 @@ decquoted(char *out, char *in, char *e, int uscores) return p - out; } -static char* +char* lowercase(char *p) { char *op; @@ -1207,7 +1384,7 @@ lowercase(char *p) return op; } -// translate latin1 directly since it fits neatly in utf +/* translate latin1 directly since it fits neatly in utf */ static int latin1toutf(char **out, char *in, char *e) { @@ -1222,38 +1399,35 @@ latin1toutf(char **out, char *in, char *e) if(n == 0) return 0; - n += e-in; - *out = p = malloc(UTFmax*n+1); + n += e - in; + *out = p = malloc(n + 1); if(p == nil) return 0; for(; in < e; in++){ - r = (*in) & 0xff; + r = (uchar)*in; p += runetochar(p, &r); } *p = 0; return p - *out; } -// translate any thing using the tcs program +/* translate any thing using the tcs program */ int xtoutf(char *charset, char **out, char *in, char *e) { - char *av[4]; - int totcs[2]; - int fromtcs[2]; - int n, len, sofar; - char *p; + char *av[4], *p; + int totcs[2], fromtcs[2], n, len, sofar; - // might not need to convert + /* might not need to convert */ if(cistrcmp(charset, "us-ascii") == 0 || cistrcmp(charset, "utf-8") == 0) return 0; if(cistrcmp(charset, "iso-8859-1") == 0) return latin1toutf(out, in, e); - len = e-in+1; + len = e - in + 1; sofar = 0; - *out = p = malloc(len+1); + *out = p = malloc(len + 1); if(p == nil) return 0; @@ -1279,7 +1453,7 @@ xtoutf(char *charset, char **out, char *in, char *e) close(fromtcs[1]); close(totcs[0]); dup(open("/dev/null", OWRITE), 2); exec("/bin/tcs", av); - _exits(0); + _exits(""); default: close(fromtcs[1]); close(totcs[0]); switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ @@ -1289,24 +1463,24 @@ xtoutf(char *charset, char **out, char *in, char *e) case 0: close(fromtcs[0]); while(in < e){ - n = write(totcs[1], in, e-in); + n = write(totcs[1], in, e - in); if(n <= 0) break; in += n; } close(totcs[1]); - _exits(0); + _exits(""); default: close(totcs[1]); for(;;){ - n = read(fromtcs[0], &p[sofar], len-sofar); + n = read(fromtcs[0], &p[sofar], len - sofar); if(n <= 0) break; sofar += n; p[sofar] = 0; if(sofar == len){ len += 1024; - p = realloc(p, len+1); + p = realloc(p, len + 1); if(p == nil) goto error; *out = p; @@ -1333,10 +1507,8 @@ emalloc(ulong n) void *p; p = mallocz(n, 1); - if(!p){ - fprint(2, "%s: out of memory alloc %lud\n", argv0, n); - exits("out of memory"); - } + if(!p) + sysfatal("malloc %lud: %r", n); setmalloctag(p, getcallerpc(&n)); return p; } @@ -1347,53 +1519,57 @@ erealloc(void *p, ulong n) if(n == 0) n = 1; p = realloc(p, n); - if(!p){ - fprint(2, "%s: out of memory realloc %lud\n", argv0, n); - exits("out of memory"); - } + if(!p) + sysfatal("realloc %lud: %r", n); setrealloctag(p, getcallerpc(&p)); return p; } +int +myplumbsend(int fd, Plumbmsg *m) +{ + char *buf; + int n; + + buf = plumbpack(m, &n); + if(buf == nil) + return -1; + n = write(fd, buf, n); + free(buf); + return n; +} + void mailplumb(Mailbox *mb, Message *m, int delete) { + char buf[256], dbuf[SHA1dlen*2 + 1], len[10], date[30], *from, *subject; + int ai, cache; Plumbmsg p; Plumbattr a[7]; - char buf[256]; - int ai; - char lenstr[10], *from, *subject, *date; static int fd = -1; - if(m->subject822 == nil) + cache = insurecache(mb, m) == 0; /* living dangerously if deleted */ + subject = m->subject; + if(subject == nil) subject = ""; - else - subject = s_to_c(m->subject822); - if(m->from822 != nil) - from = s_to_c(m->from822); + if(m->from != nil) + from = m->from; else if(m->unixfrom != nil) - from = s_to_c(m->unixfrom); + from = m->unixfrom; else from = ""; - if(m->unixdate != nil) - date = s_to_c(m->unixdate); - else - date = ""; - - sprint(lenstr, "%zd", m->end-m->start); - + sprint(len, "%lud", m->size); if(biffing && !delete) - print("[ %s / %s / %s ]\n", from, subject, lenstr); - + fprint(2, "[ %s / %s / %s ]\n", from, subject, len); if(!plumbing) - return; + goto out; if(fd < 0) fd = plumbopen("send", OWRITE); if(fd < 0) - return; + goto out; p.src = "mailfs"; p.dst = "seemail"; @@ -1409,102 +1585,123 @@ mailplumb(Mailbox *mb, Message *m, int delete) a[ai-1].next = &a[ai]; a[++ai].name = "length"; - a[ai].value = lenstr; + a[ai].value = len; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; - a[ai].value = delete?"delete":"new"; + a[ai].value = delete? "delete": "new"; a[ai-1].next = &a[ai]; + snprint(date, sizeof date, "%Δ", m->fileid); a[++ai].name = "date"; a[ai].value = date; a[ai-1].next = &a[ai]; - if(m->sdigest){ + if(m->digest){ + snprint(dbuf, sizeof dbuf, "%A", m->digest); a[++ai].name = "digest"; - a[ai].value = s_to_c(m->sdigest); + a[ai].value = dbuf; a[ai-1].next = &a[ai]; } - a[ai].next = nil; - p.attr = a; - snprint(buf, sizeof(buf), "%s/%s/%s", + snprint(buf, sizeof buf, "%s/%s/%s", mntpt, mb->name, m->name); p.ndata = strlen(buf); p.data = buf; - plumbsend(fd, &p); -} - -// -// count the number of lines in the body (for imap4) -// -void -countlines(Message *m) -{ - int i; - char *p; - - i = 0; - for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p+1, '\n')) - i++; - sprint(m->lines, "%d", i); -} - -char *LOG = "fs"; - -void -logmsg(char *s, Message *m) -{ - int pid; - - if(!logging) - return; - pid = getpid(); - if(m == nil) - syslog(0, LOG, "%s.%d: %s", user, pid, s); - else - syslog(0, LOG, "%s.%d: %s msg from %s digest %s", - user, pid, s, - m->from822 ? s_to_c(m->from822) : "?", - s_to_c(m->sdigest)); + myplumbsend(fd, &p); +out: + if(cache) + msgdecref(mb, m); } /* - * squeeze nulls out of the body + * count the number of lines in the body (for imap4) */ -static void -nullsqueeze(Message *m) +ulong +countlines(Message *m) { - char *p, *q; + char *p; + ulong i; - q = memchr(m->body, 0, m->end-m->body); - if(q == nil) - return; - - for(p = m->body; q < m->end; q++){ - if(*q == 0) - continue; - *p++ = *q; - } - m->bend = m->rbend = m->end = p; + i = 0; + for(p = strchr(m->rbody, '\n'); p != nil && p < m->rbend; p = strchr(p + 1, '\n')) + i++; + return i; } +static char *logf = "fs"; -// -// 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. -// +void +logmsg(Message *m, char *fmt, ...) +{ + char buf[256], *p, *e; + va_list args; -extern int strtotm(char*, Tm*); -String* -date822tounix(char *s) + if(!lflag) + return; + 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); +} + +void +iprint(char *fmt, ...) +{ + char buf[256], *p, *e; + va_list args; + + if(!iflag) + return; + 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. + */ + +char* +date822tounix(Message *, char *s) { char *p, *q; Tm tm; @@ -1515,6 +1712,5 @@ date822tounix(char *s) p = asctime(&tm); if(q = strchr(p, '\n')) *q = '\0'; - return s_copy(p); + return strdup(p); } - diff --git a/sys/src/cmd/upas/fs/mdir.c b/sys/src/cmd/upas/fs/mdir.c new file mode 100644 index 000000000..69d53e465 --- /dev/null +++ b/sys/src/cmd/upas/fs/mdir.c @@ -0,0 +1,354 @@ +#include "common.h" +#include "dat.h" + +typedef struct { + int debug; +} Mdir; + +#define mdprint(mdir, ...) if(mdir->debug) fprint(2, __VA_ARGS__) + +static int +slurp(char *f, char *b, uvlong o, long l) +{ + int fd, r; + + if((fd = open(f, OREAD)) == -1) + return -1; + + seek(fd, o, 0); + r = readn(fd, b, l) != l; + close(fd); + return r? -1: 0; +} + +static void +parseunix(Message *m) +{ + char *s, *p; + int l; + + l = m->header - m->start; + m->unixheader = smprint("%.*s", l, m->start); + s = m->start + 5; + if((p = strchr(s, ' ')) == nil) + abort(); + *p = 0; + m->unixfrom = strdup(s); + *p = ' '; +} + +static int +mdirfetch(Mailbox *mb, Message *m, uvlong o, ulong l) +{ + char buf[Pathlen], *x; + Mdir *mdir; + + mdir = mb->aux; + mdprint(mdir, "mdirfetch(%D) ...", m->fileid); + + snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid); + if(slurp(buf, m->start + o, o, l) == -1){ + logmsg(m, "fetch failed: %r"); + mdprint(mdir, "%r\n"); + return -1; + } + if(m->header == nil) + m->header = m->start; + if(m->header == m->start) + if(o + l >= 36) + if(strncmp(m->start, "From ", 5) == 0) + if(x = strchr(m->start, '\n')){ + m->header = x + 1; + if(m->unixfrom == nil) + parseunix(m); + } + m->mheader = m->mhend = m->header; + mdprint(mdir, "fetched [%llud, %llud]\n", o, o + l); + return 0; +} + +static int +setsize(Mailbox *mb, Message *m) +{ + char buf[Pathlen]; + Dir *d; + + snprint(buf, sizeof buf, "%s/%D", mb->path, m->fileid); + if((d = dirstat(buf)) == nil) + return -1; + m->size = d->length; + free(d); + return 0; +} + +/* must be [0-9]+(\..*)? */ +int +dirskip(Dir *a, uvlong *uv) +{ + char *p; + + if(a->length == 0) + return 1; + *uv = strtoul(a->name, &p, 0); + if(*uv < 1000000 || *p != '.') + return 1; + *uv = *uv<<8 | strtoul(p + 1, &p, 10); + if(*p) + return 1; + return 0; +} + +static int +vcmp(vlong a, vlong b) +{ + a -= b; + if(a > 0) + return 1; + if(a < 0) + return -1; + return 0; +} + +static int +dircmp(Dir *a, Dir *b) +{ + uvlong x, y; + + dirskip(a, &x); + dirskip(b, &y); + return vcmp(x, y); +} + +static char* +mdirread(Mdir* mdir, Mailbox* mb, int doplumb, int *new) +{ + int i, nnew, ndel, fd, n, c; + uvlong uv; + Dir *d; + Message *m, **ll; + static char err[ERRMAX]; + + mdprint(mdir, "mdirread()\n"); + if((fd = open(mb->path, OREAD)) == -1){ + errstr(err, sizeof err); + return err; + } + if((d = dirfstat(fd)) == nil){ + errstr(err, sizeof err); + close(fd); + return err; + } + *new = nnew = 0; + if(mb->d){ + if(d->qid.path == mb->d->qid.path) + if(d->qid.vers == mb->d->qid.vers){ + mdprint(mdir, "\tqids match\n"); + close(fd); + free(d); + goto finished; + } + free(mb->d); + } + logmsg(nil, "reading %s (mdir)", mb->path); + mb->d = d; + + n = dirreadall(fd, &d); + close(fd); + if(n == -1){ + errstr(err, sizeof err); + return err; + } + + qsort(d, n, sizeof *d, (int(*)(void*, void*))dircmp); + ndel = 0; + ll = &mb->root->part; + for(i = 0; *ll || i < n; ){ + if(i < n && dirskip(d + i, &uv)){ + i++; + continue; + } + c = -1; + if(i >= n) + c = 1; + else if(*ll) + c = vcmp(uv, (*ll)->fileid); + mdprint(mdir, "consider %s and %D -> %d\n", ifileid: 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", ifileid: 0); + ndel++; + logmsg(*ll, "deleted (refs=%d)", *ll? (*ll)->refs: -42); + if(doplumb) + mailplumb(mb, *ll, 1); + (*ll)->inmbox = 0; + (*ll)->deleted = Disappear; + ll = &(*ll)->next; + }else{ + //logmsg(*ll, "duplicate %s", d[i].name); + i++; + ll = &(*ll)->next; + } + } + + free(d); + logmsg(nil, "mbox read: %d new %d deleted", nnew, ndel); +finished: + *new = nnew; + return nil; +} + +static void +mdirdelete(Mailbox *mb, Message *m) +{ + char mpath[Pathlen]; + Mdir* mdir; + + mdir = mb->aux; + snprint(mpath, sizeof mpath, "%s/%D", mb->path, m->fileid); + mdprint(mdir, "remove: %s\n", mpath); + /* may have been removed by other fs. just log the error. */ + if(remove(mpath) == -1) + logmsg(m, "remove: %s: %r", mpath); + m->inmbox = 0; +} + +static char* +mdirsync(Mailbox* mb, int doplumb, int *new) +{ + Mdir *mdir; + + mdir = mb->aux; + mdprint(mdir, "mdirsync()\n"); + return mdirread(mdir, mb, doplumb, new); +} + +static char* +mdirctl(Mailbox *mb, int c, char **v) +{ + Mdir *mdir; + + mdir = mb->aux; + if(c == 1 && strcmp(*v, "debug") == 0) + mdir->debug = 1; + else if(c == 1 && strcmp(*v, "nodebug") == 0) + mdir->debug = 0; + else + return "bad mdir control message"; + return nil; +} + +static void +mdirclose(Mailbox *mb) +{ + free(mb->aux); +} + +static int +qidcmp(Qid *a, Qid *b) +{ + if(a->path != b->path) + return 1; + return a->vers - b->vers; +} + +/* + * .idx strategy. we save the qid.path and .vers + * of the mdir directory and the date to the index. + * we accept the work done by the other upas/fs if + * the index is based on the same (or a newer) + * qid. in any event, we recheck the directory after + * the directory is four hours old. + */ +static int +idxr(char *s, Mailbox *mb) +{ + char *f[5]; + long t, δt, n; + Dir d; + + n = tokenize(s, f, nelem(f)); + if(n != 4 || strcmp(f[0], "mdirv1") != 0) + return -1; + t = strtoul(f[1], 0, 0); + δt = time(0) - t; + if(δt < 0 || δt > 4*3600) + return 0; + memset(&d, 0, sizeof d); + d.qid.path = strtoull(f[2], 0, 0); + d.qid.vers = strtoull(f[3], 0, 0); + if(mb->d && qidcmp(&mb->d->qid, &d.qid) >= 0) + return 0; + if(mb->d == 0) + mb->d = emalloc(sizeof d); + mb->d->qid = d.qid; + mb->d->mtime = t; + return 0; +} + +static void +idxw(Biobuf *b, Mailbox *mb) +{ + Qid q; + + memset(&q, 0, sizeof q); + if(mb->d) + q = mb->d->qid; + Bprint(b, "mdirv1 %lud %llud %lud\n", time(0), q.path, q.vers); +} + +char* +mdirmbox(Mailbox *mb, char *path) +{ + int m; + Dir *d; + Mdir *mdir; + + d = dirstat(path); + if(!d && mb->flags & DMcreate){ + createfolder(getuser(), path); + d = dirstat(path); + } + m = d && (d->mode & DMDIR); + free(d); + if(!m) + return Enotme; + snprint(mb->path, sizeof mb->path, "%s", path); + mdir = emalloc(sizeof *mdir); + mdir->debug = 0; + mb->aux = mdir; + mb->sync = mdirsync; + mb->close = mdirclose; + mb->fetch = mdirfetch; + mb->delete = mdirdelete; + mb->remove = localremove; + mb->rename = localrename; + mb->idxread = idxr; + mb->idxwrite = idxw; + mb->ctl = mdirctl; + return nil; +} diff --git a/sys/src/cmd/upas/fs/mkfile b/sys/src/cmd/upas/fs/mkfile index 4a7132b86..814787a07 100644 --- a/sys/src/cmd/upas/fs/mkfile +++ b/sys/src/cmd/upas/fs/mkfile @@ -1,14 +1,24 @@ a$O + +chkidx: mtree.$O chkidx.$O + $LD $LDFLAGS -o $target $prereq diff --git a/sys/src/cmd/upas/fs/mtree.c b/sys/src/cmd/upas/fs/mtree.c new file mode 100644 index 000000000..4f2af5655 --- /dev/null +++ b/sys/src/cmd/upas/fs/mtree.c @@ -0,0 +1,80 @@ +#include "common.h" +#include +#include "dat.h" + +int +mtreecmp(Avl *va, Avl *vb) +{ + Mtree *a, *b; + + a = (Mtree*)va; + b = (Mtree*)vb; + return memcmp(a->m->digest, b->m->digest, SHA1dlen); +} + +int +mtreeisdup(Mailbox *mb, Message *m) +{ + Mtree t; + + assert(Topmsg(mb, m) && m->digest); + if(!m->digest) + return 0; + memset(&t, 0, sizeof t); + t.m = m; + if(avllookup(mb->mtree, &t)) + return 1; + return 0; +} + +Message* +mtreefind(Mailbox *mb, uchar *digest) +{ + Message m0; + Mtree t, *p; + + m0.digest = digest; + memset(&t, 0, sizeof t); + t.m = &m0; + if(p = (Mtree*)avllookup(mb->mtree, &t)) + return p->m; + return nil; +} + +void +mtreeadd(Mailbox *mb, Message *m) +{ + Avl *old; + Mtree *p; + + assert(Topmsg(mb, m) && m->digest); + p = emalloc(sizeof *p); + p->m = m; + old = avlinsert(mb->mtree, p); + assert(old == 0); +} + +void +mtreedelete(Mailbox *mb, Message *m) +{ + Mtree t, *p; + + assert(Topmsg(mb, m)); + memset(&t, 0, sizeof t); + t.m = m; + if(m->deleted & ~Deleted){ + if(m->digest == nil) + return; + p = (Mtree*)avllookup(mb->mtree, &t); + if(p == nil || p->m != m) + return; + p = (Mtree*)avldelete(mb->mtree, &t); + free(p); + return; + } + assert(m->digest); + p = (Mtree*)avldelete(mb->mtree, &t); + if(p == nil) + _assert("mtree delete fails"); + free(p); +} diff --git a/sys/src/cmd/upas/fs/plan9.c b/sys/src/cmd/upas/fs/plan9.c index 171b15617..1d65a62e0 100644 --- a/sys/src/cmd/upas/fs/plan9.c +++ b/sys/src/cmd/upas/fs/plan9.c @@ -1,151 +1,188 @@ #include "common.h" -#include -#include #include #include "dat.h" -enum { - Buffersize = 64*1024, -}; +typedef struct { + Biobuf *in; + char *shift; +} Inbuf; -typedef struct Inbuf Inbuf; -struct Inbuf +/* + * parse a Unix style header + */ +static int +memtotm(char *p, int n, Tm *t) { - int fd; - uchar *lim; - uchar *rptr; - uchar *wptr; - uchar data[Buffersize+7]; -}; + char buf[128]; + + if(n > sizeof buf - 1) + n = sizeof buf -1; + memcpy(buf, p, n); + buf[n] = 0; + return strtotm(buf, t); +} + +static int +chkunix0(char *s, int n) +{ + char *p; + Tm tm; + + if(n > 256) + return -1; + if((p = memchr(s, ' ', n)) == nil) + return -1; + if(memtotm(p, n - (p - s), &tm) < 0) + return -1; + if(tm2sec(&tm) < 1000000) + return -1; + return 0; +} + +static int +chkunix(char *s, int n) +{ + int r; + + r = chkunix0(s, n); + if(r == -1) + eprint("plan9: warning naked from [%.*s]\n", n, s); + return r; +} + +static char* +parseunix(Message *m) +{ + char *s, *p, *q; + int l; + Tm tm; + + l = m->header - m->start; + m->unixheader = smprint("%.*s", l, m->start); + s = m->start + 5; + if((p = strchr(s, ' ')) == nil) + return s; + *p = 0; + m->unixfrom = strdup(s); + *p++ = ' '; + if(q = strchr(p, '\n')) + *q = 0; + if(strtotm(p, &tm) < 0) + return p; + if(q) + *q = '\n'; + m->fileid = (uvlong)tm2sec(&tm) << 8; + return 0; +} static void -addtomessage(Message *m, uchar *p, int n, int done) +addtomessage(Message *m, char *p, int n) { int i, len; - // add to message (+1 in malloc is for a trailing NUL) + if(n == 0) + return; + /* add to message (+1 in malloc is for a trailing NUL) */ if(m->lim - m->end < n){ if(m->start != nil){ - i = m->end-m->start; - if(done) - len = i + n; - else - len = (4*(i+n))/3; + i = m->end - m->start; + len = (4*(i + n))/3; m->start = erealloc(m->start, len + 1); m->end = m->start + i; } else { - if(done) - len = n; - else - len = 2*n; + len = 2*n; m->start = emalloc(len + 1); m->end = m->start; } m->lim = m->start + len; - *m->lim = '\0'; + *m->lim = 0; } memmove(m->end, p, n); m->end += n; - *m->end = '\0'; + *m->end = 0; } -// -// read in a single message -// +/* + * read in a single message + */ static int -readmessage(Message *m, Inbuf *inb) +okmsg(Mailbox *mb, Message *m, Inbuf *b) { - int i, n, done; - uchar *p, *np; - char sdigest[SHA1dlen*2+1]; - char tmp[64]; + char e[ERRMAX], buf[128]; - for(done = 0; !done;){ - n = inb->wptr - inb->rptr; - if(n < 6){ - if(n) - memmove(inb->data, inb->rptr, n); - inb->rptr = inb->data; - inb->wptr = inb->rptr + n; - i = read(inb->fd, inb->wptr, Buffersize); - if(i < 0){ - if(fd2path(inb->fd, tmp, sizeof tmp) < 0) - strcpy(tmp, "unknown mailbox"); - fprint(2, "error reading '%s': %r\n", tmp); - return -1; - } - if(i == 0){ - if(n != 0) - addtomessage(m, inb->rptr, n, 1); - if(m->end == m->start) - return -1; - break; - } - inb->wptr += i; - } - - // look for end of message - for(p = inb->rptr; p < inb->wptr; p = np+1){ - // first part of search for '\nFrom ' - np = memchr(p, '\n', inb->wptr - p); - if(np == nil){ - p = inb->wptr; - break; - } - - /* - * if we've found a \n but there's - * not enough room for '\nFrom ', don't do - * the comparison till we've read in more. - */ - if(inb->wptr - np < 6){ - p = np; - break; - } - - if(strncmp((char*)np, "\nFrom ", 6) == 0){ - done = 1; - p = np+1; - break; - } - } - - // add to message (+ 1 in malloc is for a trailing null) - n = p - inb->rptr; - addtomessage(m, inb->rptr, n, done); - inb->rptr += n; - } - - // if it doesn't start with a 'From ', this ain't a mailbox - if(strncmp(m->start, "From ", 5) != 0) + 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; - - // dump trailing newline, make sure there's a trailing null - // (helps in body searches) - if(*(m->end-1) == '\n') + } + if(m->end == m->start) + return -1; + 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; - - // 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); - + if(m->digest == 0) + digestmessage(mb, m); return 0; } +static char* +inbread(Inbuf *b) +{ + if(b->shift) + return b->shift; + return b->shift = Brdline(b->in, '\n'); +} -// throw out deleted messages. return number of freshly deleted messages +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 */ 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 char* -_readmbox(Mailbox *mb, int doplumb, Mlock *lk) +static void +mergemsg(Message *m, Message *x) { - int fd, n; - String *tmp; + 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, int *new, Mlock *lk) +{ + 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 */ + in = Bopen(mb->path, OREAD); + if(in == nil){ + errstr(err, sizeof(err)); + if(strstr(err, "exist") != 0){ + snprint(buf, sizeof buf, "%s.tmp", mb->path); + if(sysrename(buf, mb->path) == 0) goto retry; - } - if(strstr(err, "exist") != nil){ - tmp = s_copy(mb->path); - s_append(tmp, ".tmp"); - if(sysrename(s_to_c(tmp), mb->path) == 0){ - s_free(tmp); - goto retry; - } - s_free(tmp); } return err; } @@ -204,173 +248,170 @@ retry: * a new qid.path means reread the mailbox, while * a new qid.vers means read any new messages */ - d = dirfstat(fd); + d = dirfstat(Bfildes(in)); if(d == nil){ - close(fd); - errstr(err, sizeof(err)); + Bterm(in); + errstr(err, sizeof err); return err; } if(mb->d != nil){ if(d->qid.path == mb->d->qid.path && d->qid.vers == mb->d->qid.vers){ - close(fd); + *new = 0; + Bterm(in); free(d); return nil; } if(d->qid.path == mb->d->qid.path){ while(*l != nil) l = &(*l)->next; - seek(fd, mb->d->length, 0); + Bseek(in, mb->d->length, 0); } free(mb->d); } mb->d = d; - mb->vers++; - henter(PATH(0, Qtop), mb->name, - (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); - inb = emalloc(sizeof(Inbuf)); - inb->rptr = inb->wptr = inb->data; - inb->fd = fd; + memset(&b, 0, sizeof b); + b.in = in; + b.shift = 0; - // read new messages - snprint(err, sizeof err, "reading '%s'", mb->path); - logmsg(err, nil); + /* read new messages */ + logmsg(nil, "reading %s", mb->path); + nnew = 0; for(;;){ if(lk != nil) syslockrefresh(lk); m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; - if(readmessage(m, inb) < 0){ - delmessage(mb, m); - mb->root->subname--; + if(readmessage(mb, m, &b) < 0){ + unnewmessage(mb, mb->root, m); break; } - - // merge mailbox versions + /* merge mailbox versions */ while(*l != nil){ if(memcmp((*l)->digest, m->digest, SHA1dlen) == 0){ - // matches mail we already read, discard - logmsg("duplicate", *l); - delmessage(mb, m); - mb->root->subname--; - m = nil; - l = &(*l)->next; + if((*l)->start == nil){ + logmsg(*l, "read indexed"); + mergemsg(*l, m); + unnewmessage(mb, mb->root, m); + m = *l; + }else{ + logmsg(*l, "duplicate"); + m->inmbox = 1; /* remove it */ + unnewmessage(mb, mb->root, m); + m = nil; + l = &(*l)->next; + } break; } else { - // old mail no longer in box, mark deleted - logmsg("disappeared", *l); + /* old mail no longer in box, mark deleted */ + logmsg(*l, "disappeared"); if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; - (*l)->deleted = 1; + (*l)->deleted = Disappear; l = &(*l)->next; } } if(m == nil) continue; - - x = strchr(m->start, '\n'); - if(x == nil) - m->header = m->end; - else + m->header = m->end; + if(x = strchr(m->start, '\n')) m->header = x + 1; + if(p = parseunix(m)) + sysfatal("%s:%lld naked From in body? [%s]", mb->path, seek(Bfildes(in), 0, 1), p); m->mheader = m->mhend = m->header; - parseunix(m); - parse(m, 0, mb, 0); - logmsg("new", m); - + parse(mb, m, 0, 0); + if(m != *l && m->deleted != Dup){ + logmsg(m, "new"); + newcachehash(mb, m, doplumb); + putcache(mb, m); + nnew++; + } /* chain in */ *l = m; l = &m->next; - if(doplumb) - mailplumb(mb, m, 0); - } - logmsg("mbox read", nil); + logmsg(nil, "mbox read"); - // whatever is left has been removed from the mbox, mark deleted + /* whatever is left has been removed from the mbox, mark deleted */ while(*l != nil){ if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; - (*l)->deleted = 1; + (*l)->deleted = Deleted; l = &(*l)->next; } - close(fd); - free(inb); + Bterm(in); + *new = nnew; return nil; } static void -_writembox(Mailbox *mb, Mlock *lk) +writembox(Mailbox *mb, Mlock *lk) { - Dir *d; - Message *m; - String *tmp; + char buf[Pathlen]; int mode, errs; Biobuf *b; + Dir *d; + Message *m; - tmp = s_copy(mb->path); - s_append(tmp, ".tmp"); + snprint(buf, sizeof buf, "%s.tmp", mb->path); /* * preserve old files permissions, if possible */ - d = dirstat(mb->path); - if(d != nil){ - mode = d->mode&0777; + mode = Mboxmode; + if(d = dirstat(mb->path)){ + mode = d->mode & 0777; free(d); - } else - mode = MBOXMODE; + } - sysremove(s_to_c(tmp)); - b = sysopen(s_to_c(tmp), "alc", mode); + remove(buf); + b = sysopen(buf, "alc", mode); if(b == 0){ - fprint(2, "can't write temporary mailbox %s: %r\n", s_to_c(tmp)); + eprint("plan9: can't write temporary mailbox %s: %r\n", buf); return; } - logmsg("writing new mbox", nil); + logmsg(nil, "writing new mbox"); errs = 0; for(m = mb->root->part; m != nil; m = m->next){ if(lk != nil) syslockrefresh(lk); if(m->deleted) continue; - logmsg("writing", m); + logmsg(m, "writing"); if(Bwrite(b, m->start, m->end - m->start) < 0) errs = 1; if(Bwrite(b, "\n", 1) < 0) errs = 1; } - logmsg("wrote new mbox", nil); + logmsg(nil, "wrote new mbox"); if(sysclose(b) < 0) errs = 1; if(errs){ - fprint(2, "error writing temporary mail file\n"); - s_free(tmp); + eprint("plan9: error writing temporary mail file\n"); return; } - sysremove(mb->path); - if(sysrename(s_to_c(tmp), mb->path) < 0) - fprint(2, "%s: can't rename %s to %s: %r\n", argv0, - s_to_c(tmp), mb->path); - s_free(tmp); + remove(mb->path); + if(sysrename(buf, mb->path) < 0) + eprint("plan9: can't rename %s to %s: %r\n", + buf, mb->path); if(mb->d != nil) free(mb->d); mb->d = dirstat(mb->path); } char* -plan9syncmbox(Mailbox *mb, int doplumb) +plan9syncmbox(Mailbox *mb, int doplumb, int *new) { - Mlock *lk; char *rv; + Mlock *lk; lk = nil; if(mb->dolock){ @@ -379,9 +420,9 @@ plan9syncmbox(Mailbox *mb, int doplumb) return "can't lock mailbox"; } - rv = _readmbox(mb, doplumb, lk); /* interpolate */ + rv = readmbox(mb, doplumb, new, lk); /* interpolate */ if(purgedeleted(mb) > 0) - _writembox(mb, lk); + writembox(mb, lk); if(lk != nil) sysunlock(lk); @@ -389,26 +430,30 @@ plan9syncmbox(Mailbox *mb, int doplumb) return rv; } -// -// look to see if we can open this mail box -// +void +plan9decache(Mailbox*, Message *m) +{ + m->lim = 0; +} + +/* + * look to see if we can open this mail box + */ char* plan9mbox(Mailbox *mb, char *path) { - static char err[Errlen]; - String *tmp; + char buf[Pathlen]; + static char err[Pathlen]; if(access(path, AEXIST) < 0){ - errstr(err, sizeof(err)); - tmp = s_copy(path); - s_append(tmp, ".tmp"); - if(access(s_to_c(tmp), AEXIST) < 0){ - s_free(tmp); + errstr(err, sizeof err); + snprint(buf, sizeof buf, "%s.tmp", path); + if(access(buf, AEXIST) < 0) return err; - } - s_free(tmp); } - mb->sync = plan9syncmbox; + mb->remove = localremove; + mb->rename = localrename; + mb->decache = plan9decache; return nil; } diff --git a/sys/src/cmd/upas/fs/planb.c b/sys/src/cmd/upas/fs/planb.c index 58dfe1622..fbe2ac251 100644 --- a/sys/src/cmd/upas/fs/planb.c +++ b/sys/src/cmd/upas/fs/planb.c @@ -15,12 +15,37 @@ #include #include "dat.h" +static char* +parseunix(Message *m) +{ + char *s, *p, *q; + int l; + Tm tm; + + l = m->header - m->start; + m->unixheader = smprint("%.*s", l, m->start); + s = m->start + 5; + if((p = strchr(s, ' ')) == nil) + return s; + *p = 0; + m->unixfrom = strdup(s); + *p++ = ' '; + if(q = strchr(p, '\n')) + *q = 0; + if(strtotm(p, &tm) < 0) + return p; + if(q) + *q = '\n'; + m->fileid = (uvlong)tm2sec(&tm) << 8; + return 0; +} + static int readmessage(Message *m, char *msg) { - int fd, i, n; + int fd, n; char *buf, *name, *p; - char hdr[128], sdigest[SHA1dlen*2+1]; + char hdr[128]; Dir *d; buf = nil; @@ -29,8 +54,10 @@ readmessage(Message *m, char *msg) if(name == nil) return -1; if(m->filename != nil) - s_free(m->filename); - m->filename = s_copy(name); + free(m->filename); + m->filename = strdup(name); + if(m->filename == nil) + sysfatal("malloc: %r"); fd = open(name, OREAD); if(fd < 0) goto Fail; @@ -75,10 +102,7 @@ readmessage(Message *m, char *msg) m->end--; *m->end = 0; m->bend = m->rbend = m->end; - sha1((uchar*)m->start, m->end - m->start, m->digest, nil); - for(i = 0; i < SHA1dlen; i++) - sprint(sdigest+2*i, "%2.2ux", m->digest[i]); - m->sdigest = s_copy(sdigest); + return 0; Fail: if(fd >= 0) @@ -98,7 +122,7 @@ archive(Message *m) char *dir, *p, *nname; Dir d; - dir = strdup(s_to_c(m->filename)); + dir = strdup(m->filename); nname = nil; if(dir == nil) return; @@ -162,21 +186,20 @@ mustshow(char* name) } static int -readpbmessage(Mailbox *mb, char *msg, int doplumb) +readpbmessage(Mailbox *mb, char *msg, int doplumb, int *nnew) { Message *m, **l; - char *x; + char *x, *p; m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; if(readmessage(m, msg) < 0){ - delmessage(mb, m); - mb->root->subname--; + unnewmessage(mb, mb->root, m); return -1; } for(l = &mb->root->part; *l != nil; l = &(*l)->next) - if(strcmp(s_to_c((*l)->filename), s_to_c(m->filename)) == 0 && + if(strcmp((*l)->filename, m->filename) == 0 && *l != m){ if((*l)->deleted < 0) (*l)->deleted = 0; @@ -184,15 +207,19 @@ readpbmessage(Mailbox *mb, char *msg, int doplumb) mb->root->subname--; return -1; } - x = strchr(m->start, '\n'); - if(x == nil) - m->header = m->end; - else + m->header = m->end; + if(x = strchr(m->start, '\n')) m->header = x + 1; + if(p = parseunix(m)) + sysfatal("%s:%s naked From in body? [%s]", mb->path, (*l)->filename, p); m->mheader = m->mhend = m->header; - parseunix(m); - parse(m, 0, mb, 0); - logmsg("new", m); + parse(mb, m, 0, 0); + if(m != *l && m->deleted != Dup){ + logmsg(m, "new"); + newcachehash(mb, m, doplumb); + putcache(mb, m); + nnew[0]++; + } /* chain in */ *l = m; @@ -215,17 +242,18 @@ dcmp(Dir *a, Dir *b) return strcmp(an, bn); } -static void -readpbmbox(Mailbox *mb, int doplumb) +static char* +readpbmbox(Mailbox *mb, int doplumb, int *new) { - int fd, i, j, nd, nmd; char *month, *msg; + int fd, i, j, nd, nmd; Dir *d, *md; + static char err[ERRMAX]; fd = open(mb->path, OREAD); if(fd < 0){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); - return; + errstr(err, sizeof err); + return err; } nd = dirreadall(fd, &d); close(fd); @@ -249,7 +277,7 @@ readpbmbox(Mailbox *mb, int doplumb) for(j = 0; j < nmd; j++) if(mustshow(md[j].name)){ msg = smprint("%s/%s", month, md[j].name); - readpbmessage(mb, msg, doplumb); + readpbmessage(mb, msg, doplumb, new); free(msg); } } @@ -259,45 +287,45 @@ readpbmbox(Mailbox *mb, int doplumb) md = nil; } free(d); + return nil; } -static void -readpbvmbox(Mailbox *mb, int doplumb) +static char* +readpbvmbox(Mailbox *mb, int doplumb, int *new) { + char *data, *ln, *p, *nln, *msg; int fd, nr; long sz; - char *data, *ln, *p, *nln, *msg; Dir *d; + static char err[ERRMAX]; fd = open(mb->path, OREAD); if(fd < 0){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); - return; + errstr(err, sizeof err); + return err; } d = dirfstat(fd); if(d == nil){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); - close(fd); - return; + errstr(err, sizeof err); + return err; } sz = d->length; free(d); if(sz > 2 * 1024 * 1024){ sz = 2 * 1024 * 1024; - fprint(2, "%s: %s: bug: folder too big\n", argv0, mb->path); + fprint(2, "upas/fs: %s: bug: folder too big\n", mb->path); } data = malloc(sz+1); if(data == nil){ - close(fd); - fprint(2, "%s: no memory\n", argv0); - return; + errstr(err, sizeof err); + return err; } nr = readn(fd, data, sz); close(fd); if(nr < 0){ - fprint(2, "%s: %s: %r\n", argv0, mb->path); + errstr(err, sizeof err); free(data); - return; + return err; } data[nr] = 0; @@ -318,22 +346,24 @@ readpbvmbox(Mailbox *mb, int doplumb) *p = 0; msg = smprint("/mail/box/%s/msgs/%s", user, ln); if(msg == nil){ - fprint(2, "%s: no memory\n", argv0); + fprint(2, "upas/fs: malloc: %r\n"); continue; } - readpbmessage(mb, msg, doplumb); + readpbmessage(mb, msg, doplumb, new); free(msg); } free(data); + return nil; } static char* -readmbox(Mailbox *mb, int doplumb, int virt) +readmbox(Mailbox *mb, int doplumb, int virt, int *new) { + char *mberr; int fd; Dir *d; Message *m; - static char err[Errlen]; + static char err[128]; if(debug) fprint(2, "read mbox %s\n", mb->path); @@ -364,45 +394,45 @@ readmbox(Mailbox *mb, int doplumb, int virt) henter(PATH(0, Qtop), mb->name, (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); snprint(err, sizeof err, "reading '%s'", mb->path); - logmsg(err, nil); + logmsg(nil, err, nil); for(m = mb->root->part; m != nil; m = m->next) if(m->deleted == 0) m->deleted = -1; if(virt == 0) - readpbmbox(mb, doplumb); + mberr = readpbmbox(mb, doplumb, new); else - readpbvmbox(mb, doplumb); + mberr = readpbvmbox(mb, doplumb, new); /* * messages removed from the mbox; flag them to go. */ for(m = mb->root->part; m != nil; m = m->next) if(m->deleted < 0 && doplumb){ - m->inmbox = 0; - m->deleted = 1; - mailplumb(mb, m, 1); + delmessage(mb, m); + if(doplumb) + mailplumb(mb, m, 1); } - logmsg("mbox read", nil); - return nil; + logmsg(nil, "mbox read"); + return mberr; } static char* -mbsync(Mailbox *mb, int doplumb) +mbsync(Mailbox *mb, int doplumb, int *new) { char *rv; - rv = readmbox(mb, doplumb, 0); + rv = readmbox(mb, doplumb, 0, new); purgembox(mb, 0); return rv; } static char* -mbvsync(Mailbox *mb, int doplumb) +mbvsync(Mailbox *mb, int doplumb, int *new) { char *rv; - rv = readmbox(mb, doplumb, 1); + rv = readmbox(mb, doplumb, 1, new); purgembox(mb, 1); return rv; } diff --git a/sys/src/cmd/upas/fs/pop3.c b/sys/src/cmd/upas/fs/pop3.c index ac8cb3cd0..750646d9b 100644 --- a/sys/src/cmd/upas/fs/pop3.c +++ b/sys/src/cmd/upas/fs/pop3.c @@ -1,54 +1,64 @@ #include "common.h" -#include -#include #include #include #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 *freep; /* free this to free the strings below */ + char *host; + char *user; + char *port; - char *host; - char *user; - char *port; - - int ppop; - int refreshtime; - int debug; - int pipeline; - int encrypted; - int needtls; - int notls; - int needssl; - - // open network connection - Biobuf bin; - Biobuf bout; - int fd; - char *lastline; // from Brdstr + int ppop; + int refreshtime; + int debug; + int pipeline; + int encrypted; + int needtls; + int notls; + int needssl; + Biobuf bin; /* open network connection */ + Biobuf bout; + int fd; + char *lastline; /* from Brdstr */ Thumbprint *thumb; }; -char* +static int +mesgno(Message *m) +{ + Popm *a; + + a = m->aux; + return a->mesgno; +} + +static char* geterrstr(void) { - static char err[Errlen]; + static char err[64]; err[0] = '\0'; errstr(err, sizeof(err)); return err; } -// -// get pop3 response line , without worrying -// about multiline responses; the clients -// will deal with that. -// +/* + * get pop3 response line , without worrying + * about multiline responses; the clients + * will deal with that. + */ static int isokay(char *s) { @@ -62,15 +72,13 @@ pop3cmd(Pop *pop, char *fmt, ...) va_list va; va_start(va, fmt); - vseprint(buf, buf+sizeof(buf), fmt, va); + vseprint(buf, buf + sizeof buf, fmt, va); va_end(va); - p = buf+strlen(buf); - if(p > (buf+sizeof(buf)-3)) + p = buf + strlen(buf); + if(p > buf + sizeof buf - 3) sysfatal("pop3 command too long"); - - if(pop->debug) - fprint(2, "<- %s\n", buf); + pdprint(pop, "<- %s\n", buf); strcpy(p, "\r\n"); Bwrite(&pop->bout, buf, strlen(buf)); Bflush(&pop->bout); @@ -83,20 +91,19 @@ pop3resp(Pop *pop) char *p; alarm(60*1000); - s = Brdstr(&pop->bin, '\n', 0); - alarm(0); - if(s == nil){ + if((s = Brdstr(&pop->bin, '\n', 0)) == nil){ close(pop->fd); pop->fd = -1; + alarm(0); return "unexpected eof"; } + alarm(0); - p = s+strlen(s)-1; + p = s + strlen(s) - 1; while(p >= s && (*p == '\r' || *p == '\n')) *p-- = '\0'; - if(pop->debug) - fprint(2, "-> %s\n", s); + pdprint(pop, "-> %s\n", s); free(pop->lastline); pop->lastline = s; return s; @@ -119,40 +126,35 @@ pop3pushtls(Pop *pop) int fd; uchar digest[SHA1dlen]; TLSconn conn; - char *err; - err = nil; memset(&conn, 0, sizeof conn); // conn.trace = pop3log; fd = tlsClient(pop->fd, &conn); - if(fd < 0){ - err = "tls error"; - goto out; - } - pop->fd = fd; - Binit(&pop->bin, pop->fd, OREAD); - Binit(&pop->bout, pop->fd, OWRITE); + if(fd < 0) + return "tls error"; if(conn.cert==nil || conn.certlen <= 0){ - err = "server did not provide TLS certificate"; - goto out; + close(fd); + return "server did not provide TLS certificate"; } sha1(conn.cert, conn.certlen, digest, nil); if(!pop->thumb || !okThumbprint(digest, pop->thumb)){ - fmtinstall('H', encodefmt); - fprint(2, "upas/fs pop3: server certificate %.*H not recognized\n", SHA1dlen, digest); - err = "bad server certificate"; - goto out; + close(fd); + free(conn.cert); + eprint("pop3: server certificate %.*H not recognized\n", SHA1dlen, digest); + return "bad server certificate"; } - pop->encrypted = 1; -out: - free(conn.sessionID); free(conn.cert); - return err; + close(pop->fd); + pop->fd = fd; + pop->encrypted = 1; + Binit(&pop->bin, pop->fd, OREAD); + Binit(&pop->bout, pop->fd, OWRITE); + return nil; } -// -// get capability list, possibly start tls -// +/* + * get capability list, possibly start tls + */ static char* pop3capa(Pop *pop) { @@ -186,9 +188,9 @@ pop3capa(Pop *pop) return nil; } -// -// log in using APOP if possible, password if allowed by user -// +/* + * log in using APOP if possible, password if allowed by user + */ static char* pop3login(Pop *pop) { @@ -207,10 +209,10 @@ pop3login(Pop *pop) else ubuf[0] = '\0'; - // look for apop banner - if(pop->ppop==0 && (p = strchr(s, '<')) && (q = strchr(p+1, '>'))) { + /* look for apop banner */ + if(pop->ppop == 0 && (p = strchr(s, '<')) && (q = strchr(p + 1, '>'))) { *++q = '\0'; - if((n=auth_respond(p, q-p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s", + if((n=auth_respond(p, q - p, user, sizeof user, buf, sizeof buf, auth_getkey, "proto=apop role=client server=%q%s", pop->host, ubuf)) < 0) return "factotum failed"; if(user[0]=='\0') @@ -253,83 +255,75 @@ pop3login(Pop *pop) } } -// -// dial and handshake with pop server -// +/* + * dial and handshake with pop server + */ static char* pop3dial(Pop *pop) { char *err; - if(pop->fd >= 0){ - close(pop->fd); - pop->fd = -1; - } if((pop->fd = dial(netmkaddr(pop->host, "net", pop->needssl ? "pop3s" : "pop3"), 0, 0, 0)) < 0) return geterrstr(); if(pop->needssl){ if((err = pop3pushtls(pop)) != nil) - goto Out; + return err; }else{ Binit(&pop->bin, pop->fd, OREAD); Binit(&pop->bout, pop->fd, OWRITE); } - err = pop3login(pop); -Out: - if(err != nil){ - if(pop->fd >= 0){ - close(pop->fd); - pop->fd = -1; - } + + if(err = pop3login(pop)) { + close(pop->fd); + return err; } - return err; + + return nil; } -// -// close connection -// +/* + * close connection + */ static void pop3hangup(Pop *pop) { - if(pop->fd < 0) - return; pop3cmd(pop, "QUIT"); pop3resp(pop); close(pop->fd); - pop->fd = -1; } -// -// download a single message -// +/* + * download a single message + */ static char* -pop3download(Pop *pop, Message *m) +pop3download(Mailbox *mb, Pop *pop, Message *m) { char *s, *f[3], *wp, *ep; - char sdigest[SHA1dlen*2+1]; - int i, l, sz; + int l, sz, pos, n; + Popm *a; + a = m->aux; if(!pop->pipeline) - pop3cmd(pop, "LIST %d", m->mesgno); + pop3cmd(pop, "LIST %d", a->mesgno); if(!isokay(s = pop3resp(pop))) return s; if(tokenize(s, f, 3) != 3) return "syntax error in LIST response"; - if(atoi(f[1]) != m->mesgno) + if(atoi(f[1]) != a->mesgno) return "out of sync with pop3 server"; - sz = atoi(f[2])+200; /* 200 because the plan9 pop3 server lies */ + sz = atoi(f[2]) + 200; /* 200 because the plan9 pop3 server lies */ if(sz == 0) return "invalid size in LIST response"; - m->start = wp = emalloc(sz+1); - ep = wp+sz; + m->start = wp = emalloc(sz + 1); + ep = wp + sz; if(!pop->pipeline) - pop3cmd(pop, "RETR %d", m->mesgno); + pop3cmd(pop, "RETR %d", a->mesgno); if(!isokay(s = pop3resp(pop))) { m->start = nil; free(wp); @@ -347,7 +341,7 @@ pop3download(Pop *pop, Message *m) if(strcmp(s, ".") == 0) break; - l = strlen(s)+1; + l = strlen(s) + 1; if(s[0] == '.') { s++; l--; @@ -356,14 +350,17 @@ pop3download(Pop *pop, Message *m) * grow by 10%/200bytes - some servers * lie about message sizes */ - if(wp+l > ep) { - int pos = wp - m->start; - sz += ((sz / 10) < 200)? 200: sz/10; - m->start = erealloc(m->start, sz+1); - wp = m->start+pos; - ep = m->start+sz; + if(wp + l > ep) { + pos = wp - m->start; + n = sz/10; + if(n < 200) + n = 200; + sz += n; + m->start = erealloc(m->start, sz + 1); + wp = m->start + pos; + ep = m->start + sz; } - memmove(wp, s, l-1); + memmove(wp, s, l - 1); wp[l-1] = '\n'; wp += l; } @@ -373,40 +370,41 @@ pop3download(Pop *pop, Message *m) m->end = wp; - // make sure there's a trailing null - // (helps in body searches) + /* + * make sure there's a trailing null + * (helps in body searches) + */ *m->end = 0; m->bend = m->rbend = m->end; m->header = m->start; - - // digest message - sha1((uchar*)m->start, m->end - m->start, m->digest, nil); - for(i = 0; i < SHA1dlen; i++) - sprint(sdigest+2*i, "%2.2ux", m->digest[i]); - m->sdigest = s_copy(sdigest); + m->size = m->end - m->start; + if(m->digest == nil) + digestmessage(mb, m); return nil; } -// -// check for new messages on pop server -// UIDL is not required by RFC 1939, but -// netscape requires it, so almost every server supports it. -// we'll use it to make our lives easier. -// +/* + * check for new messages on pop server + * UIDL is not required by RFC 1939, but + * netscape requires it, so almost every server supports it. + * we'll use it to make our lives easier. + */ static char* -pop3read(Pop *pop, Mailbox *mb, int doplumb) +pop3read(Pop *pop, Mailbox *mb, int doplumb, int *new) { char *s, *p, *uidl, *f[2]; - int mesgno, ignore, nnew; + int mno, ignore, nnew; Message *m, *next, **l; + Popm *a; - // Some POP servers disallow UIDL if the maildrop is empty. + *new = 0; + /* Some POP servers disallow UIDL if the maildrop is empty. */ pop3cmd(pop, "STAT"); if(!isokay(s = pop3resp(pop))) return s; - // fetch message listing; note messages to grab + /* fetch message listing; note messages to grab */ l = &mb->root->part; if(strncmp(s, "+OK 0 ", 6) != 0) { pop3cmd(pop, "UIDL"); @@ -421,25 +419,32 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb) if(tokenize(p, f, 2) != 2) continue; - mesgno = atoi(f[0]); + mno = atoi(f[0]); uidl = f[1]; - if(strlen(uidl) > 75) // RFC 1939 says 70 characters max + if(strlen(uidl) > 75) /* RFC 1939 says 70 characters max */ continue; ignore = 0; while(*l != nil) { - if(strcmp((*l)->uidl, uidl) == 0) { - // matches mail we already have, note mesgno for deletion - (*l)->mesgno = mesgno; + a = (*l)->aux; + if(strcmp((*l)->idxaux, uidl) == 0){ + if(a == 0){ + m = *l; + m->mallocd = 1; + m->inmbox = 1; + m->aux = a = emalloc(sizeof *a); + } + /* matches mail we already have, note mesgno for deletion */ + a->mesgno = mno; ignore = 1; l = &(*l)->next; break; - } else { - // old mail no longer in box mark deleted + }else{ + /* old mail no longer in box mark deleted */ if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; - (*l)->deleted = 1; + (*l)->deleted = Deleted; l = &(*l)->next; } } @@ -449,30 +454,31 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb) m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; - m->mesgno = mesgno; - strcpy(m->uidl, uidl); + m->idxaux = strdup(uidl); + m->aux = a = emalloc(sizeof *a); + a->mesgno = mno; - // chain in; will fill in message later + /* chain in; will fill in message later */ *l = m; l = &m->next; } } - // whatever is left has been removed from the mbox, mark as deleted + /* whatever is left has been removed from the mbox, mark as deleted */ while(*l != nil) { if(doplumb) mailplumb(mb, *l, 1); (*l)->inmbox = 0; - (*l)->deleted = 1; + (*l)->deleted = Disappear; l = &(*l)->next; } - // download new messages + /* download new messages */ nnew = 0; if(pop->pipeline){ switch(rfork(RFPROC|RFMEM)){ case -1: - fprint(2, "rfork: %r\n"); + eprint("pop3: rfork: %r\n"); pop->pipeline = 0; default: @@ -480,49 +486,41 @@ pop3read(Pop *pop, Mailbox *mb, int doplumb) case 0: for(m = mb->root->part; m != nil; m = m->next){ - if(m->start != nil) + if(m->start != nil || m->deleted) continue; - Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", m->mesgno, m->mesgno); + Bprint(&pop->bout, "LIST %d\r\nRETR %d\r\n", mesgno(m), mesgno(m)); } Bflush(&pop->bout); - _exits(nil); + _exits(""); } } for(m = mb->root->part; m != nil; m = next) { next = m->next; - if(m->start != nil) + if(m->start != nil || m->deleted) continue; - - if(s = pop3download(pop, m)) { - // message disappeared? unchain - fprint(2, "download %d: %s\n", m->mesgno, s); + if(s = pop3download(mb, pop, m)) { + /* message disappeared? unchain */ + eprint("pop3: download %d: %s\n", mesgno(m), s); delmessage(mb, m); mb->root->subname--; continue; } nnew++; - parse(m, 0, mb, 1); - - if(doplumb) - mailplumb(mb, m, 0); + parse(mb, m, 1, 0); + newcachehash(mb, m, doplumb); + putcache(mb, m); } if(pop->pipeline) waitpid(); - - if(nnew || mb->vers == 0) { - mb->vers++; - henter(PATH(0, Qtop), mb->name, - (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb); - } - + *new = nnew; return nil; } -// -// delete marked messages -// +/* + * delete marked messages + */ static void pop3purge(Pop *pop, Mailbox *mb) { @@ -531,7 +529,7 @@ pop3purge(Pop *pop, Mailbox *mb) if(pop->pipeline){ switch(rfork(RFPROC|RFMEM)){ case -1: - fprint(2, "rfork: %r\n"); + eprint("pop3: rfork: %r\n"); pop->pipeline = 0; default: @@ -542,11 +540,11 @@ pop3purge(Pop *pop, Mailbox *mb) next = m->next; if(m->deleted && m->refs == 0){ if(m->inmbox) - Bprint(&pop->bout, "DELE %d\r\n", m->mesgno); + Bprint(&pop->bout, "DELE %d\r\n", mesgno(m)); } } Bflush(&pop->bout); - _exits(nil); + _exits(""); } } for(m = mb->root->part; m != nil; m = next) { @@ -554,7 +552,7 @@ pop3purge(Pop *pop, Mailbox *mb) if(m->deleted && m->refs == 0) { if(m->inmbox) { if(!pop->pipeline) - pop3cmd(pop, "DELE %d", m->mesgno); + pop3cmd(pop, "DELE %d", mesgno(m)); if(isokay(pop3resp(pop))) delmessage(mb, m); } else @@ -564,19 +562,21 @@ pop3purge(Pop *pop, Mailbox *mb) } -// connect to pop3 server, sync mailbox +/* connect to pop3 server, sync mailbox */ static char* -pop3sync(Mailbox *mb, int doplumb) +pop3sync(Mailbox *mb, int doplumb, int *new) { char *err; Pop *pop; pop = mb->aux; + if(err = pop3dial(pop)) { mb->waketime = time(0) + pop->refreshtime; return err; } - if((err = pop3read(pop, mb, doplumb)) == nil){ + + if((err = pop3read(pop, mb, doplumb, new)) == nil){ pop3purge(pop, mb); mb->d->atime = mb->d->mtime = time(0); } @@ -629,7 +629,7 @@ pop3ctl(Mailbox *mb, int argc, char **argv) return Epop3ctl; } -// free extra memory associated with mb +/* free extra memory associated with mb */ static void pop3close(Mailbox *mb) { @@ -640,9 +640,18 @@ pop3close(Mailbox *mb) free(pop); } -// -// open mailboxes of the form /pop/host/user or /apop/host/user -// +static char* +mkmbox(Pop *pop, char *p, char *e) +{ + p = seprint(p, e, "%s/box/%s/pop.%s", MAILROOT, getlog(), pop->host); + if(pop->user && strcmp(pop->user, getlog())) + p = seprint(p, e, ".%s", pop->user); + return p; +} + +/* + * open mailboxes of the form /pop/host/user or /apop/host/user + */ char* pop3mbox(Mailbox *mb, char *path) { @@ -650,7 +659,6 @@ pop3mbox(Mailbox *mb, char *path) int nf, apop, ppop, popssl, apopssl, apoptls, popnotls, apopnotls, poptls; Pop *pop; - quotefmtinstall(); popssl = strncmp(path, "/pops/", 6) == 0; apopssl = strncmp(path, "/apops/", 7) == 0; poptls = strncmp(path, "/poptls/", 8) == 0; @@ -673,8 +681,7 @@ pop3mbox(Mailbox *mb, char *path) return "bad pop3 path syntax /[a]pop[tls|ssl]/system[/user]"; } - pop = emalloc(sizeof(*pop)); - pop->fd = -1; + pop = emalloc(sizeof *pop); pop->freep = path; pop->host = f[2]; if(nf < 4) @@ -688,12 +695,13 @@ pop3mbox(Mailbox *mb, char *path) pop->notls = popnotls || apopnotls; pop->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude"); + mkmbox(pop, mb->path, mb->path + sizeof mb->path); mb->aux = pop; mb->sync = pop3sync; mb->close = pop3close; mb->ctl = pop3ctl; - mb->d = emalloc(sizeof(*mb->d)); - + mb->d = emalloc(sizeof *mb->d); + mb->addfrom = 1; return nil; } diff --git a/sys/src/cmd/upas/fs/readdir.c b/sys/src/cmd/upas/fs/readdir.c deleted file mode 100644 index 42028d4c1..000000000 --- a/sys/src/cmd/upas/fs/readdir.c +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -void -main(void) -{ - Dir d; - int fd, n; - - fd = open("/mail/fs", OREAD); - while((n = dirread(fd, &d, sizeof(d))) > 0){ - print("%s\n", d.name); - } - print("n = %d\n", n); -} diff --git a/sys/src/cmd/upas/fs/ref.c b/sys/src/cmd/upas/fs/ref.c new file mode 100644 index 000000000..f3cf078b3 --- /dev/null +++ b/sys/src/cmd/upas/fs/ref.c @@ -0,0 +1,100 @@ +#include "common.h" +#include +#include "dat.h" + +/* all the data that's fit to cache */ + +typedef struct{ + char *s; + int l; + ulong ref; +}Refs; + +Refs *rtab; +int nrtab; +int nralloc; + +int +newrefs(char *s) +{ + int l, i; + Refs *r; + + l = strlen(s); + for(i = 0; i < nrtab; i++){ + r = rtab + i; + if(r->ref == 0) + goto enter; + if(l == r->l && strcmp(r->s, s) == 0){ + r->ref++; + return i; + } + } + if(nrtab == nralloc) + rtab = erealloc(rtab, sizeof *rtab*(nralloc += 50)); + nrtab = i + 1; +enter: + r = rtab + i; + r->s = strdup(s); + r->l = l; + r->ref = 1; + return i; +} + +void +delrefs(int i) +{ + Refs *r; + + r = rtab + i; + if(--r->ref > 0) + return; + free(r->s); + memset(r, 0, sizeof *r); +} + +void +refsinit(void) +{ + newrefs(""); +} + +static char *sep = "--------\n"; + +int +prrefs(Biobuf *b) +{ + int i, n; + + n = 0; + for(i = 1; i < nrtab; i++){ + if(rtab[i].ref == 0) + continue; + Bprint(b, "%s ", rtab[i].s); + if(n++%8 == 7) + Bprint(b, "\n"); + } + if(n%8 != 7) + Bprint(b, "\n"); + Bprint(b, sep); + return 0; +} + +int +rdrefs(Biobuf *b) +{ + char *f[10], *s; + int i, n; + + while(s = Brdstr(b, '\n', 1)){ + if(strcmp(s, sep) == 0){ + free(s); + return 0; + } + n = tokenize(s, f, nelem(f)); + for(i = 0; i < n; i++) + newrefs(f[i]); + free(s); + } + return -1; +} diff --git a/sys/src/cmd/upas/fs/remove.c b/sys/src/cmd/upas/fs/remove.c new file mode 100644 index 000000000..5bafe8a70 --- /dev/null +++ b/sys/src/cmd/upas/fs/remove.c @@ -0,0 +1,141 @@ +#include "common.h" +#include "dat.h" + +#define deprint(...) /* eprint(__VA_ARGS__) */ + +extern int dirskip(Dir*, uvlong*); + +static int +ismbox(char *path) +{ + char buf[512]; + int fd, r; + + fd = open(path, OREAD); + if(fd == -1) + return 0; + r = 1; + if(read(fd, buf, sizeof buf) < 28 + 5) + r = 0; + else if(strncmp(buf, "From ", 5)) + r = 0; + close(fd); + return r; +} + +static int +isindex(Dir *d) +{ + char *p; + + p = strrchr(d->name, '.'); + if(!p) + return -1; + if(strcmp(p, ".idx") || strcmp(p, ".imp")) + return 1; + return 0; +} + +static int +idiotcheck(char *path, Dir *d, int getindex) +{ + uvlong v; + + if(d->mode & DMDIR) + return 0; + if(strncmp(d->name, "L.", 2) == 0) + return 0; + if(getindex && isindex(d)) + return 0; + if(!dirskip(d, &v) || ismbox(path)) + return 0; + return -1; +} + +int +vremove(char *buf) +{ + deprint("rm %s\n", buf); + return remove(buf); +} + +static int +rm(char *dir, int flags, int level) +{ + char buf[Pathlen]; + int i, n, r, fd, isdir, rflag; + Dir *d; + + d = dirstat(dir); + isdir = d->mode & DMDIR; + free(d); + if(!isdir) + return 0; + fd = open(dir, OREAD); + if(fd == -1) + return -1; + n = dirreadall(fd, &d); + close(fd); + r = 0; + rflag = flags & Rrecur; + for(i = 0; i < n; i++){ + snprint(buf, sizeof buf, "%s/%s", dir, d[i].name); + if(rflag) + r |= rm(buf, flags, level + 1); + if(idiotcheck(buf, d + i, level + rflag) == -1) + continue; + if(vremove(buf) != 0) + r = -1; + } + free(d); + return r; +} + +void +rmidx(char *buf, int flags) +{ + char buf2[Pathlen]; + + snprint(buf2, sizeof buf2, "%s.idx", buf); + vremove(buf2); + if((flags & Rtrunc) == 0){ + snprint(buf2, sizeof buf2, "%s.imp", buf); + vremove(buf2); + } +} + +char* +localremove(Mailbox *mb, int flags) +{ + char *msg, *path; + int r, isdir; + Dir *d; + static char err[2*Pathlen]; + + path = mb->path; + if((d = dirstat(path)) == 0){ + snprint(err, sizeof err, "%s: doesn't exist\n", path); + return 0; + } + isdir = d->mode & DMDIR; + free(d); + msg = "deleting"; + if(flags & Rtrunc) + msg = "truncating"; + deprint("%s: %s\n", msg, path); + + /* must match folder.c:/^openfolder */ + r = rm(path, flags, 0); + if((flags & Rtrunc) == 0) + r = vremove(path); + else if(!isdir) + close(r = open(path, OWRITE|OTRUNC)); + + rmidx(path, flags); + + if(r == -1){ + snprint(err, sizeof err, "%s: can't %s\n", path, msg); + return err; + } + return 0; +} diff --git a/sys/src/cmd/upas/fs/rename.c b/sys/src/cmd/upas/fs/rename.c new file mode 100644 index 000000000..6d5337643 --- /dev/null +++ b/sys/src/cmd/upas/fs/rename.c @@ -0,0 +1,234 @@ +#include "common.h" +#include "dat.h" + +#define deprint(...) /* eprint(__VA_ARGS__) */ + +static int +delivery(char *s) +{ + if(strncmp(s, "/mail/fs/", 9) == 0) + if((s = strrchr(s, '/')) && strcmp(s + 1, "mbox") == 0) + return 1; + return 0; +} + +static int +isdir(char *s) +{ + int isdir; + Dir *d; + + d = dirstat(s); + isdir = d && d->mode & DMDIR; + free(d); + return isdir; +} + +static int +docreate(char *file, int perm) +{ + int fd; + Dir ndir; + Dir *d; + + fd = create(file, OREAD, perm); + if(fd < 0) + return -1; + d = dirfstat(fd); + if(d == nil) + return -1; + nulldir(&ndir); + ndir.mode = perm; + ndir.gid = d->uid; + dirfwstat(fd, &ndir); + close(fd); + return 0; +} + +static int +rollup(char *s) +{ + char *p; + int mode; + + if(access(s, 0) == 0) + return -1; + + /* + * if we can deliver to this mbox, it needs + * to be read/execable all the way down + */ + mode = 0711; + if(delivery(s)) + mode = 0755; + + for(p = s; p; p++) { + if(*p == '/') + continue; + p = strchr(p, '/'); + if(p == 0) + break; + *p = 0; + if(access(s, 0) != 0) + if(docreate(s, DMDIR|mode) < 0) + return -1; + *p = '/'; + } + return 0; +} + +static int +copyfile(char *a, char *b, int flags) +{ + char *s; + int fd, fd1, mode, i, m, n, r; + Dir *d; + + mode = 0600; + if(delivery(b)) + mode = 0622; + fd = open(a, OREAD); + fd1 = create(b, OWRITE|OEXCL, DMEXCL|mode); + if(fd == -1 || fd1 == -1){ + close(fd); + close(fd1); + return -1; + } + s = malloc(64*1024); + i = m = 0; + while((n = read(fd, s, sizeof s)) > 0) + for(i = 0; i != n; i += m) + if((m = write(fd1, s + i, n - i)) == -1) + goto lose; +lose: + free(s); + close(fd); + close(fd1); + if(i != m || n != 0) + return -1; + + if((flags & Rtrunc) == 0) + return vremove(a); + + fd = open(a, ORDWR); + if(fd == -1) + return -1; + r = -1; + if(d = dirfstat(fd)){ + d->length = 0; + r = dirfwstat(fd, d); + free(d); + } + return r; +} + +static int +copydir(char *a, char *b, int flags) +{ + char *p, buf[Pathlen], ns[Pathlen], owd[Pathlen]; + int fd, fd1, len, i, n, r; + Dir *d; + + fd = open(a, OREAD); + fd1 = create(b, OWRITE|OEXCL, DMEXCL|0777); + close(fd1); + if(fd == -1 || fd1 == -1){ + close(fd); + return -1; + } + + /* fixup mode */ + if(delivery(b)) + if(d = dirfstat(fd)){ + d->mode |= 0777; + dirfwstat(fd, d); + free(d); + } + + getwd(owd, sizeof owd); + if(chdir(a) == -1) + return -1; + + p = seprint(buf, buf + sizeof buf, "%s/", b); + len = buf + sizeof buf - p; + n = dirreadall(fd, &d); + r = 0; + for(i = 0; i < n; i++){ + snprint(p, len, "%s", d[i].name); + if(d->mode & DMDIR){ + snprint(ns, sizeof ns, "%s/%s", a, d[i].name); + r |= copydir(ns, buf, 0); + chdir(a); + }else + r |= copyfile(d[i].name, buf, 0); + if(r) + break; + } + free(d); + + if((flags & Rtrunc) == 0) + r |= vremove(a); + + chdir(owd); + return r; +} + +int +rename(char *a, char *b, int flags) +{ + char *e0, *e1; + int fd, r; + Dir *d; + + e0 = strrchr(a, '/'); + e1 = strrchr(b, '/'); + if(!e0 || !e1 || !e1[1]) + return -1; + + if(e0 - a == e1 - b) + if(strncmp(a, b, e0 - a) == 0) + if(!delivery(a) || isdir(a)){ + fd = open(a, OREAD); + if(!(d = dirfstat(fd))){ + close(fd); + return -1; + } + d->name = e1 + 1; + r = dirfwstat(fd, d); + deprint("rename %s %s -> %d\n", a, b, r); + if(r != -1 && flags & Rtrunc) + close(create(a, OWRITE, d->mode)); + free(d); + close(fd); + return r; + } + + if(rollup(b) == -1) + return -1; + if(isdir(a)) + return copydir(a, b, flags); + return copyfile(a, b, flags); +} + +char* +localrename(Mailbox *mb, char *p2, int flags) +{ + char *path, *msg; + int r; + static char err[2*Pathlen]; + + path = mb->path; + msg = "rename"; + if(flags & Rtrunc) + msg = "move"; + deprint("localrename %s: %s %s\n", msg, path, p2); + + r = rename(path, p2, flags); + if(r == -1){ + snprint(err, sizeof err, "%s: can't %s\n", path, msg); + deprint("localrename %s\n", err); + return err; + } + close(r); + return 0; +} diff --git a/sys/src/cmd/upas/fs/rfc2047-test b/sys/src/cmd/upas/fs/rfc2047-test deleted file mode 100644 index b536c4b3e..000000000 --- a/sys/src/cmd/upas/fs/rfc2047-test +++ /dev/null @@ -1,28 +0,0 @@ -From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006 -From: =?US-ASCII?Q?Keith_Moore?= -To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= -CC: =?ISO-8859-1?Q?Andr=E9?= Pirard -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?= -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 -Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se -From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= -Subject: Re: RFC-HDR care and feeding - -From moore@cs.utk.edu Tue Mar 28 21:58:10 CST 2006 -From: Nathaniel Borenstein - (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) -To: Greg Vaudreuil , Ned Freed - , Keith Moore -Subject: Test of new header generator -MIME-Version: 1.0 -Content-type: text/plain; charset=ISO-8859-1 - - diff --git a/sys/src/cmd/upas/fs/seg.c b/sys/src/cmd/upas/fs/seg.c new file mode 100644 index 000000000..394b66ad3 --- /dev/null +++ b/sys/src/cmd/upas/fs/seg.c @@ -0,0 +1,164 @@ +#include "common.h" +#include +#include "dat.h" + +/* + * unnatural acts with virtual memory + */ + +typedef struct{ + int ref; + char *va; + long sz; +}S; + +static S s[15]; /* 386 only gives 4 */ +static int nstab = nelem(s); +static long ssem = 1; +//static ulong thresh = 10*1024*1024; +static ulong thresh = 1024; + +void* +segmalloc(ulong sz) +{ + int i, j; + void *va; + + if(sz < thresh) + return emalloc(sz); + semacquire(&ssem, 1); + for(i = 0; i < nstab; i++) + if(s[i].ref == 0) + goto found; +notfound: + /* errstr not informative; assume we hit seg limit */ + for(j = nstab - 1; j >= i; j--) + if(s[j].ref) + break; + nstab = j; + semrelease(&ssem, 1); + return emalloc(sz); +found: + /* + * the system doesn't leave any room for expansion + */ + va = segattach(SG_CEXEC, "memory", 0, sz + sz/10 + 4096); + if(va == 0) + goto notfound; + s[i].ref++; + s[i].va = va; + s[i].sz = sz; + semrelease(&ssem, 1); + memset(va, 0, sz); + return va; +} + +void +segmfree(void *va) +{ + char *a; + int i; + + a = va; + for(i = 0; i < nstab; i++) + if(s[i].va == a) + goto found; + free(va); + return; +found: + semacquire(&ssem, 1); + s[i].ref--; + s[i].va = 0; + s[i].sz = 0; + semrelease(&ssem, 1); +} + +void* +segreallocfixup(int i, ulong sz) +{ + char buf[ERRMAX]; + void *va, *ova; + + rerrstr(buf, sizeof buf); + if(strstr(buf, "segments overlap") == 0) + sysfatal("segibrk: %r"); + va = segattach(SG_CEXEC, "memory", 0, sz); + if(va == 0) + sysfatal("segattach: %r"); + ova = s[i].va; +fprint(2, "fix memcpy(%p, %p, %lud)\n", va, ova, s[i].sz); + memcpy(va, ova, s[i].sz); + s[i].va = va; + s[i].sz = sz; + segdetach(ova); + return va; +} + +void* +segrealloc(void *va, ulong sz) +{ + char *a; + int i; + ulong sz0; + +fprint(2, "segrealloc %p %lud\n", va, sz); + if(va == 0) + return segmalloc(sz); + a = va; + for(i = 0; i < nstab; i++) + if(s[i].va == a) + goto found; + if(sz >= thresh) + if(a = segmalloc(sz)){ + sz0 = msize(va); + memcpy(a, va, sz0); +fprint(2, "memset(%p, 0, %lud)\n", a + sz0, sz - sz0); + memset(a + sz0, 0, sz - sz0); + return a; + } + return realloc(va, sz); +found: + sz0 = s[i].sz; +fprint(2, "segbrk(%p, %p)\n", s[i].va, s[i].va + sz); + va = segbrk(s[i].va, s[i].va + sz); + if(va == (void*)-1 || va < end) + return segreallocfixup(i, sz); + a = va; + if(sz > sz0) +{ +fprint(2, "memset(%p, 0, %lud)\n", a + sz0, sz - sz0); + memset(a + sz0, 0, sz - sz0); +} + s[i].va = va; + s[i].sz = sz; + return va; +} + +void* +emalloc(ulong n) +{ + void *p; +fprint(2, "emalloc %lud\n", n); + p = mallocz(n, 1); + if(!p) + sysfatal("malloc %lud: %r", n); + setmalloctag(p, getcallerpc(&n)); + return p; +} + +void +main(void) +{ + char *p; + int i; + ulong sz; + + p = 0; + for(i = 0; i < 6; i++){ + sz = i*512; + p = segrealloc(p, sz); + memset(p, 0, sz); + } + segmfree(p); + exits(""); +} diff --git a/sys/src/cmd/upas/fs/strtotm.c b/sys/src/cmd/upas/fs/strtotm.c index bcf0bcee0..6c9f7d3b6 100644 --- a/sys/src/cmd/upas/fs/strtotm.c +++ b/sys/src/cmd/upas/fs/strtotm.c @@ -1,11 +1,10 @@ #include #include -#include 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= 'A' && p[0] <= 'Z') + if(p[1] >= 'A' && p[1] <= 'Z') + if(p[2] == 'T'){ + strecpy(tm.zone, tm.zone + 4, p); continue; } - if(p[0]=='+'||p[0]=='-') - if(q-p==5 && strspn(p+1, "0123456789") == 4){ - delta = (((p[1]-'0')*10+p[2]-'0')*60+(p[3]-'0')*10+p[4]-'0')*60; + if(p[0] == '+'||p[0] == '-') + if(q - p == 5 && strspn(p + 1, "0123456789") == 4){ + delta = (((p[1] - '0')*10 + p[2] - '0')*60 + (p[3] - '0')*10 + p[4] - '0')*60; if(p[0] == '-') delta = -delta; continue; } - if(strspn(p, "0123456789") == q-p){ + if(strspn(p, "0123456789") == q - p){ j = strtol(p, nil, 10); - if(1 <= j && j <= 31) + if(j >= 1 && j <= 31) tm.mday = j; if(j >= 1900) - tm.year = j-1900; + tm.year = j - 1900; + continue; } + //eprint("strtotm: garbage %.*s\n", q - p, p); } - - if(tm.mon<0 || tm.year<0 - || tm.hour<0 || tm.min<0 - || tm.mday<0) + if(tm.mon < 0 || tm.year < 0 + || tm.hour < 0 || tm.min < 0 + || tm.mday < 0) return -1; - *tmp = *localtime(tm2sec(&tm)-delta); + *t = *localtime(tm2sec(&tm) - delta); return 0; } diff --git a/sys/src/cmd/upas/fs/tester.c b/sys/src/cmd/upas/fs/tester.c deleted file mode 100644 index 3d24012ef..000000000 --- a/sys/src/cmd/upas/fs/tester.c +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include -#include -#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 index 000000000..dad825bfd --- /dev/null +++ b/sys/src/cmd/upas/imap4d/auth.c @@ -0,0 +1,280 @@ +#include "imap4d.h" +#include + +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 index 000000000..b6d1a2f3f --- /dev/null +++ b/sys/src/cmd/upas/imap4d/capability @@ -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 index 000000000..5d6376935 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/copy.c @@ -0,0 +1,248 @@ +#include "imap4d.h" +#include + +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/ip/imap4d/csquery.c b/sys/src/cmd/upas/imap4d/csquery.c similarity index 67% rename from sys/src/cmd/ip/imap4d/csquery.c rename to sys/src/cmd/upas/imap4d/csquery.c index d5736160b..44a86c893 100644 --- a/sys/src/cmd/ip/imap4d/csquery.c +++ b/sys/src/cmd/upas/imap4d/csquery.c @@ -1,8 +1,5 @@ #include #include -#include -#include -#include "imap4d.h" /* * query the connection server @@ -10,8 +7,7 @@ char* csquery(char *attr, char *val, char *rattr) { - char token[64+4]; - char buf[256], *p, *sp; + char token[64 + 4], buf[256], *p, *sp; int fd, n; if(val == nil || val[0] == 0) @@ -21,14 +17,14 @@ csquery(char *attr, char *val, char *rattr) return nil; fprint(fd, "!%s=%s", attr, val); seek(fd, 0, 0); - snprint(token, sizeof(token), "%s=", rattr); + snprint(token, sizeof token, "%s=", rattr); for(;;){ - n = read(fd, buf, sizeof(buf)-1); + 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)){ + if(p != nil && (p == buf || p[-1] == 0)){ close(fd); sp = strchr(p, ' '); if(sp) @@ -36,7 +32,7 @@ csquery(char *attr, char *val, char *rattr) p = strchr(p, '='); if(p == nil) return nil; - return strdup(p+1); + return strdup(p + 1); } } close(fd); diff --git a/sys/src/cmd/ip/imap4d/date.c b/sys/src/cmd/upas/imap4d/date.c similarity index 77% rename from sys/src/cmd/ip/imap4d/date.c rename to sys/src/cmd/upas/imap4d/date.c index 9c9c4664a..9692203ca 100644 --- a/sys/src/cmd/ip/imap4d/date.c +++ b/sys/src/cmd/upas/imap4d/date.c @@ -1,193 +1,13 @@ -#include -#include -#include -#include #include "imap4d.h" -char * -wdayname[7] = -{ +static char *wdayname[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -char * -monname[12] = -{ +static char *monname[] = { "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. @@ -195,7 +15,7 @@ date2tm(Tm *tm, char *date) * | [+-][0-9][0-9][0-9][0-9] * zones is the rfc-822 list of time zone names */ -static NamedInt zones[] = +static Namedint zones[] = { {"A", -1 * 3600}, {"B", -2 * 3600}, @@ -232,18 +52,17 @@ static NamedInt zones[] = {"X", +11 * 3600}, {"Y", +12 * 3600}, {"Z", 0}, - {nil, 0} }; static void zone2tm(Tm *tm, char *s) { - Tm aux, *atm; int i; + Tm aux, *atm; if(*s == '+' || *s == '-'){ i = strtol(s, &s, 10); - tm->tzoff = (i / 100) * 3600 + i % 100; + tm->tzoff = (i/100)*3600 + i%100; strncpy(tm->zone, "", 4); return; } @@ -252,9 +71,9 @@ zone2tm(Tm *tm, char *s) * look it up in the standard rfc822 table */ strncpy(tm->zone, s, 3); - tm->zone[3] = '\0'; + tm->zone[3] = 0; tm->tzoff = 0; - for(i = 0; zones[i].name != nil; i++){ + for(i = 0; i < nelem(zones); i++){ if(cistrcmp(zones[i].name, s) == 0){ tm->tzoff = zones[i].v; return; @@ -306,3 +125,139 @@ dateindex(char *d, char **tab, int n) 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 index 000000000..4871eea8d --- /dev/null +++ b/sys/src/cmd/upas/imap4d/debug.c @@ -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/ip/imap4d/fetch.c b/sys/src/cmd/upas/imap4d/fetch.c similarity index 51% rename from sys/src/cmd/ip/imap4d/fetch.c rename to sys/src/cmd/upas/imap4d/fetch.c index 30fc0f4b7..d3e6ef3cc 100644 --- a/sys/src/cmd/ip/imap4d/fetch.c +++ b/sys/src/cmd/upas/imap4d/fetch.c @@ -1,10 +1,6 @@ -#include -#include -#include -#include #include "imap4d.h" -char *fetchPartNames[FPMax] = +char *fetchpartnames[FPmax] = { "", "HEADER", @@ -20,70 +16,60 @@ char *fetchPartNames[FPMax] = * messages are sent to the client. */ int -fetchSeen(Box *box, Msg *m, int uids, void *vf) +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; + case Frfc822: + case Frfc822text: + case Fbodysect: + msgseen(box, m); + return 1; } } -breakout: - return 1; } /* * fetch messages * - * imap4 body[] requestes get translated to upas/fs files as follows + * 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) +fetchmsg(Box *, Msg *m, int uids, void *vf) { - Tm tm; - Fetch *f; char *sep; - int todo; + Fetch *f; + Tm tm; if(m->expunged) return uids; - - todo = 0; - for(f = vf; f != nil; f = f->next){ + for(f = vf; f != nil; f = f->next) switch(f->op){ - case FFlags: - todo = 1; + case Fflags: break; - case FUid: - todo = 1; + case Fuid: 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); + 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; @@ -91,21 +77,19 @@ fetchMsg(Box *, Msg *m, int uids, void *vf) bye("bad implementation of fetch"); return 0; } - } - if(m->expunged) return uids; - if(!todo) + 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, "* %lud FETCH (", m->seq); + Bprint(&bout, "* %ud FETCH (", m->seq); sep = ""; if(uids){ - Bprint(&bout, "UID %lud", m->uid); + Bprint(&bout, "UID %ud", m->uid); sep = " "; } for(f = vf; f != nil; f = f->next){ @@ -113,54 +97,53 @@ fetchMsg(Box *, Msg *m, int uids, void *vf) default: bye("bad implementation of fetch"); break; - case FFlags: + case Fflags: Bprint(&bout, "%sFLAGS (", sep); - writeFlags(&bout, m, 1); + writeflags(&bout, m, 1); Bprint(&bout, ")"); break; - case FUid: + case Fuid: if(uids) continue; - Bprint(&bout, "%sUID %lud", sep, m->uid); + Bprint(&bout, "%sUID %ud", sep, m->uid); break; - case FEnvelope: + case Fenvelope: Bprint(&bout, "%sENVELOPE ", sep); - fetchEnvelope(m); + fetchenvelope(m); break; - case FInternalDate: - Bprint(&bout, "%sINTERNALDATE ", sep); - Bimapdate(&bout, date2tm(&tm, m->unixDate)); + case Finternaldate: + Bprint(&bout, "%sINTERNALDATE %#D", sep, date2tm(&tm, m->unixdate)); break; - case FBody: + case Fbody: Bprint(&bout, "%sBODY ", sep); - fetchBodyStruct(m, &m->head, 0); + fetchbodystruct(m, &m->head, 0); break; - case FBodyStruct: + case Fbodystruct: Bprint(&bout, "%sBODYSTRUCTURE ", sep); - fetchBodyStruct(m, &m->head, 1); + fetchbodystruct(m, &m->head, 1); break; - case FRfc822Size: - Bprint(&bout, "%sRFC822.SIZE %lud", sep, msgSize(m)); + case Frfc822size: + Bprint(&bout, "%sRFC822.SIZE %ud", sep, msgsize(m)); break; - case FRfc822: - f->part = FPAll; + case Frfc822: + f->part = FPall; Bprint(&bout, "%sRFC822", sep); - fetchBody(m, f); + fetchbody(m, f); break; - case FRfc822Head: - f->part = FPHead; + case Frfc822head: + f->part = FPhead; Bprint(&bout, "%sRFC822.HEADER", sep); - fetchBody(m, f); + fetchbody(m, f); break; - case FRfc822Text: - f->part = FPText; + case Frfc822text: + f->part = FPtext; Bprint(&bout, "%sRFC822.TEXT", sep); - fetchBody(m, f); + fetchbody(m, f); break; - case FBodySect: - case FBodyPeek: + case Fbodysect: + case Fbodypeek: Bprint(&bout, "%sBODY", sep); - fetchBody(fetchSect(m, f), f); + fetchbody(fetchsect(m, f), f); break; } sep = " "; @@ -175,68 +158,71 @@ fetchMsg(Box *, Msg *m, int uids, void *vf) * find and return message section */ Msg * -fetchSect(Msg *m, Fetch *f) +fetchsect(Msg *m, Fetch *f) { Bputc(&bout, '['); - BNList(&bout, f->sect, "."); - if(f->part != FPAll){ + Bnlist(&bout, f->sect, "."); + if(f->part != FPall){ if(f->sect != nil) Bputc(&bout, '.'); - Bprint(&bout, "%s", fetchPartNames[f->part]); + Bprint(&bout, "%s", fetchpartnames[f->part]); if(f->hdrs != nil){ Bprint(&bout, " ("); - BSList(&bout, f->hdrs, " "); + Bslist(&bout, f->hdrs, " "); Bputc(&bout, ')'); } } Bprint(&bout, "]"); - return findMsgSect(m, f->sect); + return findmsgsect(m, f->sect); } /* * actually return the body pieces */ void -fetchBody(Msg *m, Fetch *f) +fetchbody(Msg *m, Fetch *f) { - Pair p; - char *s, *t, *e, buf[BufSize + 2]; - ulong n, start, stop, pos; + char *s, *t, *e, buf[Bufsize + 2]; + uint n, start, stop, pos; int fd, nn; + Pair p; if(m == nil){ - fetchBodyStr(f, "", 0); + fetchbodystr(f, "", 0); return; } switch(f->part){ - case FPHeadFields: - case FPHeadFieldsNot: + 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); + 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); + 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); + case FPmime: + fetchbodystr(f, m->mime.buf, m->mime.size); return; - case FPAll: - fd = msgFile(m, "rawbody"); + case FPall: + fd = msgfile(m, "rawbody"); if(fd < 0){ - msgDead(m); - fetchBodyStr(f, "", 0); + msgdead(m); + fetchbodystr(f, "", 0); return; } - p = fetchBodyPart(f, msgSize(m)); + 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; - Bwrite(&bout, &m->head.buf[start], stop - start); +//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){ @@ -247,19 +233,19 @@ fetchBody(Msg *m, Fetch *f) start -= m->head.size; stop = p.stop - m->head.size; break; - case FPText: - fd = msgFile(m, "rawbody"); + case FPtext: + fd = msgfile(m, "rawbody"); if(fd < 0){ - msgDead(m); - fetchBodyStr(f, "", 0); + msgdead(m); + fetchbodystr(f, "", 0); return; } - p = fetchBodyPart(f, m->size); + p = fetchbodypart(f, m->size); start = p.start; stop = p.stop; break; default: - fetchBodyStr(f, "", 0); + fetchbodystr(f, "", 0); return; } @@ -270,16 +256,19 @@ fetchBody(Msg *m, Fetch *f) */ buf[0] = ' '; for(pos = 0; pos < stop; ){ - n = BufSize; + 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){ - fetchBodyFill(stop - pos); +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'; + *e = 0; for(s = &buf[1]; s < e && pos < stop; s = t + 1){ t = memchr(s, '\n', e - s); if(t == nil) @@ -299,7 +288,8 @@ fetchBody(Msg *m, Fetch *f) if(pos + nn > stop) nn = stop - pos; if(Bwrite(&bout, s, nn) != nn) - writeErr(); + writeerr(); +//ilog("w %ld at %ld->%ld stop %ld\n", nn, pos, pos + nn, stop); pos += n; if(*t == '\n'){ if(t[-1] != '\r'){ @@ -322,10 +312,10 @@ fetchBody(Msg *m, Fetch *f) * and print out the bounds & size of string returned */ Pair -fetchBodyPart(Fetch *f, ulong size) +fetchbodypart(Fetch *f, uint size) { + uint start, stop; Pair p; - ulong start, stop; start = 0; stop = size; @@ -336,9 +326,9 @@ fetchBodyPart(Fetch *f, ulong size) stop = start + f->size; if(stop > size) stop = size; - Bprint(&bout, "<%lud>", start); + Bprint(&bout, "<%ud>", start); } - Bprint(&bout, " {%lud}\r\n", stop - start); + Bprint(&bout, " {%ud}\r\n", stop - start); p.start = start; p.stop = stop; return p; @@ -349,37 +339,37 @@ fetchBodyPart(Fetch *f, ulong size) * produce fill bytes for what we've committed to produce */ void -fetchBodyFill(ulong n) +fetchbodyfill(uint n) { while(n-- > 0) if(Bputc(&bout, ' ') < 0) - writeErr(); + writeerr(); } /* * return a simple string */ void -fetchBodyStr(Fetch *f, char *buf, ulong size) +fetchbodystr(Fetch *f, char *buf, uint size) { Pair p; - p = fetchBodyPart(f, size); - Bwrite(&bout, &buf[p.start], p.stop-p.start); + p = fetchbodypart(f, size); + Bwrite(&bout, buf + p.start, p.stop - p.start); } char* -printnlist(NList *sect) +printnlist(Nlist *sect) { static char buf[100]; char *p; - for(p= buf; sect; sect=sect->next){ - p += sprint(p, "%ld", sect->n); + for(p = buf; sect; sect = sect->next){ + p += sprint(p, "%ud", sect->n); if(sect->next) *p++ = '.'; } - *p = '\0'; + *p = 0; return buf; } @@ -387,23 +377,12 @@ printnlist(NList *sect) * find the numbered sub-part of the message */ Msg* -findMsgSect(Msg *m, NList *sect) +findmsgsect(Msg *m, Nlist *sect) { - ulong id; + uint 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; @@ -414,51 +393,78 @@ findMsgSect(Msg *m, NList *sect) } void -fetchEnvelope(Msg *m) +fetchenvelope(Msg *m) { Tm tm; - Bputc(&bout, '('); - Brfc822date(&bout, date2tm(&tm, m->info[IDate])); - Bputc(&bout, ' '); - Bimapstr(&bout, m->info[ISubject]); - Bputc(&bout, ' '); + 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); + 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, ')'); + 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) +fetchbodystruct(Msg *m, Header *h, int extensions) { + uint len; Msg *k; - ulong len; - if(msgIsMulti(h)){ + 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); - + fetchbodystruct(k, &k->mime, extensions); + if(m->kids) + Bputc(&bout, ' '); + Bprint(&bout, "%Z", h->type->t); if(extensions){ Bputc(&bout, ' '); - BimapMimeParams(&bout, h->type->next); - fetchStructExt(h); + Bmime(&bout, h->type->next); + fetchext(&bout, h); } Bputc(&bout, ')'); @@ -467,29 +473,26 @@ fetchBodyStruct(Msg *m, Header *h, int extensions) 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); + 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) - Bimapstr(&bout, h->id->s); + Bprint(&bout, "%Z", h->id->s); else Bprint(&bout, "NIL"); Bputc(&bout, ' '); if(h->description != nil) - Bimapstr(&bout, h->description->s); + Bprint(&bout, "%Z", h->description->s); else Bprint(&bout, "NIL"); Bputc(&bout, ' '); if(h->encoding != nil) - Bimapstr(&bout, h->encoding->s); + Bprint(&bout, "%Z", h->encoding->s); else Bprint(&bout, "NIL"); @@ -500,15 +503,15 @@ fetchBodyStruct(Msg *m, Header *h, int extensions) len = m->size; if(h == &m->mime) len += m->head.size; - Bprint(&bout, " %lud", len); + 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, " %lud", len); - }else if(msgIsRfc822(h)){ + 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) @@ -516,110 +519,41 @@ fetchBodyStruct(Msg *m, Header *h, int extensions) 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); + fetchenvelope(k); Bputc(&bout, ' '); - fetchBodyStruct(k, &k->head, extensions); - Bprint(&bout, " %lud", len); + fetchbodystruct(k, &k->head, extensions); + Bprint(&bout, " %ud", len); } } if(extensions){ - Bputc(&bout, ' '); - - /* - * don't have the md5 laying around, - * since the header & body have added newlines. - */ - Bprint(&bout, "NIL"); - - fetchStructExt(h); + Bprint(&bout, " NIL"); /* md5 */ + fetchext(&bout, 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 + * each address is printed as '(' personalname atdomainlist mboxname hostname ')' + * the atdomainlist is always NIL */ int -Bimapaddr(Biobuf *b, MAddr *a) +Bimapaddr(Biobuf *b, Maddr *a) { char *host, *sep; - int n; if(a == nil) return Bprint(b, "NIL"); - - n = Bputc(b, '('); + 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 + * 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, ')'); + host = a->host? a->host: ""; + Bprint(b, "%s(%Z NIL %Z %Z)", sep, a->personal, a->box, host); sep = " "; } - n += Bputc(b, ')'); - return n; + 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 index 000000000..1e96a4b7b --- /dev/null +++ b/sys/src/cmd/upas/imap4d/fns.h @@ -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 index 000000000..f5e41dc21 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/folder.c @@ -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 index 000000000..b6df1b95d --- /dev/null +++ b/sys/src/cmd/upas/imap4d/fsenc.c @@ -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 index 000000000..13ef32b7c --- /dev/null +++ b/sys/src/cmd/upas/imap4d/fstree.c @@ -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 index 000000000..94fa0275e --- /dev/null +++ b/sys/src/cmd/upas/imap4d/imap4d.c @@ -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 index 000000000..35ce88b60 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/imap4d.h @@ -0,0 +1,395 @@ +/* + * mailbox and message representations + * + * these structures are allocated with emalloc and must be explicitly freed + */ +#include +#include +#include +#include +#include +#include + +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 index 000000000..c5542eab7 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/imp.c @@ -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 index 000000000..1dd3533eb --- /dev/null +++ b/sys/src/cmd/upas/imap4d/list.c @@ -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 index 000000000..1841b6832 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/mbox.c @@ -0,0 +1,630 @@ +#include "imap4d.h" + +static int fsctl = -1; +static char Ecanttalk[] = "can't talk to mail server"; + +static void +fsinit(void) +{ + if(fsctl != -1) + return; + fsctl = open("/mail/fs/ctl", ORDWR); + if(fsctl == -1) + bye(Ecanttalk); +} + +static void +boxflags(Box *box) +{ + Msg *m; + + box->recent = 0; + for(m = box->msgs; m != nil; m = m->next){ + if(m->uid == 0){ + // fprint(2, "unassigned uid %s\n", m->info[Idigest]); + box->dirtyimp = 1; + m->uid = box->uidnext++; + } + if(m->flags & Frecent) + box->recent++; + } +} + +/* + * try to match permissions with mbox + */ +static int +createimp(Box *box, Qid *qid) +{ + int fd; + long mode; + Dir *d; + + fd = cdcreate(mboxdir, box->imp, OREAD, 0664); + if(fd < 0) + return -1; + d = cddirstat(mboxdir, box->name); + if(d != nil){ + mode = d->mode & 0777; + nulldir(d); + d->mode = mode; + dirfwstat(fd, d); + free(d); + } + if(fqid(fd, qid) < 0){ + close(fd); + return -1; + } + + return fd; +} + +/* + * read in the .imp file, or make one if it doesn't exist. + * make sure all flags and uids are consistent. + * return the mailbox lock. + */ +static Mblock* +openimp(Box *box, int new) +{ + char buf[ERRMAX]; + int fd; + Biobuf b; + Mblock *ml; + Qid qid; + + ml = mblock(); + if(ml == nil) + return nil; + fd = cdopen(mboxdir, box->imp, OREAD); + if(fd < 0 || fqid(fd, &qid) < 0){ + if(fd < 0){ + errstr(buf, sizeof buf); + if(cistrstr(buf, "does not exist") == nil) + ilog("imp: %s: %s", box->imp, buf); + else + debuglog("imp: %s: %s .. creating", box->imp, buf); + }else{ + close(fd); + ilog("%s: bogus imp: bad qid: recreating", box->imp); + } + fd = createimp(box, &qid); + if(fd < 0){ + ilog("createimp fails: %r"); + mbunlock(ml); + return nil; + } + box->dirtyimp = 1; + if(box->uidvalidity == 0){ + ilog("set uidvalidity %lud [new]\n", box->uidvalidity); + box->uidvalidity = box->mtime; + } + box->impqid = qid; + new = 1; + }else if(qid.path != box->impqid.path || qid.vers != box->impqid.vers){ + Binit(&b, fd, OREAD); + if(parseimp(&b, box) == -1){ + ilog("%s: bogus imp: parse failure", box->imp); + box->dirtyimp = 1; + if(box->uidvalidity == 0){ + ilog("set uidvalidity %lud [parseerr]\n", box->uidvalidity); + box->uidvalidity = box->mtime; + } + } + Bterm(&b); + box->impqid = qid; + new = 1; + } + if(new) + boxflags(box); + close(fd); + return ml; +} + +/* + * mailbox is unreachable, so mark all messages expunged + * clean up .imp files as well. + */ +static void +mboxgone(Box *box) +{ + char buf[ERRMAX]; + Msg *m; + + rerrstr(buf, ERRMAX); + if(strstr(buf, "hungup channel")) + bye(Ecanttalk); +// too smart. +// if(cdexists(mboxdir, box->name) < 0) +// cdremove(mboxdir, box->imp); + for(m = box->msgs; m != nil; m = m->next) + m->expunged = 1; + ilog("mboxgone"); + box->writable = 0; +} + +/* + * read messages in the mailbox + * mark message that no longer exist as expunged + * returns -1 for failure, 0 if no new messages, 1 if new messages. + */ +enum { + Gone = 2, /* don't unexpunge messages */ +}; + +static int +readbox(Box *box) +{ + char buf[ERRMAX]; + int i, n, fd, new, id; + Dir *d; + Msg *m, *last; + + fd = cdopen(box->fsdir, ".", OREAD); + if(fd == -1){ +goinggoinggone: + rerrstr(buf, ERRMAX); + ilog("upas/fs stat of %s/%s aka %s failed: %r", + username, box->name, box->fsdir); + mboxgone(box); + return -1; + } + + if((d = dirfstat(fd)) == nil){ + close(fd); + goto goinggoinggone; + } + box->mtime = d->mtime; + box->qid = d->qid; + last = nil; + for(m = box->msgs; m != nil; m = m->next){ + last = m; + m->expunged |= Gone; + } + new = 0; + free(d); + + for(;;){ + n = dirread(fd, &d); + if(n <= 0){ + close(fd); + if(n == -1) + goto goinggoinggone; + break; + } + for(i = 0; i < n; i++){ + if((d[i].qid.type & QTDIR) == 0) + continue; + id = atoi(d[i].name); + if(m = fstreefind(box, id)){ + m->expunged &= ~Gone; + continue; + } + new = 1; + m = MKZ(Msg); + m->id = id; + m->fsdir = box->fsdir; + m->fs = emalloc(2 * (Filelen + 1)); + m->efs = seprint(m->fs, m->fs + (Filelen + 1), "%ud/", id); + m->size = ~0UL; + m->lines = ~0UL; + m->flags = Frecent; + if(!msginfo(m)) + freemsg(0, m); + else{ + fstreeadd(box, m); + if(last == nil) + box->msgs = m; + else + last->next = m; + last = m; + } + } + free(d); + } + + /* box->max is invalid here */ + return new; +} + +int +uidcmp(void *va, void *vb) +{ + Msg **a, **b; + + a = va; + b = vb; + return (*a)->uid - (*b)->uid; +} + +static void +sequence(Box *box) +{ + Msg **a, *m; + int n, i; + + n = 0; + for(m = box->msgs; m; m = m->next) + n++; + a = ezmalloc(n * sizeof *a); + i = 0; + for(m = box->msgs; m; m = m->next) + a[i++] = m; + qsort(a, n, sizeof *a, uidcmp); + for(i = 0; i < n - 1; i++) + a[i]->next = a[i + 1]; + for(i = 0; i < n; i++) + if(a[i]->seq && a[i]->seq != i + 1) + bye("internal error assigning message numbers"); + else + a[i]->seq = i + 1; + box->msgs = nil; + if(n > 0){ + a[n - 1]->next = nil; + box->msgs = a[0]; + } + box->max = n; + memset(a, 0, n*sizeof *a); + free(a); +} + +/* + * strategy: + * every mailbox file has an associated .imp file + * which maps upas/fs message digests to uids & message flags. + * + * the .imp files are locked by /mail/fs/usename/L.mbox. + * whenever the flags can be modified, the lock file + * should be opened, thereby locking the uid & flag state. + * for example, whenever new uids are assigned to messages, + * and whenever flags are changed internally, the lock file + * should be open and locked. this means the file must be + * opened during store command, and when changing the \seen + * flag for the fetch command. + * + * if no .imp file exists, a null one must be created before + * assigning uids. + * + * the .imp file has the following format + * imp : "imap internal mailbox description\n" + * uidvalidity " " uidnext "\n" + * messagelines + * + * messagelines : + * | messagelines digest " " uid " " flags "\n" + * + * uid, uidnext, and uidvalidity are 32 bit decimal numbers + * printed right justified in a field Nuid characters long. + * the 0 uid implies that no uid has been assigned to the message, + * but the flags are valid. note that message lines are in mailbox + * order, except possibly for 0 uid messages. + * + * digest is an ascii hex string Ndigest characters long. + * + * flags has a character for each of NFlag flag fields. + * if the flag is clear, it is represented by a "-". + * set flags are represented as a unique single ascii character. + * the currently assigned flags are, in order: + * Fseen s + * Fanswered a + * Fflagged f + * Fdeleted D + * Fdraft d + */ + +Box* +openbox(char *name, char *fsname, int writable) +{ + char err[ERRMAX]; + int new; + Box *box; + Mblock *ml; + + fsinit(); +if(!strcmp(name, "mbox"))ilog("open %F %q", name, fsname); + if(fprint(fsctl, "open %F %q", name, fsname) < 0){ + rerrstr(err, sizeof err); + if(strstr(err, "file does not exist") == nil) + ilog("fs open %F as %s: %s", name, fsname, err); + if(strstr(err, "hungup channel")) + bye(Ecanttalk); + fprint(fsctl, "close %s", fsname); + return nil; + } + + /* + * read box to find all messages + * each one has a directory, and is in numerical order + */ + box = MKZ(Box); + box->writable = writable; + box->name = smprint("%s", name); + box->imp = smprint("%s.imp", name); + box->fs = smprint("%s", fsname); + box->fsdir = smprint("/mail/fs/%s", fsname); + box->uidnext = 1; + box->fstree = avlcreate(fstreecmp); + new = readbox(box); + if(new >= 0 && (ml = openimp(box, new))){ + closeimp(box, ml); + sequence(box); + return box; + } + closebox(box, 0); + return nil; +} + +/* + * careful: called by idle polling proc + */ +Mblock* +checkbox(Box *box, int imped) +{ + int new; + Dir *d; + Mblock *ml; + + if(box == nil) + return nil; + + /* + * if stat fails, mailbox must be gone + */ + d = cddirstat(box->fsdir, "."); + if(d == nil){ + mboxgone(box); + return nil; + } + new = 0; + if(box->qid.path != d->qid.path || box->qid.vers != d->qid.vers + || box->mtime != d->mtime){ + new = readbox(box); + if(new < 0){ + free(d); + return nil; + } + } + free(d); + ml = openimp(box, new); + if(ml == nil){ + ilog("openimp fails; box->writable = 0: %r"); + box->writable = 0; + }else if(!imped){ + closeimp(box, ml); + ml = nil; + } + if(new || box->dirtyimp) + sequence(box); + return ml; +} + +/* + * close the .imp file, after writing out any changes + */ +void +closeimp(Box *box, Mblock *ml) +{ + int fd; + Biobuf b; + Qid qid; + + if(ml == nil) + return; + if(!box->dirtyimp){ + mbunlock(ml); + return; + } + fd = cdcreate(mboxdir, box->imp, OWRITE, 0664); + if(fd < 0){ + mbunlock(ml); + return; + } + Binit(&b, fd, OWRITE); + box->dirtyimp = 0; + wrimp(&b, box); + Bterm(&b); + + if(fqid(fd, &qid) == 0) + box->impqid = qid; + close(fd); + mbunlock(ml); +} + +void +closebox(Box *box, int opened) +{ + Msg *m, *next; + + /* + * make sure to leave the mailbox directory so upas/fs can close the mailbox + */ + mychdir(mboxdir); + + if(box->writable){ + deletemsg(box, 0); + if(expungemsgs(box, 0)) + closeimp(box, checkbox(box, 1)); + } + + if(fprint(fsctl, "close %s", box->fs) < 0 && opened) + bye(Ecanttalk); + for(m = box->msgs; m != nil; m = next){ + next = m->next; + freemsg(box, m); + } + free(box->name); + free(box->fs); + free(box->fsdir); + free(box->imp); + free(box->fstree); + free(box); +} + +int +deletemsg(Box *box, Msgset *ms) +{ + char buf[Bufsize], *p, *start; + int ok; + Msg *m; + + if(!box->writable) + return 0; + + /* + * first pass: delete messages; gang the writes together for speed. + */ + ok = 1; + start = seprint(buf, buf + sizeof buf, "delete %s", box->fs); + p = start; + for(m = box->msgs; m != nil; m = m->next) + if(ms == 0 || ms && inmsgset(ms, m->uid)) + if((m->flags & Fdeleted) && !m->expunged){ + m->expunged = 1; + p = seprint(p, buf + sizeof buf, " %ud", m->id); + if(p + 32 >= buf + sizeof buf){ + if(write(fsctl, buf, p - buf) == -1) + bye(Ecanttalk); + p = start; + } + } + if(p != start && write(fsctl, buf, p - buf) == -1) + bye(Ecanttalk); + return ok; +} + +/* + * second pass: remove the message structure, + * and renumber message sequence numbers. + * update messages counts in mailbox. + * returns true if anything changed. + */ +int +expungemsgs(Box *box, int send) +{ + uint n; + Msg *m, *next, *last; + + n = 0; + last = nil; + for(m = box->msgs; m != nil; m = next){ + m->seq -= n; + next = m->next; + if(m->expunged){ + if(send) + Bprint(&bout, "* %ud expunge\r\n", m->seq); + if(m->flags & Frecent) + box->recent--; + n++; + if(last == nil) + box->msgs = next; + else + last->next = next; + freemsg(box, m); + }else + last = m; + } + if(n){ + box->max -= n; + box->dirtyimp = 1; + } + return n; +} + +static char *stoplist[] = +{ + ".", + "dead.letter", + "forward", + "headers", + "imap.subscribed", + "mbox", + "names", + "pipefrom", + "pipeto", + 0 +}; + +/* + * reject bad mailboxes based on mailbox name + */ +int +okmbox(char *path) +{ + char *name; + int i, c; + + name = strrchr(path, '/'); + if(name == nil) + name = path; + else + name++; + if(strlen(name) + STRLEN(".imp") >= Pathlen) + return 0; + for(i = 0; stoplist[i]; i++) + if(strcmp(name, stoplist[i]) == 0) + return 0; + c = name[0]; + if(c == 0 || c == '-' || c == '/' + || isdotdot(name) + || isprefix("L.", name) + || isprefix("imap-tmp.", name) + || issuffix("-", name) + || issuffix(".00", name) + || issuffix(".imp", name) + || issuffix(".idx", name)) + return 0; + + return 1; +} + +int +creatembox(char *mbox) +{ + fsinit(); + if(fprint(fsctl, "create %q", mbox) > 0){ + fprint(fsctl, "close %s", mbox); + return 0; + } + return -1; +} + +/* + * rename mailbox. truncaes or removes the source. + * bug? is the lock required + * upas/fs helpfully moves our .imp file. + */ +int +renamebox(char *from, char *to, int doremove) +{ + char *p; + int r; + Mblock *ml; + + fsinit(); + ml = mblock(); + if(ml == nil) + return 0; + if(doremove) + r = fprint(fsctl, "rename %F %F", from, to); + else + r = fprint(fsctl, "rename -t %F %F", from, to); + if(r > 0){ + if(p = strrchr(to, '/')) + p++; + else + p = to; + fprint(fsctl, "close %s", p); + } + mbunlock(ml); + return r > 0; +} + +/* + * upas/fs likes us; he removes the .imp file + */ +int +removembox(char *path) +{ + fsinit(); + return fprint(fsctl, "remove %s", path) > 0; +} diff --git a/sys/src/cmd/ip/imap4d/mkfile b/sys/src/cmd/upas/imap4d/mkfile similarity index 82% rename from sys/src/cmd/ip/imap4d/mkfile rename to sys/src/cmd/upas/imap4d/mkfile index afadea93e..bf5b9f71f 100644 --- a/sys/src/cmd/ip/imap4d/mkfile +++ b/sys/src/cmd/upas/imap4d/mkfile @@ -1,28 +1,33 @@ @,;:\\\"/[]?="; +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*@,;:\\\"/[]?=" aka mimetokenstop> + * atom : 1*@,;:\\\".[]" 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/ip/imap4d/mutf7.c b/sys/src/cmd/upas/imap4d/mutf7.c similarity index 74% rename from sys/src/cmd/ip/imap4d/mutf7.c rename to sys/src/cmd/upas/imap4d/mutf7.c index 6d84b4efc..c8872d5d7 100644 --- a/sys/src/cmd/ip/imap4d/mutf7.c +++ b/sys/src/cmd/upas/imap4d/mutf7.c @@ -1,9 +1,7 @@ -#include -#include -#include -#include #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, @@ -47,93 +45,96 @@ initm64(void) mt64d[','] = i; } -int +char* encmutf7(char *out, int lim, char *in) { - Rune rr; - ulong r, b; - char *start = out; - char *e = out + lim; + 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') + if(r == 0) break; if(out + 1 >= e) - return -1; + return 0; *out++ = '&'; b = 0; nb = 0; for(;;){ in += chartorune(&rr, in); r = rr; - if(r == '\0' || r >= ' ' && r < Runeself) + 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]; + return 0; + *out++ = mt64e[(b >> nb - 6) & 0x3f]; } } for(; nb >= 6; nb -= 6){ if(out + 1 >= e) - return -1; - *out++ = mt64e[(b>>(nb-6))&0x3f]; + return 0; + *out++ = mt64e[(b >> nb - 6) & 0x3f]; } if(nb){ if(out + 1 >= e) - return -1; - *out++ = mt64e[(b<<(6-nb))&0x3f]; + return 0; + *out++ = mt64e[(b << 6 - nb) & 0x3f]; } if(out + 1 >= e) - return -1; + return 0; *out++ = '-'; - if(r == '\0') + if(r == 0) break; }else in++; if(out + 1 >= e) - return -1; + return 0; *out = r; out++; if(r == '&') *out++ = '-'; } - - if(out >= e) - return -1; - *out = '\0'; - return out - start; + *out = 0; + if(!in || out >= e) + return 0; + return start; } -int +char* decmutf7(char *out, int lim, char *in) { - Rune rr; - char *start = out; - char *e = out + lim; + 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') + if(c == 0) break; - return -1; + return 0; } if(c != '&'){ if(out + 1 >= e) - return -1; + return 0; *out++ = c; in++; continue; @@ -141,7 +142,7 @@ decmutf7(char *out, int lim, char *in) in++; if(*in == '-'){ if(out + 1 >= e) - return -1; + return 0; *out++ = '&'; in++; continue; @@ -152,23 +153,22 @@ decmutf7(char *out, int lim, char *in) while((c = *in++) != '-'){ c = mt64d[c]; if(c >= 64) - return -1; + 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 -1; + return 0; out += runetochar(out, &rr); } } if(b & ((1 << nb) - 1)) - return -1; + return 0; } - - if(out >= e) - return -1; - *out = '\0'; - return out - start; + *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 index 000000000..c6a8cbfbe --- /dev/null +++ b/sys/src/cmd/upas/imap4d/nlisttst.c @@ -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 index 000000000..8af84d980 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/nodes.c @@ -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 index 000000000..8fd236f61 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/print.c @@ -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 index 000000000..375185d80 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/quota.c @@ -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 index 000000000..42c7ad650 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/search.c @@ -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 index 000000000..1abe999d1 --- /dev/null +++ b/sys/src/cmd/upas/imap4d/store.c @@ -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/ip/imap4d/utils.c b/sys/src/cmd/upas/imap4d/utils.c similarity index 59% rename from sys/src/cmd/ip/imap4d/utils.c rename to sys/src/cmd/upas/imap4d/utils.c index 6e081a62e..b2a8bb046 100644 --- a/sys/src/cmd/ip/imap4d/utils.c +++ b/sys/src/cmd/upas/imap4d/utils.c @@ -1,7 +1,3 @@ -#include -#include -#include -#include #include "imap4d.h" /* @@ -22,7 +18,7 @@ strrev(char *s, char *e) int isdotdot(char *s) { - return s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == '\0'); + return s[0] == '.' && s[1] == '.' && (s[2] == '/' || s[2] == 0); } int @@ -42,28 +38,22 @@ 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) +readfile(int fd) { - Dir *d; - long length; 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 || read(fd, s, length) != length) + s = binalloc(&parsebin, length + 1, 0); + if(s == nil || readn(fd, s, length) != length) return nil; - s[length] = '\0'; + s[length] = 0; return s; } @@ -72,13 +62,13 @@ readFile(int fd) * it just happens that we don't need multiple temporary files. */ int -imapTmp(void) +imaptmp(void) { - char buf[ERRMAX], name[MboxNameLen]; + 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++){ + 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; @@ -94,21 +84,40 @@ imapTmp(void) * open a file which might be locked. * if it is, spin until available */ -int -openLocked(char *dir, char *file, int mode) +static char *etab[] = { + "not found", + "does not exist", + "file is locked", + "exclusive lock", + "already exists", +}; + +static int +bad(int idx) { char buf[ERRMAX]; - int tries, fd; + int i; - for(tries = 0; tries < LockSecs*2; tries++){ - fd = cdOpen(dir, file, mode); - if(fd >= 0) + 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; - errstr(buf, sizeof buf); - if(cistrstr(buf, "locked") == nil) - break; - sleep(500); + if((fd = cdcreate(dir, file, mode|OEXCL, DMEXCL|0600)) >= 0 || bad(2)) + return fd; + sleep(1000); } + werrstr("lock timeout"); return -1; } @@ -125,8 +134,8 @@ fqid(int fd, Qid *qid) return 0; } -ulong -mapInt(NamedInt *map, char *name) +uint +mapint(Namedint *map, char *name) { int i; @@ -180,3 +189,21 @@ erealloc(void *p, ulong n) 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); + } +} diff --git a/sys/src/cmd/upas/marshal/marshal.c b/sys/src/cmd/upas/marshal/marshal.c index f62fc9e28..7ddec78ee 100644 --- a/sys/src/cmd/upas/marshal/marshal.c +++ b/sys/src/cmd/upas/marshal/marshal.c @@ -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; diff --git a/sys/src/cmd/upas/marshal/mkfile b/sys/src/cmd/upas/marshal/mkfile index 737fcff82..acbd8fe1f 100644 --- a/sys/src/cmd/upas/marshal/mkfile +++ b/sys/src/cmd/upas/marshal/mkfile @@ -1,4 +1,5 @@ [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 index 66fb10bcf..000000000 --- a/sys/src/cmd/upas/misc/gone.fishing +++ /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 -} diff --git a/sys/src/cmd/upas/misc/gone.msg b/sys/src/cmd/upas/misc/gone.msg index ac1e7bd9e..9eb1e5a6b 100644 --- a/sys/src/cmd/upas/misc/gone.msg +++ b/sys/src/cmd/upas/misc/gone.msg @@ -1,5 +1,3 @@ -subject: away from my computer - This is a recorded message. I am currently out of contact with my computer system. Your message to me has been saved and will be read upon my return. This is the last time you will receive this diff --git a/sys/src/cmd/upas/misc/mail b/sys/src/cmd/upas/misc/mail.rc similarity index 100% rename from sys/src/cmd/upas/misc/mail rename to sys/src/cmd/upas/misc/mail.rc diff --git a/sys/src/cmd/upas/misc/mkfile b/sys/src/cmd/upas/misc/mkfile index 0fa970e17..c3593840b 100644 --- a/sys/src/cmd/upas/misc/mkfile +++ b/sys/src/cmd/upas/misc/mkfile @@ -1,32 +1,35 @@ -RCFILES=mail\ - go.fishing\ -all:VQ: +RCFILES=mail.rc\ + +all:Q: ; -installall:VQ: install +installall:Q: install ; -install:V: mail go.fishing gone.msg gone.fishing - cp mail go.fishing /rc/bin - cp gone.msg gone.fishing /mail/lib +install:V: + cp mail.rc /rc/bin/mail -safeinstall:V: install +safeinstall:V: + cp mail.rc /rc/bin/mail -safeinstallall:V: install +safeinstallall:V: + cp mail.rc /rc/bin/mail -clean:VQ: +clean:Q: ; nuke:V: - ; # rm /rc/bin/^(mail gone.fishing) + rm /rc/bin/mail UPDATE=\ - go.fishing\ gone.fishing\ gone.msg\ - mail\ + mail.rc\ + mail.sh\ + makefile\ mkfile\ namefiles\ + omail.rc\ qmail\ remotemail\ rewrite\ diff --git a/sys/src/cmd/upas/misc/omail.rc b/sys/src/cmd/upas/misc/omail.rc new file mode 100755 index 000000000..ed64f38c4 --- /dev/null +++ b/sys/src/cmd/upas/misc/omail.rc @@ -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 index a304271c0..000000000 --- a/sys/src/cmd/upas/misc/unix/gone.fishing.sh +++ /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 index 20cf23970..000000000 --- a/sys/src/cmd/upas/misc/unix/mail.c +++ /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 index a41519aef..000000000 --- a/sys/src/cmd/upas/misc/unix/mail.sh +++ /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 index e00c4f7e8..000000000 --- a/sys/src/cmd/upas/misc/unix/makefile +++ /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 - diff --git a/sys/src/cmd/upas/mkfile b/sys/src/cmd/upas/mkfile index d9bd59252..0092b5b76 100644 --- a/sys/src/cmd/upas/mkfile +++ b/sys/src/cmd/upas/mkfile @@ -1,7 +1,27 @@ 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) - fprint(fd, "!%s\n", addr); - else - fprint(fd, "%s\n", addr); - close(fd); - - if(*addr != '#') + if(rem){ sendnotification(addr, listname, rem); + fprint(fd, "!%s\n", addr); + }else{ + fprint(fd, "%s\n", addr); + if(*addr != '#') + sendnotification(addr, listname, rem); + } + close(fd); } 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()){ diff --git a/sys/src/cmd/upas/ml/mkfile b/sys/src/cmd/upas/ml/mkfile index 5142e56f4..7a6350e16 100644 --- a/sys/src/cmd/upas/ml/mkfile +++ b/sys/src/cmd/upas/ml/mkfile @@ -1,4 +1,5 @@ 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); -} diff --git a/sys/src/cmd/upas/ml/mlmgr.c b/sys/src/cmd/upas/ml/mlmgr.c index a1d1b907e..4ac4435ad 100644 --- a/sys/src/cmd/upas/ml/mlmgr.c +++ b/sys/src/cmd/upas/ml/mlmgr.c @@ -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((dom = strchr(listname, '@')) != nil) + *dom++ = 0; + mboxpathbuf(alfile, sizeof alfile, listname, "address-list"); - if(cflag){ - owner = s_copy(listname); - s_append(owner, "-owner"); - if(creatembox(listname, nil) < 0) - sysfatal("creating %s's mbox: %r", listname); - if(creatembox(s_to_c(owner), nil) < 0) - sysfatal("creating %s's mbox: %r", s_to_c(owner)); - if(createpipeto(s_to_c(alfile), listname, listname, 0) < 0) - sysfatal("creating %s's pipeto: %r", s_to_c(owner)); - if(createpipeto(s_to_c(alfile), s_to_c(owner), listname, 1) < 0) - sysfatal("creating %s's pipeto: %r", s_to_c(owner)); - writeaddr(s_to_c(alfile), "# mlmgr c flag", 0, listname); - } else if(rflag){ + if(flag['c']){ + for(i = 0; i < Nboxes; i++){ + snprint(buf, sizeof buf, "%s%s", listname, suffix[i]); + if(creatembox(buf, nil) < 0) + sysfatal("creating %s's mbox: %r", buf); + if(createpipeto(alfile, buf, listname, dom, i) < 0) + sysfatal("creating %s's pipeto: %r", buf); + } + writeaddr(alfile, "# mlmgr c flag", 0, listname); + } else if(flag['r']){ if(argc != 2) usage(); addr = argv[1]; - writeaddr(s_to_c(alfile), "# mlmgr r flag", 0, listname); - writeaddr(s_to_c(alfile), addr, 1, listname); - } else if(aflag){ + writeaddr(alfile, "# mlmgr r flag", 0, listname); + writeaddr(alfile, addr, 1, listname); + } else if(flag['a']){ if(argc != 2) usage(); addr = argv[1]; - writeaddr(s_to_c(alfile), "# mlmgr a flag", 0, listname); - writeaddr(s_to_c(alfile), addr, 0, listname); - } else - usage(); + writeaddr(alfile, "# mlmgr a flag", 0, listname); + writeaddr(alfile, addr, 0, listname); + } 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; - } - 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; -} diff --git a/sys/src/cmd/upas/ml/mlowner.c b/sys/src/cmd/upas/ml/mlowner.c index d3115978e..47761f8e2 100644 --- a/sys/src/cmd/upas/ml/mlowner.c +++ b/sys/src/cmd/upas/ml/mlowner.c @@ -22,6 +22,8 @@ main(int argc, char **argv) char *listname; ARGBEGIN{ + default: + usage(); }ARGEND; rfork(RFENVG|RFREND); diff --git a/sys/src/cmd/upas/ned/mkfile b/sys/src/cmd/upas/ned/mkfile index bc54914df..e5b8df406 100644 --- a/sys/src/cmd/upas/ned/mkfile +++ b/sys/src/cmd/upas/ned/mkfile @@ -1,4 +1,5 @@ #include +#include -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 { - char *cmd; - int args; - Message* (*f)(Cmd*, Message*); - 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 }, +struct Dirstats { + int new; + int del; + int old; + int unread; }; -enum -{ - NARG= 32, +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; + int addr; + Mfn *f; + char *help; +} cmdtab[] = { + { "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); + switchmb(mb, singleton); + top.path = strdup(root); + for(cp = ctype; cp < ctype + nelem(ctype) - 1; cp++) + cp->next = cp + 1; - top.path = s_copy(root); - - for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++) - cp->next = cp+1; - - if(singleton != nil){ - cur = dosingleton(&top, singleton); + if(singleton){ + cur = dosingleton(&top, mb); if(cur == nil){ - Bprint(&out, "no message\n"); + eprint("no message\n"); exitfs(0); } pcmd(nil, cur); } else { cur = ⊤ - n = dir2message(&top, reverse); - if(n < 0) - sysfatal("can't read %s", s_to_c(top.path)); - Bprint(&out, "%d message%s\n", n, plural(n)); + if(icmd(nil, cur) == nil) + sysfatal("can't read %s", top.path); } - notify(catchnote); - prompt = s_new(); for(;;){ - s_reset(prompt); - if(cur == &top) - s_append(prompt, ": "); - else { - mkid(prompt, cur); - s_append(prompt, ": "); - } + snprint(prompt, sizeof prompt, "%D: ", cur); - // leave space at the end of cmd line in case parsecmd needs to - // add a space after a '|' or '!' - if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil) + /* + * leave space at the end of cmd line in case parsecmd needs to + * add a space after a '|' or '!' + */ + if(readline(prompt, cmdline, sizeof cmdline - 1) == nil) break; err = parsecmd(cmdline, &cmd, top.child, cur); if(err != nil){ - Bprint(&out, "!%s\n", err); + eprint("!%s\n", err); continue; } - if(singleton != nil && cmd.f == icmd){ - Bprint(&out, "!illegal command\n"); + if(singleton && (cmd.f == icmd || cmd.f == ycmd)){ + eprint("!illegal command\n"); continue; } interrupted = 0; if(cmd.msgs == nil || cmd.msgs == &top){ - x = (*cmd.f)(&cmd, &top); - if(x != nil) + if(x = cmd.f(&cmd, &top)) cur = x; } else for(m = cmd.msgs; m != nil; m = m->cmd){ x = m; 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) + for(i = highest + 1;; i++){ + snprint(buf, sizeof buf, "%s/%d", parent->path, i); + if((d = dirstat(buf)) == nil) + break; + if((d->qid.type & QTDIR) == 0){ + free(d); continue; - if(atoi(d[i].name) <= highest) - continue; - m = file2message(parent, d[i].name); + } + free(d); + snprint(buf, sizeof buf, "%d", i); + m = file2message(parent, buf); if(m == nil) break; + m->id = m->fileno; newmsgs++; - if(reverse){ - m->next = first; - if(first != nil) - first->prev = m; + if(first == nil) first = m; - } else { - if(first == nil) - first = m; - else - last->next = m; - m->prev = last; - last = m; + 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{ + 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) +/* + * walk the path name an element + */ +char* +extendp(char *dir, char *name) { - String *s; - int fd, n, m; + static char buf[Pathlen]; - s = extendpath(dir, file); - fd = open(s_to_c(s), OREAD); - s_grow(s, 512); /* avoid multiple reads on info files */ - s_reset(s); - if(fd < 0) - return s; - - for(;;){ - n = s->end - s->ptr; - if(n == 0){ - s_grow(s, 128); - continue; - } - m = read(fd, s->ptr, n); - if(m <= 0) - break; - s->ptr += m; - if(m < n) - break; - } - s_terminate(s); - close(fd); - - return s; -} - -// -// get the length of a file -// -int -filelen(String *dir, char *file) -{ - String *path; - Dir *d; - int rv; - - path = extendpath(dir, file); - d = dirstat(s_to_c(path)); - if(d == nil){ - s_free(path); - return -1; - } - s_free(path); - rv = d->length; - free(d); - return rv; -} - -// -// walk the path name an element -// -String* -extendpath(String *dir, char *name) -{ - String *path; - - if(strcmp(s_to_c(dir), ".") == 0) - path = s_new(); - else { - path = s_copy(s_to_c(dir)); - s_append(path, "/"); - } - s_append(path, name); - return path; -} - -int -cistrncmp(char *a, char *b, int n) -{ - while(n-- > 0){ - if(tolower(*a++) != tolower(*b++)) - return -1; - } - return 0; -} - -int -cistrcmp(char *a, char *b) -{ - for(;;){ - if(tolower(*a) != tolower(*b++)) - return -1; - if(*a++ == 0) - break; - } - return 0; + 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) +pheader0(char *buf, int len, Message *m) { - char timebuf[32]; - String *id; - char *p, *q; + char *f, *p, *q, frombuf[40], timebuf[32], h[5]; + int max; - // create id - id = s_new(); - mkid(id, m); + hds(h, m); + if(hcmdfmt == 0){ + f = m->from; + max = longestfrom; + }else{ + snprint(frombuf, sizeof frombuf-5, "%s", m->to); + p = strchr(frombuf, ' '); + if(p != nil) + snprint(p, 5, " ..."); + f = frombuf; + max = longestto; + if(max > sizeof frombuf) + max = sizeof frombuf; + } - if(*m->from == 0){ - // no from - snprint(buf, len, "%-3s %s %6d %s", - s_to_c(id), - m->type, - m->len, - m->filename); - } else if(*m->subject){ + if(*f == 0) + snprint(buf, len, "%3D %s %6d %s", + m, m->type, m->len, m->filename); + else if(*m->subject){ q = p = strdup(m->subject); while(*p == ' ') p++; if(strlen(p) > 50) p[50] = 0; - cracktime(m->date, timebuf, sizeof(timebuf)); - snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s", - s_to_c(id), - m->child ? 'H' : ' ', - m->deleted ? 'd' : ' ', - m->stored ? 's' : ' ', - m->len, - timebuf, - longestfrom, longestfrom, m->from, - p); + cracktime(m->date, timebuf, sizeof timebuf); + snprint(buf, len, "%3D %s %6d %11.11s %-*.*s %s", + m, h, m->len, timebuf, max, max, f, p); free(q); } else { - cracktime(m->date, timebuf, sizeof(timebuf)); - snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s", - s_to_c(id), - m->child ? 'H' : ' ', - m->deleted ? 'd' : ' ', - m->stored ? 's' : ' ', - m->len, - timebuf, - m->from); + cracktime(m->date, timebuf, sizeof timebuf); + snprint(buf, len, "%3D %s %6d %11.11s %s", + m, h, m->len, timebuf, f); } - s_free(id); } -char *spaces = " "; - void -snprintHeader(char *buf, int len, int indent, Message *m) +pheader(char *buf, int len, int indent, Message *m) { - String *id; - char typeid[64]; - char *p, *e; - - // 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 '%' ; -// -Reprog* -parsesearch(char **pp) +/* + * 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) { - char *p, *np; + for(;; s++){ + if(*s == '\\') + s++; + else if(*s == c) + return s; + if(*s == 0) + return nil; + } +} + +Reprog* +parsesearch(char **pp, char *buf, int bufl) +{ + 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) -{ - Message *m; +enum{ + Comma = 1, +}; - 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; +/* + * search a message for a regular expression match + */ +int +fsearch(Message *m, Reprog *prog, char *field) +{ + char buf[4096 + 1]; + int i, fd, rv; + uvlong o; + + 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 = ⊤ break; } - /* fall through */ number: - err = num2msg(mp, sign, n, first, cur); - if (err != nil) - return err; + m0 = m = nil; + switch(sign){ + case 0: + for(m = first; m != nil; m0 = m, m = m->next) + if(m->id == n) + break; + break; + case -1: + if(cur != &top) + for(m = cur; m0 = m, m != nil && n > 0; n--) + m = m->prev; + break; + case 1: + if(cur == &top){ + n--; + cur = first; + } + for(m = cur; m != nil && n > 0; m0 = m, n--) + m = m->next; + break; + } + if(m == nil && f&Comma) + m = m0; + if(m == nil) + return "address"; + *mp = m; break; + case '?': + /* legacy behavior. no longer needed */ + sign = -1; case '%': case '/': - case '?': + case '#': c = *p; - prog = parsesearch(pp); + fn= chartosearch(c); + prog = parsesearch(pp, buf, sizeof buf); if(prog == nil) return "badly formed regular expression"; - m = nil; - switch(c){ - case '%': - for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ - if(rawsearch(m, prog)) + if(sign == -1){ + for(m = cur == &top ? nil : cur->prev; m; m = m->prev) + if(fn(m, prog, buf)) break; - } - break; - case '/': - for(m = cur == &top ? first : cur->next; m != nil; m = m->next){ - snprintheader(buf, sizeof(buf), m); - if(regexec(prog, buf, nil, 0)) + }else{ + for(m = cur == &top ? first : cur->next; m; m = m->next) + if(fn(m, prog, buf)) break; - } - break; - case '?': - for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){ - snprintheader(buf, sizeof(buf), m); - if(regexec(prog, buf, nil, 0)) - break; - } - break; } if(m == nil) return "search"; @@ -1008,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 = ' '; + while(*p == ' ' || *p == '\t') + p++; + /* hack to allow all messages to start with 'd' */ + if(*p == 'd' && p[1]){ + cmd->delete = 1; + p++; } - - cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n"); - if(cmd->an == 0 || *cmd->av[0] == 0) - cmd->f = pcmd; - else { - // hack to allow all messages to start with 'd' - if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){ - cmd->delete = 1; - cmd->av[0]++; - } - - // search command table - for(i = 0; cmdtab[i].cmd != nil; i++) - if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0) - break; - if(cmdtab[i].cmd == nil) - return "illegal command"; - if(cmdtab[i].args == 0 && cmd->an > 1){ - snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd); - return errbuf; - } - cmd->f = cmdtab[i].f; + while(*p == ' ' || *p == '\t') + p++; + if(*p == 0) + p = "p"; + for(i = nelem(cmdtab) - 1; i >= 0; i--) + if(strncmp(p, cmdtab[i].cmd, strlen(cmdtab[i].cmd)) == 0) + goto found; + return "illegal command"; +found: + p += strlen(cmdtab[i].cmd); + snprint(cmd->cmdline, sizeof cmd->cmdline, "%s", p); + cmd->av[0] = cmdtab[i].cmd; + cmd->an = 1 + tokenize(p, cmd->av + 1, nelem(cmd->av) - 2); + if(cmdtab[i].args == 0 && cmd->an > 1){ + snprint(errbuf, sizeof errbuf, "%s doesn't take an argument", cmdtab[i].cmd); + return errbuf; } - return nil; -} + cmd->f = cmdtab[i].f; -// inefficient read from standard input -char* -readline(char *prompt, char *line, int len) -{ - char *p, *e; - int n; - -retry: - interrupted = 0; - Bprint(&out, "%s", prompt); - Bflush(&out); - e = line + len; - for(p = line; p < e; p++){ - n = read(0, p, 1); - if(n < 0){ - if(interrupted) - goto retry; - return nil; - } - if(n == 0) - return nil; - if(*p == '\n') - break; - } - *p = 0; - return line; -} - -void -messagecount(Message *m) -{ - int i; - - i = 0; - for(; m != nil; m = m->next) - i++; - Bprint(&out, "%d message%s\n", i, plural(i)); + 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]; + char hdr[256]; - if(m == &top) - return nil; - - snprintHeader(hdr, sizeof(hdr), indent, m); + pheader(hdr, sizeof hdr, indent, m); Bprint(&out, "%s\n", hdr); for(m = m->child; m != nil; m = m->next) - aichcmd(m, indent+1); - return nil; + aichcmd(m, indent + 1); + return m; } Message* @@ -1280,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 ⊤ 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; } -Message* -pcmd(Cmd*, Message *m) +void +setflags(Message *m, char *f) { - Message *nm; + uchar f0; + + f0 = m->flags; + txflags(f, &m->flags); + if(f0 != m->flags) + if((m->nflags & Nnoflags) == 0) + writepart(m->path, "flags", f); +} + +Message* +Fcmd(Cmd *c, Message *m) +{ + int i; + + for(i = 1; i < c->an; i++) + setflags(m, c->av[i]); + return m; +} + +void +seen(Message *m) +{ + setflags(m, "s"); +} + +/* + * sleeze + */ +int +magicpart(Message *m, char *s, char *part) +{ + char buf[4096]; + int n, fd, c; + + fd = open(extendp(s, part), OREAD); + if(fd < 0){ + if(strcmp(part, "id") == 0) + Bprint(&out, "%D ", m); + else if(strcmp(part, "fpath") == 0) + Bprint(&out, "%s ", rooted(m->path)); + else + Bprint(&out, "%s ", part); + return 0; + } + + c = 0; + while((n = read(fd, buf, sizeof buf)) > 0){ + c = -1; + if(interrupted) + break; + if(Bwrite(&out, buf, n) <= 0) + break; + c = buf[n - 1]; + } + close(fd); + if(!interrupted && n != -1 && c != -1) + if(strstr(part, "body") != nil || strcmp(part, "rawunix") == 0) + seen(m); + return c; +} + +Message* +pcmd0(Cmd *c, Message *m, int mayplumb, char *tfmt) +{ + char *s, buf[128]; + int i, ch; Ctype *cp; - String *s; - char buf[128]; + Message *nm; if(m == &top) return ⊤ - if(m->parent == &top) - printpart(m->path, "unixheader"); - if(printpart(m->path, "header") > 0) + if(c && c->an >= 2){ + ch = 0; + for(i = 1; i < c->an; i++) + ch = magicpart(m, m->path, c->av[i]); + if(ch != '\n') + Bprint(&out, "\n"); + return m; + } + if(m->parent == &top){ + seen(m); + printpart(m, "unixheader", nil); + } + if(printpart(m, "header", nil) > 0) Bprint(&out, "\n"); cp = findctype(m); - if(cp->display){ + if(cp->flag & Display){ if(strcmp(m->type, "text/html") == 0) printhtml(m); else - printpart(m->path, "body"); - } else if(strcmp(m->type, "multipart/alternative") == 0){ + printpart(m, "body", tfmt); + }else if(strcmp(m->type, "multipart/alternative") == 0){ for(nm = m->child; nm != nil; nm = nm->next){ cp = findctype(nm); if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) @@ -1418,67 +1703,91 @@ 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); } -Message* -quotecmd(Cmd*, Message *m) +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* +quotecmd0(Cmd *c, Message *m, void (*p)(char*, char*, char*)) { - Message *nm; Ctype *cp; + Message *nm; if(m == &top) return ⊤ @@ -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; @@ -1607,53 +1942,82 @@ flushdeleted(Message *cur) } Message* -qcmd(Cmd*, Message*) +mbcmd(Cmd *c, Message*) { - flushdeleted(nil); + char *mb, oldmb[Pathlen]; + Message *m, **l; - if(didopen) - closemb(); - Bflush(&out); + switch(c->an){ + case 1: + mb = "mbox"; + break; + case 2: + mb = c->av[1]; + break; + default: + eprint("!usage: mbcmd [mbox]\n"); + return nil; + } - exitfs(0); - return nil; // not reached + /* flushdeleted(nil); ? */ + for(l = &top.child; *l; ){ + m = *l; + *l = m->next; + freemessage(m); + } + top.child = nil; + + strcpy(oldmb, mbpath); + if(switchmb(mb, 0) < 0){ + eprint("!no mb\n"); + if(switchmb(oldmb, 0) < 0){ + eprint("!mb disappeared\n"); + exits("fail"); + } + } + icmd(nil, nil); + interrupted = 1; /* no looping */ + return ⊤ } Message* -ycmd(Cmd*, Message *m) +qcmd(Cmd*, Message*) +{ + flushdeleted(nil); + if(didopen) + closemb(); + Bflush(&out); + exitfs(0); + return nil; +} + +Message* +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*, Message *m) +icmd(Cmd *c, Message *m) { - int n; + char buf[128], *p, *e; + Dirstats s; - n = dir2message(&top, reverse); - if(n > 0) - Bprint(&out, "%d new message%s\n", n, plural(n)); + if(skipscan()) + return m; + if(dir2message(&top, reverse, &s) < 0) + return nil; + p = buf; + e = buf + sizeof buf; + if(s.new > 0 && c == nil){ + p = seprint(p, e, "%d message%s", s.new, plural(s.new)); + if(s.unread > 0) + p = seprint(p, e, ", %d unread", s.unread); + } + else if(s.new > 0) + Bprint(&out, "%d new message%s", s.new, plural(s.new)); + if(s.new && s.del) + p = seprint(p, e, "; "); + if(s.del > 0) + p = seprint(p, e, "%d deleted message%s", s.del, plural(s.del)); + if(s.new + s.del) + p = seprint(p, e, "\n"); + if(p > buf){ + Bflush(&out); + eprint("%s", buf); + } return m; } +Message* +kcmd0(Cmd *c, Message *m) +{ + char *f, *s; + int sticky; + + if(c->an > 2){ + eprint("!usage k [flags]\n"); + return nil; + } + if(c->f == kcmd) + f = "f"; + else + f = "-f"; + if(c->an == 2) + f = c->av[1]; + setflags(m, f); + if(c->an == 2 && (m->nflags & Nnoflags) == 0){ + sticky = m->flags & Fdeleted; + s = file2string(m->path, "flags"); + m->flags = buftoflags(s) | sticky; + free(s); + } + return m; +} + +Message* +kcmd(Cmd *c, Message *m) +{ + return kcmd0(c, m); +} + +Message* +Kcmd(Cmd *c, Message *m) +{ + return kcmd0(c, m); +} + Message* helpcmd(Cmd*, Message *m) { @@ -1690,38 +2128,50 @@ helpcmd(Cmd*, Message *m) Bprint(&out, "Commands are of the form [] [args]\n"); Bprint(&out, " := | ','| 'g'\n"); Bprint(&out, " := '.' | '$' | '^' | | | '+' | '-'\n"); - Bprint(&out, " := '/''/' | '?''?' | '%%''%%'\n"); + Bprint(&out, " := 'k' | '/''/' | '?''?' | '%%''%%' | '#' '#' '#' \n"); Bprint(&out, " :=\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) -{ - Message *nm; +static char *rec[] = {"Re: ", "AW:", }; +static char *fwc[] = {"Fwd: ", }; - for(nm = m; nm != ⊤ nm = nm->parent) - if(*nm->replyto != 0) +char* +addrecolon(char **tab, int n, char *s) +{ + char *prefix; + int i; + + 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"); + ai = 1; + av[ai++] = "-8"; + addr = path = subject = nil; + for(nm = m; nm != ⊤ nm = nm->parent) + if(*nm->replyto != 0){ + addr = nm->replyto; + break; + } + if(addr == nil){ + eprint("!no reply address\n"); return nil; } - addr = getreplyto(m, &nm); - if(addr == nil){ - Bprint(&out, "!no reply address\n"); - return nil; - } if(nm == &top){ print("!noone to reply to\n"); return nil; } - av[ai++] = "-8"; - for(nm = m; nm != ⊤ nm = nm->parent){ + for(nm = m; nm != ⊤ nm = nm->parent) if(*nm->subject){ av[ai++] = "-s"; - subject = addrecolon(nm->subject); - av[ai++] = s_to_c(subject); + subject = addrecolon(rec, nelem(rec), nm->subject); + av[ai++] = subject; break; } - } av[ai++] = "-R"; - rpath = rooted(s_clone(m->path)); - av[ai++] = s_to_c(rpath); + av[ai++] = rpath = strdup(rooted(m->path)); if(strchr(c->av[0], 'f') != nil){ fcmd(c, m); @@ -1843,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"; - - if(*m->subject){ - av[ai++] = "-s"; - subject = addrecolon(m->subject); - av[ai++] = s_to_c(subject); - } - av[ai++] = "-R"; - rpath = rooted(s_clone(m->path)); - av[ai++] = s_to_c(rpath); + av[ai++] = rpath = strdup(rooted(m->path)); + + subject = nil; + if(m->subject && *m->subject){ + av[ai++] = "-s"; + subject = addrecolon(rec, nelem(rec), m->subject); + av[ai++] = subject; + } if(strchr(c->av[0], 'A') != nil){ av[ai++] = "-t"; av[ai++] = "message/rfc822"; av[ai++] = "-A"; - path = rooted(extendpath(m->path, "raw")); - av[ai++] = s_to_c(path); + av[ai++] = rooted(extendp(m->path, "raw")); } for(i = 1; i < c->an && ai < nelem(av)-1; i++) av[ai++] = c->av[i]; - from = s_copy(m->from); - ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai); - to = s_copy(m->to); - ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai); - cc = s_copy(m->cc); - ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai); + ai += tokenize822(from = strdup(m->from), &av[ai], nelem(av) - ai); + ai += tokenize822(to = strdup(m->to), &av[ai], nelem(av) - ai); + ai += tokenize822(cc = strdup(m->cc), &av[ai], nelem(av) - ai); av[ai] = 0; - if(tomailer(av) < 0) + if(tomailer(av) == -1) m = nil; - s_free(path); - s_free(rpath); - s_free(subject); - s_free(from); - s_free(to); - s_free(cc); + else + m->flags |= Fanswered; + free(from); + free(to); + free(cc); + free(subject); + free(rpath); return m; } -String * -relpath(char *path, String *to) -{ - if (*path=='/' || strncmp(path, "./", 2) == 0 - || strncmp(path, "../", 3) == 0) { - to = s_append(to, path); - } else if(mbpath) { - to = s_append(to, s_to_c(mbpath)); - to->ptr = strrchr(to->base, '/')+1; - s_append(to, path); - } - return to; -} - int -appendtofile(Message *m, char *part, char *base, int mbox) +appendtofile(Message *m, char *part, char *base, int mbox, int f) { - String *file, *h; - int in, out, rv; + char *folder, path[Pathlen]; + int in, rv, rp; - file = extendpath(m->path, part); - in = open(s_to_c(file), OREAD); - if(in < 0){ - fprint(2, "!message disappeared\n"); + in = open(extendp(m->path, part), OREAD); + if(in == -1){ + dissappeared(); return -1; } - - s_reset(file); - - relpath(base, file); - if(sysisdir(s_to_c(file))){ - s_append(file, "/"); - if(m->filename && strchr(m->filename, '/') == nil) - s_append(file, m->filename); - else { - s_append(file, "att.XXXXXXXXXXX"); - mktemp(s_to_c(file)); - } - } - if(mbox) - out = open(s_to_c(file), OWRITE); + rp = 0; + if(*base == '/') + folder = base; + else if(!mbox){ + snprint(path, sizeof path, "%s/%s", wd, base); + folder = path; + rp = 1; + }else if(f) + folder = ffoldername(mbpath, user, base); else - out = open(s_to_c(file), OWRITE|OTRUNC); - if(out < 0){ - out = create(s_to_c(file), OWRITE, 0666); - if(out < 0){ - fprint(2, "!can't open %s: %r\n", s_to_c(file)); - close(in); - s_free(file); - return -1; - } - } + folder = foldername(mbpath, user, base); + if(folder == nil) + return -1; if(mbox) - seek(out, 0, 2); - - // put on a 'From ' line - if(mbox){ - while(m->parent != &top) - m = m->parent; - h = file2string(m->path, "unixheader"); - fprint(out, "%s", s_to_c(h)); - s_free(h); - } - - // copy the message escaping what we have to ad adding newlines if we have to - if(mbox) - rv = appendfiletombox(in, out); + rv = fappendfolder(0, 0, folder, in); else - rv = appendfiletofile(in, out); - + rv = fappendfile(m->from, folder, in); close(in); - close(out); - - if(rv >= 0) - print("!saved in %s\n", s_to_c(file)); - s_free(file); + if(rv >= 0){ + eprint("!saved in %s\n", rp? base: folder); + setflags(m, "S"); + }else + eprint("!error %r\n"); return rv; } @@ -2041,11 +2448,6 @@ 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) +void +loadxfrom(int fd) { - char *p; - int i; + char *f[3], *s, *p; + int n, a, inc; + Biobuf b; + Xtab *x; - p = strrchr(s_to_c(s), '/'); - if(p == nil) - p = s_to_c(s); - else - p++; - for(i = 0; i < nelem(specialfile); i++) - if(strcmp(p, specialfile[i]) == 0) - return 1; - return 0; + Binit(&b, fd, OREAD); + a = 0; + inc = 100; + for(; s = Brdstr(&b, '\n', 1);){ + if(p = strchr(s, '#')) + *p = 0; + n = tokenize(s, f, nelem(f)); + if(n != 2){ + free(s); + continue; + } + if(nxtab == a){ + a += inc; + xtab = realloc(xtab, a*sizeof *xtab); + if(xtab == nil) + sysfatal("realloc: %r"); + inc *= 2; + } + for(x = xtab+nxtab; x > xtab && strcmp(x[-1].a, f[0]) > 0; x--) + x[0] = x[-1]; + x->a = f[0]; + x->b = f[1]; + nxtab++; + } } -// open the folder using the recipients account name -static String* -foldername(char *rcvr) +char* +frombox(char *from) { - char *p; - int c; - String *file; - Dir *d; - int scarey; + char *s; + int n, m, fd; + Xtab *t, *p; + static int once; - file = s_new(); - mboxpath("f", user, file, 0); - d = dirstat(s_to_c(file)); - - // if $mail/f exists, store there, otherwise in $mail - s_restart(file); - if(d && d->qid.type == QTDIR){ - scarey = 0; - s_append(file, "f/"); - } else { - scarey = 1; + if(once == 0){ + once = 1; + s = foldername(mbpath, user, "fromtab-"); + fd = open(s, OREAD); + if(fd != -1) + loadxfrom(fd); + close(fd); } - free(d); - - p = strrchr(rcvr, '!'); - if(p != nil) - rcvr = p+1; - - while(*rcvr && *rcvr != '@'){ - c = *rcvr++; - if(c == '/') - c = '_'; - s_putc(file, c); + t = xtab; + n = nxtab; + while(n > 1) { + m = n/2; + p = t + m; + if(strcmp(from, p->a) > 0){ + t = p; + n = n - m; + } else + n = m; } - s_terminate(file); - - if(scarey && special(file)){ - fprint(2, "!won't overwrite %s\n", s_to_c(file)); - s_free(file); - return nil; - } - - return file; + if(n && strcmp(from, t->a) == 0) + return t->b; + return from; } Message* -fcmd(Cmd *c, Message *m) +fcmd(Cmd*, Message *m) { - String *folder; + char *f; - if(c->an > 1){ - fprint(2, "!usage: f takes no arguments\n"); + f = frombox(m->from); + if(appendtofile(m, "rawunix", f, 1, 1) < 0) return nil; - } + return m; +} - if(m == &top){ - Bprint(&out, "!address\n"); - return nil; - } +Message* +fqcmd(Cmd*, Message *m) +{ + char *f; - folder = foldername(m->from); - if(folder == nil) - return nil; - - if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){ - s_free(folder); - return nil; - } - s_free(folder); - - m->stored = 1; + f = frombox(m->from); + Bprint(&out, "! %s\n", f); return m; } @@ -2219,7 +2607,7 @@ 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"); + eprint("!usage: | cmd\n"); return nil; } - if(m == &top){ - Bprint(&out, "!address\n"); - return nil; - } - - path = extendpath(m->path, part); - fd = open(s_to_c(path), OREAD); - s_free(path); - if(fd < 0){ // compatibility with older upas/fs - path = extendpath(m->path, "raw"); - fd = open(s_to_c(path), OREAD); - s_free(path); - } + fd = open(extendp(m->path, part), OREAD); if(fd < 0){ - fprint(2, "!message disappeared\n"); + dissappeared(); return nil; } - p = cmd; - e = cmd+sizeof(cmd); - cmd[0] = 0; - for(i = 1; i < c->an; i++) - p = seprint(p, e, "%s ", c->av[i]); av[0] = "rc"; av[1] = "-c"; - av[2] = cmd; + av[2] = c->cmdline; av[3] = 0; system("/bin/rc", av, fd); /* system closes fd */ - Bprint(&out, "!\n"); +// Bprint(&out, "!\n"); return m; } @@ -2323,172 +2683,128 @@ 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; - - 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); - 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); - }else{ - path = s_reset(nil); - mboxpath("mbox", user, path, 0); - strcpy(mbname, "mbox"); + p = strrchr(s, c); + if(p != nil && p > s) { + *p = 0; + return p - 1; } - - snprint(root, sizeof root, "/mail/fs/%s", mbname); - if(getwd(wd, sizeof(wd)) == 0) - wd[0] = 0; - if(singleton == nil && chdir(root) >= 0) - strcpy(root, "."); - rootlen = strlen(root); - - if(mbpath != nil) - s_free(mbpath); - mbpath = path; return 0; } -// like tokenize but for into lines +/* sometimes opens the file (or open mbox) intended. */ int -lineize(char *s, char **f, int n) +switchmb(char *mb, int singleton) { - int i; + char *p, *e, pbuf[Pathlen], buf[Pathlen], file[Pathlen]; + int fd, abs; - for(i = 0; *s && i < n; i++){ - f[i] = s; - s = strchr(s, '\n'); - if(s == nil) - break; - *s++ = 0; + 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"); + 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); + didopen = 1; + }else{ + mboxpathbuf(pbuf, sizeof pbuf, user, mb); + strcpy(mbname, mb); } - return i; + + snprint(root, sizeof root, "/mail/fs/%s", mbname); + if(getwd(wd, sizeof wd) == nil) + wd[0] = 0; + if(!singleton && chdir(root) >= 0) + strcpy(root, "."); + rootlen = strlen(root); + snprint(mbpath, sizeof mbpath, "%s", pbuf); + memset(&mbqid, 0, sizeof mbqid); + mbvers++; + + return 0; } - - -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); } diff --git a/sys/src/cmd/upas/pop3/mkfile b/sys/src/cmd/upas/pop3/mkfile index 3f3df4134..7b0348a7a 100644 --- a/sys/src/cmd/upas/pop3/mkfile +++ b/sys/src/cmd/upas/pop3/mkfile @@ -1,10 +1,10 @@ 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"); - memset(&conn, 0, sizeof conn); - 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); + + memset(&conn, 0, sizeof conn); + conn.cert = tlscert; + conn.certlen = ntlscert; + if(debug) + conn.trace = trace; 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); } diff --git a/sys/src/cmd/upas/q/mkfile b/sys/src/cmd/upas/q/mkfile index f21d2992a..f58d82791 100644 --- a/sys/src/cmd/upas/q/mkfile +++ b/sys/src/cmd/upas/q/mkfile @@ -1,4 +1,5 @@ 0){ for(i=0; i 0){ for(i=0; ifd = 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 index 000000000..5bee6c9ee --- /dev/null +++ b/sys/src/cmd/upas/qfrom/mkfile @@ -0,0 +1,13 @@ + +#include +#include + +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(""); +} diff --git a/sys/src/cmd/upas/scanmail/mkfile b/sys/src/cmd/upas/scanmail/mkfile index 5f0db8556..37141c535 100644 --- a/sys/src/cmd/upas/scanmail/mkfile +++ b/sys/src/cmd/upas/scanmail/mkfile @@ -1,4 +1,5 @@ #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; diff --git a/sys/src/cmd/upas/scanmail/spam.h b/sys/src/cmd/upas/scanmail/spam.h index f1d24b2e0..2f49d7581 100644 --- a/sys/src/cmd/upas/scanmail/spam.h +++ b/sys/src/cmd/upas/scanmail/spam.h @@ -1,4 +1,3 @@ - enum{ Dump = 0, /* Actions must be in order of descending importance */ HoldHeader, diff --git a/sys/src/cmd/upas/scanmail/testscan.c b/sys/src/cmd/upas/scanmail/testscan.c index e5ea59ad5..61ea70a15 100644 --- a/sys/src/cmd/upas/scanmail/testscan.c +++ b/sys/src/cmd/upas/scanmail/testscan.c @@ -1,4 +1,5 @@ -#include "sys.h" +#include "common.h" +#include #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 diff --git a/sys/src/cmd/upas/send/authorize.c b/sys/src/cmd/upas/send/authorize.c index 6fa12c57c..98cf1220b 100644 --- a/sys/src/cmd/upas/send/authorize.c +++ b/sys/src/cmd/upas/send/authorize.c @@ -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; diff --git a/sys/src/cmd/upas/send/bind.c b/sys/src/cmd/upas/send/bind.c index 8a8fc8eac..5e97b872f 100644 --- a/sys/src/cmd/upas/send/bind.c +++ b/sys/src/cmd/upas/send/bind.c @@ -1,20 +1,34 @@ #include "common.h" #include "send.h" -static int forward_loop(char *, char *); +/* Return TRUE if a forwarding loop exists, i.e., the String `system' + * is found more than 4 times in the return address. + */ +static int +forward_loop(char *addr, char *system) +{ + int len, found; + + found = 0; + len = strlen(system); + while(addr = strchr(addr, '!')) + if (!strncmp(++addr, system, len) + && addr[len] == '!' && ++found == 4) + return 1; + return 0; +} + /* bind the destinations to the commands to be executed */ -extern dest * +dest * up_bind(dest *destp, message *mp, int checkforward) { - dest *list[2]; /* lists of unbound destinations */ - int li; /* index into list[2] */ - dest *bound=0; /* bound destinations */ - dest *dp; - int i; + int i, li; + dest *list[2], *bound, *dp; + bound = nil; list[0] = destp; - list[1] = 0; + list[1] = nil; /* * loop once to check for: @@ -24,7 +38,7 @@ 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; -} - diff --git a/sys/src/cmd/upas/send/cat_mail.c b/sys/src/cmd/upas/send/cat_mail.c index cdd16eced..f4001aa09 100644 --- a/sys/src/cmd/upas/send/cat_mail.c +++ b/sys/src/cmd/upas/send/cat_mail.c @@ -1,57 +1,121 @@ #include "common.h" #include "send.h" +/* + * warning will robinson + * + * mbox and mdir should likely be merged with ../common/folder.c + * at a minimum, changes need to done in sync. + */ + +static int +mbox(dest *dp, message *mp, char *s) +{ + char *tmp; + int i, n, e; + Biobuf *b; + Mlock *l; + + for(i = 0;; i++){ + l = syslock(s); + if(l == 0) + return refuse(dp, mp, "can't lock mail file", 0, 0); + b = sysopen(s, "al", Mboxmode); + if(b) + break; + b = sysopen(tmp = smprint("%s.tmp", s), "al", Mboxmode); + free(tmp); + sysunlock(l); + if(b){ + syslog(0, "mail", "error: used %s.tmp", s); + break; + } + if(i >= 5) + return refuse(dp, mp, "mail file cannot be opened", 0, 0); + sleep(1000); + } + 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); + } + return 0; +} /* dispose of local addresses */ int cat_mail(dest *dp, message *mp) { - Biobuf *fp; - char *rcvr, *cp; - Mlock *l; - String *tmp, *s; - int i, n; + char *rcvr, *cp, *s; + int e, isdir; + Dir *d; + String *ss; - s = unescapespecial(s_clone(dp->repl1)); - if (nflg) { - if(!xflg) - print("cat >> %s\n", s_to_c(s)); + 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(s); + s_free(ss); return 0; } - for(i = 0;; i++){ - l = syslock(s_to_c(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) - 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); - break; - } - s_free(tmp); - sysunlock(l); - if(i >= 5) - return refuse(dp, mp, "mail file cannot be opened", 0, 0); - sleep(1000); + /* avoid lock errors */ + if(strcmp(s, "/dev/null") == 0){ + s_free(ss); + return(0); } - 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); - return refuse(dp, mp, "error writing mail file", 0, 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; } - sysclose(fp); - sysunlock(l); + 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; diff --git a/sys/src/cmd/upas/send/dest.c b/sys/src/cmd/upas/send/dest.c index 3d7fffe45..f5ff5b825 100644 --- a/sys/src/cmd/upas/send/dest.c +++ b/sys/src/cmd/upas/send/dest.c @@ -1,21 +1,17 @@ #include "common.h" #include "send.h" -static String* s_parseq(String*, String*); - /* exports */ dest *dlist; -extern dest* +dest* d_new(String *addr) { dest *dp; dp = (dest *)mallocz(sizeof(dest), 1); - if (dp == 0) { - perror("d_new"); - exit(1); - } + if (dp == 0) + sysfatal("malloc: %r"); dp->same = dp; dp->nsame = 1; dp->nchar = 0; @@ -27,7 +23,7 @@ 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; +} diff --git a/sys/src/cmd/upas/send/filter.c b/sys/src/cmd/upas/send/filter.c index 83afd049e..e5fe926e2 100644 --- a/sys/src/cmd/upas/send/filter.c +++ b/sys/src/cmd/upas/send/filter.c @@ -1,36 +1,58 @@ #include "common.h" #include "send.h" +#include 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(""); } diff --git a/sys/src/cmd/upas/send/gateway.c b/sys/src/cmd/upas/send/gateway.c index fac9f42ea..ceb0b0b00 100644 --- a/sys/src/cmd/upas/send/gateway.c +++ b/sys/src/cmd/upas/send/gateway.c @@ -1,13 +1,11 @@ #include "common.h" #include "send.h" -#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n') - /* * Translate the last component of the sender address. If the translation * yields the same address, replace the sender with its last component. */ -extern void +void gateway(message *mp) { char *base; diff --git a/sys/src/cmd/upas/send/local.c b/sys/src/cmd/upas/send/local.c index 93136b7c6..c1014d3d8 100644 --- a/sys/src/cmd/upas/send/local.c +++ b/sys/src/cmd/upas/send/local.c @@ -1,12 +1,21 @@ #include "common.h" #include "send.h" +static String* +mboxpath(char *path, char *user, String *to) +{ + char buf[Pathlen]; + + mboxpathbuf(buf, sizeof buf, user, path); + return s_append(to, buf); +} + static void mboxfile(dest *dp, String *user, String *path, char *file) { char *cp; - mboxpath(s_to_c(user), s_to_c(dp->addr), path, 0); + mboxpath(s_to_c(user), s_to_c(dp->addr), path); cp = strrchr(s_to_c(path), '/'); if(cp) path->ptr = cp+1; @@ -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; diff --git a/sys/src/cmd/upas/send/log.c b/sys/src/cmd/upas/send/log.c index 52df38001..9324267e6 100644 --- a/sys/src/cmd/upas/send/log.c +++ b/sys/src/cmd/upas/send/log.c @@ -1,11 +1,8 @@ #include "common.h" #include "send.h" -/* configuration */ -#define LOGBiobuf "log/status" - /* log mail delivery */ -extern void +void logdelivery(dest *list, char *rcvr, message *mp) { dest *parent; @@ -29,7 +26,7 @@ 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); diff --git a/sys/src/cmd/upas/send/main.c b/sys/src/cmd/upas/send/main.c index dcf518825..77cebc782 100644 --- a/sys/src/cmd/upas/send/main.c +++ b/sys/src/cmd/upas/send/main.c @@ -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 index f0abcf0c1..000000000 --- a/sys/src/cmd/upas/send/makefile +++ /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 diff --git a/sys/src/cmd/upas/send/message.c b/sys/src/cmd/upas/send/message.c index 69cddd87c..02b7520f2 100644 --- a/sys/src/cmd/upas/send/message.c +++ b/sys/src/cmd/upas/send/message.c @@ -1,49 +1,51 @@ #include "common.h" #include "send.h" - +#include #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); diff --git a/sys/src/cmd/upas/send/mkfile b/sys/src/cmd/upas/send/mkfile index a895cd4a4..549d234f0 100644 --- a/sys/src/cmd/upas/send/mkfile +++ b/sys/src/cmd/upas/send/mkfile @@ -1,4 +1,5 @@ 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: * [] */ -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:"..."); +// } +//} diff --git a/sys/src/cmd/upas/send/send.h b/sys/src/cmd/upas/send/send.h index 45fd61c33..1b7d4537d 100644 --- a/sys/src/cmd/upas/send/send.h +++ b/sys/src/cmd/upas/send/send.h @@ -1,11 +1,5 @@ -/* - * these limits are intended to stay within those imposed by SMTP - * and avoid tickling bugs in other mail systems. - * they both pertain to attempts to group recipients for the same - * destination together in a single copy of a message. - */ -#define MAXSAME 32 /* max recipients; was 16 */ -#define MAXSAMECHAR 1024 /* max chars in the list of recipients */ +#define MAXSAME 16 +#define MAXSAMECHAR 1024 /* status of a destination*/ typedef enum { @@ -47,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*); diff --git a/sys/src/cmd/upas/send/skipequiv.c b/sys/src/cmd/upas/send/skipequiv.c index 43d7b8ac3..c6da2557c 100644 --- a/sys/src/cmd/upas/send/skipequiv.c +++ b/sys/src/cmd/upas/send/skipequiv.c @@ -3,10 +3,53 @@ #define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n') +static int +okfile(char *s, Biobuf *b) +{ + char *buf, *p, *e; + int len, c; + + len = strlen(s); + Bseek(b, 0, 0); + + /* one iteration per system name in the file */ + while(buf = Brdline(b, '\n')) { + e = buf + Blinelen(b); + for(p = buf; p < e;){ + while(isspace(*p) || *p==',') + p++; + if(strncmp(p, s, len) == 0) { + c = p[len]; + if(isspace(c) || c==',') + return 1; + } + while(p < e && (!isspace(*p)) && *p!=',') + p++; + } + } + /* didn't find it, prohibit forwarding */ + return 0; +} + +/* return 1 if name found in file + * 0 if name not found + * -1 if + */ +static int +lookup(char *s, char *local, Biobuf **b) +{ + char file[Pathlen]; + + snprint(file, sizeof file, "%s/%s", UPASLIB, local); + if(*b != nil || (*b = sysopen(file, "r", 0)) != nil) + return okfile(s, *b); + return 0; +} + /* * skip past all systems in equivlist */ -extern char* +char* skipequiv(char *base) { char *sp; @@ -17,7 +60,7 @@ 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; -} diff --git a/sys/src/cmd/upas/send/translate.c b/sys/src/cmd/upas/send/translate.c index 0332659c0..e2dd7382f 100644 --- a/sys/src/cmd/upas/send/translate.c +++ b/sys/src/cmd/upas/send/translate.c @@ -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 index fed3a2a60..000000000 --- a/sys/src/cmd/upas/send/tryit +++ /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 <>>test.local<<<" -cat /usr/spool/mail/test.local -echo ">>>test.mail<<<" -cat /tmp/test.mail diff --git a/sys/src/cmd/upas/smtp/greylist.c b/sys/src/cmd/upas/smtp/greylist.c index b5cee5a6e..3bbe9f586 100644 --- a/sys/src/cmd/upas/smtp/greylist.c +++ b/sys/src/cmd/upas/smtp/greylist.c @@ -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 */ diff --git a/sys/src/cmd/upas/smtp/mkfile b/sys/src/cmd/upas/smtp/mkfile index 10b312b90..9f02f8ae2 100644 --- a/sys/src/cmd/upas/smtp/mkfile +++ b/sys/src/cmd/upas/smtp/mkfile @@ -1,8 +1,12 @@ $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 diff --git a/sys/src/cmd/upas/smtp/mxdial.c b/sys/src/cmd/upas/smtp/mxdial.c index c17e280c3..74516a030 100644 --- a/sys/src/cmd/upas/smtp/mxdial.c +++ b/sys/src/cmd/upas/smtp/mxdial.c @@ -1,282 +1,31 @@ #include "common.h" +#include "smtp.h" #include -#include /* 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; + char *s; + Ndbtuple *t; - addr = netmkaddr(addr, 0, "smtp"); - dial_string_parse(addr, &ds); - - /* try connecting to destination or any of it's mail routers */ - fd = callmx(&ds, addr, ddomain); - - /* try our mail gateway */ - if(fd < 0 && gdomain) - fd = dial(netmkaddr(gdomain, 0, "smtp"), 0, 0, 0); - - return fd; -} - -static int -busted(char *mx) -{ - char **bmp; - - for (bmp = bustedmxs; *bmp != nil; bmp++) - if (strcmp(mx, *bmp) == 0) - return 1; - return 0; -} - -static int -timeout(void*, char *msg) -{ - if(strstr(msg, "alarm")) - return 1; - return 0; -} - -long -timedwrite(int fd, void *buf, long len, long ms) -{ - long n, oalarm; - - atnotify(timeout, 1); - oalarm = alarm(ms); - n = write(fd, buf, len); - 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) -{ - 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; + 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; } - if(nmx == 0){ - if(debug) - fprint(2, "mxlookup returns nothing\n"); - return dial(dest, 0, 0, 0); - } - - /* refuse to honor loopback addresses given by dns */ - for(i = 0; i < nmx; i++) - if(strcmp(mx[i].ip, "127.0.0.1") == 0){ - if(debug) - fprint(2, "mxlookup returns loopback\n"); - werrstr("illegal: domain lists 127.0.0.1 as mail server"); - return -1; - } - - /* 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); - continue; - } - 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; -} - -/* - * 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) -{ - int n; - - /* 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); - } - } - - return n; -} - -static int -mxlookup1(DS *ds, char *domain) -{ - int i, n, fd, nmx; - char buf[1024], dnsname[Maxstring]; - char *fields[4]; - - snprint(dnsname, sizeof dnsname, "%s/dns", ds->netdir); - - fd = open(dnsname, ORDWR); - if(fd < 0) - return 0; - - nmx = 0; - snprint(buf, sizeof buf, "%s mx", ds->host); - if(debug) - fprint(2, "sending %s '%s'\n", dnsname, buf); - /* - * don't hang indefinitely in the write to /net/dns. - */ - n = timedwrite(fd, buf, strlen(buf), 60*1000); - if(n < 0){ - rerrstr(buf, sizeof buf); - if(debug) - fprint(2, "dns: %s\n", buf); - if(strstr(buf, "dns failure")){ - /* if dns fails for the mx lookup, we have to stop */ - close(fd); - return -1; - } - } else { - /* - * get any mx entries - */ - seek(fd, 0, 0); - while(nmx < Nmx && (n = read(fd, buf, sizeof buf-1)) > 0){ - buf[n] = 0; - if(debug) - fprint(2, "dns mx: %s\n", buf); - n = getfields(buf, fields, 4, 1, " \t"); - if(n < 4) - continue; - - if(strchr(domain, '.') == 0) - strcpy(domain, fields[0]); - - strncpy(mx[nmx].host, fields[3], sizeof(mx[n].host)-1); - mx[nmx].pref = atoi(fields[2]); - nmx++; - } - if(debug) - fprint(2, "dns mx; got %d entries\n", nmx); - } - - /* - * 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++; - } - - /* - * 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--; - } - return nmx; -} - -static int -compar(void *a, void *b) -{ - return ((Mx*)a)->pref - ((Mx*)b)->pref; + ndbfree(t); } /* break up an address to its component parts */ void -dial_string_parse(char *str, DS *ds) +dialstringparse(char *str, DS *ds) { char *p, *p2; - strncpy(ds->buf, str, sizeof(ds->buf)); - ds->buf[sizeof(ds->buf)-1] = 0; - + strecpy(ds->buf, ds->buf + sizeof ds->buf, str); p = strchr(ds->buf, '!'); if(p == 0) { ds->netdir = 0; @@ -300,58 +49,287 @@ dial_string_parse(char *str, DS *ds) if(ds->service) *ds->service++ = 0; if(*ds->host == '$') - expand_meta(ds); + expand(ds); +} + +void +mxtabfree(Mxtab *mx) +{ + free(mx->mx); + memset(mx, 0, sizeof *mx); } static void -expand_meta(DS *ds) +mxtabrealloc(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); + 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"); +} - 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; +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 +timeout(void*, char *msg) +{ + if(strstr(msg, "alarm")) + return 1; + return 0; +} + +static long +timedwrite(int fd, void *buf, long len, long ms) +{ + long n, oalarm; + + atnotify(timeout, 1); + oalarm = alarm(ms); + n = pwrite(fd, buf, len, 0); + alarm(oalarm); + atnotify(timeout, 0); + return n; +} + +static int +dnslookup(Mxtab *mx, int fd, char *query, char *domain, char *net, int pref0) +{ + int n; + char buf[1024], *f[4]; + + 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; } 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; + 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); + } } + + return 0; +} + +static int +busted(char *mx) +{ + char **bmp; + + for (bmp = bustedmxs; *bmp != nil; bmp++) + if (strcmp(mx, *bmp) == 0) + return 1; + return 0; +} + +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 +okaymx(Mxtab *mx, char *domain) +{ + 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; +} + +static int +lookup(Mxtab *mx, char *net, char *host, char *domain, char *type) +{ + char dns[128], buf[1024]; + int fd, i; + Mx *x; + + snprint(dns, sizeof dns, "%s/dns", net); + fd = open(dns, ORDWR); + if(fd == -1) + return -1; + + snprint(buf, sizeof buf, "%s %s", host, type); + dprint("sending %s '%s'\n", dns, buf); + dnslookup(mx, fd, buf, domain, net, 10000); + + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->ip[0] != 0) + continue; + x->valid = 0; + + snprint(buf, sizeof buf, "%s %s", x->host, "ip"); + dprint("sending %s '%s'\n", dns, buf); + dnslookup(mx, fd, buf, domain, net, x->pref); + } + 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; + 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"); } - ds->host = p+1; - /* take only first one returned (quasi-bug) */ - if((p = strchr(ds->host, ' ')) != nil) - *p = 0; + return 0; +} + +static int +lookcall(Mxtab *mx, DS *d, char *domain, char *type) +{ + 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; + } + + for(i = 0; i < mx->nmx; i++){ + x = mx->mx + i; + if(x->ip[0] == 0 || x->valid == 0){ + x->valid = 0; + continue; + } + 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; + } + + return -1; +} + +int +mxdial0(char *addr, char *ddomain, char *gdomain, Mxtab *mx) +{ + int nd, i, j; + DS *d; + static char *tab[] = {"mx", "ip", }; + + 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; + } + + /* 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; + } + + return -1; +} + +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 index 000000000..38e77fb9a --- /dev/null +++ b/sys/src/cmd/upas/smtp/parsetest.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#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(""); +} diff --git a/sys/src/cmd/upas/smtp/rfc822.y b/sys/src/cmd/upas/smtp/rfc822.y index b50f63eb1..065c15347 100644 --- a/sys/src/cmd/upas/smtp/rfc822.y +++ b/sys/src/cmd/upas/smtp/rfc822.y @@ -3,8 +3,6 @@ #include "smtp.h" #include -#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 index b74a1c90b..000000000 --- a/sys/src/cmd/upas/smtp/rmtdns.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "common.h" -#include - -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); -} -*/ diff --git a/sys/src/cmd/upas/smtp/smtp.c b/sys/src/cmd/upas/smtp/smtp.c index 96f2d65ab..99e5e631d 100644 --- a/sys/src/cmd/upas/smtp/smtp.c +++ b/sys/src/cmd/upas/smtp/smtp.c @@ -5,7 +5,7 @@ #include #include -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 1){ addhostdom(buf, field[0]); - for(j=1; jnext){ @@ -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; } diff --git a/sys/src/cmd/upas/smtp/smtp.h b/sys/src/cmd/upas/smtp/smtp.h index bfac33ed9..d420d1dc3 100644 --- a/sys/src/cmd/upas/smtp/smtp.h +++ b/sys/src/cmd/upas/smtp/smtp.h @@ -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) diff --git a/sys/src/cmd/upas/smtp/smtpd.c b/sys/src/cmd/upas/smtp/smtpd.c index 85fbb098a..6c75e9399 100644 --- a/sys/src/cmd/upas/smtp/smtpd.c +++ b/sys/src/cmd/upas/smtp/smtpd.c @@ -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; + int ign; + static int chattycathy; - USED(a); - - /* log alarms but continue */ - if(strstr(msg, "alarm") != nil){ - if(senders.first && senders.first->p && - rcvers.first && rcvers.first->p) + ign = strstr(msg, "closed pipe") != nil; + if(ign) + return 0; + if(chattycathy++ < 5){ + if(senders.first && rcvers.first) syslog(0, "smtpd", "note: %s->%s: %s", s_to_c(senders.first->p), s_to_c(rcvers.first->p), msg); else syslog(0, "smtpd", "note: %s", msg); - rv = Atnoterecog; - } else - rv = Atnoteunknown; - if (debug) { - seek(2, 0, 2); - fprint(2, "caught note: %s\n", msg); } - - /* kill the children if there are any */ - if(pp && pp->pid > 0) { - syskillpg(pp->pid); - /* none can't syskillpg, so try a variant */ - sleep(500); + if(pp){ syskill(pp->pid); + // pp = 0; } - - return rv; + return strstr(msg, "alarm") != nil; } /* override string error functions to do something reasonable */ 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 */ + 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; } - /* 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; + *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; } - nbytes += nonhdr; + return 0; +} + +static void +fancymsg(int status) +{ + static char msg[2*ERRMAX], *p, *e; + + if(!status) + return; + + p = seprint(msg, msg+ERRMAX, "%s: ", piperror); + rerrstr(p, ERRMAX); + piperror = msg; +} + +/* + * pipe message to mailer with the following transformations: + * - change \r\n into \n. + * - add sender's domain to any addrs with no domain + * - add a From: if none of From:, Sender:, or Replyto: exists + * - add a Received: line + * - elide leading dot + */ +int +pipemsg(int *byteswritten) +{ + char *cp; + int n, nbytes, sawdot, status; + String *hdr, *line; + + pipesig(&status); /* set status to 1 on write to closed pipe */ + sawdot = 0; + status = 0; + werrstr(""); + piperror = nil; + + /* + * add a 'From ' line as envelope and Received: stamp + */ + nbytes = 0; + nbytes += Bprint(pp->std[0]->fp, "From %s %s remote from \n", + s_to_c(senders.first->p), thedate()); + nbytes += Bprint(pp->std[0]->fp, "Received: from %s ", him); + if(nci->rsys) + nbytes += Bprint(pp->std[0]->fp, "([%s]) ", nci->rsys); + nbytes += Bprint(pp->std[0]->fp, "by %s; %s\n", me, thedate()); + + /* + * read first 16k obeying '.' escape. we're assuming + * the header will all be there. + */ + line = s_new(); + hdr = s_new(); + while(s_len(hdr) < 16*1024){ + n = getcrnl(s_reset(line), &bin); + + /* eof or error ends the message */ + if(n <= 0){ + piperror = "header read error"; + status = 1; + break; + } + + cp = s_to_c(line); + if(chkhdr(cp, s_len(line)) == -1){ + status = 1; + piperror = "mail refused: illegal header chars"; + break; + } + + /* a line with only a '.' ends the message */ + if(cp[0] == '.' && cp[1] == '\n'){ + sawdot = 1; + break; + } + if(cp[0] == '.'){ + cp++; + n--; + } + s_append(hdr, cp); + nbytes += n; + if(*cp == '\n') + break; + } + if(status == 0) + parseheader(hdr, &nbytes, &status); s_free(hdr); /* * 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; } - cmd = startcmd(); - if(cmd == 0) - return; - - reply("354 Input message; end with .\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 */ + cmd = startcmd(); + if(cmd == 0) + return; + reply("354 Input message; end with .\r\n"); 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< 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"); diff --git a/sys/src/cmd/upas/smtp/spam.c b/sys/src/cmd/upas/smtp/spam.c index 4b7fe4134..20ef9f944 100644 --- a/sys/src/cmd/upas/smtp/spam.c +++ b/sys/src/cmd/upas/smtp/spam.c @@ -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 index 000000000..01b2b2c43 --- /dev/null +++ b/sys/src/cmd/upas/spf/dns.c @@ -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 index 000000000..2ccc8e3b8 --- /dev/null +++ b/sys/src/cmd/upas/spf/macro.c @@ -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 index 000000000..f612714f9 --- /dev/null +++ b/sys/src/cmd/upas/spf/mkfile @@ -0,0 +1,14 @@ +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, ""); + 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 index 000000000..b37bad864 --- /dev/null +++ b/sys/src/cmd/upas/spf/spf.h @@ -0,0 +1,12 @@ +/* © 2008 erik quanstrom; plan 9 license */ +#include +#include +#include +#include +#include + +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 index 000000000..1b55f6c2a --- /dev/null +++ b/sys/src/cmd/upas/spf/testsuite @@ -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 diff --git a/sys/src/cmd/upas/unesc/mkfile b/sys/src/cmd/upas/unesc/mkfile index 904d8bba9..e02edc484 100644 --- a/sys/src/cmd/upas/unesc/mkfile +++ b/sys/src/cmd/upas/unesc/mkfile @@ -1,10 +1,9 @@