mail2fs/ 775 0 0 0 11335216257 102265ustar00nemosysmail2fs/Arch 775 0 0 177 11262667141 11017ustar00nemosys#!/bin/rc # # Archive the message shown in the Acme window # where it runs, or messages named in std. input. exec Mvmesg a $* mail2fs/Delmesg 775 0 0 211 11330534345 11502ustar00nemosys#!/bin/rc # # Delete the message shown in the Acme window # where it runs, or messages named in std. input. exec /acme/msgs/Mvmesg d $* mail2fs/M 775 0 0 3531 11330534513 10343ustar00nemosys#!/bin/rc rfork e if (~ $#* 0){ echo usage: M cmd [msgs] >[1=2] echo 'cmd=arch|spam|inbox|rm|print|list|mime|reply' exit usage } cmd=$1 shift if (~ $#* 0){ msgs=`{cat /tmp/msgs.$user | grep '.*/[0-9]*/(.\.)?[0-9]*(/.*)?$' | sed -e 's|((.*/)?[0-9]*/(.\.)?[0-9]*)(/.*)?|\1|'} } if not msgs=`{for (x) echo $x | sed -e 's|((.*/)?[0-9][0-9]*/(.\.)?[0-9][0-9]*)(/.*)?|\1|g'} if (~ $#INBOX 0) INBOX=/mail/box/$user/msgs for (m in $msgs){ if (! test -e $m) if (test -e $INBOX/$m) m=$INBOX/$m if(test -e $m) switch($cmd){ case a arch aname=`{echo $m | sed 's,/(.\.)?([0-9]*)$,/a.\2,'} ~ $m $aname || mv $m $aname case s spam aname=`{echo $m | sed 's,/(.\.)?([0-9]*)$,/s.\2,'} ~ $m $aname || mv $m $aname case i inbox aname=`{echo $m | sed 's,/(.\.)?([0-9]*)$,/\2,'} ~ $m $aname || mv $m $aname case d rm echo rm -r $m case m mime if (test -e $m/*.*) echo $m/*.* if (test -d $m/[0-9]*) echo $m/[0-9]* case l list echo $m case p print echo $m cat $m/text case r reply rfork n upas/fs -p -f $m/raw rt=`{cat /mail/fs/mbox/1/replyto} f=`{cat /mail/fs/mbox/1/from} cc=`{cat /mail/fs/mbox/1/cc /mail/fs/mbox/1/to} sub=`{cat /mail/fs/mbox/1/subject} { if (~ $#rt 0) echo To: $f if not echo To: $rt if (! ~ $#cc 0) echo Cc: $cc echo Subject: $sub echo Replying: $m/raw echo q '> ' $m/text } > /mail/box/$user/out/Out.$pid unmount /mail/fs if(! ~$#file 0 && ~ $#panel 0){ panel=`{ls -d /mnt/ui/appl/*ox*/*tree*|sed 1q} if(~ $#panel 0) echo look /mail/box/$user/out/Out.$pid if not echo look /mail/box/$user/out/Out.$pid > $panel/ctl } if not { plumb /mail/box/$user/out/Out.$pid echo edit and remove '"Out."' from the file name } case * echo usage: M 'arch|rm|cp' [msgs] >[1=2] exit usage } } exit '' mail2fs/Mcat 775 0 0 57 11262667141 11003ustar00nemosys#!/bin/rc cat `{grep $"1 | awk '{print $1}' } mail2fs/Mg 775 0 0 631 11330534542 10472ustar00nemosys#!/bin/rc rfork e opth=n if (! ~ $#1 0 && ~ $1 -h){ opth=y shift } if (~ $#* 0){ echo usage: Mg [-h] args >[1=2] exit usage } msgs=`{cat /tmp/msgs.$user | grep '.*/[0-9]*/(.\.)?[0-9]*(/.*)?$' | sed -e 's|((.*/)?[0-9]*/(.\.)?[0-9]*)(/.*)?|\1/text|'} for (m in $msgs){ if (~ $opth y){ if (sed '/^$/q' $m | grep -s $*) echo $m } if not { if (grep -s $* $m /dev/null) echo $m } } exit '' mail2fs/Month 751 0 0 562 11330534551 11211ustar00nemosys#!/bin/rc rfork e cd /mail/box/$user/msgs oldones=`{ls -d [21][09]???[0-9] | sort -r | tail +2} for(m in $oldones){ if(test -e $m/[ds].*) echo 'for (f in '^$m^'/[ds].*) { rm -r $f }' test -e $m.l* || { echo msgs -a /mail/box/$user/msgs $m msgs -a /mail/box/$user/msgs $m > $m.l } } for(tag in 9fans gsyc inferno sp9sss){ echo $tag grep -hi $tag *.l* >$tag } mail2fs/Msgs 775 0 0 777 11330534561 11054ustar00nemosys#!/bin/rc rfork e if (~ $#file 0){ echo must run under omero. Acme has its own reader. >[1=2] exit omero } if(~ $#* 0) mbox=/mail/box/$user/msgs if not mbox=$1 fn isforeign { fk=`{file <{sed -n '/^$/,$p' $1/text}} if(~ $"fk *apanese* *yrillic* *rabic* *hinese* *ussian*) status='' if not status='not foreign' } fn spam { aname=`{echo $1 | sed 's,/(.\.)?([0-9]*)$,/s.\2,'} ~ $1 $aname || mv $1 $aname } if(test -e $mbox/*/[0-9]*) for(m in $mbox/*/[0-9]*){ if(isforeign $m) spam $m } msgs $* mail2fs/Mvmesg 775 0 0 3065 11303526012 11402ustar00nemosys#!/bin/rc # # Move the message shown in the Acme or o/live window # where it runs, or messages named in std. input. rfork e if(~ $#file 0 && ~ $#winid 0 ){ echo must run under o/live or acme >[1=2] exit fail } if(~ $#* 0){ echo usage: Mvmesg char ... >[1=2] exit usage } chr=$1 ; shift if(! ~$#* 0){ for(f in $*) echo $f | $0 $chr >/dev/null exit '' } if(~ $#file 0) file = `{cat /mnt/acme/$winid/tag | sed 's/ .*//'} if not file=`{echo $file | sed 's/\[(.*)[\] ] ?.*/\1/'} switch($file){ case */text d=`{echo $file | sed 's,/text,,'} if (! test -d $d || ! ~ $d /mail/box/$user/* ) { echo $d is not a mail directory exit not } dd=`{basename -d $d} dn=`{basename $d} # touch "text" to let other programs know this file is new # e.g., let content search tools re-index the file for this new path mv $dd/$dn $dd/$chr.$dn && touch $dd/$chr.$dn/text >[2]/dev/null if(! ~ $#winid 0){ echo clean >/mnt/acme/$winid/ctl echo del >/mnt/acme/$winid/ctl } case /mail/box/$user/* d=`{pwd} tee /tmp/mvmesg.$pid | \ sed 's,(.* )?(20[0-9][0-9][0-9][0-9])/([^0-9]?\.?)([0-9]*)/text.*,mv \2/\3\4 \2/'^$chr^'.\4,' | \ rc >[2]/dev/null cat /tmp/mvmesg.$pid | sed 'sx([0-9])/[a-z]?\.?([0-9])x\1/'^$chr^'.\2x' mv /tmp/mvmesg.$pid /tmp/mvmesg.$user # in case we want to undo... case * echo Mail command in a strange place? echo file was $file } # plumb a mail event so mail list is updated # for those not running mailplumb. if(! ~ $#winid 0) plumb -s plumb -d seemail -t text \ -a 'filetype=vwhois mailtype=delete sender=any' /mail/box/$user/msgs exit '' indow # where it runs, or messages named in std. input. exec Mvmesg a $* mail2fs/Post 775 0 0 4326 11262667141 11107ustar00nemosys#!/bin/rc # # As Send, but on-line and from acme or o/live rfork ne if(~ $#file 0 && ~ $#winid 0 ){ echo must run under o/live or acme >[1=2] exit fail } d=`{pwd} if(~ $#file 0){ w = /mnt/acme/$winid file = `{cat $w/tag | sed 's/ .*//'} } fn deliver { sed '1,/^$/d' $1 > body.$1 sed '/^$/q' $1 > hdr.$1 addrs=`{grep -i '^To: ' hdr.$1 | sed 's/^[Tt][Oo]: //'} ccs=`{grep -i '^Cc: ' hdr.$1 | sed 's/^[Cc][Cc]: //'} atts=`{grep -i '^Attach: ' hdr.$1 | sed 's/^[Aa]ttach: //' } incl=`{grep -i '^Include: ' hdr.$1 | sed 's/^[Ii]nclude: //' } repl=`{grep -i '^Replying: ' hdr.$1 | sed 's/^[Rr]eplying: //' } xaddrs=$addrs xccs=$ccs if (! ~ $#ccs 0) ccs=-C^$ccs if (! ~ $#atts 0) atts=-a^$atts if (! ~ $#incl 0) incl=-A^$incl if (! ~ $#repl 0){ if(! test -f $repl && test -f /mail/box/$user/msgs/$repl) repl=/mail/box/$user/msgs/$repl if(! test -f $repl){ arepl=`{echo $repl | sed 's,(.*)/([0-9]+)/raw,\1/a.\2/raw,'} if(test -f $arepl) repl=$arepl if(test -f /mail/box/$user/msgs/$arepl) repl=/mail/box/$user/msgs/$arepl } if(! test -f $repl){ echo orginal message not found >[1=2] exit orig } upas/fs -p -f $repl repl=-R/mail/fs/mbox/1 } done=ok { for(t in $xaddrs) echo 'To: '^$"t for(t in $xccs) echo 'Cc: '^$"t } > addrs.$1 { cat addrs.$1 ; grep -vi '^(To|Cc|Attach|Include|Replying): ' hdr.$1 ; cat body.$1 } | { if(upas/marshal $repl $atts $incl $ccs $addrs >[2] errs.$1){ rm errs.$1 hdr.$1 body.$1 addrs.$1 $1 } if not { done=failed echo spool: upas/marshal $repl $atts $incl $ccs $addrs cat errs.$1 { cat errs.$1 ; q ': ' hdr.$1 body.$1 } | mail -s'returned mail' $user mv $1 Fail.$1 } } if (! ~ $#repl 0) unmount /mail/fs if(~ $done ok) status='' if not status='deliver failed' } cd /mail/box/$user/out || { echo no outgoing box >[1=2] ; exit nombox } if(! ~ $file /mail/box/$user/out/Out.*){ echo not an outgoing mail file to be posted exit nopostfile } if(! ~ $#winid 0) echo put >$w/ctl if(! test -e $file){ echo $file does not exist >[1=2] exit noexist } if(deliver `{basename $file}){ ~ $#winid 0 || echo del >$w/ctl } if not{ if(test -e /mail/box/$user/log) echo `{date} Post delivered $f >>/mail/box/$user/log } exit '' if (~ $#rt 0) echo To: $f if not echo To: $rt if (! ~ $#cc 0) echo Cc: $cc echo Subject: $sub echo Replying: $m/raw echo q '> ' $m/text } > /mail/box/$user/out/Out.$pid unmount /mail/fs if(! ~$#file 0 && ~ $#panel 0){ panel=`{ls -d /mnt/ui/appl/*ox*/*mail2fs/Reply 775 0 0 2542 11272360044 11244ustar00nemosys#!/bin/rc # # Reply to message shown in the Acme window # where it runs, or to the 1st mail named in # std. input or compose a new mail otherwise. rfork e if(~ $#file 0 && ~ $#winid 0 ){ echo must run under o/live or acme >[1=2] exit fail } d=`{pwd} if(~ $#file 0){ w = /mnt/acme/$winid file = `{cat $w/tag | sed 's/ .*//'} } fname=/mail/box/$user/out/Out.$pid if(! ~ $"file *text){ file=`{sed 1q | sed 's,(.* )?(20[0-9][0-9][0-9][0-9])/(.?\.?[0-9]*)/text.*,\2/\3/text,'} if(! ~ $"file *text){ file=new { echo 'To: ' echo 'Subject: ' echo } > $fname } if not file=`{pwd}^/^$"file } switch($file){ case /mail/*/text m=`{echo $file | sed 's,/text$,,'} upas/fs -p -f $m/raw rt=`{cat /mail/fs/mbox/1/replyto} f=`{cat /mail/fs/mbox/1/from} cc=`{cat /mail/fs/mbox/1/cc} if(! ~ $#cc 0) cc=`{echo $cc ; echo ; cat /mail/fs/mbox/1/to} if not cc=`{cat /mail/fs/mbox/1/to} attachs=`{for (f in `{ls $m | grep -v '/(text|L.mbox|raw)$'}) { test -d $f || echo $f }} { if (~ $#rt 0) echo To: $f if not echo To: $rt if (! ~ $#cc 0) echo Cc: $cc grep '^Subject: ' $m/text | sed 1q echo Replying: $m/raw for(a in $attachs) echo Attach: $a echo q '> ' $m/text } > $fname unmount /mail/fs } if(test -e /mnt/ui/appl/*ox*/ctl){ ox=`{ls -d /mnt/ui/appl/*ox*/ctl | sed 1q} echo look $fname >$ox } if not plumb $fname exit '' mail2fs/Save 775 0 0 1423 11262667141 11053ustar00nemosys#!/bin/rc rfork e if(~ $#file 0 && ~ $#winid 0 ){ echo must run under o/live or acme >[1=2] exit fail } d=`{pwd} if(~ $#file 0){ w = /mnt/acme/$winid file = `{cat $w/tag | sed 's/ .*//'} } if not file=`{echo $file | sed 's/\[(.*) .*/\1/'} switch($file){ case */text d =`{basename -d $d} d =`{basename -d $d} case /mail/box/$user/* ; case * echo Save in a strange place? exit no } for(m in $*){ if (! test -e $d/$m){ echo $d/$m does not exist >[1=2] exit fail } } dfile=/tmp/arch.$pid switch($file){ case */text Arch echo $file | sed 's,([0-9]+)/text,a.\1/text,' | mlist > $dfile case /mail/box/$user/* Arch | mlist > $dfile } for (m in $*) { cat $dfile $d/$m > $d/$m^_ && mv $d/$m^_ $d/$m } rm -f $dfile if(! ~ $#winid 0) echo clean >$w/ctl >[2]/dev/null $m > $m.l } } for(tag in 9fans gsyc inferno sp9sss){ echo $tag grep -hi $tag *.l* >$tag } mail2fs/Send 664 0 0 671 11330534574 11026ustar00nemosys#!/bin/rc rfork ne if (! ~ $#winid 0 && test -e /mnt/acme/$winid && test -x /acme/msgs/Send) exec /acme/msgs/Send $* if (~ $#file 0){ echo must run under omero exit omero } if (! test -r $file ){ echo cannot read $file exit file } fname=`{awk '{print $1}' < /dev/time} if (! test -d /mail/box/$user/out ){ echo no /mail/box/$user/out exit out } cd /mail/box/$user/out mv $file $fname && echo $file spooled for delivery exit '' mail2fs/Sort 775 0 0 100 11262667141 11053ustar00nemosys#!/bin/rc grep '^[0-9]' | sort -t/ +0nr | sort -t/ +1r | mlist mail2fs/Spam 775 0 0 177 11262667141 11042ustar00nemosys#!/bin/rc # # Archive the message shown in the Acme window # where it runs, or messages named in std. input. exec Mvmesg s $* mail2fs/guide 640 0 0 140 11330534602 11203ustar00nemosys!Msgs !Arch !Spam X/text/D !Reply !Send , Arch , >Spam , x/9fans/+- p . >Reply . >Spam mail2fs/imap/ 775 0 0 0 11330534765 111565ustar00nemosysmail2fs/imap/README 664 0 0 1165 11263575615 12045ustar00nemosysThese are changes to imap4d to add Plan B mailbox format support and to add support for the "imap.ok" file (listing which files/dirs are ok for imap access). If you don't plan to use imap with Plan B mail boxes this is not needed at all. If you want imap you must apply both this change and also those in ../upasfs (because imap4d relies on upas/fs). Files should go at /sys/src/cmd/ip/imap4d (you can just bind before and compile to test it before using). Both actual mail boxes and files with mail listings are supported as mail boxes. This makes it easy to create virtual folders. Contact nemo@lsub.org if you need help. returned mail' $user mv $1 Fail.$1 } } if (! ~ $#repl 0) unmount /mail/fs if(~ $done ok) status='' if not status='deliver failed' } cd /mail/box/$user/out || { echo no outgoing box >[1=2] ; exit nombox } if(! ~ $file /mail/box/$user/out/Out.*){ echo not an outgoing mail file to be posted exit nopostfile } if(! ~ $#winid 0) echo put >$w/ctl if(! test -e $file){ echo $fimail2fs/imap/folder.c 664 0 0 13766 11263575615 12636ustar00nemosys#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; } mail2fs/imap/list.c 664 0 0 17646 11330534765 12333ustar00nemosys#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) == 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; } o, 1); } /* * copy the contents of one mailbox to another * either truncates or removesmail2fs/imap/mbox.c 664 0 0 42553 11263575615 12324ustar00nemosys#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 }; static char *folders[100]; static char *folderbuff; static void readokfolders(void) { int fd; int nr; folderbuff = malloc(512); if(folderbuff == nil) return; fd = open("imap.ok", OREAD); if(fd < 0){ Fail: free(folderbuff); folderbuff = nil; return; } nr = read(fd, folderbuff, 511); /* once is ok */ close(fd); if(nr < 0) goto Fail; 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; } d, OREAD); if(!parseImp(&b, box)){ box->dirtyImp = 1; if(box->uidvalidity == 0) box->uidvalidity = box->mtime; } Bterm(&b); box->immail2fs/mail2fs.c 664 0 0 60146 11335216217 11752ustar00nemosys/* * Use upas/fs to convert a plan 9 mail box into * a file tree with a plan b mail box (similar to Mh). * * Implementation is not as clean as it should be, * A large part of the code is taken as-is from nedmail, trying to * keep it the same, so that bugs are easy to fix both * here and in nedmail (should they show up). * We should get rid of Strings, for example. * */ #include "common.h" #include #include #include "util.h" enum { Defperm = 0666, /* none needs access for delivery, else could be 0620 */ }; typedef struct Message Message; typedef struct Ctype Ctype; typedef struct Cmd Cmd; struct Message { Message *next; Message *prev; Message *cmd; Message *child; Message *parent; String *path; int id; int len; int fileno; /* number of directory */ String *info; char *from; char *to; char *cc; char *replyto; char *date; char *subject; char *type; char *disposition; char *filename; char *sdigest; char deleted; char stored; int saveit; }; struct Ctype { char *type; char *ext; int display; char *plumbdest; Ctype *next; }; 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 }, { "message/rfc822", "msg", 0, 0 }, { "image/bmp", "bmp", 0, "image" }, { "image/jpeg", "jpg", 0, "image" }, { "image/gif", "gif", 0, "image" }, { "image/png", "png", 0, "image" }, { "application/pdf", "pdf", 0, "postscript" }, { "application/postscript", "ps", 0, "postscript" }, { "application/", 0, 0, 0 }, { "image/", 0, 0, 0 }, { "multipart/", "mul", 0, 0 }, }; Rune altspc = L'·'; Rune altlparen = L'«'; Rune altrparen = L'»'; Rune altquote = L'´'; Rune altamp = L'­'; Rune altslash = L'.'; enum { NARG= 32, }; struct Cmd { Message *msgs; Message *(*f)(Cmd*, Message*); int an; char *av[NARG]; int delete; }; Message top; /* top-level of upas mailbox */ int mboxfd; /* lock/seq fd for plan b mail box */ int debug; int dry; int cflag; /* create */ int aflag; /* archive */ int oflag; /* old mail */ int rflag; /* save complete raw message */ int mboxisfile; char* digests; /* in-memory copy of digests */ char* mboxnm; /* name for mbox in upas */ void tabs(int n) { static char t[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; if(n >= sizeof t) n = sizeof t - 1; /* don't run off the end, truncate */ write(2, t, n); } void printmessage(Message* m, int t) { Message* l; tabs(t); fprint(2, "message %d file %d %d bytes\n", m->id, m->fileno, m->len); if(m->info != nil){ tabs(t); fprint(2, "info %s\n", s_to_c(m->info)); } if(m->from != nil){ tabs(t); fprint(2, "from %s\n", m->from); } if(m->subject != nil){ tabs(t); fprint(2, "subject %s\n", m->subject); } if(m->filename != nil){ tabs(t); fprint(2, "file %s\n", m->filename); } for(l = m->child; l != nil ; l = l->next) printmessage(l, t+1); fprint(2, "\n"); } 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; } String* file2string(String *dir, char *file) { String *s; int fd, n, m; s = extendpath(dir, file); fd = open(s_to_c(s), OREAD); s_grow(s, 512); /* avoid multiple reads on info files */ s_reset(s); if(fd < 0) return s; for(;;){ n = s->end - s->ptr; if(n == 0){ s_grow(s, 128); continue; } m = read(fd, s->ptr, n); if(m <= 0) break; s->ptr += m; if(m < n) break; } s_terminate(s); close(fd); return s; } int lineize(char *s, char **f, int n) { int i; for(i = 0; *s && i < n; i++){ f[i] = s; s = strchr(s, '\n'); if(s == nil) break; *s++ = 0; } return i; } 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; } int dir2message(Message *parent, int reverse); Message* file2message(Message *parent, char *name) { Message *m; String *path; char *f[10]; m = mallocz(sizeof(Message), 1); if(m == nil) return nil; m->path = path = extendpath(parent->path, name); m->fileno = atoi(name); m->info = file2string(path, "info"); lineize(s_to_c(m->info), f, nelem(f)); m->from = f[0]; m->to = f[1]; m->cc = f[2]; m->replyto = f[3]; m->date = f[4]; m->subject = f[5]; 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; return m; } int dir2message(Message *parent, int reverse) { int i, n, fd, highest, newmsgs; Dir *d; Message *first, *last, *m; fd = open(s_to_c(parent->path), OREAD); if(fd < 0) return -1; /* count current entries */ first = parent->child; highest = newmsgs = 0; for(last = parent->child; last != nil && last->next != nil; last = last->next) if(last->fileno > highest) highest = last->fileno; if(last != nil) if(last->fileno > highest) highest = last->fileno; n = dirreadall(fd, &d); for(i = 0; i < n; i++){ if((d[i].qid.type & QTDIR) == 0) continue; if(atoi(d[i].name) <= highest) continue; m = file2message(parent, d[i].name); if(m == nil) break; newmsgs++; 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; } } free(d); close(fd); parent->child = first; /* renumber */ for(m = first; m != nil; m = m->next) m->id = m->fileno; return newmsgs; } void compress(char *p) { char *np; int last; last = ' '; for(np = p; *p; p++) if(*p != ' ' && (*p != ' ' || last != ' ')){ last = *p; *np++ = last; } *np = 0; } int printheader(Biobuf* bout, Message* m) { if(m->from == nil || *m->from == 0) return 0; Bprint(bout, "From: %s\n", m->from); if(m->to != nil && *m->to) Bprint(bout, "To: %s\n", m->to); if(m->cc != nil && *m->cc) Bprint(bout, "Cc: %s\n", m->cc); if(m->replyto != nil && *m->replyto && m->from != nil && strcmp(m->replyto, m->from)) Bprint(bout, "Reply-To: %s\n", m->replyto); if(m->date != nil && *m->date) Bprint(bout, "Date: %s\n", m->date); if(m->subject != nil && *m->subject) Bprint(bout, "Subject: %s\n", m->subject); return 1; } int printpart(Biobuf* bout, String *s, 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); if(fd < 0){ fprint(2, "!message disappeared\n"); return 0; } tot = 0; while((n = read(fd, buf, sizeof(buf)-1)) > 0){ buf[n] = 0; compress(buf); n = strlen(buf); if(Bwrite(bout, buf, n) <= 0) break; tot += n; } close(fd); return tot; } /* * Mail box format: * dir/ (mail box) * seq (file with last msg nb, +l) * digest (file with mail digests) * yyyymm/ (per month folder) * raw (raw message; just headers) * text (text as in reader) * 1.x (first attach) * 2.x (second attach) * 3/ (third attach, a mail) */ int mkmdir(char* mdir) { int fd, seqfd, nattempts; char buf[200]; char* s; Dir* d; digests = strdup(""); d = dirstat(mdir); s = smprint("%s/seq", mdir); if(d == nil){ if(!cflag) sysfatal("%s: not a mail dir", mdir); if(debug) fprint(2, "create mdir %s\n", mdir); fd = create(mdir, OREAD, DMDIR|Defperm|0111); if(fd < 0) sysfatal("%s: %r", mdir); close(fd); fd = seqfd = create(s, ORDWR, Defperm|DMEXCL); if(fd < 0) sysfatal("%s: %r", s); fprint(fd, "%11.11d", 0); seek(fd, 0, 0); free(s); s = smprint("%s/digest", mdir); fd = create(s, OREAD, Defperm|DMAPPEND|DMEXCL); if(fd < 0) sysfatal("%s: %r", s); close(fd); } else { if((d->qid.type&QTDIR) == 0) sysfatal("%s: not a directory", mdir); free(d); nattempts = 0; while ((seqfd = open(s, ORDWR)) < 0){ rerrstr(buf, sizeof buf); if(strstr(buf, "exclusive") == nil && strstr(buf, "already open") == nil) break; if(nattempts++ >= 10) sysfatal("%s: can't get lock: %r", mdir); sleep(500); } if (seqfd < 0) seqfd = create(s, ORDWR, Defperm|DMEXCL); if (seqfd < 0) sysfatal("can't create %s: %r", s); } free(s); return seqfd; } void closemdir(int fd) { close(fd); } int newseq(int startover) { int i, nr; char buf[128]; if(mboxfd < 0) return -1; seek(mboxfd, 0, 0); nr = read(mboxfd, buf, sizeof(buf)-1); if(nr < 0) return -1; if(nr == 0) i = 0; else { buf[nr] = 0; i = atoi(buf); } i++; if(startover && !oflag) /* start sequencing each month */ i = 1; /* unless we are adding old msgs */ seek(mboxfd, 0, 0); fprint(mboxfd, "%11d ", i); if(debug) fprint(2, "newmsg %d\n", i); return i; } char* mksubmsg(int id, char* mdir) { char* fname; int fd; if(aflag) fname = smprint("%s/a.%d", mdir, id); else fname = smprint("%s/%d", mdir, id); if(debug) fprint(2, "create %s\n", fname); fd = create(fname, OREAD, Defperm|0111|DMDIR); if(fd < 0){ fprint(2, "%s: %r\n", fname); free(fname); fname = nil; } else close(fd); return fname; } static char* datedir(void) { Tm *tm; static char datebuf[30]; if(datebuf[0] == 0){ tm = localtime(time(0)); seprint(datebuf, datebuf+30, "%.4d%.2d", tm->year+1900, tm->mon+1); } return datebuf; } static struct{ char *m; char *n; } mnames[] = { { "jan", "01"}, {"feb", "02"}, {"mar", "03"}, {"apr", "04"}, {"may", "05"}, {"jun", "06"}, { "jul", "07"}, {"aug", "08"}, {"sep", "09"}, {"oct", "10"}, {"nov", "11"}, {"dec", "12"} }; static char* mname(char *m) { int i; for(i = 0; i < nelem(mnames); i++) if(cistrcmp(m, mnames[i].m)) return mnames[i].n; return "00"; } int isnumeric(char *s) { while(*s){ if(!isdigit(*s)) return 0; s++; } return 1; } static char* mdatedir(Message *m) { int n; char *s, *fld[10]; static char datebuf[30]; char *month, *year; n = 0; s= nil; if(m->date != nil){ s = strdup(m->date); n = tokenize(s, fld, 10); } if(n >= 5){ /* some dates have 19 Apr, some Apr 19 */ if(strlen(fld[1])<4 && isnumeric(fld[1])) month = fld[2]; else month = fld[1]; month = mname(month); if(n > 5) year = fld[5]; else year = fld[4]; seprint(datebuf, datebuf+30, "%.4s%.2s", year, month); }else fprint(2, "%d fields [%s]\n", n, m->date); free(s); if(datebuf[0] != 0){ if(debug) fprint(2, "olddir: %s\n", datebuf); return datebuf; /* may be an old one if we failed. that's ok. */ }else return datedir(); } /* * Create directory for the message, including the per-month * directory. * Date is today or that from the mail if oflag (old mail convert) */ char* mkmsg(Message *m, char* mdir) { int fd; char *d, *fname; int newmonth; d = smprint("%s/%s", mdir, oflag ? mdatedir(m) : datedir()); newmonth = 0; if(access(d, AEXIST) < 0){ fd = create(d, OREAD, Defperm|0111|DMDIR); if(fd < 0){ fprint(2, "%s: %s: %r\n", argv0, d); free(d); return nil; } close(fd); newmonth=1; } fname = mksubmsg(newseq(newmonth), d); free(d); return fname; } Ctype* findctype(Message *m) { int n, kid, pid, pfd[2]; char *p; char ftype[128]; 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; /* * use file(1) for any unknown mime types */ if(pipe(pfd) < 0) { fprint(2, "%s: out of pipes: %r\n", argv0); return &bintype; } *ftype = 0; switch(kid = fork()){ case -1: break; case 0: close(pfd[1]); close(0); dup(pfd[0], 0); close(1); dup(pfd[0], 1); execl("/bin/file", "file", "-m", s_to_c(extendpath(m->path, "body")), nil); sysfatal("no /bin/file: %r"); default: close(pfd[0]); n = read(pfd[1], ftype, sizeof(ftype)); if(n > 0) ftype[n] = 0; close(pfd[1]); while ((pid = waitpid()) != -1 && pid != kid) ; break; } if(*ftype=='\0' || (p = strchr(ftype, '/')) == nil) return &bintype; *p++ = 0; a = mallocz(sizeof(Ctype), 1); a->type = strdup(ftype); a->ext = strdup(p); a->display = 0; a->plumbdest = strdup(ftype); for(cp = ctype; cp->next; cp = cp->next) continue; cp->next = a; a->next = nil; return a; } void pipecmd(Cmd *c, Biobuf* bout, Message *m, char* part) { int i, fd, nr, kid, pid; int pfd[2]; char *p, *e; char cmd[128], buf[512]; char *av[4]; String *path; 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); } if(fd < 0){ fprint(2, "!message disappeared\n"); return; } 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[3] = 0; if(pipe(pfd)<0){ fprint(2, "%s: pipe: %r\n", argv0); return; } kid = fork(); switch(kid){ case -1: fprint(2, "%s: fork: %r\n", argv0); break; case 0: dup(fd, 0); close(fd); close(pfd[0]); dup(pfd[1], 1); close(pfd[1]); exec("/bin/rc", av); sysfatal("no /bin/rc: %r"); default: close(fd); close(pfd[1]); while ((nr = read(pfd[0], buf, sizeof buf)) > 0) Bwrite(bout, buf, nr); close(pfd[0]); while ((pid = waitpid()) != -1 && pid != kid) ; break; } } int printhtml(Biobuf* bout, Message *m) { Cmd c; c.an = 3; c.av[1] = "/bin/htmlfmt"; c.av[2] = "-a -l 80 -cutf-8"; pipecmd(&c, bout, m, "body"); return 0; } void fcopy(char* d, char* s, int append) { int dfd, sfd, nr; char buf[1024]; if(debug) fprint(2, "create %s\n", d); if(append){ dfd = open(d, OWRITE); if(dfd >= 0) seek(dfd, 0, 2); }else dfd = create(d, OWRITE, 0660 /* TODO 0640 */); sfd = open(s, OREAD); if(dfd < 0 || sfd < 0){ fprint(2, "%s: copying %s to %s: %r\n", argv0, s, d); goto fail; } while ((nr = read(sfd, buf, sizeof buf)) > 0) if(write(dfd, buf, nr) != nr){ fprint(2, "%s: copying %s to %s: %r\n", argv0, s, d); break; } fail: close(dfd); close(sfd); } char* importname(char* name) { int nr; Rune r; char *up, *p9name, *uname; uname = name; if(name == 0 || (strchr(name, ' ') == 0 && strchr(name, '(') == 0 && strchr(name, ')') == 0 && strchr(name, '&') == 0 && strchr(name, '\'') == 0 && strchr(name, '/') == 0)) return name; p9name = malloc(strlen(name) * 3 + 1); /* worst case: all blanks + 0 */ up = p9name; while(*name != 0){ nr = chartorune(&r, name); if(r == ' ' || r == '\n' || r == '\r' || r == '\t') r = altspc; else if(r == '(') r = altlparen; else if(r == ')') r = altrparen; else if(r == '&') r = altamp; else if(r == '\'') r = altquote; else if(r == '/') r = altslash; else if(!isalpharune(r)){ free(p9name); free(uname); return nil; } up += runetochar(up, &r); name += nr; } *up = 0; free(uname); return p9name; } char* mfname(Message* m, Ctype* cp) { char *s, *r; if(m->filename == nil || *m->filename == 0) s = smprint("%d", m->id); else s = strdup(m->filename); s = importname(s); if(s == nil) s = smprint("%d", m->id); if(cp->ext != nil) if(strstr(s, cp->ext) != s + strlen(s) - strlen(cp->ext)){ r = smprint("%s.%s",s, cp->ext); free(s); return r; } return s; } static void addnl(char *f) { int fd; fd = open(f, OWRITE); if(fd < 0) return; seek(fd, 0, 2); write(fd, "\n\n", 2); close(fd); } static char* xtabs(int n) { static char t[20]; memset(t, '\t', sizeof(t)); t[n] = 0; return t; } /* * Main conversion routine. * Convert message m into a directory. * It may recur for compound msgs, bout * is the message text, nil in the first call. */ int msg2fs(Biobuf* bout, Message* m, char* mdir) { char *dfname, *f, *fname, *msgdir; Ctype *cp; Message *nm; String *s; static int nest = 0; nest++; msgdir = nil; if(m->parent == &top || strcmp(m->type, "message/rfc822") == 0){ if(m->parent == &top) msgdir = mkmsg(m, mdir); else msgdir = mksubmsg(m->id, mdir); if(msgdir == nil){ nest--; return -1; } mdir = msgdir; fname = smprint("%s/text", mdir); if(debug){ if(m->parent == &top) fprint(2, "%stoplevel\n", xtabs(nest)); fprint(2, "%screate %s\n", xtabs(nest), fname); } if(oflag) print("%s\n", fname); close(create(fname, OWRITE, 0666 /* TODO 0640 */)); bout = Bopen(fname, OWRITE); if(bout == nil){ fprint(2, "%s: %s: %r\n", argv0, fname); free(fname); free(msgdir); nest--; return -1; } free(fname); dfname = smprint("%s/raw", mdir); fname = smprint("%s/unixheader", s_to_c(m->path)); fcopy(dfname, fname, 0); free(fname); /* save entire original in raw or save just headers? */ if(rflag){ fname = smprint("%s/raw", s_to_c(m->path)); fcopy(dfname, fname, 1); free(fname); }else{ fname = smprint("%s/rawheader", s_to_c(m->path)); fcopy(dfname, fname, 1); addnl(dfname); free(fname); } free(dfname); } if(bout != nil && printheader(bout, m) > 0) Bprint(bout, "\n"); cp = findctype(m); if(cp == nil) fprint(2, "nil ctype\n"); else if(cp->display){ if(debug) fprint(2, "%sbody\n", xtabs(nest)); if(strcmp(m->type, "text/html") == 0) printhtml(bout, m); else printpart(bout, m->path, "body"); /* experiment: save also inline attachments */ if(m->saveit) goto Save; } else if(strcmp(m->type, "multipart/alternative") == 0){ if(debug){ fprint(2, "%smultipart/alternative\n", xtabs(nest)); for(nm = m->child; nm != nil; nm = nm->next) fprint(2, "%s->%s\n", xtabs(nest+1), nm->type); } /* all parts should be equivalent, according to mime. however, * Apple mail dares to place attachs into a part that is multipart, * which is not equivalent to the first text part we find. So, we must * prefer a multipart subpart to any other plain text or displayed part. */ for(nm = m->child; nm != nil; nm = nm->next) if(strncmp(nm->type, "multipart/", 10) == 0) break; if(nm == nil) for(nm = m->child; nm != nil; nm = nm->next){ cp = findctype(nm); if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0) break; } if(nm == nil) for(nm = m->child; nm != nil; nm = nm->next){ cp = findctype(nm); if(cp->display) break; } if(nm != nil) msg2fs(bout, nm, mdir); } else if(strncmp(m->type, "multipart/", 10) == 0){ if(debug) fprint(2, "%smultipart\n", xtabs(nest)); nm = m->child; if(nm != nil){ /* always print first part */ msg2fs(bout, nm, mdir); for(nm = nm->next; nm != nil; nm = nm->next){ fname = smprint("%s", s_to_c(nm->path)); s = s_copy(fname); free(fname); cp = findctype(nm); if(nm->type != nil && strcmp(nm->type, "message/rfc822") == 0){ Bprint(bout, "\n— %d/\n", nm->id); msg2fs(bout, nm, mdir); } else { f = mfname(nm, cp); Bprint(bout,"\n!— %s\n\n",f); free(f); /* experiment: save also inline attachments */ nm->saveit = 1; msg2fs(bout, nm, mdir); } s_free(s); } } } else if(strcmp(m->type, "message/rfc822") == 0){ if(debug) fprint(2, "%smessage/rfc822\n", xtabs(nest)); msg2fs(bout, m->child, mdir); } else { Save: if(debug) fprint(2, "%sattach\n", xtabs(nest)); f = mfname(m, cp); dfname = smprint("%s/%s", mdir, f); free(f); if(cp->ext) fname = smprint("%s/body.%s", s_to_c(m->path), cp->ext); else fname = smprint("%s/body", s_to_c(m->path)); fcopy(dfname, fname, 0); free(dfname); free(fname); } free(msgdir); if(m->parent == &top || strcmp(m->type, "message/rfc822") == 0) Bterm(bout); nest--; return 0; } /* * Compute signature for mail. * Msgs with same signature are considered dups and not * added to the mail box. * Returns 0 if message is already there. */ static char sdigest[MD5dlen*2+1]; char* lastsigned(void) { return sdigest; } int sign(Message* m) { int fd, i, n; char *edigest, *odigests, *p; uchar buf[256], digest[MD5dlen]; DigestState *s; String *path; s = md5((uchar*)"mail2fs", 7, nil, nil); path = extendpath(m->path, "subject"); fd = open(s_to_c(path), OREAD); s_free(path); if(fd < 0) goto fail; n = read(fd, buf, sizeof(buf)); if(n < 0) goto fail; if(n > 0) md5(buf, n, nil, s); close(fd); path = extendpath(m->path, "body"); fd = open(s_to_c(path), OREAD); s_free(path); if(fd < 0) goto fail; while((n = read(fd, buf, 256)) > 0) md5(buf, n, nil, s); md5((uchar*)"mail2fs", 7, digest, s); close(fd); p = sdigest; edigest = sdigest + sizeof(sdigest); for(i = 0; i < MD5dlen; i++) p = seprint(p, edigest, "%02x", digest[i]); *p = 0; if(strstr(digests, sdigest)){ if(debug) fprint(2, "%s: %s: message already in digests\n", argv0, s_to_c(m->path)); return 0; } odigests = digests; digests = smprint("%s%s\n", odigests, sdigest); if(digests == nil) sysfatal("not enough memory"); if(debug) fprint(2, "%s: %s: digest %s\n", argv0, s_to_c(m->path), sdigest); free(odigests); return 1; fail: close(fd); fprint(2, "!message disappeared\n"); md5((uchar*)"mail2fs", 7, digest, s); return 1; } void upasfs(char* mbox) { int kid, pid; rfork(RFNAMEG); unmount(nil, "/mail/fs"); kid = fork(); switch(kid){ case -1: break; case 0: execl("/bin/upas/fs", "upas/fs", "-p", "-f", mbox, nil); sysfatal("no upas/fs: %r"); default: while ((pid = waitpid()) != -1 && pid != kid) ; break; } } char* openmbox(char* mbox) { char* d; int fd; mboxnm = strrchr(mbox, '/'); if (mboxnm != nil) mboxnm++; else mboxnm = mbox; d = smprint("/mail/fs/%s", mboxnm); upasfs(mbox); /* run upas/fs on mbox file to produce a tree */ fd = open("/mail/fs/ctl", OWRITE); if(fd < 0) sysfatal("/mail/fs/ctl: %r"); if(fprint(fd, "open %s\n", mbox) < 0){ close(fd); sysfatal("%s: open: %r", mbox); } close(fd); return d; } void closembox(void) { int fd, some; char *s, *e; char buf[1024]; Message *l; fd = open("/mail/fs/ctl", OWRITE); if(fd < 0) sysfatal("/mail/fs/ctl: %r"); e = buf + sizeof buf; s = seprint(buf, e, "delete %s", mboxnm); some = 0; for(l = top.child; l != nil; l = l->next) if(l->deleted){ if(e - s < 20){ if(write(fd, buf, s-buf) != s-buf) fprint(2, "%s: delete: %r\n", mboxnm); s = seprint(buf, e, "delete %s", mboxnm); some = 0; } some++; s = seprint(s, e, " %d", l->id); } if(some && write(fd, buf, s-buf) != s-buf) fprint(2, "%s: delete: %r\n", mboxnm); close(fd); } int mbox2fs(char* mbox, char* mdir) { int n, sfd; char* fname; Message *l; String *ddir, *s; if(chdir(mbox) < 0){ fprint(2, "%s: %s: not a directory: %r\n", argv0, mbox); return -1; } top.path = s_copy(mbox); ddir = s_copy(mdir); s = file2string(ddir, "digest"); digests = strdup(s_to_c(s)); s_free(s); s_free(ddir); n = dir2message(&top, 0); if(n < 0) { fprint(2, "%s: dirmessage failed\n", argv0); return -1; } fname = smprint("%s/digest", mdir); sfd = open(fname, OWRITE); free(fname); if(sfd >= 0) seek(sfd, 0, 2); for(l = top.child; l != nil; l = l->next){ if(debug) fprint(2, "converting %s/%d\n", mbox, l->id); if(sign(l)) if(msg2fs(nil, l, mdir) < 0){ fprint(2, "mbox2fs: msg2fs errors\n"); return -1; } else { if(sfd >= 0) fprint(sfd, "%s\n", lastsigned()); } if(!dry) l->deleted = 1; } if(sfd >= 0) close(sfd); return 0; } static void usage(void) { fprint(2, "usage: %s [-acDnor] [-d mdir] [mbox]\n", argv0); exits("usage"); } void main(int argc, char*argv[]) { char *mbox = "mbox"; char *mdir, *top; Ctype *cp; top = smprint("/mail/box/%s", getuser()); mdir = smprint("%s/msgs", top); ARGBEGIN{ case 'a': aflag++; break; case 'c': cflag++; break; case 'd': free(mdir); mdir = EARGF(usage()); mdir = cleanpath(mdir, top); break; case 'D': debug++; break; case 'n': dry++; break; case 'o': oflag++; break; case 'r': rflag++; break; default: usage(); }ARGEND; switch(argc){ case 1: mbox = cleanpath(argv[0], top); break; case 0: mbox = cleanpath(mbox, top); break; default: usage(); } for(cp = ctype; cp < ctype + nelem(ctype) - 1; cp++) cp->next = cp + 1; mbox = openmbox(mbox); mboxfd = mkmdir(mdir); mbox2fs(mbox, mdir); closembox(); closemdir(mboxfd); exits(nil); } mportname(char* name) { int nr; Rune r; char *up, *p9name, *uname; uname = name; if(name == 0 || (strchr(name, ' ') == 0 && strchr(name, '(') == 0 && strchr(name, ')') == 0 && strchr(name, '&') == 0 && strchr(name, '\'') == 0 && strchr(name, '/') == 0)) return name; p9name = malloc(strlen(name) * 3 + 1); /* worst case: all blanks + 0 */ up = p9name; while(*name != 0){ nr = chartorune(&r, mail2fs/mailplumb.c 664 0 0 14004 11330534625 12370ustar00nemosys#include "common.h" #include #include #include "util.h" /* * Generate plumber messages as a plan b mail box * changes. We run upas/fs and mail2fs in a cpu server * and not at the user terminal. Users get their msgs * already processed for reading. */ enum { // for plumbmsg Old, New }; typedef struct Msg Msg; struct Msg{ char* path; char* from; char* date; Msg* next; int visited; }; int hopt; // send plumb msgs for old ones int debug; char* mboxdir; // only used by plumbmsg() int octopus; // plumb using /mnt/ports/post void printmsgs(Msg* m) { if(m == nil) fprint(2, "no msgs\n"); for(; m != nil; m = m->next) fprint(2, "%s\tfrom %s\n", m->path, m->from); } static int cmpent(void* a1, void* a2) { Dir* d1 = a1; Dir* d2 = a2; int n1, n2; n1 = atoi(mailnamenb(d1->name)); n2 = atoi(mailnamenb(d2->name)); return n2 - n1; } Msg* newmsg(char* dir) { static char buf[512]; Msg* m; char* fname; int fd; char* s; int nr; fname = smprint("%s/raw", dir); m = mallocz(sizeof(Msg), 1); if(m == nil || fname == nil) sysfatal("not enough memory"); fd = open(fname, OREAD); if(fd < 0){ // maybe just a new one, give it some time. sleep(1000); fd = open(fname, OREAD); } free(fname); if(fd < 0) goto fail; nr = read(fd, buf, sizeof(buf)-1); close(fd); if(nr <= 5 || strncmp(buf, "From ", 5)) goto fail; buf[nr] = 0; s = strchr(buf+5, ' '); if(s == nil) goto fail; *s = 0; m->from = strdup(buf+5); m->path = strdup(dir); if(m->from == nil || m->path == nil) goto fail; return m; fail: /* Message may have been archived in the * mean while. In any case, this is not to * be considered an error. */ if(debug) fprint(2, "%s: %r\n", dir); free(m->path); free(m->from); free(m); return nil; } void freemsg(Msg* m) { if(m != nil){ free(m->path); free(m->from); free(m->date); } free(m); } int isnew(Msg* m, Msg* msgs) { for(; msgs != nil; msgs = msgs->next) if(strcmp(msgs->path, m->path) == 0){ msgs->visited = 1; return 0; } return 1; } void oplumbmsg(Msg* m, int what) { char* str[2] = {"gone", "new"}; char msg[80]; char* e; int fd; e = msg+sizeof(msg); e = seprint(msg, e, "/msgs: %s %s %s\n", m->path, m->from, str[what]); fd = open("/mnt/ports/post", OWRITE); if(fd < 0) sysfatal("ports open: %r"); if(write(fd, msg, e-msg) != e - msg) sysfatal("pors write: %r"); close(fd); } void plumbmsg(Msg* m, int what) { static int fd = -1; char* str[2] = { "delete", "new" }; Plumbmsg p; Plumbattr a[10]; int ai; assert(what == 0 || what == 1); if(debug) fprint(2, "%s msg %s from %s\n", str[what], m->path, m->from); if(octopus){ oplumbmsg(m, what); return; } memset(&p, 0, sizeof(p)); p.src = "mailplumb"; p.dst = "seemail"; p.wdir = mboxdir; p.type = "text"; ai = 0; a[ai].name = "filetype"; a[ai].value = "mail"; a[++ai].name = "sender"; a[ai].value = m->from; a[ai-1].next = &a[ai]; a[++ai].name = "length"; a[ai].value = "42"; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; a[ai].value = str[what]; a[ai-1].next = &a[ai]; a[++ai].name = "digest"; a[ai].value = m->path; a[ai-1].next = &a[ai]; a[ai].next = nil; p.attr = a; p.ndata = strlen(m->path); p.data = m->path; if(fd < 0) fd = plumbopen("send", OWRITE); if(fd < 0) sysfatal("plumb: %r\n"); if (plumbsend(fd, &p) < 0){ fprint(2, "plumbsend: %r\n"); close(fd); fd = -1; } } Msg** readmdir(char* dir, Msg* msgs, Msg** mp, int initial) { Msg* m; Dir* d; int n; int fd; int i; char* tf; if(dir == nil) return mp; fd = open(dir, OREAD); if(fd < 0){ fprint(2, "%s: %r\n", dir); return mp; } n = dirreadall(fd, &d); close(fd); if(n < 0) fprint(2, "%s: %r\n", dir); if(n <= 0) return mp; qsort(d, n, sizeof(Dir), cmpent); for(i = 0; i < n; i++) if(isdigit(d[i].name[0])){ tf = smprint("%s/%s", dir, d[i].name); m = newmsg(tf); free(tf); if(m != nil){ if(isnew(m, msgs)){ *mp = m; mp = &m->next; m->visited = 1; m->date = strdup(ctime(d[i].mtime)); m->date[strlen(m->date)-1] = 0; if(!initial || hopt) plumbmsg(m, New); }else freemsg(m); } } free(d); return mp; } Msg* readmbox(char* mbox, Qid* q, Msg* old, int initial) { Msg** mp; Dir* d; int fd; int i; int n; char* dir; Msg* msgs; Msg* m; msgs = old; for(mp = &msgs; *mp != nil; mp = &(*mp)->next) (*mp)->visited = 0; fd = open(mbox, OREAD); if(fd < 0) sysfatal("%s: %r\n", mbox); d = dirfstat(fd); if(d == nil) sysfatal("%s: %r\n", mbox); *q = d->qid; free(d); n = dirreadall(fd, &d); close(fd); if(n <= 0) return nil; qsort(d, n, sizeof(Dir), cmpent); for(i = 0; i < n; i++) if(d[i].qid.type&QTDIR){ dir = smprint("%s/%s", mbox, d[i].name); mp = readmdir(dir, msgs, mp, initial); free(dir); } for(mp = &msgs; (m = *mp) != nil; ) if(!m->visited){ *mp = m->next; plumbmsg(m, Old); freemsg(m); }else mp = &m->next; free(d); return msgs; } void watchmbox(char* dir, Qid q, Msg* msgs) { Dir* d; Qid nqid; for(;;){ sleep(octopus ? 5000 : 20000); d = dirstat(dir); if(d == nil) sysfatal("%s: %r\n", dir); nqid = d->qid; free(d); if(octopus && nqid.path == q.path && nqid.vers == q.vers) continue; msgs = readmbox(dir, &q, msgs, 0); } } void usage(void) { fprint(2, "usage: %s [-dho] [dir]\n", argv0); exits("usage"); } void main(int argc, char*argv[]) { char* dir; char* top; Msg* m; Qid mqid; Msg* msgs; top = smprint("/mail/box/%s", getuser()); ARGBEGIN{ case 'd': debug++; break; case 'h': hopt++; break; case 'o': octopus++; break; default: usage(); }ARGEND; if(argc == 1) dir = cleanpath(argv[0], top); else { if(argc != 0) usage(); dir = smprint("%s/msgs", top); } mboxdir = dir; msgs = readmbox(dir, &mqid, nil, 1); if(debug){ fprint(2, "initial msgs:\n"); printmsgs(msgs); } switch(rfork(RFPROC|RFNOTEG|RFFDG|RFNOWAIT)){ case -1: sysfatal("fork: %r"); case 0: if(hopt) for(m = msgs; m != nil; m = m->next) plumbmsg(m, New); watchmbox(dir, mqid, msgs); exits(nil); } exits(nil); } { int kid, pid; rfork(RFNAMEG); unmount(nil, "/mail/fs"); kid = fork(); switch(kid){ case -1: break; case 0: execl("/bin/upas/fs", "upas/fs", "-p", "-f", mbox, nil); sysfatal("no upas/fs: %r"); default: while ((pid = waitpid()) != -1 && pid != kid) ; break; } } char* openmbox(char* mbox) { char* d; int fd; mboxnm = strrchr(mbox, '/'); if (mboxnm != nil) mboxnm++; else mboxnm = mbox; d = smprint("/mail/fs/%s", mboxnm); upasfs(mbox); /* run upas/fs on mbox file to promail2fs/mget 775 0 0 533 11330534635 11067ustar00nemosys#!/bin/rc rfork ne ramfs # do not rewrite /tmp/msgs.$user mail2fs mspool # set +t on the current month dir, which will keep spam # as temp until cleaned up. # also, generate a cached listing for the current month. cd /mail/box/$user/msgs || exit nodir last=`{ls -d [21][09]???[0-9] | sort | tail -1} if (test -d $last){ chmod +t $last } exit '' mail2fs/mkfile 664 0 0 1744 11330534645 11425ustar00nemosys/n/sources/contrib/nemo/m.tar } = cp + 1; mbox = openmbox(mail2fs/mlist.c 664 0 0 6411 11330534654 11523ustar00nemosys#include #include #include #include /* * Generate a list of msgs from files named in the input. */ Biobuf bout; Biobuf blist; char* readf(char*f) { static char buf[1024]; char err[ERRMAX]; Dir* d; int fd; long n; fd = open(f, OREAD); if(fd < 0) return nil; d = dirfstat(fd); if(d == nil) goto fail; n = d->length; if(n == 0 || n > sizeof buf - 1) n = sizeof buf - 1; if(n == 0) n = read(fd, buf, n); else n = readn(fd, buf, n); if(n < 0) goto fail; buf[n] = 0; free(d); close(fd); return buf; fail: rerrstr(err, sizeof(err)); free(d); close(fd); werrstr(err); return nil; } char* gethdr(char** hdrs, char* h) { int l; l = strlen(h); while(*hdrs){ if(cistrncmp(*hdrs, h, l) == 0) return *hdrs + l + 1; hdrs++; } return ""; } char* cleanfrom(char* f) { char *c, *e; c = strdup(f); e = strchr(c, '@'); if(!e) return nil; else *e = '\0'; return c; } static void attachline(char* msg, char* rel) { Dir* d; int nd; int fd; int i; fd = open(msg, OREAD); if(fd < 0) return; nd = dirreadall(fd, &d); close(fd); for(i = 0; i < nd; i++) if(strcmp(d[i].name, "text") && strcmp(d[i].name, "raw") && strcmp(d[i].name, "L.mbox")) if(d[i].qid.type&QTDIR) Bprint(&bout, "\t%s/%s/text\n", rel, d[i].name); else Bprint(&bout, "\t%s/%s\n", rel, d[i].name); if(nd > 0) free(d); } void hdrline(char*fn, char* buf, char* flags) { char* hdrs[10+1]; int nhdrs; char* f; char* cf; char* s; Dir* de; int n; int fd; int i; int l; s = buf; for(i = 0; s && i < 10; i++){ f = utfrune(s, '\n'); if(f) *f++ = 0; hdrs[i] = s; s = f; } nhdrs = i; hdrs[nhdrs] = nil; f = gethdr(hdrs, "from"); s = gethdr(hdrs, "subject"); cf = cleanfrom(f); l = strlen(s); if(l > 48) l = 48; Bprint(&bout, "%s%-19.19s %-12.12s %-*.*s\n", flags, fn, cf?cf:f, l, l, s); free(cf); fd = open(fn, OREAD); n = dirreadall(fd, &de); close(fd); if(n > 1) Bprint(&bout, "\t"); for(i = 0; i < n; i++) if(strcmp(de[i].name, "text") != 0) Bprint(&bout, "\t%s/%s\n", fn, de[i].name); } static char* cleanpath(char* file, char* dir) { char* s; char* t; char cwd[512]; assert(file && file[0]); if(file[1]) file = strdup(file); else { s = file; file = malloc(3); file[0] = s[0]; file[1] = 0; } s = cleanname(file); if(s[0] != '/' && access(s, AEXIST) == 0){ getwd(cwd, sizeof(cwd)); t = smprint("%s/%s", cwd, s); free(s); return t; } if(s[0] != '/' && dir != nil && dir[0] != 0){ t = smprint("%s/%s", dir, s); free(s); s = cleanname(t); } return s; } void usage(void) { fprint(2, "usage: %s \n", argv0); exits("usage"); } void main(int argc, char*argv[]) { char* top; char* f; char* mf; Biobuf bin; char* buf; char* amf; char* p; ARGBEGIN{ default: usage(); }ARGEND; Binit(&bin, 0, OREAD); Binit(&bout, 1, OWRITE); top = smprint("/mail/box/%s/msgs", getuser()); while((f = Brdstr(&bin, '\n', 1)) != nil){ mf = strchr(f, ' '); if(mf != nil) *mf = 0; mf = cleanpath(f, top); buf = readf(mf); if(buf == nil) continue; amf = mf; if(strncmp(mf, top, strlen(top)) == 0) mf += strlen(top)+1; hdrline(mf, buf, ""); p = strstr(mf, "/text"); if(p != nil) *p = 0; attachline(amf,mf); } Bterm(&bout); Bterm(&bin); exits(nil); } a[ai].name = "filetype"; a[ai].value = "mail"; a[++ai].name = "sender"; a[ai].value = m->from; a[ai-1].next = &a[ai]; a[++ai].name = "length"; a[ai].value = "42"; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; a[ai].value = str[whmail2fs/msgs.c 664 0 0 15700 11330534662 11364ustar00nemosys#include #include #include #include #include "util.h" /* * Generate a list of msgs from a plan b mail box. * This can be used either to read mail by running the * program in the mail box directory or to generate * indexes for msgs */ #define dprint if(debug)fprint int showarch; int showspam; char** months; // show only these dirs char* showrunes; int nmonths; Biobuf bout; Biobuf blist; int debug; char* readf(char*f) { static char buf[1024]; char err[ERRMAX]; Dir* d; int fd; long n; fd = open(f, OREAD); if(fd < 0) return nil; d = dirfstat(fd); if(d == nil) goto fail; n = d->length; if(n == 0 || n > sizeof buf - 1) n = sizeof buf - 1; if(n == 0) n = read(fd, buf, n); else n = readn(fd, buf, n); if(n < 0) goto fail; buf[n] = 0; free(d); close(fd); return buf; fail: rerrstr(err, sizeof(err)); free(d); close(fd); werrstr(err); return nil; } char* gethdr(char** hdrs, char* h) { int l; l = strlen(h); while(*hdrs){ if(cistrncmp(*hdrs, h, l) == 0) return *hdrs + l + 1; hdrs++; } return ""; } char* cleanfrom(char* f) { char *c, *e; c = strdup(f); e = strchr(c, '@'); if(!e) return nil; else *e = '\0'; return c; } void hdrline(char*fn, char* buf, char* flags) { char* hdrs[20+1]; int nhdrs; char* f; char* cf; char* s; Dir* de; int n; int fd; int i; int l; s = buf; for(i = 0; s && i < 20; i++){ f = utfrune(s, '\n'); if(f) *f++ = 0; hdrs[i] = s; s = f; } nhdrs = i; hdrs[nhdrs] = nil; f = gethdr(hdrs, "from"); s = gethdr(hdrs, "subject"); cf = cleanfrom(f); l = strlen(s); if(l > 48) l = 48; Bprint(&bout, "%s%-19.19s %-12.12s %-*.*s\n", flags, fn, cf?cf:f, l, l, s); free(cf); fd = open(fn, OREAD); n = dirreadall(fd, &de); close(fd); if(n > 1) Bprint(&bout, "\t"); for(i = 0; i < n; i++) if(strcmp(de[i].name, "text") != 0) Bprint(&bout, "\t%s/%s\n", fn, de[i].name); } int mustshow(char* name) { Rune r; int nc; if(isdigit(name[0])) return 1; if(showarch && name[0] == 'a' && name[1] == '.') return 1; if(showrunes != nil){ nc = chartorune(&r, name); if(utfrune(showrunes, r) != nil && name[nc] == '.') return 1; } return 0; } static int cmpent(void* a1, void* a2) { Dir* d1 = a1; Dir* d2 = a2; int n1, n2; n1 = atoi(mailnamenb(d1->name)); n2 = atoi(mailnamenb(d2->name)); return n2 - n1; } static void attachline(char* msg, char* rel) { Dir* d; int nd; int fd; int i; fd = open(msg, OREAD); if(fd < 0) return; nd = dirreadall(fd, &d); close(fd); for(i = 0; i < nd; i++) if(strcmp(d[i].name, "text") && strcmp(d[i].name, "raw") && strcmp(d[i].name, "L.mbox")) if(d[i].qid.type&QTDIR) Bprint(&bout, "\t%s/%s/text\n", rel, d[i].name); else Bprint(&bout, "\t%s/%s\n", rel, d[i].name); if(nd > 0) free(d); } static int listf(char* file, int mayarchive) { Biobuf* bin; char* ln; char* s; char* e; int l; int show; int some; int allarchived; Dir* d; bin = Bopen(file, OREAD); if(bin == nil) return 0; show = some = 0; dprint(2, "%s: listing [file] %s\n", argv0, file); allarchived = 1; while((ln = Brdline(bin, '\n')) != nil){ l = Blinelen(bin); if(l < 2) continue; ln[l-1] = 0; if(ln[0] >= '0' && ln[0] <= '9'){ s = strchr(ln, '/'); if(s != nil){ s++; if(s[0] >= '0' && s[0] <= '9') allarchived = 0; show = mustshow(s); if(show){ e = utfrune(s, ' '); if(e != nil) *e = 0; Bprint(&blist, "%s\n", s); if(e != nil) *e = ' '; } } } if(show != 0){ some++; Bprint(&bout, "%s\n", ln); } } Bterm(bin); if(mayarchive && allarchived){ /* move archive from 200901.l to 200901.la; skip it next time */ dprint(2, "%s: %s -> %sa\n", argv0, file, file); d = dirstat(file); if(d != nil){ s = smprint("%sa", d->name); if(s != nil){ nulldir(d); d->name = s; dirwstat(file, d); free(s); } free(d); } } return some; } /* * list msgs in dir. If a file named `dir.l' or `dir.la' exists and is up to date * it is considered a listing of dir and used as a cache. */ int list(char* dir) { Dir* d; Dir* dd; int n; int fd; int i; char* tf; char* sf; int some; char* suf; char* buf; char* flags; char* list; int archivedlist; fd = open(dir, OREAD); if(fd < 0){ fprint(2, "%s: %r\n", dir); return 0; } list = smprint("%s.l", dir); d = dirstat(list); dd = dirfstat(fd); archivedlist = 0; if(d == nil){ free(list); list = smprint("%s.la", dir); d = dirstat(list); archivedlist = 1; } if(d != nil && dd != nil && d->length > 0 && dd->mtime < d->mtime){ free(d); free(dd); close(fd); if(archivedlist && showarch == 0 && showrunes == nil) some = 0; else some = listf(list, archivedlist == 0); free(list); return some; } free(d); free(dd); dprint(2, "%s: listing %s\n", argv0, dir); n = dirreadall(fd, &d); close(fd); if(n <= 0) return 0; some = 0; qsort(d, n, sizeof(Dir), cmpent); for(i = 0; i < n; i++) if(mustshow(d[i].name)){ dprint(2, "%s: listing %s/%s\n", argv0, dir, d[i].name); flags = ""; some++; tf = smprint("%s/%s/text", dir, d[i].name); buf = readf(tf); sf = tf + strlen(dir) - 1; for(suf = sf; *suf != '/' && suf > tf; suf--) ; suf++; hdrline(suf, buf, flags); free(tf); Bprint(&blist, "%s/%s\n", dir, d[i].name); tf = smprint("%s/%s", dir, d[i].name); sf = tf + strlen(dir) - 1; for(suf = sf; *suf != '/' && suf > tf; suf--) ; suf++; attachline(tf, suf); free(tf); } free(d); return some; } int member(char* s, char** list, int nlist) { int i; for(i = 0; i < nlist; i++) if(strcmp(s, list[i]) == 0) return 1; return 0; } int listmbox(char* mbox) { Dir* d; int fd; int i; int n; int some; char* dir; dprint(2, "%s: reading mbox...", argv0); fd = open(mbox, OREAD); if(fd < 0){ fprint(2, "%s: %r\n", mbox); return -1; } n = dirreadall(fd, &d); close(fd); dprint(2, "done\n"); if(n <= 0) return 0; some = 0; qsort(d, n, sizeof(Dir), cmpent); for(i = 0; i < n; i++) if(d[i].qid.type&QTDIR){ if(nmonths > 0) if(!member(d[i].name, months, nmonths)) continue; dir=smprint("%s/%s", mbox, d[i].name); some |= list(dir); free(dir); } free(d); return some; } void usage(void) { fprint(2, "usage: %s [-aD] [-s runes] [dir] [month...]\n", argv0); exits("usage"); } void main(int argc, char*argv[]) { char* dir; char* top; int fd; char* list; top = smprint("/mail/box/%s", getuser()); ARGBEGIN{ case 'D': debug++; break; case 'a': showarch++; break; case 's': showrunes = EARGF(usage()); break; default: usage(); }ARGEND; if(argc == 1) dir = cleanpath(argv[0], top); else{ months = argv; nmonths= argc; dir = smprint("%s/msgs", top); } list = smprint("/tmp/msgs.%s", getuser()); fd = create(list, OWRITE, 0640); if(fd < 0) sysfatal("%s: %r\n", list); free(list); Binit(&blist, fd, OWRITE); Binit(&bout, 1, OWRITE); if(!listmbox(dir)) Bprint(&bout, "No mail\n"); Bterm(&bout); Bterm(&blist); exits(nil); } oid attachline(char* msg, char* rel) { Dir* d; int nd; int fdmail2fs/mspool 775 0 0 3041 11262667142 11465ustar00nemosys#!/bin/rc rfork ne fn deliver { sed '1,/^$/d' $1 > body.$1 sed '/^$/q' $1 > hdr.$1 addrs=`{grep -i '^To: ' hdr.$1 | sed 's/^To: //'} ccs=`{grep -i '^Cc: ' hdr.$1 | sed 's/^Cc: //'} atts=`{grep -i '^Attach: ' hdr.$1 | sed 's/^Attach: //' } incl=`{grep -i '^Include: ' hdr.$1 | sed 's/^Include: //' } repl=`{grep -i '^Replying: ' hdr.$1 | sed 's/^Replying: //' } xaddrs=$addrs xccs=$ccs if (! ~ $#ccs 0) ccs=-C^$ccs if (! ~ $#atts 0) atts=-a^$atts if (! ~ $#incl 0) incl=-A^$incl if (! ~ $#repl 0){ if(! test -f $repl && test -f /mail/box/$user/msgs/$repl) repl=/mail/box/$user/msgs/$repl if(! test -f $repl){ arepl=`{echo $repl | sed 's,(.*)/([0-9]+)/raw,\1/a.\2/raw,'} if(test -f $arepl) repl=$arepl if(test -f /mail/box/$user/msgs/$arepl) repl=/mail/box/$user/msgs/$arepl } if(! test -f $repl){ echo orginal message not found >[1=2] exit orig } upas/fs -p -f $repl repl=-R/mail/fs/mbox/1 } { for(t in $xaddrs) echo 'To: '^$"t for(t in $xccs) echo 'Cc: '^$"t } > addrs.$1 { cat addrs.$1 ; grep -vi '^(To|Cc|Attach|Include|Replying): ' hdr.$1 ; cat body.$1 } | { if ( upas/marshal $repl $atts $incl $ccs $addrs >[2] errs.$1 ) { rm errs.$1 hdr.$1 body.$1 addrs.$1 $1 } if not { echo spool: upas/marshal $repl $atts $incl $ccs $addrs { cat errs.$1 ; q ': ' hdr.$1 body.$1 } | mail -s'returned mail' $user mv $1 Fail.$1 } } unmount /mail/fs } mkdir -p /mail/box/$user/out cd /mail/box/$user/out || exit nombox for (m in [0-9]*) { if (test -e $m) deliver $m } exit '' ontinue; amf = mf; if(strncmp(mf, top, strlen(top)) == 0) mf += strlen(top)+1; hdrline(mf, buf, ""); p = strstr(mf, "/text"); if(p != nil) *p = 0; attachline(amf,mf); } Bterm(&bout); Bterm(&bin); exits(nil); } a[ai].name = "filetype"; a[ai].value = "mail"; a[++ai].name = "sender"; a[ai].value = m->from; a[ai-1].next = &a[ai]; a[++ai].name = "length"; a[ai].value = "42"; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; a[ai].value = str[whmail2fs/pipefrom.example 775 0 0 263 11262667142 13412ustar00nemosys#!/bin/rc rfork ne ramfs cat >/tmp/msg.$pid cat /tmp/msg.$pid | upas/send $* echo From $user >/tmp/msgo.$pid cat /tmp/msg.$pid >>/tmp/msgo.$pid mail2fs -a /tmp/msgo.$pid exit '' mail2fs/upasfs/ 775 0 0 0 11335013101 115065ustar00nemosysmail2fs/upasfs/README 664 0 0 1002 11262670520 12374ustar00nemosysThese are changes to upasfs to add Plan B mailbox format support. If you don't plan to use plan 9 mail tools with Plan B mail boxes this is not needed. You need this only if you want imap access or using Acme's Mail with Plan B mail boxes. Files should go at /sys/src/cmd/upas/fs (you can just bind before and compile to test it before using). Both actual mail boxes and files with mail listings are supported as mail boxes. This makes it easy to create virtual folders. Contact nemo@lsub.org if you need help. 'a' && name[1] == '.') return 1; if(showrunes != nil){ nc = chartorune(&r, name); if(utfrune(showrunes, r) != nil && name[nc] == '.') return 1; } return 0; } static int cmpent(void* a1, void* a2) { Dir* d1 = a1; Dir* d2 = a2; int n1, n2; n1 = atoi(mailnamenb(d1->name)); n2 = atoi(mailnamenb(d2->name)); return n2 - n1; } static void attachline(char* msg, char* rel) { Dir* d; int nd; int fd; int i; fd = open(msg, OREAD); if(fd < 0) return; nd = dirreadall(fd, &d); close(fd); mail2fs/upasfs/dat.h 664 0 0 10174 11335010434 12460ustar00nemosystypedef struct Message Message; struct Message { int id; int refs; int subname; char name[Elemlen]; // pointers into message char *start; // start of message char *end; // end of message char *header; // start of header char *hend; // end of header int hlen; // length of header minus ignored fields char *mheader; // start of mime header char *mhend; // end of mime header char *body; // start of body char *bend; // end of body char *rbody; // raw (unprocessed) body char *rbend; // end of raw (unprocessed) body char *lim; char deleted; char inmbox; char mallocd; // message is malloc'd char ballocd; // body is malloc'd char hallocd; // header is malloce'd // mail info String *unixheader; String *unixfrom; String *unixdate; String *from822; String *sender822; String *to822; String *bcc822; String *cc822; String *replyto822; String *date822; String *inreplyto822; String *subject822; String *messageid822; String *addrs; String *mimeversion; String *sdigest; // mime info String *boundary; String *type; int encoding; int disposition; String *charset; String *filename; int converted; int decoded; char lines[10]; // number of lines in rawbody Message *next; // same level Message *part; // down a level Message *whole; // up a level uchar digest[SHA1dlen]; vlong imapuid; // used by imap4 char uidl[80]; // used by pop3 int mesgno; }; enum { // encodings Enone= 0, Ebase64, Equoted, // disposition possibilities Dnone= 0, Dinline, Dfile, Dignore, PAD64= '=', }; typedef struct Mailbox Mailbox; struct Mailbox { QLock; int refs; Mailbox *next; int id; int dolock; // lock when syncing? int std; char name[Elemlen]; char path[Pathlen]; Dir *d; Message *root; int vers; // goes up each time mailbox is read ulong waketime; char *(*sync)(Mailbox*, int); void (*close)(Mailbox*); char *(*fetch)(Mailbox*, Message*); char *(*ctl)(Mailbox*, int, char**); void *aux; // private to Mailbox implementation }; typedef char *Mailboxinit(Mailbox*, char*); extern Message *root; extern Mailboxinit plan9mbox; extern Mailboxinit pop3mbox; extern Mailboxinit imap4mbox; extern Mailboxinit planbmbox; extern Mailboxinit planbvmbox; char* syncmbox(Mailbox*, int); char* geterrstr(void); void* emalloc(ulong); void* erealloc(void*, ulong); Message* newmessage(Message*); void delmessage(Mailbox*, Message*); void delmessages(int, char**); int newid(void); void mailplumb(Mailbox*, Message*, int); char* newmbox(char*, char*, int); void freembox(char*); void logmsg(char*, Message*); void msgincref(Message*); void msgdecref(Mailbox*, Message*); void mboxincref(Mailbox*); void mboxdecref(Mailbox*); 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); void parsebody(Message*, Mailbox*); void parseunix(Message*); String* date822tounix(char*); int fidmboxrefs(Mailbox*); int hashmboxrefs(Mailbox*); void checkmboxrefs(void); extern int debug; extern int fflag; extern int logging; extern char user[Elemlen]; extern QLock mbllock; extern Mailbox *mbl; extern char *mntpt; extern int biffing; extern int plumbing; extern char* Enotme; enum { /* mail subobjects */ Qbody, Qbcc, Qcc, Qdate, Qdigest, Qdisposition, Qfilename, Qfrom, Qheader, Qinreplyto, Qlines, Qmimeheader, Qmessageid, Qraw, Qrawbody, Qrawheader, Qrawunix, Qreplyto, Qsender, Qsubject, Qto, Qtype, Qunixheader, Qinfo, Qunixdate, Qmax, /* other files */ Qtop, Qmbox, Qdir, Qctl, Qmboxctl, }; #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 typedef struct Hash Hash; struct Hash { Hash *next; char *name; ulong ppath; Qid qid; Mailbox *mb; Message *m; }; Hash *hlook(ulong, char*); void henter(ulong, char*, Qid, Message*, Mailbox*); void hfree(ulong, char*); ulong msgallocd, msgfreed; 3041 11262667142 11465ustar00nemosysmail2fs/upasfs/mbox.c 664 0 0 65262 11263165623 12673ustar00nemosys#include "common.h" #include #include #include #include "dat.h" typedef struct Header Header; struct Header { char *type; void (*f)(Message*, Header*, char*); int len; }; /* 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"; enum { Chunksize = 1024, }; Mailboxinit *boxinit[] = { imap4mbox, pop3mbox, planbmbox, planbvmbox, plan9mbox, }; char* syncmbox(Mailbox *mb, int doplumb) { return (*mb->sync)(mb, doplumb); } /* create a new mailbox */ char* newmbox(char *path, char *name, int std) { Mailbox *mb, **l; char *p, *rv; int i; initheaders(); mb = emalloc(sizeof(*mb)); strncpy(mb->path, path, sizeof(mb->path)-1); if(name == nil){ p = strrchr(path, '/'); if(p == nil) p = path; else p++; if(*p == 0){ free(mb); return "bad mbox name"; } strncpy(mb->name, p, sizeof(mb->name)-1); } else { strncpy(mb->name, name, sizeof(mb->name)-1); } rv = nil; // check for a mailbox type for(i=0; inext){ 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); free(mb); qunlock(&mbllock); return rv; } } // always try locking mb->dolock = 1; mb->refs = 1; mb->next = nil; mb->id = newid(); mb->root = newmessage(nil); mb->std = std; *l = mb; qunlock(&mbllock); qlock(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); return rv; } // close the named mailbox void freembox(char *name) { Mailbox **l, *mb; qlock(&mbllock); 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) { Header *h; static int already; if(already) return; already = 1; for(h = head; h->type != nil; h++) h->len = strlen(h->type); } /* * parse a Unix style header */ void parseunix(Message *m) { char *p; String *h; h = s_new(); for(p = m->start + 5; *p && *p != '\r' && *p != '\n'; p++) s_putc(h, *p); s_terminate(h); s_restart(h); m->unixfrom = s_parse(h, s_reset(m->unixfrom)); m->unixdate = s_append(s_reset(m->unixdate), h->ptr); s_free(h); } /* * parse a message */ void parseheaders(Message *m, int justmime, Mailbox *mb, int addfrom) { 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); } static void parseattachments(Message *m, Mailbox *mb) { Message *nm, **l; char *p, *x; // if there's a boundary, recurse... if(m->boundary != nil){ p = m->body; nm = nil; l = &m->part; for(;;){ x = strstr(p, s_to_c(m->boundary)); /* no boundary, we're done */ if(x == nil){ if(nm != nil) nm->rbend = nm->bend = nm->end = m->bend; break; } /* boundary must be at the start of a line */ 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)); /* 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; } for(nm = m->part; nm != nil; nm = nm->next) parse(nm, 1, mb, 0); return; } // if we've got an rfc822 message, recurse... if(strcmp(s_to_c(m->type), "message/rfc822") == 0){ nm = newmessage(m); m->part = nm; nm->start = nm->header = nm->body = nm->rbody = m->body; nm->end = nm->bend = nm->rbend = m->bend; parse(nm, 0, mb, 0); } } /* * pick up a header line */ static int headerline(char **pp, String *hl) { char *p, *x; s_reset(hl); p = *pp; x = strpbrk(p, ":\n"); if(x == nil || *x == '\n') return 0; for(;;){ x = strchr(p, '\n'); if(x == nil) x = p + strlen(p); s_nappend(hl, p, x-p); p = x; if(*p != '\n' || *++p != ' ' && *p != '\t') break; while(*p == ' ' || *p == '\t') p++; s_putc(hl, ' '); } *pp = p; return 1; } /* returns nil iff there are no addressees */ static String* addr822(char *p) { 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; // whitespace is ignored if(!quoted && isspace(c) || c == '\r') continue; // strings are always treated as atoms if(!quoted && c == '"'){ if(!addrdone && !incomment) s_putc(s, c); for(p++; *p; p++){ if(!addrdone && !incomment) s_putc(s, *p); if(!quoted && *p == '"') break; if(*p == '\\') quoted = 1; else quoted = 0; } if(*p == 0) break; quoted = 0; continue; } // ignore everything in an expicit comment if(!quoted && c == '('){ incomment = 1; continue; } if(incomment){ if(!quoted && c == ')') incomment = 0; quoted = 0; continue; } // anticomments makes everything outside of them comments if(!quoted && c == '<' && !inanticomment){ inanticomment = 1; s = s_reset(s); continue; } if(!quoted && c == '>' && inanticomment){ addrdone = 1; inanticomment = 0; continue; } // commas separate addresses if(!quoted && c == ',' && !inanticomment){ s_terminate(s); addrdone = 0; if(n++ != 0) s_append(list, " "); s_append(list, s_to_c(s)); s = s_reset(s); continue; } // what's left is part of the address s_putc(s, c); // quoted characters are recognized only as characters if(c == '\\') quoted = 1; else quoted = 0; } if(*s_to_c(s) != 0){ s_terminate(s); if(n++ != 0) s_append(list, " "); s_append(list, s_to_c(s)); } s_free(s); if(n == 0){ /* no addressees given, just the keyword */ s_free(list); return nil; } return list; } /* * per rfc2822 §4.5.3, permit multiple to, cc and bcc headers by * concatenating their values. */ static void to822(Message *m, Header *h, char *p) { String *s; p += strlen(h->type); s = addr822(p); if (m->to822 == nil) m->to822 = s; else if (s != nil) { s_append(m->to822, " "); s_append(m->to822, s_to_c(s)); s_free(s); } } static void cc822(Message *m, Header *h, char *p) { String *s; p += strlen(h->type); s = addr822(p); if (m->cc822 == nil) m->cc822 = s; else if (s != nil) { s_append(m->cc822, " "); s_append(m->cc822, s_to_c(s)); s_free(s); } } static void bcc822(Message *m, Header *h, char *p) { String *s; p += strlen(h->type); s = addr822(p); if (m->bcc822 == nil) m->bcc822 = s; else if (s != nil) { s_append(m->bcc822, " "); s_append(m->bcc822, s_to_c(s)); s_free(s); } } static void from822(Message *m, Header *h, char *p) { 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); } static int isattribute(char **pp, char *attr) { char *p; int n; n = strlen(attr); p = *pp; if(cistrncmp(p, attr, n) != 0) return 0; p += n; while(*p == ' ') p++; if(*p++ != '=') return 0; while(*p == ' ') p++; *pp = p; return 1; } static void ctype(Message *m, Header *h, char *p) { String *s; p += h->len; p = skipwhite(p); p = getstring(p, m->type, 1); while(*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); } else if(cistrncmp(p, "multipart", 9) == 0){ /* * the first unbounded part of a multipart message, * the preamble, is not displayed or saved */ } else if(isattribute(&p, "name")){ if(m->filename == nil) setfilename(m, p); } else if(isattribute(&p, "charset")){ p = getstring(p, s_reset(m->charset), 0); } p = skiptosemi(p); } } static void cencoding(Message *m, Header *h, char *p) { p += h->len; p = skipwhite(p); if(cistrncmp(p, "base64", 6) == 0) m->encoding = Ebase64; else if(cistrncmp(p, "quoted-printable", 16) == 0) m->encoding = Equoted; } static void cdisposition(Message *m, Header *h, char *p) { p += h->len; p = skipwhite(p); while(*p){ if(cistrncmp(p, "inline", 6) == 0){ m->disposition = Dinline; } else if(cistrncmp(p, "attachment", 10) == 0){ m->disposition = Dfile; } else if(cistrncmp(p, "filename=", 9) == 0){ p += 9; setfilename(m, p); } p = skiptosemi(p); } } ulong msgallocd, msgfreed; Message* newmessage(Message *parent) { static int id; Message *m; msgallocd++; m = emalloc(sizeof(*m)); memset(m, 0, sizeof(*m)); m->disposition = Dnone; m->type = s_copy("text/plain"); m->charset = s_copy("iso-8859-1"); m->id = newid(); if(parent) sprint(m->name, "%d", ++(parent->subname)); if(parent == nil) parent = m; m->whole = parent; m->hlen = -1; return m; } // 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 for(l = &m->whole->part; *l && *l != m; l = &(*l)->next) ; if(*l != nil) *l = m->next; // 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]); } /* recurse through sub-parts */ 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); } // mark messages (identified by path) for deletion void delmessages(int ac, char **av) { Mailbox *mb; Message *m; int i, needwrite; qlock(&mbllock); for(mb = mbl; mb != nil; mb = mb->next) if(strcmp(av[0], mb->name) == 0){ qlock(mb); break; } qunlock(&mbllock); if(mb == nil) return; needwrite = 0; 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); } break; } } if(needwrite) syncmbox(mb, 1); qunlock(mb); } /* * the following are called with the mailbox qlocked */ void msgincref(Message *m) { m->refs++; } void msgdecref(Mailbox *mb, Message *m) { m->refs--; if(m->refs == 0 && m->deleted) syncmbox(mb, 1); } /* * the following are called with mbllock'd */ void mboxincref(Mailbox *mb) { assert(mb->refs > 0); mb->refs++; } void mboxdecref(Mailbox *mb) { assert(mb->refs > 0); qlock(mb); mb->refs--; if(mb->refs == 0){ delmessage(mb, mb->root); if(mb->ctl) hfree(PATH(mb->id, Qmbox), "ctl"); if(mb->close) (*mb->close)(mb); free(mb); } else qunlock(mb); } 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; } 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 // void decode(Message *m) { int i, len; char *x; if(m->decoded) return; switch(m->encoding){ case Ebase64: len = m->bend - m->body; i = (len*3)/4+1; // room for max chars + null x = emalloc(i); len = dec64((uchar*)x, i, m->body, len); if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; break; case Equoted: len = m->bend - m->body; 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); m->body = x; m->bend = x + len; m->ballocd = 1; break; default: break; } m->decoded = 1; } // convert latin1 to utf void convert(Message *m) { int len; char *x; // don't convert if we're not a leaf, not text, or already converted if(m->converted) return; if(m->part != nil) return; if(cistrncmp(s_to_c(m->type), "text", 4) != 0) return; len = xtoutf(s_to_c(m->charset), &x, m->body, m->bend); if(len > 0){ if(m->ballocd) free(m->body); m->body = x; m->bend = x + len; m->ballocd = 1; } m->converted = 1; } static int hex2int(int x) { if(x >= '0' && x <= '9') return x - '0'; if(x >= 'A' && x <= 'F') return (x - 'A') + 10; if(x >= 'a' && x <= 'f') return (x - 'a') + 10; return -1; } // 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; /* dump trailing white space */ while(e >= in && (*e == ' ' || *e == '\t' || *e == '\r' || *e == '\n')) e--; /* trailing '=' means no newline */ if(*e == '='){ soft = 1; e--; } else soft = 0; while(in <= e){ c = (*in++) & 0xff; switch(c){ case '_': if(uscores){ *out++ = ' '; break; } default: *out++ = c; break; case '=': c = hex2int(*in++); c2 = hex2int(*in++); if (c != -1 && c2 != -1) *out++ = c<<4 | c2; else { *out++ = '='; in -= 2; } break; } } if(!soft) *out++ = '\n'; *out = 0; return out; } int decquoted(char *out, char *in, char *e, int uscores) { char *p, *nl; p = out; while((nl = strchr(in, '\n')) != nil && nl < e){ p = decquotedline(p, in, nl, uscores); in = nl + 1; } if(in < e) p = decquotedline(p, in, e-1, uscores); // make sure we end with a new line if(*(p-1) != '\n'){ *p++ = '\n'; *p = 0; } return p - out; } static char* lowercase(char *p) { char *op; int c; for(op = p; c = *p; p++) if(isupper(c)) *p = tolower(c); return op; } // translate latin1 directly since it fits neatly in utf static int latin1toutf(char **out, char *in, char *e) { int n; char *p; Rune r; n = 0; for(p = in; p < e; p++) if(*p & 0x80) n++; if(n == 0) return 0; n += e-in; *out = p = malloc(n+1); if(p == nil) return 0; for(; in < e; in++){ r = (uchar)*in; p += runetochar(p, &r); } *p = 0; return p - *out; } // 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; // 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; sofar = 0; *out = p = malloc(len+1); if(p == nil) return 0; av[0] = charset; av[1] = "-f"; av[2] = charset; av[3] = 0; if(pipe(totcs) < 0) goto error; if(pipe(fromtcs) < 0){ close(totcs[0]); close(totcs[1]); goto error; } switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ case -1: close(fromtcs[0]); close(fromtcs[1]); close(totcs[0]); close(totcs[1]); goto error; case 0: close(fromtcs[0]); close(totcs[1]); dup(fromtcs[1], 1); dup(totcs[0], 0); close(fromtcs[1]); close(totcs[0]); dup(open("/dev/null", OWRITE), 2); exec("/bin/tcs", av); _exits(0); default: close(fromtcs[1]); close(totcs[0]); switch(rfork(RFPROC|RFFDG|RFNOWAIT)){ case -1: close(fromtcs[0]); close(totcs[1]); goto error; case 0: close(fromtcs[0]); while(in < e){ n = write(totcs[1], in, e-in); if(n <= 0) break; in += n; } close(totcs[1]); _exits(0); default: close(totcs[1]); for(;;){ 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); if(p == nil) goto error; *out = p; } } close(fromtcs[0]); break; } break; } if(sofar == 0) goto error; return sofar; error: free(*out); *out = nil; return 0; } void * 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"); } setmalloctag(p, getcallerpc(&n)); return p; } void * 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"); } setrealloctag(p, getcallerpc(&p)); return p; } void mailplumb(Mailbox *mb, Message *m, int delete) { Plumbmsg p; Plumbattr a[7]; char buf[256]; int ai; char lenstr[10], *from, *subject, *date; static int fd = -1; if(m->subject822 == nil) subject = ""; else subject = s_to_c(m->subject822); if(m->from822 != nil) from = s_to_c(m->from822); else if(m->unixfrom != nil) from = s_to_c(m->unixfrom); else from = ""; if(m->unixdate != nil) date = s_to_c(m->unixdate); else date = ""; sprint(lenstr, "%ld", m->end-m->start); if(biffing && !delete) print("[ %s / %s / %s ]\n", from, subject, lenstr); if(!plumbing) return; if(fd < 0) fd = plumbopen("send", OWRITE); if(fd < 0) return; p.src = "mailfs"; p.dst = "seemail"; p.wdir = "/mail/fs"; p.type = "text"; ai = 0; a[ai].name = "filetype"; a[ai].value = "mail"; a[++ai].name = "sender"; a[ai].value = from; a[ai-1].next = &a[ai]; a[++ai].name = "length"; a[ai].value = lenstr; a[ai-1].next = &a[ai]; a[++ai].name = "mailtype"; a[ai].value = delete?"delete":"new"; a[ai-1].next = &a[ai]; a[++ai].name = "date"; a[ai].value = date; a[ai-1].next = &a[ai]; if(m->sdigest){ a[++ai].name = "digest"; a[ai].value = s_to_c(m->sdigest); a[ai-1].next = &a[ai]; } a[ai].next = nil; p.attr = a; 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)); } /* * squeeze nulls out of the body */ static void nullsqueeze(Message *m) { char *p, *q; 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; } // // convert an RFC822 date into a Unix style date // for when the Unix From line isn't there (e.g. POP3). // enough client programs depend on having a Unix date // that it's easiest to write this conversion code once, right here. // // people don't follow RFC822 particularly closely, // so we use strtotm, which is a bunch of heuristics. // extern int strtotm(char*, Tm*); String* date822tounix(char *s) { char *p, *q; Tm tm; if(strtotm(s, &tm) < 0) return nil; p = asctime(&tm); if(q = strchr(p, '\n')) *q = '\0'; return s_copy(p); } e(m->boundary); s_free(m->type); s_free(m->charset); s_free(m->filename); free(m); } // mark messages (identified by path) for deletion void delmessages(int ac, char **av) { Mailbox *mb; Message *m; int i, needwrite; qlock(&mbllock); for(mb = mbl; mb != nil; mb = mb->next) if(strcmp(av[0], mb->name) == 0){ qlock(mb)mail2fs/upasfs/mkfile 664 0 0 514 11262670520 12675ustar00nemosys #include #include #include "dat.h" static int hasbody(char *m) { char *p; int i; p = strstr(m, "\n\n"); if(p == nil) return 0; p += 2; for(i = 0; p[i] != 0 && i < 5; i++) if(p[i] != '\n') return 1; return 0; } static int readmessage(Message *m, char *msg) { int i, n; int fd; char sdigest[SHA1dlen*2+1]; Dir *d; char *raw; char *buf; char *name; char *p; buf = nil; d = nil; raw = nil; name = smprint("%s/raw", msg); if(name == nil) return -1; if(m->filename != nil) s_free(m->filename); m->filename = s_copy(name); fd = open(name, OREAD); if(fd < 0) goto Fail; d = dirfstat(fd); if(d == nil) goto Fail; raw = malloc(d->length + 1); if(raw == nil) goto Fail; n = readn(fd, raw, d->length); free(d); d = nil; if(n <= 0) goto Fail; raw[n] = 0; close(fd); fd = -1; if(hasbody(raw)){ /* assume raw has everything */ m->start = raw; m->lim = m->end = raw+n; }else{ /* assume raw has just headers */ p = strchr(raw, '\n'); if(p != nil) *++p = 0; if(strncmp(raw, "From ", 5) != 0) goto Fail; free(name); name = smprint("%s/text", msg); if(name == nil) goto Fail; fd = open(name, OREAD); if(fd < 0) goto Fail; d = dirfstat(fd); if(d == nil) goto Fail; /* allocate a few extra chars */ buf = malloc(strlen(raw) + d->length + strlen(msg) + 80); if(buf == nil) goto Fail; strcpy(buf, raw); p = buf+strlen(raw); n = readn(fd, p, d->length); if(n < 0) goto Fail; sprint(p+n, "\n[%s]\n", msg); n += 2 + strlen(msg) + 2; close(fd); free(raw); free(name); free(d); free(m->start); m->start = buf; m->lim = m->end = p+n; } *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) close(fd); free(raw); free(name); free(buf); free(d); return -1; } /* * Deleted messages are kept as spam instead. */ static void archive(Message *m) { char *dir; char *p; char *nname; Dir d; dir = strdup(s_to_c(m->filename)); nname = nil; if(dir == nil) return; p = strrchr(dir, '/'); if(p == nil) goto Fail; *p = 0; p = strrchr(dir, '/'); if(p == nil) goto Fail; p++; if(*p < '0' || *p > '9') goto Fail; nname = smprint("s.%s", p); if(nname == nil) goto Fail; nulldir(&d); d.name = nname; dirwstat(dir, &d); Fail: free(dir); free(nname); } int purgembox(Mailbox *mb, int virtual) { Message *m, *next; int newdels; // forget about what's no longer in the mailbox newdels = 0; for(m = mb->root->part; m != nil; m = next){ next = m->next; if(m->deleted > 0 && m->refs == 0){ if(m->inmbox){ newdels++; /* virtual folders are * virtual, we do not archive */ if(virtual == 0) archive(m); } delmessage(mb, m); } } return newdels; } static int mustshow(char* name) { if(isdigit(name[0])) return 1; if(0 && name[0] == 'a' && name[1] == '.') return 1; if(0 && name[0] == 's' && name[1] == '.') return 1; return 0; } static int readpbmessage(Mailbox *mb, char *msg, int doplumb) { Message *m, **l; char *x; m = newmessage(mb->root); m->mallocd = 1; m->inmbox = 1; if(readmessage(m, msg) < 0){ delmessage(mb, m); mb->root->subname--; 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(*l != m){ if((*l)->deleted < 0) (*l)->deleted = 0; delmessage(mb, m); mb->root->subname--; return -1; } x = strchr(m->start, '\n'); if(x == nil) m->header = m->end; else m->header = x + 1; m->mheader = m->mhend = m->header; parseunix(m); parse(m, 0, mb, 0); logmsg("new", m); /* chain in */ *l = m; if(doplumb) mailplumb(mb, m, 0); return 0; } static int dcmp(Dir *a, Dir *b) { char *an; char *bn; an = a->name; bn = b->name; if(an[0] != 0 && an[1] == '.') an += 2; if(bn[0] != 0 && bn[1] == '.') bn += 2; return strcmp(an, bn); } static void readpbvmbox(Mailbox *mb, int doplumb) { Dir *d; char *data; long sz; char *ln, *p, *nln; char *msg; int fd; int nr; fd = open(mb->path, OREAD); if(fd < 0){ fprint(2, "%s: %s: %r\n", argv0, mb->path); return; } d = dirfstat(fd); if(d == nil){ fprint(2, "%s: %s: %r\n", argv0, mb->path); close(fd); return; } sz = d->length; free(d); if(sz > 32 * 1024 * 1024){ sz = 32 * 1024 * 1024; fprint(2, "%s: %s: bug: folder too big (>32M)\n", argv0, mb->path); } data = malloc(sz+1); if(data == nil){ close(fd); fprint(2, "%s: no memory\n", argv0); return; } nr = readn(fd, data, sz); close(fd); if(nr < 0){ fprint(2, "%s: %s: %r\n", argv0, mb->path); free(data); return; } data[nr] = 0; for(ln = data; *ln != 0; ln = nln){ nln = strchr(ln, '\n'); if(nln != nil) *nln++ = 0; else nln = ln + strlen(ln); p = strchr(ln , ' '); if(p != nil) *p = 0; p = strchr(ln, '\t'); if(p != nil) *p = 0; p = strstr(ln, "/text"); if(p != nil) *p = 0; msg = smprint("/mail/box/%s/msgs/%s", user, ln); if(msg == nil){ fprint(2, "%s: no memory\n", argv0); continue; } readpbmessage(mb, msg, doplumb); free(msg); } free(data); } static void readpbmbox(Mailbox *mb, int doplumb) { int fd; Dir *md; Dir *cd; char *cf; int nd; Dir *d; int nmd; char *msg; char *month; int i, j; fd = open(mb->path, OREAD); if(fd < 0){ fprint(2, "%s: %s: %r\n", argv0, mb->path); return; } nd = dirreadall(fd, &d); close(fd); if(nd > 0) qsort(d, nd, sizeof d[0], (int (*)(void*, void*))dcmp); for(i = 0; i < nd; i++){ month = smprint("%s/%s", mb->path, d[i].name); cf = smprint("%s.l", month); if(month == nil || cf == nil) /* what do we do? */ break; fd = open(month, OREAD); if(fd < 0){ fprint(2, "%s: %s: %r\n", argv0, month); free(month); continue; } md = dirfstat(fd); cd = dirstat(cf); if(md != nil && (md->qid.type & QTDIR) != 0){ /* * A 200909.l file is a cached listing for month 200909. * use it instead if it's there and not out of date. * * But upas/fs is not ready for this. We can handle * huge folders, but upas/fs is eager to read everything. * Also, we are ignoring archived messages on the main * mailbox but not on the virtual mboxes. * Thus, we don't readpbvmbox for path cf. */ free(md); md = nil; nmd = dirreadall(fd, &md); for(j = 0; j < nmd; j++) if(mustshow(md[j].name)){ msg = smprint("%s/%s",month,md[j].name); readpbmessage(mb, msg, doplumb); free(msg); } } close(fd); free(month); free(md); free(cd); free(cf); md = nil; } free(d); } static char* readmbox(Mailbox *mb, int doplumb, int virt) { int fd; Dir *d; static char err[128]; Message *m; if(debug) fprint(2, "read mbox %s\n", mb->path); fd = open(mb->path, OREAD); if(fd < 0){ errstr(err, sizeof(err)); return err; } d = dirfstat(fd); if(d == nil){ close(fd); 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); free(d); return nil; } free(mb->d); } close(fd); mb->d = d; mb->vers++; 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); for(m = mb->root->part; m != nil; m = m->next) if(m->deleted == 0) m->deleted = -1; if(virt == 0) readpbmbox(mb, doplumb); else readpbvmbox(mb, doplumb); /* * 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); } logmsg("mbox read", nil); return nil; } static char* mbsync(Mailbox *mb, int doplumb) { char *rv; rv = readmbox(mb, doplumb, 0); purgembox(mb, 0); return rv; } static char* mbvsync(Mailbox *mb, int doplumb) { char *rv; rv = readmbox(mb, doplumb, 1); purgembox(mb, 1); return rv; } char* planbmbox(Mailbox *mb, char *path) { static char err[64]; char *list; if(access(path, AEXIST) < 0) return Enotme; list = smprint("%s/list", path); if(access(list, AEXIST) < 0){ free(list); return Enotme; } free(list); mb->sync = mbsync; if(debug) fprint(2, "planb mbox %s\n", path); return nil; } char* planbvmbox(Mailbox *mb, char *path) { static char err[64]; char buf[64]; int fd; int nr; int i; fd = open(path, OREAD); if(fd < 0) return Enotme; nr = read(fd, buf, sizeof(buf)-1); close(fd); if(nr < 7) return Enotme; buf[nr] = 0; for(i = 0; i < 6; i++) if(buf[i] < '0' || buf[i] > '9') return Enotme; if(buf[6] != '/') return Enotme; mb->sync = mbvsync; if(debug) fprint(2, "planb virtual mbox %s\n", path); return nil; } common/common.h\ dat.h BIN=/$objtype/bin/upas UPDATE=\ mkfile\ $HFILES\ ${TARG:%=%.c}\ ${OFILES:%.$O=%.c}\ #include #include char* mailnamenb(char *name) { Rune dummy; if(isdigit(name[0]) == 0){ name += chartorune(&dummy, name); if(name[0] == '.') name++; } return name; } char* cleanpath(char* file, char* dir) { char* s; char* t; char cwd[512]; assert(file && file[0]); if(file[1]) file = strdup(file); else { s = file; file = malloc(3); file[0] = s[0]; file[1] = 0; } s = cleanname(file); if(s[0] != '/' && access(s, AEXIST) == 0){ getwd(cwd, sizeof(cwd)); t = smprint("%s/%s", cwd, s); free(s); return t; } if(s[0] != '/' && dir != nil && dir[0] != 0){ t = smprint("%s/%s", dir, s); free(s); s = cleanname(t); } return s; } rn 0; } static int readmessage(Message *m, char *msg) { int i, n; int fd; char sdigest[SHA1dlen*2+1]; Dir *d; char *raw; char *buf; char *name; char *p; buf = nil; d = nil; raw = nil; name = smprint("%s/raw", msg); if(name == nil) return -1; if(m->filename != nil) s_free(m->filename); m->filenamail2fs/util.h 664 0 0 106 11262667142 11333ustar00nemosyschar* mailnamenb(char *name); char* cleanpath(char* file, char* dir);