From 1691e80890e5d8ba258c51c214a3e91880e1db2b Mon Sep 17 00:00:00 2001 From: Doug Morris Date: Fri, 30 Apr 1999 18:08:34 +0000 Subject: [PATCH] Initial revision --- COPYRIGHT | 20 + ChangeLog | 1504 ++++++++++++++++ DIFFERENCES | 278 +++ INSTALL | 229 +++ MACHINES | 92 + MAIL.FILTERING | 102 ++ Makefile.in | 187 ++ README | 56 + TODO | 205 +++ VERSION | 1 + ZSH.COMPLETION | 188 ++ acconfig.h | 263 +++ aclocal.m4 | 41 + config.h.in | 434 +++++ config/Makefile.in | 95 + config/config.c | 364 ++++ config/version.sh | 44 + configure | 4024 ++++++++++++++++++++++++++++++++++++++++++ configure.in | 512 ++++++ etc/MailAliases | 29 + etc/Makefile.in | 134 ++ etc/components | 4 + etc/digestcomps | 9 + etc/distcomps | 2 + etc/forwcomps | 4 + etc/mhl.body | 2 + etc/mhl.digest | 7 + etc/mhl.format | 17 + etc/mhl.forward | 13 + etc/mhl.headers | 17 + etc/mhl.reply | 5 + etc/mhn.defaults.sh | 166 ++ etc/mhn.find.sh | 40 + etc/mts.conf.in | 31 + etc/rcvdistcomps | 3 + etc/replcomps | 21 + etc/replgroupcomps | 36 + etc/scan.default | 11 + etc/scan.mailx | 9 + etc/scan.nomime | 10 + etc/scan.size | 6 + etc/scan.time | 7 + etc/scan.timely | 10 + etc/scan.unseen | 5 + etc/sendfiles | 42 + h/Makefile.in | 59 + h/addrsbr.h | 42 + h/aliasbr.h | 64 + h/dropsbr.h | 59 + h/fmt_compile.h | 109 ++ h/fmt_scan.h | 90 + h/md5.h | 81 + h/mh.h | 324 ++++ h/mhcachesbr.h | 21 + h/mhparse.h | 245 +++ h/mime.h | 38 + h/msh.h | 114 ++ h/netdb.h | 71 + h/nmh.h | 162 ++ h/nntp.h | 73 + h/picksbr.h | 12 + h/popsbr.h | 62 + h/prototypes.h | 159 ++ h/rcvmail.h | 39 + h/scansbr.h | 42 + h/signals.h | 34 + h/vmhsbr.h | 51 + install-sh | 237 +++ man/Makefile.in | 210 +++ man/ali.man | 69 + man/anno.man | 73 + man/ap.man | 84 + man/burst.man | 99 ++ man/comp.man | 124 ++ man/conflict.man | 58 + man/dist.man | 150 ++ man/dp.man | 72 + man/flist.man | 146 ++ man/fmtdump.man | 42 + man/folder.man | 199 +++ man/forw.man | 240 +++ man/inc.man | 194 ++ man/install-mh.man | 49 + man/mark.man | 128 ++ man/mh-alias.man | 198 +++ man/mh-chart.man | 585 ++++++ man/mh-draft.man | 192 ++ man/mh-format.man | 482 +++++ man/mh-mail.man | 235 +++ man/mh-mts.man | 96 + man/mh-profile.man | 587 ++++++ man/mh-sequence.man | 209 +++ man/mh-tailor.man | 287 +++ man/mhbuild.man | 580 ++++++ man/mhl.man | 246 +++ man/mhlist.man | 167 ++ man/mhmail.man | 71 + man/mhn.man | 715 ++++++++ man/mhparam.man | 81 + man/mhpath.man | 136 ++ man/mhshow.man | 480 +++++ man/mhstore.man | 420 +++++ man/msgchk.man | 95 + man/msh.man | 208 +++ man/next.man | 62 + man/nmh.man | 235 +++ man/packf.man | 67 + man/pick.man | 264 +++ man/post.man | 112 ++ man/prev.man | 62 + man/prompter.man | 116 ++ man/rcvdist.man | 60 + man/rcvpack.man | 45 + man/rcvstore.man | 100 ++ man/rcvtty.man | 84 + man/refile.man | 143 ++ man/repl.man | 337 ++++ man/rmf.man | 70 + man/rmm.man | 76 + man/scan.man | 162 ++ man/send.man | 186 ++ man/sendfiles.man | 150 ++ man/show.man | 164 ++ man/slocal.man | 354 ++++ man/sortm.man | 101 ++ man/tmac.h.in | 77 + man/vmh.man | 105 ++ man/whatnow.man | 141 ++ man/whom.man | 69 + mkinstalldirs | 32 + mts/Makefile.in | 83 + mts/mmdf/Makefile.in | 64 + mts/mmdf/hosts.c | 72 + mts/sendmail/Makefile.in | 89 + mts/sendmail/hosts.c | 135 ++ mts/sendmail/sendmail.c | 827 +++++++++ mts/smtp/Makefile.in | 92 + mts/smtp/hosts.c | 135 ++ mts/smtp/smtp.c | 1325 ++++++++++++++ mts/smtp/smtp.h | 295 ++++ sbr/Makefile.in | 130 ++ sbr/add.c | 44 + sbr/addrsbr.c | 496 ++++++ sbr/ambigsw.c | 16 + sbr/atooi.c | 24 + sbr/brkstring.c | 91 + sbr/check_charset.c | 65 + sbr/closefds.c | 18 + sbr/concat.c | 36 + sbr/context_del.c | 42 + sbr/context_find.c | 25 + sbr/context_foil.c | 52 + sbr/context_read.c | 111 ++ sbr/context_replace.c | 69 + sbr/context_save.c | 90 + sbr/copy.c | 18 + sbr/copyip.c | 20 + sbr/cpydata.c | 23 + sbr/cpydgst.c | 65 + sbr/discard.c | 65 + sbr/done.c | 14 + sbr/error.c | 140 ++ sbr/fdcompare.c | 38 + sbr/fmt_addr.c | 115 ++ sbr/fmt_compile.c | 643 +++++++ sbr/fmt_def.c | 10 + sbr/fmt_new.c | 105 ++ sbr/fmt_rfc2047.c | 235 +++ sbr/fmt_scan.c | 826 +++++++++ sbr/folder_addmsg.c | 194 ++ sbr/folder_delmsgs.c | 115 ++ sbr/folder_free.c | 28 + sbr/folder_pack.c | 86 + sbr/folder_read.c | 163 ++ sbr/folder_realloc.c | 90 + sbr/gans.c | 49 + sbr/getans.c | 75 + sbr/getanswer.c | 19 + sbr/getarguments.c | 48 + sbr/getcpy.c | 32 + sbr/getfolder.c | 32 + sbr/lock_file.c | 555 ++++++ sbr/m_atoi.c | 32 + sbr/m_backup.c | 26 + sbr/m_convert.c | 443 +++++ sbr/m_draft.c | 88 + sbr/m_getfld.c | 748 ++++++++ sbr/m_gmprot.c | 17 + sbr/m_maildir.c | 94 + sbr/m_msgdef.c | 31 + sbr/m_name.c | 21 + sbr/m_scratch.c | 26 + sbr/m_tmpfil.c | 20 + sbr/makedir.c | 79 + sbr/path.c | 161 ++ sbr/peekc.c | 19 + sbr/pidstatus.c | 63 + sbr/pidwait.c | 53 + sbr/print_help.c | 29 + sbr/print_sw.c | 53 + sbr/print_version.c | 15 + sbr/push.c | 48 + sbr/putenv.c | 76 + sbr/pwd.c | 115 ++ sbr/r1bindex.c | 32 + sbr/readconfig.c | 109 ++ sbr/refile.c | 49 + sbr/remdir.c | 22 + sbr/ruserpass.c | 218 +++ sbr/seq_add.c | 189 ++ sbr/seq_bits.c | 27 + sbr/seq_del.c | 130 ++ sbr/seq_getnum.c | 22 + sbr/seq_list.c | 104 ++ sbr/seq_nameok.c | 54 + sbr/seq_print.c | 46 + sbr/seq_read.c | 235 +++ sbr/seq_save.c | 115 ++ sbr/seq_setcur.c | 19 + sbr/seq_setprev.c | 42 + sbr/seq_setunseen.c | 58 + sbr/showfile.c | 65 + sbr/sigmsg.awk | 87 + sbr/signals.c | 116 ++ sbr/smatch.c | 45 + sbr/snprintb.c | 38 + sbr/snprintf.c | 1045 +++++++++++ sbr/ssequal.c | 27 + sbr/strcasecmp.c | 53 + sbr/strdup.c | 25 + sbr/strerror.c | 21 + sbr/strindex.c | 23 + sbr/trimcpy.c | 38 + sbr/uprf.c | 39 + sbr/vfgets.c | 73 + stamp-h.in | 1 + uip/Makefile.in | 307 ++++ uip/ali.c | 253 +++ uip/aliasbr.c | 598 +++++++ uip/anno.c | 213 +++ uip/annosbr.c | 111 ++ uip/ap.c | 205 +++ uip/burst.c | 406 +++++ uip/comp.c | 306 ++++ uip/conflict.c | 545 ++++++ uip/dist.c | 291 +++ uip/distsbr.c | 194 ++ uip/dp.c | 153 ++ uip/dropsbr.c | 720 ++++++++ uip/flist.c | 709 ++++++++ uip/fmtdump.c | 506 ++++++ uip/folder.c | 828 +++++++++ uip/forw.c | 697 ++++++++ uip/ftpsbr.c | 523 ++++++ uip/inc.c | 939 ++++++++++ uip/install-mh.c | 209 +++ uip/mark.c | 298 ++++ uip/md5.c | 308 ++++ uip/mhbuild.c | 384 ++++ uip/mhbuildsbr.c | 4216 ++++++++++++++++++++++++++++++++++++++++++++ uip/mhcachesbr.c | 443 +++++ uip/mhfree.c | 278 +++ uip/mhl.c | 32 + uip/mhlist.c | 428 +++++ uip/mhlistsbr.c | 428 +++++ uip/mhlsbr.c | 1805 +++++++++++++++++++ uip/mhmail.c | 204 +++ uip/mhmisc.c | 231 +++ uip/mhn.c | 741 ++++++++ uip/mhoutsbr.c | 471 +++++ uip/mhparam.c | 194 ++ uip/mhparse.c | 2640 +++++++++++++++++++++++++++ uip/mhpath.c | 148 ++ uip/mhshow.c | 508 ++++++ uip/mhshowsbr.c | 1014 +++++++++++ uip/mhstore.c | 440 +++++ uip/mhstoresbr.c | 1112 ++++++++++++ uip/mhtest.c | 434 +++++ uip/msgchk.c | 419 +++++ uip/msh.c | 2588 +++++++++++++++++++++++++++ uip/mshcmds.c | 3095 ++++++++++++++++++++++++++++++++ uip/packf.c | 208 +++ uip/pick.c | 316 ++++ uip/picksbr.c | 939 ++++++++++ uip/popi.c | 644 +++++++ uip/popsbr.c | 677 +++++++ uip/post.c | 1965 +++++++++++++++++++++ uip/prompter.c | 481 +++++ uip/rcvdist.c | 275 +++ uip/rcvpack.c | 103 ++ uip/rcvstore.c | 233 +++ uip/rcvtty.c | 308 ++++ uip/refile.c | 396 +++++ uip/repl.c | 462 +++++ uip/replsbr.c | 448 +++++ uip/rmf.c | 245 +++ uip/rmm.c | 151 ++ uip/scan.c | 351 ++++ uip/scansbr.c | 386 ++++ uip/send.c | 432 +++++ uip/sendsbr.c | 650 +++++++ uip/show.c | 532 ++++++ uip/slocal.c | 1543 ++++++++++++++++ uip/sortm.c | 583 ++++++ uip/spost.c | 833 +++++++++ uip/termsbr.c | 227 +++ uip/viamail.c | 249 +++ uip/vmh.c | 1513 ++++++++++++++++ uip/vmhsbr.c | 224 +++ uip/vmhtest.c | 322 ++++ uip/whatnow.c | 18 + uip/whatnowproc.c | 115 ++ uip/whatnowsbr.c | 975 ++++++++++ uip/whom.c | 189 ++ uip/wmh.c | 1387 +++++++++++++++ zotnet/Makefile.in | 103 ++ zotnet/bboards/Makefile.in | 76 + zotnet/bboards/bboards.h | 88 + zotnet/bboards/getbbent.c | 733 ++++++++ zotnet/mf/Makefile.in | 76 + zotnet/mf/mf.c | 963 ++++++++++ zotnet/mf/mf.h | 82 + zotnet/mts/Makefile.in | 94 + zotnet/mts/client.c | 467 +++++ zotnet/mts/mts.c | 454 +++++ zotnet/mts/mts.h | 82 + zotnet/tws/Makefile.in | 99 ++ zotnet/tws/dtime.c | 508 ++++++ zotnet/tws/dtimep.c-lexed | 1672 ++++++++++++++++++ zotnet/tws/dtimep.lex | 417 +++++ zotnet/tws/lexedit.sed | 20 + zotnet/tws/lexstring.c | 257 +++ zotnet/tws/tws.h | 62 + 333 files changed, 92706 insertions(+) create mode 100644 COPYRIGHT create mode 100644 ChangeLog create mode 100644 DIFFERENCES create mode 100644 INSTALL create mode 100644 MACHINES create mode 100644 MAIL.FILTERING create mode 100644 Makefile.in create mode 100644 README create mode 100644 TODO create mode 100644 VERSION create mode 100644 ZSH.COMPLETION create mode 100644 acconfig.h create mode 100644 aclocal.m4 create mode 100644 config.h.in create mode 100644 config/Makefile.in create mode 100644 config/config.c create mode 100755 config/version.sh create mode 100755 configure create mode 100644 configure.in create mode 100644 etc/MailAliases create mode 100644 etc/Makefile.in create mode 100644 etc/components create mode 100644 etc/digestcomps create mode 100644 etc/distcomps create mode 100644 etc/forwcomps create mode 100644 etc/mhl.body create mode 100644 etc/mhl.digest create mode 100644 etc/mhl.format create mode 100644 etc/mhl.forward create mode 100644 etc/mhl.headers create mode 100644 etc/mhl.reply create mode 100755 etc/mhn.defaults.sh create mode 100755 etc/mhn.find.sh create mode 100644 etc/mts.conf.in create mode 100644 etc/rcvdistcomps create mode 100644 etc/replcomps create mode 100644 etc/replgroupcomps create mode 100644 etc/scan.default create mode 100644 etc/scan.mailx create mode 100644 etc/scan.nomime create mode 100644 etc/scan.size create mode 100644 etc/scan.time create mode 100644 etc/scan.timely create mode 100644 etc/scan.unseen create mode 100755 etc/sendfiles create mode 100644 h/Makefile.in create mode 100644 h/addrsbr.h create mode 100644 h/aliasbr.h create mode 100644 h/dropsbr.h create mode 100644 h/fmt_compile.h create mode 100644 h/fmt_scan.h create mode 100644 h/md5.h create mode 100644 h/mh.h create mode 100644 h/mhcachesbr.h create mode 100644 h/mhparse.h create mode 100644 h/mime.h create mode 100644 h/msh.h create mode 100644 h/netdb.h create mode 100644 h/nmh.h create mode 100644 h/nntp.h create mode 100644 h/picksbr.h create mode 100644 h/popsbr.h create mode 100644 h/prototypes.h create mode 100644 h/rcvmail.h create mode 100644 h/scansbr.h create mode 100644 h/signals.h create mode 100644 h/vmhsbr.h create mode 100755 install-sh create mode 100644 man/Makefile.in create mode 100644 man/ali.man create mode 100644 man/anno.man create mode 100644 man/ap.man create mode 100644 man/burst.man create mode 100644 man/comp.man create mode 100644 man/conflict.man create mode 100644 man/dist.man create mode 100644 man/dp.man create mode 100644 man/flist.man create mode 100644 man/fmtdump.man create mode 100644 man/folder.man create mode 100644 man/forw.man create mode 100644 man/inc.man create mode 100644 man/install-mh.man create mode 100644 man/mark.man create mode 100644 man/mh-alias.man create mode 100644 man/mh-chart.man create mode 100644 man/mh-draft.man create mode 100644 man/mh-format.man create mode 100644 man/mh-mail.man create mode 100644 man/mh-mts.man create mode 100644 man/mh-profile.man create mode 100644 man/mh-sequence.man create mode 100644 man/mh-tailor.man create mode 100644 man/mhbuild.man create mode 100644 man/mhl.man create mode 100644 man/mhlist.man create mode 100644 man/mhmail.man create mode 100644 man/mhn.man create mode 100644 man/mhparam.man create mode 100644 man/mhpath.man create mode 100644 man/mhshow.man create mode 100644 man/mhstore.man create mode 100644 man/msgchk.man create mode 100644 man/msh.man create mode 100644 man/next.man create mode 100644 man/nmh.man create mode 100644 man/packf.man create mode 100644 man/pick.man create mode 100644 man/post.man create mode 100644 man/prev.man create mode 100644 man/prompter.man create mode 100644 man/rcvdist.man create mode 100644 man/rcvpack.man create mode 100644 man/rcvstore.man create mode 100644 man/rcvtty.man create mode 100644 man/refile.man create mode 100644 man/repl.man create mode 100644 man/rmf.man create mode 100644 man/rmm.man create mode 100644 man/scan.man create mode 100644 man/send.man create mode 100644 man/sendfiles.man create mode 100644 man/show.man create mode 100644 man/slocal.man create mode 100644 man/sortm.man create mode 100644 man/tmac.h.in create mode 100644 man/vmh.man create mode 100644 man/whatnow.man create mode 100644 man/whom.man create mode 100755 mkinstalldirs create mode 100644 mts/Makefile.in create mode 100644 mts/mmdf/Makefile.in create mode 100644 mts/mmdf/hosts.c create mode 100644 mts/sendmail/Makefile.in create mode 100644 mts/sendmail/hosts.c create mode 100644 mts/sendmail/sendmail.c create mode 100644 mts/smtp/Makefile.in create mode 100644 mts/smtp/hosts.c create mode 100644 mts/smtp/smtp.c create mode 100644 mts/smtp/smtp.h create mode 100644 sbr/Makefile.in create mode 100644 sbr/add.c create mode 100644 sbr/addrsbr.c create mode 100644 sbr/ambigsw.c create mode 100644 sbr/atooi.c create mode 100644 sbr/brkstring.c create mode 100644 sbr/check_charset.c create mode 100644 sbr/closefds.c create mode 100644 sbr/concat.c create mode 100644 sbr/context_del.c create mode 100644 sbr/context_find.c create mode 100644 sbr/context_foil.c create mode 100644 sbr/context_read.c create mode 100644 sbr/context_replace.c create mode 100644 sbr/context_save.c create mode 100644 sbr/copy.c create mode 100644 sbr/copyip.c create mode 100644 sbr/cpydata.c create mode 100644 sbr/cpydgst.c create mode 100644 sbr/discard.c create mode 100644 sbr/done.c create mode 100644 sbr/error.c create mode 100644 sbr/fdcompare.c create mode 100644 sbr/fmt_addr.c create mode 100644 sbr/fmt_compile.c create mode 100644 sbr/fmt_def.c create mode 100644 sbr/fmt_new.c create mode 100644 sbr/fmt_rfc2047.c create mode 100644 sbr/fmt_scan.c create mode 100644 sbr/folder_addmsg.c create mode 100644 sbr/folder_delmsgs.c create mode 100644 sbr/folder_free.c create mode 100644 sbr/folder_pack.c create mode 100644 sbr/folder_read.c create mode 100644 sbr/folder_realloc.c create mode 100644 sbr/gans.c create mode 100644 sbr/getans.c create mode 100644 sbr/getanswer.c create mode 100644 sbr/getarguments.c create mode 100644 sbr/getcpy.c create mode 100644 sbr/getfolder.c create mode 100644 sbr/lock_file.c create mode 100644 sbr/m_atoi.c create mode 100644 sbr/m_backup.c create mode 100644 sbr/m_convert.c create mode 100644 sbr/m_draft.c create mode 100644 sbr/m_getfld.c create mode 100644 sbr/m_gmprot.c create mode 100644 sbr/m_maildir.c create mode 100644 sbr/m_msgdef.c create mode 100644 sbr/m_name.c create mode 100644 sbr/m_scratch.c create mode 100644 sbr/m_tmpfil.c create mode 100644 sbr/makedir.c create mode 100644 sbr/path.c create mode 100644 sbr/peekc.c create mode 100644 sbr/pidstatus.c create mode 100644 sbr/pidwait.c create mode 100644 sbr/print_help.c create mode 100644 sbr/print_sw.c create mode 100644 sbr/print_version.c create mode 100644 sbr/push.c create mode 100644 sbr/putenv.c create mode 100644 sbr/pwd.c create mode 100644 sbr/r1bindex.c create mode 100644 sbr/readconfig.c create mode 100644 sbr/refile.c create mode 100644 sbr/remdir.c create mode 100644 sbr/ruserpass.c create mode 100644 sbr/seq_add.c create mode 100644 sbr/seq_bits.c create mode 100644 sbr/seq_del.c create mode 100644 sbr/seq_getnum.c create mode 100644 sbr/seq_list.c create mode 100644 sbr/seq_nameok.c create mode 100644 sbr/seq_print.c create mode 100644 sbr/seq_read.c create mode 100644 sbr/seq_save.c create mode 100644 sbr/seq_setcur.c create mode 100644 sbr/seq_setprev.c create mode 100644 sbr/seq_setunseen.c create mode 100644 sbr/showfile.c create mode 100755 sbr/sigmsg.awk create mode 100644 sbr/signals.c create mode 100644 sbr/smatch.c create mode 100644 sbr/snprintb.c create mode 100644 sbr/snprintf.c create mode 100644 sbr/ssequal.c create mode 100644 sbr/strcasecmp.c create mode 100644 sbr/strdup.c create mode 100644 sbr/strerror.c create mode 100644 sbr/strindex.c create mode 100644 sbr/trimcpy.c create mode 100644 sbr/uprf.c create mode 100644 sbr/vfgets.c create mode 100644 stamp-h.in create mode 100644 uip/Makefile.in create mode 100644 uip/ali.c create mode 100644 uip/aliasbr.c create mode 100644 uip/anno.c create mode 100644 uip/annosbr.c create mode 100644 uip/ap.c create mode 100644 uip/burst.c create mode 100644 uip/comp.c create mode 100644 uip/conflict.c create mode 100644 uip/dist.c create mode 100644 uip/distsbr.c create mode 100644 uip/dp.c create mode 100644 uip/dropsbr.c create mode 100644 uip/flist.c create mode 100644 uip/fmtdump.c create mode 100644 uip/folder.c create mode 100644 uip/forw.c create mode 100644 uip/ftpsbr.c create mode 100644 uip/inc.c create mode 100644 uip/install-mh.c create mode 100644 uip/mark.c create mode 100644 uip/md5.c create mode 100644 uip/mhbuild.c create mode 100644 uip/mhbuildsbr.c create mode 100644 uip/mhcachesbr.c create mode 100644 uip/mhfree.c create mode 100644 uip/mhl.c create mode 100644 uip/mhlist.c create mode 100644 uip/mhlistsbr.c create mode 100644 uip/mhlsbr.c create mode 100644 uip/mhmail.c create mode 100644 uip/mhmisc.c create mode 100644 uip/mhn.c create mode 100644 uip/mhoutsbr.c create mode 100644 uip/mhparam.c create mode 100644 uip/mhparse.c create mode 100644 uip/mhpath.c create mode 100644 uip/mhshow.c create mode 100644 uip/mhshowsbr.c create mode 100644 uip/mhstore.c create mode 100644 uip/mhstoresbr.c create mode 100644 uip/mhtest.c create mode 100644 uip/msgchk.c create mode 100644 uip/msh.c create mode 100644 uip/mshcmds.c create mode 100644 uip/packf.c create mode 100644 uip/pick.c create mode 100644 uip/picksbr.c create mode 100644 uip/popi.c create mode 100644 uip/popsbr.c create mode 100644 uip/post.c create mode 100644 uip/prompter.c create mode 100644 uip/rcvdist.c create mode 100644 uip/rcvpack.c create mode 100644 uip/rcvstore.c create mode 100644 uip/rcvtty.c create mode 100644 uip/refile.c create mode 100644 uip/repl.c create mode 100644 uip/replsbr.c create mode 100644 uip/rmf.c create mode 100644 uip/rmm.c create mode 100644 uip/scan.c create mode 100644 uip/scansbr.c create mode 100644 uip/send.c create mode 100644 uip/sendsbr.c create mode 100644 uip/show.c create mode 100644 uip/slocal.c create mode 100644 uip/sortm.c create mode 100644 uip/spost.c create mode 100644 uip/termsbr.c create mode 100644 uip/viamail.c create mode 100644 uip/vmh.c create mode 100644 uip/vmhsbr.c create mode 100644 uip/vmhtest.c create mode 100644 uip/whatnow.c create mode 100644 uip/whatnowproc.c create mode 100644 uip/whatnowsbr.c create mode 100644 uip/whom.c create mode 100644 uip/wmh.c create mode 100644 zotnet/Makefile.in create mode 100644 zotnet/bboards/Makefile.in create mode 100644 zotnet/bboards/bboards.h create mode 100644 zotnet/bboards/getbbent.c create mode 100644 zotnet/mf/Makefile.in create mode 100644 zotnet/mf/mf.c create mode 100644 zotnet/mf/mf.h create mode 100644 zotnet/mts/Makefile.in create mode 100644 zotnet/mts/client.c create mode 100644 zotnet/mts/mts.c create mode 100644 zotnet/mts/mts.h create mode 100644 zotnet/tws/Makefile.in create mode 100644 zotnet/tws/dtime.c create mode 100644 zotnet/tws/dtimep.c-lexed create mode 100644 zotnet/tws/dtimep.lex create mode 100644 zotnet/tws/lexedit.sed create mode 100644 zotnet/tws/lexstring.c create mode 100644 zotnet/tws/tws.h diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..1948505 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,20 @@ + +Copyright (c) 1997-1998 Richard Coleman +All rights reserved. + +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and to distribute modified versions of this software for any +purpose, provided that the above copyright notice and the following two +paragraphs appear in all copies of this software. + +In no event shall Richard Coleman be liable to any party for direct, +indirect, special, incidental, or consequential damages arising out of +the use of this software and its documentation, even if Richard Coleman +has been advised of the possibility of such damage. + +Richard Coleman specifically disclaims any warranties, including, but +not limited to, the implied warranties of merchantability and fitness +for a particular purpose. The software provided hereunder is on an "as +is" basis, and Richard Coleman has no obligation to provide maintenance, +support, updates, enhancements, or modifications. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..fa6ef47 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1504 @@ + +1999-02-06 Richard Coleman + + * Released nmh-1.0. + + * Merged mbx_open and mbx_Xopen in dropsbr.c. Fixed + mbx_open so that the mode of zero length maildrops + would not be changed. + + * Replaced the substitute version of snprintf() with the + one from the Apache web server. + + * Changed to default mode for creating new messages to 0600 + (this should have been done a long time ago). + + * Changed "flist" to handle searching for multiple sequences + for each folder. Also flist will now correctly split + Unseen-Sequence if it consists of multiple sequences. + + * Added new switches `-unlink' and `-nounlink' to "refile". + + * Added new switches `-unlink' and `-nounlink' to "rmm". + + * More cleanups of slocal output. Changed adorn() to + send to stdout, instead of stderr (to match rest of + verbose printing). + + * Merged mbx_create() into mbx_open, so that creating and + opening a nonexistent maildrop is done atomically. This + removes a bad race condition. + + * Fixed bug that caused slocal to be unable to save to MMDF + style drop file. + + * Added new wrapper function usr_folder() to slocal.c to + handle adding message to folder (currently, it still uses + usr_pipe() to call rcvstore). + + * seq_list() checks for empty folder before scanning for + sequence information. + + * num_digits() in flist.c and folder.c now returns correct + value for 0. Also added sanity check. + + * folder_delmsgs() now correctly decrements internal message + count. + + * Don't attempt to read sequence information if folder + is empty. + + * Split seq_read into seq_public and seq_private. + + * Small change to sigmsg.awk, since newer versions of gawk + interpret 034 as octal. + + * In flist, don't scan for sequence information in empty folder. + + * Updated mhn.defaults.sh to output profile entries for mhshow, + mhstore, and mhbuild. + + * Changed configuration parameter "mhn-access-ftp" to + "nmh-access-ftp". Updated man pages + + * Moved the code in InitMultipart to reverse the order of the + parts in a multipart, into its own function "reverse_parts()". + + * Changed code in mhbuildsbr.c to store unencoded content + in the c_cefile structure when building. + + * Changed code in mhoutsbr.c to look for unencoded content + in the c_cefile structure when outputing message. + + * Changed configuration parameter "mhn-cache" and + "mhn-private-cache", to "nmh-cache" and "nmh-private-cache", + since it is used in mhstore, mhlist, and mhshow. Updated man pages + + * Change configuration parameter "mhn-storage" to + "nmh-storage", since it is now used in mhstore, mhlist, + and mhshow. Updated man pages + + * Add autoconf support for KPOP (kerberized pop). + + * Add autoconf support for Hesiod. + + * Split routines to output a message given a Content structure + (output_message, output_content, write7Bit, etc..) to a new + file "mhoutsbr.c". + + * Split output_content(), into output_content() and build_headers(). + + * Changed copy_some_headers() in mhstoresbr.c, to use the linked + list of header fields, rather than reopening the message. + + * Added free_header() to mhfree.c to free structures containing + header field information. + + * Changed get_content() to use the linked list of header fields + when parsing the various MIME headers (Content-XXX). + + * Changed get_content() to store linked list of header field + values when parsing a content. + + * Changed mhbuild, mhn, mhlist, mhshow, mhstore, to use the + routines in mhcachesbr.c to handle the content cache. + + * Split various funtions (find_cache, find_cache_aux, find_cache_aux2, + cache_content) into new file mhcachesbr.c. + + * More calls to sprintf/strcpy (primarily in mhparse.c + and mhbuildsbr.c) converted to snprintf/strncpy. + + * When a message is displayed with `mhshow', it is now + removed from the "unseen" sequence. + + * Change the default "showmimeproc" to "mhshow". + + * Split "mhn -show" off into separate command "mhshow". + + * Split "mhn -store" off into separate command "mhstore". + + * Split "mhn -list" off into separate command "mhlist". + + * Add sanity checks to context_find(), context_replace(), + and context_del(), to abort if context file hasn't been + read. + + * Add calls to context_read(), to the beginning of all nmh + commands (instead of being called indirectly by context_find). + + * Changes the "substitute" version of vsnprintf/snprintf for + operating systems without native versions, to just call the + native vsprintf(), and ignore the buffer length. This is + faster, but less secure than the previous version that used + temporary files. This should only be a problem for systems + which do not have a native snprintf(), and require `inc' to + be setuid/setgid. + + * Lots more calls to sprintf/strcpy converted to snprintf/strncpy. + + * Changes client() routine to take additional parameter, which is + the buffer length of the parameter "response". Then added + buffer length checks for this parameter. + + * Changed getws() to get_fields(), since that is apparently the + name of a wide character version of gets() on some archetitures. + + * Lots of sprintf/strcpy calls converted to snprintf/strncpy. + + * Change the code in most of the commands that take multiple + message names/sequences/ranges on the command, such that + the msgs array is expanded dynamically. This removes most + of the limits on the length of command lines. + + * Add additional parameter to copyip(), to specify the + maximum number of strings that can be copied (security + fix). + + * Create new function getarguments(), to massage the argument + vector before parsing it (add any arguments from your + profile to the beginning of the argument vector). This + also removed the general limit on the number of command line + arguments. + +1998-07-04 Richard Coleman + + * Released nmh-0.27. + + * Added a new command "delete", that is available during + a "whatnow" session. It is equivalent to "quit -delete". + + * Added another parameter to editfile (in whatnowsbr.c), + that controls whether editfile should remember the last + program that was exec'ed. This way the whatnow command + "mime", will not be re-executed if "edit" is later given + with no arguments. + + * Changed whatnowsbr.c, so that whatnow doesn't abort if + mhbuild returns an error. + + * Added parameter to sendsbr(), so you may specify whether to + rename the draft file. + + * Pass delay time to splitmsg() as a parameter, rather than + use a global variable. + + * Moved code to rename draft file after sending message from + splitmsg and sendaux, to sendsbr. + + * Removed all the code in viamail to split messages and then + mail them. Replaced this with the standard sendsbr.c routines. + + * Changed sendsbr(), so that when splitting messages into + messages of type "message/partial", the header fields that + are copied are more compliant with RFC-2046. + + * Fixed mhbuild to track temporary files better. They are + now correctly removed when mhbuild aborts. + + * Created a new man page for "sendfiles". The information + about "mhn -viamail" in the "mhn" man page was moved to + this new page. + + * Changed the name of the "viamail" shell script to + "sendfiles". Modified "sendfiles" to use the new + viamail program. + + * Moved the functionality for "mhn -viamail" out of mhn, + and into a separate executable called "viamail". + + * When storing MIME contents to a folder using mhn -store, + they are now accumulated in a temporary file, and then added + to the folder using folder_addmsg(). + + * Moved code to save content to a folder from store_content + to new function output_content_folder. + + * Moved code to save content to file from store_content to + new function output_content_file. + + * Moved code to parse storage format string from store_content + to new function parse_format_string. + + * Fix copy_some_headers() in mhstoresbr.c, so that the + correct header fields in the first enclosing message/partial + will be copied (according to RFC2046), when using mhn -store + to reassemble messages of type message/partial. + + * Fixed bug to openFTP() in mhparse.c, that caused the + tmp file to not be removed, when transferring a + message/external file from ftp. + + * Moved the code in mhparse.c to process -auto switch (scan + contents for the attribute "name"), to a new function + "get_storeproc" in mhstoresbr.c. + + * Moved routines to free data structures related to MIME + content from mhparse.c and mhbuildsbr.c, to new file + mhfree.c. + + * Moved code to show/display MIME content into new + file mhshowsbr.c. + + * Moved code to store MIME content from into + new file mhstoresbr.c + + * Moved code to parse MIME content into new + file mhparse.c. + + * Moved code to list information about MIME content + into new file mhlistsbr.c. + + * Move part_ok(), type_ok(), content_error(), flush_errors(), + and set_endian() to new file mhmisc.c. + + * Start to isolate the code to show, list, and store MIME + messages. One side effect is that only one flag (-show, + -list, or -store) can be used at a time now. + + * mhn -store -auto wasn't storing file in correct directory. + + * Removed a few dead variables from sbr/ruserpass.c + + * move code for creating tmp files, and renaming the + the composition draft in mhbuild, from build_mime() + to main(). + + * remove left-over code in mhbuild.c, mhbuildsbr.c, for + the -[no]auto switch (which isn't used in mhbuild). + + * split mhn.c into mhn.c and mhnsbr.c (name later changed + to mhparse.c). + + * split mhbuild.c into mhbuild.c and mhbuildsbr.c. + +1998-05-25 Richard Coleman + + * Released nmh-0.26. + + * Added (unlisted) options [no]dashstuffing to send, post, + and whatnow to determine whether to do RFC934 quoting + (dashstuffing) for encapsulated BCC messages. The default + is still the same (dashstuffing). + + * Changed the undocumented feature "nodashmunging" in forw + and mhl, into the documented feature "nodashstuffing". The + default for forw, is still "dashstuffing" for backward + compatibility, although I don't believe that bursting + RFC934 digests is very common anymore. + + * Added an option to define REALLYDUMB in the default config.h. + But it is not on by default. + + * moved creation of config file mts.conf from zotnet/mts + to etc. This simplified the Makefile in zotnet/mts. + + * simplified directory support/general to etc. + + * removed unneeded directory support/bboards. + + * split getusername() into getusername() and getuserinfo(). + + * Changed getusr() routine to getusername(). + + * Slight cleanup in folder_pack.c on code that records the new + number of the "cur" message when packing. + +1998-05-08 Richard Coleman + + * Released nmh-0.25. + + * Change install process, so that hard linking the correct mts + library to libmts.a, is not necessary. The final link process + uses the original name of the library. + + * Fixed bug in flist.c and folder.c, so that symbolic links which + point to directories, will not decrement the number of directory + links remaining. + + * Split the function list_content (in mhn.c and mhbuild.c) into + list_content and list_debug. + + * Don't pack (folder -pack) an empty folder. + + * Exit gracefully in flist.c, if no sequence is specified, + and no "Unseen-Sequence" is given in nmh profile. + +1998-02-27 Richard Coleman + + * Released nmh-0.24. + + * Small clarification to the man page for `ali'. + + * Fix bug in inc.c so that if both flags `-file' and `-truncate' + are given, that order doesn't matter. + + * Fix bug in seq_list.c when realloc'ing for + large sequence line. + +1998-02-23 Richard Coleman + + * Released nmh-0.23. + + * Add new section on "Transfer Encodings" to man page for mhbuild. + + * In mhbuild.c, split compose_content into compose_content + (parse and execute composition string), and scan_content (scan content, + decided transfer encoding, check for clash with boundary string). + I did a good amount of rearranging of this code. + + * Moved definitions for data structures for parsing MIME + messages from mhn.c and mhbuild.c to a new include + file h/mhnsbr.h. + + * Small amount of rearranging in sendsbr.c + + * Small changes to MAIL.FILTERING file. + + * Add the file MAIL.FILTERING to nmh distribution. + + * Add line to packf so that if message begins with + "X-Envelope-From:" field, it is converted to "From ". + + * Fix packf to add "From " line to beginning of message, + even if Return-Path doesn't exist. + + * Add note to MACHINES file that on Linux, configure + doesn't find the functions sigsetjmp/siglongjmp. + + * Fix configuration for machines that don't have (or find) + sigsetjmp/siglongjmp. + +1998-02-11 Richard Coleman + + * Released nmh-0.22. + + * Add a configure check for sigsetjmp. Add some conditional + #define's in h/signals.h in case it's not found. + + * Added additional notes about -auto switch in mhn man page. + + * Added note about MM_CHARSET environment variable to + mh-profile(5) man page. + + * Fix signal problem in mhn.c (change setjmp/longjmp to + sigsetjmp/siglongjmp). + +1998-02-09 Richard Coleman + + * Released nmh-0.22-pre1. + + * Changed the first line in mhl.format from + " -- using template mhl.format -- " to a blank line. + + * Added note about automimeproc to mh-profile man page. + + * Reorganize the main entry point for parsing a MIME message + or file in mhn. Add new function parse_file() as new main + entry point for parsing MIME files. + + * Add note to mhn man page, that "mhn -file -" will accept the + source message on the standard input. + + * Changed a sanity check in folder_realloc that was too strict. + + * -norfc934mode is now the default for mhbuild, + rather than -rfc934mode. + + * Fix mhbuild, so that Content-Description and RFC-822 comments + from #forw directive will be correctly included if there is + only one message. + + * Change mhn to correctly default parts of multipart/digest to + message/rfc822 (leftover code from rfc934mode was removed). + + * Restore HP specific code to zotnet/tws/lexstring.c. Apparently + it is still needed. + +1998-02-06 Richard Coleman + + * Released nmh-0.21. + + * If the file given to mhbuild is "-", then accept the draft on + standard input, and output the MIME message to standard output. + + * Cleaned up code in mhbuild.c that decides what transfer + encoding to use. + + * Cleaned up code in mhbuild.c that decides what character set + to use for text contents. + + * Removed old hpux specific code from zotnet/tws/lexstring.c + +1998-02-02 Richard Coleman + + * Released nmh-0.21-pre2. + + * Added the "decode" variable to mhl.format and mhl.header. + + * Added new variable "decode" to mhlsbr.c to decode text in + header fields as per RFC-2047. + + * Make sure that when decoding RFC-2047 header fields, that any + spaces at the ends of the encoded text are not ignored, but the + spaces between encoded word are. + + * Removed #ifdef's for MIME. MIME support is always compiled in. + + * scan/inc will now decode both Subject and From lines as + RFC-2047 encoded header fields. + + * Added new function write_charset_8bit() to sbr. It returns + the character set to use for 8bit text in composition draft. + Changed mhbuild to use this function. + + * Split mhn man page into man pages for mhn and mhbuild. + + * mhn -show will only now only use default method for content + of type plain, if it is NOT a part of a multipart/alternative. + + * Split mhn -build into mhbuild. Did some code cleanup. + + * Added support for %(decode) to fmtdump.c. + + * check_charset() now accepts US-ASCII as a subset of any + ISO-8859-X character set. + + * Changed the default "showproc" to mhl, instead of the + pager more. + + * When reading file into mhn composition file, only need read + permissions, not write permissions. + + * Added own version of strcasecmp to distribution, since + nmh calls it frequently with NULL pointers (ughh). + + * Replaced uleq.c with strcasecmp. Removed uleq.c from + distribution. + +1998-01-22 Richard Coleman + + * Released nmh-0.21-pre1. + + * If a message is missing charset parameter to text/plain, show + will assume US-ASCII, rather than just calling showmimeproc. + + * Change show.c and mshcmds.c to use check_charset to see if text + message contains valid character set. + + * Added new scan format file "scan.nomime" to support/general + that doesn't do any RFC-2047 decoding. + + * Modified all the scan format files in support/general to do + RFC-2047 decoding of Subject field. + + * Did more work on sbr/fmt_rfc2047.c, so that it will correctly + ignore whitespace between two valid encoded words, but not + between an encoded word and normal text. + + * Created new file sbr/check_charset.c. Moved code from + fmt_rfc2047.c to check for valid character set to this file. + + * Added format escape %(decode) to decode contents of "str" register + as a RFC-2047 header field. + + * The command install-mh now recognizes the switches -version + and -help. + + * Added a new argument to print_help.c to decide whether to + print profile entries (needed for install-mh to prevent weird + loops). + + * Changed folder_read.c and folder_realloc.c so that mp->lowoff + is initialize to max (mp->lowmsg, 1) rather than always 1. + + * Changed macros for sequence/attribute manipulation so that + message status array doesn't need to always start at 1. + + * Small cleanups in folder_realloc(). + +1998-01-09 Richard Coleman + + * Released nmh-0.20. + + * Added configure option --with-pager=PAGER. + + * Added configure option --with-editor=EDITOR. + + * Changed the default format file for mhl (mhl.format) to + also ignore (not display) the header fields Content-Type, + Content-Transfer-Encoding, and Content-ID + + * Fixed core dump in addrsbr.c when using %(proper) format function + and the To: line was missing. + + * Added the file ZSH.COMPLETION to the distribution. + +1998-01-04 Richard Coleman + + * Released nmh-0.20-pre2. + + * Added new switch -snoop to both `msgchk' and `inc', so you can + watch the POP transaction. + + * Changed "replgroupcomps" to check for Mail-Followup-To header + first, and use it if available. + + * Changed "replcomps" to check for Mail-Reply-To header + first, and use it if available. + +1998-01-03 Richard Coleman + + * Released nmh-0.20-pre1. + + * Changed seq_list.c to dynamically enlarge the buffer for + collecting the message ranges in a long sequence line. + This should remove the last hard limit on the size of a + sequence line. + + * Changed seq_read.c so that can read long sequence lines. + It will use multiple calls to m_getfld() when m_getfld() + returns the state FLDPLUS. + + * Changed brkstring.c to dynamically add more space for pointers + if necessary. This is needed when splitting up large sequence + lines. + + * Did some small cleanups in seq_save.c. + + * Added new switches `-[no]unseen' to rcvstore, to control + whether new messages are added to Unseen-Sequence. + + * Moved locking routines (zotnet/mts/lock.c) to sbr/lock_file.c + + * Changed the internal UNSEEN flag to SELECT_UNSEEN which is + more appropriate. Changed the MHPATH flag to ALLOW_NEW. + + * Changed "replcomps" to not include CC and TO lines so that + that reply message is only directed at the author of the + message to which you are replying. + + * Added new switch `-group' to command repl, which causes repl + to use new forms file "replgroupcomps". This is intended for + making group replies. + + * Removed #ifdef for ATHENA. + +1997-12-28 Richard Coleman + + * Released nmh-0.19. + + * Fix repl,forw so that switch `-form file' will not abort + as ambiguious (silly mistake on my part). + + * Cleaned up the mhn man page. Added info about a few escapes + for the formatting/display strings that were not documented + (%%, %t). Moved the BNF grammar for the mime composition file, + to the end of the man page. + + * Added the options -[no]format to the command repl. The + switch `-format' will filter the message to which you are + replying with the standard message filter "mhl.reply", which + is now included in the distribution. The `-noformat' option + will negate the use of -format or -filter and not include + the message to which you are replying in the draft. + + * Did some cleaning and reorganization on many of the man + pages. + + * Added debugging switch `-debug' to mhparam, which displays + the values of all `procs' (and some other misc configuration + info) that nmh keeps in global variables. + + * When using `refile -preserve', if a conflict occurs, then use + the next available number above the message number you wish + to preserve. + + * In forw.c, split the code for creating MIME style forwarding + out of copy_draft, and into copy_mime_draft. + + * Move routines in mark.c to print sequences, into new + file sbr/seq_print.c + + * flist will now update the current folder. + + * Added the switches -[no]fast to flist, to replace + -[no]total. The previous switches are still accepted + but now undocumented. + + * More reorganization in flist of the code for + traversing folders. + + * The command "flist +foo -all" will now scan the folder + "foo" and all its 1st level children. + + * Add missing include file to sbr/snprintf.c + + * Fix alarm bug in rcvtty, so that when it calls external + process, the alarm is never longer than 30 minutes. + +1997-12-17 Richard Coleman + + * Released nmh-0.18. + + * Fixed bug in mark, so that "mark -list -seq foo" will + correctly indicate if "foo" is a private sequence. I found + this bug mentioned in Jerry Peek's book. + + * Simplified the code in seq_setcur(), since seq_addmsg() now + retains the public/private status of sequences. + + * Changed sequence handling so that if the switches -public + or -nopublic, are not specified for the commands mark, pick, + or rcvstore, then existing sequences will retain their + previous public/private status. + + * mhparam now handles the mh-sequences profile entry + correctly. + + * flist -all will now also check readonly folders (for + private sequences). + + * Improve the leaf optimization for folder command. + It will now track the number of directories in a folder, + and stop stat'ing files once it has hit all the subfolders. + + * Renamed m_getfolder to getfolder. Changed getfolder to + take option to determine whether it should get current + folder, or just default folder (Inbox). Changed rcvstore, + inc, and rmf to use the new getfolder. + + * flist now indicates if a sequence is private. + + * Change WUNTRACED to 0, in pidwait.c, so that commands will + wait for stopped processes. + + * conflict will dynamically allocate space for group names, + so it can now handle system with more than 100 groups. + +1997-12-09 Richard Coleman + + * Released nmh-0.18-pre4. + + * Check if we have enough message status space, before we + call folder_realloc() in burst, mhpath, and m_draft(). + + * mhn will now correctly identify a formatting string of "-" + for the option -store, and send content to stdout. + + * Change the way that memory for message status is + allocated. It is dynamcially allocated separately from + the folder/message structure. This required changing + folder_read.c, folder_realloc.c, folder_free.c. + + * Removed all the MTR code (experimental code for message + status allocation). + + * Renamed m_readfolder.c to folder_read.c and simplified + the code. + + * Renamed m_freefolder.c to folder_free.c. + + * Add function trim() to slocal.c to pretty print + the debugging output. + + * Changed the name of m_packfolder() to folder_pack(). + Changed the name of m_remsg() to folder_realloc(). + +Wed Dec 3 23:33:38 1997 Richard Coleman + + * Released nmh-0.18-pre3. + + * Changed installation to add `flists' which is hard linked + to `flist'. This is a equivalent to `flist -all'. + + * For flist, -showzero is on by default. + + * Major changes to flist. Default is now for flist to search + current folder. The switch `-all' is now used to specify + searching all top level folders. The new switch `-showzero' + is used to print out folders that don't contain any messages + in the given sequence. + + * Split BuildFolderList in flist.c into 2 functions + (BuildFolderList, BuildFolderListR). Changed these functions + so that flist now does better leaf optimization, and will stop + stat'ing directory entries when it knows it has hit all the + subdirectories of a given directory. + + * Reorganized code in folder.c, so that all relevant folders + are scanned first and information recorded. Then all the + folder summaries at printed out at one time. + + * Made the options of folder(s) more orthogonal. Now + "folder -all -noheader -nototal" will do the right thing. + + * Added `-noall' switch to folder, for completeness. + + * Changed the default mode for creation of new folders + to 0700 (was 0711). + + * Slightly changed the format for flist. It now indicates + if a folder is current. Also the width of the various + fields are now calculated at runtime. + + * Changed the format for folder(s). Folder names + are now left justified. The width of the various fields + are calculated at runtime. + +Sun Nov 30 19:14:53 1997 Richard Coleman + + * Released nmh-0.18-pre2. + + * Add paragraph to man page for install-mh and to INSTALL file + about checking for global mh.profile. + + * Renamed m_find() to context_find(). + Renamed m_replace() to context_replace(). + Renamed m_delete() to context_del(). + Renamed m_update() to context_save(). + Renamed m_getdefs() to context_read(). + Renamed m_foil() to context_foil(). + + * Change rcvstore to use routine folder_addmsg(), instead of + adding message to folder itself. + + * Changed refile, so that if the switch -preserve is used, + and a conflict occurs for a particular folder, then folder_addmsg() + will just use next highest available number for that folder, + instead of exiting. + + * Make folder_addmsg() more robust. It will make repeated + attempts to link file into folder if link returns with + the error EEXIST. + + * Fix bug, so that that if forking sendmail, HELO will be sent + unless clientname: option is defined but empty (so now it + is the same as the direct smtp code). + + * Changed sprintb to snprintb (now we pass the buffer length + to new routine). Changed code to use new function. + + * Added snprintf to sbr. Added configure check to build it + if you don't have a native version (but haven't changed much + code to use it yet). + +Thu Nov 13 18:42:18 1997 Richard Coleman + + * Released nmh-0.18-pre1. + + * Fixed alarm bug in slocal, so that alarm is never + called with a value larger than 30 mintues. + + * Fixed race condition in rmm and refile, so that + context is updated before external rmmproc is called. + + * Removed all the OVERHEAD code. + + * Move code to add message to folder from refile.c + to folder_addmsg.c + +Fri Jul 25 19:39:29 1997 Richard Coleman + + * Did some rearranging of the internals of inc.c. + + * Make -inplace the default for anno, forw, dist, and repl. + + * Changed --enable-smtp to --with-mts={smtp,sendmail} + + * Created new directory mts/sendmail for direct sendmail + interface (although it currently still uses SMTP). + + * Removed all the TMA (trusted mail agent) code + + * Removed all the TTYD (terminal access daemon) code + + * Removed all the MF (uucp filtering) code. + + * Removed all the code for BERK. + + * Removed all the code for stand-alone delivery (MHMTS). + + * Split the file mts/sendmail/smail.c into sendmail.c and + smtp.c. Changed the name of the directory to mts/smtp. + + * Changed autoconf to use @sysconfdir@ for location of + configuration files. + + * Changed #define in mhn.c from FTP to BUILTIN_FTP. + +Mon Jul 21 03:22:34 1997 Richard Coleman + + * Released nmh-0.17. + + * MAKEDEFS weren't passed down to recursive makes correctly. + + * slocal.c now checks for UTMP_FILE and _PATH_UTMP instead + of hard-coding "/etc/utmp". + + * rcvtty.c check for _PATH_UTMP if UTMP_FILE is not + defined. + + * Remove configure checks for ulong and ushort. Changed + code to just use unsigned {short, long}. + + * Change addmsg function in refile.c to return new + number of refiled message. + + * Added check in get_returnpath for empty unixbuf. + + * Cleanup of sbr/pidstatus to use more POSIX macros + for return value of wait(). + + * Change configure to also check /bin for "more". + +Sat Jul 12 00:02:23 1997 Richard Coleman + + * Released nmh-0.16. + +Mon Jun 23 20:13:24 1997 Richard Coleman + + * Added automimeproc, which should replace automhnproc. + + * multipart messages will no longer abort for messages + of type 8bit or binary (although we still can't really + deal with binary messages, yet). + + * Fix double free of c_storage. From John MacMillan. + + * mhn now treats unknown subtypes of "text" as text/plain. + + * mhn changed so that specifying mhn-show-multipart, or + mhn-show-multipart/{mixed, alternate, etc...) will override + the use of the internal method for displaying these types. + Previously mhn would always use the internal method for subtypes + mixed, alternate, digest, and parallel (even if an alternate + method was specified in mhn.defaults). + + * mhn show treats unknown subtypes of multipart, as type + multipart/mixed (as specified RFC2046). + + * mhn checks for the parameter "name" rather than "x-name". + From MH-6.8.4 patch. + + * Fix double free of ctinfo in user_content when using + #forw with single message. From John MacMillan (and + MH-6.8.4 patch). + + * Changed -mhnproc switch for show, to -showmimeproc. + + * Changed profile entry "mhnproc" to "showmimeproc". + + * Added "mime" option to "whatnow", which calls the program + "buildmimeproc" (default is mhn -build) to process MIME + composition files. + + * Added -build switch to mhn, to process MIME composition + files. + + * Did some reorganizing of mhn.c. + + * Changed casting in mts/sendmail/smail.c from (char) to + (signed char) so SMTP reply codes work correctly for machines + which used unsigned chars by default. + +Sat Jun 21 01:21:47 1997 Richard Coleman + + * Released nmh-0.15. + + * Added new form "scan.unseen" to distribution. It marks messages + which are in any sequence in Unseen-Sequence. + + * Do some rearranging of date/time code in zotnet/tws/dtime.c + + * Fix sign extension bugs in fmt_scan.c. + + * Fix m_atoi.c so that strings ending in non-digit characters + return 0. + + * Split code in burst.c so that finding delimiters of digested + messages and bursting a message into multiple messages are + two separate functions (find_delim and burst). + + * Add workaround fo AC_PATH_PROG in configure.in, so + that BSD4.4 machines can find sendmail, vi, more. + + * Added "-width" option to rcvtty. + + * Change a few variable names in zotnet/mts/client.c since + they conflict with defines on AIX. + + * Makefile in zotnet/tws assumes lexing of dtimep.lex was + unsuccessful if resulting file is less than 500 lines long + (rather than 10, which was previous value), since AIX + sed gives mangled file of about 200 lines. + + * Extract code in rcvstore.c to link message into folder, + and put in own subroutine. + + * Extract code in refile.c to link message into folder, + and put in own subroutine. + + * Moved code to remove messages from folder into own + routine "folder_delmsgs" in sbr. Changed rmm.c and + refile.c to use new routine. + +Fri May 16 06:09:31 1997 Richard Coleman + + * Renamed m_seqok to seq_nameok. + + * Changed m_setunseen, msh, mshcmds, flist, and scan to use + seq_getnum. + + * Changed m_seqflag to return the number of a sequence rather + than its bit flag. Changed its name to seq_getnum and renamed + file to sbr/seq_getnum.c. + + * Removed function m_seqnew and file sbr/m_seqnew.c since it is + no longer used. + + * Added zero switch to m_seqadd function to zero out bits before + adding message to sequence. + + * Renamed function m_setvis to m_setunseen, and renamed + corresponding file in sbr. + + * Renamed function m_setseq to m_setprev, and renamed corresponding + file in sbr. + + * Changed mark.c and pick.c to use m_seqaddsel and m_seqdelsel. + + * Added new function m_seqdelsel to m_seqdel.c, which deletes + all selected messages from a sequence. + + * Added new function m_seqaddsel to m_seqadd.c, which adds all + selected messages to a sequence. + + * Split sbr/m_seqnew.c into m_seqadd.c, m_seqdel.c, m_seqnew.c, + and m_seqok.c. + +Thu May 15 00:53:17 1997 Richard Coleman + + * Renamed function pack_folder to m_packfolder, and moved it + from uip/folder.c into its own file sbr/m_packfolder.c + +Wed May 14 23:38:00 1997 Richard Coleman + + * Changed function m_gmsg to m_readfolder. Renamed file + sbr/m_gmsg.c to sbr/m_readfolder.c. + +Mon May 5 19:57:11 1997 Richard Coleman + + * Expanded rcvtty man page, and added small patch from + MH-6.8.4 distribution. + +Fri May 2 15:24:34 1997 Richard Coleman + + * Released nmh-0.14. + + * Comment out configure test and code for tgetent to allocate its + own termcap buffer when passed a NULL argument. + +Sat Apr 26 03:46:38 1997 Richard Coleman + + * Added new options `-checkmime', `-nocheckmime', and `-mhnproc' + to show. Restructured code to handle options to various + `procs' better. Deprecated `-noshowproc' option and NOMHNPROC + environment variable. + + * Added new man page `mh-draft' which documents the + draft folder facility in nmh. + + * Renamed fmtsbr.h to fmt_scan.h. Renamed fmtcompile.h + to fmt_compile.h. + + * split fmtsbr.c into fmt_scan.c and fmt_new.c. Renamed + fmtcompile.c to fmt_compile.c, and formataddr.c to + fmt_addr.c. + + * `send -help' wasn't showing the -(no)mime and -split + options. + +Fri Apr 25 02:50:36 1997 Richard Coleman + + * Released nmh-0.13. + + * Changed mhpath so it doesn't abort if a message sequence + such as "mhpath all" expands to more than 1000 messages. + Also mhpath now dynamically reallocated space for message + names (The number of command line arguments is still limited + to MAXARGS). + + * Did some general restructuring of the code in folder.c + that checks for folder information, and prints it. + +Thu Apr 24 01:04:37 1997 Richard Coleman + + * Changed `folder' to reallocate space for folder names if + necessary. So `folders' can now handle more than 300 folders. + +Tue Apr 22 14:01:26 1997 Richard Coleman + + * Change configure to use a compile check to see if the tm struct + has tm_gmtoff, rather than using egrep. + +Mon Apr 21 02:19:17 1997 Richard Coleman + + * Released nmh-0.12. + + * Had set_exists and unset_exists macros backwards. + + * Released nmh-0.11. + +Thu Apr 10 02:39:53 1997 Richard Coleman + + * Added documentation to mh-profile.man about the various + `procs' (mhlproc, showproc, lproc, etc...). + + * Replace the bit twiddling for SELECTED, UNSEEN, and + mp->attrstats with macros. + + * If system doesn't have SIGEMT (like Linux), then use SIGTERM + in msh.c instead. + + * Change fstat to stat in m_gmsg.c since Linux wants + to hide dd->dd_fd. + + * Merge Linux patch sent in by Michel Oosterhof (original + patch from bsa@kf8nh.wariat.org). + + * Document an undocumented MH feature. mhn -form mhl.null + will suppress the display of the message header. + + * mhparam will now return "mhparam etcdir". + + * Add catproc to /config/config.c and use that in show.c + and mshcmds.c, rather than hard coding in /bin/cat. + + * Add mhnproc to the list of `procs' in mh-profile.man. + + * Add configure test for lorder and tsort commands. + + * Commented out the padding in the `msgs` struct in h/mh.h + + * Change m_gmsg.c to allocate elements to the `info' array by + 500 elements at a time (rather than MAXFOLDERS / 5). + + * Add note to man page for mhmail that zero length messages are + not sent. Need to use -body "" to send empty messages. + + * zotnet/mts/mts.c : compare character with '\0', not NULL. + + * sbr/getcpy.c : assign '\0' to character, not NULL. + + * add m_fmsg to most programs in uip so that they explicitly free + folder/message structure when done with folder. + + * uip/slocal.c : cleanup processing of sender. Make sure it is + defined even if message is missing "From " line. + +Mon Mar 31 03:37:35 1997 Richard Coleman + + * Released nmh-0.10. + +Sun Mar 30 21:46:17 1997 Richard Coleman + + * Add configure check for . Turn on LOCALE support + by default. + +Thu Mar 20 03:21:24 1997 Richard Coleman + + * Reversed previous decision to retain "From " lines in slocal. + The "From " line is now removed from all messages. + + * inc now saves the date from the "From " envelope in the + Delivery-Date header for all messages. + + * sbr/m_getfld.c: Clean up processing of Return-Path and + Delivery-Date from the "From " envelope. + +Mon Mar 17 19:03:36 1997 Richard Coleman + + * client.c: cast iaddr to int before comparing return value + of inet_addr with NOTOK. + +Tue Mar 11 04:38:10 1997 Richard Coleman + + * Grep test for signal names was failing on some OS'es because + of missing tabs in regex. + +Sat Mar 8 01:58:22 1997 Richard Coleman + + * Released nmh-0.09. + + * Move config files and format files to *.old before installing. + + * Add configure check for killpg. + + * msh.c: include instead of and + . + + * prompter.c: don't include anymore. + +Thu Mar 6 04:03:24 1997 Richard Coleman + + * Added `-mime' and `-nomime' options to `repl'. + From MH-6.8.4 diff. + +Tue Mar 4 03:10:37 1997 Richard Coleman + + * ruserpass.c : removed conflicting prototypes. + + * rcvtty.c : Fixed rcvtty to obey terminal permissions granted + by `mesg' command. Previously only worked on BSD machines. + +Mon Mar 3 00:18:59 1997 Richard Coleman + + * rcvtty.c : Changed to use #define UTMP_FILE (if exists) rather + than hard coded "/etc/utmp". + + * Released nmh-0.08. + + * Changed slocal to lock .maildelivery (or file given by -maildelivery) + when accessing ndbm/db file for duplicate suppression, instead of + locking database itself. + +Thu Feb 27 05:28:09 1997 Richard Coleman + + * Added slocal action `mmdf' to deliver to a file in mmdf format. + + * Changed the slocal actions `file' and `>' to always deliver in + mbox (uucp) format rather than be determined by RPATHS config + option. + + * Changed the slocal action `mbox' to deliver in mbox (uucp) format + rather than mmdf format. + + * slocal now adds Delivery-Date field to all messages (previously it + only added it to messages when delivering them to a file). The + "From " line is now retained on all messages if compiling with + RPATHS, rather than being discarded. + + * rcvpack no longer adds the Delivery-Date field to messages. + +Sun Feb 23 22:03:54 1997 Richard Coleman + + * Removed the script packmbox, since it's functionality has been + added to packf. + + * Changed packf so that it uses mbox (uucp) format by default + rather than mmdf format. Added options -mbox and -mmdf to + packf so you can choose the preferred format. + + * Changed rcvpack so that it uses mbox (uucp) format by default + rather than mmdf format. Added options -mbox and -mmdf to + rcvpack so you can choose the preferred format. + +Tue Feb 18 00:01:05 1997 Richard Coleman + + * Changed nmh to use dot locking by default (although you + can still easily change this in config.h). + + * Simplified locking code. Removed code allowing setting of + locking type in mts.conf. Now the locking type and locking + directory (if any) can only be set at compile time. + +Fri Feb 14 02:49:18 1997 Richard Coleman + + * Prefer getting timezone information from tm->gmtoff rather + than tzset and external timezone variable. + +Thu Feb 13 00:35:45 1997 Richard Coleman + + * Fixed typo in ruserpass.c in the variable toktabs. + + * When ruserpass was added to LIBOBJS, it was missing + the suffix. + + * Released nmh-0.07. + +Tue Feb 11 01:29:47 1997 Richard Coleman + + * Add check to configure, so that if ruserpass, or _ruserpass + is not found, build version of ruserpass in sbr. + + * Added define's to discard.c, m_getfld.c, and scansbr.c so + the code that manipulates internals of stdio, will build + on SCO 5.x. + + * Added #define to control whether to compile the simple + built-in FTP client in mhn. + + * Added configure check for ushort and ulong. Change code + to use ushort/ulong rather than u_short/u_long. + + * A couple of small cleanups in locking code. + + * Added configure check for gmtoff element in struct tm. + + * Added configure check for tzset. + +Fri Feb 7 03:01:57 1997 Richard Coleman + + * Released nmh-0.06. + + * Removed code for machines that don't have socket + interface (how could they get mail anyway?). + + * Removed code for BSD41 machines. I don't think there are + many such machines around anymore. + + * Add configure check for function uname, and prefer it + over gethostname. General cleanup of zotnet/mts/mts.c. + + * Change all `lseek' calls to use POSIX symbolic constants + SEEK_SET, SEEK_CUR, SEEK_END. + +Thu Feb 6 01:16:30 1997 Richard Coleman + + * Check lex generated file in zotnet/tws and use + pre-generated version if necessary. + + * Released nmh-0.05. + + * Change to use reliable signals on all platforms that have + sigaction. Change so that interrupted system calls are + restarted for all signals except SIGALRM. This fixes alarm + handling code in smail.c for BSD based systems. + + * Added lorder and tsort commands so that created libs can + be linked in one pass. + +Tue Feb 4 01:33:00 1997 Richard Coleman + + * Changed pidwait so that while it is waiting for a child, + it should block signals rather than ignore them. + +Mon Feb 3 21:05:30 1997 Richard Coleman + + * Add checks to configure for dbm_open and -lndbm. + +Thu Jan 30 05:15:42 1997 Richard Coleman + + * folder -pop and folder -push were freeing some memory too + quickly, which caused the entry popped from the stack to not + become the current folder. + +Wed Jan 29 01:28:02 1997 Richard Coleman + + * Released nmh-0.04. + + * Define ospeed and PC in termsbr.c is OS doesn't have + it. + +Sun Jan 26 20:25:10 1997 Richard Coleman + + * editfile will create a symbolic link to the altmsg if it + can't make a link, on any machine supporting lstat. Formerly + this would happen only on BSD42 based machines. + +Sat Jan 25 22:54:26 1997 Richard Coleman + + * traverse (in popsbr.c) wasn't calling va_start before using + variable argument list. Fixes core dump in inc when using POP. + +Fri Jan 24 03:27:59 1997 Richard Coleman + + * The variable pass in remotemail needed to be set to + NULL. (From MH-6.8.4 diff). Fixes core dump of msgchk when + using POP. + + * inc and msgchk were using -rpop by default when configured + with POP support. Default is now -norpop. + +Thu Jan 23 02:01:17 1997 Richard Coleman + + * By default, post will now give the SMTP HELO command with + the local hostname. If you specify a hostname with the + clientname: option in mts.conf file, post will give the + HELO command with that name instead. If the argument to the + clientname: option is empty, no HELO command is given. + (From the MH-6.8.4 diff) + +Wed Jan 22 01:55:45 1997 Richard Coleman + + * When using `-help' for a command, it will also print its + profile compents from .mh_profile. (From MH-6.8.4 diff) + + * "slocal -file" will now correctly takes its input from + a file (currently need to specify full path). + +Sun Jan 19 20:37:21 1997 Richard Coleman + + * "slocal -debug" will now issue a warning if a non-blank + line in the .maildelivery file has less than 5 fields. + +Sat Jan 18 02:26:41 1997 Richard Coleman + + * Changed slocal so that code for duplicate suppression + (MH config was MSGID) is always built. Added the options + -[no]suppressdup to slocal to turn this on/off. + +Thu Jan 16 00:26:34 1997 Richard Coleman + + * Released nmh-0.03. + + * Fixed problem where mark would core dump if no + .mh_sequence file existed. + + * Fixed problem where slocal would core dump if -debug + option was given, and certain headers were missing. + + * Added patch to slocal to add `folder' (+) action, which + is shorthand for piping message to rcvstore. Updated + man page. + +Wed Jan 15 21:30:17 1997 Richard Coleman + + * Changed flist option -unseen to -[no]all. Cleaned up + flist man page. + +Fri Jan 10 20:36:33 1997 Richard Coleman + + * Fixed flist. Changed the profile component `Folder-Order' + to `Flist-Order. Added option `-sequence' to flist, so + you can specify the name of the sequence to search for. + +Thu Jan 9 00:20:48 1997 Richard Coleman + + * A few minor portability cleanups. Changed to use PATH_MAX + rather than MAXPATHLEN. Don't assume ospeed variable exists + in termsbr.c. Removed some conflicting prototypes. + +Wed Jan 8 11:05:02 1997 Richard Coleman + + * Add configure test to check if tgetent will accept NULL + and allocate its own buffer. Borrowed from zsh. + + * Changed libpath to etcpath. + +Mon Jan 6 04:15:35 1997 Richard Coleman + + * Cleaned up source code and Makefiles, so that if your `make' + supports the VPATH option, you can build nmh in a different + directory from where the source code is located. + +Fri Jan 3 05:05:18 1997 Richard Coleman + + * Released nmh-0.02. + +Wed Jan 1 17:41:52 1997 Richard Coleman + + * Split mhook man page into man pages for rcvdist, rcvpack, + and rcvtty. + +Tue Dec 31 03:07:48 1996 Richard Coleman + + * Changed code to use strerror, rather than using sys_errlist + and sys_nerr directly. + +Mon Dec 30 02:15:25 1996 Richard Coleman + + * -compat switch from install-mh removed. + + * Changed the default POP port from "pop" to "pop3". + +Sat Dec 28 13:25:05 1996 Richard Coleman + + * Changed mhn_defaults to mhn.defaults. Changed create_mhn_defaults + (again) to mhn.defaults.sh. Changed find_program (again) to + mhn.find.sh. mhn.defaults.sh now takes the search path + as an argument. Default search path is now specified in Makefile + rather than in script. + +Fri Dec 27 16:34:01 1996 Richard Coleman + + * Changed mtstailor file to mts.conf. Updated man pages. + + * Changed si_value to si_val in mhn.c, since it conflicts with + macro defined on Solaris. + +Thu Dec 26 02:50:15 1996 Richard Coleman + + * Added --enable-nmh-mhe (and --disable-nmh-mhe) to enable/disable + support for Emacs front-end mhe. It is on by default. + + * Added the following configure options: --enable-nmh-pop to + enable client side pop support, --enable-nmh-smtp to enable + SMTP support. Client-side pop support now compiles. Man + pages for inc, msgchk, mh-chart now correctly added pop + options if enabled. + +Tue Dec 24 14:33:20 1996 Richard Coleman + + * Added configure test for bug in C libraries where linker + can't find ruserpass, but can find _ruserpass. + + * Fixed configure test so that termcap variable ospeed is + correctly found. + +Mon Dec 23 19:40:17 1996 Richard Coleman + + * Source files converted to ANSI C. + + * md5 now compiled separately rather than being included + in mhn.c. Changed md5 to use memset and memcpy. + +Fri Dec 20 02:29:37 1996 Richard Coleman + + * Collected the error routines adios, advise, admonish, and advertise + into one file (error.c), and did some rearranging of the code. + +Thu Dec 19 19:05:29 1996 Richard Coleman + + * Added awk script sigmsg.awk (originally written by + Geoff Wing for zsh) to + automatically generate signal messages for pidstatus.c. + Added files sbr/signals.c, h/signals.h. Code now uses + sigprocmask to block signals (if available). Code now uses + signal blocking on non-BSD machines. + +Wed Dec 18 01:55:17 1996 Richard Coleman + + * Add configure check for ATTVIBUG. From Soren's mh autoconf work. + + * Released nmh-0.01. + + * Added configure code to check for type of signals functions + you have (POSIX or BSD style signals). Added function + SIGPROCMASK to simulate sigprocmask on machines that don't + have POSIX signals. + +Fri Dec 13 19:40:48 1996 Richard Coleman + + * Added -version switch to all commands. Also added to + their man pages. + +Mon Dec 9 16:36:54 1996 Richard Coleman + + * Renamed uip/trmsbr.c to termsbr.c and changed it to use + POSIX termios.h style functions if present. + +Tue Dec 3 16:18:39 1996 Richard Coleman + + * Changed support/general/bootmhn.sh to output new mhn_defaults + file to standard output by default (makes it easier for testing). + Changed name of script to create_mhn_defaults. Changed bootmhn.findit + script to find_program. + +Sun Dec 1 10:00:00 1996 Richard Coleman + + * Added patch to uip/folder.c from exmh distribution to + speed up -recurse option. + + * Added flist command from exmh distribution. It doesn't work + yet, but it compiles :-) + + * Changed default location for install to /usr/local/nmh/{bin,etc,lib,man}. + Split files so that format and configuration files go in nmh/etc, and + support binaries go in nmh/lib. Of course, all this can now be changed + in the top level Makefile. + + * Started with mh-6.8.3 as based and converted to autoconf. + Rewrote all the Makefiles. Currently only works with sendmail/smtp. + Pop support and plenty of other things, are now broken. diff --git a/DIFFERENCES b/DIFFERENCES new file mode 100644 index 0000000..1a24037 --- /dev/null +++ b/DIFFERENCES @@ -0,0 +1,278 @@ + +The following are the differences between nmh and MH-6.8.3. UCI has +since released MH-6.8.4. Most of the new features it adds have +also been added to nmh, but those differences are not listed here. +There are a few new features in MH-6.8.4 that are missing from nmh, +but they are primarily undocumented in MH-6.8.4 (and no one has +ever asked me for them). + +GENERAL +------- +*) nmh has been converted to autoconf (configure) and should be + more portable and easier to install than MH. In particular, nmh + will now compile on Linux. +*) All of MH's Makefiles have been rewritten for nmh. You can now + use GNU make without any problems. Also, if your make supports + the VPATH variable (such as GNU make), you can now compile in a + different directory from the one containing the source code. +*) The source code for nmh has been substantially cleaned up. + It now requires an ANSI C compiler (gcc is fine) to compile. +*) A new option `-version' has been added to all the commands. +*) The POP server (popd) has been removed from the distribution. + But the client-side support for POP and KPOP is still present. + Also nmh doesn't currently support some of the alternate forms of + POP such as APOP or RPOP that are contained in MH (although they + could easily be resurrected, if necessary). +*) The support for MH-style bulletin boards has been removed + (NNTP makes this obsolete anyway). +*) Currently nmh doesn't support using shared libraries for libmh. + This may return in the future, but is not a high priority, since + the nmh commands are not that large, and disk space gets cheaper + every day. +*) Almost all of the commands in nmh have been modified to accept + an arbitrary number of command line arguments (most MH commands + will not accept more than 1000 arguments on the command line). +*) nmh should be more secure than MH. Hundreds of buffer overflow + problems have been fixed in the source code. Of course, I still + don't make any guarantees. + +DOCUMENTATION +------------- +*) Many of the man pages have been cleaned up or clarified. +*) The mhook man page has been split into separate man pages for + rcvtty, rcvdist, and rcvpack. +*) Added new man page `mh-draft' which discusses the nmh draft + folder facility. +*) The various `procs' (rmmproc, moreproc, showmimeproc, etc...) + are now documented in the "mh-profile" man page. Many of these + were previously undocumented. + +FORMATTING +---------- +*) Added a new formatting string escape %(decode) to decode and + display RFC-2047 encoded header fields. + +SEQUENCES +--------- +*) The is no longer a limitation on the length of lines in the file + containing your public sequences (.mh_sequences). That should be + the end of the error message ".mh_sequences is poorly formatted". + +ANNO +---- +*) The switch -inplace is now on by default. + +CONFLICT +-------- +*) Conflict now works on systems that define more + than 100 groups. + +DIST +---- +*) The switch -inplace is now on by default. + +FLIST +----- +*) A new command `flist' has been added to nmh, that will list the + folders that contain messages in a given sequence (such as the + unseen sequence). This was a much needed command in the MH suite + of programs. + +FOLDER +------ +*) `folder -all' now dynamically allocates space for folder names and can + handle more than 300 folders. +*) `folder' now uses the standard Unix trick of looking at the number of + links to a directory entry, in order to avoid doing a stat(2) call + on messages in folders with no subfolders. This greatly increases + the speed of `folder -all -recurse' on large mail setups. +*) The switches `-header' and `-total' are more orthogonal. The command + `folder -all -noheader -nototal' now does the right thing. + +FORW +---- +*) The switch -inplace is now on by default. +*) Added switches `-dashstuffing' and `-nodashstuffing', to determine + whether forwarded messages are RFC934 quotes (dashstuffed). + (This corresponds to the undocumented switch "nodashmunging" + in MH). + +INC +--- +*) If compiled with RPATHS, a Delivery-Date header is now added to all + messages incorporated with `inc'. +*) Using the new format string escape %(decode), the scan lines for `inc' + will correctly decode RFC-2047 encoded headers. + +MARK +---- +*) If neither of the switches -public/-nopublic are specified, then + existing sequences will retain their current public/private status, + instead of being changed to public. +*) The command "mark -list -sequence foo" will now indicate if the + sequence "foo" is a private sequence. + +MHBUILD +------- +*) The functionality in `mhn' to create MIME messages, has been cleaned + up substantially, and moved to separate command `mhbuild'. +*) If given a file argument of "-", mhbuild will now accept the MIME + composition file on the standard input, and output the new MIME + message to the standard output. This makes it much easier to use + this functionality in shell scripts. +*) The switch -norfc934mode is now the default. + +MHL +--- +*) There is a new variable "decode" which instructs `mhl' to decode + a component as a RFC-2047 encoded header field. + +MHLIST +------ +*) The functionality of `mhn -list' has been moved to the new + command `mhlist'. + +MHN +--- +*) mhn is now obsolete. It has been split into the commands mhbuild, + mhlist, mhshow, mhstore, and viamail. mhn still exists for + backward compatibility, but the new commands should be preferred. +*) Changed the profile entry automhnproc to automimeproc + (which has slightly different semantics). + +MHPATH +------ +*) `mhpath all' will no longer abort if the folder has more than + 998 messages. + +MHSHOW +------ +*) The functionality of `mhn -show' has been moved to the new + command `mhshow'. +*) mhshow now correctly treats unknown subtypes of text as + text/plain, as specified by RFC-2046. +*) mhshow now correctly treats unknown subtypes of multipart as + multipart/mixed, as specified by RFC-2046. +*) You can now override the default method by which mhshow handles + subtypes of multipart that are known internally (mixed, alternate, + etc...). Previously the behavior on these types could not be + changed. + +MHSTORE +------- +*) The functionality of `mhn -store' has been moved to the new + command `mhstore'. +*) mhstore will now correctly identify a formatting string of "-" + and send the content to stdout. + +PACKF +----- +*) When packing a folder, the default format is now `mbox' format, rather + than `mmdf' format. The options -mbox and -mmdf have been added to + `packf' so you can choose the desired format. + +PACKMBOX +-------- +*) The script packmbox has been removed from the nmh distribution, since + its functionality has been added to the command packf. + +PICK +---- +*) If neither of the switches -public/-nopublic are specified, then + existing sequences will retain their current public/private status, + instead of being changed to public. + +RCVPACK +------- +*) The default format for `rcvpack' is now `mbox' format, rather than + `mmdf' format. The options -mbox and -mmdf have been added to + `rcvpack' so you can choose the desired format. +*) Rcvpack no longer adds the field "Delivery-Date", as that is added + by `slocal'. + +RCVSTORE +-------- +*) Added new switches -unseen/-nounseen to control whether new messages + are added to the Unseen-Sequence. + +RCVTTY +------ +*) The option `-width' has been added. + +REFILE +------ +*) If an conflict occurs when using the `-preserve' switch, + then refile will search for and use the next available + message number above the one you wish to preserve, rather + than aborting with an error. +*) Added new options `-unlink' and `-nounlink' which specify + that that messages "removed" from the source folder should + just be unlinked, rather than moved to name with prefix. + +REPL +---- +*) Added new options `-format' and `-noformat'. The switch `-format' + will filter the message to which you are replying with a standard + message filter "mhl.reply" which is included in the distribution. + The switch `-noformat' will negate `-format' and `-filter', so that + the message to which you are replying is not included in the draft. +*) Added new options `-group' and `-nogroup'. These switches direct + repl as to whether or not this is a group reply. A group reply uses + a different forms (components) file (default is replgroupcomps). +*) The standard forms files "replcomps" and "replgroupcomps" now have + support for the "Mail-Reply-To:" and "Mail-Followup-To:" header fields. +*) The switch -inplace is now on by default. + +RMM +--- +*) Added new options `-unlink' and `-nounlink' which specify + that that "removed" messages should just be unlinked, rather + than moved to name with prefix. + +SCAN +---- +*) Using the new format string escape %(decode), the scan lines created + by `scan' will correctly decode RFC-2047 encoded headers. + +SHOW/NEXT/PREV +-------------- +*) Added new options `-checkmime' and `-nocheckmime' which allow you + to enable and disable the test for MIME messages. +*) The "mhnproc" profile entry has been changed to "showmimeproc". +*) Added `-showmimeproc' switch to specify the showmimeproc at the + command line. +*) The default "showproc" has been changed to `mhl'. +*) The default "showmimeproc" is now `mhshow'. +*) `show' is better at handling alternate character sets. It knows that + US-ASCII is a subset of any ISO-8859-X character set. + +SLOCAL +------ +*) Added new action `folder' or `+' to slocal, which adds new message + to a nmh folder. Currently, this is handled by piping the new + message to rcvstore, although this may change in the future. +*) The action `mbox' now delivers to a file in mbox format. Previously + it delivered to a file in mmdf format. +*) Added new action `mmdf' to deliver to a file in mmdf format. +*) Added new options -[no]suppressdup to slocal to check for duplicate + messages. The code for suppression of duplicate messages (MH config + was MSGID) is now always built into slocal. +*) Improved the debugging of slocal ".maildelivery" files. It will now + warn when an entry in this file is skipped because there are not + enough fields. Also the debugging output of slocal has been cleaned up, + so that it is much easier to read. +*) Slocal now adds the Delivery-Date header to all delivered messages. + Previously it only added them to messages delivered to a file. + +VIAMAIL +------- +*) The functionality of this new command, was formerly part of + `mhn' as the (undocumented) option `mhn -viamail'. + +WHATNOW +------- +*) Added new action "mime" to whatnow, which causes whatnow to call + program specified by "buildmimeproc" profile entry, to process + MIME composition files (default is mhbuild). +*) Added new action "delete" to whatnow, which deletes draft file + and exits. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..92bffa2 --- /dev/null +++ b/INSTALL @@ -0,0 +1,229 @@ +# +# INSTALL -- installation instructions +# +# $Id$ +# + +-------------- +Installing nmh +-------------- +Please read all of the following instructions before you begin +building nmh. + +You should check the MACHINES file to see if there are any specific +build instructions for your operating system. To build nmh, you will +need an ANSI C compiler such as gcc. + +1) Run the command + + sh configure [options] + + This will check the configuration of your OS, and create + the include file config.h, as well as the various Makefiles. + + The configure script accepts various options. The options of + most interest are listed below. To see the list of all available + options, you can run + + sh configure --help + +2) (IMPORTANT) Edit the user configuration section at the beginning + of the generated include file `config.h'. Currently, not everything + is auto-configured, so some #defines must be set manually. + +3) Edit the user configuration section at the top of the main Makefile. + +4) make + +5) make install + +6) Edit the file `mts.conf' (installed in the nmh `etc' directory) + and make any necessary changes for the mail transport interface + you are using. + + The default `mts.conf' file assumes you retrieve new mail from + a local (or NFS mounted) maildrop, and send outgoing mail by + injecting the message to a mail transfer agent (such as sendmail) + on the local machine via SMTP. + + If you have enabled POP support and you want this to be the + default method of accessing new mail, you will need to change + the values of the variables "servers", "pophost", "localname", + and possibly "mmailid". + + a) "servers" defines the server to which you send outgoing SMTP + traffic. + + b) "pophost" defines the server that runs the POP daemon, and to + which `inc' and `msgchk' will query for new mail. + + c) "localname" defines the hostname that nmh considers local. + If not set, then nmh queries your OS for this value. You may + want to change this if you wish your e-mail to appear as if it + originated on the POP server. + + d) "mmailid" is checked to see if nmh should do username + masquerading. If the value of this field is non-zero, then + nmh will check if the pw_gecos field in the password file + has the form + + Full Name + + If the pw_gecos field has this form, then the internal nmh + routines that find the username and full name of a user will + return "fakeusername" and "Full Name" respectively. This is + useful if you wish messages that you send to appear to come + from the username of your POP account, rather than your username + on the local machine. + + If you compile with POP support, but only want to use it occasionally, + then you can always use the `-host' and `-user' options to `inc' + and `msgchk' instead of changing `mts.conf'. + + Check the `mh-tailor' man page for a list of all the available + options for this file. + +7) If you have enabled POP support, make sure that `pop3' (or more + precisely the value of the define POPSERVICE in config.h) is defined + in the /etc/services file (or its NIS/NIS+ equivalent) on the client + machine. It should be something equivalent to "110/tcp". This might + have already been done when the pop daemon was installed. + +8) Edit the file `mhn.defaults' (installed in the nmh `etc' directory). + This file contains the default profile entries for the nmh command + `mhn' and is created by the script `mhn.defaults.sh'. This script + will search a generic path (essentially your $PATH) for programs to + handle various content types (for example, xv to display images). + You can re-run this script and give it a more tailored path. You may + want to re-run this script later if you install new programs to + display content. An example of this is: + + cd support/general + ./mhn.defaults.sh /usr/local/bin:/usr/X11/bin:/usr/ucb > mhn.defaults + + and then move `mhn.defaults' into the nmh `etc' directory. + + The `mhn.defaults.sh' script only searches for a simple set of programs. + If you have specialized programs to handle various types, you will need + to edit the `mhn.defaults' file manually. The syntax of this file is + described in the man page for `mhn', and in section 9.4 of the book + "MH & xmh: Email for Users and Programmers", 3rd edition, by Jerry Peek. + +9) Add an optional global mh.profile, if desired. This profile should be + placed in the nmh `etc' directory with the name `mh.profile'. This + file will be used to construct the initial .mh_profile of a new nmh + user, but will not be consulted after that. + +----------------------------------------------- +Compiler options, or using a different compiler +----------------------------------------------- +By default, configure will use the "gcc" compiler if found. You can use a +different compiler, or add unusual options for compiling or linking that +the "configure" script does not know about, by either editing the user +configuration section of the top level Makefile (after running configure) +or giving "configure" initial values for these variables by setting them +in the environment. Using a Bourne-compatible shell (such as sh,ksh,zsh), + +you can do that on the command line like this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the "env" program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +---------------------------------------- +Building nmh on additional architectures +---------------------------------------- +To build nmh on additional architectures, you can do a "make distclean". +This should restore the nmh source distribution back to its original +state. You can then configure nmh as above on other architectures in +which you wish to build nmh. Or alternatively, you can use a different +build directory for each architecture. + +--------------------------------- +Using a different build directory +--------------------------------- +You can compile the nmh in a different directory from the one containing +the source code. Doing so allows you to compile it on more than one +architecture at the same time. To do this, you must use a version of +"make" that supports the "VPATH" variable, such as GNU "make". "cd" to +the directory where you want the object files and executables to go and +run the "configure" script. "configure" automatically checks for the +source code in the directory that "configure" is in. For example, + + cd /usr/local/solaris/nmh + /usr/local/src/nmh-1.0/configure + make + +--------------------- +Options for configure +--------------------- +--prefix=DIR (DEFAULT is /usr/local/nmh) + This will change the base prefix for the installation location + for the various parts of nmh. Unless overridden, nmh is installed + in ${prefix}/bin, ${prefix}/etc, ${prefix}/lib, ${prefix}/man. + +--bindir=DIR (DEFAULT is ${prefix}/bin) + nmh's binaries (show, inc, comp, ...) are installed here. + +--libdir=DIR (DEFAULT is ${prefix}/lib) + nmh's support binaries (post, slocal, mhl, ...) are installed here. + +--sysconfdir=DIR (DEFAULT is ${prefix}/etc) + nmh's config files (mts.conf, mhn.defaults, ...) are installed here. + +--mandir=DIR (DEFAULT is ${prefix}/man) + nmh's man pages are installed here. + +--with-mts=MTS (DEFAULT is smtp) + specify the mail transport system you want to use. The two + acceptable options are "smtp" (which is the default), and + "sendmail". + + If you use "smtp", this will enable a direct SMTP (simple + mail transport protocol) interface in nmh. When sending + mail, instead of passing the message to the mail transport + agent, `post' will open a socket connection to the mail + port on the machine specified in the `mts.conf' file + (default is localhost), and speak SMTP directly. + + If you use "sendmail", then `post' will send messages by + passing forking a local copy of sendmail. Currently it + will still speak SMTP with this local copy of sendmail. + + If you wish to use a transport agent other than sendmail, you will + need to use a `sendmail wrapper'. + +--with-editor=EDITOR (DEFAULT is vi) + specify the full path of the default editor to use. If this + option is not given, then the configuration process will search + for the `vi' command and use it as the default. If you wish to + specify an interface which is compatible with MH, then use the + nmh command `prompter'. If you specify `prompter', then you don't + need to give the full pathname. + +--with-pager=PAGER (DEFAULT is more) + specify the default pager (file lister) to use. If this option + is not given, then the configuration process will search for the + command `more' and use it as the default. + +--enable-nmh-mhe (DEFAULT) + Add support for the Emacs front-end `mhe'. + +--enable-nmh-pop + Enable client-side support for pop. + +--with-krb4=PREFIX + Specify the location of Kerberos V4 for KPOP support. You will + also need to specify the option `--enable-nmh-pop'. After running + configure, you will probably need to change the POPSERVICE define + in config.h. See the comments inside config.h for details. + +--with-hesiod=PREFIX + Specify the location of Hesiod. + +--enable-nmh-debug + Enable debugging support. + +-- +Richard Coleman +coleman@math.gatech.edu diff --git a/MACHINES b/MACHINES new file mode 100644 index 0000000..71f5362 --- /dev/null +++ b/MACHINES @@ -0,0 +1,92 @@ +# +# MACHINE -- operating system specific information +# +# $Id$ +# + +-------------------------------------- + +FreeBSD: +OpenBSD: +NetBSD: + +Some BSD4.4 machines have problems when running nmh's configure script. +They will be unable to find the location of vi and sendmail. This is +due to POSIX features (breakage?) in the shell sh. The solution is to +run the configure script under the shell `bash' + +bash configure + +-------------------------------------- + +HPUX: + +Lots of problems have been reported with using HPUX `cc'. In particular, +problems with `scan' giving incorrect dates (everything is 01/00). +It is highly recommended that you use `gcc' instead. + +Also, new versions of HPUX (10.20?) will core dump in `scan' because +of some workaround code in zotnet/tws/lexstring.c. This workaround is +needed for older versions of HPUX, but causes problems on newer versions. +The solution is the added the line + +#undef hpux + +after line 15 of the file zotnet/tws/lexstring.c. + +-------------------------------------- + +Irix (SGI): + +If you are compiling nmh with POP support, then the configuration +process will search for (and find) the Irix version of "ruserpass". +Unfortunately, this version is buggy and causes core dumps. The best +bet is to use the version that comes with nmh. After running configure, +edit the Makefile in the "sbr" directory, and add "ruserpass.o" to the +LIBOBJS line. Then run "make" as normal. + +-------------------------------------- + +Linux: + +Make sure you uncomment the Linux section in the config.h file after +running configure. + +The configuration script does a test to discover the functions +sigsetjmp/siglongjmp. Since they are macros on Linux, the configuration +process doesn't find them. After running configure, you should change +the line in config.h to define HAVE_SIGSETJMP. + +For some Linux distributions, the configure script doesn't find the +ndbm/gdbm library (dbm_open, dbm_close). In this case, you should try to +configure nmh like this: + + LIBS=-lgdbm ./configure [configure options] + +The configuration script does a test to discover if your vi is broken +(if it reports non-zero exit codes on certain pseudo-errors). This test +will hang if the program `ex' on your system is a link to the vi clone +`vile'. The workaround is to replace the command ex as a link to another +vi clone such as nvi or elvis. + +-------------------------------------- + +SCO: + +Make sure you uncomment the SCO section in the config.h file after +running configure. + +-------------------------------------- + +Solaris: + +Builds ok. + +-------------------------------------- + +SunOS 4.1.3: + +You can't use the C compiler that comes with SunOS 4.1.3 since +it isn't ANSI C. But nmh builds just fine with gcc. + +-------------------------------------- diff --git a/MAIL.FILTERING b/MAIL.FILTERING new file mode 100644 index 0000000..becf31a --- /dev/null +++ b/MAIL.FILTERING @@ -0,0 +1,102 @@ + +INTRODUCTION +------------ +It is a common practice when using nmh to filter your inbound mail +directly into nmh folders. There are several programs which allow you +to do this, of which two common ones are procmail and slocal. + +SLOCAL +------ +The slocal command is part of the nmh distribution. It is a fairly +simple mail filtering program. Check the slocal man page for an example +filtering file (called .maildelivery). + +PROCMAIL +-------- +Probably the most popular mail filtering command is procmail. It can +filter mail into standard mbox-style spool files, as well as into MH/nmh +style folders. + +Although procmail knows how to put a message directly into an nmh folder, +this is not recommended. Procmail doesn't know about nmh sequences. +Instead you should have procmail use the nmh command `rcvstore' to put +the message into the folder. The `rcvstore' command will (by default) +add each new message to the "unseen" sequence, so you can detect new +messages in folders with the `flist' command. + +Also, nmh commands generally like to keep mail messages in RFC-822 +format. But by default, procmail will leave the first line of the +message unchanged. This line (which usually begins with "From ") is +not in the standard RFC-822 format. It is recommended that you use the +command `formail' (which comes in the procmail distribution) to rewrite +this line so that it begins with the header name "X-Envelope-From:". +An example of how to do this is given below. + +The reason the header name "X-Envelope-From:" is recommended, is that the +nmh command `packf' (as of version 0.23) will check for this header when +packing folders. The `packf' command knows how to undo the rewriting +of the "From " line to the "X-Envelope-From:" line. By checking for +this header name, `packf' is able to pack the folder into exactly the +form that is used if procmail delivers to the standard mail spool. + +If you do not rewrite the "From " line into this format, the `packf' +command will still work. But it may create fake "From " lines which +are not the same as the originals. + +Here is a typical .procmailrc file for using procmail in conjunction +with nmh. For more information, see the manual pages for procmail, +procmailrc and procmailex. + +################################################################### +# .procmailrc +################################################################### +# To use procmail, put the next line in your .forward file: +# "|IFS=' ' && exec /usr/local/bin/procmail -f- || exit 75 #XXX" +# Do not remove the double quotes. Change XXX to your username. +# Edit path to procmail above, and the VARIABLES below, as needed. +# Adapt the MAILING LIST section below for lists you subscribe to. +# Your .forward needs to be world-readable, but not world-writable. +################################################################### +# This .procmailrc is written for use with nmh/mh/exmh/mh-e +################################################################### + +### VARIABLES ### +VERBOSE=off +SHELL=/bin/sh +PATH=/usr/local/nmh/lib:/usr/local/nmh/bin:/usr/bin:/usr/local/bin +MAILDIR=$HOME/Mail +LOGFILE=$MAILDIR/procmail.log +LOCKEXT=.lock + +################# +# CLEANUP MESSAGE +################# + +# Force the "From user date" to become part of header +:0 Whf +| formail -z -R 'From ' X-Envelope-From: + +############### +# MAILING LISTS +############### + +:0 w: nmh-workers/$LOCKEXT +* ^Resent-from: *nmh-workers +| rcvstore +nmh-workers + +# catches exmh-{announce,users,workers} +:0 w: exmh/$LOCKEXT +* ^TOexmh +| rcvstore +exmh + +# Catch junk. Don't add it to "unseen" sequence (nmh only) +:0 w: junk/$LOCKEXT +* ^(reply-to|from|sender):.*(spammer|flamer|evil-host) +| rcvstore -nounseen +junk + +################ +# DEFAULT ACTION +################ +:0 w: inbox/$LOCKEXT +| rcvstore +inbox + diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..97313fc --- /dev/null +++ b/Makefile.in @@ -0,0 +1,187 @@ +# +# Makefile for top level of nmh distribution +# +# $Id$ +# + +# nmh version +VERSION = @VERSION@ + +SHELL = /bin/sh +@SET_MAKE@ + +srcdir = @srcdir@ +VPATH = @srcdir@ + +# ========== USER CONFIGURATION SECTION ========== +# +# If `make' is executed in the directory containing this Makefile, +# any changes made in this section will override the values of +# these parameters in makefiles in any of the subdirectories. + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# location of standard commands +bindir = @bindir@ + +# location of support binaries and scripts +libdir = @libdir@ + +# location of nmh configuration and formats files +etcdir = @sysconfdir@ + +# location of man pages +mandir = @mandir@ + +# location of incoming mail +mailspool = @mailspool@ + +# location of mail transport agent +sendmailpath = @sendmailpath@ + +# default editor +default_editor = @editorpath@ + +# default pager +default_pager = @pagerpath@ + +CC = @CC@ +CPPFLAGS = @CPPFLAGS@ +DEFS = @DEFS@ +CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBS = @LIBS@ + +# ========== YOU SHOULDN'T HAVE TO CHANGE ANYTHING BELOW HERE ========== + +# flags passed to recursive makes in subdirectories +MAKEDEFS = CC='$(CC)' CPPFLAGS='$(CPPFLAGS)' DEFS='$(DEFS)' \ +CFLAGS='$(CFLAGS)' LDFLAGS='$(LDFLAGS)' LIBS='$(LIBS)' \ +prefix='$(prefix)' exec_prefix='$(exec_prefix)' bindir='$(bindir)' \ +etcdir='$(etcdir)' libdir='$(libdir)' mandir='$(mandir)' \ +mailspool='$(mailspool)' sendmailpath='$(sendmailpath)' \ +default_editor='$(default_editor)' default_pager='$(default_pager)' + +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ + +.SUFFIXES: + +# all files in this directory included in the distribution +DIST = README INSTALL MACHINES COPYRIGHT VERSION DIFFERENCES FAQ \ + TODO ZSH.COMPLETION MAIL.FILTERING ChangeLog install-sh \ + mkinstalldirs Makefile.in aclocal.m4 acconfig.h config.h.in \ + configure.in configure stamp-h.in + +# subdirectories in distribution +SUBDIRS = h config sbr zotnet mts uip etc man + +# ========== DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +# default target +all: config.h Makefile all-recursive + +all-recursive: + for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) all) || exit 1; \ + done + +install uninstall: + for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) $@) || exit 1; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: mostlyclean-recursive mostlyclean-local +clean: clean-recursive clean-local +distclean: distclean-recursive distclean-local +realclean: realclean-recursive realclean-local +superclean: superclean-recursive superclean-local + +mostlyclean-local: + rm -f *~ + +clean-local: mostlyclean-local + +distclean-local: clean-local + rm -f Makefile config.h config.status config.log config.cache stamp-h distname + +realclean-local: distclean-local + +superclean-local: realclean-local + cd $(srcdir) && rm -f config.h.in stamp-h.in configure + +mostlyclean-recursive clean-recursive distclean-recursive realclean-recursive superclean-recursive: + for subdir in $(SUBDIRS); do \ + target=`echo $@ | sed 's/-recursive//'`; \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) $$target) || exit 1; \ + done + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +Makefile: Makefile.in config.status + CONFIG_FILES=$@ CONFIG_HEADERS= ./config.status + +config.status: configure VERSION + ./config.status --recheck + +configure: configure.in aclocal.m4 + cd $(srcdir) && autoconf + +config.h: stamp-h +stamp-h: config.h.in config.status + CONFIG_FILES= CONFIG_HEADERS=config.h ./config.status + +config.h.in: stamp-h.in +stamp-h.in: configure.in acconfig.h aclocal.m4 + cd $(srcdir) && autoheader + echo > $@ + +# rebuild all autoconf files +reset: + cd $(srcdir) && autoheader + cd $(srcdir) && autoconf + cd $(srcdir) && echo > stamp-h.in + +# name of new nmh distribution tar file +tarfile = nmh-$(VERSION).tar.gz + +# ftp directory location +ftpdir = /ftp/nmh + +# file containing name of new nmh distribution +distname: + @echo nmh-$(VERSION) > distname + +# build nmh distribution +distdir = `cat distname` +nmhdist: $(DIST) distname + @echo "Begin building nmh-$(VERSION) distribution" + rm -rf $(distdir) + mkdir $(distdir) + @chmod 755 $(distdir) + @echo "Copying distribution files in main directory" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + @for subdir in $(SUBDIRS); do \ + mkdir $(distdir)/$$subdir; \ + chmod 755 $(distdir)/$$subdir; \ + (cd $$subdir && $(MAKE) $@) || exit 1; \ + done + chmod -R a+r $(distdir) + tar chf - $(distdir) | gzip -c > $(tarfile) + rm -rf $(distdir) distname + @echo "Done building nmh-$(VERSION) distribution" + +# release a new nmh distribution into ftp directory +nmhrelease: + rm -f $(ftpdir)/$(tarfile) + rm -f $(ftpdir)/nmh.tar.gz + mv $(tarfile) $(ftpdir)/$(tarfile) + cd $(ftpdir) && ln -s $(tarfile) nmh.tar.gz + cd $(ftpdir) && md5sum *.gz > MD5SUM + diff --git a/README b/README new file mode 100644 index 0000000..595e444 --- /dev/null +++ b/README @@ -0,0 +1,56 @@ +# +# README -- I love README files. +# +# $Id$ +# + +----------- +what is it? +----------- +nmh (new MH) is an electronic mail handling system. It was +originally based on the package MH-6.8.3, and is intended to be +a (mostly) compatible drop-in replacement for MH. + +Although development of nmh is ongoing, it appears to be generally +stable and is in current use. But it is possible that I may break +things as changes are made. + +-------------- +installing nmh +-------------- +To install nmh, check the INSTALL and MACHINES files. If you have +previously used MH, check the file DIFFERENCES for a list of the +differences between nmh and MH. + +-------------------------------- +ftp and web sites, mailing lists +-------------------------------- +To find out about the mailing lists, ftp sites, and web page +for nmh, check the FAQ included in this distribution. + +--------------- +nmh maintenance +--------------- +nmh is currently being developed and maintained by +Richard Coleman . Please send bug reports +and suggestions to the nmh development mailing list at +nmh-workers@math.gatech.edu. + +---------------- +acknowledgments +---------------- +I would like to give credit where it is due. nmh could never have +been developed without all the hard work that the RAND Corporation +and the University of California put into the development of the +MH message system, that nmh is based on. + +Also, since I've used the version of (v)snprintf() from the +Apache web server, I need to make the following acknowlegement: + +This product includes software developed by the Apache Group +for use in the Apache HTTP server project (http://www.apache.org/). + +-- +Richard Coleman +coleman@math.gatech.edu +http://www.math.gatech.edu/~coleman/ diff --git a/TODO b/TODO new file mode 100644 index 0000000..861da67 --- /dev/null +++ b/TODO @@ -0,0 +1,205 @@ +[TODO] + +* Write different function to read configuration files, instead + of using m_getfld.c +* convert calls from sprintf/vsprintf to snprintf/vsnprintf +* convert calls from getcpy to strdup +* modularize access to context/profile list. +* add command printm to print messages +* finish changing to macros for msgstats and msgflags +* Add support for Mail-Followup-To and Mail-Reply-To +* Add support for profile entry "Mailing-Lists" +* let mhn and mhl accept files on standard in, and output to + standard out, when filtering files. +* Add switch -more to `show' to call moreproc. +* Add new command "show" at WhatNow? prompt. + +[POSSIBILITIES] + +MAN PAGES +--------- +* Update mh-tailor man page. +* generate mh-chart man page from other man pages +* update default mode in man pages with sed + +SEQUENCES +--------- +* Change so you can have more than 26 sequences. Unfortunately, + given the way that the bit flags for sequences work, this is + not easy. +* Maybe add option gracefully handle empty sequences (-force). + +ENVIRONMENT/PROFILE +------------------- +* Maybe add profile entry "Pager" to change the default pager? +* Should nmh check for EDITOR or PAGER environment variable? + +POP +--- +* Clean up uip/spop.c (I dont' think it's needed any longer). +* Need to decide if want to support APOP, RPOP, MPOP. APOP + and RPOP still work, but need autoconf support added. Does + anyone still use this stuff? + +OPTIONS +------- +* change switches to accept two dashes (--help) +* can we move option checking to its own function? Currently each + command is doing that itself. +* make the option parsing code dynamic, so that there is no limit + to the number of command line arguments (this has been done for + most all the commands). +* Add switch descriptions to -help output. + +COMP +---- +*) add option so that prompter can be used to input addresses, + before the real editor is called to edit message. + +FLIST +----- +* add -format option so you can specify the look of the output + of flist. +* add Flist-Exclude profile component + +FOLDER +------ +* add Folder-Order profile component (same as flist) +* add Folder-Exclude profile component + +FORMATS +------- +* add escape %(sequence{foobar}) to detect arbitrary sequences. + +FORW +---- +* Decode RFC-2047 headers in messages included when replying + or forwarding. +* Merge code for forw and repl. + +INC +--- +* Add ability to do filtering (call filterproc) when incorporating mail +* Change inc to use libary function folder_addmsg(). + +MHBUILD +------- +* add ability to specify Content-Transfer-Encoding in composition + drafts. +* add support for Content-Disposition header (rfc1806). +* remove the code for caching from mhbuild. + +MHL +--- +* remove naming hack in mhlsbr.c for adios and done. +* add ability to filter parts of the message by calling + an external filtering program. +* fix internal pager for mhl? + +MHMAIL +------ +* add -attach option (send could use this). This should + be done by hooking in mhbuildsbr.c + +MHN/MHSHOW/MHLIST/MHSTORE +------------------------- +* add way so user can tell mhn to use internal method for + handling type, such as multipart/mixed. +* add way so user can tell mhn to use a certain `proc' such + as moreproc, for certain content types. +* add support for Content-Disposition header (rfc1806). +* merge the two places in which mhshowsbr.c reads display + strings. +* split mhn into mhshow, mhlist, mhstore, and mhcache. +* when storing to a folder, should we save the folder context + first, so that storage string of "+" stores to the new + folder? + +MSH +--- +* change conditional includes in msh.c to use termios.h +* Add -version to mshcmd.c for each command. +* Change msh to use mbox style files, instead of mmdf. Add options -mbox, + -mmdf to choose. +*) There are couple calls to copyip() which should be changed to + getarguments(). One problem is freeing the string getcopy'ed by + getarguments(). + +PICK +---- +* split regex code out into library. +* replace regex code with Henry Spencer's regex library. + +POST +---- +* make -msgid the default +* factor msgid code into own function + +PROMPTER +-------- +* maybe add ability to use prompter just for headers, and + then use primary editor for editing message. + +RCVSTORE +-------- +* Change rcvstore so that it can store into multiple folders. +* Add folder locking. + +REPL +---- +* Decode RFC-2047 headers in messages included when replying + or forwarding. +* Merge code for forw and repl. + +SEND/SENDSBR +------------ +* Maybe add `-server' and `-client' to documentation. +* Add ability for returned messages from "send -push" to be + in MIME format (this is actually a change in mhmail). +* make -msgid the default. +* Add RFC-2047 encoding support for out-going messages. This + will probably require hooking mhparse into sendsbr.c, and doing + a complete MIME parsing. Then all handling of Content encoding + can be on the backend. + +SLOCAL +----- +* Change slocal to use .slocalrc file, instead of .maildelivery? +* Add ability to use regular expressions in header matching. +* Add support for Berkeley db. +* Clean up output from -debug option. +* Add -debuglevel to control the amount of debug info that is output. +* Add -debuglog to specify file to save debugging output. +* Add -logfile (or -audit) to specify where to record info about successful + deliveries. + +VMH +--- +* Fix vmh (or remove it). vmh seems to be using internal + knowledge of curses. + +MTA INTERFACE +------------- +* Fix locking code. Add lockfile command. +* Think about support for DSN (Delivery Status Notification) +* Test nmh with qmail. Add qmail's maildir format. +* Relax restrictions on what can be done with headers in send/post. +* figure out why smail.c and client.c need their own copies of + getcpy, copyip, etc... (funny linking problem) + +GENERAL +------- +* see if the various versions of copyfile and copyf can be + merged. +* change time functions to use POSIX functions by default. +* Add MH-6.8.4 features into nmh (mostly done). +* Maybe should move etcpath to sbr and add to libmh. +* collect winsize, struct termio, etc... together into a ttyinfo + structure. +* change adios to take exit code argument. +* use wait3 if not waitpid (maybe) +* some of the calls to setjmp/longjmp should be replaced with + sigsetjmp/siglongjmp. +* When do we need to add -lresolv for SunOS 4.1.x? +* replace use of getcpy with strdup. +* replace use of ftell with fgetpos. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..1beee2b --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +nmh-1.0 diff --git a/ZSH.COMPLETION b/ZSH.COMPLETION new file mode 100644 index 0000000..5c10cc8 --- /dev/null +++ b/ZSH.COMPLETION @@ -0,0 +1,188 @@ +# +# The following several shell functions and `compctl' commands +# that will configure the programmable command completion of +# the Z Shell (zsh) for the nmh mail system. +# +# You may need to edit where it says EDIT ME. +# These were orginally written for MH by Peter Stephenson + +# The following three functions are best autoloaded. +# +# mhcomp completes folders (including subfolders). +# mhfseq completes sequence names and message numbers. +# mhfile completes files in standard nmh locations. + +# +# Completion function for nmh folders. Works with +# both + (relative to top) and @ (relative to current). +# +function mhcomp { + local nword args pref char mhpath + read -nc nword + read -cA args + + pref=$args[$nword] + char=$pref[1] + pref=$pref[2,-1] + + # The $(...) here accounts for most of the time spent in this function. + if [[ $char = + ]]; then + # mhpath=$(mhpath +) + # EDIT ME: use a hard wired value here: it's faster. + mhpath=~/Mail + elif [[ $char = @ ]]; then + mhpath=$(mhpath) + fi + + eval "reply=($mhpath/$pref*(N-/))" + + # I'm frankly amazed that this next step works, but it does. + reply=(${reply#$mhpath/}) +} + +# +# Extract nmh message names and numbers for completion. Use of the +# correct folder, if it is not the current one, requires that it +# should be the previous command line argument. If the previous +# argument is `-draftmessage', a hard wired draft folder name is used. +# +mhfseq() { + local folder foldpath words pos nums + read -cA words + read -cn pos + + # Look for a folder name. + # First try the previous word. + if [[ $words[$pos-1] = [@+]* ]]; then + folder=$words[$pos-1] + # Next look and see if we're looking for a draftmessage + elif [[ $words[$pos-1] = -draftmessage ]]; then + # EDIT ME: shortcut -- hard-wire draftfolder here + # Should really look for a +draftfolder argument. + folder=+drafts + fi + # Else use the current folder ($folder empty) + + if [[ $folder = +* ]]; then + # EDIT ME: use hard-wired path with + for speed. + foldpath=~/Mail/$folder[2,-1] + else + foldpath=$(mhpath $folder) + fi + + # Extract all existing message numbers from the folder. + nums=($foldpath/<->(N:t)) + # If that worked, look for marked sequences. + # EDIT ME + # if you never use non-standard sequences, comment out + # or delete the next three lines. + if (( $#nums )); then + nums=($nums $(mark $folder | awk -F: '{print $1}')) + fi + + # EDIT ME: `unseen' is the value of Unseen-Sequence, if it exists; + set -A reply next cur prev first last all unseen $nums + +} + +# +# Find an nmh file; for use with -form arguments and the like. +# Use with compctl -K mhfile. +# +mhfile () { + local mhfpath file + # EDIT ME + # Array containing all the places nmh will look for templates etc. + mhfpath=(~/Mail /usr/local/nmh/lib) + + # Emulate completeinword behaviour as appropriate + local wordstr + if [[ -o completeinword ]]; then + wordstr='$1*$2' + else + wordstr='$1$2*' + fi + + if [[ $1$2 = */* ]]; then + # path given: don't search nmh locations + eval "reply=($wordstr(.N))" + else + # no path: only search nmh locations. + eval "reply=(\$mhfpath/$wordstr(.N:t))" + fi +} + +# Note: you must type the initial + or @ of a folder name to get +# completion, even in places where only folder names are allowed. +# Abbreviations for options are not recognised. Hit tab to complete +# the option name first. + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(all noall fast nofast header noheader help list nolist \ + pack nopack pop push recurse norecurse total nototal)" -- folder folders + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(sequence all noall recurse norecurse showzero noshowzero \ + alpha noalpha fast nofast help)" -- flist flists + +compctl -K mhfseq -x 's[+][@],c[-1,-draftfolder] s[+][@]' \ + -K mhcomp -S / -q - 'c[-1,-draftmessage]' -K mhfseq - \ + 'C[-1,-(editor|whatnowproc)]' -c - \ + 's[-]' -k "(draftfolder draftmessage nodraftfolder editor noedit \ + file form use nouse whatnowproc nowhatnowproc help)" - \ + 'c[-1,-form]' -K mhfile -- comp + +compctl -K mhfseq -x 's[+][@]' \ + -K mhcomp -S / -q - 'c[-1,-draftmessage]' -K mhfseq -\ + 's[-]' -k "(annotate noannotate cc nocc draftfolder nodraftfolder \ + draftmessage editor noedit fcc filter form group nogroup inplace noinplace + query noquery width whatnowproc nowhatnowproc help)" - 'c[-1,(cc|nocc)]' \ + -k "(all to cc me)" - 'C[-1,-(filter|form)]' -K mhfile - \ + 'C[-1,-(editor|whatnowproc)]' -c -- repl + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(audit noaudit changecur nochangecur form format \ + file silent nosilent truncate notruncate width help)" - \ + 'C[-1,-(audit|form)]' -K mhfile - 'c[-1,-file]' -f + -- inc + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(sequence add delete list public nopublic zero nozero help)" -- \ + mark + +compctl -K mhfseq -x 's[+][@]' \ + -K mhcomp -S / -q - 'c[-1,-file]' -f - 'c[-1,-rmmprov]' -c - \ + 's[-]' -k "(draft link nolink preserve nopreserve src file \ + rmmproc normmproc help)" -- refile + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(clear noclear form format header noheader reverse noreverse \ + file help width)" - 'c[-1,-file]' -f - 'c[-1,-form]' -K mhfile -- scan + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(draft form moreproc nomoreproc header noheader \ + showproc noshowproc length width help)" - 'C[-1,-(show|more)proc]' -c - \ + 'c[-1,-file]' -f - 'c[-1,-form]' -K mhfile - \ + 'c[-1,-length]' -s '$LINES' - 'c[-1,-width]' -s '$COLUMNS' -- show next prev + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - 's[-]' \ + -k "(help)" -- rmm + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - \ + 's[-]' -k "(after before cc date datefield from help list nolist \ + public nopublic search sequence subject to zero nozero not or and \ + lbrace rbrace)" -- pick + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - 's[-]' \ + -k "(alias check draft draftfolder draftmessage help nocheck \ + nodraftfolder)" -- whom + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - 's[-]' \ + -k "(file part type list headers noheaders realsize norealsize nolist \ + show serialonly noserialonly form pause nopause noshow store auto noauto \ + nostore cache nocache rcache wcache check nocheck ebcdicsafe noebcdicsafe \ + rfc934mode norfc934mode verbose noverbose help)" - \ + 'c[-1,-file]' -f - 'c[-1,-form]' -K mhfile - \ + 'C[-1,-[rw]cache]' -k '(public private never ask)' -- mhn + +compctl -K mhfseq -x 's[+][@]' -K mhcomp -S / -q - 's[-]' -k '(help)' -- mhpath + diff --git a/acconfig.h b/acconfig.h new file mode 100644 index 0000000..5c3f602 --- /dev/null +++ b/acconfig.h @@ -0,0 +1,263 @@ + +/****** BEGIN USER CONFIGURATION SECTION *****/ + +/* + * IMPORTANT: UNCOMMENT THE DEFINES FOR YOUR OPERATING SYSTEM + * + * These are slowly being phased out, but currently + * not everyone is auto-configured. Then decide if you + * wish to change the features that are compiled into nmh. + */ + +/* + * Solaris 2.x + * Irix + * OSF/1 + * HP-UX + * AIX + */ +/* #define SYS5 1 */ +/* #define SVR4 1 */ + +/* + * SunOS 4.1.x + */ +/* #define BIND 1 */ +/* #define BSD42 1 */ + +/* + * Linux + */ +/* #define LINUX_STDIO 1 */ + +/* + * FreeBSD 2.x + * NetBSD 1.x, + * OpenBSD 2.x + * BSD/OS 2.x + */ +/* #define BIND 1 */ +/* #define BSD42 1 */ +/* #define BSD44 1 */ + +/* + * SCO 4.x + * SCO 5.x + * + * I believe the second `define' is only necessary + * for SCO 5.x, not SCO 4.x + */ +/* #define SYS5 1 */ +/* #define SCO_5_STDIO 1 */ + +/* + * Define to 1 if you need to make `inc' set-group-id + * because your mail spool is not world writable. This + * will add some extra security checks, although I can't + * guarantee it is safe. Also, you will need to change the + * group and add the setgid bit to `inc' manually after + * installation. + */ +/* #define MAILGROUP 1 */ + +/* + * Turn on locale (setlocale) support + */ +#define LOCALE 1 + +/* + * Define to 1 the type of file locking to use. You need to + * make sure the type of locking you use is compatible with + * other programs which may modify your maildrops. + * Currently you can only use one type. + */ +#define DOT_LOCKING 1 +/* #define FCNTL_LOCKING 1 */ +/* #define LOCKF_LOCKING 1 */ +/* #define FLOCK_LOCKING 1 */ + +/* + * If you have defined DOT_LOCKING, then the default is to + * place the lock files in the same directory as the file that + * is to be locked. Alternately, if you define LOCKDIR, you + * can specify that all lock files go in a specific directory. + * Don't define this unless you know you need it. + */ +/* #define LOCKDIR "/usr/spool/locks" */ + +/* + * Define this if your passwords are stored in some type of + * distributed name service, such as NIS, or NIS+. + */ +#define DBMPWD 1 + +/* + * Directs nmh not to try and rewrite addresses + * to their official form. You probably don't + * want to change this without good reason. + */ +#define DUMB 1 + +/* + * Define this if you do not want nmh to attach the local hostname + * to local addresses. You must also define DUMB. You probably + * dont' need this unless you are behind a firewall. + */ +/* #define REALLYDUMB 1 */ + +/* + * Directs inc/slocal to extract the envelope sender from "From " + * line. If inc/slocal is saving message to folder, then this + * sender information is then used to create a Return-Path + * header which is then added to the message. + */ +#define RPATHS 1 + +/* + * If defined, slocal will use `mbox' format when saving to + * your standard mail spool. If not defined, it will use + * mmdf format. + */ +#define SLOCAL_MBOX 1 + +/* + * If this is defined, nmh will recognize the ~ construct. + */ +#define MHRC 1 + +/* + * Compile simple ftp client into mhn. This will be used by mhn + * for ftp access unless you have specified another access method + * in your .mh_profile or mhn.defaults. Use the "mhn-access-ftp" + * profile entry to override this. Check mhn(1) man page for + * details. + */ +#define BUILTIN_FTP 1 + +/* + * If you enable POP support, this is the the port name + * that nmh will use. Make sure this is defined in your + * /etc/services file (or its NIS/NIS+ equivalent). If you + * are using KPOP, you will probably need to change this + * to "kpop". + */ +#define POPSERVICE "pop3" + +/* + * Define the default creation modes for folders and messages. + */ +#define DEFAULT_FOLDER_MODE "0700" +#define DEFAULT_MESSAGE_MODE "0600" + +/* + * The prefix which is prepended to the name of messages when they + * are "removed" by rmm. This should typically be `,' or `#' + */ +#define BACKUP_PREFIX "," + +/* + * Name of link to file to which you are replying. + */ +#define LINK "@" + +/* + * If wait/waitpid returns an int (no union wait). + */ +#define WAITINT 1 + +/***** END USER CONFIGURATION SECTION *****/ +@TOP@ + +/* + * Define this if you want SMTP (simple mail transport protocol) + * support. When sending mail, instead of passing the message to + * the mail transport agent (typically sendmail), nmh will open a + * socket connection to the mail port on the machine specified in + * the `mts.conf' file (default is localhost), and speak SMTP directly. + */ +#undef SMTPMTS + +/* + * Use sendmail as transport agent. Post messages by piping + * them directly to sendmail. + */ +#undef SENDMTS + +/* + * Define this to compile client-side support for pop into + * inc and msgchk. Do not change this value manually. You + * must run configure with the '--enable-nmh-pop' option + * to correctly build the pop client support. + */ +#undef POP + +/* + * Define this to compile client-side support for kpop + * (kerberized pop) into inc and msgchk. Do not change this + * value manually. You must run configure with the option + * '--with-krb4=PREFIX' to correctly build the kpop client support. + */ +#undef KPOP + +/* + * Define this to "pop" when using Kerberos V4 + */ +#undef KPOP_PRINCIPAL + +/* + * Define this to compile support for using Hesiod to locate + * pop servers into inc and msgchk. Do not change this value + * manually. You must run configure with the option + * '--with-hesiod=PREFIX' to correctly build Hesiod support. + */ +#undef HESIOD + +/* + * Compile in support for the Emacs front-end mh-e. + */ +#undef MHE + +/* Define to 1 if your termcap library has the ospeed variable */ +#undef HAVE_OSPEED +/* Define to 1 if you have ospeed, but it is not defined in termcap.h */ +#undef MUST_DEFINE_OSPEED + +/* Define to 1 if tgetent() accepts NULL as a buffer */ +#undef TGETENT_ACCEPTS_NULL + +/* Define to 1 if you have reliable signals */ +#undef RELIABLE_SIGNALS + +/* Define to 1 if you use POSIX style signal handling */ +#undef POSIX_SIGNALS + +/* Define to 1 if you use BSD style signal handling (and can block signals) */ +#undef BSD_SIGNALS + +/* Define to 1 if you use SYS style signal handling (and can block signals) */ +#undef SYSV_SIGNALS + +/* Define to 1 if you have no signal blocking at all (bummer) */ +#undef NO_SIGNAL_BLOCKING + +/* Define to `unsigned int' if or doesn't define */ +#undef sigset_t + +/* + * Define to 1 if your vi has ATT bug, such that it returns + * non-zero exit codes on `pseudo-errors'. + */ +#undef ATTVIBUG + +/* Define ruserpass as _ruserpass if your libraries have a bug * + * such that it can't find ruserpass, but can find _ruserpass. */ +#undef ruserpass + +/* Define if your system defines TIOCGWINSZ in sys/ioctl.h. */ +#undef GWINSZ_IN_SYS_IOCTL + +/* Define if your system defines `struct winsize' in sys/ptem.h. */ +#undef WINSIZE_IN_PTEM + +/* Define to 1 if struct tm has gmtoff */ +#undef HAVE_TM_GMTOFF diff --git a/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..cbfb498 --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,41 @@ + +# Originally by John Hawkinson +# Under Solaris, those +# applications need to link with "-lsocket -lnsl". Under IRIX, they +# need to link with "-lnsl" but should *not* link with "-lsocket" +# because libsocket.a breaks a number of things (for instance, +# gethostbyname() under IRIX 5.2, and snoop sockets under most versions +# of IRIX). +# +# The check for libresolv is in case you are attempting to link +# statically and happen to have a libresolv.a lying around (and no +# libnsl.a). An example of such a case would be Solaris with +# BIND 4.9.5 installed. + +AC_DEFUN(AC_CHECK_NETLIBS, +[AC_CHECK_FUNC(gethostbyname, , + AC_CHECK_LIB(nsl, gethostbyname, , + AC_CHECK_LIB(resolv, gethostbyname))) +AC_CHECK_FUNC(socket, , + AC_CHECK_LIB(socket, socket)) +]) + + +# This checks for the function ruserpass. +# +# 1) first, check for ruserpass +# 2) else, check for _ruserpass +# 3) else, check for _ruserpass in libsocket +# 4) else, build version of ruserpass in nmh/sbr +AC_DEFUN(AC_CHECK_RUSERPASS, +[AC_CHECK_FUNC(ruserpass, , + AC_CHECK_FUNC(_ruserpass, , + AC_CHECK_LIB(socket, _ruserpass))) +if test x$ac_cv_func_ruserpass = xno; then + if test x$ac_cv_func__ruserpass = xyes -o x$ac_cv_lib_socket__ruserpass = xyes; then + AC_DEFINE(ruserpass, _ruserpass) + else + LIBOBJS="$LIBOBJS ruserpass.o" + fi +fi +]) diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..3915bde --- /dev/null +++ b/config.h.in @@ -0,0 +1,434 @@ +/* config.h.in. Generated automatically from configure.in by autoheader. */ + +/****** BEGIN USER CONFIGURATION SECTION *****/ + +/* + * IMPORTANT: UNCOMMENT THE DEFINES FOR YOUR OPERATING SYSTEM + * + * These are slowly being phased out, but currently + * not everyone is auto-configured. Then decide if you + * wish to change the features that are compiled into nmh. + */ + +/* + * Solaris 2.x + * Irix + * OSF/1 + * HP-UX + * AIX + */ +/* #define SYS5 1 */ +/* #define SVR4 1 */ + +/* + * SunOS 4.1.x + */ +/* #define BIND 1 */ +/* #define BSD42 1 */ + +/* + * Linux + */ +/* #define LINUX_STDIO 1 */ + +/* + * FreeBSD 2.x + * NetBSD 1.x, + * OpenBSD 2.x + * BSD/OS 2.x + */ +/* #define BIND 1 */ +/* #define BSD42 1 */ +/* #define BSD44 1 */ + +/* + * SCO 4.x + * SCO 5.x + * + * I believe the second `define' is only necessary + * for SCO 5.x, not SCO 4.x + */ +/* #define SYS5 1 */ +/* #define SCO_5_STDIO 1 */ + +/* + * Define to 1 if you need to make `inc' set-group-id + * because your mail spool is not world writable. This + * will add some extra security checks, although I can't + * guarantee it is safe. Also, you will need to change the + * group and add the setgid bit to `inc' manually after + * installation. + */ +/* #define MAILGROUP 1 */ + +/* + * Turn on locale (setlocale) support + */ +#define LOCALE 1 + +/* + * Define to 1 the type of file locking to use. You need to + * make sure the type of locking you use is compatible with + * other programs which may modify your maildrops. + * Currently you can only use one type. + */ +#define DOT_LOCKING 1 +/* #define FCNTL_LOCKING 1 */ +/* #define LOCKF_LOCKING 1 */ +/* #define FLOCK_LOCKING 1 */ + +/* + * If you have defined DOT_LOCKING, then the default is to + * place the lock files in the same directory as the file that + * is to be locked. Alternately, if you define LOCKDIR, you + * can specify that all lock files go in a specific directory. + * Don't define this unless you know you need it. + */ +/* #define LOCKDIR "/usr/spool/locks" */ + +/* + * Define this if your passwords are stored in some type of + * distributed name service, such as NIS, or NIS+. + */ +#define DBMPWD 1 + +/* + * Directs nmh not to try and rewrite addresses + * to their official form. You probably don't + * want to change this without good reason. + */ +#define DUMB 1 + +/* + * Define this if you do not want nmh to attach the local hostname + * to local addresses. You must also define DUMB. You probably + * dont' need this unless you are behind a firewall. + */ +/* #define REALLYDUMB 1 */ + +/* + * Directs inc/slocal to extract the envelope sender from "From " + * line. If inc/slocal is saving message to folder, then this + * sender information is then used to create a Return-Path + * header which is then added to the message. + */ +#define RPATHS 1 + +/* + * If defined, slocal will use `mbox' format when saving to + * your standard mail spool. If not defined, it will use + * mmdf format. + */ +#define SLOCAL_MBOX 1 + +/* + * If this is defined, nmh will recognize the ~ construct. + */ +#define MHRC 1 + +/* + * Compile simple ftp client into mhn. This will be used by mhn + * for ftp access unless you have specified another access method + * in your .mh_profile or mhn.defaults. Use the "mhn-access-ftp" + * profile entry to override this. Check mhn(1) man page for + * details. + */ +#define BUILTIN_FTP 1 + +/* + * If you enable POP support, this is the the port name + * that nmh will use. Make sure this is defined in your + * /etc/services file (or its NIS/NIS+ equivalent). If you + * are using KPOP, you will probably need to change this + * to "kpop". + */ +#define POPSERVICE "pop3" + +/* + * Define the default creation modes for folders and messages. + */ +#define DEFAULT_FOLDER_MODE "0700" +#define DEFAULT_MESSAGE_MODE "0600" + +/* + * The prefix which is prepended to the name of messages when they + * are "removed" by rmm. This should typically be `,' or `#' + */ +#define BACKUP_PREFIX "," + +/* + * Name of link to file to which you are replying. + */ +#define LINK "@" + +/* + * If wait/waitpid returns an int (no union wait). + */ +#define WAITINT 1 + +/***** END USER CONFIGURATION SECTION *****/ + +/* Define to empty if the keyword does not work. */ +#undef const + +/* Define to `int' if doesn't define. */ +#undef gid_t + +/* Define if your struct stat has st_blksize. */ +#undef HAVE_ST_BLKSIZE + +/* Define if you have that is POSIX.1 compatible. */ +#undef HAVE_SYS_WAIT_H + +/* Define if you have . */ +#undef HAVE_VFORK_H + +/* Define to `int' if doesn't define. */ +#undef mode_t + +/* Define to `long' if doesn't define. */ +#undef off_t + +/* Define to `int' if doesn't define. */ +#undef pid_t + +/* Define as the return type of signal handlers (int or void). */ +#undef RETSIGTYPE + +/* Define to `unsigned' if doesn't define. */ +#undef size_t + +/* Define if the `S_IS*' macros in do not work properly. */ +#undef STAT_MACROS_BROKEN + +/* Define if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define if you can safely include both and . */ +#undef TIME_WITH_SYS_TIME + +/* Define to `int' if doesn't define. */ +#undef uid_t + +/* Define vfork as fork if vfork does not work. */ +#undef vfork + +/* + * Define this if you want SMTP (simple mail transport protocol) + * support. When sending mail, instead of passing the message to + * the mail transport agent (typically sendmail), nmh will open a + * socket connection to the mail port on the machine specified in + * the `mts.conf' file (default is localhost), and speak SMTP directly. + */ +#undef SMTPMTS + +/* + * Use sendmail as transport agent. Post messages by piping + * them directly to sendmail. + */ +#undef SENDMTS + +/* + * Define this to compile client-side support for pop into + * inc and msgchk. Do not change this value manually. You + * must run configure with the '--enable-nmh-pop' option + * to correctly build the pop client support. + */ +#undef POP + +/* + * Define this to compile client-side support for kpop + * (kerberized pop) into inc and msgchk. Do not change this + * value manually. You must run configure with the option + * '--with-krb4=PREFIX' to correctly build the kpop client support. + */ +#undef KPOP + +/* + * Define this to "pop" when using Kerberos V4 + */ +#undef KPOP_PRINCIPAL + +/* + * Define this to compile support for using Hesiod to locate + * pop servers into inc and msgchk. Do not change this value + * manually. You must run configure with the option + * '--with-hesiod=PREFIX' to correctly build Hesiod support. + */ +#undef HESIOD + +/* + * Compile in support for the Emacs front-end mh-e. + */ +#undef MHE + +/* Define to 1 if your termcap library has the ospeed variable */ +#undef HAVE_OSPEED +/* Define to 1 if you have ospeed, but it is not defined in termcap.h */ +#undef MUST_DEFINE_OSPEED + +/* Define to 1 if you have reliable signals */ +#undef RELIABLE_SIGNALS + +/* Define to 1 if you use POSIX style signal handling */ +#undef POSIX_SIGNALS + + +/* Define to 1 if you use BSD style signal handling (and can block signals) */ +#undef BSD_SIGNALS + + +/* Define to 1 if you use SYS style signal handling (and can block signals) */ +#undef SYSV_SIGNALS + + +/* Define to 1 if you have no signal blocking at all (bummer) */ +#undef NO_SIGNAL_BLOCKING + +/* Define to `unsigned int' if or doesn't define */ +#undef sigset_t + +/* + * Define to 1 if your vi has ATT bug, such that it returns + * non-zero exit codes on `pseudo-errors'. + */ +#undef ATTVIBUG + +/* Define ruserpass as _ruserpass if your libraries have a bug * + * such that it can't find ruserpass, but can find _ruserpass. */ +#undef ruserpass + +/* Define if your system defines TIOCGWINSZ in sys/ioctl.h. */ +#undef GWINSZ_IN_SYS_IOCTL + +/* Define if your system defines `struct winsize' in sys/ptem.h. */ +#undef WINSIZE_IN_PTEM + +/* Define to 1 if struct tm has gmtoff */ +#undef HAVE_TM_GMTOFF + +/* Define if you have the killpg function. */ +#undef HAVE_KILLPG + +/* Define if you have the lstat function. */ +#undef HAVE_LSTAT + +/* Define if you have the sigaction function. */ +#undef HAVE_SIGACTION + +/* Define if you have the sigblock function. */ +#undef HAVE_SIGBLOCK + +/* Define if you have the sighold function. */ +#undef HAVE_SIGHOLD + +/* Define if you have the sigprocmask function. */ +#undef HAVE_SIGPROCMASK + +/* Define if you have the sigrelse function. */ +#undef HAVE_SIGRELSE + +/* Define if you have the sigsetjmp function. */ +#undef HAVE_SIGSETJMP + +/* Define if you have the sigsetmask function. */ +#undef HAVE_SIGSETMASK + +/* Define if you have the snprintf function. */ +#undef HAVE_SNPRINTF + +/* Define if you have the strdup function. */ +#undef HAVE_STRDUP + +/* Define if you have the strerror function. */ +#undef HAVE_STRERROR + +/* Define if you have the tzset function. */ +#undef HAVE_TZSET + +/* Define if you have the uname function. */ +#undef HAVE_UNAME + +/* Define if you have the wait3 function. */ +#undef HAVE_WAIT3 + +/* Define if you have the waitpid function. */ +#undef HAVE_WAITPID + +/* Define if you have the writev function. */ +#undef HAVE_WRITEV + +/* Define if you have the header file. */ +#undef HAVE_ARPA_FTP_H + +/* Define if you have the header file. */ +#undef HAVE_ARPA_INET_H + +/* Define if you have the header file. */ +#undef HAVE_CRYPT_H + +/* Define if you have the header file. */ +#undef HAVE_DIRENT_H + +/* Define if you have the header file. */ +#undef HAVE_ERRNO_H + +/* Define if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define if you have the header file. */ +#undef HAVE_LOCALE_H + +/* Define if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define if you have the header file. */ +#undef HAVE_NDIR_H + +/* Define if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define if you have the header file. */ +#undef HAVE_STRING_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_DIR_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_NDIR_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define if you have the header file. */ +#undef HAVE_SYS_UTSNAME_H + +/* Define if you have the header file. */ +#undef HAVE_TERMCAP_H + +/* Define if you have the header file. */ +#undef HAVE_TERMIO_H + +/* Define if you have the header file. */ +#undef HAVE_TERMIOS_H + +/* Define if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define if you have the ndbm library (-lndbm). */ +#undef HAVE_LIBNDBM + +/* Define if you have the nsl library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define if you have the resolv library (-lresolv). */ +#undef HAVE_LIBRESOLV + +/* Define if you have the socket library (-lsocket). */ +#undef HAVE_LIBSOCKET diff --git a/config/Makefile.in b/config/Makefile.in new file mode 100644 index 0000000..8468646 --- /dev/null +++ b/config/Makefile.in @@ -0,0 +1,95 @@ +# +# Makefile for config subdirectory +# +# $Id$ +# + +# nmh version +VERSION = @VERSION@ + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +default_editor = @editorpath@ +default_pager = @pagerpath@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I.. -I$(top_srcdir) +CONFIGDEFS = -DNMHBINDIR='"$(bindir)"' -DNMHETCDIR='"$(etcdir)"' -DNMHLIBDIR='"$(libdir)"' \ + -DDEFAULT_EDITOR='"$(default_editor)"' -DDEFAULT_PAGER='"$(default_pager)"' + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) +COMPILE2 = $(CC) -c $(DEFS) $(CONFIGDEFS) $(INCLUDES) $(CFLAGS) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# source files +SRCS = config.c + +# object files +OBJS = config.o version.o + +# auxiliary files +AUX = Makefile.in version.sh + +# all files in this directory included in the distribution +DIST = $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +all: $(OBJS) + +version.c: + ${srcdir}/version.sh $(VERSION) > version.c + +config.o: config.c + $(COMPILE2) $< + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + rm -f version.c + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = config + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/config/config.c b/config/config.c new file mode 100644 index 0000000..2a01c1b --- /dev/null +++ b/config/config.c @@ -0,0 +1,364 @@ + +/* + * config.c -- master nmh configuration file + * + * $Id$ + */ + +#include + +#ifdef MHRC +# include +#endif + +#define nmhbindir(file) NMHBINDIR#file +#define nmhetcdir(file) NMHETCDIR#file +#define nmhlibdir(file) NMHLIBDIR#file + + +/* + * Find the location of a format or configuration + * file, and return its absolute pathname. + * + * 1) If already absolute pathname, then leave unchanged. + * 2) Next, if it begins with ~user, then expand it. + * 3) Next, check in nmh Mail directory. + * 4) Next, check in nmh `etc' directory. + * + */ + +char * +etcpath (char *file) +{ + static char epath[PATH_MAX]; + char *cp; +#ifdef MHRC + char *pp; + struct passwd *pw; +#endif + +#ifdef MHRC + context_read(); +#endif + + switch (*file) { + case '/': + /* If already absolute pathname, return it */ + return file; + +#ifdef MHRC + case '~': + /* Expand ~username */ + if ((cp = strchr(pp = file + 1, '/'))) + *cp++ = '\0'; + if (*pp == '\0') { + pp = mypath; + } else { + if ((pw = getpwnam (pp))) + pp = pw->pw_dir; + else { + if (cp) + *--cp = '/'; + goto try_it; + } + } + + snprintf (epath, sizeof(epath), "%s/%s", pp, cp ? cp : ""); + if (cp) + *--cp = '/'; + + if (access (epath, R_OK) != NOTOK) + return epath; /* else fall */ +try_it: +#endif /* MHRC */ + + default: + /* Check nmh Mail directory */ + if (access ((cp = m_mailpath (file)), R_OK) != NOTOK) + return cp; + } + + /* Check nmh `etc' directory */ + snprintf (epath, sizeof(epath), nmhetcdir(/%s), file); + return (access (epath, R_OK) != NOTOK ? epath : file); +} + + +/* + * Standard yes/no switches structure + */ + +struct swit anoyes[] = { + { "no", 0 }, + { "yes", 0 }, + { NULL, 0 } +}; + +/* + * nmh constants + */ + +/* initial profile for new users */ +char *mh_defaults = nmhetcdir (/mh.profile); + +/* default name of user profile */ +char *mh_profile = ".mh_profile"; + +/* name of current message "sequence" */ +char *current = "cur"; + +/* standard component files */ +char *components = "components"; /* comp */ +char *replcomps = "replcomps"; /* repl */ +char *replgroupcomps = "replgroupcomps"; /* repl -group */ +char *forwcomps = "forwcomps"; /* forw */ +char *distcomps = "distcomps"; /* dist */ +char *rcvdistcomps = "rcvdistcomps"; /* rcvdist */ +char *digestcomps = "digestcomps"; /* forw -digest */ + +/* standard format (filter) files */ +char *mhlformat = "mhl.format"; /* show */ +char *mhlreply = "mhl.reply"; /* repl -filter */ +char *mhlforward = "mhl.forward"; /* forw -filter */ + +char *draft = "draft"; + +char *inbox = "Inbox"; +char *defaultfolder = "inbox"; + +char *pfolder = "Current-Folder"; +char *usequence = "Unseen-Sequence"; +char *psequence = "Previous-Sequence"; +char *nsequence = "Sequence-Negation"; + +/* profile entries for storage locations */ +char *nmhstorage = "nmh-storage"; +char *nmhcache = "nmh-cache"; +char *nmhprivcache = "nmh-private-cache"; + +/* profile entry for external ftp access command */ +char *nmhaccessftp = "nmh-access-ftp"; + +char *mhlibdir = NMHLIBDIR; +char *mhetcdir = NMHETCDIR; + +/* + * nmh not-so constants + */ + +/* + * Default name for the nmh context file. + */ +char *context = "context"; + +/* + * Default name of file for public sequences. If NULL, + * then nmh will use private sequences by default, unless the + * user defines a value using the "mh-sequences" profile entry. + */ +#ifdef NOPUBLICSEQ +char *mh_seq = NULL; +#else +char *mh_seq = ".mh_sequences"; +#endif + +/* + * nmh globals + */ + +char ctxflags; /* status of user's context */ +char *invo_name; /* command invocation name */ +char *mypath; /* user's $HOME */ +char *defpath; /* pathname of user's profile */ +char *ctxpath; /* pathname of user's context */ +struct node *m_defs; /* profile/context structure */ + +/* + * nmh processes + */ + +/* + * This is the program to process MIME composition files + */ +char *buildmimeproc = nmhbindir (/mhbuild); +/* + * This is the program to `cat' a file. + */ +char *catproc = "/bin/cat"; + +/* + * mhl runs this program as a visual-end. + */ + +char *faceproc = NULL; + +/* + * This program is usually called directly by users, but it is + * also invoked by the post program to process an "Fcc", or by + * comp/repl/forw/dist to refile a draft message. + */ + +char *fileproc = nmhbindir (/refile); + +/* + * This program is called to incorporate messages into a folder. + */ + +char *incproc = nmhbindir (/inc); + +/* + * When a user runs an nmh program for the first time, this program + * is called to create his nmh profile, and mail directory. + */ + +char *installproc = nmhlibdir (/install-mh); + +/* + * This is the default program invoked by a "list" response + * at the "What now?" prompt. It is also used by the draft + * folder facility in comp/dist/forw/repl to display the + * draft message. + */ + +char *lproc = DEFAULT_PAGER; + +/* + * This is the path for the Bell equivalent mail program. + */ + +char *mailproc = nmhbindir (/mhmail); + +/* + * This is used by mhl as a front-end. It is also used + * by mhn as the default method of displaying message bodies + * or message parts of type text/plain. + */ + +char *moreproc = DEFAULT_PAGER; + +/* + * This is the program (mhl) used to filter messages. It is + * used by mhn to filter and display the message headers of + * MIME messages. It is used by repl/forw (with -filter) + * to filter the message to which you are replying/forwarding. + * It is used by send/post (with -filter) to filter the message + * for "Bcc:" recipients. + */ + +char *mhlproc = nmhlibdir (/mhl); + +/* + * This is the super handy BBoard reading program, which is + * really just the nmh shell program. + */ + +char *mshproc = nmhbindir (/msh); + +/* + * This program is called to pack a folder. + */ + +char *packproc = nmhbindir (/packf); + +/* + * This is the delivery program called by send to actually + * deliver mail to users. This is the interface to the MTS. + */ + +char *postproc = nmhlibdir (/post); + +/* + * This is program is called by slocal to handle + * the action `folder' or `+'. + */ + +char *rcvstoreproc = nmhlibdir (/rcvstore); + +/* + * This program is called to remove a folder. + */ + +char *rmfproc = nmhbindir (/rmf); + +/* + * This program is called to remove a message by rmm or refile -nolink. + * It's usually empty, which means to rename the file to a backup name. + */ + +char *rmmproc = NULL; + +/* + * This program is usually called by the user's whatnowproc, but it + * may also be called directly to send a message previously composed. + */ + +char *sendproc = nmhbindir (/send); + +/* + * This is the path to the program used by "show" + * to display non-text (MIME) messages. + */ + +char *showmimeproc = nmhbindir (/mhshow); + +/* + * This is the default program called by "show" to filter + * and display standard text (non-MIME) messages. It can be + * changed to a pager (such as "more" or "less") if you prefer + * that such message not be filtered in any way. + */ + +char *showproc = nmhlibdir (/mhl); + +/* + * This program is called by vmh as the back-end to the window management + * protocol + */ + +char *vmhproc = nmhbindir (/msh); + +/* + * This program is called after comp, et. al., have built a draft + */ + +char *whatnowproc = nmhbindir (/whatnow); + +/* + * This program is called to list/validate the addresses in a message. + */ + +char *whomproc = nmhbindir (/whom); + +/* + * This is the editor invoked by the various message + * composition programs. It SHOULD be a full screen + * editor, such as vi or emacs, but any editor will work. + */ + +char *defaulteditor = DEFAULT_EDITOR; + +/* + * This is the global nmh alias file. It is somewhat obsolete, since + * global aliases should be handled by the Mail Transport Agent (MTA). + */ + +char *AliasFile = nmhetcdir (/MailAliases); + +/* + * File protections + */ + +/* + * Folders (directories) are created with this protection (mode) + */ + +char *foldprot = DEFAULT_FOLDER_MODE; + +/* + * Every NEW message will be created with this protection. When a + * message is filed it retains its protection, so this only applies + * to messages coming in through inc. + */ + +char *msgprot = DEFAULT_MESSAGE_MODE; + diff --git a/config/version.sh b/config/version.sh new file mode 100755 index 0000000..6257186 --- /dev/null +++ b/config/version.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# +# version.sh -- script to create version string(s) for nmh. +# +# You need to pass the script the version number to use. +# +# $Id$ +# + +if [ -z "$1" ]; then + echo "usage: version.sh VERSION" 1>&2 + exit 1 +fi + +VERSION=$1 +OFS="$IFS" +IFS=: +HOSTNAME=unknown + +# Find out the name of the host we are compiling on +for prog in uname hostname +do + for dir in $PATH + do + if [ ! -f $dir/$prog ]; then + continue + fi + case $prog in + uname) HOSTNAME=`$prog -n` + ;; + hostname) HOSTNAME=`$prog` + ;; + esac + break + done + if [ X"$HOSTNAME" != X -a X"$HOSTNAME" != Xunknown ]; then + break + fi +done + +IFS=" " + +echo "char *version_str = \"nmh-$VERSION [compiled on $HOSTNAME at `date`]\";" +echo "char *version_num = \"nmh-$VERSION\";" diff --git a/configure b/configure new file mode 100755 index 0000000..8786037 --- /dev/null +++ b/configure @@ -0,0 +1,4024 @@ +#! /bin/sh + +# Guess values for system-dependent variables and create Makefiles. +# Generated automatically using autoconf version 2.12 +# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc. +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. + +# Defaults: +ac_help= +ac_default_prefix=/usr/local +# Any additions from configure.in: +ac_help="$ac_help + --with-mts=MTS specify the mail transport agent" +ac_help="$ac_help + --with-editor=EDITOR specify the default editor" +ac_help="$ac_help + --with-pager=PAGER specify the default pager" +ac_help="$ac_help + --enable-nmh-mhe enable mhe support (DEFAULT)" +ac_help="$ac_help + --enable-nmh-pop enable client-side support for pop" +ac_help="$ac_help + --with-krb4=PREFIX specify location of Kerberos V4 for kpop support" +ac_help="$ac_help + --with-hesiod=PREFIX specify location of Hesiod" +ac_help="$ac_help + --enable-nmh-debug enable nmh code debugging" +ac_default_prefix=/usr/local/nmh + +# Initialize some variables set by options. +# The variables have the same names as the options, with +# dashes changed to underlines. +build=NONE +cache_file=./config.cache +exec_prefix=NONE +host=NONE +no_create= +nonopt=NONE +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +target=NONE +verbose= +x_includes=NONE +x_libraries=NONE +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datadir='${prefix}/share' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +libdir='${exec_prefix}/lib' +includedir='${prefix}/include' +oldincludedir='/usr/include' +infodir='${prefix}/info' +mandir='${prefix}/man' + +# Initialize some other variables. +subdirs= +MFLAGS= MAKEFLAGS= +# Maximum number of lines to put in a shell here document. +ac_max_here_lines=12 + +ac_prev= +for ac_option +do + + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval "$ac_prev=\$ac_option" + ac_prev= + continue + fi + + case "$ac_option" in + -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) ac_optarg= ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case "$ac_option" in + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir="$ac_optarg" ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build="$ac_optarg" ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file="$ac_optarg" ;; + + -datadir | --datadir | --datadi | --datad | --data | --dat | --da) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ + | --da=*) + datadir="$ac_optarg" ;; + + -disable-* | --disable-*) + ac_feature=`echo $ac_option|sed -e 's/-*disable-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + eval "enable_${ac_feature}=no" ;; + + -enable-* | --enable-*) + ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; } + fi + ac_feature=`echo $ac_feature| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "enable_${ac_feature}='$ac_optarg'" ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix="$ac_optarg" ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he) + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat << EOF +Usage: configure [options] [host] +Options: [defaults in brackets after descriptions] +Configuration: + --cache-file=FILE cache test results in FILE + --help print this message + --no-create do not create output files + --quiet, --silent do not print \`checking...' messages + --version print the version of autoconf that created configure +Directory and file names: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [same as prefix] + --bindir=DIR user executables in DIR [EPREFIX/bin] + --sbindir=DIR system admin executables in DIR [EPREFIX/sbin] + --libexecdir=DIR program executables in DIR [EPREFIX/libexec] + --datadir=DIR read-only architecture-independent data in DIR + [PREFIX/share] + --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data in DIR + [PREFIX/com] + --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var] + --libdir=DIR object code libraries in DIR [EPREFIX/lib] + --includedir=DIR C header files in DIR [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include] + --infodir=DIR info documentation in DIR [PREFIX/info] + --mandir=DIR man documentation in DIR [PREFIX/man] + --srcdir=DIR find the sources in DIR [configure dir or ..] + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM + run sed PROGRAM on installed program names +EOF + cat << EOF +Host type: + --build=BUILD configure for building on BUILD [BUILD=HOST] + --host=HOST configure for HOST [guessed] + --target=TARGET configure for TARGET [TARGET=HOST] +Features and packages: + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --x-includes=DIR X include files are in DIR + --x-libraries=DIR X library files are in DIR +EOF + if test -n "$ac_help"; then + echo "--enable and --with options recognized:$ac_help" + fi + exit 0 ;; + + -host | --host | --hos | --ho) + ac_prev=host ;; + -host=* | --host=* | --hos=* | --ho=*) + host="$ac_optarg" ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir="$ac_optarg" ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir="$ac_optarg" ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir="$ac_optarg" ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir="$ac_optarg" ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst \ + | --locals | --local | --loca | --loc | --lo) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* \ + | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) + localstatedir="$ac_optarg" ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir="$ac_optarg" ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir="$ac_optarg" ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix="$ac_optarg" ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix="$ac_optarg" ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix="$ac_optarg" ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name="$ac_optarg" ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir="$ac_optarg" ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir="$ac_optarg" ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site="$ac_optarg" ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir="$ac_optarg" ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir="$ac_optarg" ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target="$ac_optarg" ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers) + echo "configure generated by autoconf version 2.12" + exit 0 ;; + + -with-* | --with-*) + ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + case "$ac_option" in + *=*) ;; + *) ac_optarg=yes ;; + esac + eval "with_${ac_package}='$ac_optarg'" ;; + + -without-* | --without-*) + ac_package=`echo $ac_option|sed -e 's/-*without-//'` + # Reject names that are not valid shell variable names. + if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then + { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; } + fi + ac_package=`echo $ac_package| sed 's/-/_/g'` + eval "with_${ac_package}=no" ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes="$ac_optarg" ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries="$ac_optarg" ;; + + -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; } + ;; + + *) + if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then + echo "configure: warning: $ac_option: invalid host type" 1>&2 + fi + if test "x$nonopt" != xNONE; then + { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; } + fi + nonopt="$ac_option" + ;; + + esac +done + +if test -n "$ac_prev"; then + { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; } +fi + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +# File descriptor usage: +# 0 standard input +# 1 file creation +# 2 errors and warnings +# 3 some systems may open it to /dev/tty +# 4 used on the Kubota Titan +# 6 checking for... messages and results +# 5 compiler messages saved in config.log +if test "$silent" = yes; then + exec 6>/dev/null +else + exec 6>&1 +fi +exec 5>./config.log + +echo "\ +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. +" 1>&5 + +# Strip out --no-create and --no-recursion so they do not pile up. +# Also quote any args containing shell metacharacters. +ac_configure_args= +for ac_arg +do + case "$ac_arg" in + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c) ;; + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;; + *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*) + ac_configure_args="$ac_configure_args '$ac_arg'" ;; + *) ac_configure_args="$ac_configure_args $ac_arg" ;; + esac +done + +# NLS nuisances. +# Only set these to C if already set. These must not be set unconditionally +# because not all systems understand e.g. LANG=C (notably SCO). +# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'! +# Non-C LC_CTYPE values break the ctype check. +if test "${LANG+set}" = set; then LANG=C; export LANG; fi +if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi +if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi +if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -rf conftest* confdefs.h +# AIX cpp loses on an empty file, so make sure it contains at least a newline. +echo > confdefs.h + +# A filename unique to this package, relative to the directory that +# configure is in, which we can look for to find out if srcdir is correct. +ac_unique_file=h/nmh.h + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then its parent. + ac_prog=$0 + ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'` + test "x$ac_confdir" = "x$ac_prog" && ac_confdir=. + srcdir=$ac_confdir + if test ! -r $srcdir/$ac_unique_file; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r $srcdir/$ac_unique_file; then + if test "$ac_srcdir_defaulted" = yes; then + { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; } + else + { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; } + fi +fi +srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'` + +# Prefer explicitly selected file to automatically selected ones. +if test -z "$CONFIG_SITE"; then + if test "x$prefix" != xNONE; then + CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site" + else + CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" + fi +fi +for ac_site_file in $CONFIG_SITE; do + if test -r "$ac_site_file"; then + echo "loading site script $ac_site_file" + . "$ac_site_file" + fi +done + +if test -r "$cache_file"; then + echo "loading cache $cache_file" + . $cache_file +else + echo "creating cache $cache_file" + > $cache_file +fi + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then + # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu. + if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then + ac_n= ac_c=' +' ac_t=' ' + else + ac_n=-n ac_c= ac_t= + fi +else + ac_n= ac_c='\c' ac_t= +fi + + + + +VERSION=`sed -e 's/nmh-//' ${srcdir}/VERSION` +echo "configuring for nmh-$VERSION" + +# Check whether --with-mts or --without-mts was given. +if test "${with_mts+set}" = set; then + withval="$with_mts" + : +fi + + +if test x$with_mts = xsmtp; then + MTS="smtp" + MTSLIB="mts/smtp/libsmtp.a" + cat >> confdefs.h <<\EOF +#define SMTPMTS 1 +EOF +elif test x$with_mts = xsendmail; then + MTS="sendmail" + MTSLIB="mts/sendmail/libsend.a" + cat >> confdefs.h <<\EOF +#define SENDMTS 1 +EOF +else + MTS="smtp" + MTSLIB="mts/smtp/libsmtp.a" + cat >> confdefs.h <<\EOF +#define SMTPMTS 1 +EOF +fi + + + + +# Check whether --with-editor or --without-editor was given. +if test "${with_editor+set}" = set; then + withval="$with_editor" + : +fi + + +if test -n "$with_editor"; then + editorpath="$with_editor" +fi + +# Check whether --with-pager or --without-pager was given. +if test "${with_pager+set}" = set; then + withval="$with_pager" + : +fi + + +if test -n "$with_pager"; then + pagerpath="$with_pager" +fi + +# Check whether --enable-nmh-mhe or --disable-nmh-mhe was given. +if test "${enable_nmh_mhe+set}" = set; then + enableval="$enable_nmh_mhe" + : +fi + + +if test x$enable_nmh_mhe != xno; then + cat >> confdefs.h <<\EOF +#define MHE 1 +EOF +fi + +# Check whether --enable-nmh-pop or --disable-nmh-pop was given. +if test "${enable_nmh_pop+set}" = set; then + enableval="$enable_nmh_pop" + : +fi + +if test x$enable_nmh_pop = xyes; then + cat >> confdefs.h <<\EOF +#define POP 1 +EOF + POPLIB=popsbr.o + POPSED='/^%nmhbeginpop%/d;/^%nmhendpop%/d' +else + POPSED='/^%nmhbeginpop%/,/^%nmhendpop%/d' +fi + +# Check whether --with-krb4 or --without-krb4 was given. +if test "${with_krb4+set}" = set; then + withval="$with_krb4" + : +fi + +if test x$with_krb4 != x -a x$with_krb4 != xno; then + cat >> confdefs.h <<\EOF +#define KPOP 1 +EOF + cat >> confdefs.h <<\EOF +#define KPOP_PRINCIPAL "pop" +EOF +fi + +# Check whether --with-hesiod or --without-hesiod was given. +if test "${with_hesiod+set}" = set; then + withval="$with_hesiod" + : +fi + +if test x$with_hesiod != x -a x$with_hesiod != xno; then + cat >> confdefs.h <<\EOF +#define HESIOD 1 +EOF +fi + +# Check whether --enable-nmh-debug or --disable-nmh-debug was given. +if test "${enable_nmh_debug+set}" = set; then + enableval="$enable_nmh_debug" + : +fi + + + + +test -z "$CFLAGS" && CFLAGS= auto_cflags=1 +if test x$enable_nmh_debug = xyes; then + test -z "$LDFLAGS" && LDFLAGS=-g +fi + +# Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:669: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_CC="gcc" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:698: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + ac_prog_rejected=no + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + break + fi + done + IFS="$ac_save_ifs" +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# -gt 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + set dummy "$ac_dir/$ac_word" "$@" + shift + ac_cv_prog_CC="$@" + fi +fi +fi +fi +CC="$ac_cv_prog_CC" +if test -n "$CC"; then + echo "$ac_t""$CC" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; } +fi + +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6 +echo "configure:746: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5 + +ac_ext=c +# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options. +ac_cpp='$CPP $CPPFLAGS' +ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5' +ac_link='${CC-cc} -o conftest $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5' +cross_compiling=$ac_cv_prog_cc_cross + +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + ac_cv_prog_cc_works=yes + # If we can't run a trivial program, we are probably using a cross compiler. + if (./conftest; exit) 2>/dev/null; then + ac_cv_prog_cc_cross=no + else + ac_cv_prog_cc_cross=yes + fi +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + ac_cv_prog_cc_works=no +fi +rm -fr conftest* + +echo "$ac_t""$ac_cv_prog_cc_works" 1>&6 +if test $ac_cv_prog_cc_works = no; then + { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; } +fi +echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6 +echo "configure:780: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5 +echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6 +cross_compiling=$ac_cv_prog_cc_cross + +echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6 +echo "configure:785: checking whether we are using GNU C" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.c <&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then + ac_cv_prog_gcc=yes +else + ac_cv_prog_gcc=no +fi +fi + +echo "$ac_t""$ac_cv_prog_gcc" 1>&6 + +if test $ac_cv_prog_gcc = yes; then + GCC=yes + ac_test_CFLAGS="${CFLAGS+set}" + ac_save_CFLAGS="$CFLAGS" + CFLAGS= + echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6 +echo "configure:809: checking whether ${CC-cc} accepts -g" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + echo 'void f(){}' > conftest.c +if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then + ac_cv_prog_cc_g=yes +else + ac_cv_prog_cc_g=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_prog_cc_g" 1>&6 + if test "$ac_test_CFLAGS" = set; then + CFLAGS="$ac_save_CFLAGS" + elif test $ac_cv_prog_cc_g = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-O2" + fi +else + GCC= + test "${CFLAGS+set}" = set || CFLAGS="-g" +fi + + +if test -n "$auto_cflags"; then + if test x$enable_nmh_debug = xyes; then + if test -n "$GCC"; then + test -z "$CFLAGS" && CFLAGS="-Wall -g" || CFLAGS="$CFLAGS -Wall -g" + else + test -z "$CFLAGS" && CFLAGS=-g || CFLAGS="$CFLAGS -g" + fi + else + test -z "$LDFLAGS" && LDFLAGS=-s + if test -n "$GCC"; then + test -z "$CFLAGS" && CFLAGS=-O2 || CFLAGS="$CFLAGS -O2" + else + test -z "$CFLAGS" && CFLAGS=-O || CFLAGS="$CFLAGS -O" + fi + fi +fi + +echo $ac_n "checking for working const""... $ac_c" 1>&6 +echo "configure:855: checking for working const" >&5 +if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <j = 5; +} +{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ + const int foo = 10; +} + +; return 0; } +EOF +if { (eval echo configure:909: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_c_const=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_c_const=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_c_const" 1>&6 +if test $ac_cv_c_const = no; then + cat >> confdefs.h <<\EOF +#define const +EOF + +fi + +echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6 +echo "configure:930: checking whether ${MAKE-make} sets \${MAKE}" >&5 +set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftestmake <<\EOF +all: + @echo 'ac_maketemp="${MAKE}"' +EOF +# GNU make sometimes prints "make[1]: Entering...", which would confuse us. +eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=` +if test -n "$ac_maketemp"; then + eval ac_cv_prog_make_${ac_make}_set=yes +else + eval ac_cv_prog_make_${ac_make}_set=no +fi +rm -f conftestmake +fi +if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then + echo "$ac_t""yes" 1>&6 + SET_MAKE= +else + echo "$ac_t""no" 1>&6 + SET_MAKE="MAKE=${MAKE-make}" +fi + ac_aux_dir= +for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do + if test -f $ac_dir/install-sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f $ac_dir/install.sh; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + fi +done +if test -z "$ac_aux_dir"; then + { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; } +fi +ac_config_guess=$ac_aux_dir/config.guess +ac_config_sub=$ac_aux_dir/config.sub +ac_configure=$ac_aux_dir/configure # This should be Cygnus configure. + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# ./install, which can be erroneously created by make from ./install.sh. +echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6 +echo "configure:985: checking for a BSD compatible install" >&5 +if test -z "$INSTALL"; then +if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + IFS="${IFS= }"; ac_save_IFS="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + # Account for people who put trailing slashes in PATH elements. + case "$ac_dir/" in + /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + for ac_prog in ginstall installbsd scoinst install; do + if test -f $ac_dir/$ac_prog; then + if test $ac_prog = install && + grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + # OSF/1 installbsd also uses dspmsg, but is usable. + : + else + ac_cv_path_install="$ac_dir/$ac_prog -c" + break 2 + fi + fi + done + ;; + esac + done + IFS="$ac_save_IFS" + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL="$ac_cv_path_install" + else + # As a last resort, use the slow shell script. We don't cache a + # path for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the path is relative. + INSTALL="$ac_install_sh" + fi +fi +echo "$ac_t""$INSTALL" 1>&6 + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1036: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_RANLIB="ranlib" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":" +fi +fi +RANLIB="$ac_cv_prog_RANLIB" +if test -n "$RANLIB"; then + echo "$ac_t""$RANLIB" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + for ac_prog in mawk gawk nawk awk +do +# Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1066: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_AWK'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_AWK="$ac_prog" + break + fi + done + IFS="$ac_save_ifs" +fi +fi +AWK="$ac_cv_prog_AWK" +if test -n "$AWK"; then + echo "$ac_t""$AWK" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +test -n "$AWK" && break +done + # Extract the first word of "flex", so it can be a program name with args. +set dummy flex; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1096: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_LEX'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$LEX"; then + ac_cv_prog_LEX="$LEX" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_LEX="flex" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_LEX" && ac_cv_prog_LEX="lex" +fi +fi +LEX="$ac_cv_prog_LEX" +if test -n "$LEX"; then + echo "$ac_t""$LEX" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test -z "$LEXLIB" +then + case "$LEX" in + flex*) ac_lib=fl ;; + *) ac_lib=l ;; + esac + echo $ac_n "checking for yywrap in -l$ac_lib""... $ac_c" 1>&6 +echo "configure:1129: checking for yywrap in -l$ac_lib" >&5 +ac_lib_var=`echo $ac_lib'_'yywrap | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-l$ac_lib $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LEXLIB="-l$ac_lib" +else + echo "$ac_t""no" 1>&6 +fi + +fi + +# Extract the first word of "lorder", so it can be a program name with args. +set dummy lorder; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1173: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_LORDER'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$LORDER"; then + ac_cv_prog_LORDER="$LORDER" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_LORDER="lorder" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_LORDER" && ac_cv_prog_LORDER="no" +fi +fi +LORDER="$ac_cv_prog_LORDER" +if test -n "$LORDER"; then + echo "$ac_t""$LORDER" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi +# Extract the first word of "tsort", so it can be a program name with args. +set dummy tsort; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1201: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_prog_TSORT'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test -n "$TSORT"; then + ac_cv_prog_TSORT="$TSORT" # Let the user override the test. +else + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_prog_TSORT="tsort" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_prog_TSORT" && ac_cv_prog_TSORT="no" +fi +fi +TSORT="$ac_cv_prog_TSORT" +if test -n "$TSORT"; then + echo "$ac_t""$TSORT" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + +if test x$ac_cv_prog_LORDER != xlorder -o x$ac_cv_prog_TSORT != xtsort; then + LORDER=echo + TSORT=cat + fi + +pathtmp=/usr/lib:/usr/sbin:/usr/etc:/usr/ucblib:/usr/bin:/bin +# Extract the first word of "sendmail", so it can be a program name with args. +set dummy sendmail; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1236: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_sendmailpath'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$sendmailpath" in + /*) + ac_cv_path_sendmailpath="$sendmailpath" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $pathtmp$ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_sendmailpath="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_sendmailpath" && ac_cv_path_sendmailpath="no" + ;; +esac +fi +sendmailpath="$ac_cv_path_sendmailpath" +if test -n "$sendmailpath"; then + echo "$ac_t""$sendmailpath" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + +pathtmp=/usr/bin:/bin:/usr/ucb:/usr/local/bin +# Extract the first word of "more", so it can be a program name with args. +set dummy more; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1270: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_morepath'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$morepath" in + /*) + ac_cv_path_morepath="$morepath" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $pathtmp$ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_morepath="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_morepath" && ac_cv_path_morepath="no" + ;; +esac +fi +morepath="$ac_cv_path_morepath" +if test -n "$morepath"; then + echo "$ac_t""$morepath" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + +if test -z "$pagerpath"; then + pagerpath="$morepath" +fi + +pathtmp=/usr/bin:/bin:/usr/ucb:/usr/local/bin +# Extract the first word of "vi", so it can be a program name with args. +set dummy vi; ac_word=$2 +echo $ac_n "checking for $ac_word""... $ac_c" 1>&6 +echo "configure:1308: checking for $ac_word" >&5 +if eval "test \"`echo '$''{'ac_cv_path_vipath'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + case "$vipath" in + /*) + ac_cv_path_vipath="$vipath" # Let the user override the test with a path. + ;; + *) + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:" + for ac_dir in $pathtmp$ac_dummy; do + test -z "$ac_dir" && ac_dir=. + if test -f $ac_dir/$ac_word; then + ac_cv_path_vipath="$ac_dir/$ac_word" + break + fi + done + IFS="$ac_save_ifs" + test -z "$ac_cv_path_vipath" && ac_cv_path_vipath="no" + ;; +esac +fi +vipath="$ac_cv_path_vipath" +if test -n "$vipath"; then + echo "$ac_t""$vipath" 1>&6 +else + echo "$ac_t""no" 1>&6 +fi + + +if test -z "$editorpath"; then + editorpath="$vipath" +fi + +echo $ac_n "checking for broken vi""... $ac_c" 1>&6 +echo "configure:1343: checking for broken vi" >&5 +if eval "test \"`echo '$''{'nmh_cv_attvibug'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if echo 'r /nonexist-file +q' | ex > /dev/null 2>&1 +then + nmh_cv_attvibug=no +else + nmh_cv_attvibug=yes +fi +fi + +echo "$ac_t""$nmh_cv_attvibug" 1>&6 + +if test "$nmh_cv_attvibug" = yes; then + cat >> confdefs.h <<\EOF +#define ATTVIBUG 1 +EOF + +fi + +echo $ac_n "checking where mail spool is located""... $ac_c" 1>&6 +echo "configure:1366: checking where mail spool is located" >&5 +if eval "test \"`echo '$''{'nmh_cv_mailspool'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + for mailspool in /var/mail /var/spool/mail /usr/spool/mail /dev/null; do + test -d $mailspool && break +done +nmh_cv_mailspool=$mailspool + +fi + +echo "$ac_t""$nmh_cv_mailspool" 1>&6 +mailspool=$nmh_cv_mailspool + +ac_header_dirent=no +for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr that defines DIR""... $ac_c" 1>&6 +echo "configure:1385: checking for $ac_hdr that defines DIR" >&5 +if eval "test \"`echo '$''{'ac_cv_header_dirent_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include <$ac_hdr> +int main() { +DIR *dirp = 0; +; return 0; } +EOF +if { (eval echo configure:1398: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + eval "ac_cv_header_dirent_$ac_safe=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_dirent_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done +# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix. +if test $ac_header_dirent = dirent.h; then +echo $ac_n "checking for opendir in -ldir""... $ac_c" 1>&6 +echo "configure:1423: checking for opendir in -ldir" >&5 +ac_lib_var=`echo dir'_'opendir | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldir $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -ldir" +else + echo "$ac_t""no" 1>&6 +fi + +else +echo $ac_n "checking for opendir in -lx""... $ac_c" 1>&6 +echo "configure:1464: checking for opendir in -lx" >&5 +ac_lib_var=`echo x'_'opendir | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lx $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + LIBS="$LIBS -lx" +else + echo "$ac_t""no" 1>&6 +fi + +fi + +echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6 +echo "configure:1506: checking how to run the C preprocessor" >&5 +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then +if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + # This must be in double quotes, not single quotes, because CPP may get + # substituted into the Makefile and "${CC-cc}" will confuse make. + CPP="${CC-cc} -E" + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1527: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP="${CC-cc} -E -traditional-cpp" + cat > conftest.$ac_ext < +Syntax Error +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1544: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + : +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + CPP=/lib/cpp +fi +rm -f conftest* +fi +rm -f conftest* + ac_cv_prog_CPP="$CPP" +fi + CPP="$ac_cv_prog_CPP" +else + ac_cv_prog_CPP="$CPP" +fi +echo "$ac_t""$CPP" 1>&6 + +echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6 +echo "configure:1567: checking for ANSI C header files" >&5 +if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#include +#include +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1580: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + ac_cv_header_stdc=yes +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "memchr" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. +cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "free" >/dev/null 2>&1; then + : +else + rm -rf conftest* + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. +if test "$cross_compiling" = yes; then + : +else + cat > conftest.$ac_ext < +#define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int main () { int i; for (i = 0; i < 256; i++) +if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2); +exit (0); } + +EOF +if { (eval echo configure:1647: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + : +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_header_stdc=no +fi +rm -fr conftest* +fi + +fi +fi + +echo "$ac_t""$ac_cv_header_stdc" 1>&6 +if test $ac_cv_header_stdc = yes; then + cat >> confdefs.h <<\EOF +#define STDC_HEADERS 1 +EOF + +fi + +echo $ac_n "checking whether time.h and sys/time.h may both be included""... $ac_c" 1>&6 +echo "configure:1671: checking whether time.h and sys/time.h may both be included" >&5 +if eval "test \"`echo '$''{'ac_cv_header_time'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#include +int main() { +struct tm *tp; +; return 0; } +EOF +if { (eval echo configure:1685: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_header_time=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_header_time=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_header_time" 1>&6 +if test $ac_cv_header_time = yes; then + cat >> confdefs.h <<\EOF +#define TIME_WITH_SYS_TIME 1 +EOF + +fi + +echo $ac_n "checking for sys/wait.h that is POSIX.1 compatible""... $ac_c" 1>&6 +echo "configure:1706: checking for sys/wait.h that is POSIX.1 compatible" >&5 +if eval "test \"`echo '$''{'ac_cv_header_sys_wait_h'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif +int main() { +int s; +wait (&s); +s = WIFEXITED (s) ? WEXITSTATUS (s) : 1; +; return 0; } +EOF +if { (eval echo configure:1727: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_header_sys_wait_h=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_header_sys_wait_h=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_header_sys_wait_h" 1>&6 +if test $ac_cv_header_sys_wait_h = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_SYS_WAIT_H 1 +EOF + +fi + +echo $ac_n "checking whether stat file-mode macros are broken""... $ac_c" 1>&6 +echo "configure:1748: checking whether stat file-mode macros are broken" >&5 +if eval "test \"`echo '$''{'ac_cv_header_stat_broken'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include + +#if defined(S_ISBLK) && defined(S_IFDIR) +# if S_ISBLK (S_IFDIR) +You lose. +# endif +#endif + +#if defined(S_ISBLK) && defined(S_IFCHR) +# if S_ISBLK (S_IFCHR) +You lose. +# endif +#endif + +#if defined(S_ISLNK) && defined(S_IFREG) +# if S_ISLNK (S_IFREG) +You lose. +# endif +#endif + +#if defined(S_ISSOCK) && defined(S_IFREG) +# if S_ISSOCK (S_IFREG) +You lose. +# endif +#endif + +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "You lose" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_header_stat_broken=yes +else + rm -rf conftest* + ac_cv_header_stat_broken=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_header_stat_broken" 1>&6 +if test $ac_cv_header_stat_broken = yes; then + cat >> confdefs.h <<\EOF +#define STAT_MACROS_BROKEN 1 +EOF + +fi + +for ac_hdr in string.h memory.h stdlib.h unistd.h errno.h fcntl.h \ + limits.h crypt.h termcap.h termio.h termios.h locale.h \ + sys/param.h sys/time.h sys/utsname.h arpa/inet.h \ + arpa/ftp.h +do +ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +echo "configure:1810: checking for $ac_hdr" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1820: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'` + cat >> confdefs.h <&6 +fi +done + + +echo $ac_n "checking POSIX termios""... $ac_c" 1>&6 +echo "configure:1848: checking POSIX termios" >&5 +if eval "test \"`echo '$''{'nmh_cv_sys_posix_termios'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#include +int main() { +/* SunOS 4.0.3 has termios.h but not the library calls. */ +tcgetattr(0, 0); +; return 0; } +EOF +if { (eval echo configure:1863: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + nmh_cv_sys_posix_termios=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_sys_posix_termios=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_sys_posix_termios" 1>&6 + +if test $nmh_cv_sys_posix_termios = yes; then + echo $ac_n "checking TIOCGWINSZ in termios.h""... $ac_c" 1>&6 +echo "configure:1879: checking TIOCGWINSZ in termios.h" >&5 +if eval "test \"`echo '$''{'nmh_cv_header_termios_h_tiocgwinsz'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +int main() { +int x = TIOCGWINSZ; +; return 0; } +EOF +if { (eval echo configure:1892: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + nmh_cv_header_termios_h_tiocgwinsz=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_header_termios_h_tiocgwinsz=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_header_termios_h_tiocgwinsz" 1>&6 +else + nmh_cv_header_termios_h_tiocgwinsz=no +fi + +if test $nmh_cv_header_termios_h_tiocgwinsz = no; then + echo $ac_n "checking TIOCGWINSZ in sys/ioctl.h""... $ac_c" 1>&6 +echo "configure:1911: checking TIOCGWINSZ in sys/ioctl.h" >&5 +if eval "test \"`echo '$''{'nmh_cv_header_sys_ioctl_h_tiocgwinsz'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +int main() { +int x = TIOCGWINSZ; +; return 0; } +EOF +if { (eval echo configure:1924: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + nmh_cv_header_sys_ioctl_h_tiocgwinsz=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_header_sys_ioctl_h_tiocgwinsz=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_header_sys_ioctl_h_tiocgwinsz" 1>&6 + if test $nmh_cv_header_sys_ioctl_h_tiocgwinsz = yes; then + cat >> confdefs.h <<\EOF +#define GWINSZ_IN_SYS_IOCTL 1 +EOF + + fi +fi + +ac_safe=`echo "sys/ptem.h" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for sys/ptem.h""... $ac_c" 1>&6 +echo "configure:1947: checking for sys/ptem.h" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:1957: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + cat >> confdefs.h <<\EOF +#define WINSIZE_IN_PTEM 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi + + +echo $ac_n "checking for pid_t""... $ac_c" 1>&6 +echo "configure:1983: checking for pid_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_pid_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "pid_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_pid_t=yes +else + rm -rf conftest* + ac_cv_type_pid_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_pid_t" 1>&6 +if test $ac_cv_type_pid_t = no; then + cat >> confdefs.h <<\EOF +#define pid_t int +EOF + +fi + +ac_safe=`echo "vfork.h" | sed 'y%./+-%__p_%'` +echo $ac_n "checking for vfork.h""... $ac_c" 1>&6 +echo "configure:2017: checking for vfork.h" >&5 +if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out" +{ (eval echo configure:2027: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; } +ac_err=`grep -v '^ *+' conftest.out` +if test -z "$ac_err"; then + rm -rf conftest* + eval "ac_cv_header_$ac_safe=yes" +else + echo "$ac_err" >&5 + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_header_$ac_safe=no" +fi +rm -f conftest* +fi +if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then + echo "$ac_t""yes" 1>&6 + cat >> confdefs.h <<\EOF +#define HAVE_VFORK_H 1 +EOF + +else + echo "$ac_t""no" 1>&6 +fi + +echo $ac_n "checking for working vfork""... $ac_c" 1>&6 +echo "configure:2052: checking for working vfork" >&5 +if eval "test \"`echo '$''{'ac_cv_func_vfork_works'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + if test "$cross_compiling" = yes; then + echo $ac_n "checking for vfork""... $ac_c" 1>&6 +echo "configure:2058: checking for vfork" >&5 +if eval "test \"`echo '$''{'ac_cv_func_vfork'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char vfork(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_vfork) || defined (__stub___vfork) +choke me +#else +vfork(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2086: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_vfork=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_vfork=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'vfork`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +fi + +else + cat > conftest.$ac_ext < +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_VFORK_H +#include +#endif +/* On some sparc systems, changes by the child to local and incoming + argument registers are propagated back to the parent. + The compiler is told about this with #include , + but some compilers (e.g. gcc -O) don't grok . + Test for this by using a static variable whose address + is put into a register that is clobbered by the vfork. */ +static +#ifdef __cplusplus +sparc_address_test (int arg) +#else +sparc_address_test (arg) int arg; +#endif +{ + static pid_t child; + if (!child) { + child = vfork (); + if (child < 0) { + perror ("vfork"); + _exit(2); + } + if (!child) { + arg = getpid(); + write(-1, "", 0); + _exit (arg); + } + } +} +main() { + pid_t parent = getpid (); + pid_t child; + + sparc_address_test (); + + child = vfork (); + + if (child == 0) { + /* Here is another test for sparc vfork register problems. + This test uses lots of local variables, at least + as many local variables as main has allocated so far + including compiler temporaries. 4 locals are enough for + gcc 1.40.3 on a Solaris 4.1.3 sparc, but we use 8 to be safe. + A buggy compiler should reuse the register of parent + for one of the local variables, since it will think that + parent can't possibly be used any more in this routine. + Assigning to the local variable will thus munge parent + in the parent process. */ + pid_t + p = getpid(), p1 = getpid(), p2 = getpid(), p3 = getpid(), + p4 = getpid(), p5 = getpid(), p6 = getpid(), p7 = getpid(); + /* Convince the compiler that p..p7 are live; otherwise, it might + use the same hardware register for all 8 local variables. */ + if (p != p1 || p != p2 || p != p3 || p != p4 + || p != p5 || p != p6 || p != p7) + _exit(1); + + /* On some systems (e.g. IRIX 3.3), + vfork doesn't separate parent from child file descriptors. + If the child closes a descriptor before it execs or exits, + this munges the parent's descriptor as well. + Test for this by closing stdout in the child. */ + _exit(close(fileno(stdout)) != 0); + } else { + int status; + struct stat st; + + while (wait(&status) != child) + ; + exit( + /* Was there some problem with vforking? */ + child < 0 + + /* Did the child fail? (This shouldn't happen.) */ + || status + + /* Did the vfork/compiler bug occur? */ + || parent != getpid() + + /* Did the file descriptor bug occur? */ + || fstat(fileno(stdout), &st) != 0 + ); + } +} +EOF +if { (eval echo configure:2202: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest && (./conftest; exit) 2>/dev/null +then + ac_cv_func_vfork_works=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -fr conftest* + ac_cv_func_vfork_works=no +fi +rm -fr conftest* +fi + +fi + +echo "$ac_t""$ac_cv_func_vfork_works" 1>&6 +if test $ac_cv_func_vfork_works = no; then + cat >> confdefs.h <<\EOF +#define vfork fork +EOF + +fi + +for ac_func in waitpid wait3 sigaction sigprocmask sigblock sigsetmask \ + sighold sigrelse writev lstat uname tzset killpg \ + sigsetjmp +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:2229: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2257: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +fi +done + + +for ac_func in snprintf strerror strdup +do +echo $ac_n "checking for $ac_func""... $ac_c" 1>&6 +echo "configure:2285: checking for $ac_func" >&5 +if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $ac_func(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_$ac_func) || defined (__stub___$ac_func) +choke me +#else +$ac_func(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2313: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_$ac_func=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_$ac_func=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'` + cat >> confdefs.h <&6 +LIBOBJS="$LIBOBJS ${ac_func}.o" +fi +done + + + +echo $ac_n "checking for gethostbyname""... $ac_c" 1>&6 +echo "configure:2341: checking for gethostbyname" >&5 +if eval "test \"`echo '$''{'ac_cv_func_gethostbyname'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char gethostbyname(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_gethostbyname) || defined (__stub___gethostbyname) +choke me +#else +gethostbyname(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2369: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_gethostbyname=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_gethostbyname=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'gethostbyname`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for gethostbyname in -lnsl""... $ac_c" 1>&6 +echo "configure:2387: checking for gethostbyname in -lnsl" >&5 +ac_lib_var=`echo nsl'_'gethostbyname | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lnsl $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo nsl | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +echo $ac_n "checking for gethostbyname in -lresolv""... $ac_c" 1>&6 +echo "configure:2432: checking for gethostbyname in -lresolv" >&5 +ac_lib_var=`echo resolv'_'gethostbyname | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lresolv $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo resolv | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi + +fi + +echo $ac_n "checking for socket""... $ac_c" 1>&6 +echo "configure:2483: checking for socket" >&5 +if eval "test \"`echo '$''{'ac_cv_func_socket'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char socket(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_socket) || defined (__stub___socket) +choke me +#else +socket(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2511: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_socket=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_socket=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'socket`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for socket in -lsocket""... $ac_c" 1>&6 +echo "configure:2529: checking for socket in -lsocket" >&5 +ac_lib_var=`echo socket'_'socket | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lsocket $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo socket | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi + + + +echo $ac_n "checking for ruserpass""... $ac_c" 1>&6 +echo "configure:2580: checking for ruserpass" >&5 +if eval "test \"`echo '$''{'ac_cv_func_ruserpass'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char ruserpass(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_ruserpass) || defined (__stub___ruserpass) +choke me +#else +ruserpass(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2608: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_ruserpass=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_ruserpass=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'ruserpass`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for _ruserpass""... $ac_c" 1>&6 +echo "configure:2626: checking for _ruserpass" >&5 +if eval "test \"`echo '$''{'ac_cv_func__ruserpass'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char _ruserpass(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub__ruserpass) || defined (__stub____ruserpass) +choke me +#else +_ruserpass(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2654: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func__ruserpass=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func__ruserpass=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'_ruserpass`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for _ruserpass in -lsocket""... $ac_c" 1>&6 +echo "configure:2672: checking for _ruserpass in -lsocket" >&5 +ac_lib_var=`echo socket'_'_ruserpass | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lsocket $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo socket | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi + +fi + +if test x$ac_cv_func_ruserpass = xno; then + if test x$ac_cv_func__ruserpass = xyes -o x$ac_cv_lib_socket__ruserpass = xyes; then + cat >> confdefs.h <<\EOF +#define ruserpass _ruserpass +EOF + + else + LIBOBJS="$LIBOBJS ruserpass.o" + fi +fi + + +termcap_curses_order="termcap curses ncurses" +for lib in $termcap_curses_order; do + echo $ac_n "checking for tgetent in -l${lib}""... $ac_c" 1>&6 +echo "configure:2737: checking for tgetent in -l${lib}" >&5 +ac_lib_var=`echo ${lib}'_'tgetent | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-l${lib} $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + TERMLIB="-l$lib"; break +else + echo "$ac_t""no" 1>&6 +fi + +done + +echo $ac_n "checking for dbm_open""... $ac_c" 1>&6 +echo "configure:2779: checking for dbm_open" >&5 +if eval "test \"`echo '$''{'ac_cv_func_dbm_open'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char dbm_open(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_dbm_open) || defined (__stub___dbm_open) +choke me +#else +dbm_open(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2807: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_dbm_open=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_dbm_open=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'dbm_open`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for dbm_open in -lndbm""... $ac_c" 1>&6 +echo "configure:2825: checking for dbm_open in -lndbm" >&5 +ac_lib_var=`echo ndbm'_'dbm_open | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lndbm $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo ndbm | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +echo $ac_n "checking for dbm_open in -ldbm""... $ac_c" 1>&6 +echo "configure:2870: checking for dbm_open in -ldbm" >&5 +ac_lib_var=`echo dbm'_'dbm_open | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-ldbm $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo dbm | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi + +fi + + +if test x$with_hesiod != x -a x$with_hesiod != xno; then + if test x$with_hesiod != xyes; then + HESIOD_INCLUDES="-I$with_hesiod/include" + HESIOD_LIBS="-L$with_hesiod/lib" + fi + echo $ac_n "checking for res_send""... $ac_c" 1>&6 +echo "configure:2927: checking for res_send" >&5 +if eval "test \"`echo '$''{'ac_cv_func_res_send'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +/* Override any gcc2 internal prototype to avoid an error. */ +/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char res_send(); + +int main() { + +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined (__stub_res_send) || defined (__stub___res_send) +choke me +#else +res_send(); +#endif + +; return 0; } +EOF +if { (eval echo configure:2955: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_func_res_send=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_func_res_send=no" +fi +rm -f conftest* +fi + +if eval "test \"`echo '$ac_cv_func_'res_send`\" = yes"; then + echo "$ac_t""yes" 1>&6 + : +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for res_send in -lresolv""... $ac_c" 1>&6 +echo "configure:2973: checking for res_send in -lresolv" >&5 +ac_lib_var=`echo resolv'_'res_send | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lresolv $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + ac_tr_lib=HAVE_LIB`echo resolv | sed -e 's/^a-zA-Z0-9_/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` + cat >> confdefs.h <&6 +fi + +fi + + echo $ac_n "checking for hes_resolve in -lhesiod""... $ac_c" 1>&6 +echo "configure:3022: checking for hes_resolve in -lhesiod" >&5 +ac_lib_var=`echo hesiod'_'hes_resolve | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lhesiod $HESIOD_LIBS $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + HESIOD_LIBS="$HESIOD_LIBS -lhesiod" +else + echo "$ac_t""no" 1>&6 +{ echo "configure: error: Hesiod library not found" 1>&2; exit 1; } +fi + +fi + +if test x$with_krb4 != x -a x$with_krb4 != xno; then + if test x$with_krb4 != xyes; then + KRB4_INCLUDES="-I$with_krb4/include" + if test -d "$with_krb4/include/kerberosIV"; then + KRB4_INCLUDES="$KRB4_INCLUDES -I$with_krb4/include/kerberosIV" + fi + KRB4_LIBS="-L$with_krb4/lib" + elif test -d /usr/include/kerberosIV; then + KRB4_INCLUDES="-I/usr/include/kerberosIV" + fi + echo $ac_n "checking for krb_rd_req in -lkrb4""... $ac_c" 1>&6 +echo "configure:3075: checking for krb_rd_req in -lkrb4" >&5 +ac_lib_var=`echo krb4'_'krb_rd_req | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lkrb4 $KRB4_LIBS -ldes425 -lkrb5 -lcrypto -lcom_err $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + KRB4_LIBS="$KRB4_LIBS -lkrb4 -ldes425 -lkrb5 -lcrypto -lcom_err" +else + echo "$ac_t""no" 1>&6 +echo $ac_n "checking for krb_rd_req in -lkrb""... $ac_c" 1>&6 +echo "configure:3113: checking for krb_rd_req in -lkrb" >&5 +ac_lib_var=`echo krb'_'krb_rd_req | sed 'y%./+-%__p_%'` +if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + ac_save_LIBS="$LIBS" +LIBS="-lkrb $KRB4_LIBS -ldes $LIBS" +cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=yes" +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + eval "ac_cv_lib_$ac_lib_var=no" +fi +rm -f conftest* +LIBS="$ac_save_LIBS" + +fi +if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then + echo "$ac_t""yes" 1>&6 + KRB4_LIBS="-lkrb -ldes" +else + echo "$ac_t""no" 1>&6 +{ echo "configure: error: Kerberos 4 libraries not found" 1>&2; exit 1; } +fi + +fi + +fi + + +nmh_save_LIBS="$LIBS" +LIBS="$TERMLIB $LIBS" + +echo $ac_n "checking if an include file defines ospeed""... $ac_c" 1>&6 +echo "configure:3162: checking if an include file defines ospeed" >&5 +if eval "test \"`echo '$''{'nmh_cv_decl_ospeed_include_defines'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if HAVE_TERMIOS_H +#include +#endif +#if HAVE_TERMCAP_H +#include +#endif +int main() { +ospeed = 0; +; return 0; } +EOF +if { (eval echo configure:3180: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + nmh_cv_decl_ospeed_include_defines=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_decl_ospeed_include_defines=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_decl_ospeed_include_defines" 1>&6 + +if test $nmh_cv_decl_ospeed_include_defines = no; then + echo $ac_n "checking if you must define ospeed""... $ac_c" 1>&6 +echo "configure:3196: checking if you must define ospeed" >&5 +if eval "test \"`echo '$''{'nmh_cv_decl_ospeed_must_define'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext <&5; (eval $ac_link) 2>&5; } && test -s conftest; then + rm -rf conftest* + nmh_cv_decl_ospeed_must_define=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_decl_ospeed_must_define=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_decl_ospeed_must_define" 1>&6 +fi + +if test $nmh_cv_decl_ospeed_include_defines = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_OSPEED 1 +EOF + +elif test $nmh_cv_decl_ospeed_must_define = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_OSPEED 1 +EOF + + cat >> confdefs.h <<\EOF +#define MUST_DEFINE_OSPEED 1 +EOF + +fi + + +LIBS="$nmh_save_LIBS" + +echo $ac_n "checking return type of signal handlers""... $ac_c" 1>&6 +echo "configure:3243: checking return type of signal handlers" >&5 +if eval "test \"`echo '$''{'ac_cv_type_signal'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +#ifdef signal +#undef signal +#endif +#ifdef __cplusplus +extern "C" void (*signal (int, void (*)(int)))(int); +#else +void (*signal ()) (); +#endif + +int main() { +int i; +; return 0; } +EOF +if { (eval echo configure:3265: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_type_signal=void +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_type_signal=int +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_type_signal" 1>&6 +cat >> confdefs.h <&6 +echo "configure:3284: checking for pid_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_pid_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "pid_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_pid_t=yes +else + rm -rf conftest* + ac_cv_type_pid_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_pid_t" 1>&6 +if test $ac_cv_type_pid_t = no; then + cat >> confdefs.h <<\EOF +#define pid_t int +EOF + +fi + +echo $ac_n "checking for off_t""... $ac_c" 1>&6 +echo "configure:3317: checking for off_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_off_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "off_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_off_t=yes +else + rm -rf conftest* + ac_cv_type_off_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_off_t" 1>&6 +if test $ac_cv_type_off_t = no; then + cat >> confdefs.h <<\EOF +#define off_t long +EOF + +fi + +echo $ac_n "checking for uid_t in sys/types.h""... $ac_c" 1>&6 +echo "configure:3350: checking for uid_t in sys/types.h" >&5 +if eval "test \"`echo '$''{'ac_cv_type_uid_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "uid_t" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_uid_t=yes +else + rm -rf conftest* + ac_cv_type_uid_t=no +fi +rm -f conftest* + +fi + +echo "$ac_t""$ac_cv_type_uid_t" 1>&6 +if test $ac_cv_type_uid_t = no; then + cat >> confdefs.h <<\EOF +#define uid_t int +EOF + + cat >> confdefs.h <<\EOF +#define gid_t int +EOF + +fi + +echo $ac_n "checking for mode_t""... $ac_c" 1>&6 +echo "configure:3384: checking for mode_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_mode_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "mode_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_mode_t=yes +else + rm -rf conftest* + ac_cv_type_mode_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_mode_t" 1>&6 +if test $ac_cv_type_mode_t = no; then + cat >> confdefs.h <<\EOF +#define mode_t int +EOF + +fi + +echo $ac_n "checking for size_t""... $ac_c" 1>&6 +echo "configure:3417: checking for size_t" >&5 +if eval "test \"`echo '$''{'ac_cv_type_size_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#if STDC_HEADERS +#include +#include +#endif +EOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + egrep "size_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then + rm -rf conftest* + ac_cv_type_size_t=yes +else + rm -rf conftest* + ac_cv_type_size_t=no +fi +rm -f conftest* + +fi +echo "$ac_t""$ac_cv_type_size_t" 1>&6 +if test $ac_cv_type_size_t = no; then + cat >> confdefs.h <<\EOF +#define size_t unsigned +EOF + +fi + + +echo $ac_n "checking for sigset_t""... $ac_c" 1>&6 +echo "configure:3451: checking for sigset_t" >&5 +if eval "test \"`echo '$''{'nmh_cv_type_sigset_t'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +int main() { +sigset_t tempsigset; +; return 0; } +EOF +if { (eval echo configure:3464: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + nmh_cv_type_sigset_t=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_type_sigset_t=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_type_sigset_t" 1>&6 +if test $nmh_cv_type_sigset_t = no; then + cat >> confdefs.h <<\EOF +#define sigset_t unsigned int +EOF + +fi + +echo $ac_n "checking for st_blksize in struct stat""... $ac_c" 1>&6 +echo "configure:3485: checking for st_blksize in struct stat" >&5 +if eval "test \"`echo '$''{'ac_cv_struct_st_blksize'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +#include +int main() { +struct stat s; s.st_blksize; +; return 0; } +EOF +if { (eval echo configure:3498: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + ac_cv_struct_st_blksize=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + ac_cv_struct_st_blksize=no +fi +rm -f conftest* +fi + +echo "$ac_t""$ac_cv_struct_st_blksize" 1>&6 +if test $ac_cv_struct_st_blksize = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_ST_BLKSIZE 1 +EOF + +fi + + +echo $ac_n "checking for tm_gmtoff in struct tm""... $ac_c" 1>&6 +echo "configure:3520: checking for tm_gmtoff in struct tm" >&5 +if eval "test \"`echo '$''{'nmh_cv_struct_tm_gmtoff'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + cat > conftest.$ac_ext < +# include +#else +# ifdef TM_IN_SYS_TIME +# include +# else +# include +# endif +#endif +int main() { +struct tm temptm; temptm.tm_gmtoff = 0; +; return 0; } +EOF +if { (eval echo configure:3541: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then + rm -rf conftest* + nmh_cv_struct_tm_gmtoff=yes +else + echo "configure: failed program was:" >&5 + cat conftest.$ac_ext >&5 + rm -rf conftest* + nmh_cv_struct_tm_gmtoff=no +fi +rm -f conftest* +fi + +echo "$ac_t""$nmh_cv_struct_tm_gmtoff" 1>&6 +if test $nmh_cv_struct_tm_gmtoff = yes; then + cat >> confdefs.h <<\EOF +#define HAVE_TM_GMTOFF 1 +EOF + +fi + +echo $ac_n "checking what style of signals to use""... $ac_c" 1>&6 +echo "configure:3562: checking what style of signals to use" >&5 +if test $ac_cv_func_sigaction = yes -a $ac_cv_func_sigprocmask = yes; then + signals_style=POSIX_SIGNALS + cat >> confdefs.h <<\EOF +#define POSIX_SIGNALS 1 +EOF + + cat >> confdefs.h <<\EOF +#define RELIABLE_SIGNALS 1 +EOF + +elif test $ac_cv_func_sigblock = yes -a $ac_cv_func_sigsetmask = yes; then + signals_style=BSD_SIGNALS + cat >> confdefs.h <<\EOF +#define BSD_SIGNALS 1 +EOF + + cat >> confdefs.h <<\EOF +#define RELIABLE_SIGNALS 1 +EOF + +elif test $ac_cv_func_sighold = yes -a $ac_cv_func_sigrelse = yes; then + signals_style=SYSV_SIGNALS + cat >> confdefs.h <<\EOF +#define SYSV_SIGNALS 1 +EOF + +else + signals_style=NO_SIGNAL_BLOCKING + cat >> confdefs.h <<\EOF +#define NO_SIGNAL_BLOCKING 1 +EOF + +fi + +echo "$ac_t""$signals_style" 1>&6 + +echo $ac_n "checking where signal.h is located""... $ac_c" 1>&6 +echo "configure:3600: checking where signal.h is located" >&5 +if eval "test \"`echo '$''{'nmh_cv_path_signal_h'+set}'`\" = set"; then + echo $ac_n "(cached) $ac_c" 1>&6 +else + for SIGNAL_H in /usr/include/bsd/sys/signal.h /usr/include/asm/signal.h /usr/include/asm/signum.h /usr/include/linux/signal.h /usr/include/sys/signal.h /dev/null; do + test -f $SIGNAL_H && \ + grep '#[ ]*define[ ][ ]*SIG[0-9A-Z]*[ ]*[0-9][0-9]*' $SIGNAL_H > /dev/null && \ + break +done +nmh_cv_path_signal_h=$SIGNAL_H + +fi + +echo "$ac_t""$nmh_cv_path_signal_h" 1>&6 +SIGNAL_H=$nmh_cv_path_signal_h + +trap '' 1 2 15 +cat > confcache <<\EOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs. It is not useful on other systems. +# If it contains results you don't want to keep, you may remove or edit it. +# +# By default, configure uses ./config.cache as the cache file, +# creating it if it does not exist already. You can give configure +# the --cache-file=FILE option to use a different cache file; that is +# what configure does when it calls configure scripts in +# subdirectories, so they share the cache. +# Giving --cache-file=/dev/null disables caching, for debugging configure. +# config.status only pays attention to the cache file if you give it the +# --recheck option to rerun configure. +# +EOF +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, don't put newlines in cache variables' values. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +(set) 2>&1 | + case `(ac_space=' '; set) 2>&1` in + *ac_space=\ *) + # `set' does not quote correctly, so add quotes (double-quote substitution + # turns \\\\ into \\, and sed turns \\ into \). + sed -n \ + -e "s/'/'\\\\''/g" \ + -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p" + ;; + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p' + ;; + esac >> confcache +if cmp -s $cache_file confcache; then + : +else + if test -w $cache_file; then + echo "updating cache $cache_file" + cat confcache > $cache_file + else + echo "not updating unwritable cache $cache_file" + fi +fi +rm -f confcache + +trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15 + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Any assignment to VPATH causes Sun make to only execute +# the first set of double-colon rules, so remove it if not needed. +# If there is a colon in the path, we need to keep it. +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d' +fi + +trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 + +DEFS=-DHAVE_CONFIG_H + +# Without the "./", some shells look in PATH for config.status. +: ${CONFIG_STATUS=./config.status} + +echo creating $CONFIG_STATUS +rm -f $CONFIG_STATUS +cat > $CONFIG_STATUS </dev/null | sed 1q`: +# +# $0 $ac_configure_args +# +# Compiler output produced by configure, useful for debugging +# configure, is in ./config.log if it exists. + +ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]" +for ac_option +do + case "\$ac_option" in + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion" + exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;; + -version | --version | --versio | --versi | --vers | --ver | --ve | --v) + echo "$CONFIG_STATUS generated by autoconf version 2.12" + exit 0 ;; + -help | --help | --hel | --he | --h) + echo "\$ac_cs_usage"; exit 0 ;; + *) echo "\$ac_cs_usage"; exit 1 ;; + esac +done + +ac_given_srcdir=$srcdir +ac_given_INSTALL="$INSTALL" + +trap 'rm -fr `echo "Makefile config/Makefile h/Makefile sbr/Makefile uip/Makefile \ + zotnet/Makefile zotnet/mts/Makefile zotnet/tws/Makefile \ + zotnet/mf/Makefile zotnet/bboards/Makefile mts/Makefile \ + mts/smtp/Makefile mts/sendmail/Makefile mts/mmdf/Makefile \ + etc/Makefile man/Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15 +EOF +cat >> $CONFIG_STATUS < conftest.subs <<\\CEOF +$ac_vpsub +$extrasub +s%@CFLAGS@%$CFLAGS%g +s%@CPPFLAGS@%$CPPFLAGS%g +s%@CXXFLAGS@%$CXXFLAGS%g +s%@DEFS@%$DEFS%g +s%@LDFLAGS@%$LDFLAGS%g +s%@LIBS@%$LIBS%g +s%@exec_prefix@%$exec_prefix%g +s%@prefix@%$prefix%g +s%@program_transform_name@%$program_transform_name%g +s%@bindir@%$bindir%g +s%@sbindir@%$sbindir%g +s%@libexecdir@%$libexecdir%g +s%@datadir@%$datadir%g +s%@sysconfdir@%$sysconfdir%g +s%@sharedstatedir@%$sharedstatedir%g +s%@localstatedir@%$localstatedir%g +s%@libdir@%$libdir%g +s%@includedir@%$includedir%g +s%@oldincludedir@%$oldincludedir%g +s%@infodir@%$infodir%g +s%@mandir@%$mandir%g +s%@VERSION@%$VERSION%g +s%@MTS@%$MTS%g +s%@MTSLIB@%$MTSLIB%g +s%@POPLIB@%$POPLIB%g +s%@POPSED@%$POPSED%g +s%@CC@%$CC%g +s%@SET_MAKE@%$SET_MAKE%g +s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g +s%@INSTALL_DATA@%$INSTALL_DATA%g +s%@RANLIB@%$RANLIB%g +s%@AWK@%$AWK%g +s%@LEX@%$LEX%g +s%@LEXLIB@%$LEXLIB%g +s%@LORDER@%$LORDER%g +s%@TSORT@%$TSORT%g +s%@sendmailpath@%$sendmailpath%g +s%@morepath@%$morepath%g +s%@pagerpath@%$pagerpath%g +s%@vipath@%$vipath%g +s%@editorpath@%$editorpath%g +s%@mailspool@%$mailspool%g +s%@CPP@%$CPP%g +s%@LIBOBJS@%$LIBOBJS%g +s%@TERMLIB@%$TERMLIB%g +s%@HESIOD_INCLUDES@%$HESIOD_INCLUDES%g +s%@HESIOD_LIBS@%$HESIOD_LIBS%g +s%@KRB4_INCLUDES@%$KRB4_INCLUDES%g +s%@KRB4_LIBS@%$KRB4_LIBS%g +s%@SIGNAL_H@%$SIGNAL_H%g + +CEOF +EOF + +cat >> $CONFIG_STATUS <<\EOF + +# Split the substitutions into bite-sized pieces for seds with +# small command number limits, like on Digital OSF/1 and HP-UX. +ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script. +ac_file=1 # Number of current file. +ac_beg=1 # First line for current file. +ac_end=$ac_max_sed_cmds # Line after last line for current file. +ac_more_lines=: +ac_sed_cmds="" +while $ac_more_lines; do + if test $ac_beg -gt 1; then + sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file + else + sed "${ac_end}q" conftest.subs > conftest.s$ac_file + fi + if test ! -s conftest.s$ac_file; then + ac_more_lines=false + rm -f conftest.s$ac_file + else + if test -z "$ac_sed_cmds"; then + ac_sed_cmds="sed -f conftest.s$ac_file" + else + ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file" + fi + ac_file=`expr $ac_file + 1` + ac_beg=$ac_end + ac_end=`expr $ac_end + $ac_max_sed_cmds` + fi +done +if test -z "$ac_sed_cmds"; then + ac_sed_cmds=cat +fi +EOF + +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories. + + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`" + # A "../" for each directory in $ac_dir_suffix. + ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'` + else + ac_dir_suffix= ac_dots= + fi + + case "$ac_given_srcdir" in + .) srcdir=. + if test -z "$ac_dots"; then top_srcdir=. + else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;; + /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;; + *) # Relative path. + srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix" + top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + case "$ac_given_INSTALL" in + [/$]*) INSTALL="$ac_given_INSTALL" ;; + *) INSTALL="$ac_dots$ac_given_INSTALL" ;; + esac + + echo creating "$ac_file" + rm -f "$ac_file" + configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure." + case "$ac_file" in + *Makefile*) ac_comsub="1i\\ +# $configure_input" ;; + *) ac_comsub= ;; + esac + + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + sed -e "$ac_comsub +s%@configure_input@%$configure_input%g +s%@srcdir@%$srcdir%g +s%@top_srcdir@%$top_srcdir%g +s%@INSTALL@%$INSTALL%g +" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file +fi; done +rm -f conftest.s* + +# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where +# NAME is the cpp macro being defined and VALUE is the value it is being given. +# +# ac_d sets the value in "#define NAME VALUE" lines. +ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)' +ac_dB='\([ ][ ]*\)[^ ]*%\1#\2' +ac_dC='\3' +ac_dD='%g' +# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE". +ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_uB='\([ ]\)%\1#\2define\3' +ac_uC=' ' +ac_uD='\4%g' +# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE". +ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)' +ac_eB='$%\1#\2define\3' +ac_eC=' ' +ac_eD='%g' + +if test "${CONFIG_HEADERS+set}" != set; then +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +fi +for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then + # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in". + case "$ac_file" in + *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'` + ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + *) ac_file_in="${ac_file}.in" ;; + esac + + echo creating $ac_file + + rm -f conftest.frag conftest.in conftest.out + ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"` + cat $ac_file_inputs > conftest.in + +EOF + +# Transform confdefs.h into a sed script conftest.vals that substitutes +# the proper values into config.h.in to produce config.h. And first: +# Protect against being on the right side of a sed subst in config.status. +# Protect against being in an unquoted here document in config.status. +rm -f conftest.vals +cat > conftest.hdr <<\EOF +s/[\\&%]/\\&/g +s%[\\$`]%\\&%g +s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp +s%ac_d%ac_u%gp +s%ac_u%ac_e%gp +EOF +sed -n -f conftest.hdr confdefs.h > conftest.vals +rm -f conftest.hdr + +# This sed command replaces #undef with comments. This is necessary, for +# example, in the case of _POSIX_SOURCE, which is predefined and required +# on some systems where configure will not decide to define it. +cat >> conftest.vals <<\EOF +s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */% +EOF + +# Break up conftest.vals because some shells have a limit on +# the size of here documents, and old seds have small limits too. + +rm -f conftest.tail +while : +do + ac_lines=`grep -c . conftest.vals` + # grep -c gives empty output for an empty file on some AIX systems. + if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi + # Write a limited-size here document to conftest.frag. + echo ' cat > conftest.frag <> $CONFIG_STATUS + sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS + echo 'CEOF + sed -f conftest.frag conftest.in > conftest.out + rm -f conftest.in + mv conftest.out conftest.in +' >> $CONFIG_STATUS + sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail + rm -f conftest.vals + mv conftest.tail conftest.vals +done +rm -f conftest.vals + +cat >> $CONFIG_STATUS <<\EOF + rm -f conftest.frag conftest.h + echo "/* $ac_file. Generated automatically by configure. */" > conftest.h + cat conftest.in >> conftest.h + rm -f conftest.in + if cmp -s $ac_file conftest.h 2>/dev/null; then + echo "$ac_file is unchanged" + rm -f conftest.h + else + # Remove last slash and all that follows it. Not all systems have dirname. + ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'` + if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then + # The file is in a subdirectory. + test ! -d "$ac_dir" && mkdir "$ac_dir" + fi + rm -f $ac_file + mv conftest.h $ac_file + fi +fi; done + +EOF +cat >> $CONFIG_STATUS <> $CONFIG_STATUS <<\EOF +\ + test -z "$CONFIG_HEADERS" || echo > stamp-h +exit 0 +EOF +chmod +x $CONFIG_STATUS +rm -fr confdefs* $ac_clean_files +test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1 + + +eval "nmhbin=${bindir}"; eval "nmhbin2=${nmhbin}" +eval "nmhsysconf=${sysconfdir}"; eval "nmhsysconf2=${nmhsysconf}" +eval "nmhlib=${libdir}"; eval "nmhlib2=${nmhlib}" +eval "nmhman=${mandir}" + +echo " +nmh configuration +----------------- +nmh version : ${VERSION} +compiler : ${CC} +compiler flags : ${CFLAGS} +linker flags : ${LDFLAGS} +source code location : ${srcdir} +binary install path : ${nmhbin2} +libary install path : ${nmhlib2} +config files install path : ${nmhsysconf2} +man page install path : ${nmhman} +transport system : ${MTS} +default editor : ${editorpath} +default pager : ${pagerpath}" +echo "" diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..f544ac6 --- /dev/null +++ b/configure.in @@ -0,0 +1,512 @@ +dnl +dnl configure.in -- autoconf template for nmh +dnl +dnl $Id$ +dnl + +AC_INIT(h/nmh.h) +AC_CONFIG_HEADER(config.h) + +dnl What version of nmh are we building? +VERSION=`sed -e 's/nmh-//' ${srcdir}/VERSION` +echo "configuring for nmh-$VERSION" +AC_SUBST(VERSION)dnl + +dnl ------------------------- +dnl CHECK COMMAND LINE OPTION +dnl ------------------------- +dnl What method of posting should post use? +undefine([mts])dnl +AC_ARG_WITH(mts, +[ --with-mts=MTS specify the mail transport agent]) + +if test x$with_mts = xsmtp; then + MTS="smtp" + MTSLIB="mts/smtp/libsmtp.a" + AC_DEFINE(SMTPMTS)dnl +elif test x$with_mts = xsendmail; then + MTS="sendmail" + MTSLIB="mts/sendmail/libsend.a" + AC_DEFINE(SENDMTS)dnl +else + MTS="smtp" + MTSLIB="mts/smtp/libsmtp.a" + AC_DEFINE(SMTPMTS)dnl +fi + +AC_SUBST(MTS) +AC_SUBST(MTSLIB) + +dnl What should be the default editor? +undefine([editor])dnl +AC_ARG_WITH(editor, +[ --with-editor=EDITOR specify the default editor]) + +if test -n "$with_editor"; then + editorpath="$with_editor" +fi + +dnl What should be the default pager? +undefine([pager])dnl +AC_ARG_WITH(pager, +[ --with-pager=PAGER specify the default pager]) + +if test -n "$with_pager"; then + pagerpath="$with_pager" +fi + +dnl Do you want mhe support? +undefine([nmh-mhe])dnl +AC_ARG_ENABLE(nmh-mhe, +[ --enable-nmh-mhe enable mhe support (DEFAULT)]) + +dnl mhe support is on by default, so define it unless +dnl explicitly disabled. +if test x$enable_nmh_mhe != xno; then + AC_DEFINE(MHE)dnl +fi + +dnl Do you want client-side support for pop +undefine([nmh-pop])dnl +AC_ARG_ENABLE(nmh-pop, +[ --enable-nmh-pop enable client-side support for pop]) +if test x$enable_nmh_pop = xyes; then + AC_DEFINE(POP)dnl + POPLIB=popsbr.o + POPSED='/^%nmhbeginpop%/d;/^%nmhendpop%/d' +else + POPSED='/^%nmhbeginpop%/,/^%nmhendpop%/d' +fi +AC_SUBST(POPLIB)dnl +AC_SUBST(POPSED)dnl + +dnl Do you want client-side support for kpop +AC_ARG_WITH(krb4, +[ --with-krb4=PREFIX specify location of Kerberos V4 for kpop support]) +if test x$with_krb4 != x -a x$with_krb4 != xno; then + AC_DEFINE(KPOP)dnl + AC_DEFINE(KPOP_PRINCIPAL, "pop")dnl +fi + +dnl Do you want support for hesiod +AC_ARG_WITH(hesiod, +[ --with-hesiod=PREFIX specify location of Hesiod]) +if test x$with_hesiod != x -a x$with_hesiod != xno; then + AC_DEFINE(HESIOD)dnl +fi + +dnl Do you want to debug nmh? +undefine([nmh-debug])dnl +AC_ARG_ENABLE(nmh-debug, +[ --enable-nmh-debug enable nmh code debugging]) + +dnl ---------------------------------------------------- +dnl Default location is /usr/local/nmh/{bin,etc,lib,man} +dnl ---------------------------------------------------- +AC_PREFIX_DEFAULT(/usr/local/nmh) + +dnl ------------------ +dnl CHECK THE COMPILER +dnl ------------------ +dnl We want these before the checks, +dnl so the checks can modify their values. +test -z "$CFLAGS" && CFLAGS= auto_cflags=1 +if test x$enable_nmh_debug = xyes; then + test -z "$LDFLAGS" && LDFLAGS=-g +fi + +AC_PROG_CC + +dnl if the user hasn't specified CFLAGS, then +dnl if compiler is gcc, then +dnl use -O2 and some warning flags +dnl else use -O +if test -n "$auto_cflags"; then + if test x$enable_nmh_debug = xyes; then + if test -n "$GCC"; then + test -z "$CFLAGS" && CFLAGS="-Wall -g" || CFLAGS="$CFLAGS -Wall -g" + else + test -z "$CFLAGS" && CFLAGS=-g || CFLAGS="$CFLAGS -g" + fi + else + test -z "$LDFLAGS" && LDFLAGS=-s + if test -n "$GCC"; then + test -z "$CFLAGS" && CFLAGS=-O2 || CFLAGS="$CFLAGS -O2" + else + test -z "$CFLAGS" && CFLAGS=-O || CFLAGS="$CFLAGS -O" + fi + fi +fi + +AC_C_CONST dnl Does compiler support `const'. + +dnl ------------------ +dnl CHECK FOR PROGRAMS +dnl ------------------ +AC_PROG_MAKE_SET dnl Does make define $MAKE +AC_PROG_INSTALL dnl Check for BSD compatible `install' +AC_PROG_RANLIB dnl Check for `ranlib' +AC_PROG_AWK dnl Check for mawk,gawk,nawk, then awk +AC_PROG_LEX dnl Check for lex/flex + +dnl Check for lorder and tsort commands +AC_CHECK_PROG(LORDER, lorder, lorder, no)dnl +AC_CHECK_PROG(TSORT, tsort, tsort, no)dnl + +dnl If either doesn't exist, replace them with echo and cat +if test x$ac_cv_prog_LORDER != xlorder -o x$ac_cv_prog_TSORT != xtsort; then + LORDER=echo + TSORT=cat + AC_SUBST(LORDER)dnl + AC_SUBST(TSORT)dnl +fi + +dnl Look for `sendmail' +pathtmp=/usr/lib:/usr/sbin:/usr/etc:/usr/ucblib:/usr/bin:/bin +AC_PATH_PROG(sendmailpath, sendmail, no, [$pathtmp]) + +dnl Look for `more' +pathtmp=/usr/bin:/bin:/usr/ucb:/usr/local/bin +AC_PATH_PROG(morepath, more, no, [$pathtmp]) + +dnl If pager is not specified yet, +dnl then use `more' as the default. +if test -z "$pagerpath"; then + pagerpath="$morepath" +fi +AC_SUBST(pagerpath)dnl + +dnl Look for `vi' +pathtmp=/usr/bin:/bin:/usr/ucb:/usr/local/bin +AC_PATH_PROG(vipath, vi, no, [$pathtmp]) + +dnl If editor is not specified yet, +dnl then use `vi' as the default. +if test -z "$editorpath"; then + editorpath="$vipath" +fi +AC_SUBST(editorpath)dnl + +dnl Check for broken vi +AC_CACHE_CHECK(for broken vi, nmh_cv_attvibug, +[if echo 'r /nonexist-file +q' | ex > /dev/null 2>&1 +then + nmh_cv_attvibug=no +else + nmh_cv_attvibug=yes +fi]) + +if test "$nmh_cv_attvibug" = yes; then + AC_DEFINE(ATTVIBUG) +fi + +dnl --------------- +dnl FIND MAIL SPOOL +dnl --------------- +AC_CACHE_CHECK(where mail spool is located, nmh_cv_mailspool, +[for mailspool in /var/mail dnl + /var/spool/mail dnl + /usr/spool/mail dnl + /dev/null; dnl Just in case we fall through +do + test -d $mailspool && break +done +nmh_cv_mailspool=$mailspool +]) +mailspool=$nmh_cv_mailspool +AC_SUBST(mailspool)dnl + +dnl ------------------ +dnl CHECK HEADER FILES +dnl ------------------ +AC_HEADER_DIRENT +AC_HEADER_STDC +AC_HEADER_TIME +AC_HEADER_SYS_WAIT +AC_HEADER_STAT +AC_CHECK_HEADERS(string.h memory.h stdlib.h unistd.h errno.h fcntl.h \ + limits.h crypt.h termcap.h termio.h termios.h locale.h \ + sys/param.h sys/time.h sys/utsname.h arpa/inet.h \ + arpa/ftp.h) + +AC_CACHE_CHECK(POSIX termios, nmh_cv_sys_posix_termios, +[AC_TRY_LINK([#include +#include +#include ], +[/* SunOS 4.0.3 has termios.h but not the library calls. */ +tcgetattr(0, 0);], + nmh_cv_sys_posix_termios=yes, nmh_cv_sys_posix_termios=no)]) + +if test $nmh_cv_sys_posix_termios = yes; then + AC_CACHE_CHECK(TIOCGWINSZ in termios.h, + nmh_cv_header_termios_h_tiocgwinsz, + [AC_TRY_LINK([#include +#include ], + [int x = TIOCGWINSZ;], + nmh_cv_header_termios_h_tiocgwinsz=yes, + nmh_cv_header_termios_h_tiocgwinsz=no)]) +else + nmh_cv_header_termios_h_tiocgwinsz=no +fi + +if test $nmh_cv_header_termios_h_tiocgwinsz = no; then + AC_CACHE_CHECK(TIOCGWINSZ in sys/ioctl.h, + nmh_cv_header_sys_ioctl_h_tiocgwinsz, + [AC_TRY_LINK([#include +#include ], + [int x = TIOCGWINSZ;], + nmh_cv_header_sys_ioctl_h_tiocgwinsz=yes, + nmh_cv_header_sys_ioctl_h_tiocgwinsz=no)]) + if test $nmh_cv_header_sys_ioctl_h_tiocgwinsz = yes; then + AC_DEFINE(GWINSZ_IN_SYS_IOCTL) + fi +fi + +AC_CHECK_HEADER([sys/ptem.h], AC_DEFINE(WINSIZE_IN_PTEM)) + +dnl --------------- +dnl CHECK FUNCTIONS +dnl --------------- +AC_FUNC_VFORK +AC_CHECK_FUNCS(waitpid wait3 sigaction sigprocmask sigblock sigsetmask \ + sighold sigrelse writev lstat uname tzset killpg \ + sigsetjmp) + +AC_REPLACE_FUNCS(snprintf strerror strdup) + +dnl ------------------- +dnl CHECK FOR LIBRARIES +dnl ------------------- +dnl Checks for network libraries (nsl, socket) +AC_CHECK_NETLIBS + +dnl Check for bug in libraries such that ruserpass +dnl needs to be linked as _ruserpass. +AC_CHECK_RUSERPASS + +termcap_curses_order="termcap curses ncurses" +for lib in $termcap_curses_order; do + AC_CHECK_LIB(${lib}, tgetent, [TERMLIB="-l$lib"; break]) +done +AC_SUBST(TERMLIB)dnl + +dnl -------------- +dnl CHECK FOR NDBM +dnl -------------- +dnl Checks for ndbm +AC_CHECK_FUNC(dbm_open, , + AC_CHECK_LIB(ndbm, dbm_open, , + AC_CHECK_LIB(dbm, dbm_open))) + +dnl ---------------- +dnl CHECK FOR HESIOD +dnl ---------------- +if test x$with_hesiod != x -a x$with_hesiod != xno; then + if test x$with_hesiod != xyes; then + HESIOD_INCLUDES="-I$with_hesiod/include" + HESIOD_LIBS="-L$with_hesiod/lib" + fi + AC_CHECK_FUNC(res_send, , + AC_CHECK_LIB(resolv, res_send)) + AC_CHECK_LIB(hesiod, hes_resolve, [HESIOD_LIBS="$HESIOD_LIBS -lhesiod"], + [AC_MSG_ERROR(Hesiod library not found)], $HESIOD_LIBS) +fi +AC_SUBST(HESIOD_INCLUDES)dnl +AC_SUBST(HESIOD_LIBS)dnl + +dnl ---------------------------------- +dnl CHECK FOR KRB4 (Kerberos4 support) +dnl ---------------------------------- +if test x$with_krb4 != x -a x$with_krb4 != xno; then + if test x$with_krb4 != xyes; then + KRB4_INCLUDES="-I$with_krb4/include" + if test -d "$with_krb4/include/kerberosIV"; then + KRB4_INCLUDES="$KRB4_INCLUDES -I$with_krb4/include/kerberosIV" + fi + KRB4_LIBS="-L$with_krb4/lib" + elif test -d /usr/include/kerberosIV; then + KRB4_INCLUDES="-I/usr/include/kerberosIV" + fi + AC_CHECK_LIB(krb4, krb_rd_req, + [KRB4_LIBS="$KRB4_LIBS -lkrb4 -ldes425 -lkrb5 -lcrypto -lcom_err"], + [AC_CHECK_LIB(krb, krb_rd_req, + [KRB4_LIBS="-lkrb -ldes"], + [AC_MSG_ERROR(Kerberos 4 libraries not found)], + $KRB4_LIBS -ldes)], + $KRB4_LIBS -ldes425 -lkrb5 -lcrypto -lcom_err) +fi +AC_SUBST(KRB4_INCLUDES)dnl +AC_SUBST(KRB4_LIBS)dnl + +dnl --------------------- +dnl CHECK TERMCAP LIBRARY +dnl --------------------- + +dnl Add the termcap library, so that the following configure +dnl tests will find it when it tries to link test programs. +nmh_save_LIBS="$LIBS" +LIBS="$TERMLIB $LIBS" + +dnl Checks for external variable ospeed in the termcap library. +AC_CACHE_CHECK(if an include file defines ospeed, +nmh_cv_decl_ospeed_include_defines, +[AC_TRY_LINK( +[#include +#if HAVE_TERMIOS_H +#include +#endif +#if HAVE_TERMCAP_H +#include +#endif], [ospeed = 0;], +nmh_cv_decl_ospeed_include_defines=yes, +nmh_cv_decl_ospeed_include_defines=no)]) + +if test $nmh_cv_decl_ospeed_include_defines = no; then + AC_CACHE_CHECK(if you must define ospeed, + nmh_cv_decl_ospeed_must_define, + [AC_TRY_LINK( ,[extern short ospeed; ospeed = 0;], + nmh_cv_decl_ospeed_must_define=yes, + nmh_cv_decl_ospeed_must_define=no)]) +fi + +if test $nmh_cv_decl_ospeed_include_defines = yes; then + AC_DEFINE(HAVE_OSPEED) +elif test $nmh_cv_decl_ospeed_must_define = yes; then + AC_DEFINE(HAVE_OSPEED) + AC_DEFINE(MUST_DEFINE_OSPEED) +fi + +dnl dnl Checks if tgetent accepts NULL and will +dnl dnl allocate its own termcap buffer. +dnl AC_CACHE_CHECK(if tgetent accepts NULL, +dnl nmh_cv_func_tgetent_accepts_null, +dnl [AC_TRY_RUN([main(){int i = tgetent((char*)0,"vt100");exit(!i || i == -1);}], +dnl nmh_cv_func_tgetent_accepts_null=yes, +dnl nmh_cv_func_tgetent_accepts_null=no, +dnl nmh_cv_func_tgetent_accepts_null=no)]) +dnl if test $nmh_cv_func_tgetent_accepts_null = yes; then +dnl AC_DEFINE(TGETENT_ACCEPTS_NULL) +dnl fi + +dnl Now put the libraries back to what it was before we +dnl starting checking the termcap library. +LIBS="$nmh_save_LIBS" + +dnl -------------- +dnl CHECK TYPEDEFS +dnl -------------- +AC_TYPE_SIGNAL +AC_TYPE_PID_T +AC_TYPE_OFF_T +AC_TYPE_UID_T +AC_TYPE_MODE_T +AC_TYPE_SIZE_T + +dnl Check for sigset_t. Currently I'm looking in +dnl and . Others might need +dnl to be added. +AC_CACHE_CHECK(for sigset_t, nmh_cv_type_sigset_t, +[AC_TRY_COMPILE( +[#include +#include ], [sigset_t tempsigset;], + nmh_cv_type_sigset_t=yes, nmh_cv_type_sigset_t=no)]) +if test $nmh_cv_type_sigset_t = no; then + AC_DEFINE(sigset_t, unsigned int) +fi + +dnl ---------------- +dnl CHECK STRUCTURES +dnl ---------------- +AC_STRUCT_ST_BLKSIZE + +AC_CACHE_CHECK(for tm_gmtoff in struct tm, nmh_cv_struct_tm_gmtoff, +[AC_TRY_COMPILE( +[#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef TM_IN_SYS_TIME +# include +# else +# include +# endif +#endif], +[struct tm temptm; temptm.tm_gmtoff = 0;], + nmh_cv_struct_tm_gmtoff=yes, nmh_cv_struct_tm_gmtoff=no)]) +if test $nmh_cv_struct_tm_gmtoff = yes; then + AC_DEFINE(HAVE_TM_GMTOFF) +fi + +dnl ------------- +dnl CHECK SIGNALS +dnl ------------- +dnl What style of signal do you have (POSIX, BSD, or SYSV)? +AC_MSG_CHECKING(what style of signals to use) +if test $ac_cv_func_sigaction = yes -a $ac_cv_func_sigprocmask = yes; then + signals_style=POSIX_SIGNALS + AC_DEFINE(POSIX_SIGNALS) + AC_DEFINE(RELIABLE_SIGNALS) +elif test $ac_cv_func_sigblock = yes -a $ac_cv_func_sigsetmask = yes; then + signals_style=BSD_SIGNALS + AC_DEFINE(BSD_SIGNALS) + AC_DEFINE(RELIABLE_SIGNALS) +elif test $ac_cv_func_sighold = yes -a $ac_cv_func_sigrelse = yes; then + signals_style=SYSV_SIGNALS + AC_DEFINE(SYSV_SIGNALS) +else + signals_style=NO_SIGNAL_BLOCKING + AC_DEFINE(NO_SIGNAL_BLOCKING) +fi + +AC_MSG_RESULT($signals_style) + +dnl Where is located? Needed as input for signames.awk +AC_CACHE_CHECK(where signal.h is located, nmh_cv_path_signal_h, +[for SIGNAL_H in /usr/include/bsd/sys/signal.h dnl Next + /usr/include/asm/signal.h dnl Linux 1.3.0 and above + /usr/include/asm/signum.h dnl some versions of Linux/Alpha + /usr/include/linux/signal.h dnl Linux up to 1.2.11 + /usr/include/sys/signal.h dnl Almost everybody else + /dev/null; dnl Just in case we fall through +do + test -f $SIGNAL_H && \ + grep '#[ ]*define[ ][ ]*SIG[0-9A-Z]*[ ]*[0-9][0-9]*' $SIGNAL_H > /dev/null && \ + break +done +nmh_cv_path_signal_h=$SIGNAL_H +]) +SIGNAL_H=$nmh_cv_path_signal_h +AC_SUBST(SIGNAL_H)dnl + +dnl ---------------- +dnl OUTPUT MAKEFILES +dnl ---------------- +AC_OUTPUT(Makefile config/Makefile h/Makefile sbr/Makefile uip/Makefile \ + zotnet/Makefile zotnet/mts/Makefile zotnet/tws/Makefile \ + zotnet/mf/Makefile zotnet/bboards/Makefile mts/Makefile \ + mts/smtp/Makefile mts/sendmail/Makefile mts/mmdf/Makefile \ + etc/Makefile man/Makefile, \ + [test -z "$CONFIG_HEADERS" || echo > stamp-h]) + +eval "nmhbin=${bindir}"; eval "nmhbin2=${nmhbin}" +eval "nmhsysconf=${sysconfdir}"; eval "nmhsysconf2=${nmhsysconf}" +eval "nmhlib=${libdir}"; eval "nmhlib2=${nmhlib}" +eval "nmhman=${mandir}" + +echo " +nmh configuration +----------------- +nmh version : ${VERSION} +compiler : ${CC} +compiler flags : ${CFLAGS} +linker flags : ${LDFLAGS} +source code location : ${srcdir} +binary install path : ${nmhbin2} +libary install path : ${nmhlib2} +config files install path : ${nmhsysconf2} +man page install path : ${nmhman} +transport system : ${MTS} +default editor : ${editorpath} +default pager : ${pagerpath}" +echo "" diff --git a/etc/MailAliases b/etc/MailAliases new file mode 100644 index 0000000..c5be141 --- /dev/null +++ b/etc/MailAliases @@ -0,0 +1,29 @@ +; +; MailAliases -- nmh global aliases file +; +; $Id$ +; +; This file is used to define aliases that are valid for all mh users. +; This file is almost empty as MH now supports personal aliases. +; +; If you need to define system wide aliases such as "everyone", it is +; preferable that this be done as the mail transport level, so that they +; will be valid for users of other mail clients. + +; everyone: * + +; Blank lines and lines beginning with a ; are ignored. +; < file -> read more aliases from "file" +; foo: fum -> simple replacement +; foo: fum, fie -> list replacement +; foo: < file -> list replacement from "file" +; foo: = group -> list replacement from UNIX group +; foo: + group -> list replacement by ALL users in /etc/passwd +; with gid == group +; foo: * -> list replacement by ALL users in /etc/passwd +; with uid >= 200 +; foo*: fum -> matches foo (including the empty string) +; +; using a ';' instead of a ':' indicates that the alias should be displayed +; along with the addresses used (normally, the addresses replace the alias +; completely) diff --git a/etc/Makefile.in b/etc/Makefile.in new file mode 100644 index 0000000..67eed05 --- /dev/null +++ b/etc/Makefile.in @@ -0,0 +1,134 @@ +# +# Makefile for etc subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +mailspool = @mailspool@ + +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_DATA = @INSTALL_DATA@ + +# Path to search for programs to handle MIME +# content. Used to create mhn.defaults +MHNSEARCHPATH = "$(PATH):/usr/demo/SOUND" + +# Program used to search path for various programs to +# handle MIME content. Used to create mhn.defaults +MHNSEARCHPROG = $(srcdir)/mhn.find.sh + +SED = sed + +.SUFFIXES: + +# format and components files +DIST_FILES = mhl.body mhl.digest mhl.format mhl.forward mhl.headers \ + mhl.reply scan.default scan.mailx scan.nomime scan.size scan.time \ + scan.timely scan.unseen components digestcomps distcomps \ + forwcomps rcvdistcomps replcomps replgroupcomps MailAliases + +# format and configuration files to generate +GEN_FILES = mhn.defaults mts.conf + +# data files we need to install +FILES = $(DIST_FILES) $(GEN_FILES) + +# scripts to install +SCRIPTS = sendfiles + +# auxiliary files +AUX = Makefile.in mhn.defaults.sh mhn.find.sh mts.conf.in + +# all files in this directory included in the distribution +DIST = $(DIST_FILES) $(SCRIPTS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +all: $(GEN_FILES) + +mhn.defaults: $(srcdir)/mhn.defaults.sh $(MHNSEARCHPROG) + rm -f $@ + $(srcdir)/mhn.defaults.sh $(MHNSEARCHPATH) $(MHNSEARCHPROG) > $@ + +mts.conf: $(srcdir)/mts.conf.in + rm -f $@ + $(SED) -e 's,%mailspool%,$(mailspool),' \ + -e 's,%etcdir%,$(etcdir),' < $(srcdir)/mts.conf.in > $@ + +install: install-files install-scripts + +install-files: + $(top_srcdir)/mkinstalldirs $(etcdir) + for file in $(DIST_FILES); do \ + if [ -f $(etcdir)/$$file ]; then \ + mv $(etcdir)/$$file $(etcdir)/$$file.old; \ + fi; \ + $(INSTALL_DATA) $(srcdir)/$$file $(etcdir)/$$file; \ + done + for file in $(GEN_FILES); do \ + if [ -f $(etcdir)/$$file ]; then \ + mv $(etcdir)/$$file $(etcdir)/$$file.old; \ + fi; \ + $(INSTALL_DATA) $$file $(etcdir)/$$file; \ + done + +install-scripts: + $(top_srcdir)/mkinstalldirs $(libdir) + for script in $(SCRIPTS); do \ + $(INSTALL_PROGRAM) $(srcdir)/$$script $(libdir)/$$script; \ + done + +uninstall: uninstall-files uninstall-scripts + +uninstall-files: + for file in $(FILES); do \ + rm -f $(etcdir)/$$file; \ + done + +uninstall-scripts: + for script in $(SCRIPTS); do \ + rm -f $(libdir)/$$script; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *~ + +clean: mostlyclean + rm -f $(GEN_FILES) + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = etc + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/etc/components b/etc/components new file mode 100644 index 0000000..2669f04 --- /dev/null +++ b/etc/components @@ -0,0 +1,4 @@ +To: +cc: +Subject: +-------- diff --git a/etc/digestcomps b/etc/digestcomps new file mode 100644 index 0000000..d527e1d --- /dev/null +++ b/etc/digestcomps @@ -0,0 +1,9 @@ +From: %{digest}-Request +To: %{digest} Distribution: dist-%{digest}; +Subject: %{digest} Digest V%(cur) #%(msg) +Reply-To: %{digest} +-------- +%{digest} Digest %(weekday{date}), %2(mday{date}) %(month{date}) 19%02(year{date}) + Volume %(cur) : Issue %(msg) + +Today's Topics: diff --git a/etc/distcomps b/etc/distcomps new file mode 100644 index 0000000..c6dc39a --- /dev/null +++ b/etc/distcomps @@ -0,0 +1,2 @@ +Resent-To: +Resent-cc: diff --git a/etc/forwcomps b/etc/forwcomps new file mode 100644 index 0000000..2669f04 --- /dev/null +++ b/etc/forwcomps @@ -0,0 +1,4 @@ +To: +cc: +Subject: +-------- diff --git a/etc/mhl.body b/etc/mhl.body new file mode 100644 index 0000000..70ae463 --- /dev/null +++ b/etc/mhl.body @@ -0,0 +1,2 @@ +width=10000 +body:nocomponent,overflowoffset=0 diff --git a/etc/mhl.digest b/etc/mhl.digest new file mode 100644 index 0000000..ea4a6d5 --- /dev/null +++ b/etc/mhl.digest @@ -0,0 +1,7 @@ +width=80,overflowoffset=10 +leftadjust,compress,compwidth=9 +Date:formatfield="%<(nodate{text})%{text}%|%(tws{text})%>" +From: +Subject: +: +body:nocomponent,overflowoffset=0,noleftadjust,nocompress diff --git a/etc/mhl.format b/etc/mhl.format new file mode 100644 index 0000000..7f8a567 --- /dev/null +++ b/etc/mhl.format @@ -0,0 +1,17 @@ +; mhl.format +; +; default message filter for `show' +; +: +overflowtext="***",overflowoffset=5 +leftadjust,compwidth=9 +ignores=msgid,message-id,received,content-type,content-transfer-encoding,content-id +Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%>" +To: +cc: +From:decode +Subject:decode +: +extras:nocomponent +: +body:nocomponent,overflowtext=,overflowoffset=0,noleftadjust diff --git a/etc/mhl.forward b/etc/mhl.forward new file mode 100644 index 0000000..82b4ebc --- /dev/null +++ b/etc/mhl.forward @@ -0,0 +1,13 @@ +; mhl.forward +; +; default message filter for `forw' (forw -format) +; +width=80,overflowtext=,overflowoffset=10 +leftadjust,compress,compwidth=9 +Date:formatfield="%<(nodate{text})%{text}%|%(tws{text})%>" +From: +To: +cc: +Subject: +: +body:nocomponent,overflowoffset=0,noleftadjust,nocompress diff --git a/etc/mhl.headers b/etc/mhl.headers new file mode 100644 index 0000000..8fee181 --- /dev/null +++ b/etc/mhl.headers @@ -0,0 +1,17 @@ +; mhl.headers +; +; Default format file for displaying headers in +; MIME messages. mhn calls the mhlproc with this +; filter to display message header. +; +overflowtext="***",overflowoffset=5 +leftadjust,compwidth=9 +ignores=msgid,message-id,received,content-type,content-transfer-encoding,content-id +Date:formatfield="%<(nodate{text})%{text}%|%(pretty{text})%>" +To: +cc: +From:decode +Subject:decode +: +extras:nocomponent +: diff --git a/etc/mhl.reply b/etc/mhl.reply new file mode 100644 index 0000000..0caf5db --- /dev/null +++ b/etc/mhl.reply @@ -0,0 +1,5 @@ +; mhl.reply +; +; default message filter for `repl' (repl -format) +; +body:component="> ",overflowtext="> ",overflowoffset=0 diff --git a/etc/mhn.defaults.sh b/etc/mhn.defaults.sh new file mode 100755 index 0000000..358baf1 --- /dev/null +++ b/etc/mhn.defaults.sh @@ -0,0 +1,166 @@ +#! /bin/sh +# +# mhn.defaults.sh -- create extra profile file for MIME handling +# +# $Id$ +# +# USAGE: mhn.defaults.sh [ search-path [ search-prog ]] + +# If a search path is passed to the script, we +# use that, else we use a default search path. +if [ -n "$1" ]; then + SEARCHPATH=$1 +else + SEARCHPATH="$PATH:/usr/demo/SOUND" +fi + +# If a search program is passed to the script, we +# use that, else we use a default search program. +if [ -n "$2" ]; then + SEARCHPROG=$2 +else + SEARCHPROG="mhn.find.sh" +fi + +# put output into a temporary file, so we +# can sort it before output. +TMP=/tmp/nmh_temp.$$ +trap "rm -f $TMP" 0 1 2 3 13 15 + +echo "mhstore-store-text: %m%P.txt" >> $TMP +echo "mhstore-store-text/richtext: %m%P.rt" >> $TMP +echo "mhstore-store-video/mpeg: %m%P.mpg" >> $TMP +echo "mhstore-store-application/PostScript: %m%P.ps" >> $TMP + +PGM="`$SEARCHPROG $SEARCHPATH xwud`" +if [ ! -z "$PGM" ]; then + XWUD="$PGM" X11DIR="`echo $PGM | awk -F/ '{ for(i=2;i> $TMP +elif [ ! -z $"PBM" -a ! -z "$XWUD" ]; then + echo "mhshow-show-image/gif: %p${PBMDIR}giftoppm | ${PBMDIR}ppmtopgm | ${PBMDIR}pgmtopbm | ${PBMDIR}pbmtoxwd | $XWUD -geometry =-0+0" >> $TMP + echo "mhshow-show-image/x-pbm: %p${PBMDIR}pbmtoxwd | $XWUD -geometry =-0+0" >> $TMP + echo "mhshow-show-image/x-pgm: %p${PBMDIR}pgmtopbm | ${PBMDIR}pbmtoxwd | $XWUD -geometry =-0+0" >> $TMP + echo "mhshow-show-image/x-ppm: %p${PBMDIR}ppmtopgm | ${PBMDIR}pgmtopbm | ${PBMDIR}pbmtoxwd | $XWUD -geometry =-0+0" >> $TMP + echo "mhshow-show-image/x-xwd: %p$XWUD -geometry =-0+0" >> $TMP + + PGM="`$SEARCHPROG $SEARCHPATH djpeg`" + if [ ! -z "$PGM" ]; then + echo "mhshow-show-image/jpeg: %p$PGM -Pg | ${PBMDIR}ppmtopgm | ${PBMDIR}pgmtopbm | ${PBMDIR}pbmtoxwd | $XWUD -geometry =-0+0" >> $TMP + fi +fi + +if [ -f "/dev/audioIU" ]; then + PGM="`$SEARCHPROG $SEARCHPATH recorder`" + if [ ! -z "$PGM" ]; then + echo "mhstore-store-audio/basic: %m%P.au" >> $TMP + echo "mhbuild-compose-audio/basic: ${AUDIODIR}recorder '%f' -au -pause > /dev/tty" >> $TMP + echo "mhshow-show-audio/basic: %p${AUDIODIR}splayer -au" >> $TMP + fi +elif [ -f "/dev/audio" ]; then + PGM="`$SEARCHPROG $SEARCHPATH raw2audio`" + if [ ! -z "$PGM" ]; then + AUDIODIR="`echo $PGM | awk -F/ '{ for(i=2;i %m%P.au" >> $TMP + echo "mhstore-store-audio/x-next: %m%P.au" >> $TMP + AUDIOTOOL="`$SEARCHPROG $SEARCHPATH audiotool`" + if [ ! -z "$AUDIOTOOL" ]; then + echo "mhbuild-compose-audio/basic: $AUDIOTOOL %f && ${AUDIODIR}raw2audio -F < %f" >> $TMP + else + echo "mhbuild-compose-audio/basic: trap \"exit 0\" 2 && ${AUDIODIR}record | ${AUDIODIR}raw2audio -F" >> $TMP + fi + echo "mhshow-show-audio/basic: %p${AUDIODIR}raw2audio 2>/dev/null | ${AUDIODIR}play" >> $TMP + + PGM="`$SEARCHPROG $SEARCHPATH adpcm_enc`" + if [ ! -z "$PGM" ]; then + DIR="`echo $PGM | awk -F/ '{ for(i=2;i> $TMP + else + echo "mhbuild-compose-audio/x-next: ${AUDIODIR}record | ${DIR}adpcm_enc" >> $TMP + fi + echo "mhshow-show-audio/x-next: %p${DIR}adpcm_dec | ${AUDIODIR}play" >> $TMP + else + if [ ! -z "$AUDIOTOOL" ]; then + echo "mhbuild-compose-audio/x-next: $AUDIOTOOL %f" >> $TMP + else + echo "mhbuild-compose-audio/x-next: ${AUDIODIR}record" >> $TMP + fi + echo "mhshow-show-audio/x-next: %p${AUDIODIR}play" >> $TMP + fi + else + echo "mhbuild-compose-audio/basic: cat < /dev/audio" >> $TMP + echo "mhshow-show-audio/basic: %pcat > /dev/audio" >> $TMP + fi +fi + +PGM="`$SEARCHPROG $SEARCHPATH mpeg_play`" +if [ ! -z "$PGM" ]; then + echo "mhshow-show-video/mpeg: %p$PGM '%f'" >> $TMP +fi + +PGM="`$SEARCHPROG $SEARCHPATH lpr`" +if [ ! -z "$PGM" ]; then + echo "mhshow-show-application/PostScript: %plpr -Pps" >> $TMP +else + PGM="`$SEARCHPROG $SEARCHPATH lp`" + if [ ! -z "$PGM" ]; then + echo "mhshow-show-application/PostScript: %plp -dps" >> $TMP + fi +fi + +PGM="`$SEARCHPROG $SEARCHPATH ivs_replay`" +if [ ! -z "$PGM" ]; then + echo "mhshow-show-application/x-ivs: %p$PGM -o '%F'" >> $TMP +fi + +PGM="`$SEARCHPROG $SEARCHPATH richtext`" +if [ ! -z "$PGM" ]; then + echo "mhshow-show-text/richtext: %p$PGM -p '%F'" >> $TMP +else + PGM="`$SEARCHPROG $SEARCHPATH rt2raw`" + if [ ! -z "$PGM" ]; then + echo "mhshow-show-text/richtext: %p$PGM < '%f' | fmt -78 | more" >> $TMP + fi +fi + +PGM="`$SEARCHPROG $SEARCHPATH xterm`" +if [ ! -z "$PGM" ]; then + echo "mhshow-charset-iso-8859-1: xterm -fn '-*-*-medium-r-normal-*-*-120-*-*-c-*-iso8859-*' -e %s" >> $TMP +fi + +# output a sorted version of the file +sort < $TMP + +exit 0 + +: not until we get a "safe" postscript environment... + +PGM="`$SEARCHPROG $SEARCHPATH pageview`" +if [ "$DISPLAY" = "unix:0.0" -a ! -z "$PGM" ]; then + echo "mhshow-show-application/PostScript: %p$PGM -" >> $TMP +else + PGM="`$SEARCHPROG $SEARCHPATH gs`" + if [ ! -z "$PGM" ]; then + echo "mhshow-show-application/PostScript: %p$PGM -- '%F'" >> $TMP + fi +fi + +: have to experiment more with this + +PGM="`$SEARCHPROG $SEARCHPATH ivs_record`" +if [ ! -z "$PGM" ]; then + echo "mhbuild-compose-application/x-ivs: $PGM -u localhost '%F'" >> $TMP +fi diff --git a/etc/mhn.find.sh b/etc/mhn.find.sh new file mode 100755 index 0000000..8f128f7 --- /dev/null +++ b/etc/mhn.find.sh @@ -0,0 +1,40 @@ +#! /bin/sh +# +# mhn.find.sh -- check if a particular command is available +# +# $Id$ + +if test -z "$2"; then + echo "usage: mhn.find.sh search-path program" 1>&2 + exit 1 +fi + +# PATH to search for programs +SEARCHPATH=$1 + +# program to search for +PROGRAM=$2 + +PGM= oIFS="$IFS" IFS=":" +for A in $SEARCHPATH; do + + # skip the directories `.' and `..' + if test "$A" = "." -o "$A" = ".."; then + continue + fi + + # if program was found in /usr/local/bin, then + # just echo program name, else echo full pathname + if test -f "$A/$PROGRAM"; then + if test "$A" = "/usr/local/bin"; then + PGM="$PROGRAM" + else + PGM="$A/$PROGRAM" + fi + + echo "$PGM" + exit 0 + fi +done +IFS="$oIFS" + diff --git a/etc/mts.conf.in b/etc/mts.conf.in new file mode 100644 index 0000000..c892f0f --- /dev/null +++ b/etc/mts.conf.in @@ -0,0 +1,31 @@ +# +# nmh mail transport interface customization file. +# +# Check the mh-tailor(5) man page for a list of +# all the available options for this file. +# + +# Default location of mail drops. If this option is +# set, but empty, the user's home directory is used. +mmdfldir: %mailspool% + +# The name of the maildrop file in the directory where maildrops +# are kept. If this is empty, the user's login name is used. +mmdflfil: + +# The exceptions file for /etc/hosts used by +# `post' to try to find official names. +hostable: %etcdir%/hosts + +# List of smtp servers to try if using smtp support +servers: localhost + +# Name of POP server +# pophost: localhost + +# Name that nmh considers `local'. If not set, nmh will +# query the system for this value (gethostname, etc...). +# localname: foo.bar.com + +# Uncomment this to turn on username masquerading. +# mmailid: 1 diff --git a/etc/rcvdistcomps b/etc/rcvdistcomps new file mode 100644 index 0000000..13fe808 --- /dev/null +++ b/etc/rcvdistcomps @@ -0,0 +1,3 @@ +%(lit)%(formataddr{addresses})\ +%<(nonnull)%(void(width))%(putaddr Resent-To: )\n%>\ +Resent-Fcc: outbox diff --git a/etc/replcomps b/etc/replcomps new file mode 100644 index 0000000..52170a7 --- /dev/null +++ b/etc/replcomps @@ -0,0 +1,21 @@ +%; replcomps +%; +%; default form (components) file for `repl' +%; +%; Check for the following headers (in this order) +%; to construct the return address +%; +%; Mail-Reply-To +%; Reply-To +%; From +%; Sender +%; Return-Path +%; +%(lit)%(formataddr %<{mail-reply-to}%?{reply-to}%?{from}%?{sender}%?{return-path}%>)\ +%<(nonnull)%(void(width))%(putaddr To: )\n%>\ +%<{fcc}Fcc: %{fcc}\n%>\ +%<{subject}Subject: Re: %{subject}\n%>\ +%<{date}In-Reply-To: Your message of "\ +%<(nodate{date})%{date}%|%(pretty{date})%>."%<{message-id} + %{message-id}%>\n%>\ +-------- diff --git a/etc/replgroupcomps b/etc/replgroupcomps new file mode 100644 index 0000000..9295fd0 --- /dev/null +++ b/etc/replgroupcomps @@ -0,0 +1,36 @@ +%; replgroupcomps +%; +%; form (components) file for `repl -group' +%; +%; Check the following headers to create reply addresses. +%; +%; To: Mail-Followup-To +%; +%; OR +%; +%; To: Mail-Reply-To (or) +%; Reply-To (or) +%; From (or) +%; Sender (or) +%; Return-Path +%; +%; AND +%; +%; cc: To (and) +%; cc (and) +%; personal address +%; +%(lit)%(formataddr{mail-followup-to})\ +%<(nonnull)%(void(width))%(putaddr To: )\n\ +%|\ +%(lit)%(formataddr %<{mail-reply-to}%?{reply-to}%?{from}%?{sender}%?{return-path}%>)\ +%<(nonnull)%(void(width))%(putaddr To: )\n%>\ +%(lit)%(formataddr{to})%(formataddr{cc})%(formataddr(me))\ +%<(nonnull)%(void(width))%(putaddr cc: )\n%>%>\ +%; +%<{fcc}Fcc: %{fcc}\n%>\ +%<{subject}Subject: Re: %{subject}\n%>\ +In-Reply-To: Message from %<{from}%{from}%?{sender}%{sender}%|%{return-path}%>\n\ + of "%<(nodate{date})%{date}%|%(pretty{date})%>."\ +%<{message-id} %{message-id}%>\n\ +-------- diff --git a/etc/scan.default b/etc/scan.default new file mode 100644 index 0000000..f898e86 --- /dev/null +++ b/etc/scan.default @@ -0,0 +1,11 @@ +%; scan.default +%; +%; This file is supplied for reference only; it shows the default +%; format string (for non-UK sites) which was compiled into the +%; command "scan". See the source file "h/scansbr.h" for details. +%; +%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mon{date})/%02(mday{date})%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}>>%> diff --git a/etc/scan.mailx b/etc/scan.mailx new file mode 100644 index 0000000..27d18d3 --- /dev/null +++ b/etc/scan.mailx @@ -0,0 +1,9 @@ +%<(cur)>%| %>\ +%<{status} %|N%>\ +%<{replied}R%?{encrypted}E%| %>\ +%4(msg) \ +%<(mymbox{from})%<{to}To: %13(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%3(day{date}) %3(month{date}) %02(mday{date}) \ +%02(hour{date}):%02(min{date}) \ +%(decode{subject}) diff --git a/etc/scan.nomime b/etc/scan.nomime new file mode 100644 index 0000000..8e2cedf --- /dev/null +++ b/etc/scan.nomime @@ -0,0 +1,10 @@ +%; scan.nomime +%; +%; This file is a modification of the standard (non-UK version) +%; format for scan, that doesn't do any RFC-2047 decoding of +%; header components. +%; +%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mon{date})/%02(mday{date})%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)%17(friendly{from})%> \ +%{subject}%<{body}<<%{body}>>%> diff --git a/etc/scan.size b/etc/scan.size new file mode 100644 index 0000000..a6b6698 --- /dev/null +++ b/etc/scan.size @@ -0,0 +1,6 @@ +%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mon{date})/%02(mday{date})%<{date} %|*%>\ +%5(size) \ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}%> diff --git a/etc/scan.time b/etc/scan.time new file mode 100644 index 0000000..ee54a52 --- /dev/null +++ b/etc/scan.time @@ -0,0 +1,7 @@ +%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mon{date})/%02(mday{date}) \ +%02(hour{date}):%02(min{date})%3(tzone{date})\ +%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}%> diff --git a/etc/scan.timely b/etc/scan.timely new file mode 100644 index 0000000..06c068c --- /dev/null +++ b/etc/scan.timely @@ -0,0 +1,10 @@ +%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%(void(rclock{date}))\ +%<(gt 15768000)%03(month{date})%(void(year{date}))%02(modulo 100)\ +%?(gt 604800)%02(mday{date})%03(month{date})\ +%?(gt 86400) %(day{date}) %|\ +%02(hour{date}):%02(min{date})%>\ +%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}%> diff --git a/etc/scan.unseen b/etc/scan.unseen new file mode 100644 index 0000000..d1cd195 --- /dev/null +++ b/etc/scan.unseen @@ -0,0 +1,5 @@ +%4(msg)%<(cur)+%| %>%<(unseen)U%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mon{date})/%02(mday{date})%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}>>%> diff --git a/etc/sendfiles b/etc/sendfiles new file mode 100755 index 0000000..d53ed82 --- /dev/null +++ b/etc/sendfiles @@ -0,0 +1,42 @@ +#!/bin/sh +# +# $Id$ +# +# Send multiples files and/or directories as a tar/compressed +# image, in a MIME message. +# + +DELAY=0 +FROM= + +case "$1" in + -*) DELAY="`echo $1 | sed -e 's%-%%'`" + shift + ;; +esac + +if [ ! -z "$PERSON" ]; then + FROM="-from $PERSON" +fi + +if [ $# -lt 3 ]; then + echo 'usage: sendfiles: "mailpath" "subject-string" directory-or-file ...' 1>&2 + exit 1; +fi + +mailpath="$1" +echo "mailpath = $mailpath" 1>&2 +shift + +subject="$1" +echo "subject-string = $subject" 1>&2 +shift + +echo "files = $*" 1>&2 + +tar cvf - "$@" | compress | \ + viamail -to "$mailpath" -subject "$subject" \ + -parameters "type=tar; x-conversions=compress" \ + -comment "extract with uncompress | tar xvpf -" \ + -delay "$DELAY" \ + -verbose $FROM diff --git a/h/Makefile.in b/h/Makefile.in new file mode 100644 index 0000000..4f99259 --- /dev/null +++ b/h/Makefile.in @@ -0,0 +1,59 @@ +# +# Makefile for h subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +srcdir = @srcdir@ +VPATH = @srcdir@ + +# header files included in distribution +HDRS = addrsbr.h aliasbr.h dropsbr.h fmt_compile.h fmt_scan.h \ + md5.h mh.h mhcachesbr.h mhparse.h mime.h msh.h netdb.h nmh.h \ + nntp.h picksbr.h popsbr.h prototypes.h rcvmail.h scansbr.h \ + signals.h vmhsbr.h + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(HDRS) $(AUX) + +# ========== DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +all: + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *~ + +clean: mostlyclean + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = h + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/h/addrsbr.h b/h/addrsbr.h new file mode 100644 index 0000000..e3aa7e8 --- /dev/null +++ b/h/addrsbr.h @@ -0,0 +1,42 @@ + +/* + * addrsbr.h -- definitions for the address parsing system + * + * $Id$ + */ + +#define AD_HOST 1 /* getm(): lookup official hostname */ +#define AD_NHST 0 /* getm(): do not lookup official name */ +#define AD_NAME AD_NHST /* AD_HOST is TOO slow */ + +#define UUCPHOST (-1) +#define LOCALHOST 0 +#define NETHOST 1 +#define BADHOST 2 + +struct mailname { + struct mailname *m_next; + char *m_text; + char *m_pers; + char *m_mbox; + char *m_host; + char *m_path; + int m_type; + char m_nohost; + char m_bcc; + int m_ingrp; + char *m_gname; + char *m_note; +}; + +#define adrformat(m) auxformat ((m), 1) + +/* + * prototypes + */ +void mnfree(struct mailname *); +int ismymbox(struct mailname *); +char *getname(char *); +char *adrsprintf(char *, char *); +char *auxformat(struct mailname *, int); +struct mailname *getm(char *, char *, int, int, char *); diff --git a/h/aliasbr.h b/h/aliasbr.h new file mode 100644 index 0000000..2e464d9 --- /dev/null +++ b/h/aliasbr.h @@ -0,0 +1,64 @@ + +/* + * aliasbr.h -- definitions for the aliasing system + * + * $Id$ + */ + +extern char *AliasFile; /* mh-alias(5) */ +#define PASSWD "/etc/passwd" /* passwd(5) */ +#define GROUP "/etc/group" /* group(5) */ +#define EVERYONE 200 /* lowest uid for everyone */ + +struct aka { + char *ak_name; /* name to match against */ + struct adr *ak_addr; /* list of addresses that it maps to */ + struct aka *ak_next; /* next aka in list */ + char ak_visible; /* should be visible in headers */ +}; + +struct adr { + char *ad_text; /* text of this address in list */ + struct adr *ad_next; /* next adr in list */ + char ad_local; /* text is local (check for expansion) */ +}; + +/* + * incore version of /etc/passwd + */ +struct home { + char *h_name; /* user name */ + uid_t h_uid; /* user id */ + gid_t h_gid; /* user's group */ + char *h_home; /* user's home directory */ + char *h_shell; /* user's shell */ + int h_ngrps; /* number of groups this user belongs to */ + struct home *h_next; /* next home in list */ +}; + +#ifndef MMDFMTS +struct home *seek_home (); +#endif /* MMDFMTS */ + +/* + * prototypes + */ +int alias (char *); +int akvisible (void); +void init_pw (void); +char *akresult (struct aka *); +char *akvalue (char *); +char *akerror (int); + +/* codes returned by alias() */ + +#define AK_OK 0 /* file parsed ok */ +#define AK_NOFILE 1 /* couldn't read file */ +#define AK_ERROR 2 /* error parsing file */ +#define AK_LIMIT 3 /* memory limit exceeded */ +#define AK_NOGROUP 4 /* no such group */ + +/* should live here, not in mts.c */ + +extern int Everyone; +extern char *NoShell; diff --git a/h/dropsbr.h b/h/dropsbr.h new file mode 100644 index 0000000..146a63b --- /dev/null +++ b/h/dropsbr.h @@ -0,0 +1,59 @@ + +/* + * dropsbr.h -- definitions for maildrop-style files + * + * $Id$ + */ + +/* + * A file which is formatted like a maildrop may have a corresponding map + * file which is an index to the bounds of each message. The first record + * of such an map is special, it contains: + * + * d_id = number of messages in file + * d_size = version number of map + * d_start = last message read + * d_stop = size of file + * + * Each record after that contains: + * + * d_id = BBoard-ID: of message, or similar info + * d_size = size of message in ARPA Internet octets (\n == 2 octets) + * d_start = starting position of message in file + * d_stop = stopping position of message in file + * + * Note that d_start/d_stop do NOT include the message delimiters, so + * programs using the map can simply fseek to d_start and keep reading + * until the position is at d_stop. + */ + +/* + * various formats for maildrop files + */ +#define OTHER_FORMAT 0 +#define MBOX_FORMAT 1 +#define MMDF_FORMAT 2 + +#define DRVRSN 3 + +struct drop { + int d_id; + int d_size; + off_t d_start; + off_t d_stop; +}; + +/* + * prototypes + */ +int mbx_open (char *, int, uid_t, gid_t, mode_t); +int mbx_read (FILE *, long, struct drop **, int); +int mbx_write(char *, int, FILE *, int, long, long, off_t, int, int); +int mbx_copy (char *, int, int, int, int, char *, int); +int mbx_size (int, off_t, off_t); +int mbx_close (char *, int); +char *map_name (char *); +int map_read (char *, long, struct drop **, int); +int map_write (char *, int, int, long, off_t, off_t, long, int, int); +int map_chk (char *, int, struct drop *, long, int); + diff --git a/h/fmt_compile.h b/h/fmt_compile.h new file mode 100644 index 0000000..f911504 --- /dev/null +++ b/h/fmt_compile.h @@ -0,0 +1,109 @@ + +/* + * fmt_compile.h -- format types + * + * $Id$ + */ + +/* types that output text */ +#define FT_COMP 1 /* the text of a component */ +#define FT_COMPF 2 /* comp text, filled */ +#define FT_LIT 3 /* literal text */ +#define FT_LITF 4 /* literal text, filled */ +#define FT_CHAR 5 /* a single ascii character */ +#define FT_NUM 6 /* "value" as decimal number */ +#define FT_NUMF 7 /* "value" as filled dec number */ +#define FT_STR 8 /* "str" as text */ +#define FT_STRF 9 /* "str" as text, filled */ +#define FT_STRFW 10 /* "str" as text, filled, width in "value" */ +#define FT_PUTADDR 11 /* split and print address line */ + +/* types that modify the "str" or "value" registers */ +#define FT_LS_COMP 12 /* set "str" to component text */ +#define FT_LS_LIT 13 /* set "str" to literal text */ +#define FT_LS_GETENV 14 /* set "str" to getenv(text) */ +#define FT_LS_CFIND 15 /* set "str" to context_find(text) */ +#define FT_LS_DECODECOMP 16 /* set "str" to decoded component text */ +#define FT_LS_DECODE 17 /* decode "str" as RFC-2047 header */ +#define FT_LS_TRIM 18 /* trim trailing white space from "str" */ +#define FT_LV_COMP 19 /* set "value" to comp (as dec. num) */ +#define FT_LV_COMPFLAG 20 /* set "value" to comp flag word */ +#define FT_LV_LIT 21 /* set "value" to literal num */ +#define FT_LV_DAT 22 /* set "value" to dat[n] */ +#define FT_LV_STRLEN 23 /* set "value" to length of "str" */ +#define FT_LV_PLUS_L 24 /* set "value" += literal */ +#define FT_LV_MINUS_L 25 /* set "value" -= literal */ +#define FT_LV_DIVIDE_L 26 /* set "value" to value / literal */ +#define FT_LV_MODULO_L 27 /* set "value" to value % literal */ +#define FT_LV_CHAR_LEFT 28 /* set "value" to char left in output */ + +#define FT_LS_MONTH 29 /* set "str" to tws month */ +#define FT_LS_LMONTH 30 /* set "str" to long tws month */ +#define FT_LS_ZONE 31 /* set "str" to tws timezone */ +#define FT_LS_DAY 32 /* set "str" to tws weekday */ +#define FT_LS_WEEKDAY 33 /* set "str" to long tws weekday */ +#define FT_LS_822DATE 34 /* set "str" to 822 date str */ +#define FT_LS_PRETTY 35 /* set "str" to pretty (?) date str */ +#define FT_LV_SEC 36 /* set "value" to tws second */ +#define FT_LV_MIN 37 /* set "value" to tws minute */ +#define FT_LV_HOUR 38 /* set "value" to tws hour */ +#define FT_LV_MDAY 39 /* set "value" to tws day of month */ +#define FT_LV_MON 40 /* set "value" to tws month */ +#define FT_LV_YEAR 41 /* set "value" to tws year */ +#define FT_LV_YDAY 42 /* set "value" to tws day of year */ +#define FT_LV_WDAY 43 /* set "value" to tws weekday */ +#define FT_LV_ZONE 44 /* set "value" to tws timezone */ +#define FT_LV_CLOCK 45 /* set "value" to tws clock */ +#define FT_LV_RCLOCK 46 /* set "value" to now - tws clock */ +#define FT_LV_DAYF 47 /* set "value" to tws day flag */ +#define FT_LV_DST 48 /* set "value" to tws daylight savings flag */ +#define FT_LV_ZONEF 49 /* set "value" to tws timezone flag */ + +#define FT_LS_PERS 50 /* set "str" to person part of addr */ +#define FT_LS_MBOX 51 /* set "str" to mbox part of addr */ +#define FT_LS_HOST 52 /* set "str" to host part of addr */ +#define FT_LS_PATH 53 /* set "str" to route part of addr */ +#define FT_LS_GNAME 54 /* set "str" to group part of addr */ +#define FT_LS_NOTE 55 /* set "str" to comment part of addr */ +#define FT_LS_ADDR 56 /* set "str" to mbox@host */ +#define FT_LS_822ADDR 57 /* set "str" to 822 format addr */ +#define FT_LS_FRIENDLY 58 /* set "str" to "friendly" format addr */ +#define FT_LV_HOSTTYPE 59 /* set "value" to addr host type */ +#define FT_LV_INGRPF 60 /* set "value" to addr in-group flag */ +#define FT_LV_NOHOSTF 61 /* set "value" to addr no-host flag */ + +/* Date Coercion */ +#define FT_LOCALDATE 62 /* Coerce date to local timezone */ +#define FT_GMTDATE 63 /* Coerce date to gmt */ + +/* pre-format processing */ +#define FT_PARSEDATE 64 /* parse comp into a date (tws) struct */ +#define FT_PARSEADDR 65 /* parse comp into a mailaddr struct */ +#define FT_FORMATADDR 66 /* let external routine format addr */ +#define FT_MYMBOX 67 /* do "mymbox" test on comp */ + +/* misc. */ /* ADDTOSEQ only works if you include "options LBL" */ +#define FT_ADDTOSEQ 68 /* add current msg to a sequence */ + +/* conditionals & control flow (must be last) */ +#define FT_SAVESTR 69 /* save current str reg */ +#define FT_DONE 70 /* stop formatting */ +#define FT_PAUSE 71 /* pause */ +#define FT_NOP 72 /* nop */ +#define FT_GOTO 73 /* (relative) goto */ +#define FT_IF_S_NULL 74 /* test if "str" null */ +#define FT_IF_S 75 /* test if "str" non-null */ +#define FT_IF_V_EQ 76 /* test if "value" = literal */ +#define FT_IF_V_NE 77 /* test if "value" != literal */ +#define FT_IF_V_GT 78 /* test if "value" > literal */ +#define FT_IF_MATCH 79 /* test if "str" contains literal */ +#define FT_IF_AMATCH 80 /* test if "str" starts with literal */ +#define FT_S_NULL 81 /* V = 1 if "str" null */ +#define FT_S_NONNULL 82 /* V = 1 if "str" non-null */ +#define FT_V_EQ 83 /* V = 1 if "value" = literal */ +#define FT_V_NE 84 /* V = 1 if "value" != literal */ +#define FT_V_GT 85 /* V = 1 if "value" > literal */ +#define FT_V_MATCH 86 /* V = 1 if "str" contains literal */ +#define FT_V_AMATCH 87 /* V = 1 if "str" starts with literal */ + +#define IF_FUNCS FT_S_NULL /* start of "if" functions */ diff --git a/h/fmt_scan.h b/h/fmt_scan.h new file mode 100644 index 0000000..fd4c555 --- /dev/null +++ b/h/fmt_scan.h @@ -0,0 +1,90 @@ + +/* + * fmt_scan.h -- definitions for fmt_scan() + * + * $Id$ + */ + +/* + * This structure describes an "interesting" component. It holds + * the name & text from the component (if found) and one piece of + * auxilary info. The structure for a particular component is located + * by (open) hashing the name and using it as an index into the ptr array + * "wantcomp". All format entries that reference a particular component + * point to its comp struct (so we only have to do component specific + * processing once. e.g., parse an address.). + */ +struct comp { + char *c_name; /* component name (in lower case) */ + char *c_text; /* component text (if found) */ + struct comp *c_next; /* hash chain linkage */ + short c_flags; /* misc. flags (from fmt_scan) */ + short c_type; /* type info (from fmt_compile) */ + union { + struct tws *c_u_tws; + struct mailname *c_u_mn; + } c_un; +}; + +#define c_tws c_un.c_u_tws +#define c_mn c_un.c_u_mn + +/* + * c_type bits + */ +#define CT_ADDR (1<<0) /* referenced as address */ +#define CT_DATE (1<<1) /* referenced as date */ +#define CT_MYMBOX (1<<2) /* "mymbox" test being done */ +#define CT_ADDRPARSE (1<<3) /* address parse being done */ + +extern int fmt_norm; + +/* + * Hash table for deciding if a component is "interesting". + */ +extern struct comp *wantcomp[128]; + +/* + * Hash function for component name. The function should be + * case independent and probably shouldn't involve a routine + * call. This function is pretty good but will not work on + * single character component names. + */ +#define CHASH(nm) (((((nm)[0]) - ((nm)[1])) & 0x1f) + (((nm)[2]) & 0x5f)) + +/* + * Find a component in the hash table. + */ +#define FINDCOMP(comp,name) \ + for (comp = wantcomp[CHASH(name)]; \ + comp && strcmp(comp->c_name,name); \ + comp = comp->c_next) ; + +/* + * This structure defines one formatting instruction. + */ +struct format { + unsigned char f_type; + char f_fill; + short f_width; /* output field width */ + union { + struct comp *f_u_comp; /* associated component */ + char *f_u_text; /* literal text */ + char f_u_char; /* literal character */ + int f_u_value; /* literal value */ + } f_un; +}; + +#define f_skip f_width /* instr to skip (false "if") */ + +#define f_comp f_un.f_u_comp +#define f_text f_un.f_u_text +#define f_char f_un.f_u_char +#define f_value f_un.f_u_value + +/* + * prototypes + */ +struct format *fmt_scan (struct format *, char *, int, int *); +char *new_fs (char *, char *, char *); +int fmt_compile (char *, struct format **); diff --git a/h/md5.h b/h/md5.h new file mode 100644 index 0000000..9a394b5 --- /dev/null +++ b/h/md5.h @@ -0,0 +1,81 @@ +/* + * md5.h -- header file for md5 message digest + * taken from RFC-1321/Appendices A.1/A.2 + * + * $Id$ + */ + +/* + * RSAREF types and constants + */ + +/* + * Use prototypes for nmh/mh + */ +#define PROTOTYPES 1 + +/* + * PROTOTYPES should be set to one if and only if the compiler + * supports function argument prototyping. The following makes + * PROTOTYPES default to 0 if it has not already been defined + * with C compiler flags. + */ +#ifndef PROTOTYPES +#define PROTOTYPES 0 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. +If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init PROTO_LIST ((MD5_CTX *)); +void MD5Update PROTO_LIST ((MD5_CTX *, unsigned char *, unsigned int)); +void MD5Final PROTO_LIST ((unsigned char [16], MD5_CTX *)); + diff --git a/h/mh.h b/h/mh.h new file mode 100644 index 0000000..c8ece5d --- /dev/null +++ b/h/mh.h @@ -0,0 +1,324 @@ + +/* + * mh.h -- main header file for all of nmh + * + * $Id$ + */ + +#include + +/* + * Well-used constants + */ +#define NOTOK (-1) /* syscall()s return this on error */ +#define OK 0 /* ditto on success */ +#define DONE 1 /* trinary logic */ +#define ALL "" +#define Nbby 8 /* number of bits/byte */ + +#define MAXARGS 1000 /* max arguments to exec */ +#define NFOLDERS 1000 /* max folder arguments on command line */ +#define DMAXFOLDER 4 /* typical number of digits */ +#define MAXFOLDER 1000 /* message increment */ + +/* + * user context/profile structure + */ +struct node { + char *n_name; /* key */ + char *n_field; /* value */ + char n_context; /* context, not profile */ + struct node *n_next; /* next entry */ +}; + +/* + * switches structure + */ +#define AMBIGSW (-2) /* from smatch() on ambiguous switch */ +#define UNKWNSW (-1) /* from smatch() on unknown switch */ + +struct swit { + char *sw; + int minchars; +}; + +extern struct swit anoyes[]; /* standard yes/no switches */ + +/* + * general folder attributes + */ +#define READONLY (1<<0) /* No write access to folder */ +#define SEQMOD (1<<1) /* folder's sequences modifed */ +#define ALLOW_NEW (1<<2) /* allow the "new" sequence */ +#define OTHERS (1<<3) /* folder has other files */ +#define MODIFIED (1<<4) /* msh in-core folder modified */ + +#define FBITS "\020\01READONLY\02SEQMOD\03ALLOW_NEW\04OTHERS\05MODIFIED" + +/* + * type for holding the sequence set of a message + */ +typedef unsigned int seqset_t; + +/* + * Determine the number of user defined sequences we + * can have. The first 5 sequence flags are for + * internal nmh message flags. + */ +#define NUMATTRS ((sizeof(seqset_t) * Nbby) - 5) + +/* + * first free slot for user defined sequences + * and attributes + */ +#define FFATTRSLOT 5 + +/* + * internal messages attributes (sequences) + */ +#define EXISTS (1<<0) /* exists */ +#define DELETED (1<<1) /* deleted */ +#define SELECTED (1<<2) /* selected for use */ +#define SELECT_EMPTY (1<<3) /* "new" message */ +#define SELECT_UNSEEN (1<<4) /* inc/show "unseen" */ + +#define MBITS "\020\01EXISTS\02DELETED\03SELECTED\04NEW\05UNSEEN" + +/* + * Primary structure of folder/message information + */ +struct msgs { + int lowmsg; /* Lowest msg number */ + int hghmsg; /* Highest msg number */ + int nummsg; /* Actual Number of msgs */ + + int lowsel; /* Lowest selected msg number */ + int hghsel; /* Highest selected msg number */ + int numsel; /* Number of msgs selected */ + + int curmsg; /* Number of current msg if any */ + + int msgflags; /* Folder attributes (READONLY, etc) */ + char *foldpath; /* Pathname of folder */ + + /* + * Name of sequences in this folder. We add an + * extra slot, so we can NULL terminate the list. + */ + char *msgattrs[NUMATTRS + 1]; + + /* + * bit flags for whether sequence + * is public (0), or private (1) + */ + seqset_t attrstats; + + /* + * These represent the lowest and highest possible + * message numbers we can put in the message status + * area, without calling folder_realloc(). + */ + int lowoff; + int hghoff; + + /* + * This is an array of seqset_t which we allocate dynamically. + * Each seqset_t is a set of bits flags for a particular message. + * These bit flags represent general attributes such as + * EXISTS, SELECTED, etc. as well as track if message is + * in a particular sequence. + */ + seqset_t *msgstats; /* msg status */ +}; + +/* + * Amount of space to allocate for msgstats. Allocate + * the array to have space for messages numbers lo to hi. + */ +#define MSGSTATSIZE(mp,lo,hi) ((size_t) (((hi) - (lo) + 1) * sizeof(*(mp)->msgstats))) + +/* + * macros for message and sequence manipulation + */ +#define clear_msg_flags(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] = 0) +#define copy_msg_flags(mp,i,j) \ + ((mp)->msgstats[(i) - mp->lowoff] = (mp)->msgstats[(j) - mp->lowoff]) +#define get_msg_flags(mp,ptr,msgnum) (*(ptr) = (mp)->msgstats[(msgnum) - mp->lowoff]) +#define set_msg_flags(mp,ptr,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] = *(ptr)) + +#define does_exist(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] & EXISTS) +#define unset_exists(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] &= ~EXISTS) +#define set_exists(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] |= EXISTS) + +#define is_selected(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] & SELECTED) +#define unset_selected(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] &= ~SELECTED) +#define set_selected(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] |= SELECTED) + +#define is_select_empty(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] & SELECT_EMPTY) +#define set_select_empty(mp,msgnum) \ + ((mp)->msgstats[(msgnum) - mp->lowoff] |= SELECT_EMPTY) + +#define is_unseen(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] & SELECT_UNSEEN) +#define unset_unseen(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] &= ~SELECT_UNSEEN) +#define set_unseen(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] |= SELECT_UNSEEN) + +/* for msh only */ +#define set_deleted(mp,msgnum) ((mp)->msgstats[(msgnum) - mp->lowoff] |= DELETED) + +#define in_sequence(mp,seqnum,msgnum) \ + ((mp)->msgstats[(msgnum) - mp->lowoff] & (1 << (FFATTRSLOT + seqnum))) +#define clear_sequence(mp,seqnum,msgnum) \ + ((mp)->msgstats[(msgnum) - mp->lowoff] &= ~(1 << (FFATTRSLOT + seqnum))) +#define add_sequence(mp,seqnum,msgnum) \ + ((mp)->msgstats[(msgnum) - mp->lowoff] |= (1 << (FFATTRSLOT + seqnum))) + +#define is_seq_private(mp,seqnum) \ + ((mp)->attrstats & (1 << (FFATTRSLOT + seqnum))) +#define make_seq_public(mp,seqnum) \ + ((mp)->attrstats &= ~(1 << (FFATTRSLOT + seqnum))) +#define make_seq_private(mp,seqnum) \ + ((mp)->attrstats |= (1 << (FFATTRSLOT + seqnum))) +#define make_all_public(mp) \ + ((mp)->attrstats = 0) + +/* + * macros for folder attributes + */ +#define clear_folder_flags(mp) ((mp)->msgflags = 0) + +#define is_readonly(mp) ((mp)->msgflags & READONLY) +#define set_readonly(mp) ((mp)->msgflags |= READONLY) + +#define other_files(mp) ((mp)->msgflags & OTHERS) +#define set_other_files(mp) ((mp)->msgflags |= OTHERS) + +#define NULLMP ((struct msgs *) 0) + +/* + * m_getfld() message parsing + */ + +#define NAMESZ 128 /* Limit on component name size */ + +#define LENERR (-2) /* Name too long error from getfld */ +#define FMTERR (-3) /* Message Format error */ +#define FLD 0 /* Field returned */ +#define FLDPLUS 1 /* Field returned with more to come */ +#define FLDEOF 2 /* Field returned ending at eom */ +#define BODY 3 /* Body returned with more to come */ +#define BODYEOF 4 /* Body returned ending at eom */ +#define FILEEOF 5 /* Reached end of input file */ + +/* + * Maildrop styles + */ +#define MS_DEFAULT 0 /* default (one msg per file) */ +#define MS_UNKNOWN 1 /* type not known yet */ +#define MS_MBOX 2 /* Unix-style "from" lines */ +#define MS_MMDF 3 /* string mmdlm2 */ +#define MS_MSH 4 /* whacko msh */ + +extern int msg_count; /* m_getfld() indicators */ +extern int msg_style; /* .. */ +extern char *msg_delim; /* .. */ + +#define NOUSE 0 /* draft being re-used */ + +#define TFOLDER 0 /* path() given a +folder */ +#define TFILE 1 /* path() given a file */ +#define TSUBCWF 2 /* path() given a @folder */ + +#define OUTPUTLINELEN 72 /* default line length for headers */ + +/* + * miscellaneous macros + */ +#define pidXwait(pid,cp) pidstatus (pidwait (pid, NOTOK), stdout, cp) + +#ifndef max +# define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef abs +# define abs(a) ((a) > 0 ? (a) : -(a)) +#endif + +/* + * GLOBAL VARIABLES + */ +#define CTXMOD 0x01 /* context information modified */ +#define DBITS "\020\01CTXMOD" +extern char ctxflags; + +extern char *invo_name; /* command invocation name */ +extern char *mypath; /* user's $HOME */ +extern char *defpath; /* pathname of user's profile */ +extern char *ctxpath; /* pathname of user's context */ +extern struct node *m_defs; /* list of profile/context entries */ + +/* + * These standard strings are defined in config.c. They are the + * only system-dependent parameters in nmh, and thus by redefining + * their values and reloading the various modules, nmh will run + * on any system. + */ +extern char *buildmimeproc; +extern char *catproc; +extern char *components; +extern char *context; +extern char *current; +extern char *defaulteditor; +extern char *defaultfolder; +extern char *digestcomps; +extern char *distcomps; +extern char *draft; +extern char *faceproc; +extern char *fileproc; +extern char *foldprot; +extern char *forwcomps; +extern char *inbox; +extern char *incproc; +extern char *installproc; +extern char *lproc; +extern char *mailproc; +extern char *mh_defaults; +extern char *mh_profile; +extern char *mh_seq; +extern char *mhlformat; +extern char *mhlforward; +extern char *mhlproc; +extern char *mhlreply; +extern char *moreproc; +extern char *msgprot; +extern char *mshproc; +extern char *nmhaccessftp; +extern char *nmhstorage; +extern char *nmhcache; +extern char *nmhprivcache; +extern char *nsequence; +extern char *packproc; +extern char *postproc; +extern char *pfolder; +extern char *psequence; +extern char *rcvdistcomps; +extern char *rcvstoreproc; +extern char *replcomps; +extern char *replgroupcomps; +extern char *rmfproc; +extern char *rmmproc; +extern char *sendproc; +extern char *showmimeproc; +extern char *showproc; +extern char *usequence; +extern char *version_num; +extern char *version_str; +extern char *vmhproc; +extern char *whatnowproc; +extern char *whomproc; + +#include + diff --git a/h/mhcachesbr.h b/h/mhcachesbr.h new file mode 100644 index 0000000..a197738 --- /dev/null +++ b/h/mhcachesbr.h @@ -0,0 +1,21 @@ + +/* + * mhcachesbr.h -- definitions for manipulating MIME content cache + * + * $Id$ + */ + +/* + * various cache policies + */ +static struct swit caches[] = { +#define CACHE_NEVER 0 + { "never", 0 }, +#define CACHE_PRIVATE 1 + { "private", 0 }, +#define CACHE_PUBLIC 2 + { "public", 0 }, +#define CACHE_ASK 3 + { "ask", 0 }, + { NULL, 0 } +}; diff --git a/h/mhparse.h b/h/mhparse.h new file mode 100644 index 0000000..f99a1c7 --- /dev/null +++ b/h/mhparse.h @@ -0,0 +1,245 @@ + +/* + * mhparse.h -- definitions for parsing/building of MIME content + * -- (mhparse.c/mhbuildsbr.c) + * + * $Id$ + */ + +#define NPARTS 50 +#define NTYPES 20 +#define NPARMS 10 + +/* + * Abstract type for header fields + */ +typedef struct hfield *HF; + +/* + * Abstract types for MIME parsing/building + */ +typedef struct cefile *CE; +typedef struct CTinfo *CI; +typedef struct Content *CT; + +/* + * type for Init function (both type and transfer encoding) + */ +typedef int (*InitFunc) (CT); + +/* + * types for various transfer encoding access functions + */ +typedef int (*OpenCEFunc) (CT, char **); +typedef void (*CloseCEFunc) (CT); +typedef unsigned long (*SizeCEFunc) (CT); + +/* + * Structure for storing/encoding/decoding + * a header field and its value. + */ +struct hfield { + char *name; /* field name */ + char *value; /* field body */ + int hf_encoding; /* internal flag for transfer encoding to use */ + HF next; /* link to next header field */ +}; + +/* + * Structure for storing parsed elements + * of the Content-Type component. + */ +struct CTinfo { + char *ci_type; /* content type */ + char *ci_subtype; /* content subtype */ + char *ci_attrs[NPARMS + 2]; /* attribute names */ + char *ci_values[NPARMS]; /* attribute values */ + char *ci_comment; /* RFC-822 comments */ + char *ci_magic; +}; + +/* + * Structure for storing decoded contents after + * removing Content-Transfer-Encoding. + */ +struct cefile { + char *ce_file; /* decoded content (file) */ + FILE *ce_fp; /* decoded content (stream) */ + int ce_unlink; /* remove file when done? */ +}; + +/* + * Primary structure for handling Content (Entity) + */ +struct Content { + /* source (read) file */ + char *c_file; /* read contents (file) */ + FILE *c_fp; /* read contents (stream) */ + int c_unlink; /* remove file when done? */ + + long c_begin; /* where content body starts in file */ + long c_end; /* where content body ends in file */ + + /* linked list of header fields */ + HF c_first_hf; /* pointer to first header field */ + HF c_last_hf; /* pointer to last header field */ + + /* copies of MIME related header fields */ + char *c_vrsn; /* MIME-Version: */ + char *c_ctline; /* Content-Type: */ + char *c_celine; /* Content-Transfer-Encoding: */ + char *c_id; /* Content-ID: */ + char *c_descr; /* Content-Description: */ + char *c_partno; /* within multipart content */ + + /* Content-Type info */ + struct CTinfo c_ctinfo; /* parsed elements of Content-Type */ + int c_type; /* internal flag for content type */ + int c_subtype; /* internal flag for content subtype */ + + /* Content-Transfer-Encoding info (decoded contents) */ + CE c_cefile; /* structure holding decoded content */ + int c_encoding; /* internal flag for encoding type */ + + /* Content-MD5 info */ + int c_digested; /* have we seen this header before? */ + unsigned char c_digest[16]; /* decoded MD5 checksum */ + + /* pointers to content-specific structures */ + void *c_ctparams; /* content type specific data */ + struct exbody *c_ctexbody; /* data for type message/external */ + + /* function pointers */ + InitFunc c_ctinitfnx; /* parse content body */ + OpenCEFunc c_ceopenfnx; /* get a stream to decoded contents */ + CloseCEFunc c_ceclosefnx; /* release stream */ + SizeCEFunc c_cesizefnx; /* size of decoded contents */ + + int c_umask; /* associated umask */ + pid_t c_pid; /* process doing display */ + int c_rfc934; /* rfc934 compatibility flag */ + + char *c_showproc; /* default, if not in profile */ + char *c_termproc; /* for charset madness... */ + char *c_storeproc; /* overrides profile entry, if any */ + + char *c_storage; /* write contents (file) */ + char *c_folder; /* write contents (folder) */ +}; + +/* + * Flags for Content-Type (Content->c_type) + */ +#define CT_UNKNOWN 0x00 +#define CT_APPLICATION 0x01 +#define CT_AUDIO 0x02 +#define CT_IMAGE 0x03 +#define CT_MESSAGE 0x04 +#define CT_MULTIPART 0x05 +#define CT_TEXT 0x06 +#define CT_VIDEO 0x07 +#define CT_EXTENSION 0x08 + +/* + * Flags for Content-Transfer-Encoding (Content->c_encoding) + */ +#define CE_UNKNOWN 0x00 +#define CE_BASE64 0x01 +#define CE_QUOTED 0x02 +#define CE_8BIT 0x03 +#define CE_7BIT 0x04 +#define CE_BINARY 0x05 +#define CE_EXTENSION 0x06 +#define CE_EXTERNAL 0x07 /* for external-body */ + +/* + * TEXT content + */ + +/* Flags for subtypes of TEXT */ +#define TEXT_UNKNOWN 0x00 +#define TEXT_PLAIN 0x01 +#define TEXT_RICHTEXT 0x02 +#define TEXT_ENRICHED 0x03 + +/* Flags for character sets */ +#define CHARSET_UNKNOWN 0x00 +#define CHARSET_UNSPECIFIED 0x01 /* only needed when building drafts */ +#define CHARSET_USASCII 0x01 +#define CHARSET_LATIN 0x02 + +/* Structure for text content */ +struct text { + int tx_charset; /* flag for character set */ +}; + +/* + * MULTIPART content + */ + +/* Flags for subtypes of MULTIPART */ +#define MULTI_UNKNOWN 0x00 +#define MULTI_MIXED 0x01 +#define MULTI_ALTERNATE 0x02 +#define MULTI_DIGEST 0x03 +#define MULTI_PARALLEL 0x04 + +/* Structure for subparts of a multipart content */ +struct part { + CT mp_part; /* Content structure for subpart */ + struct part *mp_next; /* pointer to next subpart structure */ +}; + +/* Main structure for multipart content */ +struct multipart { + char *mp_start; /* boundary string separating parts */ + char *mp_stop; /* terminating boundary string */ + struct part *mp_parts; /* pointer to first subpart structure */ +}; + +/* + * MESSAGE content + */ + +/* Flags for subtypes of MESSAGE */ +#define MESSAGE_UNKNOWN 0x00 +#define MESSAGE_RFC822 0x01 +#define MESSAGE_PARTIAL 0x02 +#define MESSAGE_EXTERNAL 0x03 + +/* Structure for message/partial */ +struct partial { + char *pm_partid; + int pm_partno; + int pm_maxno; + int pm_marked; + int pm_stored; +}; + +/* Structure for message/external */ +struct exbody { + CT eb_parent; /* pointer to controlling content structure */ + CT eb_content; /* pointer to internal content structure */ + char *eb_partno; + char *eb_access; + int eb_flags; + char *eb_name; + char *eb_permission; + char *eb_site; + char *eb_dir; + char *eb_mode; + unsigned long eb_size; + char *eb_server; + char *eb_subject; + char *eb_body; +}; + +/* + * APPLICATION content + */ + +/* Flags for subtype of APPLICATION */ +#define APPLICATION_UNKNOWN 0x00 +#define APPLICATION_OCTETS 0x01 +#define APPLICATION_POSTSCRIPT 0x02 + diff --git a/h/mime.h b/h/mime.h new file mode 100644 index 0000000..b741b2f --- /dev/null +++ b/h/mime.h @@ -0,0 +1,38 @@ + +/* + * mime.h -- definitions for MIME + * + * $Id$ + */ + +#define VRSN_FIELD "MIME-Version" +#define VRSN_VALUE "1.0" +#define XXX_FIELD_PRF "Content-" +#define TYPE_FIELD "Content-Type" +#define ENCODING_FIELD "Content-Transfer-Encoding" +#define ID_FIELD "Content-ID" +#define DESCR_FIELD "Content-Description" +#define MD5_FIELD "Content-MD5" + +#define isatom(c) (!isspace (c) && !iscntrl (c) && (c) != '(' \ + && (c) != ')' && (c) != '<' && (c) != '>' \ + && (c) != '@' && (c) != ',' && (c) != ';' \ + && (c) != ':' && (c) != '\\' && (c) != '"' \ + && (c) != '.' && (c) != '[' && (c) != ']') + +/* + * Test for valid characters used in "token" + * as defined in RFC2045 + */ +#define istoken(c) (!isspace (c) && !iscntrl (c) && (c) != '(' \ + && (c) != ')' && (c) != '<' && (c) != '>' \ + && (c) != '@' && (c) != ',' && (c) != ';' \ + && (c) != ':' && (c) != '\\' && (c) != '"' \ + && (c) != '/' && (c) != '[' && (c) != ']' \ + && (c) != '?' && (c) != '=') + +#define CPERLIN 76 +#define BPERLIN (CPERLIN / 4) +#define LPERMSG 632 +#define CPERMSG (LPERMSG * CPERLIN) + diff --git a/h/msh.h b/h/msh.h new file mode 100644 index 0000000..c6b0fa5 --- /dev/null +++ b/h/msh.h @@ -0,0 +1,114 @@ + +/* + * msh.h -- definitions for msh + * + * $Id$ + */ + +/* flags for stream */ +#define STDIO 0 /* regular stdoutput */ +#define CRTIO 1 /* create re-direct */ +#define APPIO 2 /* append re-direct */ +#define PIPIO 3 /* pipe re-direct */ + +struct Cmd { + char line[BUFSIZ]; + char *args[MAXARGS]; + char *redirect; + int direction; + FILE *stream; +}; + +#define NULLCMD ((struct Cmd *) 0) + +#define MHNCHK 0x0001 /* did nontext check */ +#define MHNYES 0x0002 /* .. and known to be non-text */ + +#define CUR (1 << (FFATTRSLOT + NUMATTRS - 1)) + +#ifdef BPOP +# define VIRTUAL SELECT_EMPTY + +# define is_virtual(mp,msgnum) ((mp)->msgstats[msgnum] & VIRTUAL) +# define unset_virtual(mp,msgnum) ((mp)->msgstats[msgnum] &= ~VIRTUAL) +# define set_virtual(mp,msgnum) ((mp)->msgstats[msgnum] |= VIRTUAL) +#endif + +struct Msg { + struct drop m_drop; + char *m_scanl; + struct tws m_tb; + short m_flags; + seqset_t m_stats; +}; + +#define m_bboard_id m_drop.d_id +#define m_top m_drop.d_size +#define m_start m_drop.d_start +#define m_stop m_drop.d_stop + +/* + * FOLDER + */ +extern char *fmsh; /* folder instead of file */ +extern int modified; /* command modified folder */ +extern struct msgs *mp; /* used a lot */ +extern struct Msg *Msgs; /* Msgs[0] not used */ + +FILE *msh_ready (); + +/* + * COMMAND + */ +extern int interactive; /* running from a /dev/tty */ +extern int redirected; /* re-directing output */ +extern FILE *sp; /* original stdout */ +extern char *cmd_name; /* command being run */ +extern char myfilter[]; /* path to mhl.forward */ + +extern char *BBoard_ID; /* BBoard-ID constant */ + +/* + * SIGNALS + */ +extern SIGNAL_HANDLER istat; /* original SIGINT */ +extern SIGNAL_HANDLER qstat; /* original SIGQUIT */ +extern int interrupted; /* SIGINT detected */ +extern int broken_pipe; /* SIGPIPE detected */ +extern int told_to_quit; /* SIGQUIT detected */ + +#ifdef BSD42 +extern int should_intr; /* signal handler should interrupt call */ +extern jmp_buf sigenv; /* the environment pointer */ +#endif + +/* + * prototypes + */ +int readid (int); +int expand (char *); +void m_reset (void); +void fsetup (char *); +void setup (char *); +void readids (int); +void display_info (int); + +void forkcmd (char **s, char *); +void distcmd (char **); +void explcmd (char **); +int filehak (char **); +void filecmd (char **); +void foldcmd (char **); +void forwcmd (char **); +void helpcmd (char **); +void markcmd (char **); +void mhncmd (char **); +void showcmd (char **); +int pack (char *, int, int); +int packhak (char **); +void packcmd (char **); +void pickcmd (char **); +void replcmd (char **); +void rmmcmd (char **); +void scancmd (char **); +void sortcmd (char **); diff --git a/h/netdb.h b/h/netdb.h new file mode 100644 index 0000000..41b0226 --- /dev/null +++ b/h/netdb.h @@ -0,0 +1,71 @@ +/* + * netdb.h + * + * Copyright (c) 1980,1983,1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that this notice is preserved and that due credit is given + * to the University of California at Berkeley. The name of the University + * may not be used to endorse or promote products derived from this + * software without specific prior written permission. This software + * is provided ``as is'' without express or implied warranty. + * + * $Id$ + */ + +/* + * Structures returned by network + * data base library. All addresses + * are supplied in host order, and + * returned in network order (suitable + * for use in system calls). + */ +struct hostent { + char *h_name; /* official name of host */ + char **h_aliases; /* alias list */ + int h_addrtype; /* host address type */ + int h_length; /* length of address */ + char **h_addr_list; /* list of addresses from name server */ +#define h_addr h_addr_list[0] /* address, for backward compatiblity */ +}; + +/* + * Assumption here is that a network number + * fits in 32 bits -- probably a poor one. + */ +struct netent { + char *n_name; /* official name of net */ + char **n_aliases; /* alias list */ + int n_addrtype; /* net address type */ + unsigned long n_net; /* network # */ +}; + +struct servent { + char *s_name; /* official service name */ + char **s_aliases; /* alias list */ + int s_port; /* port # */ + char *s_proto; /* protocol to use */ +}; + +struct protoent { + char *p_name; /* official protocol name */ + char **p_aliases; /* alias list */ + int p_proto; /* protocol # */ +}; + +struct hostent *gethostbyname(), *gethostbyaddr(), *gethostent(); +struct netent *getnetbyname(), *getnetbyaddr(), *getnetent(); +struct servent *getservbyname(), *getservbyport(), *getservent(); +struct protoent *getprotobyname(), *getprotobynumber(), *getprotoent(); + +/* + * Error return codes from gethostbyname() and gethostbyaddr() + * (left in extern int h_errno). + */ + +#define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found */ +#define TRY_AGAIN 2 /* Non-Authoritive Host not found, or SERVERFAIL */ +#define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ +#define NO_DATA 4 /* Valid name, no data record of requested type */ +#define NO_ADDRESS NO_DATA /* no address, look for MX record */ diff --git a/h/nmh.h b/h/nmh.h new file mode 100644 index 0000000..f00a3ad --- /dev/null +++ b/h/nmh.h @@ -0,0 +1,162 @@ + +/* + * nmh.h -- system configuration header file + * + * $Id$ + */ + +#include + +#ifdef HAVE_UNISTD_H +# include +# include +#endif + +#include +#include +#include + +#if HAVE_DIRENT_H +# include +# define NLENGTH(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NLENGTH(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include +# endif +# if HAVE_SYS_DIR_H +# include +# endif +# if HAVE_NDIR_H +# include +# endif +#endif + +#ifdef HAVE_STDLIB_H +# include +#endif + +#include + +#if STDC_HEADERS || HAVE_STRING_H +# include +/* An ANSI string.h and pre-ANSI memory.h might conflict. */ +# if !STDC_HEADERS && HAVE_MEMORY_H +# include +# endif /* not STDC_HEADERS and HAVE_MEMORY_H */ +#else /* not STDC_HEADERS and not HAVE_STRING_H */ +# include +/* memory.h and strings.h conflict on some systems. */ +#endif /* not STDC_HEADERS and not HAVE_STRING_H */ + +#ifdef HAVE_SYS_PARAM_H +# include +#endif + +#ifdef HAVE_LOCALE_H +# include +#endif + +#ifdef HAVE_LIMITS_H +# include +#endif + +/* + * symbolic constants for lseek and fseek + */ +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif +#ifndef SEEK_CUR +# define SEEK_CUR 1 +#endif +#ifndef SEEK_END +# define SEEK_END 2 +#endif + +/* + * we should be getting this value from pathconf(_PC_PATH_MAX) + */ +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else + /* so we will just pick something */ +# define PATH_MAX 1024 +# endif +#endif + +/* + * we should get this value from sysconf(_SC_NGROUPS_MAX) + */ +#ifndef NGROUPS_MAX +# ifdef NGROUPS +# define NGROUPS_MAX NGROUPS +# else +# define NGROUPS_MAX 16 +# endif +#endif + +/* + * we should be getting this value from sysconf(_SC_OPEN_MAX) + */ +#ifndef OPEN_MAX +# ifdef NOFILE +# define OPEN_MAX NOFILE +# else + /* so we will just pick something */ +# define OPEN_MAX 64 +# endif +#endif + +#include + +#define bcmp(b1,b2,length) memcmp(b1, b2, length) +#define bcopy(b1,b2,length) memcpy (b2, b1, length) +#define bcpy(b1,b2,length) memcmp (b1, b2, length) +#define bzero(b,length) memset (b, 0, length) + +#ifdef HAVE_KILLPG +# define KILLPG(pgrp,sig) killpg(pgrp,sig); +#else +# define KILLPG(pgrp,sig) kill((-pgrp),sig); +#endif + +/* + * If your stat macros are broken, + * we will just undefine them. + */ +#ifdef STAT_MACROS_BROKEN +# ifdef S_ISBLK +# undef S_ISBLK +# endif +# ifdef S_ISCHR +# undef S_ISCHR +# endif +# ifdef S_ISDIR +# undef S_ISDIR +# endif +# ifdef S_ISFIFO +# undef S_ISFIFO +# endif +# ifdef S_ISLNK +# undef S_ISLNK +# endif +# ifdef S_ISMPB +# undef S_ISMPB +# endif +# ifdef S_ISMPC +# undef S_ISMPC +# endif +# ifdef S_ISNWK +# undef S_ISNWK +# endif +# ifdef S_ISREG +# undef S_ISREG +# endif +# ifdef S_ISSOCK +# undef S_ISSOCK +# endif +#endif /* STAT_MACROS_BROKEN. */ + diff --git a/h/nntp.h b/h/nntp.h new file mode 100644 index 0000000..b232949 --- /dev/null +++ b/h/nntp.h @@ -0,0 +1,73 @@ +/* + * nntp.h -- Response codes for NNTP server + * + * $Id$ + * + * First digit: + * + * 1xx Informative message + * 2xx Command ok + * 3xx Command ok so far, continue + * 4xx Command was correct, but couldn't be performed + * for some specified reason. + * 5xx Command unimplemented, incorrect, or a + * program error has occured. + * + * Second digit: + * + * x0x Connection, setup, miscellaneous + * x1x Newsgroup selection + * x2x Article selection + * x3x Distribution + * x4x Posting + */ + +#define CHAR_INF '1' +#define CHAR_OK '2' +#define CHAR_CONT '3' +#define CHAR_ERR '4' +#define CHAR_FATAL '5' + +#define INF_HELP 100 /* Help text on way */ +#define INF_DEBUG 199 /* Debug output */ + +#define OK_CANPOST 200 /* Hello; you can post */ +#define OK_NOPOST 201 /* Hello; you can't post */ +#define OK_SLAVE 202 /* Slave status noted */ +#define OK_GOODBYE 205 /* Closing connection */ +#define OK_GROUP 211 /* Group selected */ +#define OK_GROUPS 215 /* Newsgroups follow */ +#define OK_ARTICLE 220 /* Article (head & body) follows */ +#define OK_HEAD 221 /* Head follows */ +#define OK_BODY 222 /* Body follows */ +#define OK_NOTEXT 223 /* No text sent -- stat, next, last */ +#define OK_NEWNEWS 230 /* New articles by message-id follow */ +#define OK_NEWGROUPS 231 /* New newsgroups follow */ +#define OK_XFERED 235 /* Article transferred successfully */ +#define OK_POSTED 240 /* Article posted successfully */ + +#define CONT_XFER 335 /* Continue to send article */ +#define CONT_POST 340 /* Continue to post article */ + +#define ERR_GOODBYE 400 /* Have to hang up for some reason */ +#define ERR_NOGROUP 411 /* No such newsgroup */ +#define ERR_NCING 412 /* Not currently in newsgroup */ +#define ERR_NOCRNT 420 /* No current article selected */ +#define ERR_NONEXT 421 /* No next article in this group */ +#define ERR_NOPREV 422 /* No previous article in this group */ +#define ERR_NOARTIG 423 /* No such article in this group */ +#define ERR_NOART 430 /* No such article at all */ +#define ERR_GOTIT 435 /* Already got that article, don't send */ +#define ERR_XFERFAIL 436 /* Transfer failed */ +#define ERR_XFERRJCT 437 /* Article rejected, don't resend */ +#define ERR_NOPOST 440 /* Posting not allowed */ +#define ERR_POSTFAIL 441 /* Posting failed */ + +#define ERR_COMMAND 500 /* Command not recognized */ +#define ERR_CMDSYN 501 /* Command syntax error */ +#define ERR_ACCESS 502 /* Access to server denied */ +#define ERR_FAULT 503 /* Program fault, command not performed */ + +/* RFC 977 defines this; don't change it. */ + +#define NNTP_STRLEN 512 diff --git a/h/picksbr.h b/h/picksbr.h new file mode 100644 index 0000000..811836d --- /dev/null +++ b/h/picksbr.h @@ -0,0 +1,12 @@ + +/* + * picksbr.h -- definitions for picksbr.c + * + * $Id$ + */ + +/* + * prototypes + */ +int pcompile (char **, char *); +int pmatches (FILE *, int, long, long); diff --git a/h/popsbr.h b/h/popsbr.h new file mode 100644 index 0000000..a9e0883 --- /dev/null +++ b/h/popsbr.h @@ -0,0 +1,62 @@ + +/* + * popsbr.h -- header for POP client subroutines + * + * $Id$ + */ + +#if 0 +#if !defined(NNTP) && defined(MPOP) +# define command pop_command +# define multiline pop_multiline +#endif +#endif + +#ifdef NNTP +int pop_set (int, int, int, char *); +#else +int pop_set (int, int, int); +#endif + +#ifdef NNTP +int pop_exists (int (*)()); +#endif + +int pop_init (char *, char *, char *, int, int); +int pop_fd (char *, int, char *, int); +int pop_stat (int *, int *); +int pop_retr (int, int (*)()); +int pop_dele (int); +int pop_noop (void); +int pop_rset (void); +int pop_top (int, int, int (*)()); +int pop_quit (void); +int pop_done (void); + +#ifdef BPOP +int pop_list (int, int *, int *, int *, int *); +#else +int pop_list (int, int *, int *, int *); +#endif + +#ifdef BPOP +int pop_xtnd (int (*)(), char *, ...); +#endif + +#if defined(MPOP) && !defined(NNTP) +int pop_last (void); +#endif + +#if !defined(NNTP) && defined(MPOP) +/* otherwise they are static functions */ +int command(const char *, ...); +int multiline(void); +#endif + +/* + * Flags for the various pop authentication methods + */ +#define POP_APOP -1 +#define POP_PASSWD 0 +#define POP_RPOP 1 +#define POP_KPOP 2 diff --git a/h/prototypes.h b/h/prototypes.h new file mode 100644 index 0000000..0dccfa2 --- /dev/null +++ b/h/prototypes.h @@ -0,0 +1,159 @@ + +/* + * prototypes.h -- various prototypes + * + * $Id$ + */ + +/* + * missing system prototypes + */ +#ifndef HAVE_TERMCAP_H +extern int tgetent (char *bp, char *name); +extern int tgetnum (char *id); +extern int tgetflag (char *id); +extern char *tgetstr (char *id, char **area); +extern char *tgoto (char *cm, int destcol, int destline); +extern int tputs (char *cp, int affcnt, int (*outc) (int)); +#endif + +/* + * prototype from config.h + */ +char *etcpath(char *); + +/* + * prototypes from the nmh subroutine library + */ +char *add (char *, char *); +void adios (char *, char *, ...); +void admonish (char *, char *, ...); +void advertise (char *, char *, char *, va_list); +void advise (char *, char *, ...); +void ambigsw (char *, struct swit *); +int atooi(char *); +char **brkstring (char *, char *, char *); +int check_charset (char *, int); +void closefds(int); +char *concat (char *, ...); +int context_del (char *); +char *context_find (char *); +int context_foil (char *); +void context_read (void); +void context_replace (char *, char *); +void context_save (void); +char *copy (char *, char *); +char **copyip (char **, char **, int); +void cpydata (int, int, char *, char *); +void cpydgst (int, int, char *, char *); +int decode_rfc2047 (char *, char *); +void discard (FILE *); +void done (int); +int fdcompare (int, int); +int folder_addmsg (struct msgs **, char *, int, int, int); +int folder_delmsgs (struct msgs *, int); +void folder_free (struct msgs *); +int folder_pack (struct msgs **, int); +struct msgs *folder_read (char *); +struct msgs *folder_realloc (struct msgs *, int, int); +int gans (char *, struct swit *); +char **getans (char *, struct swit *); +int getanswer (char *); +char **getarguments (char *, int, char **, int); +char *getcpy (char *); +char *getfolder(int); +int lkclose(int, char*); +int lkfclose(FILE *, char *); +FILE *lkfopen(char *, char *); +int lkopen(char *, int, mode_t); +int m_atoi (char *); +char *m_backup (char *); +int m_convert (struct msgs *, char *); +char *m_draft (char *, char *, int, int *); +void m_eomsbr (int (*)()); +int m_getfld (int, unsigned char *, unsigned char *, int, FILE *); +int m_gmprot (void); +char *m_maildir (char *); +char *m_mailpath (char *); +char *m_name (int); +int m_putenv (char *, char *); +char *m_scratch (char *, char *); +char *m_tmpfil (char *); +void m_unknown(FILE *); +int makedir (char *); +char *new_fs (char *, char *, char *); +char *path(char *, int); +int peekc(FILE *ib); +int pidwait (pid_t, int); +int pidstatus (int, FILE *, char *); +void print_help (char *, struct swit *, int); +void print_sw (char *, struct swit *, char *); +void print_version (char *); +void push (void); +char *pwd (void); +char *r1bindex(char *, int); +void readconfig (struct node **, FILE *, char *, int); +int refile (char **, char *); +int remdir (char *); +int seq_addmsg (struct msgs *, char *, int, int, int); +int seq_addsel (struct msgs *, char *, int, int); +char *seq_bits (struct msgs *); +int seq_delmsg (struct msgs *, char *, int); +int seq_delsel (struct msgs *, char *, int, int); +int seq_getnum (struct msgs *, char *); +char *seq_list (struct msgs *, char *); +int seq_nameok (char *); +void seq_print (struct msgs *, char *); +void seq_printall (struct msgs *); +void seq_read (struct msgs *); +void seq_save (struct msgs *); +void seq_setcur (struct msgs *, int); +void seq_setprev (struct msgs *); +void seq_setunseen (struct msgs *, int); +int showfile (char **, char *); +int smatch(char *, struct swit *); +char *snprintb (char *, size_t, unsigned, char *); +int ssequal (char *, char *); +int stringdex (char *, char *); +char *trimcpy (char *); +int unputenv (char *); +int uprf (char *, char *); +int vfgets (FILE *, char **); +char *write_charset_8bit (void); + +#ifdef RPATHS +int get_returnpath (char *, int, char *, int); +#endif + +/* + * prototypes for compatibility functions in library + */ +#ifndef HAVE_SNPRINTF +int snprintf (char *, size_t, const char *, ...); +int vsnprintf (char *, size_t, const char *, va_list); +#endif + +#ifndef HAVE_STRERROR +char *strerror (int); +#endif + + +/* + * some prototypes for address parsing system + * (others are in addrsbr.h) + */ +char *LocalName(void); +char *SystemName(void); +char *OfficialName(char *); + +/* + * prototypes for some routines in uip + */ +int annotate (char *, char *, char *, int, int); +int distout (char *, char *, char *); +void replout (FILE *, char *, char *, struct msgs *, int, + int, char *, char *, char *); +int sendsbr (char **, int, char *, struct stat *, int); +int what_now (char *, int, int, char *, char *, + int, struct msgs *, char *, int, char *); + diff --git a/h/rcvmail.h b/h/rcvmail.h new file mode 100644 index 0000000..856409e --- /dev/null +++ b/h/rcvmail.h @@ -0,0 +1,39 @@ + +/* + * rcvmail.h -- rcvmail hook definitions + * + * $Id$ + */ + +#if defined(SENDMTS) || defined(SMTPMTS) +# include +# include +# include +# include +# include +# include +#endif /* SENDMTS || SMTPMTS */ + +#ifdef MMDFMTS +# include +# include +#endif /* MMDFMTS */ + + +#if defined(SENDMTS) || defined(SMTPMTS) +# define RCV_MOK 0 +# define RCV_MBX 1 +#endif /* SENDMTS || SMTPMTS */ + +#ifdef MMDFI +# define RCV_MOK RP_MOK +# define RCV_MBX RP_MECH +#endif /* MMDFI */ + + +#ifdef NRTC /* sigh */ +# undef RCV_MOK +# undef RCV_MBX +# define RCV_MOK RP_MOK +# define RCV_MBX RP_MECH +#endif /* NRTC */ diff --git a/h/scansbr.h b/h/scansbr.h new file mode 100644 index 0000000..b050d97 --- /dev/null +++ b/h/scansbr.h @@ -0,0 +1,42 @@ + +/* + * scansbr.h -- definitions for scan() + * + * $Id$ + */ + +extern char *scanl; + +#define SCNENC 2 /* message just fine, but encrypted(!!) */ +#define SCNMSG 1 /* message just fine */ +#define SCNEOF 0 /* empty message */ +#define SCNERR (-1) /* error message */ +#define SCNNUM (-2) /* number out of range */ +#define SCNFAT (-3) /* fatal error */ + +/* + * default format for `scan' and `inc' + */ + +#ifndef UK +#define FORMAT \ +"%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mon{date})/%02(mday{date})%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}>>%>\n" +#else +#define FORMAT \ +"%4(msg)%<(cur)+%| %>%<{replied}-%?{encrypted}E%| %>\ +%02(mday{date})/%02(mon{date})%<{date} %|*%>\ +%<(mymbox{from})%<{to}To:%14(decode(friendly{to}))%>%>\ +%<(zero)%17(decode(friendly{from}))%> \ +%(decode{subject})%<{body}<<%{body}>>%>\n" +#endif + +#define WIDTH 78 + +/* + * prototypes + */ +int scan (FILE *, int, int, char *, int, int, int, char *, long, int); diff --git a/h/signals.h b/h/signals.h new file mode 100644 index 0000000..9648e04 --- /dev/null +++ b/h/signals.h @@ -0,0 +1,34 @@ + +/* + * signals.h -- header file for nmh signal interface + * + * $Id$ + */ + +#include + +/* + * The type for a signal handler + */ +typedef RETSIGTYPE (*SIGNAL_HANDLER)(int); + +/* + * If not a POSIX machine, then we create our + * own POSIX style signal sets functions. This + * currently assumes you have 31 signals, which + * should be true on most pure BSD machines. + */ +#ifndef POSIX_SIGNALS +# define sigemptyset(s) (*(s) = 0) +# define sigfillset(s) (*(s) = ~((sigset_t) 0), 0) +# define sigaddset(s,n) (*(s) |= (1 << ((n) - 1)), 0) +# define sigdelset(s,n) (*(s) &= ~(1 << ((n) - 1)), 0) +# define sigismember(s,n) ((*(s) & (1 << ((n) - 1))) != 0) +#endif + +/* + * prototypes + */ +int SIGPROCMASK (int, const sigset_t *, sigset_t *); +SIGNAL_HANDLER SIGNAL (int, SIGNAL_HANDLER); +SIGNAL_HANDLER SIGNAL2 (int, SIGNAL_HANDLER); diff --git a/h/vmhsbr.h b/h/vmhsbr.h new file mode 100644 index 0000000..452f2e3 --- /dev/null +++ b/h/vmhsbr.h @@ -0,0 +1,51 @@ + +/* + * vmhsbr.h -- definitions for the vmh protocol + * + * $Id$ + */ + +#define RC_VRSN 1 + +/* flags for rh_type */ +#define RC_INI 0x01 /* must be greater than OK */ +#define RC_ACK 0x02 +#define RC_ERR 0x03 +#define RC_CMD 0x04 +#define RC_QRY 0x05 +#define RC_TTY 0x06 +#define RC_WIN 0x07 +#define RC_DATA 0x08 +#define RC_EOF 0x09 +#define RC_FIN 0x0a +#define RC_XXX 0x0b + +struct record { + struct rcheader { + char rh_type; /* type of record */ + int rh_len; /* length of data */ + } rc_header; + char *rc_data; /* extensible array */ +}; + +#define rc_head(rc) (&rc->rc_header) +#define RHSIZE(rc) (sizeof rc->rc_header) +#define rc_type rc_header.rh_type +#define rc_len rc_header.rh_len + +#define initrc(rc) rc->rc_data = NULL + +/* + * prototypes + */ +int rcinit (int, int); +int rcdone (void); +int rc2rc (char, int, char *, struct record *); +int str2rc (char, char *, struct record *); +int peer2rc (struct record *); +int rc2peer (char, int, char *); +int str2peer (char, char *); +int fmt2peer (char, char *, ...); +int err2peer (char, char *, char *, ...); +int verr2peer (char, char *, char *, va_list); + diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..0d02bf2 --- /dev/null +++ b/install-sh @@ -0,0 +1,237 @@ +#! /bin/sh +# +# install -- install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/man/Makefile.in b/man/Makefile.in new file mode 100644 index 0000000..d08c3a2 --- /dev/null +++ b/man/Makefile.in @@ -0,0 +1,210 @@ +# +# Makefile for man subdirectory +# +# $Id$ +# + +VERSION = @VERSION@ + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ +mandir = @mandir@ +manext1 = 1 +manext5 = 5 +manext8 = 8 + +mailspool = @mailspool@ +sendmailpath = @sendmailpath@ + +default_editor = @editorpath@ +default_pager = @pagerpath@ + +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ + +SED = sed +SEDMAN = $(SED) -f man.sed $< > $@ + +# sed line for editing pop information in man pages +POPSED = @POPSED@ + +.SUFFIXES: +.SUFFIXES: .man .$(manext1) .$(manext5) .$(manext8) + +.man.$(manext1): + $(SEDMAN) + +.man.$(manext5): + $(SEDMAN) + +.man.$(manext8): + $(SEDMAN) + +# include file for the man pages. It is installed +# in the nmh library directory. +HDR = tmac.h + +# input file for header +DIST_HDR = tmac.h.in + +# man pages to install in $(mandir)/$(manext1) +MAN1 = ali.$(manext1) anno.$(manext1) burst.$(manext1) comp.$(manext1) \ + dist.$(manext1) flist.$(manext1) folder.$(manext1) forw.$(manext1) \ + inc.$(manext1) mark.$(manext1) mh-chart.$(manext1) nmh.$(manext1) \ + mhbuild.$(manext1) mhl.$(manext1) mhlist.$(manext1) mhmail.$(manext1) \ + mhn.$(manext1) mhparam.$(manext1) mhpath.$(manext1) mhshow.$(manext1) \ + mhstore.$(manext1) msgchk.$(manext1) msh.$(manext1) \ + next.$(manext1) packf.$(manext1) pick.$(manext1) prev.$(manext1) \ + prompter.$(manext1) rcvdist.$(manext1) rcvpack.$(manext1) \ + rcvstore.$(manext1) rcvtty.$(manext1) refile.$(manext1) \ + repl.$(manext1) rmf.$(manext1) rmm.$(manext1) scan.$(manext1) \ + send.$(manext1) sendfiles.$(manext1) show.$(manext1) slocal.$(manext1) \ + sortm.$(manext1) vmh.$(manext1) whatnow.$(manext1) whom.$(manext1) + +MAN5 = mh-alias.$(manext5) mh-draft.$(manext5) mh-format.$(manext5) \ + mh-mail.$(manext5) mh-profile.$(manext5) mh-sequence.$(manext5) \ + mh-tailor.$(manext5) + +MAN8 = ap.$(manext8) conflict.$(manext8) dp.$(manext8) \ + fmtdump.$(manext8) install-mh.$(manext8) mh-mts.$(manext8) \ + post.$(manext8) + +# source for man pages +DIST_MAN = ali.man anno.man ap.man burst.man comp.man conflict.man \ + dist.man dp.man flist.man fmtdump.man folder.man forw.man \ + inc.man install-mh.man mark.man mh-alias.man mh-chart.man \ + mh-draft.man mh-format.man mh-mail.man mh-mts.man mh-profile.man \ + mh-sequence.man mh-tailor.man nmh.man mhbuild.man mhl.man \ + mhlist.man mhmail.man mhn.man mhparam.man mhpath.man \ + mhshow.man mhstore.man msgchk.man msh.man \ + next.man packf.man pick.man post.man prev.man prompter.man \ + rcvdist.man rcvpack.man rcvstore.man rcvtty.man refile.man \ + repl.man rmf.man rmm.man scan.man send.man sendfiles.man show.man \ + slocal.man sortm.man vmh.man whatnow.man whom.man + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(DIST_HDR) $(DIST_MAN) $(AUX) + +# ========= DEFAULT TARGET ========== + +all: tmac.h $(MAN1) $(MAN5) $(MAN8) + +$(MAN1) $(MAN5) $(MAN8): man.sed + +tmac.h: man.sed tmac.h.in + $(SED) -f man.sed $(srcdir)/tmac.h.in > $@ + +# create the sed file for building man pages +man.sed: Makefile + echo 's,%nmhwarning%,THIS FILE HAS BEEN AUTOMATICALLY GENERATED. DO NOT EDIT.,g' > $@ + echo 's,%nmhversion%,nmh-$(VERSION),g' >> $@ + echo 's,%bindir%,$(bindir),g' >> $@ + echo 's,%etcdir%,$(etcdir),g' >> $@ + echo 's,%libdir%,$(libdir),g' >> $@ + echo 's,%mandir%,$(mandir),g' >> $@ + echo 's,%mailspool%,$(mailspool),g' >> $@ + echo 's,%sendmailpath%,$(sendmailpath),g' >> $@ + echo 's,%default_editor%,$(default_editor),g' >> $@ + echo 's,%default_pager%,$(default_pager),g' >> $@ + echo 's,%manext1%,$(manext1),g' >> $@ + echo 's,%manext5%,$(manext5),g' >> $@ + echo 's,%manext8%,$(manext8),g' >> $@ + echo '$(POPSED)' >> $@ + +# ========= INSTALL TARGETS ========= + +install: install-hdr install-man1 install-man5 install-man8 + +# install the include file for man pages +install-hdr: + $(top_srcdir)/mkinstalldirs $(etcdir) + $(INSTALL_DATA) tmac.h $(etcdir)/tmac.h + +# install the man pages in man1 +install-man1: + $(top_srcdir)/mkinstalldirs $(mandir)/man$(manext1) + for file in $(MAN1); do \ + $(INSTALL_DATA) $$file $(mandir)/man$(manext1) ; \ + done + +# install the man pages in man5 +install-man5: + $(top_srcdir)/mkinstalldirs $(mandir)/man$(manext5) + for file in $(MAN5); do \ + $(INSTALL_DATA) $$file $(mandir)/man$(manext5) ; \ + done + +# install the man pages in man8 +install-man8: + $(top_srcdir)/mkinstalldirs $(mandir)/man$(manext8) + for file in $(MAN8); do \ + $(INSTALL_DATA) $$file $(mandir)/man$(manext8) ; \ + done + +# ========= UNINSTALL TARGETS ========= + +uninstall: uninstall-hdr uninstall-man1 uninstall-man5 uninstall-man8 + +# uninstall the include file for man pages +uninstall-hdr: + rm -f $(etcdir)/tmac.h + +# uninstall the man pages in man1 +uninstall-man1: + for file in $(MAN1); do \ + rm -f $(mandir)/man$(manext1)/$$file; \ + done + +# uninstall the man pages in man5 +uninstall-man5: + for file in $(MAN5); do \ + rm -f $(mandir)/man$(manext5)/$$file; \ + done + +# uninstall the man pages in man8 +uninstall-man8: + for file in $(MAN8); do \ + rm -f $(mandir)/man$(manext8)/$$file; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *~ + +clean: mostlyclean + rm -f man.sed tmac.h *.$(manext1) *.$(manext5) *.$(manext8) + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = man + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/man/ali.man b/man/ali.man new file mode 100644 index 0000000..7e0c181 --- /dev/null +++ b/man/ali.man @@ -0,0 +1,69 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH ALI %manext1% MH.6.8 [%nmhversion%] +.SH NAME +ali \- list mail aliases +.SH SYNOPSIS +.in +.5i +.ti -.5i +ali +\%[\-alias\ aliasfile] +\%[\-list] \%[\-nolist] +\%[\-normalize] +.br +\%[\-nonormalize] +\%[\-user] \%[\-nouser] +\%[aliases\ ...] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIAli\fR searches the named mail alias files for each of the given +\fIaliases\fR. It creates a list of addresses for those \fIaliases\fR, +and writes that list on standard output. If no arguments are given, +\fIali\fR outputs all alias entries. + +By default, when an aliases expands to multiple addresses, the addresses +are separated by commas and printed on as few lines as possible. If the +`\-list' option is specified, then when an address expands to multiple +addresses, each address will appear on a separate line. + +The switch `\-user' directs \fIali\fR to perform its processing in +an inverted fashion: instead of listing the addresses that each given +alias expands to, \fIali\fR will list the aliases that expand to each +given address. If the `\-normalize' switch is given, \fIali\fR will +try to track down the official hostname of the address. + +The files specified by the profile entry \*(lqAliasfile:\*(rq and any +additional alias files given by the `\-alias aliasfile' switch will be +read. Each \fIalias\fR is processed as described in \fImh\-alias\fR\0(5). +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^/etc/passwd~^List of users +^/etc/group~^List of groups +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Aliasfile:~^For a default alias file +.Sa +mh\-alias(5) +.De +`\-alias %etcdir%/MailAliases' +.Ds +`\-nolist' +.Ds +`\-nonormalize' +.Ds +`\-nouser' +.Co +None +.Bu +The `\-user' option with `\-nonormalize' is not entirely accurate, as it +does not replace local nicknames for hosts with their official site names. +.En diff --git a/man/anno.man b/man/anno.man new file mode 100644 index 0000000..5cf5831 --- /dev/null +++ b/man/anno.man @@ -0,0 +1,73 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH ANNO %manext1% MH.6.8 [%nmhversion%] +.SH NAME +anno \- annotate messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +anno +\%[+folder] \%[msgs] +\%[\-component\ field] +\%[\-inplace] +.br +\%[\-noinplace] +\%[\-date] \%[\-nodate] +\%[\-text\ body] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIAnno\fR annotates the specified messages in the named folder using +the field and body. + +Usually, annotation is performed by the commands \fIdist\fR, \fIforw\fR, +and \fIrepl\fR, if they are given the `\-anno' switch. This allows you +to keep track of your distribution of, forwarding of, and replies to +a message. + +By using \fIanno\fR, you can perform arbitrary annotations of your own. +Each message selected will be annotated with the lines + + field:\ date + field:\ body + +The `\-nodate' switch inhibits the date annotation, leaving only the +body annotation. + +If a `\-component\ field' is not specified when \fIanno\fR is invoked, +\fIanno\fR will prompt the user for the name of field for the annotation. + +The field specified should be a valid 822-style message field name, +which means that it should consist of alphanumerics (or dashes) only. +The body specified is arbitrary text. + +Normally \fIanno\fR does the annotation inplace in order to preserve +any links to the message. You may change this by using the `\-noinplace' +switch. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +dist (1), forw (1), repl (1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-inplace' +.Ds +`\-date' +.Co +If a folder is given, it will become the current folder. The first +message annotated will become the current message. +.En diff --git a/man/ap.man b/man/ap.man new file mode 100644 index 0000000..d4c24a9 --- /dev/null +++ b/man/ap.man @@ -0,0 +1,84 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH AP %manext8% MH.6.8 [%nmhversion%] +.SH NAME +ap \- parse addresses 822-style +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/ap +\%[\-form\ formatfile] +.br +\%[\-format\ string] +\%[\-normalize] \%[\-nonormalize] +.br +\%[\-width\ columns] +addrs\ ... +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIAp\fR is a program that parses addresses according to the ARPA +Internet standard. It also understands many non\-standard formats. +It is useful for seeing how \fInmh\fR will interpret an address. + +The \fIap\fR program treats each argument as one or more addresses, and +prints those addresses out in the official 822\-format. Hence, it is +usually best to enclose each argument in double\-quotes for the shell. + +To override the output format used by \fIap\fR, the `\-format\ string' or +`\-format\ file' switches are used. This permits individual fields of +the address to be extracted with ease. The string is simply a format +string, and the file is simply a format file. See \fImh\-format\fR\0(5) +for the details. + +In addition to the standard escapes, +\fIap\fR also recognizes the following additional escape: +.sp 1 +.nf +.ta \w'Escape 'u +\w'Returns 'u +\fIEscape\fR \fIReturns\fR \fIDescription\fR +error string A diagnostic if the parse failed +.re +.fi + +If the `\-normalize' switch is given, \fIap\fR will try to track down +the official hostname of the address. + +Here is the default format string used by \fIap\fR: + +.ti +.5i +%<{error}%{error}: %{text}%|%(putstr(proper{text}))%> + +which says that if an error was detected, print the error, a `:', and +the address in error. Otherwise, output the 822\-proper format of +the address. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^%etcdir%/mts.conf~^nmh mts configuration file +.Pr +None +.Sa +dp(8), +.br +\fIStandard for the Format of ARPA Internet Text Messages\fR (RFC\-822) +.De +`\-format' defaults as described above +.Ds +`\-normalize' +.Ds +`\-width' defaults to the width of the terminal +.Co +None +.Bu +The argument to the `\-format' switch must be interpreted as a single token +by the shell that invokes \fIap\fR. +Therefore, +one must usually place the argument to this switch inside double\-quotes. +.En diff --git a/man/burst.man b/man/burst.man new file mode 100644 index 0000000..6b41d66 --- /dev/null +++ b/man/burst.man @@ -0,0 +1,99 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH BURST %manext1% MH.6.8 [%nmhversion%] +.SH NAME +burst \- explode digests into messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +burst +\%[+folder] \%[msgs] +\%[\-inplace] \%[\-noinplace] +\%[\-quiet] +.br +\%[\-noquiet] +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIBurst\fR considers the specified messages in the named folder to be +Internet digests, and explodes them in that folder. + +If `\-inplace' is given, each digest is replaced by the \*(lqtable +of contents\*(rq for the digest (the original digest is removed). +\fIBurst\fR then renumbers all of the messages following the digest in the +folder to make room for each of the messages contained within the digest. +These messages are placed immediately after the digest. + +If `\-noinplace' is given, each digest is preserved, no table of contents +is produced, and the messages contained within the digest are placed at +the end of the folder. Other messages are not tampered with in any way. + +The `\-quiet' switch directs \fIburst\fR to be silent about reporting +messages that are not in digest format. + +The `\-verbose' switch directs \fIburst\fR to tell the user the general +actions that it is taking to explode the digest. + +It turns out that \fIburst\fR works equally well on forwarded messages +and blind\-carbon\-copies as on Internet digests, provided that the +former two were generated by \fIforw\fR or \fIsend\fR. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Msg\-Protect:~^To set mode when creating a new message +.Sa +\fIProposed Standard for Message Encapsulation\fR (RFC\-934), +.br +inc(1), msh(1), pack(1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-noinplace' +.Ds +`\-noquiet' +.Ds +`\-noverbose' +.Co +If a folder is given, it will become the current folder. If `\-inplace' +is given, then the first message burst becomes the current message. +This leaves the context ready for a \fIshow\fR of the table of contents +of the digest, and a \fInext\fR to see the first message of the digest. +If `\-noinplace' is given, then the first message extracted from the +first digest burst becomes the current message. This leaves the context +in a similar, but not identical, state to the context achieved when using +`\-inplace'. +.Bu +The \fIburst\fR program enforces a limit on the number of messages which +may be \fIburst\fR from a single message. This number is on the order +of 1000 messages. There is usually no limit on the number of messages +which may reside in the folder after the \fIburst\fRing. + +Although \fIburst\fR uses a sophisticated algorithm to determine where +one encapsulated message ends and another begins, not all digestifying +programs use an encapsulation algorithm. In degenerate cases, this +usually results in \fIburst\fR finding an encapsulation boundary +prematurely and splitting a single encapsulated message into two or +more messages. These erroneous digestifying programs should be fixed. + +Furthermore, any text which appears after the last encapsulated message +is not placed in a separate message by \fIburst\fR. In the case of +digestified messages, this text is usually an \*(lqEnd of digest\*(rq +string. As a result of this possibly un\-friendly behavior on the +part of \fIburst\fR, note that when the `\-inplace' option is used, +this trailing information is lost. In practice, this is not a problem +since correspondents usually place remarks in text prior to the first +encapsulated message, and this information is not lost. +.En diff --git a/man/comp.man b/man/comp.man new file mode 100644 index 0000000..aebcb00 --- /dev/null +++ b/man/comp.man @@ -0,0 +1,124 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH COMP %manext1% MH.6.8 [%nmhversion%] +.SH NAME +comp \- compose a message +.SH SYNOPSIS +.in +.5i +.ti -.5i +comp +\%[+folder] \%[msg] +.br +.br +\%[\-form\ formfile] +\%[\-use] \%[\-nouse] +\%[\-file\ file] +.br +\%[\-draftfolder\ +folder] +\%[\-draftmessage\ msg] +.br +\%[\-nodraftfolder] +\%[\-editor\ editor] +\%[\-noedit] +.br +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIComp\fR is used to create a new message to be mailed. It copies a +message form to the draft being composed and then invokes an editor on +the draft (unless `\-noedit' is given, in which case the initial edit +is suppressed). + +The default message form contains the following elements: + +.nf +.in +.5i +.ne 10 +.eo +.so %etcdir%/components +.ec +.in -.5i +.fi + +If a file named \*(lqcomponents\*(rq exists in the user's nmh directory, +it will be used instead of this form. You may specify an alternate +forms file with the switch `\-form\ formfile'. + +You may also start \fIcomp\fR using the contents of an existing message +as the form. If you supply either a `+folder' or `msg' argument, that +message will be used as the message form. You may not supply both a +`\-form\ formfile' and a `+folder' or \&`msg' argument. The line of +dashes or a blank line must be left between the header and the body of +the message for the message to be identified properly when it is sent +(see \fIsend\fR(1)). + +The switch `\-use' directs \fIcomp\fR to continue editing an already +started message. That is, if a \fIcomp\fR (or \fIdist\fR, \fIrepl\fR, +or \fIforw\fR\0) is terminated without sending the draft, the draft can +be edited again via \*(lqcomp\ \-use\*(rq. + +The `\-file\ file' switch says to use the named file as the message draft. + +If the draft already exists, \fIcomp\fR will ask you as to the disposition +of the draft. A reply of \fBquit\fR will abort \fIcomp\fR, leaving +the draft intact; \fBreplace\fR will replace the existing draft with +the appropriate form; \fBlist\fR will display the draft; \fBuse\fR will +use the draft for further composition; and \fBrefile\ +folder\fR will +file the draft in the given folder, and give you a new draft with the +appropriate form. (The `+folder' argument to \fBrefile\fR is required.) + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +The `\-editor\ editor' switch indicates the editor to use for the +initial edit. Upon exiting from the editor, \fIcomp\fR will invoke +the \fIwhatnow\fR program. See \fIwhatnow\fR\0(1) for a discussion of +available options. The invocation of this program can be inhibited +by using the `\-nowhatnowproc' switch. (In truth of fact, it is +the \fIwhatnow\fR program which starts the initial edit. Hence, +`\-nowhatnowproc' will prevent any edit from occurring.) +.Fi +^%etcdir%/components~^The standard message skeleton +^or /components~^Rather than the standard skeleton +^$HOME/\&.mh\(ruprofile~^The user profile +^/draft~^The draft file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Editor:~^To override the default editor +.Ps +^Msg\-Protect:~^To set mode when creating a new message (draft) +.Ps +^fileproc:~^Program to refile the message +.Ps +^whatnowproc:~^Program to ask the \*(lqWhat now?\*(rq questions +.Sa +dist(1), forw(1), repl(1), send(1), whatnow(1), mh-profile(5) +.De +`+folder' defaults to the current folder +.Ds +`msg' defaults to the current message +.Ds +`\-nodraftfolder' +.Ds +`\-nouse' +.Co +None +.Bu +If \fIwhatnowproc\fR is \fIwhatnow\fR, then \fIcomp\fR uses a built\-in +\fIwhatnow\fR, it does not actually run the \fIwhatnow\fR program. +Hence, if you define your own \fIwhatnowproc\fR, don't call it +\fIwhatnow\fR since \fIcomp\fR won't run it. +.En diff --git a/man/conflict.man b/man/conflict.man new file mode 100644 index 0000000..01f7936 --- /dev/null +++ b/man/conflict.man @@ -0,0 +1,58 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH CONFLICT %manext8% MH.6.8 [%nmhversion%] +.SH NAME +conflict \- search for alias/password conflicts +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/conflict +\%[\-search\ directory] +.br +\%[\-mail\ name] +\%[aliasfiles...] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIConflict\fR is a program that checks to see if the interface between +\fInmh\fR and transport system is in good shape + +\fIConflict\fR also checks for maildrops in %mailspool% which do not +belong to a valid user. It assumes that no user name will start with +`.', and thus ignores files in %mailspool% which begin with `.'. It also +checks for entries in the \fIgroup\fR\0(5) file which do not belong +to a valid user, and for users who do not have a valid group number. +In addition duplicate users and groups are noted. + +If the `\-mail\ name' switch is used, then the results will be sent +to the specified \fIname\fR. Otherwise, the results are sent to the +standard output. + +The `\-search\ directory' switch can be used to search directories +other than %mailspool% and to report anomalies in those directories. +The `\-search\ directory' switch can appear more than one time in an +invocation to \fIconflict\fR. + +\fIConflict\fR should be run under \fIcron\fR\0(8), or whenever system +accounting takes place. +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +^/etc/passwd~^List of users +^/etc/group~^List of groups +^%bindir%/mhmail~^Program to send mail +^%mailspool%/~^Directory of mail drop +.Pr +None +.Sa +mh\-alias(5) +.De +`aliasfiles' defaults to %etcdir%/MailAliases +.Co +None +.En diff --git a/man/dist.man b/man/dist.man new file mode 100644 index 0000000..f0c16aa --- /dev/null +++ b/man/dist.man @@ -0,0 +1,150 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH DIST %manext1% MH.6.8 [%nmhversion%] +.SH NAME +dist \- redistribute a message to additional addresses +.SH SYNOPSIS +.in +.5i +.ti -.5i +dist +\%[+folder] \%[msg] +\%[\-annotate] \%[\-noannotate] +.br +\%[\-inplace] \%[\-noinplace] +\%[\-form\ formfile] +.br +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] +.br +\%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +.br +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIDist\fR is similar to \fIforw\fR. It prepares the specified message +for redistribution to addresses that (presumably) are not on the original +address list. + +The default message form contains the following elements: + +.nf +.in +.5i +.ne 10 +.eo +.so %etcdir%/distcomps +.ec +.in -.5i +.fi + +If a file named \*(lqdistcomps\*(rq exists in the user's nmh directory, it +will be used instead of this default form. You may specify an alternate +forms file with the switch `\-form\ formfile'. The form used will be +prepended to the message being resent. + +If the draft already exists, \fIdist\fR will ask you as to the disposition +of the draft. A reply of \fBquit\fR will abort \fIdist\fR, leaving the +draft intact; \fBreplace\fR will replace the existing draft with a blank +skeleton; and \fBlist\fR will display the draft. + +Only those addresses in \*(lqResent\-To:\*(rq, \*(lqResent\-cc:\*(rq, +and \*(lqResent\-Bcc:\*(rq will be sent. Also, a +\*(lqResent\-Fcc:\ folder\*(rq will be honored (see \fIsend\fR\0(1)). +Note that with \fIdist\fR, the draft should contain only +\*(lqResent\-xxx:\*(rq fields and no body. The headers and the body of +the original message are copied to the draft when the message is sent. +Use care in constructing the headers for the redistribution. + +If the `\-annotate' switch is given, the message being distributed will +be annotated with the lines: + + Resent:\ date + Resent:\ addrs + +where each address list contains as many lines as required. This +annotation will be done only if the message is sent directly from +\fIdist\fR. If the message is not sent immediately from \fIdist\fR, +\*(lqcomp \-use\*(rq may be used to re\-edit and send the constructed +message, but the annotations won't take place. Normally annotations are +done inplace in order to preserve any links to the message. You may use +the '\-noinplace' switch to change this. + +See \fIcomp\fR\0(1) for a description of the `\-editor' and `\-noedit' +switches. Note that while in the editor, the message being resent +is available through a link named \*(lq@\*(rq (assuming the default +\fIwhatnowproc\fR\0). In addition, the actual pathname of the message is +stored in the environment variable \fB$editalt\fR, and the pathname of +the folder containing the message is stored in the environment variable +\fB$mhfolder\fR. + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +Upon exiting from the editor, \fIdist\fR will invoke the \fIwhatnow\fR +program. See \fIwhatnow\fR\0(1) for a discussion of available +options. The invocation of this program can be inhibited by using the +`\-nowhatnowproc' switch. (In truth of fact, it is the \fIwhatnow\fR +program which starts the initial edit. Hence, `\-nowhatnowproc' will +prevent any edit from occurring.) +.Fi +^%etcdir%/distcomps~^The standard message skeleton +^or /distcomps~^Rather than the standard skeleton +^$HOME/\&.mh\(ruprofile~^The user profile +^/draft~^The draft file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Editor:~^To override the default editor +.Ps +^fileproc:~^Program to refile the message +.Ps +^whatnowproc:~^Program to ask the \*(lqWhat now?\*(rq questions +.Sa +comp(1), forw(1), repl(1), send(1), whatnow(1) +.De +`+folder' defaults to the current folder +.Ds +`msg' defaults to cur +.Ds +`\-noannotate' +.Ds +`\-nodraftfolder' +.Ds +`\-inplace' +.Co + +If a folder is given, it will become the current folder. The message +distributed will become the current message. +.Hi +\fIDist\fR originally used headers of the form \*(lqDistribute\-xxx:\*(rq +instead of \*(lqResent\-xxx:\*(rq. In order to conform with the ARPA +Internet standard, RFC\-822, the \*(lqResent\-xxx:\*(rq form is now used. +\fIDist\fR will recognize \*(lqDistribute\-xxx:\*(rq type headers and +automatically convert them to \*(lqResent\-xxx:\*(rq. +.Bu +\fIDist\fR does not \fIrigorously\fR check the message being distributed +for adherence to the transport standard, but \fIpost\fR called by +\fIsend\fR does. The \fIpost\fR program will balk (and rightly so) at +poorly formatted messages, and \fIdist\fR won't correct things for you. + +If \fIwhatnowproc\fR is \fIwhatnow\fR, then \fIdist\fR uses a built\-in +\fIwhatnow\fR, it does not actually run the \fIwhatnow\fR program. +Hence, if you define your own \fIwhatnowproc\fR, don't call it +\fIwhatnow\fR since \fIdist\fR won't run it. + +If your current working directory is not writable, the link named +\*(lq@\*(rq is not available. +.En diff --git a/man/dp.man b/man/dp.man new file mode 100644 index 0000000..f3fe6bf --- /dev/null +++ b/man/dp.man @@ -0,0 +1,72 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH DP %manext8% MH.6.8 [%nmhversion%] +.SH NAME +dp \- parse dates 822-style +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/dp +\%[\-form\ formatfile] +.br +\%[\-format\ string] +\%[\-width\ columns] +dates\ ... +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIDp\fR is a program that parses dates according to the ARPA Internet +standard. +It also understands many non\-standard formats, +such as those produced by TOPS\-20 sites and some UNIX sites using +\fIctime\fR(3). +It is useful for seeing how \fInmh\fR will interpret a date. + +The \fIdp\fR program treats each argument as a single date, +and prints the date out in the official 822\-format. +Hence, it is usually best to enclose each argument in double\-quotes for the +shell. + +To override the output format used by \fIdp\fR, +the `\-format\ string' or `\-format\ file' switches are used. +This permits individual fields of the address to be extracted with ease. +The string is simply a format string and the file is simply a format file. +See \fImh\-format\fR(5) for the details. + +Here is the default format string used by \fIdp\fR: + +.nf +.ti +.5i +%<(nodate{text})error: %{text}%|%(putstr(pretty{text}))%> +.fi + +which says that if an error was detected, print the error, a `:', +and the date in error. +Otherwise, output the 822\-proper format of the date. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +None +.Sa +ap(8) +.br +\fIStandard for the Format of ARPA Internet Text Messages\fR (RFC\-822) +.De +`\-format' default as described above +.Ds +`\-width' default to the width of the terminal +.Co +None +.Bu +The argument to the `\-format' switch must be interpreted as a single token +by the shell that invokes \fIdp\fR. +Therefore, +one must usually place the argument to this switch inside double\-quotes. +.En diff --git a/man/flist.man b/man/flist.man new file mode 100644 index 0000000..177ad1d --- /dev/null +++ b/man/flist.man @@ -0,0 +1,146 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH FLIST %manext1% MH.6.8 [%nmhversion%] +.SH NAME +flist, flists \- list the number of messages in given sequence(s) +.SH SYNOPSIS +.in +.5i +.ti -.5i +flist +\%[+folder1 [+folder2 ...]] +.br +\%[\-sequence\ name1 [\-sequence\ name2 ...]] +.br +\%[\-all] \%[\-noall] +\%[\-showzero] \%[\-noshowzero] +.br +\%[\-recurse] \%[\-norecurse] +\%[\-alpha] \%[\-noalpha] +.br +\%[\-fast] \%[\-nofast] +\%[\-version] +\%[\-help] + +.ti .5i +flists is equivalent to flist -all +.in -.5i +.SH DESCRIPTION +This program is used to search a list of folders and display the number +of messages in these folders that are in a given sequence or set of +sequences (for example the \*(lqunseen\*(rq sequence). This is especially +useful if you use some mechanism such as \fIslocal\fP or \fIprocmail\fP +(typically in conjunction with \fIrcvstore\fP) to pre-sort your mail into +different folders before you view it. + +By default, the command \fIflist\fR will search the current folder for +the given sequence or sequences (usually \*(lqunseen\*(rq). If (possibly +multiple) folders are specified on the command line with `+folder', then +all these folders are searched for the given sequence(s). \fIFlist\fR will +display for each folder searched, the number of messages in each of the +specified sequences, and the total number of messages. + +The option `\-sequence' is used to specify the name of a sequence in +which to search for. This option may be used multiple times to specify +multiple sequences. If this is not given, then the default is to search +for all the sequences specified by the "Unseen-Sequence" profile component. +For more details about sequences, read the mh\-sequence(5) man page. + +Typically, \fIflist\fR will produce a line for each sequence, for every +folder that is searched, even those which do not contain any messages in +the given sequence. Specifying `\-noshowzero' will cause \fIflist\fR to +print only those folder/sequence combinations such the folder has a non-zero +number of messages in the given specified sequence. + +If `\-recurse' is given, then for each folder that is search, \fIflist\fR +will also recursively descend into those folders to search subfolders +for the given sequence. + +If `\-fast' is given, only the names of the folders searched will +be displayed, and \fIflist\fR will suppress all other output. If this +option is used in conjunction with `\-noshowzero', then \fIflist\fR will +only print the names of those folders searched that contain messages in +in at least one of the specified sequences. + +.Uh "Multiple Folders" +If the option `\-all' is given (and no folders are specified with +`+folder'), then \fIflist\fR will search all the folders in the top +level of the users nmh directory. These folders are all preceded by +the read\-only folders, which occur as \*(lqatr\-cur\-\*(rq entries +in the user's \fInmh\fR context. + +An example of the output of `flist \-all' is: + +.nf +.if t .in +.5i +.ta \w'/rnd/phyl/Mail/EP 'u +\w'ddd 'u +\w'new out of 'u +\w'ddd 'u +/work/Mail has 5 in sequence unseen (private); out of 46 +inbox+ has 10 in sequence unseen ; out of 153 +junklist has 0 in sequence unseen ; out of 63 +postmaster has 1 in sequence unseen ; out of 3 +.re +.if t .in -.5i +.fi + +The \*(lq+\*(rq after inbox indicates that it is the current folder. + +The \*(lqprivate\*(rq flag indicates that the given sequence for +that folder is private. See the mh\-sequence(5) man page for details +about private sequences. + +If the option `\-all' and `+folder' are both specified, then \fIflist\fR +will search this folder, and all its first level subfolders for the +given sequence. You may specify multiple folders in this way. + +If \fIflist\fR is invoked by a name ending with \*(lqs\*(rq +(e.g., \fIflists\fR\0), then the switch `\-all' is assumed by +default. + +The sorting order for the listing is alphabetical (with '\-alpha'), +or in a priority order defined by the Flist-Order profile entry (with +'\-noalpha'). Each item in the Flist-Order is a folder name or a +folder name pattern that uses * to match zero or more characters. +Longer matching patterns have precedence over shorter matching patterns. +For example: + +.nf +Flist-Order: personal petproject mh* * admin *junk +.fi + +This order puts a few interesting folders first, such as those with mail +addressed to you personally, those about a pet project, and those about +mh-related things. It places uninteresting folders at the end, and it +puts everything else in the middle in alphabetical order. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^mh-sequences:~^File that contains public sequences +.Ps +^Unseen-Sequence:~^The name of the unseen message sequence +.Ps +^Flist-Order:~^To sort folders by priority +.Sa +folder(1), rcvstore(1), slocal(1), mh\-sequence(5) +.De +`-sequence' defaults to Unseen-Sequence profile entry +.Ds +`\-showzero' +.Ds +`\-noall' +.Ds +`\-norecurse' +.Ds +`\-noalpha' +.Ds +`\-nofast' +.Co +If `+folder' is given, it will become the current folder. +If multiple folders are given, the last one specified will +become the current folder. +.En diff --git a/man/fmtdump.man b/man/fmtdump.man new file mode 100644 index 0000000..0046377 --- /dev/null +++ b/man/fmtdump.man @@ -0,0 +1,42 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH FMTDUMP %manext8% MH.6.8 [%nmhversion%] +.SH NAME +fmtdump \- decode nmh format files +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/fmtdump +\%[\-form\ formatfile] +.br +\%[\-format\ string] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIFmtdump\fR is a program that parses an \fInmh\fP format file and +produces a pseudo-language listing of the how \fInmh\fP interprets +the file. This is useful when debugging a complicated format file. + +The `\-format\ string' and `\-form\ formatfile' switches may be +used to specify a format string or format file to read. The string +is simply a format string and the file is simply a format file. +See \fImh-format\fR\|(5) for the details. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^%etcdir%/scan.default~^The default format file +.Pr +^Path:~^To determine the user's nmh directory +.Sa +mh-format(5), mh-sequences(8) +.Co +None +.Bu +The output may not be useful unless you are familiar +with the internals of the mh-format subroutines. +.En diff --git a/man/folder.man b/man/folder.man new file mode 100644 index 0000000..33b918b --- /dev/null +++ b/man/folder.man @@ -0,0 +1,199 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH FOLDER %manext1% MH.6.8 [%nmhversion%] +.SH NAME +folder, folders \- set/list current folder/message +.SH SYNOPSIS +.in +.5i +.ti -.5i +folder +\%[+folder] \%[msg] +\%[\-all] \%[\-noall] +.br +\%[\-create] \%[\-nocreate] +\%[\-fast] \%[\-nofast] +.br +\%[\-header] \%[\-noheader] +\%[\-recurse] +\%[\-norecurse] +.br +\%[\-total] \%[\-nototal] +\%[\-list] \%[\-nolist] +.br +\%[\-push] \%[\-pop] +\%[\-pack] \%[\-nopack] +\%[\-print] +.br +\%[\-verbose] +\%[\-noverbose] +\%[\-version] +\%[\-help] + +.ti .5i +folders is equivalent to folder -all +.in -.5i +.SH DESCRIPTION + +Since the \fInmh\fR environment is the shell, it is easy to lose track +of the current folder from day to day. When \fIfolder\fR is given the +`\-print' switch (the default), \fIfolder\fR will list the current folder, +the number of messages in it, the range of the messages (low\-high), +and the current message within the folder, and will flag extra files if +they exist. An example of this summary is: + +.nf +.if t .in +.5i +.ta \w'/rnd/phyl/Mail/EP 'u +\w'has ddd messages 'u +\w'(ddd\-ddd); 'u +inbox+ has \016 messages (\0\03\-\022); cur=\0\05. +.re +.if t .in -.5i +.fi + +If a `+folder' and/or `msg' are specified, they will become the current +folder and/or message. By comparison, when a `+folder' argument is given, +this corresponds to a \*(lqcd\*(rq operation in the \fIshell\fR; when no +`+folder' argument is given, this corresponds roughly to a \*(lqpwd\*(rq +operation in the \fIshell\fR. + +If the specified (or default) folder doesn't exist, the default action +is to query the user as to whether the folder should be created; when +standard input is not a tty, the answer to the query is assumed to be +\*(lqyes\*(rq. + +Specifying `\-create' will cause \fIfolder\fP to create new folders +without any query. (This is the easy way to create an empty folder for +use later.) Specifying `\-nocreate' will cause \fIfolder\fP to exit +without creating a non-existant folder. +.\" +.\" note - this doesn't work at present +.\" If `\-noprint' is specified, +.\" a `+folder' and/or `msg' may still be specified +.\" to set the current folder and/or message, +.\" but the folder summary will not be printed. +.Uh "Multiple Folders" +Specifying `\-all' will produce a summary line for each top-level folder +in the user's nmh directory, sorted alphabetically. (If \fIfolder\fR +is invoked by a name ending with \*(lqs\*(rq (e.g., \fIfolders\fR\0), +`\-all' is assumed). Specifying `\-recurse' with `\-all' will also +produce a line for all sub-folders. These folders are all preceded by +the read\-only folders, which occur as \*(lqatr\-cur\-\*(rq entries in +the user's \fInmh\fR context. For example, +.ne 9 +.nf +.if t .in +.5i +.ta \w'/rnd/phyl/Mail/EP 'u +\w'has ddd messages 'u +\w'(ddd\-ddd); 'u +FOLDER \0\0\0\0\0\0# MESSAGES RANGE CUR (OTHERS) +/var/work/folder has \035 messages (\01\-\035); cur=23. +/usr/bugs/Mail has \082 messages (\01\-108); cur=82. +ff has \0no messages. +inbox+ has \016 messages (\03\-\022); cur=\05. +mh has \076 messages (15\-\076); cur=70. +notes has \0\02 messages (\01\-\0\02); cur=\01. +ucom has 124 messages (\01\-124); cur=\06; (others). +.ta \w'/rnd/phyl/Mail/EP has 'u + +TOTAL = 339 messages in 7 folders +.re +.if t .in -.5i +.fi + +The \*(lq+\*(rq after inbox indicates that it is the current folder. +The \*(lq(others)\*(rq indicates that the folder `ucom' has files which +aren't messages. These files may either be sub\-folders, or files that +don't belong under the nmh file naming scheme. + +The header is output if either a `\-all' or a `\-header' switch is +specified. It is suppressed by `\-noheader'. + +The folder and message totals are output if either a `\-all' or a +`\-total' switch is specified. It is suppressed by `\-nototal'. + +If `\-fast' is given, only the folder name (or names in the case of +`\-all') will be listed. (This is faster because the folders need not +be read.) + +If a `+folder' is given along with the `\-all' switch, \fIfolder\fR will, +in addition to setting the current folder, list the top\-level subfolders +for the current folder (with `\-norecurse') or list all sub-folders under +the current folder recursively (with `\-recurse'). In this case, if a +`msg' is also supplied, it will become the current message of `+folder'. + +The `\-recurse' switch lists each folder recursively, so use of this +option effectively defeats the speed enhancement of the `\-fast' option, +since each folder must be searched for subfolders. Nevertheless, the +combination of these options is useful. + +.Uh "Compacting a Folder" +The `\-pack' switch will compress the message names in the designated +folders, removing holes in message numbering. The `\-verbose' switch +directs \fIfolder\fR to tell the user the general actions that it is +taking to compress the folder. + +.Uh "The Folder Stack" +The `\-push' switch directs \fIfolder\fR to push the current folder +onto the \fIfolder\-stack\fR, and make the `+folder' argument the +current folder. If `+folder' is not given, the current folder and the +top of the \fIfolder\-stack\fR are exchanged. This corresponds to the +\*(lqpushd\*(rq operation in the \fICShell\fR. + +The `\-pop' switch directs \fIfolder\fR to discard the top of the +\fIfolder\-stack\fR, after setting the current folder to that value. +No `+folder' argument is allowed. This corresponds to the \*(lqpopd\*(rq +operation in the \fICShell\fR. The `\-push' switch and the `\-pop' switch +are mutually exclusive: the last occurrence of either one overrides +any previous occurrence of the other. Both of these switches also set +`\-list' by default. + +The `\-list' switch directs \fIfolder\fR to list the contents of +the \fIfolder\-stack\fR. No `+folder' argument is allowed. After a +successful `\-push' or `\-pop', the `\-list' action is taken, unless a +`\-nolist' switch follows them on the command line. This corresponds +to the \*(lqdirs\*(rq operation in the \fICShell\fR. The `\-push', +`\-pop', and `\-list' switches turn off `\-print'. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Folder\-Protect:~^To set mode when creating a new folder +.Ps +^Folder\-Stack:~^To determine the folder stack +.\" .Ps +.\" ^lsproc:~^Program to list the contents of a folder +.Sa +refile(1), mhpath(1) +.De +`+folder' defaults to the current folder +.Ds +`msg' defaults to none +.Ds +`\-nofast' +.Ds +`\-noheader' +.Ds +`\-nototal' +.Ds +`\-nopack' +.Ds +`\-norecurse' +.Ds +`\-noverbose' +.Ds +`\-print' is the default if no `\-list', `\-push', or `\-pop' is specified +.Ds +`\-list' is the default if `\-push', or `\-pop' is specified +.Co +If `+folder' and/or `msg' are given, they will become the +current folder and/or message. +.Bu +There is no way to restore the default behavior +(to ask the user whether to create a non-existant folder) +after `\-create' or `\-nocreate' is given. +.En diff --git a/man/forw.man b/man/forw.man new file mode 100644 index 0000000..7d95613 --- /dev/null +++ b/man/forw.man @@ -0,0 +1,240 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH FORW %manext1% MH.6.8 [%nmhversion%] +.SH NAME +forw \- forward messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +forw +\%[+folder] \%[msgs] +.br +\%[\-annotate] \%[\-noannotate] +\%[\-form\ formfile] +.br +\%[\-format] \%[\-noformat] +\%[\-filter\ filterfile] +.br +\%[\-inplace] \%[\-noinplace] +\%[\-mime] \%[\-nomime] +.br +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] +.br +\%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +.br +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +.br +\%[\-dashstuffing] \%[\-nodashstuffing] +\%[\-version] +\%[\-help] + +.ti .5i +forw +\%[+folder] \%[msgs] +\%[\-digest\ list] \%[\-issue\ number] +.br +\%[\-volume\ number] +\%[other\ switches\ for\ \fIforw\fR] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIForw\fR may be used to prepare a message containing other messages. + +It constructs the new message from a forms (components) file, with a +body composed of the message(s) to be forwarded. An editor is invoked +as in \fIcomp\fR, and after editing is complete, the user is prompted +before the message is sent. + +The default message form contains the following elements: + +.nf +.in +.5i +.ne 10 +.eo +.so %etcdir%/forwcomps +.ec +.in -.5i +.fi + +If a file named \*(lqforwcomps\*(rq exists in the user's nmh directory, +it will be used instead of this default form. You may also specify an +alternate forms file with the switch `\-form\ formfile'. + +When If the draft already exists, \fIforw\fR will ask you as to the disposition +of the draft. A reply of \fBquit\fR will abort \fIforw\fR, leaving the +draft intact; \fBreplace\fR will replace the existing draft with a blank +skeleton; and \fBlist\fR will display the draft. + +If the `\-annotate' switch is given, each message being forwarded will +be annotated with the lines + + Forwarded:\ date + Forwarded:\ addrs + +where each address list contains as many lines as required. This +annotation will be done only if the message is sent directly from +\fIforw\fR. If the message is not sent immediately from \fIforw\fR, +\*(lqcomp\ \-use\*(rq may be used to re\-edit and send the constructed +message, but the annotations won't take place. Normally annotations +are done inplace in order to preserve any links to the message. You may +change this by using the '\-noinplace' switch. + +See \fIcomp\fR\0(1) for a description of the `\-editor' and `\-noedit' +switches. + +Although \fIforw\fR uses a forms (components) file to direct it how to +construct the beginning of the draft, it uses a message filter file to +direct it as to how each forwarded message should be formatted in the +body of the draft. The filter file for \fIforw\fR should be a standard +form file for \fImhl\fR, as \fIforw\fR will invoke \fImhl\fR to filter +(re\-format) the forwarded messages prior to being output to the body +of the draft. + +The switches `\-noformat', `\-format', and `\-filter\ filterfile' specify +which message filter file to use. + +If `\-noformat' is specified (this is the default), then each forwarded +message is output into the draft exactly as it appears with no \fImhl\fR +filtering. + +If `\-format' is specified, then a default message filter file is used. +This default message filter should be adequate for most users. +This default filter \*(lqmhl.forward\*(rq is: + +.nf +.in +.5i +.ne 10 +.eo +.so %etcdir%/mhl.forward +.ec +.in -.5i +.fi + +If a file named \*(lqmhl.forward\*(rq exists in the user's nmh +directory, it will be used instead of this form. You may specify an +alternate message filter file with the switch `\-filter\ filterfile'. + +Each forwarded message is separated with an encapsulation delimiter. +By default, any dashes in the first column of the forwarded messages +will be prepended with `\-\ ' so that when received, the message is +suitable for bursting by \fIburst\fR\0(1). This follows the Internet +RFC\-934 guidelines. You may use the flag `\-nodashstuffing' in order +to suppress this form of quoting to the forwarded messages. + +For users of \fIprompter\fR\0(1), by specifying prompter's `-prepend' +switch in the \&.mh\(ruprofile file, any commentary text is entered +before the forwarded messages. (A major win!) + +To use the MIME rules for encapsulation, specify the `\-mime' switch. +This directs \fIforw\fR to generate an \fImhbuild\fR composition file. +Note that nmh will not invoke \fImhbuild\fR automatically, unless you +add this line to your \&.mh\(ruprofile file: +.sp +.in +.5i +automimeproc: 1 +.in -.5i +.sp +Otherwise, +you must specifically give the command +.sp +.in +.5i +What now? mime +.in -.5i +.sp +prior to sending the draft. + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +Upon exiting from the editor, \fIforw\fR will invoke the \fIwhatnow\fR +program. See \fIwhatnow\fR\0(1) for a discussion of available +options. The invocation of this program can be inhibited by using the +`\-nowhatnowproc' switch. (In truth of fact, it is the \fIwhatnow\fR +program which starts the initial edit. Hence, `\-nowhatnowproc' will +prevent any edit from occurring.) + +The `\-digest\ list', `\-issue\ number', and `\-volume\ number' switches +implement a digest facility for \fInmh\fR. Specifying these switches +enables and/or overloads the following escapes: + +.sp 1 +.nf +.ta \w'Component 'u +\w'Escape 'u +\w'Returns 'u +\fIType\fR \fIEscape\fR \fIReturns\fR \fIDescription\fR +\fIcomponent\fR \fIdigest\fR string Argument to `\-digest' +\fIfunction\fR \fIcur\fR integer Argument to `\-volume' +\fIfunction\fR \fImsg\fR integer Argument to `\-issue' +.re +.fi + +Consult the \fBAdvanced Features\fR section of +the \fInmh\fR User's Manual for more information on making digests. +.Fi +^%etcdir%/forwcomps~^The standard message skeleton +^or /forwcomps~^Rather than the standard skeleton +^%etcdir%/digestcomps~^The message skeleton if `\-digest' is given +^or /digestcomps~^Rather than the standard skeleton +^%etcdir%/mhl.forward~^The standard message filter +^or /mhl.forward~^Rather than the standard filter +^$HOME/\&.mh\(ruprofile~^The user profile +^/draft~^The draft file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Editor:~^To override the default editor +.Ps +^Msg\-Protect:~^To set mode when creating a new message (draft) +.Ps +^fileproc:~^Program to refile the message +.Ps +^mhlproc:~^Program to filter messages being forwarded +.Ps +^whatnowproc:~^Program to ask the \*(lqWhat now?\*(rq questions +.Sa +\fIProposed Standard for Message Encapsulation\fR (RFC\-934), +.br +mhbuild(1), comp(1), repl(1), send(1), whatnow(1), mh\-format(5) +.De +`+folder' defaults to the current folder +`msgs' defaults to cur +.Ds +`\-noannotate' +.Ds +`\-nodraftfolder' +.Ds +`\-noformat' +.Ds +`\-inplace' +.Ds +`\-dashstuffing' +.Ds +`\-nomime' +.Co +If a folder is given, it will become the current folder. +The first message forwarded will become the current message. +.Bu + +If \fIwhatnowproc\fR is \fIwhatnow\fR, then \fIforw\fR uses a built\-in +\fIwhatnow\fR, it does not actually run the \fIwhatnow\fR program. +Hence, if you define your own \fIwhatnowproc\fR, don't call it +\fIwhatnow\fR since \fIforw\fR won't run it. + +When \fIforw\fR is told to annotate the messages it forwards, it +doesn't actually annotate them until the draft is successfully sent. +If from the \fIwhatnowproc\fR, you \fIpush\fR instead of \fIsend\fR, +it's possible to confuse \fIforw\fR by re\-ordering the file (e.g., +by using `folder\0\-pack') before the message is successfully sent. +\fIDist\fR and \fIrepl\fR don't have this problem. +.En diff --git a/man/inc.man b/man/inc.man new file mode 100644 index 0000000..b2e5c7b --- /dev/null +++ b/man/inc.man @@ -0,0 +1,194 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH INC %manext1% MH.6.8 [%nmhversion%] +.SH NAME +inc \- incorporate new mail +.SH SYNOPSIS +.in +.5i +.ti -.5i +inc +\%[+folder] +\%[\-audit\ audit\-file] \%[\-noaudit] +\%[\-changecur] +.br +\%[\-nochangecur] +\%[\-form\ formatfile] +\%[\-format\ string] +.br +\%[\-file\ name] +\%[\-silent] \%[\-nosilent] +\%[\-truncate] +.br +\%[\-notruncate] +\%[\-width\ columns] +%nmhbeginpop% +\%[\-host\ hostname] +.br +\%[\-user\ username] +\%[\-pack\ file] +\%[\-nopack] +.br +%nmhendpop% +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIInc\fR incorporates mail from the user's incoming mail drop into +an \fInmh\fR folder. + +You may specify which folder to use with `+folder'. If no folder +is specified, then \fIinc\fR will use either the folder given by a +(non\-empty) \*(lqInbox:\*(rq entry in the user's profile, or the folder +named \*(lqinbox\*(rq. If the specified (or default) folder doesn't +exist, the user will be queried prior to its creation. + +When the new messages are incorporated into the folder, they are assigned +numbers starting with the next highest number for the folder. As the +messages are processed, a \fIscan\fR listing of the new mail is produced. + +If the user's profile contains a \*(lqMsg\-Protect: nnn\*(rq entry, it +will be used as the protection on the newly created messages, otherwise +the \fInmh\fR default of 0644 will be used. For all subsequent operations +on these messages, this initially assigned protection will be preserved. + +If the switch `\-audit\ audit\-file' is specified (usually as a default +switch in the profile), then \fIinc\fR will append a header line and a +line per message to the end of the specified audit\-file with the format: + +.nf +.ti 1i +\*(<> date +.ti 1.5i + +.ti 1.5i + +.ti 2.5i + +.fi + +This is useful for keeping track of volume and source of incoming mail. +Eventually, \fIrepl\fR, \fIforw\fR, \fIcomp\fR, and \fIdist\fR +may also produce audits to this (or another) file, perhaps with +\*(lqMessage\-Id:\*(rq information to keep an exact correspondence +history. \*(lqAudit\-file\*(rq will be in the user's nmh directory unless +a full path is specified. + +\fIInc\fR will incorporate even improperly formatted messages into the +user's nmh folder, inserting a blank line prior to the offending component +and printing a comment identifying the bad message. + +In all cases, the user's mail drop will be zeroed, unless the +`\-notruncate' switch is given. + +If the profile entry \*(lqUnseen\-Sequence\*(rq is present and non\-empty, +then \fIinc\fR will add each of the newly incorporated messages to +each sequence named by the profile entry. \fIInc\fR will not zero each +sequence prior to adding messages. + +The interpretation of the `\-form\ formatfile', `\-format\ string', and +`\-width\ columns' switches is the same as in \fIscan\fR\0(1). + +By using the `\-file\ name' switch, one can direct \fIinc\fR to +incorporate messages from a file other than the user's maildrop. +Note that the name file will NOT be zeroed, unless the `\-truncate' +switch is given. + +If the environment variable \fB$MAILDROP\fR is set, then \fIinc\fR +uses it as the location of the user's maildrop instead of the default +(the `-file\ name' switch still overrides this, however). If this +environment variable is not set, then \fIinc\fR will consult the profile +entry \*(lqMailDrop\*(rq for this information. If the value found is +not absolute, then it is interpreted relative to the user's \fInmh\fR +directory. If the value is not found, then \fIinc\fR will look in the +standard system location for the user's maildrop. + +The `\-silent' switch directs \fIinc\fR to be quiet and not ask any +questions at all. This is useful for putting \fIinc\fR in the background +and going on to other things. +%nmhbeginpop% + +.Uh "Using POP" +\fIinc\fR will normally check local mail drops for mail, as given +above. But if the option \*(lqpophost:\*(rq is set in the mts +configuration file \*(lqmts.conf\*(rq, or if the `\-host\ hostname' +switch is given, then \fIinc\fR will query this POP service host +for mail to incorporate. + +The default is for \fIinc\fR to assume that your account name on +the POP server is the same as your current username. To specify +a different username, use the `\-user\ username' switch. + +When using POP, you will normally need to type the password for +your account on the POP server, in order to retrieve your messages. +It is possible to automate this process by creating a \*(lq.netrc\*(rq +file containing your login account information for this POP server. +For each POP server, this file should have a line of the following +form. Replace the words mypopserver, mylogin, and mypassword with +your own account information. + +machine mypopserver login mylogin password mypassword + +This \*(lq.netrc\*(rq file should be owned and readable only by +you. + +If \fIinc\fR uses POP, then the `\-pack\ file' switch is considered. +If given, then \fIinc\fR simply uses the POP to \fIpackf\fR\0(1) the +user's maildrop from the POP service host to the named file. This switch +is provided for those users who prefer to use \fImsh\fR to read their +maildrops. + +For debugging purposes, you may give the switch `\-snoop', which will +allow you to watch the POP transaction take place between you and the +POP server. +%nmhendpop% +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^%etcdir%/mts.conf~^nmh mts configuration file +^%mailspool%/$USER~^Location of mail drop +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Alternate\-Mailboxes:~^To determine the user's mailboxes +.Ps +^Inbox:~^To determine the inbox, default \*(lqinbox\*(rq +.Ps +^Folder\-Protect:~^To set mode when creating a new folder +.Ps +^Msg\-Protect:~^To set mode when creating a new message and audit\-file +.Ps +^Unseen\-Sequence:~^To name sequences denoting unseen messages +.Sa +mhmail(1), scan(1), mh\-mail(5), post(8) +.De +`+folder' defaulted by \*(lqInbox\*(rq above +.Ds +`\-noaudit' +.Ds +`\-changecur' +.Ds +`\-format' defaulted as described above +.Ds +`\-nosilent' +.Ds +`\-truncate' if `\-file\ name' not given, `\-notruncate' otherwise +.Ds +`\-width' defaulted to the width of the terminal +%nmhbeginpop% +.Ds +`\-nopack' +%nmhendpop% +.Co +The folder into which messages are being incorporated will become the +current folder. The first message incorporated will become the current +message, unless the `\-nochangecur' option is specified. This leaves +the context ready for a \fIshow\fR of the first new message. +.Bu +The argument to the `\-format' switch must be interpreted as a single +token by the shell that invokes \fIinc\fR. Therefore, one must usually +place the argument to this switch inside double\-quotes. +.En diff --git a/man/install-mh.man b/man/install-mh.man new file mode 100644 index 0000000..c00f678 --- /dev/null +++ b/man/install-mh.man @@ -0,0 +1,49 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH INSTALL-MH %manext8% MH.6.8 [%nmhversion%] +.SH NAME +install-mh \- initialize the nmh environment +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/install\-mh +\%[\-auto] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIInstall\-mh\fR is the \fInmh\fR program to create the initial setup +for a first\-time \fInmh\fR user. It is typically invoked by other +\fInmh\fR commands, and should NOT be directly invoked by the user. + +When a user runs any \fInmh\fR program for the first time, the program +will invoke \fIinstall\-mh\fR (with the `\-auto' switch) to query +the user for the initial \fInmh\fR environment. The user is asked +for the name of the directory that will be designated as the user's +\fInmh\fR directory. If this directory does not exist, the user is +asked if it should be created. Normally, this directory should be +under the user's home directory, and has the default name of Mail/. +After \fIinstall\-mh\fR has written the initial \&.mh\(ruprofile for +the user, control returns to the original \fInmh\fR program. + +As with all \fInmh\fR commands, \fIinstall\-mh\fR first consults the +\fB$HOME\fR environment variable to determine the user's home directory. +If \fB$HOME\fR is not set, then the \fI/etc/passwd\fR file is consulted. + +When creating the users initial \&.mh\(ruprofile, \fIinstall\-mh\fR will +check for the existence of a global profile %etcdir%/mh.profile. If +found, this will be used to initialize the new \&.mh\(ruprofile. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^%etcdir%/mh.profile~^Used to initialize user profile +.Pr +^Path:~^To set the user's nmh directory +.Co +With `\-auto', the current folder is changed to \*(lqinbox\*(rq. +.En diff --git a/man/mark.man b/man/mark.man new file mode 100644 index 0000000..426ea29 --- /dev/null +++ b/man/mark.man @@ -0,0 +1,128 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MARK %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mark \- manipulate message sequences +.SH SYNOPSIS +.in +.5i +.ti -.5i +mark +\%[+folder] \%[msgs] +\%[\-sequence\ name\ ...] +\%[\-add] +.br +\%[\-delete] \%[\-list] +\%[\-public] \%[\-nopublic] +\%[\-zero] +.br +\%[\-nozero] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +The \fImark\fR command manipulates message sequences by adding or deleting +message numbers from folder\-specific message sequences, or by listing +those sequences and messages. + +A message sequence is a keyword, just like one of the \*(lqreserved\*(rq +message names, such as \*(lqfirst\*(rq or \*(lqnext\*(rq. Unlike the +\*(lqreserved\*(rq message names, which have a fixed semantics on +a per\-folder basis, the semantics of a message sequence may be +defined, modified, and removed by the user. Message sequences are +folder\-specific, e.g., the sequence name \*(lqseen\*(rq in the context +of folder \*(lq+inbox\*(rq need not have any relation whatsoever to the +sequence of the same name in a folder of a different name. + +Three action switches direct the operation of \fImark\fR. These switches +are mutually exclusive: the last occurrence of any of them overrides +any previous occurrence of the other two. + +The `\-add' switch tells \fImark\fR to add messages to sequences or to +create a new sequence. For each sequence named via the `\-sequence\ name' +argument (which must occur at least once) the messages named via `msgs' +(which defaults to \*(lqcur\*(rq if no `msgs' are given), are added to the +sequence. The messages to be added need not be absent from the sequence. +If the `\-zero' switch is specified, the sequence will be emptied prior +to adding the messages. Hence, `\-add\ \-zero' means that each sequence +should be initialized to the indicated messages, while `\-add\ \-nozero' +means that each sequence should be appended to by the indicated messages. + +The `\-delete' switch tells \fImark\fR to delete messages from sequences, +and is the dual of `\-add'. For each of the named sequences, the +named messages are removed from the sequence. These messages need +not be already present in the sequence. If the `\-zero' switch is +specified, then all messages in the folder are added to the sequence +(first creating the sequence, if necessary) before removing the messages. +Hence, `\-delete\ \-zero' means that each sequence should contain +all messages except those indicated, while `\-delete\ \-nozero' means +that only the indicated messages should be removed from each sequence. +As expected, the command `mark\0\-sequence\0foo\0\-delete\0all' deletes +the sequence \*(lqfoo\*(rq from the current folder. + +When creating or modifying sequences, you can specify the switches +`\-public' or `\-nopublic' to force the new or modified sequences to be +\*(lqpublic\*(rq or \*(lqprivate\*(rq. The switch `\-public' indicates +that the sequences should be made \*(lqpublic\*(rq. These sequences +will then be readable by all \fInmh\fR users with permission to read the +relevant folders. In contrast, the `\-nopublic' switch indicates that the +sequences should be made \*(lqprivate\*(rq, and will only be accessible by +you. If neither of these switches is specified, then existing sequences +will maintain their current status, and new sequences will default to +\*(lqpublic\*(rq if you have write permission for the relevant folder. +Check the mh\-sequence(5) man page for more details about the difference +between \*(lqpublic\*(rq and \*(lqprivate\*(rq sequences. + +The `\-list' switch tells \fImark\fR to list both the sequences defined +for the folder and the messages associated with those sequences. +\fIMark\fR will list the name of each sequence given by +`\-sequence\ name' and the messages associated with that sequence. If the +sequence is private, this will also be indicated. If no sequence is +specified by the `\-sequence' switch, then all sequences for this folder +will be listed. The `\-zero' switch does not affect the operation of +`\-list'. + +The current restrictions on sequences are: + +.in +.25i +The name used to denote a message sequence must consist of an alphabetic +character followed by zero or more alphanumeric characters, and cannot +be one of the (reserved) message names \*(lqnew\*(rq, \*(lqfirst\*(rq, +\*(lqlast\*(rq, \*(lqall\*(rq, \*(lqnext\*(rq, or \*(lqprev\*(rq. + +Only a certain number of sequences may be defined for a given folder. +This number is usually limited to 26 (10 on small systems). + +Message ranges with user\-defined sequence names are restricted to the +form \*(lqname:n\*(rq, \*(lqname:+n\*(rq, or \*(lqname:-n\*(rq, and refer +to the first or last `n' messages of the sequence `name', respectively. +Constructs of the form \*(lqname1\-name2\*(rq are forbidden for user +defined sequences. +.in -.25i +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +flist(1), pick(1), mh-sequence(5) +.De +`+folder' defaults to the current folder +.Ds +`\-add' if `\-sequence' is specified, `\-list' otherwise +.Ds +`msgs' defaults to cur (or all if `\-list' is specified) +.Ds +`\-nozero' +.Co +If a folder is given, it will become the current folder. +.Hh +Use \*(lqflist\*(rq to find folders with a given sequence, and +\*(lqpick sequence \-list\*(rq to enumerate those messages in +the sequence (such as for use by a shell script). +.En diff --git a/man/mh-alias.man b/man/mh-alias.man new file mode 100644 index 0000000..451ea63 --- /dev/null +++ b/man/mh-alias.man @@ -0,0 +1,198 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-ALIAS %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-alias \- alias file for nmh message system +.SH SYNOPSIS +.in +.5i +.ti -.5i +any \fInmh\fR command +.in -.5i +.SH DESCRIPTION +This describes both \fInmh\fR personal alias files and +the global alias file for nmh mail delivery, the file + + %etcdir%/MailAliases + +It does \fBnot\fR describe aliases files used by the message transport system. +Each line of the alias file has the format: + + alias : address\-group +.br +or +.br + alias ; address\-group +.br +or +.br + < alias\-file +.br +or +.br + ; comment +.br + +where: + + address\-group := address\-list +.br + | \*(lq<\*(rq file +.br + | \*(lq=\*(rq UNIX\-group +.br + | \*(lq+\*(rq UNIX\-group +.br + | \*(lq*\*(rq + +.br + address\-list := address +.br + | address\-list, address +.br + +Continuation lines in alias files end with `\\' followed by the newline +character. + +Alias\-file and file are UNIX file names. UNIX\-group is a group name +(or number) from \fI/etc/group\fR. An address is a \*(lqsimple\*(rq +Internet\-style address. Througout this file, case is ignored, except +for alias\-file names. + +If the line starts with a `<', then the file named after the `<' is +read for more alias definitions. The reading is done recursively, so a +`<' may occur in the beginning of an alias file with the expected results. + +If the address\-group starts with a `<', then the file named after the +`<' is read and its contents are added to the address\-list for the alias. + +If the address\-group starts with an `=', then the file \fI/etc/group\fR +is consulted for the UNIX\-group named after the `='. Each login name +occurring as a member of the group is added to the address\-list for +the alias. + +In contrast, if the address\-group starts with a `+', then the file +\fI/etc/group\fR is consulted to determine the group\-id of the +UNIX\-group named after the `+'. Each login name occurring in the +\fI/etc/passwd\fR file whose group\-id is indicated by this group is +added to the address\-list for the alias. + +If the address\-group is simply `*', then the file \fI/etc/passwd\fR is +consulted and all login names with a userid greater than some magic number +(usually 200) are added to the address\-list for the alias. + +In match, a trailing * on an alias will match just about anything +appropriate. (See example below.) + +An approximation of the way aliases are resolved at posting time is +(it's not really done this way): + +.in +.5i +1) Build a list of all addresses from the message to be delivered, +eliminating duplicate addresses. + +2) If this draft originated on the local host, then for those addresses in +the message that have no host specified, perform alias resolution. + +3) For each line in the alias file, compare \*(lqalias\*(rq against all of +the existing addresses. If a match, remove the matched \*(lqalias\*(rq +from the address list, and add each new address in the address\-group to +the address list if it is not already on the list. The alias itself is +not usually output, rather the address\-group that the alias maps to is +output instead. If \*(lqalias\*(rq is terminated with a `;' instead of +a `:', then both the \*(lqalias\*(rq and the address are output in the +correct format. (This makes replies possible since \fInmh\fR aliases +and personal aliases are unknown to the mail transport system.) +.in -.5i + +Since the alias file is read line by line, forward references work, but +backward references are not recognized, thus, there is no recursion. + +.ne 10 +\fBExample:\fR +.nf +.in +.5i +<%etcdir%/BBoardAliases +sgroup: fred, fear, freida +b-people: Blind List: bill, betty; +fred: frated@UCI +UNIX\-committee: \*(rq are defined to be \*(lqnews\*(rq. + +The key thing to understand about aliasing in \fInmh\fR is that aliases +in \fInmh\fR alias files are expanded into the headers of messages posted. +This aliasing occurs first, at posting time, without the knowledge of the +message transport system. In contrast, once the message transport system +is given a message to deliver to a list of addresses, for each address +that appears to be local, a system\-wide alias file is consulted. These +aliases are \fBNOT\fR expanded into the headers of messages delivered. +.Hh +To use aliasing in \fInmh\fR quickly, do the following: + +.in +.5i +First, in your \fI\&.mh\(ruprofile\fR, choose a name for your alias +file, say \*(lqaliases\*(rq, and add the line: + +.nf +.in +.5i +Aliasfile: aliases +.\" ali: \-alias aliases +.\" send: \-alias aliases +.\" whom: \-alias aliases +.in -.5i +.fi + +Second, create the file \*(lqaliases\*(rq in your \fInmh\fR directory. + +Third, start adding aliases to your \*(lqaliases\*(rq file as appropriate. +.in -.5i +.Fi +^%etcdir%/MailAliases~^global nmh alias file +.Pr +^Aliasfile:~^For a default alias file +.Sa +ali(1), send(1), whom(1), group(5), passwd(5), conflict(8), post(8) +.De +None +.Co +None +.Bu +Although the forward-referencing semantics of \fImh\-alias\fR files +prevent recursion, the \*(lq<\ alias\-file\*(rq command may defeat this. +Since the number of file descriptors is finite (and very limited), such +infinite recursion will terminate with a meaningless diagnostic when +all the fds are used up. +.sp +Forward references do not work correctly inside blind lists. +.En diff --git a/man/mh-chart.man b/man/mh-chart.man new file mode 100644 index 0000000..9f95ee8 --- /dev/null +++ b/man/mh-chart.man @@ -0,0 +1,585 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.if '\*(ZZ'-man' \{\ +.TH MH-CHART %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mh-chart \- Chart of nmh Commands +.SH SYNOPSIS +.in +.5i +.ti -.5i +.\} +.in 1i +.na +.ti .5i +.ne 7 +ali +\%[\-alias\ aliasfile] +\%[\-list] \%[\-nolist] +\%[\-normalize] \%[\-nonormalize] +\%[\-user] \%[\-nouser] +aliases\ ... +\%[\-version] +\%[\-help] + +.ti .5i +.ne 6 +anno +\%[+folder] \%[msgs] +\%[\-component\ field] +\%[\-inplace] \%[\-noinplace] +\%[\-date] \%[\-nodate] +\%[\-text\ body] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 6 +burst +\%[+folder] \%[msgs] +\%[\-inplace] \%[\-noinplace] +\%[\-quiet] \%[\-noquiet] +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 9 +comp +\%[+folder] \%[msg] +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +\%[\-file\ file] +\%[\-form\ formfile] +\%[\-use] \%[\-nouse] +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 9 +dist +\%[+folder] \%[msg] +\%[\-annotate] \%[\-noannotate] +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +\%[\-form\ formfile] +\%[\-inplace] \%[\-noinplace] +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 9 +flist +\%[+folder1 [+folder2 ...]] +\%[\-sequence\ name1 [\-sequence\ name2 ...]] +\%[\-all] \%[\-noall] +\%[\-showzero] \%[\-noshowzero] +\%[\-alpha] \%[\-noalpha] +\%[\-recurse] \%[\-norecurse] +\%[\-fast] \%[\-nofast] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 4 +%libdir%/fmtdump +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 12 +folder +\%[+folder] \%[msg] +\%[\-all] \%[\-noall] +\%[\-fast] \%[\-nofast] +\%[\-header] \%[\-noheader] +\%[\-pack] \%[\-nopack] +\%[\-recurse] \%[\-norecurse] +\%[\-total] \%[\-nototal] +\%[\-print] \%[\-noprint] +\%[\-list] \%[\-nolist] +\%[\-push] \%[\-pop] +\%[\-version] +\%[\-help] + +.ti .5i +folders + +.ti .5i +.ne 11 +forw +\%[+folder] \%[msgs] +\%[\-annotate] \%[\-noannotate] +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +\%[\-filter\ filterfile] +\%[\-form\ formfile] +\%[\-format] \%[\-noformat] +\%[\-inplace] \%[\-noinplace] +\%[\-mime] \%[\-nomime] +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +forw +\%[+folder] \%[msgs] +\%[\-digest\ list] \%[\-issue\ number] \%[\-volume\ number] +\%[other\ switches\ for\ \fIforw\fR] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 11 +inc +\%[+folder] +\%[\-audit\ audit\-file] \%[\-noaudit] +\%[\-changecur] \%[\-nochangecur] +\%[\-file\ name] +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-silent] \%[\-nosilent] +\%[\-truncate] \%[\-notruncate] +\%[\-width\ columns] +%nmhbeginpop% +\%[\-host\ hostname] +\%[\-user\ username] +\%[\-pack\ file] +\%[\-nopack] +%nmhendpop% +\%[\-version] +\%[\-help] + +.ti .5i +.ne 7 +mark +\%[+folder] \%[msgs] +\%[\-sequence\ name\ ...] +\%[\-add] \%[\-delete] \%[\-list] +\%[\-public] \%[\-nopublic] +\%[\-zero] \%[\-nozero] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 9 +mhbuild +file +\%[\-list] \%[-nolist] +\%[\-realsize] \%[\-norealsize] +\%[\-headers] \%[\-noheaders] +\%[\-ebcdicsafe] \%[\-noebcdicsafe] +\%[\-rfc934mode] \%[\-norfc934mode] +\%[\-verbose] \%[\-noverbose] +\%[\-check] \%[\-nocheck] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 9 +%libdir%/mhl +\%[\-bell] \%[\-nobell] +\%[\-clear] \%[\-noclear] +\%[\-folder\ +folder] +\%[\-form\ formfile] +\%[\-length\ lines] \%[\-width\ columns] +\%[\-moreproc\ program] \%[\-nomoreproc] +\%[files\ ...] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 8 +mhmail +\%[ +addrs\ ... +\%[\-body\ text] +\%[\-cc\ addrs\ ...] +\%[\-from\ addr] +\%[\-subject subject]] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 16 +mhn +\%[+folder] \%[msgs] \%[\-file file] +\%[\-part number]... \%[\-type content]... +\%[\-show] \%[\-noshow] +\%[\-list] \%[-nolist] +\%[\-store] \%[\-nostore] +\%[\-cache] \%[\-nocache] +\%[\-headers] \%[\-noheaders] +\%[\-realsize] \%[\-norealsize] +\%[\-serialonly] \%[\-noserialonly] +\%[\-form formfile] +\%[\-pause] \%[\-nopause] +\%[\-auto] \%[\-noauto] +\%[\-rcache policy] \%[\-wcache policy] +\%[\-check] \%[\-nocheck] +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +mhparam +\%[profile-components] +\%[\-components] \%[\-nocomponents] +\%[\-all] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 3 +mhpath +\%[+folder] \%[msgs] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +msgchk +\%[\-date] \%[\-nodate] +\%[\-notify\ all/mail/nomail] \%[\-nonotify\ all/mail/nomail] +%nmhbeginpop% +\%[\-host\ hostname] +\%[\-user\ username] +%nmhendpop% +\%[users\ ...] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +msh +\%[\-prompt\ string] +\%[\-scan] \%[\-noscan] +\%[\-topcur] \%[\-notopcur] +\%[file] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 6 +next +\%[+folder] +\%[\-showproc\ program] +\%[\-showmimeproc\ program] +.br +\%[\-header] \%[\-noheader] +\%[\-checkmime] \%[\-nocheckmime] +.br +\%[switches\ for\ \fIshowproc\fR or\ \fIshowmimeproc\fR] +.br +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +packf +\%[+folder] \%[msgs] +\%[-file\ name] +\%[-mbox] \%[-mmdf] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 10 +pick +\%[+folder] \%[msgs] +\%[\-and\ ...] \%[\-or\ ...] \%[\-not\ ...] \%[\-lbrace\ ...\ \-rbrace] +\%[\-\|\-component\ pattern] +\%[\-after\ date] \%[\-before\ date] \%[\-datefield\ field] +\%[\-sequence\ name\ ...] +\%[\-public] \%[\-nopublic] +\%[\-zero] \%[\-nozero] +\%[\-list] \%[\-nolist] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 6 +prev +\%[+folder] +\%[\-showproc\ program] +\%[\-showmimeproc\ program] +.br +\%[\-header] \%[\-noheader] +\%[\-checkmime] \%[\-nocheckmime] +.br +\%[\-switches\ for\ \fIshowproc\fR or\ \fIshowmimeproc\fR] +.br +\%[\-version] +\%[\-help] + +.ti .5i +.ne 7 +prompter +\%[\-erase\ chr] +\%[\-kill\ chr] +\%[\-prepend] \%[\-noprepend] +\%[\-rapid] \%[\-norapid] +\%[\-doteof] \%[\-nodoteof] +file +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +%libdir%/rcvdist +\%[\-form\ formfile] +\%[switches\ for\ \fIpostproc\fR] +address1\ ... +\%[\-version] +\%[\-help] + +.ti .5i +.ne 4 +%libdir%/rcvpack +file +\%[-mbox] \%[-mmdf] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 7 +%libdir%/rcvstore +\%[+folder] +\%[\-create] \%[\-nocreate] +\%[\-unseen] \%[\-nounseen] +\%[\-sequence\ name\ ...] +\%[\-public] \%[\-nopublic] +\%[\-zero] \%[\-nozero] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 10 +%libdir%/rcvtty +\%[command] +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-width\ columns] +\%[\-bell] \%[\-nobell] +\%[\-newline] +\%[\-nonewline] +\%[\-biff] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 9 +refile +\%[msgs] +\%[\-draft] +\%[\-link] \%[\-nolink] +\%[\-preserve] \%[\-nopreserve] +\%[\-unlink] \%[\-nounlink] +\%[\-src\ +folder] +\%[\-file\ file] ++folder ... +\%[\-version] +\%[\-help] + +.ti .5i +.ne 15 +repl +\%[+folder] \%[msg] +\%[\-group] \%[\-nogroup] +\%[\-annotate] \%[\-noannotate] +\%[\-cc\ all/to/cc/me] \%[\-nocc\ all/to/cc/me] +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +\%[\-fcc\ +folder] +\%[\-filter\ filterfile] +\%[\-form\ formfile] +\%[\-format] \%[\-noformat] +\%[\-inplace] \%[\-noinplace] +\%[\-query] \%[\-noquery] +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +\%[\-width\ columns] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 4 +rmf +\%[+folder] +\%[\-interactive] \%[\-nointeractive] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 3 +rmm +\%[+folder] \%[msgs] +\%[\-unlink] \%[\-nounlink] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 8 +scan +\%[+folder] \%[msgs] +\%[\-clear] \%[\-noclear] +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-header] \%[\-noheader] +\%[\-width\ columns] +\%[\-reverse] \%[\-noreverse] +\%[\-file filename] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 15 +send +\%[\-alias\ aliasfile] +\%[\-draft] +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[\-filter\ filterfile] \%[\-nofilter] +\%[\-format] \%[\-noformat] +\%[\-forward] \%[\-noforward] +\%[\-mime] \%[\-nomime] +\%[\-msgid] \%[\-nomsgid] +\%[\-push] \%[\-nopush] +\%[\-split\ seconds] +\%[\-verbose] \%[\-noverbose] +\%[\-watch] \%[\-nowatch] +\%[\-width\ columns] +\%[file\ ...] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 7 +show +\%[+folder] \%[msgs] +\%[\-showproc\ program] +.br +\%[\-showmimeproc\ program] +\%[\-header] \%[\-noheader] +.br +\%[\-draft] +\%[\-checkmime] \%[\-nocheckmime] +.br +\%[switches\ for\ \fIshowproc\fR or \fIshowmimeproc\fR] +.br +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +sortm +\%[+folder] \%[msgs] +\%[\-datefield\ field] +\%[\-textfield\ field] \%[\-notextfield] +\%[\-limit days] \%[\-nolimit] +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 6 +whatnow +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +\%[\-prompt\ string] +\%[file] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 7 +whom +\%[\-alias\ aliasfile] +\%[\-check] \%[\-nocheck] +\%[\-draft] +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] \%[\-nodraftfolder] +\%[file] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 7 +%libdir%/ap +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-normalize] \%[\-nonormalize] +\%[\-width\ columns] +addrs\ ... +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +%libdir%/conflict +\%[\-mail\ name] +\%[\-search\ directory] +\%[aliasfiles\ ...] +\%[\-version] +\%[\-help] + +.ti .5i +.ne 5 +%libdir%/dp +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-width\ columns] +dates\ ... +\%[\-version] +\%[\-help] + +.ti .5i +.ne 3 +%libdir%/install\-mh +\%[\-auto] + +.ti .5i +.ne 11 +%libdir%/post +\%[\-alias\ aliasfile] +\%[\-filter\ filterfile] \%[\-nofilter] +\%[\-format] \%[\-noformat] +\%[\-mime] \%[\-nomime] +\%[\-msgid] \%[\-nomsgid] +\%[\-verbose] \%[\-noverbose] +\%[\-watch] \%[\-nowatch] +\%[\-width\ columns] +file +\%[\-version] +\%[\-help] + +.ti .5i +.ne 10 +%libdir%/slocal \%[address\ info\ sender] +.br +\%[\-addr\ address] +\%[\-info\ data] +\%[\-sender\ sender] +.br +\%[\-user\ username] +\%[\-mailbox\ mbox] +\%[\-file\ file] +.br +\%[\-maildelivery\ deliveryfile] +\%[\-suppressdup] +.br +\%[\-nosuppressdup] +\%[\-verbose] \%[\-noverbose] +\%[\-debug] +.br +\%[\-version] +\%[\-help] +.ad +.in 0 diff --git a/man/mh-draft.man b/man/mh-draft.man new file mode 100644 index 0000000..0b71fd7 --- /dev/null +++ b/man/mh-draft.man @@ -0,0 +1,192 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-DRAFT %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-draft \- draft folder facility for nmh message system +.SH SYNOPSIS +.in +.5i +.ti -.5i +any \fInmh\fR command +.in -.5i +.SH DESCRIPTION + +There are a number of interesting advanced facilities for the composition of +outgoing mail. + +.Uh "The Draft Folder" +The \fIcomp\fR, \fIdist\fR, \fIforw\fR, and \fIrepl\fR commands have +two additional switches, `\-draftfolder\ +folder' and `\-draftmessage\ msg' +which allow you to manipulate the various draft messages you are composing. + +If `\-draftfolder\ +folder' is used, these commands are +directed to construct a draft message in the indicated folder. +(The \*(lqDraft\-Folder:\*(rq profile entry may be used to declare a +default draft folder for use with \fIcomp\fR, \fIdist\fR, \fIforw\fR, +and \fIrepl\fR). + +If the swith `\-draftmessage\ msg' is given, the specified draft is used +to compose the message. If `\-draftmessage\ msg' is not used, then the +draft defaults to `new' (create a new draft) unless the user invokes +\fIcomp\fR with `\-use', in which case the default is `cur'. + +Hence, the user may have several message compositions in progress +simultaneously. Now, all of the \fInmh\fR tools are available on each of +the user's message drafts (e.g., \fIshow\fR, \fIscan\fR, \fIpick\fR, and +so on). If the folder does not exist, the user is asked if it should be +created (just like with \fIrefile\fR). Also, the last draft message +the user was composing is known as `cur' in the draft folder. + +Furthermore, the \fIsend\fR command has these switches as well. Hence, +from the shell, the user can send off whatever drafts desired using the +standard \fInmh\fR `msgs' convention with `\-draftmessage msgs'. If no +`msgs' are given, it defaults to `cur'. + +In addition, all five programs have a `\-nodraftfolder' switch, which +undoes the last occurrence of `\-draftfolder\ folder' (useful if the +latter occurs in the user's \fInmh\fR profile). + +If the user does not give the `\-draftfolder\ +folder' switch, then +all these commands act ``normally''. Note that the `\-draft' switch +to \fIsend\fR and \fIshow\fR still refers to the file called `draft' +in the user's \fInmh\fR directory. In the interests of economy of +expression, when using \fIcomp\fR or \fIsend\fR, the user needn't +prefix the draft `msg' or `msgs' with `\-draftmessage'. Both of these +commands accept a `file' or `files' argument, and they will, if given +`\-draftfolder\ +folder' treat these arguments as `msg' or `msgs'. +(This may appear to be inconsistent, at first, but it saves a lot of +typing) Hence, + +.ti +.5i +send -draftf +drafts first + +is the same as + +.ti +.5i +send -draftf +drafts -draftm first + +To make all this a bit more clear, here are some examples. Let's assume +that the following entries are in the \fInmh\fR profile: + +.in +.5i +.nf +Draft\-Folder: +drafts +sendf: -draftfolder +drafts +.fi +.in -.5i + +Furthermore, let's assume that the program \fIsendf\fR is a (symbolic) +link in the user's \fB$HOME/bin/\fR directory to \fIsend\fR. Then, any +of the commands + +.in +.5i +.nf +comp +dist +forw +repl +.fi +.in -.5i + +constructs the message draft in the `draft' folder using the `new' +message number. Furthermore, they each define `cur' in this folder to +be that message draft. If the user were to use the \fIquit\fR option +at `What now?' level, then later on, if no other draft composition was +done, the draft could be sent with simply + +.ti +.5i +sendf + +Or, if more editing was required, the draft could be edited with + +.ti +.5i +comp -use + +Instead, if other drafts had been composed in the meantime, so that this +message draft was no longer known as `cur' in the `draft' folder, then +the user could \fIscan\fR the folder to see which message draft in the +folder should be used for editing or sending. Clever users could even +employ a back-quoted \fIpick\fR to do the work: + +.ti +.5i +comp -use `pick +drafts -to nmh-workers` + +or + +.ti +.5i +sendf `pick +drafts -to nmh-workers` + +Note that in the \fIcomp\fR example, the output from \fIpick\fR must +resolve to a single message draft (it makes no sense to talk about +composing two or more drafts with one invocation of \fIcomp\fR). +In contrast, in the \fIsend\fR example, as many message drafts as desired +can appear, since \fIsend\fR doesn't mind sending more than one draft +at a time. + +Note that the argument `\-draftfolder\ +folder' is not included in the +profile entry for \fIsend\fR, since when \fIcomp\fR, et. al., invoke +\fIsend\fR directly, they supply \fIsend\fR with the UNIX pathname +of the message draft, and \fBnot\fR a `draftmessage\ msg' argument. +As far as \fIsend\fR is concerned, a \fIdraft folder\fR is not being used. + +It is important to realize that \fInmh\fR treats the draft folder +like a standard \fInmh\fR folder in nearly all respects. There are +two exceptions: + +First, under no circumstancs will the `\-draftfolder\ folder' switch cause the +named folder to become the current folder. + +Obviously, if the folder appeared in the context of a standard `+folder' +argument to an \fInmh\fR program, as in + +.ti +.5i +scan +drafts + +it might become the current folder, depending on the context changes of +the \fInmh\fR program in question. + +Second, although conceptually \fIsend\fR deletes the `msgs' named in +the draft folder, it does not call `delete-prog' to perform the deletion. + +.Uh "What Happens if the Draft Exists" +When the \fIcomp\fR, \fIdist\fR, \fIforw\fR, and \fIrepl\fR commands +are invoked and the draft you indicated already exists, these programs +will prompt the user for a reponse directing the program's action. +The prompt is + +.ti +.5i +Draft ``/home/foobar/nmhbox/draft'' exists (xx bytes). +.ti +.5i +Disposition? + +The appropriate responses and their meanings are: + +.nf +^replace - deletes the draft and starts afresh +^list - lists the draft +^refile - files the draft into a folder and starts afresh +^quit - leaves the draft intact and exits +.fi + +In addition, if you specified `\-draftfolder\ folder' to the command, +then one other response will be accepted: + +.nf +^new - finds a new draft, +.fi + +just as if `\-draftmessage\ new' had been given. +Finally, the \fIcomp\fR command will accept one more response: + +.nf +^use - re-uses the draft +.fi + +just as if `\-use' had been given. +.Co +None +.En diff --git a/man/mh-format.man b/man/mh-format.man new file mode 100644 index 0000000..bc40c88 --- /dev/null +++ b/man/mh-format.man @@ -0,0 +1,482 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-FORMAT %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-format \- format file for nmh message system +.SH SYNOPSIS +.in +.5i +.ti -.5i +some \fInmh\fR commands +.in -.5i +.SH DESCRIPTION +Several \fInmh\fR commands utilize either a \fIformat\fR string or a +\fIformat\fR file during their execution. For example, \fIscan\fR\0(1) +uses a format string which directs it how to generate the scan listing +for each message; \fIrepl\fR\0(1) uses a format file which directs it +how to generate the reply to a message, and so on. + +Format strings are designed to be efficiently parsed by \fInmh\fR +which means they are not necessarily simple to write and understand. +This means that novice, casual, or even advanced users of \fInmh\fR +should not have to deal with them. + +There are a few alternate scan listing formats available +in %etcdir%/scan.time, %etcdir%/scan.size, and %etcdir%/scan.timely. +Look in %etcdir% for other \fIscan\fR and \fIrepl\fR format files which +may have been written at your site. + +It suffices to have your local \fInmh\fR expert actually write new format +commands or modify existing ones. This manual section explains how to +do that. Note: familiarity with the C \fIprintf\fR routine is assumed. + +A format string consists of ordinary text, and special multi-character +\fIescape\fR sequences which begin with `%'. When specifying a format +string, the usual C backslash characters are honored: `\\b', `\\f', +`\\n', `\\r', and `\\t'. Continuation lines in format files end with +`\\' followed by the newline character. + +.\" TALK ABOUT SYNTAX FIRST, THEN SEMANTICS +There are three types of \fIescape\fR sequences: header +\fIcomponents\fR, built-in \fIfunctions\fR, and flow \fIcontrol\fR. + +A \fIcomponent\fR escape is specified as `%{\fIcomponent\fR\^}', and +exists for each header found in the message being processed. For example +`%{date}' refers to the \*(lqDate:\*(rq field of the appropriate message. +All component escapes have a string value. Normally, component values are +compressed by converting any control characters (tab and newline included) +to spaces, then eliding any leading or multiple spaces. However, commands +may give different interpretations to some component escapes; be sure +to refer to each command's manual entry for complete details. + +A \fIfunction\fR escape is specified as `%(\fIfunction\fR\^)'. +All functions are built-in, and most have a string or numeric value. + +.ne 12 +.Uh "Control-flow escapes" +A \fIcontrol\fR escape is one of: `%<', `%?', `%|', or `%>'. +These are combined into the conditional execution construct: +.sp +.nf + % +.fi +.sp +Extra white space is shown here only for clarity. These +constructs may be nested without ambiguity. They form a general +\fBif\-elseif\-else\-endif\fP block where only one of the \fIformat +text\fP segments is interpreted. + +The `%<' and `%?' control escapes causes a condition to be evaluated. +This condition +may be either a \fIcomponent\fP or a \fIfunction\fP. +The four constructs have the following syntax: +.sp 1 +.nf + %<{component} + %<(function) + %?{component} + %?(function) +.fi +.sp +These control escapes test whether the function or component value is +non-zero (for integer-valued escapes), or non-empty (for string-valued +escapes). + +If this test evaulates true, then the format text up to the next +corresponding control escape (one of `%|', `%?', or `%>') is interpreted +normally. Next, all format text (if any) up to the corresponding `%>' +control escape is skipped. The `%>' control escape is not interpreted; +normal interpretation resumes after the `%>' escape. + +If the test evaluates false, however, then the format text up to +the next corresponding control escape (again, one of `%|', `%?', or +`%>') is skipped, instead of being interpreted. If the control escape +encountered was `%?', then the condition associated with that control +escape is evaluated, and interpretation proceeds after that test as +described in the previous paragraph. If the control escape encountered +was `%|', then the format text up to the corresponding `%>' escape is +interpreted normally. As above, the `%>' escape is not interpreted and +normal interpretation resumes after the `%>' escape. + +The `%?' control escape and its following format text is optional, and may +be included zero or more times. The `%|' control escape and its following +format text is also optional, and may be included zero or one times. + +.Uh "Function escapes" +.ne 10 +Most functions expect an argument of a particular type: +.sp 1 +.nf +.ta +\w'Argument 'u +\w'An optional component, 'u +\fIArgument\fR \fIDescription\fR \fIExample Syntax\fR +literal A literal number, %(\fIfunc\fR 1234) + or string %(\fIfunc\fR text string) +comp Any header component %(\fIfunc\fR\^{\fIin-reply-to\fR\^}) +date A date component %(\fIfunc\fR\^{\fIdate\fR\^}) +addr An address component %(\fIfunc\fR\^{\fIfrom\fR\^}) +expr An optional component, %(\fIfunc\fR\^(\fIfunc2\fR\^)) + function or control, %(\fIfunc\fR %<{\fIreply-to\fR\^}%|%{\fIfrom\fR\^}%>) + perhaps nested %(\fIfunc\fR\^(\fIfunc2\fR\^{\fIcomp\fR\^})) +.re +.fi + +The types \fIdate\fR and \fIaddr\fR have the same syntax as \fIcomp\fR, +but require that the header component be a date string, or address +string, respectively. + +All arguments except those of type \fIexpr\fR are required. For the +\fIexpr\fR argument type, the leading `%' must be omitted for component +and function escape arguments, and must be present (with a leading space) +for control escape arguments. + +The evaluation of format strings is based on a simple virtual machine +with an integer register \fInum\fR, and a text string register \fIstr\fR. +When a function escape is processed, if it accepts an optional \fIexpr\fR +argument which is not present, it reads the current value of either +\fInum\fR or \fIstr\fR as appropriate. + +.Uh "Return values" +Component escapes write the value of their message header in \fIstr\fR. +Function escapes write their return value in \fInum\fR for functions +returning \fIinteger\fR or \fIboolean\fR values, and in \fIstr\fR for +functions returning string values. (The \fIboolean\fR type is a subset +of integers with usual values 0=false and 1=true.) Control escapes +return a \fIboolean\fP value, and set \fInum\fP. + +All component escapes, and those function escapes which return an +\fIinteger\fR or \fIstring\fR value, pass this value back to their caller +in addition to setting \fIstr\fR or \fInum\fR. These escapes will print +out this value unless called as part of an argument to another escape +sequence. Escapes which return a \fIboolean\fR value do pass this value +back to their caller in \fInum\fP, but will never print out the value. + +.nf +.ta \w'Formataddr 'u +\w'Argument 'u +\w'Rboolean 'u +\fIFunction\fR \fIArgument\fR \fIReturn\fR \fIDescription\fR +msg integer message number +cur integer message is current +unseen integer message is unseen +size integer size of message +strlen integer length of \fIstr\fR +width integer output buffer size in bytes +charleft integer bytes left in output buffer +timenow integer seconds since the UNIX epoch +me string the user's mailbox +eq literal boolean \fInum\fR == \fIarg\fR +ne literal boolean \fInum\fR != \fIarg\fR +gt literal boolean \fInum\fR > \fIarg\fR +match literal boolean \fIstr\fR contains \fIarg\fR +amatch literal boolean \fIstr\fR starts with \fIarg\fR +plus literal integer \fIarg\fR plus \fInum\fR +minus literal integer \fIarg\fR minus \fInum\fR +divide literal integer \fInum\fR divided by \fIarg\fR +modulo literal integer \fInum\fR modulo \fIarg\fR +num literal integer Set \fInum\fR to \fIarg\fR +lit literal string Set \fIstr\fR to \fIarg\fR +getenv literal string Set \fIstr\fR to environment value of \fIarg\fR +profile literal string Set \fIstr\fR to profile component \fIarg\fR value +.\" dat literal int return value of dat[arg] +nonzero expr boolean \fInum\fR is non-zero +zero expr boolean \fInum\fR is zero +null expr boolean \fIstr\fR is empty +nonnull expr boolean \fIstr\fR is non-empty +void expr Set \fIstr\fR or \fInum\fR +comp comp string Set \fIstr\fR to component text +compval comp integer Set \fInum\fR to \*(lq\fBatoi\fR(\fIcomp\fR\^)\*(rq +.\" compflag comp integer Set \fInum\fR to component flags bits (internal) +.\" decodecomp comp string Set \fIstr\fR to RFC-2047 decoded component text +decode expr string decode \fIstr\fR as RFC-2047 component +trim expr trim trailing white-space from \fIstr\fR +putstr expr print \fIstr\fR +putstrf expr print \fIstr\fR in a fixed width +putnum expr print \fInum\fR +putnumf expr print \fInum\fR in a fixed width +.\" addtoseq literal add msg to sequence (LBL option) +.re +.fi + +These functions require a date component as an argument: +.sp 1 +.nf +.ta \w'Formataddr 'u +\w'Argument 'u +\w'Rboolean 'u +\fIFunction\fR \fIArgument\fR \fIReturn\fR \fIDescription\fR +sec date integer seconds of the minute +min date integer minutes of the hour +hour date integer hours of the day (0-23) +wday date integer day of the week (Sun=0) +day date string day of the week (abbrev.) +weekday date string day of the week +sday date integer day of the week known? + (0=implicit,\-1=unknown) +mday date integer day of the month +yday date integer day of the year +mon date integer month of the year +month date string month of the year (abbrev.) +lmonth date string month of the year +year date integer year (may be > 100) +zone date integer timezone in hours +tzone date string timezone string +szone date integer timezone explicit? + (0=implicit,\-1=unknown) +date2local date coerce date to local timezone +date2gmt date coerce date to GMT +dst date integer daylight savings in effect? +clock date integer seconds since the UNIX epoch +rclock date integer seconds prior to current time +tws date string official 822 rendering +pretty date string user-friendly rendering +nodate date integer \fIstr\fR not a date string +.re +.fi + +.ne 12 +These functions require an address component as an argument. +The return value of functions noted with `*' pertain only to +the first address present in the header component. +.sp 1 +.nf +.ta \w'Formataddr 'u +\w'Argument 'u +\w'Rboolean 'u +\fIFunction\fR \fIArgument\fR \fIReturn\fR \fIDescription\fR +proper addr string official 822 rendering +friendly addr string user-friendly rendering +addr addr string mbox@host or host!mbox rendering* +pers addr string the personal name* +note addr string commentary text* +mbox addr string the local mailbox* +mymbox addr integer the user's addresses? (0=no,1=yes) +host addr string the host domain* +nohost addr integer no host was present* +type addr integer host type* (0=local,1=network, + \-1=uucp,2=unknown) +path addr string any leading host route* +ingrp addr integer address was inside a group* +gname addr string name of group* +formataddr expr append \fIarg\fR to \fIstr\fR as a + (comma separated) address list +putaddr literal print \fIstr\fR address list with + \fIarg\fR as optional label; + get line width from \fInum\fR +.re +.fi + +When escapes are nested, evaluation is done from inner-most to outer-most. +The outer-most escape must begin with `%'; the inner escapes must not. +For example, + +.ti +.5i +%<(mymbox{from}) To: %{to}%> + +writes the value of the header component \*(lqFrom:\*(rq to \fIstr\fR\^; +then (\fImymbox\fR\^) reads \fIstr\fR and writes its result to \fInum\fR; +then the control escape evaluates \fInum\fR. If \fInum\fR is non-zero, +the string \*(lqTo: \*(rq is printed followed by the value of the header +component \*(lqTo:\*(rq. + +A minor explanation of (\fImymbox\fR\^{\fIcomp\fR\^}) is in order. +In general, it checks each of the addresses in the header component +\*(lq\fIcomp\fR\*(rq against the user's mailbox name and any +\fIAlternate-Mailboxes\fR. It returns true if any address matches, +however, it also returns true if the \*(lq\fIcomp\fR\*(rq header is not +present in the message. If needed, the (\fInull\fR\^) function can be +used to explicitly test for this condition. + +When a function or component escape is interpreted and the result will +be immediately printed, an optional field width can be specified to +print the field in exactly a given number of characters. For example, a +numeric escape like %4(\fIsize\fR\^) will print at most 4 digits of the +message size; overflow will be indicated by a `?' in the first position +(like `?234'). A string escape like %4(\fIme\fR\^) will print the first 4 +characters and truncate at the end. Short fields are padded at the right +with the fill character (normally, a blank). If the field width argument +begins with a leading zero, then the fill character is set to a zero. + +As above, the functions (\fIputnumf\fR\^) and (\fIputstrf\fR\^) +print their result in exactly the number of characters +specified by their leading field width argument. For example, +%06(\fIputnumf\fR\^(\fIsize\fR\^)) will print the message +size in a field six characters wide filled with leading zeros; +%14(\fIputstrf\^\fR{\fIfrom\^\fR}) will print the \*(lqFrom:\*(rq header +component in fourteen characters with trailing spaces added as needed. +For \fIputstrf\fR, using a negative value for the field width causes +right-justification of the string within the field, with padding on +the left up to the field width. The functions (\fIputnum\fR\^) and +(\fIputstr\fR\^) print their result in the minimum number of characters +required, and ignore any leading field width argument. + +The available output width is kept in an internal register; any output +past this width will be truncated. + +Comments may be inserted in most places where a function argument is +not expected. A comment begins with `%;' and ends with a (non-escaped) +newline. + +With all this in mind, +here's the default format string for \fIscan\fR. +It's been divided into several pieces for readability. +The first part is: + +.ti +.5i +%4(msg)%<(cur)+%| %>%<{replied}\-%?{encrypted}E%| %> + +which says that the message number should be printed in four digits, +if the message is the current message then a `+' else a space should +be printed, and if a \*(lqReplied:\*(rq field is present then a `\-' +else if an \*(lqEncrypted:\*(rq field is present then an `E' otherwise +a space should be printed. Next: + +.ti +.5i +%02(mon{date})/%02(mday{date}) + +the month and date are printed in two digits (zero filled) separated by +a slash. +Next, + +.ti +.5i +%<{date} %|*> + +If a \*(lqDate:\*(rq field was present, +then a space is printed, otherwise a `*'. +Next, + +.ti +.5i +%<(mymbox{from})%<{to}To:%14(friendly{to})%>%> + +if the message is from me, +and there is a \*(lqTo:\*(rq header, +print `To:' followed by a \*(lquser-friendly\*(rq rendering of the +first address in the \*(lqTo:\*(rq field. +Continuing, + +.ti +.5i +%<(zero)%17(friendly{from})%> + +if either of the above two tests failed, +then the \*(lqFrom:\*(rq address is printed +in a \*(lquser-friendly\*(rq format. +And finally, + +.ti +.5i +%{subject}%<{body}<<%{body}%> + +the subject and initial body (if any) are printed. + +For a more complicated example, next consider +the default \fIreplcomps\fR format file. + +.ti +.5i +%(lit)%(formataddr %<{reply-to} + +This clears \fIstr\fR and formats the \*(lqReply-To:\*(rq header +if present. If not present, the else-if clause is executed. + +.ti +.5i +%?{from}%?{sender}%?{return-path}%>)\\ + +This formats the +\*(lqFrom:\*(rq, \*(lqSender:\*(rq and \*(lqReturn-Path:\*(rq +headers, stopping as soon as one of them is present. Next: + +.ti +.5i +%<(nonnull)%(void(width))%(putaddr To: )\\n%>\\ + +If the \fIformataddr\fR result is non-null, it is printed as +an address (with line folding if needed) in a field \fIwidth\fR +wide with a leading label of \*(lqTo: \*(rq. + +.ti +.5i +%(lit)%(formataddr{to})%(formataddr{cc})%(formataddr(me))\\ + +\fIstr\fR is cleared, and the +\*(lqTo:\*(rq and \*(lqCc:\*(rq headers, along with the user's +address +(depending on what was specified with +the \*(lq\-cc\*(rq switch to \fIrepl\fR\^) are formatted. + +.ti +.5i +%<(nonnull)%(void(width))%(putaddr cc: )\\n%>\\ + +If the result is non-null, it is printed as above with a +leading label of \*(lqcc: \*(rq. + +.ti +.5i +%<{fcc}Fcc: %{fcc}\\n%>\\ + +If a \*(lq\-fcc\ folder\*(rq switch was given to \fIrepl\fR +(see \fIrepl\fR\0(1) for more details about %{\fIfcc\fR\^}), +an \*(lqFcc:\*(rq header is output. + +.ti +.5i +%<{subject}Subject: Re: %{subject}\\n%>\\ + +If a subject component was present, +a suitable reply subject is output. + +.nf +.ti +.5i +%<{date}In-reply-to: Your message of "\\ +.ti +.5i +%<(nodate{date})%{date}%|%(pretty{date})%>."%<{message-id} +.ti +.5i + %{message-id}%>\\n%>\\ +.ti +.5i +\-\-\-\-\-\-\-\- +.fi + +If a date component was present, an \*(lqIn-Reply-To:\*(rq header is +output with the preface \*(lqYour message of \*(rq. If the date was +parseable, it is output in a user-friendly format, otherwise it is +output as-is. The message-id is included if present. As with all +plain-text, the row of dashes are output as-is. + +This last part is a good example for a little more elaboration. +Here's that part again in pseudo-code: +.sp 1 +.nf +.in +.5i +.ta .5i 1i 1.5i 2i +if (comp_exists(date)) then + print (\*(lqIn-reply-to: Your message of \\\*(lq\*(rq) + if (not_date_string(date.value) then + print (date.value) + else + print (pretty(date.value)) + endif + print (\*(lq\\\*(rq\*(rq) + if (comp_exists(message-id)) then + print (\*(lq\\n\\t\*(rq) + print (message-id.value) + endif + print (\*(lq\\n\*(rq) +endif +.re +.in -.5i +.fi +.sp 1 +Although this seems complicated, +in point of fact, +this method is flexible enough to extract individual fields and print them in +any format the user desires. +.Fi +None +.Pr +None +.Sa +scan(1), repl(1), ap(8), dp(8) +.De +None +.Co +None +.En diff --git a/man/mh-mail.man b/man/mh-mail.man new file mode 100644 index 0000000..df39b13 --- /dev/null +++ b/man/mh-mail.man @@ -0,0 +1,235 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-MAIL %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-mail \- message format for nmh message system +.SH SYNOPSIS +.in +.5i +.ti -.5i +any \fInmh\fR command +.in -.5i +.SH DESCRIPTION +\fInmh\fR processes messages in a particular format. It should be noted +that although neither Bell nor Berkeley mailers produce message files +in the format that \fInmh\fR prefers, \fInmh\fR can read message files in +that antiquated format. + +Each user possesses a mail drop box which initially receives all messages +processed by \fIpost\fR\0(8). \fIInc\fR\0(1) will read from that drop +box and incorporate the new messages found there into the user's own +mail folders (typically `+inbox'). The mail drop box consists of one +or more messages. + +Messages are expected to consist of lines of text. Graphics and binary +data are not handled. No data compression is accepted. All text is +clear ASCII 7-bit data. + +The general \*(lqmemo\*(rq framework of RFC\-822 is used. A message +consists of a block of information in a rigid format, followed by +general text with no specified format. The rigidly formatted first +part of a message is called the header, and the free-format portion is +called the body. The header must always exist, but the body is optional. +These parts are separated by an empty line, i.e., two consecutive newline +characters. Within \fInmh\fR, the header and body may be separated by +a line consisting of dashes: + +.nf +.in +.5i +.ne 10 +.eo +.so %etcdir%/components +.ec +.in -.5i +.fi + +The header is composed of one or more header items. Each header item can +be viewed as a single logical line of ASCII characters. If the text of +a header item extends across several real lines, the continuation lines +are indicated by leading spaces or tabs. + +Each header item is called a component and is composed of a keyword or +name, along with associated text. The keyword begins at the left margin, +may NOT contain spaces or tabs, may not exceed 63 characters (as specified +by RFC\-822), and is terminated by a colon (`:'). Certain components +(as identified by their keywords) must follow rigidly defined formats +in their text portions. + +The text for most formatted components (e.g., \*(lqDate:\*(rq and +\*(lqMessage\-Id:\*(rq) is produced automatically. The only ones entered +by the user are address fields such as \*(lqTo:\*(rq, \*(lqcc:\*(rq, +etc. Internet addresses are assigned mailbox names and host computer +specifications. The rough format is \*(lqlocal@domain\*(rq, such as +\*(lqMH@UCI\*(rq, or \*(lqMH@UCI\-ICSA.ARPA\*(rq. Multiple addresses +are separated by commas. A missing host/domain is assumed to be the +local host/domain. + +As mentioned above, a blank line (or a line of dashes) signals that all +following text up to the end of the file is the body. No formatting is +expected or enforced within the body. + +Following is a list of header components that are considered +meaningful to various nmh programs. + +.in +.5i +.ti -.5i +Date: +.br +Added by \fIpost\fR\0(8), contains date and time of the message's entry +into the mail transport system. + +.ti -.5i +From: +.br +Added by \fIpost\fR\0(8), contains the address of the author or authors +(may be more than one if a \*(lqSender:\*(rq field is present). For a +standard reply (using \fIrepl\fR), the reply address is constructed by +checking the following headers (in this order): \*(lqMail-Reply\-To:\*(rq, +\*(lqReply\-To:\*(rq, \*(lqFrom:\*(rq, \*(lqSender:\*(rq. + +.ti -.5i +Mail\-Reply\-To: +.br +For a standard reply (using \fIrepl\fR), the reply address is +constructed by checking the following headers (in this order): +\*(lqMail-Reply\-To:\*(rq, \*(lqReply\-To:\*(rq, \*(lqFrom:\*(rq, +\*(lqSender:\*(rq. + +.ti -.5i +Mail\-Followup\-To: +.br +When making a \*(lqgroup\*(rq reply (using \fIrepl\fR -group), any +addresses in this field will take precedence, and no other reply address +will be added to the draft. If this header is not available, then the +return addresses will be constructed from the \*(lqMail-Reply\-To:\*(rq, +or \*(lqReply\-To:\*(rq, or \*(lqFrom:\*(rq, along with adding the +addresses from the headers \*(lqTo:\*(rq, \*(lqcc:\*(rq, as well as +adding your personal address. + +.ti -.5i +Reply\-To: +.br +For a standard reply (using \fIrepl\fR), the reply address is +constructed by checking the following headers (in this order): +\*(lqMail-Reply\-To:\*(rq, \*(lqReply\-To:\*(rq, \*(lqFrom:\*(rq, +\*(lqSender:\*(rq. + +.ti -.5i +Sender: +.br +Added by \fIpost\fR\0(8) in the event that the message already has a +\*(lqFrom:\*(rq line. This line contains the address of the actual +sender. + +.ti -.5i +To: +.br +Contains addresses of primary recipients. + +.ti -.5i +cc: +.br +Contains addresses of secondary recipients. + +.ti -.5i +Bcc: +.br +Still more recipients. However, the \*(lqBcc:\*(rq line is not +copied onto the message as delivered, so these recipients are not +listed. \fInmh\fR uses an encapsulation method for blind copies, see +\fIsend\fR\0(1). + +.ti -.5i +Fcc: +.br +Causes \fIpost\fR\0(8) to copy the message into the specified folder for the +sender, +if the message was successfully given to the transport system. + +.ti -.5i +Message\-ID: +.br +A unique message identifier added by \fIpost\fR\0(8) if the `\-msgid' flag +is set. + +.ti -.5i +Subject: +.br +Sender's commentary. It is displayed by \fIscan\fR\0(1). + +.ti -.5i +In\-Reply\-To: +.br +A commentary line added by \fIrepl\fR\0(1) when replying to a message. + +.ti -.5i +Resent\-Date: +.br +Added when redistributing a message by \fIpost\fR\0(8). + +.ti -.5i +Resent\-From: +.br +Added when redistributing a message by \fIpost\fR\0(8). + +.ti -.5i +Resent\-To: +.br +New recipients for a message resent by \fIdist\fR\0(1). + +.ti -.5i +Resent\-cc: +.br +Still more recipients. +See \*(lqcc:\*(rq and \*(lqResent\-To:\*(rq. + +.ti -.5i +Resent\-Bcc: +.br +Even more recipients. +See \*(lqBcc:\*(rq and \*(lqResent\-To:\*(rq. + +.ti -.5i +Resent\-Fcc: +.br +Copy resent message into a folder. +See \*(lqFcc:\*(rq and \*(lqResent\-To:\*(rq. + +.ti -.5i +Resent\-Message\-Id: +.br +A unique identifier glued on by \fIpost\fR\0(8) if the `\-msgid' flag +is set. +See \*(lqMessage\-Id:\*(rq and \*(lqResent\-To:\*(rq. + +.ti -.5i +Resent: +.br +Annotation for \fIdist\fR\0(1) under the `\-annotate' option. + +.ti -.5i +Forwarded: +.br +Annotation for \fIforw\fR\0(1) under the `\-annotate' option. + +.ti -.5i +Replied: +.br +Annotation for \fIrepl\fR\0(1) under the `\-annotate' option. +.in -.5i +.sp +.Fi +^%mailspool%/$USER~^Location of mail drop +.Pr +None +.Sa +RFC\-822:\fIStandard for the Format of ARPA Internet Text Messages\fR +.De +None +.Co +None +.En diff --git a/man/mh-mts.man b/man/mh-mts.man new file mode 100644 index 0000000..1829ae6 --- /dev/null +++ b/man/mh-mts.man @@ -0,0 +1,96 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-MTS %manext8% MH.6.8 [%nmhversion%] +.SH NAME +mh-mts \- the nmh interface to the message transport system +.SH SYNOPSIS +.in +.5i +.ti -.5i +SendMail + +.ti .5i +Zmailer + +.ti .5i +MMDF (any release) + +.ti .5i +stand\-alone +.in -.5i +.SH DESCRIPTION +THIS IS OUT OF DATE AND NEEDS REWORKING. + +\fInmh\fR can use a wide range of message transport systems to deliver +mail. Although the \fInmh\fR administrator usually doesn't get to choose +which MTS to use (since it's already in place), this document briefly +describes the interfaces. + +When communicating with \fISendMail\fR, \fInmh\fR always uses the SMTP to +post mail. Depending on the \fInmh\fR configuration, \fISendMail\fR may +be invoked directly (via a \fIfork\fR and an \fIexec\fR), or \fInmh\fR +may open a TCP/IP connection to the SMTP server on the localhost. + +When communicating with \fIzmailer\fP, the \fISendMail\fP compatibility +program is required to be installed in /usr/lib. \fInmh\fP communicates +with \fIzmailer\fP by using the SMTP. It does this by invoking the +\fB/usr/lib/sendmail\fP compatibility program directly, with the +`\-bs' option. + +When communicating with \fIMMDF\fR, normally \fInmh\fR uses the +\*(lqmm\(ru\*(rq routines to post mail. However, depending on the +\fInmh\fR configuration, \fInmh\fR instead may open a TCP/IP connection +to the SMTP server on the localhost. + +If you are running a UNIX system with TCP/IP networking, then it is +felt that the best interface is achieved by using either \fISendMail\fR +or \fIMMDF\fR with the SMTP option. This gives greater flexibility. +To enable this option you append the /smtp suffix to the mts option +in the \fInmh\fR configuration. This yields two primary advantages: +First, you don't have to know where \fIsubmit\fR or \fISendMail\fR live. +This means that \fInmh\fR binaries (e.g., \fIpost\fR\0) don't have to have +this information hard\-coded, or can run different programs altogether; +and, second, you can post mail with the server on different systems, so +you don't need either \fIMMDF\fR or \fISendMail\fR on your local host. +Big win in conserving cycles and disk space. Since \fInmh\fR supports +the notion of a server search\-list in this respect, this approach can +be tolerant of faults. Be sure to set \*(lqservers:\*(rq as described +in mh\-tailor(8) if you use this option. + +There are four disadvantages to using the SMTP option: First, only UNIX +systems with TCP/IP are supported. Second, you need to have an SMTP +server running somewhere on any network your local host can reach. +Third, this bypasses any authentication mechanisms in \fIMMDF\fR +or \fISendMail\fR. Fourth, the file \fB/etc/hosts\fR is used for +hostname lookups (although there is an exception file). In response +to these disadvantages though: First, there's got to be an SMTP server +somewhere around if you're in the Internet or have a local network. +Since the server search\-list is very general, a wide\-range of options +are possible. Second, SMTP should be fixed to have authentication +mechanisms in it, like POP. Third, \fInmh\fR won't choke on mail to +hosts whose official names it can't verify, it'll just plug along (and +besides if you enable the DUMB configuration options, \fInmh\fR +ignores the hosts file altogether). +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +.Pr +None +.Sa +\fIMMDF\-II: A Technical Review\fR, +Proceedings, Usenix Summer '84 Conference +.br +\fISENDMAIL \-\- An Internetwork Mail Router\fR +.br +mh\-tailor(8), post(8) +.De +None +.Co +None +.Bu +The %etcdir%/mts.conf file ignores the information in the \fIMMDF\-II\fR +tailoring file. +.En diff --git a/man/mh-profile.man b/man/mh-profile.man new file mode 100644 index 0000000..e1326ed --- /dev/null +++ b/man/mh-profile.man @@ -0,0 +1,587 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-PROFILE %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-profile \- user profile customization for nmh message handler +.SH SYNOPSIS +.in +.5i +.ti -.5i +\&\fI.mh\(ruprofile\fP +.in -.5i +.SH DESCRIPTION +Each user of \fInmh\fR is expected to have a file named +\fI\&.mh\(ruprofile\fR in his or her home directory. This file contains +a set of user parameters used by some or all of the \fInmh\fR family +of programs. Each entry in the file is of the format + + \fIprofile\-component\fR: \fIvalue\fR + +If the text of profile entry is long, you may extend it across several +real lines by indenting the continuation lines with leading spaces +or tabs. + +.Uh "Standard Profile Entries" +The possible profile components are exemplified below. The only mandatory +entry is `Path:'. The others are optional; some have default values if +they are not present. In the notation used below, (profile, default) +indicates whether the information is kept in the user's \fInmh\fR profile +or \fInmh\fR context, and indicates what the default value is. + +.in +1i +.ti -1i +Path: Mail +.br +Locates \fInmh\fR transactions in directory \*(lqMail\*(rq. This is the +only mandatory profile entry. (profile, no default) + +.ti -1i +context: context +.br +Declares the location of the \fInmh\fR context file. This is +overridden by the environment variable \fBMHCONTEXT\fR. +See the \fBHISTORY\fR section below. +(profile, default: /context) + +.ti -1i +Current\-Folder:\ inbox +.br +Keeps track of the current open folder. +(context, default: folder specified by \*(lqInbox\*(rq) + +.ti -1i +Inbox: inbox +.br +Defines the name of your default inbox. +(profile, default: inbox) + +.ti -1i +Previous\-Sequence:\ pseq +.br +Names the sequence or sequences which should be defined as the `msgs' or +`msg' argument given to any \fInmh\fR command. If not present or empty, +no such sequences are defined. Otherwise, for each name given, the +sequence is first zero'd and then each message is added to the sequence. +Read the mh\-sequence(5) man page for the details about this sequence. +(profile, no default) + +.ti -1i +Sequence\-Negation:\ not +.br +Defines the string which, when prefixed to a sequence name, negates +that sequence. Hence, \*(lqnotseen\*(rq means all those messages that +are not a member of the sequence \*(lqseen\*(rq. Read the mh\-sequence(5) +man page for the details. (profile, no default) + +.ti -1i +Unseen\-Sequence:\ unseen +.br +Names the sequence or sequences which should be defined as those +messages which are unread. The commands \fIinc\fR, \fIrcvstore\fR, +\fImhshow\fR, and \fIshow\fR will add or remove messages from these +sequences when they are incorporated or read. If not present or +empty, no such sequences are defined. Otherwise, each message is +added to, or removed from, each sequence name given. Read the +mh\-sequence(5) man page for the details about this sequence. +(profile, no default) + +.ti -1i +mh\-sequences:\ \&.mh\(rusequences +.br +The name of the file in each folder which defines public sequences. +To disable the use of public sequences, leave the value portion of this +entry blank. (profile, default: \&.mh\(rusequences) + +.ti -1i +atr\-\fIseq\fR\-\fIfolder\fR:\ 172\0178\-181\0212 +.br +Keeps track of the private sequence called \fIseq\fR in the specified +folder. Private sequences are generally used for read\-only folders. +See the mh\-sequence(5) man page for details about private sequences. +(context, no default) + +.ti -1i +Editor:\ /usr/bin/vi +.br +Defines the editor to be used by the commands \fIcomp\fR\0(1), +\fIdist\fR\0(1), \fIforw\fR\0(1), and \fIrepl\fR\0(1). (profile, default: +%default_editor%) + +.ti -1i +automimeproc: +.br +If defined and set to 1, then the \fIwhatnow\fR program will automatically +invoke the buildmimeproc (discussed below) to process each message as a MIME +composition draft before it is sent. +(profile, no default) + +.ti -1i +Msg\-Protect:\ 644 +.br +An octal number which defines the permission bits for new message files. +See \fIchmod\fR\0(1) for an explanation of the octal number. +(profile, default: 0644) + +.ti -1i +Folder\-Protect:\ 700 +.br +An octal number which defines the permission bits for new folder +directories. See \fIchmod\fR\0(1) for an explanation of the octal number. +(profile, default: 0700) + +.ti -1i +\fIprogram\fR:\ default switches +.br +Sets default switches to be used whenever the mh program \fIprogram\fR +is invoked. For example, one could override the \fIEditor\fR: profile +component when replying to messages by adding a component such as: +.br + repl: \-editor /bin/ed +.br +(profile, no defaults) + +.ti -1i +\fIlasteditor\fR\-next:\ nexteditor +.br +Names \*(lqnexteditor\*(rq to be the default editor after using +\*(lqlasteditor\*(rq. This takes effect at \*(lqWhat now?\*(rq prompt +in \fIcomp\fR, \fIdist\fR, \fIforw\fR, and \fIrepl\fR. After editing +the draft with \*(lqlasteditor\*(rq, the default editor is set to be +\*(lqnexteditor\*(rq. If the user types \*(lqedit\*(rq without any +arguments to \*(lqWhat now?\*(rq, then \*(lqnexteditor\*(rq is used. +(profile, no default) + +.ti -1i +bboards: system +.br +Tells \fIbbc\fR which BBoards you are interested in. (profile, default: +system) + +.ti -1i +Folder\-Stack: \fIfolders\fR +.br +The contents of the folder-stack for the \fIfolder\fR command. +(context, no default) + +.ti -1i +mhe: +.br +If present, tells \fIinc\fR to compose an \fIMHE\fR auditfile in addition +to its other tasks. \fIMHE\fR is Brian Reid's \fIEmacs\fR front-end +for \fInmh\fR. (profile, no default) + +.ti -1i +Alternate\-Mailboxes: mh@uci\-750a, bug-mh* +.br +Tells \fIrepl\fR and \fIscan\fR which addresses are really yours. +In this way, \fIrepl\fR knows which addresses should be included in the +reply, and \fIscan\fR knows if the message really originated from you. +Addresses must be separated by a comma, and the hostnames listed should +be the \*(lqofficial\*(rq hostnames for the mailboxes you indicate, as +local nicknames for hosts are not replaced with their official site names. +For each address, if a host is not given, then that address on any host is +considered to be you. In addition, an asterisk (`*') may appear at either +or both ends of the mailbox and host to indicate wild-card matching. +(profile, default: your user-id) + +.ti -1i +Aliasfile: aliases other-alias +.br +Indicates aliases files for \fIali\fR, \fIwhom\fR, and \fIsend\fR. +This may be used instead of the `\-alias file' switch. (profile, no +default) + +.ti -1i +Draft\-Folder: drafts +.br +Indicates a default draft folder for \fIcomp\fR, \fIdist\fR, \fIforw\fR, +and \fIrepl\fR. Read the mh\-draft (5) man page for details. +(profile, no default) + +.ti -1i +digest\-issue\-\fIlist\fR:\ 1 +.br +Tells \fIforw\fR the last issue of the last volume sent for the digest +\fIlist\fR. (context, no default) + +.ti -1i +digest\-volume\-\fIlist\fR:\ 1 +.br +Tells \fIforw\fR the last volume sent for the digest \fIlist\fR. +(context, no default) + +.ti -1i +MailDrop: .mail +.br +Tells \fIinc\fR your maildrop, if different from the default. This is +superseded by the environment variable \fBMAILDROP\fR. (profile, default: +%mailspool%/$USER) + +.ti -1i +Signature: RAND MH System (agent: Marshall Rose) +.br +Tells \fIsend\fR your mail signature. This is superseded by the +environment variable \fBSIGNATURE\fR. If \fBSIGNATURE\fR is not set and +this profile entry is not present, the \*(lqgcos\*(rq field of +the \fI/etc/passwd\fP file will be used; otherwise, on hosts where +\fInmh\fR was configured with the UCI option, the file $HOME/.signature +is consulted. Your signature will be added to the address \fIsend\fP +puts in the \*(lqFrom:\*(rq header; do not include an address in the +signature text. (profile, no default) +.in -1i + +.Uh "Process Profile Entries" +The following profile elements are used whenever an \fInmh\fR +program invokes some other program such as \fImore\fR\0(1). The +\fI\&.mh\(ruprofile\fR can be used to select alternate programs if the +user wishes. The default values are given in the examples. + +.in +1i +.ti -1i +buildmimeproc: %bindir%/mhbuild +.br +This is the program used by \fIwhatnow\fR to process drafts which +are MIME composition files. + +.ti -1i +fileproc: %bindir%/refile +.br +This program is used to refile or link a message to another folder. +It is used by \fIpost\fR to file a copy of a message into a folder given +by a \*(lqFcc:\*(rq field. It is used by the draft folder facility in +\fIcomp\fR, \fIdist\fR, \fIforw\fR, and \fIrepl\fR to refile a draft +message into another folder. It is used to refile a draft message in +response to the `refile' directive at the \*(lqWhat now?\*(rq prompt. + +.ti -1i +incproc: %bindir%/inc +.br +Program called by \fImhmail\fR to incorporate new mail when it +is invoked with no arguments. + +.ti -1i +installproc: %libdir%/install\-mh +.br +This program is called to initialize the environment for +new users of nmh. + +.ti -1i +lproc: %default_pager% +.br +This program is used to list the contents of a message in response +to the `list' directive at the \*(lqWhat now?\*(rq prompt. It is +also used by the draft folder facility in \fIcomp\fR, \fIdist\fR, +\fIforw\fR, and \fIrepl\fR to display the draft message. + +.ti -1i +mailproc: %bindir%/mhmail +.br +This is the program used to automatically mail various messages +and notifications. It is used by \fIconflict\fR when using the +`-mail' option. It is used by \fIsend\fR to post failure notices. +It is used to retrieve an external-body with access-type `mail-server' +(such as when storing the body with \fImhstore\fR). + +.ti -1i +mhlproc: %libdir%/mhl +.br +This is the program used to filter messages in various ways. It +is used by \fImhshow\fR to filter and display the message headers +of MIME messages. When the `-format' or `-filter' option is used +by \fIforw\fR or \fIrepl\fR, the mhlproc is used to filter the +message that you are forwarding, or to which you are replying. +When the `-filter' option is given to \fIsend\fR or \fIpost\fR, +the mhlproc is used by \fIpost\fR to filter the copy of the message +that is sent to \*(lqBcc:\*(rq recipients. + +.ti -1i +moreproc: %default_pager% +.br +This is the program used by \fImhl\fR to page the \fImhl\fR formatted +message when displaying to a terminal. It is also the default +program used by \fImhshow\fR to display message bodies (or message +parts) of type text/plain. + +.ti -1i +mshproc: %bindir%/msh +.br +Currently not used. + +.ti -1i +packproc: %bindir%/packf +.br +Currently not used. + +.ti -1i +postproc: %libdir%/post +.br +This is the program used by \fIsend\fR, \fImhmail\fR, \fIrcvdist\fR, +and \fIviamail\fR (used by the \fIsendfiles\fR shell script) to +post a message to the mail transport system. It is also called by +\fIwhom\fR (called with the switches `-whom' and `-library') to do +address verification. + +.ti -1i +rmmproc: none +.br +This is the program used by \fIrmm\fR and \fIrefile\fR to delete +a message from a folder. + +.ti -1i +rmfproc: %bindir%/rmf +.br +Currently not used. + +.ti -1i +sendproc: %bindir%/send +.br +This is the program to use by \fIwhatnow\fR to actually +send the message + +.ti -1i +showmimeproc: %bindir%/mhshow +.br +This is the program used by \fIshow\fR to process and display +non-text (MIME) messages. + +.ti -1i +showproc: %libdir%/mhl +.br +This is the program used by \fIshow\fR to filter and display text +(non-MIME) messages. + +.ti -1i +whatnowproc: %bindir%/whatnow +.br +This is the program invoked by \fIcomp\fR, \fIforw\fR, \fIdist\fR, and +\fIrepl\fR to query about the disposition of a composed draft message. + +.ti -1i +whomproc: %bindir%/whom +.br +This is the program used by \fIwhatnow\fR to determine to whom a +message would be sent. + +.Uh "Environment Variables" +The operation of nmh and its commands it also controlled by the +presence of certain environment variables. + +Many of these environment variables are used internally by the +\*(lqWhat now?\*(rq interface. It's amazing all the information +that has to get passed via environment variables to make the +\*(lqWhat now?\*(rq interface look squeaky clean to the \fInmh\fR +user, isn't it? The reason for all this is that the \fInmh\fR user +can select \fIany\fR program as the \fIwhatnowproc\fR, including +one of the standard shells. As a result, it's not possible to pass +information via an argument list. + +If the WHATNOW option was set during \fInmh\fR configuration, and +if this environment variable is set, then if the commands \fIrefile\fR, +\fIsend\fR, \fIshow\fR, or \fIwhom\fR are not given any `msgs' +arguments, then they will default to using the file indicated by +\fBmhdraft\fR. This is useful for getting the default behavior +supplied by the default \fIwhatnowproc\fR. + +.in +.5i +.ti -.5i +\fBMH\fR\0: With this environment variable, you can specify a profile +other than \fI\&.mh\(ruprofile\fR to be read by the \fInmh\fR programs +that you invoke. If the value of \fBMH\fR is not absolute, (i.e., does +not begin with a \fB/\fR\0), it will be presumed to start from the current +working directory. This is one of the very few exceptions in \fInmh\fR +where non-absolute pathnames are not considered relative to the user's +\fInmh\fR directory. + +.ti -.5i +\fBMHCONTEXT\fR\0: With this environment variable, you can specify a +context other than the normal context file (as specified in +the \fInmh\fR profile). As always, unless the value of \fBMHCONTEXT\fR +is absolute, it will be presumed to start from your \fInmh\fR directory. + +.ti -.5i +\fBMM_CHARSET\fR\0: With this environment variable, you can specify +the native character set you are using. You must be able to display +this character set on your terminal. + +This variable is checked to see if a RFC-2047 header field should be +decoded (in \fIinc\fR, \fIscan\fR, \fImhl\fR). This variable is +checked by \fIshow\fR to see if the showproc or showmimeproc should +be called, since showmimeproc will be called if a text message uses +a character set that doesn't match MM_CHARSET. This variable is +checked by \fImhshow\fR for matches against the charset parameter +of text contents to decide it the text content can be displayed +without modifications to your terminal. This variable is checked by +\fImhbuild\fR to decide what character set to specify in the charset +parameter of text contents containing 8bit characters. + +When decoding text in such an alternate character set, \fInmh\fR +must be able to determine which characters are alphabetic, which +are control characters, etc. For many operating systems, this +will require enabling the support for locales (such as setting +the environment variable LC_CTYPE to iso_8859_1). + +.ti -.5i +\fBMAILDROP\fR\0: tells \fIinc\fR the default maildrop +.br +This supersedes the \*(lqMailDrop:\*(rq profile entry. + +.ti -.5i +\fBSIGNATURE\fR\0: tells \fIsend\fR and \fIpost\fR your mail signature +.br +This supersedes the \*(lqSignature:\*(rq profile entry. + +.ti -.5i +\fBHOME\fR\0: tells all \fInmh\fR programs your home directory + +.ti -.5i +\fBSHELL\fR\0: tells \fIbbl\fR the default shell to run + +.ti -.5i +\fBTERM\fR\0: tells \fInmh\fR your terminal type +.br +The environment variable \fBTERMCAP\fR is also consulted. In particular, +these tell \fIscan\fR and \fImhl\fR how to clear your terminal, and how +many columns wide your terminal is. They also tell \fImhl\fR how many +lines long your terminal screen is. + +.ti -.5i +\fBeditalt\fR\0: the alternate message +.br +This is set by \fIdist\fR and \fIrepl\fR during edit sessions so you can +peruse the message being distributed or replied to. The message is also +available through a link called \*(lq@\*(rq in the current directory if +your current working directory and the folder the message lives in are +on the same UNIX filesystem. + +.ti -.5i +\fBmhdraft\fR\0: the path to the working draft +.br +This is set by \fIcomp\fR, \fIdist\fR, \fIforw\fR, and \fIrepl\fR +to tell the \fIwhatnowproc\fR which file to ask \*(lqWhat now?\*(rq +questions about. + +.ti -.5i +\fBmhfolder\fR\0: +.br +This is set by \fIdist\fR, \fIforw\fR, and \fIrepl\fR, +if appropriate. + +.ti -.5i +\fBmhaltmsg\fR\0: +.br +\fIdist\fR and \fIrepl\fR set \fBmhaltmsg\fR to tell the +\fIwhatnowproc\fR about an alternate message associated with the +draft (the message being distributed or replied to). + +.ti -.5i +\fBmhdist\fR\0: +.br +\fIdist\fR sets \fBmhdist\fR to tell the \fIwhatnowproc\fR that +message re-distribution is occurring. + +.ti -.5i +\fBmheditor\fR\0: +.br +This is set to tell the \fIwhatnowproc\fR the user's choice of +editor (unless overridden by `\-noedit'). + +.ti -.5i +\fBmhuse\fR\0: +.br +This may be set by \fIcomp\fR. + +.ti -.5i +\fBmhmessages\fR\0: +.br +This is set by \fIdist\fR, \fIforw\fR, and \fIrepl\fR if annotations +are to occur. + +.ti -.5i +\fBmhannotate\fR\0: +.br +This is set by \fIdist\fR, \fIforw\fR, and \fIrepl\fR if annotations +are to occur. + +.ti -.5i +\fBmhinplace\fR\0: +.br +This is set by \fIdist\fR, \fIforw\fR, and \fIrepl\fR if annotations +are to occur. + +.ti -.5i +\fBmhfolder\fR\0: the folder containing the alternate message +.br +This is set by \fIdist\fR and \fIrepl\fR during edit sessions so you +can peruse other messages in the current folder besides the one being +distributed or replied to. The environment variable \fBmhfolder\fR is +also set by \fIshow\fR, \fIprev\fR, and \fInext\fR for use by \fImhl\fR. +.in -.5i + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^or $MH~^Rather than the standard profile +^/context~^The user context +^or $MHCONTEXT~^Rather than the standard context +^/\&.mh\(rusequences~^Public sequences for +.Pr +All +.Sa +mh(1), environ(5), mh-sequence(5) +.De +None +.Co +All +.Hi +The \fI\&.mh\(ruprofile\fR contains only static information, which +\fInmh\fR programs will \fBNOT\fR update. Changes in context are +made to the \fIcontext\fR file kept in the users nmh \fIdirectory\fR. +This includes, but is not limited to: the \*(lqCurrent\-Folder\*(rq entry +and all private sequence information. Public sequence information is +kept in each folder in the file determined by the \*(lqmh\-sequences\*(rq +profile entry (default is \fI\&.mh\(rusequences\fR). + +The \fI\&.mh\(ruprofile\fR may override the path of the \fIcontext\fR +file, by specifying a \*(lqcontext\*(rq entry (this must be in +lower-case). If the entry is not absolute (does not start with a +\fB/\fR\0), then it is interpreted relative to the user's \fInmh\fR +directory. As a result, you can actually have more than one set of +private sequences by using different context files. +.Bu +The shell quoting conventions are not available in the \&.mh\(ruprofile. +Each token is separated by whitespace. + +There is some question as to what kind of arguments should be placed +in the profile as options. In order to provide a clear answer, recall +command line semantics of all \fInmh\fR programs: conflicting switches +(e.g., `\-header and `\-noheader') may occur more than one time on the +command line, with the last switch taking effect. Other arguments, such +as message sequences, filenames and folders, are always remembered on +the invocation line and are not superseded by following arguments of +the same type. Hence, it is safe to place only switches (and their +arguments) in the profile. + +If one finds that an \fInmh\fR program is being invoked again and again +with the same arguments, and those arguments aren't switches, then there +are a few possible solutions to this problem. The first is to create a +(soft) link in your \fI$HOME/bin\fR directory to the \fInmh\fR program +of your choice. By giving this link a different name, you can create +a new entry in your profile and use an alternate set of defaults for +the \fInmh\fR command. Similarly, you could create a small shell script +which called the \fInmh\fR program of your choice with an alternate set +of invocation line switches (using links and an alternate profile entry +is preferable to this solution). + +Finally, the \fIcsh\fR user could create an alias for the command of the form: + +.ti +.5i +alias cmd 'cmd arg1 arg2 ...' + +In this way, the user can avoid lengthy type-in to the shell, and still +give \fInmh\fR commands safely. (Recall that some \fInmh\fR commands +invoke others, and that in all cases, the profile is read, meaning that +aliases are disregarded beyond an initial command invocation) +.En diff --git a/man/mh-sequence.man b/man/mh-sequence.man new file mode 100644 index 0000000..7d2c72b --- /dev/null +++ b/man/mh-sequence.man @@ -0,0 +1,209 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-SEQUENCE %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-sequence \- sequence specification for nmh message system +.SH SYNOPSIS +.in +.5i +.ti -.5i +most \fInmh\fR commands +.in -.5i +.SH DESCRIPTION +A sequence (or sequence set) is a symbolic name representing a +message or collection of messages. \fInmh\fP has several internally +defined sequences, as well as allowing users to define their own +sequences. + +.Uh "Message Specification and Pre\-Defined Message Sequences" +Most \fInmh\fP commands accept a `msg' or `msgs' specification, where +`msg' indicates one message and `msgs' indicates one or more messages. +To designate a message, you may use either its number (e.g., 1, 10, 234) +or one of these \*(lqreserved\*(rq message names: +.in +.5i +.sp 1 +.nf +.ta +\w'\fIName\fP 'u +\fIName\fP \fIDescription\fR +first the first message in the folder +last the last message in the folder +cur the most recently accessed message +prev the message numerically preceding \*(lqcur\*(rq +next the message numerically following \*(lqcur\*(rq +.re +.fi +.in -.5i + +In commands that take a `msg' argument, the default is \*(lqcur\*(rq. +As a shorthand, \*(lq\&.\*(rq is equivalent to \*(lqcur\*(rq. + +For example: In a folder containing five messages numbered 5, 10, 94, 177 +and 325, \*(lqfirst\*(rq is 5 and \*(lqlast\*(rq is 325. If \*(lqcur\*(rq +is 94, then \*(lqprev\*(rq is 10 and \*(lqnext\*(rq is 177. + +The word `msgs' indicates that one or more messages may be specified. +Such a specification consists of one message designation or of several +message designations separated by spaces. A message designation consists +either of a message name as defined above, or a message range. + +A message range is specified as \*(lqname1\-name2\*(rq or +\*(lqname:n\*(rq, where `name', `name1' and `name2' are message names, +and `n' is an integer. + +The specification \*(lqname1\-name2\*(rq designates all currently existing +messages from `name1' to `name2' inclusive. The \*(lqreserved\*(rq +message name \*(lqall\*(rq is a shorthand for the message range +\*(lqfirst\-last\*(rq. + +The specification \*(lqname:n\*(rq designates up to `n' messages. +These messages start with `name' if `name' is a message number or one of +the reserved names \*(lqfirst\*(rq \*(lqcur\*(rq, or \*(lqnext\*(rq, The +messages end with `name' if `name' is \*(lqprev\*(rq or \*(lqlast\*(rq. +The interpretation of `n' may be overridden by preceding `n' with a +plus or minus sign; `+n' always means up to `n' messages starting with +`name', and `\-n' always means up to `n' messages ending with `name'. + +In commands which accept a `msgs' argument, the default is either +\*(lqcur\*(rq or \*(lqall\*(rq, depending on which makes more sense +for each command (see the individual man pages for details). Repeated +specifications of the same message have the same effect as a single +specification of the message. + +There is also a special \*(lqreserved\*(rq message name \*(lqnew\*(rq +which is used by the \fImhpath\fR command. + +.Uh "User\-Defined Message Sequences" +In addition to the \*(lqreserved\*(rq (pre-defined) message names given +above, \fInmh\fP supports user-defined sequence names. User-defined +sequences allow the \fInmh\fR user a tremendous amount of power in dealing +with groups of messages in the same folder by allowing the user to bind +a group of messages to a meaningful symbolic name. + +The name used to denote a message sequence must consist of an alphabetic +character followed by zero or more alphanumeric characters, and can not +be one of the \*(lqreserved\*(rq message names above. After defining a +sequence, it can be used wherever an \fInmh\fR command expects a `msg' or +`msgs' argument. + +Some forms of message ranges are allowed with user-defined sequences. +The specification \*(lqname:n\*(rq may be used, and it designates up +to the first `n' messages (or last `n' messages for `\-n') which are +elements of the user-defined sequence `name'. + +The specifications \*(lqname:next\*(rq and \*(lqname:prev\*(rq may also +be used, and they designate the next or previous message (relative to the +current message) which is an element of the user-defined sequence `name'. +The specifications \*(lqname:first\*(rq and \*(lqname:last\*(rq are +equivalent to \*(lqname:1\*(rq and \*(lqname:\-1\*(rq, respectively. The +specification \*(lqname:cur\*(rq is not allowed (use just \*(lqcur\*(rq +instead). The syntax of these message range specifications is subject +to change in the future. + +User-defined sequence names are specific to each folder. They are +defined using the \fIpick\fP and \fImark\fP commands. + +.Uh "Public and Private User-Defined Sequences" +There are two varieties of user-defined sequences: \fIpublic\fR and +\fIprivate\fR. \fIPublic\fR sequences of a folder are accessible to any +\fInmh\fR user that can read that folder. They are kept in each folder +in the file determined by the \*(lqmh\-sequences\*(rq profile entry +(default is \&.mh\(rusequences). \fIPrivate\fR sequences are accessible +only to the \fInmh\fR user that defined those sequences and are kept in +the user's \fInmh\fR context file. + +In general, the commands that create sequences (such as \fIpick\fR and +\fImark\fR) will create \fIpublic\fR sequences if the folder for which +the sequences are being defined is writable by the \fInmh\fR user. +For most commands, this can be overridden by using the switches +`\-public' and `\-private'. But if the folder is read\-only, or if +the \*(lqmh\-sequences\*(rq profile entry is defined but empty, then +\fIprivate\fR sequences will be created instead. + +.Uh "Sequence Negation" +\fInmh\fP provides the ability to select all messages not elements of a +user-defined sequence. To do this, the user should define the entry +\*(lqSequence\-Negation\*(rq in the \fInmh\fR profile file; its value +may be any string. This string is then used to preface an existing +user-defined sequence name. This specification then refers to those +messages not elements of the specified sequence name. For example, if +the profile entry is: + +.ti +.5i +Sequence\-Negation:\^ not + +then anytime an \fInmh\fR command is given \*(lqnotfoo\*(rq as a `msg' or +`msgs' argument, it would substitute all messages that are not elements +of the sequence \*(lqfoo\*(rq. + +Obviously, the user should beware of defining sequences with names that +begin with the value of the \*(lqSequence\-Negation\*(rq profile entry. + +.Uh "The Previous Sequence" +\fInmh\fR provides the ability to remember the `msgs' or `msg' argument +last given to an \fInmh\fR command. The entry \*(lqPrevious\-Sequence\*(rq +should be defined in the \fInmh\fR profile; its value should be a sequence +name or multiple sequence names separated by spaces. If this entry +is defined, when when an \fInmh\fP command finishes, it will define the +sequence(s) named in the value of this entry to be those messages that +were specified to the command. Hence, a profile entry of + +.ti +.5i +Previous\-Sequence:\^ pseq + +directs any \fInmh\fR command that accepts a `msg' or `msgs' argument to +define the sequence \*(lqpseq\*(rq as those messages when it finishes. + +\fBNote:\fP there can be a performance penalty in using the +\*(lqPrevious\-Sequence\*(rq facility. If it is used, \fBall\fP +\fInmh\fR programs have to write the sequence information to the +\&.mh\(rusequences file for the folder each time they run. If the +\*(lqPrevious\-Sequence\*(rq profile entry is not included, only +\fIpick\fR and \fImark\fR will write to the \&.mh\(rusequences file. + +.Uh "The Unseen Sequence" +Finally, many users like to indicate which messages have not been +previously seen by them. The commands \fIinc\fR, \fIrcvstore\fR, +\fIshow\fR, \fImhshow\fR, and \fIflist\fR honor the profile entry +\*(lqUnseen\-Sequence\*(rq to support this activity. This entry +in the \&.mh\(ruprofile should be defined as one or more sequence +names separated by spaces. If there is a value for +\*(lqUnseen\-Sequence\*(rq in the profile, then whenever new messages +are placed in a folder (using \fIinc\fR or \fIrcvstore\fR), the +new messages will also be added to all the sequences named in this +profile entry. For example, a profile entry of + +.ti +.5i +Unseen\-Sequence:\^ unseen + +directs \fIinc\fR to add new messages to the sequence \*(lqunseen\*(rq. +Unlike the behavior of the \*(lqPrevious\-Sequence\*(rq entry in the +profile, however, the sequence(s) will \fBnot\fR be zeroed by \fIinc\fP. + +Similarly, whenever \fIshow\fR, \fImhshow\fR, \fInext\fR, or +\fIprev\fR\^ displays a message, that message will be removed from +any sequences named by the \*(lqUnseen\-Sequence\*(rq entry in the +profile. + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^/context~^The user context +^/\&.mh\(rusequences~^File for public sequences +.Pr +^mh-sequences:~^Name of file to store public sequences +.Ps +^Sequence\-Negation:~^To designate messages not in a sequence +.Ps +^Previous\-Sequence:~^The last message specification given +.Ps +^Unseen\-Sequence:~^Those messages not yet seen by the user +.Sa +flist(1), mark(1), pick(1), mh-profile(5) +.De +None +.Co +All +.En diff --git a/man/mh-tailor.man b/man/mh-tailor.man new file mode 100644 index 0000000..71cf0bc --- /dev/null +++ b/man/mh-tailor.man @@ -0,0 +1,287 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MH-TAILOR %manext5% MH.6.8 [%nmhversion%] +.SH NAME +mh-tailor, mts.conf \- mail transport customization for nmh message handler + +.SH SYNOPSIS +.in +.5i +.ti -.5i +\fI%etcdir%/mts.conf\fP +.in -.5i +.SH DESCRIPTION +The file %etcdir%/mts.conf defines run-time options for those \fInmh\fR +programs which interact (in some form) with the message transport system. +At present, these (user) programs are: \fIap\fR, \fIconflict\fR, +\fIinc\fR, \fImsgchk\fR, \fImsh\fR, \fIpost\fR, \fIrcvdist\fR, and +\fIrcvpack\fR. + +Each option should be given on a single line. Blank lines and lines +which begin with `#' are ignored. The options available along with +default values and a description of their meanings are listed below: + +.in +.5i +.ti -.5i +localname: +.br +The hostname \fInmh\fR considers local. It should typically be a fully +qualified hostname. If this is not set, depending on the version of +UNIX you're running, \fInmh\fR will query the system for this value +(e.g., uname, gethostname, etc.), and attempt to fully qualify this +value. + +If you are using POP to retrieve new messages, you may want to set this +value to the name of the POP server, so that outgoing message appear to +have originated on the POP server. + +.ti -.5i +localdomain: +.br +If this is set, a `.' followed by this string will be appended to your +hostname. + +This should only be needed, if for some reason \fInmh\fR is not able to +fully qualify the hostname returned by the system (e.g., uname, +gethostname, etc.). + +.ti -.5i +clientname: +.br +This option specifies the host name that \fInmh\fP will give in the +SMTP \fBHELO\fP (and \fBEHLO\fP) command, when posting mail. If not +set, the default is to use the host name that \fInmh\fR considers local +(see \*(lqlocalname\*(rq above). If this option is set, but empty, no +\fBHELO\fP command will be given. + +.sp +Although the \fBHELO\fP command is required by RFC\-821, many SMTP servers +do not require it. Early versions of SendMail will fail if the hostname +given in the \fBHELO\fP command is the local host. Later versions of +SendMail will complain if you omit the \fBHELO\fP command. If you run +SendMail, find out what your system expects and set this field if needed. + +.ti -.5i +systemname: +.br +This option is only used for UUCP mail. It specifies the name of the +local host in the \fIUUCP\fR \*(lqdomain\*(rq. If not set, depending +on the version of UNIX you're running, \fInmh\fR will query the system +for this value. This has no equivalent in the \fInmh\fR configuration +file. + +.ti -.5i +mmdfldir: %mailspool% +.br +The directory where maildrops are kept. If this option is set, but empty, +the user's home directory is used. This overrides the default value +chosen at the time of compilation. + +.ti -.5i +mmdflfil: +.br +The name of the maildrop file in the directory where maildrops are kept. +If this is empty, the user's login name is used. This overrides the default +value (which is empty). + +.ti -.5i +mmdelim1: \\001\\001\\001\\001\\n +.br +The beginning-of-message delimiter for maildrops. + +.ti -.5i +mmdelim2: \\001\\001\\001\\001\\n +.br +The end-of-message delimiter for maildrops. + +.ti -.5i +mmailid: 0 +.br +If this is non-zero, then activate support for MMailids (username +masquerading). When this is activated, \fInmh\fR will check if the +pw_gecos field in the password file has the form + +.ti +.5i +Full Name + +If the pw_gecos field has this form, then the internal \fInmh\fR +routines that find the username and full name of a user will return +\*(lqfakeusername\*(rq and \*(lqFull Name\*(rq respectively. If +the pw_gecos field for a user is not of this form, there will be +no username masquerading for that user. + +This facility is useful if you are using POP, and wish for messages +that are sent by users to appear to originate from the username of +their POP account, rather than their username on the local machine. + +.ti -.5i +maildelivery: %libdir%/maildelivery +.br +The name of the system-wide default \fI\&.maildelivery\fR file. +See \fIslocal\fR\0(1) for the details. + +.ti -.5i +everyone: 200 +.br +The highest user-id which should NOT receive mail addressed to +\*(lqeveryone\*(rq. + +.ti -.5i +noshell: +.br +If set, then each user-id greater than \*(lqeveryone\*(rq that has a +login shell equivalent to the given value (e.g., \*(lq/bin/csh\*(rq) +indicates that mail for \*(lqeveryone\*(rq should not be sent to them. +This is useful for handling admin, dummy, and guest logins. + +.in -.5i +.Uh "SMTP support" +These options are only available if you compiled \fInmh\fP with the +\*(lq/smtp\*(rq support. + +.in +.5i +.ti -.5i +hostable: %etcdir%/hosts +.br +The exceptions file for /etc/hosts used by \fIpost\fR to try to find +official names. The format of this file is quite simple: + +.in +.5i +1. Comments are surrounded by sharp (`#') and newline. +.br +2. Words are surrounded by white space. +.br +3. The first word on the line is the official name of a host. +.br +4. All words following the official names are aliases for that host. +.in -.5i + +.ti -.5i +servers: localhost \\01localnet +.br +A lists of hosts and networks which to look for SMTP servers when +posting local mail. It turns out this is a major win for hosts which +don't run an message transport system. The value of \*(lqservers\*(rq +should be one or more items. Each item is the name of either a host +or a net (in the latter case, precede the name of the net by a \\01). +This list is searched when looking for a smtp server to post mail. +If a host is present, the SMTP port on that host is tried. If a net +is present, the SMTP port on each host in that net is tried. Note that +if you are running with the BIND code, then any networks specified are +ignored (sorry, the interface went away under BIND). + +.in -.5i +.Uh "SendMail" +This option is only available if you compiled \fInmh\fP to use +\fISendMail\fP as your delivery agent. + +.in +.5i +.ti -.5i +sendmail: %sendmailpath% +.br +The pathname to the \fIsendmail\fR program. + +.in -.5i +.Uh "Post Office Protocol" +This option is only available if you have compiled \fInmh\fP with POP +support enabled (i.e., \*(lq--enable-nmh-pop\*(rq). + +.in +.5i +.ti -.5i +pophost: +.br +The name of the default POP service host. If this is not set, then +\fInmh\fR looks in the standard maildrop areas for waiting mail, otherwise +the named POP service host is consulted. + +.in -.5i +.Uh "BBoards Delivery" +This option is only available if you compiled \fInmh\fP with +\*(lqbbdelivery:\ on\*(rq. + +.in +.5i +.ti -.5i +bbdomain: +.br +The local BBoards domain (a UCI hack). + +.in -.5i +.Uh "BBoards & The POP" +These options are only available if you compiled \fInmh\fP with +\*(lqbboards:\ pop\*(rq and \*(lqpop:\ on\*(rq. + +.in +.5i +.ti -.5i +popbbhost: +.br +The POP service host which also acts as a BBoard server. This variable +should be set on the POP BBoards client host. + +.ti -.5i +popbbuser: +.br +The guest account on the POP/BB service host. This should be a different +login ID than either the POP user or the BBoards user. (The user-id +\*(lqftp\*(rq is highly recommended.) This variable should be set on +both the POP BBoards client and service hosts. + +.ti -.5i +popbblist: %etcdir%/hosts.popbb +.br +A file containing of lists of hosts that are allowed to use the POP +facility to access BBoards using the guest account. If this file is not +present, then no check is made. This variable should be set on the POP +BBoards service host. + +.in -.5i +.if n .ne 8 +.Uh "BBoards & The NNTP" +This option is only available if you compiled \fInmh\fP with +\*(lqbboards:\ nntp\*(rq and \*(lqpop:\ on\*(rq. + +.in +.5i +.ti -.5i +nntphost: +.br +The host which provides the NNTP service. This variable should be set +on the NNTP BBoards client host. + +.in -.5i +.Uh "File Locking" +A few words on locking: \fInmh\fR has several methods for creating locks +on files. When configuring \fInmh\fR, you will need to decide on the +locking style and locking directory (if any). The first controls the +method of locking, the second says where lock files should be created. + +To configure \fInmh\fR for kernel locking, define \fBFLOCK_LOCKING\fP if +you want to use the \fIflock\fP system call; define \fBLOCKF_LOCKING\fP if +you want to use the \fIlockf\fP system call; or define \fBFCNTL_LOCKING\fP +if you want to use the \fIfcntl\fP system call for kernel-level locking. + +Instead of kernel locking, you can configure \fInmh\fR to use dot +locking by defining \fBDOT_LOCKING\fP. Dot locking specifies that +a file should be created whose existence means \*(lqlocked\*(rq and +whose non-existence means \*(lqunlocked\*(rq. The name of this file is +constructed by appending \*(lq.lock\*(rq to the name of the file being +locked. If \fBLOCKDIR\fP is not specified, lock files will be created +in the directory where the file being locked resides. Otherwise, lock +files will be created in the directory specified by \fBLOCKDIR\fP. + +Prior to installing \fInmh\fR, you should see how locking is done at +your site, and set the appropriate values. + +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +.Pr +None +.Sa +mh\-mts(8) +.De +As listed above +.Co +None +.En diff --git a/man/mhbuild.man b/man/mhbuild.man new file mode 100644 index 0000000..497ff3d --- /dev/null +++ b/man/mhbuild.man @@ -0,0 +1,580 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHBUILD %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhbuild \- translate MIME composition draft +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhbuild file +.br +\%[\-list] \%[-nolist] +\%[\-realsize] \%[\-norealsize] +.br +\%[\-headers] \%[\-noheaders] +\%[\-ebcdicsafe] \%[\-noebcdicsafe] +.br +\%[\-rfc934mode] \%[\-norfc934mode] +\%[\-verbose] \%[\-noverbose] +.br +\%[\-check] \%[\-nocheck] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +The \fImhbuild\fR command will translate a MIME composition draft into +a valid MIME message. + +\fImhbuild\fR creates multi-media messages as specified in RFC\-2045 +thru RFC\-2049. Currently \fImhbuild\fR only supports encodings in +message bodies, and does not support the encoding of message headers as +specified in RFC\-2047. + +If you specify the name of the composition file as \*(lq-\*(rq, +then \fImhbuild\fR will accept the composition draft on the standard +input. If the translation of this input is successful, \fImhbuild\fR +will output the new MIME message to the standard output. This argument +must be the last argument on the command line. + +Otherwise if the file argument to \fImhbuild\fR is the name of a valid +composition file, and the translation is successful, \fImhbuild\fR will +replace the original file with the new MIME message. It will rename +the original file to start with the \*(lq,\*(rq character and end with the +string \*(lq.orig\*(rq, e.g., if you are editing the file \*(lqdraft\*(rq, +it will be renamed to \*(lq,draft.orig\*(rq. This allows you to easily +recover the \fImhbuild\fR input file. + +.Uh "Listing the Contents" +The `\-list' switch tells \fImhbuild\fR to list the table of contents +associated with the MIME message that is created. + +The `\-headers' switch indicates +that a one-line banner should be displayed above the listing. The +`\-realsize' switch tells \fImhbuild\fR to evaluate the \*(lqnative\*(rq +(decoded) format of each content prior to listing. This provides an +accurate count at the expense of a small delay. If the `\-verbose' switch +is present, then the listing will show any \*(lqextra\*(rq information +that is present in the message, such as comments in the Content-Type header. + +.Uh "Translating the Composition File" +\fImhbuild\fR is essentially a filter to aid in the composition of MIME +messages. \fImhbuild\fR will convert an +\fImhbuild\fR \*(lqcomposition file\*(rq +into a valid MIME message. A \fImhbuild\fR \*(lqcomposition file\*(rq +is just a file containing plain text that is interspersed +with various \fImhbuild\fR directives. When this file is processed +by \fImhbuild\fR, the various directives will be expanded to the +appropriate content, and will be encoded according to the MIME standards. +The resulting MIME message can then be sent by electronic mail. + +The formal syntax for a \fImhbuild\fR composition file is defined at the +end of this document, but the ideas behind this format are not complex. +Basically, the body contains one or more contents. A content consists of +either a directive, indicated with a \*(lq#\*(rq as the first character +of a line; or, plaintext (one or more lines of text). The continuation +character, \*(lq\\\*(lq, may be used to enter a single directive on more +than one line, e.g., +.sp +.nf +.in +.5i +#image/png \\ + /home/foobar/junk/picture.png +.in -.5i +.fi +.sp +There are four kinds of directives: \*(lqtype\*(rq directives, which +name the type and subtype of the content; \*(lqexternal-type\*(rq +directives, which also name the type and subtype of the content; the +\*(lqmessage\*(rq directive (#forw), which is used to forward one or +more messages; and, the \*(lqbegin\*(rq directive (#begin), which is +used to create a multipart content. + +The \*(lqtype\*(rq directive is used to directly specify the type and +subtype of a content. You may only specify discrete types in this manner +(can't specify the types multipart or message with this directive). +You may optionally specify the name of a file containing the contents +in \*(lqnative\*(rq (decoded) format. If this filename starts with the +\*(lq|\*(rq character, then it represents a command to execute whose +output is captured accordingly. +For example, +.sp +.nf +.in +.5i +#audio/basic |raw2audio -F < /usr/lib/sound/giggle.au +.in -.5i +.fi +.sp +If a filename is not given, \fImhbuild\fR will look for information in the +user's profile to determine how the different contents should be composed. +This is accomplished by consulting a composition string, and executing +it under \fB/bin/sh\fR, with the standard output set to the content. +If the `\-verbose' switch is given, \fImhbuild\fR will echo any commands +that are used to create contents in this way. +.ne 13 +The composition string may contain the following escapes: +.sp +.nf +.in +.5i +.ta \w'%P 'u +%a Insert parameters from directive +%f Insert filename containing content +%F %f, and stdout is not re-directed +%s Insert content subtype +%% Insert character % +.re +.in -.5i +.fi +.sp + +First, +\fImhbuild\fR will look for an entry of the form: +.sp +.in +.5i +mhbuild-compose-/ +.in -.5i +.sp +to determine the command to use to compose the content. If this isn't +found, \fImhbuild\fR will look for an entry of the form: +.sp +.in +.5i +mhbuild-compose- +.in -.5i +.sp +to determine the composition command. + +If this isn't found, \fImhbuild\fR +will complain. + +An example entry might be: +.sp +.in +.5i +mhbuild-compose-audio/basic: record | raw2audio -F +.in -.5i +.sp +Because commands like these will vary, depending on the display +environment used for login, composition strings for different +contents should probably be put in the file specified by the +\fB$MHBUILD\fR environment variable, instead of directly in your +user profile. + +The \*(lqexternal-type\*(rq directives are used to provide a MIME +reference to a content, rather than enclosing the contents itself +(for instance, by specifying an ftp site). Hence, instead of +providing a filename as with the type directives, external-parameters +are supplied. These look like regular parameters, so they must be +separated accordingly. For example, +.sp +.nf +.in +.5i +#@application/octet-stream; \\ + type=tar; \\ + conversions=compress \\ + [this is the nmh distribution] \\ + name="nmh.tar.gz"; \\ + directory="/pub/nmh"; \\ + site="ftp.math.gatech.edu"; \\ + access-type=anon-ftp; \\ + mode="image" +.in -.5i +.fi +.sp +You must give a description string to separate the content parameters +from the external-parameters (although this string may be empty). +This description string is specified by enclosing it within +\*(lq[]\*(rq. +.ne 19 +These parameters are of the form: +.sp +.nf +.in +.5i +.ta \w'access-type= 'u +access-type= usually \fIanon-ftp\fR or \fImail-server\fR +name= filename +permission= read-only or read-write +site= hostname +directory= directoryname (optional) +mode= usually \fIascii\fR or \fIimage\fR (optional) +size= number of octets +server= mailbox +subject= subject to send +body= command to send for retrieval +.re +.in -.5i +.fi +.sp + +The \*(lqmessage\*(rq directive (#forw) is used to specify a message or +group of messages to include. You may optionally specify the name of +the folder and which messages are to be forwarded. If a folder is not +given, it defaults to the current folder. Similarly, if a message is not +given, it defaults to the current message. Hence, the message directive +is similar to the \fIforw\fR\0(1) command, except that the former uses +the MIME rules for encapsulation rather than those specified in RFC\-934. +For example, +.sp +.nf +.in +.5i +#forw +inbox 42 43 99 +.in -.5i +.fi +.sp +If you include a single message, it will be included directly as a content +of type \*(lqmessage/rfc822\*(rq. If you include more than one message, +then \fImhbuild\fR will add a content of type \*(lqmultipart/digest\*(rq +and include each message as a subpart of this content. + +If you are using this directive to include more than one message, you +may use the `\-rfc934mode' switch. This switch will indicate that +\fImhbuild\fR should attempt to utilize the MIME encapsulation rules +in such a way that the \*(lqmultipart/digest\*(rq that is created +is (mostly) compatible with the encapsulation specified in RFC\-934. +If given, then RFC\-934 compliant user-agents should be able to burst the +message on reception\0--\0providing that the messages being encapsulated +do not contain encapsulated messages themselves. The drawback of this +approach is that the encapsulations are generated by placing an extra +newline at the end of the body of each message. + +The \*(lqbegin\*(rq directive is used to create a multipart content. +When using the \*(lqbegin\*(rq directive, you must specify at least one +content between the begin and end pairs. +.sp +.nf +.in +.5i +#begin +This will be a multipart with only one part. +#end +.in -.5i +.fi +.sp +If you use multiple directives in a composition draft, \fImhbuild\fR will +automatically encapsulate them inside a multipart content. Therefore the +\*(lqbegin\*(rq directive is only necessary if you wish to use nested +multiparts, or create a multipart message containing only one part. + +For all of these directives, the user may include a brief description +of the content between the \*(lq[\*(rq character and the \*(lq]\*(rq +character. This description will be copied into the +\*(lqContent-Description\*(rq header when the directive is processed. +.sp +.nf +.in +.5i +#forw [important mail from Bob] +bob 1 2 3 4 5 +.in -.5i +.fi +.sp +By default, \fImhbuild\fR will generate a unique \*(lqContent-ID:\*(rq for +each directive; however, the user may override this by defining the ID +using the \*(lq<\*(rq and \*(lq>\*(rq characters. + +In addition to the various directives, plaintext can be present. +Plaintext is gathered, until a directive is found or the draft is +exhausted, and this is made to form a text content. If the plaintext +must contain a \*(lq#\*(rq at the beginning of a line, simply double it, +.ne 6 +e.g., +.sp +.in +.5i +##when sent, this line will start with only one # +.in -.5i +.sp +If you want to end the plaintext prior to a directive, e.g., to have two +plaintext contents adjacent, simply insert a line containing a single +\*(lq#\*(rq character, +.ne 10 +e.g., +.sp +.nf +.in +.5i +this is the first content +# +and this is the second +.in -.5i +.fi +.sp +Finally, +if the plaintext starts with a line of the form: +.sp +.in +.5i +Content-Description: text +.in -.5i +.sp +then this will be used to describe the plaintext content. +You MUST follow this line with a blank line before starting +your text. + +By default, plaintext is captured as a text/plain content. You can +override this by starting the plaintext with \*(lq#<\*(rq followed by +a content-type specification. For example, +.ne 11 +e.g., +.sp +.nf +.in +.5i +#" ] + [ "[" description "]" ] + [ filename ] + EOL + + | "#@" type "/" subtype + 0*(";" attribute "=" value) + [ "(" comment ")" ] + [ "<" id ">" ] + [ "[" description "]" ] + external-parameters + EOL + + | "#forw" + [ "<" id ">" ] + [ "[" description "]" ] + [ "+"folder ] [ 0*msg ] + EOL + + | "#begin" + [ "<" id ">" ] + [ "[" description "]" ] + [ "alternative" + | "parallel" + | something-else ] + EOL + 1*body + "#end" EOL + + plaintext ::= [ "Content-Description:" + description EOL EOL ] + 1*line + [ "#" EOL ] + + | "#<" type "/" subtype + 0*(";" attribute "=" value) + [ "(" comment ")" ] + [ "[" description "]" ] + EOL + 1*line + [ "#" EOL ] + + line ::= "##" text EOL + -- interpreted as "#"text EOL + | text EOL +.in -.5i +.fi +.sp +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^$MHBUILD~^Additional profile entries +^%etcdir%/mhn.defaults~^System default MIME profile entries +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^mhbuild-compose-*~^Template for composing contents +.Sa +mhlist(1), mhshow(1), mhstore(1) +.br +RFC\-934: +.br + \fIProposed Standard for Message Encapsulation\fR, +.br +RFC\-2045: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part One: +.br + Format of Internet Message Bodies\fR, +.br +RFC\-2046: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Two: +.br + Media Types\fR, +.br +RFC\-2047: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Three: +.br + Message Header Extensions for Non-ASCII Text\fR, +.br +RFC\-2048: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Four: +.br + Registration Procedures\fR, +.br +RFC\-2049: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Five: +.br + Conformance Criteria and Examples\fR. +.De +`\-headers' +.Ds +`\-realsize' +.Ds +`\-norfc934mode' +.Ds +`\-nocheck' +.Ds +`\-noebcdicsafe' +.Ds +`\-noverbose' +.Co +If a folder is given, it will become the current folder. The last +message selected will become the current message. +.En diff --git a/man/mhl.man b/man/mhl.man new file mode 100644 index 0000000..b81415c --- /dev/null +++ b/man/mhl.man @@ -0,0 +1,246 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHL %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhl \- produce formatted listings of nmh messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/mhl +\%[\-bell] \%[\-nobell] +\%[\-clear] +.br +\%[\-noclear] +\%[\-folder\ +folder] +\%[\-form\ formfile] +.br +\%[\-length\ lines] \%[\-width\ columns] +\%[\-moreproc\ program] +.br +\%[\-nomoreproc] +\%[files\ ...] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIMhl\fR is a \fInmh\fR command for filtering and/or displaying text +messages. It is the default method of displaying text messages for +\fInmh\fR (it is the default \fIshowproc\fR). + +As with \fImore\fR, each of the messages specified as arguments (or +the standard input) will be output. If more than one message file is +specified, the user will be prompted prior to each one, and a +or will begin the output, with clearing the screen (if +appropriate), and (usually CTRL\-D) suppressing the screen clear. +An (usually CTRL\-C) will abort the current message output, +prompting for the next message (if there is one), and a (usually +CTRL-\\) will terminate the program (without core dump). + +The `\-bell' option tells \fImhl\fR to ring the terminal's bell at the +end of each page, while the `\-clear' option tells \fImhl\fR to clear the +scree at the end of each page (or output a formfeed after each message). +Both of these switches (and their inverse counterparts) take effect only +if the profile entry \fImoreproc\fR is defined but empty, and \fImhl\fR +is outputting to a terminal. If the \fImoreproc\fR entry is defined and +non-empty, and \fImhl\fR is outputting to a terminal, then \fImhl\fR will +cause the \fImoreproc\fR to be placed between the terminal and \fImhl\fR +and the switches are ignored. Furthermore, if the `\-clear' switch is +used and \fImhl's\fR output is directed to a terminal, then \fImhl\fR +will consult the \fB$TERM\fR and \fB$TERMCAP\fR environment variables +to determine the user's terminal type in order to find out how to clear +the screen. If the `\-clear' switch is used and \fImhl's\fR output is +not directed to a terminal (e.g., a pipe or a file), then \fImhl\fR will +send a formfeed after each message. + +To override the default \fImoreproc\fR and the profile entry, use the +`\-moreproc\ program' switch. Note that \fImhl\fR will never start a +\fImoreproc\fR if invoked on a hardcopy terminal. + +The `\-length\ length' and `\-width\ width' switches set the screen +length and width, respectively. These default to the values indicated +by \fB$TERMCAP\fR, if appropriate, otherwise they default to 40 and +80, respectively. + +The default format file used by \fImhl\fR is called \*(lqmhl.format\*(rq. +\fImhl\fR will first search for this file in the user's \fInmh\fR +directory, and will then search in the directory %etcdir%. This default +can be changed by using the `\-form\ formatfile' switch. + +Finally, the `\-folder\ +folder' switch sets the \fInmh\fR folder name, +which is used for the \*(lqmessagename:\*(rq field described below. The +environment variable \fB$mhfolder\fR is consulted for the default value, +which \fIshow\fR, \fInext\fR, and \fIprev\fR initialize appropriately. + +\fIMhl\fR operates in two phases: 1) read and parse the format file, and +2) process each message (file). During phase 1, an internal description +of the format is produced as a structured list. In phase 2, this list +is walked for each message, outputting message information under the +format constraints from the format file. + +The format file can contain information controlling screen clearing, +screen size, wrap\-around control, transparent text, component ordering, +and component formatting. Also, a list of components to ignore may be +specified, and a couple of \*(lqspecial\*(rq components are defined +to provide added functionality. Message output will be in the order +specified by the order in the format file. + +Each line of a format file has one of the following forms: + + ;comment + :cleartext + variable[,variable...] + component:[variable,...] + +A line beginning with a `;' is a comment, and is ignored. +A line beginning with a `:' is clear text, +and is output exactly as is. +A line containing only a `:' produces a blank line in the output. +A line beginning with \*(lqcomponent:\*(rq defines the format for the specified +component, +and finally, remaining lines define the global environment. + +For example, the line: + +.ti +.5i +width=80,length=40,clearscreen,overflowtext="***",overflowoffset=5 + +defines the screen size to be 80 columns by 40 rows, specifies that the +screen should be cleared prior to each page, that the overflow indentation +is 5, and that overflow text should be flagged with \*(lq***\*(rq. + +Following are all of the current variables and their arguments. If they +follow a component, they apply only to that component, otherwise, their +affect is global. Since the whole format is parsed before any output +processing, the last global switch setting for a variable applies to +the whole message if that variable is used in a global context (i.e., +bell, clearscreen, width, length). + +.nf +.in +.5i +.ta \w'noclearscreen 'u +\w'integer/G 'u +\fIvariable\fR \fItype\fR \fIsemantics\fR +width integer screen width or component width +length integer screen length or component length +offset integer positions to indent \*(lqcomponent: \*(rq +overflowtext string text to use at the beginning of an + overflow line +overflowoffset integer positions to indent overflow lines +compwidth integer positions to indent component text + after the first line is output +uppercase flag output text of this component in all + upper case +nouppercase flag don't uppercase +clearscreen flag/G clear the screen prior to each page +noclearscreen flag/G don't clearscreen +bell flag/G ring the bell at the end of each page +nobell flag/G don't bell +component string/L name to use instead of \*(lqcomponent\*(rq for + this component +nocomponent flag don't output \*(lqcomponent: \*(rq for this + component +center flag center component on line (works for + one\-line components only) +nocenter flag don't center +leftadjust flag strip off leading whitespace on each + line of text +noleftadjust flag don't leftadjust +compress flag change newlines in text to spaces +nocompress flag don't compress +split flag don't combine multiple fields into + a single field +nosplit flag combine multiple fields into + a single field +newline flag print newline at end of components + (this is the default) +nonewline flag don't print newline at end of components +formatfield string format string for this component + (see below) +decode flag decode text as RFC-2047 encoded + header field +addrfield flag field contains addresses +datefield flag field contains dates +.re +.in -.5i +.fi + +To specify the value of integer\-valued and string\-valued variables, +follow their name with an equals\-sign and the value. Integer\-valued +variables are given decimal values, while string\-valued variables +are given arbitrary text bracketed by double\-quotes. If a value is +suffixed by \*(lq/G\*(rq or \*(lq/L\*(rq, then its value is useful in +a global\-only or local\-only context (respectively). + +A line of the form: + + ignores=component,... + +specifies a list of components which are never output. + +The component \*(lqMessageName\*(rq (case\-insensitive) will output the +actual message name (file name) preceded by the folder name if one is +specified or found in the environment. The format is identical to that +produced by the `\-header' option to \fIshow\fR. + +The component \*(lqExtras\*(rq will output all of the components of the +message which were not matched by explicit components, or included in +the ignore list. If this component is not specified, an ignore list is +not needed since all non\-specified components will be ignored. + +If \*(lqnocomponent\*(rq is NOT specified, then the component name will +be output as it appears in the format file. + +The default format file is: + +.nf +.in +.5i +.ne 15 +.eo +.so %etcdir%/mhl.format +.ec +.in -.5i +.fi + +The variable \*(lqformatfield\*(rq specifies a format string (see +\fImh\-format\fR\0(5)). The flag variables \*(lqaddrfield\*(rq and +\*(lqdatefield\*(rq (which are mutually exclusive), tell \fImhl\fR +to interpret the escapes in the format string as either addresses or +dates, respectively. + +By default, \fImhl\fR does not apply any formatting string to fields +containing address or dates (see \fImh\-mail\fR\0(5) for a list of these +fields). Note that this results in faster operation since \fImhl\fR +must parse both addresses and dates in order to apply a format string +to them. If desired, \fImhl\fR can be given a default format string for +either address or date fields (but not both). To do this, on a global +line specify: either the flag addrfield or datefield, along with the +appropriate formatfield variable string. +.Fi +^%etcdir%/mhl.format~^The message template +^or /mhl.format~^Rather than the standard template +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^moreproc:~^Program to use as interactive front\-end +.Sa +show(1), ap(8), dp(8) +.De +`\-bell' +.Ds +`\-noclear' +.Ds +`\-length 40' +.Ds +`\-width 80' +.Co +None +.Bu +There should be some way to pass `bell' and `clear' information to the +front\-end. + +The \*(lqnonewline\*(rq option interacts badly with \*(lqcompress\*(rq +and \*(lqsplit\*(rq. +.En diff --git a/man/mhlist.man b/man/mhlist.man new file mode 100644 index 0000000..1cd7d0e --- /dev/null +++ b/man/mhlist.man @@ -0,0 +1,167 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHLIST %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhlist \- list information about MIME messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhlist \%[+folder] \%[msgs] \%[\-file file] +.br +\%[\-part number]... \%[\-type content]... +.br +\%[\-headers] \%[\-noheaders] +\%[\-realsize] \%[\-norealsize] +.br +\%[\-rcache policy] \%[\-wcache policy] +\%[\-check] \%[\-nocheck] +.br +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] +.in -.5i + +.SH DESCRIPTION +The \fImhlist\fR command allows you to list information (essentially +a table of contents) about the various parts of a collection of +MIME (multi-media) messages. + +\fImhlist\fR manipulates MIME (multi-media messages) as specified +in RFC\-2045 thru RFC\-2049. + +The `\-headers' switch indicates that a one-line banner should be +displayed above the listing. + +The `\-realsize' switch tells \fImhlist\fR to evaluate the +\*(lqnative\*(rq (decoded) format of each content prior to listing. +This provides an accurate count at the expense of a small delay. + +If the `\-verbose' switch is present, then the listing will show +any \*(lqextra\*(rq information that is present in the message, +such as comments in the Content-Type header. + +The option `\-file\ file' directs \fImhlist\fR to use the specified +file as the source message, rather than a message from a folder. +If you specify this file as \*(lq-\*(rq, then \fImhlist\fR will +accept the source message on the standard input. Note that the +file, or input from standard input should be a validly formatted +message, just like any other \fInmh\fR message. It should \fBNOT\fR +be in mail drop format (to convert a file in mail drop format to +a folder of \fInmh\fR messages, see \fIinc\fR\0(1)). + +By default, \fImhlist\fR will list information about the entire +message (all of its parts). By using the `\-part' and `\-type' +switches, you may limit the scope of this command to particular +subparts (of a multipart content) and/or particular content types. + +A part specification consists of a series of numbers separated by dots. +For example, in a multipart content containing three parts, these +would be named as 1, 2, and 3, respectively. If part 2 was also a +multipart content containing two parts, these would be named as 2.1 and +2.2, respectively. Note that the `\-part' switch is effective for only +messages containing a multipart content. If a message has some other +kind of content, or if the part is itself another multipart content, the +`\-part' switch will not prevent the content from being acted upon. + +A content specification consists of a content type and a subtype. +The initial list of \*(lqstandard\*(rq content types and subtypes can +be found in RFC\-2046. +.ne 18 +A list of commonly used contents is briefly reproduced here: +.sp +.nf +.in +.5i +.ta \w'application 'u +Type Subtypes +---- -------- +text plain, enriched +multipart mixed, alternative, digest, parallel +message rfc822, partial, external-body +application octet-stream, postscript +image jpeg, gif, png +audio basic +video mpeg +.re +.in -.5i +.fi +.sp +A legal MIME message must contain a subtype specification. +.PP +To specify a content, regardless of its subtype, just use the +name of the content, e.g., \*(lqaudio\*(rq. To specify a specific +subtype, separate the two with a slash, e.g., \*(lqaudio/basic\*(rq. +Note that regardless of the values given to the `\-type' switch, a +multipart content (of any subtype listed above) is always acted upon. +Further note that if the `\-type' switch is used, and it is desirable to +act on a message/external-body content, then the `\-type' switch must +be used twice: once for message/external-body and once for the content +externally referenced. + +.Uh "Checking the Contents" +The `\-check' switch tells \fImhlist\fR to check each content for an +integrity checksum. If a content has such a checksum (specified as a +Content-MD5 header field), then \fImhlist\fR will attempt to verify the +integrity of the content. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +mhbuild(1), mhshow(1), mhstore(1), sendfiles(1) +.br +RFC\-2045: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part One: +.br + Format of Internet Message Bodies\fR, +.br +RFC\-2046: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Two: +.br + Media Types\fR, +.br +RFC\-2047: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Three: +.br + Message Header Extensions for Non-ASCII Text\fR, +.br +RFC\-2048: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Four: +.br + Registration Procedures\fR, +.br +RFC\-2049: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Five: +.br + Conformance Criteria and Examples\fR. +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-nocheck' +.Ds +`\-headers' +.Ds +`\-realsize' +.Ds +`\-rcache ask' +.Ds +`\-wcache ask' +.Ds +`\-noverbose' +.Co +If a folder is given, it will become the current folder. The last +message selected will become the current message. +.En diff --git a/man/mhmail.man b/man/mhmail.man new file mode 100644 index 0000000..5902b7c --- /dev/null +++ b/man/mhmail.man @@ -0,0 +1,71 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHMAIL %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhmail \- send or read mail +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhmail +\%[ +addrs\ ... +\%[\-body\ text] +\%[\-cc\ addrs\ ...] +.br +\%[\-from\ addr] +\%[\-subject subject]] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fImhmail\fR is intended as a replacement for the standard Berkeley +mail program (\fImail\fR(1) or \fImailx\fR(1)), which is compatible +with \fInmh\fR. This program is intended for the use of programs such +as \fIcron\fR(1), which expect to send mail automatically to various +users. It is also used by various \fInmh\fR commands to mail various +error notifications. Although \fImhmail\fR can be used interactively, +it is recommended that \fIcomp\fR(1) and \fIsend\fR(1) be used instead +to send messages. + +When invoked without arguments, it simply invokes \fIinc\fR(1) to +incorporate new messages from the user's maildrop. When one or more users +is specified, a message is read from the standard input and spooled to +a temporary file. \fImhmail\fR then invokes \fIpost\fR(8) with the +name of the temporary file as its argument to deliver the message to +the specified user. + +The `\-subject\ subject' switch can be used to specify the +\*(lqSubject:\*(rq field of the message. + +By default, \fImhmail\fR will read the message to be sent from the +standard input. You can specify the text of the message at the command +line with the `\-body\ text' switch. If the standard input has zero +length, \fImhmail\fR will not send the message. You can use the switch +`\-body\ ""' to force an empty message. + +Normally, addresses appearing as arguments are put in the \*(lqTo:\*(rq +field. If the `\-cc' switch is used, all addresses following it are +placed in the \*(lqcc:\*(rq field. + +By using `\-from\ addr', you can specify the \*(lqFrom:\*(rq header of +the draft. Naturally, \fIpost\fR will fill\-in the \*(lqSender:\*(rq +header correctly. +.Fi +^%bindir%/inc~^Program to incorporate maildrop into folder +^%libdir%/post~^Program to deliver a message +^/tmp/mhmail*~^Temporary copy of message +.Pr +None +.Sa +inc(1), post(8) +.De +None +.Co +If \fIinc\fR is invoked, then \fIinc\fR's context changes occur. +.En diff --git a/man/mhn.man b/man/mhn.man new file mode 100644 index 0000000..e4719dc --- /dev/null +++ b/man/mhn.man @@ -0,0 +1,715 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHN %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhn \- display/list/store/cache MIME messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhn \%[+folder] \%[msgs] \%[\-file file] +.br +\%[\-part number]... \%[\-type content]... +.br +\%[\-show] \%[\-noshow] +\%[\-list] \%[-nolist] +.br +\%[\-store] \%[\-nostore] +\%[\-cache] \%[\-nocache] +.br +\%[\-headers] \%[\-noheaders] +\%[\-realsize] \%[\-norealsize] +.br +\%[\-serialonly] \%[\-noserialonly] +\%[\-form formfile] +.br +\%[\-pause] \%[\-nopause] +\%[\-auto] \%[\-noauto] +.br +\%[\-rcache policy] \%[\-wcache policy] +\%[\-check] \%[\-nocheck] +.br +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] + +.ti .5i +mhn \-build\ file +.br +\%[\-ebcdicsafe] \%[\-noebcdicsafe] +.br +\%[\-rfc934mode] \%[\-norfc934mode] +.in -.5i + +.SH DESCRIPTION +MHN SHOULD BE CONSIDERED DEPRECATED. IT IS RETAINED FOR THE PURPOSE +OF BACKWARD COMPATIBILITY, BUT EVERYONE SHOULD MIGRATE TO USING THE +COMMANDS MHSHOW, MHSTORE, AND MHLIST. CHECK THE INDIVIDUAL MAN PAGES +FOR DETAILS. + +The \fImhn\fR command allows you to display, list, store, or cache the +contents of a MIME (multi-media) messages. + +\fImhn\fR manipulates multi-media messages as specified in RFC\-2045 +thru RFC\-2049. Currently \fImhn\fR only supports encodings in message +bodies, and does not support the encoding of message headers as specified +in RFC\-2047. + +The switches `\-list', `\-show', `\-store', and `-cache' direct +the operation of \fImhn\fR. Only one of these switches may be used +at a time. These switches are used to operate on the content of +each of the named messages. By using the `\-part' and `\-type' +switches, you may limit the scope of the given operation to particular +subparts (of a multipart content) and/or particular content types. + +The switch `\-build' is used to construct a MIME message. It is +for backward compatibility and instructs \fImhn\fR to execute the +\fImhbuild\fR command. It is preferred that you use the \fImhbuild\fR +command directly. See the \fImhbuild\fR(1) man page for details. + +The option `\-file\ file' directs \fImhn\fR to use the specified file as +the source message, rather than a message from a folder. If you specify +this file as \*(lq-\*(rq, then \fImhn\fR will accept the source message +on the standard input. Note that the file, or input from standard input +should be a validly formatted message, just like any other \fInmh\fR +message. It should \fBNOT\fR be in mail drop format (to convert a file in +mail drop format to a folder of \fInmh\fR messages, see \fIinc\fR\0(1)). + +A part specification consists of a series of numbers separated by dots. +For example, in a multipart content containing three parts, these +would be named as 1, 2, and 3, respectively. If part 2 was also a +multipart content containing two parts, these would be named as 2.1 and +2.2, respectively. Note that the `\-part' switch is effective for only +messages containing a multipart content. If a message has some other +kind of content, or if the part is itself another multipart content, the +`\-part' switch will not prevent the content from being acted upon. + +A content specification consists of a content type and a subtype. +The initial list of \*(lqstandard\*(rq content types and subtypes can +be found in RFC\-2046. +.ne 18 +A list of commonly used contents is briefly reproduced here: +.sp +.nf +.in +.5i +.ta \w'application 'u +Type Subtypes +---- -------- +text plain, enriched +multipart mixed, alternative, digest, parallel +message rfc822, partial, external-body +application octet-stream, postscript +image jpeg, gif, png +audio basic +video mpeg +.re +.in -.5i +.fi +.sp +A legal MIME message must contain a subtype specification. +.PP +To specify a content, regardless of its subtype, just use the +name of the content, e.g., \*(lqaudio\*(rq. To specify a specific +subtype, separate the two with a slash, e.g., \*(lqaudio/basic\*(rq. +Note that regardless of the values given to the `\-type' switch, a +multipart content (of any subtype listed above) is always acted upon. +Further note that if the `\-type' switch is used, and it is desirable to +act on a message/external-body content, then the `\-type' switch must +be used twice: once for message/external-body and once for the content +externally referenced. + +.Uh "Checking the Contents" +The `\-check' switch tells \fImhn\fR to check each content for an +integrity checksum. If a content has such a checksum (specified as a +Content-MD5 header field), then \fImhn\fR will attempt to verify the +integrity of the content. + +.Uh "Listing the Contents" +The `\-list' switch tells \fImhn\fR to list the table of contents +associated with the named messages. + +The `\-headers' switch indicates that +a one-line banner should be displayed above the listing. The `\-realsize' +switch tells \fImhn\fR to evaluate the \*(lqnative\*(rq (decoded) format +of each content prior to listing. This provides an accurate count at +the expense of a small delay. If the `\-verbose' switch is present, then +the listing will show any \*(lqextra\*(rq information that is present in +the message, such as comments in the Content-Type header. + +.Uh "Showing the Contents" +The `\-show' switch tells \fImhn\fR to display the contents of the named +messages. + +The headers of each message are displayed with the \fImhlproc\fR +(usually \fImhl\fR), using the standard format file \fImhl.headers\fR. +You may specify an alternate format file with the `\-form formfile' +switch. If the format file \fImhl.null\fR is specified, then the display +of the message headers is suppressed. + +The method used to display the different contents in the messages bodies +will be determined by a \*(lqdisplay string\*(rq. To find the display +string, \fImhn\fR will first search your profile for an entry of the form: +.sp +.in +.5i +mhn-show-/ +.in -.5i +.sp +to determine the display string. If this isn't found, \fImhn\fR +will search for an entry of the form: +.sp +.in +.5i +mhn-show- +.in -.5i +.sp +to determine the display string. + +If a display string is found, any escapes (given below) will be expanded. +The result will be executed under \fB/bin/sh\fR, with the standard input +set to the content. +.ne 16 +The display string may contain the following escapes: +.sp +.nf +.in +.5i +.ta \w'%F 'u +%a Insert parameters from Content-Type field +%e exclusive execution +%f Insert filename containing content +%F %e, %f, and stdin is terminal not content +%l display listing prior to displaying content +%p %l, and ask for confirmation +%s Insert content subtype +%d Insert content description +%% Insert the character % +.re +.in -.5i +.fi +.sp +.ne 10 +For those display strings containing the e- or F-escape, \fImhn\fR will +execute at most one of these at any given time. Although the F-escape +expands to be the filename containing the content, the e-escape has no +expansion as far as the shell is concerned. + +When the p-escape prompts for confirmation, typing INTR (usually +control-C) will tell \fImhn\fR not to display that content. The p-escape +can be disabled by specifying the switch `\-nopause'. Further, when +\fImhn\fR is display a content, typing QUIT (usually control-\\) will +tell \fImhn\fR to wrap things up immediately. + +Note that if the content being displayed is multipart, but not one of +the subtypes listed above, then the f- and F-escapes expand to multiple +filenames, one for each subordinate content. Further, stdin is not +redirected from the terminal to the content. + +If a display string is not found, \fImhn\fR has several default values: +.sp +.nf +.in +.5i +mhn-show-text/plain: %pmoreproc '%F' +mhn-show-message/rfc822: %pshow -file '%F' +.in -.5i +.fi +.sp +If a subtype of type text doesn't have a profile entry, it will be +treated as text/plain. + +\fImhn\fR has default methods for handling multipart messages of subtype +mixed, alternative, parallel, and digest. Any unknown subtype of type +multipart (without a profile entry), will be treated as multipart/mixed. + +If none of these apply, then \fImhn\fR will check to see if the message +has an application/octet-stream content with parameter \*(lqtype=tar\*(rq. +If so, \fImhn\fR will use an appropriate command. If not, \fImhn\fR +will complain. + +.ne 10 +Example entries might be: +.sp +.nf +.in +.5i +mhn-show-audio/basic: raw2audio 2>/dev/null | play +mhn-show-image: xv '%f' +mhn-show-application/PostScript: lpr -Pps +.in -.5i +.fi +.sp +Note that when using the f- or F-escape, it's a good idea to use +single-quotes around the escape. This prevents misinterpretation by +the shell of any funny characters that might be present in the filename. + +Finally, \fImhn\fR will process each message serially\0--\0it won't start +showing the next message until all the commands executed to display the +current message have terminated. In the case of a multipart content +(of any subtype listed above), the content contains advice indicating if +the parts should be displayed serially or in parallel. Because this may +cause confusion, particularly on uni-window displays, the `\-serialonly' +switch can be given to tell \fImhn\fR to never display parts in parallel. + +.Uh "Showing Alternate Character Sets" +Because a content of type text might be in a non-ASCII character +set, when \fImhn\fR encounters a \*(lqcharset\*(rq parameter for +this content, it checks if your terminal can display this character +set natively. \fIMhn\fR checks this by examining the the environment +variable MM_CHARSET. If the value of this environment variable is equal +to the value of the charset parameter, then \fImhn\fR assumes it can +display this content without any additional setup. If this environment +variable is not set, \fImhn\fR will assume a value of \*(lqUS-ASCII\*(rq. +If the character set cannot be displayed natively, then \fImhn\fR will +look for an entry of the form: +.sp +.in +.5i +mhn-charset- +.in -.5i +.sp +which should contain a command creating an environment to render +the character set. This command string should containing a single +\*(lq%s\*(rq, which will be filled-in with the command to display the +content. + +Example entries might be: +.sp +.in +.5i +mhn-charset-iso-8859-1: xterm -fn '-*-*-medium-r-normal-*-*-120-*-*-c-*-iso8859-*' -e %s +.in -.5i +or +.in +.5i +mhn-charset-iso-8859-1: '%s' +.in -.5i +.sp +The first example tells \fImhn\fR to start \fIxterm\fR and load the +appropriate character set for that message content. The second example +tells \fImhn\fR that your pager (or other program handling that content +type) can handle that character set, and that no special processing is +needed beforehand. +.sp +Note that many pagers strip off the high-order bit or have problems +displaying text with the high-order bit set. However, the pager +\fIless\fR has support for single-octet character sets. The source +to \fIless\fR is available on many ftp sites carrying free software. +In order to view messages sent in the ISO-8859-1 character set using +\fIless\fR, +.ne 9 +put these lines in your \&.login file: +.sp +.nf +.in +.5i +setenv LESSCHARSET latin1 +setenv LESS "-f" +.in -.5i +.fi +.sp +The first line tells \fIless\fR to use the ISO-8859-1 definition for +determining whether a character is \*(lqnormal\*(rq, \*(lqcontrol\*(lq, +or \*(lqbinary\*(rq. The second line tells \fIless\fR not to warn you +if it encounters a file that has non-ASCII characters. Then, simply +set the \fBmoreproc\fR profile entry to \fIless\fR, and it will get +called automatically. (To handle other single-octet character sets, +look at the \fIless\fR\0(1) manual entry for information about the +\fBLESSCHARDEF\fR environment variable.) + +.Uh "Storing the Contents" +The `\-store' switch tells \fImhn\fR to store the contents of the +named messages in \*(lqnative\*(rq (decoded) format. Two things must +be determined: the directory to store the content, and the filenames. +Files are written in the directory given by the \fBnmh-storage\fR +profile entry, +.ne 6 +e.g., +.sp +.in +.5i +nmh-storage: /tmp +.in -.5i +.sp +If this entry isn't present, +the current working directory is used. + +If the `\-auto' switch is given, then \fImhn\fR will check if the +message contains information indicating the filename that should be +used to store the content. This information should be specified as the +attribute \*(lqname=filename\*(rq in the Content-Type header for the +content you are storing. For security reasons, this filename will be +ignored if it begins with the character '/', '.', '|', or '!', or if it +contains the character '%'. For the sake of security, this switch is +not the default, and it is recommended that you do NOT put the `\-auto' +switch in your \&.mh\(ruprofile file. + +If the `\-auto' switch is not given (or is being ignored for +security reasons) then \fImhn\fR will look in the user's profile for +a \*(lqformatting string\*(rq to determine how the different contents +should be stored. First, \fImhn\fR will look for an entry of the form: +.sp +.in +.5i +mhn-store-/ +.in -.5i +.sp +to determine the formatting string. If this isn't found, \fImhn\fR will +look for an entry of the form: +.sp +.in +.5i +mhn-store- +.in -.5i +.sp +to determine the formatting string. + +If the formatting string starts with a \*(lq+\*(rq character, then +content is stored in the named folder. A formatting string consisting +solely of a \*(lq+\*(rq character is interpreted to be the current folder. + +If the formatting string consists solely of a \*(lq-\*(rq character, +then the content is sent to the standard output. + +If the formatting string starts with a '|', then the display string will +represent a command for \fImhn\fR to execute which should ultimately +store the content. The content will be passed to the standard input of +the command. Before the command is executed, \fImhn\fR will change to +the appropriate directory, and any escapes (given below) in the display +string will be expanded. + +Otherwise the formatting string will represent a pathname in which to +store the content. If the formatting string starts with a '/', then the +content will be stored in the full path given, else the file name will +be relative to the value of \fBnmh-storage\fR or the current working +directory. Any escapes (given below) will be expanded, except for the +a-escape. + +A command or pathname formatting string may contain the following escapes. +If the content isn't part of a multipart (of any subtype listed above) +content, the p-escapes are ignored. +.sp +.nf +.in +.5i +.ta \w'%P 'u +%a Parameters from Content-type (only valid with command) +%m Insert message number +%P Insert part number with leading dot +%p Insert part number without leading dot +%t Insert content type +%s Insert content subtype +%% Insert character % +.re +.in -.5i +.fi +.sp +If no formatting string is found, \fImhn\fR will check to see if the +content is application/octet-stream with parameter \*(lqtype=tar\*(rq. +If so, \fImhn\fR will choose an appropriate filename. If the content +is not application/octet-stream, then \fImhn\fR will check to see if the +content is a message. If so, \fImhn\fR will use the value \*(lq+\*(rq. +As a last resort, \fImhn\fR will use the value \*(lq%m%P.%s\*(rq. + +.ne 10 +Example profile entries might be: +.sp +.nf +.in +.5i +mhn-store-text: %m%P.txt +mhn-store-text: +inbox +mhn-store-message/partial: + +mhn-store-audio/basic: | raw2audio -e ulaw -s 8000 -c 1 > %m%P.au +mhn-store-image/jpeg: %m%P.jpg +mhn-store-application/PostScript: %m%P.ps +.in -.5i +.fi +.sp +.Uh "Reassembling Messages of Type message/partial" +When asked to store a content containing a partial message, \fImhn\fR +will try to locate all of the portions and combine them accordingly. +The default is to store the combined parts as a new message in the +current folder, although this can be changed using formatting +strings as discussed above. Thus, if someone has sent you a message +in several parts (such as the output from \fIsendfiles\fR), you can +easily reassemble them all into a single message in the following +fashion: +.sp +.nf +.in +.5i +% mhn -list 5-8 + msg part type/subtype size description + 5 message/partial 47K part 1 of 4 + 6 message/partial 47K part 2 of 4 + 7 message/partial 47K part 3 of 4 + 8 message/partial 18K part 4 of 4 +% mhn -store 5-8 +reassembling partials 5,6,7,8 to folder inbox as message 9 +% mhn -list -verbose 9 + msg part type/subtype size description + 9 application/octet-stream 118K + (extract with uncompress | tar xvpf -) + type=tar + conversions=compress +.in -.5i +.fi +.sp +This will store exactly one message, containing the sum of the +parts. It doesn't matter whether the partials are specified in +order, since \fImhn\fR will sort the partials, so that they are +combined in the correct order. But if \fImhn\fR can not locate +every partial necessary to reassemble the message, it will not +store anything. + +.Uh "External Access" +For contents of type message/external-body, +.ne 12 +\fImhn\fR supports these access-types: +.sp +.nf +.in +.5i +afs +anon-ftp +ftp +local-file +mail-server +.in -.5i +.fi +.sp +For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types, +\fImhn\fR will look for the \fBnmh-access-ftp\fR +profile entry, +.ne 6 +e.g., +.sp +.in +.5i +nmh-access-ftp: myftp.sh +.in -.5i +.sp +to determine the pathname of a program to perform the FTP retrieval. +.ne 14 +This program is invoked with these arguments: +.sp +.nf +.in +.5i +domain name of FTP-site +username +password +remote directory +remote filename +local filename +\*(lqascii\*(rq or \*(lqbinary\*(rq +.in -.5i +.fi +.sp +The program should terminate with an exit status of zero if the +retrieval is successful, and a non-zero exit status otherwise. + +If this entry is not provided, then \fImhn\fR will use a simple +built-in FTP client to perform the retrieval. + +.Uh "The Content Cache" +When \fImhn\fR encounters an external content containing a +\*(lqContent-ID:\*(rq field, and if the content allows caching, then +depending on the caching behavior of \fImhn\fR, the content might be +read from or written to a cache. + +The caching behavior of \fImhn\fR is controlled with the `\-rcache' +and `\-wcache' switches, which define the policy for reading from, +and writing to, the cache, respectively. One of four policies may be +specified: \*(lqpublic\*(rq, indicating that \fImhn\fR should make use +of a publically-accessible content cache; \*(lqprivate\*(rq, indicating +that \fImhn\fR should make use of the user's private content cache; +\*(lqnever\*(rq, indicating that \fImhn\fR should never make use of +caching; and, \*(lqask\*(rq, indicating that \fImhn\fR should ask +the user. + +There are two directories where contents may be cached: the profile entry +\fBnmh-cache\fR names a directory containing world-readable contents, and, +the profile entry \fBnmh-private-cache\fR names a directory containing +private contents. The former should be an absolute (rooted) directory +name. +.ne 6 +For example, +.sp +.in +.5i +nmh-cache: /tmp +.in -.5i +.sp +might be used if you didn't care that the cache got wiped after each +reboot of the system. The latter is interpreted relative to the user's +nmh directory, if not rooted, +.ne 6 +e.g., +.sp +.in +.5i +nmh-private-cache: .cache +.in -.5i +.sp +(which is the default value). + +.Uh "Caching the Contents" +When you encounter a content of type message/external-body with access +type \*(lqmail-server\*(rq, \fImhn\fR will ask you if may send a message +to a mail-server requesting the content, +.ne 14 +e.g., +.sp +.nf +.in +.5i +% show 1 +Retrieve content by asking mail-server@... + +SEND file + +? yes +mhn: request sent +.in -.5i +.fi +.sp +Regardless of your decision, +\fImhn\fR can't perform any other processing on the content. + +However, if \fImhn\fR is allowed to request the content, then when it +arrives, there should be a top-level \*(lqContent-ID:\*(rq field which +corresponds to the value in the original message/external-body content. +You should now use the `-cache' switch to tell \fImhn\fR to enter the +arriving content into the content cache, +.ne 8 +e.g., +.sp +.nf +.in +.5i +% mhn -cache 2 +caching message 2 as file ... +.in -.5i +.fi +.sp +You can then re-process the original message/external-body content, and +\*(lqthe right thing should happen\*(rq, +.ne 8 +e.g., +.sp +.nf +.in +.5i +% show 1 +\0... +.in -.5i +.fi + +.Uh "User Environment" +Because the display environment in which \fImhn\fR operates may vary for +different machines, \fImhn\fR will look for the environment variable +\fB$MHN\fR. If present, this specifies the name of an additional +user profile which should be read. Hence, when a user logs in on a +particular display device, this environment variable should be set to +refer to a file containing definitions useful for the given display device. +Normally, only entries that deal with the methods to display different +content type and subtypes +.sp +.in +.5i +mhn-show-/ +.br +mhn-show- +.in -.5i +.sp +need be present in this additional profile. +Finally, +\fImhn\fR will attempt to consult one other additional user profile, +.ne 6 +e.g., +.sp +.in +.5i +%etcdir%/mhn.defaults +.in -.5i +.sp +which is created automatically during nmh installation. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^$MHN~^Additional profile entries +^%etcdir%/mhn.defaults~^System default MIME profile entries +^%etcdir%/mhl.headers~^The headers template +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^mhlproc:~^Default program to display message headers +.Ps +^nmh-access-ftp:~^Program to retrieve contents via FTP +.Ps +^nmh-cache~^Public directory to store cached external contents +.Ps +^nmh-private-cache~^Personal directory to store cached external contents +.Ps +^mhn-charset-~^Template for environment to render character sets +.Ps +^mhn-show-*~^Template for displaying contents +.Ps +^nmh-storage~^Directory to store contents +.Ps +^mhn-store-*~^Template for storing contents +.Ps +^moreproc:~^Default program to display text/plain content +.Sa +mhbuild(1), mhl(1), sendfiles(1) +.br +RFC\-934: +.br + \fIProposed Standard for Message Encapsulation\fR, +.br +RFC\-2045: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part One: +.br + Format of Internet Message Bodies\fR, +.br +RFC\-2046: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Two: +.br + Media Types\fR, +.br +RFC\-2047: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Three: +.br + Message Header Extensions for Non-ASCII Text\fR, +.br +RFC\-2048: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Four: +.br + Registration Procedures\fR, +.br +RFC\-2049: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Five: +.br + Conformance Criteria and Examples\fR. +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-noauto' +.Ds +`\-nocache' +.Ds +`\-nocheck' +.Ds +`\-form mhl.headers' +.Ds +`\-headers' +.Ds +`\-pause' +.Ds +`\-rcache ask' +.Ds +`\-realsize' +.Ds +`\-noserialonly' +.Ds +`\-show' +.Ds +`\-noverbose' +.Ds +`\-wcache ask' +.Co +If a folder is given, it will become the current folder. The last +message selected will become the current message. +.Bu +Partial messages contained within a multipart content are not reassembled +with the `\-store' switch. +.En diff --git a/man/mhparam.man b/man/mhparam.man new file mode 100644 index 0000000..a4af2b5 --- /dev/null +++ b/man/mhparam.man @@ -0,0 +1,81 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHPARAM %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhparam \- print nmh profile components +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhparam +\%[components] +\%[-all] +\%[-component] \%[-nocomponent] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIMhparam\fR writes the value of the specified profile component to the +standard output separated by newlines. If the profile component is not +present, the default value (or nothing if there is no default) is printed. + +If the switch `\-component' is given, then the component name is displayed +along with the profile components value. This can be disabled with the +switch `\-nocomponent'. + +If more than one component is specified in the `components' list, then +the switch `\-component' is on by default. If only one component is +specified, then the switch `\-nocomponent' is on by default. + +If `\-all' is specified, then all components in the nmh profile are +displayed and other arguments are ignored. + +Examples: + +.nf +.ta \w'AliasFile:'u+2n +.in +.5i +% mhparam path +Mail + +% mhparam mhlproc +%libdir%/mhl + +% mhparam \-component path +Path: Mail + +% mhparam AliasFile rmmproc +AliasFile: aliases +rmmproc: rmmproc + +% mhparam \-nocomponent AliasFile rmmproc +aliases +rmmproc +.in -.5i +.fi + +\fIMhparam\fR is also useful in back\-quoted operations: + +.nf +.in +.5i +% fgrep cornell.edu `mhpath +`/`mhparam aliasfile` + +.in -.5i +.fi +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Sa +mh-profile\|(5) +.De +`\-nocomponent' if only one component is specified +`\-component' if more than one component is specified +.Ds +`components' defaults to none +.Co +None +.En diff --git a/man/mhpath.man b/man/mhpath.man new file mode 100644 index 0000000..ff17b73 --- /dev/null +++ b/man/mhpath.man @@ -0,0 +1,136 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHPATH %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhpath \- print full pathnames of nmh messages and folders +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhpath +\%[+folder] \%[msgs] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIMhpath\fR expands and sorts the message list `msgs' and writes the full +pathnames of the messages to the standard output separated by newlines. +If no `msgs' are specified, \fImhpath\fR outputs the folder pathname +instead. If the only argument is `+', your nmh \fIPath\fR is output; +this can be useful is shell scripts. + +Contrasted with other nmh commands, a message argument to \fImhpath\fR +may often be intended for \fIwriting\fR. Because of this: +.sp +1) the name \*(lqnew\*(rq has been added to \fImhpath\fR's list of +reserved message names (the others are \*(lqfirst\*(rq, \*(lqlast\*(rq, +\*(lqprev\*(rq, \*(lqnext\*(rq, \*(lqcur\*(rq, and \*(lqall\*(rq). +The new message is equivalent to the message after the last message +in a folder (and equivalent to 1 in a folder without messages). +The \*(lqnew\*(rq message may not be used as part of a message range. +.sp +2) Within a message list, the following designations may refer to messages +that do not exist: a single numeric message name, the single message name +\*(lqcur\*(rq, and (obviously) the single message name \*(lqnew\*(rq. +All other message designations must refer to at least one existing +message. +.sp +3) An empty folder is not in itself an error. + +Message numbers greater than the highest existing message in a folder +as part of a range designation are replaced with the next free message +number. + +Examples: The current folder foo contains messages 3 5 6. +Cur is 4. + +.nf +.in +.5i +% mhpath +/r/phyl/Mail/foo + +% mhpath all +/r/phyl/Mail/foo/3 +/r/phyl/Mail/foo/5 +/r/phyl/Mail/foo/6 + +% mhpath 2001 +/r/phyl/Mail/foo/7 + +% mhpath 1\-2001 +/r/phyl/Mail/foo/3 +/r/phyl/Mail/foo/5 +/r/phyl/Mail/foo/6 + +% mhpath new +/r/phyl/Mail/foo/7 + +% mhpath last new +/r/phyl/Mail/foo/6 +/r/phyl/Mail/foo/7 + +% mhpath last\-new +bad message list \*(lqlast\-new\*(rq. + +% mhpath cur +/r/phyl/Mail/foo/4 + +% mhpath 1\-2 +no messages in range \*(lq1\-2\*(rq. + +% mhpath first:2 +/r/phyl/Mail/foo/3 +/r/phyl/Mail/foo/5 + +% mhpath 1 2 +/r/phyl/Mail/foo/1 +/r/phyl/Mail/foo/2 +.in -.5i +.fi + +\fImhpath\fR is also useful in back\-quoted operations: + +.nf +.in +.5i +% cd `mhpath +inbox` + +% echo `mhpath +` +/r/phyl/Mail +.in -.5i +.fi +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +folder(1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to none +.Co +None +.Bu +Like all nmh commands, \fImhpath\fR expands and sorts \%[msgs]. So don't +expect + +.ti +.5i +mv `mhpath 501 500` + +to move 501 to 500. +Quite the reverse. But + +.ti +.5i +mv `mhpath 501` `mhpath 500` + +will do the trick. + +Out of range message 0 is treated far more severely than large out of +range message numbers. +.En diff --git a/man/mhshow.man b/man/mhshow.man new file mode 100644 index 0000000..30a925f --- /dev/null +++ b/man/mhshow.man @@ -0,0 +1,480 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHSHOW %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhshow \- display MIME messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhshow \%[+folder] \%[msgs] \%[\-file file] +.br +\%[\-part number]... \%[\-type content]... +.br +\%[\-serialonly] \%[\-noserialonly] +\%[\-pause] \%[\-nopause] +.br +\%[\-check] \%[\-nocheck] +\%[\-form formfile] +.br +\%[\-rcache policy] \%[\-wcache policy] +.br +\%[\-verbose] \%[\-noverbose] +\%[\-version] \%[\-help] +.in -.5i + +.SH DESCRIPTION +The \fImhshow\fR command display contents of a MIME (multi-media) +message or collection of messages. + +\fImhshow\fR manipulates multi-media messages as specified in +RFC\-2045 thru RFC\-2049. Currently \fImhshow\fR only supports +encodings in message bodies, and does not support the encoding of +message headers as specified in RFC\-2047. + +By default \fImhshow\fR will display all parts of a multipart +message. By using the `\-part' and `\-type' switches, you may +limit the scope of \fImhshow\fR to particular subparts (of a +multipart content) and/or particular content types. + +The option `\-file\ file' directs \fImhshow\fR to use the specified file as +the source message, rather than a message from a folder. If you specify +this file as \*(lq-\*(rq, then \fImhshow\fR will accept the source message +on the standard input. Note that the file, or input from standard input +should be a validly formatted message, just like any other \fInmh\fR +message. It should \fBNOT\fR be in mail drop format (to convert a file in +mail drop format to a folder of \fInmh\fR messages, see \fIinc\fR\0(1)). + +A part specification consists of a series of numbers separated by dots. +For example, in a multipart content containing three parts, these +would be named as 1, 2, and 3, respectively. If part 2 was also a +multipart content containing two parts, these would be named as 2.1 and +2.2, respectively. Note that the `\-part' switch is effective for only +messages containing a multipart content. If a message has some other +kind of content, or if the part is itself another multipart content, the +`\-part' switch will not prevent the content from being acted upon. + +A content specification consists of a content type and a subtype. +The initial list of \*(lqstandard\*(rq content types and subtypes can +be found in RFC\-2046. +.ne 18 +A list of commonly used contents is briefly reproduced here: +.sp +.nf +.in +.5i +.ta \w'application 'u +Type Subtypes +---- -------- +text plain, enriched +multipart mixed, alternative, digest, parallel +message rfc822, partial, external-body +application octet-stream, postscript +image jpeg, gif, png +audio basic +video mpeg +.re +.in -.5i +.fi +.sp +A legal MIME message must contain a subtype specification. +.PP +To specify a content, regardless of its subtype, just use the +name of the content, e.g., \*(lqaudio\*(rq. To specify a specific +subtype, separate the two with a slash, e.g., \*(lqaudio/basic\*(rq. +Note that regardless of the values given to the `\-type' switch, a +multipart content (of any subtype listed above) is always acted upon. +Further note that if the `\-type' switch is used, and it is desirable to +act on a message/external-body content, then the `\-type' switch must +be used twice: once for message/external-body and once for the content +externally referenced. + +.Uh "Unseen Sequence" + +If the profile entry \*(lqUnseen\-Sequence\*(rq is present and +non\-empty, then \fImhshow\fR will remove each of the messages shown +from each sequence named by the profile entry. + +.Uh "Checking the Contents" +The `\-check' switch tells \fImhshow\fR to check each content for an +integrity checksum. If a content has such a checksum (specified as a +Content-MD5 header field), then \fImhshow\fR will attempt to verify the +integrity of the content. + +.Uh "Showing the Contents" +The headers of each message are displayed with the \fImhlproc\fR +(usually \fImhl\fR), using the standard format file \fImhl.headers\fR. +You may specify an alternate format file with the `\-form formfile' +switch. If the format file \fImhl.null\fR is specified, then the display +of the message headers is suppressed. + +The method used to display the different contents in the messages bodies +will be determined by a \*(lqdisplay string\*(rq. To find the display +string, \fImhshow\fR will first search your profile for an entry of the +form: +.sp +.in +.5i +mhshow-show-/ +.in -.5i +.sp +to determine the display string. If this isn't found, \fImhshow\fR +will search for an entry of the form: +.sp +.in +.5i +mhshow-show- +.in -.5i +.sp +to determine the display string. + +If a display string is found, any escapes (given below) will be expanded. +The result will be executed under \fB/bin/sh\fR, with the standard input +set to the content. +.ne 16 +The display string may contain the following escapes: +.sp +.nf +.in +.5i +.ta \w'%F 'u +%a Insert parameters from Content-Type field +%e exclusive execution +%f Insert filename containing content +%F %e, %f, and stdin is terminal not content +%l display listing prior to displaying content +%p %l, and ask for confirmation +%s Insert content subtype +%d Insert content description +%% Insert the character % +.re +.in -.5i +.fi +.sp +.ne 10 +For those display strings containing the e- or F-escape, \fImhshow\fR will +execute at most one of these at any given time. Although the F-escape +expands to be the filename containing the content, the e-escape has no +expansion as far as the shell is concerned. + +When the p-escape prompts for confirmation, typing INTR (usually +control-C) will tell \fImhshow\fR not to display that content. +The p-escape can be disabled by specifying the switch `\-nopause'. +Further, when \fImhshow\fR is display a content, typing QUIT (usually +control-\\) will tell \fImhshow\fR to wrap things up immediately. + +Note that if the content being displayed is multipart, but not one of +the subtypes listed above, then the f- and F-escapes expand to multiple +filenames, one for each subordinate content. Further, stdin is not +redirected from the terminal to the content. + +If a display string is not found, \fImhshow\fR has several default values: +.sp +.nf +.in +.5i +mhshow-show-text/plain: %pmoreproc '%F' +mhshow-show-message/rfc822: %pshow -file '%F' +.in -.5i +.fi +.sp +If a subtype of type text doesn't have a profile entry, it will be +treated as text/plain. + +\fImhshow\fR has default methods for handling multipart messages of subtype +mixed, alternative, parallel, and digest. Any unknown subtype of type +multipart (without a profile entry), will be treated as multipart/mixed. + +If none of these apply, then \fImhshow\fR will check to see if the message +has an application/octet-stream content with parameter \*(lqtype=tar\*(rq. +If so, \fImhshow\fR will use an appropriate command. If not, \fImhshow\fR +will complain. + +.ne 10 +Example entries might be: +.sp +.nf +.in +.5i +mhshow-show-audio/basic: raw2audio 2>/dev/null | play +mhshow-show-image: xv '%f' +mhshow-show-application/PostScript: lpr -Pps +.in -.5i +.fi +.sp +Note that when using the f- or F-escape, it's a good idea to use +single-quotes around the escape. This prevents misinterpretation by +the shell of any funny characters that might be present in the filename. + +Finally, \fImhshow\fR will process each message serially\0--\0it won't start +showing the next message until all the commands executed to display the +current message have terminated. In the case of a multipart content +(of any subtype listed above), the content contains advice indicating if +the parts should be displayed serially or in parallel. Because this may +cause confusion, particularly on uni-window displays, the `\-serialonly' +switch can be given to tell \fImhshow\fR to never display parts in parallel. + +.Uh "Showing Alternate Character Sets" +Because a content of type text might be in a non-ASCII character +set, when \fImhshow\fR encounters a \*(lqcharset\*(rq parameter for +this content, it checks if your terminal can display this character +set natively. \fIMhn\fR checks this by examining the the environment +variable MM_CHARSET. If the value of this environment variable is equal +to the value of the charset parameter, then \fImhshow\fR assumes it can +display this content without any additional setup. If this environment +variable is not set, \fImhshow\fR will assume a value of \*(lqUS-ASCII\*(rq. +If the character set cannot be displayed natively, then \fImhshow\fR will +look for an entry of the form: +.sp +.in +.5i +mhshow-charset- +.in -.5i +.sp +which should contain a command creating an environment to render +the character set. This command string should containing a single +\*(lq%s\*(rq, which will be filled-in with the command to display the +content. + +Example entries might be: +.sp +.in +.5i +mhshow-charset-iso-8859-1: xterm -fn '-*-*-medium-r-normal-*-*-120-*-*-c-*-iso8859-*' -e %s +.in -.5i +or +.in +.5i +mhshow-charset-iso-8859-1: '%s' +.in -.5i +.sp +The first example tells \fImhshow\fR to start \fIxterm\fR and load the +appropriate character set for that message content. The second example +tells \fImhshow\fR that your pager (or other program handling that content +type) can handle that character set, and that no special processing is +needed beforehand. +.sp +Note that many pagers strip off the high-order bit or have problems +displaying text with the high-order bit set. However, the pager +\fIless\fR has support for single-octet character sets. The source +to \fIless\fR is available on many ftp sites carrying free software. +In order to view messages sent in the ISO-8859-1 character set using +\fIless\fR, +.ne 9 +put these lines in your \&.login file: +.sp +.nf +.in +.5i +setenv LESSCHARSET latin1 +setenv LESS "-f" +.in -.5i +.fi +.sp +The first line tells \fIless\fR to use the ISO-8859-1 definition for +determining whether a character is \*(lqnormal\*(rq, \*(lqcontrol\*(lq, +or \*(lqbinary\*(rq. The second line tells \fIless\fR not to warn you +if it encounters a file that has non-ASCII characters. Then, simply +set the \fBmoreproc\fR profile entry to \fIless\fR, and it will get +called automatically. (To handle other single-octet character sets, +look at the \fIless\fR\0(1) manual entry for information about the +\fBLESSCHARDEF\fR environment variable.) + +.Uh "Messages of Type message/partial" +\fImhshow\fR cannot directly display messages of type partial. +You must reassemble them first into a normal message using +\fImhstore\fR. Check the man page for \fImhstore\fR for details. + +.Uh "External Access" +For contents of type message/external-body, +.ne 12 +\fImhshow\fR supports these access-types: +.sp +.nf +.in +.5i +afs +anon-ftp +ftp +local-file +mail-server +.in -.5i +.fi +.sp +For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types, +\fImhshow\fR will look for the \fBnmh-access-ftp\fR +profile entry, +.ne 6 +e.g., +.sp +.in +.5i +nmh-access-ftp: myftp.sh +.in -.5i +.sp +to determine the pathname of a program to perform the FTP retrieval. +.ne 14 +This program is invoked with these arguments: +.sp +.nf +.in +.5i +domain name of FTP-site +username +password +remote directory +remote filename +local filename +\*(lqascii\*(rq or \*(lqbinary\*(rq +.in -.5i +.fi +.sp +The program should terminate with an exit status of zero if the +retrieval is successful, and a non-zero exit status otherwise. + +If this entry is not provided, then \fImhshow\fR will use a simple +built-in FTP client to perform the retrieval. + +.Uh "The Content Cache" +When \fImhshow\fR encounters an external content containing a +\*(lqContent-ID:\*(rq field, and if the content allows caching, then +depending on the caching behavior of \fImhshow\fR, the content might be +read from or written to a cache. + +The caching behavior of \fImhshow\fR is controlled with the `\-rcache' +and `\-wcache' switches, which define the policy for reading from, +and writing to, the cache, respectively. One of four policies may be +specified: \*(lqpublic\*(rq, indicating that \fImhshow\fR should make use +of a publically-accessible content cache; \*(lqprivate\*(rq, indicating +that \fImhshow\fR should make use of the user's private content cache; +\*(lqnever\*(rq, indicating that \fImhshow\fR should never make use of +caching; and, \*(lqask\*(rq, indicating that \fImhshow\fR should ask +the user. + +There are two directories where contents may be cached: the profile entry +\fBnmh-cache\fR names a directory containing world-readable contents, and, +the profile entry \fBnmh-private-cache\fR names a directory containing +private contents. The former should be an absolute (rooted) directory +name. +.ne 6 +For example, +.sp +.in +.5i +nmh-cache: /tmp +.in -.5i +.sp +might be used if you didn't care that the cache got wiped after each +reboot of the system. The latter is interpreted relative to the user's +nmh directory, if not rooted, +.ne 6 +e.g., +.sp +.in +.5i +nmh-private-cache: .cache +.in -.5i +.sp +(which is the default value). + +.Uh "User Environment" +Because the display environment in which \fImhshow\fR operates may vary for +different machines, \fImhshow\fR will look for the environment variable +\fB$MHSHOW\fR. If present, this specifies the name of an additional +user profile which should be read. Hence, when a user logs in on a +particular display device, this environment variable should be set to +refer to a file containing definitions useful for the given display device. +Normally, only entries that deal with the methods to display different +content type and subtypes +.sp +.in +.5i +mhshow-show-/ +.br +mhshow-show- +.in -.5i +.sp +need be present in this additional profile. +Finally, +\fImhshow\fR will attempt to consult one other additional user profile, +.ne 6 +e.g., +.sp +.in +.5i +%etcdir%/mhn.defaults +.in -.5i +.sp +which is created automatically during nmh installation. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^$MHSHOW~^Additional profile entries +^%etcdir%/mhn.defaults~^System default MIME profile entries +^%etcdir%/mhl.headers~^The headers template +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Unseen\-Sequence:~^To name sequences denoting unseen messages +.Ps +^mhlproc:~^Default program to display message headers +.Ps +^nmh-access-ftp:~^Program to retrieve contents via FTP +.Ps +^nmh-cache~^Public directory to store cached external contents +.Ps +^nmh-private-cache~^Personal directory to store cached external contents +.Ps +^mhshow-charset-~^Template for environment to render character sets +.Ps +^mhshow-show-*~^Template for displaying contents +.Ps +^moreproc:~^Default program to display text/plain content +.Sa +mhbuild(1), mhl(1), mhlist(1), mhstore(1), sendfiles(1) +.br +RFC\-934: +.br + \fIProposed Standard for Message Encapsulation\fR, +.br +RFC\-2045: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part One: +.br + Format of Internet Message Bodies\fR, +.br +RFC\-2046: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Two: +.br + Media Types\fR, +.br +RFC\-2047: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Three: +.br + Message Header Extensions for Non-ASCII Text\fR, +.br +RFC\-2048: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Four: +.br + Registration Procedures\fR, +.br +RFC\-2049: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Five: +.br + Conformance Criteria and Examples\fR. +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-nocheck' +.Ds +`\-form mhl.headers' +.Ds +`\-pause' +.Ds +`\-rcache ask' +.Ds +`\-realsize' +.Ds +`\-noserialonly' +.Ds +`\-noverbose' +.Ds +`\-wcache ask' +.Co +If a folder is given, it will become the current folder. The last +message selected will become the current message. +.En diff --git a/man/mhstore.man b/man/mhstore.man new file mode 100644 index 0000000..7148dc8 --- /dev/null +++ b/man/mhstore.man @@ -0,0 +1,420 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MHSTORE %manext1% MH.6.8 [%nmhversion%] +.SH NAME +mhstore \- store contents of MIME messages into files +.SH SYNOPSIS +.in +.5i +.ti -.5i +mhstore \%[+folder] \%[msgs] \%[\-file file] +.br +\%[\-part number]... \%[\-type content]... +.br +\%[\-auto] \%[\-noauto] +\%[\-check] \%[\-nocheck] +.br +\%[\-rcache policy] \%[\-wcache policy] +.br +\%[\-verbose] \%[\-noverbose] +\%[\-version] +\%[\-help] +.in -.5i + +.SH DESCRIPTION +The \fImhstore\fR command allows you to store the contents of a +collection of MIME (multi-media) messages into files or other +messages. + +\fImhstore\fR manipulates multi-media messages as specified in +RFC\-2045 thru RFC\-2049. + +By default, \fImhstore\fR will store all the parts of each message. +Each part will be store in a separate file. The header fields of +the message are not stored. By using the `\-part' and `\-type' +switches, you may limit the scope of \fImhstore\fR to particular +subparts (of a multipart content) and/or particular content types. + +The option `\-file\ file' directs \fImhstore\fR to use the specified +file as the source message, rather than a message from a folder. +If you specify this file as \*(lq-\*(rq, then \fImhstore\fR will +accept the source message on the standard input. Note that the +file, or input from standard input should be a validly formatted +message, just like any other \fInmh\fR message. It should \fBNOT\fR +be in mail drop format (to convert a file in mail drop format to +a folder of \fInmh\fR messages, see \fIinc\fR\0(1)). + +A part specification consists of a series of numbers separated by +dots. For example, in a multipart content containing three parts, +these would be named as 1, 2, and 3, respectively. If part 2 was +also a multipart content containing two parts, these would be named +as 2.1 and 2.2, respectively. Note that the `\-part' switch is +effective for only messages containing a multipart content. If a +message has some other kind of content, or if the part is itself +another multipart content, the `\-part' switch will not prevent +the content from being acted upon. + +A content specification consists of a content type and a subtype. +The initial list of \*(lqstandard\*(rq content types and subtypes +can be found in RFC\-2046. +.ne 18 +A list of commonly used contents is briefly reproduced here: +.sp +.nf +.in +.5i +.ta \w'application 'u +Type Subtypes +---- -------- +text plain, enriched +multipart mixed, alternative, digest, parallel +message rfc822, partial, external-body +application octet-stream, postscript +image jpeg, gif, png +audio basic +video mpeg +.re +.in -.5i +.fi +.sp +A legal MIME message must contain a subtype specification. +.PP +To specify a content, regardless of its subtype, just use the name +of the content, e.g., \*(lqaudio\*(rq. To specify a specific +subtype, separate the two with a slash, e.g., \*(lqaudio/basic\*(rq. +Note that regardless of the values given to the `\-type' switch, +a multipart content (of any subtype listed above) is always acted +upon. Further note that if the `\-type' switch is used, and it is +desirable to act on a message/external-body content, then the +`\-type' switch must be used twice: once for message/external-body +and once for the content externally referenced. + +.Uh "Checking the Contents" +The `\-check' switch tells \fImhstore\fR to check each content for +an integrity checksum. If a content has such a checksum (specified +as a Content-MD5 header field), then \fImhstore\fR will attempt to +verify the integrity of the content. + +.Uh "Storing the Contents" +The \fImhstore\fR will store the contents of the named messages in +\*(lqnative\*(rq (decoded) format. Two things must be determined: +the directory to store the content, and the filenames. Files are +written in the directory given by the \fBnmh-storage\fR profile +entry, +.ne 6 +e.g., +.sp +.in +.5i +nmh-storage: /tmp +.in -.5i +.sp +If this entry isn't present, +the current working directory is used. + +If the `\-auto' switch is given, then \fImhstore\fR will check if +the message contains information indicating the filename that should +be used to store the content. This information should be specified +as the attribute \*(lqname=filename\*(rq in the Content-Type header +for the content you are storing. For security reasons, this filename +will be ignored if it begins with the character '/', '.', '|', or +'!', or if it contains the character '%'. For the sake of security, +this switch is not the default, and it is recommended that you do +NOT put the `\-auto' switch in your \&.mh\(ruprofile file. + +If the `\-auto' switch is not given (or is being ignored for security +reasons) then \fImhstore\fR will look in the user's profile for a +\*(lqformatting string\*(rq to determine how the different contents +should be stored. First, \fImhstore\fR will look for an entry of +the form: +.sp +.in +.5i +mhstore-store-/ +.in -.5i +.sp +to determine the formatting string. If this isn't found, \fImhstore\fR +will look for an entry of the form: +.sp +.in +.5i +mhstore-store- +.in -.5i +.sp +to determine the formatting string. + +If the formatting string starts with a \*(lq+\*(rq character, then +content is stored in the named folder. A formatting string consisting +solely of a \*(lq+\*(rq character is interpreted to be the current +folder. + +If the formatting string consists solely of a \*(lq-\*(rq character, +then the content is sent to the standard output. + +If the formatting string starts with a '|', then the display string +will represent a command for \fImhstore\fR to execute which should +ultimately store the content. The content will be passed to the +standard input of the command. Before the command is executed, +\fImhstore\fR will change to the appropriate directory, and any +escapes (given below) in the display string will be expanded. + +Otherwise the formatting string will represent a pathname in which +to store the content. If the formatting string starts with a '/', +then the content will be stored in the full path given, else the +file name will be relative to the value of \fBnmh-storage\fR or +the current working directory. Any escapes (given below) will be +expanded, except for the a-escape. + +A command or pathname formatting string may contain the following +escapes. If the content isn't part of a multipart (of any subtype +listed above) content, the p-escapes are ignored. +.sp +.nf +.in +.5i +.ta \w'%P 'u +%a Parameters from Content-type (only valid with command) +%m Insert message number +%P Insert part number with leading dot +%p Insert part number without leading dot +%t Insert content type +%s Insert content subtype +%% Insert character % +.re +.in -.5i +.fi +.sp +If no formatting string is found, \fImhstore\fR will check to see +if the content is application/octet-stream with parameter +\*(lqtype=tar\*(rq. If so, \fImhstore\fR will choose an appropriate +filename. If the content is not application/octet-stream, then +\fImhstore\fR will check to see if the content is a message. If +so, \fImhstore\fR will use the value \*(lq+\*(rq. As a last resort, +\fImhstore\fR will use the value \*(lq%m%P.%s\*(rq. + +.ne 10 +Example profile entries might be: +.sp +.nf +.in +.5i +mhstore-store-text: %m%P.txt +mhstore-store-text: +inbox +mhstore-store-message/partial: + +mhstore-store-audio/basic: | raw2audio -e ulaw -s 8000 -c 1 > %m%P.au +mhstore-store-image/jpeg: %m%P.jpg +mhstore-store-application/PostScript: %m%P.ps +.in -.5i +.fi +.sp +.Uh "Reassembling Messages of Type message/partial" +\fImhstore\fR is also able to reassemble messages that have been +split into multiple messages of type \*(lqmessage/partial\*(rq. + +When asked to store a content containing a partial message, +\fImhstore\fR will try to locate all of the portions and combine +them accordingly. The default is to store the combined parts as +a new message in the current folder, although this can be changed +using formatting strings as discussed above. Thus, if someone has +sent you a message in several parts (such as the output from +\fIsendfiles\fR), you can easily reassemble them all into a single +message in the following fashion: +.sp +.nf +.in +.5i +% mhlist 5-8 + msg part type/subtype size description + 5 message/partial 47K part 1 of 4 + 6 message/partial 47K part 2 of 4 + 7 message/partial 47K part 3 of 4 + 8 message/partial 18K part 4 of 4 +% mhstore 5-8 +reassembling partials 5,6,7,8 to folder inbox as message 9 +% mhlist -verbose 9 + msg part type/subtype size description + 9 application/octet-stream 118K + (extract with uncompress | tar xvpf -) + type=tar + conversions=compress +.in -.5i +.fi +.sp +This will store exactly one message, containing the sum of the +parts. It doesn't matter whether the partials are specified in +order, since \fImhstore\fR will sort the partials, so that they +are combined in the correct order. But if \fImhstore\fR can not +locate every partial necessary to reassemble the message, it will +not store anything. + +.Uh "External Access" +For contents of type message/external-body, +.ne 12 +\fImhstore\fR supports these access-types: +.sp +.nf +.in +.5i +afs +anon-ftp +ftp +local-file +mail-server +.in -.5i +.fi +.sp +For the \*(lqanon-ftp\*(rq and \*(lqftp\*(rq access types, +\fImhstore\fR will look for the \fBnmh-access-ftp\fR +profile entry, +.ne 6 +e.g., +.sp +.in +.5i +nmh-access-ftp: myftp.sh +.in -.5i +.sp +to determine the pathname of a program to perform the FTP retrieval. +.ne 14 +This program is invoked with these arguments: +.sp +.nf +.in +.5i +domain name of FTP-site +username +password +remote directory +remote filename +local filename +\*(lqascii\*(rq or \*(lqbinary\*(rq +.in -.5i +.fi +.sp +The program should terminate with an exit status of zero if the +retrieval is successful, and a non-zero exit status otherwise. + +If this entry is not provided, then \fImhstore\fR will use a simple +built-in FTP client to perform the retrieval. + +.Uh "The Content Cache" +When \fImhstore\fR encounters an external content containing a +\*(lqContent-ID:\*(rq field, and if the content allows caching, then +depending on the caching behavior of \fImhstore\fR, the content might be +read from or written to a cache. + +The caching behavior of \fImhstore\fR is controlled with the `\-rcache' +and `\-wcache' switches, which define the policy for reading from, +and writing to, the cache, respectively. One of four policies may be +specified: \*(lqpublic\*(rq, indicating that \fImhstore\fR should make use +of a publically-accessible content cache; \*(lqprivate\*(rq, indicating +that \fImhstore\fR should make use of the user's private content cache; +\*(lqnever\*(rq, indicating that \fImhstore\fR should never make use of +caching; and, \*(lqask\*(rq, indicating that \fImhstore\fR should ask +the user. + +There are two directories where contents may be cached: the profile entry +\fBnmh-cache\fR names a directory containing world-readable contents, and, +the profile entry \fBnmh-private-cache\fR names a directory containing +private contents. The former should be an absolute (rooted) directory +name. +.ne 6 +For example, +.sp +.in +.5i +nmh-cache: /tmp +.in -.5i +.sp +might be used if you didn't care that the cache got wiped after each +reboot of the system. The latter is interpreted relative to the user's +nmh directory, if not rooted, +.ne 6 +e.g., +.sp +.in +.5i +nmh-private-cache: .cache +.in -.5i +.sp +(which is the default value). + +.Uh "User Environment" +Because the environment in which \fImhstore\fR operates may vary +for different machines, \fImhstore\fR will look for the environment +variable \fB$MHSTORE\fR. If present, this specifies the name of +an additional user profile which should be read. Hence, when a +user logs in on a machine, this environment variable should be set +to refer to a file containing definitions useful for that machine. +Finally, \fImhstore\fR will attempt to consult one other additional +user profile, +.ne 6 +e.g., +.sp +.in +.5i +%etcdir%/mhn.defaults +.in -.5i +.sp +which is created automatically during nmh installation. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^$MHSTORE~^Additional profile entries +^%etcdir%/mhn.defaults~^System default MIME profile entries +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^nmh-access-ftp:~^Program to retrieve contents via FTP +.Ps +^nmh-cache~^Public directory to store cached external contents +.Ps +^nmh-private-cache~^Personal directory to store cached external contents +.Ps +^nmh-storage~^Directory to store contents +.Ps +^mhstore-store-*~^Template for storing contents +.Sa +mhbuild(1), mhlist(1), mhshow(1), sendfiles(1) +.br +RFC\-2045: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part One: +.br + Format of Internet Message Bodies\fR, +.br +RFC\-2046: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Two: +.br + Media Types\fR, +.br +RFC\-2047: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Three: +.br + Message Header Extensions for Non-ASCII Text\fR, +.br +RFC\-2048: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Four: +.br + Registration Procedures\fR, +.br +RFC\-2049: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Five: +.br + Conformance Criteria and Examples\fR. +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-noauto' +.Ds +`\-nocheck' +.Ds +`\-rcache ask' +.Ds +`\-wcache ask' +.Ds +`\-noverbose' +.Co +If a folder is given, it will become the current folder. The last +message selected will become the current message. +.Bu +Partial messages contained within a multipart content are not reassembled. +.En diff --git a/man/msgchk.man b/man/msgchk.man new file mode 100644 index 0000000..e7624a0 --- /dev/null +++ b/man/msgchk.man @@ -0,0 +1,95 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MSGCHK %manext1% MH.6.8 [%nmhversion%] +.SH NAME +msgchk \- check for messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +msgchk +\%[\-date] \%[\-nodate] +\%[\-notify\ all/mail/nomail] +.br +\%[\-nonotify\ all/mail/nomail] +.br +%nmhbeginpop% +\%[\-host\ hostname] +\%[\-user\ username] +.br +%nmhendpop% +\%[users\ ...] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +The \fImsgchk\fR program checks all known mail drops for mail waiting +for you. For those drops which have mail for you, \fImsgchk\fR will +indicate if it believes that you have seen the mail in question before. + +The `\-notify\ type' switch indicates under what circumstances +\fImsgchk\fR should produce a message. The default is `\-notify\ all' +which says that \fImsgchk\fR should always report the status of the +users maildrop. Other values for `type' include `mail' which says that +\fImsgchk\fR should report the status of waiting mail; and, `nomail' +which says that \fImsgchk\fR should report the status of empty maildrops. +The `\-nonotify\ type' switch has the inverted sense, so +`\-nonotify\ all' directs \fImsgchk\fR to never report the status of +maildrops. This is useful if the user wishes to check \fImsgchk\fR's +exit status. A non\-zero exit status indicates that mail was \fBnot\fR +waiting for at least one of the indicated users. + +If \fImsgchk\fR produces output, then the `\-date' switch directs +\fImsgchk\fR to print out the last date mail was read, if this can +be determined. +%nmhbeginpop% + +.Uh "Using POP" +\fImsgchk\fR will normally check all the local mail drops, but if +the option \*(lqpophost:\*(rq is set in the mts configuration file +\*(lqmts.conf\*(rq, or if the `\-host\ hostname' switch is given, +\fImsgchk\fR will query this POP service host as to the status of +mail waiting. + +The default is for \fImsgchk\fR to assume that your account name +on the POP server is the same as your current username. To specify +a different username, use the `\-user\ username' switch. + +When using POP, you will normally need to type the password for +your account on the POP server, in order to retrieve your messages. +It is possible to automate this process by creating a \*(lq.netrc\*(rq +file containing your login account information for this POP server. +For each POP server, this file should have a line of the following +form. Replace the words mypopserver, mylogin, and mypassword with +your own account information. + +machine mypopserver login mylogin password mypassword + +This \*(lq.netrc\*(rq file should be owned and readable only by +you. + +For debugging purposes, there is also a switch `\-snoop', which will +allow you to watch the POP transaction take place between you and the +POP server. +%nmhendpop% +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^%etcdir%/mts.conf~^nmh mts configuration file +^%mailspool%/$USER~^Location of mail drop +.Pr +None +.Sa +inc(1) +.De +`user' defaults to the current user +.Ds +`\-date' +.Ds +`\-notify\ all' +.Co +None +.En diff --git a/man/msh.man b/man/msh.man new file mode 100644 index 0000000..88ae414 --- /dev/null +++ b/man/msh.man @@ -0,0 +1,208 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH MSH %manext1% MH.6.8 [%nmhversion%] +.SH NAME +msh \- nmh shell (and BBoard reader) +.SH SYNOPSIS +.in +.5i +.ti -.5i +msh +\%[\-prompt\ string] +\%[\-scan] \%[\-noscan] +\%[\-topcur] \%[\-notopcur] +\%[file] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fImsh\fR is an interactive program that implements a subset of the normal +\fInmh\fR commands operating on a single file in \fIpackf\fR'd format. +That is, \fImsh\fR is used to read a file that contains a number +of messages, as opposed to the standard \fInmh\fR style of reading +a number of files, each file being a separate message in a folder. +\fImsh\fR's chief advantage is that the normal \fInmh\fR style does not +allow a file to have more than one message in it. Hence, \fImsh\fR is +ideal for reading \fIBBoards\fR, as these files are delivered by the +transport system in this format. In addition, \fImsh\fR can be used on +other files, such as message archives which have been \fIpack\fRed (see +\fIpackf\fR\0(1)). Finally, \fImsh\fR is an excellent \fInmh\fR tutor. +As the only commands available to the user are \fInmh\fR commands, this +allows \fInmh\fR beginners to concentrate on how commands to \fInmh\fR +are formed and (more or less) what they mean. + +When invoked, \fImsh\fR reads the named file, and enters a command loop. +The user may type most of the normal \fInmh\fR commands. The syntax and +semantics of these commands typed to \fImsh\fR are identical to their +\fInmh\fR counterparts. In cases where the nature of \fImsh\fR would be +inconsistent (e.g., specifying a `+folder' with some commands), \fImsh\fR +will duly inform the user. The commands that \fImsh\fR currently supports +(in some slightly modified or restricted forms) are: +.sp 1 +.in +.5i +ali +.br +burst +.br +comp +.br +dist +.br +folder +.br +forw +.br +inc +.br +mark +.br +mhmail +.br +mhn +.br +msgchk +.br +next +.br +packf +.br +pick +.br +prev +.br +refile +.br +repl +.br +rmm +.br +scan +.br +send +.br +show +.br +sortm +.br +whatnow +.br +whom +.in -.5i + +In addition, \fImsh\fR has a \*(lqhelp\*(rq command which gives a +brief overview. To terminate \fImsh\fR, type CTRL\-D, or use the +\*(lqquit\*(rq command. If \fImsh\fR is being invoked from \fIbbc\fR, +then typing CTRL\-D will also tell \fIbbc\fR to exit as well, while +using the \*(lqquit\*(rq command will return control to \fIbbc\fR, and +\fIbbc\fR will continue examining the list of BBoards that it is scanning. + +If the file is writable and has been modified, then using \*(lqquit\*(rq +will query the user if the file should be updated. + +The `\-prompt string' switch sets the prompting string for \fImsh\fR. + +You may wish to use an alternate \fInmh\fR profile for the commands that +\fImsh\fR executes; see \fImh-profile\fR\0(5) for details about the +\fB$MH\fR environment variable. + +When invoked from \fIbbc\fR, two special features are enabled: +First, the `\-scan' switch directs \fImsh\fR to do a `scan\0unseen' +on start\-up if new items are present in the BBoard. This feature is +best used from \fIbbc\fR, which correctly sets the stage. Second, the +\fImark\fR command in \fImsh\fR acts specially when you are reading a +BBoard, since \fImsh\fR will consult the sequence \*(lqunseen\*(rq in +determining what messages you have actually read. When \fImsh\fR exits, +it reports this information to \fIbbc\fR. In addition, if you give the +\fImark\fR command with no arguments, \fImsh\fR will interpret it as +`mark\0\-sequence\0unseen\0\-delete\0\-nozero\0all' Hence, to discard +all of the messages in the current BBoard you're reading, just use the +\fImark\fR command with no arguments. + +Normally, the \*(lqexit\*(rq command is identical to the \*(lqquit\*(rq +command in \fImsh\fR. When run under \fIbbc\fR however, \*(lqexit\*(rq +directs \fImsh\fR to mark all messages as seen and then \*(lqquit\*(rq. +For speedy type\-in, this command is often abbreviated as just +\*(lqe\*(rq. + +When invoked from \fIvmh\fR, another special feature is enabled: +The `topcur' switch directs \fImsh\fR to have the current message +\*(lqtrack\*(rq the top line of the \fIvmh\fR scan window. Normally, +\fImsh\fR has the current message \*(lqtrack\*(rq the center of the window +(under `\-notopcur', which is the default). + +\fImsh\fR supports an output redirection facility. Commands may be +followed by one of + +.nf +.in +.5i +.ta \w'| \fIcommand\fR 'u +^> \fIfile\fR~^write output to \fIfile\fR +^>> \fIfile\fR~^append output to \fIfile\fR +^| \fIcommand\fR~^pipe output to UNIX \fIcommand\fR +.re +.in -.5i +.fi + +If \fIfile\fR starts with a `\~' (tilde), then a \fIcsh\fR-like expansion +takes place. Note that \fIcommand\fR is interpreted by \fIsh\fR\0(1). +Also note that \fImsh\fR does NOT support history substitutions, variable +substitutions, or alias substitutions. + +When parsing commands to the left of any redirection symbol, \fImsh\fR +will honor `\\' (back\-slash) as the quote next\-character symbol, and +`"' (double\-quote) as quote\-word delimiters. All other input tokens +are separated by whitespace (spaces and tabs). +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^%etcdir%/mts.conf~^nmh mts configuration file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Msg\-Protect:~^To set mode when creating a new `file' +.Ps +^fileproc:~^Program to file messages +.Ps +^showproc:~^Program to show messages +.Sa +bbc(1) +.De +`file' defaults to \*(lq./msgbox\*(rq +.Ds +`\-prompt\ (msh)\ ' +.Ds +`\-noscan' +.Ds +`\-notopcur' +.Co +None +.Bu +The argument to the `\-prompt' switch must be interpreted as a single +token by the shell that invokes \fImsh\fR. Therefore, one must usually +place the argument to this switch inside double\-quotes. + +There is a strict limit of messages per file in \fIpackf\fR'd format +which \fImsh\fR can handle. Usually, this limit is 1000 messages. + +Please remember that \fImsh\fR is not the \fICShell\fR, and that a lot of +the nice facilities provided by the latter are not present in the former. + +In particular, \fImsh\fR does not understand back\-quoting, so the only +effective way to use \fIpick\fR inside \fImsh\fR is to always use the +`\-seq\0select' switch. Clever users of \fInmh\fR will put the line + +.ti +.5i +pick:\0\-seq\0select\0\-list + +in their \&.mh\(ruprofile file so that \fIpick\fR works equally well +from both the shell and \fImsh\fR. + +\fIsortm\fR always uses \*(lq\-noverbose\*(rq and if +\*(lq\-textfield\ field\*(lq is used, \*(lq\-limit 0\*(rq. + +The \fImsh\fR program inherits most (if not all) of the bugs from the +\fInmh\fR commands it implements. +.En diff --git a/man/next.man b/man/next.man new file mode 100644 index 0000000..1bc2545 --- /dev/null +++ b/man/next.man @@ -0,0 +1,62 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH NEXT %manext1% MH.6.8 [%nmhversion%] +.SH NAME +next \- show the next message +.SH SYNOPSIS +.in +.5i +.ti -.5i +next +\%[+folder] +\%[\-showproc\ program] +\%[\-showmimeproc\ program] +.br +\%[\-header] \%[\-noheader] +\%[\-checkmime] \%[\-nocheckmime] +.br +\%[switches\ for\ \fIshowproc\fR or\ \fIshowmimeproc\fR] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fINext\fR performs a \fIshow\fR on the next message in the specified +(or current) folder. Like \fIshow\fR, it passes any switches on to +the program \fIshowproc\fR or \fIshowmimeproc\fR, which is called to list +the message. This command is almost exactly equivalent to \*(lqshow +next\*(rq. Consult the manual entry for \fIshow\fR\0(1) for all the +details. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^showproc:~^Program to show non-MIME messages +.Ps +^showmimeproc:~^Program to show MIME messages +.Sa +show(1), prev(1) +.De +`+folder' defaults to the current folder +.Ds +`\-checkmime' +.Ds +`\-header' +.Co +If a folder is specified, it will become the current folder. The message +that is shown (i.e., the next message in sequence) will become the +current message. +.Bu +\fInext\fR is really a link to the \fIshow\fR program. As a result, if +you make a link to \fInext\fR and that link is not called \fInext\fR, +your link will act like \fIshow\fR instead. To circumvent this, add a +profile\-entry for the link to your \fInmh\fR profile and add the argument +\fInext\fR to the entry. +.En diff --git a/man/nmh.man b/man/nmh.man new file mode 100644 index 0000000..7cc20fc --- /dev/null +++ b/man/nmh.man @@ -0,0 +1,235 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.if '\*(ZZ'-man' \{\ +.TH NMH %manext1% MH.6.8 [%nmhversion%] +.SH NAME +nmh \- new MH message system +.SH SYNOPSIS +.in +.5i +.ti -.5i +any \fInmh\fR command +.in -.5i +.SH DESCRIPTION +\fInmh\fR is the name of a powerful message handling system. Rather than +being a single comprehensive program, \fInmh\fR consists of a collection +of fairly simple single-purpose programs to send, retrieve, save, +and manipulate messages. + +Unlike most mail clients in UNIX, \fInmh\fR is not a closed system which +must be explicitly run, then exited when you wish to return to the shell. +You may freely intersperse \fInmh\fR commands with other shell commands, +allowing you to read and answer your mail while you have (for example) +a compilation running, or search for a file or run programs as needed +to find the answer to someone's question before answering their mail. + +The rest of this manual entry is a quick tutorial which will teach you +the basics of \fInmh\fR. You should read the manual entries for the +individual programs for complete documentation. + +To get started using \fInmh\fR, put the directory \fB%bindir%\fR on your +\fB$PATH\fR. This is best done in one of the files: \fB\&.profile\fR, +\fB\&.login\fR, or \fB\&.cshrc\fR in your home directory. (Check the +manual entry for the shell you use, in case you don't know how to +do this.) Run the \fIinc\fR command. If you've never used \fInmh\fR +before, it will create the necessary default files and directories after +asking you if you wish it to do so. + +\fIinc\fR moves mail from your system maildrop into your \fInmh\fR +`+inbox' folder, breaking it up into separate files and converting it +to \fInmh\fR format as it goes. It prints one line for each message it +processes, containing the from field, the subject field and as much of +the first line of the message as will fit. It leaves the first message +it processes as your current message. You'll need to run \fIinc\fR each +time you wish to incorporate new mail into your \fInmh\fR file. + +\fIscan\fR prints a list of the messages in your current folder. + +The commands: \fIshow\fR, \fInext\fR, and \fIprev\fR are used to read +specific messages from the current folder. \fIshow\fR displays the +current message, or a specific message, which may be specified by its +number, which you pass as an argument to \fIshow\fR. \fInext\fR and +\fIprev\fR display, respectively, the message numerically after or before +the current message. In all cases, the message displayed becomes the +current message. If there is no current message, \fIshow\fR may be +called with an argument, or \fInext\fR may be used to advance to the +first message. + +\fIrmm\fR (remove message) deletes the current message. It may be called +with message numbers passed as arguments, to delete specific messages. + +\fIrepl\fR is used to respond to the current message (by default). +It places you in the editor with a prototype response form. While you're +in the editor, you may peruse the item you're responding to by reading +the file \fB@\fR. After completing your response, type \fBl\fR to list +(review) it, or \fBs\fR to send it. + +\fIcomp\fR allows you to compose a message by putting you in the editor +on a prototype message form, and then lets you send it. + +All the \fInmh\fR commands may be run with the single argument: `\-help', +which causes them to print a list of the arguments they may be invoked +with and then exit. + +All the \fInmh\fR commands may be run with the single argument: +`\-version', which cause them to print the version number of the \fInmh\fR +distribution, and then exit. + +Commands which take a message number as an argument (\fIscan\fR, +\fIshow\fR, \fIrepl\fR, ...) also take one of the words: \fIfirst\fR, +\fIprev\fR, \fIcur\fR, \fInext\fR, or \fIlast\fR to indicate +(respectively) the first, previous, current, next, or last message in +the current folder (assuming they are defined). + +Commands which take a range of message numbers (\fIrmm\fR, \fIscan\fR, +\fIshow\fR, ...) also take any of the abbreviations: +.sp +.in +5 +.ti -3 +.I - +- Indicates all messages in the range to , inclusive. The range +.B must +be nonempty. +.sp +.ti -3 +.I :+N +.ti -3 +.I :-N +- Up to +.I N +messages beginning with (or ending with) message +.I num. +.I Num +may be any of the pre-defined symbols: +.I first, prev, cur, next +or +.I last. +.sp +.ti -3 +.I first:N +.ti -3 +.I prev:N +.ti -3 +.I next:N +.ti -3 +.I last:N +- The first, previous, next or last +.I N +messages, if they exist. +.in -5 + +There are many other possibilities such as creating multiple folders +for different topics, and automatically refiling messages according to +subject, source, destination, or content. These are beyond the scope +of this manual entry. + +Following is a list of all the \fInmh\fR commands: +.\} + +.nf +.in .5i +.ta 1.5i +^ali (1)~^\- list mail aliases +^anno (1)~^\- annotate messages +^burst (1)~^\- explode digests into messages +^comp (1)~^\- compose a message +^dist (1)~^\- redistribute a message to additional addresses +^flist (1)~^\- list folders that contain messages in given sequence(s) +^flists (1)~^\- list all folders that contain messages in given sequence(s) +^folder (1)~^\- set/list current folder/message +^folders (1)~^\- list all folders +^forw (1)~^\- forward messages +^inc (1)~^\- incorporate new mail +^mark (1)~^\- mark messages +^mhbuild (1)~^\- translate MIME composition draft +^mhl (1)~^\- produce formatted listings of nmh messages +^mhlist (1)~^\- list information about content of MIME messages +^mhmail (1)~^\- send or read mail +^mhn (1)~^\- display/list/store/cache MIME messages +^mhparam (1)~^\- print nmh profile components +^mhpath (1)~^\- print full pathnames of nmh messages and folders +^mhshow (1)~^\- display MIME messages +^mhstore (1)~^\- store contents of MIME messages into files +^msgchk (1)~^\- check for messages +^msh (1)~^\- nmh shell (and BBoard reader) +^next (1)~^\- show the next message +^packf (1)~^\- compress a folder into a single file +^pick (1)~^\- select messages by content +^prev (1)~^\- show the previous message +^prompter (1)~^\- prompting editor front end +^rcvdist (1)~^\- asynchronously redistribute new mail +^rcvpack (1)~^\- append message to file +^rcvstore (1)~^\- asynchronously incorporate new mail +^rcvtty (1)~^\- report new mail +^refile (1)~^\- file messages in other folders +^repl (1)~^\- reply to a message +^rmf (1)~^\- remove folder +^rmm (1)~^\- remove messages +^scan (1)~^\- produce a one line per message scan listing +^send (1)~^\- send a message +^sendfiles (1)~^\- send multiple files and directories in MIME message +^show (1)~^\- show (display) messages +^slocal (1)~^\- asynchronously filter and deliver new mail +^sortm (1)~^\- sort messages +^whatnow (1)~^\- prompting front\-end for send +^whom (1)~^\- report to whom a message would go +.if '\*(ZZ'-man' \{\ +.sp 1 +^mh\-alias (5)~^\- alias file for nmh message system +^mh\-draft (5)~^\- draft folder facility +^mh\-format (5)~^\- format file for nmh message system +^mh\-mail (5)~^\- message format for nmh message system +^mh\-profile (5)~^\- user customization for nmh message system +^mh\-sequence (5)~^\- sequence specification for nmh message system +.sp 1 +^ap (8)~^\- parse addresses 822\-style +^conflict (8)~^\- search for alias/password conflicts +^dp (8)~^\- parse dates 822\-style +^fmtdump (8)~^\- decode \fInmh\fP format files +^install\-mh (8)~^\- initialize the nmh environment +^post (8)~^\- deliver a message +.\} +.fi +.re + +.if '\*(ZZ'-man' \{\ +.Fi +^%bindir%~^directory containing \fInmh\fR commands +^%etcdir%~^directory containing \fInmh\fR format files +^%libdir%~^\fInmh\fR library commands +.Bu +If problems are encountered with an \fInmh\fR program, the problems should +be reported to the local maintainers of \fInmh\fR. When doing this, the +name of the program should be reported, along with the version information +for the program. +.br +To find out what version of an \fInmh\fR program is being run, invoke +the program with the `\-version' switch. This information includes +the version of \fInmh\fR, the host it was generated on, and the date the +program was loaded. + +Send bug reports and suggestions to \fBnmh-workers@math.gatech.edu\fR. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.\" .Ps +.\" for each additional profile entry +.\" .Sa +.\" the see\-also's go here +.\" .De +.\" the first default goes here +.\" .Ds +.\" for each additional default +.\" .Co +.\" context changes go here +.\" You can also have +.\" .Hh \- the helpful hints section +.\" .Hi \- the history section +.\" .Bu \- the bugs section +.En +.\} diff --git a/man/packf.man b/man/packf.man new file mode 100644 index 0000000..a9f66cb --- /dev/null +++ b/man/packf.man @@ -0,0 +1,67 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH PACKF %manext1% MH.6.8 [%nmhversion%] +.SH NAME +packf \- pack messages in nmh folder into a single file +.SH SYNOPSIS +.in +.5i +.ti -.5i +packf +\%[+folder] \%[msgs] +\%[\-file\ name] +\%[\-mbox] \%[-mmdf] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIPackf\fR will pack copies of messages from a folder, into a single +file. + +If the `-mbox' switch is given (the default), then the messages are +separated using mbox (uucp) style delimiters. This is the format used +by most mail clients (elm, mailx, etc.). + +If the `-mmdf' switch is given, then the messages are separated by +mmdf style delimiters. Each message in the file is separated by four +CTRL\-A's and a newline. + +You may specify the name of the file in which to use with the +`\-file\ name' switch. If you do specify the name of the file, it +will default to `msgbox'. + +If the given file name points to an existing file, then the specified +messages will be appended to the end of the file, otherwise the file +will be created and the messages appended. + +Messages that are packed by \fIpackf\fR can be unpacked using +\fIinc\fR. + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^\&.msgbox\&.map~^A binary index of the file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Msg\-Protect:~^To set mode when creating a new `file' +.Sa +inc(1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to all +.Ds +`\-mbox' +.Ds +`\-file ./msgbox' +.Co +If a folder is given, it will become the current folder. The first +message packed will become the current message. +.En diff --git a/man/pick.man b/man/pick.man new file mode 100644 index 0000000..a2a54c4 --- /dev/null +++ b/man/pick.man @@ -0,0 +1,264 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH PICK %manext1% MH.6.8 [%nmhversion%] +.SH NAME +pick \- search for messages by content +.SH SYNOPSIS +.in +.5i +.ti -.5i +pick +\%[+folder] \%[msgs] +\%[\-and\ ...] \%[\-or\ ...] \%[\-not\ ...] +.br +\%[\-lbrace\ ...\ \-rbrace] +\%[\-\|\-component\ pattern] +.br +\%[\-cc\ pattern] +\%[\-date\ pattern] +\%[\-from\ pattern] +.br +\%[\-search\ pattern] +\%[\-subject\ pattern] +\%[\-to\ pattern] +.br +\%[\-after\ date] +\%[\-before\ date] +\%[\-datefield\ field] +.br +\%[\-sequence\ name\ ...] +\%[\-public] \%[\-nopublic] +\%[\-zero] +.br +\%[\-nozero] +\%[\-list] \%[\-nolist] +\%[\-version] +\%[\-help] +.ti .5i + +typical usage: +.br +scan\0`pick\0\-from\0jones` +.br +pick\0\-to\0holloway\0\-sequence\0select +.br +show\0`pick\0\-before\0friday` +.in -.5i +.SH DESCRIPTION +\fIPick\fR searches within a folder for messages with the specified +contents, and then identifies those messages. Two types of search +primitives are available: pattern matching and date constraint +operations. + +A modified \fIgrep\fR(1) is used to perform the matching, so the +full regular expression (see \fIed\fR(1)) facility is available +within `pattern'. With `\-search', `pattern' is used directly, +and with the others, the grep pattern constructed is: + +.ti +.5i +\*(lqcomponent[ \\t]*:\&.*pattern\*(rq + +This means that the pattern specified for a `\-search' will be found +everywhere in the message, including the header and the body, while +the other pattern matching requests are limited to the single specified +component. The expression + +.ti +.5i +`\-\|\-component\ pattern' + +is a shorthand for specifying + +.ti +.5i +`\-search \*(lqcomponent[ \\t]*:\&.*pattern\*(rq\ ' + +It is used to pick a component which is not one of \*(lqTo:\*(rq, +\*(lqcc:\*(rq, \*(lqDate:\*(rq, \*(lqFrom:\*(rq, or \*(lqSubject:\*(rq. +An example is `pick\0\-\|\-reply\-to\0pooh'. + +Pattern matching is performed on a per\-line basis. Within the header +of the message, each component is treated as one long line, but in the +body, each line is separate. Lower\-case letters in the search pattern +will match either lower or upper case in the message, while upper case +will match only upper case. + +Note that since the `\-date' switch is a pattern matching operation (as +described above), to find messages sent on a certain date the pattern +string must match the text of the \*(lqDate:\*(rq field of the message. + +Independent of any pattern matching operations requested, the switches +`\-after date' or `\-before date' may also be used to introduce date/time +constraints on all of the messages. By default, the \*(lqDate:\*(rq +field is consulted, but if another date yielding field (such as +\*(lqBB\-Posted:\*(rq or \*(lqDelivery\-Date:\*(rq) should be used, the +`\-datefield\ field' switch may be used. + +With `\-before' and `\-after', \fIpick\fR will actually parse the date +fields in each of the messages specified in `msgs' and compare them +to the date/time specified. If `\-after' is given, then only those +messages whose \*(lqDate:\*(rq field value is chronologically after the +date specified will be considered. The `\-before' switch specifies the +complimentary action. + +Both the `\-after' and `\-before' switches take legal 822\-style date +specifications as arguments. \fIPick\fR will default certain missing +fields so that the entire date need not be specified. These fields +are (in order of defaulting): timezone, time and timezone, date, date +and timezone. All defaults are taken from the current date, time, +and timezone. + +In addition to 822\-style dates, \fIpick\fR will also recognize any of +the days of the week (\*(lqsunday\*(rq, \*(lqmonday\*(rq, and so on), +and the special dates \*(lqtoday\*(rq, \*(lqyesterday\*(rq (24 hours +ago), and \*(lqtomorrow\*(rq (24 hours from now). All days of the +week are judged to refer to a day in the past (e.g., telling \fIpick\fR +\*(lqsaturday\*(rq on a \*(lqtuesday\*(rq means \*(lqlast\ saturday\*(rq +not \*(lqthis\ saturday\*(rq). + +Finally, in addition to these special specifications, \fIpick\fR will +also honor a specification of the form \*(lq\-dd\*(rq, which means +\*(lqdd days ago\*(rq. + +\fIPick\fR supports complex boolean operations on the searching primitives +with the `\-and', `\-or', `\-not', and `\-lbrace\ ...\ \-rbrace' switches. +For example, + +.ti +.5i +.ie t \{\ +pick\0\-after\0yesterday\0\-and\0\-lbrace\0\-from\0freida\0\-or\0\-from\0fear\0\-rbrace +.\} +.el \{\ +pick\0\-after\0yesterday\0\-and +.br +.ti +1i +\-lbrace\0\-from\0freida\0\-or\0\-from\0fear\0\-rbrace +.\} + +identifies messages recently sent by \*(lqfrieda\*(rq or \*(lqfear\*(rq. + +The matching primitives take precedence over the `\-not' switch, which +in turn takes precedence over `\-and' which in turn takes precedence +over `\-or'. To override the default precedence, the `\-lbrace' and +`\-rbrace' switches are provided, which act just like opening and closing +parentheses in logical expressions. + +If no search criteria are given, all the messages specified on the +command line are selected (this defaults to \*(lqall\*(rq). + +Once the search has been performed, if the `\-list' switch is given, the +message numbers of the selected messages are written to the standard +output separated by newlines. This is \fIextremely\fR useful for +quickly generating arguments for other \fInmh\fR programs by using the +\*(lqbackquoting\*(rq syntax of the shell. For example, the command + +.ti +.5i +scan\0`pick\0+todo\0\-after\0\*(lq31 Mar 83 0123 PST\*(rq` + +says to \fIscan\fR those messages in the indicated folder which meet the +appropriate criterion. Note that since \fIpick\fR\0's context changes +are written out prior to \fIscan\fR\0's invocation, you need not give +the folder argument to \fIscan\fR as well. + +Regardless of the operation of the `\-list' switch, the `\-sequence name' +switch may be given once for each sequence the user wishes to define. +For each sequence named, that sequence will be defined to mean exactly +those messages selected by \fIpick\fR. For example, + +.ti +.5i +pick\0\-from\0frated\0\-seq\0fred + +defines a new message sequence for the current folder called +\*(lqfred\*(rq which contains exactly those messages that were selected. + +Note that whenever \fIpick\fR processes a `\-sequence\ name' switch, it +sets `\-nolist'. + +By default, \fIpick\fR will zero the sequence before adding it. This +action can be disabled with the `\-nozero' switch, which means that the +messages selected by \fIpick\fR will be added to the sequence, if it +already exists, and any messages already a part of that sequence will +remain so. + +The `\-public' and `\-nopublic' switches are used by \fIpick\fR in the +same way \fImark\fR uses them. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +mark(1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to all +.Ds +`\-datefield date' +.Ds +`\-zero' +.Ds +`\-list' is the default if no `\-sequence', `\-nolist' otherwise +.Co +If a folder is given, it will become the current folder. +.Hi +In previous versions of \fIMH\fR, the \fIpick\fR command would \fIshow\fR, +\fIscan\fR, or \fIrefile\fR the selected messages. This was rather +\*(lqinverted logic\*(rq from the UNIX point of view, so \fIpick\fR was +changed to define sequences and output those sequences. Hence, \fIpick\fR +can be used to generate the arguments for all other \fIMH\fR commands, +instead of giving \fIpick\fR endless switches for invoking those commands +itself. + +Also, previous versions of \fIpick\fR balked if you didn't specify +a search string or a date/time constraint. The current version does +not, and merely matches the messages you specify. This lets you type +something like: + +.ti +.5i +show\0`pick\0last:20\0\-seq\0fear` + +instead of typing + +.in +.5i +.nf +mark\0\-add\0\-nozero\0\-seq\0fear\0last:20 +show\0fear +.fi +.in -.5i + +Finally, timezones used to be ignored when comparing dates: they aren't +any more. +.Hh +Use \*(lqpick sequence \-list\*(rq to enumerate the messages in a sequence +(such as for use by a shell script). +.Bu +The argument to the `\-after' and `\-before' switches must be interpreted +as a single token by the shell that invokes \fIpick\fR. Therefore, one +must usually place the argument to this switch inside double\-quotes. +Furthermore, any occurrence of `\-datefield' must occur prior to the +`\-after' or `\-before' switch it applies to. + +If \fIpick\fR is used in a back\-quoted operation, such as + +.ti +.5i +scan\0`pick\0\-from\0jones` + +and \fIpick\fR selects no messages (e.g., no messages are from +\*(lqjones\*(rq), then the shell will still run the outer command (e.g., +\*(lqscan\*(rq). Since no messages were matched, \fIpick\fR produced +no output, and the argument given to the outer command as a result of +backquoting \fIpick\fR is empty. In the case of \fInmh\fR programs, +the outer command now acts as if the default `msg' or `msgs' should be +used (e.g., \*(lqall\*(rq in the case of \fIscan\fR\0). To prevent this +unexpected behavior, if `\-list' was given, and if its standard output is +not a tty, then \fIpick\fR outputs the illegal message number \*(lq0\*(rq +when it fails. This lets the outer command fail gracefully as well. + +.sp +The pattern syntax \*(lq[l-r]\*(rq is not supported; each letter to be +matched must be included within the square brackets. +.En diff --git a/man/post.man b/man/post.man new file mode 100644 index 0000000..397901a --- /dev/null +++ b/man/post.man @@ -0,0 +1,112 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH POST %manext8% MH.6.8 [%nmhversion%] +.SH NAME +post \- deliver a message +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/post +\%[\-alias\ aliasfile] +.br +\%[\-filter\ filterfile] \%[\-nofilter] +\%[\-format] \%[\-noformat] +.br +\%[\-mime] \%[\-nomime] +\%[\-msgid] \%[\-nomsgid] +\%[\-verbose] +.br +\%[\-noverbose] +\%[\-watch] \%[\-nowatch] +\%[\-width\ columns] +.br +file +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIPost\fR is the default program called by \fIsend\fR\0(1) to deliver +the message in \fIfile\fR to local and remote users. In fact, most of +the features attributed to \fIsend\fR in its manual page are performed by +\fIpost\fR, with \fIsend\fR acting as a relatively simple preprocessor. +Thus, it is \fIpost\fR which parses the various header fields, appends +From: and Date: lines, and interacts with the mail transport system. +\fIPost\fR will not normally be called directly by the user. + +\fIPost\fR searches the \*(lqTo:\*(rq, \*(lqcc:\*(rq, \*(lqBcc:\*(rq, +\*(lqFcc:\*(rq, and \*(lqResent\-xxx:\*(rq header lines of the specified +message for destination addresses, checks these addresses for validity, +and formats them so as to conform to ARPAnet Internet Message Format +protocol, unless the `\-noformat' flag is set. This will normally cause +\*(lq@\fIlocal\-site\fR\*(rq to be appended to each local destination +address, as well as any local return addresses. The `\-width\ columns' +switch can be used to indicate the preferred length of the header +components that contain addresses. + +If a \*(lqBcc:\*(rq field is encountered, its addresses will be used for +delivery, and the \*(lqBcc:\*(rq field will be removed from the message +sent to sighted recipients. The blind recipients will receive an entirely +new message with a minimal set of headers. Included in the body of the +message will be a copy of the message sent to the sighted recipients. +If `\-filter\ filterfile' is specified, then this copy is filtered +(re\-formatted) by \fImhl\fR prior to being sent to the blind recipients. +Alternately, if the `\-mime' switch is given, then \fIpost\fR will use +the MIME rules for encapsulation. + +The `\-alias\ aliasfile' switch can be used to specify a file that post +should take aliases from. More than one file can be specified, each +being preceded with `\-alias'. In any event, the primary alias file is +read first. + +The `\-msgid' switch indicates that a \*(lqMessage\-ID:\*(rq or +\*(lqResent\-Message\-ID:\*(rq field should be added to the header. + +The `\-verbose' switch indicates that the user should be informed of +each step of the posting/filing process. + +The `\-watch' switch indicates that the user would like to watch the +transport system's handling of the message (e.g., local and \*(lqfast\*(rq +delivery). + +\fIPost\fR consults the environment variable \fB$SIGNATURE\fR to determine +the sender's personal name in constructing the \*(lqFrom:\*(rq line of +the message. + +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +^%etcdir%/MailAliases~^global nmh alias file +^%bindir%/refile~^Program to process Fcc:s +^%libdir%/mhl~^Program to process Bcc:s +.Pr +\fIpost\fR does \fBNOT\fR consult the user's \&.mh\(ruprofile +.Sa +\fIStandard for the Format of ARPA Internet Text Messages\fR (RFC\-822), +.br +mhmail(1), send(1), mh\-mail(5), mh\-alias(5) +.De +`\-alias %etcdir%/MailAliases' +.Ds +`\-format' +.Ds +`\-nomime' +.Ds +`\-nomsgid' +.Ds +`\-noverbose' +.Ds +`\-nowatch' +.Ds +`\-width\ 72' +.Ds +`\-nofilter' +.Co +None +.Bu +\*(lqReply\-To:\*(rq fields are allowed to have groups in them according +to the 822 specification, but \fIpost\fR won't let you use them. +.En diff --git a/man/prev.man b/man/prev.man new file mode 100644 index 0000000..5bb1619 --- /dev/null +++ b/man/prev.man @@ -0,0 +1,62 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH PREV %manext1% MH.6.8 [%nmhversion%] +.SH NAME +prev \- show the previous message +.SH SYNOPSIS +.in +.5i +.ti -.5i +prev +\%[+folder] +\%[\-showproc\ program] +\%[\-showmimeproc\ program] +.br +\%[\-header] \%[\-noheader] +\%[\-checkmime] \%[\-nocheckmime] +.br +\%[\-switches\ for\ \fIshowproc\fR or\ \fIshowmimeproc\fR] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIPrev\fR performs a \fIshow\fR on the previous message in the specified +(or current) folder. Like \fIshow\fR, it passes any switches on to +the program named by \fIshowproc\fR or \fIshowmimeproc\fR, which is called +to list the message. This command is almost exactly equivalent to +\*(lqshow prev\*(rq. Consult the manual entry for \fIshow\fR\0(1) for +all the details. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^showproc:~^Program to show non-MIME messages +.Ps +^showmimeproc:~^Program to show MIME messages +.Sa +show(1), next(1) +.De +`+folder' defaults to the current folder +.Ds +`\-checkmime' +.Ds +`\-header' +.Co +If a folder is specified, it will become the current folder. The message +that is shown (i.e., the previous message in sequence) will become the +current message. +.Bu +\fIprev\fR is really a link to the \fIshow\fR program. As a result, if +you make a link to \fIprev\fR and that link is not called \fIprev\fR, +your link will act like \fIshow\fR instead. To circumvent this, add a +profile\-entry for the link to your \fInmh\fR profile and add the argument +\fIprev\fR to the entry. +.En diff --git a/man/prompter.man b/man/prompter.man new file mode 100644 index 0000000..d8a2ef0 --- /dev/null +++ b/man/prompter.man @@ -0,0 +1,116 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH PROMPTER %manext1% MH.6.8 [%nmhversion%] +.SH NAME +prompter \- prompting editor front-end for nmh +.SH SYNOPSIS +.in +.5i +.ti -.5i +prompter +\%[\-erase\ chr] +\%[\-kill\ chr] +\%[\-prepend] \%[\-noprepend] +\%[\-rapid] \%[\-norapid] +\%[\-doteof] \%[\-nodoteof] +file +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIPrompter\fR is an editor front\-end for \fInmh\fR which allows rapid +composition of messages. This program is not normally invoked directly by +users but takes the place of an editor and acts as an editor front\-end. +It operates on an RFC\-822 style message draft skeleton specified by +file, normally provided by the nmh commands \fIcomp\fR, \fIdist\fR, +\fIforw\fR, or \fIrepl\fR. + +\fIPrompter\fR is particularly useful when composing messages over slow +network or modem lines. It is an \fInmh\fR program in that it can have +its own profile entry with switches, but it is not invoked directly by +the user. The commands \fIcomp\fR, \fIdist\fR, \fIforw\fR, and \fIrepl\fR +invoke \fIprompter\fR as an editor, either when invoked with +`\-editor\ prompter', or by the profile entry \*(lqEditor:\ prompter\*(rq, +or when given the command `edit\ prompter' at the \*(lqWhat now?\*(rq prompt. + +For each empty component \fIprompter\fR finds in the draft, the user +is prompted for a response; A will cause the whole component +to be left out. Otherwise, a `\\' preceding a will continue +the response on the next line, allowing for multiline components. +Continuation lines \fBmust\fR begin with a space or tab. + +Each non\-empty component is copied to the draft and displayed on the +terminal. + +The start of the message body is denoted by a blank line or a line +of dashes. If the body is non\-empty, the prompt, which isn't written +to the file, is + + \*(lq--------Enter additional text\*(rq, + +or (if `\-prepend' was given) + + \*(lq--------Enter initial text\*(rq. + +Message\-body typing is terminated with an end\-of\-file (usually +CTRL\-D). With the `\-doteof' switch, a period on a line all by itself +also signifies end\-of\-file. At this point control is returned to +the calling program, where the user is asked \*(lqWhat now?\*(rq. +See \fIwhatnow\fR for the valid options to this query. + +By using the `\-prepend' switch, the user can add type\-in to the +beginning of the message body and have the rest of the body follow. +This is useful for the \fIforw\fR command. + +By using the `\-rapid' switch, if the draft already contains text in +the message\-body, it is not displayed on the user's terminal. This is +useful for low\-speed terminals. + +The line editing characters for kill and erase may be specified by the +user via the arguments `\-kill\ chr' and `\-erase\ chr', where chr may +be a character; or `\\nnn', where \*(lqnnn\*(rq is the octal value for +the character. + +An interrupt (usually CTRL\-C) during component typing will abort +\fIprompter\fR and the \fInmh\fR command that invoked it. An interrupt +during message\-body typing is equivalent to CTRL\-D, for historical +reasons. This means that \fIprompter\fR should finish up and exit. + +The first non\-flag argument to \fIprompter\fR is taken as the name of +the draft file, and subsequent non\-flag arguments are ignored. +.\" (\fIRepl\fR invokes editors with two file arguments: +.\" the draft file name and the replied\-to message file name.) +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^/tmp/prompter*~^Temporary copy of message +.Pr +prompter\-next: To name the editor to be used on exit from \fIprompter\fR +.Ps +^Msg\-Protect:~^To set mode when creating a new draft +.Sa +comp(1), dist(1), forw(1), repl(1), whatnow(1) +.De +`\-prepend' +.Ds +`\-norapid' +.Ds +`\-nodoteof' +.Co +None +.Hh +The `\-rapid' option is particularly useful with \fIforw\fP, and +`\-noprepend' is useful with \fIcomp\ \-use\fP. + +The user may wish to link \fIprompter\fR under several names (e.g., +\*(lqrapid\*(rq) and give appropriate switches in the profile entries +under these names (e.g., \*(lqrapid: -rapid\*(rq). This facilitates +invoking prompter differently for different \fInmh\fP commands (e.g., +\*(lqforw: -editor rapid\*(rq). +.Bu +\fIPrompter\fR uses \fIstdio\fR\0(3), so it will lose if you edit files +with nulls in them. +.En diff --git a/man/rcvdist.man b/man/rcvdist.man new file mode 100644 index 0000000..1a2cfe5 --- /dev/null +++ b/man/rcvdist.man @@ -0,0 +1,60 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH RCVDIST %manext1% MH.6.8 [%nmhversion%] +.SH NAME +rcvdist \- asynchronously redistribute new mail +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/rcvdist +\%[\-form\ formfile] +.br +\%[switches\ for\ \fIpostproc\fR] +address1\ ... +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +The \fIrcvdist\fR program will accept a message on its standard input +and resend a copy of this message to all of the addresses listed on its +command line. + +When a message is redistributed with the \fIrcvdist\fR command, the +format of the Resent-xxx header fields is controlled by the forms files +"rcvdistcomps". If a file named "rcvdistcomps" exists in the user's nmh +directory, it will be used instead of the default one. You may specify +an alternate forms file with the switch `\-form\ formfile'. + +The "rcvdistcomps" file uses the format string facility described in +\fImh\-format\fR(5). In addition to the standard format escapes, +\fIrcvdist\fP also recognizes the following additional \fIcomponent\fR +escape: +.sp 1 +.ne 5 +.nf +.ta \w'Dtimenow 'u +\w'Returns 'u +\fIEscape\fR \fIReturns\fR \fIDescription\fR +addresses string the addresses to distribute to +.re +.fi + +By default, \fIrcvdist\fR uses the program \fIpost\fR(8) to do the actual +delivery of the message, although this can be changed by defining the +\fIpostproc\fR profile component. +.Fi +^%etcdir%/rcvdistcomps~^Default message skeleton +^or /rcvdistcomps~^Rather than standard message skeleton +^%etcdir%/mts.conf~^nmh mts configuration file +^$HOME/\&.maildelivery~^The file controlling local delivery +^%etcdir%/maildelivery~^Rather than the standard file +.Sa +rcvpack(1), rcvstore(1), rcvtty(1), mh\-format(5), slocal(1) +.Bu +Only two return codes are meaningful, others should be. +.En diff --git a/man/rcvpack.man b/man/rcvpack.man new file mode 100644 index 0000000..ed95ad0 --- /dev/null +++ b/man/rcvpack.man @@ -0,0 +1,45 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH RCVPACK %manext1% MH.6.8 [%nmhversion%] +.SH NAME +rcvpack \- append message to file +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/rcvpack +file +\%[-mbox] \%[-mmdf] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +The \fIrcvpack\fR program will append a copy of the message to the file +listed on its command line. + +If the `-mbox' switch is given (the default), then the messages are +separated using mbox (uucp) style delimiters. This is the format used +by most mail clients (elm, mailx, etc.). + +If the `-mmdf' switch is given, then the messages are separated by +mmdf style delimiters. Each message in the file is separated by four +CTRL\-A's and a newline. + +\fIrcvpack\fR will correctly lock and unlock the file to serialize +access to the file, when running multiple copies of \fIrcvpack\fR. + +In general, its use is obsoleted by the \*(lqfile\*(rq action of +\fIslocal\fR, although it might still have occasional uses in various +shell scripts. +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +.Sa +rcvdist(1), rcvstore(1), rcvtty(1), slocal(1) +.Bu +Only two return codes are meaningful, others should be. +.En diff --git a/man/rcvstore.man b/man/rcvstore.man new file mode 100644 index 0000000..eeb164d --- /dev/null +++ b/man/rcvstore.man @@ -0,0 +1,100 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH RCVSTORE %manext1% MH.6.8 [%nmhversion%] +.SH NAME +rcvstore \- asynchronously incorporate mail into a folder +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/rcvstore +\%[+folder] +.br +\%[\-create] \%[\-nocreate] +\%[\-unseen] \%[\-nounseen] +.br +\%[\-zero] \%[\-nozero] +\%[\-public] \%[\-nopublic] +.br +\%[\-sequence\ name\ ...] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIRcvstore\fR incorporates a message from the standard input into an +\fInmh\fR folder. This command is typically used in conjunction with +mail filtering programs such as \fIslocal\fR and \fIprocmail\fR, to +filter your mail into different folders. + +You may specify which folder to use with `+folder'. If no folder is +specified, \fIrcvstore\fP will use the folder given by a non\-empty +\*(lqInbox:\*(rq entry in the user's profile, else it will use the folder +named \*(lqinbox\*(rq. + +If the switch `\-create' is given (it is the default) and if the specified +(or default) folder does not exist, then it will be created. You may +disable this with the `\-nocreate' option. In this case \fIrcvstore\fP +will exit if the specified folder does not exist. + +When the new message is incorporated into the folder, it is assigned +the next highest number for that folder. + +\fIRcvstore\fR will incorporate anything except zero length messages +into the user's nmh folder. It will not change the message in any +way. + +If the user's profile contains a \*(lqMsg\-Protect: nnn\*(rq entry, it +will be used as the protection on the newly created message, otherwise +the \fInmh\fR default of 0644 will be used. For all subsequent operations +on this message, this initially assigned protection will be preserved. + +If the switch `\-unseen' is given (it is on by default), and if the +profile entry \*(lqUnseen\-Sequence\*(rq is present and non\-empty, then +\fIrcvstore\fR will add the newly incorporated message to each sequence +named by this profile entry. You may use the switch `\-nounseen' to +disable this. These sequences will not be zero'ed by \fIrcvstore\fR +prior to adding the new message. + +Furthermore, the incoming message may be added to additional sequences +as they arrive by the use of the `\-sequence' switch. As with the +commands \fIpick\fP and \fImark\fP, you may also use the switches +`\-zero' and `\-nozero' to specify whether to zero old sequences or not. +Similarly, use of the `\-public' and `\-nopublic switches may be used +to force these sequences to be public or private sequences. + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Folder\-Protect:~^To set mode when creating a new folder +.Ps +^Inbox:~^To find the default inbox +.Ps +^Msg\-Protect:~^To set mode when creating a new message +.Ps +^Unseen\-Sequence:~^To name sequences denoting unseen messages +.Sa +rcvdist(1), rcvpack(1), rcvtty(1), mh\-sequence(5) +.De +`+folder' defaults to \*(lqInbox\*(rq profile entry +.Ds +`\-create' +.Ds +`\-unseen' +.Ds +`\-nozero' +.Co +No context changes will be attempted, with the exception of +sequence manipulation. +.Bu +If you use the \*(lqUnseen\-Sequence\*(rq profile entry, \fIrcvstore\fP +could try to update the context while another \fInmh\fP process +is also trying to do so. This can cause the context to become +corrupted. To avoid this, do not use \fIrcvstore\fP if you use the +\*(lqUnseen\-Sequence\*(rq profile entry. +.En diff --git a/man/rcvtty.man b/man/rcvtty.man new file mode 100644 index 0000000..a00d1b1 --- /dev/null +++ b/man/rcvtty.man @@ -0,0 +1,84 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH RCVTTY %manext1% MH.6.8 [%nmhversion%] +.SH NAME +rcvtty \- report new mail +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/rcvtty +\%[command] +.br +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-width\ columns] +.br +\%[\-bell] \%[\-nobell] +\%[\-newline] +\%[\-nonewline] +\%[\-biff] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +The \fIrcvtty\fR can be used to report new mail. It is used primarily +in conjunction with mail filtering agents such as \fIslocal\fP or +\fIprocmail\fP. + +The \fIrcvtty\fR program executes the named command with the message as +its standard input, and writes the resulting output on your terminal. + +Alternately, if no command is specified (or is bogus), then \fIrcvtty\fR +will instead write a one\-line scan listing. The default output format +of this scan listing may be overridden by using either the +`\-form\ formatfile' or `\-format\ string' option, similar to the +equivalent options for \fIscan\fP and \fIinc\fP. +See \fImh\-format\fP(5) for details. + +A newline is output before the message output, and the terminal bell is +rung after the output. The `\-nonewline' and `\-nobell' options will +inhibit these functions. + +The switch `\-width\ columns' may be given to specify the width of +the scan line. The default is to use the width of the terminal. + +In addition to the standard format escapes described in +\fImh\-format\fR(5), \fIrcvtty\fR also recognizes the following additional +\fIcomponent\fR escapes: +.sp 1 +.ne 5 +.nf +.ta \w'Dtimenow 'u +\w'Returns 'u +\fIEscape\fR \fIReturns\fR \fIDescription\fR +body string the (compressed) first part of the body +dtimenow date the current date +folder string the name of the current folder +.re +.fi + +By default, \fIrcvtty\fP will send its output to every terminal on the +local machine that is owned by current user, and that has given write +permission as granted by the command \fImesg\fP\0(1). If the option +`\-biff' is given, then \fIrcvtty\fP will obey the notification status +set by the command \fIbiff\fP\0(1) instead. +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +^$HOME/\&.maildelivery~^The file controlling local delivery +^%etcdir%/maildelivery~^Rather than the standard file +.De +`\-width' defaults to the width of the terminal +.Ds +`\-newline' +.Ds +`\-bell' +.Sa +rcvdist(1), rcvpack(1), rcvstore(1), mh\-format(5), slocal(1) +.Bu +Only two return codes are meaningful, others should be. +.En diff --git a/man/refile.man b/man/refile.man new file mode 100644 index 0000000..778616e --- /dev/null +++ b/man/refile.man @@ -0,0 +1,143 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH REFILE %manext1% MH.6.8 [%nmhversion%] +.SH NAME +refile \- file message in other folders +.SH SYNOPSIS +.in +.5i +.ti -.5i +refile +\%[msgs] +\%[\-draft] +\%[\-link] \%[\-nolink] +.br +\%[\-preserve] \%[\-nopreserve] +\%[\-unlink] \%[\-nounlink] +.br +\%[\-src\ +folder] +\%[\-file\ file] +\%[\-rmmproc program] +.br +\%[\-normmproc] ++folder1 ... +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIRefile\fR moves (\fImv\fR\0(1)) or links (\fIln\fR\0(1)) messages +from a source folder into one or more destination folders. + +If you think of a message as a sheet of paper, this operation is not +unlike filing the sheet of paper (or copies) in file cabinet folders. +When a message is filed, it is linked into the destination folder(s) +if possible, and is copied otherwise. As long as the destination +folders are all on the same file system, multiple filing causes little +storage overhead. This facility provides a good way to cross\-file or +multiply\-index messages. For example, if a message is received from +Jones about the ARPA Map Project, the command + + refile\0cur\0+jones\0+Map + +would allow the message to be found in either of the two folders `jones' +or `Map'. + +You may specify the source folder using `\-src\ +folder'. If this is +not given, the current folder is used by default. If no message is +specified, then `cur' is used by default. + +The option `\-file\ file' directs \fIrefile\fR to use the specified file +as the source message to be filed, rather than a message from a folder. +Note that the file should be a validly formatted message, just like +any other \fInmh\fR message. It should \fBNOT\fR be in mail drop format +(to convert a file in mail drop format to a folder of \fInmh\fR messages, +see \fIinc\fR\0(1)). + +If a destination folder doesn't exist, \fIrefile\fR will ask if you want +to create it. A negative response will abort the file operation. If the +standard input for \fIrefile\fR is \fInot\fR a tty, then \fIrefile\fR +will not ask any questions and will proceed as if the user answered +\*(lqyes\*(rq to all questions. + +The option `\-link' preserves the source folder copy of the message (i.e., +it does a \fIln\fR(1) rather than a \fImv\fR(1)), whereas, `\-nolink' +(the default) deletes the filed messages from the source folder. + +Normally when a message is refiled, for each destination folder it +is assigned the number which is one above the current highest message +number in that folder. Use of the `\-preserve' switch will override +this message renaming, and try to preserve the number of the message. +If a conflict for a particular folder occurs when using the `\-preserve' +switch, then \fIrefile\fR will use the next available message number +which is above the message number you wish to preserve. + +If `\-link' is not specified (or `\-nolink' is specified), the filed +messages will be removed from the source folder. The default is to +remove these messages by renaming them with a site-dependent prefix +(usually a comma). Such files will then need to be removed in some +manner after a certain amount of time. Many sites arrange for +\fIcron\fR\0(8) to remove these files once a day, so check with your +system administrator. + +Alternately, if you wish for \fIrefile\fR to really remove the files +representing these messages from the source folder, you can use the +`-unlink' switch (not to be confused with the -link switch). But +messages removed by this method cannot be later recovered. + +.ne 4 +If you prefer a more sophisticated method of `removing' the messages +from the source folder, you can define the \fIrmmproc\fR profile +component. For example, you can add a profile component such as + + rmmproc: /home/coleman/bin/rmm_msgs + +then \fIrefile\fR will instead call the named program or script to +handle the message files. + +The user may specify `\-rmmproc program' on the command line to +override this profile specification. The `-normmproc' option forces +the message files to be deleted by renaming or unlinking them as +described above. + +The `\-draft' switch tells \fIrefile\fR to file the /draft. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Folder\-Protect:~^To set mode when creating a new folder +.Ps +^rmmproc:~^Program to delete the message +.Sa +folder(1), rmf(1), rmm(1) +.De +`\-src\ +folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-nolink' +.Ds +`\-nounlink' +.Ds +`\-nopreserve' +.Co +If `\-src\ +folder' is given, it will become the current folder. +If neither `\-link' nor `all' is specified, the current message in the +source folder will be set to the last message specified; otherwise, the +current message won't be changed. + +If the Previous\-Sequence profile entry is set, in addition to defining +the named sequences from the source folder, \fIrefile\fR will also define +those sequences for the destination folders. See \fImh\-sequence\fR\0(5) +for information concerning the previous sequence. +.Bu +Since \fIrefile\fR uses your \fIrmmproc\fP to delete the message, +the \fIrmmproc\fP must \fBNOT\fP call \fIrefile\fP without specifying +`\-normmproc', or you will create an infinite loop. +.En diff --git a/man/repl.man b/man/repl.man new file mode 100644 index 0000000..a7e1165 --- /dev/null +++ b/man/repl.man @@ -0,0 +1,337 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH REPL %manext1% MH.6.8 [%nmhversion%] +.SH NAME +repl \- reply to a message +.SH SYNOPSIS +.in +.5i +.ti -.5i +repl +\%[+folder] \%[msg] +.br +\%[\-group] \%[\-nogroup] +\%[\-annotate] \%[\-noannotate] +.br +\%[\-cc\ all/to/cc/me] \%[\-nocc\ all/to/cc/me] +.br +\%[\-query] \%[\-noquery] +\%[\-form\ formfile] +.br +\%[\-format] \%[\-noformat] +\%[\-filter\ filterfile] +.br +\%[\-inplace] \%[\-noinplace] +\%[\-mime] \%[\-nomime] +.br +\%[\-fcc\ +folder] +\%[\-width\ columns] +.br +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] +.br +\%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +.br +\%[\-whatnowproc\ program] \%[\-nowhatnowproc] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIRepl\fR may be used to produce a reply to an existing message. + +In its simplest form (with no arguments), \fIrepl\fR will set up a +message\-form skeleton in reply to the current message in the current +folder, and invoke the whatnow shell. + +In order to construct the message draft of the reply, \fIrepl\fR uses +a reply template to guide its actions. A reply template is simply a +\fImhl\fR format file (see \fImh\-format\fR\0(5) for details). + +If the switch `\-nogroup' is given (it is on by default), then \fIrepl\fR +will use the standard forms file \*(lqreplcomps\*(rq. This will construct +a draft message that is intended to be sent only to the author of the +message to which you are replying. If a file named \*(lqreplcomps\*(rq +exists in the user's nmh directory, it will be used instead of this +default forms file. + +The default reply template \*(lqreplcomps\*(rq will direct \fIrepl\fR +to construct the reply message draft as follows: + +.nf +.in 1i +To: or or +Subject: Re: +In\-Reply\-To: Your message of . +.ti +\w'In\-Reply\-To: 'u + +.in .5i +.fi + +where field names enclosed in angle brackets (<\ >) indicate the +contents of the named field from the message to which the reply is +being made. + +If the switch `\-group' is given, then \fIrepl\fR will use the the +standard forms file \*(lqreplgroupcomps\*(rq. This will construct a +draft message that is intended as a group or followup reply. If a file +named \*(lqreplgroupcomps\*(rq exists in the user's nmh directory, it +will be used instead of this default forms file. + +The default group reply template \*(lqreplgroupcomps\*(rq will direct +\fIrepl\fR to construct the reply message draft as follows: + +.nf +.in 1i +To: +Subject: Re: +In\-Reply\-To: Message from of . +.ti +\w'In\-Reply\-To: 'u + +.in .5i +.fi + +or if the field is not available: + +.nf +.in 1i +To: or or +cc: and and +Subject: Re: +In\-Reply\-To: Message from of . +.ti +\w'In\-Reply\-To: 'u + +.in .5i +.fi + +In any case, you may specify an alternate forms file with the switch +`\-form\ formfile'. + +You may selectively remove addresses from this default with the +`\-nocc\ type' switch. This switch takes an argument (all/to/cc/me) +which specifies who gets removed from the default \*(lqcc:\*(rq list of +the reply. You may give this switch multiple times (with different +arguments) if you wish to remove multiple types of addresses. + +The `\-query' switch modifies the action of `\-nocc\ type' switch by +interactively asking you if each address that normally would be placed in +the \*(lqTo:\*(rq and \*(lqcc:\*(rq list should actually be sent a copy. +This is useful for special\-purpose replies. Note that the position of +the `\-cc' and `\-nocc' switches, like all other switches which take a +positive and negative form, is important. + +Lines beginning with the fields \*(lqTo:\*(rq, \*(lqcc:\*(rq, and +\*(rqBcc:\*(rq will be standardized and have duplicate addresses removed. +In addition, the `\-width\ columns' switch will guide \fIrepl\fR's +formatting of these fields. + +If the draft already exists, \fIrepl\fR will ask you as to the disposition +of the draft. A reply of \fBquit\fR will abort \fIrepl\fR, leaving the +draft intact; \fBreplace\fR will replace the existing draft with a blank +skeleton; and \fBlist\fR will display the draft. + +See \fIcomp\fR\0(1) for a description of the `\-editor' and `\-noedit' +switches. Note that while in the editor, the message being replied +to is available through a link named \*(lq@\*(rq (assuming the default +\fIwhatnowproc\fR\0). In addition, the actual pathname of the message is +stored in the environment variable \fB$editalt\fR, and the pathname of +the folder containing the message is stored in the environment variable +\fB$mhfolder\fR. + +Although \fIrepl\fR uses a forms file to direct it how to construct +the beginning of the draft, it uses a message filter file to direct +it as to how the message to which you are replying should be filtered +(re\-formatted) in the body of the draft. The filter file for \fIrepl\fR +should be a standard form file for \fImhl\fR, as \fIrepl\fR will invoke +\fImhl\fR to format the message to which you are replying. + +The switches `\-noformat', `\-format', and `\-filter\ filterfile' specify +which message filter file to use. + +If the switch `\-noformat' is given (it is the default), then the message +to which you are replying is not included in the body of the draft. + +If the switch `\-format' is given, then a default message filter file +is used. This default message filter should be adequate for most users. +This default filter \*(lqmhl.reply\*(rq is: + +.nf +.in +.5i +.ne 10 +.eo +.so %etcdir%/mhl.reply +.ec +.in -.5i +.fi + +which outputs each line of the body of the message prefaced with the +\*(lq>\*(rq character and a space. + +If a file named \*(lqmhl.reply\*(rq exists in the user's nmh directory, +it will be used instead of this form. You may specify an alternate +message filter file with the switch `\-filter\ filterfile'. + +Other reply filters are commonly used, such as: + +.nf +.in +.5i +: +body:nocomponent,compwidth=9,offset=9 +.in -.5i +.fi + +which says to output a blank line and then the body of the message +being replied\-to, indented by one tab\-stop. Another popular format +is: + +.nf +.in +.5i +.ie n \{ +message-id:nocomponent,\|nonewline,\\ +formatfield=\*(lqIn message %{text},\ \*(rq \} +.el message-id:nocomponent,\|nonewline,\|formatfield=\*(lqIn message %{text},\ \*(rq +from:nocomponent,\|formatfield=\*(lq%(friendly{text}) writes:\*(rq +body:component=\*(lq>\*(rq,\|overflowtext=\*(lq>\*(rq,\|overflowoffset=0 +.in -.5i +.fi + +This message filter file cites the Message-ID and author of the message +being replied\-to, and then outputs each line of the body prefaced with +the \*(lq>\*(rq character. + +To use the MIME rules for encapsulation, specify the `\-mime' switch. +This directs \fIreply\fR to generate an \fImhbuild\fR composition file. +Note that nmh will not invoke \fImhbuild\fR automatically, unless you +add this line to your \&.mh\(ruprofile file: +.sp +.in +.5i +automimeproc: 1 +.in -.5i +.sp +Otherwise, you must specifically give the command +.sp +.in +.5i +What now? mime +.in -.5i +.sp +prior to sending the draft. + +If the `\-annotate' switch is given, the message being replied\-to will +be annotated with the lines + + Replied:\ date + Replied:\ addrs + +where the address list contains one line for each addressee. +The annotation will be done only if the message is sent directly from +\fIrepl\fR. If the message is not sent immediately from \fIrepl\fR, +\*(lqcomp\ \-use\*(rq may be used to re\-edit and send the constructed +message, but the annotations won't take place. Normally annotations are +done inplace in order to preserve any links to the message. You may use +the `\-noinplace' switch to change this. + +The `\-fcc\ +folder' switch can be used to automatically specify a folder +to receive Fcc:s. More than one folder, each preceded by `\-fcc' can +be named. + +In addition to the standard \fImh\-format\fR\0(5) escapes, \fIrepl\fR +also recognizes the following additional \fIcomponent\fR escape: +.sp 1 +.nf +.ta \w'Escape 'u +\w'Returns 'u +\fIEscape\fR \fIReturns\fR \fIDescription\fR +\fIfcc\fR string Any folders specified with `\-fcc\ folder' +.re +.fi + +To avoid reiteration, \fIrepl\fR strips any leading `Re: ' strings from +the \fIsubject\fR component. + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +Upon exiting from the editor, \fIrepl\fR will invoke the \fIwhatnow\fR +program. See \fIwhatnow\fR\0(1) for a discussion of available +options. The invocation of this program can be inhibited by using the +`\-nowhatnowproc' switch. (In truth of fact, it is the \fIwhatnow\fR +program which starts the initial edit. Hence, `\-nowhatnowproc' will +prevent any edit from occurring.) + +.Fi +^%etcdir%/replcomps~^The standard reply template +^or /replcomps~^Rather than the standard template +^%etcdir%/replgroupcomps~^The standard `reply -group' template +^or /replgroupcomps~^Rather than the standard template +^%etcdir%/mhl.reply~^The standard message filter +^or /mhl.reply~^Rather than the standard filter +^$HOME/\&.mh\(ruprofile~^The user profile +^/draft~^The draft file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Alternate\-Mailboxes:~^To determine the user's mailboxes +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Editor:~^To override the default editor +.Ps +^Msg\-Protect:~^To set mode when creating a new message (draft) +.Ps +^fileproc:~^Program to refile the message +.Ps +^mhlproc:~^Program to filter message being replied\-to +.Ps +^whatnowproc:~^Program to ask the \*(lqWhat now?\*(rq questions +.Sa +mhbuild(1), comp(1), forw(1), send(1), whatnow(1), mh\-format(5) +.De +`+folder' defaults to the current folder +.Ds +`msg' defaults to cur +.Ds +`\-nogroup' +.Ds +`\-cc\ all' +.Ds +`\-noannotate' +.Ds +`\-nodraftfolder' +.Ds +`\-noformat' +.Ds +`\-inplace' +.Ds +`\-nomime' +.Ds +`\-noquery' +.Ds +`\-width\ 72' +.Co +If a folder is given, it will become the current folder. The message +replied\-to will become the current message. +.Bu +If any addresses occur in the reply template, addresses in the template +that do not contain hosts are defaulted incorrectly. Instead of using +the localhost for the default, \fIrepl\fR uses the sender's host. +Moral of the story: if you're going to include addresses in a reply +template, include the host portion of the address. + +The `\-width columns' switch is only used to do address-folding; other +headers are not line\-wrapped. + +If \fIwhatnowproc\fR is \fIwhatnow\fR, then \fIrepl\fR uses a built\-in +\fIwhatnow\fR, it does not actually run the \fIwhatnow\fR program. +Hence, if you define your own \fIwhatnowproc\fR, don't call it +\fIwhatnow\fR since \fIrepl\fR won't run it. + +If your current working directory is not writable, the link named +\*(lq@\*(rq is not available. +.En diff --git a/man/rmf.man b/man/rmf.man new file mode 100644 index 0000000..de9cd8f --- /dev/null +++ b/man/rmf.man @@ -0,0 +1,70 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH RMF %manext1% MH.6.8 [%nmhversion%] +.SH NAME +rmf \- remove an nmh folder +.SH SYNOPSIS +.in +.5i +.ti -.5i +rmf +\%[+folder] +\%[\-interactive] \%[\-nointeractive] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIRmf\fR removes all of the messages (files) within the specified +(or default) folder, and then removes the folder (directory) itself. + +If there are any files within the folder which are not a part of +\fInmh\fR, they will \fInot\fR be removed, and an error will be produced. +If the folder is given explicitly or the `\-nointeractive' option is +given, then the folder will be removed without confirmation. Otherwise, +the user will be asked for confirmation. If \fIrmf\fR can't find the +current folder, for some reason, the folder to be removed defaults to +`+inbox' (unless overridden by user's profile entry \*(lqInbox\*(rq) +with confirmation. + +If the folder being removed is a subfolder, the parent folder will become +the new current folder, and \fIrmf\fR will produce a message telling the +user this has happened. This provides an easy mechanism for selecting +a set of messages, operating on the list, then removing the list and +returning to the current folder from which the list was extracted. + +If \fIrmf\fR is used on a read\-only folder, it will delete all the +(private) sequences +(i.e., \*(lqatr\-\fIseq\fR\-\fIfolder\fR\*(rq entries) for this folder +from your context without affecting the folder itself. + +\fIRmf\fR irreversibly deletes messages that don't have other links, so +use it with caution. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Inbox:~^To find the default inbox +.Sa +rmm(1) +.De +`+folder' defaults to the current folder, usually with confirmation +.Ds +`\-interactive' if +folder' not given, `\-nointeractive' otherwise +.Co +\fIRmf\fR will set the current folder to the parent folder if a +subfolder is removed; or if the current folder is removed, it will make +\*(lqinbox\*(rq current. Otherwise, it doesn't change the current folder +or message. +.Bu +Although intuitively one would suspect that \fIrmf\fR works recursively, +it does not. Hence if you have a sub\-folder within a folder, in order +to \fIrmf\fR the parent, you must first \fIrmf\fR each of the children. +.En diff --git a/man/rmm.man b/man/rmm.man new file mode 100644 index 0000000..8773853 --- /dev/null +++ b/man/rmm.man @@ -0,0 +1,76 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH RMM %manext1% MH.6.8 [%nmhversion%] +.SH NAME +rmm \- remove messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +rmm +\%[+folder] \%[msgs] +\%[\-unlink] \%[\-nounlink] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +By default, \fIrmm\fR will remove the specified messages by renaming +the message files with preceding commas. Such files will then need to +be removed in some manner after a certain amount of time. Many sites +arrange for \fIcron\fR\0(8) to remove these files once a day, so check +with your system administrator. + +Alternately, if you wish for \fIrmm\fR to really remove the files +representing these messages, you can use the `-unlink' switch. But +messages removed by this method cannot be later recovered. + +If you prefer a more sophisticated method of `removing' messages, you +can define the \fIrmmproc\fR profile component. For example, you can +add a profile component such as + + rmmproc: /home/coleman/bin/rmm_msgs + +then instead of simply renaming the message file, \fIrmm\fR will call +the named program or script to handle the files that represent the +messages to be deleted. + +Some users of csh prefer the following: + + alias rmm 'refile +d' + +where folder +d is a folder for deleted messages, and + + alias mexp 'rm `mhpath +d all`' + +is used to \*(lqexpunge\*(rq deleted messages. + +The current message is not changed by \fIrmm\fR, so a \fInext\fR will +advance to the next message in the folder as expected. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^rmmproc:~^Program to delete the message +.Sa +refile(1), rmf(1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`-nounlink' +.Co +If a folder is given, it will become the current folder. +.Bu +Since \fIrefile\fR uses your \fIrmmproc\fP to delete the message, +the \fIrmmproc\fP must \fBNOT\fP call \fIrefile\fP without specifying +`\-normmproc', or you will create an infinte loop. +.En diff --git a/man/scan.man b/man/scan.man new file mode 100644 index 0000000..d4ad6a7 --- /dev/null +++ b/man/scan.man @@ -0,0 +1,162 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH SCAN %manext1% MH.6.8 [%nmhversion%] +.SH NAME +scan \- produce a one line per message scan listing +.SH SYNOPSIS +.in +.5i +.ti -.5i +scan +\%[+folder] \%[msgs] +\%[\-clear] \%[\-noclear] +\%[\-form\ formatfile] +\%[\-format\ string] +\%[\-header] \%[\-noheader] +\%[\-width\ columns] +\%[\-reverse] \%[\-noreverse] +\%[\-file filename] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIScan\fR produces a one\-line\-per\-message listing of the specified +folder or messages. Each \fIscan\fR line contains the message number +(name), the date, the \*(lqFrom:\*(rq field, the \*(lqSubject\*(rq field, +and, if room allows, some of the body of the message. For example: + +.nf +.in +.5i +.ta \w'15+- 'u +\w'07/\|05x 'u +\w'Dcrocker 'u +15+ 10/\|05 crocker nned\0\0\*(<> if the body is sufficiently short. \fIScan\fR +actually reads each of the specified messages and parses them to extract +the desired fields. During parsing, appropriate error messages will be +produced if there are format errors in any of the messages. + +By default, \fIscan\fR will decode RFC-2047 (MIME) encoding in +these scan listings. \fIScan\fR will only decode these fields if your +terminal can natively display the character set used in the encoding. +You should set the MM_CHARSET environment variable to your native +character set, if it is not US-ASCII. See the mh-profile(5) man +page for details about this environment variable. + +The switch `\-reverse', makes \fIscan\fR list the messages in reverse +order. + +The `\-file filename' switch allows the user to obtain a \fIscan\fP +listing of a maildrop file as produced by \fIpackf\fP. This listing +includes every message in the file (you can't scan individual messages). +The switch `\-reverse' is ignored with this option. + +The switch `\-width\ columns' may be used to specify the width of +the scan line. The default is to use the width of the terminal. + +The `\-header' switch produces a header line prior to the \fIscan\fR +listing. Currently, the name of the folder and the current date and +time are output (see the \fBHISTORY\fR section for more information). + +If the `\-clear' switch is used and \fIscan's\fR output is directed +to a terminal, then \fIscan\fR will consult the environment variables +\fB$TERM\fR and \fB$TERMCAP\fR to determine your terminal type in order +to find out how to clear the screen prior to exiting. If the `\-clear' +switch is used and \fIscan's\fR output is not directed to a terminal +(e.g., a pipe or a file), then \fIscan\fR will send a formfeed prior +to exiting. + +For example, the command: + +.ti +.5i +(scan \-clear \-header; show all \-show pr \-f) | lpr + +produces a scan listing of the current folder, followed by a formfeed, +followed by a formatted listing of all messages in the folder, one +per page. Omitting `\-show\ pr\ \-f' will cause the messages to be +concatenated, separated by a one\-line header and two blank lines. + +To override the output format used by \fIscan\fR, the `\-format\ string' +or `\-form\ file' switches are used. This permits individual fields of +the scan listing to be extracted with ease. The string is simply a format +string and the file is simply a format file. See \fImh\-format\fR(5) +for the details. + +In addition to the standard \fImh\-format\fR(5) escapes, \fIscan\fR +also recognizes the following additional \fIcomponent\fR escapes: +.sp 1 +.nf +.ta \w'Dtimenow 'u +\w'Returns 'u +\fIEscape\fR \fIReturns\fR \fIDescription\fR +body string the (compressed) first part of the body +dtimenow date the current date +folder string the name of the current folder +.re +.fi + +If no date header is present in the message, the \fIfunction\fR escapes +which operate on {\fIdate\fP\|} will return values for the date of last +modification of the message file itself. This feature is handy for +scanning a \fIdraft folder\fR, as message drafts usually aren't allowed +to have dates in them. + +\fIscan\fR will update the \fInmh\fR context prior to starting the listing, +so interrupting a long \fIscan\fR listing preserves the new context. +\fInmh\fR purists hate this idea. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Alternate\-Mailboxes:~^To determine the user's mailboxes +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +inc(1), pick(1), show(1), mh\-format(5) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to all +.Ds +`\-format' defaulted as described above +.Ds +`\-noheader' +.Ds +`\-width' defaulted to the width of the terminal +.Co +If a folder is given, it will become the current folder. +.Hi +Prior to using the format string mechanism, `\-header' used to generate +a heading saying what each column in the listing was. Format strings +prevent this from happening. +.Bu +The argument to the `\-format' switch must be interpreted as a single +token by the shell that invokes \fIscan\fR. Therefore, one must usually +place the argument to this switch inside double\-quotes. + +The value of each \fIcomponent\fR escape is set by \fIscan\fR to the +contents of the first message header \fIscan\fR encounters with the +corresponding component name; any following headers with the same +component name are ignored. +.En diff --git a/man/send.man b/man/send.man new file mode 100644 index 0000000..43d1ad1 --- /dev/null +++ b/man/send.man @@ -0,0 +1,186 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH SEND %manext1% MH.6.8 [%nmhversion%] +.SH NAME +send \- send a message +.SH SYNOPSIS +.in +.5i +.ti -.5i +send +\%[\-alias\ aliasfile] +\%[\-draft] +\%[\-draftfolder\ +folder] +.br +\%[\-draftmessage\ msg] \%[\-nodraftfolder] +.br +\%[\-filter\ filterfile] \%[\-nofilter] +\%[\-format] \%[\-noformat] +.br +\%[\-forward] \%[\-noforward] +\%[\-mime] \%[\-nomime] +\%[\-msgid] +.br +\%[\-nomsgid] +\%[\-push] \%[\-nopush] +\%[\-split\ seconds] +.br +\%[\-verbose] \%[\-noverbose] +\%[\-watch] \%[\-nowatch] +.br +\%[\-width\ columns] +\%[file\ ...] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fISend\fR will cause each of the specified files to be delivered +to each of the destinations in the \*(lqTo:\*(rq, \*(lqcc:\*(rq, +\*(lqBcc:\*(rq, and \*(lqFcc:\*(rq fields of the message. If \fIsend\fR +is re\-distributing a message, as invoked from \fIdist\fR, then the +corresponding \*(lqResent\-xxx\*(rq fields are examined instead. + +By default, \fIsend\fR uses the program \fIpost\fR(8) to do the actual +delivery of the messages, although this can be changed by defining the +\fIpostproc\fR profile component. Most of the features attributed to +\fIsend\fR are actually performed by \fIpost\fR. + +If `\-push' is specified, \fIsend\fR will detach itself from the user's +terminal and perform its actions in the background. If \fIpush\fR\0'd +and the draft can't be sent, then an error message will be sent (using +the mailproc) back to the user. If `\-forward' is given, then a copy +of the draft will be attached to this failure notice. Using `\-push' +differs from putting \fIsend\fR in the background because the output is +trapped and analyzed by \fInmh\fR. + +If `\-verbose' is specified, \fIsend\fR will indicate the interactions +occurring with the transport system, prior to actual delivery. +If `\-watch' is specified \fIsend\fR will monitor the delivery of local +and network mail. Hence, by specifying both switches, a large detail +of information can be gathered about each step of the message's entry +into the transport system. + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +If `\-split' is specified, \fIsend\fR will split the draft into one +or more partial messages prior to sending. This makes use of the +MIME features in nmh. Note however that if \fIsend\fR is +invoked under \fIdist\fR\0(1), then this switch is ignored\0--\0it makes +no sense to redistribute a message in this fashion. Sometimes you want +\fIsend\fR to pause after posting a partial message. This is usually +the case when you are running \fIsendmail\fR and expect to generate a +lot of partial messages. The argument to `\-split' tells it how long +to pause between postings. + +\fISend\fR with no \fIfile\fR argument will query whether the draft +is the intended file, whereas `\-draft' will suppress this question. +Once the transport system has successfully accepted custody of the +message, the file will be renamed with a leading comma, which allows +it to be retrieved until the next draft message is sent. If there are +errors in the formatting of the message, \fIsend\fR will abort with a +(hopefully) helpful error message. + +If a \*(lqBcc:\*(rq field is encountered, its addresses will be used for +delivery, and the \*(lqBcc:\*(rq field will be removed from the message +sent to sighted recipients. The blind recipients will receive an entirely +new message with a minimal set of headers. Included in the body of the +message will be a copy of the message sent to the sighted recipients. +If `\-filter\ filterfile' is specified, then this copy is filtered +(re\-formatted) by \fImhl\fR prior to being sent to the blind recipients. +Alternately, if you specify the `-mime' switch, then \fIsend\fR will +use the MIME rules for encapsulation. + +Prior to sending the message, the fields \*(lqFrom:\ user@local\*(rq, +and \*(lqDate:\ now\*(rq will be appended to the headers in the message. +If the environment variable \fB$SIGNATURE\fR is set, then its value +is used as your personal name when constructing the \*(lqFrom:\*(rq +line of the message. If this environment variable is not set, then +\fIsend\fR will consult the profile entry \*(lqSignature\*(rq for +this information. On hosts where \fInmh\fR was configured with the UCI +option, if \fB$SIGNATURE\fR is not set and the \*(lqSignature\*(rq profile +entry is not present, then the file \fB$HOME\fR/.signature is consulted. +If `\-msgid' is specified, then a \*(lqMessage\-ID:\*(rq field will also +be added to the message. + +If \fIsend\fR is re\-distributing a message (when invoked by +\fIdist\fR\0), then \*(lqResent\-\*(rq will be prepended to each of these +fields: \*(lqFrom:\*(rq, \*(lqDate:\*(rq, and \*(lqMessage\-ID:\*(rq. +If the message already contains a \*(lqFrom:\*(rq field, then a +\*(lqSender: user@local\*(rq field will be added as well. (An already +existing \*(lqSender:\*(rq field is an error!) + +By using the `\-format' switch, each of the entries in the \*(lqTo:\*(rq +and \*(lqcc:\*(rq fields will be replaced with \*(lqstandard\*(rq +format entries. This standard format is designed to be usable by all +of the message handlers on the various systems around the Internet. +If `\-noformat' is given, then headers are output exactly as they appear +in the message draft. + +If an \*(lqFcc:\ folder\*(rq is encountered, the message will be copied +to the specified folder for the sender in the format in which it will +appear to any non\-Bcc receivers of the message. That is, it will have +the appended fields and field reformatting. The \*(lqFcc:\*(rq fields +will be removed from all outgoing copies of the message. + +By using the `\-width\ columns' switch, the user can direct \fIsend\fR +as to how long it should make header lines containing addresses. + +The files specified by the profile entry \*(lqAliasfile:\*(rq and any +additional alias files given by the `\-alias aliasfile' switch will be +read (more than one file, each preceded by `\-alias', can be named). +See \fImh\-alias\fR\0(5) for more information. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Aliasfile:~^For a default alias file +.Ps +^Signature:~^To determine the user's mail signature +.Ps +^mailproc:~^Program to post failure notices +.Ps +^postproc:~^Program to post the message +.Sa +comp(1), dist(1), forw(1), repl(1), mh\-alias(5), post(8) +.De +`file' defaults to /draft +.Ds +`\-alias %etcdir%/MailAliases' +.Ds +`\-nodraftfolder' +.Ds +`\-nofilter' +.Ds +`\-format' +.Ds +`\-forward' +.Ds +`\-nomime' +.Ds +`\-nomsgid' +.Ds +`\-nopush' +.Ds +`\-noverbose' +.Ds +`\-nowatch' +.Ds +`\-width\ 72' +.Co +None +.Bu +Under some configurations, it is not possible to monitor the mail delivery +transaction; `\-watch' is a no-op on those systems. +.sp +Using `\-split\00' doesn't work correctly. +.En diff --git a/man/sendfiles.man b/man/sendfiles.man new file mode 100644 index 0000000..72f843c --- /dev/null +++ b/man/sendfiles.man @@ -0,0 +1,150 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH SENDFILES %manext1% MH.6.8 [%nmhversion%] +.SH NAME +sendfiles \- send multiple files via a MIME message +.SH SYNOPSIS +.in +.5i +.ti -.5i +sendfiles \%[delay] mailpath subject file1 \%[file2]... +.in -.5i + +.SH DESCRIPTION +The shell script \fIsendfiles\fR, is used to send a collection +of files and directories via electronic mail. +.sp +.in +.5i +%libdir%/sendfiles mailpath \*(lqsubject\*(rq files\0... +.in -.5i +.sp +\fIsendfiles\fR will archive the files and directories you name +with the \fItar\fR\0(1) command, and then mail the compressed +archive to the `mailpath' with the given `subject'. The archive +will be automatically split up into as many messages as necessary +in order to get past most mailers. + +Sometimes you want \fIsendfiles\fR to pause after posting a partial +message. This is usually the case when you are running \fIsendmail\fR +and expect to generate a lot of partial messages. If the first +argument given to \fIsendfiles\fR starts with a dash, then it is +interpreted as the number of seconds to pause in between postings, +.ne 6 +e.g., +.sp +.in +.5i +%libdir%/sendfiles -30 mailpath \*(lqsubject\*(rq files\0... +.in -.5i +.sp +will pause 30 seconds in between each posting. + +.Uh "Extracting the Received Files" +When these messages are received, invoke \fImhstore\fR once for +the list of messages. The default is for \fImhstore\fR to store +the combined parts as a new message in the current folder, although +this can be changed using storage formatting strings. You can then +use \fImhlist\fR to find out what's inside; possibly followed by +\fImhstore\fR again to write the archive to a file where you can +subsequently uncompress and untar it. For instance: +.sp +.nf +.in +.5i +% mhlist 5-8 + msg part type/subtype size description + 5 message/partial 47K part 1 of 4 + 6 message/partial 47K part 2 of 4 + 7 message/partial 47K part 3 of 4 + 8 message/partial 18K part 4 of 4 +% mhstore 5-8 +reassembling partials 5,6,7,8 to folder inbox as message 9 +% mhlist -verbose 9 + msg part type/subtype size description + 9 application/octet-stream 118K + (extract with uncompress | tar xvpf -) + type=tar + conversions=compress +% mhstore 9 +% uncompress < 9.tar.Z | tar xvpf - +.in -.5i +.fi +.sp +Alternately, by using the `\-auto' switch, \fImhstore\fR will +automatically do the extraction for you: +.sp +.nf +.in +.5i +% mhlist 5-8 + msg part type/subtype size description + 5 message/partial 47K part 1 of 4 + 6 message/partial 47K part 2 of 4 + 7 message/partial 47K part 3 of 4 + 8 message/partial 18K part 4 of 4 +% mhstore 5-8 +reassembling partials 5,6,7,8 to folder inbox as message 9 +% mhlist -verbose 9 + msg part type/subtype size description + 9 application/octet-stream 118K + (extract with uncompress | tar xvpf -) + type=tar + conversions=compress +% mhstore -auto 9 +-- \fItar\fR listing appears here as files are extracted +.in -.5i +.fi +.sp +As the second \fItar\fR listing is generated, the files are extracted. +A prudent user will never put `\-auto' in the \&.mh\(ruprofile +file. The correct procedure is to first use \fImhlist\fR to find +out what will be extracted. Then \fImhstore\fR can be invoked with +`\-auto' to perform the extraction. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +mhbuild(1), mhlist(1), mhshow(1), mhstore(1) +.br +RFC\-934: +.br + \fIProposed Standard for Message Encapsulation\fR, +.br +RFC\-2045: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part One: +.br + Format of Internet Message Bodies\fR, +.br +RFC\-2046: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Two: +.br + Media Types\fR, +.br +RFC\-2047: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Three: +.br + Message Header Extensions for Non-ASCII Text\fR, +.br +RFC\-2048: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Four: +.br + Registration Procedures\fR, +.br +RFC\-2049: +.br + \fIMultipurpose Internet Mail Extensions (MIME) Part Five: +.br + Conformance Criteria and Examples\fR. +.De +`\-noverbose' +.Co +None. +.En diff --git a/man/show.man b/man/show.man new file mode 100644 index 0000000..a864048 --- /dev/null +++ b/man/show.man @@ -0,0 +1,164 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH SHOW %manext1% MH.6.8 [%nmhversion%] +.SH NAME +show \- show (display) messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +show +\%[+folder] \%[msgs] +\%[\-showproc\ program] +.br +\%[\-showmimeproc\ program] +\%[\-header] \%[\-noheader] +.br +\%[\-draft] +\%[\-checkmime] \%[\-nocheckmime] +.br +\%[switches\ for\ \fIshowproc\fR or \fIshowmimeproc\fR] +.br +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIShow\fR lists each of the specified messages to the standard output +(typically, the terminal). + +By default, text (non-MIME) messages are filtered and displayed by +the \fInmh\fR command \fImhl\fR. This command will display text +messages in a nice, uniform format. It also allows you to configure +the format of the displayed messages and which headers fields are +shown. See the \fImhl\fR(1) manual page for the details about this +command. This default can be changed by defining the \fIshowproc\fR +profile component. Any switches not recognized by \fIshow\fR are +passed along to that program. To override the default and the +\fIshowproc\fR profile component, use the `\-showproc\ program' +switch. For example, `\-showproc\ more' will cause the \fImore\fR(1) +program to list the messages with no reformatting. Normally, this +program is specified as the \fIshowproc\fR in the user's +\&.mh\(ruprofile, rather than using a command line switch. + +By default, non-text messages (MIME messages with multi-media +contents) are processed and displayed by the \fInmh\fR command +\fImhshow\fR. See the \fImhshow\fR(1) manual page for details +about this command. This default can changed by defining the +\fIshowmimeproc\fR profile component. Any switches not recognized +by \fIshow\fR are passed along to that program. To override this +default and the \fIshowmimeproc\fR profile component, use the +`\-showmimeproc\ program' switch. + +Note that in some cases, \fIshow\fR may invoke the \fIshowmimeproc\fR +even for textual contents. This will happen for text messages that +specify a transfer encoding (such as MIME quoted-printable or +base64) or specify a character set that \fIshow\fR doesn't believe +can be displayed natively. The environment variable MM_CHARSET +should be set to the terminal's native character set to avoid +gratuitous invocations of the \fIshowmimeproc\fR. See the +mh-profile(5) man page for details about this environment variable. + +The option `\-checkmime' (set by default) instructs \fIshow\fR to +test if any of the messages to be displayed are non-text (MIME) +messages. If any are non-text, they are displayed by the program +\fIshowmimeproc\fR, else they are displayed by the program +\fIshowproc\fR. The option `-nocheckmime' disables this test and +instructs \fIshow\fR to use \fIshowproc\fR, regardless of whether +any of the messages are non-text (MIME) messages. + +The `\-noshowproc' switch will disable any formatting or paging of +messages. It is equivalent to `-nocheckmime\ -showproc\ cat'. It +is still accepted, but should be considered (somewhat) obsolete. + +If the environment variable \fBNOMHNPROC\fR is set, the test for +non-text (MIME) messages will be disabled. This method is obsolete. +Use the `-nocheckmime' switch instead. + +The `\-header' switch tells \fIshow\fR to display a one\-line +description of the message being shown. This description includes +the folder and the message number. + +If no `msgs' are specified, the current message is used. Although +it depends on the specific \fIshowproc\fR or \fIshowmimeproc\fR, +in the default setup when more than one message is specified, you +will be prompted for a prior to listing each message. +Each message will be listed a page at a time, and when the end of +page is reached, the program will wait for a or . +If a is entered, it will print the next line, whereas + will print the next screenful. + +If the standard output is not a terminal, no queries are made, and +each file is listed with a one\-line header and two lines of +separation. + +\*(lqshow \-draft\*(rq will list the file /draft if it +exists. + +If the profile entry \*(lqUnseen\-Sequence\*(rq is present and +non\-empty, then \fIshow\fR will remove each of the messages shown +from each sequence named by the profile entry. +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Ps +^Unseen\-Sequence:~^To name sequences denoting unseen messages +.Ps +^showproc:~^Program to show text (non-MIME) messages +.Ps +^showmimeproc:~^Program to show non-text (MIME) messages +.Sa +mhl(1), mhshow(1), more(1), next(1), prev(1), scan(1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to cur +.Ds +`\-checkmime' +.Ds +`\-header' +.Co +If a folder is given, it will become the current folder. The last +message shown will become the current message. +.Bu +The `\-header' switch doesn't work when `msgs' expands to more than +one message. If the \fIshowproc\fR is \fImhl\fR, then is problem can +be circumvented by referencing the \*(lqmessagename\*(rq field in the +\fImhl\fR format file. + +\fIShow\fR updates the user's context before showing the message. +Hence \fIshow\fR will mark messages as seen prior to the user actually +seeing them. This is generally not a problem, unless the user relies +on the \*(lqunseen\*(rq messages mechanism, and interrupts \fIshow\fR +while it is showing \*(lqunseen\*(rq messages. + +If your \fIshowproc\fR is \fImhl\fR (the default), then \fIshow\fR uses +a built\-in \fImhl\fR: it does not actually run the \fImhl\fR program. +Hence, if you define your own \fIshowproc\fR, don't call it \fImhl\fR +since \fIshow\fR won't run it. + +If your \fIshowproc\fR is the pager \fImore\fR, then avoid running +\fIshow\fR in the background with only its standard output piped to +another process, as in + +.ti +.5i +show | imprint & + +Due to a bug in \fImore\fR, show will go into a \*(lqtty input\*(rq state. +To avoid this problem, re\-direct \fIshow\fR's diagnostic output as well. +For users of \fIcsh\fR: + +.ti +.5i +show |& imprint & + +For users of \fIsh\fR: + +.ti +.5i +show 2>&1 | imprint & +.En diff --git a/man/slocal.man b/man/slocal.man new file mode 100644 index 0000000..b54bc3f --- /dev/null +++ b/man/slocal.man @@ -0,0 +1,354 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH SLOCAL %manext1% MH.6.8 [%nmhversion%] +.SH NAME +slocal \- asynchronously filter and deliver new mail +.SH SYNOPSIS +.in +.5i +.ti -.5i +%libdir%/slocal \%[address\ info\ sender] +.na +.br +\%[\-addr\ address] +\%[\-info\ data] +\%[\-sender\ sender] +.br +\%[\-user\ username] +\%[\-mailbox\ mbox] +.\" \%[\-home\ homedir] +\%[\-file\ file] +.br +\%[\-maildelivery\ deliveryfile] +\%[\-verbose] \%[\-noverbose] +.br +\%[\-suppressdup] \%[\-nosuppressdup] +\%[\-debug] +.br +\%[\-version] +\%[\-help] +.ad +.in -.5i +.SH DESCRIPTION +\fISlocal\fP is a program designed to allow you to have your inbound +mail processed according to a complex set of selection criteria. +You do not normally invoke \fIslocal\fP yourself, rather \fIslocal\fP +is invoked on your behalf by your system's Message Transfer Agent +(such as sendmail) when the message arrives. + +The message selection criteria used by \fIslocal\fP is specified +in the file \fI\&.maildelivery\fP in the user's home directory. +You can specify an alternate file with the `\-maildelivery file' +option. The syntax of this file is specified below. + +The message delivery address and message sender are determined from +the Message Transfer Agent envelope information, if possible. +Under \fIsendmail\fP, the sender will obtained from the UUCP +\*(lqFrom\ \*(rq line, if present. The user may override these +values with command line arguments, or arguments to the `\-addr' +and `\-sender' switches. + +The message is normally read from the standard input. The `\-file' +switch sets the name of the file from which the message should be +read, instead of reading stdin. This is useful when debugging a +\fI\&.maildelivery\fP file. + +The `\-user' switch tells \fIslocal\fP the name of the user for +whom it is delivering mail. The `\-mailbox' switch tells \fIslocal\fP +the name of the user's maildrop file. + +\fIslocal\fR is able to detect and suppress duplicate messages. +To enable this, use the option `\-suppressdup'. \fIslocal\fR will +keep a database containing the Message-ID's of incoming messages, +in order to detect duplicates. Depending on your configuration, +this database will be in either ndbm or Berkeley db format. + +The `\-info' switch may be used to pass an arbitrary argument to +sub-processes which \fIslocal\fP may invoke on your behalf. + +The `\-verbose' switch causes \fIslocal\fP to give information on +stdout about its progress. The `\-debug' switch produces more +verbose debugging output on stderr. These flags are useful when +creating and debugging your \fI\&.maildelivery\fP file, as they +allow you to see the decisions and actions that \fIslocal\fR is +taking, as well as check for syntax errors in your \fI\&.maildelivery\fP +file. + +.Uh "Message Transfer Agents" +If your MTA is \fIsendmail\fP, you should include the line +.sp +.nf +.in +.5i + \*(lq|\ %libdir%/slocal\ \-user\ username\*(rq +.in -.5i +.fi +.sp +in your \&.forward file in your home directory. This will cause +\fIsendmail\fP to invoke \fIslocal\fP on your behalf when a +message arrives. + +If your MTA is \fIMMDF-I\fP, you should (symbolically) link +%libdir%/slocal to the file bin/rcvmail in your home directory. This will +cause \fIMMDF-I\fP to invoke \fIslocal\fP on your behalf with the correct +\*(lq\fIaddress\ info\ sender\fP\*(rq arguments. + +If your MTA is \fIMMDF-II\fP, then you should not use \fIslocal\fP. +An equivalent functionality is already provided by \fIMMDF-II\fP; see +maildelivery(5) for details. + +.Uh "The Maildelivery File" + +The \fI\&.maildelivery\fR file controls how slocal filters and delivers +incoming mail. Each line of this file consists of five fields, separated +by white-space or comma. Since double-quotes are honored, these +characters may be included in a single argument by enclosing the entire +argument in double-quotes. A double-quote can be included by preceding it +with a backslash. Lines beginning with `#' and blank lines are ignored. + +The format of each line in the \fI\&.maildelivery\fR file is: + + \fBheader pattern action result string\fR +.sp +.in +.5i +.ti -.5i +\fBheader\fP: +.br +The name of a header field (such as To, Cc, or From) that is to +be searched for a pattern. This is any field in the headers of +the message that might be present. + +The following special fields are also defined: +.sp +.in +1i +.ta +1i +.ti -1i +\fIsource\fR the out-of-band sender information +.ti -1i +\fIaddr\fR the address that was used to cause delivery to the recipient +.ti -1i +\fIdefault\fR this matches \fIonly\fR if the message hasn't been delivered yet +.ti -1i +\fI*\fR this always matches +.in -1i + +.ti -.5i +\fBpattern\fR: +.br +The sequence of characters to match in the specified header field. +Matching is case-insensitive, but does not use regular expressions. + +.ti -.5i +\fBaction\fR: +.br +The action to take to deliver the message. When a message is delivered, +a \*(lqDelivery\-Date:\ date\*(rq header is added which indicates the date +and time that message was delivered. +.sp +.in +1i +.ta +1i +.ti -1i +\fIdestroy\fR +This action always succeeds. + +.ti -1i +\fIfile\fR, \fImbox\fR, or > +Append the message to the file named by \fBstring\fR. The message is +appended to the file in mbox (uucp) format. This is the format used by most +other mail clients (such as mailx, elm). If the message can be appended to +the file, then this action succeeds. + +.ti -1i +\fImmdf\fR Identical to \fIfile\fR, but always appends the message using +the MMDF mailbox format. + +.ti -1i +\fIpipe\fR or | +Pipe the message as the standard input to the command named by +\fBstring\fR, using the Bourne shell \fIsh\fR(1) to interpret the string. +Prior to giving the string to the shell, it is expanded with the following +built-in variables: +.sp +.in +1i +.ta +1i +.ti -1i +$(sender) the out-of-band sender information +.ti -1i +$(address) the address that was used to cause delivery to the recipient +.ti -1i +$(size) the size of the message in bytes +.ti -1i +$(reply\-to) either the \*(lqReply\-To:\*(rq or \*(lqFrom:\*(rq field +of the message +.ti -1i +$(info) the out-of-band information specified +.in -1i + +.ti -1i +\fIqpipe\fR or Similar to \fIpipe\fR, but executes the command +directly, after built-in variable expansion, without assistance from +the shell. This action can be used to avoid quoting special characters +which your shell might interpret. + +.ti -1i +\fIfolder\fR or \fI\+\fR Store the message in the nmh folder named +by \fBstring\fR. Currently his is handled by piping the message to the nmh +program `rcvstore', although this may change in the future. + +.in -1i +.ti -.5i +\fBresult\fR: +.br +Indicates how the action should be performed: + +.in +1i +.ta +1i +.ti -1i +\fIA\fR Perform the action. If the action succeeds, then the message +is considered delivered. + +.ti -1i +\fIR\fR Perform the action. +Regardless of the outcome of the action, the message is not considered +delivered. + +.ti -1i +\fI?\fR Perform the action only if the message has not been delivered. +If the action succeeds, then the message is considered delivered. + +.ti -1i +\fIN\fR Perform the action only if the message has not been delivered +and the previous action succeeded. If this action succeeds, then the +message is considered delivered. +.sp +.in -1i +.in -.5i + +The delivery file is always read completely, so that several matches +can be made and several actions can be taken. +.fi + +.Uh "Security of Delivery Files" +In order to prevent security problems, the \fI\&.maildelivery\fR +file must be owned either by the user or by root, and must be +writable only by the owner. If this is not the case, the file is +not read. + +If the \fI\&.maildelivery\fR file cannot be found, or does not +perform an action which delivers the message, then \fIslocal\fP +will check for a global delivery file at %etcdir%/maildelivery. +This file is read according to the same rules. This file must be +owned by the root and must be writable only by the root. + +If a global delivery file cannot be found or does not perform an +action which delivers the message, then standard delivery to the +user's maildrop is performed. +.fi + +.Uh "Example Delivery File" +To summarize, here's an example delivery file: +.sp +.if t .in +.5i +.nf +.ta \w'default 'u +\w'mh-workersxx 'uC +\w'destroy 'uC +\w'result 'u +# +# .maildelivery file for nmh's slocal +# +# Blank lines and lines beginning with a '#' are ignored +# +# FIELD PATTERN ACTION RESULT STRING +# + +# File mail with foobar in the \*(lqTo:\*(rq line into file foobar.log +To foobar file A foobar.log + +# Pipe messages from coleman to the program message-archive +From coleman pipe A /bin/message-archive + +# Anything to the \*(lqnmh-workers\*(rq mailing list is put in +# its own folder, if not filed already +To nmh-workers folder ? nmh-workers + +# Anything with Unix in the subject is put into +# the file unix-mail +Subject unix file A unix-mail + +# I don't want to read mail from Steve, so destroy it +From steve destroy A \- + +# Put anything not matched yet into mailbox +default \- file ? mailbox + +# always run rcvtty +* \- pipe R /nmh/lib/rcvtty +.re +.fi + +.Uh "Sub-process environment" +When a process is invoked, its environment is: the user/group-ids are +set to recipient's ids; the working directory is the recipient's home +directory; the umask is 0077; the process has no /dev/tty; the standard +input is set to the message; the standard output and diagnostic output are +set to /dev/null; all other file-descriptors are closed; the environment +variables \fB$USER\fR, \fB$HOME\fR, \fB$SHELL\fR are set appropriately, +and no other environment variables exist. + +The process is given a certain amount of time to execute. If the process +does not exit within this limit, the process will be terminated with +extreme prejudice. The amount of time is calculated as ((size / 60) + +300) seconds, where size is the number of bytes in the message (with +30 minutes the maximum time allowed). + +The exit status of the process is consulted in determining the success +of the action. An exit status of zero means that the action succeeded. +Any other exit status (or abnormal termination) means that the action +failed. + +In order to avoid any time limitations, you might implement a process +that began by \fIforking\fR. The parent would return the appropriate +value immediately, and the child could continue on, doing whatever it +wanted for as long as it wanted. This approach is somewhat risky if +the parent is going to return an exit status of zero. If the parent is +going to return a non-zero exit status, then this approach can lead to +quicker delivery into your maildrop. +.Fi +^%etcdir%/mts.conf~^nmh mts configuration file +^$HOME/\&.maildelivery~^The file controlling local delivery +^%etcdir%/maildelivery~^Rather than the standard file +^%mailspool%/$USER~^The default maildrop +.Sa +rcvdist(1), rcvpack(1), rcvstore(1), rcvtty(1), mh\-format(5) +.De +`\-noverbose' +.Ds +`\-nosuppressdup' +.Ds +`\-maildelivery \&.maildelivery' +.Ds +`\-mailbox %mailspool%/$USER' +.Ds +`\-file' defaults to stdin +.Ds +`\-user' defaults to the current user +.Co +None +.Hi +\fISlocal\fP was originally designed to be backward-compatible with +the \fImaildelivery\fP facility provided by \fIMMDF-II\fP. Thus, the +\fI\&.maildelivery\fP file syntax is somewhat limited. But \fIslocal\fP +has been modified and extended, so that is it no longer compatible with +\fIMMDF-II\fP. + +In addition to an exit status of zero, the \fIMMDF\fR values \fIRP_MOK\fR +(32) and \fIRP_OK\fR (9) mean that the message has been fully delivered. +Any other non-zero exit status, including abnormal termination, is +interpreted as the \fIMMDF\fR value \fIRP_MECH\fR (200), which means +\*(lquse an alternate route\*(rq (deliver the message to the maildrop). +.Bu +Only two return codes are meaningful, others should be. + +\fISlocal\fP was originally designed to be backwards-compatible with the +\fImaildelivery\fP functionality provided by \fBMMDF-II\fP. diff --git a/man/sortm.man b/man/sortm.man new file mode 100644 index 0000000..eeaac1a --- /dev/null +++ b/man/sortm.man @@ -0,0 +1,101 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH SORTM %manext1% MH.6.8 [%nmhversion%] +.SH NAME +sortm \- sort messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +sortm +\%[+folder] \%[msgs] +\%[\-datefield\ field] +\%[\-textfield\ field] +.br +\%[\-notextfield] +\%[\-limit days] \%[\-nolimit] +\%[\-verbose] +.br +\%[\-noverbose] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fISortm\fR sorts the specified messages in the named folder according +to the chronological order of the \*(lqDate:\*(rq field of each message. + +The `\-verbose' switch directs \fIsortm\fR to tell the user the general +actions that it is taking to place the folder in sorted order. + +The `\-datefield\ field' switch tells \fIsortm\fR the name of the field to +use when making the date comparison. If the user has a special field in +each message, such as \*(lqBB\-Posted:\*(rq or \*(lqDelivery\-Date:\*(rq, +then the `\-datefield' switch can be used to direct \fIsortm\fR which +field to examine. + +The `\-textfield\ field' switch causes \fIsortm\fR to sort messages +by the specified text field. If this field is \*(lqsubject\*(rq, any +leading "re:" is stripped off. In any case, all characters except +letters and numbers are stripped and the resulting strings are sorted +datefield\-major, textfield\-minor, using a case insensitive comparison. + +With `\-textfield\ field', if `\-limit\ days' is specified, messages +with similar textfields that are dated within `days' of each other +appear together. Specifying `\-nolimit' makes the limit infinity. +With `\-limit 0', the sort is instead made textfield\-major, date\-minor. + +.\"Ex +For example, to order a folder by date-major, subject-minor, use: + +.ti +.5i +sortm -textfield subject +folder + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Current\-Folder:~^To find the default current folder +.Sa +folder (1) +.De +`+folder' defaults to the current folder +.Ds +`msgs' defaults to all +.Ds +`\-datefield date' +.Ds +`\-notextfield' +.Ds +`\-noverbose' +.Ds +`\-nolimit' +.Co +If a folder is given, it will become the current folder. If the current +message is moved, \fIsortm\fR will preserve its status as current. +.Hi +Timezones used to be ignored when comparing dates: they aren't any more. + +Messages which were in the folder, but not specified by `msgs', used to +be moved to the end of the folder; now such messages are left untouched. + +\fISortm\fP sometimes did not preserve the message numbering in a folder +(e.g., messages 1, 3, and 5, might have been renumbered to 1, 2, 3 after +sorting). This was a bug, and has been fixed. To compress the message +numbering in a folder, use \*(lq\fIfolder\ \-pack\fR\|\*(rq as always. +.Bu +If \fIsortm\fR encounters a message without a date\-field, or if the +message has a date\-field that \fIsortm\fR cannot parse, then \fIsortm\fR +attempts to keep the message in the same relative position. This does +not always work. For instance, if the first message encountered lacks +a date which can be parsed, then it will usually be placed at the end +of the messages being sorted. + +When \fIsortm\fR complains about a message which it can't temporally +order, it complains about the message number \fIprior\fR to sorting. +It should indicate what the message number will be \fIafter\fR sorting. +.En diff --git a/man/tmac.h.in b/man/tmac.h.in new file mode 100644 index 0000000..7b49b79 --- /dev/null +++ b/man/tmac.h.in @@ -0,0 +1,77 @@ +.\" +.\" $Id$ +.\" Try to keep only one copy of the documentation around +.\" by re-defining macros and so forth. +.\" +.fc ^ ~ +.\" I pity the fool who tampers with the next line... +.ds ZZ -man +.de SC \" Title section +.TH \\$1 \\$2 MH.6.8 [%nmhversion%] +.. +.de NA \" Name section +.SH NAME +.. +.de SY \" Synopsis section +.SH SYNOPSIS +.in +.5i +.ti -.5i +.. +.de DE \" Description section +.in -.5i +.SH DESCRIPTION +.. +.de Fi \" Files section +.SH FILES +.nf +.ta \w'/usr/local/nmh/lib/ExtraBigFileName 'u +.. +.de Pr \" Profile section +.SH "PROFILE\ COMPONENTS" +.nf +.ta 2.4i +.ta \w'ExtraBigProfileName 'u +.. +.de Ps \" Profile next +.br +.. +.de Sa \" See Also section +.fi +.SH "SEE\ ALSO" +.. +.de De \" Defaults section +.SH "DEFAULTS" +.nf +.. +.de Ds \" Defaults next +.br +.. +.de Co \" Context section +.fi +.SH CONTEXT +.. +.de Hh \" Hints section +.fi +.SH "HELPFUL HINTS" +.. +.de Hi \" History section +.fi +.SH HISTORY +.. +.de Bu \" Bugs section +.fi +.SH BUGS +.. +.de En +.. +.de ip +.IP "\\$1" \\$2 +.. +.de Uh +.ne 4 +.SS "\\$1" +.. +.\" a useful -me macro +.de re +.ta 0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +0.5i +.. diff --git a/man/vmh.man b/man/vmh.man new file mode 100644 index 0000000..050cdf7 --- /dev/null +++ b/man/vmh.man @@ -0,0 +1,105 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH VMH %manext1% MH.6.8 [%nmhversion%] +.SH NAME +vmh \- visual front-end to nmh +.SH SYNOPSIS +.in +.5i +.ti -.5i +vmh +\%[\-prompt\ string] +\%[\-vmhproc\ program] \%[\-novmhproc] +.br +\%[switches\ for\ \fIvmhproc\fR] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIvmh\fR is a program which implements the server side of the \fInmh\fR +window management protocol and uses \fIcurses\fR\0(3) routines to maintain +a split\-screen interface to any program which implements the client +side of the protocol. This latter program, called the \fIvmhproc\fR, +is specified using the `\-vmhproc\ program' switch. + +The upshot of all this is that one can run \fImsh\fR on a display terminal +and get a nice visual interface. To do this, for example, just add +the line + +.ti +.5i +mshproc: vmh + +to your \&.mh\(ruprofile. (This takes advantage of the fact that +\fImsh\fR is the default \fIvmhproc\fR for \fIvmh\fR.) + +In order to facilitate things, if the `\-novmhproc' switch is given, +and \fIvmh\fR can't run on the user's terminal, the \fIvmhproc\fR is +run directly without the window management protocol. + +After initializing the protocol, \fIvmh\fR prompts the user for a command +to be given to the client. Usually, this results in output being sent to +one or more windows. If a output to a window would cause it to scroll, +\fIvmh\fR prompts the user for instructions, roughly permitting the +capabilities of \fIless\fR or \fImore\fR (e.g., the ability to scroll +backwards and forwards): + +.nf +.in +.5i +.ta \w'RETURN 'u +\w'* 'u +SPACE advance to the next windowful +RETURN * advance to the next line +y * retreat to the previous line +d * advance to the next ten lines +u * retreat to the previous ten lines +g * go to an arbitrary line + (preceed g with the line number) +G * go to the end of the window + (if a line number is given, this acts like `g') +CTRL\-L refresh the entire screen +h print a help message +q abort the window +.re +.in -.5i +.fi + +(A `*' indicates that a numeric prefix is meaningful for this command.) + +Note that if a command resulted in more than one window's worth of +information being displayed, and you allow the command which is generating +information for the window to gracefully finish (i.e., you don't use +the `q' command to abort information being sent to the window), then +\fIvmh\fR will give you one last change to peruse the window. This is +useful for scrolling back and forth. Just type `q' when you're done. + +To abnormally terminate \fIvmh\fR (without core dump), use +(usually CTRL\-\\). For instance, this does the \*(lqright\*(rq thing +with \fIbbc\fR and \fImsh\fR. + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Sa +msh(1) +.De +`\-prompt\ (vmh)\ ' +.Ds +`\-vmhproc\ msh' +.Co +None +.Bu +The argument to the `\-prompt' switch must be interpreted as a single +token by the shell that invokes \fIvmh\fR. Therefore, one must usually +place the argument to this switch inside double\-quotes. + +At present, there is no way to pass signals (e.g., interrupt, quit) to +the client. However, generating QUIT when \fIvmh\fR is reading a command +from the terminal is sufficient to tell the client to go away quickly. + +Acts strangely (loses peer or botches window management protocol with +peer) on random occasions. +.En diff --git a/man/whatnow.man b/man/whatnow.man new file mode 100644 index 0000000..1a015bb --- /dev/null +++ b/man/whatnow.man @@ -0,0 +1,141 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH WHATNOW %manext1% MH.6.8 [%nmhversion%] +.SH NAME +whatnow \- prompting front-end for sending messages +.SH SYNOPSIS +.in +.5i +.ti -.5i +whatnow +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] +.br +\%[\-nodraftfolder] +\%[\-editor\ editor] \%[\-noedit] +.br +\%[\-prompt\ string] +\%[file] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIWhatnow\fR is the default program that queries the user about +the disposition of a composed draft. It is normally automatically +invoked by one of the \fInmh\fR commands \fIcomp\fR, \fIdist\fR, +\fIforw\fR, or \fIrepl\fR after the initial edit. + +When started, the editor is started on the draft (unless `\-noedit' +is given, in which case the initial edit is suppressed). Then, +\fIwhatnow\fR repetitively prompts the user with \*(lqWhat now?\*(rq +and awaits a response. The valid responses are: + +.nf +.in .5i +.ta \w'\fBrefile +folder\fR 'u +^\fBedit\fR~^re\-edit using the same editor that was used on the +^~^preceding round unless a profile entry +^~^\*(lq\-next: \*(rq names an alternate editor +^\fBedit \fR~^invoke for further editing +^\fBrefile +folder\fR~^refile the draft into the given folder +^\fBmime\fR~^process the draft as MIME composition file using +^~^the "buildmimeproc" command (mhbuild by default) +^\fBdisplay\fR~^list the message being distributed/replied\-to +^~^on the terminal +^\fBlist\fR~^list the draft on the terminal +^\fBsend\fR~^send the message +^\fBsend \-watch\fR~^send the message and monitor the delivery process +^\fBpush\fR~^send the message in the background +^\fBwhom\fR~^list the addresses that the message will go to +^\fBwhom \-check\fR~^list the addresses and verify that they are +^~^acceptable to the transport service +^\fBquit\fR~^preserve the draft and exit +^\fBquit \-delete\fR~^delete the draft and exit +^\fBdelete\fR~^delete the draft and exit +.fi +.re + +When entering your response, you need only type enough characters +to uniquely identify the response. + +For the \fBedit\fR response, any valid switch to the editor is valid. + +For the \fBsend\fR and \fBpush\fR responses, any valid switch to +\fIsend\fR\0(1) are valid (as \fBpush\fR merely invokes \fIsend\fR +with the `\-push' option). + +For the \fBwhom\fR response, any valid switch to \fIwhom\fR\0(1) +is valid. + +For the \fBrefile\fR response, any valid switch to the \fIfileproc\fR +is valid. + +For the \fBdisplay\fR and \fBlist\fR responses, any valid argument to +the \fIlproc\fR is valid. If any non\-switch arguments are present, then +the pathname of the draft will be excluded from the argument list given +to the \fIlproc\fR (this is useful for listing another \fInmh\fR message). + +See \fImh\-profile\fR\0(5) for further information about how editors +are used by nmh. It also discusses how environment variables can be +used to direct \fIwhatnow\fR's actions in complex ways. + +The `\-prompt\ string' switch sets the prompting string for \fIwhatnow\fR. + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +^/draft~^The draft file +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Editor:~^To override the default editor +.Ps +^\-next:~^To name an editor to be used after exit +^~^from +.Ps +^automimeproc:~^If value is 1, and the draft is a MIME +^~^composition file, then automatically call +^~^buildmimeproc prior to sending. +.Ps +^buildmimeproc:~^Program to translate MIME composition files +.Ps +^fileproc:~^Program to refile the message +.Ps +^lproc:~^Program to list the contents of a message +.Ps +^sendproc:~^Program to use to send the message +.Ps +^whomproc:~^Program to determine who a message would go to +.Sa +send(1), whom(1) +.De +`\-prompt\ \*(lqWhat\ Now?\ \*(rq' +.Co +None +.Bu +The argument to the `\-prompt' switch must be interpreted as a single +token by the shell that invokes \fIwhatnow\fR. Therefore, one must +usually place the argument to this switch inside double\-quotes. + +If the initial edit fails, \fIwhatnow\fR deletes your draft (by renaming +it with a leading comma); failure of a later edit preserves the draft. + +If \fIwhatnowproc\fR is \fIwhatnow\fR, then \fIcomp\fR, \fIdist\fP, +\fIforw\fP, and \fIrepl\fP use a built\-in \fIwhatnow\fR, and do not +actually run the \fIwhatnow\fR program. Hence, if you define your own +\fIwhatnowproc\fR, don't call it \fIwhatnow\fR since it won't be run. + +If \fIsendproc\fR is \fIsend\fR, then \fIwhatnow\fR uses a built\-in +\fIsend\fR, it does not actually run the \fIsend\fR program. Hence, if +you define your own \fIsendproc\fR, don't call it \fIsend\fR since +\fIwhatnow\fR won't run it. +.En diff --git a/man/whom.man b/man/whom.man new file mode 100644 index 0000000..9caf26c --- /dev/null +++ b/man/whom.man @@ -0,0 +1,69 @@ +.\" +.\" %nmhwarning% +.\" $Id$ +.\" +.\" include the -mh macro file +.so %etcdir%/tmac.h +.\" +.TH WHOM %manext1% MH.6.8 [%nmhversion%] +.SH NAME +whom \- report to whom a message would go +.SH SYNOPSIS +.in +.5i +.ti -.5i +whom +\%[\-alias\ aliasfile] +\%[\-check] \%[\-nocheck] +\%[\-draft] +.br +\%[\-draftfolder\ +folder] \%[\-draftmessage\ msg] +.br +\%[\-nodraftfolder] +\%[file] +\%[\-version] +\%[\-help] +.in -.5i +.SH DESCRIPTION +\fIWhom\fR is used to expand the headers of a message into a set of +addresses and optionally verify that those addresses are deliverable at +that time (if `\-check' is given). + +The `\-draftfolder\ +folder' and `\-draftmessage\ msg' switches invoke +the \fInmh\fR draft folder facility. This is an advanced (and highly +useful) feature. Consult the \fImh-draft\fR(5) man page for more +information. + +The files specified by the profile entry \*(lqAliasfile:\*(rq and any +additional alias files given by the `\-alias aliasfile' switch will be +read (more than one file, each preceded by `\-alias', can be named). +See \fImh\-alias\fR\0(5) for more information. + +.Fi +^$HOME/\&.mh\(ruprofile~^The user profile +.Pr +^Path:~^To determine the user's nmh directory +.Ps +^Draft\-Folder:~^To find the default draft\-folder +.Ps +^Aliasfile:~^For a default alias file +.Ps +^postproc:~^Program to post the message +.Sa +mh\-alias(5), post(8) +.De +`file' defaults to /draft +.Ds +`\-nocheck' +.Ds +`\-alias %etcdir%/MailAliases' +.Co +None +.Bu +With the `\-check' option, \fIwhom\fR makes no guarantees that the +addresses listed as being ok are really deliverable, rather, an address +being listed as ok means that at the time that \fIwhom\fR was run +the address was thought to be deliverable by the transport service. +For local addresses, this is absolute; for network addresses, it means +that the host is known; for uucp addresses, it (often) means that the +\fIUUCP\fR network is available for use. +.En diff --git a/mkinstalldirs b/mkinstalldirs new file mode 100755 index 0000000..0801ec2 --- /dev/null +++ b/mkinstalldirs @@ -0,0 +1,32 @@ +#! /bin/sh +# mkinstalldirs --- make directory hierarchy +# Author: Noah Friedman +# Created: 1993-05-16 +# Last modified: 1994-03-25 +# Public domain + +errstatus=0 + +for file in ${1+"$@"} ; do + set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'` + shift + + pathcomp= + for d in ${1+"$@"} ; do + pathcomp="$pathcomp$d" + case "$pathcomp" in + -* ) pathcomp=./$pathcomp ;; + esac + + if test ! -d "$pathcomp"; then + echo "mkdir $pathcomp" 1>&2 + mkdir "$pathcomp" || errstatus=$? + fi + + pathcomp="$pathcomp/" + done +done + +exit $errstatus + +# mkinstalldirs ends here diff --git a/mts/Makefile.in b/mts/Makefile.in new file mode 100644 index 0000000..328a9ea --- /dev/null +++ b/mts/Makefile.in @@ -0,0 +1,83 @@ +# +# Makefile for mts subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +srcdir = @srcdir@ +VPATH = @srcdir@ + +# flags passed to recursive makes in subdirectories +MAKEDEFS = CC='$(CC)' CPPFLAGS='$(CPPFLAGS)' DEFS='$(DEFS)' \ +CFLAGS='$(CFLAGS)' LDFLAGS='$(LDFLAGS)' LIBS='$(LIBS)' \ +prefix='$(prefix)' exec_prefix='$(exec_prefix)' bindir='$(bindir)' \ +etcdir='$(etcdir)' libdir='$(libdir)' mandir='$(mandir)' \ +mailspool='$(mailspool)' sendmailpath='$(sendmailpath)' \ +default_editor='$(default_editor)' default_pager='$(default_pager)' + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(AUX) + +# subdirectories +SUBDIRS = smtp sendmail mmdf + +# mail transport agent we are using +MTS = @MTS@ + +# ========= DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +all install uninstall: + for subdir in $(MTS); do \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) $@) || exit 1; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: mostlyclean-recursive mostlyclean-local +clean: clean-recursive clean-local +distclean: distclean-recursive distclean-local +realclean: realclean-recursive realclean-local +superclean: superclean-recursive superclean-local + +mostlyclean-local: + rm -f *~ + +clean-local: mostlyclean-local + +distclean-local: clean-local + rm -f Makefile + +realclean-local: distclean-local + +superclean-local: realclean-local + +mostlyclean-recursive clean-recursive distclean-recursive realclean-recursive superclean-recursive: + for subdir in $(SUBDIRS); do \ + target=`echo $@ | sed 's/-recursive//'`; \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) $$target) || exit 1; \ + done + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = mts + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + @for subdir in $(SUBDIRS); do \ + mkdir $(distdir)/$$subdir; \ + chmod 755 $(distdir)/$$subdir; \ + (cd $$subdir && $(MAKE) $@) || exit 1; \ + done + diff --git a/mts/mmdf/Makefile.in b/mts/mmdf/Makefile.in new file mode 100644 index 0000000..d076a13 --- /dev/null +++ b/mts/mmdf/Makefile.in @@ -0,0 +1,64 @@ +# +# Makefile for mts/mmdf subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +.SUFFIXES: + +# source files +SRC = hosts.c + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(SRC) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +all: + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *~ + +clean: mostlyclean + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = mts/mmdf + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/mts/mmdf/hosts.c b/mts/mmdf/hosts.c new file mode 100644 index 0000000..748d8d6 --- /dev/null +++ b/mts/mmdf/hosts.c @@ -0,0 +1,72 @@ + +/* + * hosts.c -- use MMDF to get hostname information + * + * $Id$ + */ + +#include "../../h/strings.h" +#include "util.h" +#include "mmdf.h" +#include "ch.h" + +#ifdef MMDFII +# include "dm.h" +#endif /* MMDFII */ + +#include "../../zotnet/mts.h" + +/* + * We really should be careful with the number of fd's that this routine + * opens: the ch_seq ch_table.c module likes to keep 6 (yes, SIX) fds around + * to speed-up host lookups in the channel table. Unfortunately, after all + * of them get opened, six are always open (ch_table may close one to open + * another). The bottom line is that if repl calls post, then we get 12 + * (yes, TWELVE) fds open, with only six usable. + * + * send will close all fds >= 3 prior to invoking post. It would be nice + * if one could control ch_seq's use of fds for table lookups, but such is + * life. + * + */ + +#ifndef MMDFII +char * +OfficialName (char *name) +{ + register Chan *ch; + static char buffer[BUFSIZ]; + + return ((ch = ch_h2chan (name, buffer)) == (Chan *) (-1) ? NULL + : ch == (Chan *) NULL ? LocalName () + : buffer); +} +#else /* MMDFII */ + +extern char *invo_name; + +extern short ch_yloc; /* ok to intercept local names */ + +static int inited = 0; + +char * +OfficialName (char *name) +{ + Dmn_route route; + static char buffer[BUFSIZ]; + + if (!inited) { + mmdf_init (invo_name); + inited = 1; + } + switch (dm_v2route (name, buffer, &route)) { + case NOTOK: + case OK: + return ((ch_yloc && lexequ (name, LocalName ())) ? LocalName () + : NULL); + + default: + return buffer; + } +} +#endif /* MMDFII */ diff --git a/mts/sendmail/Makefile.in b/mts/sendmail/Makefile.in new file mode 100644 index 0000000..d5580a5 --- /dev/null +++ b/mts/sendmail/Makefile.in @@ -0,0 +1,89 @@ +# +# Makefile for mts/sendmail subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) + +LORDER = @LORDER@ +TSORT = @TSORT@ +RANLIB = @RANLIB@ + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# source +SRCS = hosts.c sendmail.c + +# object files in libsend.a +OBJS = hosts.o sendmail.o + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +all: libsend.a + +libsend.a: $(OBJS) + rm -f $@ + ar cr $@ `$(LORDER) $(OBJS) | $(TSORT)` + $(RANLIB) $@ + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + rm -f libsend.a + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = mts/sendmail + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/mts/sendmail/hosts.c b/mts/sendmail/hosts.c new file mode 100644 index 0000000..55f3393 --- /dev/null +++ b/mts/sendmail/hosts.c @@ -0,0 +1,135 @@ + +/* + * hosts.c -- find out the official name of a host + * + * $Id$ + */ + +/* + * In the SendMail world, we really don't know what the valid + * hosts are. We could poke around in the sendmail.cf file, but + * that still isn't a guarantee. As a result, we'll say that + * everything is a valid host, and let SendMail worry about it. + */ + +#include +#include +#include + +static struct host { + char *h_name; + char **h_aliases; + struct host *h_next; +} hosts; + + +/* + * static prototypes + */ +static int init_hs(void); + + +char * +OfficialName (char *name) +{ + char *p, *q, site[BUFSIZ]; + struct hostent *hp; + + static char buffer[BUFSIZ]; + char **r; + struct host *h; + + for (p = name, q = site; *p && (q - site < sizeof(site) - 1); p++, q++) + *q = isupper (*p) ? tolower (*p) : *p; + *q = '\0'; + q = site; + + if (!strcasecmp (LocalName(), site)) + return LocalName(); + +#ifndef BIND + sethostent (1); +#endif + + if ((hp = gethostbyname (q))) { + strncpy (buffer, hp->h_name, sizeof(buffer)); + return buffer; + } + if (hosts.h_name || init_hs ()) + for (h = hosts.h_next; h; h = h->h_next) + if (!strcasecmp (h->h_name, q)) + return h->h_name; + else + for (r = h->h_aliases; *r; r++) + if (!strcasecmp (*r, q)) + return h->h_name; + + strncpy (buffer, site, sizeof(buffer)); + return buffer; +} + +/* + * Use hostable as an exception file for those hosts that aren't + * on the Internet (listed in /etc/hosts). These are usually + * PhoneNet and UUCP sites. + */ + +#define NALIASES 50 + +static int +init_hs (void) +{ + char *cp, *dp, **q, **r; + char buffer[BUFSIZ], *aliases[NALIASES]; + register struct host *h; + register FILE *fp; + + if ((fp = fopen (hostable, "r")) == NULL) + return 0; + + h = &hosts; + while (fgets (buffer, sizeof(buffer), fp) != NULL) { + if ((cp = strchr(buffer, '#'))) + *cp = 0; + if ((cp = strchr(buffer, '\n'))) + *cp = 0; + for (cp = buffer; *cp; cp++) + if (isspace (*cp)) + *cp = ' '; + for (cp = buffer; isspace (*cp); cp++) + continue; + if (*cp == 0) + continue; + + q = aliases; + if ((cp = strchr(dp = cp, ' '))) { + *cp = 0; + for (cp++; *cp; cp++) { + while (isspace (*cp)) + cp++; + if (*cp == 0) + break; + if ((cp = strchr(*q++ = cp, ' '))) + *cp = 0; + else + break; + if (q >= aliases + NALIASES) + break; + } + } + + *q = 0; + + h->h_next = (struct host *) calloc (1, sizeof(*h)); + h = h->h_next; + h->h_name = getcpy (dp); + r = h->h_aliases = + (char **) calloc ((size_t) (q - aliases + 1), sizeof(*q)); + for (q = aliases; *q; q++) + *r++ = getcpy (*q); + *r = 0; + } + + fclose (fp); + return 1; +} diff --git a/mts/sendmail/sendmail.c b/mts/sendmail/sendmail.c new file mode 100644 index 0000000..7556503 --- /dev/null +++ b/mts/sendmail/sendmail.c @@ -0,0 +1,827 @@ + +/* + * sendmail.c -- nmh sendmail interface + * + * $Id$ + */ + +#include +#include +#include +#include + +/* + * This module implements an interface to SendMail very similar + * to the MMDF mm_(3) routines. The sm_() routines herein talk + * SMTP to a sendmail process, mapping SMTP reply codes into + * RP_-style codes. + */ + +/* + * On older 4.2BSD machines without the POSIX function `sigaction', + * the alarm handing stuff for time-outs will NOT work due to the way + * syscalls get restarted. This is not really crucial, since SendMail + * is generally well-behaved in this area. + */ + +#ifdef SENDMAILBUG +/* + * It appears that some versions of Sendmail will return Code 451 + * when they don't really want to indicate a failure. + * "Code 451 almost always means sendmail has deferred; we don't + * really want bomb out at this point since sendmail will rectify + * things later." So, if you define SENDMAILBUG, Code 451 is + * considered the same as Code 250. Yuck! + */ +#endif + +#define TRUE 1 +#define FALSE 0 + +#define NBITS ((sizeof (int)) * 8) + +/* + * these codes must all be different! + */ +#define SM_OPEN 90 /* Changed from 30 in case of nameserver flakiness */ +#define SM_HELO 20 +#define SM_RSET 15 +#define SM_MAIL 40 +#define SM_RCPT 120 +#define SM_DATA 20 +#define SM_TEXT 150 +#define SM_DOT 180 +#define SM_QUIT 30 +#define SM_CLOS 10 + +static int sm_addrs = 0; +static int sm_alarmed = 0; +static int sm_child = NOTOK; +static int sm_debug = 0; +static int sm_nl = TRUE; +static int sm_verbose = 0; + +static FILE *sm_rfp = NULL; +static FILE *sm_wfp = NULL; + +#ifdef MPOP +static int sm_ispool = 0; +static char sm_tmpfil[BUFSIZ]; +#endif /* MPOP */ + +static char *sm_noreply = "No reply text given"; +static char *sm_moreply = "; "; + +struct smtp sm_reply; /* global... */ + +#ifdef MPOP +extern int errno; +#endif + +static int doingEHLO; + +#define MAXEHLO 20 +char *EHLOkeys[MAXEHLO + 1]; + +/* + * static prototypes + */ +static int sm_ierror (char *fmt, ...); +static int smtalk (int time, char *fmt, ...); +static int sm_wrecord (char *, int); +static int sm_wstream (char *, int); +static int sm_werror (void); +static int smhear (void); +static int sm_rrecord (char *, int *); +static int sm_rerror (void); +static RETSIGTYPE alrmser (int); + + +int +sm_init (char *client, char *server, int watch, int verbose, + int debug, int onex, int queued) +{ + int i, result, vecp; + int pdi[2], pdo[2]; + char *vec[15]; + + if (watch) + verbose = TRUE; + + sm_verbose = verbose; + sm_debug = debug; + if (sm_rfp != NULL && sm_wfp != NULL) + return RP_OK; + + if (client == NULL || *client == '\0') + if (clientname) + client = clientname; + else + client = LocalName(); /* no clientname -> LocalName */ + +#ifdef ZMAILER + if (client == NULL || *client == '\0') + client = "localhost"; +#endif + + if (pipe (pdi) == NOTOK) + return sm_ierror ("no pipes"); + if (pipe (pdo) == NOTOK) { + close (pdi[0]); + close (pdi[1]); + return sm_ierror ("no pipes"); + } + + for (i = 0; (sm_child = fork ()) == NOTOK && i < 5; i++) + sleep (5); + + switch (sm_child) { + case NOTOK: + close (pdo[0]); + close (pdo[1]); + close (pdi[0]); + close (pdi[1]); + return sm_ierror ("unable to fork"); + + case OK: + if (pdo[0] != fileno (stdin)) + dup2 (pdo[0], fileno (stdin)); + if (pdi[1] != fileno (stdout)) + dup2 (pdi[1], fileno (stdout)); + if (pdi[1] != fileno (stderr)) + dup2 (pdi[1], fileno (stderr)); + for (i = fileno (stderr) + 1; i < NBITS; i++) + close (i); + + vecp = 0; + vec[vecp++] = r1bindex (sendmail, '/'); + vec[vecp++] = "-bs"; +#ifndef ZMAILER + vec[vecp++] = watch ? "-odi" : queued ? "-odq" : "-odb"; + vec[vecp++] = "-oem"; + vec[vecp++] = "-om"; +# ifndef RAND + if (verbose) + vec[vecp++] = "-ov"; +# endif /* not RAND */ +#endif /* not ZMAILER */ + vec[vecp++] = NULL; + + setgid (getegid ()); + setuid (geteuid ()); + execvp (sendmail, vec); + fprintf (stderr, "unable to exec "); + perror (sendmail); + _exit (-1); /* NOTREACHED */ + + default: + SIGNAL (SIGALRM, alrmser); + SIGNAL (SIGPIPE, SIG_IGN); + + close (pdi[1]); + close (pdo[0]); + if ((sm_rfp = fdopen (pdi[0], "r")) == NULL + || (sm_wfp = fdopen (pdo[1], "w")) == NULL) { + close (pdi[0]); + close (pdo[1]); + sm_rfp = sm_wfp = NULL; + return sm_ierror ("unable to fdopen"); + } + sm_alarmed = 0; + alarm (SM_OPEN); + result = smhear (); + alarm (0); + switch (result) { + case 220: + break; + + default: + sm_end (NOTOK); + return RP_RPLY; + } + + if (client && *client) { + doingEHLO = 1; + result = smtalk (SM_HELO, "EHLO %s", client); + doingEHLO = 0; + + if (500 <= result && result <= 599) + result = smtalk (SM_HELO, "HELO %s", client); + + switch (result) { + case 250: + break; + + default: + sm_end (NOTOK); + return RP_RPLY; + } + } + +#ifndef ZMAILER + if (onex) + smtalk (SM_HELO, "ONEX"); +#endif + if (watch) + smtalk (SM_HELO, "VERB on"); + + return RP_OK; + } +} + + +int +sm_winit (int mode, char *from) +{ +#ifdef MPOP + if (sm_ispool && !sm_wfp) { + strlen (strcpy (sm_reply.text, "unable to create new spool file")); + sm_reply.code = NOTOK; + return RP_BHST; + } +#endif /* MPOP */ + + switch (smtalk (SM_MAIL, "%s FROM:<%s>", + mode == S_SEND ? "SEND" : mode == S_SOML ? "SOML" + : mode == S_SAML ? "SAML" : "MAIL", from)) { + case 250: + sm_addrs = 0; + return RP_OK; + + case 500: + case 501: + case 552: + return RP_PARM; + + default: + return RP_RPLY; + } +} + + +int +sm_wadr (char *mbox, char *host, char *path) +{ + switch (smtalk (SM_RCPT, host && *host ? "RCPT TO:<%s%s@%s>" + : "RCPT TO:<%s%s>", + path ? path : "", mbox, host)) { + case 250: + case 251: + sm_addrs++; + return RP_OK; + + case 451: +#ifdef SENDMAILBUG + sm_addrs++; + return RP_OK; +#endif /* SENDMAILBUG */ + case 421: + case 450: + case 452: + return RP_NO; + + case 500: + case 501: + return RP_PARM; + + case 550: + case 551: + case 552: + case 553: + return RP_USER; + + default: + return RP_RPLY; + } +} + + +int +sm_waend (void) +{ + switch (smtalk (SM_DATA, "DATA")) { + case 354: + sm_nl = TRUE; + return RP_OK; + + case 451: +#ifdef SENDMAILBUG + sm_nl = TRUE; + return RP_OK; +#endif /* SENDMAILBUG */ + case 421: + return RP_NO; + + case 500: + case 501: + case 503: + case 554: + return RP_NDEL; + + default: + return RP_RPLY; + } +} + + +int +sm_wtxt (char *buffer, int len) +{ + int result; + + sm_alarmed = 0; + alarm (SM_TEXT); + result = sm_wstream (buffer, len); + alarm (0); + + return (result == NOTOK ? RP_BHST : RP_OK); +} + + +int +sm_wtend (void) +{ + if (sm_wstream ((char *) NULL, 0) == NOTOK) + return RP_BHST; + + switch (smtalk (SM_DOT + 3 * sm_addrs, ".")) { + case 250: + case 251: + return RP_OK; + + case 451: +#ifdef SENDMAILBUG + return RP_OK; +#endif /* SENDMAILBUG */ + case 452: + default: + return RP_NO; + + case 552: + case 554: + return RP_NDEL; + } +} + + +int +sm_end (int type) +{ + int status; + struct smtp sm_note; + + switch (sm_child) { + case NOTOK: + case OK: + return RP_OK; + + default: + break; + } + + if (sm_rfp == NULL && sm_wfp == NULL) + return RP_OK; + + switch (type) { + case OK: + smtalk (SM_QUIT, "QUIT"); + break; + + case NOTOK: + sm_note.code = sm_reply.code; + strncpy (sm_note.text, sm_reply.text, sm_note.length = sm_reply.length);/* fall */ + case DONE: + if (smtalk (SM_RSET, "RSET") == 250 && type == DONE) + return RP_OK; + kill (sm_child, SIGKILL); + discard (sm_rfp); + discard (sm_wfp); + if (type == NOTOK) { + sm_reply.code = sm_note.code; + strncpy (sm_reply.text, sm_note.text, sm_reply.length = sm_note.length); + } + break; + } + if (sm_rfp != NULL) { + alarm (SM_CLOS); + fclose (sm_rfp); + alarm (0); + } + if (sm_wfp != NULL) { + alarm (SM_CLOS); + fclose (sm_wfp); + alarm (0); + } + + status = pidwait (sm_child, OK); + + sm_child = NOTOK; + sm_rfp = sm_wfp = NULL; + + return (status ? RP_BHST : RP_OK); +} + + +static int +sm_ierror (char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap); + va_end(ap); + + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; + + return RP_BHST; +} + + +static int +smtalk (int time, char *fmt, ...) +{ + int result; + char buffer[BUFSIZ]; + va_list ap; + + va_start(ap, fmt); + vsnprintf (buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + if (sm_debug) { + printf ("=> %s\n", buffer); + fflush (stdout); + } + +#ifdef MPOP + if (sm_ispool) { + char file[BUFSIZ]; + + if (strcmp (buffer, ".") == 0) + time = SM_DOT; + fprintf (sm_wfp, "%s\r\n", buffer); + switch (time) { + case SM_DOT: + fflush (sm_wfp); + if (ferror (sm_wfp)) + return sm_werror (); + snprintf (file, sizeof(file), "%s%c.bulk", sm_tmpfil, + (char) (sm_ispool + 'a' - 1)); + if (rename (sm_tmpfil, file) == NOTOK) { + int len; + char *bp; + + snprintf (sm_reply.text, sizeof(sm_reply.text), + "error renaming %s to %s: ", sm_tmpfil, file); + bp = sm_reply.text; + len = strlen (bp); + bp += len; + if ((s = strerror (errno))) + strncpy (bp, s, sizeof(sm_reply.text) - len); + else + snprintf (bp, sizeof(sm_reply.text) - len, + "unknown error %d", errno); + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; + return RP_BHST; + } + fclose (sm_wfp); + if (sm_wfp = fopen (sm_tmpfil, "w")) + chmod (sm_tmpfil, 0600); + sm_ispool++; + /* and fall... */ + + case SM_MAIL: + case SM_RCPT: + result = 250; + break; + + case SM_RSET: + fflush (sm_wfp); + ftruncate (fileno (sm_wfp), 0L); + fseek (sm_wfp, 0L, SEEK_SET); + result = 250; + break; + + case SM_DATA: + result = 354; + break; + + case SM_QUIT: + unlink (sm_tmpfil); + sm_ispool = 0; + result = 221; + break; + + default: + result = 500; + break; + } + if (sm_debug) { + printf ("<= %d\n", result); + fflush (stdout); + } + + sm_reply.text[sm_reply.length = 0] = NULL; + return (sm_reply.code = result); + } +#endif /* MPOP */ + + sm_alarmed = 0; + alarm ((unsigned) time); + if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK) + result = smhear (); + alarm (0); + + return result; +} + + +static int +sm_wrecord (char *buffer, int len) +{ + if (sm_wfp == NULL) + return sm_werror (); + + fwrite (buffer, sizeof *buffer, len, sm_wfp); + fputs ("\r\n", sm_wfp); + fflush (sm_wfp); + + return (ferror (sm_wfp) ? sm_werror () : OK); +} + + +static int +sm_wstream (char *buffer, int len) +{ + char *bp; + static char lc = 0; + + if (sm_wfp == NULL) + return sm_werror (); + + if (buffer == NULL && len == 0) { + if (lc != '\n') + fputs ("\r\n", sm_wfp); + lc = 0; + return (ferror (sm_wfp) ? sm_werror () : OK); + } + + for (bp = buffer; len > 0; bp++, len--) { + switch (*bp) { + case '\n': + sm_nl = TRUE; + fputc ('\r', sm_wfp); + break; + + case '.': + if (sm_nl) + fputc ('.', sm_wfp);/* FALL THROUGH */ + default: + sm_nl = FALSE; + } + fputc (*bp, sm_wfp); + if (ferror (sm_wfp)) + return sm_werror (); + } + + if (bp > buffer) + lc = *--bp; + return (ferror (sm_wfp) ? sm_werror () : OK); +} + + +#ifdef _AIX +/* + * AIX by default will inline the strlen and strcpy commands by redefining + * them as __strlen and __strcpy respectively. This causes compile problems + * with the #ifdef MPOP in the middle. Should the #ifdef MPOP be removed, + * remove these #undefs. + */ +# undef strlen +# undef strcpy +#endif /* _AIX */ + +static int +sm_werror (void) +{ + sm_reply.length = + strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no pipe opened" + : sm_alarmed ? "write to pipe timed out" + : "error writing to pipe")); + + return (sm_reply.code = NOTOK); +} + + +static int +smhear (void) +{ + int i, code, cont, bc, rc, more; + char *bp, *rp; + char **ehlo, buffer[BUFSIZ]; + + if (doingEHLO) { + static int at_least_once = 0; + + if (at_least_once) { + for (ehlo = EHLOkeys; *ehlo; ehlo++) + free (*ehlo); + } else { + at_least_once = 1; + } + + *(ehlo = EHLOkeys) = NULL; + } + +again: + + sm_reply.text[sm_reply.length = 0] = 0; + + rp = sm_reply.text; + rc = sizeof(sm_reply.text) - 1; + + for (more = FALSE; sm_rrecord (bp = buffer, &bc) != NOTOK;) { + if (sm_debug) { + printf ("<= %s\n", buffer); + fflush (stdout); + } + + if (doingEHLO + && strncmp (buffer, "250", sizeof("250") - 1) == 0 + && (buffer[3] == '-' || doingEHLO == 2) + && buffer[4]) { + if (doingEHLO == 2) { + if ((*ehlo = malloc ((size_t) (strlen (buffer + 4) + 1)))) { + strcpy (*ehlo++, buffer + 4); + *ehlo = NULL; + if (ehlo >= EHLOkeys + MAXEHLO) + doingEHLO = 0; + } + else + doingEHLO = 0; + } + else + doingEHLO = 2; + } + + for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--) + continue; + + cont = FALSE; + code = atoi (bp); + bp += 3, bc -= 3; + for (; bc > 0 && isspace (*bp); bp++, bc--) + continue; + if (bc > 0 && *bp == '-') { + cont = TRUE; + bp++, bc--; + for (; bc > 0 && isspace (*bp); bp++, bc--) + continue; + } + + if (more) { + if (code != sm_reply.code || cont) + continue; + more = FALSE; + } else { + sm_reply.code = code; + more = cont; + if (bc <= 0) { + strncpy (buffer, sm_noreply, sizeof(buffer)); + bp = buffer; + bc = strlen (sm_noreply); + } + } + if ((i = min (bc, rc)) > 0) { + strncpy (rp, bp, i); + rp += i; + rc -= i; + if (more && rc > strlen (sm_moreply) + 1) { + strncpy (sm_reply.text + rc, sm_moreply, sizeof(sm_reply.text) - rc); + rc += strlen (sm_moreply); + } + } + if (more) + continue; + if (sm_reply.code < 100) { + if (sm_verbose) { + printf ("%s\n", sm_reply.text); + fflush (stdout); + } + goto again; + } + + sm_reply.length = rp - sm_reply.text; + + return sm_reply.code; + } + + return NOTOK; +} + + +static int +sm_rrecord (char *buffer, int *len) +{ + if (sm_rfp == NULL) + return sm_rerror (); + + buffer[*len = 0] = 0; + + fgets (buffer, BUFSIZ, sm_rfp); + *len = strlen (buffer); + if (ferror (sm_rfp) || feof (sm_rfp)) + return sm_rerror (); + if (buffer[*len - 1] != '\n') + while (getc (sm_rfp) != '\n' && !ferror (sm_rfp) && !feof (sm_rfp)) + continue; + else + if (buffer[*len - 2] == '\r') + *len -= 1; + buffer[*len - 1] = 0; + + return OK; +} + + +static int +sm_rerror (void) +{ + sm_reply.length = + strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no pipe opened" + : sm_alarmed ? "read from pipe timed out" + : feof (sm_rfp) ? "premature end-of-file on pipe" + : "error reading from pipe")); + + return (sm_reply.code = NOTOK); +} + + +static RETSIGTYPE +alrmser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGALRM, alrmser); +#endif + + sm_alarmed++; + if (sm_debug) { + printf ("timed out...\n"); + fflush (stdout); + } +} + + +char * +rp_string (int code) +{ + char *text; + static char buffer[BUFSIZ]; + + switch (sm_reply.code != NOTOK ? code : NOTOK) { + case RP_AOK: + text = "AOK"; + break; + + case RP_MOK: + text = "MOK"; + break; + + case RP_OK: + text = "OK"; + break; + + case RP_RPLY: + text = "RPLY"; + break; + + case RP_BHST: + default: + text = "BHST"; + snprintf (buffer, sizeof(buffer), "[%s] %s", text, sm_reply.text); + return buffer; + + case RP_PARM: + text = "PARM"; + break; + + case RP_NO: + text = "NO"; + break; + + case RP_USER: + text = "USER"; + break; + + case RP_NDEL: + text = "NDEL"; + break; + } + + snprintf (buffer, sizeof(buffer), "[%s] %3d %s", + text, sm_reply.code, sm_reply.text); + return buffer; +} + diff --git a/mts/smtp/Makefile.in b/mts/smtp/Makefile.in new file mode 100644 index 0000000..7d197d5 --- /dev/null +++ b/mts/smtp/Makefile.in @@ -0,0 +1,92 @@ +# +# Makefile for mts/smtp subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) + +LORDER = @LORDER@ +TSORT = @TSORT@ +RANLIB = @RANLIB@ + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# header files +HDRS = smtp.h + +# source +SRCS = hosts.c smtp.c + +# object files in libsmtp.a +OBJS = hosts.o smtp.o + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(HDRS) $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +all: libsmtp.a + +libsmtp.a: $(OBJS) + rm -f $@ + ar cr $@ `$(LORDER) $(OBJS) | $(TSORT)` + $(RANLIB) $@ + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + rm -f libsmtp.a + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = mts/smtp + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/mts/smtp/hosts.c b/mts/smtp/hosts.c new file mode 100644 index 0000000..55f3393 --- /dev/null +++ b/mts/smtp/hosts.c @@ -0,0 +1,135 @@ + +/* + * hosts.c -- find out the official name of a host + * + * $Id$ + */ + +/* + * In the SendMail world, we really don't know what the valid + * hosts are. We could poke around in the sendmail.cf file, but + * that still isn't a guarantee. As a result, we'll say that + * everything is a valid host, and let SendMail worry about it. + */ + +#include +#include +#include + +static struct host { + char *h_name; + char **h_aliases; + struct host *h_next; +} hosts; + + +/* + * static prototypes + */ +static int init_hs(void); + + +char * +OfficialName (char *name) +{ + char *p, *q, site[BUFSIZ]; + struct hostent *hp; + + static char buffer[BUFSIZ]; + char **r; + struct host *h; + + for (p = name, q = site; *p && (q - site < sizeof(site) - 1); p++, q++) + *q = isupper (*p) ? tolower (*p) : *p; + *q = '\0'; + q = site; + + if (!strcasecmp (LocalName(), site)) + return LocalName(); + +#ifndef BIND + sethostent (1); +#endif + + if ((hp = gethostbyname (q))) { + strncpy (buffer, hp->h_name, sizeof(buffer)); + return buffer; + } + if (hosts.h_name || init_hs ()) + for (h = hosts.h_next; h; h = h->h_next) + if (!strcasecmp (h->h_name, q)) + return h->h_name; + else + for (r = h->h_aliases; *r; r++) + if (!strcasecmp (*r, q)) + return h->h_name; + + strncpy (buffer, site, sizeof(buffer)); + return buffer; +} + +/* + * Use hostable as an exception file for those hosts that aren't + * on the Internet (listed in /etc/hosts). These are usually + * PhoneNet and UUCP sites. + */ + +#define NALIASES 50 + +static int +init_hs (void) +{ + char *cp, *dp, **q, **r; + char buffer[BUFSIZ], *aliases[NALIASES]; + register struct host *h; + register FILE *fp; + + if ((fp = fopen (hostable, "r")) == NULL) + return 0; + + h = &hosts; + while (fgets (buffer, sizeof(buffer), fp) != NULL) { + if ((cp = strchr(buffer, '#'))) + *cp = 0; + if ((cp = strchr(buffer, '\n'))) + *cp = 0; + for (cp = buffer; *cp; cp++) + if (isspace (*cp)) + *cp = ' '; + for (cp = buffer; isspace (*cp); cp++) + continue; + if (*cp == 0) + continue; + + q = aliases; + if ((cp = strchr(dp = cp, ' '))) { + *cp = 0; + for (cp++; *cp; cp++) { + while (isspace (*cp)) + cp++; + if (*cp == 0) + break; + if ((cp = strchr(*q++ = cp, ' '))) + *cp = 0; + else + break; + if (q >= aliases + NALIASES) + break; + } + } + + *q = 0; + + h->h_next = (struct host *) calloc (1, sizeof(*h)); + h = h->h_next; + h->h_name = getcpy (dp); + r = h->h_aliases = + (char **) calloc ((size_t) (q - aliases + 1), sizeof(*q)); + for (q = aliases; *q; q++) + *r++ = getcpy (*q); + *r = 0; + } + + fclose (fp); + return 1; +} diff --git a/mts/smtp/smtp.c b/mts/smtp/smtp.c new file mode 100644 index 0000000..f775638 --- /dev/null +++ b/mts/smtp/smtp.c @@ -0,0 +1,1325 @@ + +/* + * smtp.c -- nmh SMTP interface + * + * $Id$ + */ + +#include +#include "smtp.h" +#include +#include + +/* + * This module implements an interface to SendMail very similar + * to the MMDF mm_(3) routines. The sm_() routines herein talk + * SMTP to a sendmail process, mapping SMTP reply codes into + * RP_-style codes. + */ + +/* + * On older 4.2BSD machines without the POSIX function `sigaction', + * the alarm handing stuff for time-outs will NOT work due to the way + * syscalls get restarted. This is not really crucial, since SendMail + * is generally well-behaved in this area. + */ + +#ifdef SENDMAILBUG +/* + * It appears that some versions of Sendmail will return Code 451 + * when they don't really want to indicate a failure. + * "Code 451 almost always means sendmail has deferred; we don't + * really want bomb out at this point since sendmail will rectify + * things later." So, if you define SENDMAILBUG, Code 451 is + * considered the same as Code 250. Yuck! + */ +#endif + +#define TRUE 1 +#define FALSE 0 + +/* + * these codes must all be different! + */ +#define SM_OPEN 90 /* Changed from 30 in case of nameserver flakiness */ +#define SM_HELO 20 +#define SM_RSET 15 +#define SM_MAIL 40 +#define SM_RCPT 120 +#define SM_DATA 20 +#define SM_TEXT 150 +#define SM_DOT 180 +#define SM_QUIT 30 +#define SM_CLOS 10 + +static int sm_addrs = 0; +static int sm_alarmed = 0; +static int sm_debug = 0; +static int sm_nl = TRUE; +static int sm_verbose = 0; + +static FILE *sm_rfp = NULL; +static FILE *sm_wfp = NULL; + +#ifdef MPOP +static int sm_ispool = 0; +static char sm_tmpfil[BUFSIZ]; +#endif /* MPOP */ + +static char *sm_noreply = "No reply text given"; +static char *sm_moreply = "; "; + +struct smtp sm_reply; /* global... */ + +#ifdef MPOP +extern int errno; +#endif + + +#define MAXEHLO 20 + +static int doingEHLO; +char *EHLOkeys[MAXEHLO + 1]; + +/* + * static prototypes + */ +static int rclient (char *, char *, char *); +static int sm_ierror (char *fmt, ...); +static int smtalk (int time, char *fmt, ...); +static int sm_wrecord (char *, int); +static int sm_wstream (char *, int); +static int sm_werror (void); +static int smhear (void); +static int sm_rrecord (char *, int *); +static int sm_rerror (void); +static RETSIGTYPE alrmser (int); +static char *EHLOset (char *); + +#ifdef MPOP +/* + * smtp.c's own static copy of several nmh library subroutines + */ +static char **smail_brkstring (char *, char *, char *); +static int smail_brkany (char, char *); +char **smail_copyip (char **, char **, int); +#endif + + +int +sm_init (char *client, char *server, int watch, int verbose, + int debug, int onex, int queued) +{ + int result, sd1, sd2; + + if (watch) + verbose = TRUE; + + sm_verbose = verbose; + sm_debug = debug; + +#ifdef MPOP + if (sm_ispool) + goto all_done; +#endif + + if (sm_rfp != NULL && sm_wfp != NULL) + goto send_options; + + if (client == NULL || *client == '\0') + if (clientname) + client = clientname; + else + client = LocalName(); /* no clientname -> LocalName */ + +#ifdef ZMAILER + if (client == NULL || *client == '\0') + client = "localhost"; +#endif + + if ((sd1 = rclient (server, "tcp", "smtp")) == NOTOK) + return RP_BHST; + +#ifdef MPOP + if (sm_ispool) { + if (sm_rfp) { + alarm (SM_CLOS); + fclose (sm_rfp); + alarm (0); + sm_rfp = NULL; + } + if ((sm_wfp = fdopen (sd1, "w")) == NULL) { + unlink (sm_tmpfil); + close (sd1); + return sm_ierror ("unable to fdopen"); + } +all_done: ; + sm_reply.text[sm_reply.length = 0] = NULL; + return (sm_reply.code = RP_OK); + } +#endif /* MPOP */ + + if ((sd2 = dup (sd1)) == NOTOK) { + close (sd1); + return sm_ierror ("unable to dup"); + } + + SIGNAL (SIGALRM, alrmser); + SIGNAL (SIGPIPE, SIG_IGN); + + if ((sm_rfp = fdopen (sd1, "r")) == NULL + || (sm_wfp = fdopen (sd2, "w")) == NULL) { + close (sd1); + close (sd2); + sm_rfp = sm_wfp = NULL; + return sm_ierror ("unable to fdopen"); + } + + sm_alarmed = 0; + alarm (SM_OPEN); + result = smhear (); + alarm (0); + + switch (result) { + case 220: + break; + + default: + sm_end (NOTOK); + return RP_RPLY; + } + + /* + * Give EHLO or HELO command + */ + if (client && *client) { + doingEHLO = 1; + result = smtalk (SM_HELO, "EHLO %s", client); + doingEHLO = 0; + + if (result >= 500 && result <= 599) + result = smtalk (SM_HELO, "HELO %s", client); + + if (result != 250) { + sm_end (NOTOK); + return RP_RPLY; + } + } + +send_options: ; + if (watch && EHLOset ("XVRB")) + smtalk (SM_HELO, "VERB on"); + if (onex && EHLOset ("XONE")) + smtalk (SM_HELO, "ONEX"); + if (queued && EHLOset ("XQUE")) + smtalk (SM_HELO, "QUED"); + + return RP_OK; +} + + +#ifdef MPOP +# define MAXARGS 1000 +#endif /* MPOP */ + +static int +rclient (char *server, char *protocol, char *service) +{ + int sd; + char response[BUFSIZ]; +#ifdef MPOP + char *cp; +#endif /* MPOP */ + + if ((sd = client (server, protocol, service, FALSE, response, sizeof(response))) != NOTOK) + return sd; + +#ifdef MPOP + if (!server && servers && (cp = strchr(servers, '/'))) { + char **ap; + char *arguments[MAXARGS]; + + smail_copyip (smail_brkstring (cp = getcpy (servers), " ", "\n"), arguments, MAXARGS); + + for (ap = arguments; *ap; ap++) + if (**ap == '/') { + char *dp; + + if ((dp = strrchr(*ap, '/')) && *++dp == NULL) + *--dp = NULL; + snprintf (sm_tmpfil, sizeof(sm_tmpfil), "%s/smtpXXXXXX", *ap); + mktemp (sm_tmpfil); + + if ((sd = creat (sm_tmpfil, 0600)) != NOTOK) { + sm_ispool = 1; + break; + } + } + + free (cp); + if (sd != NOTOK) + return sd; + } +#endif /* MPOP */ + + sm_ierror ("%s", response); + return NOTOK; +} + + +int +sm_winit (int mode, char *from) +{ + char *smtpcom; + +#ifdef MPOP + if (sm_ispool && !sm_wfp) { + strlen (strcpy (sm_reply.text, "unable to create new spool file")); + sm_reply.code = NOTOK; + return RP_BHST; + } +#endif /* MPOP */ + + switch (mode) { + case S_MAIL: + smtpcom = "MAIL"; + break; + + case S_SEND: + smtpcom = "SEND"; + break; + + case S_SOML: + smtpcom = "SOML"; + break; + + case S_SAML: + smtpcom = "SAML"; + break; + } + + switch (smtalk (SM_MAIL, "%s FROM:<%s>", smtpcom, from)) { + case 250: + sm_addrs = 0; + return RP_OK; + + case 500: + case 501: + case 552: + return RP_PARM; + + default: + return RP_RPLY; + } +} + + +int +sm_wadr (char *mbox, char *host, char *path) +{ + switch (smtalk (SM_RCPT, host && *host ? "RCPT TO:<%s%s@%s>" + : "RCPT TO:<%s%s>", + path ? path : "", mbox, host)) { + case 250: + case 251: + sm_addrs++; + return RP_OK; + + case 451: +#ifdef SENDMAILBUG + sm_addrs++; + return RP_OK; +#endif /* SENDMAILBUG */ + case 421: + case 450: + case 452: + return RP_NO; + + case 500: + case 501: + return RP_PARM; + + case 550: + case 551: + case 552: + case 553: + return RP_USER; + + default: + return RP_RPLY; + } +} + + +int +sm_waend (void) +{ + switch (smtalk (SM_DATA, "DATA")) { + case 354: + sm_nl = TRUE; + return RP_OK; + + case 451: +#ifdef SENDMAILBUG + sm_nl = TRUE; + return RP_OK; +#endif /* SENDMAILBUG */ + case 421: + return RP_NO; + + case 500: + case 501: + case 503: + case 554: + return RP_NDEL; + + default: + return RP_RPLY; + } +} + + +int +sm_wtxt (char *buffer, int len) +{ + int result; + + sm_alarmed = 0; + alarm (SM_TEXT); + result = sm_wstream (buffer, len); + alarm (0); + + return (result == NOTOK ? RP_BHST : RP_OK); +} + + +int +sm_wtend (void) +{ + if (sm_wstream ((char *) NULL, 0) == NOTOK) + return RP_BHST; + + switch (smtalk (SM_DOT + 3 * sm_addrs, ".")) { + case 250: + case 251: + return RP_OK; + + case 451: +#ifdef SENDMAILBUG + return RP_OK; +#endif /* SENDMAILBUG */ + case 452: + default: + return RP_NO; + + case 552: + case 554: + return RP_NDEL; + } +} + + +int +sm_end (int type) +{ + int status; + struct smtp sm_note; + + if (sm_rfp == NULL && sm_wfp == NULL) + return RP_OK; + + switch (type) { + case OK: + smtalk (SM_QUIT, "QUIT"); + break; + + case NOTOK: + sm_note.code = sm_reply.code; + strncpy (sm_note.text, sm_reply.text, sm_note.length = sm_reply.length);/* fall */ + case DONE: + if (smtalk (SM_RSET, "RSET") == 250 && type == DONE) + return RP_OK; + smtalk (SM_QUIT, "QUIT"); + if (type == NOTOK) { + sm_reply.code = sm_note.code; + strncpy (sm_reply.text, sm_note.text, sm_reply.length = sm_note.length); + } + break; + } + +#ifdef MPOP + if (sm_ispool) { + sm_ispool = 0; + + if (sm_wfp) { + unlink (sm_tmpfil); + fclose (sm_wfp); + sm_wfp = NULL; + } + } +#endif /* MPOP */ + + if (sm_rfp != NULL) { + alarm (SM_CLOS); + fclose (sm_rfp); + alarm (0); + } + if (sm_wfp != NULL) { + alarm (SM_CLOS); + fclose (sm_wfp); + alarm (0); + } + + status = 0; + sm_rfp = sm_wfp = NULL; + return (status ? RP_BHST : RP_OK); +} + + +#ifdef MPOP + +int +sm_bulk (char *file) +{ + int cc, i, j, k, result; + long pos; + char *dp, *bp, *cp, s; + char buffer[BUFSIZ], sender[BUFSIZ]; + FILE *fp, *gp; + + gp = NULL; + k = strlen (file) - sizeof(".bulk"); + if ((fp = fopen (file, "r")) == NULL) { + int len; + + snprintf (sm_reply.text, sizeof(sm_reply.text), + "unable to read %s: ", file); + bp = sm_reply.text; + len = strlen (bp); + bp += len; + if ((s = strerror (errno))) + strncpy (bp, s, sizeof(sm_reply.text) - len); + else + snprintf (bp, sizeof(sm_reply.text) - len, "Error %d", errno); + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; + return RP_BHST; + } + if (sm_debug) { + printf ("reading file %s\n", file); + fflush (stdout); + } + + i = j = 0; + while (fgets (buffer, sizeof(buffer), fp)) { + if (j++ == 0) + strncpy (sender, buffer + sizeof("MAIL FROM:") - 1, sizeof(sender)); + if (strcmp (buffer, "DATA\r\n") == 0) { + i = 1; + break; + } + } + if (i == 0) { + if (sm_debug) { + printf ("no DATA...\n"); + fflush (stdout); + } +losing0: + snprintf (buffer, sizeof(buffer), "%s.bad", file); + rename (file, buffer); + if (gp) { + snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); + unlink (buffer); + fclose (gp); + } + fclose (fp); + return RP_OK; + } + if (j < 3) { + if (sm_debug) { + printf ("no %srecipients...\n", j < 1 ? "sender or " : ""); + fflush (stdout); + } + goto losing0; + } + + if ((cp = malloc ((size_t) (cc = (pos = ftell (fp)) + 1))) == NULL) { + sm_reply.length = strlen (strcpy (sm_reply.text, "out of memory")); +losing1: ; + sm_reply.code = NOTOK; + fclose (fp); + return RP_BHST; + } + fseek (fp, 0L, SEEK_SET); + for (dp = cp, i = 0; i++ < j; dp += strlen (dp)) + if (fgets (dp, cc - (dp - cp), fp) == NULL) { + sm_reply.length = strlen (strcpy (sm_reply.text, "premature eof")); +losing2: + free (cp); + goto losing1; + } + *dp = NULL; + + for (dp = cp, i = cc - 1; i > 0; dp += cc, i -= cc) + if ((cc = write (fileno (sm_wfp), dp, i)) == NOTOK) { + int len; +losing3: + strcpy (sm_reply.text, "error writing to server: ", + sizeof(sm_reply.text)); + bp = sm_reply.text; + len = strlen (bp); + bp += len; + if ((s = strerror (errno))) + strncpy (bp, s, sizeof(sm_reply.text) - len); + else + snprintf (bp, sizeof(sm_reply.text) - len, + "unknown error %d", errno); + sm_reply.length = strlen (sm_reply.text); + goto losing2; + } + else + if (sm_debug) { + printf ("wrote %d octets to server\n", cc); + fflush (stdout); + } + + for (dp = cp, i = 0; i++ < j; dp = strchr(dp, '\n'), dp++) { + if (sm_debug) { + if (bp = strchr(dp, '\r')) + *bp = NULL; + printf ("=> %s\n", dp); + fflush (stdout); + if (bp) + *bp = '\r'; + } + + switch (smhear () + (i == 1 ? 1000 : i != j ? 2000 : 3000)) { + case 1000 + 250: + sm_addrs = 0; + result = RP_OK; + break; + + case 1000 + 500: + case 1000 + 501: + case 1000 + 552: + case 2000 + 500: + case 2000 + 501: + result = RP_PARM; + smtalk (SM_RSET, "RSET"); + free (cp); + goto losing0; + + case 2000 + 250: + case 2000 + 251: + sm_addrs++; + result = RP_OK; + break; + + case 2000 + 451: +#ifdef SENDMAILBUG + sm_addrs++; + result = RP_OK; + break; +#endif + case 2000 + 421: + case 2000 + 450: + case 2000 + 452: + result = RP_NO; + goto bad_addr; + + case 2000 + 550: + case 2000 + 551: + case 2000 + 552: + case 2000 + 553: + result = RP_USER; +bad_addr: + if (k <= 0 || strcmp (sender, "<>\r\n") == 0) + break; + if (gp == NULL) { + int l; + snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); + if ((gp = fopen (buffer, "w+")) == NULL) + goto bad_data; + fprintf (gp, "MAIL FROM:<>\r\nRCPT TO:%sDATA\r\n", sender); + l = strlen (sender); + fprintf (gp, + "To: %*.*s\r\nSubject: Invalid addresses (%s)\r\n", + l - 4, l - 4, sender + 1, file); + fprintf (gp, "Date: %s\r\nFrom: Postmaster@%s\r\n\r\n", + dtimenow (0), LocalName ()); + } + if (bp = strchr(dp, '\r')) + *bp = NULL; + fprintf (gp, "=> %s\r\n", dp); + if (bp) + *bp = '\r'; + fprintf (gp, "<= %s\r\n", rp_string (result)); + fflush (gp); + break; + + case 3000 + 354: +#ifdef SENDMAILBUG +ok_data: +#endif + result = RP_OK; + break; + + case 3000 + 451: +#ifdef SENDMAILBUG + goto ok_data; +#endif + case 3000 + 421: + result = RP_NO; +bad_data: + smtalk (SM_RSET, "RSET"); + free (cp); + if (gp) { + snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); + unlink (buffer); + fclose (gp); + } + fclose (fp); + return result; + + case 3000 + 500: + case 3000 + 501: + case 3000 + 503: + case 3000 + 554: + smtalk (SM_RSET, "RSET"); + free (cp); + goto no_dice; + + default: + result = RP_RPLY; + goto bad_data; + } + } + free (cp); + + { +#ifdef HAVE_ST_BLKSIZE + struct stat st; + + if (fstat (fileno (sm_wfp), &st) == NOTOK || (cc = st.st_blksize) < BUFSIZ) + cc = BUFSIZ; +#else + cc = BUFSIZ; +#endif + if ((cp = malloc ((size_t) cc)) == NULL) { + smtalk (SM_RSET, "RSET"); + sm_reply.length = strlen (strcpy (sm_reply.text, "out of memory")); + goto losing1; + } + } + + fseek (fp, pos, SEEK_SET); + for (;;) { + int eof = 0; + + for (dp = cp, i = cc; i > 0; dp += j, i -= j) + if ((j = fread (cp, sizeof(*cp), i, fp)) == OK) { + if (ferror (fp)) { + int len; + + snprintf (sm_reply.text, sizeof(sm_reply.text), + "error reading %s: ", file); + bp = sm_reply.text; + len = strlen (bp); + bp += len; + if ((s = strerror (errno))) + strncpy (bp, s, sizeof(sm_reply.text) - len); + else + snprintf (bp, sizeof(sm_reply.text) - len, + "unknown error %d", errno); + sm_reply.length = strlen (sm_reply.text); + goto losing2; + } + cc = dp - cp; + eof = 1; + break; + } + + for (dp = cp, i = cc; i > 0; dp += j, i -= j) + if ((j = write (fileno (sm_wfp), dp, i)) == NOTOK) + goto losing3; + else + if (sm_debug) { + printf ("wrote %d octets to server\n", j); + fflush (stdout); + } + + if (eof) + break; + } + free (cp); + + switch (smhear ()) { + case 250: + case 251: +#ifdef SENDMAILBUG +ok_dot: +#endif + result = RP_OK; + unlink (file); + break; + + case 451: +#ifdef SENDMAILBUG + goto ok_dot; +#endif + case 452: + default: + result = RP_NO; + if (gp) { + snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); + unlink (buffer); + fclose (gp); + gp = NULL; + } + break; + + case 552: + case 554: +no_dice: + result = RP_NDEL; + if (k <= 0 || strcmp (sender, "<>\r\n") == 0) { + unlink (file); + break; + } + if (gp) { + fflush (gp); + ftruncate (fileno (gp), 0L); + fseek (gp, 0L, SEEK_SET); + } + else { + snprintf (buffer, sizeof(buffer), "%*.*sA.bulk", k, k, file); + if ((gp = fopen (buffer, "w")) == NULL) + break; + } + fprintf (gp, "MAIL FROM:<>\r\nRCPT TO:%sDATA\r\n", sender); + i = strlen (sender); + fprintf (gp, "To: %*.*s\r\nSubject: Failed mail (%s)\r\n", + i - 4, i - 4, sender + 1, file); + fprintf (gp, "Date: %s\r\nFrom: Postmaster@%s\r\n\r\n", + dtimenow (0), LocalName ()); + break; + } + + if (gp) { + fputs ("\r\n------- Begin Returned message\r\n\r\n", gp); + fseek (fp, pos, SEEK_SET); + while (fgets (buffer, sizeof(buffer), fp)) { + if (buffer[0] == '-') + fputs ("- ", gp); + if (strcmp (buffer, ".\r\n")) + fputs (buffer, gp); + } + fputs ("\r\n------- End Returned Message\r\n\r\n.\r\n", gp); + fflush (gp); + if (!ferror (gp)) + unlink (file); + fclose (gp); + } + fclose (fp); + + return result; +} +#endif /* MPOP */ + + +static int +sm_ierror (char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsnprintf (sm_reply.text, sizeof(sm_reply.text), fmt, ap); + va_end(ap); + + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; + + return RP_BHST; +} + + +static int +smtalk (int time, char *fmt, ...) +{ + va_list ap; + int result; + char buffer[BUFSIZ]; + + va_start(ap, fmt); + vsnprintf (buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + if (sm_debug) { + printf ("=> %s\n", buffer); + fflush (stdout); + } + +#ifdef MPOP + if (sm_ispool) { + char file[BUFSIZ]; + + if (strcmp (buffer, ".") == 0) + time = SM_DOT; + fprintf (sm_wfp, "%s\r\n", buffer); + switch (time) { + case SM_DOT: + fflush (sm_wfp); + if (ferror (sm_wfp)) + return sm_werror (); + snprintf (file, sizeof(file), "%s%c.bulk", sm_tmpfil, + (char) (sm_ispool + 'a' - 1)); + if (rename (sm_tmpfil, file) == NOTOK) { + int len; + char *bp; + + snprintf (sm_reply.text, sizeof(sm_reply.text), + "error renaming %s to %s: ", sm_tmpfil, file); + bp = sm_reply.text; + len = strlen (bp); + bp += len; + if ((s = strerror (errno))) + strncpy (bp, s, sizeof(sm_reply.text) - len); + else + snprintf (bp, sizeof(sm_reply.text) - len, + "unknown error %d", errno); + sm_reply.length = strlen (sm_reply.text); + sm_reply.code = NOTOK; + return RP_BHST; + } + fclose (sm_wfp); + if (sm_wfp = fopen (sm_tmpfil, "w")) + chmod (sm_tmpfil, 0600); + sm_ispool++; + /* and fall... */ + + case SM_MAIL: + case SM_RCPT: + result = 250; + break; + + case SM_RSET: + fflush (sm_wfp); + ftruncate (fileno (sm_wfp), 0L); + fseek (sm_wfp, 0L, SEEK_SET); + result = 250; + break; + + case SM_DATA: + result = 354; + break; + + case SM_QUIT: + unlink (sm_tmpfil); + sm_ispool = 0; + result = 221; + break; + + default: + result = 500; + break; + } + if (sm_debug) { + printf ("<= %d\n", result); + fflush (stdout); + } + + sm_reply.text[sm_reply.length = 0] = NULL; + return (sm_reply.code = result); + } +#endif /* MPOP */ + + sm_alarmed = 0; + alarm ((unsigned) time); + if ((result = sm_wrecord (buffer, strlen (buffer))) != NOTOK) + result = smhear (); + alarm (0); + + return result; +} + + +/* + * write the buffer to the open SMTP channel + */ + +static int +sm_wrecord (char *buffer, int len) +{ + if (sm_wfp == NULL) + return sm_werror (); + + fwrite (buffer, sizeof(*buffer), len, sm_wfp); + fputs ("\r\n", sm_wfp); + fflush (sm_wfp); + + return (ferror (sm_wfp) ? sm_werror () : OK); +} + + +static int +sm_wstream (char *buffer, int len) +{ + char *bp; + static char lc = '\0'; + + if (sm_wfp == NULL) + return sm_werror (); + + if (buffer == NULL && len == 0) { + if (lc != '\n') + fputs ("\r\n", sm_wfp); + lc = '\0'; + return (ferror (sm_wfp) ? sm_werror () : OK); + } + + for (bp = buffer; len > 0; bp++, len--) { + switch (*bp) { + case '\n': + sm_nl = TRUE; + fputc ('\r', sm_wfp); + break; + + case '.': + if (sm_nl) + fputc ('.', sm_wfp);/* FALL THROUGH */ + default: + sm_nl = FALSE; + } + fputc (*bp, sm_wfp); + if (ferror (sm_wfp)) + return sm_werror (); + } + + if (bp > buffer) + lc = *--bp; + return (ferror (sm_wfp) ? sm_werror () : OK); +} + + +#ifdef _AIX +/* + * AIX by default will inline the strlen and strcpy commands by redefining + * them as __strlen and __strcpy respectively. This causes compile problems + * with the #ifdef MPOP in the middle. Should the #ifdef MPOP be removed, + * remove these #undefs. + */ +# undef strlen +# undef strcpy +#endif /* _AIX */ + +static int +sm_werror (void) +{ + sm_reply.length = + strlen (strcpy (sm_reply.text, sm_wfp == NULL ? "no socket opened" + : sm_alarmed ? "write to socket timed out" +#ifdef MPOP + : sm_ispool ? "error writing to spool file" +#endif + : "error writing to socket")); + + return (sm_reply.code = NOTOK); +} + + +static int +smhear (void) +{ + int i, code, cont, bc, rc, more; + char *bp, *rp; + char **ehlo, buffer[BUFSIZ]; + + if (doingEHLO) { + static int at_least_once = 0; + + if (at_least_once) { + char *ep; + + for (ehlo = EHLOkeys; *ehlo; ehlo++) { + ep = *ehlo; + free (ep); + } + } else { + at_least_once = 1; + } + + ehlo = EHLOkeys; + *ehlo = NULL; + } + +again: ; + + sm_reply.length = 0; + sm_reply.text[0] = 0; + rp = sm_reply.text; + rc = sizeof(sm_reply.text) - 1; + + for (more = FALSE; sm_rrecord (bp = buffer, &bc) != NOTOK;) { + if (sm_debug) { + printf ("<= %s\n", buffer); + fflush (stdout); + } + + if (doingEHLO + && strncmp (buffer, "250", sizeof("250") - 1) == 0 + && (buffer[3] == '-' || doingEHLO == 2) + && buffer[4]) { + if (doingEHLO == 2) { + if ((*ehlo = malloc ((size_t) (strlen (buffer + 4) + 1)))) { + strcpy (*ehlo++, buffer + 4); + *ehlo = NULL; + if (ehlo >= EHLOkeys + MAXEHLO) + doingEHLO = 0; + } + else + doingEHLO = 0; + } + else + doingEHLO = 2; + } + + for (; bc > 0 && (!isascii (*bp) || !isdigit (*bp)); bp++, bc--) + continue; + + cont = FALSE; + code = atoi (bp); + bp += 3, bc -= 3; + for (; bc > 0 && isspace (*bp); bp++, bc--) + continue; + if (bc > 0 && *bp == '-') { + cont = TRUE; + bp++, bc--; + for (; bc > 0 && isspace (*bp); bp++, bc--) + continue; + } + + if (more) { + if (code != sm_reply.code || cont) + continue; + more = FALSE; + } else { + sm_reply.code = code; + more = cont; + if (bc <= 0) { + strncpy (buffer, sm_noreply, sizeof(buffer)); + bp = buffer; + bc = strlen (sm_noreply); + } + } + + if ((i = min (bc, rc)) > 0) { + strncpy (rp, bp, i); + rp += i, rc -= i; + if (more && rc > strlen (sm_moreply) + 1) { + strcpy (sm_reply.text + rc, sm_moreply); + rc += strlen (sm_moreply); + } + } + if (more) + continue; + if (sm_reply.code < 100) { + if (sm_verbose) { + printf ("%s\n", sm_reply.text); + fflush (stdout); + } + goto again; + } + + sm_reply.length = rp - sm_reply.text; + return sm_reply.code; + } + return NOTOK; +} + + +static int +sm_rrecord (char *buffer, int *len) +{ + if (sm_rfp == NULL) + return sm_rerror (); + + buffer[*len = 0] = 0; + + fgets (buffer, BUFSIZ, sm_rfp); + *len = strlen (buffer); + if (ferror (sm_rfp) || feof (sm_rfp)) + return sm_rerror (); + if (buffer[*len - 1] != '\n') + while (getc (sm_rfp) != '\n' && !ferror (sm_rfp) && !feof (sm_rfp)) + continue; + else + if (buffer[*len - 2] == '\r') + *len -= 1; + buffer[*len - 1] = 0; + + return OK; +} + + +static int +sm_rerror (void) +{ + sm_reply.length = + strlen (strcpy (sm_reply.text, sm_rfp == NULL ? "no socket opened" + : sm_alarmed ? "read from socket timed out" + : feof (sm_rfp) ? "premature end-of-file on socket" + : "error reading from socket")); + + return (sm_reply.code = NOTOK); +} + + +static RETSIGTYPE +alrmser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGALRM, alrmser); +#endif + + sm_alarmed++; + if (sm_debug) { + printf ("timed out...\n"); + fflush (stdout); + } +} + + +char * +rp_string (int code) +{ + char *text; + static char buffer[BUFSIZ]; + + switch (sm_reply.code != NOTOK ? code : NOTOK) { + case RP_AOK: + text = "AOK"; + break; + + case RP_MOK: + text = "MOK"; + break; + + case RP_OK: + text = "OK"; + break; + + case RP_RPLY: + text = "RPLY"; + break; + + case RP_BHST: + default: + text = "BHST"; + snprintf (buffer, sizeof(buffer), "[%s] %s", text, sm_reply.text); + return buffer; + + case RP_PARM: + text = "PARM"; + break; + + case RP_NO: + text = "NO"; + break; + + case RP_USER: + text = "USER"; + break; + + case RP_NDEL: + text = "NDEL"; + break; + } + + snprintf (buffer, sizeof(buffer), "[%s] %3d %s", + text, sm_reply.code, sm_reply.text); + return buffer; +} + + +#ifdef MPOP + +static char *broken[MAXARGS + 1]; + +static char ** +smail_brkstring (char *strg, char *brksep, char *brkterm) +{ + int bi; + char c, *sp; + + sp = strg; + + for (bi = 0; bi < MAXARGS; bi++) { + while (smail_brkany (c = *sp, brksep)) + *sp++ = 0; + if (!c || smail_brkany (c, brkterm)) { + *sp = 0; + broken[bi] = 0; + return broken; + } + + broken[bi] = sp; + while ((c = *++sp) && !smail_brkany (c, brksep) && !smail_brkany (c, brkterm)) + continue; + } + broken[MAXARGS] = 0; + + return broken; +} + + +/* + * returns 1 if chr in strg, 0 otherwise + */ +static int +smail_brkany (char chr, char *strg) +{ + char *sp; + + if (strg) + for (sp = strg; *sp; sp++) + if (chr == *sp) + return 1; + return 0; +} + +/* + * copy a string array and return pointer to end + */ +char ** +smail_copyip (char **p, char **q, int len_q) +{ + while (*p && --len_q > 0) + *q++ = *p++; + + *q = NULL; + + return q; +} + +#endif /* MPOP */ + + +static char * +EHLOset (char *s) +{ + size_t len; + char *ep, **ehlo; + + len = strlen (s); + + for (ehlo = EHLOkeys; *ehlo; ehlo++) { + ep = *ehlo; + if (strncmp (ep, s, len) == 0) { + for (ep += len; *ep == ' '; ep++) + continue; + return ep; + } + } + + return 0; +} diff --git a/mts/smtp/smtp.h b/mts/smtp/smtp.h new file mode 100644 index 0000000..772035d --- /dev/null +++ b/mts/smtp/smtp.h @@ -0,0 +1,295 @@ + +/* + * smtp.h -- definitions for the nmh SMTP Interface + * + * $Id$ + */ + +/* various modes for SMTP */ +#define S_MAIL 0 +#define S_SEND 1 +#define S_SOML 2 +#define S_SAML 3 + +struct smtp { + int code; + int length; + char text[BUFSIZ]; +}; + +/* + * prototypes + */ +/* int client (); */ +int sm_init (char *, char *, int, int, int, int, int); +int sm_winit (int, char *); +int sm_wadr (char *, char *, char *); +int sm_waend (void); +int sm_wtxt (char *, int); +int sm_wtend (void); +int sm_end (int); +char *rp_string (int); + +#ifdef MPOP +int sm_bulk (char *); +#endif + + +/* The remainder of this file is derived from "mmdf.h" */ + +/* + * Copyright (C) 1979,1980,1981,1982,1983 University of Delaware + * Used by permission, May, 1984. + */ + +/* + * MULTI-CHANNEL MEMO DISTRIBUTION FACILITY (MMDF) + * + * + * Copyright (C) 1979,1980,1981,1982,1983 University of Delaware + * + * Department of Electrical Engineering + * University of Delaware + * Newark, Delaware 19711 + * + * Phone: (302) 738-1163 + * + * + * This program module was developed as part of the University + * of Delaware's Multi-Channel Memo Distribution Facility (MMDF). + * + * Acquisition, use, and distribution of this module and its listings + * are subject restricted to the terms of a license agreement. + * Documents describing systems using this module must cite its source. + * + * The above statements must be retained with all copies of this + * program and may not be removed without the consent of the + * University of Delaware. + * + */ + +/* Reply Codes for MMDF + * + * Based on: "Revised FTP Reply Codes", by Jon Postel & Nancy Neigus Arpanet + * RFC 640 / NIC 30843, in the "Arpanet Protocol Handbook", E. Feinler + * and J. Postel (eds.), NIC 7104, Network Information Center, SRI + * International: Menlo Park, CA. (NTIS AD-A0038901) + * + * Actual values are different, but scheme is same. Codes must fit into + * 8-bits (to pass on exit() calls); fields are packed 2-3-3 and interpreted + * as octal numbers. + * + * Basic format: + * + * 0yz: positive completion; entire action done + * 1yz: positive intermediate; only part done + * 2yz: Transient negative completion; may work later + * 3yz: Permanent negative completion; you lose forever + * + * x0z: syntax + * x1z: general; doesn't fit any other category + * x2z: connections; truly transfer-related + * x3z: user/authentication/account + * x4x: mail + * x5z: file system + * + * 3-bit z field is unique to the reply. In the following, + * the RP_xVAL defines are available for masking to obtain a field. + */ + +/* + * FIELD DEFINITIONS & BASIC VALUES + */ + +/* FIELD 1: Basic degree of success (2-bits) */ + +#define RP_BTYP '\200' /* good vs. bad; on => bad */ +#define RP_BVAL '\300' /* basic degree of success */ + +#define RP_BOK '\000' /* went fine; all done */ +#define RP_BPOK '\100' /* only the first part got done */ +#define RP_BTNO '\200' /* temporary failure; try later */ +#define RP_BNO '\300' /* not now, nor never; you lose */ + +/* FIELD 2: Basic domain of discourse (3-bits) */ + +#define RP_CVAL '\070' /* basic category (domain) of reply */ + +#define RP_CSYN '\000' /* purely a matter of form */ +#define RP_CGEN '\010' /* couldn't find anywhere else for it */ +#define RP_CCON '\020' /* data-transfer-related issue */ +#define RP_CUSR '\030' /* pertaining to the user */ +#define RP_CMAI '\040' /* specific to mail semantics */ +#define RP_CFIL '\050' /* file system */ +#define RP_CLIO '\060' /* local i/o system */ + +/* FIELD 3: Specific value for this reply (3-bits) */ + +#define RP_SVAL '\007' /* specific value of reply */ + + +/* + * SPECIFIC SUCCESS VALUES + */ + +/* + * Complete Success + */ + +/* done (e.g., w/transaction) */ +#define RP_DONE (RP_BOK | RP_CGEN | '\000') + +/* general-purpose OK */ +#define RP_OK (RP_BOK | RP_CGEN | '\001') + +/* message is accepted (w/text) */ +#define RP_MOK (RP_BOK | RP_CMAI | '\000') + + +/* + * Partial Success + */ + +/* you are the requestor */ +#define RP_MAST (RP_BPOK| RP_CGEN | '\000') + +/* you are the requestee */ +#define RP_SLAV (RP_BPOK| RP_CGEN | '\001') + +/* message address is accepted */ +#define RP_AOK (RP_BPOK| RP_CMAI | '\000') + + +/* + * SPECIFIC FALURE VALUES + */ + +/* + * Partial Failure + */ + +/* not now; maybe later */ +#define RP_AGN (RP_BTNO | RP_CGEN | '\000') + +/* timeout */ +#define RP_TIME (RP_BTNO | RP_CGEN | '\001') + +/* no-op; nothing done, this time */ +#define RP_NOOP (RP_BTNO | RP_CGEN | '\002') + +/* encountered an end of file */ +#define RP_EOF (RP_BTNO | RP_CGEN | '\003') + +/* channel went bad */ +#define RP_NET (RP_BTNO | RP_CCON | '\000') + +/* foreign host screwed up */ +#define RP_BHST (RP_BTNO | RP_CCON | '\001') + +/* host went away */ +#define RP_DHST (RP_BTNO | RP_CCON | '\002') + +/* general net i/o problem */ +#define RP_NIO (RP_BTNO | RP_CCON | '\004') + +/* error reading/writing file */ +#define RP_FIO (RP_BTNO | RP_CFIL | '\000') + +/* unable to create file */ +#define RP_FCRT (RP_BTNO | RP_CFIL | '\001') + +/* unable to open file */ +#define RP_FOPN (RP_BTNO | RP_CFIL | '\002') + +/* general local i/o problem */ +#define RP_LIO (RP_BTNO | RP_CLIO | '\000') + +/* resource currently locked */ +#define RP_LOCK (RP_BTNO | RP_CLIO | '\001') + + +/* + * Complete Failure + */ + +/* bad mechanism/path; try alternate? */ +#define RP_MECH (RP_BNO | RP_CGEN | '\000') + +/* general-purpose NO */ +#define RP_NO (RP_BNO | RP_CGEN | '\001') + +/* general prototocol error */ +#define RP_PROT (RP_BNO | RP_CCON | '\000') + +/* bad reply code (PERMANENT ERROR) */ +#define RP_RPLY (RP_BNO | RP_CCON | '\001') + +/* couldn't deliver */ +#define RP_NDEL (RP_BNO | RP_CMAI | '\000') + +/* couldn't parse the request */ +#define RP_HUH (RP_BNO | RP_CSYN | '\000') + +/* no such command defined */ +#define RP_NCMD (RP_BNO | RP_CSYN | '\001') + +/* bad parameter */ +#define RP_PARM (RP_BNO | RP_CSYN | '\002') + +/* command not implemented */ +#define RP_UCMD (RP_BNO | RP_CSYN | '\003') + +/* unknown user */ +#define RP_USER (RP_BNO | RP_CUSR | '\000') + + +/* + * Macros to access reply info + */ + +/* get the entire return value */ +#define rp_gval(val) ((signed char) (val)) + + +/* + * The next three give the field's bits, within the whole value + */ + +/* get the basic part of return value */ +#define rp_gbval(val) (rp_gval (val) & RP_BVAL) + +/* get the domain part of value */ +#define rp_gcval(val) (rp_gval (val) & RP_CVAL) + +/* get the specific part of value */ +#define rp_gsval(val) (rp_gval (val) & RP_SVAL) + + +/* + * The next three give the numeric value withing the field + */ + +/* get the basic part right-shifted */ +#define rp_gbbit(val) ((rp_gval (val) >> 6) & 03) + +/* get the domain part right-shifted */ +#define rp_gcbit(val) ((rp_gval (val) >> 3) & 07) + +/* get the specific part right-shifted */ +#define rp_gsbit(val) (rp_gval (val) & 07) + + +/* + * MACHINE DEPENDENCY + * + * The following treat the value as strictly numeric. + * It relies on the negative values being numerically + * negative. + */ + +/* is return value positive? */ +#define rp_isgood(val) (rp_gval (val) >= 0) + +/* is return value negative? */ +#define rp_isbad(val) (rp_gval (val) < 0) + diff --git a/sbr/Makefile.in b/sbr/Makefile.in new file mode 100644 index 0000000..9aad677 --- /dev/null +++ b/sbr/Makefile.in @@ -0,0 +1,130 @@ +# +# Makefile for sbr subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I.. -I. -I$(top_srcdir) + +AWK = @AWK@ +LORDER = @LORDER@ +TSORT = @TSORT@ +RANLIB = @RANLIB@ + +LIBOBJS = @LIBOBJS@ + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# this header file is parsed to generate signal messages (sigmsg.h) +SIGNAL_H = @SIGNAL_H@ + +# source for library functions +SRCS = add.c addrsbr.c ambigsw.c atooi.c brkstring.c check_charset.c \ + closefds.c concat.c context_del.c context_find.c context_foil.c \ + context_read.c context_replace.c context_save.c copy.c \ + copyip.c cpydata.c cpydgst.c discard.c done.c error.c \ + fdcompare.c folder_addmsg.c folder_delmsgs.c folder_free.c \ + folder_pack.c folder_read.c folder_realloc.c gans.c \ + getans.c getanswer.c getarguments.c getcpy.c getfolder.c fmt_addr.c \ + fmt_compile.c fmt_new.c fmt_rfc2047.c fmt_scan.c lock_file.c \ + m_atoi.c m_backup.c m_convert.c m_draft.c m_getfld.c m_gmprot.c \ + m_maildir.c m_name.c m_scratch.c m_tmpfil.c makedir.c \ + path.c peekc.c pidwait.c pidstatus.c print_help.c \ + print_sw.c print_version.c push.c putenv.c pwd.c refile.c \ + remdir.c r1bindex.c readconfig.c seq_add.c seq_bits.c seq_del.c \ + seq_getnum.c seq_list.c seq_nameok.c seq_print.c seq_read.c \ + seq_save.c seq_setcur.c seq_setprev.c seq_setunseen.c showfile.c \ + signals.c smatch.c snprintb.c ssequal.c strcasecmp.c strindex.c \ + trimcpy.c uprf.c vfgets.c fmt_def.c m_msgdef.c + +# source for compatibility functions +COMPAT = snprintf.c strdup.c strerror.c ruserpass.c + +OBJS = add.o addrsbr.o ambigsw.o atooi.o brkstring.o check_charset.o \ + closefds.o concat.o context_del.o context_find.o context_foil.o \ + context_read.o context_replace.o context_save.o copy.o \ + copyip.o cpydata.o cpydgst.o discard.o done.o error.o \ + fdcompare.o folder_addmsg.o folder_delmsgs.o folder_free.o \ + folder_pack.o folder_read.o folder_realloc.o gans.o \ + getans.o getanswer.o getarguments.o getcpy.o getfolder.o fmt_addr.o \ + fmt_compile.o fmt_new.o fmt_rfc2047.o fmt_scan.o lock_file.o \ + m_atoi.o m_backup.o m_convert.o m_draft.o m_getfld.o m_gmprot.o \ + m_maildir.o m_name.o m_scratch.o m_tmpfil.o makedir.o \ + path.o peekc.o pidwait.o pidstatus.o print_help.o \ + print_sw.o print_version.o push.o putenv.o pwd.o refile.o \ + remdir.o r1bindex.o readconfig.o seq_add.o seq_bits.o seq_del.o \ + seq_getnum.o seq_list.o seq_nameok.o seq_print.o seq_read.o \ + seq_save.o seq_setcur.o seq_setprev.o seq_setunseen.o showfile.o \ + signals.o smatch.o snprintb.o ssequal.o strcasecmp.o strindex.o \ + trimcpy.o uprf.o vfgets.o fmt_def.o m_msgdef.o \ + $(LIBOBJS) + +# auxiliary files +AUX = Makefile.in sigmsg.awk + +# all files in this directory included in the distribution +DIST = $(SRCS) $(COMPAT) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +# default target +all: libmh.a + +sigmsg.h: sigmsg.awk + $(AWK) -f $(srcdir)/sigmsg.awk $(SIGNAL_H) > $@ + +pidstatus.o: sigmsg.h + +libmh.a: $(OBJS) + rm -f libmh.a + ar cr libmh.a `$(LORDER) $(OBJS) | $(TSORT)` + $(RANLIB) libmh.a + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + rm -f libmh.a sigmsg.h + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = sbr + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/sbr/add.c b/sbr/add.c new file mode 100644 index 0000000..f38110f --- /dev/null +++ b/sbr/add.c @@ -0,0 +1,44 @@ + +/* + * add.c -- If "s1" is NULL, this routine just creates a + * -- copy of "s2" into newly malloc'ed memory. + * -- + * -- If "s1" is not NULL, then copy the concatenation + * -- of "s1" and "s2" (note the order) into newly + * -- malloc'ed memory. Then free "s1". + * + * $Id$ + */ + +#include + +char * +add (char *s2, char *s1) +{ + char *cp; + size_t len1 = 0, len2 = 0; + + if (s1) + len1 = strlen (s1); + if (s2) + len2 = strlen (s2); + + + if (!(cp = malloc (len1 + len2 + 1))) + adios (NULL, "unable to allocate string storage"); + + /* Copy s1 and free it */ + if (s1) { + memcpy (cp, s1, len1); + free (s1); + } + + /* Copy s2 */ + if (s2) + memcpy (cp + len1, s2, len2); + + /* Now NULL terminate the string */ + cp[len1 + len2] = '\0'; + + return cp; +} diff --git a/sbr/addrsbr.c b/sbr/addrsbr.c new file mode 100644 index 0000000..0e2f0a4 --- /dev/null +++ b/sbr/addrsbr.c @@ -0,0 +1,496 @@ + +/* + * addrsbr.c -- parse addresses 822-style + * + * $Id$ + */ + +#include +#include +#include + +/* High level parsing of addresses: + + The routines in zotnet/mf/mf.c parse the syntactic representations of + addresses. The routines in sbr/addrsbr.c associate semantics with those + addresses. + + If #ifdef DUMB is in effect, a full 822-style parser is called + for syntax recongition. This breaks each address into its components. + Note however that no semantics are assumed about the parts or their + totality. This means that implicit hostnames aren't made explicit, + and explicit hostnames aren't expanded to their "official" represenations. + + If DUMB is not in effect, then this module does some + high-level thinking about what the addresses are. + + 1. for MMDF systems: + + string%@ -> string + + 2. for non-MMDF systems: + + string@host. -> host!string + + 3. for any system, an address interpreted relative to the local host: + + string@ -> string + + For cases (1) and (3) above, the leftmost host is extracted. If it's not + present, the local host is used. If the tests above fail, the address is + considered to be a real 822-style address. + + If an explicit host is not present, then MH checks for a bang to indicate + an explicit UUCP-style address. If so, this is noted. If not, the host is + defaulted, typically to the local host. The lack of an explict host is + also noted. + + If an explicit 822-style host is present, then MH checks to see if it + can expand this to the official name for the host. If the hostname is + unknown, the address is so typed. + + To summarize, when we're all done, here's what MH knows about the address: + + DUMB - type: local, uucp, or network + host: not locally defaulted, not explicitly expanded + everything else + + other - type: local, uucp, network, unknown + everything else + */ + + +static int ingrp = 0; +static char *pers = NULL; +static char *mbox = NULL; +static char *host = NULL; +static char *route = NULL; +static char *grp = NULL; +static char *note = NULL; +static char err[BUFSIZ]; +static char adr[BUFSIZ]; + +/* + * external prototypes + */ +char *getusername (void); + + +char * +getname (char *addrs) +{ + struct adrx *ap; + + pers = mbox = host = route = grp = note = NULL; + err[0] = '\0'; + + if ((ap = getadrx (addrs ? addrs : "")) == NULL) + return NULL; + + strncpy (adr, ap->text, sizeof(adr)); + pers = ap->pers; + mbox = ap->mbox; + host = ap->host; + route = ap->path; + grp = ap->grp; + ingrp = ap->ingrp; + note = ap->note; + if (ap->err && *ap->err) + strncpy (err, ap->err, sizeof(err)); + + return adr; +} + + +struct mailname * +getm (char *str, char *dfhost, int dftype, int wanthost, char *eresult) +{ + char *pp; + struct mailname *mp; +#ifndef DUMB + char *dp; +#endif /* not DUMB */ + + if (err && err[0]) { + if (eresult) + strcpy (eresult, err); + else + if (wanthost == AD_HOST) + admonish (NULL, "bad address '%s' - %s", str, err); + return NULL; + } + if (pers == NULL + && mbox == NULL && host == NULL && route == NULL + && grp == NULL) { + if (eresult) + strcpy (eresult, "null address"); + else + if (wanthost == AD_HOST) + admonish (NULL, "null address '%s'", str); + return NULL; + } + if (mbox == NULL && grp == NULL) { + if (eresult) + strcpy (eresult, "no mailbox in address"); + else + if (wanthost == AD_HOST) + admonish (NULL, "no mailbox in address '%s'", str); + return NULL; + } + + if (dfhost == NULL) { + dfhost = LocalName (); + dftype = LOCALHOST; + } + + mp = (struct mailname *) calloc ((size_t) 1, sizeof(*mp)); + if (mp == NULL) { + if (eresult) + strcpy (eresult, "insufficient memory to represent address"); + else + if (wanthost == AD_HOST) + adios (NULL, "insufficient memory to represent address"); + return NULL; + } + + mp->m_next = NULL; + mp->m_text = getcpy (str); + if (pers) + mp->m_pers = getcpy (pers); + + if (mbox == NULL) { + mp->m_type = BADHOST; + mp->m_nohost = 1; + mp->m_ingrp = ingrp; + mp->m_gname = getcpy (grp); + if (note) + mp->m_note = getcpy (note); + return mp; + } + + if (host) { + mp->m_mbox = getcpy (mbox); + mp->m_host = getcpy (host); + } + else { + if ((pp = strchr(mbox, '!'))) { + *pp++ = '\0'; + mp->m_mbox = getcpy (pp); + mp->m_host = getcpy (mbox); + mp->m_type = UUCPHOST; + } + else { + mp->m_nohost = 1; + mp->m_mbox = getcpy (mbox); +#ifdef DUMB + if (route == NULL && dftype == LOCALHOST) { + mp->m_host = NULL; + mp->m_type = dftype; + } + else +#endif /* DUMB */ + { + mp->m_host = route ? NULL : getcpy (dfhost); + mp->m_type = route ? NETHOST : dftype; + } + } + goto got_host; + } + + if (wanthost == AD_NHST) + mp->m_type = !strcasecmp (LocalName (), mp->m_host) + ? LOCALHOST : NETHOST; +#ifdef DUMB + else + mp->m_type = strcasecmp (LocalName(), mp->m_host) ? NETHOST : LOCALHOST; +#else /* not DUMB */ + else + if (pp = OfficialName (mp->m_host)) { + got_real_host: ; + free (mp->m_host); + mp->m_host = getcpy (pp); + mp->m_type = strcasecmp (LocalName(), mp->m_host) ? NETHOST : LOCALHOST; + } + else { + if (dp = strchr(mp->m_host, '.')) { + *dp = NULL; + if (pp = OfficialName (mp->m_host)) + goto got_real_host; + *dp = '.'; + } + mp->m_type = BADHOST; + } +#endif /* not DUMB */ + +got_host: ; + if (route) + mp->m_path = getcpy (route); + mp->m_ingrp = ingrp; + if (grp) + mp->m_gname = getcpy (grp); + if (note) + mp->m_note = getcpy (note); + + return mp; +} + + +void +mnfree (struct mailname *mp) +{ + if (!mp) + return; + + if (mp->m_text) + free (mp->m_text); + if (mp->m_pers) + free (mp->m_pers); + if (mp->m_mbox) + free (mp->m_mbox); + if (mp->m_host) + free (mp->m_host); + if (mp->m_path) + free (mp->m_path); + if (mp->m_gname) + free (mp->m_gname); + if (mp->m_note) + free (mp->m_note); + + free ((char *) mp); +} + + +#define empty(s) ((s) ? (s) : "") + +char * +auxformat (struct mailname *mp, int extras) +{ + static char addr[BUFSIZ]; + static char buffer[BUFSIZ]; + +#ifdef DUMB + if (mp->m_nohost) + strncpy (addr, mp->m_mbox ? mp->m_mbox : "", sizeof(addr)); + else +#endif /* DUMB */ + +#ifndef BANG + if (mp->m_type != UUCPHOST) + snprintf (addr, sizeof(addr), mp->m_host ? "%s%s@%s" : "%s%s", + empty(mp->m_path), empty(mp->m_mbox), mp->m_host); + else +#endif /* not BANG */ + snprintf (addr, sizeof(addr), "%s!%s", mp->m_host, mp->m_mbox); + + if (!extras) + return addr; + + if (mp->m_pers || mp->m_path) + if (mp->m_note) + snprintf (buffer, sizeof(buffer), "%s %s <%s>", + legal_person (mp->m_pers ? mp->m_pers : mp->m_mbox), + mp->m_note, addr); + else + snprintf (buffer, sizeof(buffer), "%s <%s>", + legal_person (mp->m_pers ? mp->m_pers : mp->m_mbox), + addr); + else + if (mp->m_note) + snprintf (buffer, sizeof(buffer), "%s %s", addr, mp->m_note); + else + strncpy (buffer, addr, sizeof(buffer)); + + return buffer; +} + + +/* + * address specific "sprintf" + */ + +char * +adrsprintf (char *local, char *domain) +{ + static char addr[BUFSIZ]; + + if (local == NULL) +#ifdef REALLYDUMB + return getusername (); + else +#endif /* REALLYDUMB */ + local = getusername (); + + if (domain == NULL) +#ifdef REALLYDUMB + return local; + else +#endif /* REALLYDUMB */ + domain = LocalName (); + +#ifndef BANG + snprintf (addr, sizeof(addr), "%s@%s", local, domain); +#else /* BANG */ + snprintf (addr, sizeof(addr), "%s!%s", domain, local); +#endif /* BANG */ + + return addr; +} + + +#define W_NIL 0x0000 +#define W_MBEG 0x0001 +#define W_MEND 0x0002 +#define W_MBOX (W_MBEG | W_MEND) +#define W_HBEG 0x0004 +#define W_HEND 0x0008 +#define W_HOST (W_HBEG | W_HEND) +#define WBITS "\020\01MBEG\02MEND\03HBEG\04HEND" + +/* + * Check if this is my address + */ + +int +ismymbox (struct mailname *np) +{ + int oops; + register int len, i; + register char *cp; + register char *pp; + char buffer[BUFSIZ]; + struct mailname *mp; + static char *am = NULL; + static struct mailname mq={NULL}; + + /* + * If this is the first call, initialize + * list of alternate mailboxes. + */ + if (am == NULL) { + mq.m_next = NULL; + mq.m_mbox = getusername (); + if ((am = context_find ("alternate-mailboxes")) == NULL) + am = getusername(); + else { + mp = &mq; + oops = 0; + while ((cp = getname (am))) { + if ((mp->m_next = getm (cp, NULL, 0, AD_NAME, NULL)) == NULL) { + admonish (NULL, "illegal address: %s", cp); + oops++; + } else { + mp = mp->m_next; + mp->m_type = W_NIL; + if (*mp->m_mbox == '*') { + mp->m_type |= W_MBEG; + mp->m_mbox++; + } + if (*(cp = mp->m_mbox + strlen (mp->m_mbox) - 1) == '*') { + mp->m_type |= W_MEND; + *cp = '\0'; + } + if (mp->m_host) { + if (*mp->m_host == '*') { + mp->m_type |= W_HBEG; + mp->m_host++; + } + if (*(cp = mp->m_host + strlen (mp->m_host) - 1) == '*') { + mp->m_type |= W_HEND; + *cp = '\0'; + } + } + if ((cp = getenv ("MHWDEBUG")) && *cp) + fprintf (stderr, "mbox=\"%s\" host=\"%s\" %s\n", + mp->m_mbox, mp->m_host, + snprintb (buffer, sizeof(buffer), (unsigned) mp->m_type, WBITS)); + } + } + if (oops) + advise (NULL, "please fix the %s: entry in your %s file", + "alternate-mailboxes", mh_profile); + } + } + + if (np == NULL) /* XXX */ + return 0; + + switch (np->m_type) { + case NETHOST: + len = strlen (cp = LocalName ()); + if (!uprf (np->m_host, cp) || np->m_host[len] != '.') + break; + goto local_test; + + case UUCPHOST: + if (strcasecmp (np->m_host, SystemName())) + break; /* fall */ + case LOCALHOST: +local_test: ; + if (!strcasecmp (np->m_mbox, mq.m_mbox)) + return 1; + break; + + default: + break; + } + + /* + * Now scan through list of alternate + * mailboxes, and check for a match. + */ + for (mp = &mq; mp->m_next;) { + mp = mp->m_next; + if (!np->m_mbox) + continue; + if ((len = strlen (cp = np->m_mbox)) + < (i = strlen (pp = mp->m_mbox))) + continue; + switch (mp->m_type & W_MBOX) { + case W_NIL: + if (strcasecmp (cp, pp)) + continue; + break; + case W_MBEG: + if (strcasecmp (cp + len - i, pp)) + continue; + break; + case W_MEND: + if (!uprf (cp, pp)) + continue; + break; + case W_MBEG | W_MEND: + if (stringdex (pp, cp) < 0) + continue; + break; + } + + if (mp->m_nohost) + return 1; + if (np->m_host == NULL) + continue; + if ((len = strlen (cp = np->m_host)) + < (i = strlen (pp = mp->m_host))) + continue; + switch (mp->m_type & W_HOST) { + case W_NIL: + if (strcasecmp (cp, pp)) + continue; + break; + case W_HBEG: + if (strcasecmp (cp + len - i, pp)) + continue; + break; + case W_HEND: + if (!uprf (cp, pp)) + continue; + break; + case W_HBEG | W_HEND: + if (stringdex (pp, cp) < 0) + continue; + break; + } + return 1; + } + + return 0; +} diff --git a/sbr/ambigsw.c b/sbr/ambigsw.c new file mode 100644 index 0000000..48b7328 --- /dev/null +++ b/sbr/ambigsw.c @@ -0,0 +1,16 @@ + +/* + * ambigsw.c -- report an ambiguous switch + * + * $Id$ + */ + +#include + + +void +ambigsw (char *arg, struct swit *swp) +{ + advise (NULL, "-%s ambiguous. It matches", arg); + print_sw (arg, swp, "-"); +} diff --git a/sbr/atooi.c b/sbr/atooi.c new file mode 100644 index 0000000..ab58cb4 --- /dev/null +++ b/sbr/atooi.c @@ -0,0 +1,24 @@ + +/* + * atooi.c -- octal version of atoi() + * + * $Id$ + */ + +#include + + +int +atooi(char *cp) +{ + register int i, base; + + i = 0; + base = 8; + while (*cp >= '0' && *cp <= '7') { + i *= base; + i += *cp++ - '0'; + } + + return i; +} diff --git a/sbr/brkstring.c b/sbr/brkstring.c new file mode 100644 index 0000000..715aaf5 --- /dev/null +++ b/sbr/brkstring.c @@ -0,0 +1,91 @@ + +/* + * brkstring.c -- (destructively) split a string into + * -- an array of substrings + * + * $Id$ + */ + +#include + +/* allocate this number of pointers at a time */ +#define NUMBROKEN 256 + +static char **broken = NULL; /* array of substring start addresses */ +static int len = 0; /* current size of "broken" */ + +/* + * static prototypes + */ +static int brkany (char, char *); + + +char ** +brkstring (char *str, char *brksep, char *brkterm) +{ + int i; + char c, *s; + + /* allocate initial space for pointers on first call */ + if (!broken) { + len = NUMBROKEN; + if (!(broken = (char **) malloc ((size_t) (len * sizeof(*broken))))) + adios (NULL, "unable to malloc array in brkstring"); + } + + /* + * scan string, replacing separators with zeroes + * and enter start addresses in "broken". + */ + s = str; + + for (i = 0;; i++) { + + /* enlarge pointer array, if necessary */ + if (i >= len) { + len += NUMBROKEN; + if (!(broken = realloc (broken, (size_t) (len * sizeof(*broken))))) + adios (NULL, "unable to realloc array in brkstring"); + } + + while (brkany (c = *s, brksep)) + *s++ = '\0'; + + /* + * we are either at the end of the string, or the + * terminator found has been found, so finish up. + */ + if (!c || brkany (c, brkterm)) { + *s = '\0'; + broken[i] = NULL; + return broken; + } + + /* set next start addr */ + broken[i] = s; + + while ((c = *++s) && !brkany (c, brksep) && !brkany (c, brkterm)) + ; /* empty body */ + } + + return broken; /* NOT REACHED */ +} + + +/* + * If the character is in the string, + * return 1, else return 0. + */ + +static int +brkany (char c, char *str) +{ + char *s; + + if (str) { + for (s = str; *s; s++) + if (c == *s) + return 1; + } + return 0; +} diff --git a/sbr/check_charset.c b/sbr/check_charset.c new file mode 100644 index 0000000..e16f360 --- /dev/null +++ b/sbr/check_charset.c @@ -0,0 +1,65 @@ + +/* + * check_charset.c -- routines for character sets + * + * $Id$ + */ + +#include + +/* + * Check if we can display a given character set natively. + * We are passed the length of the initial part of the + * string to check, since we want to allow the name of the + * character set to be a substring of a larger string. + */ + +int +check_charset (char *str, int len) +{ + static char *mm_charset = NULL; + static char *alt_charset = NULL; + static int mm_len; + static int alt_len; + + /* Cache the name of our default character set */ + if (!mm_charset) { + if (!(mm_charset = getenv ("MM_CHARSET"))) + mm_charset = "US-ASCII"; + mm_len = strlen (mm_charset); + + /* US-ASCII is a subset of the ISO-8859-X character sets */ + if (!strncasecmp("ISO-8859-", mm_charset, 9)) { + alt_charset = "US-ASCII"; + alt_len = strlen (alt_charset); + } + } + + /* Check if character set is OK */ + if ((len == mm_len) && !strncasecmp(str, mm_charset, mm_len)) + return 1; + if (alt_charset && (len == alt_len) && !strncasecmp(str, alt_charset, alt_len)) + return 1; + + return 0; +} + + +/* + * Return the name of the character set we are + * using for 8bit text. + */ +char * +write_charset_8bit (void) +{ + static char *mm_charset = NULL; + + /* + * Cache the name of the character set to + * use for 8bit text. + */ + if (!mm_charset && !(mm_charset = getenv ("MM_CHARSET"))) + mm_charset = "x-unknown"; + + return mm_charset; +} diff --git a/sbr/closefds.c b/sbr/closefds.c new file mode 100644 index 0000000..86f91a7 --- /dev/null +++ b/sbr/closefds.c @@ -0,0 +1,18 @@ + +/* + * closefds.c -- close-up fd's + * + * $Id$ + */ + +#include + + +void +closefds(int i) +{ + int nbits = OPEN_MAX; + + for (; i < nbits; i++) + close (i); +} diff --git a/sbr/concat.c b/sbr/concat.c new file mode 100644 index 0000000..939a484 --- /dev/null +++ b/sbr/concat.c @@ -0,0 +1,36 @@ + +/* + * concat.c -- concatenate a variable number (minimum of 1) + * of strings in managed memory + * + * $Id$ + */ + +#include + + +char * +concat (char *s1, ...) +{ + char *cp, *dp, *sp; + size_t len; + va_list list; + + len = strlen (s1) + 1; + va_start(list, s1); + while ((cp = va_arg(list, char *))) + len += strlen (cp); + va_end(list); + + if (!(dp = sp = malloc(len))) + adios (NULL, "unable to allocate string storage"); + + sp = copy(s1, sp); + + va_start(list, s1); + while ((cp = va_arg (list, char *))) + sp = copy(cp, sp); + va_end(list); + + return dp; +} diff --git a/sbr/context_del.c b/sbr/context_del.c new file mode 100644 index 0000000..702c15d --- /dev/null +++ b/sbr/context_del.c @@ -0,0 +1,42 @@ + +/* + * context_del.c -- delete an entry from the context/profile list + * + * $Id$ + */ + +#include + +/* + * Delete a key/value pair from the context/profile list. + * Return 0 if key is found, else return 1. + */ + +int +context_del (char *key) +{ + register struct node *np, *pp; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + for (np = m_defs, pp = NULL; np; pp = np, np = np->n_next) { + if (!strcasecmp (np->n_name, key)) { + if (!np->n_context) + admonish (NULL, "bug: context_del(key=\"%s\")", np->n_name); + if (pp) + pp->n_next = np->n_next; + else + m_defs = np->n_next; + free (np->n_name); + if (np->n_field) + free (np->n_field); + free ((char *) np); + ctxflags |= CTXMOD; + return 0; + } + } + + return 1; +} diff --git a/sbr/context_find.c b/sbr/context_find.c new file mode 100644 index 0000000..1e32401 --- /dev/null +++ b/sbr/context_find.c @@ -0,0 +1,25 @@ + +/* + * context_find.c -- find an entry in the context/profile list + * + * $Id$ + */ + +#include + + +char * +context_find (char *str) +{ + struct node *np; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + for (np = m_defs; np; np = np->n_next) + if (!strcasecmp (np->n_name, str)) + return (np->n_field); + + return NULL; +} diff --git a/sbr/context_foil.c b/sbr/context_foil.c new file mode 100644 index 0000000..208c45e --- /dev/null +++ b/sbr/context_foil.c @@ -0,0 +1,52 @@ + +/* + * context_foil.c -- foil search of profile and context + * + * $Id$ + */ + +#include + +/* + * Foil search of users .mh_profile + * If error, return -1, else return 0 + */ + +int +context_foil (char *path) +{ + register struct node *np; + + defpath = context = "/dev/null"; + + /* + * If path is given, create a minimal profile/context list + */ + if (path) { + if (!(m_defs = (struct node *) malloc (sizeof(*np)))) { + advise (NULL, "unable to allocate profile storage"); + return -1; + } + + np = m_defs; + if (!(np->n_name = strdup ("Path"))) { + advise (NULL, "strdup failed"); + return -1; + } + if (!(np->n_field = strdup (path))) { + advise (NULL, "strdup failed"); + return -1; + } + np->n_context = 0; + np->n_next = NULL; + + if (mypath == NULL && (mypath = getenv ("HOME")) != NULL) + if (!(mypath = strdup (mypath))) { + advise (NULL, "strdup failed"); + return -1; + } + } + + return 0; +} + diff --git a/sbr/context_read.c b/sbr/context_read.c new file mode 100644 index 0000000..d2836ba --- /dev/null +++ b/sbr/context_read.c @@ -0,0 +1,111 @@ + +/* + * context_read.c -- find and read profile and context files + * + * $Id$ + */ + +#include +#include +#include + +extern int errno; + +void +context_read (void) +{ + pid_t pid; + register char *cp, *pp; + char buf[BUFSIZ]; + struct stat st; + register struct passwd *pw; + register FILE *ib; + + if (defpath) + return; + + /* + * Find user's home directory + */ + if (!mypath) { + if ((mypath = getenv ("HOME"))) + mypath = getcpy (mypath); + else + if ((pw = getpwuid (getuid ())) == NULL + || pw->pw_dir == NULL + || *pw->pw_dir == 0) + adios (NULL, "no HOME envariable"); + else + mypath = getcpy (pw->pw_dir); + if ((cp = mypath + strlen (mypath) - 1) > mypath && *cp == '/') + *cp = 0; + } + + /* + * open and read user's profile + */ + if ((cp = getenv ("MH")) && *cp != '\0') { + defpath = path (cp, TFILE); + if ((ib = fopen (defpath, "r")) == NULL) + adios (defpath, "unable to read"); + if (*cp != '/') + m_putenv ("MH", defpath); + } else { + defpath = concat (mypath, "/", mh_profile, NULL); + + if ((ib = fopen (defpath, "r")) == NULL) { + switch (pid = vfork ()) { + case -1: + adios ("fork", "unable to"); + + case 0: + setgid (getgid ()); + setuid (getuid ()); + + execlp (installproc, "install-mh", "-auto", NULL); + fprintf (stderr, "unable to exec "); + perror (installproc); + _exit (-1); + + default: + if (pidwait (pid, 0) + || (ib = fopen (defpath, "r")) == NULL) + adios (NULL, "[install-mh aborted]"); + } + } + } + readconfig (&m_defs, ib, mh_profile, 0); + fclose (ib); + + /* + * Find user's nmh directory + */ + if ((pp = context_find ("path")) && *pp != '\0') { + if (*pp != '/') + snprintf (buf, sizeof(buf), "%s/%s", mypath, pp); + else + strncpy (buf, pp, sizeof(buf)); + if (stat(buf, &st) == -1) { + if (errno != ENOENT) + adios (buf, "error opening"); + cp = concat ("Your MH-directory \"", buf, + "\" doesn't exist; Create it? ", NULL); + if (!getanswer(cp)) + adios (NULL, "unable to access MH-directory \"%s\"", buf); + free (cp); + if (!makedir (buf)) + adios (NULL, "unable to create", buf); + } + } + + /* + * open and read user's context file + */ + if (!(cp = getenv ("MHCONTEXT")) || *cp == '\0') + cp = context; + ctxpath = getcpy (m_maildir (cp)); + if ((ib = fopen (ctxpath, "r"))) { + readconfig ((struct node **) 0, ib, cp, 1); + fclose (ib); + } +} diff --git a/sbr/context_replace.c b/sbr/context_replace.c new file mode 100644 index 0000000..435127b --- /dev/null +++ b/sbr/context_replace.c @@ -0,0 +1,69 @@ + +/* + * context_replace.c -- add/replace an entry in the context/profile list + * + * $Id$ + */ + +#include + + +void +context_replace (char *key, char *value) +{ + register struct node *np; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + /* + * If list is emtpy, allocate head of profile/context list. + */ + if (!m_defs) { + if (!(m_defs = (struct node *) malloc (sizeof(*np)))) + adios (NULL, "unable to allocate profile storage"); + + np = m_defs; + np->n_name = getcpy (key); + np->n_field = getcpy (value); + np->n_context = 1; + np->n_next = NULL; + ctxflags |= CTXMOD; + return; + } + + /* + * Search list of context/profile entries for + * this key, and replace its value if found. + */ + for (np = m_defs;; np = np->n_next) { + if (!strcasecmp (np->n_name, key)) { + if (strcmp (value, np->n_field)) { + if (!np->n_context) + admonish (NULL, "bug: context_replace(key=\"%s\",value=\"%s\")", key, value); + if (np->n_field) + free (np->n_field); + np->n_field = getcpy (value); + ctxflags |= CTXMOD; + } + return; + } + if (!np->n_next) + break; + } + + /* + * Else add this new entry at the end + */ + np->n_next = (struct node *) malloc (sizeof(*np)); + if (!np->n_next) + adios (NULL, "unable to allocate profile storage"); + + np = np->n_next; + np->n_name = getcpy (key); + np->n_field = getcpy (value); + np->n_context = 1; + np->n_next = NULL; + ctxflags |= CTXMOD; +} diff --git a/sbr/context_save.c b/sbr/context_save.c new file mode 100644 index 0000000..b3f8168 --- /dev/null +++ b/sbr/context_save.c @@ -0,0 +1,90 @@ + +/* + * context_save.c -- write out the updated context file + * + * $Id$ + */ + +#include +#include + +/* + * static prototypes + */ +static int m_chkids(void); + + +void +context_save (void) +{ + int action; + register struct node *np; + FILE *out; + sigset_t set, oset; + + if (!(ctxflags & CTXMOD)) + return; + ctxflags &= ~CTXMOD; + + if ((action = m_chkids ()) > 0) + return; /* child did it for us */ + + /* block a few signals */ + sigemptyset (&set); + sigaddset (&set, SIGHUP); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGQUIT); + sigaddset (&set, SIGTERM); + SIGPROCMASK (SIG_BLOCK, &set, &oset); + + if (!(out = fopen (ctxpath, "w"))) + adios (ctxpath, "unable to write"); + for (np = m_defs; np; np = np->n_next) + if (np->n_context) + fprintf (out, "%s: %s\n", np->n_name, np->n_field); + fclose (out); + + SIGPROCMASK (SIG_SETMASK, &oset, &set); /* reset the signal mask */ + + if (action == 0) + _exit (0); /* we are child, time to die */ +} + +/* + * This hack brought to you so we can handle set[ug]id MH programs. + * If we return -1, then no fork is made, we update .mh_profile + * normally, and return to the caller normally. If we return 0, + * then the child is executing, .mh_profile is modified after + * we set our [ug]ids to the norm. If we return > 0, then the + * parent is executed and .mh_profile has already be modified. + * We can just return to the caller immediately. + */ + +static int +m_chkids (void) +{ + int i; + pid_t pid; + + if (getuid () == geteuid ()) + return (-1); + + for (i = 0; (pid = fork ()) == -1 && i < 5; i++) + sleep (5); + + switch (pid) { + case -1: + break; + + case 0: + setgid (getgid ()); + setuid (getuid ()); + break; + + default: + pidwait (pid, -1); + break; + } + + return pid; +} diff --git a/sbr/copy.c b/sbr/copy.c new file mode 100644 index 0000000..577e811 --- /dev/null +++ b/sbr/copy.c @@ -0,0 +1,18 @@ + +/* + * copy.c -- copy a string and return pointer to NULL terminator + * + * $Id$ + */ + +#include + +char * +copy(char *from, char *to) +{ + while ((*to = *from)) { + to++; + from++; + } + return (to); +} diff --git a/sbr/copyip.c b/sbr/copyip.c new file mode 100644 index 0000000..8d7b6f5 --- /dev/null +++ b/sbr/copyip.c @@ -0,0 +1,20 @@ + +/* + * copyip.c -- copy a string array and return pointer to end + * + * $Id$ + */ + +#include + + +char ** +copyip (char **p, char **q, int len_q) +{ + while (*p && --len_q > 0) + *q++ = *p++; + + *q = NULL; + + return q; +} diff --git a/sbr/cpydata.c b/sbr/cpydata.c new file mode 100644 index 0000000..a8c3375 --- /dev/null +++ b/sbr/cpydata.c @@ -0,0 +1,23 @@ + +/* + * cpydata.c -- copy all data from one fd to another + * + * $Id$ + */ + +#include + +void +cpydata (int in, int out, char *ifile, char *ofile) +{ + int i; + char buffer[BUFSIZ]; + + while ((i = read(in, buffer, sizeof(buffer))) > 0) { + if (write(out, buffer, i) != i) + adios(ofile, "error writing"); + } + + if (i == -1) + adios(ifile, "error reading"); +} diff --git a/sbr/cpydgst.c b/sbr/cpydgst.c new file mode 100644 index 0000000..068fb6e --- /dev/null +++ b/sbr/cpydgst.c @@ -0,0 +1,65 @@ + +/* + * cpydgst.c -- copy from one fd to another in encapsulating mode + * -- (do dashstuffing of input data). + * + * $Id$ + */ + +#include + +/* + * We want to perform the substitution + * + * \n(-.*)\n --> \n- \1\n + * + * This is equivalent to the sed substitution + * + * sed -e 's%^-%- -%' < ifile > ofile + * + * but the routine below is faster than the pipe, fork, and exec. + */ + +#define S1 0 +#define S2 1 + +#define output(c) if (bp >= dp) {flush(); *bp++ = c;} else *bp++ = c +#define flush() if ((j = bp - outbuf) && write (out, outbuf, j) != j) \ + adios (ofile, "error writing"); \ + else \ + bp = outbuf + + +void +cpydgst (int in, int out, char *ifile, char *ofile) +{ + register int i, j, state; + register char *cp, *ep; + register char *bp, *dp; + char buffer[BUFSIZ], outbuf[BUFSIZ]; + + dp = (bp = outbuf) + sizeof outbuf; + for (state = S1; (i = read (in, buffer, sizeof buffer)) > 0;) + for (ep = (cp = buffer) + i; cp < ep; cp++) { + if (*cp == '\0') + continue; + switch (state) { + case S1: + if (*cp == '-') { + output ('-'); + output (' '); + } + state = S2; /* fall */ + + case S2: + output (*cp); + if (*cp == '\n') + state = S1; + break; + } + } + + if (i == -1) + adios (ifile, "error reading"); + flush(); +} diff --git a/sbr/discard.c b/sbr/discard.c new file mode 100644 index 0000000..fffc0fa --- /dev/null +++ b/sbr/discard.c @@ -0,0 +1,65 @@ + +/* + * discard.c -- discard output on a file pointer + * + * $Id$ + */ + +#include + +#ifdef HAVE_TERMIOS_H +# include +#else +# ifdef HAVE_TERMIO_H +# include +# else +# include +# endif +#endif + +#ifdef SCO_5_STDIO +# define _ptr __ptr +# define _cnt __cnt +# define _base __base +# define _filbuf(fp) ((fp)->__cnt = 0, __filbuf(fp)) +#endif + + +void +discard (FILE *io) +{ +#ifndef HAVE_TERMIOS_H +# ifdef HAVE_TERMIO_H + struct termio tio; +# else + struct sgttyb tio; +# endif +#endif + + if (io == NULL) + return; + +#ifdef HAVE_TERMIOS_H + tcflush (fileno(io), TCOFLUSH); +#else +# ifdef HAVE_TERMIO_H + if (ioctl (fileno(io), TCGETA, &tio) != -1) + ioctl (fileno(io), TCSETA, &tio); +# else + if (ioctl (fileno(io), TIOCGETP, (char *) &tio) != -1) + ioctl (fileno(io), TIOCSETP, (char *) &tio); +# endif +#endif + +#ifdef _FSTDIO + fpurge (io); +#else +# ifdef LINUX_STDIO + io->_IO_write_ptr = io->_IO_write_base; +# else + if ((io->_ptr = io->_base)) + io->_cnt = 0; +# endif +#endif +} + diff --git a/sbr/done.c b/sbr/done.c new file mode 100644 index 0000000..02465ea --- /dev/null +++ b/sbr/done.c @@ -0,0 +1,14 @@ + +/* + * done.c -- terminate the program + * + * $Id$ + */ + +#include + +void +done (int status) +{ + exit (status); +} diff --git a/sbr/error.c b/sbr/error.c new file mode 100644 index 0000000..0e02f2a --- /dev/null +++ b/sbr/error.c @@ -0,0 +1,140 @@ + +/* + * error.c -- main error handling routines + * + * $Id$ + */ + +#include + +#ifdef HAVE_WRITEV +# include +# include +#endif + +extern int errno; + + +/* + * print out error message + */ +void +advise (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); +} + + +/* + * print out error message and exit + */ +void +adios (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); + done (1); +} + + +/* + * admonish the user + */ +void +admonish (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + advertise (what, "continuing...", fmt, ap); + va_end(ap); +} + + +/* + * main routine for printing error messages. + * + * Use writev() if available, for slightly better performance. + * Why? Well, there are a couple of reasons. Primarily, it + * gives a smoother output... More importantly though, it's a + * sexy syscall()... + */ +void +advertise (char *what, char *tail, char *fmt, va_list ap) +{ + int eindex = errno; + +#ifdef HAVE_WRITEV + char buffer[BUFSIZ], err[BUFSIZ]; + struct iovec iob[20], *iov; +#endif + + fflush (stdout); + +#ifdef HAVE_WRITEV + fflush (stderr); + iov = iob; + + if (invo_name && *invo_name) { + iov->iov_len = strlen (iov->iov_base = invo_name); + iov++; + iov->iov_len = strlen (iov->iov_base = ": "); + iov++; + } + + vsnprintf (buffer, sizeof(buffer), fmt, ap); + iov->iov_len = strlen (iov->iov_base = buffer); + iov++; + if (what) { + if (*what) { + iov->iov_len = strlen (iov->iov_base = " "); + iov++; + iov->iov_len = strlen (iov->iov_base = what); + iov++; + iov->iov_len = strlen (iov->iov_base = ": "); + iov++; + } + if (!(iov->iov_base = strerror (eindex))) { + /* this shouldn't happen, but we'll test for it just in case */ + snprintf (err, sizeof(err), "Error %d", eindex); + iov->iov_base = err; + } + iov->iov_len = strlen (iov->iov_base); + iov++; + } + if (tail && *tail) { + iov->iov_len = strlen (iov->iov_base = ", "); + iov++; + iov->iov_len = strlen (iov->iov_base = tail); + iov++; + } + iov->iov_len = strlen (iov->iov_base = "\n"); + iov++; + writev (fileno (stderr), iob, iov - iob); +#else + if (invo_name && *invo_name) + fprintf (stderr, "%s: ", invo_name); + vfprintf (stderr, fmt, ap); + + if (what) { + char *s; + + if (*what) + fprintf (stderr, " %s: ", what); + if ((s = strerror(eindex))) + fprintf (stderr, "%s", s); + else + fprintf (stderr, "Error %d", eindex); + } + if (tail) + fprintf (stderr, ", %s", tail); + fputc ('\n', stderr); +#endif +} diff --git a/sbr/fdcompare.c b/sbr/fdcompare.c new file mode 100644 index 0000000..3956a7b --- /dev/null +++ b/sbr/fdcompare.c @@ -0,0 +1,38 @@ + +/* + * fdcompare.c -- are two files identical? + * + * $Id$ + */ + +#include + + +int +fdcompare (int fd1, int fd2) +{ + register int i, n1, n2, resp; + register char *c1, *c2; + char b1[BUFSIZ], b2[BUFSIZ]; + + resp = 1; + while ((n1 = read (fd1, b1, sizeof(b1))) >= 0 + && (n2 = read (fd2, b2, sizeof(b2))) >= 0 + && n1 == n2) { + c1 = b1; + c2 = b2; + for (i = n1 < sizeof(b1) ? n1 : sizeof(b1); i--;) + if (*c1++ != *c2++) { + resp = 0; + goto leave; + } + if (n1 < sizeof(b1)) + goto leave; + } + resp = 0; + +leave: ; + lseek (fd1, (off_t) 0, SEEK_SET); + lseek (fd2, (off_t) 0, SEEK_SET); + return resp; +} diff --git a/sbr/fmt_addr.c b/sbr/fmt_addr.c new file mode 100644 index 0000000..782eb0a --- /dev/null +++ b/sbr/fmt_addr.c @@ -0,0 +1,115 @@ + +/* + * fmt_addr.c -- format an address field (from fmt_scan) + * + * $Id$ + */ + +#include +#include +#include + +static char *buf; /* our current working buffer */ +static char *bufend; /* end of working buffer */ +static char *last_dst; /* buf ptr at end of last call */ +static unsigned int bufsiz; /* current size of buf */ + +#define BUFINCR 512 /* how much to expand buf when if fills */ + +#define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; } + +/* check if there's enough room in buf for str. add more mem if needed */ +#define CHECKMEM(str) \ + if ((len = strlen (str)) >= bufend - dst) {\ + int i = dst - buf;\ + int n = last_dst - buf;\ + bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\ + buf = realloc (buf, bufsiz);\ + dst = buf + i;\ + last_dst = buf + n;\ + if (! buf)\ + adios (NULL, "formataddr: couldn't get buffer space");\ + bufend = buf + bufsiz;\ + } + + +/* fmt_scan will call this routine if the user includes the function + * "(formataddr {component})" in a format string. "orig" is the + * original contents of the string register. "str" is the address + * string to be formatted and concatenated onto orig. This routine + * returns a pointer to the concatenated address string. + * + * We try to not do a lot of malloc/copy/free's (which is why we + * don't call "getcpy") but still place no upper limit on the + * length of the result string. + * + * This routine is placed in a separate library so it can be + * overridden by particular programs (e.g., "replsbr"). + */ + +char * +formataddr (char *orig, char *str) +{ + register int len; + register int isgroup; + register char *dst; + register char *cp; + register char *sp; + register struct mailname *mp = NULL; + + /* if we don't have a buffer yet, get one */ + if (bufsiz == 0) { + buf = malloc (BUFINCR); + if (! buf) + adios (NULL, "formataddr: couldn't allocate buffer space"); + last_dst = buf; /* XXX */ + bufsiz = BUFINCR - 6; /* leave some slop */ + bufend = buf + bufsiz; + } + /* + * If "orig" points to our buffer we can just pick up where we + * left off. Otherwise we have to copy orig into our buffer. + */ + if (orig == buf) + dst = last_dst; + else if (!orig || !*orig) { + dst = buf; + *dst = '\0'; + } else { + dst = last_dst; /* XXX */ + CHECKMEM (orig); + CPY (orig); + } + + /* concatenate all the new addresses onto 'buf' */ + for (isgroup = 0; cp = getname (str); ) { + if ((mp = getm (cp, NULL, 0, fmt_norm, NULL)) == NULL) + continue; + + if (isgroup && (mp->m_gname || !mp->m_ingrp)) { + *dst++ = ';'; + isgroup = 0; + } + /* if we get here we're going to add an address */ + if (dst != buf) { + *dst++ = ','; + *dst++ = ' '; + } + if (mp->m_gname) { + CHECKMEM (mp->m_gname); + CPY (mp->m_gname); + isgroup++; + } + sp = adrformat (mp); + CHECKMEM (sp); + CPY (sp); + mnfree (mp); + } + + if (isgroup) + *dst++ = ';'; + + *dst = '\0'; + last_dst = dst; + return (buf); +} diff --git a/sbr/fmt_compile.c b/sbr/fmt_compile.c new file mode 100644 index 0000000..339ac0a --- /dev/null +++ b/sbr/fmt_compile.c @@ -0,0 +1,643 @@ + +/* + * fmt_compile.c -- "compile" format strings for fmt_scan + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +/* + * hash table for deciding if a component is "interesting" + */ +struct comp *wantcomp[128]; + +static struct format *formatvec; /* array to hold formats */ +static struct format *next_fp; /* next free format slot */ +static struct format *fp; /* current format slot */ +static struct comp *cm; /* most recent comp ref */ +static struct ftable *ftbl; /* most recent func ref */ +static int ncomp; +static int infunction; /* function nesting cnt */ + +extern struct mailname fmt_mnull; + +/* ftable->type (argument type) */ +#define TF_COMP 0 /* component expected */ +#define TF_NUM 1 /* number expected */ +#define TF_STR 2 /* string expected */ +#define TF_EXPR 3 /* component or func. expected */ +#define TF_NONE 4 /* no argument */ +#define TF_MYBOX 5 /* special - get current user's mbox */ +#define TF_NOW 6 /* special - get current unix time */ +#define TF_EXPR_SV 7 /* like expr but save current str reg */ +#define TF_NOP 8 /* like expr but no result */ + +/* ftable->flags */ +#define TFL_PUTS 1 /* implicit putstr if top level */ +#define TFL_PUTN 2 /* implicit putnum if top level */ + +struct ftable { + char *name; /* function name */ + char type; /* argument type */ + char f_type; /* fmt type */ + char extra; /* arg. type dependent extra info */ + char flags; +}; + +static struct ftable functable[] = { + { "nonzero", TF_EXPR, FT_V_NE, FT_IF_V_NE, 0 }, + { "zero", TF_EXPR, FT_V_EQ, FT_IF_V_EQ, 0 }, + { "eq", TF_NUM, FT_V_EQ, FT_IF_V_EQ, 0 }, + { "ne", TF_NUM, FT_V_NE, FT_IF_V_NE, 0 }, + { "gt", TF_NUM, FT_V_GT, FT_IF_V_GT, 0 }, + { "null", TF_EXPR, FT_S_NULL, FT_IF_S_NULL, 0 }, + { "nonnull", TF_EXPR, FT_S_NONNULL, FT_IF_S, 0 }, + { "match", TF_STR, FT_V_MATCH, FT_IF_MATCH, 0 }, + { "amatch", TF_STR, FT_V_AMATCH, FT_IF_AMATCH, 0 }, + + { "putstr", TF_EXPR, FT_STR, 0, 0 }, + { "putstrf", TF_EXPR, FT_STRF, 0, 0 }, + { "putnum", TF_EXPR, FT_NUM, 0, 0 }, + { "putnumf", TF_EXPR, FT_NUMF, 0, 0 }, + { "putaddr", TF_STR, FT_PUTADDR, 0, 0 }, + { "void", TF_NOP, 0, 0, 0 }, + + { "comp", TF_COMP, FT_LS_COMP, 0, TFL_PUTS }, + { "lit", TF_STR, FT_LS_LIT, 0, TFL_PUTS }, + { "getenv", TF_STR, FT_LS_GETENV, 0, TFL_PUTS }, + { "profile", TF_STR, FT_LS_CFIND, 0, TFL_PUTS }, + { "decodecomp", TF_COMP, FT_LS_DECODECOMP, 0, TFL_PUTS }, + { "decode", TF_EXPR, FT_LS_DECODE, 0, TFL_PUTS }, + { "trim", TF_EXPR, FT_LS_TRIM, 0, 0 }, + { "compval", TF_COMP, FT_LV_COMP, 0, TFL_PUTN }, + { "compflag", TF_COMP, FT_LV_COMPFLAG, 0, TFL_PUTN }, + { "num", TF_NUM, FT_LV_LIT, 0, TFL_PUTN }, + { "msg", TF_NONE, FT_LV_DAT, 0, TFL_PUTN }, + { "cur", TF_NONE, FT_LV_DAT, 1, TFL_PUTN }, + { "size", TF_NONE, FT_LV_DAT, 2, TFL_PUTN }, + { "width", TF_NONE, FT_LV_DAT, 3, TFL_PUTN }, + { "unseen", TF_NONE, FT_LV_DAT, 4, TFL_PUTN }, + { "dat", TF_NUM, FT_LV_DAT, 0, TFL_PUTN }, + { "strlen", TF_NONE, FT_LV_STRLEN, 0, TFL_PUTN }, + { "me", TF_MYBOX, FT_LS_LIT, 0, TFL_PUTS }, + { "plus", TF_NUM, FT_LV_PLUS_L, 0, TFL_PUTN }, + { "minus", TF_NUM, FT_LV_MINUS_L, 0, TFL_PUTN }, + { "divide", TF_NUM, FT_LV_DIVIDE_L, 0, TFL_PUTN }, + { "modulo", TF_NUM, FT_LV_MODULO_L, 0, TFL_PUTN }, + { "charleft", TF_NONE, FT_LV_CHAR_LEFT, 0, TFL_PUTN }, + { "timenow", TF_NOW, FT_LV_LIT, 0, TFL_PUTN }, + + { "month", TF_COMP, FT_LS_MONTH, FT_PARSEDATE, TFL_PUTS }, + { "lmonth", TF_COMP, FT_LS_LMONTH, FT_PARSEDATE, TFL_PUTS }, + { "tzone", TF_COMP, FT_LS_ZONE, FT_PARSEDATE, TFL_PUTS }, + { "day", TF_COMP, FT_LS_DAY, FT_PARSEDATE, TFL_PUTS }, + { "weekday", TF_COMP, FT_LS_WEEKDAY, FT_PARSEDATE, TFL_PUTS }, + { "tws", TF_COMP, FT_LS_822DATE, FT_PARSEDATE, TFL_PUTS }, + { "sec", TF_COMP, FT_LV_SEC, FT_PARSEDATE, TFL_PUTN }, + { "min", TF_COMP, FT_LV_MIN, FT_PARSEDATE, TFL_PUTN }, + { "hour", TF_COMP, FT_LV_HOUR, FT_PARSEDATE, TFL_PUTN }, + { "mday", TF_COMP, FT_LV_MDAY, FT_PARSEDATE, TFL_PUTN }, + { "mon", TF_COMP, FT_LV_MON, FT_PARSEDATE, TFL_PUTN }, + { "year", TF_COMP, FT_LV_YEAR, FT_PARSEDATE, TFL_PUTN }, + { "yday", TF_COMP, FT_LV_YDAY, FT_PARSEDATE, TFL_PUTN }, + { "wday", TF_COMP, FT_LV_WDAY, FT_PARSEDATE, TFL_PUTN }, + { "zone", TF_COMP, FT_LV_ZONE, FT_PARSEDATE, TFL_PUTN }, + { "clock", TF_COMP, FT_LV_CLOCK, FT_PARSEDATE, TFL_PUTN }, + { "rclock", TF_COMP, FT_LV_RCLOCK, FT_PARSEDATE, TFL_PUTN }, + { "sday", TF_COMP, FT_LV_DAYF, FT_PARSEDATE, TFL_PUTN }, + { "szone", TF_COMP, FT_LV_ZONEF, FT_PARSEDATE, TFL_PUTN }, + { "dst", TF_COMP, FT_LV_DST, FT_PARSEDATE, TFL_PUTN }, + { "pretty", TF_COMP, FT_LS_PRETTY, FT_PARSEDATE, TFL_PUTS }, + { "nodate", TF_COMP, FT_LV_COMPFLAG, FT_PARSEDATE, TFL_PUTN }, + { "date2local", TF_COMP, FT_LOCALDATE, FT_PARSEDATE, 0 }, + { "date2gmt", TF_COMP, FT_GMTDATE, FT_PARSEDATE, 0 }, + + { "pers", TF_COMP, FT_LS_PERS, FT_PARSEADDR, TFL_PUTS }, + { "mbox", TF_COMP, FT_LS_MBOX, FT_PARSEADDR, TFL_PUTS }, + { "host", TF_COMP, FT_LS_HOST, FT_PARSEADDR, TFL_PUTS }, + { "path", TF_COMP, FT_LS_PATH, FT_PARSEADDR, TFL_PUTS }, + { "gname", TF_COMP, FT_LS_GNAME, FT_PARSEADDR, TFL_PUTS }, + { "note", TF_COMP, FT_LS_NOTE, FT_PARSEADDR, TFL_PUTS }, + { "addr", TF_COMP, FT_LS_ADDR, FT_PARSEADDR, TFL_PUTS }, + { "proper", TF_COMP, FT_LS_822ADDR, FT_PARSEADDR, TFL_PUTS }, + { "type", TF_COMP, FT_LV_HOSTTYPE, FT_PARSEADDR, TFL_PUTN }, + { "ingrp", TF_COMP, FT_LV_INGRPF, FT_PARSEADDR, TFL_PUTN }, + { "nohost", TF_COMP, FT_LV_NOHOSTF, FT_PARSEADDR, TFL_PUTN }, + { "formataddr", TF_EXPR_SV,FT_FORMATADDR, FT_FORMATADDR, 0 }, + { "friendly", TF_COMP, FT_LS_FRIENDLY, FT_PARSEADDR, TFL_PUTS }, + + { "mymbox", TF_COMP, FT_LV_COMPFLAG, FT_MYMBOX, TFL_PUTN }, + { "addtoseq", TF_STR, FT_ADDTOSEQ, 0, 0 }, + + { NULL, 0, 0, 0, 0 } +}; + +/* Add new component to the hash table */ +#define NEWCOMP(cm,name)\ + cm = ((struct comp *) calloc(1, sizeof (struct comp)));\ + cm->c_name = name;\ + ncomp++;\ + i = CHASH(name);\ + cm->c_next = wantcomp[i];\ + wantcomp[i] = cm; + +#define NEWFMT (next_fp++) +#define NEW(type,fill,wid)\ + fp=NEWFMT; fp->f_type=(type); fp->f_fill=(fill); fp->f_width=(wid); + +/* Add (possibly new) component to the hash table */ +#define ADDC(name)\ + FINDCOMP(cm, name);\ + if (!cm) {\ + NEWCOMP(cm,name);\ + }\ + fp->f_comp = cm; + +#define LV(type, value) NEW(type,0,0); fp->f_value = (value); +#define LS(type, str) NEW(type,0,0); fp->f_text = (str); + +#define PUTCOMP(comp) NEW(FT_COMP,0,0); ADDC(comp); +#define PUTLIT(str) NEW(FT_LIT,0,0); fp->f_text = (str); +#define PUTC(c) NEW(FT_CHAR,0,0); fp->f_char = (c); + +static char *format_string; +static char *usr_fstring; /* for CERROR */ + +#define CERROR(str) compile_error (str, cp) + +/* + * external prototypes + */ +extern char *getusername(void); + +/* + * static prototypes + */ +static struct ftable *lookup(char *); +static void compile_error(char *, char *); +static char *compile (char *); +static char *do_spec(char *); +static char *do_name(char *, int); +static char *do_func(char *); +static char *do_expr (char *, int); +static char *do_loop(char *); +static char *do_if(char *); + + +static struct ftable * +lookup(char *name) +{ + register struct ftable *t = functable; + register char *nm; + register char c = *name; + + while ((nm = t->name)) { + if (*nm == c && strcmp (nm, name) == 0) + return (ftbl = t); + + t++; + } + return (struct ftable *) 0; +} + + +static void +compile_error(char *str, char *cp) +{ + int i, errpos, errctx; + + errpos = cp - format_string; + errctx = errpos > 20 ? 20 : errpos; + usr_fstring[errpos] = '\0'; + + for (i = errpos-errctx; i < errpos; i++) { +#ifdef LOCALE + if (iscntrl(usr_fstring[i])) +#else + if (usr_fstring[i] < 32) +#endif + usr_fstring[i] = '_'; + } + + advise(NULL, "\"%s\": format compile error - %s", + &usr_fstring[errpos-errctx], str); + adios (NULL, "%*s", errctx+1, "^"); +} + +/* + * Compile format string "fstring" into format list "fmt". + * Return the number of header components found in the format + * string. + */ + +int +fmt_compile(char *fstring, struct format **fmt) +{ + register char *cp; + int i; + + if (format_string) + free (format_string); + format_string = getcpy (fstring); + usr_fstring = fstring; + + /* init the component hash table. */ + for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++) + wantcomp[i] = 0; + + memset((char *) &fmt_mnull, 0, sizeof(fmt_mnull)); + + /* it takes at least 4 char to generate one format so we + * allocate a worst-case format array using 1/4 the length + * of the format string. We actually need twice this much + * to handle both pre-processing (e.g., address parsing) and + * normal processing. + */ + i = strlen(fstring)/2 + 1; + next_fp = formatvec = (struct format *)calloc ((size_t) i, + sizeof(struct format)); + if (next_fp == NULL) + adios (NULL, "unable to allocate format storage"); + + ncomp = 0; + infunction = 0; + + cp = compile(format_string); + if (*cp) { + CERROR("extra '%>', '%|' or '%?'"); + } + LV(FT_DONE, 0); /* really done */ + *fmt = formatvec; + + return (ncomp); +} + +static char * +compile (char *sp) +{ + register char *cp = sp; + register int c; + + for (;;) { + sp = cp; + while ((c = *cp) && c != '%') + cp++; + *cp = 0; + switch (cp-sp) { + case 0: + break; + case 1: + PUTC(*sp); + break; + default: + PUTLIT(sp); + break; + } + if (c == 0) + return (cp); + + switch (c = *++cp) { + case '%': + PUTC (*cp); + cp++; + break; + + case '|': + case '>': + case '?': + case ']': + return (cp); + + case '<': + cp = do_if(++cp); + break; + + case '[': /* ] */ + cp = do_loop(++cp); + break; + + case ';': /* comment line */ + cp++; + while ((c = *cp++) && c != '\n') + continue; + break; + + default: + cp = do_spec(cp); + break; + } + } +} + + +static char * +do_spec(char *sp) +{ + register char *cp = sp; + register int c; +#ifndef lint + register int ljust = 0; +#endif /* not lint */ + register int wid = 0; + register char fill = ' '; + + c = *cp++; + if (c == '-') { + ljust++; + c = *cp++; + } + if (c == '0') { + fill = c; + c = *cp++; + } + while (isdigit(c)) { + wid = wid*10 + (c - '0'); + c = *cp++; + } + if (c == '{') { + cp = do_name(cp, 0); + if (! infunction) + fp->f_type = wid? FT_COMPF : FT_COMP; + } + else if (c == '(') { + cp = do_func(cp); + if (! infunction) { + if (ftbl->flags & TFL_PUTS) { + LV( wid? FT_STRF : FT_STR, ftbl->extra); + } + else if (ftbl->flags & TFL_PUTN) { + LV( wid? FT_NUMF : FT_NUM, ftbl->extra); + } + } + } + else { + CERROR("component or function name expected"); + } + if (ljust) + wid = -wid; + fp->f_width = wid; + fp->f_fill = fill; + + return (cp); +} + +static char * +do_name(char *sp, int preprocess) +{ + register char *cp = sp; + register int c; + register int i; + static int primed = 0; + + while (isalnum(c = *cp++) || c == '-' || c == '_') + ; + if (c != '}') { + CERROR("'}' expected"); + } + cp[-1] = '\0'; + PUTCOMP(sp); + switch (preprocess) { + + case FT_PARSEDATE: + if (cm->c_type & CT_ADDR) { + CERROR("component used as both date and address"); + } + if (! (cm->c_type & CT_DATE)) { + cm->c_tws = (struct tws *) + calloc((size_t) 1, sizeof(*cm->c_tws)); + fp->f_type = preprocess; + PUTCOMP(sp); + cm->c_type |= CT_DATE; + } + break; + + case FT_MYMBOX: + if (!primed) { + ismymbox ((struct mailname *) 0); + primed++; + } + cm->c_type |= CT_MYMBOX; + /* fall through */ + case FT_PARSEADDR: + if (cm->c_type & CT_DATE) { + CERROR("component used as both date and address"); + } + if (! (cm->c_type & CT_ADDRPARSE)) { + cm->c_mn = &fmt_mnull; + fp->f_type = preprocess; + PUTCOMP(sp); + cm->c_type |= (CT_ADDR | CT_ADDRPARSE); + } + break; + + case FT_FORMATADDR: + if (cm->c_type & CT_DATE) { + CERROR("component used as both date and address"); + } + cm->c_type |= CT_ADDR; + break; + } + return (cp); +} + +static char * +do_func(char *sp) +{ + register char *cp = sp; + register int c; + register struct ftable *t; + register int n; + int mflag; /* minus sign in NUM */ + + infunction++; + + while (isalnum(c = *cp++)) + ; + if (c != '(' && c != '{' && c != ' ' && c != ')') { + CERROR("'(', '{', ' ' or ')' expected"); + } + cp[-1] = '\0'; + if ((t = lookup (sp)) == 0) { + CERROR("unknown function"); + } + if (isspace(c)) + c = *cp++; + + switch (t->type) { + + case TF_COMP: + if (c != '{') { + CERROR("component name expected"); + } + cp = do_name(cp, t->extra); + fp->f_type = t->f_type; + c = *cp++; + break; + + case TF_NUM: + if ((mflag = (c == '-'))) + c = *cp++; + n = 0; + while (isdigit(c)) { + n = n*10 + (c - '0'); + c = *cp++; + } + if (mflag) + n = (-n); + LV(t->f_type,n); + break; + + case TF_STR: + sp = cp - 1; + while (c && c != ')') + c = *cp++; + cp[-1] = '\0'; + LS(t->f_type,sp); + break; + + case TF_NONE: + LV(t->f_type,t->extra); + break; + + case TF_MYBOX: + LS(t->f_type, getusername()); + break; + + case TF_NOW: + LV(t->f_type, time((time_t *) 0)); + break; + + case TF_EXPR_SV: + LV(FT_SAVESTR, 0); + /* fall through */ + case TF_EXPR: + *--cp = c; + cp = do_expr(cp, t->extra); + LV(t->f_type, 0); + c = *cp++; + ftbl = t; + break; + + case TF_NOP: + *--cp = c; + cp = do_expr(cp, t->extra); + c = *cp++; + ftbl = t; + break; + } + if (c != ')') { + CERROR("')' expected"); + } + --infunction; + return (cp); +} + +static char * +do_expr (char *sp, int preprocess) +{ + register char *cp = sp; + register int c; + + if ((c = *cp++) == '{') { + cp = do_name (cp, preprocess); + fp->f_type = FT_LS_COMP; + } else if (c == '(') { + cp = do_func (cp); + } else if (c == ')') { + return (--cp); + } else if (c == '%' && *cp == '<') { + cp = do_if (cp+1); + } else { + CERROR ("'(', '{', '%<' or ')' expected"); + } + return (cp); +} + +static char * +do_loop(char *sp) +{ + register char *cp = sp; + struct format *floop; + + floop = next_fp; + cp = compile (cp); + if (*cp++ != ']') + CERROR ("']' expected"); + + LV(FT_DONE, 1); /* not yet done */ + LV(FT_GOTO, 0); + fp->f_skip = floop - fp; /* skip backwards */ + + return cp; +} + +static char * +do_if(char *sp) +{ + register char *cp = sp; + register struct format *fexpr, + *fif = (struct format *)NULL; + register int c = '<'; + + for (;;) { + if (c == '<') { /* doing an IF */ + if ((c = *cp++) == '{') /*}*/{ + cp = do_name(cp, 0); + fp->f_type = FT_LS_COMP; + LV (FT_IF_S, 0); + } + else if (c == '(') { + cp = do_func(cp); + /* see if we can merge the load and the "if" */ + if (ftbl->f_type >= IF_FUNCS) + fp->f_type = ftbl->extra; + else { + LV (FT_IF_V_NE, 0); + } + } + else { + CERROR("'(' or '{' expected"); /*}*/ + } + } + + fexpr = fp; /* loc of [ELS]IF */ + cp = compile (cp); /* compile IF TRUE stmts */ + if (fif) + fif->f_skip = next_fp - fif; + + if ((c = *cp++) == '|') { /* the last ELSE */ + LV(FT_GOTO, 0); + fif = fp; /* loc of GOTO */ + fexpr->f_skip = next_fp - fexpr; + + fexpr = (struct format *)NULL;/* no extra ENDIF */ + + cp = compile (cp); /* compile ELSE stmts */ + fif->f_skip = next_fp - fif; + c = *cp++; + } + else if (c == '?') { /* another ELSIF */ + LV(FT_GOTO, 0); + fif = fp; /* loc of GOTO */ + fexpr->f_skip = next_fp - fexpr; + + c = '<'; /* impersonate an IF */ + continue; + } + break; + } + + if (c != '>') { + CERROR("'>' expected."); + } + + if (fexpr) /* IF ... [ELSIF ...] ENDIF */ + fexpr->f_skip = next_fp - fexpr; + + return (cp); +} diff --git a/sbr/fmt_def.c b/sbr/fmt_def.c new file mode 100644 index 0000000..e3384e8 --- /dev/null +++ b/sbr/fmt_def.c @@ -0,0 +1,10 @@ + +/* + * fmt_def.c -- some defines for sbr/fmt_scan.c + * + * $Id$ + */ + +#include + +int fmt_norm = AD_NAME; diff --git a/sbr/fmt_new.c b/sbr/fmt_new.c new file mode 100644 index 0000000..003ec2b --- /dev/null +++ b/sbr/fmt_new.c @@ -0,0 +1,105 @@ + +/* + * fmt_new.c -- read format file/string and normalize + * + * $Id$ + */ + +#include + +#define QUOTE '\\' + +static char *formats = 0; + +/* + * static prototypes + */ +static void normalize (char *); + + +/* + * Get new format string + */ + +char * +new_fs (char *form, char *format, char *default_fs) +{ + struct stat st; + register FILE *fp; + + if (formats) + free (formats); + + if (form) { + if ((fp = fopen (etcpath (form), "r")) == NULL) + adios (form, "unable to open format file"); + + if (fstat (fileno (fp), &st) == -1) + adios (form, "unable to stat format file"); + + if (!(formats = malloc ((size_t) st.st_size + 1))) + adios (form, "unable to allocate space for format"); + + if (read (fileno(fp), formats, (int) st.st_size) != st.st_size) + adios (form, "error reading format file"); + + formats[st.st_size] = '\0'; + + fclose (fp); + } else { + formats = getcpy (format ? format : default_fs); + } + + normalize (formats); /* expand escapes */ + + return formats; +} + + +/* + * Expand escapes in format strings + */ + +static void +normalize (char *cp) +{ + char *dp; + + for (dp = cp; *cp; cp++) { + if (*cp != QUOTE) { + *dp++ = *cp; + } else { + switch (*++cp) { + case 'b': + *dp++ = '\b'; + break; + + case 'f': + *dp++ = '\f'; + break; + + case 'n': + *dp++ = '\n'; + break; + + case 'r': + *dp++ = '\r'; + break; + + case 't': + *dp++ = '\t'; + break; + + case '\n': + break; + + case 0: + cp--; /* fall */ + default: + *dp++ = *cp; + break; + } + } + } + *dp = '\0'; +} diff --git a/sbr/fmt_rfc2047.c b/sbr/fmt_rfc2047.c new file mode 100644 index 0000000..a9c3d0b --- /dev/null +++ b/sbr/fmt_rfc2047.c @@ -0,0 +1,235 @@ + +/* + * fmt_rfc2047.c -- decode RFC-2047 header format + * + * $Id$ + */ + +#include + +static signed char hexindex[] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 +}; + +static signed char index_64[128] = { + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +}; + +#define char64(c) (((unsigned char) (c) > 127) ? -1 : index_64[(unsigned char) (c)]) + +static int +unqp (unsigned char byte1, unsigned char byte2) +{ + if (hexindex[byte1] == -1 || hexindex[byte2] == -1) + return -1; + return (hexindex[byte1] << 4 | hexindex[byte2]); +} + +/* Check if character is linear whitespace */ +#define is_lws(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') + + +/* + * Decode the string as a RFC-2047 header field + */ + +int +decode_rfc2047 (char *str, char *dst) +{ + char *p, *q, *pp; + char *startofmime, *endofmime; + int c, quoted_printable; + int encoding_found = 0; /* did we decode anything? */ + int between_encodings = 0; /* are we between two encodings? */ + int equals_pending = 0; /* is there a '=' pending? */ + int whitespace = 0; /* how much whitespace between encodings? */ + + if (!str) + return 0; + + /* + * Do a quick and dirty check for the '=' character. + * This should quickly eliminate many cases. + */ + if (!strchr (str, '=')) + return 0; + + for (p = str, q = dst; *p; p++) { + /* + * If we had an '=' character pending from + * last iteration, then add it first. + */ + if (equals_pending) { + *q++ = '='; + equals_pending = 0; + between_encodings = 0; /* we have added non-whitespace text */ + } + + if (*p != '=') { + /* count linear whitespace while between encodings */ + if (between_encodings && is_lws(*p)) + whitespace++; + else + between_encodings = 0; /* we have added non-whitespace text */ + *q++ = *p; + continue; + } + + equals_pending = 1; /* we have a '=' pending */ + + /* Check for initial =? */ + if (*p == '=' && p[1] && p[1] == '?' && p[2]) { + startofmime = p + 2; + + /* Scan ahead for the next '?' character */ + for (pp = startofmime; *pp && *pp != '?'; pp++) + ; + + if (!*pp) + continue; + + /* Check if character set is OK */ + if (!check_charset(startofmime, pp - startofmime)) + continue; + + startofmime = pp + 1; + + /* Check for valid encoding type */ + if (*startofmime != 'B' && *startofmime != 'b' && + *startofmime != 'Q' && *startofmime != 'q') + continue; + + /* Is encoding quoted printable or base64? */ + quoted_printable = (*startofmime == 'Q' || *startofmime == 'q'); + startofmime++; + + /* Check for next '?' character */ + if (*startofmime != '?') + continue; + startofmime++; + + /* + * Scan ahead for the ending ?= + * + * While doing this, we will also check if encoded + * word has any embedded linear whitespace. + */ + endofmime = NULL; + for (pp = startofmime; *pp && *(pp+1); pp++) { + if (is_lws(*pp)) { + break; + } else if (*pp == '?' && pp[1] == '=') { + endofmime = pp; + break; + } + } + if (is_lws(*pp) || endofmime == NULL) + continue; + + /* + * We've found an encoded word, so we can drop + * the '=' that was pending + */ + equals_pending = 0; + + /* + * If we are between two encoded words separated only by + * linear whitespace, then we ignore the whitespace. + * We will roll back the buffer the number of whitespace + * characters we've seen since last encoded word. + */ + if (between_encodings) + q -= whitespace; + + /* Now decode the text */ + if (quoted_printable) { + for (pp = startofmime; pp < endofmime; pp++) { + if (*pp == '=') { + c = unqp (pp[1], pp[2]); + if (c == -1) + continue; + if (c != 0) + *q++ = c; + pp += 2; + } else if (*pp == '_') { + *q++ = ' '; + } else { + *q++ = *pp; + } + } + } else { + /* base64 */ + int c1, c2, c3, c4; + + pp = startofmime; + while (pp < endofmime) { + /* 6 + 2 bits */ + while ((pp < endofmime) && + ((c1 = char64(*pp)) == -1)) { + pp++; + } + if (pp < endofmime) { + pp++; + } + while ((pp < endofmime) && + ((c2 = char64(*pp)) == -1)) { + pp++; + } + if (pp < endofmime && c1 != -1 && c2 != -1) { + *q++ = (c1 << 2) | (c2 >> 4); + pp++; + } + /* 4 + 4 bits */ + while ((pp < endofmime) && + ((c3 = char64(*pp)) == -1)) { + pp++; + } + if (pp < endofmime && c2 != -1 && c3 != -1) { + *q++ = ((c2 & 0xF) << 4) | (c3 >> 2); + pp++; + } + /* 2 + 6 bits */ + while ((pp < endofmime) && + ((c4 = char64(*pp)) == -1)) { + pp++; + } + if (pp < endofmime && c3 != -1 && c4 != -1) { + *q++ = ((c3 & 0x3) << 6) | (c4); + pp++; + } + } + } + + /* + * Now that we are done decoding this particular + * encoded word, advance string to trailing '='. + */ + p = endofmime + 1; + + encoding_found = 1; /* we found (at least 1) encoded word */ + between_encodings = 1; /* we have just decoded something */ + whitespace = 0; /* re-initialize amount of whitespace */ + } + } + + /* If an equals was pending at end of string, add it now. */ + if (equals_pending) + *q++ = '='; + *q = '\0'; + + return encoding_found; +} diff --git a/sbr/fmt_scan.c b/sbr/fmt_scan.c new file mode 100644 index 0000000..2086a94 --- /dev/null +++ b/sbr/fmt_scan.c @@ -0,0 +1,826 @@ + +/* + * fmt_scan.c -- format string interpretation + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +#define NFMTS MAXARGS + +extern char *formataddr (); /* hook for custom address formatting */ + +#ifdef LBL +struct msgs *fmt_current_folder; /* current folder (set by main program) */ +#endif + +extern int fmt_norm; /* defined in sbr/fmt_def.c = AD_NAME */ +struct mailname fmt_mnull; + +/* + * static prototypes + */ +static int match (char *, char *); +static char *get_x400_friendly (char *, char *, int); +static int get_x400_comp (char *, char *, char *, int); + + +/* + * test if string "sub" appears anywhere in + * string "str" (case insensitive). + */ + +static int +match (char *str, char *sub) +{ + int c1, c2; + char *s1, *s2; + +#ifdef LOCALE + while ((c1 = *sub)) { + c1 = (isalpha(c1) && isupper(c1)) ? tolower(c1) : c1; + while ((c2 = *str++) && c1 != ((isalpha(c2) && isupper(c2)) ? tolower(c2) : c2)) + ; + if (! c2) + return 0; + s1 = sub + 1; s2 = str; + while ((c1 = *s1++) && ((isalpha(c1) && isupper(c1)) ? tolower(c1) : c1) == ((isalpha(c2 =*s2++) && isupper(c2)) ? tolower(c2) : c2)) + ; + if (! c1) + return 1; + } +#else + while ((c1 = *sub)) { + while ((c2 = *str++) && (c1 | 040) != (c2 | 040)) + ; + if (! c2) + return 0; + s1 = sub + 1; s2 = str; + while ((c1 = *s1++) && (c1 | 040) == (*s2++ | 040)) + ; + if (! c1) + return 1; + } +#endif + return 1; +} + +/* + * macros to format data + */ + +#define PUTDF(cp, num, wid, fill)\ + if (cp + wid < ep) {\ + if ((i = (num)) < 0)\ + i = -(num);\ + if ((c = (wid)) < 0)\ + c = -c;\ + sp = cp + c;\ + do {\ + *--sp = (i % 10) + '0';\ + i /= 10;\ + } while (i > 0 && sp > cp);\ + if (i > 0)\ + *sp = '?';\ + else if ((num) < 0 && sp > cp)\ + *--sp = '-';\ + while (sp > cp)\ + *--sp = fill;\ + cp += c;\ + } + +#define PUTD(cp, num)\ + if (cp < ep) {\ + if ((i = (num)) == 0)\ + *cp++ = '0';\ + else {\ + if ((i = (num)) < 0) {\ + *cp++ = '-';\ + i = -(num);\ + }\ + c = 10;\ + while (c <= i) \ + c *= 10;\ + while (cp < ep && c > 1) {\ + c /= 10;\ + *cp++ = (i / c) + '0';\ + i %= c;\ + }\ + }\ + } + +#ifdef LOCALE +#define PUTSF(cp, str, wid, fill) {\ + ljust = 0;\ + if ((i = (wid)) < 0) {\ + i = -i;\ + ljust++;\ + }\ + if ((sp = (str))) {\ + if (ljust) {\ + c = strlen(sp);\ + if (c > i)\ + sp += c - i;\ + else {\ + while( --i >= c && cp < ep)\ + *cp++ = fill;\ + i++;\ + }\ + } else {\ + while ((c = (unsigned char) *sp) && (iscntrl(c) || isspace(c)))\ + sp++;\ + }\ + while ((c = (unsigned char) *sp++) && --i >= 0 && cp < ep)\ + if (isgraph(c)) \ + *cp++ = c;\ + else {\ + while ((c = (unsigned char) *sp) && (iscntrl(c) || isspace(c)))\ + sp++;\ + *cp++ = ' ';\ + }\ + }\ + if (!ljust)\ + while( --i >= 0 && cp < ep)\ + *cp++ = fill;\ + } + +#define PUTS(cp, str) {\ + if ((sp = (str))) {\ + while ((c = (unsigned char) *sp) && (iscntrl(c) || isspace(c)))\ + sp++;\ + while((c = (unsigned char) *sp++) && cp < ep)\ + if (isgraph(c)) \ + *cp++ = c;\ + else {\ + while ((c = (unsigned char) *sp) && (iscntrl(c) || isspace(c)))\ + sp++;\ + *cp++ = ' ';\ + }\ + }\ + } + +#else /* LOCALE */ +#define PUTSF(cp, str, wid, fill) {\ + ljust = 0;\ + if ((i = (wid)) < 0) {\ + i = -i;\ + ljust++;\ + }\ + if (sp = (str)) {\ + if (ljust) {\ + c = strlen(sp);\ + if (c > i)\ + sp += c - i;\ + else {\ + while( --i >= c && cp < ep)\ + *cp++ = fill;\ + i++;\ + }\ + } else {\ + while ((c = *sp) && c <= 32)\ + sp++;\ + }\ + while ((c = *sp++) && --i >= 0 && cp < ep)\ + if (c > 32) \ + *cp++ = c;\ + else {\ + while ((c = *sp) && c <= 32)\ + sp++;\ + *cp++ = ' ';\ + }\ + }\ + if (!ljust)\ + while( --i >= 0 && cp < ep)\ + *cp++ = fill;\ + } + +#define PUTS(cp, str) {\ + if (sp = (str)) {\ + while ((c = *sp) && c <= 32)\ + sp++;\ + while( (c = *sp++) && cp < ep)\ + if ( c > 32 ) \ + *cp++ = c;\ + else {\ + while ( (c = *sp) && c <= 32 )\ + sp++;\ + *cp++ = ' ';\ + }\ + }\ + } + +#endif /* LOCALE */ + + +static char *lmonth[] = { "January", "February","March", "April", + "May", "June", "July", "August", + "September","October", "November","December" }; + +static char * +get_x400_friendly (char *mbox, char *buffer, int buffer_len) +{ + char given[BUFSIZ], surname[BUFSIZ]; + + if (mbox == NULL) + return NULL; + if (*mbox == '"') + mbox++; + if (*mbox != '/') + return NULL; + + if (get_x400_comp (mbox, "/PN=", buffer, buffer_len)) { + for (mbox = buffer; mbox = strchr(mbox, '.'); ) + *mbox++ = ' '; + + return buffer; + } + + if (!get_x400_comp (mbox, "/S=", surname, sizeof(surname))) + return NULL; + + if (get_x400_comp (mbox, "/G=", given, sizeof(given))) + snprintf (buffer, buffer_len, "%s %s", given, surname); + else + snprintf (buffer, buffer_len, "%s", surname); + + return buffer; +} + +static int +get_x400_comp (char *mbox, char *key, char *buffer, int buffer_len) +{ + int idx; + char *cp; + + if ((idx = stringdex (key, mbox)) < 0 + || !(cp = strchr(mbox += idx + strlen (key), '/'))) + return 0; + + snprintf (buffer, buffer_len, "%*.*s", cp - mbox, cp - mbox, mbox); + return 1; +} + +struct format * +fmt_scan (struct format *format, char *scanl, int width, int *dat) +{ + char *cp, *ep, *sp; + char *savestr, *str = NULL; + char buffer[BUFSIZ], buffer2[BUFSIZ]; + int i, c, ljust; + int value = 0; + time_t t; + struct format *fmt; + struct comp *comp; + struct tws *tws; + struct mailname *mn; + + cp = scanl; + ep = scanl + width - 1; + fmt = format; + + while (cp < ep) { + switch (fmt->f_type) { + + case FT_COMP: + PUTS (cp, fmt->f_comp->c_text); + break; + case FT_COMPF: + PUTSF (cp, fmt->f_comp->c_text, fmt->f_width, fmt->f_fill); + break; + + case FT_LIT: + sp = fmt->f_text; + while( (c = *sp++) && cp < ep) + *cp++ = c; + break; + case FT_LITF: + sp = fmt->f_text; + ljust = 0; + i = fmt->f_width; + if (i < 0) { + i = -i; + ljust++; /* XXX should do something with this */ + } + while( (c = *sp++) && --i >= 0 && cp < ep) + *cp++ = c; + while( --i >= 0 && cp < ep) + *cp++ = fmt->f_fill; + break; + + case FT_STR: + PUTS (cp, str); + break; + case FT_STRF: + PUTSF (cp, str, fmt->f_width, fmt->f_fill); + break; + case FT_STRFW: + adios (NULL, "internal error (FT_STRFW)"); + + case FT_NUM: + PUTD (cp, value); + break; + case FT_NUMF: + PUTDF (cp, value, fmt->f_width, fmt->f_fill); + break; + + case FT_CHAR: + *cp++ = fmt->f_char; + break; + + case FT_DONE: + goto finished; + + case FT_IF_S: + if (!(value = (str && *str))) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_IF_S_NULL: + if (!(value = (str == NULL || *str == 0))) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_IF_V_EQ: + if (value != fmt->f_value) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_IF_V_NE: + if (value == fmt->f_value) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_IF_V_GT: + if (value <= fmt->f_value) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_IF_MATCH: + if (!(value = (str && match (str, fmt->f_text)))) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_V_MATCH: + if (str) + value = match (str, fmt->f_text); + else + value = 0; + break; + + case FT_IF_AMATCH: + if (!(value = (str && uprf (str, fmt->f_text)))) { + fmt += fmt->f_skip; + continue; + } + break; + + case FT_V_AMATCH: + value = uprf (str, fmt->f_text); + break; + + case FT_S_NONNULL: + value = (str != NULL && *str != 0); + break; + + case FT_S_NULL: + value = (str == NULL || *str == 0); + break; + + case FT_V_EQ: + value = (fmt->f_value == value); + break; + + case FT_V_NE: + value = (fmt->f_value != value); + break; + + case FT_V_GT: + value = (fmt->f_value > value); + break; + + case FT_GOTO: + fmt += fmt->f_skip; + continue; + + case FT_NOP: + break; + + case FT_LS_COMP: + str = fmt->f_comp->c_text; + break; + case FT_LS_LIT: + str = fmt->f_text; + break; + case FT_LS_GETENV: + if (!(str = getenv (fmt->f_text))) + str = ""; + break; + case FT_LS_CFIND: + if (!(str = context_find (fmt->f_text))) + str = ""; + break; + + case FT_LS_DECODECOMP: + if (decode_rfc2047(fmt->f_comp->c_text, buffer2)) + str = buffer2; + else + str = fmt->f_comp->c_text; + break; + + case FT_LS_DECODE: + if (str && decode_rfc2047(str, buffer2)) + str = buffer2; + break; + + case FT_LS_TRIM: + if (str) { + char *xp; + + strncpy(buffer, str, sizeof(buffer)); + str = buffer; + while (isspace(*str)) + str++; + ljust = 0; + if ((i = fmt->f_width) < 0) { + i = -i; + ljust++; + } + + if (!ljust && i > 0 && strlen(str) > i) + str[i] = '\0'; + xp = str; + xp += strlen(str) - 1; + while (xp > str && isspace(*xp)) + *xp-- = '\0'; + if (ljust && i > 0 && strlen(str) > i) + str += strlen(str) - i; + } + break; + + case FT_LV_COMPFLAG: + value = fmt->f_comp->c_flags; + break; + case FT_LV_COMP: + value = (comp = fmt->f_comp)->c_text ? atoi(comp->c_text) : 0; + break; + case FT_LV_LIT: + value = fmt->f_value; + break; + case FT_LV_DAT: + value = dat[fmt->f_value]; + break; + case FT_LV_STRLEN: + value = strlen(str); + break; + case FT_LV_CHAR_LEFT: + value = width - (cp - scanl); + break; + case FT_LV_PLUS_L: + value += fmt->f_value; + break; + case FT_LV_MINUS_L: + value = fmt->f_value - value; + break; + case FT_LV_DIVIDE_L: + if (fmt->f_value) + value = value / fmt->f_value; + else + value = 0; + break; + case FT_LV_MODULO_L: + if (fmt->f_value) + value = value % fmt->f_value; + else + value = 0; + break; + case FT_SAVESTR: + savestr = str; + break; + + case FT_LV_SEC: + value = fmt->f_comp->c_tws->tw_sec; + break; + case FT_LV_MIN: + value = fmt->f_comp->c_tws->tw_min; + break; + case FT_LV_HOUR: + value = fmt->f_comp->c_tws->tw_hour; + break; + case FT_LV_MDAY: + value = fmt->f_comp->c_tws->tw_mday; + break; + case FT_LV_MON: + value = fmt->f_comp->c_tws->tw_mon + 1; + break; + case FT_LS_MONTH: + str = tw_moty[fmt->f_comp->c_tws->tw_mon]; + break; + case FT_LS_LMONTH: + str = lmonth[fmt->f_comp->c_tws->tw_mon]; + break; + case FT_LS_ZONE: + str = dtwszone (fmt->f_comp->c_tws); + break; + case FT_LV_YEAR: + value = fmt->f_comp->c_tws->tw_year; + break; + case FT_LV_WDAY: + if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP))) + set_dotw (tws); + value = tws->tw_wday; + break; + case FT_LS_DAY: + if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP))) + set_dotw (tws); + str = tw_dotw[tws->tw_wday]; + break; + case FT_LS_WEEKDAY: + if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP))) + set_dotw (tws); + str = tw_ldotw[tws->tw_wday]; + break; + case FT_LV_YDAY: + value = fmt->f_comp->c_tws->tw_yday; + break; + case FT_LV_ZONE: + value = fmt->f_comp->c_tws->tw_zone; + break; + case FT_LV_CLOCK: + if ((value = fmt->f_comp->c_tws->tw_clock) == 0) + value = dmktime(fmt->f_comp->c_tws); + break; + case FT_LV_RCLOCK: + if ((value = fmt->f_comp->c_tws->tw_clock) == 0) + value = dmktime(fmt->f_comp->c_tws); + value = time((time_t *) 0) - value; + break; + case FT_LV_DAYF: + if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP))) + set_dotw (tws); + switch (fmt->f_comp->c_tws->tw_flags & TW_SDAY) { + case TW_SEXP: + value = 1; break; + case TW_SIMP: + value = 0; break; + default: + value = -1; break; + } + case FT_LV_ZONEF: + if ((fmt->f_comp->c_tws->tw_flags & TW_SZONE) == TW_SZEXP) + value = 1; + else + value = -1; + break; + case FT_LV_DST: + value = fmt->f_comp->c_tws->tw_flags & TW_DST; + break; + case FT_LS_822DATE: + str = dasctime (fmt->f_comp->c_tws , TW_ZONE); + break; + case FT_LS_PRETTY: + str = dasctime (fmt->f_comp->c_tws, TW_NULL); + break; + + case FT_LS_PERS: + str = fmt->f_comp->c_mn->m_pers; + break; + case FT_LS_MBOX: + str = fmt->f_comp->c_mn->m_mbox; + break; + case FT_LS_HOST: + str = fmt->f_comp->c_mn->m_host; + break; + case FT_LS_PATH: + str = fmt->f_comp->c_mn->m_path; + break; + case FT_LS_GNAME: + str = fmt->f_comp->c_mn->m_gname; + break; + case FT_LS_NOTE: + str = fmt->f_comp->c_mn->m_note; + break; + case FT_LS_822ADDR: + str = adrformat( fmt->f_comp->c_mn ); + break; + case FT_LV_HOSTTYPE: + value = fmt->f_comp->c_mn->m_type; + break; + case FT_LV_INGRPF: + value = fmt->f_comp->c_mn->m_ingrp; + break; + case FT_LV_NOHOSTF: + value = fmt->f_comp->c_mn->m_nohost; + break; + case FT_LS_ADDR: + case FT_LS_FRIENDLY: + if ((mn = fmt->f_comp->c_mn) == &fmt_mnull) { + str = fmt->f_comp->c_text; + break; + } + if (fmt->f_type == FT_LS_ADDR) + goto unfriendly; + if ((str = mn->m_pers) == NULL) + if ((str = mn->m_note)) { + strncpy (buffer, str, sizeof(buffer)); + str = buffer; + if (*str == '(') + str++; + sp = str + strlen(str) - 1; + if (*sp == ')') { + *sp-- = '\0'; + while (sp >= str) + if (*sp == ' ') + *sp-- = '\0'; + else + break; + } + } else if (!(str = get_x400_friendly (mn->m_mbox, + buffer, sizeof(buffer)))) { + unfriendly: ; + switch (mn->m_type) { + case LOCALHOST: + str = mn->m_mbox; + break; + case UUCPHOST: + snprintf (buffer, sizeof(buffer), "%s!%s", + mn->m_host, mn->m_mbox); + str = buffer; + break; + default: + if (mn->m_mbox) { + snprintf (buffer, sizeof(buffer), "%s@%s", + mn->m_mbox, mn->m_host); + str= buffer; + } + else + str = mn->m_text; + break; + } + } + break; + + case FT_LOCALDATE: + comp = fmt->f_comp; + if ((t = comp->c_tws->tw_clock) == 0) + t = dmktime(comp->c_tws); + tws = dlocaltime(&t); + *comp->c_tws = *tws; + break; + + case FT_GMTDATE: + comp = fmt->f_comp; + if ((t = comp->c_tws->tw_clock) == 0) + t = dmktime(comp->c_tws); + tws = dgmtime(&t); + *comp->c_tws = *tws; + break; + + case FT_PARSEDATE: + comp = fmt->f_comp; + if ((sp = comp->c_text) && (tws = dparsetime(sp))) { + *comp->c_tws = *tws; + comp->c_flags = 0; + } else if (comp->c_flags >= 0) { + memset ((char *) comp->c_tws, 0, sizeof *comp->c_tws); + comp->c_flags = 1; + } + break; + + case FT_FORMATADDR: + /* hook for custom address list formatting (see replsbr.c) */ + str = formataddr (savestr, str); + break; + + case FT_PUTADDR: + /* output the str register as an address component, + * splitting it into multiple lines if necessary. The + * value reg. contains the max line length. The lit. + * field may contain a string to prepend to the result + * (e.g., "To: ") + */ + { + char *lp, *lastb; + int indent, wid, len; + + lp = str; + wid = value; + len = strlen (str); + sp = fmt->f_text; + indent = strlen (sp); + wid -= indent; + while( (c = *sp++) && cp < ep) + *cp++ = c; + while (len > wid) { + /* try to break at a comma; failing that, break at a + * space, failing that, just split the line. + */ + lastb = 0; sp = lp + wid; + while (sp > lp && (c = *--sp) != ',') { + if (! lastb && isspace(c)) + lastb = sp - 1; + } + if (sp == lp) + if (! (sp = lastb)) + sp = lp + wid - 1; + len -= sp - lp + 1; + while (cp < ep && lp <= sp) + *cp++ = *lp++; + *cp++ = '\n'; + for (i=indent; cp < ep && i > 0; i--) + *cp++ = ' '; + while (isspace(*lp)) + lp++, len--; + } + PUTS (cp, lp); + } + break; + + case FT_PARSEADDR: + comp = fmt->f_comp; + if (comp->c_mn != &fmt_mnull) + mnfree (comp->c_mn); + if ((sp = comp->c_text) && (sp = getname(sp)) && + (mn = getm (sp, NULL, 0, fmt_norm, NULL))) { + comp->c_mn = mn; + while (getname("")) + ; + } else { + while (getname("")) /* XXX */ + ; + comp->c_mn = &fmt_mnull; + } + break; + + case FT_MYMBOX: + /* + * if there's no component, we say true. Otherwise we + * say "true" only if we can parse the address and it + * matches one of our addresses. + */ + comp = fmt->f_comp; + if (comp->c_mn != &fmt_mnull) + mnfree (comp->c_mn); + if ((sp = comp->c_text) && (sp = getname(sp)) && + (mn = getm (sp, NULL, 0, AD_NAME, NULL))) { + comp->c_mn = mn; + comp->c_flags = ismymbox(mn); + while ((sp = getname(sp))) + if (comp->c_flags == 0 && + (mn = getm (sp, NULL, 0, AD_NAME, NULL))) + comp->c_flags |= ismymbox(mn); + } else { + while (getname("")) /* XXX */ + ; + comp->c_flags = (comp->c_text == 0); + comp->c_mn = &fmt_mnull; + } + break; + + case FT_ADDTOSEQ: +#ifdef LBL + /* If we're working on a folder (as opposed to a file), add the + * current msg to sequence given in literal field. Don't + * disturb string or value registers. + */ + if (fmt_current_folder) + seq_addmsg(fmt_current_folder, fmt->f_text, dat[0], -1); +#endif + break; + } + fmt++; + } +#ifndef JLR + finished:; + if (cp[-1] != '\n') + *cp++ = '\n'; + *cp = 0; + return ((struct format *)0); +#else /* JLR */ + if (cp[-1] != '\n') + *cp++ = '\n'; + while (fmt->f_type != FT_DONE) + fmt++; + + finished:; + *cp = '\0'; + return (fmt->f_value ? ++fmt : (struct format *) 0); + +#endif /* JLR */ +} diff --git a/sbr/folder_addmsg.c b/sbr/folder_addmsg.c new file mode 100644 index 0000000..6858f4d --- /dev/null +++ b/sbr/folder_addmsg.c @@ -0,0 +1,194 @@ + +/* + * folder_addmsg.c -- Link message into folder + * + * $Id$ + */ + +#include +#include +#include + +/* + * Link message into a folder. Return the new number + * of the message. If an error occurs, return -1. + */ + +int +folder_addmsg (struct msgs **mpp, char *msgfile, int selected, + int unseen, int preserve) +{ + int infd, outfd, linkerr, first_time, msgnum; + char *nmsg, newmsg[BUFSIZ]; + struct msgs *mp; + struct stat st1, st2; + + first_time = 1; /* this is first attempt */ + mp = *mpp; + + /* + * We might need to make several attempts + * in order to add the message to the folder. + */ + for (;;) { + /* + * Get the message number we will attempt to add. + */ + if (first_time) { + /* should we preserve the numbering of the message? */ + if (preserve && (msgnum = m_atoi (msgfile)) > 0) { + ; + } else if (mp->nummsg == 0) { + /* check if we are adding to empty folder */ + msgnum = 1; + } else { + /* else use highest message number + 1 */ + msgnum = mp->hghmsg + 1; + } + first_time = 0; + } else { + /* another attempt, so try next higher message number */ + msgnum++; + } + + /* + * See if we need more space. If we need space at the + * end, then we allocate space for an addition 100 messages. + * If we need space at the beginning of the range, then just + * extend message status range to cover this message number. + */ + if (msgnum > mp->hghoff) { + if ((mp = folder_realloc (mp, mp->lowoff, msgnum + 100))) + *mpp = mp; + else { + advise (NULL, "unable to allocate folder storage"); + return -1; + } + } else if (msgnum < mp->lowoff) { + if ((mp = folder_realloc (mp, msgnum, mp->hghoff))) + *mpp = mp; + else { + advise (NULL, "unable to allocate folder storage"); + return -1; + } + } + + /* + * If a message is already in that slot, + * then loop to next available slot. + */ + if (does_exist (mp, msgnum)) + continue; + + /* setup the bit flags for this message */ + clear_msg_flags (mp, msgnum); + set_exists (mp, msgnum); + + /* should we set the SELECT_UNSEEN bit? */ + if (unseen) { + set_unseen (mp, msgnum); + } + + /* should we set the SELECTED bit? */ + if (selected) { + set_selected (mp, msgnum); + + /* check if highest or lowest selected */ + if (mp->numsel == 0) { + mp->lowsel = msgnum; + mp->hghsel = msgnum; + } else { + if (msgnum < mp->lowsel) + mp->lowsel = msgnum; + if (msgnum > mp->hghsel) + mp->hghsel = msgnum; + } + + /* increment number selected */ + mp->numsel++; + } + + /* + * check if this is highest or lowest message + */ + if (mp->nummsg == 0) { + mp->lowmsg = msgnum; + mp->hghmsg = msgnum; + } else { + if (msgnum < mp->lowmsg) + mp->lowmsg = msgnum; + if (msgnum > mp->hghmsg) + mp->hghmsg = msgnum; + } + + /* increment message count */ + mp->nummsg++; + + nmsg = m_name (msgnum); + snprintf (newmsg, sizeof(newmsg), "%s/%s", mp->foldpath, nmsg); + + /* + * Now try to link message into folder + */ + if (link (msgfile, newmsg) != -1) { + return msgnum; + } else { + linkerr = errno; + +#ifdef EISREMOTE + if (linkerr == EISREMOTE) + linkerr = EXDEV; +#endif /* EISREMOTE */ + + /* + * Check if the file in our desired location is the same + * as the source file. If so, then just leave it alone + * and return. Otherwise, we will continue the main loop + * and try again at another slot (hghmsg+1). + */ + if (linkerr == EEXIST) { + if (stat (msgfile, &st2) == 0 && stat (newmsg, &st1) == 0 + && st2.st_ino == st1.st_ino) { + return msgnum; + } else { + continue; + } + } + + /* + * If link failed because we are trying to link + * across devices, then check if there is a message + * already in the desired location. If so, then return + * error, else just copy the message. + */ + if (linkerr == EXDEV) { + if (stat (newmsg, &st1) == 0) { + advise (NULL, "message %s:%s already exists", newmsg); + return -1; + } else { + if ((infd = open (msgfile, O_RDONLY)) == -1) { + advise (msgfile, "unable to open message %s"); + return -1; + } + fstat (infd, &st1); + if ((outfd = creat (newmsg, (int) st1.st_mode & 0777)) == -1) { + advise (newmsg, "unable to create"); + close (infd); + return -1; + } + cpydata (infd, outfd, msgfile, newmsg); + close (infd); + close (outfd); + return msgnum; + } + } + + /* + * Else, some other type of link error, + * so just return error. + */ + advise (newmsg, "error linking %s to", msgfile); + return -1; + } + } +} diff --git a/sbr/folder_delmsgs.c b/sbr/folder_delmsgs.c new file mode 100644 index 0000000..e5374b5 --- /dev/null +++ b/sbr/folder_delmsgs.c @@ -0,0 +1,115 @@ + +/* + * folder_delmsgs.c -- "remove" SELECTED messages from a folder + * + * $Id$ + */ + +#include + +/* + * 1) If we are using an external rmmproc, then exec it. + * 2) Else if unlink_msgs is non-zero, then unlink the + * SELECTED messages. + * 3) Else rename SELECTED messages by prefixing name + * with a standard prefix. + * + * If there is an error, return -1, else return 0. + */ + +int +folder_delmsgs (struct msgs *mp, int unlink_msgs) +{ + pid_t pid; + int msgnum, vecp, retval = 0; + char buf[100], *dp, **vec; + + /* + * If "rmmproc" is defined, exec it to remove messages. + */ + if (rmmproc) { + /* Unset the EXISTS flag for each message to be removed */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) + unset_exists (mp, msgnum); + } + + /* Mark that the sequence information has changed */ + mp->msgflags |= SEQMOD; + + if (mp->numsel > MAXARGS - 2) + adios (NULL, "more than %d messages for %s exec", MAXARGS - 2, + rmmproc); + vec = (char **) calloc ((size_t) (mp->numsel + 2), sizeof(*vec)); + if (vec == NULL) + adios (NULL, "unable to allocate exec vector"); + vecp = 1; + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum) && + !(vec[vecp++] = strdup (m_name (msgnum)))) + adios (NULL, "strdup failed"); + } + vec[vecp] = NULL; + + fflush (stdout); + vec[0] = r1bindex (rmmproc, '/'); + + switch (pid = vfork()) { + case -1: + advise ("fork", "unable to"); + return -1; + + case 0: + execvp (rmmproc, vec); + fprintf (stderr, "unable to exec "); + perror (rmmproc); + _exit (-1); + + default: + return (pidwait (pid, -1)); + } + } + + /* + * Either unlink or rename the SELECTED messages + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) { + /* unselect message */ + unset_selected (mp, msgnum); + mp->numsel--; + + dp = m_name (msgnum); + + if (unlink_msgs) { + /* just unlink the messages */ + if (unlink (dp) == -1) { + admonish (dp, "unable to unlink"); + retval = -1; + continue; + } + } else { + /* or rename messages with standard prefix */ + strncpy (buf, m_backup (dp), sizeof(buf)); + if (rename (dp, buf) == -1) { + admonish (buf, "unable to rename %s to", dp); + retval = -1; + continue; + } + } + + /* If removal was successful, decrement message count */ + unset_exists (mp, msgnum); + mp->nummsg--; + } + } + + /* Sanity check */ + if (mp->numsel != 0) + adios (NULL, "oops, mp->numsel should be 0"); + + /* Mark that the sequence information has changed */ + mp->msgflags |= SEQMOD; + + return retval; +} diff --git a/sbr/folder_free.c b/sbr/folder_free.c new file mode 100644 index 0000000..1c1051d --- /dev/null +++ b/sbr/folder_free.c @@ -0,0 +1,28 @@ + +/* + * folder_free.c -- free a folder/message structure + * + * $Id$ + */ + +#include + + +void +folder_free (struct msgs *mp) +{ + int i; + + if (!mp) + return; + + if (mp->foldpath) + free (mp->foldpath); + + /* free the sequence names */ + for (i = 0; mp->msgattrs[i]; i++) + free (mp->msgattrs[i]); + + free (mp->msgstats); /* free message status area */ + free (mp); /* free main folder structure */ +} diff --git a/sbr/folder_pack.c b/sbr/folder_pack.c new file mode 100644 index 0000000..05365a8 --- /dev/null +++ b/sbr/folder_pack.c @@ -0,0 +1,86 @@ + +/* + * folder_pack.c -- pack (renumber) the messages in a folder + * -- into a contiguous range from 1 to n. + * + * $Id$ + */ + +#include + +/* + * Pack the message in a folder. + * Return -1 if error, else return 0. + */ + +int +folder_pack (struct msgs **mpp, int verbose) +{ + int hole, msgnum, newcurrent = 0; + char newmsg[BUFSIZ], oldmsg[BUFSIZ]; + struct msgs *mp; + + mp = *mpp; + + /* + * Just return if folder is empty. + */ + if (mp->nummsg == 0) + return 0; + + /* + * Make sure we have message status space allocated + * for all numbers from 1 to current high message. + */ + if (mp->lowoff > 1) { + if ((mp = folder_realloc (mp, 1, mp->hghmsg))) + *mpp = mp; + else { + advise (NULL, "unable to allocate folder storage"); + return -1; + } + } + + for (msgnum = mp->lowmsg, hole = 1; msgnum <= mp->hghmsg; msgnum++) { + if (does_exist (mp, msgnum)) { + if (msgnum != hole) { + strncpy (newmsg, m_name (hole), sizeof(newmsg)); + strncpy (oldmsg, m_name (msgnum), sizeof(oldmsg)); + if (verbose) + printf ("message %s becomes %s\n", oldmsg, newmsg); + + /* move the message file */ + if (rename (oldmsg, newmsg) == -1) { + advise (newmsg, "unable to rename %s to", oldmsg); + return -1; + } + + /* check if this is the current message */ + if (msgnum == mp->curmsg) + newcurrent = hole; + + /* copy the attribute flags for this message */ + copy_msg_flags (mp, hole, msgnum); + + if (msgnum == mp->lowsel) + mp->lowsel = hole; + if (msgnum == mp->hghsel) + mp->hghsel = hole; + + /* mark that sequence information has been modified */ + mp->msgflags |= SEQMOD; + } + hole++; + } + } + + /* record the new number for the high/low message */ + mp->lowmsg = 1; + mp->hghmsg = hole - 1; + + /* update the "cur" sequence */ + if (newcurrent != 0) + seq_setcur (mp, newcurrent); + + return 0; +} diff --git a/sbr/folder_read.c b/sbr/folder_read.c new file mode 100644 index 0000000..0d01293 --- /dev/null +++ b/sbr/folder_read.c @@ -0,0 +1,163 @@ + +/* + * folder_read.c -- initialize folder structure and read folder + * + * $Id$ + */ + +#include + +/* We allocate the `mi' array 1024 elements at a time */ +#define NUMMSGS 1024 + +/* + * 1) Create the folder/message structure + * 2) Read the directory (folder) and temporarily + * record the numbers of the messages we have seen. + * 3) Then allocate the array for message attributes and + * set the initial flags for all messages we've seen. + * 4) Read and initialize the sequence information. + */ + +struct msgs * +folder_read (char *name) +{ + int msgnum, prefix_len, len, *mi; + struct msgs *mp; + struct stat st; + struct dirent *dp; + DIR *dd; + + name = m_mailpath (name); + if (!(dd = opendir (name))) { + free (name); + return NULL; + } + + if (stat (name, &st) == -1) { + free (name); + return NULL; + } + + /* Allocate the main structure for folder information */ + if (!(mp = (struct msgs *) malloc ((size_t) sizeof(*mp)))) + adios (NULL, "unable to allocate folder storage"); + + clear_folder_flags (mp); + mp->foldpath = name; + mp->lowmsg = 0; + mp->hghmsg = 0; + mp->curmsg = 0; + mp->lowsel = 0; + mp->hghsel = 0; + mp->numsel = 0; + mp->nummsg = 0; + + if (access (name, W_OK) == -1 || st.st_uid != getuid()) + set_readonly (mp); + prefix_len = strlen(BACKUP_PREFIX); + + /* + * Allocate a temporary place to record the + * name of the messages in this folder. + */ + len = NUMMSGS; + if (!(mi = (int *) malloc ((size_t) (len * sizeof(*mi))))) + adios (NULL, "unable to allocate storage"); + + while ((dp = readdir (dd))) { + if ((msgnum = m_atoi (dp->d_name))) { + /* + * Check if we need to allocate more + * temporary elements for message names. + */ + if (mp->nummsg >= len) { + len += NUMMSGS; + if (!(mi = (int *) realloc (mi, + (size_t) (len * sizeof(*mi))))) { + adios (NULL, "unable to allocate storage"); + } + } + + /* Check if this is the first message we've seen */ + if (mp->nummsg == 0) { + mp->lowmsg = msgnum; + mp->hghmsg = msgnum; + } else { + /* Check if this is it the highest or lowest we've seen? */ + if (msgnum < mp->lowmsg) + mp->lowmsg = msgnum; + if (msgnum > mp->hghmsg) + mp->hghmsg = msgnum; + } + + /* + * Now increment count, and record message + * number in a temporary place for now. + */ + mi[mp->nummsg++] = msgnum; + + } else { + switch (dp->d_name[0]) { + case '.': + case ',': +#ifdef MHE + case '+': +#endif /* MHE */ + continue; + + default: + /* skip any files beginning with backup prefix */ + if (!strncmp (dp->d_name, BACKUP_PREFIX, prefix_len)) + continue; + + /* skip the LINK file */ + if (!strcmp (dp->d_name, LINK)) + continue; + + /* indicate that there are other files in folder */ + set_other_files (mp); + continue; + } + } + } + + closedir (dd); + mp->lowoff = max (mp->lowmsg, 1); + + /* Go ahead and allocate space for 100 additional messages. */ + mp->hghoff = mp->hghmsg + 100; + + /* for testing, allocate minimal necessary space */ + /* mp->hghoff = max (mp->hghmsg, 1); */ + + /* + * Allocate space for status of each message. + */ + if (!(mp->msgstats = malloc (MSGSTATSIZE(mp, mp->lowoff, mp->hghoff)))) + adios (NULL, "unable to allocate storage for msgstats"); + + /* + * Clear all the flag bits for all the message + * status entries we just allocated. + */ + for (msgnum = mp->lowoff; msgnum <= mp->hghoff; msgnum++) + clear_msg_flags (mp, msgnum); + + /* + * Scan through the array of messages we've seen and + * setup the initial flags for those messages in the + * newly allocated mp->msgstats area. + */ + for (msgnum = 0; msgnum < mp->nummsg; msgnum++) + set_exists (mp, mi[msgnum]); + + free (mi); /* We don't need this anymore */ + + /* + * Read and initialize the sequence information. + */ + seq_read (mp); + + return mp; +} diff --git a/sbr/folder_realloc.c b/sbr/folder_realloc.c new file mode 100644 index 0000000..f3130f1 --- /dev/null +++ b/sbr/folder_realloc.c @@ -0,0 +1,90 @@ + +/* + * folder_realloc.c -- realloc a folder/msgs structure + * + * $Id$ + */ + +#include + +/* + * Reallocate some of the space in the folder + * structure (currently just message status array). + * + * Return pointer to new folder structure. + * If error, return NULL. + */ + +struct msgs * +folder_realloc (struct msgs *mp, int lo, int hi) +{ + int msgnum; + + /* sanity checks */ + if (lo < 1) + adios (NULL, "BUG: called folder_realloc with lo (%d) < 1", lo); + if (hi < 1) + adios (NULL, "BUG: called folder_realloc with hi (%d) < 1", hi); + if (mp->nummsg > 0 && lo > mp->lowmsg) + adios (NULL, "BUG: called folder_realloc with lo (%d) > mp->lowmsg (%d)", + lo, mp->lowmsg); + if (mp->nummsg > 0 && hi < mp->hghmsg) + adios (NULL, "BUG: called folder_realloc with hi (%d) < mp->hghmsg (%d)", + hi, mp->hghmsg); + + /* Check if we really need to reallocate anything */ + if (lo == mp->lowoff && hi == mp->hghoff) + return mp; + + if (lo == mp->lowoff) { + /* + * We are just extending (or shrinking) the end of message + * status array. So we don't have to move anything and can + * just realloc the message status array. + */ + if (!(mp->msgstats = realloc (mp->msgstats, MSGSTATSIZE(mp, lo, hi)))) { + advise (NULL, "unable to reallocate message storage"); + return NULL; + } + } else { + /* + * We are changing the offset of the message status + * array. So we will need to shift everything. + */ + seqset_t *tmpstats; + + /* first allocate the new message status space */ + if (!(tmpstats = malloc (MSGSTATSIZE(mp, lo, hi)))) { + advise (NULL, "unable to reallocate message storage"); + return NULL; + } + + /* then copy messages status array with shift */ + if (mp->nummsg > 0) { + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) + tmpstats[msgnum - lo] = mp->msgstats[msgnum - mp->lowoff]; + } + free(mp->msgstats); + mp->msgstats = tmpstats; + } + + mp->lowoff = lo; + mp->hghoff = hi; + + /* + * Clear all the flags for entries outside + * the current message range for this folder. + */ + if (mp->nummsg > 0) { + for (msgnum = mp->lowoff; msgnum < mp->lowmsg; msgnum++) + clear_msg_flags (mp, msgnum); + for (msgnum = mp->hghmsg + 1; msgnum <= mp->hghoff; msgnum++) + clear_msg_flags (mp, msgnum); + } else { + /* no messages, so clear entire range */ + for (msgnum = mp->lowoff; msgnum <= mp->hghoff; msgnum++) + clear_msg_flags (mp, msgnum); + } + + return mp; +} diff --git a/sbr/gans.c b/sbr/gans.c new file mode 100644 index 0000000..dcfb316 --- /dev/null +++ b/sbr/gans.c @@ -0,0 +1,49 @@ + +/* + * gans.c -- get an answer from the user + * + * $Id$ + */ + +#include + + +int +gans (char *prompt, struct swit *ansp) +{ + register int i; + register char *cp; + register struct swit *ap; + char ansbuf[BUFSIZ]; + + for (;;) { + printf ("%s", prompt); + fflush (stdout); + cp = ansbuf; + while ((i = getchar ()) != '\n') { + if (i == EOF) + return 0; + if (cp < &ansbuf[sizeof ansbuf - 1]) { +#ifdef LOCALE + i = (isalpha(i) && isupper(i)) ? tolower(i) : i; +#else + if (i >= 'A' && i <= 'Z') + i += 'a' - 'A'; +#endif + *cp++ = i; + } + } + *cp = '\0'; + if (ansbuf[0] == '?' || cp == ansbuf) { + printf ("Options are:\n"); + for (ap = ansp; ap->sw; ap++) + printf (" %s\n", ap->sw); + continue; + } + if ((i = smatch (ansbuf, ansp)) < 0) { + printf ("%s: %s.\n", ansbuf, i == -1 ? "unknown" : "ambiguous"); + continue; + } + return i; + } +} diff --git a/sbr/getans.c b/sbr/getans.c new file mode 100644 index 0000000..987ed3c --- /dev/null +++ b/sbr/getans.c @@ -0,0 +1,75 @@ + +/* + * getans.c -- get an answer from the user and return a string array + * + * $Id$ + */ + +#include +#include +#include +#include + +static char ansbuf[BUFSIZ]; +static jmp_buf sigenv; + +/* + * static prototypes + */ +static RETSIGTYPE intrser (int); + + +char ** +getans (char *prompt, struct swit *ansp) +{ + int i; + SIGNAL_HANDLER istat; + char *cp, **cpp; + + if (!(setjmp (sigenv))) { + istat = SIGNAL (SIGINT, intrser); + } else { + SIGNAL (SIGINT, istat); + return NULL; + } + + for (;;) { + printf ("%s", prompt); + fflush (stdout); + cp = ansbuf; + while ((i = getchar ()) != '\n') { + if (i == EOF) + longjmp (sigenv, 1); + if (cp < &ansbuf[sizeof ansbuf - 1]) + *cp++ = i; + } + *cp = '\0'; + if (ansbuf[0] == '?' || cp == ansbuf) { + printf ("Options are:\n"); + print_sw (ALL, ansp, ""); + continue; + } + cpp = brkstring (ansbuf, " ", NULL); + switch (smatch (*cpp, ansp)) { + case AMBIGSW: + ambigsw (*cpp, ansp); + continue; + case UNKWNSW: + printf (" -%s unknown. Hit for help.\n", *cpp); + continue; + default: + SIGNAL (SIGINT, istat); + return cpp; + } + } +} + + +static RETSIGTYPE +intrser (int i) +{ + /* + * should this be siglongjmp? + */ + longjmp (sigenv, 1); +} diff --git a/sbr/getanswer.c b/sbr/getanswer.c new file mode 100644 index 0000000..f38a834 --- /dev/null +++ b/sbr/getanswer.c @@ -0,0 +1,19 @@ + +/* + * getanswer.c -- get a yes/no answer from the user + */ + +#include +#include + + +int +getanswer (char *prompt) +{ + static int interactive = -1; + + if (interactive < 0) + interactive = isatty (fileno (stdin)) ? 1 : 0; + + return (interactive ? gans (prompt, anoyes) : 1); +} diff --git a/sbr/getarguments.c b/sbr/getarguments.c new file mode 100644 index 0000000..1c61d9b --- /dev/null +++ b/sbr/getarguments.c @@ -0,0 +1,48 @@ + +/* + * getarguments.c -- Get the argument vector ready to go. + * + * $Id$ + */ + +#include + +char ** +getarguments (char *invo_name, int argc, char **argv, int check_context) +{ + char *cp, **ap, **bp, **arguments; + int n = 0; + + /* + * Check if profile/context specifies any arguments + */ + if (check_context && (cp = context_find (invo_name))) { + cp = getcpy (cp); /* make copy */ + ap = brkstring (cp, " ", "\n"); /* split string */ + + /* Count number of arguments split */ + bp = ap; + while (*bp++) + n++; + } + + if (!(arguments = (char **) malloc ((argc + n) * sizeof(*arguments)))) + adios (NULL, "unable to malloc argument storage"); + bp = arguments; + + /* Copy any arguments from profile/context */ + if (n > 0) { + while (*ap) + *bp++ = *ap++; + } + + /* Copy arguments from command line */ + argv++; + while (*argv) + *bp++ = *argv++; + + /* Now NULL terminate the array */ + *bp = NULL; + + return arguments; +} diff --git a/sbr/getcpy.c b/sbr/getcpy.c new file mode 100644 index 0000000..07bc365 --- /dev/null +++ b/sbr/getcpy.c @@ -0,0 +1,32 @@ + +/* + * getcpy.c -- copy a string in managed memory + * + * THIS IS OBSOLETE. NEED TO REPLACE ALL OCCURENCES + * OF GETCPY WITH STRDUP. BUT THIS WILL REQUIRE + * CHANGING PARTS OF THE CODE TO DEAL WITH NULL VALUES. + * + * $Id$ + */ + +#include + + +char * +getcpy (char *str) +{ + char *cp; + size_t len; + + if (str) { + len = strlen(str) + 1; + if (!(cp = malloc (len))) + adios (NULL, "unable to allocate string storage"); + memcpy (cp, str, len); + } else { + if (!(cp = malloc ((size_t) 1))) + adios (NULL, "unable to allocate string storage"); + *cp = '\0'; + } + return cp; +} diff --git a/sbr/getfolder.c b/sbr/getfolder.c new file mode 100644 index 0000000..21db9a9 --- /dev/null +++ b/sbr/getfolder.c @@ -0,0 +1,32 @@ + +/* + * getfolder.c -- get the current or default folder + * + * $Id$ + */ + +#include + + +char * +getfolder(int wantcurrent) +{ + register char *folder; + + /* + * If wantcurrent == 1, then try the current folder first + */ + if (wantcurrent && (folder = context_find (pfolder)) && *folder != '\0') + return folder; + + /* + * Else try the Inbox profile entry + */ + if ((folder = context_find (inbox)) && *folder != '\0') + return folder; + + /* + * Else return compile time default. + */ + return defaultfolder; +} diff --git a/sbr/lock_file.c b/sbr/lock_file.c new file mode 100644 index 0000000..fac895b --- /dev/null +++ b/sbr/lock_file.c @@ -0,0 +1,555 @@ + +/* + * lock.c -- routines to lock/unlock files + * + * $Id$ + */ + +#include +#include + +#ifdef HAVE_ERRNO_H +# include +#endif + +#ifdef MMDFONLY +# include +# include +#endif /* MMDFONLY */ + +#ifdef HAVE_FCNTL_H +# include +#else +# include +#endif + +#if defined(LOCKF_LOCKING) || defined(FLOCK_LOCKING) +# include +#endif + +#include + +extern int errno; + +#ifdef LOCKDIR +char *lockdir = LOCKDIR; +#endif + +/* Are we using any kernel locking? */ +#if defined (FLOCK_LOCKING) || defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING) +# define KERNEL_LOCKING +#endif + +#ifdef DOT_LOCKING + +/* struct for getting name of lock file to create */ +struct lockinfo { + char curlock[BUFSIZ]; + char tmplock[BUFSIZ]; +}; + +/* + * Amount of time to wait before + * updating ctime of lock file. + */ +#define NSECS 20 + +/* + * How old does a lock file need to be + * before we remove it. + */ +#define RSECS 180 + +/* struct for recording and updating locks */ +struct lock { + int l_fd; + char *l_lock; + struct lock *l_next; +}; + +/* top of list containing all open locks */ +static struct lock *l_top = NULL; +#endif /* DOT_LOCKING */ + +/* + * static prototypes + */ +#ifdef KERNEL_LOCKING +static int lkopen_kernel (char *, int, mode_t); +#endif + +#ifdef DOT_LOCKING +static int lkopen_dot (char *, int, mode_t); +static int lockit (struct lockinfo *); +static void lockname (char *, struct lockinfo *, int); +static void timerON (char *, int); +static void timerOFF (int); +static RETSIGTYPE alrmser (int); +#endif + + +/* + * Base routine to open and lock a file, + * and return a file descriptor. + */ + +int +lkopen (char *file, int access, mode_t mode) +{ +#ifdef KERNEL_LOCKING + return lkopen_kernel(file, access, mode); +#endif + +#ifdef DOT_LOCKING + return lkopen_dot(file, access, mode); +#endif +} + + +/* + * Base routine to close and unlock a file, + * given a file descriptor. + */ + +int +lkclose (int fd, char *file) +{ +#ifdef FCNTL_LOCKING + struct flock buf; +#endif + +#ifdef DOT_LOCKING + struct lockinfo lkinfo; +#endif + + if (fd == -1) + return 0; + +#ifdef FCNTL_LOCKING + buf.l_type = F_UNLCK; + buf.l_whence = SEEK_SET; + buf.l_start = 0; + buf.l_len = 0; + fcntl(fd, F_SETLK, &buf); +#endif + +#ifdef FLOCK_LOCKING + flock (fd, LOCK_UN); +#endif + +#ifdef LOCKF_LOCKING + /* make sure we unlock the whole thing */ + lseek (fd, (off_t) 0, SEEK_SET); + lockf (fd, F_ULOCK, 0L); +#endif + +#ifdef DOT_LOCKING + lockname (file, &lkinfo, 0); /* get name of lock file */ + unlink (lkinfo.curlock); /* remove lock file */ + timerOFF (fd); /* turn off lock timer */ +#endif + + return (close (fd)); +} + + +/* + * Base routine to open and lock a file, + * and return a FILE pointer + */ + +FILE * +lkfopen (char *file, char *mode) +{ + int fd, access; + FILE *fp; + + if (strcmp (mode, "r") == 0) + access = O_RDONLY; + else + access = O_RDWR; + + if ((fd = lkopen (file, access, 0)) == -1) + return NULL; + + if ((fp = fdopen (fd, mode)) == NULL) { + close (fd); + return NULL; + } + + return fp; +} + + +/* + * Base routine to close and unlock a file, + * given a FILE pointer + */ + +int +lkfclose (FILE *fp, char *file) +{ +#ifdef FCNTL_LOCKING + struct flock buf; +#endif + +#ifdef DOT_LOCKING + struct lockinfo lkinfo; +#endif + + if (fp == NULL) + return 0; + +#ifdef FCNTL_LOCKING + buf.l_type = F_UNLCK; + buf.l_whence = SEEK_SET; + buf.l_start = 0; + buf.l_len = 0; + fcntl(fileno(fp), F_SETLK, &buf); +#endif + +#ifdef FLOCK_LOCKING + flock (fileno(fp), LOCK_UN); +#endif + +#ifdef LOCKF_LOCKING + /* make sure we unlock the whole thing */ + fseek (fp, 0L, SEEK_SET); + lockf (fileno(fp), F_ULOCK, 0L); +#endif + +#ifdef DOT_LOCKING + lockname (file, &lkinfo, 0); /* get name of lock file */ + unlink (lkinfo.curlock); /* remove lock file */ + timerOFF (fileno(fp)); /* turn off lock timer */ +#endif + + return (fclose (fp)); +} + + +#ifdef KERNEL_LOCKING + +/* + * open and lock a file, using kernel locking + */ + +static int +lkopen_kernel (char *file, int access, mode_t mode) +{ + int fd, i, j; + +# ifdef FCNTL_LOCKING + struct flock buf; +# endif /* FCNTL_LOCKING */ + + for (i = 0; i < 5; i++) { + +# if defined(LOCKF_LOCKING) || defined(FCNTL_LOCKING) + /* remember the original mode */ + j = access; + + /* make sure we open at the beginning */ + access &= ~O_APPEND; + + /* + * We MUST have write permission or + * lockf/fcntl() won't work + */ + if ((access & 03) == O_RDONLY) { + access &= ~O_RDONLY; + access |= O_RDWR; + } +# endif /* LOCKF_LOCKING || FCNTL_LOCKING */ + + if ((fd = open (file, access | O_NDELAY, mode)) == -1) + return -1; + +# ifdef FCNTL_LOCKING + buf.l_type = F_WRLCK; + buf.l_whence = SEEK_SET; + buf.l_start = 0; + buf.l_len = 0; + if (fcntl (fd, F_SETLK, &buf) != -1) + return fd; +# endif + +# ifdef FLOCK_LOCKING + if (flock (fd, LOCK_EX | LOCK_NB) != -1) + return fd; +# endif + +# ifdef LOCKF_LOCKING + if (lockf (fd, F_TLOCK, 0L) != -1) { + /* see if we should be at the end */ + if (j & O_APPEND) + lseek (fd, (off_t) 0, SEEK_END); + return fd; + } +# endif + + j = errno; + close (fd); + sleep (5); + } + + close (fd); + errno = j; + return -1; +} + +#endif /* KERNEL_LOCKING */ + + +#ifdef DOT_LOCKING + +/* + * open and lock a file, using dot locking + */ + +static int +lkopen_dot (char *file, int access, mode_t mode) +{ + int i, fd; + time_t curtime; + struct lockinfo lkinfo; + struct stat st; + + /* open the file */ + if ((fd = open (file, access, mode)) == -1) + return -1; + + /* + * Get the name of the eventual lock file, as well + * as a name for a temporary lock file. + */ + lockname (file, &lkinfo, 1); + + for (i = 0;;) { + /* attempt to create lock file */ + if (lockit (&lkinfo) == 0) { + /* if successful, turn on timer and return */ + timerON (lkinfo.curlock, fd); + return fd; + } else { + /* + * Abort locking, if we fail to lock after 5 attempts + * and are never able to stat the lock file. + */ + if (stat (lkinfo.curlock, &st) == -1) { + if (i++ > 5) + return -1; + sleep (5); + } else { + i = 0; + time (&curtime); + + /* check for stale lockfile, else sleep */ + if (curtime > st.st_ctime + RSECS) + unlink (lkinfo.curlock); + else + sleep (5); + } + } + } +} + +/* + * Routine that actually tries to create + * the lock file. + */ + +static int +lockit (struct lockinfo *li) +{ + int fd; + char *curlock, *tmplock; + +#if 0 + char buffer[128]; +#endif + + curlock = li->curlock; + tmplock = li->tmplock; + + /* create the temporary lock file */ + if ((fd = creat(tmplock, 0600)) == -1) + return -1; + +#if 0 + /* write our process id into lock file */ + snprintf (buffer, sizeof(buffer), "nmh lock: pid %d\n", (int) getpid()); + write(fd, buffer, strlen(buffer) + 1); +#endif + + close (fd); + + /* + * Now try to create the real lock file + * by linking to the temporary file. + */ + fd = link(tmplock, curlock); + unlink(tmplock); + + return (fd == -1 ? -1 : 0); +} + +/* + * Get name of lock file, and temporary lock file + */ + +static void +lockname (char *file, struct lockinfo *li, int isnewlock) +{ + int bplen, tmplen; + char *bp, *cp; + +#if 0 + struct stat st; +#endif + + if ((cp = strrchr (file, '/')) == NULL || *++cp == 0) + cp = file; + + bp = li->curlock; + bplen = 0; +#ifdef LOCKDIR + snprintf (bp, sizeof(li->curlock), "%s/", lockdir); + tmplen = strlen (bp); + bp += tmplen; + bplen += tmplen; +#else + if (cp != file) { + snprintf (bp, sizeof(li->curlock), "%.*s", cp - file, file); + tmplen = strlen (bp); + bp += tmplen; + bplen += tmplen; + } +#endif + +#if 0 + /* + * mmdf style dot locking. Currently not supported. + * If we start supporting mmdf style dot locking, + * we will need to change the return value of lockname + */ + if (stat (file, &st) == -1) + return -1; + + snprintf (bp, sizeof(li->curlock) - bplen, "LCK%05d.%05d", + st.st_dev, st.st_ino); +#endif + + snprintf (bp, sizeof(li->curlock) - bplen, "%s.lock", cp); + + /* + * If this is for a new lock, create a name for + * the temporary lock file for lockit() + */ + if (isnewlock) { + if ((cp = strrchr (li->curlock, '/')) == NULL || *++cp == 0) + strncpy (li->tmplock, ",LCK.XXXXXX", sizeof(li->tmplock)); + else + snprintf (li->tmplock, sizeof(li->tmplock), "%.*s,LCK.XXXXXX", + cp - li->curlock, li->curlock); + mktemp (li->tmplock); + unlink (li->tmplock); /* remove any stray */ + } +} + + +/* + * Add new lockfile to the list of open lockfiles + * and start the lock file timer. + */ + +static void +timerON (char *curlock, int fd) +{ + struct lock *lp; + size_t len; + + if (!(lp = (struct lock *) malloc (sizeof(*lp)))) + return; + + len = strlen(curlock) + 1; + lp->l_fd = fd; + if (!(lp->l_lock = malloc (len))) { + free ((char *) lp); + return; + } + memcpy (lp->l_lock, curlock, len); + lp->l_next = l_top; + + if (!l_top) { + /* perhaps SIGT{STP,TIN,TOU} ? */ + SIGNAL (SIGALRM, alrmser); + alarm (NSECS); + } + + l_top = lp; +} + + +/* + * Search through the list of lockfiles for the + * current lockfile, and remove it from the list. + */ + +static void +timerOFF (int fd) +{ + struct lock *pp, *lp; + + alarm(0); + + if (l_top) { + for (pp = lp = l_top; lp; pp = lp, lp = lp->l_next) { + if (lp->l_fd == fd) + break; + } + if (lp) { + if (lp == l_top) + l_top = lp->l_next; + else + pp->l_next = lp->l_next; + + free (lp->l_lock); + free (lp); + } + } + + /* if there are locks left, restart timer */ + if (l_top) + alarm (NSECS); +} + + +/* + * If timer goes off, we update the ctime of all open + * lockfiles, so another command doesn't remove them. + */ + +static RETSIGTYPE +alrmser (int sig) +{ + int j; + char *lockfile; + struct lock *lp; + +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGALRM, alrmser); +#endif + + /* update the ctime of all the lock files */ + for (lp = l_top; lp; lp = lp->l_next) { + lockfile = lp->l_lock; + if (*lockfile && (j = creat (lockfile, 0600)) != -1) + close (j); + } + + /* restart the alarm */ + alarm (NSECS); +} + +#endif /* DOT_LOCKING */ diff --git a/sbr/m_atoi.c b/sbr/m_atoi.c new file mode 100644 index 0000000..b1b5133 --- /dev/null +++ b/sbr/m_atoi.c @@ -0,0 +1,32 @@ + +/* + * m_atoi.c -- Parse a string representation of a message number, and + * -- return the numeric value of the message. If the string + * -- contains any non-digit characters, then return 0. + * + * $Id$ + */ + +#include + + +int +m_atoi (char *str) +{ + int i; + char *cp; + + for (i = 0, cp = str; *cp; cp++) { +#ifdef LOCALE + if (!isdigit(*cp)) +#else + if (*cp < '0' || *cp > '9') +#endif + return 0; + + i *= 10; + i += (*cp - '0'); + } + + return i; +} diff --git a/sbr/m_backup.c b/sbr/m_backup.c new file mode 100644 index 0000000..aa9edc8 --- /dev/null +++ b/sbr/m_backup.c @@ -0,0 +1,26 @@ + +/* + * m_backup.c -- construct a backup file + * + * $Id$ + */ + +#include + + +char * +m_backup (char *file) +{ + char *cp; + static char buffer[BUFSIZ]; + + if ((cp = r1bindex(file, '/')) == file) + snprintf(buffer, sizeof(buffer), "%s%s", + BACKUP_PREFIX, cp); + else + snprintf(buffer, sizeof(buffer), "%.*s%s%s", cp - file, file, + BACKUP_PREFIX, cp); + + unlink(buffer); + return buffer; +} diff --git a/sbr/m_convert.c b/sbr/m_convert.c new file mode 100644 index 0000000..e9a5d18 --- /dev/null +++ b/sbr/m_convert.c @@ -0,0 +1,443 @@ + +/* + * m_convert.c -- parse a message range or sequence and set SELECTED + * + * $Id$ + */ + +#include + +/* + * error codes for sequence + * and message range processing + */ +#define BADMSG (-2) +#define BADRNG (-3) +#define BADNEW (-4) +#define BADNUM (-5) +#define BADLST (-6) + +#define FIRST 1 +#define LAST 2 + +#define getnew(mp) (mp->hghmsg + 1) + +static int convdir; /* convert direction */ +static char *delimp; + +/* + * static prototypes + */ +static int m_conv (struct msgs *, char *, int); +static int attr (struct msgs *, char *); + + +int +m_convert (struct msgs *mp, char *name) +{ + int first, last, found, range, err; + char *bp, *cp; + + /* check if user defined sequence */ + err = attr (mp, cp = name); + + if (err == -1) + return 0; + else if (err < 0) + goto badmsg; + else if (err > 0) + return 1; + /* + * else err == 0, so continue + */ + + found = 0; + + /* + * Check for special "new" sequence, which + * is valid only if ALLOW_NEW is set. + */ + if ((mp->msgflags & ALLOW_NEW) && !strcmp (cp, "new")) { + if ((err = first = getnew (mp)) <= 0) + goto badmsg; + else + goto single; + } + + if (!strcmp (cp, "all")) + cp = "first-last"; + + if ((err = first = m_conv (mp, cp, FIRST)) <= 0) + goto badmsg; + + cp = delimp; + if (*cp != '\0' && *cp != '-' && *cp != ':') { +badelim: + advise (NULL, "illegal argument delimiter: `%c'(0%o)", *delimp, *delimp); + return 0; + } + + if (*cp == '-') { + cp++; + if ((err = last = m_conv (mp, cp, LAST)) <= 0) { +badmsg: + switch (err) { + case BADMSG: + advise (NULL, "no %s message", cp); + break; + + case BADNUM: + advise (NULL, "message %s doesn't exist", cp); + break; + + case BADRNG: + advise (NULL, "message %s out of range 1-%d", cp, mp->hghmsg); + break; + + case BADLST: +badlist: + advise (NULL, "bad message list %s", name); + break; + + case BADNEW: + advise (NULL, "folder full, no %s message", name); + break; + + default: + advise (NULL, "no messages match specification"); + } + return 0; + } + + if (last < first) + goto badlist; + if (*delimp) + goto badelim; + if (first > mp->hghmsg || last < mp->lowmsg) { +rangerr: + advise (NULL, "no messages in range %s", name); + return 0; + } + + /* tighten the range to search */ + if (last > mp->hghmsg) + last = mp->hghmsg; + if (first < mp->lowmsg) + first = mp->lowmsg; + + } else if (*cp == ':') { + cp++; + if (*cp == '-') { + convdir = -1; + cp++; + } else { + if (*cp == '+') { + convdir = 1; + cp++; + } + } + if ((range = atoi (bp = cp)) == 0) + goto badlist; + while (isdigit (*bp)) + bp++; + if (*bp) + goto badelim; + if ((convdir > 0 && first > mp->hghmsg) + || (convdir < 0 && first < mp->lowmsg)) + goto rangerr; + + /* tighten the range to search */ + if (first < mp->lowmsg) + first = mp->lowmsg; + if (first > mp->hghmsg) + first = mp->hghmsg; + + for (last = first; + last >= mp->lowmsg && last <= mp->hghmsg; + last += convdir) + if (does_exist (mp, last)) + if (--range <= 0) + break; + if (last < mp->lowmsg) + last = mp->lowmsg; + if (last > mp->hghmsg) + last = mp->hghmsg; + if (last < first) { + range = last; + last = first; + first = range; + } + } else { + +single: + /* + * Single Message + * + * If ALLOW_NEW is set, then allow selecting of an + * empty slot. If ALLOW_NEW is not set, then we + * check if message is in-range and exists. + */ + if (mp->msgflags & ALLOW_NEW) { + set_select_empty (mp, first); + } else { + if (first > mp->hghmsg + || first < mp->lowmsg + || !(does_exist (mp, first))) { + if (!strcmp (name, "cur") || !strcmp (name, ".")) + advise (NULL, "no %s message", name); + else + advise (NULL, "message %d doesn't exist", first); + return 0; + } + } + last = first; /* range of 1 */ + } + + /* + * Cycle through the range and select the messages + * that exist. If ALLOW_NEW is set, then we also check + * if we are selecting an empty slot. + */ + for (; first <= last; first++) { + if (does_exist (mp, first) || + ((mp->msgflags & ALLOW_NEW) && is_select_empty (mp, first))) { + if (!is_selected (mp, first)) { + set_selected (mp, first); + mp->numsel++; + if (mp->lowsel == 0 || first < mp->lowsel) + mp->lowsel = first; + if (first > mp->hghsel) + mp->hghsel = first; + } + found++; + } + } + + if (!found) + goto rangerr; + + return 1; +} + +/* + * Convert the various message names to + * there numeric value. + * + * n (integer) + * prev + * next + * first + * last + * cur + * . (same as cur) + */ + +static int +m_conv (struct msgs *mp, char *str, int call) +{ + register int i; + register char *cp, *bp; + char buf[16]; + + convdir = 1; + cp = bp = str; + if (isdigit (*cp)) { + while (isdigit (*bp)) + bp++; + delimp = bp; + i = atoi (cp); + + if (i <= mp->hghmsg) + return i; + else if (*delimp || call == LAST) + return mp->hghmsg + 1; + else if (mp->msgflags & ALLOW_NEW) + return BADRNG; + else + return BADNUM; + } + +#ifdef LOCALE + /* doesn't enforce lower case */ + for (bp = buf; (isalpha(*cp) || *cp == '.') + && (bp - buf < sizeof(buf) - 1); ) +#else + for (bp = buf; ((*cp >= 'a' && *cp <= 'z') || *cp == '.') + && (bp - buf < sizeof(buf) - 1); ) +#endif /* LOCALE */ + { + *bp++ = *cp++; + } + *bp++ = '\0'; + delimp = cp; + + if (!strcmp (buf, "first")) + return (mp->hghmsg || !(mp->msgflags & ALLOW_NEW) + ? mp->lowmsg : BADMSG); + + if (!strcmp (buf, "last")) { + convdir = -1; + return (mp->hghmsg || !(mp->msgflags & ALLOW_NEW) ? mp->hghmsg : BADMSG); + } + + if (!strcmp (buf, "cur") || !strcmp (buf, ".")) + return (mp->curmsg > 0 ? mp->curmsg : BADMSG); + + if (!strcmp (buf, "prev")) { + convdir = -1; + for (i = (mp->curmsg <= mp->hghmsg) ? mp->curmsg - 1 : mp->hghmsg; + i >= mp->lowmsg; i--) { + if (does_exist (mp, i)) + return i; + } + return BADMSG; + } + + if (!strcmp (buf, "next")) { + for (i = (mp->curmsg >= mp->lowmsg) ? mp->curmsg + 1 : mp->lowmsg; + i <= mp->hghmsg; i++) { + if (does_exist (mp, i)) + return i; + } + return BADMSG; + } + + return BADLST; +} + +/* + * Handle user defined sequences. + * They can take the following forms: + * + * seq + * seq:prev + * seq:next + * seq:first + * seq:last + * seq:+n + * seq:-n + * seq:n + */ + +static int +attr (struct msgs *mp, char *cp) +{ + register char *dp; + char *bp = NULL; + register int i, j; + int found, + inverted = 0, + range = 0, /* no range */ + first = 0; + + /* hack for "cur-name", "cur-n", etc. */ + if (!strcmp (cp, "cur")) + return 0; + if (ssequal ("cur:", cp)) /* this code need to be rewritten... */ + return 0; + + /* Check for sequence negation */ + if ((dp = context_find (nsequence)) && *dp != '\0' && ssequal (dp, cp)) { + inverted = 1; + cp += strlen (dp); + } + + convdir = 1; /* convert direction */ + + for (dp = cp; *dp && isalnum(*dp); dp++) + continue; + + if (*dp == ':') { + bp = dp++; + range = 1; + + /* + * seq:prev (or) + * seq:next (or) + * seq:first (or) + * seq:last + */ + if (isalpha (*dp)) { + if (!strcmp (dp, "prev")) { + convdir = -1; + first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg) + ? mp->curmsg - 1 + : mp->hghmsg; + } + else if (!strcmp (dp, "next")) { + convdir = 1; + first = (mp->curmsg >= mp->lowmsg) + ? mp->curmsg + 1 + : mp->lowmsg; + } + else if (!strcmp (dp, "first")) { + convdir = 1; + } + else if (!strcmp (dp, "last")) { + convdir = -1; + } + else + return BADLST; + } else { + /* + * seq:n (or) + * seq:+n (or) + * seq:-n + */ + if (*dp == '+') + dp++; + else if (*dp == '-') { + dp++; + convdir = -1; + } + if ((range = atoi(dp)) == 0) + return BADLST; + while (isdigit (*dp)) + dp++; + if (*dp) + return BADLST; + } + + *bp = '\0'; /* temporarily terminate sequence name */ + } + + i = seq_getnum (mp, cp); /* get index of sequence */ + + if (bp) + *bp = ':'; /* restore sequence name */ + if (i == -1) + return 0; + + found = 0; /* count the number we select for this argument */ + + for (j = first ? first : (convdir > 0) ? mp->lowmsg : mp->hghmsg; + j >= mp->lowmsg && j <= mp->hghmsg; j += convdir) { + if (does_exist (mp, j) + && inverted ? !in_sequence (mp, i, j) : in_sequence (mp, i, j)) { + if (!is_selected (mp, j)) { + set_selected (mp, j); + mp->numsel++; + if (mp->lowsel == 0 || j < mp->lowsel) + mp->lowsel = j; + if (j > mp->hghsel) + mp->hghsel = j; + } + found++; + + /* + * If we have a range, then break out + * once we've found enough. + */ + if (range && found >= range) + break; + } + } + + if (found > 0) + return found; + + if (first) + return BADMSG; + advise (NULL, "sequence %s %s", cp, inverted ? "full" : "empty"); + return -1; +} diff --git a/sbr/m_draft.c b/sbr/m_draft.c new file mode 100644 index 0000000..712e173 --- /dev/null +++ b/sbr/m_draft.c @@ -0,0 +1,88 @@ + +/* + * m_draft.c -- construct the name of a draft message + * + * $Id$ + */ + +#include +#include + +extern int errno; + + +char * +m_draft (char *folder, char *msg, int use, int *isdf) +{ + register char *cp; + register struct msgs *mp; + struct stat st; + static char buffer[BUFSIZ]; + + if (*isdf == -1 || folder == NULL || *folder == '\0') { + if (*isdf == -1 || (cp = context_find ("Draft-Folder")) == NULL) { + *isdf = 0; + return m_maildir (msg && *msg ? msg : draft); + } else { + folder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + } + } + *isdf = 1; + + chdir (m_maildir ("")); + strncpy (buffer, m_maildir (folder), sizeof(buffer)); + if (stat (buffer, &st) == -1) { + if (errno != ENOENT) + adios (buffer, "error on folder"); + cp = concat ("Create folder \"", buffer, "\"? ", NULL); + if (!getanswer (cp)) + done (0); + free (cp); + if (!makedir (buffer)) + adios (NULL, "unable to create folder %s", buffer); + } + + if (chdir (buffer) == -1) + adios (buffer, "unable to change directory to"); + + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* + * Make sure we have enough message status space for all + * the message numbers from 1 to "new", since we might + * select an empty slot. If we add more space at the + * end, go ahead and add 10 additional slots. + */ + if (mp->hghmsg >= mp->hghoff) { + if (!(mp = folder_realloc (mp, 1, mp->hghmsg + 10))) + adios (NULL, "unable to allocate folder storage"); + } else if (mp->lowoff > 1) { + if (!(mp = folder_realloc (mp, 1, mp->hghoff))) + adios (NULL, "unable to allocate folder storage"); + } + + mp->msgflags |= ALLOW_NEW; /* allow the "new" sequence */ + + /* + * If we have been give a valid message name, then use that. + * Else, if we are given the "use" option, then use the + * current message. Else, use special sequence "new". + */ + if (!m_convert (mp, msg && *msg ? msg : use ? "cur" : "new")) + done (1); + seq_setprev (mp); + + if (mp->numsel > 1) + adios (NULL, "only one message draft at a time!"); + + snprintf (buffer, sizeof(buffer), "%s/%s", mp->foldpath, m_name (mp->lowsel)); + cp = buffer; + + seq_setcur (mp, mp->lowsel);/* set current message for folder */ + seq_save (mp); /* synchronize message sequences */ + folder_free (mp); /* free folder/message structure */ + + return cp; +} diff --git a/sbr/m_getfld.c b/sbr/m_getfld.c new file mode 100644 index 0000000..8697f20 --- /dev/null +++ b/sbr/m_getfld.c @@ -0,0 +1,748 @@ + +/* + * m_getfld.c -- read/parse a message + * + * $Id$ + */ + +#include +#include + +/* This module has a long and checkered history. First, it didn't burst + maildrops correctly because it considered two CTRL-A:s in a row to be + an inter-message delimiter. It really is four CTRL-A:s followed by a + newline. Unfortunately, MMDF will convert this delimiter *inside* a + message to a CTRL-B followed by three CTRL-A:s and a newline. This + caused the old version of m_getfld() to declare eom prematurely. The + fix was a lot slower than + + c == '\001' && peekc (iob) == '\001' + + but it worked, and to increase generality, MBOX style maildrops could + be parsed as well. Unfortunately the speed issue finally caught up with + us since this routine is at the very heart of MH. + + To speed things up considerably, the routine Eom() was made an auxilary + function called by the macro eom(). Unless we are bursting a maildrop, + the eom() macro returns FALSE saying we aren't at the end of the + message. + + The next thing to do is to read the mts.conf file and initialize + delimiter[] and delimlen accordingly... + + After mhl was made a built-in in msh, m_getfld() worked just fine + (using m_unknown() at startup). Until one day: a message which was + the result of a bursting was shown. Then, since the burst boundaries + aren't CTRL-A:s, m_getfld() would blinding plunge on past the boundary. + Very sad. The solution: introduce m_eomsbr(). This hook gets called + after the end of each line (since testing for eom involves an fseek()). + This worked fine, until one day: a message with no body portion arrived. + Then the + + while (eom (c = Getc (iob), iob)) + continue; + + loop caused m_getfld() to return FMTERR. So, that logic was changed to + check for (*eom_action) and act accordingly. + + This worked fine, until one day: someone didn't use four CTRL:A's as + their delimiters. So, the bullet got bit and we read mts.h and + continue to struggle on. It's not that bad though, since the only time + the code gets executed is when inc (or msh) calls it, and both of these + have already called mts_init(). + + ------------------------ + (Written by Van Jacobson for the mh6 m_getfld, January, 1986): + + This routine was accounting for 60% of the cpu time used by most mh + programs. I spent a bit of time tuning and it now accounts for <10% + of the time used. Like any heavily tuned routine, it's a bit + complex and you want to be sure you understand everything that it's + doing before you start hacking on it. Let me try to emphasize + that: every line in this atrocity depends on every other line, + sometimes in subtle ways. You should understand it all, in detail, + before trying to change any part. If you do change it, test the + result thoroughly (I use a hand-constructed test file that exercises + all the ways a header name, header body, header continuation, + header-body separator, body line and body eom can align themselves + with respect to a buffer boundary). "Minor" bugs in this routine + result in garbaged or lost mail. + + If you hack on this and slow it down, I, my children and my + children's children will curse you. + + This routine gets used on three different types of files: normal, + single msg files, "packed" unix or mmdf mailboxs (when used by inc) + and packed, directoried bulletin board files (when used by msh). + The biggest impact of different file types is in "eom" testing. The + code has been carefully organized to test for eom at appropriate + times and at no other times (since the check is quite expensive). + I have tried to arrange things so that the eom check need only be + done on entry to this routine. Since an eom can only occur after a + newline, this is easy to manage for header fields. For the msg + body, we try to efficiently search the input buffer to see if + contains the eom delimiter. If it does, we take up to the + delimiter, otherwise we take everything in the buffer. (The change + to the body eom/copy processing produced the most noticeable + performance difference, particularly for "inc" and "show".) + + There are three qualitatively different things this routine busts + out of a message: field names, field text and msg bodies. Field + names are typically short (~8 char) and the loop that extracts them + might terminate on a colon, newline or max width. I considered + using a Vax "scanc" to locate the end of the field followed by a + "bcopy" but the routine call overhead on a Vax is too large for this + to work on short names. If Berkeley ever makes "inline" part of the + C optimiser (so things like "scanc" turn into inline instructions) a + change here would be worthwhile. + + Field text is typically 60 - 100 characters so there's (barely) + a win in doing a routine call to something that does a "locc" + followed by a "bmove". About 30% of the fields have continuations + (usually the 822 "received:" lines) and each continuation generates + another routine call. "Inline" would be a big win here, as well. + + Messages, as of this writing, seem to come in two flavors: small + (~1K) and long (>2K). Most messages have 400 - 600 bytes of headers + so message bodies average at least a few hundred characters. + Assuming your system uses reasonably sized stdio buffers (1K or + more), this routine should be able to remove the body in large + (>500 byte) chunks. The makes the cost of a call to "bcopy" + small but there is a premium on checking for the eom in packed + maildrops. The eom pattern is always a simple string so we can + construct an efficient pattern matcher for it (e.g., a Vax "matchc" + instruction). Some thought went into recognizing the start of + an eom that has been split across two buffers. + + This routine wants to deal with large chunks of data so, rather + than "getc" into a local buffer, it uses stdio's buffer. If + you try to use it on a non-buffered file, you'll get what you + deserve. This routine "knows" that struct FILEs have a _ptr + and a _cnt to describe the current state of the buffer and + it knows that _filbuf ignores the _ptr & _cnt and simply fills + the buffer. If stdio on your system doesn't work this way, you + may have to make small changes in this routine. + + This routine also "knows" that an EOF indication on a stream is + "sticky" (i.e., you will keep getting EOF until you reposition the + stream). If your system doesn't work this way it is broken and you + should complain to the vendor. As a consequence of the sticky + EOF, this routine will never return any kind of EOF status when + there is data in "name" or "buf"). + */ + + +/* + * static prototypes + */ +static int m_Eom (int, FILE *); +static unsigned char *matchc(int, char *, int, char *); +static unsigned char *locc(int, unsigned char *, unsigned char); + +#define Getc(iob) getc(iob) +#define eom(c,iob) (msg_style != MS_DEFAULT && \ + (((c) == *msg_delim && m_Eom(c,iob)) ||\ + (eom_action && (*eom_action)(c)))) + +static unsigned char **pat_map; + +/* + * defined in sbr/m_msgdef.c = 0 + * This is a disgusting hack for "inc" so it can know how many + * characters were stuffed in the buffer on the last call + * (see comments in uip/scansbr.c). + */ +extern int msg_count; + +/* + * defined in sbr/m_msgdef.c = MS_DEFAULT + */ +extern int msg_style; + +/* + * The "full" delimiter string for a packed maildrop consists + * of a newline followed by the actual delimiter. E.g., the + * full string for a Unix maildrop would be: "\n\nFrom ". + * "Fdelim" points to the start of the full string and is used + * in the BODY case of the main routine to search the buffer for + * a possible eom. Msg_delim points to the first character of + * the actual delim. string (i.e., fdelim+1). Edelim + * points to the 2nd character of actual delimiter string. It + * is used in m_Eom because the first character of the string + * has been read and matched before m_Eom is called. + */ +extern char *msg_delim; /* defined in sbr/m_msgdef.c = "" */ +static unsigned char *fdelim; +static unsigned char *delimend; +static int fdelimlen; +static unsigned char *edelim; +static int edelimlen; + +static int (*eom_action)() = NULL; + +#ifdef _FSTDIO +# define _ptr _p /* Gag */ +# define _cnt _r /* Retch */ +# define _filbuf __srget /* Puke */ +#endif + +#ifdef SCO_5_STDIO +# define _ptr __ptr +# define _cnt __cnt +# define _base __base +# define _filbuf(fp) ((fp)->__cnt = 0, __filbuf(fp)) +#endif + + +int +m_getfld (int state, unsigned char *name, unsigned char *buf, + int bufsz, FILE *iob) +{ + register unsigned char *bp, *cp, *ep, *sp; + register int cnt, c, i, j; + + if ((c = Getc(iob)) < 0) { + msg_count = 0; + *buf = 0; + return FILEEOF; + } + if (eom (c, iob)) { + if (! eom_action) { + /* flush null messages */ + while ((c = Getc(iob)) >= 0 && eom (c, iob)) + ; + if (c >= 0) + ungetc(c, iob); + } + msg_count = 0; + *buf = 0; + return FILEEOF; + } + + switch (state) { + case FLDEOF: + case BODYEOF: + case FLD: + if (c == '\n' || c == '-') { + /* we hit the header/body separator */ + while (c != '\n' && (c = Getc(iob)) >= 0) + ; + + if (c < 0 || (c = Getc(iob)) < 0 || eom (c, iob)) { + if (! eom_action) { + /* flush null messages */ + while ((c = Getc(iob)) >= 0 && eom (c, iob)) + ; + if (c >= 0) + ungetc(c, iob); + } + msg_count = 0; + *buf = 0; + return FILEEOF; + } + state = BODY; + goto body; + } + /* + * get the name of this component. take characters up + * to a ':', a newline or NAMESZ-1 characters, whichever + * comes first. + */ + cp = name; + i = NAMESZ - 1; + for (;;) { +#ifdef LINUX_STDIO + bp = sp = (unsigned char *) iob->_IO_read_ptr - 1; + j = (cnt = ((long) iob->_IO_read_end - + (long) iob->_IO_read_ptr) + 1) < i ? cnt : i; +#else + bp = sp = (unsigned char *) iob->_ptr - 1; + j = (cnt = iob->_cnt+1) < i ? cnt : i; +#endif + while ((c = *bp++) != ':' && c != '\n' && --j >= 0) + *cp++ = c; + + j = bp - sp; + if ((cnt -= j) <= 0) { +#ifdef LINUX_STDIO + iob->_IO_read_ptr = iob->_IO_read_end; + if (__underflow(iob) == EOF) { +#else + if (_filbuf(iob) == EOF) { +#endif + *cp = *buf = 0; + advise (NULL, "eof encountered in field \"%s\"", name); + return FMTERR; + } +#ifdef LINUX_STDIO + iob->_IO_read_ptr++; /* NOT automatic in __underflow()! */ +#endif + } else { +#ifdef LINUX_STDIO + iob->_IO_read_ptr = bp + 1; +#else + iob->_ptr = bp + 1; + iob->_cnt = cnt - 1; +#endif + } + if (c == ':') + break; + + /* + * something went wrong. possibilities are: + * . hit a newline (error) + * . got more than namesz chars. (error) + * . hit the end of the buffer. (loop) + */ + if (c == '\n') { + *cp = *buf = 0; + advise (NULL, "eol encountered in field \"%s\"", name); + state = FMTERR; + goto finish; + } + if ((i -= j) <= 0) { + *cp = *buf = 0; + advise (NULL, "field name \"%s\" exceeds %d bytes", name, NAMESZ - 1); + state = LENERR; + goto finish; + } + } + + while (isspace (*--cp) && cp >= name) + ; + *++cp = 0; + /* fall through */ + + case FLDPLUS: + /* + * get (more of) the text of a field. take + * characters up to the end of this field (newline + * followed by non-blank) or bufsz-1 characters. + */ + cp = buf; i = bufsz-1; + for (;;) { +#ifdef LINUX_STDIO + cnt = (long) iob->_IO_read_end - (long) iob->_IO_read_ptr; + bp = (unsigned char *) --iob->_IO_read_ptr; +#else + cnt = iob->_cnt++; + bp = (unsigned char *) --iob->_ptr; +#endif + c = cnt < i ? cnt : i; + while ((ep = locc( c, bp, '\n' ))) { + /* + * if we hit the end of this field, return. + */ + if ((j = *++ep) != ' ' && j != '\t') { +#ifdef LINUX_STDIO + j = ep - (unsigned char *) iob->_IO_read_ptr; + memcpy (cp, iob->_IO_read_ptr, j); + iob->_IO_read_ptr = ep; +#else + j = ep - (unsigned char *) iob->_ptr; + memcpy (cp, iob->_ptr, j); + iob->_ptr = ep; + iob->_cnt -= j; +#endif + cp += j; + state = FLD; + goto finish; + } + c -= ep - bp; + bp = ep; + } + /* + * end of input or dest buffer - copy what we've found. + */ +#ifdef LINUX_STDIO + c += bp - (unsigned char *) iob->_IO_read_ptr; + memcpy( cp, iob->_IO_read_ptr, c); +#else + c += bp - (unsigned char *) iob->_ptr; + memcpy( cp, iob->_ptr, c); +#endif + i -= c; + cp += c; + if (i <= 0) { + /* the dest buffer is full */ +#ifdef LINUX_STDIO + iob->_IO_read_ptr += c; +#else + iob->_cnt -= c; + iob->_ptr += c; +#endif + state = FLDPLUS; + break; + } + /* + * There's one character left in the input buffer. + * Copy it & fill the buffer. If the last char + * was a newline and the next char is not whitespace, + * this is the end of the field. Otherwise loop. + */ + --i; +#ifdef LINUX_STDIO + *cp++ = j = *(iob->_IO_read_ptr + c); + iob->_IO_read_ptr = iob->_IO_read_end; + c = __underflow(iob); + iob->_IO_read_ptr++; /* NOT automatic! */ +#else + *cp++ = j = *(iob->_ptr + c); + c = _filbuf(iob); +#endif + if ((j == '\0' || j == '\n') && c != ' ' && c != '\t') { + if (c != EOF) { +#ifdef LINUX_STDIO + --iob->_IO_read_ptr; +#else + --iob->_ptr; + ++iob->_cnt; +#endif + } + state = FLD; + break; + } + } + break; + + case BODY: + body: + /* + * get the message body up to bufsz characters or the + * end of the message. Sleazy hack: if bufsz is negative + * we assume that we were called to copy directly into + * the output buffer and we don't add an eos. + */ + i = (bufsz < 0) ? -bufsz : bufsz-1; +#ifdef LINUX_STDIO + bp = (unsigned char *) --iob->_IO_read_ptr; + cnt = (long) iob->_IO_read_end - (long) iob->_IO_read_ptr; +#else + bp = (unsigned char *) --iob->_ptr; + cnt = ++iob->_cnt; +#endif + c = (cnt < i ? cnt : i); + if (msg_style != MS_DEFAULT && c > 1) { + /* + * packed maildrop - only take up to the (possible) + * start of the next message. This "matchc" should + * probably be a Boyer-Moore matcher for non-vaxen, + * particularly since we have the alignment table + * all built for the end-of-buffer test (next). + * But our vax timings indicate that the "matchc" + * instruction is 50% faster than a carefully coded + * B.M. matcher for most strings. (So much for elegant + * algorithms vs. brute force.) Since I (currently) + * run MH on a vax, we use the matchc instruction. --vj + */ + if ((ep = matchc( fdelimlen, fdelim, c, bp ))) + c = ep - bp + 1; + else { + /* + * There's no delim in the buffer but there may be + * a partial one at the end. If so, we want to leave + * it so the "eom" check on the next call picks it up. + * Use a modified Boyer-Moore matcher to make this + * check relatively cheap. The first "if" figures + * out what position in the pattern matches the last + * character in the buffer. The inner "while" matches + * the pattern against the buffer, backwards starting + * at that position. Note that unless the buffer + * ends with one of the characters in the pattern + * (excluding the first and last), we do only one test. + */ + ep = bp + c - 1; + if ((sp = pat_map[*ep])) { + do { + cp = sp; + while (*--ep == *--cp) + ; + if (cp < fdelim) { + if (ep >= bp) + /* + * ep < bp means that all the buffer + * contains is a prefix of delim. + * If this prefix is really a delim, the + * m_eom call at entry should have found + * it. Thus it's not a delim and we can + * take all of it. + */ + c = (ep - bp) + 2; + break; + } + /* try matching one less char of delim string */ + ep = bp + c - 1; + } while (--sp > fdelim); + } + } + } + memcpy( buf, bp, c ); +#ifdef LINUX_STDIO + iob->_IO_read_ptr += c; +#else + iob->_cnt -= c; + iob->_ptr += c; +#endif + if (bufsz < 0) { + msg_count = c; + return (state); + } + cp = buf + c; + break; + + default: + adios (NULL, "m_getfld() called with bogus state of %d", state); + } +finish: + *cp = 0; + msg_count = cp - buf; + return (state); +} + + +#ifdef RPATHS +static char unixbuf[BUFSIZ] = ""; +#endif /* RPATHS */ + +void +m_unknown(FILE *iob) +{ + register int c; + register long pos; + char text[10]; + register char *cp; + register char *delimstr; + +/* + * Figure out what the message delimitter string is for this + * maildrop. (This used to be part of m_Eom but I didn't like + * the idea of an "if" statement that could only succeed on the + * first call to m_Eom getting executed on each call, i.e., at + * every newline in the message). + * + * If the first line of the maildrop is a Unix "From " line, we + * say the style is MBOX and eat the rest of the line. Otherwise + * we say the style is MMDF and look for the delimiter string + * specified when nmh was built (or from the mts.conf file). + */ + + msg_style = MS_UNKNOWN; + + pos = ftell (iob); + if (fread (text, sizeof(*text), 5, iob) == 5 + && strncmp (text, "From ", 5) == 0) { + msg_style = MS_MBOX; + delimstr = "\nFrom "; +#ifndef RPATHS + while ((c = getc (iob)) != '\n' && c >= 0) + ; +#else /* RPATHS */ + cp = unixbuf; + while ((c = getc (iob)) != '\n') + *cp++ = c; + *cp = 0; +#endif /* RPATHS */ + } else { + /* not a Unix style maildrop */ + fseek (iob, pos, SEEK_SET); + if (mmdlm2 == NULL || *mmdlm2 == 0) + mmdlm2 = "\001\001\001\001\n"; + delimstr = mmdlm2; + msg_style = MS_MMDF; + } + c = strlen (delimstr); + fdelim = (unsigned char *) malloc((size_t) (c + 3)); + *fdelim++ = '\0'; + *fdelim = '\n'; + msg_delim = (char *)fdelim+1; + edelim = (unsigned char *)msg_delim+1; + fdelimlen = c + 1; + edelimlen = c - 1; + strcpy (msg_delim, delimstr); + delimend = (unsigned char *)msg_delim + edelimlen; + if (edelimlen <= 1) + adios (NULL, "maildrop delimiter must be at least 2 bytes"); + /* + * build a Boyer-Moore end-position map for the matcher in m_getfld. + * N.B. - we don't match just the first char (since it's the newline + * separator) or the last char (since the matchc would have found it + * if it was a real delim). + */ + pat_map = (unsigned char **) calloc (256, sizeof(unsigned char *)); + + for (cp = (char *) fdelim + 1; cp < (char *) delimend; cp++ ) + pat_map[*cp] = (unsigned char *) cp; + + if (msg_style == MS_MMDF) { + /* flush extra msg hdrs */ + while ((c = Getc(iob)) >= 0 && eom (c, iob)) + ; + if (c >= 0) + ungetc(c, iob); + } +} + + +void +m_eomsbr (int (*action)()) +{ + if ((eom_action = action)) { + msg_style = MS_MSH; + *msg_delim = 0; + fdelimlen = 1; + delimend = fdelim; + } else { + msg_style = MS_MMDF; + msg_delim = (char *)fdelim + 1; + fdelimlen = strlen((char *)fdelim); + delimend = (unsigned char *)(msg_delim + edelimlen); + } +} + + +/* + * test for msg delimiter string + */ + +static int +m_Eom (int c, FILE *iob) +{ + register long pos = 0L; + register int i; + char text[10]; +#ifdef RPATHS + register char *cp; +#endif /* RPATHS */ + + pos = ftell (iob); + if ((i = fread (text, sizeof *text, edelimlen, iob)) != edelimlen + || strncmp (text, (char *)edelim, edelimlen)) { + if (i == 0 && msg_style == MS_MBOX) + /* the final newline in the (brain damaged) unix-format + * maildrop is part of the delimitter - delete it. + */ + return 1; + +#if 0 + fseek (iob, pos, SEEK_SET); +#endif + + fseek (iob, (long)(pos-1), SEEK_SET); + getc (iob); /* should be OK */ + return 0; + } + + if (msg_style == MS_MBOX) { +#ifndef RPATHS + while ((c = getc (iob)) != '\n') + if (c < 0) + break; +#else /* RPATHS */ + cp = unixbuf; + while ((c = getc (iob)) != '\n' && c >= 0) + *cp++ = c; + *cp = 0; +#endif /* RPATHS */ + } + + return 1; +} + + +#ifdef RPATHS +/* + * Return the Return-Path and Delivery-Date + * header information. + * + * Currently, I'm assuming that the "From " line + * takes one of the following forms. + * + * From sender date remote from host (for UUCP delivery) + * From sender@host date (for sendmail delivery) + */ + +int +get_returnpath (char *rp, int rplen, char *dd, int ddlen) +{ + char *ap, *bp, *cp, *dp; + + ap = unixbuf; + if (!(bp = cp = strchr(ap, ' '))) + return 0; + + /* + * Check for "remote from" in envelope to see + * if this message uses UUCP style addressing + */ + while ((cp = strchr(++cp, 'r'))) { + if (strncmp (cp, "remote from", 11) == 0) { + cp = strrchr (cp, ' '); + break; + } + } + + /* + * Get the Return-Path information from + * the "From " envelope. + */ + if (cp) { + /* return path for UUCP style addressing */ + dp = strchr (++cp, '\n'); + snprintf (rp, rplen, "%.*s!%.*s\n", dp - cp, cp, bp - ap, ap); + } else { + /* return path for standard domain addressing */ + snprintf (rp, rplen, "%.*s\n", bp - ap, ap); + } + + /* + * advance over the spaces to get to + * delivery date on envelope + */ + while (*bp == ' ') + bp++; + + /* Now get delivery date from envelope */ + snprintf (dd, ddlen, "%.*s\n", 24, bp); + + unixbuf[0] = 0; + return 1; +} +#endif /* RPATHS */ + + +static unsigned char * +matchc(int patln, char *pat, int strln, char *str) +{ + register char *es = str + strln - patln; + register char *sp; + register char *pp; + register char *ep = pat + patln; + register char pc = *pat++; + + for(;;) { + while (pc != *str++) + if (str > es) + return 0; + + sp = str; pp = pat; + while (pp < ep && *sp++ == *pp) + pp++; + if (pp >= ep) + return ((unsigned char *)--str); + } +} + + +/* + * Locate character "term" in the next "cnt" characters of "src". + * If found, return its address, otherwise return 0. + */ + +static unsigned char * +locc(int cnt, unsigned char *src, unsigned char term) +{ + while (*src++ != term && --cnt > 0); + + return (cnt > 0 ? --src : (unsigned char *)0); +} + diff --git a/sbr/m_gmprot.c b/sbr/m_gmprot.c new file mode 100644 index 0000000..3f54c45 --- /dev/null +++ b/sbr/m_gmprot.c @@ -0,0 +1,17 @@ + +/* + * m_gmprot.c -- return the msg-protect value + * + * $Id$ + */ + +#include + + +int +m_gmprot (void) +{ + register char *cp; + + return atooi ((cp = context_find ("msg-protect")) && *cp ? cp : msgprot); +} diff --git a/sbr/m_maildir.c b/sbr/m_maildir.c new file mode 100644 index 0000000..11c6ee3 --- /dev/null +++ b/sbr/m_maildir.c @@ -0,0 +1,94 @@ + +/* + * m_maildir.c -- get the path for the mail directory + * + * $Id$ + */ + +#include + +#define CWD "./" +#define NCWD (sizeof(CWD) - 1) +#define DOT "." +#define DOTDOT ".." +#define PWD "../" +#define NPWD (sizeof(PWD) - 1) + +static char mailfold[BUFSIZ]; + +/* + * static prototypes + */ +static char *exmaildir (char *); + + +char * +m_maildir (char *folder) +{ + register char *cp, *ep; + + if ((cp = exmaildir (folder)) + && (ep = cp + strlen (cp) - 1) > cp + && *ep == '/') + *ep = '\0'; + + return cp; +} + + +char * +m_mailpath (char *folder) +{ + register char *cp; + char maildir[BUFSIZ]; + + if (*folder != '/' + && strncmp (folder, CWD, NCWD) + && strcmp (folder, DOT) + && strcmp (folder, DOTDOT) + && strncmp (folder, PWD, NPWD)) { + strncpy (maildir, mailfold, sizeof(maildir)); /* preserve... */ + cp = getcpy (m_maildir (folder)); + strncpy (mailfold, maildir, sizeof(mailfold)); + } else { + cp = path (folder, TFOLDER); + } + + return cp; +} + + +static char * +exmaildir (char *folder) +{ + register char *cp, *pp; + + /* use current folder if none is specified */ + if (folder == NULL) + folder = getfolder(1); + + if (!(*folder != '/' + && strncmp (folder, CWD, NCWD) + && strcmp (folder, DOT) + && strcmp (folder, DOTDOT) + && strncmp (folder, PWD, NPWD))) { + strncpy (mailfold, folder, sizeof(mailfold)); + return mailfold; + } + + cp = mailfold; + if ((pp = context_find ("path")) && *pp) { + if (*pp != '/') { + sprintf (cp, "%s/", mypath); + cp += strlen (cp); + } + cp = copy (pp, cp); + } else { + cp = copy (path ("./", TFOLDER), cp); + } + if (cp[-1] != '/') + *cp++ = '/'; + strcpy (cp, folder); + + return mailfold; +} diff --git a/sbr/m_msgdef.c b/sbr/m_msgdef.c new file mode 100644 index 0000000..89e0f29 --- /dev/null +++ b/sbr/m_msgdef.c @@ -0,0 +1,31 @@ + +/* + * m_msgdef.c -- some defines for sbr/m_getfld.c + * + * $Id$ + */ + +#include + +/* + * disgusting hack for "inc" so it can know how many characters + * were stuffed in the buffer on the last call (see comments + * in uip/scansbr.c) + */ +int msg_count = 0; + +int msg_style = MS_DEFAULT; + +/* + * The "full" delimiter string for a packed maildrop consists + * of a newline followed by the actual delimiter. E.g., the + * full string for a Unix maildrop would be: "\n\nFrom ". + * "Fdelim" points to the start of the full string and is used + * in the BODY case of the main routine to search the buffer for + * a possible eom. Msg_delim points to the first character of + * the actual delim. string (i.e., fdelim+1). Edelim + * points to the 2nd character of actual delimiter string. It + * is used in m_Eom because the first character of the string + * has been read and matched before m_Eom is called. + */ +char *msg_delim = ""; diff --git a/sbr/m_name.c b/sbr/m_name.c new file mode 100644 index 0000000..dd8303e --- /dev/null +++ b/sbr/m_name.c @@ -0,0 +1,21 @@ + +/* + * m_name.c -- return a message number as a string + * + * $Id$ + */ + +#include + +static char name[BUFSIZ]; + + +char * +m_name (int num) +{ + if (num <= 0) + return "?"; + + snprintf (name, sizeof(name), "%d", num); + return name; +} diff --git a/sbr/m_scratch.c b/sbr/m_scratch.c new file mode 100644 index 0000000..123a498 --- /dev/null +++ b/sbr/m_scratch.c @@ -0,0 +1,26 @@ + +/* + * m_scratch.c -- construct a scratch file + * + * $Id$ + */ + +#include + + +char * +m_scratch (char *file, char *template) +{ + char *cp; + static char buffer[BUFSIZ], tmpfil[BUFSIZ]; + + snprintf (tmpfil, sizeof(tmpfil), "%sXXXXXX", template); + mktemp (tmpfil); + if ((cp = r1bindex (file, '/')) == file) + strncpy (buffer, tmpfil, sizeof(buffer)); + else + snprintf (buffer, sizeof(buffer), "%.*s%s", cp - file, file, tmpfil); + unlink (buffer); + + return buffer; +} diff --git a/sbr/m_tmpfil.c b/sbr/m_tmpfil.c new file mode 100644 index 0000000..1b4f354 --- /dev/null +++ b/sbr/m_tmpfil.c @@ -0,0 +1,20 @@ + +/* + * m_tmpfil.c -- construct a temporary file + * + * $Id$ + */ + +#include + + +char * +m_tmpfil (char *template) +{ + static char tmpfil[BUFSIZ]; + + snprintf (tmpfil, sizeof(tmpfil), "/tmp/%sXXXXXX", template); + unlink(mktemp(tmpfil)); + + return tmpfil; +} diff --git a/sbr/makedir.c b/sbr/makedir.c new file mode 100644 index 0000000..6cf9e03 --- /dev/null +++ b/sbr/makedir.c @@ -0,0 +1,79 @@ + +/* + * makedir.c -- make a directory + * + * $Id$ + */ + +/* + * Modified to try recursive create. + */ + +#include +#include +#include +#include + +extern int errno; + +int +makedir (char *dir) +{ + pid_t pid; + register char *cp; + register char *c; + char path[PATH_MAX]; + + context_save(); /* save the context file */ + fflush(stdout); + + if (getuid () == geteuid ()) { + c = strncpy(path, dir, sizeof(path)); + + while ((c = strchr((c + 1), '/')) != NULL) { + *c = (char)0; + if (access(path, X_OK)) { + if (errno != ENOENT){ + advise (dir, "unable to create directory"); + return 0; + } + if (mkdir(path, 0775)) { + advise (dir, "unable to create directory"); + return 0; + } + } + *c = '/'; + } + + if (mkdir (dir, 0755) == -1) { + advise (dir, "unable to create directory"); + return 0; + } + } else { + switch (pid = vfork()) { + case -1: + advise ("fork", "unable to"); + return 0; + + case 0: + setgid (getgid ()); + setuid (getuid ()); + + execl ("/bin/mkdir", "mkdir", dir, NULL); + execl ("/usr/bin/mkdir", "mkdir", dir, NULL); + fprintf (stderr, "unable to exec "); + perror ("mkdir"); + _exit (-1); + + default: + if (pidXwait(pid, "mkdir")) + return 0; + break; + } + } + + if (!(cp = context_find ("folder-protect"))) + cp = foldprot; + chmod (dir, atooi (cp)); + return 1; +} diff --git a/sbr/path.c b/sbr/path.c new file mode 100644 index 0000000..df5d963 --- /dev/null +++ b/sbr/path.c @@ -0,0 +1,161 @@ + +/* + * path.c -- return a pathname + * + * $Id$ + */ + +#include + +#define CWD "./" +#define NCWD (sizeof(CWD) - 1) +#define DOT "." +#define DOTDOT ".." +#define PWD "../" +#define NPWD (sizeof(PWD) - 1) + +static char *pwds; + +/* + * static prototypes + */ +static char *expath(char *,int); +static void compath(char *); + + +char * +path(char *name, int flag) +{ + register char *cp, *ep; + + if ((cp = expath (name, flag)) + && (ep = cp + strlen (cp) - 1) > cp + && *ep == '/') + *ep = '\0'; + + return cp; +} + + +static char * +expath (char *name, int flag) +{ + register char *cp, *ep; + char buffer[BUFSIZ]; + + if (flag == TSUBCWF) { + snprintf (buffer, sizeof(buffer), "%s/%s", getfolder (1), name); + name = m_mailpath (buffer); + compath (name); + snprintf (buffer, sizeof(buffer), "%s/", m_maildir ("")); + if (ssequal (buffer, name)) { + cp = name; + name = getcpy (name + strlen (buffer)); + free (cp); + } + flag = TFOLDER; + } + + if (*name == '/' + || (flag == TFOLDER + && (strncmp (name, CWD, NCWD) + && strcmp (name, DOT) + && strcmp (name, DOTDOT) + && strncmp (name, PWD, NPWD)))) + return getcpy (name); + + if (pwds == NULL) + pwds = pwd (); + + if (strcmp (name, DOT) == 0 || strcmp (name, CWD) == 0) + return getcpy (pwds); + + ep = pwds + strlen (pwds); + if ((cp = strrchr(pwds, '/')) == NULL) + cp = ep; + else + if (cp == pwds) + cp++; + + if (strncmp (name, CWD, NCWD) == 0) + name += NCWD; + + if (strcmp (name, DOTDOT) == 0 || strcmp (name, PWD) == 0) { + snprintf (buffer, sizeof(buffer), "%.*s", cp - pwds, pwds); + return getcpy (buffer); + } + + if (strncmp (name, PWD, NPWD) == 0) + name += NPWD; + else + cp = ep; + + snprintf (buffer, sizeof(buffer), "%.*s/%s", cp - pwds, pwds, name); + return getcpy (buffer); +} + + +static void +compath (char *f) +{ + register char *cp, *dp; + + if (*f != '/') + return; + + for (cp = f; *cp;) + if (*cp == '/') { + switch (*++cp) { + case 0: + if (--cp > f) + *cp = '\0'; + break; + + case '/': + for (dp = cp; *dp == '/'; dp++) + continue; + strcpy (cp--, dp); + continue; + + case '.': + if (strcmp (cp, DOT) == 0) { + if (cp > f + 1) + cp--; + *cp = '\0'; + break; + } + if (strcmp (cp, DOTDOT) == 0) { + for (cp -= 2; cp > f; cp--) + if (*cp == '/') + break; + if (cp <= f) + cp = f + 1; + *cp = '\0'; + break; + } + if (strncmp (cp, PWD, NPWD) == 0) { + for (dp = cp - 2; dp > f; dp--) + if (*dp == '/') + break; + if (dp <= f) + dp = f; + strcpy (dp, cp + NPWD - 1); + cp = dp; + continue; + } + if (strncmp (cp, CWD, NCWD) == 0) { + strcpy (cp - 1, cp + NCWD - 1); + cp--; + continue; + } + continue; + + default: + cp++; + continue; + } + break; + } + else + cp++; +} diff --git a/sbr/peekc.c b/sbr/peekc.c new file mode 100644 index 0000000..d77039b --- /dev/null +++ b/sbr/peekc.c @@ -0,0 +1,19 @@ + +/* + * peekc.c -- peek at the next character in a stream + * + * $Id$ + */ + +#include + + +int +peekc(FILE *fp) +{ + register int c; + + c = getc(fp); + ungetc(c, fp); + return c; +} diff --git a/sbr/pidstatus.c b/sbr/pidstatus.c new file mode 100644 index 0000000..aa66464 --- /dev/null +++ b/sbr/pidstatus.c @@ -0,0 +1,63 @@ + +/* + * pidstatus.c -- report child's status + * + * $Id$ + */ + +#include + +/* + * auto-generated header + */ +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +#ifndef WTERMSIG +# define WTERMSIG(s) ((int)((s) & 0x7F)) +#endif + +#ifndef WCOREDUMP +# define WCOREDUMP(s) ((s) & 0x80) +#endif + +int +pidstatus (int status, FILE *fp, char *cp) +{ + int signum; + +/* + * I have no idea what this is for (rc) + * so I'm commenting it out for right now. + * + * if ((status & 0xff00) == 0xff00) + * return status; + */ + + /* If child process returned normally */ + if (WIFEXITED(status)) { + if ((signum = WEXITSTATUS(status))) { + if (cp) + fprintf (fp, "%s: ", cp); + fprintf (fp, "exit %d\n", signum); + } + } else if (WIFSIGNALED(status)) { + /* If child process terminated due to receipt of a signal */ + signum = WTERMSIG(status); + if (signum != SIGINT) { + if (cp) + fprintf (fp, "%s: ", cp); + fprintf (fp, "signal %d", signum); + if (signum >= 0 && signum < sizeof(sigmsg) && sigmsg[signum] != NULL) + fprintf (fp, " (%s%s)\n", sigmsg[signum], + WCOREDUMP(status) ? ", core dumped" : ""); + else + fprintf (fp, "%s\n", WCOREDUMP(status) ? " (core dumped)" : ""); + } + } + + return status; +} diff --git a/sbr/pidwait.c b/sbr/pidwait.c new file mode 100644 index 0000000..4bd02e3 --- /dev/null +++ b/sbr/pidwait.c @@ -0,0 +1,53 @@ + +/* + * pidwait.c -- wait for child to exit + * + * $Id$ + */ + +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +int +pidwait (pid_t id, int sigsok) +{ + pid_t pid; + sigset_t set, oset; + +#ifdef WAITINT + int status; +#else + union wait status; +#endif + + if (sigsok == -1) { + /* block a couple of signals */ + sigemptyset (&set); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGQUIT); + SIGPROCMASK (SIG_BLOCK, &set, &oset); + } + +#ifdef HAVE_WAITPID + pid = waitpid(id, &status, 0); +#else + while ((pid = wait(&status)) != -1 && pid != id) + continue; +#endif + + if (sigsok == -1) { + /* reset the signal mask */ + SIGPROCMASK (SIG_SETMASK, &oset, &set); + } + +#ifdef WAITINT + return (pid == -1 ? -1 : status); +#else + return (pid == -1 ? -1 : status.w_status); +#endif +} diff --git a/sbr/print_help.c b/sbr/print_help.c new file mode 100644 index 0000000..1ceeb85 --- /dev/null +++ b/sbr/print_help.c @@ -0,0 +1,29 @@ + +/* + * print_help.c -- print a help message, and possibly the + * -- profile/context entries for this command + * + * $Id$ + */ + +#include + + +void +print_help (char *str, struct swit *swp, int print_context) +{ + char *s; + + /* print Usage string */ + printf ("Usage: %s\n", str); + + /* print all the switches */ + printf (" switches are:\n"); + print_sw (ALL, swp, "-"); + + /* + * check if we should print any profile entries + */ + if (print_context && (s = context_find (invo_name))) + printf ("\nProfile: %s\n", s); +} diff --git a/sbr/print_sw.c b/sbr/print_sw.c new file mode 100644 index 0000000..c9c1159 --- /dev/null +++ b/sbr/print_sw.c @@ -0,0 +1,53 @@ + +/* + * print_sw.c -- print switches + * + * $Id$ + */ + +#include + + +void +print_sw (char *substr, struct swit *swp, char *prefix) +{ + int len, optno; + register int i; + register char *cp, *cp1, *sp; + char buf[128]; + + len = strlen(substr); + for (; swp->sw; swp++) { + /* null matches all strings */ + if (!*substr || (ssequal (substr, swp->sw) && len >= swp->minchars)) { + optno = 0; + /* next switch */ + if ((sp = (&swp[1])->sw)) { + if (!*substr && sp[0] == 'n' && sp[1] == 'o' && + strcmp (&sp[2], swp->sw) == 0 && ( + ((&swp[1])->minchars == 0 && swp->minchars == 0) || + ((&swp[1])->minchars == (swp->minchars) + 2))) + optno++; + } + + if (swp->minchars > 0) { + cp = buf; + *cp++ = '('; + if (optno) { + strcpy (cp, "[no]"); + cp += strlen (cp); + } + for (cp1 = swp->sw, i = 0; i < swp->minchars; i++) + *cp++ = *cp1++; + *cp++ = ')'; + while ((*cp++ = *cp1++)); + printf (" %s%s\n", prefix, buf); + } else { + if (!swp->minchars) + printf(optno ? " %s[no]%s\n" : " %s%s\n", prefix, swp->sw); + } + if (optno) + swp++; /* skip -noswitch */ + } + } +} diff --git a/sbr/print_version.c b/sbr/print_version.c new file mode 100644 index 0000000..1b37b1e --- /dev/null +++ b/sbr/print_version.c @@ -0,0 +1,15 @@ + +/* + * print_version.c -- print a version string + * + * $Id$ + */ + +#include + + +void +print_version (char *invo_name) +{ + printf("%s -- %s\n", invo_name, version_str); +} diff --git a/sbr/push.c b/sbr/push.c new file mode 100644 index 0000000..05c3942 --- /dev/null +++ b/sbr/push.c @@ -0,0 +1,48 @@ + +/* + * push.c -- push a fork into the background + * + * $Id$ + */ + +#include +#include +#include + + +void +push(void) +{ + pid_t pid; + int i; + + for (i = 0; (pid = fork()) == -1 && i < 5; i++) + sleep (5); + + switch (pid) { + case -1: + /* fork error */ + advise (NULL, "unable to fork, so can't push..."); + break; + + case 0: + /* child, block a few signals and continue */ + SIGNAL (SIGHUP, SIG_IGN); + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); + SIGNAL (SIGTERM, SIG_IGN); +#ifdef SIGTSTP + SIGNAL (SIGTSTP, SIG_IGN); + SIGNAL (SIGTTIN, SIG_IGN); + SIGNAL (SIGTTOU, SIG_IGN); +#endif + freopen ("/dev/null", "r", stdin); + freopen ("/dev/null", "w", stdout); + break; + + default: + /* parent, just exit */ + done (0); + } +} + diff --git a/sbr/putenv.c b/sbr/putenv.c new file mode 100644 index 0000000..2c6af0d --- /dev/null +++ b/sbr/putenv.c @@ -0,0 +1,76 @@ + +/* + * putenv.c -- (un)set an envariable + * + * $Id$ + */ + +#include + +extern char **environ; + +/* + * prototypes + */ +int m_putenv (char *, char *); +int unputenv (char *); +static int nvmatch (char *, char *); + + +int +m_putenv (char *name, char *value) +{ + register int i; + register char **ep, **nep, *cp; + + if (!(cp = malloc ((size_t) (strlen (name) + strlen (value) + 2)))) + return 1; + + sprintf (cp, "%s=%s", name, value); + + for (ep = environ, i = 0; *ep; ep++, i++) + if (nvmatch (name, *ep)) { + *ep = cp; + return 0; + } + + if (!(nep = (char **) malloc ((size_t) ((i + 2) * sizeof(*nep))))) + return 1; + + for (ep = environ, i = 0; *ep; nep[i++] = *ep++) + continue; + nep[i++] = cp; + nep[i] = NULL; + environ = nep; + return 0; +} + + +int +unputenv (char *name) +{ + char **ep, **nep; + + for (ep = environ; *ep; ep++) + if (nvmatch (name, *ep)) + break; + if (*ep == NULL) + return 1; + + for (nep = ep + 1; *nep; nep++) + continue; + *ep = *--nep; + *nep = NULL; + return 0; +} + + +static int +nvmatch (char *s1, char *s2) +{ + while (*s1 == *s2++) + if (*s1++ == '=') + return 1; + + return (*s1 == '\0' && *--s2 == '='); +} diff --git a/sbr/pwd.c b/sbr/pwd.c new file mode 100644 index 0000000..0036d20 --- /dev/null +++ b/sbr/pwd.c @@ -0,0 +1,115 @@ + +/* + * pwd.c -- return the current working directory + * + * $Id$ + */ + +#include + +static char curwd[PATH_MAX]; + + +char * +pwd(void) +{ + register char *cp; + + if (!getcwd (curwd, PATH_MAX)) { + admonish (NULL, "unable to determine working directory"); + if (!mypath || !*mypath + || (strcpy (curwd, mypath), chdir (curwd)) == -1) { + strcpy (curwd, "/"); + chdir (curwd); + } + return curwd; + } + + if ((cp = curwd + strlen (curwd) - 1) > curwd && *cp == '/') + *cp = '\0'; + + return curwd; +} + + +#if 0 + +/* + * Currently commented out. Everyone seems + * to have a native version these days. + */ + +/* + * getwd() - get the current working directory + */ + +int +getwd(char *cwd) +{ + int found; + char tmp1[BUFSIZ], tmp2[BUFSIZ]; + struct stat st1, st2, root; + register struct direct *dp; + register DIR *dd; + + strcpy (cwd, "/"); + stat ("/", &root); + + for (;;) { + if ((dd = opendir ("..")) == NULL) + return -1; + if (stat (".", &st2) == -1 || stat ("..", &st1) == -1) + goto out; + if (st2.st_ino == root.st_ino && st2.st_dev == root.st_dev) { + closedir (dd); + return chdir (cwd); + } + + if (st2.st_ino == st1.st_ino && st2.st_dev == st1.st_dev) { + closedir (dd); + chdir ("/"); + if ((dd = opendir (".")) == NULL) + return -1; + if (stat (".", &st1) < 0) + goto out; + if (st2.st_dev != st1.st_dev) + while (dp = readdir (dd)) { + if (stat (dp->d_name, &st1) == -1) + goto out; + if (st2.st_dev == st1.st_dev) { + snprintf (tmp1, sizeof(tmp1), "%s%s", dp->d_name, cwd); + strcpy (cwd + 1, tmp1); + closedir (dd); + return (chdir (cwd)); + } + } + else { + closedir (dd); + return (chdir (cwd)); + } + } + + found = 0; + while (dp = readdir (dd)) { + snprintf (tmp2, sizeof(tmp2), "../%s", dp->d_name); + if (stat (tmp2, &st1) != -1 + && st1.st_ino == st2.st_ino + && st1.st_dev == st2.st_dev) { + closedir (dd); + found++; + chdir (".."); + snprintf (tmp1, sizeof(tmp1), "%s%s", dp->d_name, cwd); + strcpy (cwd + 1, tmp1); + break; + } + } + if (!found) + goto out; + } + +out: ; + closedir (dd); + return -1; +} + +#endif diff --git a/sbr/r1bindex.c b/sbr/r1bindex.c new file mode 100644 index 0000000..087ed78 --- /dev/null +++ b/sbr/r1bindex.c @@ -0,0 +1,32 @@ + +/* + * r1bindex.c -- Given a string and a character, return a pointer + * -- to the right of the rightmost occurrence of the + * -- character. If the character doesn't occur, the + * -- pointer will be at the beginning of the string. + * + * $Id$ + */ + +#include + + +char * +r1bindex(char *str, int chr) +{ + char *cp; + + /* find null at the end of the string */ + for (cp = str; *cp; cp++) + continue; + + /* backup to the rightmost character */ + --cp; + + /* now search for the rightmost occurrence of the character */ + while (cp >= str && *cp != chr) + --cp; + + /* now move one to the right */ + return (++cp); +} diff --git a/sbr/readconfig.c b/sbr/readconfig.c new file mode 100644 index 0000000..9cd128e --- /dev/null +++ b/sbr/readconfig.c @@ -0,0 +1,109 @@ + +/* + * readconfig.c -- base routine to read nmh configuration files + * -- such as nmh profile, context file, or mhn.defaults. + * + * $Id$ + */ + +#include + +struct procstr { + char *procname; + char **procnaddr; +}; + +static struct procstr procs[] = { + { "context", &context }, + { "mh-sequences", &mh_seq }, + { "buildmimeproc", &buildmimeproc }, + { "faceproc", &faceproc }, + { "fileproc", &fileproc }, + { "incproc", &incproc }, + { "installproc", &installproc }, + { "lproc", &lproc }, + { "mailproc", &mailproc }, + { "mhlproc", &mhlproc }, + { "moreproc", &moreproc }, + { "mshproc", &mshproc }, + { "packproc", &packproc }, + { "postproc", &postproc }, + { "rmfproc", &rmfproc }, + { "rmmproc", &rmmproc }, + { "sendproc", &sendproc }, + { "showmimeproc", &showmimeproc }, + { "showproc", &showproc }, + { "vmhproc", &vmhproc }, + { "whatnowproc", &whatnowproc }, + { "whomproc", &whomproc }, + { NULL, NULL } +}; + +static struct node **opp = NULL; + + +void +readconfig (struct node **npp, FILE *ib, char *file, int ctx) +{ + register int state; + register char *cp; + char name[NAMESZ], field[BUFSIZ]; + register struct node *np; + register struct procstr *ps; + + if (npp == NULL && (npp = opp) == NULL) { + admonish (NULL, "bug: readconfig called but pump not primed"); + return; + } + + for (state = FLD;;) { + switch (state = m_getfld (state, name, field, sizeof(field), ib)) { + case FLD: + case FLDPLUS: + case FLDEOF: + if (!(np = (struct node *) malloc (sizeof(*np)))) + adios (NULL, "unable to allocate profile storage"); + *npp = np; + *(npp = &np->n_next) = NULL; + np->n_name = getcpy (name); + if (state == FLDPLUS) { + cp = getcpy (field); + while (state == FLDPLUS) { + state = m_getfld (state, name, field, sizeof(field), ib); + cp = add (field, cp); + } + np->n_field = trimcpy (cp); + free (cp); + } else { + np->n_field = trimcpy (field); + } + np->n_context = ctx; + + /* + * Now scan the list of `procs' and link in the + * field value to the global variable. + */ + for (ps = procs; ps->procname; ps++) + if (strcmp (np->n_name, ps->procname) == 0) { + *ps->procnaddr = np->n_field; + break; + } + if (state == FLDEOF) + break; + continue; + + case BODY: + case BODYEOF: + adios (NULL, "no blank lines are permitted in %s", file); + + case FILEEOF: + break; + + default: + adios (NULL, "%s is poorly formatted", file); + } + break; + } + + opp = npp; +} diff --git a/sbr/refile.c b/sbr/refile.c new file mode 100644 index 0000000..17c6715 --- /dev/null +++ b/sbr/refile.c @@ -0,0 +1,49 @@ + +/* + * refile.c -- call the "fileproc" to refile the + * -- msg or draft into another folder + * + * $Id$ + */ + +#include + + +int +refile (char **arg, char *file) +{ + pid_t pid; + register int vecp; + char *vec[MAXARGS]; + + vecp = 0; + vec[vecp++] = r1bindex (fileproc, '/'); + vec[vecp++] = "-nolink"; /* override bad .mh_profile defaults */ + vec[vecp++] = "-nopreserve"; + vec[vecp++] = "-file"; + vec[vecp++] = file; + + if (arg) { + while (*arg) + vec[vecp++] = *arg++; + } + vec[vecp] = NULL; + + context_save(); /* save the context file */ + fflush(stdout); + + switch (pid = vfork()) { + case -1: + advise ("fork", "unable to"); + return -1; + + case 0: + execvp (fileproc, vec); + fprintf (stderr, "unable to exec "); + perror (fileproc); + _exit (-1); + + default: + return (pidwait (pid, -1)); + } +} diff --git a/sbr/remdir.c b/sbr/remdir.c new file mode 100644 index 0000000..4ef1649 --- /dev/null +++ b/sbr/remdir.c @@ -0,0 +1,22 @@ + +/* + * remdir.c -- remove a directory + * + * $Id$ + */ + +#include + + +int +remdir (char *dir) +{ + context_save(); /* save the context file */ + fflush(stdout); + + if (rmdir(dir) == -1) { + admonish (dir, "unable to remove directory"); + return 0; + } + return 1; +} diff --git a/sbr/ruserpass.c b/sbr/ruserpass.c new file mode 100644 index 0000000..68809df --- /dev/null +++ b/sbr/ruserpass.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 1985 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * $Id$ + */ + +#include +#include +#include + +static FILE *cfile; + +#ifndef MAXHOSTNAMELEN +# define MAXHOSTNAMELEN 64 +#endif + +#define DEFAULT 1 +#define LOGIN 2 +#define PASSWD 3 +#define ACCOUNT 4 +#define MACDEF 5 +#define ID 10 +#define MACH 11 + +static char tokval[100]; + +struct toktab { + char *tokstr; + int tval; +}; + +static struct toktab toktabs[] = { + { "default", DEFAULT }, + { "login", LOGIN }, + { "password", PASSWD }, + { "passwd", PASSWD }, + { "account", ACCOUNT }, + { "machine", MACH }, + { "macdef", MACDEF }, + { 0, 0 } +}; + +/* + * prototypes + */ +static int token(void); + + +int +ruserpass(char *host, char **aname, char **apass) +{ + char *hdir, buf[BUFSIZ]; + int t, usedefault = 0; + struct stat stb; + extern int errno; + + hdir = getenv("HOME"); + if (hdir == NULL) + hdir = "."; + snprintf(buf, sizeof(buf), "%s/.netrc", hdir); + cfile = fopen(buf, "r"); + if (cfile == NULL) { + if (errno != ENOENT) + perror(buf); + goto done; + } + + while ((t = token())) { + switch(t) { + case DEFAULT: + usedefault = 1; + /* FALL THROUGH */ + + case MACH: + if (!usedefault) { + if (token() != ID) + continue; + /* + * Allow match either for user's host name. + */ + if (strcasecmp(host, tokval) == 0) + goto match; + continue; + } +match: + while ((t = token()) && t != MACH && t != DEFAULT) { + switch(t) { + case LOGIN: + if (token() && *aname == 0) { + *aname = malloc((size_t) strlen(tokval) + 1); + strcpy(*aname, tokval); + } + break; + case PASSWD: + if (fstat(fileno(cfile), &stb) >= 0 && + (stb.st_mode & 077) != 0) { + fprintf(stderr, "Error - .netrc file not correct mode.\n"); + fprintf(stderr, "Remove password or correct mode.\n"); + goto bad; + } + if (token() && *apass == 0) { + *apass = malloc((size_t) strlen(tokval) + 1); + strcpy(*apass, tokval); + } + break; + case ACCOUNT: + break; + + case MACDEF: + goto done_close; + break; + default: + fprintf(stderr, "Unknown .netrc keyword %s\n", tokval); + break; + } + } + goto done; + } + } + +done_close: + fclose(cfile); + +done: + if (!*aname) { + char tmp[80]; + char *myname; + + if ((myname = getlogin()) == NULL) { + struct passwd *pp; + + if ((pp = getpwuid (getuid())) != NULL) + myname = pp->pw_name; + } + printf("Name (%s:%s): ", host, myname); + + fgets(tmp, sizeof(tmp) - 1, stdin); + tmp[strlen(tmp) - 1] = '\0'; + if (*tmp != '\0') { + myname = tmp; + } + + *aname = malloc((size_t) strlen(myname) + 1); + strcpy (*aname, myname); + } + + if (!*apass) { + char prompt[256]; + char *mypass; + + snprintf(prompt, sizeof(prompt), "Password (%s:%s): ", host, *aname); + mypass = getpass (prompt); + + if (*mypass == '\0') { + mypass = *aname; + } + + *apass = malloc((size_t) strlen(mypass) + 1); + strcpy (*apass, mypass); + } + + return(0); +bad: + fclose(cfile); + return(-1); +} + +static int +token(void) +{ + char *cp; + int c; + struct toktab *t; + + if (feof(cfile)) + return (0); + while ((c = getc(cfile)) != EOF && + (c == '\n' || c == '\t' || c == ' ' || c == ',')) + continue; + if (c == EOF) + return (0); + cp = tokval; + if (c == '"') { + while ((c = getc(cfile)) != EOF && c != '"') { + if (c == '\\') + c = getc(cfile); + *cp++ = c; + } + } else { + *cp++ = c; + while ((c = getc(cfile)) != EOF + && c != '\n' && c != '\t' && c != ' ' && c != ',') { + if (c == '\\') + c = getc(cfile); + *cp++ = c; + } + } + *cp = 0; + if (tokval[0] == 0) + return (0); + for (t = toktabs; t->tokstr; t++) + if (!strcmp(t->tokstr, tokval)) + return (t->tval); + return (ID); +} diff --git a/sbr/seq_add.c b/sbr/seq_add.c new file mode 100644 index 0000000..b586fc3 --- /dev/null +++ b/sbr/seq_add.c @@ -0,0 +1,189 @@ + +/* + * seq_add.c -- add message(s) to a sequence + * + * $Id$ + */ + +#include + + +/* + * Add all the SELECTED messages to a (possibly new) sequence. + * + * If public == 1, make sequence public. + * If public == 0, make sequence private. + * If public == -1, leave the public/private bit alone for existing + * sequences. For new sequences, set this bit based + * on its readonly status. + * + * If error, return 0, else return 1. + */ + +int +seq_addsel (struct msgs *mp, char *cp, int public, int zero) +{ + int i, msgnum, new_seq = 1; + + if (!seq_nameok (cp)) + return 0; + + /* + * We keep mp->curmsg and "cur" sequence in sync. + * See seq_list() and seq_init(). + */ + if (!strcmp (current,cp)) + mp->curmsg = mp->hghsel; + + /* + * Get the number for this sequence + */ + for (i = 0; mp->msgattrs[i]; i++) { + if (!strcmp (mp->msgattrs[i], cp)) { + new_seq = 0; + break; + } + } + + /* + * If this is a new sequence, add a slot for it + */ + if (new_seq) { + if (i >= NUMATTRS) { + advise (NULL, "only %d sequences allowed (no room for %s)!", NUMATTRS, cp); + return 0; + } + if (!(mp->msgattrs[i] = strdup (cp))) { + advise (NULL, "strdup failed"); + return 0; + } + mp->msgattrs[i + 1] = NULL; + } + + /* + * If sequence is new, or zero flag is set, then first + * clear the bit for this sequence from all messages. + */ + if (new_seq || zero) { + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) + clear_sequence (mp, i, msgnum); + } + + /* + * Now flip on the bit for this sequence + * for all selected messages. + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) + add_sequence (mp, i, msgnum); + + /* + * Set the public/private bit for this sequence. + */ + if (public == 1) + make_seq_public (mp, i); + else if (public == 0) + make_seq_private (mp, i); + else if (new_seq) { + /* + * If public == -1, then only set the + * public/private bit for new sequences. + */ + if (is_readonly (mp)) + make_seq_private (mp, i); + else + make_seq_public (mp, i); + } + + mp->msgflags |= SEQMOD; + return 1; +} + + +/* + * Add a message to a (possibly new) sequence. + * + * If public == 1, make sequence public. + * If public == 0, make sequence private. + * If public == -1, leave the public/private bit alone for existing + * sequences. For new sequences, set this bit based + * on its readonly status. + * + * If error, return 0, else return 1. + */ + +int +seq_addmsg (struct msgs *mp, char *cp, int msgnum, int public, int zero) +{ + int i, j, new_seq = 1; + + if (!seq_nameok (cp)) + return 0; + + /* + * keep mp->curmsg and msgattrs["cur"] in sync - see seq_list() + */ + if (!strcmp (current,cp)) + mp->curmsg = msgnum; + + /* + * Get the number for this sequence + */ + for (i = 0; mp->msgattrs[i]; i++) { + if (!strcmp (mp->msgattrs[i], cp)) { + new_seq = 0; + break; + } + } + + /* + * If this is a new sequence, add a slot for it + */ + if (new_seq) { + if (i >= NUMATTRS) { + advise (NULL, "only %d sequences allowed (no room for %s)!", NUMATTRS, cp); + return 0; + } + if (!(mp->msgattrs[i] = strdup (cp))) { + advise (NULL, "strdup failed"); + return 0; + } + mp->msgattrs[i + 1] = NULL; + } + + /* + * If sequence is new, or zero flag is set, then first + * clear the bit for this sequence from all messages. + */ + if (new_seq || zero) { + for (j = mp->lowmsg; j <= mp->hghmsg; j++) + clear_sequence (mp, i, j); + } + + /* + * Now flip on the bit for this sequence + * for this particular message. + */ + add_sequence (mp, i, msgnum); + + /* + * Set the public/private bit for this sequence. + */ + if (public == 1) + make_seq_public (mp, i); + else if (public == 0) + make_seq_private (mp, i); + else if (new_seq) { + /* + * If public == -1, then only set the + * public/private bit for new sequences. + */ + if (is_readonly (mp)) + make_seq_private (mp, i); + else + make_seq_public (mp, i); + } + + mp->msgflags |= SEQMOD; + return 1; +} diff --git a/sbr/seq_bits.c b/sbr/seq_bits.c new file mode 100644 index 0000000..b0f6e6c --- /dev/null +++ b/sbr/seq_bits.c @@ -0,0 +1,27 @@ + +/* + * seq_bits.c -- return the snprintb() string for a sequence + * + * $Id$ + */ + +#include + + +char * +seq_bits (struct msgs *mp) +{ + int i; + size_t len; + static char buffer[BUFSIZ]; + + strncpy (buffer, MBITS, sizeof(buffer)); + + for (i = 0; mp->msgattrs[i]; i++) { + len = strlen (buffer); + snprintf (buffer + len, sizeof(buffer) - len, + "%c%s", FFATTRSLOT + 1 + i, mp->msgattrs[i]); + } + + return buffer; +} diff --git a/sbr/seq_del.c b/sbr/seq_del.c new file mode 100644 index 0000000..ed70c5d --- /dev/null +++ b/sbr/seq_del.c @@ -0,0 +1,130 @@ + +/* + * seq_del.c -- delete message(s) from a sequence + * + * $Id$ + */ + +#include + + +/* + * Delete all SELECTED messages from sequence + * + * If public == 1, make sequence public. + * If public == 0, make sequence private. + * If public == -1, leave the public/private bit alone for existing + * sequences. For new sequences, set this bit based + * on its readonly status. + * + * If error, return 0, else return 1. + */ + +int +seq_delsel (struct msgs *mp, char *cp, int public, int zero) +{ + int i, msgnum, new_seq = 1; + + if (!seq_nameok (cp)) + return 0; + + /* + * Get the number for this sequence + */ + for (i = 0; mp->msgattrs[i]; i++) { + if (!strcmp (mp->msgattrs[i], cp)) { + new_seq = 0; + break; + } + } + + /* + * If the zero flag is set, first add all existing + * messages in this folder to the sequence. + */ + if (zero) { + /* + * create the sequence, if necessary + */ + if (new_seq) { + if (i >= NUMATTRS) { + advise (NULL, "only %d sequences allowed (no room for %s)!", NUMATTRS, cp); + return 0; + } + if (!(mp->msgattrs[i] = strdup (cp))) { + advise (NULL, "strdup failed"); + return 0; + } + mp->msgattrs[i + 1] = NULL; + } + /* + * now add sequence bit to all existing messages + */ + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) { + if (does_exist (mp, msgnum)) + add_sequence (mp, i, msgnum); + else + clear_sequence (mp, i, msgnum); + } + } else { + if (new_seq) { + advise (NULL, "no such sequence as %s", cp); + return 0; + } + } + + /* + * Now clear the bit on all selected messages + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) + clear_sequence (mp, i, msgnum); + + /* + * Set the public/private bit for this sequence. + */ + if (public == 1) + make_seq_public (mp, i); + else if (public == 0) + make_seq_private (mp, i); + else if (new_seq) { + /* + * If public == -1, then only set the + * public/private bit for new sequences. + */ + if (is_readonly (mp)) + make_seq_private (mp, i); + else + make_seq_public (mp, i); + } + + mp->msgflags |= SEQMOD; + return 1; +} + + +/* + * Delete message from sequence. + * + * If error, return 0, else return 1. + */ + +int +seq_delmsg (struct msgs *mp, char *cp, int msgnum) +{ + int i; + + if (!seq_nameok (cp)) + return 0; + + for (i = 0; mp->msgattrs[i]; i++) { + if (!strcmp (mp->msgattrs[i], cp)) { + clear_sequence (mp, i, msgnum); + mp->msgflags |= SEQMOD; + return 1; + } + } + + advise (NULL, "no such sequence as %s", cp); + return 0; +} diff --git a/sbr/seq_getnum.c b/sbr/seq_getnum.c new file mode 100644 index 0000000..c44f177 --- /dev/null +++ b/sbr/seq_getnum.c @@ -0,0 +1,22 @@ + +/* + * seq_getnum.c -- find the index for a sequence + * -- return -1 if sequence doesn't exist + * + * $Id$ + */ + +#include + + +int +seq_getnum (struct msgs *mp, char *seqname) +{ + int i; + + for (i = 0; mp->msgattrs[i]; i++) + if (!strcmp (mp->msgattrs[i], seqname)) + return i; + + return -1; +} diff --git a/sbr/seq_list.c b/sbr/seq_list.c new file mode 100644 index 0000000..afa8671 --- /dev/null +++ b/sbr/seq_list.c @@ -0,0 +1,104 @@ + +/* + * seq_list.c -- Get all messages in a sequence and return them + * -- as a space separated list of message ranges. + * + * $Id$ + */ + +#include + +/* allocate this much buffer space at a time */ +#define MAXBUFFER 1024 + +/* static buffer to collect the sequence line */ +static char *buffer = NULL; +static int len = 0; + + +char * +seq_list(struct msgs *mp, char *seqname) +{ + int i, j, seqnum; + char *bp; + + /* On first invocation, allocate initial buffer space */ + if (!buffer) { + len = MAXBUFFER; + if (!(buffer = malloc ((size_t) len))) + adios (NULL, "unable to malloc storage in seq_list"); + } + + /* + * Special processing for "cur" sequence. We assume that the + * "cur" sequence and mp->curmsg are in sync (see seq_add.c). + * This is returned, even if message doesn't exist or the + * folder is empty. + */ + if (!strcmp (current, seqname)) { + if (mp->curmsg) { + sprintf(buffer, "%s", m_name(mp->curmsg)); + return (buffer); + } else + return (NULL); + } + + /* If the folder is empty, just return NULL */ + if (mp->nummsg == 0) + return NULL; + + /* Get the index of the sequence */ + if ((seqnum = seq_getnum (mp, seqname)) == -1) + return NULL; + + bp = buffer; + + for (i = mp->lowmsg; i <= mp->hghmsg; ++i) { + /* + * If message doesn't exist, or isn't in + * the sequence, then continue. + */ + if (!does_exist(mp, i) || !in_sequence(mp, seqnum, i)) + continue; + + /* + * See if we need to enlarge buffer. Since we don't know + * exactly how many character this particular message range + * will need, we enlarge the buffer if we are within + * 50 characters of the end. + */ + if (bp - buffer > len - 50) { + char *newbuf; + + len += MAXBUFFER; + if (!(newbuf = realloc (buffer, (size_t) len))) + adios (NULL, "unable to realloc storage in seq_list"); + bp = newbuf + (bp - buffer); + buffer = newbuf; + } + + /* + * If this is not the first message range in + * the list, first add a space. + */ + if (bp > buffer) + *bp++ = ' '; + + sprintf(bp, "%s", m_name(i)); + bp += strlen(bp); + j = i; /* Remember beginning of message range */ + + /* + * Scan to the end of this message range + */ + for (++i; i <= mp->hghmsg && does_exist(mp, i) && in_sequence(mp, seqnum, i); + ++i) + ; + + if (i - j > 1) { + sprintf(bp, "-%s", m_name(i - 1)); + bp += strlen(bp); + } + } + return (bp > buffer? buffer : NULL); +} diff --git a/sbr/seq_nameok.c b/sbr/seq_nameok.c new file mode 100644 index 0000000..66319b5 --- /dev/null +++ b/sbr/seq_nameok.c @@ -0,0 +1,54 @@ + +/* + * seq_nameok.c -- check if a sequence name is ok + * + * $Id$ + */ + +#include + + +int +seq_nameok (char *s) +{ + char *pp; + + if (s == NULL || *s == '\0') { + advise (NULL, "empty sequence name"); + return 0; + } + + /* + * Make sure sequence name doesn't clash with one + * of the `reserved' sequence names. + */ + if (!(strcmp (s, "new") && + strcmp (s, "all") && + strcmp (s, "first") && + strcmp (s, "last") && + strcmp (s, "prev") && + strcmp (s, "next"))) { + advise (NULL, "illegal sequence name: %s", s); + return 0; + } + + /* + * First character in a sequence name must be + * an alphabetic character ... + */ + if (!isalpha (*s)) { + advise (NULL, "illegal sequence name: %s", s); + return 0; + } + + /* + * and can be followed by zero or more alphanumeric characters + */ + for (pp = s + 1; *pp; pp++) + if (!isalnum (*pp)) { + advise (NULL, "illegal sequence name: %s", s); + return 0; + } + + return 1; +} diff --git a/sbr/seq_print.c b/sbr/seq_print.c new file mode 100644 index 0000000..ee34fd9 --- /dev/null +++ b/sbr/seq_print.c @@ -0,0 +1,46 @@ + +/* + * seq_print.c -- Routines to print sequence information. + * + * $Id$ + */ + +#include + +#define empty(s) ((s) ? (s) : "") + +/* + * Print all the sequences in a folder + */ +void +seq_printall (struct msgs *mp) +{ + int i; + char *list; + + for (i = 0; mp->msgattrs[i]; i++) { + list = seq_list (mp, mp->msgattrs[i]); + printf ("%s%s: %s\n", mp->msgattrs[i], + is_seq_private (mp, i) ? " (private)" : "", empty(list)); + } +} + + +/* + * Print a particular sequence in a folder + */ +void +seq_print (struct msgs *mp, char *seqname) +{ + int i; + char *list; + + /* get the index of sequence */ + i = seq_getnum (mp, seqname); + + /* get sequence information */ + list = seq_list (mp, seqname); + + printf ("%s%s: %s\n", seqname, + (i == -1) ? "" : is_seq_private(mp, i) ? " (private)" : "", empty(list)); +} diff --git a/sbr/seq_read.c b/sbr/seq_read.c new file mode 100644 index 0000000..649a360 --- /dev/null +++ b/sbr/seq_read.c @@ -0,0 +1,235 @@ + +/* + * seq_read.c -- read the .mh_sequence file and + * -- initialize sequence information + * + * $Id$ + */ + +#include + +/* + * static prototypes + */ +static int seq_init (struct msgs *, char *, char *); +static void seq_public (struct msgs *); +static void seq_private (struct msgs *); + + +/* + * Get the sequence information for this folder from + * .mh_sequence (or equivalent specified in .mh_profile) + * or context file (for private sequences). + */ + +void +seq_read (struct msgs *mp) +{ + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + /* + * Initialize the list of sequence names. Go ahead and + * add the "cur" sequence to the list of sequences. + */ + mp->msgattrs[0] = getcpy (current); + mp->msgattrs[1] = NULL; + make_all_public (mp); /* initially, make all public */ + + /* If folder is empty, don't scan for sequence information */ + if (mp->nummsg == 0) + return; + + /* Initialize the public sequences */ + seq_public (mp); + + /* Initialize the private sequences */ + seq_private (mp); +} + + +/* + * read folder's sequences file for public sequences + */ + +static void +seq_public (struct msgs *mp) +{ + int state; + char *cp, seqfile[PATH_MAX]; + char name[NAMESZ], field[BUFSIZ]; + FILE *fp; + + /* + * If mh_seq == NULL (such as if nmh been compiled with + * NOPUBLICSEQ), or if *mh_seq == '\0' (the user has defined + * the "mh-sequences" profile entry, but left it empty), + * then just return, and do not initialize any public sequences. + */ + if (mh_seq == NULL || *mh_seq == '\0') + return; + + /* get filename of sequence file */ + snprintf (seqfile, sizeof(seqfile), "%s/%s", mp->foldpath, mh_seq); + + if ((fp = fopen (seqfile, "r")) == NULL) + return; + + /* Use m_getfld to scan sequence file */ + for (state = FLD;;) { + switch (state = m_getfld (state, name, field, sizeof(field), fp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + if (state == FLDPLUS) { + cp = getcpy (field); + while (state == FLDPLUS) { + state = m_getfld (state, name, field, sizeof(field), fp); + cp = add (field, cp); + } + seq_init (mp, getcpy (name), trimcpy (cp)); + free (cp); + } else { + seq_init (mp, getcpy (name), trimcpy (field)); + } + if (state == FLDEOF) + break; + continue; + + case BODY: + case BODYEOF: + adios (NULL, "no blank lines are permitted in %s", seqfile); + /* fall */ + + case FILEEOF: + break; + + default: + adios (NULL, "%s is poorly formatted", seqfile); + } + break; /* break from for loop */ + } + + fclose (fp); +} + + +/* + * Scan profile/context list for private sequences. + * + * We search the context list for all keys that look like + * "atr-seqname-folderpath", and add them as private sequences. + */ + +static void +seq_private (struct msgs *mp) +{ + int i, j, alen, plen; + char *cp; + struct node *np; + + alen = strlen ("atr-"); + plen = strlen (mp->foldpath) + 1; + + for (np = m_defs; np; np = np->n_next) { + if (ssequal ("atr-", np->n_name) + && (j = strlen (np->n_name) - plen) > alen + && *(np->n_name + j) == '-' + && strcmp (mp->foldpath, np->n_name + j + 1) == 0) { + cp = getcpy (np->n_name + alen); + *(cp + j - alen) = '\0'; + if ((i = seq_init (mp, cp, getcpy (np->n_field))) != -1) + make_seq_private (mp, i); + } + } +} + + +/* + * Add the name of sequence to the list of folder sequences. + * Then parse the list of message ranges for this + * sequence, and setup the various bit flags for each + * message in the sequence. + * + * Return internal index for the sequence if successful. + * Return -1 on error. + */ + +static int +seq_init (struct msgs *mp, char *name, char *field) +{ + int i, j, k, is_current; + char *cp, **ap; + + /* + * Check if this is "cur" sequence, + * so we can do some special things. + */ + is_current = !strcmp (current, name); + + /* + * Search for this sequence name to see if we've seen + * it already. If we've seen this sequence before, + * then clear the bit for this sequence from all the + * mesages in this folder. + */ + for (i = 0; mp->msgattrs[i]; i++) { + if (!strcmp (mp->msgattrs[i], name)) { + for (j = mp->lowmsg; j <= mp->hghmsg; j++) + clear_sequence (mp, i, j); + break; + } + } + + /* Return error, if too many sequences */ + if (i >= NUMATTRS) { + free (name); + free (field); + return -1; + } + + /* + * If we've already seen this sequence name, just free the + * name string. Else add it to the list of sequence names. + */ + if (mp->msgattrs[i]) { + free (name); + } else { + mp->msgattrs[i] = name; + mp->msgattrs[i + 1] = NULL; + } + + /* + * Split up the different message ranges at whitespace + */ + for (ap = brkstring (field, " ", "\n"); *ap; ap++) { + if ((cp = strchr(*ap, '-'))) + *cp++ = '\0'; + if ((j = m_atoi (*ap)) > 0) { + k = cp ? m_atoi (cp) : j; + + /* + * Keep mp->curmsg and "cur" sequence in synch. Unlike + * other sequences, this message doesn't need to exist. + * Think about the series of command (rmm; next) to + * understand why this can be the case. But if it does + * exist, we will still set the bit flag for it like + * other sequences. + */ + if (is_current) + mp->curmsg = j; + /* + * We iterate through messages in this range + * and flip on bit for this sequence. + */ + for (; j <= k; j++) { + if (j >= mp->lowmsg && j <= mp->hghmsg && does_exist(mp, j)) + add_sequence (mp, i, j); + } + } + } + + free (field); /* free string containing message ranges */ + return i; +} diff --git a/sbr/seq_save.c b/sbr/seq_save.c new file mode 100644 index 0000000..e0f42ae --- /dev/null +++ b/sbr/seq_save.c @@ -0,0 +1,115 @@ + +/* + * seq_save.c -- 1) synchronize sequences + * -- 2) save public sequences + * + * $Id$ + */ + +#include +#include + + +/* + * 1. If sequence is public and folder is readonly, + * then change it to be private + * 2a. If sequence is public, then add it to the sequences file + * in folder (name specified by mh-sequences profile entry). + * 2b. If sequence is private, then add it to the + * context file. + */ + +void +seq_save (struct msgs *mp) +{ + int i; + char flags, *cp, attr[BUFSIZ], seqfile[PATH_MAX]; + FILE *fp; + sigset_t set, oset; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + /* check if sequence information has changed */ + if (!(mp->msgflags & SEQMOD)) + return; + mp->msgflags &= ~SEQMOD; + + fp = NULL; + flags = mp->msgflags; /* record folder flags */ + + /* + * If no mh-sequences file is defined, or if a mh-sequences file + * is defined but empty (*mh_seq == '\0'), then pretend folder + * is readonly. This will force all sequences to be private. + */ + if (mh_seq == NULL || *mh_seq == '\0') + set_readonly (mp); + else + snprintf (seqfile, sizeof(seqfile), "%s/%s", mp->foldpath, mh_seq); + + for (i = 0; mp->msgattrs[i]; i++) { + snprintf (attr, sizeof(attr), "atr-%s-%s", mp->msgattrs[i], mp->foldpath); + + /* get space separated list of sequence ranges */ + if (!(cp = seq_list(mp, mp->msgattrs[i]))) { + context_del (attr); /* delete sequence from context */ + continue; + } + + if (is_readonly(mp) || is_seq_private(mp, i)) { +priv: + /* + * sequence is private + */ + context_replace (attr, cp); /* update sequence in context */ + } else { + /* + * sequence is public + */ + context_del (attr); /* delete sequence from context */ + + if (!fp) { + /* + * Attempt to open file for public sequences. + * If that fails (probably because folder is + * readonly), then make sequence private. + */ + if ((fp = fopen (seqfile, "w")) == NULL + && (unlink (seqfile) == -1 || + (fp = fopen (seqfile, "w")) == NULL)) { + admonish (attr, "unable to write"); + goto priv; + } + + /* block a few signals */ + sigemptyset (&set); + sigaddset(&set, SIGHUP); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGQUIT); + sigaddset(&set, SIGTERM); + SIGPROCMASK (SIG_BLOCK, &set, &oset); + } + fprintf (fp, "%s: %s\n", mp->msgattrs[i], cp); + } + } + + if (fp) { + fclose (fp); + SIGPROCMASK (SIG_SETMASK, &oset, &set); /* reset signal mask */ + } else { + /* + * If folder is not readonly, and we didn't save any + * public sequences, then remove that file. + */ + if (!is_readonly(mp)) + unlink (seqfile); + } + + /* + * Reset folder flag, since we may be + * pretending that folder is readonly. + */ + mp->msgflags = flags; +} diff --git a/sbr/seq_setcur.c b/sbr/seq_setcur.c new file mode 100644 index 0000000..7e7acba --- /dev/null +++ b/sbr/seq_setcur.c @@ -0,0 +1,19 @@ + +/* + * seq_setcur.c -- set the current message ("cur" sequence) for a folder + * + * $Id$ + */ + +#include + + +void +seq_setcur (struct msgs *mp, int msgnum) +{ + /* + * Just call seq_addmsg() to update the + * "cur" sequence. + */ + seq_addmsg (mp, current, msgnum, -1, 1); +} diff --git a/sbr/seq_setprev.c b/sbr/seq_setprev.c new file mode 100644 index 0000000..e5161c5 --- /dev/null +++ b/sbr/seq_setprev.c @@ -0,0 +1,42 @@ + +/* + * seq_setprev.c -- set the Previous-Sequence + * + * $Id$ + */ + +#include + +/* + * Add all the messages currently SELECTED to + * the Previous-Sequence. This way, when the next + * command is given, there is a convenient way to + * selected all the messages used in the previous + * command. + */ + +void +seq_setprev (struct msgs *mp) +{ + char **ap, *cp, *dp; + + /* + * Get the list of sequences for Previous-Sequence + * and split them. + */ + if ((cp = context_find (psequence))) { + dp = getcpy (cp); + if (!(ap = brkstring (dp, " ", "\n")) || !*ap) { + free (dp); + return; + } + } else { + return; + } + + /* Now add all SELECTED messages to each sequence */ + for (; *ap; ap++) + seq_addsel (mp, *ap, -1, 1); + + free (dp); +} diff --git a/sbr/seq_setunseen.c b/sbr/seq_setunseen.c new file mode 100644 index 0000000..906b0ac --- /dev/null +++ b/sbr/seq_setunseen.c @@ -0,0 +1,58 @@ + +/* + * seq_setunseen.c -- add/delete all messages which have the SELECT_UNSEEN + * -- bit set to/from the Unseen-Sequence + * + * $Id$ + */ + +#include + +/* + * We scan through the folder and act upon all messages + * that are marked with the SELECT_UNSEEN bit. + * + * If seen == 1, delete messages from unseen sequence. + * If seen == 0, add messages to unseen sequence. + */ + +void +seq_setunseen (struct msgs *mp, int seen) +{ + int msgnum; + char **ap, *cp, *dp; + + /* + * Get the list of sequences for Unseen-Sequence + * and split them. + */ + if ((cp = context_find (usequence))) { + dp = getcpy (cp); + if (!(ap = brkstring (dp, " ", "\n")) || !*ap) { + free (dp); + return; + } + } else { + return; + } + + /* + * Now add/delete each message which has the SELECT_UNSEEN + * bit set to/from each of these sequences. + */ + for (; *ap; ap++) { + if (seen) { + /* make sure sequence exists first */ + if (seq_getnum(mp, *ap) != -1) + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_unseen (mp, msgnum)) + seq_delmsg (mp, *ap, msgnum); + } else { + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) + if (is_unseen (mp, msgnum)) + seq_addmsg (mp, *ap, msgnum, -1, 0); + } + } + + free (dp); +} diff --git a/sbr/showfile.c b/sbr/showfile.c new file mode 100644 index 0000000..7ed13ca --- /dev/null +++ b/sbr/showfile.c @@ -0,0 +1,65 @@ + +/* + * showfile.c -- invoke the `lproc' command + * + * $Id$ + */ + +#include + + +int +showfile (char **arg, char *file) +{ + pid_t pid; + int isdraft, vecp; + char *vec[MAXARGS]; + + context_save(); /* save the context file */ + fflush(stdout); + + /* + * If you have your lproc listed as "mhl", + * then really invoked the mhlproc instead + * (which is usually mhl anyway). + */ + if (!strcmp (r1bindex (lproc, '/'), "mhl")) + lproc = mhlproc; + + switch (pid = vfork()) { + case -1: + /* fork error */ + advise ("fork", "unable to"); + return 1; + + case 0: + /* child */ + vecp = 0; + vec[vecp++] = r1bindex (lproc, '/'); + isdraft = 1; + if (arg) { + while (*arg) { + if (**arg != '-') + isdraft = 0; + vec[vecp++] = *arg++; + } + } + if (isdraft) { + if (!strcmp (vec[0], "show")) + vec[vecp++] = "-file"; + vec[vecp++] = file; + } + vec[vecp] = NULL; + + execvp (lproc, vec); + fprintf (stderr, "unable to exec "); + perror (lproc); + _exit (-1); + + default: + /* parent */ + return (pidwait (pid, -1) & 0377 ? 1 : 0); + } + + return 1; /* NOT REACHED */ +} diff --git a/sbr/sigmsg.awk b/sbr/sigmsg.awk new file mode 100755 index 0000000..d4f6e52 --- /dev/null +++ b/sbr/sigmsg.awk @@ -0,0 +1,87 @@ +# +# sigmsg.awk -- awk/nawk/gawk script to generate sigmsg.h +# +# provided by Geoff Wing +# +# $Id$ +# +# On SunOS 4.1.3 - user-functions don't work properly, also \" problems +# Without 0 + hacks some nawks compare numbers as strings +# +/^[\t ]*#[\t ]*define[\t _]*SIG[A-Z][A-Z0-9]*[\t ]*[1-9][0-9]*/ { + sigindex = index($0, "SIG") + sigtail = substr($0, sigindex, 80) + split(sigtail, tmp) + signam = substr(tmp[1], 4, 20) + signum = tmp[2] + if (sig[signum] == "") { + sig[signum] = signam + if (0 + max < 0 + signum && signum < 60) + max = signum + if (signam == "ABRT") { msg[signum] = "abort" } + if (signam == "ALRM") { msg[signum] = "alarm" } + if (signam == "BUS") { msg[signum] = "bus error" } + if (signam == "CHLD") { msg[signum] = "death of child" } + if (signam == "CLD") { msg[signum] = "death of child" } + if (signam == "CONT") { msg[signum] = "continued" } + if (signam == "EMT") { msg[signum] = "EMT instruction" } + if (signam == "FPE") { msg[signum] = "floating point exception" } + if (signam == "HUP") { msg[signum] = "hangup" } + if (signam == "ILL") { msg[signum] = "illegal hardware instruction" } + if (signam == "INFO") { msg[signum] = "status request from keyboard" } + if (signam == "INT") { msg[signum] = "interrupt" } + if (signam == "IO") { msg[signum] = "i/o ready" } + if (signam == "IOT") { msg[signum] = "IOT instruction" } + if (signam == "KILL") { msg[signum] = "killed" } + if (signam == "LOST") { msg[signum] = "resource lost" } + if (signam == "PIPE") { msg[signum] = "broken pipe" } + if (signam == "POLL") { msg[signum] = "pollable event occurred" } + if (signam == "PROF") { msg[signum] = "profile signal" } + if (signam == "PWR") { msg[signum] = "power fail" } + if (signam == "QUIT") { msg[signum] = "quit" } + if (signam == "SEGV") { msg[signum] = "segmentation fault" } + if (signam == "SYS") { msg[signum] = "invalid system call" } + if (signam == "TERM") { msg[signum] = "terminated" } + if (signam == "TRAP") { msg[signum] = "trace trap" } + if (signam == "URG") { msg[signum] = "urgent condition" } + if (signam == "USR1") { msg[signum] = "user-defined signal 1" } + if (signam == "USR2") { msg[signum] = "user-defined signal 2" } + if (signam == "VTALRM") { msg[signum] = "virtual time alarm" } + if (signam == "WINCH") { msg[signum] = "window size changed" } + if (signam == "XCPU") { msg[signum] = "cpu limit exceeded" } + if (signam == "XFSZ") { msg[signum] = "file size limit exceeded" } + } +} + +END { + ps = "%s" + ifdstr = sprintf("\t%cstopped%s%c,\n", 34, ps, 34) + + print "\n/*" + print " * sigmsg.h -- architecture-customized signal messages for nmh" + print " *" + print " * automatically generated by sigmsg.awk" + print " */\n" + printf("%s %d\n\n", "#define SIGCOUNT", max) + print "char *sigmsg[SIGCOUNT+2] = {" + print "\tNULL," + + for (i = 1; i <= 0 + max; i++) + if (msg[i] == "") { + if (sig[i] == "") + printf("\tNULL,\n") + else if (sig[i] == "STOP") + printf ifdstr, " (signal)", " (signal)" + else if (sig[i] == "TSTP") + printf ifdstr, "", "" + else if (sig[i] == "TTIN") + printf ifdstr, " (tty input)", " (tty input)" + else if (sig[i] == "TTOU") + printf ifdstr, " (tty output)", " (tty output)" + else + printf("\t%cSIG%s%c,\n", 34, sig[i], 34) + } else + printf("\t%c%s%c,\n", 34, msg[i], 34) + print "\tNULL" + print "};" +} diff --git a/sbr/signals.c b/sbr/signals.c new file mode 100644 index 0000000..a8c12d6 --- /dev/null +++ b/sbr/signals.c @@ -0,0 +1,116 @@ + +/* + * signals.c -- general signals interface for nmh + * + * $Id$ + */ + +#include +#include + + +int +SIGPROCMASK (int how, const sigset_t *set, sigset_t *oset) +{ +#ifdef POSIX_SIGNALS + return sigprocmask(how, set, oset); +#else +# ifdef BSD_SIGNALS + switch(how) { + case SIG_BLOCK: + *oset = sigblock(*set); + break; + case SIG_UNBLOCK: + sigfillset(*oset); + *oset = sigsetmask(*oset); + sigsetmask(*oset & ~(*set)); + break; + case SIG_SETMASK: + *oset = sigsetmask(*set); + break; + default: + adios(NULL, "unknown flag in SIGPROCMASK"); + break; + } + return 0; +# endif +#endif +} + + +/* + * A version of the function `signal' that uses reliable + * signals, if the machine supports them. Also, (assuming + * OS support), it restarts interrupted system calls for all + * signals except SIGALRM. + */ + +SIGNAL_HANDLER +SIGNAL (int sig, SIGNAL_HANDLER func) +{ +#ifdef POSIX_SIGNALS + struct sigaction act, oact; + + act.sa_handler = func; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + if (sig == SIGALRM) { +# ifdef SA_INTERRUPT + act.sa_flags |= SA_INTERRUPT; /* SunOS */ +# endif + } else { +# ifdef SA_RESTART + act.sa_flags |= SA_RESTART; /* SVR4, BSD4.4 */ +# endif + } + if (sigaction(sig, &act, &oact) < 0) + return (SIG_ERR); + return (oact.sa_handler); +#else + return signal (sig, func); +#endif +} + + +/* + * A version of the function `signal' that will set + * the handler of `sig' to `func' if the signal is + * not currently set to SIG_IGN. Also uses reliable + * signals if available. + */ +SIGNAL_HANDLER +SIGNAL2 (int sig, SIGNAL_HANDLER func) +{ +#ifdef POSIX_SIGNALS + struct sigaction act, oact; + + if (sigaction(sig, NULL, &oact) < 0) + return (SIG_ERR); + if (oact.sa_handler != SIG_IGN) { + act.sa_handler = func; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + if (sig == SIGALRM) { +# ifdef SA_INTERRUPT + act.sa_flags |= SA_INTERRUPT; /* SunOS */ +# endif + } else { +# ifdef SA_RESTART + act.sa_flags |= SA_RESTART; /* SVR4, BSD4.4 */ +# endif + } + if (sigaction(sig, &act, &oact) < 0) + return (SIG_ERR); + } + return (oact.sa_handler); +#else + SIGNAL_HANDLER ofunc; + + if ((ofunc = signal (sig, SIG_IGN)) != SIG_IGN) + signal (sig, func); + return ofunc; +#endif +} + diff --git a/sbr/smatch.c b/sbr/smatch.c new file mode 100644 index 0000000..54ff550 --- /dev/null +++ b/sbr/smatch.c @@ -0,0 +1,45 @@ + +/* + * smatch.c -- match a switch (option) + * + * $Id$ + */ + +#include + + +int +smatch(char *string, struct swit *swp) +{ + char *sp, *tcp; + int firstone, len; + struct swit *tp; + + firstone = UNKWNSW; + + if (!string) + return firstone; + len = strlen(string); + + for (tp = swp; tp->sw; tp++) { + tcp = tp->sw; + if (len < abs(tp->minchars)) + continue; /* no match */ + for (sp = string; *sp == *tcp++;) { + if (*sp++ == '\0') + return (tp - swp); /* exact match */ + } + if (*sp) { + if (*sp != ' ') + continue; /* no match */ + if (*--tcp == '\0') + return (tp - swp); /* exact match */ + } + if (firstone == UNKWNSW) + firstone = tp - swp; + else + firstone = AMBIGSW; + } + + return (firstone); +} diff --git a/sbr/snprintb.c b/sbr/snprintb.c new file mode 100644 index 0000000..06140c8 --- /dev/null +++ b/sbr/snprintb.c @@ -0,0 +1,38 @@ + +/* + * snprintb.c -- snprintf a %b string + * + * $Id$ + */ + +#include + + +char * +snprintb (char *buffer, size_t n, unsigned v, char *bits) +{ + register int i, j; + register char c, *bp; + + snprintf (buffer, n, bits && *bits == 010 ? "0%o" : "0x%x", v); + bp = buffer + strlen(buffer); + + if (bits && *++bits) { + j = 0; + *bp++ = '<'; + while ((i = *bits++)) + if (v & (1 << (i - 1))) { + if (j++) + *bp++ = ','; + for (; (c = *bits) > 32; bits++) + *bp++ = c; + } + else + for (; *bits > 32; bits++) + continue; + *bp++ = '>'; + *bp = 0; + } + + return buffer; +} diff --git a/sbr/snprintf.c b/sbr/snprintf.c new file mode 100644 index 0000000..efb6df0 --- /dev/null +++ b/sbr/snprintf.c @@ -0,0 +1,1045 @@ +/* + * snprintf.c -- formatted output to a string + * + * This is an implementation of snprintf() and vsnprintf() + * taken from the Apache web server. This is only used on + * systems which do not have a native version. + */ + +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + * This code is based on, and used with the permission of, the + * SIO stdio-replacement strx_* functions by Panos Tsirigotis + * for xinetd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + NO = 0, YES = 1 +} boolean_e; + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#define NUL '\0' +#define INT_NULL ((int *)0) +#define WIDE_INT long + +typedef struct { + char *curpos; + char *endpos; +} ap_vformatter_buff; + +typedef WIDE_INT wide_int; +typedef unsigned WIDE_INT u_wide_int; +typedef int bool_int; + +#define S_NULL "(null)" +#define S_NULL_LEN 6 + +#define FLOAT_DIGITS 6 +#define EXPONENT_LENGTH 10 + +/* These macros allow correct support of 8-bit characters on systems which + * support 8-bit characters. Pretty dumb how the cast is required, but + * that's legacy libc for ya. These new macros do not support EOF like + * the standard macros do. Tough. + */ +#define ap_isalpha(c) (isalpha(((unsigned char)(c)))) +#define ap_isdigit(c) (isdigit(((unsigned char)(c)))) +#define ap_islower(c) (islower(((unsigned char)(c)))) + +/* + * NUM_BUF_SIZE is the size of the buffer used for arithmetic conversions + * + * XXX: this is a magic number; do not decrease it + */ +#define NUM_BUF_SIZE 512 + +/* + * cvt.c - IEEE floating point formatting routines for FreeBSD + * from GNU libc-4.6.27. Modified to be thread safe. + */ + +/* + * ap_ecvt converts to decimal + * the number of digits is specified by ndigit + * decpt is set to the position of the decimal point + * sign is set to 0 for positive, 1 for negative + */ + +#define NDIG 80 + +/* buf must have at least NDIG bytes */ +static char *ap_cvt(double arg, int ndigits, int *decpt, int *sign, int eflag, char *buf) +{ + register int r2; + double fi, fj; + register char *p, *p1; + + if (ndigits >= NDIG - 1) + ndigits = NDIG - 2; + r2 = 0; + *sign = 0; + p = &buf[0]; + if (arg < 0) { + *sign = 1; + arg = -arg; + } + arg = modf(arg, &fi); + p1 = &buf[NDIG]; + /* + * Do integer part + */ + if (fi != 0) { + p1 = &buf[NDIG]; + while (fi != 0) { + fj = modf(fi / 10, &fi); + *--p1 = (int) ((fj + .03) * 10) + '0'; + r2++; + } + while (p1 < &buf[NDIG]) + *p++ = *p1++; + } + else if (arg > 0) { + while ((fj = arg * 10) < 1) { + arg = fj; + r2--; + } + } + p1 = &buf[ndigits]; + if (eflag == 0) + p1 += r2; + *decpt = r2; + if (p1 < &buf[0]) { + buf[0] = '\0'; + return (buf); + } + while (p <= p1 && p < &buf[NDIG]) { + arg *= 10; + arg = modf(arg, &fj); + *p++ = (int) fj + '0'; + } + if (p1 >= &buf[NDIG]) { + buf[NDIG - 1] = '\0'; + return (buf); + } + p = p1; + *p1 += 5; + while (*p1 > '9') { + *p1 = '0'; + if (p1 > buf) + ++ * --p1; + else { + *p1 = '1'; + (*decpt)++; + if (eflag == 0) { + if (p > buf) + *p = '0'; + p++; + } + } + } + *p = '\0'; + return (buf); +} + +static char *ap_ecvt(double arg, int ndigits, int *decpt, int *sign, char *buf) +{ + return (ap_cvt(arg, ndigits, decpt, sign, 1, buf)); +} + +static char *ap_fcvt(double arg, int ndigits, int *decpt, int *sign, char *buf) +{ + return (ap_cvt(arg, ndigits, decpt, sign, 0, buf)); +} + +/* + * ap_gcvt - Floating output conversion to + * minimal length string + */ + +static char *ap_gcvt(double number, int ndigit, char *buf, boolean_e altform) +{ + int sign, decpt; + register char *p1, *p2; + register int i; + char buf1[NDIG]; + + p1 = ap_ecvt(number, ndigit, &decpt, &sign, buf1); + p2 = buf; + if (sign) + *p2++ = '-'; + for (i = ndigit - 1; i > 0 && p1[i] == '0'; i--) + ndigit--; + if ((decpt >= 0 && decpt - ndigit > 4) + || (decpt < 0 && decpt < -3)) { /* use E-style */ + decpt--; + *p2++ = *p1++; + *p2++ = '.'; + for (i = 1; i < ndigit; i++) + *p2++ = *p1++; + *p2++ = 'e'; + if (decpt < 0) { + decpt = -decpt; + *p2++ = '-'; + } + else + *p2++ = '+'; + if (decpt / 100 > 0) + *p2++ = decpt / 100 + '0'; + if (decpt / 10 > 0) + *p2++ = (decpt % 100) / 10 + '0'; + *p2++ = decpt % 10 + '0'; + } + else { + if (decpt <= 0) { + if (*p1 != '0') + *p2++ = '.'; + while (decpt < 0) { + decpt++; + *p2++ = '0'; + } + } + for (i = 1; i <= ndigit; i++) { + *p2++ = *p1++; + if (i == decpt) + *p2++ = '.'; + } + if (ndigit < decpt) { + while (ndigit++ < decpt) + *p2++ = '0'; + *p2++ = '.'; + } + } + if (p2[-1] == '.' && !altform) + p2--; + *p2 = '\0'; + return (buf); +} + +/* + * The INS_CHAR macro inserts a character in the buffer and writes + * the buffer back to disk if necessary + * It uses the char pointers sp and bep: + * sp points to the next available character in the buffer + * bep points to the end-of-buffer+1 + * While using this macro, note that the nextb pointer is NOT updated. + * + * NOTE: Evaluation of the c argument should not have any side-effects + */ +#define INS_CHAR(c, sp, bep, cc) \ + { \ + if (sp >= bep) { \ + vbuff->curpos = sp; \ + if (flush_func(vbuff)) \ + return -1; \ + sp = vbuff->curpos; \ + bep = vbuff->endpos; \ + } \ + *sp++ = (c); \ + cc++; \ + } + +#define NUM( c ) ( c - '0' ) + +#define STR_TO_DEC( str, num ) \ + num = NUM( *str++ ) ; \ + while ( ap_isdigit( *str ) ) \ + { \ + num *= 10 ; \ + num += NUM( *str++ ) ; \ + } + +/* + * This macro does zero padding so that the precision + * requirement is satisfied. The padding is done by + * adding '0's to the left of the string that is going + * to be printed. + */ +#define FIX_PRECISION( adjust, precision, s, s_len ) \ + if ( adjust ) \ + while ( s_len < precision ) \ + { \ + *--s = '0' ; \ + s_len++ ; \ + } + +/* + * Macro that does padding. The padding is done by printing + * the character ch. + */ +#define PAD( width, len, ch ) do \ + { \ + INS_CHAR( ch, sp, bep, cc ) ; \ + width-- ; \ + } \ + while ( width > len ) + +/* + * Prefix the character ch to the string str + * Increase length + * Set the has_prefix flag + */ +#define PREFIX( str, length, ch ) *--str = ch ; length++ ; has_prefix = YES + + +/* + * Convert num to its decimal format. + * Return value: + * - a pointer to a string containing the number (no sign) + * - len contains the length of the string + * - is_negative is set to TRUE or FALSE depending on the sign + * of the number (always set to FALSE if is_unsigned is TRUE) + * + * The caller provides a buffer for the string: that is the buf_end argument + * which is a pointer to the END of the buffer + 1 (i.e. if the buffer + * is declared as buf[ 100 ], buf_end should be &buf[ 100 ]) + */ +static char *conv_10(register wide_int num, register bool_int is_unsigned, + register bool_int *is_negative, char *buf_end, + register int *len) +{ + register char *p = buf_end; + register u_wide_int magnitude; + + if (is_unsigned) { + magnitude = (u_wide_int) num; + *is_negative = FALSE; + } + else { + *is_negative = (num < 0); + + /* + * On a 2's complement machine, negating the most negative integer + * results in a number that cannot be represented as a signed integer. + * Here is what we do to obtain the number's magnitude: + * a. add 1 to the number + * b. negate it (becomes positive) + * c. convert it to unsigned + * d. add 1 + */ + if (*is_negative) { + wide_int t = num + 1; + + magnitude = ((u_wide_int) -t) + 1; + } + else + magnitude = (u_wide_int) num; + } + + /* + * We use a do-while loop so that we write at least 1 digit + */ + do { + register u_wide_int new_magnitude = magnitude / 10; + + *--p = (char) (magnitude - new_magnitude * 10 + '0'); + magnitude = new_magnitude; + } + while (magnitude); + + *len = buf_end - p; + return (p); +} + + + +static char *conv_in_addr(struct in_addr *ia, char *buf_end, int *len) +{ + unsigned addr = ntohl(ia->s_addr); + char *p = buf_end; + bool_int is_negative; + int sub_len; + + p = conv_10((addr & 0x000000FF) , TRUE, &is_negative, p, &sub_len); + *--p = '.'; + p = conv_10((addr & 0x0000FF00) >> 8, TRUE, &is_negative, p, &sub_len); + *--p = '.'; + p = conv_10((addr & 0x00FF0000) >> 16, TRUE, &is_negative, p, &sub_len); + *--p = '.'; + p = conv_10((addr & 0xFF000000) >> 24, TRUE, &is_negative, p, &sub_len); + + *len = buf_end - p; + return (p); +} + + + +static char *conv_sockaddr_in(struct sockaddr_in *si, char *buf_end, int *len) +{ + char *p = buf_end; + bool_int is_negative; + int sub_len; + + p = conv_10(ntohs(si->sin_port), TRUE, &is_negative, p, &sub_len); + *--p = ':'; + p = conv_in_addr(&si->sin_addr, p, &sub_len); + + *len = buf_end - p; + return (p); +} + + + +/* + * Convert a floating point number to a string formats 'f', 'e' or 'E'. + * The result is placed in buf, and len denotes the length of the string + * The sign is returned in the is_negative argument (and is not placed + * in buf). + */ +static char *conv_fp(register char format, register double num, + boolean_e add_dp, int precision, bool_int *is_negative, + char *buf, int *len) +{ + register char *s = buf; + register char *p; + int decimal_point; + char buf1[NDIG]; + + if (format == 'f') + p = ap_fcvt(num, precision, &decimal_point, is_negative, buf1); + else /* either e or E format */ + p = ap_ecvt(num, precision + 1, &decimal_point, is_negative, buf1); + + /* + * Check for Infinity and NaN + */ + if (ap_isalpha(*p)) { + *len = strlen(strcpy(buf, p)); + *is_negative = FALSE; + return (buf); + } + + if (format == 'f') { + if (decimal_point <= 0) { + *s++ = '0'; + if (precision > 0) { + *s++ = '.'; + while (decimal_point++ < 0) + *s++ = '0'; + } + else if (add_dp) + *s++ = '.'; + } + else { + while (decimal_point-- > 0) + *s++ = *p++; + if (precision > 0 || add_dp) + *s++ = '.'; + } + } + else { + *s++ = *p++; + if (precision > 0 || add_dp) + *s++ = '.'; + } + + /* + * copy the rest of p, the NUL is NOT copied + */ + while (*p) + *s++ = *p++; + + if (format != 'f') { + char temp[EXPONENT_LENGTH]; /* for exponent conversion */ + int t_len; + bool_int exponent_is_negative; + + *s++ = format; /* either e or E */ + decimal_point--; + if (decimal_point != 0) { + p = conv_10((wide_int) decimal_point, FALSE, &exponent_is_negative, + &temp[EXPONENT_LENGTH], &t_len); + *s++ = exponent_is_negative ? '-' : '+'; + + /* + * Make sure the exponent has at least 2 digits + */ + if (t_len == 1) + *s++ = '0'; + while (t_len--) + *s++ = *p++; + } + else { + *s++ = '+'; + *s++ = '0'; + *s++ = '0'; + } + } + + *len = s - buf; + return (buf); +} + + +/* + * Convert num to a base X number where X is a power of 2. nbits determines X. + * For example, if nbits is 3, we do base 8 conversion + * Return value: + * a pointer to a string containing the number + * + * The caller provides a buffer for the string: that is the buf_end argument + * which is a pointer to the END of the buffer + 1 (i.e. if the buffer + * is declared as buf[ 100 ], buf_end should be &buf[ 100 ]) + */ +static char *conv_p2(register u_wide_int num, register int nbits, + char format, char *buf_end, register int *len) +{ + register int mask = (1 << nbits) - 1; + register char *p = buf_end; + static const char low_digits[] = "0123456789abcdef"; + static const char upper_digits[] = "0123456789ABCDEF"; + register const char *digits = (format == 'X') ? upper_digits : low_digits; + + do { + *--p = digits[num & mask]; + num >>= nbits; + } + while (num); + + *len = buf_end - p; + return (p); +} + + +/* + * Do format conversion placing the output in buffer + */ +int ap_vformatter(int (*flush_func)(ap_vformatter_buff *), + ap_vformatter_buff *vbuff, const char *fmt, va_list ap) +{ + register char *sp; + register char *bep; + register int cc = 0; + register int i; + + register char *s = NULL; + char *q; + int s_len; + + register int min_width = 0; + int precision = 0; + enum { + LEFT, RIGHT + } adjust; + char pad_char; + char prefix_char; + + double fp_num; + wide_int i_num = (wide_int) 0; + u_wide_int ui_num; + + char num_buf[NUM_BUF_SIZE]; + char char_buf[2]; /* for printing %% and % */ + + /* + * Flag variables + */ + boolean_e is_long; + boolean_e alternate_form; + boolean_e print_sign; + boolean_e print_blank; + boolean_e adjust_precision; + boolean_e adjust_width; + bool_int is_negative; + + sp = vbuff->curpos; + bep = vbuff->endpos; + + while (*fmt) { + if (*fmt != '%') { + INS_CHAR(*fmt, sp, bep, cc); + } + else { + /* + * Default variable settings + */ + adjust = RIGHT; + alternate_form = print_sign = print_blank = NO; + pad_char = ' '; + prefix_char = NUL; + + fmt++; + + /* + * Try to avoid checking for flags, width or precision + */ + if (!ap_islower(*fmt)) { + /* + * Recognize flags: -, #, BLANK, + + */ + for (;; fmt++) { + if (*fmt == '-') + adjust = LEFT; + else if (*fmt == '+') + print_sign = YES; + else if (*fmt == '#') + alternate_form = YES; + else if (*fmt == ' ') + print_blank = YES; + else if (*fmt == '0') + pad_char = '0'; + else + break; + } + + /* + * Check if a width was specified + */ + if (ap_isdigit(*fmt)) { + STR_TO_DEC(fmt, min_width); + adjust_width = YES; + } + else if (*fmt == '*') { + min_width = va_arg(ap, int); + fmt++; + adjust_width = YES; + if (min_width < 0) { + adjust = LEFT; + min_width = -min_width; + } + } + else + adjust_width = NO; + + /* + * Check if a precision was specified + * + * XXX: an unreasonable amount of precision may be specified + * resulting in overflow of num_buf. Currently we + * ignore this possibility. + */ + if (*fmt == '.') { + adjust_precision = YES; + fmt++; + if (ap_isdigit(*fmt)) { + STR_TO_DEC(fmt, precision); + } + else if (*fmt == '*') { + precision = va_arg(ap, int); + fmt++; + if (precision < 0) + precision = 0; + } + else + precision = 0; + } + else + adjust_precision = NO; + } + else + adjust_precision = adjust_width = NO; + + /* + * Modifier check + */ + if (*fmt == 'l') { + is_long = YES; + fmt++; + } + else { + if (*fmt == 'h') /* "short" backward compatibility */ + ++fmt; + is_long = NO; + } + + /* + * Argument extraction and printing. + * First we determine the argument type. + * Then, we convert the argument to a string. + * On exit from the switch, s points to the string that + * must be printed, s_len has the length of the string + * The precision requirements, if any, are reflected in s_len. + * + * NOTE: pad_char may be set to '0' because of the 0 flag. + * It is reset to ' ' by non-numeric formats + */ + switch (*fmt) { + case 'u': + if (is_long) + i_num = va_arg(ap, u_wide_int); + else + i_num = (wide_int) va_arg(ap, unsigned int); + s = conv_10(i_num, 1, &is_negative, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + break; + + case 'd': + case 'i': + if (is_long) + i_num = va_arg(ap, wide_int); + else + i_num = (wide_int) va_arg(ap, int); + s = conv_10(i_num, 0, &is_negative, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + + if (is_negative) + prefix_char = '-'; + else if (print_sign) + prefix_char = '+'; + else if (print_blank) + prefix_char = ' '; + break; + + + case 'o': + if (is_long) + ui_num = va_arg(ap, u_wide_int); + else + ui_num = (u_wide_int) va_arg(ap, unsigned int); + s = conv_p2(ui_num, 3, *fmt, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + if (alternate_form && *s != '0') { + *--s = '0'; + s_len++; + } + break; + + + case 'x': + case 'X': + if (is_long) + ui_num = (u_wide_int) va_arg(ap, u_wide_int); + else + ui_num = (u_wide_int) va_arg(ap, unsigned int); + s = conv_p2(ui_num, 4, *fmt, + &num_buf[NUM_BUF_SIZE], &s_len); + FIX_PRECISION(adjust_precision, precision, s, s_len); + if (alternate_form && i_num != 0) { + *--s = *fmt; /* 'x' or 'X' */ + *--s = '0'; + s_len += 2; + } + break; + + + case 's': + s = va_arg(ap, char *); + if (s != NULL) { + s_len = strlen(s); + if (adjust_precision && precision < s_len) + s_len = precision; + } + else { + s = S_NULL; + s_len = S_NULL_LEN; + } + pad_char = ' '; + break; + + + case 'f': + case 'e': + case 'E': + fp_num = va_arg(ap, double); + /* + * * We use &num_buf[ 1 ], so that we have room for the sign + */ + s = conv_fp(*fmt, fp_num, alternate_form, + (adjust_precision == NO) ? FLOAT_DIGITS : precision, + &is_negative, &num_buf[1], &s_len); + if (is_negative) + prefix_char = '-'; + else if (print_sign) + prefix_char = '+'; + else if (print_blank) + prefix_char = ' '; + break; + + + case 'g': + case 'G': + if (adjust_precision == NO) + precision = FLOAT_DIGITS; + else if (precision == 0) + precision = 1; + /* + * * We use &num_buf[ 1 ], so that we have room for the sign + */ + s = ap_gcvt(va_arg(ap, double), precision, &num_buf[1], + alternate_form); + if (*s == '-') + prefix_char = *s++; + else if (print_sign) + prefix_char = '+'; + else if (print_blank) + prefix_char = ' '; + + s_len = strlen(s); + + if (alternate_form && (q = strchr(s, '.')) == NULL) { + s[s_len++] = '.'; + s[s_len] = '\0'; /* delimit for following strchr() */ + } + if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL) + *q = 'E'; + break; + + + case 'c': + char_buf[0] = (char) (va_arg(ap, int)); + s = &char_buf[0]; + s_len = 1; + pad_char = ' '; + break; + + + case '%': + char_buf[0] = '%'; + s = &char_buf[0]; + s_len = 1; + pad_char = ' '; + break; + + + case 'n': + *(va_arg(ap, int *)) = cc; + break; + + /* + * This is where we extend the printf format, with a second + * type specifier + */ + case 'p': + switch(*++fmt) { + /* + * If the pointer size is equal to the size of an unsigned + * integer we convert the pointer to a hex number, otherwise + * we print "%p" to indicate that we don't handle "%p". + */ + case 'p': + ui_num = (u_wide_int) va_arg(ap, void *); + + if (sizeof(char *) <= sizeof(u_wide_int)) + s = conv_p2(ui_num, 4, 'x', + &num_buf[NUM_BUF_SIZE], &s_len); + else { + s = "%p"; + s_len = 2; + prefix_char = NUL; + } + pad_char = ' '; + break; + + /* print a struct sockaddr_in as a.b.c.d:port */ + case 'I': + { + struct sockaddr_in *si; + + si = va_arg(ap, struct sockaddr_in *); + if (si != NULL) { + s = conv_sockaddr_in(si, &num_buf[NUM_BUF_SIZE], &s_len); + if (adjust_precision && precision < s_len) + s_len = precision; + } + else { + s = S_NULL; + s_len = S_NULL_LEN; + } + pad_char = ' '; + } + break; + + /* print a struct in_addr as a.b.c.d */ + case 'A': + { + struct in_addr *ia; + + ia = va_arg(ap, struct in_addr *); + if (ia != NULL) { + s = conv_in_addr(ia, &num_buf[NUM_BUF_SIZE], &s_len); + if (adjust_precision && precision < s_len) + s_len = precision; + } + else { + s = S_NULL; + s_len = S_NULL_LEN; + } + pad_char = ' '; + } + break; + + case NUL: + /* if %p ends the string, oh well ignore it */ + continue; + + default: + s = "bogus %p"; + s_len = 8; + prefix_char = NUL; + break; + } + break; + + case NUL: + /* + * The last character of the format string was %. + * We ignore it. + */ + continue; + + + /* + * The default case is for unrecognized %'s. + * We print % to help the user identify what + * option is not understood. + * This is also useful in case the user wants to pass + * the output of format_converter to another function + * that understands some other % (like syslog). + * Note that we can't point s inside fmt because the + * unknown could be preceded by width etc. + */ + default: + char_buf[0] = '%'; + char_buf[1] = *fmt; + s = char_buf; + s_len = 2; + pad_char = ' '; + break; + } + + if (prefix_char != NUL && s != S_NULL && s != char_buf) { + *--s = prefix_char; + s_len++; + } + + if (adjust_width && adjust == RIGHT && min_width > s_len) { + if (pad_char == '0' && prefix_char != NUL) { + INS_CHAR(*s, sp, bep, cc); + s++; + s_len--; + min_width--; + } + PAD(min_width, s_len, pad_char); + } + + /* + * Print the string s. + */ + for (i = s_len; i != 0; i--) { + INS_CHAR(*s, sp, bep, cc); + s++; + } + + if (adjust_width && adjust == LEFT && min_width > s_len) + PAD(min_width, s_len, pad_char); + } + fmt++; + } + vbuff->curpos = sp; + return cc; +} + + +static int snprintf_flush(ap_vformatter_buff *vbuff) +{ + /* if the buffer fills we have to abort immediately, there is no way + * to "flush" a snprintf... there's nowhere to flush it to. + */ + return -1; +} + + +int snprintf(char *buf, size_t len, const char *format,...) +{ + int cc; + va_list ap; + ap_vformatter_buff vbuff; + + if (len == 0) + return 0; + + /* save one byte for nul terminator */ + vbuff.curpos = buf; + vbuff.endpos = buf + len - 1; + va_start(ap, format); + cc = ap_vformatter(snprintf_flush, &vbuff, format, ap); + va_end(ap); + *vbuff.curpos = '\0'; + return (cc == -1) ? len : cc; +} + + +int vsnprintf(char *buf, size_t len, const char *format, va_list ap) +{ + int cc; + ap_vformatter_buff vbuff; + + if (len == 0) + return 0; + + /* save one byte for nul terminator */ + vbuff.curpos = buf; + vbuff.endpos = buf + len - 1; + cc = ap_vformatter(snprintf_flush, &vbuff, format, ap); + *vbuff.curpos = '\0'; + return (cc == -1) ? len : cc; +} diff --git a/sbr/ssequal.c b/sbr/ssequal.c new file mode 100644 index 0000000..dd5c097 --- /dev/null +++ b/sbr/ssequal.c @@ -0,0 +1,27 @@ + +/* + * ssequal.c -- check if a string is a substring of another + * + * $Id$ + */ + +#include + +/* + * Check if s1 is a substring of s2. + * If yes, then return 1, else return 0. + */ + +int +ssequal (char *s1, char *s2) +{ + if (!s1) + s1 = ""; + if (!s2) + s2 = ""; + + while (*s1) + if (*s1++ != *s2++) + return 0; + return 1; +} diff --git a/sbr/strcasecmp.c b/sbr/strcasecmp.c new file mode 100644 index 0000000..bbb26cb --- /dev/null +++ b/sbr/strcasecmp.c @@ -0,0 +1,53 @@ + +/* + * strcasecmp.c -- compare strings, ignoring case + * + * $Id$ + */ + +#include + +/* + * Our version of strcasecmp has to deal with NULL strings. + * Once that is fixed in the rest of the code, we can use the + * native version, instead of this one. + */ + +int +strcasecmp (const char *s1, const char *s2) +{ + const unsigned char *us1, *us2; + + us1 = (const unsigned char *) s1, + us2 = (const unsigned char *) s2; + + if (!us1) + us1 = ""; + if (!us2) + us2 = ""; + + while (tolower(*us1) == tolower(*us2++)) + if (*us1++ == '\0') + return (0); + return (tolower(*us1) - tolower(*--us2)); +} + + +int +strncasecmp (const char *s1, const char *s2, size_t n) +{ + const unsigned char *us1, *us2; + + if (n != 0) { + us1 = (const unsigned char *) s1, + us2 = (const unsigned char *) s2; + + do { + if (tolower(*us1) != tolower(*us2++)) + return (tolower(*us1) - tolower(*--us2)); + if (*us1++ == '\0') + break; + } while (--n != 0); + } + return (0); +} diff --git a/sbr/strdup.c b/sbr/strdup.c new file mode 100644 index 0000000..05a017e --- /dev/null +++ b/sbr/strdup.c @@ -0,0 +1,25 @@ + +/* + * strdup.c -- duplicate a string + * + * $Id$ + */ + +#include + + +char * +strdup (char *str) +{ + char *cp; + size_t len; + + if (!str) + return NULL; + + len = strlen(str) + 1; + if (!(cp = malloc (len))) + return NULL; + memcpy (cp, str, len); + return cp; +} diff --git a/sbr/strerror.c b/sbr/strerror.c new file mode 100644 index 0000000..d780955 --- /dev/null +++ b/sbr/strerror.c @@ -0,0 +1,21 @@ + +/* + * strerror.c -- get error message string + * + * $Id$ + */ + +#include + +extern int sys_nerr; +extern char *sys_errlist[]; + + +char * +strerror (int errnum) +{ + if (errnum > 0 && errnum < sys_nerr) + return sys_errlist[errnum]; + else + return NULL; +} diff --git a/sbr/strindex.c b/sbr/strindex.c new file mode 100644 index 0000000..03910fd --- /dev/null +++ b/sbr/strindex.c @@ -0,0 +1,23 @@ + +/* + * strindex.c -- "unsigned" lexical index + * + * $Id$ + */ + +#include + +int +stringdex (char *p1, char *p2) +{ + char *p; + + if (p1 == NULL || p2 == NULL) + return -1; + + for (p = p2; *p; p++) + if (uprf (p, p1)) + return (p - p2); + + return -1; +} diff --git a/sbr/trimcpy.c b/sbr/trimcpy.c new file mode 100644 index 0000000..d135548 --- /dev/null +++ b/sbr/trimcpy.c @@ -0,0 +1,38 @@ + +/* + * trimcpy.c -- strip leading and trailing whitespace, + * -- replace internal whitespace with spaces, + * -- then return a copy. + * + * $Id$ + */ + +#include + + +char * +trimcpy (char *cp) +{ + char *sp; + + /* skip over leading whitespace */ + while (isspace(*cp)) + cp++; + + /* start at the end and zap trailing whitespace */ + for (sp = cp + strlen(cp) - 1; sp >= cp; sp--) { + if (isspace(*sp)) + *sp = '\0'; + else + break; + } + + /* replace remaining whitespace with spaces */ + for (sp = cp; *sp; sp++) { + if (isspace(*sp)) + *sp = ' '; + } + + /* now return a copy */ + return getcpy(cp); +} diff --git a/sbr/uprf.c b/sbr/uprf.c new file mode 100644 index 0000000..7adc321 --- /dev/null +++ b/sbr/uprf.c @@ -0,0 +1,39 @@ + +/* + * uprf.c -- "unsigned" lexical prefix + * + * $Id$ + */ + +#include + +#define TO_LOWER 040 +#define NO_MASK 000 + + +int +uprf (char *c1, char *c2) +{ + int c, mask; + + if (!(c1 && c2)) + return 0; + + while ((c = *c2++)) + { +#ifdef LOCALE + c &= 0xff; + mask = *c1 & 0xff; + c = (isalpha(c) && isupper(c)) ? tolower(c) : c; + mask = (isalpha(mask) && isupper(mask)) ? tolower(mask) : mask; + if (c != mask) +#else + mask = (isalpha(c) && isalpha(*c1)) ? TO_LOWER : NO_MASK; + if ((c | mask) != (*c1 | mask)) +#endif + return 0; + else + c1++; + } + return 1; +} diff --git a/sbr/vfgets.c b/sbr/vfgets.c new file mode 100644 index 0000000..52a8e74 --- /dev/null +++ b/sbr/vfgets.c @@ -0,0 +1,73 @@ + +/* + * vfgets.c -- virtual fgets + * + * $Id$ + */ + +#include + +#define QUOTE '\\' + + +int +vfgets (FILE *in, char **bp) +{ + int toggle; + char *cp, *dp, *ep, *fp; + static int len = 0; + static char *pp = NULL; + + if (pp == NULL) + if (!(pp = malloc ((size_t) (len = BUFSIZ)))) + adios (NULL, "unable to allocate string storage"); + + for (ep = (cp = pp) + len - 1;;) { + if (fgets (cp, ep - cp + 1, in) == NULL) { + if (cp != pp) { + *bp = pp; + return 0; + } + return (ferror (in) && !feof (in) ? -1 : 1); + } + + if ((dp = cp + strlen (cp) - 2) < cp || *dp != QUOTE) { +wrong_guess: + if (cp > ++dp) + adios (NULL, "vfgets() botch -- you lose big"); + if (*dp == '\n') { + *bp = pp; + return 0; + } else { + cp = ++dp; + } + } else { + for (fp = dp - 1, toggle = 0; fp >= cp; fp--) { + if (*fp != QUOTE) + break; + else + toggle = !toggle; + } + if (toggle) + goto wrong_guess; + + if (*++dp == '\n') { + *--dp = 0; + cp = dp; + } else { + cp = ++dp; + } + } + + if (cp >= ep) { + int curlen = cp - pp; + + if (!(dp = realloc (pp, (size_t) (len += BUFSIZ)))) { + adios (NULL, "unable to allocate string storage"); + } else { + cp = dp + curlen; + ep = (pp = dp) + len - 1; + } + } + } +} diff --git a/stamp-h.in b/stamp-h.in new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/stamp-h.in @@ -0,0 +1 @@ + diff --git a/uip/Makefile.in b/uip/Makefile.in new file mode 100644 index 0000000..c292bd2 --- /dev/null +++ b/uip/Makefile.in @@ -0,0 +1,307 @@ +# +# Makefile for uip subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +HESIOD_INCLUDES = @HESIOD_INCLUDES@ +INCLUDES = -I.. -I$(srcdir) -I$(top_srcdir) $(HESIOD_INCLUDES) +LDFLAGS = @LDFLAGS@ + +LIBS = @LIBS@ +MTSLIB = @MTSLIB@ +KRB4_LIBS = @KRB4_LIBS@ +HESIOD_LIBS = @HESIOD_LIBS@ +LOCALLIBS = ../config/version.o ../config/config.o ../sbr/libmh.a ../$(MTSLIB) ../zotnet/libzot.a +LINKLIBS = $(LOCALLIBS) $(KRB4_LIBS) $(HESIOD_LIBS) $(LIBS) + +TERMLIB = @TERMLIB@ +LEXLIB = @LEXLIB@ +POPLIB = @POPLIB@ + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) +LINK = $(CC) $(LDFLAGS) -o $@ +LN = ln + +INSTALL = @INSTALL@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# commands to build +CMDS = ali anno burst comp dist flist folder forw inc mark mhbuild \ + mhlist mhmail mhn mhparam mhpath mhshow mhstore mhtest msgchk \ + msh packf pick prompter refile repl rmf rmm scan send show \ + sortm viamail whatnow whom + +## removed this from CMDS until I can fix it +## OTHERCMDS = vmh + +# commands that are links to other commands +LCMDS = flists folders next prev + +# misc support binaries +MISC = ap conflict dp fmtdump install-mh mhl post rcvdist rcvpack \ + rcvstore rcvtty slocal spost + +# source files +SRCS = ali.c aliasbr.c anno.c annosbr.c ap.c burst.c comp.c \ + conflict.c dist.c distsbr.c dp.c dropsbr.c flist.c fmtdump.c \ + folder.c forw.c ftpsbr.c inc.c install-mh.c mark.c md5.c mhbuild.c \ + mhbuildsbr.c mhcachesbr.c mhfree.c mhl.c mhlist.c mhlistsbr.c mhlsbr.c \ + mhmail.c mhmisc.c mhn.c mhoutsbr.c mhparam.c mhparse.c mhpath.c mhshow.c \ + mhshowsbr.c mhstore.c mhstoresbr.c mhtest.c msgchk.c msh.c mshcmds.c packf.c \ + pick.c picksbr.c popi.c popsbr.c post.c prompter.c rcvdist.c rcvpack.c \ + rcvstore.c rcvtty.c refile.c repl.c replsbr.c rmf.c rmm.c scan.c \ + scansbr.c send.c sendsbr.c show.c slocal.c sortm.c spost.c \ + termsbr.c viamail.c vmh.c vmhsbr.c vmhtest.c whatnow.c whatnowproc.c \ + whatnowsbr.c whom.c wmh.c + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(SRCS) $(AUX) + +# ========== DEFAULT TARGET ========== + +all: $(CMDS) $(MISC) + +# ========= DEPENDENCIES FOR BUILDING ========== + +ali: ali.o aliasbr.o $(LOCALLIBS) + $(LINK) ali.o aliasbr.o $(LINKLIBS) + +ap: ap.o termsbr.o $(LOCALLIBS) + $(LINK) ap.o termsbr.o $(LINKLIBS) $(TERMLIB) + +anno: anno.o annosbr.o $(LOCALLIBS) + $(LINK) anno.o annosbr.o $(LINKLIBS) + +burst: burst.o $(LOCALLIBS) + $(LINK) burst.o $(LINKLIBS) + +comp: comp.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) comp.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) + +conflict: conflict.o aliasbr.o $(LOCALLIBS) + $(LINK) conflict.o aliasbr.o $(LINKLIBS) + +dist: dist.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) dist.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) + +dp: dp.o termsbr.o $(LOCALLIBS) + $(LINK) dp.o termsbr.o $(LINKLIBS) $(TERMLIB) + +flist: flist.o $(LOCALLIBS) + $(LINK) flist.o $(LINKLIBS) + +fmtdump: fmtdump.o $(LOCALLIBS) + $(LINK) fmtdump.o $(LINKLIBS) + +folder: folder.o $(LOCALLIBS) + $(LINK) folder.o $(LINKLIBS) + +forw: forw.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) forw.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) + +inc: inc.o scansbr.o dropsbr.o termsbr.o $(POPLIB) $(LOCALLIBS) + $(LINK) inc.o scansbr.o dropsbr.o termsbr.o $(POPLIB) $(LINKLIBS) $(TERMLIB) + +install-mh: install-mh.o $(LOCALLIBS) + $(LINK) install-mh.o $(LINKLIBS) + +mark: mark.o $(LOCALLIBS) + $(LINK) mark.o $(LINKLIBS) + +mhbuild: mhbuild.o mhbuildsbr.o mhcachesbr.o mhlistsbr.o mhoutsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhbuild.o mhbuildsbr.o mhcachesbr.o mhlistsbr.o mhoutsbr.o mhmisc.o mhfree.o ftpsbr.o md5.o $(LINKLIBS) $(TERMLIB) + +mhl: mhl.o mhlsbr.o termsbr.o $(LOCALLIBS) + $(LINK) mhl.o mhlsbr.o termsbr.o $(LINKLIBS) $(TERMLIB) + +mhlist: mhlist.o mhparse.o mhcachesbr.o mhlistsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhlist.o mhparse.o mhcachesbr.o mhlistsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LINKLIBS) $(TERMLIB) + +mhmail: mhmail.o $(LOCALLIBS) + $(LINK) mhmail.o $(LINKLIBS) + +mhn: mhn.o mhparse.o mhcachesbr.o mhshowsbr.o mhlistsbr.o mhstoresbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhn.o mhparse.o mhcachesbr.o mhshowsbr.o mhlistsbr.o mhstoresbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LINKLIBS) $(TERMLIB) + +mhparam: mhparam.o $(LOCALLIBS) + $(LINK) mhparam.o $(LINKLIBS) + +mhpath: mhpath.o $(LOCALLIBS) + $(LINK) mhpath.o $(LINKLIBS) + +mhshow: mhshow.o mhparse.o mhcachesbr.o mhshowsbr.o mhlistsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhshow.o mhparse.o mhcachesbr.o mhshowsbr.o mhlistsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LINKLIBS) $(TERMLIB) + +mhstore: mhstore.o mhparse.o mhcachesbr.o mhshowsbr.o mhlistsbr.o mhstoresbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhstore.o mhparse.o mhcachesbr.o mhshowsbr.o mhlistsbr.o mhstoresbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LINKLIBS) $(TERMLIB) + +mhtest: mhtest.o mhparse.o mhcachesbr.o mhoutsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LOCALLIBS) + $(LINK) mhtest.o mhparse.o mhcachesbr.o mhoutsbr.o mhmisc.o mhfree.o ftpsbr.o termsbr.o md5.o $(LINKLIBS) $(TERMLIB) + +msgchk: msgchk.o $(POPLIB) $(LOCALLIBS) + $(LINK) msgchk.o $(POPLIB) $(LINKLIBS) + +msh: msh.o mshcmds.o vmhsbr.o picksbr.o scansbr.o dropsbr.o mhlsbr.o termsbr.o $(LOCALLIBS) + $(LINK) msh.o mshcmds.o vmhsbr.o picksbr.o scansbr.o dropsbr.o mhlsbr.o termsbr.o $(LINKLIBS) $(TERMLIB) + +packf: packf.o dropsbr.o $(LOCALLIBS) + $(LINK) packf.o dropsbr.o $(LINKLIBS) + +pick: pick.o picksbr.o $(LOCALLIBS) + $(LINK) pick.o picksbr.o $(LINKLIBS) + +post: post.o aliasbr.o $(LOCALLIBS) + $(LINK) post.o aliasbr.o $(LINKLIBS) + +prompter: prompter.o $(LOCALLIBS) + $(LINK) prompter.o $(LINKLIBS) + +rcvdist: rcvdist.o distsbr.o $(LOCALLIBS) + $(LINK) rcvdist.o distsbr.o $(LINKLIBS) + +rcvpack: rcvpack.o dropsbr.o $(LOCALLIBS) + $(LINK) rcvpack.o dropsbr.o $(LINKLIBS) + +rcvstore: rcvstore.o $(LOCALLIBS) + $(LINK) rcvstore.o $(LINKLIBS) + +rcvtty: rcvtty.o scansbr.o termsbr.o $(LOCALLIBS) + $(LINK) rcvtty.o scansbr.o termsbr.o $(LINKLIBS) $(TERMLIB) + +refile: refile.o $(LOCALLIBS) + $(LINK) refile.o $(LINKLIBS) + +repl: repl.o replsbr.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) repl.o replsbr.o whatnowproc.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) + +rmf: rmf.o $(LOCALLIBS) + $(LINK) rmf.o $(LINKLIBS) + +rmm: rmm.o $(LOCALLIBS) + $(LINK) rmm.o $(LINKLIBS) + +scan: scan.o scansbr.o termsbr.o $(LOCALLIBS) + $(LINK) scan.o scansbr.o termsbr.o $(LINKLIBS) $(TERMLIB) + +send: send.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) send.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) + +show: show.o mhlsbr.o termsbr.o $(LOCALLIBS) + $(LINK) show.o mhlsbr.o termsbr.o $(LINKLIBS) $(TERMLIB) + +slocal: slocal.o aliasbr.o dropsbr.o $(LOCALLIBS) + $(LINK) slocal.o aliasbr.o dropsbr.o $(LINKLIBS) + +sortm: sortm.o $(LOCALLIBS) + $(LINK) sortm.o $(LINKLIBS) + +spost: spost.o aliasbr.o $(LOCALLIBS) + $(LINK) spost.o aliasbr.o $(LINKLIBS) + +viamail: viamail.o mhmisc.o mhoutsbr.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) viamail.o mhmisc.o mhoutsbr.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) $(TERMLIB) + +vmh: vmh.o vmhsbr.o $(LOCALLIBS) + $(LINK) vmh.o vmhsbr.o $(LINKLIBS) $(TERMLIB) + +whatnow: whatnow.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LOCALLIBS) + $(LINK) whatnow.o whatnowsbr.o sendsbr.o annosbr.o distsbr.o $(LINKLIBS) + +whom: whom.o distsbr.o $(LOCALLIBS) + $(LINK) whom.o distsbr.o $(LINKLIBS) + +# ========== DEPENDENCIES FOR INSTALLING ========== + +# install everything +install: install-cmds install-lcmds install-misc + +# install commands +install-cmds: + $(top_srcdir)/mkinstalldirs $(bindir) + for cmd in $(CMDS); do \ + $(INSTALL_PROGRAM) $$cmd $(bindir)/$$cmd; \ + done + +# install links +install-lcmds: + rm -f $(bindir)/flists + rm -f $(bindir)/folders + rm -f $(bindir)/prev + rm -f $(bindir)/next + $(LN) $(bindir)/flist $(bindir)/flists + $(LN) $(bindir)/folder $(bindir)/folders + $(LN) $(bindir)/show $(bindir)/prev + $(LN) $(bindir)/show $(bindir)/next + +# install misc support binaries +install-misc: + $(top_srcdir)/mkinstalldirs $(libdir) + for misc in $(MISC); do \ + $(INSTALL_PROGRAM) $$misc $(libdir)/$$misc; \ + done + +uninstall: + for cmd in $(CMDS); do \ + rm -f $(bindir)/$$cmd; \ + done + for lcmd in $(LCMDS); do \ + rm -f $(bindir)/$$lcmd; \ + done + for misc in $(MISC); do \ + rm -f $(libdir)/$$misc; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + rm -f $(CMDS) $(MISC) + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = uip + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/uip/ali.c b/uip/ali.c new file mode 100644 index 0000000..bbbf0c1 --- /dev/null +++ b/uip/ali.c @@ -0,0 +1,253 @@ + +/* + * ali.c -- list nmh mail aliases + * + * $Id$ + */ + +#include +#include +#include + +/* + * maximum number of names + */ +#define NVEC 50 + +static struct swit switches[] = { +#define ALIASW 0 + { "alias aliasfile", 0 }, +#define NALIASW 1 + { "noalias", -7 }, +#define LISTSW 2 + { "list", 0 }, +#define NLISTSW 3 + { "nolist", 0 }, +#define NORMSW 4 + { "normalize", 0 }, +#define NNORMSW 5 + { "nonormalize", 0 }, +#define USERSW 6 + { "user", 0 }, +#define NUSERSW 7 + { "nouser", 0 }, +#define VERSIONSW 8 + { "version", 0 }, +#define HELPSW 9 + { "help", 4 }, + { NULL, 0 } +}; + +static int pos = 1; + +extern struct aka *akahead; + +/* + * prototypes + */ +void print_aka (char *, int, int); +void print_usr (char *, int, int); + + +int +main (int argc, char **argv) +{ + int i, vecp = 0, inverted = 0, list = 0; + int noalias = 0, normalize = AD_NHST; + char *cp, **ap, **argp, buf[BUFSIZ]; + char *vec[NVEC], **arguments; + struct aka *ak; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] aliases ...", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version (invo_name); + done (1); + + case ALIASW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((i = alias (cp)) != AK_OK) + adios (NULL, "aliasing error in %s - %s", cp, akerror (i)); + continue; + case NALIASW: + noalias++; + continue; + + case LISTSW: + list++; + continue; + case NLISTSW: + list = 0; + continue; + + case NORMSW: + normalize = AD_HOST; + continue; + case NNORMSW: + normalize = AD_NHST; + continue; + + case USERSW: + inverted++; + continue; + case NUSERSW: + inverted = 0; + continue; + } + } + vec[vecp++] = cp; + } + + if (!noalias) { + /* allow Aliasfile: profile entry */ + if ((cp = context_find ("Aliasfile"))) { + char *dp = NULL; + + for (ap = brkstring(dp = getcpy(cp), " ", "\n"); ap && *ap; ap++) + if ((i = alias (*ap)) != AK_OK) + adios (NULL, "aliasing error in %s - %s", *ap, akerror (i)); + if (dp) + free(dp); + } + alias (AliasFile); + } + + /* + * If -user is specified + */ + if (inverted) { + if (vecp == 0) + adios (NULL, "usage: %s -user addresses ... (you forgot the addresses)", + invo_name); + + for (i = 0; i < vecp; i++) + print_usr (vec[i], list, normalize); + + done (0); + } + + if (vecp) { + /* print specified aliases */ + for (i = 0; i < vecp; i++) + print_aka (akvalue (vec[i]), list, 0); + } else { + /* print them all */ + for (ak = akahead; ak; ak = ak->ak_next) { + printf ("%s: ", ak->ak_name); + pos += strlen (ak->ak_name) + 1; + print_aka (akresult (ak), list, pos); + } + } + + done (0); +} + +void +print_aka (char *p, int list, int margin) +{ + char c; + + if (p == NULL) { + printf ("\n"); + return; + } + + while ((c = *p++)) { + switch (c) { + case ',': + if (*p) { + if (list) + printf ("\n%*s", margin, ""); + else { + if (pos >= 68) { + printf (",\n "); + pos = 2; + } else { + printf (", "); + pos += 2; + } + } + } + + case 0: + break; + + default: + pos++; + putchar (c); + } + } + + putchar ('\n'); + pos = 1; +} + +void +print_usr (char *s, int list, int norm) +{ + register char *cp, *pp, *vp; + register struct aka *ak; + register struct mailname *mp, *np; + + if ((pp = getname (s)) == NULL) + adios (NULL, "no address in \"%s\"", s); + if ((mp = getm (pp, NULL, 0, norm, NULL)) == NULL) + adios (NULL, "bad address \"%s\"", s); + while (getname ("")) + continue; + + vp = NULL; + for (ak = akahead; ak; ak = ak->ak_next) { + pp = akresult (ak); + while ((cp = getname (pp))) { + if ((np = getm (cp, NULL, 0, norm, NULL)) == NULL) + continue; + if (!strcasecmp (mp->m_host, np->m_host) + && !strcasecmp (mp->m_mbox, np->m_mbox)) { + vp = vp ? add (ak->ak_name, add (",", vp)) + : getcpy (ak->ak_name); + mnfree (np); + while (getname ("")) + continue; + break; + } + mnfree (np); + } + } + mnfree (mp); + +#if 0 + printf ("%s: ", s); + print_aka (vp ? vp : s, list, pos += strlen (s) + 1); +#else + print_aka (vp ? vp : s, list, 0); +#endif + + if (vp) + free (vp); +} diff --git a/uip/aliasbr.c b/uip/aliasbr.c new file mode 100644 index 0000000..8ea8c13 --- /dev/null +++ b/uip/aliasbr.c @@ -0,0 +1,598 @@ + +/* + * aliasbr.c -- new aliasing mechanism + * + * $Id$ + */ + +#include +#include +#include +#include + +static int akvis; +static char *akerrst; + +struct aka *akahead = NULL; +struct aka *akatail = NULL; + +struct home *homehead = NULL; +struct home *hometail = NULL; + +/* + * prototypes + */ +int alias (char *); +int akvisible (void); +void init_pw (void); +char *akresult (struct aka *); +char *akvalue (char *); +char *akerror (int); + +static char *akval (struct aka *, char *); +static int aleq (char *, char *); +static char *scanp (char *); +static char *getp (char *); +static char *seekp (char *, char *, char **); +static int addfile (struct aka *, char *); +static int addgroup (struct aka *, char *); +static int addmember (struct aka *, char *); +static int addall (struct aka *); +static char *getalias (char *); +static void add_aka (struct aka *, char *); +static struct aka *akalloc (char *); +static struct home *hmalloc (struct passwd *); +#ifndef MMDFMTS +struct home *seek_home (char *); +#endif + + +char * +akvalue (char *s) +{ + register char *v; + + if (akahead == NULL) + alias (AliasFile); + + akvis = -1; + v = akval (akahead, s); + if (akvis == -1) + akvis = 0; + return v; +} + + +int +akvisible (void) +{ + return akvis; +} + + +char * +akresult (struct aka *ak) +{ + register char *cp = NULL, *dp, *pp; + register struct adr *ad; + + for (ad = ak->ak_addr; ad; ad = ad->ad_next) { + pp = ad->ad_local ? akval (ak->ak_next, ad->ad_text) + : getcpy (ad->ad_text); + + if (cp) { + dp = cp; + cp = concat (cp, ",", pp, NULL); + free (dp); + free (pp); + } + else + cp = pp; + } + + if (akvis == -1) + akvis = ak->ak_visible; + return cp; +} + + +static char * +akval (struct aka *ak, char *s) +{ + if (!s) + return s; /* XXX */ + + for (; ak; ak = ak->ak_next) + if (aleq (s, ak->ak_name)) + return akresult (ak); + + return getcpy (s); +} + + +static int +aleq (char *string, char *aliasent) +{ + register char c; + + while ((c = *string++)) + if (*aliasent == '*') + return 1; + else + if ((c | 040) != (*aliasent | 040)) + return 0; + else + aliasent++; + + return (*aliasent == 0 || *aliasent == '*'); +} + + +int +alias (char *file) +{ + int i; + register char *bp, *cp, *pp; + char lc, *ap; + register struct aka *ak = NULL; + register FILE *fp; + + if (*file != '/' + && (strncmp (file, "./", 2) && strncmp (file, "../", 3))) + file = etcpath (file); + if ((fp = fopen (file, "r")) == NULL) { + akerrst = file; + return AK_NOFILE; + } + + while (vfgets (fp, &ap) == OK) { + bp = ap; + switch (*(pp = scanp (bp))) { + case '<': /* recurse a level */ + if (!*(cp = getp (pp + 1))) { + akerrst = "'<' without alias-file"; + fclose (fp); + return AK_ERROR; + } + if ((i = alias (cp)) != AK_OK) { + fclose (fp); + return i; + } + + case ':': /* comment */ + case ';': + case '#': + case 0: + continue; + } + + akerrst = bp; + if (!*(cp = seekp (pp, &lc, &ap))) { + fclose (fp); + return AK_ERROR; + } + if (!(ak = akalloc (cp))) { + fclose (fp); + return AK_LIMIT; + } + switch (lc) { + case ':': + ak->ak_visible = 0; + break; + + case ';': + ak->ak_visible = 1; + break; + + default: + fclose (fp); + return AK_ERROR; + } + + switch (*(pp = scanp (ap))) { + case 0: /* EOL */ + fclose (fp); + return AK_ERROR; + + case '<': /* read values from file */ + if (!*(cp = getp (pp + 1))) { + fclose (fp); + return AK_ERROR; + } + if (!addfile (ak, cp)) { + fclose (fp); + return AK_NOFILE; + } + break; + + case '=': /* UNIX group */ + if (!*(cp = getp (pp + 1))) { + fclose (fp); + return AK_ERROR; + } + if (!addgroup (ak, cp)) { + fclose (fp); + return AK_NOGROUP; + } + break; + + case '+': /* UNIX group members */ + if (!*(cp = getp (pp + 1))) { + fclose (fp); + return AK_ERROR; + } + if (!addmember (ak, cp)) { + fclose (fp); + return AK_NOGROUP; + } + break; + + case '*': /* Everyone */ + addall (ak); + break; + + default: /* list */ + while ((cp = getalias (pp))) + add_aka (ak, cp); + break; + } + } + + fclose (fp); + return AK_OK; +} + + +char * +akerror (int i) +{ + static char buffer[BUFSIZ]; + + switch (i) { + case AK_NOFILE: + snprintf (buffer, sizeof(buffer), "unable to read '%s'", akerrst); + break; + + case AK_ERROR: + snprintf (buffer, sizeof(buffer), "error in line '%s'", akerrst); + break; + + case AK_LIMIT: + snprintf (buffer, sizeof(buffer), "out of memory while on '%s'", akerrst); + break; + + case AK_NOGROUP: + snprintf (buffer, sizeof(buffer), "no such group as '%s'", akerrst); + break; + + default: + snprintf (buffer, sizeof(buffer), "unknown error (%d)", i); + break; + } + + return buffer; +} + + +static char * +scanp (char *p) +{ + while (isspace (*p)) + p++; + return p; +} + + +static char * +getp (char *p) +{ + register char *cp = scanp (p); + + p = cp; + while (!isspace (*cp) && *cp) + cp++; + *cp = 0; + + return p; +} + + +static char * +seekp (char *p, char *c, char **a) +{ + register char *cp; + + p = cp = scanp (p); + while (!isspace (*cp) && *cp && *cp != ':' && *cp != ';') + cp++; + *c = *cp; + *cp++ = 0; + *a = cp; + + return p; +} + + +static int +addfile (struct aka *ak, char *file) +{ + register char *cp; + char buffer[BUFSIZ]; + register FILE *fp; + + if (!(fp = fopen (etcpath (file), "r"))) { + akerrst = file; + return 0; + } + + while (fgets (buffer, sizeof buffer, fp)) + while ((cp = getalias (buffer))) + add_aka (ak, cp); + + fclose (fp); + return 1; +} + + +static int +addgroup (struct aka *ak, char *grp) +{ + register char *gp; + register struct group *gr = getgrnam (grp); + register struct home *hm = NULL; + + if (!gr) + gr = getgrgid (atoi (grp)); + if (!gr) { + akerrst = grp; + return 0; + } + +#ifndef DBMPWD + if (homehead == NULL) + init_pw (); +#endif /* DBMPWD */ + + while ((gp = *gr->gr_mem++)) +#ifdef DBMPWD + { + struct passwd *pw; +#endif /* DBMPWD */ + for (hm = homehead; hm; hm = hm->h_next) + if (!strcmp (hm->h_name, gp)) { + add_aka (ak, hm->h_name); + break; + } +#ifdef DBMPWD + if ((pw = getpwnam(gp))) + { + hmalloc(pw); + add_aka (ak, gp); + } + } +#endif /* DBMPWD */ + + return 1; +} + + +static int +addmember (struct aka *ak, char *grp) +{ + gid_t gid; + register struct group *gr = getgrnam (grp); + register struct home *hm = NULL; + + if (gr) + gid = gr->gr_gid; + else { + gid = atoi (grp); + gr = getgrgid (gid); + } + if (!gr) { + akerrst = grp; + return 0; + } + +#ifndef DBMPWD + if (homehead == NULL) +#endif /* DBMPWD */ + init_pw (); + + for (hm = homehead; hm; hm = hm->h_next) + if (hm->h_gid == gid) + add_aka (ak, hm->h_name); + + return 1; +} + + +static int +addall (struct aka *ak) +{ + int noshell = NoShell == NULL || *NoShell == 0; + register struct home *hm; + +#ifndef DBMPWD + if (homehead == NULL) +#endif /* DBMPWD */ + init_pw (); + if (Everyone < 0) + Everyone = EVERYONE; + + for (hm = homehead; hm; hm = hm->h_next) + if (hm->h_uid > Everyone + && (noshell || strcmp (hm->h_shell, NoShell))) + add_aka (ak, hm->h_name); + + return homehead != NULL; +} + + +static char * +getalias (char *addrs) +{ + register char *pp, *qp; + static char *cp = NULL; + + if (cp == NULL) + cp = addrs; + else + if (*cp == 0) + return (cp = NULL); + + for (pp = cp; isspace (*pp); pp++) + continue; + if (*pp == 0) + return (cp = NULL); + for (qp = pp; *qp != 0 && *qp != ','; qp++) + continue; + if (*qp == ',') + *qp++ = 0; + for (cp = qp, qp--; qp > pp; qp--) + if (*qp != 0) + if (isspace (*qp)) + *qp = 0; + else + break; + + return pp; +} + + +static void +add_aka (struct aka *ak, char *pp) +{ + register struct adr *ad, *ld; + + for (ad = ak->ak_addr, ld = NULL; ad; ld = ad, ad = ad->ad_next) + if (!strcmp (pp, ad->ad_text)) + return; + + ad = (struct adr *) malloc (sizeof(*ad)); + if (ad == NULL) + return; + ad->ad_text = getcpy (pp); + ad->ad_local = strchr(pp, '@') == NULL && strchr(pp, '!') == NULL; + ad->ad_next = NULL; + if (ak->ak_addr) + ld->ad_next = ad; + else + ak->ak_addr = ad; +} + + +void +init_pw (void) +{ + register struct passwd *pw; +#ifdef DBMPWD + static int init; + + if (!init) + { + /* if the list has yet to be initialized */ + /* zap the list, and rebuild from scratch */ + homehead=NULL; + hometail=NULL; + init++; +#endif /* DBMPWD */ + + setpwent (); + + while ((pw = getpwent ())) + if (!hmalloc (pw)) + break; + + endpwent (); +#ifdef DBMPWD + } +#endif /* DBMPWD */ +} + + +static struct aka * +akalloc (char *id) +{ + register struct aka *p; + + if (!(p = (struct aka *) malloc (sizeof(*p)))) + return NULL; + + p->ak_name = getcpy (id); + p->ak_visible = 0; + p->ak_addr = NULL; + p->ak_next = NULL; + if (akatail != NULL) + akatail->ak_next = p; + if (akahead == NULL) + akahead = p; + akatail = p; + + return p; +} + + +static struct home * +hmalloc (struct passwd *pw) +{ + register struct home *p; + + if (!(p = (struct home *) malloc (sizeof(*p)))) + return NULL; + + p->h_name = getcpy (pw->pw_name); + p->h_uid = pw->pw_uid; + p->h_gid = pw->pw_gid; + p->h_home = getcpy (pw->pw_dir); + p->h_shell = getcpy (pw->pw_shell); + p->h_ngrps = 0; + p->h_next = NULL; + if (hometail != NULL) + hometail->h_next = p; + if (homehead == NULL) + homehead = p; + hometail = p; + + return p; +} + + +#ifndef MMDFMTS +struct home * +seek_home (char *name) +{ + register struct home *hp; +#ifdef DBMPWD + struct passwd *pw; + char lname[32]; + char *c,*c1; +#else /* DBMPWD */ + + if (homehead == NULL) + init_pw (); +#endif /* DBMPWD */ + + for (hp = homehead; hp; hp = hp->h_next) + if (!strcasecmp (name, hp->h_name)) + return hp; + +#ifdef DBMPWD + /* + * The only place where there might be problems. + * This assumes that ALL usernames are kept in lowercase. + */ + for (c = name, c1 = lname; *c && (c1 - lname < sizeof(lname) - 1); c++, c1++) { + if (isalpha(*c) && isupper(*c)) + *c1 = tolower (*c); + else + *c1 = *c; + } + *c1 = '\0'; + if ((pw = getpwnam(lname))) + return(hmalloc(pw)); +#endif /* DBMPWD */ + + return NULL; +} +#endif /* MMDFMTS */ diff --git a/uip/anno.c b/uip/anno.c new file mode 100644 index 0000000..c318a22 --- /dev/null +++ b/uip/anno.c @@ -0,0 +1,213 @@ + +/* + * anno.c -- annotate messages + * + * $Id$ + */ + +#include + +/* + * We allocate space for messages (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define COMPSW 0 + { "component field", 0 }, +#define INPLSW 1 + { "inplace", 0 }, +#define NINPLSW 2 + { "noinplace", 0 }, +#define DATESW 3 + { "date", 0 }, +#define NDATESW 4 + { "nodate", 0 }, +#define TEXTSW 5 + { "text body", 0 }, +#define VERSIONSW 6 + { "version", 0 }, +#define HELPSW 7 + { "help", 4 }, + { NULL, 0 } +}; + +/* + * static prototypes + */ +static void make_comp (char **); + + +int +main (int argc, char **argv) +{ + int inplace = 1, datesw = 1; + int nummsgs, maxmsgs, msgnum; + char *cp, *maildir, *comp = NULL; + char *text = NULL, *folder = NULL, buf[BUFSIZ]; + char **argp, **arguments, **msgs; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names and ranges. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case COMPSW: + if (comp) + adios (NULL, "only one component at a time!"); + if (!(comp = *argp++) || *comp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case DATESW: + datesw++; + continue; + case NDATESW: + datesw = 0; + continue; + + case INPLSW: + inplace++; + continue; + case NINPLSW: + inplace = 0; + continue; + + case TEXTSW: + if (text) + adios (NULL, "only one body at a time!"); + if (!(text = *argp++) || *text == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message name/ranges. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + +#ifdef UCI + if (strcmp(invo_name, "fanno") == 0) /* ugh! */ + datesw = 0; +#endif /* UCI */ + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + + make_comp (&comp); + + /* annotate all the SELECTED messages */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected(mp, msgnum)) + annotate (m_name (msgnum), comp, text, inplace, datesw); + + context_replace (pfolder, folder); /* update current folder */ + seq_setcur (mp, mp->lowsel); /* update current message */ + seq_save (mp); /* synchronize message sequences */ + folder_free (mp); /* free folder/message structure */ + context_save (); /* save the context file */ + done (0); +} + + +static void +make_comp (char **ap) +{ + register char *cp; + char buffer[BUFSIZ]; + + if (*ap == NULL) { + printf ("Enter component name: "); + fflush (stdout); + + if (fgets (buffer, sizeof buffer, stdin) == NULL) + done (1); + *ap = trimcpy (buffer); + } + + if ((cp = *ap + strlen (*ap) - 1) > *ap && *cp == ':') + *cp = 0; + if (strlen (*ap) == 0) + adios (NULL, "null component name"); + if (**ap == '-') + adios (NULL, "invalid component name %s", *ap); + if (strlen (*ap) >= NAMESZ) + adios (NULL, "too large component name %s", *ap); + + for (cp = *ap; *cp; cp++) + if (!isalnum (*cp) && *cp != '-') + adios (NULL, "invalid component name %s", *ap); +} + diff --git a/uip/annosbr.c b/uip/annosbr.c new file mode 100644 index 0000000..c237022 --- /dev/null +++ b/uip/annosbr.c @@ -0,0 +1,111 @@ + +/* + * annosbr.c -- prepend annotation to messages + * + * $Id$ + */ + +#include +#include +#include +#include + +extern int errno; + +/* + * static prototypes + */ +static int annosbr (int, char *, char *, char *, int, int); + + +int +annotate (char *file, char *comp, char *text, int inplace, int datesw) +{ + int i, fd; + + /* open and lock the file to be annotated */ + if ((fd = lkopen (file, O_RDWR, 0)) == NOTOK) { + switch (errno) { + case ENOENT: + break; + + default: + admonish (file, "unable to lock and open"); + break; + } + return 1; + } + + i = annosbr (fd, file, comp, text, inplace, datesw); + lkclose (fd, file); + return i; +} + + +static int +annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw) +{ + int mode, tmpfd; + char *cp, *sp; + char buffer[BUFSIZ], tmpfil[BUFSIZ]; + struct stat st; + FILE *tmp; + + mode = fstat (fd, &st) != NOTOK ? (st.st_mode & 0777) : m_gmprot (); + + strncpy (tmpfil, m_scratch (file, "annotate"), sizeof(tmpfil)); + + if ((tmp = fopen (tmpfil, "w")) == NULL) { + admonish (tmpfil, "unable to create"); + return 1; + } + chmod (tmpfil, mode); + + if (datesw) + fprintf (tmp, "%s: %s\n", comp, dtimenow (0)); + if ((cp = text)) { + do { + while (*cp == ' ' || *cp == '\t') + cp++; + sp = cp; + while (*cp && *cp++ != '\n') + continue; + if (cp - sp) + fprintf (tmp, "%s: %*.*s", comp, cp - sp, cp - sp, sp); + } while (*cp); + if (cp[-1] != '\n' && cp != text) + putc ('\n', tmp); + } + fflush (tmp); + cpydata (fd, fileno (tmp), file, tmpfil); + fclose (tmp); + + if (inplace) { + if ((tmpfd = open (tmpfil, O_RDONLY)) == NOTOK) + adios (tmpfil, "unable to open for re-reading"); + lseek (fd, (off_t) 0, SEEK_SET); + cpydata (tmpfd, fd, tmpfil, file); + close (tmpfd); + unlink (tmpfil); + } else { + strncpy (buffer, m_backup (file), sizeof(buffer)); + if (rename (file, buffer) == NOTOK) { + switch (errno) { + case ENOENT: /* unlinked early - no annotations */ + unlink (tmpfil); + break; + + default: + admonish (buffer, "unable to rename %s to", file); + break; + } + return 1; + } + if (rename (tmpfil, file) == NOTOK) { + admonish (file, "unable to rename %s to", tmpfil); + return 1; + } + } + + return 0; +} diff --git a/uip/ap.c b/uip/ap.c new file mode 100644 index 0000000..7e16b92 --- /dev/null +++ b/uip/ap.c @@ -0,0 +1,205 @@ + +/* + * ap.c -- parse addresses 822-style + * + * $Id$ + */ + +#include +#include +#include + +#define NADDRS 100 + +#define WIDTH 78 +#define WBUFSIZ BUFSIZ + +#define FORMAT "%<{error}%{error}: %{text}%|%(putstr(proper{text}))%>" + +static struct swit switches[] = { +#define FORMSW 0 + { "form formatfile", 0 }, +#define FMTSW 1 + { "format string", 5 }, +#define NORMSW 2 + { "normalize", 0 }, +#define NNORMSW 3 + { "nonormalize", 0 }, +#define WIDTHSW 4 + { "width columns", 0 }, +#define VERSIONSW 5 + { "version", 0 }, +#define HELPSW 6 + { "help", 4 }, + { NULL, 0 } +}; + +static struct format *fmt; + +static int dat[5]; + +/* + * prototypes + */ +int sc_width (void); /* from termsbr.c */ + +/* + * static prototypes + */ +static int process (char *, int, int); + + +int +main (int argc, char **argv) +{ + int addrp = 0, normalize = AD_HOST; + int width = 0, status = 0; + char *cp, *form = NULL, *format = NULL, *nfs; + char buf[BUFSIZ], **argp; + char **arguments, *addrs[NADDRS]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] addrs ...", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version (invo_name); + done (1); + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + width = atoi (cp); + continue; + + case NORMSW: + normalize = AD_HOST; + continue; + case NNORMSW: + normalize = AD_NHST; + continue; + } + } + if (addrp > NADDRS) + adios (NULL, "more than %d addresses", NADDRS); + else + addrs[addrp++] = cp; + } + addrs[addrp] = NULL; + + if (addrp == 0) + adios (NULL, "usage: %s [switches] addrs ...", invo_name); + + /* get new format string */ + nfs = new_fs (form, format, FORMAT); + + if (width == 0) { + if ((width = sc_width ()) < WIDTH / 2) + width = WIDTH / 2; + width -= 2; + } + if (width > WBUFSIZ) + width = WBUFSIZ; + fmt_norm = normalize; + fmt_compile (nfs, &fmt); + + dat[0] = 0; + dat[1] = 0; + dat[2] = 0; + dat[3] = width; + dat[4] = 0; + + for (addrp = 0; addrs[addrp]; addrp++) + status += process (addrs[addrp], width, normalize); + + done (status); +} + +struct pqpair { + char *pq_text; + char *pq_error; + struct pqpair *pq_next; +}; + + +static int +process (char *arg, int length, int norm) +{ + int status = 0; + register char *cp; + char buffer[WBUFSIZ + 1], error[BUFSIZ]; + register struct comp *cptr; + register struct pqpair *p, *q; + struct pqpair pq; + register struct mailname *mp; + + (q = &pq)->pq_next = NULL; + while ((cp = getname (arg))) { + if ((p = (struct pqpair *) calloc ((size_t) 1, sizeof(*p))) == NULL) + adios (NULL, "unable to allocate pqpair memory"); + if ((mp = getm (cp, NULL, 0, norm, error)) == NULL) { + p->pq_text = getcpy (cp); + p->pq_error = getcpy (error); + status++; + } + else { + p->pq_text = getcpy (mp->m_text); + mnfree (mp); + } + q = (q->pq_next = p); + } + + for (p = pq.pq_next; p; p = q) { + FINDCOMP (cptr, "text"); + if (cptr) + cptr->c_text = p->pq_text; + FINDCOMP (cptr, "error"); + if (cptr) + cptr->c_text = p->pq_error; + + fmt_scan (fmt, buffer, length, dat); + fputs (buffer, stdout); + + free (p->pq_text); + if (p->pq_error) + free (p->pq_error); + q = p->pq_next; + free ((char *) p); + } + + return status; +} diff --git a/uip/burst.c b/uip/burst.c new file mode 100644 index 0000000..d13b076 --- /dev/null +++ b/uip/burst.c @@ -0,0 +1,406 @@ + +/* + * burst.c -- explode digests into individual messages + * + * $Id$ + */ + +#include + +static struct swit switches[] = { +#define INPLSW 0 + { "inplace", 0 }, +#define NINPLSW 1 + { "noinplace", 0 }, +#define QIETSW 2 + { "quiet", 0 }, +#define NQIETSW 3 + { "noquiet", 0 }, +#define VERBSW 4 + { "verbose", 0 }, +#define NVERBSW 5 + { "noverbose", 0 }, +#define VERSIONSW 6 + { "version", 0 }, +#define HELPSW 7 + { "help", 4 }, + { NULL, 0 } +}; + +static char delim3[] = "-------"; + +struct smsg { + long s_start; + long s_stop; +}; + +/* + * static prototypes + */ +static int find_delim (int, struct smsg *); +static void burst (struct msgs **, int, struct smsg *, int, int, int); +static void cpybrst (FILE *, FILE *, char *, char *, int); + + +int +main (int argc, char **argv) +{ + int inplace = 0, quietsw = 0, verbosw = 0; + int msgp = 0, hi, msgnum, numburst; + char *cp, *maildir, *folder = NULL, buf[BUFSIZ]; + char **argp, **arguments, *msgs[MAXARGS]; + struct smsg *smsgs; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case INPLSW: + inplace++; + continue; + case NINPLSW: + inplace = 0; + continue; + + case QIETSW: + quietsw++; + continue; + case NQIETSW: + quietsw = 0; + continue; + + case VERBSW: + verbosw++; + continue; + case NVERBSW: + verbosw = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + msgs[msgp++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!msgp) + msgs[msgp++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + smsgs = (struct smsg *) + calloc ((size_t) (MAXFOLDER + 2), sizeof(*smsgs)); + if (smsgs == NULL) + adios (NULL, "unable to allocate burst storage"); + + hi = mp->hghmsg + 1; + + /* burst all the SELECTED messages */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) { + if ((numburst = find_delim (msgnum, smsgs)) >= 1) { + if (verbosw) + printf ("%d message%s exploded from digest %d\n", + numburst, numburst > 1 ? "s" : "", msgnum); + burst (&mp, msgnum, smsgs, numburst, inplace, verbosw); + } else { + if (numburst == 0) + if (!quietsw) + admonish (NULL, "message %d not in digest format", msgnum); + else + adios (NULL, "burst() botch -- you lose big"); + } + } + } + + free ((char *) smsgs); + context_replace (pfolder, folder); /* update current folder */ + + /* + * If -inplace is given, then the first message burst becomes + * the current message (which will now show a table of contents). + * Otherwise, the first message extracted from the first digest + * becomes the current message. + */ + if (inplace) { + if (mp->lowsel != mp->curmsg) + seq_setcur (mp, mp->lowsel); + } else { + if (hi <= mp->hghmsg) + seq_setcur (mp, hi); + } + + seq_save (mp); /* synchronize message sequences */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder/message structure */ + done (0); +} + + +/* + * Scan the message and find the beginning and + * end of all the messages in the digest. + */ + +static int +find_delim (int msgnum, struct smsg *smsgs) +{ + int ld3, wasdlm, msgp; + long pos; + char c, *msgnam; + int cc; + char buffer[BUFSIZ]; + FILE *in; + + ld3 = strlen (delim3); + + if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL) + adios (msgnam, "unable to read message"); + + for (msgp = 0, pos = 0L; msgp <= MAXFOLDER;) { + while (fgets (buffer, sizeof(buffer), in) && buffer[0] == '\n') + pos += (long) strlen (buffer); + if (feof (in)) + break; + fseek (in, pos, SEEK_SET); + smsgs[msgp].s_start = pos; + + for (c = 0; fgets (buffer, sizeof(buffer), in); c = buffer[0]) { + if (strncmp (buffer, delim3, ld3) == 0 + && (msgp == 1 || c == '\n') + && ((cc = peekc (in)) == '\n' || cc == EOF)) + break; + else + pos += (long) strlen (buffer); + } + + wasdlm = strncmp (buffer, delim3, ld3) == 0; + if (smsgs[msgp].s_start != pos) + smsgs[msgp++].s_stop = (c == '\n' && wasdlm) ? pos - 1 : pos; + if (feof (in)) { +#if 0 + if (wasdlm) { + smsgs[msgp - 1].s_stop -= ((long) strlen (buffer) + 1); + msgp++; /* fake "End of XXX Digest" */ + } +#endif + break; + } + pos += (long) strlen (buffer); + } + + fclose (in); + return (msgp - 1); /* toss "End of XXX Digest" */ +} + + +/* + * Burst out the messages in the digest into the folder + */ + +static void +burst (struct msgs **mpp, int msgnum, struct smsg *smsgs, int numburst, + int inplace, int verbosw) +{ + int i, j, mode; + char *msgnam; + char f1[BUFSIZ], f2[BUFSIZ], f3[BUFSIZ]; + FILE *in, *out; + struct stat st; + struct msgs *mp; + + if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL) + adios (msgnam, "unable to read message"); + + mode = fstat (fileno(in), &st) != NOTOK ? (st.st_mode & 0777) : m_gmprot(); + mp = *mpp; + + /* + * See if we have enough space in the folder + * structure for all the new messages. + */ + if ((mp->hghmsg + numburst > mp->hghoff) && + !(mp = folder_realloc (mp, mp->lowoff, mp->hghmsg + numburst))) + adios (NULL, "unable to allocate folder storage"); + *mpp = mp; + + j = mp->hghmsg; /* old value */ + mp->hghmsg += numburst; + mp->nummsg += numburst; + + /* + * If this is not the highest SELECTED message, then + * increment mp->hghsel by numburst, since the highest + * SELECTED is about to be slid down by that amount. + */ + if (msgnum < mp->hghsel) + mp->hghsel += numburst; + + /* + * If -inplace is given, renumber the messages after the + * source message, to make room for each of the messages + * contained within the digest. + */ + if (inplace) { + for (i = mp->hghmsg; j > msgnum; i--, j--) { + strncpy (f1, m_name (i), sizeof(f1)); + strncpy (f2, m_name (j), sizeof(f2)); + if (does_exist (mp, j)) { + if (verbosw) + printf ("message %d becomes message %d\n", j, i); + + if (rename (f2, f1) == NOTOK) + admonish (f1, "unable to rename %s to", f2); + copy_msg_flags (mp, i, j); + clear_msg_flags (mp, j); + mp->msgflags |= SEQMOD; + } + } + } + + unset_selected (mp, msgnum); + + /* new hghmsg is hghmsg + numburst */ + i = inplace ? msgnum + numburst : mp->hghmsg; + for (j = numburst; j >= (inplace ? 0 : 1); i--, j--) { + strncpy (f1, m_name (i), sizeof(f1)); + strncpy (f2, m_scratch ("", invo_name), sizeof(f2)); + if (verbosw && i != msgnum) + printf ("message %d of digest %d becomes message %d\n", j, msgnum, i); + + if ((out = fopen (f2, "w")) == NULL) + adios (f2, "unable to write message"); + chmod (f2, mode); + fseek (in, smsgs[j].s_start, SEEK_SET); + cpybrst (in, out, msgnam, f2, + (int) (smsgs[j].s_stop - smsgs[j].s_start)); + fclose (out); + + if (i == msgnum) { + strncpy (f3, m_backup (f1), sizeof(f3)); + if (rename (f1, f3) == NOTOK) + admonish (f3, "unable to rename %s to", f1); + } + if (rename (f2, f1) == NOTOK) + admonish (f1, "unable to rename %s to", f2); + copy_msg_flags (mp, i, msgnum); + mp->msgflags |= SEQMOD; + } + + fclose (in); +} + + +#define S1 0 +#define S2 1 +#define S3 2 + +/* + * Copy a mesage which is being burst out of a digest. + * It will remove any "dashstuffing" in the message. + */ + +static void +cpybrst (FILE *in, FILE *out, char *ifile, char *ofile, int len) +{ + register int c, state; + + for (state = S1; (c = fgetc (in)) != EOF && len > 0; len--) { + if (c == 0) + continue; + switch (state) { + case S1: + switch (c) { + case '-': + state = S3; + break; + + default: + state = S2; + case '\n': + fputc (c, out); + break; + } + break; + + case S2: + switch (c) { + case '\n': + state = S1; + default: + fputc (c, out); + break; + } + break; + + case S3: + switch (c) { + case ' ': + state = S2; + break; + + default: + state = (c == '\n') ? S1 : S2; + fputc ('-', out); + fputc (c, out); + break; + } + break; + } + } + + if (ferror (in) && !feof (in)) + adios (ifile, "error reading"); + if (ferror (out)) + adios (ofile, "error writing"); +} diff --git a/uip/comp.c b/uip/comp.c new file mode 100644 index 0000000..e40c968 --- /dev/null +++ b/uip/comp.c @@ -0,0 +1,306 @@ + +/* + * comp.c -- compose a message + * + * $Id$ + */ + +#include +#include + +static struct swit switches[] = { +#define DFOLDSW 0 + { "draftfolder +folder", 0 }, +#define DMSGSW 1 + { "draftmessage msg", 0 }, +#define NDFLDSW 2 + { "nodraftfolder", 0 }, +#define EDITRSW 3 + { "editor editor", 0 }, +#define NEDITSW 4 + { "noedit", 0 }, +#define FILESW 5 + { "file file", 0 }, +#define FORMSW 6 + { "form formfile", 0 }, +#define USESW 7 + { "use", 0 }, +#define NUSESW 8 + { "nouse", 0 }, +#define WHATSW 9 + { "whatnowproc program", 0 }, +#define NWHATSW 10 + { "nowhatnowproc", 0 }, +#define VERSIONSW 11 + { "version", 0 }, +#define HELPSW 12 + { "help", 4 }, + { NULL, 0 } +}; + +static struct swit aqrunl[] = { +#define NOSW 0 + { "quit", 0 }, +#define YESW 1 + { "replace", 0 }, +#define USELSW 2 + { "use", 0 }, +#define LISTDSW 3 + { "list", 0 }, +#define REFILSW 4 + { "refile +folder", 0 }, +#define NEWSW 5 + { "new", 0 }, + { NULL, 0 } +}; + +static struct swit aqrul[] = { + { "quit", 0 }, + { "replace", 0 }, + { "use", 0 }, + { "list", 0 }, + { "refile", 0 }, + { NULL, 0 } +}; + + +int +main (int argc, char **argv) +{ + int use = NOUSE, nedit = 0, nwhat = 0; + int i, in, isdf = 0, out; + char *cp, *cwd, *maildir, *dfolder = NULL; + char *ed = NULL, *file = NULL, *form = NULL; + char *folder = NULL, *msg = NULL, buf[BUFSIZ]; + char drft[BUFSIZ], **argp, **arguments; + struct msgs *mp = NULL; + struct stat st; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case EDITRSW: + if (!(ed = *argp++) || *ed == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nedit = 0; + continue; + case NEDITSW: + nedit++; + continue; + + case WHATSW: + if (!(whatnowproc = *argp++) || *whatnowproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nwhat = 0; + continue; + case NWHATSW: + nwhat++; + continue; + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case USESW: + use++; + continue; + case NUSESW: + use = NOUSE; + continue; + + case FILESW: /* compatibility */ + if (file) + adios (NULL, "only one file at a time!"); + if (!(file = *argp++) || *file == '-') + adios (NULL, "missing argument to %s", argp[-2]); + isdf = NOTOK; + continue; + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (file) + adios (NULL, "only one draft message at a time!"); + if (!(file = *argp++) || *file == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + if (msg) + adios (NULL, "only one message at a time!"); + else + msg = cp; + } + } + + cwd = getcpy (pwd ()); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* + * Check if we are using a draft folder + * and have specified a message in it. + */ + if ((dfolder || context_find ("Draft-Folder")) && !folder && msg && !file) { + file = msg; + msg = NULL; + } + if (form && (folder || msg)) + adios (NULL, "can't mix forms and folders/msgs"); + + if (folder || msg) { + /* + * Use a message as the "form" for the new message. + */ + if (!msg) + msg = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse the message range/sequence/name and set SELECTED */ + if (!m_convert (mp, msg)) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (mp->numsel > 1) + adios (NULL, "only one message at a time!"); + + if ((in = open (form = getcpy (m_name (mp->lowsel)), O_RDONLY)) == NOTOK) + adios (form, "unable to open message"); + } else { + /* + * Open a component or forms file + */ + if (form) { + if ((in = open (etcpath (form), O_RDONLY)) == NOTOK) + adios (form, "unable to open form file"); + } else { + if ((in = open (etcpath (components), O_RDONLY)) == NOTOK) + adios (components, "unable to open default components file"); + form = components; + } + } + +try_it_again: + strncpy (drft, m_draft (dfolder, file, use, &isdf), sizeof(drft)); + + /* + * Check if we have an existing draft + */ + if ((out = open (drft, O_RDONLY)) != NOTOK) { + i = fdcompare (in, out); + close (out); + + /* + * If we have given -use flag, or if the + * draft is just the same as the components + * file, then no need to ask any questions. + */ + if (use || i) + goto edit_it; + + if (stat (drft, &st) == NOTOK) + adios (drft, "unable to stat"); + printf ("Draft \"%s\" exists (%ld bytes).", drft, (long) st.st_size); + for (i = LISTDSW; i != YESW;) { + if (!(argp = getans ("\nDisposition? ", isdf ? aqrunl : aqrul))) + done (1); + switch (i = smatch (*argp, isdf ? aqrunl : aqrul)) { + case NOSW: + done (0); + case NEWSW: + file = NULL; + use = NOUSE; + goto try_it_again; + case YESW: + break; + case USELSW: + use++; + goto edit_it; + case LISTDSW: + showfile (++argp, drft); + break; + case REFILSW: + if (refile (++argp, drft) == 0) + i = YESW; + break; + default: + advise (NULL, "say what?"); + break; + } + } + } else { + if (use) + adios (drft, "unable to open"); + } + + if ((out = creat (drft, m_gmprot ())) == NOTOK) + adios (drft, "unable to create"); + cpydata (in, out, form, drft); + close (in); + close (out); + +edit_it: + context_save (); /* save the context file */ + + if (nwhat) + done (0); + what_now (ed, nedit, use, drft, NULL, 0, NULLMP, NULL, 0, cwd); + done (1); +} diff --git a/uip/conflict.c b/uip/conflict.c new file mode 100644 index 0000000..29a12e7 --- /dev/null +++ b/uip/conflict.c @@ -0,0 +1,545 @@ + +/* + * conflict.c -- check for conflicts in mail system + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include + +/* + * maximum number of directories that can + * be specified using -search switch. + */ +#define NDIRS 100 + +/* + * Add space for group names, 100 at a time + */ +#define NGRPS 100 + +static struct swit switches[] = { +#define MAILSW 0 + { "mail name", 0 }, +#define SERCHSW 1 + { "search directory", 0 }, +#define VERSIONSW 2 + { "version", 0 }, +#define HELPSW 3 + { "help", 4 }, + { NULL, 0 } +}; + +static char *mail = NULL; +static char *dirs[NDIRS]; +static FILE *out = NULL; + +extern struct aka *akahead; +extern struct home *homehead; + +/* + * prototypes + */ +void alias_files (int, char **); +void pwd_names (void); +void grp_names (void); +void grp_members (void); +void grp_ids (void); +void maildrops (void); +void mdrop(char *); +int check (char *); +void setup (void); + + +int +main (int argc, char **argv) +{ + int akp = 0, dp = 0; + char *cp, **argp, **arguments; + char buf[BUFSIZ], *akv[50]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] [aliasfiles ...]", + invo_name); + print_help (buf, switches, 0); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case MAILSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (mail) + adios (NULL, "mail to one address only"); + else + mail = cp; + continue; + + case SERCHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (dp >= NDIRS) + adios (NULL, "more than %d directories", NDIRS); + dirs[dp++] = cp; + continue; + } + } + akv[akp++] = cp; + } + + if (akp == 0) + akv[akp++] = AliasFile; + if (!homehead) + init_pw (); + if (!mail) + out = stdout; + dirs[dp] = NULL; + + alias_files (akp, akv); + pwd_names (); + grp_names (); + grp_members (); + grp_ids (); +#ifdef UCI + ldr_names (); + ldr_ship (); +#endif /* UCI */ + maildrops (); + + done (0); +} + + +void +alias_files (int akp, char **akv) +{ + register int i, err; + + for (i = 0; i < akp; i++) + if ((err = alias (akv[i])) != AK_OK) { + setup (); + fprintf (out, "aliasing error in %s - %s\n", akv[i], akerror (err)); + } + else + if (out && !mail) + fprintf (out, "alias file %s is ok\n", akv[i]); +} + + +void +pwd_names (void) +{ + int hit = 0; + register struct home *hm, *lm; + + for (hm = homehead; hm; hm = hm->h_next) + for (lm = hm->h_next; lm; lm = lm->h_next) + if (strcmp (hm->h_name, lm->h_name) == 0) { + setup (); + fprintf (out, "duplicate user %s(uid=%d)\n", + lm->h_name, (int) lm->h_uid); + hit++; + } + + if (!hit && out && !mail) + fprintf (out, "no duplicate users\n"); +} + + +void +grp_names (void) +{ + int numgroups, maxgroups; + int i, hit = 0; + char **grps; + struct group *gr; + + /* allocate space NGRPS at a time */ + numgroups = 0; + maxgroups = NGRPS; + if (!(grps = (char **) malloc((size_t) (maxgroups * sizeof(*grps))))) + adios (NULL, "unable to allocate group name storage"); + + setgrent (); + while ((gr = getgrent ())) { + for (i = 0; i < numgroups; i++) + if (!strcmp (grps[i], gr->gr_name)) { + setup (); + fprintf (out, "duplicate group %s(gid=%d)\n", + gr->gr_name, (int) gr->gr_gid); + hit++; + break; + } + if (i >= numgroups) { + if (numgroups >= maxgroups) { + maxgroups += NGRPS; + if (!(grps = (char **) realloc(grps, + (size_t) (maxgroups * sizeof(*grps))))) + adios (NULL, "unable to reallocate group name storage"); + } + grps[numgroups++] = getcpy (gr->gr_name); + } + } + endgrent (); + + for (i = 0; i < numgroups; i++) + free (grps[i]); + free (grps); + + if (!hit && out && !mail) + fprintf (out, "no duplicate groups\n"); +} + + +void +grp_members (void) +{ + register int hit = 0; + register char **cp, **dp; + register struct group *gr; + register struct home *hm; + + setgrent (); + while ((gr = getgrent ())) { + for (cp = gr->gr_mem; *cp; cp++) { + for (hm = homehead; hm; hm = hm->h_next) + if (!strcmp (*cp, hm->h_name)) + break; + if (hm == NULL) { + setup (); + fprintf (out, "group %s(gid=%d) has unknown member %s\n", + gr->gr_name, (int) gr->gr_gid, *cp); + hit++; + } else { + hm->h_ngrps++; + } + + for (dp = cp + 1; *dp; dp++) + if (strcmp (*cp, *dp) == 0) { + setup (); + fprintf (out, "group %s(gid=%d) has duplicate member %s\n", + gr->gr_name, (int) gr->gr_gid, *cp); + hit++; + } + } + } + endgrent (); + + for (hm = homehead; hm; hm = hm->h_next) + if (hm->h_ngrps > NGROUPS_MAX) { + setup (); + fprintf (out, "user %s is a member of %d groups (max %d)\n", + hm->h_name, hm->h_ngrps, NGROUPS_MAX); + hit++; + } + + if (!hit && out && !mail) + fprintf (out, "all group members accounted for\n"); +} + + +void +grp_ids (void) +{ /* -DRAND not implemented at most places */ + register int hit = 0; + register struct home *hm; + + for (hm = homehead; hm; hm = hm->h_next) + if (getgrgid (hm->h_gid) == NULL) { + setup (); + fprintf (out, "user %s(uid=%d) has unknown group-id %d\n", + hm->h_name, (int) hm->h_uid, (int) hm->h_gid); + hit++; + } + + if (!hit && out && !mail) + fprintf (out, "all group-id users accounted for\n"); +} + + +void +maildrops (void) +{ + register int i; + + if (mmdfldir && *mmdfldir) + mdrop (mmdfldir); + if (uucpldir && *uucpldir) + mdrop (uucpldir); + for (i = 0; dirs[i]; i++) + mdrop (dirs[i]); +} + + +void +mdrop(char *drop) +{ + register int hit = 0; + register struct dirent *dp; + register DIR *dd = opendir (drop); + + if (!dd) { + setup (); + fprintf (out, "unable to open maildrop area %s\n", drop); + return; + } + + while ((dp = readdir (dd))) + if (dp->d_name[0] != '.' && !check (dp->d_name)) { + setup (); + fprintf (out, + "there is a maildrop for the unknown user %s in %s\n", + dp->d_name, drop); + hit++; + } + + closedir (dd); + if (!hit && out && !mail) + fprintf (out, "all maildrops accounted for in %s\n", drop); +} + + +int +check (char *s) +{ + register struct home *hm; + + for (hm = homehead; hm; hm = hm->h_next) + if (!strcmp (s, hm->h_name)) + return 1; + return 0; +} + +void +setup (void) +{ + int fd, pd[2]; + + if (out) + return; + + if (mail) { + if (pipe (pd) == NOTOK) + adios ("pipe", "unable to"); + + switch (fork ()) { + case NOTOK: + adios ("fork", "unable to"); + + case OK: + close (pd[1]); + if (pd[0] != 0) { + dup2 (pd[0], 0); + close (pd[0]); + } + if ((fd = open ("/dev/null", O_WRONLY)) != NOTOK) + if (fd != 1) { + dup2 (fd, 1); + close (fd); + } + execlp (mailproc, r1bindex (mailproc, '/'), + mail, "-subject", invo_name, NULL); + adios (mailproc, "unable to exec "); + + default: + close (pd[0]); + out = fdopen (pd[1], "w"); + fprintf (out, "%s: the following is suspicious\n\n", + invo_name); + } + } +} + +#ifdef UCI +/* + * UCI specific stuff for conflict + */ + +/* taken from */ + +#define GLDRS "/admin/etc/GroupLeaders" + +struct grpldr { + char *gl_name; + char **gl_ldr; +}; + +int setglent (), endglent (); +struct grpldr *getglent (), *getglnam (); + + +/* taken from the getglent() routines */ + +#define MAXGLS 100 + +static FILE *glp = NULL; +static char line[BUFSIZ+1]; +static struct grpldr grpldr; +static char *gl_ldr[MAXGLS + 1]; + + +setglent() { + if (glp == NULL) + glp = fopen (GLDRS, "r"); + else + rewind (glp); + + return (glp != NULL); +} + + +endglent() { + if (glp != NULL) { + fclose (glp); + glp = NULL; + } + + return 1; +} + +struct grpldr *getglent () { + register char *cp, + **q; + + if (glp == NULL && !setglent ()) + return NULL; + if ((cp = fgets (line, BUFSIZ, glp)) == NULL) + return NULL; + + grpldr.gl_name = cp; + grpldr.gl_ldr = q = gl_ldr; + + while (*cp) { + while (*cp && !isspace (*cp)) + cp++; + while (*cp && isspace (*cp)) + *cp++ = '\0'; + if (*cp == '\0') + break; + if (q < gl_ldr + MAXGLS) + *q++ = cp; + else + break; + } + *q = NULL; + + return (&grpldr); +} + +struct grpldr *getglnam (name) +char *name; +{ + register struct grpldr *gl = NULL; + + setglent (); + while (gl = getglent ()) + if (strcmp (name, gl->gl_name) == 0) + break; + endglent (); + + return gl; +} + +ldr_names () { + register int gp, + hit = 0; + char *gldrs[NGRPS]; + register struct grpldr *gl; + + gldrs[0] = NULL; + setglent (); + while (gl = getglent ()) { + if (getgrnam (gl->gl_name) == NULL) { + setup (); + fprintf (out, "unknown group %s in group leaders file\n", + gl->gl_name); + hit++; + } + for (gp = 0; gldrs[gp]; gp++) + if (strcmp (gldrs[gp], gl->gl_name) == 0) { + setup (); + fprintf (out, "duplicate group %s in group leaders file\n", + gl->gl_name); + hit++; + break; + } + if (gldrs[gp] == NULL) + if (gp < NGRPS) { + gldrs[gp++] = getcpy (gl->gl_name); + gldrs[gp] = NULL; + } + else { + setup (); + fprintf (out, "more than %d groups in group leaders file%s\n", + " (time to recompile)", NGRPS - 1); + hit++; + } + } + endglent (); + + for (gp = 0; gldrs[gp]; gp++) + free (gldrs[gp]); + + if (!hit && out && !mail) + fprintf (out, "all groups in group leaders file accounted for\n"); +} + + +ldr_ship () { + register int hit = 0; + register char **cp, + **dp; + register struct grpldr *gl; + + setglent (); + while (gl = getglent ()) + for (cp = gl->gl_ldr; *cp; cp++) { + if (!check (*cp)) { + setup (); + fprintf (out, "group %s has unknown leader %s\n", + gl->gl_name, *cp); + hit++; + } + + for (dp = cp + 1; *dp; dp++) + if (strcmp (*cp, *dp) == 0) { + setup (); + fprintf (out, "group %s had duplicate leader %s\n", + gl->gl_name, *cp); + hit++; + } + } + endglent (); + + if (!hit && out && !mail) + fprintf (out, "all group leaders accounted for\n"); +} +#endif /* UCI */ diff --git a/uip/dist.c b/uip/dist.c new file mode 100644 index 0000000..1fb1741 --- /dev/null +++ b/uip/dist.c @@ -0,0 +1,291 @@ + +/* + * dist.c -- re-distribute a message + * + * $Id$ + */ + +#include +#include + +static struct swit switches[] = { +#define ANNOSW 0 + { "annotate", 0 }, +#define NANNOSW 1 + { "noannotate", 0 }, +#define DFOLDSW 2 + { "draftfolder +folder", 0 }, +#define DMSGSW 3 + { "draftmessage msg", 0 }, +#define NDFLDSW 4 + { "nodraftfolder", 0 }, +#define EDITRSW 5 + { "editor editor", 0 }, +#define NEDITSW 6 + { "noedit", 0 }, +#define FORMSW 7 + { "form formfile", 0 }, +#define INPLSW 8 + { "inplace", 0 }, +#define NINPLSW 9 + { "noinplace", 0 }, +#define WHATSW 10 + { "whatnowproc program", 0 }, +#define NWHATSW 11 + { "nowhatnowproc", 0 }, +#define VERSIONSW 12 + { "version", 0 }, +#define HELPSW 13 + { "help", 4 }, +#define FILESW 14 + { "file file", -4 }, /* interface from msh */ + { NULL, 0 } +}; + +static struct swit aqrnl[] = { +#define NOSW 0 + { "quit", 0 }, +#define YESW 1 + { "replace", 0 }, +#define LISTDSW 2 + { "list", 0 }, +#define REFILSW 3 + { "refile +folder", 0 }, +#define NEWSW 4 + { "new", 0 }, + { NULL, 0 } +}; + + +static struct swit aqrl[] = { + { "quit", 0 }, + { "replace", 0 }, + { "list", 0 }, + { "refile +folder", 0 }, + { NULL, 0 } +}; + + +int +main (int argc, char **argv) +{ + int anot = 0, inplace = 1, nedit = 0; + int nwhat = 0, i, in, isdf = 0, out; + char *cp, *cwd, *maildir, *msgnam, *dfolder = NULL; + char *dmsg = NULL, *ed = NULL, *file = NULL, *folder = NULL; + char *form = NULL, *msg = NULL, buf[BUFSIZ], drft[BUFSIZ]; + char **argp, **arguments; + struct msgs *mp = NULL; + struct stat st; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case ANNOSW: + anot++; + continue; + case NANNOSW: + anot = 0; + continue; + + case EDITRSW: + if (!(ed = *argp++) || *ed == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nedit = 0; + continue; + case NEDITSW: + nedit++; + continue; + + case WHATSW: + if (!(whatnowproc = *argp++) || *whatnowproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nwhat = 0; + continue; + case NWHATSW: + nwhat++; + continue; + + case FILESW: + if (file) + adios (NULL, "only one file at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + file = path (cp, TFILE); + continue; + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case INPLSW: + inplace++; + continue; + case NINPLSW: + inplace = 0; + continue; + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (dmsg) + adios (NULL, "only one draft message at a time!"); + if (!(dmsg = *argp++) || *dmsg == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + if (msg) + adios (NULL, "only one message at a time!"); + else + msg = cp; + } + } + + cwd = getcpy (pwd ()); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (file && (msg || folder)) + adios (NULL, "can't mix files and folders/msgs"); + + if (form) { + if ((in = open (etcpath (form), O_RDONLY)) == NOTOK) + adios (form, "unable to open form file"); + } else { + if ((in = open (etcpath (distcomps), O_RDONLY)) == NOTOK) + adios (distcomps, "unable to open default components file"); + form = distcomps; + } + +try_it_again: + strncpy (drft, m_draft (dfolder, dmsg, NOUSE, &isdf), sizeof(drft)); + + /* Check if draft already exists */ + if (stat (drft, &st) != NOTOK) { + printf ("Draft \"%s\" exists (%ld bytes).", drft, (long) st.st_size); + for (i = LISTDSW; i != YESW;) { + if (!(argp = getans ("\nDisposition? ", isdf ? aqrnl : aqrl))) + done (1); + switch (i = smatch (*argp, isdf ? aqrnl : aqrl)) { + case NOSW: + done (0); + case NEWSW: + dmsg = NULL; + goto try_it_again; + case YESW: + break; + case LISTDSW: + showfile (++argp, drft); + break; + case REFILSW: + if (refile (++argp, drft) == 0) + i = YESW; + break; + default: + advise (NULL, "say what?"); + break; + } + } + } + if ((out = creat (drft, m_gmprot ())) == NOTOK) + adios (drft, "unable to create"); + + cpydata (in, out, form, drft); + close (in); + close (out); + + if (file) { + /* + * Dist a file + */ + anot = 0; /* don't want to annotate a file */ + } else { + /* + * Dist a message + */ + if (!msg) + msg = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse the message range/sequence/name and set SELECTED */ + if (!m_convert (mp, msg)) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (mp->numsel > 1) + adios (NULL, "only one message at a time!"); + } + + msgnam = file ? file : getcpy (m_name (mp->lowsel)); + if ((in = open (msgnam, O_RDONLY)) == NOTOK) + adios (msgnam, "unable to open message"); + + if (!file) { + context_replace (pfolder, folder);/* update current folder */ + seq_setcur (mp, mp->lowsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + if (nwhat) + done (0); + what_now (ed, nedit, NOUSE, drft, msgnam, 1, mp, + anot ? "Resent" : NULL, inplace, cwd); + done (1); +} diff --git a/uip/distsbr.c b/uip/distsbr.c new file mode 100644 index 0000000..be2716b --- /dev/null +++ b/uip/distsbr.c @@ -0,0 +1,194 @@ + +/* + * distsbr.c -- routines to do additional "dist-style" processing + * + * $Id$ + */ + +#include +#include + +static int hdrfd = NOTOK; +static int txtfd = NOTOK; + +#define BADHDR "please re-edit %s to remove the ``%s'' header!" +#define BADTXT "please re-edit %s to consist of headers only!" +#define BADMSG "please re-edit %s to include a ``Resent-To:''!" +#define BADRFT "please re-edit %s and fix that header!" + +/* + * static prototypes + */ +static void ready_msg(char *); + +int +distout (char *drft, char *msgnam, char *backup) +{ + int state; + register char *dp, *resent; + char name[NAMESZ], buffer[BUFSIZ]; + register FILE *ifp, *ofp; + + if (rename (drft, strcpy (backup, m_backup (drft))) == NOTOK) + adios (backup, "unable to rename %s to",drft); + if ((ifp = fopen (backup, "r")) == NULL) + adios (backup, "unable to read"); + + if ((ofp = fopen (drft, "w")) == NULL) + adios (drft, "unable to create temporary file"); + chmod (drft, m_gmprot ()); + + ready_msg (msgnam); + lseek (hdrfd, (off_t) 0, SEEK_SET); /* msgnam not accurate */ + cpydata (hdrfd, fileno (ofp), msgnam, drft); + + for (state = FLD, resent = NULL;;) + switch (state = + m_getfld (state, name, buffer, sizeof buffer, ifp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + if (uprf (name, "distribute-")) + snprintf (name, sizeof(name), "%s%s", "Resent", &name[10]); + if (uprf (name, "distribution-")) + snprintf (name, sizeof(name), "%s%s", "Resent", &name[12]); + if (!uprf (name, "resent")) { + advise (NULL, BADHDR, "draft", name); + goto leave_bad; + } + if (state == FLD) + resent = add (":", add (name, resent)); + resent = add (buffer, resent); + fprintf (ofp, "%s: %s", name, buffer); + while (state == FLDPLUS) { + state = m_getfld (state, name, + buffer, sizeof buffer, ifp); + resent = add (buffer, resent); + fputs (buffer, ofp); + } + if (state == FLDEOF) + goto process; + break; + + case BODY: + case BODYEOF: + for (dp = buffer; *dp; dp++) + if (!isspace (*dp)) { + advise (NULL, BADTXT, "draft"); + goto leave_bad; + } + + case FILEEOF: + goto process; + + case LENERR: + case FMTERR: + advise (NULL, BADRFT, "draft"); + leave_bad: ; + fclose (ifp); + fclose (ofp); + unlink (drft); + if (rename (backup, drft) == NOTOK) + adios (drft, "unable to rename %s to", backup); + return NOTOK; + + default: + adios (NULL, "getfld() returned %d", state); + } +process: ; + fclose (ifp); + fflush (ofp); + + if (!resent) { + advise (NULL, BADMSG, "draft"); + fclose (ofp); + unlink (drft); + if (rename (backup, drft) == NOTOK) + adios (drft, "unable to rename %s to", backup); + return NOTOK; + } + free (resent); + + if (txtfd != NOTOK) { + lseek (txtfd, (off_t) 0, SEEK_SET); /* msgnam not accurate */ + cpydata (txtfd, fileno (ofp), msgnam, drft); + } + + fclose (ofp); + + return OK; +} + + +static void +ready_msg (char *msgnam) +{ + int state, out; + char name[NAMESZ], buffer[BUFSIZ], tmpfil[BUFSIZ]; + register FILE *ifp, *ofp; + + if (hdrfd != NOTOK) + close (hdrfd), hdrfd = NOTOK; + if (txtfd != NOTOK) + close (txtfd), txtfd = NOTOK; + + if ((ifp = fopen (msgnam, "r")) == NULL) + adios (msgnam, "unable to open message"); + + strncpy (tmpfil, m_tmpfil ("dist"), sizeof(tmpfil)); + if ((hdrfd = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK) + adios (tmpfil, "unable to re-open temporary file"); + if ((out = dup (hdrfd)) == NOTOK + || (ofp = fdopen (out, "w")) == NULL) + adios (NULL, "no file descriptors -- you lose big"); + unlink (tmpfil); + + for (state = FLD;;) + switch (state = + m_getfld (state, name, buffer, sizeof buffer, ifp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + if (uprf (name, "resent")) + fprintf (ofp, "Prev-"); + fprintf (ofp, "%s: %s", name, buffer); + while (state == FLDPLUS) { + state = m_getfld (state, name, + buffer, sizeof buffer, ifp); + fputs (buffer, ofp); + } + if (state == FLDEOF) + goto process; + break; + + case BODY: + case BODYEOF: + fclose (ofp); + + strncpy (tmpfil, m_tmpfil ("dist"), sizeof(tmpfil)); + if ((txtfd = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK) + adios (tmpfil, "unable to open temporary file"); + if ((out = dup (txtfd)) == NOTOK + || (ofp = fdopen (out, "w")) == NULL) + adios (NULL, "no file descriptors -- you lose big"); + unlink (tmpfil); + fprintf (ofp, "\n%s", buffer); + while (state == BODY) { + state = m_getfld (state, name, + buffer, sizeof buffer, ifp); + fputs (buffer, ofp); + } + case FILEEOF: + goto process; + + case LENERR: + case FMTERR: + adios (NULL, "format error in message %s", msgnam); + + default: + adios (NULL, "getfld() returned %d", state); + } +process: ; + fclose (ifp); + fclose (ofp); +} diff --git a/uip/dp.c b/uip/dp.c new file mode 100644 index 0000000..0041198 --- /dev/null +++ b/uip/dp.c @@ -0,0 +1,153 @@ + +/* + * dp.c -- parse dates 822-style + * + * $Id$ + */ + +#include +#include +#include + +#define NDATES 100 + +#define WIDTH 78 +#define WBUFSIZ BUFSIZ + +#define FORMAT "%<(nodate{text})error: %{text}%|%(putstr(pretty{text}))%>" + +static struct swit switches[] = { +#define FORMSW 0 + { "form formatfile", 0 }, +#define FMTSW 1 + { "format string", 5 }, +#define WIDTHSW 2 + { "width columns", 0 }, +#define VERSIONSW 3 + { "version", 0 }, +#define HELPSW 4 + { "help", 4 }, + { NULL, 0 } +}; + +static struct format *fmt; + +static int dat[5]; + +/* + * prototypes + */ +int sc_width (void); /* from termsbr.c */ + +/* + * static prototypes + */ +static int process (char *, int); + + +int +main (int argc, char **argv) +{ + int datep = 0, width = 0, status = 0; + char *cp, *form = NULL, *format = NULL, *nfs; + char buf[BUFSIZ], **argp, **arguments; + char *dates[NDATES]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] dates ...", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + width = atoi (cp); + continue; + } + } + if (datep > NDATES) + adios (NULL, "more than %d dates", NDATES); + else + dates[datep++] = cp; + } + dates[datep] = NULL; + + if (datep == 0) + adios (NULL, "usage: %s [switches] dates ...", invo_name); + + /* get new format string */ + nfs = new_fs (form, format, FORMAT); + + if (width == 0) { + if ((width = sc_width ()) < WIDTH / 2) + width = WIDTH / 2; + width -= 2; + } + if (width > WBUFSIZ) + width = WBUFSIZ; + fmt_compile (nfs, &fmt); + + dat[0] = 0; + dat[1] = 0; + dat[2] = 0; + dat[3] = width; + dat[4] = 0; + + for (datep = 0; dates[datep]; datep++) + status += process (dates[datep], width); + + context_save (); /* save the context file */ + done (status); +} + + +static int +process (char *date, int length) +{ + int status = 0; + char buffer[WBUFSIZ + 1]; + register struct comp *cptr; + + FINDCOMP (cptr, "text"); + if (cptr) + cptr->c_text = date; + fmt_scan (fmt, buffer, length, dat); + fputs (buffer, stdout); + + return status; +} diff --git a/uip/dropsbr.c b/uip/dropsbr.c new file mode 100644 index 0000000..857263a --- /dev/null +++ b/uip/dropsbr.c @@ -0,0 +1,720 @@ + +/* + * dropsbr.c -- create/read/manipulate mail drops + * + * $Id$ + */ + +#include + +#ifndef MMDFONLY +# include +# include +# include +# include +#else +# include "dropsbr.h" +# include "strings.h" +# include "mmdfonly.h" +#endif + +#ifdef HAVE_ERRNO_H +# include +#endif + +#ifdef NTOHLSWAP +# include +#else +# undef ntohl +# define ntohl(n) (n) +#endif + +#include + +extern int errno; + +/* + * static prototypes + */ +static int mbx_chk_mbox (int); +static int mbx_chk_mmdf (int); +static int map_open (char *, int *, int); + + +/* + * Main entry point to open/create and lock + * a file or maildrop. + */ + +int +mbx_open (char *file, int mbx_style, uid_t uid, gid_t gid, mode_t mode) +{ + int j, count, fd; + struct stat st; + + j = 0; + + /* attempt to open and lock file */ + for (count = 4; count > 0; count--) { + if ((fd = lkopen (file, O_RDWR | O_CREAT | O_NONBLOCK, mode)) == NOTOK) { + switch (errno) { +#if defined(FCNTL_LOCKING) || defined(LOCKF_LOCKING) + case EACCES: + case EAGAIN: +#endif + +#ifdef FLOCK_LOCKING + case EWOULDBLOCK: +#endif + case ETXTBSY: + j = errno; + sleep (5); + break; + + default: + /* just return error */ + return NOTOK; + } + } + + /* good file descriptor */ + break; + } + + errno = j; + + /* + * Return if we still failed after 4 attempts, + * or we just want to skip the sanity checks. + */ + if (fd == NOTOK || mbx_style == OTHER_FORMAT) + return fd; + + /* + * Do sanity checks on maildrop. + */ + if (fstat (fd, &st) == NOTOK) { + /* + * The stat failed. So we make sure file + * has right ownership/modes + */ + chown (file, uid, gid); + chmod (file, mode); + } else if (st.st_size > (off_t) 0) { + int status; + + /* check the maildrop */ + switch (mbx_style) { + case MMDF_FORMAT: + default: + status = mbx_chk_mmdf (fd); + break; + + case MBOX_FORMAT: + status = mbx_chk_mbox (fd); + break; + } + + /* if error, attempt to close it */ + if (status == NOTOK) { + close (fd); + return NOTOK; + } + } + + return fd; +} + + +/* + * Check/prepare MBOX style maildrop for appending. + */ + +static int +mbx_chk_mbox (int fd) +{ + /* just seek to the end */ + if (lseek (fd, (off_t) 0, SEEK_END) == (off_t) NOTOK) + return NOTOK; + + return OK; +} + + +/* + * Check/prepare MMDF style maildrop for appending. + */ + +static int +mbx_chk_mmdf (int fd) +{ + size_t count; + char ldelim[BUFSIZ]; + + count = strlen (mmdlm2); + + /* casting -count to off_t, seem to break FreeBSD 2.2.6 */ + if (lseek (fd, (long) (-count), SEEK_END) == (off_t) NOTOK) + return NOTOK; + if (read (fd, ldelim, count) != count) + return NOTOK; + + ldelim[count] = 0; + + if (strcmp (ldelim, mmdlm2) + && write (fd, "\n", 1) != 1 + && write (fd, mmdlm2, count) != count) + return NOTOK; + + return OK; +} + + +int +mbx_read (FILE *fp, long pos, struct drop **drops, int noisy) +{ + register int len, size; + register long ld1, ld2; + register char *bp; + char buffer[BUFSIZ]; + register struct drop *cp, *dp, *ep, *pp; + + pp = (struct drop *) calloc ((size_t) (len = MAXFOLDER), sizeof(*dp)); + if (pp == NULL) { + if (noisy) + admonish (NULL, "unable to allocate drop storage"); + return NOTOK; + } + + ld1 = (long) strlen (mmdlm1); + ld2 = (long) strlen (mmdlm2); + + fseek (fp, pos, SEEK_SET); + for (ep = (dp = pp) + len - 1; fgets (buffer, sizeof(buffer), fp);) { + size = 0; + if (strcmp (buffer, mmdlm1) == 0) + pos += ld1, dp->d_start = (long) pos; + else { + dp->d_start = (long)pos , pos += (long) strlen (buffer); + for (bp = buffer; *bp; bp++, size++) + if (*bp == '\n') + size++; + } + + while (fgets (buffer, sizeof(buffer), fp) != NULL) + if (strcmp (buffer, mmdlm2) == 0) + break; + else { + pos += (long) strlen (buffer); + for (bp = buffer; *bp; bp++, size++) + if (*bp == '\n') + size++; + } + + if (dp->d_start != (long) pos) { + dp->d_id = 0; + dp->d_size = (long) size; + dp->d_stop = pos; + dp++; + } + pos += ld2; + + if (dp >= ep) { + register int curlen = dp - pp; + + cp = (struct drop *) realloc ((char *) pp, + (size_t) (len += MAXFOLDER) * sizeof(*pp)); + if (cp == NULL) { + if (noisy) + admonish (NULL, "unable to allocate drop storage"); + free ((char *) pp); + return 0; + } + dp = cp + curlen, ep = (pp = cp) + len - 1; + } + } + + if (dp == pp) + free ((char *) pp); + else + *drops = pp; + return (dp - pp); +} + + +int +mbx_write(char *mailbox, int md, FILE *fp, int id, long last, + long pos, off_t stop, int mapping, int noisy) +{ + register int i, j, size; + off_t start; + long off; + register char *cp; + char buffer[BUFSIZ]; + + off = (long) lseek (md, (off_t) 0, SEEK_CUR); + j = strlen (mmdlm1); + if (write (md, mmdlm1, j) != j) + return NOTOK; + start = lseek (md, (off_t) 0, SEEK_CUR); + size = 0; + + fseek (fp, pos, SEEK_SET); + while (fgets (buffer, sizeof(buffer), fp) && (pos < stop)) { + i = strlen (buffer); + for (j = 0; (j = stringdex (mmdlm1, buffer)) >= 0; buffer[j]++) + continue; + for (j = 0; (j = stringdex (mmdlm2, buffer)) >= 0; buffer[j]++) + continue; + if (write (md, buffer, i) != i) + return NOTOK; + pos += (long) i; + if (mapping) + for (cp = buffer; i-- > 0; size++) + if (*cp++ == '\n') + size++; + } + + stop = lseek (md, (off_t) 0, SEEK_CUR); + j = strlen (mmdlm2); + if (write (md, mmdlm2, j) != j) + return NOTOK; + if (mapping) + map_write (mailbox, md, id, last, start, stop, off, size, noisy); + + return OK; +} + + +/* + * Append message to end of file or maildrop. + */ + +int +mbx_copy (char *mailbox, int mbx_style, int md, int fd, + int mapping, char *text, int noisy) +{ + int i, j, size; + off_t start, stop; + long pos; + char *cp, buffer[BUFSIZ]; + FILE *fp; + + pos = (long) lseek (md, (off_t) 0, SEEK_CUR); + size = 0; + + switch (mbx_style) { + case MMDF_FORMAT: + default: + j = strlen (mmdlm1); + if (write (md, mmdlm1, j) != j) + return NOTOK; + start = lseek (md, (off_t) 0, SEEK_CUR); + + if (text) { + i = strlen (text); + if (write (md, text, i) != i) + return NOTOK; + for (cp = text; *cp++; size++) + if (*cp == '\n') + size++; + } + + while ((i = read (fd, buffer, sizeof(buffer))) > 0) { + for (j = 0; + (j = stringdex (mmdlm1, buffer)) >= 0; + buffer[j]++) + continue; + for (j = 0; + (j = stringdex (mmdlm2, buffer)) >= 0; + buffer[j]++) + continue; + if (write (md, buffer, i) != i) + return NOTOK; + if (mapping) + for (cp = buffer; i-- > 0; size++) + if (*cp++ == '\n') + size++; + } + + stop = lseek (md, (off_t) 0, SEEK_CUR); + j = strlen (mmdlm2); + if (write (md, mmdlm2, j) != j) + return NOTOK; + if (mapping) + map_write (mailbox, md, 0, (long) 0, start, stop, pos, size, noisy); + + return (i != NOTOK ? OK : NOTOK); + + case MBOX_FORMAT: + if ((j = dup (fd)) == NOTOK) + return NOTOK; + if ((fp = fdopen (j, "r")) == NULL) { + close (j); + return NOTOK; + } + start = lseek (md, (off_t) 0, SEEK_CUR); + + /* If text is given, we add it to top of message */ + if (text) { + i = strlen (text); + if (write (md, text, i) != i) + return NOTOK; + for (cp = text; *cp++; size++) + if (*cp == '\n') + size++; + } + + for (j = 0; fgets (buffer, sizeof(buffer), fp) != NULL; j++) { + + /* + * Check the first line, and make some changes. + */ + if (j == 0 && !text) { + /* + * Change the "Return-Path:" field (if in first line) + * back to "From ". + */ + if (!strncmp (buffer, "Return-Path:", 12)) { + char tmpbuffer[BUFSIZ]; + char *tp, *ep, *fp; + + strncpy(tmpbuffer, buffer, sizeof(tmpbuffer)); + ep = tmpbuffer + 13; + if (!(fp = strchr(ep + 1, ' '))) + fp = strchr(ep + 1, '\n'); + tp = dctime(dlocaltimenow()); + snprintf (buffer, sizeof(buffer), "From %.*s %s", + fp - ep, ep, tp); + } else if (!strncmp (buffer, "X-Envelope-From:", 16)) { + /* + * Change the "X-Envelope-From:" field + * (if first line) back to "From ". + */ + char tmpbuffer[BUFSIZ]; + char *ep; + + strncpy(tmpbuffer, buffer, sizeof(tmpbuffer)); + ep = tmpbuffer + 17; + snprintf (buffer, sizeof(buffer), "From %s", ep); + } else if (strncmp (buffer, "From ", 5)) { + /* + * If there is already a "From " line, + * then leave it alone. Else we add one. + */ + char tmpbuffer[BUFSIZ]; + char *tp, *ep; + + strncpy(tmpbuffer, buffer, sizeof(tmpbuffer)); + ep = "nobody@nowhere"; + tp = dctime(dlocaltimenow()); + snprintf (buffer, sizeof(buffer), "From %s %s", ep, tp); + strcat (buffer, tmpbuffer); + } + } + + /* + * If this is not first line, and begins with + * "From ", then prepend line with ">". + */ + if (j != 0 && strncmp (buffer, "From ", 5) == 0) { + write (md, ">", 1); + size++; + } + i = strlen (buffer); + if (write (md, buffer, i) != i) { + fclose (fp); + return NOTOK; + } + if (mapping) + for (cp = buffer; i-- > 0; size++) + if (*cp++ == '\n') + size++; + } + if (write (md, "\n", 1) != 1) { + fclose (fp); + return NOTOK; + } + if (mapping) + size += 2; + + fclose (fp); + lseek (fd, (off_t) 0, SEEK_END); + stop = lseek (md, (off_t) 0, SEEK_CUR); + if (mapping) + map_write (mailbox, md, 0, (long) 0, start, stop, pos, size, noisy); + + return OK; + } +} + + +int +mbx_size (int md, off_t start, off_t stop) +{ + register int i, fd; + register long pos; + register FILE *fp; + + if ((fd = dup (md)) == NOTOK || (fp = fdopen (fd, "r")) == NULL) { + if (fd != NOTOK) + close (fd); + return NOTOK; + } + + fseek (fp, start, SEEK_SET); + for (i = 0, pos = stop - start; pos-- > 0; i++) + if (fgetc (fp) == '\n') + i++; + + fclose (fp); + return i; +} + + +/* + * Close and unlock file/maildrop. + */ + +int +mbx_close (char *mailbox, int md) +{ + lkclose (md, mailbox); + return OK; +} + + +/* + * This function is performed implicitly by getbbent.c: + * bb->bb_map = map_name (bb->bb_file); + */ + +char * +map_name (char *file) +{ + register char *cp, *dp; + static char buffer[BUFSIZ]; + + if ((dp = strchr(cp = r1bindex (file, '/'), '.')) == NULL) + dp = cp + strlen (cp); + if (cp == file) + snprintf (buffer, sizeof(buffer), ".%.*s%s", dp - cp, cp, ".map"); + else + snprintf (buffer, sizeof(buffer), "%.*s.%.*s%s", + cp - file, file, dp - cp, cp, ".map"); + + return buffer; +} + + +int +map_read (char *file, long pos, struct drop **drops, int noisy) +{ + register int i, md, msgp; + register char *cp; + struct drop d; + register struct drop *mp, *dp; + + if ((md = open (cp = map_name (file), O_RDONLY)) == NOTOK + || map_chk (cp, md, mp = &d, pos, noisy)) { + if (md != NOTOK) + close (md); + return 0; + } + + msgp = mp->d_id; + dp = (struct drop *) calloc ((size_t) (msgp + 1), sizeof(*dp)); + if (dp == NULL) { + close (md); + return 0; + } + + memcpy((char *) dp, (char *) mp, sizeof(*dp)); + + lseek (md, (off_t) sizeof(*mp), SEEK_SET); + if ((i = read (md, (char *) (dp + 1), msgp * sizeof(*dp))) < sizeof(*dp)) { + i = 0; + free ((char *) dp); + } else { +#ifdef NTOHLSWAP + register struct drop *tdp; + int j; + + for (j = 0, tdp = dp; j < i / sizeof(*dp); j++, tdp++) { + tdp->d_id = ntohl(tdp->d_id); + tdp->d_size = ntohl(tdp->d_size); + tdp->d_start = ntohl(tdp->d_start); + tdp->d_stop = ntohl(tdp->d_stop); + } +#endif + *drops = dp; + } + + close (md); + + return (i / sizeof(*dp)); +} + + +int +map_write (char *mailbox, int md, int id, long last, off_t start, + off_t stop, long pos, int size, int noisy) +{ + register int i; + int clear, fd, td; + char *file; + register struct drop *dp; + struct drop d1, d2, *rp; + register FILE *fp; + + if ((fd = map_open (file = map_name (mailbox), &clear, md)) == NOTOK) + return NOTOK; + + if (!clear && map_chk (file, fd, &d1, pos, noisy)) { + unlink (file); + mbx_close (file, fd); + if ((fd = map_open (file, &clear, md)) == NOTOK) + return NOTOK; + clear++; + } + + if (clear) { + if ((td = dup (md)) == NOTOK || (fp = fdopen (td, "r")) == NULL) { + if (noisy) + admonish (file, "unable to %s", td != NOTOK ? "fdopen" : "dup"); + if (td != NOTOK) + close (td); + mbx_close (file, fd); + return NOTOK; + } + + switch (i = mbx_read (fp, 0, &rp, noisy)) { + case NOTOK: + fclose (fp); + mbx_close (file, fd); + return NOTOK; + + case OK: + break; + + default: + d1.d_id = 0; + for (dp = rp; i-- >0; dp++) { + if (dp->d_start == start) + dp->d_id = id; + lseek (fd, (off_t) (++d1.d_id * sizeof(*dp)), SEEK_SET); + if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) { + if (noisy) + admonish (file, "write error"); + mbx_close (file, fd); + fclose (fp); + return NOTOK; + } + } + free ((char *) rp); + break; + } + } + else { + if (last == 0) + last = d1.d_start; + dp = &d2; + dp->d_id = id; + dp->d_size = (long) (size ? size : mbx_size (fd, start, stop)); + dp->d_start = start; + dp->d_stop = stop; + lseek (fd, (off_t) (++d1.d_id * sizeof(*dp)), SEEK_SET); + if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) { + if (noisy) + admonish (file, "write error"); + mbx_close (file, fd); + return NOTOK; + } + } + + dp = &d1; + dp->d_size = DRVRSN; + dp->d_start = (long) last; + dp->d_stop = lseek (md, (off_t) 0, SEEK_CUR); + + lseek (fd, (off_t) 0, SEEK_SET); + if (write (fd, (char *) dp, sizeof(*dp)) != sizeof(*dp)) { + if (noisy) + admonish (file, "write error"); + mbx_close (file, fd); + return NOTOK; + } + + mbx_close (file, fd); + + return OK; +} + + +static int +map_open (char *file, int *clear, int md) +{ + mode_t mode; + struct stat st; + + mode = fstat (md, &st) != NOTOK ? (mode_t) (st.st_mode & 0777) : m_gmprot (); + return mbx_open (file, OTHER_FORMAT, st.st_uid, st.st_gid, mode); +} + + +int +map_chk (char *file, int fd, struct drop *dp, long pos, int noisy) +{ + long count; + struct drop d, tmpd; + register struct drop *dl; + + if (read (fd, (char *) &tmpd, sizeof(*dp)) != sizeof(*dp)) { +#ifdef notdef + admonish (NULL, "%s: missing or partial index", file); +#endif /* notdef */ + return NOTOK; + } +#ifndef NTOHLSWAP + *dp = tmpd; /* if ntohl(n)=(n), can use struct assign */ +#else + dp->d_id = ntohl(tmpd.d_id); + dp->d_size = ntohl(tmpd.d_size); + dp->d_start = ntohl(tmpd.d_start); + dp->d_stop = ntohl(tmpd.d_stop); +#endif + + if (dp->d_size != DRVRSN) { + if (noisy) + admonish (NULL, "%s: version mismatch (%d != %d)", file, + dp->d_size, DRVRSN); + return NOTOK; + } + + if (dp->d_stop != pos) { + if (noisy && pos != (long) 0) + admonish (NULL, + "%s: pointer mismatch or incomplete index (%ld!=%ld)", + file, dp->d_stop, (long) pos); + return NOTOK; + } + + if ((long) ((dp->d_id + 1) * sizeof(*dp)) != (long) lseek (fd, (off_t) 0, SEEK_END)) { + if (noisy) + admonish (NULL, "%s: corrupt index(1)", file); + return NOTOK; + } + + dl = &d; + count = (long) strlen (mmdlm2); + lseek (fd, (off_t) (dp->d_id * sizeof(*dp)), SEEK_SET); + if (read (fd, (char *) dl, sizeof(*dl)) != sizeof(*dl) + || (ntohl(dl->d_stop) != dp->d_stop + && ntohl(dl->d_stop) + count != dp->d_stop)) { + if (noisy) + admonish (NULL, "%s: corrupt index(2)", file); + return NOTOK; + } + + return OK; +} diff --git a/uip/flist.c b/uip/flist.c new file mode 100644 index 0000000..0e2b161 --- /dev/null +++ b/uip/flist.c @@ -0,0 +1,709 @@ +/* + * flist.c -- list nmh folders containing messages + * -- in a given sequence + * + * originally by + * David Nichols, Xerox-PARC, November, 1992 + * + * Copyright (c) 1994 Xerox Corporation. + * Use and copying of this software and preparation of derivative works based + * upon this software are permitted. Any distribution of this software or + * derivative works must comply with all applicable United States export + * control laws. This software is made available AS IS, and Xerox Corporation + * makes no warranty about the software, its performance or its conformity to + * any specification. + * + * $Id$ + */ + +#include + +#define FALSE 0 +#define TRUE 1 + +/* + * We allocate space to record the names of folders + * (foldersToDo array), this number of elements at a time. + */ +#define MAXFOLDERS 100 + + +static struct swit switches[] = { +#define SEQSW 0 + { "sequence name", 0 }, +#define ALLSW 1 + { "all", 0 }, +#define NOALLSW 2 + { "noall", 0 }, +#define RECURSE 3 + { "recurse", 0 }, +#define NORECURSE 4 + { "norecurse", 0 }, +#define SHOWZERO 5 + { "showzero", 0 }, +#define NOSHOWZERO 6 + { "noshowzero", 0 }, +#define ALPHASW 7 + { "alpha", 0 }, +#define NOALPHASW 8 + { "noalpha", 0 }, +#define FASTSW 9 + { "fast", 0 }, +#define NOFASTSW 10 + { "nofast", 0 }, +#define TOTALSW 11 + { "total", -5 }, +#define NOTOTALSW 12 + { "nototal", -7 }, +#define VERSIONSW 13 + { "version", 0 }, +#define HELPSW 14 + { "help", 4 }, + { NULL, 0 } +}; + +struct Folder { + char *name; /* name of folder */ + int priority; + int error; /* error == 1 for unreadable folder */ + int nMsgs; /* number of messages in folder */ + int nSeq[NUMATTRS]; /* number of messages in each sequence */ + int private[NUMATTRS]; /* is given sequence, public or private */ +}; + +static struct Folder *orders = NULL; +static int nOrders = 0; +static int nOrdersAlloced = 0; +static struct Folder *folders = NULL; +static int nFolders = 0; +static int nFoldersAlloced = 0; + +/* info on folders to search */ +static char **foldersToDo; +static int numfolders; +static int maxfolders; + +/* info on sequences to search for */ +static char *sequencesToDo[NUMATTRS]; +static int numsequences; + +static int all = FALSE; /* scan all folders in top level? */ +static int alphaOrder = FALSE; /* want alphabetical order only */ +static int recurse = FALSE; /* show nested folders? */ +static int showzero = TRUE; /* show folders even if no messages in seq? */ +static int Total = TRUE; /* display info on number of messages in * + * sequence found, and total num messages */ + +static char curfolder[BUFSIZ]; /* name of the current folder */ +static char *nmhdir; /* base nmh mail directory */ + +/* + * Type for a compare function for qsort. This keeps + * the compiler happy. + */ +typedef int (*qsort_comp) (const void *, const void *); + +/* + * prototypes + */ +int CompareFolders(struct Folder *, struct Folder *); +void GetFolderOrder(void); +void ScanFolders(void); +int AddFolder(char *, int); +void BuildFolderList(char *, int); +void BuildFolderListRecurse(char *, struct stat *, int); +void PrintFolders(void); +static int num_digits (int); +void AllocFolders(struct Folder **, int *, int); +int AssignPriority(char *); +static void do_readonly_folders(void); + + + +int +main(int argc, char **argv) +{ + char *cp, **argp; + char **arguments; + char buf[BUFSIZ]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex(argv[0], '/'); + + /* read user profile/context */ + context_read(); + + /* + * If program was invoked with name ending + * in `s', then add switch `-all'. + */ + if (argv[0][strlen (argv[0]) - 1] == 's') + all = TRUE; + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* allocate the initial space to record the folder names */ + numfolders = 0; + maxfolders = MAXFOLDERS; + if (!(foldersToDo = (char **) malloc ((size_t) (maxfolders * sizeof(*foldersToDo))))) + adios (NULL, "unable to allocate folder storage"); + + /* no sequences yet */ + numsequences = 0; + + /* parse arguments */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch(++cp, switches)) { + case AMBIGSW: + ambigsw(cp, switches); + done(1); + case UNKWNSW: + adios(NULL, "-%s unknown", cp); + + case HELPSW: + snprintf(buf, sizeof(buf), "%s [+folder1 [+folder2 ...]][switches]", + invo_name); + print_help(buf, switches, 1); + done(1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case SEQSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + + /* check if too many sequences specified */ + if (numsequences >= NUMATTRS) + adios (NULL, "too many sequences (more than %d) specified", NUMATTRS); + sequencesToDo[numsequences++] = cp; + break; + + case ALLSW: + all = TRUE; + break; + case NOALLSW: + all = FALSE; + break; + + case SHOWZERO: + showzero = TRUE; + break; + case NOSHOWZERO: + showzero = FALSE; + break; + + case ALPHASW: + alphaOrder = TRUE; + break; + case NOALPHASW: + alphaOrder = FALSE; + break; + + case NOFASTSW: + case TOTALSW: + Total = TRUE; + break; + + case FASTSW: + case NOTOTALSW: + Total = FALSE; + break; + + case RECURSE: + recurse = TRUE; + break; + case NORECURSE: + recurse = FALSE; + break; + } + } else { + /* + * Check if we need to allocate more space + * for folder names. + */ + if (numfolders >= maxfolders) { + maxfolders += MAXFOLDERS; + if (!(foldersToDo = (char **) realloc (foldersToDo, + (size_t) (maxfolders * sizeof(*foldersToDo))))) + adios (NULL, "unable to reallocate folder name storage"); + } + if (*cp == '+') + ++cp; + foldersToDo[numfolders++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* get current folder */ + strncpy (curfolder, getfolder(1), sizeof(curfolder)); + + /* get nmh base directory */ + nmhdir = m_maildir (""); + + /* + * If we didn't specify any sequences, we search + * for the "Unseen-Sequence" profile entry and use + * all the sequences defined there. We check to + * make sure that the Unseen-Sequence entry doesn't + * contain more than NUMATTRS sequences. + */ + if (numsequences == 0) { + if ((cp = context_find(usequence)) && *cp) { + char **ap, *dp; + + dp = getcpy(cp); + ap = brkstring (dp, " ", "\n"); + for (; ap && *ap; ap++) { + if (numsequences >= NUMATTRS) + adios (NULL, "too many sequences (more than %d) in %s profile entry", + NUMATTRS, usequence); + else + sequencesToDo[numsequences++] = *ap; + } + } else { + adios (NULL, "no sequence specified or %s profile entry found", usequence); + } + } + + GetFolderOrder(); + ScanFolders(); + qsort(folders, nFolders, sizeof(struct Folder), (qsort_comp) CompareFolders); + PrintFolders(); + done (0); +} + +/* + * Read the Flist-Order profile entry to determine + * how to sort folders for output. + */ + +void +GetFolderOrder(void) +{ + char *p, *s; + int priority = 1; + struct Folder *o; + + if (!(p = context_find("Flist-Order"))) + return; + for (;;) { + while (isspace(*p)) + ++p; + s = p; + while (*p && !isspace(*p)) + ++p; + if (p != s) { + /* Found one. */ + AllocFolders(&orders, &nOrdersAlloced, nOrders + 1); + o = &orders[nOrders++]; + o->priority = priority++; + o->name = (char *) malloc(p - s + 1); + strncpy(o->name, s, p - s); + o->name[p - s] = 0; + } else + break; + } +} + +/* + * Scan all the necessary folders + */ + +void +ScanFolders(void) +{ + int i; + + /* + * change directory to base of nmh directory + */ + if (chdir (nmhdir) == NOTOK) + adios (nmhdir, "unable to change directory to"); + + if (numfolders > 0) { + /* Update context */ + strncpy (curfolder, foldersToDo[numfolders - 1], sizeof(curfolder)); + context_replace (pfolder, curfolder);/* update current folder */ + context_save (); /* save the context file */ + + /* + * Scan each given folder. If -all is given, + * then also scan the 1st level subfolders under + * each given folder. + */ + for (i = 0; i < numfolders; ++i) + BuildFolderList(foldersToDo[i], all ? 1 : 0); + } else { + if (all) { + /* + * Do the readonly folders + */ + do_readonly_folders(); + + /* + * Now scan the entire nmh directory for folders + */ + BuildFolderList(".", 0); + } else { + /* + * Else scan current folder + */ + BuildFolderList(curfolder, 0); + } + } +} + +/* + * Initial building of folder list for + * the top of our search tree. + */ + +void +BuildFolderList(char *dirName, int searchdepth) +{ + struct stat st; + + /* Make sure we have a directory */ + if ((stat(dirName, &st) == -1) || !S_ISDIR(st.st_mode)) + return; + + /* + * If base directory, don't add it to the + * folder list. We just recurse into it. + */ + if (!strcmp (dirName, ".")) { + BuildFolderListRecurse (".", &st, 0); + return; + } + + /* + * Add this folder to the list. + * If recursing and directory has subfolders, + * then build folder list for subfolders. + */ + if (AddFolder(dirName, showzero) && (recurse || searchdepth) && st.st_nlink > 2) + BuildFolderListRecurse(dirName, &st, searchdepth - 1); +} + +/* + * Recursive building of folder list + */ + +void +BuildFolderListRecurse(char *dirName, struct stat *s, int searchdepth) +{ + char *base, name[PATH_MAX]; + int nlinks; + DIR *dir; + struct dirent *dp; + struct stat st; + + /* + * Keep track of number of directories we've seen so we can + * stop stat'ing entries in this directory once we've seen + * them all. This optimization will fail if you have extra + * directories beginning with ".", since we don't bother to + * stat them. But that shouldn't generally be a problem. + */ + nlinks = s->st_nlink; + + if (!(dir = opendir(dirName))) + adios(dirName, "can't open directory"); + + /* + * A hack so that we don't see a + * leading "./" in folder names. + */ + base = strcmp (dirName, ".") ? dirName : dirName + 1; + + while (nlinks && (dp = readdir(dir))) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) { + nlinks--; + continue; + } + if (dp->d_name[0] == '.') + continue; + strncpy (name, base, sizeof(name) - 2); + if (*base) + strcat(name, "/"); + strncat(name, dp->d_name, sizeof(name) - strlen(name) - 1); + if ((stat(name, &st) != -1) && S_ISDIR(st.st_mode)) { + /* + * Check if this was really a symbolic link pointing + * to a directory. If not, then decrement link count. + */ + if (lstat (name, &st) == -1) + nlinks--; + /* Add this folder to the list */ + if (AddFolder(name, showzero) && + (recurse || searchdepth) && st.st_nlink > 2) + BuildFolderListRecurse(name, &st, searchdepth - 1); + } + } + closedir(dir); +} + +/* + * Add this folder to our list, counting the total number of + * messages and the number of messages in each sequence. + */ + +int +AddFolder(char *name, int force) +{ + int i, msgnum, nonzero; + int seqnum[NUMATTRS], nSeq[NUMATTRS]; + struct Folder *f; + struct msgs *mp; + + /* Read folder and create message structure */ + if (!(mp = folder_read (name))) { + /* Oops, error occurred. Record it and continue. */ + AllocFolders(&folders, &nFoldersAlloced, nFolders + 1); + f = &folders[nFolders++]; + f->name = getcpy(name); + f->error = 1; + f->priority = AssignPriority(f->name); + return 0; + } + + for (i = 0; i < numsequences; i++) { + /* Convert sequences to their sequence numbers */ + if (sequencesToDo[i]) + seqnum[i] = seq_getnum(mp, sequencesToDo[i]); + else + seqnum[i] = -1; + + /* Now count messages in this sequence */ + nSeq[i] = 0; + if (mp->nummsg > 0 && seqnum[i] != -1) { + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) { + if (in_sequence(mp, seqnum[i], msgnum)) + nSeq[i]++; + } + } + } + + /* Check if any of the sequence checks were nonzero */ + nonzero = 0; + for (i = 0; i < numsequences; i++) { + if (nSeq[i] > 0) { + nonzero = 1; + break; + } + } + + if (nonzero || force) { + /* save general folder information */ + AllocFolders(&folders, &nFoldersAlloced, nFolders + 1); + f = &folders[nFolders++]; + f->name = getcpy(name); + f->nMsgs = mp->nummsg; + f->error = 0; + f->priority = AssignPriority(f->name); + + /* record the sequence information */ + for (i = 0; i < numsequences; i++) { + f->nSeq[i] = nSeq[i]; + f->private[i] = (seqnum[i] != -1) ? is_seq_private(mp, seqnum[i]) : 0; + } + } + + folder_free (mp); /* free folder/message structure */ + return 1; +} + +/* + * Print the folder/sequence information + */ + +void +PrintFolders(void) +{ + char tmpname[BUFSIZ]; + int i, j, len, has_private = 0; + int maxfolderlen = 0, maxseqlen = 0; + int maxnum = 0, maxseq = 0; + + if (!Total) { + for (i = 0; i < nFolders; i++) + printf("%s\n", folders[i].name); + return; + } + + /* + * Find the width we need for various fields + */ + for (i = 0; i < nFolders; ++i) { + /* find the length of longest folder name */ + len = strlen(folders[i].name); + if (len > maxfolderlen) + maxfolderlen = len; + + /* If folder had error, skip the rest */ + if (folders[i].error) + continue; + + /* find the maximum total messages */ + if (folders[i].nMsgs > maxnum) + maxnum = folders[i].nMsgs; + + for (j = 0; j < numsequences; j++) { + /* find maximum width of sequence name */ + len = strlen (sequencesToDo[j]); + if ((folders[i].nSeq[j] > 0 || showzero) && (len > maxseqlen)) + maxseqlen = len; + + /* find the maximum number of messages in sequence */ + if (folders[i].nSeq[j] > maxseq) + maxseq = folders[i].nSeq[j]; + + /* check if this sequence is private in any of the folders */ + if (folders[i].private[j]) + has_private = 1; + } + } + + /* Now print all the folder/sequence information */ + for (i = 0; i < nFolders; i++) { + for (j = 0; j < numsequences; j++) { + if (folders[i].nSeq[j] > 0 || showzero) { + /* Add `+' to end of name of current folder */ + if (strcmp(curfolder, folders[i].name)) + snprintf(tmpname, sizeof(tmpname), "%s", folders[i].name); + else + snprintf(tmpname, sizeof(tmpname), "%s+", folders[i].name); + + if (folders[i].error) { + printf("%-*s is unreadable\n", maxfolderlen+1, tmpname); + continue; + } + + printf("%-*s has %*d in sequence %-*s%s; out of %*d\n", + maxfolderlen+1, tmpname, + num_digits(maxseq), folders[i].nSeq[j], + maxseqlen, sequencesToDo[j], + !has_private ? "" : folders[i].private[j] ? " (private)" : " ", + num_digits(maxnum), folders[i].nMsgs); + } + } + } +} + +/* + * Calculate the number of digits in a nonnegative integer + */ +static int +num_digits (int n) +{ + int ndigits = 0; + + /* Sanity check */ + if (n < 0) + adios (NULL, "oops, num_digits called with negative value"); + + if (n == 0) + return 1; + + while (n) { + n /= 10; + ndigits++; + } + + return ndigits; +} + +/* + * Put them in priority order. + */ + +int +CompareFolders(struct Folder *f1, struct Folder *f2) +{ + if (!alphaOrder && f1->priority != f2->priority) + return f1->priority - f2->priority; + else + return strcmp(f1->name, f2->name); +} + +/* + * Make sure we have at least n folders allocated. + */ + +void +AllocFolders(struct Folder **f, int *nfa, int n) +{ + if (n <= *nfa) + return; + if (*f == NULL) { + *nfa = 10; + *f = (struct Folder *) malloc (*nfa * (sizeof(struct Folder))); + } else { + *nfa *= 2; + *f = (struct Folder *) realloc (*f, *nfa * (sizeof(struct Folder))); + } +} + +/* + * Return the priority for a name. The highest comes from an exact match. + * After that, the longest match (then first) assigns the priority. + */ +int +AssignPriority(char *name) +{ + int i, ol, nl; + int best = nOrders; + int bestLen = 0; + struct Folder *o; + + nl = strlen(name); + for (i = 0; i < nOrders; ++i) { + o = &orders[i]; + if (!strcmp(name, o->name)) + return o->priority; + ol = strlen(o->name); + if (nl < ol - 1) + continue; + if (ol < bestLen) + continue; + if (o->name[0] == '*' && !strcmp(o->name + 1, name + (nl - ol + 1))) { + best = o->priority; + bestLen = ol; + } else if (o->name[ol - 1] == '*' && strncmp(o->name, name, ol - 1) == 0) { + best = o->priority; + bestLen = ol; + } + } + return best; +} + +/* + * Do the read only folders + */ + +static void +do_readonly_folders (void) +{ + int atrlen; + char atrcur[BUFSIZ]; + register struct node *np; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + snprintf (atrcur, sizeof(atrcur), "atr-%s-", current); + atrlen = strlen (atrcur); + + for (np = m_defs; np; np = np->n_next) + if (ssequal (atrcur, np->n_name) + && !ssequal (nmhdir, np->n_name + atrlen)) + BuildFolderList (np->n_name + atrlen, 0); +} diff --git a/uip/fmtdump.c b/uip/fmtdump.c new file mode 100644 index 0000000..89e70ef --- /dev/null +++ b/uip/fmtdump.c @@ -0,0 +1,506 @@ + +/* + * fmtdump.c -- compile format file and dump out instructions + * + * $Id$ + */ + +#include +#include +#include +#include + +static struct swit switches[] = { +#define FORMSW 0 + { "form formatfile", 0 }, +#define FMTSW 1 + { "format string", 5 }, +#define VERSIONSW 2 + { "version", 0 }, +#define HELPSW 3 + { "help", 4 }, + { NULL, 0 } +}; + +/* for assignlabel */ +static struct format *lvec[128]; +static lused = 0; + +/* + * static prototypes + */ +static void fmt_dump (struct format *); +static void dumpone(struct format *); +static int findlabel(struct format *); +static void assignlabel(struct format *); +static char *f_typestr(int); +static char *c_typestr(int); +static void litputs(char *); +static void litputc(char); + + +int +main (int argc, char **argv) +{ + int ncomps; + char *cp, *form = NULL, *format = NULL; + char buf[BUFSIZ], *nfs, **argp, **arguments; + struct format *fmt; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches]", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + } + } + if (form) + adios (NULL, "only one form at a time!"); + else + form = cp; + } + + /* + * Get new format string. Must be before chdir(). + */ + nfs = new_fs (form, format, FORMAT); + ncomps = fmt_compile(nfs, &fmt); + + fmt_dump(fmt); + done(0); +} + +static void +fmt_dump (struct format *fmth) +{ + int i; + register struct format *fmt, *addr; + + /* Assign labels */ + for (fmt = fmth; fmt; ++fmt) { + i = fmt->f_type; + if (i == FT_IF_S || + i == FT_IF_S_NULL || + i == FT_IF_V_EQ || + i == FT_IF_V_NE || + i == FT_IF_V_GT || + i == FT_IF_MATCH || + i == FT_IF_AMATCH || + i == FT_GOTO) { + addr = fmt + fmt->f_skip; + if (findlabel(addr) < 0) + assignlabel(addr); + } + if (fmt->f_type == FT_DONE && fmt->f_value == 0) + break; + } + + /* Dump them out! */ + for (fmt = fmth; fmt; ++fmt) { + dumpone(fmt); + if (fmt->f_type == FT_DONE && fmt->f_value == 0) + break; + } +} + +static void +dumpone(struct format *fmt) +{ + register int i; + + if ((i = findlabel(fmt)) >= 0) + printf("L%d:", i); + putchar('\t'); + + fputs(f_typestr((int)fmt->f_type), stdout); + + switch (fmt->f_type) { + + case FT_COMP: + case FT_LS_COMP: + case FT_LV_COMPFLAG: + case FT_LV_COMP: + printf(", comp "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %d", fmt->f_comp->c_flags); + break; + + case FT_LV_SEC: + case FT_LV_MIN: + case FT_LV_HOUR: + case FT_LV_MDAY: + case FT_LV_MON: + case FT_LS_MONTH: + case FT_LS_LMONTH: + case FT_LS_ZONE: + case FT_LV_YEAR: + case FT_LV_WDAY: + case FT_LS_DAY: + case FT_LS_WEEKDAY: + case FT_LV_YDAY: + case FT_LV_ZONE: + case FT_LV_CLOCK: + case FT_LV_RCLOCK: + case FT_LV_DAYF: + case FT_LV_ZONEF: + case FT_LV_DST: + case FT_LS_822DATE: + case FT_LS_PRETTY: + case FT_LOCALDATE: + case FT_GMTDATE: + case FT_PARSEDATE: + printf(", c_name "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %d", fmt->f_comp->c_flags); + break; + + case FT_LS_ADDR: + case FT_LS_PERS: + case FT_LS_MBOX: + case FT_LS_HOST: + case FT_LS_PATH: + case FT_LS_GNAME: + case FT_LS_NOTE: + case FT_LS_822ADDR: + case FT_LV_HOSTTYPE: + case FT_LV_INGRPF: + case FT_LV_NOHOSTF: + case FT_LS_FRIENDLY: + case FT_PARSEADDR: + case FT_MYMBOX: + printf(", c_name "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %d", fmt->f_comp->c_flags); + break; + + case FT_COMPF: + printf(", width %d, fill '", fmt->f_width); + litputc(fmt->f_fill); + printf("' name "); + litputs(fmt->f_comp->c_name); + if (fmt->f_comp->c_type) + printf(", c_type %s", c_typestr(fmt->f_comp->c_type)); + if (fmt->f_comp->c_flags) + printf(", c_flags %d", fmt->f_comp->c_flags); + break; + + case FT_STRF: + case FT_NUMF: + printf(", width %d, fill '", fmt->f_width); + litputc(fmt->f_fill); + putchar('\''); + break; + + case FT_LIT: +#ifdef FT_LIT_FORCE + case FT_LIT_FORCE: +#endif + putchar(' '); + litputs(fmt->f_text); + break; + + case FT_LITF: + printf(", width %d, fill '", fmt->f_width); + litputc(fmt->f_fill); + printf("' "); + litputs(fmt->f_text); + break; + + case FT_CHAR: + putchar(' '); + putchar('\''); + litputc(fmt->f_char); + putchar('\''); + break; + + + case FT_IF_S: + case FT_IF_S_NULL: + case FT_IF_MATCH: + case FT_IF_AMATCH: + printf(" continue else goto"); + case FT_GOTO: + i = findlabel(fmt + fmt->f_skip); + printf(" L%d", i); + break; + + case FT_IF_V_EQ: + case FT_IF_V_NE: + case FT_IF_V_GT: + i = findlabel(fmt + fmt->f_skip); + printf(" %d continue else goto L%d", fmt->f_value, i); + break; + + case FT_V_EQ: + case FT_V_NE: + case FT_V_GT: + case FT_LV_LIT: + case FT_LV_PLUS_L: + case FT_LV_MINUS_L: + case FT_LV_DIVIDE_L: + case FT_LV_MODULO_L: + printf(" value %d", fmt->f_value); + break; + + case FT_LS_LIT: + printf(" str "); + litputs(fmt->f_text); + break; + + case FT_LS_GETENV: + printf(" getenv "); + litputs(fmt->f_text); + break; + + case FT_LS_DECODECOMP: + printf(", comp "); + litputs(fmt->f_comp->c_name); + break; + + case FT_LS_DECODE: + break; + + case FT_LS_TRIM: + printf(", width %d", fmt->f_width); + break; + + case FT_LV_DAT: + printf(", value dat[%d]", fmt->f_value); + break; + } + putchar('\n'); +} + +static int +findlabel(struct format *addr) +{ + register int i; + + for (i = 0; i < lused; ++i) + if (addr == lvec[i]) + return(i); + return(-1); +} + +static void +assignlabel(struct format *addr) +{ + lvec[lused++] = addr; +} + +static char * +f_typestr(int t) +{ + static char buf[32]; + + switch (t) { + case FT_COMP: return("COMP"); + case FT_COMPF: return("COMPF"); + case FT_LIT: return("LIT"); + case FT_LITF: return("LITF"); +#ifdef FT_LIT_FORCE + case FT_LIT_FORCE: return("LIT_FORCE"); +#endif + case FT_CHAR: return("CHAR"); + case FT_NUM: return("NUM"); + case FT_NUMF: return("NUMF"); + case FT_STR: return("STR"); + case FT_STRF: return("STRF"); + case FT_STRFW: return("STRFW"); + case FT_PUTADDR: return("PUTADDR"); + case FT_LS_COMP: return("LS_COMP"); + case FT_LS_LIT: return("LS_LIT"); + case FT_LS_GETENV: return("LS_GETENV"); + case FT_LS_DECODECOMP: return("FT_LS_DECODECOMP"); + case FT_LS_DECODE: return("FT_LS_DECODE"); + case FT_LS_TRIM: return("LS_TRIM"); + case FT_LV_COMP: return("LV_COMP"); + case FT_LV_COMPFLAG: return("LV_COMPFLAG"); + case FT_LV_LIT: return("LV_LIT"); + case FT_LV_DAT: return("LV_DAT"); + case FT_LV_STRLEN: return("LV_STRLEN"); + case FT_LV_PLUS_L: return("LV_PLUS_L"); + case FT_LV_MINUS_L: return("LV_MINUS_L"); + case FT_LV_DIVIDE_L: return("LV_DIVIDE_L"); + case FT_LV_MODULO_L: return("LV_MODULO_L"); + case FT_LV_CHAR_LEFT: return("LV_CHAR_LEFT"); + case FT_LS_MONTH: return("LS_MONTH"); + case FT_LS_LMONTH: return("LS_LMONTH"); + case FT_LS_ZONE: return("LS_ZONE"); + case FT_LS_DAY: return("LS_DAY"); + case FT_LS_WEEKDAY: return("LS_WEEKDAY"); + case FT_LS_822DATE: return("LS_822DATE"); + case FT_LS_PRETTY: return("LS_PRETTY"); + case FT_LV_SEC: return("LV_SEC"); + case FT_LV_MIN: return("LV_MIN"); + case FT_LV_HOUR: return("LV_HOUR"); + case FT_LV_MDAY: return("LV_MDAY"); + case FT_LV_MON: return("LV_MON"); + case FT_LV_YEAR: return("LV_YEAR"); + case FT_LV_YDAY: return("LV_YDAY"); + case FT_LV_WDAY: return("LV_WDAY"); + case FT_LV_ZONE: return("LV_ZONE"); + case FT_LV_CLOCK: return("LV_CLOCK"); + case FT_LV_RCLOCK: return("LV_RCLOCK"); + case FT_LV_DAYF: return("LV_DAYF"); + case FT_LV_DST: return("LV_DST"); + case FT_LV_ZONEF: return("LV_ZONEF"); + case FT_LS_ADDR: return("LS_ADDR"); + case FT_LS_PERS: return("LS_PERS"); + case FT_LS_MBOX: return("LS_MBOX"); + case FT_LS_HOST: return("LS_HOST"); + case FT_LS_PATH: return("LS_PATH"); + case FT_LS_GNAME: return("LS_GNAME"); + case FT_LS_NOTE: return("LS_NOTE"); + case FT_LS_822ADDR: return("LS_822ADDR"); + case FT_LS_FRIENDLY: return("LS_FRIENDLY"); + case FT_LV_HOSTTYPE: return("LV_HOSTTYPE"); + case FT_LV_INGRPF: return("LV_INGRPF"); + case FT_LV_NOHOSTF: return("LV_NOHOSTF"); + case FT_LOCALDATE: return("LOCALDATE"); + case FT_GMTDATE: return("GMTDATE"); + case FT_PARSEDATE: return("PARSEDATE"); + case FT_PARSEADDR: return("PARSEADDR"); + case FT_FORMATADDR: return("FORMATADDR"); + case FT_MYMBOX: return("MYMBOX"); +#ifdef FT_ADDTOSEQ + case FT_ADDTOSEQ: return("ADDTOSEQ"); +#endif + case FT_SAVESTR: return("SAVESTR"); +#ifdef FT_PAUSE + case FT_PAUSE: return ("PAUSE"); +#endif + case FT_DONE: return("DONE"); + case FT_NOP: return("NOP"); + case FT_GOTO: return("GOTO"); + case FT_IF_S_NULL: return("IF_S_NULL"); + case FT_IF_S: return("IF_S"); + case FT_IF_V_EQ: return("IF_V_EQ"); + case FT_IF_V_NE: return("IF_V_NE"); + case FT_IF_V_GT: return("IF_V_GT"); + case FT_IF_MATCH: return("IF_MATCH"); + case FT_IF_AMATCH: return("IF_AMATCH"); + case FT_S_NULL: return("S_NULL"); + case FT_S_NONNULL: return("S_NONNULL"); + case FT_V_EQ: return("V_EQ"); + case FT_V_NE: return("V_NE"); + case FT_V_GT: return("V_GT"); + case FT_V_MATCH: return("V_MATCH"); + case FT_V_AMATCH: return("V_AMATCH"); + default: + printf(buf, "/* ??? #%d */", t); + return(buf); + } +} + +#define FNORD(v, s) if (t & (v)) { \ + if (i++ > 0) \ + strcat(buf, "|"); \ + strcat(buf, s); } + +static char * +c_typestr(int t) +{ + register int i; + static char buf[64]; + + buf[0] = '\0'; + if (t & ~(CT_ADDR|CT_DATE|CT_MYMBOX|CT_ADDRPARSE)) + printf(buf, "0x%x ", t); + strcat(buf, "<"); + i = 0; + FNORD(CT_ADDR, "ADDR"); + FNORD(CT_DATE, "DATE"); + FNORD(CT_MYMBOX, "MYMBOX"); + FNORD(CT_ADDRPARSE, "ADDRPARSE"); + strcat(buf, ">"); + return(buf); +#undef FNORD +} + +static void +litputs(char *s) +{ + if (s) { + putc('"', stdout); + while (*s) + litputc(*s++); + putc('"', stdout); + } else + fputs("", stdout); +} + +static void +litputc(char c) +{ + if (c & ~ 0177) { + putc('M', stdout); + putc('-', stdout); + c &= 0177; + } + if (c < 0x20 || c == 0177) { + if (c == '\b') { + putc('\\', stdout); + putc('b', stdout); + } else if (c == '\f') { + putc('\\', stdout); + putc('f', stdout); + } else if (c == '\n') { + putc('\\', stdout); + putc('n', stdout); + } else if (c == '\r') { + putc('\\', stdout); + putc('r', stdout); + } else if (c == '\t') { + putc('\\', stdout); + putc('t', stdout); + } else { + putc('^', stdout); + putc(c ^ 0x40, stdout); /* DEL to ?, others to alpha */ + } + } else + putc(c, stdout); +} diff --git a/uip/folder.c b/uip/folder.c new file mode 100644 index 0000000..c023e11 --- /dev/null +++ b/uip/folder.c @@ -0,0 +1,828 @@ + +/* + * folder(s).c -- set/list the current message and/or folder + * -- push/pop a folder onto/from the folder stack + * -- list the folder stack + * + * $Id$ + */ + +#include +#include + +static struct swit switches[] = { +#define ALLSW 0 + { "all", 0 }, +#define NALLSW 1 + { "noall", 0 }, +#define CREATSW 2 + { "create", 0 }, +#define NCREATSW 3 + { "nocreate", 0 }, +#define FASTSW 4 + { "fast", 0 }, +#define NFASTSW 5 + { "nofast", 0 }, +#define HDRSW 6 + { "header", 0 }, +#define NHDRSW 7 + { "noheader", 0 }, +#define PACKSW 8 + { "pack", 0 }, +#define NPACKSW 9 + { "nopack", 0 }, +#define VERBSW 10 + { "verbose", 0 }, +#define NVERBSW 11 + { "noverbose", 0 }, +#define RECURSW 12 + { "recurse", 0 }, +#define NRECRSW 13 + { "norecurse", 0 }, +#define TOTALSW 14 + { "total", 0 }, +#define NTOTLSW 15 + { "nototal", 0 }, +#define LISTSW 16 + { "list", 0 }, +#define NLISTSW 17 + { "nolist", 0 }, +#define PRNTSW 18 + { "print", 0 }, +#define NPRNTSW 19 + { "noprint", -4 }, +#define PUSHSW 20 + { "push", 0 }, +#define POPSW 21 + { "pop", 0 }, +#define VERSIONSW 22 + { "version", 0 }, +#define HELPSW 23 + { "help", 4 }, + { NULL, 0 } +}; + +extern int errno; + +static int fshort = 0; /* output only folder names */ +static int fcreat = 0; /* should we ask to create new folders? */ +static int fpack = 0; /* are we packing the folder? */ +static int fverb = 0; /* print actions taken while packing folder */ +static int fheader = 0; /* should we output a header? */ +static int frecurse = 0; /* recurse through subfolders */ +static int ftotal = 0; /* should we output the totals? */ +static int all = 0; /* should we output all folders */ + +static int total_folders = 0; /* total number of folders */ + +static int start = 0; +static int foldp = 0; + +static char *nmhdir; +static char *stack = "Folder-Stack"; +static char folder[BUFSIZ]; + +#define NUMFOLDERS 100 + +/* + * This is how many folders we currently can hold in the array + * `folds'. We increase this amount by NUMFOLDERS at a time. + */ +static int maxfolders; +static char **folds; + +/* + * Structure to hold information about + * folders as we scan them. + */ +struct FolderInfo { + char *name; + int nummsg; + int curmsg; + int lowmsg; + int hghmsg; + int others; /* others == 1 if other files in folder */ + int error; /* error == 1 for unreadable folder */ +}; + +/* + * Dynamically allocated space to hold + * all the folder information. + */ +static struct FolderInfo *fi; +static int maxFolderInfo; + +/* + * static prototypes + */ +static void dodir (char *); +static int get_folder_info (char *, char *); +static void print_folders (void); +static int num_digits (int); +static int sfold (struct msgs *, char *); +static void addir (char *); +static void addfold (char *); +static int compare (char *, char *); +static void readonly_folders (void); + + +int +main (int argc, char **argv) +{ + int printsw = 0, listsw = 0; + int pushsw = 0, popsw = 0; + char *cp, *dp, *msg = NULL, *argfolder = NULL; + char **ap, **argp, buf[BUFSIZ], **arguments; + struct stat st; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + /* + * If program was invoked with name ending + * in `s', then add switch `-all'. + */ + if (argv[0][strlen (argv[0]) - 1] == 's') + all = 1; + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case ALLSW: + all = 1; + continue; + + case NALLSW: + all = 0; + continue; + + case CREATSW: + fcreat = 1; + continue; + case NCREATSW: + fcreat = -1; + continue; + + case FASTSW: + fshort++; + continue; + case NFASTSW: + fshort = 0; + continue; + + case HDRSW: + fheader = 1; + continue; + case NHDRSW: + fheader = -1; + continue; + + case PACKSW: + fpack++; + continue; + case NPACKSW: + fpack = 0; + continue; + + case VERBSW: + fverb++; + continue; + case NVERBSW: + fverb = 0; + continue; + + case RECURSW: + frecurse++; + continue; + case NRECRSW: + frecurse = 0; + continue; + + case TOTALSW: + ftotal = 1; + continue; + case NTOTLSW: + ftotal = -1; + continue; + + case PRNTSW: + printsw = 1; + continue; + case NPRNTSW: + printsw = 0; + continue; + + case LISTSW: + listsw = 1; + continue; + case NLISTSW: + listsw = 0; + continue; + + case PUSHSW: + pushsw = 1; + listsw = 1; + popsw = 0; + continue; + case POPSW: + popsw = 1; + listsw = 1; + pushsw = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (argfolder) + adios (NULL, "only one folder at a time!"); + else + argfolder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + if (msg) + adios (NULL, "only one (current) message at a time!"); + else + msg = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + nmhdir = concat (m_maildir (""), "/", NULL); + + /* + * If we aren't working with the folder stack + * (-push, -pop, -list) then the default is to print. + */ + if (pushsw == 0 && popsw == 0 && listsw == 0) + printsw++; + + /* Pushing a folder onto the folder stack */ + if (pushsw) { + if (!argfolder) { + /* If no folder is given, the current folder and */ + /* the top of the folder stack are swapped. */ + if ((cp = context_find (stack))) { + dp = getcpy (cp); + ap = brkstring (dp, " ", "\n"); + argfolder = getcpy(*ap++); + } else { + adios (NULL, "no other folder"); + } + for (cp = getcpy (getfolder (1)); *ap; ap++) + cp = add (*ap, add (" ", cp)); + free (dp); + context_replace (stack, cp); /* update folder stack */ + } else { + /* update folder stack */ + context_replace (stack, + (cp = context_find (stack)) + ? concat (getfolder (1), " ", cp, NULL) + : getcpy (getfolder (1))); + } + } + + /* Popping a folder off of the folder stack */ + if (popsw) { + if (argfolder) + adios (NULL, "sorry, no folders allowed with -pop"); + if ((cp = context_find (stack))) { + dp = getcpy (cp); + ap = brkstring (dp, " ", "\n"); + argfolder = getcpy(*ap++); + } else { + adios (NULL, "folder stack empty"); + } + if (*ap) { + /* if there's anything left in the stack */ + cp = getcpy (*ap++); + for (; *ap; ap++) + cp = add (*ap, add (" ", cp)); + context_replace (stack, cp); /* update folder stack */ + } else { + context_del (stack); /* delete folder stack entry from context */ + } + free (dp); + } + if (pushsw || popsw) { + cp = m_maildir(argfolder); + if (access (cp, F_OK) == NOTOK) + adios (cp, "unable to find folder"); + context_replace (pfolder, argfolder); /* update current folder */ + context_save (); /* save the context file */ + argfolder = NULL; + } + + /* Listing the folder stack */ + if (listsw) { + printf ("%s", argfolder ? argfolder : getfolder (1)); + if ((cp = context_find (stack))) { + dp = getcpy (cp); + for (ap = brkstring (dp, " ", "\n"); *ap; ap++) + printf (" %s", *ap); + free (dp); + } + printf ("\n"); + + if (!printsw) + done (0); + } + + /* Allocate initial space to record folder names */ + maxfolders = NUMFOLDERS; + if ((folds = malloc (maxfolders * sizeof(char *))) == NULL) + adios (NULL, "unable to allocate storage for folder names"); + + /* Allocate initial space to record folder information */ + maxFolderInfo = NUMFOLDERS; + if ((fi = malloc (maxFolderInfo * sizeof(*fi))) == NULL) + adios (NULL, "unable to allocate storage for folder info"); + + /* + * Scan the folders + */ + if (all || ftotal > 0) { + /* + * If no folder is given, do them all + */ + if (!argfolder) { + if (msg) + admonish (NULL, "no folder given for message %s", msg); + readonly_folders (); /* do any readonly folders */ + strncpy (folder, (cp = context_find (pfolder)) ? cp : "", sizeof(folder)); + dodir ("."); + } else { + strncpy (folder, argfolder, sizeof(folder)); + if (get_folder_info (argfolder, msg)) { + context_replace (pfolder, argfolder);/* update current folder */ + context_save (); /* save the context file */ + } + /* + * Since recurse wasn't done in get_folder_info(), + * we still need to list all level-1 sub-folders. + */ + if (!frecurse) + dodir (folder); + } + } else { + strncpy (folder, argfolder ? argfolder : getfolder (1), sizeof(folder)); + + /* + * Check if folder exists. If not, then see if + * we should create it, or just exit. + */ + if (stat (strncpy (buf, m_maildir (folder), sizeof(buf)), &st) == -1) { + if (errno != ENOENT) + adios (buf, "error on folder"); + if (fcreat == 0) { + /* ask before creating folder */ + cp = concat ("Create folder \"", buf, "\"? ", NULL); + if (!getanswer (cp)) + done (1); + free (cp); + } else if (fcreat == -1) { + /* do not create, so exit */ + done (1); + } + if (!makedir (buf)) + adios (NULL, "unable to create folder %s", buf); + } + + if (get_folder_info (folder, msg) && argfolder) { + /* update current folder */ + context_replace (pfolder, argfolder); + } + } + + /* + * Print out folder information + */ + print_folders(); + + context_save (); /* save the context file */ + done (0); +} + +/* + * Base routine for scanning a folder + */ + +static void +dodir (char *dir) +{ + int i; + int os = start; + int of = foldp; + char buffer[BUFSIZ]; + + start = foldp; + + /* change directory to base of nmh directory */ + if (chdir (nmhdir) == NOTOK) + adios (nmhdir, "unable to change directory to"); + + addir (strncpy (buffer, dir, sizeof(buffer))); + + for (i = start; i < foldp; i++) { + get_folder_info (folds[i], NULL); + fflush (stdout); + } + + start = os; + foldp = of; +} + +static int +get_folder_info (char *fold, char *msg) +{ + int i, retval = 1; + char *mailfile; + struct msgs *mp = NULL; + + i = total_folders++; + + /* + * if necessary, reallocate the space + * for folder information + */ + if (total_folders >= maxFolderInfo) { + maxFolderInfo += NUMFOLDERS; + if ((fi = realloc (fi, maxFolderInfo * sizeof(*fi))) == NULL) + adios (NULL, "unable to re-allocate storage for folder info"); + } + + fi[i].name = fold; + fi[i].nummsg = 0; + fi[i].curmsg = 0; + fi[i].lowmsg = 0; + fi[i].hghmsg = 0; + fi[i].others = 0; + fi[i].error = 0; + + mailfile = m_maildir (fold); + + if (!chdir (mailfile)) { + if ((ftotal > 0) || !fshort || msg || fpack) { + /* + * create message structure and get folder info + */ + if (!(mp = folder_read (fold))) { + admonish (NULL, "unable to read folder %s", fold); + return 0; + } + + /* set the current message */ + if (msg && !sfold (mp, msg)) + retval = 0; + + if (fpack) { + if (folder_pack (&mp, fverb) == -1) + done (1); + seq_save (mp); /* synchronize the sequences */ + context_save (); /* save the context file */ + } + + /* record info for this folder */ + if ((ftotal > 0) || !fshort) { + fi[i].nummsg = mp->nummsg; + fi[i].curmsg = mp->curmsg; + fi[i].lowmsg = mp->lowmsg; + fi[i].hghmsg = mp->hghmsg; + fi[i].others = other_files (mp); + } + + folder_free (mp); /* free folder/message structure */ + } + } else { + fi[i].error = 1; + } + + if (frecurse && (fshort || fi[i].others) && (fi[i].error == 0)) + dodir (fold); + return retval; +} + +/* + * Print folder information + */ + +static void +print_folders (void) +{ + int i, len, hasempty = 0, curprinted; + int maxlen = 0, maxnummsg = 0, maxlowmsg = 0; + int maxhghmsg = 0, maxcurmsg = 0, total_msgs = 0; + int nummsgdigits, lowmsgdigits; + int hghmsgdigits, curmsgdigits; + char tmpname[BUFSIZ]; + + /* + * compute a few values needed to for + * printing various fields + */ + for (i = 0; i < total_folders; i++) { + /* length of folder name */ + len = strlen (fi[i].name); + if (len > maxlen) + maxlen = len; + + /* If folder has error, skip the rest */ + if (fi[i].error) + continue; + + /* calculate total number of messages */ + total_msgs += fi[i].nummsg; + + /* maximum number of messages */ + if (fi[i].nummsg > maxnummsg) + maxnummsg = fi[i].nummsg; + + /* maximum low message */ + if (fi[i].lowmsg > maxlowmsg) + maxlowmsg = fi[i].lowmsg; + + /* maximum high message */ + if (fi[i].hghmsg > maxhghmsg) + maxhghmsg = fi[i].hghmsg; + + /* maximum current message */ + if (fi[i].curmsg >= fi[i].lowmsg && + fi[i].curmsg <= fi[i].hghmsg && + fi[i].curmsg > maxcurmsg) + maxcurmsg = fi[i].curmsg; + + /* check for empty folders */ + if (fi[i].nummsg == 0) + hasempty = 1; + } + nummsgdigits = num_digits (maxnummsg); + lowmsgdigits = num_digits (maxlowmsg); + hghmsgdigits = num_digits (maxhghmsg); + curmsgdigits = num_digits (maxcurmsg); + + if (hasempty && nummsgdigits < 2) + nummsgdigits = 2; + + /* + * Print the header + */ + if (fheader > 0 || (all && !fshort && fheader >= 0)) + printf ("%-*s %*s %-*s; %-*s %*s\n", + maxlen+1, "FOLDER", + nummsgdigits + 13, "# MESSAGES", + lowmsgdigits + hghmsgdigits + 4, " RANGE", + curmsgdigits + 4, "CUR", + 9, "(OTHERS)"); + + /* + * Print folder information + */ + if (all || fshort || ftotal < 1) { + for (i = 0; i < total_folders; i++) { + if (fshort) { + printf ("%s\n", fi[i].name); + continue; + } + + /* Add `+' to end of name, if folder is current */ + if (strcmp (folder, fi[i].name)) + snprintf (tmpname, sizeof(tmpname), "%s", fi[i].name); + else + snprintf (tmpname, sizeof(tmpname), "%s+", fi[i].name); + + if (fi[i].error) { + printf ("%-*s is unreadable\n", maxlen+1, tmpname); + continue; + } + + printf ("%-*s ", maxlen+1, tmpname); + + curprinted = 0; /* remember if we print cur */ + if (fi[i].nummsg == 0) { + printf ("has %*s messages%*s", + nummsgdigits, "no", + fi[i].others ? lowmsgdigits + hghmsgdigits + 5 : 0, ""); + } else { + printf ("has %*d message%s (%*d-%*d)", + nummsgdigits, fi[i].nummsg, + (fi[i].nummsg == 1) ? " " : "s", + lowmsgdigits, fi[i].lowmsg, + hghmsgdigits, fi[i].hghmsg); + if (fi[i].curmsg >= fi[i].lowmsg && fi[i].curmsg <= fi[i].hghmsg) { + curprinted = 1; + printf ("; cur=%*d", curmsgdigits, fi[i].curmsg); + } + } + + if (fi[i].others) + printf (";%*s (others)", curprinted ? 0 : curmsgdigits + 6, ""); + printf (".\n"); + } + } + + /* + * Print folder/message totals + */ + if (ftotal > 0 || (all && !fshort && ftotal >= 0)) { + if (all) + printf ("\n"); + printf ("TOTAL = %d message%c in %d folder%s.\n", + total_msgs, total_msgs != 1 ? 's' : ' ', + total_folders, total_folders != 1 ? "s" : ""); + } + + fflush (stdout); +} + +/* + * Calculate the number of digits in a nonnegative integer + */ +int +num_digits (int n) +{ + int ndigits = 0; + + /* Sanity check */ + if (n < 0) + adios (NULL, "oops, num_digits called with negative value"); + + if (n == 0) + return 1; + + while (n) { + n /= 10; + ndigits++; + } + + return ndigits; +} + +/* + * Set the current message and sychronize sequences + */ + +static int +sfold (struct msgs *mp, char *msg) +{ + /* parse the message range/sequence/name and set SELECTED */ + if (!m_convert (mp, msg)) + return 0; + + if (mp->numsel > 1) { + admonish (NULL, "only one message at a time!"); + return 0; + } + seq_setprev (mp); /* set the previous-sequence */ + seq_setcur (mp, mp->lowsel);/* set current message */ + seq_save (mp); /* synchronize message sequences */ + context_save (); /* save the context file */ + + return 1; +} + + +static void +addir (char *name) +{ + int nlink; + char *base, *cp; + struct stat st; + struct dirent *dp; + DIR * dd; + + cp = name + strlen (name); + *cp++ = '/'; + *cp = '\0'; + + /* + * A hack to skip over a leading + * "./" in folder names. + */ + base = strcmp (name, "./") ? name : name + 2; + + /* short-cut to see if directory has any sub-directories */ + if (stat (name, &st) != -1 && st.st_nlink == 2) + return; + + if (!(dd = opendir (name))) { + admonish (name, "unable to read directory "); + return; + } + + /* + * Keep track of the number of directories we've seen + * so we can quit stat'ing early, if we've seen them all. + */ + nlink = st.st_nlink; + + while (nlink && (dp = readdir (dd))) { + if (!strcmp (dp->d_name, ".") || !strcmp (dp->d_name, "..")) { + nlink--; + continue; + } + if (cp + NLENGTH(dp) + 2 >= name + BUFSIZ) + continue; + strcpy (cp, dp->d_name); + if (stat (name, &st) != -1 && S_ISDIR(st.st_mode)) { + /* + * Check if this was really a symbolic link pointing at + * a directory. If not, then decrement link count. + */ + if (lstat (name, &st) == -1) + nlink--; + addfold (base); + } + } + + closedir (dd); + *--cp = '\0'; +} + +/* + * Add the folder name into the + * list in a sorted fashion. + */ + +static void +addfold (char *fold) +{ + register int i, j; + register char *cp; + + /* if necessary, reallocate the space for folder names */ + if (foldp >= maxfolders) { + maxfolders += NUMFOLDERS; + if ((folds = realloc (folds, maxfolders * sizeof(char *))) == NULL) + adios (NULL, "unable to re-allocate storage for folder names"); + } + + cp = getcpy (fold); + for (i = start; i < foldp; i++) + if (compare (cp, folds[i]) < 0) { + for (j = foldp - 1; j >= i; j--) + folds[j + 1] = folds[j]; + foldp++; + folds[i] = cp; + return; + } + + folds[foldp++] = cp; +} + + +static int +compare (char *s1, char *s2) +{ + register int i; + + while (*s1 || *s2) + if ((i = *s1++ - *s2++)) + return i; + + return 0; +} + +/* + * Do the read only folders + */ + +static void +readonly_folders (void) +{ + int atrlen; + char atrcur[BUFSIZ]; + register struct node *np; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + snprintf (atrcur, sizeof(atrcur), "atr-%s-", current); + atrlen = strlen (atrcur); + + for (np = m_defs; np; np = np->n_next) + if (ssequal (atrcur, np->n_name) + && !ssequal (nmhdir, np->n_name + atrlen)) + get_folder_info (np->n_name + atrlen, NULL); +} diff --git a/uip/forw.c b/uip/forw.c new file mode 100644 index 0000000..ade775e --- /dev/null +++ b/uip/forw.c @@ -0,0 +1,697 @@ + +/* + * forw.c -- forward a message, or group of messages. + * + * $Id$ + */ + +#include +#include +#include +#include + + +#define IFORMAT "digest-issue-%s" +#define VFORMAT "digest-volume-%s" + +static struct swit switches[] = { +#define ANNOSW 0 + { "annotate", 0 }, +#define NANNOSW 1 + { "noannotate", 0 }, +#define DFOLDSW 2 + { "draftfolder +folder", 0 }, +#define DMSGSW 3 + { "draftmessage msg", 0 }, +#define NDFLDSW 4 + { "nodraftfolder", 0 }, +#define EDITRSW 5 + { "editor editor", 0 }, +#define NEDITSW 6 + { "noedit", 0 }, +#define FILTSW 7 + { "filter filterfile", 0 }, +#define FORMSW 8 + { "form formfile", 0 }, +#define FRMTSW 9 + { "format", 5 }, +#define NFRMTSW 10 + { "noformat", 7 }, +#define INPLSW 11 + { "inplace", 0 }, +#define NINPLSW 12 + { "noinplace", 0 }, +#define MIMESW 13 + { "mime", 0 }, +#define NMIMESW 14 + { "nomime", 0 }, +#define DGSTSW 15 + { "digest list", 0 }, +#define ISSUESW 16 + { "issue number", 0 }, +#define VOLUMSW 17 + { "volume number", 0 }, +#define WHATSW 18 + { "whatnowproc program", 0 }, +#define NWHATSW 19 + { "nowhatnowproc", 0 }, +#define BITSTUFFSW 20 + { "dashstuffing", 0 }, /* interface to mhl */ +#define NBITSTUFFSW 21 + { "nodashstuffing", 0 }, +#define VERSIONSW 22 + { "version", 0 }, +#define HELPSW 23 + { "help", 4 }, +#define FILESW 24 + { "file file", -4 }, /* interface from msh */ + +#ifdef MHE +#define BILDSW 25 + { "build", -5 }, /* interface from mhe */ +#endif /* MHE */ + + { NULL, 0 } +}; + +static struct swit aqrnl[] = { +#define NOSW 0 + { "quit", 0 }, +#define YESW 1 + { "replace", 0 }, +#define LISTDSW 2 + { "list", 0 }, +#define REFILSW 3 + { "refile +folder", 0 }, +#define NEWSW 4 + { "new", 0 }, + { NULL, 0 } +}; + +static struct swit aqrl[] = { + { "quit", 0 }, + { "replace", 0 }, + { "list", 0 }, + { "refile +folder", 0 }, + { NULL, 0 } +}; + +static char drft[BUFSIZ]; + +static char delim3[] = + "\n------------------------------------------------------------\n\n"; +static char delim4[] = "\n------------------------------\n\n"; + + +static struct msgs *mp = NULL; /* used a lot */ + + +/* + * static prototypes + */ +static void mhl_draft (int, char *, int, int, char *, char *, int); +static void copy_draft (int, char *, char *, int, int, int); +static void copy_mime_draft (int); +static int build_form (char *, char *, int, int); + + +int +main (int argc, char **argv) +{ + int msgp = 0, anot = 0, inplace = 1, mime = 0; + int issue = 0, volume = 0, dashstuff = 0; + int nedit = 0, nwhat = 0, i, in; + int out, isdf = 0, msgnum; + char *cp, *cwd, *maildir, *dfolder = NULL; + char *dmsg = NULL, *digest = NULL, *ed = NULL; + char *file = NULL, *filter = NULL, *folder = NULL; + char *form = NULL, buf[BUFSIZ], value[10]; + char **argp, **arguments, *msgs[MAXARGS]; + struct stat st; + +#ifdef MHE + int buildsw = 0; +#endif /* MHE */ + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case ANNOSW: + anot++; + continue; + case NANNOSW: + anot = 0; + continue; + + case EDITRSW: + if (!(ed = *argp++) || *ed == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nedit = 0; + continue; + case NEDITSW: + nedit++; + continue; + + case WHATSW: + if (!(whatnowproc = *argp++) || *whatnowproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nwhat = 0; + continue; +#ifdef MHE + case BILDSW: + buildsw++; /* fall... */ +#endif /* MHE */ + case NWHATSW: + nwhat++; + continue; + + case FILESW: + if (file) + adios (NULL, "only one file at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + file = path (cp, TFILE); + continue; + case FILTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + filter = getcpy (etcpath (cp)); + mime = 0; + continue; + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case FRMTSW: + filter = getcpy (etcpath (mhlforward)); + continue; + case NFRMTSW: + filter = NULL; + continue; + + case INPLSW: + inplace++; + continue; + case NINPLSW: + inplace = 0; + continue; + + case MIMESW: + mime++; + filter = NULL; + continue; + case NMIMESW: + mime = 0; + continue; + + case DGSTSW: + if (!(digest = *argp++) || *digest == '-') + adios (NULL, "missing argument to %s", argp[-2]); + mime = 0; + continue; + case ISSUESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((issue = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case VOLUMSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((volume = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (dmsg) + adios (NULL, "only one draft message at a time!"); + if (!(dmsg = *argp++) || *dmsg == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + + case BITSTUFFSW: + dashstuff = 1; /* trinary logic */ + continue; + case NBITSTUFFSW: + dashstuff = -1; /* trinary logic */ + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + msgs[msgp++] = cp; + } + } + + cwd = getcpy (pwd ()); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (file && (msgp || folder)) + adios (NULL, "can't mix files and folders/msgs"); + +try_it_again: + +#ifdef MHE + strncpy (drft, buildsw ? m_maildir ("draft") + : m_draft (dfolder, NULL, NOUSE, &isdf), sizeof(drft)); + + /* Check if a draft already exists */ + if (!buildsw && stat (drft, &st) != NOTOK) { +#else + strncpy (drft, m_draft (dfolder, dmsg, NOUSE, &isdf), sizeof(drft)); + + /* Check if a draft already exists */ + if (stat (drft, &st) != NOTOK) { +#endif /* MHE */ + printf ("Draft \"%s\" exists (%ld bytes).", drft, (long) st.st_size); + for (i = LISTDSW; i != YESW;) { + if (!(argp = getans ("\nDisposition? ", isdf ? aqrnl : aqrl))) + done (1); + switch (i = smatch (*argp, isdf ? aqrnl : aqrl)) { + case NOSW: + done (0); + case NEWSW: + dmsg = NULL; + goto try_it_again; + case YESW: + break; + case LISTDSW: + showfile (++argp, drft); + break; + case REFILSW: + if (refile (++argp, drft) == 0) + i = YESW; + break; + default: + advise (NULL, "say what?"); + break; + } + } + } + + if (file) { + /* + * Forwarding a file. + */ + anot = 0; /* don't want to annotate a file */ + } else { + /* + * Forwarding a message. + */ + if (!msgp) + msgs[msgp++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous sequence */ + } + + if (filter && access (filter, R_OK) == NOTOK) + adios (filter, "unable to read"); + + /* + * Open form (component) file. + */ + if (digest) { + if (issue == 0) { + snprintf (buf, sizeof(buf), IFORMAT, digest); + if (volume == 0 + && (cp = context_find (buf)) + && ((issue = atoi (cp)) < 0)) + issue = 0; + issue++; + } + if (volume == 0) + snprintf (buf, sizeof(buf), VFORMAT, digest); + if ((cp = context_find (buf)) == NULL || (volume = atoi (cp)) <= 0) + volume = 1; + if (!form) + form = digestcomps; + in = build_form (form, digest, volume, issue); + } else { + if (form) { + if ((in = open (etcpath (form), O_RDONLY)) == NOTOK) + adios (form, "unable to open form file"); + } else { + if ((in = open (etcpath (forwcomps), O_RDONLY)) == NOTOK) + adios (forwcomps, "unable to open default components file"); + form = forwcomps; + } + } + + if ((out = creat (drft, m_gmprot ())) == NOTOK) + adios (drft, "unable to create"); + + /* + * copy the components into the draft + */ + cpydata (in, out, form, drft); + close (in); + + if (file) { + /* just copy the file into the draft */ + if ((in = open (file, O_RDONLY)) == NOTOK) + adios (file, "unable to open"); + cpydata (in, out, file, drft); + close (in); + close (out); + } else { + /* + * If filter file is defined, then format the + * messages into the draft using mhlproc. + */ + if (filter) + mhl_draft (out, digest, volume, issue, drft, filter, dashstuff); + else if (mime) + copy_mime_draft (out); + else + copy_draft (out, digest, drft, volume, issue, dashstuff); + close (out); + + if (digest) { + snprintf (buf, sizeof(buf), IFORMAT, digest); + snprintf (value, sizeof(value), "%d", issue); + context_replace (buf, getcpy (value)); + snprintf (buf, sizeof(buf), VFORMAT, digest); + snprintf (value, sizeof(value), "%d", volume); + context_replace (buf, getcpy (value)); + } + + context_replace (pfolder, folder); /* update current folder */ + seq_setcur (mp, mp->lowsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + if (nwhat) + done (0); + what_now (ed, nedit, NOUSE, drft, NULL, 0, mp, + anot ? "Forwarded" : NULL, inplace, cwd); + done (1); +} + + +/* + * Filter the messages you are forwarding, into the + * draft calling the mhlproc, and reading its output + * from a pipe. + */ + +static void +mhl_draft (int out, char *digest, int volume, int issue, + char *file, char *filter, int dashstuff) +{ + pid_t child_id; + int i, msgnum, pd[2]; + char *vec[MAXARGS]; + char buf1[BUFSIZ]; + char buf2[BUFSIZ]; + + if (pipe (pd) == NOTOK) + adios ("pipe", "unable to create"); + + vec[0] = r1bindex (mhlproc, '/'); + + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + adios ("fork", "unable to"); + + case OK: + close (pd[0]); + dup2 (pd[1], 1); + close (pd[1]); + + i = 1; + vec[i++] = "-forwall"; + vec[i++] = "-form"; + vec[i++] = filter; + + if (digest) { + vec[i++] = "-digest"; + vec[i++] = digest; + vec[i++] = "-issue"; + snprintf (buf1, sizeof(buf1), "%d", issue); + vec[i++] = buf1; + vec[i++] = "-volume"; + snprintf (buf2, sizeof(buf2), "%d", volume); + vec[i++] = buf2; + } + + /* + * Are we dashstuffing (quoting) the lines that begin + * with `-'. We use the mhl default (don't add any flag) + * unless the user has specified a specific flag. + */ + if (dashstuff > 0) + vec[i++] = "-dashstuffing"; + else if (dashstuff < 0) + vec[i++] = "-nodashstuffing"; + + if (mp->numsel >= MAXARGS - i) + adios (NULL, "more than %d messages for %s exec", + vec[0], MAXARGS - i); + + /* + * Now add the message names to filter. We can only + * handle about 995 messages (because vec is fixed size), + * but that should be plenty. + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel && i < sizeof(vec) - 1; + msgnum++) + if (is_selected (mp, msgnum)) + vec[i++] = getcpy (m_name (msgnum)); + vec[i] = NULL; + + execvp (mhlproc, vec); + fprintf (stderr, "unable to exec "); + perror (mhlproc); + _exit (-1); + + default: + close (pd[1]); + cpydata (pd[0], out, vec[0], file); + close (pd[0]); + pidXwait(child_id, mhlproc); + break; + } +} + + +/* + * Copy the messages into the draft. The messages are + * not filtered through the mhlproc. Do dashstuffing if + * necessary. + */ + +static void +copy_draft (int out, char *digest, char *file, int volume, int issue, int dashstuff) +{ + int fd,i, msgcnt, msgnum; + int len, buflen; + register char *bp, *msgnam; + char buffer[BUFSIZ]; + + msgcnt = 1; + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) { + if (digest) { + strncpy (buffer, msgnum == mp->lowsel ? delim3 : delim4, + sizeof(buffer)); + } else { + /* Get buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + strncpy (bp, "\n-------", buflen); + len = strlen (bp); + bp += len; + buflen -= len; + + if (msgnum == mp->lowsel) { + snprintf (bp, buflen, " Forwarded Message%s", + mp->numsel > 1 ? "s" : ""); + } else { + snprintf (bp, buflen, " Message %d", msgcnt); + } + len = strlen (bp); + bp += len; + buflen -= len; + + strncpy (bp, "\n\n", buflen); + } + write (out, buffer, strlen (buffer)); + + if ((fd = open (msgnam = m_name (msgnum), O_RDONLY)) == NOTOK) { + admonish (msgnam, "unable to read message"); + continue; + } + + /* + * Copy the message. Add RFC934 quoting (dashstuffing) + * unless given the -nodashstuffing flag. + */ + if (dashstuff >= 0) + cpydgst (fd, out, msgnam, file); + else + cpydata (fd, out, msgnam, file); + + close (fd); + msgcnt++; + } + } + + if (digest) { + strncpy (buffer, delim4, sizeof(buffer)); + } else { + snprintf (buffer, sizeof(buffer), "\n------- End of Forwarded Message%s\n\n", + mp->numsel > 1 ? "s" : ""); + } + write (out, buffer, strlen (buffer)); + + if (digest) { + snprintf (buffer, sizeof(buffer), "End of %s Digest [Volume %d Issue %d]\n", + digest, volume, issue); + i = strlen (buffer); + for (bp = buffer + i; i > 1; i--) + *bp++ = '*'; + *bp++ = '\n'; + *bp = 0; + write (out, buffer, strlen (buffer)); + } +} + + +/* + * Create a mhn composition file for forwarding message. + */ + +static void +copy_mime_draft (int out) +{ + int msgnum; + char buffer[BUFSIZ]; + + snprintf (buffer, sizeof(buffer), "#forw [forwarded message%s] +%s", + mp->numsel == 1 ? "" : "s", mp->foldpath); + write (out, buffer, strlen (buffer)); + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) { + snprintf (buffer, sizeof(buffer), " %s", m_name (msgnum)); + write (out, buffer, strlen (buffer)); + } + write (out, "\n", 1); +} + + +static int +build_form (char *form, char *digest, int volume, int issue) +{ + int in; + int fmtsize; + register char *nfs; + char *line, tmpfil[BUFSIZ]; + register FILE *tmp; + register struct comp *cptr; + struct format *fmt; + int dat[5]; + + /* Get new format string */ + nfs = new_fs (form, NULL, NULL); + fmtsize = strlen (nfs) + 256; + + /* Compile format string */ + fmt_compile (nfs, &fmt); + + FINDCOMP (cptr, "digest"); + if (cptr) + cptr->c_text = digest; + FINDCOMP (cptr, "date"); + if (cptr) + cptr->c_text = getcpy(dtimenow (0)); + + dat[0] = issue; + dat[1] = volume; + dat[2] = 0; + dat[3] = fmtsize; + dat[4] = 0; + + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((tmp = fopen (tmpfil, "w+")) == NULL) + adios (tmpfil, "unable to create"); + unlink (tmpfil); + if ((in = dup (fileno (tmp))) == NOTOK) + adios ("dup", "unable to"); + + if ((line = malloc ((unsigned) fmtsize)) == NULL) + adios (NULL, "unable to allocate format line storage"); + fmt_scan (fmt, line, fmtsize, dat); + fputs (line, tmp); + free (line); + if (fclose (tmp)) + adios (tmpfil, "error writing"); + + lseek (in, (off_t) 0, SEEK_SET); + return in; +} diff --git a/uip/ftpsbr.c b/uip/ftpsbr.c new file mode 100644 index 0000000..d36e0e5 --- /dev/null +++ b/uip/ftpsbr.c @@ -0,0 +1,523 @@ + +/* + * ftpsbr.c -- simple FTP client library + * + * $Id$ + */ + +#include +#include + +#ifdef HAVE_ARPA_FTP_H +# include +#endif + +#define v_debug debugsw +#define v_verbose verbosw + +static int ftp_fd = NOTOK; +static int data_fd = NOTOK; + +static int v_noise; + +extern int v_debug; +extern int v_verbose; + +#include +#include +#include +#include + +#if defined(BIND) && !defined(h_addr) +# define h_addr h_addr_list[0] +#endif + +#define inaddr_copy(hp,sin) \ + memcpy((char *) &((sin)->sin_addr), (hp)->h_addr, (hp)->h_length) + +extern int errno; + +#define start_tcp_client(sock,priv) \ + socket (AF_INET, SOCK_STREAM, 0) + +#define join_tcp_server(fd, sock) \ + connect ((fd), (struct sockaddr *) (sock), sizeof *(sock)) + +/* + * prototypes + */ +struct hostent *gethostbystring (); +int ftp_get (char *, char *, char *, char *, char *, char *, int, int); +int ftp_trans (char *, char *, char *, char *, char *, char *, char *, int, int); + +/* + * static prototypes + */ +static int start_tcp_server (struct sockaddr_in *, int, int, int); +static void _asnprintf (char *, int, char *, va_list); +static int ftp_quit (void); +static int ftp_read (char *, char *, char *, int); +static int initconn (void); +static int dataconn (void); +static int command (int arg1, ...); +static int vcommand (int, va_list); +static int getreply (int, int); + + +static int +start_tcp_server (struct sockaddr_in *sock, int backlog, int opt1, int opt2) +{ + int eindex, sd; + + if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == NOTOK) + return NOTOK; + + if (bind (sd, (struct sockaddr *) sock, sizeof *sock) == NOTOK) { + eindex = errno; + close (sd); + errno = eindex; + } else { + listen (sd, backlog); + } + + return sd; +} + + +static int __len__; + +#define join_tcp_client(fd,sock) \ + accept ((fd), (struct sockaddr *) (sock), \ + (__len__ = sizeof *(sock), &__len__)) + +#define read_tcp_socket read +#define write_tcp_socket write +#define close_tcp_socket close + +static void +_asnprintf (char *bp, int len_bp, char *what, va_list ap) +{ + int eindex, len; + char *fmt; + + eindex = errno; + + *bp = '\0'; + fmt = va_arg (ap, char *); + + if (fmt) { + vsnprintf(bp, len_bp, fmt, ap); + len = strlen(bp); + bp += len; + len_bp -= len; + } + + if (what) { + char *s; + + if (*what) { + snprintf (bp, len_bp, " %s: ", what); + len = strlen (bp); + bp += len; + len_bp -= len; + } + if ((s = strerror(eindex))) + strncpy (bp, s, len_bp); + else + snprintf (bp, len_bp, "Error %d", eindex); + bp += strlen (bp); + } + + errno = eindex; +} + + +int +ftp_get (char *host, char *user, char *password, char *cwd, + char *remote, char *local, int ascii, int stayopen) +{ + return ftp_trans (host, user, password, cwd, remote, local, + "RETR", ascii, stayopen); +} + + +int +ftp_trans (char *host, char *user, char *password, char *cwd, char *remote, + char *local, char *cmd, int ascii, int stayopen) +{ + int result; + + if (stayopen <= 0) { + result = ftp_quit (); + if (host == NULL) + return result; + } + + if (ftp_fd == NOTOK) { + struct sockaddr_in in_socket; + register struct hostent *hp; + register struct servent *sp; + + if ((sp = getservbyname ("ftp", "tcp")) == NULL) { + fprintf (stderr, "tcp/ftp: unknown service"); + return NOTOK; + } + if ((hp = gethostbystring (host)) == NULL) { + fprintf (stderr, "%s: unknown host\n", host); + return NOTOK; + } + in_socket.sin_family = hp->h_addrtype; + inaddr_copy (hp, &in_socket); + in_socket.sin_port = sp->s_port; + + if ((ftp_fd = start_tcp_client ((struct sockaddr_in *) NULL, 0)) + == NOTOK) { + perror (host); + return NOTOK; + } + if (join_tcp_server (ftp_fd, &in_socket) == NOTOK) { + perror (host); + close_tcp_socket (ftp_fd), ftp_fd = NOTOK; + return NOTOK; + } + getreply (1, 0); + + if (v_verbose) { + fprintf (stdout, "Connected to %s\n", host); + fflush (stdout); + } + + if (user) { + if ((result = command (0, "USER %s", user)) == CONTINUE) + result = command (1, "PASS %s", password); + if (result != COMPLETE) { + result = NOTOK; + goto out; + } + } + + if (remote == NULL) + return OK; + } + + if (cwd && ((result = command (0, "CWD %s", cwd)) != COMPLETE + && result != CONTINUE)) { + result = NOTOK; + goto out; + } + + if (command (1, ascii ? "TYPE A" : "TYPE I") != COMPLETE) { + result = NOTOK; + goto out; + } + + result = ftp_read (remote, local, cmd, ascii); + +out: ; + if (result != OK || !stayopen) + ftp_quit (); + + return result; +} + + +static int +ftp_quit (void) +{ + int n; + + if (ftp_fd == NOTOK) + return OK; + + n = command (1, "QUIT"); + close_tcp_socket (ftp_fd), ftp_fd = NOTOK; + return (n == 0 || n == COMPLETE ? OK : NOTOK); +} + +static int +ftp_read (char *remote, char *local, char *cmd, int ascii) +{ + int istdio = 0, istore; + register int cc; + int expectingreply = 0; + char buffer[BUFSIZ]; + FILE *fp = NULL; + + if (initconn () == NOTOK) + goto bad; + + v_noise = v_verbose; + if (command (-1, *remote ? "%s %s" : "%s", cmd, remote) != PRELIM) + goto bad; + + expectingreply++; + if (dataconn () == NOTOK) { +bad: ; + if (fp && !istdio) + fclose (fp); + if (data_fd != NOTOK) + close_tcp_socket (data_fd), data_fd = NOTOK; + if (expectingreply) + getreply (-2, 0); + + return NOTOK; + } + + istore = !strcmp (cmd, "STOR"); + + if ((istdio = !strcmp (local, "-"))) + fp = istore ? stdin : stdout; + else + if ((fp = fopen (local, istore ? "r" : "w")) == NULL) { + perror (local); + goto bad; + } + + if (istore) { + if (ascii) { + int c; + FILE *out; + + if (!(out = fdopen (data_fd, "w"))) { + perror ("fdopen"); + goto bad; + } + + while ((c = getc (fp)) != EOF) { + if (c == '\n') + putc ('\r', out); + if (putc (c, out) == EOF) { + perror ("putc"); + fclose (out); + data_fd = NOTOK; + goto bad; + } + } + + fclose (out); + data_fd = NOTOK; + } + else { + while ((cc = fread (buffer, sizeof *buffer, sizeof buffer, fp)) > 0) + if (write_tcp_socket (data_fd, buffer, cc) != cc) { + perror ("write_tcp_socket"); + goto bad; + } + + close_tcp_socket (data_fd), data_fd = NOTOK; + } + } + else { + if (ascii) { + int c; + FILE *in; + + if (!(in = fdopen (data_fd, "r"))) { + perror ("fdopen"); + goto bad; + } + + while ((c = getc (in)) != EOF) { + if (c == '\r') + switch (c = getc (in)) { + case EOF: + case '\0': + c = '\r'; + break; + + case '\n': + break; + + default: + putc ('\r', fp); + break; + } + + if (putc (c, fp) == EOF) { + perror ("putc"); + fclose (in); + data_fd = NOTOK; + goto bad; + } + } + + fclose (in); + data_fd = NOTOK; + } + else { + while ((cc = read_tcp_socket (data_fd, buffer, sizeof buffer)) > 0) + if (fwrite (buffer, sizeof *buffer, cc, fp) == 0) { + perror ("fwrite"); + goto bad; + } + if (cc < 0) { + perror ("read_tcp_socket"); + goto bad; + } + + close_tcp_socket (data_fd), data_fd = NOTOK; + } + } + + if (!istdio) + fclose (fp); + + v_noise = v_verbose; + return (getreply (1, 0) == COMPLETE ? OK : NOTOK); +} + + +#define UC(b) (((int) b) & 0xff) + +static int +initconn (void) +{ + int len; + register char *a, *p; + struct sockaddr_in in_socket; + + if (getsockname (ftp_fd, (struct sockaddr *) &in_socket, + (len = sizeof(in_socket), &len)) == NOTOK) { + perror ("getsockname"); + return NOTOK; + } + in_socket.sin_port = 0; + if ((data_fd = start_tcp_server (&in_socket, 1, 0, 0)) == NOTOK) { + perror ("start_tcp_server"); + return NOTOK; + } + + if (getsockname (data_fd, (struct sockaddr *) &in_socket, + (len = sizeof in_socket, &len)) == NOTOK) { + perror ("getsockname"); + return NOTOK; + } + + a = (char *) &in_socket.sin_addr; + p = (char *) &in_socket.sin_port; + + if (command (1, "PORT %d,%d,%d,%d,%d,%d", + UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(p[0]), UC(p[1])) == COMPLETE) + return OK; + + return NOTOK; +} + +static int +dataconn (void) +{ + int fd; + struct sockaddr_in in_socket; + + if ((fd = join_tcp_client (data_fd, &in_socket)) == NOTOK) { + perror ("join_tcp_client"); + return NOTOK; + } + close_tcp_socket (data_fd); + data_fd = fd; + + return OK; +} + + +static int +command (int arg1, ...) +{ + int val; + va_list ap; + + va_start (ap, arg1); + val = vcommand (arg1, ap); + va_end (ap); + + return val; +} + +static int +vcommand (int complete, va_list ap) +{ + int len; + char buffer[BUFSIZ]; + + if (ftp_fd == NOTOK) + return NOTOK; + + _asnprintf (buffer, sizeof(buffer), NULL, ap); + if (v_debug) + fprintf (stderr, "<--- %s\n", buffer); + + strcat (buffer, "\r\n"); + len = strlen (buffer); + + if (write_tcp_socket (ftp_fd, buffer, len) != len) { + perror ("write_tcp_socket"); + return NOTOK; + } + + return (getreply (complete, !strcmp (buffer, "QUIT"))); +} + + +static int +getreply (int complete, int expecteof) +{ + for (;;) { + register int code, dig, n; + int continuation; + register char *bp; + char buffer[BUFSIZ]; + + code = dig = n = continuation = 0; + bp = buffer; + + for (;;) { + char c; + + if (read_tcp_socket (ftp_fd, &c, 1) < 1) { + if (expecteof) + return OK; + + perror ("read_tcp_socket"); + return DONE; + } + if (c == '\n') + break; + *bp++ = c != '\r' ? c : '\0'; + + dig++; + if (dig < 4) { + if (isdigit(c)) + code = code * 10 + (c - '0'); + else /* XXX: naughty FTP... */ + if (isspace (c)) + continuation++; + } + else + if (dig == 4 && c == '-') + continuation++; + if (n == 0) + n = c; + } + + if (v_debug) + fprintf (stderr, "---> %s\n", buffer); + if (continuation) + continue; + + n -= '0'; + + if (v_noise) { + fprintf (stdout, "%s\n", buffer); + fflush (stdout); + v_noise = 0; + } + else + if ((complete == -1 && n != PRELIM) + || (complete == 0 && n != CONTINUE && n != COMPLETE) + || (complete == 1 && n != COMPLETE)) + fprintf (stderr, "%s\n", buffer); + + return n; + } +} diff --git a/uip/inc.c b/uip/inc.c new file mode 100644 index 0000000..9752b5a --- /dev/null +++ b/uip/inc.c @@ -0,0 +1,939 @@ + +/* + * inc.c -- incorporate messages from a maildrop into a folder + * + * $Id$ + */ + +#ifdef MAILGROUP +/* Revised: Sat Apr 14 17:08:17 PDT 1990 (marvit@hplabs) + * Added hpux hacks to set and reset gid to be "mail" as needed. The reset + * is necessary so inc'ed mail is the group of the inc'er, rather than + * "mail". We setgid to egid only when [un]locking the mail file. This + * is also a major security precaution which will not be explained here. + * + * Fri Feb 7 16:04:57 PST 1992 John Romine + * NB: I'm not 100% sure that this setgid stuff is secure even now. + */ +#endif + +#include +#include + +#ifdef POP +# include +# include +#endif + +#ifdef HESIOD +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifndef POP +# define POPminc(a) (a) +#else +# define POPminc(a) 0 +#endif + +#ifndef RPOP +# define RPOPminc(a) (a) +#else +# define RPOPminc(a) 0 +#endif + +#ifndef APOP +# define APOPminc(a) (a) +#else +# define APOPminc(a) 0 +#endif + +static struct swit switches[] = { +#define AUDSW 0 + { "audit audit-file", 0 }, +#define NAUDSW 1 + { "noaudit", 0 }, +#define CHGSW 2 + { "changecur", 0 }, +#define NCHGSW 3 + { "nochangecur", 0 }, +#define FILESW 4 + { "file name", 0 }, +#define FORMSW 5 + { "form formatfile", 0 }, +#define FMTSW 6 + { "format string", 5 }, +#define HOSTSW 7 + { "host hostname", POPminc (-4) }, +#define USERSW 8 + { "user username", POPminc (-4) }, +#define PACKSW 9 + { "pack file", POPminc (-4) }, +#define NPACKSW 10 + { "nopack", POPminc (-6) }, +#define APOPSW 11 + { "apop", APOPminc (-4) }, +#define NAPOPSW 12 + { "noapop", APOPminc (-6) }, +#define RPOPSW 13 + { "rpop", RPOPminc (-4) }, +#define NRPOPSW 14 + { "norpop", RPOPminc (-6) }, +#define SILSW 15 + { "silent", 0 }, +#define NSILSW 16 + { "nosilent", 0 }, +#define TRNCSW 17 + { "truncate", 0 }, +#define NTRNCSW 18 + { "notruncate", 0 }, +#define WIDTHSW 19 + { "width columns", 0 }, +#define VERSIONSW 20 + { "version", 0 }, +#define HELPSW 21 + { "help", 4 }, +#define SNOOPSW 22 + { "snoop", -5 }, + { NULL, 0 } +}; + +extern int errno; + +/* + * flags for the mail source + */ +#define INC_FILE 0 +#define INC_POP 1 + +static int inc_type; +static int snoop = 0; + +#ifdef POP +extern char response[]; + +static char *packfile = NULL; +static int size; +static long pos; +static long start; +static long stop; + +static int mbx_style = MMDF_FORMAT; +static int pd = NOTOK; +static FILE *pf = NULL; +#endif /* POP */ + + +/* + * For setting and returning to "mail" gid + */ +#ifdef MAILGROUP +static int return_gid; +#endif + +/* + * prototypes + */ +char *map_name(char *); + +#ifdef POP +void done(int); +static int pop_action(char *); +static int pop_pack(char *); +static int map_count(void); +#endif + + +int +main (int argc, char **argv) +{ + int chgflag = 1, trnflag = 1; + int noisy = 1, width = 0, locked = 0; + int rpop, i, hghnum, msgnum; + char *cp, *maildir, *folder = NULL; + char *format = NULL, *form = NULL; + char *newmail, *host = NULL, *user = NULL; + char *audfile = NULL, *from = NULL; + char buf[BUFSIZ], **argp, *nfs, **arguments; + struct msgs *mp; + struct stat st, s1; + FILE *in, *aud = NULL; + +#ifdef POP + int nmsgs, nbytes, p = 0; + char *pass = NULL; +#endif + +#ifdef MHE + FILE *mhe = NULL; +#endif + +#ifdef HESIOD + struct hes_postoffice *po; + char *tmphost; +#endif + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + +#ifdef POP +# ifdef HESIOD + /* + * Scheme is: + * use MAILHOST environment variable if present, + * else try Hesiod. + * If that fails, use the default (if any) + * provided by mts.conf in mts_init() + */ + if ((tmphost = getenv("MAILHOST")) != NULL) + pophost = tmphost; + else if ((po = hes_getmailhost(getusername())) != NULL && + strcmp(po->po_type, "POP") == 0) + pophost = po->po_host; +# endif /* HESIOD */ + /* + * If there is a valid "pophost" entry in mts.conf, + * then use it as the default host. + */ + if (pophost && *pophost) + host = pophost; + + if ((cp = getenv ("MHPOPDEBUG")) && *cp) + snoop++; +#endif /* POP */ + +#ifdef KPOP + rpop = 1; +#else + rpop = 0; +#endif + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [switches]", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case AUDSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + audfile = getcpy (m_maildir (cp)); + continue; + case NAUDSW: + audfile = NULL; + continue; + + case CHGSW: + chgflag++; + continue; + case NCHGSW: + chgflag = 0; + continue; + + /* + * The flag `trnflag' has the value: + * + * 2 if -truncate is given + * 1 by default (truncating is default) + * 0 if -notruncate is given + */ + case TRNCSW: + trnflag = 2; + continue; + case NTRNCSW: + trnflag = 0; + continue; + + case FILESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + from = path (cp, TFILE); + + /* + * If the truncate file is in default state, + * change to not truncate. + */ + if (trnflag == 1) + trnflag = 0; + continue; + + case SILSW: + noisy = 0; + continue; + case NSILSW: + noisy++; + continue; + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + width = atoi (cp); + continue; + + case HOSTSW: + if (!(host = *argp++) || *host == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case USERSW: + if (!(user = *argp++) || *user == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case PACKSW: +#ifndef POP + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); +#else /* POP */ + if (!(packfile = *argp++) || *packfile == '-') + adios (NULL, "missing argument to %s", argp[-2]); +#endif /* POP */ + continue; + case NPACKSW: +#ifdef POP + packfile = NULL; +#endif /* POP */ + continue; + + case APOPSW: + rpop = -1; + continue; + case NAPOPSW: + rpop = 0; + continue; + + case RPOPSW: + rpop = 1; + continue; + case NRPOPSW: + rpop = 0; + continue; + + case SNOOPSW: + snoop++; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + adios (NULL, "usage: %s [+folder] [switches]", invo_name); + } + } + +#ifdef MAILGROUP + return_gid = getegid(); /* Save effective gid, assuming we'll use it */ + setgid(getgid()); /* Turn off extraordinary privileges */ +#endif /* MAILGROUP */ + +#ifdef POP + if (host && !*host) + host = NULL; + if (from || !host || rpop <= 0) + setuid (getuid ()); +#endif /* POP */ + + /* + * Where are we getting the new mail? + */ + if (from) + inc_type = INC_FILE; +#ifdef POP + else if (host) + inc_type = INC_POP; +#endif + else + inc_type = INC_FILE; + +#ifdef POP + /* + * Are we getting the mail from + * a POP server? + */ + if (inc_type == INC_POP) { + if (user == NULL) + user = getusername (); + if (rpop > 0) + pass = getusername (); + else + ruserpass (host, &user, &pass); + + /* + * initialize POP connection + */ + if (pop_init (host, user, pass, snoop, rpop) == NOTOK) + adios (NULL, "%s", response); + + /* Check if there are any messages */ + if (pop_stat (&nmsgs, &nbytes) == NOTOK) + adios (NULL, "%s", response); + + if (rpop > 0) + setuid (getuid ()); + if (nmsgs == 0) { + pop_quit(); + adios (NULL, "no mail to incorporate"); + } + } +#endif /* POP */ + + /* + * We will get the mail from a file + * (typically the standard maildrop) + */ + + if (inc_type == INC_FILE) { + if (from) + newmail = from; + else if ((newmail = getenv ("MAILDROP")) && *newmail) + newmail = m_mailpath (newmail); + else if ((newmail = context_find ("maildrop")) && *newmail) + newmail = m_mailpath (newmail); + else { + newmail = concat (MAILDIR, "/", MAILFIL, NULL); + } + if (stat (newmail, &s1) == NOTOK || s1.st_size == 0) + adios (NULL, "no mail to incorporate"); + } + +#ifdef POP + /* skip the folder setup */ + if ((inc_type == INC_POP) && packfile) + goto go_to_it; +#endif /* POP */ + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!folder) + folder = getfolder (0); + maildir = m_maildir (folder); + + if (stat (maildir, &st) == NOTOK) { + if (errno != ENOENT) + adios (maildir, "error on folder"); + cp = concat ("Create folder \"", maildir, "\"? ", NULL); + if (noisy && !getanswer (cp)) + done (1); + free (cp); + if (!makedir (maildir)) + adios (NULL, "unable to create folder %s", maildir); + } + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + +#ifdef POP +go_to_it: +#endif /* POP */ + + if (inc_type == INC_FILE) { + if (access (newmail, W_OK) != NOTOK) { + locked++; + if (trnflag) { + SIGNAL (SIGHUP, SIG_IGN); + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); + SIGNAL (SIGTERM, SIG_IGN); + } + +#ifdef MAILGROUP + setgid(return_gid); /* Reset gid to lock mail file */ +#endif /* MAILGROUP */ + + /* lock and fopen the mail spool */ + if ((in = lkfopen (newmail, "r")) == NULL) + adios (NULL, "unable to lock and fopen %s", newmail); + +#ifdef MAILGROUP + setgid(getgid()); /* Return us to normal privileges */ +#endif /* MAILGROUP */ + fstat (fileno(in), &s1); + } else { + trnflag = 0; + if ((in = fopen (newmail, "r")) == NULL) + adios (newmail, "unable to read"); + } + } + +#ifdef MAILGROUP + setgid(getgid()); /* Return us to normal privileges */ +#endif /* MAILGROUP */ + + if (audfile) { + if ((i = stat (audfile, &st)) == NOTOK) + advise (NULL, "Creating Receive-Audit: %s", audfile); + if ((aud = fopen (audfile, "a")) == NULL) + adios (audfile, "unable to append to"); + else if (i == NOTOK) + chmod (audfile, m_gmprot ()); + +#ifdef POP + fprintf (aud, from ? "<> %s -ms %s\n" + : host ? "<> %s -host %s -user %s%s\n" + : "<> %s\n", + dtimenow (0), from ? from : host, user, + rpop < 0 ? " -apop" : rpop > 0 ? " -rpop" : ""); +#else /* POP */ + fprintf (aud, from ? "<> %s -ms %s\n" : "<> %s\n", + dtimenow (0), from); +#endif /* POP */ + } + +#ifdef MHE + if (context_find ("mhe")) { + cp = concat (maildir, "/++", NULL); + i = stat (cp, &st); + if ((mhe = fopen (cp, "a")) == NULL) + admonish (cp, "unable to append to"); + else + if (i == NOTOK) + chmod (cp, m_gmprot ()); + free (cp); + } +#endif /* MHE */ + + /* Get new format string */ + nfs = new_fs (form, format, FORMAT); + + if (noisy) { + printf ("Incorporating new mail into %s...\n\n", folder); + fflush (stdout); + } + +#ifdef POP + /* + * Get the mail from a POP server + */ + if (inc_type == INC_POP) { + if (packfile) { + packfile = path (packfile, TFILE); + if (stat (packfile, &st) == NOTOK) { + if (errno != ENOENT) + adios (packfile, "error on file"); + cp = concat ("Create file \"", packfile, "\"? ", NULL); + if (noisy && !getanswer (cp)) + done (1); + free (cp); + } + msgnum = map_count (); + if ((pd = mbx_open (packfile, mbx_style, getuid(), getgid(), m_gmprot())) + == NOTOK) + adios (packfile, "unable to open"); + if ((pf = fdopen (pd, "w+")) == NULL) + adios (NULL, "unable to fdopen %s", packfile); + } else { + hghnum = msgnum = mp->hghmsg; + /* + * Check if we have enough message space for all the new + * messages. If not, then realloc the folder and add enough + * space for all new messages plus 10 additional slots. + */ + if (mp->hghmsg + nmsgs >= mp->hghoff + && !(mp = folder_realloc (mp, mp->lowoff, mp->hghmsg + nmsgs + 10))) + adios (NULL, "unable to allocate folder storage"); + } + + for (i = 1; i <= nmsgs; i++) { + msgnum++; + if (packfile) { + fseek (pf, 0L, SEEK_CUR); + pos = ftell (pf); + size = 0; + fwrite (mmdlm1, 1, strlen (mmdlm1), pf); + start = ftell (pf); + + if (pop_retr (i, pop_pack) == NOTOK) + adios (NULL, "%s", response); + + fseek (pf, 0L, SEEK_CUR); + stop = ftell (pf); + if (fflush (pf)) + adios (packfile, "write error on"); + fseek (pf, start, SEEK_SET); + } else { + cp = getcpy (m_name (msgnum)); + if ((pf = fopen (cp, "w+")) == NULL) + adios (cp, "unable to write"); + chmod (cp, m_gmprot ()); + start = stop = 0L; + + if (pop_retr (i, pop_action) == NOTOK) + adios (NULL, "%s", response); + + if (fflush (pf)) + adios (cp, "write error on"); + fseek (pf, 0L, SEEK_SET); + } + switch (p = scan (pf, msgnum, 0, nfs, width, + packfile ? 0 : msgnum == mp->hghmsg + 1 && chgflag, + 1, NULL, stop - start, noisy)) { + case SCNEOF: + printf ("%*d empty\n", DMAXFOLDER, msgnum); + break; + + case SCNFAT: + trnflag = 0; + noisy++; + /* advise (cp, "unable to read"); already advised */ + /* fall thru */ + + case SCNERR: + case SCNNUM: + break; + + case SCNMSG: + case SCNENC: + default: + if (aud) + fputs (scanl, aud); +# ifdef MHE + if (mhe) + fputs (scanl, mhe); +# endif /* MHE */ + if (noisy) + fflush (stdout); + if (!packfile) { + clear_msg_flags (mp, msgnum); + set_exists (mp, msgnum); + set_unseen (mp, msgnum); + mp->msgflags |= SEQMOD; + } + break; + } + if (packfile) { + fseek (pf, stop, SEEK_SET); + fwrite (mmdlm2, 1, strlen (mmdlm2), pf); + if (fflush (pf) || ferror (pf)) { + int e = errno; + pop_quit (); + errno = e; + adios (packfile, "write error on"); + } + map_write (packfile, pd, 0, 0L, start, stop, pos, size, noisy); + } else { + if (ferror(pf) || fclose (pf)) { + int e = errno; + unlink (cp); + pop_quit (); + errno = e; + adios (cp, "write error on"); + } + free (cp); + } + + if (trnflag && pop_dele (i) == NOTOK) + adios (NULL, "%s", response); + } + + if (pop_quit () == NOTOK) + adios (NULL, "%s", response); + if (packfile) { + mbx_close (packfile, pd); + pd = NOTOK; + } + } +#endif /* POP */ + + /* + * Get the mail from file (usually mail spool) + */ + if (inc_type == INC_FILE) { + m_unknown (in); /* the MAGIC invocation... */ + hghnum = msgnum = mp->hghmsg; + for (i = 0;;) { + /* + * Check if we need to allocate more space for message status. + * If so, then add space for an additional 100 messages. + */ + if (msgnum >= mp->hghoff + && !(mp = folder_realloc (mp, mp->lowoff, mp->hghoff + 100))) { + advise (NULL, "unable to allocate folder storage"); + i = NOTOK; + break; + } + +#if 0 + /* copy file from spool to tmp file */ + tmpfilenam = m_scratch ("", invo_name); + if ((fd = creat (tmpfilenam, m_gmprot ())) == NOTOK) + adios (tmpfilenam, "unable to create"); + chmod (tmpfilenam, m_gmprot ()); + if (!(in2 = fdopen (fd, "r+"))) + adios (tmpfilenam, "unable to access"); + cpymsg (in, in2); + + /* link message into folder */ + newmsg = folder_addmsg(mp, tmpfilenam); +#endif + + /* create scanline for new message */ + switch (i = scan (in, msgnum + 1, msgnum + 1, nfs, width, + msgnum == hghnum && chgflag, 1, NULL, 0L, noisy)) { + case SCNFAT: + case SCNEOF: + break; + + case SCNERR: + if (aud) + fputs ("inc aborted!\n", aud); + advise (NULL, "aborted!"); /* doesn't clean up locks! */ + break; + + case SCNNUM: + advise (NULL, "BUG in %s, number out of range", invo_name); + break; + + default: + advise (NULL, "BUG in %s, scan() botch (%d)", invo_name, i); + break; + + case SCNMSG: + case SCNENC: + if (aud) + fputs (scanl, aud); +#ifdef MHE + if (mhe) + fputs (scanl, mhe); +#endif /* MHE */ + if (noisy) + fflush (stdout); + + msgnum++; + mp->hghmsg++; + clear_msg_flags (mp, msgnum); + set_exists (mp, msgnum); + set_unseen (mp, msgnum); + mp->msgflags |= SEQMOD; + continue; + } + break; + } + } + +#ifdef POP + if (p < 0) { /* error */ +#else + if (i < 0) { /* error */ +#endif + if (locked) { +#ifdef MAILGROUP + /* Be sure we can unlock mail file */ + setgid(return_gid); +#endif /* MAILGROUP */ + + lkfclose (in, newmail); + +#ifdef MAILGROUP + /* And then return us to normal privileges */ + setgid(getgid()); +#endif /* MAILGROUP */ + } else { + fclose (in); + } + adios (NULL, "failed"); + } + + if (aud) + fclose (aud); + +#ifdef MHE + if (mhe) + fclose (mhe); +#endif /* MHE */ + + if (noisy) + fflush (stdout); + +#ifdef POP + if ((inc_type == INC_POP) && packfile) + done (0); +#endif /* POP */ + + /* + * truncate file we are incorporating from + */ + if (inc_type == INC_FILE) { + if (trnflag) { + if (stat (newmail, &st) != NOTOK && s1.st_mtime != st.st_mtime) + advise (NULL, "new messages have arrived!\007"); + else { + if ((i = creat (newmail, 0600)) != NOTOK) + close (i); + else + admonish (newmail, "error zero'ing"); + unlink(map_name(newmail)); + } + } else { + if (noisy) + printf ("%s not zero'd\n", newmail); + } + } + + if (msgnum == hghnum) { + admonish (NULL, "no messages incorporated"); + } else { + context_replace (pfolder, folder); /* update current folder */ + if (chgflag) + mp->curmsg = hghnum + 1; + mp->hghmsg = msgnum; + if (mp->lowmsg == 0) + mp->lowmsg = 1; + if (chgflag) /* sigh... */ + seq_setcur (mp, mp->curmsg); + } + + /* + * unlock the mail spool + */ + if (inc_type == INC_FILE) { + if (locked) { +#ifdef MAILGROUP + setgid(return_gid); /* Be sure we can unlock mail file */ +#endif /* MAILGROUP */ + + lkfclose (in, newmail); + +#ifdef MAILGROUP + setgid(getgid()); /* And then return us to normal privileges */ +#endif /* MAILGROUP */ + } else { + fclose (in); + } + } + + seq_setunseen (mp, 0); /* set the Unseen-Sequence */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + done (0); +} + + +#if 0 + +/* + * Copy message message from spool into + * temporary file. Massage the "From " line + * while copying. + */ + +cpymsg (FILE *in, FILE *out) +{ + int state; + char *tmpbuf, name[NAMESZ]; + + for (;;) { + state = m_getfld (state, name, tmpbuf, rlwidth, in); + switch (state) { + case FLD: + case FLDPLUS: + break; + case BODY: + break; + case LENERR: + case FMTERR: + break; + case FILEEOF: + break; + default: + } + } +} +#endif /* if 0 */ + + +#ifdef POP +void +done (int status) +{ + if (packfile && pd != NOTOK) + mbx_close (packfile, pd); + + exit (status); +} + +static int +pop_action (char *s) +{ + fprintf (pf, "%s\n", s); + stop += strlen (s) + 1; +} + +static int +pop_pack (char *s) +{ + int j; + char buffer[BUFSIZ]; + + snprintf (buffer, sizeof(buffer), "%s\n", s); + for (j = 0; (j = stringdex (mmdlm1, buffer)) >= 0; buffer[j]++) + continue; + for (j = 0; (j = stringdex (mmdlm2, buffer)) >= 0; buffer[j]++) + continue; + fputs (buffer, pf); + size += strlen (buffer) + 1; +} + +static int +map_count (void) +{ + int md; + char *cp; + struct drop d; + struct stat st; + + if (stat (packfile, &st) == NOTOK) + return 0; + if ((md = open (cp = map_name (packfile), O_RDONLY)) == NOTOK + || map_chk (cp, md, &d, (long) st.st_size, 1)) { + if (md != NOTOK) + close (md); + return 0; + } + close (md); + return (d.d_id); +} +#endif /* POP */ diff --git a/uip/install-mh.c b/uip/install-mh.c new file mode 100644 index 0000000..b2d2486 --- /dev/null +++ b/uip/install-mh.c @@ -0,0 +1,209 @@ + +/* + * install-mh.c -- initialize the nmh environment of a new user + * + * $Id$ + */ + +#include +#include + +static struct swit switches[] = { +#define AUTOSW 0 + { "auto", 0 }, +#define VERSIONSW 1 + { "version", 0 }, +#define HELPSW 2 + { "help", 4 }, + { NULL, 0 } +}; + +static char *message[] = { + "Prior to using nmh, it is necessary to have a file in your login", + "directory (%s) named %s which contains information", + "to direct certain nmh operations. The only item which is required", + "is the path to use for all nmh folder operations. The suggested nmh", + "path for you is %s/Mail...", + NULL +}; + +/* + * static prototypes + */ +static char *geta(void); + + +int +main (int argc, char **argv) +{ + int i, autof = 0; + char *cp, *path, buf[BUFSIZ]; + char *dp, **arguments, **argp; + struct node *np; + struct passwd *pw; + struct stat st; + FILE *in, *out; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + while ((dp = *argp++)) { + if (*dp == '-') { + switch (smatch (++dp, switches)) { + case AMBIGSW: + ambigsw (dp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", dp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches]", invo_name); + print_help (buf, switches, 0); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case AUTOSW: + autof++; + continue; + } + } else { + adios (NULL, "%s is invalid argument", dp); + } + } + + /* straight from context_read ... */ + if (mypath == NULL) { + if ((mypath = getenv ("HOME"))) { + mypath = getcpy (mypath); + } else { + if ((pw = getpwuid (getuid ())) == NULL + || pw->pw_dir == NULL + || *pw->pw_dir == 0) + adios (NULL, "no HOME envariable"); + else + mypath = getcpy (pw->pw_dir); + } + if ((cp = mypath + strlen (mypath) - 1) > mypath && *cp == '/') + *cp = 0; + } + defpath = concat (mypath, "/", mh_profile, NULL); + + if (stat (defpath, &st) != NOTOK) { + if (autof) + adios (NULL, "invocation error"); + else + adios (NULL, + "You already have an nmh profile, use an editor to modify it"); + } + + if (!autof && gans ("Do you want help? ", anoyes)) { + putchar ('\n'); + for (i = 0; message[i]; i++) { + printf (message[i], mypath, mh_profile); + putchar ('\n'); + } + putchar ('\n'); + } + + cp = concat (mypath, "/", "Mail", NULL); + if (stat (cp, &st) != NOTOK) { + if (S_ISDIR(st.st_mode)) { + cp = concat ("You already have the standard nmh directory \"", + cp, "\".\nDo you want to use it for nmh? ", NULL); + if (gans (cp, anoyes)) + path = "Mail"; + else + goto query; + } else { + goto query; + } + } else { + if (autof) + printf ("I'm going to create the standard nmh path for you.\n"); + else + cp = concat ("Do you want the standard nmh path \"", + mypath, "/", "Mail\"? ", NULL); + if (autof || gans (cp, anoyes)) + path = "Mail"; + else { +query: + if (gans ("Do you want a path below your login directory? ", + anoyes)) { + printf ("What is the path? %s/", mypath); + path = geta (); + } else { + printf ("What is the whole path? /"); + path = concat ("/", geta (), NULL); + } + } + } + + chdir (mypath); + if (chdir (path) == NOTOK) { + cp = concat ("\"", path, "\" doesn't exist; Create it? ", NULL); + if (autof || gans (cp, anoyes)) + if (makedir (path) == 0) + adios (NULL, "unable to create %s", path); + } else { + printf ("[Using existing directory]\n"); + } + + /* + * Add some initial elements to the profile/context list + */ + if (!(m_defs = (struct node *) malloc (sizeof *np))) + adios (NULL, "unable to allocate profile storage"); + np = m_defs; + np->n_name = getcpy ("Path"); + np->n_field = getcpy (path); + np->n_context = 0; + np->n_next = NULL; + + /* + * If there is a default profile file in the + * nmh `etc' directory, then read it also. + */ + if ((in = fopen (mh_defaults, "r"))) { + readconfig (&np->n_next, in, mh_defaults, 0); + fclose (in); + } + + ctxpath = getcpy (m_maildir (context = "context")); + + /* Initialize current folder to default */ + context_replace (pfolder, defaultfolder); + context_save (); + + /* + * Now write out the initial .mh_profile + */ + if ((out = fopen (defpath, "w")) == NULL) + adios (defpath, "unable to write"); + for (np = m_defs; np; np = np->n_next) { + if (!np->n_context) + fprintf (out, "%s: %s\n", np->n_name, np->n_field); + } + fclose (out); + done (0); +} + + +static char * +geta (void) +{ + char *cp; + static char line[BUFSIZ]; + + fflush(stdout); + if (fgets(line, sizeof(line), stdin) == NULL) + done (1); + if ((cp = strchr(line, '\n'))) + *cp = 0; + return line; +} diff --git a/uip/mark.c b/uip/mark.c new file mode 100644 index 0000000..2bbba6d --- /dev/null +++ b/uip/mark.c @@ -0,0 +1,298 @@ + +/* + * mark.c -- add message(s) to sequences in given folder + * -- delete messages (s) from sequences in given folder + * -- list sequences in given folder + * + * $Id$ + */ + +#include + +/* + * We allocate space for messages (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define ADDSW 0 + { "add", 0 }, +#define DELSW 1 + { "delete", 0 }, +#define LSTSW 2 + { "list", 0 }, +#define SEQSW 3 + { "sequence name", 0 }, +#define PUBLSW 4 + { "public", 0 }, +#define NPUBLSW 5 + { "nopublic", 0 }, +#define ZEROSW 6 + { "zero", 0 }, +#define NZEROSW 7 + { "nozero", 0 }, +#define VERSIONSW 8 + { "version", 0 }, +#define HELPSW 9 + { "help", 4 }, +#define DEBUGSW 10 + { "debug", -5 }, + { NULL, 0 } +}; + +/* + * static prototypes + */ +static void print_debug (struct msgs *); +static void seq_printdebug (struct msgs *); + + +int +main (int argc, char **argv) +{ + int addsw = 0, deletesw = 0, debugsw = 0; + int listsw = 0, publicsw = -1, zerosw = 0; + int seqp = 0, msgnum, nummsgs, maxmsgs; + char *cp, *maildir, *folder = NULL, buf[BUFSIZ]; + char **argp, **arguments; + char *seqs[NUMATTRS + 1], **msgs; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case ADDSW: + addsw++; + deletesw = listsw = 0; + continue; + case DELSW: + deletesw++; + addsw = listsw = 0; + continue; + case LSTSW: + listsw++; + addsw = deletesw = 0; + continue; + + case SEQSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + + /* check if too many sequences specified */ + if (seqp >= NUMATTRS) + adios (NULL, "too many sequences (more than %d) specified", NUMATTRS); + seqs[seqp++] = cp; + continue; + + case PUBLSW: + publicsw = 1; + continue; + case NPUBLSW: + publicsw = 0; + continue; + + case DEBUGSW: + debugsw++; + continue; + + case ZEROSW: + zerosw++; + continue; + case NZEROSW: + zerosw = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + /* + * If we haven't specified -add, -delete, or -list, + * then use -add if a sequence was specified, else + * use -list. + */ + if (!addsw && !deletesw && !listsw) { + if (seqp) + addsw++; + else + listsw++; + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!nummsgs) + msgs[nummsgs++] = listsw ? "all" :"cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* print some general debugging info */ + if (debugsw) + print_debug(mp); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + + if (publicsw == 1 && is_readonly(mp)) + adios (NULL, "folder %s is read-only, so -public not allowed", folder); + + /* + * Make sure at least one sequence has been + * specified if we are adding or deleting. + */ + if (seqp == 0 && (addsw || deletesw)) + adios (NULL, "-%s requires at least one -sequence argument", + addsw ? "add" : "delete"); + seqs[seqp] = NULL; + + /* Adding messages to sequences */ + if (addsw) { + for (seqp = 0; seqs[seqp]; seqp++) + if (!seq_addsel (mp, seqs[seqp], publicsw, zerosw)) + done (1); + } + + /* Deleting messages from sequences */ + if (deletesw) { + for (seqp = 0; seqs[seqp]; seqp++) + if (!seq_delsel (mp, seqs[seqp], publicsw, zerosw)) + done (1); + } + + /* Listing messages in sequences */ + if (listsw) { + if (seqp) { + /* print the sequences given */ + for (seqp = 0; seqs[seqp]; seqp++) + seq_print (mp, seqs[seqp]); + } else { + /* else print them all */ + seq_printall (mp); + } + + /* print debugging info about SELECTED messages */ + if (debugsw) + seq_printdebug (mp); + } + + seq_save (mp); /* synchronize message sequences */ + context_replace (pfolder, folder); /* update current folder */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder/message structure */ + done (0); +} + + +/* + * Print general debugging info + */ +static void +print_debug (struct msgs *mp) +{ + char buf[100]; + + printf ("invo_name = %s\n", invo_name); + printf ("mypath = %s\n", mypath); + printf ("defpath = %s\n", defpath); + printf ("ctxpath = %s\n", ctxpath); + printf ("context flags = %s\n", snprintb (buf, sizeof(buf), + (unsigned) ctxflags, DBITS)); + printf ("foldpath = %s\n", mp->foldpath); + printf ("folder flags = %s\n\n", snprintb(buf, sizeof(buf), + (unsigned) mp->msgflags, FBITS)); + printf ("lowmsg=%d hghmsg=%d nummsg=%d curmsg=%d\n", + mp->lowmsg, mp->hghmsg, mp->nummsg, mp->curmsg); + printf ("lowsel=%d hghsel=%d numsel=%d\n", + mp->lowsel, mp->hghsel, mp->numsel); + printf ("lowoff=%d hghoff=%d\n\n", mp->lowoff, mp->hghoff); +} + + +/* + * Print debugging info about all the SELECTED + * messages and the sequences they are in. + */ +static void +seq_printdebug (struct msgs *mp) +{ + int msgnum; + char buf[100]; + + printf ("\n"); + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) + printf ("%*d: %s\n", DMAXFOLDER, msgnum, + snprintb (buf, sizeof(buf), + (unsigned) mp->msgstats[msgnum - mp->lowoff], seq_bits (mp))); + } +} diff --git a/uip/md5.c b/uip/md5.c new file mode 100644 index 0000000..2cb6cb5 --- /dev/null +++ b/uip/md5.c @@ -0,0 +1,308 @@ + +/* + * md5.c -- md5 message digest algorithm + * taken from RFC-1321/Appendix A.3 + * + * $Id$ + */ + +/* + * MD5C.C -- RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* + * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + * rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD5 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD5 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +#include + +/* + * Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform PROTO_LIST ((UINT4 [4], unsigned char [64])); +static void Encode PROTO_LIST ((unsigned char *, UINT4 *, unsigned int)); +static void Decode PROTO_LIST ((UINT4 *, unsigned char *, unsigned int)); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +void MD5Init (context) +MD5_CTX *context; /* context */ +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +void MD5Update (context, input, inputLen) +MD5_CTX *context; /* context */ +unsigned char *input; /* input block */ +unsigned int inputLen; /* length of input block */ +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. */ + if (inputLen >= partLen) { + memcpy ((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + +/* + * MD5 finalization. Ends an MD5 message-digest operation, writing the + * the message digest and zeroizing the context. + */ +void MD5Final (digest, context) +unsigned char digest[16]; /* message digest */ +MD5_CTX *context; /* context */ +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. */ + memset ((POINTER)context, 0, sizeof(*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (state, block) +UINT4 state[4]; +unsigned char block[64]; +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. */ + memset ((POINTER)x, 0, sizeof(x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (output, input, len) +unsigned char *output; +UINT4 *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (output, input, len) +UINT4 *output; +unsigned char *input; +unsigned int len; +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + diff --git a/uip/mhbuild.c b/uip/mhbuild.c new file mode 100644 index 0000000..bacec10 --- /dev/null +++ b/uip/mhbuild.c @@ -0,0 +1,384 @@ + +/* + * mhbuild.c -- expand/translate MIME composition files + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +static struct swit switches[] = { +#define CHECKSW 0 + { "check", 0 }, +#define NCHECKSW 1 + { "nocheck", 0 }, +#define EBCDICSW 2 + { "ebcdicsafe", 0 }, +#define NEBCDICSW 3 + { "noebcdicsafe", 0 }, +#define HEADSW 4 + { "headers", 0 }, +#define NHEADSW 5 + { "noheaders", 0 }, +#define LISTSW 6 + { "list", 0 }, +#define NLISTSW 7 + { "nolist", 0 }, +#define SIZESW 8 + { "realsize", 0 }, +#define NSIZESW 9 + { "norealsize", 0 }, +#define RFC934SW 10 + { "rfc934mode", 0 }, +#define NRFC934SW 11 + { "norfc934mode", 0 }, +#define VERBSW 12 + { "verbose", 0 }, +#define NVERBSW 13 + { "noverbose", 0 }, +#define RCACHESW 14 + { "rcache policy", 0 }, +#define WCACHESW 15 + { "wcache policy", 0 }, +#define VERSIONSW 16 + { "version", 0 }, +#define HELPSW 17 + { "help", 4 }, +#define DEBUGSW 18 + { "debug", -5 }, + { NULL, 0 } +}; + + +extern int errno; + +/* mhbuildsbr.c */ +extern int checksw; +extern char *tmp; /* directory to place temp files */ + +/* mhcachesbr.c */ +extern int rcachesw; +extern int wcachesw; +extern char *cache_public; +extern char *cache_private; + +int debugsw = 0; +int verbosw = 0; + +int ebcdicsw = 0; +int listsw = 0; +int rfc934sw = 0; + +/* + * Temporary files + */ +static char infile[BUFSIZ]; +static int unlink_infile = 0; + +static char outfile[BUFSIZ]; +static int unlink_outfile = 0; + + +/* mhbuildsbr.c */ +CT build_mime (char *); +int output_message (CT, char *); + +/* mhlistsbr.c */ +int list_all_messages (CT *, int, int, int, int); + +/* mhmisc.c */ +void set_endian (void); + +/* mhfree.c */ +void free_content (CT); + + +int +main (int argc, char **argv) +{ + int sizesw = 1, headsw = 1; + int *icachesw; + char *cp, buf[BUFSIZ]; + char buffer[BUFSIZ], *compfile = NULL; + char **argp, **arguments; + CT ct, cts[2]; + FILE *fp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (cp[0] == '-' && cp[1] == '\0') { + if (compfile) + adios (NULL, "cannot specify both standard input and a file"); + else + compfile = cp; + listsw = 0; /* turn off -list if using standard in/out */ + verbosw = 0; /* turn off -verbose listings */ + break; + } + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] file", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case RCACHESW: + icachesw = &rcachesw; + goto do_cache; + case WCACHESW: + icachesw = &wcachesw; + do_cache: ; + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + switch (*icachesw = smatch (cp, caches)) { + case AMBIGSW: + ambigsw (cp, caches); + done (1); + case UNKWNSW: + adios (NULL, "%s unknown", cp); + default: + break; + } + continue; + + case CHECKSW: + checksw++; + continue; + case NCHECKSW: + checksw = 0; + continue; + + case EBCDICSW: + ebcdicsw++; + continue; + case NEBCDICSW: + ebcdicsw = 0; + continue; + + case HEADSW: + headsw++; + continue; + case NHEADSW: + headsw = 0; + continue; + + case LISTSW: + listsw++; + continue; + case NLISTSW: + listsw = 0; + continue; + + case RFC934SW: + rfc934sw++; + continue; + case NRFC934SW: + rfc934sw = 0; + continue; + + case SIZESW: + sizesw++; + continue; + case NSIZESW: + sizesw = 0; + continue; + + case VERBSW: + verbosw++; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (compfile) + adios (NULL, "only one composition file allowed"); + else + compfile = cp; + } + + set_endian (); + + if ((cp = getenv ("MM_NOASK")) && !strcmp (cp, "1")) + listsw = 0; + + /* + * Check if we've specified an additional profile + */ + if ((cp = getenv ("MHBUILD"))) { + if ((fp = fopen (cp, "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } else { + admonish ("", "unable to read $MHBUILD profile (%s)", cp); + } + } + + /* + * Read the standard profile setup + */ + if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } + + /* Check for public cache location */ + if ((cache_public = context_find (nmhcache)) && *cache_public != '/') + cache_public = NULL; + + /* Check for private cache location */ + if (!(cache_private = context_find (nmhprivcache))) + cache_private = ".cache"; + cache_private = getcpy (m_maildir (cache_private)); + + /* + * Check for storage directory. If defined, we + * will store temporary files there. Else we + * store them in standard nmh directory. + */ + if ((cp = context_find (nmhstorage)) && *cp) + tmp = concat (cp, "/", invo_name, NULL); + else + tmp = add (m_maildir (invo_name), NULL); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* Check if we have a file to process */ + if (!compfile) + adios (NULL, "need to specify a %s composition file", invo_name); + + /* + * Process the composition file from standard input. + */ + if (compfile[0] == '-' && compfile[1] == '\0') { + + /* copy standard input to temporary file */ + strncpy (infile, m_scratch ("", invo_name), sizeof(infile)); + if ((fp = fopen (infile, "w")) == NULL) + adios (infile, "unable to open"); + while (fgets (buffer, BUFSIZ, stdin)) + fputs (buffer, fp); + fclose (fp); + unlink_infile = 1; + + /* build the content structures for MIME message */ + ct = build_mime (infile); + cts[0] = ct; + cts[1] = NULL; + + /* output MIME message to this temporary file */ + strncpy (outfile, m_scratch ("", invo_name), sizeof(outfile)); + unlink_outfile = 1; + + /* output the message */ + output_message (ct, outfile); + + /* output the temp file to standard output */ + if ((fp = fopen (outfile, "r")) == NULL) + adios (outfile, "unable to open"); + while (fgets (buffer, BUFSIZ, fp)) + fputs (buffer, stdout); + fclose (fp); + + unlink (infile); + unlink_infile = 0; + + unlink (outfile); + unlink_outfile = 0; + + free_content (ct); + done (0); + } + + /* + * Process the composition file from a file. + */ + + /* build the content structures for MIME message */ + ct = build_mime (compfile); + cts[0] = ct; + cts[1] = NULL; + + /* output MIME message to this temporary file */ + strncpy (outfile, m_scratch (compfile, invo_name), sizeof(outfile)); + unlink_outfile = 1; + + /* output the message */ + output_message (ct, outfile); + + /* + * List the message info + */ + if (listsw) + list_all_messages (cts, headsw, sizesw, verbosw, debugsw); + + /* Rename composition draft */ + snprintf (buffer, sizeof(buffer), "%s.orig", m_backup (compfile)); + if (rename (compfile, buffer) == NOTOK) + adios (compfile, "unable to rename %s to", buffer); + + /* Rename output file to take its place */ + if (rename (outfile, compfile) == NOTOK) { + advise (outfile, "unable to rename %s to", compfile); + rename (buffer, compfile); + done (1); + } + unlink_outfile = 0; + + free_content (ct); + done (0); + /* NOT REACHED */ +} + + +void +done (int status) +{ + /* + * Check if we need to remove stray + * temporary files. + */ + if (unlink_infile) + unlink (infile); + if (unlink_outfile) + unlink (outfile); + + exit (status); +} diff --git a/uip/mhbuildsbr.c b/uip/mhbuildsbr.c new file mode 100644 index 0000000..73907ab --- /dev/null +++ b/uip/mhbuildsbr.c @@ -0,0 +1,4216 @@ + +/* + * mhbuildsbr.c -- routines to expand/translate MIME composition files + * + * $Id$ + */ + +/* + * This code was originally part of mhn.c. I split it into + * a separate program (mhbuild.c) and then later split it + * again (mhbuildsbr.c). But the code still has some of + * the mhn.c code in it. This program needs additional + * streamlining and removal of unneeded code. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + + +extern int errno; + +extern int debugsw; +extern int verbosw; + +extern int ebcdicsw; +extern int listsw; +extern int rfc934sw; + +extern int endian; /* mhmisc.c */ + +/* cache policies */ +extern int rcachesw; /* mhcachesbr.c */ +extern int wcachesw; /* mhcachesbr.c */ + +int checksw = 0; /* Add Content-MD5 field */ + +/* + * Directory to place tmp files. This must + * be set before these routines are called. + */ +char *tmp; + +pid_t xpid = 0; + +static char prefix[] = "----- =_aaaaaaaaaa"; + +/* + * Structure for mapping types to their internal flags + */ +struct k2v { + char *kv_key; + int kv_value; +}; + +/* + * Structures for TEXT messages + */ +static struct k2v SubText[] = { + { "plain", TEXT_PLAIN }, + { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */ + { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */ + { NULL, TEXT_UNKNOWN } /* this one must be last! */ +}; + +static struct k2v Charset[] = { + { "us-ascii", CHARSET_USASCII }, + { "iso-8859-1", CHARSET_LATIN }, + { NULL, CHARSET_UNKNOWN } /* this one must be last! */ +}; + +/* + * Structures for MULTIPART messages + */ +static struct k2v SubMultiPart[] = { + { "mixed", MULTI_MIXED }, + { "alternative", MULTI_ALTERNATE }, + { "digest", MULTI_DIGEST }, + { "parallel", MULTI_PARALLEL }, + { NULL, MULTI_UNKNOWN } /* this one must be last! */ +}; + +/* + * Structures for MESSAGE messages + */ +static struct k2v SubMessage[] = { + { "rfc822", MESSAGE_RFC822 }, + { "partial", MESSAGE_PARTIAL }, + { "external-body", MESSAGE_EXTERNAL }, + { NULL, MESSAGE_UNKNOWN } /* this one must be last! */ +}; + +/* + * Structure for APPLICATION messages + */ +static struct k2v SubApplication[] = { + { "octet-stream", APPLICATION_OCTETS }, + { "postscript", APPLICATION_POSTSCRIPT }, + { NULL, APPLICATION_UNKNOWN } /* this one must be last! */ +}; + + +/* mhmisc.c */ +int make_intermediates (char *); +void content_error (char *, CT, char *, ...); + +/* mhcachesbr.c */ +int find_cache (CT, int, int *, char *, char *, int); + +/* ftpsbr.c */ +int ftp_get (char *, char *, char *, char *, char *, char *, int, int); + +/* mhfree.c */ +void free_content (CT); +void free_ctinfo (CT); +void free_encoding (CT, int); + +/* + * prototypes + */ +CT build_mime (char *); +int pidcheck (int); + +/* + * static prototypes + */ +static CT get_content (FILE *, char *, int); +static int add_header (CT, char *, char *); +static int get_ctinfo (char *, CT, int); +static int get_comment (CT, char **, int); +static int InitGeneric (CT); +static int InitText (CT); +static int InitMultiPart (CT); +static void reverse_parts (CT); +static int InitMessage (CT); +static int params_external (CT, int); +static int InitApplication (CT); +static int init_decoded_content (CT); +static int init_encoding (CT, OpenCEFunc); +static void close_encoding (CT); +static unsigned long size_encoding (CT); +static int InitBase64 (CT); +static int openBase64 (CT, char **); +static int InitQuoted (CT); +static int openQuoted (CT, char **); +static int Init7Bit (CT); +static int open7Bit (CT, char **); +static int openExternal (CT, CT, CE, char **, int *); +static int InitFile (CT); +static int openFile (CT, char **); +static int InitFTP (CT); +static int openFTP (CT, char **); +static int InitMail (CT); +static int openMail (CT, char **); +static char *fgetstr (char *, int, FILE *); +static int user_content (FILE *, char *, char *, CT *); +static void set_id (CT, int); +static int compose_content (CT); +static int scan_content (CT); +static int build_headers (CT); +static char *calculate_digest (CT, int); +static int readDigest (CT, char *); + +/* + * Structures for mapping (content) types to + * the functions to handle them. + */ +struct str2init { + char *si_key; + int si_val; + InitFunc si_init; +}; + +static struct str2init str2cts[] = { + { "application", CT_APPLICATION, InitApplication }, + { "audio", CT_AUDIO, InitGeneric }, + { "image", CT_IMAGE, InitGeneric }, + { "message", CT_MESSAGE, InitMessage }, + { "multipart", CT_MULTIPART, InitMultiPart }, + { "text", CT_TEXT, InitText }, + { "video", CT_VIDEO, InitGeneric }, + { NULL, CT_EXTENSION, NULL }, /* these two must be last! */ + { NULL, CT_UNKNOWN, NULL }, +}; + +static struct str2init str2ces[] = { + { "base64", CE_BASE64, InitBase64 }, + { "quoted-printable", CE_QUOTED, InitQuoted }, + { "8bit", CE_8BIT, Init7Bit }, + { "7bit", CE_7BIT, Init7Bit }, + { "binary", CE_BINARY, NULL }, + { NULL, CE_EXTENSION, NULL }, /* these two must be last! */ + { NULL, CE_UNKNOWN, NULL }, +}; + +/* + * NOTE WELL: si_key MUST NOT have value of NOTOK + * + * si_key is 1 if access method is anonymous. + */ +static struct str2init str2methods[] = { + { "afs", 1, InitFile }, + { "anon-ftp", 1, InitFTP }, + { "ftp", 0, InitFTP }, + { "local-file", 0, InitFile }, + { "mail-server", 0, InitMail }, + { NULL, 0, NULL } +}; + + +int +pidcheck (int status) +{ + if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT) + return status; + + fflush (stdout); + fflush (stderr); + done (1); + /* NOTREACHED */ +} + + +/* + * Main routine for translating composition file + * into valid MIME message. It translates the draft + * into a content structure (actually a tree of content + * structures). This message then can be manipulated + * in various ways, including being output via + * output_message(). + */ + +CT +build_mime (char *infile) +{ + int compnum, state; + char buf[BUFSIZ], name[NAMESZ]; + char *cp, *np, *vp; + struct multipart *m; + struct part **pp; + CT ct; + FILE *in; + + umask (~m_gmprot ()); + + /* open the composition draft */ + if ((in = fopen (infile, "r")) == NULL) + adios (infile, "unable to open for reading"); + + /* + * Allocate space for primary (outside) content + */ + if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) + adios (NULL, "out of memory"); + + /* + * Allocate structure for handling decoded content + * for this part. We don't really need this, but + * allocate it to remain consistent. + */ + init_decoded_content (ct); + + /* + * Parse some of the header fields in the composition + * draft into the linked list of header fields for + * the new MIME message. + */ + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; + + /* abort if draft has Mime-Version header field */ + if (!strcasecmp (name, VRSN_FIELD)) + adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD); + + /* abort if draft has Content-Transfer-Encoding header field */ + if (!strcasecmp (name, ENCODING_FIELD)) + adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD); + + /* ignore any Content-Type fields in the header */ + if (!strcasecmp (name, TYPE_FIELD)) { + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), in); + goto finish_field; + } + + /* get copies of the buffers */ + np = add (name, NULL); + vp = add (buf, NULL); + + /* if necessary, get rest of field */ + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + vp = add (buf, vp); /* add to previous value */ + } + + /* Now add the header data to the list */ + add_header (ct, np, vp); + +finish_field: + /* if this wasn't the last header field, then continue */ + if (state != FLDEOF) + continue; + /* else fall... */ + + case FILEEOF: + adios (NULL, "draft has empty body -- no directives!"); + /* NOTREACHED */ + + case BODY: + case BODYEOF: + fseek (in, (long) (-strlen (buf)), SEEK_CUR); + break; + + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", compnum); + + default: + adios (NULL, "getfld() returned %d", state); + } + break; + } + + /* + * Now add the MIME-Version header field + * to the list of header fields. + */ + np = add (VRSN_FIELD, NULL); + vp = concat (" ", VRSN_VALUE, "\n", NULL); + add_header (ct, np, vp); + + /* + * We initally assume we will find multiple contents in the + * draft. So create a multipart/mixed content to hold everything. + * We can remove this later, if it is not needed. + */ + if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_MIXED; + ct->c_file = add (infile, NULL); + + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; + + /* + * read and parse the composition file + * and the directives it contains. + */ + while (fgetstr (buf, sizeof(buf) - 1, in)) { + struct part *part; + CT p; + + if (user_content (in, infile, buf, &p) == DONE) { + admonish (NULL, "ignoring spurious #end"); + continue; + } + if (!p) + continue; + + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + + /* + * close the composition draft since + * it's not needed any longer. + */ + fclose (in); + + /* check if any contents were found */ + if (!m->mp_parts) + adios (NULL, "no content directives found"); + + /* + * If only one content was found, then remove and + * free the outer multipart content. + */ + if (!m->mp_parts->mp_next) { + CT p; + + p = m->mp_parts->mp_part; + m->mp_parts->mp_part = NULL; + + /* move header fields */ + p->c_first_hf = ct->c_first_hf; + p->c_last_hf = ct->c_last_hf; + ct->c_first_hf = NULL; + ct->c_last_hf = NULL; + + free_content (ct); + ct = p; + } else { + set_id (ct, 1); + } + + /* + * Fill out, or expand directives. Parse and execute + * commands specified by profile composition strings. + */ + compose_content (ct); + + if ((cp = strchr(prefix, 'a')) == NULL) + adios (NULL, "internal error(4)"); + + /* + * Scan the contents. Choose a transfer encoding, and + * check if prefix for multipart boundary clashes with + * any of the contents. + */ + while (scan_content (ct) == NOTOK) { + if (*cp < 'z') { + (*cp)++; + } else { + if (*++cp == 0) + adios (NULL, "giving up trying to find a unique delimiter string"); + else + (*cp)++; + } + } + + /* Build the rest of the header field structures */ + build_headers (ct); + + return ct; +} + + +/* + * Main routine for reading/parsing the headers + * of a message content. + * + * toplevel = 1 # we are at the top level of the message + * toplevel = 0 # we are inside message type or multipart type + * # other than multipart/digest + * toplevel = -1 # we are inside multipart/digest + */ + +static CT +get_content (FILE *in, char *file, int toplevel) +{ + int compnum, state; + char buf[BUFSIZ], name[NAMESZ]; + CT ct; + + if (!(ct = (CT) calloc (1, sizeof(*ct)))) + adios (NULL, "out of memory"); + + ct->c_fp = in; + ct->c_file = add (file, NULL); + ct->c_begin = ftell (ct->c_fp) + 1; + + /* + * Read the content headers + */ + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; + + /* Get MIME-Version field */ + if (!strcasecmp (name, VRSN_FIELD)) { + int ucmp; + char c, *cp, *dp; + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + + if (ct->c_vrsn) { + advise (NULL, "message %s has multiple %s: fields (%s)", + ct->c_file, VRSN_FIELD, dp = trimcpy (cp)); + free (dp); + free (cp); + goto out; + } + + ct->c_vrsn = cp; + while (isspace (*cp)) + cp++; + for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) + *dp++ = ' '; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + if (debugsw) + fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); + + if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) + goto out; + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp, *dp = '\0'; + ucmp = !strcasecmp (cp, VRSN_VALUE); + *dp = c; + if (!ucmp) + admonish (NULL, + "message %s has unknown value for %s: field (%s)", + ct->c_file, VRSN_FIELD, cp); + goto got_header; + } + + /* Get Content-Type field */ + if (!strcasecmp (name, TYPE_FIELD)) { + char *cp; + struct str2init *s2i; + CI ci = &ct->c_ctinfo; + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + + /* Check if we've already seen a Content-Type header */ + if (ct->c_ctline) { + char *dp = trimcpy (cp); + + advise (NULL, "message %s has multiple %s: fields (%s)", + ct->c_file, TYPE_FIELD, dp); + free (dp); + free (cp); + goto out; + } + + /* Parse the Content-Type field */ + if (get_ctinfo (cp, ct, 0) == NOTOK) + goto out; + + /* + * Set the Init function and the internal + * flag for this content type. + */ + for (s2i = str2cts; s2i->si_key; s2i++) + if (!strcasecmp (ci->ci_type, s2i->si_key)) + break; + if (!s2i->si_key && !uprf (ci->ci_type, "X-")) + s2i++; + ct->c_type = s2i->si_val; + ct->c_ctinitfnx = s2i->si_init; + goto got_header; + } + + /* Get Content-Transfer-Encoding field */ + if (!strcasecmp (name, ENCODING_FIELD)) { + char *cp, *dp; + char c; + struct str2init *s2i; + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + + /* + * Check if we've already seen the + * Content-Transfer-Encoding field + */ + if (ct->c_celine) { + advise (NULL, "message %s has multiple %s: fields (%s)", + ct->c_file, ENCODING_FIELD, dp = trimcpy (cp)); + free (dp); + free (cp); + goto out; + } + + ct->c_celine = cp; /* Save copy of this field */ + while (isspace (*cp)) + cp++; + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp; + *dp = '\0'; + + /* + * Find the internal flag and Init function + * for this transfer encoding. + */ + for (s2i = str2ces; s2i->si_key; s2i++) + if (!strcasecmp (cp, s2i->si_key)) + break; + if (!s2i->si_key && !uprf (cp, "X-")) + s2i++; + *dp = c; + ct->c_encoding = s2i->si_val; + + /* Call the Init function for this encoding */ + if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK) + goto out; + goto got_header; + } + + /* Get Content-ID field */ + if (!strcasecmp (name, ID_FIELD)) { + ct->c_id = add (buf, ct->c_id); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + ct->c_id = add (buf, ct->c_id); + } + goto got_header; + } + + /* Get Content-Description field */ + if (!strcasecmp (name, DESCR_FIELD)) { + ct->c_descr = add (buf, ct->c_descr); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + ct->c_descr = add (buf, ct->c_descr); + } + goto got_header; + } + + /* Get Content-MD5 field */ + if (!strcasecmp (name, MD5_FIELD)) { + char *cp, *dp, *ep; + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + + if (!checksw) { + free (cp); + goto got_header; + } + + if (ct->c_digested) { + advise (NULL, "message %s has multiple %s: fields (%s)", + ct->c_file, MD5_FIELD, dp = trimcpy (cp)); + free (dp); + free (cp); + goto out; + } + + ep = cp; + while (isspace (*cp)) + cp++; + for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) + *dp++ = ' '; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + if (debugsw) + fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); + + if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { + free (ep); + goto out; + } + + for (dp = cp; *dp && !isspace (*dp); dp++) + continue; + *dp = '\0'; + + readDigest (ct, cp); + free (ep); + ct->c_digested++; + goto got_header; + } + +#if 0 + if (uprf (name, XXX_FIELD_PRF)) + advise (NULL, "unknown field (%s) in message %s", + name, ct->c_file); + /* and fall... */ +#endif + + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), in); + +got_header: + if (state != FLDEOF) { + ct->c_begin = ftell (in) + 1; + continue; + } + /* else fall... */ + + case BODY: + case BODYEOF: + ct->c_begin = ftell (in) - strlen (buf); + break; + + case FILEEOF: + ct->c_begin = ftell (in); + break; + + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", compnum); + + default: + adios (NULL, "getfld() returned %d", state); + } + break; + } + + /* + * Check if we saw a Content-Type field. + * If not, then assign a default value for + * it, and the Init function. + */ + if (!ct->c_ctline) { + /* + * If we are inside a multipart/digest message, + * so default type is message/rfc822 + */ + if (toplevel < 0) { + if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) + goto out; + ct->c_type = CT_MESSAGE; + ct->c_ctinitfnx = InitMessage; + } else { + /* + * Else default type is text/plain + */ + if (get_ctinfo ("text/plain", ct, 0) == NOTOK) + goto out; + ct->c_type = CT_TEXT; + ct->c_ctinitfnx = InitText; + } + } + + /* Use default Transfer-Encoding, if necessary */ + if (!ct->c_celine) { + ct->c_encoding = CE_7BIT; + Init7Bit (ct); + } + + return ct; + +out: + free_content (ct); + return NULL; +} + + +/* + * small routine to add header field to list + */ + +static int +add_header (CT ct, char *name, char *value) +{ + HF hp; + + /* allocate header field structure */ + if (!(hp = malloc (sizeof(*hp)))) + adios (NULL, "out of memory"); + + /* link data into header structure */ + hp->name = name; + hp->value = value; + hp->next = NULL; + + /* link header structure into the list */ + if (ct->c_first_hf == NULL) { + ct->c_first_hf = hp; /* this is the first */ + ct->c_last_hf = hp; + } else { + ct->c_last_hf->next = hp; /* add it to the end */ + ct->c_last_hf = hp; + } + + return 0; +} + + +/* + * Used to parse both: + * 1) Content-Type line + * 2) composition directives + * + * and fills in the information of the CTinfo structure. + */ + +static int +get_ctinfo (char *cp, CT ct, int magic) +{ + int i; + char *dp, **ap, **ep; + char c; + CI ci; + + ci = &ct->c_ctinfo; + i = strlen (invo_name) + 2; + + /* store copy of Content-Type line */ + cp = ct->c_ctline = add (cp, NULL); + + while (isspace (*cp)) /* trim leading spaces */ + cp++; + + /* change newlines to spaces */ + for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) + *dp++ = ' '; + + /* trim trailing spaces */ + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + + if (debugsw) + fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp, *dp = '\0'; + ci->ci_type = add (cp, NULL); /* store content type */ + *dp = c, cp = dp; + + if (!*ci->ci_type) { + advise (NULL, "invalid %s: field in message %s (empty type)", + TYPE_FIELD, ct->c_file); + return NOTOK; + } + + /* down case the content type string */ + for (dp = ci->ci_type; *dp; dp++) + if (isalpha(*dp) && isupper (*dp)) + *dp = tolower (*dp); + + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + if (*cp != '/') { + if (!magic) + ci->ci_subtype = add ("", NULL); + goto magic_skip; + } + + cp++; + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp, *dp = '\0'; + ci->ci_subtype = add (cp, NULL); /* store the content subtype */ + *dp = c, cp = dp; + + if (!*ci->ci_subtype) { + advise (NULL, + "invalid %s: field in message %s (empty subtype for \"%s\")", + TYPE_FIELD, ct->c_file, ci->ci_type); + return NOTOK; + } + + /* down case the content subtype string */ + for (dp = ci->ci_subtype; *dp; dp++) + if (isalpha(*dp) && isupper (*dp)) + *dp = tolower (*dp); + +magic_skip: + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + /* + * Parse attribute/value pairs given with Content-Type + */ + ep = (ap = ci->ci_attrs) + NPARMS; + while (*cp == ';') { + char *vp, *up; + + if (ap >= ep) { + advise (NULL, + "too many parameters in message %s's %s: field (%d max)", + ct->c_file, TYPE_FIELD, NPARMS); + return NOTOK; + } + + cp++; + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + if (*cp == 0) { + advise (NULL, + "extraneous trailing ';' in message %s's %s: parameter list", + ct->c_file, TYPE_FIELD); + return OK; + } + + /* down case the attribute name */ + for (dp = cp; istoken (*dp); dp++) + if (isalpha(*dp) && isupper (*dp)) + *dp = tolower (*dp); + + for (up = dp; isspace (*dp); ) + dp++; + if (dp == cp || *dp != '=') { + advise (NULL, + "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)", + ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp); + return NOTOK; + } + + vp = (*ap = add (cp, NULL)) + (up - cp); + *vp = '\0'; + for (dp++; isspace (*dp); ) + dp++; + + /* now add the attribute value */ + ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp); + + if (*dp == '"') { + for (cp = ++dp, dp = vp;;) { + switch (c = *cp++) { + case '\0': +bad_quote: + advise (NULL, + "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)", + ct->c_file, TYPE_FIELD, i, i, "", *ap); + return NOTOK; + + case '\\': + *dp++ = c; + if ((c = *cp++) == '\0') + goto bad_quote; + /* else fall... */ + + default: + *dp++ = c; + continue; + + case '"': + *dp = '\0'; + break; + } + break; + } + } else { + for (cp = dp, dp = vp; istoken (*cp); cp++, dp++) + continue; + *dp = '\0'; + } + if (!*vp) { + advise (NULL, + "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)", + ct->c_file, TYPE_FIELD, i, i, "", *ap); + return NOTOK; + } + ap++; + + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + } + + /* + * Get any given in buffer + */ + if (magic && *cp == '<') { + if (ct->c_id) { + free (ct->c_id); + ct->c_id = NULL; + } + if (!(dp = strchr(ct->c_id = ++cp, '>'))) { + advise (NULL, "invalid ID in message %s", ct->c_file); + return NOTOK; + } + c = *dp; + *dp = '\0'; + if (*ct->c_id) + ct->c_id = concat ("<", ct->c_id, ">\n", NULL); + else + ct->c_id = NULL; + *dp++ = c; + cp = dp; + + while (isspace (*cp)) + cp++; + } + + /* + * Get any [Content-Description] given in buffer. + */ + if (magic && *cp == '[') { + ct->c_descr = ++cp; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (*dp == ']') + break; + if (dp < cp) { + advise (NULL, "invalid description in message %s", ct->c_file); + ct->c_descr = NULL; + return NOTOK; + } + + c = *dp; + *dp = '\0'; + if (*ct->c_descr) + ct->c_descr = concat (ct->c_descr, "\n", NULL); + else + ct->c_descr = NULL; + *dp++ = c; + cp = dp; + + while (isspace (*cp)) + cp++; + } + + /* + * Check if anything is left over + */ + if (*cp) { + if (magic) + ci->ci_magic = add (cp, NULL); + else + advise (NULL, + "extraneous information in message %s's %s: field\n%*.*s(%s)", + ct->c_file, TYPE_FIELD, i, i, "", cp); + } + + return OK; +} + + +static int +get_comment (CT ct, char **ap, int istype) +{ + int i; + char *bp, *cp; + char c, buffer[BUFSIZ], *dp; + CI ci; + + ci = &ct->c_ctinfo; + cp = *ap; + bp = buffer; + cp++; + + for (i = 0;;) { + switch (c = *cp++) { + case '\0': +invalid: + advise (NULL, "invalid comment in message %s's %s: field", + ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD); + return NOTOK; + + case '\\': + *bp++ = c; + if ((c = *cp++) == '\0') + goto invalid; + *bp++ = c; + continue; + + case '(': + i++; + /* and fall... */ + default: + *bp++ = c; + continue; + + case ')': + if (--i < 0) + break; + *bp++ = c; + continue; + } + break; + } + *bp = '\0'; + + if (istype) { + if ((dp = ci->ci_comment)) { + ci->ci_comment = concat (dp, " ", buffer, NULL); + free (dp); + } else { + ci->ci_comment = add (buffer, NULL); + } + } + + while (isspace (*cp)) + cp++; + + *ap = cp; + return OK; +} + + +/* + * CONTENTS + * + * Handles content types audio, image, and video. + * There's not much to do right here. + */ + +static int +InitGeneric (CT ct) +{ + return OK; /* not much to do here */ +} + + +/* + * TEXT + */ + +static int +InitText (CT ct) +{ + char **ap, **ep; + struct k2v *kv; + struct text *t; + CI ci = &ct->c_ctinfo; + + /* check for missing subtype */ + if (!*ci->ci_subtype) + ci->ci_subtype = add ("plain", ci->ci_subtype); + + /* match subtype */ + for (kv = SubText; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + /* allocate text character set structure */ + if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) t; + + /* initially mark character set as unspecified */ + t->tx_charset = CHARSET_UNSPECIFIED; + + /* scan for charset parameter */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) + if (!strcasecmp (*ap, "charset")) + break; + + /* check if content specified a character set */ + if (*ap) { + /* match character set or set to CHARSET_UNKNOWN */ + for (kv = Charset; kv->kv_key; kv++) + if (!strcasecmp (*ep, kv->kv_key)) + break; + t->tx_charset = kv->kv_value; + } + + return OK; +} + + +/* + * MULTIPART + */ + +static int +InitMultiPart (CT ct) +{ + int inout; + long last, pos; + char *cp, *dp, **ap, **ep; + char *bp, buffer[BUFSIZ]; + struct multipart *m; + struct k2v *kv; + struct part *part, **next; + CI ci = &ct->c_ctinfo; + CT p; + FILE *fp; + + /* + * The encoding for multipart messages must be either + * 7bit, 8bit, or binary (per RFC2045). + */ + if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT + && ct->c_encoding != CE_BINARY) { + admonish (NULL, + "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary", + ci->ci_type, ci->ci_subtype, ct->c_file); + return NOTOK; + } + + /* match subtype */ + for (kv = SubMultiPart; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + /* + * Check for "boundary" parameter, which is + * required for multipart messages. + */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "boundary")) { + bp = *ep; + break; + } + } + + /* complain if boundary parameter is missing */ + if (!*ap) { + advise (NULL, + "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field", + ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); + return NOTOK; + } + + /* allocate primary structure for multipart info */ + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + + /* check if boundary parameter contains only whitespace characters */ + for (cp = bp; isspace (*cp); cp++) + continue; + if (!*cp) { + advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field", + ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); + return NOTOK; + } + + /* remove trailing whitespace from boundary parameter */ + for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + + /* record boundary separators */ + m->mp_start = concat (bp, "\n", NULL); + m->mp_stop = concat (bp, "--\n", NULL); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + advise (ct->c_file, "unable to open for reading"); + return NOTOK; + } + + fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET); + last = ct->c_end; + next = &m->mp_parts; + part = NULL; + inout = 1; + + while (fgets (buffer, sizeof(buffer) - 1, fp)) { + if (pos > last) + break; + + pos += strlen (buffer); + if (buffer[0] != '-' || buffer[1] != '-') + continue; + if (inout) { + if (strcmp (buffer + 2, m->mp_start)) + continue; +next_part: + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *next = part; + next = &part->mp_next; + + if (!(p = get_content (fp, ct->c_file, + rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) { + fclose (ct->c_fp); + ct->c_fp = NULL; + return NOTOK; + } + p->c_fp = NULL; + part->mp_part = p; + pos = p->c_begin; + fseek (fp, pos, SEEK_SET); + inout = 0; + } else { + if (strcmp (buffer + 2, m->mp_start) == 0) { + inout = 1; +end_part: + p = part->mp_part; + p->c_end = ftell(fp) - (strlen(buffer) + 1); + if (p->c_end < p->c_begin) + p->c_begin = p->c_end; + if (inout) + goto next_part; + goto last_part; + } else { + if (strcmp (buffer + 2, m->mp_stop) == 0) + goto end_part; + } + } + } + + advise (NULL, "bogus multipart content in message %s", ct->c_file); + if (!inout && part) { + p = part->mp_part; + p->c_end = ct->c_end; + + if (p->c_begin >= p->c_end) { + for (next = &m->mp_parts; *next != part; + next = &((*next)->mp_next)) + continue; + *next = NULL; + free_content (p); + free ((char *) part); + } + } + +last_part: + /* reverse the order of the parts for multipart/alternative */ + if (ct->c_subtype == MULTI_ALTERNATE) + reverse_parts (ct); + + /* + * label all subparts with part number, and + * then initialize the content of the subpart. + */ + { + int partnum; + char *pp; + char partnam[BUFSIZ]; + + if (ct->c_partno) { + snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno); + pp = partnam + strlen (partnam); + } else { + pp = partnam; + } + + for (part = m->mp_parts, partnum = 1; part; + part = part->mp_next, partnum++) { + p = part->mp_part; + + sprintf (pp, "%d", partnum); + p->c_partno = add (partnam, NULL); + + /* initialize the content of the subparts */ + if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) { + fclose (ct->c_fp); + ct->c_fp = NULL; + return NOTOK; + } + } + } + + fclose (ct->c_fp); + ct->c_fp = NULL; + return OK; +} + + +/* + * reverse the order of the parts of a multipart + */ + +static void +reverse_parts (CT ct) +{ + int i; + struct multipart *m; + struct part **base, **bmp, **next, *part; + + m = (struct multipart *) ct->c_ctparams; + + /* if only one part, just return */ + if (!m->mp_parts || !m->mp_parts->mp_next) + return; + + /* count number of parts */ + i = 0; + for (part = m->mp_parts; part; part = part->mp_next) + i++; + + /* allocate array of pointers to the parts */ + if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base)))) + adios (NULL, "out of memory"); + bmp = base; + + /* point at all the parts */ + for (part = m->mp_parts; part; part = part->mp_next) + *bmp++ = part; + *bmp = NULL; + + /* reverse the order of the parts */ + next = &m->mp_parts; + for (bmp--; bmp >= base; bmp--) { + part = *bmp; + *next = part; + next = &part->mp_next; + } + *next = NULL; + + /* free array of pointers */ + free ((char *) base); +} + + +/* + * MESSAGE + */ + +static int +InitMessage (CT ct) +{ + struct k2v *kv; + CI ci = &ct->c_ctinfo; + + if (ct->c_encoding != CE_7BIT) { + admonish (NULL, + "\"%s/%s\" type in message %s should be encoded in 7bit", + ci->ci_type, ci->ci_subtype, ct->c_file); + return NOTOK; + } + + /* check for missing subtype */ + if (!*ci->ci_subtype) + ci->ci_subtype = add ("rfc822", ci->ci_subtype); + + /* match subtype */ + for (kv = SubMessage; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + switch (ct->c_subtype) { + case MESSAGE_RFC822: + break; + + case MESSAGE_PARTIAL: + { + char **ap, **ep; + struct partial *p; + + if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) p; + + /* scan for parameters "id", "number", and "total" */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "id")) { + p->pm_partid = add (*ep, NULL); + continue; + } + if (!strcasecmp (*ap, "number")) { + if (sscanf (*ep, "%d", &p->pm_partno) != 1 + || p->pm_partno < 1) { +invalid_param: + advise (NULL, + "invalid %s parameter for \"%s/%s\" type in message %s's %s field", + *ap, ci->ci_type, ci->ci_subtype, + ct->c_file, TYPE_FIELD); + return NOTOK; + } + continue; + } + if (!strcasecmp (*ap, "total")) { + if (sscanf (*ep, "%d", &p->pm_maxno) != 1 + || p->pm_maxno < 1) + goto invalid_param; + continue; + } + } + + if (!p->pm_partid + || !p->pm_partno + || (p->pm_maxno && p->pm_partno > p->pm_maxno)) { + advise (NULL, + "invalid parameters for \"%s/%s\" type in message %s's %s field", + ci->ci_type, ci->ci_subtype, + ct->c_file, TYPE_FIELD); + return NOTOK; + } + } + break; + + case MESSAGE_EXTERNAL: + { + int exresult; + struct exbody *e; + CT p; + FILE *fp; + + if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) e; + + if (!ct->c_fp + && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + advise (ct->c_file, "unable to open for reading"); + return NOTOK; + } + + fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET); + + if (!(p = get_content (fp, ct->c_file, 0))) { + fclose (ct->c_fp); + ct->c_fp = NULL; + return NOTOK; + } + + e->eb_parent = ct; + e->eb_content = p; + p->c_ctexbody = e; + if ((exresult = params_external (ct, 0)) != NOTOK + && p->c_ceopenfnx == openMail) { + int cc, size; + char *bp; + + if ((size = ct->c_end - p->c_begin) <= 0) { + if (!e->eb_subject) + content_error (NULL, ct, + "empty body for access-type=mail-server"); + goto no_body; + } + + if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL) + adios (NULL, "out of memory"); + fseek (p->c_fp, p->c_begin, SEEK_SET); + while (size > 0) + switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) { + case NOTOK: + adios ("failed", "fread"); + + case OK: + adios (NULL, "unexpected EOF from fread"); + + default: + bp += cc, size -= cc; + break; + } + *bp = 0; + } +no_body: + p->c_fp = NULL; + p->c_end = p->c_begin; + + fclose (ct->c_fp); + ct->c_fp = NULL; + + if (exresult == NOTOK) + return NOTOK; + if (e->eb_flags == NOTOK) + return OK; + + switch (p->c_type) { + case CT_MULTIPART: + break; + + case CT_MESSAGE: + if (p->c_subtype != MESSAGE_RFC822) + break; + /* else fall... */ + default: + e->eb_partno = ct->c_partno; + if (p->c_ctinitfnx) + (*p->c_ctinitfnx) (p); + break; + } + } + break; + + default: + break; + } + + return OK; +} + + +static int +params_external (CT ct, int composing) +{ + char **ap, **ep; + struct exbody *e = (struct exbody *) ct->c_ctparams; + CI ci = &ct->c_ctinfo; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "access-type")) { + struct str2init *s2i; + CT p = e->eb_content; + + for (s2i = str2methods; s2i->si_key; s2i++) + if (!strcasecmp (*ep, s2i->si_key)) + break; + + if (!s2i->si_key) { + e->eb_access = *ep; + e->eb_flags = NOTOK; + p->c_encoding = CE_EXTERNAL; + continue; + } + e->eb_access = s2i->si_key; + e->eb_flags = s2i->si_val; + p->c_encoding = CE_EXTERNAL; + + /* Call the Init function for this external type */ + if ((*s2i->si_init)(p) == NOTOK) + return NOTOK; + continue; + } + if (!strcasecmp (*ap, "name")) { + e->eb_name = *ep; + continue; + } + if (!strcasecmp (*ap, "permission")) { + e->eb_permission = *ep; + continue; + } + if (!strcasecmp (*ap, "site")) { + e->eb_site = *ep; + continue; + } + if (!strcasecmp (*ap, "directory")) { + e->eb_dir = *ep; + continue; + } + if (!strcasecmp (*ap, "mode")) { + e->eb_mode = *ep; + continue; + } + if (!strcasecmp (*ap, "size")) { + sscanf (*ep, "%lu", &e->eb_size); + continue; + } + if (!strcasecmp (*ap, "server")) { + e->eb_server = *ep; + continue; + } + if (!strcasecmp (*ap, "subject")) { + e->eb_subject = *ep; + continue; + } + if (composing && !strcasecmp (*ap, "body")) { + e->eb_body = getcpy (*ep); + continue; + } + } + + if (!e->eb_access) { + advise (NULL, + "invalid parameters for \"%s/%s\" type in message %s's %s field", + ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); + return NOTOK; + } + + return OK; +} + + +/* + * APPLICATION + */ + +static int +InitApplication (CT ct) +{ + struct k2v *kv; + CI ci = &ct->c_ctinfo; + + /* match subtype */ + for (kv = SubApplication; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + return OK; +} + + +/* + * Set up structures for placing unencoded + * content when building parts. + */ + +static int +init_decoded_content (CT ct) +{ + CE ce; + + if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) + adios (NULL, "out of memory"); + + ct->c_cefile = ce; + ct->c_ceopenfnx = open7Bit; /* since unencoded */ + ct->c_ceclosefnx = close_encoding; + ct->c_cesizefnx = NULL; /* since unencoded */ + + return OK; +} + + +/* + * TRANSFER ENCODINGS + */ + +static int +init_encoding (CT ct, OpenCEFunc openfnx) +{ + CE ce; + + if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) + adios (NULL, "out of memory"); + + ct->c_cefile = ce; + ct->c_ceopenfnx = openfnx; + ct->c_ceclosefnx = close_encoding; + ct->c_cesizefnx = size_encoding; + + return OK; +} + + +static void +close_encoding (CT ct) +{ + CE ce; + + if (!(ce = ct->c_cefile)) + return; + + if (ce->ce_fp) { + fclose (ce->ce_fp); + ce->ce_fp = NULL; + } +} + + +static unsigned long +size_encoding (CT ct) +{ + int fd; + unsigned long size; + char *file; + CE ce; + struct stat st; + + if (!(ce = ct->c_cefile)) + return (ct->c_end - ct->c_begin); + + if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK) + return (long) st.st_size; + + if (ce->ce_file) { + if (stat (ce->ce_file, &st) != NOTOK) + return (long) st.st_size; + else + return 0L; + } + + if (ct->c_encoding == CE_EXTERNAL) + return (ct->c_end - ct->c_begin); + + file = NULL; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return (ct->c_end - ct->c_begin); + + if (fstat (fd, &st) != NOTOK) + size = (long) st.st_size; + else + size = 0L; + + (*ct->c_ceclosefnx) (ct); + return size; +} + + +/* + * BASE64 + */ + +static unsigned char b642nib[0x80] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +static int +InitBase64 (CT ct) +{ + return init_encoding (ct, openBase64); +} + + +static int +openBase64 (CT ct, char **file) +{ + int bitno, cc, digested; + int fd, len, skip; + unsigned long bits; + unsigned char value, *b, *b1, *b2, *b3; + char *cp, *ep, buffer[BUFSIZ]; + CE ce; + MD5_CTX mdContext; + + b = (unsigned char *) &bits; + b1 = &b[endian > 0 ? 1 : 2]; + b2 = &b[endian > 0 ? 2 : 1]; + b3 = &b[endian > 0 ? 3 : 0]; + + ce = ct->c_cefile; + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_to_go; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_to_go; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if ((len = ct->c_end - ct->c_begin) < 0) + adios (NULL, "internal error(1)"); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + + if ((digested = ct->c_digested)) + MD5Init (&mdContext); + + bitno = 18; + bits = 0L; + skip = 0; + + lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); + while (len > 0) { + switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { + case NOTOK: + content_error (ct->c_file, ct, "error reading from"); + goto clean_up; + + case OK: + content_error (NULL, ct, "premature eof"); + goto clean_up; + + default: + if (cc > len) + cc = len; + len -= cc; + + for (ep = (cp = buffer) + cc; cp < ep; cp++) { + switch (*cp) { + default: + if (isspace (*cp)) + break; + if (skip || (*cp & 0x80) + || (value = b642nib[*cp & 0x7f]) > 0x3f) { + if (debugsw) { + fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n", + *cp, + (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)), + skip); + } + content_error (NULL, ct, + "invalid BASE64 encoding -- continuing"); + continue; + } + + bits |= value << bitno; +test_end: + if ((bitno -= 6) < 0) { + putc ((char) *b1, ce->ce_fp); + if (digested) + MD5Update (&mdContext, b1, 1); + if (skip < 2) { + putc ((char) *b2, ce->ce_fp); + if (digested) + MD5Update (&mdContext, b2, 1); + if (skip < 1) { + putc ((char) *b3, ce->ce_fp); + if (digested) + MD5Update (&mdContext, b3, 1); + } + } + + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, + "error writing to"); + goto clean_up; + } + bitno = 18, bits = 0L, skip = 0; + } + break; + + case '=': + if (++skip > 3) + goto self_delimiting; + goto test_end; + } + } + } + } + + if (bitno != 18) { + if (debugsw) + fprintf (stderr, "premature ending (bitno %d)\n", bitno); + + content_error (NULL, ct, "invalid BASE64 encoding"); + goto clean_up; + } + +self_delimiting: + fseek (ct->c_fp, 0L, SEEK_SET); + + if (fflush (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + + if (digested) { + unsigned char digest[16]; + + MD5Final (digest, &mdContext); + if (memcmp((char *) digest, (char *) ct->c_digest, + sizeof(digest) / sizeof(digest[0]))) + content_error (NULL, ct, + "content integrity suspect (digest mismatch) -- continuing"); + else + if (debugsw) + fprintf (stderr, "content integrity confirmed\n"); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + +ready_to_go: + *file = ce->ce_file; + return fileno (ce->ce_fp); + +clean_up: + free_encoding (ct, 0); + return NOTOK; +} + + +/* + * QUOTED PRINTABLE + */ + +static char hex2nib[0x80] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +static int +InitQuoted (CT ct) +{ + return init_encoding (ct, openQuoted); +} + + +static int +openQuoted (CT ct, char **file) +{ + int cc, digested, len, quoted; + char *cp, *ep; + char buffer[BUFSIZ]; + unsigned char mask; + CE ce; + MD5_CTX mdContext; + + ce = ct->c_cefile; + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_to_go; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_to_go; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if ((len = ct->c_end - ct->c_begin) < 0) + adios (NULL, "internal error(2)"); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + + if ((digested = ct->c_digested)) + MD5Init (&mdContext); + + quoted = 0; +#ifdef lint + mask = 0; +#endif + + fseek (ct->c_fp, ct->c_begin, SEEK_SET); + while (len > 0) { + char *dp; + + if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) { + content_error (NULL, ct, "premature eof"); + goto clean_up; + } + + if ((cc = strlen (buffer)) > len) + cc = len; + len -= cc; + + for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--) + if (!isspace (*ep)) + break; + *++ep = '\n', ep++; + + for (; cp < ep; cp++) { + if (quoted) { + if (quoted > 1) { + if (!isxdigit (*cp)) { +invalid_hex: + dp = "expecting hexidecimal-digit"; + goto invalid_encoding; + } + mask <<= 4; + mask |= hex2nib[*cp & 0x7f]; + putc (mask, ce->ce_fp); + if (digested) + MD5Update (&mdContext, &mask, 1); + } else { + switch (*cp) { + case ':': + putc (*cp, ce->ce_fp); + if (digested) + MD5Update (&mdContext, (unsigned char *) ":", 1); + break; + + default: + if (!isxdigit (*cp)) + goto invalid_hex; + mask = hex2nib[*cp & 0x7f]; + quoted = 2; + continue; + } + } + + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + quoted = 0; + continue; + } + + switch (*cp) { + default: + if (*cp < '!' || *cp > '~') { + int i; + dp = "expecting character in range [!..~]"; + +invalid_encoding: + i = strlen (invo_name) + 2; + content_error (NULL, ct, + "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x", + dp, i, i, "", *cp); + goto clean_up; + } + /* and fall...*/ + case ' ': + case '\t': + case '\n': + putc (*cp, ce->ce_fp); + if (digested) { + if (*cp == '\n') + MD5Update (&mdContext, (unsigned char *) "\r\n",2); + else + MD5Update (&mdContext, (unsigned char *) cp, 1); + } + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + break; + + case '=': + if (*++cp != '\n') { + quoted = 1; + cp--; + } + break; + } + } + } + if (quoted) { + content_error (NULL, ct, + "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting"); + goto clean_up; + } + + fseek (ct->c_fp, 0L, SEEK_SET); + + if (fflush (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + + if (digested) { + unsigned char digest[16]; + + MD5Final (digest, &mdContext); + if (memcmp((char *) digest, (char *) ct->c_digest, + sizeof(digest) / sizeof(digest[0]))) + content_error (NULL, ct, + "content integrity suspect (digest mismatch) -- continuing"); + else + if (debugsw) + fprintf (stderr, "content integrity confirmed\n"); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + +ready_to_go: + *file = ce->ce_file; + return fileno (ce->ce_fp); + +clean_up: + free_encoding (ct, 0); + return NOTOK; +} + + +/* + * 7BIT + */ + +static int +Init7Bit (CT ct) +{ + if (init_encoding (ct, open7Bit) == NOTOK) + return NOTOK; + + ct->c_cesizefnx = NULL; /* no need to decode for real size */ + return OK; +} + + +static int +open7Bit (CT ct, char **file) +{ + int cc, fd, len; + char buffer[BUFSIZ]; + CE ce; + + ce = ct->c_cefile; + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_to_go; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_to_go; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if (ct->c_type == CT_MULTIPART) { + char **ap, **ep; + CI ci = &ct->c_ctinfo; + + len = 0; + fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype); + len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type) + + 1 + strlen (ci->ci_subtype); + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + putc (';', ce->ce_fp); + len++; + + snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep); + + if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) { + fputs ("\n\t", ce->ce_fp); + len = 8; + } else { + putc (' ', ce->ce_fp); + len++; + } + fprintf (ce->ce_fp, "%s", buffer); + len += cc; + } + + if (ci->ci_comment) { + if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) { + fputs ("\n\t", ce->ce_fp); + len = 8; + } + else { + putc (' ', ce->ce_fp); + len++; + } + fprintf (ce->ce_fp, "(%s)", ci->ci_comment); + len += cc; + } + fprintf (ce->ce_fp, "\n"); + if (ct->c_id) + fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id); + if (ct->c_descr) + fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr); + fprintf (ce->ce_fp, "\n"); + } + + if ((len = ct->c_end - ct->c_begin) < 0) + adios (NULL, "internal error(3)"); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + + lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); + while (len > 0) + switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { + case NOTOK: + content_error (ct->c_file, ct, "error reading from"); + goto clean_up; + + case OK: + content_error (NULL, ct, "premature eof"); + goto clean_up; + + default: + if (cc > len) + cc = len; + len -= cc; + + fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp); + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + } + + fseek (ct->c_fp, 0L, SEEK_SET); + + if (fflush (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + +ready_to_go: + *file = ce->ce_file; + return fileno (ce->ce_fp); + +clean_up: + free_encoding (ct, 0); + return NOTOK; +} + + +/* + * External + */ + +static int +openExternal (CT ct, CT cb, CE ce, char **file, int *fd) +{ + char cachefile[BUFSIZ]; + + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_already; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_already; + } + + if (find_cache (ct, rcachesw, (int *) 0, cb->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + if ((ce->ce_fp = fopen (cachefile, "r"))) { + ce->ce_file = getcpy (cachefile); + ce->ce_unlink = 0; + goto ready_already; + } else { + admonish (cachefile, "unable to fopen for reading"); + } + } + + return OK; + +ready_already: + *file = ce->ce_file; + *fd = fileno (ce->ce_fp); + return DONE; +} + +/* + * File + */ + +static int +InitFile (CT ct) +{ + return init_encoding (ct, openFile); +} + + +static int +openFile (CT ct, char **file) +{ + int fd, cachetype; + char cachefile[BUFSIZ]; + struct exbody *e = ct->c_ctexbody; + CE ce = ct->c_cefile; + + switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { + case NOTOK: + return NOTOK; + + case OK: + break; + + case DONE: + return fd; + } + + if (!e->eb_name) { + content_error (NULL, ct, "missing name parameter"); + return NOTOK; + } + + ce->ce_file = getcpy (e->eb_name); + ce->ce_unlink = 0; + + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + + if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) + && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + int mask; + FILE *fp; + + mask = umask (cachetype ? ~m_gmprot () : 0222); + if ((fp = fopen (cachefile, "w"))) { + int cc; + char buffer[BUFSIZ]; + FILE *gp = ce->ce_fp; + + fseek (gp, 0L, SEEK_SET); + + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) + > 0) + fwrite (buffer, sizeof(*buffer), cc, fp); + fflush (fp); + + if (ferror (gp)) { + admonish (ce->ce_file, "error reading"); + unlink (cachefile); + } + else + if (ferror (fp)) { + admonish (cachefile, "error writing"); + unlink (cachefile); + } + fclose (fp); + } + umask (mask); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + *file = ce->ce_file; + return fileno (ce->ce_fp); +} + +/* + * FTP + */ + +static int +InitFTP (CT ct) +{ + return init_encoding (ct, openFTP); +} + + +static int +openFTP (CT ct, char **file) +{ + int cachetype, caching, fd; + int len, buflen; + char *bp, *ftp, *user, *pass; + char buffer[BUFSIZ], cachefile[BUFSIZ]; + struct exbody *e; + CE ce; + static char *username = NULL; + static char *password = NULL; + + e = ct->c_ctexbody; + ce = ct->c_cefile; + + if ((ftp = context_find (nmhaccessftp)) && !*ftp) + ftp = NULL; + +#ifndef BUILTIN_FTP + if (!ftp) + return NOTOK; +#endif + + switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { + case NOTOK: + return NOTOK; + + case OK: + break; + + case DONE: + return fd; + } + + if (!e->eb_name || !e->eb_site) { + content_error (NULL, ct, "missing %s parameter", + e->eb_name ? "site": "name"); + return NOTOK; + } + + if (xpid) { + if (xpid < 0) + xpid = -xpid; + pidcheck (pidwait (xpid, NOTOK)); + xpid = 0; + } + + /* Get the buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + /* + * Construct the query message for user + */ + snprintf (bp, buflen, "Retrieve %s", e->eb_name); + len = strlen (bp); + bp += len; + buflen -= len; + + if (e->eb_partno) { + snprintf (bp, buflen, " (content %s)", e->eb_partno); + len = strlen (bp); + bp += len; + buflen -= len; + } + + snprintf (bp, buflen, "\n using %sFTP from site %s", + e->eb_flags ? "anonymous " : "", e->eb_site); + len = strlen (bp); + bp += len; + buflen -= len; + + if (e->eb_size > 0) { + snprintf (bp, buflen, " (%lu octets)", e->eb_size); + len = strlen (bp); + bp += len; + buflen -= len; + } + snprintf (bp, buflen, "? "); + + /* + * Now, check the answer + */ + if (!getanswer (buffer)) + return NOTOK; + + if (e->eb_flags) { + user = "anonymous"; + snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ()); + pass = buffer; + } else { + ruserpass (e->eb_site, &username, &password); + user = username; + pass = password; + } + + ce->ce_unlink = (*file == NULL); + caching = 0; + cachefile[0] = '\0'; + if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) + && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + if (*file == NULL) { + ce->ce_unlink = 0; + caching = 1; + } + } + + if (*file) + ce->ce_file = add (*file, NULL); + else if (caching) + ce->ce_file = add (cachefile, NULL); + else + ce->ce_file = add (m_scratch ("", tmp), NULL); + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + +#ifdef BUILTIN_FTP + if (ftp) +#endif + { + int child_id, i, vecp; + char *vec[9]; + + vecp = 0; + vec[vecp++] = r1bindex (ftp, '/'); + vec[vecp++] = e->eb_site; + vec[vecp++] = user; + vec[vecp++] = pass; + vec[vecp++] = e->eb_dir; + vec[vecp++] = e->eb_name; + vec[vecp++] = ce->ce_file, + vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii") + ? "ascii" : "binary"; + vec[vecp] = NULL; + + fflush (stdout); + + for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + adios ("fork", "unable to"); + /* NOTREACHED */ + + case OK: + close (fileno (ce->ce_fp)); + execvp (ftp, vec); + fprintf (stderr, "unable to exec "); + perror (ftp); + _exit (-1); + /* NOTREACHED */ + + default: + if (pidXwait (child_id, NULL)) { +#ifdef BUILTIN_FTP +losing_ftp: +#endif + username = password = NULL; + ce->ce_unlink = 1; + return NOTOK; + } + break; + } + } +#ifdef BUILTIN_FTP + else + if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name, + ce->ce_file, + e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0) + == NOTOK) + goto losing_ftp; +#endif + + if (cachefile[0]) + if (caching) + chmod (cachefile, cachetype ? m_gmprot () : 0444); + else { + int mask; + FILE *fp; + + mask = umask (cachetype ? ~m_gmprot () : 0222); + if ((fp = fopen (cachefile, "w"))) { + int cc; + FILE *gp = ce->ce_fp; + + fseek (gp, 0L, SEEK_SET); + + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) + > 0) + fwrite (buffer, sizeof(*buffer), cc, fp); + fflush (fp); + + if (ferror (gp)) { + admonish (ce->ce_file, "error reading"); + unlink (cachefile); + } + else + if (ferror (fp)) { + admonish (cachefile, "error writing"); + unlink (cachefile); + } + fclose (fp); + } + umask (mask); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + *file = ce->ce_file; + return fileno (ce->ce_fp); +} + + +/* + * Mail + */ + +static int +InitMail (CT ct) +{ + return init_encoding (ct, openMail); +} + + +static int +openMail (CT ct, char **file) +{ + int child_id, fd, i, vecp; + int len, buflen; + char *bp, buffer[BUFSIZ], *vec[7]; + struct exbody *e = ct->c_ctexbody; + CE ce = ct->c_cefile; + + switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { + case NOTOK: + return NOTOK; + + case OK: + break; + + case DONE: + return fd; + } + + if (!e->eb_server) { + content_error (NULL, ct, "missing server parameter"); + return NOTOK; + } + + if (xpid) { + if (xpid < 0) + xpid = -xpid; + pidcheck (pidwait (xpid, NOTOK)); + xpid = 0; + } + + /* Get buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + /* Now construct query message */ + snprintf (bp, buflen, "Retrieve content"); + len = strlen (bp); + bp += len; + buflen -= len; + + if (e->eb_partno) { + snprintf (bp, buflen, " %s", e->eb_partno); + len = strlen (bp); + bp += len; + buflen -= len; + } + + snprintf (bp, buflen, " by asking %s\n\n%s\n? ", + e->eb_server, + e->eb_subject ? e->eb_subject : e->eb_body); + + /* Now, check answer */ + if (!getanswer (buffer)) + return NOTOK; + + vecp = 0; + vec[vecp++] = r1bindex (mailproc, '/'); + vec[vecp++] = e->eb_server; + vec[vecp++] = "-subject"; + vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request"; + vec[vecp++] = "-body"; + vec[vecp++] = e->eb_body; + vec[vecp] = NULL; + + for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + advise ("fork", "unable to"); + return NOTOK; + + case OK: + execvp (mailproc, vec); + fprintf (stderr, "unable to exec "); + perror (mailproc); + _exit (-1); + /* NOTREACHED */ + + default: + if (pidXwait (child_id, NULL) == OK) + advise (NULL, "request sent"); + break; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + *file = ce->ce_file; + return fileno (ce->ce_fp); +} + + +static char * +fgetstr (char *s, int n, FILE *stream) +{ + char *cp, *ep; + + for (ep = (cp = s) + n; cp < ep; ) { + int i; + + if (!fgets (cp, n, stream)) + return (cp != s ? s : NULL); + if (cp == s && *cp != '#') + return s; + + cp += (i = strlen (cp)) - 1; + if (i <= 1 || *cp-- != '\n' || *cp != '\\') + break; + *cp = '\0'; + n -= (i - 2); + } + + return s; +} + + +/* + * Parse the composition draft for text and directives. + * Do initial setup of Content structure. + */ + +static int +user_content (FILE *in, char *file, char *buf, CT *ctp) +{ + int extrnal, vrsn; + char *cp, **ap; + char buffer[BUFSIZ]; + struct multipart *m; + struct part **pp; + struct stat st; + struct str2init *s2i; + CI ci; + CT ct; + CE ce; + + if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) { + *ctp = NULL; + return OK; + } + + /* allocate basic Content structure */ + if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) + adios (NULL, "out of memory"); + *ctp = ct; + + /* allocate basic structure for handling decoded content */ + init_decoded_content (ct); + ce = ct->c_cefile; + + ci = &ct->c_ctinfo; + set_id (ct, 0); + + /* + * Handle inline text. Check if line + * is one of the following forms: + * + * 1) doesn't begin with '#' (implicit directive) + * 2) begins with "##" (implicit directive) + * 3) begins with "#<" + */ + if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') { + int headers; + int inlineD; + long pos; + char content[BUFSIZ]; + FILE *out; + + /* use a temp file to collect the plain text lines */ + ce->ce_file = add (m_tmpfil (invo_name), NULL); + ce->ce_unlink = 1; + + if ((out = fopen (ce->ce_file, "w")) == NULL) + adios (ce->ce_file, "unable to open for writing"); + + if (buf[0] == '#' && buf[1] == '<') { + strncpy (content, buf + 2, sizeof(content)); + inlineD = 1; + goto rock_and_roll; + } else { + inlineD = 0; + } + + /* the directive is implicit */ + strncpy (content, "text/plain", sizeof(content)); + headers = 0; + strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer)); + for (;;) { + int i; + + if (headers >= 0 && uprf (buffer, DESCR_FIELD) + && buffer[i = strlen (DESCR_FIELD)] == ':') { + headers = 1; + +again_descr: + ct->c_descr = add (buffer + i + 1, ct->c_descr); + if (!fgetstr (buffer, sizeof(buffer) - 1, in)) + adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD); + switch (buffer[0]) { + case ' ': + case '\t': + i = -1; + goto again_descr; + + case '#': + adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD); + /* NOTREACHED */ + + default: + break; + } + } + + if (headers != 1 || buffer[0] != '\n') + fputs (buffer, out); + +rock_and_roll: + headers = -1; + pos = ftell (in); + if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL) + break; + if (buffer[0] == '#') { + char *bp; + + if (buffer[1] != '#') + break; + for (cp = (bp = buffer) + 1; *cp; cp++) + *bp++ = *cp; + *bp = '\0'; + } + } + + if (listsw) + ct->c_end = ftell (out); + fclose (out); + + /* parse content type */ + if (get_ctinfo (content, ct, inlineD) == NOTOK) + done (1); + + for (s2i = str2cts; s2i->si_key; s2i++) + if (!strcasecmp (ci->ci_type, s2i->si_key)) + break; + if (!s2i->si_key && !uprf (ci->ci_type, "X-")) + s2i++; + + /* + * check type specified (possibly implicitly) + */ + switch (ct->c_type = s2i->si_val) { + case CT_MESSAGE: + if (!strcasecmp (ci->ci_subtype, "rfc822")) { + ct->c_encoding = CE_7BIT; + goto call_init; + } + /* else fall... */ + case CT_MULTIPART: + adios (NULL, "it doesn't make sense to define an in-line %s content", + ct->c_type == CT_MESSAGE ? "message" : "multipart"); + /* NOTREACHED */ + + default: +call_init: + if ((ct->c_ctinitfnx = s2i->si_init)) + (*ct->c_ctinitfnx) (ct); + break; + } + + if (cp) + fseek (in, pos, SEEK_SET); + return OK; + } + + /* + * If we've reached this point, the next line + * must be some type of explicit directive. + */ + + /* check if directive is external-type */ + extrnal = (buf[1] == '@'); + + /* parse directive */ + if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK) + done (1); + + /* check directive against the list of MIME types */ + for (s2i = str2cts; s2i->si_key; s2i++) + if (!strcasecmp (ci->ci_type, s2i->si_key)) + break; + + /* + * Check if the directive specified a valid type. + * This will happen if it was one of the following forms: + * + * #type/subtype (or) + * #@type/subtype + */ + if (s2i->si_key) { + if (!ci->ci_subtype) + adios (NULL, "missing subtype in \"#%s\"", ci->ci_type); + + switch (ct->c_type = s2i->si_val) { + case CT_MULTIPART: + adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", + ci->ci_type, ci->ci_subtype); + /* NOTREACHED */ + + case CT_MESSAGE: + if (!strcasecmp (ci->ci_subtype, "partial")) + adios (NULL, "sorry, \"#%s/%s\" isn't supported", + ci->ci_type, ci->ci_subtype); + if (!strcasecmp (ci->ci_subtype, "external-body")) + adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", + ci->ci_type, ci->ci_subtype); +use_forw: + adios (NULL, + "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", + ci->ci_type, ci->ci_subtype); + /* NOTREACHED */ + + default: + if ((ct->c_ctinitfnx = s2i->si_init)) + (*ct->c_ctinitfnx) (ct); + break; + } + + /* + * #@type/subtype (external types directive) + */ + if (extrnal) { + struct exbody *e; + CT p; + + if (!ci->ci_magic) + adios (NULL, "need external information for \"#@%s/%s\"", + ci->ci_type, ci->ci_subtype); + p = ct; + + snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic); + free (ci->ci_magic); + ci->ci_magic = NULL; + + /* + * Since we are using the current Content structure to + * hold information about the type of the external + * reference, we need to create another Content structure + * for the message/external-body to wrap it in. + */ + if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) + adios (NULL, "out of memory"); + *ctp = ct; + ci = &ct->c_ctinfo; + if (get_ctinfo (buffer, ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MESSAGE; + ct->c_subtype = MESSAGE_EXTERNAL; + + if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) e; + + e->eb_parent = ct; + e->eb_content = p; + p->c_ctexbody = e; + + if (params_external (ct, 1) == NOTOK) + done (1); + + return OK; + } + + /* Handle [file] argument */ + if (ci->ci_magic) { + /* check if specifies command to execute */ + if (*ci->ci_magic == '|' || *ci->ci_magic == '!') { + for (cp = ci->ci_magic + 1; isspace (*cp); cp++) + continue; + if (!*cp) + adios (NULL, "empty pipe command for #%s directive", ci->ci_type); + cp = add (cp, NULL); + free (ci->ci_magic); + ci->ci_magic = cp; + } else { + /* record filename of decoded contents */ + ce->ce_file = ci->ci_magic; + if (access (ce->ce_file, R_OK) == NOTOK) + adios ("reading", "unable to access %s for", ce->ce_file); + if (listsw && stat (ce->ce_file, &st) != NOTOK) + ct->c_end = (long) st.st_size; + ci->ci_magic = NULL; + } + return OK; + } + + /* + * No [file] argument, so check profile for + * method to compose content. + */ + snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) == NULL || *cp == '\0') { + snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) == NULL || *cp == '\0') { + content_error (NULL, ct, "don't know how to compose content"); + done (1); + } + } + ci->ci_magic = add (cp, NULL); + return OK; + } + + if (extrnal) + adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type); + + /* + * Message directive + * #forw [+folder] [msgs] + */ + if (!strcasecmp (ci->ci_type, "forw")) { + int msgnum; + char *folder, *arguments[MAXARGS]; + struct msgs *mp; + + if (ci->ci_magic) { + ap = brkstring (ci->ci_magic, " ", "\n"); + copyip (ap, arguments, MAXARGS); + } else { + arguments[0] = "cur"; + arguments[1] = NULL; + } + folder = NULL; + + /* search the arguments for a folder name */ + for (ap = arguments; *ap; ap++) { + cp = *ap; + if (*cp == '+' || *cp == '@') + if (folder) + adios (NULL, "only one folder per #forw directive"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } + + /* else, use the current folder */ + if (!folder) + folder = add (getfolder (1), NULL); + + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + for (ap = arguments; *ap; ap++) { + cp = *ap; + if (*cp != '+' && *cp != '@') + if (!m_convert (mp, cp)) + done (1); + } + free (folder); + free_ctinfo (ct); + + /* + * If there is more than one message to include, make this + * a content of type "multipart/digest" and insert each message + * as a subpart. If there is only one message, then make this + * a content of type "message/rfc822". + */ + if (mp->numsel > 1) { + /* we are forwarding multiple messages */ + if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_DIGEST; + + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + struct part *part; + CT p; + CE pe; + + if ((p = (CT) calloc (1, sizeof(*p))) == NULL) + adios (NULL, "out of memory"); + init_decoded_content (p); + pe = p->c_cefile; + if (get_ctinfo ("message/rfc822", p, 0) == NOTOK) + done (1); + p->c_type = CT_MESSAGE; + p->c_subtype = MESSAGE_RFC822; + + snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum); + pe->ce_file = add (buffer, NULL); + if (listsw && stat (pe->ce_file, &st) != NOTOK) + p->c_end = (long) st.st_size; + + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + } + } else { + /* we are forwarding one message */ + if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MESSAGE; + ct->c_subtype = MESSAGE_RFC822; + + msgnum = mp->lowsel; + snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum); + ce->ce_file = add (buffer, NULL); + if (listsw && stat (ce->ce_file, &st) != NOTOK) + ct->c_end = (long) st.st_size; + } + + folder_free (mp); /* free folder/message structure */ + return OK; + } + + /* + * #end + */ + if (!strcasecmp (ci->ci_type, "end")) { + free_content (ct); + *ctp = NULL; + return DONE; + } + + /* + * #begin [ alternative | parallel ] + */ + if (!strcasecmp (ci->ci_type, "begin")) { + if (!ci->ci_magic) { + vrsn = MULTI_MIXED; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (!strcasecmp (ci->ci_magic, "alternative")) { + vrsn = MULTI_ALTERNATE; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (!strcasecmp (ci->ci_magic, "parallel")) { + vrsn = MULTI_PARALLEL; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (uprf (ci->ci_magic, "digest")) { + goto use_forw; + } else { + vrsn = MULTI_UNKNOWN; + cp = ci->ci_magic; + } + + free_ctinfo (ct); + snprintf (buffer, sizeof(buffer), "multipart/%s", cp); + if (get_ctinfo (buffer, ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = vrsn; + + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + + pp = &m->mp_parts; + while (fgetstr (buffer, sizeof(buffer) - 1, in)) { + struct part *part; + CT p; + + if (user_content (in, file, buffer, &p) == DONE) { + if (!m->mp_parts) + adios (NULL, "empty \"#begin ... #end\" sequence"); + return OK; + } + if (!p) + continue; + + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + admonish (NULL, "premature end-of-file, missing #end"); + return OK; + } + + /* + * Unknown directive + */ + adios (NULL, "unknown directive \"#%s\"", ci->ci_type); + return NOTOK; /* NOT REACHED */ +} + + +static void +set_id (CT ct, int top) +{ + char msgid[BUFSIZ]; + static int partno; + static time_t clock = 0; + static char *msgfmt; + + if (clock == 0) { + time (&clock); + snprintf (msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n", + (int) getpid(), (long) clock, LocalName()); + partno = 0; + msgfmt = getcpy(msgid); + } + snprintf (msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno); + ct->c_id = getcpy (msgid); +} + + +static char ebcdicsafe[0x100] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +/* + * Fill out, or expand the various contents in the composition + * draft. Read-in any necessary files. Parse and execute any + * commands specified by profile composition strings. + */ + +static int +compose_content (CT ct) +{ + CE ce = ct->c_cefile; + + switch (ct->c_type) { + case CT_MULTIPART: + { + int partnum; + char *pp; + char partnam[BUFSIZ]; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + + if (ct->c_partno) { + snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno); + pp = partnam + strlen (partnam); + } else { + pp = partnam; + } + + /* first, we call compose_content on all the subparts */ + for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) { + CT p = part->mp_part; + + sprintf (pp, "%d", partnum); + p->c_partno = add (partnam, NULL); + if (compose_content (p) == NOTOK) + return NOTOK; + } + + /* + * If the -rfc934mode switch is given, then check all + * the subparts of a multipart/digest. If they are all + * message/rfc822, then mark this content and all + * subparts with the rfc934 compatibility mode flag. + */ + if (rfc934sw && ct->c_subtype == MULTI_DIGEST) { + int is934 = 1; + + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + if (p->c_subtype != MESSAGE_RFC822) { + is934 = 0; + break; + } + } + ct->c_rfc934 = is934; + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + if ((p->c_rfc934 = is934)) + p->c_end++; + } + } + + if (listsw) { + ct->c_end = (partnum = strlen (prefix) + 2) + 2; + if (ct->c_rfc934) + ct->c_end += 1; + + for (part = m->mp_parts; part; part = part->mp_next) + ct->c_end += part->mp_part->c_end + partnum; + } + } + break; + + case CT_MESSAGE: + /* Nothing to do for type message */ + break; + + /* + * Discrete types (text/application/audio/image/video) + */ + default: + if (!ce->ce_file) { + pid_t child_id; + int i, xstdout, len, buflen; + char *bp, **ap, *cp; + char *vec[4], buffer[BUFSIZ]; + FILE *out; + CI ci = &ct->c_ctinfo; + + if (!(cp = ci->ci_magic)) + adios (NULL, "internal error(5)"); + + ce->ce_file = add (m_tmpfil (invo_name), NULL); + ce->ce_unlink = 1; + + xstdout = 0; + + /* Get buffer ready to go */ + bp = buffer; + bp[0] = '\0'; + buflen = sizeof(buffer); + + /* + * Parse composition string into buffer + */ + for ( ; *cp; cp++) { + if (*cp == '%') { + switch (*++cp) { + case 'a': + { + /* insert parameters from directive */ + char **ep; + char *s = ""; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + len = strlen (bp); + bp += len; + buflen -= len; + s = " "; + } + } + break; + + case 'F': + /* %f, and stdout is not-redirected */ + xstdout = 1; + /* and fall... */ + + case 'f': + /* + * insert temporary filename where + * content should be written + */ + snprintf (bp, buflen, "%s", ce->ce_file); + break; + + case 's': + /* insert content subtype */ + strncpy (bp, ci->ci_subtype, buflen); + break; + + case '%': + /* insert character % */ + goto raw; + + default: + *bp++ = *--cp; + *bp = '\0'; + buflen--; + continue; + } + len = strlen (bp); + bp += len; + buflen -= len; + } else { +raw: + *bp++ = *cp; + *bp = '\0'; + buflen--; + } + } + + if (verbosw) + printf ("composing content %s/%s from command\n\t%s\n", + ci->ci_type, ci->ci_subtype, buffer); + + fflush (stdout); /* not sure if need for -noverbose */ + + vec[0] = "/bin/sh"; + vec[1] = "-c"; + vec[2] = buffer; + vec[3] = NULL; + + if ((out = fopen (ce->ce_file, "w")) == NULL) + adios (ce->ce_file, "unable to open for writing"); + + for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + adios ("fork", "unable to fork"); + /* NOTREACHED */ + + case OK: + if (!xstdout) + dup2 (fileno (out), 1); + close (fileno (out)); + execvp ("/bin/sh", vec); + fprintf (stderr, "unable to exec "); + perror ("/bin/sh"); + _exit (-1); + /* NOTREACHED */ + + default: + fclose (out); + if (pidXwait(child_id, NULL)) + done (1); + break; + } + } + + /* Check size of file */ + if (listsw && ct->c_end == 0L) { + struct stat st; + + if (stat (ce->ce_file, &st) != NOTOK) + ct->c_end = (long) st.st_size; + } + break; + } + + return OK; +} + + +/* + * Scan the content. + * + * 1) choose a transfer encoding. + * 2) check for clashes with multipart boundary string. + * 3) for text content, figure out which character set is being used. + * + * If there is a clash with one of the contents and the multipart boundary, + * this function will exit with NOTOK. This will cause the scanning process + * to be repeated with a different multipart boundary. It is possible + * (although highly unlikely) that this scan will be repeated multiple times. + */ + +static int +scan_content (CT ct) +{ + int len; + int check8bit, contains8bit = 0; /* check if contains 8bit data */ + int checklinelen, linelen = 0; /* check for long lines */ + int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary */ + int checklinespace, linespace = 0; /* check if any line ends with space */ + int checkebcdic, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */ + char *cp, buffer[BUFSIZ]; + struct text *t; + FILE *in; + CE ce = ct->c_cefile; + + /* + * handle multipart by scanning all subparts + * and then checking their encoding. + */ + if (ct->c_type == CT_MULTIPART) { + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + + /* initially mark the domain of enclosing multipart as 7bit */ + ct->c_encoding = CE_7BIT; + + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + if (scan_content (p) == NOTOK) /* choose encoding for subpart */ + return NOTOK; + + /* if necessary, enlarge encoding for enclosing multipart */ + if (p->c_encoding == CE_BINARY) + ct->c_encoding = CE_BINARY; + if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY) + ct->c_encoding = CE_8BIT; + } + + return OK; + } + + /* + * Decide what to check while scanning this content. + */ + switch (ct->c_type) { + case CT_TEXT: + check8bit = 1; + checkboundary = 1; + if (ct->c_subtype == TEXT_PLAIN) { + checkebcdic = 0; + checklinelen = 0; + checklinespace = 0; + } else { + checkebcdic = ebcdicsw; + checklinelen = 1; + checklinespace = 1; + } + break; + + case CT_APPLICATION: + check8bit = 1; + checkebcdic = ebcdicsw; + checklinelen = 1; + checklinespace = 1; + checkboundary = 1; + break; + + case CT_MESSAGE: + check8bit = 0; + checkebcdic = 0; + checklinelen = 0; + checklinespace = 0; + + /* don't check anything for message/external */ + if (ct->c_subtype == MESSAGE_EXTERNAL) + checkboundary = 0; + else + checkboundary = 1; + break; + + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + /* + * Don't check anything for these types, + * since we are forcing use of base64. + */ + check8bit = 0; + checkebcdic = 0; + checklinelen = 0; + checklinespace = 0; + checkboundary = 0; + break; + } + + /* + * Scan the unencoded content + */ + if (check8bit || checklinelen || checklinespace || checkboundary) { + if ((in = fopen (ce->ce_file, "r")) == NULL) + adios (ce->ce_file, "unable to open for reading"); + len = strlen (prefix); + + while (fgets (buffer, sizeof(buffer) - 1, in)) { + /* + * Check for 8bit data. + */ + if (check8bit) { + for (cp = buffer; *cp; cp++) { + if (!isascii (*cp)) { + contains8bit = 1; + check8bit = 0; /* no need to keep checking */ + } + /* + * Check if character is ebcdic-safe. We only check + * this if also checking for 8bit data. + */ + if (checkebcdic && !ebcdicsafe[*cp & 0xff]) { + ebcdicunsafe = 1; + checkebcdic = 0; /* no need to keep checking */ + } + } + } + + /* + * Check line length. + */ + if (checklinelen && (strlen (buffer) > CPERLIN + 1)) { + linelen = 1; + checklinelen = 0; /* no need to keep checking */ + } + + /* + * Check if line ends with a space. + */ + if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace (*cp)) { + linespace = 1; + checklinespace = 0; /* no need to keep checking */ + } + + /* + * Check if content contains a line that clashes + * with our standard boundary for multipart messages. + */ + if (checkboundary && buffer[0] == '-' && buffer[1] == '-') { + for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--) + if (!isspace (*cp)) + break; + *++cp = '\0'; + if (!strncmp(buffer + 2, prefix, len) && isdigit(buffer[2 + len])) { + boundaryclash = 1; + checkboundary = 0; /* no need to keep checking */ + } + } + } + fclose (in); + } + + /* + * Decide which transfer encoding to use. + */ + switch (ct->c_type) { + case CT_TEXT: + /* + * If the text content didn't specify a character + * set, we need to figure out which one was used. + */ + t = (struct text *) ct->c_ctparams; + if (t->tx_charset == CHARSET_UNSPECIFIED) { + CI ci = &ct->c_ctinfo; + char **ap, **ep; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) + continue; + + if (contains8bit) { + t->tx_charset = CHARSET_UNKNOWN; + *ap = concat ("charset=", write_charset_8bit(), NULL); + } else { + t->tx_charset = CHARSET_USASCII; + *ap = add ("charset=us-ascii", NULL); + } + + cp = strchr(*ap++, '='); + *ap = NULL; + *cp++ = '\0'; + *ep = cp; + } + + if (contains8bit || ebcdicunsafe || linelen || linespace || checksw) + ct->c_encoding = CE_QUOTED; + else + ct->c_encoding = CE_7BIT; + break; + + case CT_APPLICATION: + /* For application type, use base64, except when postscript */ + if (contains8bit || ebcdicunsafe || linelen || linespace || checksw) + ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT) + ? CE_QUOTED : CE_BASE64; + else + ct->c_encoding = CE_7BIT; + break; + + case CT_MESSAGE: + ct->c_encoding = CE_7BIT; + break; + + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + /* For audio, image, and video contents, just use base64 */ + ct->c_encoding = CE_BASE64; + break; + } + + return (boundaryclash ? NOTOK : OK); +} + + +/* + * Scan the content structures, and build header + * fields that will need to be output into the + * message. + */ + +static int +build_headers (CT ct) +{ + int cc, mailbody, len; + char **ap, **ep; + char *np, *vp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* + * If message is type multipart, then add the multipart + * boundary to the list of attribute/value pairs. + */ + if (ct->c_type == CT_MULTIPART) { + char *cp; + static int level = 0; /* store nesting level */ + + ap = ci->ci_attrs; + ep = ci->ci_values; + snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++); + cp = strchr(*ap++ = add (buffer, NULL), '='); + *ap = NULL; + *cp++ = '\0'; + *ep = cp; + } + + /* + * Skip the output of Content-Type, parameters, content + * description, and Content-ID if the content is of type + * "message" and the rfc934 compatibility flag is set + * (which means we are inside multipart/digest and the + * switch -rfc934mode was given). + */ + if (ct->c_type == CT_MESSAGE && ct->c_rfc934) + goto skip_headers; + + /* + * output the content type and subtype + */ + np = add (TYPE_FIELD, NULL); + vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL); + + /* keep track of length of line */ + len = strlen (TYPE_FIELD) + strlen (ci->ci_type) + + strlen (ci->ci_subtype) + 3; + + mailbody = ct->c_type == CT_MESSAGE + && ct->c_subtype == MESSAGE_EXTERNAL + && ((struct exbody *) ct->c_ctparams)->eb_body; + + /* + * Append the attribute/value pairs to + * the end of the Content-Type line. + */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (mailbody && !strcasecmp (*ap, "body")) + continue; + + vp = add (";", vp); + len++; + + snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep); + if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) { + vp = add ("\n\t", vp); + len = 8; + } else { + vp = add (" ", vp); + len++; + } + vp = add (buffer, vp); + len += cc; + } + + /* + * Append any RFC-822 comment to the end of + * the Content-Type line. + */ + if (ci->ci_comment) { + snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment); + if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) { + vp = add ("\n\t", vp); + len = 8; + } else { + vp = add (" ", vp); + len++; + } + vp = add (buffer, vp); + len += cc; + } + vp = add ("\n", vp); + add_header (ct, np, vp); + + /* + * output the Content-ID + */ + if (ct->c_id) { + np = add (ID_FIELD, NULL); + vp = concat (" ", ct->c_id, NULL); + add_header (ct, np, vp); + } + + /* + * output the Content-Description + */ + if (ct->c_descr) { + np = add (DESCR_FIELD, NULL); + vp = concat (" ", ct->c_descr, NULL); + add_header (ct, np, vp); + } + +skip_headers: + /* + * If this is the internal content structure for a + * "message/external", then we are done with the + * headers (since it has no body). + */ + if (ct->c_ctexbody) + return OK; + + /* + * output the Content-MD5 + */ + if (checksw) { + np = add (MD5_FIELD, NULL); + vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0); + add_header (ct, np, vp); + } + + /* + * output the Content-Transfer-Encoding + */ + switch (ct->c_encoding) { + case CE_7BIT: + /* Nothing to output */ +#if 0 + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "7bit", "\n", NULL); + add_header (ct, np, vp); +#endif + break; + + case CE_8BIT: + if (ct->c_type == CT_MESSAGE) + adios (NULL, "internal error, invalid encoding"); + + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "8bit", "\n", NULL); + add_header (ct, np, vp); + break; + + case CE_QUOTED: + if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART) + adios (NULL, "internal error, invalid encoding"); + + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "quoted-printable", "\n", NULL); + add_header (ct, np, vp); + break; + + case CE_BASE64: + if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART) + adios (NULL, "internal error, invalid encoding"); + + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "base64", "\n", NULL); + add_header (ct, np, vp); + break; + + case CE_BINARY: + if (ct->c_type == CT_MESSAGE) + adios (NULL, "internal error, invalid encoding"); + + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "binary", "\n", NULL); + add_header (ct, np, vp); + break; + + default: + adios (NULL, "unknown transfer encoding in content"); + break; + } + + /* + * Additional content specific header processing + */ + switch (ct->c_type) { + case CT_MULTIPART: + { + struct multipart *m; + struct part *part; + + m = (struct multipart *) ct->c_ctparams; + for (part = m->mp_parts; part; part = part->mp_next) { + CT p; + + p = part->mp_part; + build_headers (p); + } + } + break; + + case CT_MESSAGE: + if (ct->c_subtype == MESSAGE_EXTERNAL) { + struct exbody *e; + + e = (struct exbody *) ct->c_ctparams; + build_headers (e->eb_content); + } + break; + + default: + /* Nothing to do */ + break; + } + + return OK; +} + + +static char nib2b64[0x40+1] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static char * +calculate_digest (CT ct, int asciiP) +{ + int cc; + char buffer[BUFSIZ], *vp, *op; + unsigned char *dp; + unsigned char digest[16]; + unsigned char outbuf[25]; + FILE *in; + MD5_CTX mdContext; + CE ce = ct->c_cefile; + + /* open content */ + if ((in = fopen (ce->ce_file, "r")) == NULL) + adios (ce->ce_file, "unable to open for reading"); + + /* Initialize md5 context */ + MD5Init (&mdContext); + + /* calculate md5 message digest */ + if (asciiP) { + while (fgets (buffer, sizeof(buffer) - 1, in)) { + char c, *cp; + + cp = buffer + strlen (buffer) - 1; + if ((c = *cp) == '\n') + *cp = '\0'; + + MD5Update (&mdContext, (unsigned char *) buffer, + (unsigned int) strlen (buffer)); + + if (c == '\n') + MD5Update (&mdContext, (unsigned char *) "\r\n", 2); + } + } else { + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0) + MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc); + } + + /* md5 finalization. Write digest and zero md5 context */ + MD5Final (digest, &mdContext); + + /* close content */ + fclose (in); + + /* print debugging info */ + if (debugsw) { + unsigned char *ep; + + fprintf (stderr, "MD5 digest="); + for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]); + dp < ep; dp++) + fprintf (stderr, "%02x", *dp & 0xff); + fprintf (stderr, "\n"); + } + + /* encode the digest using base64 */ + for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]); + cc > 0; cc -= 3, op += 4) { + unsigned long bits; + char *bp; + + bits = (*dp++ & 0xff) << 16; + if (cc > 1) { + bits |= (*dp++ & 0xff) << 8; + if (cc > 2) + bits |= *dp++ & 0xff; + } + + for (bp = op + 4; bp > op; bits >>= 6) + *--bp = nib2b64[bits & 0x3f]; + if (cc < 3) { + *(op + 3) = '='; + if (cc < 2) + *(op + 2) = '='; + } + } + + /* null terminate string */ + outbuf[24] = '\0'; + + /* now make copy and return string */ + vp = concat (" ", outbuf, "\n", NULL); + return vp; +} + + +static int +readDigest (CT ct, char *cp) +{ + int bitno, skip; + unsigned long bits; + char *bp = cp; + unsigned char *dp, value, *ep; + unsigned char *b, *b1, *b2, *b3; + + b = (unsigned char *) &bits, + b1 = &b[endian > 0 ? 1 : 2], + b2 = &b[endian > 0 ? 2 : 1], + b3 = &b[endian > 0 ? 3 : 0]; + bitno = 18; + bits = 0L; + skip = 0; + + for (ep = (dp = ct->c_digest) + + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++) + switch (*cp) { + default: + if (skip + || (*cp & 0x80) + || (value = b642nib[*cp & 0x7f]) > 0x3f) { + if (debugsw) + fprintf (stderr, "invalid BASE64 encoding\n"); + return NOTOK; + } + + bits |= value << bitno; +test_end: + if ((bitno -= 6) < 0) { + if (dp + (3 - skip) > ep) + goto invalid_digest; + *dp++ = *b1; + if (skip < 2) { + *dp++ = *b2; + if (skip < 1) + *dp++ = *b3; + } + bitno = 18; + bits = 0L; + skip = 0; + } + break; + + case '=': + if (++skip > 3) + goto self_delimiting; + goto test_end; + } + if (bitno != 18) { + if (debugsw) + fprintf (stderr, "premature ending (bitno %d)\n", bitno); + + return NOTOK; + } +self_delimiting: + if (dp != ep) { +invalid_digest: + if (debugsw) { + while (*cp) + cp++; + fprintf (stderr, "invalid MD5 digest (got %d octets)\n", + cp - bp); + } + + return NOTOK; + } + + if (debugsw) { + fprintf (stderr, "MD5 digest="); + for (dp = ct->c_digest; dp < ep; dp++) + fprintf (stderr, "%02x", *dp & 0xff); + fprintf (stderr, "\n"); + } + + return OK; +} diff --git a/uip/mhcachesbr.c b/uip/mhcachesbr.c new file mode 100644 index 0000000..b372f95 --- /dev/null +++ b/uip/mhcachesbr.c @@ -0,0 +1,443 @@ + +/* + * mhcachesbr.c -- routines to manipulate the MIME content cache + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + + +extern int errno; +extern int debugsw; + +extern pid_t xpid; /* mhshowsbr.c or mhbuildsbr.c */ + +/* cache policies */ +int rcachesw = CACHE_ASK; +int wcachesw = CACHE_ASK; + +/* + * Location of public and private cache. These must + * be set before these routines are called. + */ +char *cache_public; +char *cache_private; + + +/* mhparse.c (OR) mhbuildsbr.c */ +int pidcheck (int); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +int make_intermediates (char *); +void content_error (char *, CT, char *, ...); +void flush_errors (void); + +/* + * prototypes + */ +void cache_all_messages (CT *); +int find_cache (CT, int, int *, char *, char *, int); + +/* + * static prototypes + */ +static void cache_content (CT); +static int find_cache_aux (int, char *, char *, char *, int); +static int find_cache_aux2 (char *, char *, char *, int); + + +/* + * Top level entry point to cache content + * from a group of messages + */ + +void +cache_all_messages (CT *cts) +{ + CT ct, *ctp; + + for (ctp = cts; *ctp; ctp++) { + ct = *ctp; + if (type_ok (ct, 1)) { + cache_content (ct); + if (ct->c_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } + if (ct->c_ceclosefnx) + (*ct->c_ceclosefnx) (ct); + } + } + flush_errors (); +} + + +/* + * Entry point to cache content from external sources. + */ + +static void +cache_content (CT ct) +{ + int cachetype; + char *file, cachefile[BUFSIZ]; + CE ce = ct->c_cefile; + + if (!ct->c_id) { + advise (NULL, "no %s: field in %s", ID_FIELD, ct->c_file); + return; + } + + if (!ce) { + advise (NULL, "unable to decode %s", ct->c_file); + return; + } + +/* THIS NEEDS TO BE FIXED */ +#if 0 + if (ct->c_ceopenfnx == openMail) { + advise (NULL, "a radish may no know Greek, but I do..."); + return; + } +#endif + + if (find_cache (NULL, wcachesw != CACHE_NEVER ? wcachesw : CACHE_ASK, + &cachetype, ct->c_id, cachefile, sizeof(cachefile)) + == NOTOK) { + advise (NULL, "unable to cache %s's contents", ct->c_file); + return; + } + if (wcachesw != CACHE_NEVER && wcachesw != CACHE_ASK) { + fflush (stdout); + fprintf (stderr, "caching message %s as file %s\n", ct->c_file, + cachefile); + } + + if (ce->ce_file) { + int mask = umask (cachetype ? ~m_gmprot () : 0222); + FILE *fp; + + if (debugsw) + fprintf (stderr, "caching by copying %s...\n", ce->ce_file); + + file = NULL; + if ((*ct->c_ceopenfnx) (ct, &file) == NOTOK) + goto reset_umask; + + if ((fp = fopen (cachefile, "w"))) { + int cc; + char buffer[BUFSIZ]; + FILE *gp = ce->ce_fp; + + fseek (gp, 0L, SEEK_SET); + + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) + > 0) + fwrite (buffer, sizeof(*buffer), cc, fp); + fflush (fp); + + if (ferror (gp)) { + admonish (ce->ce_file, "error reading"); + unlink (cachefile); + } else { + if (ferror (fp)) { + admonish (cachefile, "error writing"); + unlink (cachefile); + } + } + fclose (fp); + } else + content_error (cachefile, ct, "unable to fopen for writing"); +reset_umask: + umask (mask); + } else { + if (debugsw) + fprintf (stderr, "in place caching...\n"); + + file = cachefile; + if ((*ct->c_ceopenfnx) (ct, &file) != NOTOK) + chmod (cachefile, cachetype ? m_gmprot () : 0444); + } +} + + +int +find_cache (CT ct, int policy, int *writing, char *id, + char *buffer, int buflen) +{ + int status = NOTOK; + + if (id == NULL) + return NOTOK; + id = trimcpy (id); + + if (debugsw) + fprintf (stderr, "find_cache %s(%d) %s %s\n", caches[policy].sw, + policy, writing ? "writing" : "reading", id); + + switch (policy) { + case CACHE_NEVER: + default: + break; + + case CACHE_ASK: + case CACHE_PUBLIC: + if (cache_private + && !writing + && find_cache_aux (writing ? 2 : 0, cache_private, id, + buffer, buflen) == OK) { + if (access (buffer, R_OK) != NOTOK) { +got_private: + if (writing) + *writing = 1; +got_it: + status = OK; + break; + } + } + if (cache_public + && find_cache_aux (writing ? 1 : 0, cache_public, id, + buffer, buflen) == OK) { + if (writing || access (buffer, R_OK) != NOTOK) { + if (writing) + *writing = 0; + goto got_it; + } + } + break; + + case CACHE_PRIVATE: + if (cache_private + && find_cache_aux (writing ? 2 : 0, cache_private, id, + buffer, buflen) == OK) { + if (writing || access (buffer, R_OK) != NOTOK) + goto got_private; + } + break; + + } + + if (status == OK && policy == CACHE_ASK) { + int len, buflen; + char *bp, query[BUFSIZ]; + + if (xpid) { + if (xpid < 0) + xpid = -xpid; + pidcheck (pidwait (xpid, NOTOK)); + xpid = 0; + } + + /* Get buffer ready to go */ + bp = query; + buflen = sizeof(query); + + /* Now, construct query */ + if (writing) { + snprintf (bp, buflen, "Make cached, publically-accessible copy"); + } else { + struct stat st; + + snprintf (bp, buflen, "Use cached copy"); + len = strlen (bp); + bp += len; + buflen -= len; + + if (ct->c_partno) { + snprintf (bp, buflen, " of content %s", ct->c_partno); + len = strlen (bp); + bp += len; + buflen -= len; + } + + stat (buffer, &st); + snprintf (bp, buflen, " (size %lu octets)", + (unsigned long) st.st_size); + } + len = strlen (bp); + bp += len; + buflen -= len; + + snprintf (bp, buflen, "\n in file %s? ", buffer); + + /* Now, check answer */ + if (!getanswer (query)) + status = NOTOK; + } + + if (status == OK && writing) { + if (*writing && strchr(buffer, '/')) + make_intermediates (buffer); + unlink (buffer); + } + + free (id); + return status; +} + + +static int +find_cache_aux (int writing, char *directory, char *id, + char *buffer, int buflen) +{ + int mask, usemap; + char mapfile[BUFSIZ], mapname[BUFSIZ]; + FILE *fp; + static int partno, pid; + static time_t clock = 0; + +#ifdef BSD42 + usemap = strchr (id, '/') ? 1 : 0; +#else + usemap = 1; +#endif + + if (debugsw) + fprintf (stderr, "find_cache_aux %s usemap=%d\n", directory, usemap); + + snprintf (mapfile, sizeof(mapfile), "%s/cache.map", directory); + if (find_cache_aux2 (mapfile, id, mapname, sizeof(mapname)) == OK) + goto done_map; + + if (!writing) { + if (usemap) + return NOTOK; + +use_raw: + snprintf (buffer, buflen, "%s/%s", directory, id); + return OK; + } + + if (!usemap && access (mapfile, W_OK) == NOTOK) + goto use_raw; + + if (clock != 0) { + time_t now; + + time (&now); + if (now > clock) + clock = 0; + } else { + pid = getpid (); + } + + if (clock == 0) { + time (&clock); + partno = 0; + } else { + if (partno > 0xff) { + clock++; + partno = 0; + } + } + + snprintf (mapname, sizeof(mapname), "%08x%04x%02x", + (unsigned int) (clock & 0xffffffff), + (unsigned int) (pid & 0xffff), + (unsigned int) (partno++ & 0xff)); + + if (debugsw) + fprintf (stderr, "creating mapping %s->%s\n", mapname, id); + + make_intermediates (mapfile); + mask = umask (writing == 2 ? 0077 : 0); + if (!(fp = lkfopen (mapfile, "a")) && errno == ENOENT) { + int fd; + + if ((fd = creat (mapfile, 0666)) != NOTOK) { + close (fd); + fp = lkfopen (mapfile, "a"); + } + } + umask (mask); + if (!fp) + return NOTOK; + fprintf (fp, "%s: %s\n", mapname, id); + lkfclose (fp, mapfile); + +done_map: + if (*mapname == '/') + strncpy (buffer, mapname, buflen); + else + snprintf (buffer, buflen, "%s/%s", directory, mapname); + if (debugsw) + fprintf (stderr, "use %s\n", buffer); + + return OK; +} + + +static int +find_cache_aux2 (char *mapfile, char *id, char *mapname, int namelen) +{ + int state; + char buf[BUFSIZ], name[NAMESZ]; + FILE *fp; + + if (!(fp = lkfopen (mapfile, "r"))) + return NOTOK; + + for (state = FLD;;) { + int result; + char *cp, *dp; + + switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + strncpy (mapname, name, namelen); + if (state != FLDPLUS) + cp = buf; + else { + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), fp); + cp = add (buf, cp); + } + } + dp = trimcpy (cp); + if (cp != buf) + free (cp); + if (debugsw) + fprintf (stderr, "compare %s to %s <- %s\n", id, dp, + mapname); + result = strcmp (id, dp); + free (dp); + if (result == 0) { + lkfclose (fp, mapfile); + return OK; + } + if (state != FLDEOF) + continue; + /* else fall... */ + + case BODY: + case BODYEOF: + case FILEEOF: + default: + break; + } + break; + } + + lkfclose (fp, mapfile); + return NOTOK; +} diff --git a/uip/mhfree.c b/uip/mhfree.c new file mode 100644 index 0000000..aef86cd --- /dev/null +++ b/uip/mhfree.c @@ -0,0 +1,278 @@ + +/* + * mhfree.c -- routines to free the data structures used to + * -- represent MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include + +extern int errno; + +/* + * prototypes + */ +void free_content (CT); +void free_header (CT); +void free_ctinfo (CT); +void free_encoding (CT, int); + +/* + * static prototypes + */ +static void free_text (CT); +static void free_multi (CT); +static void free_partial (CT); +static void free_external (CT); + + +/* + * Primary routine to free a MIME content structure + */ + +void +free_content (CT ct) +{ + if (!ct) + return; + + /* + * free all the header fields + */ + free_header (ct); + + if (ct->c_partno) + free (ct->c_partno); + + if (ct->c_vrsn) + free (ct->c_vrsn); + + if (ct->c_ctline) + free (ct->c_ctline); + + free_ctinfo (ct); + + /* + * some of the content types have extra + * parts which need to be freed. + */ + switch (ct->c_type) { + case CT_MULTIPART: + free_multi (ct); + break; + + case CT_MESSAGE: + switch (ct->c_subtype) { + case MESSAGE_PARTIAL: + free_partial (ct); + break; + + case MESSAGE_EXTERNAL: + free_external (ct); + break; + } + break; + + case CT_TEXT: + free_text (ct); + break; + } + + if (ct->c_showproc) + free (ct->c_showproc); + if (ct->c_termproc) + free (ct->c_termproc); + if (ct->c_storeproc) + free (ct->c_storeproc); + + if (ct->c_celine) + free (ct->c_celine); + + /* free structures for content encodings */ + free_encoding (ct, 1); + + if (ct->c_id) + free (ct->c_id); + if (ct->c_descr) + free (ct->c_descr); + + if (ct->c_file) { + if (ct->c_unlink) + unlink (ct->c_file); + free (ct->c_file); + } + if (ct->c_fp) + fclose (ct->c_fp); + + if (ct->c_storage) + free (ct->c_storage); + if (ct->c_folder) + free (ct->c_folder); + + free (ct); +} + + +/* + * Free the linked list of header fields + * for this content. + */ + +void +free_header (CT ct) +{ + HF hp1, hp2; + + hp1 = ct->c_first_hf; + while (hp1) { + hp2 = hp1->next; + + free (hp1->name); + free (hp1->value); + free (hp1); + + hp1 = hp2; + } + + ct->c_first_hf = NULL; + ct->c_last_hf = NULL; +} + + +void +free_ctinfo (CT ct) +{ + char **ap; + CI ci; + + ci = &ct->c_ctinfo; + if (ci->ci_type) { + free (ci->ci_type); + ci->ci_type = NULL; + } + if (ci->ci_subtype) { + free (ci->ci_subtype); + ci->ci_subtype = NULL; + } + for (ap = ci->ci_attrs; *ap; ap++) { + free (*ap); + *ap = NULL; + } + if (ci->ci_comment) { + free (ci->ci_comment); + ci->ci_comment = NULL; + } + if (ci->ci_magic) { + free (ci->ci_magic); + ci->ci_magic = NULL; + } +} + + +static void +free_text (CT ct) +{ + struct text *t; + + if (!(t = (struct text *) ct->c_ctparams)) + return; + + free ((char *) t); + ct->c_ctparams = NULL; +} + + +static void +free_multi (CT ct) +{ + struct multipart *m; + struct part *part, *next; + + if (!(m = (struct multipart *) ct->c_ctparams)) + return; + + if (m->mp_start) + free (m->mp_start); + if (m->mp_stop) + free (m->mp_stop); + + for (part = m->mp_parts; part; part = next) { + next = part->mp_next; + free_content (part->mp_part); + free ((char *) part); + } + m->mp_parts = NULL; + + free ((char *) m); + ct->c_ctparams = NULL; +} + + +static void +free_partial (CT ct) +{ + struct partial *p; + + if (!(p = (struct partial *) ct->c_ctparams)) + return; + + if (p->pm_partid) + free (p->pm_partid); + + free ((char *) p); + ct->c_ctparams = NULL; +} + + +static void +free_external (CT ct) +{ + struct exbody *e; + + if (!(e = (struct exbody *) ct->c_ctparams)) + return; + + free_content (e->eb_content); + if (e->eb_body) + free (e->eb_body); + + free ((char *) e); + ct->c_ctparams = NULL; +} + + +/* + * Free data structures related to encoding/decoding + * Content-Transfer-Encodings. + */ + +void +free_encoding (CT ct, int toplevel) +{ + CE ce; + + if (!(ce = ct->c_cefile)) + return; + + if (ce->ce_fp) { + fclose (ce->ce_fp); + ce->ce_fp = NULL; + } + + if (ce->ce_file) { + if (ce->ce_unlink) + unlink (ce->ce_file); + free (ce->ce_file); + } + + if (toplevel) { + free ((char *) ce); + ct->c_cefile = NULL; + } else { + ct->c_ceopenfnx = NULL; + } +} diff --git a/uip/mhl.c b/uip/mhl.c new file mode 100644 index 0000000..4c9e5e0 --- /dev/null +++ b/uip/mhl.c @@ -0,0 +1,32 @@ + +/* + * mhl.c -- the nmh message listing program + * + * $Id$ + */ + +#include + + +int +main (int argc, char **argv) +{ +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + done (mhl (argc, argv)); +} + + +/* + * Cheat: we are loaded with adrparse, which wants a routine called + * OfficialName(). We call adrparse:getm() with the correct arguments + * to prevent OfficialName() from being called. Hence, the following + * is to keep the loader happy. + */ + +char * +OfficialName(char *name) +{ + return name; +} diff --git a/uip/mhlist.c b/uip/mhlist.c new file mode 100644 index 0000000..d5f2908 --- /dev/null +++ b/uip/mhlist.c @@ -0,0 +1,428 @@ + +/* + * mhlist.c -- list the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +/* + * We allocate space for message names (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define CHECKSW 0 + { "check", 0 }, +#define NCHECKSW 1 + { "nocheck", 0 }, +#define HEADSW 2 + { "headers", 0 }, +#define NHEADSW 3 + { "noheaders", 0 }, +#define SIZESW 4 + { "realsize", 0 }, +#define NSIZESW 5 + { "norealsize", 0 }, +#define VERBSW 6 + { "verbose", 0 }, +#define NVERBSW 7 + { "noverbose", 0 }, +#define FILESW 8 /* interface from show */ + { "file file", 0 }, +#define PARTSW 9 + { "part number", 0 }, +#define TYPESW 10 + { "type content", 0 }, +#define RCACHESW 11 + { "rcache policy", 0 }, +#define WCACHESW 12 + { "wcache policy", 0 }, +#define VERSIONSW 13 + { "version", 0 }, +#define HELPSW 14 + { "help", 4 }, + +/* + * switches for debugging + */ +#define DEBUGSW 15 + { "debug", -5 }, + { NULL, 0 } +}; + + +extern int errno; + +/* mhparse.c */ +extern int checksw; +extern char *tmp; /* directory to place temp files */ + +/* mhcachesbr.c */ +extern int rcachesw; +extern int wcachesw; +extern char *cache_public; +extern char *cache_private; + +/* mhmisc.c */ +extern int npart; +extern int ntype; +extern char *parts[NPARTS + 1]; +extern char *types[NTYPES + 1]; +extern int userrs; + +/* + * This is currently needed to keep mhparse happy. + * This needs to be changed. + */ +pid_t xpid = 0; + +int debugsw = 0; +int verbosw = 0; + +/* The list of top-level contents to display */ +CT *cts = NULL; + +#define quitser pipeser + +/* mhparse.c */ +CT parse_mime (char *); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void set_endian (void); +void flush_errors (void); + +/* mhlistsbr.c */ +void list_all_messages (CT *, int, int, int, int); + +/* mhfree.c */ +void free_content (CT); + +/* + * static prototypes + */ +static RETSIGTYPE pipeser (int); + + +int +main (int argc, char **argv) +{ + int sizesw = 1, headsw = 1; + int nummsgs, maxmsgs, msgnum, *icachesw; + char *cp, *file = NULL, *folder = NULL; + char *maildir, buf[100], **argp; + char **arguments, **msgs; + struct msgs *mp = NULL; + CT ct, *ctp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case RCACHESW: + icachesw = &rcachesw; + goto do_cache; + case WCACHESW: + icachesw = &wcachesw; +do_cache: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + switch (*icachesw = smatch (cp, caches)) { + case AMBIGSW: + ambigsw (cp, caches); + done (1); + case UNKWNSW: + adios (NULL, "%s unknown", cp); + default: + break; + } + continue; + + case CHECKSW: + checksw++; + continue; + case NCHECKSW: + checksw = 0; + continue; + + case HEADSW: + headsw = 1; + continue; + case NHEADSW: + headsw = 0; + continue; + + case SIZESW: + sizesw = 1; + continue; + case NSIZESW: + sizesw = 0; + continue; + + case PARTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (npart >= NPARTS) + adios (NULL, "too many parts (starting with %s), %d max", + cp, NPARTS); + parts[npart++] = cp; + continue; + + case TYPESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (ntype >= NTYPES) + adios (NULL, "too many types (starting with %s), %d max", + cp, NTYPES); + types[ntype++] = cp; + continue; + + case FILESW: + if (!(cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + file = *cp == '-' ? cp : path (cp, TFILE); + continue; + + case VERBSW: + verbosw = 1; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + /* null terminate the list of acceptable parts/types */ + parts[npart] = NULL; + types[ntype] = NULL; + + set_endian (); + + /* Check for public cache location */ + if ((cache_public = context_find (nmhcache)) && *cache_public != '/') + cache_public = NULL; + + /* Check for private cache location */ + if (!(cache_private = context_find (nmhprivcache))) + cache_private = ".cache"; + cache_private = getcpy (m_maildir (cache_private)); + + /* + * Check for storage directory. If specified, + * then store temporary files there. Else we + * store them in standard nmh directory. + */ + if ((cp = context_find (nmhstorage)) && *cp) + tmp = concat (cp, "/", invo_name, NULL); + else + tmp = add (m_maildir (invo_name), NULL); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (file && nummsgs) + adios (NULL, "cannot specify msg and file at same time!"); + + /* + * check if message is coming from file + */ + if (file) { + if (!(cts = (CT *) calloc ((size_t) 2, sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + if ((ct = parse_mime (file))); + *ctp++ = ct; + } else { + /* + * message(s) are coming from a folder + */ + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (!(cts = (CT *) calloc ((size_t) (mp->numsel + 1), sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + char *msgnam; + + msgnam = m_name (msgnum); + if ((ct = parse_mime (msgnam))) + *ctp++ = ct; + } + } + } + + if (!*cts) + done (1); + + userrs = 1; + SIGNAL (SIGQUIT, quitser); + SIGNAL (SIGPIPE, pipeser); + + /* + * Get the associated umask for the relevant contents. + */ + for (ctp = cts; *ctp; ctp++) { + struct stat st; + + ct = *ctp; + if (type_ok (ct, 1) && !ct->c_umask) { + if (stat (ct->c_file, &st) != NOTOK) + ct->c_umask = ~(st.st_mode & 0777); + else + ct->c_umask = ~m_gmprot(); + } + } + + /* + * List the message content + */ + list_all_messages (cts, headsw, sizesw, verbosw, debugsw); + + /* Now free all the structures for the content */ + for (ctp = cts; *ctp; ctp++) + free_content (*ctp); + + free ((char *) cts); + cts = NULL; + + /* If reading from a folder, do some updating */ + if (mp) { + context_replace (pfolder, folder);/* update current folder */ + seq_setcur (mp, mp->hghsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + done (0); + /* NOTREACHED */ +} + + +static RETSIGTYPE +pipeser (int i) +{ + if (i == SIGQUIT) { + unlink ("core"); + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + done (1); + /* NOTREACHED */ +} + + +void +done (int status) +{ + CT *ctp; + + if ((ctp = cts)) + for (; *ctp; ctp++) + free_content (*ctp); + + exit (status); +} diff --git a/uip/mhlistsbr.c b/uip/mhlistsbr.c new file mode 100644 index 0000000..ca34902 --- /dev/null +++ b/uip/mhlistsbr.c @@ -0,0 +1,428 @@ + +/* + * mhlistsbr.c -- routines to list information about the + * -- contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int errno; + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void flush_errors (void); + +/* + * prototypes + */ +void list_all_messages (CT *, int, int, int, int); +int list_switch (CT, int, int, int, int); +int list_content (CT, int, int, int, int); + +/* + * static prototypes + */ +static void list_single_message (CT, int, int, int); +static int list_debug (CT); +static int list_multi (CT, int, int, int, int); +static int list_partial (CT, int, int, int, int); +static int list_external (CT, int, int, int, int); +static int list_application (CT, int, int, int, int); +static int list_encoding (CT); + + +/* + * various formats for -list option + */ +#define LSTFMT1 "%4s %-5s %-24s %5s %-36s\n" +#define LSTFMT2a "%4d " +#define LSTFMT2b "%-5s %-24.24s " +#define LSTFMT2c1 "%5lu" +#define LSTFMT2c2 "%4lu%c" +#define LSTFMT2c3 "huge " +#define LSTFMT2c4 " " +#define LSTFMT2d1 " %-36.36s" +#define LSTFMT2d2 "\t %-65.65s\n" + + +/* + * Top level entry point to list group of messages + */ + +void +list_all_messages (CT *cts, int headers, int realsize, int verbose, int debug) +{ + CT ct, *ctp; + + if (headers) + printf (LSTFMT1, "msg", "part", "type/subtype", "size", "description"); + + for (ctp = cts; *ctp; ctp++) { + ct = *ctp; + list_single_message (ct, realsize, verbose, debug); + } + + flush_errors (); +} + + +/* + * Entry point to list a single message + */ + +static void +list_single_message (CT ct, int realsize, int verbose, int debug) +{ + if (type_ok (ct, 1)) { + umask (ct->c_umask); + list_switch (ct, 1, realsize, verbose, debug); + if (ct->c_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } + if (ct->c_ceclosefnx) + (*ct->c_ceclosefnx) (ct); + } +} + + +/* + * Primary switching routine to list information about a content + */ + +int +list_switch (CT ct, int toplevel, int realsize, int verbose, int debug) +{ + switch (ct->c_type) { + case CT_MULTIPART: + return list_multi (ct, toplevel, realsize, verbose, debug); + break; + + case CT_MESSAGE: + switch (ct->c_subtype) { + case MESSAGE_PARTIAL: + return list_partial (ct, toplevel, realsize, verbose, debug); + break; + + case MESSAGE_EXTERNAL: + return list_external (ct, toplevel, realsize, verbose, debug); + break; + + case MESSAGE_RFC822: + default: + return list_content (ct, toplevel, realsize, verbose, debug); + break; + } + break; + + case CT_TEXT: + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + return list_content (ct, toplevel, realsize, verbose, debug); + break; + + case CT_APPLICATION: + return list_application (ct, toplevel, realsize, verbose, debug); + break; + + default: + /* list_debug (ct); */ + adios (NULL, "unknown content type %d", ct->c_type); + break; + } + + return 0; /* NOT REACHED */ +} + + +#define empty(s) ((s) ? (s) : "") + +/* + * Method for listing information about a simple/generic content + */ + +int +list_content (CT ct, int toplevel, int realsize, int verbose, int debug) +{ + unsigned long size; + char *cp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + printf (toplevel > 0 ? LSTFMT2a : toplevel < 0 ? "part " : " ", + atoi (r1bindex (empty (ct->c_file), '/'))); + snprintf (buffer, sizeof(buffer), "%s/%s", empty (ci->ci_type), + empty (ci->ci_subtype)); + printf (LSTFMT2b, empty (ct->c_partno), buffer); + + if (ct->c_cesizefnx && realsize) + size = (*ct->c_cesizefnx) (ct); + else + size = ct->c_end - ct->c_begin; + + /* find correct scale for size (Kilo/Mega/Giga/Tera) */ + for (cp = " KMGT"; size > 9999; size >>= 10) + if (!*++cp) + break; + + /* print size of this body part */ + switch (*cp) { + case ' ': + if (size > 0 || ct->c_encoding != CE_EXTERNAL) + printf (LSTFMT2c1, size); + else + printf (LSTFMT2c4); + break; + + default: + printf (LSTFMT2c2, size, *cp); + break; + + case '\0': + printf (LSTFMT2c3); + } + + /* print Content-Description */ + if (ct->c_descr) { + char *dp; + + dp = trimcpy (cp = add (ct->c_descr, NULL)); + free (cp); + printf (LSTFMT2d1, dp); + free (dp); + } + + printf ("\n"); + + /* + * If verbose, print any RFC-822 comments in the + * Content-Type line. + */ + if (verbose && ci->ci_comment) { + char *dp; + + dp = trimcpy (cp = add (ci->ci_comment, NULL)); + free (cp); + snprintf (buffer, sizeof(buffer), "(%s)", dp); + free (dp); + printf (LSTFMT2d2, buffer); + } + + if (debug) + list_debug (ct); + + return OK; +} + + +/* + * Print debugging information about a content + */ + +static int +list_debug (CT ct) +{ + char **ap, **ep; + CI ci = &ct->c_ctinfo; + + fflush (stdout); + fprintf (stderr, " partno \"%s\"\n", empty (ct->c_partno)); + + /* print MIME-Version line */ + if (ct->c_vrsn) + fprintf (stderr, " %s:%s\n", VRSN_FIELD, ct->c_vrsn); + + /* print Content-Type line */ + if (ct->c_ctline) + fprintf (stderr, " %s:%s\n", TYPE_FIELD, ct->c_ctline); + + /* print parsed elements of content type */ + fprintf (stderr, " type \"%s\"\n", empty (ci->ci_type)); + fprintf (stderr, " subtype \"%s\"\n", empty (ci->ci_subtype)); + fprintf (stderr, " comment \"%s\"\n", empty (ci->ci_comment)); + fprintf (stderr, " magic \"%s\"\n", empty (ci->ci_magic)); + + /* print parsed parameters attached to content type */ + fprintf (stderr, " parameters\n"); + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) + fprintf (stderr, " %s=\"%s\"\n", *ap, *ep); + + /* print internal flags for type/subtype */ + fprintf (stderr, " type 0x%x subtype 0x%x params 0x%x\n", + ct->c_type, ct->c_subtype, (unsigned int) ct->c_ctparams); + + fprintf (stderr, " showproc \"%s\"\n", empty (ct->c_showproc)); + fprintf (stderr, " termproc \"%s\"\n", empty (ct->c_termproc)); + fprintf (stderr, " storeproc \"%s\"\n", empty (ct->c_storeproc)); + + /* print transfer encoding information */ + if (ct->c_celine) + fprintf (stderr, " %s:%s", ENCODING_FIELD, ct->c_celine); + + /* print internal flags for transfer encoding */ + fprintf (stderr, " transfer encoding 0x%x params 0x%x\n", + ct->c_encoding, (unsigned int) ct->c_cefile); + + /* print Content-ID */ + if (ct->c_id) + fprintf (stderr, " %s:%s", ID_FIELD, ct->c_id); + + /* print Content-Description */ + if (ct->c_descr) + fprintf (stderr, " %s:%s", DESCR_FIELD, ct->c_descr); + + fprintf (stderr, " read fp 0x%x file \"%s\" begin %ld end %ld\n", + (unsigned int) ct->c_fp, empty (ct->c_file), + ct->c_begin, ct->c_end); + + /* print more information about transfer encoding */ + list_encoding (ct); + + return OK; +} + +#undef empty + + +/* + * list content information for type "multipart" + */ + +static int +list_multi (CT ct, int toplevel, int realsize, int verbose, int debug) +{ + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + + /* list the content for toplevel of this multipart */ + list_content (ct, toplevel, realsize, verbose, debug); + + /* now list for all the subparts */ + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + if (part_ok (p, 1) && type_ok (p, 1)) + list_switch (p, 0, realsize, verbose, debug); + } + + return OK; +} + + +/* + * list content information for type "message/partial" + */ + +static int +list_partial (CT ct, int toplevel, int realsize, int verbose, int debug) +{ + struct partial *p = (struct partial *) ct->c_ctparams; + + list_content (ct, toplevel, realsize, verbose, debug); + if (verbose) { + printf ("\t [message %s, part %d", p->pm_partid, p->pm_partno); + if (p->pm_maxno) + printf (" of %d", p->pm_maxno); + printf ("]\n"); + } + + return OK; +} + + +/* + * list content information for type "message/external" + */ + +static int +list_external (CT ct, int toplevel, int realsize, int verbose, int debug) +{ + struct exbody *e = (struct exbody *) ct->c_ctparams; + + /* + * First list the information for the + * message/external content itself. + */ + list_content (ct, toplevel, realsize, verbose, debug); + + if (verbose) { + if (e->eb_name) + printf ("\t name=\"%s\"\n", e->eb_name); + if (e->eb_dir) + printf ("\t directory=\"%s\"\n", e->eb_dir); + if (e->eb_site) + printf ("\t site=\"%s\"\n", e->eb_site); + if (e->eb_server) + printf ("\t server=\"%s\"\n", e->eb_server); + if (e->eb_subject) + printf ("\t subject=\"%s\"\n", e->eb_subject); + + /* This must be defined */ + printf ("\t access-type=\"%s\"\n", e->eb_access); + + if (e->eb_mode) + printf ("\t mode=\"%s\"\n", e->eb_mode); + if (e->eb_permission) + printf ("\t permission=\"%s\"\n", e->eb_permission); + + if (e->eb_flags == NOTOK) + printf ("\t [service unavailable]\n"); + } + + /* + * Now list the information for the external content + * to which this content points. + */ + list_content (e->eb_content, 0, realsize, verbose, debug); + + return OK; +} + + +/* + * list content information for type "application" + */ + +static int +list_application (CT ct, int toplevel, int realsize, int verbose, int debug) +{ + list_content (ct, toplevel, realsize, verbose, debug); + if (verbose) { + char **ap, **ep; + CI ci = &ct->c_ctinfo; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) + printf ("\t %s=\"%s\"\n", *ap, *ep); + } + + return OK; +} + + +/* + * list information about the Content-Transfer-Encoding + * used by a content. + */ + +static int +list_encoding (CT ct) +{ + CE ce; + + if ((ce = ct->c_cefile)) + fprintf (stderr, " decoded fp 0x%x file \"%s\"\n", + (unsigned int) ce->ce_fp, ce->ce_file ? ce->ce_file : ""); + + return OK; +} diff --git a/uip/mhlsbr.c b/uip/mhlsbr.c new file mode 100644 index 0000000..18617d3 --- /dev/null +++ b/uip/mhlsbr.c @@ -0,0 +1,1805 @@ + +/* + * mhlsbr.c -- main routines for nmh message lister + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * MAJOR BUG: + * for a component containing addresses, ADDRFMT, if COMPRESS is also + * set, then addresses get split wrong (not at the spaces between commas). + * To fix this correctly, putstr() should know about "atomic" strings that + * must NOT be broken across lines. That's too difficult for right now + * (it turns out that there are a number of degernate cases), so in + * oneline(), instead of + * + * (*onelp == '\n' && !onelp[1]) + * + * being a terminating condition, + * + * (*onelp == '\n' && (!onelp[1] || (flags & ADDRFMT))) + * + * is used instead. This cuts the line prematurely, and gives us a much + * better chance of getting things right. + */ + +#define ONECOMP 0 +#define TWOCOMP 1 +#define BODYCOMP 2 + +#define QUOTE '\\' + +static struct swit mhlswitches[] = { +#define BELLSW 0 + { "bell", 0 }, +#define NBELLSW 1 + { "nobell", 0 }, +#define CLRSW 2 + { "clear", 0 }, +#define NCLRSW 3 + { "noclear", 0 }, +#define FACESW 4 + { "faceproc program", 0 }, +#define NFACESW 5 + { "nofaceproc", 0 }, +#define FOLDSW 6 + { "folder +folder", 0 }, +#define FORMSW 7 + { "form formfile", 0 }, +#define PROGSW 8 + { "moreproc program", 0 }, +#define NPROGSW 9 + { "nomoreproc", 0 }, +#define LENSW 10 + { "length lines", 0 }, +#define WIDTHSW 11 + { "width columns", 0 }, +#define SLEEPSW 12 + { "sleep seconds", 0 }, +#define BITSTUFFSW 13 + { "dashstuffing", -12 }, /* interface from forw */ +#define NBITSTUFFSW 14 + { "nodashstuffing", -14 }, /* interface from forw */ +#define VERSIONSW 15 + { "version", 0 }, +#define HELPSW 16 + { "help", 4 }, +#define FORW1SW 17 + { "forward", -7 }, /* interface from forw */ +#define FORW2SW 18 + { "forwall", -7 }, /* interface from forw */ +#define DGSTSW 19 + { "digest list", -6 }, +#define VOLUMSW 20 + { "volume number", -6 }, +#define ISSUESW 21 + { "issue number", -5 }, +#define NBODYSW 22 + { "nobody", -6 }, + { NULL, 0 } +}; + +#define NOCOMPONENT 0x000001 /* don't show component name */ +#define UPPERCASE 0x000002 /* display in all upper case */ +#define CENTER 0x000004 /* center line */ +#define CLEARTEXT 0x000008 /* cleartext */ +#define EXTRA 0x000010 /* an "extra" component */ +#define HDROUTPUT 0x000020 /* already output */ +#define CLEARSCR 0x000040 /* clear screen */ +#define LEFTADJUST 0x000080 /* left justify multiple lines */ +#define COMPRESS 0x000100 /* compress text */ +#define ADDRFMT 0x000200 /* contains addresses */ +#define BELL 0x000400 /* sound bell at EOP */ +#define DATEFMT 0x000800 /* contains dates */ +#define FORMAT 0x001000 /* parse address/date/RFC-2047 field */ +#define INIT 0x002000 /* initialize component */ +#define FACEFMT 0x004000 /* contains face */ +#define FACEDFLT 0x008000 /* default for face */ +#define SPLIT 0x010000 /* split headers (don't concatenate) */ +#define NONEWLINE 0x020000 /* don't write trailing newline */ +#define LBITS "\020\01NOCOMPONENT\02UPPERCASE\03CENTER\04CLEARTEXT\05EXTRA\06HDROUTPUT\07CLEARSCR\010LEFTADJUST\011COMPRESS\012ADDRFMT\013BELL\014DATEFMT\015FORMAT\016INIT\017FACEFMT\020FACEDFLT\021SPLIT\022NONEWLINE" +#define GFLAGS (NOCOMPONENT | UPPERCASE | CENTER | LEFTADJUST | COMPRESS | SPLIT) + +struct mcomp { + char *c_name; /* component name */ + char *c_text; /* component text */ + char *c_ovtxt; /* text overflow indicator */ + char *c_nfs; /* iff FORMAT */ + struct format *c_fmt; /* .. */ + char *c_face; /* face designator */ + int c_offset; /* left margin indentation */ + int c_ovoff; /* overflow indentation */ + int c_width; /* width of field */ + int c_cwidth; /* width of component */ + int c_length; /* length in lines */ + long c_flags; + struct mcomp *c_next; +}; + +static struct mcomp *msghd = NULL; +static struct mcomp *msgtl = NULL; +static struct mcomp *fmthd = NULL; +static struct mcomp *fmttl = NULL; + +static struct mcomp global = { + NULL, NULL, "", NULL, NULL, 0, -1, 80, -1, 40, BELL, 0 +}; + +static struct mcomp holder = { + NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, NOCOMPONENT, 0 +}; + +struct pair { + char *p_name; + long p_flags; +}; + +static struct pair pairs[] = { + { "Date", DATEFMT }, + { "From", ADDRFMT|FACEDFLT }, + { "Sender", ADDRFMT }, + { "Reply-To", ADDRFMT }, + { "To", ADDRFMT }, + { "cc", ADDRFMT }, + { "Bcc", ADDRFMT }, + { "Resent-Date", DATEFMT }, + { "Resent-From", ADDRFMT }, + { "Resent-Sender", ADDRFMT }, + { "Resent-Reply-To", ADDRFMT }, + { "Resent-To", ADDRFMT }, + { "Resent-cc", ADDRFMT }, + { "Resent-Bcc", ADDRFMT }, + { "Face", FACEFMT }, + { NULL, 0 } +}; + +struct triple { + char *t_name; + long t_on; + long t_off; +}; + +static struct triple triples[] = { + { "nocomponent", NOCOMPONENT, 0 }, + { "uppercase", UPPERCASE, 0 }, + { "nouppercase", 0, UPPERCASE }, + { "center", CENTER, 0 }, + { "nocenter", 0, CENTER }, + { "clearscreen", CLEARSCR, 0 }, + { "noclearscreen", 0, CLEARSCR }, + { "noclear", 0, CLEARSCR }, + { "leftadjust", LEFTADJUST, 0 }, + { "noleftadjust", 0, LEFTADJUST }, + { "compress", COMPRESS, 0 }, + { "nocompress", 0, COMPRESS }, + { "split", SPLIT, 0 }, + { "nosplit", 0, SPLIT }, + { "addrfield", ADDRFMT, DATEFMT }, + { "bell", BELL, 0 }, + { "nobell", 0, BELL }, + { "datefield", DATEFMT, ADDRFMT }, + { "newline", 0, NONEWLINE }, + { "nonewline", NONEWLINE, 0 }, + { NULL, 0, 0 } +}; + + +static int bellflg = 0; +static int clearflg = 0; +static int dashstuff = 0; +static int dobody = 1; +static int forwflg = 0; +static int forwall = 0; + +static int sleepsw = NOTOK; + +static char *digest = NULL; +static int volume = 0; +static int issue = 0; + +static int exitstat = 0; +static int mhldebug = 0; + +#define PITTY (-1) +#define NOTTY 0 +#define ISTTY 1 +static int ontty = NOTTY; + +static int row; +static int column; + +static int lm; +static int llim; +static int ovoff; +static int term; +static int wid; + +static char *ovtxt; + +static char *onelp; + +static char *parptr; + +static int num_ignores = 0; +static char *ignores[MAXARGS]; + +static jmp_buf env; +static jmp_buf mhlenv; + +static char delim3[] = /* from forw.c */ + "\n----------------------------------------------------------------------\n\n"; +static char delim4[] = "\n------------------------------\n\n"; + +static FILE *(*mhl_action) () = (FILE *(*) ()) 0; + + +/* + * Redefine a couple of functions. + * These are undefined later in the code. + */ +#define adios mhladios +#define done mhldone + +/* + * prototypes + */ +static void mhl_format (char *, int, int); +static int evalvar (struct mcomp *); +static int ptoi (char *, int *); +static int ptos (char *, char **); +static char *parse (void); +static void process (char *, char *, int, int); +static void mhlfile (FILE *, char *, int, int); +static int mcomp_flags (char *); +static char *mcomp_add (long, char *, char *); +static void mcomp_format (struct mcomp *, struct mcomp *); +static struct mcomp *add_queue (struct mcomp **, struct mcomp **, char *, char *, int); +static void free_queue (struct mcomp **, struct mcomp **); +static void putcomp (struct mcomp *, struct mcomp *, int); +static char *oneline (char *, long); +static void putstr (char *); +static void putch (char); +static RETSIGTYPE intrser (int); +static RETSIGTYPE pipeser (int); +static RETSIGTYPE quitser (int); +static void face_format (struct mcomp *); +static int doface (struct mcomp *); +static void mhladios (char *, char *, ...); +static void mhldone (int); +static void m_popen (char *); + +int mhl (int, char **); +int mhlsbr (int, char **, FILE *(*)()); +void m_pclose (void); + +void clear_screen (void); /* from termsbr.c */ +int SOprintf (char *, ...); /* from termsbr.c */ +int sc_width (void); /* from termsbr.c */ +int sc_length (void); /* from termsbr.c */ +int sc_hardcopy (void); /* from termsbr.c */ +struct hostent *gethostbystring (); + + +int +mhl (int argc, char **argv) +{ + int length = 0, nomore = 0; + int i, width = 0, vecp = 0; + char *cp, *folder = NULL, *form = NULL; + char buf[BUFSIZ], *files[MAXARGS]; + char **argp, **arguments; + + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + if ((cp = getenv ("MHLDEBUG")) && *cp) + mhldebug++; + + if ((cp = getenv ("FACEPROC"))) + faceproc = cp; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, mhlswitches)) { + case AMBIGSW: + ambigsw (cp, mhlswitches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] [files ...]", invo_name); + print_help (buf, mhlswitches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case BELLSW: + bellflg = 1; + continue; + case NBELLSW: + bellflg = -1; + continue; + + case CLRSW: + clearflg = 1; + continue; + case NCLRSW: + clearflg = -1; + continue; + + case FOLDSW: + if (!(folder = *argp++) || *folder == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case FACESW: + if (!(faceproc = *argp++) || *faceproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NFACESW: + faceproc = NULL; + continue; + case SLEEPSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + sleepsw = atoi (cp);/* ZERO ok! */ + continue; + + case PROGSW: + if (!(moreproc = *argp++) || *moreproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NPROGSW: + nomore++; + continue; + + case LENSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((length = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((width = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + + case DGSTSW: + if (!(digest = *argp++) || *digest == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case ISSUESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((issue = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case VOLUMSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((volume = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + + case FORW2SW: + forwall++; /* fall */ + case FORW1SW: + forwflg++; + clearflg = -1;/* XXX */ + continue; + + case BITSTUFFSW: + dashstuff = 1; /* trinary logic */ + continue; + case NBITSTUFFSW: + dashstuff = -1; /* trinary logic */ + continue; + + case NBODYSW: + dobody = 0; + continue; + } + } + files[vecp++] = cp; + } + + if (!folder) + folder = getenv ("mhfolder"); + + if (isatty (fileno (stdout))) { + if (!nomore && !sc_hardcopy() && moreproc && *moreproc != '\0') { + if (mhl_action) { + SIGNAL (SIGINT, SIG_IGN); + SIGNAL2 (SIGQUIT, quitser); + } + m_popen (moreproc); + ontty = PITTY; + } else { + SIGNAL (SIGINT, SIG_IGN); + SIGNAL2 (SIGQUIT, quitser); + ontty = ISTTY; + } + } else { + ontty = NOTTY; + } + + mhl_format (form ? form : mhlformat, length, width); + + if (vecp == 0) { + process (folder, NULL, 1, vecp = 1); + } else { + for (i = 0; i < vecp; i++) + process (folder, files[i], i + 1, vecp); + } + + if (forwall) { + if (digest) { + printf ("%s", delim4); + if (volume == 0) { + snprintf (buf, sizeof(buf), "End of %s Digest\n", digest); + } else { + snprintf (buf, sizeof(buf), "End of %s Digest [Volume %d Issue %d]\n", + digest, volume, issue); + } + i = strlen (buf); + for (cp = buf + i; i > 1; i--) + *cp++ = '*'; + *cp++ = '\n'; + *cp = 0; + printf ("%s", buf); + } + else + printf ("\n------- End of Forwarded Message%s\n\n", + vecp > 1 ? "s" : ""); + } + + if (clearflg > 0 && ontty == NOTTY) + clear_screen (); + + if (ontty == PITTY) + m_pclose (); + + return exitstat; +} + + +static void +mhl_format (char *file, int length, int width) +{ + int i; + char *bp, *cp, **ip; + char *ap, buffer[BUFSIZ], name[NAMESZ]; + struct mcomp *c1; + struct stat st; + FILE *fp; + static dev_t dev = 0; + static ino_t ino = 0; + static time_t mtime = 0; + + if (fmthd != NULL) + if (stat (etcpath (file), &st) != NOTOK + && mtime == st.st_mtime + && dev == st.st_dev + && ino == st.st_ino) + goto out; + else + free_queue (&fmthd, &fmttl); + + if ((fp = fopen (etcpath (file), "r")) == NULL) + adios (file, "unable to open format file"); + + if (fstat (fileno (fp), &st) != NOTOK) { + mtime = st.st_mtime; + dev = st.st_dev; + ino = st.st_ino; + } + + global.c_ovtxt = global.c_nfs = NULL; + global.c_fmt = NULL; + global.c_offset = 0; + global.c_ovoff = -1; + if ((i = sc_width ()) > 5) + global.c_width = i; + global.c_cwidth = -1; + if ((i = sc_length ()) > 5) + global.c_length = i - 1; + global.c_flags = BELL; /* BELL is default */ + *(ip = ignores) = NULL; + + while (vfgets (fp, &ap) == OK) { + bp = ap; + if (*bp == ';') + continue; + + if ((cp = strchr(bp, '\n'))) + *cp = 0; + + if (*bp == ':') { + c1 = add_queue (&fmthd, &fmttl, NULL, bp + 1, CLEARTEXT); + continue; + } + + parptr = bp; + strncpy (name, parse(), sizeof(name)); + switch (*parptr) { + case '\0': + case ',': + case '=': + /* + * Split this list of fields to ignore, and copy + * it to the end of the current "ignores" list. + */ + if (!strcasecmp (name, "ignores")) { + char **tmparray, **p; + int n = 0; + + /* split the fields */ + tmparray = brkstring (getcpy (++parptr), ",", NULL); + + /* count number of fields split */ + p = tmparray; + while (*p++) + n++; + + /* copy pointers to split fields to ignores array */ + ip = copyip (tmparray, ip, MAXARGS - num_ignores); + num_ignores += n; + continue; + } + parptr = bp; + while (*parptr) { + if (evalvar (&global)) + adios (NULL, "format file syntax error: %s", bp); + if (*parptr) + parptr++; + } + continue; + + case ':': + c1 = add_queue (&fmthd, &fmttl, name, NULL, INIT); + while (*parptr == ':' || *parptr == ',') { + parptr++; + if (evalvar (c1)) + adios (NULL, "format file syntax error: %s", bp); + } + if (!c1->c_nfs && global.c_nfs) + if (c1->c_flags & DATEFMT) { + if (global.c_flags & DATEFMT) + c1->c_nfs = getcpy (global.c_nfs); + } + else + if (c1->c_flags & ADDRFMT) { + if (global.c_flags & ADDRFMT) + c1->c_nfs = getcpy (global.c_nfs); + } + continue; + + default: + adios (NULL, "format file syntax error: %s", bp); + } + } + fclose (fp); + + if (mhldebug) { + for (c1 = fmthd; c1; c1 = c1->c_next) { + fprintf (stderr, "c1: name=\"%s\" text=\"%s\" ovtxt=\"%s\"\n", + c1->c_name, c1->c_text, c1->c_ovtxt); + fprintf (stderr, "\tnfs=0x%x fmt=0x%x\n", + (unsigned int) c1->c_nfs, (unsigned int) c1->c_fmt); + fprintf (stderr, "\toffset=%d ovoff=%d width=%d cwidth=%d length=%d\n", + c1->c_offset, c1->c_ovoff, c1->c_width, + c1->c_cwidth, c1->c_length); + fprintf (stderr, "\tflags=%s\n", + snprintb (buffer, sizeof(buffer), (unsigned) c1->c_flags, LBITS)); + } + } + +out: + if (clearflg == 1) { + global.c_flags |= CLEARSCR; + } else { + if (clearflg == -1) + global.c_flags &= ~CLEARSCR; + } + + switch (bellflg) { /* command line may override format file */ + case 1: + global.c_flags |= BELL; + break; + case -1: + global.c_flags &= ~BELL; + break; + } + + if (length) + global.c_length = length; + if (width) + global.c_width = width; + if (global.c_length < 5) + global.c_length = 10000; + if (global.c_width < 5) + global.c_width = 10000; +} + + +static int +evalvar (struct mcomp *c1) +{ + char *cp, name[NAMESZ]; + struct triple *ap; + + if (!*parptr) + return 0; + strncpy (name, parse(), sizeof(name)); + + if (!strcasecmp (name, "component")) { + if (ptos (name, &c1->c_text)) + return 1; + c1->c_flags &= ~NOCOMPONENT; + return 0; + } + + if (!strcasecmp (name, "overflowtext")) + return ptos (name, &c1->c_ovtxt); + + if (!strcasecmp (name, "formatfield")) { + char *nfs; + + if (ptos (name, &cp)) + return 1; + nfs = new_fs (NULL, NULL, cp); + c1->c_nfs = getcpy (nfs); + c1->c_flags |= FORMAT; + return 0; + } + + if (!strcasecmp (name, "decode")) { + char *nfs; + + nfs = new_fs (NULL, NULL, "%(decode{text})"); + c1->c_nfs = getcpy (nfs); + c1->c_flags |= FORMAT; + return 0; + } + + if (!strcasecmp (name, "offset")) + return ptoi (name, &c1->c_offset); + if (!strcasecmp (name, "overflowoffset")) + return ptoi (name, &c1->c_ovoff); + if (!strcasecmp (name, "width")) + return ptoi (name, &c1->c_width); + if (!strcasecmp (name, "compwidth")) + return ptoi (name, &c1->c_cwidth); + if (!strcasecmp (name, "length")) + return ptoi (name, &c1->c_length); + if (!strcasecmp (name, "nodashstuffing")) + return (dashstuff = -1); + + for (ap = triples; ap->t_name; ap++) + if (!strcasecmp (ap->t_name, name)) { + c1->c_flags |= ap->t_on; + c1->c_flags &= ~ap->t_off; + return 0; + } + + return 1; +} + + +static int +ptoi (char *name, int *i) +{ + char *cp; + + if (*parptr++ != '=' || !*(cp = parse ())) { + advise (NULL, "missing argument to variable %s", name); + return 1; + } + + *i = atoi (cp); + return 0; +} + + +static int +ptos (char *name, char **s) +{ + char c, *cp; + + if (*parptr++ != '=') { + advise (NULL, "missing argument to variable %s", name); + return 1; + } + + if (*parptr != '"') { + for (cp = parptr; + *parptr && *parptr != ':' && *parptr != ','; + parptr++) + continue; + } else { + for (cp = ++parptr; *parptr && *parptr != '"'; parptr++) + if (*parptr == QUOTE) + if (!*++parptr) + parptr--; + } + c = *parptr; + *parptr = 0; + *s = getcpy (cp); + if ((*parptr = c) == '"') + parptr++; + return 0; +} + + +static char * +parse (void) +{ + int c; + char *cp; + static char result[NAMESZ]; + + for (cp = result; *parptr && (cp - result < NAMESZ); parptr++) { + c = *parptr; + if (isalnum (c) + || c == '.' + || c == '-' + || c == '_' + || c =='[' + || c == ']') + *cp++ = c; + else + break; + } + *cp = '\0'; + + return result; +} + + +static void +process (char *folder, char *fname, int ofilen, int ofilec) +{ + char *cp; + FILE *fp; + struct mcomp *c1; + + switch (setjmp (env)) { + case OK: + if (fname) { + fp = mhl_action ? (*mhl_action) (fname) : fopen (fname, "r"); + if (fp == NULL) { + advise (fname, "unable to open"); + exitstat++; + return; + } + } else { + fname = "(stdin)"; + fp = stdin; + } + cp = folder ? concat (folder, ":", fname, NULL) : getcpy (fname); + if (ontty != PITTY) + SIGNAL (SIGINT, intrser); + mhlfile (fp, cp, ofilen, ofilec); /* FALL THROUGH! */ + + default: + if (ontty != PITTY) + SIGNAL (SIGINT, SIG_IGN); + if (mhl_action == NULL && fp != stdin) + fclose (fp); + free (cp); + if (holder.c_text) { + free (holder.c_text); + holder.c_text = NULL; + } + free_queue (&msghd, &msgtl); + for (c1 = fmthd; c1; c1 = c1->c_next) + c1->c_flags &= ~HDROUTPUT; + break; + } +} + + +static void +mhlfile (FILE *fp, char *mname, int ofilen, int ofilec) +{ + int state; + struct mcomp *c1, *c2, *c3; + char **ip, name[NAMESZ], buf[BUFSIZ]; + + if (forwall) { + if (digest) + printf ("%s", ofilen == 1 ? delim3 : delim4); + else { + printf ("\n-------"); + if (ofilen == 1) + printf (" Forwarded Message%s", ofilec > 1 ? "s" : ""); + else + printf (" Message %d", ofilen); + printf ("\n\n"); + } + } else { + switch (ontty) { + case PITTY: + if (ofilec > 1) { + if (ofilen > 1) { + if ((global.c_flags & CLEARSCR)) + clear_screen (); + else + printf ("\n\n\n"); + } + printf (">>> %s\n\n", mname); + } + break; + + case ISTTY: + strncpy (buf, "\n", sizeof(buf)); + if (ofilec > 1) { + if (SOprintf ("Press to list \"%s\"...", mname)) { + if (ofilen > 1) + printf ("\n\n\n"); + printf ("Press to list \"%s\"...", mname); + } + fflush (stdout); + buf[0] = 0; + read (fileno (stdout), buf, sizeof(buf)); + } + if (strchr(buf, '\n')) { + if ((global.c_flags & CLEARSCR)) + clear_screen (); + } + else + printf ("\n"); + break; + + default: + if (ofilec > 1) { + if (ofilen > 1) { + printf ("\n\n\n"); + if (clearflg > 0) + clear_screen (); + } + printf (">>> %s\n\n", mname); + } + break; + } + } + + for (state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) { + case FLD: + case FLDPLUS: + for (ip = ignores; *ip; ip++) + if (!strcasecmp (name, *ip)) { + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), fp); + break; + } + if (*ip) + continue; + + for (c2 = fmthd; c2; c2 = c2->c_next) + if (!strcasecmp (c2->c_name, name)) + break; + c1 = NULL; + if (!((c3 = c2 ? c2 : &global)->c_flags & SPLIT)) + for (c1 = msghd; c1; c1 = c1->c_next) + if (!strcasecmp (c1->c_name, c3->c_name)) { + c1->c_text = + mcomp_add (c1->c_flags, buf, c1->c_text); + break; + } + if (c1 == NULL) + c1 = add_queue (&msghd, &msgtl, name, buf, 0); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), fp); + c1->c_text = add (buf, c1->c_text); + } + if (c2 == NULL) + c1->c_flags |= EXTRA; + continue; + + case BODY: + case FILEEOF: + row = column = 0; + for (c1 = fmthd; c1; c1 = c1->c_next) { + if (c1->c_flags & CLEARTEXT) { + putcomp (c1, c1, ONECOMP); + continue; + } + if (!strcasecmp (c1->c_name, "messagename")) { + holder.c_text = concat ("(Message ", mname, ")\n", + NULL); + putcomp (c1, &holder, ONECOMP); + free (holder.c_text); + holder.c_text = NULL; + continue; + } + if (!strcasecmp (c1->c_name, "extras")) { + for (c2 = msghd; c2; c2 = c2->c_next) + if (c2->c_flags & EXTRA) + putcomp (c1, c2, TWOCOMP); + continue; + } + if (dobody && !strcasecmp (c1->c_name, "body")) { + if ((holder.c_text = malloc (sizeof(buf))) == NULL) + adios (NULL, "unable to allocate buffer memory"); + strncpy (holder.c_text, buf, sizeof(buf)); + while (state == BODY) { + putcomp (c1, &holder, BODYCOMP); + state = m_getfld (state, name, holder.c_text, + sizeof(buf), fp); + } + free (holder.c_text); + holder.c_text = NULL; + continue; + } + for (c2 = msghd; c2; c2 = c2->c_next) + if (!strcasecmp (c2->c_name, c1->c_name)) { + putcomp (c1, c2, ONECOMP); + if (!(c1->c_flags & SPLIT)) + break; + } + if (faceproc && c2 == NULL && (c1->c_flags & FACEFMT)) + for (c2 = msghd; c2; c2 = c2->c_next) + if (c2->c_flags & FACEDFLT) { + if (c2->c_face == NULL) + face_format (c2); + if ((holder.c_text = c2->c_face)) { + putcomp (c1, &holder, ONECOMP); + holder.c_text = NULL; + } + break; + } + } + return; + + case LENERR: + case FMTERR: + advise (NULL, "format error in message %s", mname); + exitstat++; + return; + + default: + adios (NULL, "getfld() returned %d", state); + } + } +} + + +static int +mcomp_flags (char *name) +{ + struct pair *ap; + + for (ap = pairs; ap->p_name; ap++) + if (!strcasecmp (ap->p_name, name)) + return (ap->p_flags); + + return 0; +} + + +static char * +mcomp_add (long flags, char *s1, char *s2) +{ + char *dp; + + if (!(flags & ADDRFMT)) + return add (s1, s2); + + if (s2 && *(dp = s2 + strlen (s2) - 1) == '\n') + *dp = 0; + + return add (s1, add (",\n", s2)); +} + + +struct pqpair { + char *pq_text; + char *pq_error; + struct pqpair *pq_next; +}; + + +static void +mcomp_format (struct mcomp *c1, struct mcomp *c2) +{ + int dat[5]; + char *ap, *cp; + char buffer[BUFSIZ], error[BUFSIZ]; + struct comp *cptr; + struct pqpair *p, *q; + struct pqpair pq; + struct mailname *mp; + + ap = c2->c_text; + c2->c_text = NULL; + dat[0] = 0; + dat[1] = 0; + dat[2] = 0; + dat[3] = sizeof(buffer) - 1; + dat[4] = 0; + fmt_compile (c1->c_nfs, &c1->c_fmt); + + if (!(c1->c_flags & ADDRFMT)) { + FINDCOMP (cptr, "text"); + if (cptr) + cptr->c_text = ap; + if ((cp = strrchr(ap, '\n'))) /* drop ending newline */ + if (!cp[1]) + *cp = 0; + + fmt_scan (c1->c_fmt, buffer, sizeof(buffer) - 1, dat); + /* Don't need to append a newline, dctime() already did */ + c2->c_text = getcpy (buffer); + + free (ap); + return; + } + + (q = &pq)->pq_next = NULL; + while ((cp = getname (ap))) { + if ((p = (struct pqpair *) calloc ((size_t) 1, sizeof(*p))) == NULL) + adios (NULL, "unable to allocate pqpair memory"); + + if ((mp = getm (cp, NULL, 0, AD_NAME, error)) == NULL) { + p->pq_text = getcpy (cp); + p->pq_error = getcpy (error); + } else { + if ((c1->c_flags & FACEDFLT) && c2->c_face == NULL) { + char *h, *o; + if ((h = mp->m_host) == NULL) + h = LocalName (); + if ((o = OfficialName (h))) + h = o; + c2->c_face = concat ("address ", h, " ", mp->m_mbox, + NULL); + } + p->pq_text = getcpy (mp->m_text); + mnfree (mp); + } + q = (q->pq_next = p); + } + + for (p = pq.pq_next; p; p = q) { + FINDCOMP (cptr, "text"); + if (cptr) + cptr->c_text = p->pq_text; + FINDCOMP (cptr, "error"); + if (cptr) + cptr->c_text = p->pq_error; + + fmt_scan (c1->c_fmt, buffer, sizeof(buffer) - 1, dat); + if (*buffer) { + if (c2->c_text) + c2->c_text = add (",\n", c2->c_text); + if (*(cp = buffer + strlen (buffer) - 1) == '\n') + *cp = 0; + c2->c_text = add (buffer, c2->c_text); + } + + free (p->pq_text); + if (p->pq_error) + free (p->pq_error); + q = p->pq_next; + free ((char *) p); + } + + c2->c_text = add ("\n", c2->c_text); + free (ap); +} + + +static struct mcomp * +add_queue (struct mcomp **head, struct mcomp **tail, char *name, char *text, int flags) +{ + struct mcomp *c1; + + if ((c1 = (struct mcomp *) calloc ((size_t) 1, sizeof(*c1))) == NULL) + adios (NULL, "unable to allocate comp memory"); + + c1->c_flags = flags & ~INIT; + if ((c1->c_name = name ? getcpy (name) : NULL)) + c1->c_flags |= mcomp_flags (c1->c_name); + c1->c_text = text ? getcpy (text) : NULL; + if (flags & INIT) { + if (global.c_ovtxt) + c1->c_ovtxt = getcpy (global.c_ovtxt); + c1->c_offset = global.c_offset; + c1->c_ovoff = global. c_ovoff; + c1->c_width = c1->c_length = 0; + c1->c_cwidth = global.c_cwidth; + c1->c_flags |= global.c_flags & GFLAGS; + } + if (*head == NULL) + *head = c1; + if (*tail != NULL) + (*tail)->c_next = c1; + *tail = c1; + + return c1; +} + + +static void +free_queue (struct mcomp **head, struct mcomp **tail) +{ + struct mcomp *c1, *c2; + + for (c1 = *head; c1; c1 = c2) { + c2 = c1->c_next; + if (c1->c_name) + free (c1->c_name); + if (c1->c_text) + free (c1->c_text); + if (c1->c_ovtxt) + free (c1->c_ovtxt); + if (c1->c_nfs) + free (c1->c_nfs); + if (c1->c_fmt) + free ((char *) c1->c_fmt); + if (c1->c_face) + free (c1->c_face); + free ((char *) c1); + } + + *head = *tail = NULL; +} + + +static void +putcomp (struct mcomp *c1, struct mcomp *c2, int flag) +{ + int count, cchdr; + char *cp; + + cchdr = 0; + lm = 0; + llim = c1->c_length ? c1->c_length : -1; + wid = c1->c_width ? c1->c_width : global.c_width; + ovoff = (c1->c_ovoff >= 0 ? c1->c_ovoff : global.c_ovoff) + + c1->c_offset; + if ((ovtxt = c1->c_ovtxt ? c1->c_ovtxt : global.c_ovtxt) == NULL) + ovtxt = ""; + if (wid < ovoff + strlen (ovtxt) + 5) + adios (NULL, "component: %s width(%d) too small for overflow(%d)", + c1->c_name, wid, ovoff + strlen (ovtxt) + 5); + onelp = NULL; + + if (c1->c_flags & CLEARTEXT) { + putstr (c1->c_text); + putstr ("\n"); + return; + } + + if (c1->c_flags & FACEFMT) + switch (doface (c2)) { + case NOTOK: /* error */ + case OK: /* async faceproc */ + return; + + default: /* sync faceproc */ + break; + } + + if (c1->c_nfs && (c1->c_flags & (ADDRFMT | DATEFMT | FORMAT))) + mcomp_format (c1, c2); + + if (c1->c_flags & CENTER) { + count = (c1->c_width ? c1->c_width : global.c_width) + - c1->c_offset - strlen (c2->c_text); + if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT)) + count -= strlen (c1->c_text ? c1->c_text : c1->c_name) + 2; + lm = c1->c_offset + (count / 2); + } else { + if (c1->c_offset) + lm = c1->c_offset; + } + + if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT)) { + if (c1->c_flags & UPPERCASE) /* uppercase component also */ + for (cp = (c1->c_text ? c1->c_text : c1->c_name); *cp; cp++) + if (islower (*cp)) + *cp = toupper (*cp); + putstr (c1->c_text ? c1->c_text : c1->c_name); + if (flag != BODYCOMP) { + putstr (": "); + if (!(c1->c_flags & SPLIT)) + c1->c_flags |= HDROUTPUT; + + cchdr++; + if ((count = c1->c_cwidth - + strlen (c1->c_text ? c1->c_text : c1->c_name) - 2) > 0) + while (count--) + putstr (" "); + } + else + c1->c_flags |= HDROUTPUT; /* for BODYCOMP */ + } + + if (flag == TWOCOMP + && !(c2->c_flags & HDROUTPUT) + && !(c2->c_flags & NOCOMPONENT)) { + if (c1->c_flags & UPPERCASE) + for (cp = c2->c_name; *cp; cp++) + if (islower (*cp)) + *cp = toupper (*cp); + putstr (c2->c_name); + putstr (": "); + if (!(c1->c_flags & SPLIT)) + c2->c_flags |= HDROUTPUT; + + cchdr++; + if ((count = c1->c_cwidth - strlen (c2->c_name) - 2) > 0) + while (count--) + putstr (" "); + } + if (c1->c_flags & UPPERCASE) + for (cp = c2->c_text; *cp; cp++) + if (islower (*cp)) + *cp = toupper (*cp); + + count = 0; + if (cchdr) + if (flag == TWOCOMP) + count = (c1->c_cwidth >= 0) ? c1->c_cwidth + : strlen (c2->c_name) + 2; + else + count = (c1->c_cwidth >= 0) ? c1->c_cwidth + : strlen (c1->c_text ? c1->c_text : c1->c_name) + 2; + count += c1->c_offset; + + if ((cp = oneline (c2->c_text, c1->c_flags))) + putstr(cp); + if (term == '\n') + putstr ("\n"); + while ((cp = oneline (c2->c_text, c1->c_flags))) { + lm = count; + if (flag == BODYCOMP + && !(c1->c_flags & NOCOMPONENT)) + putstr (c1->c_text ? c1->c_text : c1->c_name); + if (*cp) + putstr (cp); + if (term == '\n') + putstr ("\n"); + } +} + + +static char * +oneline (char *stuff, long flags) +{ + int spc; + char *cp, *ret; + + if (onelp == NULL) + onelp = stuff; + if (*onelp == 0) + return (onelp = NULL); + + ret = onelp; + term = 0; + if (flags & COMPRESS) { + for (spc = 1, cp = ret; *onelp; onelp++) + if (isspace (*onelp)) { + if (*onelp == '\n' && (!onelp[1] || (flags & ADDRFMT))) { + term = '\n'; + *onelp++ = 0; + break; + } + else + if (!spc) { + *cp++ = ' '; + spc++; + } + } + else { + *cp++ = *onelp; + spc = 0; + } + + *cp = 0; + } + else { + while (*onelp && *onelp != '\n') + onelp++; + if (*onelp == '\n') { + term = '\n'; + *onelp++ = 0; + } + if (flags & LEFTADJUST) + while (*ret == ' ' || *ret == '\t') + ret++; + } + if (*onelp == 0 && term == '\n' && (flags & NONEWLINE)) + term = 0; + + return ret; +} + + +static void +putstr (char *string) +{ + if (!column && lm > 0) + while (lm > 0) + if (lm >= 8) { + putch ('\t'); + lm -= 8; + } + else { + putch (' '); + lm--; + } + lm = 0; + while (*string) + putch (*string++); +} + + +static void +putch (char ch) +{ + char buf[BUFSIZ]; + + if (llim == 0) + return; + + switch (ch) { + case '\n': + if (llim > 0) + llim--; + column = 0; + row++; + if (ontty != ISTTY || row != global.c_length) + break; + if (global.c_flags & BELL) + putchar ('\007'); + fflush (stdout); + buf[0] = 0; + read (fileno (stdout), buf, sizeof(buf)); + if (strchr(buf, '\n')) { + if (global.c_flags & CLEARSCR) + clear_screen (); + row = 0; + } else { + putchar ('\n'); + row = global.c_length / 3; + } + return; + + case '\t': + column |= 07; + column++; + break; + + case '\b': + column--; + break; + + case '\r': + column = 0; + break; + + default: + /* + * If we are forwarding this message, and the first + * column contains a dash, then add a dash and a space. + */ + if (column == 0 && forwflg && (dashstuff >= 0) && ch == '-') { + putchar ('-'); + putchar (' '); + } + if (ch >= ' ') + column++; + break; + } + + if (column >= wid) { + putch ('\n'); + if (ovoff > 0) + lm = ovoff; + putstr (ovtxt ? ovtxt : ""); + putch (ch); + return; + } + + putchar (ch); +} + + +static RETSIGTYPE +intrser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGINT, intrser); +#endif + + discard (stdout); + putchar ('\n'); + longjmp (env, DONE); +} + + +static RETSIGTYPE +pipeser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGPIPE, pipeser); +#endif + + done (NOTOK); +} + + +static RETSIGTYPE +quitser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGQUIT, quitser); +#endif + + putchar ('\n'); + fflush (stdout); + done (NOTOK); +} + + +static void +face_format (struct mcomp *c1) +{ + char *cp; + struct mailname *mp; + + if ((cp = c1->c_text) == NULL) + return; + + if ((cp = getname (cp))) { + if ((mp = getm (cp, NULL, 0, AD_NAME, NULL))) { + char *h, *o; + if ((h = mp->m_host) == NULL) + h = LocalName (); + if ((o = OfficialName (h))) + h = o; + c1->c_face = concat ("address ", h, " ", mp->m_mbox, NULL); + } + + while ((cp = getname (cp))) + continue; + } +} + + +/* + * faceproc is two elements defining the image agent's location: + * Internet host + * UDP port + */ + +#include +#include +#include + +#ifdef HAVE_ARPA_INET_H +# include +#endif + +static int +doface (struct mcomp *c1) +{ + int result, sd; + struct sockaddr_in in_socket; + struct sockaddr_in *isock = &in_socket; + static int inited = OK; + static int addrlen; + static struct in_addr addr; + static unsigned short portno; + + if (inited == OK) { + char *cp; + char **ap = brkstring (cp = getcpy (faceproc), " ", "\n"); + struct hostent *hp; + + if (ap[0] == NULL || ap[1] == NULL) { +bad_faceproc: ; + free (cp); + return (inited = NOTOK); + } + + if (!(hp = gethostbystring (ap[0]))) + goto bad_faceproc; + memcpy((char *) &addr, hp->h_addr, addrlen = hp->h_length); + + portno = htons ((unsigned short) atoi (ap[1])); + free (cp); + + inited = DONE; + } + if (inited == NOTOK) + return NOTOK; + + isock->sin_family = AF_INET; + isock->sin_port = portno; + memcpy((char *) &isock->sin_addr, (char *) &addr, addrlen); + + if ((sd = socket (AF_INET, SOCK_DGRAM, 0)) == NOTOK) + return NOTOK; + + result = sendto (sd, c1->c_text, strlen (c1->c_text), 0, + (struct sockaddr *) isock, sizeof(*isock)); + + close (sd); + + return (result != NOTOK ? OK : NOTOK); +} + +/* + * COMMENTED OUT + * This version doesn't use sockets + */ +#if 0 + +static int +doface (struct mcomp *c1) +{ + int i, len, vecp; + pid_t child_id; + int result, pdi[2], pdo[2]; + char *bp, *cp; + char buffer[BUFSIZ], *vec[10]; + + if (pipe (pdi) == NOTOK) + return NOTOK; + if (pipe (pdo) == NOTOK) { + close (pdi[0]); + close (pdi[1]); + return NOTOK; + } + + for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++) + sleep (5); + + switch (child_id) { + case NOTOK: + /* oops... fork error */ + return NOTOK; + + case OK: + /* child process */ + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); + if (pdi[0] != fileno (stdin)) { + dup2 (pdi[0], fileno (stdin)); + close (pdi[0]); + } + close (pdi[1]); + close (pdo[0]); + if (pdo[1] != fileno (stdout)) { + dup2 (pdo[1], fileno (stdout)); + close (pdo[1]); + } + vecp = 0; + vec[vecp++] = r1bindex (faceproc, '/'); + vec[vecp++] = "-e"; + if (sleepsw != NOTOK) { + vec[vecp++] = "-s"; + snprintf (buffer, sizeof(buffer), "%d", sleepsw); + vec[vecp++] = buffer; + } + vec[vecp] = NULL; + execvp (faceproc, vec); + fprintf (stderr, "unable to exec "); + perror (faceproc); + _exit (-1); /* NOTREACHED */ + + default: + /* parent process */ + close (pdi[0]); + i = strlen (c1->c_text); + if (write (pdi[1], c1->c_text, i) != i) + adios ("pipe", "error writing to"); + free (c1->c_text), c1->c_text = NULL; + close (pdi[1]); + + close (pdo[1]); + cp = NULL, len = 0; + result = DONE; + while ((i = read (pdo[0], buffer, strlen (buffer))) > 0) { + if (cp) { + int j; + char *dp; + if ((dp = realloc (cp, (unsigned) (j = len + i))) == NULL) + adios (NULL, "unable to allocate face storage"); + memcpy(dp + len, buffer, i); + cp = dp, len = j; + } + else { + if ((cp = malloc ((unsigned) i)) == NULL) + adios (NULL, "unable to allocate face storage"); + memcpy(cp, buffer, i); + len = i; + } + if (result == DONE) + for (bp = buffer + i - 1; bp >= buffer; bp--) + if (!isascii (*bp) || iscntrl (*bp)) { + result = OK; + break; + } + } + close (pdo[0]); + +/* no waiting for child... */ + + if (result == OK) { /* binary */ + if (write (1, cp, len) != len) + adios ("writing", "error"); + free (cp); + } + else /* empty */ + if ((c1->c_text = cp) == NULL) + result = OK; + break; + } + + return result; +} +#endif /* COMMENTED OUT */ + + +int +mhlsbr (int argc, char **argv, FILE *(*action)()) +{ + SIGNAL_HANDLER istat, pstat, qstat; + char *cp; + struct mcomp *c1; + + switch (setjmp (mhlenv)) { + case OK: + cp = invo_name; + sleepsw = 0; /* XXX */ + bellflg = clearflg = forwflg = forwall = exitstat = 0; + digest = NULL; + ontty = NOTTY; + mhl_action = action; + + /* + * If signal is at default action, then start ignoring + * it, else let it set to its current action. + */ + if ((istat = SIGNAL (SIGINT, SIG_IGN)) != SIG_DFL) + SIGNAL (SIGINT, istat); + if ((qstat = SIGNAL (SIGQUIT, SIG_IGN)) != SIG_DFL) + SIGNAL (SIGQUIT, qstat); + pstat = SIGNAL (SIGPIPE, pipeser); + mhl (argc, argv); /* FALL THROUGH! */ + + default: + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + SIGNAL (SIGPIPE, SIG_IGN);/* should probably change to block instead */ + if (ontty == PITTY) + m_pclose (); + SIGNAL (SIGPIPE, pstat); + invo_name = cp; + if (holder.c_text) { + free (holder.c_text); + holder.c_text = NULL; + } + free_queue (&msghd, &msgtl); + for (c1 = fmthd; c1; c1 = c1->c_next) + c1->c_flags &= ~HDROUTPUT; + return exitstat; + } +} + +#undef adios +#undef done + +static void +mhladios (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); + mhldone (1); +} + + +static void +mhldone (int status) +{ + exitstat = status; + if (mhl_action) + longjmp (mhlenv, DONE); + else + done (exitstat); +} + + +static int m_pid = NOTOK; +static int sd = NOTOK; + +static void +m_popen (char *name) +{ + int pd[2]; + + if (mhl_action && (sd = dup (fileno (stdout))) == NOTOK) + adios ("standard output", "unable to dup()"); + + if (pipe (pd) == NOTOK) + adios ("pipe", "unable to"); + + switch (m_pid = vfork ()) { + case NOTOK: + adios ("fork", "unable to"); + + case OK: + SIGNAL (SIGINT, SIG_DFL); + SIGNAL (SIGQUIT, SIG_DFL); + + close (pd[1]); + if (pd[0] != fileno (stdin)) { + dup2 (pd[0], fileno (stdin)); + close (pd[0]); + } + execlp (name, r1bindex (name, '/'), NULL); + fprintf (stderr, "unable to exec "); + perror (name); + _exit (-1); + + default: + close (pd[0]); + if (pd[1] != fileno (stdout)) { + dup2 (pd[1], fileno (stdout)); + close (pd[1]); + } + } +} + + +void +m_pclose (void) +{ + if (m_pid == NOTOK) + return; + + if (sd != NOTOK) { + fflush (stdout); + if (dup2 (sd, fileno (stdout)) == NOTOK) + adios ("standard output", "unable to dup2()"); + + clearerr (stdout); + close (sd); + sd = NOTOK; + } + else + fclose (stdout); + + pidwait (m_pid, OK); + m_pid = NOTOK; +} diff --git a/uip/mhmail.c b/uip/mhmail.c new file mode 100644 index 0000000..f9dc33b --- /dev/null +++ b/uip/mhmail.c @@ -0,0 +1,204 @@ + +/* + * mhmail.c -- simple mail program + * + * $Id$ + */ + +#include +#include +#include + +static struct swit switches[] = { +#define BODYSW 0 + { "body text", 0 }, +#define CCSW 1 + { "cc addrs ...", 0 }, +#define FROMSW 2 + { "from addr", 0 }, +#define SUBJSW 3 + { "subject text", 0 }, +#define VERSIONSW 4 + { "version", 0 }, +#define HELPSW 5 + { "help", 4 }, +#define RESNDSW 6 + { "resent", -6 }, +#define QUEUESW 7 + { "queued", -6 }, + { NULL, 0 } +}; + +static char tmpfil[BUFSIZ]; + +/* + * static prototypes + */ +static RETSIGTYPE intrser (int); + + +int +main (int argc, char **argv) +{ + pid_t child_id; + int status, i, iscc = 0, nvec; + int queued = 0, resent = 0, somebody; + char *cp, *tolist = NULL, *cclist = NULL, *subject = NULL; + char *from = NULL, *body = NULL, **argp, **arguments; + char *vec[5], buf[BUFSIZ]; + FILE *out; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + /* If no arguments, just incorporate new mail */ + if (argc == 1) { + execlp (incproc, r1bindex (incproc, '/'), NULL); + adios (incproc, "unable to exec"); + } + + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [addrs ... [switches]]", + invo_name); + print_help (buf, switches, 0); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case FROMSW: + if (!(from = *argp++) || *from == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case BODYSW: + if (!(body = *argp++) || *body == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case CCSW: + iscc++; + continue; + + case SUBJSW: + if (!(subject = *argp++) || *subject == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case RESNDSW: + resent++; + continue; + + case QUEUESW: + queued++; + continue; + } + } + if (iscc) + cclist = cclist ? add (cp, add (", ", cclist)) : getcpy (cp); + else + tolist = tolist ? add (cp, add (", ", tolist)) : getcpy (cp); + } + + if (tolist == NULL) + adios (NULL, "usage: %s addrs ... [switches]", invo_name); + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((out = fopen (tmpfil, "w")) == NULL) + adios (tmpfil, "unable to write"); + chmod (tmpfil, 0600); + + SIGNAL2 (SIGINT, intrser); + + fprintf (out, "%sTo: %s\n", resent ? "Resent-" : "", tolist); + if (cclist) + fprintf (out, "%scc: %s\n", resent ? "Resent-" : "", cclist); + if (subject) + fprintf (out, "%sSubject: %s\n", resent ? "Resent-" : "", subject); + if (from) + fprintf (out, "%sFrom: %s\n", resent ? "Resent-" : "", from); + if (!resent) + fputs ("\n", out); + + if (body) { + fprintf (out, "%s", body); + if (*body && *(body + strlen (body) - 1) != '\n') + fputs ("\n", out); + } else { + for (somebody = 0; + (i = fread (buf, sizeof(*buf), sizeof(buf), stdin)) > 0; + somebody++) + if (fwrite (buf, sizeof(*buf), i, out) != i) + adios (tmpfil, "error writing"); + if (!somebody) { + unlink (tmpfil); + done (1); + } + } + fclose (out); + + nvec = 0; + vec[nvec++] = r1bindex (postproc, '/'); + vec[nvec++] = tmpfil; + if (resent) + vec[nvec++] = "-dist"; + if (queued) + vec[nvec++] = "-queued"; + vec[nvec] = NULL; + + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep (5); + + if (child_id == NOTOK) { + /* report failure and then send it */ + admonish (NULL, "unable to fork"); + } else if (child_id) { + /* parent process */ + if ((status = pidXwait(child_id, postproc))) { + fprintf (stderr, "Letter saved in dead.letter\n"); + execl ("/bin/mv", "mv", tmpfil, "dead.letter", NULL); + execl ("/usr/bin/mv", "mv", tmpfil, "dead.letter", NULL); + perror ("mv"); + _exit (-1); + } + unlink (tmpfil); + done (status ? 1 : 0); + } else { + /* child process */ + execvp (postproc, vec); + fprintf (stderr, "unable to exec "); + perror (postproc); + _exit (-1); + } +} + + +static RETSIGTYPE +intrser (int i) +{ +#ifndef RELIABLE_SIGNALS + if (i) + SIGNAL (i, SIG_IGN); +#endif + + unlink (tmpfil); + done (i != 0 ? 1 : 0); +} + diff --git a/uip/mhmisc.c b/uip/mhmisc.c new file mode 100644 index 0000000..75ac158 --- /dev/null +++ b/uip/mhmisc.c @@ -0,0 +1,231 @@ + +/* + * mhparse.c -- misc routines to process MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include + +extern int errno; +extern int debugsw; + +/* + * limit actions to specified parts or content types + */ +int npart = 0; +int ntype = 0; +char *parts[NPARTS + 1]; +char *types[NTYPES + 1]; + +int endian = 0; /* little or big endian */ +int userrs = 0; + +static char *errs = NULL; + + +/* + * prototypes + */ +int part_ok (CT, int); +int type_ok (CT, int); +void set_endian (void); +int make_intermediates (char *); +void content_error (char *, CT, char *, ...); +void flush_errors (void); + + +int +part_ok (CT ct, int sP) +{ + char **ap; + + if (npart == 0 || (ct->c_type == CT_MULTIPART && (sP || ct->c_subtype))) + return 1; + + for (ap = parts; *ap; ap++) + if (!strcmp (*ap, ct->c_partno)) + return 1; + + return 0; +} + + +int +type_ok (CT ct, int sP) +{ + char **ap; + char buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + if (ntype == 0 || (ct->c_type == CT_MULTIPART && (sP || ct->c_subtype))) + return 1; + + snprintf (buffer, sizeof(buffer), "%s/%s", ci->ci_type, ci->ci_subtype); + for (ap = types; *ap; ap++) + if (!strcasecmp (*ap, ci->ci_type) || !strcasecmp (*ap, buffer)) + return 1; + + return 0; +} + + +void +set_endian (void) +{ + union { + long l; + char c[sizeof(long)]; + } un; + + un.l = 1; + endian = un.c[0] ? -1 : 1; + if (debugsw) + fprintf (stderr, "%s endian architecture\n", + endian > 0 ? "big" : "little"); +} + + +int +make_intermediates (char *file) +{ + char *cp; + + for (cp = file + 1; cp = strchr(cp, '/'); cp++) { + struct stat st; + + *cp = '\0'; + if (stat (file, &st) == NOTOK) { + int answer; + char *ep; + if (errno != ENOENT) { + advise (file, "error on directory"); +losing_directory: + *cp = '/'; + return NOTOK; + } + + ep = concat ("Create directory \"", file, "\"? ", NULL); + answer = getanswer (ep); + free (ep); + + if (!answer) + goto losing_directory; + if (!makedir (file)) { + advise (NULL, "unable to create directory %s", file); + goto losing_directory; + } + } + + *cp = '/'; + } + + return OK; +} + + +/* + * Construct error message for content + */ + +void +content_error (char *what, CT ct, char *fmt, ...) +{ + va_list arglist; + int i, len, buflen; + char *bp, buffer[BUFSIZ]; + CI ci; + + bp = buffer; + buflen = sizeof(buffer); + + if (userrs && invo_name && *invo_name) { + snprintf (bp, buflen, "%s: ", invo_name); + len = strlen (bp); + bp += len; + buflen -= len; + } + + va_start (arglist, fmt); + + vsnprintf (bp, buflen, fmt, arglist); + len = strlen (bp); + bp += len; + buflen -= len; + + ci = &ct->c_ctinfo; + + if (what) { + char *s; + + if (*what) { + snprintf (bp, buflen, " %s: ", what); + len = strlen (bp); + bp += len; + buflen -= len; + } + + if ((s = strerror (errno))) + snprintf (bp, buflen, "%s", s); + else + snprintf (bp, buflen, "Error %d", errno); + + len = strlen (bp); + bp += len; + buflen -= len; + } + + i = strlen (invo_name) + 2; + + /* Now add content type and subtype */ + snprintf (bp, buflen, "\n%*.*s(content %s/%s", i, i, "", + ci->ci_type, ci->ci_subtype); + len = strlen (bp); + bp += len; + buflen -= len; + + /* Now add the message/part number */ + if (ct->c_file) { + snprintf (bp, buflen, " in message %s", ct->c_file); + len = strlen (bp); + bp += len; + buflen -= len; + + if (ct->c_partno) { + snprintf (bp, buflen, ", part %s", ct->c_partno); + len = strlen (bp); + bp += len; + buflen -= len; + } + } + + snprintf (bp, buflen, ")"); + len = strlen (bp); + bp += len; + buflen -= len; + + if (userrs) { + *bp++ = '\n'; + *bp = '\0'; + buflen--; + + errs = add (buffer, errs); + } else { + advise (NULL, "%s", buffer); + } +} + + +void +flush_errors (void) +{ + if (errs) { + fflush (stdout); + fprintf (stderr, "%s", errs); + free (errs); + errs = NULL; + } +} diff --git a/uip/mhn.c b/uip/mhn.c new file mode 100644 index 0000000..70f730b --- /dev/null +++ b/uip/mhn.c @@ -0,0 +1,741 @@ + +/* + * mhn.c -- display, list, cache, or store the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +/* + * We allocate space for message names (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define AUTOSW 0 + { "auto", 0 }, +#define NAUTOSW 1 + { "noauto", 0 }, +#define CACHESW 2 + { "cache", 0 }, +#define NCACHESW 3 + { "nocache", 0 }, +#define CHECKSW 4 + { "check", 0 }, +#define NCHECKSW 5 + { "nocheck", 0 }, +#define HEADSW 6 + { "headers", 0 }, +#define NHEADSW 7 + { "noheaders", 0 }, +#define LISTSW 8 + { "list", 0 }, +#define NLISTSW 9 + { "nolist", 0 }, +#define PAUSESW 10 + { "pause", 0 }, +#define NPAUSESW 11 + { "nopause", 0 }, +#define SIZESW 12 + { "realsize", 0 }, +#define NSIZESW 13 + { "norealsize", 0 }, +#define SERIALSW 14 + { "serialonly", 0 }, +#define NSERIALSW 15 + { "noserialonly", 0 }, +#define SHOWSW 16 + { "show", 0 }, +#define NSHOWSW 17 + { "noshow", 0 }, +#define STORESW 18 + { "store", 0 }, +#define NSTORESW 19 + { "nostore", 0 }, +#define VERBSW 20 + { "verbose", 0 }, +#define NVERBSW 21 + { "noverbose", 0 }, +#define FILESW 22 /* interface from show */ + { "file file", 0 }, +#define FORMSW 23 + { "form formfile", 0 }, +#define PARTSW 24 + { "part number", 0 }, +#define TYPESW 25 + { "type content", 0 }, +#define RCACHESW 26 + { "rcache policy", 0 }, +#define WCACHESW 27 + { "wcache policy", 0 }, +#define VERSIONSW 28 + { "version", 0 }, +#define HELPSW 29 + { "help", 4 }, + +/* + * switches for debugging + */ +#define DEBUGSW 30 + { "debug", -5 }, + +/* + * switches for moreproc/mhlproc + */ +#define PROGSW 31 + { "moreproc program", -4 }, +#define NPROGSW 32 + { "nomoreproc", -3 }, +#define LENSW 33 + { "length lines", -4 }, +#define WIDTHSW 34 + { "width columns", -4 }, + +/* + * switches for mhbuild + */ +#define BUILDSW 35 + { "build", -5 }, +#define NBUILDSW 36 + { "nobuild", -7 }, +#define EBCDICSW 37 + { "ebcdicsafe", -10 }, +#define NEBCDICSW 38 + { "noebcdicsafe", -12 }, +#define RFC934SW 39 + { "rfc934mode", -10 }, +#define NRFC934SW 40 + { "norfc934mode", -12 }, + { NULL, 0 } +}; + + +extern int errno; + +/* mhparse.c */ +extern int checksw; +extern char *tmp; /* directory to place temp files */ + +/* mhcachesbr.c */ +extern int rcachesw; +extern int wcachesw; +extern char *cache_public; +extern char *cache_private; + +/* mhshowsbr.c */ +extern int pausesw; +extern int serialsw; +extern char *progsw; +extern int nolist; +extern int nomore; /* flags for moreproc/header display */ +extern char *formsw; + +/* mhstoresbr.c */ +extern int autosw; +extern char *cwd; /* cache current working directory */ + +/* mhmisc.c */ +extern int npart; +extern int ntype; +extern char *parts[NPARTS + 1]; +extern char *types[NTYPES + 1]; +extern int userrs; + +int debugsw = 0; +int verbosw = 0; + +/* The list of top-level contents to display */ +CT *cts = NULL; + +/* + * variables for mhbuild (mhn -build) + */ +static int buildsw = 0; +static int ebcdicsw = 0; +static int rfc934sw = 0; + +/* + * what action to take? + */ +static int cachesw = 0; +static int listsw = 0; +static int showsw = 0; +static int storesw = 0; + +#define quitser pipeser + +/* mhparse.c */ +CT parse_mime (char *); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void set_endian (void); +void flush_errors (void); + +/* mhshowsbr.c */ +void show_all_messages (CT *); + +/* mhlistsbr.c */ +void list_all_messages (CT *, int, int, int, int); + +/* mhstoresbr.c */ +void store_all_messages (CT *); + +/* mhcachesbr.c */ +void cache_all_messages (CT *); + +/* mhfree.c */ +void free_content (CT); + +/* + * static prototypes + */ +static RETSIGTYPE pipeser (int); + + +int +main (int argc, char **argv) +{ + int sizesw = 1, headsw = 1; + int nummsgs, maxmsgs, msgnum, *icachesw; + char *cp, *file = NULL, *folder = NULL; + char *maildir, buf[100], **argp; + char **arguments, **msgs; + struct msgs *mp = NULL; + CT ct, *ctp; + FILE *fp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case AUTOSW: + autosw++; + continue; + case NAUTOSW: + autosw = 0; + continue; + + case CACHESW: + cachesw++; + continue; + case NCACHESW: + cachesw = 0; + continue; + + case RCACHESW: + icachesw = &rcachesw; + goto do_cache; + case WCACHESW: + icachesw = &wcachesw; +do_cache: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + switch (*icachesw = smatch (cp, caches)) { + case AMBIGSW: + ambigsw (cp, caches); + done (1); + case UNKWNSW: + adios (NULL, "%s unknown", cp); + default: + break; + } + continue; + + case CHECKSW: + checksw++; + continue; + case NCHECKSW: + checksw = 0; + continue; + + case HEADSW: + headsw = 1; + continue; + case NHEADSW: + headsw = 0; + continue; + + case LISTSW: + listsw = 1; + continue; + case NLISTSW: + listsw = 0; + continue; + + case PAUSESW: + pausesw = 1; + continue; + case NPAUSESW: + pausesw = 0; + continue; + + case SERIALSW: + serialsw = 1; + continue; + case NSERIALSW: + serialsw = 0; + continue; + + case SHOWSW: + showsw = 1; + continue; + case NSHOWSW: + showsw = 0; + continue; + + case SIZESW: + sizesw = 1; + continue; + case NSIZESW: + sizesw = 0; + continue; + + case STORESW: + storesw = 1; + continue; + case NSTORESW: + storesw = 0; + continue; + + case PARTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (npart >= NPARTS) + adios (NULL, "too many parts (starting with %s), %d max", + cp, NPARTS); + parts[npart++] = cp; + continue; + + case TYPESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (ntype >= NTYPES) + adios (NULL, "too many types (starting with %s), %d max", + cp, NTYPES); + types[ntype++] = cp; + continue; + + case FILESW: + if (!(cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + file = *cp == '-' ? cp : path (cp, TFILE); + continue; + + case FORMSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (formsw) + free (formsw); + formsw = getcpy (etcpath (cp)); + continue; + + /* + * Switches for moreproc/mhlproc + */ + case PROGSW: + if (!(progsw = *argp++) || *progsw == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NPROGSW: + nomore++; + continue; + + case LENSW: + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + /* + * Switches for mhbuild + */ + case BUILDSW: + buildsw = 1; + continue; + case NBUILDSW: + buildsw = 0; + continue; + case RFC934SW: + rfc934sw = 1; + continue; + case NRFC934SW: + rfc934sw = -1; + continue; + case EBCDICSW: + ebcdicsw = 1; + continue; + case NEBCDICSW: + ebcdicsw = -1; + continue; + + case VERBSW: + verbosw = 1; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + /* null terminate the list of acceptable parts/types */ + parts[npart] = NULL; + types[ntype] = NULL; + + set_endian (); + + if ((cp = getenv ("MM_NOASK")) && !strcmp (cp, "1")) { + nolist = 1; + listsw = 0; + pausesw = 0; + } + + /* + * Check if we've specified an additional profile + */ + if ((cp = getenv ("MHN"))) { + if ((fp = fopen (cp, "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } else { + admonish ("", "unable to read $MHN profile (%s)", cp); + } + } + + /* + * Read the standard profile setup + */ + if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } + + /* Check for public cache location */ + if ((cache_public = context_find (nmhcache)) && *cache_public != '/') + cache_public = NULL; + + /* Check for private cache location */ + if (!(cache_private = context_find (nmhprivcache))) + cache_private = ".cache"; + cache_private = getcpy (m_maildir (cache_private)); + + /* + * Cache the current directory before we do any chdirs()'s. + */ + cwd = getcpy (pwd()); + + /* + * Check for storage directory. If specified, + * then store temporary files there. Else we + * store them in standard nmh directory. + */ + if ((cp = context_find (nmhstorage)) && *cp) + tmp = concat (cp, "/", invo_name, NULL); + else + tmp = add (m_maildir (invo_name), NULL); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* + * Process a mhn composition file (mhn -build) + */ + if (buildsw) { + char *vec[MAXARGS]; + int vecp; + + if (showsw || storesw || cachesw) + adios (NULL, "cannot use -build with -show, -store, -cache"); + if (nummsgs < 1) + adios (NULL, "need to specify a %s composition file", invo_name); + if (nummsgs > 1) + adios (NULL, "only one %s composition file at a time", invo_name); + + vecp = 0; + vec[vecp++] = "mhbuild"; + + if (ebcdicsw == 1) + vec[vecp++] = "-ebcdicsafe"; + else if (ebcdicsw == -1) + vec[vecp++] = "-noebcdicsafe"; + + if (rfc934sw == 1) + vec[vecp++] = "-rfc934mode"; + else if (rfc934sw == -1) + vec[vecp++] = "-norfc934mode"; + + vec[vecp++] = msgs[0]; + vec[vecp] = NULL; + + execvp ("mhbuild", vec); + fprintf (stderr, "unable to exec "); + _exit (-1); + } + + /* + * Process a mhn composition file (old MH style) + */ + if (nummsgs == 1 && !folder && !npart && !cachesw + && !showsw && !storesw && !ntype && !file + && (cp = getenv ("mhdraft")) + && strcmp (cp, msgs[0]) == 0) { + + char *vec[MAXARGS]; + int vecp; + + vecp = 0; + vec[vecp++] = "mhbuild"; + + if (ebcdicsw == 1) + vec[vecp++] = "-ebcdicsafe"; + else if (ebcdicsw == -1) + vec[vecp++] = "-noebcdicsafe"; + + if (rfc934sw == 1) + vec[vecp++] = "-rfc934mode"; + else if (rfc934sw == -1) + vec[vecp++] = "-norfc934mode"; + + vec[vecp++] = cp; + vec[vecp] = NULL; + + execvp ("mhbuild", vec); + fprintf (stderr, "unable to exec "); + _exit (-1); + } + + if (file && nummsgs) + adios (NULL, "cannot specify msg and file at same time!"); + + /* + * check if message is coming from file + */ + if (file) { + if (!(cts = (CT *) calloc ((size_t) 2, sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + if ((ct = parse_mime (file))); + *ctp++ = ct; + } else { + /* + * message(s) are coming from a folder + */ + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (!(cts = (CT *) calloc ((size_t) (mp->numsel + 1), sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + char *msgnam; + + msgnam = m_name (msgnum); + if ((ct = parse_mime (msgnam))) + *ctp++ = ct; + } + } + } + + if (!*cts) + done (1); + + /* + * You can't give more than one of these flags + * at a time. + */ + if (showsw + listsw + storesw + cachesw > 1) + adios (NULL, "can only use one of -show, -list, -store, -cache at same time"); + + /* If no action is specified, assume -show */ + if (!listsw && !showsw && !storesw && !cachesw) + showsw = 1; + + userrs = 1; + SIGNAL (SIGQUIT, quitser); + SIGNAL (SIGPIPE, pipeser); + + /* + * Get the associated umask for the relevant contents. + */ + for (ctp = cts; *ctp; ctp++) { + struct stat st; + + ct = *ctp; + if (type_ok (ct, 1) && !ct->c_umask) { + if (stat (ct->c_file, &st) != NOTOK) + ct->c_umask = ~(st.st_mode & 0777); + else + ct->c_umask = ~m_gmprot(); + } + } + + /* + * List the message content + */ + if (listsw) + list_all_messages (cts, headsw, sizesw, verbosw, debugsw); + + /* + * Store the message content + */ + if (storesw) + store_all_messages (cts); + + /* + * Cache the message content + */ + if (cachesw) + cache_all_messages (cts); + + /* + * Show the message content + */ + if (showsw) + show_all_messages (cts); + + /* Now free all the structures for the content */ + for (ctp = cts; *ctp; ctp++) + free_content (*ctp); + + free ((char *) cts); + cts = NULL; + + /* If reading from a folder, do some updating */ + if (mp) { + context_replace (pfolder, folder);/* update current folder */ + seq_setcur (mp, mp->hghsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + done (0); + /* NOTREACHED */ +} + + +static RETSIGTYPE +pipeser (int i) +{ + if (i == SIGQUIT) { + unlink ("core"); + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + done (1); + /* NOTREACHED */ +} + + +void +done (int status) +{ + CT *ctp; + + if ((ctp = cts)) + for (; *ctp; ctp++) + free_content (*ctp); + + exit (status); +} diff --git a/uip/mhoutsbr.c b/uip/mhoutsbr.c new file mode 100644 index 0000000..6d2914a --- /dev/null +++ b/uip/mhoutsbr.c @@ -0,0 +1,471 @@ + +/* + * mhoutsbr.c -- routines to output MIME messages + * -- given a Content structure + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + + +extern int errno; +extern int ebcdicsw; + +static char ebcdicsafe[0x100] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static char nib2b64[0x40+1] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * prototypes + */ +int output_message (CT, char *); +int writeBase64aux (FILE *, FILE *); + +/* + * static prototypes + */ +static int output_content (CT, FILE *); +static int output_headers (CT, FILE *); +static int writeExternalBody (CT, FILE *); +static int write8Bit (CT, FILE *); +static int writeQuoted (CT, FILE *); +static int writeBase64 (CT, FILE *); + + +/* + * Main routine to output a MIME message contained + * in a Content structure, to a file. Any necessary + * transfer encoding is added. + */ + +int +output_message (CT ct, char *file) +{ + FILE *fp; + + if ((fp = fopen (file, "w")) == NULL) { + advise (file, "unable to open for writing"); + return NOTOK; + } + + if (output_content (ct, fp) == NOTOK) + return NOTOK; + + if (fflush (fp)) { + advise (file, "error writing to"); + return NOTOK; + } + fclose (fp); + + return OK; +} + + +/* + * Output a Content structure to a file. + */ + +static int +output_content (CT ct, FILE *out) +{ + int result = 0; + CI ci = &ct->c_ctinfo; + + /* + * Output all header fields for this content + */ + output_headers (ct, out); + + /* + * If this is the internal content structure for a + * "message/external", then we are done with the + * headers (since it has no body). + */ + if (ct->c_ctexbody) + return OK; + + /* + * Now output the content bodies. + */ + switch (ct->c_type) { + case CT_MULTIPART: + { + struct multipart *m; + struct part *part; + + if (ct->c_rfc934) + putc ('\n', out); + + m = (struct multipart *) ct->c_ctparams; + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + fprintf (out, "\n--%s\n", ci->ci_values[0]); + if (output_content (p, out) == NOTOK) + return NOTOK; + } + fprintf (out, "\n--%s--\n", ci->ci_values[0]); + } + break; + + case CT_MESSAGE: + putc ('\n', out); + if (ct->c_subtype == MESSAGE_EXTERNAL) { + struct exbody *e; + + e = (struct exbody *) ct->c_ctparams; + if (output_content (e->eb_content, out) == NOTOK) + return NOTOK; + + /* output phantom body for access-type "mail-server" */ + if (e->eb_body) + writeExternalBody (ct, out); + } else { + result = write8Bit (ct, out); + } + break; + + /* + * Handle discrete types (text/application/audio/image/video) + */ + default: + switch (ct->c_encoding) { + case CE_7BIT: + putc ('\n', out); + result = write8Bit (ct, out); + break; + + case CE_8BIT: + putc ('\n', out); + result = write8Bit (ct, out); + break; + + case CE_QUOTED: + putc ('\n', out); + result = writeQuoted (ct, out); + break; + + case CE_BASE64: + putc ('\n', out); + result = writeBase64 (ct, out); + break; + + case CE_BINARY: + advise (NULL, "can't handle binary transfer encoding in content"); + result = NOTOK; + break; + + default: + advise (NULL, "unknown transfer encoding in content"); + result = NOTOK; + break; + } + break; + } + + return result; +} + + +/* + * Output all the header fields for a content + */ + +static int +output_headers (CT ct, FILE *out) +{ + HF hp; + + hp = ct->c_first_hf; + while (hp) { + fprintf (out, "%s:%s", hp->name, hp->value); + hp = hp->next; + } +} + + +/* + * Write the phantom body for access-type "mail-server". + */ + +static int +writeExternalBody (CT ct, FILE *out) +{ + char **ap, **ep, *cp; + struct exbody *e = (struct exbody *) ct->c_ctparams; + + putc ('\n', out); + for (cp = e->eb_body; *cp; cp++) { + CT ct2 = e->eb_content; + CI ci2 = &ct2->c_ctinfo; + + if (*cp == '\\') { + switch (*++cp) { + case 'I': + if (ct2->c_id) { + char *dp = trimcpy (ct2->c_id); + + fputs (dp, out); + free (dp); + } + continue; + + case 'N': + for (ap = ci2->ci_attrs, ep = ci2->ci_values; *ap; ap++, ep++) + if (!strcasecmp (*ap, "name")) { + fprintf (out, "%s", *ep); + break; + } + continue; + + case 'T': + fprintf (out, "%s/%s", ci2->ci_type, ci2->ci_subtype); + for (ap = ci2->ci_attrs, ep = ci2->ci_values; *ap; ap++, ep++) + fprintf (out, "; %s=\"%s\"", *ap, *ep); + continue; + + case 'n': + putc ('\n', out); + continue; + + case 't': + putc ('\t', out); + continue; + + case '\0': + cp--; + break; + + case '\\': + case '"': + break; + + default: + putc ('\\', out); + break; + } + } + putc (*cp, out); + } + putc ('\n', out); + + return OK; +} + + +/* + * Output a content without any transfer encoding + */ + +static int +write8Bit (CT ct, FILE *out) +{ + int fd; + char c, *file, buffer[BUFSIZ]; + CE ce = ct->c_cefile; + + file = NULL; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return NOTOK; + + c = '\n'; + while (fgets (buffer, sizeof(buffer) - 1, ce->ce_fp)) { + c = buffer[strlen (buffer) - 1]; + fputs (buffer, out); + } + if (c != '\n') + putc ('\n', out); + + (*ct->c_ceclosefnx) (ct); + return OK; +} + + +/* + * Output a content using quoted-printable + */ + +static int +writeQuoted (CT ct, FILE *out) +{ + int fd; + char *cp, *file; + char c, buffer[BUFSIZ]; + CE ce = ct->c_cefile; + + file = NULL; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return NOTOK; + + while (fgets (buffer, sizeof(buffer) - 1, ce->ce_fp)) { + int n; + + cp = buffer + strlen (buffer) - 1; + if ((c = *cp) == '\n') + *cp = '\0'; + + if (strncmp (cp = buffer, "From ", sizeof("From ") - 1) == 0) { + fprintf (out, "=%02X", *cp++ & 0xff); + n = 3; + } else { + n = 0; + } + for (; *cp; cp++) { + if (n > CPERLIN - 3) { + fputs ("=\n", out); + n = 0; + } + + switch (*cp) { + case ' ': + case '\t': + putc (*cp, out); + n++; + break; + + default: + if (*cp < '!' || *cp > '~' + || (ebcdicsw && !ebcdicsafe[*cp & 0xff])) + goto three_print; + putc (*cp, out); + n++; + break; + + case '=': +three_print: + fprintf (out, "=%02X", *cp & 0xff); + n += 3; + break; + } + } + + if (c == '\n') { + if (cp > buffer && (*--cp == ' ' || *cp == '\t')) + fputs ("=\n", out); + + putc ('\n', out); + } else { + fputs ("=\n", out); + } + } + + (*ct->c_ceclosefnx) (ct); + return OK; +} + + +/* + * Output a content using base64 + */ + +static int +writeBase64 (CT ct, FILE *out) +{ + int fd, result; + char *file; + CE ce = ct->c_cefile; + + file = NULL; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return NOTOK; + + result = writeBase64aux (ce->ce_fp, out); + (*ct->c_ceclosefnx) (ct); + return result; +} + + +int +writeBase64aux (FILE *in, FILE *out) +{ + int cc, n; + char inbuf[3]; + + n = BPERLIN; + while ((cc = fread (inbuf, sizeof(*inbuf), sizeof(inbuf), in)) > 0) { + unsigned long bits; + char *bp; + char outbuf[4]; + + if (cc < sizeof(inbuf)) { + inbuf[2] = 0; + if (cc < sizeof(inbuf) - 1) + inbuf[1] = 0; + } + bits = (inbuf[0] & 0xff) << 16; + bits |= (inbuf[1] & 0xff) << 8; + bits |= inbuf[2] & 0xff; + + for (bp = outbuf + sizeof(outbuf); bp > outbuf; bits >>= 6) + *--bp = nib2b64[bits & 0x3f]; + if (cc < sizeof(inbuf)) { + outbuf[3] = '='; + if (cc < sizeof inbuf - 1) + outbuf[2] = '='; + } + + fwrite (outbuf, sizeof(*outbuf), sizeof(outbuf), out); + + if (cc < sizeof(inbuf)) { + putc ('\n', out); + return OK; + } + + if (--n <= 0) { + n = BPERLIN; + putc ('\n', out); + } + } + if (n != BPERLIN) + putc ('\n', out); + + return OK; +} diff --git a/uip/mhparam.c b/uip/mhparam.c new file mode 100644 index 0000000..1da25d3 --- /dev/null +++ b/uip/mhparam.c @@ -0,0 +1,194 @@ + +/* + * mhparam.c -- print mh_profile values + * + * Originally contributed by + * Jeffrey C Honig + * + * $Id$ + */ + +#include + +extern char *mhlibdir; +extern char *mhetcdir; + +char *sbackup = BACKUP_PREFIX; +char *slink = LINK; + +static struct swit switches[] = { +#define COMPSW 0 + { "components", 0 }, +#define NCOMPSW 1 + { "nocomponents", 0 }, +#define ALLSW 2 + { "all", 0 }, +#define VERSIONSW 3 + { "version", 0 }, +#define HELPSW 4 + { "help", 4 }, +#define DEBUGSW 5 + { "debug", -5 }, + { NULL, 0 } +}; + +struct proc { + char *p_name; + char **p_field; +}; + +static struct proc procs [] = { + { "context", &context }, + { "mh-sequences", &mh_seq }, + { "buildmimeproc", &buildmimeproc }, + { "faceproc", &faceproc }, + { "fileproc", &fileproc }, + { "foldprot", &foldprot }, + { "incproc", &incproc }, + { "installproc", &installproc }, + { "lproc", &lproc }, + { "mailproc", &mailproc }, + { "mhlproc", &mhlproc }, + { "moreproc", &moreproc }, + { "msgprot", &msgprot }, + { "mshproc", &mshproc }, + { "packproc", &packproc }, + { "postproc", &postproc }, + { "rmfproc", &rmfproc }, + { "rmmproc", &rmmproc }, + { "sendproc", &sendproc }, + { "showmimeproc", &showmimeproc }, + { "showproc", &showproc }, + { "version", &version_num }, + { "vmhproc", &vmhproc }, + { "whatnowproc", &whatnowproc }, + { "whomproc", &whomproc }, + { "etcdir", &mhetcdir }, + { "libdir", &mhlibdir }, + { "sbackup", &sbackup }, + { "link", &slink }, + { NULL, NULL }, +}; + + +/* + * static prototypes + */ +static char *p_find(char *); + + +int +main(int argc, char **argv) +{ + int i, compp = 0, missed = 0; + int all = 0, debug = 0; + int components = -1; + char *cp, buf[BUFSIZ], **argp; + char **arguments, *comps[MAXARGS]; + + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [profile-components] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case COMPSW: + components = 1; + break; + case NCOMPSW: + components = 0; + break; + + case ALLSW: + all = 1; + break; + + case DEBUGSW: + debug = 1; + break; + } + } else { + comps[compp++] = cp; + } + } + + if (all) { + struct node *np; + + if (compp) + advise(NULL, "profile-components ignored with -all"); + + if (components >= 0) + advise(NULL, "-%scomponents ignored with -all", + components ? "" : "no"); + + /* print all entries in context/profile list */ + for (np = m_defs; np; np = np->n_next) + printf("%s: %s\n", np->n_name, np->n_field); + + } if (debug) { + struct proc *ps; + + /* + * Print the current value of everything in + * procs array. This will show their current + * value (as determined after context is read). + */ + for (ps = procs; ps->p_name; ps++) + printf ("%s: %s\n", ps->p_name, *ps->p_field ? *ps->p_field : ""); + + } else { + if (components < 0) + components = compp > 1; + + for (i = 0; i < compp; i++) { + register char *value; + + value = context_find (comps[i]); + if (!value) + value = p_find (comps[i]); + if (value) { + if (components) + printf("%s: ", comps[i]); + + printf("%s\n", value); + } else + missed++; + } + } + + done (missed); +} + + +static char * +p_find(char *str) +{ + struct proc *ps; + + for (ps = procs; ps->p_name; ps++) + if (!strcasecmp (ps->p_name, str)) + return (*ps->p_field); + + return NULL; +} diff --git a/uip/mhparse.c b/uip/mhparse.c new file mode 100644 index 0000000..1839a15 --- /dev/null +++ b/uip/mhparse.c @@ -0,0 +1,2640 @@ + +/* + * mhparse.c -- routines to parse the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + + +extern int errno; +extern int debugsw; + +extern int endian; /* mhmisc.c */ + +extern pid_t xpid; /* mhshowsbr.c */ + +/* cache policies */ +extern int rcachesw; /* mhcachesbr.c */ +extern int wcachesw; /* mhcachesbr.c */ + +int checksw = 0; /* check Content-MD5 field */ + +/* + * Directory to place temp files. This must + * be set before these routines are called. + */ +char *tmp; + +/* + * Structure for mapping types to their internal flags + */ +struct k2v { + char *kv_key; + int kv_value; +}; + +/* + * Structures for TEXT messages + */ +static struct k2v SubText[] = { + { "plain", TEXT_PLAIN }, + { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */ + { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */ + { NULL, TEXT_UNKNOWN } /* this one must be last! */ +}; + +static struct k2v Charset[] = { + { "us-ascii", CHARSET_USASCII }, + { "iso-8859-1", CHARSET_LATIN }, + { NULL, CHARSET_UNKNOWN } /* this one must be last! */ +}; + +/* + * Structures for MULTIPART messages + */ +static struct k2v SubMultiPart[] = { + { "mixed", MULTI_MIXED }, + { "alternative", MULTI_ALTERNATE }, + { "digest", MULTI_DIGEST }, + { "parallel", MULTI_PARALLEL }, + { NULL, MULTI_UNKNOWN } /* this one must be last! */ +}; + +/* + * Structures for MESSAGE messages + */ +static struct k2v SubMessage[] = { + { "rfc822", MESSAGE_RFC822 }, + { "partial", MESSAGE_PARTIAL }, + { "external-body", MESSAGE_EXTERNAL }, + { NULL, MESSAGE_UNKNOWN } /* this one must be last! */ +}; + +/* + * Structure for APPLICATION messages + */ +static struct k2v SubApplication[] = { + { "octet-stream", APPLICATION_OCTETS }, + { "postscript", APPLICATION_POSTSCRIPT }, + { NULL, APPLICATION_UNKNOWN } /* this one must be last! */ +}; + + +/* ftpsbr.c */ +int ftp_get (char *, char *, char *, char *, char *, char *, int, int); + +/* mhcachesbr.c */ +int find_cache (CT, int, int *, char *, char *, int); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +int make_intermediates (char *); +void content_error (char *, CT, char *, ...); + +/* mhfree.c */ +void free_content (CT); +void free_encoding (CT, int); + +/* + * prototypes + */ +int pidcheck (int); +CT parse_mime (char *); + +/* + * static prototypes + */ +static CT get_content (FILE *, char *, int); +static int add_header (CT, char *, char *); +static int get_ctinfo (char *, CT); +static int get_comment (CT, char **, int); +static int InitGeneric (CT); +static int InitText (CT); +static int InitMultiPart (CT); +static void reverse_parts (CT); +static int InitMessage (CT); +static int params_external (CT, int); +static int InitApplication (CT); +static int init_encoding (CT, OpenCEFunc); +static void close_encoding (CT); +static unsigned long size_encoding (CT); +static int InitBase64 (CT); +static int openBase64 (CT, char **); +static int InitQuoted (CT); +static int openQuoted (CT, char **); +static int Init7Bit (CT); +static int open7Bit (CT, char **); +static int openExternal (CT, CT, CE, char **, int *); +static int InitFile (CT); +static int openFile (CT, char **); +static int InitFTP (CT); +static int openFTP (CT, char **); +static int InitMail (CT); +static int openMail (CT, char **); +static int readDigest (CT, char *); + +/* + * Structures for mapping (content) types to + * the functions to handle them. + */ +struct str2init { + char *si_key; + int si_val; + InitFunc si_init; +}; + +static struct str2init str2cts[] = { + { "application", CT_APPLICATION, InitApplication }, + { "audio", CT_AUDIO, InitGeneric }, + { "image", CT_IMAGE, InitGeneric }, + { "message", CT_MESSAGE, InitMessage }, + { "multipart", CT_MULTIPART, InitMultiPart }, + { "text", CT_TEXT, InitText }, + { "video", CT_VIDEO, InitGeneric }, + { NULL, CT_EXTENSION, NULL }, /* these two must be last! */ + { NULL, CT_UNKNOWN, NULL }, +}; + +static struct str2init str2ces[] = { + { "base64", CE_BASE64, InitBase64 }, + { "quoted-printable", CE_QUOTED, InitQuoted }, + { "8bit", CE_8BIT, Init7Bit }, + { "7bit", CE_7BIT, Init7Bit }, + { "binary", CE_BINARY, NULL }, + { NULL, CE_EXTENSION, NULL }, /* these two must be last! */ + { NULL, CE_UNKNOWN, NULL }, +}; + +/* + * NOTE WELL: si_key MUST NOT have value of NOTOK + * + * si_key is 1 if access method is anonymous. + */ +static struct str2init str2methods[] = { + { "afs", 1, InitFile }, + { "anon-ftp", 1, InitFTP }, + { "ftp", 0, InitFTP }, + { "local-file", 0, InitFile }, + { "mail-server", 0, InitMail }, + { NULL, 0, NULL } +}; + + +int +pidcheck (int status) +{ + if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT) + return status; + + fflush (stdout); + fflush (stderr); + done (1); + /* NOTREACHED */ +} + + +/* + * Main entry point for parsing a MIME message or file. + * It returns the Content structure for the top level + * entity in the file. + */ + +CT +parse_mime (char *file) +{ + int is_stdin; + char buffer[BUFSIZ]; + FILE *fp; + CT ct; + + /* + * Check if file is actually standard input + */ + if ((is_stdin = !(strcmp (file, "-")))) { + file = add (m_tmpfil (invo_name), NULL); + if ((fp = fopen (file, "w+")) == NULL) { + advise (file, "unable to fopen for writing and reading"); + return NULL; + } + chmod (file, 0600); + while (fgets (buffer, sizeof(buffer), stdin)) + fputs (buffer, fp); + fflush (fp); + + if (ferror (stdin)) { + unlink (file); + advise ("stdin", "error reading"); + return NULL; + } + if (ferror (fp)) { + unlink (file); + advise (file, "error writing"); + return NULL; + } + fseek (fp, 0L, SEEK_SET); + } else if ((fp = fopen (file, "r")) == NULL) { + advise (file, "unable to read"); + return NULL; + } + + if (!(ct = get_content (fp, file, 1))) { + if (is_stdin) + unlink (file); + fclose (fp); + advise (NULL, "unable to decode %s", file); + return NULL; + } + + if (is_stdin) + ct->c_unlink = 1; /* temp file to remove */ + + ct->c_fp = NULL; + + if (ct->c_end == 0L) { + fseek (fp, 0L, SEEK_END); + ct->c_end = ftell (fp); + } + + if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) { + fclose (fp); + free_content (ct); + return NULL; + } + + fclose (fp); + return ct; +} + + +/* + * Main routine for reading/parsing the headers + * of a message content. + * + * toplevel = 1 # we are at the top level of the message + * toplevel = 0 # we are inside message type or multipart type + * # other than multipart/digest + * toplevel = -1 # we are inside multipart/digest + */ + +static CT +get_content (FILE *in, char *file, int toplevel) +{ + int compnum, state; + char buf[BUFSIZ], name[NAMESZ]; + char *np, *vp; + CT ct; + HF hp; + + /* allocate the content structure */ + if (!(ct = (CT) calloc (1, sizeof(*ct)))) + adios (NULL, "out of memory"); + + ct->c_fp = in; + ct->c_file = add (file, NULL); + ct->c_begin = ftell (ct->c_fp) + 1; + + /* + * Parse the header fields for this + * content into a linked list. + */ + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; + + /* get copies of the buffers */ + np = add (name, NULL); + vp = add (buf, NULL); + + /* if necessary, get rest of field */ + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + vp = add (buf, vp); /* add to previous value */ + } + + /* Now add the header data to the list */ + add_header (ct, np, vp); + + /* continue, if this isn't the last header field */ + if (state != FLDEOF) { + ct->c_begin = ftell (in) + 1; + continue; + } + /* else fall... */ + + case BODY: + case BODYEOF: + ct->c_begin = ftell (in) - strlen (buf); + break; + + case FILEEOF: + ct->c_begin = ftell (in); + break; + + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", compnum); + + default: + adios (NULL, "getfld() returned %d", state); + } + + /* break out of the loop */ + break; + } + + /* + * Read the content headers. We will parse the + * MIME related header fields into their various + * structures and set internal flags related to + * content type/subtype, etc. + */ + + hp = ct->c_first_hf; /* start at first header field */ + while (hp) { + /* Get MIME-Version field */ + if (!strcasecmp (hp->name, VRSN_FIELD)) { + int ucmp; + char c, *cp, *dp; + + if (ct->c_vrsn) { + advise (NULL, "message %s has multiple %s: fields", + ct->c_file, VRSN_FIELD); + goto next_header; + } + ct->c_vrsn = add (hp->value, NULL); + + /* Now, cleanup this field */ + cp = ct->c_vrsn; + + while (isspace (*cp)) + cp++; + for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) + *dp++ = ' '; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + if (debugsw) + fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); + + if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) + goto out; + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp; + *dp = '\0'; + ucmp = !strcasecmp (cp, VRSN_VALUE); + *dp = c; + if (!ucmp) { + admonish (NULL, "message %s has unknown value for %s: field (%s)", + ct->c_file, VRSN_FIELD, cp); + } + } + else if (!strcasecmp (hp->name, TYPE_FIELD)) { + /* Get Content-Type field */ + struct str2init *s2i; + CI ci = &ct->c_ctinfo; + + /* Check if we've already seen a Content-Type header */ + if (ct->c_ctline) { + advise (NULL, "message %s has multiple %s: fields", + ct->c_file, TYPE_FIELD); + goto next_header; + } + + /* Parse the Content-Type field */ + if (get_ctinfo (hp->value, ct) == NOTOK) + goto out; + + /* + * Set the Init function and the internal + * flag for this content type. + */ + for (s2i = str2cts; s2i->si_key; s2i++) + if (!strcasecmp (ci->ci_type, s2i->si_key)) + break; + if (!s2i->si_key && !uprf (ci->ci_type, "X-")) + s2i++; + ct->c_type = s2i->si_val; + ct->c_ctinitfnx = s2i->si_init; + } + else if (!strcasecmp (hp->name, ENCODING_FIELD)) { + /* Get Content-Transfer-Encoding field */ + char c, *cp, *dp; + struct str2init *s2i; + + /* + * Check if we've already seen the + * Content-Transfer-Encoding field + */ + if (ct->c_celine) { + advise (NULL, "message %s has multiple %s: fields", + ct->c_file, ENCODING_FIELD); + goto next_header; + } + + /* get copy of this field */ + ct->c_celine = cp = add (hp->value, NULL); + + while (isspace (*cp)) + cp++; + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp; + *dp = '\0'; + + /* + * Find the internal flag and Init function + * for this transfer encoding. + */ + for (s2i = str2ces; s2i->si_key; s2i++) + if (!strcasecmp (cp, s2i->si_key)) + break; + if (!s2i->si_key && !uprf (cp, "X-")) + s2i++; + *dp = c; + ct->c_encoding = s2i->si_val; + + /* Call the Init function for this encoding */ + if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK) + goto out; + } + else if (!strcasecmp (hp->name, MD5_FIELD)) { + /* Get Content-MD5 field */ + char *cp, *dp, *ep; + + if (!checksw) + goto next_header; + + if (ct->c_digested) { + advise (NULL, "message %s has multiple %s: fields", + ct->c_file, MD5_FIELD); + goto next_header; + } + + ep = cp = add (hp->value, NULL); /* get a copy */ + + while (isspace (*cp)) + cp++; + for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) + *dp++ = ' '; + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + if (debugsw) + fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); + + if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { + free (ep); + goto out; + } + + for (dp = cp; *dp && !isspace (*dp); dp++) + continue; + *dp = '\0'; + + readDigest (ct, cp); + free (ep); + ct->c_digested++; + } + else if (!strcasecmp (hp->name, ID_FIELD)) { + /* Get Content-ID field */ + ct->c_id = add (hp->value, ct->c_id); + } + else if (!strcasecmp (hp->name, DESCR_FIELD)) { + /* Get Content-Description field */ + ct->c_descr = add (hp->value, ct->c_descr); + } + +next_header: + hp = hp->next; /* next header field */ + } + + /* + * Check if we saw a Content-Type field. + * If not, then assign a default value for + * it, and the Init function. + */ + if (!ct->c_ctline) { + /* + * If we are inside a multipart/digest message, + * so default type is message/rfc822 + */ + if (toplevel < 0) { + if (get_ctinfo ("message/rfc822", ct) == NOTOK) + goto out; + ct->c_type = CT_MESSAGE; + ct->c_ctinitfnx = InitMessage; + } else { + /* + * Else default type is text/plain + */ + if (get_ctinfo ("text/plain", ct) == NOTOK) + goto out; + ct->c_type = CT_TEXT; + ct->c_ctinitfnx = InitText; + } + } + + /* Use default Transfer-Encoding, if necessary */ + if (!ct->c_celine) { + ct->c_encoding = CE_7BIT; + Init7Bit (ct); + } + + return ct; + +out: + free_content (ct); + return NULL; +} + + +/* + * small routine to add header field to list + */ + +static int +add_header (CT ct, char *name, char *value) +{ + HF hp; + + /* allocate header field structure */ + if (!(hp = malloc (sizeof(*hp)))) + adios (NULL, "out of memory"); + + /* link data into header structure */ + hp->name = name; + hp->value = value; + hp->next = NULL; + + /* link header structure into the list */ + if (ct->c_first_hf == NULL) { + ct->c_first_hf = hp; /* this is the first */ + ct->c_last_hf = hp; + } else { + ct->c_last_hf->next = hp; /* add it to the end */ + ct->c_last_hf = hp; + } + + return 0; +} + + +/* + * Parse Content-Type line and fill in the + * information of the CTinfo structure. + */ + +static int +get_ctinfo (char *cp, CT ct) +{ + int i; + char *dp, **ap, **ep; + char c; + CI ci; + + ci = &ct->c_ctinfo; + i = strlen (invo_name) + 2; + + /* store copy of Content-Type line */ + cp = ct->c_ctline = add (cp, NULL); + + while (isspace (*cp)) /* trim leading spaces */ + cp++; + + /* change newlines to spaces */ + for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) + *dp++ = ' '; + + /* trim trailing spaces */ + for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + + if (debugsw) + fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp, *dp = '\0'; + ci->ci_type = add (cp, NULL); /* store content type */ + *dp = c, cp = dp; + + if (!*ci->ci_type) { + advise (NULL, "invalid %s: field in message %s (empty type)", + TYPE_FIELD, ct->c_file); + return NOTOK; + } + + /* down case the content type string */ + for (dp = ci->ci_type; *dp; dp++) + if (isalpha(*dp) && isupper (*dp)) + *dp = tolower (*dp); + + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + if (*cp != '/') { + ci->ci_subtype = add ("", NULL); + goto magic_skip; + } + + cp++; + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + for (dp = cp; istoken (*dp); dp++) + continue; + c = *dp, *dp = '\0'; + ci->ci_subtype = add (cp, NULL); /* store the content subtype */ + *dp = c, cp = dp; + + if (!*ci->ci_subtype) { + advise (NULL, + "invalid %s: field in message %s (empty subtype for \"%s\")", + TYPE_FIELD, ct->c_file, ci->ci_type); + return NOTOK; + } + + /* down case the content subtype string */ + for (dp = ci->ci_subtype; *dp; dp++) + if (isalpha(*dp) && isupper (*dp)) + *dp = tolower (*dp); + +magic_skip: + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + /* + * Parse attribute/value pairs given with Content-Type + */ + ep = (ap = ci->ci_attrs) + NPARMS; + while (*cp == ';') { + char *vp, *up; + + if (ap >= ep) { + advise (NULL, + "too many parameters in message %s's %s: field (%d max)", + ct->c_file, TYPE_FIELD, NPARMS); + return NOTOK; + } + + cp++; + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + + if (*cp == 0) { + advise (NULL, + "extraneous trailing ';' in message %s's %s: parameter list", + ct->c_file, TYPE_FIELD); + return OK; + } + + /* down case the attribute name */ + for (dp = cp; istoken (*dp); dp++) + if (isalpha(*dp) && isupper (*dp)) + *dp = tolower (*dp); + + for (up = dp; isspace (*dp);) + dp++; + if (dp == cp || *dp != '=') { + advise (NULL, + "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)", + ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp); + return NOTOK; + } + + vp = (*ap = add (cp, NULL)) + (up - cp); + *vp = '\0'; + for (dp++; isspace (*dp);) + dp++; + + /* now add the attribute value */ + ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp); + + if (*dp == '"') { + for (cp = ++dp, dp = vp;;) { + switch (c = *cp++) { + case '\0': +bad_quote: + advise (NULL, + "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)", + ct->c_file, TYPE_FIELD, i, i, "", *ap); + return NOTOK; + + case '\\': + *dp++ = c; + if ((c = *cp++) == '\0') + goto bad_quote; + /* else fall... */ + + default: + *dp++ = c; + continue; + + case '"': + *dp = '\0'; + break; + } + break; + } + } else { + for (cp = dp, dp = vp; istoken (*cp); cp++, dp++) + continue; + *dp = '\0'; + } + if (!*vp) { + advise (NULL, + "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)", + ct->c_file, TYPE_FIELD, i, i, "", *ap); + return NOTOK; + } + ap++; + + while (isspace (*cp)) + cp++; + + if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) + return NOTOK; + } + + /* + * Check if anything is left over + */ + if (*cp) { + advise (NULL, "extraneous information in message %s's %s: field\n%*.*s(%s)", + ct->c_file, TYPE_FIELD, i, i, "", cp); + } + + return OK; +} + + +static int +get_comment (CT ct, char **ap, int istype) +{ + int i; + char *bp, *cp; + char c, buffer[BUFSIZ], *dp; + CI ci; + + ci = &ct->c_ctinfo; + cp = *ap; + bp = buffer; + cp++; + + for (i = 0;;) { + switch (c = *cp++) { + case '\0': +invalid: + advise (NULL, "invalid comment in message %s's %s: field", + ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD); + return NOTOK; + + case '\\': + *bp++ = c; + if ((c = *cp++) == '\0') + goto invalid; + *bp++ = c; + continue; + + case '(': + i++; + /* and fall... */ + default: + *bp++ = c; + continue; + + case ')': + if (--i < 0) + break; + *bp++ = c; + continue; + } + break; + } + *bp = '\0'; + + if (istype) { + if ((dp = ci->ci_comment)) { + ci->ci_comment = concat (dp, " ", buffer, NULL); + free (dp); + } else { + ci->ci_comment = add (buffer, NULL); + } + } + + while (isspace (*cp)) + cp++; + + *ap = cp; + return OK; +} + + +/* + * CONTENTS + * + * Handles content types audio, image, and video. + * There's not much to do right here. + */ + +static int +InitGeneric (CT ct) +{ + return OK; /* not much to do here */ +} + + +/* + * TEXT + */ + +static int +InitText (CT ct) +{ + char buffer[BUFSIZ]; + char *chset; + char **ap, **ep, *cp; + struct k2v *kv; + struct text *t; + CI ci = &ct->c_ctinfo; + + /* check for missing subtype */ + if (!*ci->ci_subtype) + ci->ci_subtype = add ("plain", ci->ci_subtype); + + /* match subtype */ + for (kv = SubText; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + /* allocate text structure */ + if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) t; + + /* scan for charset parameter */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) + if (!strcasecmp (*ap, "charset")) + break; + + if (*ap) + chset = *ep; + else + chset = "US-ASCII"; /* default for text */ + + /* match character set, or set to unknown */ + for (kv = Charset; kv->kv_key; kv++) + if (!strcasecmp (chset, kv->kv_key)) + break; + t->tx_charset = kv->kv_value; + + /* + * If we can not handle character set natively, + * then check profile for string to modify the + * terminal or display method. + */ + if (!check_charset (chset, strlen (chset))) { + snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset); + if ((cp = context_find (buffer))) + ct->c_termproc = getcpy (cp); + } + + return OK; +} + + +/* + * MULTIPART + */ + +static int +InitMultiPart (CT ct) +{ + int inout; + long last, pos; + char *cp, *dp, **ap, **ep; + char *bp, buffer[BUFSIZ]; + struct multipart *m; + struct k2v *kv; + struct part *part, **next; + CI ci = &ct->c_ctinfo; + CT p; + FILE *fp; + + /* + * The encoding for multipart messages must be either + * 7bit, 8bit, or binary (per RFC2045). + */ + if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT + && ct->c_encoding != CE_BINARY) { + admonish (NULL, + "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary", + ci->ci_type, ci->ci_subtype, ct->c_file); + return NOTOK; + } + + /* match subtype */ + for (kv = SubMultiPart; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + /* + * Check for "boundary" parameter, which is + * required for multipart messages. + */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "boundary")) { + bp = *ep; + break; + } + } + + /* complain if boundary parameter is missing */ + if (!*ap) { + advise (NULL, + "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field", + ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); + return NOTOK; + } + + /* allocate primary structure for multipart info */ + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + + /* check if boundary parameter contains only whitespace characters */ + for (cp = bp; isspace (*cp); cp++) + continue; + if (!*cp) { + advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field", + ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); + return NOTOK; + } + + /* remove trailing whitespace from boundary parameter */ + for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--) + if (!isspace (*dp)) + break; + *++dp = '\0'; + + /* record boundary separators */ + m->mp_start = concat (bp, "\n", NULL); + m->mp_stop = concat (bp, "--\n", NULL); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + advise (ct->c_file, "unable to open for reading"); + return NOTOK; + } + + fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET); + last = ct->c_end; + next = &m->mp_parts; + part = NULL; + inout = 1; + + while (fgets (buffer, sizeof(buffer) - 1, fp)) { + if (pos > last) + break; + + pos += strlen (buffer); + if (buffer[0] != '-' || buffer[1] != '-') + continue; + if (inout) { + if (strcmp (buffer + 2, m->mp_start)) + continue; +next_part: + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *next = part; + next = &part->mp_next; + + if (!(p = get_content (fp, ct->c_file, + ct->c_subtype == MULTI_DIGEST ? -1 : 0))) { + fclose (ct->c_fp); + ct->c_fp = NULL; + return NOTOK; + } + p->c_fp = NULL; + part->mp_part = p; + pos = p->c_begin; + fseek (fp, pos, SEEK_SET); + inout = 0; + } else { + if (strcmp (buffer + 2, m->mp_start) == 0) { + inout = 1; +end_part: + p = part->mp_part; + p->c_end = ftell(fp) - (strlen(buffer) + 1); + if (p->c_end < p->c_begin) + p->c_begin = p->c_end; + if (inout) + goto next_part; + goto last_part; + } else { + if (strcmp (buffer + 2, m->mp_stop) == 0) + goto end_part; + } + } + } + + advise (NULL, "bogus multipart content in message %s", ct->c_file); + if (!inout && part) { + p = part->mp_part; + p->c_end = ct->c_end; + + if (p->c_begin >= p->c_end) { + for (next = &m->mp_parts; *next != part; + next = &((*next)->mp_next)) + continue; + *next = NULL; + free_content (p); + free ((char *) part); + } + } + +last_part: + /* reverse the order of the parts for multipart/alternative */ + if (ct->c_subtype == MULTI_ALTERNATE) + reverse_parts (ct); + + /* + * label all subparts with part number, and + * then initialize the content of the subpart. + */ + { + int partnum; + char *pp; + char partnam[BUFSIZ]; + + if (ct->c_partno) { + snprintf (partnam, sizeof(partnum), "%s.", ct->c_partno); + pp = partnam + strlen (partnam); + } else { + pp = partnam; + } + + for (part = m->mp_parts, partnum = 1; part; + part = part->mp_next, partnum++) { + p = part->mp_part; + + sprintf (pp, "%d", partnum); + p->c_partno = add (partnam, NULL); + + /* initialize the content of the subparts */ + if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) { + fclose (ct->c_fp); + ct->c_fp = NULL; + return NOTOK; + } + } + } + + fclose (ct->c_fp); + ct->c_fp = NULL; + return OK; +} + + +/* + * reverse the order of the parts of a multipart + */ + +static void +reverse_parts (CT ct) +{ + int i; + struct multipart *m; + struct part **base, **bmp, **next, *part; + + m = (struct multipart *) ct->c_ctparams; + + /* if only one part, just return */ + if (!m->mp_parts || !m->mp_parts->mp_next) + return; + + /* count number of parts */ + i = 0; + for (part = m->mp_parts; part; part = part->mp_next) + i++; + + /* allocate array of pointers to the parts */ + if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base)))) + adios (NULL, "out of memory"); + bmp = base; + + /* point at all the parts */ + for (part = m->mp_parts; part; part = part->mp_next) + *bmp++ = part; + *bmp = NULL; + + /* reverse the order of the parts */ + next = &m->mp_parts; + for (bmp--; bmp >= base; bmp--) { + part = *bmp; + *next = part; + next = &part->mp_next; + } + *next = NULL; + + /* free array of pointers */ + free ((char *) base); +} + + +/* + * MESSAGE + */ + +static int +InitMessage (CT ct) +{ + struct k2v *kv; + CI ci = &ct->c_ctinfo; + + if (ct->c_encoding != CE_7BIT) { + admonish (NULL, + "\"%s/%s\" type in message %s should be encoded in 7bit", + ci->ci_type, ci->ci_subtype, ct->c_file); + return NOTOK; + } + + /* check for missing subtype */ + if (!*ci->ci_subtype) + ci->ci_subtype = add ("rfc822", ci->ci_subtype); + + /* match subtype */ + for (kv = SubMessage; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + switch (ct->c_subtype) { + case MESSAGE_RFC822: + break; + + case MESSAGE_PARTIAL: + { + char **ap, **ep; + struct partial *p; + + if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) p; + + /* scan for parameters "id", "number", and "total" */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "id")) { + p->pm_partid = add (*ep, NULL); + continue; + } + if (!strcasecmp (*ap, "number")) { + if (sscanf (*ep, "%d", &p->pm_partno) != 1 + || p->pm_partno < 1) { +invalid_param: + advise (NULL, + "invalid %s parameter for \"%s/%s\" type in message %s's %s field", + *ap, ci->ci_type, ci->ci_subtype, + ct->c_file, TYPE_FIELD); + return NOTOK; + } + continue; + } + if (!strcasecmp (*ap, "total")) { + if (sscanf (*ep, "%d", &p->pm_maxno) != 1 + || p->pm_maxno < 1) + goto invalid_param; + continue; + } + } + + if (!p->pm_partid + || !p->pm_partno + || (p->pm_maxno && p->pm_partno > p->pm_maxno)) { + advise (NULL, + "invalid parameters for \"%s/%s\" type in message %s's %s field", + ci->ci_type, ci->ci_subtype, + ct->c_file, TYPE_FIELD); + return NOTOK; + } + } + break; + + case MESSAGE_EXTERNAL: + { + int exresult; + struct exbody *e; + CT p; + FILE *fp; + + if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) e; + + if (!ct->c_fp + && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + advise (ct->c_file, "unable to open for reading"); + return NOTOK; + } + + fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET); + + if (!(p = get_content (fp, ct->c_file, 0))) { + fclose (ct->c_fp); + ct->c_fp = NULL; + return NOTOK; + } + + e->eb_parent = ct; + e->eb_content = p; + p->c_ctexbody = e; + if ((exresult = params_external (ct, 0)) != NOTOK + && p->c_ceopenfnx == openMail) { + int cc, size; + char *bp; + + if ((size = ct->c_end - p->c_begin) <= 0) { + if (!e->eb_subject) + content_error (NULL, ct, + "empty body for access-type=mail-server"); + goto no_body; + } + + if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL) + adios (NULL, "out of memory"); + fseek (p->c_fp, p->c_begin, SEEK_SET); + while (size > 0) + switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) { + case NOTOK: + adios ("failed", "fread"); + + case OK: + adios (NULL, "unexpected EOF from fread"); + + default: + bp += cc, size -= cc; + break; + } + *bp = 0; + } +no_body: + p->c_fp = NULL; + p->c_end = p->c_begin; + + fclose (ct->c_fp); + ct->c_fp = NULL; + + if (exresult == NOTOK) + return NOTOK; + if (e->eb_flags == NOTOK) + return OK; + + switch (p->c_type) { + case CT_MULTIPART: + break; + + case CT_MESSAGE: + if (p->c_subtype != MESSAGE_RFC822) + break; + /* else fall... */ + default: + e->eb_partno = ct->c_partno; + if (p->c_ctinitfnx) + (*p->c_ctinitfnx) (p); + break; + } + } + break; + + default: + break; + } + + return OK; +} + + +static int +params_external (CT ct, int composing) +{ + char **ap, **ep; + struct exbody *e = (struct exbody *) ct->c_ctparams; + CI ci = &ct->c_ctinfo; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "access-type")) { + struct str2init *s2i; + CT p = e->eb_content; + + for (s2i = str2methods; s2i->si_key; s2i++) + if (!strcasecmp (*ep, s2i->si_key)) + break; + if (!s2i->si_key) { + e->eb_access = *ep; + e->eb_flags = NOTOK; + p->c_encoding = CE_EXTERNAL; + continue; + } + e->eb_access = s2i->si_key; + e->eb_flags = s2i->si_val; + p->c_encoding = CE_EXTERNAL; + + /* Call the Init function for this external type */ + if ((*s2i->si_init)(p) == NOTOK) + return NOTOK; + continue; + } + if (!strcasecmp (*ap, "name")) { + e->eb_name = *ep; + continue; + } + if (!strcasecmp (*ap, "permission")) { + e->eb_permission = *ep; + continue; + } + if (!strcasecmp (*ap, "site")) { + e->eb_site = *ep; + continue; + } + if (!strcasecmp (*ap, "directory")) { + e->eb_dir = *ep; + continue; + } + if (!strcasecmp (*ap, "mode")) { + e->eb_mode = *ep; + continue; + } + if (!strcasecmp (*ap, "size")) { + sscanf (*ep, "%lu", &e->eb_size); + continue; + } + if (!strcasecmp (*ap, "server")) { + e->eb_server = *ep; + continue; + } + if (!strcasecmp (*ap, "subject")) { + e->eb_subject = *ep; + continue; + } + if (composing && !strcasecmp (*ap, "body")) { + e->eb_body = getcpy (*ep); + continue; + } + } + + if (!e->eb_access) { + advise (NULL, + "invalid parameters for \"%s/%s\" type in message %s's %s field", + ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); + return NOTOK; + } + + return OK; +} + + +/* + * APPLICATION + */ + +static int +InitApplication (CT ct) +{ + struct k2v *kv; + CI ci = &ct->c_ctinfo; + + /* match subtype */ + for (kv = SubApplication; kv->kv_key; kv++) + if (!strcasecmp (ci->ci_subtype, kv->kv_key)) + break; + ct->c_subtype = kv->kv_value; + + return OK; +} + + +/* + * TRANSFER ENCODINGS + */ + +static int +init_encoding (CT ct, OpenCEFunc openfnx) +{ + CE ce; + + if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) + adios (NULL, "out of memory"); + + ct->c_cefile = ce; + ct->c_ceopenfnx = openfnx; + ct->c_ceclosefnx = close_encoding; + ct->c_cesizefnx = size_encoding; + + return OK; +} + + +static void +close_encoding (CT ct) +{ + CE ce; + + if (!(ce = ct->c_cefile)) + return; + + if (ce->ce_fp) { + fclose (ce->ce_fp); + ce->ce_fp = NULL; + } +} + + +static unsigned long +size_encoding (CT ct) +{ + int fd; + unsigned long size; + char *file; + CE ce; + struct stat st; + + if (!(ce = ct->c_cefile)) + return (ct->c_end - ct->c_begin); + + if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK) + return (long) st.st_size; + + if (ce->ce_file) { + if (stat (ce->ce_file, &st) != NOTOK) + return (long) st.st_size; + else + return 0L; + } + + if (ct->c_encoding == CE_EXTERNAL) + return (ct->c_end - ct->c_begin); + + file = NULL; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return (ct->c_end - ct->c_begin); + + if (fstat (fd, &st) != NOTOK) + size = (long) st.st_size; + else + size = 0L; + + (*ct->c_ceclosefnx) (ct); + return size; +} + + +/* + * BASE64 + */ + +static unsigned char b642nib[0x80] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +static int +InitBase64 (CT ct) +{ + return init_encoding (ct, openBase64); +} + + +static int +openBase64 (CT ct, char **file) +{ + int bitno, cc, digested; + int fd, len, skip; + unsigned long bits; + unsigned char value, *b, *b1, *b2, *b3; + char *cp, *ep, buffer[BUFSIZ]; + CE ce; + MD5_CTX mdContext; + + b = (unsigned char *) &bits; + b1 = &b[endian > 0 ? 1 : 2]; + b2 = &b[endian > 0 ? 2 : 1]; + b3 = &b[endian > 0 ? 3 : 0]; + + ce = ct->c_cefile; + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_to_go; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_to_go; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if ((len = ct->c_end - ct->c_begin) < 0) + adios (NULL, "internal error(1)"); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + + if ((digested = ct->c_digested)) + MD5Init (&mdContext); + + bitno = 18; + bits = 0L; + skip = 0; + + lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); + while (len > 0) { + switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { + case NOTOK: + content_error (ct->c_file, ct, "error reading from"); + goto clean_up; + + case OK: + content_error (NULL, ct, "premature eof"); + goto clean_up; + + default: + if (cc > len) + cc = len; + len -= cc; + + for (ep = (cp = buffer) + cc; cp < ep; cp++) { + switch (*cp) { + default: + if (isspace (*cp)) + break; + if (skip || (*cp & 0x80) + || (value = b642nib[*cp & 0x7f]) > 0x3f) { + if (debugsw) { + fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n", + *cp, + (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)), + skip); + } + content_error (NULL, ct, + "invalid BASE64 encoding -- continuing"); + continue; + } + + bits |= value << bitno; +test_end: + if ((bitno -= 6) < 0) { + putc ((char) *b1, ce->ce_fp); + if (digested) + MD5Update (&mdContext, b1, 1); + if (skip < 2) { + putc ((char) *b2, ce->ce_fp); + if (digested) + MD5Update (&mdContext, b2, 1); + if (skip < 1) { + putc ((char) *b3, ce->ce_fp); + if (digested) + MD5Update (&mdContext, b3, 1); + } + } + + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, + "error writing to"); + goto clean_up; + } + bitno = 18, bits = 0L, skip = 0; + } + break; + + case '=': + if (++skip > 3) + goto self_delimiting; + goto test_end; + } + } + } + } + + if (bitno != 18) { + if (debugsw) + fprintf (stderr, "premature ending (bitno %d)\n", bitno); + + content_error (NULL, ct, "invalid BASE64 encoding"); + goto clean_up; + } + +self_delimiting: + fseek (ct->c_fp, 0L, SEEK_SET); + + if (fflush (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + + if (digested) { + unsigned char digest[16]; + + MD5Final (digest, &mdContext); + if (memcmp((char *) digest, (char *) ct->c_digest, + sizeof(digest) / sizeof(digest[0]))) + content_error (NULL, ct, + "content integrity suspect (digest mismatch) -- continuing"); + else + if (debugsw) + fprintf (stderr, "content integrity confirmed\n"); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + +ready_to_go: + *file = ce->ce_file; + return fileno (ce->ce_fp); + +clean_up: + free_encoding (ct, 0); + return NOTOK; +} + + +/* + * QUOTED PRINTABLE + */ + +static char hex2nib[0x80] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +static int +InitQuoted (CT ct) +{ + return init_encoding (ct, openQuoted); +} + + +static int +openQuoted (CT ct, char **file) +{ + int cc, digested, len, quoted; + char *cp, *ep; + char buffer[BUFSIZ]; + unsigned char mask; + CE ce; + MD5_CTX mdContext; + + ce = ct->c_cefile; + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_to_go; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_to_go; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if ((len = ct->c_end - ct->c_begin) < 0) + adios (NULL, "internal error(2)"); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + + if ((digested = ct->c_digested)) + MD5Init (&mdContext); + + quoted = 0; +#ifdef lint + mask = 0; +#endif + + fseek (ct->c_fp, ct->c_begin, SEEK_SET); + while (len > 0) { + char *dp; + + if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) { + content_error (NULL, ct, "premature eof"); + goto clean_up; + } + + if ((cc = strlen (buffer)) > len) + cc = len; + len -= cc; + + for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--) + if (!isspace (*ep)) + break; + *++ep = '\n', ep++; + + for (; cp < ep; cp++) { + if (quoted) { + if (quoted > 1) { + if (!isxdigit (*cp)) { +invalid_hex: + dp = "expecting hexidecimal-digit"; + goto invalid_encoding; + } + mask <<= 4; + mask |= hex2nib[*cp & 0x7f]; + putc (mask, ce->ce_fp); + if (digested) + MD5Update (&mdContext, &mask, 1); + } else { + switch (*cp) { + case ':': + putc (*cp, ce->ce_fp); + if (digested) + MD5Update (&mdContext, (unsigned char *) ":", 1); + break; + + default: + if (!isxdigit (*cp)) + goto invalid_hex; + mask = hex2nib[*cp & 0x7f]; + quoted = 2; + continue; + } + } + + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + quoted = 0; + continue; + } + + switch (*cp) { + default: + if (*cp < '!' || *cp > '~') { + int i; + dp = "expecting character in range [!..~]"; + +invalid_encoding: + i = strlen (invo_name) + 2; + content_error (NULL, ct, + "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x", + dp, i, i, "", *cp); + goto clean_up; + } + /* and fall...*/ + case ' ': + case '\t': + case '\n': + putc (*cp, ce->ce_fp); + if (digested) { + if (*cp == '\n') + MD5Update (&mdContext, (unsigned char *) "\r\n",2); + else + MD5Update (&mdContext, (unsigned char *) cp, 1); + } + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + break; + + case '=': + if (*++cp != '\n') { + quoted = 1; + cp--; + } + break; + } + } + } + if (quoted) { + content_error (NULL, ct, + "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting"); + goto clean_up; + } + + fseek (ct->c_fp, 0L, SEEK_SET); + + if (fflush (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + + if (digested) { + unsigned char digest[16]; + + MD5Final (digest, &mdContext); + if (memcmp((char *) digest, (char *) ct->c_digest, + sizeof(digest) / sizeof(digest[0]))) + content_error (NULL, ct, + "content integrity suspect (digest mismatch) -- continuing"); + else + if (debugsw) + fprintf (stderr, "content integrity confirmed\n"); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + +ready_to_go: + *file = ce->ce_file; + return fileno (ce->ce_fp); + +clean_up: + free_encoding (ct, 0); + return NOTOK; +} + + +/* + * 7BIT + */ + +static int +Init7Bit (CT ct) +{ + if (init_encoding (ct, open7Bit) == NOTOK) + return NOTOK; + + ct->c_cesizefnx = NULL; /* no need to decode for real size */ + return OK; +} + + +static int +open7Bit (CT ct, char **file) +{ + int cc, fd, len; + char buffer[BUFSIZ]; + CE ce; + + ce = ct->c_cefile; + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_to_go; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_to_go; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if (ct->c_type == CT_MULTIPART) { + char **ap, **ep; + CI ci = &ct->c_ctinfo; + + len = 0; + fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype); + len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type) + + 1 + strlen (ci->ci_subtype); + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + putc (';', ce->ce_fp); + len++; + + snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep); + + if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) { + fputs ("\n\t", ce->ce_fp); + len = 8; + } else { + putc (' ', ce->ce_fp); + len++; + } + fprintf (ce->ce_fp, "%s", buffer); + len += cc; + } + + if (ci->ci_comment) { + if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) { + fputs ("\n\t", ce->ce_fp); + len = 8; + } + else { + putc (' ', ce->ce_fp); + len++; + } + fprintf (ce->ce_fp, "(%s)", ci->ci_comment); + len += cc; + } + fprintf (ce->ce_fp, "\n"); + if (ct->c_id) + fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id); + if (ct->c_descr) + fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr); + fprintf (ce->ce_fp, "\n"); + } + + if ((len = ct->c_end - ct->c_begin) < 0) + adios (NULL, "internal error(3)"); + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + content_error (ct->c_file, ct, "unable to open for reading"); + return NOTOK; + } + + lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); + while (len > 0) + switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { + case NOTOK: + content_error (ct->c_file, ct, "error reading from"); + goto clean_up; + + case OK: + content_error (NULL, ct, "premature eof"); + goto clean_up; + + default: + if (cc > len) + cc = len; + len -= cc; + + fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp); + if (ferror (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + } + + fseek (ct->c_fp, 0L, SEEK_SET); + + if (fflush (ce->ce_fp)) { + content_error (ce->ce_file, ct, "error writing to"); + goto clean_up; + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + +ready_to_go: + *file = ce->ce_file; + return fileno (ce->ce_fp); + +clean_up: + free_encoding (ct, 0); + return NOTOK; +} + + +/* + * External + */ + +static int +openExternal (CT ct, CT cb, CE ce, char **file, int *fd) +{ + char cachefile[BUFSIZ]; + + if (ce->ce_fp) { + fseek (ce->ce_fp, 0L, SEEK_SET); + goto ready_already; + } + + if (ce->ce_file) { + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + goto ready_already; + } + + if (find_cache (ct, rcachesw, (int *) 0, cb->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + if ((ce->ce_fp = fopen (cachefile, "r"))) { + ce->ce_file = getcpy (cachefile); + ce->ce_unlink = 0; + goto ready_already; + } else { + admonish (cachefile, "unable to fopen for reading"); + } + } + + return OK; + +ready_already: + *file = ce->ce_file; + *fd = fileno (ce->ce_fp); + return DONE; +} + +/* + * File + */ + +static int +InitFile (CT ct) +{ + return init_encoding (ct, openFile); +} + + +static int +openFile (CT ct, char **file) +{ + int fd, cachetype; + char cachefile[BUFSIZ]; + struct exbody *e = ct->c_ctexbody; + CE ce = ct->c_cefile; + + switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { + case NOTOK: + return NOTOK; + + case OK: + break; + + case DONE: + return fd; + } + + if (!e->eb_name) { + content_error (NULL, ct, "missing name parameter"); + return NOTOK; + } + + ce->ce_file = getcpy (e->eb_name); + ce->ce_unlink = 0; + + if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading"); + return NOTOK; + } + + if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) + && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + int mask; + FILE *fp; + + mask = umask (cachetype ? ~m_gmprot () : 0222); + if ((fp = fopen (cachefile, "w"))) { + int cc; + char buffer[BUFSIZ]; + FILE *gp = ce->ce_fp; + + fseek (gp, 0L, SEEK_SET); + + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) + > 0) + fwrite (buffer, sizeof(*buffer), cc, fp); + fflush (fp); + + if (ferror (gp)) { + admonish (ce->ce_file, "error reading"); + unlink (cachefile); + } + else + if (ferror (fp)) { + admonish (cachefile, "error writing"); + unlink (cachefile); + } + fclose (fp); + } + umask (mask); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + *file = ce->ce_file; + return fileno (ce->ce_fp); +} + +/* + * FTP + */ + +static int +InitFTP (CT ct) +{ + return init_encoding (ct, openFTP); +} + + +static int +openFTP (CT ct, char **file) +{ + int cachetype, caching, fd; + int len, buflen; + char *bp, *ftp, *user, *pass; + char buffer[BUFSIZ], cachefile[BUFSIZ]; + struct exbody *e; + CE ce; + static char *username = NULL; + static char *password = NULL; + + e = ct->c_ctexbody; + ce = ct->c_cefile; + + if ((ftp = context_find (nmhaccessftp)) && !*ftp) + ftp = NULL; + +#ifndef BUILTIN_FTP + if (!ftp) + return NOTOK; +#endif + + switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { + case NOTOK: + return NOTOK; + + case OK: + break; + + case DONE: + return fd; + } + + if (!e->eb_name || !e->eb_site) { + content_error (NULL, ct, "missing %s parameter", + e->eb_name ? "site": "name"); + return NOTOK; + } + + if (xpid) { + if (xpid < 0) + xpid = -xpid; + pidcheck (pidwait (xpid, NOTOK)); + xpid = 0; + } + + /* Get the buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + /* + * Construct the query message for user + */ + snprintf (bp, buflen, "Retrieve %s", e->eb_name); + len = strlen (bp); + bp += len; + buflen -= len; + + if (e->eb_partno) { + snprintf (bp, buflen, " (content %s)", e->eb_partno); + len = strlen (bp); + bp += len; + buflen -= len; + } + + snprintf (bp, buflen, "\n using %sFTP from site %s", + e->eb_flags ? "anonymous " : "", e->eb_site); + len = strlen (bp); + bp += len; + buflen -= len; + + if (e->eb_size > 0) { + snprintf (bp, buflen, " (%lu octets)", e->eb_size); + len = strlen (bp); + bp += len; + buflen -= len; + } + snprintf (bp, buflen, "? "); + + /* + * Now, check the answer + */ + if (!getanswer (buffer)) + return NOTOK; + + if (e->eb_flags) { + user = "anonymous"; + snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ()); + pass = buffer; + } else { + ruserpass (e->eb_site, &username, &password); + user = username; + pass = password; + } + + ce->ce_unlink = (*file == NULL); + caching = 0; + cachefile[0] = '\0'; + if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) + && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, + cachefile, sizeof(cachefile)) != NOTOK) { + if (*file == NULL) { + ce->ce_unlink = 0; + caching = 1; + } + } + + if (*file) + ce->ce_file = add (*file, NULL); + else if (caching) + ce->ce_file = add (cachefile, NULL); + else + ce->ce_file = add (m_scratch ("", tmp), NULL); + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + +#ifdef BUILTIN_FTP + if (ftp) +#endif + { + int child_id, i, vecp; + char *vec[9]; + + vecp = 0; + vec[vecp++] = r1bindex (ftp, '/'); + vec[vecp++] = e->eb_site; + vec[vecp++] = user; + vec[vecp++] = pass; + vec[vecp++] = e->eb_dir; + vec[vecp++] = e->eb_name; + vec[vecp++] = ce->ce_file, + vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii") + ? "ascii" : "binary"; + vec[vecp] = NULL; + + fflush (stdout); + + for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + adios ("fork", "unable to"); + /* NOTREACHED */ + + case OK: + close (fileno (ce->ce_fp)); + execvp (ftp, vec); + fprintf (stderr, "unable to exec "); + perror (ftp); + _exit (-1); + /* NOTREACHED */ + + default: + if (pidXwait (child_id, NULL)) { +#ifdef BUILTIN_FTP +losing_ftp: +#endif + username = password = NULL; + ce->ce_unlink = 1; + return NOTOK; + } + break; + } + } +#ifdef BUILTIN_FTP + else + if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name, + ce->ce_file, + e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0) + == NOTOK) + goto losing_ftp; +#endif + + if (cachefile[0]) + if (caching) + chmod (cachefile, cachetype ? m_gmprot () : 0444); + else { + int mask; + FILE *fp; + + mask = umask (cachetype ? ~m_gmprot () : 0222); + if ((fp = fopen (cachefile, "w"))) { + int cc; + FILE *gp = ce->ce_fp; + + fseek (gp, 0L, SEEK_SET); + + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) + > 0) + fwrite (buffer, sizeof(*buffer), cc, fp); + fflush (fp); + + if (ferror (gp)) { + admonish (ce->ce_file, "error reading"); + unlink (cachefile); + } + else + if (ferror (fp)) { + admonish (cachefile, "error writing"); + unlink (cachefile); + } + fclose (fp); + } + umask (mask); + } + + fseek (ce->ce_fp, 0L, SEEK_SET); + *file = ce->ce_file; + return fileno (ce->ce_fp); +} + + +/* + * Mail + */ + +static int +InitMail (CT ct) +{ + return init_encoding (ct, openMail); +} + + +static int +openMail (CT ct, char **file) +{ + int child_id, fd, i, vecp; + int len, buflen; + char *bp, buffer[BUFSIZ], *vec[7]; + struct exbody *e = ct->c_ctexbody; + CE ce = ct->c_cefile; + + switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { + case NOTOK: + return NOTOK; + + case OK: + break; + + case DONE: + return fd; + } + + if (!e->eb_server) { + content_error (NULL, ct, "missing server parameter"); + return NOTOK; + } + + if (xpid) { + if (xpid < 0) + xpid = -xpid; + pidcheck (pidwait (xpid, NOTOK)); + xpid = 0; + } + + /* Get buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + /* Now, construct query message */ + snprintf (bp, buflen, "Retrieve content"); + len = strlen (bp); + bp += len; + buflen -= len; + + if (e->eb_partno) { + snprintf (bp, buflen, " %s", e->eb_partno); + len = strlen (bp); + bp += len; + buflen -= len; + } + + snprintf (bp, buflen, " by asking %s\n\n%s\n? ", + e->eb_server, + e->eb_subject ? e->eb_subject : e->eb_body); + + /* Now, check answer */ + if (!getanswer (buffer)) + return NOTOK; + + vecp = 0; + vec[vecp++] = r1bindex (mailproc, '/'); + vec[vecp++] = e->eb_server; + vec[vecp++] = "-subject"; + vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request"; + vec[vecp++] = "-body"; + vec[vecp++] = e->eb_body; + vec[vecp] = NULL; + + for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + advise ("fork", "unable to"); + return NOTOK; + + case OK: + execvp (mailproc, vec); + fprintf (stderr, "unable to exec "); + perror (mailproc); + _exit (-1); + /* NOTREACHED */ + + default: + if (pidXwait (child_id, NULL) == OK) + advise (NULL, "request sent"); + break; + } + + if (*file == NULL) { + ce->ce_file = add (m_scratch ("", tmp), NULL); + ce->ce_unlink = 1; + } else { + ce->ce_file = add (*file, NULL); + ce->ce_unlink = 0; + } + + if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { + content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); + return NOTOK; + } + + if (ct->c_showproc) + free (ct->c_showproc); + ct->c_showproc = add ("true", NULL); + + fseek (ce->ce_fp, 0L, SEEK_SET); + *file = ce->ce_file; + return fileno (ce->ce_fp); +} + + +static int +readDigest (CT ct, char *cp) +{ + int bitno, skip; + unsigned long bits; + char *bp = cp; + unsigned char *dp, value, *ep; + unsigned char *b, *b1, *b2, *b3; + + b = (unsigned char *) &bits, + b1 = &b[endian > 0 ? 1 : 2], + b2 = &b[endian > 0 ? 2 : 1], + b3 = &b[endian > 0 ? 3 : 0]; + bitno = 18; + bits = 0L; + skip = 0; + + for (ep = (dp = ct->c_digest) + + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++) + switch (*cp) { + default: + if (skip + || (*cp & 0x80) + || (value = b642nib[*cp & 0x7f]) > 0x3f) { + if (debugsw) + fprintf (stderr, "invalid BASE64 encoding\n"); + return NOTOK; + } + + bits |= value << bitno; +test_end: + if ((bitno -= 6) < 0) { + if (dp + (3 - skip) > ep) + goto invalid_digest; + *dp++ = *b1; + if (skip < 2) { + *dp++ = *b2; + if (skip < 1) + *dp++ = *b3; + } + bitno = 18; + bits = 0L; + skip = 0; + } + break; + + case '=': + if (++skip > 3) + goto self_delimiting; + goto test_end; + } + if (bitno != 18) { + if (debugsw) + fprintf (stderr, "premature ending (bitno %d)\n", bitno); + + return NOTOK; + } +self_delimiting: + if (dp != ep) { +invalid_digest: + if (debugsw) { + while (*cp) + cp++; + fprintf (stderr, "invalid MD5 digest (got %d octets)\n", + cp - bp); + } + + return NOTOK; + } + + if (debugsw) { + fprintf (stderr, "MD5 digest="); + for (dp = ct->c_digest; dp < ep; dp++) + fprintf (stderr, "%02x", *dp & 0xff); + fprintf (stderr, "\n"); + } + + return OK; +} diff --git a/uip/mhpath.c b/uip/mhpath.c new file mode 100644 index 0000000..ac1854e --- /dev/null +++ b/uip/mhpath.c @@ -0,0 +1,148 @@ + +/* + * mhpath.c -- print full pathnames of nmh messages and folders + * + * $Id$ + */ + +#include + +static struct swit switches[] = { +#define VERSIONSW 0 + { "version", 0 }, +#define HELPSW 1 + { "help", 4 }, + { NULL, 0 } +}; + +/* + * Add space for message/sequence names + * by MAXMSGS at a time. + */ +#define MAXMSGS 256 + + +int +main(int argc, char **argv) +{ + int i, maxmsgs, nummsgs; + char *cp, *maildir, *folder = NULL; + char **argp, **msgs; + char **arguments, buf[BUFSIZ]; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate initial space to record message + * and sequence names + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * if necessary, reallocate space for + * message/sequence names + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage "); + } + msgs[nummsgs++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + /* If no messages are given, print folder pathname */ + if (!nummsgs) { + printf ("%s\n", maildir); + done (0); + } + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* + * We need to make sure there is message status space + * for all the message numbers from 1 to "new" since + * mhpath can select empty slots. If we are adding + * space at the end, we go ahead and add 10 slots. + */ + if (mp->hghmsg >= mp->hghoff) { + if (!(mp = folder_realloc (mp, 1, mp->hghmsg + 10))) + adios (NULL, "unable to allocate folder storage"); + } else if (mp->lowoff > 1) { + if (!(mp = folder_realloc (mp, 1, mp->hghoff))) + adios (NULL, "unable to allocate folder storage"); + } + + mp->msgflags |= ALLOW_NEW; /* allow the "new" sequence */ + + /* parse all the message ranges/sequences and set SELECTED */ + for (i = 0; i < nummsgs; i++) + if (!m_convert (mp, msgs[i])) + done (1); + + seq_setprev (mp); /* set the previous-sequence */ + + /* print the path of all selected messages */ + for (i = mp->lowsel; i <= mp->hghsel; i++) + if (is_selected (mp, i)) + printf ("%s/%s\n", mp->foldpath, m_name (i)); + + seq_save (mp); /* synchronize message sequences */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder/message structure */ + done (0); +} diff --git a/uip/mhshow.c b/uip/mhshow.c new file mode 100644 index 0000000..ff2bee8 --- /dev/null +++ b/uip/mhshow.c @@ -0,0 +1,508 @@ + +/* + * mhshow.c -- display the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +/* + * We allocate space for message names (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define CHECKSW 0 + { "check", 0 }, +#define NCHECKSW 1 + { "nocheck", 0 }, +#define PAUSESW 2 + { "pause", 0 }, +#define NPAUSESW 3 + { "nopause", 0 }, +#define SERIALSW 4 + { "serialonly", 0 }, +#define NSERIALSW 5 + { "noserialonly", 0 }, +#define VERBSW 6 + { "verbose", 0 }, +#define NVERBSW 7 + { "noverbose", 0 }, +#define FILESW 8 /* interface from show */ + { "file file", 0 }, +#define FORMSW 9 + { "form formfile", 0 }, +#define PARTSW 10 + { "part number", 0 }, +#define TYPESW 11 + { "type content", 0 }, +#define RCACHESW 12 + { "rcache policy", 0 }, +#define WCACHESW 13 + { "wcache policy", 0 }, +#define VERSIONSW 14 + { "version", 0 }, +#define HELPSW 15 + { "help", 4 }, + +/* + * switches for moreproc/mhlproc + */ +#define PROGSW 16 + { "moreproc program", -4 }, +#define NPROGSW 17 + { "nomoreproc", -3 }, +#define LENSW 18 + { "length lines", -4 }, +#define WIDTHSW 19 + { "width columns", -4 }, + +/* + * switches for debugging + */ +#define DEBUGSW 20 + { "debug", -5 }, + { NULL, 0 } +}; + + +extern int errno; + +/* mhparse.c */ +extern int checksw; +extern char *tmp; /* directory to place temp files */ + +/* mhcachesbr.c */ +extern int rcachesw; +extern int wcachesw; +extern char *cache_public; +extern char *cache_private; + +/* mhshowsbr.c */ +extern int pausesw; +extern int serialsw; +extern char *progsw; +extern int nolist; +extern int nomore; /* flags for moreproc/header display */ +extern char *formsw; + +/* mhmisc.c */ +extern int npart; +extern int ntype; +extern char *parts[NPARTS + 1]; +extern char *types[NTYPES + 1]; +extern int userrs; + +int debugsw = 0; +int verbosw = 0; + +/* The list of top-level contents to display */ +CT *cts = NULL; + +#define quitser pipeser + +/* mhparse.c */ +CT parse_mime (char *); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void set_endian (void); +void flush_errors (void); + +/* mhshowsbr.c */ +void show_all_messages (CT *); + +/* mhfree.c */ +void free_content (CT); + +/* + * static prototypes + */ +static RETSIGTYPE pipeser (int); + + +int +main (int argc, char **argv) +{ + int nummsgs, maxmsgs, msgnum, *icachesw; + char *cp, *file = NULL, *folder = NULL; + char *maildir, buf[100], **argp; + char **arguments, **msgs; + struct msgs *mp = NULL; + CT ct, *ctp; + FILE *fp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case RCACHESW: + icachesw = &rcachesw; + goto do_cache; + case WCACHESW: + icachesw = &wcachesw; +do_cache: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + switch (*icachesw = smatch (cp, caches)) { + case AMBIGSW: + ambigsw (cp, caches); + done (1); + case UNKWNSW: + adios (NULL, "%s unknown", cp); + default: + break; + } + continue; + + case CHECKSW: + checksw++; + continue; + case NCHECKSW: + checksw = 0; + continue; + + case PAUSESW: + pausesw = 1; + continue; + case NPAUSESW: + pausesw = 0; + continue; + + case SERIALSW: + serialsw = 1; + continue; + case NSERIALSW: + serialsw = 0; + continue; + + case PARTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (npart >= NPARTS) + adios (NULL, "too many parts (starting with %s), %d max", + cp, NPARTS); + parts[npart++] = cp; + continue; + + case TYPESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (ntype >= NTYPES) + adios (NULL, "too many types (starting with %s), %d max", + cp, NTYPES); + types[ntype++] = cp; + continue; + + case FILESW: + if (!(cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + file = *cp == '-' ? cp : path (cp, TFILE); + continue; + + case FORMSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (formsw) + free (formsw); + formsw = getcpy (etcpath (cp)); + continue; + + /* + * Switches for moreproc/mhlproc + */ + case PROGSW: + if (!(progsw = *argp++) || *progsw == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NPROGSW: + nomore++; + continue; + + case LENSW: + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case VERBSW: + verbosw = 1; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + /* null terminate the list of acceptable parts/types */ + parts[npart] = NULL; + types[ntype] = NULL; + + set_endian (); + + if ((cp = getenv ("MM_NOASK")) && !strcmp (cp, "1")) { + nolist = 1; + pausesw = 0; + } + + /* + * Check if we've specified an additional profile + */ + if ((cp = getenv ("MHSHOW"))) { + if ((fp = fopen (cp, "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } else { + admonish ("", "unable to read $MHSHOW profile (%s)", cp); + } + } + + /* + * Read the standard profile setup + */ + if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } + + /* Check for public cache location */ + if ((cache_public = context_find (nmhcache)) && *cache_public != '/') + cache_public = NULL; + + /* Check for private cache location */ + if (!(cache_private = context_find (nmhprivcache))) + cache_private = ".cache"; + cache_private = getcpy (m_maildir (cache_private)); + + /* + * Check for storage directory. If specified, + * then store temporary files there. Else we + * store them in standard nmh directory. + */ + if ((cp = context_find (nmhstorage)) && *cp) + tmp = concat (cp, "/", invo_name, NULL); + else + tmp = add (m_maildir (invo_name), NULL); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (file && nummsgs) + adios (NULL, "cannot specify msg and file at same time!"); + + /* + * check if message is coming from file + */ + if (file) { + if (!(cts = (CT *) calloc ((size_t) 2, sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + if ((ct = parse_mime (file))); + *ctp++ = ct; + } else { + /* + * message(s) are coming from a folder + */ + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + + /* + * Set the SELECT_UNSEEN bit for all the SELECTED messages, + * since we will use that as a tag to know which messages + * to remove from the "unseen" sequence. + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected(mp, msgnum)) + set_unseen (mp, msgnum); + + seq_setprev (mp); /* set the Previous-Sequence */ + seq_setunseen (mp, 1); /* unset the Unseen-Sequence */ + + if (!(cts = (CT *) calloc ((size_t) (mp->numsel + 1), sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + /* + * Parse all the SELECTED messages. + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + char *msgnam; + + msgnam = m_name (msgnum); + if ((ct = parse_mime (msgnam))) + *ctp++ = ct; + } + } + } + + if (!*cts) + done (1); + + userrs = 1; + SIGNAL (SIGQUIT, quitser); + SIGNAL (SIGPIPE, pipeser); + + /* + * Get the associated umask for the relevant contents. + */ + for (ctp = cts; *ctp; ctp++) { + struct stat st; + + ct = *ctp; + if (type_ok (ct, 1) && !ct->c_umask) { + if (stat (ct->c_file, &st) != NOTOK) + ct->c_umask = ~(st.st_mode & 0777); + else + ct->c_umask = ~m_gmprot(); + } + } + + /* + * Show the message content + */ + show_all_messages (cts); + + /* Now free all the structures for the content */ + for (ctp = cts; *ctp; ctp++) + free_content (*ctp); + + free ((char *) cts); + cts = NULL; + + /* If reading from a folder, do some updating */ + if (mp) { + context_replace (pfolder, folder);/* update current folder */ + seq_setcur (mp, mp->hghsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + done (0); + /* NOTREACHED */ +} + + +static RETSIGTYPE +pipeser (int i) +{ + if (i == SIGQUIT) { + unlink ("core"); + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + done (1); + /* NOTREACHED */ +} + + +void +done (int status) +{ + CT *ctp; + + if ((ctp = cts)) + for (; *ctp; ctp++) + free_content (*ctp); + + exit (status); +} diff --git a/uip/mhshowsbr.c b/uip/mhshowsbr.c new file mode 100644 index 0000000..a95ff7d --- /dev/null +++ b/uip/mhshowsbr.c @@ -0,0 +1,1014 @@ + +/* + * mhshowsbr.c -- routines to display the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +/* + * Just use sigjmp/longjmp on older machines that + * don't have sigsetjmp/siglongjmp. + */ +#ifndef HAVE_SIGSETJMP +# define sigjmp_buf jmp_buf +# define sigsetjmp(env,mask) setjmp(env) +# define siglongjmp(env,val) longjmp(env,val) +#endif + +extern int errno; +extern int debugsw; + +int pausesw = 1; +int serialsw = 0; +int nolist = 0; + +char *progsw = NULL; + +/* flags for moreproc/header display */ +int nomore = 0; +char *formsw = NULL; + +pid_t xpid = 0; + +static sigjmp_buf intrenv; + + +/* termsbr.c */ +int SOprintf (char *, ...); + +/* mhparse.c */ +int pidcheck (int); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void content_error (char *, CT, char *, ...); +void flush_errors (void); + +/* mhlistsbr.c */ +int list_switch (CT, int, int, int, int); +int list_content (CT, int, int, int, int); + +/* + * prototypes + */ +void show_all_messages (CT *); +int show_content_aux (CT, int, int, char *, char *); + +/* + * static prototypes + */ +static void show_single_message (CT, char *); +static void DisplayMsgHeader (CT, char *); +static int show_switch (CT, int, int); +static int show_content (CT, int, int); +static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int, int); +static int show_text (CT, int, int); +static int show_multi (CT, int, int); +static int show_multi_internal (CT, int, int); +static int show_multi_aux (CT, int, int, char *); +static int show_message_rfc822 (CT, int, int); +static int show_partial (CT, int, int); +static int show_external (CT, int, int); +static RETSIGTYPE intrser (int); + + +/* + * Top level entry point to show/display a group of messages + */ + +void +show_all_messages (CT *cts) +{ + CT ct, *ctp; + + /* + * If form is not specified, then get default form + * for showing headers of MIME messages. + */ + if (!formsw) + formsw = getcpy (etcpath ("mhl.headers")); + + /* + * If form is "mhl.null", suppress display of header. + */ + if (!strcmp (formsw, "mhl.null")) + formsw = NULL; + + for (ctp = cts; *ctp; ctp++) { + ct = *ctp; + + /* if top-level type is ok, then display message */ + if (type_ok (ct, 0)) + show_single_message (ct, formsw); + } +} + + +/* + * Entry point to show/display a single message + */ + +static void +show_single_message (CT ct, char *form) +{ + sigset_t set, oset; + +#ifdef WAITINT + int status; +#else + union wait status; +#endif + + umask (ct->c_umask); + + /* + * If you have a format file, then display + * the message headers. + */ + if (form) + DisplayMsgHeader(ct, form); + else + xpid = 0; + + /* Show the body of the message */ + show_switch (ct, 1, 0); + + if (ct->c_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } + if (ct->c_ceclosefnx) + (*ct->c_ceclosefnx) (ct); + + /* block a few signals */ + sigemptyset (&set); + sigaddset (&set, SIGHUP); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGQUIT); + sigaddset (&set, SIGTERM); + SIGPROCMASK (SIG_BLOCK, &set, &oset); + + while (wait (&status) != NOTOK) { +#ifdef WAITINT + pidcheck (status); +#else + pidcheck (status.w_status); +#endif + continue; + } + + /* reset the signal mask */ + SIGPROCMASK (SIG_SETMASK, &oset, &set); + + xpid = 0; + flush_errors (); +} + + +/* + * Use the mhlproc to show the header fields + */ + +static void +DisplayMsgHeader (CT ct, char *form) +{ + pid_t child_id; + int i, vecp; + char *vec[8]; + + vecp = 0; + vec[vecp++] = r1bindex (mhlproc, '/'); + vec[vecp++] = "-form"; + vec[vecp++] = form; + vec[vecp++] = "-nobody"; + vec[vecp++] = ct->c_file; + + /* + * If we've specified -(no)moreproc, + * then just pass that along. + */ + if (nomore) { + vec[vecp++] = "-nomoreproc"; + } else if (progsw) { + vec[vecp++] = "-moreproc"; + vec[vecp++] = progsw; + } + vec[vecp] = NULL; + + fflush (stdout); + + for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++) + sleep (5); + + switch (child_id) { + case NOTOK: + adios ("fork", "unable to"); + /* NOTREACHED */ + + case OK: + execvp (mhlproc, vec); + fprintf (stderr, "unable to exec "); + perror (mhlproc); + _exit (-1); + /* NOTREACHED */ + + default: + xpid = -child_id; + break; + } +} + + +/* + * Switching routine. Call the correct routine + * based on content type. + */ + +static int +show_switch (CT ct, int serial, int alternate) +{ + switch (ct->c_type) { + case CT_MULTIPART: + return show_multi (ct, serial, alternate); + break; + + case CT_MESSAGE: + switch (ct->c_subtype) { + case MESSAGE_PARTIAL: + return show_partial (ct, serial, alternate); + break; + + case MESSAGE_EXTERNAL: + return show_external (ct, serial, alternate); + break; + + case MESSAGE_RFC822: + default: + return show_message_rfc822 (ct, serial, alternate); + break; + } + break; + + case CT_TEXT: + return show_text (ct, serial, alternate); + break; + + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + case CT_APPLICATION: + return show_content (ct, serial, alternate); + break; + + default: + adios (NULL, "unknown content type %d", ct->c_type); + break; + } + + return 0; /* NOT REACHED */ +} + + +/* + * Generic method for displaying content + */ + +static int +show_content (CT ct, int serial, int alternate) +{ + char *cp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* Check for mhn-show-type/subtype */ + snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* Check for mhn-show-type */ + snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + if ((cp = ct->c_showproc)) + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* complain if we are not a part of a multipart/alternative */ + if (!alternate) + content_error (NULL, ct, "don't know how to display content"); + + return NOTOK; +} + + +/* + * Parse the display string for displaying generic content + */ + +int +show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked) +{ + int fd, len, buflen; + int xstdin, xlist, xpause, xtty; + char *bp, *file, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + if (!ct->c_ceopenfnx) { + if (!alternate) + content_error (NULL, ct, "don't know how to decode content"); + + return NOTOK; + } + + file = NULL; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return NOTOK; + if (ct->c_showproc && !strcmp (ct->c_showproc, "true")) + return (alternate ? DONE : OK); + + xlist = 0; + xpause = 0; + xstdin = 0; + xtty = 0; + + if (cracked) { + strncpy (buffer, cp, sizeof(buffer)); + goto got_command; + } + + /* get buffer ready to go */ + bp = buffer; + bp[0] = '\0'; + buflen = sizeof(buffer); + + /* Now parse display string */ + for ( ; *cp; cp++) { + if (*cp == '%') { + switch (*++cp) { + case 'a': + /* insert parameters from Content-Type field */ + { + char **ap, **ep; + char *s = ""; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + len = strlen (bp); + bp += len; + buflen -= len; + s = " "; + } + } + break; + + case 'd': + /* insert content description */ + if (ct->c_descr) { + char *s; + + s = trimcpy (ct->c_descr); + strncpy (bp, s, buflen); + free (s); + } + break; + + case 'e': + /* exclusive execution */ + xtty = 1; + break; + + case 'F': + /* %e, %f, and stdin is terminal not content */ + xstdin = 1; + xtty = 1; + /* and fall... */ + + case 'f': + /* insert filename containing content */ + snprintf (bp, buflen, "%s", file); + break; + + case 'p': + /* %l, and pause prior to displaying content */ + xpause = pausesw; + /* and fall... */ + + case 'l': + /* display listing prior to displaying content */ + xlist = !nolist; + break; + + case 's': + /* insert subtype of content */ + strncpy (bp, ci->ci_subtype, buflen); + break; + + case '%': + /* insert character % */ + goto raw; + + default: + *bp++ = *--cp; + *bp = '\0'; + buflen--; + continue; + } + len = strlen (bp); + bp += len; + buflen -= len; + } else { +raw: + *bp++ = *cp; + *bp = '\0'; + buflen--; + } + } + + /* use charset string to modify display method */ + if (ct->c_termproc) { + char term[BUFSIZ]; + + strncpy (term, buffer, sizeof(term)); + snprintf (buffer, sizeof(buffer), ct->c_termproc, term); + } + +got_command: + return show_content_aux2 (ct, serial, alternate, cracked, buffer, + fd, xlist, xpause, xstdin, xtty); +} + + +/* + * Routine to actually display the content + */ + +static int +show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer, + int fd, int xlist, int xpause, int xstdin, int xtty) +{ + pid_t child_id; + int i; + char *vec[4], exec[BUFSIZ + sizeof "exec "]; + + if (debugsw || cracked) { + fflush (stdout); + + fprintf (stderr, "%s msg %s", cracked ? "storing" : "show", + ct->c_file); + if (ct->c_partno) + fprintf (stderr, " part %s", ct->c_partno); + if (cracked) + fprintf (stderr, " using command (cd %s; %s)\n", cracked, buffer); + else + fprintf (stderr, " using command %s\n", buffer); + } + + if (xpid < 0 || (xtty && xpid)) { + if (xpid < 0) + xpid = -xpid; + pidcheck(pidwait (xpid, NOTOK)); + xpid = 0; + } + + if (xlist) { + char prompt[BUFSIZ]; + + if (ct->c_type == CT_MULTIPART) + list_content (ct, -1, 1, 0, 0); + else + list_switch (ct, -1, 1, 0, 0); + + if (xpause && SOprintf ("Press to show content...")) + printf ("Press to show content..."); + + if (xpause) { + int intr; + SIGNAL_HANDLER istat; + + istat = SIGNAL (SIGINT, intrser); + if ((intr = sigsetjmp (intrenv, 1)) == OK) { + fflush (stdout); + prompt[0] = 0; + read (fileno (stdout), prompt, sizeof(prompt)); + } + SIGNAL (SIGINT, istat); + if (intr != OK) { + (*ct->c_ceclosefnx) (ct); + return (alternate ? DONE : NOTOK); + } + } + } + + snprintf (exec, sizeof(exec), "exec %s", buffer); + + vec[0] = "/bin/sh"; + vec[1] = "-c"; + vec[2] = exec; + vec[3] = NULL; + + fflush (stdout); + + for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + advise ("fork", "unable to"); + (*ct->c_ceclosefnx) (ct); + return NOTOK; + + case OK: + if (cracked) + chdir (cracked); + if (!xstdin) + dup2 (fd, 0); + close (fd); + execvp ("/bin/sh", vec); + fprintf (stderr, "unable to exec "); + perror ("/bin/sh"); + _exit (-1); + /* NOTREACHED */ + + default: + if (!serial) { + ct->c_pid = child_id; + if (xtty) + xpid = child_id; + } else { + pidcheck (pidXwait (child_id, NULL)); + } + + if (fd != NOTOK) + (*ct->c_ceclosefnx) (ct); + return (alternate ? DONE : OK); + } +} + + +/* + * show content of type "text" + */ + +static int +show_text (CT ct, int serial, int alternate) +{ + char *cp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* Check for mhn-show-type/subtype */ + snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* Check for mhn-show-type */ + snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* + * Use default method if content is text/plain, or if + * if it is not a text part of a multipart/alternative + */ + if (!alternate || ct->c_subtype == TEXT_PLAIN) { + snprintf (buffer, sizeof(buffer), "%%p%s '%%F'", progsw ? progsw : + moreproc && *moreproc ? moreproc : "more"); + cp = (ct->c_showproc = add (buffer, NULL)); + return show_content_aux (ct, serial, alternate, cp, NULL); + } + + return NOTOK; +} + + +/* + * show message body of type "multipart" + */ + +static int +show_multi (CT ct, int serial, int alternate) +{ + char *cp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* Check for mhn-show-type/subtype */ + snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_multi_aux (ct, serial, alternate, cp); + + /* Check for mhn-show-type */ + snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_multi_aux (ct, serial, alternate, cp); + + if ((cp = ct->c_showproc)) + return show_multi_aux (ct, serial, alternate, cp); + + /* + * Use default method to display this multipart content + * if it is not a (nested) part of a multipart/alternative, + * or if it is one of the known subtypes of multipart. + */ + if (!alternate || ct->c_subtype != MULTI_UNKNOWN) + return show_multi_internal (ct, serial, alternate); + + return NOTOK; +} + + +/* + * show message body of subtypes of multipart that + * we understand directly (mixed, alternate, etc...) + */ + +static int +show_multi_internal (CT ct, int serial, int alternate) +{ + int alternating, nowalternate, nowserial, result; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + CT p; + sigset_t set, oset; + + alternating = 0; + nowalternate = alternate; + + if (ct->c_subtype == MULTI_PARALLEL) { + nowserial = serialsw; + } else if (ct->c_subtype == MULTI_ALTERNATE) { + nowalternate = 1; + alternating = 1; + nowserial = serial; + } else { + /* + * multipart/mixed + * mutlipart/digest + * unknown subtypes of multipart (treat as mixed per rfc2046) + */ + nowserial = serial; + } + + /* block a few signals */ + if (!nowserial) { + sigemptyset (&set); + sigaddset (&set, SIGHUP); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGQUIT); + sigaddset (&set, SIGTERM); + SIGPROCMASK (SIG_BLOCK, &set, &oset); + } + +/* + * alternate -> we are a part inside an multipart/alternative + * alternating -> we are a multipart/alternative + */ + + result = alternate ? NOTOK : OK; + + for (part = m->mp_parts; part; part = part->mp_next) { + p = part->mp_part; + + if (part_ok (p, 0) && type_ok (p, 0)) { + int inneresult; + + inneresult = show_switch (p, nowserial, nowalternate); + switch (inneresult) { + case NOTOK: + if (alternate && !alternating) { + result = NOTOK; + goto out; + } + continue; + + case OK: + case DONE: + if (alternating) { + result = DONE; + break; + } + if (alternate) { + alternate = nowalternate = 0; + if (result == NOTOK) + result = inneresult; + } + continue; + } + break; + } + } + + if (alternating && !part) { + if (!alternate) + content_error (NULL, ct, "don't know how to display any of the contents"); + result = NOTOK; + goto out; + } + + if (serial && !nowserial) { + pid_t pid; + int kids; +#ifdef WAITINT + int status; +#else + union wait status; +#endif + + kids = 0; + for (part = m->mp_parts; part; part = part->mp_next) { + p = part->mp_part; + + if (p->c_pid > OK) + if (kill (p->c_pid, 0) == NOTOK) + p->c_pid = 0; + else + kids++; + } + + while (kids > 0 && (pid = wait (&status)) != NOTOK) { +#ifdef WAITINT + pidcheck (status); +#else + pidcheck (status.w_status); +#endif + + for (part = m->mp_parts; part; part = part->mp_next) { + p = part->mp_part; + + if (xpid == pid) + xpid = 0; + if (p->c_pid == pid) { + p->c_pid = 0; + kids--; + break; + } + } + } + } + +out: + if (!nowserial) { + /* reset the signal mask */ + SIGPROCMASK (SIG_SETMASK, &oset, &set); + } + + return result; +} + + +/* + * Parse display string for multipart content + * and use external program to display it. + */ + +static int +show_multi_aux (CT ct, int serial, int alternate, char *cp) +{ + int len, buflen; + int xlist, xpause, xtty; + char *bp, *file, buffer[BUFSIZ]; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + CI ci = &ct->c_ctinfo; + CT p; + + for (part = m->mp_parts; part; part = part->mp_next) { + p = part->mp_part; + + if (!p->c_ceopenfnx) { + if (!alternate) + content_error (NULL, p, "don't know how to decode content"); + return NOTOK; + } + + if (p->c_storage == NULL) { + file = NULL; + if ((*p->c_ceopenfnx) (p, &file) == NOTOK) + return NOTOK; + + /* I'm not sure if this is necessary? */ + p->c_storage = add (file, NULL); + + if (p->c_showproc && !strcmp (p->c_showproc, "true")) + return (alternate ? DONE : OK); + (*p->c_ceclosefnx) (p); + } + } + + xlist = 0; + xpause = 0; + xtty = 0; + + /* get buffer ready to go */ + bp = buffer; + bp[0] = '\0'; + buflen = sizeof(buffer); + + /* Now parse display string */ + for ( ; *cp; cp++) { + if (*cp == '%') { + switch (*++cp) { + case 'a': + /* insert parameters from Content-Type field */ + { + char **ap, **ep; + char *s = ""; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + len = strlen (bp); + bp += len; + buflen -= len; + s = " "; + } + } + break; + + case 'd': + /* insert content description */ + if (ct->c_descr) { + char *s; + + s = trimcpy (ct->c_descr); + strncpy (bp, s, buflen); + free (s); + } + break; + + case 'e': + /* exclusive execution */ + xtty = 1; + break; + + case 'F': + /* %e and %f */ + xtty = 1; + /* and fall... */ + + case 'f': + /* insert filename(s) containing content */ + { + char *s = ""; + + for (part = m->mp_parts; part; part = part->mp_next) { + p = part->mp_part; + + snprintf (bp, buflen, "%s'%s'", s, p->c_storage); + len = strlen (bp); + bp += len; + buflen -= len; + s = " "; + } + } + break; + + case 'p': + /* %l, and pause prior to displaying content */ + xpause = pausesw; + /* and fall... */ + + case 'l': + /* display listing prior to displaying content */ + xlist = !nolist; + break; + + case 's': + /* insert subtype of content */ + strncpy (bp, ci->ci_subtype, buflen); + break; + + case '%': + /* insert character % */ + goto raw; + + default: + *bp++ = *--cp; + *bp = '\0'; + buflen--; + continue; + } + len = strlen (bp); + bp += len; + buflen -= len; + } else { +raw: + *bp++ = *cp; + *bp = '\0'; + buflen--; + } + } + + /* use charset string to modify display method */ + if (ct->c_termproc) { + char term[BUFSIZ]; + + strncpy (term, buffer, sizeof(term)); + snprintf (buffer, sizeof(buffer), ct->c_termproc, term); + } + + return show_content_aux2 (ct, serial, alternate, NULL, buffer, + NOTOK, xlist, xpause, 0, xtty); +} + + +/* + * show content of type "message/rfc822" + */ + +static int +show_message_rfc822 (CT ct, int serial, int alternate) +{ + char *cp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; + + /* Check for mhn-show-type/subtype */ + snprintf (buffer, sizeof(buffer), "%s-show-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* Check for mhn-show-type */ + snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) && *cp != '\0') + return show_content_aux (ct, serial, alternate, cp, NULL); + + if ((cp = ct->c_showproc)) + return show_content_aux (ct, serial, alternate, cp, NULL); + + /* default method for message/rfc822 */ + if (ct->c_subtype == MESSAGE_RFC822) { + cp = (ct->c_showproc = add ("%pshow -file '%F'", NULL)); + return show_content_aux (ct, serial, alternate, cp, NULL); + } + + /* complain if we are not a part of a multipart/alternative */ + if (!alternate) + content_error (NULL, ct, "don't know how to display content"); + + return NOTOK; +} + + +/* + * Show content of type "message/partial". + */ + +static int +show_partial (CT ct, int serial, int alternate) +{ + content_error (NULL, ct, + "in order to display this message, you must reassemble it"); + return NOTOK; +} + + +/* + * Show content of type "message/external". + * + * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET. + */ + +static int +show_external (CT ct, int serial, int alternate) +{ + struct exbody *e = (struct exbody *) ct->c_ctparams; + CT p = e->eb_content; + + if (!type_ok (p, 0)) + return OK; + + return show_switch (p, serial, alternate); + +#if 0 + content_error (NULL, p, "don't know how to display content"); + return NOTOK; +#endif +} + + +static RETSIGTYPE +intrser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGINT, intrser); +#endif + + putchar ('\n'); + siglongjmp (intrenv, DONE); +} diff --git a/uip/mhstore.c b/uip/mhstore.c new file mode 100644 index 0000000..a52e00b --- /dev/null +++ b/uip/mhstore.c @@ -0,0 +1,440 @@ + +/* + * mhstore.c -- store the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +/* + * We allocate space for message names (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define AUTOSW 0 + { "auto", 0 }, +#define NAUTOSW 1 + { "noauto", 0 }, +#define CHECKSW 2 + { "check", 0 }, +#define NCHECKSW 3 + { "nocheck", 0 }, +#define VERBSW 4 + { "verbose", 0 }, +#define NVERBSW 5 + { "noverbose", 0 }, +#define FILESW 6 /* interface from show */ + { "file file", 0 }, +#define PARTSW 7 + { "part number", 0 }, +#define TYPESW 8 + { "type content", 0 }, +#define RCACHESW 9 + { "rcache policy", 0 }, +#define WCACHESW 10 + { "wcache policy", 0 }, +#define VERSIONSW 11 + { "version", 0 }, +#define HELPSW 12 + { "help", 4 }, + +/* + * switches for debugging + */ +#define DEBUGSW 13 + { "debug", -5 }, + { NULL, 0 } +}; + + +extern int errno; + +/* mhparse.c */ +extern int checksw; +extern char *tmp; /* directory to place temp files */ + +/* mhcachesbr.c */ +extern int rcachesw; +extern int wcachesw; +extern char *cache_public; +extern char *cache_private; + +/* mhstoresbr.c */ +extern int autosw; +extern char *cwd; /* cache current working directory */ + +/* mhmisc.c */ +extern int npart; +extern int ntype; +extern char *parts[NPARTS + 1]; +extern char *types[NTYPES + 1]; +extern int userrs; + +int debugsw = 0; +int verbosw = 0; + +/* The list of top-level contents to display */ +CT *cts = NULL; + +#define quitser pipeser + +/* mhparse.c */ +CT parse_mime (char *); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void set_endian (void); +void flush_errors (void); + +/* mhstoresbr.c */ +void store_all_messages (CT *); + +/* mhfree.c */ +void free_content (CT); + +/* + * static prototypes + */ +static RETSIGTYPE pipeser (int); + + +int +main (int argc, char **argv) +{ + int nummsgs, maxmsgs, msgnum, *icachesw; + char *cp, *file = NULL, *folder = NULL; + char *maildir, buf[100], **argp; + char **arguments, **msgs; + struct msgs *mp = NULL; + CT ct, *ctp; + FILE *fp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case AUTOSW: + autosw++; + continue; + case NAUTOSW: + autosw = 0; + continue; + + case RCACHESW: + icachesw = &rcachesw; + goto do_cache; + case WCACHESW: + icachesw = &wcachesw; +do_cache: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + switch (*icachesw = smatch (cp, caches)) { + case AMBIGSW: + ambigsw (cp, caches); + done (1); + case UNKWNSW: + adios (NULL, "%s unknown", cp); + default: + break; + } + continue; + + case CHECKSW: + checksw++; + continue; + case NCHECKSW: + checksw = 0; + continue; + + case PARTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (npart >= NPARTS) + adios (NULL, "too many parts (starting with %s), %d max", + cp, NPARTS); + parts[npart++] = cp; + continue; + + case TYPESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (ntype >= NTYPES) + adios (NULL, "too many types (starting with %s), %d max", + cp, NTYPES); + types[ntype++] = cp; + continue; + + case FILESW: + if (!(cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + file = *cp == '-' ? cp : path (cp, TFILE); + continue; + + case VERBSW: + verbosw = 1; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + /* null terminate the list of acceptable parts/types */ + parts[npart] = NULL; + types[ntype] = NULL; + + set_endian (); + + /* + * Check if we've specified an additional profile + */ + if ((cp = getenv ("MHSTORE"))) { + if ((fp = fopen (cp, "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } else { + admonish ("", "unable to read $MHSTORE profile (%s)", cp); + } + } + + /* + * Read the standard profile setup + */ + if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) { + readconfig ((struct node **) 0, fp, cp, 0); + fclose (fp); + } + + /* Check for public cache location */ + if ((cache_public = context_find (nmhcache)) && *cache_public != '/') + cache_public = NULL; + + /* Check for private cache location */ + if (!(cache_private = context_find (nmhprivcache))) + cache_private = ".cache"; + cache_private = getcpy (m_maildir (cache_private)); + + /* + * Cache the current directory before we do any chdirs()'s. + */ + cwd = getcpy (pwd()); + + /* + * Check for storage directory. If specified, + * then store temporary files there. Else we + * store them in standard nmh directory. + */ + if ((cp = context_find (nmhstorage)) && *cp) + tmp = concat (cp, "/", invo_name, NULL); + else + tmp = add (m_maildir (invo_name), NULL); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (file && nummsgs) + adios (NULL, "cannot specify msg and file at same time!"); + + /* + * check if message is coming from file + */ + if (file) { + if (!(cts = (CT *) calloc ((size_t) 2, sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + if ((ct = parse_mime (file))); + *ctp++ = ct; + } else { + /* + * message(s) are coming from a folder + */ + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (!(cts = (CT *) calloc ((size_t) (mp->numsel + 1), sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + char *msgnam; + + msgnam = m_name (msgnum); + if ((ct = parse_mime (msgnam))) + *ctp++ = ct; + } + } + } + + if (!*cts) + done (1); + + userrs = 1; + SIGNAL (SIGQUIT, quitser); + SIGNAL (SIGPIPE, pipeser); + + /* + * Get the associated umask for the relevant contents. + */ + for (ctp = cts; *ctp; ctp++) { + struct stat st; + + ct = *ctp; + if (type_ok (ct, 1) && !ct->c_umask) { + if (stat (ct->c_file, &st) != NOTOK) + ct->c_umask = ~(st.st_mode & 0777); + else + ct->c_umask = ~m_gmprot(); + } + } + + /* + * Store the message content + */ + store_all_messages (cts); + + /* Now free all the structures for the content */ + for (ctp = cts; *ctp; ctp++) + free_content (*ctp); + + free ((char *) cts); + cts = NULL; + + /* If reading from a folder, do some updating */ + if (mp) { + context_replace (pfolder, folder);/* update current folder */ + seq_setcur (mp, mp->hghsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + done (0); + /* NOTREACHED */ +} + + +static RETSIGTYPE +pipeser (int i) +{ + if (i == SIGQUIT) { + unlink ("core"); + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + done (1); + /* NOTREACHED */ +} + + +void +done (int status) +{ + CT *ctp; + + if ((ctp = cts)) + for (; *ctp; ctp++) + free_content (*ctp); + + exit (status); +} diff --git a/uip/mhstoresbr.c b/uip/mhstoresbr.c new file mode 100644 index 0000000..d849609 --- /dev/null +++ b/uip/mhstoresbr.c @@ -0,0 +1,1112 @@ + +/* + * mhstoresbr.c -- routines to save/store the contents of MIME messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int errno; + +/* + * The list of top-level contents to display + */ +extern CT *cts; + +int autosw = 0; + +/* + * Cache of current directory. This must be + * set before these routines are called. + */ +char *cwd; + +/* + * The directory in which to store the contents. + */ +static char *dir; + +/* + * Type for a compare function for qsort. This keeps + * the compiler happy. + */ +typedef int (*qsort_comp) (const void *, const void *); + + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +int make_intermediates (char *); +void flush_errors (void); + +/* mhshowsbr.c */ +int show_content_aux (CT, int, int, char *, char *); + +/* + * prototypes + */ +void store_all_messages (CT *); + +/* + * static prototypes + */ +static void store_single_message (CT); +static int store_switch (CT); +static int store_generic (CT); +static int store_application (CT); +static int store_multi (CT); +static int store_partial (CT); +static int store_external (CT); +static int ct_compar (CT *, CT *); +static int store_content (CT, CT); +static int output_content_file (CT, int); +static int check_folder (char *); +static int output_content_folder (char *, char *); +static int parse_format_string (CT, char *, char *, int, char *); +static void get_storeproc (CT); +static int copy_some_headers (FILE *, CT); + + +/* + * Main entry point to store content + * from a collection of messages. + */ + +void +store_all_messages (CT *cts) +{ + CT ct, *ctp; + char *cp; + + /* + * Check for the directory in which to + * store any contents. + */ + if (autosw) + dir = getcpy (cwd); + else if ((cp = context_find (nmhstorage)) && *cp) + dir = getcpy (cp); + else + dir = getcpy (cwd); + + for (ctp = cts; *ctp; ctp++) { + ct = *ctp; + store_single_message (ct); + } + + flush_errors (); +} + + +/* + * Entry point to store the content + * in a (single) message + */ + +static void +store_single_message (CT ct) +{ + if (type_ok (ct, 1)) { + umask (ct->c_umask); + store_switch (ct); + if (ct->c_fp) { + fclose (ct->c_fp); + ct->c_fp = NULL; + } + if (ct->c_ceclosefnx) + (*ct->c_ceclosefnx) (ct); + } +} + + +/* + * Switching routine to store different content types + */ + +static int +store_switch (CT ct) +{ + switch (ct->c_type) { + case CT_MULTIPART: + return store_multi (ct); + break; + + case CT_MESSAGE: + switch (ct->c_subtype) { + case MESSAGE_PARTIAL: + return store_partial (ct); + break; + + case MESSAGE_EXTERNAL: + return store_external (ct); + + case MESSAGE_RFC822: + default: + return store_generic (ct); + break; + } + break; + + case CT_APPLICATION: + return store_application (ct); + break; + + case CT_TEXT: + case CT_AUDIO: + case CT_IMAGE: + case CT_VIDEO: + return store_generic (ct); + break; + + default: + adios (NULL, "unknown content type %d", ct->c_type); + break; + } + + return OK; /* NOT REACHED */ +} + + +/* + * Generic routine to store a MIME content. + * (audio, video, image, text, message/rfc922) + */ + +static int +store_generic (CT ct) +{ + /* + * Check if the content specifies a filename. + * Don't bother with this for type "message" + * (only "message/rfc822" will use store_generic). + */ + if (autosw && ct->c_type != CT_MESSAGE) + get_storeproc (ct); + + return store_content (ct, NULL); +} + + +/* + * Store content of type "application" + */ + +static int +store_application (CT ct) +{ + char **ap, **ep; + CI ci = &ct->c_ctinfo; + + /* Check if the content specifies a filename */ + if (autosw) + get_storeproc (ct); + + /* + * If storeproc is not defined, and the content is type + * "application/octet-stream", we also check for various + * attribute/value pairs which specify if this a tar file. + */ + if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) { + int tarP = 0, zP = 0; + + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + /* check for "type=tar" attribute */ + if (!strcasecmp (*ap, "type")) { + if (strcasecmp (*ep, "tar")) + break; + + tarP = 1; + continue; + } + + /* check for "conversions=compress" attribute */ + if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions")) + && (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) { + zP = 1; + continue; + } + } + + if (tarP) { + ct->c_showproc = add (zP ? "%euncompress | tar tvf -" + : "%etar tvf -", NULL); + if (!ct->c_storeproc) + if (autosw) { + ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -" + : "| tar xvpf -", NULL); + ct->c_umask = 0022; + } else { + ct->c_storeproc = add (zP ? "%m%P.tar.Z" : "%m%P.tar", NULL); + } + } + } + + return store_content (ct, NULL); +} + + +/* + * Store the content of a multipart message + */ + +static int +store_multi (CT ct) +{ + int result; + struct multipart *m = (struct multipart *) ct->c_ctparams; + struct part *part; + + result = NOTOK; + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; + + if (part_ok (p, 1) && type_ok (p, 1)) { + result = store_switch (p); + if (result == OK && ct->c_subtype == MULTI_ALTERNATE) + break; + } + } + + return result; +} + + +/* + * Reassemble and store the contents of a collection + * of messages of type "message/partial". + */ + +static int +store_partial (CT ct) +{ + int cur, hi, i; + CT p, *ctp, *ctq; + CT *base; + struct partial *pm, *qm; + + qm = (struct partial *) ct->c_ctparams; + if (qm->pm_stored) + return OK; + + hi = i = 0; + for (ctp = cts; *ctp; ctp++) { + p = *ctp; + if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) { + pm = (struct partial *) p->c_ctparams; + if (!pm->pm_stored + && strcmp (qm->pm_partid, pm->pm_partid) == 0) { + pm->pm_marked = pm->pm_partno; + if (pm->pm_maxno) + hi = pm->pm_maxno; + pm->pm_stored = 1; + i++; + } + else + pm->pm_marked = 0; + } + } + + if (hi == 0) { + advise (NULL, "missing (at least) last part of multipart message"); + return NOTOK; + } + + if ((base = (CT *) calloc ((size_t) (i + 1), sizeof(*base))) == NULL) + adios (NULL, "out of memory"); + + ctq = base; + for (ctp = cts; *ctp; ctp++) { + p = *ctp; + if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) { + pm = (struct partial *) p->c_ctparams; + if (pm->pm_marked) + *ctq++ = p; + } + } + *ctq = NULL; + + if (i > 1) + qsort ((char *) base, i, sizeof(*base), (qsort_comp) ct_compar); + + cur = 1; + for (ctq = base; *ctq; ctq++) { + p = *ctq; + pm = (struct partial *) p->c_ctparams; + if (pm->pm_marked != cur) { + if (pm->pm_marked == cur - 1) { + admonish (NULL, + "duplicate part %d of %d part multipart message", + pm->pm_marked, hi); + continue; + } + +missing_part: + advise (NULL, + "missing %spart %d of %d part multipart message", + cur != hi ? "(at least) " : "", cur, hi); + goto losing; + } + else + cur++; + } + if (hi != --cur) { + cur = hi; + goto missing_part; + } + + /* + * Now cycle through the sorted list of messages of type + * "message/partial" and save/append them to a file. + */ + + ctq = base; + ct = *ctq++; + if (store_content (ct, NULL) == NOTOK) { +losing: + free ((char *) base); + return NOTOK; + } + + for (; *ctq; ctq++) { + p = *ctq; + if (store_content (p, ct) == NOTOK) + goto losing; + } + + free ((char *) base); + return OK; +} + + +/* + * Store content from a message of type "message/external". + */ + +static int +store_external (CT ct) +{ + int result = NOTOK; + struct exbody *e = (struct exbody *) ct->c_ctparams; + CT p = e->eb_content; + + if (!type_ok (p, 1)) + return OK; + + /* + * Check if the parameters for the external body + * specified a filename. + */ + if (autosw) { + char *cp; + + if ((cp = e->eb_name) + && *cp != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { + if (!ct->c_storeproc) + ct->c_storeproc = add (cp, NULL); + if (!p->c_storeproc) + p->c_storeproc = add (cp, NULL); + } + } + + /* + * Since we will let the Content structure for the + * external body substitute for the current content, + * we temporarily change its partno (number inside + * multipart), so everything looks right. + */ + p->c_partno = ct->c_partno; + + /* we probably need to check if content is really there */ + result = store_switch (p); + + p->c_partno = NULL; + return result; +} + + +/* + * Compare the numbering from two different + * message/partials (needed for sorting). + */ + +static int +ct_compar (CT *a, CT *b) +{ + struct partial *am = (struct partial *) ((*a)->c_ctparams); + struct partial *bm = (struct partial *) ((*b)->c_ctparams); + + return (am->pm_marked - bm->pm_marked); +} + + +/* + * Store contents of a message or message part to + * a folder, a file, the standard output, or pass + * the contents to a command. + * + * If the current content to be saved is a followup part + * to a collection of messages of type "message/partial", + * then field "p" is a pointer to the Content structure + * to the first message/partial in the group. + */ + +static int +store_content (CT ct, CT p) +{ + int appending = 0, msgnum; + int is_partial = 0, first_partial = 0; + int last_partial = 0; + char *cp, buffer[BUFSIZ]; + + /* + * Do special processing for messages of + * type "message/partial". + * + * We first check if this content is of type + * "message/partial". If it is, then we need to check + * whether it is the first and/or last in the group. + * + * Then if "p" is a valid pointer, it points to the Content + * structure of the first partial in the group. So we copy + * the file name and/or folder name from that message. In + * this case, we also note that we will be appending. + */ + if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) { + struct partial *pm = (struct partial *) ct->c_ctparams; + + /* Yep, it's a message/partial */ + is_partial = 1; + + /* But is it the first and/or last in the collection? */ + if (pm->pm_partno == 1) + first_partial = 1; + if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno) + last_partial = 1; + + /* + * If "p" is a valid pointer, then it points to the + * Content structure for the first message in the group. + * So we just copy the filename or foldername information + * from the previous iteration of this function. + */ + if (p) { + appending = 1; + ct->c_storage = add (p->c_storage, NULL); + + /* record the folder name */ + if (p->c_folder) { + ct->c_folder = add (p->c_folder, NULL); + } + goto got_filename; + } + } + + /* + * Get storage formatting string. + * + * 1) If we have storeproc defined, then use that + * 2) Else check for a mhn-store-/ entry + * 3) Else check for a mhn-store- entry + * 4) Else if content is "message", use "+" (current folder) + * 5) Else use string "%m%P.%s". + */ + if ((cp = ct->c_storeproc) == NULL || *cp == '\0') { + CI ci = &ct->c_ctinfo; + + snprintf (buffer, sizeof(buffer), "%s-store-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) == NULL || *cp == '\0') { + snprintf (buffer, sizeof(buffer), "%s-store-%s", invo_name, ci->ci_type); + if ((cp = context_find (buffer)) == NULL || *cp == '\0') { + cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s"; + } + } + } + + /* + * Check the beginning of storage formatting string + * to see if we are saving content to a folder. + */ + if (*cp == '+' || *cp == '@') { + char *tmpfilenam, *folder; + + /* Store content in temporary file for now */ + tmpfilenam = m_scratch ("", invo_name); + ct->c_storage = add (tmpfilenam, NULL); + + /* Get the folder name */ + if (cp[1]) + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + else + folder = getfolder (1); + + /* Check if folder exists */ + if (check_folder (folder) == NOTOK) + return NOTOK; + + /* Record the folder name */ + ct->c_folder = add (folder, NULL); + + if (cp[1]) + free (folder); + + goto got_filename; + } + + /* + * Parse and expand the storage formatting string + * in `cp' into `buffer'. + */ + parse_format_string (ct, cp, buffer, sizeof(buffer), dir); + + /* + * If formatting begins with '|' or '!', then pass + * content to standard input of a command and return. + */ + if (buffer[0] == '|' || buffer[0] == '!') + return show_content_aux (ct, 1, 0, buffer + 1, dir); + + /* record the filename */ + ct->c_storage = add (buffer, NULL); + +got_filename: + /* flush the output stream */ + fflush (stdout); + + /* Now save or append the content to a file */ + if (output_content_file (ct, appending) == NOTOK) + return NOTOK; + + /* + * If necessary, link the file into a folder and remove + * the temporary file. If this message is a partial, + * then only do this if it is the last one in the group. + */ + if (ct->c_folder && (!is_partial || last_partial)) { + msgnum = output_content_folder (ct->c_folder, ct->c_storage); + unlink (ct->c_storage); + if (msgnum == NOTOK) + return NOTOK; + } + + /* + * Now print out the name/number of the message + * that we are storing. + */ + if (is_partial) { + if (first_partial) + fprintf (stderr, "reassembling partials "); + if (last_partial) + fprintf (stderr, "%s", ct->c_file); + else + fprintf (stderr, "%s,", ct->c_file); + } else { + fprintf (stderr, "storing message %s", ct->c_file); + if (ct->c_partno) + fprintf (stderr, " part %s", ct->c_partno); + } + + /* + * Unless we are in the "middle" of group of message/partials, + * we now print the name of the file, folder, and/or message + * to which we are storing the content. + */ + if (!is_partial || last_partial) { + if (ct->c_folder) { + fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, msgnum); + } else if (!strcmp(ct->c_storage, "-")) { + fprintf (stderr, " to stdout\n"); + } else { + int cwdlen; + + cwdlen = strlen (cwd); + fprintf (stderr, " as file %s\n", + strncmp (ct->c_storage, cwd, cwdlen) + || ct->c_storage[cwdlen] != '/' + ? ct->c_storage : ct->c_storage + cwdlen + 1); + } + } + + return OK; +} + + +/* + * Output content to a file + */ + +static int +output_content_file (CT ct, int appending) +{ + int filterstate; + char *file, buffer[BUFSIZ]; + long pos, last; + FILE *fp; + + /* + * If the pathname is absolute, make sure + * all the relevant directories exist. + */ + if (strchr(ct->c_storage, '/') + && make_intermediates (ct->c_storage) == NOTOK) + return NOTOK; + + if (ct->c_encoding != CE_7BIT) { + int cc, fd; + + if (!ct->c_ceopenfnx) { + advise (NULL, "don't know how to decode part %s of message %s", + ct->c_partno, ct->c_file); + return NOTOK; + } + + file = appending || !strcmp (ct->c_storage, "-") ? NULL + : ct->c_storage; + if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) + return NOTOK; + if (!strcmp (file, ct->c_storage)) { + (*ct->c_ceclosefnx) (ct); + return OK; + } + + /* + * Send to standard output + */ + if (!strcmp (ct->c_storage, "-")) { + int gd; + + if ((gd = dup (fileno (stdout))) == NOTOK) { + advise ("stdout", "unable to dup"); +losing: + (*ct->c_ceclosefnx) (ct); + return NOTOK; + } + if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) { + advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd, + appending ? "a" : "w"); + close (gd); + goto losing; + } + } else { + /* + * Open output file + */ + if ((fp = fopen (ct->c_storage, appending ? "a" : "w")) == NULL) { + advise (ct->c_storage, "unable to fopen for %s", + appending ? "appending" : "writing"); + goto losing; + } + } + + /* + * Filter the header fields of the initial enclosing + * message/partial into the file. + */ + if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) { + struct partial *pm = (struct partial *) ct->c_ctparams; + + if (pm->pm_partno == 1) + copy_some_headers (fp, ct); + } + + for (;;) { + switch (cc = read (fd, buffer, sizeof(buffer))) { + case NOTOK: + advise (file, "error reading content from"); + break; + + case OK: + break; + + default: + fwrite (buffer, sizeof(*buffer), cc, fp); + continue; + } + break; + } + + (*ct->c_ceclosefnx) (ct); + + if (cc != NOTOK && fflush (fp)) + advise (ct->c_storage, "error writing to"); + + fclose (fp); + + return (cc != NOTOK ? OK : NOTOK); + } + + if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { + advise (ct->c_file, "unable to open for reading"); + return NOTOK; + } + + pos = ct->c_begin; + last = ct->c_end; + fseek (ct->c_fp, pos, SEEK_SET); + + if (!strcmp (ct->c_storage, "-")) { + int gd; + + if ((gd = dup (fileno (stdout))) == NOTOK) { + advise ("stdout", "unable to dup"); + return NOTOK; + } + if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) { + advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd, + appending ? "a" : "w"); + close (gd); + return NOTOK; + } + } else { + if ((fp = fopen (ct->c_storage, appending ? "a" : "w")) == NULL) { + advise (ct->c_storage, "unable to fopen for %s", + appending ? "appending" : "writing"); + return NOTOK; + } + } + + /* + * Copy a few of the header fields of the initial + * enclosing message/partial into the file. + */ + filterstate = 0; + if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) { + struct partial *pm = (struct partial *) ct->c_ctparams; + + if (pm->pm_partno == 1) { + copy_some_headers (fp, ct); + filterstate = 1; + } + } + + while (fgets (buffer, sizeof(buffer) - 1, ct->c_fp)) { + if ((pos += strlen (buffer)) > last) { + int diff; + + diff = strlen (buffer) - (pos - last); + if (diff >= 0) + buffer[diff] = '\0'; + } + /* + * If this is the first content of a group of + * message/partial contents, then we only copy a few + * of the header fields of the enclosed message. + */ + if (filterstate) { + switch (buffer[0]) { + case ' ': + case '\t': + if (filterstate < 0) + buffer[0] = 0; + break; + + case '\n': + filterstate = 0; + break; + + default: + if (!uprf (buffer, XXX_FIELD_PRF) + && !uprf (buffer, VRSN_FIELD) + && !uprf (buffer, "Subject:") + && !uprf (buffer, "Encrypted:") + && !uprf (buffer, "Message-ID:")) { + filterstate = -1; + buffer[0] = 0; + break; + } + filterstate = 1; + break; + } + } + fputs (buffer, fp); + if (pos >= last) + break; + } + + if (fflush (fp)) + advise (ct->c_storage, "error writing to"); + + fclose (fp); + fclose (ct->c_fp); + ct->c_fp = NULL; + return OK; +} + + +/* + * Check if folder exists, and create + * if necessary. + */ + +static int +check_folder (char *folder) +{ + char *folderdir; + struct stat st; + + /* expand path to the folder */ + folderdir = m_mailpath (folder); + + /* Check if folder exists */ + if (stat (folderdir, &st) == NOTOK) { + int answer; + char *ep; + + if (errno != ENOENT) { + advise (folderdir, "error on folder"); + return NOTOK; + } + + ep = concat ("Create folder \"", folderdir, "\"? ", NULL); + answer = getanswer (ep); + free (ep); + + if (!answer) + return NOTOK; + + if (!makedir (folderdir)) { + advise (NULL, "unable to create folder %s", folderdir); + return NOTOK; + } + } + + return OK; +} + + +/* + * Add a file to a folder. + * + * Return the new message number of the file + * when added to the folder. Return -1, if + * there is an error. + */ + +static int +output_content_folder (char *folder, char *filename) +{ + int msgnum; + struct msgs *mp; + + /* Read the folder. */ + if ((mp = folder_read (folder))) { + /* Link file into folder */ + msgnum = folder_addmsg (&mp, filename, 0, 0, 0); + } else { + advise (NULL, "unable to read folder %s", folder); + return NOTOK; + } + + /* free folder structure */ + folder_free (mp); + + /* + * Return msgnum. We are relying on the fact that + * msgnum will be -1, if folder_addmsg() had an error. + */ + return msgnum; +} + + +/* + * Parse and expand the storage formatting string + * pointed to by "cp" into "buffer". + */ + +static int +parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir) +{ + int len; + char *bp; + CI ci = &ct->c_ctinfo; + + /* + * If storage string is "-", just copy it, and + * return (send content to standard output). + */ + if (cp[0] == '-' && cp[1] == '\0') { + strncpy (buffer, cp, buflen); + return 0; + } + + bp = buffer; + bp[0] = '\0'; + + /* + * If formatting string is a pathname that doesn't + * begin with '/', then preface the path with the + * appropriate directory. + */ + if (*cp != '/' && *cp != '|' && *cp != '!') { + snprintf (bp, buflen, "%s/", dir[1] ? dir : ""); + len = strlen (bp); + bp += len; + buflen -= len; + } + + for (; *cp; cp++) { + + /* We are processing a storage escape */ + if (*cp == '%') { + switch (*++cp) { + case 'a': + /* + * Insert parameters from Content-Type. + * This is only valid for '|' commands. + */ + if (buffer[0] != '|' && buffer[0] != '!') { + *bp++ = *--cp; + *bp = '\0'; + buflen--; + continue; + } else { + char **ap, **ep; + char *s = ""; + + for (ap = ci->ci_attrs, ep = ci->ci_values; + *ap; ap++, ep++) { + snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep); + len = strlen (bp); + bp += len; + buflen -= len; + s = " "; + } + } + break; + + case 'm': + /* insert message number */ + snprintf (bp, buflen, "%s", r1bindex (ct->c_file, '/')); + break; + + case 'P': + /* insert part number with leading dot */ + if (ct->c_partno) + snprintf (bp, buflen, ".%s", ct->c_partno); + break; + + case 'p': + /* insert part number withouth leading dot */ + if (ct->c_partno) + strncpy (bp, ct->c_partno, buflen); + break; + + case 't': + /* insert content type */ + strncpy (bp, ci->ci_type, buflen); + break; + + case 's': + /* insert content subtype */ + strncpy (bp, ci->ci_subtype, buflen); + break; + + case '%': + /* insert the character % */ + goto raw; + + default: + *bp++ = *--cp; + *bp = '\0'; + buflen--; + continue; + } + + /* Advance bp and decrement buflen */ + len = strlen (bp); + bp += len; + buflen -= len; + + } else { +raw: + *bp++ = *cp; + *bp = '\0'; + buflen--; + } + } + + return 0; +} + + +/* + * Check if the content specifies a filename + * in its MIME parameters. + */ + +static void +get_storeproc (CT ct) +{ + char **ap, **ep, *cp; + CI ci = &ct->c_ctinfo; + + /* + * If the storeproc has already been defined, + * we just return (for instance, if this content + * is part of a "message/external". + */ + if (ct->c_storeproc) + return; + + /* + * Check the attribute/value pairs, for the attribute "name". + * If found, do a few sanity checks and copy the value into + * the storeproc. + */ + for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { + if (!strcasecmp (*ap, "name") + && *(cp = *ep) != '/' + && *cp != '.' + && *cp != '|' + && *cp != '!' + && !strchr (cp, '%')) { + ct->c_storeproc = add (cp, NULL); + return; + } + } +} + + +/* + * Copy some of the header fields of the initial message/partial + * message into the header of the reassembled message. + */ + +static int +copy_some_headers (FILE *out, CT ct) +{ + HF hp; + + hp = ct->c_first_hf; /* start at first header field */ + + while (hp) { + /* + * A few of the header fields of the enclosing + * messages are not copied. + */ + if (!uprf (hp->name, XXX_FIELD_PRF) + && strcasecmp (hp->name, VRSN_FIELD) + && strcasecmp (hp->name, "Subject") + && strcasecmp (hp->name, "Encrypted") + && strcasecmp (hp->name, "Message-ID")) + fprintf (out, "%s:%s", hp->name, hp->value); + hp = hp->next; /* next header field */ + } + + return OK; +} diff --git a/uip/mhtest.c b/uip/mhtest.c new file mode 100644 index 0000000..80a0c7b --- /dev/null +++ b/uip/mhtest.c @@ -0,0 +1,434 @@ + +/* + * mhtest.c -- test harness for MIME routines + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +/* + * We allocate space for message names (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define CHECKSW 0 + { "check", 0 }, +#define NCHECKSW 1 + { "nocheck", 0 }, +#define VERBSW 2 + { "verbose", 0 }, +#define NVERBSW 3 + { "noverbose", 0 }, +#define FILESW 4 + { "file file", 0 }, +#define OUTFILESW 5 + { "outfile file", 0 }, +#define PARTSW 6 + { "part number", 0 }, +#define TYPESW 7 + { "type content", 0 }, +#define RCACHESW 8 + { "rcache policy", 0 }, +#define WCACHESW 9 + { "wcache policy", 0 }, +#define VERSIONSW 10 + { "version", 0 }, +#define HELPSW 11 + { "help", 4 }, + +/* + * switches for debugging + */ +#define DEBUGSW 12 + { "debug", -5 }, + { NULL, 0 } +}; + + +extern int errno; + +int ebcdicsw = 0; /* hack for linking purposes */ + +/* mhparse.c */ +extern int checksw; +extern char *tmp; /* directory to place temp files */ + +/* mhcachesbr.c */ +extern int rcachesw; +extern int wcachesw; +extern char *cache_public; +extern char *cache_private; + +/* mhmisc.c */ +extern int npart; +extern int ntype; +extern char *parts[NPARTS + 1]; +extern char *types[NTYPES + 1]; +extern int userrs; + +/* + * This is currently needed to keep mhparse happy. + * This needs to be changed. + */ +pid_t xpid = 0; + +int debugsw = 0; +int verbosw = 0; + +/* The list of top-level contents to display */ +CT *cts = NULL; + +#define quitser pipeser + +/* mhparse.c */ +CT parse_mime (char *); + +/* mhoutsbr.c */ +int output_message (CT, char *); + +/* mhmisc.c */ +int part_ok (CT, int); +int type_ok (CT, int); +void set_endian (void); +void flush_errors (void); + +/* mhfree.c */ +void free_content (CT); + +/* + * static prototypes + */ +static int write_content (CT *, char *); +static RETSIGTYPE pipeser (int); + + +int +main (int argc, char **argv) +{ + int nummsgs, maxmsgs, msgnum, *icachesw; + char *cp, *file = NULL, *folder = NULL; + char *maildir, buf[100], *outfile = NULL; + char **argp, **arguments, **msgs; + struct msgs *mp = NULL; + CT ct, *ctp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case RCACHESW: + icachesw = &rcachesw; + goto do_cache; + case WCACHESW: + icachesw = &wcachesw; +do_cache: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + switch (*icachesw = smatch (cp, caches)) { + case AMBIGSW: + ambigsw (cp, caches); + done (1); + case UNKWNSW: + adios (NULL, "%s unknown", cp); + default: + break; + } + continue; + + case CHECKSW: + checksw++; + continue; + case NCHECKSW: + checksw = 0; + continue; + + case PARTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (npart >= NPARTS) + adios (NULL, "too many parts (starting with %s), %d max", + cp, NPARTS); + parts[npart++] = cp; + continue; + + case TYPESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (ntype >= NTYPES) + adios (NULL, "too many types (starting with %s), %d max", + cp, NTYPES); + types[ntype++] = cp; + continue; + + case FILESW: + if (!(cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + file = *cp == '-' ? cp : path (cp, TFILE); + continue; + + case OUTFILESW: + if (!(cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + outfile = *cp == '-' ? cp : path (cp, TFILE); + continue; + + case VERBSW: + verbosw = 1; + continue; + case NVERBSW: + verbosw = 0; + continue; + case DEBUGSW: + debugsw = 1; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + /* null terminate the list of acceptable parts/types */ + parts[npart] = NULL; + types[ntype] = NULL; + + set_endian (); + + if (outfile == NULL) + adios (NULL, "must specify output file"); + + /* Check for public cache location */ + if ((cache_public = context_find (nmhcache)) && *cache_public != '/') + cache_public = NULL; + + /* Check for private cache location */ + if (!(cache_private = context_find (nmhprivcache))) + cache_private = ".cache"; + cache_private = getcpy (m_maildir (cache_private)); + + /* + * Check for storage directory. If specified, + * then store temporary files there. Else we + * store them in standard nmh directory. + */ + if ((cp = context_find (nmhstorage)) && *cp) + tmp = concat (cp, "/", invo_name, NULL); + else + tmp = add (m_maildir (invo_name), NULL); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (file && nummsgs) + adios (NULL, "cannot specify msg and file at same time!"); + + /* + * check if message is coming from file + */ + if (file) { + if (!(cts = (CT *) calloc ((size_t) 2, sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + if ((ct = parse_mime (file))); + *ctp++ = ct; + } else { + /* + * message(s) are coming from a folder + */ + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (!(cts = (CT *) calloc ((size_t) (mp->numsel + 1), sizeof(*cts)))) + adios (NULL, "out of memory"); + ctp = cts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + char *msgnam; + + msgnam = m_name (msgnum); + if ((ct = parse_mime (msgnam))) + *ctp++ = ct; + } + } + } + + if (!*cts) + done (1); + + userrs = 1; + SIGNAL (SIGQUIT, quitser); + SIGNAL (SIGPIPE, pipeser); + + /* + * Get the associated umask for the relevant contents. + */ + for (ctp = cts; *ctp; ctp++) { + struct stat st; + + ct = *ctp; + if (type_ok (ct, 1) && !ct->c_umask) { + if (stat (ct->c_file, &st) != NOTOK) + ct->c_umask = ~(st.st_mode & 0777); + else + ct->c_umask = ~m_gmprot(); + } + } + + /* + * Write the content to a file + */ + write_content (cts, outfile); + + /* Now free all the structures for the content */ + for (ctp = cts; *ctp; ctp++) + free_content (*ctp); + + free ((char *) cts); + cts = NULL; + + /* If reading from a folder, do some updating */ + if (mp) { + context_replace (pfolder, folder);/* update current folder */ + seq_setcur (mp, mp->hghsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + done (0); + /* NOTREACHED */ +} + + +static int +write_content (CT *cts, char *outfile) +{ + CT ct, *ctp; + + for (ctp = cts; *ctp; ctp++) { + ct = *ctp; + output_message (ct, outfile); + } + + flush_errors (); + return OK; +} + + +static RETSIGTYPE +pipeser (int i) +{ + if (i == SIGQUIT) { + unlink ("core"); + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + done (1); + /* NOTREACHED */ +} + + +void +done (int status) +{ + CT *ctp; + + if ((ctp = cts)) + for (; *ctp; ctp++) + free_content (*ctp); + + exit (status); +} diff --git a/uip/msgchk.c b/uip/msgchk.c new file mode 100644 index 0000000..65fed7e --- /dev/null +++ b/uip/msgchk.c @@ -0,0 +1,419 @@ + +/* + * msgchk.c -- check for mail + * + * $Id$ + */ + +#include +#include +#include +#include + +#ifdef POP +# include +#endif + +#ifdef HESIOD +# include +#endif + +#ifndef POP +# define POPminc(a) (a) +#else +# define POPminc(a) 0 +#endif + +#ifndef RPOP +# define RPOPminc(a) (a) +#else +# define RPOPminc(a) 0 +#endif + +#ifndef APOP +# define APOPminc(a) (a) +#else +# define APOPminc(a) 0 +#endif + +static struct swit switches[] = { +#define DATESW 0 + { "date", 0 }, +#define NDATESW 1 + { "nodate", 0 }, +#define NOTESW 2 + { "notify type", 0 }, +#define NNOTESW 3 + { "nonotify type", 0 }, +#define HOSTSW 4 + { "host hostname", POPminc (-4) }, +#define USERSW 5 + { "user username", POPminc (-4) }, +#define APOPSW 6 + { "apop", APOPminc (-4) }, +#define NAPOPSW 7 + { "noapop", APOPminc (-6) }, +#define RPOPSW 8 + { "rpop", RPOPminc (-4) }, +#define NRPOPSW 9 + { "norpop", RPOPminc (-6) }, +#define VERSIONSW 10 + { "version", 0 }, +#define HELPSW 11 + { "help", 4 }, +#define SNOOPSW 12 + { "snoop", -5 }, + { NULL, 0 } +}; + +/* + * Maximum numbers of users we can check (plus + * one for the NULL vector at the end). + */ +#define MAXVEC 51 + +#define NT_NONE 0x0 +#define NT_MAIL 0x1 +#define NT_NMAI 0x2 +#define NT_ALL (NT_MAIL | NT_NMAI) + +#define NONEOK 0x0 +#define UUCPOLD 0x1 +#define UUCPNEW 0x2 +#define UUCPOK (UUCPOLD | UUCPNEW) +#define MMDFOLD 0x4 +#define MMDFNEW 0x8 +#define MMDFOK (MMDFOLD | MMDFNEW) + + +/* + * static prototypes + */ +static int donote (char *, int); +static int checkmail (char *, char *, int, int, int); + +#ifdef POP +static int remotemail (char *, char *, int, int, int, int); +#endif + + +int +main (int argc, char **argv) +{ + int datesw = 1, notifysw = NT_ALL; + int rpop, status = 0; + int snoop = 0, vecp = 0; + uid_t uid; + char *cp, *host = NULL, *user, buf[BUFSIZ]; + char **argp, **arguments, *vec[MAXVEC]; + struct passwd *pw; + +#ifdef HESIOD + struct hes_postoffice *po; + char *tmphost; +#endif + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + uid = getuid (); + user = getusername(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + +#ifdef POP + if ((cp = getenv ("MHPOPDEBUG")) && *cp) + snoop++; +#endif + +#ifdef KPOP + rpop = 1; +#else + rpop = 0; +#endif + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] [users ...]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case DATESW: + datesw++; + continue; + case NDATESW: + datesw = 0; + continue; + + case NOTESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + notifysw |= donote (cp, 1); + continue; + case NNOTESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + notifysw &= ~donote (cp, 0); + continue; + + case HOSTSW: + if (!(host = *argp++) || *host == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case USERSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (vecp >= MAXVEC-1) + adios (NULL, "you can only check %d users at a time", MAXVEC-1); + else + vec[vecp++] = cp; + continue; + + case APOPSW: + rpop = -1; + continue; + case NAPOPSW: + rpop = 0; + continue; + + case RPOPSW: + rpop = 1; + continue; + case NRPOPSW: + rpop = 0; + continue; + + case SNOOPSW: + snoop++; + continue; + } + } + if (vecp >= MAXVEC-1) + adios (NULL, "you can only check %d users at a time", MAXVEC-1); + else + vec[vecp++] = cp; + } + +#ifdef POP + /* + * If -host is not specified by user + */ + if (!host || !*host) { +# ifdef HESIOD + /* + * Scheme is: + * use MAILHOST environment variable if present, + * else try Hesiod. + * If that fails, use the default (if any) + * provided by mts.conf in mts_init() + */ + if ((tmphost = getenv("MAILHOST")) != NULL) + pophost = tmphost; + else if ((po = hes_getmailhost(vecp ? vec[0] : user)) != NULL && + strcmp(po->po_type, "POP") == 0) + pophost = po->po_host; +# endif /* HESIOD */ + /* + * If "pophost" is specified in mts.conf, + * use it as default value. + */ + if (pophost && *pophost) + host = pophost; + } + if (!host || !*host) + host = NULL; + if (!host || rpop <= 0) + setuid (uid); +#endif /* POP */ + + if (vecp != 0) + vec[vecp] = NULL; + +#ifdef POP + if (host) { + if (vecp == 0) { + status = remotemail (host, user, rpop, notifysw, 1, snoop); + } else { + for (vecp = 0; vec[vecp]; vecp++) + status += remotemail (host, vec[vecp], rpop, notifysw, 0, snoop); + } + } else { +#endif /* POP */ + + if (vecp == 0) { + char *home; + + home = (uid = geteuid()) ? home = getenv ("HOME") : NULL; + if (home == NULL) { + pw = getpwnam (user); + if (pw == NULL) + adios (NULL, "unable to get information about user"); + if (home == NULL) + home = pw->pw_dir; + } + status = checkmail (user, home, datesw, notifysw, 1); + } else { + for (vecp = 0; vec[vecp]; vecp++) { + if ((pw = getpwnam (vec[vecp]))) + status += checkmail (pw->pw_name, pw->pw_dir, datesw, notifysw, 0); + else + advise (NULL, "no such user as %s", vec[vecp]); + } + } +#ifdef POP + } /* host == NULL */ +#endif + + done (status); +} + + +static struct swit ntswitches[] = { +#define NALLSW 0 + { "all", 0 }, +#define NMAISW 1 + { "mail", 0 }, +#define NNMAISW 2 + { "nomail", 0 }, + { NULL, 0 } +}; + + +static int +donote (char *cp, int ntflag) +{ + switch (smatch (cp, ntswitches)) { + case AMBIGSW: + ambigsw (cp, ntswitches); + done (1); + case UNKWNSW: + adios (NULL, "-%snotify %s unknown", ntflag ? "" : "no", cp); + + case NALLSW: + return NT_ALL; + case NMAISW: + return NT_MAIL; + case NNMAISW: + return NT_NMAI; + } +} + + +static int +checkmail (char *user, char *home, int datesw, int notifysw, int personal) +{ + int mf, status; + char buffer[BUFSIZ]; + struct stat st; + + snprintf (buffer, sizeof(buffer), "%s/%s", mmdfldir[0] ? mmdfldir : home, mmdflfil[0] ? mmdflfil : user); + if (datesw) { + st.st_size = 0; + st.st_atime = st.st_mtime = 0; + } + mf = (stat (buffer, &st) == NOTOK || st.st_size == 0) ? NONEOK + : st.st_atime <= st.st_mtime ? MMDFNEW : MMDFOLD; + + if ((mf & UUCPOK) || (mf & MMDFOK)) { + if (notifysw & NT_MAIL) { + printf (personal ? "You have " : "%s has ", user); + if (mf & UUCPOK) + printf ("%s old-style bell", mf & UUCPOLD ? "old" : "new"); + if ((mf & UUCPOK) && (mf & MMDFOK)) + printf (" and "); + if (mf & MMDFOK) + printf ("%s%s", mf & MMDFOLD ? "old" : "new", + mf & UUCPOK ? " Internet" : ""); + printf (" mail waiting"); + } else { + notifysw = 0; + } + status = 0; + } + else { + if (notifysw & NT_NMAI) + printf (personal ? "You don't %s%s" : "%s doesn't %s", + personal ? "" : user, "have any mail waiting"); + else + notifysw = 0; + + status = 1; + } + + if (notifysw) + if (datesw && st.st_atime) + printf ("; last read on %s", dtime (&st.st_atime, 1)); + if (notifysw) + printf ("\n"); + + return status; +} + + +#ifdef POP +extern char response[]; + +static int +remotemail (char *host, char *user, int rpop, int notifysw, int personal, int snoop) +{ + int nmsgs, nbytes, status; + char *pass = NULL; + + if (user == NULL) + user = getusername (); + if (rpop > 0) + pass = getusername (); + else + ruserpass (host, &user, &pass); + + /* open the POP connection */ + if (pop_init (host, user, pass, snoop, rpop) == NOTOK + || pop_stat (&nmsgs, &nbytes) == NOTOK /* check for messages */ + || pop_quit () == NOTOK) { /* quit POP connection */ + advise (NULL, "%s", response); + return 1; + } + + if (nmsgs) { + if (notifysw & NT_MAIL) { + printf (personal ? "You have " : "%s has ", user); + printf ("%d message%s (%d bytes)", + nmsgs, nmsgs != 1 ? "s" : "", nbytes); + } + else + notifysw = 0; + + status = 0; + } else { + if (notifysw & NT_NMAI) + printf (personal ? "You don't %s%s" : "%s doesn't %s", + personal ? "" : user, "have any mail waiting"); + else + notifysw = 0; + status = 1; + } + if (notifysw) + printf (" on %s\n", host); + + return status; +} +#endif /* POP */ diff --git a/uip/msh.c b/uip/msh.c new file mode 100644 index 0000000..6755a8c --- /dev/null +++ b/uip/msh.c @@ -0,0 +1,2588 @@ + +/* + * msh.c -- The nmh shell + * + * $Id$ + */ + +/* + * TODO: + * Keep more status information in maildrop map + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_TERMIOS_H +# include +#else +# ifdef HAVE_TERMIO_H +# include +# else +# include +# endif +#endif + +#include +#include +#include +#include +#include + +#define QUOTE '\\' /* sigh */ + +static struct swit switches[] = { +#define IDSW 0 + { "idstart number", -7 }, /* interface from bbc */ +#define FDSW 1 + { "idstop number", -6 }, /* .. */ +#define QDSW 2 + { "idquit number", -6 }, /* .. */ +#define NMSW 3 + { "idname BBoard", -6 }, /* .. */ +#define PRMPTSW 4 + { "prompt string", 0 }, +#define SCANSW 5 + { "scan", 0 }, +#define NSCANSW 6 + { "noscan", 0 }, +#define READSW 7 + { "vmhread fd", -7 }, +#define WRITESW 8 + { "vmhwrite fd", -8 }, +#define PREADSW 9 + { "popread fd", -7 }, +#define PWRITSW 10 + { "popwrite fd", -8 }, +#define TCURSW 11 + { "topcur", 0 }, +#define NTCURSW 12 + { "notopcur", 0 }, +#define VERSIONSW 13 + { "version", 0 }, +#define HELPSW 14 + { "help", 4 }, + { NULL, 0 } +}; + +static int mbx_style = MMDF_FORMAT; + +/* + * FOLDER + */ +char*fmsh = NULL; /* folder instead of file */ +int modified; /* command modified folder */ +struct msgs *mp; /* used a lot */ +static int nMsgs = 0; +struct Msg *Msgs = NULL; /* Msgs[0] not used */ +static FILE *fp; /* input file */ +static FILE *yp = NULL; /* temporary file */ +static int mode; /* mode of file */ +static int numfds = 0; /* number of files cached */ +static int maxfds = 0; /* number of files cached to be cached */ +static time_t mtime = (time_t) 0; /* mtime of file */ + +/* + * VMH + */ +#define ALARM ((unsigned int) 10) +#define ttyN(c) ttyNaux ((c), NULL) + +static int vmh = 0; + +static int vmhpid = OK; +static int vmhfd0; +static int vmhfd1; +static int vmhfd2; + +static int vmhtty = NOTOK; + +#define SCAN 1 +#define STATUS 2 +#define DISPLAY 3 +#define NWIN DISPLAY + +static int topcur = 0; + +static int numwins = 0; +static int windows[NWIN + 1]; + +static jmp_buf peerenv; + +#ifdef BPOP +int pmsh = 0; /* BPOP enabled */ +extern char response[]; +#endif /* BPOP */ + +/* + * PARENT + */ +static int pfd = NOTOK; /* fd parent is reading from */ +static int ppid = 0; /* pid of parent */ + +/* + * COMMAND + */ +int interactive; /* running from a /dev/tty */ +int redirected; /* re-directing output */ +FILE *sp = NULL; /* original stdout */ + +char *cmd_name; /* command being run */ +char myfilter[BUFSIZ]; /* path to mhl.forward */ + +static char *myprompt = "(%s) ";/* prompting string */ + +/* + * BBOARDS + */ +static int gap; /* gap in BBoard-ID:s */ +static char *myname = NULL; /* BBoard name */ +char *BBoard_ID = "BBoard-ID"; /* BBoard-ID constant */ + +/* + * SIGNALS + */ +SIGNAL_HANDLER istat; /* original SIGINT */ +static SIGNAL_HANDLER pstat; /* current SIGPIPE */ +SIGNAL_HANDLER qstat; /* original SIGQUIT */ + +#ifdef SIGTSTP +SIGNAL_HANDLER tstat; /* original SIGTSTP */ +#endif + +int interrupted; /* SIGINT detected */ +int broken_pipe; /* SIGPIPE detected */ +int told_to_quit; /* SIGQUIT detected */ + +#ifdef BSD42 +int should_intr; /* signal handler should interrupt call */ +jmp_buf sigenv; /* the environment pointer */ +#endif + +/* + * prototypes + */ +int SOprintf (char *, ...); /* from termsbr.c */ +int sc_width (void); /* from termsbr.c */ +void fsetup (char *); +void setup (char *); +FILE *msh_ready (int, int); +void readids (int); +int readid (int); +void display_info (int); +int expand (char *); +void m_reset (void); +void seq_setcur (struct msgs *, int); +void padios (char *, char *, ...); +void padvise (char *, char *, ...); + + +/* + * static prototypes + */ +static void msh (int); +static int read_map (char *, long); +static int read_file (long, int); + +#ifdef BPOP +# ifdef NNTP +static int pop_statmsg (char *); +# endif /* NNTP */ +static int read_pop (void); +static int pop_action (char *); +#endif /* BPOP */ + +static void m_gMsgs (int); +FILE *msh_ready (int, int); +static int check_folder (int); +static void scanrange (int, int); +static void scanstring (char *); +static void write_ids (void); +static void quit (void); +static int getargs (char *, struct swit *, struct Cmd *); +static int getcmds (struct swit *, struct Cmd *, int); +static int parse (char *, struct Cmd *); +static int init_io (struct Cmd *, int); +static int initaux_io (struct Cmd *); +static void fin_io (struct Cmd *, int); +static void finaux_io (struct Cmd *); +static void m_init (void); +static RETSIGTYPE intrser (int); +static RETSIGTYPE pipeser (int); +static RETSIGTYPE quitser (int); +static RETSIGTYPE alrmser (int); +static int pINI (void); +static int pQRY (char *, int); +static int pQRY1 (int); +static int pQRY2 (void); +static int pCMD (char *, struct swit *, struct Cmd *); +static int pFIN (void); +static int peerwait (void); +static int ttyNaux (struct Cmd *, char *); +static int ttyR (struct Cmd *); +static int winN (struct Cmd *, int, int); +static int winR (struct Cmd *); +static int winX (int); + + +int +main (int argc, char **argv) +{ + int id = 0, scansw = 0, vmh1 = 0, vmh2 = 0; + char *cp, *file = NULL, *folder = NULL; + char **argp, **arguments, buf[BUFSIZ]; +#ifdef BPOP + int pmsh1 = 0, pmsh2 = 0; +#endif + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc,argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] file", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case IDSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((id = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case FDSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((pfd = atoi (cp)) <= 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case QDSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((ppid = atoi (cp)) <= 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case NMSW: + if (!(myname = *argp++) || *myname == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case SCANSW: + scansw++; + continue; + case NSCANSW: + scansw = 0; + continue; + + case PRMPTSW: + if (!(myprompt = *argp++) || *myprompt == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case READSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((vmh1 = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case WRITESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((vmh2 = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + + case PREADSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); +#ifdef BPOP + if ((pmsh1 = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); +#endif /* BPOP */ + continue; + case PWRITSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); +#ifdef BPOP + if ((pmsh2 = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); +#endif /* BPOP */ + continue; + + case TCURSW: + topcur++; + continue; + case NTCURSW: + topcur = 0; + continue; + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } + else + if (file) + adios (NULL, "only one file at a time!"); + else + file = cp; + } + + if (!file && !folder) + file = "./msgbox"; + if (file && folder) + adios (NULL, "use a file or a folder, not both"); + strncpy (myfilter, etcpath (mhlforward), sizeof(myfilter)); +#ifdef FIOCLEX + if (pfd > 1) + ioctl (pfd, FIOCLEX, NULL); +#endif /* FIOCLEX */ + +#ifdef BSD42 + should_intr = 0; +#endif /* BSD42 */ + istat = SIGNAL2 (SIGINT, intrser); + qstat = SIGNAL2 (SIGQUIT, quitser); + + sc_width (); /* MAGIC... */ + + if ((vmh = vmh1 && vmh2)) { + rcinit (vmh1, vmh2); + pINI (); + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); +#ifdef SIGTSTP + tstat = SIGNAL (SIGTSTP, SIG_IGN); +#endif /* SIGTSTP */ + } + +#ifdef BPOP + if (pmsh = pmsh1 && pmsh2) { + cp = getenv ("MHPOPDEBUG"); +#ifdef NNTP + if (pop_set (pmsh1, pmsh2, cp && *cp, myname) == NOTOK) +#else /* NNTP */ + if (pop_set (pmsh1, pmsh2, cp && *cp) == NOTOK) +#endif /* NNTP */ + padios (NULL, "%s", response); + if (folder) + file = folder, folder = NULL; + } +#endif /* BPOP */ + + if (folder) + fsetup (folder); + else + setup (file); + readids (id); + display_info (id > 0 ? scansw : 0); + + msh (id > 0 ? scansw : 0); + + m_reset (); + + done (0); +} + + +static struct swit mshcmds[] = { +#define ADVCMD 0 + { "advance", -7 }, +#define ALICMD 1 + { "ali", 0 }, +#define EXPLCMD 2 + { "burst", 0 }, +#define COMPCMD 3 + { "comp", 0 }, +#define DISTCMD 4 + { "dist", 0 }, +#define EXITCMD 5 + { "exit", 0 }, +#define FOLDCMD 6 + { "folder", 0 }, +#define FORWCMD 7 + { "forw", 0 }, +#define HELPCMD 8 + { "help", 0 }, +#define INCMD 9 + { "inc", 0 }, +#define MARKCMD 10 + { "mark", 0 }, +#define MAILCMD 11 + { "mhmail", 0 }, +#define MHNCMD 12 + { "mhn", 0 }, +#define MSGKCMD 13 + { "msgchk", 0 }, +#define NEXTCMD 14 + { "next", 0 }, +#define PACKCMD 15 + { "packf", 0 }, +#define PICKCMD 16 + { "pick", 0 }, +#define PREVCMD 17 + { "prev", 0 }, +#define QUITCMD 18 + { "quit", 0 }, +#define FILECMD 19 + { "refile", 0 }, +#define REPLCMD 20 + { "repl", 0 }, +#define RMMCMD 21 + { "rmm", 0 }, +#define SCANCMD 22 + { "scan", 0 }, +#define SENDCMD 23 + { "send", 0 }, +#define SHOWCMD 24 + { "show", 0 }, +#define SORTCMD 25 + { "sortm", 0 }, +#define WHATCMD 26 + { "whatnow", 0 }, +#define WHOMCMD 27 + { "whom", 0 }, + { NULL, 0 } +}; + + +static void +msh (int scansw) +{ + int i; + register char *cp, **ap; + char prompt[BUFSIZ], *vec[MAXARGS]; + struct Cmd typein; + register struct Cmd *cmdp; + static int once_only = ADVCMD; + + snprintf (prompt, sizeof(prompt), myprompt, invo_name); + cmdp = &typein; + + for (;;) { + if (yp) { + fclose (yp); + yp = NULL; + } + if (vmh) { + if ((i = getcmds (mshcmds, cmdp, scansw)) == EOF) { + rcdone (); + return; + } + } else { + check_folder (scansw); + if ((i = getargs (prompt, mshcmds, cmdp)) == EOF) { + putchar ('\n'); + return; + } + } + cmd_name = mshcmds[i].sw; + + switch (i) { + case QUITCMD: + quit (); + return; + + case ADVCMD: + if (once_only == ADVCMD) + once_only = i = SHOWCMD; + else + i = mp->curmsg != mp->hghmsg ? NEXTCMD : EXITCMD; + cmd_name = mshcmds[i].sw; + /* and fall... */ + + case EXITCMD: + case EXPLCMD: + case FOLDCMD: + case FORWCMD: /* sigh */ + case MARKCMD: + case NEXTCMD: + case PACKCMD: + case PICKCMD: + case PREVCMD: + case RMMCMD: + case SHOWCMD: + case SCANCMD: + case SORTCMD: + if ((cp = context_find (cmd_name))) { + cp = getcpy (cp); + ap = brkstring (cp, " ", "\n"); + ap = copyip (ap, vec, MAXARGS); + } else { + ap = vec; + } + break; + + default: + cp = NULL; + ap = vec; + break; + } + copyip (cmdp->args + 1, ap, MAXARGS); + + m_init (); + + if (!vmh && init_io (cmdp, vmh) == NOTOK) { + if (cp != NULL) + free (cp); + continue; + } + modified = 0; + redirected = vmh || cmdp->direction != STDIO; + + switch (i) { + case ALICMD: + case COMPCMD: + case INCMD: + case MAILCMD: + case MSGKCMD: + case SENDCMD: + case WHATCMD: + case WHOMCMD: + if (!vmh || ttyN (cmdp) != NOTOK) + forkcmd (vec, cmd_name); + break; + + case DISTCMD: + if (!vmh || ttyN (cmdp) != NOTOK) + distcmd (vec); + break; + + case EXPLCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + explcmd (vec); + break; + + case FILECMD: + if (!vmh + || (filehak (vec) == OK ? ttyN (cmdp) + : winN (cmdp, DISPLAY, 1)) != NOTOK) + filecmd (vec); + break; + + case FOLDCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + foldcmd (vec); + break; + + case FORWCMD: + if (!vmh || ttyN (cmdp) != NOTOK) + forwcmd (vec); + break; + + case HELPCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + helpcmd (vec); + break; + + case EXITCMD: + case MARKCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + markcmd (vec); + break; + + case MHNCMD: + if (!vmh || ttyN (cmdp) != NOTOK) + mhncmd (vec); + break; + + case NEXTCMD: + case PREVCMD: + case SHOWCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + showcmd (vec); + break; + + case PACKCMD: + if (!vmh + || (packhak (vec) == OK ? ttyN (cmdp) + : winN (cmdp, DISPLAY, 1)) != NOTOK) + packcmd (vec); + break; + + case PICKCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + pickcmd (vec); + break; + + case REPLCMD: + if (!vmh || ttyN (cmdp) != NOTOK) + replcmd (vec); + break; + + case RMMCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + rmmcmd (vec); + break; + + case SCANCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + scancmd (vec); + break; + + case SORTCMD: + if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK) + sortcmd (vec); + break; + + default: + padios (NULL, "no dispatch for %s", cmd_name); + } + + if (vmh) { + if (vmhtty != NOTOK) + ttyR (cmdp); + if (vmhpid > OK) + winR (cmdp); + } + else + fin_io (cmdp, vmh); + if (cp != NULL) + free (cp); + if (i == EXITCMD) { + quit (); + return; + } + } +} + + +void +fsetup (char *folder) +{ + register int msgnum; + char *maildir; + struct stat st; + + maildir = m_maildir (folder); + if (chdir (maildir) == NOTOK) + padios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + padios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + padios (NULL, "no messages in %s", folder); + + mode = m_gmprot (); + mtime = stat (mp->foldpath, &st) != NOTOK ? st.st_mtime : 0; + + m_gMsgs (mp->hghmsg); + + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) { + Msgs[msgnum].m_bboard_id = 0; + Msgs[msgnum].m_top = NOTOK; + Msgs[msgnum].m_start = Msgs[msgnum].m_stop = 0L; + Msgs[msgnum].m_scanl = NULL; + } + + m_init (); + + fmsh = getcpy (folder); + + maxfds = OPEN_MAX / 2; + + if ((maxfds -= 2) < 1) + maxfds = 1; +} + + +void +setup (char *file) +{ + int i, msgp; + struct stat st; +#ifdef BPOP + char tmpfil[BUFSIZ]; +#endif + +#ifdef BPOP + if (pmsh) { + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((fp = fopen (tmpfil, "w+")) == NULL) + padios (tmpfil, "unable to create"); + unlink (tmpfil); + } + else +#endif /* BPOP */ + if ((fp = fopen (file, "r")) == NULL) + padios (file, "unable to read"); +#ifdef FIOCLEX + ioctl (fileno (fp), FIOCLEX, NULL); +#endif /* FIOCLEX */ + if (fstat (fileno (fp), &st) != NOTOK) { + mode = (int) (st.st_mode & 0777), mtime = st.st_mtime; + msgp = read_map (file, (long) st.st_size); + } + else { + mode = m_gmprot (), mtime = 0; + msgp = 0; + } + + if ((msgp = read_file (msgp ? Msgs[msgp].m_stop : 0L, msgp + 1)) < 1) + padios (NULL, "no messages in %s", myname ? myname : file); + + if (!(mp = (struct msgs *) calloc ((size_t) 1, sizeof(*mp)))) + padios (NULL, "unable to allocate folder storage"); + + if (!(mp->msgstats = calloc ((size_t) 1, msgp + 3))) + padios (NULL, "unable to allocate message status storage"); + + mp->hghmsg = msgp; + mp->nummsg = msgp; + mp->lowmsg = 1; + mp->curmsg = 0; + mp->foldpath = getcpy (myname ? myname : file); + clear_folder_flags (mp); + +#ifdef BPOP + if (pmsh) + set_readonly (mp); + else { +#endif /* BPOP */ + stat (file, &st); + if (st.st_uid != getuid () || access (file, W_OK) == NOTOK) + set_readonly (mp); +#ifdef BPOP + } +#endif /* BPOP */ + + mp->lowoff = 1; + mp->hghoff = mp->hghmsg + 1; + +#ifdef BPOP + if (pmsh) { +#ifndef NNTP + for (i = mp->lowmsg; i <= mp->hghmsg; i++) { + Msgs[i].m_top = i; + clear_msg_flags (mp, i); + set_exists (mp, i); + set_virtual (mp, i); + } +#else /* NNTP */ + for (i = mp->lowmsg; i <= mp->hghmsg; i++) { + if (Msgs[i].m_top) /* set in read_pop() */ + clear_msg_flags (mp, i); + set_exists (mp, i); + set_virtual (mp, i); + } +#endif /* NNTP */ + } + else +#endif /* BPOP */ + for (i = mp->lowmsg; i <= mp->hghmsg; i++) { + clear_msg_flags (mp, i); + set_exists (mp, i); + } + m_init (); + + mp->msgattrs[0] = getcpy ("unseen"); + mp->msgattrs[1] = NULL; + + m_unknown (fp); /* the MAGIC invocation */ + if (fmsh) { + free (fmsh); + fmsh = NULL; + } +} + + +static int +read_map (char *file, long size) +{ + register int i, msgp; + register struct drop *dp, *mp; + struct drop *rp; + +#ifdef BPOP + if (pmsh) + return read_pop (); +#endif /* BPOP */ + + if ((i = map_read (file, size, &rp, 1)) == 0) + return 0; + + m_gMsgs (i); + + msgp = 1; + for (dp = rp + 1; i-- > 0; msgp++, dp++) { + mp = &Msgs[msgp].m_drop; + mp->d_id = dp->d_id; + mp->d_size = dp->d_size; + mp->d_start = dp->d_start; + mp->d_stop = dp->d_stop; + Msgs[msgp].m_scanl = NULL; + } + free ((char *) rp); + + return (msgp - 1); +} + + +static int +read_file (long pos, int msgp) +{ + register int i; + register struct drop *dp, *mp; + struct drop *rp; + +#ifdef BPOP + if (pmsh) + return (msgp - 1); +#endif /* BPOP */ + + if ((i = mbx_read (fp, pos, &rp, 1)) <= 0) + return (msgp - 1); + + m_gMsgs ((msgp - 1) + i); + + for (dp = rp; i-- > 0; msgp++, dp++) { + mp = &Msgs[msgp].m_drop; + mp->d_id = 0; + mp->d_size = dp->d_size; + mp->d_start = dp->d_start; + mp->d_stop = dp->d_stop; + Msgs[msgp].m_scanl = NULL; + } + free ((char *) rp); + + return (msgp - 1); +} + + +#ifdef BPOP +#ifdef NNTP +static int pop_base = 0; + +static int +pop_statmsg (char *s) +{ + register int i, n; + + n = (i = atoi (s)) - pop_base; /* s="nnn header-line..." */ + Msgs[n].m_top = Msgs[n].m_bboard_id = i; +} + +#endif /* NNTP */ + +static int +read_pop (void) +{ + int nmsgs, nbytes; + + if (pop_stat (&nmsgs, &nbytes) == NOTOK) + padios (NULL, "%s", response); + + m_gMsgs (nmsgs); + +#ifdef NNTP /* this makes read_pop() do some real work... */ + pop_base = nbytes - 1; /* nmsgs=last-first+1, nbytes=first */ + pop_exists (pop_statmsg); +#endif /* NNTP */ + return nmsgs; +} + + +static int +pop_action (char *s) +{ + fprintf (yp, "%s\n", s); +} +#endif /* BPOP */ + + +static void +m_gMsgs (int n) +{ + int nmsgs; + + if (Msgs == NULL) { + nMsgs = n + MAXFOLDER / 2; + Msgs = (struct Msg *) calloc ((size_t) (nMsgs + 2), sizeof *Msgs); + if (Msgs == NULL) + padios (NULL, "unable to allocate Msgs structure"); + return; + } + + if (nMsgs >= n) + return; + + nmsgs = nMsgs + n + MAXFOLDER / 2; + Msgs = (struct Msg *) realloc ((char *) Msgs, (size_t) (nmsgs + 2) * sizeof *Msgs); + if (Msgs == NULL) + padios (NULL, "unable to reallocate Msgs structure"); + memset((char *) (Msgs + nMsgs + 2), 0, (size_t) ((nmsgs - nMsgs) * sizeof *Msgs)); + + nMsgs = nmsgs; +} + + +FILE * +msh_ready (int msgnum, int full) +{ + register int msgp; + int fd; + char *cp; +#ifdef BPOP + char tmpfil[BUFSIZ]; + long pos1, pos2; +#endif + + if (yp) { + fclose (yp); + yp = NULL; + } + + if (fmsh) { + if ((fd = Msgs[msgnum].m_top) == NOTOK) { + if (numfds >= maxfds) + for (msgp = mp->lowmsg; msgp <= mp->hghmsg; msgp++) + if (Msgs[msgp].m_top != NOTOK) { + close (Msgs[msgp].m_top); + Msgs[msgp].m_top = NOTOK; + numfds--; + break; + } + + if ((fd = open (cp = m_name (msgnum), O_RDONLY)) == NOTOK) + padios (cp, "unable to open message"); + Msgs[msgnum].m_top = fd; + numfds++; + } + + if ((fd = dup (fd)) == NOTOK) + padios ("cached message", "unable to dup"); + if ((yp = fdopen (fd, "r")) == NULL) + padios (NULL, "unable to fdopen cached message"); + fseek (yp, 0L, SEEK_SET); + return yp; + } + +#ifdef BPOP + if (pmsh && is_virtual (mp, msgnum)) { + if (Msgs[msgnum].m_top == 0) + padios (NULL, "msh_ready (%d, %d) botch", msgnum, full); + if (!full) { + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((yp = fopen (tmpfil, "w+")) == NULL) + padios (tmpfil, "unable to create"); + unlink (tmpfil); + + if (pop_top (Msgs[msgnum].m_top, 4, pop_action) == NOTOK) + padios (NULL, "%s", response); + + m_eomsbr ((int (*)()) 0); /* XXX */ + msg_style = MS_DEFAULT; /* .. */ + fseek (yp, 0L, SEEK_SET); + return yp; + } + + fseek (fp, 0L, SEEK_END); + fwrite (mmdlm1, 1, strlen (mmdlm1), fp); + if (fflush (fp)) + padios ("temporary file", "write error on"); + fseek (fp, 0L, SEEK_END); + pos1 = ftell (fp); + + yp = fp; + if (pop_retr (Msgs[msgnum].m_top, pop_action) == NOTOK) + padios (NULL, "%s", response); + yp = NULL; + + fseek (fp, 0L, SEEK_END); + pos2 = ftell (fp); + fwrite (mmdlm2, 1, strlen (mmdlm2), fp); + if (fflush (fp)) + padios ("temporary file", "write error on"); + + Msgs[msgnum].m_start = pos1; + Msgs[msgnum].m_stop = pos2; + + unset_virtual (mp, msgnum); + } +#endif /* BPOP */ + + m_eomsbr ((int (*)()) 0); /* XXX */ + fseek (fp, Msgs[msgnum].m_start, SEEK_SET); + return fp; +} + + +static int +check_folder (int scansw) +{ + int seqnum, i, low, hgh, msgp; + struct stat st; + +#ifdef BPOP + if (pmsh) + return 0; +#endif /* BPOP */ + + if (fmsh) { + if (stat (mp->foldpath, &st) == NOTOK) + padios (mp->foldpath, "unable to stat"); + if (mtime == st.st_mtime) + return 0; + mtime = st.st_mtime; + + low = mp->hghmsg + 1; + folder_free (mp); /* free folder/message structure */ + + if (!(mp = folder_read (fmsh))) + padios (NULL, "unable to re-read folder %s", fmsh); + + hgh = mp->hghmsg; + + for (msgp = mp->lowmsg; msgp <= mp->hghmsg; msgp++) { + if (Msgs[msgp].m_top != NOTOK) { + close (Msgs[msgp].m_top); + Msgs[msgp].m_top = NOTOK; + numfds--; + } + if (Msgs[msgp].m_scanl) { + free (Msgs[msgp].m_scanl); + Msgs[msgp].m_scanl = NULL; + } + } + + m_init (); + + if (modified || low > hgh) + return 1; + goto check_vmh; + } + if (fstat (fileno (fp), &st) == NOTOK) + padios (mp->foldpath, "unable to fstat"); + if (mtime == st.st_mtime) + return 0; + mode = (int) (st.st_mode & 0777); + mtime = st.st_mtime; + + if ((msgp = read_file (Msgs[mp->hghmsg].m_stop, mp->hghmsg + 1)) < 1) + padios (NULL, "no messages in %s", mp->foldpath); /* XXX */ + if (msgp >= MAXFOLDER) + padios (NULL, "more than %d messages in %s", MAXFOLDER, + mp->foldpath); + if (msgp <= mp->hghmsg) + return 0; /* XXX */ + + if (!(mp = folder_realloc (mp, mp->lowoff, msgp))) + padios (NULL, "unable to allocate folder storage"); + + low = mp->hghmsg + 1, hgh = msgp; + seqnum = scansw ? seq_getnum (mp, "unseen") : -1; + for (i = mp->hghmsg + 1; i <= msgp; i++) { + set_exists(mp, i); + if (seqnum != -1) + add_sequence(mp, seqnum, i); + mp->nummsg++; + } + mp->hghmsg = msgp; + m_init (); + +check_vmh: ; + if (vmh) + return 1; + + advise (NULL, "new messages have arrived!\007"); + if (scansw) + scanrange (low, hgh); + + return 1; +} + + +static void +scanrange (int low, int hgh) +{ + char buffer[BUFSIZ]; + + snprintf (buffer, sizeof(buffer), "%d-%d", low, hgh); + scanstring (buffer); +} + + +static void +scanstring (char *arg) +{ + char *cp, **ap, *vec[MAXARGS]; + + /* + * This should be replace with a call to getarguments() + */ + if ((cp = context_find (cmd_name = "scan"))) { + cp = getcpy (cp); + ap = brkstring (cp, " ", "\n"); + ap = copyip (ap, vec, MAXARGS); + } else { + ap = vec; + } + *ap++ = arg; + *ap = NULL; + m_init (); + scancmd (vec); + if (cp != NULL) + free (cp); +} + + +void +readids (int id) +{ + register int cur, seqnum, i, msgnum; + + if (mp->curmsg == 0) + seq_setcur (mp, mp->lowmsg); + if (id <= 0 || (seqnum = seq_getnum (mp, "unseen")) == -1) + return; + + for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--) + add_sequence(mp, seqnum, msgnum); + + if (id != 1) { + cur = mp->curmsg; + + for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--) + if (does_exist(mp, msgnum)) /* FIX */ + if ((i = readid (msgnum)) > 0 && i < id) { + cur = msgnum + 1; + clear_sequence(mp, seqnum, msgnum); + break; + } + for (i = mp->lowmsg; i < msgnum; i++) + clear_sequence(mp, seqnum, i); + + if (cur > mp->hghmsg) + cur = mp->hghmsg; + + seq_setcur (mp, cur); + } + + if ((gap = 1 < id && id < (i = readid (mp->lowmsg)) ? id : 0) && !vmh) + advise (NULL, "gap in ID:s, last seen %d, lowest present %d\n", + id - 1, i); +} + + +int +readid (int msgnum) +{ + int i, state; + char *bp, buf[BUFSIZ], name[NAMESZ]; + register FILE *zp; +#ifdef BPOP + int arg1, arg2, arg3; +#endif + + if (Msgs[msgnum].m_bboard_id) + return Msgs[msgnum].m_bboard_id; +#ifdef BPOP + if (pmsh) { + if (Msgs[msgnum].m_top == 0) + padios (NULL, "readid (%d) botch", msgnum); + if (pop_list (Msgs[msgnum].m_top, (int *) 0, &arg1, &arg2, &arg3) == OK + && arg3 > 0) + return (Msgs[msgnum].m_bboard_id = arg3); + } +#endif /* BPOP */ + + zp = msh_ready (msgnum, 0); + for (state = FLD;;) + switch (state = m_getfld (state, name, buf, sizeof(buf), zp)) { + case FLD: + case FLDEOF: + case FLDPLUS: + if (!strcasecmp (name, BBoard_ID)) { + bp = getcpy (buf); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), zp); + bp = add (buf, bp); + } + i = atoi (bp); + free (bp); + if (i > 0) + return (Msgs[msgnum].m_bboard_id = i); + else + continue; + } + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), zp); + if (state != FLDEOF) + continue; + + default: + return 0; + } +} + + +void +display_info (int scansw) +{ + int seqnum, sd; + + interactive = isatty (fileno (stdout)); + if (sp == NULL) { + if ((sd = dup (fileno (stdout))) == NOTOK) + padios ("standard output", "unable to dup"); +#ifndef BSD42 /* XXX */ +#ifdef FIOCLEX + ioctl (sd, FIOCLEX, NULL); +#endif /* FIOCLEX */ +#endif /* not BSD42 */ + if ((sp = fdopen (sd, "w")) == NULL) + padios ("standard output", "unable to fdopen"); + } + + m_putenv ("mhfolder", mp->foldpath); + if (vmh) + return; + + if (myname) { + printf ("Reading "); + if (SOprintf ("%s", myname)) + printf ("%s", myname); + printf (", currently at message %d of %d\n", + mp->curmsg, mp->hghmsg); + } + else { + printf ("Reading "); + if (fmsh) + printf ("+%s", fmsh); + else + printf ("%s", mp->foldpath); + printf (", currently at message %d of %d\n", + mp->curmsg, mp->hghmsg); + } + + if (((seqnum = seq_getnum (mp, "unseen")) != -1) + && scansw + && in_sequence(mp, seqnum, mp->hghmsg)) + scanstring ("unseen"); +} + + +static void +write_ids (void) +{ + int i = 0, seqnum, msgnum; + char buffer[80]; + + if (pfd <= 1) + return; + + if ((seqnum = seq_getnum (mp, "unseen")) != -1) + for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--) + if (!in_sequence(mp, seqnum, msgnum)) { + if (Msgs[msgnum].m_bboard_id == 0) + readid (msgnum); + if ((i = Msgs[msgnum].m_bboard_id) > 0) + break; + } + + snprintf (buffer, sizeof(buffer), "%d %d\n", i, Msgs[mp->hghmsg].m_bboard_id); + write (pfd, buffer, sizeof(buffer)); + close (pfd); + pfd = NOTOK; +} + + +static void +quit (void) +{ + int i, md, msgnum; + char *cp, tmpfil[BUFSIZ]; + char map1[BUFSIZ], map2[BUFSIZ]; + struct stat st; + FILE *dp; + + if (!(mp->msgflags & MODIFIED) || is_readonly(mp) || fmsh) { + if (vmh) + rc2peer (RC_FIN, 0, NULL); + return; + } + + if (vmh) + ttyNaux (NULLCMD, "FAST"); + cp = NULL; + if ((dp = lkfopen (mp->foldpath, "r")) == NULL) { + advise (mp->foldpath, "unable to lock"); + if (vmh) { + ttyR (NULLCMD); + pFIN (); + } + return; + } + if (fstat (fileno (dp), &st) == NOTOK) { + advise (mp->foldpath, "unable to stat"); + goto release; + } + if (mtime != st.st_mtime) { + advise (NULL, "new messages have arrived, no update"); + goto release; + } + mode = (int) (st.st_mode & 0777); + + if (mp->nummsg == 0) { + cp = concat ("Zero file \"", mp->foldpath, "\"? ", NULL); + if (getanswer (cp)) { + if ((i = creat (mp->foldpath, mode)) != NOTOK) + close (i); + else + advise (mp->foldpath, "error zero'ing"); + unlink (map_name (mp->foldpath));/* XXX */ + } + goto release; + } + + cp = concat ("Update file \"", mp->foldpath, "\"? ", NULL); + if (!getanswer (cp)) + goto release; + strncpy (tmpfil, m_backup (mp->foldpath), sizeof(tmpfil)); + if ((md = mbx_open (tmpfil, mbx_style, st.st_uid, st.st_gid, mode)) == NOTOK) { + advise (tmpfil, "unable to open"); + goto release; + } + + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) + if (does_exist(mp, msgnum) && pack (tmpfil, md, msgnum) == NOTOK) { + mbx_close (tmpfil, md); + unlink (tmpfil); + unlink (map_name (tmpfil)); + goto release; + } + mbx_close (tmpfil, md); + + if (rename (tmpfil, mp->foldpath) == NOTOK) + admonish (mp->foldpath, "unable to rename %s to", tmpfil); + else { + strncpy (map1, map_name (tmpfil), sizeof(map1)); + strncpy (map2, map_name (mp->foldpath), sizeof(map2)); + + if (rename (map1, map2) == NOTOK) { + admonish (map2, "unable to rename %s to", map1); + unlink (map1); + unlink (map2); + } + } + +release: ; + if (cp) + free (cp); + lkfclose (dp, mp->foldpath); + if (vmh) { + ttyR (NULLCMD); + pFIN (); + } +} + + +static int +getargs (char *prompt, struct swit *sw, struct Cmd *cmdp) +{ + int i; + char *cp; + static char buffer[BUFSIZ]; + + told_to_quit = 0; + for (;;) { + interrupted = 0; +#ifdef BSD42 + switch (setjmp (sigenv)) { + case OK: + should_intr = 1; + break; + + default: + should_intr = 0; + if (interrupted && !told_to_quit) { + putchar ('\n'); + continue; + } + if (ppid > 0) +#ifdef SIGEMT + kill (ppid, SIGEMT); +#else + kill (ppid, SIGTERM); +#endif + return EOF; + } +#endif /* BSD42 */ + if (interactive) { + printf ("%s", prompt); + fflush (stdout); + } + for (cp = buffer; (i = getchar ()) != '\n';) { +#ifndef BSD42 + if (interrupted && !told_to_quit) { + buffer[0] = '\0'; + putchar ('\n'); + break; + } + if (told_to_quit || i == EOF) { + if (ppid > 0) +#ifdef SIGEMT + kill (ppid, SIGEMT); +#else + kill (ppid, SIGTERM); +#endif + return EOF; + } +#else /* BSD42 */ + if (i == EOF) + longjmp (sigenv, DONE); +#endif /* BSD42 */ + if (cp < &buffer[sizeof buffer - 2]) + *cp++ = i; + } + *cp = 0; + + if (buffer[0] == 0) + continue; + if (buffer[0] == '?') { + printf ("commands:\n"); + print_sw (ALL, sw, ""); + printf ("type CTRL-D or use ``quit'' to leave %s\n", + invo_name); + continue; + } + + if (parse (buffer, cmdp) == NOTOK) + continue; + + switch (i = smatch (cmdp->args[0], sw)) { + case AMBIGSW: + ambigsw (cmdp->args[0], sw); + continue; + case UNKWNSW: + printf ("say what: ``%s'' -- type ? (or help) for help\n", + cmdp->args[0]); + continue; + default: +#ifdef BSD42 + should_intr = 0; +#endif /* BSD42 */ + return i; + } + } +} + + +static int +getcmds (struct swit *sw, struct Cmd *cmdp, int scansw) +{ + int i; + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + for (;;) + switch (peer2rc (rc)) { + case RC_QRY: + pQRY (rc->rc_data, scansw); + break; + + case RC_CMD: + if ((i = pCMD (rc->rc_data, sw, cmdp)) != NOTOK) + return i; + break; + + case RC_FIN: + if (ppid > 0) +#ifdef SIGEMT + kill (ppid, SIGEMT); +#else + kill (ppid, SIGTERM); +#endif + return EOF; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pLOOP protocol screw-up"); + done (1); + } +} + + +static int +parse (char *buffer, struct Cmd *cmdp) +{ + int argp = 0; + char c, *cp, *pp; + + cmdp->line[0] = 0; + pp = cmdp->args[argp++] = cmdp->line; + cmdp->redirect = NULL; + cmdp->direction = STDIO; + cmdp->stream = NULL; + + for (cp = buffer; c = *cp; cp++) { + if (!isspace (c)) + break; + } + if (c == '\0') { + if (vmh) + fmt2peer (RC_EOF, "null command"); + return NOTOK; + } + + while ((c = *cp++)) { + if (isspace (c)) { + while (isspace (c)) + c = *cp++; + if (c == 0) + break; + *pp++ = 0; + cmdp->args[argp++] = pp; + *pp = 0; + } + + switch (c) { + case '"': + for (;;) { + switch (c = *cp++) { + case 0: + padvise (NULL, "unmatched \""); + return NOTOK; + case '"': + break; + case QUOTE: + if ((c = *cp++) == 0) + goto no_quoting; + default: + *pp++ = c; + continue; + } + break; + } + continue; + + case QUOTE: + if ((c = *cp++) == 0) { + no_quoting: ; + padvise (NULL, "the newline character can not be quoted"); + return NOTOK; + } + + default: ; + *pp++ = c; + continue; + + case '>': + case '|': + if (pp == cmdp->line) { + padvise (NULL, "invalid null command"); + return NOTOK; + } + if (*cmdp->args[argp - 1] == 0) + argp--; + cmdp->direction = c == '>' ? CRTIO : PIPIO; + if (cmdp->direction == CRTIO && (c = *cp) == '>') { + cmdp->direction = APPIO; + cp++; + } + cmdp->redirect = pp + 1;/* sigh */ + for (; c = *cp; cp++) + if (!isspace (c)) + break; + if (c == 0) { + padvise (NULL, cmdp->direction != PIPIO + ? "missing name for redirect" + : "invalid null command"); + return NOTOK; + } + strcpy (cmdp->redirect, cp); + if (cmdp->direction != PIPIO) { + for (; *cp; cp++) + if (isspace (*cp)) { + padvise (NULL, "bad name for redirect"); + return NOTOK; + } + if (expand (cmdp->redirect) == NOTOK) + return NOTOK; + } + break; + } + break; + } + + *pp++ = 0; + cmdp->args[argp] = NULL; + + return OK; +} + + +int +expand (char *redirect) +{ + char *cp, *pp; + char path[BUFSIZ]; + struct passwd *pw; + + if (*redirect != '~') + return OK; + + if ((cp = strchr(pp = redirect + 1, '/'))) + *cp++ = 0; + if (*pp == 0) + pp = mypath; + else + if ((pw = getpwnam (pp))) + pp = pw->pw_dir; + else { + padvise (NULL, "unknown user: %s", pp); + return NOTOK; + } + + snprintf (path, sizeof(path), "%s/%s", pp, cp ? cp : ""); + strcpy (redirect, path); + return OK; +} + + +static int +init_io (struct Cmd *cmdp, int vio) +{ + int io, result; + + io = vmh; + + vmh = vio; + result = initaux_io (cmdp); + vmh = io; + + return result; +} + + +static int +initaux_io (struct Cmd *cmdp) +{ + char *mode; + + switch (cmdp->direction) { + case STDIO: + return OK; + + case CRTIO: + case APPIO: + mode = cmdp->direction == CRTIO ? "write" : "append"; + if ((cmdp->stream = fopen (cmdp->redirect, mode)) == NULL) { + padvise (cmdp->redirect, "unable to %s ", mode); + cmdp->direction = STDIO; + return NOTOK; + } + break; + + case PIPIO: + if ((cmdp->stream = popen (cmdp->redirect, "w")) == NULL) { + padvise (cmdp->redirect, "unable to pipe"); + cmdp->direction = STDIO; + return NOTOK; + } + SIGNAL (SIGPIPE, pipeser); + broken_pipe = 0; + break; + + default: + padios (NULL, "unknown redirection for command"); + } + + fflush (stdout); + if (dup2 (fileno (cmdp->stream), fileno (stdout)) == NOTOK) + padios ("standard output", "unable to dup2"); + clearerr (stdout); + + return OK; +} + + +static void +fin_io (struct Cmd *cmdp, int vio) +{ + int io; + + io = vmh; + vmh = vio; + finaux_io (cmdp); + vmh = io; +} + + +static void +finaux_io (struct Cmd *cmdp) +{ + switch (cmdp->direction) { + case STDIO: + return; + + case CRTIO: + case APPIO: + fflush (stdout); + close (fileno (stdout)); + if (ferror (stdout)) + padvise (NULL, "problems writing %s", cmdp->redirect); + fclose (cmdp->stream); + break; + + case PIPIO: + fflush (stdout); + close (fileno (stdout)); + pclose (cmdp->stream); + SIGNAL (SIGPIPE, SIG_DFL); + break; + + default: + padios (NULL, "unknown redirection for command"); + } + + if (dup2 (fileno (sp), fileno (stdout)) == NOTOK) + padios ("standard output", "unable to dup2"); + clearerr (stdout); + + cmdp->direction = STDIO; +} + + +static void +m_init (void) +{ + int msgnum; + + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) + unset_selected (mp, msgnum); + mp->lowsel = mp->hghsel = mp->numsel = 0; +} + + +void +m_reset (void) +{ + write_ids (); + folder_free (mp); /* free folder/message structure */ + myname = NULL; +#ifdef BPOP + if (pmsh) { + pop_done (); + pmsh = 0; + } +#endif /* BPOP */ +} + + +void +seq_setcur (struct msgs *mp, int msgnum) +{ + if (mp->curmsg == msgnum) + return; + + if (mp->curmsg && Msgs[mp->curmsg].m_scanl) { + free (Msgs[mp->curmsg].m_scanl); + Msgs[mp->curmsg].m_scanl = NULL; + } + if (Msgs[msgnum].m_scanl) { + free (Msgs[msgnum].m_scanl); + Msgs[msgnum].m_scanl = NULL; + } + + mp->curmsg = msgnum; +} + + + +static RETSIGTYPE +intrser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGINT, intrser); +#endif + + discard (stdout); + interrupted++; + +#ifdef BSD42 + if (should_intr) + longjmp (sigenv, NOTOK); +#endif +} + + +static RETSIGTYPE +pipeser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGPIPE, pipeser); +#endif + + if (broken_pipe++ == 0) + fprintf (stderr, "broken pipe\n"); + told_to_quit++; + interrupted++; + +#ifdef BSD42 + if (should_intr) + longjmp (sigenv, NOTOK); +#endif +} + + +static RETSIGTYPE +quitser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGQUIT, quitser); +#endif + + told_to_quit++; + interrupted++; + +#ifdef BSD42 + if (should_intr) + longjmp (sigenv, NOTOK); +#endif +} + + +static RETSIGTYPE +alrmser (int i) +{ + longjmp (peerenv, DONE); +} + + +static int +pINI (void) +{ + int i, vrsn; + char *bp; + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + switch (peer2rc (rc)) { + case RC_INI: + bp = rc->rc_data; + while (isspace (*bp)) + bp++; + if (sscanf (bp, "%d", &vrsn) != 1) { + bad_init: ; + fmt2peer (RC_ERR, "bad init \"%s\"", rc->rc_data); + done (1); + } + if (vrsn != RC_VRSN) { + fmt2peer (RC_ERR, "version %d unsupported", vrsn); + done (1); + } + + while (*bp && !isspace (*bp)) + bp++; + while (isspace (*bp)) + bp++; + if (sscanf (bp, "%d", &numwins) != 1 || numwins <= 0) + goto bad_init; + if (numwins > NWIN) + numwins = NWIN; + + for (i = 1; i <= numwins; i++) { + while (*bp && !isspace (*bp)) + bp++; + while (isspace (*bp)) + bp++; + if (sscanf (bp, "%d", &windows[i]) != 1 || windows[i] <= 0) + goto bad_init; + } + rc2peer (RC_ACK, 0, NULL); + return OK; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pINI protocol screw-up"); + done (1); /* NOTREACHED */ + } +} + + +static int +pQRY (char *str, int scansw) +{ + if (pQRY1 (scansw) == NOTOK || pQRY2 () == NOTOK) + return NOTOK; + + rc2peer (RC_EOF, 0, NULL); + return OK; +} + + +static int +pQRY1 (int scansw) +{ + int oldhgh; + static int lastlow = 0, + lastcur = 0, + lasthgh = 0, + lastnum = 0; + + oldhgh = mp->hghmsg; + if (check_folder (scansw) && oldhgh < mp->hghmsg) { + switch (winX (STATUS)) { + case NOTOK: + return NOTOK; + + case OK: + printf ("new messages have arrived!"); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + lastlow = lastcur = lasthgh = lastnum = 0; + break; + } + + switch (winX (DISPLAY)) { + case NOTOK: + return NOTOK; + + case OK: + scanrange (oldhgh + 1, mp->hghmsg); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + break; + } + return OK; + } + + if (gap) + switch (winX (STATUS)) { + case NOTOK: + return NOTOK; + + case OK: + printf ("%s: gap in ID:s, last seen %d, lowest present %d\n", + myname ? myname : fmsh ? fmsh : mp->foldpath, gap - 1, + readid (mp->lowmsg)); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + gap = 0; + return OK; + } + + if (mp->lowmsg != lastlow + || mp->curmsg != lastcur + || mp->hghmsg != lasthgh + || mp->nummsg != lastnum) + switch (winX (STATUS)) { + case NOTOK: + return NOTOK; + + case OK: + foldcmd (NULL); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + lastlow = mp->lowmsg; + lastcur = mp->curmsg; + lasthgh = mp->hghmsg; + lastnum = mp->nummsg; + return OK; + } + + return OK; +} + + +static int +pQRY2 (void) +{ + int i, j, k, msgnum, n; + static int cur = 0, + num = 0, + lo = 0, + hi = 0; + + if (mp->nummsg == 0 && mp->nummsg != num) + switch (winX (SCAN)) { + case NOTOK: + return NOTOK; + + case OK: + printf ("empty!"); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + num = mp->nummsg; + return OK; + } + num = mp->nummsg; + + i = 0; + j = (k = windows[SCAN]) / 2; + for (msgnum = mp->curmsg; msgnum <= mp->hghmsg; msgnum++) + if (does_exist (mp, msgnum)) + i++; + if (i-- > 0) + if (topcur) + k = i >= k ? 1 : k - i; + else + k -= i > j ? j : i; + + i = j = 0; + n = 1; + for (msgnum = mp->curmsg; msgnum >= mp->lowmsg; msgnum--) + if (does_exist (mp, msgnum)) { + i = msgnum; + if (j == 0) + j = msgnum; + if (n++ >= k) + break; + } + for (msgnum = mp->curmsg + 1; msgnum <= mp->hghmsg; msgnum++) + if (does_exist (mp, msgnum)) { + if (i == 0) + i = msgnum; + j = msgnum; + if (n++ >= windows[SCAN]) + break; + } + if (!topcur + && lo > 0 + && hi > 0 + && does_exist (mp, lo) + && does_exist (mp, hi) + && (lo < mp->curmsg + || (lo == mp->curmsg && lo == mp->lowmsg)) + && (mp->curmsg < hi + || (hi == mp->curmsg && hi == mp->hghmsg)) + && hi - lo == j - i) + i = lo, j = hi; + + if (mp->curmsg != cur || modified) + switch (winN (NULLCMD, SCAN, 0)) { + case NOTOK: + return NOTOK; + + case OK: + return OK; + + default: + scanrange (lo = i, hi = j); + cur = mp->curmsg; + winR (NULLCMD); + return OK; + } + + return OK; +} + + +static int +pCMD (char *str, struct swit *sw, struct Cmd *cmdp) +{ + int i; + + if (*str == '?') + switch (winX (DISPLAY)) { + case NOTOK: + return NOTOK; + + case OK: + printf ("commands:\n"); + print_sw (ALL, sw, ""); + printf ("type ``quit'' to leave %s\n", invo_name); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + rc2peer (RC_EOF, 0, NULL); + return NOTOK; + } + + if (parse (str, cmdp) == NOTOK) + return NOTOK; + + switch (i = smatch (cmdp->args[0], sw)) { + case AMBIGSW: + switch (winX (DISPLAY)) { + case NOTOK: + return NOTOK; + + case OK: + ambigsw (cmdp->args[0], sw); + fflush (stdout); + fflush (stderr); + _exit (0); /* NOTREACHED */ + + default: + rc2peer (RC_EOF, 0, NULL); + return NOTOK; + } + + case UNKWNSW: + fmt2peer (RC_ERR, + "say what: ``%s'' -- type ? (or help) for help", + cmdp->args[0]); + return NOTOK; + + default: + return i; + } +} + + +static int +pFIN (void) +{ + int status; + + switch (setjmp (peerenv)) { + case OK: + SIGNAL (SIGALRM, alrmser); + alarm (ALARM); + + status = peerwait (); + + alarm (0); + return status; + + default: + return NOTOK; + } +} + + +static int +peerwait (void) +{ + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + switch (peer2rc (rc)) { + case RC_QRY: + case RC_CMD: + rc2peer (RC_FIN, 0, NULL); + return OK; + + case RC_XXX: + advise (NULL, "%s", rc->rc_data); + return NOTOK; + + default: + fmt2peer (RC_FIN, "pLOOP protocol screw-up"); + return NOTOK; + } +} + + +static int +ttyNaux (struct Cmd *cmdp, char *s) +{ + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + if (cmdp && init_io (cmdp, vmh) == NOTOK) + return NOTOK; + + /* XXX: fseek() too tricky for our own good */ + if (!fmsh) + fseek (fp, 0L, SEEK_SET); + + vmhtty = NOTOK; + switch (rc2rc (RC_TTY, s ? strlen (s) : 0, s, rc)) { + case RC_ACK: + vmhtty = OK; /* fall */ + case RC_ERR: + break; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data);/* NOTREACHED */ + + default: + fmt2peer (RC_ERR, "pTTY protocol screw-up"); + done (1); /* NOTREACHED */ + } + +#ifdef SIGTSTP + SIGNAL (SIGTSTP, tstat); +#endif + return vmhtty; +} + + +static int +ttyR (struct Cmd *cmdp) +{ + struct record rcs, *rc; + + rc = &rcs; + +#ifdef SIGTSTP + SIGNAL (SIGTSTP, SIG_IGN); +#endif + + if (vmhtty != OK) + return NOTOK; + + initrc (rc); + + if (cmdp) + fin_io (cmdp, 0); + + vmhtty = NOTOK; + switch (rc2rc (RC_EOF, 0, NULL, rc)) { + case RC_ACK: + rc2peer (RC_EOF, 0, NULL); + return OK; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data);/* NOTREACHED */ + + default: + fmt2peer (RC_ERR, "pTTY protocol screw-up"); + done (1); /* NOTREACHED */ + } +} + + +static int +winN (struct Cmd *cmdp, int n, int eof) +{ + int i, pd[2]; + char buffer[BUFSIZ]; + struct record rcs, *rc; + + rc = &rcs; + if (vmhpid == NOTOK) + return OK; + + initrc (rc); + + /* XXX: fseek() too tricky for our own good */ + if (!fmsh) + fseek (fp, 0L, SEEK_SET); + + vmhpid = OK; + + snprintf (buffer, sizeof(buffer), "%d", n); + switch (str2rc (RC_WIN, buffer, rc)) { + case RC_ACK: + break; + + case RC_ERR: + return NOTOK; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + + if (pipe (pd) == NOTOK) { + err2peer (RC_ERR, "pipe", "unable to"); + return NOTOK; + } + + switch (vmhpid = fork()) { + case NOTOK: + err2peer (RC_ERR, "fork", "unable to"); + close (pd[0]); + close (pd[1]); + return NOTOK; + + case OK: + close (pd[1]); + SIGNAL (SIGPIPE, SIG_IGN); + while ((i = read (pd[0], buffer, sizeof buffer)) > 0) + switch (rc2rc (RC_DATA, i, buffer, rc)) { + case RC_ACK: + break; + + case RC_ERR: + _exit (1); + + case RC_XXX: + advise (NULL, "%s", rc->rc_data); + _exit (2); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + _exit (2); + } + if (i == OK) + switch (rc2rc (RC_EOF, 0, NULL, rc)) { + case RC_ACK: + if (eof) + rc2peer (RC_EOF, 0, NULL); + i = 0; + break; + + case RC_XXX: + advise (NULL, "%s", rc->rc_data); + i = 2; + break; + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + i = 2; + break; + } + if (i == NOTOK) + err2peer (RC_ERR, "pipe", "error reading from"); + close (pd[0]); + _exit (i != NOTOK ? i : 1); + + default: + if ((vmhfd0 = dup (fileno (stdin))) == NOTOK) + padios ("standard input", "unable to dup"); + if ((vmhfd1 = dup (fileno (stdout))) == NOTOK) + padios ("standard output", "unable to dup"); + if ((vmhfd2 = dup (fileno (stderr))) == NOTOK) + padios ("diagnostic output", "unable to dup"); + + close (0); + if ((i = open ("/dev/null", O_RDONLY)) != NOTOK && i != fileno (stdin)) { + dup2 (i, fileno (stdin)); + close (i); + } + + fflush (stdout); + if (dup2 (pd[1], fileno (stdout)) == NOTOK) + padios ("standard output", "unable to dup2"); + clearerr (stdout); + + fflush (stderr); + if (dup2 (pd[1], fileno (stderr)) == NOTOK) + padios ("diagnostic output", "unable to dup2"); + clearerr (stderr); + + if (cmdp && init_io (cmdp, 0) == NOTOK) + return NOTOK; + pstat = SIGNAL (SIGPIPE, pipeser); + broken_pipe = 1; + + close (pd[0]); + close (pd[1]); + + return vmhpid; + } +} + + +static int +winR (struct Cmd *cmdp) +{ + int status; + + if (vmhpid <= OK) + return NOTOK; + + if (cmdp) + fin_io (cmdp, 0); + + if (dup2 (vmhfd0, fileno (stdin)) == NOTOK) + padios ("standard input", "unable to dup2"); + clearerr (stdin); + close (vmhfd0); + + fflush (stdout); + if (dup2 (vmhfd1, fileno (stdout)) == NOTOK) + padios ("standard output", "unable to dup2"); + clearerr (stdout); + close (vmhfd1); + + fflush (stderr); + if (dup2 (vmhfd2, fileno (stderr)) == NOTOK) + padios ("diagnostic output", "unable to dup2"); + clearerr (stderr); + close (vmhfd2); + + SIGNAL (SIGPIPE, pstat); + + if ((status = pidwait (vmhpid, OK)) == 2) + done (1); + + vmhpid = OK; + return (status == 0 ? OK : NOTOK); +} + + +static int +winX (int n) +{ + int i, pid, pd[2]; + char buffer[BUFSIZ]; + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + /* XXX: fseek() too tricky for our own good */ + if (!fmsh) + fseek (fp, 0L, SEEK_SET); + + snprintf (buffer, sizeof(buffer), "%d", n); + switch (str2rc (RC_WIN, buffer, rc)) { + case RC_ACK: + break; + + case RC_ERR: + return NOTOK; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + + if (pipe (pd) == NOTOK) { + err2peer (RC_ERR, "pipe", "unable to"); + return NOTOK; + } + + switch (pid = fork ()) { + case NOTOK: + err2peer (RC_ERR, "fork", "unable to"); + close (pd[0]); + close (pd[1]); + return NOTOK; + + case OK: + close (fileno (stdin)); + if ((i = open ("/dev/null", O_RDONLY)) != NOTOK && i != fileno (stdin)) { + dup2 (i, fileno (stdin)); + close (i); + } + dup2 (pd[1], fileno (stdout)); + dup2 (pd[1], fileno (stderr)); + close (pd[0]); + close (pd[1]); + vmhpid = NOTOK; + return OK; + + default: + close (pd[1]); + while ((i = read (pd[0], buffer, sizeof buffer)) > 0) + switch (rc2rc (RC_DATA, i, buffer, rc)) { + case RC_ACK: + break; + + case RC_ERR: + close (pd[0]); + pidwait (pid, OK); + return NOTOK; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + if (i == OK) + switch (rc2rc (RC_EOF, 0, NULL, rc)) { + case RC_ACK: + break; + + case RC_XXX: + padios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + if (i == NOTOK) + err2peer (RC_ERR, "pipe", "error reading from"); + + close (pd[0]); + pidwait (pid, OK); + return (i != NOTOK ? pid : NOTOK); + } +} + + +void +padios (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (vmh) { + verr2peer (RC_FIN, what, fmt, ap); + rcdone (); + } else { + advertise (what, NULL, fmt, ap); + } + va_end(ap); + + done (1); +} + + +void +padvise (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (vmh) { + verr2peer (RC_ERR, what, fmt, ap); + } else { + advertise (what, NULL, fmt, ap); + } + va_end(ap); +} diff --git a/uip/mshcmds.c b/uip/mshcmds.c new file mode 100644 index 0000000..fcdd5e1 --- /dev/null +++ b/uip/mshcmds.c @@ -0,0 +1,3095 @@ + +/* + * mshcmds.c -- command handlers in msh + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int errno; + +static char delim3[] = "-------"; /* from burst.c */ + +static int mhlnum; +static FILE *mhlfp; + +#if defined(NNTP) && defined(MPOP) +# undef MPOP +#endif + +#ifdef MPOP +# ifdef BPOP +extern int pmsh; +extern char response[]; +# endif +#endif /* MPOP */ + +/* + * Type for a compare function for qsort. This keeps + * the compiler happy. + */ +typedef int (*qsort_comp) (const void *, const void *); + +/* + * prototypes + */ +void clear_screen (void); /* from termsbr.c */ +int SOprintf (char *, ...); /* from termsbr.c */ +int sc_width (void); /* from termsbr.c */ + +/* + * static prototypes + */ +static int burst (struct Msg *, int, int, int, int); +static void forw (char *, char *, int, char **); +static void rmm (void); +static void show (int); +static int eom_action (int); +static FILE *mhl_action (char *); +static int ask (int); +static int is_nontext (int); +static int get_fields (char *, char *, int, struct Msg *); +static int msgsort (struct Msg *, struct Msg *); +static int subsort (struct Msg *, struct Msg *); +static char *sosmash (char *, char *); +static int process (int, char *, int, char **); +static void copy_message (int, FILE *); +static void copy_digest (int, FILE *); + + +void +forkcmd (char **args, char *pgm) +{ + int child_id; + char *vec[MAXARGS]; + + vec[0] = r1bindex (pgm, '/'); + copyip (args, vec + 1, MAXARGS - 1); + + if (fmsh) { + context_del (pfolder); + context_replace (pfolder, fmsh);/* update current folder */ + seq_save (mp); + context_save (); /* save the context file */ + } + fflush (stdout); + switch (child_id = fork ()) { + case NOTOK: + advise ("fork", "unable to"); + return; + + case OK: + closefds (3); + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + + execvp (pgm, vec); + fprintf (stderr, "unable to exec "); + perror (cmd_name); + _exit (1); + + default: + pidXwait (child_id, NULL); + break; + } + if (fmsh) { /* assume the worst case */ + mp->msgflags |= MODIFIED; + modified++; + } +} + + +static struct swit distswit[] = { +#define DIANSW 0 + { "annotate", 0 }, +#define DINANSW 1 + { "noannotate", 0 }, +#define DIDFSW 2 + { "draftfolder +folder", 0 }, +#define DIDMSW 3 + { "draftmessage msg", 0 }, +#define DINDFSW 4 + { "nodraftfolder", 0 }, +#define DIEDTSW 5 + { "editor editor", 0 }, +#define DINEDSW 6 + { "noedit", 0 }, +#define DIFRMSW 7 + { "form formfile", 0 }, +#define DIINSW 8 + { "inplace", 0 }, +#define DININSW 9 + { "noinplace", 0 }, +#define DIWHTSW 10 + { "whatnowproc program", 0 }, +#define DINWTSW 11 + { "nowhatnowproc", 0 }, +#define DIHELP 12 + { "help", 4 }, + { NULL, 0 } +}; + + +void +distcmd (char **args) +{ + int vecp = 1; + char *cp, *msg = NULL; + char buf[BUFSIZ], *vec[MAXARGS]; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, distswit)) { + case AMBIGSW: + ambigsw (cp, distswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case DIHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, distswit, 1); + return; + + case DIANSW: /* not implemented */ + case DINANSW: + case DIINSW: + case DININSW: + continue; + + case DINDFSW: + case DINEDSW: + case DINWTSW: + vec[vecp++] = --cp; + continue; + + case DIEDTSW: + case DIFRMSW: + case DIDFSW: + case DIDMSW: + case DIWHTSW: + vec[vecp++] = --cp; + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + vec[vecp++] = cp; + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + if (msg) { + advise (NULL, "only one message at a time!"); + return; + } + else + msg = cp; + } + + vec[0] = cmd_name; + vec[vecp++] = "-file"; + vec[vecp] = NULL; + if (!msg) + msg = "cur"; + if (!m_convert (mp, msg)) + return; + seq_setprev (mp); + + if (mp->numsel > 1) { + advise (NULL, "only one message at a time!"); + return; + } + process (mp->hghsel, cmd_name, vecp, vec); + seq_setcur (mp, mp->hghsel); +} + + +static struct swit explswit[] = { +#define EXINSW 0 + { "inplace", 0 }, +#define EXNINSW 1 + { "noinplace", 0 }, +#define EXQISW 2 + { "quiet", 0 }, +#define EXNQISW 3 + { "noquiet", 0 }, +#define EXVBSW 4 + { "verbose", 0 }, +#define EXNVBSW 5 + { "noverbose", 0 }, +#define EXHELP 6 + { "help", 4 }, + { NULL, 0 } +}; + + +void +explcmd (char **args) +{ + int inplace = 0, quietsw = 0, verbosw = 0; + int msgp = 0, hi, msgnum; + char *cp, buf[BUFSIZ], *msgs[MAXARGS]; + struct Msg *smsgs; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, explswit)) { + case AMBIGSW: + ambigsw (cp, explswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case EXHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, explswit, 1); + return; + + case EXINSW: + inplace++; + continue; + case EXNINSW: + inplace = 0; + continue; + case EXQISW: + quietsw++; + continue; + case EXNQISW: + quietsw = 0; + continue; + case EXVBSW: + verbosw++; + continue; + case EXNVBSW: + verbosw = 0; + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + + if (!msgp) + msgs[msgp++] = "cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + smsgs = (struct Msg *) + calloc ((size_t) (MAXFOLDER + 2), sizeof *smsgs); + if (smsgs == NULL) + adios (NULL, "unable to allocate folder storage"); + + hi = mp->hghmsg + 1; + interrupted = 0; + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) + if (burst (smsgs, msgnum, inplace, quietsw, verbosw) != OK) + break; + + free ((char *) smsgs); + + if (inplace) + seq_setcur (mp, mp->lowsel); + else + if (hi <= mp->hghmsg) + seq_setcur (mp, hi); + + mp->msgflags |= MODIFIED; + modified++; +} + + +static int +burst (struct Msg *smsgs, int msgnum, int inplace, int quietsw, int verbosw) +{ + int i, j, ld3, wasdlm, msgp; + long pos; + char c, buffer[BUFSIZ]; + register FILE *zp; + + ld3 = strlen (delim3); + + if (Msgs[msgnum].m_scanl) { + free (Msgs[msgnum].m_scanl); + Msgs[msgnum].m_scanl = NULL; + } + + pos = ftell (zp = msh_ready (msgnum, 1)); + for (msgp = 0; msgp <= MAXFOLDER;) { + while (fgets (buffer, sizeof buffer, zp) != NULL + && buffer[0] == '\n' + && pos < Msgs[msgnum].m_stop) + pos += (long) strlen (buffer); + if (feof (zp) || pos >= Msgs[msgnum].m_stop) + break; + fseek (zp, pos, SEEK_SET); + smsgs[msgp].m_start = pos; + + for (c = 0; + pos < Msgs[msgnum].m_stop + && fgets (buffer, sizeof buffer, zp) != NULL; + c = buffer[0]) + if (strncmp (buffer, delim3, ld3) == 0 + && (msgp == 1 || c == '\n') + && peekc (zp) == '\n') + break; + else + pos += (long) strlen (buffer); + + wasdlm = strncmp (buffer, delim3, ld3) == 0; + if (smsgs[msgp].m_start != pos) + smsgs[msgp++].m_stop = (c == '\n' && wasdlm) ? pos - 1 : pos; + if (feof (zp) || pos >= Msgs[msgnum].m_stop) { + if (wasdlm) + smsgs[msgp - 1].m_stop -= ((long) strlen (buffer) + 1); + break; + } + pos += (long) strlen (buffer); + } + + switch (msgp--) { /* toss "End of XXX Digest" */ + case 0: + adios (NULL, "burst() botch -- you lose big"); + + case 1: + if (!quietsw) + printf ("message %d not in digest format\n", msgnum); + return OK; + + default: + if (verbosw) + printf ("%d message%s exploded from digest %d\n", + msgp, msgp != 1 ? "s" : "", msgnum); + break; + } + + if ((i = msgp + mp->hghmsg) > MAXFOLDER) { + advise (NULL, "more than %d messages", MAXFOLDER); + return NOTOK; + } + if (!(mp = folder_realloc (mp, mp->lowoff, i))) + adios (NULL, "unable to allocate folder storage"); + + j = mp->hghmsg; + mp->hghmsg += msgp; + mp->nummsg += msgp; + if (mp->hghsel > msgnum) + mp->hghsel += msgp; + + if (inplace) + for (i = mp->hghmsg; j > msgnum; i--, j--) { + if (verbosw) + printf ("message %d becomes message %d\n", j, i); + + Msgs[i].m_bboard_id = Msgs[j].m_bboard_id; + Msgs[i].m_top = Msgs[j].m_top; + Msgs[i].m_start = Msgs[j].m_start; + Msgs[i].m_stop = Msgs[j].m_stop; + Msgs[i].m_scanl = NULL; + if (Msgs[j].m_scanl) { + free (Msgs[j].m_scanl); + Msgs[j].m_scanl = NULL; + } + copy_msg_flags (mp, i, j); + } + + if (Msgs[msgnum].m_bboard_id == 0) + readid (msgnum); + + unset_selected (mp, msgnum); + i = inplace ? msgnum + msgp : mp->hghmsg; + for (j = msgp; j >= (inplace ? 0 : 1); i--, j--) { + if (verbosw && i != msgnum) + printf ("message %d of digest %d becomes message %d\n", + j, msgnum, i); + + Msgs[i].m_bboard_id = Msgs[msgnum].m_bboard_id; + Msgs[i].m_top = Msgs[j].m_top; + Msgs[i].m_start = smsgs[j].m_start; + Msgs[i].m_stop = smsgs[j].m_stop; + Msgs[i].m_scanl = NULL; + copy_msg_flags (mp, i, msgnum); + } + + return OK; +} + + +static struct swit fileswit[] = { +#define FIDRFT 0 + { "draft", 0 }, +#define FILINK 1 + { "link", 0 }, +#define FINLINK 2 + { "nolink", 0 }, +#define FIPRES 3 + { "preserve", 0 }, +#define FINPRES 4 + { "nopreserve", 0 }, +#define FISRC 5 + { "src +folder", 0 }, +#define FIFILE 6 + { "file file", 0 }, +#define FIPROC 7 + { "rmmproc program", 0 }, +#define FINPRC 8 + { "normmproc", 0 }, +#define FIHELP 9 + { "help", 4 }, + { NULL, 0 } +}; + + +void +filecmd (char **args) +{ + int linksw = 0, msgp = 0; + int vecp = 1, i, msgnum; + char *cp, buf[BUFSIZ]; + char *msgs[MAXARGS], *vec[MAXARGS]; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (i = smatch (++cp, fileswit)) { + case AMBIGSW: + ambigsw (cp, fileswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case FIHELP: + snprintf (buf, sizeof(buf), "%s +folder... [msgs] [switches]", cmd_name); + print_help (buf, fileswit, 1); + return; + + case FILINK: + linksw++; + continue; + case FINLINK: + linksw = 0; + continue; + + case FIPRES: + case FINPRES: + continue; + + case FISRC: + case FIDRFT: + case FIFILE: + case FIPROC: + case FINPRC: + advise (NULL, "sorry, -%s not allowed!", fileswit[i].sw); + return; + } + if (*cp == '+' || *cp == '@') + vec[vecp++] = cp; + else + msgs[msgp++] = cp; + } + + vec[0] = cmd_name; + vec[vecp++] = "-file"; + vec[vecp] = NULL; + if (!msgp) + msgs[msgp++] = "cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + interrupted = 0; + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) + if (process (msgnum, fileproc, vecp, vec)) { + unset_selected (mp, msgnum); + mp->numsel--; + } + + if (mp->numsel != mp->nummsg || linksw) + seq_setcur (mp, mp->hghsel); + if (!linksw) + rmm (); +} + + +int +filehak (char **args) +{ + int result, vecp = 0; + char *cp, *cwd, *vec[MAXARGS]; + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, fileswit)) { + case AMBIGSW: + case UNKWNSW: + case FIHELP: + return NOTOK; + + case FILINK: + case FINLINK: + case FIPRES: + case FINPRES: + continue; + + case FISRC: + case FIDRFT: + case FIFILE: + return NOTOK; + } + if (*cp == '+' || *cp == '@') + vec[vecp++] = cp; + } + vec[vecp] = NULL; + + result = NOTOK; + cwd = NULL; + for (vecp = 0; (cp = vec[vecp]) && result == NOTOK; vecp++) { + if (cwd == NULL) + cwd = getcpy (pwd ()); + chdir (m_maildir ("")); + cp = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + if (access (m_maildir (cp), F_OK) == NOTOK) + result = OK; + free (cp); + } + if (cwd) + chdir (cwd); + + return result; +} + + +static struct swit foldswit[] = { +#define FLALSW 0 + { "all", 0 }, +#define FLFASW 1 + { "fast", 0 }, +#define FLNFASW 2 + { "nofast", 0 }, +#define FLHDSW 3 + { "header", 0 }, +#define FLNHDSW 4 + { "noheader", 0 }, +#define FLPKSW 5 + { "pack", 0 }, +#define FLNPKSW 6 + { "nopack", 0 }, +#define FLRCSW 7 + { "recurse", 0 }, +#define FLNRCSW 8 + { "norecurse", 0 }, +#define FLTLSW 9 + { "total", 0 }, +#define FLNTLSW 10 + { "nototal", 0 }, +#define FLPRSW 11 + { "print", 0 }, +#define FLPUSW 12 + { "push", 0 }, +#define FLPOSW 13 + { "pop", 0 }, +#define FLLISW 14 + { "list", 0 }, +#define FLHELP 15 + { "help", 4 }, + { NULL, 0 } +}; + + +void +foldcmd (char **args) +{ + int fastsw = 0, headersw = 0, packsw = 0; + int hole, msgnum; + char *cp, *folder = NULL, *msg = NULL; + char buf[BUFSIZ], **vec = args; + + if (args == NULL) + goto fast; + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, foldswit)) { + case AMBIGSW: + ambigsw (cp, foldswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case FLHELP: + snprintf (buf, sizeof(buf), "%s [+folder] [msg] [switches]", cmd_name); + print_help (buf, foldswit, 1); + return; + + case FLALSW: /* not implemented */ + case FLRCSW: + case FLNRCSW: + case FLTLSW: + case FLNTLSW: + case FLPRSW: + case FLPUSW: + case FLPOSW: + case FLLISW: + continue; + + case FLFASW: + fastsw++; + continue; + case FLNFASW: + fastsw = 0; + continue; + case FLHDSW: + headersw++; + continue; + case FLNHDSW: + headersw = 0; + continue; + case FLPKSW: + packsw++; + continue; + case FLNPKSW: + packsw = 0; + continue; + } + if (*cp == '+' || *cp == '@') + if (folder) { + advise (NULL, "only one folder at a time!\n"); + return; + } + else + folder = fmsh ? path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF) + : cp + 1; + else + if (msg) { + advise (NULL, "only one message at a time!\n"); + return; + } + else + msg = cp; + } + + if (folder) { + if (*folder == 0) { + advise (NULL, "null folder names are not permitted"); + return; + } + if (fmsh) { + if (access (m_maildir (folder), R_OK) == NOTOK) { + advise (folder, "unable to read"); + return; + } + } + else { + strncpy (buf, folder, sizeof(buf)); + if (expand (buf) == NOTOK) + return; + folder = buf; + if (access (folder, R_OK) == NOTOK) { + advise (folder, "unable to read"); + return; + } + } + m_reset (); + + if (fmsh) + fsetup (folder); + else + setup (folder); + readids (0); + display_info (0); + } + + if (msg) { + if (!m_convert (mp, msg)) + return; + seq_setprev (mp); + + if (mp->numsel > 1) { + advise (NULL, "only one message at a time!"); + return; + } + seq_setcur (mp, mp->hghsel); + } + + if (packsw) { + if (fmsh) { + forkcmd (vec, cmd_name); + return; + } + + if (mp->lowoff > 1 && !(mp = folder_realloc (mp, 1, mp->hghmsg))) + adios (NULL, "unable to allocate folder storage"); + + for (msgnum = mp->lowmsg, hole = 1; msgnum <= mp->hghmsg; msgnum++) + if (does_exist (mp, msgnum)) { + if (msgnum != hole) { + Msgs[hole].m_bboard_id = Msgs[msgnum].m_bboard_id; + Msgs[hole].m_top = Msgs[msgnum].m_top; + Msgs[hole].m_start = Msgs[msgnum].m_start; + Msgs[hole].m_stop = Msgs[msgnum].m_stop; + Msgs[hole].m_scanl = NULL; + if (Msgs[msgnum].m_scanl) { + free (Msgs[msgnum].m_scanl); + Msgs[msgnum].m_scanl = NULL; + } + copy_msg_flags (mp, hole, msgnum); + if (mp->curmsg == msgnum) + seq_setcur (mp, hole); + } + hole++; + } + if (mp->nummsg > 0) { + mp->lowmsg = 1; + mp->hghmsg = hole - 1; + } + mp->msgflags |= MODIFIED; + modified++; + } + +fast: ; + if (fastsw) + printf ("%s\n", fmsh ? fmsh : mp->foldpath); + else { + if (headersw) + printf ("\t\tFolder %*s# of messages (%*srange%*s); cur%*smsg\n", + DMAXFOLDER, "", DMAXFOLDER - 2, "", DMAXFOLDER - 2, "", + DMAXFOLDER - 2, ""); + printf (args ? "%22s " : "%s ", fmsh ? fmsh : mp->foldpath); + + /* check for empty folder */ + if (mp->nummsg == 0) { + printf ("has no messages%*s", + mp->msgflags & OTHERS ? DMAXFOLDER * 2 + 4 : 0, ""); + } else { + printf ("has %*d message%s (%*d-%*d)", + DMAXFOLDER, mp->nummsg, mp->nummsg != 1 ? "s" : "", + DMAXFOLDER, mp->lowmsg, DMAXFOLDER, mp->hghmsg); + if (mp->curmsg >= mp->lowmsg + && mp->curmsg <= mp->hghmsg) + printf ("; cur=%*d", DMAXFOLDER, mp->curmsg); + } + printf (".\n"); + } +} + + +static struct swit forwswit[] = { +#define FOANSW 0 + { "annotate", 0 }, +#define FONANSW 1 + { "noannotate", 0 }, +#define FODFSW 2 + { "draftfolder +folder", 0 }, +#define FODMSW 3 + { "draftmessage msg", 0 }, +#define FONDFSW 4 + { "nodraftfolder", 0 }, +#define FOEDTSW 5 + { "editor editor", 0 }, +#define FONEDSW 6 + { "noedit", 0 }, +#define FOFTRSW 7 + { "filter filterfile", 0 }, +#define FOFRMSW 8 + { "form formfile", 0 }, +#define FOFTSW 9 + { "format", 5 }, +#define FONFTSW 10 + { "noformat", 7 }, +#define FOINSW 11 + { "inplace", 0 }, +#define FONINSW 12 + { "noinplace", 0 }, +#define FOMISW 13 + { "mime", 0 }, +#define FONMISW 14 + { "nomime", 0 }, +#define FOWHTSW 15 + { "whatnowproc program", 0 }, +#define FONWTSW 16 + { "nowhatnow", 0 }, +#define FOHELP 17 + { "help", 4 }, + { NULL, 0 } +}; + + +void +forwcmd (char **args) +{ + int msgp = 0, vecp = 1, msgnum; + char *cp, *filter = NULL, buf[BUFSIZ]; + char *msgs[MAXARGS], *vec[MAXARGS]; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, forwswit)) { + case AMBIGSW: + ambigsw (cp, forwswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case FOHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, forwswit, 1); + return; + + case FOANSW: /* not implemented */ + case FONANSW: + case FOINSW: + case FONINSW: + case FOMISW: + case FONMISW: + continue; + + case FONDFSW: + case FONEDSW: + case FONWTSW: + vec[vecp++] = --cp; + continue; + + case FOEDTSW: + case FOFRMSW: + case FODFSW: + case FODMSW: + case FOWHTSW: + vec[vecp++] = --cp; + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + vec[vecp++] = cp; + continue; + case FOFTRSW: + if (!(filter = *args++) || *filter == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + continue; + case FOFTSW: + if (access (filter = myfilter, R_OK) == NOTOK) { + advise (filter, "unable to read default filter file"); + return; + } + continue; + case FONFTSW: + filter = NULL; + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + + /* foil search of .mh_profile */ + snprintf (buf, sizeof(buf), "%sXXXXXX", invo_name); + vec[0] = (char *)mktemp (buf); + vec[vecp++] = "-file"; + vec[vecp] = NULL; + if (!msgp) + msgs[msgp++] = "cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + if (filter) { + strncpy (buf, filter, sizeof(buf)); + if (expand (buf) == NOTOK) + return; + if (access (filter = getcpy (etcpath (buf)), R_OK) == NOTOK) { + advise (filter, "unable to read"); + free (filter); + return; + } + } + forw (cmd_name, filter, vecp, vec); + seq_setcur (mp, mp->hghsel); + if (filter) + free (filter); +} + + +static void +forw (char *proc, char *filter, int vecp, char **vec) +{ + int i, child_id, msgnum, msgcnt; + char tmpfil[80], *args[MAXARGS]; + FILE *out; + + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + interrupted = 0; + if (filter) + switch (child_id = fork ()) { + case NOTOK: + advise ("fork", "unable to"); + return; + + case OK: /* "trust me" */ + if (freopen (tmpfil, "w", stdout) == NULL) { + fprintf (stderr, "unable to create "); + perror (tmpfil); + _exit (1); + } + args[0] = r1bindex (mhlproc, '/'); + i = 1; + args[i++] = "-forwall"; + args[i++] = "-form"; + args[i++] = filter; + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) + args[i++] = getcpy (m_name (msgnum)); + args[i] = NULL; + mhlsbr (i, args, mhl_action); + m_eomsbr ((int (*) ()) 0); + fclose (stdout); + _exit (0); + + default: + if (pidXwait (child_id, NULL)) + interrupted++; + break; + } + else { + if ((out = fopen (tmpfil, "w")) == NULL) { + advise (tmpfil, "unable to create temporary file"); + return; + } + + msgcnt = 1; + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) { + fprintf (out, "\n\n-------"); + if (msgnum == mp->lowsel) + fprintf (out, " Forwarded Message%s", + mp->numsel > 1 ? "s" : ""); + else + fprintf (out, " Message %d", msgcnt); + fprintf (out, "\n\n"); + copy_digest (msgnum, out); + msgcnt++; + } + + fprintf (out, "\n\n------- End of Forwarded Message%s\n", + mp->numsel > 1 ? "s" : ""); + fclose (out); + } + + fflush (stdout); + if (!interrupted) + switch (child_id = fork ()) { + case NOTOK: + advise ("fork", "unable to"); + break; + + case OK: + closefds (3); + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + + vec[vecp++] = tmpfil; + vec[vecp] = NULL; + + execvp (proc, vec); + fprintf (stderr, "unable to exec "); + perror (proc); + _exit (1); + + default: + pidXwait (child_id, NULL); + break; + } + + unlink (tmpfil); +} + + +static char *hlpmsg[] = { + "The %s program emulates many of the commands found in the nmh", + "system. Instead of operating on nmh folders, commands to %s concern", + "a single file.", + "", + "To see the list of commands available, just type a ``?'' followed by", + "the RETURN key. To find out what switches each command takes, type", + "the name of the command followed by ``-help''. To leave %s, use the", + "``quit'' command.", + "", + "Although a lot of nmh commands are found in %s, not all are fully", + "implemented. %s will always recognize all legal switches for a", + "given command though, and will let you know when you ask for an", + "option that it is unable to perform.", + "", + "Running %s is fun, but using nmh from your shell is far superior.", + "After you have familiarized yourself with the nmh style by using %s,", + "you should try using nmh from the shell. You can still use %s for", + "message files that aren't in nmh format, such as BBoard files.", + NULL +}; + + +void +helpcmd (char **args) +{ + int i; + + for (i = 0; hlpmsg[i]; i++) { + printf (hlpmsg[i], invo_name); + putchar ('\n'); + } +} + + +static struct swit markswit[] = { +#define MADDSW 0 + { "add", 0 }, +#define MDELSW 1 + { "delete", 0 }, +#define MLSTSW 2 + { "list", 0 }, +#define MSEQSW 3 + { "sequence name", 0 }, +#define MPUBSW 4 + { "public", 0 }, +#define MNPUBSW 5 + { "nopublic", 0 }, +#define MZERSW 6 + { "zero", 0 }, +#define MNZERSW 7 + { "nozero", 0 }, +#define MHELP 8 + { "help", 4 }, +#define MDBUGSW 9 + { "debug", -5 }, + { NULL, 0 } +}; + + +void +markcmd (char **args) +{ + int addsw = 0, deletesw = 0, debugsw = 0; + int listsw = 0, zerosw = 0, seqp = 0; + int msgp = 0, msgnum; + char *cp, buf[BUFSIZ]; + char *seqs[NUMATTRS + 1], *msgs[MAXARGS]; + + while ((cp = *args++)) { + if (*cp == '-') { + switch (smatch (++cp, markswit)) { + case AMBIGSW: + ambigsw (cp, markswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case MHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, markswit, 1); + return; + + case MADDSW: + addsw++; + deletesw = listsw = 0; + continue; + case MDELSW: + deletesw++; + addsw = listsw = 0; + continue; + case MLSTSW: + listsw++; + addsw = deletesw = 0; + continue; + + case MSEQSW: + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + if (seqp < NUMATTRS) + seqs[seqp++] = cp; + else { + advise (NULL, "only %d sequences allowed!", NUMATTRS); + return; + } + continue; + + case MPUBSW: /* not implemented */ + case MNPUBSW: + continue; + + case MDBUGSW: + debugsw++; + continue; + + case MZERSW: + zerosw++; + continue; + case MNZERSW: + zerosw = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } else { + msgs[msgp++] = cp; + } + } + + if (!addsw && !deletesw && !listsw) + if (seqp) + addsw++; + else + if (debugsw) + listsw++; + else { + seqs[seqp++] = "unseen"; + deletesw++; + zerosw = 0; + if (!msgp) + msgs[msgp++] = "all"; + } + + if (!msgp) + msgs[msgp++] = listsw ? "all" :"cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + + if (debugsw) { + printf ("invo_name=%s mypath=%s defpath=%s\n", + invo_name, mypath, defpath); + printf ("ctxpath=%s context flags=%s\n", + ctxpath, snprintb (buf, sizeof(buf), (unsigned) ctxflags, DBITS)); + printf ("foldpath=%s flags=%s\n", + mp->foldpath, + snprintb (buf, sizeof(buf), (unsigned) mp->msgflags, FBITS)); + printf ("hghmsg=%d lowmsg=%d nummsg=%d curmsg=%d\n", + mp->hghmsg, mp->lowmsg, mp->nummsg, mp->curmsg); + printf ("lowsel=%d hghsel=%d numsel=%d\n", + mp->lowsel, mp->hghsel, mp->numsel); + printf ("lowoff=%d hghoff=%d\n", mp->lowoff, mp->hghoff); + } + + if (seqp == 0 && (addsw || deletesw)) { + advise (NULL, "-%s requires at least one -sequence argument", + addsw ? "add" : "delete"); + return; + } + seqs[seqp] = NULL; + + if (addsw) { + for (seqp = 0; seqs[seqp]; seqp++) + if (!seq_addsel (mp, seqs[seqp], 0, zerosw)) + return; + } + + if (deletesw) { + for (seqp = 0; seqs[seqp]; seqp++) + if (!seq_delsel (mp, seqs[seqp], 0, zerosw)) + return; + } + + /* Listing messages in sequences */ + if (listsw) { + if (seqp) { + /* list the given sequences */ + for (seqp = 0; seqs[seqp]; seqp++) + seq_print (mp, seqs[seqp]); + } else { + /* else list them all */ + seq_printall (mp); + } + + interrupted = 0; + if (debugsw) + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) { + printf ("%*d: id=%d top=%d start=%ld stop=%ld %s\n", + DMAXFOLDER, + msgnum, + Msgs[msgnum].m_bboard_id, + Msgs[msgnum].m_top, + (long) Msgs[msgnum].m_start, + (long) Msgs[msgnum].m_stop, + snprintb (buf, sizeof(buf), + (unsigned) mp->msgstats[msgnum - mp->lowoff], + seq_bits (mp))); + if (Msgs[msgnum].m_scanl) + printf ("%s", Msgs[msgnum].m_scanl); + } + } +} + + +static struct swit mhnswit[] = { +#define MHNAUTOSW 0 + { "auto", 0 }, +#define MHNNAUTOSW 1 + { "noauto", 0 }, +#define MHNDEBUGSW 2 + { "debug", -5 }, +#define MHNEBCDICSW 3 + { "ebcdicsafe", 0 }, +#define MHNNEBCDICSW 4 + { "noebcdicsafe", 0 }, +#define MHNFORMSW 5 + { "form formfile", 4 }, +#define MHNHEADSW 6 + { "headers", 0 }, +#define MHNNHEADSW 7 + { "noheaders", 0 }, +#define MHNLISTSW 8 + { "list", 0 }, +#define MHNNLISTSW 9 + { "nolist", 0 }, +#define MHNPARTSW 10 + { "part number", 0 }, +#define MHNSIZESW 11 + { "realsize", 0 }, +#define MHNNSIZESW 12 + { "norealsize", 0 }, +#define MHNRFC934SW 13 + { "rfc934mode", 0 }, +#define MHNNRFC934SW 14 + { "norfc934mode", 0 }, +#define MHNSERIALSW 15 + { "serialonly", 0 }, +#define MHNNSERIALSW 16 + { "noserialonly", 0 }, +#define MHNSHOWSW 17 + { "show", 0 }, +#define MHNNSHOWSW 18 + { "noshow", 0 }, +#define MHNSTORESW 19 + { "store", 0 }, +#define MHNNSTORESW 20 + { "nostore", 0 }, +#define MHNTYPESW 21 + { "type content", 0 }, +#define MHNVERBSW 22 + { "verbose", 0 }, +#define MHNNVERBSW 23 + { "noverbose", 0 }, +#define MHNHELPSW 24 + { "help", 4 }, +#define MHNPROGSW 25 + { "moreproc program", -4 }, +#define MHNNPROGSW 26 + { "nomoreproc", -3 }, +#define MHNLENSW 27 + { "length lines", -4 }, +#define MHNWIDSW 28 + { "width columns", -4 }, + { NULL, 0 } +}; + + +void +mhncmd (char **args) +{ + int msgp = 0, vecp = 1; + int msgnum; + char *cp, buf[BUFSIZ]; + char *msgs[MAXARGS], *vec[MAXARGS]; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + while ((cp = *args++)) { + if (*cp == '-') { + switch (smatch (++cp, mhnswit)) { + case AMBIGSW: + ambigsw (cp, mhnswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case MHNHELPSW: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, mhnswit, 1); + return; + + case MHNAUTOSW: + case MHNNAUTOSW: + case MHNDEBUGSW: + case MHNEBCDICSW: + case MHNNEBCDICSW: + case MHNHEADSW: + case MHNNHEADSW: + case MHNLISTSW: + case MHNNLISTSW: + case MHNSIZESW: + case MHNNSIZESW: + case MHNRFC934SW: + case MHNNRFC934SW: + case MHNSERIALSW: + case MHNNSERIALSW: + case MHNSHOWSW: + case MHNNSHOWSW: + case MHNSTORESW: + case MHNNSTORESW: + case MHNVERBSW: + case MHNNVERBSW: + case MHNNPROGSW: + vec[vecp++] = --cp; + continue; + + case MHNFORMSW: + case MHNPARTSW: + case MHNTYPESW: + case MHNPROGSW: + case MHNLENSW: + case MHNWIDSW: + vec[vecp++] = --cp; + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + vec[vecp++] = cp; + continue; + } + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } else { + msgs[msgp++] = cp; + } + } + + vec[0] = cmd_name; + vec[vecp++] = "-file"; + vec[vecp] = NULL; + if (!msgp) + msgs[msgp++] = "cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + interrupted = 0; + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) + if (process (msgnum, cmd_name, vecp, vec)) { + unset_selected (mp, msgnum); + mp->numsel--; + } + + seq_setcur (mp, mp->hghsel); +} + + +static struct swit packswit[] = { +#define PAFISW 0 + { "file name", 0 }, +#define PAHELP 1 + { "help", 4 }, + { NULL, 0 } +}; + +static mbx_style = MMDF_FORMAT; + +void +packcmd (char **args) +{ + int msgp = 0, md, msgnum; + char *cp, *file = NULL; + char buf[BUFSIZ], *msgs[MAXARGS]; + struct stat st; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, packswit)) { + case AMBIGSW: + ambigsw (cp, packswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case PAHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, packswit, 1); + return; + + case PAFISW: + if (!(file = *args++) || *file == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + + if (!file) + file = "./msgbox"; + file = path (file, TFILE); + if (stat (file, &st) == NOTOK) { + if (errno != ENOENT) { + advise (file, "error on file"); + goto done_pack; + } + md = getanswer (cp = concat ("Create file \"", file, "\"? ", NULL)); + free (cp); + if (!md) + goto done_pack; + } + + if (!msgp) + msgs[msgp++] = "all"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + goto done_pack; + seq_setprev (mp); + + if ((md = mbx_open (file, mbx_style, getuid (), getgid (), m_gmprot ())) == NOTOK) { + advise (file, "unable to open"); + goto done_pack; + } + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) + if (pack (file, md, msgnum) == NOTOK) + break; + mbx_close (file, md); + + if (mp->hghsel != mp->curmsg) + seq_setcur (mp, mp->lowsel); + +done_pack: ; + free (file); +} + + +int +pack (char *mailbox, int md, int msgnum) +{ + register FILE *zp; + + if (Msgs[msgnum].m_bboard_id == 0) + readid (msgnum); + + zp = msh_ready (msgnum, 1); + return mbx_write (mailbox, md, zp, Msgs[msgnum].m_bboard_id, + 0L, ftell (zp), Msgs[msgnum].m_stop, 1, 1); +} + + +int +packhak (char **args) +{ + int result; + char *cp, *file = NULL; + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, packswit)) { + case AMBIGSW: + case UNKWNSW: + case PAHELP: + return NOTOK; + + case PAFISW: + if (!(file = *args++) || *file == '-') + return NOTOK; + continue; + } + if (*cp == '+' || *cp == '@') + return NOTOK; + } + + file = path (file ? file : "./msgbox", TFILE); + result = access (file, F_OK) == NOTOK ? OK : NOTOK; + free (file); + + return result; +} + + +static struct swit pickswit[] = { +#define PIANSW 0 + { "and", 0 }, +#define PIORSW 1 + { "or", 0 }, +#define PINTSW 2 + { "not", 0 }, +#define PILBSW 3 + { "lbrace", 0 }, +#define PIRBSW 4 + { "rbrace", 0 }, +#define PICCSW 5 + { "cc pattern", 0 }, +#define PIDASW 6 + { "date pattern", 0 }, +#define PIFRSW 7 + { "from pattern", 0 }, +#define PISESW 8 + { "search pattern", 0 }, +#define PISUSW 9 + { "subject pattern", 0 }, +#define PITOSW 10 + { "to pattern", 0 }, +#define PIOTSW 11 + { "-othercomponent pattern", 15 }, +#define PIAFSW 12 + { "after date", 0 }, +#define PIBFSW 13 + { "before date", 0 }, +#define PIDFSW 14 + { "datefield field", 5 }, +#define PISQSW 15 + { "sequence name", 0 }, +#define PIPUSW 16 + { "public", 0 }, +#define PINPUSW 17 + { "nopublic", 0 }, +#define PIZRSW 18 + { "zero", 0 }, +#define PINZRSW 19 + { "nozero", 0 }, +#define PILISW 20 + { "list", 0 }, +#define PINLISW 21 + { "nolist", 0 }, +#define PIHELP 22 + { "help", 4 }, + { NULL, 0 } +}; + + +void +pickcmd (char **args) +{ + int zerosw = 1, msgp = 0, seqp = 0; + int vecp = 0, hi, lo, msgnum; + char *cp, buf[BUFSIZ], *msgs[MAXARGS]; + char *seqs[NUMATTRS], *vec[MAXARGS]; + register FILE *zp; + + while ((cp = *args++)) { + if (*cp == '-') { + if (*++cp == '-') { + vec[vecp++] = --cp; + goto pattern; + } + switch (smatch (cp, pickswit)) { + case AMBIGSW: + ambigsw (cp, pickswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case PIHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, pickswit, 1); + return; + + case PICCSW: + case PIDASW: + case PIFRSW: + case PISUSW: + case PITOSW: + case PIDFSW: + case PIAFSW: + case PIBFSW: + case PISESW: + vec[vecp++] = --cp; +pattern: ; + if (!(cp = *args++)) {/* allow -xyz arguments */ + advise (NULL, "missing argument to %s", args[-2]); + return; + } + vec[vecp++] = cp; + continue; + case PIOTSW: + advise (NULL, "internal error!"); + return; + case PIANSW: + case PIORSW: + case PINTSW: + case PILBSW: + case PIRBSW: + vec[vecp++] = --cp; + continue; + + case PISQSW: + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + if (seqp < NUMATTRS) + seqs[seqp++] = cp; + else { + advise (NULL, "only %d sequences allowed!", NUMATTRS); + return; + } + continue; + case PIZRSW: + zerosw++; + continue; + case PINZRSW: + zerosw = 0; + continue; + + case PIPUSW: /* not implemented */ + case PINPUSW: + case PILISW: + case PINLISW: + continue; + } + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + vec[vecp] = NULL; + + if (!msgp) + msgs[msgp++] = "all"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + interrupted = 0; + if (!pcompile (vec, NULL)) + return; + + lo = mp->lowsel; + hi = mp->hghsel; + + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) { + zp = msh_ready (msgnum, 1); + if (pmatches (zp, msgnum, fmsh ? 0L : Msgs[msgnum].m_start, + fmsh ? 0L : Msgs[msgnum].m_stop)) { + if (msgnum < lo) + lo = msgnum; + if (msgnum > hi) + hi = msgnum; + } + else { + unset_selected (mp, msgnum); + mp->numsel--; + } + } + + if (interrupted) + return; + + mp->lowsel = lo; + mp->hghsel = hi; + + if (mp->numsel <= 0) { + advise (NULL, "no messages match specification"); + return; + } + + seqs[seqp] = NULL; + for (seqp = 0; seqs[seqp]; seqp++) + if (!seq_addsel (mp, seqs[seqp], 0, zerosw)) + return; + + printf ("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s"); +} + + +static struct swit replswit[] = { +#define REANSW 0 + { "annotate", 0 }, +#define RENANSW 1 + { "noannotate", 0 }, +#define RECCSW 2 + { "cc type", 0 }, +#define RENCCSW 3 + { "nocc type", 0 }, +#define REDFSW 4 + { "draftfolder +folder", 0 }, +#define REDMSW 5 + { "draftmessage msg", 0 }, +#define RENDFSW 6 + { "nodraftfolder", 0 }, +#define REEDTSW 7 + { "editor editor", 0 }, +#define RENEDSW 8 + { "noedit", 0 }, +#define REFCCSW 9 + { "fcc +folder", 0 }, +#define REFLTSW 10 + { "filter filterfile", 0 }, +#define REFRMSW 11 + { "form formfile", 0 }, +#define REINSW 12 + { "inplace", 0 }, +#define RENINSW 13 + { "noinplace", 0 }, +#define REQUSW 14 + { "query", 0 }, +#define RENQUSW 15 + { "noquery", 0 }, +#define REWHTSW 16 + { "whatnowproc program", 0 }, +#define RENWTSW 17 + { "nowhatnow", 0 }, +#define REWIDSW 19 + { "width columns", 0 }, +#define REHELP 20 + { "help", 4 }, + { NULL, 0 } +}; + + +void +replcmd (char **args) +{ + int vecp = 1; + char *cp, *msg = NULL; + char buf[BUFSIZ], *vec[MAXARGS]; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, replswit)) { + case AMBIGSW: + ambigsw (cp, replswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case REHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, replswit, 1); + return; + + case REANSW: /* not implemented */ + case RENANSW: + case REINSW: + case RENINSW: + continue; + + case REQUSW: + case RENQUSW: + case RENDFSW: + case RENEDSW: + case RENWTSW: + vec[vecp++] = --cp; + continue; + + case RECCSW: + case RENCCSW: + case REEDTSW: + case REFCCSW: + case REFLTSW: + case REFRMSW: + case REWIDSW: + case REDFSW: + case REDMSW: + case REWHTSW: + vec[vecp++] = --cp; + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + vec[vecp++] = cp; + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + if (msg) { + advise (NULL, "only one message at a time!"); + return; + } + else + msg = cp; + } + + vec[0] = cmd_name; + vec[vecp++] = "-file"; + vec[vecp] = NULL; + if (!msg) + msg = "cur"; + if (!m_convert (mp, msg)) + return; + seq_setprev (mp); + + if (mp->numsel > 1) { + advise (NULL, "only one message at a time!"); + return; + } + process (mp->hghsel, cmd_name, vecp, vec); + seq_setcur (mp, mp->hghsel); +} + + +static struct swit rmmswit[] = { +#define RMHELP 0 + { "help", 4 }, + { NULL, 0 } +}; + + +void +rmmcmd (char **args) +{ + int msgp = 0, msgnum; + char *cp, buf[BUFSIZ], *msgs[MAXARGS]; + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, rmmswit)) { + case AMBIGSW: + ambigsw (cp, rmmswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case RMHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, rmmswit, 1); + return; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + + if (!msgp) + msgs[msgp++] = "cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + rmm (); +} + + +static void +rmm (void) +{ + register int msgnum, vecp; + register char *cp; + char buffer[BUFSIZ], *vec[MAXARGS]; + + if (fmsh) { + if (rmmproc) { + if (mp->numsel > MAXARGS - 1) { + advise (NULL, "more than %d messages for %s exec", + MAXARGS - 1, rmmproc); + return; + } + vecp = 0; + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) + vec[vecp++] = getcpy (m_name (msgnum)); + vec[vecp] = NULL; + forkcmd (vec, rmmproc); + for (vecp = 0; vec[vecp]; vecp++) + free (vec[vecp]); + } + else + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) { + strncpy (buffer, m_backup (cp = m_name (msgnum)), sizeof(buffer)); + if (rename (cp, buffer) == NOTOK) + admonish (buffer, "unable to rename %s to", cp); + } + } + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) { + set_deleted (mp, msgnum); + unset_exists (mp, msgnum); +#ifdef MPOP +#ifdef BPOP + if (pmsh && pop_dele (msgnum) != OK) + fprintf (stderr, "%s", response); +#endif +#endif /* MPOP */ + } + + if ((mp->nummsg -= mp->numsel) <= 0) { + if (fmsh) + admonish (NULL, "no messages remaining in +%s", fmsh); + else + admonish (NULL, "no messages remaining in %s", mp->foldpath); + mp->lowmsg = mp->hghmsg = mp->nummsg = 0; + } + if (mp->lowsel == mp->lowmsg) { + for (msgnum = mp->lowmsg + 1; msgnum <= mp->hghmsg; msgnum++) + if (does_exist (mp, msgnum)) + break; + mp->lowmsg = msgnum; + } + if (mp->hghsel == mp->hghmsg) { + for (msgnum = mp->hghmsg - 1; msgnum >= mp->lowmsg; msgnum--) + if (does_exist (mp, msgnum)) + break; + mp->hghmsg = msgnum; + } + + mp->msgflags |= MODIFIED; + modified++; +} + + +static struct swit scanswit[] = { +#define SCCLR 0 + { "clear", 0 }, +#define SCNCLR 1 + { "noclear", 0 }, +#define SCFORM 2 + { "form formatfile", 0 }, +#define SCFMT 3 + { "format string", 5 }, +#define SCHEAD 4 + { "header", 0 }, +#define SCNHEAD 5 + { "noheader", 0 }, +#define SCWID 6 + { "width columns", 0 }, +#define SCHELP 7 + { "help", 4 }, + { NULL, 0 } +}; + + +void +scancmd (char **args) +{ +#define equiv(a,b) (a ? b && !strcmp (a, b) : !b) + + int clearsw = 0, headersw = 0, width = 0, msgp = 0; + int msgnum, optim, state; + char *cp, *form = NULL, *format = NULL; + char buf[BUFSIZ], *nfs, *msgs[MAXARGS]; + register FILE *zp; +#ifdef MPOP +#ifdef BPOP + static int p_optim = 0; +#endif +#endif /* MPOP */ + static int s_optim = 0; + static char *s_form = NULL, *s_format = NULL; + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, scanswit)) { + case AMBIGSW: + ambigsw (cp, scanswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case SCHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, scanswit, 1); + return; + + case SCCLR: + clearsw++; + continue; + case SCNCLR: + clearsw = 0; + continue; + case SCHEAD: + headersw++; + continue; + case SCNHEAD: + headersw = 0; + continue; + case SCFORM: + if (!(form = *args++) || *form == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + format = NULL; + continue; + case SCFMT: + if (!(format = *args++) || *format == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + form = NULL; + continue; + case SCWID: + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + width = atoi (cp); + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + + if (!msgp) + msgs[msgp++] = "all"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + /* Get new format string */ + nfs = new_fs (form, format, FORMAT); + + /* force scansbr to (re)compile format */ + if (scanl) { + free (scanl); + scanl = NULL; + } + + if (s_optim == 0) { + s_optim = optim = 1; + s_form = form ? getcpy (form) : NULL; + s_format = format ? getcpy (format) : NULL; + +#ifdef MPOP +#ifdef BPOP + if (pmsh) { + int i; + char *dp, *ep, *fp; + + if (width == 0) + width = sc_width (); + + for (dp = nfs, i = 0; *dp; dp++, i++) + if (*dp == '\\' || *dp == '"' || *dp == '\n') + i++; + i++; + if ((ep = malloc ((unsigned) i)) == NULL) + adios (NULL, "out of memory"); + for (dp = nfs, fp = ep; *dp; dp++) { + if (*dp == '\n') { + *fp++ = '\\', *fp++ = 'n'; + continue; + } + if (*dp == '"' || *dp == '\\') + *fp++ = '\\'; + *fp++ = *dp; + } + *fp = NULL; + + if (pop_command ("XTND SCAN %d \"%s\"", width, ep) == OK) + p_optim = 1; + + free (ep); + } +#endif +#endif /* MPOP */ + } + else + optim = equiv (s_form, form) && equiv (s_format, format); + +#ifdef MPOP +#ifdef BPOP + if (p_optim && optim) { + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) + if (!is_selected(mp, msgnum) || Msgs[msgnum].m_scanl) + break; + if (msgnum > mp->hghmsg && pop_command ("LIST") == OK) { + fprintf (stderr, "Stand-by..."); + fflush (stderr); + + for (;;) { + int size; + + switch (pop_multiline ()) { + case NOTOK: + fprintf (stderr, "%s", response); + /* and fall... */ + case DONE: + fprintf (stderr,"\n"); + break; + + case OK: + if (sscanf (response, "%d %d", &msgnum, &size) == 2 + && mp->lowmsg <= msgnum + && msgnum <= mp->hghmsg + && (cp = strchr(response, '#')) + && *++cp) + Msgs[msgnum].m_scanl = concat (cp, "\n", NULL); + continue; + } + break; + } + } + } +#endif +#endif /* MPOP */ + + interrupted = 0; + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) { + if (optim && Msgs[msgnum].m_scanl) + printf ("%s", Msgs[msgnum].m_scanl); + else { +#ifdef MPOP +#ifdef BPOP + if (p_optim + && optim + && is_virtual (mp, msgnum) + && pop_command ("LIST %d", msgnum) == OK + && (cp = strchr(response, '#')) + && *++cp) { + Msgs[msgnum].m_scanl = concat (cp, "\n", NULL); + printf ("%s", Msgs[msgnum].m_scanl); + continue; + } +#endif +#endif /* MPOP */ + + zp = msh_ready (msgnum, 0); + switch (state = scan (zp, msgnum, 0, nfs, width, + msgnum == mp->curmsg, + is_unseen (mp, msgnum), + headersw ? (fmsh ? fmsh : mp->foldpath) : NULL, + fmsh ? 0L : (long) (Msgs[msgnum].m_stop - Msgs[msgnum].m_start), + 1)) { + case SCNMSG: + case SCNENC: + case SCNERR: + if (optim) + Msgs[msgnum].m_scanl = getcpy (scanl); + break; + + default: + advise (NULL, "scan() botch (%d)", state); + return; + + case SCNEOF: + printf ("%*d empty\n", DMAXFOLDER, msgnum); + break; + } + } + headersw = 0; + } + + if (clearsw) + clear_screen (); +} + + +static struct swit showswit[] = { +#define SHDRAFT 0 + { "draft", 5 }, +#define SHFORM 1 + { "form formfile", 4 }, +#define SHPROG 2 + { "moreproc program", 4 }, +#define SHNPROG 3 + { "nomoreproc", 3 }, +#define SHLEN 4 + { "length lines", 4 }, +#define SHWID 5 + { "width columns", 4 }, +#define SHSHOW 6 + { "showproc program", 4 }, +#define SHNSHOW 7 + { "noshowproc", 3 }, +#define SHHEAD 8 + { "header", 4 }, +#define SHNHEAD 9 + { "noheader", 3 }, +#define SHHELP 10 + { "help", 4 }, + { NULL, 0 } +}; + + +void +showcmd (char **args) +{ + int headersw = 1, nshow = 0, msgp = 0, vecp = 1; + int mhl = 0, seqnum = -1, mode = 0, i, msgnum; + char *cp, *proc = showproc, buf[BUFSIZ]; + char *msgs[MAXARGS], *vec[MAXARGS]; + + if (!strcasecmp (cmd_name, "next")) + mode = 1; + else + if (!strcasecmp (cmd_name, "prev")) + mode = -1; + while ((cp = *args++)) { + if (*cp == '-') + switch (i = smatch (++cp, showswit)) { + case AMBIGSW: + ambigsw (cp, showswit); + return; + case UNKWNSW: + case SHNPROG: + vec[vecp++] = --cp; + continue; + case SHHELP: + snprintf (buf, sizeof(buf), "%s %s[switches] [switches for showproc]", + cmd_name, mode ? NULL : "[msgs] "); + print_help (buf, showswit, 1); + return; + + case SHFORM: + case SHPROG: + case SHLEN: + case SHWID: + vec[vecp++] = --cp; + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + vec[vecp++] = cp; + continue; + case SHHEAD: + headersw++; + continue; + case SHNHEAD: + headersw = 0; + continue; + case SHSHOW: + if (!(proc = *args++) || *proc == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + nshow = 0; + continue; + case SHNSHOW: + nshow++; + continue; + + case SHDRAFT: + advise (NULL, "sorry, -%s not allowed!", showswit[i].sw); + return; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + if (mode) { + fprintf (stderr, + "usage: %s [switches] [switches for showproc]\n", + cmd_name); + return; + } + else + msgs[msgp++] = cp; + } + vec[vecp] = NULL; + + if (!msgp) + msgs[msgp++] = mode > 0 ? "next" : mode < 0 ? "prev" : "cur"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + if (!nshow && !getenv ("NOMHNPROC")) + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum) && is_nontext (msgnum)) { + proc = showmimeproc; + vec[vecp++] = "-show"; + vec[vecp++] = "-file"; + vec[vecp] = NULL; + goto finish; + } + + if (nshow) + proc = catproc; + else + if (strcmp (showproc, "mhl") == 0) { + proc = mhlproc; + mhl++; + } + +finish: ; + seqnum = seq_getnum (mp, "unseen"); + vec[0] = r1bindex (proc, '/'); + if (mhl) { + msgp = vecp; + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) { + vec[vecp++] = getcpy (m_name (msgnum)); + if (seqnum != -1) + seq_delmsg (mp, "unseen", msgnum); + } + vec[vecp] = NULL; + if (mp->numsel == 1 && headersw) + show (mp->lowsel); + mhlsbr (vecp, vec, mhl_action); + m_eomsbr ((int (*)()) 0); + while (msgp < vecp) + free (vec[msgp++]); + } else { + interrupted = 0; + for (msgnum = mp->lowsel; + msgnum <= mp->hghsel && !interrupted; + msgnum++) + if (is_selected (mp, msgnum)) { + switch (ask (msgnum)) { + case NOTOK: /* QUIT */ + break; + + case OK: /* INTR */ + continue; + + default: + if (mp->numsel == 1 && headersw) + show (msgnum); + if (nshow) + copy_message (msgnum, stdout); + else + process (msgnum, proc, vecp, vec); + + if (seqnum != -1) + seq_delmsg (mp, "unseen", msgnum); + continue; + } + break; + } + } + + seq_setcur (mp, mp->hghsel); +} + + +static void +show (int msgnum) +{ + if (Msgs[msgnum].m_bboard_id == 0) + readid (msgnum); + + printf ("(Message %d", msgnum); + if (Msgs[msgnum].m_bboard_id > 0) + printf (", %s: %d", BBoard_ID, Msgs[msgnum].m_bboard_id); + printf (")\n"); +} + + + +static int +eom_action (int c) +{ + return (ftell (mhlfp) >= Msgs[mhlnum].m_stop); +} + + +static FILE * +mhl_action (char *name) +{ + int msgnum; + + if ((msgnum = m_atoi (name)) < mp->lowmsg + || msgnum > mp->hghmsg + || !does_exist (mp, msgnum)) + return NULL; + mhlnum = msgnum; + + mhlfp = msh_ready (msgnum, 1); + if (!fmsh) + m_eomsbr (eom_action); + + return mhlfp; +} + + + +static int +ask (int msgnum) +{ + char buf[BUFSIZ]; + + if (mp->numsel == 1 || !interactive || redirected) + return DONE; + + if (SOprintf ("Press to list \"%d\"...", msgnum)) { + if (mp->lowsel != msgnum) + printf ("\n\n\n"); + printf ("Press to list \"%d\"...", msgnum); + } + fflush (stdout); + buf[0] = 0; + +#ifndef BSD42 + read (fileno (stdout), buf, sizeof buf); +#else /* BSD42 */ + switch (setjmp (sigenv)) { + case OK: + should_intr = 1; + read (fileno (stdout), buf, sizeof buf);/* fall... */ + + default: + should_intr = 0; + break; + } +#endif /* BSD42 */ + + if (strchr(buf, '\n') == NULL) + putchar ('\n'); + + if (told_to_quit) { + told_to_quit = interrupted = 0; + return NOTOK; + } + if (interrupted) { + interrupted = 0; + return OK; + } + + return DONE; +} + + +#include + +static int +is_nontext (int msgnum) +{ + int result, state; + char *bp, *cp, *dp; + char buf[BUFSIZ], name[NAMESZ]; + FILE *fp; + + if (Msgs[msgnum].m_flags & MHNCHK) + return (Msgs[msgnum].m_flags & MHNYES); + Msgs[msgnum].m_flags |= MHNCHK; + + fp = msh_ready (msgnum, 1); + + for (state = FLD;;) + switch (state = m_getfld (state, name, buf, sizeof buf, fp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + /* + * Check Content-Type field + */ + if (!strcasecmp (name, TYPE_FIELD)) { + int passno; + char c; + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof buf, fp); + cp = add (buf, cp); + } + bp = cp; + passno = 1; + +again: + for (; isspace (*bp); bp++) + continue; + if (*bp == '(') { + int i; + + for (bp++, i = 0;;) { + switch (*bp++) { + case '\0': +invalid: + result = 0; + goto out; + case '\\': + if (*bp++ == '\0') + goto invalid; + continue; + case '(': + i++; + /* and fall... */ + default: + continue; + case ')': + if (--i < 0) + break; + continue; + } + break; + } + } + if (passno == 2) { + if (*bp != '/') + goto invalid; + bp++; + passno = 3; + goto again; + } + for (dp = bp; istoken (*dp); dp++) + continue; + c = *dp; + *dp = '\0'; + if (!*bp) + goto invalid; + if (passno > 1) { + if ((result = (strcasecmp (bp, "plain") != 0))) + goto out; + *dp = c; + for (dp++; isspace (*dp); dp++) + continue; + if (*dp) { + if ((result = !uprf (dp, "charset"))) + goto out; + dp += sizeof "charset" - 1; + while (isspace (*dp)) + dp++; + if (*dp++ != '=') + goto invalid; + while (isspace (*dp)) + dp++; + if (*dp == '"') { + if ((bp = strchr(++dp, '"'))) + *bp = '\0'; + } else { + for (bp = dp; *bp; bp++) + if (isspace (*bp)) { + *bp = '\0'; + break; + } + } + } else { + /* Default character set */ + dp = "US-ASCII"; + } + /* Check the character set */ + result = !check_charset (dp, strlen (dp)); + } else { + if (!(result = (strcasecmp (bp, "text") != 0))) { + *dp = c; + bp = dp; + passno = 2; + goto again; + } + } +out: + free (cp); + if (result) { + Msgs[msgnum].m_flags |= MHNYES; + return result; + } + break; + } + + /* + * Check Content-Transfer-Encoding field + */ + if (!strcasecmp (name, ENCODING_FIELD)) { + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof buf, fp); + cp = add (buf, cp); + } + for (bp = cp; isspace (*bp); bp++) + continue; + for (dp = bp; istoken (*dp); dp++) + continue; + *dp = '\0'; + result = (strcasecmp (bp, "7bit") + && strcasecmp (bp, "8bit") + && strcasecmp (bp, "binary")); + + free (cp); + if (result) { + Msgs[msgnum].m_flags |= MHNYES; + return result; + } + break; + } + + /* + * Just skip the rest of this header + * field and go to next one. + */ + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), fp); + break; + + /* + * We've passed the message header, + * so message is just text. + */ + default: + return 0; + } +} + + +static struct swit sortswit[] = { +#define SODATE 0 + { "datefield field", 0 }, +#define SOSUBJ 1 + { "textfield field", 0 }, +#define SONSUBJ 2 + { "notextfield", 0 }, +#define SOLIMT 3 + { "limit days", 0 }, +#define SONLIMT 4 + { "nolimit", 0 }, +#define SOVERB 5 + { "verbose", 0 }, +#define SONVERB 6 + { "noverbose", 0 }, +#define SOHELP 7 + { "help", 4 }, + { NULL, 0 } +}; + + +void +sortcmd (char **args) +{ + int msgp = 0, msgnum; + char *cp, *datesw = NULL, *subjsw = NULL; + char buf[BUFSIZ], *msgs[MAXARGS]; + struct tws tb; + + if (fmsh) { + forkcmd (args, cmd_name); + return; + } + + while ((cp = *args++)) { + if (*cp == '-') + switch (smatch (++cp, sortswit)) { + case AMBIGSW: + ambigsw (cp, sortswit); + return; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + return; + case SOHELP: + snprintf (buf, sizeof(buf), "%s [msgs] [switches]", cmd_name); + print_help (buf, sortswit, 1); + return; + + case SODATE: + if (datesw) { + advise (NULL, "only one date field at a time!"); + return; + } + if (!(datesw = *args++) || *datesw == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + continue; + + case SOSUBJ: + if (subjsw) { + advise (NULL, "only one text field at a time!"); + return; + } + if (!(subjsw = *args++) || *subjsw == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + continue; + case SONSUBJ: + subjsw = (char *)0; + continue; + + case SOLIMT: /* too hard */ + if (!(cp = *args++) || *cp == '-') { + advise (NULL, "missing argument to %s", args[-2]); + return; + } + case SONLIMT: + case SOVERB: /* not implemented */ + case SONVERB: + continue; + } + if (*cp == '+' || *cp == '@') { + advise (NULL, "sorry, no folders allowed!"); + return; + } + else + msgs[msgp++] = cp; + } + + if (!msgp) + msgs[msgp++] = "all"; + if (!datesw) + datesw = "Date"; + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + return; + seq_setprev (mp); + + twscopy (&tb, dlocaltimenow ()); + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (Msgs[msgnum].m_scanl) { + free (Msgs[msgnum].m_scanl); + Msgs[msgnum].m_scanl = NULL; + } + if (is_selected (mp, msgnum)) { + if (get_fields (datesw, subjsw, msgnum, &Msgs[msgnum])) + twscopy (&Msgs[msgnum].m_tb, + msgnum != mp->lowsel ? &Msgs[msgnum - 1].m_tb : &tb); + } + else /* m_scaln is already NULL */ + twscopy (&Msgs[msgnum].m_tb, &tb); + Msgs[msgnum].m_stats = mp->msgstats[msgnum - mp->lowoff]; + if (mp->curmsg == msgnum) + Msgs[msgnum].m_stats |= CUR; + } + + qsort ((char *) &Msgs[mp->lowsel], mp->hghsel - mp->lowsel + 1, + sizeof(struct Msg), (qsort_comp) (subjsw ? subsort : msgsort)); + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (subjsw && Msgs[msgnum].m_scanl) { + free (Msgs[msgnum].m_scanl); /* from subjsort */ + Msgs[msgnum].m_scanl = NULL; + } + mp->msgstats[msgnum - mp->lowoff] = Msgs[msgnum].m_stats & ~CUR; + if (Msgs[msgnum].m_stats & CUR) + seq_setcur (mp, msgnum); + } + + mp->msgflags |= MODIFIED; + modified++; +} + + +/* + * get_fields - parse message, and get date and subject if needed. + * We'll use the msgp->m_tb tws struct for the date, and overload + * the msgp->m_scanl field with our subject string. + */ +static int +get_fields (char *datesw, char *subjsw, int msgnum, struct Msg *msgp) +{ + int state, gotdate = 0; + char *bp, buf[BUFSIZ], name[NAMESZ]; + struct tws *tw = (struct tws *) 0; + register FILE *zp; + + zp = msh_ready (msgnum, 0); + for (state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof buf, zp)) { + case FLD: + case FLDEOF: + case FLDPLUS: + if (!strcasecmp (name, datesw)) { + bp = getcpy (buf); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof buf, zp); + bp = add (buf, bp); + } + if ((tw = dparsetime (bp)) == NULL) + admonish (NULL, + "unable to parse %s field in message %d", + datesw, msgnum); + else + twscopy (&(msgp->m_tb), tw); + free (bp); + if (!subjsw) /* not using this, or already done */ + break; /* all done! */ + gotdate++; + } + else if (subjsw && !strcasecmp(name, subjsw)) { + bp = getcpy (buf); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof buf, zp); + bp = add (buf, bp); + } + msgp->m_scanl = sosmash(subjsw, bp); + if (gotdate) + break; /* date done so we're done */ + else + subjsw = (char *)0;/* subject done, need date */ + } else { + while (state == FLDPLUS) /* flush this one */ + state = m_getfld (state, name, buf, sizeof buf, zp); + } + continue; + + case BODY: + case BODYEOF: + case FILEEOF: + break; + + case LENERR: + case FMTERR: + admonish (NULL, "format error in message %d", msgnum); + if (msgp->m_scanl) { /* this might need free'd */ + free (msgp->m_scanl); /* probably can't use subj anyway */ + msgp->m_scanl = NULL; + } + return NOTOK; + + default: + adios (NULL, "internal error -- you lose"); + } + break; + } + if (tw) + return OK; /* not an error if subj not found */ + + admonish (NULL, "no %s field in message %d", datesw, msgnum); + return NOTOK; /* NOTOK means use some other date */ +} + + +/* + * sort routines + */ + +static int +msgsort (struct Msg *a, struct Msg *b) +{ + return twsort (&a->m_tb, &b->m_tb); +} + + +static int +subsort (struct Msg *a, struct Msg *b) +{ + register int i; + + if (a->m_scanl && b->m_scanl) + if ((i = strcmp (a->m_scanl, b->m_scanl))) + return (i); + + return twsort (&a->m_tb, &b->m_tb); +} + + +/* + * try to make the subject "canonical": delete leading "re:", everything + * but letters & smash letters to lower case. + */ +static char * +sosmash (char *subj, char *s) +{ + register char *cp, *dp, c; + + if (s) { + cp = s; + dp = s; /* dst pointer */ + if (!strcasecmp (subj, "subject")) + while ((c = *cp)) { + if (! isspace(c)) { + if(uprf(cp, "re:")) + cp += 2; + else { + if (isalnum(c)) + *dp++ = isupper(c) ? tolower(c) : c; + break; + } + } + cp++; + } + while ((c = *cp++)) { + if (isalnum(c)) + *dp++ = isupper(c) ? tolower(c) : c; + + } + *dp = '\0'; + } + return s; +} + + +static int +process (int msgnum, char *proc, int vecp, char **vec) +{ + int child_id, status; + char tmpfil[80]; + FILE *out; + + if (fmsh) { + strncpy (tmpfil, m_name (msgnum), sizeof(tmpfil)); + context_del (pfolder); + context_replace (pfolder, fmsh);/* update current folder */ + seq_save (mp); + context_save (); /* save the context file */ + goto ready; + } + + strncpy (tmpfil, m_scratch ("", invo_name), sizeof(tmpfil)); + if ((out = fopen (tmpfil, "w")) == NULL) { + int olderr; + extern int errno; + char newfil[80]; + + olderr = errno; + strncpy (newfil, m_tmpfil (invo_name), sizeof(newfil)); + if ((out = fopen (newfil, "w")) == NULL) { + errno = olderr; + advise (tmpfil, "unable to create temporary file"); + return NOTOK; + } else { + strncpy (tmpfil, newfil, sizeof(tmpfil)); + } + } + copy_message (msgnum, out); + fclose (out); + +ready: ; + fflush (stdout); + switch (child_id = fork ()) { + case NOTOK: + advise ("fork", "unable to"); + status = NOTOK; + break; + + case OK: + closefds (3); + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + + vec[vecp++] = tmpfil; + vec[vecp] = NULL; + + execvp (proc, vec); + fprintf (stderr, "unable to exec "); + perror (proc); + _exit (1); + + default: + status = pidXwait (child_id, NULL); + break; + } + + if (!fmsh) + unlink (tmpfil); + return status; +} + + +static void +copy_message (int msgnum, FILE *out) +{ + long pos; + static char buffer[BUFSIZ]; + register FILE *zp; + + zp = msh_ready (msgnum, 1); + if (fmsh) { + while (fgets (buffer, sizeof buffer, zp) != NULL) { + fputs (buffer, out); + if (interrupted && out == stdout) + break; + } + } + else { + pos = ftell (zp); + while (fgets (buffer, sizeof buffer, zp) != NULL + && pos < Msgs[msgnum].m_stop) { + fputs (buffer, out); + pos += (long) strlen (buffer); + if (interrupted && out == stdout) + break; + } + } +} + + +static void +copy_digest (int msgnum, FILE *out) +{ + char c; + long pos; + static char buffer[BUFSIZ]; + register FILE *zp; + + c = '\n'; + zp = msh_ready (msgnum, 1); + if (!fmsh) + pos = ftell (zp); + while (fgets (buffer, sizeof buffer, zp) != NULL + && !fmsh && pos < Msgs[msgnum].m_stop) { + if (c == '\n' && *buffer == '-') + fputc (' ', out); + fputs (buffer, out); + c = buffer[strlen (buffer) - 1]; + if (!fmsh) + pos += (long) strlen (buffer); + if (interrupted && out == stdout) + break; + } +} diff --git a/uip/packf.c b/uip/packf.c new file mode 100644 index 0000000..ec2f975 --- /dev/null +++ b/uip/packf.c @@ -0,0 +1,208 @@ + +/* + * packf.c -- pack a nmh folder into a file + * + * $Id$ + */ + +#include +#include +#include +#include + +/* + * We allocate space for messages (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define FILESW 0 + { "file name", 0 }, +#define MBOXSW 1 + { "mbox", 0 }, +#define MMDFSW 2 + { "mmdf", 0 }, +#define VERSIONSW 3 + { "version", 0 }, +#define HELPSW 4 + { "help", 4 }, + { NULL, 0 } +}; + +extern int errno; + +static int md = NOTOK; +static int mbx_style = MBOX_FORMAT; +static int mapping = 0; + +char *file = NULL; + + +int +main (int argc, char **argv) +{ + int nummsgs, maxmsgs, fd, msgnum; + char *cp, *maildir, *msgnam, *folder = NULL, buf[BUFSIZ]; + char **argp, **arguments, **msgs; + struct msgs *mp; + struct stat st; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* Allocate the initial space to record message + * names and ranges. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case FILESW: + if (file) + adios (NULL, "only one file at a time!"); + if (!(file = *argp++) || *file == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case MBOXSW: + mbx_style = MBOX_FORMAT; + mapping = 0; + continue; + case MMDFSW: + mbx_style = MMDF_FORMAT; + mapping = 1; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message name/ranges. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + if (!file) + file = "./msgbox"; + file = path (file, TFILE); + + /* + * Check if file to be created (or appended to) + * exists. If not, ask for confirmation. + */ + if (stat (file, &st) == NOTOK) { + if (errno != ENOENT) + adios (file, "error on file"); + cp = concat ("Create file \"", file, "\"? ", NULL); + if (!getanswer (cp)) + done (1); + free (cp); + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* default is to pack whole folder */ + if (!nummsgs) + msgs[nummsgs++] = "all"; + + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to "); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + /* open and lock new maildrop file */ + if ((md = mbx_open(file, mbx_style, getuid(), getgid(), m_gmprot())) == NOTOK) + adios (file, "unable to open"); + + /* copy all the SELECTED messages to the file */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected(mp, msgnum)) { + if ((fd = open (msgnam = m_name (msgnum), O_RDONLY)) == NOTOK) { + admonish (msgnam, "unable to read message"); + break; + } + + if (mbx_copy (file, mbx_style, md, fd, mapping, NULL, 1) == NOTOK) + adios (file, "error writing to file"); + + close (fd); + } + + /* close and unlock maildrop file */ + mbx_close (file, md); + + context_replace (pfolder, folder); /* update current folder */ + if (mp->hghsel != mp->curmsg) + seq_setcur (mp, mp->lowsel); + seq_save (mp); + context_save (); /* save the context file */ + folder_free (mp); /* free folder/message structure */ + done (0); +} + +void +done (int status) +{ + mbx_close (file, md); + exit (status); +} diff --git a/uip/pick.c b/uip/pick.c new file mode 100644 index 0000000..d2f72ef --- /dev/null +++ b/uip/pick.c @@ -0,0 +1,316 @@ + +/* + * pick.c -- search for messages by content + * + * $Id$ + */ + +#include +#include +#include + +/* + * We allocate space for messages (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define ANDSW 0 + { "and", 0 }, +#define ORSW 1 + { "or", 0 }, +#define NOTSW 2 + { "not", 0 }, +#define LBRSW 3 + { "lbrace", 0 }, +#define RBRSW 4 + { "rbrace", 0 }, +#define CCSW 5 + { "cc pattern", 0 }, +#define DATESW 6 + { "date pattern", 0 }, +#define FROMSW 7 + { "from pattern", 0 }, +#define SRCHSW 8 + { "search pattern", 0 }, +#define SUBJSW 9 + { "subject pattern", 0 }, +#define TOSW 10 + { "to pattern", 0 }, +#define OTHRSW 11 + { "-othercomponent pattern", 0 }, +#define AFTRSW 12 + { "after date", 0 }, +#define BEFRSW 13 + { "before date", 0 }, +#define DATFDSW 14 + { "datefield field", 5 }, +#define SEQSW 15 + { "sequence name", 0 }, +#define PUBLSW 16 + { "public", 0 }, +#define NPUBLSW 17 + { "nopublic", 0 }, +#define ZEROSW 18 + { "zero", 0 }, +#define NZEROSW 19 + { "nozero", 0 }, +#define LISTSW 20 + { "list", 0 }, +#define NLISTSW 21 + { "nolist", 0 }, +#define VERSIONSW 22 + { "version", 0 }, +#define HELPSW 23 + { "help", 4 }, + { NULL, 0 } +}; + +static int listsw = 0; + + +int +main (int argc, char **argv) +{ + int publicsw = -1, zerosw = 1, seqp = 0, vecp = 0; + int nummsgs, maxmsgs, lo, hi, msgnum; + char *maildir, *folder = NULL, buf[100]; + char *cp, **argp, **arguments; + char **msgs, *seqs[NUMATTRS + 1], *vec[MAXARGS]; + struct msgs *mp; + register FILE *fp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + while ((cp = *argp++)) { + if (*cp == '-') { + if (*++cp == '-') { + vec[vecp++] = --cp; + goto pattern; + } + switch (smatch (cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + listsw = 0; /* HACK */ + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case CCSW: + case DATESW: + case FROMSW: + case SUBJSW: + case TOSW: + case DATFDSW: + case AFTRSW: + case BEFRSW: + case SRCHSW: + vec[vecp++] = --cp; + pattern: + if (!(cp = *argp++))/* allow -xyz arguments */ + adios (NULL, "missing argument to %s", argp[-2]); + vec[vecp++] = cp; + continue; + case OTHRSW: + adios (NULL, "internal error!"); + + case ANDSW: + case ORSW: + case NOTSW: + case LBRSW: + case RBRSW: + vec[vecp++] = --cp; + continue; + + case SEQSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + + /* check if too many sequences specified */ + if (seqp >= NUMATTRS) + adios (NULL, "too many sequences (more than %d) specified", NUMATTRS); + seqs[seqp++] = cp; + listsw = 0; + continue; + case PUBLSW: + publicsw = 1; + continue; + case NPUBLSW: + publicsw = 0; + continue; + case ZEROSW: + zerosw++; + continue; + case NZEROSW: + zerosw = 0; + continue; + + case LISTSW: + listsw++; + continue; + case NLISTSW: + listsw = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message name/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + vec[vecp] = NULL; + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* + * If we didn't specify which messages to search, + * then search the whole folder. + */ + if (!nummsgs) + msgs[nummsgs++] = "all"; + + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + /* + * If we aren't saving the results to a sequence, + * we need to list the results. + */ + if (seqp == 0) + listsw++; + + if (publicsw == 1 && is_readonly(mp)) + adios (NULL, "folder %s is read-only, so -public not allowed", folder); + + if (!pcompile (vec, NULL)) + done (1); + + lo = mp->lowsel; + hi = mp->hghsel; + + /* + * Scan through all the SELECTED messages and check for a + * match. If the message does not match, then unselect it. + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) { + if ((fp = fopen (cp = m_name (msgnum), "r")) == NULL) + admonish (cp, "unable to read message"); + if (fp && pmatches (fp, msgnum, 0L, 0L)) { + if (msgnum < lo) + lo = msgnum; + if (msgnum > hi) + hi = msgnum; + } else { + /* if it doesn't match, then unselect it */ + unset_selected (mp, msgnum); + mp->numsel--; + } + if (fp) + fclose (fp); + } + } + + mp->lowsel = lo; + mp->hghsel = hi; + + if (mp->numsel <= 0) + adios (NULL, "no messages match specification"); + + seqs[seqp] = NULL; + + /* + * Add the matching messages to sequences + */ + for (seqp = 0; seqs[seqp]; seqp++) + if (!seq_addsel (mp, seqs[seqp], publicsw, zerosw)) + done (1); + + /* + * Print the name of all the matches + */ + if (listsw) { + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum)) + printf ("%s\n", m_name (msgnum)); + } else { + printf ("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s"); + } + + context_replace (pfolder, folder); /* update current folder */ + seq_save (mp); /* synchronize message sequences */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder/message structure */ + done (0); +} + + +void +done (int status) +{ + if (listsw && status && !isatty (fileno (stdout))) + printf ("0\n"); + exit (status); +} diff --git a/uip/picksbr.c b/uip/picksbr.c new file mode 100644 index 0000000..88463ed --- /dev/null +++ b/uip/picksbr.c @@ -0,0 +1,939 @@ + +/* + * picksbr.c -- routines to help pick along... + * + * $Id$ + */ + +#include +#include +#include + +static struct swit parswit[] = { +#define PRAND 0 + { "and", 0 }, +#define PROR 1 + { "or", 0 }, +#define PRNOT 2 + { "not", 0 }, +#define PRLBR 3 + { "lbrace", 0 }, +#define PRRBR 4 + { "rbrace", 0 }, +#define PRCC 5 + { "cc pattern", 0 }, +#define PRDATE 6 + { "date pattern", 0 }, +#define PRFROM 7 + { "from pattern", 0 }, +#define PRSRCH 8 + { "search pattern", 0 }, +#define PRSUBJ 9 + { "subject pattern", 0 }, +#define PRTO 10 + { "to pattern", 0 }, +#define PROTHR 11 + { "-othercomponent pattern", 15 }, +#define PRAFTR 12 + { "after date", 0 }, +#define PRBEFR 13 + { "before date", 0 }, +#define PRDATF 14 + { "datefield field", 5 }, + { NULL, 0 } +}; + +/* DEFINITIONS FOR PATTERN MATCHING */ + +/* + * We really should be using re_comp() and re_exec() here. Unfortunately, + * pick advertises that lowercase characters matches characters of both + * cases. Since re_exec() doesn't exhibit this behavior, we are stuck + * with this version. Furthermore, we need to be able to save and restore + * the state of the pattern matcher in order to do things "efficiently". + * + * The matching power of this algorithm isn't as powerful as the re_xxx() + * routines (no \(xxx\) and \n constructs). Such is life. + */ + +#define CCHR 2 +#define CDOT 4 +#define CCL 6 +#define NCCL 8 +#define CDOL 10 +#define CEOF 11 + +#define STAR 01 + +#define LBSIZE 1024 +#define ESIZE 256 + + +static char linebuf[LBSIZE + 1]; + +/* the magic array for case-independence */ +static char cc[] = { + 0000,0001,0002,0003,0004,0005,0006,0007, + 0010,0011,0012,0013,0014,0015,0016,0017, + 0020,0021,0022,0023,0024,0025,0026,0027, + 0030,0031,0032,0033,0034,0035,0036,0037, + 0040,0041,0042,0043,0044,0045,0046,0047, + 0050,0051,0052,0053,0054,0055,0056,0057, + 0060,0061,0062,0063,0064,0065,0066,0067, + 0070,0071,0072,0073,0074,0075,0076,0077, + 0100,0141,0142,0143,0144,0145,0146,0147, + 0150,0151,0152,0153,0154,0155,0156,0157, + 0160,0161,0162,0163,0164,0165,0166,0167, + 0170,0171,0172,0133,0134,0135,0136,0137, + 0140,0141,0142,0143,0144,0145,0146,0147, + 0150,0151,0152,0153,0154,0155,0156,0157, + 0160,0161,0162,0163,0164,0165,0166,0167, + 0170,0171,0172,0173,0174,0175,0176,0177, +}; + +/* + * DEFINITIONS FOR NEXUS + */ + +#define nxtarg() (*argp ? *argp++ : NULL) +#define prvarg() argp-- + +#define padvise if (!talked++) advise + +struct nexus { + int (*n_action)(); + + union { + /* for {OR,AND,NOT}action */ + struct { + struct nexus *un_L_child; + struct nexus *un_R_child; + } st1; + + /* for GREPaction */ + struct { + int un_header; + int un_circf; + char un_expbuf[ESIZE]; + char *un_patbuf; + } st2; + + /* for TWSaction */ + struct { + char *un_datef; + int un_after; + struct tws un_tws; + } st3; + } un; +}; + +#define n_L_child un.st1.un_L_child +#define n_R_child un.st1.un_R_child + +#define n_header un.st2.un_header +#define n_circf un.st2.un_circf +#define n_expbuf un.st2.un_expbuf +#define n_patbuf un.st2.un_patbuf + +#define n_datef un.st3.un_datef +#define n_after un.st3.un_after +#define n_tws un.st3.un_tws + +static int talked; +static int pdebug = 0; + +static char *datesw; +static char **argp; + +static struct nexus *head; + +/* + * prototypes for date routines + */ +static struct tws *tws_parse(); +static struct tws *tws_special(); + +/* + * static prototypes + */ +static void PRaction(); +static int gcompile(); +static int advance(); +static int cclass(); +static int tcompile(); + +static struct nexus *parse(); +static struct nexus *exp1(); +static struct nexus *exp2(); +static struct nexus *exp3(); +static struct nexus *newnexus(); + +static int ORaction(); +static int ANDaction(); +static int NOTaction(); +static int GREPaction(); +static int TWSaction(); + + +int +pcompile (char **vec, char *date) +{ + register char *cp; + + if ((cp = getenv ("MHPDEBUG")) && *cp) + pdebug++; + + argp = vec; + if ((datesw = date) == NULL) + datesw = "date"; + talked = 0; + + if ((head = parse ()) == NULL) + return (talked ? 0 : 1); + + if (*argp) { + padvise (NULL, "%s unexpected", *argp); + return 0; + } + + return 1; +} + + +static struct nexus * +parse (void) +{ + register char *cp; + register struct nexus *n, *o; + + if ((n = exp1 ()) == NULL || (cp = nxtarg ()) == NULL) + return n; + + if (*cp != '-') { + padvise (NULL, "%s unexpected", cp); + return NULL; + } + + if (*++cp == '-') + goto header; + switch (smatch (cp, parswit)) { + case AMBIGSW: + ambigsw (cp, parswit); + talked++; + return NULL; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + talked++; + return NULL; + + case PROR: + o = newnexus (ORaction); + o->n_L_child = n; + if ((o->n_R_child = parse ())) + return o; + padvise (NULL, "missing disjunctive"); + return NULL; + +header: ; + default: + prvarg (); + return n; + } +} + +static struct nexus * +exp1 (void) +{ + register char *cp; + register struct nexus *n, *o; + + if ((n = exp2 ()) == NULL || (cp = nxtarg ()) == NULL) + return n; + + if (*cp != '-') { + padvise (NULL, "%s unexpected", cp); + return NULL; + } + + if (*++cp == '-') + goto header; + switch (smatch (cp, parswit)) { + case AMBIGSW: + ambigsw (cp, parswit); + talked++; + return NULL; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + talked++; + return NULL; + + case PRAND: + o = newnexus (ANDaction); + o->n_L_child = n; + if ((o->n_R_child = exp1 ())) + return o; + padvise (NULL, "missing conjunctive"); + return NULL; + +header: ; + default: + prvarg (); + return n; + } +} + + +static struct nexus * +exp2 (void) +{ + register char *cp; + register struct nexus *n; + + if ((cp = nxtarg ()) == NULL) + return NULL; + + if (*cp != '-') { + prvarg (); + return exp3 (); + } + + if (*++cp == '-') + goto header; + switch (smatch (cp, parswit)) { + case AMBIGSW: + ambigsw (cp, parswit); + talked++; + return NULL; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + talked++; + return NULL; + + case PRNOT: + n = newnexus (NOTaction); + if ((n->n_L_child = exp3 ())) + return n; + padvise (NULL, "missing negation"); + return NULL; + +header: ; + default: + prvarg (); + return exp3 (); + } +} + +static struct nexus * +exp3 (void) +{ + int i; + register char *cp, *dp; + char buffer[BUFSIZ], temp[64]; + register struct nexus *n; + + if ((cp = nxtarg ()) == NULL) + return NULL; + + if (*cp != '-') { + padvise (NULL, "%s unexpected", cp); + return NULL; + } + + if (*++cp == '-') { + dp = ++cp; + goto header; + } + switch (i = smatch (cp, parswit)) { + case AMBIGSW: + ambigsw (cp, parswit); + talked++; + return NULL; + case UNKWNSW: + fprintf (stderr, "-%s unknown\n", cp); + talked++; + return NULL; + + case PRLBR: + if ((n = parse ()) == NULL) { + padvise (NULL, "missing group"); + return NULL; + } + if ((cp = nxtarg ()) == NULL) { + padvise (NULL, "missing -rbrace"); + return NULL; + } + if (*cp++ == '-' && smatch (cp, parswit) == PRRBR) + return n; + padvise (NULL, "%s unexpected", --cp); + return NULL; + + default: + prvarg (); + return NULL; + + case PRCC: + case PRDATE: + case PRFROM: + case PRTO: + case PRSUBJ: + strncpy(temp, parswit[i].sw, sizeof(temp)); + temp[sizeof(temp) - 1] = '\0'; + dp = *brkstring (temp, " ", NULL); + header: ; + if (!(cp = nxtarg ())) {/* allow -xyz arguments */ + padvise (NULL, "missing argument to %s", argp[-2]); + return NULL; + } + n = newnexus (GREPaction); + n->n_header = 1; + snprintf (buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp); + dp = buffer; + goto pattern; + + case PRSRCH: + n = newnexus (GREPaction); + n->n_header = 0; + if (!(cp = nxtarg ())) {/* allow -xyz arguments */ + padvise (NULL, "missing argument to %s", argp[-2]); + return NULL; + } + dp = cp; + pattern: ; + if (!gcompile (n, dp)) { + padvise (NULL, "pattern error in %s %s", argp[-2], cp); + return NULL; + } + n->n_patbuf = getcpy (dp); + return n; + + case PROTHR: + padvise (NULL, "internal error!"); + return NULL; + + case PRDATF: + if (!(datesw = nxtarg ()) || *datesw == '-') { + padvise (NULL, "missing argument to %s", argp[-2]); + return NULL; + } + return exp3 (); + + case PRAFTR: + case PRBEFR: + if (!(cp = nxtarg ())) {/* allow -xyz arguments */ + padvise (NULL, "missing argument to %s", argp[-2]); + return NULL; + } + n = newnexus (TWSaction); + n->n_datef = datesw; + if (!tcompile (cp, &n->n_tws, n->n_after = i == PRAFTR)) { + padvise (NULL, "unable to parse %s %s", argp[-2], cp); + return NULL; + } + return n; + } +} + + +static struct nexus * +newnexus (int (*action)()) +{ + register struct nexus *p; + + if ((p = (struct nexus *) calloc ((size_t) 1, sizeof *p)) == NULL) + adios (NULL, "unable to allocate component storage"); + + p->n_action = action; + return p; +} + + +#define args(a) a, fp, msgnum, start, stop +#define params args (n) +#define plist \ + register struct nexus *n; \ + register FILE *fp; \ + int msgnum; \ + long start, \ + stop; + +int +pmatches (FILE *fp, int msgnum, long start, long stop) +{ + if (!head) + return 1; + + if (!talked++ && pdebug) + PRaction (head, 0); + + return (*head->n_action) (args (head)); +} + + +static void +PRaction (struct nexus *n, int level) +{ + register int i; + + for (i = 0; i < level; i++) + fprintf (stderr, "| "); + + if (n->n_action == ORaction) { + fprintf (stderr, "OR\n"); + PRaction (n->n_L_child, level + 1); + PRaction (n->n_R_child, level + 1); + return; + } + if (n->n_action == ANDaction) { + fprintf (stderr, "AND\n"); + PRaction (n->n_L_child, level + 1); + PRaction (n->n_R_child, level + 1); + return; + } + if (n->n_action == NOTaction) { + fprintf (stderr, "NOT\n"); + PRaction (n->n_L_child, level + 1); + return; + } + if (n->n_action == GREPaction) { + fprintf (stderr, "PATTERN(%s) %s\n", + n->n_header ? "header" : "body", n->n_patbuf); + return; + } + if (n->n_action == TWSaction) { + fprintf (stderr, "TEMPORAL(%s) %s: %s\n", + n->n_after ? "after" : "before", n->n_datef, + dasctime (&n->n_tws, TW_NULL)); + return; + } + fprintf (stderr, "UNKNOWN(0x%x)\n", (unsigned int) (*n->n_action)); +} + + +static int +ORaction (params) +plist +{ + if ((*n->n_L_child->n_action) (args (n->n_L_child))) + return 1; + return (*n->n_R_child->n_action) (args (n->n_R_child)); +} + + +static int +ANDaction (params) +plist +{ + if (!(*n->n_L_child->n_action) (args (n->n_L_child))) + return 0; + return (*n->n_R_child->n_action) (args (n->n_R_child)); +} + + +static int +NOTaction (params) +plist +{ + return (!(*n->n_L_child->n_action) (args (n->n_L_child))); +} + + +static int +gcompile (struct nexus *n, char *astr) +{ + register int c; + int cclcnt; + register char *ep, *dp, *sp, *lastep; + + dp = (ep = n->n_expbuf) + sizeof n->n_expbuf; + sp = astr; + if (*sp == '^') { + n->n_circf = 1; + sp++; + } + else + n->n_circf = 0; + for (;;) { + if (ep >= dp) + goto cerror; + if ((c = *sp++) != '*') + lastep = ep; + switch (c) { + case '\0': + *ep++ = CEOF; + return 1; + + case '.': + *ep++ = CDOT; + continue; + + case '*': + if (lastep == 0) + goto defchar; + *lastep |= STAR; + continue; + + case '$': + if (*sp != '\0') + goto defchar; + *ep++ = CDOL; + continue; + + case '[': + *ep++ = CCL; + *ep++ = 0; + cclcnt = 1; + if ((c = *sp++) == '^') { + c = *sp++; + ep[-2] = NCCL; + } + do { + *ep++ = c; + cclcnt++; + if (c == '\0' || ep >= dp) + goto cerror; + } while ((c = *sp++) != ']'); + lastep[1] = cclcnt; + continue; + + case '\\': + if ((c = *sp++) == '\0') + goto cerror; + defchar: + default: + *ep++ = CCHR; + *ep++ = c; + } + } + +cerror: ; + return 0; +} + + +static int +GREPaction (params) +plist +{ + int c, body, lf; + long pos = start; + register char *p1, *p2, *ebp, *cbp; + char ibuf[BUFSIZ]; + + fseek (fp, start, SEEK_SET); + body = 0; + ebp = cbp = ibuf; + for (;;) { + if (body && n->n_header) + return 0; + p1 = linebuf; + p2 = cbp; + lf = 0; + for (;;) { + if (p2 >= ebp) { + if (fgets (ibuf, sizeof ibuf, fp) == NULL + || (stop && pos >= stop)) { + if (lf) + break; + return 0; + } + pos += (long) strlen (ibuf); + p2 = ibuf; + ebp = ibuf + strlen (ibuf); + } + c = *p2++; + if (lf && c != '\n') + if (c != ' ' && c != '\t') { + --p2; + break; + } + else + lf = 0; + if (c == '\n') + if (body) + break; + else { + if (lf) { + body++; + break; + } + lf++; + c = ' '; + } + if (c && p1 < &linebuf[LBSIZE - 1]) + *p1++ = c; + } + + *p1++ = 0; + cbp = p2; + p1 = linebuf; + p2 = n->n_expbuf; + + if (n->n_circf) { + if (advance (p1, p2)) + return 1; + continue; + } + + if (*p2 == CCHR) { + c = p2[1]; + do { + if (*p1 == c || cc[*p1] == c) + if (advance (p1, p2)) + return 1; + } while (*p1++); + continue; + } + + do { + if (advance (p1, p2)) + return 1; + } while (*p1++); + } +} + + +static int +advance (char *alp, char *aep) +{ + register char *lp, *ep, *curlp; + + lp = alp; + ep = aep; + for (;;) + switch (*ep++) { + case CCHR: + if (*ep++ == *lp++ || ep[-1] == cc[lp[-1]]) + continue; + return 0; + + case CDOT: + if (*lp++) + continue; + return 0; + + case CDOL: + if (*lp == 0) + continue; + return 0; + + case CEOF: + return 1; + + case CCL: + if (cclass (ep, *lp++, 1)) { + ep += *ep; + continue; + } + return 0; + + case NCCL: + if (cclass (ep, *lp++, 0)) { + ep += *ep; + continue; + } + return 0; + + case CDOT | STAR: + curlp = lp; + while (*lp++) + continue; + goto star; + + case CCHR | STAR: + curlp = lp; + while (*lp++ == *ep || cc[lp[-1]] == *ep) + continue; + ep++; + goto star; + + case CCL | STAR: + case NCCL | STAR: + curlp = lp; + while (cclass (ep, *lp++, ep[-1] == (CCL | STAR))) + continue; + ep += *ep; + goto star; + + star: + do { + lp--; + if (advance (lp, ep)) + return (1); + } while (lp > curlp); + return 0; + + default: + admonish (NULL, "advance() botch -- you lose big"); + return 0; + } +} + + +static int +cclass (char *aset, int ac, int af) +{ + register int n; + register char c, + *set; + + set = aset; + if ((c = ac) == 0) + return (0); + + n = *set++; + while (--n) + if (*set++ == c) + return (af); + + return (!af); +} + + +static int +tcompile (char *ap, struct tws *tb, int isafter) +{ + register struct tws *tw; + + if ((tw = tws_parse (ap, isafter)) == NULL) + return 0; + + twscopy (tb, tw); + return 1; +} + + +static struct tws * +tws_parse (char *ap, int isafter) +{ + char buffer[BUFSIZ]; + register struct tws *tw, *ts; + + if ((tw = tws_special (ap)) != NULL) { + tw->tw_sec = tw->tw_min = isafter ? 59 : 0; + tw->tw_hour = isafter ? 23 : 0; + return tw; + } + if ((tw = dparsetime (ap)) != NULL) + return tw; + + if ((ts = dlocaltimenow ()) == NULL) + return NULL; + + snprintf (buffer, sizeof(buffer), "%s %s", ap, dtwszone (ts)); + if ((tw = dparsetime (buffer)) != NULL) + return tw; + + snprintf (buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap, + ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone (ts)); + if ((tw = dparsetime (buffer)) != NULL) + return tw; + + snprintf (buffer, sizeof(buffer), "%02d %s %04d %s", + ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap); + if ((tw = dparsetime (buffer)) != NULL) + return tw; + + snprintf (buffer, sizeof(buffer), "%02d %s %04d %s %s", + ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, + ap, dtwszone (ts)); + if ((tw = dparsetime (buffer)) != NULL) + return tw; + + return NULL; +} + + +static struct tws * +tws_special (char *ap) +{ + int i; + time_t clock; + register struct tws *tw; + + time (&clock); + if (!strcasecmp (ap, "today")) + return dlocaltime (&clock); + if (!strcasecmp (ap, "yesterday")) { + clock -= (long) (60 * 60 * 24); + return dlocaltime (&clock); + } + if (!strcasecmp (ap, "tomorrow")) { + clock += (long) (60 * 60 * 24); + return dlocaltime (&clock); + } + + for (i = 0; tw_ldotw[i]; i++) + if (!strcasecmp (ap, tw_ldotw[i])) + break; + if (tw_ldotw[i]) { + if ((tw = dlocaltime (&clock)) == NULL) + return NULL; + if ((i -= tw->tw_wday) > 0) + i -= 7; + } + else + if (*ap != '-') + return NULL; + else /* -ddd days ago */ + i = atoi (ap); /* we should error check this */ + + clock += (long) ((60 * 60 * 24) * i); + return dlocaltime (&clock); +} + + +static int +TWSaction (params) +plist +{ + int state; + register char *bp; + char buf[BUFSIZ], name[NAMESZ]; + register struct tws *tw; + + fseek (fp, start, SEEK_SET); + for (state = FLD, bp = NULL;;) { + switch (state = m_getfld (state, name, buf, sizeof buf, fp)) { + case FLD: + case FLDEOF: + case FLDPLUS: + if (bp != NULL) + free (bp), bp = NULL; + bp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof buf, fp); + bp = add (buf, bp); + } + if (!strcasecmp (name, n->n_datef)) + break; + if (state != FLDEOF) + continue; + + case BODY: + case BODYEOF: + case FILEEOF: + case LENERR: + case FMTERR: + if (state == LENERR || state == FMTERR) + advise (NULL, "format error in message %d", msgnum); + if (bp != NULL) + free (bp); + return 0; + + default: + adios (NULL, "internal error -- you lose"); + } + break; + } + + if ((tw = dparsetime (bp)) == NULL) + advise (NULL, "unable to parse %s field in message %d, matching...", + n->n_datef, msgnum), state = 1; + else + state = n->n_after ? (twsort (tw, &n->n_tws) > 0) + : (twsort (tw, &n->n_tws) < 0); + + if (bp != NULL) + free (bp); + return state; +} diff --git a/uip/popi.c b/uip/popi.c new file mode 100644 index 0000000..59ab61a --- /dev/null +++ b/uip/popi.c @@ -0,0 +1,644 @@ + +/* + * popi.c -- POP initiator for MPOP + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +#ifndef RPOP +# define RPOPminc(a) (a) +#else +# define RPOPminc(a) 0 +#endif + +#ifndef APOP +# define APOPminc(a) (a) +#else +# define APOPminc(a) 0 +#endif + +#ifndef BPOP +# define BPOPminc(a) (a) +#else +# define BPOPminc(a) 0 +#endif + +#ifndef SMTPMTS +# define BULKminc(a) (a) +#else +# define BULKminc(a) 0 +#endif + +static struct swit switches[] = { +#define APOPSW 0 + { "apop", APOPminc (-4) }, +#define NAPOPSW 1 + { "noapop", APOPminc (-6) }, +#define AUTOSW 2 + { "auto", BPOPminc(-4) }, +#define NAUTOSW 3 + { "noauto", BPOPminc(-6) }, +#define BULKSW 4 + { "bulk directory", BULKminc(-4) }, +#define FORMSW 5 + { "form formatfile", 0 }, +#define FMTSW 6 + { "format string", 5 }, +#define HOSTSW 7 + { "host host", 0 }, +#define PROGSW 8 + { "mshproc program", 0 }, +#define RPOPSW 9 + { "rpop", RPOPminc (-4) }, +#define NRPOPSW 10 + { "norpop", RPOPminc (-6) }, +#define USERSW 11 + { "user user", 0 }, +#define WIDTHSW 12 + { "width columns", 0 }, +#define VERSIONSW 13 + { "version", 0 }, +#define HELPSW 14 + { "help", 4 }, + { NULL, 0 } +}; + +static char *bulksw = NULL; +static int snoop = 0; +static int width = 0; +static char mailname[BUFSIZ]; +static char *nfs = NULL; +static struct msgs *mp; + +extern int errno; +extern char response[]; + +/* + * prototypes + */ +int sc_width (void); /* from termsbr.c */ + + +int +main (int argc, char **argv) +{ + int autosw = 1, noisy = 1, rpop; + char *cp, *maildir, *folder = NULL, *form = NULL; + char *format = NULL, *host = NULL, *user = NULL; + char *pass = NULL, buf[BUFSIZ], **argp; + char **arguments; + struct stat st; + + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + if (pophost && *pophost) + host = pophost; + if ((cp = getenv ("MHPOPDEBUG")) && *cp) + snoop++; + + rpop = getuid() && !geteuid(); + + while (cp = *argp++) { + if (*cp == '-') + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case AUTOSW: + autosw = 1; + continue; + case NAUTOSW: + autosw = 0; + continue; + + case BULKSW: + if (!(bulksw = *argp++) || *bulksw == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + width = atoi (cp); + continue; + + case HOSTSW: + if (!(host = *argp++) || *host == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case USERSW: + if (!(user = *argp++) || *user == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case APOPSW: + rpop = -1; + continue; + case RPOPSW: + rpop = 1; + continue; + case NAPOPSW: + case NRPOPSW: + rpop = 0; + continue; + + case PROGSW: + if (!(mshproc = *argp++) || *mshproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } + else + adios (NULL, "usage: %s [+folder] [switches]", invo_name); + } + + if (!host) + adios (NULL, "usage: %s -host \"host\"", invo_name); + +#ifdef SMTPMTS + if (bulksw) + do_bulk (host); +#endif + + if (user == NULL) + user = getusername (); + if (rpop > 0) + pass = getusername (); + else { + setuid (getuid ()); + ruserpass (host, &user, &pass); + } + snprintf (mailname, sizeof(mailname), "PO box for %s@%s", user, host); + + if (pop_init (host, user, pass, snoop, rpop) == NOTOK) + adios (NULL, "%s", response); + if (rpop > 0) + setuid (getuid ()); + + /* get new format string */ + nfs = new_fs (form, format, FORMAT); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!folder) + folder = getfolder (0); + maildir = m_maildir (folder); + + if (stat (maildir, &st) == NOTOK) { + if (errno != ENOENT) + adios (maildir, "error on folder"); + cp = concat ("Create folder \"", maildir, "\"? ", NULL); + if (noisy && !getanswer (cp)) + done (1); + free (cp); + if (!makedir (maildir)) + adios (NULL, "unable to create folder %s", maildir); + } + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + +#ifdef BPOP + if (autosw) + msh (); + else +#endif + + popi(); + pop_quit(); + + context_replace (pfolder, folder); /* update current folder */ + seq_setunseen (mp, 0); /* set the Unseen-Sequence */ + seq_save (mp); + context_save (); /* save the context file */ + done (0); + + /* NOTREACHED */ +} + + +static struct swit popicmds[] = { +#define DELECMD 0 + "dele", 0, +#define LASTCMD 1 + "last", 0, +#define LISTCMD 2 + "list", 0, +#define NOOPCMD 3 + "noop", 0, +#define QUITCMD 4 + "quit", 0, +#define RETRCMD 5 + "retr", 0, +#define RSETCMD 6 + "rset", 0, +#define SCANCMD 7 + "scan", 0, +#define STATCMD 8 + "stat", 0, +#define TOPCMD 9 + "top", 0, +#ifdef BPOP +#define MSHCMD 10 + "msh", 0, +#endif + + NULL, 0 +}; + + +static void +popi (void) +{ + int eof = 0; + + for (;;) { + int i; + register char *cp; + char buffer[BUFSIZ]; + + if (eof) + return; + + printf ("(%s) ", invo_name); + for (cp = buffer; (i = getchar ()) != '\n'; ) { + if (i == EOF) { + putchar ('\n'); + if (cp == buffer) + return; + eof = 1; + break; + } + + if (cp < buffer + sizeof buffer - 2) + *cp++ = i; + } + *cp = '\0'; + if (buffer[0] == '\0') + continue; + if (buffer[0] == '?') { + printf ("commands:\n"); + print_sw (ALL, popicmds, ""); + printf ("type CTRL-D or use \"quit\" to leave %s\n", invo_name); + continue; + } + + if (cp = strchr (buffer, ' ')) + *cp = '\0'; + switch (i = smatch (buffer, popicmds)) { + case AMBIGSW: + ambigsw (buffer, popicmds); + continue; + case UNKWNSW: + printf ("%s unknown -- type \"?\" for help\n", buffer); + continue; + + case QUITCMD: + return; + + case STATCMD: + case DELECMD: + case NOOPCMD: + case LASTCMD: + case RSETCMD: + case TOPCMD: + if (cp) + *cp = ' '; + pop_command ("%s%s", popicmds[i].sw, cp ? cp : ""); + printf ("%s\n", response); + break; + + case LISTCMD: + if (cp) + *cp = ' '; + if (pop_command ("%s%s", popicmds[i].sw, cp ? cp : "") + == OK) { + printf ("%s\n", response); + if (!cp) + for (;;) { + switch (pop_multiline ()) { + case DONE: + strcpy (response, "."); + /* and fall... */ + case NOTOK: + printf ("%s\n", response); + break; + + case OK: + printf ("%s\n", response); + continue; + } + break; + } + } + break; + + case RETRCMD: + if (!cp) { + advise (NULL, "missing argument to %s", buffer); + break; + } + retr_action (NULL, OK); + pop_retr (atoi (++cp), retr_action); + retr_action (NULL, DONE); + printf ("%s\n", response); + break; + + case SCANCMD: + { + char *dp, + *ep, + *fp; + + if (width == 0) + width = sc_width (); + + for (dp = nfs, i = 0; *dp; dp++, i++) + if (*dp == '\\' || *dp == '"' || *dp == '\n') + i++; + i++; + if ((ep = malloc ((unsigned) i)) == NULL) + adios (NULL, "out of memory"); + for (dp = nfs, fp = ep; *dp; dp++) { + if (*dp == '\n') { + *fp++ = '\\', *fp++ = 'n'; + continue; + } + if (*dp == '"' || *dp == '\\') + *fp++ = '\\'; + *fp++ = *dp; + } + *fp = '\0'; + + pop_command ("xtnd scan %d \"%s\"", width, ep); + printf ("%s\n", response); + + free (ep); + } + break; + +#ifdef BPOP + case MSHCMD: + msh (); + break; +#endif + } + } +} + + +static int +retr_action (char *rsp, int flag) +{ + static FILE *fp; + + if (rsp == NULL) { + static int msgnum; + static char *cp; + + if (flag == OK) { + if (!(mp = folder_realloc (mp, mp->lowoff, msgnum = mp->hghmsg + 1))) + adios (NULL, "unable to allocate folder storage"); + + cp = getcpy (m_name (mp->hghmsg + 1)); + if ((fp = fopen (cp, "w+")) == NULL) + adios (cp, "unable to write"); + chmod (cp, m_gmprot ()); + } + else { + struct stat st; + + fflush (fp); + if (fstat (fileno (fp), &st) != NOTOK && st.st_size > 0) { + clear_msg_flags (mp, msgnum); + set_exists (mp, msgnum); + set_unseen (mp, msgnum); + mp->msgflags |= SEQMOD; + + if (ferror (fp)) + advise (cp, "write error on"); + mp->hghmsg = msgnum; + } + else + unlink (cp); + + fclose (fp), fp = NULL; + free (cp), cp = NULL; + } + + return; + } + + fprintf (fp, "%s\n", rsp); +} + + +#ifdef BPOP +static void +msh (void) +{ + int child_id, vecp; + char buf1[BUFSIZ], buf2[BUFSIZ], *vec[9]; + + if (pop_fd (buf1, sizeof(buf1), buf2, sizeof(buf2)) == NOTOK) + adios (NULL, "%s", response); + + vecp = 0; + vec[vecp++] = r1bindex (mshproc, '/'); + + switch (child_id = fork ()) { + case NOTOK: + adios ("fork", "unable to"); + + case OK: + vec[vecp++] = "-popread"; + vec[vecp++] = buf1; + vec[vecp++] = "-popwrite"; + vec[vecp++] = buf2; + vec[vecp++] = "-idname"; + vec[vecp++] = mailname; + vec[vecp++] = mailname; + vec[vecp] = NULL; + execvp (mshproc, vec); + fprintf (stderr, "unable to exec "); + perror (mshproc); + _exit (-1); + + default: + pidXwait (child_id, mshproc); + break; + } +} +#endif + + +#ifdef SMTPMTS +#include +#include + +static int +dselect (struct direct *d) +{ + int i; + + if ((i = strlen (d->d_name)) < sizeof "smtp" + || strncmp (d->d_name, "smtp", sizeof "smtp" - 1)) + return 0; + return ((i -= (sizeof ".bulk" - 1)) > 0 + && !strcmp (d->d_name + i, ".bulk")); +} + + +static int +dcompar (struct direct *d1, struct direct *d2) +{ + struct stat s1, s2; + + if (stat ((*d1)->d_name, &s1) == NOTOK) + return 1; + if (stat ((*d2)->d_name, &s2) == NOTOK) + return -1; + return ((int) (s1.st_mtime - s2.st_mtime)); +} + + +static void +do_bulk (char *host) +{ + register int i; + int n, retval, sm; + struct direct **namelist; + + if (chdir (bulksw) == NOTOK) + adios (bulksw, "unable to change directory to"); + + if ((n = scandir (".", &namelist, dselect, dcompar)) == NOTOK) + adios (bulksw, "unable to scan directory"); + + sm = NOTOK; + for (i = 0; i < n; i++) { + register struct direct *d = namelist[i]; + + if (sm == NOTOK) { + if (rp_isbad (retval = sm_init (NULL, host, 1, 1, snoop))) + adios (NULL, "problem initializing server: %s", + rp_string (retval)); + else + sm = OK; + } + + switch (retval = sm_bulk (d->d_name)) { + default: + if (rp_isbad (retval)) + adios (NULL, "problem delivering msg %s: %s", + d->d_name, rp_string (retval)); + /* else fall... */ + case RP_OK: + case RP_NO: + case RP_NDEL: + advise (NULL, "msg %s: %s", d->d_name, rp_string (retval)); + break; + } + } + + if (sm == OK) { + register int j; + int l, + m; + struct direct **newlist; + + while ((l = scandir (".", &newlist, dselect, dcompar)) > OK) { + m = 0; + + for (j = 0; j < l; j++) { + register struct direct *d = newlist[j]; + + for (i = 0; i < n; i++) + if (strcmp (d->d_name, namelist[i]->d_name) == 0) + break; + if (i >= n) { + switch (retval = sm_bulk (d->d_name)) { + default: + if (rp_isbad (retval)) + adios (NULL, "problem delivering msg %s: %s", + d->d_name, rp_string (retval)); + /* else fall... */ + case RP_OK: + case RP_NO: + case RP_NDEL: + advise (NULL, "msg %s: %s", d->d_name, + rp_string (retval)); + break; + } + + m = 1; + } + } + + for (i = 0; i < n; i++) + free ((char *) namelist[i]); + free ((char *) namelist); + namelist = newlist, n = l; + + if (!m) + break; + newlist = NULL; + } + } + + if (sm == OK && rp_isbad (retval = sm_end (OK))) + adios (NULL, "problem finalizing server: %s", rp_string (retval)); + + for (i = 0; i < n; i++) + free ((char *) namelist[i]); + free ((char *) namelist); + + free ((char *) namelist); + + done (0); +} +#endif diff --git a/uip/popsbr.c b/uip/popsbr.c new file mode 100644 index 0000000..6b026c6 --- /dev/null +++ b/uip/popsbr.c @@ -0,0 +1,677 @@ + +/* + * popsbr.c -- POP client subroutines + * + * $Id$ + */ + +#include + +#if defined(NNTP) && !defined(PSHSBR) +# undef NNTP +#endif + +#ifdef NNTP /* building pshsbr.o from popsbr.c */ +# include +#endif /* NNTP */ + +#if !defined(NNTP) && defined(APOP) +# include +#endif + +#include +#include +#include + +#define TRM "." +#define TRMLEN (sizeof TRM - 1) + +extern int errno; + +static int poprint = 0; +static int pophack = 0; + +char response[BUFSIZ]; + +static FILE *input; +static FILE *output; + +#define targ_t char * + +#if !defined(NNTP) && defined(MPOP) +# define command pop_command +# define multiline pop_multiline +#endif + +#ifdef NNTP +# ifdef BPOP /* stupid */ +static int xtnd_last = -1; +static int xtnd_first = 0; +static char xtnd_name[512]; /* INCREDIBLE HACK!! */ +# endif +#endif /* NNTP */ + +/* + * static prototypes + */ +#if !defined(NNTP) && defined(APOP) +static char *pop_auth (char *, char *); +#endif + +#if defined(NNTP) || !defined(MPOP) +/* otherwise they are not static functions */ +static int command(const char *, ...); +static int multiline(void); +#endif + +static int traverse (int (*)(), const char *, ...); +static int vcommand(const char *, va_list); +static int getline (char *, int, FILE *); +static int putline (char *, FILE *); + + +#if !defined(NNTP) && defined(APOP) +static char * +pop_auth (char *user, char *pass) +{ + int len, buflen; + char *cp, *lp; + unsigned char *dp, *ep, digest[16]; + MD5_CTX mdContext; + static char buffer[BUFSIZ]; + + if ((cp = strchr (response, '<')) == NULL + || (lp = strchr (cp, '>')) == NULL) { + snprintf (buffer, sizeof(buffer), "APOP not available: %s", response); + strncpy (response, buffer, sizeof(response)); + return NULL; + } + + *++lp = NULL; + snprintf (buffer, sizeof(buffer), "%s%s", cp, pass); + + MD5Init (&mdContext); + MD5Update (&mdContext, (unsigned char *) buffer, + (unsigned int) strlen (buffer)); + MD5Final (digest, &mdContext); + + cp = buffer; + buflen = sizeof(buffer); + + snprintf (cp, buflen, "%s ", user); + len = strlen (cp); + cp += len; + buflen -= len; + + for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]); dp < ep; ) { + snprintf (cp, buflen, "%02x", *dp++ & 0xff); + cp += 2; + buflen -= 2; + } + *cp = NULL; + + return buffer; +} +#endif /* !NNTP && APOP */ + + +int +pop_init (char *host, char *user, char *pass, int snoop, int rpop) +{ + int fd1, fd2; + char buffer[BUFSIZ]; + +#ifdef APOP + int apop; + + if ((apop = rpop) < 0) + rpop = 0; +#endif + +#ifndef NNTP +# ifndef KPOP + if ((fd1 = client (host, "tcp", POPSERVICE, rpop, response, sizeof(response))) == NOTOK) +# else /* KPOP */ + snprintf (buffer, sizeof(buffer), "%s/%s", KPOP_PRINCIPAL, POPSERVICE); + if ((fd1 = client (host, "tcp", buffer, rpop, response, sizeof(response))) == NOTOK) +# endif +#else /* NNTP */ + if ((fd1 = client (host, "tcp", "nntp", rpop, response, sizeof(response))) == NOTOK) +#endif + return NOTOK; + + if ((fd2 = dup (fd1)) == NOTOK) { + char *s; + + if ((s = strerror(errno))) + snprintf (response, sizeof(response), + "unable to dup connection descriptor: %s", s); + else + snprintf (response, sizeof(response), + "unable to dup connection descriptor: unknown error"); + close (fd1); + return NOTOK; + } +#ifndef NNTP + if (pop_set (fd1, fd2, snoop) == NOTOK) +#else /* NNTP */ + if (pop_set (fd1, fd2, snoop, (char *)0) == NOTOK) +#endif /* NNTP */ + return NOTOK; + + SIGNAL (SIGPIPE, SIG_IGN); + + switch (getline (response, sizeof response, input)) { + case OK: + if (poprint) + fprintf (stderr, "<--- %s\n", response); +#ifndef NNTP + if (*response == '+') { +# ifndef KPOP +# ifdef APOP + if (apop < 0) { + char *cp = pop_auth (user, pass); + + if (cp && command ("APOP %s", cp) != NOTOK) + return OK; + } + else +# endif /* APOP */ + if (command ("USER %s", user) != NOTOK + && command ("%s %s", rpop ? "RPOP" : (pophack++, "PASS"), + pass) != NOTOK) + return OK; +# else /* KPOP */ + if (command ("USER %s", user) != NOTOK + && command ("PASS %s", pass) != NOTOK) + return OK; +# endif + } +#else /* NNTP */ + if (*response < CHAR_ERR) { + command ("MODE READER"); + return OK; + } +#endif + strncpy (buffer, response, sizeof(buffer)); + command ("QUIT"); + strncpy (response, buffer, sizeof(response)); + /* and fall */ + + case NOTOK: + case DONE: + if (poprint) + fprintf (stderr, "%s\n", response); + fclose (input); + fclose (output); + return NOTOK; + } + + return NOTOK; /* NOTREACHED */ +} + +#ifdef NNTP +int +pop_set (int in, int out, int snoop, char *myname) +#else +int +pop_set (int in, int out, int snoop) +#endif +{ + +#ifdef NNTP + if (myname && *myname) { + /* interface from bbc to msh */ + strncpy (xtnd_name, myname, sizeof(xtnd_name)); + } +#endif /* NNTP */ + + if ((input = fdopen (in, "r")) == NULL + || (output = fdopen (out, "w")) == NULL) { + strncpy (response, "fdopen failed on connection descriptor", sizeof(response)); + if (input) + fclose (input); + else + close (in); + close (out); + return NOTOK; + } + + poprint = snoop; + + return OK; +} + + +int +pop_fd (char *in, int inlen, char *out, int outlen) +{ + snprintf (in, inlen, "%d", fileno (input)); + snprintf (out, outlen, "%d", fileno (output)); + return OK; +} + + +/* + * Find out number of messages available + * and their total size. + */ + +int +pop_stat (int *nmsgs, int *nbytes) +{ +#ifdef NNTP + char **ap; +#endif /* NNTP */ + +#ifndef NNTP + if (command ("STAT") == NOTOK) + return NOTOK; + + *nmsgs = *nbytes = 0; + sscanf (response, "+OK %d %d", nmsgs, nbytes); + +#else /* NNTP */ + if (xtnd_last < 0) { /* in msh, xtnd_name is set from myname */ + if (command("GROUP %s", xtnd_name) == NOTOK) + return NOTOK; + + ap = brkstring (response, " ", "\n"); /* "211 nart first last ggg" */ + xtnd_first = atoi (ap[2]); + xtnd_last = atoi (ap[3]); + } + + /* nmsgs is not the real nart, but an incredible simuation */ + if (xtnd_last > 0) + *nmsgs = xtnd_last - xtnd_first + 1; /* because of holes... */ + else + *nmsgs = 0; + *nbytes = xtnd_first; /* for subtracting offset in msh() */ +#endif /* NNTP */ + + return OK; +} + +#ifdef NNTP +int +pop_exists (int (*action)()) +{ +#ifdef XMSGS /* hacked into NNTP 1.5 */ + if (traverse (action, "XMSGS %d-%d", (targ_t) xtnd_first, (targ_t) xtnd_last) == OK) + return OK; +#endif + /* provided by INN 1.4 */ + if (traverse (action, "LISTGROUP") == OK) + return OK; + return traverse (action, "XHDR NONAME %d-%d", (targ_t) xtnd_first, (targ_t) xtnd_last); +} +#endif /* NNTP */ + + +#ifdef BPOP +int +pop_list (int msgno, int *nmsgs, int *msgs, int *bytes, int *ids) +#else +int +pop_list (int msgno, int *nmsgs, int *msgs, int *bytes) +#endif +{ + int i; +#ifndef BPOP + int *ids = NULL; +#endif + + if (msgno) { +#ifndef NNTP + if (command ("LIST %d", msgno) == NOTOK) + return NOTOK; + *msgs = *bytes = 0; + if (ids) { + *ids = 0; + sscanf (response, "+OK %d %d %d", msgs, bytes, ids); + } + else + sscanf (response, "+OK %d %d", msgs, bytes); +#else /* NNTP */ + *msgs = *bytes = 0; + if (command ("STAT %d", msgno) == NOTOK) + return NOTOK; + if (ids) { + *ids = msgno; + } +#endif /* NNTP */ + return OK; + } + +#ifndef NNTP + if (command ("LIST") == NOTOK) + return NOTOK; + + for (i = 0; i < *nmsgs; i++) + switch (multiline ()) { + case NOTOK: + return NOTOK; + case DONE: + *nmsgs = ++i; + return OK; + case OK: + *msgs = *bytes = 0; + if (ids) { + *ids = 0; + sscanf (response, "%d %d %d", + msgs++, bytes++, ids++); + } + else + sscanf (response, "%d %d", msgs++, bytes++); + break; + } + for (;;) + switch (multiline ()) { + case NOTOK: + return NOTOK; + case DONE: + return OK; + case OK: + break; + } +#else /* NNTP */ + return NOTOK; +#endif /* NNTP */ +} + + +int +pop_retr (int msgno, int (*action)()) +{ +#ifndef NNTP + return traverse (action, "RETR %d", (targ_t) msgno); +#else /* NNTP */ + return traverse (action, "ARTICLE %d", (targ_t) msgno); +#endif /* NNTP */ +} + + +static int +traverse (int (*action)(), const char *fmt, ...) +{ + int result; + va_list ap; + char buffer[sizeof(response)]; + + va_start(ap, fmt); + result = vcommand (fmt, ap); + va_end(ap); + + if (result == NOTOK) + return NOTOK; + strncpy (buffer, response, sizeof(buffer)); + + for (;;) + switch (multiline ()) { + case NOTOK: + return NOTOK; + + case DONE: + strncpy (response, buffer, sizeof(response)); + return OK; + + case OK: + (*action) (response); + break; + } +} + + +int +pop_dele (int msgno) +{ + return command ("DELE %d", msgno); +} + + +int +pop_noop (void) +{ + return command ("NOOP"); +} + + +#if defined(MPOP) && !defined(NNTP) +int +pop_last (void) +{ + return command ("LAST"); +} +#endif + + +int +pop_rset (void) +{ + return command ("RSET"); +} + + +int +pop_top (int msgno, int lines, int (*action)()) +{ +#ifndef NNTP + return traverse (action, "TOP %d %d", (targ_t) msgno, (targ_t) lines); +#else /* NNTP */ + return traverse (action, "HEAD %d", (targ_t) msgno); +#endif /* NNTP */ +} + + +#ifdef BPOP +int +pop_xtnd (int (*action)(), char *fmt, ...) +{ + int result; + va_list ap; + char buffer[BUFSIZ]; + +#ifdef NNTP + char **ap; +#endif + + va_start(ap, fmt); +#ifndef NNTP + /* needs to be fixed... va_end needs to be added */ + snprintf (buffer, sizeof(buffer), "XTND %s", fmt); + result = traverse (action, buffer, a, b, c, d); + va_end(ap); + return result; +#else /* NNTP */ + snprintf (buffer, sizeof(buffer), fmt, a, b, c, d); + ap = brkstring (buffer, " ", "\n"); /* a hack, i know... */ + + if (!strcasecmp(ap[0], "x-bboards")) { /* XTND "X-BBOARDS group */ + /* most of these parameters are meaningless under NNTP. + * bbc.c was modified to set AKA and LEADERS as appropriate, + * the rest are left blank. + */ + return OK; + } + if (!strcasecmp (ap[0], "archive") && ap[1]) { + snprintf (xtnd_name, sizeof(xtnd_name), "%s", ap[1]); /* save the name */ + xtnd_last = 0; + xtnd_first = 1; /* setup to fail in pop_stat */ + return OK; + } + if (!strcasecmp (ap[0], "bboards")) { + + if (ap[1]) { /* XTND "BBOARDS group" */ + snprintf (xtnd_name, sizeof(xtnd_name), "%s", ap[1]); /* save the name */ + if (command("GROUP %s", xtnd_name) == NOTOK) + return NOTOK; + + /* action must ignore extra args */ + strncpy (buffer, response, sizeof(buffer)); + ap = brkstring (response, " ", "\n");/* "211 nart first last g" */ + xtnd_first = atoi (ap[2]); + xtnd_last = atoi (ap[3]); + + (*action) (buffer); + return OK; + + } else { /* XTND "BBOARDS" */ + return traverse (action, "LIST", a, b, c, d); + } + } + return NOTOK; /* unknown XTND command */ +#endif /* NNTP */ +} +#endif BPOP + + +int +pop_quit (void) +{ + int i; + + i = command ("QUIT"); + pop_done (); + + return i; +} + + +int +pop_done (void) +{ + fclose (input); + fclose (output); + + return OK; +} + + +#if !defined(MPOP) || defined(NNTP) +static +#endif +int +command(const char *fmt, ...) +{ + va_list ap; + int result; + + va_start(ap, fmt); + result = vcommand(fmt, ap); + va_end(ap); + + return result; +} + + +static int +vcommand (const char *fmt, va_list ap) +{ + char *cp, buffer[BUFSIZ]; + + vsnprintf (buffer, sizeof(buffer), fmt, ap); + if (poprint) + if (pophack) { + if ((cp = strchr (buffer, ' '))) + *cp = 0; + fprintf (stderr, "---> %s ********\n", buffer); + if (cp) + *cp = ' '; + pophack = 0; + } + else + fprintf (stderr, "---> %s\n", buffer); + + if (putline (buffer, output) == NOTOK) + return NOTOK; + + switch (getline (response, sizeof response, input)) { + case OK: + if (poprint) + fprintf (stderr, "<--- %s\n", response); +#ifndef NNTP + return (*response == '+' ? OK : NOTOK); +#else /* NNTP */ + return (*response < CHAR_ERR ? OK : NOTOK); +#endif /* NNTP */ + + case NOTOK: + case DONE: + if (poprint) + fprintf (stderr, "%s\n", response); + return NOTOK; + } + + return NOTOK; /* NOTREACHED */ +} + + +#if defined(MPOP) && !defined(NNTP) +int +multiline (void) +#else +static int +multiline (void) +#endif +{ + char buffer[BUFSIZ + TRMLEN]; + + if (getline (buffer, sizeof buffer, input) != OK) + return NOTOK; +#ifdef DEBUG + if (poprint) + fprintf (stderr, "<--- %s\n", response); +#endif DEBUG + if (strncmp (buffer, TRM, TRMLEN) == 0) { + if (buffer[TRMLEN] == 0) + return DONE; + else + strncpy (response, buffer + TRMLEN, sizeof(response)); + } + else + strncpy (response, buffer, sizeof(response)); + + return OK; +} + + +static int +getline (char *s, int n, FILE *iop) +{ + int c; + char *p; + + p = s; + while (--n > 0 && (c = fgetc (iop)) != EOF) + if ((*p++ = c) == '\n') + break; + if (ferror (iop) && c != EOF) { + strncpy (response, "error on connection", sizeof(response)); + return NOTOK; + } + if (c == EOF && p == s) { + strncpy (response, "connection closed by foreign host", sizeof(response)); + return DONE; + } + *p = 0; + if (*--p == '\n') + *p = 0; + if (*--p == '\r') + *p = 0; + + return OK; +} + + +static int +putline (char *s, FILE *iop) +{ + fprintf (iop, "%s\r\n", s); + fflush (iop); + if (ferror (iop)) { + strncpy (response, "lost connection", sizeof(response)); + return NOTOK; + } + + return OK; +} diff --git a/uip/post.c b/uip/post.c new file mode 100644 index 0000000..4602dc0 --- /dev/null +++ b/uip/post.c @@ -0,0 +1,1965 @@ + +/* + * post.c -- enter messages into the mail transport system + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef MMDFMTS +# include +# include +#endif + +/* + * Currently smtp and sendmail use + * the same interface for posting. + */ +#ifdef SMTPMTS +# define SENDMTS +#endif + +#ifdef SENDMTS +# include +#endif + +#ifndef MMDFMTS +# define uptolow(c) ((isalpha(c) && isupper (c)) ? tolower (c) : (c)) +#endif + +#define FCCS 10 /* max number of fccs allowed */ + +static struct swit switches[] = { +#define ALIASW 0 + { "alias aliasfile", 0 }, +#define CHKSW 1 + { "check", -5 }, /* interface from whom */ +#define NCHKSW 2 + { "nocheck", -7 }, /* interface from whom */ +#define DEBUGSW 3 + { "debug", -5 }, +#define DISTSW 4 + { "dist", -4 }, /* interface from dist */ +#define FILTSW 5 + { "filter filterfile", 0 }, +#define NFILTSW 6 + { "nofilter", 0 }, +#define FRMTSW 7 + { "format", 0 }, +#define NFRMTSW 8 + { "noformat", 0 }, +#define LIBSW 9 + { "library directory", -7 }, /* interface from send, whom */ +#define MIMESW 10 + { "mime", 0 }, +#define NMIMESW 11 + { "nomime", 0 }, +#define MSGDSW 12 + { "msgid", 0 }, +#define NMSGDSW 13 + { "nomsgid", 0 }, +#define VERBSW 14 + { "verbose", 0 }, +#define NVERBSW 15 + { "noverbose", 0 }, +#define WATCSW 16 + { "watch", 0 }, +#define NWATCSW 17 + { "nowatch", 0 }, +#define WHOMSW 18 + { "whom", -4 }, /* interface from whom */ +#define WIDTHSW 19 + { "width columns", 0 }, +#define VERSIONSW 20 + { "version", 0 }, +#define HELPSW 21 + { "help", 4 }, +#define BITSTUFFSW 22 + { "dashstuffing", -12 }, /* should we dashstuff BCC messages? */ +#define NBITSTUFFSW 23 + { "nodashstuffing", -14 }, +#define MAILSW 24 + { "mail", -4 }, /* specify MAIL smtp mode */ +#define SAMLSW 25 + { "saml", -4 }, /* specify SAML smtp mode */ +#define SENDSW 26 + { "send", -4 }, /* specify SEND smtp mode */ +#define SOMLSW 27 + { "soml", -4 }, /* specify SOML smtp mode */ +#define ANNOSW 28 + { "idanno number", -6 }, /* interface from send */ +#define DLVRSW 29 + { "deliver address-list", -7 }, +#define CLIESW 30 + { "client host", -6 }, +#define SERVSW 31 + { "server host", -6 }, /* specify alternate SMTP server */ +#define SNOOPSW 32 + { "snoop", -5 }, /* snoop the SMTP transaction */ +#define FILLSW 33 + { "fill-in file", -7 }, +#define FILLUSW 34 + { "fill-up", -7 }, +#define PARTSW 35 + { "partno", -6 }, +#define QUEUESW 36 + { "queued", -6 }, + { NULL, 0 } +}; + + +struct headers { + char *value; + unsigned int flags; + unsigned int set; +}; + +/* + * flags for headers->flags + */ +#define HNOP 0x0000 /* just used to keep .set around */ +#define HBAD 0x0001 /* bad header - don't let it through */ +#define HADR 0x0002 /* header has an address field */ +#define HSUB 0x0004 /* Subject: header */ +#define HTRY 0x0008 /* try to send to addrs on header */ +#define HBCC 0x0010 /* don't output this header */ +#define HMNG 0x0020 /* munge this header */ +#define HNGR 0x0040 /* no groups allowed in this header */ +#define HFCC 0x0080 /* FCC: type header */ +#define HNIL 0x0100 /* okay for this header not to have addrs */ +#define HIGN 0x0200 /* ignore this header */ +#define HDCC 0x0400 /* another undocumented feature */ + +/* + * flags for headers->set + */ +#define MFRM 0x0001 /* we've seen a From: */ +#define MDAT 0x0002 /* we've seen a Date: */ +#define MRFM 0x0004 /* we've seen a Resent-From: */ +#define MVIS 0x0008 /* we've seen sighted addrs */ +#define MINV 0x0010 /* we've seen blind addrs */ + + +static struct headers NHeaders[] = { + { "Return-Path", HBAD, 0 }, + { "Received", HBAD, 0 }, + { "Reply-To", HADR|HNGR, 0 }, + { "From", HADR|HNGR, MFRM }, + { "Sender", HADR|HBAD, 0 }, + { "Date", HBAD, 0 }, + { "Subject", HSUB, 0 }, + { "To", HADR|HTRY, MVIS }, + { "cc", HADR|HTRY, MVIS }, + { "Bcc", HADR|HTRY|HBCC|HNIL, MINV }, + { "Dcc", HADR|HTRY|HDCC|HNIL, MVIS }, /* sorta cc & bcc combined */ + { "Message-ID", HBAD, 0 }, + { "Fcc", HFCC, 0 }, + { NULL, 0, 0 } +}; + +static struct headers RHeaders[] = { + { "Resent-Reply-To", HADR|HNGR, 0 }, + { "Resent-From", HADR|HNGR, MRFM }, + { "Resent-Sender", HADR|HBAD, 0 }, + { "Resent-Date", HBAD, 0 }, + { "Resent-Subject", HSUB, 0 }, + { "Resent-To", HADR|HTRY, MVIS }, + { "Resent-cc", HADR|HTRY, MVIS }, + { "Resent-Bcc", HADR|HTRY|HBCC, MINV }, + { "Resent-Message-ID", HBAD, 0 }, + { "Resent-Fcc", HFCC, 0 }, + { "Reply-To", HADR, 0 }, + { "From", HADR|HNGR, MFRM }, +#ifdef MMDFI + { "Sender", HADR|HNGR|HMNG, 0 }, +#else + { "Sender", HADR|HNGR, 0 }, +#endif + { "Date", HNOP, MDAT }, + { "To", HADR|HNIL, 0 }, + { "cc", HADR|HNIL, 0 }, + { "Bcc", HADR|HTRY|HBCC|HNIL, 0 }, + { "Fcc", HIGN, 0 }, + { NULL, 0, 0 } +}; + +static short fccind = 0; /* index into fccfold[] */ +static short outputlinelen = OUTPUTLINELEN; + +static int pfd = NOTOK; /* fd to write annotation list to */ +static uid_t myuid= -1; /* my user id */ +static gid_t mygid= -1; /* my group id */ +static int recipients = 0; /* how many people will get a copy */ +static int unkadr = 0; /* how many of those were unknown */ +static int badadr = 0; /* number of bad addrs */ +static int badmsg = 0; /* message has bad semantics */ +static int verbose = 0; /* spell it out */ +static int format = 1; /* format addresses */ +static int mime = 0; /* use MIME-style encapsulations for Bcc */ +static int msgid = 0; /* add msgid */ +static int debug = 0; /* debugging post */ +static int watch = 0; /* watch the delivery process */ +static int whomsw = 0; /* we are whom not post */ +static int checksw = 0; /* whom -check */ +static int linepos=0; /* putadr()'s position on the line */ +static int nameoutput=0; /* putadr() has output header name */ + +static unsigned msgflags = 0; /* what we've seen */ + +#define NORMAL 0 +#define RESENT 1 +static int msgstate = NORMAL; + +static time_t tclock = 0; /* the time we started (more or less) */ + +static SIGNAL_HANDLER hstat, istat, qstat, tstat; + +static char tmpfil[BUFSIZ]; +static char bccfil[BUFSIZ]; + +static char from[BUFSIZ]; /* my network address */ +static char signature[BUFSIZ]; /* my signature */ +static char *filter = NULL; /* the filter for BCC'ing */ +static char *subject = NULL; /* the subject field for BCC'ing */ +static char *fccfold[FCCS]; /* foldernames for FCC'ing */ + +static struct headers *hdrtab; /* table for the message we're doing */ + +static struct mailname localaddrs={NULL}; /* local addrs */ +static struct mailname netaddrs={NULL}; /* network addrs */ +static struct mailname uuaddrs={NULL}; /* uucp addrs */ +static struct mailname tmpaddrs={NULL}; /* temporary queue */ + +#ifdef MMDFMTS +static char *submitmode = "m"; /* deliver to mailbox only */ +static char submitopts[6] = "vl"; /* initial options for submit */ +#endif /* MMDFMTS */ + +#ifdef SENDMTS +static int snoop = 0; +static int smtpmode = S_MAIL; +static char *clientsw = NULL; +static char *serversw = NULL; + +extern struct smtp sm_reply; +#endif /* SENDMTS */ + +static char prefix[] = "----- =_aaaaaaaaaa"; + +static int fill_up = 0; +static char *fill_in = NULL; +static char *partno = NULL; +static int queued = 0; + +/* + * static prototypes + */ +static void putfmt (char *, char *, FILE *); +static void start_headers (void); +static void finish_headers (FILE *); +static int get_header (char *, struct headers *); +static int putadr (char *, char *, struct mailname *, FILE *, unsigned int); +static void putgrp (char *, char *, FILE *, unsigned int); +static int insert (struct mailname *); +static void pl (void); +static void anno (void); +static int annoaux (struct mailname *); +static void insert_fcc (struct headers *, char *); +static void make_bcc_file (int); +static void verify_all_addresses (int); +static void chkadr (void); +static void sigon (void); +static void sigoff (void); +static void p_refile (char *); +static void fcc (char *, char *); +static void die (char *, char *, ...); +static void post (char *, int, int); +static void do_text (char *file, int fd); +static void do_an_address (struct mailname *, int); +static void do_addresses (int, int); +static int find_prefix (void); + + +int +main (int argc, char **argv) +{ + int state, compnum, dashstuff = 0; + char *cp, *msg = NULL, **argp, **arguments; + char buf[BUFSIZ], name[NAMESZ]; + FILE *in, *out; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + +#if defined(MMDFMTS) && defined(MMDFII) + mmdf_init (invo_name); +#endif /* MMDFMTS and MMDFII */ + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] file", invo_name); + print_help (buf, switches, 0); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case LIBSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + /* create a minimal context */ + if (context_foil (cp) == -1) + done (1); + continue; + + case ALIASW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((state = alias (cp)) != AK_OK) + adios (NULL, "aliasing error in %s - %s", + cp, akerror (state)); + continue; + + case CHKSW: + checksw++; + continue; + case NCHKSW: + checksw = 0; + continue; + + case DEBUGSW: + debug++; + continue; + + case DISTSW: + msgstate = RESENT; + continue; + + case FILTSW: + if (!(filter = *argp++) || *filter == '-') + adios (NULL, "missing argument to %s", argp[-2]); + mime = 0; + continue; + case NFILTSW: + filter = NULL; + continue; + + case FRMTSW: + format++; + continue; + case NFRMTSW: + format = 0; + continue; + + case BITSTUFFSW: + dashstuff = 1; + continue; + case NBITSTUFFSW: + dashstuff = -1; + continue; + + case MIMESW: + mime++; + filter = NULL; + continue; + case NMIMESW: + mime = 0; + continue; + + case MSGDSW: + msgid++; + continue; + case NMSGDSW: + msgid = 0; + continue; + + case VERBSW: + verbose++; + continue; + case NVERBSW: + verbose = 0; + continue; + + case WATCSW: + watch++; + continue; + case NWATCSW: + watch = 0; + continue; + + case WHOMSW: + whomsw++; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((outputlinelen = atoi (cp)) < 10) + adios (NULL, "impossible width %d", outputlinelen); + continue; + + case ANNOSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((pfd = atoi (cp)) <= 2) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + +#ifdef MMDFMTS + case MAILSW: + submitmode = "m"; + continue; + case SOMLSW: /* for right now, sigh... */ + case SAMLSW: + submitmode = "b"; + continue; + case SENDSW: + submitmode = "y"; + continue; +#endif /* MMDFMTS */ + + case DLVRSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + +#ifndef SENDMTS + case CLIESW: + case SERVSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case SNOOPSW: + continue; +#else /* SENDMTS */ + case MAILSW: + smtpmode = S_MAIL; + continue; + case SAMLSW: + smtpmode = S_SAML; + continue; + case SOMLSW: + smtpmode = S_SOML; + continue; + case SENDSW: + smtpmode = S_SEND; + continue; + case CLIESW: + if (!(clientsw = *argp++) || *clientsw == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case SERVSW: + if (!(serversw = *argp++) || *serversw == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case SNOOPSW: + snoop++; + continue; +#endif /* SENDMTS */ + + case FILLSW: + if (!(fill_in = *argp++) || *fill_in == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case FILLUSW: + fill_up++; + continue; + case PARTSW: + if (!(partno = *argp++) || *partno == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case QUEUESW: + queued++; + continue; + } + } + if (msg) + adios (NULL, "only one message at a time!"); + else + msg = cp; + } + + alias (AliasFile); + + if (!msg) + adios (NULL, "usage: %s [switches] file", invo_name); + + if (outputlinelen < 10) + adios (NULL, "impossible width %d", outputlinelen); + + if ((in = fopen (msg, "r")) == NULL) + adios (msg, "unable to open"); + + start_headers (); + if (debug) { + verbose++; + discard (out = stdout); /* XXX: reference discard() to help loader */ + } else { + if (whomsw) { + if ((out = fopen (fill_in ? fill_in : "/dev/null", "w")) == NULL) + adios ("/dev/null", "unable to open"); + } else { + strncpy (tmpfil, m_scratch ("", m_maildir (invo_name)), + sizeof(tmpfil)); + if ((out = fopen (tmpfil, "w")) == NULL) { + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((out = fopen (tmpfil, "w")) == NULL) + adios (tmpfil, "unable to create"); + } + chmod (tmpfil, 0600); + } + } + + hdrtab = msgstate == NORMAL ? NHeaders : RHeaders; + + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDEOF: + case FLDPLUS: + compnum++; + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + putfmt (name, cp, out); + free (cp); + if (state != FLDEOF) + continue; + finish_headers (out); + break; + + case BODY: + case BODYEOF: + finish_headers (out); + if (whomsw && !fill_in) + break; + fprintf (out, "\n%s", buf); + while (state == BODY) { + state = m_getfld (state, name, buf, sizeof(buf), in); + fputs (buf, out); + } + break; + + case FILEEOF: + finish_headers (out); + break; + + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", compnum); + + default: + adios (NULL, "getfld() returned %d", state); + } + break; + } + + if (pfd != NOTOK) + anno (); + fclose (in); + + if (debug) { + pl (); + done (0); + } else { + fclose (out); + } + + /* If we are doing a "whom" check */ + if (whomsw) { + if (!fill_up) + verify_all_addresses (1); + done (0); + } + +#ifdef MMDFMTS + strcat (submitopts, submitmode); + if (watch) + strcat (submitopts, "nw"); +#endif /* MMDFMTS */ + + if (msgflags & MINV) { + make_bcc_file (dashstuff); + if (msgflags & MVIS) { + verify_all_addresses (verbose); + post (tmpfil, 0, verbose); + } + post (bccfil, 1, verbose); + unlink (bccfil); + } else { + post (tmpfil, 0, isatty (1)); + } + + p_refile (tmpfil); + unlink (tmpfil); + + if (verbose) + printf (partno ? "Partial Message #%s Processed\n" : "Message Processed\n", + partno); + done (0); +} + + +/* + * DRAFT GENERATION + */ + +static void +putfmt (char *name, char *str, FILE *out) +{ + int count, grp, i, keep; + char *cp, *pp, *qp; + char namep[BUFSIZ]; + struct mailname *mp, *np; + struct headers *hdr; + + while (*str == ' ' || *str == '\t') + str++; + + if (msgstate == NORMAL && uprf (name, "resent")) { + advise (NULL, "illegal header line -- %s:", name); + badmsg++; + return; + } + + if ((i = get_header (name, hdrtab)) == NOTOK) { + fprintf (out, "%s: %s", name, str); + return; + } + + hdr = &hdrtab[i]; + if (hdr->flags & HIGN) { + if (fill_in) + fprintf (out, "%s: %s", name, str); + return; + } + if (hdr->flags & HBAD) { + if (fill_in) + fprintf (out, "%s: %s", name, str); + else { + advise (NULL, "illegal header line -- %s:", name); + badmsg++; + } + return; + } + msgflags |= (hdr->set & ~(MVIS | MINV)); + + if (hdr->flags & HSUB) + subject = subject ? add (str, add ("\t", subject)) : getcpy (str); + if (hdr->flags & HFCC) { + if (fill_in) { + fprintf (out, "%s: %s", name, str); + return; + } + + if ((cp = strrchr(str, '\n'))) + *cp = 0; + for (cp = pp = str; cp = strchr(pp, ','); pp = cp) { + *cp++ = 0; + insert_fcc (hdr, pp); + } + insert_fcc (hdr, pp); + return; + } + + if (!(hdr->flags & HADR)) { + fprintf (out, "%s: %s", name, str); + return; + } + + tmpaddrs.m_next = NULL; + for (count = 0; cp = getname (str); count++) + if ((mp = getm (cp, NULL, 0, AD_HOST, NULL))) { + if (tmpaddrs.m_next) + np->m_next = mp; + else + tmpaddrs.m_next = mp; + np = mp; + } + else + if (hdr->flags & HTRY) + badadr++; + else + badmsg++; + + if (count < 1) { + if (hdr->flags & HNIL) + fprintf (out, "%s: %s", name, str); + else { +#ifdef notdef + advise (NULL, "%s: field requires at least one address", name); + badmsg++; +#endif /* notdef */ + } + return; + } + + nameoutput = linepos = 0; + snprintf (namep, sizeof(namep), "%s%s", + !fill_in && (hdr->flags & HMNG) ? "Original-" : "", name); + + for (grp = 0, mp = tmpaddrs.m_next; mp; mp = np) + if (mp->m_nohost) { /* also used to test (hdr->flags & HTRY) */ + pp = akvalue (mp->m_mbox); + qp = akvisible () ? mp->m_mbox : ""; + np = mp; + if (np->m_gname) + putgrp (namep, np->m_gname, out, hdr->flags); + while ((cp = getname (pp))) { + if (!(mp = getm (cp, NULL, 0, AD_HOST, NULL))) { + badadr++; + continue; + } + if (hdr->flags & HBCC) + mp->m_bcc++; + if (np->m_ingrp) + mp->m_ingrp = np->m_ingrp; + else + if (mp->m_gname) + putgrp (namep, mp->m_gname, out, hdr->flags); + if (mp->m_ingrp) + grp++; + if (putadr (namep, qp, mp, out, hdr->flags)) + msgflags |= (hdr->set & (MVIS | MINV)); + else + mnfree (mp); + } + mp = np; + np = np->m_next; + mnfree (mp); + } + else { + if (hdr->flags & HBCC) + mp->m_bcc++; + if (mp->m_gname) + putgrp (namep, mp->m_gname, out, hdr->flags); + if (mp->m_ingrp) + grp++; + keep = putadr (namep, "", mp, out, hdr->flags); + np = mp->m_next; + if (keep) { + mp->m_next = NULL; + msgflags |= (hdr->set & (MVIS | MINV)); + } + else + mnfree (mp); + } + + if (grp > 0 && (hdr->flags & HNGR)) { + advise (NULL, "%s: field does not allow groups", name); + badmsg++; + } + if (linepos) { + if (fill_in && grp > 0) + putc (';', out); + putc ('\n', out); + } +} + + +static void +start_headers (void) +{ + char *cp; + char myhost[BUFSIZ], sigbuf[BUFSIZ]; + struct mailname *mp; + + myuid = getuid (); + mygid = getgid (); + time (&tclock); + + strncpy (from, adrsprintf (NULL, NULL), sizeof(from)); + strncpy (myhost, LocalName (), sizeof(myhost)); + + for (cp = myhost; *cp; cp++) + *cp = uptolow (*cp); + + if ((cp = getfullname ()) && *cp) { + strncpy (sigbuf, cp, sizeof(sigbuf)); + snprintf (signature, sizeof(signature), "%s <%s>", + sigbuf, adrsprintf (NULL, NULL)); + if ((cp = getname (signature)) == NULL) + adios (NULL, "getname () failed -- you lose extraordinarily big"); + if ((mp = getm (cp, NULL, 0, AD_HOST, NULL)) == NULL) + adios (NULL, "bad signature '%s'", sigbuf); + mnfree (mp); + while (getname ("")) + continue; + } else { + strncpy (signature, adrsprintf (NULL, NULL), sizeof(signature)); + } +} + + +/* + * Now that we've outputted the header fields in the draft + * message, we will now output any remaining header fields + * that we need to add/create. + */ + +static void +finish_headers (FILE *out) +{ + switch (msgstate) { + case NORMAL: + if (whomsw && !fill_up) + break; + + fprintf (out, "Date: %s\n", dtime (&tclock, 0)); + if (msgid) + fprintf (out, "Message-ID: <%d.%ld@%s>\n", + (int) getpid (), tclock, LocalName ()); + if (msgflags & MFRM) + fprintf (out, "Sender: %s\n", from); + else + fprintf (out, "From: %s\n", signature); + if (whomsw) + break; + + if (!(msgflags & MVIS)) + fprintf (out, "Bcc: Blind Distribution List: ;\n"); + break; + + case RESENT: + if (!(msgflags & MDAT)) { + advise (NULL, "message has no Date: header"); + badmsg++; + } + if (!(msgflags & MFRM)) { + advise (NULL, "message has no From: header"); + badmsg++; + } + if (whomsw && !fill_up) + break; + +#ifdef MMDFI /* sigh */ + fprintf (out, "Sender: %s\n", from); +#endif /* MMDFI */ + + fprintf (out, "Resent-Date: %s\n", dtime (&tclock, 0)); + if (msgid) + fprintf (out, "Resent-Message-ID: <%d.%ld@%s>\n", + (int) getpid (), tclock, LocalName ()); + if (msgflags & MRFM) + fprintf (out, "Resent-Sender: %s\n", from); + else + fprintf (out, "Resent-From: %s\n", signature); + if (whomsw) + break; + if (!(msgflags & MVIS)) + fprintf (out, "Resent-Bcc: Blind Re-Distribution List: ;\n"); + break; + } + + if (badmsg) + adios (NULL, "re-format message and try again"); + if (!recipients) + adios (NULL, "no addressees"); +} + + +static int +get_header (char *header, struct headers *table) +{ + struct headers *h; + + for (h = table; h->value; h++) + if (!strcasecmp (header, h->value)) + return (h - table); + + return NOTOK; +} + + +static int +putadr (char *name, char *aka, struct mailname *mp, FILE *out, unsigned int flags) +{ + int len; + char *cp; + char buffer[BUFSIZ]; + + if (mp->m_mbox == NULL || ((flags & HTRY) && !insert (mp))) + return 0; + if (!fill_in && (flags & (HBCC | HDCC)) || mp->m_ingrp) + return 1; + + if (!nameoutput) { + fprintf (out, "%s: ", name); + linepos += (nameoutput = strlen (name) + 2); + } + + if (*aka && mp->m_type != UUCPHOST && !mp->m_pers) + mp->m_pers = getcpy (aka); + if (format) { + if (mp->m_gname && !fill_in) { + snprintf (buffer, sizeof(buffer), "%s;", mp->m_gname); + cp = buffer; + } else { + cp = adrformat (mp); + } + } else { + cp = mp->m_text; + } + len = strlen (cp); + + if (linepos != nameoutput) + if (len + linepos + 2 > outputlinelen) + fprintf (out, ",\n%*s", linepos = nameoutput, ""); + else { + fputs (", ", out); + linepos += 2; + } + + fputs (cp, out); + linepos += len; + + return (flags & HTRY); +} + + +static void +putgrp (char *name, char *group, FILE *out, unsigned int flags) +{ + int len; + char *cp; + + if (!fill_in && (flags & HBCC)) + return; + + if (!nameoutput) { + fprintf (out, "%s: ", name); + linepos += (nameoutput = strlen (name) + 2); + if (fill_in) + linepos -= strlen (group); + } + + cp = fill_in ? group : concat (group, ";", NULL); + len = strlen (cp); + + if (linepos > nameoutput) + if (len + linepos + 2 > outputlinelen) { + fprintf (out, ",\n%*s", nameoutput, ""); + linepos = nameoutput; + } + else { + fputs (", ", out); + linepos += 2; + } + + fputs (cp, out); + linepos += len; +} + + +static int +insert (struct mailname *np) +{ + struct mailname *mp; + + if (np->m_mbox == NULL) + return 0; + + for (mp = np->m_type == LOCALHOST ? &localaddrs + : np->m_type == UUCPHOST ? &uuaddrs + : &netaddrs; + mp->m_next; + mp = mp->m_next) + if (!strcasecmp (np->m_host, mp->m_next->m_host) + && !strcasecmp (np->m_mbox, mp->m_next->m_mbox) + && np->m_bcc == mp->m_next->m_bcc) + return 0; + + mp->m_next = np; + recipients++; + return 1; +} + + +static void +pl (void) +{ + int i; + struct mailname *mp; + + printf ("-------\n\t-- Addresses --\nlocal:\t"); + for (mp = localaddrs.m_next; mp; mp = mp->m_next) + printf ("%s%s%s", mp->m_mbox, + mp->m_bcc ? "[BCC]" : "", + mp->m_next ? ",\n\t" : ""); + + printf ("\nnet:\t"); + for (mp = netaddrs.m_next; mp; mp = mp->m_next) + printf ("%s%s@%s%s%s", mp->m_path ? mp->m_path : "", + mp->m_mbox, mp->m_host, + mp->m_bcc ? "[BCC]" : "", + mp->m_next ? ",\n\t" : ""); + + printf ("\nuucp:\t"); + for (mp = uuaddrs.m_next; mp; mp = mp->m_next) + printf ("%s!%s%s%s", mp->m_host, mp->m_mbox, + mp->m_bcc ? "[BCC]" : "", + mp->m_next ? ",\n\t" : ""); + + printf ("\n\t-- Folder Copies --\nfcc:\t"); + for (i = 0; i < fccind; i++) + printf ("%s%s", fccfold[i], i + 1 < fccind ? ",\n\t" : ""); + printf ("\n"); +} + + +static void +anno (void) +{ + struct mailname *mp; + + for (mp = localaddrs.m_next; mp; mp = mp->m_next) + if (annoaux (mp) == NOTOK) + goto oops; + + for (mp = netaddrs.m_next; mp; mp = mp->m_next) + if (annoaux (mp) == NOTOK) + goto oops; + + for (mp = uuaddrs.m_next; mp; mp = mp->m_next) + if (annoaux (mp) == NOTOK) + break; + +oops: ; + close (pfd); + pfd = NOTOK; +} + + +static int +annoaux (struct mailname *mp) +{ + int i; + char buffer[BUFSIZ]; + + snprintf (buffer, sizeof(buffer), "%s\n", adrformat (mp)); + i = strlen (buffer); + + return (write (pfd, buffer, i) == i ? OK : NOTOK); +} + + +static void +insert_fcc (struct headers *hdr, char *pp) +{ + char *cp; + + for (cp = pp; isspace (*cp); cp++) + continue; + for (pp += strlen (pp) - 1; pp > cp && isspace (*pp); pp--) + continue; + if (pp >= cp) + *++pp = 0; + if (*cp == 0) + return; + + if (fccind >= FCCS) + adios (NULL, "too many %ss", hdr->value); + fccfold[fccind++] = getcpy (cp); +} + +/* + * BCC GENERATION + */ + +static void +make_bcc_file (int dashstuff) +{ + int fd, i; + pid_t child_id; + char *vec[6]; + FILE *out; + + strncpy (bccfil, m_tmpfil ("bccs"), sizeof(bccfil)); + if ((out = fopen (bccfil, "w")) == NULL) + adios (bccfil, "unable to create"); + chmod (bccfil, 0600); + + fprintf (out, "Date: %s\n", dtime (&tclock, 0)); + if (msgid) + fprintf (out, "Message-ID: <%d.%ld@%s>\n", + (int) getpid (), tclock, LocalName ()); + fprintf (out, "From: %s\n", signature); + if (subject) + fprintf (out, "Subject: %s", subject); + fprintf (out, "BCC:\n"); + + /* + * Use MIME encapsulation for Bcc messages + */ + if (mime) { + char *cp; + + /* + * Check if any lines in the message clash with the + * prefix for the MIME multipart separator. If there + * is a clash, increment one of the letters in the + * prefix and check again. + */ + if ((cp = strchr(prefix, 'a')) == NULL) + adios (NULL, "lost prefix start"); + while (find_prefix () == NOTOK) { + if (*cp < 'z') + (*cp)++; + else + if (*++cp == 0) + adios (NULL, "can't find a unique delimiter string"); + else + (*cp)++; + } + + fprintf (out, "%s: %s\n%s: multipart/digest; boundary=\"", + VRSN_FIELD, VRSN_VALUE, TYPE_FIELD); + fprintf (out, "%s\"\n\n--%s\n\n", prefix, prefix); + } else { + fprintf (out, "\n------- Blind-Carbon-Copy\n\n"); + } + + fflush (out); + + /* + * Do mhl filtering of Bcc messages instead + * of MIME encapsulation. + */ + if (filter != NULL) { + vec[0] = r1bindex (mhlproc, '/'); + + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + adios ("fork", "unable to"); + + case OK: + dup2 (fileno (out), 1); + + i = 1; + vec[i++] = "-forward"; + vec[i++] = "-form"; + vec[i++] = filter; + vec[i++] = tmpfil; + + /* was the flag -[no]dashstuffing specified? */ + if (dashstuff > 0) + vec[i++] = "-dashstuffing"; + else if (dashstuff < 0) + vec[i++] = "-nodashstuffing"; + vec[i] = NULL; + + execvp (mhlproc, vec); + fprintf (stderr, "unable to exec "); + perror (mhlproc); + _exit (-1); + + default: + pidXwait (child_id, mhlproc); + break; + } + } else { + if ((fd = open (tmpfil, O_RDONLY)) == NOTOK) + adios (tmpfil, "unable to re-open"); + + /* + * If using MIME encapsulation, or if the -nodashstuffing + * flag was given, then just copy message. Else do + * RFC934 quoting (dashstuffing). + */ + if (mime || dashstuff < 0) + cpydata (fd, fileno (out), tmpfil, bccfil); + else + cpydgst (fd, fileno (out), tmpfil, bccfil); + close (fd); + } + + fseek (out, 0L, SEEK_END); + if (mime) + fprintf (out, "\n--%s--\n", prefix); + else + fprintf (out, "\n------- End of Blind-Carbon-Copy\n"); + fclose (out); +} + + +/* + * Scan message to check if any lines clash with + * the prefix of the MIME multipart separator. + */ + +static int +find_prefix (void) +{ + int len, result; + char buffer[BUFSIZ]; + FILE *in; + + if ((in = fopen (tmpfil, "r")) == NULL) + adios (tmpfil, "unable to re-open"); + + len = strlen (prefix); + + result = OK; + while (fgets (buffer, sizeof(buffer) - 1, in)) + if (buffer[0] == '-' && buffer[1] == '-') { + char *cp; + + for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--) + if (!isspace (*cp)) + break; + *++cp = '\0'; + if (strcmp (buffer + 2, prefix) == 0) { + result = NOTOK; + break; + } + } + + fclose (in); + return result; +} + + +#define plural(x) (x == 1 ? "" : "s") + +static void +chkadr (void) +{ + if (badadr && unkadr) + die (NULL, "%d address%s unparsable, %d addressee%s undeliverable", + badadr, plural (badadr), unkadr, plural (badadr)); + if (badadr) + die (NULL, "%d address%s unparsable", badadr, plural (badadr)); + if (unkadr) + die (NULL, "%d addressee%s undeliverable", unkadr, plural (unkadr)); +} + + +static void +do_addresses (int bccque, int talk) +{ + int retval; + int state; + struct mailname *lp; + + state = 0; + for (lp = localaddrs.m_next; lp; lp = lp->m_next) + if (lp->m_bcc ? bccque : !bccque) { + if (talk && !state) + printf (" -- Local Recipients --\n"); + do_an_address (lp, talk); + state++; + } + + state = 0; + for (lp = uuaddrs.m_next; lp; lp = lp->m_next) + if (lp->m_bcc ? bccque : !bccque) { + if (talk && !state) + printf (" -- UUCP Recipients --\n"); + do_an_address (lp, talk); + state++; + } + + state = 0; + for (lp = netaddrs.m_next; lp; lp = lp->m_next) + if (lp->m_bcc ? bccque : !bccque) { + if (talk && !state) + printf (" -- Network Recipients --\n"); + do_an_address (lp, talk); + state++; + } + + chkadr (); + +#ifdef MMDFMTS + if (rp_isbad (retval = mm_waend ())) + die (NULL, "problem ending addresses [%s]\n", rp_valstr (retval)); +#endif /* MMDFMTS */ + +#ifdef SENDMTS + if (rp_isbad (retval = sm_waend ())) + die (NULL, "problem ending addresses; %s", rp_string (retval)); +#endif /* SENDMTS */ +} + + +/* + * MTS-SPECIFIC INTERACTION + */ + + +/* + * SENDMAIL/SMTP routines + */ + +#ifdef SENDMTS + +static void +post (char *file, int bccque, int talk) +{ + int fd, onex; + int retval; + + onex = !(msgflags & MINV) || bccque; + if (verbose) { + if (msgflags & MINV) + printf (" -- Posting for %s Recipients --\n", + bccque ? "Blind" : "Sighted"); + else + printf (" -- Posting for All Recipients --\n"); + } + + sigon (); + + if (rp_isbad (retval = sm_init (clientsw, serversw, watch, verbose, + snoop, onex, queued)) + || rp_isbad (retval = sm_winit (smtpmode, from))) + die (NULL, "problem initializing server; %s", rp_string (retval)); + + do_addresses (bccque, talk && verbose); + if ((fd = open (file, O_RDONLY)) == NOTOK) + die (file, "unable to re-open"); + do_text (file, fd); + close (fd); + fflush (stdout); + + sm_end (onex ? OK : DONE); + sigoff (); + + if (verbose) { + if (msgflags & MINV) + printf (" -- %s Recipient Copies Posted --\n", + bccque ? "Blind" : "Sighted"); + else + printf (" -- Recipient Copies Posted --\n"); + } + + fflush (stdout); +} + + +/* Address Verification */ + +static void +verify_all_addresses (int talk) +{ + int retval; + struct mailname *lp; + + sigon (); + + if (!whomsw || checksw) + if (rp_isbad (retval = sm_init (clientsw, serversw, 0, 0, snoop, 0, 0)) + || rp_isbad (retval = sm_winit (smtpmode, from))) + die (NULL, "problem initializing server; %s", rp_string (retval)); + + if (talk && !whomsw) + printf (" -- Address Verification --\n"); + if (talk && localaddrs.m_next) + printf (" -- Local Recipients --\n"); + for (lp = localaddrs.m_next; lp; lp = lp->m_next) + do_an_address (lp, talk); + + if (talk && uuaddrs.m_next) + printf (" -- UUCP Recipients --\n"); + for (lp = uuaddrs.m_next; lp; lp = lp->m_next) + do_an_address (lp, talk); + + if (talk && netaddrs.m_next) + printf (" -- Network Recipients --\n"); + for (lp = netaddrs.m_next; lp; lp = lp->m_next) + do_an_address (lp, talk); + + chkadr (); + if (talk && !whomsw) + printf (" -- Address Verification Successful --\n"); + + if (!whomsw || checksw) + sm_end (DONE); + + fflush (stdout); + sigoff (); +} + + +static void +do_an_address (struct mailname *lp, int talk) +{ + int retval; + char *mbox, *host; + char addr[BUFSIZ]; + + switch (lp->m_type) { + case LOCALHOST: + mbox = lp->m_mbox; + host = lp->m_host; + strncpy (addr, mbox, sizeof(addr)); + break; + + case UUCPHOST: + mbox = auxformat (lp, 0); + host = NULL; + snprintf (addr, sizeof(addr), "%s!%s", lp->m_host, lp->m_mbox); + break; + + default: /* let SendMail decide if the host is bad */ + mbox = lp->m_mbox; + host = lp->m_host; + snprintf (addr, sizeof(addr), "%s at %s", mbox, host); + break; + } + + if (talk) + printf (" %s%s", addr, whomsw && lp->m_bcc ? "[BCC]" : ""); + + if (whomsw && !checksw) { + putchar ('\n'); + return; + } + if (talk) + printf (": "); + fflush (stdout); + + switch (retval = sm_wadr (mbox, host, + lp->m_type != UUCPHOST ? lp->m_path : NULL)) { + case RP_OK: + if (talk) + printf ("address ok\n"); + break; + + case RP_NO: + case RP_USER: + if (!talk) + fprintf (stderr, " %s: ", addr); + fprintf (talk ? stdout : stderr, "loses; %s\n", + rp_string (retval)); + unkadr++; + break; + + default: + if (!talk) + fprintf (stderr, " %s: ", addr); + die (NULL, "unexpected response; %s", rp_string (retval)); + } + + fflush (stdout); +} + + +static void +do_text (char *file, int fd) +{ + int retval, state; + char buf[BUFSIZ]; + + lseek (fd, (off_t) 0, SEEK_SET); + + while ((state = read (fd, buf, sizeof(buf))) > 0) { + if (rp_isbad (retval = sm_wtxt (buf, state))) + die (NULL, "problem writing text; %s\n", rp_string (retval)); + } + + if (state == NOTOK) + die (file, "problem reading from"); + + switch (retval = sm_wtend ()) { + case RP_OK: + break; + + case RP_NO: + case RP_NDEL: + die (NULL, "posting failed; %s", rp_string (retval)); + + default: + die (NULL, "unexpected response; %s", rp_string (retval)); + } +} + +#endif /* SENDMTS */ + +/* + * MMDF routines + */ + +#ifdef MMDFMTS + +static void +post (char *file, int bccque, int talk) +{ + int fd, onex; + int retval; +#ifdef RP_NS + int len; + struct rp_bufstruct reply; +#endif /* RP_NS */ + + onex = !(msgflags & MINV) || bccque; + if (verbose) { + if (msgflags & MINV) + printf (" -- Posting for %s Recipients --\n", + bccque ? "Blind" : "Sighted"); + else + printf (" -- Posting for All Recipients --\n"); + } + + sigon (); + + if (rp_isbad (retval = mm_init ()) + || rp_isbad (retval = mm_sbinit ()) + || rp_isbad (retval = mm_winit (NULL, submitopts, from))) + die (NULL, "problem initializing MMDF system [%s]", + rp_valstr (retval)); +#ifdef RP_NS + if (rp_isbad (retval = mm_rrply (&reply, &len))) + die (NULL, "problem with sender address [%s]", + rp_valstr (retval)); +#endif /* RP_NS */ + + do_addresses (bccque, talk && verbose); + if ((fd = open (file, O_RDONLY)) == NOTOK) + die (file, "unable to re-open"); + do_text (file, fd); + close (fd); + fflush (stdout); + + mm_sbend (); + mm_end (OK); + sigoff (); + + if (verbose) + if (msgflags & MINV) + printf (" -- %s Recipient Copies Posted --\n", + bccque ? "Blind" : "Sighted"); + else + printf (" -- Recipient Copies Posted --\n"); + fflush (stdout); +} + + +/* Address Verification */ + +static void +verify_all_addresses (int talk) +{ + int retval; + struct mailname *lp; + +#ifdef RP_NS + int len; + struct rp_bufstruct reply; +#endif /* RP_NS */ + + sigon (); + + if (!whomsw || checksw) { + if (rp_isbad (retval = mm_init ()) + || rp_isbad (retval = mm_sbinit ()) + || rp_isbad (retval = mm_winit (NULL, submitopts, from))) + die (NULL, "problem initializing MMDF system [%s]", + rp_valstr (retval)); +#ifdef RP_NS + if (rp_isbad (retval = mm_rrply (&reply, &len))) + die (NULL, "problem with sender address [%s]", rp_valstr (retval)); +#endif /* RP_NS */ + } + + if (talk && !whomsw) + printf (" -- Address Verification --\n"); + if (talk && localaddrs.m_next) + printf (" -- Local Recipients --\n"); + for (lp = localaddrs.m_next; lp; lp = lp->m_next) + do_an_address (lp, talk); + + if (talk && uuaddrs.m_next) + printf (" -- UUCP Recipients --\n"); + for (lp = uuaddrs.m_next; lp; lp = lp->m_next) + do_an_address (lp, talk); + + if (talk && netaddrs.m_next) + printf (" -- Network Recipients --\n"); + for (lp = netaddrs.m_next; lp; lp = lp->m_next) + do_an_address (lp, talk); + + chkadr (); + if (talk && !whomsw) + printf (" -- Address Verification Successful --\n"); + + if (!whomsw || checksw) + mm_end (NOTOK); + + fflush (stdout); + sigoff (); +} + + +static void +do_an_address (struct mailname *lp, int talk) +{ + int len, retval; + char *mbox, *host, *text, *path; + char addr[BUFSIZ]; + struct rp_bufstruct reply; + + switch (lp->m_type) { + case LOCALHOST: + mbox = lp->m_mbox; + host = LocalName (); + strncpy (addr, mbox, sizeof(addr)); + break; + + case UUCPHOST: + fprintf (talk ? stdout : stderr, " %s!%s: %s\n", + lp->m_host, lp->m_mbox, "not supported; UUCP address"); + unkadr++; + fflush (stdout); + return; + + default: /* let MMDF decide if the host is bad */ + mbox = lp->m_mbox; + host = lp->m_host; + snprintf (addr, sizeof(addr), "%s at %s", mbox, host); + break; + } + + if (talk) + printf (" %s%s", addr, whomsw && lp->m_bcc ? "[BCC]" : ""); + + if (whomsw && !checksw) { + putchar ('\n'); + return; + } + if (talk) + printf (": "); + fflush (stdout); + +#ifdef MMDFII + if (lp->m_path) + path = concat (lp->m_path, mbox, "@", host, NULL); + else +#endif /* MMDFII */ + path = NULL; + if (rp_isbad (retval = mm_wadr (path ? NULL : host, path ? path : mbox)) + || rp_isbad (retval = mm_rrply (&reply, &len))) + die (NULL, "problem submitting address [%s]", rp_valstr (retval)); + + switch (rp_gval (reply.rp_val)) { + case RP_AOK: + if (talk) + printf ("address ok\n"); + fflush (stdout); + return; + +#ifdef RP_DOK + case RP_DOK: + if (talk) + printf ("nameserver timeout - queued for checking\n"); + fflush (stdout); + return; +#endif /* RP_DOK */ + + case RP_NO: + text = "you lose"; + break; + +#ifdef RP_NS + case RP_NS: + text = "temporary nameserver failure"; + break; + +#endif /* RP_NS */ + + case RP_USER: + case RP_NDEL: + text = "not deliverable"; + break; + + case RP_AGN: + text = "try again later"; + break; + + case RP_NOOP: + text = "nothing done"; + break; + + default: + if (!talk) + fprintf (stderr, " %s: ", addr); + text = "unexpected response"; + die (NULL, "%s;\n [%s] -- %s", text, + rp_valstr (reply.rp_val), reply.rp_line); + } + + if (!talk) + fprintf (stderr, " %s: ", addr); + fprintf (talk ? stdout : stderr, "%s;\n %s\n", text, reply.rp_line); + unkadr++; + + fflush (stdout); +} + + +static void +do_text (char *file, int fd) +{ + int retval, state; + char buf[BUFSIZ]; + struct rp_bufstruct reply; + + lseek (fd, (off_t) 0, SEEK_SET); + + while ((state = read (fd, buf, sizeof(buf))) > 0) { + if (rp_isbad (mm_wtxt (buf, state))) + die (NULL, "problem writing text [%s]\n", rp_valstr (retval)); + } + + if (state == NOTOK) + die (file, "problem reading from"); + + if (rp_isbad (retval = mm_wtend ())) + die (NULL, "problem ending text [%s]\n", rp_valstr (retval)); + + if (rp_isbad (retval = mm_rrply (&reply, &state))) + die (NULL, "problem getting submission status [%s]\n", + rp_valstr (retval)); + + switch (rp_gval (reply.rp_val)) { + case RP_OK: + case RP_MOK: + break; + + case RP_NO: + die (NULL, "you lose; %s", reply.rp_line); + + case RP_NDEL: + die (NULL, "no delivery occurred; %s", reply.rp_line); + + case RP_AGN: + die (NULL, "try again later; %s", reply.rp_line); + + case RP_NOOP: + die (NULL, "nothing done; %s", reply.rp_line); + + default: + die (NULL, "unexpected response;\n\t[%s] -- %s", + rp_valstr (reply.rp_val), reply.rp_line); + } +} + +#endif /* MMDFMTS */ + + +/* + * SIGNAL HANDLING + */ + +static RETSIGTYPE +sigser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (i, SIG_IGN); +#endif + + unlink (tmpfil); + if (msgflags & MINV) + unlink (bccfil); + +#ifdef MMDFMTS + if (!whomsw || checksw) + mm_end (NOTOK); +#endif /* MMDFMTS */ + +#ifdef SENDMTS + if (!whomsw || checksw) + sm_end (NOTOK); +#endif /* SENDMTS */ + + done (1); +} + + +static void +sigon (void) +{ + if (debug) + return; + + hstat = SIGNAL2 (SIGHUP, sigser); + istat = SIGNAL2 (SIGINT, sigser); + qstat = SIGNAL2 (SIGQUIT, sigser); + tstat = SIGNAL2 (SIGTERM, sigser); +} + + +static void +sigoff (void) +{ + if (debug) + return; + + SIGNAL (SIGHUP, hstat); + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + SIGNAL (SIGTERM, tstat); +} + +/* + * FCC INTERACTION + */ + +static void +p_refile (char *file) +{ + int i; + + if (fccind == 0) + return; + + if (verbose) + printf (" -- Filing Folder Copies --\n"); + for (i = 0; i < fccind; i++) + fcc (file, fccfold[i]); + if (verbose) + printf (" -- Folder Copies Filed --\n"); +} + + +/* + * Call the `fileproc' to add the file to the folder. + */ + +static void +fcc (char *file, char *folder) +{ + pid_t child_id; + int i, status; + char fold[BUFSIZ]; + + if (verbose) + printf (" %sFcc %s: ", msgstate == RESENT ? "Resent-" : "", folder); + fflush (stdout); + + for (i = 0; (child_id = fork ()) == NOTOK && i < 5; i++) + sleep (5); + + switch (child_id) { + case NOTOK: + if (!verbose) + fprintf (stderr, " %sFcc %s: ", + msgstate == RESENT ? "Resent-" : "", folder); + fprintf (verbose ? stdout : stderr, "no forks, so not ok\n"); + break; + + case OK: + /* see if we need to add `+' */ + snprintf (fold, sizeof(fold), "%s%s", + *folder == '+' || *folder == '@' ? "" : "+", folder); + + /* now exec the fileproc */ + execlp (fileproc, r1bindex (fileproc, '/'), + "-link", "-file", file, fold, NULL); + _exit (-1); + + default: + if ((status = pidwait (child_id, OK))) { + if (!verbose) + fprintf (stderr, " %sFcc %s: ", + msgstate == RESENT ? "Resent-" : "", folder); + pidstatus (status, verbose ? stdout : stderr, NULL); + } else { + if (verbose) + printf ("folder ok\n"); + } + } + + fflush (stdout); +} + +/* + * TERMINATION + */ + +static void +die (char *what, char *fmt, ...) +{ + va_list ap; + + unlink (tmpfil); + if (msgflags & MINV) + unlink (bccfil); + +#ifdef MMDFMTS + if (!whomsw || checksw) + mm_end (NOTOK); +#endif /* MMDFMTS */ + +#ifdef SENDMTS + if (!whomsw || checksw) + sm_end (NOTOK); +#endif /* SENDMTS */ + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); + done (1); +} + + +#ifdef MMDFMTS +/* + * err_abrt() is used by the mm_ routines + * do not, under *ANY* circumstances, remove it from post, + * or you will lose *BIG* + */ + +void +err_abrt (int code, char *fmt, ...) +{ + char buffer[BUFSIZ]; + va_list ap; + + snprintf (buffer, sizeof(buffer), "[%s]", rp_valstr (code)); + + va_start(ap, fmt); + advertise (buffer, NULL, fmt, ap); + va_end(ap); + + done (1); +} +#endif /* MMDFMTS */ diff --git a/uip/prompter.c b/uip/prompter.c new file mode 100644 index 0000000..fa1bfae --- /dev/null +++ b/uip/prompter.c @@ -0,0 +1,481 @@ + +/* + * prompter.c -- simple prompting editor front-end + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_TERMIOS_H +# include +#else +# ifdef HAVE_TERMIO_H +# include +# else +# include +# endif +#endif + +#define QUOTE '\\' + +#ifndef CKILL +# define CKILL '@' +#endif + +#ifndef CERASE +# define CERASE '#' +#endif + +static struct swit switches[] = { +#define ERASESW 0 + { "erase chr", 0 }, +#define KILLSW 1 + { "kill chr", 0 }, +#define PREPSW 2 + { "prepend", 0 }, +#define NPREPSW 3 + { "noprepend", 0 }, +#define RAPDSW 4 + { "rapid", 0 }, +#define NRAPDSW 5 + { "norapid", 0 }, +#define BODYSW 6 + { "body", -4 }, +#define NBODYSW 7 + { "nobody", -6 }, +#define DOTSW 8 + { "doteof", 0 }, +#define NDOTSW 9 + { "nodoteof", 0 }, +#define VERSIONSW 10 + { "version", 0 }, +#define HELPSW 11 + { "help", 4 }, + { NULL, 0 } +}; + +extern int errno; + +#ifdef HAVE_TERMIOS_H +static struct termios tio; +# define ERASE tio.c_cc[VERASE] +# define KILL tio.c_cc[VKILL] +# define INTR tio.c_cc[VINTR] +#else +# ifdef HAVE_TERMIO_H +static struct termio tio; +# define ERASE tio.c_cc[VERASE] +# define KILL tio.c_cc[VKILL] +# define INTR tio.c_cc[VINTR] +# else +static struct sgttyb tio; +static struct tchars tc; +# define ERASE tio.sg_erase +# define KILL tio.sg_kill +# define INTR tc.t_intrc +# endif +#endif + +static int wtuser = 0; +static int sigint = 0; +static jmp_buf sigenv; + +/* + * prototypes + */ +int getln (char *, int); +static int chrcnv (char *); +static void chrdsp (char *, char); +static RETSIGTYPE intrser (int); + + +int +main (int argc, char **argv) +{ + int body = 1, prepend = 1, rapid = 0; + int doteof = 0, fdi, fdo, i, state; + char *cp, *drft = NULL, *erasep = NULL; + char *killp = NULL, name[NAMESZ], field[BUFSIZ]; + char buffer[BUFSIZ], tmpfil[BUFSIZ]; + char **arguments, **argp; + FILE *in, *out; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buffer, sizeof(buffer), "%s [switches] file", + invo_name); + print_help (buffer, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case ERASESW: + if (!(erasep = *argp++) || *erasep == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case KILLSW: + if (!(killp = *argp++) || *killp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case PREPSW: + prepend++; + continue; + case NPREPSW: + prepend = 0; + continue; + + case RAPDSW: + rapid++; + continue; + case NRAPDSW: + rapid = 0; + continue; + + case BODYSW: + body++; + continue; + case NBODYSW: + body = 0; + continue; + + case DOTSW: + doteof++; + continue; + case NDOTSW: + doteof = 0; + continue; + } + } else { + if (!drft) + drft = cp; + } + + if (!drft) + adios (NULL, "usage: %s [switches] file", invo_name); + if ((in = fopen (drft, "r")) == NULL) + adios (drft, "unable to open"); + + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((out = fopen (tmpfil, "w")) == NULL) + adios (tmpfil, "unable to create"); + chmod (tmpfil, 0600); + + /* + * Are we changing the kill or erase character? + */ + if (killp || erasep) { +#ifdef HAVE_TERMIOS_H + cc_t save_erase, save_kill; +#else + int save_erase, save_kill; +#endif + + /* get the current terminal attributes */ +#ifdef HAVE_TERMIOS_H + tcgetattr(0, &tio); +#else +# ifdef HAVE_TERMIO_H + ioctl(0, TCGETA, &tio); +# else + ioctl (0, TIOCGETP, (char *) &tio); + ioctl (0, TIOCGETC, (char *) &tc); +# endif +#endif + + /* save original kill, erase character for later */ + save_kill = KILL; + save_erase = ERASE; + + /* set new kill, erase character in terminal structure */ + KILL = killp ? chrcnv (killp) : save_kill; + ERASE = erasep ? chrcnv (erasep) : save_erase; + + /* set the new terminal attributes */ +#ifdef HAVE_TERMIOS_H + tcsetattr(0, TCSADRAIN, &tio); +#else +# ifdef HAVE_TERMIO_H + ioctl(0, TCSETAW, &tio); +# else + ioctl (0, TIOCSETN, (char *) &tio); +# endif +#endif + + /* print out new kill erase characters */ + chrdsp ("erase", ERASE); + chrdsp (", kill", KILL); + chrdsp (", intr", INTR); + putchar ('\n'); + fflush (stdout); + + /* + * We set the kill and erase character back to original + * setup in terminal structure so we can easily + * restore it upon exit. + */ + KILL = save_kill; + ERASE = save_erase; + } + + sigint = 0; + SIGNAL2 (SIGINT, intrser); + + /* + * Loop through the lines of the draft skeleton. + */ + for (state = FLD;;) { + switch (state = m_getfld (state, name, field, sizeof(field), in)) { + case FLD: + case FLDEOF: + case FLDPLUS: + /* + * Check if the value of field contains anything + * other than space or tab. + */ + for (cp = field; *cp; cp++) + if (*cp != ' ' && *cp != '\t') + break; + + /* If so, just add header line to draft */ + if (*cp++ != '\n' || *cp != 0) { + printf ("%s:%s", name, field); + fprintf (out, "%s:%s", name, field); + while (state == FLDPLUS) { + state = + m_getfld (state, name, field, sizeof(field), in); + printf ("%s", field); + fprintf (out, "%s", field); + } + } else { + /* Else, get value of header field */ + printf ("%s: ", name); + fflush (stdout); + i = getln (field, sizeof(field)); + if (i == -1) { +abort: + if (killp || erasep) { +#ifdef HAVE_TERMIOS_H + tcsetattr(0, TCSADRAIN, &tio); +#else +# ifdef HAVE_TERMIO + ioctl (0, TCSETA, &tio); +# else + ioctl (0, TIOCSETN, (char *) &tio); +# endif +#endif + } + unlink (tmpfil); + done (1); + } + if (i != 0 || (field[0] != '\n' && field[0] != 0)) { + fprintf (out, "%s:", name); + do { + if (field[0] != ' ' && field[0] != '\t') + putc (' ', out); + fprintf (out, "%s", field); + } while (i == 1 + && (i = getln (field, sizeof(field))) >= 0); + if (i == -1) + goto abort; + } + } + + if (state == FLDEOF) { /* moby hack */ + fprintf (out, "--------\n"); + printf ("--------\n"); + if (!body) + break; + goto no_body; + } + continue; + + case BODY: + case BODYEOF: + case FILEEOF: + if (!body) + break; + fprintf (out, "--------\n"); + if (field[0] == 0 || !prepend) + printf ("--------\n"); + if (field[0]) { + if (prepend && body) { + printf ("\n--------Enter initial text\n\n"); + fflush (stdout); + for (;;) { + getln (buffer, sizeof(buffer)); + if (doteof && buffer[0] == '.' && buffer[1] == '\n') + break; + if (buffer[0] == 0) + break; + fprintf (out, "%s", buffer); + } + } + + do { + fprintf (out, "%s", field); + if (!rapid && !sigint) + printf ("%s", field); + } while (state == BODY && + (state = m_getfld (state, name, field, sizeof(field), in))); + if (prepend || !body) + break; + else + printf ("\n--------Enter additional text\n\n"); + } +no_body: + fflush (stdout); + for (;;) { + getln (field, sizeof(field)); + if (doteof && field[0] == '.' && field[1] == '\n') + break; + if (field[0] == 0) + break; + fprintf (out, "%s", field); + } + break; + + default: + adios (NULL, "skeleton is poorly formatted"); + } + break; + } + + if (body) + printf ("--------\n"); + + fflush (stdout); + fclose (in); + fclose (out); + SIGNAL (SIGINT, SIG_IGN); + + if (killp || erasep) { +#ifdef HAVE_TERMIOS_H + tcsetattr(0, TCSADRAIN, &tio); +#else +# ifdef HAVE_TERMIO_H + ioctl (0, TCSETAW, &tio); +# else + ioctl (0, TIOCSETN, (char *) &tio); +# endif +#endif + } + + if ((fdi = open (tmpfil, O_RDONLY)) == NOTOK) + adios (tmpfil, "unable to re-open"); + if ((fdo = creat (drft, m_gmprot ())) == NOTOK) + adios (drft, "unable to write"); + cpydata (fdi, fdo, tmpfil, drft); + close (fdi); + close (fdo); + unlink (tmpfil); + + context_save (); /* save the context file */ + done (0); +} + + +int +getln (char *buffer, int n) +{ + int c; + char *cp; + + cp = buffer; + *cp = 0; + + switch (setjmp (sigenv)) { + case OK: + wtuser = 1; + break; + + case DONE: + wtuser = 0; + return 0; + + default: + wtuser = 0; + return NOTOK; + } + + for (;;) { + switch (c = getchar ()) { + case EOF: + clearerr (stdin); + longjmp (sigenv, DONE); + + case '\n': + if (cp[-1] == QUOTE) { + cp[-1] = c; + wtuser = 0; + return 1; + } + *cp++ = c; + *cp = 0; + wtuser = 0; + return 0; + + default: + if (cp < buffer + n) + *cp++ = c; + *cp = 0; + } + } +} + + +static RETSIGTYPE +intrser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGINT, intrser); +#endif + + if (wtuser) + longjmp (sigenv, NOTOK); + sigint++; +} + + +static int +chrcnv (char *cp) +{ + return (*cp != QUOTE ? *cp : m_atoi (++cp)); +} + + +static void +chrdsp (char *s, char c) +{ + printf ("%s ", s); + if (c < ' ' || c == 0177) + printf ("^%c", c ^ 0100); + else + printf ("%c", c); +} diff --git a/uip/rcvdist.c b/uip/rcvdist.c new file mode 100644 index 0000000..b210696 --- /dev/null +++ b/uip/rcvdist.c @@ -0,0 +1,275 @@ + +/* + * rcvdist.c -- asynchronously redistribute messages + * + * $Id$ + */ + +#include +#include +#include +#include + +static struct swit switches[] = { +#define FORMSW 0 + { "form formfile", 4 }, +#define VERSIONSW 1 + { "version", 0 }, +#define HELPSW 2 + { "help", 4 }, + { NULL, 0 } +}; + +static char backup[BUFSIZ] = ""; +static char drft[BUFSIZ] = ""; +static char tmpfil[BUFSIZ] = ""; + +/* + * prototypes + */ +static void rcvdistout (FILE *, char *, char *); +void done (int); + + +int +main (int argc, char **argv) +{ + pid_t child_id; + int i, vecp = 1; + char *addrs = NULL, *cp, *form = NULL, buf[BUFSIZ]; + char **argp, **arguments, *vec[MAXARGS]; + register FILE *fp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + vec[vecp++] = --cp; + continue; + + case HELPSW: + snprintf (buf, sizeof(buf), + "%s [switches] [switches for postproc] address ...", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + } + } + addrs = addrs ? add (cp, add (", ", addrs)) : getcpy (cp); + } + + if (addrs == NULL) + adios (NULL, "usage: %s [switches] [switches for postproc] address ...", + invo_name); + + umask (~m_gmprot ()); + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((fp = fopen (tmpfil, "w+")) == NULL) + adios (tmpfil, "unable to create"); + cpydata (fileno (stdin), fileno (fp), "message", tmpfil); + fseek (fp, 0L, SEEK_SET); + strncpy (drft, m_tmpfil (invo_name), sizeof(drft)); + rcvdistout (fp, form, addrs); + fclose (fp); + + if (distout (drft, tmpfil, backup) == NOTOK) + done (1); + + vec[0] = r1bindex (postproc, '/'); + vec[vecp++] = "-dist"; + vec[vecp++] = drft; + vec[vecp] = NULL; + + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + admonish (NULL, "unable to fork");/* fall */ + case OK: + execvp (postproc, vec); + fprintf (stderr, "unable to exec "); + perror (postproc); + _exit (1); + + default: + done (pidXwait(child_id, postproc)); + } +/* NOTREACHED */ +} + +/* very similar to routine in replsbr.c */ + +#define SBUFSIZ 256 + +static int outputlinelen = OUTPUTLINELEN; + +static struct format *fmt; + +static int ncomps = 0; +static char **compbuffers = 0; +static struct comp **used_buf = 0; + +static int dat[5]; + +static char *addrcomps[] = { + "from", + "sender", + "reply-to", + "to", + "cc", + "bcc", + "resent-from", + "resent-sender", + "resent-reply-to", + "resent-to", + "resent-cc", + "resent-bcc", + NULL +}; + + +static void +rcvdistout (FILE *inb, char *form, char *addrs) +{ + register int char_read = 0, format_len, i, state; + register char *tmpbuf, **nxtbuf, **ap; + char *cp, *scanl, name[NAMESZ]; + register struct comp *cptr, **savecomp; + FILE *out; + + if (!(out = fopen (drft, "w"))) + adios (drft, "unable to create"); + + /* get new format string */ + cp = new_fs (form ? form : rcvdistcomps, NULL, NULL); + format_len = strlen (cp); + ncomps = fmt_compile (cp, &fmt) + 1; + if (!(nxtbuf = compbuffers = (char **) calloc ((size_t) ncomps, sizeof(char *)))) + adios (NULL, "unable to allocate component buffers"); + if (!(savecomp = used_buf = (struct comp **) calloc ((size_t) (ncomps + 1), sizeof(struct comp *)))) + adios (NULL, "unable to allocate component buffer stack"); + savecomp += ncomps + 1; + *--savecomp = 0; + + for (i = ncomps; i--;) + if (!(*nxtbuf++ = malloc (SBUFSIZ))) + adios (NULL, "unable to allocate component buffer"); + nxtbuf = compbuffers; + tmpbuf = *nxtbuf++; + + for (ap = addrcomps; *ap; ap++) { + FINDCOMP (cptr, *ap); + if (cptr) + cptr->c_type |= CT_ADDR; + } + + FINDCOMP (cptr, "addresses"); + if (cptr) + cptr->c_text = addrs; + + for (state = FLD;;) { + switch (state = m_getfld (state, name, tmpbuf, SBUFSIZ, inb)) { + case FLD: + case FLDPLUS: + if ((cptr = wantcomp[CHASH (name)])) + do { + if (!strcasecmp (name, cptr->c_name)) { + char_read += msg_count; + if (!cptr->c_text) { + cptr->c_text = tmpbuf; + *--savecomp = cptr; + tmpbuf = *nxtbuf++; + } + else { + i = strlen (cp = cptr->c_text) - 1; + if (cp[i] == '\n') + if (cptr->c_type & CT_ADDR) { + cp[i] = 0; + cp = add (",\n\t", cp); + } + else + cp = add ("\t", cp); + cptr->c_text = add (tmpbuf, cp); + } + while (state == FLDPLUS) { + state = m_getfld (state, name, tmpbuf, + SBUFSIZ, inb); + cptr->c_text = add (tmpbuf, cptr->c_text); + char_read += msg_count; + } + break; + } + } while ((cptr = cptr->c_next)); + + while (state == FLDPLUS) + state = m_getfld (state, name, tmpbuf, SBUFSIZ, inb); + break; + + case LENERR: + case FMTERR: + case BODY: + case FILEEOF: + goto finished; + + default: + adios (NULL, "m_getfld() returned %d", state); + } + } +finished: ; + + i = format_len + char_read + 256; + scanl = malloc ((size_t) i + 2); + dat[0] = dat[1] = dat[2] = dat[4] = 0; + dat[3] = outputlinelen; + fmt_scan (fmt, scanl, i, dat); + fputs (scanl, out); + + if (ferror (out)) + adios (drft, "error writing"); + fclose (out); + + free (scanl); + for (nxtbuf = compbuffers, i = ncomps; cptr = *savecomp++; nxtbuf++, i--) + free (cptr->c_text); + while (i-- > 0) + free (*nxtbuf++); + free ((char *) compbuffers); + free ((char *) used_buf); +} + + +void +done (int status) +{ + if (backup[0]) + unlink (backup); + if (drft[0]) + unlink (drft); + if (tmpfil[0]) + unlink (tmpfil); + + exit (status ? RCV_MBX : RCV_MOK); +} diff --git a/uip/rcvpack.c b/uip/rcvpack.c new file mode 100644 index 0000000..1520cbe --- /dev/null +++ b/uip/rcvpack.c @@ -0,0 +1,103 @@ + +/* + * rcvpack.c -- append message to a file + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +static struct swit switches[] = { +#define MBOXSW 0 + { "mbox", 0 }, +#define MMDFSW 1 + { "mmdf", 0 }, +#define VERSIONSW 2 + { "version", 0 }, +#define HELPSW 3 + { "help", 4 }, + { NULL, 0 } +}; + +/* + * default format in which to save messages + */ +static int mbx_style = MBOX_FORMAT; + + +int +main (int argc, char **argv) +{ + int md; + char *cp, *file = NULL, buf[BUFSIZ]; + char **argp, **arguments; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* parse arguments */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] file", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case MBOXSW: + mbx_style = MBOX_FORMAT; + continue; + case MMDFSW: + mbx_style = MMDF_FORMAT; + continue; + } + } + if (file) + adios (NULL, "only one file at a time!"); + else + file = cp; + } + + if (!file) + adios (NULL, "%s [switches] file", invo_name); + + rewind (stdin); + + /* open and lock the file */ + if ((md = mbx_open (file, mbx_style, getuid(), getgid(), m_gmprot())) == NOTOK) + done (RCV_MBX); + + /* append the message */ + if (mbx_copy (file, mbx_style, md, fileno(stdin), 1, NULL, 0) == NOTOK) { + mbx_close (file, md); + done (RCV_MBX); + } + + /* close and unlock the file */ + if (mbx_close (file, md) == NOTOK) + done (RCV_MBX); + + done (RCV_MOK); +} diff --git a/uip/rcvstore.c b/uip/rcvstore.c new file mode 100644 index 0000000..81a03e8 --- /dev/null +++ b/uip/rcvstore.c @@ -0,0 +1,233 @@ + +/* + * rcvstore.c -- asynchronously add mail to a folder + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +static struct swit switches[] = { +#define CRETSW 0 + { "create", 0 }, +#define NCRETSW 1 + { "nocreate", 0 }, +#define UNSEENSW 2 + { "unseen", 0 }, +#define NUNSEENSW 3 + { "nounseen", 0 }, +#define PUBSW 4 + { "public", 0 }, +#define NPUBSW 5 + { "nopublic", 0 }, +#define ZEROSW 6 + { "zero", 0 }, +#define NZEROSW 7 + { "nozero", 0 }, +#define SEQSW 8 + { "sequence name", 0 }, +#define VERSIONSW 9 + { "version", 0 }, +#define HELPSW 10 + { "help", 4 }, + { NULL, 0 } +}; + +extern int errno; + +/* + * name of temporary file to store incoming message + */ +static char *tmpfilenam = NULL; + + +int +main (int argc, char **argv) +{ + int publicsw = -1, zerosw = 0; + int create = 1, unseensw = 1; + int fd, msgnum, seqp = 0; + char *cp, *maildir, *folder = NULL, buf[BUFSIZ]; + char **argp, **arguments, *seqs[NUMATTRS+1]; + struct msgs *mp; + struct stat st; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* parse arguments */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case SEQSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument name to %s", argp[-2]); + + /* check if too many sequences specified */ + if (seqp >= NUMATTRS) + adios (NULL, "too many sequences (more than %d) specified", NUMATTRS); + seqs[seqp++] = cp; + continue; + + case UNSEENSW: + unseensw = 1; + continue; + case NUNSEENSW: + unseensw = 0; + continue; + + case PUBSW: + publicsw = 1; + continue; + case NPUBSW: + publicsw = 0; + continue; + + case ZEROSW: + zerosw++; + continue; + case NZEROSW: + zerosw = 0; + continue; + + case CRETSW: + create++; + continue; + case NCRETSW: + create = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + adios (NULL, "usage: %s [+folder] [switches]", invo_name); + } + } + + seqs[seqp] = NULL; /* NULL terminate list of sequences */ + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* if no folder is given, use default folder */ + if (!folder) + folder = getfolder (0); + maildir = m_maildir (folder); + + /* check if folder exists */ + if (stat (maildir, &st) == NOTOK) { + if (errno != ENOENT) + adios (maildir, "error on folder"); + if (!create) + adios (NULL, "folder %s doesn't exist", maildir); + if (!makedir (maildir)) + adios (NULL, "unable to create folder %s", maildir); + } + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* ignore a few signals */ + SIGNAL (SIGHUP, SIG_IGN); + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); + SIGNAL (SIGTERM, SIG_IGN); + + /* create a temporary file */ + tmpfilenam = m_scratch ("", invo_name); + if ((fd = creat (tmpfilenam, m_gmprot ())) == NOTOK) + adios (tmpfilenam, "unable to create"); + chmod (tmpfilenam, m_gmprot()); + + /* copy the message from stdin into temp file */ + cpydata (fileno (stdin), fd, "standard input", tmpfilenam); + + if (fstat (fd, &st) == NOTOK) { + unlink (tmpfilenam); + adios (tmpfilenam, "unable to fstat"); + } + if (close (fd) == NOTOK) + adios (tmpfilenam, "error closing"); + + /* don't add file if it is empty */ + if (st.st_size == 0) { + unlink (tmpfilenam); + advise (NULL, "empty file"); + done (0); + } + + /* + * read folder and create message structure + */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* + * Link message into folder, and possibly add + * to the Unseen-Sequence's. + */ + if ((msgnum = folder_addmsg (&mp, tmpfilenam, 0, unseensw, 0)) == -1) + done (1); + + /* + * Add the message to any extra sequences + * that have been specified. + */ + for (seqp = 0; seqs[seqp]; seqp++) { + if (!seq_addmsg (mp, seqs[seqp], msgnum, publicsw, zerosw)) + done (1); + } + + seq_setunseen (mp, 0); /* synchronize any Unseen-Sequence's */ + seq_save (mp); /* synchronize and save message sequences */ + folder_free (mp); /* free folder/message structure */ + + context_save (); /* save the global context file */ + unlink (tmpfilenam); /* remove temporary file */ + tmpfilenam = NULL; + + done (0); +} + +/* + * Clean up and exit + */ +void +done(int status) +{ + if (tmpfilenam && *tmpfilenam) + unlink (tmpfilenam); + exit (status); +} diff --git a/uip/rcvtty.c b/uip/rcvtty.c new file mode 100644 index 0000000..9eb365f --- /dev/null +++ b/uip/rcvtty.c @@ -0,0 +1,308 @@ + +/* + * rcvtty.c -- a rcvmail program (a lot like rcvalert) handling IPC ttys + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#ifndef UTMP_FILE +# ifdef _PATH_UTMP +# define UTMP_FILE _PATH_UTMP +# else +# define UTMP_FILE "/etc/utmp" +# endif +#endif + +#define SCANFMT \ +"%2(hour{dtimenow}):%02(min{dtimenow}): %<(size)%5(size) %>%<{encrypted}E%>\ +%<(mymbox{from})%<{to}To:%14(friendly{to})%>%>%<(zero)%17(friendly{from})%> \ +%{subject}%<{body}<<%{body}>>%>" + +static struct swit switches[] = { +#define BIFFSW 0 + { "biff", 0 }, +#define FORMSW 1 + { "form formatfile", 0 }, +#define FMTSW 2 + { "format string", 5 }, +#define WIDTHSW 3 + { "width columns", 0 }, +#define NLSW 4 + { "newline", 0 }, +#define NNLSW 5 + { "nonewline", 0 }, +#define BELSW 6 + { "bell", 0 }, +#define NBELSW 7 + { "nobell", 0 }, +#define VERSIONSW 8 + { "version", 0 }, +#define HELPSW 9 + { "help", 4 }, + { NULL, 0 } +}; + +static jmp_buf myctx; +static int bell = 1; +static int newline = 1; +static int biff = 0; +static int width = 0; +static char *form = NULL; +static char *format = NULL; + +/* + * external prototypes + */ +char *getusername(void); + +/* + * static prototypes + */ +static RETSIGTYPE alrmser (int); +static int message_fd (char **); +static int header_fd (void); +static void alert (char *, int); + + +int +main (int argc, char **argv) +{ + int md, vecp = 0; + char *cp, *user, buf[BUFSIZ], tty[BUFSIZ]; + char **argp, **arguments, *vec[MAXARGS]; + struct utmp ut; + register FILE *uf; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + vec[vecp++] = --cp; + continue; + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [command ...]", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case BIFFSW: + biff = 1; + continue; + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios(NULL, "missing argument to %s", argp[-2]); + width = atoi(cp); + continue; + case NLSW: + newline = 1; + continue; + case NNLSW: + newline = 0; + continue; + case BELSW: + bell = 1; + continue; + case NBELSW: + bell = 0; + continue; + + } + } + vec[vecp++] = cp; + } + vec[vecp] = 0; + + if ((md = vecp ? message_fd (vec) : header_fd ()) == NOTOK) + exit (RCV_MBX); + + user = getusername(); + if ((uf = fopen (UTMP_FILE, "r")) == NULL) + exit (RCV_MBX); + + while (fread ((char *) &ut, sizeof(ut), 1, uf) == 1) + if (ut.ut_name[0] != 0 + && strncmp (user, ut.ut_name, sizeof(ut.ut_name)) == 0) { + strncpy (tty, ut.ut_line, sizeof(ut.ut_line)); + alert (tty, md); + } + + fclose (uf); + exit (RCV_MOK); +} + + +static RETSIGTYPE +alrmser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGALRM, alrmser); +#endif + + longjmp (myctx, 1); +} + + +static int +message_fd (char **vec) +{ + pid_t child_id; + int bytes, fd, seconds; + char tmpfil[BUFSIZ]; + struct stat st; + + unlink (mktemp (strncpy (tmpfil, "/tmp/rcvttyXXXXX", sizeof(tmpfil)))); + if ((fd = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK) + return header_fd (); + unlink (tmpfil); + + if ((child_id = vfork()) == NOTOK) { + /* fork error */ + close (fd); + return header_fd (); + } else if (child_id) { + /* parent process */ + if (!setjmp (myctx)) { + SIGNAL (SIGALRM, alrmser); + bytes = fstat(fileno (stdin), &st) != NOTOK ? (int) st.st_size : 100; + + /* amount of time to wait depends on message size */ + if (bytes <= 100) { + /* give at least 5 minutes */ + seconds = 300; + } else if (bytes >= 90000) { + /* but 30 minutes should be long enough */ + seconds = 1800; + } else { + seconds = (bytes / 60) + 300; + } + alarm ((unsigned int) seconds); + pidwait(child_id, OK); + alarm (0); + + if (fstat (fd, &st) != NOTOK && st.st_size > (off_t) 0) + return fd; + } else { + /* + * Ruthlessly kill the child and anything + * else in its process group. + */ + KILLPG(child_id, SIGKILL); + } + close (fd); + return header_fd (); + } + + /* child process */ + rewind (stdin); + if (dup2 (fd, 1) == NOTOK || dup2 (fd, 2) == NOTOK) + _exit (-1); + closefds (3); + setpgid ((pid_t) 0, getpid ()); /* put in own process group */ + execvp (vec[0], vec); + _exit (-1); +} + + +static int +header_fd (void) +{ + int fd; + char *nfs, tmpfil[BUFSIZ]; + + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((fd = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK) + return NOTOK; + unlink (tmpfil); + + rewind (stdin); + + /* get new format string */ + nfs = new_fs (form, format, SCANFMT); + scan (stdin, 0, 0, nfs, width, 0, 0, NULL, 0L, 0); + if (newline) + write (fd, "\n\r", 2); + write (fd, scanl, strlen (scanl)); + if (bell) + write (fd, "\007", 1); + + return fd; +} + + +static void +alert (char *tty, int md) +{ + int i, td, mask; + char buffer[BUFSIZ], ttyspec[BUFSIZ]; + struct stat st; + + snprintf (ttyspec, sizeof(ttyspec), "/dev/%s", tty); + + /* + * The mask depends on whether we are checking for + * write permission based on `biff' or `mesg'. + */ + mask = biff ? S_IEXEC : (S_IWRITE >> 3); + if (stat (ttyspec, &st) == NOTOK || (st.st_mode & mask) == 0) + return; + + if (!setjmp (myctx)) { + SIGNAL (SIGALRM, alrmser); + alarm (2); + td = open (ttyspec, O_WRONLY); + alarm (0); + if (td == NOTOK) + return; + } else { + alarm (0); + return; + } + + lseek (md, (off_t) 0, SEEK_SET); + + while ((i = read (md, buffer, sizeof(buffer))) > 0) + if (write (td, buffer, i) != i) + break; + + close (td); +} + diff --git a/uip/refile.c b/uip/refile.c new file mode 100644 index 0000000..83e5639 --- /dev/null +++ b/uip/refile.c @@ -0,0 +1,396 @@ + +/* + * refile.c -- move or link message(s) from a source folder + * -- into one or more destination folders + * + * $Id$ + */ + +#include +#include +#include + +/* + * We allocate spaces for messages (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define DRAFTSW 0 + { "draft", 0 }, +#define LINKSW 1 + { "link", 0 }, +#define NLINKSW 2 + { "nolink", 0 }, +#define PRESSW 3 + { "preserve", 0 }, +#define NPRESSW 4 + { "nopreserve", 0 }, +#define UNLINKSW 5 + { "unlink", 0 }, +#define NUNLINKSW 6 + { "nounlink", 0 }, +#define SRCSW 7 + { "src +folder", 0 }, +#define FILESW 8 + { "file file", 0 }, +#define RPROCSW 9 + { "rmmproc program", 0 }, +#define NRPRCSW 10 + { "normmproc", 0 }, +#define VERSIONSW 11 + { "version", 0 }, +#define HELPSW 12 + { "help", 4 }, + { NULL, 0 } +}; + +extern int errno; + +static char maildir[BUFSIZ]; + +struct st_fold { + char *f_name; + struct msgs *f_mp; +}; + +/* + * static prototypes + */ +static void opnfolds (struct st_fold *, int); +static void clsfolds (struct st_fold *, int); +static void remove_files (int, char **); +static int m_file (char *, struct st_fold *, int, int); + + +int +main (int argc, char **argv) +{ + int linkf = 0, preserve = 0, filep = 0; + int foldp = 0, isdf = 0, unlink_msgs = 0; + int i, msgnum, nummsgs, maxmsgs; + char *cp, *folder = NULL, buf[BUFSIZ]; + char **argp, **arguments, **msgs; + char *filevec[NFOLDERS + 2]; + char **files = &filevec[1]; /* leave room for remove_files:vec[0] */ + struct st_fold folders[NFOLDERS + 1]; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [msgs] [switches] +folder ...", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case LINKSW: + linkf++; + continue; + case NLINKSW: + linkf = 0; + continue; + + case PRESSW: + preserve++; + continue; + case NPRESSW: + preserve = 0; + continue; + + case UNLINKSW: + unlink_msgs++; + continue; + case NUNLINKSW: + unlink_msgs = 0; + continue; + + case SRCSW: + if (folder) + adios (NULL, "only one source folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + folder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DRAFTSW: + if (filep > NFOLDERS) + adios (NULL, "only %d files allowed!", NFOLDERS); + isdf = 0; + files[filep++] = getcpy (m_draft (NULL, NULL, 1, &isdf)); + continue; + case FILESW: + if (filep > NFOLDERS) + adios (NULL, "only %d files allowed!", NFOLDERS); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + files[filep++] = path (cp, TFILE); + continue; + + case RPROCSW: + if (!(rmmproc = *argp++) || *rmmproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NRPRCSW: + rmmproc = NULL; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (foldp > NFOLDERS) + adios (NULL, "only %d folders allowed!", NFOLDERS); + folders[foldp++].f_name = + path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names, ranges, and sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (foldp == 0) + adios (NULL, "no folder specified"); + +#ifdef WHATNOW + if (!nummsgs && !foldp && !filep && (cp = getenv ("mhdraft")) && *cp) + files[filep++] = cp; +#endif /* WHATNOW */ + + /* + * We are refiling a file to the folders + */ + if (filep > 0) { + if (folder || nummsgs) + adios (NULL, "use -file or some messages, not both"); + opnfolds (folders, foldp); + for (i = 0; i < filep; i++) + if (m_file (files[i], folders, foldp, preserve)) + done (1); + /* If -nolink, then "remove" files */ + if (!linkf) + remove_files (filep, filevec); + done (0); + } + + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + strncpy (maildir, m_maildir (folder), sizeof(maildir)); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read source folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse the message range/sequence/name and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + /* create folder structures for each destination folder */ + opnfolds (folders, foldp); + + /* Link all the selected messages into destination folders */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) { + cp = getcpy (m_name (msgnum)); + if (m_file (cp, folders, foldp, preserve)) + done (1); + free (cp); + } + } + + /* + * This is a hack. If we are using an external rmmproc, + * then save the current folder to the context file, + * so the external rmmproc will remove files from the correct + * directory. This should be moved to folder_delmsgs(). + */ + if (rmmproc) { + context_replace (pfolder, folder); + context_save (); + fflush (stdout); + } + + /* If -nolink, then "remove" messages from source folder */ + if (!linkf) { + folder_delmsgs (mp, unlink_msgs); + } + + clsfolds (folders, foldp); + + if (mp->hghsel != mp->curmsg + && (mp->numsel != mp->nummsg || linkf)) + seq_setcur (mp, mp->hghsel); + seq_save (mp); /* synchronize message sequences */ + + context_replace (pfolder, folder); /* update current folder */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder structure */ + done (0); +} + + +/* + * Read all the destination folders and + * create folder structures for all of them. + */ + +static void +opnfolds (struct st_fold *folders, int nfolders) +{ + register char *cp; + char nmaildir[BUFSIZ]; + register struct st_fold *fp, *ep; + register struct msgs *mp; + struct stat st; + + for (fp = folders, ep = folders + nfolders; fp < ep; fp++) { + chdir (m_maildir ("")); + strncpy (nmaildir, m_maildir (fp->f_name), sizeof(nmaildir)); + + if (stat (nmaildir, &st) == NOTOK) { + if (errno != ENOENT) + adios (nmaildir, "error on folder"); + cp = concat ("Create folder \"", nmaildir, "\"? ", NULL); + if (!getanswer (cp)) + done (1); + free (cp); + if (!makedir (nmaildir)) + adios (NULL, "unable to create folder %s", nmaildir); + } + + if (chdir (nmaildir) == NOTOK) + adios (nmaildir, "unable to change directory to"); + if (!(mp = folder_read (fp->f_name))) + adios (NULL, "unable to read folder %s", fp->f_name); + mp->curmsg = 0; + + fp->f_mp = mp; + + chdir (maildir); + } +} + + +/* + * Set the Previous-Sequence and then sychronize the + * sequence file, for each destination folder. + */ + +static void +clsfolds (struct st_fold *folders, int nfolders) +{ + register struct st_fold *fp, *ep; + register struct msgs *mp; + + for (fp = folders, ep = folders + nfolders; fp < ep; fp++) { + mp = fp->f_mp; + seq_setprev (mp); + seq_save (mp); + } +} + + +/* + * If you have a "rmmproc" defined, we called that + * to remove all the specified files. If "rmmproc" + * is not defined, then just unlink the files. + */ + +static void +remove_files (int filep, char **files) +{ + int i; + char **vec; + + /* If rmmproc is defined, we use that */ + if (rmmproc) { + vec = files++; /* vec[0] = filevec[0] */ + files[filep] = NULL; /* NULL terminate list */ + + fflush (stdout); + vec[0] = r1bindex (rmmproc, '/'); + execvp (rmmproc, vec); + adios (rmmproc, "unable to exec"); + } + + /* Else just unlink the files */ + files++; /* advance past filevec[0] */ + for (i = 0; i < filep; i++) { + if (unlink (files[i]) == NOTOK) + admonish (files[i], "unable to unlink"); + } +} + + +/* + * Link (or copy) the message into each of + * the destination folders. + */ + +static int +m_file (char *msgfile, struct st_fold *folders, int nfolders, int preserve) +{ + int msgnum; + struct st_fold *fp, *ep; + + for (fp = folders, ep = folders + nfolders; fp < ep; fp++) { + if ((msgnum = folder_addmsg (&fp->f_mp, msgfile, 1, 0, preserve)) == -1) + return 1; + } + return 0; +} diff --git a/uip/repl.c b/uip/repl.c new file mode 100644 index 0000000..af58c59 --- /dev/null +++ b/uip/repl.c @@ -0,0 +1,462 @@ + +/* + * repl.c -- reply to a message + * + * $Id$ + */ + +#include + + +static struct swit switches[] = { +#define GROUPSW 0 + { "group", 0 }, +#define NGROUPSW 1 + { "nogroup", 0 }, +#define ANNOSW 2 + { "annotate", 0 }, +#define NANNOSW 3 + { "noannotate", 0 }, +#define CCSW 4 + { "cc all|to|cc|me", 0 }, +#define NCCSW 5 + { "nocc type", 0 }, +#define DFOLDSW 6 + { "draftfolder +folder", 0 }, +#define DMSGSW 7 + { "draftmessage msg", 0 }, +#define NDFLDSW 8 + { "nodraftfolder", 0 }, +#define EDITRSW 9 + { "editor editor", 0 }, +#define NEDITSW 10 + { "noedit", 0 }, +#define FCCSW 11 + { "fcc folder", 0 }, +#define FILTSW 12 + { "filter filterfile", 0 }, +#define FORMSW 13 + { "form formfile", 0 }, +#define FRMTSW 14 + { "format", 5 }, +#define NFRMTSW 15 + { "noformat", 7 }, +#define INPLSW 16 + { "inplace", 0 }, +#define NINPLSW 17 + { "noinplace", 0 }, +#define MIMESW 18 + { "mime", 0 }, +#define NMIMESW 19 + { "nomime", 0 }, +#define QURYSW 20 + { "query", 0 }, +#define NQURYSW 21 + { "noquery", 0 }, +#define WHATSW 22 + { "whatnowproc program", 0 }, +#define NWHATSW 23 + { "nowhatnowproc", 0 }, +#define WIDTHSW 24 + { "width columns", 0 }, +#define VERSIONSW 25 + { "version", 0 }, +#define HELPSW 26 + { "help", 4 }, +#define FILESW 27 + { "file file", -4 }, /* interface from msh */ + +#ifdef MHE +#define BILDSW 28 + { "build", -5 }, /* interface from mhe */ +#endif + + { NULL, 0 } +}; + +static struct swit ccswitches[] = { +#define CTOSW 0 + { "to", 0 }, +#define CCCSW 1 + { "cc", 0 }, +#define CMESW 2 + { "me", 0 }, +#define CALSW 3 + { "all", 0 }, + { NULL, 0 } +}; + +static struct swit aqrnl[] = { +#define NOSW 0 + { "quit", 0 }, +#define YESW 1 + { "replace", 0 }, +#define LISTDSW 2 + { "list", 0 }, +#define REFILSW 3 + { "refile +folder", 0 }, +#define NEWSW 4 + { "new", 0 }, + { NULL, 0 } +}; + +static struct swit aqrl[] = { + { "quit", 0 }, + { "replace", 0 }, + { "list", 0 }, + { "refile +folder", 0 }, + { NULL, 0 } +}; + +short ccto = 1; /* global for replsbr */ +short cccc = 1; +short ccme = 1; +short querysw = 0; + +short outputlinelen = OUTPUTLINELEN; +short groupreply = 0; /* Is this a group reply? */ + +int mime = 0; /* include original as MIME part */ +char *form = NULL; /* form (components) file */ +char *filter = NULL; /* message filter file */ +char *fcc = NULL; /* folders to add to Fcc: header */ + + +/* + * prototypes + */ +void docc (char *, int); + + +int +main (int argc, char **argv) +{ + int i, isdf = 0; + int anot = 0, inplace = 1; + int nedit = 0, nwhat = 0; + char *cp, *cwd, *dp, *maildir, *file = NULL; + char *folder = NULL, *msg = NULL, *dfolder = NULL; + char *dmsg = NULL, *ed = NULL, drft[BUFSIZ], buf[BUFSIZ]; + char **argp, **arguments; + struct msgs *mp = NULL; + struct stat st; + FILE *in; + +#ifdef MHE + int buildsw = 0; +#endif /* MHE */ + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s: [+folder] [msg] [switches]", + invo_name); + print_help (buf, switches, 1); + done (0); + case VERSIONSW: + print_version(invo_name); + done (1); + + case GROUPSW: + groupreply++; + continue; + case NGROUPSW: + groupreply = 0; + continue; + + case ANNOSW: + anot++; + continue; + case NANNOSW: + anot = 0; + continue; + + case CCSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + docc (cp, 1); + continue; + case NCCSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + docc (cp, 0); + continue; + + case EDITRSW: + if (!(ed = *argp++) || *ed == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nedit = 0; + continue; + case NEDITSW: + nedit++; + continue; + + case WHATSW: + if (!(whatnowproc = *argp++) || *whatnowproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nwhat = 0; + continue; +#ifdef MHE + case BILDSW: + buildsw++; /* fall... */ +#endif /* MHE */ + case NWHATSW: + nwhat++; + continue; + + case FCCSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dp = NULL; + if (*cp == '@') + cp = dp = path (cp + 1, TSUBCWF); + if (fcc) + fcc = add (", ", fcc); + fcc = add (cp, fcc); + if (dp) + free (dp); + continue; + + case FILESW: + if (file) + adios (NULL, "only one file at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + file = path (cp, TFILE); + continue; + case FILTSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + filter = getcpy (etcpath (cp)); + mime = 0; + continue; + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case FRMTSW: + filter = getcpy (etcpath (mhlreply)); + mime = 0; + continue; + case NFRMTSW: + filter = NULL; + continue; + + case INPLSW: + inplace++; + continue; + case NINPLSW: + inplace = 0; + continue; + + case MIMESW: + mime++; + filter = NULL; + continue; + case NMIMESW: + mime = 0; + continue; + + case QURYSW: + querysw++; + continue; + case NQURYSW: + querysw = 0; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((outputlinelen = atoi (cp)) < 10) + adios (NULL, "impossible width %d", outputlinelen); + continue; + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (dmsg) + adios (NULL, "only one draft message at a time!"); + if (!(dmsg = *argp++) || *dmsg == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + if (msg) + adios (NULL, "only one message at a time!"); + else + msg = cp; + } + } + + cwd = getcpy (pwd ()); + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (file && (msg || folder)) + adios (NULL, "can't mix files and folders/msgs"); + +try_it_again: + +#ifdef MHE + strncpy (drft, buildsw ? m_maildir ("reply") + : m_draft (dfolder, NULL, NOUSE, &isdf), sizeof(drft)); + + /* Check if a draft exists */ + if (!buildsw && stat (drft, &st) != NOTOK) { +#else + strncpy (drft, m_draft (dfolder, dmsg, NOUSE, &isdf), sizeof(drft)); + + /* Check if a draft exists */ + if (stat (drft, &st) != NOTOK) { +#endif /* MHE */ + printf ("Draft \"%s\" exists (%ld bytes).", drft, (long) st.st_size); + for (i = LISTDSW; i != YESW;) { + if (!(argp = getans ("\nDisposition? ", isdf ? aqrnl : aqrl))) + done (1); + switch (i = smatch (*argp, isdf ? aqrnl : aqrl)) { + case NOSW: + done (0); + case NEWSW: + dmsg = NULL; + goto try_it_again; + case YESW: + break; + case LISTDSW: + showfile (++argp, drft); + break; + case REFILSW: + if (refile (++argp, drft) == 0) + i = YESW; + break; + default: + advise (NULL, "say what?"); + break; + } + } + } + + if (file) { + /* + * We are replying to a file. + */ + anot = 0; /* we don't want to annotate a file */ + } else { + /* + * We are replying to a message. + */ + if (!msg) + msg = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse the message range/sequence/name and set SELECTED */ + if (!m_convert (mp, msg)) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + if (mp->numsel > 1) + adios (NULL, "only one message at a time!"); + + context_replace (pfolder, folder); /* update current folder */ + seq_setcur (mp, mp->lowsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_save (); /* save the context file */ + } + + msg = file ? file : getcpy (m_name (mp->lowsel)); + + if ((in = fopen (msg, "r")) == NULL) + adios (msg, "unable to open"); + + /* find form (components) file */ + if (!form) { + if (groupreply) + form = etcpath (replgroupcomps); + else + form = etcpath (replcomps); + } + + replout (in, msg, drft, mp, outputlinelen, mime, form, filter, fcc); + fclose (in); + + if (nwhat) + done (0); + what_now (ed, nedit, NOUSE, drft, msg, 0, mp, + anot ? "Replied" : NULL, inplace, cwd); + done (1); +} + +void +docc (char *cp, int ccflag) +{ + switch (smatch (cp, ccswitches)) { + case AMBIGSW: + ambigsw (cp, ccswitches); + done (1); + case UNKWNSW: + adios (NULL, "-%scc %s unknown", ccflag ? "" : "no", cp); + + case CTOSW: + ccto = ccflag; + break; + + case CCCSW: + cccc = ccflag; + break; + + case CMESW: + ccme = ccflag; + break; + + case CALSW: + ccto = cccc = ccme = ccflag; + break; + } +} diff --git a/uip/replsbr.c b/uip/replsbr.c new file mode 100644 index 0000000..57eb6cd --- /dev/null +++ b/uip/replsbr.c @@ -0,0 +1,448 @@ + +/* + * replsbr.c -- routines to help repl along... + * + * $Id$ + */ + +#include +#include +#include +#include /* L_SET */ + +extern short ccto; /* from repl.c */ +extern short cccc; +extern short ccme; +extern short querysw; + +static int dftype=0; + +static char *badaddrs = NULL; +static char *dfhost = NULL; + +static struct mailname mq = { NULL }; + +/* + * Buffer size for content part of header fields. + * We want this to be large enough so that we don't + * do a lot of extra FLDPLUS calls on m_getfld but + * small enough so that we don't snarf the entire + * message body when we're not going to use any of it. + */ +#define SBUFSIZ 256 + +static struct format *fmt; + +static int ncomps = 0; /* # of interesting components */ +static char **compbuffers = NULL; /* buffers for component text */ +static struct comp **used_buf = NULL; /* stack for comp that use buffers */ + +static int dat[5]; /* aux. data for format routine */ + +static char *addrcomps[] = { + "from", + "sender", + "reply-to", + "to", + "cc", + "bcc", + "resent-from", + "resent-sender", + "resent-reply-to", + "resent-to", + "resent-cc", + "resent-bcc", + NULL +}; + +/* + * static prototypes + */ +static int insert (struct mailname *); +static void replfilter (FILE *, FILE *, char *); + + +void +replout (FILE *inb, char *msg, char *drft, struct msgs *mp, int outputlinelen, + int mime, char *form, char *filter, char *fcc) +{ + register int state, i; + register struct comp *cptr; + register char *tmpbuf; + register char **nxtbuf; + register char **ap; + register struct comp **savecomp; + int char_read = 0, format_len; + char name[NAMESZ], *scanl, *cp; + FILE *out; + + umask(~m_gmprot()); + if ((out = fopen (drft, "w")) == NULL) + adios (drft, "unable to create"); + + /* get new format string */ + cp = new_fs (form, NULL, NULL); + format_len = strlen (cp); + + /* compile format string */ + ncomps = fmt_compile (cp, &fmt) + 1; + + if (!(nxtbuf = compbuffers = (char **) + calloc((size_t) ncomps, sizeof(char *)))) + adios (NULL, "unable to allocate component buffers"); + if (!(savecomp = used_buf = (struct comp **) + calloc((size_t) (ncomps+1), sizeof(struct comp *)))) + adios (NULL, "unable to allocate component buffer stack"); + savecomp += ncomps + 1; + *--savecomp = NULL; /* point at zero'd end minus 1 */ + + for (i = ncomps; i--; ) + if (!(*nxtbuf++ = malloc(SBUFSIZ))) + adios (NULL, "unable to allocate component buffer"); + + nxtbuf = compbuffers; /* point at start */ + tmpbuf = *nxtbuf++; + + for (ap = addrcomps; *ap; ap++) { + FINDCOMP (cptr, *ap); + if (cptr) + cptr->c_type |= CT_ADDR; + } + + /* + * ignore any components killed by command line switches + */ + if (!ccto) { + FINDCOMP (cptr, "to"); + if (cptr) + cptr->c_name = ""; + } + if (!cccc) { + FINDCOMP (cptr, "cc"); + if (cptr) + cptr->c_name = ""; + } + /* set up the "fcc" pseudo-component */ + if (fcc) { + FINDCOMP (cptr, "fcc"); + if (cptr) + cptr->c_text = getcpy (fcc); + } + if ((cp = getenv("USER"))) { + FINDCOMP (cptr, "user"); + if (cptr) + cptr->c_text = getcpy(cp); + } + if (!ccme) + ismymbox (NULL); + + /* + * pick any interesting stuff out of msg "inb" + */ + for (state = FLD;;) { + state = m_getfld (state, name, tmpbuf, SBUFSIZ, inb); + switch (state) { + case FLD: + case FLDPLUS: + /* + * if we're interested in this component, save a pointer + * to the component text, then start using our next free + * buffer as the component temp buffer (buffer switching + * saves an extra copy of the component text). + */ + if ((cptr = wantcomp[CHASH(name)])) + do { + if (!strcasecmp(name, cptr->c_name)) { + char_read += msg_count; + if (! cptr->c_text) { + cptr->c_text = tmpbuf; + *--savecomp = cptr; + tmpbuf = *nxtbuf++; + } else { + i = strlen (cp = cptr->c_text) - 1; + if (cp[i] == '\n') + if (cptr->c_type & CT_ADDR) { + cp[i] = '\0'; + cp = add (",\n\t", cp); + } else { + cp = add ("\t", cp); + } + cptr->c_text = add (tmpbuf, cp); + } + while (state == FLDPLUS) { + state = m_getfld (state, name, tmpbuf, + SBUFSIZ, inb); + cptr->c_text = add (tmpbuf, cptr->c_text); + char_read += msg_count; + } + break; + } + } while ((cptr = cptr->c_next)); + + while (state == FLDPLUS) + state = m_getfld (state, name, tmpbuf, SBUFSIZ, inb); + break; + + case LENERR: + case FMTERR: + case BODY: + case FILEEOF: + goto finished; + + default: + adios (NULL, "m_getfld() returned %d", state); + } + } + + /* + * format and output the header lines. + */ +finished: + + /* + * if there's a "Subject" component, strip any "Re:"s off it + */ + FINDCOMP (cptr, "subject") + if (cptr && (cp = cptr->c_text)) { + register char *sp = cp; + + for (;;) { + while (isspace(*cp)) + cp++; + if(uprf(cp, "re:")) + cp += 3; + else + break; + sp = cp; + } + if (sp != cptr->c_text) { + cp = cptr->c_text; + cptr->c_text = getcpy (sp); + free (cp); + } + } + i = format_len + char_read + 256; + scanl = malloc ((size_t) i + 2); + dat[0] = 0; + dat[1] = 0; + dat[2] = 0; + dat[3] = outputlinelen; + dat[4] = 0; + fmt_scan (fmt, scanl, i, dat); + fputs (scanl, out); + if (badaddrs) { + fputs ("\nrepl: bad addresses:\n", out); + fputs ( badaddrs, out); + } + + /* + * Check if we should filter the message + * or add mhn directives + */ + if (filter) { + replfilter (inb, out, filter); + } else if (mime && mp) { + fprintf (out, "#forw [original message] +%s %s\n", + mp->foldpath, m_name (mp->lowsel)); + } + + if (ferror (out)) + adios (drft, "error writing"); + fclose (out); + + /* return dynamically allocated buffers */ + free (scanl); + for (nxtbuf = compbuffers, i = ncomps; cptr = *savecomp++; nxtbuf++, i--) + free (cptr->c_text); /* if not nxtbuf, nxtbuf already freed */ + while ( i-- > 0) + free (*nxtbuf++); /* free unused nxtbufs */ + free ((char *) compbuffers); + free ((char *) used_buf); +} + +static char *buf; /* our current working buffer */ +static char *bufend; /* end of working buffer */ +static char *last_dst; /* buf ptr at end of last call */ +static unsigned int bufsiz=0; /* current size of buf */ + +#define BUFINCR 512 /* how much to expand buf when if fills */ + +#define CPY(s) { cp = (s); while ((*dst++ = *cp++)) ; --dst; } + +/* + * check if there's enough room in buf for str. + * add more mem if needed + */ +#define CHECKMEM(str) \ + if ((len = strlen (str)) >= bufend - dst) {\ + int i = dst - buf;\ + int n = last_dst - buf;\ + bufsiz += ((dst + len - bufend) / BUFINCR + 1) * BUFINCR;\ + buf = realloc (buf, bufsiz);\ + dst = buf + i;\ + last_dst = buf + n;\ + if (! buf)\ + adios (NULL, "formataddr: couldn't get buffer space");\ + bufend = buf + bufsiz;\ + } + + +/* + * fmt_scan will call this routine if the user includes the function + * "(formataddr {component})" in a format string. "orig" is the + * original contents of the string register. "str" is the address + * string to be formatted and concatenated onto orig. This routine + * returns a pointer to the concatenated address string. + * + * We try to not do a lot of malloc/copy/free's (which is why we + * don't call "getcpy") but still place no upper limit on the + * length of the result string. + */ +char * +formataddr (char *orig, char *str) +{ + register int len; + char baddr[BUFSIZ], error[BUFSIZ]; + register int isgroup; + register char *dst; + register char *cp; + register char *sp; + register struct mailname *mp = NULL; + + /* if we don't have a buffer yet, get one */ + if (bufsiz == 0) { + buf = malloc (BUFINCR); + if (! buf) + adios (NULL, "formataddr: couldn't allocate buffer space"); + last_dst = buf; /* XXX */ + bufsiz = BUFINCR - 6; /* leave some slop */ + bufend = buf + bufsiz; + } + /* + * If "orig" points to our buffer we can just pick up where we + * left off. Otherwise we have to copy orig into our buffer. + */ + if (orig == buf) + dst = last_dst; + else if (!orig || !*orig) { + dst = buf; + *dst = '\0'; + } else { + dst = last_dst; /* XXX */ + CHECKMEM (orig); + CPY (orig); + } + + /* concatenate all the new addresses onto 'buf' */ + for (isgroup = 0; cp = getname (str); ) { + if ((mp = getm (cp, dfhost, dftype, AD_NAME, error)) == NULL) { + snprintf (baddr, sizeof(baddr), "\t%s -- %s\n", cp, error); + badaddrs = add (baddr, badaddrs); + continue; + } + if (isgroup && (mp->m_gname || !mp->m_ingrp)) { + *dst++ = ';'; + isgroup = 0; + } + if (insert (mp)) { + /* if we get here we're going to add an address */ + if (dst != buf) { + *dst++ = ','; + *dst++ = ' '; + } + if (mp->m_gname) { + CHECKMEM (mp->m_gname); + CPY (mp->m_gname); + isgroup++; + } + sp = adrformat (mp); + CHECKMEM (sp); + CPY (sp); + } + } + + if (isgroup) + *dst++ = ';'; + + *dst = '\0'; + last_dst = dst; + return (buf); +} + + +static int +insert (struct mailname *np) +{ + char buffer[BUFSIZ]; + register struct mailname *mp; + + if (np->m_mbox == NULL) + return 0; + + for (mp = &mq; mp->m_next; mp = mp->m_next) { + if (!strcasecmp (np->m_host, mp->m_next->m_host) + && !strcasecmp (np->m_mbox, mp->m_next->m_mbox)) + return 0; + } + if (!ccme && ismymbox (np)) + return 0; + + if (querysw) { + snprintf (buffer, sizeof(buffer), "Reply to %s? ", adrformat (np)); + if (!gans (buffer, anoyes)) + return 0; + } + mp->m_next = np; + +#ifdef ISI + if (ismymbox (np)) + ccme = 0; +#endif + + return 1; +} + + +/* + * Call the mhlproc + */ + +static void +replfilter (FILE *in, FILE *out, char *filter) +{ + int pid; + char *mhl; + + if (filter == NULL) + return; + + if (access (filter, R_OK) == NOTOK) + adios (filter, "unable to read"); + + mhl = r1bindex (mhlproc, '/'); + + rewind (in); + lseek (fileno(in), (off_t) 0, SEEK_SET); + fflush (out); + + switch (pid = vfork ()) { + case NOTOK: + adios ("fork", "unable to"); + + case OK: + dup2 (fileno (in), fileno (stdin)); + dup2 (fileno (out), fileno (stdout)); + closefds (3); + + execlp (mhlproc, mhl, "-form", filter, "-noclear", NULL); + fprintf (stderr, "unable to exec "); + perror (mhlproc); + _exit (-1); + + default: + if (pidXwait (pid, mhl)) + done (1); + fseek (out, 0L, SEEK_END); + break; + } +} diff --git a/uip/rmf.c b/uip/rmf.c new file mode 100644 index 0000000..059e607 --- /dev/null +++ b/uip/rmf.c @@ -0,0 +1,245 @@ + +/* + * rmf.c -- remove a folder + * + * $Id$ + */ + +#include + +static struct swit switches[] = { +#define INTRSW 0 + { "interactive", 0 }, +#define NINTRSW 1 + { "nointeractive", 0 }, +#define VERSIONSW 2 + { "version", 0 }, +#define HELPSW 3 + { "help", 4 }, + { NULL, 0 } +}; + +/* + * static prototypes + */ +static int rmf(char *); +static void rma (char *); + + +int +main (int argc, char **argv) +{ + int defolder = 0, interactive = -1; + char *cp, *folder = NULL, newfolder[BUFSIZ]; + char buf[BUFSIZ], **argp, **arguments; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case INTRSW: + interactive = 1; + continue; + case NINTRSW: + interactive = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + adios (NULL, "usage: %s [+folder] [switches]", invo_name); + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!folder) { + folder = getfolder (1); + defolder++; + } + if (strcmp (m_mailpath (folder), pwd ()) == 0) + adios (NULL, "sorry, you can't remove the current working directory"); + + if (interactive == -1) + interactive = defolder; + + if (strchr (folder, '/') && (*folder != '/') && (*folder != '.')) { + for (cp = copy (folder, newfolder); cp > newfolder && *cp != '/'; cp--) + continue; + if (cp > newfolder) + *cp = '\0'; + else + strncpy (newfolder, getfolder(0), sizeof(newfolder)); + } else { + strncpy (newfolder, getfolder(0), sizeof(newfolder)); + } + + if (interactive) { + cp = concat ("Remove folder \"", folder, "\"? ", NULL); + if (!getanswer (cp)) + done (0); + free (cp); + } + + if (rmf (folder) == OK && strcmp (context_find (pfolder), newfolder)) { + printf ("[+%s now current]\n", newfolder); + context_replace (pfolder, newfolder); /* update current folder */ + } + context_save (); /* save the context file */ + done (0); +} + +static int +rmf (char *folder) +{ + int i, j, others; + register char *maildir; + char cur[BUFSIZ]; + register struct dirent *dp; + register DIR *dd; + + switch (i = chdir (maildir = m_maildir (folder))) { + case OK: + if (access (".", W_OK) != NOTOK && access ("..", W_OK) != NOTOK) + break; /* fall otherwise */ + + case NOTOK: + snprintf (cur, sizeof(cur), "atr-%s-%s", + current, m_mailpath (folder)); + if (!context_del (cur)) { + printf ("[+%s de-referenced]\n", folder); + return OK; + } + advise (NULL, "you have no profile entry for the %s folder +%s", + i == NOTOK ? "unreadable" : "read-only", folder); + return NOTOK; + } + + if ((dd = opendir (".")) == NULL) + adios (NULL, "unable to read folder +%s", folder); + others = 0; + + j = strlen(BACKUP_PREFIX); + while ((dp = readdir (dd))) { + switch (dp->d_name[0]) { + case '.': + if (strcmp (dp->d_name, ".") == 0 + || strcmp (dp->d_name, "..") == 0) + continue; /* else fall */ + + case ',': +#ifdef MHE + case '+': +#endif /* MHE */ +#ifdef UCI + case '_': + case '#': +#endif /* UCI */ + break; + + default: + if (m_atoi (dp->d_name)) + break; + if (strcmp (dp->d_name, LINK) == 0 + || strncmp (dp->d_name, BACKUP_PREFIX, j) == 0) + break; + + admonish (NULL, "file \"%s/%s\" not deleted", + folder, dp->d_name); + others++; + continue; + } + if (unlink (dp->d_name) == NOTOK) { + admonish (dp->d_name, "unable to unlink %s:", folder); + others++; + } + } + + closedir (dd); + + /* + * Remove any relevant private sequences + * or attributes from context file. + */ + rma (folder); + + chdir (".."); + if (others == 0 && remdir (maildir)) + return OK; + + advise (NULL, "folder +%s not removed", folder); + return NOTOK; +} + + +/* + * Remove all the (private) sequence information for + * this folder from the profile/context list. + */ + +static void +rma (char *folder) +{ + register int alen, j, plen; + register char *cp; + register struct node *np, *pp; + + /* sanity check - check that context has been read */ + if (defpath == NULL) + adios (NULL, "oops, context hasn't been read yet"); + + alen = strlen ("atr-"); + plen = strlen (cp = m_mailpath (folder)) + 1; + + /* + * Search context list for keys that look like + * "atr-something-folderpath", and remove them. + */ + for (np = m_defs, pp = NULL; np; np = np->n_next) { + if (ssequal ("atr-", np->n_name) + && (j = strlen (np->n_name) - plen) > alen + && *(np->n_name + j) == '-' + && strcmp (cp, np->n_name + j + 1) == 0) { + if (!np->n_context) + admonish (NULL, "bug: context_del(key=\"%s\")", np->n_name); + if (pp) { + pp->n_next = np->n_next; + np = pp; + } else { + m_defs = np->n_next; + } + ctxflags |= CTXMOD; + } else { + pp = np; + } + } +} diff --git a/uip/rmm.c b/uip/rmm.c new file mode 100644 index 0000000..1b09907 --- /dev/null +++ b/uip/rmm.c @@ -0,0 +1,151 @@ + +/* + * rmm.c -- remove a message(s) + * + * $Id$ + */ + +#include + +/* + * We allocate space for message names and ranges + * (msgs array) this number of elements at a time. + */ +#define MAXMSGS 256 + +static struct swit switches[] = { +#define UNLINKSW 0 + { "unlink", 0 }, +#define NUNLINKSW 1 + { "nounlink", 0 }, +#define VERSIONSW 2 + { "version", 0 }, +#define HELPSW 3 + { "help", 4 }, + { NULL, 0 } +}; + + +int +main (int argc, char **argv) +{ + int nummsgs, maxmsgs, msgnum, unlink_msgs = 0; + char *cp, *maildir, *folder = NULL; + char buf[BUFSIZ], **argp; + char **arguments, **msgs; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names and ranges. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* parse arguments */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case UNLINKSW: + unlink_msgs++; + continue; + case NUNLINKSW: + unlink_msgs = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges. + */ + if (nummsgs >= maxmsgs){ + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!nummsgs) + msgs[nummsgs++] = "cur"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + /* + * This is hackish. If we are using a external rmmproc, + * then we need to update the current folder in the + * context so the external rmmproc will remove files + * from the correct directory. This should be moved to + * folder_delmsgs(). + */ + if (rmmproc) { + context_replace (pfolder, folder); + context_save (); + fflush (stdout); + } + + /* "remove" the SELECTED messages */ + folder_delmsgs (mp, unlink_msgs); + + seq_save (mp); /* synchronize message sequences */ + context_replace (pfolder, folder); /* update current folder */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder structure */ + done (0); +} diff --git a/uip/scan.c b/uip/scan.c new file mode 100644 index 0000000..1f9b8c0 --- /dev/null +++ b/uip/scan.c @@ -0,0 +1,351 @@ + +/* + * scan.c -- display a one-line "scan" listing of folder or messages + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +/* + * We allocate space for message names (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define CLRSW 0 + { "clear", 0 }, +#define NCLRSW 1 + { "noclear", 0 }, +#define FORMSW 2 + { "form formatfile", 0 }, +#define FMTSW 3 + { "format string", 5 }, +#define HEADSW 4 + { "header", 0 }, +#define NHEADSW 5 + { "noheader", 0 }, +#define WIDTHSW 6 + { "width columns", 0 }, +#define REVSW 7 + { "reverse", 0 }, +#define NREVSW 8 + { "noreverse", 0 }, +#define FILESW 9 + { "file file", 4 }, +#define VERSIONSW 10 + { "version", 0 }, +#define HELPSW 11 + { "help", 4 }, + { NULL, 0 } +}; + +extern int errno; + +/* + * global for sbr/formatsbr.c - yech! + */ +#ifdef LBL +extern struct msgs *fmt_current_folder; +#endif + +/* + * prototypes + */ +void clear_screen(void); /* from termsbr.c */ + + +int +main (int argc, char **argv) +{ + int clearflag = 0, hdrflag = 0, ontty; + int width = 0, revflag = 0; + int i, state, msgnum, nummsgs, maxmsgs; + int seqnum[NUMATTRS], unseen, num_unseen_seq = 0; + char *cp, *maildir, *file = NULL, *folder = NULL; + char *form = NULL, *format = NULL, buf[BUFSIZ]; + char **argp, *nfs, **arguments, **msgs; + struct msgs *mp; + FILE *in; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names, ranges, and sequences. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case CLRSW: + clearflag++; + continue; + case NCLRSW: + clearflag = 0; + continue; + + case FORMSW: + if (!(form = *argp++) || *form == '-') + adios (NULL, "missing argument to %s", argp[-2]); + format = NULL; + continue; + case FMTSW: + if (!(format = *argp++) || *format == '-') + adios (NULL, "missing argument to %s", argp[-2]); + form = NULL; + continue; + + case HEADSW: + hdrflag++; + continue; + case NHEADSW: + hdrflag = 0; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + width = atoi (cp); + continue; + case REVSW: + revflag++; + continue; + case NREVSW: + revflag = 0; + continue; + + case FILESW: + if (!(cp = *argp++) || (cp[0] == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + if (strcmp (file = cp, "-")) + file = path (cp, TFILE); + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges/sequences. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + /* + * Get new format string. Must be before chdir(). + */ + nfs = new_fs (form, format, FORMAT); + + /* + * We are scanning a maildrop file + */ + if (file) { + if (nummsgs) + adios (NULL, "\"msgs\" not allowed with -file"); + if (folder) + adios (NULL, "\"+folder\" not allowed with -file"); + + /* check if "file" is really stdin */ + if (strcmp (file, "-") == 0) { + in = stdin; + file = "stdin"; + } else { + if ((in = fopen (file, "r")) == NULL) + adios (file, "unable to open"); + } + +#ifndef JLR + if (hdrflag) { + printf ("FOLDER %s\t%s\n", file, dtimenow (1)); + } +#endif /* JLR */ + + m_unknown (in); + for (msgnum = 1; ; ++msgnum) { + state = scan (in, msgnum, -1, nfs, width, 0, 0, + hdrflag ? file : NULL, 0L, 1); + if (state != SCNMSG && state != SCNENC) + break; + } + fclose (in); + done (0); + } + + /* + * We are scanning a folder + */ + + if (!nummsgs) + msgs[nummsgs++] = "all"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done(1); + seq_setprev (mp); /* set the Previous-Sequence */ + + context_replace (pfolder, folder); /* update current folder */ + seq_save (mp); /* synchronize message sequences */ + context_save (); /* save the context file */ + + /* + * Get the sequence number for each sequence + * specified by Unseen-Sequence + */ + if ((cp = context_find (usequence)) && *cp) { + char **ap, *dp; + + dp = getcpy(cp); + ap = brkstring (dp, " ", "\n"); + for (i = 0; ap && *ap; i++, ap++) + seqnum[i] = seq_getnum (mp, *ap); + + num_unseen_seq = i; + if (dp) + free(dp); + } + + ontty = isatty (fileno (stdout)); + +#ifdef LBL + else + fmt_current_folder = mp; +#endif + + for (msgnum = revflag ? mp->hghsel : mp->lowsel; + (revflag ? msgnum >= mp->lowsel : msgnum <= mp->hghsel); + msgnum += (revflag ? -1 : 1)) { + if (is_selected(mp, msgnum)) { + if ((in = fopen (cp = m_name (msgnum), "r")) == NULL) { +#if 0 + if (errno != EACCES) +#endif + admonish (cp, "unable to open message"); +#if 0 + else + printf ("%*d unreadable\n", DMAXFOLDER, msgnum); +#endif + continue; + } + +#ifndef JLR + if (hdrflag) { + printf ("FOLDER %s\t%s\n", folder, dtimenow(1)); + } +#endif /* JLR */ + + /* + * Check if message is in any sequence given + * by Unseen-Sequence profile entry. + */ + unseen = 0; + for (i = 0; i < num_unseen_seq; i++) { + if (in_sequence(mp, seqnum[i], msgnum)) { + unseen = 1; + break; + } + } + + switch (state = scan (in, msgnum, 0, nfs, width, + msgnum == mp->curmsg, unseen, + hdrflag ? folder : NULL, 0L, 1)) { + case SCNMSG: + case SCNENC: + case SCNERR: + break; + + default: + adios (NULL, "scan() botch (%d)", state); + + case SCNEOF: +#if 0 + printf ("%*d empty\n", DMAXFOLDER, msgnum); +#else + advise (NULL, "message %d: empty", msgnum); +#endif + break; + } + hdrflag = 0; + fclose (in); + if (ontty) + fflush (stdout); + } + } + +#ifdef LBL + seq_save (mp); /* because formatsbr might have made changes */ +#endif + + folder_free (mp); /* free folder/message structure */ + if (clearflag) + clear_screen (); + + done (0); +} diff --git a/uip/scansbr.c b/uip/scansbr.c new file mode 100644 index 0000000..44cbf0d --- /dev/null +++ b/uip/scansbr.c @@ -0,0 +1,386 @@ + +/* + * scansbr.c -- routines to help scan along... + * + * $Id$ + */ + +#include +#include +#include +#include +#include + +#ifdef _FSTDIO +# define _ptr _p /* Gag */ +# define _cnt _w /* Wretch */ +#endif + +#ifdef SCO_5_STDIO +# define _ptr __ptr +# define _cnt __cnt +# define _base __base +# define _filbuf(fp) ((fp)->__cnt = 0, __filbuf(fp)) +#endif + +#define MAXSCANL 256 /* longest possible scan line */ + +/* + * Buffer size for content part of header fields. We want this + * to be large enough so that we don't do a lot of extra FLDPLUS + * calls on m_getfld but small enough so that we don't snarf + * the entire message body when we're only going to display 30 + * characters of it. + */ +#define SBUFSIZ 512 + +static struct format *fmt; +#ifdef JLR +static struct format *fmt_top; +#endif /* JLR */ + +static struct comp *datecomp; /* pntr to "date" comp */ +static struct comp *bodycomp; /* pntr to "body" pseudo-comp * + * (if referenced) */ +static int ncomps = 0; /* # of interesting components */ +static char **compbuffers = 0; /* buffers for component text */ +static struct comp **used_buf = 0; /* stack for comp that use buffers */ + +static int dat[5]; /* aux. data for format routine */ + +char *scanl = 0; /* text of most recent scanline */ + +#define FPUTS(buf) {\ + if (mh_fputs(buf,scnout) == EOF)\ + adios (scnmsg, "write error on");\ + } + +/* + * prototypes + */ +int sc_width (void); /* from termsbr.c */ +static int mh_fputs(char *, FILE *); + + +int +scan (FILE *inb, int innum, int outnum, char *nfs, int width, int curflg, + int unseen, char *folder, long size, int noisy) +{ + int i, compnum, encrypted, state; + char *cp, *tmpbuf, **nxtbuf; + char *saved_c_text; + struct comp *cptr; + struct comp **savecomp; + char *scnmsg; + FILE *scnout; + char name[NAMESZ]; + static int rlwidth, slwidth; + +#ifdef RPATHS + char returnpath[BUFSIZ]; + char deliverydate[BUFSIZ]; +#endif + + /* first-time only initialization */ + if (!scanl) { + if (width == 0) { + if ((width = sc_width ()) < WIDTH/2) + width = WIDTH/2; + else if (width > MAXSCANL) + width = MAXSCANL; + } + dat[3] = slwidth = width; + if ((scanl = (char *) malloc((size_t) (slwidth + 2) )) == NULL) + adios (NULL, "unable to malloc scan line (%d bytes)", slwidth+2); + if (outnum) + umask(~m_gmprot()); + + /* Compile format string */ + ncomps = fmt_compile (nfs, &fmt) + 1; + +#ifdef JLR + fmt_top = fmt; +#endif /* JLR */ + FINDCOMP(bodycomp, "body"); + FINDCOMP(datecomp, "date"); + FINDCOMP(cptr, "folder"); + if (cptr && folder) + cptr->c_text = folder; + FINDCOMP(cptr, "encrypted"); + if (!cptr) + if ((cptr = (struct comp *) calloc (1, sizeof(*cptr)))) { + cptr->c_name = "encrypted"; + cptr->c_next = wantcomp[i = CHASH (cptr->c_name)]; + wantcomp[i] = cptr; + ncomps++; + } + FINDCOMP (cptr, "dtimenow"); + if (cptr) + cptr->c_text = getcpy(dtimenow (0)); + nxtbuf = compbuffers = (char **) calloc((size_t) ncomps, sizeof(char *)); + if (nxtbuf == NULL) + adios (NULL, "unable to allocate component buffers"); + used_buf = (struct comp **) calloc((size_t) (ncomps+1), + sizeof(struct comp *)); + if (used_buf == NULL) + adios (NULL, "unable to allocate component buffer stack"); + used_buf += ncomps+1; *--used_buf = 0; + rlwidth = bodycomp && (width > SBUFSIZ) ? width : SBUFSIZ; + for (i = ncomps; i--; ) + if ((*nxtbuf++ = malloc(rlwidth)) == NULL) + adios (NULL, "unable to allocate component buffer"); + } + + /* + * each-message initialization + */ + nxtbuf = compbuffers; + savecomp = used_buf; + tmpbuf = *nxtbuf++; + dat[0] = innum ? innum : outnum; + dat[1] = curflg; + dat[4] = unseen; + + /* + * Get the first field. If the message is non-empty + * and we're doing an "inc", open the output file. + */ + if ((state = m_getfld (FLD, name, tmpbuf, rlwidth, inb)) == FILEEOF) { + if (ferror(inb)) { + advise("read", "unable to"); /* "read error" */ + return SCNFAT; + } else { + return SCNEOF; + } + } + + if (outnum) { + if (outnum > 0) { + scnmsg = m_name (outnum); + if (*scnmsg == '?') /* msg num out of range */ + return SCNNUM; + } else { + scnmsg = "/dev/null"; + } + if ((scnout = fopen (scnmsg, "w")) == NULL) + adios (scnmsg, "unable to write"); +#ifdef RPATHS + /* + * Add the Return-Path and Delivery-Date + * header fields to message. + */ + if (get_returnpath (returnpath, sizeof(returnpath), + deliverydate, sizeof(deliverydate))) { + FPUTS ("Return-Path: "); + FPUTS (returnpath); + FPUTS ("Delivery-Date: "); + FPUTS (deliverydate); + } +#endif /* RPATHS */ + } + + /* scan - main loop */ + for (compnum = 1; ; state = m_getfld (state, name, tmpbuf, rlwidth, inb)) { + switch (state) { + case FLD: + case FLDPLUS: + compnum++; + if (outnum) { + FPUTS (name); + putc (':', scnout); + FPUTS (tmpbuf); + } + /* + * if we're interested in this component, save a pointer + * to the component text, then start using our next free + * buffer as the component temp buffer (buffer switching + * saves an extra copy of the component text). + */ + if ((cptr = wantcomp[CHASH(name)])) { + do { + if (!strcasecmp(name, cptr->c_name)) { + if (! cptr->c_text) { + cptr->c_text = tmpbuf; + for (cp = tmpbuf + strlen (tmpbuf) - 1; + cp >= tmpbuf; cp--) + if (isspace (*cp)) + *cp = 0; + else + break; + *--savecomp = cptr; + tmpbuf = *nxtbuf++; + } + break; + } + } while ((cptr = cptr->c_next)); + } + + while (state == FLDPLUS) { + state = m_getfld (state, name, tmpbuf, rlwidth, inb); + if (outnum) + FPUTS (tmpbuf); + } + break; + + case BODY: + compnum = -1; + if (! outnum) { + state = FILEEOF; /* stop now if scan cmd */ + goto finished; + } + putc ('\n', scnout); + FPUTS (tmpbuf); + /* + * performance hack: some people like to run "inc" on + * things like net.sources or large digests. We do a + * copy directly into the output buffer rather than + * going through an intermediate buffer. + * + * We need the amount of data m_getfld found & don't + * want to do a strlen on the long buffer so there's + * a hack in m_getfld to save the amount of data it + * returned in the global "msg_count". + */ +body:; + while (state == BODY) { +#ifdef LINUX_STDIO + if (scnout->_IO_write_ptr == scnout->_IO_write_end) { +#else + if (scnout->_cnt <= 0) { +#endif + if (fflush(scnout) == EOF) + adios (scnmsg, "write error on"); + } +#ifdef LINUX_STDIO + state = m_getfld(state, name, scnout->_IO_write_ptr, + (long)scnout->_IO_write_ptr-(long)scnout->_IO_write_end , inb); + scnout->_IO_write_ptr += msg_count; +#else + state = m_getfld( state, name, scnout->_ptr, -(scnout->_cnt), inb ); + scnout->_cnt -= msg_count; + scnout->_ptr += msg_count; +#endif + } + goto finished; + + case LENERR: + case FMTERR: + fprintf (stderr, + innum ? "??Format error (message %d) in " + : "??Format error in ", + outnum ? outnum : innum); + fprintf (stderr, "component %d\n", compnum); + + if (outnum) { + FPUTS ("\n\nBAD MSG:\n"); + FPUTS (name); + putc ('\n', scnout); + state = BODY; + goto body; + } + /* fall through */ + + case FILEEOF: + goto finished; + + default: + adios (NULL, "getfld() returned %d", state); + } + } + + /* + * format and output the scan line. + */ +finished: + if (ferror(inb)) { + advise("read", "unable to"); /* "read error" */ + return SCNFAT; + } + + /* Save and restore buffer so we don't trash our dynamic pool! */ + if (bodycomp) { + saved_c_text = bodycomp->c_text; + bodycomp->c_text = tmpbuf; + } + + if (size) + dat[2] = size; + else if (outnum > 0) + dat[2] = ftell(scnout); + + if ((datecomp && !datecomp->c_text) || (!size && !outnum)) { + struct stat st; + + fstat (fileno(inb), &st); + if (!size && !outnum) + dat[2] = st.st_size; + if (datecomp) { + if (! datecomp->c_text) { + if (datecomp->c_tws == NULL) + datecomp->c_tws = (struct tws *) + calloc((size_t) 1, sizeof(*datecomp->c_tws)); + if (datecomp->c_tws == NULL) + adios (NULL, "unable to allocate tws buffer"); + *datecomp->c_tws = *dlocaltime ((time_t *) &st.st_mtime); + datecomp->c_flags = -1; + } else { + datecomp->c_flags = 0; + } + } + } + + fmt_scan (fmt, scanl, slwidth, dat); + +#if 0 + fmt = fmt_scan (fmt, scanl, slwidth, dat); + if (!fmt) + fmt = fmt_top; /* reset for old format files */ +#endif + + if (bodycomp) + bodycomp->c_text = saved_c_text; + + if (noisy) + fputs (scanl, stdout); + + FINDCOMP (cptr, "encrypted"); + encrypted = cptr && cptr->c_text; + + /* return dynamically allocated buffers to pool */ + while ((cptr = *savecomp++)) { + *--nxtbuf = cptr->c_text; + cptr->c_text = NULL; + } + *--nxtbuf = tmpbuf; + + if (outnum && fclose (scnout) == EOF) + adios (scnmsg, "write error on"); + + return (state != FILEEOF ? SCNERR : encrypted ? SCNENC : SCNMSG); +} + + +/* + * Cheat: we are loaded with adrparse, which wants a routine called + * OfficialName(). We call adrparse:getm() with the correct arguments + * to prevent OfficialName() from being called. Hence, the following + * is to keep the loader happy. + */ +char * +OfficialName (char *name) +{ + return name; +} + + +static int +mh_fputs(char *s, FILE *stream) +{ + char c; + + while ((c = *s++)) + if (putc (c,stream) == EOF ) + return(EOF); + return (0); +} + diff --git a/uip/send.c b/uip/send.c new file mode 100644 index 0000000..2370ea4 --- /dev/null +++ b/uip/send.c @@ -0,0 +1,432 @@ + +/* + * send.c -- send a composed message + * + * $Id$ + */ + +#include +#include +#include +#include + + +static struct swit switches[] = { +#define ALIASW 0 + { "alias aliasfile", 0 }, +#define DEBUGSW 1 + { "debug", -5 }, +#define DRAFTSW 2 + { "draft", 0 }, +#define DFOLDSW 3 + { "draftfolder +folder", 6 }, +#define DMSGSW 4 + { "draftmessage msg", 6 }, +#define NDFLDSW 5 + { "nodraftfolder", 0 }, +#define FILTSW 6 + { "filter filterfile", 0 }, +#define NFILTSW 7 + { "nofilter", 0 }, +#define FRMTSW 8 + { "format", 0 }, +#define NFRMTSW 9 + { "noformat", 0 }, +#define FORWSW 10 + { "forward", 0 }, +#define NFORWSW 11 + { "noforward", 0 }, +#define MIMESW 12 + { "mime", 0 }, +#define NMIMESW 13 + { "nomime", 0 }, +#define MSGDSW 14 + { "msgid", 0 }, +#define NMSGDSW 15 + { "nomsgid", 0 }, +#define PUSHSW 16 + { "push", 0 }, +#define NPUSHSW 17 + { "nopush", 0 }, +#define SPLITSW 18 + { "split seconds", 0 }, +#define UNIQSW 19 + { "unique", -6 }, +#define NUNIQSW 20 + { "nounique", -8 }, +#define VERBSW 21 + { "verbose", 0 }, +#define NVERBSW 22 + { "noverbose", 0 }, +#define WATCSW 23 + { "watch", 0 }, +#define NWATCSW 24 + { "nowatch", 0 }, +#define WIDTHSW 25 + { "width columns", 0 }, +#define VERSIONSW 26 + { "version", 0 }, +#define HELPSW 27 + { "help", 4 }, +#define BITSTUFFSW 28 + { "dashstuffing", -12 }, +#define NBITSTUFFSW 29 + { "nodashstuffing", -14 }, +#define MAILSW 30 + { "mail", -4 }, +#define SAMLSW 31 + { "saml", -4 }, +#define SENDSW 32 + { "send", -4 }, +#define SOMLSW 33 + { "soml", -4 }, +#define CLIESW 34 + { "client host", -6 }, +#define SERVSW 35 + { "server host", -6 }, +#define SNOOPSW 36 + { "snoop", -5 }, + { NULL, 0 } +}; + +static struct swit anyl[] = { +#define NOSW 0 + { "no", 0 }, +#define YESW 1 + { "yes", 0 }, +#define LISTDSW 2 + { "list", 0 }, + { NULL, 0 } +}; + +extern int debugsw; /* from sendsbr.c */ +extern int forwsw; +extern int inplace; +extern int pushsw; +extern int splitsw; +extern int unique; +extern int verbsw; + +extern char *altmsg; /* .. */ +extern char *annotext; +extern char *distfile; + +extern int errno; + +int +main (int argc, char **argv) +{ + int msgp = 0, distsw = 0, vecp = 1; + int isdf = 0, mime = 0; + int msgnum, status; + char *cp, *dfolder = NULL, *maildir = NULL; + char buf[BUFSIZ], **ap, **argp, **arguments; + char *msgs[MAXARGS], *vec[MAXARGS]; + struct msgs *mp; + struct stat st; +#ifdef UCI + FILE *fp; +#endif /* UCI */ + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + vec[vecp++] = "-library"; + vec[vecp++] = getcpy (m_maildir ("")); + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown\n", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [file] [switches]", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case DRAFTSW: + msgs[msgp++] = draft; + continue; + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + msgs[msgp++] = cp; + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + + case PUSHSW: + pushsw++; + continue; + case NPUSHSW: + pushsw = 0; + continue; + + case SPLITSW: + if (!(cp = *argp++) || sscanf (cp, "%d", &splitsw) != 1) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case UNIQSW: + unique++; + continue; + case NUNIQSW: + unique = 0; + continue; + + case FORWSW: + forwsw++; + continue; + case NFORWSW: + forwsw = 0; + continue; + + case VERBSW: + verbsw++; + vec[vecp++] = --cp; + continue; + case NVERBSW: + verbsw = 0; + vec[vecp++] = --cp; + continue; + + case MIMESW: + mime++; + vec[vecp++] = --cp; + continue; + case NMIMESW: + mime = 0; + vec[vecp++] = --cp; + continue; + + case DEBUGSW: + debugsw++; /* fall */ + case NFILTSW: + case FRMTSW: + case NFRMTSW: + case BITSTUFFSW: + case NBITSTUFFSW: + case MSGDSW: + case NMSGDSW: + case WATCSW: + case NWATCSW: + case MAILSW: + case SAMLSW: + case SENDSW: + case SOMLSW: + case SNOOPSW: + vec[vecp++] = --cp; + continue; + + case ALIASW: + case FILTSW: + case WIDTHSW: + case CLIESW: + case SERVSW: + vec[vecp++] = --cp; + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + vec[vecp++] = cp; + continue; + } + } else { + msgs[msgp++] = cp; + } + } + + /* + * check for "Aliasfile:" profile entry + */ + if ((cp = context_find ("Aliasfile"))) { + char *dp = NULL; + + for (ap = brkstring(dp = getcpy(cp), " ", "\n"); ap && *ap; ap++) { + vec[vecp++] = "-alias"; + vec[vecp++] = *ap; + } + } + + if (dfolder == NULL) { + if (msgp == 0) { +#ifdef WHATNOW + if ((cp = getenv ("mhdraft")) && *cp) { + msgs[msgp++] = cp; + goto go_to_it; + } +#endif /* WHATNOW */ + msgs[msgp++] = getcpy (m_draft (NULL, NULL, 1, &isdf)); + if (stat (msgs[0], &st) == NOTOK) + adios (msgs[0], "unable to stat draft file"); + cp = concat ("Use \"", msgs[0], "\"? ", NULL); + for (status = LISTDSW; status != YESW;) { + if (!(argp = getans (cp, anyl))) + done (1); + switch (status = smatch (*argp, anyl)) { + case NOSW: + done (0); + case YESW: + break; + case LISTDSW: + showfile (++argp, msgs[0]); + break; + default: + advise (NULL, "say what?"); + break; + } + } + } else { + for (msgnum = 0; msgnum < msgp; msgnum++) + msgs[msgnum] = getcpy (m_maildir (msgs[msgnum])); + } + } else { + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (!msgp) + msgs[msgp++] = "cur"; + maildir = m_maildir (dfolder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (dfolder))) + adios (NULL, "unable to read folder %s", dfolder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", dfolder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous-sequence */ + + for (msgp = 0, msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected (mp, msgnum)) { + msgs[msgp++] = getcpy (m_name (msgnum)); + unset_exists (mp, msgnum); + } + } + + mp->msgflags |= SEQMOD; + seq_save (mp); + } + +#ifdef WHATNOW +go_to_it: +#endif /* WHATNOW */ + + if ((cp = getenv ("SIGNATURE")) == NULL || *cp == 0) + if ((cp = context_find ("signature")) && *cp) + m_putenv ("SIGNATURE", cp); +#ifdef UCI + else { + snprintf (buf, sizeof(buf), "%s/.signature", mypath); + if ((fp = fopen (buf, "r")) != NULL + && fgets (buf, sizeof buf, fp) != NULL) { + fclose (fp); + if (cp = strchr (buf, '\n')) + *cp = 0; + m_putenv ("SIGNATURE", buf); + } + } +#endif /* UCI */ + + for (msgnum = 0; msgnum < msgp; msgnum++) + if (stat (msgs[msgnum], &st) == NOTOK) + adios (msgs[msgnum], "unable to stat draft file"); + + if ((annotext = getenv ("mhannotate")) == NULL || *annotext == 0) + annotext = NULL; + if (annotext && ((cp = getenv ("mhinplace")) != NULL && *cp != 0)) + inplace = atoi (cp); + if ((altmsg = getenv ("mhaltmsg")) == NULL || *altmsg == 0) + altmsg = NULL; /* used by dist interface - see below */ + + if ((cp = getenv ("mhdist")) + && *cp + && (distsw = atoi (cp)) + && altmsg) { + vec[vecp++] = "-dist"; + distfile = getcpy (m_scratch (altmsg, invo_name)); + if (link (altmsg, distfile) == NOTOK) { + if (errno != EXDEV +#ifdef EISREMOTE + && errno != EISREMOTE +#endif /* EISREMOTE */ + ) + adios (distfile, "unable to link %s to", altmsg); + free (distfile); + distfile = getcpy (m_tmpfil (invo_name)); + { + int in, out; + struct stat st; + + if ((in = open (altmsg, O_RDONLY)) == NOTOK) + adios (altmsg, "unable to open"); + fstat(in, &st); + if ((out = creat (distfile, (int) st.st_mode & 0777)) == NOTOK) + adios (distfile, "unable to write"); + cpydata (in, out, altmsg, distfile); + close (in); + close (out); + } + } + } else { + distfile = NULL; + } + + if (altmsg == NULL || stat (altmsg, &st) == NOTOK) { + st.st_mtime = 0; + st.st_dev = 0; + st.st_ino = 0; + } + if (pushsw) + push (); + + status = 0; + vec[0] = r1bindex (postproc, '/'); + closefds (3); + + for (msgnum = 0; msgnum < msgp; msgnum++) { + switch (sendsbr (vec, vecp, msgs[msgnum], &st, 1)) { + case DONE: + done (++status); + case NOTOK: + status++; /* fall */ + case OK: + break; + } + } + + context_save (); /* save the context file */ + done (status); +} diff --git a/uip/sendsbr.c b/uip/sendsbr.c new file mode 100644 index 0000000..245a538 --- /dev/null +++ b/uip/sendsbr.c @@ -0,0 +1,650 @@ + +/* + * sendsbr.c -- routines to help WhatNow/Send along + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include + +int debugsw = 0; /* global */ +int forwsw = 1; +int inplace = 1; +int pushsw = 0; +int splitsw = -1; +int unique = 0; +int verbsw = 0; + +char *altmsg = NULL; /* .. */ +char *annotext = NULL; +char *distfile = NULL; + +static int armed = 0; +static jmp_buf env; + +/* + * external prototypes + */ +int sendsbr (char **, int, char *, struct stat *, int); +void done (int); +char *getusername (void); + +/* + * static prototypes + */ +static void alert (char *, int); +static int tmp_fd (void); +static void anno (int, struct stat *); +static void annoaux (int); +static int splitmsg (char **, int, char *, struct stat *, int); +static int sendaux (char **, int, char *, struct stat *); + + +/* + * Entry point into (back-end) routines to send message. + */ + +int +sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft) +{ + int status; + char buffer[BUFSIZ], file[BUFSIZ]; + struct stat sts; + + armed++; + switch (setjmp (env)) { + case OK: + /* + * If given -push and -unique (which is undocumented), then + * rename the draft file. I'm not quite sure why. + */ + if (pushsw && unique) { + if (rename (drft, strncpy (file, m_scratch (drft, invo_name), sizeof(file))) + == NOTOK) + adios (file, "unable to rename %s to", drft); + drft = file; + } + + /* + * Check if we need to split the message into + * multiple messages of type "message/partial". + */ + if (splitsw >= 0 && !distfile && stat (drft, &sts) != NOTOK + && sts.st_size >= CPERMSG) { + status = splitmsg (vec, vecp, drft, st, splitsw) ? NOTOK : OK; + } else { + status = sendaux (vec, vecp, drft, st) ? NOTOK : OK; + } + + /* rename the original draft */ + if (rename_drft && status == OK && + rename (drft, strncpy (buffer, m_backup (drft), sizeof(buffer))) == NOTOK) + advise (buffer, "unable to rename %s to", drft); + break; + + default: + status = DONE; + break; + } + + armed = 0; + if (distfile) + unlink (distfile); + + return status; +} + + +/* + * Split large message into several messages of + * type "message/partial" and send them. + */ + +static int +splitmsg (char **vec, int vecp, char *drft, struct stat *st, int delay) +{ + int compnum, nparts, partno, state, status; + long pos, start; + time_t clock; + char *cp, *dp, buffer[BUFSIZ], msgid[BUFSIZ]; + char subject[BUFSIZ]; + char name[NAMESZ], partnum[BUFSIZ]; + FILE *in; + + if ((in = fopen (drft, "r")) == NULL) + adios (drft, "unable to open for reading"); + + cp = dp = NULL; + start = 0L; + + /* + * Scan through the message and examine the various header fields, + * as well as locate the beginning of the message body. + */ + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buffer, sizeof(buffer), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; + + /* + * This header field is discarded. + */ + if (!strcasecmp (name, "Message-ID")) { + while (state == FLDPLUS) + state = m_getfld (state, name, buffer, sizeof(buffer), in); + } else if (uprf (name, XXX_FIELD_PRF) + || !strcasecmp (name, VRSN_FIELD) + || !strcasecmp (name, "Subject") + || !strcasecmp (name, "Encrypted")) { + /* + * These header fields are copied to the enclosed + * header of the first message in the collection + * of message/partials. For the "Subject" header + * field, we also record it, so that a modified + * version of it, can be copied to the header + * of each messsage/partial in the collection. + */ + if (!strcasecmp (name, "Subject")) { + size_t sublen; + + strncpy (subject, buffer, BUFSIZ); + sublen = strlen (subject); + if (sublen > 0 && subject[sublen - 1] == '\n') + subject[sublen - 1] = '\0'; + } + + dp = add (concat (name, ":", buffer, NULL), dp); + while (state == FLDPLUS) { + state = m_getfld (state, name, buffer, sizeof(buffer), in); + dp = add (buffer, dp); + } + } else { + /* + * These header fields are copied to the header of + * each message/partial in the collection. + */ + cp = add (concat (name, ":", buffer, NULL), cp); + while (state == FLDPLUS) { + state = m_getfld (state, name, buffer, sizeof(buffer), in); + cp = add (buffer, cp); + } + } + + if (state != FLDEOF) { + start = ftell (in) + 1; + continue; + } + /* else fall... */ + + case BODY: + case BODYEOF: + case FILEEOF: + break; + + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", compnum); + + default: + adios (NULL, "getfld () returned %d", state); + } + + break; + } + if (cp == NULL) + adios (NULL, "headers missing from draft"); + + nparts = 1; + pos = start; + while (fgets (buffer, sizeof(buffer) - 1, in)) { + long len; + + if ((pos += (len = strlen (buffer))) > CPERMSG) { + nparts++; + pos = len; + } + } + + /* Only one part, nothing to split */ + if (nparts == 1) { + free (cp); + if (dp) + free (dp); + + fclose (in); + return sendaux (vec, vecp, drft, st); + } + + if (!pushsw) { + printf ("Sending as %d Partial Messages\n", nparts); + fflush (stdout); + } + status = OK; + + vec[vecp++] = "-partno"; + vec[vecp++] = partnum; + if (delay == 0) + vec[vecp++] = "-queued"; + + time (&clock); + snprintf (msgid, sizeof(msgid), "<%d.%ld@%s>", + (int) getpid(), (long) clock, LocalName()); + + fseek (in, start, SEEK_SET); + for (partno = 1; partno <= nparts; partno++) { + char tmpdrf[BUFSIZ]; + FILE *out; + + strncpy (tmpdrf, m_scratch (drft, invo_name), sizeof(tmpdrf)); + if ((out = fopen (tmpdrf, "w")) == NULL) + adios (tmpdrf, "unable to open for writing"); + chmod (tmpdrf, 0600); + + /* + * Output the header fields + */ + fputs (cp, out); + fprintf (out, "Subject: %s (part %d of %d)\n", subject, partno, nparts); + fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE); + fprintf (out, "%s: message/partial; id=\"%s\";\n", TYPE_FIELD, msgid); + fprintf (out, "\tnumber=%d; total=%d\n", partno, nparts); + fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno, nparts); + + /* + * If this is the first in the collection, output the + * header fields we are encapsulating at the beginning + * of the body of the first message. + */ + if (partno == 1) { + if (dp) + fputs (dp, out); + fprintf (out, "Message-ID: %s\n", msgid); + fprintf (out, "\n"); + } + + pos = 0; + for (;;) { + long len; + + if (!fgets (buffer, sizeof(buffer) - 1, in)) { + if (partno == nparts) + break; + adios (NULL, "premature eof"); + } + + if ((pos += (len = strlen (buffer))) > CPERMSG) { + fseek (in, -len, SEEK_CUR); + break; + } + + fputs (buffer, out); + } + + if (fflush (out)) + adios (tmpdrf, "error writing to"); + + fclose (out); + + if (!pushsw && verbsw) { + printf ("\n"); + fflush (stdout); + } + + /* Pause here, if a delay is specified */ + if (delay > 0 && 1 < partno && partno <= nparts) { + if (!pushsw) { + printf ("pausing %d seconds before sending part %d...\n", + delay, partno); + fflush (stdout); + } + sleep ((unsigned int) delay); + } + + snprintf (partnum, sizeof(partnum), "%d", partno); + status = sendaux (vec, vecp, tmpdrf, st); + unlink (tmpdrf); + if (status != OK) + break; + + /* + * This is so sendaux will only annotate + * the altmsg the first time it is called. + */ + annotext = NULL; + } + + free (cp); + if (dp) + free (dp); + + fclose (in); /* close the draft */ + return status; +} + + +/* + * Annotate original message, and + * call `postproc' to send message. + */ + +static int +sendaux (char **vec, int vecp, char *drft, struct stat *st) +{ + pid_t child_id; + int i, status, fd, fd2; + char backup[BUFSIZ], buf[BUFSIZ]; + + fd = pushsw ? tmp_fd () : NOTOK; + fd2 = NOTOK; + + vec[vecp++] = drft; + if (annotext) { + if ((fd2 = tmp_fd ()) != NOTOK) { + vec[vecp++] = "-idanno"; + snprintf (buf, sizeof(buf), "%d", fd2); + vec[vecp++] = buf; + } else { + admonish (NULL, "unable to create file for annotation list"); + } + } + if (distfile && distout (drft, distfile, backup) == NOTOK) + done (1); + vec[vecp] = NULL; + + for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++) + sleep (5); + + switch (child_id) { + case -1: + /* oops -- fork error */ + adios ("fork", "unable to"); + break; /* NOT REACHED */ + + case 0: + /* + * child process -- send it + * + * If fd is ok, then we are pushing and fd points to temp + * file, so capture anything on stdout and stderr there. + */ + if (fd != NOTOK) { + dup2 (fd, fileno (stdout)); + dup2 (fd, fileno (stderr)); + close (fd); + } + execvp (postproc, vec); + fprintf (stderr, "unable to exec "); + perror (postproc); + _exit (-1); + break; /* NOT REACHED */ + + default: + /* + * parent process -- wait for it + */ + if ((status = pidwait(child_id, NOTOK)) == OK) { + if (annotext && fd2 != NOTOK) + anno (fd2, st); + } else { + /* + * If postproc failed, and we have good fd (which means + * we pushed), then mail error message (and possibly the + * draft) back to the user. + */ + if (fd != NOTOK) { + alert (drft, fd); + close (fd); + } else { + advise (NULL, "message not delivered to anyone"); + } + if (annotext && fd2 != NOTOK) + close (fd2); + if (distfile) { + unlink (drft); + if (rename (backup, drft) == NOTOK) + advise (drft, "unable to rename %s to", backup); + } + } + break; + } + + return status; +} + + +/* + * Mail error notification (and possibly a copy of the + * message) back to the user, using the mailproc + */ + +static void +alert (char *file, int out) +{ + pid_t child_id; + int i, in; + char buf[BUFSIZ]; + + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep (5); + + switch (child_id) { + case NOTOK: + /* oops -- fork error */ + advise ("fork", "unable to"); + + case OK: + /* child process -- send it */ + SIGNAL (SIGHUP, SIG_IGN); + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); + SIGNAL (SIGTERM, SIG_IGN); + if (forwsw) { + if ((in = open (file, O_RDONLY)) == NOTOK) { + admonish (file, "unable to re-open"); + } else { + lseek (out, (off_t) 0, SEEK_END); + strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf)); + write (out, buf, strlen (buf)); + strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf)); + write (out, buf, strlen (buf)); + cpydgst (in, out, file, "temporary file"); + close (in); + strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf)); + write (out, buf, strlen (buf)); + if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK) + admonish (buf, "unable to rename %s to", file); + } + } + lseek (out, (off_t) 0, SEEK_SET); + dup2 (out, fileno (stdin)); + close (out); + /* create subject for error notification */ + snprintf (buf, sizeof(buf), "send failed on %s", + forwsw ? "enclosed draft" : file); + + execlp (mailproc, r1bindex (mailproc, '/'), getusername (), + "-subject", buf, NULL); + fprintf (stderr, "unable to exec "); + perror (mailproc); + _exit (-1); + + default: /* no waiting... */ + break; + } +} + + +static int +tmp_fd (void) +{ + int fd; + char tmpfil[BUFSIZ]; + + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((fd = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK) + return NOTOK; + if (debugsw) + advise (NULL, "temporary file %s selected", tmpfil); + else + if (unlink (tmpfil) == NOTOK) + advise (tmpfil, "unable to remove"); + + return fd; +} + + +static void +anno (int fd, struct stat *st) +{ + pid_t child_id; + sigset_t set, oset; + static char *cwd = NULL; + struct stat st2; + + if (altmsg && + (stat (altmsg, &st2) == NOTOK + || st->st_mtime != st2.st_mtime + || st->st_dev != st2.st_dev + || st->st_ino != st2.st_ino)) { + if (debugsw) + admonish (NULL, "$mhaltmsg mismatch"); + return; + } + + child_id = debugsw ? NOTOK : fork (); + switch (child_id) { + case NOTOK: /* oops */ + if (!debugsw) + advise (NULL, + "unable to fork, so doing annotations by hand..."); + if (cwd == NULL) + cwd = getcpy (pwd ()); + + case OK: + /* block a few signals */ + sigemptyset (&set); + sigaddset (&set, SIGHUP); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGQUIT); + sigaddset (&set, SIGTERM); + SIGPROCMASK (SIG_BLOCK, &set, &oset); + + annoaux (fd); + if (child_id == OK) + _exit (0); + + /* reset the signal mask */ + SIGPROCMASK (SIG_SETMASK, &oset, &set); + + chdir (cwd); + break; + + default: /* no waiting... */ + close (fd); + break; + } +} + + +static void +annoaux (int fd) +{ + int fd2, fd3, msgnum; + char *cp, *folder, *maildir; + char buffer[BUFSIZ], **ap; + FILE *fp; + struct msgs *mp; + + if ((folder = getenv ("mhfolder")) == NULL || *folder == 0) { + if (debugsw) + admonish (NULL, "$mhfolder not set"); + return; + } + maildir = m_maildir (folder); + if (chdir (maildir) == NOTOK) { + if (debugsw) + admonish (maildir, "unable to change directory to"); + return; + } + if (!(mp = folder_read (folder))) { + if (debugsw) + admonish (NULL, "unable to read folder %s"); + return; + } + + /* check for empty folder */ + if (mp->nummsg == 0) { + if (debugsw) + admonish (NULL, "no messages in %s", folder); + goto oops; + } + + if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) { + if (debugsw) + admonish (NULL, "$mhmessages not set"); + goto oops; + } + if (!debugsw /* MOBY HACK... */ + && pushsw + && (fd3 = open ("/dev/null", O_RDWR)) != NOTOK + && (fd2 = dup (fileno (stderr))) != NOTOK) { + dup2 (fd3, fileno (stderr)); + close (fd3); + } + else + fd2 = NOTOK; + for (ap = brkstring (cp = getcpy (cp), " ", NULL); *ap; ap++) + m_convert (mp, *ap); + free (cp); + if (fd2 != NOTOK) + dup2 (fd2, fileno (stderr)); + if (mp->numsel == 0) { + if (debugsw) + admonish (NULL, "no messages to annotate"); + goto oops; + } + + lseek (fd, (off_t) 0, SEEK_SET); + if ((fp = fdopen (fd, "r")) == NULL) { + if (debugsw) + admonish (NULL, "unable to fdopen annotation list"); + goto oops; + } + cp = NULL; + while (fgets (buffer, sizeof(buffer), fp) != NULL) + cp = add (buffer, cp); + fclose (fp); + + if (debugsw) + advise (NULL, "annotate%s with %s: \"%s\"", + inplace ? " inplace" : "", annotext, cp); + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + if (debugsw) + advise (NULL, "annotate message %d", msgnum); + annotate (m_name (msgnum), annotext, cp, inplace, 1); + } + } + + free (cp); + +oops: + folder_free (mp); /* free folder/message structure */ +} + + +void +done (int status) +{ + if (armed) + longjmp (env, status ? status : NOTOK); + + exit (status); +} diff --git a/uip/show.c b/uip/show.c new file mode 100644 index 0000000..5ab6370 --- /dev/null +++ b/uip/show.c @@ -0,0 +1,532 @@ + +/* + * show.c -- show/list messages + * + * $Id$ + */ + +#include +#include + +static struct swit switches[] = { +#define CHECKMIMESW 0 + { "checkmime", 0 }, +#define NOCHECKMIMESW 1 + { "nocheckmime", 0 }, +#define HEADSW 2 + { "header", 0 }, +#define NHEADSW 3 + { "noheader", 0 }, +#define FORMSW 4 + { "form formfile", 0 }, +#define PROGSW 5 + { "moreproc program", 0 }, +#define NPROGSW 6 + { "nomoreproc", 0 }, +#define LENSW 7 + { "length lines", 0 }, +#define WIDTHSW 8 + { "width columns", 0 }, +#define SHOWSW 9 + { "showproc program", 0 }, +#define SHOWMIMESW 10 + { "showmimeproc program", 0 }, +#define NSHOWSW 11 + { "noshowproc", 0 }, +#define DRFTSW 12 + { "draft", 0 }, +#define FILESW 13 + { "file file", -4 }, /* interface from showfile */ +#define VERSIONSW 14 + { "version", 0 }, +#define HELPSW 15 + { "help", 0 }, + { NULL, 0 } +}; + +/* + * static prototypes + */ +static int is_nontext(char *); + +#define SHOW 0 +#define NEXT 1 +#define PREV 2 + + +int +main (int argc, char **argv) +{ + int draftsw = 0, headersw = 1, msgp = 0; + int nshow = 0, checkmime = 1, mime; + int vecp = 1, procp = 1, isdf = 0, mode = SHOW, msgnum; + char *cp, *maildir, *file = NULL, *folder = NULL, *proc; + char buf[BUFSIZ], **argp, **arguments; + char *msgs[MAXARGS], *vec[MAXARGS]; + struct msgs *mp; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + if (!strcasecmp (invo_name, "next")) { + mode = NEXT; + } else if (!strcasecmp (invo_name, "prev")) { + mode = PREV; + } + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + case NPROGSW: + vec[vecp++] = --cp; + continue; + + case HELPSW: + snprintf (buf, sizeof(buf), + "%s [+folder] %s[switches] [switches for showproc]", + invo_name, mode == SHOW ? "[msgs] ": ""); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case DRFTSW: + if (file) + adios (NULL, "only one file at a time!"); + draftsw++; + if (mode == SHOW) + continue; +usage: + adios (NULL, + "usage: %s [+folder] [switches] [switches for showproc]", + invo_name); + case FILESW: + if (mode != SHOW) + goto usage; + if (draftsw || file) + adios (NULL, "only one file at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + file = path (cp, TFILE); + continue; + + case HEADSW: + headersw++; + continue; + case NHEADSW: + headersw = 0; + continue; + + case FORMSW: + vec[vecp++] = --cp; + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + vec[vecp++] = getcpy (etcpath(cp)); + continue; + + case PROGSW: + case LENSW: + case WIDTHSW: + vec[vecp++] = --cp; + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + vec[vecp++] = cp; + continue; + + case SHOWSW: + if (!(showproc = *argp++) || *showproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nshow = 0; + continue; + case NSHOWSW: + nshow++; + continue; + + case SHOWMIMESW: + if (!(showmimeproc = *argp++) || *showmimeproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nshow = 0; + continue; + case CHECKMIMESW: + checkmime++; + continue; + case NOCHECKMIMESW: + checkmime = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + if (mode != SHOW) + goto usage; + else + msgs[msgp++] = cp; + } + } + procp = vecp; + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + + if (draftsw || file) { + if (msgp) + adios (NULL, "only one file at a time!"); + vec[vecp++] = draftsw + ? getcpy (m_draft (folder, msgp ? msgs[0] : NULL, 1, &isdf)) + : file; + goto go_to_it; + } + +#ifdef WHATNOW + if (!msgp && !folder && mode == SHOW && (cp = getenv ("mhdraft")) && *cp) { + draftsw++; + vec[vecp++] = cp; + goto go_to_it; + } +#endif /* WHATNOW */ + + if (!msgp) { + switch (mode) { + case NEXT: + msgs[msgp++] = "next"; + break; + case PREV: + msgs[msgp++] = "prev"; + break; + default: + msgs[msgp++] = "cur"; + break; + } + } + + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < msgp; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + + /* + * Set the SELECT_UNSEEN bit for all the SELECTED messages, + * since we will use that as a tag to know which messages + * to remove from the "unseen" sequence. + */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected(mp, msgnum)) + set_unseen (mp, msgnum); + + seq_setprev (mp); /* set the Previous-Sequence */ + seq_setunseen (mp, 1); /* unset the Unseen-Sequence */ + + if (mp->numsel > MAXARGS - 2) + adios (NULL, "more than %d messages for show exec", MAXARGS - 2); + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected(mp, msgnum)) + vec[vecp++] = getcpy (m_name (msgnum)); + + seq_setcur (mp, mp->hghsel); /* update current message */ + seq_save (mp); /* synchronize sequences */ + context_replace (pfolder, folder); /* update current folder */ + context_save (); /* save the context file */ + + if (headersw && vecp == 2) + printf ("(Message %s:%s)\n", folder, vec[1]); + +go_to_it: ; + fflush (stdout); + + vec[vecp] = NULL; + + /* + * Decide which "proc" to use + */ + mime = 0; + if (nshow) { + proc = catproc; + } else { + /* check if any messages are non-text MIME messages */ + if (checkmime && !getenv ("NOMHNPROC")) { + if (!draftsw && !file) { + /* loop through selected messages and check for MIME */ + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) + if (is_selected (mp, msgnum) && is_nontext (m_name (msgnum))) { + mime = 1; + break; + } + } else { + /* check the file or draft for MIME */ + if (is_nontext (vec[vecp - 1])) + mime = 1; + } + } + + /* Set the "proc" */ + if (mime) + proc = showmimeproc; + else + proc = showproc; + } + + if (folder && !draftsw && !file) + m_putenv ("mhfolder", folder); + + /* + * For backward compatibility, if the "proc" is mhn, + * then add "-show" option. Add "-file" if showing + * file or draft. + */ + if (strcmp (r1bindex (proc, '/'), "mhn") == 0) { + if (draftsw || file) { + vec[vecp] = vec[vecp - 1]; + vec[vecp - 1] = "-file"; + vecp++; + } + vec[vecp++] = "-show"; + vec[vecp] = NULL; + } + + /* + * If "proc" is mhl, then run it internally + * rather than exec'ing it. + */ + if (strcmp (r1bindex (proc, '/'), "mhl") == 0) { + vec[0] = "mhl"; + mhl (vecp, vec); + done (0); + } + + /* + * If you are not using a nmh command as your "proc", then + * add the path to the message names. Currently, we are just + * checking for mhn here, since we've already taken care of mhl. + */ + if (!strcmp (r1bindex (proc, '/'), "mhl") + && !draftsw + && !file + && chdir (maildir = concat (m_maildir (""), "/", NULL)) != NOTOK) { + mp->foldpath = concat (mp->foldpath, "/", NULL); + cp = ssequal (maildir, mp->foldpath) + ? mp->foldpath + strlen (maildir) + : mp->foldpath; + for (msgnum = procp; msgnum < vecp; msgnum++) + vec[msgnum] = concat (cp, vec[msgnum], NULL); + } + + vec[0] = r1bindex (proc, '/'); + execvp (proc, vec); + adios (proc, "unable to exec"); +} + +/* + * Cheat: we are loaded with adrparse, which wants a routine called + * OfficialName(). We call adrparse:getm() with the correct arguments + * to prevent OfficialName() from being called. Hence, the following + * is to keep the loader happy. + */ + +char * +OfficialName (char *name) +{ + return name; +} + + +/* + * Check if a message or file contains any non-text parts + */ +static int +is_nontext (char *msgnam) +{ + int result, state; + char *bp, *cp, *dp; + char buf[BUFSIZ], name[NAMESZ]; + FILE *fp; + + if ((fp = fopen (msgnam, "r")) == NULL) + return 0; + + for (state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + /* + * Check Content-Type field + */ + if (!strcasecmp (name, TYPE_FIELD)) { + int passno; + char c; + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), fp); + cp = add (buf, cp); + } + bp = cp; + passno = 1; + +again: + for (; isspace (*bp); bp++) + continue; + if (*bp == '(') { + int i; + + for (bp++, i = 0;;) { + switch (*bp++) { + case '\0': +invalid: + result = 0; + goto out; + case '\\': + if (*bp++ == '\0') + goto invalid; + continue; + case '(': + i++; + /* and fall... */ + default: + continue; + case ')': + if (--i < 0) + break; + continue; + } + break; + } + } + if (passno == 2) { + if (*bp != '/') + goto invalid; + bp++; + passno = 3; + goto again; + } + for (dp = bp; istoken (*dp); dp++) + continue; + c = *dp; + *dp = '\0'; + if (!*bp) + goto invalid; + if (passno > 1) { + if ((result = (strcasecmp (bp, "plain") != 0))) + goto out; + *dp = c; + for (dp++; isspace (*dp); dp++) + continue; + if (*dp) { + if ((result = !uprf (dp, "charset"))) + goto out; + dp += sizeof("charset") - 1; + while (isspace (*dp)) + dp++; + if (*dp++ != '=') + goto invalid; + while (isspace (*dp)) + dp++; + if (*dp == '"') { + if ((bp = strchr(++dp, '"'))) + *bp = '\0'; + } else { + for (bp = dp; *bp; bp++) + if (isspace (*bp)) { + *bp = '\0'; + break; + } + } + } else { + /* Default character set */ + dp = "US-ASCII"; + } + /* Check the character set */ + result = !check_charset (dp, strlen (dp)); + } else { + if (!(result = (strcasecmp (bp, "text") != 0))) { + *dp = c; + bp = dp; + passno = 2; + goto again; + } + } +out: + free (cp); + if (result) { + fclose (fp); + return result; + } + break; + } + + /* + * Check Content-Transfer-Encoding field + */ + if (!strcasecmp (name, ENCODING_FIELD)) { + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), fp); + cp = add (buf, cp); + } + for (bp = cp; isspace (*bp); bp++) + continue; + for (dp = bp; istoken (*dp); dp++) + continue; + *dp = '\0'; + result = (strcasecmp (bp, "7bit") + && strcasecmp (bp, "8bit") + && strcasecmp (bp, "binary")); + + free (cp); + if (result) { + fclose (fp); + return result; + } + break; + } + + /* + * Just skip the rest of this header + * field and go to next one. + */ + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), fp); + break; + + /* + * We've passed the message header, + * so message is just text. + */ + default: + fclose (fp); + return 0; + } + } +} diff --git a/uip/slocal.c b/uip/slocal.c new file mode 100644 index 0000000..c1ee80f --- /dev/null +++ b/uip/slocal.c @@ -0,0 +1,1543 @@ + +/* + * slocal.c -- asynchronously filter and deliver new mail + * + * $Id$ + */ + +/* + * Under sendmail, users should add the line + * + * "| /usr/local/nmh/lib/slocal" + * + * to their $HOME/.forward file. + * + * Under MMDF-I, users should (symbolically) link + * /usr/local/nmh/lib/slocal to $HOME/bin/rcvmail. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#ifndef UTMP_FILE +# ifdef _PATH_UTMP +# define UTMP_FILE _PATH_UTMP +# else +# define UTMP_FILE "/etc/utmp" +# endif +#endif + +static struct swit switches[] = { +#define ADDRSW 0 + { "addr address", 0 }, +#define USERSW 1 + { "user name", 0 }, +#define FILESW 2 + { "file file", 0 }, +#define SENDERSW 3 + { "sender address", 0 }, +#define MAILBOXSW 4 + { "mailbox file", 0 }, +#define HOMESW 5 + { "home directory", -4 }, +#define INFOSW 6 + { "info data", 0 }, +#define MAILSW 7 + { "maildelivery file", 0 }, +#define VERBSW 8 + { "verbose", 0 }, +#define NVERBSW 9 + { "noverbose", 0 }, +#define SUPPRESSDUP 10 + { "suppressdup", 0 }, +#define NSUPPRESSDUP 11 + { "nosuppressdup", 0 }, +#define DEBUGSW 12 + { "debug", 0 }, +#define VERSIONSW 13 + { "version", 0 }, +#define HELPSW 14 + { "help", 4 }, + { NULL, 0 } +}; + +static int globbed = 0; /* have we built "vars" table yet? */ +static int parsed = 0; /* have we built header field table yet */ +static int utmped = 0; /* have we scanned umtp(x) file yet */ +static int suppressdup = 0; /* are we suppressing duplicate messages? */ + +static int verbose = 0; +static int debug = 0; + +static char *addr = NULL; +static char *user = NULL; +static char *info = NULL; +static char *file = NULL; +static char *sender = NULL; +static char *envelope = NULL; /* envelope information ("From " line) */ +static char *mbox = NULL; +static char *home = NULL; + +static struct passwd *pw; /* passwd file entry */ + +static char ddate[BUFSIZ]; /* record the delivery date */ +struct tws *now; + +static jmp_buf myctx; + +/* flags for pair->p_flags */ +#define P_NIL 0x00 +#define P_ADR 0x01 /* field is address */ +#define P_HID 0x02 /* special (fake) field */ +#define P_CHK 0x04 + +struct pair { + char *p_name; + char *p_value; + char p_flags; +}; + +#define NVEC 100 + +/* + * Lookup table for matching fields and patterns + * in messages. The rest of the table is added + * when the message is parsed. + */ +static struct pair hdrs[NVEC + 1] = { + { "source", NULL, P_HID }, + { "addr", NULL, P_HID }, + { "Return-Path", NULL, P_ADR }, + { "Reply-To", NULL, P_ADR }, + { "From", NULL, P_ADR }, + { "Sender", NULL, P_ADR }, + { "To", NULL, P_ADR }, + { "cc", NULL, P_ADR }, + { "Resent-Reply-To", NULL, P_ADR }, + { "Resent-From", NULL, P_ADR }, + { "Resent-Sender", NULL, P_ADR }, + { "Resent-To", NULL, P_ADR }, + { "Resent-cc", NULL, P_ADR }, + { NULL, NULL, 0 } +}; + +/* + * The list of builtin variables to expand in a string + * before it is executed by the "pipe" or "qpipe" action. + */ +static struct pair vars[] = { + { "sender", NULL, P_NIL }, + { "address", NULL, P_NIL }, + { "size", NULL, P_NIL }, + { "reply-to", NULL, P_CHK }, + { "info", NULL, P_NIL }, + { NULL, NULL, 0 } +}; + +extern char **environ; + +/* + * static prototypes + */ +static int localmail (int, char *); +static int usr_delivery (int, char *, int); +static int split (char *, char **); +static int parse (int); +static void expand (char *, char *, int); +static void glob (int); +static struct pair *lookup (struct pair *, char *); +static int logged_in (void); +static int timely (char *, char *); +static int usr_file (int, char *, int); +static int usr_pipe (int, char *, char *, char **, int); +static int usr_folder (int, char *); +static RETSIGTYPE alrmser (int); +static void get_sender (char *, char **); +static int copy_message (int, char *, int); +static void verbose_printf (char *fmt, ...); +static void adorn (char *, char *, ...); +static void debug_printf (char *fmt, ...); +static int suppress_duplicates (int, char *); +static char *trim (char *); + + +int +main (int argc, char **argv) +{ + int fd, status; + FILE *fp = stdin; + char *cp, *mdlvr = NULL, buf[BUFSIZ]; + char mailbox[BUFSIZ], tmpfil[BUFSIZ]; + char **argp, **arguments; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (*argv, '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + /* Parse arguments */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), + "%s [switches] [address info sender]", invo_name); + print_help (buf, switches, 0); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case ADDRSW: + if (!(addr = *argp++))/* allow -xyz arguments */ + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case INFOSW: + if (!(info = *argp++))/* allow -xyz arguments */ + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case USERSW: + if (!(user = *argp++))/* allow -xyz arguments */ + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case FILESW: + if (!(file = *argp++) || *file == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case SENDERSW: + if (!(sender = *argp++))/* allow -xyz arguments */ + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case MAILBOXSW: + if (!(mbox = *argp++) || *mbox == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case HOMESW: + if (!(home = *argp++) || *home == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case MAILSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (mdlvr) + adios (NULL, "only one maildelivery file at a time!"); + mdlvr = cp; + continue; + + case VERBSW: + verbose++; + continue; + case NVERBSW: + verbose = 0; + continue; + + case SUPPRESSDUP: + suppressdup++; + continue; + case NSUPPRESSDUP: + suppressdup = 0; + continue; + case DEBUGSW: + debug++; + continue; + } + } + + switch (argp - (argv + 1)) { + case 1: + addr = cp; + break; + + case 2: + info = cp; + break; + + case 3: + sender = cp; + break; + } + } + + if (addr == NULL) + addr = getusername (); + if (user == NULL) + user = (cp = strchr(addr, '.')) ? ++cp : addr; + if ((pw = getpwnam (user)) == NULL) + adios (NULL, "no such local user as %s", user); + + if (chdir (pw->pw_dir) == -1) + chdir ("/"); + umask (0077); + + if (geteuid() == 0) { + setgid (pw->pw_gid); + initgroups (pw->pw_name, pw->pw_gid); + setuid (pw->pw_uid); + } + + if (info == NULL) + info = ""; + + setbuf (stdin, NULL); + + /* Record the delivery time */ + if ((now = dlocaltimenow ()) == NULL) + adios (NULL, "unable to ascertain local time"); + snprintf (ddate, sizeof(ddate), "Delivery-Date: %s\n", dtimenow (0)); + + /* + * Copy the message to a temporary file + */ + if (file) { + int tempfd; + + /* getting message from file */ + if ((tempfd = open (file, O_RDONLY)) == -1) + adios (file, "unable to open"); + if (debug) + debug_printf ("retrieving message from file \"%s\"\n", file); + if ((fd = copy_message (tempfd, tmpfil, 1)) == -1) + adios (NULL, "unable to create temporary file"); + close (tempfd); + } else { + /* getting message from stdin */ + if (debug) + debug_printf ("retrieving message from stdin\n"); + if ((fd = copy_message (fileno (stdin), tmpfil, 1)) == -1) + adios (NULL, "unable to create temporary file"); + } + if (debug) + debug_printf ("temporary file=\"%s\"\n", tmpfil); + else + unlink (tmpfil); + + if (!(fp = fdopen (fd, "r+"))) + adios (NULL, "unable to access temporary file"); + + /* + * If no sender given, extract it + * from envelope information. + */ + if (sender == NULL) + get_sender (envelope, &sender); + + if (mbox == NULL) { + snprintf (mailbox, sizeof(mailbox), "%s/%s", + mmdfldir[0] ? mmdfldir : pw->pw_dir, + mmdflfil[0] ? mmdflfil : pw->pw_name); + mbox = mailbox; + } + if (home == NULL) + home = pw->pw_dir; + + if (debug) { + debug_printf ("addr=\"%s\"\n", trim(addr)); + debug_printf ("user=\"%s\"\n", trim(user)); + debug_printf ("info=\"%s\"\n", trim(info)); + debug_printf ("sender=\"%s\"\n", trim(sender)); + debug_printf ("envelope=\"%s\"\n", envelope ? trim(envelope) : ""); + debug_printf ("mbox=\"%s\"\n", trim(mbox)); + debug_printf ("home=\"%s\"\n", trim(home)); + debug_printf ("ddate=\"%s\"\n", trim(ddate)); + debug_printf ("now=%02d:%02d\n\n", now->tw_hour, now->tw_min); + } + + /* deliver the message */ + status = localmail (fd, mdlvr); + + done (status != -1 ? RCV_MOK : RCV_MBX); +} + + +/* + * Main routine for delivering message. + */ + +static int +localmail (int fd, char *mdlvr) +{ + /* check if this message is a duplicate */ + if (suppressdup && + suppress_duplicates(fd, mdlvr ? mdlvr : ".maildelivery") == DONE) + return 0; + + /* delivery according to personal Maildelivery file */ + if (usr_delivery (fd, mdlvr ? mdlvr : ".maildelivery", 0) != -1) + return 0; + + /* delivery according to global Maildelivery file */ + if (usr_delivery (fd, maildelivery, 1) != -1) + return 0; + + if (verbose) + verbose_printf ("(delivering to standard mail spool)\n"); + + /* last resort - deliver to standard mail spool */ +#ifdef SLOCAL_MBOX + return usr_file (fd, mbox, MBOX_FORMAT); +#else + return usr_file (fd, mbox, MMDF_FORMAT); +#endif +} + + +#define matches(a,b) (stringdex (b, a) >= 0) + +/* + * Parse the delivery file, and process incoming message. + */ + +static int +usr_delivery (int fd, char *delivery, int su) +{ + int i, accept, status, won, vecp, next; + char *field, *pattern, *action, *result, *string; + char buffer[BUFSIZ], tmpbuf[BUFSIZ]; + char *cp, *vec[NVEC]; + struct stat st; + struct pair *p; + FILE *fp; + + /* open the delivery file */ + if ((fp = fopen (delivery, "r")) == NULL) + return -1; + + /* check if delivery file has bad ownership or permissions */ + if (fstat (fileno (fp), &st) == -1 + || (st.st_uid != 0 && (su || st.st_uid != pw->pw_uid)) + || st.st_mode & (S_IWGRP|S_IWOTH)) { + if (verbose) { + verbose_printf ("WARNING: %s has bad ownership/modes (su=%d,uid=%d,owner=%d,mode=0%o)\n", + delivery, su, (int) pw->pw_uid, (int) st.st_uid, (int) st.st_mode); + } + return -1; + } + + won = 0; + next = 1; + + /* read and process delivery file */ + while (fgets (buffer, sizeof(buffer), fp)) { + /* skip comments and empty lines */ + if (*buffer == '#' || *buffer == '\n') + continue; + + /* zap trailing newline */ + if ((cp = strchr(buffer, '\n'))) + *cp = 0; + + /* split buffer into fields */ + vecp = split (buffer, vec); + + /* check for too few fields */ + if (vecp < 5) { + if (debug) + debug_printf ("WARNING: entry with only %d fields, skipping.\n", vecp); + continue; + } + + if (debug) { + for (i = 0; vec[i]; i++) + debug_printf ("vec[%d]: \"%s\"\n", i, trim(vec[i])); + } + + field = vec[0]; + pattern = vec[1]; + action = vec[2]; + result = vec[3]; + string = vec[4]; + + /* find out how to perform the action */ + switch (result[0]) { + case 'N': + case 'n': + /* + * If previous condition failed, don't + * do this - else fall through + */ + if (!next) + continue; /* else fall */ + + case '?': + /* + * If already delivered, skip this action. Else + * consider delivered if action is successful. + */ + if (won) + continue; /* else fall */ + + case 'A': + case 'a': + /* + * Take action, and consider delivered if + * action is successful. + */ + accept = 1; + break; + + case 'R': + case 'r': + default: + /* + * Take action, but don't consider delivered, even + * if action is successful + */ + accept = 0; + break; + } + + if (vecp > 5) { + if (!strcasecmp (vec[5], "select")) { + if (logged_in () != -1) + continue; + if (vecp > 7 && timely (vec[6], vec[7]) == -1) + continue; + } + } + + /* check if the field matches */ + switch (*field) { + case '*': + /* always matches */ + break; + + case 'd': + /* + * "default" matches only if the message hasn't + * been delivered yet. + */ + if (!strcasecmp (field, "default")) { + if (won) + continue; + break; + } /* else fall */ + + default: + /* parse message and build lookup table */ + if (!parsed && parse (fd) == -1) { + fclose (fp); + return -1; + } + /* + * find header field in lookup table, and + * see if the pattern matches. + */ + if ((p = lookup (hdrs, field)) && (p->p_value != NULL) + && matches (p->p_value, pattern)) { + next = 1; + } else { + next = 0; + continue; + } + break; + } + + /* find out the action to perform */ + switch (*action) { + case 'q': + /* deliver to quoted pipe */ + if (strcasecmp (action, "qpipe")) + continue; /* else fall */ + case '^': + expand (tmpbuf, string, fd); + if (split (tmpbuf, vec) < 1) + continue; + status = usr_pipe (fd, tmpbuf, vec[0], vec, 0); + break; + + case 'p': + /* deliver to pipe */ + if (strcasecmp (action, "pipe")) + continue; /* else fall */ + case '|': + vec[2] = "sh"; + vec[3] = "-c"; + expand (tmpbuf, string, fd); + vec[4] = tmpbuf; + vec[5] = NULL; + status = usr_pipe (fd, tmpbuf, "/bin/sh", vec + 2, 0); + break; + + case 'f': + /* mbox format */ + if (!strcasecmp (action, "file")) { + status = usr_file (fd, string, MBOX_FORMAT); + break; + } + /* deliver to nmh folder */ + else if (strcasecmp (action, "folder")) + continue; /* else fall */ + case '+': + status = usr_folder (fd, string); + break; + + case 'm': + /* mmdf format */ + if (!strcasecmp (action, "mmdf")) { + status = usr_file (fd, string, MMDF_FORMAT); + break; + } + /* mbox format */ + else if (strcasecmp (action, "mbox")) + continue; /* else fall */ + + case '>': + /* mbox format */ + status = usr_file (fd, string, MBOX_FORMAT); + break; + + case 'd': + /* ignore message */ + if (strcasecmp (action, "destroy")) + continue; + status = 0; + break; + } + + if (accept && status == 0) + won++; + } + + fclose (fp); + return (won ? 0 : -1); +} + + +#define QUOTE '\\' + +/* + * Split buffer into fields (delimited by whitespace or + * comma's). Return the number of fields found. + */ + +static int +split (char *cp, char **vec) +{ + int i; + char *s; + + s = cp; + + /* split into a maximum of NVEC fields */ + for (i = 0; i <= NVEC;) { + vec[i] = NULL; + + /* zap any whitespace and comma's */ + while (isspace (*s) || *s == ',') + *s++ = 0; + + /* end of buffer, time to leave */ + if (*s == 0) + break; + + /* get double quote text as a single field */ + if (*s == '"') { + for (vec[i++] = ++s; *s && *s != '"'; s++) { + /* + * Check for escaped double quote. We need + * to shift the string to remove slash. + */ + if (*s == QUOTE) { + if (*++s == '"') + strcpy (s - 1, s); + s--; + } + } + if (*s == '"') /* zap trailing double quote */ + *s++ = 0; + continue; + } + + if (*s == QUOTE && *++s != '"') + s--; + vec[i++] = s++; + + /* move forward to next field delimiter */ + while (*s && !isspace (*s) && *s != ',') + s++; + } + vec[i] = NULL; + + return i; +} + + +/* + * Parse the headers of a message, and build the + * lookup table for matching fields and patterns. + */ + +static int +parse (int fd) +{ + int i, state; + int fd1; + char *cp, *dp, *lp; + char name[NAMESZ], field[BUFSIZ]; + struct pair *p, *q; + FILE *in; + + if (parsed++) + return 0; + + /* get a new FILE pointer to message */ + if ((fd1 = dup (fd)) == -1) + return -1; + if ((in = fdopen (fd1, "r")) == NULL) { + close (fd1); + return -1; + } + rewind (in); + + /* add special entries to lookup table */ + if ((p = lookup (hdrs, "source"))) + p->p_value = getcpy (sender); + if ((p = lookup (hdrs, "addr"))) + p->p_value = getcpy (addr); + + /* + * Scan the headers of the message and build + * a lookup table. + */ + for (i = 0, state = FLD;;) { + switch (state = m_getfld (state, name, field, sizeof(field), in)) { + case FLD: + case FLDEOF: + case FLDPLUS: + lp = add (field, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, field, sizeof(field), in); + lp = add (field, lp); + } + for (p = hdrs; p->p_name; p++) { + if (!strcasecmp (p->p_name, name)) { + if (!(p->p_flags & P_HID)) { + if ((cp = p->p_value)) + if (p->p_flags & P_ADR) { + dp = cp + strlen (cp) - 1; + if (*dp == '\n') + *dp = 0; + cp = add (",\n\t", cp); + } else { + cp = add ("\t", cp); + } + p->p_value = add (lp, cp); + } + free (lp); + break; + } + } + if (p->p_name == NULL && i < NVEC) { + p->p_name = getcpy (name); + p->p_value = lp; + p->p_flags = P_NIL; + p++, i++; + p->p_name = NULL; + } + if (state != FLDEOF) + continue; + break; + + case BODY: + case BODYEOF: + case FILEEOF: + break; + + case LENERR: + case FMTERR: + advise (NULL, "format error in message"); + break; + + default: + advise (NULL, "internal error in m_getfld"); + fclose (in); + return -1; + } + break; + } + fclose (in); + + if ((p = lookup (vars, "reply-to"))) { + if ((q = lookup (hdrs, "reply-to")) == NULL || q->p_value == NULL) + q = lookup (hdrs, "from"); + p->p_value = getcpy (q ? q->p_value : ""); + p->p_flags &= ~P_CHK; + if (debug) + debug_printf ("vars[%d]: name=\"%s\" value=\"%s\"\n", + p - vars, p->p_name, trim(p->p_value)); + } + if (debug) { + for (p = hdrs; p->p_name; p++) + debug_printf ("hdrs[%d]: name=\"%s\" value=\"%s\"\n", + p - hdrs, p->p_name, p->p_value ? trim(p->p_value) : ""); + } + + return 0; +} + + +#define LPAREN '(' +#define RPAREN ')' + +/* + * Expand any builtin variables such as $(sender), + * $(address), etc., in a string. + */ + +static void +expand (char *s1, char *s2, int fd) +{ + char c, *cp; + struct pair *p; + + if (!globbed) + glob (fd); + + while ((c = *s2++)) { + if (c != '$' || *s2 != LPAREN) { + *s1++ = c; + } else { + for (cp = ++s2; *s2 && *s2 != RPAREN; s2++) + continue; + if (*s2 != RPAREN) { + s2 = --cp; + continue; + } + *s2++ = 0; + if ((p = lookup (vars, cp))) { + if (!parsed && (p->p_flags & P_CHK)) + parse (fd); + + strcpy (s1, p->p_value); + s1 += strlen (s1); + } + } + } + *s1 = 0; +} + + +/* + * Fill in the information missing from the "vars" + * table, which is necessary to expand any builtin + * variables in the string for a "pipe" or "qpipe" + * action. + */ + +static void +glob (int fd) +{ + char buffer[BUFSIZ]; + struct stat st; + struct pair *p; + + if (globbed++) + return; + + if ((p = lookup (vars, "sender"))) + p->p_value = getcpy (sender); + if ((p = lookup (vars, "address"))) + p->p_value = getcpy (addr); + if ((p = lookup (vars, "size"))) { + snprintf (buffer, sizeof(buffer), "%d", + fstat (fd, &st) != -1 ? (int) st.st_size : 0); + p->p_value = getcpy (buffer); + } + if ((p = lookup (vars, "info"))) + p->p_value = getcpy (info); + + if (debug) { + for (p = vars; p->p_name; p++) + debug_printf ("vars[%d]: name=\"%s\" value=\"%s\"\n", + p - vars, p->p_name, trim(p->p_value)); + } +} + + +/* + * Find a matching name in a lookup table. If found, + * return the "pairs" entry, else return NULL. + */ + +static struct pair * +lookup (struct pair *pairs, char *key) +{ + for (; pairs->p_name; pairs++) + if (!strcasecmp (pairs->p_name, key)) + return pairs; + + return NULL; +} + + +/* + * Check utmp(x) file to see if user is currently + * logged in. + */ + +static int +logged_in (void) +{ + struct utmp ut; + FILE *uf; + + if (utmped) + return utmped; + + if ((uf = fopen (UTMP_FILE, "r")) == NULL) + return NOTOK; + + while (fread ((char *) &ut, sizeof(ut), 1, uf) == 1) { + if (ut.ut_name[0] != 0 + && strncmp (user, ut.ut_name, sizeof(ut.ut_name)) == 0) { + if (debug) + continue; + fclose (uf); + return (utmped = DONE); + } + } + + fclose (uf); + return (utmped = NOTOK); +} + + +#define check(t,a,b) if (t < a || t > b) return -1 +#define cmpar(h1,m1,h2,m2) if (h1 < h2 || (h1 == h2 && m1 < m2)) return 0 + +static int +timely (char *t1, char *t2) +{ + int t1hours, t1mins, t2hours, t2mins; + + if (sscanf (t1, "%d:%d", &t1hours, &t1mins) != 2) + return -1; + check (t1hours, 0, 23); + check (t1mins, 0, 59); + + if (sscanf (t2, "%d:%d", &t2hours, &t2mins) != 2) + return -1; + check (t2hours, 0, 23); + check (t2mins, 0, 59); + + cmpar (now->tw_hour, now->tw_min, t1hours, t1mins); + cmpar (t2hours, t2mins, now->tw_hour, now->tw_min); + + return -1; +} + + +/* + * Deliver message by appending to a file. + */ + +static int +usr_file (int fd, char *mailbox, int mbx_style) +{ + int md, mapping; + + if (verbose) + verbose_printf ("delivering to file \"%s\"", mailbox); + + if (mbx_style == MBOX_FORMAT) { + if (verbose) + verbose_printf (" (mbox style)"); + mapping = 0; + } else { + if (verbose) + verbose_printf (" (mmdf style)"); + mapping = 1; + } + + /* open and lock the file */ + if ((md = mbx_open (mailbox, mbx_style, pw->pw_uid, pw->pw_gid, m_gmprot())) == -1) { + if (verbose) + adorn ("", "unable to open:"); + return -1; + } + + lseek (fd, (off_t) 0, SEEK_SET); + + /* append message to file */ + if (mbx_copy (mailbox, mbx_style, md, fd, mapping, NULL, verbose) == -1) { + if (verbose) + adorn ("", "error writing to:"); + return -1; + } + + /* close and unlock file */ + mbx_close (mailbox, md); + + if (verbose) + verbose_printf (", success.\n"); + return 0; +} + + +/* + * Deliver message to a nmh folder. + */ + +static int +usr_folder (int fd, char *string) +{ + int status; + char folder[BUFSIZ], *vec[3]; + + /* get folder name ready */ + if (*string == '+') + strncpy(folder, string, sizeof(folder)); + else + snprintf(folder, sizeof(folder), "+%s", string); + + if (verbose) + verbose_printf ("delivering to folder \"%s\"", folder + 1); + + vec[0] = "rcvstore"; + vec[1] = folder; + vec[2] = NULL; + + /* use rcvstore to put message in folder */ + status = usr_pipe (fd, "rcvstore", rcvstoreproc, vec, 1); + +#if 0 + /* + * Currently, verbose status messages are handled by usr_pipe(). + */ + if (verbose) { + if (status == 0) + verbose_printf (", success.\n"); + else + verbose_printf (", failed.\n"); + } +#endif + + return status; +} + +/* + * Deliver message to a process. + */ + +static int +usr_pipe (int fd, char *cmd, char *pgm, char **vec, int suppress) +{ + pid_t child_id; + int i, bytes, seconds, status; + struct stat st; + + if (verbose && !suppress) + verbose_printf ("delivering to pipe \"%s\"", cmd); + + lseek (fd, (off_t) 0, SEEK_SET); + + for (i = 0; (child_id = fork()) == -1 && i < 5; i++) + sleep (5); + + switch (child_id) { + case -1: + /* fork error */ + if (verbose) + adorn ("fork", "unable to"); + return -1; + + case 0: + /* child process */ + if (fd != 0) + dup2 (fd, 0); + freopen ("/dev/null", "w", stdout); + freopen ("/dev/null", "w", stderr); + if (fd != 3) + dup2 (fd, 3); + closefds (4); + +#ifdef TIOCNOTTY + if ((fd = open ("/dev/tty", O_RDWR)) != -1) { + ioctl (fd, TIOCNOTTY, NULL); + close (fd); + } +#endif /* TIOCNOTTY */ + + setpgid ((pid_t) 0, getpid ()); /* put in own process group */ + + *environ = NULL; + m_putenv ("USER", pw->pw_name); + m_putenv ("HOME", pw->pw_dir); + m_putenv ("SHELL", pw->pw_shell); + + execvp (pgm, vec); + _exit (-1); + + default: + /* parent process */ + if (!setjmp (myctx)) { + SIGNAL (SIGALRM, alrmser); + bytes = fstat (fd, &st) != -1 ? (int) st.st_size : 100; + + /* amount of time to wait depends on message size */ + if (bytes <= 100) { + /* give at least 5 minutes */ + seconds = 300; + } else if (bytes >= 90000) { + /* a half hour is long enough */ + seconds = 1800; + } else { + seconds = (bytes / 60) + 300; + } + alarm ((unsigned int) seconds); + status = pidwait (child_id, 0); + alarm (0); + +#ifdef MMDFI + if (status == RP_MOK || status == RP_OK) + status = 0; +#endif + if (verbose) { + if (status == 0) + verbose_printf (", success.\n"); + else + if ((status & 0xff00) == 0xff00) + verbose_printf (", system error\n"); + else + pidstatus (status, stdout, ", failed"); + } + return (status == 0 ? 0 : -1); + } else { + /* + * Ruthlessly kill the child and anything + * else in its process group. + */ + KILLPG(child_id, SIGKILL); + if (verbose) + verbose_printf (", timed-out; terminated\n"); + return -1; + } + } +} + + +static RETSIGTYPE +alrmser (int i) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (SIGALRM, alrmser); +#endif + + longjmp (myctx, DONE); +} + + +/* + * Get the `sender' from the envelope + * information ("From " line). + */ + +static void +get_sender (char *envelope, char **sender) +{ + int i; + char *cp; + char buffer[BUFSIZ]; + + if (envelope == NULL) { + *sender = getcpy (""); + return; + } + + i = strlen ("From "); + strncpy (buffer, envelope + i, sizeof(buffer)); + if ((cp = strchr(buffer, '\n'))) { + *cp = 0; + cp -= 24; + if (cp < buffer) + cp = buffer; + } else { + cp = buffer; + } + *cp = 0; + + for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--) + if (isspace (*cp)) + *cp = 0; + else + break; + *sender = getcpy (buffer); +} + + +/* + * Copy message into a temporary file. + * While copying, it will do some header processing + * including the extraction of the envelope information. + */ + +static int +copy_message (int qd, char *tmpfil, int fold) +{ + int i, first = 1, fd1, fd2; + char buffer[BUFSIZ]; + FILE *qfp, *ffp; + + strcpy (tmpfil, m_tmpfil (invo_name)); + + /* open temporary file to put message in */ + if ((fd1 = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == -1) + return -1; + + if (!fold) { + while ((i = read (qd, buffer, sizeof(buffer))) > 0) + if (write (fd1, buffer, i) != i) { +you_lose: + close (fd1); + unlink (tmpfil); + return -1; + } + if (i == -1) + goto you_lose; + lseek (fd1, (off_t) 0, SEEK_SET); + return fd1; + } + + /* dup the fd for incoming message */ + if ((fd2 = dup (qd)) == -1) { + close (fd1); + return -1; + } + + /* now create a FILE pointer for it */ + if ((qfp = fdopen (fd2, "r")) == NULL) { + close (fd1); + close (fd2); + return -1; + } + + /* dup the fd for temporary file */ + if ((fd2 = dup (fd1)) == -1) { + close (fd1); + fclose (qfp); + return -1; + } + + /* now create a FILE pointer for it */ + if ((ffp = fdopen (fd2, "r+")) == NULL) { + close (fd1); + close (fd2); + fclose (qfp); + return -1; + } + + /* + * copy message into temporary file + * and massage the headers. Save + * a copy of the "From " line for later. + */ + i = strlen ("From "); + while (fgets (buffer, sizeof(buffer), qfp)) { + if (first) { + first = 0; + if (!strncmp (buffer, "From ", i)) { +#ifdef RPATHS + char *fp, *cp, *hp, *ep; +#endif + /* get copy of envelope information ("From " line) */ + envelope = getcpy (buffer); + +#if 0 + /* First go ahead and put "From " line in message */ + fputs (buffer, ffp); + if (ferror (ffp)) + goto fputs_error; +#endif + +#ifdef RPATHS + /* + * Now create a "Return-Path:" line + * from the "From " line. + */ + hp = cp = strchr(fp = envelope + i, ' '); + while ((hp = strchr(++hp, 'r'))) + if (uprf (hp, "remote from")) { + hp = strrchr(hp, ' '); + break; + } + if (hp) { + /* return path for UUCP style addressing */ + ep = strchr(++hp, '\n'); + snprintf (buffer, sizeof(buffer), "Return-Path: %.*s!%.*s\n", + ep - hp, hp, cp - fp, fp); + } else { + /* return path for standard domain addressing */ + snprintf (buffer, sizeof(buffer), "Return-Path: %.*s\n", + cp - fp, fp); + } + + /* Add Return-Path header to message */ + fputs (buffer, ffp); + if (ferror (ffp)) + goto fputs_error; +#endif + /* Put the delivery date in message */ + fputs (ddate, ffp); + if (ferror (ffp)) + goto fputs_error; + + continue; + } + } + + fputs (buffer, ffp); + if (ferror (ffp)) + goto fputs_error; + } + + fclose (ffp); + if (ferror (qfp)) { + close (fd1); + fclose (qfp); + return -1; + } + fclose (qfp); + lseek (fd1, (off_t) 0, SEEK_SET); + return fd1; + + +fputs_error: + close (fd1); + fclose (ffp); + fclose (qfp); + return -1; +} + +/* + * Trim strings for pretty printing of debugging output + */ + +static char * +trim (char *cp) +{ + char buffer[BUFSIZ*4]; + char *bp, *sp; + + if (cp == NULL) + return NULL; + + /* copy string into temp buffer */ + strncpy (buffer, cp, sizeof(buffer)); + bp = buffer; + + /* skip over leading whitespace */ + while (isspace(*bp)) + bp++; + + /* start at the end and zap trailing whitespace */ + for (sp = bp + strlen(bp) - 1; sp >= bp; sp--) { + if (isspace(*sp)) + *sp = 0; + else + break; + } + + /* replace remaining whitespace with spaces */ + for (sp = bp; *sp; sp++) + if (isspace(*sp)) + *sp = ' '; + + /* now return a copy */ + return getcpy(bp); +} + +/* + * Function for printing `verbose' messages. + */ + +static void +verbose_printf (char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf (stdout, fmt, ap); + va_end(ap); + + fflush (stdout); /* now flush output */ +} + + +/* + * Function for printing `verbose' delivery + * error messages. + */ + +static void +adorn (char *what, char *fmt, ...) +{ + va_list ap; + int eindex; + char *s; + + eindex = errno; /* save the errno */ + fprintf (stdout, ", "); + + va_start(ap, fmt); + vfprintf (stdout, fmt, ap); + va_end(ap); + + if (what) { + if (*what) + fprintf (stdout, " %s: ", what); + if ((s = strerror (eindex))) + fprintf (stdout, "%s", s); + else + fprintf (stdout, "Error %d", eindex); + } + + fputc ('\n', stdout); + fflush (stdout); +} + + +/* + * Function for printing `debug' messages. + */ + +static void +debug_printf (char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf (stderr, fmt, ap); + va_end(ap); +} + + +/* + * Check ndbm/db file(s) to see if the Message-Id of this + * message matches the Message-Id of a previous message, + * so we can discard it. If it doesn't match, we add the + * Message-Id of this message to the ndbm/db file. + */ +static int +suppress_duplicates (int fd, char *file) +{ + int fd1, lockfd, state, result; + char *cp, buf[BUFSIZ], name[NAMESZ]; + datum key, value; + DBM *db; + FILE *in; + + if ((fd1 = dup (fd)) == -1) + return -1; + if (!(in = fdopen (fd1, "r"))) { + close (fd1); + return -1; + } + rewind (in); + + for (state = FLD;;) { + state = m_getfld (state, name, buf, sizeof(buf), in); + switch (state) { + case FLD: + case FLDPLUS: + case FLDEOF: + /* Search for the message ID */ + if (strcasecmp (name, "Message-ID")) { + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), in); + continue; + } + + cp = add (buf, NULL); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + key.dptr = trimcpy (cp); + key.dsize = strlen (key.dptr) + 1; + free (cp); + cp = key.dptr; + + if (!(db = dbm_open (file, O_RDWR | O_CREAT, 0600))) { + advise (file, "unable to perform dbm_open on"); + free (cp); + fclose (in); + return -1; + } + /* + * Since it is difficult to portable lock a ndbm file, + * we will open and lock the Maildelivery file instead. + * This will fail if your Maildelivery file doesn't + * exist. + */ + if ((lockfd = lkopen(file, O_RDWR, 0)) == -1) { + advise (file, "unable to perform file locking on"); + free (cp); + fclose (in); + return -1; + } + value = dbm_fetch (db, key); + if (value.dptr) { + if (verbose) + verbose_printf ("Message-ID: %s\n already received on %s", + cp, value.dptr); + result = DONE; + } else { + value.dptr = ddate + sizeof("Delivery-Date:"); + value.dsize = strlen(value.dptr) + 1; + if (dbm_store (db, key, value, DBM_INSERT)) + advise (file, "possibly corrupt file"); + result = 0; + } + + dbm_close (db); + lkclose(lockfd, file); + free (cp); + fclose (in); + return result; + break; + + case BODY: + case BODYEOF: + case FILEEOF: + break; + + case LENERR: + case FMTERR: + default: + break; + } + + break; + } + + fclose (in); + return 0; +} diff --git a/uip/sortm.c b/uip/sortm.c new file mode 100644 index 0000000..398045b --- /dev/null +++ b/uip/sortm.c @@ -0,0 +1,583 @@ + +/* + * sortm.c -- sort messages in a folder by date/time + * + * $Id$ + */ + +#include +#include + +/* + * We allocate space for messages (msgs array) + * this number of elements at a time. + */ +#define MAXMSGS 256 + + +static struct swit switches[] = { +#define DATESW 0 + { "datefield field", 0 }, +#define TEXTSW 1 + { "textfield field", 0 }, +#define NSUBJSW 2 + { "notextfield", 0 }, +#define SUBJSW 3 + { "subject", -3 }, /* backward-compatibility */ +#define LIMSW 4 + { "limit days", 0 }, +#define NLIMSW 5 + { "nolimit", 0 }, +#define VERBSW 6 + { "verbose", 0 }, +#define NVERBSW 7 + { "noverbose", 0 }, +#define VERSIONSW 8 + { "version", 0 }, +#define HELPSW 9 + { "help", 4 }, + { NULL, 0 } +}; + +struct smsg { + int s_msg; + time_t s_clock; + char *s_subj; +}; + +static struct smsg *smsgs; +int nmsgs; + +char *subjsort = (char *) 0; /* sort on subject if != 0 */ +unsigned long datelimit = 0; +int submajor = 0; /* if true, sort on subject-major */ +int verbose; + +/* This keeps compiler happy on calls to qsort */ +typedef int (*qsort_comp) (const void *, const void *); + +/* + * static prototypes + */ +static int read_hdrs (struct msgs *, char *); +static int get_fields (char *, int, struct smsg *); +static int dsort (struct smsg **, struct smsg **); +static int subsort (struct smsg **, struct smsg **); +static int txtsort (struct smsg **, struct smsg **); +static void rename_chain (struct msgs *, struct smsg **, int, int); +static void rename_msgs (struct msgs *, struct smsg **); + + +int +main (int argc, char **argv) +{ + int nummsgs, maxmsgs, i, msgnum; + char *cp, *maildir, *datesw = NULL; + char *folder = NULL, buf[BUFSIZ], **argp; + char **arguments, **msgs; + struct msgs *mp; + struct smsg **dlist; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + /* + * Allocate the initial space to record message + * names and ranges. + */ + nummsgs = 0; + maxmsgs = MAXMSGS; + if (!(msgs = (char **) malloc ((size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to allocate storage"); + + /* + * Parse arguments + */ + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", + invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case DATESW: + if (datesw) + adios (NULL, "only one date field at a time"); + if (!(datesw = *argp++) || *datesw == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case TEXTSW: + if (subjsort) + adios (NULL, "only one text field at a time"); + if (!(subjsort = *argp++) || *subjsort == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case SUBJSW: + subjsort = "subject"; + continue; + case NSUBJSW: + subjsort = (char *)0; + continue; + + case LIMSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + while (*cp == '0') + cp++; /* skip any leading zeros */ + if (!*cp) { /* hit end of string */ + submajor++; /* sort subject-major */ + continue; + } + if (!isdigit(*cp) || !(datelimit = atoi(cp))) + adios (NULL, "impossible limit %s", cp); + datelimit *= 60*60*24; + continue; + case NLIMSW: + submajor = 0; /* use date-major, but */ + datelimit = 0; /* use no limit */ + continue; + + case VERBSW: + verbose++; + continue; + case NVERBSW: + verbose = 0; + continue; + } + } + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder at a time!"); + else + folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); + } else { + /* + * Check if we need to allocate more space + * for message names/ranges. + */ + if (nummsgs >= maxmsgs) { + maxmsgs += MAXMSGS; + if (!(msgs = (char **) realloc (msgs, + (size_t) (maxmsgs * sizeof(*msgs))))) + adios (NULL, "unable to reallocate msgs storage"); + } + msgs[nummsgs++] = cp; + } + } + + if (!context_find ("path")) + free (path ("./", TFOLDER)); + if (!nummsgs) + msgs[nummsgs++] = "all"; + if (!datesw) + datesw = "date"; + if (!folder) + folder = getfolder (1); + maildir = m_maildir (folder); + + if (chdir (maildir) == NOTOK) + adios (maildir, "unable to change directory to"); + + /* read folder and create message structure */ + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + + /* check for empty folder */ + if (mp->nummsg == 0) + adios (NULL, "no messages in %s", folder); + + /* parse all the message ranges/sequences and set SELECTED */ + for (msgnum = 0; msgnum < nummsgs; msgnum++) + if (!m_convert (mp, msgs[msgnum])) + done (1); + seq_setprev (mp); /* set the previous sequence */ + + if ((nmsgs = read_hdrs (mp, datesw)) <= 0) + adios (NULL, "no messages to sort"); + + /* + * sort a list of pointers to our "messages to be sorted". + */ + dlist = (struct smsg **) malloc ((nmsgs+1) * sizeof(*dlist)); + if (! dlist) + adios (NULL, "couldn't allocate sort memory"); + for (i = 0; i < nmsgs; i++) + dlist[i] = &smsgs[i]; + dlist[nmsgs] = 0; + + if (verbose) /* announce what we're doing */ + if (subjsort) + printf ("sorting by %s-major %s-minor\n", + submajor ? subjsort : datesw, + submajor ? datesw : subjsort); + else + printf ("sorting by datefield %s\n", datesw); + + /* first sort by date, or by subject-major, date-minor */ + qsort ((char *) dlist, nmsgs, sizeof(*dlist), + (qsort_comp) (submajor && subjsort ? txtsort : dsort)); + + /* + * if we're sorting on subject, we need another list + * in subject order, then a merge pass to collate the + * two sorts. + */ + if (!submajor && subjsort) { /* already date sorted */ + struct smsg **slist, **flist; + register struct smsg ***il, **fp, **dp; + + slist = (struct smsg **) malloc ((nmsgs+1) * sizeof(*slist)); + if (! slist) + adios (NULL, "couldn't allocate sort memory"); + memcpy((char *)slist, (char *)dlist, (nmsgs+1)*sizeof(*slist)); + qsort((char *)slist, nmsgs, sizeof(*slist), (qsort_comp) subsort); + + /* + * make an inversion list so we can quickly find + * the collection of messages with the same subj + * given a message number. + */ + il = (struct smsg ***) calloc (mp->hghsel+1, sizeof(*il)); + if (! il) + adios (NULL, "couldn't allocate msg list"); + for (i = 0; i < nmsgs; i++) + il[slist[i]->s_msg] = &slist[i]; + /* + * make up the final list, chronological but with + * all the same subjects grouped together. + */ + flist = (struct smsg **) malloc ((nmsgs+1) * sizeof(*flist)); + if (! flist) + adios (NULL, "couldn't allocate msg list"); + fp = flist; + for (dp = dlist; *dp;) { + register struct smsg **s = il[(*dp++)->s_msg]; + + /* see if we already did this guy */ + if (! s) + continue; + + *fp++ = *s++; + /* + * take the next message(s) if there is one, + * its subject isn't null and its subject + * is the same as this one and it's not too + * far away in time. + */ + while (*s && (*s)->s_subj[0] && + strcmp((*s)->s_subj, s[-1]->s_subj) == 0 && + (datelimit == 0 || + (*s)->s_clock - s[-1]->s_clock <= datelimit)) { + il[(*s)->s_msg] = 0; + *fp++ = *s++; + } + } + *fp = 0; + free (slist); + free (dlist); + dlist = flist; + } + rename_msgs (mp, dlist); + + context_replace (pfolder, folder); /* update current folder */ + seq_save (mp); /* synchronize message sequences */ + context_save (); /* save the context file */ + folder_free (mp); /* free folder/message structure */ + done (0); +} + +static int +read_hdrs (struct msgs *mp, char *datesw) +{ + int msgnum; + struct tws tb; + register struct smsg *s; + + twscopy (&tb, dlocaltimenow ()); + + smsgs = (struct smsg *) + calloc ((size_t) (mp->hghsel - mp->lowsel + 2), + sizeof(*smsgs)); + if (smsgs == NULL) + adios (NULL, "unable to allocate sort storage"); + + s = smsgs; + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + if (get_fields (datesw, msgnum, s)) { + s->s_msg = msgnum; + s++; + } + } + } + s->s_msg = 0; + return(s - smsgs); +} + + +/* + * Parse the message and get the data or subject field, + * if needed. + */ + +static int +get_fields (char *datesw, int msg, struct smsg *smsg) +{ + register int state; + int compnum; + char *msgnam, buf[BUFSIZ], nam[NAMESZ]; + register struct tws *tw; + register char *datecomp = NULL, *subjcomp = NULL; + register FILE *in; + + if ((in = fopen (msgnam = m_name (msg), "r")) == NULL) { + admonish (msgnam, "unable to read message"); + return (0); + } + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, nam, buf, sizeof(buf), in)) { + case FLD: + case FLDEOF: + case FLDPLUS: + compnum++; + if (!strcasecmp (nam, datesw)) { + datecomp = add (buf, datecomp); + while (state == FLDPLUS) { + state = m_getfld (state, nam, buf, sizeof(buf), in); + datecomp = add (buf, datecomp); + } + if (!subjsort || subjcomp) + break; + } else if (subjsort && !strcasecmp (nam, subjsort)) { + subjcomp = add (buf, subjcomp); + while (state == FLDPLUS) { + state = m_getfld (state, nam, buf, sizeof(buf), in); + subjcomp = add (buf, subjcomp); + } + if (datecomp) + break; + } else { + /* just flush this guy */ + while (state == FLDPLUS) + state = m_getfld (state, nam, buf, sizeof(buf), in); + } + continue; + + case BODY: + case BODYEOF: + case FILEEOF: + break; + + case LENERR: + case FMTERR: + if (state == LENERR || state == FMTERR) + admonish (NULL, "format error in message %d (header #%d)", + msg, compnum); + if (datecomp) + free (datecomp); + if (subjcomp) + free (subjcomp); + fclose (in); + return (0); + + default: + adios (NULL, "internal error -- you lose"); + } + break; + } + + /* + * If no date component, then use the modification + * time of the file as its date + */ + if (!datecomp || (tw = dparsetime (datecomp)) == NULL) { + struct stat st; + + admonish (NULL, "can't parse %s field in message %d", datesw, msg); + fstat (fileno (in), &st); + smsg->s_clock = st.st_mtime; + } else { + smsg->s_clock = dmktime (tw); + } + + if (subjsort) { + if (subjcomp) { + /* + * try to make the subject "canonical": delete + * leading "re:", everything but letters & smash + * letters to lower case. + */ + register char *cp, *cp2, c; + + cp = subjcomp; + cp2 = subjcomp; + if (strcmp (subjsort, "subject") == 0) + while ((c = *cp)) { + if (! isspace(c)) { + if(uprf(cp, "re:")) + cp += 2; + else { + if (isalnum(c)) + *cp2++ = isupper(c) ? tolower(c) : c; + break; + } + } + cp++; + } + while ((c = *cp++)) { + if (isalnum(c)) + *cp2++ = isupper(c) ? tolower(c) : c; + + } + *cp2 = '\0'; + } + else + subjcomp = ""; + + smsg->s_subj = subjcomp; + } + fclose (in); + if (datecomp) + free (datecomp); + + return (1); +} + +/* + * sort on dates. + */ +static int +dsort (struct smsg **a, struct smsg **b) +{ + if ((*a)->s_clock < (*b)->s_clock) + return (-1); + else if ((*a)->s_clock > (*b)->s_clock) + return (1); + else if ((*a)->s_msg < (*b)->s_msg) + return (-1); + else + return (1); +} + +/* + * sort on subjects. + */ +static int +subsort (struct smsg **a, struct smsg **b) +{ + register int i; + + if ((i = strcmp ((*a)->s_subj, (*b)->s_subj))) + return (i); + + return (dsort (a, b)); +} + +static int +txtsort (struct smsg **a, struct smsg **b) +{ + register int i; + + if ((i = strcmp ((*a)->s_subj, (*b)->s_subj))) + return (i); + else if ((*a)->s_msg < (*b)->s_msg) + return (-1); + else + return (1); +} + +static void +rename_chain (struct msgs *mp, struct smsg **mlist, int msg, int endmsg) +{ + int nxt, old, new; + char *newname, oldname[BUFSIZ]; + + for (;;) { + nxt = mlist[msg] - smsgs; /* mlist[msg] is a ptr into smsgs */ + mlist[msg] = (struct smsg *)0; + old = smsgs[nxt].s_msg; + new = smsgs[msg].s_msg; + strncpy (oldname, m_name (old), sizeof(oldname)); + newname = m_name (new); + if (verbose) + printf ("message %d becomes message %d\n", old, new); + + if (rename (oldname, newname) == NOTOK) + adios (newname, "unable to rename %s to", oldname); + + copy_msg_flags (mp, new, old); + if (mp->curmsg == old) + seq_setcur (mp, new); + + if (nxt == endmsg) + break; + + msg = nxt; + } +/* if (nxt != endmsg); */ +/* rename_chain (mp, mlist, nxt, endmsg); */ +} + +static void +rename_msgs (struct msgs *mp, struct smsg **mlist) +{ + int i, j, old, new; + seqset_t tmpset; + char f1[BUFSIZ], tmpfil[BUFSIZ]; + struct smsg *sp; + + strncpy (tmpfil, m_name (mp->hghmsg + 1), sizeof(tmpfil)); + + for (i = 0; i < nmsgs; i++) { + if (! (sp = mlist[i])) + continue; /* did this one */ + + j = sp - smsgs; + if (j == i) + continue; /* this one doesn't move */ + + /* + * the guy that was msg j is about to become msg i. + * rename 'j' to make a hole, then recursively rename + * guys to fill up the hole. + */ + old = smsgs[j].s_msg; + new = smsgs[i].s_msg; + strncpy (f1, m_name (old), sizeof(f1)); + + if (verbose) + printf ("renaming message chain from %d to %d\n", old, new); + + if (rename (f1, tmpfil) == NOTOK) + adios (tmpfil, "unable to rename %s to ", f1); + get_msg_flags (mp, &tmpset, old); + + rename_chain (mp, mlist, j, i); + if (rename (tmpfil, m_name(new)) == NOTOK) + adios (m_name(new), "unable to rename %s to", tmpfil); + + set_msg_flags (mp, &tmpset, new); + mp->msgflags |= SEQMOD; + } +} diff --git a/uip/spost.c b/uip/spost.c new file mode 100644 index 0000000..7d65ead --- /dev/null +++ b/uip/spost.c @@ -0,0 +1,833 @@ + +/* + * spost.c -- feed messages to sendmail + * + * This is a simpler, faster, replacement for "post" for use + * when "sendmail" is the transport system. + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include + +#define uptolow(c) ((isalpha(c) && isupper (c)) ? tolower (c) : c) + +#define MAX_SM_FIELD 1476 /* < largest hdr field sendmail will accept */ +#define FCCS 10 /* max number of fccs allowed */ + +struct swit switches[] = { +#define FILTSW 0 + { "filter filterfile", 0 }, +#define NFILTSW 1 + { "nofilter", 0 }, +#define FRMTSW 2 + { "format", 0 }, +#define NFRMTSW 3 + { "noformat", 0 }, +#define REMVSW 4 + { "remove", 0 }, +#define NREMVSW 5 + { "noremove", 0 }, +#define VERBSW 6 + { "verbose", 0 }, +#define NVERBSW 7 + { "noverbose", 0 }, +#define WATCSW 8 + { "watch", 0 }, +#define NWATCSW 9 + { "nowatch", 0 }, +#define BACKSW 10 + { "backup", 0 }, +#define NBACKSW 11 + { "nobackup", 0 }, +#define ALIASW 12 + { "alias aliasfile", 0 }, +#define NALIASW 13 + { "noalias", 0 }, +#define WIDTHSW 14 + { "width columns", 0 }, +#define VERSIONSW 15 + { "version", 0 }, +#define HELPSW 16 + { "help", 4 }, +#define DEBUGSW 17 + { "debug", -5 }, +#define DISTSW 18 + { "dist", -4 }, /* interface from dist */ +#define CHKSW 19 + { "check", -5 }, /* interface from whom */ +#define NCHKSW 20 + { "nocheck", -7 }, /* interface from whom */ +#define WHOMSW 21 + { "whom", -4 }, /* interface from whom */ +#define PUSHSW 22 /* fork to sendmail then exit */ + { "push", -4 }, +#define NPUSHSW 23 /* exec sendmail */ + { "nopush", -6 }, +#define LIBSW 24 + { "library directory", -7 }, +#define ANNOSW 25 + { "idanno number", -6 }, + { NULL, 0 } +}; + + +/* flags for headers->flags */ +#define HNOP 0x0000 /* just used to keep .set around */ +#define HBAD 0x0001 /* bad header - don't let it through */ +#define HADR 0x0002 /* header has an address field */ +#define HSUB 0x0004 /* Subject: header */ +#define HTRY 0x0008 /* try to send to addrs on header */ +#define HBCC 0x0010 /* don't output this header */ +#define HMNG 0x0020 /* mung this header */ +#define HNGR 0x0040 /* no groups allowed in this header */ +#define HFCC 0x0080 /* FCC: type header */ +#define HNIL 0x0100 /* okay for this header not to have addrs */ +#define HIGN 0x0200 /* ignore this header */ + +/* flags for headers->set */ +#define MFRM 0x0001 /* we've seen a From: */ +#define MDAT 0x0002 /* we've seen a Date: */ +#define MRFM 0x0004 /* we've seen a Resent-From: */ +#define MVIS 0x0008 /* we've seen sighted addrs */ +#define MINV 0x0010 /* we've seen blind addrs */ +#define MRDT 0x0020 /* we've seen a Resent-Date: */ + +struct headers { + char *value; + unsigned int flags; + unsigned int set; +}; + + +static struct headers NHeaders[] = { + { "Return-Path", HBAD, 0 }, + { "Received", HBAD, 0 }, + { "Reply-To", HADR|HNGR, 0 }, + { "From", HADR|HNGR, MFRM }, + { "Sender", HADR|HBAD, 0 }, + { "Date", HNOP, MDAT }, + { "Subject", HSUB, 0 }, + { "To", HADR|HTRY, MVIS }, + { "cc", HADR|HTRY, MVIS }, + { "Bcc", HADR|HTRY|HBCC|HNIL, MINV }, + { "Message-Id", HBAD, 0 }, + { "Fcc", HFCC, 0 }, + { NULL, 0, 0 } +}; + +static struct headers RHeaders[] = { + { "Resent-Reply-To", HADR|HNGR, 0 }, + { "Resent-From", HADR|HNGR, MRFM }, + { "Resent-Sender", HADR|HBAD, 0 }, + { "Resent-Date", HNOP, MRDT }, + { "Resent-Subject", HSUB, 0 }, + { "Resent-To", HADR|HTRY, MVIS }, + { "Resent-cc", HADR|HTRY, MVIS }, + { "Resent-Bcc", HADR|HTRY|HBCC, MINV }, + { "Resent-Message-Id", HBAD, 0 }, + { "Resent-Fcc", HFCC, 0 }, + { "Reply-To", HADR, 0 }, + { "Fcc", HIGN, 0 }, + { NULL, 0, 0 } +}; + + +static short fccind = 0; /* index into fccfold[] */ + +static int badmsg = 0; /* message has bad semantics */ +static int verbose = 0; /* spell it out */ +static int debug = 0; /* debugging post */ +static int rmflg = 1; /* remove temporary file when done */ +static int watch = 0; /* watch the delivery process */ +static int backflg = 0; /* rename input file as *.bak when done */ +static int whomflg = 0; /* if just checking addresses */ +static int pushflg = 0; /* if going to fork to sendmail */ +static int aliasflg = -1; /* if going to process aliases */ +static int outputlinelen=72; + +static unsigned msgflags = 0; /* what we've seen */ + +static enum { + normal, resent +} msgstate = normal; + +static char tmpfil[] = "/tmp/pstXXXXXX"; + +static char from[BUFSIZ]; /* my network address */ +static char signature[BUFSIZ]; /* my signature */ +static char *filter = NULL; /* the filter for BCC'ing */ +static char *subject = NULL; /* the subject field for BCC'ing */ +static char *fccfold[FCCS]; /* foldernames for FCC'ing */ + +static struct headers *hdrtab; /* table for the message we're doing */ +static FILE *out; /* output (temp) file */ + +extern char *sendmail; + +/* + * external prototypes + */ +extern char *getfullname (void); +extern char *getusername (void); + +/* + * static prototypes + */ +static void putfmt (char *, char *, FILE *); +static void start_headers (void); +static void finish_headers (FILE *); +static int get_header (char *, struct headers *); +static void putadr (char *, struct mailname *); +static int putone (char *, int, int); +static void insert_fcc (struct headers *, char *); +static void file (char *); +static void fcc (char *, char *); + +#if 0 +static void die (char *, char *, ...); +static void make_bcc_file (void); +#endif + + +int +main (int argc, char **argv) +{ + int state, i, pid, compnum; + char *cp, *msg = NULL, **argp, **arguments; + char *sargv[16], buf[BUFSIZ], name[NAMESZ]; + FILE *in; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + mts_init (invo_name); + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] file", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case DEBUGSW: + debug++; + continue; + + case DISTSW: + msgstate = resent; + continue; + + case WHOMSW: + whomflg++; + continue; + + case FILTSW: + if (!(filter = *argp++) || *filter == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NFILTSW: + filter = NULL; + continue; + + case REMVSW: + rmflg++; + continue; + case NREMVSW: + rmflg = 0; + continue; + + case BACKSW: + backflg++; + continue; + case NBACKSW: + backflg = 0; + continue; + + case VERBSW: + verbose++; + continue; + case NVERBSW: + verbose = 0; + continue; + + case WATCSW: + watch++; + continue; + case NWATCSW: + watch = 0; + continue; + + case PUSHSW: + pushflg++; + continue; + case NPUSHSW: + pushflg = 0; + continue; + + case ALIASW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if (aliasflg < 0) + alias (AliasFile);/* load default aka's */ + aliasflg = 1; + if ((state = alias(cp)) != AK_OK) + adios (NULL, "aliasing error in file %s - %s", + cp, akerror(state) ); + continue; + case NALIASW: + aliasflg = 0; + continue; + + case WIDTHSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + outputlinelen = atoi (cp); + if (outputlinelen <= 10) + outputlinelen = 72; + continue; + + case LIBSW: + case ANNOSW: + /* -library & -idanno switch ignored */ + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + } + } + if (msg) + adios (NULL, "only one message at a time!"); + else + msg = cp; + } + + if (aliasflg < 0) + alias (AliasFile); /* load default aka's */ + + if (!msg) + adios (NULL, "usage: %s [switches] file", invo_name); + + if ((in = fopen (msg, "r")) == NULL) + adios (msg, "unable to open"); + + start_headers (); + if (debug) { + verbose++; + out = stdout; + } + else { + mktemp (tmpfil); + if ((out = fopen (tmpfil, "w")) == NULL) + adios (tmpfil, "unable to create"); + chmod (tmpfil, 0600); + } + + hdrtab = (msgstate == normal) ? NHeaders : RHeaders; + + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + compnum++; + putfmt (name, buf, out); + continue; + + case FLDPLUS: + compnum++; + cp = add (buf, cp); + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + cp = add (buf, cp); + } + putfmt (name, cp, out); + free (cp); + continue; + + case BODY: + finish_headers (out); + fprintf (out, "\n%s", buf); + if(whomflg == 0) + while (state == BODY) { + state = m_getfld (state, name, buf, sizeof(buf), in); + fputs (buf, out); + } + break; + + case FILEEOF: + finish_headers (out); + break; + + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", + compnum); + + default: + adios (NULL, "getfld() returned %d", state); + } + break; + } + + fclose (in); + if (backflg && !whomflg) { + strncpy (buf, m_backup (msg), sizeof(buf)); + if (rename (msg, buf) == NOTOK) + advise (buf, "unable to rename %s to", msg); + } + + if (debug) { + done (0); + } + else + fclose (out); + + file (tmpfil); + + /* + * re-open the temp file, unlink it and exec sendmail, giving it + * the msg temp file as std in. + */ + if ( freopen( tmpfil, "r", stdin) == NULL) + adios (tmpfil, "can't reopen for sendmail"); + if (rmflg) + unlink (tmpfil); + + argp = sargv; + *argp++ = "send-mail"; + *argp++ = "-m"; /* send to me too */ + *argp++ = "-t"; /* read msg for recipients */ + *argp++ = "-i"; /* don't stop on "." */ + if (whomflg) + *argp++ = "-bv"; + if (watch || verbose) + *argp++ = "-v"; + *argp = NULL; + + if (pushflg && !(watch || verbose)) { + /* fork to a child to run sendmail */ + for (i=0; (pid = vfork()) == NOTOK && i < 5; i++) + sleep(5); + switch (pid) { + case NOTOK: + fprintf (verbose ? stdout : stderr, "%s: can't fork to %s\n", + invo_name, sendmail); + exit(-1); + case OK: + /* we're the child .. */ + break; + default: + exit(0); + } + } + execv ( sendmail, sargv); + adios ( sendmail, "can't exec"); +} + +/* DRAFT GENERATION */ + +static void +putfmt (char *name, char *str, FILE *out) +{ + int i; + char *cp, *pp; + struct headers *hdr; + + while (*str == ' ' || *str == '\t') + str++; + + if ((i = get_header (name, hdrtab)) == NOTOK) { + fprintf (out, "%s: %s", name, str); + return; + } + + hdr = &hdrtab[i]; + if (hdr->flags & HIGN) + return; + if (hdr->flags & HBAD) { + advise (NULL, "illegal header line -- %s:", name); + badmsg++; + return; + } + msgflags |= hdr->set; + + if (hdr->flags & HSUB) + subject = subject ? add (str, add ("\t", subject)) : getcpy (str); + + if (hdr->flags & HFCC) { + if ((cp = strrchr(str, '\n'))) + *cp = 0; + for (cp = pp = str; cp = strchr(pp, ','); pp = cp) { + *cp++ = 0; + insert_fcc (hdr, pp); + } + insert_fcc (hdr, pp); + return; + } + +#ifdef notdef + if (hdr->flags & HBCC) { + insert_bcc(str); + return; + } +#endif /* notdef */ + + if (*str != '\n' && *str != '\0') + if (aliasflg && hdr->flags & HTRY) { + /* this header contains address(es) that we have to do + * alias expansion on. Because of the saved state in + * getname we have to put all the addresses into a list. + * We then let putadr munch on that list, possibly + * expanding aliases. + */ + register struct mailname *f = 0; + register struct mailname *mp = 0; + + while ((cp = getname(str))) { + mp = getm( cp, NULL, 0, AD_HOST, NULL); + if (f == 0) { + f = mp; + mp->m_next = mp; + } else { + mp->m_next = f->m_next; + f->m_next = mp; + f = mp; + } + } + f = mp->m_next; mp->m_next = 0; + putadr( name, f ); + } else { + fprintf (out, "%s: %s", name, str ); + } +} + + +static void +start_headers (void) +{ + char *cp; + char sigbuf[BUFSIZ]; + + strncpy(from, getusername(), sizeof(from)); + + if ((cp = getfullname ()) && *cp) { + strncpy (sigbuf, cp, sizeof(sigbuf)); + snprintf (signature, sizeof(signature), "%s <%s>", sigbuf, from); + } + else + snprintf (signature, sizeof(signature), "%s", from); +} + + +static void +finish_headers (FILE *out) +{ + switch (msgstate) { + case normal: + if (!(msgflags & MDAT)) + fprintf (out, "Date: %s\n", dtimenow (0)); + if (msgflags & MFRM) + fprintf (out, "Sender: %s\n", from); + else + fprintf (out, "From: %s\n", signature); +#ifdef notdef + if (!(msgflags & MVIS)) + fprintf (out, "Bcc: Blind Distribution List: ;\n"); +#endif /* notdef */ + break; + + case resent: + if (!(msgflags & MRDT)) + fprintf (out, "Resent-Date: %s\n", dtimenow(0)); + if (msgflags & MRFM) + fprintf (out, "Resent-Sender: %s\n", from); + else + fprintf (out, "Resent-From: %s\n", signature); +#ifdef notdef + if (!(msgflags & MVIS)) + fprintf (out, "Resent-Bcc: Blind Re-Distribution List: ;\n"); +#endif /* notdef */ + break; + } + + if (badmsg) + adios (NULL, "re-format message and try again"); +} + + +static int +get_header (char *header, struct headers *table) +{ + struct headers *h; + + for (h = table; h->value; h++) + if (!strcasecmp (header, h->value)) + return (h - table); + + return NOTOK; +} + + +/* + * output the address list for header "name". The address list + * is a linked list of mailname structs. "nl" points to the head + * of the list. Alias substitution should be done on nl. + */ +static void +putadr (char *name, struct mailname *nl) +{ + register struct mailname *mp, *mp2; + register int linepos; + register char *cp; + int namelen; + + fprintf (out, "%s: ", name); + namelen = strlen(name) + 2; + linepos = namelen; + + for (mp = nl; mp; ) { + if (linepos > MAX_SM_FIELD) { + fprintf (out, "\n%s: ", name); + linepos = namelen; + } + if (mp->m_nohost) { + /* a local name - see if it's an alias */ + cp = akvalue(mp->m_mbox); + if (cp == mp->m_mbox) + /* wasn't an alias - use what the user typed */ + linepos = putone( mp->m_text, linepos, namelen ); + else + /* an alias - expand it */ + while ((cp = getname(cp))) { + if (linepos > MAX_SM_FIELD) { + fprintf (out, "\n%s: ", name); + linepos = namelen; + } + mp2 = getm( cp, NULL, 0, AD_HOST, NULL); + if (akvisible()) { + mp2->m_pers = getcpy(mp->m_mbox); + linepos = putone( adrformat(mp2), linepos, namelen ); + } else { + linepos = putone( mp2->m_text, linepos, namelen ); + } + mnfree( mp2 ); + } + } else { + /* not a local name - use what the user typed */ + linepos = putone( mp->m_text, linepos, namelen ); + } + mp2 = mp; + mp = mp->m_next; + mnfree( mp2 ); + } + putc( '\n', out ); +} + +static int +putone (char *adr, int pos, int indent) +{ + register int len; + static int linepos; + + len = strlen( adr ); + if (pos == indent) + linepos = pos; + else if ( linepos+len > outputlinelen ) { + fprintf ( out, ",\n%*s", indent, ""); + linepos = indent; + pos += indent + 2; + } + else { + fputs( ", ", out ); + linepos += 2; + pos += 2; + } + fputs( adr, out ); + + linepos += len; + return (pos+len); +} + + +static void +insert_fcc (struct headers *hdr, char *pp) +{ + char *cp; + + for (cp = pp; isspace (*cp); cp++) + continue; + for (pp += strlen (pp) - 1; pp > cp && isspace (*pp); pp--) + continue; + if (pp >= cp) + *++pp = 0; + if (*cp == 0) + return; + + if (fccind >= FCCS) + adios (NULL, "too many %ss", hdr->value); + fccfold[fccind++] = getcpy (cp); +} + +#if 0 +/* BCC GENERATION */ + +static void +make_bcc_file (void) +{ + pid_t child_id; + int fd, i, status; + char *vec[6]; + FILE * in, *out; + + mktemp (bccfil); + if ((out = fopen (bccfil, "w")) == NULL) + adios (bccfil, "unable to create"); + chmod (bccfil, 0600); + + fprintf (out, "Date: %s\n", dtimenow (0)); + fprintf (out, "From: %s\n", signature); + if (subject) + fprintf (out, "Subject: %s", subject); + fprintf (out, "BCC:\n\n------- Blind-Carbon-Copy\n\n"); + fflush (out); + + if (filter == NULL) { + if ((fd = open (tmpfil, O_RDONLY)) == NOTOK) + adios (NULL, "unable to re-open"); + cpydgst (fd, fileno (out), tmpfil, bccfil); + close (fd); + } + else { + vec[0] = r1bindex (mhlproc, '/'); + + for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + adios ("vfork", "unable to"); + + case OK: + dup2 (fileno (out), 1); + + i = 1; + vec[i++] = "-forward"; + vec[i++] = "-form"; + vec[i++] = filter; + vec[i++] = tmpfil; + vec[i] = NULL; + + execvp (mhlproc, vec); + adios (mhlproc, "unable to exec"); + + default: + if (status = pidwait(child_id, OK)) + admonish (NULL, "%s lost (status=0%o)", vec[0], status); + break; + } + } + + fseek (out, 0L, SEEK_END); + fprintf (out, "\n------- End of Blind-Carbon-Copy\n"); + fclose (out); +} +#endif /* if 0 */ + +/* FCC INTERACTION */ + +static void +file (char *path) +{ + int i; + + if (fccind == 0) + return; + + for (i = 0; i < fccind; i++) + if (whomflg) + printf ("Fcc: %s\n", fccfold[i]); + else + fcc (path, fccfold[i]); +} + + +static void +fcc (char *file, char *folder) +{ + pid_t child_id; + int i, status; + char fold[BUFSIZ]; + + if (verbose) + printf ("%sFcc: %s\n", msgstate == resent ? "Resent-" : "", folder); + fflush (stdout); + + for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + if (!verbose) + fprintf (stderr, " %sFcc %s: ", + msgstate == resent ? "Resent-" : "", folder); + fprintf (verbose ? stdout : stderr, "no forks, so not ok\n"); + break; + + case OK: + snprintf (fold, sizeof(fold), "%s%s", + *folder == '+' || *folder == '@' ? "" : "+", folder); + execlp (fileproc, r1bindex (fileproc, '/'), + "-link", "-file", file, fold, NULL); + _exit (-1); + + default: + if ((status = pidwait(child_id, OK))) { + if (!verbose) + fprintf (stderr, " %sFcc %s: ", + msgstate == resent ? "Resent-" : "", folder); + fprintf (verbose ? stdout : stderr, + " errored (0%o)\n", status); + } + } + + fflush (stdout); +} + + +#if 0 + +/* + * TERMINATION + */ + +static void +die (char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); + + done (1); +} +#endif diff --git a/uip/termsbr.c b/uip/termsbr.c new file mode 100644 index 0000000..77af468 --- /dev/null +++ b/uip/termsbr.c @@ -0,0 +1,227 @@ + +/* + * termsbr.c -- termcap support + * + * $Id$ + */ + +#include + +#ifdef HAVE_TERMIOS_H +# include +#else +# ifdef HAVE_TERMIO_H +# include +# else +# include +# endif +#endif + +#ifdef HAVE_TERMCAP_H +# include +#endif + +#ifdef GWINSZ_IN_SYS_IOCTL +# include +#endif +#ifdef WINSIZE_IN_PTEM +# include +# include +#endif + +#if BUFSIZ<2048 +# define TXTSIZ 2048 +#else +# define TXTSIZ BUFSIZ +#endif + +/* + * These variables are sometimes defined in, + * and needed by the termcap library. + */ +#ifdef HAVE_OSPEED +# ifdef MUST_DEFINE_OSPEED +extern short ospeed; +extern char PC; +# endif +#else +short ospeed; +char PC; +#endif + +static long speedcode; + +static int initLI = 0; +static int initCO = 0; + +static int HC = 0; /* are we on a hardcopy terminal? */ +static int LI = 40; /* number of lines */ +static int CO = 80; /* number of colums */ +static char *CL = NULL; /* termcap string to clear screen */ +static char *SE = NULL; /* termcap string to end standout mode */ +static char *SO = NULL; /* termcap string to begin standout mode */ + +static char termcap[TXTSIZ]; + + +static void +read_termcap(void) +{ + char *bp, *cp; + char *term; + +#ifndef TGETENT_ACCEPTS_NULL + char termbuf[TXTSIZ]; +#endif + +#ifdef HAVE_TERMIOS_H + struct termios tio; +#else +# ifdef HAVE_TERMIO_H + struct termio tio; +# else + struct sgttyb tio; +# endif +#endif + + static int inited = 0; + + if (inited++) + return; + + if (!(term = getenv ("TERM"))) + return; + +/* + * If possible, we let tgetent allocate its own termcap buffer + */ +#ifdef TGETENT_ACCEPTS_NULL + if (tgetent (NULL, term) <= 0) + return +#else + if (tgetent (termbuf, term) <= 0) + return; +#endif + +#ifdef HAVE_TERMIOS_H + speedcode = cfgetospeed(&tio); +#else +# ifdef HAVE_TERMIO_H + speedcode = ioctl(fileno(stdout), TCGETA, &tio) != NOTOK ? tio.c_cflag & CBAUD : 0; +# else + speedcode = ioctl(fileno(stdout), TIOCGETP, (char *) &tio) != NOTOK ? tio.sg_ospeed : 0; +# endif +#endif + + HC = tgetflag ("hc"); + + if (!initCO && (CO = tgetnum ("co")) <= 0) + CO = 80; + if (!initLI && (LI = tgetnum ("li")) <= 0) + LI = 24; + + cp = termcap; + CL = tgetstr ("cl", &cp); + if ((bp = tgetstr ("pc", &cp))) + PC = *bp; + if (tgetnum ("sg") <= 0) { + SE = tgetstr ("se", &cp); + SO = tgetstr ("so", &cp); + } +} + + +int +sc_width (void) +{ +#ifdef TIOCGWINSZ + struct winsize win; + int width; + + if (ioctl (fileno (stderr), TIOCGWINSZ, &win) != NOTOK + && (width = win.ws_col) > 0) { + CO = width; + initCO++; + } else +#endif /* TIOCGWINSZ */ + read_termcap(); + + return CO; +} + + +int +sc_length (void) +{ +#ifdef TIOCGWINSZ + struct winsize win; + + if (ioctl (fileno (stderr), TIOCGWINSZ, &win) != NOTOK + && (LI = win.ws_row) > 0) + initLI++; + else +#endif /* TIOCGWINSZ */ + read_termcap(); + + return LI; +} + + +static int +outc (int c) +{ + putchar(c); +} + + +void +clear_screen (void) +{ + read_termcap (); + + if (CL && speedcode) + tputs (CL, LI, outc); + else { + printf ("\f"); + if (speedcode) + printf ("\200"); + } + + fflush (stdout); +} + + +/* + * print in standout mode + */ +int +SOprintf (char *fmt, ...) +{ + va_list ap; + + read_termcap (); + if (!(SO && SE)) + return NOTOK; + + tputs (SO, 1, outc); + + va_start(ap, fmt); + vprintf (fmt, ap); + va_end(ap); + + tputs (SE, 1, outc); + + return OK; +} + +/* + * Is this a hardcopy terminal? + */ + +int +sc_hardcopy(void) +{ + read_termcap(); + return HC; +} + diff --git a/uip/viamail.c b/uip/viamail.c new file mode 100644 index 0000000..bb5b12c --- /dev/null +++ b/uip/viamail.c @@ -0,0 +1,249 @@ + +/* + * viamail.c -- send multiple files in a MIME message + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +static struct swit switches[] = { +#define TOSW 0 + { "to mailpath", 0 }, +#define FROMSW 1 + { "from mailpath", 0 }, +#define SUBJECTSW 2 + { "subject subject", 0 }, +#define PARAMSW 3 + { "parameters arguments", 0 }, +#define DESCRIPTSW 4 + { "description text", 0 }, +#define COMMENTSW 5 + { "comment text", 0 }, +#define DELAYSW 6 + { "delay seconds", 0 }, +#define VERBSW 7 + { "verbose", 0 }, +#define NVERBSW 8 + { "noverbose", 0 }, +#define VERSIONSW 9 + { "version", 0 }, +#define HELPSW 10 + { "help", 4 }, +#define DEBUGSW 11 + { "debug", -5 }, + { NULL, 0 } +}; + +extern int errno; +extern int debugsw; +extern int splitsw; +extern int verbsw; + +int ebcdicsw = 0; /* hack for linking purposes */ + +/* mhmisc.c */ +void set_endian (void); + +/* mhoutsbr.c */ +int writeBase64aux (FILE *, FILE *); + +/* + * static prototypes + */ +static int via_mail (char *, char *, char *, char *, char *, int, char *); + + +int +main (int argc, char **argv) +{ + int delay = 0; + char *f1 = NULL, *f2 = NULL, *f3 = NULL; + char *f4 = NULL, *f5 = NULL, *f7 = NULL; + char *cp, buf[BUFSIZ]; + char **argp, **arguments; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches]", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case TOSW: + if (!(f1 = *argp++)) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case SUBJECTSW: + if (!(f2 = *argp++)) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case PARAMSW: + if (!(f3 = *argp++)) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case DESCRIPTSW: + if (!(f4 = *argp++)) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case COMMENTSW: + if (!(f5 = *argp++)) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case DELAYSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + + /* + * If there is an error, just reset the delay parameter + * to -1. We will set a default delay later. + */ + if (sscanf (cp, "%d", &delay) != 1) + delay = -1; + continue; + case FROMSW: + if (!(f7 = *argp++)) + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case VERBSW: + verbsw = 1; + continue; + case NVERBSW: + verbsw = 0; + continue; + + case DEBUGSW: + debugsw = 1; + continue; + } + } + } + + set_endian (); + + if (!f1) + adios (NULL, "missing -viamail \"mailpath\" switch"); + + via_mail (f1, f2, f3, f4, f5, delay, f7); + /* NOTREACHED */ +} + + +/* + * VIAMAIL + */ + +static int +via_mail (char *mailsw, char *subjsw, char *parmsw, char *descsw, + char *cmntsw, int delay, char *fromsw) +{ + int status, vecp = 1; + char tmpfil[BUFSIZ]; + char *vec[MAXARGS]; + struct stat st; + FILE *fp; + + umask (~m_gmprot ()); + + strncpy (tmpfil, m_tmpfil (invo_name), sizeof(tmpfil)); + if ((fp = fopen (tmpfil, "w+")) == NULL) + adios (tmpfil, "unable to open for writing"); + chmod (tmpfil, 0600); + + if (!strchr(mailsw, '@')) + mailsw = concat (mailsw, "@", LocalName (), NULL); + fprintf (fp, "To: %s\n", mailsw); + + if (subjsw) + fprintf (fp, "Subject: %s\n", subjsw); + + if (fromsw) { + if (!strchr(fromsw, '@')) + fromsw = concat (fromsw, "@", LocalName (), NULL); + fprintf (fp, "From: %s\n", fromsw); + } + + fprintf (fp, "%s: %s\n", VRSN_FIELD, VRSN_VALUE); + fprintf (fp, "%s: application/octet-stream", TYPE_FIELD); + + if (parmsw) + fprintf (fp, "; %s", parmsw); + + if (cmntsw) + fprintf (fp, "\n\t(%s)", cmntsw); + + if (descsw) + fprintf (fp, "\n%s: %s", DESCR_FIELD, descsw); + + fprintf (fp, "\n%s: %s\n\n", ENCODING_FIELD, "base64"); + + if (fflush (fp)) + adios (tmpfil, "error writing to"); + + writeBase64aux (stdin, fp); + if (fflush (fp)) + adios (tmpfil, "error writing to"); + + if (fstat (fileno (fp), &st) == NOTOK) + adios ("failed", "fstat of %s", tmpfil); + + if (delay < 0) + splitsw = 10; + else + splitsw = delay; + + status = 0; + vec[0] = r1bindex (postproc, '/'); + if (verbsw) + vec[vecp++] = "-verbose"; + + switch (sendsbr (vec, vecp, tmpfil, &st, 0)) { + case DONE: + case NOTOK: + status++; + break; + case OK: + break; + } + + fclose (fp); + if (unlink (tmpfil) == -1) + advise (NULL, "unable to remove temp file %s", tmpfil); + done (status); +} diff --git a/uip/vmh.c b/uip/vmh.c new file mode 100644 index 0000000..8b398e6 --- /dev/null +++ b/uip/vmh.c @@ -0,0 +1,1513 @@ + +/* + * vmh.c -- visual front-end to nmh + * + * $Id$ + */ + +#include +#include + +#if 0 +#if defined(SYS5) && !defined(TERMINFO) +/* + * Define TERMINFO if you have it. + * You get it automatically if you're running SYS5, and you don't get + * it if you're not. (If you're not SYS5, you probably have termcap.) + * We distinguish TERMINFO from SYS5 because in this file SYS5 really + * means "AT&T line discipline" (termio, not sgttyb), whereas terminfo + * is quite a separate issue. + */ +#define TERMINFO 1 +#endif +#endif + +/* + * TODO: + * 1) Pass signals to client during execution + * 2) Figure out a way for the user to say how big the Scan/Display + * windows should be. + * 3) If curses ever gets fixed, then XYZ code can be removed + */ + +#include + +#ifdef ncr +# define _SYS_REG_H /* NCR redefines "ERR" in */ +#endif + +#undef OK /* tricky */ + +/* removed for right now */ +#if 0 +#ifdef TERMINFO +# include /* variables describing terminal capabilities */ +#endif /* TERMINFO */ +#endif + +#include +#include +#include +#include + +#ifndef sigmask +# define sigmask(s) (1 << ((s) - 1)) +#endif /* not sigmask */ + +#ifdef ridge +# undef SIGTSTP +#endif /* ridge */ + +#ifdef HAVE_WRITEV +# include +#else +struct iovec { + char *iov_base; + int iov_len; +}; +#endif + +#ifdef hpux +# include +# define TCGETATTR /* tcgetattr() */ +#endif + +#ifdef BSD44 +# define USE_OLD_TTY +# define _maxx maxx /* curses.h */ +# define _maxy maxy +# define _curx curx /* curses.h */ +# define _cury cury +void __cputchar __P((int)); +# undef _putchar +# define _putchar __cputchar +# include /* sgttyb */ +#endif + +#define ALARM ((unsigned int) 10) +#define PAUSE ((unsigned int) 2) + +#ifndef abs +# define abs(a) ((a) > 0 ? (a) : -(a)) +#endif + +#define SMALLMOVE 1 +#define LARGEMOVE 10 + +#define XYZ /* XXX */ + +static struct swit switches[] = { +#define PRMPTSW 0 + { "prompt string", 6 }, +#define PROGSW 1 + { "vmhproc program", 7 }, +#define NPROGSW 2 + { "novmhproc", 9 }, +#define VERSIONSW 3 + { "version", 0 }, +#define HELPSW 4 + { "help", 4 }, + { NULL, 0 } +}; + + /* PEERS */ +static int PEERpid = NOTOK; + +static jmp_buf PEERctx; + + /* WINDOWS */ +static char *myprompt = "(%s) "; + +static WINDOW *Scan; +static WINDOW *Status; +static WINDOW *Display; +static WINDOW *Command; + +#define NWIN 3 +static int numwins; +WINDOW *windows[NWIN + 1]; + + + /* LINES */ + +struct line { + int l_no; + char *l_buf; + struct line *l_prev; + struct line *l_next; +}; + +static struct line *lhead = NULL; +static struct line *ltop = NULL; +static struct line *ltail = NULL; + +static int did_less = 0; +static int smallmove = SMALLMOVE; +static int largemove = LARGEMOVE; + + + /* TTYS */ + +static int tty_ready = NOTOK; + +static int intrc; + +#ifndef SYS5 +# define ERASE sg.sg_erase +# define KILL sg.sg_kill +static struct sgttyb sg; + +#define EOFC tc.t_eofc +#define INTR tc.t_intrc +static struct tchars tc; +#else /* SYS5 */ +# define ERASE sg.c_cc[VERASE] +# define KILL sg.c_cc[VKILL] +# define EOFC sg.c_cc[VEOF] +# define INTR sg.c_cc[VINTR] +static struct termio sg; +#endif /* SYS5 */ + +#ifndef TIOCGLTC +# define WERASC ('W' & 037) +#else /* TIOCGLTC */ +# ifndef SVR4 +# define WERASC ltc.t_werasc +static struct ltchars ltc; +# else /* SVR4 */ +# define WERASC sg.c_cc[VWERASE] +# undef TIOCGLTC /* the define exists, but struct ltchars doesn't */ +# endif +#endif /* TIOCGLTC */ + + +#if !defined(SYS5) && !defined(BSD44) +int _putchar(); +#endif /* not SYS5 */ + +#ifdef SIGTSTP +char *tgoto(); +#endif /* SIGTSTP */ + + /* SIGNALS */ +static RETSIGTYPE ALRMser(int); +static RETSIGTYPE PIPEser(int); +static RETSIGTYPE SIGser(int); +#ifdef SIGTSTP +static RETSIGTYPE TSTPser(int); +#endif /* SIGTSTP */ + + + /* MISCELLANY */ +extern int errno; + +/* + * static prototypes + */ +static void adorn (char *, char *, ...); + +static vmh(), lreset(), linsert(), ladvance(), lretreat(), lgo(); +static TTYon(), TTYoff(), foreground(); +static int PEERinit(), pINI(), pLOOP(), pTTY(), pWIN(), WINinit(); +static int WINgetstr(), WINless(), WINputc(), TTYinit(), pWINaux(); + + +int +main (int argc, char **argv) +{ + int vecp = 1, nprog = 0; + char *cp, buffer[BUFSIZ]; + char **argp, **arguments, *vec[MAXARGS]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) + if (*cp == '-') + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + vec[vecp++] = --cp; + continue; + + case HELPSW: + snprintf (buffer, sizeof(buffer), "%s [switches for vmhproc]", + invo_name); + print_help (buffer, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case PRMPTSW: + if (!(myprompt = *argp++) || *myprompt == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case PROGSW: + if (!(vmhproc = *argp++) || *vmhproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NPROGSW: + nprog++; + continue; + } + else + vec[vecp++] = cp; + + if (TTYinit (nprog) == NOTOK || WINinit (nprog) == NOTOK) { + vec[vecp] = NULL; + + vec[0] = r1bindex (vmhproc, '/'); + execvp (vmhproc, vec); + adios (vmhproc, "unable to exec"); + } + TTYoff (); + PEERinit (vecp, vec); + TTYon (); + + vmh (); + + done (0); +} + + +static void +vmh (void) +{ + char buffer[BUFSIZ]; + + for (;;) { + pLOOP (RC_QRY, NULL); + + wmove (Command, 0, 0); + wprintw (Command, myprompt, invo_name); + wclrtoeol (Command); + wrefresh (Command); + + switch (WINgetstr (Command, buffer)) { + case NOTOK: + break; + + case OK: + done (0); /* NOTREACHED */ + + default: + if (*buffer) + pLOOP (RC_CMD, buffer); + break; + } + } +} + +/* PEERS */ + +static int +PEERinit (int vecp, char *vec[]) +{ + int pfd0[2], pfd1[2]; + char buf1[BUFSIZ], buf2[BUFSIZ]; + + if (pipe (pfd0) == NOTOK || pipe (pfd1) == NOTOK) + adios ("pipe", "unable to"); +#ifdef hpux + switch (PEERpid = fork ()) { + /* + * Calling vfork() and then another routine [like close()] before + * an exec() messes up the stack frame, causing crib death. + * Use fork() instead. + */ +#else /* not hpux */ + switch (PEERpid = vfork ()) { +#endif /* not hpux */ + case NOTOK: + adios ("vfork", "unable to");/* NOTREACHED */ + + case OK: + close (pfd0[0]); + close (pfd1[1]); + + vec[vecp++] = "-vmhread"; + snprintf (buf1, sizeof(buf1), "%d", pfd1[0]); + vec[vecp++] = buf1; + vec[vecp++] = "-vmhwrite"; + snprintf (buf2, sizeof(buf2), "%d", pfd0[1]); + vec[vecp++] = buf2; + vec[vecp] = NULL; + + SIGNAL (SIGINT, SIG_DFL); + SIGNAL (SIGQUIT, SIG_DFL); + + vec[0] = r1bindex (vmhproc, '/'); + execvp (vmhproc, vec); + perror (vmhproc); + _exit (-1); /* NOTREACHED */ + + default: + close (pfd0[1]); + close (pfd1[0]); + + rcinit (pfd0[0], pfd1[1]); + return pINI (); + } +} + + +static int +pINI (void) +{ + int len, buflen; + char *bp, buffer[BUFSIZ]; + struct record rcs; + register struct record *rc = &rcs; + register WINDOW **w; + + initrc (rc); + + /* Get buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + snprintf (bp, buflen, "%d %d", RC_VRSN, numwins); + len = strlen (bp); + bp += len; + buflen -= len; + + for (w = windows; *w; w++) { + snprintf (bp, buflen, " %d", (*w)->_maxy); + len = strlen (bp); + bp += len; + buflen -= len; + } + + switch (str2rc (RC_INI, buffer, rc)) { + case RC_ACK: + return OK; + + case RC_ERR: + if (rc->rc_len) + adios (NULL, "%s", rc->rc_data); + else + adios (NULL, "pINI peer error"); + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pINI protocol screw-up"); + } +/* NOTREACHED */ +} + + +static int +pLOOP (char *code, char *str) +{ + int i; + struct record rcs; + register struct record *rc = &rcs; + + initrc (rc); + + str2peer (code, str); + for (;;) + switch (peer2rc (rc)) { + case RC_TTY: + if (pTTY (rc) == NOTOK) + return NOTOK; + break; + + case RC_WIN: + if (sscanf (rc->rc_data, "%d", &i) != 1 + || i <= 0 + || i > numwins) { + fmt2peer (RC_ERR, "no such window \"%s\"", rc->rc_data); + return NOTOK; + } + if (pWIN (windows[i - 1]) == NOTOK) + return NOTOK; + break; + + case RC_EOF: + return OK; + + case RC_ERR: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + else + adorn (NULL, "pLOOP(%s) peer error", + code == RC_QRY ? "QRY" : "CMD"); + return NOTOK; + + case RC_FIN: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + rcdone (); + i = pidwait (PEERpid, OK); + PEERpid = NOTOK; + done (i); + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pLOOP(%s) protocol screw-up", + code == RC_QRY ? "QRY" : "CMD"); + } +} + + +static int +pTTY (struct record *r) +{ + SIGNAL_HANDLER hstat, istat, qstat, tstat; + struct record rcs; + register struct record *rc = &rcs; + + initrc (rc); + + TTYoff (); + + /* should be changed to block instead of ignore */ + hstat = SIGNAL (SIGHUP, SIG_IGN); + istat = SIGNAL (SIGINT, SIG_IGN); + qstat = SIGNAL (SIGQUIT, SIG_IGN); + tstat = SIGNAL (SIGTERM, SIG_IGN); + + rc2rc (RC_ACK, 0, NULL, rc); + + SIGNAL (SIGHUP, hstat); + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + SIGNAL (SIGTERM, tstat); + + TTYon (); + + if (r->rc_len && strcmp (r->rc_data, "FAST") == 0) + goto no_refresh; + +#ifdef SIGTSTP + SIGNAL (SIGTSTP, SIG_IGN); +#endif + +#ifndef TERMINFO + if (SO) + tputs (SO, 0, _putchar); +#else /* TERMINFO */ + putp(enter_standout_mode); +#endif /* TERMINFO */ + fprintf (stdout, "Type any key to continue... "); + fflush (stdout); +#ifndef TERMINFO + if (SE) + tputs (SE, 0, _putchar); +#else /* TERMINFO */ + putp(exit_standout_mode); +#endif /* TERMINFO */ + getc (stdin); +#ifdef SIGTSTP + SIGNAL (SIGTSTP, TSTPser); +#endif /* SIGTSTP */ + + wrefresh (curscr); + +no_refresh: ; + switch (rc->rc_type) { + case RC_EOF: + rc2peer (RC_ACK, 0, NULL); + return OK; + + case RC_ERR: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + else + adorn (NULL, "pTTY peer error"); + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pTTY protocol screw-up"); + } +/* NOTREACHED */ +} + + +static int +pWIN (WINDOW *w) +{ + int i; + + did_less = 0; + if ((i = pWINaux (w)) == OK && did_less) + WINless (w, 1); + + lreset (); + + return i; +} + + +static int +pWINaux (WINDOW *w) +{ + register int n; + int eol; + register char c, *bp; + struct record rcs; + register struct record *rc = &rcs; + + initrc (rc); + + werase (w); + wmove (w, 0, 0); +#ifdef XYZ + if (w == Status) + wstandout (w); +#endif /* XYZ */ + + for (eol = 0;;) + switch (rc2rc (RC_ACK, 0, NULL, rc)) { + case RC_DATA: + if (eol && WINputc (w, '\n') == ERR && WINless (w, 0)) + goto flush; + for (bp = rc->rc_data, n = rc->rc_len; n-- > 0; ) { + if ((c = *bp++) == '\n') + linsert (w); + if (WINputc (w, c) == ERR) + if (n == 0 && c == '\n') + eol++; + else + if (WINless (w, 0)) { +flush: ; + fmt2peer (RC_ERR, "flush window"); +#ifdef XYZ /* should NEVER happen... */ + if (w == Status) + wstandend (w); +#endif /* XYZ */ + wrefresh (w); + return NOTOK; + } + } + break; + + case RC_EOF: + rc2peer (RC_ACK, 0, NULL); +#ifdef XYZ + if (w == Status) + wstandend (w); +#endif /* XYZ */ + wrefresh (w); + return OK; + + case RC_ERR: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + else + adorn (NULL, "pWIN peer error"); + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pWIN protocol screw-up"); + } +/* NOTREACHED */ +} + + +static int +pFIN (void) +{ + int status; + + if (PEERpid <= OK) + return OK; + + rc2peer (RC_FIN, 0, NULL); + rcdone (); + + switch (setjmp (PEERctx)) { + case OK: + SIGNAL (SIGALRM, ALRMser); + alarm (ALARM); + + status = pidwait (PEERpid, OK); + + alarm (0); + break; + + default: + kill (PEERpid, SIGKILL); + status = NOTOK; + break; + } + PEERpid = NOTOK; + + return status; +} + +/* WINDOWS */ + +static int +WINinit (int nprog) +{ + register int nlines, /* not "lines" because terminfo uses that */ + top, + bottom; + + foreground (); + if (initscr () == (WINDOW *) ERR) + if (nprog) + return NOTOK; + else + adios (NULL, "could not initialize terminal"); +#ifdef SIGTSTP + SIGNAL (SIGTSTP, SIG_DFL); +#endif /* SIGTSTP */ + sideground (); + +#ifndef TERMINFO + if (CM == NULL) +#else /* TERMINFO */ + if (cursor_address == NULL) /* assume mtr wanted "cm", not "CM" */ +#endif /* TERMINFO */ + if (nprog) + return NOTOK; + else + adios (NULL, + "sorry, your terminal isn't powerful enough to run %s", + invo_name); + +#ifndef TERMINFO + if (tgetflag ("xt") || tgetnum ("sg") > 0) + SO = SE = US = UE = NULL; +#else /* TERMINFO */ +/* + * If termcap mapped directly to terminfo, we'd use the following: + * if (teleray_glitch || magic_cookie_glitch > 0) + * enter_standout_mode = exit_standout_mode = + * enter_underline_mode = exit_underline_mode = NULL; + * But terminfo does the right thing so we don't have to resort to that. + */ +#endif /* TERMINFO */ + + if ((nlines = LINES - 1) < 11) + adios (NULL, "screen too small"); + if ((top = nlines / 3 + 1) > LINES / 4 + 2) + top--; + bottom = nlines - top - 2; + + numwins = 0; + Scan = windows[numwins++] = newwin (top, COLS, 0, 0); + Status = windows[numwins++] = newwin (1, COLS, top, 0); +#ifndef XYZ + wstandout (Status); +#endif /* XYZ */ + Display = windows[numwins++] = newwin (bottom, COLS, top + 1, 0); + Command = newwin (1, COLS - 1, top + 1 + bottom, 0); + windows[numwins] = NULL; + + largemove = Display->_maxy / 2 + 2; + return OK; +} + + +static int WINgetstr (WINDOW *w, char *buffer) +{ + register int c; + register char *bp; + + bp = buffer; + *bp = 0; + + for (;;) { + switch (c = toascii (wgetch (w))) { + case ERR: + adios (NULL, "wgetch lost"); + + case '\f': + wrefresh (curscr); + break; + + case '\r': + case '\n': + *bp = 0; + if (bp > buffer) { + leaveok (curscr, FALSE); + wmove (w, 0, w->_curx - (bp - buffer)); + wrefresh (w); + leaveok (curscr, TRUE); + } + return DONE; + + default: + if (c == intrc) { + wprintw (w, " "); + wstandout (w); + wprintw (w, "Interrupt"); + wstandend (w); + wrefresh (w); + *buffer = 0; + return NOTOK; + } + if (c == EOFC) { + if (bp <= buffer) + return OK; + break; + } + if (c == ERASE) { + if (bp <= buffer) + continue; + bp--, w->_curx--; + wclrtoeol (w); + break; + } + if (c == KILL) { + if (bp <= buffer) + continue; + w->_curx -= bp - buffer; + bp = buffer; + wclrtoeol (w); + break; + } + if (c == WERASC) { + if (bp <= buffer) + continue; + do { + bp--, w->_curx--; + } while (isspace (*bp) && bp > buffer); + + if (bp > buffer) { + do { + bp--, w->_curx--; + } while (!isspace (*bp) && bp > buffer); + if (isspace (*bp)) + bp++, w->_curx++; + } + wclrtoeol (w); + break; + } + + if (c >= ' ' && c < '\177') + waddch (w, *bp++ = c); + break; + } + + wrefresh (w); + } +} + + +static int +WINwritev (WINDOW *w, struct iovec *iov, int n) +{ + register int i; + + werase (w); + wmove (w, 0, 0); + for (i = 0; i < n; i++, iov++) + wprintw (w, "%*.*s", iov->iov_len, iov->iov_len, iov->iov_base); + wrefresh (w); + + sleep (PAUSE); + + return OK; +} + + +static struct { + char *h_msg; + int *h_val; +} hlpmsg[] = { + " forward backwards", NULL, + " ------- ---------", NULL, + "next screen SPACE", NULL, + "next %d line%s RETURN y", &smallmove, + "next %d line%s EOT u", &largemove, + "go g G", NULL, + "", NULL, + "refresh CTRL-L", NULL, + "quit q", NULL, + + NULL, NULL +}; + + +static int +WINless (WINDOW *w, int fin) +{ + register int c, i, n; + char *cp; + register struct line *lbottom; + int nfresh, nwait; + +#ifdef notdef + int nlatch; +#endif + + did_less++; + + cp = NULL; +#ifdef notdef + if (fin) + ltop = NULL; +#endif /* notdef */ + lbottom = NULL; + nfresh = 1; + nwait = 0; + wrefresh (w); + + for (;;) { + if (nfresh || nwait) { + nfresh = 0; +#ifdef notdef + nlatch = 1; + +once_only: ; +#endif /* notdef */ + werase (w); + wmove (w, 0, 0); + + if (ltop == NULL) + if (fin) { + lgo (ltail->l_no - w->_maxy + 1); + if (ltop == NULL) + ltop = lhead; + } + else + ltop = lbottom && lbottom->l_prev ? lbottom->l_prev + : lbottom; + + for (lbottom = ltop; lbottom; lbottom = lbottom->l_next) + if (waddstr (w, lbottom->l_buf) == ERR + || waddch (w, '\n') == ERR) + break; + if (lbottom == NULL) + if (fin) { +#ifdef notdef + if (nlatch && (ltail->l_no >= w->_maxy)) { + lgo (ltail->l_no - w->_maxy + 1); + nlatch = 0; + goto once_only; + } +#endif /* notdef */ + lbottom = ltail; + while (waddstr (w, "~\n") != ERR) + continue; + } + else { + wrefresh (w); + return 0; + } + + if (!nwait) + wrefresh (w); + } + + wmove (Command, 0, 0); + if (cp) { + wstandout (Command); + wprintw (Command, "%s", cp); + wstandend (Command); + cp = NULL; + } + else + wprintw (Command, fin ? "top:%d bot:%d end:%d" : "top:%d bot:%d", + ltop->l_no, lbottom->l_no, ltail->l_no); + wprintw (Command, ">> "); + wclrtoeol (Command); + wrefresh (Command); + + c = toascii (wgetch (Command)); + + werase (Command); + wrefresh (Command); + + if (nwait) { + nwait = 0; + wrefresh (w); + } + + n = 0; +again: ; + switch (c) { + case ' ': + ltop = lbottom->l_next; + nfresh++; + break; + + case '\r': + case '\n': + case 'e': + case 'j': + if (n) + smallmove = n; + if (ladvance (smallmove)) + nfresh++; + break; + + case 'y': + case 'k': + if (n) + smallmove = n; + if (lretreat (smallmove)) + nfresh++; + break; + + case 'd': + eof: ; + if (n) + largemove = n; + if (ladvance (largemove)) + nfresh++; + break; + + case 'u': + if (n) + largemove = n; + if (lretreat (largemove)) + nfresh++; + break; + + case 'g': + if (lgo (n ? n : 1)) + nfresh++; + break; + + case 'G': + if (lgo (n ? n : ltail->l_no - w->_maxy + 1)) + nfresh++; + break; + + case '\f': + case 'r': + wrefresh (curscr); + break; + + case 'h': + case '?': + werase (w); + wmove (w, 0, 0); + for (i = 0; hlpmsg[i].h_msg; i++) { + if (hlpmsg[i].h_val) + wprintw (w, hlpmsg[i].h_msg, *hlpmsg[i].h_val, + *hlpmsg[i].h_val != 1 ? "s" : ""); + else + waddstr (w, hlpmsg[i].h_msg); + waddch (w, '\n'); + } + wrefresh (w); + nwait++; + break; + + case 'q': + return 1; + + default: + if (c == EOFC) + goto eof; + + if (isdigit (c)) { + wmove (Command, 0, 0); + i = 0; + while (isdigit (c)) { + wprintw (Command, "%c", c); + wrefresh (Command); + i = i * 10 + c - '0'; + c = toascii (wgetch (Command)); + } + werase (Command); + wrefresh (Command); + + if (i > 0) { + n = i; + goto again; + } + cp = "bad number"; + } + else + cp = "not understood"; + break; + } + } +} + + +static int +WINputc (WINDOW *w, char c) +{ + register int x, y; + + switch (c) { + default: + if (!isascii (c)) { + if (WINputc (w, 'M') == ERR || WINputc (w, '-') == ERR) + return ERR; + c = toascii (c); + } + else + if (c < ' ' || c == '\177') { + if (WINputc (w, '^') == ERR) + return ERR; + c ^= 0100; + } + break; + + case '\t': + case '\n': + break; + } + + if (w != Scan) + return waddch (w, c); + + if ((x = w->_curx) < 0 || x >= w->_maxx + || (y = w->_cury) < 0 || y >= w->_maxy) + return DONE; + + switch (c) { + case '\t': + for (x = 8 - (x & 0x07); x > 0; x--) + if (WINputc (w, ' ') == ERR) + return ERR; + break; + + case '\n': + if (++y < w->_maxy) + waddch (w, c); + else + wclrtoeol (w); + break; + + default: + if (++x < w->_maxx) + waddch (w, c); + break; + } + + return DONE; +} + +/* LINES */ + +static void +lreset (void) +{ + register struct line *lp, *mp; + + for (lp = lhead; lp; lp = mp) { + mp = lp->l_next; + free (lp->l_buf); + free ((char *) lp); + } + lhead = ltop = ltail = NULL; +} + + +static void +linsert (WINDOW *w) +{ + register char *cp; + register struct line *lp; + + if ((lp = (struct line *) calloc ((size_t) 1, sizeof *lp)) == NULL) + adios (NULL, "unable to allocate line storage"); + + lp->l_no = (ltail ? ltail->l_no : 0) + 1; +#ifndef BSD44 + lp->l_buf = getcpy (w->_y[w->_cury]); +#else + lp->l_buf = getcpy (w->lines[w->_cury]->line); +#endif + for (cp = lp->l_buf + strlen (lp->l_buf) - 1; cp >= lp->l_buf; cp--) + if (isspace (*cp)) + *cp = 0; + else + break; + + if (lhead == NULL) + lhead = lp; + if (ltop == NULL) + ltop = lp; + if (ltail) + ltail->l_next = lp; + lp->l_prev = ltail; + ltail = lp; +} + + +static int +ladvance (int n) +{ + register int i; + register struct line *lp; + + for (i = 0, lp = ltop; i < n && lp; i++, lp = lp->l_next) + continue; + + if (ltop == lp) + return 0; + + ltop = lp; + return 1; +} + + +static int +lretreat (int n) +{ + register int i; + register struct line *lp; + + for (i = 0, lp = ltop; i < n && lp; i++, lp = lp->l_prev) + if (!lp->l_prev) + break; + + if (ltop == lp) + return 0; + + ltop = lp; + return 1; +} + + +static int +lgo (int n) +{ + register int i, j; + register struct line *lp; + + if ((i = n - (lp = lhead)->l_no) + > (j = abs (n - (ltop ? ltop : ltail)->l_no))) + i = j, lp = ltop ? ltop : ltail; + if (i > (j = abs (ltail->l_no - n))) + i = j, lp = ltail; + + if (n >= lp->l_no) { + for (; lp; lp = lp->l_next) + if (lp->l_no == n) + break; + } + else { + for (; lp; lp = lp->l_prev) + if (lp->l_no == n) + break; + if (!lp) + lp = lhead; + } + + if (ltop == lp) + return 0; + + ltop = lp; + return 1; +} + +/* TTYS */ + +static int +TTYinit (int nprog) +{ + if (!isatty (fileno (stdin)) || !isatty (fileno (stdout))) + if (nprog) + return NOTOK; + else + adios (NULL, "not a tty"); + + foreground (); +#ifndef SYS5 + if (ioctl (fileno (stdin), TIOCGETP, (char *) &sg) == NOTOK) + adios ("failed", "ioctl TIOCGETP"); + if (ioctl (fileno (stdin), TIOCGETC, (char *) &tc) == NOTOK) + adios ("failed", "ioctl TIOCGETC"); +#else +#ifdef TCGETATTR + if( tcgetattr( fileno(stdin), &sg) == NOTOK) + adios( "failed", "tcgetattr"); +#else /* SYS5 */ + if (ioctl (fileno (stdin), TCGETA, &sg) == NOTOK) + adios ("failed", "ioctl TCGETA"); +#endif +#endif +#ifdef TIOCGLTC + if (ioctl (fileno (stdin), TIOCGLTC, (char *) <c) == NOTOK) + adios ("failed", "ioctl TIOCGLTC"); +#endif /* TIOCGLTC */ + intrc = INTR; + sideground (); + + tty_ready = OK; + + SIGNAL (SIGPIPE, PIPEser); + + return OK; +} + + +static void +TTYon (void) +{ + if (tty_ready == DONE) + return; + + INTR = NOTOK; +#ifndef SYS5 + ioctl (fileno (stdin), TIOCSETC, (char *) &tc); +#else /* SYS5 */ + ioctl (fileno (stdin), TCSETA, &sg); +#endif /* SYS5 */ + + crmode (); + noecho (); + nonl (); + scrollok (curscr, FALSE); + + discard (stdin); + + tty_ready = DONE; + + SIGNAL (SIGHUP, SIGser); + SIGNAL (SIGINT, SIGser); + SIGNAL (SIGQUIT, SIGser); +#ifdef SIGTSTP + SIGNAL (SIGTSTP, TSTPser); +#endif /* SIGTSTP */ +} + + +static void +TTYoff (void) +{ + if (tty_ready == NOTOK) + return; + + INTR = intrc; +#ifndef SYS5 + ioctl (fileno (stdin), TIOCSETC, (char *) &tc); +#else /* SYS5 */ + ioctl (fileno (stdin), TCSETA, &sg); +#endif /* SYS5 */ + + leaveok (curscr, TRUE); + mvcur (0, COLS - 1, LINES - 1, 0); + endwin (); + if (tty_ready == DONE) { +#ifndef TERMINFO + if (CE) + tputs (CE, 0, _putchar); + else +#else /* TERMINFO */ + putp(clr_eol); +#endif /* TERMINFO */ + fprintf (stdout, "\r\n"); + } + fflush (stdout); + + tty_ready = NOTOK; + + SIGNAL (SIGHUP, SIG_DFL); + SIGNAL (SIGINT, SIG_DFL); + SIGNAL (SIGQUIT, SIG_DFL); +#ifdef SIGTSTP + SIGNAL (SIGTSTP, SIG_DFL); +#endif /* SIGTSTP */ +} + + +static void +foreground (void) +{ +#ifdef TIOCGPGRP + int pgrp, tpgrp; + SIGNAL_HANDLER tstat; + + if ((pgrp = getpgrp()) == NOTOK) + adios ("process group", "unable to determine"); + for (;;) { + if (ioctl (fileno (stdin), TIOCGPGRP, (char *) &tpgrp) == NOTOK) + adios ("tty's process group", "unable to determine"); + if (pgrp == tpgrp) + break; + + tstat = SIGNAL (SIGTTIN, SIG_DFL); + kill (0, SIGTTIN); + SIGNAL (SIGTTIN, tstat); + } + + SIGNAL (SIGTTIN, SIG_IGN); + SIGNAL (SIGTTOU, SIG_IGN); + SIGNAL (SIGTSTP, SIG_IGN); +#endif /* TIOCGPGRP */ +} + + +void +sideground (void) +{ +#ifdef TIOCGPGRP + SIGNAL (SIGTTIN, SIG_DFL); + SIGNAL (SIGTTOU, SIG_DFL); + SIGNAL (SIGTSTP, SIG_DFL); +#endif /* TIOCGPGRP */ +} + +/* SIGNALS */ + + +static RETSIGTYPE +ALRMser (int sig) +{ + longjmp (PEERctx, DONE); +} + + +#ifdef BSD42 +/* ARGSUSED */ +#endif /* BSD42 */ + +static RETSIGTYPE +PIPEser (int sig) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (sig, SIG_IGN); +#endif + + adios (NULL, "lost peer"); +} + + +static RETSIGTYPE +SIGser (int sig) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (sig, SIG_IGN); +#endif + + done (1); +} + + +#ifdef SIGTSTP +static RETSIGTYPE +TSTPser (int sig) +{ +#ifndef TERMINFO + tputs (tgoto (CM, 0, LINES - 1), 0, _putchar); +#else /* TERMINFO */ + move(LINES - 1, 0); /* to lower left corner */ + clrtoeol(); /* clear bottom line */ + wrefresh(curscr); /* flush out everything */ +#endif /* TERMINFO */ + fflush (stdout); + + TTYoff (); +#ifdef BSD42 + sigsetmask (sigblock (0) & ~sigmask (SIGTSTP)); +#endif /* BSD42 */ + + kill (getpid (), sig); + +#ifdef BSD42 + sigblock (sigmask (SIGTSTP)); +#endif /* BSD42 */ + TTYon (); + + wrefresh (curscr); +} +#endif /* SIGTSTP */ + + +/* MISCELLANY */ + +void +done (int status) +{ + TTYoff (); + pFIN (); + + exit (status); +} + + +static void +adorn (char *what, char *fmt, ...) +{ + va_list ap; + char *cp; + + cp = invo_name; + invo_name = NULL; + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); + + invo_name = cp; +} + + +void +advertise (char *what, char *tail, char *fmt, va_list ap) +{ + int eindex = errno; + char buffer[BUFSIZ], err[BUFSIZ]; + struct iovec iob[20]; + register struct iovec *iov = iob; + + fflush (stdout); + fflush (stderr); + + if (invo_name) { + iov->iov_len = strlen (iov->iov_base = invo_name); + iov++; + iov->iov_len = strlen (iov->iov_base = ": "); + iov++; + } + + vsnprintf (buffer, sizeof(buffer), fmt, ap); + iov->iov_len = strlen (iov->iov_base = buffer); + iov++; + if (what) { + if (*what) { + iov->iov_len = strlen (iov->iov_base = " "); + iov++; + iov->iov_len = strlen (iov->iov_base = what); + iov++; + iov->iov_len = strlen (iov->iov_base = ": "); + iov++; + } + if (!(iov->iov_base = strerror (eindex))) { + snprintf (err, sizeof(err), "Error %d", eindex); + iov->iov_base = err; + } + iov->iov_len = strlen (iov->iov_base); + iov++; + } + if (tail && *tail) { + iov->iov_len = strlen (iov->iov_base = ", "); + iov++; + iov->iov_len = strlen (iov->iov_base = tail); + iov++; + } + iov->iov_len = strlen (iov->iov_base = "\n"); + iov++; + + if (tty_ready == DONE) + WINwritev (Display, iob, iov - iob); + else + writev (fileno (stderr), iob, iov - iob); +} + diff --git a/uip/vmhsbr.c b/uip/vmhsbr.c new file mode 100644 index 0000000..664c388 --- /dev/null +++ b/uip/vmhsbr.c @@ -0,0 +1,224 @@ + +/* + * vmhsbr.c -- routines to help vmh along + * + * $Id$ + */ + +/* + * TODO (for vrsn 2): + * INI: include width of windows + */ + +#include +#include + +static char *types[] = { + "OK", + "INI", "ACK", "ERR", "CMD", "QRY", "TTY", "WIN", "DATA", "EOF", "FIN", + "XXX", NULL +}; + +static FILE *fp = NULL; + +static int PEERrfd = NOTOK; +static int PEERwfd = NOTOK; + +extern int errno; + +/* + * static prototypes + */ +static int rclose (struct record *, char *, ...); + + +int +rcinit (int rfd, int wfd) +{ + char *cp, buffer[BUFSIZ]; + + PEERrfd = rfd; + PEERwfd = wfd; + + if ((cp = getenv ("MHVDEBUG")) && *cp) { + snprintf (buffer, sizeof(buffer), "%s.out", invo_name); + if ((fp = fopen (buffer, "w"))) { + fseek (fp, 0L, SEEK_END); + fprintf (fp, "%d: rcinit (%d, %d)\n", (int) getpid(), rfd, wfd); + fflush (fp); + } + } + + return OK; +} + + +int +rcdone (void) +{ + if (PEERrfd != NOTOK) + close (PEERrfd); + if (PEERwfd != NOTOK) + close (PEERwfd); + + if (fp) { + fclose (fp); + fp = NULL; + } + return OK; +} + + +int +rc2rc (char code, int len, char *data, struct record *rc) +{ + if (rc2peer (code, len, data) == NOTOK) + return NOTOK; + + return peer2rc (rc); +} + + +int +str2rc (char code, char *str, struct record *rc) +{ + return rc2rc (code, str ? strlen (str) : 0, str, rc); +} + + +int +peer2rc (struct record *rc) +{ + if (rc->rc_data) + free (rc->rc_data); + + if (read (PEERrfd, (char *) rc_head (rc), RHSIZE (rc)) != RHSIZE (rc)) + return rclose (rc, "read from peer lost(1)"); + if (rc->rc_len) { + if ((rc->rc_data = malloc ((unsigned) rc->rc_len + 1)) == NULL) + return rclose (rc, "malloc of %d lost", rc->rc_len + 1); + if (read (PEERrfd, rc->rc_data, rc->rc_len) != rc->rc_len) + return rclose (rc, "read from peer lost(2)"); + rc->rc_data[rc->rc_len] = 0; + } + else + rc->rc_data = NULL; + + if (fp) { + fseek (fp, 0L, SEEK_END); + fprintf (fp, "%d: <--- %s %d: \"%*.*s\"\n", (int) getpid(), + types[rc->rc_type], rc->rc_len, + rc->rc_len, rc->rc_len, rc->rc_data); + fflush (fp); + } + + return rc->rc_type; +} + + +int +rc2peer (char code, int len, char *data) +{ + struct record rcs; + register struct record *rc = &rcs; + + rc->rc_type = code; + rc->rc_len = len; + + if (fp) { + fseek (fp, 0L, SEEK_END); + fprintf (fp, "%d: ---> %s %d: \"%*.*s\"\n", (int) getpid(), + types[rc->rc_type], rc->rc_len, + rc->rc_len, rc->rc_len, data); + fflush (fp); + } + + if (write (PEERwfd, (char *) rc_head (rc), RHSIZE (rc)) != RHSIZE (rc)) + return rclose (rc, "write to peer lost(1)"); + + if (rc->rc_len) + if (write (PEERwfd, data, rc->rc_len) != rc->rc_len) + return rclose (rc, "write to peer lost(2)"); + + return OK; +} + + +int +str2peer (char code, char *str) +{ + return rc2peer (code, str ? strlen (str) : 0, str); +} + + +int +fmt2peer (char code, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + return verr2peer (code, NULL, fmt, ap); + va_end(ap); +} + + +int +err2peer (char code, char *what, char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verr2peer(code, what, fmt, ap); + va_end(ap); +} + + +int +verr2peer (char code, char *what, char *fmt, va_list ap) +{ + int eindex = errno; + int len, buflen; + char *bp, *s, buffer[BUFSIZ * 2]; + + /* Get buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + vsnprintf (bp, buflen, fmt, ap); + len = strlen (bp); + bp += len; + buflen -= len; + + if (what) { + if (*what) { + snprintf (bp, buflen, " %s: ", what); + len = strlen (bp); + bp += len; + buflen -= len; + } + if ((s = strerror (eindex))) + strncpy (bp, s, buflen); + else + snprintf (bp, buflen, "unknown error %d", eindex); + len = strlen (bp); + bp += len; + buflen -= len; + } + + return rc2peer (code, bp - buffer, buffer); +} + + +static int +rclose (struct record *rc, char *fmt, ...) +{ + va_list ap; + static char buffer[BUFSIZ * 2]; + + va_start(ap, fmt); + vsnprintf (buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + rc->rc_len = strlen (rc->rc_data = getcpy (buffer)); + return (rc->rc_type = RC_XXX); +} diff --git a/uip/vmhtest.c b/uip/vmhtest.c new file mode 100644 index 0000000..813e6b7 --- /dev/null +++ b/uip/vmhtest.c @@ -0,0 +1,322 @@ + +/* + * vmhtest.c -- test out vmh protocol + * + * $Id$ + */ + +#include +#include + +static struct swit switches[] = { +#define READSW 0 + { "vmhread fd", 7 }, +#define WRITESW 1 + { "vmhwrite fd", 8 }, +#define VERSIONSW 2 + { "version", 0 }, +#define HELPSW 3 + { "help", 4 }, + { NULL, NULL } +}; + +#define NWIN 20 +static int numwins = 0; +static int windows[NWIN + 1]; + + +static int selcmds = 0; +#define selcmd() (selcmds++ % 2) + +static int selwins = 0; +#define selwin() (selwins++ % 2 ? 3 : 1) + + +int +main (int argc, char **argv) +{ + int fd1, fd2; + char *cp, buffer[BUFSIZ]; + char **argp, **arguments; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* foil search of user profile/context */ + if (context_foil (NULL) == -1) + done (1); + + arguments = getarguments (invo_name, argc, argv, 0); + argp = arguments; + + while ((cp = *argp++)) + if (*cp == '-') + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buffer, sizeof(buffer), "%s [switches]", invo_name); + print_help (buffer, switches, 0); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case READSW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((fd1 = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + case WRITESW: + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + if ((fd2 = atoi (cp)) < 1) + adios (NULL, "bad argument %s %s", argp[-2], cp); + continue; + } + else + adios (NULL, "usage: %s [switches]", invo_name); + + rcinit (fd1, fd2); + pINI (); + pLOOP (); + + done (0); +} + + +static int pINI () { + int i, + vrsn; + char *bp; + struct record rcs, + *rc = &rcs; + + initrc (rc); + + switch (peer2rc (rc)) { + case RC_INI: + bp = rc->rc_data; + while (isspace (*bp)) + bp++; + if (sscanf (bp, "%d", &vrsn) != 1) { + bad_init: ; + fmt2peer (RC_ERR, "bad init \"%s\"", rc->rc_data); + done (1); + } + if (vrsn != RC_VRSN) { + fmt2peer (RC_ERR, "version %d unsupported", vrsn); + done (1); + } + + while (*bp && !isspace (*bp)) + bp++; + while (isspace (*bp)) + bp++; + if (sscanf (bp, "%d", &numwins) != 1 || numwins <= 0) + goto bad_init; + if (numwins > NWIN) + numwins = NWIN; + + for (i = 1; i <= numwins; i++) { + while (*bp && !isspace (*bp)) + bp++; + while (isspace (*bp)) + bp++; + if (sscanf (bp, "%d", &windows[i]) != 1 || windows[i] <= 0) + goto bad_init; + } + rc2peer (RC_ACK, 0, NULL); + return OK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pINI protocol screw-up"); + done (1); /* NOTREACHED */ + } +} + + +static int pLOOP () { + struct record rcs, + *rc = &rcs; + + initrc (rc); + + for (;;) + switch (peer2rc (rc)) { + case RC_QRY: + pQRY (rc->rc_data); + break; + + case RC_CMD: + pCMD (rc->rc_data); + break; + + case RC_FIN: + done (0); + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pLOOP protocol screw-up"); + done (1); + } +} + + +static int pQRY (str) +char *str; +{ + rc2peer (RC_EOF, 0, NULL); + return OK; +} + + +static int pCMD (str) +char *str; +{ + if ((selcmd () ? pTTY (str) : pWIN (str)) == NOTOK) + return NOTOK; + rc2peer (RC_EOF, 0, NULL); + return OK; +} + + +static int pTTY (str) +char *str; +{ + struct record rcs, + *rc = &rcs; + + initrc (rc); + + switch (rc2rc (RC_TTY, 0, NULL, rc)) { + case RC_ACK: + break; + + case RC_ERR: + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pTTY protocol screw-up"); + done (1); + } + + system (str); + + switch (rc2rc (RC_EOF, 0, NULL, rc)) { + case RC_ACK: + return OK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data);/* NOTREACHED */ + + default: + fmt2peer (RC_ERR, "pTTY protocol screw-up"); + done (1); /* NOTREACHED */ + } +} + + +static int pWIN (str) +char *str; +{ + int i, + pid, + pd[2]; + char buffer[BUFSIZ]; + struct record rcs, + *rc = &rcs; + + initrc (rc); + + snprintf (buffer, sizeof(buffer), "%d", selwin ()); + switch (str2rc (RC_WIN, buffer, rc)) { + case RC_ACK: + break; + + case RC_ERR: + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + + if (pipe (pd) == NOTOK) { + fmt2peer (RC_ERR, "no pipes"); + return NOTOK; + } + + switch (pid = vfork ()) { + case NOTOK: + fmt2peer (RC_ERR, "no forks"); + return NOTOK; + + case OK: + close (0); + open ("/dev/null", O_RDONLY); + dup2 (pd[1], 1); + dup2 (pd[1], 2); + close (pd[0]); + close (pd[1]); + execlp ("/bin/sh", "sh", "-c", str, NULL); + write (2, "no shell\n", strlen ("no shell\n")); + _exit (1); + + default: + close (pd[1]); + while ((i = read (pd[0], buffer, sizeof buffer)) > 0) + switch (rc2rc (RC_DATA, i, buffer, rc)) { + case RC_ACK: + break; + + case RC_ERR: + close (pd[0]); + pidwait (pid, OK); + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + if (i == OK) + switch (rc2rc (RC_EOF, 0, NULL, rc)) { + case RC_ACK: + break; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + fmt2peer (RC_ERR, "pWIN protocol screw-up"); + done (1); + } + if (i == NOTOK) + fmt2peer (RC_ERR, "read from pipe lost"); + + close (pd[0]); + pidwait (pid, OK); + return (i != NOTOK ? OK : NOTOK); + } +} diff --git a/uip/whatnow.c b/uip/whatnow.c new file mode 100644 index 0000000..083650e --- /dev/null +++ b/uip/whatnow.c @@ -0,0 +1,18 @@ + +/* + * whatnow.c -- the nmh `WhatNow' shell + * + * $Id$ + */ + +#include + + +int +main (int argc, char **argv) +{ +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + WhatNow (argc, argv); +} diff --git a/uip/whatnowproc.c b/uip/whatnowproc.c new file mode 100644 index 0000000..caeeec2 --- /dev/null +++ b/uip/whatnowproc.c @@ -0,0 +1,115 @@ + +/* + * whatnowproc.c -- exec the "whatnowproc" + * + * $Id$ + */ + +#include + + +/* + * This routine is used by comp, repl, forw, and dist to exec + * the "whatnowproc". It first sets up all the environment + * variables that the "whatnowproc" will need to check, and + * then execs the command. As an optimization, if the + * "whatnowproc" is the nmh command "whatnow" (typical case), + * it will call this routine directly without exec'ing it. + */ + +int +what_now (char *ed, int nedit, int use, char *file, char *altmsg, int dist, + struct msgs *mp, char *text, int inplace, char *cwd) +{ + int found, k, msgnum, vecp; + int len, buflen; + register char *bp; + char buffer[BUFSIZ], *vec[MAXARGS]; + + vecp = 0; + vec[vecp++] = r1bindex (whatnowproc, '/'); + vec[vecp] = NULL; + + m_putenv ("mhdraft", file); + if (mp) + m_putenv ("mhfolder", mp->foldpath); + else + unputenv ("mhfolder"); + if (altmsg) { + if (mp == NULL || *altmsg == '/' || cwd == NULL) + m_putenv ("mhaltmsg", altmsg); + else { + snprintf (buffer, sizeof(buffer), "%s/%s", mp->foldpath, altmsg); + m_putenv ("mhaltmsg", buffer); + } + } else { + unputenv ("mhaltmsg"); + } + if ((bp = getenv ("mhaltmsg")))/* XXX */ + m_putenv ("editalt", bp); + snprintf (buffer, sizeof(buffer), "%d", dist); + m_putenv ("mhdist", buffer); + if (nedit) { + unputenv ("mheditor"); + } else { + m_putenv ("mheditor", ed ? ed : (ed = context_find ("editor")) + ? ed : defaulteditor); + } + snprintf (buffer, sizeof(buffer), "%d", use); + m_putenv ("mhuse", buffer); + + unputenv ("mhmessages"); + unputenv ("mhannotate"); + unputenv ("mhinplace"); + + if (text && mp && !is_readonly(mp)) { + found = 0; + bp = buffer; + buflen = sizeof(buffer); + for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) { + if (is_selected(mp, msgnum)) { + snprintf (bp, buflen, "%s%s", found ? " " : "", m_name (msgnum)); + len = strlen (bp); + bp += len; + buflen -= len; + for (k = msgnum + 1; k <= mp->hghmsg && is_selected(mp, k); k++) + continue; + if (--k > msgnum) { + snprintf (bp, buflen, "-%s", m_name (k)); + len = strlen (bp); + bp += len; + buflen -= len; + } + msgnum = k + 1; + found++; + } + } + if (found) { + m_putenv ("mhmessages", buffer); + m_putenv ("mhannotate", text); + snprintf (buffer, sizeof(buffer), "%d", inplace); + m_putenv ("mhinplace", buffer); + } + } + + context_save (); /* save the context file */ + fflush (stdout); + + if (cwd) + chdir (cwd); + + /* + * If the "whatnowproc" is the nmh command "whatnow", + * we run it internally, rather than exec'ing it. + */ + if (strcmp (vec[0], "whatnow") == 0) { + WhatNow (vecp, vec); + done (0); + } + + execvp (whatnowproc, vec); + fprintf (stderr, "unable to exec "); + perror (whatnowproc); + + return 0; +} diff --git a/uip/whatnowsbr.c b/uip/whatnowsbr.c new file mode 100644 index 0000000..b12307d --- /dev/null +++ b/uip/whatnowsbr.c @@ -0,0 +1,975 @@ + +/* + * whatnowsbr.c -- the WhatNow shell + * + * $Id$ + */ + +#include +#include +#include +#include + +static struct swit whatnowswitches[] = { +#define DFOLDSW 0 + { "draftfolder +folder", 0 }, +#define DMSGSW 1 + { "draftmessage msg", 0 }, +#define NDFLDSW 2 + { "nodraftfolder", 0 }, +#define EDITRSW 3 + { "editor editor", 0 }, +#define NEDITSW 4 + { "noedit", 0 }, +#define PRMPTSW 5 + { "prompt string", 4 }, +#define VERSIONSW 6 + { "version", 0 }, +#define HELPSW 7 + { "help", 4 }, + { NULL, 0 } +}; + +/* + * Options at the "whatnow" prompt + */ +static struct swit aleqs[] = { +#define EDITSW 0 + { "edit [ ]", 0 }, +#define REFILEOPT 1 + { "refile [] +folder", 0 }, +#define BUILDMIMESW 2 + { "mime []", 0 }, +#define DISPSW 3 + { "display []", 0 }, +#define LISTSW 4 + { "list []", 0 }, +#define SENDSW 5 + { "send []", 0 }, +#define PUSHSW 6 + { "push []", 0 }, +#define WHOMSW 7 + { "whom []", 0 }, +#define QUITSW 8 + { "quit [-delete]", 0 }, +#define DELETESW 9 + { "delete", 0 }, + { NULL, 0 } +}; + +static char *myprompt = "\nWhat now? "; + +/* + * static prototypes + */ +static int editfile (char **, char **, char *, int, struct msgs *, + char *, char *, int); +static int sendfile (char **, char *, int); +static void sendit (char *, char **, char *, int); +static int buildfile (char **, char *); +static int check_draft (char *); +static int whomfile (char **, char *); +static int removefile (char *); + +#ifdef HAVE_LSTAT +static int copyf (char *, char *); +#endif + + +int +WhatNow (int argc, char **argv) +{ + int isdf = 0, nedit = 0, use = 0; + char *cp, *dfolder = NULL, *dmsg = NULL; + char *ed = NULL, *drft = NULL, *msgnam = NULL; + char buf[BUFSIZ], prompt[BUFSIZ]; + char **argp, **arguments; + struct stat st; + + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, whatnowswitches)) { + case AMBIGSW: + ambigsw (cp, whatnowswitches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] [file]", invo_name); + print_help (buf, whatnowswitches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (dmsg) + adios (NULL, "only one draft message at a time!"); + if (!(dmsg = *argp++) || *dmsg == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + + case EDITRSW: + if (!(ed = *argp++) || *ed == '-') + adios (NULL, "missing argument to %s", argp[-2]); + nedit = 0; + continue; + case NEDITSW: + nedit++; + continue; + + case PRMPTSW: + if (!(myprompt = *argp++) || *myprompt == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + } + } + if (drft) + adios (NULL, "only one draft at a time!"); + else + drft = cp; + } + + if ((drft == NULL && (drft = getenv ("mhdraft")) == NULL) || *drft == 0) + drft = getcpy (m_draft (dfolder, dmsg, 1, &isdf)); + + msgnam = (cp = getenv ("mhaltmsg")) && *cp ? getcpy (cp) : NULL; + + if ((cp = getenv ("mhuse")) && *cp) + use = atoi (cp); + + if (ed == NULL && ((ed = getenv ("mheditor")) == NULL || *ed == 0)) { + ed = NULL; + nedit++; + } + + /* start editing the draft, unless -noedit was given */ + if (!nedit && editfile (&ed, NULL, drft, use, NULL, msgnam, NULL, 1) < 0) + done (1); + + snprintf (prompt, sizeof(prompt), myprompt, invo_name); + for (;;) { + if (!(argp = getans (prompt, aleqs))) { + unlink (LINK); + done (1); + } + switch (smatch (*argp, aleqs)) { + case DISPSW: + /* display the message being replied to, or distributed */ + if (msgnam) + showfile (++argp, msgnam); + else + advise (NULL, "no alternate message to display"); + break; + + case BUILDMIMESW: + /* Translate MIME composition file */ + buildfile (++argp, drft); + break; + + case EDITSW: + /* Call an editor on the draft file */ + if (*++argp) + ed = *argp++; + if (editfile (&ed, argp, drft, NOUSE, NULL, msgnam, NULL, 1) == NOTOK) + done (1); + break; + + case LISTSW: + /* display the draft file */ + showfile (++argp, drft); + break; + + case WHOMSW: + /* Check to whom the draft would be sent */ + whomfile (++argp, drft); + break; + + case QUITSW: + /* Quit, and possibly delete the draft */ + if (*++argp && (*argp[0] == 'd' || + ((*argp)[0] == '-' && (*argp)[1] == 'd'))) { + removefile (drft); + } else { + if (stat (drft, &st) != NOTOK) + advise (NULL, "draft left on %s", drft); + } + done (1); + + case DELETESW: + /* Delete draft and exit */ + removefile (drft); + done (1); + + case PUSHSW: + /* Send draft in background */ + if (sendfile (++argp, drft, 1)) + done (1); + break; + + case SENDSW: + /* Send draft */ + sendfile (++argp, drft, 0); + break; + + case REFILEOPT: + /* Refile the draft */ + if (refile (++argp, drft) == 0) + done (0); + break; + + default: + /* Unknown command */ + advise (NULL, "say what?"); + break; + } + } + /*NOTREACHED*/ +} + +/* + * EDIT + */ + +static int reedit = 0; /* have we been here before? */ +static char *edsave = NULL; /* the editor we used previously */ + + +static int +editfile (char **ed, char **arg, char *file, int use, struct msgs *mp, + char *altmsg, char *cwd, int save_editor) +{ + int pid, status, vecp; + char altpath[BUFSIZ], linkpath[BUFSIZ]; + char *cp, *vec[MAXARGS]; + struct stat st; + +#ifdef HAVE_LSTAT + int slinked; +#if 0 + int oumask; /* PJS: for setting permissions on symlinks. */ +#endif +#endif /* HAVE_LSTAT */ + + /* Was there a previous edit session? */ + if (reedit) { + if (!*ed) { /* no explicit editor */ + *ed = edsave; /* so use the previous one */ + if ((cp = r1bindex (*ed, '/')) == NULL) + cp = *ed; + + /* unless we've specified it via "editor-next" */ + cp = concat (cp, "-next", NULL); + if ((cp = context_find (cp)) != NULL) + *ed = cp; + } + } else { + /* set initial editor */ + if (*ed == NULL && (*ed = context_find ("editor")) == NULL) + *ed = defaulteditor; + } + + if (altmsg) { + if (mp == NULL || *altmsg == '/' || cwd == NULL) + strncpy (altpath, altmsg, sizeof(altpath)); + else + snprintf (altpath, sizeof(altpath), "%s/%s", mp->foldpath, altmsg); + if (cwd == NULL) + strncpy (linkpath, LINK, sizeof(linkpath)); + else + snprintf (linkpath, sizeof(linkpath), "%s/%s", cwd, LINK); + } + + if (altmsg) { + unlink (linkpath); +#ifdef HAVE_LSTAT + if (link (altpath, linkpath) == NOTOK) { +#if 0 + /* I don't think permission on symlinks matters /JLR */ + oumask = umask(0044); /* PJS: else symlinks are world 'r' */ +#endif + symlink (altpath, linkpath); +#if 0 + umask(oumask); /* PJS: else symlinks are world 'r' */ +#endif + slinked = 1; + } else { + slinked = 0; + } +#else /* not HAVE_LSTAT */ + link (altpath, linkpath); +#endif /* not HAVE_LSTAT */ + } + + context_save (); /* save the context file */ + fflush (stdout); + + switch (pid = vfork ()) { + case NOTOK: + advise ("fork", "unable to"); + status = NOTOK; + break; + + case OK: + if (cwd) + chdir (cwd); + if (altmsg) { + if (mp) + m_putenv ("mhfolder", mp->foldpath); + m_putenv ("editalt", altpath); + } + + vecp = 0; + vec[vecp++] = r1bindex (*ed, '/'); + if (arg) + while (*arg) + vec[vecp++] = *arg++; + vec[vecp++] = file; + vec[vecp] = NULL; + + execvp (*ed, vec); + fprintf (stderr, "unable to exec "); + perror (*ed); + _exit (-1); + + default: + if ((status = pidwait (pid, NOTOK))) { +#ifdef ATTVIBUG + if ((cp = r1bindex (*ed, '/')) + && strcmp (cp, "vi") == 0 + && (status & 0x00ff) == 0) + status = 0; + else { +#endif + if (((status & 0xff00) != 0xff00) + && (!reedit || (status & 0x00ff))) + if (!use && (status & 0xff00) && + (rename (file, cp = m_backup (file)) != NOTOK)) { + advise (NULL, "problems with edit--draft left in %s", cp); + } else { + advise (NULL, "problems with edit--%s preserved", file); + } + status = -2; /* maybe "reedit ? -2 : -1"? */ + break; +#ifdef ATTVIBUG + } +#endif + } + + reedit++; +#ifdef HAVE_LSTAT + if (altmsg + && mp + && !is_readonly(mp) + && (slinked + ? lstat (linkpath, &st) != NOTOK + && S_ISREG(st.st_mode) + && copyf (linkpath, altpath) == NOTOK + : stat (linkpath, &st) != NOTOK + && st.st_nlink == 1 + && (unlink (altpath) == NOTOK + || link (linkpath, altpath) == NOTOK))) + advise (linkpath, "unable to update %s from", altmsg); +#else /* HAVE_LSTAT */ + if (altmsg + && mp + && !is_readonly(mp) + && stat (linkpath, &st) != NOTOK + && st.st_nlink == 1 + && (unlink (altpath) == NOTOK + || link (linkpath, altpath) == NOTOK)) + advise (linkpath, "unable to update %s from", altmsg); +#endif /* HAVE_LSTAT */ + } + + /* normally, we remember which editor we used */ + if (save_editor) + edsave = getcpy (*ed); + + *ed = NULL; + if (altmsg) + unlink (linkpath); + + return status; +} + + +#ifdef HAVE_LSTAT +static int +copyf (char *ifile, char *ofile) +{ + int i, in, out; + char buffer[BUFSIZ]; + + if ((in = open (ifile, O_RDONLY)) == NOTOK) + return NOTOK; + if ((out = open (ofile, O_WRONLY | O_TRUNC)) == NOTOK) { + admonish (ofile, "unable to open and truncate"); + close (in); + return NOTOK; + } + + while ((i = read (in, buffer, sizeof(buffer))) > OK) + if (write (out, buffer, i) != i) { + advise (ofile, "may have damaged"); + i = NOTOK; + break; + } + + close (in); + close (out); + return i; +} +#endif /* HAVE_LSTAT */ + + +/* + * SEND + */ + +static int +sendfile (char **arg, char *file, int pushsw) +{ + pid_t child_id; + int i, vecp; + char *cp, *sp, *vec[MAXARGS]; + + /* Translate MIME composition file, if necessary */ + if ((cp = context_find ("automimeproc")) + && (!strcmp (cp, "1")) + && !getenv ("NOMHNPROC") + && check_draft (file) + && (buildfile (NULL, file) == NOTOK)) + return 0; + + /* For backwards compatibility */ + if ((cp = context_find ("automhnproc")) + && !getenv ("NOMHNPROC") + && check_draft (file) + && (i = editfile (&cp, NULL, file, NOUSE, NULL, NULL, NULL, 0))) + return 0; + + /* + * If the sendproc is the nmh command `send', then we call + * those routines directly rather than exec'ing the command. + */ + if (strcmp (sp = r1bindex (sendproc, '/'), "send") == 0) { + cp = invo_name; + sendit (invo_name = sp, arg, file, pushsw); + invo_name = cp; + return 1; + } + + context_save (); /* save the context file */ + fflush (stdout); + + for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++) + sleep (5); + switch (child_id) { + case NOTOK: + advise (NULL, "unable to fork, so sending directly..."); + case OK: + vecp = 0; + vec[vecp++] = invo_name; + if (pushsw) + vec[vecp++] = "-push"; + if (arg) + while (*arg) + vec[vecp++] = *arg++; + vec[vecp++] = file; + vec[vecp] = NULL; + + execvp (sendproc, vec); + fprintf (stderr, "unable to exec "); + perror (sendproc); + _exit (-1); + + default: + if (pidwait(child_id, OK) == 0) + done (0); + return 1; + } +} + + +/* + * Translate MIME composition file (call buildmimeproc) + */ + +static int +buildfile (char **argp, char *file) +{ + int i; + char **args, *ed; + + ed = buildmimeproc; + + /* allocate space for arguments */ + i = 0; + if (argp) { + while (argp[i]) + i++; + } + if ((args = (char **) malloc((i + 2) * sizeof(char *))) == NULL) + adios (NULL, "unable to malloc memory"); + + /* + * For backward compatibility, we need to add -build + * if we are using mhn as buildmimeproc + */ + i = 0; + if (strcmp (r1bindex (ed, '/'), "mhn") == 0) + args[i++] = "-build"; + + /* copy any other arguments */ + while (argp && *argp) + args[i++] = *argp++; + args[i] = NULL; + + i = editfile (&ed, args, file, NOUSE, NULL, NULL, NULL, 0); + free (args); + + return (i ? NOTOK : OK); +} + + +/* + * Check if draft is a mhbuild composition file + */ + +static int +check_draft (char *msgnam) +{ + int state; + char buf[BUFSIZ], name[NAMESZ]; + FILE *fp; + + if ((fp = fopen (msgnam, "r")) == NULL) + return 0; + for (state = FLD;;) + switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) { + case FLD: + case FLDPLUS: + case FLDEOF: + /* + * If draft already contains any of the + * Content-XXX fields, then assume it already + * been converted. + */ + if (uprf (name, XXX_FIELD_PRF)) { + fclose (fp); + return 0; + } + while (state == FLDPLUS) + state = m_getfld (state, name, buf, sizeof(buf), fp); + break; + + case BODY: + do { + char *bp; + + for (bp = buf; *bp; bp++) + if (*bp != ' ' && *bp != '\t' && *bp != '\n') { + fclose (fp); + return 1; + } + + state = m_getfld (state, name, buf, sizeof(buf), fp); + } while (state == BODY); + /* and fall... */ + + default: + fclose (fp); + return 0; + } +} + + +static struct swit sendswitches[] = { +#define ALIASW 0 + { "alias aliasfile", 0 }, +#define DEBUGSW 1 + { "debug", -5 }, +#define FILTSW 2 + { "filter filterfile", 0 }, +#define NFILTSW 3 + { "nofilter", 0 }, +#define FRMTSW 4 + { "format", 0 }, +#define NFRMTSW 5 + { "noformat", 0 }, +#define FORWSW 6 + { "forward", 0 }, +#define NFORWSW 7 + { "noforward", 0 }, +#define MIMESW 8 + { "mime", 0 }, +#define NMIMESW 9 + { "nomime", 0 }, +#define MSGDSW 10 + { "msgid", 0 }, +#define NMSGDSW 11 + { "nomsgid", 0 }, +#define SPSHSW 12 + { "push", 0 }, +#define NSPSHSW 13 + { "nopush", 0 }, +#define SPLITSW 14 + { "split seconds", 0 }, +#define UNIQSW 15 + { "unique", -6 }, +#define NUNIQSW 16 + { "nounique", -8 }, +#define VERBSW 17 + { "verbose", 0 }, +#define NVERBSW 18 + { "noverbose", 0 }, +#define WATCSW 19 + { "watch", 0 }, +#define NWATCSW 20 + { "nowatch", 0 }, +#define WIDTHSW 21 + { "width columns", 0 }, +#define SVERSIONSW 22 + { "version", 0 }, +#define SHELPSW 23 + { "help", 4 }, +#define BITSTUFFSW 24 + { "dashstuffing", -12 }, +#define NBITSTUFFSW 25 + { "nodashstuffing", -14 }, +#define MAILSW 26 + { "mail", -4 }, +#define SAMLSW 27 + { "saml", -4 }, +#define SSNDSW 28 + { "send", -4 }, +#define SOMLSW 29 + { "soml", -4 }, +#define CLIESW 30 + { "client host", -6 }, +#define SERVSW 31 + { "server host", -6 }, +#define SNOOPSW 32 + { "snoop", -5 }, +#define SDRFSW 33 + { "draftfolder +folder", -6 }, +#define SDRMSW 34 + { "draftmessage msg", -6 }, +#define SNDRFSW 35 + { "nodraftfolder", -3 }, + { NULL, 0 } +}; + + +extern int debugsw; /* from sendsbr.c */ +extern int forwsw; +extern int inplace; +extern int pushsw; +extern int splitsw; +extern int unique; +extern int verbsw; + +extern char *altmsg; /* .. */ +extern char *annotext; +extern char *distfile; + + +static void +sendit (char *sp, char **arg, char *file, int pushed) +{ + int vecp, n = 1; + char *cp, buf[BUFSIZ], **argp; + char **arguments, *vec[MAXARGS]; + struct stat st; + +#ifndef lint + int distsw = 0; +#endif +#ifdef UCI + FILE *fp; +#endif + + /* + * Make sure these are defined. In particular, we need + * vec[1] to be NULL, in case "arg" is NULL below. It + * doesn't matter what is the value of vec[0], but we + * set it to NULL, to help catch "off-by-one" errors. + */ + vec[0] = NULL; + vec[1] = NULL; + + /* + * Temporarily copy arg to vec, since the brkstring() call in + * getarguments() will wipe it out before it is merged in. + * Also, we skip the first element of vec, since getarguments() + * skips it. Then we count the number of arguments + * copied. The value of "n" will be one greater than + * this in order to simulate the standard argc/argv. + */ + if (arg) { + char **bp; + + copyip (arg, vec+1, MAXARGS-1); + bp = vec+1; + while (*bp++) + n++; + } + + /* + * Merge any arguments from command line (now in vec) + * and arguments from profile. + */ + arguments = getarguments (sp, n, vec, 1); + argp = arguments; + + debugsw = 0; + forwsw = 1; + inplace = 1; + unique = 0; + + altmsg = NULL; + annotext = NULL; + distfile = NULL; + + vecp = 1; /* we'll get the zero'th element later */ + vec[vecp++] = "-library"; + vec[vecp++] = getcpy (m_maildir ("")); + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, sendswitches)) { + case AMBIGSW: + ambigsw (cp, sendswitches); + return; + case UNKWNSW: + advise (NULL, "-%s unknown\n", cp); + return; + + case SHELPSW: + snprintf (buf, sizeof(buf), "%s [switches]", sp); + print_help (buf, sendswitches, 1); + return; + case SVERSIONSW: + print_version (invo_name); + return; + + case SPSHSW: + pushed++; + continue; + case NSPSHSW: + pushed = 0; + continue; + + case SPLITSW: + if (!(cp = *argp++) || sscanf (cp, "%d", &splitsw) != 1) { + advise (NULL, "missing argument to %s", argp[-2]); + return; + } + continue; + + case UNIQSW: + unique++; + continue; + case NUNIQSW: + unique = 0; + continue; + case FORWSW: + forwsw++; + continue; + case NFORWSW: + forwsw = 0; + continue; + + case VERBSW: + verbsw++; + vec[vecp++] = --cp; + continue; + case NVERBSW: + verbsw = 0; + vec[vecp++] = --cp; + continue; + + case DEBUGSW: + debugsw++; /* fall */ + case NFILTSW: + case FRMTSW: + case NFRMTSW: + case BITSTUFFSW: + case NBITSTUFFSW: + case MIMESW: + case NMIMESW: + case MSGDSW: + case NMSGDSW: + case WATCSW: + case NWATCSW: + case MAILSW: + case SAMLSW: + case SSNDSW: + case SOMLSW: + case SNOOPSW: + vec[vecp++] = --cp; + continue; + + case ALIASW: + case FILTSW: + case WIDTHSW: + case CLIESW: + case SERVSW: + vec[vecp++] = --cp; + if (!(cp = *argp++) || *cp == '-') { + advise (NULL, "missing argument to %s", argp[-2]); + return; + } + vec[vecp++] = cp; + continue; + + case SDRFSW: + case SDRMSW: + if (!(cp = *argp++) || *cp == '-') { + advise (NULL, "missing argument to %s", argp[-2]); + return; + } + case SNDRFSW: + continue; + } + } + advise (NULL, "usage: %s [switches]", sp); + return; + } + + /* allow Aliasfile: profile entry */ + if ((cp = context_find ("Aliasfile"))) { + char **ap, *dp; + + dp = getcpy (cp); + for (ap = brkstring (dp, " ", "\n"); ap && *ap; ap++) { + vec[vecp++] = "-alias"; + vec[vecp++] = *ap; + } + } + + if ((cp = getenv ("SIGNATURE")) == NULL || *cp == 0) + if ((cp = context_find ("signature")) && *cp) + m_putenv ("SIGNATURE", cp); +#ifdef UCI + else { + snprintf (buf, sizeof(buf), "%s/.signature", mypath); + if ((fp = fopen (buf, "r")) != NULL + && fgets (buf, sizeof(buf), fp) != NULL) { + fclose (fp); + if (cp = strchr (buf, '\n')) + *cp = 0; + m_putenv ("SIGNATURE", buf); + } + } +#endif /* UCI */ + + if ((annotext = getenv ("mhannotate")) == NULL || *annotext == 0) + annotext = NULL; + if ((altmsg = getenv ("mhaltmsg")) == NULL || *altmsg == 0) + altmsg = NULL; + if (annotext && ((cp = getenv ("mhinplace")) != NULL && *cp != 0)) + inplace = atoi (cp); + + if ((cp = getenv ("mhdist")) + && *cp +#ifndef lint + && (distsw = atoi (cp)) +#endif /* not lint */ + && altmsg) { + vec[vecp++] = "-dist"; + distfile = getcpy (m_scratch (altmsg, invo_name)); + if (link (altmsg, distfile) == NOTOK) + adios (distfile, "unable to link %s to", altmsg); + } else { + distfile = NULL; + } + + if (altmsg == NULL || stat (altmsg, &st) == NOTOK) { + st.st_mtime = 0; + st.st_dev = 0; + st.st_ino = 0; + } + if ((pushsw = pushed)) + push (); + + vec[0] = r1bindex (postproc, '/'); + closefds (3); + + if (sendsbr (vec, vecp, file, &st, 1) == OK) + done (0); +} + +/* + * WHOM + */ + +static int +whomfile (char **arg, char *file) +{ + pid_t pid; + int vecp; + char *vec[MAXARGS]; + + context_save (); /* save the context file */ + fflush (stdout); + + switch (pid = vfork ()) { + case NOTOK: + advise ("fork", "unable to"); + return 1; + + case OK: + vecp = 0; + vec[vecp++] = r1bindex (whomproc, '/'); + vec[vecp++] = file; + if (arg) + while (*arg) + vec[vecp++] = *arg++; + vec[vecp] = NULL; + + execvp (whomproc, vec); + fprintf (stderr, "unable to exec "); + perror (whomproc); + _exit (-1); /* NOTREACHED */ + + default: + return (pidwait (pid, NOTOK) & 0377 ? 1 : 0); + } +} + + +/* + * Remove the draft file + */ + +static int +removefile (char *drft) +{ + if (unlink (drft) == NOTOK) + adios (drft, "unable to unlink"); + + return OK; +} diff --git a/uip/whom.c b/uip/whom.c new file mode 100644 index 0000000..aeb8d3b --- /dev/null +++ b/uip/whom.c @@ -0,0 +1,189 @@ + +/* + * whom.c -- report to whom a message would be sent + * + * $Id$ + */ + +#include +#include +#include + +static struct swit switches[] = { +#define ALIASW 0 + { "alias aliasfile", 0 }, +#define CHKSW 1 + { "check", 0 }, +#define NOCHKSW 2 + { "nocheck", 0 }, +#define DRAFTSW 3 + { "draft", 0 }, +#define DFOLDSW 4 + { "draftfolder +folder", 6 }, +#define DMSGSW 5 + { "draftmessage msg", 6 }, +#define NDFLDSW 6 + { "nodraftfolder", 0 }, +#define VERSIONSW 7 + { "version", 0 }, +#define HELPSW 8 + { "help", 4 }, +#define CLIESW 9 + { "client host", -6 }, +#define SERVSW 10 + { "server host", -6 }, +#define SNOOPSW 11 + { "snoop", -5 }, + { NULL, 0 } +}; + + +int +main (int argc, char **argv) +{ + pid_t child_id; + int i, status, isdf = 0; + int distsw = 0, vecp = 0; + char *cp, *dfolder = NULL, *dmsg = NULL; + char *msg = NULL, **ap, **argp, backup[BUFSIZ]; + char buf[BUFSIZ], **arguments, *vec[MAXARGS]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc, argv, 1); + argp = arguments; + + vec[vecp++] = invo_name; + vec[vecp++] = "-whom"; + vec[vecp++] = "-library"; + vec[vecp++] = getcpy (m_maildir ("")); + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + adios (NULL, "-%s unknown", cp); + + case HELPSW: + snprintf (buf, sizeof(buf), "%s [switches] [file]", invo_name); + print_help (buf, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case CHKSW: + case NOCHKSW: + case SNOOPSW: + vec[vecp++] = --cp; + continue; + + case DRAFTSW: + msg = draft; + continue; + + case DFOLDSW: + if (dfolder) + adios (NULL, "only one draft folder at a time!"); + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp, + *cp != '@' ? TFOLDER : TSUBCWF); + continue; + case DMSGSW: + if (dmsg) + adios (NULL, "only one draft message at a time!"); + if (!(dmsg = *argp++) || *dmsg == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NDFLDSW: + dfolder = NULL; + isdf = NOTOK; + continue; + + case ALIASW: + case CLIESW: + case SERVSW: + vec[vecp++] = --cp; + if (!(cp = *argp++) || *cp == '-') + adios (NULL, "missing argument to %s", argp[-2]); + vec[vecp++] = cp; + continue; + } + } + if (msg) + adios (NULL, "only one draft at a time!"); + else + vec[vecp++] = msg = cp; + } + + /* allow Aliasfile: profile entry */ + if ((cp = context_find ("Aliasfile"))) { + char *dp = NULL; + + for (ap = brkstring(dp = getcpy(cp), " ", "\n"); ap && *ap; ap++) { + vec[vecp++] = "-alias"; + vec[vecp++] = *ap; + } + } + + if (msg == NULL) { +#ifdef WHATNOW + if (dfolder || (cp = getenv ("mhdraft")) == NULL || *cp == '\0') +#endif /* WHATNOW */ + cp = getcpy (m_draft (dfolder, dmsg, 1, &isdf)); + msg = vec[vecp++] = cp; + } + if ((cp = getenv ("mhdist")) + && *cp + && (distsw = atoi (cp)) + && (cp = getenv ("mhaltmsg")) + && *cp) { + if (distout (msg, cp, backup) == NOTOK) + done (1); + vec[vecp++] = "-dist"; + distsw++; + } + vec[vecp] = NULL; + + closefds (3); + + if (distsw) { + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep (5); + } + + switch (distsw ? child_id : OK) { + case NOTOK: + advise (NULL, "unable to fork, so checking directly..."); + case OK: + execvp (postproc, vec); + fprintf (stderr, "unable to exec "); + perror (postproc); + _exit (-1); + + default: + SIGNAL (SIGHUP, SIG_IGN); + SIGNAL (SIGINT, SIG_IGN); + SIGNAL (SIGQUIT, SIG_IGN); + SIGNAL (SIGTERM, SIG_IGN); + + status = pidwait(child_id, OK); + + unlink (msg); + if (rename (backup, msg) == NOTOK) + adios (msg, "unable to rename %s to", backup); + done (status); + } + + exit (-1); /* NOT REACHED */ +} diff --git a/uip/wmh.c b/uip/wmh.c new file mode 100644 index 0000000..fce1f59 --- /dev/null +++ b/uip/wmh.c @@ -0,0 +1,1387 @@ + +/* + * wmh.c -- window front-end to nmh + * + * $Id$ + */ + +/* + * TODO: + * Pass signals to client during execution + * + * Figure out a way for the user to say how big the Scan/Display + * windows should be, and where all the windows should be. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ALARM ((unsigned int) 10) +#define PAUSE ((unsigned int) 2) + +#define SZ(a) (sizeof a / sizeof a[0]) + +static struct swit switches[] = { +#define PRMPTSW 0 + { "prompt string", 6 }, +#define PROGSW 1 + { "vmhproc program", 7 }, +#define NPROGSW 2 + { "novmhproc", 9 }, +#define VERSIONSW 3 + { "version", 0 }, +#define HELPSW 4 + { "help", 4 }, + { NULL, NULL } +}; + /* PEERS */ +static int PEERpid = NOTOK; + +static jmp_buf PEERctx; + + + /* WINDOWS */ +static int dfd = NOTOK; +static int twd = NOTOK; +static char *myprompt = "(%s) "; + +struct line { + int l_no; + char *l_buf; + struct line *l_prev; + struct line *l_next; +}; + +#define W_NULL 0x00 +#define W_CMND 0x01 +#define W_FAKE 0x02 +#define W_EBAR 0x04 + +typedef struct { + int w_fd; + int w_flags; + int w_wd; + struct wstate w_ws; + char *w_eb; + int w_ebloc; + int w_ebsize; + int w_cbase; + int w_height; + int w_cheight; + int w_width; + int w_cwidth; + struct line *w_head; + struct line *w_top; + struct line *w_bottom; + struct line *w_tail; + char w_buffer[BUFSIZ]; + int w_bufpos; +} WINDOW; + + +static WINDOW *Scan; +static WINDOW *Status; +static WINDOW *Display; +static WINDOW *Command; + +#define NWIN 4 +static int numwins; +WINDOW *windows[NWIN + 1]; + +WINDOW *WINnew (); + + +#ifdef HAVE_TERMIOS_H +static struct termios tio; +# define ERASE tio.c_cc[VERASE] +# define KILL tio.c_cc[VKILL] +# define INTR tio.c_cc[VINTR] +#else +# ifdef HAVE_TERMIO_H +static struct termio tio; +# define ERASE tio.c_cc[VERASE] +# define KILL tio.c_cc[VKILL] +# define INTR tio.c_cc[VINTR] +# else +static struct sgttyb tio; +static struct tchars tc; +# define ERASE tio.sg_erase +# define KILL tio.sg_kill +# define INTR tc.t_intrc +# define EOFC tc.t_eofc +# endif +#endif + +#define WERASC ltc.t_werasc +static struct ltchars ltc; + +extern int errno; + +int ALRMser (), PIPEser (), SIGser (); +int ADJser (), REFser (); + +/* + * static prototypes + */ +static void adorn(char *, char *, ...); + + +int +main (int argc, char **argv) +{ + int vecp = 1, nprog = 0; + char *cp, buffer[BUFSIZ], **argp; + char **arguments, *vec[MAXARGS]; + +#ifdef LOCALE + setlocale(LC_ALL, ""); +#endif + invo_name = r1bindex (argv[0], '/'); + + /* read user profile/context */ + context_read(); + + arguments = getarguments (invo_name, argc,argv, 1); + argp = arguments; + + while ((cp = *argp++)) + if (*cp == '-') + switch (smatch (++cp, switches)) { + case AMBIGSW: + ambigsw (cp, switches); + done (1); + case UNKWNSW: + vec[vecp++] = --cp; + continue; + + case HELPSW: + snprintf (buffer, sizeof(buffer), "%s [switches for vmhproc]", + invo_name); + print_help (buffer, switches, 1); + done (1); + case VERSIONSW: + print_version(invo_name); + done (1); + + case PRMPTSW: + if (!(myprompt = *argp++) || *myprompt == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + + case PROGSW: + if (!(vmhproc = *argp++) || *vmhproc == '-') + adios (NULL, "missing argument to %s", argp[-2]); + continue; + case NPROGSW: + nprog++; + continue; + } + else + vec[vecp++] = cp; + + SIGinit (); + if (WINinit (nprog) == NOTOK) { + vec[vecp] = NULL; + + vec[0] = r1bindex (vmhproc, '/'); + execvp (vmhproc, vec); + adios (vmhproc, "unable to exec"); + } + PEERinit (vecp, vec); + + vmh (); + + done (0); +} + + +static void +vmh (void) +{ + char buffer[BUFSIZ], prompt[BUFSIZ]; + + for (;;) { + pLOOP (RC_QRY, NULL); + + snprintf (prompt, sizeof(prompt), myprompt, invo_name); + + switch (WINgetstr (Command, prompt, buffer)) { + case NOTOK: + break; + + case OK: + done (0); /* NOTREACHED */ + + default: + if (*buffer) + pLOOP (RC_CMD, buffer); + break; + } + } +} + +/* PEERS */ + +static int +PEERinit (int vecp, char *vec[]) +{ + int pfd0[2], pfd1[2]; + char buf1[BUFSIZ], buf2[BUFSIZ]; + register WINDOW **w; + + SIGNAL (SIGPIPE, PIPEser); + + if (pipe (pfd0) == NOTOK || pipe (pfd1) == NOTOK) + adios ("pipe", "unable to"); + switch (PEERpid = vfork ()) { + case NOTOK: + adios ("vfork", "unable to");/* NOTREACHED */ + + case OK: + for (w = windows; *w; w++) + if ((*w)->w_fd != NOTOK) + close ((*w)->w_fd); + close (pfd0[0]); + close (pfd1[1]); + + vec[vecp++] = "-vmhread"; + snprintf (buf1, sizeof(buf1), "%d", pfd1[0]); + vec[vecp++] = buf1; + vec[vecp++] = "-vmhwrite"; + snprintf (buf2, sizeof(buf2), "%d", pfd0[1]); + vec[vecp++] = buf2; + vec[vecp] = NULL; + + SIGNAL (SIGINT, SIG_DFL); + SIGNAL (SIGQUIT, SIG_DFL); + SIGNAL (SIGTERM, SIG_DFL); + + vec[0] = r1bindex (vmhproc, '/'); + execvp (vmhproc, vec); + perror (vmhproc); + _exit (-1); /* NOTREACHED */ + + default: + close (pfd0[1]); + close (pfd1[0]); + + rcinit (pfd0[0], pfd1[1]); + return pINI (); + } +} + + +static int +pINI (void) +{ + int len, buflen; + char *bp, buffer[BUFSIZ]; + struct record rcs, *rc; + WINDOW **w; + + rc = &rcs; + initrc (rc); + + /* Get buffer ready to go */ + bp = buffer; + buflen = sizeof(buffer); + + snprintf (bp, buflen, "%d %d", RC_VRSN, numwins); + len = strlen (bp); + bp += len; + buflen -= len; + + for (w = windows; *w; w++) { + snprintf (bp, buflen, " %d", (*w)->w_height); + len = strlen (bp); + bp += len; + buflen -= len; + } + + switch (str2rc (RC_INI, buffer, rc)) { + case RC_ACK: + return OK; + + case RC_ERR: + if (rc->rc_len) + adios (NULL, "%s", rc->rc_data); + else + adios (NULL, "pINI peer error"); + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pINI protocol screw-up"); + } +/* NOTREACHED */ +} + + +static int +pLOOP (char code, char *str) +{ + int i; + struct record rcs, *rc; + WINDOW *w; + + rc = &rcs; + initrc (rc); + + str2peer (code, str); + for (;;) + switch (peer2rc (rc)) { + case RC_TTY: + if (pTTY () == NOTOK) + return NOTOK; + break; + + case RC_WIN: + if (sscanf (rc->rc_data, "%d", &i) != 1 + || i <= 0 + || i > numwins) { + fmt2peer (RC_ERR, "no such window \"%s\"", rc->rc_data); + return NOTOK; + } + if ((w = windows[i - 1])->w_flags & W_CMND) { + fmt2peer (RC_ERR, "not a display window \"%s\"", rc->rc_data); + return NOTOK; + } + if (pWIN (w) == NOTOK) + return NOTOK; + break; + + case RC_EOF: + return OK; + + case RC_ERR: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + else + adorn (NULL, "pLOOP(%s) peer error", + code == RC_QRY ? "QRY" : "CMD"); + return NOTOK; + + case RC_FIN: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + rcdone (); + i = pidwait (PEERpid, OK); + PEERpid = NOTOK; + done (i); + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pLOOP(%s) protocol screw-up", + code == RC_QRY ? "QRY" : "CMD"); + } +} + + +static int +pTTY (void) +{ + SIGNAL_HANDLER hstat, istat, qstat, tstat; + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + if (ChangeWindowDepth (dfd, twd, 0) == NOTOK) + adios ("failed", "ChangeWindowDepth"); + + /* should block here instead of ignore */ + hstat = SIGNAL (SIGHUP, SIG_IGN); + istat = SIGNAL (SIGINT, SIG_IGN); + qstat = SIGNAL (SIGQUIT, SIG_IGN); + tstat = SIGNAL (SIGTERM, SIG_IGN); + + rc2rc (RC_ACK, 0, NULL, rc); + + SIGNAL (SIGHUP, hstat); + SIGNAL (SIGINT, istat); + SIGNAL (SIGQUIT, qstat); + SIGNAL (SIGTERM, tstat); + + switch (rc->rc_type) { + case RC_EOF: + rc2peer (RC_ACK, 0, NULL); + return OK; + + case RC_ERR: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + else + adorn (NULL, "pTTY peer error"); + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pTTY protocol screw-up"); + } +/* NOTREACHED */ +} + + +static int +pWIN (WINDOW *w) +{ + int i; + + if ((i = pWINaux (w)) == OK) + WINless (w); + + return i; +} + + +static int +pWINaux (WINDOW *w) +{ + register int n; + register char *bp; + register struct line *lp, *mp; + struct record rcs, *rc; + + rc = &rcs; + initrc (rc); + + for (lp = w->w_head; lp; lp = mp) { + mp = lp->l_next; + free (lp->l_buf); + free ((char *) lp); + } + w->w_head = w->w_top = w->w_bottom = w->w_tail = NULL; + w->w_bufpos = 0; + + for (;;) + switch (rc2rc (RC_ACK, 0, NULL, rc)) { + case RC_DATA: + for (bp = rc->rc_data, n = rc->rc_len; n-- > 0; ) + WINputc (w, *bp++); + break; + + case RC_EOF: + rc2peer (RC_ACK, 0, NULL); + if (w->w_bufpos) + WINputc (w, '\n'); + return OK; + + case RC_ERR: + if (rc->rc_len) + adorn (NULL, "%s", rc->rc_data); + else + adorn (NULL, "pWIN peer error"); + return NOTOK; + + case RC_XXX: + adios (NULL, "%s", rc->rc_data); + + default: + adios (NULL, "pWIN protocol screw-up"); + } +/* NOTREACHED */ +} + + +static int +pFIN (void) +{ + int status; + + if (PEERpid <= OK) + return OK; + + rc2peer (RC_FIN, 0, NULL); + rcdone (); + + switch (setjmp (PEERctx)) { + case OK: + SIGNAL (SIGALRM, ALRMser); + alarm (ALARM); + + status = pidwait (PEERpid, OK); + + alarm (0); + break; + + default: + kill (PEERpid, SIGKILL); + status = NOTOK; + break; + } + PEERpid = NOTOK; + + return status; +} + +/* WINDOWS */ + +/* should dynamically determine all this stuff from gconfig... */ + +#define MyX 20 /* anchored hpos */ +#define MyY 40 /* .. vpos */ +#define MyW 800 /* .. width */ +#define MyH 500 /* .. height */ +#define MyS 30 /* .. height for Status, about one line */ + + +#define MySlop 45 /* slop */ + +#define EWIDTH 25 /* Width of vertical EBAR */ +#define ESLOP 5 /* .. slop */ + + +static intWINinit (nprog) { + short wx, wy, wh, sy; + struct gconfig gc; + + if (GetGraphicsConfig (fileno (stderr), &gc) == NOTOK) + if (nprog) + return NOTOK; + else + adios (NULL, "not a window"); + + if ((dfd = open ("/dev/ttyw0", O_RDWR)) == NOTOK) + adios ("/dev/ttyw0", "unable to open"); + + if ((twd = GetTopWindow (dfd)) == NOTOK) + adios ("failed", "GetTopWindow"); + + BlockRefreshAdjust (1); + + numwins = 0; + + wx = gc.w - (MyX + MyW + EWIDTH + ESLOP); + Scan = WINnew (wx, wy = MyY, MyW, wh = MyH * 2 / 3, "Scan", W_EBAR); + + wy += wh + MySlop; + Status = WINnew (wx, sy = wy, MyW, wh = MyS, "Status", W_FAKE); + + wy += wh + MySlop; + Display = WINnew (wx, wy, MyW, MyH, "Display", W_EBAR); + + Command = WINnew (wx, sy, MyW, MyS, invo_name, W_CMND); + + windows[numwins] = NULL; + + return OK; +} + + +WINDOW * +WINnew (short wx, short wy, short ww, short wh, char *name, int flags) +{ + register WINDOW *w; + + if ((w = (WINDOW *) calloc (1, sizeof *w)) == NULL) + adios (NULL, "unable to allocate window"); + + if ((w->w_flags = flags) & W_FAKE) { + w->w_fd = NOTOK; + w->w_height = 1; + + goto out; + } + + if (w->w_flags & W_EBAR) + ww += EWIDTH + ESLOP; + else + wx += EWIDTH + ESLOP; + + if ((w->w_fd = OpenWindow (wx, wy, ww, wh, name)) == NOTOK) + adios ("failed", "OpenWindow"); + if ((w->w_wd = GetTopWindow (dfd)) == NOTOK) + adios ("failed", "GetTopWindow"); + if (GetWindowState (w->w_fd, &w->w_ws) == NOTOK) + adios ("failed", "GetWindowState"); + if (SetLineDisc (w->w_fd, TWSDISC) == NOTOK) + adios ("failed", "SetLineDisc"); + + SetBuf (w->w_fd, 1024); + SetAdjust (w->w_fd, numwins, ADJser); + SetRefresh (w->w_fd, numwins, REFser); + + SetAddressing (w->w_fd, VT_ABSOLUTE); + + if (w->w_flags & W_EBAR) { + w->w_eb = CreateElevatorBar (w->w_fd, 0, 0, EWIDTH, + w->w_ws.height, VT_Gray50, 1, EB_VERTICAL, + EB_ARROWS, w->w_ebloc = 0, w->w_ebsize = EB_MAX, + VT_White); + if (w->w_eb == NULL) + adios (NULL, "CreateElevatorBar failed"); + RefreshElevatorBar (w->w_eb); + } + + if ((w->w_cbase = CharacterBaseline (w->w_ws.font)) <= 0) + w->w_cbase = 14; + + if ((w->w_cheight = CharacterHeight (w->w_ws.font)) <= 0) + w->w_cheight = 20; + w->w_height = w->w_ws.height / w->w_cheight; + if (w->w_height < 1) + w->w_height = 1; + + /* 1 em */ + if ((w->w_cwidth = CharacterWidth (w->w_ws.font, 'm')) <= 0) + w->w_cwidth = 10; + w->w_width = (w->w_ws.width - (w->w_eb ? (EWIDTH + ESLOP) : 0)) + / w->w_cwidth; + if (w->w_width < 1) + w->w_width = 1; + +out: ; + windows[numwins++] = w; + + return w; +} + + +static int +WINgetstr (WINDOW *w, char *prompt, char *buffer) +{ + register int c; + register char *bp, *ip; + char image[BUFSIZ]; + struct vtseq vts; + register struct vtseq *vt = &vts; + + if (w->w_eb != NULL) + adios (NULL, "internal error--elevator bar found"); + + if (w->w_head == NULL + && (w->w_head = (struct line *) calloc (1, sizeof *w->w_head)) + == NULL) + adios (NULL, "unable to allocate line storage"); + w->w_head->l_buf = image; + w->w_top = w->w_bottom = w->w_tail = w->w_head; + + if (ChangeWindowDepth (dfd, w->w_wd, 0) == NOTOK) + adios ("failed", "ChangeWindowDepth"); + + strncpy (image, prompt, sizeof(image)); + bp = ip = image + strlen (image); + + Redisplay (w, 0); + + for (;;) + switch (getvtseq (w->w_fd, vt)) { + case VT_HARDKEY: + DisplayStatus (w->w_fd, "no hardkeys, please"); + break; + + case VT_ASCII: + switch (c = toascii (vt->u.ascii)) { + case '\f': /* refresh? */ + break; + + case '\r': + case '\n': + strcpy (buffer, ip); + return DONE; + + default: + if (c == INTR) { + adorn (NULL, "Interrupt"); + return NOTOK; + } + + if (c == EOFC) { + if (bp <= ip) + return OK; + break; + } + + if (c == ERASE) { + if (bp <= ip) + continue; + bp--; + break; + } + + if (c == KILL) { + if (bp <= ip) + continue; + bp = ip; + break; + } + + if (c == WERASC) { + if (bp <= ip) + continue; + do { + bp--; + } while (isspace (*bp) && bp > ip); + if (bp > ip) { + do { + bp--; + } while (!isspace (*bp) && bp > buffer); + if (isspace (*bp)) + bp++; + } + break; + } + + if (c < ' ' || c >= '\177') + continue; + *bp++ = c; + break; + } + *bp = NULL; + Redisplay (w, 0); + break; + + case VT_MOUSE: + switch (vt->u.mouse.buttons + & (VT_MOUSE_LEFT | VT_MOUSE_MIDDLE | VT_MOUSE_RIGHT)) { + case VT_MOUSE_LEFT: + DisplayStatus (w->w_fd, "use middle or right button"); + break; + +#define WPOP "WMH\0Advance\0Burst\0Exit\0EOF\0" + case VT_MOUSE_MIDDLE: + SetPosition (w->w_fd, vt->u.mouse.x, + vt->u.mouse.y); + switch (DisplayPopUp (w->w_fd, WPOP)) { + case 1: /* Advance */ + do_advance: ; + strcpy (buffer, "advance"); + return DONE; + + case 2: /* Burst */ + strcpy (buffer, "burst"); + return DONE; + + case 3: /* Exit */ + strcpy (buffer, "exit"); + return DONE; + + case 4: /* EOF */ + return OK; + + default: /* failed or none taken */ + break; + } + break; +#undef WPOP + + case VT_MOUSE_RIGHT: + goto do_advance; + } + break; + + case VT_EOF: + adios (NULL, "end-of-file on window");/* NOTREACHED */ + + default: + DisplayStatus (w->w_fd, "unknown VT sequence"); + break; + } +} + + +static int +WINputc (WINDOW *w, char c) +{ + register int i; + register char *cp; + register struct line *lp; + + switch (c) { + default: + if (!isascii (c)) { + if (WINputc (w, 'M') == NOTOK || WINputc (w, '-') == NOTOK) + return NOTOK; + c = toascii (c); + } + else + if (c < ' ' || c == '\177') { + if (WINputc (w, '^') == NOTOK) + return NOTOK; + c ^= 0100; + } + break; + + case '\t': + for (i = 8 - (w->w_bufpos & 0x07); i > 0; i--) + if (WINputc (w, ' ') == NOTOK) + return NOTOK; + return OK; + + case '\b': + if (w->w_bufpos > 0) + w->w_bufpos--; + return OK; + + case '\n': + break; + } + + if (c != '\n') { + w->w_buffer[w->w_bufpos++] = c; + return OK; + } + + w->w_buffer[w->w_bufpos] = NULL; + w->w_bufpos = 0; + + if ((lp = (struct line *) calloc (1, sizeof *lp)) == NULL) + adios (NULL, "unable to allocate line storage"); + + lp->l_no = (w->w_tail ? w->w_tail->l_no : 0) + 1; + lp->l_buf = getcpy (w->w_buffer); + for (cp = lp->l_buf + strlen (lp->l_buf) - 1; cp >= lp->l_buf; cp--) + if (isspace (*cp)) + *cp = NULL; + else + break; + + if (w->w_head == NULL) + w->w_head = lp; + if (w->w_top == NULL) + w->w_top = lp; + if (w->w_bottom == NULL) + w->w_bottom = lp; + if (w->w_tail) + w->w_tail->l_next = lp; + lp->l_prev = w->w_tail; + w->w_tail = lp; + + return DONE; +} + +#define PSLOP 2 + + +static char mylineno[5]; + +static bool cancel[] = { 1 }; +static struct choice mychoices[] = { LABEL, "cancel", VT_White }; + +static struct question myquestions[] = { + STRING, "Line", SZ (mylineno), (struct choice *) 0, + + TOGGLE, "", SZ (mychoices), mychoices +}; + +static struct menu mymenu = { "Goto", SZ (myquestions), myquestions }; + +static int *myanswers[] = { (int *) mylineno, (int *) cancel }; + + +static void +WINless (WINDOW *w) +{ + int clear, pos, forw, refresh; + struct vtseq vts; + register struct vtseq *vt = &vts; + + if (w->w_fd == NOTOK) { + if (w->w_head) + DisplayStatus (dfd, w->w_top->l_buf); + else + RemoveStatus (dfd); + + return; + } + + if (ChangeWindowDepth (dfd, w->w_wd, 0) == NOTOK) + adios ("failed", "ChangeWindowDepth"); + + Redisplay (w, 0); + + if (w->w_bottom == w->w_tail) + return; + + if (w->w_eb == NULL) + adios (NULL, "internal error--no elevator bar"); + + for (clear = refresh = 0, forw = 1;;) { + if (clear) { + RemoveStatus (w->w_fd); + clear = 0; + } + if (refresh) { + Redisplay (w, 0); + refresh = 0; + } + + switch (getvtseq (w->w_fd, vt)) { + case VT_HARDKEY: + case VT_ASCII: + DisplayStatus (w->w_fd, "use the mouse"); + clear++; + break; + + case VT_MOUSE: + switch (vt->u.mouse.buttons + & (VT_MOUSE_LEFT | VT_MOUSE_MIDDLE | VT_MOUSE_RIGHT)) { + case VT_MOUSE_LEFT: + if ((pos = vt->u.mouse.x) < EWIDTH) { + pos = w->w_ebloc = DoElevatorBar (w->w_eb, pos, + vt->u.mouse.y); + refresh = WINgoto (w, ((pos * (w->w_tail->l_no + - w->w_head->l_no)) + / EB_MAX) + w->w_head->l_no); + } + break; + +#define WPOP "Paging\0Next\0Prev\0Left\0Right\0First\0Last\0Goto ...\0Exit\0" + case VT_MOUSE_MIDDLE: + SetPosition (w->w_fd, vt->u.mouse.x, + vt->u.mouse.y); + switch (DisplayPopUp (w->w_fd, WPOP)) { + case 1: /* Next */ + do_next_page: ; + if (w->w_bottom == w->w_tail) + forw = 0; + refresh = WINgoto (w, w->w_bottom->l_no + 1 - PSLOP); + break; + + case 2: /* Prev */ + do_prev_page: ; + if (w->w_top == w->w_head) + forw = 1; + refresh = WINgoto (w, w->w_top->l_no + - w->w_height + PSLOP); + break; + + case 3: /* Left */ + case 4: /* Right */ + DisplayStatus (w->w_fd, "not yet"); + clear++; + break; + + case 5: /* First */ + forw = 1; + refresh = WINgoto (w, w->w_head->l_no); + break; + + case 6: /* Last */ + forw = 0; + refresh = WINgoto (w, w->w_tail->l_no + - w->w_height + 1); + break; + + case 7: /* Goto ... */ + snprintf (mylineno, sizeof(mylineno), + "%d", w->w_top->l_no); + cancel[0] = 0; + if (PresentMenu (&mymenu, myanswers) + || cancel[0]) + break; + if (sscanf (mylineno, "%d", &pos) != 1) { + DisplayStatus (w->w_fd, "bad format"); + clear++; + break; + } + if (pos < w->w_head->l_no + || pos > w->w_tail->l_no) { + DisplayStatus (w->w_fd, "no such line"); + clear++; + break; + } + refresh = WINgoto (w, pos); + break; + + case 8: /* Exit */ + return; + + default: /* failed or none taken */ + break; + } + break; +#undef WPOP + + case VT_MOUSE_RIGHT: + if (forw) { + if (w->w_bottom == w->w_tail) + return; + else + goto do_next_page; + } + else + goto do_prev_page; + } + break; + + case VT_EOF: + adios (NULL, "end-of-file on window");/* NOTREACHED */ + + default: + DisplayStatus (w->w_fd, "unknown VT sequence"); + clear++; + break; + } + } +} + + +static int +WINgoto (WINDOW *w, int n) +{ + register int i, j; + register struct line *lp; + + if (n > (i = w->w_tail->l_no - w->w_height + 1)) + n = i; + if (n < w->w_head->l_no) + n = w->w_head->l_no; + + if ((i = n - (lp = w->w_head)->l_no) + > (j = abs (n - w->w_top->l_no))) + i = j, lp = w->w_top; + + if (i > (j = abs (w->w_tail->l_no - n))) + i = j, lp = w->w_tail; + + if (n >= lp->l_no) { + for (; lp; lp = lp->l_next) + if (lp->l_no == n) + break; + } + else { + for (; lp; lp = lp->l_prev) + if (lp->l_no == n) + break; + if (!lp) + lp = w->w_head; + } + + if (w->w_top == lp) + return 0; + + w->w_top = lp; + + return 1; +} + + +static int +ADJser (int id, short ww, short wh) +{ + register WINDOW *w; + + if (id < 0 || id >= numwins) + adios (NULL, "ADJser on bogus window (%d)", id); + w = windows[id]; + if (w->w_fd == NOTOK) + adios (NULL, "ADJser on closed window (%d)", id); + + w->w_ws.width = w->w_ws.tw = ww; + w->w_ws.height = w->w_ws.th = wh; + + if (w->w_eb) { + DeleteElevatorBar (w->w_eb); + w->w_eb = CreateElevatorBar (w->w_fd, 0, 0, EWIDTH, + w->w_ws.height, VT_Gray50, 1, EB_VERTICAL, + EB_ARROWS, w->w_ebloc = 0, w->w_ebsize = EB_MAX, + VT_White); + if (w->w_eb == NULL) + adios (NULL, "CreateElevatorBar failed"); + } + + Redisplay (w, 1); +} + + +static int +REFser (int id, short wx, short wy, short ww, short wh) +{ + short cx, cy, cw, ch; + register WINDOW *w; + + if (id < 0 || id >= numwins) + adios (NULL, "REFser on bogus window (%d)", id); + w = windows[id]; + if (w->w_fd == NOTOK) + adios (NULL, "REFser on closed window (%d)", id); + + + if (GetWindowState (w->w_fd, &w->w_ws) == NOTOK) + adios ("failed", "GetWindowState"); + + GetPermanentClipping (w->w_fd, &cx, &cy, &cw, &ch); + SetPermanentClipping (w->w_fd, wx, wy, ww, wh); + Redisplay (w, 1); + SetPermanentClipping (w->w_fd, cx, cy, cw, ch); +} + + +static void +Redisplay (WINDOW *w, int doeb) +{ + register int y; + short sx; + register struct line *lp; + + if (w->w_fd == NOTOK) + return; + + sx = w->w_eb ? (EWIDTH + ESLOP) : 0; + w->w_height = w->w_ws.height / w->w_cheight; + if (w->w_height < 1) + w->w_height = 1; + + w->w_width = (w->w_ws.width - (w->w_eb ? (EWIDTH + ESLOP) : 0)) + / w->w_cwidth; + if (w->w_width < 1) + w->w_width = 1; + + SetPosition (w->w_fd, sx, 0); + SetColor (w->w_fd, VT_White); + PaintRectangleInterior (w->w_fd, w->w_ws.width, w->w_ws.height); + + if (w->w_head) { + SetColor (w->w_fd, VT_Black); + for (lp = w->w_top, y = 0; + lp && y < w->w_height; + w->w_bottom = lp, lp = lp->l_next, y++) { + SetPosition (w->w_fd, sx, y * w->w_cheight + w->w_cbase); + PaintString (w->w_fd, VT_STREND, lp->l_buf); + } + } + + if (w->w_eb) { + if ((y = EB_LOC (w)) != w->w_ebloc) + MoveElevator (w->w_eb, w->w_ebloc = y); + if ((y = EB_SIZE (w)) != w->w_ebsize) + SizeElevator (w->w_eb, w->w_ebsize = y); + if (doeb) + RefreshElevatorBar (w->w_eb); + } + + Flush (w->w_fd); +} + + +static int +EB_SIZE (WINDOW *w) +{ + register int i; + + if (w->w_head == NULL) + return 0; + + if ((i = w->w_tail->l_no - w->w_head->l_no) <= 0) + return EB_MAX; + + return (((w->w_bottom->l_no - w->w_top->l_no) * EB_MAX) / i); +} + + +static int +EB_LOC (WINDOW *w) +{ + register int i; + + if (w->w_head == NULL) + return 0; + + if ((i = w->w_tail->l_no - w->w_head->l_no) <= 0) + return EB_MAX; + + return (((w->w_top->l_no - w->w_head->l_no) * EB_MAX) / i); +} + +/* SIGNALS */ + +static void +SIGinit (void) +{ + foreground (); + if (ioctl (fileno (stdin), TIOCGETP, (char *) &tio) == NOTOK) + adios ("failed", "ioctl TIOCGETP"); + if (ioctl (fileno (stdin), TIOCGETC, (char *) &tc) == NOTOK) + adios ("failed", "ioctl TIOCGETC"); + if (ioctl (fileno (stdin), TIOCGLTC, (char *) <c) == NOTOK) + adios ("failed", "ioctl TIOCGLTC"); + sideground (); + + SIGNAL (SIGHUP, SIGser); + SIGNAL (SIGINT, SIGser); + SIGNAL (SIGQUIT, SIGser); +} + + +static void +foreground (void) +{ +#ifdef TIOCGPGRP + int pgrp, tpgrp; + SIGNAL_HANDLER tstat; + + if ((pgrp = getpgrp (0)) == NOTOK) + adios ("process group", "unable to determine"); + for (;;) { + if (ioctl (fileno (stdin), TIOCGPGRP, (char *) &tpgrp) == NOTOK) + adios ("tty's process group", "unable to determine"); + if (pgrp == tpgrp) + break; + + tstat = SIGNAL (SIGTTIN, SIG_DFL); + kill (0, SIGTTIN); + SIGNAL (SIGTTIN, tstat); + } + + SIGNAL (SIGTTIN, SIG_IGN); + SIGNAL (SIGTTOU, SIG_IGN); + SIGNAL (SIGTSTP, SIG_IGN); +#endif TIOCGPGRP +} + + +static void +sideground (void) +{ +#ifdef TIOCGPGRP + SIGNAL (SIGTTIN, SIG_DFL); + SIGNAL (SIGTTOU, SIG_DFL); + SIGNAL (SIGTSTP, SIG_DFL); +#endif TIOCGPGRP +} + + +static int +ALRMser (int sig) +{ + longjmp (PEERctx, DONE); +} + + +static int +PIPEser (int sig) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (sig, SIG_IGN); +#endif + + adios (NULL, "lost peer"); +} + + +static int +SIGser (int sig) +{ +#ifndef RELIABLE_SIGNALS + SIGNAL (sig, SIG_IGN); +#endif + + done (1); +} + + +void +done (int status) +{ + if (dfd != NOTOK) + RemoveStatus (dfd); + + pFIN (); + + exit (status); +} + + +static void +adorn (char *what, char *fmt, ...) +{ + va_list ap + char *cp; + + cp = invo_name; + invo_name = NULL; + + va_start(ap, fmt); + advertise (what, NULL, fmt, ap); + va_end(ap); + + invo_name = cp; +} + + +void +advertise (char *what, char *tail, va_list ap) +{ + int eindex = errno; + char buffer[BUFSIZ], err[BUFSIZ]; + struct iovec iob[20]; + register struct iovec *iov = iob; + + fflush (stdout); + fflush (stderr); + + if (invo_name) { + iov->iov_len = strlen (iov->iov_base = invo_name); + iov++; + iov->iov_len = strlen (iov->iov_base = ": "); + iov++; + } + + vsnprintf (buffer, sizeof(buffer), fmt, ap); + iov->iov_len = strlen (iov->iov_base = buffer); + iov++; + if (what) { + if (*what) { + iov->iov_len = strlen (iov->iov_base = " "); + iov++; + iov->iov_len = strlen (iov->iov_base = what); + iov++; + iov->iov_len = strlen (iov->iov_base = ": "); + iov++; + } + if (!(iov->iov_base = strerror (eindex))) { + snprintf (err, sizeof(err), "unknown error %d", eindex); + iov->iov_base = err; + } + iov->iov_len = strlen (iov->iov_base); + iov++; + } + if (tail && *tail) { + iov->iov_len = strlen (iov->iov_base = ", "); + iov++; + iov->iov_len = strlen (iov->iov_base = tail); + iov++; + } + iov->iov_len = strlen (iov->iov_base = "\n"); + iov++; + + if (dfd != NOTOK) + DisplayVector (iob, iov - iob); + else + writev (fileno (stderr), iob, iov - iob); +} + + +static void +DisplayVector (struct iovec *iov, int n) +{ + register int i; + register char *cp; + char buffer[BUFSIZ]; + + for (i = 0, cp = NULL; i < n; i++, iov++) { + snprintf (buffer, sizeof(buffer), "%*.*s", iov->iov_len, + iov->iov_len, iov->iov_base); + cp = add (buffer, cp); + } + + DisplayStatus (dfd, cp); + free (cp); + sleep (PAUSE); + RemoveStatus (dfd); +} diff --git a/zotnet/Makefile.in b/zotnet/Makefile.in new file mode 100644 index 0000000..7391814 --- /dev/null +++ b/zotnet/Makefile.in @@ -0,0 +1,103 @@ +# +# Makefile for zotnet subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +srcdir = @srcdir@ +VPATH = @srcdir@ + +LORDER = @LORDER@ +TSORT = @TSORT@ +RANLIB = @RANLIB@ + +# flags passed to recursive makes in subdirectories +MAKEDEFS = CC='$(CC)' CPPFLAGS='$(CPPFLAGS)' DEFS='$(DEFS)' \ +CFLAGS='$(CFLAGS)' LDFLAGS='$(LDFLAGS)' LIBS='$(LIBS)' \ +prefix='$(prefix)' exec_prefix='$(exec_prefix)' bindir='$(bindir)' \ +etcdir='$(etcdir)' libdir='$(libdir)' mandir='$(mandir)' \ +mailspool='$(mailspool)' sendmailpath='$(sendmailpath)' \ +default_editor='$(default_editor)' default_pager='$(default_pager)' + +# object files in libzot.a +OBJS = mts/mts.o mts/client.o \ + tws/dtime.o tws/dtimep.o tws/lexstring.o \ + mf/mf.o \ + bboards/getbbent.o + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(AUX) + +# subdirectories +SUBDIRS = mts tws mf bboards + +# ========= DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +all: all-recursive libzot.a + +libzot.a: $(OBJS) + rm -f libzot.a + ar cr libzot.a `$(LORDER) $(OBJS) | $(TSORT)` + $(RANLIB) libzot.a + +all-recursive: + for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) all) || exit 1; \ + done + +install uninstall: + for subdir in $(SUBDIRS); do \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) $@) || exit 1; \ + done + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: mostlyclean-recursive mostlyclean-local +clean: clean-recursive clean-local +distclean: distclean-recursive distclean-local +realclean: realclean-recursive realclean-local +superclean: superclean-recursive superclean-local + +mostlyclean-local: + rm -f *~ + +clean-local: mostlyclean-local + rm -f libzot.a + +distclean-local: clean-local + rm -f Makefile + +realclean-local: distclean-local + +superclean-local: realclean-local + +mostlyclean-recursive clean-recursive distclean-recursive realclean-recursive superclean-recursive: + for subdir in $(SUBDIRS); do \ + target=`echo $@ | sed 's/-recursive//'`; \ + (cd $$subdir && $(MAKE) $(MAKEDEFS) $$target) || exit 1; \ + done + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = zotnet + +Makefile: Makefile.in ../config.status + cd .. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../`cat ../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + @for subdir in $(SUBDIRS); do \ + mkdir $(distdir)/$$subdir; \ + chmod 755 $(distdir)/$$subdir; \ + (cd $$subdir && $(MAKE) $@) || exit 1; \ + done + diff --git a/zotnet/bboards/Makefile.in b/zotnet/bboards/Makefile.in new file mode 100644 index 0000000..7dedeea --- /dev/null +++ b/zotnet/bboards/Makefile.in @@ -0,0 +1,76 @@ +# +# Makefile for zotnet/bboards subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# header files +HDRS = bboards.h + +# source files +SRCS = getbbent.c + +# object files +OBJS = getbbent.o + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(HDRS) $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +all: $(OBJS) + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = zotnet/bboards + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/zotnet/bboards/bboards.h b/zotnet/bboards/bboards.h new file mode 100644 index 0000000..227bee5 --- /dev/null +++ b/zotnet/bboards/bboards.h @@ -0,0 +1,88 @@ + +/* + * bboards.h -- definition of a BBoard structure + * + * $Id$ + */ + +#define BBOARDS "bboards" /* name in /etc/passwd */ +#define BBDB "BBoards" /* file in BBOARDS' home directory */ +#define BBMODE 0644 /* default BBoards mode */ +#define DISTADR "dist-" /* prefix for distribution addresses */ + +#ifdef POP +# define POPUID "pop" /* name in /etc/passwd */ +# define POPDB "POP" /* file in POPUID's home directory */ +# define POMODE 0600 /* default POP subscriber maildrop mode */ +#endif /* POP */ + +#define BB_NULL 0x0000 +#define BB_ARCH 0x0007 /* archive policy */ +#define BB_ASAV 0x0001 /* save in archives/ directory */ +#define BB_AREM 0x0002 /* remove without saving */ +#define BB_INVIS 0x0010 /* invisible to bbc */ +#define BB_REMOTE 0x0020 /* remote to bbc */ +#define BB_SEEN 0x0040 /* seen by bbc */ +#define BBITS "\020\01ARCHIVE\02REMOVE\05INVIS\06REMOTE\07SEEN" + +struct bboard { + char *bb_name; /* name of the bboard */ + char **bb_aka; /* aliases for the bboards */ + + char *bb_file; /* file it resides in */ + char *bb_archive; /* file where archives reside */ + char *bb_info; /* file where maxima resides */ + char *bb_map; /* file where binary map resides */ + + char *bb_passwd; /* password for it */ + char **bb_leader; /* list of local leaders */ + + char *bb_addr; /* network address */ + char *bb_request; /* network address for requests */ + char *bb_relay; /* host acting as relay in local domain */ + char **bb_dist; /* distribution list */ + + unsigned int bb_flags; /* various flags */ + + union { /* unassigned */ + unsigned int un_count; + long un_mtime; + } bb_un; + + unsigned int bb_maxima; /* highest BBoard-Id in it */ + char *bb_date; /* date that maxima was written */ + + struct bboard *bb_next; /* unassigned */ + struct bboard *bb_link; /* unassigned */ + struct bboard *bb_chain; /* unassigned */ +}; + +#define bb_count bb_un.un_count +#define bb_mtime bb_un.un_mtime + +/* flags for setbbent() */ +#define SB_NULL 0x0000 +#define SB_STAY 0x0001 /* stay open between calls */ +#define SB_FAST 0x0002 /* fast parse of file */ + + +/* + * prototypes + */ +int setbbfile (char *, int); +int setbbinfo (char *, char *, int); +int setpwinfo (struct passwd *, char *, int); +int setbbent (int); +int endbbent (void); +long getbbtime (void); +struct bboard *getbbent (void); +struct bboard *getbbnam (char *); +struct bboard *getbbaka (char *); +static void BBread (void); +int ldrbb (struct bboard *); +int ldrchk (struct bboard *); +struct bboard *getbbcpy (struct bboard *); +int getbbdist (struct bboard *, int (*)()); +char *getbberr (void); +void make_lower (char *, char *); + diff --git a/zotnet/bboards/getbbent.c b/zotnet/bboards/getbbent.c new file mode 100644 index 0000000..b9904be --- /dev/null +++ b/zotnet/bboards/getbbent.c @@ -0,0 +1,733 @@ + +/* + * getbbent.c -- subroutines for accessing the BBoards file + * + * $Id$ + */ + +#include + +#ifdef MMDFONLY +# include +# include +# include +#endif /* MMDFONLY */ + +#include +#include +#include + +#ifdef HAVE_CRYPT_H +# include +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifndef MMDFONLY +# define NOTOK (-1) +# define OK 0 +#endif + +#define MaxBBAka 100 +#define MaxBBLdr 100 +#define MaxBBDist 100 + +#define NCOLON 9 /* currently 10 fields per entry */ + +#define COLON ':' +#define COMMA ',' +#define NEWLINE '\n' + +#define ARCHIVE "archive" +#define CNTFILE ".cnt" +#define DSTFILE ".dist" +#define MAPFILE ".map" + +static int BBuid = -1; + +static unsigned int BBflags = SB_NULL; + +static char BBName[BUFSIZ] = BBOARDS; +static char BBDir[BUFSIZ] = ""; +static char BBData[BUFSIZ] = ""; + +static FILE *BBfile = NULL; + +static struct bboard BB; +static struct bboard *bb = &BB; + +static int BBload = 1; + +static char BBFile[BUFSIZ]; +static char BBArchive[BUFSIZ]; +static char BBInfo[BUFSIZ]; +static char BBMap[BUFSIZ]; +static char *BBAkas[MaxBBAka]; +static char *BBLeaders[MaxBBLdr]; +static char *BBDists[MaxBBDist]; +static char BBAddr[BUFSIZ]; +static char BBRequest[BUFSIZ]; +static char BBDate[BUFSIZ]; +static char BBErrors[BUFSIZ]; + +#ifdef MMDFONLY +extern LLog *logptr; +#endif + +#ifdef UCL +int called_bbc = 0; +char *bbs[101]; +#endif + + +/* + * static prototypes + */ +static int setbbaux (char *, char *); +static int setpwaux (struct passwd *, char *); +static void BBread (void); +static int getbbitem (struct bboard *, char *, int (*)()); +static int bblose (char *, ...); +static char *bbskip (char *, char); +static char *getcpy (char *); + + +int +setbbfile (char *file, int f) +{ + if (BBuid == -1) + return setbbinfo (BBOARDS, file, f); + + strncpy (BBData, file, sizeof(BBData)); + + BBflags = SB_NULL; + endbbent (); + + return setbbent (f); +} + + +int +setbbinfo (char *user, char *file, int f) +{ + register struct passwd *pw; + + if ((pw = getpwnam (user)) == NULL) { + snprintf (BBErrors, sizeof(BBErrors), "unknown user: %s", user); + return 0; + } + + return setpwinfo (pw, file, f); +} + + +int +setpwinfo (struct passwd *pw, char *file, int f) +{ + if (!setpwaux (pw, file)) + return 0; + + BBflags = SB_NULL; + endbbent (); + + return setbbent (f); +} + + +static int +setbbaux (char *name, char *file) +{ + register struct passwd *pw; + + if ((pw = getpwnam (name)) == NULL) { + snprintf (BBErrors, sizeof(BBErrors), "unknown user: %s", name); + return 0; + } + + return setpwaux (pw, file); +} + + +static int +setpwaux (struct passwd *pw, char *file) +{ + strncpy (BBName, pw->pw_name, sizeof(BBName)); + BBuid = pw->pw_uid; + strncpy (BBDir, pw->pw_dir, sizeof(BBDir)); + snprintf (BBData, sizeof(BBData), "%s/%s", + *file != '/' ? BBDir : "", + *file != '/' ? file : file + 1); + + BBflags = SB_NULL; + + return 1; +} + + +int +setbbent (int f) +{ + if (BBfile == NULL) { + if (BBuid == -1 && !setbbaux (BBOARDS, BBDB)) + return 0; + + if ((BBfile = fopen (BBData, "r")) == NULL) { + snprintf (BBErrors, sizeof(BBErrors), "unable to open: %s", BBData); + return 0; + } + } + else + rewind (BBfile); + + BBflags |= f; + return (BBfile != NULL); +} + + +int +endbbent (void) +{ + if (BBfile != NULL && !(BBflags & SB_STAY)) { + fclose (BBfile); + BBfile = NULL; + } + + return 1; +} + + +long +getbbtime (void) +{ + struct stat st; + + if (BBfile == NULL) { + if (BBuid == -1 && !setbbaux (BBOARDS, BBDB)) + return 0; + + if (stat (BBData, &st) == NOTOK) { + snprintf (BBErrors, sizeof(BBErrors), "unable to stat: %s", BBData); + return 0; + } + } else { + if (fstat (fileno (BBfile), &st) == NOTOK) { + snprintf (BBErrors, sizeof(BBErrors), "unable to fstat: %s", BBData); + return 0; + } + } + + return ((long) st.st_mtime); +} + + +struct bboard * +getbbent (void) +{ + register int count; + register char *p, *q, *r, *d, *f, **s; + static char line[BUFSIZ]; + + if (BBfile == NULL && !setbbent (SB_NULL)) + return NULL; + +retry: ; + if ((p = fgets (line, sizeof line, BBfile)) == NULL) + return NULL; + + for (q = p, count = 0; *q != 0 && *q != NEWLINE; q++) + if (*q == COLON) + count++; + + if (count != NCOLON) { +#ifdef MMDFONLY + if (q = strchr(p, NEWLINE)) + *q = 0; + ll_log (logptr, LLOGTMP, "bad entry in %s: %s", BBData, p); +#endif /* MMDFONLY */ + goto retry; + } + + bb->bb_name = p; + p = q = bbskip (p, COLON); + p = bb->bb_file = bbskip (p, COLON); + bb->bb_archive = bb->bb_info = bb->bb_map = ""; + p = bb->bb_passwd = bbskip (p, COLON); + p = r = bbskip (p, COLON); + p = bb->bb_addr = bbskip (p, COLON); + p = bb->bb_request = bbskip (p, COLON); + p = bb->bb_relay = bbskip (p, COLON); + p = d = bbskip (p, COLON); + p = f = bbskip (p, COLON); + bbskip (p, NEWLINE); + + s = bb->bb_aka = BBAkas; + while (*q) { + *s++ = q; + q = bbskip (q, COMMA); + } + *s = 0; + + s = bb->bb_leader = BBLeaders; + if (*r == 0) { + if (!(BBflags & SB_FAST)) { + *s++ = BBName; + *s = 0; + } + } + else { + while (*r) { + *s++ = r; + r = bbskip (r, COMMA); + } + *s = 0; + } + + s = bb->bb_dist = BBDists; + while (*d) { + *s++ = d; + d = bbskip (d, COMMA); + } + *s = 0; + + if (*f) + sscanf (f, "%o", &bb->bb_flags); + else + bb->bb_flags = BB_NULL; + bb->bb_count = bb->bb_maxima = 0; + bb->bb_date = NULL; + bb->bb_next = bb->bb_link = bb->bb_chain = NULL; + +#ifdef UCL + /* + * Only do a BBread on bboards that the user has expressed an + * interest in, if we were called by bbc. + */ + if (BBload) { + register char **ap, *cp; + register int bbp; + + if (called_bbc == 0) + BBread(); + else { + for (bbp = 0; cp = bbs[bbp]; bbp++) { + if (!strcmp(bb->bb_name, cp)) { + BBread(); + break; + } + for (ap = bb->bb_aka; *ap; ap++) + if (!strcmp(*ap, cp)) { + BBread(); + break; + } + } + } + } +#else + if (BBload) + BBread (); +#endif + + return bb; +} + + +struct bboard * +getbbnam (char *name) +{ + register struct bboard *b = NULL; + + if (!setbbent (SB_NULL)) + return NULL; + BBload = 0; + while ((b = getbbent ()) && strcmp (name, b->bb_name)) + continue; + BBload = 1; + endbbent (); + + if (b != NULL) + BBread (); + + return b; +} + + +struct bboard * +getbbaka (char *aka) +{ + register char **ap; + register struct bboard *b = NULL; + + if (!setbbent (SB_NULL)) + return NULL; + BBload = 0; + while ((b = getbbent ()) != NULL) + for (ap = b->bb_aka; *ap; ap++) + if (strcmp (aka, *ap) == 0) + goto hit; +hit: ; + BBload = 1; + endbbent (); + + if (b != NULL) + BBread (); + + return b; +} + + +static void +BBread (void) +{ + register int i; + register char *cp, *dp, *p, *r; + char prf[BUFSIZ]; + static char line[BUFSIZ]; + register FILE * info; + + if (BBflags & SB_FAST) + return; + + p = strchr(bb->bb_request, '@'); + r = strchr(bb->bb_addr, '@'); + BBRequest[0] = 0; + + if (*bb->bb_request == '-') + if (p == NULL && r && *r == '@') + snprintf (BBRequest, sizeof(BBRequest), "%s%s%s", bb->bb_name, bb->bb_request, r); + else + snprintf (BBRequest, sizeof(BBRequest), "%s%s", bb->bb_name, bb->bb_request); + else + if (p == NULL && r && *r == '@' && *bb->bb_request) + snprintf (BBRequest, sizeof(BBRequest), "%s%s", bb->bb_request, r); + + if (BBRequest[0]) + bb->bb_request = BBRequest; + else + if (*bb->bb_request == 0) + bb->bb_request = *bb->bb_addr ? bb->bb_addr + : bb->bb_leader[0]; + + if (*bb->bb_addr == '@') { + snprintf (BBAddr, sizeof(BBAddr), "%s%s", bb->bb_name, bb->bb_addr); + bb->bb_addr = BBAddr; + } + else + if (*bb->bb_addr == 0) + bb->bb_addr = bb->bb_name; + + if (*bb->bb_file == 0) + return; + if (*bb->bb_file != '/') { + snprintf (BBFile, sizeof(BBFile), "%s/%s", BBDir, bb->bb_file); + bb->bb_file = BBFile; + } + + if ((cp = strrchr(bb->bb_file, '/')) == NULL || *++cp == 0) { + strcpy (prf, ""); + cp = bb->bb_file; + } else { + snprintf (prf, sizeof(prf), "%.*s", cp - bb->bb_file, bb->bb_file); + } + if ((dp = strchr(cp, '.')) == NULL) + dp = cp + strlen (cp); + + snprintf (BBArchive, sizeof(BBArchive), "%s%s/%s", prf, ARCHIVE, cp); + bb->bb_archive = BBArchive; + snprintf (BBInfo, sizeof(BBInfo), "%s.%.*s%s", prf, dp - cp, cp, CNTFILE); + bb->bb_info = BBInfo; + snprintf (BBMap, sizeof(BBMap), "%s.%.*s%s", prf, dp - cp, cp, MAPFILE); + bb->bb_map = BBMap; + + if ((info = fopen (bb->bb_info, "r")) == NULL) + return; + + if (fgets (line, sizeof line, info) && (i = atoi (line)) > 0) + bb->bb_maxima = (unsigned) i; + if (!feof (info) && fgets (line, sizeof line, info)) { + strncpy (BBDate, line, sizeof(BBData)); + if ((cp = strchr(BBDate, NEWLINE))) + *cp = 0; + bb->bb_date = BBDate; + } + + fclose (info); +} + + +int +ldrbb (struct bboard *b) +{ + register char *p, **q, **r; + static uid_t uid = 0; + static gid_t gid = 0; + static char username[10] = ""; + register struct passwd *pw; + register struct group *gr; + + if (b == NULL) + return 0; + if (BBuid == -1 && !setbbaux (BBOARDS, BBDB)) + return 0; + + if (username[0] == 0) { + if ((pw = getpwuid (uid = getuid ())) == NULL) + return 0; + gid = getgid (); + strncpy (username, pw->pw_name, sizeof(username)); + } + + if (uid == BBuid) + return 1; + + q = b->bb_leader; + while ((p = *q++)) + if (*p == '=') { + if ((gr = getgrnam (++p)) == NULL) + continue; + if (gid == gr->gr_gid) + return 1; + r = gr->gr_mem; + while ((p = *r++)) + if (strcmp (username, p) == 0) + return 1; + } + else + if (strcmp (username, p) == 0) + return 1; + + return 0; +} + + +int +ldrchk (struct bboard *b) +{ + if (b == NULL) + return 0; + + if (*b->bb_passwd == 0) + return 1; + + if (strcmp (b->bb_passwd, + crypt (getpass ("Password: "), b->bb_passwd)) == 0) + return 1; + + fprintf (stderr, "Sorry\n"); + return 0; +} + + +struct bboard * +getbbcpy (struct bboard *bp) +{ + register char **p, **q; + register struct bboard *b; + + if (bp == NULL) + return NULL; + + b = (struct bboard *) malloc ((unsigned) sizeof *b); + if (b == NULL) + return NULL; + + b->bb_name = getcpy (bp->bb_name); + b->bb_file = getcpy (bp->bb_file); + b->bb_archive = getcpy (bp->bb_archive); + b->bb_info = getcpy (bp->bb_info); + b->bb_map = getcpy (bp->bb_map); + b->bb_passwd = getcpy (bp->bb_passwd); + b->bb_flags = bp->bb_flags; + b->bb_count = bp->bb_count; + b->bb_maxima = bp->bb_maxima; + b->bb_date = getcpy (bp->bb_date); + b->bb_addr = getcpy (bp->bb_addr); + b->bb_request = getcpy (bp->bb_request); + b->bb_relay = getcpy (bp->bb_relay); + + for (p = bp->bb_aka; *p; p++) + continue; + b->bb_aka = + q = (char **) calloc ((unsigned) (p - bp->bb_aka + 1), sizeof *q); + if (q == NULL) + return NULL; + for (p = bp->bb_aka; *p; *q++ = getcpy (*p++)) + continue; + *q = NULL; + + for (p = bp->bb_leader; *p; p++) + continue; + b->bb_leader = + q = (char **) calloc ((unsigned) (p - bp->bb_leader + 1), sizeof *q); + if (q == NULL) + return NULL; + for (p = bp->bb_leader; *p; *q++ = getcpy (*p++)) + continue; + *q = NULL; + + for (p = bp->bb_dist; *p; p++) + continue; + b->bb_dist = + q = (char **) calloc ((unsigned) (p - bp->bb_dist + 1), sizeof *q); + if (q == NULL) + return NULL; + for (p = bp->bb_dist; *p; *q++ = getcpy (*p++)) + continue; + *q = NULL; + + b->bb_next = bp->bb_next; + b->bb_link = bp->bb_link; + b->bb_chain = bp->bb_chain; + + return b; +} + + +int +getbbdist (struct bboard *bb, int (*action)()) +{ + register int result; + register char **dp; + + BBErrors[0] = 0; + for (dp = bb->bb_dist; *dp; dp++) + if ((result = getbbitem (bb, *dp, action))) + return result; + + return result; +} + +char * +getbberr (void) +{ + return (BBErrors[0] ? BBErrors : NULL); +} + + +static int +getbbitem (struct bboard *bb, char *item, int (*action)()) +{ + register int result; + register char *cp, *dp, *hp, *np; + char mbox[BUFSIZ], + buffer[BUFSIZ], + file[BUFSIZ], + host[BUFSIZ], + prf[BUFSIZ]; + register FILE *fp; + + switch (*item) { + case '*': + switch (*++item) { + case '/': + hp = item; + break; + + case 0: + if ((cp = strrchr(bb->bb_file, '/')) == NULL || *++cp == 0) { + strcpy (prf, ""); + cp = bb->bb_file; + } else { + snprintf (prf, sizeof(prf), "%.*s", cp - bb->bb_file, bb->bb_file); + } + if ((dp = strchr(cp, '.')) == NULL) + dp = cp + strlen (cp); + snprintf (file, sizeof(file), "%s.%.*s%s", prf, dp - cp, cp, DSTFILE); + hp = file; + break; + + default: + snprintf (file, sizeof(file), "%s/%s", BBDir, item); + hp = file; + break; + } + + if ((fp = fopen (hp, "r")) == NULL) + return bblose ("unable to read file %s", hp); + while (fgets (buffer, sizeof buffer, fp)) { + if ((np = strchr(buffer, '\n'))) + *np = 0; + if ((result = getbbitem (bb, buffer, action))) { + fclose (fp); + bblose ("error with file %s, item %s", hp, buffer); + return result; + } + } + fclose (fp); + return OK; + + default: + if ((hp = strrchr(item, '@'))) { + *hp++ = 0; + strncpy (mbox, item, sizeof(mbox)); + strncpy (host, hp, sizeof(host)); + *--hp = '@'; + } + else { + snprintf (mbox, sizeof(mbox), "%s%s", DISTADR, bb->bb_name); + strncpy (host, item, sizeof(host)); + } + if ((result = (*action) (mbox, host))) + bblose ("action (%s, %s) returned 0%o", mbox, host, result); + return result; + } +} + + +static int +bblose (char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (BBErrors[0] == 0) + vsnprintf (BBErrors, sizeof(BBErrors), fmt, ap); + + va_end(ap); + return NOTOK; +} + + +void +make_lower (char *s1, char *s2) +{ + if (!s1 || !s2) + return; + + for (; *s2; s2++) + *s1++ = isupper (*s2) ? tolower (*s2) : *s2; + *s1 = 0; +} + + +static char * +bbskip (char *p, char c) +{ + if (p == NULL) + return NULL; + + while (*p && *p != c) + p++; + if (*p) + *p++ = 0; + + return p; +} + + +static char * +getcpy (char *s) +{ + register char *p; + size_t len; + + if (s == NULL) + return NULL; + + len = strlen (s) + 1; + if ((p = malloc (len))) + memcpy (p, s, len); + return p; +} + diff --git a/zotnet/mf/Makefile.in b/zotnet/mf/Makefile.in new file mode 100644 index 0000000..7871ca7 --- /dev/null +++ b/zotnet/mf/Makefile.in @@ -0,0 +1,76 @@ +# +# Makefile for zotnet/mf subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# header files +HDRS = mf.h + +# source files +SRCS = mf.c + +# object files +OBJS = mf.o + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(HDRS) $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +all: $(OBJS) + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = zotnet/mf + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/zotnet/mf/mf.c b/zotnet/mf/mf.c new file mode 100644 index 0000000..b129785 --- /dev/null +++ b/zotnet/mf/mf.c @@ -0,0 +1,963 @@ + +/* + * mf.c -- mail filter subroutines + * + * $Id$ + */ + +#include +#include +#include + +/* + * static prototypes + */ +static char *getcpy (char *); +static char *add (char *, char *); +static void compress (char *, char *); +static int isat (char *); +static int parse_address (void); +static int phrase (char *); +static int route_addr (char *); +static int local_part (char *); +static int domain (char *); +static int route (char *); +static int my_lex (char *); + + +static char * +getcpy (char *s) +{ + register char *p; + + if (!s) { + _cleanup(); + abort(); + for(;;) + pause(); + } + if ((p = malloc ((size_t) (strlen (s) + 2)))) + strcpy (p, s); + return p; +} + + +static char * +add (char *s1, char *s2) +{ + register char *p; + + if (!s2) + return getcpy (s1); + + if ((p = malloc ((size_t) (strlen (s1) + strlen (s2) + 2)))) + sprintf (p, "%s%s", s2, s1); + free (s2); + return p; +} + +int +isfrom(char *string) +{ + return (strncmp (string, "From ", 5) == 0 + || strncmp (string, ">From ", 6) == 0); +} + + +int +lequal (char *a, char *b) +{ + for (; *a; a++, b++) + if (*b == 0) + return FALSE; + else { + char c1 = islower (*a) ? toupper (*a) : *a; + char c2 = islower (*b) ? toupper (*b) : *b; + if (c1 != c2) + return FALSE; + } + + return (*b == 0); +} + + +/* + * seekadrx() is tricky. We want to cover both UUCP-style and ARPA-style + * addresses, so for each list of addresses we see if we can find some + * character to give us a hint. + */ + + +#define CHKADR 0 /* undertermined address style */ +#define UNIXDR 1 /* UNIX-style address */ +#define ARPADR 2 /* ARPAnet-style address */ + + +static char *punctuators = ";<>.()[]"; +static char *vp = NULL; +static char *tp = NULL; + +static struct adrx adrxs1; + + +struct adrx * +seekadrx (char *addrs) +{ + static int state = CHKADR; + register char *cp; + register struct adrx *adrxp; + + if (state == CHKADR) + for (state = UNIXDR, cp = addrs; *cp; cp++) + if (strchr(punctuators, *cp)) { + state = ARPADR; + break; + } + + switch (state) { + case UNIXDR: + adrxp = uucpadrx (addrs); + break; + + case ARPADR: + default: + adrxp = getadrx (addrs); + break; + } + + if (adrxp == NULL) + state = CHKADR; + + return adrxp; +} + + +/* + * uucpadrx() implements a partial UUCP-style address parser. It's based + * on the UUCP notion that addresses are separated by spaces or commas. + */ + + +struct adrx * +uucpadrx (char *addrs) +{ + register char *cp, *wp, *xp, *yp, *zp; + register struct adrx *adrxp = &adrxs1; + + if (vp == NULL) { + vp = tp = getcpy (addrs); + compress (addrs, vp); + } + else + if (tp == NULL) { + free (vp); + vp = NULL; + return NULL; + } + + for (cp = tp; isspace (*cp); cp++) + continue; + if (*cp == 0) { + free (vp); + vp = tp = NULL; + return NULL; + } + + if ((wp = strchr(cp, ',')) == NULL) + if ((wp = strchr(cp, ' ')) != NULL) { + xp = wp; + while (isspace (*xp)) + xp++; + if (*xp != 0 && isat (--xp)) { + yp = xp + 4; + while (isspace (*yp)) + yp++; + if (*yp != 0) + if ((zp = strchr(yp, ' ')) != NULL) + *zp = 0, tp = ++zp; + else + tp = NULL; + else + *wp = 0, tp = ++wp; + } + else + *wp = 0, tp = ++wp; + } + else + tp = NULL; + else + *wp = 0, tp = ++wp; + + if (adrxp->text) + free (adrxp->text); + adrxp->text = getcpy (cp); + adrxp->mbox = cp; + adrxp->host = adrxp->path = NULL; + if ((wp = strrchr(cp, '@')) != NULL) { + *wp++ = 0; + adrxp->host = *wp ? wp : NULL; + } + else + for (wp = cp + strlen (cp) - 4; wp >= cp; wp--) + if (isat (wp)) { + *wp++ = 0; + adrxp->host = wp + 3; + } + + adrxp->pers = adrxp->grp = adrxp->note = adrxp->err = NULL; + adrxp->ingrp = 0; + + return adrxp; +} + + +static void +compress (char *fp, char *tp) +{ + register char c, *cp; + + for (c = ' ', cp = tp; (*tp = *fp++) != 0;) + if (isspace (*tp)) { + if (c != ' ') + *tp++ = c = ' '; + } + else + c = *tp++; + + if (c == ' ' && cp < tp) + *--tp = 0; +} + + +static int +isat (char *p) +{ + return (strncmp (p, " AT ", 4) + && strncmp (p, " At ", 4) + && strncmp (p, " aT ", 4) + && strncmp (p, " at ", 4) ? FALSE : TRUE); +} + + +/* + * + * getadrx() implements a partial 822-style address parser. The parser + * is neither complete nor correct. It does however recognize nearly all + * of the 822 address syntax. In addition it handles the majority of the + * 733 syntax as well. Most problems arise from trying to accomodate both. + * + * In terms of 822, the route-specification in + * + * "<" [route] local-part "@" domain ">" + * + * is parsed and returned unchanged. Multiple at-signs are compressed + * via source-routing. Recursive groups are not allowed as per the + * standard. + * + * In terms of 733, " at " is recognized as equivalent to "@". + * + * In terms of both the parser will not complain about missing hosts. + * + * ----- + * + * We should not allow addresses like + * + * Marshall T. Rose + * + * but should insist on + * + * "Marshall T. Rose" + * + * Unfortunately, a lot of mailers stupidly let people get away with this. + * + * ----- + * + * We should not allow addresses like + * + * + * + * but should insist on + * + * MRose@UCI + * + * Unfortunately, a lot of mailers stupidly let people's UAs get away with + * this. + * + * ----- + * + * We should not allow addresses like + * + * @UCI:MRose@UCI-750a + * + * but should insist on + * + * Marshall Rose <@UCI:MRose@UCI-750a> + * + * Unfortunately, a lot of mailers stupidly do this. + * + */ + +#define QUOTE '\\' + +#define LX_END 0 +#define LX_ERR 1 +#define LX_ATOM 2 +#define LX_QSTR 3 +#define LX_DLIT 4 +#define LX_SEMI 5 +#define LX_COMA 6 +#define LX_LBRK 7 +#define LX_RBRK 8 +#define LX_COLN 9 +#define LX_DOT 10 +#define LX_AT 11 + +struct specials { + char lx_chr; + int lx_val; +}; + +static struct specials special[] = { + { ';', LX_SEMI }, + { ',', LX_COMA }, + { '<', LX_LBRK }, + { '>', LX_RBRK }, + { ':', LX_COLN }, + { '.', LX_DOT }, + { '@', LX_AT }, + { '(', LX_ERR }, + { ')', LX_ERR }, + { QUOTE, LX_ERR }, + { '"', LX_ERR }, + { '[', LX_ERR }, + { ']', LX_ERR }, + { 0, 0 } +}; + +static int glevel = 0; +static int ingrp = 0; +static int last_lex = LX_END; + +static char *dp = NULL; +static char *cp = NULL; +static char *ap = NULL; +static char *pers = NULL; +static char *mbox = NULL; +static char *host = NULL; +static char *path = NULL; +static char *grp = NULL; +static char *note = NULL; +static char err[BUFSIZ]; +static char adr[BUFSIZ]; + +static struct adrx adrxs2; + + +struct adrx * +getadrx (char *addrs) +{ + register char *bp; + register struct adrx *adrxp = &adrxs2; + + if (pers) + free (pers); + if (mbox) + free (mbox); + if (host) + free (host); + if (path) + free (path); + if (grp) + free (grp); + if (note) + free (note); + pers = mbox = host = path = grp = note = NULL; + err[0] = 0; + + if (dp == NULL) { + dp = cp = getcpy (addrs ? addrs : ""); + glevel = 0; + } + else + if (cp == NULL) { + free (dp); + dp = NULL; + return NULL; + } + + switch (parse_address ()) { + case DONE: + free (dp); + dp = cp = NULL; + return NULL; + + case OK: + switch (last_lex) { + case LX_COMA: + case LX_END: + break; + + default: /* catch trailing comments */ + bp = cp; + my_lex (adr); + cp = bp; + break; + } + break; + + default: + break; + } + + if (err[0]) + for (;;) { + switch (last_lex) { + case LX_COMA: + case LX_END: + break; + + default: + my_lex (adr); + continue; + } + break; + } + while (isspace (*ap)) + ap++; + if (cp) + sprintf (adr, "%.*s", cp - ap, ap); + else + strcpy (adr, ap); + bp = adr + strlen (adr) - 1; + if (*bp == ',' || *bp == ';' || *bp == '\n') + *bp = 0; + + adrxp->text = adr; + adrxp->pers = pers; + adrxp->mbox = mbox; + adrxp->host = host; + adrxp->path = path; + adrxp->grp = grp; + adrxp->ingrp = ingrp; + adrxp->note = note; + adrxp->err = err[0] ? err : NULL; + + return adrxp; +} + + +static int +parse_address (void) +{ + char buffer[BUFSIZ]; + +again: ; + ap = cp; + switch (my_lex (buffer)) { + case LX_ATOM: + case LX_QSTR: + pers = getcpy (buffer); + break; + + case LX_SEMI: + if (glevel-- <= 0) { + strcpy (err, "extraneous semi-colon"); + return NOTOK; + } + case LX_COMA: + if (note) { + free (note); + note = NULL; + } + goto again; + + case LX_END: + return DONE; + + case LX_LBRK: /* sigh (2) */ + goto get_addr; + + case LX_AT: /* sigh (3) */ + cp = ap; + if (route_addr (buffer) == NOTOK) + return NOTOK; + return OK; /* why be choosy? */ + + default: + sprintf (err, "illegal address construct (%s)", buffer); + return NOTOK; + } + + switch (my_lex (buffer)) { + case LX_ATOM: + case LX_QSTR: + pers = add (buffer, add (" ", pers)); + more_phrase: ; /* sigh (1) */ + if (phrase (buffer) == NOTOK) + return NOTOK; + + switch (last_lex) { + case LX_LBRK: + get_addr: ; + if (route_addr (buffer) == NOTOK) + return NOTOK; + if (last_lex == LX_RBRK) + return OK; + sprintf (err, "missing right-bracket (%s)", buffer); + return NOTOK; + + case LX_COLN: + get_group: ; + if (glevel++ > 0) { + sprintf (err, "nested groups not allowed (%s)", pers); + return NOTOK; + } + grp = add (": ", pers); + pers = NULL; + { + char *pp = cp; + + for (;;) + switch (my_lex (buffer)) { + case LX_SEMI: + case LX_END: /* tsk, tsk */ + glevel--; + return OK; + + case LX_COMA: + continue; + + default: + cp = pp; + return parse_address (); + } + } + + case LX_DOT: /* sigh (1) */ + pers = add (".", pers); + goto more_phrase; + + default: + sprintf (err, "no mailbox in address, only a phrase (%s%s)", + pers, buffer); + return NOTOK; + } + + case LX_LBRK: + goto get_addr; + + case LX_COLN: + goto get_group; + + case LX_DOT: + mbox = add (buffer, pers); + pers = NULL; + if (route_addr (buffer) == NOTOK) + return NOTOK; + goto check_end; + + case LX_AT: + ingrp = glevel; + mbox = pers; + pers = NULL; + if (domain (buffer) == NOTOK) + return NOTOK; + check_end: ; + switch (last_lex) { + case LX_SEMI: + if (glevel-- <= 0) { + strcpy (err, "extraneous semi-colon"); + return NOTOK; + } + case LX_COMA: + case LX_END: + return OK; + + default: + sprintf (err, "junk after local@domain (%s)", buffer); + return NOTOK; + } + + case LX_SEMI: /* no host */ + case LX_COMA: + case LX_END: + ingrp = glevel; + if (last_lex == LX_SEMI && glevel-- <= 0) { + strcpy (err, "extraneous semi-colon"); + return NOTOK; + } + mbox = pers; + pers = NULL; + return OK; + + default: + sprintf (err, "missing mailbox (%s)", buffer); + return NOTOK; + } +} + + +static int +phrase (char *buffer) +{ + for (;;) + switch (my_lex (buffer)) { + case LX_ATOM: + case LX_QSTR: + pers = add (buffer, add (" ", pers)); + continue; + + default: + return OK; + } +} + + +static int +route_addr (char *buffer) +{ + register char *pp = cp; + + if (my_lex (buffer) == LX_AT) { + if (route (buffer) == NOTOK) + return NOTOK; + } + else + cp = pp; + + if (local_part (buffer) == NOTOK) + return NOTOK; + + switch (last_lex) { + case LX_AT: + return domain (buffer); + + case LX_SEMI: /* if in group */ + case LX_RBRK: /* no host */ + case LX_COMA: + case LX_END: + return OK; + + default: + sprintf (err, "no at-sign after local-part (%s)", buffer); + return NOTOK; + } +} + + +static int +local_part (char *buffer) +{ + ingrp = glevel; + + for (;;) { + switch (my_lex (buffer)) { + case LX_ATOM: + case LX_QSTR: + mbox = add (buffer, mbox); + break; + + default: + sprintf (err, "no mailbox in local-part (%s)", buffer); + return NOTOK; + } + + switch (my_lex (buffer)) { + case LX_DOT: + mbox = add (buffer, mbox); + continue; + + default: + return OK; + } + } +} + + +static int +domain (char *buffer) +{ + for (;;) { + switch (my_lex (buffer)) { + case LX_ATOM: + case LX_DLIT: + host = add (buffer, host); + break; + + default: + sprintf (err, "no sub-domain in domain-part of address (%s)", buffer); + return NOTOK; + } + + switch (my_lex (buffer)) { + case LX_DOT: + host = add (buffer, host); + continue; + + case LX_AT: /* sigh (0) */ + mbox = add (host, add ("%", mbox)); + free (host); + host = NULL; + continue; + + default: + return OK; + } + } +} + + +static int +route (char *buffer) +{ + path = getcpy ("@"); + + for (;;) { + switch (my_lex (buffer)) { + case LX_ATOM: + case LX_DLIT: + path = add (buffer, path); + break; + + default: + sprintf (err, "no sub-domain in domain-part of address (%s)", buffer); + return NOTOK; + } + switch (my_lex (buffer)) { + case LX_COMA: + path = add (buffer, path); + for (;;) { + switch (my_lex (buffer)) { + case LX_COMA: + continue; + + case LX_AT: + path = add (buffer, path); + break; + + default: + sprintf (err, "no at-sign found for next domain in route (%s)", + buffer); + } + break; + } + continue; + + case LX_AT: /* XXX */ + case LX_DOT: + path = add (buffer, path); + continue; + + case LX_COLN: + path = add (buffer, path); + return OK; + + default: + sprintf (err, "no colon found to terminate route (%s)", buffer); + return NOTOK; + } + } +} + + +static int +my_lex (char *buffer) +{ + int i, gotat = 0; + register char c, *bp; + + bp = buffer; + *bp = 0; + if (!cp) + return (last_lex = LX_END); + + gotat = isat (cp); + c = *cp++; + while (isspace (c)) + c = *cp++; + if (c == 0) { + cp = NULL; + return (last_lex = LX_END); + } + + if (c == '(') + for (*bp++ = c, i = 0;;) + switch (c = *cp++) { + case 0: + cp = NULL; + return (last_lex = LX_ERR); + case QUOTE: + *bp++ = c; + if ((c = *cp++) == 0) { + cp = NULL; + return (last_lex = LX_ERR); + } + *bp++ = c; + continue; + case '(': + i++; + default: + *bp++ = c; + continue; + case ')': + *bp++ = c; + if (--i < 0) { + *bp = 0; + note = note ? add (buffer, add (" ", note)) + : getcpy (buffer); + return my_lex (buffer); + } + } + + if (c == '"') + for (*bp++ = c;;) + switch (c = *cp++) { + case 0: + cp = NULL; + return (last_lex = LX_ERR); + case QUOTE: + *bp++ = c; + if ((c = *cp++) == 0) { + cp = NULL; + return (last_lex = LX_ERR); + } + default: + *bp++ = c; + continue; + case '"': + *bp++ = c; + *bp = 0; + return (last_lex = LX_QSTR); + } + + if (c == '[') + for (*bp++ = c;;) + switch (c = *cp++) { + case 0: + cp = NULL; + return (last_lex = LX_ERR); + case QUOTE: + *bp++ = c; + if ((c = *cp++) == 0) { + cp = NULL; + return (last_lex = LX_ERR); + } + default: + *bp++ = c; + continue; + case ']': + *bp++ = c; + *bp = 0; + return (last_lex = LX_DLIT); + } + + *bp++ = c; + *bp = 0; + for (i = 0; special[i].lx_chr != 0; i++) + if (c == special[i].lx_chr) + return (last_lex = special[i].lx_val); + + if (iscntrl (c)) + return (last_lex = LX_ERR); + + for (;;) { + if ((c = *cp++) == 0) + break; + for (i = 0; special[i].lx_chr != 0; i++) + if (c == special[i].lx_chr) + goto got_atom; + if (iscntrl (c) || isspace (c)) + break; + *bp++ = c; + } +got_atom: ; + if (c == 0) + cp = NULL; + else + cp--; + *bp = 0; + last_lex = !gotat || cp == NULL || strchr(cp, '<') != NULL + ? LX_ATOM : LX_AT; + return last_lex; +} + + +char * +legal_person (char *p) +{ + int i; + register char *cp; + static char buffer[BUFSIZ]; + + if (*p == '"') + return p; + for (cp = p; *cp; cp++) + for (i = 0; special[i].lx_chr; i++) + if (*cp == special[i].lx_chr) { + sprintf (buffer, "\"%s\"", p); + return buffer; + } + + return p; +} + + +int +mfgets (FILE *in, char **bp) +{ + int i; + register char *cp, *dp, *ep; + static int len = 0; + static char *pp = NULL; + + if (pp == NULL) + if (!(pp = malloc ((size_t) (len = BUFSIZ)))) + return NOTOK; + + for (ep = (cp = pp) + len - 2;;) { + switch (i = getc (in)) { + case EOF: + eol: ; + if (cp != pp) { + *cp = 0; + *bp = pp; + return OK; + } + eoh: ; + *bp = NULL; + free (pp); + pp = NULL; + return DONE; + + case 0: + continue; + + case '\n': + if (cp == pp) /* end of headers, gobble it */ + goto eoh; + switch (i = getc (in)) { + default: /* end of line */ + case '\n': /* end of headers, save for next call */ + ungetc (i, in); + goto eol; + + case ' ': /* continue headers */ + case '\t': + *cp++ = '\n'; + break; + } /* fall into default case */ + + default: + *cp++ = i; + break; + } + if (cp >= ep) + if (!(dp = realloc (pp, (size_t) (len += BUFSIZ)))) { + free (pp); + pp = NULL; + return NOTOK; + } + else + cp += dp - pp, ep = (pp = cp) + len - 2; + } +} diff --git a/zotnet/mf/mf.h b/zotnet/mf/mf.h new file mode 100644 index 0000000..a4c81e8 --- /dev/null +++ b/zotnet/mf/mf.h @@ -0,0 +1,82 @@ + +/* + * mf.h -- include file for mailbox filters + * + * $Id$ + */ + +#include + +#ifndef TRUE +# define TRUE 1 +#endif + +#ifndef FALSE +# define FALSE 0 +#endif + +#ifndef NOTOK +# define NOTOK (-1) +#endif + +#ifndef OK +# define OK 0 +#endif + +#ifndef DONE +# define DONE 1 +#endif + +#define LINESIZ 512 + +#define MBXMODE 0600 +#define TMPMODE 0600 + +#define OWIDTH 75 /* length of a header line */ + +#define HFROM 1 /* header has From: component */ +#define HSNDR 2 /* header has Sender: component */ +#define HADDR 3 /* header has address component */ +#define HDATE 4 /* header has Date: component */ +#define HOTHR 5 /* header is unimportant */ + + +struct adrx { + char *text; + char *pers; + char *mbox; + char *host; + char *path; + char *grp; + int ingrp; + char *note; + char *err; +}; + + +/* + * Codes returned by uucp2mmdf(), mmdf2uucp() + */ + +#define MFOK 0 /* all went well */ + /* remaining codes must > DONE */ +#define MFPRM 2 /* bad parameter */ +#define MFSIO 3 /* stdio package went screwy */ +#define MFROM 4 /* from line was bad */ +#define MFHDR 5 /* headers were bad */ +#define MFTXT 6 /* text was bad */ +#define MFERR 7 /* I/O or system error */ +#define MFDLM 8 /* Bad delimiter in MMDF file */ + + +/* + * prototypes + */ +int isfrom(char *); +int lequal (char *, char *); +int mfgets (FILE *, char **); +char *legal_person (char *); +struct adrx *seekadrx (char *); +struct adrx *getadrx (char *); +struct adrx *uucpadrx (char *); + diff --git a/zotnet/mts/Makefile.in b/zotnet/mts/Makefile.in new file mode 100644 index 0000000..ab8ad65 --- /dev/null +++ b/zotnet/mts/Makefile.in @@ -0,0 +1,94 @@ +# +# Makefile for zotnet/mts subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +bindir = @bindir@ +libdir = @libdir@ +etcdir = @sysconfdir@ + +mailspool = @mailspool@ +sendmailpath = @sendmailpath@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +KRB4_INCLUDES = @KRB4_INCLUDES@ +HESIOD_INCLUDES = @HESIOD_INCLUDES@ +INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) $(KRB4_INCLUDES) $(HESIOD_INCLUDES) +CONFIGDEFS = -DNMHETCDIR='"$(etcdir)"' -DMAILSPOOL='"$(mailspool)"' -DSENDMAILPATH='"$(sendmailpath)"' + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) +COMPILE2 = $(CC) -c $(DEFS) $(CONFIGDEFS) $(INCLUDES) $(CFLAGS) + +SED = sed + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# header files +HDRS = mts.h + +# source files +SRCS = mts.c client.c + +# object files +OBJS = mts.o client.o + +# auxiliary files +AUX = Makefile.in + +# all files in this directory included in the distribution +DIST = $(HDRS) $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING AND INSTALLING ========== + +all: $(OBJS) + +mts.o: mts.c + $(COMPILE2) $< + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + +distclean: clean + rm -f Makefile + +realclean: distclean + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = zotnet/mts + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/zotnet/mts/client.c b/zotnet/mts/client.c new file mode 100644 index 0000000..6d86057 --- /dev/null +++ b/zotnet/mts/client.c @@ -0,0 +1,467 @@ + +/* + * client.c -- connect to a server + * + * $Id$ + */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#ifdef HESIOD +# include +#endif + +#ifdef KPOP +# include +# include +#endif /* KPOP */ + +#define TRUE 1 +#define FALSE 0 + +#define OOPS1 (-2) +#define OOPS2 (-3) + +#define MAXARGS 1000 +#define MAXNETS 5 +#define MAXHOSTS 25 + +extern int errno; + +struct addrent { + int a_addrtype; /* assumes AF_INET for inet_netof () */ + union { + int un_net; + char un_addr[14]; + } un; +}; + +#define a_net un.un_net +#define a_addr un.un_addr + +static struct addrent *n1, *n2; +static struct addrent nets[MAXNETS]; +static struct addrent *h1, *h2; +static struct addrent hosts[MAXHOSTS]; + +#ifdef KPOP +static CREDENTIALS cred; +static MSG_DAT msg_data; +static KTEXT ticket = (KTEXT) NULL; +static Key_schedule schedule; +static char *kservice; /* "pop" if using kpop */ +char krb_realm[REALM_SZ]; +char *PrincipalHostname(); +#endif /* KPOP */ + +#if defined(BIND) && !defined(h_addr) +# define h_addr h_addr_list[0] +#endif + +#define inaddr_copy(hp,sin) \ + memcpy(&((sin)->sin_addr), (hp)->h_addr, (hp)->h_length) + +/* + * static prototypes + */ +static int rcaux (struct servent *, struct hostent *, int, char *, int); +static int getport (int, int, char *, int); +static int inet (struct hostent *, int); +struct hostent *gethostbystring (char *s); + +/* client's own static version of several nmh subroutines */ +static char **client_brkstring (char *, char *, char *); +static int client_brkany (char, char *); +static char **client_copyip (char **, char **, int); +static char *client_getcpy (char *); + + +int +client (char *args, char *protocol, char *service, int rproto, + char *response, int len_response) +{ + int sd; + register char **ap; + char *arguments[MAXARGS]; + register struct hostent *hp; + register struct servent *sp; +#ifndef BIND + register struct netent *np; +#endif + +#ifdef KPOP + char *cp; + + kservice = service; + if (cp = strchr (service, '/')) { /* "pop/kpop" */ + *cp++ = '\0'; /* kservice = "pop" */ + service = cp; /* service = "kpop" */ + } else { + kservice = NULL; /* not using KERBEROS */ + } +#endif /* KPOP */ + + + if ((sp = getservbyname (service, protocol)) == NULL) { +#ifdef HESIOD + if ((sp = hes_getservbyname (service, protocol)) == NULL) { + snprintf (response, len_response, "%s/%s: unknown service", protocol, service); + return NOTOK; + } +#else + snprintf (response, len_response, "%s/%s: unknown service", protocol, service); + return NOTOK; +#endif + } + + ap = arguments; + if (args != NULL && *args != 0) { + ap = client_copyip (client_brkstring (client_getcpy (args), " ", "\n"), + ap, MAXARGS); + } else { + if (servers != NULL && *servers != 0) + ap = client_copyip (client_brkstring (client_getcpy (servers), " ", "\n"), + ap, MAXARGS); + } + if (ap == arguments) { + *ap++ = client_getcpy ("localhost"); + *ap = NULL; + } + + n1 = nets; + n2 = nets + sizeof(nets) / sizeof(nets[0]); + + h1 = hosts; + h2 = hosts + sizeof(hosts) / sizeof(hosts[0]); + + for (ap = arguments; *ap; ap++) { + if (**ap == '\01') { +#ifndef BIND + if ((np = getnetbyname (*ap + 1))) { + sethostent (1); + while ((hp = gethostent())) + if (np->n_addrtype == hp->h_addrtype + && inet (hp, np->n_net)) { + switch (sd = rcaux (sp, hp, rproto, response, len_response)) { + case NOTOK: + continue; + case OOPS1: + break; + case OOPS2: + return NOTOK; + + default: + return sd; + } + break; + } + } +#endif + continue; + } + + if ((hp = gethostbystring (*ap))) { + switch (sd = rcaux (sp, hp, rproto, response, len_response)) { + case NOTOK: + case OOPS1: + break; + case OOPS2: + return NOTOK; + + default: + return sd; + } + continue; + } + } + + strncpy (response, "no servers available", len_response); + return NOTOK; +} + + +static int +rcaux (struct servent *sp, struct hostent *hp, int rproto, + char *response, int len_response) +{ + int sd; + struct in_addr in; + register struct addrent *ap; + struct sockaddr_in in_socket; + register struct sockaddr_in *isock = &in_socket; + +#ifdef KPOP + int rem; +#endif /* KPOP */ + + for (ap = nets; ap < n1; ap++) + if (ap->a_addrtype == hp->h_addrtype && inet (hp, ap->a_net)) + return NOTOK; + + for (ap = hosts; ap < h1; ap++) + if (ap->a_addrtype == hp->h_addrtype + && memcmp(ap->a_addr, hp->h_addr, hp->h_length) == 0) + return NOTOK; + + if ((sd = getport (rproto, hp->h_addrtype, response, len_response)) == NOTOK) + return OOPS2; + + memset (isock, 0, sizeof(*isock)); + isock->sin_family = hp->h_addrtype; + inaddr_copy (hp, isock); + isock->sin_port = sp->s_port; + + if (connect (sd, (struct sockaddr *) isock, sizeof(*isock)) == NOTOK) + switch (errno) { + case ENETDOWN: + case ENETUNREACH: + close (sd); + if (n1 < n2) { + n1->a_addrtype = hp->h_addrtype; + memcpy(&in, hp->h_addr, sizeof(in)); + n1->a_net = inet_netof (in); + n1++; + } + return OOPS1; + + case ETIMEDOUT: + case ECONNREFUSED: + default: + close (sd); + if (h1 < h2) { + h1->a_addrtype = hp->h_addrtype; + memcpy(h1->a_addr, hp->h_addr, hp->h_length); + h1++; + } + return NOTOK; + } + +#ifdef KPOP + if (kservice) { /* "pop" */ + char *instance; + + if ((instance = strdup (hp->h_name)) == NULL) { + close (sd); + strncpy (response, "Out of memory.", len_response); + return OOPS2; + } + ticket = (KTEXT) malloc (sizeof(KTEXT_ST)); + rem = krb_sendauth (0L, sd, ticket, kservice, instance, + (char *) krb_realmofhost (instance), + (unsigned long) 0, &msg_data, &cred, schedule, + (struct sockaddr_in *) NULL, + (struct sockaddr_in *) NULL, + "KPOPV0.1"); + free (instance); + if (rem != KSUCCESS) { + close (sd); + strncpy (response, "Post office refused connection: ", len_response); + strncat (response, krb_err_txt[rem], len_response - strlen(response)); + return OOPS2; + } + } +#endif /* KPOP */ + + return sd; +} + + +static int +getport (int rproto, int addrtype, char *response, int len_response) +{ + int sd, port; + struct sockaddr_in in_socket, *isock; + + isock = &in_socket; + if (rproto && addrtype != AF_INET) { + snprintf (response, len_response, "reserved ports not supported for af=%d", addrtype); + errno = ENOPROTOOPT; + return NOTOK; + } + + if ((sd = socket (AF_INET, SOCK_STREAM, 0)) == NOTOK) { + char *s; + + if ((s = strerror (errno))) + snprintf (response, len_response, "unable to create socket: %s", s); + else + snprintf (response, len_response, "unable to create socket: unknown error"); + return NOTOK; + } +#ifdef KPOP + if (kservice) /* "pop" */ + return(sd); +#endif /* KPOP */ + if (!rproto) + return sd; + + memset(isock, 0, sizeof(*isock)); + isock->sin_family = addrtype; + for (port = IPPORT_RESERVED - 1;;) { + isock->sin_port = htons ((unsigned short) port); + if (bind (sd, (struct sockaddr *) isock, sizeof(*isock)) != NOTOK) + return sd; + + switch (errno) { + char *s; + + case EADDRINUSE: + case EADDRNOTAVAIL: + if (--port <= IPPORT_RESERVED / 2) { + strncpy (response, "ports available", len_response); + return NOTOK; + } + break; + + default: + if ((s = strerror (errno))) + snprintf (response, len_response, "unable to bind socket: %s", s); + else + snprintf (response, len_response, "unable to bind socket: unknown error"); + return NOTOK; + } + } +} + + +static int +inet (struct hostent *hp, int net) +{ + struct in_addr in; + + memcpy(&in, hp->h_addr, sizeof(in)); + return (inet_netof (in) == net); +} + + +/* + * taken from ISODE's compat/internet.c + */ + +static char *empty = NULL; + +#ifdef h_addr +static char *addrs[2] = { NULL }; +#endif + +struct hostent * +gethostbystring (char *s) +{ + register struct hostent *h; + static struct hostent hs; +#ifdef DG + static struct in_addr iaddr; +#else + static unsigned long iaddr; +#endif + + iaddr = inet_addr (s); +#ifdef DG + if (iaddr.s_addr == NOTOK && strcmp (s, "255.255.255.255")) +#else + if (((int) iaddr == NOTOK) && strcmp (s, "255.255.255.255")) +#endif + return gethostbyname (s); + + h = &hs; + h->h_name = s; + h->h_aliases = ∅ + h->h_addrtype = AF_INET; + h->h_length = sizeof(iaddr); +#ifdef h_addr + h->h_addr_list = addrs; + memset(addrs, 0, sizeof(addrs)); +#endif + h->h_addr = (char *) &iaddr; + + return h; +} + + +/* + * static copies of three nmh subroutines + */ + +static char *broken[MAXARGS + 1]; + +static char ** +client_brkstring (char *strg, char *brksep, char *brkterm) +{ + register int bi; + register char c, *sp; + + sp = strg; + + for (bi = 0; bi < MAXARGS; bi++) { + while (client_brkany (c = *sp, brksep)) + *sp++ = 0; + if (!c || client_brkany (c, brkterm)) { + *sp = 0; + broken[bi] = 0; + return broken; + } + + broken[bi] = sp; + while ((c = *++sp) && !client_brkany (c, brksep) && !client_brkany (c, brkterm)) + continue; + } + broken[MAXARGS] = 0; + + return broken; +} + + +/* + * returns 1 if chr in strg, 0 otherwise + */ +static int +client_brkany (char chr, char *strg) +{ + register char *sp; + + if (strg) + for (sp = strg; *sp; sp++) + if (chr == *sp) + return 1; + return 0; +} + + +/* + * copy a string array and return pointer to end + */ +static char ** +client_copyip (char **p, char **q, int len_q) +{ + while (*p && --len_q > 0) + *q++ = *p++; + + *q = NULL; + + return q; +} + + +static char * +client_getcpy (char *str) +{ + char *cp; + size_t len; + + len = strlen(str) + 1; + if (!(cp = malloc(len))) + return NULL; + + memcpy (cp, str, len); + return cp; +} + diff --git a/zotnet/mts/mts.c b/zotnet/mts/mts.c new file mode 100644 index 0000000..31d96bd --- /dev/null +++ b/zotnet/mts/mts.c @@ -0,0 +1,454 @@ + +/* + * mts.c -- definitions for the mail transport system + * + * $Id$ + */ + +#include + +#define nmhetcdir(file) NMHETCDIR#file + +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_UTSNAME_H +# include +#endif + +#define NOTOK (-1) +#define OK 0 + +extern int errno; + +/* + * static prototypes + */ +static char *tailor_value (char *); +static void getuserinfo (void); + +/* + * *mmdfldir and *uucpldir are the maildrop directories. If maildrops + * are kept in the user's home directory, then these should be empty + * strings. In this case, the appropriate ...lfil array should contain + * the name of the file in the user's home directory. Usually, this is + * something like ".mail". + */ + +/* + * nmh mail transport interface customization file + */ +static char *mtsconf = nmhetcdir(/mts.conf); + +static char *localname = ""; +static char *localdomain = ""; +static char *systemname = ""; + +char *mmdfldir = MAILSPOOL; +char *mmdflfil = ""; +char *uucpldir = "/usr/spool/mail"; +char *uucplfil = ""; + +char *mmdlm1 = "\001\001\001\001\n"; +char *mmdlm2 = "\001\001\001\001\n"; + +/* Cache the username and fullname of the user */ +static char username[BUFSIZ]; +static char fullname[BUFSIZ]; + +/* variables for username masquerading */ +static int MMailids = 0; +static char *mmailid = "0"; + + +/* + * MTS specific variables + */ +#if defined(SENDMTS) || defined(SMTPMTS) +char *hostable = nmhetcdir(/hosts); +char *sendmail = SENDMAILPATH; +#endif + +/* + * SMTP/POP stuff + */ +char *clientname = NULL; +char *servers = "localhost \01localnet"; +char *pophost = ""; + +/* + * BBoards-specific variables + */ +char *bb_domain = ""; + + +/* + * POP BBoards-specific variables + */ +#ifdef BPOP +char *popbbhost = ""; +char *popbbuser = ""; +char *popbblist = nmhetcdir(/hosts.popbb); +#endif /* BPOP */ + +/* + * Global MailDelivery file + */ +char *maildelivery = nmhetcdir(/maildelivery); + + +/* + * Aliasing Facility (doesn't belong here) + */ +int Everyone = NOTOK; +static char *everyone = "-1"; +char *NoShell = ""; + +/* + * Customize the MTS settings for nmh by adjusting + * the file mts.conf in the nmh etc directory. + */ + +struct bind { + char *keyword; + char **value; +}; + +static struct bind binds[] = { + { "localname", &localname }, + { "localdomain", &localdomain }, + { "systemname", &systemname }, + { "mmdfldir", &mmdfldir }, + { "mmdflfil", &mmdflfil }, + { "uucpldir", &uucpldir }, + { "uucplfil", &uucplfil }, + { "mmdelim1", &mmdlm1 }, + { "mmdelim2", &mmdlm2 }, + { "mmailid", &mmailid }, + +#if defined(SENDMTS) || defined(SMTPMTS) + { "hostable", &hostable }, +#endif + +#ifdef SENDMTS + { "sendmail", &sendmail }, +#endif + + { "clientname", &clientname }, + { "servers", &servers }, + { "pophost", &pophost }, + { "bbdomain", &bb_domain }, + +#ifdef BPOP + { "popbbhost", &popbbhost }, + { "popbbuser", &popbbuser }, + { "popbblist", &popbblist }, +#endif + +#ifdef NNTP + { "nntphost", &popbbhost }, +#endif + + { "maildelivery", &maildelivery }, + { "everyone", &everyone }, + { "noshell", &NoShell }, + { NULL, NULL } +}; + + +/* + * Read the configuration file for the nmh interface + * to the mail transport system (MTS). + */ + +void +mts_init (char *name) +{ + char *bp, *cp, buffer[BUFSIZ]; + struct bind *b; + FILE *fp; + static int inited = 0; + + if (inited++ || (fp = fopen (mtsconf, "r")) == NULL) + return; + + while (fgets (buffer, sizeof(buffer), fp)) { + if (!(cp = strchr(buffer, '\n'))) + break; + *cp = 0; + if (*buffer == '#' || *buffer == '\0') + continue; + if (!(bp = strchr(buffer, ':'))) + break; + *bp++ = 0; + while (isspace (*bp)) + *bp++ = 0; + + for (b = binds; b->keyword; b++) + if (!strcmp (buffer, b->keyword)) + break; + if (b->keyword && (cp = tailor_value (bp))) + *b->value = cp; + } + + fclose (fp); + MMailids = atoi (mmailid); + Everyone = atoi (everyone); +} + + +#define QUOTE '\\' + +/* + * Convert escaped values, malloc some new space, + * and copy string to malloc'ed memory. + */ + +static char * +tailor_value (char *s) +{ + int i, r; + char *bp; + char buffer[BUFSIZ]; + size_t len; + + for (bp = buffer; *s; bp++, s++) { + if (*s != QUOTE) { + *bp = *s; + } else { + switch (*++s) { + case 'b': *bp = '\b'; break; + case 'f': *bp = '\f'; break; + case 'n': *bp = '\n'; break; + case 't': *bp = '\t'; break; + + case 0: s--; + case QUOTE: + *bp = QUOTE; + break; + + default: + if (!isdigit (*s)) { + *bp++ = QUOTE; + *bp = *s; + } + r = *s != '0' ? 10 : 8; + for (i = 0; isdigit (*s); s++) + i = i * r + *s - '0'; + s--; + *bp = toascii (i); + break; + } + } + } + *bp = 0; + + len = strlen (buffer) + 1; + if ((bp = malloc (len))) + memcpy (bp, buffer, len); + + return bp; +} + +/* + * Get the fully qualified name of the local host. + */ + +char * +LocalName (void) +{ + static char buffer[BUFSIZ] = ""; + struct hostent *hp; + +#ifdef HAVE_UNAME + struct utsname name; +#endif + + /* check if we have cached the local name */ + if (buffer[0]) + return buffer; + + mts_init ("mts"); + + /* check if the mts.conf file specifies a "localname" */ + if (*localname) { + strncpy (buffer, localname, sizeof(buffer)); + } else { +#ifdef HAVE_UNAME + /* first get our local name */ + uname (&name); + strncpy (buffer, name.nodename, sizeof(buffer)); +#else + /* first get our local name */ + gethostname (buffer, sizeof(buffer)); +#endif +#ifndef BIND + sethostent (1); +#endif + /* now fully qualify our name */ + if ((hp = gethostbyname (buffer))) + strncpy (buffer, hp->h_name, sizeof(buffer)); + } + + /* + * If the mts.conf file specifies a "localdomain", + * we append that now. This should rarely be needed. + */ + if (*localdomain) { + strcat (buffer, "."); + strcat (buffer, localdomain); + } + + return buffer; +} + + +/* + * This is only for UUCP mail. It gets the hostname + * as part of the UUCP "domain". + */ + +char * +SystemName (void) +{ + static char buffer[BUFSIZ] = ""; + +#ifdef HAVE_UNAME + struct utsname name; +#endif + + /* check if we have cached the system name */ + if (buffer[0]) + return buffer; + + mts_init ("mts"); + + /* check if mts.conf file specifies a "systemname" */ + if (*systemname) { + strncpy (buffer, systemname, sizeof(buffer)); + return buffer; + } + +#ifdef HAVE_UNAME + uname (&name); + strncpy (buffer, name.nodename, sizeof(buffer)); +#else + gethostname (buffer, sizeof(buffer)); +#endif + + return buffer; +} + + +/* + * Get the username of current user + */ + +char * +getusername (void) +{ + if (username[0] == '\0') + getuserinfo(); + + return username; +} + + +/* + * Get full name of current user (typically from GECOS + * field of password file). + */ + +char * +getfullname (void) +{ + if (username[0] == '\0') + getuserinfo(); + + return fullname; +} + + +/* + * Find the user's username and full name, and cache them. + * It also handles mmailid processing (username masquerading) + */ + +static void +getuserinfo (void) +{ + register char *cp, *np; + register struct passwd *pw; + +#ifdef KPOP + uid_t uid; + + uid = getuid (); + if (uid == geteuid () && (cp = getenv ("USER")) != NULL + && (pw = getpwnam (cp)) != NULL) + strncpy (username, cp, sizeof(username)); + else if ((pw = getpwuid (uid)) == NULL + || pw->pw_name == NULL + || *pw->pw_name == '\0') { +#else /* KPOP */ + if ((pw = getpwuid (getuid ())) == NULL + || pw->pw_name == NULL + || *pw->pw_name == '\0') { +#endif /* KPOP */ + + strncpy (username, "unknown", sizeof(username)); + snprintf (fullname, sizeof(fullname), "The Unknown User-ID (%d)", + (int) getuid ()); + return; + } + + np = pw->pw_gecos; + + /* + * Do mmailid (username masquerading) processing. The GECOS + * field should have the form "Full Name ". + */ +#ifndef GCOS_HACK + for (cp = fullname; *np && *np != (MMailids ? '<' : ','); *cp++ = *np++) + continue; +#else + for (cp = fullname; *np && *np != (MMailids ? '<' : ','); ) { + if (*np == '&') { /* blech! */ + strcpy (cp, pw->pw_name); + *cp = toupper(*cp); + while (*cp) + cp++; + np++; + } else { + *cp++ = *np++; + } + } +#endif + + *cp = '\0'; + if (MMailids) { + if (*np) + np++; + for (cp = username; *np && *np != '>'; *cp++ = *np++) + continue; + *cp = '\0'; + } + if (MMailids == 0 || *np == '\0') + strncpy (username, pw->pw_name, sizeof(username)); + + if ((cp = getenv ("SIGNATURE")) && *cp) + strncpy (fullname, cp, sizeof(fullname)); + + if (strchr(fullname, '.')) { /* quote any .'s */ + char tmp[BUFSIZ]; + + /* should quote "'s too */ + snprintf (tmp, sizeof(tmp), "\"%s\"", fullname); + strncpy (fullname, tmp, sizeof(fullname)); + } + + return; +} diff --git a/zotnet/mts/mts.h b/zotnet/mts/mts.h new file mode 100644 index 0000000..66aa590 --- /dev/null +++ b/zotnet/mts/mts.h @@ -0,0 +1,82 @@ + +/* + * mts.h -- definitions for the mail system + * + * $Id$ + */ + +/* + * Local and UUCP Host Name + */ +char *LocalName(void); +char *SystemName(void); + +/* + * Mailboxes + */ +extern char *mmdfldir; +extern char *mmdflfil; +extern char *uucpldir; +extern char *uucplfil; + +#define MAILDIR (mmdfldir && *mmdfldir ? mmdfldir : getenv ("HOME")) +#define MAILFIL (mmdflfil && *mmdflfil ? mmdflfil : getusername ()) +#define UUCPDIR (uucpldir && *uucpldir ? uucpldir : getenv ("HOME")) +#define UUCPFIL (uucplfil && *uucplfil ? uucplfil : getusername ()) + +char *getusername(void); +char *getfullname(void); + +/* + * Separators + */ +extern char *mmdlm1; +extern char *mmdlm2; + +#define isdlm1(s) (strcmp (s, mmdlm1) == 0) +#define isdlm2(s) (strcmp (s, mmdlm2) == 0) + +/* + * Read mts.conf file + */ +void mts_init (char *); + +/* + * MTS specific variables + */ +#if defined(SENDMTS) || defined (SMTPMTS) +extern char *hostable; +extern char *sendmail; +#endif + +/* + * SMTP/POP stuff + */ +extern char *clientname; +extern char *servers; +extern char *pophost; + +/* + * BBoards-specific variables + */ +extern char *bb_domain; + +/* + * POP BBoards-specific variables + */ +#ifdef BPOP +extern char *popbbhost; +extern char *popbbuser; +extern char *popbblist; +#endif /* BPOP */ + +/* + * Global MailDelivery File + */ +extern char *maildelivery; + +/* + * Aliasing Facility (doesn't belong here) + */ +extern int Everyone; +extern char *NoShell; diff --git a/zotnet/tws/Makefile.in b/zotnet/tws/Makefile.in new file mode 100644 index 0000000..3f96711 --- /dev/null +++ b/zotnet/tws/Makefile.in @@ -0,0 +1,99 @@ +# +# Makefile for zotnet/tws subdirectory +# +# $Id$ +# + +SHELL = /bin/sh + +top_srcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +CC = @CC@ +CFLAGS = @CFLAGS@ +DEFS = @DEFS@ +INCLUDES = -I../.. -I$(srcdir) -I$(top_srcdir) + +COMPILE = $(CC) -c $(DEFS) $(INCLUDES) $(CFLAGS) + +AWK = @AWK@ +# LEX = @LEX@ +LEX = lex +SED = sed + +.SUFFIXES: +.SUFFIXES: .c .o + +.c.o: + $(COMPILE) $< + +# header files +HDRS = tws.h + +# source files +SRCS = dtime.c lexstring.c + +# object files +OBJS = dtimep.o dtime.o lexstring.o + +# auxiliary files +AUX = Makefile.in dtimep.lex lexedit.sed dtimep.c-lexed + +# all files in this directory included in the distribution +DIST = $(HDRS) $(SRCS) $(AUX) + +# ========= DEPENDENCIES FOR BUILDING ========== + +all: $(OBJS) + +# This will bomb if lex is really flex, so check +# file and use pre-generated version if necessary +dtimep.c: $(srcdir)/dtimep.lex $(srcdir)/lexedit.sed + $(LEX) -nt $(srcdir)/dtimep.lex | $(SED) -f $(srcdir)/lexedit.sed > $@ + -@len=`wc -l $@ | $(AWK) ' { print $$1 } '`; \ + if [ $$len -gt 500 ]; \ + then exit 0; \ + else \ + echo "LEX FAILED: using pre-lexed $@"; \ + cp $(srcdir)/$@-lexed $@; \ + fi + +# This needs to be generated by lex, not flex +dtimep.c-lexed: $(srcdir)/dtimep.lex $(srcdir)/lexedit.sed + $(LEX) -nt $(srcdir)/dtimep.lex | $(SED) -f $(srcdir)/lexedit.sed > $(srcdir)/$@ + +install: + +uninstall: + +# ========== DEPENDENCIES FOR CLEANUP ========== + +mostlyclean: + rm -f *.o *~ + +clean: mostlyclean + rm -f dtimep.c + +distclean: clean + rm -f Makefile + +realclean: distclean + rm -f dtimep.c-lexed + +superclean: realclean + +# ========== DEPENDENCIES FOR MAINTENANCE ========== + +subdir = zotnet/tws + +Makefile: Makefile.in ../../config.status + cd ../.. && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= ./config.status + +distdir = ../../`cat ../../distname`/$(subdir) +nmhdist: $(DIST) + @echo "Copying distribution files in $(subdir)" + @for file in $(DIST); do \ + cp -p $(srcdir)/$$file $(distdir); \ + done + diff --git a/zotnet/tws/dtime.c b/zotnet/tws/dtime.c new file mode 100644 index 0000000..7e791f3 --- /dev/null +++ b/zotnet/tws/dtime.c @@ -0,0 +1,508 @@ + +/* + * dtime.c -- time/date routines + * + * $Id$ + */ + +#include +#include + +#if !defined(HAVE_TM_GMTOFF) && !defined(HAVE_TZSET) +# include +#endif + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#if !defined(HAVE_TM_GMTOFF) && defined(HAVE_TZSET) +extern int daylight; +extern long timezone; +extern char *tzname[]; +#endif + +#ifndef abs +# define abs(a) (a >= 0 ? a : -a) +#endif + +/* + * The number of days in the year, accounting for leap years + */ +#define dysize(y) \ + (((y) % 4) ? 365 : (((y) % 100) ? 366 : (((y) % 400) ? 365 : 366))) + +char *tw_moty[] = { + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec", + NULL +}; + +char *tw_dotw[] = { + "Sun", "Mon", "Tue", + "Wed", "Thu", "Fri", + "Sat", NULL +}; + +char *tw_ldotw[] = { + "Sunday", "Monday", "Tuesday", + "Wednesday", "Thursday", "Friday", + "Saturday", NULL +}; + +struct zone { + char *std; + char *dst; + int shift; +}; + +static struct zone zones[] = { + { "GMT", "BST", 0 }, + { "EST", "EDT", -5 }, + { "CST", "CDT", -6 }, + { "MST", "MDT", -7 }, + { "PST", "PDT", -8 }, +#if 0 +/* RFC1123 specifies do not use military TZs */ + { "A", NULL, -1 }, + { "B", NULL, -2 }, + { "C", NULL, -3 }, + { "D", NULL, -4 }, + { "E", NULL, -5 }, + { "F", NULL, -6 }, + { "G", NULL, -7 }, + { "H", NULL, -8 }, + { "I", NULL, -9 }, + { "K", NULL, -10 }, + { "L", NULL, -11 }, + { "M", NULL, -12 }, + { "N", NULL, 1 }, +#ifndef HUJI + { "O", NULL, 2 }, +#else + { "JST", "JDT", 2 }, +#endif + { "P", NULL, 3 }, + { "Q", NULL, 4 }, + { "R", NULL, 5 }, + { "S", NULL, 6 }, + { "T", NULL, 7 }, + { "U", NULL, 8 }, + { "V", NULL, 9 }, + { "W", NULL, 10 }, + { "X", NULL, 11 }, + { "Y", NULL, 12 }, +#endif + { NULL, NULL, 0 } +}; + +static int dmsize[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + + +/* + * Get current time (adjusted for local time + * zone and daylight savings time) expressed + * as nmh "broken-down" time structure. + */ + +struct tws * +dlocaltimenow (void) +{ + time_t clock; + + time (&clock); + return dlocaltime (&clock); +} + + +/* + * Take clock value and return pointer to nmh time structure + * containing "broken-down" time. The time is adjusted for + * local time zone and daylight savings time. + */ + +struct tws * +dlocaltime (time_t *clock) +{ + static struct tws tw; + struct tm *tm; + +#if !defined(HAVE_TM_GMTOFF) && !defined(HAVE_TZSET) + struct timeb tb; +#endif + + if (!clock) + return NULL; + + tm = localtime (clock); + + tw.tw_sec = tm->tm_sec; + tw.tw_min = tm->tm_min; + tw.tw_hour = tm->tm_hour; + tw.tw_mday = tm->tm_mday; + tw.tw_mon = tm->tm_mon; + + /* + * tm_year is always "year - 1900". + * So we correct for this. + */ + tw.tw_year = tm->tm_year + 1900; + tw.tw_wday = tm->tm_wday; + tw.tw_yday = tm->tm_yday; + + tw.tw_flags = TW_NULL; + if (tm->tm_isdst) + tw.tw_flags |= TW_DST; + +#ifdef HAVE_TM_GMTOFF + tw.tw_zone = tm->tm_gmtoff / 60; + if (tm->tm_isdst) /* if DST is in effect */ + tw.tw_zone -= 60; /* reset to normal offset */ +#else +# ifdef HAVE_TZSET + tzset(); + tw.tw_zone = -(timezone / 60); +# else + ftime (&tb); + tw.tw_zone = -tb.timezone; +# endif +#endif + + tw.tw_flags &= ~TW_SDAY; + tw.tw_flags |= TW_SEXP; + tw.tw_flags &= ~TW_SZONE; + tw.tw_flags |= TW_SZEXP; + + tw.tw_clock = *clock; + + return (&tw); +} + + +/* + * Take clock value and return pointer to nmh time + * structure containing "broken-down" time. Time is + * expressed in UTC (Coordinated Universal Time). + */ + +struct tws * +dgmtime (time_t *clock) +{ + static struct tws tw; + struct tm *tm; + + if (!clock) + return NULL; + + tm = gmtime (clock); + + tw.tw_sec = tm->tm_sec; + tw.tw_min = tm->tm_min; + tw.tw_hour = tm->tm_hour; + tw.tw_mday = tm->tm_mday; + tw.tw_mon = tm->tm_mon; + + /* + * tm_year is always "year - 1900" + * So we correct for this. + */ + tw.tw_year = tm->tm_year + 1900; + tw.tw_wday = tm->tm_wday; + tw.tw_yday = tm->tm_yday; + + tw.tw_flags = TW_NULL; + if (tm->tm_isdst) + tw.tw_flags |= TW_DST; + + tw.tw_zone = 0; + + tw.tw_flags &= ~TW_SDAY; + tw.tw_flags |= TW_SEXP; + tw.tw_flags &= ~TW_SZONE; + tw.tw_flags |= TW_SZEXP; + + tw.tw_clock = *clock; + + return (&tw); +} + + +/* + * Using a nmh "broken-down" time structure, + * produce a 26-byte date/time string, such as + * + * Tue Jan 14 17:49:03 1992\n\0 + */ + +char * +dctime (struct tws *tw) +{ + static char buffer[25]; + + if (!tw) + return NULL; + + snprintf (buffer, sizeof(buffer), "%.3s %.3s %02d %02d:%02d:%02d %.4d\n", + tw_dotw[tw->tw_wday], tw_moty[tw->tw_mon], tw->tw_mday, + tw->tw_hour, tw->tw_min, tw->tw_sec, + tw->tw_year < 100 ? tw->tw_year + 1900 : tw->tw_year); + + return buffer; +} + + +/* + * Produce a date/time string of the form + * + * Mon, 16 Jun 1992 15:30:48 -700 (or) + * Mon, 16 Jun 1992 15:30:48 EDT + * + * for the current time, as specified by rfc822. + * The first form is required by rfc1123. + */ + +char * +dtimenow (int alpha_timezone) +{ + time_t clock; + + time (&clock); + return dtime (&clock, alpha_timezone); +} + + +/* + * Using a local calendar time value, produce + * a date/time string of the form + * + * Mon, 16 Jun 1992 15:30:48 -700 (or) + * Mon, 16 Jun 1992 15:30:48 EDT + * + * as specified by rfc822. The first form is required + * by rfc1123 for outgoing messages. + */ + +char * +dtime (time_t *clock, int alpha_timezone) +{ + if (alpha_timezone) + /* use alpha-numeric timezones */ + return dasctime (dlocaltime (clock), TW_NULL); + else + /* use numeric timezones */ + return dasctime (dlocaltime (clock), TW_ZONE); +} + + +/* + * Using a nmh "broken-down" time structure, produce + * a date/time string of the form + * + * Mon, 16 Jun 1992 15:30:48 -0700 + * + * as specified by rfc822 and rfc1123. + */ + +char * +dasctime (struct tws *tw, int flags) +{ + char buffer[80]; + static char result[80]; + + if (!tw) + return NULL; + + /* Display timezone if known */ + if ((tw->tw_flags & TW_SZONE) == TW_SZNIL) + result[0] = '\0'; + else + snprintf(result, sizeof(result), " %s", dtimezone(tw->tw_zone, tw->tw_flags | flags)); + + snprintf(buffer, sizeof(buffer), "%02d %s %0*d %02d:%02d:%02d%s", + tw->tw_mday, tw_moty[tw->tw_mon], + tw->tw_year < 100 ? 2 : 4, tw->tw_year, + tw->tw_hour, tw->tw_min, tw->tw_sec, result); + + if ((tw->tw_flags & TW_SDAY) == TW_SEXP) + snprintf (result, sizeof(result), "%s, %s", tw_dotw[tw->tw_wday], buffer); + else + if ((tw->tw_flags & TW_SDAY) == TW_SNIL) + strncpy (result, buffer, sizeof(result)); + else + snprintf (result, sizeof(result), "%s (%s)", buffer, tw_dotw[tw->tw_wday]); + + return result; +} + + +/* + * Get the timezone for given offset + */ + +char * +dtimezone (int offset, int flags) +{ + int hours, mins; + struct zone *z; + static char buffer[10]; + + if (offset < 0) { + mins = -((-offset) % 60); + hours = -((-offset) / 60); + } else { + mins = offset % 60; + hours = offset / 60; + } + + if (!(flags & TW_ZONE) && mins == 0) { +#if defined(HAVE_TZSET) && defined(HAVE_TZNAME) + tzset(); + return ((flags & TW_DST) ? tzname[1] : tzname[0]); +#else + for (z = zones; z->std; z++) + if (z->shift == hours) + return (z->dst && (flags & TW_DST) ? z->dst : z->std); +#endif + } + +#if defined(DSTXXX) + if (flags & TW_DST) + hours += 1; +#endif /* defined(DSTXXX) */ + snprintf (buffer, sizeof(buffer), "%s%02d%02d", + offset < 0 ? "-" : "+", abs (hours), abs (mins)); + return buffer; +} + + +/* + * Convert nmh time structure for local "broken-down" + * time to calendar time (clock value). This routine + * is based on the gtime() routine written by Steven Shafer + * at CMU. It was forwarded to MTR by Jay Lepreau at Utah-CS. + */ + +time_t +dmktime (struct tws *tw) +{ + int i, sec, min, hour, mday, mon, year; + time_t result; + + if (tw->tw_clock != 0) + return tw->tw_clock; + + if ((sec = tw->tw_sec) < 0 || sec > 61 + || (min = tw->tw_min) < 0 || min > 59 + || (hour = tw->tw_hour) < 0 || hour > 23 + || (mday = tw->tw_mday) < 1 || mday > 31 + || (mon = tw->tw_mon + 1) < 1 || mon > 12) + return (tw->tw_clock = (time_t) -1); + + year = tw->tw_year; + + result = 0; + if (year < 100) + year += 1900; + + for (i = 1970; i < year; i++) + result += dysize (i); + if (dysize (year) == 366 && mon >= 3) + result++; + while (--mon) + result += dmsize[mon - 1]; + result += mday - 1; + result = 24 * result + hour; + result = 60 * result + min; + result = 60 * result + sec; + result -= 60 * tw->tw_zone; + if (tw->tw_flags & TW_DST) + result -= 60 * 60; + + return (tw->tw_clock = result); +} + + +/* + * Simple calculation of day of the week. Algorithm + * used is Zeller's congruence. We assume that + * if tw->tw_year < 100, then the century = 19. + */ + +void +set_dotw (struct tws *tw) +{ + int month, day, year, century; + + month = tw->tw_mon - 1; + day = tw->tw_mday; + year = tw->tw_year % 100; + century = tw->tw_year < 100 ? 19 : tw->tw_year / 100; + + if (month <= 0) { + month += 12; + if (--year < 0) { + year += 100; + century--; + } + } + + tw->tw_wday = + ((26 * month - 2) / 10 + day + year + year / 4 + - 3 * century / 4 + 1) % 7; + + tw->tw_flags &= ~TW_SDAY, tw->tw_flags |= TW_SIMP; +} + + +/* + * Copy nmh time structure + */ + +void +twscopy (struct tws *tb, struct tws *tw) +{ + *tb = *tw; /* struct copy */ + +#if 0 + tb->tw_sec = tw->tw_sec; + tb->tw_min = tw->tw_min; + tb->tw_hour = tw->tw_hour; + tb->tw_mday = tw->tw_mday; + tb->tw_mon = tw->tw_mon; + tb->tw_year = tw->tw_year; + tb->tw_wday = tw->tw_wday; + tb->tw_yday = tw->tw_yday; + tb->tw_zone = tw->tw_zone; + tb->tw_clock = tw->tw_clock; + tb->tw_flags = tw->tw_flags; +#endif +} + + +/* + * Compare two nmh time structures + */ + +int +twsort (struct tws *tw1, struct tws *tw2) +{ + time_t c1, c2; + + if (tw1->tw_clock == 0) + dmktime (tw1); + if (tw2->tw_clock == 0) + dmktime (tw2); + + return ((c1 = tw1->tw_clock) > (c2 = tw2->tw_clock) ? 1 + : c1 == c2 ? 0 : -1); +} diff --git a/zotnet/tws/dtimep.c-lexed b/zotnet/tws/dtimep.c-lexed new file mode 100644 index 0000000..12a209b --- /dev/null +++ b/zotnet/tws/dtimep.c-lexed @@ -0,0 +1,1672 @@ +#include +static int start_cond = 0; +#define BEGIN start_cond = +struct yysvf { + struct yywork *yystoff; + struct yysvf *yyother; + int *yystops;}; +# define Z 2 +#include +#include +#if !defined(HAVE_TM_GMTOFF) && !defined(HAVE_TZSET) +# include +#endif + +#if !defined(HAVE_TM_GMTOFF) && defined(HAVE_TZSET) +extern int daylight; +extern long timezone; +extern char *tzname[]; +#endif + + +# line 49 "./dtimep.lex" +/* + * Patchable flag that says how to interpret NN/NN/NN dates. When + * true, we do it European style: DD/MM/YY. When false, we do it + * American style: MM/DD/YY. Of course, these are all non-RFC822 + * compliant. + */ +int europeandate = 0; + + +# line 57 "./dtimep.lex" +/* + * Table to convert month names to numeric month. We use the + * fact that the low order 5 bits of the sum of the 2nd & 3rd + * characters of the name is a hash with no collisions for the 12 + * valid month names. (The mask to 5 bits maps any combination of + * upper and lower case into the same hash value). + */ +static int month_map[] = { + 0, + 6, /* 1 - Jul */ + 3, /* 2 - Apr */ + 5, /* 3 - Jun */ + 0, + 10, /* 5 - Nov */ + 0, + 1, /* 7 - Feb */ + 11, /* 8 - Dec */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, /*15 - Jan */ + 0, + 0, + 0, + 2, /*19 - Mar */ + 0, + 8, /*21 - Sep */ + 0, + 9, /*23 - Oct */ + 0, + 0, + 4, /*26 - May */ + 0, + 7 /*28 - Aug */ +}; + +# line 95 "./dtimep.lex" +/* + * Same trick for day-of-week using the hash function + * (c1 & 7) + (c2 & 4) + */ +static int day_map[] = { + 0, + 0, + 0, + 6, /* 3 - Sat */ + 4, /* 4 - Thu */ + 0, + 5, /* 6 - Fri */ + 0, /* 7 - Sun */ + 2, /* 8 - Tue */ + 1 /* 9 - Mon */, + 0, + 3 /*11 - Wed */ +}; +#define SETDAY { tw.tw_wday= day_map[(cp[0] & 7) + (cp[1] & 4)];\ + tw.tw_flags &= ~TW_SDAY; tw.tw_flags |= TW_SEXP;\ + cp += 2; } +#define SETMONTH { tw.tw_mon = month_map[(cp[0] + cp[1]) & 0x1f]; gotdate++;\ + cp += 2;\ + SKIPD;} +#define CVT1OR2 (i=(*cp++ - '0'), isdigit(*cp)? i*10 + (*cp++ - '0') : i) +#define CVT2 ((cp[0] - '0')*10 + (cp[1] - '0')) +#define CVT4 ((((cp[0] - '0')*10 + (cp[1] - '0'))*10 + \ + (cp[2] - '0'))*10 + (cp[3] - '0')) +#define SKIPD { while ( !isdigit(*cp++) ) ; --cp; } +#define EXPZONE { tw.tw_flags &= ~TW_SZONE; tw.tw_flags |= TW_SZEXP; } +#define ZONE(x) { tw.tw_zone=(x); EXPZONE; } +#define ZONED(x) { ZONE(x); tw.tw_flags |= TW_DST; } +#define LC(c) (isupper (c) ? tolower (c) : (c)) + +#ifdef DSTXXX +# ifdef TIME_WITH_SYS_TIME +# include +# include +# else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +# endif + +static void +zonehack (struct tws *tw) +{ + register struct tm *tm; + + if (dmktime (tw) == (time_t) -1) + return; + + tm = localtime (&tw->tw_clock); + if (tm->tm_isdst) { + tw->tw_flags |= TW_DST; + tw->tw_zone -= 60; + } +} +#endif /* DSTXXX */ +struct tws * +dparsetime (char *str) +{ + register int i; + static struct tws tw; + register char *cp; + register int gotdate = 0; + time_t tclock; + +#ifdef HAVE_TM_GMTOFF + struct tm *tm; + time_t clock; +#else +# ifndef HAVE_TZSET + struct timeb tb; +# endif /* not HAVE_TZSET */ +#endif /* HAVE_TM_GMTOFF */ + + start_cond = 0; + + /* Zero out the struct. */ + memset( (char *) &tw, 0, sizeof(tw)); + + /* Set default time zone. */ +#ifdef HAVE_TM_GMTOFF + time (&clock); + tm = localtime(&clock); + tw.tw_zone = tm->tm_gmtoff / 60; + if (tm->tm_isdst) /* if DST is in effect */ + tw.tw_zone -= 60; /* reset to normal offset */ +#else +# ifdef HAVE_TZSET + tzset(); + tw.tw_zone = -(timezone / 60); +# else + ftime(&tb); + tw.tw_zone = -tb.timezone; +# endif /* HAVE_TZSET */ +#endif /* HAVE_TM_GMTOFF */ + + while (isspace(*str)) + str++; + while (1) + switch (cp = str, *cp ? lex_string( &str, start_cond) : 0) { + + case -1: + if (!gotdate || tw.tw_year == 0) + return (struct tws *)0; + /* fall through */ + case 0: + if (tw.tw_year == 0) { + /* Set default year. */ + time (&tclock); + tw.tw_year = localtime(&tclock)->tm_year + 1900; + } else if (tw.tw_year < 100) { + /* assume no 2-digit years > 1999 */ + tw.tw_year += 1900; + } + return &tw; + +#ifdef __cplusplus +/* to avoid CC and lint complaining yyfussy not being used ...*/ +static int __lex_hack = 0; +if (__lex_hack) goto yyfussy; +#endif +case 1: + +# line 219 "./dtimep.lex" + SETDAY; +break; +case 2: + +# line 220 "./dtimep.lex" + { + cp++; + SETDAY; + } +break; +case 3: + +# line 224 "./dtimep.lex" +{ + if (europeandate) { + /* European: DD/MM/YY */ + tw.tw_mday = CVT1OR2; + cp++; + tw.tw_mon = CVT1OR2 - 1; + } else { + /* American: MM/DD/YY */ + tw.tw_mon = CVT1OR2 - 1; + cp++; + tw.tw_mday = CVT1OR2; + } + cp++; + for (i = 0; isdigit(*cp); ) + i = i*10 + (*cp++ - '0'); + tw.tw_year = i; + gotdate++; /* XXX */ + } +break; +case 4: + +# line 242 "./dtimep.lex" + { + if (europeandate) { + tw.tw_mday = CVT1OR2; cp++; + tw.tw_mon = CVT1OR2 - 1; + } else { + tw.tw_mon = CVT1OR2 - 1; cp++; + tw.tw_mday = CVT1OR2; + } + gotdate++; + } +break; +case 5: + +# line 252 "./dtimep.lex" +{ + tw.tw_mday = CVT1OR2; + while ( !isalpha(*cp++) ) + ; + SETMONTH; + for (i = 0; isdigit(*cp); ) + i = i*10 + (*cp++ - '0'); + tw.tw_year = i; + } +break; +case 6: + +# line 261 "./dtimep.lex" + { + tw.tw_mday = CVT1OR2; + while ( ! isalpha( *cp++ ) ) + ; + SETMONTH; + } +break; +case 7: + +# line 267 "./dtimep.lex" +{ + cp++; + SETMONTH; + tw.tw_mday = CVT1OR2; + SKIPD; + for (i = 0; isdigit(*cp); ) + i = i*10 + (*cp++ - '0'); + tw.tw_year = i; + } +break; +case 8: + +# line 276 "./dtimep.lex" + { + cp++; + SETMONTH; + tw.tw_mday = CVT1OR2; + } +break; +case 9: + +# line 282 "./dtimep.lex" + { /* hack: ctime w/o TZ */ + tw.tw_hour = CVT1OR2; cp++; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + SKIPD; + tw.tw_year = CVT4; cp+=4; + } +break; +case 10: + +# line 289 "./dtimep.lex" + { + tw.tw_hour = CVT1OR2; cp++; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + BEGIN Z; + } +break; +case 11: + +# line 295 "./dtimep.lex" + { + tw.tw_hour = CVT1OR2; cp++; + tw.tw_min = CVT1OR2; + BEGIN Z; + } +break; +case 12: + +# line 300 "./dtimep.lex" + { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour == 12) + tw.tw_hour = 0; + tw.tw_min = CVT1OR2; + BEGIN Z; + } +break; +case 13: + +# line 307 "./dtimep.lex" + { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour == 12) + tw.tw_hour = 0; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + BEGIN Z; + } +break; +case 14: + +# line 315 "./dtimep.lex" + { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour != 12) + tw.tw_hour += 12; + tw.tw_min = CVT1OR2; + BEGIN Z; + } +break; +case 15: + +# line 322 "./dtimep.lex" + { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour != 12) + tw.tw_hour += 12; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + BEGIN Z; + } +break; +case 16: + +# line 330 "./dtimep.lex" + { + tw.tw_hour = CVT2; cp+=2; + tw.tw_min = CVT2; cp+=2; + tw.tw_sec = CVT2; cp+=2; + BEGIN Z; + } +break; +case 17: + +# line 336 "./dtimep.lex" + { + /* + * Luckly, 4 digit times in the range + * 1960-1999 aren't legal as hour + * and minutes. + */ + tw.tw_year = CVT4; cp+=4; + } +break; +case 18: + +# line 344 "./dtimep.lex" + { + if (tw.tw_hour || tw.tw_min + || tw.tw_sec) { + tw.tw_year = CVT4; cp+=4; + tw.tw_zone = 0; + } else { + tw.tw_hour = CVT2; cp+=2; + tw.tw_min = CVT2; cp+=2; + BEGIN Z; + } + } +break; +case 19: + +# line 355 "./dtimep.lex" + ZONE(0 * 60); +break; +case 20: + +# line 356 "./dtimep.lex" + ZONE(0 * 60); +break; +case 21: + +# line 357 "./dtimep.lex" + ZONE(2 * 60); +break; +case 22: + +# line 358 "./dtimep.lex" + ZONED(2 * 60); +break; +case 23: + +# line 359 "./dtimep.lex" + ZONE(-5 * 60); +break; +case 24: + +# line 360 "./dtimep.lex" + ZONED(-5 * 60); +break; +case 25: + +# line 361 "./dtimep.lex" + ZONE(-6 * 60); +break; +case 26: + +# line 362 "./dtimep.lex" + ZONED(-6 * 60); +break; +case 27: + +# line 363 "./dtimep.lex" + ZONE(-7 * 60); +break; +case 28: + +# line 364 "./dtimep.lex" + ZONED(-7 * 60); +break; +case 29: + +# line 365 "./dtimep.lex" + ZONE(-8 * 60); +break; +case 30: + +# line 366 "./dtimep.lex" + ZONED(-8 * 60); +break; +case 31: + +# line 367 "./dtimep.lex" + ZONE(-(3 * 60 + 30)); +break; +case 32: + +# line 368 "./dtimep.lex" + ZONE(-4 * 60); +break; +case 33: + +# line 369 "./dtimep.lex" + ZONED(-4 * 60); +break; +case 34: + +# line 370 "./dtimep.lex" + ZONE(-9 * 60); +break; +case 35: + +# line 371 "./dtimep.lex" + ZONED(-9 * 60); +break; +case 36: + +# line 372 "./dtimep.lex" + ZONE(-10 * 60); +break; +case 37: + +# line 373 "./dtimep.lex" + ZONED(-10 * 60); +break; +case 38: + +# line 374 "./dtimep.lex" + ZONED(-1 * 60); +break; +case 39: + +# line 375 "./dtimep.lex" + { + tw.tw_zone = 60 * (('a'-1) - LC(*cp)); + EXPZONE; + } +break; +case 40: + +# line 379 "./dtimep.lex" + { + tw.tw_zone = 60 * ('a' - LC(*cp)); + EXPZONE; + } +break; +case 41: + +# line 383 "./dtimep.lex" + { + tw.tw_zone = 60 * (LC(*cp) - 'm'); + EXPZONE; + } +break; +case 42: + +# line 387 "./dtimep.lex" + { + cp++; + tw.tw_zone = ((cp[0] * 10 + cp[1]) + -('0' * 10 + '0'))*60 + +((cp[2] * 10 + cp[3]) + -('0' * 10 + '0')); + EXPZONE; +#ifdef DSTXXX + zonehack (&tw); +#endif /* DSTXXX */ + cp += 4; + } +break; +case 43: + +# line 399 "./dtimep.lex" + { + cp++; + tw.tw_zone = (('0' * 10 + '0') + -(cp[0] * 10 + cp[1]))*60 + +(('0' * 10 + '0') + -(cp[2] * 10 + cp[3])); + EXPZONE; +#ifdef DSTXXX + zonehack (&tw); +#endif /* DSTXXX */ + cp += 4; + } +break; +case 44: + +# line 411 "./dtimep.lex" + { + SKIPD; + tw.tw_year = CVT4; cp+=4; + } +break; +case 45: + +# line 415 "./dtimep.lex" +case 46: + +# line 416 "./dtimep.lex" +; +break; + default: return(0); +} } +/* end of yylex */ +int yyvstop[] = { +0, + +46, +0, + +45, +0, + +46, +0, + +39, +0, + +39, +0, + +39, +0, + +39, +0, + +39, +0, + +39, +0, + +39, +0, + +39, +0, + +39, +0, + +40, +0, + +40, +0, + +41, +0, + +41, +0, + +41, +0, + +41, +0, + +41, +0, + +41, +0, + +41, +0, + +41, +0, + +41, +0, + +19, +0, + +4, +0, + +4, +0, + +11, +0, + +1, +0, + +1, +0, + +1, +0, + +1, +0, + +1, +0, + +1, +0, + +1, +0, + +33, +0, + +32, +0, + +38, +0, + +26, +0, + +25, +0, + +24, +0, + +23, +0, + +20, +0, + +37, +0, + +36, +0, + +22, +0, + +21, +0, + +28, +0, + +27, +0, + +31, +0, + +30, +0, + +29, +0, + +35, +0, + +34, +0, + +4, +0, + +4, +0, + +4, +0, + +18, +0, + +11, +0, + +11, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +6, +0, + +17, +18, +0, + +1, +0, + +2, +0, + +18, +0, + +10, +0, + +12, +0, + +14, +0, + +6, +0, + +17, +18, +0, + +8, +0, + +44, +0, + +42, +0, + +43, +0, + +2, +0, + +3, +0, + +16, +0, + +10, +0, + +10, +0, + +5, +0, + +8, +0, + +8, +0, + +1, +0, + +3, +0, + +3, +0, + +13, +0, + +15, +0, + +6, +0, + +5, +0, + +5, +0, + +5, +0, + +5, +0, + +7, +0, + +9, +0, + +7, +0, + +7, +0, +0}; +# define YYTYPE int +struct yywork { YYTYPE verify, advance; } yycrank[] = { +0,0, 0,0, 0,0, 0,0, +0,0, 0,0, 0,0, 0,0, +0,0, 0,0, 1,5, 1,6, +5,5, 0,0, 0,0, 0,0, +0,0, 0,0, 0,0, 0,0, +0,0, 0,0, 0,0, 0,0, +0,0, 0,0, 0,0, 0,0, +0,0, 0,0, 0,0, 0,0, +0,0, 1,5, 0,0, 5,5, +3,21, 3,6, 0,0, 0,0, +0,0, 1,7, 0,0, 0,0, +0,0, 0,0, 0,0, 0,0, +0,0, 1,8, 1,9, 1,8, +1,10, 1,10, 1,10, 1,10, +1,10, 1,10, 1,10, 3,21, +9,63, 22,83, 22,83, 0,0, +0,0, 0,0, 0,0, 3,7, +0,0, 0,0, 3,22, 0,0, +3,23, 0,0, 0,0, 3,8, +3,9, 3,8, 3,10, 3,10, +3,10, 3,10, 3,10, 3,10, +3,10, 10,64, 10,64, 10,64, +10,64, 10,64, 10,64, 10,64, +10,64, 10,64, 10,64, 0,0, +0,0, 0,0, 1,11, 15,72, +59,143, 1,12, 14,70, 1,13, +12,67, 13,68, 17,75, 1,14, +19,79, 20,81, 1,15, 1,16, +1,17, 15,73, 11,65, 16,74, +1,18, 1,19, 13,69, 11,66, +1,20, 19,80, 14,71, 25,99, +3,24, 3,25, 3,26, 3,27, +3,28, 3,29, 3,30, 3,31, +3,32, 3,33, 3,34, 3,34, +3,35, 3,36, 3,37, 3,38, +3,39, 3,39, 3,40, 3,41, +3,42, 3,39, 3,43, 3,39, +3,44, 7,45, 8,50, 18,76, +26,100, 28,102, 30,104, 18,77, +7,46, 24,97, 42,114, 45,117, +31,105, 21,21, 7,47, 7,48, +23,84, 23,84, 7,49, 26,101, +28,103, 24,65, 38,112, 18,78, +24,98, 8,50, 24,66, 31,106, +36,74, 46,118, 49,123, 56,139, +36,111, 57,140, 55,137, 60,144, +21,21, 38,113, 8,51, 55,138, +8,52, 8,53, 8,53, 8,53, +8,53, 8,53, 8,53, 8,53, +8,53, 8,53, 8,53, 8,54, +21,82, 21,82, 21,82, 21,82, +21,82, 21,82, 21,82, 21,82, +21,82, 21,82, 47,119, 61,145, +62,146, 23,85, 23,86, 23,87, +44,115, 23,88, 35,72, 23,89, +23,90, 35,109, 23,91, 50,50, +33,70, 23,92, 23,93, 33,107, +23,94, 58,141, 47,120, 44,116, +35,73, 23,95, 65,148, 48,121, +35,110, 23,96, 8,55, 51,124, +66,149, 8,56, 33,108, 8,57, +33,71, 67,150, 50,50, 8,58, +48,122, 58,142, 8,59, 8,60, +8,61, 68,151, 69,152, 70,153, +8,62, 73,158, 71,154, 50,124, +71,155, 74,159, 51,124, 52,134, +52,134, 52,134, 52,134, 52,134, +52,134, 52,134, 52,134, 52,134, +52,134, 75,160, 76,161, 77,162, +78,163, 79,164, 51,133, 51,133, +51,133, 51,133, 51,133, 51,133, +51,133, 51,133, 51,133, 51,133, +53,135, 53,135, 53,135, 53,135, +53,135, 53,135, 53,135, 53,135, +53,135, 53,135, 54,136, 54,136, +54,136, 54,136, 54,136, 54,136, +54,136, 54,136, 54,136, 54,136, +72,156, 80,165, 81,166, 50,125, +93,111, 85,97, 50,126, 72,157, +50,127, 97,170, 91,107, 92,109, +50,128, 64,50, 98,171, 50,129, +50,130, 50,131, 99,172, 51,55, +85,98, 50,132, 51,56, 100,173, +51,57, 91,108, 92,110, 101,174, +51,58, 102,175, 103,176, 51,59, +51,60, 51,61, 104,177, 105,178, +64,50, 51,62, 63,135, 63,135, +63,135, 63,135, 63,135, 63,135, +63,147, 63,147, 63,147, 63,147, +106,179, 64,51, 107,180, 64,52, +82,167, 82,167, 82,167, 82,167, +82,167, 82,167, 82,167, 82,167, +82,167, 82,167, 64,54, 83,168, +83,168, 83,168, 83,168, 83,168, +83,168, 83,168, 83,168, 83,168, +83,168, 84,169, 84,169, 84,169, +84,169, 84,169, 84,169, 84,169, +84,169, 84,169, 84,169, 108,181, +109,182, 110,183, 111,184, 112,185, +113,186, 115,187, 116,188, 117,189, +118,190, 119,191, 120,192, 121,193, +122,194, 123,195, 124,124, 126,198, +125,196, 64,55, 127,199, 128,200, +64,56, 125,197, 64,57, 129,202, +130,203, 131,204, 64,58, 132,205, +133,206, 64,59, 64,60, 64,61, +137,216, 138,217, 139,218, 64,62, +140,219, 124,124, 141,220, 128,201, +134,206, 135,210, 135,210, 135,210, +135,210, 135,210, 135,210, 135,210, +135,210, 135,210, 135,210, 133,206, +142,221, 143,223, 142,222, 144,225, +145,226, 146,227, 153,236, 155,157, +143,224, 158,238, 159,239, 134,206, +133,207, 160,240, 162,242, 133,208, +133,208, 133,208, 133,208, 133,208, +133,208, 133,208, 133,208, 133,208, +133,208, 148,229, 134,207, 134,209, +134,209, 134,209, 134,209, 134,209, +134,209, 134,209, 134,209, 134,209, +134,209, 136,211, 147,228, 147,228, +147,228, 147,228, 147,228, 147,228, +147,228, 147,228, 147,228, 147,228, +148,229, 149,229, 124,125, 150,229, +154,229, 124,126, 163,243, 124,127, +190,252, 192,254, 196,258, 124,128, +136,211, 191,250, 124,129, 124,130, +124,131, 151,229, 156,229, 152,234, +124,132, 157,229, 161,234, 164,234, +149,229, 165,234, 150,229, 154,229, +136,212, 136,212, 136,212, 136,212, +136,212, 136,212, 136,212, 136,212, +136,212, 136,212, 136,213, 166,234, +151,229, 156,229, 152,234, 194,250, +157,229, 161,234, 164,234, 189,250, +165,234, 195,250, 193,250, 197,259, +198,260, 199,261, 152,234, 200,262, +203,267, 161,234, 164,234, 201,263, +165,234, 201,264, 166,234, 167,247, +167,247, 167,247, 167,247, 167,247, +167,247, 167,247, 167,247, 167,247, +167,247, 148,230, 166,234, 204,268, +205,269, 136,214, 168,248, 168,248, +168,248, 168,248, 168,248, 168,248, +168,248, 168,248, 168,248, 168,248, +206,206, 191,253, 208,207, 209,207, +136,215, 212,213, 214,274, 150,232, +169,249, 169,249, 169,249, 169,249, +169,249, 169,249, 169,249, 169,249, +169,249, 169,249, 189,251, 202,265, +156,237, 149,231, 152,235, 206,206, +210,271, 211,211, 202,266, 215,275, +154,157, 194,256, 195,257, 220,283, +222,224, 225,285, 151,233, 193,255, +226,286, 227,287, 230,157, 231,290, +164,244, 232,291, 161,241, 165,245, +233,292, 235,293, 236,294, 210,271, +211,211, 237,157, 238,295, 239,296, +166,246, 207,270, 207,270, 207,270, +207,270, 207,270, 207,270, 207,270, +207,270, 207,270, 207,270, 210,272, +210,272, 210,272, 210,272, 210,272, +210,272, 210,272, 210,272, 210,272, +210,272, 213,273, 213,273, 213,273, +213,273, 213,273, 213,273, 213,273, +213,273, 213,273, 213,273, 240,297, +228,288, 234,234, 241,298, 242,299, +243,300, 244,301, 245,302, 216,276, +246,303, 247,304, 247,304, 247,304, +247,304, 247,304, 247,304, 247,304, +247,304, 247,304, 247,304, 250,307, +251,308, 252,309, 217,276, 228,288, +234,234, 253,310, 254,311, 255,312, +256,313, 211,214, 216,276, 257,314, +276,330, 258,266, 260,266, 279,224, +218,276, 265,266, 280,332, 281,333, +282,334, 283,335, 284,224, 216,277, +211,215, 217,276, 216,278, 216,278, +216,278, 216,278, 216,278, 216,278, +216,278, 216,278, 216,278, 216,278, +258,266, 260,266, 217,277, 218,276, +265,266, 217,278, 217,278, 217,278, +217,278, 217,278, 217,278, 217,278, +217,278, 217,278, 217,278, 219,276, +218,277, 259,266, 285,336, 218,278, +218,278, 218,278, 218,278, 218,278, +218,278, 218,278, 218,278, 218,278, +218,278, 264,266, 263,266, 286,337, +287,338, 290,157, 291,342, 292,343, +293,344, 294,345, 219,276, 296,346, +259,266, 221,276, 266,266, 271,271, +297,347, 274,274, 262,266, 216,279, +298,348, 299,349, 301,350, 219,277, +264,266, 263,266, 219,278, 219,278, +219,278, 219,278, 219,278, 219,278, +219,278, 219,278, 219,278, 219,278, +221,276, 266,266, 271,271, 223,276, +274,274, 262,266, 260,317, 265,320, +218,281, 258,315, 217,280, 261,266, +268,266, 221,277, 269,266, 275,275, +221,278, 221,278, 221,278, 221,278, +221,278, 221,278, 221,278, 221,278, +221,278, 221,278, 223,276, 302,351, +303,352, 224,276, 267,266, 288,288, +308,353, 310,354, 261,266, 268,266, +312,355, 269,266, 275,275, 223,277, +229,229, 313,356, 223,278, 223,278, +223,278, 223,278, 223,278, 223,278, +223,278, 223,278, 223,278, 223,278, +224,276, 267,266, 288,288, 314,357, +219,282, 264,266, 315,266, 316,358, +317,359, 259,316, 318,360, 229,229, +319,361, 224,277, 320,266, 321,362, +224,278, 224,278, 224,278, 224,278, +224,278, 224,278, 224,278, 224,278, +224,278, 224,278, 263,266, 229,289, +229,289, 229,289, 229,289, 229,289, +229,289, 229,289, 229,289, 229,289, +229,289, 221,224, 262,319, 322,363, +323,364, 223,284, 248,305, 248,305, +248,305, 248,305, 248,305, 248,305, +248,305, 248,305, 248,305, 248,305, +249,306, 249,306, 249,306, 249,306, +249,306, 249,306, 249,306, 249,306, +249,306, 249,306, 268,322, 328,368, +261,318, 329,369, 330,370, 332,224, +273,326, 269,323, 267,321, 270,324, +270,324, 270,324, 270,324, 270,324, +270,324, 270,324, 270,324, 270,324, +270,324, 272,325, 272,325, 272,325, +272,325, 272,325, 272,325, 272,325, +272,325, 272,325, 272,325, 273,326, +333,373, 334,374, 277,277, 278,331, +278,331, 278,331, 278,331, 278,331, +278,331, 278,331, 278,331, 278,331, +278,331, 324,365, 325,325, 273,327, +273,327, 273,327, 273,327, 273,327, +273,327, 273,327, 273,327, 273,327, +273,327, 277,277, 335,375, 336,376, +289,339, 337,377, 338,378, 341,340, +342,380, 343,381, 344,234, 345,157, +324,365, 325,325, 346,382, 347,157, +348,383, 277,278, 277,278, 277,278, +277,278, 277,278, 277,278, 277,278, +277,278, 277,278, 277,278, 289,339, +324,366, 324,366, 324,366, 324,366, +324,366, 324,366, 324,366, 324,366, +324,366, 324,366, 326,326, 289,340, +273,328, 327,326, 331,371, 289,341, +289,341, 289,341, 289,341, 289,341, +289,341, 289,341, 289,341, 289,341, +289,341, 339,339, 340,379, 273,329, +349,384, 350,385, 352,386, 353,250, +354,387, 326,326, 355,388, 357,389, +327,326, 331,371, 358,266, 359,390, +360,391, 361,392, 362,393, 363,394, +364,395, 365,365, 367,396, 373,399, +339,339, 340,379, 326,367, 374,400, +375,224, 331,372, 331,372, 331,372, +331,372, 331,372, 331,372, 331,372, +331,372, 331,372, 331,372, 368,368, +369,369, 370,370, 371,371, 376,401, +365,365, 366,365, 366,365, 366,365, +366,365, 366,365, 366,365, 366,365, +366,365, 366,365, 366,365, 377,224, +378,402, 384,404, 386,405, 389,406, +390,407, 391,408, 368,368, 369,369, +370,370, 371,371, 392,266, 379,379, +393,409, 394,266, 395,410, 397,412, +402,413, 410,415, 326,328, 398,371, +412,412, 327,328, 372,398, 372,398, +372,398, 372,398, 372,398, 372,398, +372,398, 372,398, 372,398, 372,398, +0,0, 326,329, 379,379, 0,0, +327,329, 396,411, 396,411, 396,411, +396,411, 0,0, 398,371, 412,412, +0,0, 0,0, 0,0, 414,417, +417,417, 0,0, 379,403, 379,403, +379,403, 379,403, 379,403, 379,403, +379,403, 379,403, 379,403, 379,403, +403,414, 403,414, 403,414, 403,414, +403,414, 403,414, 403,414, 403,414, +403,414, 403,414, 414,417, 417,417, +0,0, 0,0, 371,397, 411,416, +411,416, 411,416, 411,416, 411,416, +411,416, 411,416, 411,416, 411,416, +411,416, 0,0, 414,418, 414,418, +414,418, 414,418, 414,418, 414,418, +414,418, 414,418, 414,418, 414,418, +418,417, 418,417, 418,417, 418,417, +418,417, 418,417, 418,417, 418,417, +418,417, 418,417, 0,0, 0,0, +0,0}; +struct yysvf yysvec[] = { +0, 0, 0, +yycrank+1, 0, 0, +yycrank+0, yysvec+1, 0, +yycrank+27, 0, 0, +yycrank+0, yysvec+3, 0, +yycrank+3, 0, yyvstop+1, +yycrank+0, 0, yyvstop+3, +yycrank+47, 0, 0, +yycrank+141, 0, 0, +yycrank+3, yysvec+8, 0, +yycrank+37, yysvec+8, 0, +yycrank+2, 0, 0, +yycrank+3, 0, 0, +yycrank+4, 0, 0, +yycrank+5, 0, 0, +yycrank+2, 0, 0, +yycrank+4, 0, 0, +yycrank+7, 0, 0, +yycrank+54, 0, 0, +yycrank+4, 0, 0, +yycrank+8, 0, 0, +yycrank+152, 0, yyvstop+5, +yycrank+13, 0, 0, +yycrank+116, 0, 0, +yycrank+57, 0, yyvstop+7, +yycrank+8, 0, yyvstop+9, +yycrank+52, 0, yyvstop+11, +yycrank+0, yysvec+12, yyvstop+13, +yycrank+53, 0, yyvstop+15, +yycrank+0, yysvec+13, yyvstop+17, +yycrank+45, 0, yyvstop+19, +yycrank+60, 0, yyvstop+21, +yycrank+0, 0, yyvstop+23, +yycrank+127, 0, 0, +yycrank+0, 0, yyvstop+25, +yycrank+121, 0, yyvstop+27, +yycrank+65, 0, yyvstop+29, +yycrank+0, yysvec+17, yyvstop+31, +yycrank+70, 0, yyvstop+33, +yycrank+0, 0, yyvstop+35, +yycrank+0, yysvec+18, yyvstop+37, +yycrank+0, yysvec+19, yyvstop+39, +yycrank+42, 0, yyvstop+41, +yycrank+0, yysvec+20, yyvstop+43, +yycrank+116, 0, yyvstop+45, +yycrank+45, 0, 0, +yycrank+66, 0, 0, +yycrank+113, 0, 0, +yycrank+131, 0, 0, +yycrank+77, 0, 0, +yycrank+214, 0, 0, +yycrank+230, 0, 0, +yycrank+215, 0, 0, +yycrank+240, yysvec+8, 0, +yycrank+250, 0, 0, +yycrank+70, 0, 0, +yycrank+78, 0, 0, +yycrank+80, 0, 0, +yycrank+132, 0, 0, +yycrank+3, 0, 0, +yycrank+72, 0, 0, +yycrank+112, 0, 0, +yycrank+111, 0, 0, +yycrank+298, yysvec+8, 0, +yycrank+312, 0, 0, +yycrank+120, 0, 0, +yycrank+137, 0, 0, +yycrank+146, 0, 0, +yycrank+155, 0, 0, +yycrank+149, 0, 0, +yycrank+145, 0, 0, +yycrank+150, 0, 0, +yycrank+194, 0, 0, +yycrank+147, 0, 0, +yycrank+143, 0, 0, +yycrank+157, 0, 0, +yycrank+158, 0, 0, +yycrank+163, 0, 0, +yycrank+166, 0, 0, +yycrank+160, 0, 0, +yycrank+208, 0, 0, +yycrank+210, 0, 0, +yycrank+312, 0, 0, +yycrank+323, 0, 0, +yycrank+333, 0, 0, +yycrank+213, 0, 0, +yycrank+0, yysvec+25, 0, +yycrank+0, yysvec+26, 0, +yycrank+0, yysvec+28, 0, +yycrank+0, yysvec+30, 0, +yycrank+0, yysvec+31, 0, +yycrank+218, 0, 0, +yycrank+219, 0, 0, +yycrank+197, 0, 0, +yycrank+0, yysvec+38, 0, +yycrank+0, yysvec+42, 0, +yycrank+0, yysvec+44, 0, +yycrank+201, 0, 0, +yycrank+206, 0, 0, +yycrank+210, 0, 0, +yycrank+215, 0, 0, +yycrank+219, 0, 0, +yycrank+221, 0, 0, +yycrank+222, 0, 0, +yycrank+226, 0, 0, +yycrank+227, 0, 0, +yycrank+240, 0, 0, +yycrank+242, 0, 0, +yycrank+275, 0, 0, +yycrank+276, 0, 0, +yycrank+277, 0, 0, +yycrank+278, 0, 0, +yycrank+279, 0, 0, +yycrank+280, 0, 0, +yycrank+0, 0, yyvstop+47, +yycrank+281, 0, 0, +yycrank+282, 0, 0, +yycrank+294, 0, 0, +yycrank+290, 0, 0, +yycrank+285, 0, 0, +yycrank+292, 0, 0, +yycrank+286, 0, 0, +yycrank+303, 0, 0, +yycrank+305, 0, 0, +yycrank+397, 0, 0, +yycrank+296, 0, 0, +yycrank+306, 0, 0, +yycrank+309, 0, 0, +yycrank+314, 0, 0, +yycrank+318, 0, 0, +yycrank+305, 0, 0, +yycrank+318, 0, 0, +yycrank+318, 0, 0, +yycrank+411, 0, yyvstop+49, +yycrank+423, 0, yyvstop+51, +yycrank+385, 0, 0, +yycrank+472, 0, yyvstop+53, +yycrank+310, 0, 0, +yycrank+322, 0, 0, +yycrank+327, 0, 0, +yycrank+330, 0, 0, +yycrank+320, 0, 0, +yycrank+336, 0, 0, +yycrank+331, 0, 0, +yycrank+329, 0, 0, +yycrank+332, 0, 0, +yycrank+337, 0, 0, +yycrank+434, 0, 0, +yycrank+460, 0, 0, +yycrank+484, 0, 0, +yycrank+486, 0, 0, +yycrank+500, 0, 0, +yycrank+502, 0, yyvstop+55, +yycrank+333, yysvec+149, 0, +yycrank+487, 0, 0, +yycrank+350, yysvec+150, 0, +yycrank+501, 0, 0, +yycrank+504, 0, 0, +yycrank+353, yysvec+152, yyvstop+57, +yycrank+353, yysvec+150, 0, +yycrank+346, yysvec+157, 0, +yycrank+505, 0, yyvstop+59, +yycrank+342, yysvec+157, 0, +yycrank+398, yysvec+152, yyvstop+61, +yycrank+506, 0, yyvstop+63, +yycrank+508, 0, yyvstop+65, +yycrank+522, 0, yyvstop+67, +yycrank+507, 0, 0, +yycrank+522, 0, 0, +yycrank+540, 0, 0, +yycrank+0, 0, yyvstop+69, +yycrank+0, 0, yyvstop+71, +yycrank+0, 0, yyvstop+73, +yycrank+0, 0, yyvstop+75, +yycrank+0, 0, yyvstop+77, +yycrank+0, 0, yyvstop+79, +yycrank+0, 0, yyvstop+81, +yycrank+0, 0, yyvstop+83, +yycrank+0, 0, yyvstop+85, +yycrank+0, 0, yyvstop+87, +yycrank+0, 0, yyvstop+89, +yycrank+0, 0, yyvstop+91, +yycrank+0, 0, yyvstop+93, +yycrank+0, 0, yyvstop+95, +yycrank+0, 0, yyvstop+97, +yycrank+0, 0, yyvstop+99, +yycrank+0, 0, yyvstop+101, +yycrank+0, 0, yyvstop+103, +yycrank+0, 0, yyvstop+105, +yycrank+498, 0, 0, +yycrank+400, yysvec+189, 0, +yycrank+464, 0, 0, +yycrank+401, yysvec+189, 0, +yycrank+501, 0, 0, +yycrank+494, 0, 0, +yycrank+500, 0, 0, +yycrank+388, 0, 0, +yycrank+440, 0, 0, +yycrank+445, 0, 0, +yycrank+447, 0, 0, +yycrank+437, 0, 0, +yycrank+443, 0, 0, +yycrank+485, 0, 0, +yycrank+430, 0, 0, +yycrank+451, 0, 0, +yycrank+456, 0, 0, +yycrank+571, 0, yyvstop+107, +yycrank+585, 0, 0, +yycrank+537, yysvec+206, yyvstop+109, +yycrank+536, yysvec+206, yyvstop+111, +yycrank+595, 0, yyvstop+113, +yycrank+596, 0, yyvstop+115, +yycrank+527, yysvec+211, yyvstop+117, +yycrank+605, 0, 0, +yycrank+477, 0, 0, +yycrank+498, 0, 0, +yycrank+662, 0, yyvstop+119, +yycrank+677, 0, yyvstop+121, +yycrank+691, 0, yyvstop+123, +yycrank+726, 0, yyvstop+125, +yycrank+494, yysvec+217, yyvstop+127, +yycrank+752, 0, yyvstop+129, +yycrank+511, yysvec+218, yyvstop+131, +yycrank+778, 0, yyvstop+133, +yycrank+804, 0, yyvstop+135, +yycrank+512, yysvec+218, yyvstop+137, +yycrank+505, yysvec+224, yyvstop+139, +yycrank+501, yysvec+224, yyvstop+141, +yycrank+655, yysvec+210, yyvstop+143, +yycrank+815, 0, 0, +yycrank+510, 0, 0, +yycrank+504, 0, 0, +yycrank+512, 0, 0, +yycrank+507, 0, 0, +yycrank+656, 0, yyvstop+146, +yycrank+528, 0, 0, +yycrank+529, 0, 0, +yycrank+525, 0, 0, +yycrank+533, 0, 0, +yycrank+522, 0, 0, +yycrank+565, 0, 0, +yycrank+552, 0, 0, +yycrank+566, 0, 0, +yycrank+571, 0, 0, +yycrank+554, 0, 0, +yycrank+570, 0, 0, +yycrank+571, 0, 0, +yycrank+625, 0, 0, +yycrank+830, 0, 0, +yycrank+840, 0, 0, +yycrank+639, 0, yyvstop+148, +yycrank+587, 0, 0, +yycrank+588, 0, 0, +yycrank+575, 0, 0, +yycrank+593, 0, 0, +yycrank+576, 0, 0, +yycrank+592, 0, 0, +yycrank+594, 0, 0, +yycrank+688, yysvec+216, 0, +yycrank+728, yysvec+217, 0, +yycrank+689, yysvec+218, 0, +yycrank+786, yysvec+219, 0, +yycrank+757, yysvec+217, 0, +yycrank+741, yysvec+221, 0, +yycrank+740, yysvec+218, 0, +yycrank+692, yysvec+223, 0, +yycrank+753, yysvec+224, 0, +yycrank+805, yysvec+218, 0, +yycrank+787, yysvec+224, 0, +yycrank+789, yysvec+224, 0, +yycrank+859, 0, 0, +yycrank+754, 0, yyvstop+150, +yycrank+869, 0, 0, +yycrank+895, 0, yyvstop+152, +yycrank+756, 0, yyvstop+154, +yycrank+790, 0, yyvstop+156, +yycrank+599, yysvec+224, yyvstop+158, +yycrank+921, 0, 0, +yycrank+883, 0, 0, +yycrank+591, 0, 0, +yycrank+587, 0, 0, +yycrank+594, 0, 0, +yycrank+587, 0, 0, +yycrank+608, 0, 0, +yycrank+602, 0, 0, +yycrank+629, 0, 0, +yycrank+653, 0, 0, +yycrank+651, 0, 0, +yycrank+806, 0, yyvstop+160, +yycrank+947, 0, yyvstop+163, +yycrank+637, 0, 0, +yycrank+656, 0, 0, +yycrank+658, 0, 0, +yycrank+635, 0, 0, +yycrank+643, 0, 0, +yycrank+0, yysvec+293, 0, +yycrank+661, 0, 0, +yycrank+663, 0, 0, +yycrank+668, 0, 0, +yycrank+660, 0, 0, +yycrank+0, yysvec+293, 0, +yycrank+670, 0, 0, +yycrank+714, 0, 0, +yycrank+697, 0, 0, +yycrank+0, 0, yyvstop+165, +yycrank+0, 0, yyvstop+167, +yycrank+0, 0, yyvstop+169, +yycrank+0, 0, yyvstop+171, +yycrank+695, 0, 0, +yycrank+0, yysvec+308, 0, +yycrank+717, 0, 0, +yycrank+0, yysvec+308, 0, +yycrank+720, 0, 0, +yycrank+728, 0, 0, +yycrank+724, 0, 0, +yycrank+734, 0, 0, +yycrank+728, 0, 0, +yycrank+735, 0, 0, +yycrank+729, 0, 0, +yycrank+751, 0, 0, +yycrank+746, 0, 0, +yycrank+742, 0, 0, +yycrank+777, 0, 0, +yycrank+775, 0, 0, +yycrank+932, 0, yyvstop+173, +yycrank+933, 0, yyvstop+175, +yycrank+981, 0, yyvstop+177, +yycrank+984, 0, yyvstop+179, +yycrank+790, 0, 0, +yycrank+792, 0, 0, +yycrank+786, 0, 0, +yycrank+985, 0, yyvstop+181, +yycrank+787, 0, 0, +yycrank+830, 0, 0, +yycrank+832, 0, 0, +yycrank+840, 0, 0, +yycrank+857, 0, 0, +yycrank+856, 0, 0, +yycrank+849, 0, 0, +yycrank+996, 0, yyvstop+183, +yycrank+997, 0, 0, +yycrank+915, yysvec+339, yyvstop+185, +yycrank+859, 0, 0, +yycrank+847, 0, 0, +yycrank+918, yysvec+234, yyvstop+187, +yycrank+842, 0, 0, +yycrank+865, 0, 0, +yycrank+853, 0, 0, +yycrank+871, 0, 0, +yycrank+910, 0, 0, +yycrank+912, 0, 0, +yycrank+0, yysvec+293, 0, +yycrank+910, 0, 0, +yycrank+970, 0, 0, +yycrank+915, 0, 0, +yycrank+917, 0, 0, +yycrank+0, yysvec+308, 0, +yycrank+915, 0, 0, +yycrank+902, 0, 0, +yycrank+921, 0, 0, +yycrank+923, 0, 0, +yycrank+907, 0, 0, +yycrank+924, 0, 0, +yycrank+922, 0, 0, +yycrank+915, 0, 0, +yycrank+1016, 0, yyvstop+189, +yycrank+1001, yysvec+324, yyvstop+191, +yycrank+969, 0, 0, +yycrank+1034, 0, yyvstop+193, +yycrank+1035, 0, yyvstop+195, +yycrank+1036, 0, yyvstop+197, +yycrank+1037, 0, yyvstop+199, +yycrank+1034, yysvec+331, yyvstop+201, +yycrank+926, 0, 0, +yycrank+917, 0, 0, +yycrank+911, 0, 0, +yycrank+946, 0, 0, +yycrank+945, 0, 0, +yycrank+962, 0, 0, +yycrank+1062, 0, 0, +yycrank+0, yysvec+347, 0, +yycrank+0, yysvec+345, 0, +yycrank+0, yysvec+347, 0, +yycrank+0, yysvec+293, 0, +yycrank+960, 0, 0, +yycrank+0, yysvec+293, 0, +yycrank+965, 0, 0, +yycrank+0, yysvec+308, 0, +yycrank+0, yysvec+308, 0, +yycrank+966, 0, 0, +yycrank+963, 0, 0, +yycrank+951, 0, 0, +yycrank+949, 0, 0, +yycrank+971, 0, 0, +yycrank+959, 0, 0, +yycrank+976, 0, 0, +yycrank+1043, 0, 0, +yycrank+959, 0, 0, +yycrank+1070, 0, yyvstop+203, +yycrank+0, yysvec+377, 0, +yycrank+0, yysvec+375, 0, +yycrank+0, yysvec+377, 0, +yycrank+975, 0, 0, +yycrank+1072, 0, 0, +yycrank+0, yysvec+347, 0, +yycrank+0, yysvec+293, 0, +yycrank+0, yysvec+308, 0, +yycrank+0, yysvec+394, 0, +yycrank+0, yysvec+392, 0, +yycrank+0, yysvec+394, 0, +yycrank+976, 0, 0, +yycrank+1087, 0, 0, +yycrank+1071, 0, yyvstop+205, +yycrank+0, yysvec+377, 0, +yycrank+1098, 0, yyvstop+207, +yycrank+0, yysvec+394, 0, +yycrank+0, 0, yyvstop+209, +yycrank+1099, 0, yyvstop+211, +yycrank+1108, yysvec+414, yyvstop+213, +0, 0, 0}; +struct yywork *yytop = yycrank+1165; +struct yysvf *yybgin = yysvec+1; +char yymatch[] = { + 0, 1, 1, 1, 1, 1, 1, 1, + 1, 9, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 9, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 48, 48, 50, 51, 51, 51, 54, 54, + 54, 54, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 97, 97, 97, 97, 97, 97, 97, + 97, 97, 1, 107, 107, 107, 110, 110, +110, 110, 110, 110, 110, 110, 110, 110, +110, 110, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +0}; +char yyextra[] = { +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0}; +/* Copyright (c) 1989 AT&T */ +/* All Rights Reserved */ + +/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ +/* The copyright notice above does not evidence any */ +/* actual or intended publication of such source code. */ + +#pragma ident "@(#)ncform 6.8 95/02/11 SMI" + diff --git a/zotnet/tws/dtimep.lex b/zotnet/tws/dtimep.lex new file mode 100644 index 0000000..467dc85 --- /dev/null +++ b/zotnet/tws/dtimep.lex @@ -0,0 +1,417 @@ +%e 2000 +%p 5000 +%n 1000 +%a 4000 +%START Z +sun (sun(day)?) +mon (mon(day)?) +tue (tue(sday)?) +wed (wed(nesday)?) +thu (thu(rsday)?) +fri (fri(day)?) +sat (sat(urday)?) + +DAY ({sun}|{mon}|{tue}|{wed}|{thu}|{fri}|{sat}) + +jan (jan(uary)?) +feb (feb(ruary)?) +mar (mar(ch)?) +apr (apr(il)?) +may (may) +jun (jun(e)?) +jul (jul(y)?) +aug (aug(ust)?) +sep (sep(tember)?) +oct (oct(ober)?) +nov (nov(ember)?) +dec (dec(ember)?) + +MONTH ({jan}|{feb}|{mar}|{apr}|{may}|{jun}|{jul}|{aug}|{sep}|{oct}|{nov}|{dec}) + +w ([ \t]*) +W ([ \t]+) +D ([0-9]?[0-9]) +d [0-9] +%{ +#include +#include +#if !defined(HAVE_TM_GMTOFF) && !defined(HAVE_TZSET) +# include +#endif + +#if !defined(HAVE_TM_GMTOFF) && defined(HAVE_TZSET) +extern int daylight; +extern long timezone; +extern char *tzname[]; +#endif + +/* + * Patchable flag that says how to interpret NN/NN/NN dates. When + * true, we do it European style: DD/MM/YY. When false, we do it + * American style: MM/DD/YY. Of course, these are all non-RFC822 + * compliant. + */ +int europeandate = 0; + +/* + * Table to convert month names to numeric month. We use the + * fact that the low order 5 bits of the sum of the 2nd & 3rd + * characters of the name is a hash with no collisions for the 12 + * valid month names. (The mask to 5 bits maps any combination of + * upper and lower case into the same hash value). + */ +static int month_map[] = { + 0, + 6, /* 1 - Jul */ + 3, /* 2 - Apr */ + 5, /* 3 - Jun */ + 0, + 10, /* 5 - Nov */ + 0, + 1, /* 7 - Feb */ + 11, /* 8 - Dec */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, /*15 - Jan */ + 0, + 0, + 0, + 2, /*19 - Mar */ + 0, + 8, /*21 - Sep */ + 0, + 9, /*23 - Oct */ + 0, + 0, + 4, /*26 - May */ + 0, + 7 /*28 - Aug */ +}; +/* + * Same trick for day-of-week using the hash function + * (c1 & 7) + (c2 & 4) + */ +static int day_map[] = { + 0, + 0, + 0, + 6, /* 3 - Sat */ + 4, /* 4 - Thu */ + 0, + 5, /* 6 - Fri */ + 0, /* 7 - Sun */ + 2, /* 8 - Tue */ + 1 /* 9 - Mon */, + 0, + 3 /*11 - Wed */ +}; +#define SETDAY { tw.tw_wday= day_map[(cp[0] & 7) + (cp[1] & 4)];\ + tw.tw_flags &= ~TW_SDAY; tw.tw_flags |= TW_SEXP;\ + cp += 2; } +#define SETMONTH { tw.tw_mon = month_map[(cp[0] + cp[1]) & 0x1f]; gotdate++;\ + cp += 2;\ + SKIPD;} +#define CVT1OR2 (i=(*cp++ - '0'), isdigit(*cp)? i*10 + (*cp++ - '0') : i) +#define CVT2 ((cp[0] - '0')*10 + (cp[1] - '0')) +#define CVT4 ((((cp[0] - '0')*10 + (cp[1] - '0'))*10 + \ + (cp[2] - '0'))*10 + (cp[3] - '0')) +#define SKIPD { while ( !isdigit(*cp++) ) ; --cp; } +#define EXPZONE { tw.tw_flags &= ~TW_SZONE; tw.tw_flags |= TW_SZEXP; } +#define ZONE(x) { tw.tw_zone=(x); EXPZONE; } +#define ZONED(x) { ZONE(x); tw.tw_flags |= TW_DST; } +#define LC(c) (isupper (c) ? tolower (c) : (c)) + +#ifdef DSTXXX +# ifdef TIME_WITH_SYS_TIME +# include +# include +# else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +# endif + +static void +zonehack (struct tws *tw) +{ + register struct tm *tm; + + if (dmktime (tw) == (time_t) -1) + return; + + tm = localtime (&tw->tw_clock); + if (tm->tm_isdst) { + tw->tw_flags |= TW_DST; + tw->tw_zone -= 60; + } +} +#endif /* DSTXXX */ +%} +%% +%{ +struct tws * +dparsetime (char *str) +{ + register int i; + static struct tws tw; + register char *cp; + register int gotdate = 0; + time_t tclock; + +#ifdef HAVE_TM_GMTOFF + struct tm *tm; + time_t clock; +#else +# ifndef HAVE_TZSET + struct timeb tb; +# endif /* not HAVE_TZSET */ +#endif /* HAVE_TM_GMTOFF */ + + start_cond = 0; + + /* Zero out the struct. */ + memset( (char *) &tw, 0, sizeof(tw)); + + /* Set default time zone. */ +#ifdef HAVE_TM_GMTOFF + time (&clock); + tm = localtime(&clock); + tw.tw_zone = tm->tm_gmtoff / 60; + if (tm->tm_isdst) /* if DST is in effect */ + tw.tw_zone -= 60; /* reset to normal offset */ +#else +# ifdef HAVE_TZSET + tzset(); + tw.tw_zone = -(timezone / 60); +# else + ftime(&tb); + tw.tw_zone = -tb.timezone; +# endif /* HAVE_TZSET */ +#endif /* HAVE_TM_GMTOFF */ + + while (isspace(*str)) + str++; + while (1) + switch (cp = str, *cp ? lex_string( &str, start_cond) : 0) { + + case -1: + if (!gotdate || tw.tw_year == 0) + return (struct tws *)0; + /* fall through */ + case 0: + if (tw.tw_year == 0) { + /* Set default year. */ + time (&tclock); + tw.tw_year = localtime(&tclock)->tm_year + 1900; + } else if (tw.tw_year < 100) { + /* assume no 2-digit years > 1999 */ + tw.tw_year += 1900; + } + return &tw; + +%} +{DAY}","?{w} SETDAY; +"("{DAY}")"(","?) { + cp++; + SETDAY; + } +{D}(("-"{D}"-")|("/"{D}"/")){D}?{d}{d}{w} { + if (europeandate) { + /* European: DD/MM/YY */ + tw.tw_mday = CVT1OR2; + cp++; + tw.tw_mon = CVT1OR2 - 1; + } else { + /* American: MM/DD/YY */ + tw.tw_mon = CVT1OR2 - 1; + cp++; + tw.tw_mday = CVT1OR2; + } + cp++; + for (i = 0; isdigit(*cp); ) + i = i*10 + (*cp++ - '0'); + tw.tw_year = i; + gotdate++; /* XXX */ + } +{D}("/"|"-"){D}{w} { + if (europeandate) { + tw.tw_mday = CVT1OR2; cp++; + tw.tw_mon = CVT1OR2 - 1; + } else { + tw.tw_mon = CVT1OR2 - 1; cp++; + tw.tw_mday = CVT1OR2; + } + gotdate++; + } +{D}{w}(-)?{w}{MONTH}{w}(-)?{w}{D}?{d}{d}({W}at)?{w} { + tw.tw_mday = CVT1OR2; + while ( !isalpha(*cp++) ) + ; + SETMONTH; + for (i = 0; isdigit(*cp); ) + i = i*10 + (*cp++ - '0'); + tw.tw_year = i; + } +{D}"-"?{MONTH}({W}at)?{w} { + tw.tw_mday = CVT1OR2; + while ( ! isalpha( *cp++ ) ) + ; + SETMONTH; + } +{MONTH}{W}{D}","{W}{D}?{d}{d}{w} { + cp++; + SETMONTH; + tw.tw_mday = CVT1OR2; + SKIPD; + for (i = 0; isdigit(*cp); ) + i = i*10 + (*cp++ - '0'); + tw.tw_year = i; + } +{MONTH}{W}{D}{w} { + cp++; + SETMONTH; + tw.tw_mday = CVT1OR2; + } + +{D}:{D}:{D}{W}19[6-9]{d} { /* hack: ctime w/o TZ */ + tw.tw_hour = CVT1OR2; cp++; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + SKIPD; + tw.tw_year = CVT4; cp+=4; + } +{D}:{D}:{D}{w} { + tw.tw_hour = CVT1OR2; cp++; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + BEGIN Z; + } +{D}:{D}{w} { + tw.tw_hour = CVT1OR2; cp++; + tw.tw_min = CVT1OR2; + BEGIN Z; + } +{D}:{D}{w}am{w} { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour == 12) + tw.tw_hour = 0; + tw.tw_min = CVT1OR2; + BEGIN Z; + } +{D}:{D}:{D}{w}am{w} { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour == 12) + tw.tw_hour = 0; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + BEGIN Z; + } +{D}:{D}{w}pm{w} { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour != 12) + tw.tw_hour += 12; + tw.tw_min = CVT1OR2; + BEGIN Z; + } +{D}:{D}:{D}{w}pm{w} { + tw.tw_hour = CVT1OR2; cp++; + if (tw.tw_hour != 12) + tw.tw_hour += 12; + tw.tw_min = CVT1OR2; cp++; + tw.tw_sec = CVT1OR2; + BEGIN Z; + } +[0-2]{d}{d}{d}{d}{d}{w} { + tw.tw_hour = CVT2; cp+=2; + tw.tw_min = CVT2; cp+=2; + tw.tw_sec = CVT2; cp+=2; + BEGIN Z; + } +19[6-9]{d}{w} { + /* + * Luckly, 4 digit times in the range + * 1960-1999 aren't legal as hour + * and minutes. + */ + tw.tw_year = CVT4; cp+=4; + } +[0-2]{d}{d}{d}{w} { + if (tw.tw_hour || tw.tw_min + || tw.tw_sec) { + tw.tw_year = CVT4; cp+=4; + tw.tw_zone = 0; + } else { + tw.tw_hour = CVT2; cp+=2; + tw.tw_min = CVT2; cp+=2; + BEGIN Z; + } + } +"-"?ut ZONE(0 * 60); +"-"?gmt ZONE(0 * 60); +"-"?jst ZONE(2 * 60); +"-"?jdt ZONED(2 * 60); +"-"?est ZONE(-5 * 60); +"-"?edt ZONED(-5 * 60); +"-"?cst ZONE(-6 * 60); +"-"?cdt ZONED(-6 * 60); +"-"?mst ZONE(-7 * 60); +"-"?mdt ZONED(-7 * 60); +"-"?pst ZONE(-8 * 60); +"-"?pdt ZONED(-8 * 60); +"-"?nst ZONE(-(3 * 60 + 30)); +"-"?ast ZONE(-4 * 60); +"-"?adt ZONED(-4 * 60); +"-"?yst ZONE(-9 * 60); +"-"?ydt ZONED(-9 * 60); +"-"?hst ZONE(-10 * 60); +"-"?hdt ZONED(-10 * 60); +"-"?bst ZONED(-1 * 60); +[a-i] { + tw.tw_zone = 60 * (('a'-1) - LC(*cp)); + EXPZONE; + } +[k-m] { + tw.tw_zone = 60 * ('a' - LC(*cp)); + EXPZONE; + } +[n-y] { + tw.tw_zone = 60 * (LC(*cp) - 'm'); + EXPZONE; + } +"+"[0-1]{d}{d}{d} { + cp++; + tw.tw_zone = ((cp[0] * 10 + cp[1]) + -('0' * 10 + '0'))*60 + +((cp[2] * 10 + cp[3]) + -('0' * 10 + '0')); + EXPZONE; +#ifdef DSTXXX + zonehack (&tw); +#endif /* DSTXXX */ + cp += 4; + } +"-"[0-1]{d}{d}{d} { + cp++; + tw.tw_zone = (('0' * 10 + '0') + -(cp[0] * 10 + cp[1]))*60 + +(('0' * 10 + '0') + -(cp[2] * 10 + cp[3])); + EXPZONE; +#ifdef DSTXXX + zonehack (&tw); +#endif /* DSTXXX */ + cp += 4; + } +{W}{d}{d}{d}{d} { + SKIPD; + tw.tw_year = CVT4; cp+=4; + } +\n | +{W} ; +%% diff --git a/zotnet/tws/lexedit.sed b/zotnet/tws/lexedit.sed new file mode 100644 index 0000000..601df0f --- /dev/null +++ b/zotnet/tws/lexedit.sed @@ -0,0 +1,20 @@ +2,/^extern int yylineno;$/c\ +static int start_cond = 0;\ +#define BEGIN start_cond = +/^struct yysvf \*yyestate;$/,/^extern struct yysvf yysvec/d +/^# define YYNEWLINE /,/^[ ]*int nstr;/d +/^[ ]*while((nstr = yylook()/,/^[ ]*if(yywrap()) /d +/^case -1:$/,/^} return(0); }/c\ + default: return(0);\ +} } +/^struct yysvf *yybgin = yysvec+1;$/d +/^int yylineno /,$d +/^# define YYTYPE short/c\ +# define YYTYPE int +/^unsigned char yymatch\[\] = {/c\ +char yymatch[] = { +/^unsigned char yyextra\[\] = {/c\ +char yyextra[] = { +/^# define YYTYPE unsigned short/c\ +# define YYTYPE int +/^if (__once_yylex) {$/,/if(yymbcurmax<=0) yymbcurmax=MB_CUR_MAX;$/d diff --git a/zotnet/tws/lexstring.c b/zotnet/tws/lexstring.c new file mode 100644 index 0000000..91506e7 --- /dev/null +++ b/zotnet/tws/lexstring.c @@ -0,0 +1,257 @@ + +/* + * lexstring.c + * + * $Id$ + */ + +#define ONECASE 1 + +#include +#include + +#define YYLERR yysvec +#define YYTYPE int +#define YYLMAX 256 + +struct yysvf { +#ifndef hpux + struct yywork *yystoff; +#else /* hpux */ + int yystoff; +#endif /* hpux */ + struct yysvf *yyother; + int *yystops; +}; + +struct yywork { + YYTYPE verify; + YYTYPE advance; +}; + +extern int yyvstop[]; +extern struct yywork yycrank[]; +extern struct yysvf yysvec[]; +extern char yymatch[]; +extern char yyextra[]; + +#ifdef LEXDEBUG +static int debug = 0; +#endif /* LEXDEBUG */ + +#ifdef ONECASE +static char case_map[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; +#endif /* ONECASE */ + + +int +lex_string (char **strptr, int start_cond) +{ + struct yysvf *state, **lsp; + struct yywork *tran; + int statenum; + int ch; + char *cp = *strptr; + int *found; + struct yysvf *yylstate[YYLMAX]; + + /* start off machines */ + lsp = yylstate; + statenum = 1 + start_cond; + state = yysvec + statenum; + for (;;) { +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr,"%d ",statenum - 1); + } +#endif +#ifndef hpux + tran = state->yystoff; +#else /* hpux */ + tran = &yycrank[state->yystoff]; +#endif /* hpux */ + if(tran == yycrank) + /* may not be any transitions */ + if (state->yyother == 0 || +#ifndef hpux + state->yyother->yystoff == yycrank) +#else /* hpux */ + state->yyother->yystoff == 0) +#endif /* hpux */ + break; + +#ifdef ONECASE + ch = case_map[(unsigned char) *cp++]; +#else /* not ONECASE */ + ch = *cp++; +#endif /* ONECASE */ +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr,"("); + allprint(ch); + fprintf(stderr, ")"); + } +#endif +tryagain: +#ifndef hpux + if ( tran > yycrank){ +#else /* hpux */ + if ( (int)tran > (int)yycrank){ +#endif /* hpux */ + tran += ch; + if (tran->verify == statenum){ + if ((statenum = tran->advance) == 0){ + /* error transitions */ + --cp; + break; + } + state = statenum + yysvec; + *lsp++ = state; + goto contin; + } + +#ifndef hpux + } else if(tran < yycrank) { +#else /* hpux */ + } else if( (int)tran < (int)yycrank) { +#endif /* hpux */ + tran = yycrank+(yycrank-tran) + ch; +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr," compressed"); + } +#endif + if (tran->verify == statenum){ + if ((statenum = tran->advance) == 0) + /* error transitions */ + break; + + state = statenum + yysvec; + *lsp++ = state; + goto contin; + } + tran += (yymatch[ch] - ch); +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr,"(fb "); + allprint(yymatch[ch]); + fprintf(stderr,")"); + } +#endif + if (tran->verify == statenum){ + if((statenum = tran->advance) == 0) + /* error transition */ + break; + + state = statenum + yysvec; + *lsp++ = state; + goto contin; + } + } + if ((state = state->yyother) && +#ifndef hpux + (tran = state->yystoff) != yycrank){ +#else /* hpux */ + (tran = &yycrank[state->yystoff]) != yycrank){ +#endif /* hpux */ + statenum = state - yysvec; +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr,"fb %d", statenum - 1); + } +#endif + goto tryagain; + } else + break; + +contin: +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr,">"); + } +#endif + ; + } +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr,"\nStopped in state %d (",*(lsp-1)-yysvec-1); + allprint(ch); + fprintf(stderr, ") "); + } +#endif + while (lsp-- > yylstate){ + if (*lsp != 0 && (found= (*lsp)->yystops) && *found > 0){ + if(yyextra[*found]){ + /* must backup */ + ch = -*found; + do { + while (*found && *found++ != ch) + ; + } while (lsp > yylstate && + (found = (*--lsp)->yystops)); + } +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr," Match \""); + for ( cp = *strptr; + cp <= ((*strptr)+(lsp-yylstate)); + cp++) + allprint( *cp ); + fprintf(stderr,"\" action %d\n",*found); + } +#endif + *strptr += (lsp - yylstate + 1); + return(*found); + } + } + /* the string didn't match anything - if we're looking at + * eos, just return 0. Otherwise, bump the string pointer + * and return -1. + */ +#ifdef LEXDEBUG + if (debug) { + fprintf(stderr," No match\n"); + } +#endif /* LEXDEBUG */ + if ( **strptr ) { + (*strptr)++; + return (-1); + } + return (0); +} + +#ifdef LEXDEBUG +void +allprint(char c) +{ + if ( c < 32 ) { + putc( '^', stderr ); + c += 32; + } else if ( c == 127 ) { + putc( '^', stderr ); + c = '?'; + } + putc( c, stderr ); +} +#endif /* LEXDEBUG */ diff --git a/zotnet/tws/tws.h b/zotnet/tws/tws.h new file mode 100644 index 0000000..29702db --- /dev/null +++ b/zotnet/tws/tws.h @@ -0,0 +1,62 @@ + +/* + * tws.h + * + * $Id$ + */ + +/* DST vs. GMT nonsense */ +#define DSTXXX + +struct tws { + int tw_sec; /* seconds after the minute - [0, 61] */ + int tw_min; /* minutes after the hour - [0, 59] */ + int tw_hour; /* hour since midnight - [0, 23] */ + int tw_mday; /* day of the month - [1, 31] */ + int tw_mon; /* months since January - [0, 11] */ + int tw_year; /* 4 digit year (ie, 1997) */ + int tw_wday; /* days since Sunday - [0, 6] */ + int tw_yday; /* days since January 1 - [0, 365] */ + int tw_zone; + time_t tw_clock; /* if != 0, corresponding calendar value */ + int tw_flags; +}; + +#define TW_NULL 0x0000 + +#define TW_SDAY 0x0003 /* how day-of-week was determined */ +#define TW_SNIL 0x0000 /* not given */ +#define TW_SEXP 0x0001 /* explicitly given */ +#define TW_SIMP 0x0002 /* implicitly given */ + +#define TW_SZONE 0x0004 /* how timezone was determined */ +#define TW_SZNIL 0x0000 /* not given */ +#define TW_SZEXP 0x0004 /* explicitly given */ + +#define TW_DST 0x0010 /* daylight savings time */ +#define TW_ZONE 0x0020 /* use numeric timezones only */ + +#define dtwszone(tw) dtimezone (tw->tw_zone, tw->tw_flags) + +extern char *tw_dotw[]; +extern char *tw_ldotw[]; +extern char *tw_moty[]; + +/* + * prototypes + */ +char *dtime (time_t *, int); +char *dtimenow (int); +char *dctime (struct tws *); +struct tws *dlocaltimenow (void); +struct tws *dlocaltime (time_t *); +struct tws *dgmtime (time_t *); +char *dasctime (struct tws *, int); +char *dtimezone (int, int); +void twscopy (struct tws *, struct tws *); +int twsort (struct tws *, struct tws *); +time_t dmktime (struct tws *); +void set_dotw (struct tws *); + +struct tws *dparsetime (char *); + -- 1.7.10.4