From: Doug Morris Date: Fri, 30 Apr 1999 18:08:34 +0000 (+0000) Subject: Initial revision X-Git-Tag: nmh-1_0~199 X-Git-Url: http://git.marmaro.de/?p=mmh;a=commitdiff_plain;h=1691e80890e5d8ba258c51c214a3e91880e1db2b Initial revision --- 1691e80890e5d8ba258c51c214a3e91880e1db2b 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 *); +